@cassiomc1/forgeloop 1.1.1 → 1.2.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 +1 -1
- package/.github/copilot-instructions.md +1 -1
- package/AGENTS.md +1 -1
- package/CLAUDE.md +1 -1
- package/DOCS_INDEX.md +3 -0
- package/ENG/design-code-eng.md +124 -0
- package/ENG/premium-sites-studio-eng.md +28 -0
- package/ENG/taste-frontend-eng.md +3 -2
- package/ENG/test-code-eng.md +45 -0
- package/LOOP_ENGINEERING.md +74 -0
- package/LOOP_SYSTEM_DESIGN.md +9 -5
- package/ORCHESTRATOR_INTEGRATION.md +41 -6
- package/PROTOCOL_INTEGRATION.md +13 -0
- package/README.md +40 -6
- package/TERMINOLOGY.md +10 -0
- package/THIRD_PARTY_NOTICES.md +58 -1
- package/THREAT_MODEL.md +12 -1
- package/docs/ARTIFACT_REFERENCE.md +152 -2
- package/docs/CLI_REFERENCE.md +346 -30
- package/docs/CROSS_HARNESS_CONTINUITY.md +1 -0
- package/docs/DOCUMENTATION_GUIDE.md +41 -4
- package/docs/GETTING_STARTED.md +39 -8
- package/docs/RECIPES.md +66 -7
- package/docs/TROUBLESHOOTING.md +279 -6
- package/package.json +1 -1
- package/schemas/policy-baseline.schema.json +26 -0
- package/schemas/policy-discovery.schema.json +45 -0
- package/schemas/policy-lock.schema.json +16 -0
- package/schemas/policy-rules.schema.json +48 -0
- package/schemas/policy-snapshot.schema.json +16 -0
- package/src/cli.js +102 -1
- package/src/commands/baseline.js +120 -0
- package/src/commands/init.js +304 -6
- package/src/commands/next.js +15 -1
- package/src/commands/policy-diff.js +51 -0
- package/src/commands/policy-discover.js +42 -0
- package/src/commands/policy-status.js +33 -0
- package/src/commands/profile-interview.js +50 -0
- package/src/commands/progress.js +51 -0
- package/src/commands/reconcile-closure.js +49 -0
- package/src/commands/record-decision-criterion.js +34 -0
- package/src/commands/record-diagnosis.js +49 -0
- package/src/commands/rule-verify.js +36 -0
- package/src/commands/validate-receipt.js +38 -3
- package/src/core/artifact-registry.js +60 -0
- package/src/core/audit.js +24 -0
- package/src/core/cli-command-definitions.js +163 -7
- package/src/core/cli-metadata.js +1 -1
- package/src/core/completion-artifacts.js +29 -3
- package/src/core/completion.js +101 -10
- package/src/core/diagnosis-model.js +214 -0
- package/src/core/diagnosis.js +171 -0
- package/src/core/error-codes.js +292 -0
- package/src/core/events.js +47 -1
- package/src/core/execution-prerequisites.js +38 -20
- package/src/core/execution.js +20 -3
- package/src/core/native-adapters.js +14 -4
- package/src/core/next-action-model.js +40 -5
- package/src/core/next-action.js +234 -91
- package/src/core/phase.js +29 -0
- package/src/core/policy-adapters.js +276 -0
- package/src/core/policy-baseline.js +144 -0
- package/src/core/policy-diff.js +133 -0
- package/src/core/policy-discovery.js +225 -0
- package/src/core/policy-engine.js +533 -0
- package/src/core/policy-mutation.js +139 -0
- package/src/core/preflight-consistency.js +23 -15
- package/src/core/preflight-model.js +10 -2
- package/src/core/preflight.js +65 -1
- package/src/core/progress.js +143 -0
- package/src/core/protocol.js +8 -0
- package/src/core/reconcile-closure.js +173 -0
- package/src/core/schema-validation.js +6 -0
- package/src/core/settlement-model.js +85 -0
- package/src/core/settlement.js +78 -0
- package/src/core/task-context.js +11 -0
- package/src/core/task-discovery.js +67 -1
- package/src/core/task-paths.js +9 -0
- package/src/core/templates.js +5 -0
|
@@ -5,6 +5,7 @@ import { evaluatePreflight, validatePersistedPreflight } from "./preflight.js";
|
|
|
5
5
|
import { readPersistedRoute } from "./route-artifact.js";
|
|
6
6
|
import { stateIdentityErrors } from "./completion-relationships.js";
|
|
7
7
|
import { classifyLoadedWorkState } from "./work-state.js";
|
|
8
|
+
import { taskArtifactPath } from "./task-paths.js";
|
|
8
9
|
|
|
9
10
|
const START_EXECUTION_EVENTS = Object.freeze([
|
|
10
11
|
"CONTRACT_VALIDATED",
|
|
@@ -128,34 +129,51 @@ function prerequisiteLedgerErrors(ledger, taskId, preflight, route) {
|
|
|
128
129
|
return errors;
|
|
129
130
|
}
|
|
130
131
|
|
|
131
|
-
export async function evaluateStartExecutionPrerequisites({
|
|
132
|
+
export async function evaluateStartExecutionPrerequisites({
|
|
133
|
+
target,
|
|
134
|
+
state,
|
|
135
|
+
packageRoot,
|
|
136
|
+
taskId = null,
|
|
137
|
+
contractPath = null,
|
|
138
|
+
routePath = null,
|
|
139
|
+
statePath = null,
|
|
140
|
+
preflightPath = null,
|
|
141
|
+
eventsPath = null,
|
|
142
|
+
} = {}) {
|
|
143
|
+
const effectiveTaskId = taskId ?? null;
|
|
144
|
+
const contractRel = contractPath ?? (effectiveTaskId ? taskArtifactPath(effectiveTaskId, "contract") : ARTIFACT_PATHS.contract);
|
|
145
|
+
const routeRel = routePath ?? (effectiveTaskId ? taskArtifactPath(effectiveTaskId, "route") : ARTIFACT_PATHS.route);
|
|
146
|
+
const stateRel = statePath ?? (effectiveTaskId ? taskArtifactPath(effectiveTaskId, "state") : ARTIFACT_PATHS.state);
|
|
147
|
+
const preflightRel = preflightPath ?? (effectiveTaskId ? taskArtifactPath(effectiveTaskId, "preflight") : ARTIFACT_PATHS.preflight);
|
|
148
|
+
const eventsRel = eventsPath ?? (effectiveTaskId ? taskArtifactPath(effectiveTaskId, "events") : ARTIFACT_PATHS.events);
|
|
149
|
+
|
|
132
150
|
const errors = [];
|
|
133
151
|
const requiredArtifacts = [
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
152
|
+
stateRel,
|
|
153
|
+
contractRel,
|
|
154
|
+
routeRel,
|
|
155
|
+
preflightRel,
|
|
156
|
+
eventsRel,
|
|
139
157
|
];
|
|
140
158
|
if (!state) {
|
|
141
159
|
return {
|
|
142
|
-
errors: [issue("E_PHASE_PREREQUISITE_MISSING", "EXECUTING requires a work state", [
|
|
160
|
+
errors: [issue("E_PHASE_PREREQUISITE_MISSING", "EXECUTING requires a work state", [stateRel])],
|
|
143
161
|
requiredArtifacts,
|
|
144
162
|
};
|
|
145
163
|
}
|
|
146
164
|
|
|
147
165
|
const contract = await load(
|
|
148
|
-
() => readContract(target, packageRoot),
|
|
166
|
+
() => readContract(target, packageRoot, { taskId: effectiveTaskId, contractPath }),
|
|
149
167
|
"E_PHASE_PREREQUISITE_MISSING",
|
|
150
|
-
`EXECUTING requires ${
|
|
151
|
-
[
|
|
168
|
+
`EXECUTING requires ${contractRel}`,
|
|
169
|
+
[contractRel],
|
|
152
170
|
errors,
|
|
153
171
|
);
|
|
154
172
|
const route = await load(
|
|
155
|
-
() => readPersistedRoute(target, packageRoot),
|
|
173
|
+
() => readPersistedRoute(target, packageRoot, { taskId: effectiveTaskId, routePath }),
|
|
156
174
|
"E_PHASE_PREREQUISITE_MISSING",
|
|
157
|
-
`EXECUTING requires ${
|
|
158
|
-
[
|
|
175
|
+
`EXECUTING requires ${routeRel}`,
|
|
176
|
+
[routeRel],
|
|
159
177
|
errors,
|
|
160
178
|
);
|
|
161
179
|
if (!contract || !route) return { errors, requiredArtifacts, contract, route };
|
|
@@ -164,7 +182,7 @@ export async function evaluateStartExecutionPrerequisites({ target, state, packa
|
|
|
164
182
|
errors.push(issue(
|
|
165
183
|
"E_ROUTE_STALE",
|
|
166
184
|
"EXECUTING requires work state and route to match the current contract",
|
|
167
|
-
[
|
|
185
|
+
[stateRel, routeRel, contractRel],
|
|
168
186
|
));
|
|
169
187
|
}
|
|
170
188
|
errors.push(...stateIdentityErrors({ contract, route, state }));
|
|
@@ -172,14 +190,14 @@ export async function evaluateStartExecutionPrerequisites({ target, state, packa
|
|
|
172
190
|
const freshness = await classifyLoadedWorkState({
|
|
173
191
|
target,
|
|
174
192
|
state,
|
|
175
|
-
contractFile:
|
|
193
|
+
contractFile: contractRel,
|
|
176
194
|
});
|
|
177
195
|
errors.push(...freshnessErrors(state, freshness));
|
|
178
196
|
|
|
179
|
-
const preflight = await evaluatePreflight({ target, packageRoot });
|
|
197
|
+
const preflight = await evaluatePreflight({ target, packageRoot, taskId: effectiveTaskId, contractPath, routePath, statePath });
|
|
180
198
|
let persistedPreflight = null;
|
|
181
199
|
try {
|
|
182
|
-
persistedPreflight = await readJsonArtifact(target,
|
|
200
|
+
persistedPreflight = await readJsonArtifact(target, preflightRel, "preflight", packageRoot);
|
|
183
201
|
} catch {
|
|
184
202
|
// validatePersistedPreflight reports the stable, actionable preflight reason.
|
|
185
203
|
}
|
|
@@ -189,16 +207,16 @@ export async function evaluateStartExecutionPrerequisites({ target, state, packa
|
|
|
189
207
|
errors.push(issue(
|
|
190
208
|
"E_PREFLIGHT_GATES_STALE",
|
|
191
209
|
"Work state gate sets do not match the current preflight evaluation",
|
|
192
|
-
[
|
|
210
|
+
[stateRel, preflightRel, ARTIFACT_PATHS.gates],
|
|
193
211
|
));
|
|
194
212
|
}
|
|
195
213
|
|
|
196
|
-
const ledger = await validateEventLedger(target, packageRoot);
|
|
214
|
+
const ledger = await validateEventLedger(target, packageRoot, { taskId: effectiveTaskId, eventsPath });
|
|
197
215
|
errors.push(...prerequisiteLedgerErrors(ledger, contract.value.taskId, preflight, route));
|
|
198
216
|
errors.push(...validateStateLedgerCoherence(state, ledger.events).map((error) => issue(
|
|
199
217
|
error.code,
|
|
200
218
|
error.message,
|
|
201
|
-
[
|
|
219
|
+
[stateRel, eventsRel],
|
|
202
220
|
)));
|
|
203
221
|
errors.push(...persistedPreflightErrors);
|
|
204
222
|
return { errors, requiredArtifacts, contract, route, preflight, persistedPreflight, ledger };
|
package/src/core/execution.js
CHANGED
|
@@ -2,14 +2,14 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import { readdir } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import { fileExists } from "./filesystem.js";
|
|
5
|
+
import { ensureWithin, fileExists } from "./filesystem.js";
|
|
6
6
|
import {
|
|
7
7
|
ARTIFACT_PATHS,
|
|
8
8
|
executionArtifactPath,
|
|
9
9
|
readJsonArtifact,
|
|
10
10
|
writeJsonArtifact,
|
|
11
11
|
} from "./artifacts.js";
|
|
12
|
-
import { taskExecutionPath } from "./task-paths.js";
|
|
12
|
+
import { taskArtifactPath, taskExecutionPath } from "./task-paths.js";
|
|
13
13
|
import {
|
|
14
14
|
resolveExecutionResolution,
|
|
15
15
|
validateVerificationAuthority,
|
|
@@ -77,6 +77,23 @@ function executeProcess(argv, cwd) {
|
|
|
77
77
|
});
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Resolves where a new execution artifact should be written. Task-scoped
|
|
82
|
+
* execution artifacts require a real modern task namespace (a task.json
|
|
83
|
+
* descriptor). A descriptor-less task is legacy: writing task-scoped here
|
|
84
|
+
* would create a phantom `.forgeloop/task-state/<key>/executions/` namespace
|
|
85
|
+
* that corrupts task discovery. Reads already fall back across both
|
|
86
|
+
* locations, so a legacy execution stays resolvable.
|
|
87
|
+
*/
|
|
88
|
+
export async function resolveExecutionArtifactPath(target, taskId, executionId) {
|
|
89
|
+
if (!taskId) return executionArtifactPath(executionId);
|
|
90
|
+
const descriptorRel = taskArtifactPath(taskId, "descriptor");
|
|
91
|
+
if (await fileExists(ensureWithin(target, descriptorRel))) {
|
|
92
|
+
return taskExecutionPath(taskId, executionId);
|
|
93
|
+
}
|
|
94
|
+
return executionArtifactPath(executionId);
|
|
95
|
+
}
|
|
96
|
+
|
|
80
97
|
export async function runCommandExecution({
|
|
81
98
|
target,
|
|
82
99
|
packageRoot,
|
|
@@ -153,7 +170,7 @@ export async function runCommandExecution({
|
|
|
153
170
|
status: processResult.exitCode === 0 && !processResult.spawnError ? "passed" : "failed",
|
|
154
171
|
exitCode: processResult.exitCode,
|
|
155
172
|
};
|
|
156
|
-
const execPath = executionPath ?? (
|
|
173
|
+
const execPath = executionPath ?? await resolveExecutionArtifactPath(target, taskId, executionId);
|
|
157
174
|
const written = await writeJsonArtifact(target, execPath, execution, "execution", packageRoot);
|
|
158
175
|
return {
|
|
159
176
|
path: written.path,
|
|
@@ -41,10 +41,20 @@ Before changing product or executable files, establish the ForgeLoop contract,
|
|
|
41
41
|
route, required gates, and READY preflight.
|
|
42
42
|
|
|
43
43
|
Before creating or activating new lifecycle state:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
\`forgeloop
|
|
47
|
-
|
|
44
|
+
|
|
45
|
+
1. Inspect existing ForgeLoop tasks first.
|
|
46
|
+
2. Use \`forgeloop task-list --json\` to discover current task namespaces.
|
|
47
|
+
3. If an existing task is selected or identifiable, use
|
|
48
|
+
\`forgeloop next --task <id> --json\` before creating another task.
|
|
49
|
+
4. Reconcile continuity when the selected task has continuity state.
|
|
50
|
+
5. Inspect the checkout before resuming work.
|
|
51
|
+
|
|
52
|
+
A change of harness, model, provider, IDE, process, terminal, or session
|
|
53
|
+
does not create a new task.
|
|
54
|
+
|
|
55
|
+
Legacy singleton state such as \`.forgeloop/work-state.json\` remains
|
|
56
|
+
supported only for backward compatibility and must not be treated as the
|
|
57
|
+
primary modern discovery mechanism.
|
|
48
58
|
|
|
49
59
|
Use the project-local ForgeLoop CLI for lifecycle-owned protocol state.
|
|
50
60
|
Never manually synthesize lifecycle chronology or assign ForgeLoop COMPLETE.
|
|
@@ -13,13 +13,24 @@ export const NEXT_ACTIONS = Object.freeze({
|
|
|
13
13
|
CONTINUE_IMPLEMENTATION: "CONTINUE_IMPLEMENTATION",
|
|
14
14
|
RECORD_VERIFICATION: "RECORD_VERIFICATION",
|
|
15
15
|
DIAGNOSE: "DIAGNOSE",
|
|
16
|
+
RECORD_DIAGNOSIS: "RECORD_DIAGNOSIS",
|
|
16
17
|
CORRECT: "CORRECT",
|
|
18
|
+
CHANGE_STRATEGY: "CHANGE_STRATEGY",
|
|
17
19
|
ENTER_REVIEWING: "ENTER_REVIEWING",
|
|
18
20
|
RECORD_TERMINAL_RESULT: "RECORD_TERMINAL_RESULT",
|
|
19
21
|
PREPARE_COMPLETION: "PREPARE_COMPLETION",
|
|
20
22
|
RUN_COMPLETE: "RUN_COMPLETE",
|
|
21
23
|
RESOLVE_STALE_ROUTE: "RESOLVE_STALE_ROUTE",
|
|
22
24
|
RESOLVE_BLOCKER: "RESOLVE_BLOCKER",
|
|
25
|
+
RESTORE_POLICY: "RESTORE_POLICY",
|
|
26
|
+
REVERIFY_AFTER_POLICY_CHANGE: "REVERIFY_AFTER_POLICY_CHANGE",
|
|
27
|
+
VERIFY_RULE: "VERIFY_RULE",
|
|
28
|
+
RESOLVE_INERT_CHECK: "RESOLVE_INERT_CHECK",
|
|
29
|
+
RUN_REQUIRED_CHECK: "RUN_REQUIRED_CHECK",
|
|
30
|
+
REPAIR_CHECKER: "REPAIR_CHECKER",
|
|
31
|
+
REPAIR_POLICY: "REPAIR_POLICY",
|
|
32
|
+
RESTORE_BASELINE: "RESTORE_BASELINE",
|
|
33
|
+
CONTINUE_WITH_EXISTING_BASELINE: "CONTINUE_WITH_EXISTING_BASELINE",
|
|
23
34
|
NONE: "NONE",
|
|
24
35
|
});
|
|
25
36
|
|
|
@@ -32,13 +43,20 @@ export function result({
|
|
|
32
43
|
commandSpecs = [],
|
|
33
44
|
requiredArtifacts = [],
|
|
34
45
|
missingArtifacts = [],
|
|
46
|
+
progress = undefined,
|
|
35
47
|
}) {
|
|
36
48
|
const normalizedReasons = reasons
|
|
37
|
-
.map((reason) =>
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
49
|
+
.map((reason) => {
|
|
50
|
+
const base = {
|
|
51
|
+
code: reason.code ?? "E_NEXT_ACTION_BLOCKED",
|
|
52
|
+
message: reason.message ?? String(reason),
|
|
53
|
+
artifacts: uniqueSorted(reason.artifacts ?? []),
|
|
54
|
+
};
|
|
55
|
+
if (reason.resolution) {
|
|
56
|
+
base.resolution = structuredClone(reason.resolution);
|
|
57
|
+
}
|
|
58
|
+
return base;
|
|
59
|
+
})
|
|
42
60
|
.sort((left, right) => left.code.localeCompare(right.code)
|
|
43
61
|
|| left.artifacts.join("\0").localeCompare(right.artifacts.join("\0"))
|
|
44
62
|
|| left.message.localeCompare(right.message));
|
|
@@ -57,6 +75,7 @@ export function result({
|
|
|
57
75
|
.sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right))),
|
|
58
76
|
requiredArtifacts: uniqueSorted(requiredArtifacts),
|
|
59
77
|
missingArtifacts: uniqueSorted(missingArtifacts),
|
|
78
|
+
...(progress ? { progress: structuredClone(progress) } : {}),
|
|
60
79
|
};
|
|
61
80
|
}
|
|
62
81
|
|
|
@@ -91,6 +110,22 @@ export function recordCheckCommandSpec(requirement) {
|
|
|
91
110
|
};
|
|
92
111
|
}
|
|
93
112
|
|
|
113
|
+
export function recordDiagnosisCommandSpec() {
|
|
114
|
+
return {
|
|
115
|
+
commandId: "record-diagnosis",
|
|
116
|
+
executable: "forgeloop",
|
|
117
|
+
subcommand: "record-diagnosis",
|
|
118
|
+
argv: ["record-diagnosis"],
|
|
119
|
+
requiredInputs: [
|
|
120
|
+
{ name: "hypothesis", option: "--hypothesis=<text>" },
|
|
121
|
+
{ name: "failureClass", option: "--failure-class=<class>" },
|
|
122
|
+
{ name: "evidenceRef", option: "--evidence-ref=<check-id>", repeatable: true },
|
|
123
|
+
{ name: "settledBy", option: "--settled-by=<text>" },
|
|
124
|
+
{ name: "nextSafeAction", option: "--next-safe-action=<text>" },
|
|
125
|
+
],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
94
129
|
export function recordTerminalResultCommandSpec(requirement) {
|
|
95
130
|
const reqId = requirement.id ?? requirement;
|
|
96
131
|
const type = requirement.type ?? "PUBLICATION";
|