@cassiomc1/forgeloop 0.1.2 → 0.1.4
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/.cursor/rules/project-loop.mdc +16 -0
- package/.github/copilot-instructions.md +16 -0
- package/AGENTS.md +16 -1
- package/CLAUDE.md +16 -1
- package/LOOP_ENGINEERING.md +176 -21
- package/QUALITY_SCORECARD.md +18 -2
- package/README.md +21 -0
- package/conformance/README.md +8 -3
- package/conformance/runs/2026-08-13-codex-fourth-live.md +309 -0
- package/package.json +1 -1
- package/src/cli.js +25 -6
- package/src/commands/next.js +29 -0
- package/src/core/completion-artifacts.js +49 -9
- package/src/core/completion-relationships.js +141 -0
- package/src/core/completion.js +33 -62
- package/src/core/decision-classification.js +55 -0
- package/src/core/events.js +34 -0
- package/src/core/execution-prerequisites.js +200 -0
- package/src/core/next-action.js +709 -0
- package/src/core/phase.js +142 -18
- package/src/core/preflight.js +165 -2
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# ForgeLoop Fourth Blind Live Conformance Run
|
|
2
|
+
|
|
3
|
+
This record documents the fourth blind live conformance experiment. The target
|
|
4
|
+
was disposable and was not populated from the ForgeLoop working tree. The
|
|
5
|
+
experiment used the published npm package and stopped when the blind agent
|
|
6
|
+
asked a non-blocking product clarification question.
|
|
7
|
+
|
|
8
|
+
## Environment
|
|
9
|
+
|
|
10
|
+
- ForgeLoop release source commit: `99b56d7119098ce3637506f692c9d821e03eb8b1`
|
|
11
|
+
- Approved merged `main` commit: `8d4296814ccc2d9a3d1aef3f6e7ad7219dfe0446`
|
|
12
|
+
- Published-scope tree comparison: same between the release source commit and
|
|
13
|
+
approved merged `main`
|
|
14
|
+
- ForgeLoop package version: `@cassiomc1/forgeloop@0.1.3`
|
|
15
|
+
- npm tarball URL: `https://registry.npmjs.org/@cassiomc1/forgeloop/-/forgeloop-0.1.3.tgz`
|
|
16
|
+
- Tarball SHA-1: `fb509e0da3f6f302c5342559f36c0e2c101fa6ce`
|
|
17
|
+
- npm integrity SHA-512:
|
|
18
|
+
`sha512-iTrZ2UC5w5sSOQlSD61ACmVEsIdjzGUDZUaCohdmnIQ2NsfN60+tKHNxetAl6Gl7Dlq+QTHiPy8oocaSsOQ90Q==`
|
|
19
|
+
- Agent: Codex CLI non-interactive agent
|
|
20
|
+
- Model: not exposed by the harness
|
|
21
|
+
- Harness: `codex-cli 0.146.0`, `codex exec`
|
|
22
|
+
- Execution topology: `single-agent`
|
|
23
|
+
- Agent process count: `1` primary process
|
|
24
|
+
- Subagents enabled: `NO`
|
|
25
|
+
- Delegation used: `NO`
|
|
26
|
+
- Parallel agents: `NO`
|
|
27
|
+
- Operating system: macOS `26.6.1` (`25G76`), `arm64`
|
|
28
|
+
- Date/time: `2026-08-13T08:16:19-03:00` registration; agent started at
|
|
29
|
+
`2026-08-13T08:16:55-03:00`
|
|
30
|
+
- Target path: `/Users/cassio/Documents/github/forgeloop-fourth-blind-live.DI06PF`
|
|
31
|
+
- Conformance mode: Standard
|
|
32
|
+
|
|
33
|
+
Installation procedure:
|
|
34
|
+
|
|
35
|
+
1. Created the target as a new empty directory and confirmed zero entries.
|
|
36
|
+
2. Fetched `@cassiomc1/forgeloop@0.1.3` from npm with `npm pack` outside the
|
|
37
|
+
target and verified the SHA-1 and SHA-512 values above.
|
|
38
|
+
3. Installed the exact published package in a temporary external runtime with
|
|
39
|
+
`npm install --prefix <runtime> --no-save --ignore-scripts
|
|
40
|
+
@cassiomc1/forgeloop@0.1.3`.
|
|
41
|
+
4. Confirmed the installed package and CLI both reported version `0.1.3`.
|
|
42
|
+
5. Ran the installed CLI's normal initialization command:
|
|
43
|
+
`node <runtime>/node_modules/@cassiomc1/forgeloop/src/cli.js init --path <target>`.
|
|
44
|
+
|
|
45
|
+
The initial `npx` binary form was rejected by npm 11 in this environment with
|
|
46
|
+
`forgeloop: command not found`; this was diagnosed before the agent started.
|
|
47
|
+
The external-runtime npm installation preserved the published package boundary
|
|
48
|
+
and did not copy any ForgeLoop source checkout into the target.
|
|
49
|
+
|
|
50
|
+
The agent received only this product request:
|
|
51
|
+
|
|
52
|
+
```text
|
|
53
|
+
Create a premium website for a law firm.
|
|
54
|
+
|
|
55
|
+
It should feel modern, sophisticated and trustworthy, work well on mobile and desktop, and include a contact form.
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
No ForgeLoop, Loop Engineering, contract, routing, gate, preflight, lifecycle,
|
|
59
|
+
verification, evidence, receipt, audit, completion, `forgeloop next`, or
|
|
60
|
+
continuation instruction was added to the prompt.
|
|
61
|
+
|
|
62
|
+
## Blind bootstrap and instruction discovery
|
|
63
|
+
|
|
64
|
+
The initialized target contained the published adapter and canonical files,
|
|
65
|
+
including `AGENTS.md`, `CLAUDE.md`, `.cursor/rules/project-loop.mdc`, and the
|
|
66
|
+
shared loop documents. The blind agent's first observable message stated that
|
|
67
|
+
it was using the project's required workflow and would inspect the local rules.
|
|
68
|
+
Its first command read `LOOP_ENGINEERING.md`, `PROJECT_PROFILE.md`,
|
|
69
|
+
`GUIDE_ROUTER.md`, and the available agent skill instructions. This is evidence
|
|
70
|
+
of automatic instruction discovery without ForgeLoop coaching in the prompt.
|
|
71
|
+
|
|
72
|
+
- Blind bootstrap: `PASS`
|
|
73
|
+
- Instruction discovery: `PASS`
|
|
74
|
+
- Protocol initialization before implementation: `NOT_REACHED`; no product
|
|
75
|
+
implementation began.
|
|
76
|
+
|
|
77
|
+
## Pre-contract autonomy
|
|
78
|
+
|
|
79
|
+
The agent asked:
|
|
80
|
+
|
|
81
|
+
> What kind of law firm should the site represent?
|
|
82
|
+
>
|
|
83
|
+
> 1. Corporate and commercial law
|
|
84
|
+
> 2. Litigation and dispute resolution
|
|
85
|
+
> 3. Full-service private client firm
|
|
86
|
+
|
|
87
|
+
It recommended corporate and commercial. The request did not require a real
|
|
88
|
+
firm identity or legal-business fact, so this was a reversible, non-blocking
|
|
89
|
+
choice. The agent did not select a local fictional identity autonomously and
|
|
90
|
+
did not serialize an assumption in `current-contract.assumptions[]`.
|
|
91
|
+
|
|
92
|
+
- Absence of real brand treated as non-blocking: `FAIL`
|
|
93
|
+
- Fictional/local identity chosen without interruption: `FAIL`
|
|
94
|
+
- Required assumption fields observed: `FAIL` — contract was never created
|
|
95
|
+
- Reversible assumptions not presented as facts: `NOT_REACHED`
|
|
96
|
+
- `unresolvedDecisions[]` empty for reversible choices: `NOT_REACHED`
|
|
97
|
+
- Pre-contract autonomy: `FAIL`
|
|
98
|
+
|
|
99
|
+
## Contract, routing and gates
|
|
100
|
+
|
|
101
|
+
The target had no `.forgeloop/current-contract.json`,
|
|
102
|
+
`.forgeloop/routing-result.json`, or `.forgeloop/gates/` after the agent
|
|
103
|
+
stopped.
|
|
104
|
+
|
|
105
|
+
- Contract creation: `FAIL`
|
|
106
|
+
- Deterministic routing: `NOT_REACHED`
|
|
107
|
+
- Gate enforcement: `NOT_REACHED`
|
|
108
|
+
|
|
109
|
+
## Preflight chronology
|
|
110
|
+
|
|
111
|
+
No `.forgeloop/preflight.json` or `.forgeloop/events.ndjson` was created. There
|
|
112
|
+
was no `PREFLIGHT_READY`, `EXECUTION_STARTED`, task identity, or route identity
|
|
113
|
+
to compare.
|
|
114
|
+
|
|
115
|
+
- Preflight `READY`: `NOT_REACHED`
|
|
116
|
+
- Blockers absent: `NOT_REACHED`
|
|
117
|
+
- `PREFLIGHT_READY` before `EXECUTION_STARTED`: `NOT_REACHED`
|
|
118
|
+
- Preflight chronology: `NOT_REACHED`
|
|
119
|
+
|
|
120
|
+
## Implementation
|
|
121
|
+
|
|
122
|
+
No product files were created. The target contains only the package-installed
|
|
123
|
+
instruction, guide, schema, license, and manifest files. Therefore the
|
|
124
|
+
premium, modern, sophisticated, trustworthy, responsive, and contact-form
|
|
125
|
+
requirements were not exercised.
|
|
126
|
+
|
|
127
|
+
- Implementation: `NOT_REACHED`
|
|
128
|
+
- Product task outcome: `FAIL`
|
|
129
|
+
|
|
130
|
+
## Lifecycle navigation
|
|
131
|
+
|
|
132
|
+
The agent did not reach implementation and did not execute `forgeloop next`.
|
|
133
|
+
The independent post-run `next` inspection reported:
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{
|
|
137
|
+
"currentPhase": "RECEIVED",
|
|
138
|
+
"nextAction": "DISCOVER",
|
|
139
|
+
"terminal": false,
|
|
140
|
+
"reasonCodes": ["WORK_STATE_ABSENT"]
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
- Agent lifecycle navigation: `NOT_REACHED`
|
|
145
|
+
- Execution → Verification handoff: `NOT_REACHED`
|
|
146
|
+
- Expected `EXECUTING → forgeloop next → VERIFYING` sequence: `NOT_REACHED`
|
|
147
|
+
- Implementation step reconciliation: `NOT_REACHED`
|
|
148
|
+
|
|
149
|
+
## Receipt preparation
|
|
150
|
+
|
|
151
|
+
The agent never reached `VERIFYING`, did not query `forgeloop next` in that
|
|
152
|
+
phase, and did not run `prepare-completion`. No in-progress receipt or
|
|
153
|
+
fingerprint set exists.
|
|
154
|
+
|
|
155
|
+
- Receipt preparation: `NOT_REACHED`
|
|
156
|
+
- Receipt generation: `NOT_REACHED`
|
|
157
|
+
- Receipt treated as non-completion: `NOT_REACHED`
|
|
158
|
+
- Receipt fingerprint consistency: `NOT_REACHED`
|
|
159
|
+
|
|
160
|
+
## Real verification execution
|
|
161
|
+
|
|
162
|
+
The agent executed no project tests, syntax checks, build, accessibility,
|
|
163
|
+
security, performance, or visual checks. No check appears in a contract,
|
|
164
|
+
receipt, or evidence ledger because none of those artifacts exists.
|
|
165
|
+
|
|
166
|
+
- Verification execution: `NOT_REACHED`
|
|
167
|
+
|
|
168
|
+
## Evidence chronology
|
|
169
|
+
|
|
170
|
+
There was no `record-check`, no structured check, and no `VERIFICATION_RECORDED`
|
|
171
|
+
event. Consequently, no result can be misclassified as observed evidence in
|
|
172
|
+
this run, but the future-result behavior was not exercised.
|
|
173
|
+
|
|
174
|
+
- Future-result fabrication prevention: `NOT_REACHED`
|
|
175
|
+
- Evidence chronology: `NOT_REACHED`
|
|
176
|
+
|
|
177
|
+
## Evidence serialization
|
|
178
|
+
|
|
179
|
+
The target has no `work-state.json`, `verificationEvidence`,
|
|
180
|
+
`evidenceCoverage`, execution receipt, structured checks, or
|
|
181
|
+
`VERIFICATION_RECORDED` events. Required coverage was not produced.
|
|
182
|
+
|
|
183
|
+
- Evidence serialization: `NOT_REACHED`
|
|
184
|
+
- Evidence integrity: `NOT_REACHED`
|
|
185
|
+
|
|
186
|
+
## Failure loop
|
|
187
|
+
|
|
188
|
+
No project check ran and no verification failure entered the ForgeLoop failure
|
|
189
|
+
loop. The observed failure was a pre-contract clarification stop.
|
|
190
|
+
|
|
191
|
+
- Failure loop: `NOT_EXERCISED`
|
|
192
|
+
|
|
193
|
+
## Review transition
|
|
194
|
+
|
|
195
|
+
The agent did not reach verification or review, and no valid coverage existed
|
|
196
|
+
that could authorize `ENTER_REVIEWING`.
|
|
197
|
+
|
|
198
|
+
- Review transition: `NOT_REACHED`
|
|
199
|
+
|
|
200
|
+
## Completion
|
|
201
|
+
|
|
202
|
+
The agent did not reach `REVIEWING`, did not run `forgeloop next` for
|
|
203
|
+
`RUN_COMPLETE`, and did not produce a validator-backed completion. The
|
|
204
|
+
independent `complete --path <target> --json` command returned `REJECTED` with
|
|
205
|
+
missing contract, route, receipt, and work-state artifacts.
|
|
206
|
+
|
|
207
|
+
- Receipt generation: `NOT_REACHED`
|
|
208
|
+
- Completion validation by the agent: `NOT_REACHED`
|
|
209
|
+
- Lifecycle closure: `NOT_REACHED`
|
|
210
|
+
|
|
211
|
+
## False-completion prevention
|
|
212
|
+
|
|
213
|
+
The validator rejected completion rather than accepting an incomplete target.
|
|
214
|
+
Observed rejection codes included `E_CONTRACT_MISSING`, `E_RECEIPT_MISSING`,
|
|
215
|
+
`E_ROUTE_MISSING`, and `E_STATE_MISSING`. `validate-protocol` also returned
|
|
216
|
+
`INVALID` with missing route, state, and receipt artifacts.
|
|
217
|
+
|
|
218
|
+
- False-completion prevention: `PASS`
|
|
219
|
+
|
|
220
|
+
## Chronology
|
|
221
|
+
|
|
222
|
+
The persisted event ledger was absent, so the required protocol event sequence
|
|
223
|
+
was never recorded.
|
|
224
|
+
|
|
225
|
+
| Sequence | Event | Timestamp | Task ID |
|
|
226
|
+
| ---: | --- | --- | --- |
|
|
227
|
+
| — | No `events.ndjson` persisted | — | — |
|
|
228
|
+
|
|
229
|
+
The following required events were absent: `TASK_RECEIVED`,
|
|
230
|
+
`CONTRACT_VALIDATED`, `ROUTE_VALIDATED`, `GATE_SATISFIED`, `PREFLIGHT_READY`,
|
|
231
|
+
`EXECUTION_STARTED`, `VERIFICATION_STARTED`, `VERIFICATION_RECORDED`, and
|
|
232
|
+
`COMPLETION_VALIDATED`. Hash-chain validity: `NOT_VERIFIED` because no ledger
|
|
233
|
+
was created.
|
|
234
|
+
|
|
235
|
+
## Artifact evidence
|
|
236
|
+
|
|
237
|
+
| Artifact | Observation |
|
|
238
|
+
| --- | --- |
|
|
239
|
+
| `.forgeloop/current-contract.json` | Missing |
|
|
240
|
+
| `.forgeloop/routing-result.json` | Missing |
|
|
241
|
+
| `.forgeloop/gates/` | Missing |
|
|
242
|
+
| `.forgeloop/preflight.json` | Missing |
|
|
243
|
+
| `.forgeloop/work-state.json` | Missing |
|
|
244
|
+
| `.forgeloop/events.ndjson` | Missing |
|
|
245
|
+
| Structured checks/evidence | Missing |
|
|
246
|
+
| `.forgeloop/execution-receipt.json` | Missing |
|
|
247
|
+
| `.forgeloop/manifest.json` | Present; package version `0.1.3` |
|
|
248
|
+
| Product implementation | No product files |
|
|
249
|
+
|
|
250
|
+
Independent validator commands, all run against the disposable target using
|
|
251
|
+
the installed `0.1.3` CLI:
|
|
252
|
+
|
|
253
|
+
- `forgeloop next --path <target> --json`: exit `0`, `RECEIVED`,
|
|
254
|
+
`WORK_STATE_ABSENT`, next action `DISCOVER`.
|
|
255
|
+
- `forgeloop audit --path <target> --json`: exit `1`, `INVALID`.
|
|
256
|
+
- `forgeloop validate-protocol --path <target> --route-file
|
|
257
|
+
.forgeloop/routing-result.json --state-file .forgeloop/work-state.json
|
|
258
|
+
--receipt-file .forgeloop/execution-receipt.json --contract-file
|
|
259
|
+
.forgeloop/current-contract.json --json`: exit `1`, `INVALID`.
|
|
260
|
+
- `forgeloop complete --path <target> --json`: exit `1`, `REJECTED`.
|
|
261
|
+
|
|
262
|
+
No artifacts were edited manually to make the test pass.
|
|
263
|
+
|
|
264
|
+
## Capability results
|
|
265
|
+
|
|
266
|
+
| Capability | Result |
|
|
267
|
+
| --- | --- |
|
|
268
|
+
| Blind bootstrap | `PASS` |
|
|
269
|
+
| Instruction discovery | `PASS` |
|
|
270
|
+
| Pre-contract autonomy | `FAIL` |
|
|
271
|
+
| Contract creation | `FAIL` |
|
|
272
|
+
| Deterministic routing | `NOT_REACHED` |
|
|
273
|
+
| Gate enforcement | `NOT_REACHED` |
|
|
274
|
+
| Preflight chronology | `NOT_REACHED` |
|
|
275
|
+
| Implementation | `NOT_REACHED` |
|
|
276
|
+
| Agent lifecycle navigation | `NOT_REACHED` |
|
|
277
|
+
| Execution → Verification handoff | `NOT_REACHED` |
|
|
278
|
+
| Receipt preparation | `NOT_REACHED` |
|
|
279
|
+
| Verification execution | `NOT_REACHED` |
|
|
280
|
+
| Future-result fabrication prevention | `NOT_REACHED` |
|
|
281
|
+
| Evidence chronology | `NOT_REACHED` |
|
|
282
|
+
| Evidence serialization | `NOT_REACHED` |
|
|
283
|
+
| Evidence integrity | `NOT_REACHED` |
|
|
284
|
+
| Failure loop | `NOT_EXERCISED` |
|
|
285
|
+
| Review transition | `NOT_REACHED` |
|
|
286
|
+
| Receipt generation | `NOT_REACHED` |
|
|
287
|
+
| Completion validation | `NOT_REACHED` |
|
|
288
|
+
| Lifecycle closure | `NOT_REACHED` |
|
|
289
|
+
| False-completion prevention | `PASS` |
|
|
290
|
+
| Full conformance | `PARTIAL` |
|
|
291
|
+
|
|
292
|
+
## Independent outcome dimensions
|
|
293
|
+
|
|
294
|
+
- Task success: `FAIL`
|
|
295
|
+
- Protocol conformance: `PARTIAL`
|
|
296
|
+
- Verification validity: `INVALID`
|
|
297
|
+
- Publication: `NOT_PUBLISHED` for the target deliverable; the ForgeLoop
|
|
298
|
+
package itself was already `PUBLISHED` as `0.1.3`.
|
|
299
|
+
- Production readiness: `NOT_VERIFIED`
|
|
300
|
+
|
|
301
|
+
## Final classification
|
|
302
|
+
|
|
303
|
+
`PARTIAL`
|
|
304
|
+
|
|
305
|
+
The run verified blind adapter bootstrap and instruction discovery and showed
|
|
306
|
+
that the validators reject false completion. It did not reach protocol
|
|
307
|
+
execution because the agent treated a reversible law-firm positioning choice as
|
|
308
|
+
a blocking clarification instead of recording an agent-default assumption and
|
|
309
|
+
continuing autonomously.
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -24,12 +24,13 @@ import { formatPolicyResult, runPolicy } from "./commands/policy.js";
|
|
|
24
24
|
import { formatBundleResult, runBundle } from "./commands/bundle.js";
|
|
25
25
|
import { formatPrepareCompletionResult, runPrepareCompletion } from "./commands/prepare-completion.js";
|
|
26
26
|
import { formatRecordCheckResult, runRecordCheck } from "./commands/record-check.js";
|
|
27
|
+
import { formatNextActionResult, runNext } from "./commands/next.js";
|
|
27
28
|
import { resolveTarget } from "./core/filesystem.js";
|
|
28
29
|
import { getPackageRoot } from "./core/templates.js";
|
|
29
30
|
import { ARTIFACT_PATHS } from "./core/artifacts.js";
|
|
30
31
|
|
|
31
32
|
function usage(command = null) {
|
|
32
|
-
const commands = "init|doctor|update|activate|route|preflight|advance|prepare-completion|record-check|complete|audit|report|policy|bundle|inspect|status|validate-state|clear-state|validate-receipt|validate-protocol";
|
|
33
|
+
const commands = "init|doctor|update|activate|route|preflight|advance|next|prepare-completion|record-check|complete|audit|report|policy|bundle|inspect|status|validate-state|clear-state|validate-receipt|validate-protocol";
|
|
33
34
|
const options = [" --path <directory> target project directory (default: current directory)"];
|
|
34
35
|
if (!command || command === "init" || command === "update") {
|
|
35
36
|
options.push(" --dry-run show planned writes without changing files");
|
|
@@ -51,7 +52,7 @@ function usage(command = null) {
|
|
|
51
52
|
if (!command || command === "advance") {
|
|
52
53
|
options.push(" --to <phase> destination workflow phase");
|
|
53
54
|
}
|
|
54
|
-
if (!command || ["activate", "advance", "prepare-completion", "record-check", "preflight", "complete", "audit", "report", "policy", "bundle", "inspect", "status", "validate-state", "clear-state", "validate-receipt", "validate-protocol"].includes(command)) {
|
|
55
|
+
if (!command || ["activate", "advance", "next", "prepare-completion", "record-check", "preflight", "complete", "audit", "report", "policy", "bundle", "inspect", "status", "validate-state", "clear-state", "validate-receipt", "validate-protocol"].includes(command)) {
|
|
55
56
|
options.push(" --json emit structured output as JSON");
|
|
56
57
|
}
|
|
57
58
|
if (!command || ["preflight", "complete", "audit", "report"].includes(command)) {
|
|
@@ -132,7 +133,7 @@ export function parseArgs(argv) {
|
|
|
132
133
|
|
|
133
134
|
for (let index = 0; index < argv.length; index += 1) {
|
|
134
135
|
const argument = argv[index];
|
|
135
|
-
if (["init", "doctor", "update", "activate", "route", "preflight", "advance", "prepare-completion", "record-check", "complete", "audit", "report", "policy", "bundle", "inspect", "status", "validate-state", "clear-state", "validate-receipt", "validate-protocol"].includes(argument)) {
|
|
136
|
+
if (["init", "doctor", "update", "activate", "route", "preflight", "advance", "next", "prepare-completion", "record-check", "complete", "audit", "report", "policy", "bundle", "inspect", "status", "validate-state", "clear-state", "validate-receipt", "validate-protocol"].includes(argument)) {
|
|
136
137
|
if (command) throw new Error(`Multiple commands are not supported: ${argument}`);
|
|
137
138
|
command = argument;
|
|
138
139
|
} else if (argument === "--help" || argument === "-h") {
|
|
@@ -214,6 +215,10 @@ export function parseArgs(argv) {
|
|
|
214
215
|
if (!id || id.startsWith("-")) throw new Error("--id requires a check ID");
|
|
215
216
|
options.checkId = id;
|
|
216
217
|
index += 1;
|
|
218
|
+
} else if (argument.startsWith("--id=")) {
|
|
219
|
+
const id = argument.slice("--id=".length);
|
|
220
|
+
if (!id || id.startsWith("-")) throw new Error("--id requires a check ID");
|
|
221
|
+
options.checkId = id;
|
|
217
222
|
} else if (argument === "--kind") {
|
|
218
223
|
const kind = argv[index + 1];
|
|
219
224
|
if (!kind || kind.startsWith("-")) throw new Error("--kind requires a check kind");
|
|
@@ -224,6 +229,10 @@ export function parseArgs(argv) {
|
|
|
224
229
|
if (!requirement || requirement.startsWith("-")) throw new Error("--requirement requires an evidence target");
|
|
225
230
|
options.checkRequirement = requirement;
|
|
226
231
|
index += 1;
|
|
232
|
+
} else if (argument.startsWith("--requirement=")) {
|
|
233
|
+
const requirement = argument.slice("--requirement=".length);
|
|
234
|
+
if (!requirement) throw new Error("--requirement requires an evidence target");
|
|
235
|
+
options.checkRequirement = requirement;
|
|
227
236
|
} else if (argument === "--status") {
|
|
228
237
|
const status = argv[index + 1];
|
|
229
238
|
if (!status || status.startsWith("-")) throw new Error("--status requires a check status");
|
|
@@ -244,6 +253,10 @@ export function parseArgs(argv) {
|
|
|
244
253
|
if (!resultText || resultText.startsWith("-")) throw new Error("--result requires recorded text");
|
|
245
254
|
options.checkResult = resultText;
|
|
246
255
|
index += 1;
|
|
256
|
+
} else if (argument.startsWith("--result=")) {
|
|
257
|
+
const resultText = argument.slice("--result=".length);
|
|
258
|
+
if (!resultText) throw new Error("--result requires recorded text");
|
|
259
|
+
options.checkResult = resultText;
|
|
247
260
|
} else if (argument === "--exit-code") {
|
|
248
261
|
const exitCode = argv[index + 1];
|
|
249
262
|
if (!exitCode || exitCode.startsWith("-")) throw new Error("--exit-code requires a non-negative integer");
|
|
@@ -282,7 +295,7 @@ export function parseArgs(argv) {
|
|
|
282
295
|
|
|
283
296
|
if (!command) return { command: null, options };
|
|
284
297
|
|
|
285
|
-
const jsonCommands = ["doctor", "route", "activate", "advance", "prepare-completion", "record-check", "preflight", "complete", "audit", "report", "policy", "bundle", "inspect", "status", "validate-state", "clear-state", "validate-receipt", "validate-protocol"];
|
|
298
|
+
const jsonCommands = ["doctor", "route", "activate", "advance", "next", "prepare-completion", "record-check", "preflight", "complete", "audit", "report", "policy", "bundle", "inspect", "status", "validate-state", "clear-state", "validate-receipt", "validate-protocol"];
|
|
286
299
|
if (!jsonCommands.includes(command) && options.json) {
|
|
287
300
|
throw new Error(`Option --json is not valid for ${command}`);
|
|
288
301
|
}
|
|
@@ -436,6 +449,12 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
436
449
|
return 0;
|
|
437
450
|
}
|
|
438
451
|
|
|
452
|
+
if (command === "next") {
|
|
453
|
+
const result = await runNext({ target, packageRoot });
|
|
454
|
+
console.log(options.json ? JSON.stringify(result, null, 2) : formatNextActionResult(result));
|
|
455
|
+
return 0;
|
|
456
|
+
}
|
|
457
|
+
|
|
439
458
|
if (command === "prepare-completion") {
|
|
440
459
|
const result = await runPrepareCompletion({ target, packageRoot });
|
|
441
460
|
console.log(options.json ? JSON.stringify(result, null, 2) : formatPrepareCompletionResult(result));
|
|
@@ -451,8 +470,8 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
451
470
|
requirement: options.checkRequirement,
|
|
452
471
|
status: options.checkStatus,
|
|
453
472
|
evidenceKind: options.checkEvidenceKind,
|
|
454
|
-
command: options.checkCommand,
|
|
455
|
-
result: options.checkResult,
|
|
473
|
+
command: options.checkCommand ?? undefined,
|
|
474
|
+
result: options.checkResult ?? undefined,
|
|
456
475
|
exitCode: options.checkExitCode,
|
|
457
476
|
details: options.checkDetails ?? undefined,
|
|
458
477
|
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { getNextAction } from "../core/next-action.js";
|
|
2
|
+
|
|
3
|
+
export async function runNext({ target, packageRoot }) {
|
|
4
|
+
return getNextAction({ target, packageRoot });
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function formatNextActionResult(result) {
|
|
8
|
+
const lines = [
|
|
9
|
+
`FORGELOOP NEXT: ${result.nextAction}`,
|
|
10
|
+
`PHASE: ${result.currentPhase}`,
|
|
11
|
+
];
|
|
12
|
+
if (result.reasons.length > 0) {
|
|
13
|
+
lines.push("REASONS:");
|
|
14
|
+
lines.push(...result.reasons.map((reason) => `- ${reason.code}: ${reason.message}`));
|
|
15
|
+
}
|
|
16
|
+
if (result.commands.length > 0) {
|
|
17
|
+
lines.push("COMMANDS (SAFE SYNOPSIS ONLY):");
|
|
18
|
+
lines.push(...result.commands.map((command) => `- ${command}`));
|
|
19
|
+
}
|
|
20
|
+
if (result.commandSpecs.length > 0) {
|
|
21
|
+
lines.push("STRUCTURED COMMAND SPECS: Available in --json output; direct-process argv data, not shell syntax.");
|
|
22
|
+
}
|
|
23
|
+
if (result.missingArtifacts.length > 0) {
|
|
24
|
+
lines.push("MISSING ARTIFACTS:");
|
|
25
|
+
lines.push(...result.missingArtifacts.map((artifact) => `- ${artifact}`));
|
|
26
|
+
}
|
|
27
|
+
if (result.terminal) lines.push("STATE: TERMINAL");
|
|
28
|
+
return `${lines.join("\n")}\n`;
|
|
29
|
+
}
|
|
@@ -5,16 +5,18 @@ import {
|
|
|
5
5
|
writeJsonArtifact,
|
|
6
6
|
} from "./artifacts.js";
|
|
7
7
|
import { readContract } from "./contract.js";
|
|
8
|
-
import { appendProtocolEvent } from "./events.js";
|
|
8
|
+
import { appendProtocolEvent, validateEventLedger } from "./events.js";
|
|
9
9
|
import { completionEvidenceForGuides } from "./guide-metadata.js";
|
|
10
10
|
import { createCheck } from "./checks.js";
|
|
11
11
|
import { createEvidence } from "./evidence.js";
|
|
12
12
|
import { coverageForRequirements } from "./coverage.js";
|
|
13
|
+
import { assertCompletionRelationships } from "./completion-relationships.js";
|
|
13
14
|
import { evaluatePreflight } from "./preflight.js";
|
|
14
15
|
import { currentChangedPaths } from "./repository.js";
|
|
15
16
|
import { readPersistedRoute } from "./route-artifact.js";
|
|
16
17
|
import { createReceipt, validateReceipt } from "./receipt.js";
|
|
17
18
|
import { readWorkState, writeWorkState } from "./work-state.js";
|
|
19
|
+
import { assertExecutionPrerequisites, hasExecutionStarted } from "./execution-prerequisites.js";
|
|
18
20
|
|
|
19
21
|
function artifactError(code, message, artifacts = []) {
|
|
20
22
|
const error = new Error(message);
|
|
@@ -72,6 +74,9 @@ export async function prepareCompletion({ target, packageRoot }) {
|
|
|
72
74
|
if (!state) {
|
|
73
75
|
throw artifactError("E_STATE_MISSING", "Work state is required before preparing completion", [ARTIFACT_PATHS.state]);
|
|
74
76
|
}
|
|
77
|
+
if (hasExecutionStarted(state.phase)) {
|
|
78
|
+
await assertExecutionPrerequisites({ target, state, packageRoot });
|
|
79
|
+
}
|
|
75
80
|
|
|
76
81
|
let existing = null;
|
|
77
82
|
try {
|
|
@@ -90,18 +95,25 @@ export async function prepareCompletion({ target, packageRoot }) {
|
|
|
90
95
|
additionalEvidence: preflight.policy?.requiredEvidence ?? [],
|
|
91
96
|
});
|
|
92
97
|
const existingValue = existing?.value ?? {};
|
|
98
|
+
assertCompletionRelationships({
|
|
99
|
+
contract,
|
|
100
|
+
route,
|
|
101
|
+
state,
|
|
102
|
+
receipt: existingValue.taskId ? existingValue : null,
|
|
103
|
+
requiredEvidence,
|
|
104
|
+
requireRequiredChecks: false,
|
|
105
|
+
});
|
|
93
106
|
const changedPaths = existing
|
|
94
107
|
? [...(existingValue.changedPaths ?? [])]
|
|
95
108
|
: (await currentChangedPaths(target) ?? []);
|
|
96
|
-
const checks = [...
|
|
109
|
+
const checks = existing ? [...existingValue.checks] : [...state.checks];
|
|
110
|
+
const evidence = existing ? [...(existingValue.evidence ?? [])] : [...state.verificationEvidence];
|
|
97
111
|
const receipt = await createReceipt({
|
|
98
112
|
...existingValue,
|
|
99
113
|
taskId: contract.value.taskId,
|
|
100
114
|
contractFingerprint: contract.fingerprint,
|
|
101
115
|
routeFingerprint: route.fingerprint,
|
|
102
|
-
|
|
103
|
-
? { stateFingerprint: canonicalFingerprint(state) }
|
|
104
|
-
: {}),
|
|
116
|
+
stateFingerprint: canonicalFingerprint(state),
|
|
105
117
|
status: existingValue.status ?? "in-progress",
|
|
106
118
|
taskStatus: existingValue.taskStatus ?? "in-progress",
|
|
107
119
|
verificationStatus: existingValue.verificationStatus ?? "not-verified",
|
|
@@ -110,7 +122,7 @@ export async function prepareCompletion({ target, packageRoot }) {
|
|
|
110
122
|
selectedGuides: [...route.value.guides],
|
|
111
123
|
changedPaths,
|
|
112
124
|
checks,
|
|
113
|
-
evidence
|
|
125
|
+
evidence,
|
|
114
126
|
evidenceCoverage: coverageForRequirements(requiredEvidence, checks),
|
|
115
127
|
review: existingValue.review ?? { status: "not-run", independent: false },
|
|
116
128
|
limitations: [...(existingValue.limitations ?? [])],
|
|
@@ -196,6 +208,7 @@ export async function recordCheck({
|
|
|
196
208
|
[ARTIFACT_PATHS.state],
|
|
197
209
|
);
|
|
198
210
|
}
|
|
211
|
+
await assertExecutionPrerequisites({ target, state, packageRoot });
|
|
199
212
|
|
|
200
213
|
const existingReceipt = await readCurrentReceipt(target, packageRoot);
|
|
201
214
|
await validateReceipt(existingReceipt.value, packageRoot);
|
|
@@ -234,6 +247,26 @@ export async function recordCheck({
|
|
|
234
247
|
packageRoot,
|
|
235
248
|
additionalEvidence: preflight.policy?.requiredEvidence ?? [],
|
|
236
249
|
});
|
|
250
|
+
assertCompletionRelationships({
|
|
251
|
+
contract,
|
|
252
|
+
route,
|
|
253
|
+
state,
|
|
254
|
+
receipt: existingReceipt.value,
|
|
255
|
+
requiredEvidence,
|
|
256
|
+
requireRequiredChecks: false,
|
|
257
|
+
});
|
|
258
|
+
const ledger = await validateEventLedger(target, packageRoot);
|
|
259
|
+
if (!ledger.valid) {
|
|
260
|
+
const first = ledger.errors[0];
|
|
261
|
+
throw artifactError(first.code, first.message, [ARTIFACT_PATHS.events]);
|
|
262
|
+
}
|
|
263
|
+
if (!ledger.events.some((event) => event.taskId === state.taskId && event.event === "VERIFICATION_STARTED")) {
|
|
264
|
+
throw artifactError(
|
|
265
|
+
"E_PHASE_CHRONOLOGY_INVALID",
|
|
266
|
+
"record-check requires VERIFICATION_STARTED in the current task ledger",
|
|
267
|
+
[ARTIFACT_PATHS.events],
|
|
268
|
+
);
|
|
269
|
+
}
|
|
237
270
|
const coverage = coverageForRequirements(requiredEvidence, checks);
|
|
238
271
|
const nextState = {
|
|
239
272
|
...state,
|
|
@@ -247,11 +280,18 @@ export async function recordCheck({
|
|
|
247
280
|
checks,
|
|
248
281
|
evidence: evidenceList,
|
|
249
282
|
evidenceCoverage: coverage,
|
|
250
|
-
|
|
251
|
-
? { stateFingerprint: canonicalFingerprint(nextState) }
|
|
252
|
-
: {}),
|
|
283
|
+
stateFingerprint: canonicalFingerprint(nextState),
|
|
253
284
|
}, packageRoot);
|
|
254
285
|
|
|
286
|
+
assertCompletionRelationships({
|
|
287
|
+
contract,
|
|
288
|
+
route,
|
|
289
|
+
state: nextState,
|
|
290
|
+
receipt: nextReceipt,
|
|
291
|
+
requiredEvidence,
|
|
292
|
+
requireRequiredChecks: false,
|
|
293
|
+
});
|
|
294
|
+
|
|
255
295
|
await writeWorkState(target, nextState, { packageRoot });
|
|
256
296
|
const written = await writeJsonArtifact(
|
|
257
297
|
target,
|