@haposoft/cafekit 0.16.7 → 0.18.1

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.
Files changed (231) hide show
  1. package/README.md +14 -22
  2. package/bin/__tests__/codex-hooks-ownership.test.js +2 -2
  3. package/bin/__tests__/codex-hooks.test.js +169 -272
  4. package/bin/__tests__/codex-native.test.js +100 -202
  5. package/bin/__tests__/develop-contract.test.js +122 -1413
  6. package/bin/__tests__/docs-sync-on-demand.test.js +93 -0
  7. package/bin/__tests__/grok-envelope.test.js +0 -56
  8. package/bin/__tests__/hook-payload.test.js +4 -6
  9. package/bin/__tests__/legacy-authority-decoupled.test.js +154 -0
  10. package/bin/__tests__/legacy-hooks-retired.test.js +221 -0
  11. package/bin/__tests__/omp-bridge.test.js +20 -17
  12. package/bin/__tests__/omp-hooks.test.js +0 -19
  13. package/bin/__tests__/optional-skill-inventory.test.js +112 -4
  14. package/bin/__tests__/orca-session.test.js +0 -2
  15. package/bin/__tests__/package-inventory.test.js +90 -925
  16. package/bin/__tests__/runtime-dir.test.js +0 -3
  17. package/bin/__tests__/runtime-schema.test.js +3 -2
  18. package/bin/__tests__/skill-routing-source.test.js +27 -15
  19. package/bin/__tests__/spec-narrowing.test.js +30 -161
  20. package/bin/__tests__/specs-v2-execution-closeout.test.js +0 -437
  21. package/bin/__tests__/test-runtime-dependency-closure.cjs +1 -2
  22. package/bin/__tests__/ui-ux-skill.test.js +101 -0
  23. package/bin/__tests__/usage-hook-retired.test.js +114 -0
  24. package/bin/__tests__/validator-grounding.test.js +18 -2949
  25. package/bin/lib/managed-writer.js +2 -3
  26. package/bin/phases/codex-runtime.js +27 -0
  27. package/bin/phases/copy-payload.js +13 -28
  28. package/bin/phases/omp-runtime.js +25 -0
  29. package/package.json +2 -3
  30. package/src/claude/CLAUDE.md +3 -10
  31. package/src/claude/agents/brainstormer.md +7 -3
  32. package/src/claude/agents/code-auditor.md +44 -56
  33. package/src/claude/agents/debugger.md +8 -88
  34. package/src/claude/agents/deployer.md +3 -5
  35. package/src/claude/agents/docs-keeper.md +6 -14
  36. package/src/claude/agents/git-ops.md +2 -4
  37. package/src/claude/agents/implementer.md +12 -16
  38. package/src/claude/agents/inspector.md +4 -3
  39. package/src/claude/agents/project-manager.md +9 -17
  40. package/src/claude/agents/researcher.md +9 -3
  41. package/src/claude/agents/spec-maker.md +15 -15
  42. package/src/claude/agents/test-runner.md +8 -11
  43. package/src/claude/agents/ui-ux-designer.md +0 -1
  44. package/src/claude/hooks/__tests__/fixtures/statusline-default.golden +1 -1
  45. package/src/claude/hooks/__tests__/spec-gate.test.js +244 -333
  46. package/src/claude/hooks/__tests__/spec-state-touched.test.js +160 -0
  47. package/src/claude/hooks/__tests__/state.test.js +68 -0
  48. package/src/claude/hooks/__tests__/statusline.test.js +140 -11
  49. package/src/claude/hooks/agent.cjs +2 -1
  50. package/src/claude/hooks/lib/hook-payload.cjs +2 -2
  51. package/src/claude/hooks/rules.cjs +2 -1
  52. package/src/claude/hooks/session.cjs +0 -1
  53. package/src/claude/hooks/spec-gate.cjs +17 -83
  54. package/src/claude/hooks/spec-state.cjs +142 -39
  55. package/src/claude/hooks/state.cjs +21 -2
  56. package/src/claude/migration-manifest.json +42 -22
  57. package/src/claude/output-styles/coding-level-0-eli5.md +9 -9
  58. package/src/claude/output-styles/coding-level-1-junior.md +3 -3
  59. package/src/claude/output-styles/coding-level-2-mid.md +3 -3
  60. package/src/claude/output-styles/coding-level-3-senior.md +2 -2
  61. package/src/claude/output-styles/coding-level-4-lead.md +5 -5
  62. package/src/claude/output-styles/coding-level-5-god.md +6 -6
  63. package/src/claude/rules/ai-dev-rules.md +2 -3
  64. package/src/claude/rules/hook-protocols.md +8 -15
  65. package/src/claude/rules/manage-docs.md +9 -23
  66. package/src/claude/rules/orchestrator.md +3 -4
  67. package/src/claude/rules/review-audit-self-decision.md +3 -3
  68. package/src/claude/rules/skill-domain-routing.md +1 -3
  69. package/src/claude/rules/skill-workflow-routing.md +4 -5
  70. package/src/claude/rules/state-sync.md +3 -12
  71. package/src/claude/rules/workflow.md +11 -19
  72. package/src/claude/runtime.schema.json +5 -3
  73. package/src/claude/scripts/generate-skill-catalog.cjs +7 -6
  74. package/src/claude/scripts/provenance.cjs +23 -5
  75. package/src/claude/scripts/spec-receipt.cjs +24 -121
  76. package/src/claude/scripts/spec-resolver.cjs +41 -458
  77. package/src/claude/scripts/workflow-policy.cjs +3 -1801
  78. package/src/claude/settings/settings.json +1 -32
  79. package/src/claude/skills/agent-browser/SKILL.md +1 -1
  80. package/src/claude/skills/{question → ask}/SKILL.md +9 -75
  81. package/src/claude/skills/brainstorm/SKILL.md +43 -131
  82. package/src/claude/skills/chrome-devtools/SKILL.md +14 -31
  83. package/src/claude/skills/code-review/SKILL.md +29 -81
  84. package/src/claude/skills/code-review/references/spec-compliance-review.md +4 -8
  85. package/src/claude/skills/debug/SKILL.md +29 -149
  86. package/src/claude/skills/develop/SKILL.md +26 -83
  87. package/src/claude/skills/develop/references/parallel-waves.md +0 -8
  88. package/src/claude/skills/develop/references/quality-gate.md +15 -13
  89. package/src/claude/skills/develop/references/subagent-patterns.md +0 -6
  90. package/src/claude/skills/docs/SKILL.md +2 -2
  91. package/src/claude/skills/docs/references/update-workflow.md +1 -1
  92. package/src/claude/skills/fix/SKILL.md +153 -0
  93. package/src/claude/skills/{hotfix → fix}/references/diagnosis-protocol.md +2 -2
  94. package/src/claude/skills/{hotfix → fix}/references/parallel-patterns.md +2 -2
  95. package/src/claude/skills/{hotfix → fix}/references/prevention-gate.md +3 -3
  96. package/src/claude/skills/{hotfix → fix}/references/review-cycle.md +1 -1
  97. package/src/claude/skills/git/SKILL.md +48 -9
  98. package/src/claude/skills/git/references/commit-protocols.md +35 -16
  99. package/src/claude/skills/git/references/worktree-blueprint.md +42 -6
  100. package/src/claude/skills/orca/SKILL.md +9 -8
  101. package/src/claude/skills/research/SKILL.md +17 -46
  102. package/src/claude/skills/scout/SKILL.md +61 -0
  103. package/src/claude/skills/{inspect → scout}/references/internal-inspection.md +26 -10
  104. package/src/claude/skills/specs/SKILL.md +22 -29
  105. package/src/claude/skills/specs/references/review.md +8 -6
  106. package/src/claude/skills/specs/references/templates.md +34 -41
  107. package/src/claude/skills/sync/SKILL.md +16 -45
  108. package/src/claude/skills/sync/references/rebind-and-audit.md +59 -0
  109. package/src/claude/skills/sync/references/sync-protocols.md +4 -13
  110. package/src/claude/skills/test/SKILL.md +39 -104
  111. package/src/claude/skills/test/references/execution-strategy.md +93 -21
  112. package/src/claude/skills/test/references/failure-triage.md +1 -1
  113. package/src/claude/skills/ui-ux/LICENSE +21 -0
  114. package/src/claude/skills/ui-ux/SKILL.md +497 -0
  115. package/src/claude/skills/ui-ux/references/brand-tokens.md +90 -0
  116. package/src/claude/skills/ui-ux/references/budgets.md +113 -0
  117. package/src/claude/skills/ui-ux/references/checklist.md +271 -0
  118. package/src/claude/skills/ui-ux/references/components/accordion.md +246 -0
  119. package/src/claude/skills/ui-ux/references/components/avatar.md +153 -0
  120. package/src/claude/skills/ui-ux/references/components/banner.md +43 -0
  121. package/src/claude/skills/ui-ux/references/components/breadcrumb.md +201 -0
  122. package/src/claude/skills/ui-ux/references/components/button.md +142 -0
  123. package/src/claude/skills/ui-ux/references/components/card.md +113 -0
  124. package/src/claude/skills/ui-ux/references/components/charts.md +376 -0
  125. package/src/claude/skills/ui-ux/references/components/chat.md +132 -0
  126. package/src/claude/skills/ui-ux/references/components/choice-controls.md +369 -0
  127. package/src/claude/skills/ui-ux/references/components/comment-thread.md +17 -0
  128. package/src/claude/skills/ui-ux/references/components/description-list.md +116 -0
  129. package/src/claude/skills/ui-ux/references/components/empty-state.md +98 -0
  130. package/src/claude/skills/ui-ux/references/components/file-upload.md +107 -0
  131. package/src/claude/skills/ui-ux/references/components/inline-edit.md +128 -0
  132. package/src/claude/skills/ui-ux/references/components/input.md +161 -0
  133. package/src/claude/skills/ui-ux/references/components/list-row.md +56 -0
  134. package/src/claude/skills/ui-ux/references/components/loading.md +198 -0
  135. package/src/claude/skills/ui-ux/references/components/logo.md +147 -0
  136. package/src/claude/skills/ui-ux/references/components/otp-input.md +43 -0
  137. package/src/claude/skills/ui-ux/references/components/quantity-input.md +100 -0
  138. package/src/claude/skills/ui-ux/references/components/range-slider.md +16 -0
  139. package/src/claude/skills/ui-ux/references/components/small-controls.md +340 -0
  140. package/src/claude/skills/ui-ux/references/components/sortable-header.md +86 -0
  141. package/src/claude/skills/ui-ux/references/components/tag-input.md +14 -0
  142. package/src/claude/skills/ui-ux/references/components/timeline.md +38 -0
  143. package/src/claude/skills/ui-ux/references/components/tree.md +30 -0
  144. package/src/claude/skills/ui-ux/references/design-process.md +650 -0
  145. package/src/claude/skills/ui-ux/references/layouts/app-kanban.html +303 -0
  146. package/src/claude/skills/ui-ux/references/layouts/app.md +1369 -0
  147. package/src/claude/skills/ui-ux/references/layouts/form.md +430 -0
  148. package/src/claude/skills/ui-ux/references/layouts/overlay.md +552 -0
  149. package/src/claude/skills/ui-ux/references/layouts/pricing.md +204 -0
  150. package/src/claude/skills/ui-ux/references/locked-rules.md +35 -0
  151. package/src/claude/skills/ui-ux/references/principles.md +408 -0
  152. package/src/claude/skills/ui-ux/references/refactor.md +245 -0
  153. package/src/claude/skills/ui-ux/references/responsive.md +195 -0
  154. package/src/claude/skills/ui-ux/references/review.md +528 -0
  155. package/src/claude/skills/ui-ux/references/rules-color.md +623 -0
  156. package/src/claude/skills/ui-ux/references/rules-form.md +235 -0
  157. package/src/claude/skills/ui-ux/references/rules-state.md +680 -0
  158. package/src/claude/skills/ui-ux/references/rules-type.md +369 -0
  159. package/src/claude/skills/ui-ux/references/styles.md +311 -0
  160. package/src/claude/skills/ui-ux/references/system.md +219 -0
  161. package/src/claude/skills/ui-ux/references/tailwind-v4-traps.md +237 -0
  162. package/src/claude/skills/ui-ux/references/tokens.css +362 -0
  163. package/src/claude/skills/ui-ux/scripts/lint-skill.mjs +216 -0
  164. package/src/claude/skills/ui-ux/scripts/package.json +9 -0
  165. package/src/claude/skills/ui-ux/scripts/probe.mjs +4447 -0
  166. package/src/claude/skills/ui-ux-pro-max/SKILL.md +25 -80
  167. package/src/claude/skills/web-testing/SKILL.md +2 -20
  168. package/src/claude/status.cjs +149 -144
  169. package/src/codex/AGENTS.md +8 -13
  170. package/src/codex/hooks/agent.cjs +2 -1
  171. package/src/codex/hooks/lib/spec-receipt.cjs +4 -32
  172. package/src/codex/hooks/lib/spec-utils.cjs +6 -52
  173. package/src/codex/hooks/rules.cjs +2 -1
  174. package/src/codex/hooks/session.cjs +0 -2
  175. package/src/codex/hooks/spec-gate.cjs +13 -81
  176. package/src/codex/hooks/spec-state.cjs +37 -41
  177. package/src/codex/hooks.json +0 -20
  178. package/src/codex/rules/state-sync.md +3 -12
  179. package/src/common/AGENTS.md +6 -12
  180. package/src/omp/extensions/cafekit-bridge.mjs +6 -6
  181. package/bin/__tests__/change-firewall.semantic-firewall.test.js +0 -2133
  182. package/bin/__tests__/finalizer-pass-gate.semantic-firewall.test.js +0 -342
  183. package/bin/__tests__/release-preflight.semantic-firewall.test.js +0 -586
  184. package/bin/__tests__/review-receipt-c2.semantic-firewall.test.js +0 -378
  185. package/bin/__tests__/specs-v2-policy-and-scaffold.test.js +0 -665
  186. package/bin/__tests__/specs-v2-validator-grounder-contract.test.js +0 -1132
  187. package/src/claude/hooks/__tests__/completion-authority.test.js +0 -990
  188. package/src/claude/hooks/__tests__/semantic-review-authority.test.js +0 -259
  189. package/src/claude/hooks/completion-authority-check.cjs +0 -56
  190. package/src/claude/hooks/completion-authority-state.cjs +0 -527
  191. package/src/claude/hooks/completion-authority.cjs +0 -163
  192. package/src/claude/hooks/lib/context.cjs +0 -616
  193. package/src/claude/hooks/lib/detect.cjs +0 -474
  194. package/src/claude/hooks/semantic-review-authority.cjs +0 -154
  195. package/src/claude/hooks/task-scaffold-guard.cjs +0 -114
  196. package/src/claude/hooks/usage.cjs +0 -197
  197. package/src/claude/scripts/change-firewall.cjs +0 -1748
  198. package/src/claude/scripts/spec-authoring-digest.cjs +0 -151
  199. package/src/claude/scripts/spec-authoring-validation.cjs +0 -126
  200. package/src/claude/scripts/spec-final-state.cjs +0 -290
  201. package/src/claude/scripts/spec-ground.cjs +0 -650
  202. package/src/claude/scripts/spec-readiness.cjs +0 -425
  203. package/src/claude/scripts/spec-scaffold.cjs +0 -2317
  204. package/src/claude/scripts/spec-semantic-model.cjs +0 -396
  205. package/src/claude/scripts/validate-spec-output.cjs +0 -3941
  206. package/src/claude/skills/code-review/references/verification-gate.md +0 -46
  207. package/src/claude/skills/delegate/SKILL.md +0 -124
  208. package/src/claude/skills/delegate/references/codex-delegation.md +0 -71
  209. package/src/claude/skills/delegate/references/grok-delegation.md +0 -84
  210. package/src/claude/skills/hotfix/SKILL.md +0 -289
  211. package/src/claude/skills/inspect/SKILL.md +0 -142
  212. package/src/claude/skills/loop/SKILL.md +0 -114
  213. package/src/claude/skills/loop/references/bounded-loop-protocol.md +0 -104
  214. package/src/claude/skills/loop/references/metric-and-guard-contract.md +0 -76
  215. package/src/claude/skills/route/SKILL.md +0 -94
  216. package/src/claude/skills/route/references/agent-timing.md +0 -46
  217. package/src/claude/skills/route/references/chaining-patterns.md +0 -53
  218. package/src/claude/skills/route/references/task-taxonomy.md +0 -48
  219. package/src/claude/skills/specs/templates/design.md +0 -82
  220. package/src/claude/skills/specs/templates/requirements-init.md +0 -8
  221. package/src/claude/skills/specs/templates/requirements.md +0 -42
  222. package/src/claude/skills/specs/templates/spec-state.json +0 -56
  223. package/src/claude/skills/specs/templates/task.md +0 -47
  224. package/src/codex/hooks/completion-authority-check.cjs +0 -67
  225. package/src/codex/hooks/completion-authority-state.cjs +0 -527
  226. package/src/codex/hooks/completion-authority.cjs +0 -109
  227. package/src/codex/hooks/semantic-review-authority.cjs +0 -130
  228. package/src/codex/hooks/task-scaffold-guard.cjs +0 -63
  229. /package/src/claude/skills/{question → ask}/templates/question.md +0 -0
  230. /package/src/claude/skills/{hotfix → fix}/references/escalation-tactics.md +0 -0
  231. /package/src/claude/skills/{hotfix → fix}/references/workflow-specialized.md +0 -0
