@cassiomc1/forgeloop 0.1.0 → 0.1.2
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 +7 -1
- package/.github/copilot-instructions.md +6 -0
- package/AGENTS.md +9 -1
- package/AGENT_COMPATIBILITY.md +14 -1
- package/CLAUDE.md +7 -1
- package/ENG/accessibility-eng.md +5 -0
- package/ENG/clean-code-eng.md +1 -0
- package/ENG/design-code-eng.md +5 -0
- package/ENG/games-code-design-web-eng.md +6 -0
- package/ENG/perf-code-eng.md +3 -0
- package/ENG/premium-sites-studio-eng.md +9 -0
- package/ENG/sec-code-eng.md +5 -0
- package/ENG/test-code-eng.md +3 -0
- package/GUIDE_ROUTER.md +16 -0
- package/LOOP_ENGINEERING.md +139 -4
- package/ORCHESTRATOR_INTEGRATION.md +15 -0
- package/QUALITY_SCORECARD.md +4 -0
- package/README.md +43 -1
- package/THREAT_MODEL.md +6 -0
- package/conformance/README.md +42 -0
- package/conformance/backend-auth/EXPECTED_ROUTE.json +7 -0
- package/conformance/backend-auth/REQUEST.md +4 -0
- package/conformance/backend-auth/REQUIRED_EVIDENCE.json +3 -0
- package/conformance/backend-auth/REQUIRED_GATES.json +3 -0
- package/conformance/blind-premium-website/EXPECTED_ROUTE.json +7 -0
- package/conformance/blind-premium-website/REQUEST.md +6 -0
- package/conformance/blind-premium-website/REQUIRED_EVIDENCE.json +14 -0
- package/conformance/blind-premium-website/REQUIRED_GATES.json +3 -0
- package/conformance/complete-website/EXPECTED_ROUTE.json +7 -0
- package/conformance/complete-website/REQUEST.md +6 -0
- package/conformance/complete-website/REQUIRED_EVIDENCE.json +3 -0
- package/conformance/complete-website/REQUIRED_GATES.json +3 -0
- package/conformance/docs-only/EXPECTED_ROUTE.json +7 -0
- package/conformance/docs-only/REQUEST.md +4 -0
- package/conformance/docs-only/REQUIRED_EVIDENCE.json +3 -0
- package/conformance/docs-only/REQUIRED_GATES.json +3 -0
- package/conformance/runs/2026-08-11-codex-first-live.md +98 -0
- package/conformance/runs/2026-08-11-codex-second-live.md +87 -0
- package/conformance/simple-bug/EXPECTED_ROUTE.json +7 -0
- package/conformance/simple-bug/REQUEST.md +4 -0
- package/conformance/simple-bug/REQUIRED_EVIDENCE.json +3 -0
- package/conformance/simple-bug/REQUIRED_GATES.json +3 -0
- package/package.json +5 -4
- package/schemas/activation.schema.json +15 -0
- package/schemas/check.schema.json +23 -0
- package/schemas/config.schema.json +16 -0
- package/schemas/current-contract.schema.json +50 -0
- package/schemas/event.schema.json +20 -0
- package/schemas/evidence-coverage.schema.json +17 -0
- package/schemas/execution-receipt.schema.json +10 -0
- package/schemas/gate.schema.json +32 -0
- package/schemas/policy.schema.json +19 -0
- package/schemas/preflight.schema.json +23 -0
- package/schemas/routing-result.schema.json +1 -0
- package/schemas/source-registry.schema.json +29 -0
- package/schemas/task-bundle.schema.json +14 -0
- package/schemas/work-state.schema.json +7 -1
- package/src/cli.js +230 -9
- package/src/commands/activate.js +11 -0
- package/src/commands/advance.js +12 -0
- package/src/commands/audit.js +12 -0
- package/src/commands/bundle.js +10 -0
- package/src/commands/complete.js +18 -0
- package/src/commands/policy.js +19 -0
- package/src/commands/preflight.js +12 -0
- package/src/commands/prepare-completion.js +18 -0
- package/src/commands/record-check.js +20 -0
- package/src/commands/report.js +10 -0
- package/src/commands/route.js +14 -2
- package/src/commands/validate-receipt.js +7 -6
- package/src/core/activation.js +16 -0
- package/src/core/artifacts.js +132 -0
- package/src/core/audit.js +88 -0
- package/src/core/bundles.js +120 -0
- package/src/core/checks.js +115 -0
- package/src/core/completion-artifacts.js +281 -0
- package/src/core/completion.js +295 -0
- package/src/core/config.js +35 -0
- package/src/core/conformance.js +7 -0
- package/src/core/contract.js +141 -0
- package/src/core/coverage.js +89 -0
- package/src/core/events.js +124 -0
- package/src/core/gate-artifact.js +54 -0
- package/src/core/gates.js +55 -0
- package/src/core/guide-metadata.js +62 -0
- package/src/core/phase.js +88 -0
- package/src/core/policies.js +69 -0
- package/src/core/preflight.js +246 -0
- package/src/core/profile.js +48 -0
- package/src/core/protocol.js +34 -0
- package/src/core/receipt.js +40 -1
- package/src/core/report.js +49 -0
- package/src/core/repository.js +18 -0
- package/src/core/route-artifact.js +33 -0
- package/src/core/router.js +16 -8
- package/src/core/schema-validation.js +11 -0
- package/src/core/sources.js +86 -0
- package/src/core/templates.js +22 -4
- package/src/core/work-state.js +41 -13
- /package/.forgeloop/{.gitignore → forgeloop.gitignore} +0 -0
package/src/cli.js
CHANGED
|
@@ -14,11 +14,22 @@ import { formatRouteResult, runRoute } from "./commands/route.js";
|
|
|
14
14
|
import { runValidateReceipt } from "./commands/validate-receipt.js";
|
|
15
15
|
import { formatValidateProtocolResult, runValidateProtocol } from "./commands/validate-protocol.js";
|
|
16
16
|
import { runUpdate } from "./commands/update.js";
|
|
17
|
+
import { formatActivateResult, runActivate } from "./commands/activate.js";
|
|
18
|
+
import { formatAdvanceResult, runAdvance } from "./commands/advance.js";
|
|
19
|
+
import { formatPreflightResult, runPreflight } from "./commands/preflight.js";
|
|
20
|
+
import { formatCompleteResult, runComplete } from "./commands/complete.js";
|
|
21
|
+
import { formatAuditResult, runAudit } from "./commands/audit.js";
|
|
22
|
+
import { formatReportResult, runReport } from "./commands/report.js";
|
|
23
|
+
import { formatPolicyResult, runPolicy } from "./commands/policy.js";
|
|
24
|
+
import { formatBundleResult, runBundle } from "./commands/bundle.js";
|
|
25
|
+
import { formatPrepareCompletionResult, runPrepareCompletion } from "./commands/prepare-completion.js";
|
|
26
|
+
import { formatRecordCheckResult, runRecordCheck } from "./commands/record-check.js";
|
|
17
27
|
import { resolveTarget } from "./core/filesystem.js";
|
|
18
28
|
import { getPackageRoot } from "./core/templates.js";
|
|
29
|
+
import { ARTIFACT_PATHS } from "./core/artifacts.js";
|
|
19
30
|
|
|
20
31
|
function usage(command = null) {
|
|
21
|
-
const commands = "init|doctor|update|route|inspect|status|validate-state|clear-state|validate-receipt|validate-protocol";
|
|
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";
|
|
22
33
|
const options = [" --path <directory> target project directory (default: current directory)"];
|
|
23
34
|
if (!command || command === "init" || command === "update") {
|
|
24
35
|
options.push(" --dry-run show planned writes without changing files");
|
|
@@ -37,9 +48,21 @@ function usage(command = null) {
|
|
|
37
48
|
options.push(" --executable-change declare executable/configuration change");
|
|
38
49
|
options.push(" --json emit route result as JSON");
|
|
39
50
|
}
|
|
40
|
-
if (!command ||
|
|
51
|
+
if (!command || command === "advance") {
|
|
52
|
+
options.push(" --to <phase> destination workflow phase");
|
|
53
|
+
}
|
|
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)) {
|
|
41
55
|
options.push(" --json emit structured output as JSON");
|
|
42
56
|
}
|
|
57
|
+
if (!command || ["preflight", "complete", "audit", "report"].includes(command)) {
|
|
58
|
+
options.push(" --strict require strict protocol compliance");
|
|
59
|
+
}
|
|
60
|
+
if (!command || command === "policy") {
|
|
61
|
+
options.push(" <name> policy pack name");
|
|
62
|
+
}
|
|
63
|
+
if (!command || command === "bundle") {
|
|
64
|
+
options.push(" --task <id> task ID to export as a portable bundle");
|
|
65
|
+
}
|
|
43
66
|
if (!command || ["status", "inspect", "validate-protocol"].includes(command)) {
|
|
44
67
|
options.push(" --contract-file <path> current JSON contract used for freshness comparison");
|
|
45
68
|
}
|
|
@@ -53,6 +76,17 @@ function usage(command = null) {
|
|
|
53
76
|
if (!command || command === "validate-receipt") {
|
|
54
77
|
options.push(" --file <path> receipt file relative to target");
|
|
55
78
|
}
|
|
79
|
+
if (!command || command === "record-check") {
|
|
80
|
+
options.push(" --id <id> stable check identifier");
|
|
81
|
+
options.push(" --kind <kind> check kind (default: command)");
|
|
82
|
+
options.push(" --requirement <id> completion requirement covered by the check");
|
|
83
|
+
options.push(" --status <status> passed, failed, blocked, or not-run");
|
|
84
|
+
options.push(" --evidence-kind <kind> OBSERVED, INFERRED, NOT_VERIFIED, or BLOCKED");
|
|
85
|
+
options.push(" --command <text> command already run by the agent (recorded only)");
|
|
86
|
+
options.push(" --result <text> observed result supplied by the agent");
|
|
87
|
+
options.push(" --exit-code <number> observed process exit code");
|
|
88
|
+
options.push(" --details <json> additional structured check details");
|
|
89
|
+
}
|
|
56
90
|
options.push(" --version show the installed package version");
|
|
57
91
|
options.push(" --help show this help");
|
|
58
92
|
|
|
@@ -72,6 +106,7 @@ export function parseArgs(argv) {
|
|
|
72
106
|
platforms: [],
|
|
73
107
|
behaviorChange: false,
|
|
74
108
|
executableChange: false,
|
|
109
|
+
to: null,
|
|
75
110
|
file: null,
|
|
76
111
|
contractFile: null,
|
|
77
112
|
routeFile: null,
|
|
@@ -79,6 +114,17 @@ export function parseArgs(argv) {
|
|
|
79
114
|
receiptFile: null,
|
|
80
115
|
taskBriefFiles: [],
|
|
81
116
|
delegatedResultFiles: [],
|
|
117
|
+
checkId: null,
|
|
118
|
+
checkKind: null,
|
|
119
|
+
checkRequirement: null,
|
|
120
|
+
checkStatus: null,
|
|
121
|
+
checkEvidenceKind: null,
|
|
122
|
+
checkCommand: null,
|
|
123
|
+
checkResult: null,
|
|
124
|
+
checkExitCode: null,
|
|
125
|
+
checkDetails: null,
|
|
126
|
+
policy: null,
|
|
127
|
+
task: null,
|
|
82
128
|
help: false,
|
|
83
129
|
version: false,
|
|
84
130
|
};
|
|
@@ -86,7 +132,7 @@ export function parseArgs(argv) {
|
|
|
86
132
|
|
|
87
133
|
for (let index = 0; index < argv.length; index += 1) {
|
|
88
134
|
const argument = argv[index];
|
|
89
|
-
if (["init", "doctor", "update", "route", "inspect", "status", "validate-state", "clear-state", "validate-receipt", "validate-protocol"].includes(argument)) {
|
|
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)) {
|
|
90
136
|
if (command) throw new Error(`Multiple commands are not supported: ${argument}`);
|
|
91
137
|
command = argument;
|
|
92
138
|
} else if (argument === "--help" || argument === "-h") {
|
|
@@ -126,6 +172,16 @@ export function parseArgs(argv) {
|
|
|
126
172
|
options.behaviorChange = true;
|
|
127
173
|
} else if (argument === "--executable-change") {
|
|
128
174
|
options.executableChange = true;
|
|
175
|
+
} else if (argument === "--to") {
|
|
176
|
+
const phase = argv[index + 1];
|
|
177
|
+
if (!phase || phase.startsWith("-")) throw new Error("--to requires a phase");
|
|
178
|
+
options.to = phase;
|
|
179
|
+
index += 1;
|
|
180
|
+
} else if (argument === "--task") {
|
|
181
|
+
const task = argv[index + 1];
|
|
182
|
+
if (!task || task.startsWith("-")) throw new Error("--task requires an ID");
|
|
183
|
+
options.task = task;
|
|
184
|
+
index += 1;
|
|
129
185
|
} else if (argument === "--file") {
|
|
130
186
|
const file = argv[index + 1];
|
|
131
187
|
if (!file || file.startsWith("-")) throw new Error("--file requires a path");
|
|
@@ -153,6 +209,59 @@ export function parseArgs(argv) {
|
|
|
153
209
|
if (!file || file.startsWith("-")) throw new Error("--delegated-result-file requires a path");
|
|
154
210
|
options.delegatedResultFiles.push(file);
|
|
155
211
|
index += 1;
|
|
212
|
+
} else if (argument === "--id") {
|
|
213
|
+
const id = argv[index + 1];
|
|
214
|
+
if (!id || id.startsWith("-")) throw new Error("--id requires a check ID");
|
|
215
|
+
options.checkId = id;
|
|
216
|
+
index += 1;
|
|
217
|
+
} else if (argument === "--kind") {
|
|
218
|
+
const kind = argv[index + 1];
|
|
219
|
+
if (!kind || kind.startsWith("-")) throw new Error("--kind requires a check kind");
|
|
220
|
+
options.checkKind = kind;
|
|
221
|
+
index += 1;
|
|
222
|
+
} else if (argument === "--requirement") {
|
|
223
|
+
const requirement = argv[index + 1];
|
|
224
|
+
if (!requirement || requirement.startsWith("-")) throw new Error("--requirement requires an evidence target");
|
|
225
|
+
options.checkRequirement = requirement;
|
|
226
|
+
index += 1;
|
|
227
|
+
} else if (argument === "--status") {
|
|
228
|
+
const status = argv[index + 1];
|
|
229
|
+
if (!status || status.startsWith("-")) throw new Error("--status requires a check status");
|
|
230
|
+
options.checkStatus = status;
|
|
231
|
+
index += 1;
|
|
232
|
+
} else if (argument === "--evidence-kind") {
|
|
233
|
+
const evidenceKind = argv[index + 1];
|
|
234
|
+
if (!evidenceKind || evidenceKind.startsWith("-")) throw new Error("--evidence-kind requires an evidence kind");
|
|
235
|
+
options.checkEvidenceKind = evidenceKind;
|
|
236
|
+
index += 1;
|
|
237
|
+
} else if (argument === "--command") {
|
|
238
|
+
const commandText = argv[index + 1];
|
|
239
|
+
if (!commandText || commandText.startsWith("-")) throw new Error("--command requires recorded text");
|
|
240
|
+
options.checkCommand = commandText;
|
|
241
|
+
index += 1;
|
|
242
|
+
} else if (argument === "--result") {
|
|
243
|
+
const resultText = argv[index + 1];
|
|
244
|
+
if (!resultText || resultText.startsWith("-")) throw new Error("--result requires recorded text");
|
|
245
|
+
options.checkResult = resultText;
|
|
246
|
+
index += 1;
|
|
247
|
+
} else if (argument === "--exit-code") {
|
|
248
|
+
const exitCode = argv[index + 1];
|
|
249
|
+
if (!exitCode || exitCode.startsWith("-")) throw new Error("--exit-code requires a non-negative integer");
|
|
250
|
+
if (!/^\d+$/.test(exitCode)) throw new Error("--exit-code requires a non-negative integer");
|
|
251
|
+
options.checkExitCode = Number(exitCode);
|
|
252
|
+
index += 1;
|
|
253
|
+
} else if (argument === "--details") {
|
|
254
|
+
const details = argv[index + 1];
|
|
255
|
+
if (!details || details.startsWith("-")) throw new Error("--details requires a JSON object");
|
|
256
|
+
try {
|
|
257
|
+
options.checkDetails = JSON.parse(details);
|
|
258
|
+
} catch {
|
|
259
|
+
throw new Error("--details must be valid JSON");
|
|
260
|
+
}
|
|
261
|
+
if (!options.checkDetails || typeof options.checkDetails !== "object" || Array.isArray(options.checkDetails)) {
|
|
262
|
+
throw new Error("--details must be a JSON object");
|
|
263
|
+
}
|
|
264
|
+
index += 1;
|
|
156
265
|
} else if (argument === "--path") {
|
|
157
266
|
options.path = argv[index + 1];
|
|
158
267
|
if (!options.path || options.path.startsWith("-")) throw new Error("--path requires a directory");
|
|
@@ -162,6 +271,8 @@ export function parseArgs(argv) {
|
|
|
162
271
|
if (!options.path || options.path.startsWith("-")) throw new Error("--path requires a directory");
|
|
163
272
|
} else if (argument === "--version" || argument === "-v") {
|
|
164
273
|
options.version = true;
|
|
274
|
+
} else if (!argument.startsWith("-") && command === "policy" && !options.policy) {
|
|
275
|
+
options.policy = argument;
|
|
165
276
|
} else if (!argument.startsWith("-") && !command) {
|
|
166
277
|
command = argument;
|
|
167
278
|
} else {
|
|
@@ -171,7 +282,7 @@ export function parseArgs(argv) {
|
|
|
171
282
|
|
|
172
283
|
if (!command) return { command: null, options };
|
|
173
284
|
|
|
174
|
-
const jsonCommands = ["doctor", "route", "inspect", "status", "validate-state", "clear-state", "validate-receipt", "validate-protocol"];
|
|
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"];
|
|
175
286
|
if (!jsonCommands.includes(command) && options.json) {
|
|
176
287
|
throw new Error(`Option --json is not valid for ${command}`);
|
|
177
288
|
}
|
|
@@ -181,7 +292,7 @@ export function parseArgs(argv) {
|
|
|
181
292
|
].includes(command) && options.dryRun) {
|
|
182
293
|
throw new Error(`Option --dry-run is not valid for ${command}`);
|
|
183
294
|
}
|
|
184
|
-
if (
|
|
295
|
+
if (!["doctor", "preflight", "complete", "audit", "report"].includes(command) && options.strict) {
|
|
185
296
|
throw new Error(`Option --strict is not valid for ${command}`);
|
|
186
297
|
}
|
|
187
298
|
if (command !== "doctor" && options.adopt.length > 0) {
|
|
@@ -190,6 +301,21 @@ export function parseArgs(argv) {
|
|
|
190
301
|
if (command !== "route" && (options.work || options.surfaces.length || options.risks.length || options.platforms.length || options.behaviorChange || options.executableChange)) {
|
|
191
302
|
throw new Error(`Route options are not valid for ${command}`);
|
|
192
303
|
}
|
|
304
|
+
if (command !== "advance" && options.to) {
|
|
305
|
+
throw new Error(`Option --to is not valid for ${command}`);
|
|
306
|
+
}
|
|
307
|
+
if (command === "policy" && !options.policy) {
|
|
308
|
+
throw new Error("policy requires a name");
|
|
309
|
+
}
|
|
310
|
+
if (command !== "policy" && options.policy) {
|
|
311
|
+
throw new Error(`Policy name is not valid for ${command}`);
|
|
312
|
+
}
|
|
313
|
+
if (command === "bundle" && !options.task) {
|
|
314
|
+
throw new Error("bundle requires --task");
|
|
315
|
+
}
|
|
316
|
+
if (command !== "bundle" && options.task) {
|
|
317
|
+
throw new Error(`--task is not valid for ${command}`);
|
|
318
|
+
}
|
|
193
319
|
if (command !== "validate-receipt" && options.file) {
|
|
194
320
|
throw new Error(`Option --file is not valid for ${command}`);
|
|
195
321
|
}
|
|
@@ -199,6 +325,27 @@ export function parseArgs(argv) {
|
|
|
199
325
|
if (command !== "validate-protocol" && (options.routeFile || options.stateFile || options.receiptFile || options.taskBriefFiles.length > 0 || options.delegatedResultFiles.length > 0)) {
|
|
200
326
|
throw new Error(`Protocol artifact options are not valid for ${command}`);
|
|
201
327
|
}
|
|
328
|
+
const checkOptions = [
|
|
329
|
+
options.checkId,
|
|
330
|
+
options.checkKind,
|
|
331
|
+
options.checkRequirement,
|
|
332
|
+
options.checkStatus,
|
|
333
|
+
options.checkEvidenceKind,
|
|
334
|
+
options.checkCommand,
|
|
335
|
+
options.checkResult,
|
|
336
|
+
options.checkExitCode,
|
|
337
|
+
options.checkDetails,
|
|
338
|
+
];
|
|
339
|
+
if (command !== "record-check" && checkOptions.some((value) => value !== null)) {
|
|
340
|
+
throw new Error(`Check recording options are not valid for ${command}`);
|
|
341
|
+
}
|
|
342
|
+
if (command === "record-check" && !options.help) {
|
|
343
|
+
if (!options.checkId) throw new Error("record-check requires --id");
|
|
344
|
+
if (!options.checkRequirement) throw new Error("record-check requires --requirement");
|
|
345
|
+
if (!options.checkStatus) throw new Error("record-check requires --status");
|
|
346
|
+
if (!options.checkEvidenceKind) throw new Error("record-check requires --evidence-kind");
|
|
347
|
+
if (!options.checkCommand && !options.checkResult) throw new Error("record-check requires --command or --result");
|
|
348
|
+
}
|
|
202
349
|
return { command, options };
|
|
203
350
|
}
|
|
204
351
|
|
|
@@ -257,7 +404,9 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
257
404
|
}
|
|
258
405
|
|
|
259
406
|
if (command === "route") {
|
|
260
|
-
const result = runRoute({
|
|
407
|
+
const result = await runRoute({
|
|
408
|
+
target,
|
|
409
|
+
packageRoot,
|
|
261
410
|
workType: options.work,
|
|
262
411
|
surfaces: options.surfaces,
|
|
263
412
|
risks: options.risks,
|
|
@@ -269,6 +418,78 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
269
418
|
return 0;
|
|
270
419
|
}
|
|
271
420
|
|
|
421
|
+
if (command === "activate") {
|
|
422
|
+
const result = await runActivate({ target, packageRoot });
|
|
423
|
+
console.log(options.json ? JSON.stringify(result, null, 2) : formatActivateResult(result));
|
|
424
|
+
return 0;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (command === "preflight") {
|
|
428
|
+
const result = await runPreflight({ target, packageRoot, strict: options.strict });
|
|
429
|
+
console.log(options.json ? JSON.stringify(result, null, 2) : formatPreflightResult(result));
|
|
430
|
+
return result.status === "READY" ? 0 : 1;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (command === "advance") {
|
|
434
|
+
const result = await runAdvance({ target, packageRoot, to: options.to });
|
|
435
|
+
console.log(options.json ? JSON.stringify(result, null, 2) : formatAdvanceResult(result));
|
|
436
|
+
return 0;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (command === "prepare-completion") {
|
|
440
|
+
const result = await runPrepareCompletion({ target, packageRoot });
|
|
441
|
+
console.log(options.json ? JSON.stringify(result, null, 2) : formatPrepareCompletionResult(result));
|
|
442
|
+
return 0;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (command === "record-check") {
|
|
446
|
+
const result = await runRecordCheck({
|
|
447
|
+
target,
|
|
448
|
+
packageRoot,
|
|
449
|
+
id: options.checkId,
|
|
450
|
+
kind: options.checkKind ?? "command",
|
|
451
|
+
requirement: options.checkRequirement,
|
|
452
|
+
status: options.checkStatus,
|
|
453
|
+
evidenceKind: options.checkEvidenceKind,
|
|
454
|
+
command: options.checkCommand,
|
|
455
|
+
result: options.checkResult,
|
|
456
|
+
exitCode: options.checkExitCode,
|
|
457
|
+
details: options.checkDetails ?? undefined,
|
|
458
|
+
});
|
|
459
|
+
console.log(options.json ? JSON.stringify(result, null, 2) : formatRecordCheckResult(result));
|
|
460
|
+
return 0;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (command === "complete") {
|
|
464
|
+
const result = await runComplete({ target, packageRoot, strict: options.strict });
|
|
465
|
+
console.log(options.json ? JSON.stringify(result, null, 2) : formatCompleteResult(result));
|
|
466
|
+
return result.status === "VALID" ? 0 : 1;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (command === "audit") {
|
|
470
|
+
const result = await runAudit({ target, packageRoot, strict: options.strict });
|
|
471
|
+
console.log(options.json ? JSON.stringify(result, null, 2) : formatAuditResult(result));
|
|
472
|
+
return result.status === "VALID" ? 0 : 1;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (command === "report") {
|
|
476
|
+
const result = await runReport({ target, packageRoot, strict: options.strict });
|
|
477
|
+
console.log(options.json ? JSON.stringify(result, null, 2) : formatReportResult(result));
|
|
478
|
+
return result.verdict === "VALID" ? 0 : 1;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (command === "policy") {
|
|
482
|
+
const result = await runPolicy({ target, packageRoot, name: options.policy });
|
|
483
|
+
console.log(options.json ? JSON.stringify(result, null, 2) : formatPolicyResult(result));
|
|
484
|
+
return 0;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if (command === "bundle") {
|
|
488
|
+
const result = await runBundle({ target, packageRoot, taskId: options.task });
|
|
489
|
+
console.log(options.json ? JSON.stringify(result, null, 2) : formatBundleResult(result));
|
|
490
|
+
return 0;
|
|
491
|
+
}
|
|
492
|
+
|
|
272
493
|
if (command === "inspect") {
|
|
273
494
|
const result = await inspectTarget({ target, packageRoot, contractFile: options.contractFile });
|
|
274
495
|
console.log(options.json ? JSON.stringify(result, null, 2) : formatInspectResult(result));
|
|
@@ -285,9 +506,9 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
285
506
|
const result = await runValidateProtocol({
|
|
286
507
|
target,
|
|
287
508
|
packageRoot,
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
509
|
+
stateFile: options.stateFile ?? ARTIFACT_PATHS.state,
|
|
510
|
+
receiptFile: options.receiptFile ?? ARTIFACT_PATHS.receipt,
|
|
511
|
+
routeFile: options.routeFile ?? ARTIFACT_PATHS.route,
|
|
291
512
|
contractFile: options.contractFile,
|
|
292
513
|
taskBriefFiles: options.taskBriefFiles,
|
|
293
514
|
delegatedResultFiles: options.delegatedResultFiles,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { activateSession } from "../core/activation.js";
|
|
2
|
+
|
|
3
|
+
export { activateSession };
|
|
4
|
+
|
|
5
|
+
export async function runActivate({ target, packageRoot }) {
|
|
6
|
+
return activateSession(target, packageRoot);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function formatActivateResult(result) {
|
|
10
|
+
return `activated: ${result.activationMarker}\nsession: ${result.sessionId}\n`;
|
|
11
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { advanceWorkState } from "../core/phase.js";
|
|
2
|
+
|
|
3
|
+
export { advanceWorkState };
|
|
4
|
+
|
|
5
|
+
export async function runAdvance({ target, packageRoot, to }) {
|
|
6
|
+
if (!to) throw new Error("--to is required for advance");
|
|
7
|
+
return advanceWorkState(target, to, { packageRoot });
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function formatAdvanceResult(result) {
|
|
11
|
+
return `phase: ${result.phase}\nprevious: ${result.previousPhase ?? "none"}\n`;
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { evaluateAudit } from "../core/audit.js";
|
|
2
|
+
|
|
3
|
+
export { evaluateAudit as runAudit };
|
|
4
|
+
|
|
5
|
+
export function formatAuditResult(result) {
|
|
6
|
+
const lines = [`FORGELOOP AUDIT: ${result.status}`];
|
|
7
|
+
for (const error of result.errors) {
|
|
8
|
+
lines.push(`${error.code}: ${error.message}`);
|
|
9
|
+
if (error.next) lines.push(`NEXT: ${error.next}`);
|
|
10
|
+
}
|
|
11
|
+
return `${lines.join("\n")}\n`;
|
|
12
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { exportTaskBundle } from "../core/bundles.js";
|
|
2
|
+
|
|
3
|
+
export async function runBundle({ target, packageRoot, taskId }) {
|
|
4
|
+
if (!taskId) throw new Error("--task is required for bundle");
|
|
5
|
+
return exportTaskBundle(target, taskId, packageRoot);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function formatBundleResult(result) {
|
|
9
|
+
return `bundle: ${result.taskId}\npath: ${result.path}\nartifacts: ${result.artifacts.join(", ")}\n`;
|
|
10
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { runComplete as evaluateAndComplete } from "../core/completion.js";
|
|
2
|
+
|
|
3
|
+
export { evaluateAndComplete as runComplete };
|
|
4
|
+
|
|
5
|
+
export function formatCompleteResult(result) {
|
|
6
|
+
const lines = [`FORGELOOP COMPLETE: ${result.status}`];
|
|
7
|
+
if (result.status === "VALID") {
|
|
8
|
+
lines.push(`TASK: ${result.taskStatus}`);
|
|
9
|
+
lines.push(`VERIFICATION: ${result.verificationStatus}`);
|
|
10
|
+
lines.push(`PUBLICATION: ${result.publicationStatus}`);
|
|
11
|
+
lines.push(`PRODUCTION_READINESS: ${result.productionReadiness}`);
|
|
12
|
+
}
|
|
13
|
+
for (const error of result.errors) {
|
|
14
|
+
lines.push(`${error.code}: ${error.message}`);
|
|
15
|
+
if (error.next) lines.push(`NEXT: ${error.next}`);
|
|
16
|
+
}
|
|
17
|
+
return `${lines.join("\n")}\n`;
|
|
18
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createConfig, writeConfig } from "../core/config.js";
|
|
2
|
+
import { getPolicy, validatePolicy } from "../core/policies.js";
|
|
3
|
+
|
|
4
|
+
export async function runPolicy({ target, packageRoot, name }) {
|
|
5
|
+
const policy = getPolicy(name);
|
|
6
|
+
await validatePolicy(policy, packageRoot);
|
|
7
|
+
const config = createConfig({
|
|
8
|
+
complianceMode: policy.complianceMode,
|
|
9
|
+
policy: policy.name,
|
|
10
|
+
requiredGates: policy.requiredGates,
|
|
11
|
+
requiredEvidence: policy.requiredEvidence,
|
|
12
|
+
});
|
|
13
|
+
await writeConfig(target, config, packageRoot);
|
|
14
|
+
return { policy, config };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function formatPolicyResult(result) {
|
|
18
|
+
return `policy: ${result.policy.name}\nmode: ${result.policy.complianceMode}\n`;
|
|
19
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { runPreflight as evaluateAndPersistPreflight } from "../core/preflight.js";
|
|
2
|
+
|
|
3
|
+
export { evaluateAndPersistPreflight as runPreflight };
|
|
4
|
+
|
|
5
|
+
export function formatPreflightResult(result) {
|
|
6
|
+
const lines = [`FORGELOOP PREFLIGHT: ${result.status}`];
|
|
7
|
+
for (const error of result.errors) {
|
|
8
|
+
lines.push(`${error.code}: ${error.message}`);
|
|
9
|
+
if (error.next) lines.push(`NEXT: ${error.next}`);
|
|
10
|
+
}
|
|
11
|
+
return `${lines.join("\n")}\n`;
|
|
12
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { prepareCompletion as prepareCompletionArtifacts } from "../core/completion-artifacts.js";
|
|
2
|
+
|
|
3
|
+
export { prepareCompletionArtifacts as prepareCompletion };
|
|
4
|
+
|
|
5
|
+
export async function runPrepareCompletion({ target, packageRoot }) {
|
|
6
|
+
return prepareCompletionArtifacts({ target, packageRoot });
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function formatPrepareCompletionResult(result) {
|
|
10
|
+
return [
|
|
11
|
+
"FORGELOOP COMPLETION PREPARED",
|
|
12
|
+
`receipt: ${result.path}`,
|
|
13
|
+
`task: ${result.receipt.taskId}`,
|
|
14
|
+
`required evidence: ${result.requiredEvidence.length}`,
|
|
15
|
+
`changed paths: ${result.changedPaths.length}`,
|
|
16
|
+
"",
|
|
17
|
+
].join("\n");
|
|
18
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { recordCheck as recordCheckArtifact } from "../core/completion-artifacts.js";
|
|
2
|
+
|
|
3
|
+
export { recordCheckArtifact as recordCheck };
|
|
4
|
+
|
|
5
|
+
export async function runRecordCheck(input) {
|
|
6
|
+
return recordCheckArtifact(input);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function formatRecordCheckResult(result) {
|
|
10
|
+
return [
|
|
11
|
+
"FORGELOOP CHECK RECORDED",
|
|
12
|
+
`id: ${result.check.id}`,
|
|
13
|
+
`requirement: ${result.check.requirement}`,
|
|
14
|
+
`status: ${result.check.status}`,
|
|
15
|
+
`evidence: ${result.evidence.kind}`,
|
|
16
|
+
`coverage: ${result.coverage.find((item) => item.requirement === result.check.requirement)?.status ?? "NOT_VERIFIED"}`,
|
|
17
|
+
`receipt: ${result.path}`,
|
|
18
|
+
"",
|
|
19
|
+
].join("\n");
|
|
20
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { evaluateReport } from "../core/report.js";
|
|
2
|
+
|
|
3
|
+
export { evaluateReport as runReport };
|
|
4
|
+
|
|
5
|
+
export function formatReportResult(result) {
|
|
6
|
+
const lines = ["ForgeLoop Compliance"];
|
|
7
|
+
for (const item of result.sections) lines.push(`${item.label.padEnd(20)} ${item.status}`);
|
|
8
|
+
lines.push(`\nVERDICT: ${result.verdict}`);
|
|
9
|
+
return `${lines.join("\n")}\n`;
|
|
10
|
+
}
|
package/src/commands/route.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { evaluateRoute } from "../core/router.js";
|
|
2
|
+
import { persistRoute } from "../core/route-artifact.js";
|
|
3
|
+
import { readContract } from "../core/contract.js";
|
|
2
4
|
|
|
3
|
-
export function runRoute({ workType, surfaces, risks, platforms, behaviorChange, executableChange }) {
|
|
4
|
-
|
|
5
|
+
export async function runRoute({ target, packageRoot, workType, surfaces, risks, platforms, behaviorChange, executableChange }) {
|
|
6
|
+
const route = evaluateRoute({
|
|
5
7
|
workType,
|
|
6
8
|
surfaces,
|
|
7
9
|
risks,
|
|
@@ -9,6 +11,16 @@ export function runRoute({ workType, surfaces, risks, platforms, behaviorChange,
|
|
|
9
11
|
behaviorChange,
|
|
10
12
|
executableChange,
|
|
11
13
|
});
|
|
14
|
+
if (target && packageRoot) {
|
|
15
|
+
let contractFingerprint;
|
|
16
|
+
try {
|
|
17
|
+
contractFingerprint = (await readContract(target, packageRoot)).fingerprint;
|
|
18
|
+
} catch {
|
|
19
|
+
contractFingerprint = undefined;
|
|
20
|
+
}
|
|
21
|
+
await persistRoute(target, route, packageRoot, { contractFingerprint });
|
|
22
|
+
}
|
|
23
|
+
return route;
|
|
12
24
|
}
|
|
13
25
|
|
|
14
26
|
export function formatRouteResult(result) {
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import { assertSafePath, ensureWithin, readBytes } from "../core/filesystem.js";
|
|
2
2
|
import { validateReceipt } from "../core/receipt.js";
|
|
3
3
|
import { assertJsonBytes, assertJsonLimits } from "../core/json-safety.js";
|
|
4
|
+
import { ARTIFACT_PATHS } from "../core/artifacts.js";
|
|
4
5
|
|
|
5
6
|
export async function runValidateReceipt({ target, packageRoot, file }) {
|
|
6
|
-
|
|
7
|
-
await assertSafePath(target,
|
|
8
|
-
const receiptPath = ensureWithin(target,
|
|
7
|
+
const relativeFile = file ?? ARTIFACT_PATHS.receipt;
|
|
8
|
+
await assertSafePath(target, relativeFile);
|
|
9
|
+
const receiptPath = ensureWithin(target, relativeFile);
|
|
9
10
|
let receipt;
|
|
10
11
|
try {
|
|
11
12
|
const bytes = await readBytes(receiptPath);
|
|
12
|
-
assertJsonBytes(bytes,
|
|
13
|
+
assertJsonBytes(bytes, relativeFile);
|
|
13
14
|
receipt = JSON.parse(bytes.toString("utf8"));
|
|
14
|
-
assertJsonLimits(receipt,
|
|
15
|
+
assertJsonLimits(receipt, relativeFile);
|
|
15
16
|
} catch (error) {
|
|
16
|
-
throw new Error(`Unable to parse receipt ${
|
|
17
|
+
throw new Error(`Unable to parse receipt ${relativeFile}: ${error.message}`);
|
|
17
18
|
}
|
|
18
19
|
return validateReceipt(receipt, packageRoot);
|
|
19
20
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { ARTIFACT_PATHS, writeJsonArtifact } from "./artifacts.js";
|
|
4
|
+
import { PROTOCOL_VERSION } from "./protocol.js";
|
|
5
|
+
|
|
6
|
+
export async function activateSession(target, packageRoot, options = {}) {
|
|
7
|
+
const value = {
|
|
8
|
+
schemaVersion: 1,
|
|
9
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
10
|
+
sessionId: randomUUID(),
|
|
11
|
+
activationMarker: `forgeloop-${randomUUID()}`,
|
|
12
|
+
createdAt: options.createdAt ?? new Date().toISOString(),
|
|
13
|
+
};
|
|
14
|
+
const written = await writeJsonArtifact(target, ARTIFACT_PATHS.session, value, "activation", packageRoot, options);
|
|
15
|
+
return written.value;
|
|
16
|
+
}
|