@ikon85/agent-workflow-kit 0.16.1 → 0.16.3
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/.agents/skills/grill-me/SKILL.md +4 -0
- package/.agents/skills/grill-with-docs/SKILL.md +4 -0
- package/.claude/skills/grill-me/SKILL.md +4 -0
- package/.claude/skills/grill-me-codex/SKILL.md +4 -0
- package/.claude/skills/grill-with-docs/SKILL.md +4 -0
- package/.claude/skills/grill-with-docs-codex/SKILL.md +4 -0
- package/README.md +13 -0
- package/agent-workflow-kit.package.json +7 -7
- package/package.json +2 -1
- package/scripts/build-kit.test.mjs +30 -14
- package/scripts/grill-census-wiring-guard.mjs +87 -0
- package/scripts/grill-census-wiring-guard.test.mjs +107 -0
- package/.claude/logs/drift-guard.log +0 -3
|
@@ -38,6 +38,10 @@ Is the change **cross-cutting** (a new pattern OR a new data structure/domain di
|
|
|
38
38
|
|
|
39
39
|
Never claim "complete" from plan/memory — count the denominator fresh, report `X of Y`. Substance, trigger threshold + guard template → the project convention file `docs/conventions/spec-completeness.md` (if present), §Cross-cutting fork.
|
|
40
40
|
|
|
41
|
+
### Census preflight before plan-lock
|
|
42
|
+
|
|
43
|
+
For a cross-cutting plan, run `python3 .claude/hooks/drift-guard.py --census-status` before locking it. When an activated census reports `block_handoff: true` (including stale or open surfaces), stop the lock, run `$census-update`, resolve the findings, and retry. When the census is disabled or not activated, keep the status visible and perform the existing manual surface walk; do not replace that walk with census guesses.
|
|
44
|
+
|
|
41
45
|
## Plan-lock — writing PLAN.md
|
|
42
46
|
|
|
43
47
|
Once all decisions are made (plan locked, before sign-off/handoff) and the session runs in a **worktree** → write the locked plan as `PLAN.md` to the worktree root (gitignored since, doesn't travel over git — consistent with CLAUDE.md "plan inside the worktree"). This way the locked plan survives a session cut and `to-prd` finds its default source. Continuing same-session work **without** a worktree stays allowed (conversation = source); but for a deliberate **session cut** before `to-prd`, `PLAN.md` is mandatory. (The `-codex` variant already writes `PLAN.md` anyway.)
|
|
@@ -16,6 +16,10 @@ Do not enact the plan until I confirm we have reached a shared understanding.
|
|
|
16
16
|
|
|
17
17
|
</what-to-do>
|
|
18
18
|
|
|
19
|
+
### Census preflight before plan-lock
|
|
20
|
+
|
|
21
|
+
For a cross-cutting plan, run `python3 .claude/hooks/drift-guard.py --census-status` before locking it. When an activated census reports `block_handoff: true` (including stale or open surfaces), stop the lock, run `$census-update`, resolve the findings, and retry. When the census is disabled or not activated, keep the status visible and perform the existing manual surface walk; do not replace that walk with census guesses.
|
|
22
|
+
|
|
19
23
|
## Related skills
|
|
20
24
|
|
|
21
25
|
**No codebase/`CONTEXT.md` present, or a simpler case?** → `grill-me` is enough — same grill without the docs layer (CONTEXT.md/ADRs).
|
|
@@ -38,6 +38,10 @@ Is the change **cross-cutting** (a new pattern OR a new data structure/domain di
|
|
|
38
38
|
|
|
39
39
|
Never claim "complete" from plan/memory — count the denominator fresh, report `X of Y`. Substance, trigger threshold + guard template → the project convention file `docs/conventions/spec-completeness.md` (if present), §Cross-cutting fork.
|
|
40
40
|
|
|
41
|
+
### Census preflight before plan-lock
|
|
42
|
+
|
|
43
|
+
For a cross-cutting plan, run `python3 .claude/hooks/drift-guard.py --census-status` before locking it. When an activated census reports `block_handoff: true` (including stale or open surfaces), stop the lock, run `$census-update`, resolve the findings, and retry. When the census is disabled or not activated, keep the status visible and perform the existing manual surface walk; do not replace that walk with census guesses.
|
|
44
|
+
|
|
41
45
|
## Plan-lock — writing PLAN.md
|
|
42
46
|
|
|
43
47
|
Once all decisions are made (plan locked, before sign-off/handoff) and the session runs in a **worktree** → write the locked plan as `PLAN.md` to the worktree root (gitignored since, doesn't travel over git — consistent with CLAUDE.md "plan inside the worktree"). This way the locked plan survives a session cut and `to-prd` finds its default source. Continuing same-session work **without** a worktree stays allowed (conversation = source); but for a deliberate **session cut** before `to-prd`, `PLAN.md` is mandatory. (The `-codex` variant already writes `PLAN.md` anyway.)
|
|
@@ -35,6 +35,10 @@ Is the change **cross-cutting** (a new pattern OR a new data structure/domain di
|
|
|
35
35
|
|
|
36
36
|
Never claim "complete" from plan/memory — count the denominator fresh, report `X of Y`. Substance, trigger threshold + guard template → the project convention file `docs/conventions/spec-completeness.md` (if present), §Cross-cutting fork.
|
|
37
37
|
|
|
38
|
+
### Census preflight before plan-lock
|
|
39
|
+
|
|
40
|
+
For a cross-cutting plan, run `python3 .claude/hooks/drift-guard.py --census-status` before locking it. When an activated census reports `block_handoff: true` (including stale or open surfaces), stop the lock, run `$census-update`, resolve the findings, and retry. When the census is disabled or not activated, keep the status visible and perform the existing manual surface walk; do not replace that walk with census guesses.
|
|
41
|
+
|
|
38
42
|
When the decision tree is resolved and we're aligned, **write the agreed plan to `PLAN.md`** in this structure, then move to Act 2:
|
|
39
43
|
|
|
40
44
|
> **Where to write it:** `PLAN.md` + `PLAN-REVIEW-LOG.md` are per-session scratch — write them in the working directory the implementing session will actually use, and run Codex from there (`-C <dir>` on the round-1 `exec`; `exec resume` rejects both `-C` and `-s`, so run resume from that cwd and force read-only via `-c sandbox_mode="read-only"`). A project may gitignore these files, so don't rely on git to carry them across checkouts/worktrees. In worktree-based repos, create the issue worktree BEFORE this write and plan inside it.
|
|
@@ -16,6 +16,10 @@ Do not enact the plan until I confirm we have reached a shared understanding.
|
|
|
16
16
|
|
|
17
17
|
</what-to-do>
|
|
18
18
|
|
|
19
|
+
### Census preflight before plan-lock
|
|
20
|
+
|
|
21
|
+
For a cross-cutting plan, run `python3 .claude/hooks/drift-guard.py --census-status` before locking it. When an activated census reports `block_handoff: true` (including stale or open surfaces), stop the lock, run `$census-update`, resolve the findings, and retry. When the census is disabled or not activated, keep the status visible and perform the existing manual surface walk; do not replace that walk with census guesses.
|
|
22
|
+
|
|
19
23
|
## Related skills
|
|
20
24
|
|
|
21
25
|
**No codebase/`CONTEXT.md` present, or a simpler case?** → `grill-me` is enough — same grill without the docs layer (CONTEXT.md/ADRs).
|
|
@@ -117,6 +117,10 @@ Is the change **cross-cutting** (a new pattern OR a new data structure/domain di
|
|
|
117
117
|
|
|
118
118
|
Never claim "complete" from plan/memory — count the denominator fresh, report `X of Y`. Substance, trigger threshold + guard template → the project convention file `docs/conventions/spec-completeness.md` (if present), §Cross-cutting fork.
|
|
119
119
|
|
|
120
|
+
### Census preflight before plan-lock
|
|
121
|
+
|
|
122
|
+
For a cross-cutting plan, run `python3 .claude/hooks/drift-guard.py --census-status` before locking it. When an activated census reports `block_handoff: true` (including stale or open surfaces), stop the lock, run `$census-update`, resolve the findings, and retry. When the census is disabled or not activated, keep the status visible and perform the existing manual surface walk; do not replace that walk with census guesses.
|
|
123
|
+
|
|
120
124
|
### Handoff to Act 2
|
|
121
125
|
|
|
122
126
|
When the decision tree is resolved, the glossary/ADRs are updated, and we're aligned, **write the agreed plan to `PLAN.md`** (use the canonical terms from `CONTEXT.md`), then run Act 2:
|
package/README.md
CHANGED
|
@@ -332,6 +332,19 @@ still reference. Flags: `--force` (overwrite pre-existing files on `init`),
|
|
|
332
332
|
|
|
333
333
|
## Release notes
|
|
334
334
|
|
|
335
|
+
### 0.16.3
|
|
336
|
+
|
|
337
|
+
- changed: `.agents/skills/grill-me/SKILL.md`
|
|
338
|
+
- changed: `.agents/skills/grill-with-docs/SKILL.md`
|
|
339
|
+
- changed: `.claude/skills/grill-me-codex/SKILL.md`
|
|
340
|
+
- changed: `.claude/skills/grill-me/SKILL.md`
|
|
341
|
+
- changed: `.claude/skills/grill-with-docs-codex/SKILL.md`
|
|
342
|
+
- changed: `.claude/skills/grill-with-docs/SKILL.md`
|
|
343
|
+
|
|
344
|
+
### 0.16.2
|
|
345
|
+
|
|
346
|
+
- Metadata-only release.
|
|
347
|
+
|
|
335
348
|
### 0.16.1
|
|
336
349
|
|
|
337
350
|
- changed: `.agents/skills/kit-update/SKILL.md`
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"kitVersion": "0.16.
|
|
2
|
+
"kitVersion": "0.16.3",
|
|
3
3
|
"files": [
|
|
4
4
|
{
|
|
5
5
|
"path": ".agents/skills/ask-matt/SKILL.md",
|
|
@@ -186,7 +186,7 @@
|
|
|
186
186
|
"kind": "skill",
|
|
187
187
|
"ownerSkill": "grill-me",
|
|
188
188
|
"surface": "codex",
|
|
189
|
-
"sha256": "
|
|
189
|
+
"sha256": "132cb0e6bf3e1b5b71575f4d2707880c7ffb844ccfa2c71a80270f891d04b724",
|
|
190
190
|
"mode": 420,
|
|
191
191
|
"origin": "kit"
|
|
192
192
|
},
|
|
@@ -222,7 +222,7 @@
|
|
|
222
222
|
"kind": "skill",
|
|
223
223
|
"ownerSkill": "grill-with-docs",
|
|
224
224
|
"surface": "codex",
|
|
225
|
-
"sha256": "
|
|
225
|
+
"sha256": "06a68913f904ce9bb1b21c86f146a6738b401da07eb57fb3c97a4eec00b58de6",
|
|
226
226
|
"mode": 420,
|
|
227
227
|
"origin": "kit"
|
|
228
228
|
},
|
|
@@ -1114,7 +1114,7 @@
|
|
|
1114
1114
|
"kind": "skill",
|
|
1115
1115
|
"ownerSkill": "grill-me-codex",
|
|
1116
1116
|
"surface": "claude",
|
|
1117
|
-
"sha256": "
|
|
1117
|
+
"sha256": "ab73dd6b147e05837082db5d5ab68ab118e51e59767a717c8b7a0262cfbd07b5",
|
|
1118
1118
|
"mode": 420,
|
|
1119
1119
|
"origin": "kit"
|
|
1120
1120
|
},
|
|
@@ -1132,7 +1132,7 @@
|
|
|
1132
1132
|
"kind": "skill",
|
|
1133
1133
|
"ownerSkill": "grill-me",
|
|
1134
1134
|
"surface": "claude",
|
|
1135
|
-
"sha256": "
|
|
1135
|
+
"sha256": "01e95acd6abb396c4f4c3ec7980b79f81bbee22d45247e96f1fe130fea3f5dbe",
|
|
1136
1136
|
"mode": 420,
|
|
1137
1137
|
"origin": "kit"
|
|
1138
1138
|
},
|
|
@@ -1168,7 +1168,7 @@
|
|
|
1168
1168
|
"kind": "skill",
|
|
1169
1169
|
"ownerSkill": "grill-with-docs-codex",
|
|
1170
1170
|
"surface": "claude",
|
|
1171
|
-
"sha256": "
|
|
1171
|
+
"sha256": "5c7635bf31a3dc575762793ae80dfe7a26498aedcf7f1ce0513822ea1ebdb100",
|
|
1172
1172
|
"mode": 420,
|
|
1173
1173
|
"origin": "kit"
|
|
1174
1174
|
},
|
|
@@ -1204,7 +1204,7 @@
|
|
|
1204
1204
|
"kind": "skill",
|
|
1205
1205
|
"ownerSkill": "grill-with-docs",
|
|
1206
1206
|
"surface": "claude",
|
|
1207
|
-
"sha256": "
|
|
1207
|
+
"sha256": "6250d2e64ba1248bbd40dd0c7018ab432eda4010ebc34f6c9eccefd514a1f22b",
|
|
1208
1208
|
"mode": 420,
|
|
1209
1209
|
"origin": "kit"
|
|
1210
1210
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ikon85/agent-workflow-kit",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.3",
|
|
4
4
|
"description": "Portable AI-agent workflow skills (plan → execute → land → learn) for Claude Code & Codex — grilling, TDD, diagnosis, two-axis code review, cross-model Codex review, design & domain-modeling, plus a skill router (ask-matt). npx init/update/diff/uninstall.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
},
|
|
20
20
|
"files": [
|
|
21
21
|
".claude/",
|
|
22
|
+
"!.claude/logs/",
|
|
22
23
|
".agents/",
|
|
23
24
|
"scripts/",
|
|
24
25
|
"docs/",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { test } from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
|
-
import { mkdtemp, readFile, rm } from 'node:fs/promises';
|
|
3
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { join, dirname } from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
@@ -48,19 +48,35 @@ test('current build is self-contained and never reaches into a consumer checkout
|
|
|
48
48
|
assert.doesNotMatch(source, /testreporter|tools\/agent-workflow-kit/);
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
-
test('npm pack keeps
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
51
|
+
test('npm pack keeps product files but excludes runtime residue', async () => {
|
|
52
|
+
const logsDir = join(REPO, '.claude/logs');
|
|
53
|
+
await mkdir(logsDir, { recursive: true });
|
|
54
|
+
const ownedLogDir = await mkdtemp(join(logsDir, 'pack-runtime-'));
|
|
55
|
+
const runtimeLog = join(ownedLogDir, 'drift-guard.log');
|
|
56
|
+
const sentinelLog = join(ownedLogDir, 'preexisting.log');
|
|
57
|
+
const sentinelBytes = 'preexisting runtime log bytes\n';
|
|
58
|
+
await writeFile(sentinelLog, sentinelBytes);
|
|
59
|
+
await writeFile(runtimeLog, `${new Date().toISOString()} runtime-only\n`);
|
|
60
|
+
try {
|
|
61
|
+
const output = execFileSync('npm', ['pack', '--dry-run', '--json'], {
|
|
62
|
+
cwd: REPO, encoding: 'utf8',
|
|
63
|
+
});
|
|
64
|
+
const files = JSON.parse(output)[0].files.map((file) => file.path);
|
|
65
|
+
assert.ok(files.includes('scripts/build-kit.mjs'));
|
|
66
|
+
assert.ok(files.includes('scripts/board-sync.py'));
|
|
67
|
+
assert.ok(files.includes('scripts/kit-update-pr.mjs'));
|
|
68
|
+
assert.ok(files.includes('.claude/hooks/drift-guard.py'));
|
|
69
|
+
assert.ok(files.includes('.claude/skills/tdd/SKILL.md'));
|
|
70
|
+
assert.ok(files.every((path) => !path.startsWith('.claude/logs/')));
|
|
71
|
+
assert.ok(files.every((path) => !path.includes('__pycache__') && !path.endsWith('.pyc')));
|
|
72
|
+
const pkg = JSON.parse(execFileSync('node', ['-p', 'JSON.stringify(require("./package.json"))'], {
|
|
73
|
+
cwd: REPO, encoding: 'utf8',
|
|
74
|
+
}));
|
|
75
|
+
assert.ok(pkg.files.includes('scripts/'), 'package must ship future script file types');
|
|
76
|
+
assert.equal(await readFile(sentinelLog, 'utf8'), sentinelBytes);
|
|
77
|
+
} finally {
|
|
78
|
+
await rm(ownedLogDir, { recursive: true, force: true });
|
|
79
|
+
}
|
|
64
80
|
});
|
|
65
81
|
|
|
66
82
|
test('packed scoped artifact keeps the existing npx default-bin inference', async () => {
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { access, readFile } from 'node:fs/promises';
|
|
4
|
+
import { constants } from 'node:fs';
|
|
5
|
+
import { dirname, join } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
export const CENSUS_PREFLIGHT_INVOCATION =
|
|
9
|
+
'python3 .claude/hooks/drift-guard.py --census-status';
|
|
10
|
+
export const CENSUS_PREFLIGHT_CONTRACT =
|
|
11
|
+
'For a cross-cutting plan, run `python3 .claude/hooks/drift-guard.py --census-status` before locking it. When an activated census reports `block_handoff: true` (including stale or open surfaces), stop the lock, run `$census-update`, resolve the findings, and retry. When the census is disabled or not activated, keep the status visible and perform the existing manual surface walk; do not replace that walk with census guesses.';
|
|
12
|
+
|
|
13
|
+
const GRILL_FAMILY_NAME = /^grill-/;
|
|
14
|
+
const SURFACE_TREES = { claude: '.claude', codex: '.agents' };
|
|
15
|
+
const LOCAL_CENSUS_LOGIC = /\b(?:scanCensus|diffCensus|fingerprintCensus|resolveCensusState|activateCensus)\s*\(/;
|
|
16
|
+
|
|
17
|
+
async function exists(path) {
|
|
18
|
+
try {
|
|
19
|
+
await access(path, constants.R_OK);
|
|
20
|
+
return true;
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function auditGrillCensusWiring(repoRoot) {
|
|
27
|
+
const manifestPath = join(repoRoot, '.claude', 'skills', 'skill-manifest.json');
|
|
28
|
+
const manifest = JSON.parse(await readFile(manifestPath, 'utf8'));
|
|
29
|
+
const entries = Object.entries(manifest.skills ?? {})
|
|
30
|
+
.filter(([name]) => GRILL_FAMILY_NAME.test(name));
|
|
31
|
+
const problems = [];
|
|
32
|
+
const physical = [];
|
|
33
|
+
|
|
34
|
+
for (const [name, entry] of entries) {
|
|
35
|
+
if (!Array.isArray(entry.surfaces) || entry.surfaces.length === 0) {
|
|
36
|
+
problems.push(`manifest grill has no surfaces: ${name}`);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
for (const surface of entry.surfaces) {
|
|
40
|
+
const tree = SURFACE_TREES[surface];
|
|
41
|
+
if (!tree) {
|
|
42
|
+
problems.push(`manifest names an unknown surface for ${name}: ${surface}`);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const path = join(repoRoot, tree, 'skills', name, 'SKILL.md');
|
|
46
|
+
physical.push({ name, surface, path });
|
|
47
|
+
if (!(await exists(path))) {
|
|
48
|
+
problems.push(`manifest surface has no skill file: ${surface}:${name}`);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const prose = await readFile(path, 'utf8');
|
|
52
|
+
const invocationCount = prose.split(CENSUS_PREFLIGHT_INVOCATION).length - 1;
|
|
53
|
+
if (invocationCount !== 1) {
|
|
54
|
+
problems.push(
|
|
55
|
+
invocationCount === 0
|
|
56
|
+
? `missing census preflight invocation: ${surface}:${name}`
|
|
57
|
+
: `duplicate census preflight invocation: ${surface}:${name}`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
if (!prose.includes(CENSUS_PREFLIGHT_CONTRACT)) {
|
|
61
|
+
problems.push(`census preflight contract drifted: ${surface}:${name}`);
|
|
62
|
+
}
|
|
63
|
+
if (LOCAL_CENSUS_LOGIC.test(prose)) {
|
|
64
|
+
problems.push(`grill duplicates census engine logic: ${surface}:${name}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
counts: { logical: entries.length, physical: physical.length },
|
|
71
|
+
problems,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function main() {
|
|
76
|
+
const repoRoot = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
77
|
+
const result = await auditGrillCensusWiring(repoRoot);
|
|
78
|
+
console.log(`grill census wiring: ${result.counts.logical} logical, ${result.counts.physical} physical`);
|
|
79
|
+
if (result.problems.length > 0) {
|
|
80
|
+
for (const problem of result.problems) console.error(`- ${problem}`);
|
|
81
|
+
process.exitCode = 1;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
86
|
+
await main();
|
|
87
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { mkdtemp, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import test from 'node:test';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
CENSUS_PREFLIGHT_CONTRACT,
|
|
10
|
+
auditGrillCensusWiring,
|
|
11
|
+
} from './grill-census-wiring-guard.mjs';
|
|
12
|
+
|
|
13
|
+
const REPO = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
14
|
+
const realManifest = JSON.parse(await readFile(
|
|
15
|
+
join(REPO, '.claude', 'skills', 'skill-manifest.json'),
|
|
16
|
+
'utf8',
|
|
17
|
+
)).skills;
|
|
18
|
+
const baselineGrills = Object.fromEntries(
|
|
19
|
+
Object.entries(realManifest).filter(([name]) => name.startsWith('grill-')),
|
|
20
|
+
);
|
|
21
|
+
const baselineCounts = {
|
|
22
|
+
logical: Object.keys(baselineGrills).length,
|
|
23
|
+
physical: Object.values(baselineGrills)
|
|
24
|
+
.reduce((count, entry) => count + entry.surfaces.length, 0),
|
|
25
|
+
};
|
|
26
|
+
const [baselineName, baselineEntry] = Object.entries(baselineGrills)[0];
|
|
27
|
+
const baselineSurface = baselineEntry.surfaces[0];
|
|
28
|
+
let extraGrillName = 'grill-fixture-extra';
|
|
29
|
+
while (extraGrillName in baselineGrills) extraGrillName += '-next';
|
|
30
|
+
|
|
31
|
+
async function fixture({ manifest = baselineGrills, omitInvocation, omitFile } = {}) {
|
|
32
|
+
const root = await mkdtemp(join(tmpdir(), 'grill-census-guard-'));
|
|
33
|
+
await mkdir(join(root, '.claude', 'skills'), { recursive: true });
|
|
34
|
+
await writeFile(
|
|
35
|
+
join(root, '.claude', 'skills', 'skill-manifest.json'),
|
|
36
|
+
`${JSON.stringify({ skills: manifest })}\n`,
|
|
37
|
+
);
|
|
38
|
+
for (const [name, entry] of Object.entries(manifest)) {
|
|
39
|
+
for (const surface of entry.surfaces) {
|
|
40
|
+
const relative = `${surface}:${name}`;
|
|
41
|
+
if (relative === omitFile) continue;
|
|
42
|
+
const tree = surface === 'claude' ? '.claude' : '.agents';
|
|
43
|
+
const directory = join(root, tree, 'skills', name);
|
|
44
|
+
await mkdir(directory, { recursive: true });
|
|
45
|
+
const invocation = relative === omitInvocation ? '' : CENSUS_PREFLIGHT_CONTRACT;
|
|
46
|
+
await writeFile(join(directory, 'SKILL.md'), `# ${name}\n${invocation}\n`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return root;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
test('fixture wiring covers every grill and surface in the real manifest', async () => {
|
|
53
|
+
const result = await auditGrillCensusWiring(await fixture());
|
|
54
|
+
|
|
55
|
+
assert.deepEqual(result.counts, baselineCounts);
|
|
56
|
+
assert.deepEqual(result.problems, []);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('a grill surface missing the shared preflight invocation is rejected', async () => {
|
|
60
|
+
const result = await auditGrillCensusWiring(await fixture({
|
|
61
|
+
omitInvocation: `${baselineSurface}:${baselineName}`,
|
|
62
|
+
}));
|
|
63
|
+
|
|
64
|
+
assert.match(
|
|
65
|
+
result.problems.join('\n'),
|
|
66
|
+
new RegExp(`missing census preflight invocation.*${baselineName}`, 'i'),
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('a dead manifest surface reference is rejected', async () => {
|
|
71
|
+
const result = await auditGrillCensusWiring(await fixture({
|
|
72
|
+
omitFile: `${baselineSurface}:${baselineName}`,
|
|
73
|
+
}));
|
|
74
|
+
|
|
75
|
+
assert.match(
|
|
76
|
+
result.problems.join('\n'),
|
|
77
|
+
new RegExp(`manifest surface has no skill file.*${baselineName}`, 'i'),
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('a newly manifested grill variant expands the family and cannot hide a missing surface', async () => {
|
|
82
|
+
const addedSurfaces = ['claude', 'codex'];
|
|
83
|
+
const manifest = {
|
|
84
|
+
...baselineGrills,
|
|
85
|
+
[extraGrillName]: { surfaces: addedSurfaces },
|
|
86
|
+
};
|
|
87
|
+
const result = await auditGrillCensusWiring(await fixture({
|
|
88
|
+
manifest,
|
|
89
|
+
omitFile: `codex:${extraGrillName}`,
|
|
90
|
+
}));
|
|
91
|
+
|
|
92
|
+
assert.deepEqual(result.counts, {
|
|
93
|
+
logical: baselineCounts.logical + 1,
|
|
94
|
+
physical: baselineCounts.physical + addedSurfaces.length,
|
|
95
|
+
});
|
|
96
|
+
assert.match(
|
|
97
|
+
result.problems.join('\n'),
|
|
98
|
+
new RegExp(`manifest surface has no skill file.*${extraGrillName}`, 'i'),
|
|
99
|
+
);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('the repository wiring covers the complete manifest-derived grill family', async () => {
|
|
103
|
+
const result = await auditGrillCensusWiring(REPO);
|
|
104
|
+
|
|
105
|
+
assert.deepEqual(result.counts, baselineCounts);
|
|
106
|
+
assert.deepEqual(result.problems, []);
|
|
107
|
+
});
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
2026-07-15T18:45:54 census state=failed reasons=["target repository unavailable (Command '['git', '-C', '/tmp/repo', 'rev-parse', '--show-toplevel']' returned non-zero exit status 128.)"]
|
|
2
|
-
2026-07-15T18:45:55 census state=failed reasons=['target repository unavailable (handoff target repository containment check failed)']
|
|
3
|
-
2026-07-15T18:45:59 census state=failed reasons=['scan']
|