@haposoft/cafekit 0.16.0-rc.6 → 0.16.0-rc.8
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 +382 -0
- package/bin/__tests__/codex-native.test.js +579 -80
- 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 +494 -26
- package/bin/__tests__/release-preflight.semantic-firewall.test.js +2 -2
- 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 +2 -5
- package/src/claude/CLAUDE.md +19 -6
- package/src/claude/agents/project-manager.md +41 -9
- package/src/claude/agents/spec-maker.md +82 -155
- package/src/claude/gitignore +1 -1
- package/src/claude/hooks/__tests__/spec-gate.test.js +238 -0
- package/src/claude/hooks/__tests__/state.test.js +249 -3
- 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 +51 -13
- 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 +196 -1
- package/src/claude/scripts/spec-resolver.cjs +428 -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 +152 -204
- package/src/claude/skills/specs/references/review.md +124 -243
- package/src/claude/skills/specs/references/templates.md +202 -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 +54 -16
- 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
|
@@ -1,175 +1,102 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: spec-maker
|
|
3
|
-
description:
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
description: Build a small process-first feature plan and flat executable task packets, then run evidence-backed adversarial review before implementation.
|
|
4
|
+
tools: Read, Grep, Glob, Bash, Write, Edit, AskUserQuestion
|
|
5
|
+
model: inherit
|
|
6
6
|
---
|
|
7
|
+
# Spec Maker — process-first planner
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
Turn a substantial feature request into the smallest durable packet that
|
|
10
|
+
removes product decisions from implementation. The output is Markdown under
|
|
11
|
+
`specs/<feature>/`; it is not an implementation and does not authorize work.
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
`.claude/skills/specs/SKILL.md` first; it is the canonical routing contract.
|
|
13
|
+
## Inputs
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
- the user's feature description;
|
|
16
|
+
- current repository instructions and code;
|
|
17
|
+
- existing plans or decisions explicitly placed in scope;
|
|
18
|
+
- the three human-gate decisions from `hapo:specs`.
|
|
14
19
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
records authority/integrity only and never supplies product semantics.
|
|
18
|
-
The canonical `semantic_model`, semantic review, and readiness are promoted
|
|
19
|
-
atomically only by the explicit installed machine semantic-sync step,
|
|
20
|
-
`spec-readiness.cjs`. Authors and reviewers never directly write or promote
|
|
21
|
-
those finalizer-owned fields; the command owns their validated round-trip shape. Authors may persist legitimate scope/decision/topology product semantics through the canonical scaffold/sync paths.
|
|
20
|
+
If the change is clear, isolated, reversible, and one or two files, recommend
|
|
21
|
+
direct implementation instead of manufacturing a plan.
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
# Claude Code
|
|
25
|
-
node .claude/scripts/spec-readiness.cjs specs/<feature> --review-result <review.json>
|
|
26
|
-
# Codex
|
|
27
|
-
node .codex/scripts/spec-readiness.cjs specs/<feature> --review-result <review.json>
|
|
28
|
-
```
|
|
23
|
+
## Required process
|
|
29
24
|
|
|
30
|
-
|
|
31
|
-
leaves the exact original `spec.json` bytes unchanged.
|
|
32
|
-
|
|
33
|
-
Persist the canonical 2.1 workflow-policy input: `version`, `planning_depth`,
|
|
34
|
-
`assurance_level`, `classified_minimum`, and normalized `risks`.
|
|
35
|
-
`planning_depth` and `assurance_level` are independent. Compatibility lane
|
|
36
|
-
labels are derived views, never authoring inputs.
|
|
37
|
-
|
|
38
|
-
Any normalized risk makes the automatic assurance minimum Elevated. Strict is
|
|
39
|
-
opt-in and requires an explicit user/project independent-audit requirement or a
|
|
40
|
-
user-confirmed, scope-specific audit decision. Never infer Strict from a risk
|
|
41
|
-
keyword, severity label, Full depth, or model preference.
|
|
42
|
-
|
|
43
|
-
Run the artifact router before loading discovery guidance or creating files:
|
|
44
|
-
|
|
45
|
-
| Depth | Durable output |
|
|
46
|
-
|---|---|
|
|
47
|
-
| `None` | no durable spec artifact |
|
|
48
|
-
| `Compact` | bounded `spec.json`, `requirements.md`, and `design.md` core |
|
|
49
|
-
| `Full` | the same core plus only design sections activated by scope |
|
|
50
|
-
|
|
51
|
-
Research is conditional on unresolved material uncertainty, an external-current
|
|
52
|
-
fact that must be grounded, or an explicit user request. Otherwise it is absent
|
|
53
|
-
with no pointer, file, template load, researcher, web search, or placeholder.
|
|
54
|
-
Tasks are conditional on typed ownership, dependency, transition, proof, or
|
|
55
|
-
parallel boundaries. Full and Strict do not trigger either artifact.
|
|
56
|
-
|
|
57
|
-
Never write `init.json`, `spec-state.json`, `hydration.md`, phase files, or
|
|
58
|
-
shorthand task filenames into a feature directory.
|
|
59
|
-
|
|
60
|
-
## Authoring route
|
|
61
|
-
|
|
62
|
-
1. Resolve the active feature and scope; ambiguity fails closed.
|
|
63
|
-
2. Classify planning and assurance independently, then route artifacts.
|
|
64
|
-
3. Establish `scope_lock` before semantic authoring.
|
|
65
|
-
4. Author requirements and design; load discovery only after routing.
|
|
66
|
-
5. Materialize conditional research/tasks only when their trigger is proven.
|
|
67
|
-
6. Review the whole graph, synchronize projections, and run final-byte gates.
|
|
68
|
-
7. Ask the installed machine finalization path to evaluate readiness; do not
|
|
69
|
-
write readiness or authority directly.
|
|
70
|
-
|
|
71
|
-
Investigate and ground repository facts. Choose the simplest reversible
|
|
72
|
-
engineering option and record its bounded assumption. Ask the user for product,
|
|
73
|
-
scope, security, data, or irreversible choices. Persist only actual items in
|
|
74
|
-
`spec.json.decisions`; unresolved `user_owned` items block readiness.
|
|
75
|
-
|
|
76
|
-
Reclassify before persistence. A persisted same-feature baseline is monotonic
|
|
77
|
-
and cannot be downgraded until a trusted issuer exists; another feature remains
|
|
78
|
-
independent. Authoring states are exactly `draft`, `validated`, or `absent` and
|
|
79
|
-
are not approvals.
|
|
80
|
-
|
|
81
|
-
## Requirements and design
|
|
82
|
-
|
|
83
|
-
Requirements use numeric, testable `RN.M` criteria. Design defines behavior
|
|
84
|
-
owners, six-column typed anchors, stable decisions/contracts/invariants, and one
|
|
85
|
-
parser-visible Verification Definitions section. Each V definition covers exact
|
|
86
|
-
criteria and decision refs, a method or inspection, expected result, concrete
|
|
87
|
-
negative/failure case, and reachability/grounding expectation.
|
|
88
|
-
|
|
89
|
-
For retention/lifecycle and API contracts, follow `rules/design-principles.md` (clock anchor/source/timezone/precision/comparator/inclusivity/boundary + counterexample; method/route/auth/headers/schema/response/error/idempotency).
|
|
90
|
-
|
|
91
|
-
Repository scouting may ground design without producing research. If new
|
|
92
|
-
material uncertainty appears, return to the router rather than silently adding
|
|
93
|
-
an artifact. Never invent a product decision, source, anchor, or command.
|
|
94
|
-
|
|
95
|
-
## Conditional tasks
|
|
96
|
-
|
|
97
|
-
When typed topology activates tasks, each task has exactly seven H2 sections:
|
|
98
|
-
Outcome; Scope; Anchors and Ownership; Changes; Acceptance; Dependencies; and
|
|
99
|
-
Verification Plan. The only ownership table is
|
|
100
|
-
`ID | Type | Target | Role | Access | Action`.
|
|
101
|
-
|
|
102
|
-
Typed `coordination.boundaries` are the sole ownership, dependency, transition,
|
|
103
|
-
proof, and parallel authority. Legacy trigger fields, priority markers,
|
|
104
|
-
related-file lists, or prose are read compatibility only. Each `RN.M` has one
|
|
105
|
-
implementation owner. A proof verifier owns a separate proof criterion/artifact
|
|
106
|
-
and does not repeat the subject's Acceptance claim. Planned verification is not
|
|
107
|
-
execution evidence. Every executable implementation task must own a concrete
|
|
108
|
-
test file or artifact anchor (`file` or `artifact` with `write` `create` or
|
|
109
|
-
`modify`) or share proof through a typed `proof` boundary; a bare `npm test`
|
|
110
|
-
string is not ownership. Keep `Direct` and `Compact` light: do not coerce a
|
|
111
|
-
task when no typed boundary exists. `spec.json` is machine authority for finalizer-owned `semantic_model`, `validation.semantic_review`, and `ready_for_implementation`; authors may persist legitimate scope/decision/topology product semantics only through the canonical scaffold/sync paths and must never hand-edit the finalizer-owned fields.
|
|
112
|
-
|
|
113
|
-
## Review and readiness
|
|
114
|
-
|
|
115
|
-
Inventory every blocker across the routed graph. For every `RN.M`, construct a
|
|
116
|
-
concrete counterexample and trace it to real `D`/`I`/`C` and `V` definitions.
|
|
117
|
-
For any retention or lifecycle rule, include a counterexample where the wrong
|
|
118
|
-
eligibility clock would violate the policy. Keep presentation concise without
|
|
119
|
-
limiting finding count. Two-review target is advisory: after two failed semantic rounds, pause for unresolved product/security/architecture uncertainty or repeated non-convergence; one bounded mechanical correction without weakening gates is allowed; budgets never override correctness. Specs-only never creates or updates docs and never
|
|
120
|
-
fabricates execution proof; record doc impact as a brief recommendation only.
|
|
121
|
-
|
|
122
|
-
Routine and Elevated have no reviewer ceremony. Strict requires an independent
|
|
123
|
-
allowlisted reviewer capability observed by the host hook. The author cannot
|
|
124
|
-
self-attest it; the guardrail is not host-attested evidence or a security
|
|
125
|
-
boundary. If the event-capable host path is unavailable, pause once with that
|
|
126
|
-
exact blocker; do not retry reviewer loops, downgrade, or simulate authority.
|
|
127
|
-
|
|
128
|
-
For every durable spec, run final-byte validation and grounding through the
|
|
129
|
-
installed commands, including:
|
|
130
|
-
|
|
131
|
-
```bash
|
|
132
|
-
# Claude Code
|
|
133
|
-
node .claude/scripts/validate-spec-output.cjs specs/<feature>
|
|
134
|
-
# Codex
|
|
135
|
-
node .codex/scripts/validate-spec-output.cjs specs/<feature>
|
|
136
|
-
```
|
|
25
|
+
### 1. Inspect before asking
|
|
137
26
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
artifact, projection round-trip, semantic review, grounding check, and gate
|
|
142
|
-
passes. The author never fabricates or directly writes that conclusion.
|
|
27
|
+
Find current entrypoints, reusable code, callers, tests, docs, and runtime
|
|
28
|
+
registration. Cite concrete `path:line` evidence. Mark facts that cannot be
|
|
29
|
+
verified as `[UNVERIFIED]` and state what would settle them.
|
|
143
30
|
|
|
144
|
-
|
|
31
|
+
### 2. Open C1 once
|
|
145
32
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
33
|
+
Summarize what exists, the minimum change set, and expansion signals. Ask the
|
|
34
|
+
user to EXPAND, KEEP, or CUT. Record the answer and explicit exclusions. Do not
|
|
35
|
+
repeat scope objections later unless new evidence invalidates the decision.
|
|
149
36
|
|
|
150
|
-
|
|
151
|
-
|
|
37
|
+
### 3. Author the flat packet
|
|
38
|
+
|
|
39
|
+
Read `skills/specs/references/templates.md`, then create its marked flat packet:
|
|
152
40
|
|
|
153
41
|
```text
|
|
154
|
-
|
|
155
|
-
|
|
42
|
+
specs/<feature>/plan.md
|
|
43
|
+
specs/<feature>/task-01-<slug>.md
|
|
44
|
+
specs/<feature>/task-NN-<slug>.md
|
|
156
45
|
```
|
|
157
46
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
47
|
+
Keep tasks flat beside the plan. Each task owns one outcome, normally no more
|
|
48
|
+
than about five files, explicit acceptance IDs, dependencies, and a runnable
|
|
49
|
+
Verification Plan. Every criterion maps to at least one task and one proof.
|
|
50
|
+
Use EARS sentences for observable acceptance behavior.
|
|
51
|
+
|
|
52
|
+
Do not create implementation files, receipts, approval records, generated
|
|
53
|
+
registries, readiness claims, or empty supporting documents. Keep every new
|
|
54
|
+
task `Status: blocked` while C2 is open, and keep its `## Receipt` empty.
|
|
55
|
+
|
|
56
|
+
### 4. Review from fresh context
|
|
57
|
+
|
|
58
|
+
Read `skills/specs/references/review.md`. Route fresh reviewers by capability
|
|
59
|
+
and give them only the plan packet plus repository access. Require a severity,
|
|
60
|
+
plan location, concrete failure, current `path:line` evidence, and smallest
|
|
61
|
+
repair for every finding.
|
|
62
|
+
|
|
63
|
+
Deduplicate and cap findings at 15. Open C2 so the user can accept, reject, or
|
|
64
|
+
revise each one. Apply only accepted changes and run the full consistency sweep
|
|
65
|
+
after every edit. Then derive every task state, not only the first candidate:
|
|
66
|
+
`pending` means semantically ready for the dependency-aware queue. Keep a task
|
|
67
|
+
`blocked` while a C1/C2 decision, accepted finding, or `UNKNOWN` closure remains
|
|
68
|
+
open. A named task dependency alone does not make it blocked; write dependencies
|
|
69
|
+
as exact flat task basenames and let the resolver derive the next pending task.
|
|
70
|
+
Move `blocked` to `pending` only when current evidence closes every non-dependency
|
|
71
|
+
blocker. Stop after two paper rounds; later findings need runtime evidence.
|
|
162
72
|
|
|
163
|
-
|
|
73
|
+
### 5. Hand off without dispatch
|
|
164
74
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
| activated research | research reference/template |
|
|
170
|
-
| activated task topology | `rules/tasks-generation.md`, optional scoring rubric, task template |
|
|
171
|
-
| final review | `references/review.md`, review and grounding rules |
|
|
75
|
+
Report the created files, C1 decision, accepted C2 findings, remaining
|
|
76
|
+
uncertainties, every task status, and the first pending task.
|
|
77
|
+
Do not start Develop or treat dispatchability as user authorization. The user
|
|
78
|
+
chooses when execution begins.
|
|
172
79
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
80
|
+
## Authoring constraints
|
|
81
|
+
|
|
82
|
+
- One mutable fact has one home; other files link to it.
|
|
83
|
+
- A task is not a bucket for several independent outcomes.
|
|
84
|
+
- Unknown outcomes become questions, not guessed requirements.
|
|
85
|
+
- Ambiguous rules receive two or three examples before C2.
|
|
86
|
+
- Ownership overlap or dependency uncertainty prevents a parallel claim.
|
|
87
|
+
- A command that cannot be run from the named work context is not verification.
|
|
88
|
+
- User approval and test proof are different facts; never infer either.
|
|
89
|
+
- Existing legacy specs remain untouched unless migration is explicitly asked.
|
|
90
|
+
|
|
91
|
+
## Output
|
|
92
|
+
|
|
93
|
+
End with:
|
|
94
|
+
|
|
95
|
+
```text
|
|
96
|
+
Status: DONE | DONE_WITH_CONCERNS | BLOCKED | NEEDS_CONTEXT
|
|
97
|
+
Files: <plan and task paths>
|
|
98
|
+
C1: <decision>
|
|
99
|
+
C2: <accepted/rejected/revised counts>
|
|
100
|
+
Next: <first `Status: pending` task or blocker>
|
|
101
|
+
Unresolved questions: <none or concise list>
|
|
102
|
+
```
|
package/src/claude/gitignore
CHANGED
|
@@ -78,6 +78,44 @@ function bindFixtureReceipt(dir, value) {
|
|
|
78
78
|
return value.replaceAll(VALID_BASE, context.base).replaceAll(VALID_HEAD, context.head);
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
function workflowRuntimeContext(dir) {
|
|
82
|
+
return PROVENANCE.deriveRuntimeContext({
|
|
83
|
+
projectRoot: dir,
|
|
84
|
+
specsRoot: path.join(dir, 'specs'),
|
|
85
|
+
specFile: path.join(dir, 'specs', FEATURE, 'plan.md'),
|
|
86
|
+
featureName: FEATURE,
|
|
87
|
+
runtimeSession: 'test',
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function makeWorkflowFixture(receiptLines = [], plannedCommand = 'node --test') {
|
|
92
|
+
const dir = tmpDir();
|
|
93
|
+
const featureDir = path.join(dir, 'specs', FEATURE);
|
|
94
|
+
fs.mkdirSync(featureDir, { recursive: true });
|
|
95
|
+
fs.writeFileSync(path.join(featureDir, 'plan.md'), '# Demo plan\n');
|
|
96
|
+
const context = workflowRuntimeContext(dir);
|
|
97
|
+
const task = [
|
|
98
|
+
'# Task 01: demo',
|
|
99
|
+
'',
|
|
100
|
+
'Status: done',
|
|
101
|
+
'',
|
|
102
|
+
'## Dependencies',
|
|
103
|
+
'',
|
|
104
|
+
'- none',
|
|
105
|
+
'',
|
|
106
|
+
'## Verification Plan',
|
|
107
|
+
'',
|
|
108
|
+
`- Command: ${plannedCommand}`,
|
|
109
|
+
'',
|
|
110
|
+
...receiptLines,
|
|
111
|
+
'',
|
|
112
|
+
].join('\n')
|
|
113
|
+
.replaceAll(VALID_BASE, context.base)
|
|
114
|
+
.replaceAll(VALID_HEAD, context.head);
|
|
115
|
+
fs.writeFileSync(path.join(featureDir, 'task-01-demo.md'), task);
|
|
116
|
+
return dir;
|
|
117
|
+
}
|
|
118
|
+
|
|
81
119
|
function installFeatureReceipt(dir, featureName = FEATURE, session = 'test') {
|
|
82
120
|
const featureDir = path.join(dir, 'specs', featureName);
|
|
83
121
|
const specFile = path.join(featureDir, 'spec.json');
|
|
@@ -1241,3 +1279,203 @@ test('30. phantom vectors via gate evidence must block with verification_state (
|
|
|
1241
1279
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
1242
1280
|
}
|
|
1243
1281
|
});
|
|
1282
|
+
|
|
1283
|
+
test('31. process-v3 done task without inline Receipt blocks', () => {
|
|
1284
|
+
const dir = makeWorkflowFixture();
|
|
1285
|
+
try {
|
|
1286
|
+
clearCache();
|
|
1287
|
+
const body = parseBlock(runHook({}, dir).stdout);
|
|
1288
|
+
assert.ok(body, 'missing process-v3 receipt should block');
|
|
1289
|
+
assert.strictEqual(body.decision, 'block');
|
|
1290
|
+
assert.match(body.reason, /missing_receipt/);
|
|
1291
|
+
assert.match(body.reason, /## Receipt/);
|
|
1292
|
+
} finally {
|
|
1293
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
1294
|
+
}
|
|
1295
|
+
});
|
|
1296
|
+
|
|
1297
|
+
test('32. process-v3 requires Receipt heading and runtime-bound provenance', () => {
|
|
1298
|
+
const legacyHeading = makeWorkflowFixture([
|
|
1299
|
+
'## Evidence', '', 'Verification: PASS', 'Command: node --test', 'Exit: 0',
|
|
1300
|
+
`Base: ${VALID_BASE}`, `Head: ${VALID_HEAD}`, '```text', 'pass', '```',
|
|
1301
|
+
]);
|
|
1302
|
+
try {
|
|
1303
|
+
clearCache();
|
|
1304
|
+
const body = parseBlock(runHook({}, legacyHeading).stdout);
|
|
1305
|
+
assert.ok(body, 'legacy Evidence heading must not satisfy process-v3 receipt');
|
|
1306
|
+
assert.match(body.reason, /missing_receipt/);
|
|
1307
|
+
} finally {
|
|
1308
|
+
fs.rmSync(legacyHeading, { recursive: true, force: true });
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
const stale = makeWorkflowFixture([
|
|
1312
|
+
'## Receipt', '', 'Verification: PASS', 'Command: node --test', 'Exit: 0',
|
|
1313
|
+
`Base: ${VALID_BASE}`, `Head: ${VALID_BASE}`, '```text', 'pass', '```',
|
|
1314
|
+
]);
|
|
1315
|
+
try {
|
|
1316
|
+
clearCache();
|
|
1317
|
+
const body = parseBlock(runHook({}, stale).stdout);
|
|
1318
|
+
assert.ok(body, 'stale process-v3 provenance should block');
|
|
1319
|
+
assert.match(body.reason, /provenance/);
|
|
1320
|
+
} finally {
|
|
1321
|
+
fs.rmSync(stale, { recursive: true, force: true });
|
|
1322
|
+
}
|
|
1323
|
+
});
|
|
1324
|
+
|
|
1325
|
+
test('32b. process-v3 ignores a Receipt heading and proof contained only in a fence', () => {
|
|
1326
|
+
const dir = makeWorkflowFixture([
|
|
1327
|
+
'~~~~markdown', '## Receipt', '', 'Verification: PASS', 'Command: node --test', 'Exit: 0',
|
|
1328
|
+
`Base: ${VALID_BASE}`, `Head: ${VALID_HEAD}`, '```text', 'pass: 1', '```', '~~~~',
|
|
1329
|
+
]);
|
|
1330
|
+
try {
|
|
1331
|
+
clearCache();
|
|
1332
|
+
const body = parseBlock(runHook({}, dir).stdout);
|
|
1333
|
+
assert.ok(body, 'fenced-only process-v3 Receipt must block');
|
|
1334
|
+
assert.strictEqual(body.decision, 'block');
|
|
1335
|
+
assert.match(body.reason, /missing_receipt/);
|
|
1336
|
+
} finally {
|
|
1337
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
1338
|
+
}
|
|
1339
|
+
});
|
|
1340
|
+
|
|
1341
|
+
test('32c. process-v3 ignores canonical Receipt fields contained only in its output fence', () => {
|
|
1342
|
+
const dir = makeWorkflowFixture([
|
|
1343
|
+
'## Receipt', '', '```text', 'Verification: PASS', 'Command: node --test', 'Exit: 0',
|
|
1344
|
+
`Base: ${VALID_BASE}`, `Head: ${VALID_HEAD}`, 'pass: 1', '```',
|
|
1345
|
+
]);
|
|
1346
|
+
try {
|
|
1347
|
+
clearCache();
|
|
1348
|
+
const body = parseBlock(runHook({}, dir).stdout);
|
|
1349
|
+
assert.ok(body, 'fenced-only canonical fields must block');
|
|
1350
|
+
assert.strictEqual(body.decision, 'block');
|
|
1351
|
+
assert.match(body.reason, /verification_state/);
|
|
1352
|
+
assert.match(body.reason, /command/);
|
|
1353
|
+
assert.match(body.reason, /provenance/);
|
|
1354
|
+
} finally {
|
|
1355
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
1356
|
+
}
|
|
1357
|
+
});
|
|
1358
|
+
|
|
1359
|
+
test('32d. process-v3 preserves explicit failures from the Receipt output fence', () => {
|
|
1360
|
+
const dir = makeWorkflowFixture([
|
|
1361
|
+
'## Receipt', '', 'Verification: PASS', 'Command: node --test', 'Exit: 0',
|
|
1362
|
+
`Base: ${VALID_BASE}`, `Head: ${VALID_HEAD}`, '```text', '$ node --test',
|
|
1363
|
+
'exit code: 1', 'FAIL', '```',
|
|
1364
|
+
]);
|
|
1365
|
+
try {
|
|
1366
|
+
clearCache();
|
|
1367
|
+
const body = parseBlock(runHook({}, dir).stdout);
|
|
1368
|
+
assert.ok(body, 'explicit fenced output failure must block');
|
|
1369
|
+
assert.strictEqual(body.decision, 'block');
|
|
1370
|
+
assert.match(body.reason, /verification_state/);
|
|
1371
|
+
assert.match(body.reason, /exit_result/);
|
|
1372
|
+
} finally {
|
|
1373
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
1374
|
+
}
|
|
1375
|
+
});
|
|
1376
|
+
|
|
1377
|
+
test('32e. process-v3 rejects output whose closing fence is shorter than its opener', () => {
|
|
1378
|
+
const dir = makeWorkflowFixture([
|
|
1379
|
+
'## Receipt', '', 'Verification: PASS', 'Command: node --test', 'Exit: 0',
|
|
1380
|
+
`Base: ${VALID_BASE}`, `Head: ${VALID_HEAD}`, '````text', '$ node --test',
|
|
1381
|
+
'pass: 1', '```',
|
|
1382
|
+
]);
|
|
1383
|
+
try {
|
|
1384
|
+
clearCache();
|
|
1385
|
+
const body = parseBlock(runHook({}, dir).stdout);
|
|
1386
|
+
assert.ok(body, 'malformed output fence must block');
|
|
1387
|
+
assert.strictEqual(body.decision, 'block');
|
|
1388
|
+
assert.match(body.reason, /command_output/);
|
|
1389
|
+
} finally {
|
|
1390
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
1391
|
+
}
|
|
1392
|
+
});
|
|
1393
|
+
|
|
1394
|
+
test('33. process-v3 done task with canonical inline Receipt passes', () => {
|
|
1395
|
+
const dir = makeWorkflowFixture([
|
|
1396
|
+
'## Receipt', '', 'Verification: PASS', 'Command: node --test', 'Exit: 0',
|
|
1397
|
+
`Base: ${VALID_BASE}`, `Head: ${VALID_HEAD}`, '```text', '$ node --test',
|
|
1398
|
+
'Status: pending', '## Receipt', 'pass: 1', '```',
|
|
1399
|
+
]);
|
|
1400
|
+
try {
|
|
1401
|
+
clearCache();
|
|
1402
|
+
const result = runHook({}, dir);
|
|
1403
|
+
assert.strictEqual(result.code, 0, result.stderr);
|
|
1404
|
+
assert.strictEqual(result.stdout, '');
|
|
1405
|
+
} finally {
|
|
1406
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
1407
|
+
}
|
|
1408
|
+
});
|
|
1409
|
+
|
|
1410
|
+
test('34. process-v3 Receipt command must match the exact Verification Plan command', () => {
|
|
1411
|
+
const dir = makeWorkflowFixture([
|
|
1412
|
+
'## Receipt', '', 'Verification: PASS', 'Command: true', 'Exit: 0',
|
|
1413
|
+
`Base: ${VALID_BASE}`, `Head: ${VALID_HEAD}`, '```text', '$ true', 'pass: 1', '```',
|
|
1414
|
+
], 'pnpm test');
|
|
1415
|
+
try {
|
|
1416
|
+
clearCache();
|
|
1417
|
+
const body = parseBlock(runHook({}, dir).stdout);
|
|
1418
|
+
assert.ok(body, 'a substituted receipt command must block');
|
|
1419
|
+
assert.strictEqual(body.decision, 'block');
|
|
1420
|
+
assert.match(body.reason, /command_identity/);
|
|
1421
|
+
} finally {
|
|
1422
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
1423
|
+
}
|
|
1424
|
+
});
|
|
1425
|
+
|
|
1426
|
+
test('35. process-v3 Stop ignores a completed packet when one packet remains active', () => {
|
|
1427
|
+
const dir = makeWorkflowFixture([
|
|
1428
|
+
'## Receipt', '', 'Verification: PASS', 'Command: node --test', 'Exit: 0',
|
|
1429
|
+
`Base: ${VALID_BASE}`, `Head: ${VALID_HEAD}`, '```text', '$ node --test', 'pass: 1', '```',
|
|
1430
|
+
]);
|
|
1431
|
+
try {
|
|
1432
|
+
const activeDir = path.join(dir, 'specs', 'active');
|
|
1433
|
+
fs.mkdirSync(activeDir, { recursive: true });
|
|
1434
|
+
fs.writeFileSync(path.join(activeDir, 'plan.md'), '# Active plan\n');
|
|
1435
|
+
fs.writeFileSync(path.join(activeDir, 'task-01-active.md'), [
|
|
1436
|
+
'# Task 01: active', '', 'Status: pending', '',
|
|
1437
|
+
'## Dependencies', '', '- none', '',
|
|
1438
|
+
'## Verification Plan', '', '- Command: node --test', '',
|
|
1439
|
+
].join('\n'));
|
|
1440
|
+
clearCache();
|
|
1441
|
+
const result = runHook({}, dir);
|
|
1442
|
+
assert.strictEqual(result.code, 0, result.stderr);
|
|
1443
|
+
assert.strictEqual(result.stdout, '');
|
|
1444
|
+
} finally {
|
|
1445
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
1446
|
+
}
|
|
1447
|
+
});
|
|
1448
|
+
|
|
1449
|
+
test('36. process-v3 Stop accepts two completed packets with valid Receipts', () => {
|
|
1450
|
+
const dir = makeWorkflowFixture([
|
|
1451
|
+
'## Receipt', '', 'Verification: PASS', 'Command: node --test', 'Exit: 0',
|
|
1452
|
+
`Base: ${VALID_BASE}`, `Head: ${VALID_HEAD}`, '```text', '$ node --test', 'pass: 1', '```',
|
|
1453
|
+
]);
|
|
1454
|
+
try {
|
|
1455
|
+
const otherDir = path.join(dir, 'specs', 'other');
|
|
1456
|
+
fs.mkdirSync(otherDir, { recursive: true });
|
|
1457
|
+
fs.writeFileSync(path.join(otherDir, 'plan.md'), '# Other plan\n');
|
|
1458
|
+
const context = PROVENANCE.deriveRuntimeContext({
|
|
1459
|
+
projectRoot: dir,
|
|
1460
|
+
specsRoot: path.join(dir, 'specs'),
|
|
1461
|
+
specFile: path.join(otherDir, 'plan.md'),
|
|
1462
|
+
featureName: 'other',
|
|
1463
|
+
runtimeSession: 'test',
|
|
1464
|
+
});
|
|
1465
|
+
fs.writeFileSync(path.join(otherDir, 'task-01-other.md'), [
|
|
1466
|
+
'# Task 01: other', '', 'Status: done', '',
|
|
1467
|
+
'## Dependencies', '', '- none', '',
|
|
1468
|
+
'## Verification Plan', '', '- Command: node --test', '',
|
|
1469
|
+
'## Receipt', '',
|
|
1470
|
+
'Verification: PASS', 'Command: node --test', 'Exit: 0',
|
|
1471
|
+
`Base: ${context.base}`, `Head: ${context.head}`,
|
|
1472
|
+
'```text', '$ node --test', 'pass: 1', '```', '',
|
|
1473
|
+
].join('\n'));
|
|
1474
|
+
clearCache();
|
|
1475
|
+
const result = runHook({}, dir);
|
|
1476
|
+
assert.strictEqual(result.code, 0, result.stderr);
|
|
1477
|
+
assert.strictEqual(result.stdout, '');
|
|
1478
|
+
} finally {
|
|
1479
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
1480
|
+
}
|
|
1481
|
+
});
|