Skip to content

[CLI] META-195 — populate generated.fileIndex and generated.frameworkManifest - #11

Merged
qmarcelle merged 2 commits into
mainfrom
feature/meta-195-p0-workspacejsoncli-v04-producer-populate-fileindex-and
Jul 26, 2026
Merged

[CLI] META-195 — populate generated.fileIndex and generated.frameworkManifest#11
qmarcelle merged 2 commits into
mainfrom
feature/meta-195-p0-workspacejsoncli-v04-producer-populate-fileindex-and

Conversation

@qmarcelle

@qmarcelle qmarcelle commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Closes META-195. All four of its blockers (META-236, META-240, META-203, META-200) are Done.

Before this, the producer emitted:

frameworkManifest: agentsMd.frameworkTokens.map((name) => ({ name, confidence: 0.5 })),
fileIndex: {},

fileIndex — the tracked inventory, and nothing more

Keys are repository-root-relative POSIX paths from git ls-files; entries are {}.

Keys alone are the load-bearing part. The consumer adapter extracted under META-248 joined by hasOwnProperty(fileIndex, key) and never read a value — so fileIndex: {} is precisely why that join silently returned zero rows.

Entries are deliberately empty. The per-file values the schema names — fragility, aiModificationCount, humanModificationCount — are all behavioral, and the only evidence available for them is git-derived. META-195's experimental boundary keeps that harness-side pending VR-526, which is open because a weak git-scan fragility formula diverges from live scores. Publishing that number into the four-path stable contract is the disputed act, so this emits keys and claims nothing about them.

frameworkManifest — corroborated detection only

The published schema describes this field as "Detected frameworks (confidence >= 0.7). Read by Buildomator." Every entry the producer emitted was a bare AGENTS.md token at 0.5 — under its own contract's floor, universally. A consumer filtering at the documented threshold read an empty manifest. AJV only enforces 0..1 numerically, so this validated while contradicting the field's stated semantics.

Entries are now those a declared manifest dependency corroborates by exact case-insensitive name equality, at 0.9. Tokens are normalized to lowercase before deduping, so React and react cannot both survive.

Why exact, not containment

An earlier revision of this PR matched by substring. That is categorically unsound against the vocabulary it matches, not merely edge-casey — these are all real KNOWN_FRAMEWORKS tokens sitting inside real dependency names:

token contained in
vite vitest
nest nestjs, @nestjs/core
redis ioredis
zod zod-to-json-schema
graphql graphql-tag
rest restify, interest

Some of those collisions are outright false positives and others accidentally approximate a legitimate alias, and containment cannot distinguish the two. Publishing either at 0.9 tells a consumer to trust a claim the repository does not support. Recovering aliases safely requires a curated map, not a string test.

It therefore under-reports, deliberately. @workspacejson/rules keeps FRAMEWORK_MANIFEST_MAP and KNOWN_FRAMEWORKS internal — neither is exported. Re-typing that table here would fork standard-owned knowledge, which META-195's own contract-owner clause forbids and which is the split-brain META-200 just spent a week reconciling. So tokens whose dependency is published under a different name (next.jsnext, nestjs@nestjs/core, drizzledrizzle-orm, tailwindtailwindcss) are omitted rather than guessed. Absent, not wrong.

Exporting that map from workspacejson/standard is the real fix and is deliberately left to a follow-up, so the DataHub-critical fileIndex work is not coupled to a cross-repository contract change.

This is not the test frameworkDrift performs, and does not claim to be. That rule looks a token up in the internal variant map, continues when unmapped, and only then compares — so it never matches on a raw token the way this must.

Determinism — the actual hazard was not the one the issue named

Both fields sit inside the material projection (generatedProjection excludes only generatedAt and by), so any churn rewrites the artifact every run and breaks generate --check as a CI gate.

The issue warned about map iteration order, absolute paths, timestamps and float churn. The real landmine is RepoState.gitHistory:

  • filesChangedLast30Days is git log --since=30 days ago — a moving wall-clock window that changes with zero commits as history ages out;
  • it is [...Set] in git-log order, so unsorted;
  • when git is absent or the checkout is shallow, the fallback is every file, and getTrackedFiles degrades from sorted git ls-files to fast-glob.

