* feat(#3227): publish machine-readable state contract at step boundaries Adds src/state-contract.cts, a best-effort publisher that writes .planning/state.json (contract 1.0.0) at 11 step-boundary commands, so external tools read a versioned contract instead of parsing STATE.md and ROADMAP.md heuristically. Composes existing owners rather than re-deriving: phase rows come from a new locateProgressTable extracted from deriveProgressFromRoadmap (so the snapshot can never disagree with GSD's own progress counters), milestone identity from getMilestoneInfo, and next from classifyProject. Owners are required lazily to avoid the state -> state-contract -> smart-entry -> state require cycle. Also fixes a pre-existing defect in scripts/lint-test-file-count.cjs (maintainer-approved as a second concern): testEffectivePrefix never stripped the suite qualifier, so 65 dotted test files counted against no module and 9 mis-bucketed into a shorter one. Allowlist re-baselined for the 74 files the gate can now see. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(#3227): backfill PR number into the changeset fragment pr:0 -> pr:3824 now that the PR exists. Doc-only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(#3227): shape hostile-name fixtures away from the scan corpus The two hostile-input fixtures used a literal phrase from scripts/prompt-injection-scan.sh's corpus, so CI's Security Scan redded on this file. These tests assert that an arbitrary phase name round-trips into state.json as inert data -- the property holds for any string, so the injection flavor is illustrative, not load-bearing. Reshaped to a hyphenated fake instruction tag, which stays hostile-looking while matching none of the scanner's patterns. Allowlisting the file was rejected: that mechanism is for suites whose subject IS injection defense, and it would blind the scanner to this whole file permanently. See DEFECT.PROMPT-INJECTION-SCAN-COLLISION. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(#3227): ratchet the state-contract mutation floor to its measured score The module was registered at minScore 50, the ratchet's minimum permitted floor for a newly-registered module whose score had not been measured. This PR's own Stryker shard measured 66.25% (run 32769289750, job 97565813640), so the floor moves to floor(measured) - 1 = 65, per the rule the registry documents. 66.25 is below TARGET_MUTATION_SCORE (80), so this stays a ratchet candidate: raise as the tests improve, never lower. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: sim <sim@local> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,17 +1,44 @@
|
||||
{
|
||||
"_doc": "Baseline of modules currently exceeding the 2-test-file limit. Each entry locks in TODAY's exact test filenames as the allowlisted set (identity ratchet). Adding a NEW test file to a capped module fails (novel). Removing one requires pruning this list (stale, ratchet-down). When a cluster drops to \u2264 2, remove its entry entirely. New entries require justification in PR description.",
|
||||
"modules": {
|
||||
"adr-parser": {
|
||||
"files": [
|
||||
"adr-parser.property.test.cjs",
|
||||
"adr-parser.test.cjs",
|
||||
"adr-parser.unit.test.cjs"
|
||||
],
|
||||
"issue": "3227",
|
||||
"justification": "Grandfathered by #3227 when the testEffectivePrefix() dot/hyphen bucketing fix first made this pre-existing over-cap cluster visible to the gate; not new test sprawl."
|
||||
},
|
||||
"config": {
|
||||
"files": [
|
||||
"config-defaults-runtime-exclusion.test.cjs",
|
||||
"config-field-docs.test.cjs",
|
||||
"config-get-default.test.cjs",
|
||||
"config-schema.property.test.cjs",
|
||||
"config-validation.test.cjs",
|
||||
"config.test.cjs"
|
||||
],
|
||||
"issue": "TBD"
|
||||
},
|
||||
"context-predicates": {
|
||||
"files": [
|
||||
"context-predicates-query.test.cjs",
|
||||
"context-predicates.property.test.cjs",
|
||||
"context-predicates.test.cjs"
|
||||
],
|
||||
"issue": "3227",
|
||||
"justification": "Grandfathered by #3227 when the testEffectivePrefix() dot/hyphen bucketing fix first made this pre-existing over-cap cluster visible to the gate; not new test sprawl."
|
||||
},
|
||||
"frontmatter": {
|
||||
"files": [
|
||||
"frontmatter-cli.test.cjs",
|
||||
"frontmatter.property.test.cjs",
|
||||
"frontmatter.test.cjs",
|
||||
"frontmatter.unit.test.cjs"
|
||||
],
|
||||
"issue": "3227",
|
||||
"justification": "Grandfathered by #3227 when the testEffectivePrefix() dot/hyphen bucketing fix first made this pre-existing over-cap cluster visible to the gate; not new test sprawl."
|
||||
},
|
||||
"graphify": {
|
||||
"files": [
|
||||
"graphify-auto-update.slow.test.cjs",
|
||||
@@ -151,9 +178,65 @@
|
||||
"files": [
|
||||
"prompt-budget-cli.test.cjs",
|
||||
"prompt-budget-parity.test.cjs",
|
||||
"prompt-budget.test.cjs"
|
||||
"prompt-budget.property.test.cjs",
|
||||
"prompt-budget.test.cjs",
|
||||
"prompt-budget.unit.test.cjs"
|
||||
],
|
||||
"issue": "2929"
|
||||
},
|
||||
"mcp-catalog": {
|
||||
"files": [
|
||||
"mcp-catalog-parity.install.test.cjs",
|
||||
"mcp-catalog.property.test.cjs",
|
||||
"mcp-catalog.test.cjs"
|
||||
],
|
||||
"issue": "3227",
|
||||
"justification": "Grandfathered by #3227 when the testEffectivePrefix() dot/hyphen bucketing fix first made this pre-existing over-cap cluster visible to the gate; not new test sprawl."
|
||||
},
|
||||
"model-catalog": {
|
||||
"files": [
|
||||
"model-catalog-runtime-defaults.test.cjs",
|
||||
"model-catalog-valid-tiers.test.cjs",
|
||||
"model-catalog.unit.test.cjs"
|
||||
],
|
||||
"issue": "3227",
|
||||
"justification": "Grandfathered by #3227 when the testEffectivePrefix() dot/hyphen bucketing fix first made this pre-existing over-cap cluster visible to the gate; not new test sprawl."
|
||||
},
|
||||
"package-legitimacy": {
|
||||
"files": [
|
||||
"package-legitimacy-gate.test.cjs",
|
||||
"package-legitimacy.property.test.cjs",
|
||||
"package-legitimacy.test.cjs"
|
||||
],
|
||||
"issue": "3227",
|
||||
"justification": "Grandfathered by #3227 when the testEffectivePrefix() dot/hyphen bucketing fix first made this pre-existing over-cap cluster visible to the gate; not new test sprawl."
|
||||
},
|
||||
"runtime-homes": {
|
||||
"files": [
|
||||
"runtime-homes-descriptor-drive.test.cjs",
|
||||
"runtime-homes-legacy-ids-drift-guard.test.cjs",
|
||||
"runtime-homes.property.test.cjs"
|
||||
],
|
||||
"issue": "3227",
|
||||
"justification": "Grandfathered by #3227 when the testEffectivePrefix() dot/hyphen bucketing fix first made this pre-existing over-cap cluster visible to the gate; not new test sprawl."
|
||||
},
|
||||
"section-manifest": {
|
||||
"files": [
|
||||
"section-manifest-init-facts.test.cjs",
|
||||
"section-manifest.property.test.cjs",
|
||||
"section-manifest.test.cjs"
|
||||
],
|
||||
"issue": "3227",
|
||||
"justification": "Grandfathered by #3227 when the testEffectivePrefix() dot/hyphen bucketing fix first made this pre-existing over-cap cluster visible to the gate; not new test sprawl."
|
||||
},
|
||||
"workflow-fragments": {
|
||||
"files": [
|
||||
"workflow-fragments-emission.install.test.cjs",
|
||||
"workflow-fragments.property.test.cjs",
|
||||
"workflow-fragments.test.cjs"
|
||||
],
|
||||
"issue": "3227",
|
||||
"justification": "Grandfathered by #3227 when the testEffectivePrefix() dot/hyphen bucketing fix first made this pre-existing over-cap cluster visible to the gate; not new test sprawl."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,16 @@ function prodPrefix(filename) {
|
||||
function testEffectivePrefix(testName) {
|
||||
const bare = testName
|
||||
.replace(/\.integration\.test\.(ts|cjs)$/, '')
|
||||
.replace(/\.test\.(ts|cjs)$/, '');
|
||||
.replace(/\.test\.(ts|cjs)$/, '')
|
||||
// #3227: strip a trailing suite/kind qualifier (`.unit`, `.property`,
|
||||
// `.security`, `.slow`, `.install`, `.rule`, `.regression`, `.qa`, ...).
|
||||
// A production prefix comes from `prodPrefix`, which strips only the file
|
||||
// extension, so it never contains a dot — any surviving trailing `.<word>`
|
||||
// is a qualifier. Without this, `frontmatter.property` matched no module at
|
||||
// all (65 files counted against nothing repo-wide) and `state-contract.unit`
|
||||
// matched the SHORTER `state` module through the `prefix + '-'` rule
|
||||
// (9 files mis-bucketed).
|
||||
.replace(/\.[a-z0-9]+$/i, '');
|
||||
const m = bare.match(/^(?:feat|bug|enh|fix)-\d+(?:-\d+)*-(.+)$/);
|
||||
return m ? m[1] : bare;
|
||||
}
|
||||
|
||||
@@ -296,6 +296,39 @@ const COVERED = {
|
||||
tests: ['tests/model-catalog.unit.test.cjs'],
|
||||
minScore: 58,
|
||||
},
|
||||
// state-contract: net-new module from #3227. Without this entry the
|
||||
// Stryker gate reports has_work: "false" and SKIPS it entirely — the
|
||||
// exact gap #2790 (planning-inspect / plan-document / planning-command-router)
|
||||
// and #3007 (model-catalog) each had to fix after the fact.
|
||||
//
|
||||
// Same #2790 precedent as planning-inspect / model-catalog above: this
|
||||
// shard points at tests/state-contract.unit.test.cjs, NOT
|
||||
// tests/state-contract.test.cjs — the latter spawns a `gsd-tools` child
|
||||
// process per case via runGsdTools, and Stryker's command runner treats
|
||||
// the whole `node --test <file>` invocation as ONE test costing whatever
|
||||
// its slowest case costs, re-run once per mutant, so it cannot finish
|
||||
// inside the 15-minute shard cap. tests/state-contract.unit.test.cjs is
|
||||
// spawn-free and in-process.
|
||||
//
|
||||
// Measured CI score (GitHub Actions run 32769289750, job 97565813640,
|
||||
// `Stryker (state-contract)`, PASSED in 2m23s):
|
||||
// state-contract 66.25% → floor 65 (below TARGET_MUTATION_SCORE (80) —
|
||||
// ratchet candidate like planning-inspect (56) and model-catalog (58):
|
||||
// comfortably clears its own floor but has real room to grow. Raise as
|
||||
// its tests improve, never lower it.)
|
||||
// Floor follows this file's documented rule, minScore = floor(measured) - 1,
|
||||
// matching the sibling precedent exactly (57.03 → 56, 76.58 → 75,
|
||||
// 95.65 → 94, 59.62 → 58, 66.25 → 65).
|
||||
//
|
||||
// The floor MUST come from a CI shard, never a local run: local runs count
|
||||
// timeouts as kills and inflate scores badly (this file already records
|
||||
// prompt-budget 99.6% local vs 68.33% CI, and config-schema 69.7% local vs
|
||||
// 54.55% CI).
|
||||
'state-contract': {
|
||||
cjs: 'gsd-core/bin/lib/state-contract.cjs',
|
||||
tests: ['tests/state-contract.unit.test.cjs'],
|
||||
minScore: 65,
|
||||
},
|
||||
};
|
||||
|
||||
// ── Files that, when changed, invalidate ALL modules ─────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user