@haposoft/cafekit 0.11.11 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/install.js +4 -0
- package/bin/lib/context.js +4 -1
- package/bin/lib/i18n.js +33 -0
- package/bin/lib/rtk-run.js +28 -0
- package/bin/phases/setup-rtk.js +159 -0
- package/bin/phases/summary.js +4 -0
- package/package.json +1 -1
- package/src/claude/hooks/spec-state.cjs +21 -0
- package/src/claude/scripts/validate-spec-output.cjs +136 -3
- package/src/claude/skills/specs/SKILL.md +32 -57
- package/src/claude/skills/specs/rules/tasks-generation.md +3 -1
- package/src/claude/skills/specs/templates/design.md +6 -0
- package/src/claude/skills/specs/references/task-hydration.md +0 -93
- package/src/claude/skills/specs/rules/tasks-parallel-analysis.md +0 -34
package/bin/install.js
CHANGED
|
@@ -37,6 +37,7 @@ const { checkVersions } = require('./lib/version-check');
|
|
|
37
37
|
const { ensureGitignore } = require('./phases/root-config');
|
|
38
38
|
const { runPostInstall } = require('./phases/post-install');
|
|
39
39
|
const { setupSkillDeps } = require('./phases/skills-setup');
|
|
40
|
+
const { setupRtk } = require('./phases/setup-rtk');
|
|
40
41
|
const { printSummary } = require('./phases/summary');
|
|
41
42
|
|
|
42
43
|
/** Install a single platform: payload + runtime + metadata, under one spinner. */
|
|
@@ -91,6 +92,8 @@ Options:
|
|
|
91
92
|
-u, --upgrade, -f, --force Alias of --force-overwrite
|
|
92
93
|
--with-skills-deps Install skill dependencies (Python venv + pip, npm,
|
|
93
94
|
Chromium/Playwright). Otherwise prompted interactively.
|
|
95
|
+
--with-rtk Install the rtk token-saver (binary + Claude Code hook).
|
|
96
|
+
Otherwise prompted interactively.
|
|
94
97
|
-y, --yes Non-interactive: skip prompts, use defaults (CI)
|
|
95
98
|
-h, --help Show this help
|
|
96
99
|
-v, --version Print version
|
|
@@ -166,6 +169,7 @@ async function main() {
|
|
|
166
169
|
ensureGitignore(ctx);
|
|
167
170
|
await runPostInstall(ctx);
|
|
168
171
|
await setupSkillDeps(ctx);
|
|
172
|
+
await setupRtk(ctx);
|
|
169
173
|
printSummary(ctx);
|
|
170
174
|
|
|
171
175
|
if (!ctx.dryRun) backup.prune(3);
|
package/bin/lib/context.js
CHANGED
|
@@ -185,7 +185,8 @@ function parseInstallerArgs(argv) {
|
|
|
185
185
|
forceOverwrite: false,
|
|
186
186
|
dryRun: false,
|
|
187
187
|
yes: false,
|
|
188
|
-
withSkillsDeps: false
|
|
188
|
+
withSkillsDeps: false,
|
|
189
|
+
withRtk: false
|
|
189
190
|
};
|
|
190
191
|
|
|
191
192
|
for (let i = 2; i < argv.length; i++) {
|
|
@@ -199,6 +200,8 @@ function parseInstallerArgs(argv) {
|
|
|
199
200
|
args.yes = true;
|
|
200
201
|
} else if (arg === '--with-skills-deps') {
|
|
201
202
|
args.withSkillsDeps = true;
|
|
203
|
+
} else if (arg === '--with-rtk') {
|
|
204
|
+
args.withRtk = true;
|
|
202
205
|
} else if (arg === '--lang') {
|
|
203
206
|
args.lang = argv[++i];
|
|
204
207
|
} else if (arg.startsWith('--lang=')) {
|
package/bin/lib/i18n.js
CHANGED
|
@@ -59,6 +59,17 @@ const MESSAGES = {
|
|
|
59
59
|
// Skills / deps setup
|
|
60
60
|
skillDepsConfirm: 'Install skill dependencies? (Python venv, pip, npm, Chromium — takes a few minutes)',
|
|
61
61
|
skillsSkipped: 'Skill dependencies skipped.\n Run later: npx @haposoft/cafekit --with-skills-deps',
|
|
62
|
+
|
|
63
|
+
// rtk token-saver setup
|
|
64
|
+
rtkConfirm: 'Install the rtk token-saver? (compacts git/grep/ls/build/test output for Claude Code Bash commands)',
|
|
65
|
+
rtkInstalling: 'Installing rtk via {method}',
|
|
66
|
+
rtkInstalled: 'rtk installed',
|
|
67
|
+
rtkInstallFailed: 'Could not install rtk — skipping (non-fatal). Install manually later: https://github.com/rtk-ai/rtk',
|
|
68
|
+
rtkInitFailed: 'rtk init -g failed: {reason} — hook not registered (non-fatal)',
|
|
69
|
+
rtkNeedsJq: 'rtk hook needs jq to run. Install jq, then the hook activates automatically.',
|
|
70
|
+
rtkFailed: 'rtk setup error: {reason} — skipped (non-fatal)',
|
|
71
|
+
rtkSkipped: 'rtk token-saver skipped.\n Run later: npx @haposoft/cafekit --with-rtk',
|
|
72
|
+
rtkSummary: 'rtk token-saver: hook registered for Claude Code Bash commands',
|
|
62
73
|
venvCreating: 'Setting up Python environment',
|
|
63
74
|
venvReady: 'Python environment ready',
|
|
64
75
|
venvFailed: 'Could not create Python environment — check your Python 3 installation.',
|
|
@@ -165,6 +176,17 @@ const MESSAGES = {
|
|
|
165
176
|
|
|
166
177
|
skillDepsConfirm: 'スキルの依存関係をインストールしますか?(Python venv、pip、npm、Chromium — 数分かかります)',
|
|
167
178
|
skillsSkipped: 'スキルの依存関係をスキップしました。\n 後で実行: npx @haposoft/cafekit --with-skills-deps',
|
|
179
|
+
|
|
180
|
+
// rtk token-saver setup
|
|
181
|
+
rtkConfirm: 'rtk トークンセーバーをインストールしますか?(Claude Code の Bash コマンドの git/grep/ls/build/test 出力を圧縮します)',
|
|
182
|
+
rtkInstalling: '{method} で rtk をインストール中',
|
|
183
|
+
rtkInstalled: 'rtk をインストールしました',
|
|
184
|
+
rtkInstallFailed: 'rtk をインストールできませんでした — スキップします(致命的ではありません)。後で手動でインストール: https://github.com/rtk-ai/rtk',
|
|
185
|
+
rtkInitFailed: 'rtk init -g が失敗しました: {reason} — フックは未登録です(致命的ではありません)',
|
|
186
|
+
rtkNeedsJq: 'rtk フックの実行には jq が必要です。jq をインストールするとフックが自動的に有効になります。',
|
|
187
|
+
rtkFailed: 'rtk セットアップエラー: {reason} — スキップしました(致命的ではありません)',
|
|
188
|
+
rtkSkipped: 'rtk トークンセーバーをスキップしました。\n 後で実行: npx @haposoft/cafekit --with-rtk',
|
|
189
|
+
rtkSummary: 'rtk トークンセーバー: Claude Code の Bash コマンド用にフックを登録しました',
|
|
168
190
|
venvCreating: 'Python 環境を準備しています',
|
|
169
191
|
venvReady: 'Python 環境の準備ができました',
|
|
170
192
|
venvFailed: 'Python 環境を作成できませんでした — Python 3 のインストールを確認してください。',
|
|
@@ -268,6 +290,17 @@ const MESSAGES = {
|
|
|
268
290
|
|
|
269
291
|
skillDepsConfirm: 'Cài dependencies cho skill ngay? (Python venv, pip, npm, Chromium — mất vài phút)',
|
|
270
292
|
skillsSkipped: 'Đã bỏ qua dependencies skill.\n Cài sau bằng lệnh: npx @haposoft/cafekit --with-skills-deps',
|
|
293
|
+
|
|
294
|
+
// rtk token-saver setup
|
|
295
|
+
rtkConfirm: 'Cài rtk token-saver? (rút gọn output git/grep/ls/build/test cho các lệnh Bash của Claude Code)',
|
|
296
|
+
rtkInstalling: 'Đang cài rtk qua {method}',
|
|
297
|
+
rtkInstalled: 'Đã cài rtk',
|
|
298
|
+
rtkInstallFailed: 'Không thể cài rtk — bỏ qua (không nghiêm trọng). Cài thủ công sau: https://github.com/rtk-ai/rtk',
|
|
299
|
+
rtkInitFailed: 'rtk init -g thất bại: {reason} — hook chưa được đăng ký (không nghiêm trọng)',
|
|
300
|
+
rtkNeedsJq: 'Hook của rtk cần jq để chạy. Cài jq thì hook sẽ tự kích hoạt.',
|
|
301
|
+
rtkFailed: 'Lỗi cài rtk: {reason} — đã bỏ qua (không nghiêm trọng)',
|
|
302
|
+
rtkSkipped: 'Đã bỏ qua rtk token-saver.\n Cài sau bằng lệnh: npx @haposoft/cafekit --with-rtk',
|
|
303
|
+
rtkSummary: 'rtk token-saver: đã đăng ký hook cho các lệnh Bash của Claude Code',
|
|
271
304
|
venvCreating: 'Đang thiết lập môi trường Python',
|
|
272
305
|
venvReady: 'Môi trường Python đã sẵn sàng',
|
|
273
306
|
venvFailed: 'Không thể tạo môi trường Python — hãy kiểm tra cài đặt Python 3.',
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local captured-spawnSync helper for the rtk install phase.
|
|
3
|
+
*
|
|
4
|
+
* Why local: `lib/skill-deps.js` does not export a `run(cmd, args)` helper
|
|
5
|
+
* (it uses `runAsync` internally and never re-exports it). The rtk phase
|
|
6
|
+
* needs a synchronous, captured spawnSync (R2.2, R4.2) that returns
|
|
7
|
+
* `{ ok, status, stdout, stderr }` — same shape as `skill-deps.runAsync` but
|
|
8
|
+
* blocking. KISS: one 20-line module, no dependency on `lib/skill-deps.js`
|
|
9
|
+
* (which is skill-domain and not a generic command runner).
|
|
10
|
+
*
|
|
11
|
+
* @param {string} cmd
|
|
12
|
+
* @param {string[]} args
|
|
13
|
+
* @param {object} [opts] — passed through to spawnSync (e.g. { timeout })
|
|
14
|
+
* @returns {{ ok: boolean, status: number|null, stdout: string, stderr: string }}
|
|
15
|
+
*/
|
|
16
|
+
const { spawnSync } = require('child_process');
|
|
17
|
+
|
|
18
|
+
function run(cmd, args, opts = {}) {
|
|
19
|
+
const r = spawnSync(cmd, args, { encoding: 'utf8', ...opts });
|
|
20
|
+
return {
|
|
21
|
+
ok: !r.error && r.status === 0,
|
|
22
|
+
status: r.status,
|
|
23
|
+
stdout: r.stdout || '',
|
|
24
|
+
stderr: r.stderr || ''
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = { run };
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* setup-rtk — opt-in install of the rtk token-saver and its Claude Code hook.
|
|
3
|
+
*
|
|
4
|
+
* Phase order (per specs/rtk-installer-integration/design.md):
|
|
5
|
+
* shouldRunRtk → ensureRtkBinary → `rtk init -g` → jq precheck → manifest
|
|
6
|
+
*
|
|
7
|
+
* R1-01 wired the consent gate (`shouldRunRtk`) and a thin `setupRtk` wrapper.
|
|
8
|
+
* R2-01 adds the orchestrator: `ensureRtkBinary` (detect/install), the
|
|
9
|
+
* `rtk init -g` hook registration, the `jq` precheck, and the manifest write.
|
|
10
|
+
*
|
|
11
|
+
* Non-fatal invariant (R2.7, R4.1): the entire `setupRtk` body is wrapped in
|
|
12
|
+
* try/catch. Any throw / non-zero exit / timeout is logged as a warning via
|
|
13
|
+
* `ctx.t('rtkFailed')` (with `{reason}` interpolation) and the cafekit
|
|
14
|
+
* install continues with its existing exit code. Mirrors the non-fatal
|
|
15
|
+
* contract of `phases/skills-setup.js`.
|
|
16
|
+
*
|
|
17
|
+
* Idempotency (R2.1, R2.6): re-runs see `hasCmd('rtk')` short-circuit the
|
|
18
|
+
* binary install path and re-invoke `rtk init -g` (rtk's own command handles
|
|
19
|
+
* its hook idempotency). The in-memory `ctx.rtkSetupRan` flag records that
|
|
20
|
+
* setup executed, so downstream consumers (R3-01 summary line) can read it.
|
|
21
|
+
*
|
|
22
|
+
* Cross-OS install (validated 2026-06-15): best-effort order
|
|
23
|
+
* (1) official prebuilt install script (pinned source)
|
|
24
|
+
* (2) `cargo install rtk` when `hasCmd('cargo')`
|
|
25
|
+
* (3) skip + warn
|
|
26
|
+
* covers macOS/Linux dev machines (cargo fall-through) and CI (skip+warn).
|
|
27
|
+
*
|
|
28
|
+
* No timeout for `cargo install rtk` (validated 2026-06-15): user accepted
|
|
29
|
+
* default `spawnSync` timeout; non-fatal guarantee holds via try/catch.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
const dep = require('../lib/skill-deps');
|
|
33
|
+
const rtkRun = require('../lib/rtk-run');
|
|
34
|
+
const { hasCmd } = dep;
|
|
35
|
+
const { run } = rtkRun;
|
|
36
|
+
|
|
37
|
+
const RTK_INIT_TIMEOUT_MS = 30_000; // `rtk init -g` is a fast global-hook write
|
|
38
|
+
const RTK_PREBUILT_TIMEOUT_MS = 120_000; // prebuilt install: curl download + sh pipe; slow networks need headroom
|
|
39
|
+
const RTK_PREBUILT_INSTALL_URL =
|
|
40
|
+
'https://raw.githubusercontent.com/rtk-ai/rtk/main/install.sh';
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Detect rtk binary, installing it if absent. Pure function over `ctx`.
|
|
44
|
+
*
|
|
45
|
+
* @param {object} ctx
|
|
46
|
+
* @returns {'present'|'installed'|'absent'}
|
|
47
|
+
*/
|
|
48
|
+
function ensureRtkBinary(ctx) {
|
|
49
|
+
if (hasCmd('rtk')) return 'present';
|
|
50
|
+
return installRtkBinary(ctx) ? 'installed' : 'absent';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Best-effort rtk install. Tries the official prebuilt install script first
|
|
55
|
+
* (R5.2: surface source), then falls back to `cargo install rtk` if cargo is
|
|
56
|
+
* available, otherwise returns false (R2.3 non-fatal skip).
|
|
57
|
+
*
|
|
58
|
+
* @param {object} ctx
|
|
59
|
+
* @returns {boolean} true on success, false on failure or no method available
|
|
60
|
+
*/
|
|
61
|
+
function installRtkBinary(ctx) {
|
|
62
|
+
// Method 1: official prebuilt install script (pinned URL).
|
|
63
|
+
ctx.ui.info(ctx.t('rtkInstalling', { method: `prebuilt script (${RTK_PREBUILT_INSTALL_URL})` }));
|
|
64
|
+
const prebuilt = run('sh', ['-c', `curl -fsSL ${RTK_PREBUILT_INSTALL_URL} | sh`], {
|
|
65
|
+
timeout: RTK_PREBUILT_TIMEOUT_MS
|
|
66
|
+
});
|
|
67
|
+
if (prebuilt.ok) return true;
|
|
68
|
+
|
|
69
|
+
// Method 2: `cargo install rtk` if cargo is on PATH.
|
|
70
|
+
if (hasCmd('cargo')) {
|
|
71
|
+
ctx.ui.info(ctx.t('rtkInstalling', { method: 'cargo install rtk' }));
|
|
72
|
+
const cargo = run('cargo', ['install', 'rtk']);
|
|
73
|
+
if (cargo.ok) return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// No method succeeded.
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Phase entrypoint. R2-01 wires the full install + hook + manifest pipeline.
|
|
82
|
+
* The consent gate is delegated to `shouldRunRtk(ctx)` (R1-01).
|
|
83
|
+
*
|
|
84
|
+
* @param {object} ctx
|
|
85
|
+
* @returns {Promise<object>} the same ctx
|
|
86
|
+
*/
|
|
87
|
+
async function setupRtk(ctx) {
|
|
88
|
+
if (!(await shouldRunRtk(ctx))) {
|
|
89
|
+
// Dry-run: enumerate the action set that would have run (validated 2026-06-15).
|
|
90
|
+
if (ctx.dryRun) {
|
|
91
|
+
ctx.ui.info(
|
|
92
|
+
'[dry-run] rtk setup skipped (would: detect rtk binary, install if missing via prebuilt/cargo, run rtk init -g, check jq)'
|
|
93
|
+
);
|
|
94
|
+
} else {
|
|
95
|
+
ctx.ui.info(ctx.t('rtkSkipped'));
|
|
96
|
+
}
|
|
97
|
+
return ctx;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
// Step 1: ensure the binary is on PATH (R2.1, R2.2, R2.3).
|
|
102
|
+
const state = ensureRtkBinary(ctx);
|
|
103
|
+
if (state === 'absent') {
|
|
104
|
+
ctx.ui.warn(ctx.t('rtkInstallFailed'));
|
|
105
|
+
return ctx;
|
|
106
|
+
}
|
|
107
|
+
if (state === 'installed') {
|
|
108
|
+
ctx.ui.info(ctx.t('rtkInstalled'));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Step 2: register rtk's official Claude Code hook via `rtk init -g` (R2.4).
|
|
112
|
+
const init = run('rtk', ['init', '-g'], { timeout: RTK_INIT_TIMEOUT_MS });
|
|
113
|
+
if (!init.ok) {
|
|
114
|
+
ctx.ui.warn(ctx.t('rtkInitFailed', { reason: init.stderr || `exit ${init.status}` }));
|
|
115
|
+
return ctx;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Step 3: `jq` precheck (R2.5). Warn if missing; do not fail.
|
|
119
|
+
if (!hasCmd('jq')) {
|
|
120
|
+
ctx.ui.warn(ctx.t('rtkNeedsJq'));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Step 4: record the action (R2.6, idempotency). In-memory flag — re-runs
|
|
124
|
+
// observe `hasCmd('rtk')` short-circuit and re-invoke `rtk init -g`,
|
|
125
|
+
// which is itself idempotent. R3-01 reads this flag for the summary line.
|
|
126
|
+
ctx.rtkSetupRan = true;
|
|
127
|
+
return ctx;
|
|
128
|
+
} catch (err) {
|
|
129
|
+
// Non-fatal boundary: any unexpected throw becomes a warning, the
|
|
130
|
+
// installer continues (R2.7, R4.1).
|
|
131
|
+
ctx.ui.warn(ctx.t('rtkFailed', { reason: (err && err.message) || String(err) }));
|
|
132
|
+
return ctx;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Decision matrix for whether the rtk setup phase should run. R1-01.
|
|
138
|
+
*
|
|
139
|
+
* 1. --dry-run → false (R1.4)
|
|
140
|
+
* 2. ctx.options.withRtk === true → true (R1.1)
|
|
141
|
+
* 3. !ctx.interactive → false (R1.3)
|
|
142
|
+
* 4. interactive confirm resolves true → true (R1.2)
|
|
143
|
+
* 5. interactive confirm resolves false → false (R1.2)
|
|
144
|
+
*
|
|
145
|
+
* @param {object} ctx
|
|
146
|
+
* @returns {Promise<boolean>}
|
|
147
|
+
*/
|
|
148
|
+
async function shouldRunRtk(ctx) {
|
|
149
|
+
if (ctx.dryRun) return false;
|
|
150
|
+
if (ctx.options.withRtk) return true;
|
|
151
|
+
if (!ctx.interactive) return false;
|
|
152
|
+
const yes = await ctx.ui.confirm(
|
|
153
|
+
{ message: ctx.t('rtkConfirm'), initialValue: false },
|
|
154
|
+
false
|
|
155
|
+
);
|
|
156
|
+
return yes === true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
module.exports = { shouldRunRtk, setupRtk, ensureRtkBinary, installRtkBinary };
|
package/bin/phases/summary.js
CHANGED
|
@@ -55,6 +55,10 @@ function printSummary(ctx) {
|
|
|
55
55
|
for (const key of platforms) {
|
|
56
56
|
lines.push(key === 'claude' ? t('nsClaude') : t('nsOpencode'));
|
|
57
57
|
}
|
|
58
|
+
// rtk token-saver result (set by phases/setup-rtk.js when setup completed).
|
|
59
|
+
if (ctx.rtkSetupRan) {
|
|
60
|
+
lines.push(t('rtkSummary'));
|
|
61
|
+
}
|
|
58
62
|
const keySkills = skillsNeedingKeys(platforms);
|
|
59
63
|
if (keySkills.length > 0) {
|
|
60
64
|
lines.push(t('nsKeys', { skills: keySkills.join(', ') }));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haposoft/cafekit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Claude Code-first spec-driven workflow for AI coding assistants. Bundles CafeKit hapo: skills, runtime hooks, agents, and installer scaffolding.",
|
|
5
5
|
"author": "Haposoft <nghialt@haposoft.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -77,6 +77,27 @@ try {
|
|
|
77
77
|
return status === 'pending' && deps.every((dep) => taskStatusByPath.get(dep) === 'done');
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
+
// ── State-change gate: only emit the full tollgate when spec state changed ──
|
|
81
|
+
// This hook runs on EVERY UserPromptSubmit. Re-printing the whole block when
|
|
82
|
+
// phase + done/total are unchanged is wasted context (~460 tok/turn). We keep
|
|
83
|
+
// a tiny state fingerprint in a temp file: same -> one-line reminder; changed
|
|
84
|
+
// -> full block (and refresh the fingerprint). Fail-open at every step.
|
|
85
|
+
const stateKey = `${phase}|${taskCounts.done || 0}/${taskEntries.length}`;
|
|
86
|
+
const cacheFile = path.join(__dirname, '.logs', 'tollgate-last.txt');
|
|
87
|
+
|
|
88
|
+
let lastKey = '';
|
|
89
|
+
try { lastKey = fs.readFileSync(cacheFile, 'utf8').trim(); } catch { /* first run */ }
|
|
90
|
+
|
|
91
|
+
if (lastKey === stateKey) {
|
|
92
|
+
console.log(`\n> 🔵 Spec \`${featureName}\` @ \`${phase}\` (${taskCounts.done || 0}/${taskEntries.length} tasks done). Tollgate active — sync \`spec.json\` when state changes.\n`);
|
|
93
|
+
process.exit(0);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
fs.mkdirSync(path.dirname(cacheFile), { recursive: true });
|
|
98
|
+
fs.writeFileSync(cacheFile, stateKey);
|
|
99
|
+
} catch { /* fail-open: if the write fails we still print the full block */ }
|
|
100
|
+
|
|
80
101
|
// Format the output
|
|
81
102
|
const lines = [];
|
|
82
103
|
lines.push('');
|
|
@@ -85,6 +85,25 @@ function extractRequirementIds(requirementsText) {
|
|
|
85
85
|
return [...ids].filter((id) => id !== 'R0').sort();
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Extract sub-criteria IDs (e.g. R3.4) ONLY when requirements.md declares them
|
|
90
|
+
* as explicit literals — bold `**R3.4**` or a line-leading `R3.4`. Specs that
|
|
91
|
+
* write acceptance criteria as a plain numbered list under a heading declare no
|
|
92
|
+
* such literals, so this returns empty and per-criterion coverage is skipped
|
|
93
|
+
* for them (no behaviour change). This is a progressive check: it rewards an
|
|
94
|
+
* unambiguous format, it never penalises the legacy one.
|
|
95
|
+
*/
|
|
96
|
+
function extractSubCriteriaIds(requirementsText) {
|
|
97
|
+
const ids = new Set();
|
|
98
|
+
const re = /(?:^|\s)\**(R\d+\.\d+)\**/gim;
|
|
99
|
+
let match;
|
|
100
|
+
while ((match = re.exec(requirementsText)) !== null) {
|
|
101
|
+
const id = match[1].toUpperCase();
|
|
102
|
+
if (!id.startsWith('R0.')) ids.add(id);
|
|
103
|
+
}
|
|
104
|
+
return [...ids].sort();
|
|
105
|
+
}
|
|
106
|
+
|
|
88
107
|
function validateTaskSections(taskPath, content, errors) {
|
|
89
108
|
const hasContext = hasHeading(content, 'Context');
|
|
90
109
|
const hasConstraints = hasHeading(content, 'Constraints');
|
|
@@ -113,6 +132,75 @@ function validateTaskSections(taskPath, content, errors) {
|
|
|
113
132
|
}
|
|
114
133
|
}
|
|
115
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Each phase completion must carry its own timestamp. Reusing `timestamps.init`
|
|
137
|
+
* for a later phase is forbidden (SKILL.md spec.json Update Rules). This used to
|
|
138
|
+
* be a prompt-only rule the model had to remember; here it is a hard backstop.
|
|
139
|
+
*/
|
|
140
|
+
function validateTimestamps(spec, errors) {
|
|
141
|
+
const ts = spec.timestamps;
|
|
142
|
+
if (!ts || typeof ts !== 'object') return;
|
|
143
|
+
const init = ts.init;
|
|
144
|
+
if (!init) return;
|
|
145
|
+
|
|
146
|
+
for (const phase of ['requirements_done', 'design_done', 'tasks_done']) {
|
|
147
|
+
if (ts[phase] && ts[phase] === init) {
|
|
148
|
+
errors.push(
|
|
149
|
+
`spec.json.timestamps.${phase}: reuses init timestamp (${init}); ` +
|
|
150
|
+
'each phase must stamp its own completion time',
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Normalize a fenced code block body for byte-comparison: trim + collapse
|
|
157
|
+
* trailing whitespace per line + drop blank edges. Keeps inner structure so a
|
|
158
|
+
* real field rename (user_name vs userName) still differs. */
|
|
159
|
+
function normalizeBlock(body) {
|
|
160
|
+
return body
|
|
161
|
+
.split('\n')
|
|
162
|
+
.map((line) => line.replace(/\s+$/, ''))
|
|
163
|
+
.join('\n')
|
|
164
|
+
.replace(/^\n+|\n+$/g, '');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Parse canonical contract definitions from design.md. A definition is an HTML
|
|
169
|
+
* marker `<!-- contract:NAME -->` immediately followed by a fenced code block.
|
|
170
|
+
* Returns a Map<name, normalizedBody>. Empty when the spec uses no markers —
|
|
171
|
+
* which makes the whole cross-layer check opt-in (no effect on legacy specs).
|
|
172
|
+
*/
|
|
173
|
+
function extractContractDefs(designText) {
|
|
174
|
+
const defs = new Map();
|
|
175
|
+
const re = /<!--\s*contract:([A-Za-z0-9_.-]+)\s*-->\s*\n```[^\n]*\n([\s\S]*?)\n```/g;
|
|
176
|
+
let match;
|
|
177
|
+
while ((match = re.exec(designText)) !== null) {
|
|
178
|
+
defs.set(match[1], normalizeBlock(match[2]));
|
|
179
|
+
}
|
|
180
|
+
return defs;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* From a task body, return the contracts it claims plus the first fenced block
|
|
185
|
+
* it carries (the task's local copy of the contract). Shape:
|
|
186
|
+
* { names: string[], block: string|null }
|
|
187
|
+
* `names` come from a `Contracts: A, B` line; `block` is the first fenced code
|
|
188
|
+
* block in the task (normalized) used to compare against the canonical defs.
|
|
189
|
+
*/
|
|
190
|
+
function extractTaskContracts(taskText) {
|
|
191
|
+
const names = [];
|
|
192
|
+
const nameLine = taskText.match(/^\s*Contracts:\s*([^\n]+)$/im);
|
|
193
|
+
if (nameLine) {
|
|
194
|
+
for (const token of nameLine[1].split(',')) {
|
|
195
|
+
const name = token.trim();
|
|
196
|
+
if (name) names.push(name);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const blockMatch = taskText.match(/```[^\n]*\n([\s\S]*?)\n```/);
|
|
200
|
+
const block = blockMatch ? normalizeBlock(blockMatch[1]) : null;
|
|
201
|
+
return { names, block };
|
|
202
|
+
}
|
|
203
|
+
|
|
116
204
|
function validateSpec(specDir) {
|
|
117
205
|
const errors = [];
|
|
118
206
|
const warnings = [];
|
|
@@ -141,6 +229,8 @@ function validateSpec(specDir) {
|
|
|
141
229
|
errors.push('spec.json.scope_lock: must be an object, not a boolean or array');
|
|
142
230
|
}
|
|
143
231
|
|
|
232
|
+
validateTimestamps(spec, errors);
|
|
233
|
+
|
|
144
234
|
const taskFiles = listTaskFiles(specDir);
|
|
145
235
|
const taskFileSet = new Set(taskFiles);
|
|
146
236
|
|
|
@@ -236,11 +326,20 @@ function validateSpec(specDir) {
|
|
|
236
326
|
}
|
|
237
327
|
|
|
238
328
|
let requirementIds = [];
|
|
329
|
+
let subCriteriaIds = [];
|
|
239
330
|
if (fs.existsSync(requirementsPath)) {
|
|
240
|
-
|
|
331
|
+
const requirementsText = fs.readFileSync(requirementsPath, 'utf8');
|
|
332
|
+
requirementIds = extractRequirementIds(requirementsText);
|
|
333
|
+
subCriteriaIds = extractSubCriteriaIds(requirementsText);
|
|
241
334
|
}
|
|
242
335
|
|
|
243
336
|
const coveredRequirementIds = new Set();
|
|
337
|
+
const coveredSubCriteriaIds = new Set();
|
|
338
|
+
// Cross-layer contract defs (opt-in): empty unless design.md uses
|
|
339
|
+
// <!-- contract:NAME --> markers, so legacy specs are unaffected.
|
|
340
|
+
const contractDefs = fs.existsSync(designPath)
|
|
341
|
+
? extractContractDefs(fs.readFileSync(designPath, 'utf8'))
|
|
342
|
+
: new Map();
|
|
244
343
|
for (const taskFile of taskFiles) {
|
|
245
344
|
const fullPath = path.join(specDir, taskFile);
|
|
246
345
|
const content = fs.readFileSync(fullPath, 'utf8');
|
|
@@ -256,8 +355,30 @@ function validateSpec(specDir) {
|
|
|
256
355
|
const numericMappingRe = /_Requirements:\s*([^_\n]+)_/gi;
|
|
257
356
|
while ((match = numericMappingRe.exec(content)) !== null) {
|
|
258
357
|
for (const token of match[1].split(',')) {
|
|
259
|
-
const
|
|
260
|
-
|
|
358
|
+
const trimmed = token.trim();
|
|
359
|
+
const major = trimmed.match(/^(\d+)(?:\.\d+)?$/);
|
|
360
|
+
if (major) coveredRequirementIds.add(`R${major[1]}`);
|
|
361
|
+
// Record the full sub-criterion (e.g. 3.4 -> R3.4) for per-criterion coverage.
|
|
362
|
+
const sub = trimmed.match(/^(\d+\.\d+)$/);
|
|
363
|
+
if (sub) coveredSubCriteriaIds.add(`R${sub[1]}`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Cross-layer contract check (opt-in via design.md markers). When a task
|
|
368
|
+
// claims `Contracts: NAME`, that name must exist in design.md and the task's
|
|
369
|
+
// local copy of the block must match the canonical definition byte-for-byte
|
|
370
|
+
// (after whitespace normalization). This catches BE/FE drift like
|
|
371
|
+
// user_name vs userName before integration.
|
|
372
|
+
if (contractDefs.size > 0) {
|
|
373
|
+
const { names, block } = extractTaskContracts(content);
|
|
374
|
+
for (const name of names) {
|
|
375
|
+
if (!contractDefs.has(name)) {
|
|
376
|
+
errors.push(`${taskFile}: declares unknown contract "${name}" (not defined in design.md)`);
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
if (block !== null && block !== contractDefs.get(name)) {
|
|
380
|
+
errors.push(`${taskFile}: contract "${name}" body diverges from the canonical definition in design.md`);
|
|
381
|
+
}
|
|
261
382
|
}
|
|
262
383
|
}
|
|
263
384
|
}
|
|
@@ -268,6 +389,18 @@ function validateSpec(specDir) {
|
|
|
268
389
|
}
|
|
269
390
|
}
|
|
270
391
|
|
|
392
|
+
// Per-criterion coverage: only enforced when requirements.md declares explicit
|
|
393
|
+
// R{N}.{M} literals AND tasks use the numeric `_Requirements: x.y_` mapping.
|
|
394
|
+
// If a spec declares sub-criteria but no task maps any at sub-level, that is the
|
|
395
|
+
// legacy coarse format (major-only) — skip silently to avoid false failures.
|
|
396
|
+
if (subCriteriaIds.length > 0 && coveredSubCriteriaIds.size > 0) {
|
|
397
|
+
for (const subId of subCriteriaIds) {
|
|
398
|
+
if (!coveredSubCriteriaIds.has(subId)) {
|
|
399
|
+
errors.push(`requirements.md:${subId}: acceptance criterion not covered by any task`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
271
404
|
if (spec.ready_for_implementation === true && errors.length > 0) {
|
|
272
405
|
errors.push('spec.json.ready_for_implementation: cannot be true while validator errors exist');
|
|
273
406
|
}
|
|
@@ -19,7 +19,7 @@ metadata:
|
|
|
19
19
|
This skill provides a 10-step workflow to transform ideas into evidence-backed specs:
|
|
20
20
|
|
|
21
21
|
```
|
|
22
|
-
Analyze → Dependency Scan → Complexity Assessment → Init → Evidence Gate + Requirements → Design → Tasks →
|
|
22
|
+
Analyze → Dependency Scan → Complexity Assessment → Init → Evidence Gate + Requirements → Design → Tasks → Review → Completion
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
An entry/dispatch layer sits in front of the pipeline: four flags (`--auto`, `--validate`, `--status`, `--archive`) plus **Interactive State Discovery** (the no-flag path) decide *which part of the pipeline to run and where to stop* — see **Default Behavior**. The pipeline itself is unchanged regardless of how it is invoked.
|
|
@@ -90,7 +90,6 @@ Forbidden generated artifacts:
|
|
|
90
90
|
- Do NOT create `specs/<feature>/hydration.md`.
|
|
91
91
|
- Do NOT create shorthand task files such as `tasks/task-R0-1.md`, `tasks/task-R1-1.md`, or `tasks/R0-1-<slug>.md`.
|
|
92
92
|
- The template file name is never the output file name. `templates/spec-state.json` is only the schema source for generated `spec.json`.
|
|
93
|
-
- Task hydration is session/task-state synchronization only; it MUST NOT be written as a markdown artifact.
|
|
94
93
|
- Before marking a spec ready, run the deterministic validator:
|
|
95
94
|
- `node .claude/scripts/validate-spec-output.cjs specs/<feature>`
|
|
96
95
|
- Any validator failure blocks `ready_for_implementation = true`.
|
|
@@ -111,7 +110,7 @@ Forbidden generated artifacts:
|
|
|
111
110
|
### Dispatch order
|
|
112
111
|
|
|
113
112
|
1. `--status` (or bare `status`) → run the status report (see **Subcommands**), then stop.
|
|
114
|
-
2. `--validate <feature>` → jump to **Step
|
|
113
|
+
2. `--validate <feature>` → jump to **Step 8** (the `--validate` rules below are unchanged).
|
|
115
114
|
3. `--archive` (or bare `archive`) → run the archive workflow, then stop.
|
|
116
115
|
4. `--auto` → **non-interactive run**. If the argument matches an unfinished spec → resume it from `current_phase` and finish to Tasks. Otherwise create new and run the full pipeline (Step 1→10) end-to-end. Either way: auto-approve and skip the Creation Mode question; if a new description is missing, ask only for it; the hard safety gates still apply.
|
|
117
116
|
5. Otherwise (`/hapo:specs` or `/hapo:specs "<description>"`) → **Interactive State Discovery**.
|
|
@@ -131,7 +130,7 @@ Goal: ask the user enough to determine **state** and **intent**, then run the ex
|
|
|
131
130
|
- For non-trivial specs, execute the Step 5 Evidence Gate before writing final requirements. Do not design from memory when codebase or current external evidence can answer the question.
|
|
132
131
|
3. **Creation Mode Gate** (how far to run) — see the dedicated section below. Options: `Auto (→ Tasks)` · `Stop after Design` · `Step by step`. If `.claude/settings.json` → `language` is not English, also offer the **Translation Mirror** (see below).
|
|
133
132
|
4. **Continue an unfinished spec** → read `spec.json.current_phase`, then `AskUserQuestion` offering the **remaining** stop points (e.g. `→ Design`, `→ Tasks`, `Step by step`) and resume the existing pipeline from that phase. If a message accompanied the call, treat it as added context for the next phase (scope expansion → ask, per `references/ask-user-question-gates.md`).
|
|
134
|
-
5. **Run the chosen scope.** On an early stop, emit the Paused Block (Step
|
|
133
|
+
5. **Run the chosen scope.** On an early stop, emit the Paused Block (Step 9b). Sync `spec.json` per `state-sync.md`.
|
|
135
134
|
|
|
136
135
|
> The pipeline, validator, templates, and rules are unchanged. "Stop after Design" simply halts before Step 7; it runs Steps 1–6 exactly as specified.
|
|
137
136
|
|
|
@@ -147,7 +146,7 @@ Shown once (via `AskUserQuestion`) on the no-flag create path, after the descrip
|
|
|
147
146
|
|
|
148
147
|
Rules:
|
|
149
148
|
- `--auto` equals choosing **Auto (→ Tasks)** without showing this gate.
|
|
150
|
-
- **Stop after Design** / **Step by step** leave `ready_for_implementation=false` and emit the Paused Block (Step
|
|
149
|
+
- **Stop after Design** / **Step by step** leave `ready_for_implementation=false` and emit the Paused Block (Step 9b).
|
|
151
150
|
- Continuing later: run `/hapo:specs` again → Interactive State Discovery detects the unfinished spec and offers the remaining stop points. No flag or keyword required.
|
|
152
151
|
|
|
153
152
|
### Translation Mirror (optional reference copy)
|
|
@@ -161,7 +160,7 @@ Load `references/translation-mirror.md`. This is the only language feature in th
|
|
|
161
160
|
|
|
162
161
|
### When called WITH `--validate` argument
|
|
163
162
|
|
|
164
|
-
System IMMEDIATELY jumps to **Step
|
|
163
|
+
System IMMEDIATELY jumps to **Step 8: Validation Review**.
|
|
165
164
|
The system MUST NOT execute Steps 1-8. Instead, load `references/review.md` and follow it **step-by-step**.
|
|
166
165
|
|
|
167
166
|
#### `--validate` Guardrails (NON-NEGOTIABLE)
|
|
@@ -216,8 +215,7 @@ flowchart TD
|
|
|
216
215
|
CM -->|"Stop after Design"| STOP["Paused at design → emit Paused Block"]
|
|
217
216
|
CM -->|"Auto / Step by step"| S["Step 7: Tasks — split into individual files"]
|
|
218
217
|
S --> T["Create tasks/task-R*.md + task_registry"]
|
|
219
|
-
T -->
|
|
220
|
-
U --> V{Review?}
|
|
218
|
+
T --> V{Review?}
|
|
221
219
|
V -->|Yes| W["Run review — auto-pick red team or validation"]
|
|
222
220
|
V -->|No| X["Update spec.json → DONE"]
|
|
223
221
|
W --> X
|
|
@@ -310,6 +308,7 @@ Load: `references/scope-inquiry.md`
|
|
|
310
308
|
- Design decisions MUST trace back to `research.md` evidence. If a design choice lacks evidence and is not a user-approved constraint, gather more evidence or ask the user before finalizing.
|
|
311
309
|
- Add diagrams only when design has multi-step or cross-boundary flows
|
|
312
310
|
- For auth/session, transport/entrypoint, persistence/schema, generated-artifact, or runtime-sensitive work, the design MUST fill the `Canonical Contracts & Invariants` section and tasks MUST inherit the same decisions verbatim.
|
|
311
|
+
- When a data shape must stay byte-identical across layers (e.g. an API response consumed by both a backend task and a frontend task, or a DB row shape), declare it as a **named contract block** — `<!-- contract:NAME -->` followed by a fenced block — per `templates/design.md`. Tasks that produce/consume it add `Contracts: NAME` and copy the block verbatim; the validator then hard-fails on cross-layer drift. Prefer this for any spec spanning BE/FE/DB.
|
|
313
312
|
- Update `spec.json` phase, timestamps, discovery mode
|
|
314
313
|
|
|
315
314
|
### Step 7: Task Breakdown
|
|
@@ -319,7 +318,6 @@ Load: `references/scope-inquiry.md`
|
|
|
319
318
|
- Load `rules/phase-decision-matrix.md` before generating task files. Treat "phase" as an implementation slice/task cluster, not a `phase-XX.md` artifact.
|
|
320
319
|
- Load `rules/task-scoring-rubric.md` for every candidate task to decide priority, split/merge, spike needs, dependencies, parallel eligibility, and evidence depth.
|
|
321
320
|
- Load `references/ask-user-question-gates.md`; if scoring reveals unapproved scope expansion or an unresolved user-owned choice, pause before writing task files.
|
|
322
|
-
- Load `rules/tasks-parallel-analysis.md` for parallel markers (default: enabled)
|
|
323
321
|
- Each task file follows template `templates/task.md`
|
|
324
322
|
- `Related Files` and test plans must inherit paths, contracts, and test targets from the codebase scout. If exact files/tests cannot be named for an enhancement, run targeted inspect before generating tasks.
|
|
325
323
|
- Each task file MUST include `Completion Criteria` and `Evidence` sections detailed enough that a downstream quality gate can prove the task is truly done. Existing specs may use `Task Test Plan & Verification Evidence` or legacy `Verification & Evidence`.
|
|
@@ -388,14 +386,7 @@ Each task file MUST be **self-contained and implementation-ready** — detailed
|
|
|
388
386
|
|
|
389
387
|
**FORBIDDEN:** Task files with only vague checkboxes and no exact files, requirements, or evidence. Compact is good; vague is invalid.
|
|
390
388
|
|
|
391
|
-
### Step 8:
|
|
392
|
-
Load: `references/task-hydration.md`
|
|
393
|
-
- Only run if >= 3 task files
|
|
394
|
-
- Convert task files → Claude Tasks with dependency chain (`addBlockedBy`)
|
|
395
|
-
- If TaskCreate tool unavailable → fallback to `TodoWrite`
|
|
396
|
-
- Task files are the single source of truth — hydration is just a convenience
|
|
397
|
-
|
|
398
|
-
### Step 9: Validation Review (Optional)
|
|
389
|
+
### Step 8: Validation Review (Optional)
|
|
399
390
|
Load: `references/review.md` + `rules/design-review.md`
|
|
400
391
|
- Load `references/ask-user-question-gates.md` before applying validation or red-team changes. User approval is required when findings modify approved scope, requirements, canonical contracts, design decisions, or task behavior.
|
|
401
392
|
- System auto-evaluates spec complexity and decides review depth:
|
|
@@ -409,33 +400,26 @@ Load: `references/review.md` + `rules/design-review.md`
|
|
|
409
400
|
- **Reconciliation Rule:** `validation.status = "completed"` is forbidden until all accepted findings and validation decisions are physically propagated into `requirements.md`, `design.md`, `tasks/*.md`, and `spec.json` where applicable.
|
|
410
401
|
- **Deterministic Gate:** Run `node .claude/scripts/validate-spec-output.cjs specs/<feature>` after all fixes and before final output. Script failure overrides any LLM checklist result and blocks `ready_for_implementation = true`.
|
|
411
402
|
|
|
412
|
-
### Step
|
|
413
|
-
- Re-scan the `tasks/` directory and rebuild `spec.json.task_files` from the real filesystem (sorted
|
|
414
|
-
- Rebuild `spec.json.task_registry` from the real filesystem if it is missing, stale, or missing keys. Preserve task status fields when the path still matches.
|
|
403
|
+
### Step 8.5: Finalization Audit (MANDATORY)
|
|
404
|
+
- Re-scan the `tasks/` directory and rebuild `spec.json.task_files` + `task_registry` from the real filesystem (sorted relative paths; preserve task status when the path still matches).
|
|
415
405
|
- Run `node .claude/scripts/validate-spec-output.cjs specs/<feature>` and treat any non-zero exit as a blocking failure.
|
|
416
|
-
|
|
417
|
-
-
|
|
418
|
-
|
|
419
|
-
-
|
|
420
|
-
- FAIL if
|
|
421
|
-
- FAIL if
|
|
422
|
-
- FAIL if
|
|
423
|
-
- FAIL if a task lacks `Completion Criteria` or `Evidence` (existing `Task Test Plan & Verification Evidence` or legacy `Verification & Evidence` is accepted)
|
|
424
|
-
- FAIL if a task creates runtime-facing artifacts but neither proves reachability from an entrypoint/caller nor names a later integration task responsible for wiring them.
|
|
425
|
-
- FAIL if a UI/app/runtime spec has multiple user-facing task outputs but no final integration/reachability task or final integration section.
|
|
426
|
-
- FAIL if accepted validation decisions exist in reports but are not reflected in the implementation-facing sections of affected artifacts (`Context`, `Steps`, `Requirements`, `Completion Criteria`, `Evidence`, canonical contracts, or requirements text).
|
|
427
|
-
- FAIL if any generated task replaces the required task template with a reduced `Objective` / `Steps` / `Evidence` shape. `Context`, `Constraints`, `Related Files`, `Completion Criteria`, `Evidence`, and `Risk Assessment` must all remain present.
|
|
428
|
-
- FAIL if the spec scope/provider was switched away from Anthropic/Claude but `requirements.md`, `design.md`, or `tasks/*.md` still contain stale provider-specific strings such as `Claude API`, `Haiku`, or `haiku_reachable`. `research.md` is the only allowed place for historical cost comparisons.
|
|
406
|
+
|
|
407
|
+
**Validator-enforced (do not re-check by hand — a clean exit clears all of these):** task_files/task_registry synced to disk; task naming `tasks/task-R{N}-{SEQ}-<slug>.md`; no forbidden artifacts; research.md Evidence Summary present; every requirement **and sub-criterion** covered by a task; each task keeps the full template (Context, Constraints, Steps, Related Files, Completion Criteria, Evidence, Risk Assessment) plus Runtime reachability; numeric requirement IDs only; validation_recommended vs validation.status; timestamps not reused from init; ready_for_implementation blocked while any error exists.
|
|
408
|
+
|
|
409
|
+
**Judgment-only audit (validator cannot see these — assert manually):**
|
|
410
|
+
- FAIL if a UI/app/runtime spec has multiple user-facing task outputs but no final integration/reachability task or section.
|
|
411
|
+
- FAIL if accepted validation/red-team decisions exist in reports but are not reflected in implementation-facing sections (`Context`, `Steps`, `Requirements`, `Completion Criteria`, `Evidence`, canonical contracts, or requirements text).
|
|
412
|
+
- FAIL if the scope/provider switched away from Anthropic/Claude but `requirements.md`, `design.md`, or `tasks/*.md` still contain stale strings such as `Claude API`, `Haiku`, or `haiku_reachable`. `research.md` is the only allowed place for historical comparisons.
|
|
429
413
|
- FAIL if privacy/delete-data work lacks a single canonical deletion policy. The design MUST explicitly choose either:
|
|
430
414
|
1. hard-delete with no re-registration lock, or
|
|
431
415
|
2. privacy-preserving re-registration lock using a non-raw identifier (for example `email_hash` / `email_fingerprint`) with a retention period.
|
|
432
416
|
Tasks and requirements must reuse the same policy verbatim; mixed policies are invalid.
|
|
433
|
-
- FAIL if `validation.status = "completed"` but `timestamps.validation_done` / `
|
|
434
|
-
- If `validation_recommended = true` and `validation.status` is not `completed` (or an explicit accepted
|
|
435
|
-
- If `translation.enabled = true`, ensure `i18n/<language_code>/` exists and was re-synced after the final canonical write; the mirror
|
|
436
|
-
- Only after this audit passes may the system mark `progress.tasks = "done"` and `ready_for_implementation = true
|
|
417
|
+
- FAIL if `validation.status = "completed"` but `timestamps.validation_done` / `review_done`, `updated_at`, and report metadata are not synchronized with the final reviewed state.
|
|
418
|
+
- If `validation_recommended = true` and `validation.status` is not `completed` (or an explicit user-accepted risk state), `ready_for_implementation` MUST remain `false`.
|
|
419
|
+
- If `translation.enabled = true`, ensure `i18n/<language_code>/` exists and was re-synced after the final canonical write; the mirror is excluded from validation and never blocks `ready_for_implementation`.
|
|
420
|
+
- Only after this audit passes may the system mark `progress.tasks = "done"` and `ready_for_implementation = true`.
|
|
437
421
|
|
|
438
|
-
### Step
|
|
422
|
+
### Step 9: Completion — Context Reminder (MANDATORY)
|
|
439
423
|
After completing the spec, output a short summary of what was generated, then you MUST output the following block EXACTLY as written. DO NOT use awkward translations like "Điểm đã phản ánh đúng quyết định của bạn", keep it professional or just output the block directly:
|
|
440
424
|
|
|
441
425
|
**Command integrity:** The implementation handoff command is always `/hapo:develop <feature>`. Never suggest `/work`, `/code`, or any non-CafeKit alias as the next step for this workflow.
|
|
@@ -448,7 +432,7 @@ After completing the spec, output a short summary of what was generated, then yo
|
|
|
448
432
|
💡 Tip: Run /clear or start a new chat session before implementing to reduce planning context carryover.
|
|
449
433
|
```
|
|
450
434
|
|
|
451
|
-
### Step
|
|
435
|
+
### Step 9b: Paused Block (early stop)
|
|
452
436
|
|
|
453
437
|
When the run stops before Tasks (Creation Mode = **Stop after Design** or **Step by step**), do NOT print the completion block above and do NOT run the deterministic validator (no tasks exist yet). Instead print:
|
|
454
438
|
|
|
@@ -583,26 +567,19 @@ The canonical surface is four flags. Bare-verb forms are kept only as silent bac
|
|
|
583
567
|
- No over-engineering: if a function suffices, don't create a class
|
|
584
568
|
|
|
585
569
|
### Pre-Finalization Checklist
|
|
586
|
-
Before finalizing any specification, assert all the following
|
|
587
|
-
|
|
588
|
-
-
|
|
589
|
-
|
|
590
|
-
-
|
|
570
|
+
Before finalizing any specification, assert all the following.
|
|
571
|
+
|
|
572
|
+
**Machine-enforced** (run `node .claude/scripts/validate-spec-output.cjs specs/<feature>` — it hard-fails on all of these, so do not re-verify by hand): scope_lock object, research.md Evidence Summary, every requirement + sub-criterion covered by a task, each task has Completion Criteria + Evidence + Risk Assessment + the required headings, task_files/task_registry synced to disk with valid dependencies, task naming convention, validation gate vs validation.status, timestamps not reused. A clean validator exit clears this whole group.
|
|
573
|
+
|
|
574
|
+
**Judgment-only** (validator cannot see these — assert manually):
|
|
575
|
+
- [ ] **EARS format** applied to all acceptance criteria, with measurable thresholds (no "fast"/"safe"/"robust")
|
|
591
576
|
- [ ] **Discovery mode** selected and recorded in spec.json.design_context
|
|
592
577
|
- [ ] **Requirements traceability** matrix present in design.md
|
|
593
|
-
- [ ] **Canonical Contracts & Invariants** filled for auth/transport/persistence/artifact-sensitive work
|
|
594
|
-
- [ ] **Every task file** maps to at least 1 valid in-scope requirement ID
|
|
595
|
-
- [ ] **Every task file** includes `Evidence` with executable or inspectable proof
|
|
578
|
+
- [ ] **Canonical Contracts & Invariants** filled for auth/transport/persistence/artifact-sensitive work, and inherited verbatim by every task that touches the contract
|
|
596
579
|
- [ ] **State Machine Blueprint:** design.md contains Mermaid diagrams for non-trivial flows
|
|
597
|
-
- [ ] **Dependency graph complete**: no task can start before its blockers are listed
|
|
598
|
-
- [ ] **Risk matrix filled**: likelihood × impact, with mitigation for High items
|
|
599
580
|
- [ ] **Test strategy defined**: what gets unit tested, integration tested, e2e validated
|
|
600
|
-
- [ ] **task_files inventory synced**: no missing or orphaned task references
|
|
601
|
-
- [ ] **task_registry synced**: every task file has exactly one machine-state entry with valid status + dependencies
|
|
602
|
-
- [ ] **deterministic validator passed**: `node .claude/scripts/validate-spec-output.cjs specs/<feature>`
|
|
603
|
-
- [ ] **Validation gate consistent**: validation_recommended and validation.status agree with spec risk
|
|
604
581
|
- [ ] **Provider wording clean**: no stale vendor/provider strings outside allowed research context
|
|
605
|
-
- [ ] **
|
|
582
|
+
- [ ] **Validation decisions propagated** into implementation-facing sections, not only reports/Risk Assessment
|
|
606
583
|
|
|
607
584
|
## When TO Use
|
|
608
585
|
|
|
@@ -635,8 +612,7 @@ Before finalizing any specification, assert all the following:
|
|
|
635
612
|
- `design-discovery-light.md` — Lightweight research workflow
|
|
636
613
|
- `design-review.md` — Design review GO/NO-GO process
|
|
637
614
|
- `phase-decision-matrix.md` — Implementation slice/task-cluster boundary rules
|
|
638
|
-
- `tasks-generation.md` — Task generation rules (includes spike task rules)
|
|
639
|
-
- `tasks-parallel-analysis.md` — Parallel task analysis
|
|
615
|
+
- `tasks-generation.md` — Task generation rules (includes spike task rules + parallel analysis)
|
|
640
616
|
- `task-scoring-rubric.md` — Task priority, split/merge, spike, dependency, and evidence-depth scoring
|
|
641
617
|
|
|
642
618
|
### References (`references/`)
|
|
@@ -645,7 +621,6 @@ Before finalizing any specification, assert all the following:
|
|
|
645
621
|
- `scope-inquiry.md` — 5-Dimension Complexity Assessment (Semantic, Hypothesis, Gap, Risk/Cynefin, Blast Radius)
|
|
646
622
|
- `research-strategy.md` — Research strategy (7 tools)
|
|
647
623
|
- `codebase-analysis.md` — Codebase analysis (4 mandatory files)
|
|
648
|
-
- `task-hydration.md` — Task files → Claude Tasks conversion
|
|
649
624
|
- `review.md` — Spec review (auto-decides: red team 8 steps + validate 6 steps)
|
|
650
625
|
- `archive-workflow.md` — Archive workflow (5 steps)
|
|
651
626
|
- `translation-mirror.md` — Optional reference-only translation mirror (detection, layout, sync, fields)
|
|
@@ -212,13 +212,15 @@ Choose verification by task risk and touched surface. Do not force every task to
|
|
|
212
212
|
- No data dependency on other pending tasks
|
|
213
213
|
- No shared file or resource contention
|
|
214
214
|
- No prerequisite review/approval from another task
|
|
215
|
+
- Environment/setup work needed by the task is already satisfied or covered within the task itself
|
|
215
216
|
- Validate that identified parallel tasks operate within separate boundaries defined in the Architecture Pattern & Boundary Map.
|
|
216
217
|
- Confirm API/event contracts from design.md do not overlap in ways that cause conflicts.
|
|
217
|
-
- Append `(P)` immediately after the task number for each parallel-capable task:
|
|
218
|
+
- Append `(P)` immediately after the task number for each parallel-capable task, kept **outside** the checkbox brackets:
|
|
218
219
|
- Example: `- [ ] 2.1 (P) Build background worker`
|
|
219
220
|
- Apply to both major tasks and sub-tasks when appropriate.
|
|
220
221
|
- If sequential mode is requested, omit `(P)` markers entirely.
|
|
221
222
|
- Group parallel tasks logically (same parent when possible) and highlight any ordering caveats in detail bullets.
|
|
223
|
+
- Do not mark container-only major tasks (no own actionable bullets) with `(P)` — evaluate parallelism at the sub-task level.
|
|
222
224
|
- Explicitly call out dependencies that prevent `(P)` even when tasks look similar.
|
|
223
225
|
|
|
224
226
|
### Checkbox Format
|
|
@@ -79,6 +79,12 @@ Capture only the contracts whose inconsistency would break downstream implementa
|
|
|
79
79
|
|
|
80
80
|
> Task files must reuse the same contract wording. If the feature touches delete-data or privacy retention, explicitly decide whether re-registration is blocked and how that lock works without keeping raw PII. If implementation later needs a different contract, update this section first before generating or editing tasks.
|
|
81
81
|
|
|
82
|
+
### Machine-checkable contracts (optional, recommended for cross-layer work)
|
|
83
|
+
|
|
84
|
+
When a data shape must stay identical across layers (e.g. a response consumed by both a backend task and a frontend task), define it as a **named contract block** so the validator can detect drift. Mark it with an HTML comment `<!-- contract:NAME -->` on its own line, immediately followed by a fenced code block holding the shape. For example, a contract named `OrderResponse` would be an HTML marker line followed by a json fenced block containing `{ "orderId": "string", "total": "number" }`.
|
|
85
|
+
|
|
86
|
+
Any task that produces or consumes this shape adds a `Contracts: OrderResponse` line in its body and **copies the same fenced block verbatim**. `validate-spec-output.cjs` then fails the spec if a task's copy diverges from this canonical definition, or references a contract name not defined here. This is opt-in: specs without `<!-- contract:* -->` markers are unaffected.
|
|
87
|
+
|
|
82
88
|
## System Flows
|
|
83
89
|
|
|
84
90
|
Provide only the diagrams needed to explain non-trivial flows. Use pure Mermaid syntax. Common patterns:
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
# Task Hydration
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
Convert task files (persistent storage) into Claude Tasks (session-scoped only), enabling real-time progress tracking and automatic unblocking when predecessor tasks complete. `spec.json.task_registry` is the machine-state bridge between markdown task files and session-scoped Claude Tasks.
|
|
6
|
-
|
|
7
|
-
## When to Run
|
|
8
|
-
|
|
9
|
-
- **Default:** Auto-run after task files are created
|
|
10
|
-
- **Skip if:** Fewer than 3 task files (overhead not worth it)
|
|
11
|
-
- **User opt-out:** When user explicitly says no task tracking
|
|
12
|
-
|
|
13
|
-
## Hydrate ↔ Sync-back Diagram
|
|
14
|
-
|
|
15
|
-
```
|
|
16
|
-
┌──────────────────┐ Hydrate ┌───────────────────┐
|
|
17
|
-
│ Task Files │ ─────────► │ Claude Tasks │
|
|
18
|
-
│ (persistent) │ │ (session-scoped) │
|
|
19
|
-
│ [ ] task-R0-01 │ │ ◼ pending │
|
|
20
|
-
│ [ ] task-R0-02 │ │ ◼ pending │
|
|
21
|
-
└──────────────────┘ └───────────────────┘
|
|
22
|
-
│ Work
|
|
23
|
-
▼
|
|
24
|
-
┌──────────────────┐ Sync-back ┌───────────────────┐
|
|
25
|
-
│ Task Files │ ◄───────── │ Task Updates │
|
|
26
|
-
│ (updated) │ │ (completed) │
|
|
27
|
-
│ [x] task-R0-01 │ │ ✓ completed │
|
|
28
|
-
│ [ ] task-R0-02 │ │ ◼ in_progress │
|
|
29
|
-
└──────────────────┘ └───────────────────┘
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
- **Hydrate:** Read task files → `TaskCreate` for each unchecked `[ ]` item
|
|
33
|
-
- **Work:** `TaskUpdate` tracks in_progress/completed in real-time
|
|
34
|
-
- **Sync-back:** Update `[ ]` → `[x]` in task files, update `spec.json` progress
|
|
35
|
-
|
|
36
|
-
## Required Metadata for Task Creation
|
|
37
|
-
|
|
38
|
-
| Field | Description | Example |
|
|
39
|
-
|---|---|---|
|
|
40
|
-
| `taskNumber` | Task sequence number | `R0-02` |
|
|
41
|
-
| `priority` | Priority level | `P1`, `P2`, `P3` |
|
|
42
|
-
| `effort` | Time estimate | `2h` |
|
|
43
|
-
| `specDir` | Spec directory path | `specs/mobile-app/` |
|
|
44
|
-
| `taskFile` | Task file name | `task-R0-02-extension-shell.md` |
|
|
45
|
-
|
|
46
|
-
## Hydration Workflow
|
|
47
|
-
|
|
48
|
-
1. Read all `tasks/task-R*.md` files → filter incomplete tasks
|
|
49
|
-
2. Load `spec.json.task_registry` and prefer its task status/dependencies when present
|
|
50
|
-
3. Create `TaskCreate` for each major task, attach `addBlockedBy` per dependency chain:
|
|
51
|
-
```
|
|
52
|
-
Task R0-01 (no blockers) ← start here
|
|
53
|
-
Task R0-02 (addBlockedBy: [task-R0-01-id])
|
|
54
|
-
Task R1-01 (addBlockedBy: [task-R0-02-id])
|
|
55
|
-
```
|
|
56
|
-
4. Create additional `TaskCreate` for high-risk steps within tasks (if any)
|
|
57
|
-
5. Verify: no dependency cycles, all tasks have complete metadata, and every hydrated task has a corresponding `task_registry` entry
|
|
58
|
-
|
|
59
|
-
## Fallback
|
|
60
|
-
|
|
61
|
-
`TaskCreate`/`TaskUpdate`/`TaskGet`/`TaskList` tools are CLI-only. If running in VSCode extension:
|
|
62
|
-
- Use `TodoWrite` as a tracking substitute
|
|
63
|
-
- Task files remain the single source of truth
|
|
64
|
-
- Hydration is an optimization convenience, NOT a requirement
|
|
65
|
-
|
|
66
|
-
## Cook Handoff Protocol
|
|
67
|
-
|
|
68
|
-
### Same session (spec → code immediately)
|
|
69
|
-
1. Spec creates tasks + hydrates → tasks exist in session
|
|
70
|
-
2. Code reads `TaskList` → finds existing tasks → picks them up
|
|
71
|
-
3. Skips re-creation, begins implementing directly
|
|
72
|
-
|
|
73
|
-
### New session (resume)
|
|
74
|
-
1. User runs `/hapo:develop <feature>` in a new session
|
|
75
|
-
2. Code reads `TaskList` → empty (tasks died with old session)
|
|
76
|
-
3. Code reads task files → re-hydrates from `[ ]` items
|
|
77
|
-
4. `[x]` items = done, skip those
|
|
78
|
-
|
|
79
|
-
### Sync-back (after code completes)
|
|
80
|
-
1. `TaskUpdate` marks tasks as completed
|
|
81
|
-
2. Update `[ ]` → `[x]` in task files
|
|
82
|
-
3. Update `spec.json.task_registry[path].status`, timestamps, blocker, and last_updated_at
|
|
83
|
-
4. Update `spec.json` progress for the corresponding phase
|
|
84
|
-
5. Git commit captures the state transition
|
|
85
|
-
|
|
86
|
-
## Quality Checks
|
|
87
|
-
|
|
88
|
-
After hydration, verify:
|
|
89
|
-
- [ ] Dependency chain has no cycles
|
|
90
|
-
- [ ] All task files have corresponding Tasks
|
|
91
|
-
- [ ] Required metadata fields are complete
|
|
92
|
-
- [ ] Task count matches `[ ]` items in files
|
|
93
|
-
- Output: `✓ Hydrated [N] tasks with dependency chain`
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# Parallel Task Analysis Rules
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
Provide a consistent way to identify implementation tasks that can be safely executed in parallel while generating individual task files.
|
|
5
|
-
|
|
6
|
-
## When to Consider Tasks Parallel
|
|
7
|
-
Only mark a task as parallel-capable when **all** of the following are true:
|
|
8
|
-
|
|
9
|
-
1. **No data dependency** on pending tasks.
|
|
10
|
-
2. **No conflicting files or shared mutable resources** are touched.
|
|
11
|
-
3. **No prerequisite review/approval** from another task is required beforehand.
|
|
12
|
-
4. **Environment/setup work** needed by this task is already satisfied or covered within the task itself.
|
|
13
|
-
|
|
14
|
-
## Marking Convention
|
|
15
|
-
- Append `(P)` immediately after the numeric identifier for each qualifying task.
|
|
16
|
-
- Example: `- [ ] 2.1 (P) Build background worker for emails`
|
|
17
|
-
- Apply `(P)` to both major tasks and sub-tasks when appropriate.
|
|
18
|
-
- If sequential execution is requested (e.g. via `--sequential` flag), omit `(P)` markers entirely.
|
|
19
|
-
- Keep `(P)` **outside** of checkbox brackets to avoid confusion with completion state.
|
|
20
|
-
|
|
21
|
-
## Grouping & Ordering Guidelines
|
|
22
|
-
- Group parallel tasks under the same parent whenever the work belongs to the same theme.
|
|
23
|
-
- List obvious prerequisites or caveats in the detail bullets (e.g., "Requires schema migration from 1.2").
|
|
24
|
-
- When two tasks look similar but are not parallel-safe, call out the blocking dependency explicitly.
|
|
25
|
-
- Skip marking container-only major tasks (those without their own actionable detail bullets) with `(P)`—evaluate parallel execution at the sub-task level instead.
|
|
26
|
-
|
|
27
|
-
## Quality Checklist
|
|
28
|
-
Before marking a task with `(P)`, ensure you have:
|
|
29
|
-
|
|
30
|
-
- Verified that running this task concurrently will not create merge or deployment conflicts.
|
|
31
|
-
- Captured any shared state expectations in the detail bullets.
|
|
32
|
-
- Confirmed that the implementation can be tested independently.
|
|
33
|
-
|
|
34
|
-
If any check fails, **do not** mark the task with `(P)` and explain the dependency in the task details.
|