cool-workflow 0.1.97 → 0.2.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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +11 -2
- package/apps/architecture-review/app.json +1 -1
- package/apps/architecture-review-fast/app.json +1 -1
- package/apps/end-to-end-golden-path/app.json +1 -1
- package/apps/pr-review-fix-ci/app.json +1 -1
- package/apps/release-cut/app.json +1 -1
- package/apps/research-synthesis/app.json +1 -1
- package/dist/cli/dispatch.js +236 -0
- package/dist/cli/entry.js +120 -0
- package/dist/cli/io.js +21 -4
- package/dist/cli/parseargv.js +157 -0
- package/dist/cli.js +6 -33
- package/dist/core/capability-table.js +3534 -0
- package/dist/core/format/help.js +314 -0
- package/dist/{state-explosion/format.js → core/format/state-explosion-text.js} +10 -1
- package/dist/core/hash.js +137 -0
- package/dist/core/multi-agent/candidate-scoring.js +219 -0
- package/dist/core/multi-agent/collaboration.js +481 -0
- package/dist/core/multi-agent/coordinator.js +515 -0
- package/dist/core/multi-agent/eval-replay.js +306 -0
- package/dist/core/multi-agent/runtime.js +929 -0
- package/dist/core/multi-agent/topology.js +298 -0
- package/dist/core/multi-agent/trust-policy.js +197 -0
- package/dist/core/pipeline/commit-gate.js +320 -0
- package/dist/{pipeline-contract.js → core/pipeline/contract.js} +24 -37
- package/dist/core/pipeline/dispatch.js +103 -0
- package/dist/core/pipeline/drive-decide.js +227 -0
- package/dist/core/pipeline/error-feedback.js +161 -0
- package/dist/core/pipeline/loop-expansion.js +124 -0
- package/dist/{result-normalize.js → core/pipeline/result-normalize.js} +14 -37
- package/dist/core/pipeline/runner.js +230 -0
- package/dist/{contract-migration.js → core/state/contract-migration.js} +68 -92
- package/dist/{state-migrations.js → core/state/migrations.js} +103 -96
- package/dist/core/state/node-projection.js +95 -0
- package/dist/core/state/node-snapshot.js +230 -0
- package/dist/core/state/run-paths.js +93 -0
- package/dist/{schema-validate.js → core/state/schema-validate.js} +35 -28
- package/dist/core/state/schema.js +50 -0
- package/dist/core/state/state-explosion/digest.js +243 -0
- package/dist/core/state/state-explosion/graph.js +527 -0
- package/dist/{state-explosion → core/state/state-explosion}/helpers.js +34 -3
- package/dist/core/state/state-explosion/report.js +187 -0
- package/dist/{state-explosion → core/state/state-explosion}/size.js +25 -7
- package/dist/{state-node.js → core/state/state-node.js} +90 -113
- package/dist/core/state/types.js +19 -0
- package/dist/{validation.js → core/state/validation.js} +64 -131
- package/dist/core/trust/evidence-grounding.js +134 -0
- package/dist/core/trust/ledger.js +199 -0
- package/dist/{telemetry-attestation.js → core/trust/telemetry-attestation.js} +94 -68
- package/dist/core/trust/telemetry-ledger.js +127 -0
- package/dist/core/types.js +20 -0
- package/dist/core/version.js +16 -0
- package/dist/{workflow-app-framework.js → core/workflow-apps/app-schema.js} +337 -426
- package/dist/mcp/dispatch.js +104 -0
- package/dist/mcp/server.js +144 -0
- package/dist/mcp-server.js +6 -84
- package/dist/{agent-config.js → shell/agent-config.js} +118 -71
- package/dist/shell/app-run-cli.js +88 -0
- package/dist/shell/audit-cli.js +259 -0
- package/dist/shell/audit-provenance.js +83 -0
- package/dist/shell/candidate-scoring-io.js +459 -0
- package/dist/shell/collaboration-io.js +264 -0
- package/dist/shell/commit-summary.js +104 -0
- package/dist/shell/commit.js +290 -0
- package/dist/shell/coordinator-io.js +476 -0
- package/dist/shell/demo-cli.js +19 -0
- package/dist/shell/dispatch.js +162 -0
- package/dist/{doctor.js → shell/doctor.js} +123 -56
- package/dist/shell/drive.js +873 -0
- package/dist/shell/error-feedback-io.js +305 -0
- package/dist/shell/eval-io.js +473 -0
- package/dist/{multi-agent-eval/format.js → shell/eval-text.js} +78 -49
- package/dist/{evidence-reasoning.js → shell/evidence-reasoning.js} +124 -255
- package/dist/shell/exec-backend-cli.js +88 -0
- package/dist/shell/execution-backend/agent.js +475 -0
- package/dist/shell/execution-backend/ci.js +15 -0
- package/dist/shell/execution-backend/container.js +69 -0
- package/dist/shell/execution-backend/envelopes.js +55 -0
- package/dist/shell/execution-backend/local.js +113 -0
- package/dist/shell/execution-backend/probes.js +175 -0
- package/dist/shell/execution-backend/registry.js +402 -0
- package/dist/shell/execution-backend/remote.js +128 -0
- package/dist/shell/execution-backend/types.js +11 -0
- package/dist/shell/feedback-cli.js +81 -0
- package/dist/shell/feedback-operations.js +48 -0
- package/dist/shell/fs-atomic.js +276 -0
- package/dist/shell/harness.js +98 -0
- package/dist/shell/ledger-cli.js +212 -0
- package/dist/shell/ledger-io.js +169 -0
- package/dist/shell/man-cli.js +89 -0
- package/dist/shell/metrics-cli.js +98 -0
- package/dist/shell/multi-agent-cli.js +1002 -0
- package/dist/shell/multi-agent-host.js +563 -0
- package/dist/shell/multi-agent-io.js +387 -0
- package/dist/{multi-agent-operator-ux.js → shell/multi-agent-operator-ux.js} +234 -191
- package/dist/shell/node-store.js +124 -0
- package/dist/{observability/format.js → shell/observability-format.js} +7 -1
- package/dist/{observability/intake.js → shell/observability-intake.js} +79 -56
- package/dist/{observability.js → shell/observability.js} +159 -332
- package/dist/{onramp.js → shell/onramp.js} +11 -0
- package/dist/{operator-ux/format.js → shell/operator-ux-text.js} +250 -239
- package/dist/shell/operator-ux.js +431 -0
- package/dist/shell/orchestrator.js +231 -0
- package/dist/shell/pipeline-cli.js +667 -0
- package/dist/shell/pipeline.js +217 -0
- package/dist/shell/reclamation-io.js +1366 -0
- package/dist/shell/registry-cli.js +329 -0
- package/dist/{remote-source.js → shell/remote-source.js} +12 -5
- package/dist/shell/report-cli.js +101 -0
- package/dist/shell/report-view-cli.js +117 -0
- package/dist/{orchestrator → shell}/report.js +289 -282
- package/dist/shell/reporter.js +62 -0
- package/dist/shell/run-export-cli.js +106 -0
- package/dist/shell/run-export.js +680 -0
- package/dist/shell/run-registry-io.js +1014 -0
- package/dist/shell/run-store.js +164 -0
- package/dist/{sandbox-profile.js → shell/sandbox-profile.js} +134 -95
- package/dist/{scheduler.js → shell/scheduler-io.js} +248 -48
- package/dist/shell/scheduling-io.js +311 -0
- package/dist/shell/state-cli.js +181 -0
- package/dist/shell/state-explosion-cli.js +197 -0
- package/dist/shell/telemetry-cli.js +85 -0
- package/dist/{telemetry-demo.js → shell/telemetry-demo.js} +149 -119
- package/dist/shell/telemetry-ledger-io.js +132 -0
- package/dist/{term.js → shell/term.js} +36 -46
- package/dist/shell/topology-io.js +361 -0
- package/dist/shell/trust-audit.js +471 -0
- package/dist/{multi-agent-trust.js → shell/trust-policy-io.js} +67 -205
- package/dist/shell/verifier.js +48 -0
- package/dist/shell/workbench-host.js +250 -0
- package/dist/shell/workbench-text.js +18 -0
- package/dist/shell/workbench.js +175 -0
- package/dist/shell/worker-cli.js +124 -0
- package/dist/shell/worker-isolation.js +852 -0
- package/dist/shell/workflow-app-loader.js +650 -0
- package/docs/agent-delegation-drive.7.md +4 -0
- package/docs/cli-mcp-parity.7.md +284 -211
- package/docs/contract-migration-tooling.7.md +4 -0
- package/docs/control-plane-scheduling.7.md +4 -0
- package/docs/cross-agent-ledger.7.md +217 -0
- package/docs/designs/handoff-ledger.md +145 -0
- package/docs/durable-state-and-locking.7.md +4 -0
- package/docs/evidence-adoption-reasoning-chain.7.md +4 -0
- package/docs/execution-backends.7.md +4 -0
- package/docs/handoff-setup.md +120 -0
- package/docs/multi-agent-cli-mcp-surface.7.md +4 -0
- package/docs/multi-agent-eval-replay-harness.7.md +4 -0
- package/docs/multi-agent-operator-ux.7.md +4 -0
- package/docs/node-snapshot-diff-replay.7.md +4 -0
- package/docs/observability-cost-accounting.7.md +4 -0
- package/docs/project-index.md +143 -71
- package/docs/real-execution-backends.7.md +4 -0
- package/docs/release-and-migration.7.md +4 -0
- package/docs/release-tooling.7.md +4 -0
- package/docs/run-registry-control-plane.7.md +4 -0
- package/docs/run-retention-reclamation.7.md +25 -0
- package/docs/state-explosion-management.7.md +4 -0
- package/docs/team-collaboration.7.md +4 -0
- package/docs/web-desktop-workbench.7.md +4 -0
- package/manifest/plugin.manifest.json +1 -1
- package/manifest/source-context-profiles.json +9 -13
- package/package.json +1 -1
- package/scripts/agents/codex-agent.js +34 -4
- package/scripts/agents/cw-attest-wrap.js +2 -2
- package/scripts/bump-version.js +4 -3
- package/scripts/canonical-apps.js +4 -4
- package/scripts/children/batch-delegate-child.js +30 -10
- package/scripts/dogfood-architecture-review.js +2 -3
- package/scripts/dogfood-release.js +3 -3
- package/scripts/gen-parity-doc.js +15 -2
- package/scripts/golden-path.js +4 -4
- package/scripts/onramp-check.js +1 -1
- package/scripts/parity-check.js +38 -21
- package/scripts/release-flow.js +9 -3
- package/scripts/sync-project-index.js +51 -27
- package/scripts/validate-run-state-schema.js +2 -2
- package/scripts/version-sync-check.js +30 -30
- package/dist/candidate-scoring.js +0 -729
- package/dist/capability-core.js +0 -1186
- package/dist/capability-registry.js +0 -879
- package/dist/cli/command-surface.js +0 -490
- package/dist/cli/format.js +0 -56
- package/dist/cli/handlers/audit.js +0 -82
- package/dist/cli/handlers/blackboard.js +0 -81
- package/dist/cli/handlers/candidate.js +0 -40
- package/dist/cli/handlers/clones.js +0 -34
- package/dist/cli/handlers/collaboration.js +0 -61
- package/dist/cli/handlers/eval.js +0 -40
- package/dist/cli/handlers/maintenance.js +0 -107
- package/dist/cli/handlers/multi-agent.js +0 -165
- package/dist/cli/handlers/node.js +0 -41
- package/dist/cli/handlers/operational.js +0 -155
- package/dist/cli/handlers/operator.js +0 -146
- package/dist/cli/handlers/registry.js +0 -68
- package/dist/cli/handlers/run.js +0 -153
- package/dist/cli/handlers/scheduling.js +0 -132
- package/dist/cli/handlers/workbench.js +0 -41
- package/dist/cli/handlers/worker.js +0 -45
- package/dist/cli/run-summary.js +0 -45
- package/dist/clones.js +0 -162
- package/dist/collaboration.js +0 -726
- package/dist/commit.js +0 -592
- package/dist/compare.js +0 -18
- package/dist/coordinator/classify.js +0 -45
- package/dist/coordinator/paths.js +0 -42
- package/dist/coordinator/util.js +0 -126
- package/dist/coordinator.js +0 -990
- package/dist/daemon.js +0 -44
- package/dist/dispatch.js +0 -250
- package/dist/drive.js +0 -827
- package/dist/error-feedback.js +0 -419
- package/dist/evidence-grounding.js +0 -184
- package/dist/execution-backend/agent.js +0 -294
- package/dist/execution-backend/probes.js +0 -112
- package/dist/execution-backend/util.js +0 -47
- package/dist/execution-backend.js +0 -961
- package/dist/gates.js +0 -48
- package/dist/harness.js +0 -61
- package/dist/loop-expansion.js +0 -60
- package/dist/mcp/tool-call.js +0 -434
- package/dist/mcp/tool-definitions.js +0 -1040
- package/dist/mcp-surface.js +0 -30
- package/dist/multi-agent/graph.js +0 -84
- package/dist/multi-agent/helpers.js +0 -141
- package/dist/multi-agent/ids.js +0 -20
- package/dist/multi-agent/paths.js +0 -22
- package/dist/multi-agent-eval/normalize.js +0 -51
- package/dist/multi-agent-eval.js +0 -678
- package/dist/multi-agent-host.js +0 -777
- package/dist/multi-agent.js +0 -984
- package/dist/node-projection.js +0 -59
- package/dist/node-snapshot.js +0 -260
- package/dist/operator-ux.js +0 -631
- package/dist/orchestrator/app-operations.js +0 -211
- package/dist/orchestrator/audit-operations.js +0 -182
- package/dist/orchestrator/candidate-operations.js +0 -117
- package/dist/orchestrator/cli-options.js +0 -294
- package/dist/orchestrator/collaboration-operations.js +0 -86
- package/dist/orchestrator/feedback-operations.js +0 -81
- package/dist/orchestrator/host-operations.js +0 -78
- package/dist/orchestrator/lifecycle-operations.js +0 -626
- package/dist/orchestrator/migration-operations.js +0 -44
- package/dist/orchestrator/multi-agent-operations.js +0 -362
- package/dist/orchestrator/topology-operations.js +0 -84
- package/dist/orchestrator.js +0 -917
- package/dist/pipeline-runner.js +0 -285
- package/dist/reclamation/hash.js +0 -72
- package/dist/reclamation.js +0 -812
- package/dist/reporter.js +0 -67
- package/dist/run-export.js +0 -784
- package/dist/run-registry/derive.js +0 -175
- package/dist/run-registry/format.js +0 -124
- package/dist/run-registry/gc.js +0 -251
- package/dist/run-registry/policy.js +0 -16
- package/dist/run-registry/queue.js +0 -115
- package/dist/run-registry.js +0 -850
- package/dist/run-state-schema.js +0 -68
- package/dist/scheduling.js +0 -184
- package/dist/state-explosion.js +0 -1014
- package/dist/state.js +0 -367
- package/dist/telemetry-ledger.js +0 -196
- package/dist/topology.js +0 -565
- package/dist/triggers.js +0 -184
- package/dist/trust-audit.js +0 -644
- package/dist/types/blackboard.js +0 -2
- package/dist/types/candidate.js +0 -2
- package/dist/types/collaboration.js +0 -2
- package/dist/types/core.js +0 -2
- package/dist/types/drive.js +0 -10
- package/dist/types/error-feedback.js +0 -2
- package/dist/types/evidence-reasoning.js +0 -2
- package/dist/types/execution-backend.js +0 -2
- package/dist/types/multi-agent.js +0 -2
- package/dist/types/observability.js +0 -2
- package/dist/types/pipeline.js +0 -2
- package/dist/types/reclamation.js +0 -8
- package/dist/types/report-bundle.js +0 -6
- package/dist/types/result.js +0 -2
- package/dist/types/run-registry.js +0 -2
- package/dist/types/run.js +0 -2
- package/dist/types/sandbox.js +0 -2
- package/dist/types/schedule.js +0 -2
- package/dist/types/state-node.js +0 -2
- package/dist/types/topology.js +0 -2
- package/dist/types/trust.js +0 -2
- package/dist/types/workbench.js +0 -2
- package/dist/types/worker.js +0 -2
- package/dist/types/workflow-app.js +0 -2
- package/dist/types.js +0 -44
- package/dist/util/fingerprint.js +0 -19
- package/dist/util/fingerprint.test.js +0 -27
- package/dist/verifier.js +0 -78
- package/dist/version.js +0 -8
- package/dist/workbench-host.js +0 -182
- package/dist/workbench.js +0 -192
- package/dist/worker-accept/acceptance.js +0 -114
- package/dist/worker-accept/blackboard-fanout.js +0 -80
- package/dist/worker-accept/blackboard-linkage.js +0 -19
- package/dist/worker-accept/context.js +0 -2
- package/dist/worker-accept/telemetry-ledger.js +0 -126
- package/dist/worker-accept/validation.js +0 -77
- package/dist/worker-accept/verifier-completion.js +0 -73
- package/dist/worker-isolation/helpers.js +0 -51
- package/dist/worker-isolation/paths.js +0 -46
- package/dist/worker-isolation.js +0 -656
- package/dist/workflow-api.js +0 -131
- /package/dist/{types → core/types}/boundary.js +0 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// shell/fs-atomic.ts — atomic/durable JSON file IO and the cross-process
|
|
3
|
+
// advisory file lock. Impure by nature (fs calls); this is the shell layer,
|
|
4
|
+
// not core. Every writer in core/+shell/ that touches disk goes through
|
|
5
|
+
// `writeJson` here, so the write bytes stay exactly one place.
|
|
6
|
+
//
|
|
7
|
+
// Evidence: SPEC/state-core.md "Write ordering and atomic rules", "Lock
|
|
8
|
+
// protocol"; v2/PLAN.md byte-compat items 1 and 6.
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.writeJson = writeJson;
|
|
44
|
+
exports.readJson = readJson;
|
|
45
|
+
exports.safeFileName = safeFileName;
|
|
46
|
+
exports.assertSafeRunId = assertSafeRunId;
|
|
47
|
+
exports.realResolve = realResolve;
|
|
48
|
+
exports.isContainedPath = isContainedPath;
|
|
49
|
+
exports.durableAppendFileSync = durableAppendFileSync;
|
|
50
|
+
exports.withFileLock = withFileLock;
|
|
51
|
+
const fs = __importStar(require("node:fs"));
|
|
52
|
+
const path = __importStar(require("node:path"));
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// writeJson — the ONE JSON-on-disk byte format: JSON.stringify(value, null, 2)
|
|
55
|
+
// + "\n", written via temp-file + optional fsync + rename (atomic).
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
let atomicWriteCounter = 0;
|
|
58
|
+
/** Atomic, optionally-durable JSON write. ORDER IS THE SAFETY PROPERTY: write
|
|
59
|
+
* the full bytes to a unique temp file, optionally fsync it, close, then
|
|
60
|
+
* rename over the target. `rename(2)` is atomic on POSIX, so a reader always
|
|
61
|
+
* sees either the old bytes or the new bytes, never a torn file. On a rename
|
|
62
|
+
* failure the temp file is removed (best-effort) and the error is rethrown —
|
|
63
|
+
* the old bytes at `file` are never touched. */
|
|
64
|
+
function writeJson(file, value, options = {}) {
|
|
65
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
66
|
+
const tmp = `${file}.tmp.${process.pid}.${atomicWriteCounter++}`;
|
|
67
|
+
const fd = fs.openSync(tmp, "w");
|
|
68
|
+
try {
|
|
69
|
+
fs.writeFileSync(fd, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
70
|
+
if (options.durable)
|
|
71
|
+
fs.fsyncSync(fd);
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
fs.closeSync(fd);
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
fs.renameSync(tmp, file);
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
try {
|
|
81
|
+
fs.rmSync(tmp, { force: true });
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
/* best-effort temp cleanup */
|
|
85
|
+
}
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
if (options.durable) {
|
|
89
|
+
try {
|
|
90
|
+
const dirFd = fs.openSync(path.dirname(file), "r");
|
|
91
|
+
try {
|
|
92
|
+
fs.fsyncSync(dirFd);
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
fs.closeSync(dirFd);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
/* directory fsync is best-effort (not supported on every platform) */
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/** Read + JSON.parse a file. Throws `File not found: <file>` when absent,
|
|
104
|
+
* `Invalid JSON in <file>: <message>` on a parse error. */
|
|
105
|
+
function readJson(file) {
|
|
106
|
+
if (!fs.existsSync(file))
|
|
107
|
+
throw new Error(`File not found: ${file}`);
|
|
108
|
+
try {
|
|
109
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
113
|
+
throw new Error(`Invalid JSON in ${file}: ${message}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
// Safe ids / names — src/state.ts:310-334 in the old build.
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
/** Replaces every run of chars outside `[a-zA-Z0-9_.:-]` with a single `_`. */
|
|
120
|
+
function safeFileName(value) {
|
|
121
|
+
return String(value).replace(/[^a-zA-Z0-9_.:-]+/g, "_");
|
|
122
|
+
}
|
|
123
|
+
/** Refuse a run id that is not a single safe path segment, so an id taken
|
|
124
|
+
* from an untrusted source (an imported run archive/bundle) can never
|
|
125
|
+
* escape the runs directory via a separator or a `..`/`.` component. The
|
|
126
|
+
* charset already forbids any separator, so the whole id is ONE path
|
|
127
|
+
* component; only the exact components `.` and `..` are refused — an
|
|
128
|
+
* EMBEDDED `..` (e.g. `v1..2`) is a safe directory name and is allowed. */
|
|
129
|
+
function assertSafeRunId(value, context = "run id") {
|
|
130
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
131
|
+
throw new Error(`Invalid ${context}: expected a non-empty string`);
|
|
132
|
+
}
|
|
133
|
+
if (!/^[A-Za-z0-9._:-]+$/.test(value) || value === "." || value === "..") {
|
|
134
|
+
throw new Error(`Unsafe ${context}: ${JSON.stringify(value)} must be a single path segment ([A-Za-z0-9._:-], not '.' or '..')`);
|
|
135
|
+
}
|
|
136
|
+
return value;
|
|
137
|
+
}
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
// Symlink-hardened path containment — src/state.ts:195-227 in the old build.
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
/** Realpath the deepest EXISTING ancestor (follows symlinks), then re-join
|
|
142
|
+
* the not-yet-created tail. If nothing exists up to the root, returns
|
|
143
|
+
* plain `path.resolve(target)`. */
|
|
144
|
+
function realResolve(target) {
|
|
145
|
+
let current = path.resolve(target);
|
|
146
|
+
const tail = [];
|
|
147
|
+
for (;;) {
|
|
148
|
+
try {
|
|
149
|
+
const real = fs.realpathSync.native ? fs.realpathSync.native(current) : fs.realpathSync(current);
|
|
150
|
+
return tail.length ? path.join(real, ...tail.reverse()) : real;
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
const parent = path.dirname(current);
|
|
154
|
+
if (parent === current)
|
|
155
|
+
return path.resolve(target);
|
|
156
|
+
tail.push(path.basename(current));
|
|
157
|
+
current = parent;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/** True when `realResolve(candidate)` equals `realResolve(allowed)` or
|
|
162
|
+
* starts with it plus `path.sep`. Realpaths BOTH sides so a symlinked temp
|
|
163
|
+
* root (macOS `/tmp` -> `/private/tmp`) compares right. */
|
|
164
|
+
function isContainedPath(candidate, allowed) {
|
|
165
|
+
const realCandidate = realResolve(candidate);
|
|
166
|
+
const realAllowed = realResolve(allowed);
|
|
167
|
+
return realCandidate === realAllowed || realCandidate.startsWith(realAllowed + path.sep);
|
|
168
|
+
}
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
// durableAppendFileSync — append a line and fsync it before returning. Used
|
|
171
|
+
// by the trust-audit event log, whose loss breaks audit-completeness.
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
function durableAppendFileSync(file, data) {
|
|
174
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
175
|
+
const fd = fs.openSync(file, "a");
|
|
176
|
+
try {
|
|
177
|
+
fs.writeFileSync(fd, data, "utf8");
|
|
178
|
+
fs.fsyncSync(fd);
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
fs.closeSync(fd);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
185
|
+
// withFileLock — portable advisory cross-process lock.
|
|
186
|
+
//
|
|
187
|
+
// Lock file: `<targetPath>.lock`, created with O_EXCL (`wx`), body
|
|
188
|
+
// `"<pid>@<ISO>\n"`. Up to 240 tries; on EEXIST, a lock whose mtime is older
|
|
189
|
+
// than FILE_LOCK_STALE_MS (30_000) is stolen (deleted) and retried AT ONCE;
|
|
190
|
+
// else sleep 25ms (Atomics.wait busy-safe sleep) and retry. Any non-EEXIST
|
|
191
|
+
// open error is rethrown. No lock after 240 tries throws
|
|
192
|
+
// `could not acquire file lock for <targetPath>`.
|
|
193
|
+
//
|
|
194
|
+
// Right before fn() the lock mtime is refreshed (utimesSync, best-effort).
|
|
195
|
+
// After fn() returns, the lock body is re-read: if it no longer starts with
|
|
196
|
+
// "<pid>@", the lock was stolen mid-operation -> throw the "stolen" error and
|
|
197
|
+
// do NOT delete the thief's lock. On the release path the lock is removed
|
|
198
|
+
// only when still owned by this pid.
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
const FILE_LOCK_STALE_MS = 30_000;
|
|
201
|
+
function sleepSync(ms) {
|
|
202
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
203
|
+
}
|
|
204
|
+
/** Run `fn` while holding an advisory lock for `targetPath`; always released
|
|
205
|
+
* (unless the lock was stolen mid-operation, in which case releasing would
|
|
206
|
+
* corrupt the thief's critical section, so it is deliberately NOT released). */
|
|
207
|
+
function withFileLock(targetPath, fn) {
|
|
208
|
+
const lock = `${targetPath}.lock`;
|
|
209
|
+
fs.mkdirSync(path.dirname(lock), { recursive: true });
|
|
210
|
+
const pid = String(process.pid);
|
|
211
|
+
let acquired = false;
|
|
212
|
+
for (let attempt = 0; attempt < 240 && !acquired; attempt++) {
|
|
213
|
+
try {
|
|
214
|
+
const fd = fs.openSync(lock, "wx");
|
|
215
|
+
fs.writeFileSync(fd, `${pid}@${new Date().toISOString()}\n`, "utf8");
|
|
216
|
+
fs.closeSync(fd);
|
|
217
|
+
acquired = true;
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
if (!(error && typeof error === "object" && error.code === "EEXIST")) {
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
if (Date.now() - fs.statSync(lock).mtimeMs > FILE_LOCK_STALE_MS) {
|
|
225
|
+
fs.rmSync(lock, { force: true });
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
sleepSync(25);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (!acquired)
|
|
236
|
+
throw new Error(`could not acquire file lock for ${targetPath}`);
|
|
237
|
+
// Refresh mtime right before the critical section.
|
|
238
|
+
try {
|
|
239
|
+
fs.utimesSync(lock, new Date(), new Date());
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
/* best-effort */
|
|
243
|
+
}
|
|
244
|
+
try {
|
|
245
|
+
const result = fn();
|
|
246
|
+
// Verify the lock was not stolen during fn(). If our pid is no longer at
|
|
247
|
+
// the front of the lock body, another process's stale-steal fired mid-
|
|
248
|
+
// operation and now owns the lock — do NOT release it (that would corrupt
|
|
249
|
+
// the thief's critical section).
|
|
250
|
+
try {
|
|
251
|
+
const current = fs.readFileSync(lock, "utf8");
|
|
252
|
+
if (!current.startsWith(pid + "@")) {
|
|
253
|
+
throw new Error(`File lock for ${targetPath} was stolen during the critical section ` +
|
|
254
|
+
`(lock now owned by another process). The operation may have lost ` +
|
|
255
|
+
`cross-process isolation — increase FILE_LOCK_STALE_MS or split the work.`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
catch (checkError) {
|
|
259
|
+
if (checkError instanceof Error && checkError.message.includes("stolen"))
|
|
260
|
+
throw checkError;
|
|
261
|
+
/* lock vanished — another process already released it, nothing to clean up */
|
|
262
|
+
}
|
|
263
|
+
return result;
|
|
264
|
+
}
|
|
265
|
+
finally {
|
|
266
|
+
try {
|
|
267
|
+
// Only release if we still own the lock.
|
|
268
|
+
const current = fs.readFileSync(lock, "utf8");
|
|
269
|
+
if (current.startsWith(pid + "@"))
|
|
270
|
+
fs.rmSync(lock, { force: true });
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
/* releasing a missing lock is fine */
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// shell/harness.ts — writeTaskFiles, renderTask (task file template).
|
|
3
|
+
//
|
|
4
|
+
// MILESTONE 6+7 (combined). Byte-exact port of the old build's
|
|
5
|
+
// src/harness.ts.
|
|
6
|
+
//
|
|
7
|
+
// Evidence: SPEC/pipeline-run.md "Task files — src/harness.ts", "Task
|
|
8
|
+
// file template (verbatim skeleton from renderTask)".
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.renderTask = renderTask;
|
|
44
|
+
exports.writeTaskFiles = writeTaskFiles;
|
|
45
|
+
const fs = __importStar(require("node:fs"));
|
|
46
|
+
const path = __importStar(require("node:path"));
|
|
47
|
+
const fs_atomic_1 = require("./fs-atomic");
|
|
48
|
+
function formatInputList(value) {
|
|
49
|
+
if (Array.isArray(value))
|
|
50
|
+
return value.join("; ");
|
|
51
|
+
return value ? String(value) : "";
|
|
52
|
+
}
|
|
53
|
+
function renderTask(run, task) {
|
|
54
|
+
return [
|
|
55
|
+
`# ${task.id}`,
|
|
56
|
+
"",
|
|
57
|
+
`- Workflow: ${run.workflow.title}`,
|
|
58
|
+
`- Run: ${run.id}`,
|
|
59
|
+
`- Phase: ${task.phase}`,
|
|
60
|
+
`- Kind: ${task.kind}`,
|
|
61
|
+
"",
|
|
62
|
+
"## Inputs",
|
|
63
|
+
"",
|
|
64
|
+
`- Repository: ${String(run.inputs.repo || run.cwd)}`,
|
|
65
|
+
`- Question: ${String(run.inputs.question || "")}`,
|
|
66
|
+
`- Invariants: ${formatInputList(run.inputs.invariant)}`,
|
|
67
|
+
"",
|
|
68
|
+
"## Task",
|
|
69
|
+
"",
|
|
70
|
+
task.prompt,
|
|
71
|
+
"",
|
|
72
|
+
"## Output Contract",
|
|
73
|
+
"",
|
|
74
|
+
"- Return a concise Markdown summary.",
|
|
75
|
+
"- Include concrete evidence paths and line numbers when applicable.",
|
|
76
|
+
"- Separate real, conditional, non-issue, and unknown findings when reviewing risk.",
|
|
77
|
+
"- For verification or verdict tasks, include a `cw:result` JSON fence with `findings` and `evidence`.",
|
|
78
|
+
"- Do not edit files unless the parent agent session explicitly assigned implementation work.",
|
|
79
|
+
"",
|
|
80
|
+
"```cw:result",
|
|
81
|
+
"{",
|
|
82
|
+
' "summary": "one sentence",',
|
|
83
|
+
' "findings": [',
|
|
84
|
+
' { "id": "finding-id", "classification": "real|conditional|non-issue|unknown", "severity": "P0|P1|P2|P3|none", "evidence": ["file-or-url:line"] }',
|
|
85
|
+
" ],",
|
|
86
|
+
' "evidence": ["file-or-url:line"]',
|
|
87
|
+
"}",
|
|
88
|
+
"```",
|
|
89
|
+
"",
|
|
90
|
+
].join("\n");
|
|
91
|
+
}
|
|
92
|
+
function writeTaskFiles(run) {
|
|
93
|
+
for (const task of run.tasks) {
|
|
94
|
+
const taskPath = path.join(run.paths.tasksDir, `${(0, fs_atomic_1.safeFileName)(task.id)}.md`);
|
|
95
|
+
task.taskPath = taskPath;
|
|
96
|
+
fs.writeFileSync(taskPath, renderTask(run, task), "utf8");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// shell/ledger-cli.ts — CLI/MCP-reachable bodies for `cw ledger
|
|
3
|
+
// propose|review|verify|apply|list`.
|
|
4
|
+
//
|
|
5
|
+
// MILESTONE 8. Byte-exact port of the old build's src/cli/handlers/
|
|
6
|
+
// ledger.ts argv shape, now calling core/trust/ledger.ts's pure
|
|
7
|
+
// functions + shell/ledger-io.ts's directory reads. Impure (fs: stdin/
|
|
8
|
+
// file reads for verify/apply, directory scans for list) — this is the
|
|
9
|
+
// shell layer the capability-table's CLI/MCP handlers delegate to.
|
|
10
|
+
//
|
|
11
|
+
// Evidence: SPEC/ledger-trust.md "CLI: `cw ledger`", "Edge cases";
|
|
12
|
+
// plugins/cool-workflow/src/cli/handlers/ledger.ts:1-133.
|
|
13
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
16
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
17
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
18
|
+
}
|
|
19
|
+
Object.defineProperty(o, k2, desc);
|
|
20
|
+
}) : (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
o[k2] = m[k];
|
|
23
|
+
}));
|
|
24
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
25
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
26
|
+
}) : function(o, v) {
|
|
27
|
+
o["default"] = v;
|
|
28
|
+
});
|
|
29
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
30
|
+
var ownKeys = function(o) {
|
|
31
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
32
|
+
var ar = [];
|
|
33
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
34
|
+
return ar;
|
|
35
|
+
};
|
|
36
|
+
return ownKeys(o);
|
|
37
|
+
};
|
|
38
|
+
return function (mod) {
|
|
39
|
+
if (mod && mod.__esModule) return mod;
|
|
40
|
+
var result = {};
|
|
41
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
42
|
+
__setModuleDefault(result, mod);
|
|
43
|
+
return result;
|
|
44
|
+
};
|
|
45
|
+
})();
|
|
46
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
+
exports.ledgerProposeCli = ledgerProposeCli;
|
|
48
|
+
exports.ledgerProposeMcp = ledgerProposeMcp;
|
|
49
|
+
exports.ledgerReviewCli = ledgerReviewCli;
|
|
50
|
+
exports.ledgerReviewMcp = ledgerReviewMcp;
|
|
51
|
+
exports.ledgerVerifyCli = ledgerVerifyCli;
|
|
52
|
+
exports.ledgerApplyCli = ledgerApplyCli;
|
|
53
|
+
exports.ledgerListCli = ledgerListCli;
|
|
54
|
+
exports.ledgerVerifyEntry = ledgerVerifyEntry;
|
|
55
|
+
exports.ledgerApplyEntry = ledgerApplyEntry;
|
|
56
|
+
exports.ledgerListMcp = ledgerListMcp;
|
|
57
|
+
const fs = __importStar(require("node:fs"));
|
|
58
|
+
const ledger_1 = require("../core/trust/ledger");
|
|
59
|
+
const ledger_io_1 = require("./ledger-io");
|
|
60
|
+
/** Coerce a repeatable/comma-joined list option to a clean string[]. */
|
|
61
|
+
function listOption(value) {
|
|
62
|
+
const parts = Array.isArray(value) ? value : typeof value === "string" ? value.split(",") : [];
|
|
63
|
+
return parts.map((p) => String(p).trim()).filter(Boolean);
|
|
64
|
+
}
|
|
65
|
+
function stringOption(value) {
|
|
66
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
67
|
+
}
|
|
68
|
+
function required(value, label) {
|
|
69
|
+
if (!value)
|
|
70
|
+
throw new Error(`${label} is required`);
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
function nowIso() {
|
|
74
|
+
return new Date().toISOString();
|
|
75
|
+
}
|
|
76
|
+
function ledgerProposeCli(options) {
|
|
77
|
+
return (0, ledger_1.buildLedgerProposal)({
|
|
78
|
+
from: required(stringOption(options.from), "--from <agent/repo>"),
|
|
79
|
+
to: required(stringOption(options.to), "--to <agent/repo>"),
|
|
80
|
+
title: required(stringOption(options.title), "--title <text>"),
|
|
81
|
+
rationale: required(stringOption(options.rationale), "--rationale <text>"),
|
|
82
|
+
targetFiles: listOption(options.files),
|
|
83
|
+
// Do NOT trim the diff: it is a unified patch (payload, not a
|
|
84
|
+
// label), and trimming strips the trailing newline `git apply`
|
|
85
|
+
// requires. Presence is detected with a trimmed test, but the bytes
|
|
86
|
+
// are passed through verbatim.
|
|
87
|
+
suggestedDiff: typeof options.diff === "string" && options.diff.trim() ? options.diff : undefined,
|
|
88
|
+
createdAt: nowIso(),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/** MCP-facing `cw_ledger_propose`: `files` is a comma string; `diff` is
|
|
92
|
+
* passed through as-is (undefined only when the arg itself is absent),
|
|
93
|
+
* a slightly looser shape than the CLI handler's flag-labeled
|
|
94
|
+
* requireds (byte-exact to the old build's src/mcp/tool-call.ts). */
|
|
95
|
+
function ledgerProposeMcp(args) {
|
|
96
|
+
return (0, ledger_1.buildLedgerProposal)({
|
|
97
|
+
from: String(args.from || ""),
|
|
98
|
+
to: String(args.to || ""),
|
|
99
|
+
title: String(args.title || ""),
|
|
100
|
+
rationale: String(args.rationale || ""),
|
|
101
|
+
targetFiles: String(args.files || "").split(",").map((f) => f.trim()).filter(Boolean),
|
|
102
|
+
suggestedDiff: args.diff === undefined ? undefined : String(args.diff),
|
|
103
|
+
createdAt: nowIso(),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
function ledgerReviewCli(options) {
|
|
107
|
+
const verdictRaw = required(stringOption(options.verdict), "--verdict <approved|rejected>").toUpperCase();
|
|
108
|
+
if (verdictRaw !== "APPROVED" && verdictRaw !== "REJECTED") {
|
|
109
|
+
throw new Error('--verdict must be "approved" or "rejected".');
|
|
110
|
+
}
|
|
111
|
+
return (0, ledger_1.buildLedgerReview)({
|
|
112
|
+
from: required(stringOption(options.from), "--from <agent/repo>"),
|
|
113
|
+
to: required(stringOption(options.to), "--to <agent/repo>"),
|
|
114
|
+
target: required(stringOption(options.target), "--target <proposal-id|pr-ref>"),
|
|
115
|
+
verdict: verdictRaw,
|
|
116
|
+
findings: listOption(options.findings),
|
|
117
|
+
createdAt: nowIso(),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/** MCP-facing `cw_ledger_review`: same verdict check, but WITHOUT the
|
|
121
|
+
* `--verdict` CLI-flag framing in the error message (byte-exact to the
|
|
122
|
+
* old build's src/mcp/tool-call.ts, a separate call site from the CLI
|
|
123
|
+
* handler's own message). */
|
|
124
|
+
function ledgerReviewMcp(args) {
|
|
125
|
+
const verdict = String(args.verdict || "").toUpperCase();
|
|
126
|
+
if (verdict !== "APPROVED" && verdict !== "REJECTED") {
|
|
127
|
+
throw new Error('verdict must be "approved" or "rejected".');
|
|
128
|
+
}
|
|
129
|
+
return (0, ledger_1.buildLedgerReview)({
|
|
130
|
+
from: String(args.from || ""),
|
|
131
|
+
to: String(args.to || ""),
|
|
132
|
+
target: String(args.target || ""),
|
|
133
|
+
verdict: verdict,
|
|
134
|
+
findings: String(args.findings || "").split(",").map((f) => f.trim()).filter(Boolean),
|
|
135
|
+
createdAt: nowIso(),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
const BAD_JSON_VERIFY = {
|
|
139
|
+
ok: false,
|
|
140
|
+
id: null,
|
|
141
|
+
kind: null,
|
|
142
|
+
checks: [{ name: "parse", pass: false, code: "ledger-bad-json" }],
|
|
143
|
+
failedChecks: [{ name: "parse", code: "ledger-bad-json" }],
|
|
144
|
+
};
|
|
145
|
+
const BAD_JSON_APPLY = {
|
|
146
|
+
ok: false,
|
|
147
|
+
id: null,
|
|
148
|
+
kind: null,
|
|
149
|
+
diff: null,
|
|
150
|
+
failedChecks: [{ name: "parse", code: "ledger-bad-json" }],
|
|
151
|
+
};
|
|
152
|
+
function readLedgerEntryInput(options) {
|
|
153
|
+
const file = stringOption(options.file);
|
|
154
|
+
try {
|
|
155
|
+
// --file <path>, else read the entry from stdin (fd 0).
|
|
156
|
+
return fs.readFileSync(file || 0, "utf8");
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
throw new Error(`Cannot read ledger entry${file ? ` from ${file}` : " from stdin"}: ${error.message}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function ledgerVerifyCli(options) {
|
|
163
|
+
let text;
|
|
164
|
+
try {
|
|
165
|
+
text = readLedgerEntryInput(options);
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
let parsed;
|
|
171
|
+
try {
|
|
172
|
+
parsed = JSON.parse(text);
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return BAD_JSON_VERIFY;
|
|
176
|
+
}
|
|
177
|
+
return (0, ledger_1.verifyLedgerEntry)(parsed);
|
|
178
|
+
}
|
|
179
|
+
function ledgerApplyCli(options) {
|
|
180
|
+
const text = readLedgerEntryInput(options);
|
|
181
|
+
let parsed;
|
|
182
|
+
try {
|
|
183
|
+
parsed = JSON.parse(text);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return BAD_JSON_APPLY;
|
|
187
|
+
}
|
|
188
|
+
return (0, ledger_1.applyLedgerProposal)(parsed);
|
|
189
|
+
}
|
|
190
|
+
function ledgerListCli(options) {
|
|
191
|
+
const dirs = Array.isArray(options.dir) ? options.dir.map(String).filter(Boolean) : [];
|
|
192
|
+
if (dirs.length > 1)
|
|
193
|
+
return (0, ledger_io_1.unionLedgerEntries)(dirs);
|
|
194
|
+
const dir = required(dirs[0] || stringOption(options.dir), "--dir <ledger-directory>");
|
|
195
|
+
return (0, ledger_io_1.listLedgerEntries)(dir);
|
|
196
|
+
}
|
|
197
|
+
/** MCP-facing verify/apply take the entry OBJECT directly (not a file/
|
|
198
|
+
* stdin path). */
|
|
199
|
+
function ledgerVerifyEntry(entry) {
|
|
200
|
+
return (0, ledger_1.verifyLedgerEntry)(entry);
|
|
201
|
+
}
|
|
202
|
+
function ledgerApplyEntry(entry) {
|
|
203
|
+
return (0, ledger_1.applyLedgerProposal)(entry);
|
|
204
|
+
}
|
|
205
|
+
function ledgerListMcp(args) {
|
|
206
|
+
const dirsArg = args.dirs;
|
|
207
|
+
const dirs = Array.isArray(dirsArg) ? dirsArg.map(String).filter(Boolean) : [];
|
|
208
|
+
if (dirs.length > 1)
|
|
209
|
+
return (0, ledger_io_1.unionLedgerEntries)(dirs);
|
|
210
|
+
const dir = required(dirs[0] || stringOption(args.dir), "dir");
|
|
211
|
+
return (0, ledger_io_1.listLedgerEntries)(dir);
|
|
212
|
+
}
|