@haposoft/cafekit 0.13.3 → 0.14.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.
Files changed (114) hide show
  1. package/README.md +1 -2
  2. package/bin/lib/i18n.js +2 -2
  3. package/bin/lib/manifest.js +15 -0
  4. package/bin/lib/opencode-install.js +0 -14
  5. package/bin/phases/claude-runtime.js +4 -1
  6. package/bin/phases/claude-settings.js +38 -7
  7. package/package.json +1 -1
  8. package/src/claude/agents/ui-ux-designer.md +4 -4
  9. package/src/claude/hooks/__tests__/privacy-block.test.js +79 -0
  10. package/src/claude/hooks/__tests__/session-writeenv.test.js +68 -0
  11. package/src/claude/hooks/__tests__/spec-gate.test.js +270 -0
  12. package/src/claude/hooks/privacy-block.cjs +22 -2
  13. package/src/claude/hooks/session.cjs +5 -1
  14. package/src/claude/hooks/spec-gate.cjs +173 -0
  15. package/src/claude/hooks/spec-state.cjs +10 -17
  16. package/src/claude/hooks/task-scaffold-guard.cjs +4 -2
  17. package/src/claude/migration-manifest.json +5 -4
  18. package/src/claude/rules/skill-domain-routing.md +2 -3
  19. package/src/claude/rules/skill-workflow-routing.md +2 -3
  20. package/src/claude/rules/state-sync.md +4 -0
  21. package/src/claude/runtime.json +4 -0
  22. package/src/claude/scripts/validate-docs.cjs +4 -7
  23. package/src/claude/scripts/validate-spec-output.cjs +29 -9
  24. package/src/claude/settings/settings.json +4 -0
  25. package/src/claude/skills/ai-multimodal/.env.example +11 -13
  26. package/src/claude/skills/ai-multimodal/SKILL.md +1 -1
  27. package/src/claude/skills/ai-multimodal/references/audio-processing.md +25 -27
  28. package/src/claude/skills/ai-multimodal/references/video-analysis.md +23 -27
  29. package/src/claude/skills/ai-multimodal/references/vision-understanding.md +26 -26
  30. package/src/claude/skills/ai-multimodal/scripts/check_setup.py +1 -1
  31. package/src/claude/skills/ai-multimodal/scripts/gemini_batch_process.py +7 -9
  32. package/src/claude/skills/brainstorm/SKILL.md +1 -2
  33. package/src/claude/skills/docx/SKILL.md +1 -1
  34. package/src/claude/skills/frontend-design/SKILL.md +1 -1
  35. package/src/claude/skills/frontend-design/references/ai-multimodal-overview.md +4 -4
  36. package/src/claude/skills/frontend-design/references/analysis-techniques.md +3 -3
  37. package/src/claude/skills/frontend-design/references/asset-generation.md +2 -2
  38. package/src/claude/skills/frontend-design/references/design-extraction-overview.md +4 -4
  39. package/src/claude/skills/frontend-design/references/extraction-best-practices.md +3 -3
  40. package/src/claude/skills/frontend-design/references/technical-accessibility.md +1 -1
  41. package/src/claude/skills/frontend-design/references/technical-optimization.md +1 -1
  42. package/src/claude/skills/frontend-design/references/technical-overview.md +2 -2
  43. package/src/claude/skills/frontend-design/references/technical-workflows.md +6 -6
  44. package/src/claude/skills/frontend-design/references/visual-analysis-overview.md +3 -3
  45. package/src/claude/skills/inspect/SKILL.md +3 -3
  46. package/src/claude/skills/inspect/references/external-gemini-inspection.md +9 -9
  47. package/src/claude/skills/pdf/SKILL.md +1 -1
  48. package/src/claude/skills/pptx/SKILL.md +1 -1
  49. package/src/claude/skills/specs/SKILL.md +1 -1
  50. package/src/claude/skills/xlsx/SKILL.md +1 -1
  51. package/src/opencode/AGENTS.md +1 -2
  52. package/src/opencode/plugins/session.ts +2 -2
  53. package/src/opencode/plugins/spec-state.ts +244 -0
  54. package/src/opencode/plugins/task-scaffold-guard.ts +161 -0
  55. package/src/claude/scripts/browser-tool.cjs +0 -138
  56. package/src/claude/skills/generate-graph/LICENSE +0 -21
  57. package/src/claude/skills/generate-graph/README.md +0 -523
  58. package/src/claude/skills/generate-graph/SKILL.md +0 -429
  59. package/src/claude/skills/generate-graph/agentloop-core.svg +0 -101
  60. package/src/claude/skills/generate-graph/agents/openai.yaml +0 -4
  61. package/src/claude/skills/generate-graph/assets/samples/sample-style1-flat.png +0 -0
  62. package/src/claude/skills/generate-graph/assets/samples/sample-style2-dark.png +0 -0
  63. package/src/claude/skills/generate-graph/assets/samples/sample-style3-blueprint.png +0 -0
  64. package/src/claude/skills/generate-graph/assets/samples/sample-style4-notion.png +0 -0
  65. package/src/claude/skills/generate-graph/assets/samples/sample-style5-glass.png +0 -0
  66. package/src/claude/skills/generate-graph/assets/samples/sample-style6-claude.png +0 -0
  67. package/src/claude/skills/generate-graph/assets/samples/sample-style7-openai.png +0 -0
  68. package/src/claude/skills/generate-graph/fixtures/agent-memory-types-style4.json +0 -181
  69. package/src/claude/skills/generate-graph/fixtures/api-flow-style7.json +0 -40
  70. package/src/claude/skills/generate-graph/fixtures/mem0-style1.json +0 -297
  71. package/src/claude/skills/generate-graph/fixtures/microservices-style3.json +0 -64
  72. package/src/claude/skills/generate-graph/fixtures/multi-agent-style5.json +0 -45
  73. package/src/claude/skills/generate-graph/fixtures/system-architecture-style6.json +0 -48
  74. package/src/claude/skills/generate-graph/fixtures/tool-call-style2.json +0 -182
  75. package/src/claude/skills/generate-graph/package.json +0 -42
  76. package/src/claude/skills/generate-graph/references/icons.md +0 -281
  77. package/src/claude/skills/generate-graph/references/style-1-flat-icon.md +0 -108
  78. package/src/claude/skills/generate-graph/references/style-2-dark-terminal.md +0 -107
  79. package/src/claude/skills/generate-graph/references/style-3-blueprint.md +0 -113
  80. package/src/claude/skills/generate-graph/references/style-4-notion-clean.md +0 -94
  81. package/src/claude/skills/generate-graph/references/style-5-glassmorphism.md +0 -125
  82. package/src/claude/skills/generate-graph/references/style-6-claude-official.md +0 -209
  83. package/src/claude/skills/generate-graph/references/style-7-openai.md +0 -215
  84. package/src/claude/skills/generate-graph/references/style-diagram-matrix.md +0 -135
  85. package/src/claude/skills/generate-graph/references/svg-layout-best-practices.md +0 -100
  86. package/src/claude/skills/generate-graph/scripts/generate-diagram.sh +0 -157
  87. package/src/claude/skills/generate-graph/scripts/generate-from-template.py +0 -1556
  88. package/src/claude/skills/generate-graph/scripts/test-all-styles.sh +0 -135
  89. package/src/claude/skills/generate-graph/scripts/validate-svg.sh +0 -292
  90. package/src/claude/skills/generate-graph/templates/agent-architecture.svg +0 -28
  91. package/src/claude/skills/generate-graph/templates/architecture.svg +0 -23
  92. package/src/claude/skills/generate-graph/templates/comparison-matrix.svg +0 -14
  93. package/src/claude/skills/generate-graph/templates/data-flow.svg +0 -28
  94. package/src/claude/skills/generate-graph/templates/er-diagram.svg +0 -21
  95. package/src/claude/skills/generate-graph/templates/flowchart.svg +0 -21
  96. package/src/claude/skills/generate-graph/templates/sequence.svg +0 -20
  97. package/src/claude/skills/generate-graph/templates/state-machine.svg +0 -20
  98. package/src/claude/skills/generate-graph/templates/timeline.svg +0 -19
  99. package/src/claude/skills/generate-graph/templates/use-case.svg +0 -21
  100. package/src/claude/skills/impact-analysis/SKILL.md +0 -276
  101. package/src/claude/skills/impact-analysis/references/change-detection.md +0 -270
  102. package/src/claude/skills/impact-analysis/references/dependency-scouting.md +0 -338
  103. package/src/claude/skills/impact-analysis/references/edge-case-identification.md +0 -439
  104. package/src/claude/skills/impact-analysis/references/industry-techniques.md +0 -695
  105. package/src/claude/skills/impact-analysis/references/practical-techniques-guide.md +0 -753
  106. package/src/claude/skills/impact-analysis/references/project-detection.md +0 -704
  107. package/src/claude/skills/impact-analysis/references/react-native-customization.md +0 -508
  108. package/src/claude/skills/impact-analysis/references/report-template.md +0 -604
  109. package/src/claude/skills/impact-analysis/references/test-scenario-generation.md +0 -459
  110. package/src/claude/skills/impact-analysis/scripts/README.md +0 -476
  111. package/src/claude/skills/impact-analysis/scripts/ast-analyze.js +0 -403
  112. package/src/claude/skills/impact-analysis/scripts/calculate-risk.js +0 -475
  113. package/src/claude/skills/impact-analysis/scripts/find-dependencies.sh +0 -202
  114. package/src/claude/skills/impact-analysis/scripts/run-analysis.sh +0 -312
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Copyright (c) 2026 Haposoft. MIT License.
4
+ *
5
+ * Stop Hook — spec-gate.cjs
6
+ * Implements: https://docs.anthropic.com/en/docs/claude-code/hooks
7
+ *
8
+ * Blocks turn end when *newly-done* tasks lack a verification receipt.
9
+ * Block contract (exit 0 + stdout): {"decision":"block","reason":"..."}.
10
+ *
11
+ * Safety: stop_hook_active loop guard; runtime.spec.completion_gate=false
12
+ * escape hatch (missing key → ON); first-run seeds cache without blocking;
13
+ * crash → fail-open + hooks/.logs/hook-log.jsonl. Exit: 0 always.
14
+ */
15
+
16
+ try {
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+
20
+ const stdin = fs.readFileSync(0, 'utf8').trim();
21
+ if (!stdin) process.exit(0);
22
+
23
+ const payload = JSON.parse(stdin);
24
+ const cwd = payload.cwd || process.cwd();
25
+
26
+ // Never re-block a continuation caused by our own block (infinite-loop guard).
27
+ if (payload.stop_hook_active === true) process.exit(0);
28
+
29
+ // Missing/malformed runtime.json keeps the gate ON (fail-closed, valve 3 style).
30
+ let runtime = {};
31
+ try {
32
+ const rp = path.join(cwd, '.claude', 'runtime.json');
33
+ if (fs.existsSync(rp)) runtime = JSON.parse(fs.readFileSync(rp, 'utf8'));
34
+ } catch { /* gate stays on */ }
35
+ if (runtime.spec && runtime.spec.completion_gate === false) process.exit(0);
36
+
37
+ // Active-spec discovery — same rules as spec-state.cjs.
38
+ const baseDir = process.env.PROJECT_ROOT || cwd;
39
+ const specsPath = path.join(baseDir, runtime.paths?.specs || 'specs');
40
+ if (!fs.existsSync(specsPath)) process.exit(0);
41
+
42
+ let activeSpec = null;
43
+ let featureName = null;
44
+ for (const entry of fs.readdirSync(specsPath, { withFileTypes: true })) {
45
+ if (!entry.isDirectory()) continue;
46
+ const specFile = path.join(specsPath, entry.name, 'spec.json');
47
+ if (!fs.existsSync(specFile)) continue;
48
+ try {
49
+ const specData = JSON.parse(fs.readFileSync(specFile, 'utf8'));
50
+ if (specData.status === 'in_progress' || specData.status === 'in-progress') {
51
+ activeSpec = specData;
52
+ featureName = entry.name;
53
+ break;
54
+ }
55
+ } catch { /* skip bad JSON */ }
56
+ }
57
+ if (!activeSpec) process.exit(0);
58
+
59
+ const taskRegistry = activeSpec.task_registry || {};
60
+ const cacheFile = path.join(__dirname, '.logs', 'spec-gate-last.json');
61
+ const cacheExists = fs.existsSync(cacheFile);
62
+ let cache = {};
63
+ if (cacheExists) {
64
+ try { cache = JSON.parse(fs.readFileSync(cacheFile, 'utf8')); } catch { cache = {}; }
65
+ }
66
+
67
+ const currentStatuses = {};
68
+ for (const [tp, task] of Object.entries(taskRegistry)) {
69
+ currentStatuses[tp] = task?.status || 'pending';
70
+ }
71
+
72
+ // First run: treat all current done as historical — seed cache, never block.
73
+ if (!cacheExists) {
74
+ try {
75
+ fs.mkdirSync(path.dirname(cacheFile), { recursive: true });
76
+ cache[featureName] = currentStatuses;
77
+ fs.writeFileSync(cacheFile, JSON.stringify(cache));
78
+ } catch { /* fail-open */ }
79
+ process.exit(0);
80
+ }
81
+
82
+ const featureCache = cache[featureName] || {};
83
+ // Newly done: status is done now AND cached status differs or is absent.
84
+ const newlyDone = Object.keys(taskRegistry).filter((tp) =>
85
+ (taskRegistry[tp]?.status || 'pending') === 'done' && featureCache[tp] !== 'done'
86
+ );
87
+ if (newlyDone.length === 0) process.exit(0);
88
+
89
+ // /m so ^ matches line starts (Evidence is never at byte 0 of the file).
90
+ const EVID_RE = /^#{2,3}\s+(Evidence|Task Test Plan & Verification Evidence|Verification & Evidence)\b/m;
91
+ const PROOF_RE = /(PASS|FAIL|exit code|passed|✓)/;
92
+
93
+ /** Body of first Evidence heading until next same-or-higher heading. */
94
+ function evidenceBody(text) {
95
+ const lines = text.split('\n');
96
+ let start = -1, level = 0;
97
+ for (let i = 0; i < lines.length; i++) {
98
+ const m = lines[i].match(/^(#{2,3})\s+(Evidence|Task Test Plan & Verification Evidence|Verification & Evidence)\b/);
99
+ if (m) { start = i + 1; level = m[1].length; break; }
100
+ }
101
+ if (start < 0) return null;
102
+ let end = lines.length;
103
+ for (let i = start; i < lines.length; i++) {
104
+ const hm = lines[i].match(/^(#{1,6})\s+/);
105
+ if (hm && hm[1].length <= level) { end = i; break; }
106
+ }
107
+ return lines.slice(start, end).join('\n');
108
+ }
109
+
110
+ // Receipt checks a–d → failed letter list.
111
+ // a: file + Status header has "done"; b: Evidence heading; c: no {{...}} + (fence|proof); d: completed_at
112
+ function checkReceipt(taskPath) {
113
+ const fails = [];
114
+ const abs = path.join(specsPath, featureName, taskPath);
115
+ if (!fs.existsSync(abs)) return ['a'];
116
+ let text = '';
117
+ try { text = fs.readFileSync(abs, 'utf8'); } catch { return ['a']; }
118
+
119
+ const statusLine = text.split('\n').find((l) => /^\s*(\*\*)?Status(\*\*)?\s*:/i.test(l));
120
+ if (!statusLine || !/\bdone\b/i.test(statusLine)) fails.push('a');
121
+
122
+ const body = evidenceBody(text);
123
+ if (body === null || !EVID_RE.test(text)) {
124
+ fails.push('b');
125
+ } else if (/\{\{[^}]+\}\}/.test(body) || !(/```/.test(body) || PROOF_RE.test(body))) {
126
+ fails.push('c');
127
+ }
128
+
129
+ const at = taskRegistry[taskPath]?.completed_at;
130
+ if (typeof at !== 'string' || at.trim() === '') fails.push('d');
131
+ return fails;
132
+ }
133
+
134
+ const failures = newlyDone
135
+ .map((tp) => ({ taskPath: tp, fails: checkReceipt(tp) }))
136
+ .filter((f) => f.fails.length > 0);
137
+
138
+ // Leave failing newly-done at old cache status so the gate re-fires next Stop.
139
+ try {
140
+ const nextFeature = { ...featureCache, ...currentStatuses };
141
+ for (const { taskPath } of failures) {
142
+ if (featureCache[taskPath] === undefined) delete nextFeature[taskPath];
143
+ else nextFeature[taskPath] = featureCache[taskPath];
144
+ }
145
+ cache[featureName] = nextFeature;
146
+ fs.mkdirSync(path.dirname(cacheFile), { recursive: true });
147
+ fs.writeFileSync(cacheFile, JSON.stringify(cache));
148
+ } catch { /* fail-open */ }
149
+
150
+ if (failures.length === 0) process.exit(0);
151
+
152
+ const lines = [
153
+ `⚠️ Completion gate: ${failures.length} newly-done task(s) lack a verification receipt.`,
154
+ ];
155
+ for (const { taskPath, fails } of failures) {
156
+ lines.push(`- \`${taskPath}\`: failed check(s) ${fails.join(', ')}`);
157
+ lines.push(
158
+ ` Fix: add a verification receipt to \`## Evidence\` in \`specs/${featureName}/${taskPath}\`: commands run + outcomes, then re-sync spec.json`
159
+ );
160
+ }
161
+ process.stdout.write(JSON.stringify({ decision: 'block', reason: lines.slice(0, 8).join('\n') }) + '\n');
162
+ process.exit(0);
163
+
164
+ } catch (e) {
165
+ try {
166
+ const fs = require('fs'), p = require('path');
167
+ const d = p.join(__dirname, '.logs');
168
+ if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true });
169
+ fs.appendFileSync(p.join(d, 'hook-log.jsonl'),
170
+ JSON.stringify({ ts: new Date().toISOString(), hook: 'spec-gate', status: 'crash', error: e.message }) + '\n');
171
+ } catch (_) {}
172
+ process.exit(0);
173
+ }
@@ -98,26 +98,19 @@ try {
98
98
  fs.writeFileSync(cacheFile, stateKey);
99
99
  } catch { /* fail-open: if the write fails we still print the full block */ }
100
100
 
101
- // Format the output
101
+ // Compact state-change block (enforcement lives on Stop via spec-gate.cjs).
102
102
  const lines = [];
103
103
  lines.push('');
104
- lines.push('### 🔴 URGENT SYSTEM TOLLGATE (STATE SYNC) 🔴');
105
- lines.push(`- **Active Feature:** \`${featureName}\``);
106
- lines.push(`- **Current Phase:** \`${phase}\``);
107
- if (taskEntries.length > 0) {
108
- lines.push(`- **Task Registry:** \`${taskEntries.length} total | ${(taskCounts.done || 0)} done | ${(taskCounts.in_progress || 0)} in_progress | ${(taskCounts.blocked || 0)} blocked | ${(taskCounts.pending || 0)} pending\``);
109
- if (nextUnblocked) {
110
- lines.push(`- **Next Unblocked Task:** \`${nextUnblocked[0]}\``);
111
- }
104
+ lines.push(`### Spec state changed: \`${featureName}\``);
105
+ lines.push(`- Phase: \`${phase}\` | Tasks: ${(taskCounts.done || 0)} done / ${taskEntries.length} total` +
106
+ (taskEntries.length > 0
107
+ ? ` (${(taskCounts.in_progress || 0)} in_progress, ${(taskCounts.pending || 0)} pending, ${(taskCounts.blocked || 0)} blocked)`
108
+ : ''));
109
+ if (nextUnblocked) {
110
+ lines.push(`- Next unblocked: \`${nextUnblocked[0]}\``);
112
111
  }
113
- lines.push('');
114
- lines.push(`> BẮT BUỘC (MANDATORY): Nếu bạn vừa hoàn thành một bước, bạn KHÔNG ĐƯỢC báo cáo "Đã xong" ngay.`);
115
- lines.push(`> Bạn PHẢI sử dụng công cụ Edit để cập nhật trạng thái vật lý sau khi đã có bằng chứng verify thật (build/test/runtime/artifact), không phải chỉ vì code đã viết xong.`);
116
- lines.push(`> 1. Sửa file \`spec.json\` (status, phase/current_phase, timestamps, \`task_files\`, \`task_registry\`, validation state nếu có thay đổi).`);
117
- lines.push(`> 2. Chỉ khi verify xong mới sửa file \`tasks/task-*.md\` (status + tick '[x]' các sub-task và completion criteria liên quan).`);
118
- lines.push(`> 3. Trước khi set \`ready_for_implementation = true\`, PHẢI chạy \`node .claude/scripts/validate-spec-output.cjs specs/${featureName}\` và sửa mọi lỗi.`);
119
- lines.push(`> 4. NẾU VỪA HOÀN THÀNH 1 TASK CÓ SỬA SOURCE CODE, BẮT BUỘC cập nhật ngay tài liệu trong \`docs/\` (\`system-architecture.md\` hoặc Changelog) cho đồng bộ.`);
120
- lines.push(`> CẤM VI PHẠM LUẬT TOLLGATE NÀY NHẰM ĐẢM BẢO TÍNH ĐỒNG BỘ CỦA HỆ THỐNG.`);
112
+ lines.push(`- Sync \`spec.json\` + task file after verified work; run \`node .claude/scripts/validate-spec-output.cjs specs/${featureName}\` before \`ready_for_implementation=true\`.`);
113
+ lines.push(`- A completion gate verifies receipts when you end a turn with newly-done tasks.`);
121
114
  lines.push('');
122
115
 
123
116
  console.log(lines.join('\n'));
@@ -66,6 +66,9 @@ try {
66
66
  if (!fs.existsSync(scaffold)) process.exit(0);
67
67
 
68
68
  // Valve 2: block with an actionable message carrying the exact command.
69
+ // NOTE: deliberately does NOT mention the runtime.json escape hatch — an
70
+ // OpenCode smoke test proved the model reads the advertised override and
71
+ // simply disables the guard itself. The hatch stays functional for humans.
69
72
  const m = norm.match(/(^|\/)specs\/([^/]+)\/tasks\//);
70
73
  const feature = m ? m[2] : '<feature>';
71
74
  console.log(
@@ -73,8 +76,7 @@ try {
73
76
  `Blocked Write: ${filePath}\n\n` +
74
77
  `Generate the stub(s), then Edit-fill the {{...}} placeholders:\n` +
75
78
  ` node .claude/scripts/spec-scaffold.cjs ${feature} --tasks "R0-01-slug,R1-01-slug,..." --tasks-only\n` +
76
- `Then use Edit (not Write) on each tasks/task-*.md stub.\n` +
77
- `Override: set "spec": { "scaffold_guard": false } in .claude/runtime.json`
79
+ `Then use Edit (not Write) on each tasks/task-*.md stub.`
78
80
  );
79
81
  process.exit(2);
80
82
 
@@ -19,10 +19,8 @@
19
19
  "docx",
20
20
  "frontend-design",
21
21
  "frontend-development",
22
- "generate-graph",
23
22
  "git",
24
23
  "hotfix",
25
- "impact-analysis",
26
24
  "inspect",
27
25
  "mobile-development",
28
26
  "pdf",
@@ -60,7 +58,6 @@
60
58
  "validate-docs.cjs",
61
59
  "validate-docs-reconstruct.cjs",
62
60
  "generate-skill-catalog.cjs",
63
- "browser-tool.cjs",
64
61
  "validate-spec-output.cjs"
65
62
  ]
66
63
  },
@@ -73,7 +70,10 @@
73
70
  "obsolete": {
74
71
  "runtimeFiles": [
75
72
  "hooks/skill-router.cjs",
76
- "hooks/lib/skill-router-routes.cjs"
73
+ "hooks/lib/skill-router-routes.cjs",
74
+ "scripts/browser-tool.cjs",
75
+ "skills/generate-graph",
76
+ "skills/impact-analysis"
77
77
  ],
78
78
  "settingsHookCommandSubstrings": [
79
79
  "hooks/skill-router.cjs"
@@ -93,6 +93,7 @@
93
93
  "hooks/task-scaffold-guard.cjs",
94
94
  "hooks/rules.cjs",
95
95
  "hooks/spec-state.cjs",
96
+ "hooks/spec-gate.cjs",
96
97
  "hooks/state.cjs",
97
98
  "hooks/lib/color.cjs",
98
99
  "hooks/lib/parser.cjs",
@@ -21,8 +21,7 @@ Use `/hapo:react-best-practices` for performance/re-render patterns. Use `/hapo:
21
21
  User wants to...
22
22
  ├── API, service, auth, database-backed feature -> /hapo:backend-development
23
23
  ├── deployment, Docker, Kubernetes, Cloudflare, CI/CD -> /hapo:devops
24
- ├── mobile apps, iOS, Android, React Native, Flutter -> /hapo:mobile-development
25
- └── impact of changing existing behavior -> /hapo:impact-analysis
24
+ └── mobile apps, iOS, Android, React Native, Flutter -> /hapo:mobile-development
26
25
  ```
27
26
 
28
27
  ## Codebase Understanding
@@ -32,7 +31,7 @@ User wants to...
32
31
  ├── answer questions from source code/docs/specs/config -> /hapo:question
33
32
  ├── find files, locate code, scan project structure -> /hapo:inspect
34
33
  ├── diagnose an error or failing check -> /hapo:debug
35
- ├── understand possible side effects -> /hapo:impact-analysis
34
+ ├── understand possible side effects -> /hapo:inspect
36
35
  └── compare external approaches or best practices -> /hapo:research
37
36
  ```
38
37
 
@@ -55,13 +55,13 @@ Do not turn reconstructed docs directly into implementation. Human review must a
55
55
  ## Research And Analysis Workflow
56
56
 
57
57
  ```text
58
- /hapo:inspect -> /hapo:impact-analysis -> /hapo:research
58
+ /hapo:inspect -> /hapo:research
59
59
  ```
60
60
 
61
61
  | User intent | Suggested start |
62
62
  |---|---|
63
63
  | file discovery, codebase structure, source scouting | `/hapo:inspect` |
64
- | affected files, blast radius, dependency impact, side effects | `/hapo:impact-analysis` |
64
+ | affected files, blast radius, dependency impact, side effects | `/hapo:inspect` |
65
65
  | external best practices, tool comparison, technical investigation | `/hapo:research` |
66
66
 
67
67
  Use `/hapo:research` for external knowledge. Use `/hapo:inspect` for local source truth.
@@ -70,7 +70,6 @@ Use `/hapo:research` for external knowledge. Use `/hapo:inspect` for local sourc
70
70
 
71
71
  | User intent | Suggested skill |
72
72
  |---|---|
73
- | diagrams, architecture maps, Mermaid/SVG/PNG graph output | `/hapo:generate-graph` |
74
73
  | PowerPoint or slide deck creation/editing | `/hapo:pptx` |
75
74
  | Word documents | `/hapo:docx` |
76
75
  | PDF processing | `/hapo:pdf` |
@@ -25,3 +25,7 @@ Before returning control to the user or orchestrator, the agent **MUST**:
25
25
  **Canonical state values:** New specs MUST use `status: "in_progress"` for active work. Legacy `in-progress` may be read for compatibility, but must not be emitted in new files.
26
26
 
27
27
  **Golden Rule:** If the current phase changes, or a task completes, the agent must update the physical files. Never mark a task completed before there is execution proof, and never let `task_registry` disagree with the matching markdown task file. The context is intentionally NOT persisted in the chat to save tokens. An injected Hook (`spec-state.cjs`) constantly enforces and validates this state.
28
+
29
+ ### Completion gate (Stop)
30
+
31
+ Marking a task `done` is now machine-gated on the **Stop** hook (`spec-gate.cjs`), not only reminded on each prompt. When a turn ends with *newly-done* tasks, the gate requires a verification receipt in each task markdown: `Status: done`, an Evidence section (or legacy `Task Test Plan & Verification Evidence` / `Verification & Evidence`) with real proof (no `{{...}}` placeholders; at least one fenced command block or PASS/FAIL line), and a non-empty `task_registry[path].completed_at`. First adoption (no cache yet) seeds history without blocking. Escape hatch for humans: `"spec": { "completion_gate": false }` in `.claude/runtime.json` (missing key keeps the gate ON).
@@ -15,6 +15,10 @@
15
15
  "docs": "docs",
16
16
  "specs": "specs"
17
17
  },
18
+ "spec": {
19
+ "scaffold_guard": true,
20
+ "completion_gate": true
21
+ },
18
22
  "locale": {
19
23
  "thinkingLanguage": null,
20
24
  "responseLanguage": null
@@ -53,17 +53,14 @@ function main() {
53
53
 
54
54
  const linkIssues = checkBrokenLinks(docsDir);
55
55
  if (linkIssues.length > 0) {
56
- console.warn('\n⚠️ WARNING: CRITICAL HYPERLINK CORRUPTION DETECTED IN DOCUMENTATION MATRIX!');
56
+ console.error(`\n❌ FAIL: ${linkIssues.length} broken relative link(s) found:`);
57
57
  linkIssues.forEach(issue => {
58
- console.warn(`- Compromised File: ${path.relative(process.cwd(), issue.file)} -> Links externally to Missing Null Pointer: ./${issue.brokenLink}`);
58
+ console.error(`- ${path.relative(process.cwd(), issue.file)} -> ./${issue.brokenLink} (target missing)`);
59
59
  });
60
- } else {
61
- console.log('✅ Validation verified! Zero broken integrity links detected across the system.');
60
+ process.exit(1);
62
61
  }
63
62
 
64
- // Structural Note: To honor Execution Velocity patterns inherent to Architecture,
65
- // aggressive deep-code grepping functions (function tracing) have been stripped.
66
- // This ensures document validation pipeline triggers definitively under < 1s (Zero-latency verification).
63
+ console.log('✅ PASS: no broken relative links detected.');
67
64
  }
68
65
 
69
66
  main();
@@ -203,11 +203,13 @@ function extractContractDefs(designText) {
203
203
  }
204
204
 
205
205
  /**
206
- * From a task body, return the contracts it claims plus the first fenced block
207
- * it carries (the task's local copy of the contract). Shape:
208
- * { names: string[], block: string|null }
209
- * `names` come from a `Contracts: A, B` line; `block` is the first fenced code
210
- * block in the task (normalized) used to compare against the canonical defs.
206
+ * From a task body, return the contracts it claims plus its local contract
207
+ * copies. Shape: { names: string[], blocks: Map<name, string>, firstBlock: string|null }
208
+ * `names` come from a `Contracts: A, B` line. `blocks` maps every
209
+ * `<!-- contract:NAME -->` marker in the task to its fenced block, so a task
210
+ * carrying multiple contracts has EACH copy verified (not just the first).
211
+ * `firstBlock` keeps the legacy fallback for single-contract tasks that copy
212
+ * the block without repeating the marker.
211
213
  */
212
214
  function extractTaskContracts(taskText) {
213
215
  const names = [];
@@ -218,9 +220,10 @@ function extractTaskContracts(taskText) {
218
220
  if (name) names.push(name);
219
221
  }
220
222
  }
223
+ const blocks = extractContractDefs(taskText); // same marker+fence grammar as design.md
221
224
  const blockMatch = taskText.match(/```[^\n]*\n([\s\S]*?)\n```/);
222
- const block = blockMatch ? normalizeBlock(blockMatch[1]) : null;
223
- return { names, block };
225
+ const firstBlock = blockMatch ? normalizeBlock(blockMatch[1]) : null;
226
+ return { names, blocks, firstBlock };
224
227
  }
225
228
 
226
229
  function validateSpec(specDir) {
@@ -393,16 +396,33 @@ function validateSpec(specDir) {
393
396
  // (after whitespace normalization). This catches BE/FE drift like
394
397
  // user_name vs userName before integration.
395
398
  if (contractDefs.size > 0) {
396
- const { names, block } = extractTaskContracts(content);
399
+ const { names, blocks, firstBlock } = extractTaskContracts(content);
397
400
  for (const name of names) {
398
401
  if (!contractDefs.has(name)) {
399
402
  errors.push(`${taskFile}: declares unknown contract "${name}" (not defined in design.md)`);
400
403
  continue;
401
404
  }
402
- if (block !== null && block !== contractDefs.get(name)) {
405
+ // Prefer the task's marker-tagged copy for this contract; fall back to
406
+ // the first fenced block only for single-contract tasks (legacy format).
407
+ const localCopy = blocks.has(name)
408
+ ? blocks.get(name)
409
+ : (names.length === 1 ? firstBlock : null);
410
+ if (localCopy === null) {
411
+ if (names.length > 1) {
412
+ errors.push(`${taskFile}: contract "${name}" has no <!-- contract:${name} --> tagged block (multi-contract tasks must tag each copy)`);
413
+ }
414
+ continue;
415
+ }
416
+ if (localCopy !== contractDefs.get(name)) {
403
417
  errors.push(`${taskFile}: contract "${name}" body diverges from the canonical definition in design.md`);
404
418
  }
405
419
  }
420
+ // Marker-tagged blocks not declared on the Contracts: line are drift risks.
421
+ for (const taggedName of blocks.keys()) {
422
+ if (!names.includes(taggedName)) {
423
+ warnings.push(`${taskFile}: carries <!-- contract:${taggedName} --> block but does not declare it on the Contracts: line`);
424
+ }
425
+ }
406
426
  }
407
427
  }
408
428
 
@@ -102,6 +102,10 @@
102
102
  "Stop": [
103
103
  {
104
104
  "hooks": [
105
+ {
106
+ "type": "command",
107
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/spec-gate.cjs\""
108
+ },
105
109
  {
106
110
  "type": "command",
107
111
  "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/state.cjs\""
@@ -41,16 +41,16 @@ GEMINI_API_KEY=your_api_key_here
41
41
 
42
42
  # --- Image Generation ---
43
43
  # Used by: --task generate (image)
44
- # Default: gemini-2.5-flash-image (Nano Banana Flash - fast, cost-effective)
44
+ # Default: gemma-4-31b-it-image (Nano Banana Flash - fast, cost-effective)
45
45
  # Alternative: imagen-4.0-generate-001 (production quality)
46
46
  # NOTE: All image generation requires billing - no free tier available (limit: 0)
47
47
  # Options:
48
- # gemini-2.5-flash-image - Nano Banana Flash: fast, ~$1/1M tokens (DEFAULT)
49
- # gemini-3-pro-image-preview - Nano Banana Pro: 4K text, reasoning (requires billing)
48
+ # gemma-4-31b-it-image - Nano Banana Flash: fast, ~$1/1M tokens (DEFAULT)
49
+ # gemma-4-31b-it-image-preview - Nano Banana Pro: 4K text, reasoning (requires billing)
50
50
  # imagen-4.0-generate-001 - Imagen 4 Standard: production quality (~$0.02/image)
51
51
  # imagen-4.0-ultra-generate-001 - Imagen 4 Ultra: maximum quality (~$0.04/image)
52
52
  # imagen-4.0-fast-generate-001 - Imagen 4 Fast: speed-optimized (~$0.01/image)
53
- # IMAGE_GEN_MODEL=gemini-2.5-flash-image
53
+ # IMAGE_GEN_MODEL=gemma-4-31b-it-image
54
54
 
55
55
  # --- Video Generation ---
56
56
  # Used by: --task generate-video (new capability)
@@ -65,17 +65,15 @@ GEMINI_API_KEY=your_api_key_here
65
65
 
66
66
  # --- Multimodal Analysis ---
67
67
  # Used by: --task analyze, transcribe, extract
68
- # Default: gemini-2.5-flash
68
+ # Default: gemma-4-31b-it
69
69
  # Options:
70
- # gemini-3-pro-preview - Latest, agentic workflows, 1M context
71
- # gemini-2.5-flash - Best price/performance (recommended)
72
- # gemini-2.5-pro - Highest quality
73
- # MULTIMODAL_MODEL=gemini-2.5-flash
70
+ # gemma-4-31b-it - Best price/performance (recommended)
71
+ # MULTIMODAL_MODEL=gemma-4-31b-it
74
72
 
75
73
  # --- Legacy Compatibility ---
76
74
  # Generic model override (use specific variables above instead)
77
- # GEMINI_MODEL=gemini-2.5-flash
78
- # GEMINI_IMAGE_GEN_MODEL=gemini-2.5-flash-image
75
+ # GEMINI_MODEL=gemma-4-31b-it
76
+ # GEMINI_IMAGE_GEN_MODEL=gemma-4-31b-it-image
79
77
 
80
78
  # ============================================================================
81
79
  # MiniMax API Configuration (Optional - for image/video/speech/music generation)
@@ -223,8 +221,8 @@ GEMINI_API_KEY=your_api_key_here
223
221
  # 5. Free tier limits: 15 RPM, 1M-4M TPM, 1,500 RPD
224
222
  # 6. Vertex AI requires GCP authentication via gcloud CLI
225
223
  # 7. Model defaults (Dec 2025):
226
- # - Image gen: gemini-2.5-flash-image (Nano Banana Flash - default)
224
+ # - Image gen: gemma-4-31b-it-image (Nano Banana Flash - default)
227
225
  # - Image gen: imagen-4.0-generate-001 (alternative for production)
228
226
  # - Video gen: veo-3.1-generate-preview
229
- # - Analysis: gemini-2.5-flash
227
+ # - Analysis: gemma-4-31b-it
230
228
  # 8. Preview models (veo-3.1, gemini-3) may have API changes
@@ -43,7 +43,7 @@ export GEMINI_API_KEY_2="key2" # auto-rotates on rate limit
43
43
  ## Models
44
44
 
45
45
  ### Google Gemini
46
- - **Analysis**: `gemma-4-31b-it` (recommended), `gemini-2.5-pro` (advanced)
46
+ - **Analysis**: `gemma-4-31b-it` (recommended), `gemma-4-31b-it` (advanced)
47
47
 
48
48
  ## Scripts
49
49