@haposoft/cafekit 0.16.0-rc.6 → 0.16.0-rc.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +21 -58
  2. package/bin/__tests__/change-firewall.semantic-firewall.test.js +4 -5
  3. package/bin/__tests__/codex-hooks.test.js +137 -0
  4. package/bin/__tests__/codex-native.test.js +130 -74
  5. package/bin/__tests__/develop-contract.test.js +155 -76
  6. package/bin/__tests__/installer-safety.test.js +19 -154
  7. package/bin/__tests__/package-inventory.test.js +10 -20
  8. package/bin/__tests__/release-preflight.semantic-firewall.test.js +2 -2
  9. package/bin/__tests__/validator-grounding.test.js +11 -11
  10. package/bin/install.js +2 -7
  11. package/bin/lib/context.js +0 -45
  12. package/bin/lib/copy-utils.js +1 -1
  13. package/bin/lib/i18n.js +0 -3
  14. package/bin/lib/manifest.js +1 -1
  15. package/bin/phases/claude-runtime.js +1 -3
  16. package/bin/phases/codex-runtime.js +4 -2
  17. package/bin/phases/copy-payload.js +3 -15
  18. package/bin/phases/post-install.js +5 -17
  19. package/bin/phases/root-config.js +2 -3
  20. package/bin/phases/select-platform.js +2 -5
  21. package/bin/phases/summary.js +0 -1
  22. package/package.json +2 -5
  23. package/src/claude/CLAUDE.md +19 -6
  24. package/src/claude/agents/project-manager.md +41 -9
  25. package/src/claude/agents/spec-maker.md +76 -155
  26. package/src/claude/gitignore +1 -1
  27. package/src/claude/hooks/__tests__/spec-gate.test.js +162 -0
  28. package/src/claude/hooks/lib/context.cjs +1 -1
  29. package/src/claude/hooks/lib/detect.cjs +1 -1
  30. package/src/claude/hooks/spec-gate.cjs +44 -9
  31. package/src/claude/hooks/spec-state.cjs +22 -11
  32. package/src/claude/hooks/task-scaffold-guard.cjs +1 -1
  33. package/src/claude/migration-manifest.json +17 -3
  34. package/src/claude/rules/manage-docs.md +43 -24
  35. package/src/claude/rules/state-sync.md +57 -62
  36. package/src/claude/rules/workflow.md +30 -13
  37. package/src/claude/scripts/change-firewall.cjs +2 -2
  38. package/src/claude/scripts/generate-skill-catalog.cjs +0 -1
  39. package/src/claude/scripts/spec-receipt.cjs +141 -1
  40. package/src/claude/scripts/spec-resolver.cjs +288 -0
  41. package/src/claude/skills/develop/SKILL.md +89 -125
  42. package/src/claude/skills/develop/references/parallel-waves.md +75 -162
  43. package/src/claude/skills/develop/references/quality-gate.md +61 -121
  44. package/src/claude/skills/develop/references/subagent-patterns.md +31 -38
  45. package/src/claude/skills/question/SKILL.md +1 -1
  46. package/src/claude/skills/specs/SKILL.md +142 -204
  47. package/src/claude/skills/specs/references/review.md +109 -243
  48. package/src/claude/skills/specs/references/templates.md +173 -0
  49. package/src/claude/skills/sync/SKILL.md +66 -90
  50. package/src/claude/skills/sync/references/sync-protocols.md +62 -83
  51. package/src/codex/AGENTS.md +20 -7
  52. package/src/codex/hooks/lib/spec-receipt.cjs +18 -0
  53. package/src/codex/hooks/lib/spec-utils.cjs +25 -0
  54. package/src/codex/hooks/spec-gate.cjs +46 -16
  55. package/src/codex/hooks/spec-state.cjs +22 -12
  56. package/src/codex/rules/state-sync.md +57 -61
  57. package/src/common/AGENTS.md +8 -1
  58. package/bin/__tests__/opencode-spec-gate.test.js +0 -799
  59. package/bin/lib/opencode-install.js +0 -899
  60. package/bin/phases/opencode-runtime.js +0 -38
  61. package/src/claude/skills/specs/references/archive-workflow.md +0 -74
  62. package/src/claude/skills/specs/references/ask-user-question-gates.md +0 -70
  63. package/src/claude/skills/specs/references/codebase-analysis.md +0 -102
  64. package/src/claude/skills/specs/references/cross-spec-dependency.md +0 -76
  65. package/src/claude/skills/specs/references/research-strategy.md +0 -107
  66. package/src/claude/skills/specs/references/scope-inquiry.md +0 -107
  67. package/src/claude/skills/specs/references/translation-mirror.md +0 -83
  68. package/src/claude/skills/specs/rules/design-discovery-full.md +0 -45
  69. package/src/claude/skills/specs/rules/design-discovery-light.md +0 -28
  70. package/src/claude/skills/specs/rules/design-principles.md +0 -138
  71. package/src/claude/skills/specs/rules/design-review.md +0 -55
  72. package/src/claude/skills/specs/rules/ears-format.md +0 -42
  73. package/src/claude/skills/specs/rules/phase-decision-matrix.md +0 -56
  74. package/src/claude/skills/specs/rules/task-scoring-rubric.md +0 -52
  75. package/src/claude/skills/specs/rules/tasks-generation.md +0 -189
  76. package/src/opencode/AGENTS.md +0 -41
  77. package/src/opencode/plugins/docs-sync.ts +0 -175
  78. package/src/opencode/plugins/inspect-block.ts +0 -164
  79. package/src/opencode/plugins/privacy-block.ts +0 -182
  80. package/src/opencode/plugins/rules.ts +0 -155
  81. package/src/opencode/plugins/session.ts +0 -179
  82. package/src/opencode/plugins/spec-gate.ts +0 -457
  83. package/src/opencode/plugins/spec-state.ts +0 -311
  84. package/src/opencode/plugins/state.ts +0 -253
  85. package/src/opencode/plugins/task-scaffold-guard.ts +0 -161
  86. package/src/opencode/plugins/usage.ts +0 -178