@@ -5,23 +5,8 @@ const crypto = require('node:crypto');
5
5
  const fs = require('node:fs');
6
6
  const os = require('node:os');
7
7
  const path = require('node:path');
8
- const { spawnSync } = require('node:child_process');
9
8
  const test = require('node:test');
10
9
 
11
- const ROOT = path.resolve(__dirname, '../..');
12
- const SCRIPTS = path.join(ROOT, 'src/claude/scripts');
13
- const POLICY = require(path.join(SCRIPTS, 'workflow-policy.cjs'));
14
- const PROVENANCE = require(path.join(SCRIPTS, 'provenance.cjs'));
15
- const RECEIPT = require(path.join(SCRIPTS, 'spec-receipt.cjs'));
16
- const SCAFFOLD = path.join(SCRIPTS, 'spec-scaffold.cjs');
17
- const VALIDATOR = path.join(SCRIPTS, 'validate-spec-output.cjs');
18
- const AUTHORING_VALIDATION = path.join(SCRIPTS, 'spec-authoring-validation.cjs');
19
- const RUNTIME_CLOSURE = require('./test-runtime-dependency-closure.cjs');
20
- const SEMANTIC_MODEL = require(path.join(SCRIPTS, 'spec-semantic-model.cjs'));
21
- const CLAUDE_CHECK = require(path.join(ROOT, 'src/claude/hooks/completion-authority-check.cjs'));
22
- const CODEX_CHECK = require(path.join(ROOT, 'src/codex/hooks/completion-authority-check.cjs'));
23
- const FEATURE = 'closeout-demo';
24
- const TASK = 'tasks/task-R1-01-closeout.md';
25
10
  const TEMP_RUN_ID = String(process.env.CAFEKIT_CLOSEOUT_TEMP_RUN_ID || `${process.pid}-${Date.now()}`)
