@sabaiway/agent-workflow-memory 4.1.0 → 4.2.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 +31 -0
- package/SKILL.md +1 -1
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/scripts/migrate-gates.mjs +102 -10
- package/references/scripts/migrate-gates.test.mjs +37 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,37 @@ All notable changes to the memory substrate. Versions are this **package's** npm
|
|
|
4
4
|
they are distinct from the **deployment-lineage** stamp written into a project's
|
|
5
5
|
`docs/ai/.memory-version` (which tracks the shared `agent-workflow` lineage, head `3.0.0`).
|
|
6
6
|
|
|
7
|
+
## 4.2.0 — the gates migration stops handing you a coverage checker with nothing to read (AD-089)
|
|
8
|
+
|
|
9
|
+
`migrate-gates.mjs` added the canonical `coverage-check` gate to any legacy declaration that lacked
|
|
10
|
+
one — whether or not anything in that declaration would ever WRITE the lcov the checker reads. The
|
|
11
|
+
result passes: the gate reports `skipped-no-lcov` and exits 0, so a migrated project came out with a
|
|
12
|
+
green matrix that certified nothing. This release stops the migration creating that pair, and makes
|
|
13
|
+
it say so when it finds one already there.
|
|
14
|
+
|
|
15
|
+
- **The checker is added only over a declaration that produces the lcov.** With no producer it is
|
|
16
|
+
WITHHELD, loudly, with the exact suite-gate line to declare first and an invitation to re-run the
|
|
17
|
+
migration afterwards.
|
|
18
|
+
- **An already-declared checker over no producer is reported as INERT** — same dead pair, made by an
|
|
19
|
+
earlier deployment rather than by this migration. Nothing is removed for you; the warning names
|
|
20
|
+
the remedy, and the result is no longer called final-run-capable.
|
|
21
|
+
- **`finalCapable` now means what it says.** It was computed from the review-state check alone, so a
|
|
22
|
+
withheld or inert checker still reported a final-run-capable result. The review-state warning is
|
|
23
|
+
now keyed on its own condition instead of riding that flag.
|
|
24
|
+
- **The "no canonical `unit-tests` entry" note stops firing over a working producer.** It was keyed
|
|
25
|
+
on the entry ID, but a producer is recognised under any id — the note was sending people to fix
|
|
26
|
+
something that was already correct.
|
|
27
|
+
- **The producer vocabulary is exported** (`COVERAGE_PRODUCER_BODY`, `matchesCoverageProducer`) as a
|
|
28
|
+
CLOSED set of the full command forms this family emits, never a substring probe. Its tail rule is
|
|
29
|
+
a positive path-shaped grammar rather than an operator blocklist, because
|
|
30
|
+
`node --test <flags> && rm -f <lcov>` runs the suite and then deletes the file. The scope is
|
|
31
|
+
stated in the source: recognising a producer means "configured with the reporters", never "the
|
|
32
|
+
lcov survives the command".
|
|
33
|
+
|
|
34
|
+
This package stays standalone — it imports nothing from the kit, and the kit imports nothing from
|
|
35
|
+
here. The kit carries its own byte-identical copy of the block above, held equal by a text drift
|
|
36
|
+
guard rather than by an import.
|
|
37
|
+
|
|
7
38
|
## 4.1.0 — the ADR rotation carries your inbound links with it (AD-087)
|
|
8
39
|
|
|
9
40
|
Rotating `decisions.md` used to be a link-breaking event: `archive-decisions.mjs` moved ADR
|
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: agent-workflow-memory
|
|
|
3
3
|
description: Deploy or upgrade a portable AI-agent memory substrate in any project — an entry-point `AGENTS.md` (+ `CLAUDE.md` alias) and a structured `docs/ai/` context store with cap/archive/index enforcement. Use when the user wants to bootstrap `docs/ai/`, set up the Memory Map and session protocols, install the docs-rotation pre-commit hook, or run `/agent-workflow-memory` / `/agent-workflow-memory upgrade`. Triggers on "set up the memory system", "deploy the AI memory here", "bootstrap docs/ai", "upgrade the memory substrate". This is the substrate only — the workflow methodology (plan→execute→review, queue, Cleanup) is owned elsewhere and injected into AGENTS.md by the family composition root.
|
|
4
4
|
disable-model-invocation: true
|
|
5
5
|
metadata:
|
|
6
|
-
version: '4.
|
|
6
|
+
version: '4.2.0'
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# agent-workflow-memory
|
package/capability.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sabaiway/agent-workflow-memory",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"description": "Portable, cross-agent memory substrate for AI coding agents — an AGENTS.md entry point + docs/ai context with cap/archive/index enforcement, deployable standalone or as part of the agent-workflow family. The memory layer of the agent-workflow family.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agents",
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
// COMPLETELY in one consented step: it REMOVES the canonical legacy entries, EXTENDS the
|
|
6
6
|
// canonical `unit-tests` cmd with the built-in lcov reporters (the D3(d) coverage source), and
|
|
7
7
|
// ADDS the coverage-check gate LAST (removal alone is not a migration — the result must satisfy
|
|
8
|
-
// `run-gates --final` and carry a working commit path).
|
|
8
|
+
// `run-gates --final` and carry a working commit path). The checker is added ONLY over a
|
|
9
|
+
// declaration that PRODUCES the lcov it reads; with no producer it is WITHHELD with a loud
|
|
10
|
+
// warning, because a checker with no producer passes (`skipped-no-lcov`) while verifying nothing.
|
|
9
11
|
//
|
|
10
12
|
// Matching is by the DOCUMENTED cmd forms ONLY (hand-wired history included — never by seed
|
|
11
13
|
// provenance): a legacy entry is `node <path>/review-ledger.mjs --check` or
|
|
@@ -45,12 +47,69 @@ export const LEGACY_FORMS = Object.freeze([
|
|
|
45
47
|
{ name: 'fold-completeness', re: legacyRe('fold-completeness\\.mjs') },
|
|
46
48
|
]);
|
|
47
49
|
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
//
|
|
50
|
+
// coverage-producer canon >>> BEGIN drift-guarded region
|
|
51
|
+
// Authored TWICE, byte-identically: in the memory substrate's references/scripts/migrate-gates.mjs
|
|
52
|
+
// and in the composition root's tools/coverage-producer.mjs. Neither side imports the other — the
|
|
53
|
+
// substrate is standalone and must not depend on the root, and the root must not import mirrored
|
|
54
|
+
// bytes — so a TEXT drift guard beside the root's copy holds them equal. Edit BOTH, then re-run the
|
|
55
|
+
// mirror sync.
|
|
56
|
+
//
|
|
57
|
+
// The destination is written against AW_GIT_DIR, which run-gates exports to every gate child on a
|
|
58
|
+
// plain run AND on --final (AW_LCOV_FILE is --final only), so one cmd survives the unmet
|
|
59
|
+
// producer-variable preflight in both modes. The explicit stdout reporter is not decoration:
|
|
60
|
+
// without it the lcov reporter swallows the human TAP/spec stream.
|
|
51
61
|
export const UNIT_TESTS_COVERAGE_FLAGS =
|
|
52
62
|
'--experimental-test-coverage --test-reporter=lcov --test-reporter-destination="$AW_GIT_DIR/agent-workflow-lcov.info" --test-reporter=spec --test-reporter-destination=stdout';
|
|
53
63
|
|
|
64
|
+
// The ONE suite body that produces that lcov with no extra dependency.
|
|
65
|
+
export const COVERAGE_PRODUCER_BODY = `node --test ${UNIT_TESTS_COVERAGE_FLAGS}`;
|
|
66
|
+
|
|
67
|
+
// The per-PM exec wrappers a fill offer puts that body behind. Recognition must cover every form
|
|
68
|
+
// the kit has EMITTED, so the prefixes are matched literally; gates-init's execCmdFor stays the one
|
|
69
|
+
// EMITTER and is bound to this list by a named acceptance test, never by a second grammar.
|
|
70
|
+
const PRODUCER_EXEC_PREFIXES = Object.freeze([
|
|
71
|
+
'COREPACK_ENABLE_NETWORK=0 npm exec --offline --script-shell /bin/sh -- ',
|
|
72
|
+
'COREPACK_ENABLE_NETWORK=0 pnpm exec -- ',
|
|
73
|
+
'COREPACK_ENABLE_NETWORK=0 yarn exec -- ',
|
|
74
|
+
]);
|
|
75
|
+
|
|
76
|
+
// A trailing suffix is the project's own test paths; a leading one would mean the body is not what
|
|
77
|
+
// this cmd runs. The tail passes a POSITIVE closed grammar — every whitespace-separated token must
|
|
78
|
+
// be path-shaped — never an operator blocklist: `node --test <flags> && rm -f <lcov>` runs the
|
|
79
|
+
// suite and then DELETES the file, so an open-ended tail would certify a producer that leaves
|
|
80
|
+
// nothing behind, and scanning for operator BYTES would put the incomplete-scan failure on the
|
|
81
|
+
// unsafe side (a missed operator is a dead pair) instead of the mild one (an unrecognised
|
|
82
|
+
// legitimate tail merely withholds the offer — add the gate by hand).
|
|
83
|
+
// The token set is everything that appears in a PATH or a glob and can never sequence, redirect or
|
|
84
|
+
// substitute a command — quoting and `~` included, `( ) $ ` ; & | < > # \` excluded. SCOPE, stated
|
|
85
|
+
// exactly: the screen judges each token's SOURCE bytes. Quote removal adds none, but brace SEQUENCE
|
|
86
|
+
// expansion does — `{Y..a}` yields ``[ \ ] ^ _ ` `` (probed) — so "no new bytes" would be a false
|
|
87
|
+
// claim. What holds is the property that matters: bash does not re-scan an expansion result as
|
|
88
|
+
// syntax, so a byte arriving that way is literal argument DATA, never an operator. The leading-`-`
|
|
89
|
+
// exclusion is weaker still — a FIRST-ORDER screen only, defeated by `'--flag'` and
|
|
90
|
+
// `{path,--flag}`. It is kept because the tail is the project's test PATHS and it costs only a loud
|
|
91
|
+
// withhold. Deciding an argument's post-expansion identity needs a shell lexer, which this family
|
|
92
|
+
// deliberately has NOWHERE (AD-079). So the claim is "configured with the reporters", never "the
|
|
93
|
+
// lcov survives the command"; a run that produces none is caught honestly at runtime as
|
|
94
|
+
// `skipped-no-lcov`.
|
|
95
|
+
const PRODUCER_PATH_TOKEN = /^(?!-)[A-Za-z0-9_./*{},:@+=~?[\]!'"-]+$/;
|
|
96
|
+
const pathShapedTail = (tail) => tail === '' || tail.split(/[ \t]+/).every((token) => PRODUCER_PATH_TOKEN.test(token));
|
|
97
|
+
const carriesProducerBody = (text) =>
|
|
98
|
+
text === COVERAGE_PRODUCER_BODY ||
|
|
99
|
+
(text.startsWith(`${COVERAGE_PRODUCER_BODY} `) && pathShapedTail(text.slice(COVERAGE_PRODUCER_BODY.length).trim()));
|
|
100
|
+
|
|
101
|
+
// matchesCoverageProducer(cmd) → CLOSED-WORLD over the full command forms the kit emits, never a
|
|
102
|
+
// substring probe: `echo "$AW_GIT_DIR/agent-workflow-lcov.info"`, a half-written reporter flag set,
|
|
103
|
+
// or the path as a bare substring must all read as NOT a producer — otherwise the checker is
|
|
104
|
+
// declared over a gate that writes nothing and then PASSES while certifying nothing.
|
|
105
|
+
export const matchesCoverageProducer = (cmd) => {
|
|
106
|
+
if (typeof cmd !== 'string') return false;
|
|
107
|
+
const trimmed = cmd.trim();
|
|
108
|
+
if (carriesProducerBody(trimmed)) return true;
|
|
109
|
+
return PRODUCER_EXEC_PREFIXES.some((prefix) => trimmed.startsWith(prefix) && carriesProducerBody(trimmed.slice(prefix.length)));
|
|
110
|
+
};
|
|
111
|
+
// coverage-producer canon <<< END drift-guarded region
|
|
112
|
+
|
|
54
113
|
// The RETIRED kit-owned git-dir stores the deleted machinery wrote — dead data a consumer's
|
|
55
114
|
// upgrade would otherwise strand forever. The migration cleans them (consented via the preview;
|
|
56
115
|
// ENOENT is a silent no-op; any other unlink error is reported loudly but never fails the
|
|
@@ -147,13 +206,20 @@ export const buildMigrationPlan = (gates, kitToolsDir) => {
|
|
|
147
206
|
plan.push({ action: 'keep', entry: gate, reason: null });
|
|
148
207
|
}
|
|
149
208
|
const kept = plan.filter((r) => r.action === 'keep' || r.action === 'extend');
|
|
209
|
+
// The checker READS an lcov; something has to WRITE it. Adding the checker over a declaration
|
|
210
|
+
// with no producer creates the dead pair — the gate PASSES (`skipped-no-lcov`) and certifies
|
|
211
|
+
// nothing, so the migration withholds it and says why instead.
|
|
212
|
+
const hasProducer = kept.some((r) => matchesCoverageProducer(r.entry.cmd));
|
|
150
213
|
let collision = null;
|
|
214
|
+
let checkerWithheld = false;
|
|
151
215
|
if (checkerRow === null) {
|
|
152
216
|
// A surviving NON-canonical entry already holding the checker's id blocks the add — two
|
|
153
217
|
// `coverage-check` rows would be ambiguous; the customized entry must be resolved by hand
|
|
154
218
|
// FIRST (the caller turns this into a loud STOP on preview and apply alike).
|
|
155
219
|
if (kept.some((r) => r.entry.id === 'coverage-check')) {
|
|
156
220
|
collision = 'coverage-check';
|
|
221
|
+
} else if (!hasProducer) {
|
|
222
|
+
checkerWithheld = true;
|
|
157
223
|
} else {
|
|
158
224
|
plan.push({
|
|
159
225
|
action: 'add',
|
|
@@ -165,8 +231,23 @@ export const buildMigrationPlan = (gates, kitToolsDir) => {
|
|
|
165
231
|
checkerRow.action = 'move';
|
|
166
232
|
checkerRow.reason = 'the canonical checker must be the LAST declared gate (nothing may run after it consumed the lcov)';
|
|
167
233
|
}
|
|
234
|
+
// An ALREADY-declared checker over no producer is the same dead pair the withhold prevents — an
|
|
235
|
+
// earlier deployment could have created it. The migration removes no declared gate, so it reports
|
|
236
|
+
// the inertness and refuses to call the result final-run-capable.
|
|
237
|
+
const checkerInert = checkerRow !== null && !hasProducer;
|
|
168
238
|
const reviewStateCandidate = `{ "id": "review-state", "title": "Review receipts converged (D3(b))", "cmd": "node \\"${join(kitToolsDir, 'review-state.mjs')}\\" --check" }`;
|
|
169
|
-
return {
|
|
239
|
+
return {
|
|
240
|
+
plan,
|
|
241
|
+
customized,
|
|
242
|
+
unitTestsExtended,
|
|
243
|
+
finalCapable: hasReviewState && !checkerWithheld && !checkerInert,
|
|
244
|
+
hasProducer,
|
|
245
|
+
hasReviewState,
|
|
246
|
+
checkerWithheld,
|
|
247
|
+
checkerInert,
|
|
248
|
+
reviewStateCandidate,
|
|
249
|
+
collision,
|
|
250
|
+
};
|
|
170
251
|
};
|
|
171
252
|
|
|
172
253
|
export const resultingGates = (plan) => {
|
|
@@ -184,7 +265,7 @@ const customizedRecovery = (gate) =>
|
|
|
184
265
|
? `declare the canonical suite gate by hand so the coverage contract is verifiable: node --test ${UNIT_TESTS_COVERAGE_FLAGS} <your test paths>`
|
|
185
266
|
: 'remove the entry, or repoint it at a living check — the review-ledger / fold-completeness tools no longer exist.';
|
|
186
267
|
|
|
187
|
-
const warningLines = ({ customized, finalCapable, reviewStateCandidate }) => {
|
|
268
|
+
const warningLines = ({ customized, finalCapable, hasReviewState = finalCapable, checkerWithheld = false, checkerInert = false, reviewStateCandidate }) => {
|
|
188
269
|
const lines = [];
|
|
189
270
|
for (const gate of customized) {
|
|
190
271
|
lines.push(` CUSTOMIZED (untouched): ${gate.id}: ${gate.cmd}`);
|
|
@@ -193,7 +274,15 @@ const warningLines = ({ customized, finalCapable, reviewStateCandidate }) => {
|
|
|
193
274
|
if (customized.length) {
|
|
194
275
|
lines.push(' IMPORTANT: do NOT install the commit guard until every customized entry above is resolved — a declaration that cannot pass run-gates --final would block every commit.');
|
|
195
276
|
}
|
|
196
|
-
if (
|
|
277
|
+
if (checkerWithheld) {
|
|
278
|
+
lines.push(' WARNING: the canonical coverage-check gate was NOT added — no declared gate would PRODUCE the lcov it reads, and a checker with no producer passes while verifying nothing. Declare the suite gate first, then re-run this migration:');
|
|
279
|
+
lines.push(` node --test ${UNIT_TESTS_COVERAGE_FLAGS} <your test paths>`);
|
|
280
|
+
}
|
|
281
|
+
if (checkerInert) {
|
|
282
|
+
lines.push(' WARNING: the DECLARED coverage-check gate is INERT — no declared gate PRODUCES the lcov it reads, so it passes while verifying nothing. Nothing is removed for you; declare the suite gate:');
|
|
283
|
+
lines.push(` node --test ${UNIT_TESTS_COVERAGE_FLAGS} <your test paths>`);
|
|
284
|
+
}
|
|
285
|
+
if (!hasReviewState) {
|
|
197
286
|
lines.push(' WARNING: the result is NOT final-run-capable — no canonical review-state check is declared. Add it (paste-ready), then run-gates --final can mint the receipt:');
|
|
198
287
|
lines.push(` ${reviewStateCandidate}`);
|
|
199
288
|
}
|
|
@@ -201,7 +290,7 @@ const warningLines = ({ customized, finalCapable, reviewStateCandidate }) => {
|
|
|
201
290
|
};
|
|
202
291
|
|
|
203
292
|
export const formatPreview = (analysis, applyHint) => {
|
|
204
|
-
const { plan, unitTestsExtended, finalCapable, retiredStores = [] } = analysis;
|
|
293
|
+
const { plan, unitTestsExtended, finalCapable, hasProducer = unitTestsExtended, retiredStores = [] } = analysis;
|
|
205
294
|
const lines = ['[agent-workflow] legacy gates.json migration preview (dry-run — nothing was written):'];
|
|
206
295
|
const acted = plan.filter((r) => r.action !== 'keep');
|
|
207
296
|
for (const r of acted) {
|
|
@@ -217,7 +306,9 @@ export const formatPreview = (analysis, applyHint) => {
|
|
|
217
306
|
: ' nothing to migrate mechanically — no canonical legacy entries and no retired stores; the warnings below still need a hand.',
|
|
218
307
|
);
|
|
219
308
|
}
|
|
220
|
-
|
|
309
|
+
// Keyed on the ID, but a PRODUCER is recognized under any id — repeating this advice over a
|
|
310
|
+
// working producer sends the user to fix nothing.
|
|
311
|
+
if (!hasProducer && !unitTestsExtended && !plan.some((r) => r.entry.id === 'unit-tests')) {
|
|
221
312
|
lines.push(' note: no canonical `unit-tests` entry found — declare your suite gate with the lcov reporters by hand (the coverage-check gate reads the file it produces).');
|
|
222
313
|
}
|
|
223
314
|
lines.push(...warningLines(analysis));
|
|
@@ -317,7 +408,8 @@ Usage:
|
|
|
317
408
|
Default is a dry-run PREVIEW (writes nothing). --apply rewrites ${GATES_REL} atomically:
|
|
318
409
|
canonical legacy entries (review-ledger / fold-completeness --check, matched by their documented
|
|
319
410
|
single-invocation forms) are REMOVED; the canonical unit-tests cmd gains the built-in lcov
|
|
320
|
-
reporters; the coverage-check gate is ADDED last (resolved, QUOTED path)
|
|
411
|
+
reporters; the coverage-check gate is ADDED last (resolved, QUOTED path) — and WITHHELD, loudly,
|
|
412
|
+
when no declared gate produces the lcov it reads. Customized entries are
|
|
321
413
|
NEVER auto-touched — the preview names each with a paste-ready recovery, and the commit guard
|
|
322
414
|
must not be installed until they are resolved.`;
|
|
323
415
|
|
|
@@ -12,6 +12,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
12
12
|
import {
|
|
13
13
|
LEGACY_FORMS,
|
|
14
14
|
UNIT_TESTS_COVERAGE_FLAGS,
|
|
15
|
+
COVERAGE_PRODUCER_BODY,
|
|
15
16
|
RETIRED_STORE_BASENAMES,
|
|
16
17
|
findRetiredStores,
|
|
17
18
|
buildMigrationPlan,
|
|
@@ -71,6 +72,42 @@ describe('migrate-gates — the pure migration plan', () => {
|
|
|
71
72
|
assert.ok(!again.plan.some((r) => r.action === 'add'), 'a declaration already carrying the checker gains no duplicate');
|
|
72
73
|
});
|
|
73
74
|
|
|
75
|
+
it('a declaration with NO producer never GAINS the checker — the pair is declared together or not at all', () => {
|
|
76
|
+
const npmSuite = { id: 'suite', title: 'S', cmd: 'npm test' };
|
|
77
|
+
const analysis = buildMigrationPlan([LEGACY_LEDGER, npmSuite], KIT_TOOLS);
|
|
78
|
+
assert.ok(!analysis.plan.some((r) => r.action === 'add'), 'no checker is added over a declaration that produces no lcov');
|
|
79
|
+
assert.deepEqual(resultingGates(analysis.plan).map((g) => g.id), ['suite'], 'the legacy entry still goes, nothing dead arrives');
|
|
80
|
+
assert.equal(analysis.finalCapable, false, 'a declaration with no checker is not final-run-capable');
|
|
81
|
+
const preview = formatPreview(analysis, 'APPLY');
|
|
82
|
+
assert.match(preview, /coverage-check/, 'the withheld checker is named');
|
|
83
|
+
assert.match(preview, /produce/i, 'the preview says WHY — no gate produces the lcov it would read');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('an ALREADY-declared checker over no producer is reported INERT, is never removed, and is not final-run-capable', () => {
|
|
87
|
+
const checker = { id: 'coverage-check', title: 'CC', cmd: `node "${join(KIT_TOOLS, 'coverage-check.mjs')}" --check` };
|
|
88
|
+
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);
|
|
90
|
+
assert.equal(analysis.finalCapable, false, 'a review-state present must NOT make an inert pair read as final-run-capable');
|
|
91
|
+
assert.ok(resultingGates(analysis.plan).some((g) => g.id === 'coverage-check'), 'the declared checker is never removed');
|
|
92
|
+
const preview = formatPreview(analysis, 'APPLY');
|
|
93
|
+
assert.match(preview, /INERT/, 'the inert pair is named');
|
|
94
|
+
assert.match(preview, /--experimental-test-coverage/, 'the paste-ready suite cmd is carried');
|
|
95
|
+
assert.doesNotMatch(preview, /already final-run-capable/);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('a gates-init-shaped producer (the exec-wrapped offer form) is recognized — the checker IS added over it', () => {
|
|
99
|
+
const offered = {
|
|
100
|
+
id: 'test',
|
|
101
|
+
title: 'T',
|
|
102
|
+
cmd: `COREPACK_ENABLE_NETWORK=0 npm exec --offline --script-shell /bin/sh -- ${COVERAGE_PRODUCER_BODY}`,
|
|
103
|
+
};
|
|
104
|
+
const analysis = buildMigrationPlan([offered], KIT_TOOLS);
|
|
105
|
+
assert.deepEqual(resultingGates(analysis.plan).map((g) => g.id), ['test', 'coverage-check']);
|
|
106
|
+
// The `no canonical unit-tests entry` advice is keyed on the ID, but a producer is recognized
|
|
107
|
+
// under ANY id — repeating the advice over a working producer sends the user to fix nothing.
|
|
108
|
+
assert.doesNotMatch(formatPreview(analysis, 'APPLY'), /declare your suite gate with the lcov reporters by hand/);
|
|
109
|
+
});
|
|
110
|
+
|
|
74
111
|
it('a CUSTOMIZED dead-tool reference (compound form) is kept untouched and reported', () => {
|
|
75
112
|
const analysis = buildMigrationPlan([CUSTOM], KIT_TOOLS);
|
|
76
113
|
assert.equal(analysis.plan.find((r) => r.entry.id === 'my-ledger-wrap').action, 'keep');
|