Skip to content

feat: write artifacts to .codemetrics/ and stop measuring our own output - #117

Merged
kenjudy merged 7 commits into
mainfrom
feat/codemetrics-output-dir
Aug 20, 2026
Merged

kenjudy merged 7 commits into
mainfrom
feat/codemetrics-output-dir

Conversation

@kenjudy

@kenjudy kenjudy commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Closes code-quality-metrics-w3wn (P1) and GitHub #112.

Two halves, and the second is the one that gets forgotten

Running the tool used to leave six files in the analysed repository root. Measured across the six repositories this toolkit evaluates: 235 untracked files, and only code-quality-metrics ignores them via local_.json and local_.html in its own .gitignore. The five targets had no protection, so every file was one git add . from being committed, and two of those are client repositories whose JSON carries author names, commit messages, prod_file_paths and model prose. Disclosure risk, not housekeeping.

Output now goes to /.codemetrics/, created when absent. The name pairs with the existing per-repo .codemetrics.json config file: one convention rather than two, and hidden by default in a repository someone else opens. The deliberate near-collision between a tracked .codemetrics.json file and an ignored .codemetrics/ directory is stated in the documentation rather than left to be discovered.

The second half: .codemetrics/** is now in BOTH CONFIG.DUPLICATE_IGNORE_PATTERNS and CONFIG.ANALYSIS_IGNORE_PATTERNS. Without it the second run measures the first run output. local_commit_metrics.json is an array of records sharing a schema, which is precisely the shape a clone detector reads as massive duplication, and local_drift_report.html is around 140KB including six base64 font blobs. Verified by unit test and by a live two-run smoke test.

Why a global default here, when data files stayed per-repo

Hand-curated data files are repo-specific: which files those are is a fact about the repository, so they belong in a per-repo config. The tool own output directory has the same name and shape in every repository it is ever pointed at, so requiring each target to declare it would repeat the same discovery forever and fail silently wherever nobody had hit the problem yet.

CLAUDE.md states ANALYSIS_IGNORE_PATTERNS is deliberately empty because seeding it would change every existing measurement including the calibration observations. This addition cannot do that, and the reasoning is recorded beside the entry: .codemetrics/ is a directory this tool only began writing with this change, strictly after every observation was recorded, so a pattern excluding it cannot have matched a file in any past run.

The provenance gate fired, and was handled by precedent rather than by weakening it

Seeding DUPLICATE_IGNORE_PATTERNS broke tests/thresholdProvenance.test.js, which exact-matches the recorded pattern list on every observation. The 43 include_in_derivation records were updated to the new default set and a config_change_log entry records why, mirroring the existing .terraform/ entry from code-quality-metrics-53j. The justification here is stronger than that precedent: .terraform/ needed a live GitHub trees API check to confirm the directory happened not to exist in the six reference repos, whereas .codemetrics/ non-existence is true by construction. No metric value was edited; the diff touches pattern lists and the log entry only.

Two decisions recorded rather than assumed

A legacy root-level file is refused, not read. generate-drift-report.js detects one a directory up from an absent .codemetrics/ and throws naming its exact path with an instruction to re-run, rather than silently rendering stale data. Given this project repeated trouble with stale inputs producing plausible output, failing loudly is the right default.

The backup--_ convention is declared an operator concern, not tool-owned. No code ever implemented it, confirmed by grep; only AGENTS.md cautionary note referenced it, so building retention logic would be new scope. The documentation now states that the tool overwrites in place, and that a backup kept inside .codemetrics/ inherits the new exclusions automatically while one kept elsewhere does not.

--output-dir is CLI-only with no .codemetrics.json key, on the same reasoning that keeps --max-commits CLI-only: where a run writes its output is a property of the run, not a fact about the repository.

Verification

729 tests across 43 suites after rebasing onto the content-duplicate work, lint and typecheck clean. Ten cycles with quoted RED; one is disclosed as vacuously green before implementation and meaningful only after, rather than presented as a clean cycle. Six pre-existing tests broke as a direct predicted consequence of the new ANALYSIS_IGNORE_PATTERNS default and were updated as stale assumptions, each named in the report.

🤖 Generated with Claude Code

kenjudy and others added 7 commits August 20, 2026 13:26
…ity-metrics-w3wn)

Part 2 of the .codemetrics/ output-directory change: without this, a run
following one whose output was ever committed would score its own prior
local_commit_metrics.json and local_drift_report.html as production code and
duplicated content. Added '**/.codemetrics/**' to both
CONFIG.DUPLICATE_IGNORE_PATTERNS and CONFIG.ANALYSIS_IGNORE_PATTERNS in
lib/config.js.

This is a documented, narrow exception to ANALYSIS_IGNORE_PATTERNS' empty
default: the directory did not exist in any repository's history before this
change, so seeding it is provably retroactively inert. Updated the 43
include_in_derivation calibration observations' recorded DUPLICATE_IGNORE_PATTERNS
to match (mechanical, following the existing '.terraform/' precedent), with a
new calibration/observations.json config_change_log entry recording why no
re-measurement is required. Updated CLAUDE.md's Configuration table row counts
and the tests that asserted the prior empty/11-pattern defaults.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Part of code-quality-metrics-w3wn's .codemetrics/ output-directory change.
CLI-only, no .codemetrics.json key, mirroring --max-commits' own reasoning:
where a run writes its output is a property of the run, not a fact about the
repository being measured.

parseCliArgs only; collectLocalMetrics does not yet read options.outputDir.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…rget root

Part 1 of code-quality-metrics-w3wn. Measured across the six repositories this
toolkit evaluates: writing local_*.json/local_*.html directly into the
analyzed repository's root left 235 untracked files behind, none protected by
that repository's own .gitignore, each one a git add . away from being
committed. .codemetrics/ pairs with the existing per-repo .codemetrics.json
config file and is hidden by default.

Created via fs.mkdirSync recursive when absent, left alone (and its prior
contents overwritten in place, unchanged from the tool's existing behavior)
when already present. --output-dir overrides the default location entirely.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Part of code-quality-metrics-w3wn's implementation hazard: local-code-metrics.js
and generate-drift-report.js are a write/read boundary across two separate
invocations, and a partial move of only the write side would make the later
read (and PR #94's counted_additions/counted_deletions guard) fail with a
confusing "stale file" message when the real fault is a path mismatch.

generate-drift-report.js's CLI now defaults its no-argument target directory
to <cwd>/.codemetrics, matching local-code-metrics.js's own new default. An
explicit directory argument still overrides this entirely, unchanged, for a
run whose local-code-metrics.js invocation used --output-dir.

Decision recorded: when .codemetrics/ is absent but a legacy root-level
local_metrics_summary.json or local_commit_metrics.json exists one directory
up, readRequiredJson refuses rather than reading it, naming the legacy file's
path and instructing a re-run of local-code-metrics.js. Given this project's
repeated trouble with stale inputs producing plausible output, silently
reading an old pre-move file would be exactly that failure mode.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…decision

CLAUDE.md gains an Output Location section: the near-collision between the
tracked .codemetrics.json config file and the ignored .codemetrics/ output
directory, the --output-dir flag, the legacy-root-level-file refusal, and why
the agent-invented backup-<timestamp>-<label>_<filename> convention is left
an operator/agent concern rather than a tool-owned feature (it would be a
materially larger feature than this issue's output-location move, and a
backup kept inside .codemetrics/ already inherits the new exclusion). Also
updates the Configuration table row counts' surrounding prose and the
Architecture section's local-code-metrics.js description.

README.md's tool descriptions, Drift Report section, new Output Location
section, and File Structure diagram now show .codemetrics/ instead of the
target repository's root.

AGENTS.md's "Running the Report Against an External Repository" section now
describes the .codemetrics/ default and points at CLAUDE.md's fuller
treatment of the backup-file decision, rather than leaving process.cwd() as
the stated (now-stale) output location.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds .codemetrics/ explicitly alongside the existing local_*.json/local_*.html
patterns. Those patterns already matched every generated file by name
regardless of directory, so this repository was never at risk, but the README
and CLAUDE.md now tell every other target repository to add .codemetrics/ to
their own .gitignore, and this project's own ignore file should show the
canonical pattern rather than rely on a filename coincidence.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Output Location section sits above the Configuration table, not below it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kenjudy
kenjudy merged commit 36e8779 into main Aug 20, 2026
1 check passed
@github-actions

Copy link
Copy Markdown

PR Analysis

Size: large (based on production code)
Production Code: 457 lines (8 files)
Test Code: 168 lines (6 files)
Total: 625 lines (14 files)
Test-to-Production Ratio: 0.37:1

Concerns

  • 2/7 commits exceed 100 production lines
  • 1/7 commits touch more than 5 files

Strengths

  • High test/prod co-change rate across commits

Commit Analysis

Total Commits: 7
Average Commit Size: 66 production lines
Average Files per Commit: 2.6

Metric Value
Large commits (>100 prod lines) 2/7 (29%)
Sprawling commits (>5 files) 1/7 (14%)
Test/prod co-change rate 4/7 (57%)
Message quality 7/7 (100%)
Median net additions ratio 0.74
Test-only commits 0
Production-only commits 3

Commit Details

7b1ad51 Ken Judy (8/20/2026)
feat: seed .codemetrics/ into both ignore-pattern defaults (code-quality-metrics-w3wn)
239 prod lines, 7 files [large, sprawling, test+prod]

55c6284 Ken Judy (8/20/2026)
feat: add --output-dir CLI flag to local-code-metrics.js
14 prod lines, 2 files [test+prod]

aa48257 Ken Judy (8/20/2026)
feat: write local-code-metrics.js output to .codemetrics/, not the target root
27 prod lines, 2 files [test+prod]

11153c5 Ken Judy (8/20/2026)
fix: resolve generate-drift-report.js's default target to .codemetrics/
26 prod lines, 2 files [test+prod]

6b8c655 Ken Judy (8/20/2026)
docs: describe the .codemetrics/ output location and the backup-file decision
144 prod lines, 3 files [large]

37493f2 Ken Judy (8/20/2026)
chore: add explicit .codemetrics/ entry to this project's own .gitignore
7 prod lines, 1 files

19d00d8 Ken Judy (8/20/2026)
docs: fix a below/above cross-reference in the CLAUDE.md config table
2 prod lines, 1 files

Test Coverage

Test-to-Production Ratio: 0.37:1 (no calibrated target for this ratio; reported descriptively)

DORA Capability Assessment

Archetype: mixed-signals
No clear archetype pattern. Review individual metric thresholds.

Capability Metric Value Target
Small Batches Large commit % 29% <18% (no critical band established)
Small Batches Sprawling commit % 14% <18% (no critical band established)
Version Control Test/prod co-change rate 57% >23% (no critical band established)
Version Control Message quality 100% no band, see spec
AI Risk Signal Net additions ratio (median) 0.74 no band, see spec

Duplicate Code

Found 37 duplicate block(s) across the changed files:

  • AGENTS.md:markdown:589-604 duplicates CLAUDE.md:markdown:391-408 (16 lines, 515 tokens)
  • calibration/observations.json:1101-1143 duplicates calibration/observations.json:1202-1244 (43 lines, 122 tokens)
  • calibration/observations.json:1101-1143 duplicates calibration/observations.json:1303-1345 (43 lines, 122 tokens)
  • calibration/observations.json:1101-1143 duplicates calibration/observations.json:1404-1446 (43 lines, 122 tokens)
  • calibration/observations.json:1101-1143 duplicates calibration/observations.json:1526-1568 (43 lines, 122 tokens)
  • calibration/observations.json:1101-1143 duplicates calibration/observations.json:1627-1669 (43 lines, 122 tokens)
  • calibration/observations.json:1101-1144 duplicates calibration/observations.json:1728-1771 (44 lines, 126 tokens)
  • calibration/observations.json:1101-1144 duplicates calibration/observations.json:1829-1872 (44 lines, 126 tokens)
  • calibration/observations.json:1101-1143 duplicates calibration/observations.json:1930-1972 (43 lines, 122 tokens)
  • calibration/observations.json:1101-1144 duplicates calibration/observations.json:2031-2074 (44 lines, 126 tokens)
    and 27 more...
    Layer 1 (static) ran. Set ANTHROPIC_API_KEY to enable semantic analysis.
    Automated by Code Metrics Workflow

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant