Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,13 @@ across the whole thing, honestly*.
the project-built checks once *per* sub-package — correctness/lint (cluster A),
dependency-health + `circular-deps` (cluster B), and `unused-code` + `coverage`
(cluster C) — records namespaced per package (`backend/npm-audit`) with finding
paths re-prefixed so the by-file/focus join stays coherent (Phase 2). A single
package / declared workspace is unchanged (byte-identical). Declared workspaces
(npm/pnpm/yarn/nx/turbo/lerna) are healthy and don't alarm.
paths re-prefixed so the by-file/focus join stays coherent (Phase 2). The
scc-measured **codebase statistics** recover too: each sub-package's totals are
re-aggregated from the *one* cached `scc --by-file` walk, sliced to its subtree
(reuse, never re-walk), labelled per package (`api/codebase-stats`) — Phase 2b,
first increment. A single package / declared workspace is unchanged
(byte-identical). Declared workspaces (npm/pnpm/yarn/nx/turbo/lerna) are healthy
and don't alarm.
- **`lizard`** as a true multi-language complexity + duplication engine, and a
**Focus Areas** view synthesising the forensic axes (#36–#38).
- **Command profiles** + a **`.checkup.yml`** override layer (stack / checks /
Expand Down
56 changes: 35 additions & 21 deletions bin/checkup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1758,27 +1758,41 @@ else
echo -e "${YELLOW}⚠️ scc produced no usable output${NC}"
write_failed "codebase-stats" "scc returned no usable --by-file JSON" "$SCC_INTENT"
else
STATS_BREAKDOWN=$(scc_breakdown "$SCC_KEEP_JSON" < "$SCC_BYFILE")
# "FILES CODE COMPLEXITY", reconstructed from the breakdown (faithful Σ).
read -r TOTAL_FILES TOTAL_CODE COMPLEXITY <<< "$(scc_breakdown_total "$STATS_BREAKDOWN")"
SCC_TOP_LANGS=$(scc_breakdown_toplangs "$STATS_BREAKDOWN" 3)
[ -z "$SCC_TOP_LANGS" ] && SCC_TOP_LANGS="n/a"

# Console table from the first-party breakdown (replaces scc's whole-tree
# tabular output, which would contradict the first-party totals).
printf "%-24s %8s %12s %12s\n" "Language" "Files" "Code" "Complexity"
echo "------------------------------------------------------------"
echo "$STATS_BREAKDOWN" | jq -r '.[] | [.Name, .Count, .Code, .Complexity] | @tsv' \
| awk -F'\t' '{ printf "%-24s %8s %12s %12s\n", $1, $2, $3, $4 }'
echo ""
echo -e "${BLUE}📈 Summary:${NC} ${TOTAL_CODE:-0} lines of code across ${TOTAL_FILES:-0} files (first-party)"
echo -e " Top: ${SCC_TOP_LANGS} | Complexity: ${COMPLEXITY:-0}"

# Standardised parsed JSON for the tool-agnostic markdown writer.
write_parsed "codebase-stats" "pass" "${TOTAL_FILES:-0}" \
"${TOTAL_CODE:-0} lines across ${TOTAL_FILES:-0} files, first-party (top: ${SCC_TOP_LANGS})" \
'[]' \
"$SCC_INTENT"
# Per-package recovery (#78 increment 2): on an undeclared fan-out, measure
# EACH assessment root off the ONE cached --by-file walk — slice the keep-set
# to the sub-package subtree (reuse, never re-walk) and emit a per-package
# record via SLUG_NS. Single package / declared workspace →
# TOPO_ASSESSMENT_ROOTS is ("."), the slice is the full keep-set, SLUG_NS
# stays empty → record + console output are byte-identical to before (gate).
for TOPO_ROOT in "${TOPO_ASSESSMENT_ROOTS[@]}"; do
SLUG_NS=""; [ "$TOPO_ROOT" != "." ] && SLUG_NS="$TOPO_ROOT"
STATS_KEEP=$(scc_keep_for_root "$TOPO_ROOT")
STATS_BREAKDOWN=$(scc_breakdown "$STATS_KEEP" < "$SCC_BYFILE")
# "FILES CODE COMPLEXITY", reconstructed from the breakdown (faithful Σ).
read -r TOTAL_FILES TOTAL_CODE COMPLEXITY <<< "$(scc_breakdown_total "$STATS_BREAKDOWN")"
SCC_TOP_LANGS=$(scc_breakdown_toplangs "$STATS_BREAKDOWN" 3)
[ -z "$SCC_TOP_LANGS" ] && SCC_TOP_LANGS="n/a"

# On a fan-out, label which sub-package this table describes; a single
# package prints no header (byte-identical to before).
[ -n "$SLUG_NS" ] && echo -e "${BLUE}📦 ${TOPO_ROOT}/${NC}"
# Console table from the first-party breakdown (replaces scc's whole-tree
# tabular output, which would contradict the first-party totals).
printf "%-24s %8s %12s %12s\n" "Language" "Files" "Code" "Complexity"
echo "------------------------------------------------------------"
echo "$STATS_BREAKDOWN" | jq -r '.[] | [.Name, .Count, .Code, .Complexity] | @tsv' \
| awk -F'\t' '{ printf "%-24s %8s %12s %12s\n", $1, $2, $3, $4 }'
echo ""
echo -e "${BLUE}📈 Summary:${NC} ${TOTAL_CODE:-0} lines of code across ${TOTAL_FILES:-0} files (first-party)"
echo -e " Top: ${SCC_TOP_LANGS} | Complexity: ${COMPLEXITY:-0}"

# Standardised parsed JSON for the tool-agnostic markdown writer.
write_parsed "codebase-stats" "pass" "${TOTAL_FILES:-0}" \
"${TOTAL_CODE:-0} lines across ${TOTAL_FILES:-0} files, first-party (top: ${SCC_TOP_LANGS})" \
'[]' \
"$SCC_INTENT"
done
SLUG_NS=""
fi
fi
echo ""
Expand Down
14 changes: 9 additions & 5 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,11 +207,15 @@ resolve), and their records are namespaced — `.slug` carries `<pkg>/<check>` (
cross-tree by-file / focus join stays coherent, and the renderer maps each to its
pillar on the base slug. A single package / declared workspace runs the loop once
at `.` with no namespace — byte-identical to before. Recovered today: the
dependency-health (`npm-audit`, `deps-freshness`) and correctness/lint
(`typecheck`, `unit-tests`, `code-quality`, `type-aware-lint`, `build`) clusters.
The engine-routed / artifact / whole-tree checks (`complexity`, `duplication`,
`coverage`) stay root-scoped for now — recovering them per package (each needs its
own engine re-routing) is the remaining slice. Honesty survives recovery: a
dependency-health (`npm-audit`, `deps-freshness`, `circular-deps`), correctness/lint
(`typecheck`, `unit-tests`, `code-quality`, `type-aware-lint`, `build`) and
`unused-code` + `coverage` clusters (Phase 2/2b increment 1), plus the
scc-measured **`codebase-stats`** — re-aggregated per sub-package by slicing the
*one* cached `scc --by-file` walk to each root's subtree (`scc_keep_for_root`;
reuse, never re-walk), so no extra scc cost (Phase 2b increment 2). Still
root-scoped: the engine-routed `complexity` and `duplication`, which need their
engine (eslint / lizard / jscpd) re-routed per sub-package — the remaining slice.
Honesty survives recovery: a
sub-package whose toolchain can't run still `skip`s with a reason (never
green-by-default), the floor laid in #85.

Expand Down
24 changes: 24 additions & 0 deletions lib/scc-inventory.sh
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,27 @@ scc_perfile_findings() {
]
| sort_by( -.ccn, .file )'
}

# scc_keep_for_root <root> → prints the path to a keep-set file scoped to <root>.
# The per-package measurement recover pass (#78 increment 2): the keep-set
# ($SCC_KEEP_JSON) is a JSON array of TARGET-relative first-party paths, so a
# sub-package's slice is just the entries under "<root>/". The scc-based
# measurement arms then re-aggregate the ONE cached --by-file walk per
# sub-package (reuse, never re-walk — same seam as scc_breakdown).
# "." (single package / declared-workspace root) returns the FULL keep-set
# path UNCHANGED → byte-identical to the pre-#78 single-package behaviour (the
# acceptance gate). Other roots cache the slice at $RAW_DIR/scc-keep.<ns>.json
# (path separators in <ns> flattened to '_' for a safe filename). A leading
# "./" on either side is normalised away, mirroring scc-aggregate.jq, so the
# slice is robust to the keep-set's prefix style. Needs $SCC_KEEP_JSON + $RAW_DIR.
scc_keep_for_root() {
local root="$1"
if [ "$root" = "." ]; then
printf '%s' "$SCC_KEEP_JSON"
return 0
fi
local out="$RAW_DIR/scc-keep.${root//\//_}.json"
jq --arg p "${root#./}/" '[ .[] | select((sub("^\\./"; "")) | startswith($p)) ]' \
"$SCC_KEEP_JSON" > "$out" 2>/dev/null || printf '[]' > "$out"
printf '%s' "$out"
}
35 changes: 35 additions & 0 deletions test/scc-inventory.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,41 @@ assert_eq "stable under reorder" \
"$(scc_concentration "$CONC_KEEP" 25 < "$CONC_BYFILE" | jq -S -c '.')" \
"$(scc_concentration "$CONC_KEEP" 25 < "$TMP/conc-shuf.json" | jq -S -c '.')"

