From cece782b09bc26d1688f55ad58dfff6b0e73b3c7 Mon Sep 17 00:00:00 2001 From: hapo-nghialuu Date: Sun, 21 Jun 2026 13:03:53 +0700 Subject: [PATCH 01/13] feat(specs): add Layer-2 grounding check (spec-ground.cjs) New deterministic grounding script greps the real work tree to verify every Related Files path a task cites (Modify/Delete/Read) actually exists, or is Created earlier in the same spec (intra-spec resolution). Closes the gap where validate-spec-output.cjs checks spec SHAPE but is blind to phantom file paths. Active-grep, not opt-in (field test showed opt-in checks get dodged). Wired into SKILL.md Step 8.5 as Layer 2 + the --validate deterministic gate. Supports --root for monorepo/sibling-project specs. Verified on real spec post_list_screen: 37 paths GROUNDED; injected ghost path -> FAIL; ghost path Created by another task -> GROUNDED (intra-spec). --- .../spec/src/claude/scripts/spec-ground.cjs | 146 ++++++++++++++++++ .../spec/src/claude/skills/specs/SKILL.md | 7 +- 2 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 packages/spec/src/claude/scripts/spec-ground.cjs diff --git a/packages/spec/src/claude/scripts/spec-ground.cjs b/packages/spec/src/claude/scripts/spec-ground.cjs new file mode 100644 index 0000000..522bc19 --- /dev/null +++ b/packages/spec/src/claude/scripts/spec-ground.cjs @@ -0,0 +1,146 @@ +#!/usr/bin/env node +/** + * CafeKit spec GROUNDING checker (Specs v2, Layer 2). + * + * The deterministic validator (`validate-spec-output.cjs`) checks spec SHAPE + * (headings, registry sync, coverage). It is blind to whether the file paths a + * task cites actually EXIST. This script closes that gap: it parses every task's + * `Related Files` table and verifies, against the real work tree, that each + * Modify/Delete/Read path exists — unless another task in the same spec Creates + * it first (intra-spec resolution). + * + * Why active-grep instead of opt-in: field tests showed opt-in checks get + * skipped by the author. Grounding runs unconditionally over whatever paths the + * tasks already declare, so it cannot be dodged by choosing a different format. + * + * Usage: + * node spec-ground.cjs [--root ] + * + * : path to specs/ (relative or absolute) + * --root : work tree the paths resolve against. Default: two levels up from + * specDir (…//specs/ -> ). Override for + * monorepos where the spec lives apart from the code. + * + * Exit: 0 = GROUNDED, 1 = FAIL (missing path), 2 = usage error. + */ + +const fs = require('fs'); +const path = require('path'); + +function usage() { + console.error('Usage: node spec-ground.cjs [--root ]'); +} + +function parseArgs(argv) { + const args = { specDir: null, root: null }; + for (let i = 2; i < argv.length; i++) { + if (argv[i] === '--root') args.root = argv[++i]; + else if (!args.specDir) args.specDir = argv[i]; + } + return args; +} + +function listTaskFiles(specDir) { + const tasksDir = path.join(specDir, 'tasks'); + if (!fs.existsSync(tasksDir)) return []; + return fs + .readdirSync(tasksDir, { withFileTypes: true }) + .filter((e) => e.isFile() && e.name.endsWith('.md')) + .map((e) => path.join(tasksDir, e.name)) + .sort(); +} + +/** + * Pull rows out of a markdown `## Related Files` table. Returns + * [{ path, action, taskFile }]. Tables look like: + * | Path | Action | Description | + * | `front/x.vue` | Modify | ... | + */ +function parseRelatedFiles(content, taskFile) { + const rows = []; + const lines = content.split('\n'); + let inSection = false; + for (const line of lines) { + if (/^##+\s+Related Files\s*$/i.test(line)) { inSection = true; continue; } + if (inSection && /^##+\s+/.test(line)) break; // next heading ends section + if (!inSection) continue; + if (!line.trim().startsWith('|')) continue; + + const cells = line.split('|').map((c) => c.trim()); + // cells[0] is '' (leading pipe). path=cells[1], action=cells[2]. + const rawPath = (cells[1] || '').replace(/`/g, '').trim(); + const action = (cells[2] || '').toLowerCase().trim(); + if (!rawPath || rawPath === 'path') continue; // header row + if (/^-+$/.test(rawPath)) continue; // separator row + if (!action || !/(create|modify|delete|read)/.test(action)) continue; + rows.push({ path: rawPath, action, taskFile: path.basename(taskFile) }); + } + return rows; +} + +/** Lenient existence check with minimal glob handling. */ +function pathExists(root, p) { + // Glob-ish path (e.g. `assets/{x,instagram,facebook}.svg` or `dir/*.ts`): + // verify the parent directory exists rather than the literal name. + if (/[{*]/.test(p)) { + const parent = path.dirname(p.replace(/\{.*$/, '').replace(/\*.*$/, '')); + return fs.existsSync(path.join(root, parent)); + } + return fs.existsSync(path.join(root, p)); +} + +function main() { + const { specDir: specInput, root: rootInput } = parseArgs(process.argv); + if (!specInput) { usage(); process.exit(2); } + + const specDir = path.resolve(process.cwd(), specInput); + if (!fs.existsSync(specDir)) { + console.error(`FAIL ${specInput}\n- spec directory does not exist`); + process.exit(1); + } + // Default work-context: two levels up (…//specs/). + const root = rootInput + ? path.resolve(process.cwd(), rootInput) + : path.resolve(specDir, '..', '..'); + + const taskFiles = listTaskFiles(specDir); + const allRows = []; + for (const tf of taskFiles) { + allRows.push(...parseRelatedFiles(fs.readFileSync(tf, 'utf8'), tf)); + } + + // Paths that will exist because a task Creates them (intra-spec resolution). + const createdPaths = new Set( + allRows.filter((r) => r.action.includes('create')).map((r) => r.path), + ); + + const errors = []; + const warnings = []; + let checked = 0; + + for (const row of allRows) { + const onDisk = pathExists(root, row.path); + if (row.action.includes('create')) { + if (onDisk) warnings.push(`${row.taskFile}: Create path already exists (will overwrite): ${row.path}`); + continue; + } + // modify / delete / read must exist OR be created by another task in-spec. + checked++; + if (!onDisk && !createdPaths.has(row.path)) { + errors.push(`${row.taskFile}: ${row.action} path not found in work tree: ${row.path}`); + } + } + + for (const w of warnings) console.warn(`[WARN] ${w}`); + + const rel = path.relative(process.cwd(), specDir) || specDir; + if (errors.length > 0) { + console.error(`FAIL ${rel} (root: ${root})`); + for (const e of errors) console.error(`- ${e}`); + console.error(`\n${errors.length} missing / ${checked} checked path(s).`); + process.exit(1); + } + console.log(`GROUNDED ${rel} (${checked} path(s) verified against ${root})`); +} + +main(); diff --git a/packages/spec/src/claude/skills/specs/SKILL.md b/packages/spec/src/claude/skills/specs/SKILL.md index ae70949..be4eb10 100644 --- a/packages/spec/src/claude/skills/specs/SKILL.md +++ b/packages/spec/src/claude/skills/specs/SKILL.md @@ -398,14 +398,17 @@ Load: `references/review.md` + `rules/design-review.md` - **PROHIBITION:** The system MUST NOT skip Red Team because of a prior code-auditor review. Code review ≠ Spec review. - **PROHIBITION:** The system MUST NOT create `.ts`, `.js`, `.py` or any implementation files during validation. Spec-only outputs. - **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. -- **Deterministic Gate:** Run `node .claude/scripts/validate-spec-output.cjs specs/` after all fixes and before final output. Script failure overrides any LLM checklist result and blocks `ready_for_implementation = true`. +- **Deterministic Gate (2 layers):** Run `node .claude/scripts/validate-spec-output.cjs specs/` (structural) AND `node .claude/scripts/spec-ground.cjs specs/ [--root ]` (grounding — paths exist) after all fixes and before final output. Either script failing overrides any LLM checklist result and blocks `ready_for_implementation = true`. ### Step 8.5: Finalization Audit (MANDATORY) - 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). -- Run `node .claude/scripts/validate-spec-output.cjs specs/` and treat any non-zero exit as a blocking failure. +- **Layer 1 — Structural:** run `node .claude/scripts/validate-spec-output.cjs specs/` and treat any non-zero exit as a blocking failure. +- **Layer 2 — Grounding (MANDATORY):** run `node .claude/scripts/spec-ground.cjs specs/ [--root ]` 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. **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}-.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. +**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. + **Judgment-only audit (validator cannot see these — assert manually):** - FAIL if a UI/app/runtime spec has multiple user-facing task outputs but no final integration/reachability task or section. - 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). From 540cb84bf6117414e224f184ea73296ef755fa72 Mon Sep 17 00:00:00 2001 From: hapo-nghialuu Date: Sun, 21 Jun 2026 13:06:06 +0700 Subject: [PATCH 02/13] feat(specs): add auto-scaling Execution Tier (Light/Standard/Deep) Small specs no longer pay full-pipeline overhead. Tier is picked right after the 5-Dimension assessment and recorded in design_context.execution_tier: - Light (Clear + Isolated + <=2 tasks): skip external research, minimal discovery, Validate-only. - Standard (default): targeted research, light discovery. - Deep (Complex/critical-path/security/5+ tasks): full research + mandatory Red-Team -> Validate. Quality floor (scope_lock, EARS, Layer 1 structural + Layer 2 grounding) never skips at any tier. Auth/payment/migration/schema/privacy force Deep regardless of size. Tier only scales research/discovery/review depth, never the output contract or DoCT. --- packages/spec/src/claude/skills/specs/SKILL.md | 14 ++++++++++++++ .../claude/skills/specs/templates/spec-state.json | 1 + 2 files changed, 15 insertions(+) diff --git a/packages/spec/src/claude/skills/specs/SKILL.md b/packages/spec/src/claude/skills/specs/SKILL.md index be4eb10..9cc5d8c 100644 --- a/packages/spec/src/claude/skills/specs/SKILL.md +++ b/packages/spec/src/claude/skills/specs/SKILL.md @@ -261,6 +261,20 @@ Load: `references/scope-inquiry.md` - User picks scope level: Expand / Hold / Reduce - **Skip if:** trivial task (< 20 words, 1 file, user says "just do it") +#### Execution Tier (auto-scale — set right after the 5-Dimension assessment) +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`. + +| Tier | Trigger | Research (Step 5 external) | Discovery (Step 6) | Red-Team (Step 8) | Always runs | +|---|---|---|---|---|---| +| **Light** | Cynefin Clear + Blast Isolated + likely ≤2 tasks | skip (record skip rationale) | minimal | skip → Validate-only | scope_lock, EARS, **Layer 1 + Layer 2 grounding** | +| **Standard** | default (Complicated / Moderate blast / 3-4 tasks) | targeted | light | per Step 8 auto-decision | all of the above | +| **Deep** | Complex/Critical-path / security-migration / 5+ tasks | full (researchers + per-area scout) | full | Red-Team → Validate (mandatory) | all of the above | + +Rules: +- Grounding (Layer 2) + structural validator (Layer 1) + scope_lock **never skip**, any tier — they are the quality floor, not depth knobs. +- 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. +- Tier only changes *research/discovery/review depth*; it never changes the Hard Output Contract or DoCT. + ### Step 4: Init - Check for duplicate slugs in `specs/` via Glob - Create directory `specs//` diff --git a/packages/spec/src/claude/skills/specs/templates/spec-state.json b/packages/spec/src/claude/skills/specs/templates/spec-state.json index 5e6a11a..d778e0c 100644 --- a/packages/spec/src/claude/skills/specs/templates/spec-state.json +++ b/packages/spec/src/claude/skills/specs/templates/spec-state.json @@ -60,6 +60,7 @@ "design_context": { "discovery_mode": null, "discovery_reason": null, + "execution_tier": "standard", "validation_recommended": false }, "validation": { From 177828fd4d0708e567ce41a145f0ffd3048fce5a Mon Sep 17 00:00:00 2001 From: hapo-nghialuu Date: Sun, 21 Jun 2026 13:12:15 +0700 Subject: [PATCH 03/13] feat(specs): add spec-scaffold.cjs to cut output tokens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Generates spec.json + doc templates + task stubs from canonical templates so the model Edit-fills placeholders instead of hand-Writing whole files (the dominant output-token cost — a 16-task spec emitted ~935K output). Two modes: - full: new spec (spec.json + 3 docs + task stubs), refuses to overwrite. - --tasks-only: merge task stubs + task_files/task_registry into an existing spec.json without touching docs; never overwrites an already-filled task. Wired into SKILL.md Step 7 (--tasks-only) as the standard way to materialize tasks. Verified: full create, tasks-only merge, no-overwrite-of-filled-task. --- .../spec/src/claude/scripts/spec-scaffold.cjs | 187 ++++++++++++++++++ .../spec/src/claude/skills/specs/SKILL.md | 7 +- 2 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 packages/spec/src/claude/scripts/spec-scaffold.cjs diff --git a/packages/spec/src/claude/scripts/spec-scaffold.cjs b/packages/spec/src/claude/scripts/spec-scaffold.cjs new file mode 100644 index 0000000..b7d0d9e --- /dev/null +++ b/packages/spec/src/claude/scripts/spec-scaffold.cjs @@ -0,0 +1,187 @@ +#!/usr/bin/env node +/** + * CafeKit spec SCAFFOLD generator (Specs v2 — output-cost reducer). + * + * Field tests showed the dominant cost of a spec run is OUTPUT tokens: the + * model hand-Writes spec.json + every task file from scratch (a 16-task spec + * emitted ~935K output tokens). This script does the mechanical scaffolding so + * the model only has to Edit-fill content, not Write whole files. + * + * It creates spec.json (with task_files + task_registry pre-populated) and one + * stub per task from the canonical templates, leaving the `{{...}}` placeholders + * for the model to fill. It NEVER overwrites an existing spec directory. + * + * Usage: + * node spec-scaffold.cjs --tasks "R0-01-slug,R1-01-slug,..." \ + * [--lang en] [--title "..."] [--specs-root specs] + * + * Exit: 0 = scaffolded, 2 = usage/precondition error. + */ + +const fs = require('fs'); +const path = require('path'); + +const TEMPLATES = path.join(__dirname, '..', 'skills', 'specs', 'templates'); +const TASK_ID_RE = /^R(\d+)-(\d+)-([a-z0-9]+(?:-[a-z0-9]+)*)$/; + +function usage() { + console.error('Usage: node spec-scaffold.cjs --tasks "R0-01-slug,R1-01-slug" [--lang en] [--title "..."] [--specs-root specs]'); +} + +function parseArgs(argv) { + const a = { feature: null, tasks: null, lang: 'en', title: null, specsRoot: 'specs', tasksOnly: false }; + for (let i = 2; i < argv.length; i++) { + const v = argv[i]; + if (v === '--tasks') a.tasks = argv[++i]; + else if (v === '--lang') a.lang = argv[++i]; + else if (v === '--title') a.title = argv[++i]; + else if (v === '--specs-root') a.specsRoot = argv[++i]; + else if (v === '--tasks-only') a.tasksOnly = true; + else if (!a.feature) a.feature = v; + } + return a; +} + +function readTemplate(name) { + const p = path.join(TEMPLATES, name); + if (!fs.existsSync(p)) { + console.error(`precondition: template not found: ${p}`); + process.exit(2); + } + return fs.readFileSync(p, 'utf8'); +} + +function titleFromSlug(slug) { + const s = slug.replace(/-/g, ' '); + return s.charAt(0).toUpperCase() + s.slice(1); +} + +function registryEntry(t) { + return { + id: `R${t.req}-${t.seq}`, + title: titleFromSlug(t.slug), + status: 'pending', + dependencies: [], + blocker: null, + started_at: null, + completed_at: null, + last_updated_at: null, + }; +} + +function fillTask(taskTpl, t, feature) { + return taskTpl + .replace(/\{\{REQ_NUMBER\}\}/g, t.req) + .replace(/\{\{SEQ\}\}/g, t.seq) + .replace(/\{\{TITLE\}\}/g, titleFromSlug(t.slug)) + .replace(/\{\{FEATURE_NAME\}\}/g, feature) + .replace(/\{\{PRIORITY\}\}/g, 'P2') + .replace(/\{\{EFFORT\}\}/g, 'TBD') + .replace(/\{\{DEPENDENCIES\}\}/g, 'none'); +} + +function nowIso() { + // Local ISO with offset, e.g. 2026-06-21T12:30:00+07:00 + const d = new Date(); + const pad = (n) => String(n).padStart(2, '0'); + const off = -d.getTimezoneOffset(); + const sign = off >= 0 ? '+' : '-'; + const oh = pad(Math.floor(Math.abs(off) / 60)); + const om = pad(Math.abs(off) % 60); + return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}${sign}${oh}:${om}`; +} + +function main() { + const args = parseArgs(process.argv); + if (!args.feature || !args.tasks) { usage(); process.exit(2); } + + const ids = args.tasks.split(',').map((s) => s.trim()).filter(Boolean); + const parsed = []; + for (const id of ids) { + const m = id.match(TASK_ID_RE); + if (!m) { + console.error(`precondition: invalid task id "${id}" (want R{N}-{SEQ}-, SEQ 2 digits)`); + process.exit(2); + } + parsed.push({ raw: id, req: m[1], seq: m[2], slug: m[3], file: `tasks/task-${id}.md` }); + } + + const specDir = path.resolve(process.cwd(), args.specsRoot, args.feature); + const taskTpl = readTemplate('task.md'); + const ts = nowIso(); + const created = []; + + // --- --tasks-only: add task stubs + merge registry into an existing spec --- + if (args.tasksOnly) { + const specPath = path.join(specDir, 'spec.json'); + if (!fs.existsSync(specPath)) { + console.error(`precondition: --tasks-only needs an existing spec.json at ${specPath}`); + process.exit(2); + } + const spec = JSON.parse(fs.readFileSync(specPath, 'utf8')); + spec.task_files = spec.task_files || []; + spec.task_registry = spec.task_registry || {}; + fs.mkdirSync(path.join(specDir, 'tasks'), { recursive: true }); + for (const t of parsed) { + const abs = path.join(specDir, t.file); + if (fs.existsSync(abs)) continue; // never overwrite an already-filled task + fs.writeFileSync(abs, fillTask(taskTpl, t, args.feature)); + if (!spec.task_files.includes(t.file)) spec.task_files.push(t.file); + spec.task_registry[t.file] = registryEntry(t); + created.push(t.file); + } + spec.task_files.sort(); + spec.updated_at = ts; + fs.writeFileSync(specPath, JSON.stringify(spec, null, 2) + '\n'); + const relTo = path.relative(process.cwd(), specDir) || specDir; + console.log(`SCAFFOLDED (tasks-only) ${relTo}`); + console.log(`- ${created.length} new task stub(s); task_files + task_registry merged.`); + console.log(`- NEXT: Edit-fill {{...}} in each stub; set dependencies; run validator + spec-ground.`); + return; + } + + if (fs.existsSync(specDir)) { + console.error(`precondition: spec dir already exists, refusing to overwrite: ${specDir}`); + process.exit(2); + } + + fs.mkdirSync(path.join(specDir, 'tasks'), { recursive: true }); + fs.mkdirSync(path.join(specDir, 'reports'), { recursive: true }); + + // --- spec.json (from spec-state.json template) --- + const spec = JSON.parse(readTemplate('spec-state.json')); + spec.feature_name = args.feature; + spec.created_at = ts; + spec.updated_at = ts; + spec.language = args.lang; + spec.timestamps = spec.timestamps || {}; + spec.timestamps.init = ts; + spec.scope_lock = spec.scope_lock || {}; + spec.scope_lock.source = args.title || `{{PROJECT_DESCRIPTION}}`; + spec.task_files = parsed.map((t) => t.file); + spec.task_registry = {}; + for (const t of parsed) spec.task_registry[t.file] = registryEntry(t); + fs.writeFileSync(path.join(specDir, 'spec.json'), JSON.stringify(spec, null, 2) + '\n'); + created.push('spec.json'); + + // --- doc templates (placeholders left for the model to fill) --- + for (const [tpl, out] of [['requirements.md', 'requirements.md'], ['research.md', 'research.md'], ['design.md', 'design.md']]) { + const body = readTemplate(tpl).replace(/\{\{FEATURE_NAME\}\}/g, args.feature); + fs.writeFileSync(path.join(specDir, out), body); + created.push(out); + } + + // --- task stubs (fill the cheap placeholders; leave the rest) --- + for (const t of parsed) { + fs.writeFileSync(path.join(specDir, t.file), fillTask(taskTpl, t, args.feature)); + created.push(t.file); + } + + const rel = path.relative(process.cwd(), specDir) || specDir; + console.log(`SCAFFOLDED ${rel}`); + console.log(`- ${created.length} files created: spec.json + 3 docs + ${parsed.length} task stub(s)`); + console.log(`- task_files + task_registry pre-populated (${parsed.length} entries, all pending)`); + console.log(`- NEXT: Edit-fill the {{...}} placeholders (do NOT leave any); set dependencies in task_registry; run validator + spec-ground before ready.`); +} + +main(); diff --git a/packages/spec/src/claude/skills/specs/SKILL.md b/packages/spec/src/claude/skills/specs/SKILL.md index 9cc5d8c..34c1b0e 100644 --- a/packages/spec/src/claude/skills/specs/SKILL.md +++ b/packages/spec/src/claude/skills/specs/SKILL.md @@ -332,8 +332,11 @@ Rules: - Load `rules/phase-decision-matrix.md` before generating task files. Treat "phase" as an implementation slice/task cluster, not a `phase-XX.md` artifact. - Load `rules/task-scoring-rubric.md` for every candidate task to decide priority, split/merge, spike needs, dependencies, parallel eligibility, and evidence depth. - Load `references/ask-user-question-gates.md`; if scoring reveals unapproved scope expansion or an unresolved user-owned choice, pause before writing task files. -- Each task file follows template `templates/task.md` -- `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. +- **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: + `node .claude/scripts/spec-scaffold.cjs --tasks "R0-01-slug,R1-01-slug,..." --tasks-only` + 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. +- Each task file follows template `templates/task.md` (scaffold already applies it). **Leave NO `{{...}}` placeholder unfilled** — a stub with placeholders is an incomplete task. +- `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. - 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`. - 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. - 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. From 5b69f0de8298a3f4c512d6f95fbe63033ea02aba Mon Sep 17 00:00:00 2001 From: hapo-nghialuu Date: Sun, 21 Jun 2026 13:15:11 +0700 Subject: [PATCH 04/13] feat(specs): evidence-gated red-team + DoCT quality bar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit review.md: add Step 5.5 Evidence Filter — red-team findings whose Location/ Evidence does not cite a concrete task/section or verbatim quote are auto- rejected before adjudication (spec-level analogue of ck-plan's file:line gate). Stops reviewers inventing flaws not actually in the artifact. SKILL.md: add Definition of a Complete Task (DoCT) — the explicit quality bar mapping each completeness element (real paths, contract, measurable acceptance, real evidence commands, reachability, requirement mapping) to the mechanism that enforces it (Layer 1 validator, Layer 2 grounding, or reviewer judgment). --- packages/spec/src/claude/skills/specs/SKILL.md | 14 ++++++++++++++ .../src/claude/skills/specs/references/review.md | 7 ++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/spec/src/claude/skills/specs/SKILL.md b/packages/spec/src/claude/skills/specs/SKILL.md index 34c1b0e..391de62 100644 --- a/packages/spec/src/claude/skills/specs/SKILL.md +++ b/packages/spec/src/claude/skills/specs/SKILL.md @@ -403,6 +403,20 @@ Each task file MUST be **self-contained and implementation-ready** — detailed **FORBIDDEN:** Task files with only vague checkboxes and no exact files, requirements, or evidence. Compact is good; vague is invalid. +#### Definition of a Complete Task (DoCT) — the quality bar +A task is "complete enough to implement without guessing" only when ALL hold. Each item is enforced by a named mechanism, not goodwill: + +| DoCT element | Enforced by | +|---|---| +| **Related Files** name exact real paths (Create/Modify/Delete) | Layer 2 grounding (`spec-ground.cjs`) — phantom path fails | +| **Contract** (API/DB/event shape) stated concretely | Layer 1 contract-drift check (``) | +| **Acceptance** measurable (no "fast/nice/safe" without a threshold) | EARS rule + reviewer judgment | +| **Evidence** uses commands that exist in the project (`package.json`) | Author + grounding spirit; never invent test commands | +| **Reachability** names a real entrypoint/caller | `Runtime reachability verification` (Layer 1 presence) + judgment | +| **Requirements mapping** present (`_Requirements: x.y_`) | Layer 1 coverage check | + +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. + ### Step 8: Validation Review (Optional) Load: `references/review.md` + `rules/design-review.md` - 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. diff --git a/packages/spec/src/claude/skills/specs/references/review.md b/packages/spec/src/claude/skills/specs/references/review.md index 1f8a169..6ae1419 100644 --- a/packages/spec/src/claude/skills/specs/references/review.md +++ b/packages/spec/src/claude/skills/specs/references/review.md @@ -121,8 +121,13 @@ Rules: 3. Sort by severity: Critical → High → Medium 4. Cap at 15 findings maximum +#### Step 5.5: Evidence Filter (MANDATORY — auto-reject before merit) +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. + +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. + #### Step 6: Adjudicate -For each finding, evaluate and propose: **Accept** or **Reject** with rationale. +For each finding (that survived the Evidence Filter), evaluate and propose: **Accept** or **Reject** with rationale. #### Step 7: User Review Present via `AskUserQuestion`: From 0107003af2212aeeacd4c00e84a96664f8b1201f Mon Sep 17 00:00:00 2001 From: hapo-nghialuu Date: Sun, 21 Jun 2026 13:18:14 +0700 Subject: [PATCH 05/13] feat(specs): wake P1d by making literal R{N}.{M} the default requirement format Field test showed the model dodged P1d sub-criterion coverage by writing a bare numbered list (the opt-in literal format that triggers P1d). Fix the incentive at the source: requirements.md template now uses explicit `- **R1.1** ...` literal IDs (functional + NFR), and SKILL.md Step 5 mandates them for non-trivial specs. Now following the template naturally activates per-criterion coverage instead of silently disabling it. #3 contract markers kept (already routed at Step 6); both opt-in checks are now easy to activate rather than dead. --- .../spec/src/claude/skills/specs/SKILL.md | 2 +- .../skills/specs/templates/requirements.md | 29 ++++++++++--------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/packages/spec/src/claude/skills/specs/SKILL.md b/packages/spec/src/claude/skills/specs/SKILL.md index 391de62..3d5a0ff 100644 --- a/packages/spec/src/claude/skills/specs/SKILL.md +++ b/packages/spec/src/claude/skills/specs/SKILL.md @@ -300,7 +300,7 @@ Rules: - External/current research must prefer official docs, standards, primary sources, or maintained upstream references. Record source links and the date/context of the finding. - 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. - 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. -- Write requirements in **EARS** format (see `rules/ears-format.md`) +- 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. - **Feasibility Check:** Cross-check each requirement against known technical constraints from `research.md`. - Each requirement gets a unique numeric ID - **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). diff --git a/packages/spec/src/claude/skills/specs/templates/requirements.md b/packages/spec/src/claude/skills/specs/templates/requirements.md index 2c13161..18eb08b 100644 --- a/packages/spec/src/claude/skills/specs/templates/requirements.md +++ b/packages/spec/src/claude/skills/specs/templates/requirements.md @@ -7,23 +7,24 @@ ### Requirement 1: {{REQUIREMENT_AREA_1}} + **Objective:** As a {{ROLE}}, I want {{CAPABILITY}}, so that {{BENEFIT}} #### Acceptance Criteria -1. When [event], the [system] shall [response/action] -2. If [trigger], then the [system] shall [response/action] -3. While [precondition], the [system] shall [response/action] -4. Where [feature is included], the [system] shall [response/action] -5. The [system] shall [response/action] +- **R1.1** When [event], the [system] shall [response/action] +- **R1.2** If [trigger], then the [system] shall [response/action] +- **R1.3** While [precondition], the [system] shall [response/action] +- **R1.4** Where [feature is included], the [system] shall [response/action] +- **R1.5** The [system] shall [response/action] ### Requirement 2: {{REQUIREMENT_AREA_2}} **Objective:** As a {{ROLE}}, I want {{CAPABILITY}}, so that {{BENEFIT}} #### Acceptance Criteria -1. When [event], the [system] shall [response/action] -2. When [event] and [condition], the [system] shall [response/action] +- **R2.1** When [event], the [system] shall [response/action] +- **R2.2** When [event] and [condition], the [system] shall [response/action] - + ## Non-Functional Requirements @@ -33,19 +34,19 @@ **Objective:** As a system owner, I want predictable performance characteristics, so that the feature remains usable under expected load. #### Acceptance Criteria -{{NEXT_REQ_NUMBER}}.1 The [system] shall [measurable performance metric, e.g. "respond within 500ms"] -{{NEXT_REQ_NUMBER}}.2 The [system] shall [measurable scale metric, e.g. "support 100 concurrent users"] +- **R{{NEXT_REQ_NUMBER}}.1** The [system] shall [measurable performance metric, e.g. "respond within 500ms"] +- **R{{NEXT_REQ_NUMBER}}.2** The [system] shall [measurable scale metric, e.g. "support 100 concurrent users"] ### Requirement {{NEXT_REQ_NUMBER_PLUS_ONE}}: Security & Privacy **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. #### Acceptance Criteria -{{NEXT_REQ_NUMBER_PLUS_ONE}}.1 The [system] shall [measurable security behavior, e.g. "encrypt data at rest using AES-256"] -{{NEXT_REQ_NUMBER_PLUS_ONE}}.2 If [unauthorized or invalid condition], the [system] shall [deny or recover with explicit behavior] +- **R{{NEXT_REQ_NUMBER_PLUS_ONE}}.1** The [system] shall [measurable security behavior, e.g. "encrypt data at rest using AES-256"] +- **R{{NEXT_REQ_NUMBER_PLUS_ONE}}.2** If [unauthorized or invalid condition], the [system] shall [deny or recover with explicit behavior] ### Requirement {{NEXT_REQ_NUMBER_PLUS_TWO}}: Reliability & Availability **Objective:** As an operator, I want predictable failure handling, so that incidents remain diagnosable and recoverable. #### Acceptance Criteria -{{NEXT_REQ_NUMBER_PLUS_TWO}}.1 If [failure condition], the [system] shall [recovery behavior] -{{NEXT_REQ_NUMBER_PLUS_TWO}}.2 The [system] shall [durability / retry / fallback expectation] +- **R{{NEXT_REQ_NUMBER_PLUS_TWO}}.1** If [failure condition], the [system] shall [recovery behavior] +- **R{{NEXT_REQ_NUMBER_PLUS_TWO}}.2** The [system] shall [durability / retry / fallback expectation] From 628c715842f8bf8d657fafaf288dab97b04404c9 Mon Sep 17 00:00:00 2001 From: hapo-nghialuu Date: Sun, 21 Jun 2026 13:20:38 +0700 Subject: [PATCH 06/13] docs(changelog): document Specs v2 (grounding, scaffold, tiers, evidence-gate, DoCT) --- docs/project-changelog.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/project-changelog.md b/docs/project-changelog.md index ce7a2a4..2d1b32a 100644 --- a/docs/project-changelog.md +++ b/docs/project-changelog.md @@ -5,6 +5,16 @@ All notable changes to CafeKit are documented here, following ## [Unreleased] +### Added — Specs v2 (quality + grounding + output-cost) +- **Layer 2 grounding (`spec-ground.cjs`)**: new deterministic check that greps the real work tree to verify every `Related Files` path a task cites (Modify/Delete/Read) exists, or is Created earlier in the spec. Closes the gap where the structural validator checks spec *shape* but is blind to phantom file paths. Active-grep (not opt-in). Wired into Step 8.5 + the `--validate` gate. `--root` for monorepo/sibling-project specs. +- **Spec scaffolding (`spec-scaffold.cjs`)**: generates spec.json + doc templates + task stubs so the model Edit-fills placeholders instead of hand-Writing whole files (the dominant output-token cost). `--tasks-only` merges task stubs + task_files/task_registry into an existing spec without overwriting filled tasks. Wired into Step 7. +- **Execution Tier (Light/Standard/Deep)**: auto-scales research/discovery/review depth by complexity so small specs skip full-pipeline overhead. Recorded in `spec.json.design_context.execution_tier`. Quality floor (scope_lock, EARS, Layer 1 + Layer 2) never skips. +- **Evidence-gated red-team**: `review.md` Step 5.5 auto-rejects findings that don't cite a concrete task/section or verbatim quote (spec-level analogue of ck-plan's `file:line` gate). +- **Definition of a Complete Task (DoCT)**: explicit quality bar in SKILL.md mapping each completeness element (real paths, contract, measurable acceptance, real evidence commands, reachability, requirement mapping) to its enforcing mechanism. + +### Changed — Specs v2 +- **Literal `R{N}.{M}` is now the default requirement format** (template + SKILL.md Step 5). Wakes per-criterion coverage (P1d), which the model previously dodged by writing a bare numbered list. NFR section uses literal IDs too. + ## [0.13.0] - 2026-06-18 ### Changed From b866a6ceaee1e1be1d1346bf79b3d5cad19b79ba Mon Sep 17 00:00:00 2001 From: hapo-nghialuu Date: Sun, 21 Jun 2026 14:58:49 +0700 Subject: [PATCH 07/13] =?UTF-8?q?feat(specs):=20Frontend=20Fidelity=20Rule?= =?UTF-8?q?=20=E2=80=94=20match=20provided=20visual=20references?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a frontend task has a provided visual reference (design image, Figma, screenshot, palette, design tokens, style guide), the task MUST reproduce it faithfully: extract concrete values (exact hex, font, spacing, verbatim UI text) into the task, state a 'MUST: match ' constraint, and prove fidelity in Evidence (visual diff/side-by-side). New components with a reference must cite its tokens; reused components inherit the design system; new components without any reference flag an open design question instead of inventing styling. Closes the gap (seen in the greenfield game spec) where FE tasks omitted the provided palette/text/spacing and the build drifted from the design. Conditional — only applies when a reference exists, so brownfield reuse is unaffected. Added to tasks-generation.md + DoCT table in SKILL.md. --- packages/spec/src/claude/skills/specs/SKILL.md | 1 + .../claude/skills/specs/rules/tasks-generation.md | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/packages/spec/src/claude/skills/specs/SKILL.md b/packages/spec/src/claude/skills/specs/SKILL.md index 3d5a0ff..0aa1eae 100644 --- a/packages/spec/src/claude/skills/specs/SKILL.md +++ b/packages/spec/src/claude/skills/specs/SKILL.md @@ -414,6 +414,7 @@ A task is "complete enough to implement without guessing" only when ALL hold. Ea | **Evidence** uses commands that exist in the project (`package.json`) | Author + grounding spirit; never invent test commands | | **Reachability** names a real entrypoint/caller | `Runtime reachability verification` (Layer 1 presence) + judgment | | **Requirements mapping** present (`_Requirements: x.y_`) | Layer 1 coverage check | +| **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 ` constraint | `tasks-generation.md` Frontend Fidelity Rule + reviewer/visual check | 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. diff --git a/packages/spec/src/claude/skills/specs/rules/tasks-generation.md b/packages/spec/src/claude/skills/specs/rules/tasks-generation.md index 9dfaba4..4e10f9a 100644 --- a/packages/spec/src/claude/skills/specs/rules/tasks-generation.md +++ b/packages/spec/src/claude/skills/specs/rules/tasks-generation.md @@ -192,6 +192,21 @@ Choose verification by task risk and touched surface. Do not force every task to `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. +### Frontend Fidelity Rule (when a visual reference is provided) + +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: + +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. `「投稿を削除しました」`). +2. **State a `MUST: match ` constraint** naming the exact reference (e.g. `image_4.png` / Figma node / `tokens.css`), so fidelity is a requirement, not a suggestion. +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. + +Reuse vs new component: +- **Reuse** an existing component → reference it by path; it carries the design system's style (no need to restate pixels). +- **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). +- **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. + +> 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. + ## Task Hierarchy Rules ### Maximum 2 Levels From 461ef9b97bc054515c5667aa0b82de7a7435c6cf Mon Sep 17 00:00:00 2001 From: hapo-nghialuu Date: Sun, 21 Jun 2026 15:22:03 +0700 Subject: [PATCH 08/13] feat(specs): complexity smell check (by numbers) in scope inquiry Adds quantitative YAGNI tripwires (ck-plan-inspired) on top of the qualitative 5-Dimension pass: >8 files / >2 new services / >12 tasks -> challenge; >15 tasks -> split into sibling specs. Directly targets the mega-spec failure mode seen in the field test (16-task spec -> 1h38m + 8 timeouts). Tripwires are surfaced in the Scope Inquiry summary for the user to decide (Expand/Hold/Reduce/Split), not silently built. scope-inquiry.md + SKILL.md Step 3. --- packages/spec/src/claude/skills/specs/SKILL.md | 1 + .../skills/specs/references/scope-inquiry.md | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/spec/src/claude/skills/specs/SKILL.md b/packages/spec/src/claude/skills/specs/SKILL.md index 0aa1eae..dd7e46c 100644 --- a/packages/spec/src/claude/skills/specs/SKILL.md +++ b/packages/spec/src/claude/skills/specs/SKILL.md @@ -258,6 +258,7 @@ Load: `references/scope-inquiry.md` - If Risk = **Chaotic** → exit spec workflow, redirect to `hapo:hotfix` - If Risk = **Complex** → include spike/prototype tasks in the spec - If Blast Radius = **Critical Path** → spec MUST include rollback strategy and test coverage requirements +- **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. - User picks scope level: Expand / Hold / Reduce - **Skip if:** trivial task (< 20 words, 1 file, user says "just do it") diff --git a/packages/spec/src/claude/skills/specs/references/scope-inquiry.md b/packages/spec/src/claude/skills/specs/references/scope-inquiry.md index 23b5d19..10e0676 100644 --- a/packages/spec/src/claude/skills/specs/references/scope-inquiry.md +++ b/packages/spec/src/claude/skills/specs/references/scope-inquiry.md @@ -48,6 +48,17 @@ Assess how many other parts of the system are affected if this change breaks or > **Rule**: If Blast Radius = Critical Path, the spec MUST include rollback strategy and explicit test coverage requirements in the task files. +### Complexity Smell Check (by numbers) + +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: + +- **> 8 files touched** → challenge whether the same goal is reachable with fewer; flag accidental scope creep. +- **> 2 new classes / services / modules** → smell; justify each new abstraction (could an existing module absorb it?). +- **> 12 task files** → the spec is large; consider splitting by deliverable boundary into sibling specs (cross-spec `blockedBy`) rather than one mega-spec. +- **> 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. + +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. + ## Level Selection After completing the 5-Dimension Assessment, present via `AskUserQuestion`: @@ -77,6 +88,7 @@ Scope Inquiry: - Gap Size: [Small/Medium/Large] — [reason] - Risk Level: [Clear/Complicated/Complex/Chaotic] — [reason] - Blast Radius: [Isolated/Moderate/Critical Path] — [affected modules] +- Complexity smells: [none | >8 files | >2 new services | >12 tasks → consider split | >15 tasks → split] - Minimum Change: [Essential gap vs Niceties] -- User chose: [Expand / Hold / Reduce] +- User chose: [Expand / Hold / Reduce / Split] ``` From 3d10fc124b455e7db3113184bae26ef9a1e02370 Mon Sep 17 00:00:00 2001 From: hapo-nghialuu Date: Sun, 21 Jun 2026 15:50:19 +0700 Subject: [PATCH 09/13] chore(release): bump @haposoft/cafekit to 0.13.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Specs v2: Layer-2 grounding, spec scaffolding (output-cost), execution tiers, evidence-gated red-team, DoCT, Frontend Fidelity Rule, complexity smell check, and literal R{N}.{M} default. All opt-in/progressive or active-grep — legacy specs unaffected; spec structure unchanged. --- docs/project-changelog.md | 4 ++++ packages/spec/CHANGELOG.md | 17 +++++++++++++++++ packages/spec/package.json | 2 +- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/docs/project-changelog.md b/docs/project-changelog.md index 2d1b32a..401490e 100644 --- a/docs/project-changelog.md +++ b/docs/project-changelog.md @@ -5,12 +5,16 @@ All notable changes to CafeKit are documented here, following ## [Unreleased] +## [0.13.1] - 2026-06-21 + ### Added — Specs v2 (quality + grounding + output-cost) - **Layer 2 grounding (`spec-ground.cjs`)**: new deterministic check that greps the real work tree to verify every `Related Files` path a task cites (Modify/Delete/Read) exists, or is Created earlier in the spec. Closes the gap where the structural validator checks spec *shape* but is blind to phantom file paths. Active-grep (not opt-in). Wired into Step 8.5 + the `--validate` gate. `--root` for monorepo/sibling-project specs. - **Spec scaffolding (`spec-scaffold.cjs`)**: generates spec.json + doc templates + task stubs so the model Edit-fills placeholders instead of hand-Writing whole files (the dominant output-token cost). `--tasks-only` merges task stubs + task_files/task_registry into an existing spec without overwriting filled tasks. Wired into Step 7. - **Execution Tier (Light/Standard/Deep)**: auto-scales research/discovery/review depth by complexity so small specs skip full-pipeline overhead. Recorded in `spec.json.design_context.execution_tier`. Quality floor (scope_lock, EARS, Layer 1 + Layer 2) never skips. - **Evidence-gated red-team**: `review.md` Step 5.5 auto-rejects findings that don't cite a concrete task/section or verbatim quote (spec-level analogue of ck-plan's `file:line` gate). - **Definition of a Complete Task (DoCT)**: explicit quality bar in SKILL.md mapping each completeness element (real paths, contract, measurable acceptance, real evidence commands, reachability, requirement mapping) to its enforcing mechanism. +- **Frontend Fidelity Rule**: when a frontend task has a provided visual reference (design image, Figma, screenshot, palette, design tokens, style guide), the task MUST reproduce it faithfully — extract concrete values (exact hex, font, spacing, verbatim UI text), state a `match ` constraint, and prove fidelity in Evidence. New components with a reference must cite its tokens; conditional so brownfield reuse is unaffected. (`tasks-generation.md` + DoCT) +- **Complexity Smell Check (by numbers)**: quantitative YAGNI tripwires in scope inquiry (>8 files / >2 new services / >12 tasks → challenge; >15 tasks → split into sibling specs), targeting the mega-spec failure mode. Surfaced for the user to decide (Expand/Hold/Reduce/Split), never silently built. (`scope-inquiry.md` + SKILL.md Step 3) ### Changed — Specs v2 - **Literal `R{N}.{M}` is now the default requirement format** (template + SKILL.md Step 5). Wakes per-criterion coverage (P1d), which the model previously dodged by writing a bare numbered list. NFR section uses literal IDs too. diff --git a/packages/spec/CHANGELOG.md b/packages/spec/CHANGELOG.md index 6b7eae3..5fab886 100644 --- a/packages/spec/CHANGELOG.md +++ b/packages/spec/CHANGELOG.md @@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.13.1] - 2026-06-21 + +### Added — Specs v2 (quality + grounding + output-cost) +- **Layer 2 grounding (`spec-ground.cjs`)**: deterministic check that greps the real work tree to verify every `Related Files` path a task cites (Modify/Delete/Read) exists, or is Created earlier in the spec. Active-grep, not opt-in. Wired into Step 8.5 + `--validate`. `--root` for monorepo specs. +- **Spec scaffolding (`spec-scaffold.cjs`)**: generates spec.json + doc templates + task stubs so the model Edit-fills placeholders instead of hand-Writing whole files (cuts the dominant output-token cost). `--tasks-only` merges into an existing spec without overwriting filled tasks. +- **Execution Tier (Light/Standard/Deep)**: auto-scales research/discovery/review depth so small specs skip full-pipeline overhead; recorded in `design_context.execution_tier`. Quality floor never skips. +- **Evidence-gated red-team**: findings without a concrete task/section citation are auto-rejected. +- **Definition of a Complete Task (DoCT)**: explicit quality bar mapping each completeness element to its enforcing mechanism. +- **Frontend Fidelity Rule**: FE tasks with a provided visual reference must reproduce it faithfully (concrete hex/font/spacing/verbatim text + `match ` constraint). Conditional — brownfield reuse unaffected. +- **Complexity Smell Check (by numbers)**: quantitative YAGNI tripwires (>8 files / >2 services / >12 tasks → challenge; >15 → split). + +### Changed +- **Literal `R{N}.{M}` is the default requirement format** (template + Step 5), waking per-criterion coverage that was previously dodged via bare numbered lists. + +### Notes +- All new validator/grounding checks are opt-in/progressive or active-grep — legacy specs are unaffected and continue to pass. Spec structure (spec.json + requirements/design/research/tasks) unchanged. + ## [0.13.0] - 2026-06-18 ### Changed diff --git a/packages/spec/package.json b/packages/spec/package.json index d843f2e..74f4805 100644 --- a/packages/spec/package.json +++ b/packages/spec/package.json @@ -1,6 +1,6 @@ { "name": "@haposoft/cafekit", - "version": "0.13.0", + "version": "0.13.1", "description": "Claude Code-first spec-driven workflow for AI coding assistants. Bundles CafeKit hapo: skills, runtime hooks, agents, and installer scaffolding.", "author": "Haposoft ", "license": "MIT", From 6cab900ff700b6808cb0728ce78d25d44f60d3c7 Mon Sep 17 00:00:00 2001 From: hapo-nghialuu Date: Sun, 21 Jun 2026 23:59:02 +0700 Subject: [PATCH 10/13] feat(specs): enforce scaffold on task creation via PreToolUse guard Block raw Write to specs//tasks/task-*.md so task files are generated by spec-scaffold.cjs and Edit-filled, closing the dodge where the model hand-writes task files and skips the opt-in scaffold step. Scope is narrow (only the Write tool on a task-file path); Edit/MultiEdit, other Writes, and the scaffold script are untouched. Three safety valves: fail-open when the scaffold script is absent, an actionable block message with the exact command, and a runtime.json escape hatch. --- .../src/claude/hooks/task-scaffold-guard.cjs | 91 +++++++++++++++++++ .../spec/src/claude/migration-manifest.json | 1 + .../spec/src/claude/settings/settings.json | 9 ++ .../spec/src/claude/skills/specs/SKILL.md | 4 +- 4 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 packages/spec/src/claude/hooks/task-scaffold-guard.cjs diff --git a/packages/spec/src/claude/hooks/task-scaffold-guard.cjs b/packages/spec/src/claude/hooks/task-scaffold-guard.cjs new file mode 100644 index 0000000..5eaf289 --- /dev/null +++ b/packages/spec/src/claude/hooks/task-scaffold-guard.cjs @@ -0,0 +1,91 @@ +#!/usr/bin/env node +/** + * Copyright (c) 2026 Haposoft. MIT License. + * + * PreToolUse Hook — task-scaffold-guard.cjs + * Implements: https://docs.anthropic.com/en/docs/claude-code/hooks + * + * Forces spec task files to be GENERATED via spec-scaffold.cjs instead of + * hand-written. Field tests showed the model dodges the (opt-in) scaffold step + * and raw-`Write`s every task file — the single biggest output-token cost of a + * spec run. This hook blocks `Write` to `specs//tasks/task-*.md`, so + * the only path to a task file is: scaffold (creates the stub) -> Edit (fills it). + * + * Scope is deliberately narrow — it only ever blocks the `Write` tool on a task + * file path. `Edit`/`MultiEdit` (filling a stub), and `Write` to any other file + * (requirements.md, design.md, source code) are never touched. The scaffold + * script itself writes via Node fs through a Bash call, not the `Write` tool, so + * it is never blocked. + * + * Safety valves: + * 1. fail-open when spec-scaffold.cjs is absent — you cannot force a tool that + * is not installed, so a hook shipped without its script must not deadlock. + * 2. actionable block message — prints the exact scaffold command to run. + * 3. escape hatch — "spec": { "scaffold_guard": false } in .claude/runtime.json. + * + * Exit: 0 = allow, 2 = block. + */ + +try { + const fs = require('fs'); + const path = require('path'); + + const stdin = fs.readFileSync(0, 'utf8').trim(); + if (!stdin) process.exit(0); + + const data = JSON.parse(stdin); + const toolName = data.tool_name || ''; + const toolInput = data.tool_input || {}; + const cwd = data.cwd || process.cwd(); + + // Only `Write` can create a file from scratch. Edit/MultiEdit require an + // existing file, so filling a scaffolded stub is always allowed. + if (toolName !== 'Write') process.exit(0); + + const filePath = toolInput.file_path || toolInput.path || ''; + if (!filePath) process.exit(0); + + // Match a spec task file: .../specs//tasks/task-<...>.md + const norm = filePath.replace(/\\/g, '/'); + const isTaskFile = /(^|\/)specs\/[^/]+\/tasks\/task-[^/]+\.md$/.test(norm); + if (!isTaskFile) process.exit(0); + + // Valve 3: explicit escape hatch via .claude/runtime.json (fail-closed: a + // missing/broken runtime.json keeps the guard ON). + let runtime = {}; + try { + const rp = path.join(cwd, '.claude', 'runtime.json'); + if (fs.existsSync(rp)) runtime = JSON.parse(fs.readFileSync(rp, 'utf8')); + } catch { /* malformed runtime.json -> keep guard on */ } + if (runtime.spec && runtime.spec.scaffold_guard === false) process.exit(0); + + // Valve 1: fail-open if the scaffold script is not installed next to this hook + // (hook lives in .claude/hooks/, script in .claude/scripts/). Forcing a tool + // that does not exist would deadlock task creation. + const scaffold = path.join(__dirname, '..', 'scripts', 'spec-scaffold.cjs'); + if (!fs.existsSync(scaffold)) process.exit(0); + + // Valve 2: block with an actionable message carrying the exact command. + const m = norm.match(/(^|\/)specs\/([^/]+)\/tasks\//); + const feature = m ? m[2] : ''; + console.log( + `TASK SCAFFOLD REQUIRED: task files must be generated, not hand-written.\n` + + `Blocked Write: ${filePath}\n\n` + + `Generate the stub(s), then Edit-fill the {{...}} placeholders:\n` + + ` node .claude/scripts/spec-scaffold.cjs ${feature} --tasks "R0-01-slug,R1-01-slug,..." --tasks-only\n` + + `Then use Edit (not Write) on each tasks/task-*.md stub.\n` + + `Override: set "spec": { "scaffold_guard": false } in .claude/runtime.json` + ); + process.exit(2); + +} catch (e) { + // Never let a hook crash block the user — log and fail-open. + try { + const fs = require('fs'), p = require('path'); + const d = p.join(__dirname, '.logs'); + if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true }); + fs.appendFileSync(p.join(d, 'hook-log.jsonl'), + JSON.stringify({ ts: new Date().toISOString(), hook: 'task-scaffold-guard', status: 'crash', error: e.message }) + '\n'); + } catch (_) {} + process.exit(0); +} diff --git a/packages/spec/src/claude/migration-manifest.json b/packages/spec/src/claude/migration-manifest.json index 9366809..dd9e5de 100644 --- a/packages/spec/src/claude/migration-manifest.json +++ b/packages/spec/src/claude/migration-manifest.json @@ -90,6 +90,7 @@ "hooks/usage.cjs", "hooks/privacy-block.cjs", "hooks/inspect-block.cjs", + "hooks/task-scaffold-guard.cjs", "hooks/rules.cjs", "hooks/spec-state.cjs", "hooks/state.cjs", diff --git a/packages/spec/src/claude/settings/settings.json b/packages/spec/src/claude/settings/settings.json index 34ca93c..3e6aad4 100644 --- a/packages/spec/src/claude/settings/settings.json +++ b/packages/spec/src/claude/settings/settings.json @@ -67,6 +67,15 @@ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/inspect-block.cjs\"" } ] + }, + { + "matcher": "Write", + "hooks": [ + { + "type": "command", + "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/task-scaffold-guard.cjs\"" + } + ] } ], "PostToolUse": [ diff --git a/packages/spec/src/claude/skills/specs/SKILL.md b/packages/spec/src/claude/skills/specs/SKILL.md index dd7e46c..0eb3180 100644 --- a/packages/spec/src/claude/skills/specs/SKILL.md +++ b/packages/spec/src/claude/skills/specs/SKILL.md @@ -333,9 +333,9 @@ Rules: - Load `rules/phase-decision-matrix.md` before generating task files. Treat "phase" as an implementation slice/task cluster, not a `phase-XX.md` artifact. - Load `rules/task-scoring-rubric.md` for every candidate task to decide priority, split/merge, spike needs, dependencies, parallel eligibility, and evidence depth. - Load `references/ask-user-question-gates.md`; if scoring reveals unapproved scope expansion or an unresolved user-owned choice, pause before writing task files. -- **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: +- **Scaffold is mandatory — raw `Write` to a task file is blocked.** A PreToolUse hook (`task-scaffold-guard.cjs`) rejects any `Write` whose path matches `specs//tasks/task-*.md`. The only path to a task file is scaffold → Edit. Once the task list is decided, generate the stubs: `node .claude/scripts/spec-scaffold.cjs --tasks "R0-01-slug,R1-01-slug,..." --tasks-only` - 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. + 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 (`Edit`/`MultiEdit` are allowed; `Write` is not). Hand-writing whole task files is the single biggest output-token cost — scaffold + fill cuts it. (The guard fails open if the scaffold script is absent, and can be disabled via `"spec": { "scaffold_guard": false }` in `.claude/runtime.json`.) - Each task file follows template `templates/task.md` (scaffold already applies it). **Leave NO `{{...}}` placeholder unfilled** — a stub with placeholders is an incomplete task. - `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. - 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`. From 65e6b4179127f25d1cb93908ff23d3e74afe5e59 Mon Sep 17 00:00:00 2001 From: hapo-nghialuu Date: Mon, 22 Jun 2026 00:00:28 +0700 Subject: [PATCH 11/13] chore(release): bump @haposoft/cafekit to 0.13.2 --- docs/project-changelog.md | 14 ++++++++++++++ packages/spec/CHANGELOG.md | 14 ++++++++++++++ packages/spec/package.json | 2 +- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/docs/project-changelog.md b/docs/project-changelog.md index 401490e..2cacf64 100644 --- a/docs/project-changelog.md +++ b/docs/project-changelog.md @@ -5,6 +5,20 @@ All notable changes to CafeKit are documented here, following ## [Unreleased] +## [0.13.2] - 2026-06-21 + +### Added — Enforce scaffold on task creation +- **`task-scaffold-guard.cjs` (PreToolUse hook)**: hard-blocks any `Write` to `specs//tasks/task-*.md`, forcing task files to be generated via `spec-scaffold.cjs` then `Edit`-filled. Fixes the field-test dodge where the model hand-wrote every task file and skipped the opt-in scaffold step. Only the `Write` tool on a task-file path is blocked — `Edit`/`MultiEdit`, other `Write`s, and the scaffold script itself are untouched. +- **Safety valves**: fail-open when the scaffold script is missing (no deadlock), an actionable block message with the exact command, and a `.claude/runtime.json` escape hatch (`"spec": { "scaffold_guard": false }`). + +### Changed +- `settings.json`: guard registered under a dedicated `Write` matcher in `PreToolUse`. +- `migration-manifest.json`: hook declared in `runtime.files` so the installer ships it. +- Specs `SKILL.md` Step 7: scaffold is now mandatory (Write to a task file is blocked), not a suggestion. + +### Notes +- Buys process discipline and consistent `task_registry`/`task_files`, not a large token reduction — task content is still emitted via `Edit`. + ## [0.13.1] - 2026-06-21 ### Added — Specs v2 (quality + grounding + output-cost) diff --git a/packages/spec/CHANGELOG.md b/packages/spec/CHANGELOG.md index 5fab886..4267fc7 100644 --- a/packages/spec/CHANGELOG.md +++ b/packages/spec/CHANGELOG.md @@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.13.2] - 2026-06-21 + +### Added — Enforce scaffold on task creation +- **`task-scaffold-guard.cjs` (PreToolUse hook)**: hard-blocks any `Write` whose path matches `specs//tasks/task-*.md`, so task files can only be created via `spec-scaffold.cjs` and then `Edit`-filled. Closes the dodge where the model hand-`Write`s task files and bypasses the (previously opt-in) scaffold step. Narrow scope: only the `Write` tool on a task-file path is blocked; `Edit`/`MultiEdit` and `Write` to any other file are untouched, and the scaffold script (writing via Node fs through Bash) is never blocked. +- **Three safety valves**: fail-open when `spec-scaffold.cjs` is absent (a hook shipped without its script must not deadlock task creation); actionable block message carrying the exact scaffold command; escape hatch via `"spec": { "scaffold_guard": false }` in `.claude/runtime.json`. + +### Changed +- `settings/settings.json`: registered the guard under a dedicated `Write` matcher in `PreToolUse` (separate entry so the settings-merge dedupe does not swallow it). +- `migration-manifest.json`: declared `hooks/task-scaffold-guard.cjs` in `runtime.files` so the installer ships it alongside `spec-scaffold.cjs`. +- `skills/specs/SKILL.md` Step 7: scaffold is now stated as **mandatory** (raw `Write` to a task file is blocked), not a suggestion. + +### Notes +- Enforces *process discipline* (every spec goes through scaffold; `task_files`/`task_registry` stay consistent), not a large token cut — task content is still emitted via `Edit`. + ## [0.13.1] - 2026-06-21 ### Added — Specs v2 (quality + grounding + output-cost) diff --git a/packages/spec/package.json b/packages/spec/package.json index 74f4805..8839922 100644 --- a/packages/spec/package.json +++ b/packages/spec/package.json @@ -1,6 +1,6 @@ { "name": "@haposoft/cafekit", - "version": "0.13.1", + "version": "0.13.2", "description": "Claude Code-first spec-driven workflow for AI coding assistants. Bundles CafeKit hapo: skills, runtime hooks, agents, and installer scaffolding.", "author": "Haposoft ", "license": "MIT", From f07ceb72a8af2fd1f6179d90b69014eca701faca Mon Sep 17 00:00:00 2001 From: hapo-nghialuu Date: Mon, 22 Jun 2026 09:33:30 +0700 Subject: [PATCH 12/13] test for v0.13.2 --- docs/project-changelog.md | 1 + notes/v0.13.0-field-test-post-list-screen.md | 67 ++++++ packages/spec/CHANGELOG.md | 1 + .../spec/scripts/run-skill-self-tests.mjs | 4 +- .../claude/scripts/validate-spec-output.cjs | 23 ++ .../spec/src/claude/skills/specs/SKILL.md | 2 +- sample/.gitignore | 8 + sample/CLAUDE.md | 131 +++++++++++ .../references/01_color_palette_reference.png | Bin 0 -> 1565483 bytes .../references/02_fox_mascot_reference.png | Bin 0 -> 1981989 bytes .../references/03_ui_component_reference.png | Bin 0 -> 1898443 bytes .../references/04_map_world_reference.png | Bin 0 -> 2327384 bytes .../screens/00_product_concept_sheet.png | Bin 0 -> 2139637 bytes .../screens/01_adventure_map_screen.png | Bin 0 -> 2368174 bytes .../screens/02_lesson_question_screen.png | Bin 0 -> 1756486 bytes .../screens/03_reward_completion_screen.png | Bin 0 -> 2039819 bytes .../screens/mathquest_app_concept_sheet.png | Bin 0 -> 2083947 bytes .../mathquest_ai_agent_art_guideline.json | 112 +++++++++ sample/json/mathquest_asset_manifest.json | 105 +++++++++ .../mathquest_mvp_implementation_config.json | 167 ++++++++++++++ sample/specs/place-value-bridge-mvp/design.md | 217 ++++++++++++++++++ .../reports/red-team-report.md | 102 ++++++++ .../reports/validate-log.md | 44 ++++ .../place-value-bridge-mvp/requirements.md | 93 ++++++++ .../specs/place-value-bridge-mvp/research.md | 98 ++++++++ sample/specs/place-value-bridge-mvp/spec.json | 212 +++++++++++++++++ .../tasks/task-R0-01-project-scaffolding.md | 86 +++++++ .../task-R0-02-asset-generation-pipeline.md | 86 +++++++ .../task-R0-03-state-machine-and-store.md | 78 +++++++ .../tasks/task-R1-01-adventure-map-scene.md | 90 ++++++++ .../tasks/task-R2-01-lesson-question-scene.md | 91 ++++++++ .../task-R3-01-scoring-and-feedback-engine.md | 83 +++++++ .../task-R4-01-reward-completion-scene.md | 89 +++++++ ...k-R5-01-progress-persistence-and-unlock.md | 90 ++++++++ ...6-01-gsap-animations-and-reduced-motion.md | 78 +++++++ .../task-R7-01-integration-and-acceptance.md | 83 +++++++ 36 files changed, 2239 insertions(+), 2 deletions(-) create mode 100644 notes/v0.13.0-field-test-post-list-screen.md create mode 100644 sample/.gitignore create mode 100644 sample/CLAUDE.md create mode 100644 sample/assets/references/01_color_palette_reference.png create mode 100644 sample/assets/references/02_fox_mascot_reference.png create mode 100644 sample/assets/references/03_ui_component_reference.png create mode 100644 sample/assets/references/04_map_world_reference.png create mode 100644 sample/assets/screens/00_product_concept_sheet.png create mode 100644 sample/assets/screens/01_adventure_map_screen.png create mode 100644 sample/assets/screens/02_lesson_question_screen.png create mode 100644 sample/assets/screens/03_reward_completion_screen.png create mode 100644 sample/assets/screens/mathquest_app_concept_sheet.png create mode 100644 sample/json/mathquest_ai_agent_art_guideline.json create mode 100644 sample/json/mathquest_asset_manifest.json create mode 100644 sample/json/mathquest_mvp_implementation_config.json create mode 100644 sample/specs/place-value-bridge-mvp/design.md create mode 100644 sample/specs/place-value-bridge-mvp/reports/red-team-report.md create mode 100644 sample/specs/place-value-bridge-mvp/reports/validate-log.md create mode 100644 sample/specs/place-value-bridge-mvp/requirements.md create mode 100644 sample/specs/place-value-bridge-mvp/research.md create mode 100644 sample/specs/place-value-bridge-mvp/spec.json create mode 100644 sample/specs/place-value-bridge-mvp/tasks/task-R0-01-project-scaffolding.md create mode 100644 sample/specs/place-value-bridge-mvp/tasks/task-R0-02-asset-generation-pipeline.md create mode 100644 sample/specs/place-value-bridge-mvp/tasks/task-R0-03-state-machine-and-store.md create mode 100644 sample/specs/place-value-bridge-mvp/tasks/task-R1-01-adventure-map-scene.md create mode 100644 sample/specs/place-value-bridge-mvp/tasks/task-R2-01-lesson-question-scene.md create mode 100644 sample/specs/place-value-bridge-mvp/tasks/task-R3-01-scoring-and-feedback-engine.md create mode 100644 sample/specs/place-value-bridge-mvp/tasks/task-R4-01-reward-completion-scene.md create mode 100644 sample/specs/place-value-bridge-mvp/tasks/task-R5-01-progress-persistence-and-unlock.md create mode 100644 sample/specs/place-value-bridge-mvp/tasks/task-R6-01-gsap-animations-and-reduced-motion.md create mode 100644 sample/specs/place-value-bridge-mvp/tasks/task-R7-01-integration-and-acceptance.md diff --git a/docs/project-changelog.md b/docs/project-changelog.md index 2cacf64..772a830 100644 --- a/docs/project-changelog.md +++ b/docs/project-changelog.md @@ -10,6 +10,7 @@ All notable changes to CafeKit are documented here, following ### Added — Enforce scaffold on task creation - **`task-scaffold-guard.cjs` (PreToolUse hook)**: hard-blocks any `Write` to `specs//tasks/task-*.md`, forcing task files to be generated via `spec-scaffold.cjs` then `Edit`-filled. Fixes the field-test dodge where the model hand-wrote every task file and skipped the opt-in scaffold step. Only the `Write` tool on a task-file path is blocked — `Edit`/`MultiEdit`, other `Write`s, and the scaffold script itself are untouched. - **Safety valves**: fail-open when the scaffold script is missing (no deadlock), an actionable block message with the exact command, and a `.claude/runtime.json` escape hatch (`"spec": { "scaffold_guard": false }`). +- **Validator placeholder gate**: `validate-spec-output.cjs` now hard-fails a task that still has an unfilled `{{...}}` scaffold placeholder (warns on a leftover `.../` path). Fill-side complement to the guard: the hook forces creation via scaffold, this proves the stub was completed. ### Changed - `settings.json`: guard registered under a dedicated `Write` matcher in `PreToolUse`. diff --git a/notes/v0.13.0-field-test-post-list-screen.md b/notes/v0.13.0-field-test-post-list-screen.md new file mode 100644 index 0000000..80b3e84 --- /dev/null +++ b/notes/v0.13.0-field-test-post-list-screen.md @@ -0,0 +1,67 @@ +# Field Test v0.13.0 — spec `post_list_screen` (e8194-ai-mining-sns) + +> Ghi chú đánh giá thực địa. Spec production thật, đa tầng (NestJS + Nuxt 3 + MySQL). +> Conversation: `~/.claude/projects/-Users-nghialuutrung-Desktop-e8194-ai-mining-sns/b6cfe865-b814-40d7-b82a-30874b2030ef/` +> Ngày: 2026-06-18. Chưa commit — memo nội bộ. + +## Bối cảnh test +- Yêu cầu: tính năng màn danh sách bài đăng (一覧画面), 5 tab trạng thái, bulk actions, filter, 9 ảnh references. +- Bản chạy: **đúng 0.13.0** (đã verify: Step 8 Hydration đã bỏ, không còn `task-hydration.md`/`tasks-parallel-analysis.md`, `cafekit.json` version=0.13.0). +- Kết quả: 16 task files, validator PASS, red-team 4-persona, `ready_for_implementation=true`. + +## 1. Đo lường vận hành (số cứng từ session JSONL) +| Chỉ số | Giá trị | So game spec (27m) | +|---|---|---| +| Thời gian | **1h38m04s** | 3.6× | +| Output tokens | **935.000** | 3× | +| cache_read đỉnh | **296.000** (vượt 200K, nhờ model 1M) | — | +| Timeout/API error | **8 lần** | rủi ro resume | +| Tool calls | 27 Read · 21 Write · 16 Edit · 12 TaskUpdate · 6 TaskCreate · 2 Agent | output-bound | + +→ Thủ phạm 1h38m = **spec quá lớn (16 task) + output 935K + 8 timeout**, KHÔNG phải context nạp. + +## 2. Cải tiến v0.13.0 phát huy thế nào — KẾT QUẢ HỖN HỢP +| Cải tiến | Thực địa | Bằng chứng | +|---|---|---| +| P1b timestamps | ✅ thành công (gián tiếp) | 6 mốc riêng biệt, không reuse init | +| **P1d sub-criteria** | ❌ **BỊ NÉ** | requirements dùng 90 numbered-list, 0 literal `Rx.y`. Log AI ghi thẳng: *"dạng list số — tránh kích hoạt check sub-criteria"* | +| **#3 contract drift** | ❌ **BỊ NÉ** | design 0 contract marker. Log: *"cố ý KHÔNG dùng marker... rủi ro lớn hơn lợi ích"* | +| P0 tollgate + #2 + A1 | ⚪ vô nghĩa ở case này | tiết kiệm vài K token, bị 935K output át | + +### PHÁT HIỆN GỐC: incentive ngược +- P1d/#3 là opt-in **CÓ downside** (dùng sai → validator FAIL). AI tính ra **né hoàn toàn là tối ưu** (pass chắc, 0 rủi ro). +- "Format rõ ràng" vô tình thành "gánh thêm rủi ro validator" → ai cũng tránh. +- Mỉa mai: spec này CHÍNH XÁC là loại đa-tầng BE↔FE mà #3 sinh ra để bảo vệ. RT-2 (red-team thủ công) đã bắt đúng 1 contract drift — nhưng bằng LLM, không phải cơ chế deterministic đã xây. + +## 3. Chất lượng SẢN PHẨM: xuất sắc (9/10) +- **Coverage 14/14 requirement** đều có task ref (R9 ít nhất 1, R6 nhiều nhất 4). Không mồ côi. +- Contract khóa từ **source thật**: PostStatus 1-8, CancelApproval=4, platform X=3/IG=2/FB=1, delete_flg=1. +- Evidence trung thực: ghi rõ "FE không có test runner → chỉ typecheck+lint+manual", không bịa test. +- Red-team bắt 4 lỗ thật, đáng giá nhất **RT-2**: endpoint `bulk-cancel-approval` xung đột cap với spec `cancel_bulk_approval` đang tồn tại → reconcile max 200 BE / cap 10 UI. + +## 4. Đọc ảnh references: CHÍNH XÁC xuất sắc +- Chép đúng **từng chữ JP** modal/button/field từ 6 ảnh đã verify tận mắt (R4.3/R4.5/R8.3/R8.6 khớp nguyên văn). +- **Bắt mâu thuẫn 2 ảnh cùng màn**: image_1 nút 「編集」 vs image_2 nút 「設定」 → Open Q1. Chứng minh nhìn+so sánh thật, không bịa. +- **Phân biệt nội dung vs nhiễu**: banner đỏ「予期しないエラー」 ở ảnh 7/8 (artifact lỗi screenshot) → không chép mù, ghi Open Q6 hỏi nguồn text. +- Lưu ý: tiêu đề màn thật trong ảnh là 「投稿カレンダー」, spec đặt route `/posts/list` theo mô tả — hợp lý nhưng cần PM xác nhận route. + +## 5. Chỉ dẫn UI trong task: đủ structure, thiếu pixel-style +- **CÓ (tầng structure/behavior)**: 14 component đặt tên (`PostListTable.vue`, `ModalRepost.vue`...), file path chính xác, Read/Create/Modify rõ, label JP nguyên văn, `#DAF1EE` ×5, tham chiếu "khớp ảnh 2/3", format `M月D日 HH:mm`. +- **THIẾU (tầng pixel)**: 0 spacing/padding/font-size/tailwind/token. Component **Create mới** (`PostListTable`, `ModalRepost`, `PostListPlatformFilter`) chỉ tả cấu trúc, không tả visual → dev tự suy từ ảnh + component anh em. +- Đánh giá: hợp lý cho **brownfield** (reuse design system sẵn thay tả pixel). Khác hẳn case game greenfield (0 hex/font/path = lỗ nghiêm trọng). Chỗ hở duy nhất: style component Create mới (badge color, button variant). + +## 6. KẾT LUẬN then chốt +1. **Chất lượng spec không nhờ P1d/#3** (cả 2 bị né) — nhờ **xương sống vốn có**: evidence gate (scout source thật) + red-team 4-persona + input đại ca chi tiết. → **0.12.0 cũng cho spec tương đương**. Giá trị thật của hệ nằm ở evidence+red-team, không ở 2 validator-check đã thêm. +2. **Công sức 0.13.0 đặt sai trọng tâm**: tối ưu *context nạp* + xây *cơ chế chặt opt-in*. Thực địa: (a) context bị 935K output át, (b) cơ chế chặt bị né vì opt-in-có-downside. Chỉ P1b "thắng" mà case này AI vốn đã tự đúng. +3. **Đòn bẩy thật chưa đụng**: (a) output sinh ra (thinking + viết lại), (b) spec-size quá lớn = nguồn 1h38m + 8 timeout. + +## 7. Việc đáng làm tiếp (theo thứ tự đòn bẩy thật) +1. **Sửa incentive P1d/#3** — đổi "opt-in im lặng" → SKILL.md chỉ thị BẮT BUỘC ("spec đa-tầng BE+FE PHẢI dùng contract marker"; "requirements PHẢI dùng literal Rx.y"). Biến né-được thành phải-dùng. Nếu không → gỡ luôn cho đỡ ảo tưởng an toàn. +2. **Chống spec-quá-lớn** — spec >10 task PHẢI tách hoặc song song hóa sinh task. Đây mới là thứ cắt 1h38m. +3. **Resume an toàn sau timeout** — 8 lần ngắt là rủi ro mất việc thật. +4. (phụ) Bổ sung style-reference 1 dòng cho component Create mới khi cần pixel-perfect. + +## Điểm tổng +- Sản phẩm spec: **9/10** +- Cải tiến-v0.13.0-phát-huy: **4/10** (2/3 validator-check bị né) +- Vận hành: **4/10** (1h38m, 8 timeout) diff --git a/packages/spec/CHANGELOG.md b/packages/spec/CHANGELOG.md index 4267fc7..831936a 100644 --- a/packages/spec/CHANGELOG.md +++ b/packages/spec/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added — Enforce scaffold on task creation - **`task-scaffold-guard.cjs` (PreToolUse hook)**: hard-blocks any `Write` whose path matches `specs//tasks/task-*.md`, so task files can only be created via `spec-scaffold.cjs` and then `Edit`-filled. Closes the dodge where the model hand-`Write`s task files and bypasses the (previously opt-in) scaffold step. Narrow scope: only the `Write` tool on a task-file path is blocked; `Edit`/`MultiEdit` and `Write` to any other file are untouched, and the scaffold script (writing via Node fs through Bash) is never blocked. - **Three safety valves**: fail-open when `spec-scaffold.cjs` is absent (a hook shipped without its script must not deadlock task creation); actionable block message carrying the exact scaffold command; escape hatch via `"spec": { "scaffold_guard": false }` in `.claude/runtime.json`. +- **Validator placeholder gate (`validate-spec-output.cjs`)**: a task file that still carries an unfilled `{{...}}` scaffold placeholder now hard-fails (previously a prompt-only DoCT rule). A leftover `.../` path fragment warns. This is the fill-side complement to the guard — the hook forces task files through the scaffold; this proves the resulting stubs were actually completed, closing the "stub created but not filled" gap. ### Changed - `settings/settings.json`: registered the guard under a dedicated `Write` matcher in `PreToolUse` (separate entry so the settings-merge dedupe does not swallow it). diff --git a/packages/spec/scripts/run-skill-self-tests.mjs b/packages/spec/scripts/run-skill-self-tests.mjs index d57b8e3..9865be3 100644 --- a/packages/spec/scripts/run-skill-self-tests.mjs +++ b/packages/spec/scripts/run-skill-self-tests.mjs @@ -250,7 +250,9 @@ async function runStaticSemanticTests() { file: "src/claude/skills/specs/SKILL.md", assert: (content) => content.includes("**MUST run deterministic validator.**") && - content.includes("Script failure overrides any LLM checklist result") && + // Substring is invariant to "Script failure" (single-layer) vs the + // Specs-v2 "Either script failing" (validator + grounding) wording. + content.includes("overrides any LLM checklist result") && content.includes("output MUST NOT suggest `/hapo:develop`"), }, { diff --git a/packages/spec/src/claude/scripts/validate-spec-output.cjs b/packages/spec/src/claude/scripts/validate-spec-output.cjs index 093cc94..8e0956a 100755 --- a/packages/spec/src/claude/scripts/validate-spec-output.cjs +++ b/packages/spec/src/claude/scripts/validate-spec-output.cjs @@ -132,6 +132,28 @@ function validateTaskSections(taskPath, content, errors) { } } +/** + * Task files are created from the scaffold template (the scaffold-guard hook + * forces creation through it), so every task starts as a stub full of `{{...}}` + * placeholders. The hook guarantees the stub is CREATED via scaffold, but + * nothing guaranteed the model FILLED it. An unfilled `{{...}}` is an + * incomplete task — SKILL.md: "Leave NO {{...}} placeholder ... fails DoCT" — + * so it is a hard error here. A `.../` path fragment is a not-yet-resolved path + * placeholder; it is only a warning, because it usually survives in prose Steps + * while the Related Files table (which spec-ground.cjs does verify) is already + * concrete. Matching `\.\.\.\/` (three dots + slash) avoids flagging a relative + * `../` path or a prose ellipsis. + */ +function validateTaskPlaceholders(taskPath, content, errors, warnings) { + const stub = content.match(/\{\{[^}\n]+\}\}/); + if (stub) { + errors.push(`${taskPath}: unfilled scaffold placeholder ${stub[0]} — task stub was not completed`); + } + if (/\.\.\.\//.test(content)) { + warnings.push(`${taskPath}: contains a '.../' path placeholder — replace with a concrete path`); + } +} + /** * Each phase completion must carry its own timestamp. Reusing `timestamps.init` * for a later phase is forbidden (SKILL.md spec.json Update Rules). This used to @@ -344,6 +366,7 @@ function validateSpec(specDir) { const fullPath = path.join(specDir, taskFile); const content = fs.readFileSync(fullPath, 'utf8'); validateTaskSections(taskFile, content, errors); + validateTaskPlaceholders(taskFile, content, errors, warnings); const idRe = /\b((?:REQ-\d+)|(?:R\d+))\b/gi; let match; diff --git a/packages/spec/src/claude/skills/specs/SKILL.md b/packages/spec/src/claude/skills/specs/SKILL.md index 0eb3180..0dba5b7 100644 --- a/packages/spec/src/claude/skills/specs/SKILL.md +++ b/packages/spec/src/claude/skills/specs/SKILL.md @@ -335,7 +335,7 @@ Rules: - Load `references/ask-user-question-gates.md`; if scoring reveals unapproved scope expansion or an unresolved user-owned choice, pause before writing task files. - **Scaffold is mandatory — raw `Write` to a task file is blocked.** A PreToolUse hook (`task-scaffold-guard.cjs`) rejects any `Write` whose path matches `specs//tasks/task-*.md`. The only path to a task file is scaffold → Edit. Once the task list is decided, generate the stubs: `node .claude/scripts/spec-scaffold.cjs --tasks "R0-01-slug,R1-01-slug,..." --tasks-only` - 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 (`Edit`/`MultiEdit` are allowed; `Write` is not). Hand-writing whole task files is the single biggest output-token cost — scaffold + fill cuts it. (The guard fails open if the scaffold script is absent, and can be disabled via `"spec": { "scaffold_guard": false }` in `.claude/runtime.json`.) + 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 (`Edit`/`MultiEdit` are allowed; `Write` is not). (The guard fails open if the scaffold script is absent, and can be disabled via `"spec": { "scaffold_guard": false }` in `.claude/runtime.json`.) - Each task file follows template `templates/task.md` (scaffold already applies it). **Leave NO `{{...}}` placeholder unfilled** — a stub with placeholders is an incomplete task. - `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. - 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`. diff --git a/sample/.gitignore b/sample/.gitignore new file mode 100644 index 0000000..33bf9a3 --- /dev/null +++ b/sample/.gitignore @@ -0,0 +1,8 @@ +# Git Ignore + +# CafeKit / Ecosystem +specs/_shared/ +plans/ +!plans/templates/ +.cafekit-backup/ +.cafekit.lock diff --git a/sample/CLAUDE.md b/sample/CLAUDE.md new file mode 100644 index 0000000..3d82a16 --- /dev/null +++ b/sample/CLAUDE.md @@ -0,0 +1,131 @@ +# CLAUDE.md + +Primary operating instructions for Claude Code or any AI agent using this CafeKit runtime. + +## Core Objective + +Act as the project orchestrator: understand the request, keep scope tight, use the right skills/agents, and deliver verified work that follows the project's architecture. + +## Core Behavior + +These rules reduce common agent coding failures: hidden assumptions, overbuilt solutions, unrelated edits, and unverified completion claims. They take priority over speed-oriented shortcuts. + +### 1. Think Before Coding + +- Do not assume silently. State assumptions when they affect the work. +- If multiple interpretations are plausible, surface them before implementation. +- If the simpler option is likely better, say so and push back. +- If the user asks a question about the project, use `/question` to answer from source evidence before planning. +- Before feature planning or coding, read `./README.md` for project context. + +### 2. Simplicity First + +- Solve the requested problem with the smallest maintainable change. +- Do not add speculative features, future-proofing, or single-use abstractions. +- Reuse existing modules before creating new ones. +- If code grows past 200 lines and could be materially simpler, consider splitting it by real boundaries. +- Prefer YAGNI, KISS, and DRY in that order. + +### 3. Surgical Changes + +- Touch only files required by the task. +- Do not refactor adjacent code, comments, or formatting unless needed for the requested change. +- Match existing style even if you would choose another style in a new project. +- Remove only dead code/imports created by your own change. +- Mention unrelated issues instead of fixing them opportunistically. + +### 4. Goal-Driven Execution + +- Convert requests into verifiable success criteria. +- For spec tasks, use `Completion Criteria` and `Evidence` as the source of truth. Existing task files may use `Task Test Plan & Verification Evidence` or legacy `Verification & Evidence`. +- For bugs, reproduce with a failing test or concrete evidence when feasible before fixing. +- Loop until verification passes or a real blocker is recorded. + +## CafeKit Operating Loop + +Use this loop for non-trivial work: + +1. **Understand** — read README, relevant docs, active spec/task, and existing code. +2. **Plan** — choose the smallest coherent path; use `/question` for evidence-backed project questions and `/specs` for feature specs when ready. +3. **Execute** — implement only the active task/scope; no placeholder completion. +4. **Verify** — run exact task commands first, then repo-level lint/test/build as needed. +5. **Sync** — mark task state only after proof exists. + +## Operating Discipline + +- If a CafeKit skill may apply, read and use that skill before acting. Do not improvise around an available skill workflow. +- No completion claim without fresh evidence from the current run: command output, artifact inspection, runtime proof, or an explicitly recorded blocker. +- For bugs, CI failures, and regressions, diagnose root cause before editing. Symptom patches are not completion. +- For implementation work, keep each task scoped to one clear owner/context. Reviewers should receive task files, diffs, and acceptance criteria, not chat history. +- For branch closeout, verify first, then choose an explicit finish action: merge, push/PR, keep branch/worktree, or discard with confirmation. +- If workflow tools such as `Agent` (legacy `Task`), `TaskCreate`, `TaskUpdate`, `TaskList`, `TaskGet`, `AskUserQuestion`, `SendMessage`, or `TodoWrite` are unavailable in the current runtime, do not fail the workflow. Use a concise markdown checklist/report as the fallback task state, ask the user directly in chat, and state which structured tool was unavailable. + +## Definition Of Done + +A task is done only when all apply: + +- implementation satisfies `Completion Criteria` +- `Evidence` is satisfied with concrete proof +- preflight/build/test outcomes are passing or an explicit blocker is recorded +- code review has no critical issues +- a verification receipt exists before task state is synced to `done` + +`NO_TESTS` and `0 tests + exit 0` are not passing outcomes when the task requires automated tests. + +## Non-Negotiable Gates + +- Never bypass hooks. A hook block is an instruction boundary, not an obstacle. +- Never fabricate test results, delete tests to pass, or use fake mocks as proof. +- Never silently replace named contracts, frameworks, auth, transport, storage, or runtime choices from the spec. +- Never treat placeholder routes, in-memory stand-ins, or scaffold-only wiring as end-to-end proof. +- Never claim complete from stale evidence, memory, or a previous run. +- Never modify real `.env` secrets unless explicitly requested; update `.env.example` when env vars change. +- Never commit secrets. AI attribution is optional only when the user or project asks for it. + +## Rule References + +Consult these when the task touches the relevant area: + +- Primary workflow: `./.claude/rules/workflow.md` +- Development rules: `./.claude/rules/ai-dev-rules.md` +- Skill workflow routing: `./.claude/rules/skill-workflow-routing.md` +- Skill domain routing: `./.claude/rules/skill-domain-routing.md` +- Subagent coordination: `./.claude/rules/orchestrator.md` +- Docs maintenance: `./.claude/rules/manage-docs.md` +- State sync: `./.claude/rules/state-sync.md` +- Hook handling: `./.claude/rules/hook-protocols.md` +- Other protocols: `./.claude/rules/*` + +## Skill And Script Use + +- **IMPORTANT:** Analyze the skills catalog and activate the skills that are needed for the task during the process. +- **IMPORTANT:** DO NOT modify skills in `~/.claude/skills` directly. **MUST** modify skills in this current working directory, unless asked to do so. +- Use `./.claude/rules/skill-workflow-routing.md` and `./.claude/rules/skill-domain-routing.md` as advisory routing when choosing a skill. +- Run Python skill scripts with the skill venv: + - macOS/Linux: `.claude/skills/.venv/bin/python3 scripts/