Independence from it is structural: neither builder is passed gitHistory at all. Their signatures take only files and (tokens, manifests), so there is nothing time-varying to read. No test here simulates wall-clock movement — git runs in a subprocess, so no in-process clock fake would reach it, and a test claiming otherwise would be theatre.

Both builders sort explicitly with the default comparator, never localeCompare, which is locale-dependent and would make the written bytes vary by host.

Verification

architecture check         62 files, 3 manifests — clean
architecture red tests     20 passed, 0 failed
packages/cli               56 tests passed  (35 before: +15 unit, +6 conformance)
agents-audit-compat        44 tests passed
build + typecheck          clean
release:verify-packs       release-safe
agents-audit scan .        exit 0
runtime parity             25/29, 4 differences — exactly the ratified baseline
packed-artifact parity     GATE PASS

Both guards were verified to bite rather than pass vacuously:

  • restoring the old fileIndex: {} / 0.5 emission turns 3 of the 6 new conformance tests red;
  • restoring substring corroboration turns 2 of the framework unit tests red.

Framework coverage is guarded in both directions — negative regressions (vite/vitest, rest/interest, next.js/next), a positive exact-match control so the guard cannot pass by rejecting everything, case-variant dedupe, and the deliberate tailwind/tailwindcss omission.

Real output on this repository: 76 files indexed, 4.3 KB artifact. frameworkManifest is [] here — honestly so: this repo's AGENTS.md contains no framework tokens at all.

Parity baseline

artifact-equivalence and agents-audit generate --dry-run were already ratified expected differences from META-203, so the gate still passes mechanically. But that file records why each difference is authorized, and its own header says an entry is a claim a human made. The META-203 block asserted the verified diffs were only specVersion and + conventions, which is no longer true. Updated to describe both fields, cite META-195, record that matching is exact and why — including that frameworkManifest's entry count can now drop against the frozen source, since an unbacked token is omitted rather than published below its contract's floor.

Noted, not fixed

types/ambient.d.ts hand-declares node:child_process with stdio?: 'pipe' only, so stdio: 'ignore' fails typecheck. This is the same shadowing-of-Node-builtins defect OWNERSHIP.md flags as a follow-up after META-248 — which turns out to cover more than node:fs. Worked within it rather than expanding this PR's scope.

Boundary

  • No schema changes. @workspacejson/spec and @workspacejson/rules consumed as released packages, unmodified.
  • agents-audit compatibility preserved — both binaries still route through one implementation (META-247).
  • Nothing published; nothing publishable changed.

fileIndex was emitted as `{}` unconditionally and frameworkManifest as bare
AGENTS.md tokens at a hardcoded confidence 0.5. Both now derive from repository
evidence, deterministically.

fileIndex is the tracked file inventory, keyed by repository-root-relative
POSIX path, entries empty. Keys are the load-bearing part: the consumer adapter
extracted under META-248 joined by key presence and never read a value, so an
empty index made every such join return zero rows. The per-file values the
schema names are behavioral and only derivable from git history, which
META-195's experimental boundary keeps out of the stable contract pending the
VR-526 ruling.

frameworkManifest now emits only frameworks a declared manifest dependency
corroborates, at 0.9. The schema documents this field as "Detected frameworks
(confidence >= 0.7)", so every entry the producer emitted before sat under its
own contract's floor and a consumer filtering at the threshold read an empty
manifest. Corroboration matches the fallback branch frameworkDrift already
uses, so the emitter and the rule that audits it agree.

Both fields sit inside the material projection, so both are explicitly sorted
with a locale-independent comparator and neither touches RepoState.gitHistory,
which is a moving 30-day window whose no-git fallback is "every file".

Updates the ratified parity baseline: artifact-equivalence and `generate
--dry-run` were already expected differences from META-203, but their recorded
reason no longer described the full diff.
Copilot AI review requested due to automatic review settings July 26, 2026 19:54

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@charliecreates charliecreates Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file index work is deterministic and scoped appropriately, but the framework corroboration currently permits false positives at the advertised 0.9 confidence.