echo ""
echo "scc_keep_for_root: per-package slice of the keep-set (#78 increment 2)"
RAW_DIR="$TMP"
SCC_KEEP_JSON="$TMP/keep-slice.json"
echo '["backend/src/a.go","backend/cmd/main.go","frontend/src/app.ts","./frontend/src/util.ts","top.md"]' > "$SCC_KEEP_JSON"

# "." returns the SAME file path unchanged → the byte-identical single-package gate.
assert_eq "root '.' returns the full keep-set path unchanged" "$SCC_KEEP_JSON" "$(scc_keep_for_root .)"

BK=$(scc_keep_for_root backend)
assert_eq "backend slice keeps only backend/ files" \
'["backend/src/a.go","backend/cmd/main.go"]' "$(jq -c '.' "$BK")"
assert_eq "slice cached under RAW_DIR" "$TMP/scc-keep.backend.json" "$BK"

# The "./"-prefixed entry is normalised before matching but preserved verbatim in
# the slice (scc-aggregate.jq normalises Location at join time anyway).
FE=$(scc_keep_for_root frontend)
assert_eq "frontend slice keeps both (incl. ./-prefixed, verbatim)" \
'["frontend/src/app.ts","./frontend/src/util.ts"]' "$(jq -c '.' "$FE")"

assert_eq "no-match root → empty array" '[]' "$(jq -c '.' "$(scc_keep_for_root nope)")"

NESTED=$(scc_keep_for_root "packages/api")
assert_eq "nested root flattens '/' to '_' in the filename" \
"$TMP/scc-keep.packages_api.json" "$NESTED"

# Round-trip: a backend slice fed to scc_breakdown sums ONLY backend code.
echo '[{"Name":"Go","Files":[
{"Location":"backend/src/a.go","Code":30,"Complexity":3,"Lines":40},
{"Location":"backend/cmd/main.go","Code":20,"Complexity":2,"Lines":25}]},
{"Name":"TypeScript","Files":[
{"Location":"frontend/src/app.ts","Code":99,"Complexity":9,"Lines":120}]}]' > "$TMP/slice-byfile.json"
assert_eq "breakdown over backend slice sums only backend Go" "50" \
"$(scc_breakdown "$BK" < "$TMP/slice-byfile.json" | jq 'map(.Code)|add')"

echo ""
echo "Results: $PASS passed, $FAIL failed"
[ "$FAIL" -eq 0 ]