@ikon85/agent-workflow-kit 0.30.0 → 0.31.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/.agents/skills/board-to-waves/SKILL.md +14 -0
- package/.agents/skills/code-review/SKILL.md +22 -3
- package/.agents/skills/kit-update/SKILL.md +21 -1
- package/.agents/skills/local-ci/SKILL.md +16 -0
- package/.agents/skills/project-release/SKILL.md +18 -0
- package/.agents/skills/security-audit/SKILL.md +18 -0
- package/.agents/skills/setup-workflow/SKILL.md +39 -2
- package/.agents/skills/spec-self-critique/SKILL.md +16 -7
- package/.agents/skills/to-issues/SKILL.md +13 -1
- package/.agents/skills/to-prd/SKILL.md +14 -0
- package/.agents/skills/to-waves/SKILL.md +16 -2
- package/.agents/skills/triage/SKILL.md +24 -0
- package/.agents/skills/verify-spike/SKILL.md +20 -1
- package/.agents/skills/wrapup/SKILL.md +26 -4
- package/.claude/skills/board-to-waves/SKILL.md +14 -0
- package/.claude/skills/code-review/SKILL.md +22 -3
- package/.claude/skills/kit-update/SKILL.md +21 -1
- package/.claude/skills/local-ci/SKILL.md +16 -0
- package/.claude/skills/project-release/SKILL.md +18 -0
- package/.claude/skills/security-audit/SKILL.md +18 -0
- package/.claude/skills/setup-workflow/SKILL.md +39 -2
- package/.claude/skills/spec-self-critique/SKILL.md +16 -7
- package/.claude/skills/to-issues/SKILL.md +13 -1
- package/.claude/skills/to-prd/SKILL.md +14 -0
- package/.claude/skills/to-waves/SKILL.md +16 -2
- package/.claude/skills/triage/SKILL.md +24 -0
- package/.claude/skills/verify-spike/SKILL.md +20 -1
- package/.claude/skills/wrapup/SKILL.md +26 -4
- package/README.md +32 -0
- package/agent-workflow-kit.package.json +30 -30
- package/package.json +1 -1
- package/scripts/kit-update-pr.mjs +14 -1
- package/scripts/kit-update-pr.test.mjs +21 -0
- package/scripts/test_retro_wrapup_contract.py +17 -0
- package/scripts/test_skill_optional_readiness.py +171 -0
- package/scripts/test_skill_readiness_preflight.py +180 -0
- package/scripts/test_skill_required_readiness.py +233 -0
- package/scripts/test_skill_setup_workflow_seeds.py +22 -0
- package/src/cli.mjs +10 -2
- package/src/commands/update.mjs +65 -8
- package/src/lib/updateCandidate.mjs +116 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"kitVersion": "0.
|
|
2
|
+
"kitVersion": "0.31.0",
|
|
3
3
|
"files": [
|
|
4
4
|
{
|
|
5
5
|
"path": ".agents/skills/ask-matt/SKILL.md",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"ownerSkill": "board-to-waves",
|
|
38
38
|
"surface": "codex",
|
|
39
39
|
"installRole": "consumer",
|
|
40
|
-
"sha256": "
|
|
40
|
+
"sha256": "203211bc1bd149918783de4ead312d94c4faf1e4ba25d2625ce34f99a0806282",
|
|
41
41
|
"mode": 420,
|
|
42
42
|
"origin": "kit"
|
|
43
43
|
},
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"ownerSkill": "code-review",
|
|
58
58
|
"surface": "codex",
|
|
59
59
|
"installRole": "consumer",
|
|
60
|
-
"sha256": "
|
|
60
|
+
"sha256": "9d0e54bd73bd4cdd810e714f1a5af07a4cd00b7cc07fe360e587abbf3005f2aa",
|
|
61
61
|
"mode": 420,
|
|
62
62
|
"origin": "kit"
|
|
63
63
|
},
|
|
@@ -377,7 +377,7 @@
|
|
|
377
377
|
"ownerSkill": "kit-update",
|
|
378
378
|
"surface": "codex",
|
|
379
379
|
"installRole": "consumer",
|
|
380
|
-
"sha256": "
|
|
380
|
+
"sha256": "880766b7aef88760f6d79dd31c3333356eef060623fc053fc61f5d5611765bb8",
|
|
381
381
|
"mode": 420,
|
|
382
382
|
"origin": "kit"
|
|
383
383
|
},
|
|
@@ -387,7 +387,7 @@
|
|
|
387
387
|
"ownerSkill": "local-ci",
|
|
388
388
|
"surface": "codex",
|
|
389
389
|
"installRole": "consumer",
|
|
390
|
-
"sha256": "
|
|
390
|
+
"sha256": "1ebf841e35d0df7e68b2684ba8fd7c674327bcb729a693fd5ed87189ac5e90be",
|
|
391
391
|
"mode": 420,
|
|
392
392
|
"origin": "kit"
|
|
393
393
|
},
|
|
@@ -427,7 +427,7 @@
|
|
|
427
427
|
"ownerSkill": "project-release",
|
|
428
428
|
"surface": "codex",
|
|
429
429
|
"installRole": "consumer",
|
|
430
|
-
"sha256": "
|
|
430
|
+
"sha256": "754910dc182b3868f9c0f95b7ddb5665fe955f68bc9bea4e44c7c6f4ef5d8df9",
|
|
431
431
|
"mode": 420,
|
|
432
432
|
"origin": "kit"
|
|
433
433
|
},
|
|
@@ -537,7 +537,7 @@
|
|
|
537
537
|
"ownerSkill": "security-audit",
|
|
538
538
|
"surface": "codex",
|
|
539
539
|
"installRole": "consumer",
|
|
540
|
-
"sha256": "
|
|
540
|
+
"sha256": "2e937d1e8c40b20da0c733e1e5c355531b3e777abf6197795186405afb5e009d",
|
|
541
541
|
"mode": 420,
|
|
542
542
|
"origin": "kit"
|
|
543
543
|
},
|
|
@@ -647,7 +647,7 @@
|
|
|
647
647
|
"ownerSkill": "setup-workflow",
|
|
648
648
|
"surface": "codex",
|
|
649
649
|
"installRole": "consumer",
|
|
650
|
-
"sha256": "
|
|
650
|
+
"sha256": "f0d6809b86eec77cf54bccc30e6a7603d831678fcc8b5f52b4e073f9f56a72a8",
|
|
651
651
|
"mode": 420,
|
|
652
652
|
"origin": "kit"
|
|
653
653
|
},
|
|
@@ -737,7 +737,7 @@
|
|
|
737
737
|
"ownerSkill": "spec-self-critique",
|
|
738
738
|
"surface": "codex",
|
|
739
739
|
"installRole": "consumer",
|
|
740
|
-
"sha256": "
|
|
740
|
+
"sha256": "586d49acff382484e7db58719a34d257936ffb8c19c9de1a3322f6b1b5e675ad",
|
|
741
741
|
"mode": 420,
|
|
742
742
|
"origin": "kit"
|
|
743
743
|
},
|
|
@@ -817,7 +817,7 @@
|
|
|
817
817
|
"ownerSkill": "to-issues",
|
|
818
818
|
"surface": "codex",
|
|
819
819
|
"installRole": "consumer",
|
|
820
|
-
"sha256": "
|
|
820
|
+
"sha256": "cae01235d1647e30bf60df93d32cb2f9be1b5dc908429514287aaf648f8094e1",
|
|
821
821
|
"mode": 420,
|
|
822
822
|
"origin": "kit"
|
|
823
823
|
},
|
|
@@ -847,7 +847,7 @@
|
|
|
847
847
|
"ownerSkill": "to-prd",
|
|
848
848
|
"surface": "codex",
|
|
849
849
|
"installRole": "consumer",
|
|
850
|
-
"sha256": "
|
|
850
|
+
"sha256": "dae11a76b317e190f2b10580eda098a3dcf73c0be3806050a7caa1b933107fa6",
|
|
851
851
|
"mode": 420,
|
|
852
852
|
"origin": "kit"
|
|
853
853
|
},
|
|
@@ -867,7 +867,7 @@
|
|
|
867
867
|
"ownerSkill": "to-waves",
|
|
868
868
|
"surface": "codex",
|
|
869
869
|
"installRole": "consumer",
|
|
870
|
-
"sha256": "
|
|
870
|
+
"sha256": "0229b717783054c87c42e088d1323990b6a30ffc80b337b6c14caf5e8f7964d1",
|
|
871
871
|
"mode": 420,
|
|
872
872
|
"origin": "kit"
|
|
873
873
|
},
|
|
@@ -907,7 +907,7 @@
|
|
|
907
907
|
"ownerSkill": "triage",
|
|
908
908
|
"surface": "codex",
|
|
909
909
|
"installRole": "consumer",
|
|
910
|
-
"sha256": "
|
|
910
|
+
"sha256": "dc27da1360edbc8e52a3b2ba67a9bf3d7d15a1a5403204737d7d0d11b7c53e32",
|
|
911
911
|
"mode": 420,
|
|
912
912
|
"origin": "kit"
|
|
913
913
|
},
|
|
@@ -927,7 +927,7 @@
|
|
|
927
927
|
"ownerSkill": "verify-spike",
|
|
928
928
|
"surface": "codex",
|
|
929
929
|
"installRole": "consumer",
|
|
930
|
-
"sha256": "
|
|
930
|
+
"sha256": "a823094a83cdfde40954f581064a5a64b5fcc41a0b5eba2989c9a20ecb39b02b",
|
|
931
931
|
"mode": 420,
|
|
932
932
|
"origin": "kit"
|
|
933
933
|
},
|
|
@@ -957,7 +957,7 @@
|
|
|
957
957
|
"ownerSkill": "wrapup",
|
|
958
958
|
"surface": "codex",
|
|
959
959
|
"installRole": "consumer",
|
|
960
|
-
"sha256": "
|
|
960
|
+
"sha256": "fd4b4abc17cf3d333057d1a25d3689dbab3fe59d08189b6136ac2198cec0abe4",
|
|
961
961
|
"mode": 420,
|
|
962
962
|
"origin": "kit"
|
|
963
963
|
},
|
|
@@ -1199,7 +1199,7 @@
|
|
|
1199
1199
|
"ownerSkill": "board-to-waves",
|
|
1200
1200
|
"surface": "claude",
|
|
1201
1201
|
"installRole": "consumer",
|
|
1202
|
-
"sha256": "
|
|
1202
|
+
"sha256": "33c897cf670e57b41bf231be7cf1ce3e35634907c94f6b0e037a2eae19adf582",
|
|
1203
1203
|
"mode": 420,
|
|
1204
1204
|
"origin": "kit"
|
|
1205
1205
|
},
|
|
@@ -1219,7 +1219,7 @@
|
|
|
1219
1219
|
"ownerSkill": "code-review",
|
|
1220
1220
|
"surface": "claude",
|
|
1221
1221
|
"installRole": "consumer",
|
|
1222
|
-
"sha256": "
|
|
1222
|
+
"sha256": "9d0e54bd73bd4cdd810e714f1a5af07a4cd00b7cc07fe360e587abbf3005f2aa",
|
|
1223
1223
|
"mode": 420,
|
|
1224
1224
|
"origin": "kit"
|
|
1225
1225
|
},
|
|
@@ -1659,7 +1659,7 @@
|
|
|
1659
1659
|
"ownerSkill": "kit-update",
|
|
1660
1660
|
"surface": "claude",
|
|
1661
1661
|
"installRole": "consumer",
|
|
1662
|
-
"sha256": "
|
|
1662
|
+
"sha256": "880766b7aef88760f6d79dd31c3333356eef060623fc053fc61f5d5611765bb8",
|
|
1663
1663
|
"mode": 420,
|
|
1664
1664
|
"origin": "kit"
|
|
1665
1665
|
},
|
|
@@ -1669,7 +1669,7 @@
|
|
|
1669
1669
|
"ownerSkill": "local-ci",
|
|
1670
1670
|
"surface": "claude",
|
|
1671
1671
|
"installRole": "consumer",
|
|
1672
|
-
"sha256": "
|
|
1672
|
+
"sha256": "3af9e71a2b65b66cfa2b40d780badd86b925c1d18e0cb16fdd25041db12f2cbc",
|
|
1673
1673
|
"mode": 420,
|
|
1674
1674
|
"origin": "kit"
|
|
1675
1675
|
},
|
|
@@ -1709,7 +1709,7 @@
|
|
|
1709
1709
|
"ownerSkill": "project-release",
|
|
1710
1710
|
"surface": "claude",
|
|
1711
1711
|
"installRole": "consumer",
|
|
1712
|
-
"sha256": "
|
|
1712
|
+
"sha256": "754910dc182b3868f9c0f95b7ddb5665fe955f68bc9bea4e44c7c6f4ef5d8df9",
|
|
1713
1713
|
"mode": 420,
|
|
1714
1714
|
"origin": "kit"
|
|
1715
1715
|
},
|
|
@@ -1819,7 +1819,7 @@
|
|
|
1819
1819
|
"ownerSkill": "security-audit",
|
|
1820
1820
|
"surface": "claude",
|
|
1821
1821
|
"installRole": "consumer",
|
|
1822
|
-
"sha256": "
|
|
1822
|
+
"sha256": "4fcd28c4019e84b6ae5307437ecc979a2d6a490a7a379a7e104b3a4974ca877e",
|
|
1823
1823
|
"mode": 420,
|
|
1824
1824
|
"origin": "kit"
|
|
1825
1825
|
},
|
|
@@ -1949,7 +1949,7 @@
|
|
|
1949
1949
|
"ownerSkill": "setup-workflow",
|
|
1950
1950
|
"surface": "claude",
|
|
1951
1951
|
"installRole": "consumer",
|
|
1952
|
-
"sha256": "
|
|
1952
|
+
"sha256": "f0d6809b86eec77cf54bccc30e6a7603d831678fcc8b5f52b4e073f9f56a72a8",
|
|
1953
1953
|
"mode": 420,
|
|
1954
1954
|
"origin": "kit"
|
|
1955
1955
|
},
|
|
@@ -2047,7 +2047,7 @@
|
|
|
2047
2047
|
"ownerSkill": "spec-self-critique",
|
|
2048
2048
|
"surface": "claude",
|
|
2049
2049
|
"installRole": "consumer",
|
|
2050
|
-
"sha256": "
|
|
2050
|
+
"sha256": "0ed723ebad6ceb74defed56a51aa7cee8c343665288e96ff2b06ef214165c7e1",
|
|
2051
2051
|
"mode": 420,
|
|
2052
2052
|
"origin": "kit"
|
|
2053
2053
|
},
|
|
@@ -2127,7 +2127,7 @@
|
|
|
2127
2127
|
"ownerSkill": "to-issues",
|
|
2128
2128
|
"surface": "claude",
|
|
2129
2129
|
"installRole": "consumer",
|
|
2130
|
-
"sha256": "
|
|
2130
|
+
"sha256": "874a636bde53180dab4d69b801c7d3a5c5cbdaf74e1969af56c8d7805163ef24",
|
|
2131
2131
|
"mode": 420,
|
|
2132
2132
|
"origin": "kit"
|
|
2133
2133
|
},
|
|
@@ -2157,7 +2157,7 @@
|
|
|
2157
2157
|
"ownerSkill": "to-prd",
|
|
2158
2158
|
"surface": "claude",
|
|
2159
2159
|
"installRole": "consumer",
|
|
2160
|
-
"sha256": "
|
|
2160
|
+
"sha256": "621644d4de0bdb02d367381c8cf58584c393b9d385f5f2881c00f9d4f70fd834",
|
|
2161
2161
|
"mode": 420,
|
|
2162
2162
|
"origin": "kit"
|
|
2163
2163
|
},
|
|
@@ -2177,7 +2177,7 @@
|
|
|
2177
2177
|
"ownerSkill": "to-waves",
|
|
2178
2178
|
"surface": "claude",
|
|
2179
2179
|
"installRole": "consumer",
|
|
2180
|
-
"sha256": "
|
|
2180
|
+
"sha256": "0229b717783054c87c42e088d1323990b6a30ffc80b337b6c14caf5e8f7964d1",
|
|
2181
2181
|
"mode": 420,
|
|
2182
2182
|
"origin": "kit"
|
|
2183
2183
|
},
|
|
@@ -2217,7 +2217,7 @@
|
|
|
2217
2217
|
"ownerSkill": "triage",
|
|
2218
2218
|
"surface": "claude",
|
|
2219
2219
|
"installRole": "consumer",
|
|
2220
|
-
"sha256": "
|
|
2220
|
+
"sha256": "ea9be39611b3f007edd12089a03f9b4a408e3651765eb104a966cf85c2717496",
|
|
2221
2221
|
"mode": 420,
|
|
2222
2222
|
"origin": "kit"
|
|
2223
2223
|
},
|
|
@@ -2237,7 +2237,7 @@
|
|
|
2237
2237
|
"ownerSkill": "verify-spike",
|
|
2238
2238
|
"surface": "claude",
|
|
2239
2239
|
"installRole": "consumer",
|
|
2240
|
-
"sha256": "
|
|
2240
|
+
"sha256": "ee2258edd8bb9fb03bb1f98926aae20b25147b04aa88af68e06a45a6d75549b5",
|
|
2241
2241
|
"mode": 420,
|
|
2242
2242
|
"origin": "kit"
|
|
2243
2243
|
},
|
|
@@ -2267,7 +2267,7 @@
|
|
|
2267
2267
|
"ownerSkill": "wrapup",
|
|
2268
2268
|
"surface": "claude",
|
|
2269
2269
|
"installRole": "consumer",
|
|
2270
|
-
"sha256": "
|
|
2270
|
+
"sha256": "05a4981b54fb5c4ef1b842bbdd45f6ca91cf0047e38e511feac86e20206c0fe7",
|
|
2271
2271
|
"mode": 420,
|
|
2272
2272
|
"origin": "kit"
|
|
2273
2273
|
},
|
|
@@ -2475,7 +2475,7 @@
|
|
|
2475
2475
|
"path": "scripts/kit-update-pr.mjs",
|
|
2476
2476
|
"kind": "script",
|
|
2477
2477
|
"installRole": "consumer",
|
|
2478
|
-
"sha256": "
|
|
2478
|
+
"sha256": "83f30a4ca938171cfa2f4153917813fae40bf2a906940e047c7066d18284209b",
|
|
2479
2479
|
"mode": 493,
|
|
2480
2480
|
"origin": "kit"
|
|
2481
2481
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ikon85/agent-workflow-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
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": {
|
|
@@ -33,7 +33,7 @@ export async function orchestrateUpdatePullRequest(options) {
|
|
|
33
33
|
return { status: 'failed', branch, reason: 'multiple-open-pull-requests' };
|
|
34
34
|
}
|
|
35
35
|
await publishBranch(branch);
|
|
36
|
-
const input = { title: UPDATE_TITLE, body:
|
|
36
|
+
const input = { title: UPDATE_TITLE, body: pullRequestBody(update.stdout), branch };
|
|
37
37
|
if (pulls.length === 1) {
|
|
38
38
|
await updatePullRequest(pulls[0].number, input);
|
|
39
39
|
return { status: 'updated', branch, pullRequest: pulls[0].number };
|
|
@@ -42,6 +42,19 @@ export async function orchestrateUpdatePullRequest(options) {
|
|
|
42
42
|
return { status: 'created', branch };
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
function pullRequestBody(stdout = '') {
|
|
46
|
+
const labels = ['newly available:', 'newly degraded:', 'newly blocked:', 'still unresolved:'];
|
|
47
|
+
const clean = stdout.replaceAll(/\x1b\[[0-9;]*m/g, '');
|
|
48
|
+
const summary = clean.split('\n')
|
|
49
|
+
.map((line) => line.trim())
|
|
50
|
+
.filter((line) => labels.some((label) => line.includes(label)))
|
|
51
|
+
.map((line) => line.slice(Math.min(...labels.map((label) => {
|
|
52
|
+
const index = line.indexOf(label);
|
|
53
|
+
return index < 0 ? line.length : index;
|
|
54
|
+
}))));
|
|
55
|
+
return [UPDATE_BODY, '## Availability', summary.join('\n') || 'No readiness availability changes reported.'].join('\n\n');
|
|
56
|
+
}
|
|
57
|
+
|
|
45
58
|
export function createSystemAdapters({
|
|
46
59
|
cwd = process.cwd(), env = process.env, execute = exec,
|
|
47
60
|
} = {}) {
|
|
@@ -28,6 +28,27 @@ test('a successful update creates one stable update pull request', async () => {
|
|
|
28
28
|
]);
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
+
test('automated update pull requests carry the behavior availability summary', async () => {
|
|
32
|
+
const h = harness({ update: {
|
|
33
|
+
exitCode: 0,
|
|
34
|
+
stdout: [
|
|
35
|
+
'newly available: orchestrate-wave',
|
|
36
|
+
'newly degraded: orchestrate-wave.projectRecipe',
|
|
37
|
+
'newly blocked: none',
|
|
38
|
+
'still unresolved: orchestrateWaveRecipe:invalid',
|
|
39
|
+
].join('\n'),
|
|
40
|
+
stderr: '',
|
|
41
|
+
} });
|
|
42
|
+
|
|
43
|
+
await orchestrateUpdatePullRequest(h.options);
|
|
44
|
+
|
|
45
|
+
const created = h.calls.find((call) => Array.isArray(call) && call[0] === 'create')[1];
|
|
46
|
+
assert.match(created.body, /## Availability/);
|
|
47
|
+
assert.match(created.body, /newly degraded: orchestrate-wave\.projectRecipe/);
|
|
48
|
+
assert.match(created.body, /still unresolved: orchestrateWaveRecipe:invalid/);
|
|
49
|
+
assert.match(created.body, /never merged automatically/);
|
|
50
|
+
});
|
|
51
|
+
|
|
31
52
|
test('a conflict produces a structured report without touching the stable branch', async () => {
|
|
32
53
|
const h = harness({ update: { exitCode: 2, stdout: 'conflicts: 1', stderr: '' } });
|
|
33
54
|
const report = await orchestrateUpdatePullRequest(h.options);
|
|
@@ -59,6 +59,23 @@ class RetroEnforcementContract(unittest.TestCase):
|
|
|
59
59
|
|
|
60
60
|
|
|
61
61
|
class WrapupChainingContract(unittest.TestCase):
|
|
62
|
+
def test_prod_readiness_degrades_reporting_without_blocking_landing(self):
|
|
63
|
+
required = (
|
|
64
|
+
"readiness.mjs check --skill wrapup --json",
|
|
65
|
+
"Prod readiness is pending or missing; deploy reporting omitted.",
|
|
66
|
+
"landing continues normally",
|
|
67
|
+
"never authorizes the agent to invent or configure a deploy target",
|
|
68
|
+
"<!-- readiness:block deployReport -->",
|
|
69
|
+
"<!-- readiness:end -->",
|
|
70
|
+
)
|
|
71
|
+
for surface in SURFACES:
|
|
72
|
+
with self.subTest(surface=surface):
|
|
73
|
+
text = skill(surface, "wrapup")
|
|
74
|
+
for phrase in required:
|
|
75
|
+
self.assertIn(phrase, text)
|
|
76
|
+
self.assertEqual(text.count("<!-- readiness:block deployReport -->"), 1)
|
|
77
|
+
self.assertEqual(text.count("<!-- readiness:end -->"), 1)
|
|
78
|
+
|
|
62
79
|
def test_retro_is_model_invocable_on_both_surfaces(self):
|
|
63
80
|
for surface in SURFACES:
|
|
64
81
|
with self.subTest(surface=surface):
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Behavior contract for optional review and spike readiness layers."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import subprocess
|
|
7
|
+
import tempfile
|
|
8
|
+
import unittest
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
12
|
+
READINESS = ROOT / "scripts/readiness.mjs"
|
|
13
|
+
MANIFEST = ROOT / ".claude/skills/skill-manifest.json"
|
|
14
|
+
SURFACES = (".claude/skills", ".agents/skills")
|
|
15
|
+
SKILLS = {
|
|
16
|
+
"spec-self-critique": {
|
|
17
|
+
"block": "projectEnrichment",
|
|
18
|
+
"capability": "specCritiqueLayer",
|
|
19
|
+
"path": "docs/agents/skills/spec-self-critique.md",
|
|
20
|
+
"core": ("## The 12-point checklist", "Self-Critique complete"),
|
|
21
|
+
"enrichment": ("per-point enrichment", "project-specific incidents"),
|
|
22
|
+
},
|
|
23
|
+
"code-review": {
|
|
24
|
+
"block": "projectEnrichment",
|
|
25
|
+
"capability": "codeReviewLayer",
|
|
26
|
+
"path": "docs/agents/code-review.md",
|
|
27
|
+
"core": ("## Axis 1 — Standards", "## Axis 2 — Spec"),
|
|
28
|
+
"enrichment": ("which sources count", "adjacent tools"),
|
|
29
|
+
},
|
|
30
|
+
"verify-spike": {
|
|
31
|
+
"block": "projectPlacement",
|
|
32
|
+
"capability": "verifySpikeLayer",
|
|
33
|
+
"path": "docs/agents/skills/verify-spike.md",
|
|
34
|
+
"core": ("Frame one falsifiable question", "Delete the harness"),
|
|
35
|
+
"enrichment": ("harness-placement", "import rules"),
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
PREFLIGHT_START = "<!-- readiness:optional-preflight:start -->"
|
|
39
|
+
PREFLIGHT_END = "<!-- readiness:optional-preflight:end -->"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def write(root: Path, relative: str, body: str) -> None:
|
|
43
|
+
path = root / relative
|
|
44
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
path.write_text(body, encoding="utf-8")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class OptionalReadinessTests(unittest.TestCase):
|
|
49
|
+
def setUp(self) -> None:
|
|
50
|
+
self.temp = tempfile.TemporaryDirectory()
|
|
51
|
+
self.root = Path(self.temp.name)
|
|
52
|
+
write(
|
|
53
|
+
self.root,
|
|
54
|
+
".claude/skills/skill-manifest.json",
|
|
55
|
+
MANIFEST.read_text(encoding="utf-8"),
|
|
56
|
+
)
|
|
57
|
+
self.consumer = {
|
|
58
|
+
"kitVersion": "0.0.0-test",
|
|
59
|
+
"readinessContractVersion": 1,
|
|
60
|
+
"readinessDecisions": {},
|
|
61
|
+
"installed": [],
|
|
62
|
+
}
|
|
63
|
+
self.write_consumer()
|
|
64
|
+
|
|
65
|
+
def tearDown(self) -> None:
|
|
66
|
+
self.temp.cleanup()
|
|
67
|
+
|
|
68
|
+
def write_consumer(self) -> None:
|
|
69
|
+
write(self.root, "agent-workflow-kit.json", json.dumps(self.consumer))
|
|
70
|
+
|
|
71
|
+
def check(self, skill: str) -> dict:
|
|
72
|
+
completed = subprocess.run(
|
|
73
|
+
[
|
|
74
|
+
"node",
|
|
75
|
+
str(READINESS),
|
|
76
|
+
"check",
|
|
77
|
+
"--skill",
|
|
78
|
+
skill,
|
|
79
|
+
"--json",
|
|
80
|
+
"--root",
|
|
81
|
+
str(self.root),
|
|
82
|
+
],
|
|
83
|
+
check=True,
|
|
84
|
+
capture_output=True,
|
|
85
|
+
text=True,
|
|
86
|
+
)
|
|
87
|
+
return json.loads(completed.stdout)
|
|
88
|
+
|
|
89
|
+
def test_missing_and_pending_subtract_exactly_one_optional_block(self) -> None:
|
|
90
|
+
for decision in (None, "pending"):
|
|
91
|
+
for skill, contract in SKILLS.items():
|
|
92
|
+
with self.subTest(skill=skill, decision=decision):
|
|
93
|
+
self.consumer["readinessDecisions"] = {}
|
|
94
|
+
if decision:
|
|
95
|
+
self.consumer["readinessDecisions"][contract["capability"]] = decision
|
|
96
|
+
self.write_consumer()
|
|
97
|
+
result = self.check(skill)
|
|
98
|
+
self.assertEqual(result["verdict"], "degraded")
|
|
99
|
+
self.assertEqual(
|
|
100
|
+
result["capabilities"][contract["capability"]]["state"],
|
|
101
|
+
decision or "missing",
|
|
102
|
+
)
|
|
103
|
+
self.assertEqual(result["inactiveBlocks"], [contract["block"]])
|
|
104
|
+
self.assertEqual(result["activeBlocks"], [])
|
|
105
|
+
|
|
106
|
+
def test_invalid_enrichment_is_visible_and_never_an_opt_out(self) -> None:
|
|
107
|
+
for skill, contract in SKILLS.items():
|
|
108
|
+
with self.subTest(skill=skill):
|
|
109
|
+
write(
|
|
110
|
+
self.root,
|
|
111
|
+
contract["path"],
|
|
112
|
+
"<!-- setup-workflow: state=stub -->\npartial configuration\n",
|
|
113
|
+
)
|
|
114
|
+
self.consumer["readinessDecisions"][contract["capability"]] = "pending"
|
|
115
|
+
self.write_consumer()
|
|
116
|
+
result = self.check(skill)
|
|
117
|
+
self.assertEqual(result["verdict"], "degraded")
|
|
118
|
+
self.assertEqual(
|
|
119
|
+
result["capabilities"][contract["capability"]]["state"],
|
|
120
|
+
"invalid",
|
|
121
|
+
)
|
|
122
|
+
self.assertEqual(result["inactiveBlocks"], [contract["block"]])
|
|
123
|
+
|
|
124
|
+
def test_ready_enrichment_activates_the_block(self) -> None:
|
|
125
|
+
for skill, contract in SKILLS.items():
|
|
126
|
+
with self.subTest(skill=skill):
|
|
127
|
+
write(
|
|
128
|
+
self.root,
|
|
129
|
+
contract["path"],
|
|
130
|
+
"<!-- setup-workflow: state=filled -->\nProject guidance.\n",
|
|
131
|
+
)
|
|
132
|
+
self.consumer["readinessDecisions"] = {}
|
|
133
|
+
self.write_consumer()
|
|
134
|
+
result = self.check(skill)
|
|
135
|
+
self.assertEqual(result["verdict"], "ready")
|
|
136
|
+
self.assertEqual(result["activeBlocks"], [contract["block"]])
|
|
137
|
+
self.assertEqual(result["inactiveBlocks"], [])
|
|
138
|
+
|
|
139
|
+
def test_preflight_and_exact_subtraction_match_across_both_surfaces(self) -> None:
|
|
140
|
+
for skill, contract in SKILLS.items():
|
|
141
|
+
blocks = []
|
|
142
|
+
preflights = []
|
|
143
|
+
for surface in SURFACES:
|
|
144
|
+
body = (ROOT / surface / skill / "SKILL.md").read_text(encoding="utf-8")
|
|
145
|
+
start = f"<!-- readiness:block {contract['block']} -->"
|
|
146
|
+
self.assertEqual(body.count(start), 1)
|
|
147
|
+
self.assertEqual(body.count("<!-- readiness:end -->"), 1)
|
|
148
|
+
block = body.split(start, 1)[1].split("<!-- readiness:end -->", 1)[0]
|
|
149
|
+
preflight = body.split(PREFLIGHT_START, 1)[1].split(PREFLIGHT_END, 1)[0]
|
|
150
|
+
generic = body.replace(start + block + "<!-- readiness:end -->", "")
|
|
151
|
+
for anchor in contract["core"]:
|
|
152
|
+
self.assertIn(anchor, generic)
|
|
153
|
+
for anchor in contract["enrichment"]:
|
|
154
|
+
self.assertIn(anchor, block)
|
|
155
|
+
self.assertLess(body.index(PREFLIGHT_START), body.index(start))
|
|
156
|
+
self.assertIn(
|
|
157
|
+
f"node scripts/readiness.mjs check --skill {skill} --json",
|
|
158
|
+
preflight,
|
|
159
|
+
)
|
|
160
|
+
self.assertIn(f"inactive block `{contract['block']}`", preflight)
|
|
161
|
+
self.assertIn(contract["capability"], preflight)
|
|
162
|
+
self.assertIn("Ready is silent", preflight)
|
|
163
|
+
self.assertIn("Invalid is always visible", preflight)
|
|
164
|
+
blocks.append(block)
|
|
165
|
+
preflights.append(preflight)
|
|
166
|
+
self.assertEqual(blocks[0], blocks[1])
|
|
167
|
+
self.assertEqual(preflights[0], preflights[1])
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
if __name__ == "__main__":
|
|
171
|
+
unittest.main()
|