From 8c8b314fa5c2ebfe7622459b627e406601764953 Mon Sep 17 00:00:00 2001 From: hapo-nghialuu Date: Thu, 18 Jun 2026 12:05:09 +0700 Subject: [PATCH 1/7] feat(specs): reduce tollgate context + harden validator (timestamps, sub-criteria) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P0: spec-state hook emits full Tollgate block only on state change; unchanged turns get a one-line reminder (~460 tok/turn saved). Fingerprint cached in .claude/hooks/.logs/tollgate-last.txt (gitignored, fail-open). P1b: validator fails when requirements_done/design_done/tasks_done reuse timestamps.init — prompt-only rule moved to deterministic backstop. P1d: validator verifies per-criterion coverage (R3.4), not just group (R3). Progressive: enforced only when requirements.md uses explicit R{N}.{M} literals + numeric task mapping; legacy numbered-list specs skipped (no false failures). --- docs/project-changelog.md | 7 ++ packages/spec/src/claude/hooks/spec-state.cjs | 21 ++++++ .../claude/scripts/validate-spec-output.cjs | 68 ++++++++++++++++++- 3 files changed, 93 insertions(+), 3 deletions(-) diff --git a/docs/project-changelog.md b/docs/project-changelog.md index 1e83c922..1ad61fc3 100644 --- a/docs/project-changelog.md +++ b/docs/project-changelog.md @@ -3,6 +3,13 @@ All notable changes to CafeKit are documented here, following [Keep a Changelog](https://keepachangelog.com/). +## [Unreleased] + +### Changed +- **Specs context optimization (P0 + P1b)**: The `spec-state` UserPromptSubmit hook now emits the full Tollgate block only when spec state (phase + done/total tasks) actually changes; unchanged turns get a one-line reminder, cutting repeated per-turn context (~460 tokens/turn). State fingerprint cached in `.claude/hooks/.logs/tollgate-last.txt` (gitignored, fail-open). +- **Deterministic timestamp check**: `validate-spec-output.cjs` now fails a spec when `timestamps.requirements_done` / `design_done` / `tasks_done` reuse `timestamps.init`, moving a previously prompt-only rule into the hard validator backstop. +- **Per-criterion coverage check (P1d)**: `validate-spec-output.cjs` now verifies acceptance-criterion coverage at sub-level (e.g. `R3.4`), not just the requirement group (`R3`). Enforced only when `requirements.md` declares explicit `R{N}.{M}` literals and tasks use the numeric `_Requirements: x.y_` mapping; specs using the legacy numbered-list format are skipped (no false failures). Closes the "phantom traceability" gap where a task referencing `R3` counted as covering every `R3.x` criterion. + ## [0.12.0] - 2026-06-16 ### Added diff --git a/packages/spec/src/claude/hooks/spec-state.cjs b/packages/spec/src/claude/hooks/spec-state.cjs index 8f9c1743..5f39accc 100755 --- a/packages/spec/src/claude/hooks/spec-state.cjs +++ b/packages/spec/src/claude/hooks/spec-state.cjs @@ -77,6 +77,27 @@ try { return status === 'pending' && deps.every((dep) => taskStatusByPath.get(dep) === 'done'); }); + // ── State-change gate: only emit the full tollgate when spec state changed ── + // This hook runs on EVERY UserPromptSubmit. Re-printing the whole block when + // phase + done/total are unchanged is wasted context (~460 tok/turn). We keep + // a tiny state fingerprint in a temp file: same -> one-line reminder; changed + // -> full block (and refresh the fingerprint). Fail-open at every step. + const stateKey = `${phase}|${taskCounts.done || 0}/${taskEntries.length}`; + const cacheFile = path.join(__dirname, '.logs', 'tollgate-last.txt'); + + let lastKey = ''; + try { lastKey = fs.readFileSync(cacheFile, 'utf8').trim(); } catch { /* first run */ } + + if (lastKey === stateKey) { + console.log(`\n> 🔵 Spec \`${featureName}\` @ \`${phase}\` (${taskCounts.done || 0}/${taskEntries.length} tasks done). Tollgate active — sync \`spec.json\` when state changes.\n`); + process.exit(0); + } + + try { + fs.mkdirSync(path.dirname(cacheFile), { recursive: true }); + fs.writeFileSync(cacheFile, stateKey); + } catch { /* fail-open: if the write fails we still print the full block */ } + // Format the output const lines = []; lines.push(''); diff --git a/packages/spec/src/claude/scripts/validate-spec-output.cjs b/packages/spec/src/claude/scripts/validate-spec-output.cjs index 66a7271a..fd214ba2 100755 --- a/packages/spec/src/claude/scripts/validate-spec-output.cjs +++ b/packages/spec/src/claude/scripts/validate-spec-output.cjs @@ -85,6 +85,25 @@ function extractRequirementIds(requirementsText) { return [...ids].filter((id) => id !== 'R0').sort(); } +/** + * Extract sub-criteria IDs (e.g. R3.4) ONLY when requirements.md declares them + * as explicit literals — bold `**R3.4**` or a line-leading `R3.4`. Specs that + * write acceptance criteria as a plain numbered list under a heading declare no + * such literals, so this returns empty and per-criterion coverage is skipped + * for them (no behaviour change). This is a progressive check: it rewards an + * unambiguous format, it never penalises the legacy one. + */ +function extractSubCriteriaIds(requirementsText) { + const ids = new Set(); + const re = /(?:^|\s)\**(R\d+\.\d+)\**/gim; + let match; + while ((match = re.exec(requirementsText)) !== null) { + const id = match[1].toUpperCase(); + if (!id.startsWith('R0.')) ids.add(id); + } + return [...ids].sort(); +} + function validateTaskSections(taskPath, content, errors) { const hasContext = hasHeading(content, 'Context'); const hasConstraints = hasHeading(content, 'Constraints'); @@ -113,6 +132,27 @@ function validateTaskSections(taskPath, content, errors) { } } +/** + * 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 + * be a prompt-only rule the model had to remember; here it is a hard backstop. + */ +function validateTimestamps(spec, errors) { + const ts = spec.timestamps; + if (!ts || typeof ts !== 'object') return; + const init = ts.init; + if (!init) return; + + for (const phase of ['requirements_done', 'design_done', 'tasks_done']) { + if (ts[phase] && ts[phase] === init) { + errors.push( + `spec.json.timestamps.${phase}: reuses init timestamp (${init}); ` + + 'each phase must stamp its own completion time', + ); + } + } +} + function validateSpec(specDir) { const errors = []; const warnings = []; @@ -141,6 +181,8 @@ function validateSpec(specDir) { errors.push('spec.json.scope_lock: must be an object, not a boolean or array'); } + validateTimestamps(spec, errors); + const taskFiles = listTaskFiles(specDir); const taskFileSet = new Set(taskFiles); @@ -236,11 +278,15 @@ function validateSpec(specDir) { } let requirementIds = []; + let subCriteriaIds = []; if (fs.existsSync(requirementsPath)) { - requirementIds = extractRequirementIds(fs.readFileSync(requirementsPath, 'utf8')); + const requirementsText = fs.readFileSync(requirementsPath, 'utf8'); + requirementIds = extractRequirementIds(requirementsText); + subCriteriaIds = extractSubCriteriaIds(requirementsText); } const coveredRequirementIds = new Set(); + const coveredSubCriteriaIds = new Set(); for (const taskFile of taskFiles) { const fullPath = path.join(specDir, taskFile); const content = fs.readFileSync(fullPath, 'utf8'); @@ -256,8 +302,12 @@ function validateSpec(specDir) { const numericMappingRe = /_Requirements:\s*([^_\n]+)_/gi; while ((match = numericMappingRe.exec(content)) !== null) { for (const token of match[1].split(',')) { - const number = token.trim().match(/^(\d+)(?:\.\d+)?$/); - if (number) coveredRequirementIds.add(`R${number[1]}`); + const trimmed = token.trim(); + const major = trimmed.match(/^(\d+)(?:\.\d+)?$/); + if (major) coveredRequirementIds.add(`R${major[1]}`); + // Record the full sub-criterion (e.g. 3.4 -> R3.4) for per-criterion coverage. + const sub = trimmed.match(/^(\d+\.\d+)$/); + if (sub) coveredSubCriteriaIds.add(`R${sub[1]}`); } } } @@ -268,6 +318,18 @@ function validateSpec(specDir) { } } + // Per-criterion coverage: only enforced when requirements.md declares explicit + // R{N}.{M} literals AND tasks use the numeric `_Requirements: x.y_` mapping. + // If a spec declares sub-criteria but no task maps any at sub-level, that is the + // legacy coarse format (major-only) — skip silently to avoid false failures. + if (subCriteriaIds.length > 0 && coveredSubCriteriaIds.size > 0) { + for (const subId of subCriteriaIds) { + if (!coveredSubCriteriaIds.has(subId)) { + errors.push(`requirements.md:${subId}: acceptance criterion not covered by any task`); + } + } + } + if (spec.ready_for_implementation === true && errors.length > 0) { errors.push('spec.json.ready_for_implementation: cannot be true while validator errors exist'); } From 13ea79e506a84949bc510bc42ed16b7cd2dbe672 Mon Sep 17 00:00:00 2001 From: hapo-nghialuu Date: Thu, 18 Jun 2026 12:09:16 +0700 Subject: [PATCH 2/7] refactor(specs): de-duplicate SKILL.md audit rules already enforced by validator Step 9.5 Finalization Audit and Pre-Finalization Checklist now separate validator-enforced checks (collapsed to one pointer line) from judgment-only checks the validator cannot see. Removes hand-restated rules the deterministic validator already hard-fails on; preserves every semantic rule verbatim (deletion policy, provider drift, decision propagation, EARS measurability, Mermaid diagrams). ~290 tokens off the always-loaded skill body, no loss of enforcement. --- docs/project-changelog.md | 1 + .../spec/src/claude/skills/specs/SKILL.md | 54 +++++++------------ 2 files changed, 21 insertions(+), 34 deletions(-) diff --git a/docs/project-changelog.md b/docs/project-changelog.md index 1ad61fc3..8bae03d3 100644 --- a/docs/project-changelog.md +++ b/docs/project-changelog.md @@ -9,6 +9,7 @@ All notable changes to CafeKit are documented here, following - **Specs context optimization (P0 + P1b)**: The `spec-state` UserPromptSubmit hook now emits the full Tollgate block only when spec state (phase + done/total tasks) actually changes; unchanged turns get a one-line reminder, cutting repeated per-turn context (~460 tokens/turn). State fingerprint cached in `.claude/hooks/.logs/tollgate-last.txt` (gitignored, fail-open). - **Deterministic timestamp check**: `validate-spec-output.cjs` now fails a spec when `timestamps.requirements_done` / `design_done` / `tasks_done` reuse `timestamps.init`, moving a previously prompt-only rule into the hard validator backstop. - **Per-criterion coverage check (P1d)**: `validate-spec-output.cjs` now verifies acceptance-criterion coverage at sub-level (e.g. `R3.4`), not just the requirement group (`R3`). Enforced only when `requirements.md` declares explicit `R{N}.{M}` literals and tasks use the numeric `_Requirements: x.y_` mapping; specs using the legacy numbered-list format are skipped (no false failures). Closes the "phantom traceability" gap where a task referencing `R3` counted as covering every `R3.x` criterion. +- **SKILL.md de-duplication (#2)**: `specs/SKILL.md` Step 9.5 Finalization Audit and the Pre-Finalization Checklist now split into a "validator-enforced" group (collapsed to one line pointing at `validate-spec-output.cjs`) and a "judgment-only" group the validator cannot see. Removes hand-restated rules that the deterministic validator already hard-fails on; all semantic rules (deletion policy, provider drift, decision propagation, EARS measurability, diagrams) are preserved verbatim. ~290 tokens off the always-loaded skill body with no loss of enforcement. ## [0.12.0] - 2026-06-16 diff --git a/packages/spec/src/claude/skills/specs/SKILL.md b/packages/spec/src/claude/skills/specs/SKILL.md index 630404b6..3b5d84a6 100644 --- a/packages/spec/src/claude/skills/specs/SKILL.md +++ b/packages/spec/src/claude/skills/specs/SKILL.md @@ -410,30 +410,23 @@ Load: `references/review.md` + `rules/design-review.md` - **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`. ### Step 9.5: Finalization Audit (MANDATORY) -- Re-scan the `tasks/` directory and rebuild `spec.json.task_files` from the real filesystem (sorted, relative paths) -- 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. +- 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. -- FAIL if any task file exists on disk but is missing from `task_files` -- FAIL if any path in `task_files` does not exist on disk -- FAIL if any task file exists on disk but is missing from `task_registry` -- FAIL if any path in `task_registry` does not exist on disk -- FAIL if any task file path does not match `tasks/task-R{N}-{SEQ}-.md` with two-digit `SEQ` (for example `tasks/task-R0-01-project-scaffolding.md`) -- 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. -- FAIL if any requirement or NFR mapping uses non-numeric labels (`NFR-1`, `SEC-1`, etc.) -- FAIL if a task lacks `Completion Criteria` or `Evidence` (existing `Task Test Plan & Verification Evidence` or legacy `Verification & Evidence` is accepted) -- 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. -- FAIL if a UI/app/runtime spec has multiple user-facing task outputs but no final integration/reachability task or final integration section. -- 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). -- 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. -- 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. + +**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. + +**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). +- 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. - FAIL if privacy/delete-data work lacks a single canonical deletion policy. The design MUST explicitly choose either: 1. hard-delete with no re-registration lock, or 2. privacy-preserving re-registration lock using a non-raw identifier (for example `email_hash` / `email_fingerprint`) with a retention period. Tasks and requirements must reuse the same policy verbatim; mixed policies are invalid. -- 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. -- 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` -- If `translation.enabled = true`, ensure `i18n//` exists and was re-synced after the final canonical write; the mirror itself is excluded from validation and never blocks `ready_for_implementation`. -- Only after this audit passes may the system mark `progress.tasks = "done"` and `ready_for_implementation = true` +- FAIL if `validation.status = "completed"` but `timestamps.validation_done` / `review_done`, `updated_at`, and report metadata are not synchronized with the final reviewed state. +- If `validation_recommended = true` and `validation.status` is not `completed` (or an explicit user-accepted risk state), `ready_for_implementation` MUST remain `false`. +- If `translation.enabled = true`, ensure `i18n//` exists and was re-synced after the final canonical write; the mirror is excluded from validation and never blocks `ready_for_implementation`. +- Only after this audit passes may the system mark `progress.tasks = "done"` and `ready_for_implementation = true`. ### Step 10: Completion — Context Reminder (MANDATORY) 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: @@ -583,26 +576,19 @@ The canonical surface is four flags. Bare-verb forms are kept only as silent bac - No over-engineering: if a function suffices, don't create a class ### Pre-Finalization Checklist -Before finalizing any specification, assert all the following: -- [ ] **scope_lock** initialized and respected throughout all phases -- [ ] **Evidence Summary** exists in `research.md` with codebase scout, external research or skip rationale, selected decision, rejected alternatives, and task/test implications -- [ ] **EARS format** applied to all acceptance criteria in requirements.md -- [ ] **Numeric requirement IDs** assigned to every requirement +Before finalizing any specification, assert all the following. + +**Machine-enforced** (run `node .claude/scripts/validate-spec-output.cjs specs/` — 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. + +**Judgment-only** (validator cannot see these — assert manually): +- [ ] **EARS format** applied to all acceptance criteria, with measurable thresholds (no "fast"/"safe"/"robust") - [ ] **Discovery mode** selected and recorded in spec.json.design_context - [ ] **Requirements traceability** matrix present in design.md -- [ ] **Canonical Contracts & Invariants** filled for auth/transport/persistence/artifact-sensitive work -- [ ] **Every task file** maps to at least 1 valid in-scope requirement ID -- [ ] **Every task file** includes `Evidence` with executable or inspectable proof +- [ ] **Canonical Contracts & Invariants** filled for auth/transport/persistence/artifact-sensitive work, and inherited verbatim by every task that touches the contract - [ ] **State Machine Blueprint:** design.md contains Mermaid diagrams for non-trivial flows -- [ ] **Dependency graph complete**: no task can start before its blockers are listed -- [ ] **Risk matrix filled**: likelihood × impact, with mitigation for High items - [ ] **Test strategy defined**: what gets unit tested, integration tested, e2e validated -- [ ] **task_files inventory synced**: no missing or orphaned task references -- [ ] **task_registry synced**: every task file has exactly one machine-state entry with valid status + dependencies -- [ ] **deterministic validator passed**: `node .claude/scripts/validate-spec-output.cjs specs/` -- [ ] **Validation gate consistent**: validation_recommended and validation.status agree with spec risk - [ ] **Provider wording clean**: no stale vendor/provider strings outside allowed research context -- [ ] **spec.json fully updated**: phase, current_phase, progress, timestamps, approvals, design_context +- [ ] **Validation decisions propagated** into implementation-facing sections, not only reports/Risk Assessment ## When TO Use From 3819e3b0087b1ead032f235235b27562e243f6c0 Mon Sep 17 00:00:00 2001 From: hapo-nghialuu Date: Thu, 18 Jun 2026 12:19:30 +0700 Subject: [PATCH 3/7] feat(specs): detect cross-layer contract drift in validator (opt-in) design.md can declare a named contract via `` + a fenced block. Every task that declares `Contracts: NAME` must copy that block verbatim. validate-spec-output.cjs fails the spec when a task's copy diverges from the canonical definition (e.g. orderId vs order_id) or names an undefined contract. Closes the BE/FE/DB contract-drift gap where each layer "inherits verbatim" by hand-copying prose and silent divergence only surfaces at integration. Opt-in: specs without contract markers are unaffected. templates/design.md documents the syntax. Verified on a synthetic BE+FE fixture (match PASS, field rename FAIL, unknown name FAIL) + regression on both real specs (no effect). --- docs/project-changelog.md | 1 + .../claude/scripts/validate-spec-output.cjs | 71 +++++++++++++++++++ .../claude/skills/specs/templates/design.md | 6 ++ 3 files changed, 78 insertions(+) diff --git a/docs/project-changelog.md b/docs/project-changelog.md index 8bae03d3..481f8a15 100644 --- a/docs/project-changelog.md +++ b/docs/project-changelog.md @@ -10,6 +10,7 @@ All notable changes to CafeKit are documented here, following - **Deterministic timestamp check**: `validate-spec-output.cjs` now fails a spec when `timestamps.requirements_done` / `design_done` / `tasks_done` reuse `timestamps.init`, moving a previously prompt-only rule into the hard validator backstop. - **Per-criterion coverage check (P1d)**: `validate-spec-output.cjs` now verifies acceptance-criterion coverage at sub-level (e.g. `R3.4`), not just the requirement group (`R3`). Enforced only when `requirements.md` declares explicit `R{N}.{M}` literals and tasks use the numeric `_Requirements: x.y_` mapping; specs using the legacy numbered-list format are skipped (no false failures). Closes the "phantom traceability" gap where a task referencing `R3` counted as covering every `R3.x` criterion. - **SKILL.md de-duplication (#2)**: `specs/SKILL.md` Step 9.5 Finalization Audit and the Pre-Finalization Checklist now split into a "validator-enforced" group (collapsed to one line pointing at `validate-spec-output.cjs`) and a "judgment-only" group the validator cannot see. Removes hand-restated rules that the deterministic validator already hard-fails on; all semantic rules (deletion policy, provider drift, decision propagation, EARS measurability, diagrams) are preserved verbatim. ~290 tokens off the always-loaded skill body with no loss of enforcement. +- **Cross-layer contract drift check (#3)**: `validate-spec-output.cjs` can now detect BE/FE/DB contract drift. When `design.md` defines a named contract via `` + a fenced block, every task that declares `Contracts: NAME` must copy that block verbatim; the validator fails the spec on a divergent copy (e.g. `orderId` vs `order_id`) or an unknown contract name. Fully opt-in — specs without contract markers are unaffected. `templates/design.md` documents the syntax. Verified on a synthetic BE+FE fixture (match → PASS, field rename → FAIL, unknown name → FAIL) plus regression on the two real specs (no effect). ## [0.12.0] - 2026-06-16 diff --git a/packages/spec/src/claude/scripts/validate-spec-output.cjs b/packages/spec/src/claude/scripts/validate-spec-output.cjs index fd214ba2..093cc94d 100755 --- a/packages/spec/src/claude/scripts/validate-spec-output.cjs +++ b/packages/spec/src/claude/scripts/validate-spec-output.cjs @@ -153,6 +153,54 @@ function validateTimestamps(spec, errors) { } } +/** Normalize a fenced code block body for byte-comparison: trim + collapse + * trailing whitespace per line + drop blank edges. Keeps inner structure so a + * real field rename (user_name vs userName) still differs. */ +function normalizeBlock(body) { + return body + .split('\n') + .map((line) => line.replace(/\s+$/, '')) + .join('\n') + .replace(/^\n+|\n+$/g, ''); +} + +/** + * Parse canonical contract definitions from design.md. A definition is an HTML + * marker `` immediately followed by a fenced code block. + * Returns a Map. Empty when the spec uses no markers — + * which makes the whole cross-layer check opt-in (no effect on legacy specs). + */ +function extractContractDefs(designText) { + const defs = new Map(); + const re = /\s*\n```[^\n]*\n([\s\S]*?)\n```/g; + let match; + while ((match = re.exec(designText)) !== null) { + defs.set(match[1], normalizeBlock(match[2])); + } + return defs; +} + +/** + * From a task body, return the contracts it claims plus the first fenced block + * it carries (the task's local copy of the contract). Shape: + * { names: string[], block: string|null } + * `names` come from a `Contracts: A, B` line; `block` is the first fenced code + * block in the task (normalized) used to compare against the canonical defs. + */ +function extractTaskContracts(taskText) { + const names = []; + const nameLine = taskText.match(/^\s*Contracts:\s*([^\n]+)$/im); + if (nameLine) { + for (const token of nameLine[1].split(',')) { + const name = token.trim(); + if (name) names.push(name); + } + } + const blockMatch = taskText.match(/```[^\n]*\n([\s\S]*?)\n```/); + const block = blockMatch ? normalizeBlock(blockMatch[1]) : null; + return { names, block }; +} + function validateSpec(specDir) { const errors = []; const warnings = []; @@ -287,6 +335,11 @@ function validateSpec(specDir) { const coveredRequirementIds = new Set(); const coveredSubCriteriaIds = new Set(); + // Cross-layer contract defs (opt-in): empty unless design.md uses + // markers, so legacy specs are unaffected. + const contractDefs = fs.existsSync(designPath) + ? extractContractDefs(fs.readFileSync(designPath, 'utf8')) + : new Map(); for (const taskFile of taskFiles) { const fullPath = path.join(specDir, taskFile); const content = fs.readFileSync(fullPath, 'utf8'); @@ -310,6 +363,24 @@ function validateSpec(specDir) { if (sub) coveredSubCriteriaIds.add(`R${sub[1]}`); } } + + // Cross-layer contract check (opt-in via design.md markers). When a task + // claims `Contracts: NAME`, that name must exist in design.md and the task's + // local copy of the block must match the canonical definition byte-for-byte + // (after whitespace normalization). This catches BE/FE drift like + // user_name vs userName before integration. + if (contractDefs.size > 0) { + const { names, block } = extractTaskContracts(content); + for (const name of names) { + if (!contractDefs.has(name)) { + errors.push(`${taskFile}: declares unknown contract "${name}" (not defined in design.md)`); + continue; + } + if (block !== null && block !== contractDefs.get(name)) { + errors.push(`${taskFile}: contract "${name}" body diverges from the canonical definition in design.md`); + } + } + } } for (const requirementId of requirementIds) { diff --git a/packages/spec/src/claude/skills/specs/templates/design.md b/packages/spec/src/claude/skills/specs/templates/design.md index 6914c756..ac927b34 100644 --- a/packages/spec/src/claude/skills/specs/templates/design.md +++ b/packages/spec/src/claude/skills/specs/templates/design.md @@ -79,6 +79,12 @@ Capture only the contracts whose inconsistency would break downstream implementa > 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. +### Machine-checkable contracts (optional, recommended for cross-layer work) + +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 `` 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" }`. + +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 `` markers are unaffected. + ## System Flows Provide only the diagrams needed to explain non-trivial flows. Use pure Mermaid syntax. Common patterns: From 21da9cd96c6f24f23ed445413e4b231014f9934e Mon Sep 17 00:00:00 2001 From: hapo-nghialuu Date: Thu, 18 Jun 2026 12:57:37 +0700 Subject: [PATCH 4/7] docs(specs): route SKILL.md design step to named contract blocks for cross-layer specs Tells the design phase to declare cross-layer data shapes as blocks so the new validator drift check actually engages on BE/FE/DB specs, instead of the syntax sitting unused in the template. --- packages/spec/src/claude/skills/specs/SKILL.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/spec/src/claude/skills/specs/SKILL.md b/packages/spec/src/claude/skills/specs/SKILL.md index 3b5d84a6..3ff049ae 100644 --- a/packages/spec/src/claude/skills/specs/SKILL.md +++ b/packages/spec/src/claude/skills/specs/SKILL.md @@ -310,6 +310,7 @@ Load: `references/scope-inquiry.md` - 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. - Add diagrams only when design has multi-step or cross-boundary flows - 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. +- 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** — `` 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. - Update `spec.json` phase, timestamps, discovery mode ### Step 7: Task Breakdown From b9afacd5814b891887985c2a9dc1ea398443830e Mon Sep 17 00:00:00 2001 From: hapo-nghialuu Date: Thu, 18 Jun 2026 14:21:37 +0700 Subject: [PATCH 5/7] refactor(specs): remove Task Hydration step from pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drops Step 8 (Task Hydration) and deletes references/task-hydration.md. It created session-scoped Claude Tasks that died with the session and were re-derived from task files regardless; develop reads task files directly, so the hydration step was effectively dead weight. Pipeline renumbered: 1-7 + Validation (8) + Finalization Audit (8.5) + Completion (9). Task files + task_registry remain the single source of truth — no behavior loss. --- docs/project-changelog.md | 1 + .../spec/src/claude/skills/specs/SKILL.md | 30 ++---- .../skills/specs/references/task-hydration.md | 93 ------------------- 3 files changed, 11 insertions(+), 113 deletions(-) delete mode 100644 packages/spec/src/claude/skills/specs/references/task-hydration.md diff --git a/docs/project-changelog.md b/docs/project-changelog.md index 481f8a15..c25c42dc 100644 --- a/docs/project-changelog.md +++ b/docs/project-changelog.md @@ -10,6 +10,7 @@ All notable changes to CafeKit are documented here, following - **Deterministic timestamp check**: `validate-spec-output.cjs` now fails a spec when `timestamps.requirements_done` / `design_done` / `tasks_done` reuse `timestamps.init`, moving a previously prompt-only rule into the hard validator backstop. - **Per-criterion coverage check (P1d)**: `validate-spec-output.cjs` now verifies acceptance-criterion coverage at sub-level (e.g. `R3.4`), not just the requirement group (`R3`). Enforced only when `requirements.md` declares explicit `R{N}.{M}` literals and tasks use the numeric `_Requirements: x.y_` mapping; specs using the legacy numbered-list format are skipped (no false failures). Closes the "phantom traceability" gap where a task referencing `R3` counted as covering every `R3.x` criterion. - **SKILL.md de-duplication (#2)**: `specs/SKILL.md` Step 9.5 Finalization Audit and the Pre-Finalization Checklist now split into a "validator-enforced" group (collapsed to one line pointing at `validate-spec-output.cjs`) and a "judgment-only" group the validator cannot see. Removes hand-restated rules that the deterministic validator already hard-fails on; all semantic rules (deletion policy, provider drift, decision propagation, EARS measurability, diagrams) are preserved verbatim. ~290 tokens off the always-loaded skill body with no loss of enforcement. +- **Removed Task Hydration step**: dropped Step 8 (Task Hydration) from the `specs` pipeline and deleted `references/task-hydration.md`. It created session-scoped Claude Tasks that died with the session and were re-derived from task files anyway; `develop` reads task files directly. Pipeline renumbered to 1→7 + Validation (8) + Finalization Audit (8.5) + Completion (9). Removes ~1 step and ~1KB of always-referenced guidance with no loss of source-of-truth (task files + `task_registry` unchanged). - **Cross-layer contract drift check (#3)**: `validate-spec-output.cjs` can now detect BE/FE/DB contract drift. When `design.md` defines a named contract via `` + a fenced block, every task that declares `Contracts: NAME` must copy that block verbatim; the validator fails the spec on a divergent copy (e.g. `orderId` vs `order_id`) or an unknown contract name. Fully opt-in — specs without contract markers are unaffected. `templates/design.md` documents the syntax. Verified on a synthetic BE+FE fixture (match → PASS, field rename → FAIL, unknown name → FAIL) plus regression on the two real specs (no effect). ## [0.12.0] - 2026-06-16 diff --git a/packages/spec/src/claude/skills/specs/SKILL.md b/packages/spec/src/claude/skills/specs/SKILL.md index 3ff049ae..9e87025f 100644 --- a/packages/spec/src/claude/skills/specs/SKILL.md +++ b/packages/spec/src/claude/skills/specs/SKILL.md @@ -19,7 +19,7 @@ metadata: This skill provides a 10-step workflow to transform ideas into evidence-backed specs: ``` -Analyze → Dependency Scan → Complexity Assessment → Init → Evidence Gate + Requirements → Design → Tasks → Hydration → Review → Completion +Analyze → Dependency Scan → Complexity Assessment → Init → Evidence Gate + Requirements → Design → Tasks → Review → Completion ``` 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: - Do NOT create `specs//hydration.md`. - Do NOT create shorthand task files such as `tasks/task-R0-1.md`, `tasks/task-R1-1.md`, or `tasks/R0-1-.md`. - The template file name is never the output file name. `templates/spec-state.json` is only the schema source for generated `spec.json`. -- Task hydration is session/task-state synchronization only; it MUST NOT be written as a markdown artifact. - Before marking a spec ready, run the deterministic validator: - `node .claude/scripts/validate-spec-output.cjs specs/` - Any validator failure blocks `ready_for_implementation = true`. @@ -111,7 +110,7 @@ Forbidden generated artifacts: ### Dispatch order 1. `--status` (or bare `status`) → run the status report (see **Subcommands**), then stop. -2. `--validate ` → jump to **Step 9** (the `--validate` rules below are unchanged). +2. `--validate ` → jump to **Step 8** (the `--validate` rules below are unchanged). 3. `--archive` (or bare `archive`) → run the archive workflow, then stop. 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. 5. Otherwise (`/hapo:specs` or `/hapo:specs ""`) → **Interactive State Discovery**. @@ -131,7 +130,7 @@ Goal: ask the user enough to determine **state** and **intent**, then run the ex - 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. 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). 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`). -5. **Run the chosen scope.** On an early stop, emit the Paused Block (Step 10). Sync `spec.json` per `state-sync.md`. +5. **Run the chosen scope.** On an early stop, emit the Paused Block (Step 9b). Sync `spec.json` per `state-sync.md`. > The pipeline, validator, templates, and rules are unchanged. "Stop after Design" simply halts before Step 7; it runs Steps 1–6 exactly as specified. @@ -147,7 +146,7 @@ Shown once (via `AskUserQuestion`) on the no-flag create path, after the descrip Rules: - `--auto` equals choosing **Auto (→ Tasks)** without showing this gate. -- **Stop after Design** / **Step by step** leave `ready_for_implementation=false` and emit the Paused Block (Step 10). +- **Stop after Design** / **Step by step** leave `ready_for_implementation=false` and emit the Paused Block (Step 9b). - Continuing later: run `/hapo:specs` again → Interactive State Discovery detects the unfinished spec and offers the remaining stop points. No flag or keyword required. ### Translation Mirror (optional reference copy) @@ -161,7 +160,7 @@ Load `references/translation-mirror.md`. This is the only language feature in th ### When called WITH `--validate` argument -System IMMEDIATELY jumps to **Step 9: Validation Review**. +System IMMEDIATELY jumps to **Step 8: Validation Review**. The system MUST NOT execute Steps 1-8. Instead, load `references/review.md` and follow it **step-by-step**. #### `--validate` Guardrails (NON-NEGOTIABLE) @@ -216,8 +215,7 @@ flowchart TD CM -->|"Stop after Design"| STOP["Paused at design → emit Paused Block"] CM -->|"Auto / Step by step"| S["Step 7: Tasks — split into individual files"] S --> T["Create tasks/task-R*.md + task_registry"] - T --> U["Step 8: Hydrate Claude Tasks if >= 3 task files"] - U --> V{Review?} + T --> V{Review?} V -->|Yes| W["Run review — auto-pick red team or validation"] V -->|No| X["Update spec.json → DONE"] W --> X @@ -389,14 +387,7 @@ 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. -### Step 8: Task Hydration -Load: `references/task-hydration.md` -- Only run if >= 3 task files -- Convert task files → Claude Tasks with dependency chain (`addBlockedBy`) -- If TaskCreate tool unavailable → fallback to `TodoWrite` -- Task files are the single source of truth — hydration is just a convenience - -### Step 9: Validation Review (Optional) +### 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. - System auto-evaluates spec complexity and decides review depth: @@ -410,7 +401,7 @@ Load: `references/review.md` + `rules/design-review.md` - **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`. -### Step 9.5: Finalization Audit (MANDATORY) +### 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. @@ -429,7 +420,7 @@ Load: `references/review.md` + `rules/design-review.md` - If `translation.enabled = true`, ensure `i18n//` exists and was re-synced after the final canonical write; the mirror is excluded from validation and never blocks `ready_for_implementation`. - Only after this audit passes may the system mark `progress.tasks = "done"` and `ready_for_implementation = true`. -### Step 10: Completion — Context Reminder (MANDATORY) +### Step 9: Completion — Context Reminder (MANDATORY) 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: **Command integrity:** The implementation handoff command is always `/hapo:develop `. Never suggest `/work`, `/code`, or any non-CafeKit alias as the next step for this workflow. @@ -442,7 +433,7 @@ After completing the spec, output a short summary of what was generated, then yo 💡 Tip: Run /clear or start a new chat session before implementing to reduce planning context carryover. ``` -### Step 10b: Paused Block (early stop) +### Step 9b: Paused Block (early stop) 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: @@ -632,7 +623,6 @@ Before finalizing any specification, assert all the following. - `scope-inquiry.md` — 5-Dimension Complexity Assessment (Semantic, Hypothesis, Gap, Risk/Cynefin, Blast Radius) - `research-strategy.md` — Research strategy (7 tools) - `codebase-analysis.md` — Codebase analysis (4 mandatory files) -- `task-hydration.md` — Task files → Claude Tasks conversion - `review.md` — Spec review (auto-decides: red team 8 steps + validate 6 steps) - `archive-workflow.md` — Archive workflow (5 steps) - `translation-mirror.md` — Optional reference-only translation mirror (detection, layout, sync, fields) diff --git a/packages/spec/src/claude/skills/specs/references/task-hydration.md b/packages/spec/src/claude/skills/specs/references/task-hydration.md deleted file mode 100644 index 52da7228..00000000 --- a/packages/spec/src/claude/skills/specs/references/task-hydration.md +++ /dev/null @@ -1,93 +0,0 @@ -# Task Hydration - -## Purpose - -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. - -## When to Run - -- **Default:** Auto-run after task files are created -- **Skip if:** Fewer than 3 task files (overhead not worth it) -- **User opt-out:** When user explicitly says no task tracking - -## Hydrate ↔ Sync-back Diagram - -``` -┌──────────────────┐ Hydrate ┌───────────────────┐ -│ Task Files │ ─────────► │ Claude Tasks │ -│ (persistent) │ │ (session-scoped) │ -│ [ ] task-R0-01 │ │ ◼ pending │ -│ [ ] task-R0-02 │ │ ◼ pending │ -└──────────────────┘ └───────────────────┘ - │ Work - ▼ -┌──────────────────┐ Sync-back ┌───────────────────┐ -│ Task Files │ ◄───────── │ Task Updates │ -│ (updated) │ │ (completed) │ -│ [x] task-R0-01 │ │ ✓ completed │ -│ [ ] task-R0-02 │ │ ◼ in_progress │ -└──────────────────┘ └───────────────────┘ -``` - -- **Hydrate:** Read task files → `TaskCreate` for each unchecked `[ ]` item -- **Work:** `TaskUpdate` tracks in_progress/completed in real-time -- **Sync-back:** Update `[ ]` → `[x]` in task files, update `spec.json` progress - -## Required Metadata for Task Creation - -| Field | Description | Example | -|---|---|---| -| `taskNumber` | Task sequence number | `R0-02` | -| `priority` | Priority level | `P1`, `P2`, `P3` | -| `effort` | Time estimate | `2h` | -| `specDir` | Spec directory path | `specs/mobile-app/` | -| `taskFile` | Task file name | `task-R0-02-extension-shell.md` | - -## Hydration Workflow - -1. Read all `tasks/task-R*.md` files → filter incomplete tasks -2. Load `spec.json.task_registry` and prefer its task status/dependencies when present -3. Create `TaskCreate` for each major task, attach `addBlockedBy` per dependency chain: - ``` - Task R0-01 (no blockers) ← start here - Task R0-02 (addBlockedBy: [task-R0-01-id]) - Task R1-01 (addBlockedBy: [task-R0-02-id]) - ``` -4. Create additional `TaskCreate` for high-risk steps within tasks (if any) -5. Verify: no dependency cycles, all tasks have complete metadata, and every hydrated task has a corresponding `task_registry` entry - -## Fallback - -`TaskCreate`/`TaskUpdate`/`TaskGet`/`TaskList` tools are CLI-only. If running in VSCode extension: -- Use `TodoWrite` as a tracking substitute -- Task files remain the single source of truth -- Hydration is an optimization convenience, NOT a requirement - -## Cook Handoff Protocol - -### Same session (spec → code immediately) -1. Spec creates tasks + hydrates → tasks exist in session -2. Code reads `TaskList` → finds existing tasks → picks them up -3. Skips re-creation, begins implementing directly - -### New session (resume) -1. User runs `/hapo:develop ` in a new session -2. Code reads `TaskList` → empty (tasks died with old session) -3. Code reads task files → re-hydrates from `[ ]` items -4. `[x]` items = done, skip those - -### Sync-back (after code completes) -1. `TaskUpdate` marks tasks as completed -2. Update `[ ]` → `[x]` in task files -3. Update `spec.json.task_registry[path].status`, timestamps, blocker, and last_updated_at -4. Update `spec.json` progress for the corresponding phase -5. Git commit captures the state transition - -## Quality Checks - -After hydration, verify: -- [ ] Dependency chain has no cycles -- [ ] All task files have corresponding Tasks -- [ ] Required metadata fields are complete -- [ ] Task count matches `[ ]` items in files -- Output: `✓ Hydrated [N] tasks with dependency chain` From adf9295e4ccc8ff4a94e902745506f997390dd07 Mon Sep 17 00:00:00 2001 From: hapo-nghialuu Date: Thu, 18 Jun 2026 14:59:45 +0700 Subject: [PATCH 6/7] refactor(specs): merge tasks-parallel-analysis into tasks-generation The 34-line rules/tasks-parallel-analysis.md was ~90% a restatement of tasks-generation.md's Parallel Analysis section. Merged its 3 unique points (env/setup precondition, (P) outside checkbox brackets, skip container-only majors) into tasks-generation.md and deleted the file. Step 7 no longer loads a separate parallel-analysis rule. No behavior change; one fewer file to load. --- docs/project-changelog.md | 1 + .../spec/src/claude/skills/specs/SKILL.md | 4 +-- .../skills/specs/rules/tasks-generation.md | 4 ++- .../specs/rules/tasks-parallel-analysis.md | 34 ------------------- 4 files changed, 5 insertions(+), 38 deletions(-) delete mode 100644 packages/spec/src/claude/skills/specs/rules/tasks-parallel-analysis.md diff --git a/docs/project-changelog.md b/docs/project-changelog.md index c25c42dc..ff9b64ac 100644 --- a/docs/project-changelog.md +++ b/docs/project-changelog.md @@ -12,6 +12,7 @@ All notable changes to CafeKit are documented here, following - **SKILL.md de-duplication (#2)**: `specs/SKILL.md` Step 9.5 Finalization Audit and the Pre-Finalization Checklist now split into a "validator-enforced" group (collapsed to one line pointing at `validate-spec-output.cjs`) and a "judgment-only" group the validator cannot see. Removes hand-restated rules that the deterministic validator already hard-fails on; all semantic rules (deletion policy, provider drift, decision propagation, EARS measurability, diagrams) are preserved verbatim. ~290 tokens off the always-loaded skill body with no loss of enforcement. - **Removed Task Hydration step**: dropped Step 8 (Task Hydration) from the `specs` pipeline and deleted `references/task-hydration.md`. It created session-scoped Claude Tasks that died with the session and were re-derived from task files anyway; `develop` reads task files directly. Pipeline renumbered to 1→7 + Validation (8) + Finalization Audit (8.5) + Completion (9). Removes ~1 step and ~1KB of always-referenced guidance with no loss of source-of-truth (task files + `task_registry` unchanged). - **Cross-layer contract drift check (#3)**: `validate-spec-output.cjs` can now detect BE/FE/DB contract drift. When `design.md` defines a named contract via `` + a fenced block, every task that declares `Contracts: NAME` must copy that block verbatim; the validator fails the spec on a divergent copy (e.g. `orderId` vs `order_id`) or an unknown contract name. Fully opt-in — specs without contract markers are unaffected. `templates/design.md` documents the syntax. Verified on a synthetic BE+FE fixture (match → PASS, field rename → FAIL, unknown name → FAIL) plus regression on the two real specs (no effect). +- **Merged tasks-parallel-analysis into tasks-generation (A1)**: deleted `rules/tasks-parallel-analysis.md` (~90% a restatement of `tasks-generation.md`'s Parallel Analysis section). Its 3 unique points (env/setup precondition, `(P)` outside checkbox brackets, skip container-only majors) were merged into `tasks-generation.md`. Step 7 loads one fewer rule file; no behavior change. ## [0.12.0] - 2026-06-16 diff --git a/packages/spec/src/claude/skills/specs/SKILL.md b/packages/spec/src/claude/skills/specs/SKILL.md index 9e87025f..ae709499 100644 --- a/packages/spec/src/claude/skills/specs/SKILL.md +++ b/packages/spec/src/claude/skills/specs/SKILL.md @@ -318,7 +318,6 @@ Load: `references/scope-inquiry.md` - 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. -- Load `rules/tasks-parallel-analysis.md` for parallel markers (default: enabled) - 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. - 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`. @@ -613,8 +612,7 @@ Before finalizing any specification, assert all the following. - `design-discovery-light.md` — Lightweight research workflow - `design-review.md` — Design review GO/NO-GO process - `phase-decision-matrix.md` — Implementation slice/task-cluster boundary rules -- `tasks-generation.md` — Task generation rules (includes spike task rules) -- `tasks-parallel-analysis.md` — Parallel task analysis +- `tasks-generation.md` — Task generation rules (includes spike task rules + parallel analysis) - `task-scoring-rubric.md` — Task priority, split/merge, spike, dependency, and evidence-depth scoring ### References (`references/`) 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 818f86d4..9dfaba44 100644 --- a/packages/spec/src/claude/skills/specs/rules/tasks-generation.md +++ b/packages/spec/src/claude/skills/specs/rules/tasks-generation.md @@ -212,13 +212,15 @@ Choose verification by task risk and touched surface. Do not force every task to - No data dependency on other pending tasks - No shared file or resource contention - No prerequisite review/approval from another task + - Environment/setup work needed by the task is already satisfied or covered within the task itself - Validate that identified parallel tasks operate within separate boundaries defined in the Architecture Pattern & Boundary Map. - Confirm API/event contracts from design.md do not overlap in ways that cause conflicts. -- Append `(P)` immediately after the task number for each parallel-capable task: +- Append `(P)` immediately after the task number for each parallel-capable task, kept **outside** the checkbox brackets: - Example: `- [ ] 2.1 (P) Build background worker` - Apply to both major tasks and sub-tasks when appropriate. - If sequential mode is requested, omit `(P)` markers entirely. - Group parallel tasks logically (same parent when possible) and highlight any ordering caveats in detail bullets. +- Do not mark container-only major tasks (no own actionable bullets) with `(P)` — evaluate parallelism at the sub-task level. - Explicitly call out dependencies that prevent `(P)` even when tasks look similar. ### Checkbox Format diff --git a/packages/spec/src/claude/skills/specs/rules/tasks-parallel-analysis.md b/packages/spec/src/claude/skills/specs/rules/tasks-parallel-analysis.md deleted file mode 100644 index 5484424b..00000000 --- a/packages/spec/src/claude/skills/specs/rules/tasks-parallel-analysis.md +++ /dev/null @@ -1,34 +0,0 @@ -# Parallel Task Analysis Rules - -## Purpose -Provide a consistent way to identify implementation tasks that can be safely executed in parallel while generating individual task files. - -## When to Consider Tasks Parallel -Only mark a task as parallel-capable when **all** of the following are true: - -1. **No data dependency** on pending tasks. -2. **No conflicting files or shared mutable resources** are touched. -3. **No prerequisite review/approval** from another task is required beforehand. -4. **Environment/setup work** needed by this task is already satisfied or covered within the task itself. - -## Marking Convention -- Append `(P)` immediately after the numeric identifier for each qualifying task. - - Example: `- [ ] 2.1 (P) Build background worker for emails` -- Apply `(P)` to both major tasks and sub-tasks when appropriate. -- If sequential execution is requested (e.g. via `--sequential` flag), omit `(P)` markers entirely. -- Keep `(P)` **outside** of checkbox brackets to avoid confusion with completion state. - -## Grouping & Ordering Guidelines -- Group parallel tasks under the same parent whenever the work belongs to the same theme. -- List obvious prerequisites or caveats in the detail bullets (e.g., "Requires schema migration from 1.2"). -- When two tasks look similar but are not parallel-safe, call out the blocking dependency explicitly. -- Skip marking container-only major tasks (those without their own actionable detail bullets) with `(P)`—evaluate parallel execution at the sub-task level instead. - -## Quality Checklist -Before marking a task with `(P)`, ensure you have: - -- Verified that running this task concurrently will not create merge or deployment conflicts. -- Captured any shared state expectations in the detail bullets. -- Confirmed that the implementation can be tested independently. - -If any check fails, **do not** mark the task with `(P)` and explain the dependency in the task details. From 35ad58fb11b6bf2034577541a10069f19f44d829 Mon Sep 17 00:00:00 2001 From: hapo-nghialuu Date: Thu, 18 Jun 2026 15:08:23 +0700 Subject: [PATCH 7/7] chore(release): bump @haposoft/cafekit to 0.13.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Specs pipeline streamlined (9 steps, Task Hydration removed), lower per-turn tollgate context, leaner SKILL.md, and deterministic validator hardening (timestamps, sub-criteria coverage, cross-layer contract drift — all opt-in). --- docs/project-changelog.md | 2 ++ packages/spec/CHANGELOG.md | 16 ++++++++++++++++ packages/spec/package.json | 2 +- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/docs/project-changelog.md b/docs/project-changelog.md index ff9b64ac..ce7a2a41 100644 --- a/docs/project-changelog.md +++ b/docs/project-changelog.md @@ -5,6 +5,8 @@ All notable changes to CafeKit are documented here, following ## [Unreleased] +## [0.13.0] - 2026-06-18 + ### Changed - **Specs context optimization (P0 + P1b)**: The `spec-state` UserPromptSubmit hook now emits the full Tollgate block only when spec state (phase + done/total tasks) actually changes; unchanged turns get a one-line reminder, cutting repeated per-turn context (~460 tokens/turn). State fingerprint cached in `.claude/hooks/.logs/tollgate-last.txt` (gitignored, fail-open). - **Deterministic timestamp check**: `validate-spec-output.cjs` now fails a spec when `timestamps.requirements_done` / `design_done` / `tasks_done` reuse `timestamps.init`, moving a previously prompt-only rule into the hard validator backstop. diff --git a/packages/spec/CHANGELOG.md b/packages/spec/CHANGELOG.md index 5bc327d7..6b7eae30 100644 --- a/packages/spec/CHANGELOG.md +++ b/packages/spec/CHANGELOG.md @@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.13.0] - 2026-06-18 + +### Changed +- **`hapo:specs` pipeline streamlined to 9 steps.** Removed the Task Hydration step (old Step 8): it created session-scoped Claude Tasks that died with the session and were re-derived from task files anyway, while `hapo:develop` reads task files directly. Task files + `spec.json.task_registry` remain the single source of truth. +- **Lower per-turn context cost.** The `spec-state` tollgate hook now emits its full block only when spec state changes (phase or done/total tasks); unchanged turns get a one-line reminder. +- **Leaner skill body.** De-duplicated `SKILL.md` finalization/checklist rules already enforced by the validator, and merged `rules/tasks-parallel-analysis.md` into `rules/tasks-generation.md` (one fewer rule file). No behavior change. + +### Added +- **Deterministic validator hardening** (`validate-spec-output.cjs`): + - Fails when `timestamps.requirements_done` / `design_done` / `tasks_done` reuse `timestamps.init`. + - Verifies acceptance-criterion coverage at sub-level (e.g. `R3.4`), not just the requirement group — opt-in when `requirements.md` uses explicit `R{N}.{M}` literals. + - Detects cross-layer contract drift: a `` block in `design.md` must be copied verbatim by every task declaring `Contracts: NAME`; divergent copies or unknown names fail. Opt-in via markers; documented in `templates/design.md`. + +### Notes +- All new validator checks are opt-in / progressive — specs in the legacy format are unaffected and continue to pass. + ## [0.11.11] - 2026-06-08 ### Changed diff --git a/packages/spec/package.json b/packages/spec/package.json index eaa70b15..d843f2ef 100644 --- a/packages/spec/package.json +++ b/packages/spec/package.json @@ -1,6 +1,6 @@ { "name": "@haposoft/cafekit", - "version": "0.12.0", + "version": "0.13.0", "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",