@@ -73,14 +73,17 @@ try {
73
73
  if (fs.existsSync(p)) runtime = JSON.parse(fs.readFileSync(p, 'utf8'));
74
74
  } catch { /* ignore */ }
75
75
 
76
- // Reminder toggle — same key the OpenCode spec-state plugin honors.
76
+ // Reminder toggle.
77
77
  // (The Stop completion gate has its own toggle: spec.completion_gate.)
78
78
  if (runtime.spec && runtime.spec.tollgate === false) process.exit(0);
79
79
 
80
80
  const baseDir = process.env.PROJECT_ROOT || cwd;
81
81
  const explicitFeature = payload.featureName || payload.feature || payload.explicitFeature || null;
82
82
  const explicitPath = payload.specPath || payload.spec_path || payload.featurePath || null;
83
- const resolved = RESOLVER.resolveActiveSpec({ projectRoot: baseDir, runtime, explicitFeature, explicitPath });
83
+ const resolveWorkflow = typeof RESOLVER.resolveWorkflowCandidate === 'function'
84
+ ? RESOLVER.resolveWorkflowCandidate
85
+ : RESOLVER.resolveActiveSpec;
86
+ const resolved = resolveWorkflow({ projectRoot: baseDir, runtime, explicitFeature, explicitPath });
84
87
 