26
11
  .replace(/[^a-zA-Z0-9_-]/g, '-');
27
12
  const TEMP_PREFIX = `cafekit-v2-closeout-owned-${TEMP_RUN_ID}`;
@@ -71,425 +56,3 @@ test('temp resource scope cleans exact owned paths and restores HOME after failu
71
56
  assert.equal(Object.hasOwn(process.env, 'HOME'), originalHome.present);
72
57
  if (originalHome.present) assert.equal(process.env.HOME, originalHome.value);
73
58
  });
74
-
75
- function gitRoot(resources) {
76
- const root = resources.directory('git-root');
77
- for (const args of [['init', '-q'], ['config', 'user.email', 'test@example.invalid'], ['config', 'user.name', 'Test'], ['commit', '--allow-empty', '-qm', 'base']]) {
78
- const result = spawnSync('git', ['-C', root, ...args], { encoding: 'utf8' });
79
- assert.equal(result.status, 0, result.stderr);
80
- }
81
- return root;
82
- }
83
-
84
- function runtime(root, session = 'execution-session') {
85
- return PROVENANCE.deriveRuntimeContext({
86
- projectRoot: root,
87
- specsRoot: path.join(root, 'specs'),
88
- specFile: path.join(root, 'specs', FEATURE, 'spec.json'),
89
- featureName: FEATURE,
90
- runtimeSession: session,
91
- });
92
- }
93
-
94
- function receiptBody(ctx, extra = '') {
95
- return [
96
- 'Verification: PASS',
97
- 'Command: node --test',
98
- 'Exit: 0',
99
- 'Result: PASS',
100
- 'Expected: focused verification passes',
101
- 'Observed: focused verification passed',
102
- `Base: ${ctx.base}`,
103
- `Head: ${ctx.head}`,
104
- extra,
105
- ].filter(Boolean).join('\n') + '\n';
106
- }
107
-
108
- function prepareCanonicalTaskless(root, { specStatus, featureReceipt }) {
109
- const scaffold = spawnSync(process.execPath, [SCAFFOLD, FEATURE, '--title', 'Closeout fixture'], { cwd: root, encoding: 'utf8' });
110
- assert.equal(scaffold.status, 0, scaffold.stderr);
111
- const featureDir = path.join(root, 'specs', FEATURE);
112
- fs.mkdirSync(path.join(root, 'src'), { recursive: true });
113
- fs.mkdirSync(path.join(root, 'test'), { recursive: true });
114
- fs.writeFileSync(path.join(root, 'src/final-state.js'), 'module.exports = { ready: true };\n');
115
- fs.writeFileSync(path.join(root, 'test/final-state.test.js'), "require('node:test')('final state', () => {});\n");
116
- fs.writeFileSync(path.join(featureDir, 'requirements.md'), `# Requirements
117
-
118
- ## Requirements
119
- ### Requirement 1: Closeout state
120
- - **R1.1**: When closeout is requested, the system shall accept current validated state and reject stale semantic state.
121
- - **R1.2**: The system shall prove the current digest through the grounded final-state boundary.
122
- `);
123
- fs.writeFileSync(path.join(featureDir, 'design.md'), `# Design
124
-
125
- ## Architecture
126
- The final-state authority recomputes current semantic state before closeout.
127
-
128
- ## Typed Anchors
129
- | ID | Type | Target | Role | Access | Action |
130
- |---|---|---|---|---|---|
131
- | A-D-01 | file | \`src/final-state.js\` | design boundary | read | read |
132
- | A-D-02 | file | \`test/final-state.test.js\` | separate proof boundary | read | read |
133
-
134
- ## Canonical Contracts & Invariants
135
- ### D1 — Current-state decision
136
- The current persisted artifacts are authoritative.
137
- ### I1 — Stale-state invariant
138
- A changed semantic artifact cannot retain readiness.
139
- ### C1 — Closeout contract
140
- Only a validated and grounded current digest reaches closeout.
141
-
142
- ## Verification Definitions
143
- - **V1**: Subject criteria R1.1; Subject owner A-D-01; Proof criteria R1.2; Proof owner A-D-02; Evidence anchor A-D-02; Decision refs D1, I1, C1; Method inspection \`src/final-state.js\` via A-D-01; Expected current validated state reaches closeout with the reviewed digest; Negative/failure stale semantic state is rejected before execution closeout; Reachability/grounding entrypoint \`src/final-state.js\` via A-D-01, A-D-02.
144
-
145
- ## Verification
146
- R1.1 and R1.2 are verified by V1 against D1, I1, and C1.
147
- `);
148
- const specPath = path.join(featureDir, 'spec.json');
149
- const seed = JSON.parse(fs.readFileSync(specPath, 'utf8'));
150
- const seedProjection = SEMANTIC_MODEL.modelFromMarkdown(featureDir, seed);
151
- assert.deepEqual(seedProjection.errors, []);
152
- seed.semantic_model = seedProjection.model;
153
- fs.writeFileSync(specPath, `${JSON.stringify(seed, null, 2)}\n`);
154
- // I21: only the C16 coordinator may set an authoring stage to `validated`,
155
- // and only together with the digest receipt that proves it fresh. The fixture
156
- // runs the real command instead of hand-authoring the enum.
157
- const authored = spawnSync(
158
- process.execPath,
159
- [AUTHORING_VALIDATION, featureDir, '--root', root],
160
- { cwd: root, encoding: 'utf8' },
161
- );
162
- assert.equal(authored.status, 0, `${authored.stdout}\n${authored.stderr}`);
163
- const spec = JSON.parse(fs.readFileSync(specPath, 'utf8'));
164
- assert.equal(spec.authoring.requirements, 'validated');
165
- assert.equal(spec.authoring.design, 'validated');
166
- spec.validation.status = 'completed';
167
- spec.validation.semantic_review = {
168
- status: 'completed',
169
- semantic_digest: null,
170
- verdict: 'PASS',
171
- lifecycle_disposition: 'CONTINUE',
172
- findings: [],
173
- unresolved_decisions: [],
174
- graph_coverage: [
175
- 'criterion_local', 'cross_criterion', 'runtime_path',
176
- 'assumption_provenance', 'compatibility_migration',
177
- ].map((surface) => ({ surface, covered: true, notes: `${surface} reviewed for the closeout fixture.` })),
178
- repair_round: 0,
179
- reviewer_evidence: {
180
- assurance: 'Routine',
181
- summary: 'Closeout fixture reviewer confirmed both criteria against the grounded final-state boundary.',
182
- },
183
- reviewed_criteria: ['R1.1', 'R1.2'],
184
- counterexamples: [{
185
- criterion: 'R1.1', case_kind: 'failure',
186
- scenario: 'A requirements artifact changes after the reviewed digest was recorded.',
187
- expected: 'Final-state authority rejects closeout until the current digest is reviewed.',
188
- decision_refs: ['D1', 'I1', 'C1'], verification_ref: 'V1',
189
- }, {
190
- criterion: 'R1.2', case_kind: 'adversarial',
191
- scenario: 'A closeout supplies execution provenance while retaining a digest for older authored artifacts.',
192
- expected: 'Final-state authority rejects the stale digest despite valid execution provenance.',
193
- decision_refs: ['D1', 'I1', 'C1'], verification_ref: 'V1',
194
- }],
195
- };
196
- fs.writeFileSync(specPath, `${JSON.stringify(spec, null, 2)}\n`);
197
- const digest = spawnSync(process.execPath, [VALIDATOR, featureDir, '--semantic-digest'], { cwd: root, encoding: 'utf8' });
198
- assert.equal(digest.status, 0, `${digest.stdout}\n${digest.stderr}`);
199
- spec.validation.semantic_review.semantic_digest = digest.stdout.trim();
200
- // C13: a completed receipt is only terminal when the durable history records
201
- // it. The entry binds the same semantic digest, the receipt's own canonical
202
- // digest, and the attempt index the receipt reports as repair_round.
203
- spec.validation.semantic_review_history.entries = [{
204
- sequence: 0,
205
- semantic_digest: spec.validation.semantic_review.semantic_digest,
206
- review_receipt_digest: `sha256:${crypto.createHash('sha256')
207
- .update(SEMANTIC_MODEL.stableJson(spec.validation.semantic_review), 'utf8').digest('hex')}`,
208
- verdict: 'PASS',
209
- lifecycle_disposition: 'CONTINUE',
210
- blocking_count: 0,
211
- attempt_index: spec.validation.semantic_review.repair_round,
212
- review_epoch: 0,
213
- }];
214
- spec.ready_for_implementation = true;
215
- spec.status = specStatus === 'complete' ? 'done' : specStatus;
216
- delete spec.current_phase;
217
- delete spec.phase;
218
- fs.writeFileSync(specPath, `${JSON.stringify(spec, null, 2)}\n`);
219
- const ctx = runtime(root);
220
- if (featureReceipt) fs.writeFileSync(path.join(featureDir, 'feature-receipt.md'), receiptBody(ctx, `Feature: ${FEATURE}`));
221
- return { featureDir, ctx };
222
- }
223
-
224
- function prepare(root, { taskless = false, specStatus = 'in_progress', taskStatus = 'done', taskReceipt = true, legacy = false, featureReceipt = false } = {}) {
225
- if (taskless) return prepareCanonicalTaskless(root, { specStatus, featureReceipt });
226
- const featureDir = path.join(root, 'specs', FEATURE);
227
- fs.mkdirSync(path.join(featureDir, 'tasks'), { recursive: true });
228
- const policy = POLICY.canonicalWorkflowPolicySnapshot({ planning_depth: 'Compact', assurance_level: 'Routine' });
229
- const spec = {
230
- status: specStatus === 'complete' ? 'in_progress' : specStatus,
231
- current_phase: specStatus === 'complete' ? 'closeout' : 'implementation',
232
- feature_name: FEATURE, workflow_policy: policy, task_registry: {},
233
- };
234
- if (!taskless) spec.task_registry[TASK] = { status: taskStatus, completed_at: '2026-08-13T00:00:00.000Z', dependencies: [] };
235
- fs.writeFileSync(path.join(featureDir, 'spec.json'), JSON.stringify(spec));
236
- const ctx = runtime(root);
237
- if (!taskless) {
238
- const plan = [
239
- '# Task R1-01: closeout', `**Status:** ${taskStatus}`, '## Outcome', 'Closeout works.',
240
- '## Verification Plan', '- **Command:** `node --test`', '- **Expected:** focused verification passes',
241
- '- **Negative path:** not relevant', '- **Reachability:** not relevant',
242
- ].join('\n') + (legacy ? `\n## Evidence\n\n${receiptBody(ctx)}` : '\n');
243
- fs.writeFileSync(path.join(featureDir, TASK), plan);
244
- if (taskReceipt) {
245
- fs.mkdirSync(path.join(featureDir, 'receipts'), { recursive: true });
246
- fs.writeFileSync(path.join(featureDir, 'receipts', path.basename(TASK)), receiptBody(ctx, `Task: ${path.basename(TASK)}\nTask path: ${TASK}`));
247
- }
248
- }
249
- if (featureReceipt) fs.writeFileSync(path.join(featureDir, 'feature-receipt.md'), receiptBody(ctx, `Feature: ${FEATURE}`));
250
- return { featureDir, ctx };
251
- }
252
-
253
- function installGate(root, kind) {
254
- const runtimeDir = path.join(root, kind === 'claude' ? '.claude' : '.codex');
255
- fs.mkdirSync(path.join(runtimeDir, 'hooks'), { recursive: true });
256
- fs.mkdirSync(path.join(runtimeDir, 'hooks', 'lib'), { recursive: true });
257
- fs.mkdirSync(path.join(runtimeDir, 'scripts'), { recursive: true });
258
- // Copy the real transitive require closure rather than a hand-listed subset.
259
- // A hand-listed subset silently rots the moment one of these entrypoints
260
- // gains a new local dependency: the fixture keeps building, and the gate only
261
- // fails later with a bare MODULE_NOT_FOUND from inside the installed runtime.
262
- RUNTIME_CLOSURE.copyClaudeTestRuntime(ROOT, runtimeDir, [
263
- 'scripts/workflow-policy.cjs', 'scripts/provenance.cjs', 'scripts/spec-resolver.cjs',
264
- 'scripts/spec-receipt.cjs', 'scripts/validate-spec-output.cjs', 'scripts/spec-ground.cjs',
265
- 'scripts/spec-semantic-model.cjs', 'scripts/spec-final-state.cjs',
266
- 'hooks/lib/runtime-path-safety.cjs', 'hooks/lib/hook-state-dir.cjs', 'hooks/lib/runtime-dir.cjs',
267
- 'hooks/lib/hook-payload.cjs',
268
- ]);
269
- if (kind === 'claude') {
270
- for (const name of [
271
- 'spec-gate.cjs', 'completion-authority-check.cjs', 'completion-authority-state.cjs',
272
- 'semantic-review-authority.cjs',
273
- ]) fs.copyFileSync(path.join(ROOT, 'src/claude/hooks', name), path.join(runtimeDir, 'hooks', name));
274
- } else {
275
- fs.cpSync(path.join(ROOT, 'src/codex/hooks'), path.join(runtimeDir, 'hooks'), { recursive: true });
276
- }
277
- // The real installer materializes this canonical helper for both adapters.
278
- // Keep this narrow runtime fixture faithful to the same dependency closure.
279
- fs.copyFileSync(
280
- path.join(ROOT, 'src/claude/hooks/lib/runtime-path-safety.cjs'),
281
- path.join(runtimeDir, 'hooks/lib/runtime-path-safety.cjs'),
282
- );
283
- return path.join(runtimeDir, 'hooks/spec-gate.cjs');
284
- }
285
-
286
- function gate(root, kind) {
287
- const home = path.join(root, '.claude', 'hooks', '.logs', 'test-home');
288
- fs.mkdirSync(home, { recursive: true });
289
- const hook = installGate(root, kind);
290
- const featureReceipt = path.join(root, 'specs', FEATURE, 'feature-receipt.md');
291
- if (fs.existsSync(featureReceipt)) {
292
- const current = runtime(root);
293
- fs.writeFileSync(featureReceipt, receiptBody(current, `Feature: ${FEATURE}`));
294
- assert.deepEqual(RECEIPT.checkFeatureReceipt(path.dirname(featureReceipt), current, POLICY).failures, []);
295
- }
296
- const result = spawnSync(process.execPath, [hook], {
297
- cwd: root,
298
- env: { ...process.env, HOME: home, USERPROFILE: home, ...(kind === 'claude' ? { CLAUDE_PROJECT_DIR: root } : {}) },
299
- input: JSON.stringify({ cwd: root, session_id: 'execution-session', hook_event_name: 'Stop', stop_hook_active: false }),
300
- encoding: 'utf8',
301
- });
302
- assert.equal(result.status, 0, result.stderr);
303
- return result.stdout.trim();
304
- }
305
-
306
- test('separate receipt passes; missing, traversal, symlink, and conflicting legacy proof fail closed', () => {
307
- withTempResources((resources) => {
308
- const root = gitRoot(resources);
309
- const { featureDir, ctx } = prepare(root);
310
- assert.deepEqual(RECEIPT.checkTaskReceipt(featureDir, TASK, { completed_at: '2026-08-13T00:00:00.000Z' }, ctx, POLICY).failures, []);
311
- fs.unlinkSync(path.join(featureDir, 'receipts', path.basename(TASK)));
312
- assert.ok(RECEIPT.checkTaskReceipt(featureDir, TASK, { completed_at: '2026-08-13T00:00:00.000Z' }, ctx, POLICY).failures.includes('missing_receipt'));
313
- assert.equal(RECEIPT.canonicalTaskReceiptPath('../task-x.md'), null);
314
- const outside = path.join(root, 'outside.md');
315
- fs.writeFileSync(outside, receiptBody(ctx));
316
- fs.mkdirSync(path.join(featureDir, 'receipts'), { recursive: true });
317
- fs.symlinkSync(outside, path.join(featureDir, 'receipts', path.basename(TASK)));
318
- assert.ok(RECEIPT.checkTaskReceipt(featureDir, TASK, { completed_at: '2026-08-13T00:00:00.000Z' }, ctx, POLICY).failures.includes('unsafe_path'));
319
- fs.unlinkSync(path.join(featureDir, 'receipts', path.basename(TASK)));
320
- const taskFile = path.join(featureDir, TASK);
321
- fs.appendFileSync(taskFile, `\n## Evidence\n\n${receiptBody(ctx)}`);
322
- assert.equal(RECEIPT.checkTaskReceipt(featureDir, TASK, { completed_at: '2026-08-13T00:00:00.000Z' }, ctx, POLICY).source, 'legacy');
323
- fs.writeFileSync(path.join(featureDir, 'receipts', path.basename(TASK)), receiptBody(ctx, `Task: ${path.basename(TASK)}\nTask path: ${TASK}`).replace('Command: node --test', 'Command: node --test changed'));
324
- assert.ok(RECEIPT.checkTaskReceipt(featureDir, TASK, { completed_at: '2026-08-13T00:00:00.000Z' }, ctx, POLICY).failures.includes('receipt_conflict'));
325
- });
326
- });
327
-
328
- test('Claude and Codex gates require task proof at every Stop and feature proof only at durable closeout', () => {
329
- withTempResources((resources) => {
330
- for (const kind of ['claude', 'codex']) {
331
- const early = gitRoot(resources);
332
- const invalidAlias = gitRoot(resources);
333
- const taskless = gitRoot(resources);
334
- const taskBearing = gitRoot(resources);
335
- prepare(early, { taskless: true, specStatus: 'in_progress' });
336
- assert.equal(gate(early, kind), '', `${kind} pre-closeout`);
337
- const { featureDir: invalidAliasDir } = prepare(invalidAlias, { taskless: true, specStatus: 'in_progress' });
338
- const invalidAliasFile = path.join(invalidAliasDir, 'spec.json');
339
- const invalidAliasSpec = JSON.parse(fs.readFileSync(invalidAliasFile, 'utf8'));
340
- invalidAliasSpec.status = 'completed';
341
- fs.writeFileSync(invalidAliasFile, `${JSON.stringify(invalidAliasSpec, null, 2)}\n`);
342
- assert.match(gate(invalidAlias, kind), /spec status is invalid: completed/, `${kind} rejects legacy aliases in schema 2.1`);
343
- prepare(taskless, { taskless: true, specStatus: 'complete', featureReceipt: true });
344
- assert.equal(gate(taskless, kind), '', `${kind} taskless closeout`);
345
- fs.appendFileSync(path.join(taskless, 'specs', FEATURE, 'requirements.md'), '\nSemantic mutation after review.\n');
346
- assert.match(gate(taskless, kind), /validation failed|semantic digest|stale/i, `${kind} rejects stale semantics with unchanged execution provenance`);
347
- prepare(taskBearing, { taskReceipt: false, featureReceipt: false });
348
- assert.match(gate(taskBearing, kind), new RegExp(path.basename(TASK)), `${kind} unproven done task before closeout`);
349
- prepare(taskBearing, { taskReceipt: true, featureReceipt: false });
350
- assert.equal(gate(taskBearing, kind), '', `${kind} proven done task before closeout`);
351
- assert.equal(fs.existsSync(path.join(taskBearing, 'specs', FEATURE, 'feature-receipt.md')), false);
352
- prepare(taskBearing, { specStatus: 'complete', featureReceipt: false });
353
- assert.match(gate(taskBearing, kind), /feature-receipt\.md/);
354
- prepare(taskBearing, { specStatus: 'complete', featureReceipt: true });
355
- assert.equal(gate(taskBearing, kind), '');
356
- fs.appendFileSync(path.join(taskBearing, 'specs', FEATURE, 'receipts', path.basename(TASK)), 'Exit: 1\n');
357
- assert.match(gate(taskBearing, kind), new RegExp(path.basename(TASK)));
358
- }
359
- });
360
- });
361
-
362
- test('an unproven done dependency cannot advance a dependent task', () => {
363
- withTempResources((resources) => {
364
- for (const kind of ['claude', 'codex']) {
365
- const root = gitRoot(resources);
366
- const { featureDir } = prepare(root, { taskReceipt: false });
367
- const dependent = 'tasks/task-R1-02-dependent.md';
368
- const specFile = path.join(featureDir, 'spec.json');
369
- const spec = JSON.parse(fs.readFileSync(specFile, 'utf8'));
370
- spec.task_registry[dependent] = { status: 'pending', completed_at: null, dependencies: [TASK] };
371
- fs.writeFileSync(specFile, JSON.stringify(spec));
372
- fs.writeFileSync(path.join(featureDir, dependent), '# Task R1-02: dependent\n\n**Status:** pending\n');
373
- const blocked = gate(root, kind);
374
- assert.match(blocked, new RegExp(path.basename(TASK)), `${kind} must reject the unproven done dependency`);
375
- assert.equal(JSON.parse(fs.readFileSync(specFile, 'utf8')).task_registry[dependent].status, 'pending');
376
- }
377
- });
378
- });
379
-
380
- test('completion authority supports taskless v2 and binds task plan plus receipt bytes', () => {
381
- for (const [kind, checker] of [['claude', CLAUDE_CHECK], ['codex', CODEX_CHECK]]) {
382
- withTempResources((resources) => {
383
- const root = gitRoot(resources);
384
- resources.useHome(`${kind}-home`);
385
- prepare(root, { taskless: true, specStatus: 'complete', featureReceipt: true });
386
- const args = { policy: POLICY, projectRoot: root, runtime: {}, payload: { session_id: 'execution-session' } };
387
- if (kind === 'claude') args.resolver = require(path.join(SCRIPTS, 'spec-resolver.cjs'));
388
- const result = checker.evaluateCloseout(args);
389
- assert.equal(result.ok, true, result.reason);
390
- assert.match(result.binding.feature_receipt_digest, /^[a-f0-9]{64}$/);
391
- });
392
- }
393
- });
394
-
395
- test('shared taskless terminal predicate requires canonical 2.1 policy and physical tasklessness', () => {
396
- withTempResources((resources) => {
397
- const root = gitRoot(resources);
398
- const { featureDir } = prepare(root, { taskless: true, specStatus: 'complete' });
399
- const specPath = path.join(featureDir, 'spec.json');
400
- const canonical = JSON.parse(fs.readFileSync(specPath, 'utf8'));
401
- assert.equal(POLICY.isCanonicalTasklessTerminalSpec(canonical, featureDir), true);
402
-
403
- const legacy = {
404
- ...canonical,
405
- schema_version: '2.0',
406
- workflow_policy: POLICY.workflowPolicySnapshot({ planning_depth: 'Compact', assurance_level: 'Routine' }),
407
- };
408
- assert.equal(POLICY.isCanonicalTasklessTerminalSpec(legacy, featureDir), false);
409
-
410
- fs.mkdirSync(path.join(featureDir, 'tasks'), { recursive: true });
411
- fs.writeFileSync(path.join(featureDir, 'tasks', 'hidden.md'), '# physical task\n');
412
- assert.equal(POLICY.isCanonicalTasklessTerminalSpec(canonical, featureDir), false);
413
- });
414
- });
415
-
416
- test('completion authority activates only at durable closeout and honors the Stop loop guard', () => {
417
- for (const [kind, checker] of [['claude', CLAUDE_CHECK], ['codex', CODEX_CHECK]]) {
418
- withTempResources((resources) => {
419
- const root = gitRoot(resources);
420
- resources.useHome(`${kind}-home`);
421
- prepare(root, { featureReceipt: false });
422
- const args = { policy: POLICY, projectRoot: root, runtime: {}, payload: { session_id: 'execution-session' } };
423
- if (kind === 'claude') args.resolver = require(path.join(SCRIPTS, 'spec-resolver.cjs'));
424
- const inactive = checker.evaluateCloseout(args);
425
- assert.equal(inactive.ok, true, inactive.reason);
426
- assert.equal(inactive.active, false);
427
-
428
- args.payload.stop_hook_active = true;
429
- const loop = checker.evaluateCloseout(args);
430
- assert.equal(loop.ok, true, loop.reason);
431
- assert.equal(loop.active, false);
432
-
433
- prepare(root, { specStatus: 'complete', featureReceipt: false });
434
- args.payload.stop_hook_active = false;
435
- const blocked = checker.evaluateCloseout(args);
436
- assert.equal(blocked.ok, false);
437
- assert.match(blocked.reason, /feature-receipt\.md/);
438
- });
439
- }
440
- });
441
-
442
- test('v2 floor merge keeps axes independent, planning controls artifact profile, and risks raise assurance floor', () => {
443
- const policy = {
444
- PLANNING_DEPTHS: ['None', 'Compact', 'Full'], ASSURANCE_LEVELS: ['Routine', 'Elevated', 'Strict'],
445
- compatibilityLane: POLICY.compatibilityLane, planningObligationsFor: POLICY.planningObligationsFor,
446
- obligationsForAssurance: POLICY.obligationsForAssurance, actorNeedsFor: POLICY.actorNeedsFor,
447
- classifyLane: POLICY.classifyLane,
448
- };
449
- const floor = { version: '2', planning_depth: 'Full', automatic_planning_depth: 'Full', assurance_level: 'Routine', automatic_assurance_level: 'Routine', lane: 'Standard', automatic_lane: 'Standard', risks: [], artifact_profile: 'strict', planning_obligations: [], proof_obligations: [], actor_needs: [] };
450
- const current = { ...floor, planning_depth: 'Compact', automatic_planning_depth: 'Compact', risks: ['privacy'], artifact_profile: 'bounded' };
451
- for (const checker of [CLAUDE_CHECK, CODEX_CHECK]) {
452
- const merged = checker.mergePolicyFloor(policy, floor, current);
453
- assert.equal(merged.planning_depth, 'Full');
454
- assert.equal(merged.artifact_profile, 'strict');
455
- assert.equal(merged.automatic_assurance_level, 'Elevated');
456
- assert.equal(merged.assurance_level, 'Elevated');
457
- assert.ok(merged.proof_obligations.includes('needsExecutionProof'));
458
- }
459
- });
460
-
461
- test('scaffold creates planning artifacts but no task or feature receipts', () => {
462
- withTempResources((resources) => {
463
- const root = resources.directory('scaffold-root');
464
- const result = spawnSync(process.execPath, [path.join(SCRIPTS, 'spec-scaffold.cjs'), 'no-receipt', '--specs-root', 'specs'], { cwd: root, encoding: 'utf8' });
465
- assert.equal(result.status, 0, result.stderr);
466
- const files = fs.readdirSync(path.join(root, 'specs', 'no-receipt'), { recursive: true }).map(String);
467
- assert.equal(files.some((file) => file.includes('receipt')), false);
468
- });
469
- });
470
-
471
- test('legacy receipt still requires provenance under a clean committed tree', () => {
472
- // The workflow task path may validate a committed, unchanged receipt on
473
- // structure alone; the legacy task and feature receipt paths must not.
474
- withTempResources((resources) => {
475
- const root = gitRoot(resources);
476
- const { featureDir } = prepare(root);
477
- const stale = { base: 'f'.repeat(40), head: 'e'.repeat(40) };
478
- fs.writeFileSync(
479
- path.join(featureDir, 'receipts', path.basename(TASK)),
480
- receiptBody(stale, `Task: ${path.basename(TASK)}\nTask path: ${TASK}`),
481
- );
482
- fs.writeFileSync(path.join(featureDir, 'feature-receipt.md'), receiptBody(stale, `Feature: ${FEATURE}`));
483
- for (const args of [['add', '-A'], ['commit', '-qm', 'committed legacy receipts']]) {
484
- const result = spawnSync('git', ['-C', root, ...args], { encoding: 'utf8' });
485
- assert.equal(result.status, 0, result.stderr);
486
- }
487
- const clean = spawnSync('git', ['-C', root, 'status', '--porcelain'], { encoding: 'utf8' });
488
- assert.equal(clean.stdout.trim(), '', 'fixture tree must be clean before the check');
489
- const ctx = runtime(root);
490
- const taskFailures = RECEIPT.checkTaskReceipt(featureDir, TASK, { completed_at: '2026-08-13T00:00:00.000Z' }, ctx, POLICY).failures;
491
- assert.ok(taskFailures.includes('provenance'), `legacy task receipts keep provenance binding when committed and clean; got ${JSON.stringify(taskFailures)}`);
492
- const featureFailures = RECEIPT.checkFeatureReceipt(featureDir, ctx, POLICY).failures;
493
- assert.ok(featureFailures.includes('provenance'), `feature receipts keep provenance binding when committed and clean; got ${JSON.stringify(featureFailures)}`);
494
- });
495
- });
@@ -6,10 +6,9 @@ const os = require('node:os');
6
6
  const path = require('node:path');
