@cassiomc1/forgeloop 1.3.0 → 1.6.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/.github/copilot-instructions.md +1 -0
- package/AGENTS.md +1 -0
- package/CLAUDE.md +1 -0
- package/DOCS_INDEX.md +20 -8
- package/EXECUTION_STATE.md +60 -0
- package/LOOP_ENGINEERING.md +135 -5
- package/LOOP_SYSTEM_DESIGN.md +54 -1
- package/PROTOCOL_INTEGRATION.md +87 -0
- package/QUALITY_SCORECARD.md +2 -0
- package/README.md +69 -9
- package/TERMINOLOGY.md +15 -0
- package/THIRD_PARTY_NOTICES.md +30 -0
- package/THREAT_MODEL.md +59 -1
- package/docs/ARTIFACT_REFERENCE.md +183 -0
- package/docs/CLI_REFERENCE.md +391 -6
- package/docs/CROSS_HARNESS_CONTINUITY.md +23 -0
- package/docs/DIAGNOSTIC_MODEL.md +181 -0
- package/docs/DOCUMENTATION_GUIDE.md +36 -13
- package/docs/EXECUTION_TRACE.md +76 -0
- package/docs/GETTING_STARTED.md +1 -0
- package/docs/MCP.md +159 -0
- package/docs/RECIPES.md +149 -0
- package/docs/RELEASE_CHECKLIST_1_4.md +38 -0
- package/docs/RELEASE_CHECKLIST_1_5_MCP.md +78 -0
- package/docs/TROUBLESHOOTING.md +217 -3
- package/docs/UNIVERSAL_INTEGRATION.md +48 -0
- package/docs/assets/diagrams/forgeloop-engineering-flow.html +13797 -0
- package/docs/assets/diagrams/forgeloop-engineering-flow.receipt.json +37 -0
- package/docs/assets/diagrams/forgeloop-engineering-flow.svg +5002 -0
- package/docs/diagrams/README.md +55 -0
- package/docs/diagrams/forgeloop-engineering-flow.workflow.json +122 -0
- package/docs/diagrams/manifest.json +42 -0
- package/docs/diagrams/reviews/forgeloop-engineering-flow.review.json +20 -0
- package/package.json +21 -8
- package/schemas/action.schema.json +100 -0
- package/schemas/approval.schema.json +51 -0
- package/schemas/capability-policy.schema.json +41 -0
- package/schemas/diagnostic-case.schema.json +85 -0
- package/schemas/execution-receipt.schema.json +16 -0
- package/schemas/hypothesis-disposition.schema.json +16 -0
- package/schemas/intervention.schema.json +27 -0
- package/schemas/policy-lock.schema.json +1 -0
- package/schemas/policy-snapshot.schema.json +2 -0
- package/schemas/task-recovery.schema.json +61 -0
- package/schemas/trajectory-evaluation.schema.json +64 -0
- package/schemas/trajectory-scenario.schema.json +42 -0
- package/src/cli.js +267 -347
- package/src/commands/action-authorize.js +41 -0
- package/src/commands/action-propose.js +10 -0
- package/src/commands/action-reconcile.js +10 -0
- package/src/commands/action-record.js +47 -0
- package/src/commands/action-show.js +10 -0
- package/src/commands/action-verify.js +10 -0
- package/src/commands/advance.js +7 -2
- package/src/commands/approval-request.js +64 -0
- package/src/commands/approval-resolve.js +10 -0
- package/src/commands/audit.js +5 -0
- package/src/commands/baseline.js +3 -3
- package/src/commands/eval.js +6 -0
- package/src/commands/history.js +18 -0
- package/src/commands/init.js +2 -2
- package/src/commands/inspect.js +55 -0
- package/src/commands/metrics.js +7 -0
- package/src/commands/next.js +8 -2
- package/src/commands/policy-discover.js +2 -2
- package/src/commands/progress.js +6 -2
- package/src/commands/record-diagnosis.js +37 -1
- package/src/commands/record-hypothesis-disposition.js +45 -0
- package/src/commands/record-intervention.js +35 -0
- package/src/commands/reflect.js +38 -0
- package/src/commands/report.js +9 -1
- package/src/commands/run-action.js +18 -0
- package/src/commands/status.js +17 -0
- package/src/commands/task-create.js +39 -1
- package/src/commands/task-list.js +14 -1
- package/src/commands/task-lock-status.js +2 -2
- package/src/commands/task-recover.js +202 -0
- package/src/commands/task-repair-legacy-recovery.js +417 -0
- package/src/commands/task-resume.js +172 -0
- package/src/commands/task-scope.js +23 -4
- package/src/commands/task-show.js +18 -4
- package/src/commands/trace.js +34 -0
- package/src/commands/validate-protocol.js +40 -15
- package/src/core/action-authorization.js +106 -0
- package/src/core/action-constants.js +86 -0
- package/src/core/action-execution.js +105 -0
- package/src/core/action-ledger-projection.js +302 -0
- package/src/core/action-model.js +581 -0
- package/src/core/action-readiness.js +141 -0
- package/src/core/action-reconciliation-policy.js +49 -0
- package/src/core/action-reconciliation.js +66 -0
- package/src/core/action-verification.js +111 -0
- package/src/core/actions.js +462 -0
- package/src/core/approvals.js +405 -0
- package/src/core/artifact-registry.js +60 -0
- package/src/core/audit.js +45 -4
- package/src/core/bundles.js +30 -0
- package/src/core/capability-policy.js +226 -0
- package/src/core/cli-command-definitions.js +260 -5
- package/src/core/command-executors.js +543 -0
- package/src/core/command-input.js +107 -0
- package/src/core/command-runtime.js +117 -0
- package/src/core/completion-artifacts.js +39 -15
- package/src/core/completion-ownership.js +88 -0
- package/src/core/completion-recovery-rebind.js +194 -0
- package/src/core/completion.js +70 -0
- package/src/core/continuity-reconciliation.js +24 -5
- package/src/core/diagnostic-model.js +396 -0
- package/src/core/diagnostic-projection.js +51 -0
- package/src/core/diagnostic-record.js +360 -0
- package/src/core/error-codes.js +461 -1
- package/src/core/events.js +171 -2
- package/src/core/execution-prerequisites.js +4 -1
- package/src/core/execution.js +26 -188
- package/src/core/failure-signature.js +70 -0
- package/src/core/failure-surface.js +57 -0
- package/src/core/filesystem.js +55 -6
- package/src/core/history.js +110 -0
- package/src/core/hypothesis-projection.js +85 -0
- package/src/core/information-gain-projection.js +283 -0
- package/src/core/information-gain.js +138 -0
- package/src/core/inspect.js +132 -7
- package/src/core/integration-invocation-policy.js +217 -0
- package/src/core/integration-limits.js +20 -0
- package/src/core/integration-resources.js +178 -0
- package/src/core/next-action-model.js +94 -0
- package/src/core/next-action.js +490 -3
- package/src/core/phase.js +42 -22
- package/src/core/policy-engine.js +113 -6
- package/src/core/preflight-consistency.js +31 -5
- package/src/core/preflight.js +19 -2
- package/src/core/prepared-execution.js +227 -0
- package/src/core/progress.js +41 -4
- package/src/core/project-root.js +21 -0
- package/src/core/protocol-info.js +61 -0
- package/src/core/protocol.js +14 -0
- package/src/core/receipt.js +1 -0
- package/src/core/reconcile-closure.js +35 -10
- package/src/core/recovery-history.js +116 -0
- package/src/core/reflection.js +305 -0
- package/src/core/resumability.js +57 -3
- package/src/core/schema-validation.js +9 -0
- package/src/core/strategy-analysis.js +97 -0
- package/src/core/task-claim-state.js +272 -0
- package/src/core/task-command.js +5 -1
- package/src/core/task-conflict-inspection.js +321 -0
- package/src/core/task-context.js +32 -29
- package/src/core/task-discovery.js +14 -1
- package/src/core/task-lock.js +216 -22
- package/src/core/task-paths.js +31 -2
- package/src/core/task-recovery-migration.js +192 -0
- package/src/core/task-recovery.js +205 -0
- package/src/core/task-scope.js +33 -1
- package/src/core/task-snapshot.js +53 -0
- package/src/core/templates.js +9 -0
- package/src/core/trace.js +548 -0
- package/src/core/trajectory-evaluation.js +71 -0
- package/src/core/trajectory-metrics.js +80 -0
- package/src/core/transaction.js +36 -2
- package/src/core/work-state.js +10 -5
- package/src/integration.js +47 -0
- package/docs/assets/forgeloop-flow.svg +0 -1
- package/docs/forgeloop-flow.mmd +0 -51
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { authorizeAction } from "../core/action-authorization.js";
|
|
2
|
+
import { withTaskMutation } from "../core/task-command.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Canonical public authorization surface. This command is only an adapter
|
|
6
|
+
* over the core authorization service: it creates no new trust mechanism and
|
|
7
|
+
* accepts no authority fields in actor-controlled arguments. Trusted host
|
|
8
|
+
* authority arrives exclusively through the out-of-band executor context.
|
|
9
|
+
*/
|
|
10
|
+
export async function runActionAuthorize({
|
|
11
|
+
target,
|
|
12
|
+
packageRoot,
|
|
13
|
+
taskId,
|
|
14
|
+
actionId,
|
|
15
|
+
approvalId,
|
|
16
|
+
authorityContext,
|
|
17
|
+
}) {
|
|
18
|
+
return withTaskMutation(
|
|
19
|
+
target,
|
|
20
|
+
{ taskId, packageRoot },
|
|
21
|
+
"action-authorize",
|
|
22
|
+
(ctx) => authorizeAction({
|
|
23
|
+
target,
|
|
24
|
+
packageRoot,
|
|
25
|
+
taskId: ctx.taskId,
|
|
26
|
+
actionId,
|
|
27
|
+
approvalId,
|
|
28
|
+
authorityContext,
|
|
29
|
+
}),
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function formatActionAuthorizeResult(result) {
|
|
34
|
+
return [
|
|
35
|
+
"FORGELOOP ACTION AUTHORIZED",
|
|
36
|
+
`action: ${result.action.actionId}`,
|
|
37
|
+
`state: ${result.action.state}`,
|
|
38
|
+
`decision: ${result.authorization.capabilityDecision}`,
|
|
39
|
+
"",
|
|
40
|
+
].join("\n");
|
|
41
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { proposeAction } from "../core/actions.js";
|
|
2
|
+
import { withTaskMutation } from "../core/task-command.js";
|
|
3
|
+
|
|
4
|
+
export async function runActionPropose({ target, packageRoot, taskId, input }) {
|
|
5
|
+
return withTaskMutation(target, { taskId, packageRoot }, "action-propose", (ctx) =>
|
|
6
|
+
proposeAction(target, { packageRoot, taskId: ctx.taskId, input: { ...input, provenance: "CALLER_REPORTED" } }));
|
|
7
|
+
}
|
|
8
|
+
export function formatActionProposeResult(result) {
|
|
9
|
+
return `FORGELOOP ACTION PROPOSED\naction: ${result.action.actionId}\nstate: ${result.action.state}\n`;
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { reconcileAction } from "../core/action-reconciliation.js";
|
|
2
|
+
import { withTaskMutation } from "../core/task-command.js";
|
|
3
|
+
export async function runActionReconcile(args) {
|
|
4
|
+
const { target, packageRoot, taskId } = args;
|
|
5
|
+
return withTaskMutation(target, { taskId, packageRoot }, "action-reconcile", (ctx) =>
|
|
6
|
+
reconcileAction({ ...args, taskId: ctx.taskId }));
|
|
7
|
+
}
|
|
8
|
+
export function formatActionReconcileResult(result) {
|
|
9
|
+
return `FORGELOOP ACTION RECONCILED\naction: ${result.action.actionId}\noutcome: ${result.outcome}\nstate: ${result.action.state}\n`;
|
|
10
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { transitionAction } from "../core/actions.js";
|
|
2
|
+
import { withTaskMutation } from "../core/task-command.js";
|
|
3
|
+
import {
|
|
4
|
+
E_ACTION_AUTHORIZATION_INVALID,
|
|
5
|
+
E_ACTION_INVALID,
|
|
6
|
+
E_ACTION_VERIFICATION_REQUIRED,
|
|
7
|
+
} from "../core/error-codes.js";
|
|
8
|
+
|
|
9
|
+
// Public action-record may only record observations that do not create
|
|
10
|
+
// authority. AUTHORIZED and VERIFIED are owned by canonical core services and
|
|
11
|
+
// are rejected here even if a transport mistakenly exposes this command.
|
|
12
|
+
const CALLER_RECORDABLE_ACTION_STATES = Object.freeze([
|
|
13
|
+
"STARTED",
|
|
14
|
+
"COMMITTED",
|
|
15
|
+
"COMMIT_UNKNOWN",
|
|
16
|
+
"FAILED",
|
|
17
|
+
"CANCELLED",
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
export async function runActionRecord({ target, packageRoot, taskId, actionId, state, provenance, evidenceRef }) {
|
|
21
|
+
if (!["CALLER_REPORTED", "EXTERNAL_OBSERVED"].includes(provenance)) {
|
|
22
|
+
const error = new Error("action-record provenance must be CALLER_REPORTED or EXTERNAL_OBSERVED");
|
|
23
|
+
error.code = E_ACTION_INVALID;
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
if (state === "AUTHORIZED") {
|
|
27
|
+
const error = new Error("AUTHORIZED is owned by the canonical authorization service; caller surfaces cannot authorize actions");
|
|
28
|
+
error.code = E_ACTION_AUTHORIZATION_INVALID;
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
if (state === "VERIFIED") {
|
|
32
|
+
const error = new Error("VERIFIED requires canonical independent postcondition evidence via forgeloop action-verify");
|
|
33
|
+
error.code = E_ACTION_VERIFICATION_REQUIRED;
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
if (!CALLER_RECORDABLE_ACTION_STATES.includes(state)) {
|
|
37
|
+
const error = new Error(`action-record state must be one of ${CALLER_RECORDABLE_ACTION_STATES.join(", ")}`);
|
|
38
|
+
error.code = E_ACTION_INVALID;
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
return withTaskMutation(target, { taskId, packageRoot }, "action-record", (ctx) =>
|
|
42
|
+
transitionAction(target, { packageRoot, taskId: ctx.taskId, actionId, to: state,
|
|
43
|
+
details: { ...(evidenceRef ? { evidenceRef } : {}), reportedProvenance: provenance } }));
|
|
44
|
+
}
|
|
45
|
+
export function formatActionRecordResult(result) {
|
|
46
|
+
return `FORGELOOP ACTION RECORDED\naction: ${result.actionId}\nstate: ${result.state}\n`;
|
|
47
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { readAction } from "../core/actions.js";
|
|
2
|
+
import { resolveTaskContext } from "../core/task-context.js";
|
|
3
|
+
|
|
4
|
+
export async function runActionShow({ target, packageRoot, taskId, actionId }) {
|
|
5
|
+
const ctx = await resolveTaskContext(target, { packageRoot, taskId });
|
|
6
|
+
return readAction(target, { packageRoot, taskId: ctx.taskId, actionId });
|
|
7
|
+
}
|
|
8
|
+
export function formatActionShowResult(result) {
|
|
9
|
+
return `FORGELOOP ACTION\naction: ${result.actionId}\nstate: ${result.state}\ncapability: ${result.capability}\n`;
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { verifyAction } from "../core/action-verification.js";
|
|
2
|
+
import { withTaskMutation } from "../core/task-command.js";
|
|
3
|
+
|
|
4
|
+
export async function runActionVerify({ target, packageRoot, taskId, actionId, evidenceRef }) {
|
|
5
|
+
return withTaskMutation(target, { taskId, packageRoot }, "action-verify", (ctx) =>
|
|
6
|
+
verifyAction({ target, packageRoot, taskId: ctx.taskId, actionId, evidenceRef }));
|
|
7
|
+
}
|
|
8
|
+
export function formatActionVerifyResult(result) {
|
|
9
|
+
return `FORGELOOP ACTION VERIFIED\naction: ${result.actionId}\nstate: ${result.state}\nevidence: ${result.lastEvidenceRef}\n`;
|
|
10
|
+
}
|
package/src/commands/advance.js
CHANGED
|
@@ -3,10 +3,15 @@ import { withTaskMutation } from "../core/task-command.js";
|
|
|
3
3
|
|
|
4
4
|
export { advanceWorkState };
|
|
5
5
|
|
|
6
|
-
export async function runAdvance({ target, packageRoot, to, taskId, task }) {
|
|
6
|
+
export async function runAdvance({ target, packageRoot, to, taskId, task, authorityContext, runtimeContext }) {
|
|
7
7
|
if (!to) throw new Error("--to is required for advance");
|
|
8
8
|
return withTaskMutation(target, { taskId: taskId ?? task, packageRoot }, "advance", async (ctx) => {
|
|
9
|
-
return advanceWorkState(target, to, {
|
|
9
|
+
return advanceWorkState(target, to, {
|
|
10
|
+
packageRoot,
|
|
11
|
+
taskId: ctx?.taskId ?? null,
|
|
12
|
+
authorityContext,
|
|
13
|
+
runtimeContext,
|
|
14
|
+
});
|
|
10
15
|
});
|
|
11
16
|
}
|
|
12
17
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { readAction } from "../core/actions.js";
|
|
2
|
+
import { readWorkState } from "../core/work-state.js";
|
|
3
|
+
import { requestApproval } from "../core/approvals.js";
|
|
4
|
+
import { evaluateActionCapability } from "../core/capability-policy.js";
|
|
5
|
+
import { loadPolicyIdentity } from "../core/policy-engine.js";
|
|
6
|
+
import {
|
|
7
|
+
E_ACTION_APPROVAL_NOT_REQUIRED,
|
|
8
|
+
E_ACTION_AUTHORITY_REQUIRED,
|
|
9
|
+
E_ACTION_CAPABILITY_DENIED,
|
|
10
|
+
} from "../core/error-codes.js";
|
|
11
|
+
import { withTaskMutation } from "../core/task-command.js";
|
|
12
|
+
|
|
13
|
+
function approvalPolicyError(code, message) {
|
|
14
|
+
const error = new Error(message);
|
|
15
|
+
error.code = code;
|
|
16
|
+
return error;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function runApprovalRequest({ target, packageRoot, taskId, approvalId, actionId, reason }) {
|
|
20
|
+
return withTaskMutation(target, { taskId, packageRoot }, "approval-request", async (ctx) => {
|
|
21
|
+
const action = await readAction(target, { packageRoot, taskId: ctx.taskId, actionId });
|
|
22
|
+
const state = await readWorkState(target, { packageRoot, taskId: ctx.taskId });
|
|
23
|
+
let policyIdentity;
|
|
24
|
+
try {
|
|
25
|
+
policyIdentity = await loadPolicyIdentity(target, packageRoot, ctx.taskId);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
throw approvalPolicyError(
|
|
28
|
+
error.code ?? "E_POLICY_INVALID",
|
|
29
|
+
`Capability policy epoch validation failed before requesting approval: ${error.message}`,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
if (policyIdentity.status !== "VALID") {
|
|
33
|
+
throw approvalPolicyError(
|
|
34
|
+
policyIdentity.code ?? "E_ACTION_POLICY_LOCK_REQUIRED",
|
|
35
|
+
"The current capability policy is not bound to a valid task policy epoch; repair or refresh policy state before requesting approval.",
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
const capability = await evaluateActionCapability({ target, packageRoot, action });
|
|
39
|
+
if (capability.decision === "ALLOW") {
|
|
40
|
+
throw approvalPolicyError(
|
|
41
|
+
E_ACTION_APPROVAL_NOT_REQUIRED,
|
|
42
|
+
"The current capability policy allows this action without approval.",
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
if (capability.decision === "DENY") {
|
|
46
|
+
throw approvalPolicyError(
|
|
47
|
+
capability.reasonCode ?? E_ACTION_CAPABILITY_DENIED,
|
|
48
|
+
`The current capability policy denies ${action.capability}; approval cannot override a denial.`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
if (capability.decision === "REQUIRE_AUTHORITY") {
|
|
52
|
+
throw approvalPolicyError(
|
|
53
|
+
capability.reasonCode ?? E_ACTION_AUTHORITY_REQUIRED,
|
|
54
|
+
"The current capability policy requires trusted host authority; approval cannot substitute for it.",
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
return requestApproval(target, { packageRoot, taskId: ctx.taskId, input: {
|
|
58
|
+
approvalId, actionId: action.actionId, actionFingerprint: action.actionFingerprint,
|
|
59
|
+
contractFingerprint: state.contractFingerprint, taskRevision: state.revision ?? 0,
|
|
60
|
+
capability: action.capability, reason,
|
|
61
|
+
} });
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
export function formatApprovalRequestResult(result) { return `FORGELOOP APPROVAL REQUESTED\napproval: ${result.approval.approvalId}\nstatus: ${result.approval.status}\n`; }
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { resolveApproval } from "../core/approvals.js";
|
|
2
|
+
import { withTaskMutation } from "../core/task-command.js";
|
|
3
|
+
export async function runApprovalResolve({ target, packageRoot, taskId, approvalId, decision, authorityKind, hostGrantRef, authorityContext, reason }) {
|
|
4
|
+
return withTaskMutation(target, { taskId, packageRoot }, "approval-resolve", (ctx) => resolveApproval(target, {
|
|
5
|
+
packageRoot, taskId: ctx.taskId, approvalId, decision, authorityKind, hostGrantRef, reason,
|
|
6
|
+
// Trusted host authority arrives out-of-band; it is never actor input.
|
|
7
|
+
authorityContext,
|
|
8
|
+
}));
|
|
9
|
+
}
|
|
10
|
+
export function formatApprovalResolveResult(result) { return `FORGELOOP APPROVAL RESOLVED\napproval: ${result.approvalId}\ndecision: ${result.decision}\n`; }
|
package/src/commands/audit.js
CHANGED
|
@@ -14,6 +14,11 @@ export async function runAudit(options = {}) {
|
|
|
14
14
|
|
|
15
15
|
export function formatAuditResult(result) {
|
|
16
16
|
const lines = [`FORGELOOP AUDIT: ${result.status}`];
|
|
17
|
+
if (result.recovery) lines.push(`RECOVERY: ${result.recovery.status} (${result.recovery.recoveryId})`);
|
|
18
|
+
if (result.claims) {
|
|
19
|
+
lines.push(`CLAIM STATE: ${result.claims.state}`);
|
|
20
|
+
lines.push(`MUTATION ALLOWED: ${result.claims.mutationAllowed ? "yes" : "no"}`);
|
|
21
|
+
}
|
|
17
22
|
for (const error of result.errors) {
|
|
18
23
|
lines.push(`${error.code}: ${error.message}`);
|
|
19
24
|
if (error.next) lines.push(`NEXT: ${error.next}`);
|
package/src/commands/baseline.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
computePersistedPolicyLockData,
|
|
3
3
|
evaluateTargetPolicy,
|
|
4
4
|
loadEffectiveRules,
|
|
5
5
|
writePolicyLock,
|
|
@@ -56,7 +56,7 @@ export async function runBaseline({
|
|
|
56
56
|
await writeBaseline(target, baseline, packageRoot);
|
|
57
57
|
|
|
58
58
|
const rules = await loadEffectiveRules(target, packageRoot);
|
|
59
|
-
const lock =
|
|
59
|
+
const lock = await computePersistedPolicyLockData(target, packageRoot, rules, baseline);
|
|
60
60
|
await writePolicyLock(target, lock, packageRoot);
|
|
61
61
|
|
|
62
62
|
return {
|
|
@@ -85,7 +85,7 @@ export async function runBaseline({
|
|
|
85
85
|
await writeBaseline(target, baseline, packageRoot);
|
|
86
86
|
|
|
87
87
|
const rules = await loadEffectiveRules(target, packageRoot);
|
|
88
|
-
const lock =
|
|
88
|
+
const lock = await computePersistedPolicyLockData(target, packageRoot, rules, baseline);
|
|
89
89
|
await writePolicyLock(target, lock, packageRoot);
|
|
90
90
|
|
|
91
91
|
return {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { evaluateTrajectory } from "../core/trajectory-evaluation.js";
|
|
2
|
+
import { withResolvedTask } from "../core/task-command.js";
|
|
3
|
+
export async function runEval({ target, packageRoot, taskId, scenarioPath }) {
|
|
4
|
+
return withResolvedTask(target, { taskId, packageRoot }, (ctx) => evaluateTrajectory({ target, packageRoot, taskId: ctx.taskId, scenarioPath }));
|
|
5
|
+
}
|
|
6
|
+
export function formatEvalResult(result) { return `${JSON.stringify(result, null, 2)}\n`; }
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { buildTaskHistory, formatHistoryResult } from "../core/history.js";
|
|
2
|
+
import { resolveTaskContext, TASK_SELECTION_MODES } from "../core/task-context.js";
|
|
3
|
+
|
|
4
|
+
export { buildTaskHistory, formatHistoryResult };
|
|
5
|
+
|
|
6
|
+
export async function runHistory({ target, packageRoot, taskId, task, filters = {} }) {
|
|
7
|
+
const resolved = await resolveTaskContext(target, {
|
|
8
|
+
packageRoot,
|
|
9
|
+
taskId: taskId ?? task,
|
|
10
|
+
selectionMode: TASK_SELECTION_MODES.READ,
|
|
11
|
+
});
|
|
12
|
+
return buildTaskHistory({
|
|
13
|
+
target,
|
|
14
|
+
packageRoot,
|
|
15
|
+
taskId: resolved?.taskId ?? null,
|
|
16
|
+
filters,
|
|
17
|
+
});
|
|
18
|
+
}
|
package/src/commands/init.js
CHANGED
|
@@ -187,7 +187,7 @@ export async function runInit({
|
|
|
187
187
|
const { discoverPolicy } = await import("../core/policy-discovery.js");
|
|
188
188
|
const {
|
|
189
189
|
loadEffectiveRules,
|
|
190
|
-
|
|
190
|
+
computePersistedPolicyLockData,
|
|
191
191
|
writeDiscoveryReport,
|
|
192
192
|
writePolicyLock,
|
|
193
193
|
detectPolicyCapability,
|
|
@@ -210,7 +210,7 @@ export async function runInit({
|
|
|
210
210
|
}
|
|
211
211
|
baseline = createBaselineFromViolations([]);
|
|
212
212
|
effectiveRules = await loadEffectiveRules(target, packageRoot);
|
|
213
|
-
lock =
|
|
213
|
+
lock = await computePersistedPolicyLockData(target, packageRoot, effectiveRules, baseline);
|
|
214
214
|
} catch (cause) {
|
|
215
215
|
throw policyInitializationError(cause);
|
|
216
216
|
}
|
package/src/commands/inspect.js
CHANGED
|
@@ -11,6 +11,8 @@ export async function inspectTarget(options = {}) {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export function formatInspectResult(report) {
|
|
14
|
+
const task = report.taskInspection;
|
|
15
|
+
if (task) return formatTaskInspection(report, task);
|
|
14
16
|
const lines = [
|
|
15
17
|
`Target: ${report.target.path}`,
|
|
16
18
|
`Manifest: ${report.manifest.status}`,
|
|
@@ -19,9 +21,62 @@ export function formatInspectResult(report) {
|
|
|
19
21
|
`Authority source: ${report.authority.sourceType ?? "none configured"} / ${report.authority.trusted ? "TRUSTED" : report.authority.trustMode === "NONE" ? "UNATTESTED" : "UNTRUSTED"}`,
|
|
20
22
|
`Authority trust: ${report.authority.trustMode}`,
|
|
21
23
|
`State: ${report.state.status}`,
|
|
24
|
+
...(report.recovery
|
|
25
|
+
? [`Recovery: ${report.recovery.status} (${report.recovery.recoveryId})`]
|
|
26
|
+
: []),
|
|
27
|
+
...(report.claims
|
|
28
|
+
? [`Claim state: ${report.claims.state}`, `Mutation allowed: ${report.claims.mutationAllowed ? "yes" : "no"}`]
|
|
29
|
+
: []),
|
|
22
30
|
`Adapters: ${report.adapters.detected.length} detected`,
|
|
23
31
|
`Findings: ${report.findings.length}`,
|
|
24
32
|
report.ok ? "healthy: ForgeLoop target is ready" : "unhealthy: ForgeLoop target needs attention",
|
|
25
33
|
];
|
|
26
34
|
return `${lines.join("\n")}\n`;
|
|
27
35
|
}
|
|
36
|
+
|
|
37
|
+
function formatTaskInspection(report, task) {
|
|
38
|
+
const rule = "-".repeat(56);
|
|
39
|
+
const lines = [
|
|
40
|
+
"ForgeLoop Task Inspection",
|
|
41
|
+
rule,
|
|
42
|
+
`Task: ${task.task ?? report.claims?.state ?? "-"}`,
|
|
43
|
+
`Phase: ${task.lifecycle.phase ?? "-"}`,
|
|
44
|
+
`Verification cycle: ${task.lifecycle.verificationCycle ?? "-"}`,
|
|
45
|
+
`Ledger: ${task.integrity.valid ? "VALID" : "INVALID"}`,
|
|
46
|
+
`Snapshot: ${task.snapshot.consistent ? "CONSISTENT" : "INCONSISTENT"}`,
|
|
47
|
+
`Progress: ${task.progress.status}`,
|
|
48
|
+
"",
|
|
49
|
+
"Verification",
|
|
50
|
+
];
|
|
51
|
+
for (const check of task.verification.checks) {
|
|
52
|
+
lines.push(` ${check.id.padEnd(20)} ${check.currentResult ?? "unknown"}${Number.isInteger(check.attemptCount) ? ` attempts: ${check.attemptCount}` : ""}`);
|
|
53
|
+
}
|
|
54
|
+
if (task.verification.checks.length === 0) lines.push(" (no checks recorded)");
|
|
55
|
+
lines.push(
|
|
56
|
+
"",
|
|
57
|
+
"Diagnostics",
|
|
58
|
+
` Cases: ${task.diagnostics.diagnosticCaseCount}`,
|
|
59
|
+
` Legacy diagnoses: ${task.diagnostics.legacyDiagnosisCount}`,
|
|
60
|
+
` Interventions: ${task.diagnostics.interventionCount}`,
|
|
61
|
+
` Dispositions: ${task.diagnostics.dispositionCount}`,
|
|
62
|
+
);
|
|
63
|
+
const surfaces = task.failureSurfaces ?? [];
|
|
64
|
+
if (surfaces.length > 0) {
|
|
65
|
+
lines.push("", "Failure surface");
|
|
66
|
+
for (const entry of surfaces) {
|
|
67
|
+
lines.push(` cycle ${entry.verificationCycle}: ${entry.surface.join(", ")}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if ((task.issues ?? []).length > 0) {
|
|
71
|
+
lines.push("", "Signals");
|
|
72
|
+
for (const issue of task.issues) {
|
|
73
|
+
lines.push(` ${issue.code}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
lines.push(
|
|
77
|
+
"",
|
|
78
|
+
"Recommended next command",
|
|
79
|
+
` ${task.next.command}`,
|
|
80
|
+
);
|
|
81
|
+
return `${lines.join("\n")}\n`;
|
|
82
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { buildTrajectoryMetrics } from "../core/trajectory-metrics.js";
|
|
2
|
+
import { withResolvedTask } from "../core/task-command.js";
|
|
3
|
+
export async function runMetrics({ target, packageRoot, taskId }) {
|
|
4
|
+
return withResolvedTask(target, { taskId, packageRoot }, (ctx) =>
|
|
5
|
+
buildTrajectoryMetrics({ target, packageRoot, taskId: ctx.taskId }));
|
|
6
|
+
}
|
|
7
|
+
export function formatMetricsResult(result) { return `${JSON.stringify(result, null, 2)}\n`; }
|
package/src/commands/next.js
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { getNextAction } from "../core/next-action.js";
|
|
2
2
|
import { withResolvedTask } from "../core/task-command.js";
|
|
3
3
|
|
|
4
|
-
export async function runNext({ target, packageRoot, taskId, task }) {
|
|
4
|
+
export async function runNext({ target, packageRoot, taskId, task, authorityContext, runtimeContext }) {
|
|
5
5
|
return withResolvedTask(target, { taskId: taskId ?? task, packageRoot }, async (ctx) => {
|
|
6
|
-
return getNextAction({
|
|
6
|
+
return getNextAction({
|
|
7
|
+
target,
|
|
8
|
+
packageRoot,
|
|
9
|
+
taskId: ctx?.taskId ?? null,
|
|
10
|
+
authorityContext,
|
|
11
|
+
runtimeContext,
|
|
12
|
+
});
|
|
7
13
|
});
|
|
8
14
|
}
|
|
9
15
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { discoverPolicy } from "../core/policy-discovery.js";
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
computePersistedPolicyLockData,
|
|
4
4
|
loadEffectiveRules,
|
|
5
5
|
readBaseline,
|
|
6
6
|
writeDiscoveryReport,
|
|
@@ -15,7 +15,7 @@ export async function runPolicyDiscover({ target = process.cwd(), packageRoot, w
|
|
|
15
15
|
await writeDiscoveryReport(target, discovery, packageRoot);
|
|
16
16
|
const baseline = await readBaseline(target, packageRoot);
|
|
17
17
|
const rules = await loadEffectiveRules(target, packageRoot);
|
|
18
|
-
lock =
|
|
18
|
+
lock = await computePersistedPolicyLockData(target, packageRoot, rules, baseline);
|
|
19
19
|
await writePolicyLock(target, lock, packageRoot);
|
|
20
20
|
}
|
|
21
21
|
|
package/src/commands/progress.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { evaluateProgress, PROGRESS_STATUS } from "../core/progress.js";
|
|
2
2
|
import { readEvents } from "../core/events.js";
|
|
3
3
|
import { readWorkState } from "../core/work-state.js";
|
|
4
|
-
import { resolveTaskContext } from "../core/task-context.js";
|
|
4
|
+
import { resolveTaskContext, TASK_SELECTION_MODES } from "../core/task-context.js";
|
|
5
5
|
|
|
6
6
|
export { evaluateProgress };
|
|
7
7
|
|
|
8
8
|
export async function runProgress({ target, packageRoot, taskId, task }) {
|
|
9
|
-
const resolved = await resolveTaskContext(target, {
|
|
9
|
+
const resolved = await resolveTaskContext(target, {
|
|
10
|
+
packageRoot,
|
|
11
|
+
taskId: taskId ?? task,
|
|
12
|
+
selectionMode: TASK_SELECTION_MODES.READ,
|
|
13
|
+
});
|
|
10
14
|
const activeTaskId = resolved.taskId;
|
|
11
15
|
|
|
12
16
|
const state = await readWorkState(target, { packageRoot, taskId: activeTaskId });
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { recordDiagnosis } from "../core/diagnosis.js";
|
|
2
|
+
import { recordStructuredDiagnosticCase } from "../core/diagnostic-record.js";
|
|
2
3
|
import { withTaskMutation } from "../core/task-command.js";
|
|
3
4
|
|
|
4
|
-
export { recordDiagnosis };
|
|
5
|
+
export { recordDiagnosis, recordStructuredDiagnosticCase };
|
|
5
6
|
|
|
6
7
|
export async function runRecordDiagnosis({
|
|
7
8
|
target,
|
|
8
9
|
packageRoot,
|
|
10
|
+
file = null,
|
|
9
11
|
hypothesis,
|
|
10
12
|
failureClass,
|
|
11
13
|
evidenceRefs = [],
|
|
@@ -15,6 +17,26 @@ export async function runRecordDiagnosis({
|
|
|
15
17
|
taskId,
|
|
16
18
|
task,
|
|
17
19
|
}) {
|
|
20
|
+
if (file) {
|
|
21
|
+
const legacyFields = [hypothesis, failureClass, settledBy, nextSafeAction].some((value) => value !== undefined && value !== null)
|
|
22
|
+
|| (Array.isArray(evidenceRefs) && evidenceRefs.length > 0)
|
|
23
|
+
|| Boolean(evidenceRef);
|
|
24
|
+
if (legacyFields) {
|
|
25
|
+
const error = new Error("record-diagnosis accepts either --file (structured case) or legacy diagnosis fields, not both");
|
|
26
|
+
error.code = "E_INPUT_CONFLICT";
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
return withTaskMutation(target, { taskId: taskId ?? task, packageRoot }, "record-diagnosis", async (ctx) => {
|
|
30
|
+
return recordStructuredDiagnosticCase({
|
|
31
|
+
target,
|
|
32
|
+
packageRoot,
|
|
33
|
+
caseFile: file,
|
|
34
|
+
caseInput: null,
|
|
35
|
+
taskId: ctx?.taskId ?? null,
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
18
40
|
const refs = Array.isArray(evidenceRefs) && evidenceRefs.length > 0
|
|
19
41
|
? evidenceRefs
|
|
20
42
|
: (evidenceRef ? [evidenceRef] : []);
|
|
@@ -34,6 +56,20 @@ export async function runRecordDiagnosis({
|
|
|
34
56
|
}
|
|
35
57
|
|
|
36
58
|
export function formatRecordDiagnosisResult(result) {
|
|
59
|
+
if (result.diagnosticCase) {
|
|
60
|
+
const d = result.diagnosticCase;
|
|
61
|
+
return [
|
|
62
|
+
`FORGELOOP DIAGNOSTIC CASE RECORDED`,
|
|
63
|
+
`CYCLE: ${d.verificationCycle}`,
|
|
64
|
+
`REVISION: ${d.diagnosticRevision}`,
|
|
65
|
+
`FAILURE CLASS: ${d.failureClass}`,
|
|
66
|
+
`OBSERVATIONS: ${(d.observations ?? []).length}`,
|
|
67
|
+
`CONTRIBUTORS: ${(d.contributors ?? []).length}`,
|
|
68
|
+
`HYPOTHESES: ${(d.hypotheses ?? []).map((hypothesis) => hypothesis.id).join(", ")}`,
|
|
69
|
+
`NEXT SAFE ACTION: ${d.nextSafeAction?.statement ?? ""}`,
|
|
70
|
+
`FINGERPRINT: ${d.diagnosticFingerprint}`,
|
|
71
|
+
].join("\n") + "\n";
|
|
72
|
+
}
|
|
37
73
|
const d = result.diagnosis ?? result.event?.details ?? {};
|
|
38
74
|
return [
|
|
39
75
|
`FORGELOOP DIAGNOSIS RECORDED`,
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { recordHypothesisDisposition } from "../core/diagnostic-record.js";
|
|
2
|
+
import { withTaskMutation } from "../core/task-command.js";
|
|
3
|
+
|
|
4
|
+
export { recordHypothesisDisposition };
|
|
5
|
+
|
|
6
|
+
export async function runRecordHypothesisDisposition({
|
|
7
|
+
target,
|
|
8
|
+
packageRoot,
|
|
9
|
+
hypothesis,
|
|
10
|
+
hypothesisRef = null,
|
|
11
|
+
status,
|
|
12
|
+
evidenceRefs = [],
|
|
13
|
+
evidenceRef = null,
|
|
14
|
+
reason,
|
|
15
|
+
taskId,
|
|
16
|
+
task,
|
|
17
|
+
}) {
|
|
18
|
+
const refs = Array.isArray(evidenceRefs) && evidenceRefs.length > 0
|
|
19
|
+
? evidenceRefs
|
|
20
|
+
: (evidenceRef ? [evidenceRef] : []);
|
|
21
|
+
|
|
22
|
+
return withTaskMutation(target, { taskId: taskId ?? task, packageRoot }, "record-hypothesis-disposition", async (ctx) => {
|
|
23
|
+
return recordHypothesisDisposition({
|
|
24
|
+
target,
|
|
25
|
+
packageRoot,
|
|
26
|
+
hypothesisRef: hypothesis ?? hypothesisRef,
|
|
27
|
+
status,
|
|
28
|
+
evidenceRefs: refs,
|
|
29
|
+
reason,
|
|
30
|
+
taskId: ctx?.taskId ?? null,
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function formatRecordHypothesisDispositionResult(result) {
|
|
36
|
+
const disposition = result.disposition ?? {};
|
|
37
|
+
return [
|
|
38
|
+
"FORGELOOP HYPOTHESIS DISPOSITION RECORDED",
|
|
39
|
+
`CYCLE: ${disposition.verificationCycle}`,
|
|
40
|
+
`HYPOTHESIS: ${disposition.hypothesisRef}`,
|
|
41
|
+
`STATUS: ${disposition.status}`,
|
|
42
|
+
`EVIDENCE: ${(disposition.evidenceRefs ?? []).join(", ")}`,
|
|
43
|
+
`REASON: ${disposition.reason}`,
|
|
44
|
+
].join("\n") + "\n";
|
|
45
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { recordIntervention } from "../core/diagnostic-record.js";
|
|
2
|
+
import { withTaskMutation } from "../core/task-command.js";
|
|
3
|
+
|
|
4
|
+
export { recordIntervention };
|
|
5
|
+
|
|
6
|
+
export async function runRecordIntervention({
|
|
7
|
+
target,
|
|
8
|
+
packageRoot,
|
|
9
|
+
file = null,
|
|
10
|
+
taskId,
|
|
11
|
+
task,
|
|
12
|
+
}) {
|
|
13
|
+
return withTaskMutation(target, { taskId: taskId ?? task, packageRoot }, "record-intervention", async (ctx) => {
|
|
14
|
+
return recordIntervention({
|
|
15
|
+
target,
|
|
16
|
+
packageRoot,
|
|
17
|
+
interventionFile: file,
|
|
18
|
+
interventionInput: null,
|
|
19
|
+
taskId: ctx?.taskId ?? null,
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function formatRecordInterventionResult(result) {
|
|
25
|
+
const intervention = result.intervention?.intervention ?? {};
|
|
26
|
+
return [
|
|
27
|
+
"FORGELOOP INTERVENTION RECORDED",
|
|
28
|
+
`CYCLE: ${result.intervention?.verificationCycle}`,
|
|
29
|
+
`ID: ${intervention.id}`,
|
|
30
|
+
`KIND: ${intervention.kind}`,
|
|
31
|
+
`STATEMENT: ${intervention.statement}`,
|
|
32
|
+
`HYPOTHESES: ${(intervention.hypothesisRefs ?? []).join(", ")}`,
|
|
33
|
+
result.repeatedSemanticIntervention ? "WARNING: this semantic intervention has been recorded before" : null,
|
|
34
|
+
].filter((line) => line !== null).join("\n") + "\n";
|
|
35
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { buildTaskReflection } from "../core/reflection.js";
|
|
2
|
+
import { resolveTaskContext, TASK_SELECTION_MODES } from "../core/task-context.js";
|
|
3
|
+
|
|
4
|
+
export { buildTaskReflection };
|
|
5
|
+
|
|
6
|
+
export function formatReflectResult(result) {
|
|
7
|
+
const lines = [
|
|
8
|
+
"ForgeLoop Reflection",
|
|
9
|
+
"─".repeat(56),
|
|
10
|
+
"",
|
|
11
|
+
`Task: ${result.taskId ?? "unknown"}`,
|
|
12
|
+
`Phase: ${result.taskPhase ?? "UNKNOWN"}`,
|
|
13
|
+
`Status: ${result.status}`,
|
|
14
|
+
`Cycles: ${result.verificationCycles}`,
|
|
15
|
+
`Integrity: ${result.integrityValid ? "VALID" : "INCONSISTENT"}`,
|
|
16
|
+
"",
|
|
17
|
+
`Hypotheses: ${result.hypotheses.created} created, ${result.hypotheses.open} open, ${result.hypotheses.supported} supported, ${result.hypotheses.weakened} weakened, ${result.hypotheses.falsified} falsified`,
|
|
18
|
+
`Interventions: ${result.interventions.count} (${result.interventions.informative} informative, ${result.interventions.nonInformative} non-informative)`,
|
|
19
|
+
`Oscillation: ${result.oscillation.detected ? "DETECTED" : "none"}`,
|
|
20
|
+
result.signals.length > 0 ? `Signals: ${result.signals.join(", ")}` : "Signals: none",
|
|
21
|
+
"",
|
|
22
|
+
`Recommended protocol action: ${result.recommendedProtocolAction}`,
|
|
23
|
+
];
|
|
24
|
+
return lines.join("\n") + "\n";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function runReflect({ target, packageRoot, taskId, task }) {
|
|
28
|
+
const resolved = await resolveTaskContext(target, {
|
|
29
|
+
packageRoot,
|
|
30
|
+
taskId: taskId ?? task,
|
|
31
|
+
selectionMode: TASK_SELECTION_MODES.READ,
|
|
32
|
+
});
|
|
33
|
+
return buildTaskReflection({
|
|
34
|
+
target,
|
|
35
|
+
packageRoot,
|
|
36
|
+
taskId: resolved?.taskId ?? null,
|
|
37
|
+
});
|
|
38
|
+
}
|
package/src/commands/report.js
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { evaluateReport } from "../core/report.js";
|
|
2
|
+
import { withResolvedTask } from "../core/task-command.js";
|
|
2
3
|
|
|
3
|
-
export
|
|
4
|
+
export async function runReport(options = {}) {
|
|
5
|
+
const target = options.target;
|
|
6
|
+
const packageRoot = options.packageRoot;
|
|
7
|
+
const taskId = options.taskId ?? options.task ?? null;
|
|
8
|
+
return withResolvedTask(target, { taskId, packageRoot }, async (ctx) => {
|
|
9
|
+
return evaluateReport({ ...options, taskId: ctx?.taskId ?? null });
|
|
10
|
+
});
|
|
11
|
+
}
|
|
4
12
|
|
|
5
13
|
export function formatReportResult(result) {
|
|
6
14
|
const lines = ["ForgeLoop Compliance"];
|