85
88
  if (!resolved) {
86
89
  process.exit(0); // No active spec, do nothing
@@ -108,19 +111,20 @@ try {
108
111
  process.exit(0);
109
112
  }
110
113
 
111
- const activeSpec = resolved.spec;
114
+ const activeSpec = resolved.spec || {};
115
+ const processWorkflow = resolved.layoutKind === 'process-v3';
112
116
  const featureName = resolved.featureName;
113
117
  const specsPath = resolved.specsDir;
114
118
  const runtimeContext = POLICY.deriveRuntimeContext({
115
119
  projectRoot: baseDir,
116
120
  specsRoot: specsPath,
117
- specFile: resolved.specFile || path.join(specsPath, featureName, 'spec.json'),
121
+ specFile: resolved.stateFile || resolved.specFile || path.join(specsPath, featureName, 'spec.json'),
118
122
  featureName,
119
123
  runtimeSession: sessionIdentity(payload),
120
124
  });
121
125
 
122
- const phase = activeSpec.current_phase || activeSpec.phase || 'unknown';
123
- const taskRegistry = activeSpec.task_registry || {};
126
+ const phase = resolved.phase || activeSpec.current_phase || activeSpec.phase || 'unknown';
127
+ const taskRegistry = resolved.taskRegistry || activeSpec.task_registry || {};
124
128
  const flashTasks = POLICY.flashState(taskRegistry);
125
129
  const taskEntries = Object.entries(taskRegistry);
126
130
  const taskCounts = taskEntries.reduce((acc, [, task]) => {
@@ -135,7 +139,8 @@ try {
135
139
  return status === 'pending' && deps.every((dep) => taskStatusByPath.get(dep) === 'done');
136
140
  });
137
141
  const featureDir = path.join(specsPath, featureName);
138
- const featureReceiptPresent = RECEIPT.safeRead(featureDir, 'feature-receipt.md').status === 'ok';
142
+ const featureReceiptPresent = !processWorkflow
143
+ && RECEIPT.safeRead(featureDir, 'feature-receipt.md').status === 'ok';
139
144
 
140
145
  // ── State-change gate: only emit the full tollgate when spec state changed ──
141
146
  // The cache is shared by installed hook copies, so its key must carry the
@@ -164,7 +169,8 @@ try {
164
169
  }
165
170
 
166
171
  if (stateKey && lastKey === stateKey) {
167
- console.log(`\n> 🔵 Spec \`${featureName}\` @ \`${phase}\` (${taskCounts.done || 0}/${taskEntries.length} tasks done). Tollgate active — sync \`spec.json\` when state changes.\n`);
172
+ const stateTarget = processWorkflow ? 'task Markdown' : '`spec.json`';
173
+ console.log(`\n> 🔵 Spec \`${featureName}\` @ \`${phase}\` (${taskCounts.done || 0}/${taskEntries.length} tasks done). Tollgate active — sync ${stateTarget} when state changes.\n`);
168
174
  process.exit(0);
169
175
  }
170
176
 
@@ -187,9 +193,14 @@ try {
187
193
  if (flashTasks.length > 0) {
188
194
  lines.push(`- Flash verification pending: ${flashTasks.map((taskPath) => `\`${taskPath}\``).join(', ')}. A PASS proof keeps the persisted task in_progress until explicit sync-finalize.`);
189
195
  }
190
- lines.push(`- Sync \`spec.json\` + task Markdown status after verified work; task proof belongs in \`receipts/<task-basename>.md\`.`);
191
- lines.push(`- Create \`feature-receipt.md\` once after final integration proof${featureReceiptPresent ? ' (present)' : ' (not required before closeout)'}.`);
192
- lines.push(`- Validate with \`node .claude/scripts/validate-spec-output.cjs specs/${featureName}\`; hooks revalidate receipt bytes but never grant approval.`);
196
+ if (processWorkflow) {
197
+ lines.push('- Sync each flat task `Status:` only after verified work; task proof belongs in that task\'s inline `## Receipt`.');
198
+ lines.push(`- Workflow source: \`specs/${featureName}/plan.md\`; Stop revalidates every task currently marked done.`);
199
+ } else {
200
+ lines.push('- Sync `spec.json` + task Markdown status after verified work; task proof belongs in `receipts/<task-basename>.md`.');
201
+ lines.push(`- Create \`feature-receipt.md\` once after final integration proof${featureReceiptPresent ? ' (present)' : ' (not required before closeout)'}.`);
202
+ lines.push(`- Validate with \`node .claude/scripts/validate-spec-output.cjs specs/${featureName}\`; hooks revalidate receipt bytes but never grant approval.`);
203
+ }
193
204
  lines.push('');
194
205
 
195
206
  console.log(lines.join('\n'));
@@ -67,7 +67,7 @@ try {
67
67
 
68
68
  // Valve 2: block with an actionable message carrying the exact command.
69
69
  // NOTE: deliberately does NOT mention the runtime.json escape hatch — an
70
- // OpenCode smoke test proved the model reads the advertised override and
70
+ // Smoke testing proved the model reads the advertised override and
71
71
  // simply disables the guard itself. The hatch stays functional for humans.
72
72
  const m = norm.match(/(^|\/)specs\/([^/]+)\/tasks\//);
73
73
  const feature = m ? m[2] : '<feature>';
@@ -80,15 +80,29 @@
80
80
  "obsolete": {
81
81
  "agents": {
82
82
  "claude": ["agents/god-developer.md"],
83
- "codex": ["agents/god_developer.toml"],
84
- "opencode": ["agents/god-developer.md"]
83
+ "codex": ["agents/god_developer.toml"]
85
84
  },
86
85
  "runtimeFiles": [
87
86
  "hooks/skill-router.cjs",
88
87
  "hooks/lib/skill-router-routes.cjs",
89
88
  "scripts/browser-tool.cjs",
90
89
  "skills/generate-graph",
91
- "skills/impact-analysis"
90
+ "skills/impact-analysis",
91
+ "skills/specs/references/archive-workflow.md",
92
+ "skills/specs/references/ask-user-question-gates.md",
93
+ "skills/specs/references/codebase-analysis.md",
94
+ "skills/specs/references/cross-spec-dependency.md",
95
+ "skills/specs/references/research-strategy.md",
96
+ "skills/specs/references/scope-inquiry.md",
97
+ "skills/specs/references/translation-mirror.md",
98
+ "skills/specs/rules/design-discovery-full.md",
99
+ "skills/specs/rules/design-discovery-light.md",
100
+ "skills/specs/rules/design-principles.md",
101
+ "skills/specs/rules/design-review.md",
102
+ "skills/specs/rules/ears-format.md",
103
+ "skills/specs/rules/phase-decision-matrix.md",
104
+ "skills/specs/rules/task-scoring-rubric.md",
105
+ "skills/specs/rules/tasks-generation.md"
92
106
  ],
93
107
  "settingsHookCommandSubstrings": [
94
108
  "hooks/skill-router.cjs"
@@ -54,39 +54,58 @@ Use [Keep a Changelog](https://keepachangelog.com/) convention:
54
54
 
55
55
  ---
56
56
 
57
- ## Specification & Execution Tracker (Hapo Protocol)
57
+ ## Process-first specification and execution tracker
58
58
 
59
- ### Where Specs Live
60
- Hapo does not use chaotic plan folders. Requirements and architecture are centralized into a machine-readable Specification structure via `specs`.
59
+ ### Where plans live
61
60
 
62
- All specifications exist strictly in `./specs/<feature-slug>/`.
61
+ Keep every new feature packet in one direct child of `./specs/`. The Markdown
62
+ files are the durable state and must remain readable without a compiler or a
63
+ generated index.
64
+
65
+ ### Primary directory layout
63
66
 
64
- ### Directory Layout
65
67
  ```text
66
68
  specs/
67
69
  └── user-auth/
68
- ├── spec.json # System state machine & global status
69
- ├── design.md # Architecture, requirements, and data flows
70
- └── tasks/
71
- ├── task-R0-01-setup.md # Actionable granular steps for development
72
- └── task-R1-01-api.md # Next requirement-driven task
70
+ ├── plan.md
71
+ ├── task-01-setup.md
72
+ └── task-02-api.md
73
73
  ```
74
74
 
75
- ### The State Machine (`spec.json`)
76
- The `spec.json` is the sole source of truth for the Project State.
77
- When creating or updating a spec, it must accurately reflect the overall progress.
75
+ Task files are direct children beside `plan.md`; do not place them in a task
76
+ subdirectory.
77
+ The plan records the C1 scope decision, explicit exclusions, acceptance
78
+ criteria, and task mapping. Each task owns one usable outcome, bounded paths,
79
+ dependencies, acceptance, and a runnable Verification Plan.
80
+
81
+ ### Gates and execution handoff
82
+
83
+ - C1: the user chooses EXPAND, KEEP, or CUT before the plan is written.
84
+ - C2: the user accepts, rejects, or revises deduplicated adversarial findings.
85
+ - Specs stops after planning; implementation begins only through a new explicit
86
+ Develop invocation.
87
+ - C3: after execution, current receipts and limitations are shown and the user
88
+ decides whether the feature is complete.
78
89
 
79
- ### Architecture Document (`design.md`)
80
- This blueprint covers:
81
- - **Context & Overview:** The "Why" of the feature.
82
- - **Data Flow:** Mandatory Mermaid Data Flow Diagram detailing state transitions, DB interactions, and API payloads.
83
- - **Risk Assessment:** Pre-identified failure points and mitigations.
90
+ ### Task state and proof
84
91
 
85
- ### Execution Checklists (`tasks/task-R*.md`)
86
- Work is decomposed into requirement-driven markdown task files.
87
- Each task file contains:
88
- - **Prerequisites:** Blockers that must clear before this stage begins. (Task N+1 cannot start without Task N defining its payload).
89
- - **Execution Checklist:** Granular `[ ]` markdown items for agents to toggle `[x]` as they implement code.
90
- - **Success Criteria:** Strict definition of "Done".
92
+ Each task contains exactly one `Status:` field. The final inline `## Receipt`
93
+ is canonical proof and must contain the exact command, `Exit: 0`,
94
+ `Verification: PASS`, runtime-derived Base and Head values, and non-empty
95
+ fenced current output before the task becomes done.
96
+
97
+ Sync only observed state with surgical edits. Re-read the changed task after
98
+ every update; never infer missing proof, approval, or readiness. When a done
99
+ task changes user-facing behavior, architecture, API contracts, operations, or
100
+ project status, classify docs impact and update only affected existing docs.
91
101
 
92
102
  Comply with the overarching rules in `./rules/ai-dev-rules.md`.
103
+
104
+ ## Legacy specification layout
105
+
106
+ Existing features containing `spec.json`, nested `tasks/task-R*.md`, or other
107
+ legacy kernel artifacts keep their installed adapters and original layout.
108
+ Preserve `task_registry`, `semantic_model`, `planning_depth`, lane,
109
+ `execution_tier`, machine authority, typed topology, separate receipts, and
110
+ feature closeout files. Do not migrate them as part of unrelated documentation
111
+ work.
@@ -1,62 +1,57 @@
1
- # Specs v2.1 state synchronization
2
-
3
- ## Authority layers
4
-
5
- `spec.json` is machine semantic authority. Requirements, design, conditional
6
- research, and conditional task Markdown are human projections. Host state may
7
- record authority or integrity, but it never supplies product semantics.
8
-
9
- Planning depth and assurance level remain independent. The artifact router runs
10
- before discovery: None persists no spec; Compact and Full persist the bounded
11
- core; research and tasks remain absent unless their semantic triggers exist.
12
-
13
- ## Canonical lifecycle
14
-
15
- The feature lifecycle is exactly `in_progress`, `paused`, `blocked`, or `done`.
16
- Authoring state is independently `draft`, `validated`, or `absent`. Do not emit
17
- legacy lifecycle aliases in a new 2.1 artifact.
18
-
19
- Technical readiness is not lifecycle completion. `ready_for_implementation`
20
- may change only through the installed machine finalization path after every
21
- routed artifact, projection, semantic review, grounding check, and deterministic
22
- gate passes. An author must not directly declare readiness, authority, approval,
23
- review capability, execution proof, or closeout.
24
-
25
- ## Projection sync
26
-
27
- After any semantic Markdown edit:
28
-
29
- 1. invoke the explicit installed machine semantic-sync step to promote `semantic_model`;
30
- 2. regenerate or reconcile every affected Markdown projection;
31
- 3. verify requirements, decisions, anchors, task registry, typed boundaries,
32
- and verification refs round-trip without loss; and
33
- 4. rerun the applicable deterministic validation and grounding checks.
34
-
35
- For Claude Code, the isolated promotion is
36
- `node .claude/scripts/spec-scaffold.cjs <feature> --sync-semantic-model`.
37
-
38
- Task Markdown exists only when typed ownership, dependency, transition, proof,
39
- or parallel topology requires it. Its status and dependencies project the
40
- matching `task_registry` entry. Its Verification Plan is prospective and never
41
- contains an execution verdict or proof claim. Topology comes only from typed
42
- `coordination.boundaries`, never from legacy trigger fields, priority markers,
43
- related-file lists, or prose.
44
-
45
- ## Lifecycle updates
46
-
47
- - Starting or continuing authored work keeps the feature `in_progress`.
48
- - A deliberate resumable stop uses `paused` with the exact remaining decision.
49
- - An external or semantic blocker uses `blocked` with a concrete cause.
50
- - Task state changes update `task_registry` and the matching task projection
51
- together, but only from observed work and verification.
52
- - Feature `done` is reserved for final-state authority after implementation
53
- closeout; authoring or readiness cannot produce it.
54
-
55
- State synchronization never fabricates commands, PASS results, timestamps,
56
- receipts, completed work, reviewer identity, or user approval. When execution
57
- evidence is absent, preserve the unfinished state and report the missing proof.
58
- Never hand-author the `semantic_model` shape or treat an unsynchronized Markdown
59
- edit as machine authority.
60
-
61
- Claude-native plan/task state may mirror physical state when available, but it
62
- is never a substitute for `spec.json` or authority to advance the lifecycle.
1
+ # Process-first state synchronization
2
+
3
+ ## Primary file state
4
+
5
+ For new work, `specs/<feature>/plan.md` and its direct-child
6
+ `task-NN-*.md` files are canonical, hand-editable state. Task files stay flat
7
+ beside the plan; indexes and host task views are disposable projections.
8
+
9
+ Each task has exactly one `Status:` field using `pending`, `in_progress`,
10
+ `paused`, `blocked`, or `done`. The controller is the sole writer of task
11
+ status and proof. A task's final `## Receipt` is its only canonical execution
12
+ proof.
13
+
14
+ ## Surgical synchronization
15
+
16
+ Before changing state:
17
+
18
+ 1. read the plan row and the task's Outcome, Acceptance, Dependencies,
19
+ Verification Plan, Status, and Receipt;
20
+ 2. confirm every dependency and owned path at the requested boundary;
21
+ 3. edit only the observed Status, concrete blocker, implemented checkbox, or
22
+ Receipt; and
23
+ 4. re-read the file and confirm exactly one Status field and one Receipt
24
+ section.
25
+
26
+ Starting or resuming changes only Status to `in_progress`. Blocking records a
27
+ specific cause without creating proof. Do not replace a whole packet to make a
28
+ state transition.
29
+
30
+ Set `Status: done` only after the inline Receipt passes the
31
+ `validateCanonicalReceipt` contract: exact command, `Exit: 0`,
32
+ `Verification: PASS`, runtime-derived Base and Head values, and a non-empty
33
+ fenced block of current command output. Missing, stale, contradictory,
34
+ placeholder, copied, failure, or zero-test evidence leaves the task unfinished.
35
+
36
+ Synchronization never fabricates commands, output, timestamps, completed
37
+ work, reviewer identity, user approval, or product readiness. A passing command
38
+ proves only the boundary it executed.
39
+
40
+ ## Human gates and workflow ownership
41
+
42
+ - C1 records the user's scope decision before the plan is written.
43
+ - C2 records the user's disposition of deduplicated adversarial findings.
44
+ - Develop starts only after a new explicit user invocation and executes one
45
+ unblocked task at a time.
46
+ - Sync changes only observed file state and never starts implementation.
47
+ - C3 occurs after current receipts and limitations are shown; the user decides
48
+ whether the requested feature is complete.
49
+
50
+ ## Legacy compatibility
51
+
52
+ Existing packets containing `spec.json`, nested `tasks/task-R*.md`, or other
53
+ legacy kernel artifacts stay on their installed adapters. Preserve their
54
+ `task_registry`, `semantic_model`, `planning_depth`, lane,
55
+ `execution_tier`, typed topology, separate receipts, and feature closeout
56
+ contract. Do not migrate that machine authority or storage shape while syncing
57
+ an unrelated process-first packet.
@@ -12,14 +12,18 @@ Use the CafeKit loop: **Understand -> Plan -> Execute -> Verify -> Sync**.
12
12
 
13
13
  ## 2. Plan
14
14
 
15
- - For non-trivial features, use `/specs` to create or validate the spec.
16
- - For technically ready specs, start implementation only after a new explicit Develop invocation and work one task file at a time.
15
+ - For non-trivial features, use `/hapo:specs` to challenge the minimum scope,
16
+ open C1, and create `specs/<feature>/plan.md` with flat
17
+ `task-NN-*.md` files beside it.
18
+ - After adversarial review, open C2 and apply only the findings the user accepts
19
+ or revises. Specs never starts implementation.
20
+ - Start implementation only after a new explicit `/hapo:develop` invocation,
21
+ then select one unblocked task at a time.
17
22
  - Extract from the active task:
18
23
  - `Status`
19
24
  - `Outcome`
20
25
  - `Scope`
21
- - `Anchors and Ownership`
22
- - `Changes`
26
+ - `Ownership`
23
27
  - `Acceptance`
24
28
  - `Dependencies`
25
29
  - `Verification Plan`
@@ -37,22 +41,26 @@ Use the CafeKit loop: **Understand -> Plan -> Execute -> Verify -> Sync**.
37
41
  - Run exact commands from `Verification Plan` first.
38
42
  - Then run repo-level lint/test/build as needed for confidence.
39
43
  - Use only fresh verification from the current run when claiming completion.
44
+ - Review correctness, security, scope, and reachability without turning the
45
+ review into execution proof.
40
46
  - `PRECHECK_FAIL` outranks `NO_TESTS`.
41
47
  - `NO_TESTS` or `0 tests + exit 0` is not a pass when automated tests are required.
42
48
  - If verification fails, fix root cause and rerun. After 3 failed attempts, escalate with evidence.
43
49
 
44
50
  ## 5. Sync
45
51
 
46
- - Mark task state only after implementation, tests/evidence, and review pass.
47
- - Write task proof to `receipts/<task-basename>.md`; use legacy task-local
48
- `## Evidence` only when consuming an old task format.
49
- - Create `feature-receipt.md` only for final integration closeout.
50
- - Treat `spec.json` as machine authority; task Markdown is a human projection.
51
- - Derive task ordering, ownership, proof, and parallel eligibility only from
52
- typed `coordination.boundaries`, never `(P)`, `task_triggers`, `Related Files`,
53
- or prose markers.
54
- - Keep `spec.json.task_registry` and markdown task files aligned.
52
+ - Use `/hapo:sync` to edit observed state surgically; the controller is the
53
+ sole writer of task Status and proof.
54
+ - Keep exactly one `Status:` field. Write or replace the task's final inline
55
+ `## Receipt` before setting `Status: done`.
56
+ - A canonical Receipt contains the exact command, `Exit: 0`,
57
+ `Verification: PASS`, runtime-derived Base and Head values, and non-empty
58
+ fenced current output. Never invent, copy, or infer missing proof.
59
+ - Re-read each edited task and reconcile its plan row, dependencies, acceptance
60
+ mapping, status, and receipt without rewriting unrelated bytes.
55
61
  - Run docs checkpoint when a completed task affects public docs or architecture docs.
62
+ - After all requested work has current proof, show evidence and limitations at
63
+ C3. The user decides whether the feature is complete.
56
64
 
57
65
  ## Production Or CI Issues
58
66
 
@@ -63,3 +71,12 @@ Use the CafeKit loop: **Understand -> Plan -> Execute -> Verify -> Sync**.
63
71
  5. Review before syncing or shipping.
64
72
 
65
73
  Do not patch symptoms before diagnosis unless the issue is a trivial syntax/type/lint failure with an obvious local cause.
74
+
75
+ ## Legacy compatibility
76
+
77
+ When an existing feature contains `spec.json`, nested `tasks/task-R*.md`, or
78
+ other legacy kernel artifacts, keep using the installed adapter. Preserve its
79
+ `task_registry`, `semantic_model`, `planning_depth`, lane,
80
+ `execution_tier`, typed boundaries, separate receipts, and final feature
81
+ receipt. Do not project that machine authority or storage shape into a new
82
+ process-first packet.
@@ -37,7 +37,7 @@ const REL = {
37
37
  ledger: 'packages/spec/benchmarks/benchmark-failure-ledger.json',
38
38
  freeze: 'packages/spec/.cafekit-release/change-firewall-freeze.json',
39
39
  receipt: 'packages/spec/.cafekit-release/release-receipt.json',
40
- legacyBridge: 'specs/cafekit-semantic-eval-firewall/reports/bootstrap-legacy-bridge-review.json',
40
+ legacyBridge: 'specs/archive/cafekit-semantic-eval-firewall/reports/bootstrap-legacy-bridge-review.json',
41
41
  cafekitReleaseDir: 'packages/spec/.cafekit-release/',
42
42
  packageJson: 'packages/spec/package.json',
43
43
  };
@@ -716,7 +716,7 @@ function readLegacyBridgeArtifact(root) {
716
716
  // C14 BootstrapAttestation / bootstrapBaseline
717
717
  // ---------------------------------------------------------------------------
718
718
 
719
- const REL_FEATURE_SPEC = 'specs/cafekit-semantic-eval-firewall/spec.json';
719
+ const REL_FEATURE_SPEC = 'specs/archive/cafekit-semantic-eval-firewall/spec.json';
720
720
 
721
721
  function readFeatureSpecJson(root) {
722
722
  const loaded = readJsonFileIfExists(root, REL_FEATURE_SPEC);
@@ -41,7 +41,6 @@ function defaultSkillsRoot() {
41
41
  path.resolve(__dirname, '..', 'skills'),
42
42
  path.resolve(process.cwd(), '.claude', 'skills'),
43
43
  path.resolve(process.cwd(), '.agents', 'skills'),
44
- path.resolve(process.cwd(), '.opencode', 'skills'),
45
44
  path.resolve(__dirname, '..', '..', '.agents', 'skills')
46
45
  ];
47
46
  return candidates.find((candidate) => fs.existsSync(candidate)) || candidates[0];
@@ -66,6 +66,24 @@ function evidenceBody(text, policy = {}) {
66
66
  return lines.slice(start, end).join('\n');
67
67
  }
68
68
 
69
+ function workflowReceiptBody(text, policy = {}) {
70
+ const lines = String(text || '').split('\n');
71
+ const starts = [];
72
+ for (let index = 0; index < lines.length; index += 1) {
73
+ if (/^##\s+Receipt\s*$/.test(lines[index])) starts.push(index + 1);
74
+ }
75
+ if (starts.length !== 1) return null;
76
+
77
+ const start = starts[0];
78
+ let end = lines.length;
79
+ for (let index = start; index < lines.length; index += 1) {
80
+ const heading = lines[index].match(/^(#{1,6})\s+/);
81
+ const tap = typeof policy.isTapMetadataHeading === 'function' && policy.isTapMetadataHeading(lines[index]);
82
+ if (heading && heading[1].length <= 2 && !tap) { end = index; break; }
83
+ }
84
+ return lines.slice(start, end).join('\n');
85
+ }
86
+
69
87
  function fieldValues(body, name) {
70
88
  const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
71
89
  const matcher = new RegExp(`^\\s*${escaped}\\s*:\\s*(.*?)\\s*$`, 'i');
@@ -76,6 +94,57 @@ function concrete(value) {
76
94
  return typeof value === 'string' && value.trim() !== '' && !/^(?:N\/A|NONE|PENDING|TBD|UNKNOWN|\{\{.*\}\})$/i.test(value.trim());
77
95
  }
78
96
 
97
+ function normalizedCommand(value) {
98
+ if (typeof value !== 'string') return null;
99
+ let command = value.trim();
100
+ if (/^`[^`\r\n]+`$/.test(command)) command = command.slice(1, -1).trim();
101
+ if (!concrete(command) || /^<[^>]+>$/.test(command)) return null;
102
+ return command;
103
+ }
104
+
105
+ function workflowVerificationCommand(taskText) {
106
+ const sections = [];
107
+ let current = null;
108
+ let fence = null;
109
+ for (const line of String(taskText || '').split('\n')) {
110
+ const marker = line.match(/^\s{0,3}(`{3,}|~{3,})/);
111
+ if (marker) {
112
+ const kind = marker[1][0];
113
+ if (fence === null) fence = kind;
114
+ else if (fence === kind) fence = null;
115
+ continue;
116
+ }
117
+ if (fence !== null) continue;
118
+ if (fence === null && /^##\s+Verification Plan\s*$/.test(line)) {
119
+ current = [];
120
+ sections.push(current);
121
+ continue;
122
+ }
123
+ if (fence === null && current && /^#{1,2}\s+/.test(line)) {
124
+ current = null;
125
+ continue;
126
+ }
127
+ if (current) current.push(line);
128
+ }
129
+ if (sections.length !== 1) return null;
130
+ const commands = sections[0]
131
+ .map((line) => line.match(/^\s*-\s+(?:\*\*Command\*\*|Command)\s*:\s*(.*?)\s*$/i))
132
+ .filter(Boolean)
133
+ .map((match) => normalizedCommand(match[1]));
134
+ return commands.length === 1 ? commands[0] : null;
135
+ }
136
+
137
+ function workflowCommandFailures(taskText, body) {
138
+ const planned = workflowVerificationCommand(taskText);
139
+ const actualValues = [
140
+ ...fieldValues(body, 'Command'),
141
+ ...fieldValues(body, 'Commands'),
142
+ ].map(normalizedCommand);
143
+ return planned && actualValues.length === 1 && actualValues[0] === planned
144
+ ? []
145
+ : ['command_identity'];
146
+ }
147
+
79
148
  function plannedField(taskText, name) {
80
149
  const plan = String(taskText || '').split(/^##\s+Verification Plan\s*$/m)[1];
81
150
  if (!plan) return null;
@@ -149,6 +218,67 @@ function checkTaskReceipt(featureDir, taskPath, task, runtimeContext, policy) {
149
218
  return { ...proof, failures: [...new Set(failures)] };
150
219
  }
151
220
 
221
+ function checkWorkflowTaskReceipt(featureDir, taskPath, runtimeContext, policy) {
222
+ const safeTaskPath = typeof taskPath === 'string' && /^task-[A-Za-z0-9][A-Za-z0-9._-]*\.md$/.test(taskPath);
223
+ if (!safeTaskPath) return { status: 'unsafe', taskPath, failures: ['unsafe_path'] };
224
+ const taskFile = safeRead(featureDir, taskPath);
225
+ if (taskFile.status !== 'ok') {
226
+ return { ...taskFile, taskPath, failures: [taskFile.status === 'unsafe' ? 'unsafe_path' : 'missing_receipt'] };
227
+ }
228
+
229
+ const taskText = taskFile.bytes.toString('utf8');
230
+ const statusLines = [...taskText.matchAll(/^\s*(?:\*\*)?Status(?:\*\*)?\s*:\s*(?:\*\*)?\s*([A-Za-z_-]+)(?:\*\*)?\s*$/gim)];
231
+ const body = workflowReceiptBody(taskText, policy);
232
+ const failures = [];
233
+ if (statusLines.length !== 1 || statusLines[0][1].toLowerCase().replaceAll('-', '_') !== 'done') {
234
+ failures.push('task_status');
235
+ }
236
+ if (body === null) {
237
+ failures.push('missing_receipt');
238
+ } else {
239
+ const options = typeof policy?.receiptValidatorOptions === 'function'
240
+ ? policy.receiptValidatorOptions({}, { runtimeContext, requireProvenanceBinding: true })
241
+ : { requireProvenanceBinding: true };
242
+ failures.push(...canonicalFailures(body, {}, runtimeContext, policy));
243
+ failures.push(...workflowCommandFailures(taskText, body));
244
+ if (!/```[^\n]*\n[\s\S]*?\S[\s\S]*?\n```/m.test(body)) failures.push('command_output');
245
+ if (!options.expectedProvenance) failures.push('provenance');
246
+ }
247
+ return {
248
+ status: failures.length === 0 ? 'ok' : 'invalid',
249
+ source: 'inline',
250
+ body,
251
+ receiptBytes: body === null ? null : Buffer.from(body),
252
+ taskFile,
253
+ taskText,
254
+ taskPath,
255
+ failures: [...new Set(failures)],
256
+ };
257
+ }
258
+
259
+ function checkWorkflowReceiptSet(candidates, projectRoot, runtimeSession, policy) {
260
+ const failures = [];
261
+ for (const candidate of Array.isArray(candidates) ? candidates : []) {
262
+ const runtimeContext = policy.deriveRuntimeContext({
263
+ projectRoot,
264
+ specsRoot: candidate.specsDir,
265
+ specFile: candidate.stateFile || candidate.planFile,
266
+ featureName: candidate.featureName,
267
+ runtimeSession,
268
+ });
269
+ const doneTasks = Object.keys(candidate.taskRegistry || {}).filter((taskPath) => (
270
+ candidate.taskRegistry[taskPath]?.status === 'done'
271
+ ));
272
+ for (const taskPath of doneTasks) {
273
+ const proof = checkWorkflowTaskReceipt(candidate.featureDir, taskPath, runtimeContext, policy);
274
+ if (proof.failures.length > 0) {
275
+ failures.push({ featureName: candidate.featureName, taskPath, failures: proof.failures });
276
+ }
277
+ }
278
+ }
279
+ return { failures };
280
+ }
281
+
152
282
  function checkFeatureReceipt(featureDir, runtimeContext, policy) {
153
283
  const receipt = safeRead(featureDir, 'feature-receipt.md');
154
284
  if (receipt.status !== 'ok') return { status: receipt.status, failures: [receipt.status === 'unsafe' ? 'unsafe_path' : 'missing_receipt'] };
@@ -159,4 +289,14 @@ function checkFeatureReceipt(featureDir, runtimeContext, policy) {
159
289
  return { status: 'ok', source: 'feature', body, receiptBytes: receipt.bytes, receiptFile: receipt, failures: [...new Set(failures)] };
160
290
  }
161
291
 
162
- module.exports = { canonicalTaskReceiptPath, checkFeatureReceipt, checkTaskReceipt, evidenceBody, readTaskProof, safeRead };
292
+ module.exports = {
293
+ canonicalTaskReceiptPath,
294
+ checkFeatureReceipt,
295
+ checkTaskReceipt,
296
+ checkWorkflowReceiptSet,
297
+ checkWorkflowTaskReceipt,
298
+ evidenceBody,
299
+ readTaskProof,
300
+ safeRead,
301
+ workflowReceiptBody,
302
+ };