@sabaiway/agent-workflow-memory 4.2.0 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -0
- package/SKILL.md +22 -4
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/scripts/check-docs-size-cli.test.mjs +5 -4
- package/references/scripts/check-docs-size-ensure.test.mjs +332 -0
- package/references/scripts/check-docs-size.mjs +181 -30
- package/references/scripts/migrate-gates-branches.test.mjs +146 -1
- package/references/scripts/migrate-gates.mjs +295 -60
- package/references/scripts/migrate-gates.test.mjs +206 -14
- package/references/templates/gates.json +1 -1
|
@@ -12,6 +12,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
12
12
|
import {
|
|
13
13
|
LEGACY_FORMS,
|
|
14
14
|
UNIT_TESTS_COVERAGE_FLAGS,
|
|
15
|
+
KNOWN_COVERAGE_FLAG_SETS,
|
|
15
16
|
COVERAGE_PRODUCER_BODY,
|
|
16
17
|
RETIRED_STORE_BASENAMES,
|
|
17
18
|
findRetiredStores,
|
|
@@ -21,8 +22,17 @@ import {
|
|
|
21
22
|
main,
|
|
22
23
|
} from './migrate-gates.mjs';
|
|
23
24
|
|
|
25
|
+
// An INSTALLED kit tools dir carries both core checks as real files. That is a fixture
|
|
26
|
+
// requirement, not decoration: canonicity is a realpath anchor, so a core check whose file is not
|
|
27
|
+
// there resolves to nothing and is no claim at all — the same fail-closed answer run-gates gives.
|
|
24
28
|
const KIT_TOOLS = mkdtempSync(join(tmpdir(), 'migrate-gates-kit-'));
|
|
25
29
|
writeFileSync(join(KIT_TOOLS, 'coverage-check.mjs'), '// the installed checker the migration points at\n');
|
|
30
|
+
writeFileSync(join(KIT_TOOLS, 'review-state.mjs'), '// the installed review-state check\n');
|
|
31
|
+
// The project root the plan builder resolves declared RELATIVE tokens against — the same anchor
|
|
32
|
+
// run-gates uses. Empty on purpose for the pure-plan cases: a relative lookalike resolves to
|
|
33
|
+
// nothing there, which is exactly the "no claim can be made" outcome those rows assert. The
|
|
34
|
+
// vendored rows below build their own project and pass it explicitly.
|
|
35
|
+
const PROJECT = mkdtempSync(join(tmpdir(), 'migrate-gates-project-'));
|
|
26
36
|
|
|
27
37
|
const mkProject = (gates) => {
|
|
28
38
|
const root = mkdtempSync(join(tmpdir(), 'migrate-gates-'));
|
|
@@ -37,6 +47,21 @@ const quiet = () => {
|
|
|
37
47
|
return { log: (l) => out.push(String(l)), error: (l) => err.push(String(l)), out, err };
|
|
38
48
|
};
|
|
39
49
|
|
|
50
|
+
// The FIRST flag set the kit ever emitted, frozen here as literal bytes — never read back out of
|
|
51
|
+
// KNOWN_COVERAGE_FLAG_SETS. Deployed declarations on disk carry exactly these bytes, so the
|
|
52
|
+
// append-only promise needs a checker that goes red if they are edited or dropped; deriving the
|
|
53
|
+
// "prior" from the set under test would keep this green while real deployments broke.
|
|
54
|
+
const PRIOR_FLAG_SET_V1 =
|
|
55
|
+
'--experimental-test-coverage --test-reporter=lcov --test-reporter-destination="$AW_GIT_DIR/agent-workflow-lcov.info" --test-reporter=spec --test-reporter-destination=stdout';
|
|
56
|
+
|
|
57
|
+
// A VENDORED deployment: the SAME tools, from a copy `--kit-tools` does not name. Recognition is a
|
|
58
|
+
// realpath anchor, so these are real files — a fake path would be the unresolvable case instead.
|
|
59
|
+
const VENDORED_TOOLS = mkdtempSync(join(tmpdir(), 'migrate-gates-vendored-'));
|
|
60
|
+
writeFileSync(join(VENDORED_TOOLS, 'coverage-check.mjs'), '// a vendored copy of the checker\n');
|
|
61
|
+
writeFileSync(join(VENDORED_TOOLS, 'review-state.mjs'), '// a vendored copy of the review-state check\n');
|
|
62
|
+
const vendoredCmd = (name) => `node "${join(VENDORED_TOOLS, `${name}.mjs`)}" --check`;
|
|
63
|
+
const installedCmd = (name) => `node "${join(KIT_TOOLS, `${name}.mjs`)}" --check`;
|
|
64
|
+
|
|
40
65
|
const LEGACY_LEDGER = { id: 'review-ledger', title: 'L', cmd: 'node "/kit/tools/review-ledger.mjs" --check' };
|
|
41
66
|
const LEGACY_FOLD = { id: 'fold-completeness', title: 'F', cmd: 'node /kit/tools/fold-completeness.mjs --check' };
|
|
42
67
|
const UNIT = { id: 'unit-tests', title: 'U', cmd: 'node --test tools/*.test.mjs' };
|
|
@@ -45,12 +70,12 @@ const CUSTOM = { id: 'my-ledger-wrap', title: 'C', cmd: 'node scripts/wrap.mjs &
|
|
|
45
70
|
describe('migrate-gates — the pure migration plan', () => {
|
|
46
71
|
it('matches BOTH documented legacy forms (quoted and bare paths) and removes them', () => {
|
|
47
72
|
for (const form of LEGACY_FORMS) assert.ok(form.re instanceof RegExp);
|
|
48
|
-
const { plan } = buildMigrationPlan([LEGACY_LEDGER, LEGACY_FOLD], KIT_TOOLS);
|
|
73
|
+
const { plan } = buildMigrationPlan([LEGACY_LEDGER, LEGACY_FOLD], KIT_TOOLS, PROJECT);
|
|
49
74
|
assert.deepEqual(plan.filter((r) => r.action === 'remove').map((r) => r.entry.id), ['review-ledger', 'fold-completeness']);
|
|
50
75
|
});
|
|
51
76
|
|
|
52
77
|
it('extends the canonical unit-tests cmd with the lcov reporters (flags inserted after `node --test`)', () => {
|
|
53
|
-
const { plan, unitTestsExtended } = buildMigrationPlan([UNIT], KIT_TOOLS);
|
|
78
|
+
const { plan, unitTestsExtended } = buildMigrationPlan([UNIT], KIT_TOOLS, PROJECT);
|
|
54
79
|
assert.ok(unitTestsExtended);
|
|
55
80
|
const extended = plan.find((r) => r.action === 'extend').entry;
|
|
56
81
|
assert.equal(extended.cmd, `node --test ${UNIT_TESTS_COVERAGE_FLAGS} tools/*.test.mjs`);
|
|
@@ -58,23 +83,60 @@ describe('migrate-gates — the pure migration plan', () => {
|
|
|
58
83
|
|
|
59
84
|
it('an already-extended unit-tests cmd is left alone (idempotent)', () => {
|
|
60
85
|
const done = { id: 'unit-tests', title: 'U', cmd: `node --test ${UNIT_TESTS_COVERAGE_FLAGS} tools/*.test.mjs` };
|
|
61
|
-
const { plan } = buildMigrationPlan([done], KIT_TOOLS);
|
|
86
|
+
const { plan } = buildMigrationPlan([done], KIT_TOOLS, PROJECT);
|
|
62
87
|
assert.equal(plan.find((r) => r.entry.id === 'unit-tests').action, 'keep');
|
|
63
88
|
});
|
|
64
89
|
|
|
90
|
+
it('a declaration carrying a PRIOR emitted flag set reads as already-configured — keep, zero diff, no warning', () => {
|
|
91
|
+
// The canonical flag set moved (the destination became a required-parameter expansion). A
|
|
92
|
+
// deployment written by the earlier kit must not suddenly read as customized: that would send
|
|
93
|
+
// the maintainer to hand-fix a gate which already produces the lcov the checker reads.
|
|
94
|
+
assert.notEqual(PRIOR_FLAG_SET_V1, UNIT_TESTS_COVERAGE_FLAGS, 'the v1 bytes are a form the kit no longer emits');
|
|
95
|
+
assert.ok(KNOWN_COVERAGE_FLAG_SETS.includes(PRIOR_FLAG_SET_V1), 'and the append-only set still carries them');
|
|
96
|
+
const deployed = [
|
|
97
|
+
{ id: 'unit-tests', title: 'U', cmd: `node --test ${PRIOR_FLAG_SET_V1} tools/*.test.mjs` },
|
|
98
|
+
{ id: 'review-state', title: 'RS', cmd: `node "${join(KIT_TOOLS, 'review-state.mjs')}" --check` },
|
|
99
|
+
{ id: 'coverage-check', title: 'CC', cmd: `node "${join(KIT_TOOLS, 'coverage-check.mjs')}" --check` },
|
|
100
|
+
];
|
|
101
|
+
const analysis = buildMigrationPlan(deployed, KIT_TOOLS, PROJECT);
|
|
102
|
+
assert.equal(analysis.plan.find((r) => r.entry.id === 'unit-tests').action, 'keep');
|
|
103
|
+
assert.deepEqual(analysis.customized, [], 'a prior emitted form is never reported customized');
|
|
104
|
+
assert.equal(analysis.hasProducer, true, 'it still counts as the producer the checker reads');
|
|
105
|
+
assert.equal(analysis.checkerInert, false);
|
|
106
|
+
assert.equal(analysis.finalCapable, true);
|
|
107
|
+
assert.deepEqual(resultingGates(analysis.plan), deployed, 'zero diff — nothing is rewritten');
|
|
108
|
+
const preview = formatPreview(analysis, 'APPLY');
|
|
109
|
+
assert.match(preview, /nothing to migrate/, 'the preview says there is nothing to do');
|
|
110
|
+
assert.doesNotMatch(preview, /CUSTOMIZED|INERT|WARNING/, 'and warns about nothing');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('a unit-tests entry that merely CONTAINS the prior bytes is CUSTOMIZED, never a silent keep', () => {
|
|
114
|
+
// The already-configured decision runs through the closed producer predicate. A substring probe
|
|
115
|
+
// would call `echo <prior flags>` already configured and say nothing, leaving the maintainer
|
|
116
|
+
// with an entry the tool cannot verify and no recovery line.
|
|
117
|
+
const nearMiss = { id: 'unit-tests', title: 'U', cmd: `echo ${PRIOR_FLAG_SET_V1}` };
|
|
118
|
+
const analysis = buildMigrationPlan([nearMiss], KIT_TOOLS, PROJECT);
|
|
119
|
+
assert.equal(analysis.plan.find((r) => r.entry.id === 'unit-tests').action, 'keep', 'nothing is rewritten');
|
|
120
|
+
assert.deepEqual(analysis.customized.map((g) => g.id), ['unit-tests'], 'but it IS reported customized');
|
|
121
|
+
assert.equal(analysis.hasProducer, false, 'and it never counts as the producer the checker would read');
|
|
122
|
+
const preview = formatPreview(analysis, 'APPLY');
|
|
123
|
+
assert.match(preview, /CUSTOMIZED \(untouched\): unit-tests/);
|
|
124
|
+
assert.match(preview, /declare the canonical suite gate by hand/, 'the paste-ready recovery rides along');
|
|
125
|
+
});
|
|
126
|
+
|
|
65
127
|
it('adds the coverage-check gate LAST with the RESOLVED quoted path; never a second one', () => {
|
|
66
|
-
const { plan } = buildMigrationPlan([UNIT], KIT_TOOLS);
|
|
128
|
+
const { plan } = buildMigrationPlan([UNIT], KIT_TOOLS, PROJECT);
|
|
67
129
|
const result = resultingGates(plan);
|
|
68
130
|
const last = result[result.length - 1];
|
|
69
131
|
assert.equal(last.id, 'coverage-check');
|
|
70
132
|
assert.equal(last.cmd, `node "${join(KIT_TOOLS, 'coverage-check.mjs')}" --check`);
|
|
71
|
-
const again = buildMigrationPlan(result, KIT_TOOLS);
|
|
133
|
+
const again = buildMigrationPlan(result, KIT_TOOLS, PROJECT);
|
|
72
134
|
assert.ok(!again.plan.some((r) => r.action === 'add'), 'a declaration already carrying the checker gains no duplicate');
|
|
73
135
|
});
|
|
74
136
|
|
|
75
137
|
it('a declaration with NO producer never GAINS the checker — the pair is declared together or not at all', () => {
|
|
76
138
|
const npmSuite = { id: 'suite', title: 'S', cmd: 'npm test' };
|
|
77
|
-
const analysis = buildMigrationPlan([LEGACY_LEDGER, npmSuite], KIT_TOOLS);
|
|
139
|
+
const analysis = buildMigrationPlan([LEGACY_LEDGER, npmSuite], KIT_TOOLS, PROJECT);
|
|
78
140
|
assert.ok(!analysis.plan.some((r) => r.action === 'add'), 'no checker is added over a declaration that produces no lcov');
|
|
79
141
|
assert.deepEqual(resultingGates(analysis.plan).map((g) => g.id), ['suite'], 'the legacy entry still goes, nothing dead arrives');
|
|
80
142
|
assert.equal(analysis.finalCapable, false, 'a declaration with no checker is not final-run-capable');
|
|
@@ -86,7 +148,7 @@ describe('migrate-gates — the pure migration plan', () => {
|
|
|
86
148
|
it('an ALREADY-declared checker over no producer is reported INERT, is never removed, and is not final-run-capable', () => {
|
|
87
149
|
const checker = { id: 'coverage-check', title: 'CC', cmd: `node "${join(KIT_TOOLS, 'coverage-check.mjs')}" --check` };
|
|
88
150
|
const reviewState = { id: 'review-state', title: 'RS', cmd: `node "${join(KIT_TOOLS, 'review-state.mjs')}" --check` };
|
|
89
|
-
const analysis = buildMigrationPlan([{ id: 'suite', title: 'S', cmd: 'npm test' }, reviewState, checker], KIT_TOOLS);
|
|
151
|
+
const analysis = buildMigrationPlan([{ id: 'suite', title: 'S', cmd: 'npm test' }, reviewState, checker], KIT_TOOLS, PROJECT);
|
|
90
152
|
assert.equal(analysis.finalCapable, false, 'a review-state present must NOT make an inert pair read as final-run-capable');
|
|
91
153
|
assert.ok(resultingGates(analysis.plan).some((g) => g.id === 'coverage-check'), 'the declared checker is never removed');
|
|
92
154
|
const preview = formatPreview(analysis, 'APPLY');
|
|
@@ -101,7 +163,7 @@ describe('migrate-gates — the pure migration plan', () => {
|
|
|
101
163
|
title: 'T',
|
|
102
164
|
cmd: `COREPACK_ENABLE_NETWORK=0 npm exec --offline --script-shell /bin/sh -- ${COVERAGE_PRODUCER_BODY}`,
|
|
103
165
|
};
|
|
104
|
-
const analysis = buildMigrationPlan([offered], KIT_TOOLS);
|
|
166
|
+
const analysis = buildMigrationPlan([offered], KIT_TOOLS, PROJECT);
|
|
105
167
|
assert.deepEqual(resultingGates(analysis.plan).map((g) => g.id), ['test', 'coverage-check']);
|
|
106
168
|
// The `no canonical unit-tests entry` advice is keyed on the ID, but a producer is recognized
|
|
107
169
|
// under ANY id — repeating the advice over a working producer sends the user to fix nothing.
|
|
@@ -109,7 +171,7 @@ describe('migrate-gates — the pure migration plan', () => {
|
|
|
109
171
|
});
|
|
110
172
|
|
|
111
173
|
it('a CUSTOMIZED dead-tool reference (compound form) is kept untouched and reported', () => {
|
|
112
|
-
const analysis = buildMigrationPlan([CUSTOM], KIT_TOOLS);
|
|
174
|
+
const analysis = buildMigrationPlan([CUSTOM], KIT_TOOLS, PROJECT);
|
|
113
175
|
assert.equal(analysis.plan.find((r) => r.entry.id === 'my-ledger-wrap').action, 'keep');
|
|
114
176
|
assert.deepEqual(analysis.customized.map((g) => g.id), ['my-ledger-wrap']);
|
|
115
177
|
const preview = formatPreview(analysis, 'APPLY');
|
|
@@ -121,7 +183,7 @@ describe('migrate-gates — the pure migration plan', () => {
|
|
|
121
183
|
describe('migrate-gates — the canonical anchor + final-capability validation (round-1 folds)', () => {
|
|
122
184
|
it('a canonical checker NOT in the last position is MOVED last (never left mid-list)', () => {
|
|
123
185
|
const canonical = { id: 'coverage-check', title: 'CC', cmd: `node "${join(KIT_TOOLS, 'coverage-check.mjs')}" --check` };
|
|
124
|
-
const { plan } = buildMigrationPlan([canonical, UNIT], KIT_TOOLS);
|
|
186
|
+
const { plan } = buildMigrationPlan([canonical, UNIT], KIT_TOOLS, PROJECT);
|
|
125
187
|
const result = resultingGates(plan);
|
|
126
188
|
assert.equal(result[result.length - 1].id, 'coverage-check', 'the canonical checker ends up LAST');
|
|
127
189
|
assert.ok(plan.some((r) => r.action === 'move' && r.entry.id === 'coverage-check'), 'the reorder is an explicit move action');
|
|
@@ -130,14 +192,14 @@ describe('migrate-gates — the canonical anchor + final-capability validation (
|
|
|
130
192
|
|
|
131
193
|
it('a LOOKALIKE checker cmd is CUSTOMIZED (never counted canonical) and the canonical one is still added', () => {
|
|
132
194
|
const lookalike = { id: 'cov', title: 'C', cmd: 'node scripts/coverage-check.mjs --check' };
|
|
133
|
-
const analysis = buildMigrationPlan([lookalike, UNIT], KIT_TOOLS);
|
|
195
|
+
const analysis = buildMigrationPlan([lookalike, UNIT], KIT_TOOLS, PROJECT);
|
|
134
196
|
assert.ok(analysis.customized.some((g) => g.id === 'cov'), 'the lookalike is reported customized');
|
|
135
197
|
const result = resultingGates(analysis.plan);
|
|
136
198
|
assert.equal(result[result.length - 1].id, 'coverage-check', 'the REAL canonical checker is added last');
|
|
137
199
|
});
|
|
138
200
|
|
|
139
201
|
it('the result is judged final-capable ONLY with a canonical review-state present; missing → a LOUD warning with the candidate line, never "final-run-capable"', () => {
|
|
140
|
-
const analysis = buildMigrationPlan([UNIT], KIT_TOOLS);
|
|
202
|
+
const analysis = buildMigrationPlan([UNIT], KIT_TOOLS, PROJECT);
|
|
141
203
|
assert.equal(analysis.finalCapable, false, 'no review-state → not final-capable');
|
|
142
204
|
const preview = formatPreview(analysis, 'APPLY');
|
|
143
205
|
assert.match(preview, /review-state/, 'the warning names the missing core check');
|
|
@@ -146,13 +208,14 @@ describe('migrate-gates — the canonical anchor + final-capability validation (
|
|
|
146
208
|
const withRs = buildMigrationPlan(
|
|
147
209
|
[UNIT, { id: 'review-state', title: 'RS', cmd: `node "${join(KIT_TOOLS, 'review-state.mjs')}" --check` }],
|
|
148
210
|
KIT_TOOLS,
|
|
211
|
+
PROJECT,
|
|
149
212
|
);
|
|
150
213
|
assert.equal(withRs.finalCapable, true);
|
|
151
214
|
});
|
|
152
215
|
|
|
153
216
|
it('a NON-canonical unit-tests cmd (npm test / wrapper) is CUSTOMIZED with the full flag set as the recovery', () => {
|
|
154
217
|
const npmTest = { id: 'unit-tests', title: 'U', cmd: 'npm test' };
|
|
155
|
-
const analysis = buildMigrationPlan([npmTest], KIT_TOOLS);
|
|
218
|
+
const analysis = buildMigrationPlan([npmTest], KIT_TOOLS, PROJECT);
|
|
156
219
|
assert.equal(analysis.plan.find((r) => r.entry.id === 'unit-tests').action, 'keep');
|
|
157
220
|
assert.ok(analysis.customized.some((g) => g.id === 'unit-tests'), 'a non-canonical suite cmd is customized');
|
|
158
221
|
const preview = formatPreview(analysis, 'APPLY');
|
|
@@ -161,7 +224,7 @@ describe('migrate-gates — the canonical anchor + final-capability validation (
|
|
|
161
224
|
|
|
162
225
|
it('a PARTIALLY-flagged unit-tests cmd is CUSTOMIZED (a lone coverage flag never reads as configured)', () => {
|
|
163
226
|
const partial = { id: 'unit-tests', title: 'U', cmd: 'node --test --experimental-test-coverage tools/*.test.mjs' };
|
|
164
|
-
const analysis = buildMigrationPlan([partial], KIT_TOOLS);
|
|
227
|
+
const analysis = buildMigrationPlan([partial], KIT_TOOLS, PROJECT);
|
|
165
228
|
assert.equal(analysis.plan.find((r) => r.entry.id === 'unit-tests').action, 'keep');
|
|
166
229
|
assert.ok(analysis.customized.some((g) => g.id === 'unit-tests'), 'the half-wired cmd is customized, never silently left');
|
|
167
230
|
});
|
|
@@ -203,6 +266,135 @@ describe('migrate-gates — the canonical anchor + final-capability validation (
|
|
|
203
266
|
});
|
|
204
267
|
});
|
|
205
268
|
|
|
269
|
+
describe('migrate-gates — a VENDORED core check is the tool, from a copy --kit-tools does not name (D6)', () => {
|
|
270
|
+
const UNIT_DONE = { id: 'unit-tests', title: 'U', cmd: `${COVERAGE_PRODUCER_BODY} tools/*.test.mjs` };
|
|
271
|
+
const INSTALLED_REVIEW_STATE = { id: 'review-state', title: 'RS', cmd: installedCmd('review-state') };
|
|
272
|
+
|
|
273
|
+
it('a vendored coverage-check is PRESERVED exactly as declared — declared, never added over, never a collision', () => {
|
|
274
|
+
// Before the split this entry was a LOOKALIKE holding the checker's id, which made the whole
|
|
275
|
+
// upgrade a hard STOP: every preview and every apply over a vendored deployment failed.
|
|
276
|
+
const vendored = { id: 'coverage-check', title: 'CC', cmd: vendoredCmd('coverage-check') };
|
|
277
|
+
const declaration = [UNIT_DONE, INSTALLED_REVIEW_STATE, vendored];
|
|
278
|
+
const analysis = buildMigrationPlan(declaration, KIT_TOOLS, PROJECT);
|
|
279
|
+
assert.equal(analysis.collision, null, 'a vendored copy carries the tool\'s own id legitimately — it is no squatter');
|
|
280
|
+
assert.ok(!analysis.plan.some((r) => r.action === 'add'), 'the checker IS declared, so a second one is never added');
|
|
281
|
+
assert.deepEqual(analysis.plan.map((r) => r.action), ['keep', 'keep', 'keep'], 'nothing is rewritten or reordered');
|
|
282
|
+
assert.deepEqual(resultingGates(analysis.plan), declaration, 'the declaration comes out byte-for-byte as it went in');
|
|
283
|
+
assert.deepEqual(analysis.externalCoreChecks.map((c) => c.name), ['coverage-check']);
|
|
284
|
+
assert.deepEqual(analysis.customized, [], 'a real copy of the tool is not an entry the tool cannot verify');
|
|
285
|
+
assert.equal(analysis.finalCapable, false, '--final anchors on the INSTALLED copy, so the capability claim is withheld');
|
|
286
|
+
const preview = formatPreview(analysis, 'APPLY');
|
|
287
|
+
assert.match(preview, /VERIFY \(preserved exactly as declared\): coverage-check/, 'the outcome is NAMED');
|
|
288
|
+
assert.match(preview, /DIFFERENT copy of the tool/, 'and says what it actually found');
|
|
289
|
+
assert.match(preview, /NOT final-run-capable/);
|
|
290
|
+
assert.doesNotMatch(preview, /already final-run-capable/);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('a vendored review-state is preserved too — and is never advised to "add it" on top of itself', () => {
|
|
294
|
+
const vendored = { id: 'review-state', title: 'RS', cmd: vendoredCmd('review-state') };
|
|
295
|
+
const analysis = buildMigrationPlan([UNIT_DONE, vendored], KIT_TOOLS, PROJECT);
|
|
296
|
+
assert.deepEqual(analysis.externalCoreChecks.map((c) => c.name), ['review-state']);
|
|
297
|
+
assert.equal(analysis.hasReviewState, false, 'the INSTALLED review-state is still not declared');
|
|
298
|
+
assert.equal(analysis.finalCapable, false);
|
|
299
|
+
const preview = formatPreview(analysis, 'APPLY');
|
|
300
|
+
assert.match(preview, /VERIFY \(preserved exactly as declared\): review-state/);
|
|
301
|
+
assert.doesNotMatch(preview, /Add it \(paste-ready\)/, 'a second review-state entry is the ambiguity, not the remedy');
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('a core check naming a path nothing resolves is NO claim — fail-closed exactly where --final is', () => {
|
|
305
|
+
// A lexical path compare called this canonical: the file need not exist to compare equal after
|
|
306
|
+
// resolve(). The migration then promised final-run-capability over a cmd --final cannot run.
|
|
307
|
+
const ghost = { id: 'review-state', title: 'RS', cmd: `node "${join(KIT_TOOLS, 'nowhere', 'review-state.mjs')}" --check` };
|
|
308
|
+
const analysis = buildMigrationPlan([UNIT_DONE, ghost], KIT_TOOLS, PROJECT);
|
|
309
|
+
assert.equal(analysis.hasReviewState, false, 'an unresolvable path is never the installed tool');
|
|
310
|
+
assert.deepEqual(analysis.externalCoreChecks, [], 'nor evidence that the tool lives somewhere else');
|
|
311
|
+
assert.ok(analysis.customized.some((g) => g.id === 'review-state'), 'it is reported as an entry the tool cannot verify');
|
|
312
|
+
assert.equal(analysis.finalCapable, false);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('a vendored checker never produces the lcov it reads — the declared pair stays INERT', () => {
|
|
316
|
+
const vendored = { id: 'coverage-check', title: 'CC', cmd: vendoredCmd('coverage-check'), lcovProducer: true };
|
|
317
|
+
const analysis = buildMigrationPlan(
|
|
318
|
+
[{ id: 'lint', title: 'L', cmd: 'eslint .' }, INSTALLED_REVIEW_STATE, vendored],
|
|
319
|
+
KIT_TOOLS,
|
|
320
|
+
PROJECT,
|
|
321
|
+
);
|
|
322
|
+
assert.equal(analysis.hasProducer, false, 'a marker on the checker itself never self-pairs, whichever copy it is');
|
|
323
|
+
assert.equal(analysis.checkerInert, true, 'a checker over nothing that writes the lcov passes verifying nothing');
|
|
324
|
+
assert.match(formatPreview(analysis, 'APPLY'), /INERT/);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('producer-after-vendored-checker: a producer declared AFTER a vendored checker never covers it', () => {
|
|
328
|
+
// A canonical checker is always MOVED last, so "a producer exists" answers "a producer runs
|
|
329
|
+
// first" for it. A vendored checker is left where the deployment put it, so the position-blind
|
|
330
|
+
// answer reported a live pair over a checker that reads the lcov before anything writes one.
|
|
331
|
+
const vendored = { id: 'coverage-check', title: 'CC', cmd: vendoredCmd('coverage-check') };
|
|
332
|
+
const after = buildMigrationPlan([INSTALLED_REVIEW_STATE, vendored, UNIT_DONE], KIT_TOOLS, PROJECT);
|
|
333
|
+
assert.equal(after.hasProducer, true, 'a producer IS declared somewhere');
|
|
334
|
+
assert.equal(after.checkerInert, true, 'but not before the checker that reads what it writes');
|
|
335
|
+
assert.equal(after.finalCapable, false);
|
|
336
|
+
assert.match(formatPreview(after, 'APPLY'), /INERT/);
|
|
337
|
+
|
|
338
|
+
const before = buildMigrationPlan([UNIT_DONE, INSTALLED_REVIEW_STATE, vendored], KIT_TOOLS, PROJECT);
|
|
339
|
+
assert.equal(before.checkerInert, false, 'the same entries in producer-first order are a live pair');
|
|
340
|
+
assert.doesNotMatch(formatPreview(before, 'APPLY'), /INERT/);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it('the INERT warning names the edit the reader must actually make — order, not a missing gate', () => {
|
|
344
|
+
// The two ways to be inert need two sentences. Telling someone whose suite gate is already
|
|
345
|
+
// declared to declare it again sends them to fix nothing while the real defect stays.
|
|
346
|
+
const vendored = { id: 'coverage-check', title: 'CC', cmd: vendoredCmd('coverage-check') };
|
|
347
|
+
const misordered = formatPreview(buildMigrationPlan([INSTALLED_REVIEW_STATE, vendored, UNIT_DONE], KIT_TOOLS, PROJECT), 'APPLY');
|
|
348
|
+
assert.match(misordered, /runs AFTER this entry/, 'the ORDER is named as the defect, on the row it belongs to');
|
|
349
|
+
assert.match(misordered, /a checker belongs LAST, after its producer/, 'and the remedy is the reorder');
|
|
350
|
+
assert.doesNotMatch(misordered, /declare the suite gate/, 'never advise declaring a gate that is already there');
|
|
351
|
+
|
|
352
|
+
const absent = formatPreview(buildMigrationPlan([INSTALLED_REVIEW_STATE, vendored], KIT_TOOLS, PROJECT), 'APPLY');
|
|
353
|
+
assert.match(absent, /no declared gate PRODUCES the lcov/, 'with nothing producing, the old sentence still holds');
|
|
354
|
+
assert.match(absent, /declare the suite gate/);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it('a mixed declaration renders ONE edit, never a removal and a reorder at once', () => {
|
|
358
|
+
// The two folds met here: canonicalTwin asks for a removal, the inert arm asked for a reorder,
|
|
359
|
+
// and a preview carrying both leaves the reader with no unambiguous next step.
|
|
360
|
+
const canonical = { id: 'coverage-check', title: 'CC', cmd: installedCmd('coverage-check') };
|
|
361
|
+
const vendored = { id: 'coverage-check-vendor', title: 'CCV', cmd: vendoredCmd('coverage-check') };
|
|
362
|
+
const analysis = buildMigrationPlan([INSTALLED_REVIEW_STATE, vendored, UNIT_DONE, canonical], KIT_TOOLS, PROJECT);
|
|
363
|
+
assert.deepEqual(analysis.externalCoreChecks.map((c) => [c.canonicalTwin, c.inert]), [[true, true]]);
|
|
364
|
+
assert.equal(analysis.canonicalCheckerInert, false, 'the canonical checker ends up last, after the producer');
|
|
365
|
+
const preview = formatPreview(analysis, 'APPLY');
|
|
366
|
+
assert.match(preview, /Remove THIS entry by hand/);
|
|
367
|
+
assert.match(preview, /the ONE edit that resolves both/);
|
|
368
|
+
assert.doesNotMatch(preview, /belongs LAST, after its producer/, 'no second, contradictory edit');
|
|
369
|
+
assert.doesNotMatch(preview, /declare the suite gate/);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it('a canonical checker declared BESIDE the vendored one changes the recovery — remove, never repoint', () => {
|
|
373
|
+
// Repointing the vendored cmd at the installed copy would leave TWO canonical checkers, and
|
|
374
|
+
// --final accepts exactly one; a recovery that cannot converge is worse than none.
|
|
375
|
+
const canonical = { id: 'coverage-check', title: 'CC', cmd: installedCmd('coverage-check') };
|
|
376
|
+
const vendored = { id: 'coverage-check-vendor', title: 'CCV', cmd: vendoredCmd('coverage-check') };
|
|
377
|
+
const analysis = buildMigrationPlan([UNIT_DONE, INSTALLED_REVIEW_STATE, vendored, canonical], KIT_TOOLS, PROJECT);
|
|
378
|
+
assert.deepEqual(analysis.externalCoreChecks.map((c) => c.canonicalTwin), [true]);
|
|
379
|
+
const preview = formatPreview(analysis, 'APPLY');
|
|
380
|
+
assert.match(preview, /accepts exactly ONE canonical check/);
|
|
381
|
+
assert.match(preview, /Remove THIS entry by hand/);
|
|
382
|
+
assert.doesNotMatch(preview, /either repoint the cmd/, 'the non-convergent recovery is not offered here');
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it('a vendored deployment carries the SAME commit-guard consequence a customized one does', () => {
|
|
386
|
+
// Both end in a declaration --final refuses, which mints no receipt, which makes the guard
|
|
387
|
+
// refuse every commit. Naming the consequence for one and not the other is a false asymmetry.
|
|
388
|
+
const vendored = { id: 'coverage-check', title: 'CC', cmd: vendoredCmd('coverage-check') };
|
|
389
|
+
const preview = formatPreview(buildMigrationPlan([UNIT_DONE, INSTALLED_REVIEW_STATE, vendored], KIT_TOOLS, PROJECT), 'APPLY');
|
|
390
|
+
assert.match(preview, /do NOT install the commit guard/);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it('the plan builder REFUSES without the project root — a relative cmd cannot be resolved without it', () => {
|
|
394
|
+
assert.throws(() => buildMigrationPlan([UNIT_DONE], KIT_TOOLS), /project root/);
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
|
|
206
398
|
describe('migrate-gates — preview writes NOTHING; apply is atomic and complete', () => {
|
|
207
399
|
it('the dry-run default leaves gates.json byte-identical and prints the plan + the apply hint', () => {
|
|
208
400
|
const root = mkProject([LEGACY_LEDGER, UNIT]);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_README": "Per-project gate declaration: the ordered list of verification commands (tests, validators, scanners, docs checks) that must be green before a commit. Run them all in one batch with the family gate runner (the composition root's `gates` command); re-run one with `--only <id>`. Each entry is { id, title, cmd }: `id` = a unique kebab-case handle, `title` = a short human label, `cmd` = ONE bash command line — gates are spawned via bash (brace/glob expansion works; a host without bash gets a loud preflight error, never a silent reinterpretation under another shell). This file declares WHAT to check, never who executes it — the schema has no lane/model/routing fields and rejects unknown keys loudly. Trust posture: the runner executes this project's OWN declared commands with the caller's privileges — a batching convenience over commands the project already runs by hand, not a sandbox. Strict JSON — no comments.",
|
|
2
|
+
"_README": "Per-project gate declaration: the ordered list of verification commands (tests, validators, scanners, docs checks) that must be green before a commit. Run them all in one batch with the family gate runner (the composition root's `gates` command); re-run one with `--only <id>`. Each entry is { id, title, cmd } plus ONE optional key: `id` = a unique kebab-case handle, `title` = a short human label, `cmd` = ONE bash command line — gates are spawned via bash (brace/glob expansion works; a host without bash gets a loud preflight error, never a silent reinterpretation under another shell) — and `lcovProducer` (boolean, optional) DECLARES that this gate writes the lcov the coverage checker reads, for a suite the kit's closed `node --test` recognition cannot read on its own. Only the literal true claims it, and it widens what the declaration may CLAIM, never what a run CERTIFIES: a marked gate that produces no lcov still ends `skipped-no-lcov` at run time. This file declares WHAT to check, never who executes it — the schema has no lane/model/routing fields and rejects unknown keys loudly. Trust posture: the runner executes this project's OWN declared commands with the caller's privileges — a batching convenience over commands the project already runs by hand, not a sandbox. Strict JSON — no comments.",
|
|
3
3
|
"gates": []
|
|
4
4
|
}
|