@haposoft/cafekit 0.16.0-rc.5 → 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.
- package/README.md +21 -58
- package/bin/__tests__/change-firewall.semantic-firewall.test.js +4 -5
- package/bin/__tests__/codex-hooks.test.js +137 -0
- package/bin/__tests__/codex-native.test.js +130 -74
- package/bin/__tests__/develop-contract.test.js +155 -76
- package/bin/__tests__/installer-safety.test.js +19 -154
- package/bin/__tests__/package-inventory.test.js +10 -20
- package/bin/__tests__/release-preflight.semantic-firewall.test.js +13 -5
- package/bin/__tests__/validator-grounding.test.js +11 -11
- package/bin/install.js +2 -7
- package/bin/lib/context.js +0 -45
- package/bin/lib/copy-utils.js +1 -1
- package/bin/lib/i18n.js +0 -3
- package/bin/lib/manifest.js +1 -1
- package/bin/phases/claude-runtime.js +1 -3
- package/bin/phases/codex-runtime.js +4 -2
- package/bin/phases/copy-payload.js +3 -15
- package/bin/phases/post-install.js +5 -17
- package/bin/phases/root-config.js +2 -3
- package/bin/phases/select-platform.js +2 -5
- package/bin/phases/summary.js +0 -1
- package/package.json +3 -8
- package/src/claude/CLAUDE.md +19 -6
- package/src/claude/agents/project-manager.md +41 -9
- package/src/claude/agents/spec-maker.md +76 -155
- package/src/claude/gitignore +1 -1
- package/src/claude/hooks/__tests__/spec-gate.test.js +162 -0
- package/src/claude/hooks/lib/context.cjs +1 -1
- package/src/claude/hooks/lib/detect.cjs +1 -1
- package/src/claude/hooks/spec-gate.cjs +44 -9
- package/src/claude/hooks/spec-state.cjs +22 -11
- package/src/claude/hooks/task-scaffold-guard.cjs +1 -1
- package/src/claude/migration-manifest.json +17 -3
- package/src/claude/rules/manage-docs.md +43 -24
- package/src/claude/rules/state-sync.md +57 -62
- package/src/claude/rules/workflow.md +30 -13
- package/src/claude/scripts/change-firewall.cjs +2 -2
- package/src/claude/scripts/generate-skill-catalog.cjs +0 -1
- package/src/claude/scripts/spec-receipt.cjs +141 -1
- package/src/claude/scripts/spec-resolver.cjs +288 -0
- package/src/claude/skills/develop/SKILL.md +89 -125
- package/src/claude/skills/develop/references/parallel-waves.md +75 -162
- package/src/claude/skills/develop/references/quality-gate.md +61 -121
- package/src/claude/skills/develop/references/subagent-patterns.md +31 -38
- package/src/claude/skills/question/SKILL.md +1 -1
- package/src/claude/skills/specs/SKILL.md +142 -204
- package/src/claude/skills/specs/references/review.md +109 -243
- package/src/claude/skills/specs/references/templates.md +173 -0
- package/src/claude/skills/sync/SKILL.md +66 -90
- package/src/claude/skills/sync/references/sync-protocols.md +62 -83
- package/src/codex/AGENTS.md +20 -7
- package/src/codex/hooks/lib/spec-receipt.cjs +18 -0
- package/src/codex/hooks/lib/spec-utils.cjs +25 -0
- package/src/codex/hooks/spec-gate.cjs +46 -16
- package/src/codex/hooks/spec-state.cjs +22 -12
- package/src/codex/rules/state-sync.md +57 -61
- package/src/common/AGENTS.md +8 -1
- package/bin/__tests__/opencode-spec-gate.test.js +0 -799
- package/bin/lib/opencode-install.js +0 -899
- package/bin/phases/opencode-runtime.js +0 -38
- package/src/claude/skills/specs/references/archive-workflow.md +0 -74
- package/src/claude/skills/specs/references/ask-user-question-gates.md +0 -70
- package/src/claude/skills/specs/references/codebase-analysis.md +0 -102
- package/src/claude/skills/specs/references/cross-spec-dependency.md +0 -76
- package/src/claude/skills/specs/references/research-strategy.md +0 -107
- package/src/claude/skills/specs/references/scope-inquiry.md +0 -107
- package/src/claude/skills/specs/references/translation-mirror.md +0 -83
- package/src/claude/skills/specs/rules/design-discovery-full.md +0 -45
- package/src/claude/skills/specs/rules/design-discovery-light.md +0 -28
- package/src/claude/skills/specs/rules/design-principles.md +0 -138
- package/src/claude/skills/specs/rules/design-review.md +0 -55
- package/src/claude/skills/specs/rules/ears-format.md +0 -42
- package/src/claude/skills/specs/rules/phase-decision-matrix.md +0 -56
- package/src/claude/skills/specs/rules/task-scoring-rubric.md +0 -52
- package/src/claude/skills/specs/rules/tasks-generation.md +0 -189
- package/src/opencode/AGENTS.md +0 -41
- package/src/opencode/plugins/docs-sync.ts +0 -175
- package/src/opencode/plugins/inspect-block.ts +0 -164
- package/src/opencode/plugins/privacy-block.ts +0 -182
- package/src/opencode/plugins/rules.ts +0 -155
- package/src/opencode/plugins/session.ts +0 -179
- package/src/opencode/plugins/spec-gate.ts +0 -457
- package/src/opencode/plugins/spec-state.ts +0 -311
- package/src/opencode/plugins/state.ts +0 -253
- package/src/opencode/plugins/task-scaffold-guard.ts +0 -161
- 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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
//
|
|
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
|
-
##
|
|
57
|
+
## Process-first specification and execution tracker
|
|
58
58
|
|
|
59
|
-
### Where
|
|
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
|
-
|
|
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
|
-
├──
|
|
69
|
-
├──
|
|
70
|
-
└──
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
#
|
|
2
|
-
|
|
3
|
-
##
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
16
|
-
|
|
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
|
-
- `
|
|
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
|
-
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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 = {
|
|
292
|
+
module.exports = {
|
|
293
|
+
canonicalTaskReceiptPath,
|
|
294
|
+
checkFeatureReceipt,
|
|
295
|
+
checkTaskReceipt,
|
|
296
|
+
checkWorkflowReceiptSet,
|
|
297
|
+
checkWorkflowTaskReceipt,
|
|
298
|
+
evidenceBody,
|
|
299
|
+
readTaskProof,
|
|
300
|
+
safeRead,
|
|
301
|
+
workflowReceiptBody,
|
|
302
|
+
};
|