@haposoft/cafekit 0.12.0 → 0.13.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haposoft/cafekit",
3
- "version": "0.12.0",
3
+ "version": "0.13.1",
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('');
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CafeKit spec GROUNDING checker (Specs v2, Layer 2).
4
+ *
5
+ * The deterministic validator (`validate-spec-output.cjs`) checks spec SHAPE
6
+ * (headings, registry sync, coverage). It is blind to whether the file paths a
7
+ * task cites actually EXIST. This script closes that gap: it parses every task's
8
+ * `Related Files` table and verifies, against the real work tree, that each
9
+ * Modify/Delete/Read path exists — unless another task in the same spec Creates
10
+ * it first (intra-spec resolution).
11
+ *
12
+ * Why active-grep instead of opt-in: field tests showed opt-in checks get
13
+ * skipped by the author. Grounding runs unconditionally over whatever paths the
14
+ * tasks already declare, so it cannot be dodged by choosing a different format.
15
+ *
16
+ * Usage:
17
+ * node spec-ground.cjs <specDir> [--root <work-context>]
18
+ *
19
+ * <specDir> : path to specs/<feature> (relative or absolute)
20
+ * --root : work tree the paths resolve against. Default: two levels up from
21
+ * specDir (…/<root>/specs/<feature> -> <root>). Override for
22
+ * monorepos where the spec lives apart from the code.
23
+ *
24
+ * Exit: 0 = GROUNDED, 1 = FAIL (missing path), 2 = usage error.
25
+ */
26
+
27
+ const fs = require('fs');
28
+ const path = require('path');
29
+
30
+ function usage() {
31
+ console.error('Usage: node spec-ground.cjs <specDir> [--root <work-context>]');
32
+ }
33
+
34
+ function parseArgs(argv) {
35
+ const args = { specDir: null, root: null };
36
+ for (let i = 2; i < argv.length; i++) {
37
+ if (argv[i] === '--root') args.root = argv[++i];
38
+ else if (!args.specDir) args.specDir = argv[i];
39
+ }
40
+ return args;
41
+ }
42
+
43
+ function listTaskFiles(specDir) {
44
+ const tasksDir = path.join(specDir, 'tasks');
45
+ if (!fs.existsSync(tasksDir)) return [];
46
+ return fs
47
+ .readdirSync(tasksDir, { withFileTypes: true })
48
+ .filter((e) => e.isFile() && e.name.endsWith('.md'))
49
+ .map((e) => path.join(tasksDir, e.name))
50
+ .sort();
51
+ }
52
+
53
+ /**
54
+ * Pull rows out of a markdown `## Related Files` table. Returns
55
+ * [{ path, action, taskFile }]. Tables look like:
56
+ * | Path | Action | Description |
57
+ * | `front/x.vue` | Modify | ... |
58
+ */
59
+ function parseRelatedFiles(content, taskFile) {
60
+ const rows = [];
61
+ const lines = content.split('\n');
62
+ let inSection = false;
63
+ for (const line of lines) {
64
+ if (/^##+\s+Related Files\s*$/i.test(line)) { inSection = true; continue; }
65
+ if (inSection && /^##+\s+/.test(line)) break; // next heading ends section
66
+ if (!inSection) continue;
67
+ if (!line.trim().startsWith('|')) continue;
68
+
69
+ const cells = line.split('|').map((c) => c.trim());
70
+ // cells[0] is '' (leading pipe). path=cells[1], action=cells[2].
71
+ const rawPath = (cells[1] || '').replace(/`/g, '').trim();
72
+ const action = (cells[2] || '').toLowerCase().trim();
73
+ if (!rawPath || rawPath === 'path') continue; // header row
74
+ if (/^-+$/.test(rawPath)) continue; // separator row
75
+ if (!action || !/(create|modify|delete|read)/.test(action)) continue;
76
+ rows.push({ path: rawPath, action, taskFile: path.basename(taskFile) });
77
+ }
78
+ return rows;
79
+ }
80
+
81
+ /** Lenient existence check with minimal glob handling. */
82
+ function pathExists(root, p) {
83
+ // Glob-ish path (e.g. `assets/{x,instagram,facebook}.svg` or `dir/*.ts`):
84
+ // verify the parent directory exists rather than the literal name.
85
+ if (/[{*]/.test(p)) {
86
+ const parent = path.dirname(p.replace(/\{.*$/, '').replace(/\*.*$/, ''));
87
+ return fs.existsSync(path.join(root, parent));
88
+ }
89
+ return fs.existsSync(path.join(root, p));
90
+ }
91
+
92
+ function main() {
93
+ const { specDir: specInput, root: rootInput } = parseArgs(process.argv);
94
+ if (!specInput) { usage(); process.exit(2); }
95
+
96
+ const specDir = path.resolve(process.cwd(), specInput);
97
+ if (!fs.existsSync(specDir)) {
98
+ console.error(`FAIL ${specInput}\n- spec directory does not exist`);
99
+ process.exit(1);
100
+ }
101
+ // Default work-context: two levels up (…/<root>/specs/<feature>).
102
+ const root = rootInput
103
+ ? path.resolve(process.cwd(), rootInput)
104
+ : path.resolve(specDir, '..', '..');
105
+
106
+ const taskFiles = listTaskFiles(specDir);
107
+ const allRows = [];
108
+ for (const tf of taskFiles) {
109
+ allRows.push(...parseRelatedFiles(fs.readFileSync(tf, 'utf8'), tf));
110
+ }
111
+
112
+ // Paths that will exist because a task Creates them (intra-spec resolution).
113
+ const createdPaths = new Set(
114
+ allRows.filter((r) => r.action.includes('create')).map((r) => r.path),
115
+ );
116
+
117
+ const errors = [];
118
+ const warnings = [];
119
+ let checked = 0;
120
+
121
+ for (const row of allRows) {
122
+ const onDisk = pathExists(root, row.path);
123
+ if (row.action.includes('create')) {
124
+ if (onDisk) warnings.push(`${row.taskFile}: Create path already exists (will overwrite): ${row.path}`);
125
+ continue;
126
+ }
127
+ // modify / delete / read must exist OR be created by another task in-spec.
128
+ checked++;
129
+ if (!onDisk && !createdPaths.has(row.path)) {
130
+ errors.push(`${row.taskFile}: ${row.action} path not found in work tree: ${row.path}`);
131
+ }
132
+ }
133
+
134
+ for (const w of warnings) console.warn(`[WARN] ${w}`);
135
+
136
+ const rel = path.relative(process.cwd(), specDir) || specDir;
137
+ if (errors.length > 0) {
138
+ console.error(`FAIL ${rel} (root: ${root})`);
139
+ for (const e of errors) console.error(`- ${e}`);
140
+ console.error(`\n${errors.length} missing / ${checked} checked path(s).`);
141
+ process.exit(1);
142
+ }
143
+ console.log(`GROUNDED ${rel} (${checked} path(s) verified against ${root})`);
144
+ }
145
+
146
+ main();
@@ -0,0 +1,187 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CafeKit spec SCAFFOLD generator (Specs v2 — output-cost reducer).
4
+ *
5
+ * Field tests showed the dominant cost of a spec run is OUTPUT tokens: the
6
+ * model hand-Writes spec.json + every task file from scratch (a 16-task spec
7
+ * emitted ~935K output tokens). This script does the mechanical scaffolding so
8
+ * the model only has to Edit-fill content, not Write whole files.
9
+ *
10
+ * It creates spec.json (with task_files + task_registry pre-populated) and one
11
+ * stub per task from the canonical templates, leaving the `{{...}}` placeholders
12
+ * for the model to fill. It NEVER overwrites an existing spec directory.
13
+ *
14
+ * Usage:
15
+ * node spec-scaffold.cjs <feature> --tasks "R0-01-slug,R1-01-slug,..." \
16
+ * [--lang en] [--title "..."] [--specs-root specs]
17
+ *
18
+ * Exit: 0 = scaffolded, 2 = usage/precondition error.
19
+ */
20
+
21
+ const fs = require('fs');
22
+ const path = require('path');
23
+
24
+ const TEMPLATES = path.join(__dirname, '..', 'skills', 'specs', 'templates');
25
+ const TASK_ID_RE = /^R(\d+)-(\d+)-([a-z0-9]+(?:-[a-z0-9]+)*)$/;
26
+
27
+ function usage() {
28
+ console.error('Usage: node spec-scaffold.cjs <feature> --tasks "R0-01-slug,R1-01-slug" [--lang en] [--title "..."] [--specs-root specs]');
29
+ }
30
+
31
+ function parseArgs(argv) {
32
+ const a = { feature: null, tasks: null, lang: 'en', title: null, specsRoot: 'specs', tasksOnly: false };
33
+ for (let i = 2; i < argv.length; i++) {
34
+ const v = argv[i];
35
+ if (v === '--tasks') a.tasks = argv[++i];
36
+ else if (v === '--lang') a.lang = argv[++i];
37
+ else if (v === '--title') a.title = argv[++i];
38
+ else if (v === '--specs-root') a.specsRoot = argv[++i];
39
+ else if (v === '--tasks-only') a.tasksOnly = true;
40
+ else if (!a.feature) a.feature = v;
41
+ }
42
+ return a;
43
+ }
44
+
45
+ function readTemplate(name) {
46
+ const p = path.join(TEMPLATES, name);
47
+ if (!fs.existsSync(p)) {
48
+ console.error(`precondition: template not found: ${p}`);
49
+ process.exit(2);
50
+ }
51
+ return fs.readFileSync(p, 'utf8');
52
+ }
53
+
54
+ function titleFromSlug(slug) {
55
+ const s = slug.replace(/-/g, ' ');
56
+ return s.charAt(0).toUpperCase() + s.slice(1);
57
+ }
58
+
59
+ function registryEntry(t) {
60
+ return {
61
+ id: `R${t.req}-${t.seq}`,
62
+ title: titleFromSlug(t.slug),
63
+ status: 'pending',
64
+ dependencies: [],
65
+ blocker: null,
66
+ started_at: null,
67
+ completed_at: null,
68
+ last_updated_at: null,
69
+ };
70
+ }
71
+
72
+ function fillTask(taskTpl, t, feature) {
73
+ return taskTpl
74
+ .replace(/\{\{REQ_NUMBER\}\}/g, t.req)
75
+ .replace(/\{\{SEQ\}\}/g, t.seq)
76
+ .replace(/\{\{TITLE\}\}/g, titleFromSlug(t.slug))
77
+ .replace(/\{\{FEATURE_NAME\}\}/g, feature)
78
+ .replace(/\{\{PRIORITY\}\}/g, 'P2')
79
+ .replace(/\{\{EFFORT\}\}/g, 'TBD')
80
+ .replace(/\{\{DEPENDENCIES\}\}/g, 'none');
81
+ }
82
+
83
+ function nowIso() {
84
+ // Local ISO with offset, e.g. 2026-06-21T12:30:00+07:00
85
+ const d = new Date();
86
+ const pad = (n) => String(n).padStart(2, '0');
87
+ const off = -d.getTimezoneOffset();
88
+ const sign = off >= 0 ? '+' : '-';
89
+ const oh = pad(Math.floor(Math.abs(off) / 60));
90
+ const om = pad(Math.abs(off) % 60);
91
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}${sign}${oh}:${om}`;
92
+ }
93
+
94
+ function main() {
95
+ const args = parseArgs(process.argv);
96
+ if (!args.feature || !args.tasks) { usage(); process.exit(2); }
97
+
98
+ const ids = args.tasks.split(',').map((s) => s.trim()).filter(Boolean);
99
+ const parsed = [];
100
+ for (const id of ids) {
101
+ const m = id.match(TASK_ID_RE);
102
+ if (!m) {
103
+ console.error(`precondition: invalid task id "${id}" (want R{N}-{SEQ}-<slug>, SEQ 2 digits)`);
104
+ process.exit(2);
105
+ }
106
+ parsed.push({ raw: id, req: m[1], seq: m[2], slug: m[3], file: `tasks/task-${id}.md` });
107
+ }
108
+
109
+ const specDir = path.resolve(process.cwd(), args.specsRoot, args.feature);
110
+ const taskTpl = readTemplate('task.md');
111
+ const ts = nowIso();
112
+ const created = [];
113
+
114
+ // --- --tasks-only: add task stubs + merge registry into an existing spec ---
115
+ if (args.tasksOnly) {
116
+ const specPath = path.join(specDir, 'spec.json');
117
+ if (!fs.existsSync(specPath)) {
118
+ console.error(`precondition: --tasks-only needs an existing spec.json at ${specPath}`);
119
+ process.exit(2);
120
+ }
121
+ const spec = JSON.parse(fs.readFileSync(specPath, 'utf8'));
122
+ spec.task_files = spec.task_files || [];
123
+ spec.task_registry = spec.task_registry || {};
124
+ fs.mkdirSync(path.join(specDir, 'tasks'), { recursive: true });
125
+ for (const t of parsed) {
126
+ const abs = path.join(specDir, t.file);
127
+ if (fs.existsSync(abs)) continue; // never overwrite an already-filled task
128
+ fs.writeFileSync(abs, fillTask(taskTpl, t, args.feature));
129
+ if (!spec.task_files.includes(t.file)) spec.task_files.push(t.file);
130
+ spec.task_registry[t.file] = registryEntry(t);
131
+ created.push(t.file);
132
+ }
133
+ spec.task_files.sort();
134
+ spec.updated_at = ts;
135
+ fs.writeFileSync(specPath, JSON.stringify(spec, null, 2) + '\n');
136
+ const relTo = path.relative(process.cwd(), specDir) || specDir;
137
+ console.log(`SCAFFOLDED (tasks-only) ${relTo}`);
138
+ console.log(`- ${created.length} new task stub(s); task_files + task_registry merged.`);
139
+ console.log(`- NEXT: Edit-fill {{...}} in each stub; set dependencies; run validator + spec-ground.`);
140
+ return;
141
+ }
142
+
143
+ if (fs.existsSync(specDir)) {
144
+ console.error(`precondition: spec dir already exists, refusing to overwrite: ${specDir}`);
145
+ process.exit(2);
146
+ }
147
+
148
+ fs.mkdirSync(path.join(specDir, 'tasks'), { recursive: true });
149
+ fs.mkdirSync(path.join(specDir, 'reports'), { recursive: true });
150
+
151
+ // --- spec.json (from spec-state.json template) ---
152
+ const spec = JSON.parse(readTemplate('spec-state.json'));
153
+ spec.feature_name = args.feature;
154
+ spec.created_at = ts;
155
+ spec.updated_at = ts;
156
+ spec.language = args.lang;
157
+ spec.timestamps = spec.timestamps || {};
158
+ spec.timestamps.init = ts;
159
+ spec.scope_lock = spec.scope_lock || {};
160
+ spec.scope_lock.source = args.title || `{{PROJECT_DESCRIPTION}}`;
161
+ spec.task_files = parsed.map((t) => t.file);
162
+ spec.task_registry = {};
163
+ for (const t of parsed) spec.task_registry[t.file] = registryEntry(t);
164
+ fs.writeFileSync(path.join(specDir, 'spec.json'), JSON.stringify(spec, null, 2) + '\n');
165
+ created.push('spec.json');
166
+
167
+ // --- doc templates (placeholders left for the model to fill) ---
168
+ for (const [tpl, out] of [['requirements.md', 'requirements.md'], ['research.md', 'research.md'], ['design.md', 'design.md']]) {
169
+ const body = readTemplate(tpl).replace(/\{\{FEATURE_NAME\}\}/g, args.feature);
170
+ fs.writeFileSync(path.join(specDir, out), body);
171
+ created.push(out);
172
+ }
173
+
174
+ // --- task stubs (fill the cheap placeholders; leave the rest) ---
175
+ for (const t of parsed) {
176
+ fs.writeFileSync(path.join(specDir, t.file), fillTask(taskTpl, t, args.feature));
177
+ created.push(t.file);
178
+ }
179
+
180
+ const rel = path.relative(process.cwd(), specDir) || specDir;
181
+ console.log(`SCAFFOLDED ${rel}`);
182
+ console.log(`- ${created.length} files created: spec.json + 3 docs + ${parsed.length} task stub(s)`);
183
+ console.log(`- task_files + task_registry pre-populated (${parsed.length} entries, all pending)`);
184
+ console.log(`- NEXT: Edit-fill the {{...}} placeholders (do NOT leave any); set dependencies in task_registry; run validator + spec-ground before ready.`);
185
+ }
186
+
187
+ main();
@@ -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
- requirementIds = extractRequirementIds(fs.readFileSync(requirementsPath, 'utf8'));
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 number = token.trim().match(/^(\d+)(?:\.\d+)?$/);
260
- if (number) coveredRequirementIds.add(`R${number[1]}`);
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 → Hydration → Review → Completion
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 9** (the `--validate` rules below are unchanged).
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 10). Sync `spec.json` per `state-sync.md`.
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 10).
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 9: Validation Review**.
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 --> U["Step 8: Hydrate Claude Tasks if >= 3 task files"]
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
@@ -260,9 +258,24 @@ Load: `references/scope-inquiry.md`
260
258
  - If Risk = **Chaotic** → exit spec workflow, redirect to `hapo:hotfix`
261
259
  - If Risk = **Complex** → include spike/prototype tasks in the spec
262
260
  - If Blast Radius = **Critical Path** → spec MUST include rollback strategy and test coverage requirements
261
+ - **Complexity smell check (by numbers)** — quick YAGNI tripwires: >8 files touched / >2 new classes-services / >12 task files → challenge or simplify; **>15 task files → split into sibling specs** (a mega-spec is slow + failure-prone, per field test). Surface tripwires in the scope summary; never silently build the mega-version.
263
262
  - User picks scope level: Expand / Hold / Reduce
264
263
  - **Skip if:** trivial task (< 20 words, 1 file, user says "just do it")
265
264
 
265
+ #### Execution Tier (auto-scale — set right after the 5-Dimension assessment)
266
+ Pick ONE tier from the assessment; it controls how deep the pipeline runs so small specs stay cheap. Record it in `spec.json.design_context.execution_tier`.
267
+
268
+ | Tier | Trigger | Research (Step 5 external) | Discovery (Step 6) | Red-Team (Step 8) | Always runs |
269
+ |---|---|---|---|---|---|
270
+ | **Light** | Cynefin Clear + Blast Isolated + likely ≤2 tasks | skip (record skip rationale) | minimal | skip → Validate-only | scope_lock, EARS, **Layer 1 + Layer 2 grounding** |
271
+ | **Standard** | default (Complicated / Moderate blast / 3-4 tasks) | targeted | light | per Step 8 auto-decision | all of the above |
272
+ | **Deep** | Complex/Critical-path / security-migration / 5+ tasks | full (researchers + per-area scout) | full | Red-Team → Validate (mandatory) | all of the above |
273
+
274
+ Rules:
275
+ - Grounding (Layer 2) + structural validator (Layer 1) + scope_lock **never skip**, any tier — they are the quality floor, not depth knobs.
276
+ - Light tier is the antidote to "small spec, full-pipeline overhead". Do NOT use Light for auth/payment/migration/schema/privacy work — those force Deep regardless of size.
277
+ - Tier only changes *research/discovery/review depth*; it never changes the Hard Output Contract or DoCT.
278
+
266
279
  ### Step 4: Init
267
280
  - Check for duplicate slugs in `specs/` via Glob
268
281
  - Create directory `specs/<feature-name>/`
@@ -288,7 +301,7 @@ Load: `references/scope-inquiry.md`
288
301
  - External/current research must prefer official docs, standards, primary sources, or maintained upstream references. Record source links and the date/context of the finding.
289
302
  - Write `research.md` before final requirements. It MUST include an Evidence Summary with: codebase scout result, external research result or skip rationale, selected decision, rejected alternatives, remaining gaps, and downstream task/test implications.
290
303
  - If evidence exposes unresolved architecture choices, unclear acceptance criteria, or multiple viable approaches with no obvious winner, stop and route to `/hapo:brainstorm` instead of forcing a spec.
291
- - Write requirements in **EARS** format (see `rules/ears-format.md`)
304
+ - Write requirements in **EARS** format (see `rules/ears-format.md`). Give each acceptance criterion an **explicit literal ID `R{N}.{M}`** (e.g. `- **R1.1** When ...`), NOT a bare numbered list. This activates per-criterion coverage at Layer 1 (each `R1.1` must be mapped by a task `_Requirements: 1.1_`). A bare `1. 2. 3.` list silently disables sub-criterion coverage — do not use it for non-trivial specs.
292
305
  - **Feasibility Check:** Cross-check each requirement against known technical constraints from `research.md`.
293
306
  - Each requirement gets a unique numeric ID
294
307
  - **Verify Quality:** Before proceeding, assert each requirement is: *Singular, Unambiguous, Testable, and has a numeric ID*. Include Non-Functional Requirements (Performance, Security, Scalability, Reliability, Accessibility).
@@ -310,6 +323,7 @@ Load: `references/scope-inquiry.md`
310
323
  - 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
324
  - Add diagrams only when design has multi-step or cross-boundary flows
312
325
  - 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.
326
+ - 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
327
  - Update `spec.json` phase, timestamps, discovery mode
314
328
 
315
329
  ### Step 7: Task Breakdown
@@ -319,9 +333,11 @@ Load: `references/scope-inquiry.md`
319
333
  - 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
334
  - Load `rules/task-scoring-rubric.md` for every candidate task to decide priority, split/merge, spike needs, dependencies, parallel eligibility, and evidence depth.
321
335
  - 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
- - Each task file follows template `templates/task.md`
324
- - `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.
336
+ - **Scaffold instead of hand-writing (output-cost reducer).** Once the task list is decided, generate the stubs with the CLI rather than `Write`-ing each file by hand:
337
+ `node .claude/scripts/spec-scaffold.cjs <feature> --tasks "R0-01-slug,R1-01-slug,..." --tasks-only`
338
+ This creates each `tasks/task-R*.md` from `templates/task.md` and merges `task_files` + `task_registry` into the existing `spec.json` (pending, no overwrite of already-filled tasks). Then **Edit-fill** the `{{...}}` placeholders in each stub. Hand-writing whole task files is the single biggest output-token cost — scaffold + fill cuts it.
339
+ - Each task file follows template `templates/task.md` (scaffold already applies it). **Leave NO `{{...}}` placeholder unfilled** — a stub with placeholders is an incomplete task.
340
+ - `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. Every `Related Files` path will be grounded (Layer 2) at Step 8.5 — phantom paths hard-fail, so cite real paths.
325
341
  - 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`.
326
342
  - Each task's `Evidence` MUST choose the right proof type for the touched surface: unit for pure logic, component/integration for UI or state wiring, E2E/UI flow for complete user workflows, visual/responsive checks for style/layout work, accessibility checks for interactive UI, smoke checks for scaffold/config, regression checks for bug fixes, and performance/security checks only when the requirement or risk calls for them.
327
343
  - Every task MUST preserve the approved `scope_lock`: implement all scoped acceptance criteria for its requirement, avoid out-of-scope features, and record any intentional deferral as a named later task rather than implicit omission.
@@ -388,14 +404,22 @@ Each task file MUST be **self-contained and implementation-ready** — detailed
388
404
 
389
405
  **FORBIDDEN:** Task files with only vague checkboxes and no exact files, requirements, or evidence. Compact is good; vague is invalid.
390
406
 
391
- ### Step 8: Task Hydration
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
407
+ #### Definition of a Complete Task (DoCT) — the quality bar
408
+ A task is "complete enough to implement without guessing" only when ALL hold. Each item is enforced by a named mechanism, not goodwill:
409
+
410
+ | DoCT element | Enforced by |
411
+ |---|---|
412
+ | **Related Files** name exact real paths (Create/Modify/Delete) | Layer 2 grounding (`spec-ground.cjs`) — phantom path fails |
413
+ | **Contract** (API/DB/event shape) stated concretely | Layer 1 contract-drift check (`<!-- contract:NAME -->`) |
414
+ | **Acceptance** measurable (no "fast/nice/safe" without a threshold) | EARS rule + reviewer judgment |
415
+ | **Evidence** uses commands that exist in the project (`package.json`) | Author + grounding spirit; never invent test commands |
416
+ | **Reachability** names a real entrypoint/caller | `Runtime reachability verification` (Layer 1 presence) + judgment |
417
+ | **Requirements mapping** present (`_Requirements: x.y_`) | Layer 1 coverage check |
418
+ | **FE fidelity** — if a visual reference (image/Figma/tokens/style guide) is provided, the task carries the concrete values (hex/font/spacing/verbatim text) + a `match <reference>` constraint | `tasks-generation.md` Frontend Fidelity Rule + reviewer/visual check |
419
+
420
+ A stub with unfilled `{{...}}` placeholders fails DoCT by definition. The two scripts (Layer 1 structural + Layer 2 grounding) are the floor; reviewer judgment covers the rest.
397
421
 
398
- ### Step 9: Validation Review (Optional)
422
+ ### Step 8: Validation Review (Optional)
399
423
  Load: `references/review.md` + `rules/design-review.md`
400
424
  - 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
425
  - System auto-evaluates spec complexity and decides review depth:
@@ -407,35 +431,31 @@ Load: `references/review.md` + `rules/design-review.md`
407
431
  - **PROHIBITION:** The system MUST NOT skip Red Team because of a prior code-auditor review. Code review ≠ Spec review.
408
432
  - **PROHIBITION:** The system MUST NOT create `.ts`, `.js`, `.py` or any implementation files during validation. Spec-only outputs.
409
433
  - **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
- - **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
-
412
- ### Step 9.5: Finalization Audit (MANDATORY)
413
- - Re-scan the `tasks/` directory and rebuild `spec.json.task_files` from the real filesystem (sorted, relative paths)
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.
415
- - Run `node .claude/scripts/validate-spec-output.cjs specs/<feature>` and treat any non-zero exit as a blocking failure.
416
- - FAIL if any task file exists on disk but is missing from `task_files`
417
- - FAIL if any path in `task_files` does not exist on disk
418
- - FAIL if any task file exists on disk but is missing from `task_registry`
419
- - FAIL if any path in `task_registry` does not exist on disk
420
- - FAIL if any task file path does not match `tasks/task-R{N}-{SEQ}-<slug>.md` with two-digit `SEQ` (for example `tasks/task-R0-01-project-scaffolding.md`)
421
- - FAIL if a newly generated non-trivial spec lacks a `research.md` Evidence Summary with codebase scout result, external research result or skip rationale, selected decision, rejected alternatives, and downstream task/test implications.
422
- - FAIL if any requirement or NFR mapping uses non-numeric labels (`NFR-1`, `SEC-1`, etc.)
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.
434
+ - **Deterministic Gate (2 layers):** Run `node .claude/scripts/validate-spec-output.cjs specs/<feature>` (structural) AND `node .claude/scripts/spec-ground.cjs specs/<feature> [--root <work-context>]` (grounding — paths exist) after all fixes and before final output. Either script failing overrides any LLM checklist result and blocks `ready_for_implementation = true`.
435
+
436
+ ### Step 8.5: Finalization Audit (MANDATORY)
437
+ - 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).
438
+ - **Layer 1 — Structural:** run `node .claude/scripts/validate-spec-output.cjs specs/<feature>` and treat any non-zero exit as a blocking failure.
439
+ - **Layer 2 — Grounding (MANDATORY):** run `node .claude/scripts/spec-ground.cjs specs/<feature> [--root <work-context>]` and treat any non-zero exit as a blocking failure. This greps the real work tree to verify every `Related Files` path a task cites (Modify/Delete/Read) actually exists — or is Created by another task in the spec. Pass `--root` when the code lives apart from the spec (monorepo / sibling project). A spec that PASSES Layer 1 but FAILS Layer 2 is trolling phantom files and is NOT ready.
440
+
441
+ **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.
442
+
443
+ **Grounding-enforced (spec-ground.cjs — do not re-check by hand):** every Modify/Delete/Read path in any task's `Related Files` exists in the work tree or is Created earlier in the spec. Phantom paths hard-fail.
444
+
445
+ **Judgment-only audit (validator cannot see these — assert manually):**
446
+ - FAIL if a UI/app/runtime spec has multiple user-facing task outputs but no final integration/reachability task or section.
447
+ - 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).
448
+ - 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
449
  - FAIL if privacy/delete-data work lacks a single canonical deletion policy. The design MUST explicitly choose either:
430
450
  1. hard-delete with no re-registration lock, or
431
451
  2. privacy-preserving re-registration lock using a non-raw identifier (for example `email_hash` / `email_fingerprint`) with a retention period.
432
452
  Tasks and requirements must reuse the same policy verbatim; mixed policies are invalid.
433
- - FAIL if `validation.status = "completed"` but `timestamps.validation_done` / `timestamps.review_done`, `updated_at`, and report metadata are not synchronized with the final reviewed state.
434
- - If `validation_recommended = true` and `validation.status` is not `completed` (or an explicit accepted-risk state recorded by the user), `ready_for_implementation` MUST remain `false`
435
- - If `translation.enabled = true`, ensure `i18n/<language_code>/` exists and was re-synced after the final canonical write; the mirror itself is excluded from validation and never blocks `ready_for_implementation`.
436
- - Only after this audit passes may the system mark `progress.tasks = "done"` and `ready_for_implementation = true`
453
+ - FAIL if `validation.status = "completed"` but `timestamps.validation_done` / `review_done`, `updated_at`, and report metadata are not synchronized with the final reviewed state.
454
+ - If `validation_recommended = true` and `validation.status` is not `completed` (or an explicit user-accepted risk state), `ready_for_implementation` MUST remain `false`.
455
+ - 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`.
456
+ - Only after this audit passes may the system mark `progress.tasks = "done"` and `ready_for_implementation = true`.
437
457
 
438
- ### Step 10: Completion — Context Reminder (MANDATORY)
458
+ ### Step 9: Completion — Context Reminder (MANDATORY)
439
459
  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
460
 
441
461
  **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 +468,7 @@ After completing the spec, output a short summary of what was generated, then yo
448
468
  💡 Tip: Run /clear or start a new chat session before implementing to reduce planning context carryover.
449
469
  ```
450
470
 
451
- ### Step 10b: Paused Block (early stop)
471
+ ### Step 9b: Paused Block (early stop)
452
472
 
453
473
  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
474
 
@@ -583,26 +603,19 @@ The canonical surface is four flags. Bare-verb forms are kept only as silent bac
583
603
  - No over-engineering: if a function suffices, don't create a class
584
604
 
585
605
  ### Pre-Finalization Checklist
586
- Before finalizing any specification, assert all the following:
587
- - [ ] **scope_lock** initialized and respected throughout all phases
588
- - [ ] **Evidence Summary** exists in `research.md` with codebase scout, external research or skip rationale, selected decision, rejected alternatives, and task/test implications
589
- - [ ] **EARS format** applied to all acceptance criteria in requirements.md
590
- - [ ] **Numeric requirement IDs** assigned to every requirement
606
+ Before finalizing any specification, assert all the following.
607
+
608
+ **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.
609
+
610
+ **Judgment-only** (validator cannot see these — assert manually):
611
+ - [ ] **EARS format** applied to all acceptance criteria, with measurable thresholds (no "fast"/"safe"/"robust")
591
612
  - [ ] **Discovery mode** selected and recorded in spec.json.design_context
592
613
  - [ ] **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
614
+ - [ ] **Canonical Contracts & Invariants** filled for auth/transport/persistence/artifact-sensitive work, and inherited verbatim by every task that touches the contract
596
615
  - [ ] **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
616
  - [ ] **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
617
  - [ ] **Provider wording clean**: no stale vendor/provider strings outside allowed research context
605
- - [ ] **spec.json fully updated**: phase, current_phase, progress, timestamps, approvals, design_context
618
+ - [ ] **Validation decisions propagated** into implementation-facing sections, not only reports/Risk Assessment
606
619
 
607
620
  ## When TO Use
608
621
 
@@ -635,8 +648,7 @@ Before finalizing any specification, assert all the following:
635
648
  - `design-discovery-light.md` — Lightweight research workflow
636
649
  - `design-review.md` — Design review GO/NO-GO process
637
650
  - `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
651
+ - `tasks-generation.md` — Task generation rules (includes spike task rules + parallel analysis)
640
652
  - `task-scoring-rubric.md` — Task priority, split/merge, spike, dependency, and evidence-depth scoring
641
653
 
642
654
  ### References (`references/`)
@@ -645,7 +657,6 @@ Before finalizing any specification, assert all the following:
645
657
  - `scope-inquiry.md` — 5-Dimension Complexity Assessment (Semantic, Hypothesis, Gap, Risk/Cynefin, Blast Radius)
646
658
  - `research-strategy.md` — Research strategy (7 tools)
647
659
  - `codebase-analysis.md` — Codebase analysis (4 mandatory files)
648
- - `task-hydration.md` — Task files → Claude Tasks conversion
649
660
  - `review.md` — Spec review (auto-decides: red team 8 steps + validate 6 steps)
650
661
  - `archive-workflow.md` — Archive workflow (5 steps)
651
662
  - `translation-mirror.md` — Optional reference-only translation mirror (detection, layout, sync, fields)
@@ -121,8 +121,13 @@ Rules:
121
121
  3. Sort by severity: Critical → High → Medium
122
122
  4. Cap at 15 findings maximum
123
123
 
124
+ #### Step 5.5: Evidence Filter (MANDATORY — auto-reject before merit)
125
+ Before adjudicating, drop any finding whose `Location`/`Evidence` does not point at a concrete spot in the spec: a task/section name (`task-R2-01... §Steps`) or a verbatim quote of the offending text. A finding that only asserts a problem in the abstract ("this might race", "auth could be bypassed") **without citing where in the spec** is auto-`Reject (no evidence)` and is NOT counted toward the cap or shown to the user.
126
+
127
+ Rationale: this is the spec-level analogue of ck-plan's `file:line` evidence gate. It stops reviewers from inventing plausible-sounding flaws that aren't actually in the artifact. A real flaw can always be located; an imagined one cannot.
128
+
124
129
  #### Step 6: Adjudicate
125
- For each finding, evaluate and propose: **Accept** or **Reject** with rationale.
130
+ For each finding (that survived the Evidence Filter), evaluate and propose: **Accept** or **Reject** with rationale.
126
131
 
127
132
  #### Step 7: User Review
128
133
  Present via `AskUserQuestion`:
@@ -48,6 +48,17 @@ Assess how many other parts of the system are affected if this change breaks or
48
48
 
49
49
  > **Rule**: If Blast Radius = Critical Path, the spec MUST include rollback strategy and explicit test coverage requirements in the task files.
50
50
 
51
+ ### Complexity Smell Check (by numbers)
52
+
53
+ After the qualitative 5-Dimension pass, run a quick quantitative gut-check. These are YAGNI tripwires, not hard limits — when one trips, pause and justify or simplify before locking scope:
54
+
55
+ - **> 8 files touched** → challenge whether the same goal is reachable with fewer; flag accidental scope creep.
56
+ - **> 2 new classes / services / modules** → smell; justify each new abstraction (could an existing module absorb it?).
57
+ - **> 12 task files** → the spec is large; consider splitting by deliverable boundary into sibling specs (cross-spec `blockedBy`) rather than one mega-spec.
58
+ - **> 15 task files** → strong signal to split. A single mega-spec is slow to generate, prone to mid-run failure, and hard to review. Prefer 2-3 focused specs unless the work is genuinely one indivisible slice.
59
+
60
+ If any tripwire fires, surface it in the Scope Inquiry summary and let the user decide (Expand / Hold / Reduce / Split). Do not silently build the mega-version.
61
+
51
62
  ## Level Selection
52
63
 
53
64
  After completing the 5-Dimension Assessment, present via `AskUserQuestion`:
@@ -77,6 +88,7 @@ Scope Inquiry:
77
88
  - Gap Size: [Small/Medium/Large] — [reason]
78
89
  - Risk Level: [Clear/Complicated/Complex/Chaotic] — [reason]
79
90
  - Blast Radius: [Isolated/Moderate/Critical Path] — [affected modules]
91
+ - Complexity smells: [none | >8 files | >2 new services | >12 tasks → consider split | >15 tasks → split]
80
92
  - Minimum Change: [Essential gap vs Niceties]
81
- - User chose: [Expand / Hold / Reduce]
93
+ - User chose: [Expand / Hold / Reduce / Split]
82
94
  ```
@@ -192,6 +192,21 @@ Choose verification by task risk and touched surface. Do not force every task to
192
192
 
193
193
  `hapo:specs` writes the expected proof into each task. `hapo:develop` executes the task-local proof before marking the task done. `hapo:test` runs the broader system pass after implementation or for a requested feature scope.
194
194
 
195
+ ### Frontend Fidelity Rule (when a visual reference is provided)
196
+
197
+ If the user/spec provides ANY visual reference for a frontend task — design image, Figma frame, screenshot, mockup, brand palette, design tokens, or a style guide — the task MUST reproduce it faithfully, not approximate it. Concretely, the task file MUST:
198
+
199
+ 1. **Extract concrete values into the task** (do NOT paraphrase as "make it look nice"): exact colors (`#DAF1EE`), font family + sizes, spacing/radius/shadow when the reference shows them, and verbatim UI text/labels (especially non-English copy, e.g. `「投稿を削除しました」`).
200
+ 2. **State a `MUST: match <reference>` constraint** naming the exact reference (e.g. `image_4.png` / Figma node / `tokens.css`), so fidelity is a requirement, not a suggestion.
201
+ 3. **Prove fidelity in Evidence**: a visual/runtime check that compares the rendered UI against the named reference (screenshot diff or side-by-side inspection), plus accessibility/contrast if interactive.
202
+
203
+ Reuse vs new component:
204
+ - **Reuse** an existing component → reference it by path; it carries the design system's style (no need to restate pixels).
205
+ - **New** component (`Create`) WITH a reference → the task MUST cite the concrete tokens/values from the reference (this is the gap that lets new components drift from the design).
206
+ - **New** component WITHOUT any reference → derive from a named sibling component or explicitly flag it as an open design question; never invent un-grounded styling silently.
207
+
208
+ > Rationale: a spec that says "build the list screen" but omits the provided palette/text/spacing forces the implementer to guess, and the result drifts from what the user actually showed. When a reference exists, "looks roughly similar" is a failure — the task must carry the real values so the build matches the design.
209
+
195
210
  ## Task Hierarchy Rules
196
211
 
197
212
  ### Maximum 2 Levels
@@ -212,13 +227,15 @@ Choose verification by task risk and touched surface. Do not force every task to
212
227
  - No data dependency on other pending tasks
213
228
  - No shared file or resource contention
214
229
  - No prerequisite review/approval from another task
230
+ - Environment/setup work needed by the task is already satisfied or covered within the task itself
215
231
  - Validate that identified parallel tasks operate within separate boundaries defined in the Architecture Pattern & Boundary Map.
216
232
  - 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:
233
+ - Append `(P)` immediately after the task number for each parallel-capable task, kept **outside** the checkbox brackets:
218
234
  - Example: `- [ ] 2.1 (P) Build background worker`
219
235
  - Apply to both major tasks and sub-tasks when appropriate.
220
236
  - If sequential mode is requested, omit `(P)` markers entirely.
221
237
  - Group parallel tasks logically (same parent when possible) and highlight any ordering caveats in detail bullets.
238
+ - Do not mark container-only major tasks (no own actionable bullets) with `(P)` — evaluate parallelism at the sub-task level.
222
239
  - Explicitly call out dependencies that prevent `(P)` even when tasks look similar.
223
240
 
224
241
  ### 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:
@@ -7,23 +7,24 @@
7
7
 
8
8
  ### Requirement 1: {{REQUIREMENT_AREA_1}}
9
9
  <!-- Requirement headings MUST include a leading numeric ID only (for example: "Requirement 1: ...", "1. Overview", "2 Feature: ..."). Alphabetic IDs like "Requirement A" are not allowed. -->
10
+ <!-- Acceptance criteria use EXPLICIT literal IDs `R{N}.{M}` (R1.1, R1.2, ...), NOT a bare numbered list. This lets the validator verify per-criterion coverage (each R1.1 must be mapped by a task `_Requirements: 1.1_`). Do not switch to a bare `1. 2. 3.` list — that silently disables sub-criterion coverage. -->
10
11
  **Objective:** As a {{ROLE}}, I want {{CAPABILITY}}, so that {{BENEFIT}}
11
12
 
12
13
  #### Acceptance Criteria
13
- 1. When [event], the [system] shall [response/action]
14
- 2. If [trigger], then the [system] shall [response/action]
15
- 3. While [precondition], the [system] shall [response/action]
16
- 4. Where [feature is included], the [system] shall [response/action]
17
- 5. The [system] shall [response/action]
14
+ - **R1.1** When [event], the [system] shall [response/action]
15
+ - **R1.2** If [trigger], then the [system] shall [response/action]
16
+ - **R1.3** While [precondition], the [system] shall [response/action]
17
+ - **R1.4** Where [feature is included], the [system] shall [response/action]
18
+ - **R1.5** The [system] shall [response/action]
18
19
 
19
20
  ### Requirement 2: {{REQUIREMENT_AREA_2}}
20
21
  **Objective:** As a {{ROLE}}, I want {{CAPABILITY}}, so that {{BENEFIT}}
21
22
 
22
23
  #### Acceptance Criteria
23
- 1. When [event], the [system] shall [response/action]
24
- 2. When [event] and [condition], the [system] shall [response/action]
24
+ - **R2.1** When [event], the [system] shall [response/action]
25
+ - **R2.2** When [event] and [condition], the [system] shall [response/action]
25
26
 
26
- <!-- Additional requirements follow the same pattern -->
27
+ <!-- Additional requirements follow the same pattern, with literal R{N}.{M} IDs -->
27
28
 
28
29
  ## Non-Functional Requirements
29
30
 
@@ -33,19 +34,19 @@
33
34
  **Objective:** As a system owner, I want predictable performance characteristics, so that the feature remains usable under expected load.
34
35
 
35
36
  #### Acceptance Criteria
36
- {{NEXT_REQ_NUMBER}}.1 The [system] shall [measurable performance metric, e.g. "respond within 500ms"]
37
- {{NEXT_REQ_NUMBER}}.2 The [system] shall [measurable scale metric, e.g. "support 100 concurrent users"]
37
+ - **R{{NEXT_REQ_NUMBER}}.1** The [system] shall [measurable performance metric, e.g. "respond within 500ms"]
38
+ - **R{{NEXT_REQ_NUMBER}}.2** The [system] shall [measurable scale metric, e.g. "support 100 concurrent users"]
38
39
 
39
40
  ### Requirement {{NEXT_REQ_NUMBER_PLUS_ONE}}: Security & Privacy
40
41
  **Objective:** As a security/compliance stakeholder, I want the feature to protect sensitive data and enforce access boundaries, so that the system is safe to ship.
41
42
 
42
43
  #### Acceptance Criteria
43
- {{NEXT_REQ_NUMBER_PLUS_ONE}}.1 The [system] shall [measurable security behavior, e.g. "encrypt data at rest using AES-256"]
44
- {{NEXT_REQ_NUMBER_PLUS_ONE}}.2 If [unauthorized or invalid condition], the [system] shall [deny or recover with explicit behavior]
44
+ - **R{{NEXT_REQ_NUMBER_PLUS_ONE}}.1** The [system] shall [measurable security behavior, e.g. "encrypt data at rest using AES-256"]
45
+ - **R{{NEXT_REQ_NUMBER_PLUS_ONE}}.2** If [unauthorized or invalid condition], the [system] shall [deny or recover with explicit behavior]
45
46
 
46
47
  ### Requirement {{NEXT_REQ_NUMBER_PLUS_TWO}}: Reliability & Availability
47
48
  **Objective:** As an operator, I want predictable failure handling, so that incidents remain diagnosable and recoverable.
48
49
 
49
50
  #### Acceptance Criteria
50
- {{NEXT_REQ_NUMBER_PLUS_TWO}}.1 If [failure condition], the [system] shall [recovery behavior]
51
- {{NEXT_REQ_NUMBER_PLUS_TWO}}.2 The [system] shall [durability / retry / fallback expectation]
51
+ - **R{{NEXT_REQ_NUMBER_PLUS_TWO}}.1** If [failure condition], the [system] shall [recovery behavior]
52
+ - **R{{NEXT_REQ_NUMBER_PLUS_TWO}}.2** The [system] shall [durability / retry / fallback expectation]
@@ -60,6 +60,7 @@
60
60
  "design_context": {
61
61
  "discovery_mode": null,
62
62
  "discovery_reason": null,
63
+ "execution_tier": "standard",
63
64
  "validation_recommended": false
64
65
  },
65
66
  "validation": {
@@ -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.