@sabaiway/agent-workflow-kit 14.25.0 → 14.25.2
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 +23 -0
- package/SKILL.md +1 -1
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/modes/mcp.md +1 -1
- package/references/scripts/migrate-gates.mjs +6 -2
- package/references/scripts/migrate-gates.test.mjs +4 -0
- package/tools/mcp.mjs +4 -2
- package/tools/payload-prune.mjs +5 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,29 @@ Semantically versioned ([semver](https://semver.org)), newest first. The `versio
|
|
|
4
4
|
is the current release. `upgrade` mode reads a project's `docs/ai/.workflow-version` and applies
|
|
5
5
|
every `migrations/<version>-<slug>.md` newer than it, in semver order.
|
|
6
6
|
|
|
7
|
+
## 14.25.2 — the gate migration names `"lcovProducer": true` in the four hints that print the paste-ready suite cmd (AD-175)
|
|
8
|
+
|
|
9
|
+
- **A suite cmd the migration cannot recognise is no longer told only to rewrite itself.** When the upgrade's
|
|
10
|
+
`gates-migration` step (`references/scripts/migrate-gates.mjs`) finds no gate it recognises as the lcov producer, for
|
|
11
|
+
example a suite cmd that starts with `shopt -s globstar &&`, it asked only for the canonical `node --test` form. It
|
|
12
|
+
now also says: `or, if your suite cmd already writes that lcov in a form this check cannot recognise, add
|
|
13
|
+
"lcovProducer": true to that gate in docs/ai/gates.json`. The marker is the second producer form `gates.md` already
|
|
14
|
+
documents, and for a suite cmd that keeps a `shopt -s globstar &&` prefix the only one that works. The line rides the customized `unit-tests`
|
|
15
|
+
recovery, the INERT row of a vendored checker, and both coverage-check warnings; every existing sentence is unchanged
|
|
16
|
+
and no migration result changes.
|
|
17
|
+
|
|
18
|
+
## 14.25.1 — `init` reports what it did: a replaced MCP entry is labelled replaced, and a folder inside a kept folder prints no line of its own (AD-174)
|
|
19
|
+
|
|
20
|
+
- **The MCP apply report no longer shows the old entry as the current one.** After the `y` that replaces a differing
|
|
21
|
+
`agent-workflow` entry, `init` printed the old entry under `the entry standing in .mcp.json now:` while the file
|
|
22
|
+
already held the new one. It now prints `the entry this run replaced in .mcp.json:` and `the entry now in
|
|
23
|
+
.mcp.json`. The preview before the `y` is unchanged.
|
|
24
|
+
- **A folder inside a kept folder prints no line of its own.** A folder inside one of the kit's own folders
|
|
25
|
+
(`tools/`, `references/` and the like) that the package does not carry is still kept and reported as yours to
|
|
26
|
+
remove by hand, but the folders inside it no longer print a line each:
|
|
27
|
+
a home that kept an emptied `tools/manifest/fixtures/` printed 18 lines on every run and now prints one. A stray
|
|
28
|
+
file inside such a folder is still removed with its own line.
|
|
29
|
+
|
|
7
30
|
## 14.25.0 — `init`, run in a console from a project's folder, applies that project's harness configuration on one "y" (AD-173)
|
|
8
31
|
|
|
9
32
|
**`npx @sabaiway/agent-workflow-kit@latest init` gains a project step.** Run in your own console from a project's
|
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: agent-workflow-kit
|
|
|
3
3
|
description: Deploy or upgrade a portable AI-agent memory-and-workflow system in any project. Use when the user wants to bootstrap `docs/ai/` + an entry-point `AGENTS.md` (+ `CLAUDE.md` alias) + cap/archive/index enforcement in a new or existing repo, set up the Memory Map and session protocols, install the docs-rotation pre-commit hook, or run `/agent-workflow-kit` / `/agent-workflow-kit upgrade`. Triggers on phrases like "set up the memory system", "deploy the AI workflow here", "bootstrap docs/ai", "upgrade the workflow". New to the epic, story and task tier? Run `/agent-workflow-kit tier`. A typed decision with a confidence from Jev? Run `/agent-workflow-kit jev`.
|
|
4
4
|
disable-model-invocation: true
|
|
5
5
|
metadata:
|
|
6
|
-
version: '14.25.
|
|
6
|
+
version: '14.25.2'
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# agent-workflow-kit
|
package/capability.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sabaiway/agent-workflow-kit",
|
|
3
|
-
"version": "14.25.
|
|
3
|
+
"version": "14.25.2",
|
|
4
4
|
"description": "Portable, cross-agent memory & workflow for AI coding agents — Claude Code, Codex, Cursor, Devin Desktop. One command deploys an AGENTS.md entry point + docs/ai context with cap/archive/index enforcement into any repo.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agents",
|
package/references/modes/mcp.md
CHANGED
|
@@ -25,7 +25,7 @@ An existing `.mcp.json` and `.claude/settings.json` are written in place: each k
|
|
|
25
25
|
|
|
26
26
|
- **DIFFERING** — `.mcp.json` already carries an `"agent-workflow"` entry that **structurally differs** from ours (another kit copy, a hand-edited path, an added `env`, a `null`). The comparison is deliberately key-order-independent, so a re-serialized identical entry is the SAME registration, while any real difference in what would be launched is a difference. Without `--replace`, the run **STOPs unwritten, on both lanes**: silently changing what an MCP server launches is exactly what consent must not slide past.
|
|
27
27
|
With `--replace`, the preview prints the entry standing in `.mcp.json` now and the entry this registration declares, writes nothing and exits 0.
|
|
28
|
-
With `--replace --apply`, it replaces that entry, keeping every other server and key of `.mcp.json
|
|
28
|
+
With `--replace --apply`, it replaces that entry, keeping every other server and key of `.mcp.json`, and prints the old entry as the one it replaced and ours as the one now in the file.
|
|
29
29
|
This is the console item `mcp-channel.differing`: `init` previews it in the user's console and applies it on the terminal "y".
|
|
30
30
|
- **MASKED** — an OS sandbox injects a **character device** where `.mcp.json` would be (this is the normal condition of a sandboxed session, not a fault; see `${CLAUDE_SKILL_DIR}/references/modes/sandbox-masks.md`). Writing through a device node is refused by construction, so the mode **hands over what to merge, writes nothing, and exits 0**. The two halves of that handoff are worded differently, and the difference is load-bearing: `.claude/settings.json` was **observable — and read where present** — so its body is a real merge already carrying every foreign key it had, while the masked `.mcp.json` was not observable at all, so the mode hands over **the entry alone**, to be merged under `mcpServers` keeping every other server. A whole-file body would name only our server, and pasted as instructed it would delete every server the mask hid. Every other never-committable class (block device, FIFO, socket) takes the same arm. The handoff is scoped to `.mcp.json`: a masked `settings.json` is a **refusal**, because this mode can neither write it nor merge into what it cannot read.
|
|
31
31
|
|
|
@@ -461,12 +461,14 @@ export const resultingGates = (plan) => {
|
|
|
461
461
|
return [...kept, ...moved, ...added]; // move/add go LAST — the checker ends up the last declared gate
|
|
462
462
|
};
|
|
463
463
|
|
|
464
|
+
const MARKER_LANE = 'or, if your suite cmd already writes that lcov in a form this check cannot recognise, add "lcovProducer": true to that gate in docs/ai/gates.json';
|
|
465
|
+
|
|
464
466
|
// The per-entry customized recovery: a unit-tests-class entry gets the FULL canonical flag set
|
|
465
467
|
// (the intent is known — the cmd shape just cannot be verified); anything else gets the
|
|
466
468
|
// dead-tool recovery.
|
|
467
469
|
const customizedRecovery = (gate) =>
|
|
468
470
|
gate.id === 'unit-tests'
|
|
469
|
-
? `declare the canonical suite gate by hand so the coverage contract is verifiable: node --test ${UNIT_TESTS_COVERAGE_FLAGS} <your test paths
|
|
471
|
+
? `declare the canonical suite gate by hand so the coverage contract is verifiable: node --test ${UNIT_TESTS_COVERAGE_FLAGS} <your test paths> — ${MARKER_LANE}`
|
|
470
472
|
: 'remove the entry, or repoint it at a living check — the review-ledger / fold-completeness tools no longer exist.';
|
|
471
473
|
|
|
472
474
|
const warningLines = ({ customized, finalCapable, hasReviewState = finalCapable, hasProducer = false, checkerWithheld = false, canonicalCheckerInert = false, duplicateCheckers = 0, externalCoreChecks = [], reviewStateCandidate }) => {
|
|
@@ -495,7 +497,7 @@ const warningLines = ({ customized, finalCapable, hasReviewState = finalCapable,
|
|
|
495
497
|
} else if (inert && hasProducer) {
|
|
496
498
|
lines.push(' it is also INERT as declared: a gate DOES produce the lcov, but it runs AFTER this entry, so this checker reads nothing (or stale bytes) and passes while verifying nothing — a checker belongs LAST, after its producer.');
|
|
497
499
|
} else if (inert) {
|
|
498
|
-
lines.push(` it is also INERT as declared: no declared gate PRODUCES the lcov it reads, so it passes while verifying nothing — declare the suite gate: node --test ${UNIT_TESTS_COVERAGE_FLAGS} <your test paths
|
|
500
|
+
lines.push(` it is also INERT as declared: no declared gate PRODUCES the lcov it reads, so it passes while verifying nothing — declare the suite gate: node --test ${UNIT_TESTS_COVERAGE_FLAGS} <your test paths> — ${MARKER_LANE}`);
|
|
499
501
|
}
|
|
500
502
|
}
|
|
501
503
|
if (externalCoreChecks.length) {
|
|
@@ -506,12 +508,14 @@ const warningLines = ({ customized, finalCapable, hasReviewState = finalCapable,
|
|
|
506
508
|
if (checkerWithheld) {
|
|
507
509
|
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:');
|
|
508
510
|
lines.push(` node --test ${UNIT_TESTS_COVERAGE_FLAGS} <your test paths>`);
|
|
511
|
+
lines.push(` ${MARKER_LANE}, then re-run this migration`);
|
|
509
512
|
}
|
|
510
513
|
// The CANONICAL checker's inertness only. An external row's is said on the row itself above, with
|
|
511
514
|
// the edit that fits that row — this block would otherwise add a second, contradictory one.
|
|
512
515
|
if (canonicalCheckerInert) {
|
|
513
516
|
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:');
|
|
514
517
|
lines.push(` node --test ${UNIT_TESTS_COVERAGE_FLAGS} <your test paths>`);
|
|
518
|
+
lines.push(` ${MARKER_LANE}`);
|
|
515
519
|
}
|
|
516
520
|
if (duplicateCheckers > 1) {
|
|
517
521
|
lines.push(` WARNING: ${duplicateCheckers} declared gates are the canonical coverage checker — run-gates --final accepts EXACTLY ONE, so the result is NOT final-run-capable. Nothing is removed for you; keep a single checker and delete the rest by hand.`);
|
|
@@ -122,6 +122,7 @@ describe('migrate-gates — the pure migration plan', () => {
|
|
|
122
122
|
const preview = formatPreview(analysis, 'APPLY');
|
|
123
123
|
assert.match(preview, /CUSTOMIZED \(untouched\): unit-tests/);
|
|
124
124
|
assert.match(preview, /declare the canonical suite gate by hand/, 'the paste-ready recovery rides along');
|
|
125
|
+
assert.match(preview, /recovery \(apply by hand[^\n]*"lcovProducer": true/, 'the recovery names the marker as the other lane');
|
|
125
126
|
});
|
|
126
127
|
|
|
127
128
|
it('adds the coverage-check gate LAST with the RESOLVED quoted path; never a second one', () => {
|
|
@@ -143,6 +144,7 @@ describe('migrate-gates — the pure migration plan', () => {
|
|
|
143
144
|
const preview = formatPreview(analysis, 'APPLY');
|
|
144
145
|
assert.match(preview, /coverage-check/, 'the withheld checker is named');
|
|
145
146
|
assert.match(preview, /produce/i, 'the preview says WHY — no gate produces the lcov it would read');
|
|
147
|
+
assert.match(preview, /WARNING: the canonical coverage-check gate was NOT added[^\n]*\n[^\n]*\n[^\n]*"lcovProducer": true/, 'the withheld block names the marker as the other lane');
|
|
146
148
|
});
|
|
147
149
|
|
|
148
150
|
it('an ALREADY-declared checker over no producer is reported INERT, is never removed, and is not final-run-capable', () => {
|
|
@@ -154,6 +156,7 @@ describe('migrate-gates — the pure migration plan', () => {
|
|
|
154
156
|
const preview = formatPreview(analysis, 'APPLY');
|
|
155
157
|
assert.match(preview, /INERT/, 'the inert pair is named');
|
|
156
158
|
assert.match(preview, /--experimental-test-coverage/, 'the paste-ready suite cmd is carried');
|
|
159
|
+
assert.match(preview, /WARNING: the DECLARED coverage-check gate is INERT[^\n]*\n[^\n]*\n[^\n]*"lcovProducer": true/, 'the inert block names the marker as the other lane');
|
|
157
160
|
assert.doesNotMatch(preview, /already final-run-capable/);
|
|
158
161
|
});
|
|
159
162
|
|
|
@@ -352,6 +355,7 @@ describe('migrate-gates — a VENDORED core check is the tool, from a copy --kit
|
|
|
352
355
|
const absent = formatPreview(buildMigrationPlan([INSTALLED_REVIEW_STATE, vendored], KIT_TOOLS, PROJECT), 'APPLY');
|
|
353
356
|
assert.match(absent, /no declared gate PRODUCES the lcov/, 'with nothing producing, the old sentence still holds');
|
|
354
357
|
assert.match(absent, /declare the suite gate/);
|
|
358
|
+
assert.match(absent, /no declared gate PRODUCES the lcov[^\n]*"lcovProducer": true/, 'the INERT row names the marker as the other lane');
|
|
355
359
|
});
|
|
356
360
|
|
|
357
361
|
it('a mixed declaration renders ONE edit, never a removal and a reorder at once', () => {
|
package/tools/mcp.mjs
CHANGED
|
@@ -317,10 +317,12 @@ export const formatResult = (result) => {
|
|
|
317
317
|
mcpJsonLine(result),
|
|
318
318
|
settingsLine(result),
|
|
319
319
|
...(result.registration.mcpJson.differs ? [
|
|
320
|
-
' the entry standing in .mcp.json now:',
|
|
320
|
+
result.dryRun ? ' the entry standing in .mcp.json now:' : ' the entry this run replaced in .mcp.json:',
|
|
321
321
|
indented(JSON.stringify(result.registration.mcpJson.existing, null, JSON_INDENT)),
|
|
322
322
|
] : []),
|
|
323
|
-
|
|
323
|
+
result.dryRun
|
|
324
|
+
? ' the entry this registration declares (re-serialized here; the same structured value goes into the file):'
|
|
325
|
+
: ' the entry now in .mcp.json (re-serialized here; the file holds the same structured value):',
|
|
324
326
|
indented(JSON.stringify(result.registration.entry, null, JSON_INDENT)),
|
|
325
327
|
POSTURE_LINE,
|
|
326
328
|
HIDDEN_MODE_LINE,
|
package/tools/payload-prune.mjs
CHANGED
|
@@ -107,7 +107,9 @@ const readTree = (root, entry, reports, directoryOnly = false) => {
|
|
|
107
107
|
return entries;
|
|
108
108
|
};
|
|
109
109
|
|
|
110
|
+
// The walk lists a directory before its children, so a kept directory's line covers the directories beneath it.
|
|
110
111
|
const planSubtree = (carried, installed, orphans, reports) => {
|
|
112
|
+
const keptDirectories = [];
|
|
111
113
|
for (const [path, kind] of installed) {
|
|
112
114
|
if (carried.has(path)) {
|
|
113
115
|
const packageKind = carried.get(path);
|
|
@@ -119,7 +121,10 @@ const planSubtree = (carried, installed, orphans, reports) => {
|
|
|
119
121
|
}
|
|
120
122
|
} else if (kind === 'file') {
|
|
121
123
|
orphans.push(path);
|
|
124
|
+
} else if (kind === 'directory' && keptDirectories.some((kept) => path.startsWith(`${kept}/`))) {
|
|
125
|
+
continue;
|
|
122
126
|
} else {
|
|
127
|
+
if (kind === 'directory') keptDirectories.push(path);
|
|
123
128
|
reports.push({
|
|
124
129
|
ok: true,
|
|
125
130
|
line: `${PREFIX} kept ${path} (a ${kind} the package does not carry — yours to remove by hand)`,
|