Comment thread packages/cli/src/producer/evidence.ts Outdated
…ETA-195)

Substring containment awarded 0.9 confidence on lexical overlap. `vite` is a
substring of `vitest`, so a repository installing only vitest — an ordinary
setup, not an edge case — published `vite` at the confidence that tells a
consumer to trust it. That inverts the field's whole point and contradicts this
change's own "absent, not wrong" policy.

Corroboration is now exact case-insensitive dependency equality, and tokens are
normalized before deduping so `React` and `react` cannot both survive.

Adds negative regressions (vite/vitest, rest/interest, next.js/next) plus a
positive control so the guard cannot pass by rejecting everything. The
tailwind -> tailwindcss expectation codified the old behavior as desirable; it
now asserts the omission, alongside the corroborating `tailwindcss` token.

Corrects two claims that were wrong rather than merely imprecise:
  - the comment said this matched `frameworkDrift`. It does not: that rule
    looks a token up in the internal variant map and skips it when unmapped,
    so it never matches on a raw token.
  - a conformance test was named for wall-clock movement it never performed.
    Git runs in a subprocess, so no in-process clock fake reaches it. The
    immunity is structural — neither builder is passed RepoState.gitHistory —
    and the test is renamed to what it actually establishes.
@qmarcelle

Copy link
Copy Markdown
Contributor Author

Fixed in c8505ad. The substring defect was real and worse than the rest/interest example suggested — I reproduced it against the actual token set:

vite token, ONLY vitest installed : [{"name":"vite","confidence":0.9}]
rest token, only "interest"       : [{"name":"rest","confidence":0.9}]

vite and vitest are both real framework tokens and vitest-without-vite is an ordinary setup, so this was a likely false positive in normal repositories, not an edge case.

Changed

  • Corroboration is now exact case-insensitive dependency equality.
  • Tokens are normalized before deduping, so React and react cannot both survive.
  • Negative regressions added: vite/vitest, rest/interest, next.js/next. Plus a positive control (vite does corroborate when vite itself is installed) so the guard cannot pass by rejecting everything.
  • The tailwindtailwindcss test codified the old behavior as desirable. It now asserts the omission, alongside the tailwindcss token that does corroborate.

Two corrections where the review was right that I was wrong, not merely imprecise

  • The comment claimed this matched frameworkDrift. It does not — that rule looks a token up in the internal variant map and continues when unmapped, so it never matches on a raw token. Comment corrected rather than softened.
  • The conformance test named for wall-clock movement never moved the clock. Git runs in a subprocess, so no in-process fake reaches git log --since=30 days ago. The immunity is structural — neither builder is passed RepoState.gitHistory — and the test is renamed to what it actually establishes, with that reasoning recorded.

The byte-stability unit test now compares reversed input rather than calling the same function twice.

Scope held as advised: no cross-repo change. Exporting FRAMEWORK_MANIFEST_MAP from workspacejson/standard remains the real fix for the under-reporting and stays a follow-up, so the fileIndex work is not coupled to a contract change.

Verification after the fix

packages/cli               56 tests passed  (was 53)
agents-audit-compat        44 tests passed
architecture check         62 files, 3 manifests — clean
architecture red tests     20 passed, 0 failed
build + typecheck          clean
runtime parity             25/29 — 4 differences, exactly the ratified baseline
packed-artifact parity     GATE PASS
release:verify-packs       release-safe
agents-audit scan .        exit 0

The new negative regression was verified to bite: restoring substring matching turns 2 of the framework tests red.

The parity baseline note was updated to record that matching is exact and why, since it now describes a narrower set of entries than before.

@qmarcelle
qmarcelle merged commit d83a829 into main Jul 26, 2026
3 checks passed
@qmarcelle
qmarcelle deleted the feature/meta-195-p0-workspacejsoncli-v04-producer-populate-fileindex-and branch July 27, 2026 03:41
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.

2 participants