7
7
 
8
8
  const CLAUDE_RUNTIME_ENTRYPOINTS = Object.freeze([
9
- 'scripts/spec-final-state.cjs',
10
9
  'scripts/spec-receipt.cjs',
11
10
  'scripts/spec-resolver.cjs',
12
- 'scripts/validate-spec-output.cjs',
11
+ 'scripts/workflow-policy.cjs',
13
12
  ]);
14
13
 
15
14
  function inside(root, target) {
@@ -0,0 +1,101 @@
1
+ 'use strict';
2
+
3
+ // cf:ui-ux is evondevKit's ui-ux skill vendored byte-identical apart from its frontmatter
4
+ // and one body line. A resync copies the upstream tree again, so each case here pins what
5
+ // that copy must not lose, and states the failure it prevents.
6
+
7
+ const assert = require('node:assert/strict');
8
+ const fs = require('node:fs');
9
+ const os = require('node:os');
10
+ const path = require('node:path');
11
+ const { spawnSync } = require('node:child_process');
12
+ const { test } = require('node:test');
13
+
14
+ const { loadClaudeMigrationManifest } = require('../lib/context');
15
+
16
+ const PACKAGE_ROOT = path.join(__dirname, '..', '..');
17
+ const INSTALLER = path.join(PACKAGE_ROOT, 'bin/install.js');
18
+ const SKILL_DIR = path.join(PACKAGE_ROOT, 'src/claude/skills/ui-ux');
19
+ const SKILL_SOURCE = path.join(SKILL_DIR, 'SKILL.md');
20
+ const PRO_MAX_SOURCE = path.join(PACKAGE_ROOT, 'src/claude/skills/ui-ux-pro-max/SKILL.md');
21
+
22
+ function inTempProject(run) {
23
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-ui-ux-skill-'));
24
+ try {
25
+ const init = spawnSync('git', ['-C', root, 'init', '-q'], { encoding: 'utf8' });
26
+ assert.equal(init.status, 0, init.stderr);
27
+ return run(root);
28
+ } finally {
29
+ fs.rmSync(root, { recursive: true, force: true });
30
+ }
31
+ }
32
+
33
+ function install(root, platforms, extra = []) {
34
+ return spawnSync(process.execPath, [INSTALLER, '--platform', platforms.join(','), '--yes', ...extra],
35
+ { cwd: root, encoding: 'utf8', env: { ...process.env, PATH: '/usr/bin:/bin' } });
36
+ }
37
+
38
+ function frontmatterLines(content) {
39
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
40
+ assert.ok(match, 'SKILL.md must open with a frontmatter block');
41
+ return match[1].split(/\r?\n/);
42
+ }
43
+
44
+ function field(lines, key) {
45
+ const line = lines.find((entry) => entry.startsWith(`${key}:`));
46
+ return line === undefined ? undefined : line.slice(key.length + 1).trim();
47
+ }
48
+
49
+ test('manifest requires ui-ux', () => {
50
+ // Without the entry the installer never copies the skill, and nothing else fails.
51
+ const required = loadClaudeMigrationManifest().skills.required;
52
+ assert.equal(required.includes('ui-ux'), true);
53
+ assert.equal(required.includes('ui-ux-pro-max'), true, 'the lookup skill stays installed beside it');
54
+ });
55
+
56
+ test('cf:ui-ux frontmatter carries routing metadata and the MIT license', () => {
57
+ // A resync that copies upstream SKILL.md whole brings back `name: ui-ux` (no catalog
58
+ // identity), the Vietnamese description, and `grep -rlE "$1"`, which Claude Code fills
59
+ // with the invocation arguments and so corrupts the style audit.
60
+ const content = fs.readFileSync(SKILL_SOURCE, 'utf8');
61
+ const lines = frontmatterLines(content);
62
+ assert.equal(field(lines, 'name'), 'cf:ui-ux');
63
+ assert.equal(field(lines, 'license'), 'MIT');
64
+ assert.equal(field(lines, 'category'), 'frontend');
65
+ assert.ok(field(lines, 'when_to_use'), 'when_to_use must be set');
66
+ assert.match(field(lines, 'keywords') || '', /^\[.+\]$/, 'keywords must be a non-empty list');
67
+
68
+ const description = field(lines, 'description') || '';
69
+ assert.ok(description.length > 2 && description.length <= 1024, `description length ${description.length}`);
70
+
71
+ assert.doesNotMatch(content, /\$[0-9]/, 'no positional argument placeholder in the skill body');
72
+ assert.doesNotMatch(content, /\$ARGUMENTS/);
73
+
74
+ // MIT requires the copyright and permission notice to travel with the copy.
75
+ assert.equal(fs.existsSync(path.join(SKILL_DIR, 'LICENSE')), true);
76
+ const pkg = JSON.parse(fs.readFileSync(path.join(SKILL_DIR, 'scripts', 'package.json'), 'utf8'));
77
+ assert.ok(pkg.dependencies && pkg.dependencies.playwright, 'the installer provisions Playwright only when declared');
78
+ });
79
+
80
+ test('ui-ux-pro-max when_to_use no longer claims layout or design systems', () => {
81
+ // Both skills advertising layout and design systems leaves the host to pick at random.
82
+ const lines = frontmatterLines(fs.readFileSync(PRO_MAX_SOURCE, 'utf8'));
83
+ const whenToUse = field(lines, 'when_to_use') || '';
84
+ assert.doesNotMatch(whenToUse, /layout/i);
85
+ assert.doesNotMatch(whenToUse, /design system/i);
86
+ assert.match(whenToUse, /cf:ui-ux/);
87
+ });
88
+
89
+ test('fresh claude install copies ui-ux with its playwright package manifest', () => {
90
+ // The package manifest is what makes --with-skills-deps install Playwright for probe.mjs.
91
+ inTempProject((root) => {
92
+ const result = install(root, ['claude']);
93
+ assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`);
94
+
95
+ const installedSkill = path.join(root, '.claude', 'skills', 'ui-ux', 'SKILL.md');
96
+ const installedPackage = path.join(root, '.claude', 'skills', 'ui-ux', 'scripts', 'package.json');
97
+ assert.equal(fs.existsSync(installedSkill), true, 'the skill directory must be installed');
98
+ assert.equal(fs.existsSync(installedPackage), true, 'scripts/package.json must be installed');
99
+ assert.match(fs.readFileSync(installedSkill, 'utf8'), /^name: cf:ui-ux$/m);
100
+ });
101
+ });