@cassiomc1/forgeloop 1.0.0 → 1.1.0
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 +3 -2
- package/.github/copilot-instructions.md +1 -0
- package/AGENTS.md +2 -1
- package/CLAUDE.md +1 -0
- package/DOCS_INDEX.md +36 -0
- package/ENG/design-code-eng.md +31 -0
- package/ENG/documentation-quality-eng.md +400 -0
- package/EXECUTION_STATE.md +23 -13
- package/GUIDE_ROUTER.md +23 -8
- package/LOOP_ENGINEERING.md +77 -12
- package/PROTOCOL_INTEGRATION.md +9 -6
- package/README.md +82 -39
- package/docs/ARTIFACT_REFERENCE.md +442 -0
- package/docs/CLI_REFERENCE.md +892 -0
- package/docs/CROSS_HARNESS_CONTINUITY.md +198 -0
- package/docs/DOCUMENTATION_GUIDE.md +161 -0
- package/docs/GETTING_STARTED.md +348 -0
- package/docs/RECIPES.md +250 -0
- package/docs/TROUBLESHOOTING.md +345 -0
- package/docs/assets/forgeloop-flow.svg +1 -1
- package/docs/forgeloop-flow.mmd +1 -1
- package/package.json +13 -2
- package/schemas/task-descriptor.schema.json +56 -0
- package/schemas/work-state.schema.json +18 -1
- package/scripts/CI_VALIDATORS.md +7 -0
- package/src/cli.js +280 -388
- package/src/commands/advance.js +5 -2
- package/src/commands/audit.js +11 -1
- package/src/commands/clear-continuity.js +5 -2
- package/src/commands/clear-state.js +5 -2
- package/src/commands/complete.js +9 -1
- package/src/commands/continuity.js +5 -2
- package/src/commands/inspect.js +10 -2
- package/src/commands/next.js +5 -2
- package/src/commands/preflight.js +9 -1
- package/src/commands/prepare-completion.js +5 -2
- package/src/commands/reconcile-continuity.js +5 -2
- package/src/commands/record-check.js +7 -1
- package/src/commands/record-continuity.js +21 -14
- package/src/commands/record-terminal-result.js +7 -1
- package/src/commands/route.js +22 -18
- package/src/commands/run-check.js +52 -44
- package/src/commands/status.js +18 -12
- package/src/commands/task-create.js +94 -0
- package/src/commands/task-list.js +48 -0
- package/src/commands/task-migrate.js +34 -0
- package/src/commands/task-scope.js +75 -0
- package/src/commands/task-show.js +81 -0
- package/src/commands/task-unlock.js +35 -0
- package/src/commands/validate-protocol.js +37 -20
- package/src/commands/validate-state.js +24 -18
- package/src/config/guides.json +42 -0
- package/src/core/activation.js +8 -4
- package/src/core/artifact-registry.js +166 -0
- package/src/core/audit.js +65 -12
- package/src/core/bundles.js +76 -50
- package/src/core/cli-command-definitions.js +611 -0
- package/src/core/cli-metadata.js +23 -0
- package/src/core/completion-artifacts.js +161 -74
- package/src/core/completion.js +134 -76
- package/src/core/continuity.js +20 -13
- package/src/core/contract.js +6 -3
- package/src/core/error-codes.js +197 -0
- package/src/core/events.js +19 -14
- package/src/core/execution.js +38 -6
- package/src/core/gate-artifact.js +12 -9
- package/src/core/gates.js +4 -2
- package/src/core/guide-metadata.js +7 -11
- package/src/core/guide-registry.js +29 -0
- package/src/core/inspect.js +7 -4
- package/src/core/native-adapters.js +6 -0
- package/src/core/phase.js +85 -33
- package/src/core/preflight-consistency.js +24 -14
- package/src/core/preflight-loaders.js +16 -11
- package/src/core/preflight.js +44 -25
- package/src/core/protocol.js +2 -11
- package/src/core/receipt.js +1 -1
- package/src/core/report.js +2 -2
- package/src/core/repository.js +46 -12
- package/src/core/resumability.js +6 -4
- package/src/core/route-artifact.js +9 -5
- package/src/core/router.js +11 -7
- package/src/core/schema-validation.js +1 -0
- package/src/core/task-command.js +41 -0
- package/src/core/task-context.js +126 -0
- package/src/core/task-descriptor.js +81 -0
- package/src/core/task-discovery.js +116 -0
- package/src/core/task-identity.js +76 -0
- package/src/core/task-lock.js +209 -0
- package/src/core/task-migration-validation.js +140 -0
- package/src/core/task-migration.js +361 -0
- package/src/core/task-paths.js +96 -0
- package/src/core/task-scope.js +179 -0
- package/src/core/templates.js +3 -9
- package/src/core/work-state.js +24 -13
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { resolveTaskContext } from "../core/task-context.js";
|
|
2
|
+
import { readTaskDescriptor, writeTaskDescriptor } from "../core/task-descriptor.js";
|
|
3
|
+
import { normalizeWriteClaims, assertNoScopeConflicts, assertScopeClean, assertScopeNotFrozen } from "../core/task-scope.js";
|
|
4
|
+
import { discoverTasks } from "../core/task-discovery.js";
|
|
5
|
+
import { readWorkState } from "../core/work-state.js";
|
|
6
|
+
import { withProjectClaimsLock, withTaskLock } from "../core/task-lock.js";
|
|
7
|
+
import { E_TASK_NOT_FOUND } from "../core/error-codes.js";
|
|
8
|
+
|
|
9
|
+
function taskError(code, message, artifacts = []) {
|
|
10
|
+
const error = new Error(message);
|
|
11
|
+
error.code = code;
|
|
12
|
+
error.artifacts = artifacts;
|
|
13
|
+
return error;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function runTaskScope({ target, packageRoot, taskId, claims } = {}) {
|
|
17
|
+
const context = await resolveTaskContext(target, { taskId, packageRoot, explicitRequired: true });
|
|
18
|
+
const effectiveTaskId = context.taskId;
|
|
19
|
+
|
|
20
|
+
let descriptorArtifact;
|
|
21
|
+
try {
|
|
22
|
+
descriptorArtifact = await readTaskDescriptor(target, effectiveTaskId, packageRoot);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
if (error.code === "ARTIFACT_MISSING") {
|
|
25
|
+
throw taskError(E_TASK_NOT_FOUND, `Task descriptor not found for task: ${effectiveTaskId}`);
|
|
26
|
+
}
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const descriptor = descriptorArtifact.value;
|
|
31
|
+
|
|
32
|
+
if (claims !== undefined && claims !== null) {
|
|
33
|
+
return withProjectClaimsLock(target, async () => {
|
|
34
|
+
return withTaskLock(target, effectiveTaskId, "task-scope", async () => {
|
|
35
|
+
const state = await readWorkState(target, { packageRoot, taskId: effectiveTaskId });
|
|
36
|
+
if (state?.phase) {
|
|
37
|
+
assertScopeNotFrozen(state.phase);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const normalizedClaims = normalizeWriteClaims(claims);
|
|
41
|
+
const allTasks = await discoverTasks(target, packageRoot);
|
|
42
|
+
assertNoScopeConflicts(normalizedClaims, allTasks, effectiveTaskId);
|
|
43
|
+
if (normalizedClaims.length > 0) {
|
|
44
|
+
await assertScopeClean(target, normalizedClaims);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const updatedDescriptor = {
|
|
48
|
+
...descriptor,
|
|
49
|
+
writeClaims: normalizedClaims,
|
|
50
|
+
updatedAt: new Date().toISOString(),
|
|
51
|
+
};
|
|
52
|
+
await writeTaskDescriptor(target, updatedDescriptor, packageRoot);
|
|
53
|
+
return {
|
|
54
|
+
taskId: effectiveTaskId,
|
|
55
|
+
taskKey: context.taskKey,
|
|
56
|
+
writeClaims: updatedDescriptor.writeClaims,
|
|
57
|
+
updated: true,
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
taskId: effectiveTaskId,
|
|
65
|
+
taskKey: context.taskKey,
|
|
66
|
+
writeClaims: descriptor.writeClaims ?? [],
|
|
67
|
+
updated: false,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function formatTaskScopeResult(result) {
|
|
72
|
+
const claims = result.writeClaims.length === 0 ? "none" : result.writeClaims.join(", ");
|
|
73
|
+
const action = result.updated ? "updated scope" : "current scope";
|
|
74
|
+
return `${action} for ${result.taskId}: ${claims}\n`;
|
|
75
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { resolveTaskContext } from "../core/task-context.js";
|
|
2
|
+
import { readTaskDescriptor } from "../core/task-descriptor.js";
|
|
3
|
+
import { readLockInfo } from "../core/task-lock.js";
|
|
4
|
+
import { taskDirectory, taskArtifactPath } from "../core/task-paths.js";
|
|
5
|
+
import { readWorkState } from "../core/work-state.js";
|
|
6
|
+
import { readContract } from "../core/contract.js";
|
|
7
|
+
import { fileExists, ensureWithin } from "../core/filesystem.js";
|
|
8
|
+
import { E_TASK_NOT_FOUND } from "../core/error-codes.js";
|
|
9
|
+
|
|
10
|
+
function taskError(code, message, artifacts = []) {
|
|
11
|
+
const error = new Error(message);
|
|
12
|
+
error.code = code;
|
|
13
|
+
error.artifacts = artifacts;
|
|
14
|
+
return error;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function runTaskShow({ target, packageRoot, taskId } = {}) {
|
|
18
|
+
const context = await resolveTaskContext(target, { taskId, packageRoot, explicitRequired: true });
|
|
19
|
+
const effectiveTaskId = context.taskId;
|
|
20
|
+
|
|
21
|
+
let descriptor = null;
|
|
22
|
+
try {
|
|
23
|
+
const descArtifact = await readTaskDescriptor(target, effectiveTaskId, packageRoot);
|
|
24
|
+
descriptor = descArtifact.value;
|
|
25
|
+
} catch (error) {
|
|
26
|
+
if (error.code === "ARTIFACT_MISSING") {
|
|
27
|
+
throw taskError(E_TASK_NOT_FOUND, `Task descriptor not found for task: ${effectiveTaskId}`);
|
|
28
|
+
}
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const state = await readWorkState(target, { packageRoot, taskId: effectiveTaskId });
|
|
33
|
+
let contract = null;
|
|
34
|
+
try {
|
|
35
|
+
const contractArtifact = await readContract(target, packageRoot, { taskId: effectiveTaskId });
|
|
36
|
+
contract = contractArtifact.value;
|
|
37
|
+
} catch {
|
|
38
|
+
// contract may not be present
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const lockInfo = await readLockInfo(target, effectiveTaskId);
|
|
42
|
+
const artifacts = {};
|
|
43
|
+
for (const name of ["contract", "route", "state", "preflight", "receipt", "continuity", "events"]) {
|
|
44
|
+
const rel = taskArtifactPath(effectiveTaskId, name);
|
|
45
|
+
artifacts[name] = {
|
|
46
|
+
path: rel,
|
|
47
|
+
exists: await fileExists(ensureWithin(target, rel)),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
taskId: effectiveTaskId,
|
|
53
|
+
taskKey: context.taskKey,
|
|
54
|
+
directory: taskDirectory(effectiveTaskId),
|
|
55
|
+
phase: state?.phase ?? "UNINITIALIZED",
|
|
56
|
+
writeClaims: descriptor.writeClaims ?? [],
|
|
57
|
+
lock: lockInfo,
|
|
58
|
+
contract: contract ? { title: contract.title ?? null, taskType: contract.taskType ?? null } : null,
|
|
59
|
+
artifacts,
|
|
60
|
+
createdAt: descriptor.createdAt,
|
|
61
|
+
updatedAt: descriptor.updatedAt,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function formatTaskShowResult(result) {
|
|
66
|
+
const claims = result.writeClaims.length === 0 ? "none" : result.writeClaims.join(", ");
|
|
67
|
+
const lockStatus = result.lock
|
|
68
|
+
? `locked by PID ${result.lock.pid ?? "unknown"} since ${result.lock.acquiredAt ?? "unknown"}`
|
|
69
|
+
: "unlocked";
|
|
70
|
+
const lines = [
|
|
71
|
+
`Task: ${result.taskId}`,
|
|
72
|
+
`Key: ${result.taskKey}`,
|
|
73
|
+
`Directory: ${result.directory}`,
|
|
74
|
+
`Phase: ${result.phase}`,
|
|
75
|
+
`Write Claims: ${claims}`,
|
|
76
|
+
`Lock: ${lockStatus}`,
|
|
77
|
+
`Created: ${result.createdAt}`,
|
|
78
|
+
`Updated: ${result.updatedAt}`,
|
|
79
|
+
];
|
|
80
|
+
return `${lines.join("\n")}\n`;
|
|
81
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { resolveTaskContext } from "../core/task-context.js";
|
|
2
|
+
import { forceUnlockTask, readLockInfo } from "../core/task-lock.js";
|
|
3
|
+
|
|
4
|
+
export async function runTaskUnlock({ target, packageRoot, taskId, force = false } = {}) {
|
|
5
|
+
const context = await resolveTaskContext(target, { taskId, packageRoot, explicitRequired: true });
|
|
6
|
+
const effectiveTaskId = context.taskId;
|
|
7
|
+
|
|
8
|
+
const lockInfo = await readLockInfo(target, effectiveTaskId);
|
|
9
|
+
if (!lockInfo) {
|
|
10
|
+
return {
|
|
11
|
+
taskId: effectiveTaskId,
|
|
12
|
+
taskKey: context.taskKey,
|
|
13
|
+
unlocked: false,
|
|
14
|
+
message: "Task is not locked",
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!force) {
|
|
19
|
+
const error = new Error(`Task ${effectiveTaskId} is locked by operation "${lockInfo.operation ?? "unknown"}" (PID ${lockInfo.pid ?? "unknown"}). Use --force to unlock.`);
|
|
20
|
+
error.code = "E_TASK_LOCKED";
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
await forceUnlockTask(target, effectiveTaskId);
|
|
25
|
+
return {
|
|
26
|
+
taskId: effectiveTaskId,
|
|
27
|
+
taskKey: context.taskKey,
|
|
28
|
+
unlocked: true,
|
|
29
|
+
message: `Released lock for task ${effectiveTaskId}`,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function formatTaskUnlockResult(result) {
|
|
34
|
+
return `${result.message}\n`;
|
|
35
|
+
}
|
|
@@ -43,6 +43,9 @@ async function readArtifact(target, relativePath, label) {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
import { taskArtifactPath } from "../core/task-paths.js";
|
|
47
|
+
import { withResolvedTask } from "../core/task-command.js";
|
|
48
|
+
|
|
46
49
|
export async function runValidateProtocol({
|
|
47
50
|
target,
|
|
48
51
|
packageRoot,
|
|
@@ -53,12 +56,24 @@ export async function runValidateProtocol({
|
|
|
53
56
|
continuityFile = null,
|
|
54
57
|
taskBriefFiles = [],
|
|
55
58
|
delegatedResultFiles = [],
|
|
59
|
+
taskId = null,
|
|
60
|
+
task = null,
|
|
56
61
|
}) {
|
|
62
|
+
return withResolvedTask(target, { taskId: taskId ?? task, packageRoot }, async (ctx) => {
|
|
63
|
+
const effectiveTaskId = ctx?.taskId ?? null;
|
|
64
|
+
const effectiveRouteFile = routeFile ?? (effectiveTaskId ? taskArtifactPath(effectiveTaskId, "route") : ARTIFACT_PATHS.route);
|
|
65
|
+
const effectiveStateFile = stateFile ?? (effectiveTaskId ? taskArtifactPath(effectiveTaskId, "state") : ARTIFACT_PATHS.state);
|
|
66
|
+
const effectiveReceiptFile = receiptFile ?? (effectiveTaskId ? taskArtifactPath(effectiveTaskId, "receipt") : ARTIFACT_PATHS.receipt);
|
|
67
|
+
const effectiveContractFile = contractFile ?? (effectiveTaskId ? taskArtifactPath(effectiveTaskId, "contract") : null);
|
|
68
|
+
const effectiveContinuityFile = continuityFile ?? (effectiveTaskId ? taskArtifactPath(effectiveTaskId, "continuity") : null);
|
|
69
|
+
const effectiveEventsFile = effectiveTaskId ? taskArtifactPath(effectiveTaskId, "events") : ARTIFACT_PATHS.events;
|
|
70
|
+
const effectivePreflightFile = effectiveTaskId ? taskArtifactPath(effectiveTaskId, "preflight") : ARTIFACT_PATHS.preflight;
|
|
71
|
+
|
|
57
72
|
const descriptors = [
|
|
58
|
-
["route",
|
|
59
|
-
["state",
|
|
60
|
-
["receipt",
|
|
61
|
-
["continuity",
|
|
73
|
+
["route", effectiveRouteFile],
|
|
74
|
+
["state", effectiveStateFile],
|
|
75
|
+
["receipt", effectiveReceiptFile],
|
|
76
|
+
["continuity", effectiveContinuityFile],
|
|
62
77
|
...taskBriefFiles.map((file) => [`task brief:${file}`, file]),
|
|
63
78
|
...delegatedResultFiles.map((file) => [`delegated result:${file}`, file]),
|
|
64
79
|
];
|
|
@@ -117,19 +132,20 @@ export async function runValidateProtocol({
|
|
|
117
132
|
for (const item of loaded.filter((candidate) => candidate.label.startsWith("delegated result:"))) {
|
|
118
133
|
await validateLoaded(item, "delegated-result", async (value) => validateDelegatedResult(value, packageRoot));
|
|
119
134
|
}
|
|
120
|
-
const stateValidationError = schemaErrors.some((error) => error.artifacts.includes(
|
|
135
|
+
const stateValidationError = schemaErrors.some((error) => error.artifacts.includes(effectiveStateFile));
|
|
121
136
|
const stateClassification = state && readErrors.length === 0 && !stateValidationError
|
|
122
|
-
? await classifyLoadedWorkState({ target, state, contractFile })
|
|
137
|
+
? await classifyLoadedWorkState({ target, state, contractFile: effectiveContractFile, taskId: effectiveTaskId })
|
|
123
138
|
: null;
|
|
124
139
|
let readyConsistencyErrors = [];
|
|
125
140
|
try {
|
|
126
|
-
const persistedPreflight = await readJsonArtifact(target,
|
|
141
|
+
const persistedPreflight = await readJsonArtifact(target, effectivePreflightFile, "preflight", packageRoot);
|
|
127
142
|
if (persistedPreflight.value.status === "READY") {
|
|
128
143
|
readyConsistencyErrors = await validateReadyProtocolConsistency({
|
|
129
144
|
target,
|
|
130
145
|
packageRoot,
|
|
131
146
|
persisted: persistedPreflight.value,
|
|
132
|
-
current: await evaluatePreflight({ target, packageRoot }),
|
|
147
|
+
current: await evaluatePreflight({ target, packageRoot, taskId: effectiveTaskId }),
|
|
148
|
+
taskId: effectiveTaskId,
|
|
133
149
|
});
|
|
134
150
|
}
|
|
135
151
|
} catch {
|
|
@@ -138,13 +154,13 @@ export async function runValidateProtocol({
|
|
|
138
154
|
let ledgerEvents = [];
|
|
139
155
|
let ledgerErrors = [];
|
|
140
156
|
if (state && !stateValidationError) {
|
|
141
|
-
const ledger = await validateEventLedger(target, packageRoot);
|
|
157
|
+
const ledger = await validateEventLedger(target, packageRoot, { taskId: effectiveTaskId, eventsPath: effectiveEventsFile });
|
|
142
158
|
ledgerEvents = ledger.events ?? [];
|
|
143
159
|
ledgerErrors = [
|
|
144
|
-
...ledger.errors.map((error) => ({ ...error, artifacts: [
|
|
160
|
+
...ledger.errors.map((error) => ({ ...error, artifacts: [effectiveEventsFile] })),
|
|
145
161
|
...validateStateLedgerCoherence(state, ledger.events).map((error) => ({
|
|
146
162
|
...error,
|
|
147
|
-
artifacts: [
|
|
163
|
+
artifacts: [effectiveStateFile, effectiveEventsFile],
|
|
148
164
|
})),
|
|
149
165
|
];
|
|
150
166
|
}
|
|
@@ -166,15 +182,16 @@ export async function runValidateProtocol({
|
|
|
166
182
|
delegatedResults,
|
|
167
183
|
events: ledgerEvents,
|
|
168
184
|
});
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
185
|
+
if (readErrors.length > 0 || schemaErrors.length > 0 || readyConsistencyErrors.length > 0 || ledgerErrors.length > 0) {
|
|
186
|
+
return {
|
|
187
|
+
...result,
|
|
188
|
+
status: "INVALID",
|
|
189
|
+
errors: [...result.errors, ...readErrors, ...schemaErrors, ...readyConsistencyErrors, ...ledgerErrors]
|
|
190
|
+
.sort((left, right) => left.code.localeCompare(right.code) || left.message.localeCompare(right.message)),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
return result;
|
|
194
|
+
});
|
|
178
195
|
}
|
|
179
196
|
|
|
180
197
|
export function formatValidateProtocolResult(result) {
|
|
@@ -1,27 +1,33 @@
|
|
|
1
1
|
import { readWorkState, WORK_STATE_PATH } from "../core/work-state.js";
|
|
2
|
+
import { withResolvedTask } from "../core/task-command.js";
|
|
3
|
+
import { taskArtifactPath } from "../core/task-paths.js";
|
|
2
4
|
|
|
3
|
-
export async function runValidateState({ target, packageRoot }) {
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
|
|
5
|
+
export async function runValidateState({ target, packageRoot, taskId, task } = {}) {
|
|
6
|
+
return withResolvedTask(target, { taskId: taskId ?? task, packageRoot }, async (ctx) => {
|
|
7
|
+
const effectiveTaskId = ctx?.taskId ?? null;
|
|
8
|
+
const path = effectiveTaskId ? taskArtifactPath(effectiveTaskId, "state") : WORK_STATE_PATH;
|
|
9
|
+
try {
|
|
10
|
+
const state = await readWorkState(target, { packageRoot, taskId: effectiveTaskId });
|
|
11
|
+
if (!state) {
|
|
12
|
+
return {
|
|
13
|
+
ok: true,
|
|
14
|
+
path,
|
|
15
|
+
state: null,
|
|
16
|
+
errors: [],
|
|
17
|
+
warnings: ["No work-state checkpoint is present."],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
return { ok: true, path, state, errors: [], warnings: [] };
|
|
21
|
+
} catch (error) {
|
|
7
22
|
return {
|
|
8
|
-
ok:
|
|
9
|
-
path
|
|
23
|
+
ok: false,
|
|
24
|
+
path,
|
|
10
25
|
state: null,
|
|
11
|
-
errors: [],
|
|
12
|
-
warnings: [
|
|
26
|
+
errors: [error.message],
|
|
27
|
+
warnings: [],
|
|
13
28
|
};
|
|
14
29
|
}
|
|
15
|
-
|
|
16
|
-
} catch (error) {
|
|
17
|
-
return {
|
|
18
|
-
ok: false,
|
|
19
|
-
path: WORK_STATE_PATH,
|
|
20
|
-
state: null,
|
|
21
|
-
errors: [error.message],
|
|
22
|
-
warnings: [],
|
|
23
|
-
};
|
|
24
|
-
}
|
|
30
|
+
});
|
|
25
31
|
}
|
|
26
32
|
|
|
27
33
|
export function formatValidateStateResult(result) {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"premium": {
|
|
3
|
+
"path": "ENG/premium-sites-studio-eng.md",
|
|
4
|
+
"install": true
|
|
5
|
+
},
|
|
6
|
+
"clean": {
|
|
7
|
+
"path": "ENG/clean-code-eng.md",
|
|
8
|
+
"install": true
|
|
9
|
+
},
|
|
10
|
+
"test": {
|
|
11
|
+
"path": "ENG/test-code-eng.md",
|
|
12
|
+
"install": true
|
|
13
|
+
},
|
|
14
|
+
"security": {
|
|
15
|
+
"path": "ENG/sec-code-eng.md",
|
|
16
|
+
"install": true
|
|
17
|
+
},
|
|
18
|
+
"design": {
|
|
19
|
+
"path": "ENG/design-code-eng.md",
|
|
20
|
+
"install": true
|
|
21
|
+
},
|
|
22
|
+
"taste": {
|
|
23
|
+
"path": "ENG/taste-frontend-eng.md",
|
|
24
|
+
"install": true
|
|
25
|
+
},
|
|
26
|
+
"performance": {
|
|
27
|
+
"path": "ENG/perf-code-eng.md",
|
|
28
|
+
"install": true
|
|
29
|
+
},
|
|
30
|
+
"accessibility": {
|
|
31
|
+
"path": "ENG/accessibility-eng.md",
|
|
32
|
+
"install": true
|
|
33
|
+
},
|
|
34
|
+
"games": {
|
|
35
|
+
"path": "ENG/games-code-design-web-eng.md",
|
|
36
|
+
"install": true
|
|
37
|
+
},
|
|
38
|
+
"documentation": {
|
|
39
|
+
"path": "ENG/documentation-quality-eng.md",
|
|
40
|
+
"install": true
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/core/activation.js
CHANGED
|
@@ -2,15 +2,19 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
|
|
3
3
|
import { ARTIFACT_PATHS, writeJsonArtifact } from "./artifacts.js";
|
|
4
4
|
import { PROTOCOL_VERSION } from "./protocol.js";
|
|
5
|
+
import { sessionArtifactPath } from "./task-paths.js";
|
|
5
6
|
|
|
6
7
|
export async function activateSession(target, packageRoot, options = {}) {
|
|
8
|
+
const sessionId = options.sessionId ?? randomUUID();
|
|
7
9
|
const value = {
|
|
8
10
|
schemaVersion: 1,
|
|
9
11
|
protocolVersion: PROTOCOL_VERSION,
|
|
10
|
-
sessionId
|
|
11
|
-
activationMarker: `forgeloop-${randomUUID()}`,
|
|
12
|
+
sessionId,
|
|
13
|
+
activationMarker: options.activationMarker ?? `forgeloop-${randomUUID()}`,
|
|
12
14
|
createdAt: options.createdAt ?? new Date().toISOString(),
|
|
13
15
|
};
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
+
const relativePath = sessionArtifactPath(sessionId);
|
|
17
|
+
const written = await writeJsonArtifact(target, relativePath, value, "activation", packageRoot, options);
|
|
18
|
+
await writeJsonArtifact(target, ARTIFACT_PATHS.session, value, "activation", packageRoot, options).catch(() => {});
|
|
19
|
+
return { ...written.value, path: relativePath };
|
|
16
20
|
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { TASK_STATE_ROOT, TASK_ARTIFACT_FILES, PROJECT_ARTIFACT_PATHS, SESSIONS_ROOT } from "./task-paths.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Canonical registry of ForgeLoop artifacts, defining filesystem paths,
|
|
5
|
+
* schema bindings, ownership, mutability, trust classifications, and scopes.
|
|
6
|
+
*
|
|
7
|
+
* Schema field shapes remain strictly owned by JSON schemas in `schemas/`.
|
|
8
|
+
*/
|
|
9
|
+
export const ARTIFACT_REGISTRY = Object.freeze({
|
|
10
|
+
descriptor: Object.freeze({
|
|
11
|
+
key: "descriptor",
|
|
12
|
+
scope: "TASK",
|
|
13
|
+
path: `${TASK_STATE_ROOT}/<task-key>/${TASK_ARTIFACT_FILES.descriptor}`,
|
|
14
|
+
schema: "task-descriptor",
|
|
15
|
+
owner: "PROTOCOL_MANAGED",
|
|
16
|
+
mutability: "MUTABLE_BEFORE_EXECUTION",
|
|
17
|
+
trustRole: "TASK_DESCRIPTOR",
|
|
18
|
+
isPublic: true,
|
|
19
|
+
isPersisted: true,
|
|
20
|
+
description: "Canonical task descriptor declaring task ID, hash key, timestamps, and write claims.",
|
|
21
|
+
}),
|
|
22
|
+
contract: Object.freeze({
|
|
23
|
+
key: "contract",
|
|
24
|
+
scope: "TASK",
|
|
25
|
+
path: `${TASK_STATE_ROOT}/<task-key>/${TASK_ARTIFACT_FILES.contract}`,
|
|
26
|
+
schema: "current-contract",
|
|
27
|
+
owner: "AGENT_AUTHORED",
|
|
28
|
+
mutability: "MUTABLE_BEFORE_EXECUTION",
|
|
29
|
+
trustRole: "OPERATIONAL_SPECIFICATION",
|
|
30
|
+
isPublic: true,
|
|
31
|
+
isPersisted: true,
|
|
32
|
+
description: "Current task contract defining objectives, deliverables, constraints, and success criteria.",
|
|
33
|
+
}),
|
|
34
|
+
route: Object.freeze({
|
|
35
|
+
key: "route",
|
|
36
|
+
scope: "TASK",
|
|
37
|
+
path: `${TASK_STATE_ROOT}/<task-key>/${TASK_ARTIFACT_FILES.route}`,
|
|
38
|
+
schema: "routing-result",
|
|
39
|
+
owner: "PROTOCOL_GENERATED",
|
|
40
|
+
mutability: "MUTABLE_ON_REROUTE",
|
|
41
|
+
trustRole: "GUIDE_ROUTING_SPECIFICATION",
|
|
42
|
+
isPublic: true,
|
|
43
|
+
isPersisted: true,
|
|
44
|
+
description: "Deterministic engineering guide routing result bound to current contract.",
|
|
45
|
+
}),
|
|
46
|
+
preflight: Object.freeze({
|
|
47
|
+
key: "preflight",
|
|
48
|
+
scope: "TASK",
|
|
49
|
+
path: `${TASK_STATE_ROOT}/<task-key>/${TASK_ARTIFACT_FILES.preflight}`,
|
|
50
|
+
schema: "preflight",
|
|
51
|
+
owner: "PROTOCOL_GENERATED",
|
|
52
|
+
mutability: "OVERWRITTEN_ON_PREFLIGHT",
|
|
53
|
+
trustRole: "READINESS_ATTESTATION",
|
|
54
|
+
isPublic: true,
|
|
55
|
+
isPersisted: true,
|
|
56
|
+
description: "Preflight readiness report verifying profile, contract, routing, and gates.",
|
|
57
|
+
}),
|
|
58
|
+
sources: Object.freeze({
|
|
59
|
+
key: "sources",
|
|
60
|
+
scope: "PROJECT",
|
|
61
|
+
path: PROJECT_ARTIFACT_PATHS.sources,
|
|
62
|
+
schema: "source-registry",
|
|
63
|
+
owner: "OPERATOR_OR_AGENT",
|
|
64
|
+
mutability: "MUTABLE_ON_DISCOVERY",
|
|
65
|
+
trustRole: "SOURCE_ATTESTATION",
|
|
66
|
+
isPublic: true,
|
|
67
|
+
isPersisted: true,
|
|
68
|
+
description: "Registry of discovered project facts, platforms, runtimes, and dependencies.",
|
|
69
|
+
}),
|
|
70
|
+
events: Object.freeze({
|
|
71
|
+
key: "events",
|
|
72
|
+
scope: "TASK",
|
|
73
|
+
path: `${TASK_STATE_ROOT}/<task-key>/${TASK_ARTIFACT_FILES.events}`,
|
|
74
|
+
schema: "event",
|
|
75
|
+
owner: "PROTOCOL_APPENDED",
|
|
76
|
+
mutability: "APPEND_ONLY",
|
|
77
|
+
trustRole: "AUDIT_LEDGER",
|
|
78
|
+
isPublic: true,
|
|
79
|
+
isPersisted: true,
|
|
80
|
+
description: "Append-only cryptographic hash-chained event ledger.",
|
|
81
|
+
}),
|
|
82
|
+
session: Object.freeze({
|
|
83
|
+
key: "session",
|
|
84
|
+
scope: "SESSION",
|
|
85
|
+
path: `${SESSIONS_ROOT}/<session-id>.json`,
|
|
86
|
+
schema: "activation",
|
|
87
|
+
owner: "PROTOCOL_GENERATED",
|
|
88
|
+
mutability: "OVERWRITTEN_ON_ACTIVATE",
|
|
89
|
+
trustRole: "SESSION_MARKER",
|
|
90
|
+
isPublic: true,
|
|
91
|
+
isPersisted: true,
|
|
92
|
+
description: "Active harness session marker recording session ID and activation marker.",
|
|
93
|
+
}),
|
|
94
|
+
config: Object.freeze({
|
|
95
|
+
key: "config",
|
|
96
|
+
scope: "PROJECT",
|
|
97
|
+
path: PROJECT_ARTIFACT_PATHS.config,
|
|
98
|
+
schema: "config",
|
|
99
|
+
owner: "OPERATOR_OR_AGENT",
|
|
100
|
+
mutability: "MUTABLE_CONFIGURATION",
|
|
101
|
+
trustRole: "PROJECT_CONFIGURATION",
|
|
102
|
+
isPublic: true,
|
|
103
|
+
isPersisted: true,
|
|
104
|
+
description: "Local ForgeLoop configuration settings and overrides.",
|
|
105
|
+
}),
|
|
106
|
+
gates: Object.freeze({
|
|
107
|
+
key: "gates",
|
|
108
|
+
scope: "TASK",
|
|
109
|
+
path: `${TASK_STATE_ROOT}/<task-key>/gates/<gate>.json`,
|
|
110
|
+
schema: "gate",
|
|
111
|
+
owner: "AGENT_AUTHORED_OR_REVIEWER",
|
|
112
|
+
mutability: "OVERWRITTEN_ON_GATE_SATISFACTION",
|
|
113
|
+
trustRole: "GATE_APPROVAL_ATTESTATION",
|
|
114
|
+
isPublic: true,
|
|
115
|
+
isPersisted: true,
|
|
116
|
+
description: "Preflight gate satisfaction artifacts recording gate decisions, artifacts, and evidence.",
|
|
117
|
+
}),
|
|
118
|
+
state: Object.freeze({
|
|
119
|
+
key: "state",
|
|
120
|
+
scope: "TASK",
|
|
121
|
+
path: `${TASK_STATE_ROOT}/<task-key>/${TASK_ARTIFACT_FILES.state}`,
|
|
122
|
+
schema: "work-state",
|
|
123
|
+
owner: "PROTOCOL_MANAGED",
|
|
124
|
+
mutability: "ATOMIC_STATE_TRANSITIONS",
|
|
125
|
+
trustRole: "CANONICAL_LIFECYCLE_STATE",
|
|
126
|
+
isPublic: true,
|
|
127
|
+
isPersisted: true,
|
|
128
|
+
description: "Canonical lifecycle work state recording active phase, checks, blockers, and fingerprints.",
|
|
129
|
+
}),
|
|
130
|
+
continuity: Object.freeze({
|
|
131
|
+
key: "continuity",
|
|
132
|
+
scope: "TASK",
|
|
133
|
+
path: `${TASK_STATE_ROOT}/<task-key>/${TASK_ARTIFACT_FILES.continuity}`,
|
|
134
|
+
schema: "continuity",
|
|
135
|
+
owner: "AGENT_OR_HARNESS",
|
|
136
|
+
mutability: "MUTABLE_HANDOFF_NOTES",
|
|
137
|
+
trustRole: "NON_EVIDENCE_HANDOFF",
|
|
138
|
+
isPublic: true,
|
|
139
|
+
isPersisted: true,
|
|
140
|
+
description: "Cross-harness non-evidence handoff notes containing focus, remaining work, and inspect first targets.",
|
|
141
|
+
}),
|
|
142
|
+
receipt: Object.freeze({
|
|
143
|
+
key: "receipt",
|
|
144
|
+
scope: "TASK",
|
|
145
|
+
path: `${TASK_STATE_ROOT}/<task-key>/${TASK_ARTIFACT_FILES.receipt}`,
|
|
146
|
+
schema: "execution-receipt",
|
|
147
|
+
owner: "PROTOCOL_COMPILED",
|
|
148
|
+
mutability: "ATOMIC_COMPILATION",
|
|
149
|
+
trustRole: "EVIDENCE_COMPILATION",
|
|
150
|
+
isPublic: true,
|
|
151
|
+
isPersisted: true,
|
|
152
|
+
description: "Cryptographically bound verification receipt summarizing evidence coverage and publication readiness.",
|
|
153
|
+
}),
|
|
154
|
+
executions: Object.freeze({
|
|
155
|
+
key: "executions",
|
|
156
|
+
scope: "TASK",
|
|
157
|
+
path: `${TASK_STATE_ROOT}/<task-key>/executions/exec-<id>.json`,
|
|
158
|
+
schema: "execution",
|
|
159
|
+
owner: "PROTOCOL_EXECUTED",
|
|
160
|
+
mutability: "IMMUTABLE_ONCE_WRITTEN",
|
|
161
|
+
trustRole: "EXECUTION_PROVENANCE",
|
|
162
|
+
isPublic: true,
|
|
163
|
+
isPersisted: true,
|
|
164
|
+
description: "Attested command execution provenance recording argv, cwd, resolution, status, and exit code.",
|
|
165
|
+
}),
|
|
166
|
+
});
|