@haposoft/cafekit 0.13.4 → 0.14.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.
- package/README.md +1 -2
- package/bin/lib/i18n.js +2 -2
- package/bin/lib/manifest.js +15 -0
- package/bin/lib/opencode-install.js +0 -14
- package/bin/phases/claude-runtime.js +4 -1
- package/bin/phases/claude-settings.js +38 -7
- package/package.json +1 -1
- package/src/claude/agents/ui-ux-designer.md +4 -4
- package/src/claude/hooks/__tests__/privacy-block.test.js +79 -0
- package/src/claude/hooks/__tests__/session-writeenv.test.js +68 -0
- package/src/claude/hooks/__tests__/spec-gate.test.js +270 -0
- package/src/claude/hooks/privacy-block.cjs +22 -2
- package/src/claude/hooks/session.cjs +5 -1
- package/src/claude/hooks/spec-gate.cjs +173 -0
- package/src/claude/hooks/spec-state.cjs +10 -17
- package/src/claude/hooks/task-scaffold-guard.cjs +4 -2
- package/src/claude/migration-manifest.json +5 -4
- package/src/claude/rules/skill-domain-routing.md +2 -3
- package/src/claude/rules/skill-workflow-routing.md +2 -3
- package/src/claude/rules/state-sync.md +4 -0
- package/src/claude/runtime.json +4 -0
- package/src/claude/scripts/validate-docs.cjs +4 -7
- package/src/claude/scripts/validate-spec-output.cjs +29 -9
- package/src/claude/settings/settings.json +4 -0
- package/src/claude/skills/ai-multimodal/.env.example +11 -13
- package/src/claude/skills/ai-multimodal/SKILL.md +1 -1
- package/src/claude/skills/ai-multimodal/references/audio-processing.md +25 -27
- package/src/claude/skills/ai-multimodal/references/video-analysis.md +23 -27
- package/src/claude/skills/ai-multimodal/references/vision-understanding.md +26 -26
- package/src/claude/skills/ai-multimodal/scripts/check_setup.py +1 -1
- package/src/claude/skills/ai-multimodal/scripts/gemini_batch_process.py +7 -9
- package/src/claude/skills/brainstorm/SKILL.md +1 -2
- package/src/claude/skills/docx/SKILL.md +1 -1
- package/src/claude/skills/frontend-design/SKILL.md +1 -1
- package/src/claude/skills/frontend-design/references/ai-multimodal-overview.md +4 -4
- package/src/claude/skills/frontend-design/references/analysis-techniques.md +3 -3
- package/src/claude/skills/frontend-design/references/asset-generation.md +2 -2
- package/src/claude/skills/frontend-design/references/design-extraction-overview.md +4 -4
- package/src/claude/skills/frontend-design/references/extraction-best-practices.md +3 -3
- package/src/claude/skills/frontend-design/references/technical-accessibility.md +1 -1
- package/src/claude/skills/frontend-design/references/technical-optimization.md +1 -1
- package/src/claude/skills/frontend-design/references/technical-overview.md +2 -2
- package/src/claude/skills/frontend-design/references/technical-workflows.md +6 -6
- package/src/claude/skills/frontend-design/references/visual-analysis-overview.md +3 -3
- package/src/claude/skills/inspect/SKILL.md +3 -3
- package/src/claude/skills/inspect/references/external-gemini-inspection.md +9 -9
- package/src/claude/skills/pdf/SKILL.md +1 -1
- package/src/claude/skills/pptx/SKILL.md +1 -1
- package/src/claude/skills/specs/SKILL.md +1 -1
- package/src/claude/skills/xlsx/SKILL.md +1 -1
- package/src/opencode/AGENTS.md +1 -2
- package/src/opencode/plugins/session.ts +2 -2
- package/src/opencode/plugins/spec-state.ts +244 -0
- package/src/opencode/plugins/task-scaffold-guard.ts +161 -0
- package/src/claude/scripts/browser-tool.cjs +0 -138
- package/src/claude/skills/generate-graph/LICENSE +0 -21
- package/src/claude/skills/generate-graph/README.md +0 -523
- package/src/claude/skills/generate-graph/SKILL.md +0 -429
- package/src/claude/skills/generate-graph/agentloop-core.svg +0 -101
- package/src/claude/skills/generate-graph/agents/openai.yaml +0 -4
- package/src/claude/skills/generate-graph/assets/samples/sample-style1-flat.png +0 -0
- package/src/claude/skills/generate-graph/assets/samples/sample-style2-dark.png +0 -0
- package/src/claude/skills/generate-graph/assets/samples/sample-style3-blueprint.png +0 -0
- package/src/claude/skills/generate-graph/assets/samples/sample-style4-notion.png +0 -0
- package/src/claude/skills/generate-graph/assets/samples/sample-style5-glass.png +0 -0
- package/src/claude/skills/generate-graph/assets/samples/sample-style6-claude.png +0 -0
- package/src/claude/skills/generate-graph/assets/samples/sample-style7-openai.png +0 -0
- package/src/claude/skills/generate-graph/fixtures/agent-memory-types-style4.json +0 -181
- package/src/claude/skills/generate-graph/fixtures/api-flow-style7.json +0 -40
- package/src/claude/skills/generate-graph/fixtures/mem0-style1.json +0 -297
- package/src/claude/skills/generate-graph/fixtures/microservices-style3.json +0 -64
- package/src/claude/skills/generate-graph/fixtures/multi-agent-style5.json +0 -45
- package/src/claude/skills/generate-graph/fixtures/system-architecture-style6.json +0 -48
- package/src/claude/skills/generate-graph/fixtures/tool-call-style2.json +0 -182
- package/src/claude/skills/generate-graph/package.json +0 -42
- package/src/claude/skills/generate-graph/references/icons.md +0 -281
- package/src/claude/skills/generate-graph/references/style-1-flat-icon.md +0 -108
- package/src/claude/skills/generate-graph/references/style-2-dark-terminal.md +0 -107
- package/src/claude/skills/generate-graph/references/style-3-blueprint.md +0 -113
- package/src/claude/skills/generate-graph/references/style-4-notion-clean.md +0 -94
- package/src/claude/skills/generate-graph/references/style-5-glassmorphism.md +0 -125
- package/src/claude/skills/generate-graph/references/style-6-claude-official.md +0 -209
- package/src/claude/skills/generate-graph/references/style-7-openai.md +0 -215
- package/src/claude/skills/generate-graph/references/style-diagram-matrix.md +0 -135
- package/src/claude/skills/generate-graph/references/svg-layout-best-practices.md +0 -100
- package/src/claude/skills/generate-graph/scripts/generate-diagram.sh +0 -157
- package/src/claude/skills/generate-graph/scripts/generate-from-template.py +0 -1556
- package/src/claude/skills/generate-graph/scripts/test-all-styles.sh +0 -135
- package/src/claude/skills/generate-graph/scripts/validate-svg.sh +0 -292
- package/src/claude/skills/generate-graph/templates/agent-architecture.svg +0 -28
- package/src/claude/skills/generate-graph/templates/architecture.svg +0 -23
- package/src/claude/skills/generate-graph/templates/comparison-matrix.svg +0 -14
- package/src/claude/skills/generate-graph/templates/data-flow.svg +0 -28
- package/src/claude/skills/generate-graph/templates/er-diagram.svg +0 -21
- package/src/claude/skills/generate-graph/templates/flowchart.svg +0 -21
- package/src/claude/skills/generate-graph/templates/sequence.svg +0 -20
- package/src/claude/skills/generate-graph/templates/state-machine.svg +0 -20
- package/src/claude/skills/generate-graph/templates/timeline.svg +0 -19
- package/src/claude/skills/generate-graph/templates/use-case.svg +0 -21
- package/src/claude/skills/impact-analysis/SKILL.md +0 -276
- package/src/claude/skills/impact-analysis/references/change-detection.md +0 -270
- package/src/claude/skills/impact-analysis/references/dependency-scouting.md +0 -338
- package/src/claude/skills/impact-analysis/references/edge-case-identification.md +0 -439
- package/src/claude/skills/impact-analysis/references/industry-techniques.md +0 -695
- package/src/claude/skills/impact-analysis/references/practical-techniques-guide.md +0 -753
- package/src/claude/skills/impact-analysis/references/project-detection.md +0 -704
- package/src/claude/skills/impact-analysis/references/react-native-customization.md +0 -508
- package/src/claude/skills/impact-analysis/references/report-template.md +0 -604
- package/src/claude/skills/impact-analysis/references/test-scenario-generation.md +0 -459
- package/src/claude/skills/impact-analysis/scripts/README.md +0 -476
- package/src/claude/skills/impact-analysis/scripts/ast-analyze.js +0 -403
- package/src/claude/skills/impact-analysis/scripts/calculate-risk.js +0 -475
- package/src/claude/skills/impact-analysis/scripts/find-dependencies.sh +0 -202
- 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
|
-
//
|
|
101
|
+
// Compact state-change block (enforcement lives on Stop via spec-gate.cjs).
|
|
102
102
|
const lines = [];
|
|
103
103
|
lines.push('');
|
|
104
|
-
lines.push(
|
|
105
|
-
lines.push(`-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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(
|
|
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
|
|
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
|
|
|
@@ -20,10 +20,8 @@
|
|
|
20
20
|
"docx",
|
|
21
21
|
"frontend-design",
|
|
22
22
|
"frontend-development",
|
|
23
|
-
"generate-graph",
|
|
24
23
|
"git",
|
|
25
24
|
"hotfix",
|
|
26
|
-
"impact-analysis",
|
|
27
25
|
"inspect",
|
|
28
26
|
"mobile-development",
|
|
29
27
|
"pdf",
|
|
@@ -61,7 +59,6 @@
|
|
|
61
59
|
"validate-docs.cjs",
|
|
62
60
|
"validate-docs-reconstruct.cjs",
|
|
63
61
|
"generate-skill-catalog.cjs",
|
|
64
|
-
"browser-tool.cjs",
|
|
65
62
|
"validate-spec-output.cjs"
|
|
66
63
|
]
|
|
67
64
|
},
|
|
@@ -74,7 +71,10 @@
|
|
|
74
71
|
"obsolete": {
|
|
75
72
|
"runtimeFiles": [
|
|
76
73
|
"hooks/skill-router.cjs",
|
|
77
|
-
"hooks/lib/skill-router-routes.cjs"
|
|
74
|
+
"hooks/lib/skill-router-routes.cjs",
|
|
75
|
+
"scripts/browser-tool.cjs",
|
|
76
|
+
"skills/generate-graph",
|
|
77
|
+
"skills/impact-analysis"
|
|
78
78
|
],
|
|
79
79
|
"settingsHookCommandSubstrings": [
|
|
80
80
|
"hooks/skill-router.cjs"
|
|
@@ -94,6 +94,7 @@
|
|
|
94
94
|
"hooks/task-scaffold-guard.cjs",
|
|
95
95
|
"hooks/rules.cjs",
|
|
96
96
|
"hooks/spec-state.cjs",
|
|
97
|
+
"hooks/spec-gate.cjs",
|
|
97
98
|
"hooks/state.cjs",
|
|
98
99
|
"hooks/lib/color.cjs",
|
|
99
100
|
"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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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.
|
|
@@ -78,7 +78,6 @@ Claude Code remains the orchestrator: it writes the task brief, dispatches, moni
|
|
|
78
78
|
|
|
79
79
|
| User intent | Suggested skill |
|
|
80
80
|
|---|---|
|
|
81
|
-
| diagrams, architecture maps, Mermaid/SVG/PNG graph output | `/hapo:generate-graph` |
|
|
82
81
|
| PowerPoint or slide deck creation/editing | `/hapo:pptx` |
|
|
83
82
|
| Word documents | `/hapo:docx` |
|
|
84
83
|
| 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).
|
package/src/claude/runtime.json
CHANGED
|
@@ -53,17 +53,14 @@ function main() {
|
|
|
53
53
|
|
|
54
54
|
const linkIssues = checkBrokenLinks(docsDir);
|
|
55
55
|
if (linkIssues.length > 0) {
|
|
56
|
-
console.
|
|
56
|
+
console.error(`\n❌ FAIL: ${linkIssues.length} broken relative link(s) found:`);
|
|
57
57
|
linkIssues.forEach(issue => {
|
|
58
|
-
console.
|
|
58
|
+
console.error(`- ${path.relative(process.cwd(), issue.file)} -> ./${issue.brokenLink} (target missing)`);
|
|
59
59
|
});
|
|
60
|
-
|
|
61
|
-
console.log('✅ Validation verified! Zero broken integrity links detected across the system.');
|
|
60
|
+
process.exit(1);
|
|
62
61
|
}
|
|
63
62
|
|
|
64
|
-
|
|
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
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
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
|
|
223
|
-
return { names,
|
|
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,
|
|
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
|
-
|
|
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
|
|
|
@@ -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:
|
|
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
|
-
#
|
|
49
|
-
#
|
|
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=
|
|
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:
|
|
68
|
+
# Default: gemma-4-31b-it
|
|
69
69
|
# Options:
|
|
70
|
-
#
|
|
71
|
-
#
|
|
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=
|
|
78
|
-
# GEMINI_IMAGE_GEN_MODEL=
|
|
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:
|
|
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:
|
|
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), `
|
|
46
|
+
- **Analysis**: `gemma-4-31b-it` (recommended), `gemma-4-31b-it` (advanced)
|
|
47
47
|
|
|
48
48
|
## Scripts
|
|
49
49
|
|