@lsctech/polaris 0.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/README.md +87 -0
- package/dist/__mocks__/tool-server-linear.js +34 -0
- package/dist/cli/adopt-approve.js +60 -0
- package/dist/cli/adopt-cognition.js +199 -0
- package/dist/cli/adopt-instructions.js +143 -0
- package/dist/cli/adopt-rules.js +169 -0
- package/dist/cli/adopt-scan.js +25 -0
- package/dist/cli/adopt-smartdocs.js +192 -0
- package/dist/cli/adoption-inventory.js +350 -0
- package/dist/cli/adoption-plan.js +222 -0
- package/dist/cli/args.js +28 -0
- package/dist/cli/args.test.js +30 -0
- package/dist/cli/branding.js +41 -0
- package/dist/cli/graph.js +358 -0
- package/dist/cli/index.js +127 -0
- package/dist/cli/index.test.js +162 -0
- package/dist/cli/init-detect.js +61 -0
- package/dist/cli/init.js +492 -0
- package/dist/cli/init.test.js +259 -0
- package/dist/cli/librarian.js +32 -0
- package/dist/cli/medic.js +96 -0
- package/dist/cli/startup.test.js +23 -0
- package/dist/cli/tracker.js +130 -0
- package/dist/cli/tracker.test.js +126 -0
- package/dist/cli/version.js +10 -0
- package/dist/cli/version.test.js +11 -0
- package/dist/cli/worker.js +287 -0
- package/dist/cluster-state/fixtures/test-clusters.json +19 -0
- package/dist/cluster-state/store.js +302 -0
- package/dist/cluster-state/store.test.js +145 -0
- package/dist/cluster-state/types.js +2 -0
- package/dist/cognition/archive.js +158 -0
- package/dist/cognition/closeout-librarian-types.js +161 -0
- package/dist/cognition/cognition.test.js +623 -0
- package/dist/cognition/index.js +41 -0
- package/dist/cognition/librarian-dispatch.js +391 -0
- package/dist/cognition/librarian-dispatch.test.js +247 -0
- package/dist/cognition/librarian-packet.js +313 -0
- package/dist/cognition/librarian-types.js +57 -0
- package/dist/cognition/route-cognition-delta.js +350 -0
- package/dist/cognition/summary-delta.js +279 -0
- package/dist/cognition/validate.js +162 -0
- package/dist/config/defaults.js +98 -0
- package/dist/config/doctor.js +273 -0
- package/dist/config/index.js +14 -0
- package/dist/config/loader.js +58 -0
- package/dist/config/provider-detect.js +60 -0
- package/dist/config/provider-detect.test.js +119 -0
- package/dist/config/schema.js +2 -0
- package/dist/config/show.js +44 -0
- package/dist/config/show.test.js +42 -0
- package/dist/config/validator.js +675 -0
- package/dist/config/validator.test.js +77 -0
- package/dist/docs/canon-check.js +334 -0
- package/dist/docs/canon-check.test.js +207 -0
- package/dist/docs/doctrine.js +133 -0
- package/dist/docs/doctrine.test.js +139 -0
- package/dist/docs/index.js +168 -0
- package/dist/docs/index.test.js +83 -0
- package/dist/docs/ingest.js +295 -0
- package/dist/docs/ingest.test.js +71 -0
- package/dist/docs/migrate.js +165 -0
- package/dist/docs/migrate.test.js +136 -0
- package/dist/docs/seed-instructions.js +147 -0
- package/dist/docs/seed-instructions.test.js +137 -0
- package/dist/docs/validate-instructions.js +267 -0
- package/dist/docs/validate-instructions.test.js +238 -0
- package/dist/finalize/artifact-policy.js +214 -0
- package/dist/finalize/artifact-policy.test.js +84 -0
- package/dist/finalize/delivery-integrity.js +141 -0
- package/dist/finalize/finalize.test.js +210 -0
- package/dist/finalize/github.js +41 -0
- package/dist/finalize/index.js +474 -0
- package/dist/finalize/linear.js +213 -0
- package/dist/finalize/run-report.js +47 -0
- package/dist/finalize/steps/01-map-update.js +11 -0
- package/dist/finalize/steps/02-map-validate.js +11 -0
- package/dist/finalize/steps/03-schema-validate.js +11 -0
- package/dist/finalize/steps/04-run-checks.js +118 -0
- package/dist/finalize/steps/05-generate-report.js +14 -0
- package/dist/finalize/steps/06-commit.js +39 -0
- package/dist/finalize/steps/07-push.js +10 -0
- package/dist/finalize/steps/08-create-pr.js +9 -0
- package/dist/finalize/steps/09-update-state.js +9 -0
- package/dist/finalize/steps/10-append-jsonl.js +23 -0
- package/dist/finalize/steps/11-update-linear.js +22 -0
- package/dist/finalize/steps/12-archive.js +21 -0
- package/dist/graph/adapter/c/adapter.js +45 -0
- package/dist/graph/adapter/c/extract.js +135 -0
- package/dist/graph/adapter/c/index.js +10 -0
- package/dist/graph/adapter/c/runtime.js +67 -0
- package/dist/graph/adapter/cpp/adapter.js +45 -0
- package/dist/graph/adapter/cpp/extract.js +159 -0
- package/dist/graph/adapter/cpp/index.js +10 -0
- package/dist/graph/adapter/cpp/runtime.js +61 -0
- package/dist/graph/adapter/csharp/adapter.js +45 -0
- package/dist/graph/adapter/csharp/extract.js +127 -0
- package/dist/graph/adapter/csharp/index.js +10 -0
- package/dist/graph/adapter/csharp/runtime.js +61 -0
- package/dist/graph/adapter/dart/adapter.js +56 -0
- package/dist/graph/adapter/dart/extract.js +130 -0
- package/dist/graph/adapter/dart/index.js +10 -0
- package/dist/graph/adapter/dart/runtime.js +61 -0
- package/dist/graph/adapter/go/adapter.js +45 -0
- package/dist/graph/adapter/go/extract.js +112 -0
- package/dist/graph/adapter/go/index.js +10 -0
- package/dist/graph/adapter/go/runtime.js +58 -0
- package/dist/graph/adapter/kotlin-java/extract-common.js +83 -0
- package/dist/graph/adapter/kotlin-java/index.js +15 -0
- package/dist/graph/adapter/kotlin-java/java-adapter.js +45 -0
- package/dist/graph/adapter/kotlin-java/java-extract.js +42 -0
- package/dist/graph/adapter/kotlin-java/kotlin-adapter.js +48 -0
- package/dist/graph/adapter/kotlin-java/kotlin-extract.js +58 -0
- package/dist/graph/adapter/kotlin-java/runtime.js +75 -0
- package/dist/graph/adapter/python/adapter.js +45 -0
- package/dist/graph/adapter/python/extract.js +121 -0
- package/dist/graph/adapter/python/index.js +10 -0
- package/dist/graph/adapter/python/runtime.js +70 -0
- package/dist/graph/adapter/registry.js +82 -0
- package/dist/graph/adapter/rust/adapter.js +45 -0
- package/dist/graph/adapter/rust/extract.js +110 -0
- package/dist/graph/adapter/rust/index.js +10 -0
- package/dist/graph/adapter/rust/runtime.js +74 -0
- package/dist/graph/adapter/shell/adapter.js +34 -0
- package/dist/graph/adapter/shell/extract.js +98 -0
- package/dist/graph/adapter/shell/index.js +8 -0
- package/dist/graph/adapter/svelte/adapter.js +91 -0
- package/dist/graph/adapter/svelte/extract.js +41 -0
- package/dist/graph/adapter/svelte/index.js +8 -0
- package/dist/graph/adapter/swift/adapter.js +45 -0
- package/dist/graph/adapter/swift/extract.js +120 -0
- package/dist/graph/adapter/swift/index.js +10 -0
- package/dist/graph/adapter/swift/runtime.js +70 -0
- package/dist/graph/adapter/types.js +2 -0
- package/dist/graph/adapter/typescript-javascript/adapter.js +72 -0
- package/dist/graph/adapter/typescript-javascript/extract.js +115 -0
- package/dist/graph/adapter/typescript-javascript/index.js +10 -0
- package/dist/graph/adapter/typescript-javascript/runtime.js +97 -0
- package/dist/graph/capability/index.js +105 -0
- package/dist/graph/governance.js +104 -0
- package/dist/graph/parser/extract.js +5 -0
- package/dist/graph/parser/loader.js +2 -0
- package/dist/graph/parser/pipeline.js +194 -0
- package/dist/graph/query/index.js +277 -0
- package/dist/graph/query/types.js +5 -0
- package/dist/graph/resolver/build-edges.js +211 -0
- package/dist/graph/resolver/index.js +23 -0
- package/dist/graph/resolver/resolve-imports.js +168 -0
- package/dist/graph/store/adapter.js +132 -0
- package/dist/graph/store/queries.js +124 -0
- package/dist/graph/store/types.js +4 -0
- package/dist/ignore/defaults.js +41 -0
- package/dist/ignore/parser.js +39 -0
- package/dist/lint/map-reference-validator.js +72 -0
- package/dist/loop/abort.js +188 -0
- package/dist/loop/abort.test.js +151 -0
- package/dist/loop/adapters/__tests__/terminal-cli.test.js +92 -0
- package/dist/loop/adapters/agent-subtask.js +137 -0
- package/dist/loop/adapters/agent-subtask.test.js +151 -0
- package/dist/loop/adapters/cli-subtask-bridge.js +108 -0
- package/dist/loop/adapters/index.js +9 -0
- package/dist/loop/adapters/registry.js +17 -0
- package/dist/loop/adapters/terminal-cli.js +403 -0
- package/dist/loop/adapters/types.js +2 -0
- package/dist/loop/adapters/worker-instructions.js +85 -0
- package/dist/loop/body-parser.js +226 -0
- package/dist/loop/bootstrap-packet.js +97 -0
- package/dist/loop/bootstrap-packet.test.js +171 -0
- package/dist/loop/branch-governance.js +54 -0
- package/dist/loop/budget.js +63 -0
- package/dist/loop/budget.test.js +163 -0
- package/dist/loop/checkpoint.js +325 -0
- package/dist/loop/compact-return.js +59 -0
- package/dist/loop/continue.js +645 -0
- package/dist/loop/continue.test.js +337 -0
- package/dist/loop/dispatch-boundary.js +335 -0
- package/dist/loop/dispatch-state.js +223 -0
- package/dist/loop/dispatch-state.test.js +186 -0
- package/dist/loop/dispatch.js +1236 -0
- package/dist/loop/dispatch.test.js +499 -0
- package/dist/loop/ephemeral-smoke.test.js +160 -0
- package/dist/loop/evidence-backfill.js +216 -0
- package/dist/loop/execution-adapter.js +141 -0
- package/dist/loop/execution-adapter.test.js +66 -0
- package/dist/loop/finalize-evidence.js +169 -0
- package/dist/loop/git-custody.js +250 -0
- package/dist/loop/impl-packet-input.js +38 -0
- package/dist/loop/index.js +270 -0
- package/dist/loop/ledger.js +49 -0
- package/dist/loop/ledger.test.js +127 -0
- package/dist/loop/lifecycle-dispatch.js +348 -0
- package/dist/loop/lifecycle-dispatch.test.js +318 -0
- package/dist/loop/lifecycle.js +93 -0
- package/dist/loop/lifecycle.test.js +150 -0
- package/dist/loop/orphan-recovery.js +276 -0
- package/dist/loop/orphan-recovery.test.js +236 -0
- package/dist/loop/parent.js +1566 -0
- package/dist/loop/parent.test.js +738 -0
- package/dist/loop/provider-sealed-packet.test.js +164 -0
- package/dist/loop/resume.js +295 -0
- package/dist/loop/resume.test.js +172 -0
- package/dist/loop/run-bootstrap.js +234 -0
- package/dist/loop/run-preflight.js +203 -0
- package/dist/loop/status.js +665 -0
- package/dist/loop/status.test.js +235 -0
- package/dist/loop/wait.js +86 -0
- package/dist/loop/worker-packet.js +406 -0
- package/dist/loop/worker-packet.test.js +243 -0
- package/dist/loop/worker-prompt.js +260 -0
- package/dist/loop/worker-prompt.test.js +247 -0
- package/dist/loop/worker.js +498 -0
- package/dist/loop/worker.test.js +372 -0
- package/dist/map/atlas.js +77 -0
- package/dist/map/backfill.js +171 -0
- package/dist/map/backfill.test.js +120 -0
- package/dist/map/index.js +255 -0
- package/dist/map/inference.js +73 -0
- package/dist/map/inference.test.js +35 -0
- package/dist/map/query.js +157 -0
- package/dist/map/query.test.js +174 -0
- package/dist/map/update.js +355 -0
- package/dist/map/update.test.js +67 -0
- package/dist/map/validate.js +153 -0
- package/dist/map/validate.test.js +90 -0
- package/dist/map/welfare.js +86 -0
- package/dist/mcp/lib/invoke.js +35 -0
- package/dist/mcp/lib/redact.js +24 -0
- package/dist/mcp/lib/redact.test.js +112 -0
- package/dist/mcp/lib/root.js +41 -0
- package/dist/mcp/lib/root.test.js +114 -0
- package/dist/mcp/server.js +15 -0
- package/dist/mcp/tools/claim-child.js +119 -0
- package/dist/mcp/tools/claim-child.test.js +69 -0
- package/dist/mcp/tools/current-state.js +73 -0
- package/dist/mcp/tools/current-state.test.js +114 -0
- package/dist/mcp/tools/dispatch-result.js +186 -0
- package/dist/mcp/tools/dispatch-result.test.js +109 -0
- package/dist/mcp/tools/index.js +222 -0
- package/dist/mcp/tools/index.test.js +172 -0
- package/dist/mcp/tools/linear.js +74 -0
- package/dist/mcp/tools/loop-continue.js +159 -0
- package/dist/mcp/tools/loop-continue.test.js +186 -0
- package/dist/mcp/tools/loop-dry-run.js +15 -0
- package/dist/mcp/tools/status.js +65 -0
- package/dist/medic/chart-id.js +75 -0
- package/dist/medic/chart-schema.js +139 -0
- package/dist/runs/index.js +119 -0
- package/dist/runs/index.test.js +184 -0
- package/dist/runs/ledger-tail.js +15 -0
- package/dist/runs/list.js +29 -0
- package/dist/runs/reconcile.js +74 -0
- package/dist/runs/show.js +10 -0
- package/dist/runtime/audit/logger.js +15 -0
- package/dist/runtime/audit/reader.js +42 -0
- package/dist/runtime/checkpoint.js +89 -0
- package/dist/runtime/continuation/confirmed.js +225 -0
- package/dist/runtime/continuation/confirmed.test.js +421 -0
- package/dist/runtime/continuation/dry-run.js +74 -0
- package/dist/runtime/execution-window.js +106 -0
- package/dist/runtime/scheduling/child-selector.js +16 -0
- package/dist/runtime/state.js +43 -0
- package/dist/runtime/status.js +46 -0
- package/dist/runtime/verification/envelope.js +80 -0
- package/dist/runtime/verification/fingerprint.js +26 -0
- package/dist/skill-packet/cli.test.js +105 -0
- package/dist/skill-packet/generator.js +225 -0
- package/dist/skill-packet/generator.test.js +159 -0
- package/dist/skill-packet/index.js +49 -0
- package/dist/skill-packet/types.js +2 -0
- package/dist/smartdocs-engine/audit.js +194 -0
- package/dist/smartdocs-engine/audit.test.js +122 -0
- package/dist/smartdocs-engine/canon-check.js +340 -0
- package/dist/smartdocs-engine/canon-check.test.js +234 -0
- package/dist/smartdocs-engine/doctrine.js +633 -0
- package/dist/smartdocs-engine/doctrine.test.js +374 -0
- package/dist/smartdocs-engine/index.js +456 -0
- package/dist/smartdocs-engine/index.test.js +82 -0
- package/dist/smartdocs-engine/ingest.js +495 -0
- package/dist/smartdocs-engine/ingest.test.js +163 -0
- package/dist/smartdocs-engine/migrate.js +217 -0
- package/dist/smartdocs-engine/migrate.test.js +200 -0
- package/dist/smartdocs-engine/seed-instructions.js +335 -0
- package/dist/smartdocs-engine/seed-instructions.test.js +275 -0
- package/dist/smartdocs-engine/smartdoc-ignore.js +258 -0
- package/dist/smartdocs-engine/smartdoc-ignore.test.js +146 -0
- package/dist/smartdocs-engine/validate-instructions.js +366 -0
- package/dist/smartdocs-engine/validate-instructions.test.js +283 -0
- package/dist/tracker/adapters/github/index.js +305 -0
- package/dist/tracker/adapters/jira/index.js +293 -0
- package/dist/tracker/adapters/linear/http-errors.test.js +51 -0
- package/dist/tracker/adapters/linear/index.js +621 -0
- package/dist/tracker/adapters/linear/index.test.js +238 -0
- package/dist/tracker/adapters/linear.js +50 -0
- package/dist/tracker/adapters/local/index.js +10 -0
- package/dist/tracker/adapters/local-file/index.js +159 -0
- package/dist/tracker/adapters/mcp-bridge.js +158 -0
- package/dist/tracker/adapters/mcp-bridge.test.js +12 -0
- package/dist/tracker/adapters/spec/index.js +146 -0
- package/dist/tracker/capabilities.js +2 -0
- package/dist/tracker/index.js +49 -0
- package/dist/tracker/lifecycle-policy.js +139 -0
- package/dist/tracker/lifecycle-transition.js +164 -0
- package/dist/tracker/local-graph.js +98 -0
- package/dist/tracker/migration.js +56 -0
- package/dist/tracker/schema.js +86 -0
- package/dist/tracker/sync/index.js +404 -0
- package/dist/tracker/sync/queue-store.js +95 -0
- package/dist/tracker/sync/queue-store.test.js +104 -0
- package/dist/tracker/types.js +11 -0
- package/dist/types/linear.js +3 -0
- package/dist/types/result-packet.js +12 -0
- package/dist/types/runtime-state.js +2 -0
- package/dist/types/work-contract.js +2 -0
- package/dist/utils/monotonic-timestamp.js +13 -0
- package/dist/utils/monotonic-timestamp.test.js +19 -0
- package/package.json +64 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveLedgerPath = resolveLedgerPath;
|
|
4
|
+
exports.resolveStateFile = resolveStateFile;
|
|
5
|
+
exports.readLedger = readLedger;
|
|
6
|
+
exports.createRunsCommand = createRunsCommand;
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_path_1 = require("node:path");
|
|
9
|
+
const commander_1 = require("commander");
|
|
10
|
+
const ledger_js_1 = require("../loop/ledger.js");
|
|
11
|
+
const list_js_1 = require("./list.js");
|
|
12
|
+
const show_js_1 = require("./show.js");
|
|
13
|
+
const ledger_tail_js_1 = require("./ledger-tail.js");
|
|
14
|
+
const reconcile_js_1 = require("./reconcile.js");
|
|
15
|
+
function resolveLedgerPath(repoRoot) {
|
|
16
|
+
return (0, node_path_1.resolve)(repoRoot, ledger_js_1.DEFAULT_LEDGER_PATH);
|
|
17
|
+
}
|
|
18
|
+
function resolveStateFile(repoRoot) {
|
|
19
|
+
const taskchainPath = (0, node_path_1.join)(repoRoot, ".taskchain_artifacts", "polaris-run", "current-state.json");
|
|
20
|
+
const polarisPath = (0, node_path_1.join)(repoRoot, ".polaris", "runs", "current-state.json");
|
|
21
|
+
if ((0, node_fs_1.existsSync)(taskchainPath))
|
|
22
|
+
return taskchainPath;
|
|
23
|
+
if ((0, node_fs_1.existsSync)(polarisPath))
|
|
24
|
+
return polarisPath;
|
|
25
|
+
return taskchainPath;
|
|
26
|
+
}
|
|
27
|
+
function readLedger(repoRoot) {
|
|
28
|
+
const ledgerPath = resolveLedgerPath(repoRoot);
|
|
29
|
+
if (!(0, node_fs_1.existsSync)(ledgerPath)) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const raw = (0, node_fs_1.readFileSync)(ledgerPath, "utf-8");
|
|
33
|
+
const rawLines = raw.split(/\r?\n/).filter((line) => line.length > 0);
|
|
34
|
+
const events = [];
|
|
35
|
+
for (let i = 0; i < rawLines.length; i++) {
|
|
36
|
+
try {
|
|
37
|
+
events.push(JSON.parse(rawLines[i]));
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
41
|
+
console.error(`Warning: skipping malformed ledger line ${i + 1} in ${ledgerPath}: ${msg}\nContent: ${rawLines[i]}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
ledgerPath,
|
|
46
|
+
rawLines,
|
|
47
|
+
events,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function noLedgerFound() {
|
|
51
|
+
process.stdout.write("no ledger found\n");
|
|
52
|
+
}
|
|
53
|
+
function createRunsCommand(options) {
|
|
54
|
+
const runs = new commander_1.Command("runs")
|
|
55
|
+
.description("safe/read-only: inspect Polaris global run ledger")
|
|
56
|
+
.showHelpAfterError()
|
|
57
|
+
.showSuggestionAfterError()
|
|
58
|
+
.action(() => {
|
|
59
|
+
runs.outputHelp();
|
|
60
|
+
});
|
|
61
|
+
runs
|
|
62
|
+
.command("list")
|
|
63
|
+
.description("safe/read-only: list runs from the global ledger")
|
|
64
|
+
.option("--open", "Only show runs whose latest status is not complete or finalized")
|
|
65
|
+
.action((commandOptions) => {
|
|
66
|
+
const ledger = readLedger(options.repoRoot);
|
|
67
|
+
if (!ledger) {
|
|
68
|
+
noLedgerFound();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
(0, list_js_1.runRunsList)(ledger.events, { open: Boolean(commandOptions.open) });
|
|
72
|
+
});
|
|
73
|
+
runs
|
|
74
|
+
.command("show")
|
|
75
|
+
.description("safe/read-only: show all ledger events for a run")
|
|
76
|
+
.argument("<run-id>", "Run ID to inspect")
|
|
77
|
+
.action((runId) => {
|
|
78
|
+
const ledger = readLedger(options.repoRoot);
|
|
79
|
+
if (!ledger) {
|
|
80
|
+
noLedgerFound();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
(0, show_js_1.runRunsShow)(ledger.events, runId);
|
|
84
|
+
});
|
|
85
|
+
const ledger = runs
|
|
86
|
+
.command("ledger")
|
|
87
|
+
.description("safe/read-only: inspect raw ledger storage")
|
|
88
|
+
.showHelpAfterError()
|
|
89
|
+
.action(() => {
|
|
90
|
+
ledger.outputHelp();
|
|
91
|
+
});
|
|
92
|
+
ledger
|
|
93
|
+
.command("tail")
|
|
94
|
+
.description("safe/read-only: print the last N raw ledger lines")
|
|
95
|
+
.option("--n <count>", "Number of lines to print", "20")
|
|
96
|
+
.action((commandOptions) => {
|
|
97
|
+
const ledgerData = readLedger(options.repoRoot);
|
|
98
|
+
if (!ledgerData) {
|
|
99
|
+
noLedgerFound();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
(0, ledger_tail_js_1.runRunsLedgerTail)(ledgerData.rawLines, commandOptions.n);
|
|
103
|
+
});
|
|
104
|
+
runs
|
|
105
|
+
.command("reconcile")
|
|
106
|
+
.description("safe/read-only: report divergence between ledger and current-state.json")
|
|
107
|
+
.action(() => {
|
|
108
|
+
const ledgerData = readLedger(options.repoRoot);
|
|
109
|
+
if (!ledgerData) {
|
|
110
|
+
noLedgerFound();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
(0, reconcile_js_1.runRunsReconcile)(ledgerData.events, {
|
|
114
|
+
repoRoot: options.repoRoot,
|
|
115
|
+
stateFile: resolveStateFile(options.repoRoot),
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
return runs;
|
|
119
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const node_fs_1 = require("node:fs");
|
|
4
|
+
const node_os_1 = require("node:os");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const vitest_1 = require("vitest");
|
|
7
|
+
const index_js_1 = require("../cli/index.js");
|
|
8
|
+
const stdout = vitest_1.vi.spyOn(process.stdout, "write").mockImplementation(() => true);
|
|
9
|
+
const stderr = vitest_1.vi.spyOn(process.stderr, "write").mockImplementation(() => true);
|
|
10
|
+
(0, vitest_1.afterEach)(() => {
|
|
11
|
+
stdout.mockClear();
|
|
12
|
+
stderr.mockClear();
|
|
13
|
+
});
|
|
14
|
+
function repoWithLedger(events) {
|
|
15
|
+
const repoRoot = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "polaris-runs-"));
|
|
16
|
+
if (events) {
|
|
17
|
+
const ledgerDir = (0, node_path_1.join)(repoRoot, ".polaris", "runs");
|
|
18
|
+
(0, node_fs_1.mkdirSync)(ledgerDir, { recursive: true });
|
|
19
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(ledgerDir, "ledger.jsonl"), events.map((event) => JSON.stringify(event)).join("\n") + "\n");
|
|
20
|
+
}
|
|
21
|
+
return repoRoot;
|
|
22
|
+
}
|
|
23
|
+
function event(overrides) {
|
|
24
|
+
return {
|
|
25
|
+
schema_version: 1,
|
|
26
|
+
event_id: overrides.event_id ?? "event-1",
|
|
27
|
+
event: overrides.event ?? "run-started",
|
|
28
|
+
run_id: overrides.run_id ?? "run-1",
|
|
29
|
+
run_type: overrides.run_type ?? "implement",
|
|
30
|
+
cluster_id: overrides.cluster_id ?? "POL-1",
|
|
31
|
+
issue_id: overrides.issue_id ?? null,
|
|
32
|
+
branch: overrides.branch ?? "feature/run-1",
|
|
33
|
+
status: overrides.status ?? "running",
|
|
34
|
+
completed_children: overrides.completed_children ?? [],
|
|
35
|
+
open_children: overrides.open_children ?? [],
|
|
36
|
+
next_child: overrides.next_child ?? null,
|
|
37
|
+
last_commit: overrides.last_commit ?? null,
|
|
38
|
+
pr_url: overrides.pr_url ?? null,
|
|
39
|
+
timestamp: overrides.timestamp ?? "2026-05-28T00:00:00.000Z",
|
|
40
|
+
...overrides,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
async function run(args, repoRoot) {
|
|
44
|
+
const program = (0, index_js_1.createPolarisCommand)({ repoRoot });
|
|
45
|
+
let exitCode = 0;
|
|
46
|
+
program.exitOverride();
|
|
47
|
+
try {
|
|
48
|
+
await program.parseAsync(["node", "polaris", ...args], { from: "node" });
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (error instanceof Error && "exitCode" in error) {
|
|
52
|
+
exitCode = Number(error.exitCode);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
exitCode,
|
|
60
|
+
output: stdout.mock.calls.map((call) => String(call[0])).join(""),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
(0, vitest_1.describe)("polaris runs", () => {
|
|
64
|
+
(0, vitest_1.it)("lists the latest row for each run with required columns", async () => {
|
|
65
|
+
const repoRoot = repoWithLedger([
|
|
66
|
+
event({ run_id: "run-a", event_id: "a-1", status: "running" }),
|
|
67
|
+
event({
|
|
68
|
+
run_id: "run-a",
|
|
69
|
+
event_id: "a-2",
|
|
70
|
+
status: "cluster-complete",
|
|
71
|
+
timestamp: "2026-05-28T00:05:00.000Z",
|
|
72
|
+
}),
|
|
73
|
+
event({
|
|
74
|
+
run_id: "run-b",
|
|
75
|
+
event_id: "b-1",
|
|
76
|
+
run_type: "analyze",
|
|
77
|
+
cluster_id: "POL-2",
|
|
78
|
+
branch: "feature/run-2",
|
|
79
|
+
status: "finalized",
|
|
80
|
+
timestamp: "2026-05-28T00:07:00.000Z",
|
|
81
|
+
}),
|
|
82
|
+
]);
|
|
83
|
+
const result = await run(["runs", "list"], repoRoot);
|
|
84
|
+
(0, vitest_1.expect)(result.exitCode).toBe(0);
|
|
85
|
+
(0, vitest_1.expect)(result.output).toContain("run_id");
|
|
86
|
+
(0, vitest_1.expect)(result.output).toContain("run_type");
|
|
87
|
+
(0, vitest_1.expect)(result.output).toContain("cluster_id");
|
|
88
|
+
(0, vitest_1.expect)(result.output).toContain("branch");
|
|
89
|
+
(0, vitest_1.expect)(result.output).toContain("status");
|
|
90
|
+
(0, vitest_1.expect)(result.output).toContain("last event timestamp");
|
|
91
|
+
(0, vitest_1.expect)(result.output).toContain("run-a");
|
|
92
|
+
(0, vitest_1.expect)(result.output).toContain("cluster-complete");
|
|
93
|
+
(0, vitest_1.expect)(result.output).toContain("run-b");
|
|
94
|
+
(0, vitest_1.expect)(result.output).toContain("finalized");
|
|
95
|
+
});
|
|
96
|
+
(0, vitest_1.it)("filters list --open to runs not complete or finalized", async () => {
|
|
97
|
+
const repoRoot = repoWithLedger([
|
|
98
|
+
event({ run_id: "open-run", status: "running" }),
|
|
99
|
+
event({ run_id: "complete-run", status: "complete" }),
|
|
100
|
+
event({ run_id: "finalized-run", status: "finalized" }),
|
|
101
|
+
]);
|
|
102
|
+
const result = await run(["runs", "list", "--open"], repoRoot);
|
|
103
|
+
(0, vitest_1.expect)(result.exitCode).toBe(0);
|
|
104
|
+
(0, vitest_1.expect)(result.output).toContain("open-run");
|
|
105
|
+
(0, vitest_1.expect)(result.output).not.toContain("complete-run");
|
|
106
|
+
(0, vitest_1.expect)(result.output).not.toContain("finalized-run");
|
|
107
|
+
});
|
|
108
|
+
(0, vitest_1.it)("shows events for a run in chronological order", async () => {
|
|
109
|
+
const repoRoot = repoWithLedger([
|
|
110
|
+
event({
|
|
111
|
+
run_id: "run-a",
|
|
112
|
+
event_id: "late",
|
|
113
|
+
event: "child-completed",
|
|
114
|
+
timestamp: "2026-05-28T00:05:00.000Z",
|
|
115
|
+
issue_id: "POL-2",
|
|
116
|
+
last_commit: "abc123",
|
|
117
|
+
validation: { status: "passed" },
|
|
118
|
+
}),
|
|
119
|
+
event({
|
|
120
|
+
run_id: "run-a",
|
|
121
|
+
event_id: "early",
|
|
122
|
+
event: "run-started",
|
|
123
|
+
timestamp: "2026-05-28T00:01:00.000Z",
|
|
124
|
+
}),
|
|
125
|
+
event({ run_id: "run-b", event_id: "other" }),
|
|
126
|
+
]);
|
|
127
|
+
const result = await run(["runs", "show", "run-a"], repoRoot);
|
|
128
|
+
(0, vitest_1.expect)(result.exitCode).toBe(0);
|
|
129
|
+
(0, vitest_1.expect)(result.output.indexOf('"event_id":"early"')).toBeLessThan(result.output.indexOf('"event_id":"late"'));
|
|
130
|
+
(0, vitest_1.expect)(result.output).not.toContain('"event_id":"other"');
|
|
131
|
+
});
|
|
132
|
+
(0, vitest_1.it)("tails the last N raw ledger lines", async () => {
|
|
133
|
+
const repoRoot = repoWithLedger([
|
|
134
|
+
event({ event_id: "one" }),
|
|
135
|
+
event({ event_id: "two" }),
|
|
136
|
+
event({ event_id: "three" }),
|
|
137
|
+
]);
|
|
138
|
+
const result = await run(["runs", "ledger", "tail", "--n", "2"], repoRoot);
|
|
139
|
+
(0, vitest_1.expect)(result.exitCode).toBe(0);
|
|
140
|
+
(0, vitest_1.expect)(result.output).not.toContain('"event_id":"one"');
|
|
141
|
+
(0, vitest_1.expect)(result.output).toContain('"event_id":"two"');
|
|
142
|
+
(0, vitest_1.expect)(result.output).toContain('"event_id":"three"');
|
|
143
|
+
});
|
|
144
|
+
(0, vitest_1.it)("reports reconcile divergence without mutating state", async () => {
|
|
145
|
+
const repoRoot = repoWithLedger([
|
|
146
|
+
event({
|
|
147
|
+
run_id: "run-a",
|
|
148
|
+
cluster_id: "POL-1",
|
|
149
|
+
branch: "feature/ledger",
|
|
150
|
+
status: "running",
|
|
151
|
+
completed_children: [],
|
|
152
|
+
open_children: ["POL-2"],
|
|
153
|
+
next_child: "POL-2",
|
|
154
|
+
last_commit: "ledger-commit",
|
|
155
|
+
}),
|
|
156
|
+
]);
|
|
157
|
+
const statePath = (0, node_path_1.join)(repoRoot, ".taskchain_artifacts", "polaris-run", "current-state.json");
|
|
158
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(repoRoot, ".taskchain_artifacts", "polaris-run"), { recursive: true });
|
|
159
|
+
const state = {
|
|
160
|
+
run_id: "run-a",
|
|
161
|
+
cluster_id: "POL-1",
|
|
162
|
+
branch: "feature/state",
|
|
163
|
+
status: "running",
|
|
164
|
+
completed_children: ["POL-1"],
|
|
165
|
+
open_children: [],
|
|
166
|
+
next_open_child: null,
|
|
167
|
+
last_commit: "state-commit",
|
|
168
|
+
};
|
|
169
|
+
(0, node_fs_1.writeFileSync)(statePath, JSON.stringify(state, null, 2));
|
|
170
|
+
const result = await run(["runs", "reconcile"], repoRoot);
|
|
171
|
+
(0, vitest_1.expect)(result.exitCode).toBe(0);
|
|
172
|
+
(0, vitest_1.expect)(result.output).toContain("divergence");
|
|
173
|
+
(0, vitest_1.expect)(result.output).toContain("branch");
|
|
174
|
+
(0, vitest_1.expect)(result.output).toContain("completed_children");
|
|
175
|
+
(0, vitest_1.expect)(result.output).toContain("read-only");
|
|
176
|
+
(0, vitest_1.expect)(JSON.parse((0, node_fs_1.readFileSync)(statePath, "utf-8"))).toEqual(state);
|
|
177
|
+
});
|
|
178
|
+
(0, vitest_1.it)("exits cleanly when the ledger is absent", async () => {
|
|
179
|
+
const repoRoot = repoWithLedger();
|
|
180
|
+
const result = await run(["runs", "list"], repoRoot);
|
|
181
|
+
(0, vitest_1.expect)(result.exitCode).toBe(0);
|
|
182
|
+
(0, vitest_1.expect)(result.output.trim()).toBe("no ledger found");
|
|
183
|
+
});
|
|
184
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runRunsLedgerTail = runRunsLedgerTail;
|
|
4
|
+
function parseLineCount(value) {
|
|
5
|
+
const parsed = Number.parseInt(value, 10);
|
|
6
|
+
if (!Number.isFinite(parsed) || parsed < 1) {
|
|
7
|
+
return 20;
|
|
8
|
+
}
|
|
9
|
+
return parsed;
|
|
10
|
+
}
|
|
11
|
+
function runRunsLedgerTail(rawLines, count) {
|
|
12
|
+
const lineCount = parseLineCount(count);
|
|
13
|
+
const lines = rawLines.slice(-lineCount);
|
|
14
|
+
process.stdout.write(lines.length > 0 ? `${lines.join("\n")}\n` : "");
|
|
15
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runRunsList = runRunsList;
|
|
4
|
+
const CLOSED_STATUSES = new Set(["complete", "finalized"]);
|
|
5
|
+
function latestEventsByRun(events) {
|
|
6
|
+
const latest = new Map();
|
|
7
|
+
for (const event of events) {
|
|
8
|
+
latest.set(event.run_id, event);
|
|
9
|
+
}
|
|
10
|
+
return Array.from(latest.values()).sort((left, right) => left.run_id.localeCompare(right.run_id));
|
|
11
|
+
}
|
|
12
|
+
function formatRow(values) {
|
|
13
|
+
return values.join("\t");
|
|
14
|
+
}
|
|
15
|
+
function runRunsList(events, options) {
|
|
16
|
+
const rows = latestEventsByRun(events).filter((event) => !options.open || !CLOSED_STATUSES.has(event.status));
|
|
17
|
+
const lines = [
|
|
18
|
+
formatRow(["run_id", "run_type", "cluster_id", "branch", "status", "last event timestamp"]),
|
|
19
|
+
...rows.map((event) => formatRow([
|
|
20
|
+
event.run_id,
|
|
21
|
+
event.run_type,
|
|
22
|
+
event.cluster_id ?? "",
|
|
23
|
+
event.branch,
|
|
24
|
+
event.status,
|
|
25
|
+
event.timestamp,
|
|
26
|
+
])),
|
|
27
|
+
];
|
|
28
|
+
process.stdout.write(`${lines.join("\n")}\n`);
|
|
29
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runRunsReconcile = runRunsReconcile;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
function latestEventForRun(events, runId) {
|
|
6
|
+
return events.filter((event) => event.run_id === runId).at(-1) ?? null;
|
|
7
|
+
}
|
|
8
|
+
function sameStringArray(left, right) {
|
|
9
|
+
return JSON.stringify(left ?? []) === JSON.stringify(right ?? []);
|
|
10
|
+
}
|
|
11
|
+
function collectDivergences(ledger, state) {
|
|
12
|
+
const divergences = [];
|
|
13
|
+
const scalarChecks = [
|
|
14
|
+
["cluster_id", ledger.cluster_id, state.cluster_id],
|
|
15
|
+
["branch", ledger.branch, state.branch],
|
|
16
|
+
["status", ledger.status, state.status],
|
|
17
|
+
["next_child", ledger.next_child, state.next_open_child ?? null],
|
|
18
|
+
["last_commit", ledger.last_commit, state.last_commit ?? null],
|
|
19
|
+
];
|
|
20
|
+
for (const [field, ledgerValue, stateValue] of scalarChecks) {
|
|
21
|
+
if (ledgerValue !== stateValue) {
|
|
22
|
+
divergences.push({ field, ledger: ledgerValue, current_state: stateValue });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (!sameStringArray(ledger.completed_children, state.completed_children)) {
|
|
26
|
+
divergences.push({
|
|
27
|
+
field: "completed_children",
|
|
28
|
+
ledger: ledger.completed_children,
|
|
29
|
+
current_state: state.completed_children ?? [],
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
if (!sameStringArray(ledger.open_children, state.open_children)) {
|
|
33
|
+
divergences.push({
|
|
34
|
+
field: "open_children",
|
|
35
|
+
ledger: ledger.open_children,
|
|
36
|
+
current_state: state.open_children ?? [],
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return divergences;
|
|
40
|
+
}
|
|
41
|
+
function runRunsReconcile(events, options) {
|
|
42
|
+
if (!(0, node_fs_1.existsSync)(options.stateFile)) {
|
|
43
|
+
process.stdout.write(`read-only: no current-state.json found at ${options.stateFile}\n`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
let state;
|
|
47
|
+
try {
|
|
48
|
+
state = JSON.parse((0, node_fs_1.readFileSync)(options.stateFile, "utf-8"));
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
process.stdout.write("read-only: current-state.json has no run_id\n");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (!state.run_id) {
|
|
55
|
+
process.stdout.write("read-only: current-state.json has no run_id\n");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const latest = latestEventForRun(events, state.run_id);
|
|
59
|
+
if (!latest) {
|
|
60
|
+
process.stdout.write(`read-only: divergence found\nrun_id\tledger\tcurrent_state\n${state.run_id}\tmissing\tpresent\n`);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const divergences = collectDivergences(latest, state);
|
|
64
|
+
if (divergences.length === 0) {
|
|
65
|
+
process.stdout.write("read-only: no divergence found\n");
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const lines = [
|
|
69
|
+
"read-only: divergence found",
|
|
70
|
+
"field\tledger\tcurrent_state",
|
|
71
|
+
...divergences.map((divergence) => `${divergence.field}\t${JSON.stringify(divergence.ledger)}\t${JSON.stringify(divergence.current_state)}`),
|
|
72
|
+
];
|
|
73
|
+
process.stdout.write(`${lines.join("\n")}\n`);
|
|
74
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runRunsShow = runRunsShow;
|
|
4
|
+
function runRunsShow(events, runId) {
|
|
5
|
+
const lines = events
|
|
6
|
+
.filter((event) => event.run_id === runId)
|
|
7
|
+
.sort((left, right) => left.timestamp.localeCompare(right.timestamp))
|
|
8
|
+
.map((event) => JSON.stringify(event));
|
|
9
|
+
process.stdout.write(lines.length > 0 ? `${lines.join("\n")}\n` : "");
|
|
10
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.appendAuditEvent = appendAuditEvent;
|
|
7
|
+
const promises_1 = require("node:fs/promises");
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const state_js_1 = require("../state.js");
|
|
10
|
+
async function appendAuditEvent(artifactDir, event) {
|
|
11
|
+
const dir = (0, state_js_1.getArtifactDir)(artifactDir);
|
|
12
|
+
await (0, promises_1.mkdir)(dir, { recursive: true });
|
|
13
|
+
const entry = { timestamp: new Date().toISOString(), ...event };
|
|
14
|
+
await (0, promises_1.appendFile)(node_path_1.default.join(dir, "audit.jsonl"), JSON.stringify(entry) + "\n", "utf-8");
|
|
15
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.readAuditLog = readAuditLog;
|
|
7
|
+
exports.findLastEvent = findLastEvent;
|
|
8
|
+
const promises_1 = require("node:fs/promises");
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const state_js_1 = require("../state.js");
|
|
11
|
+
async function readAuditLog(artifactDir) {
|
|
12
|
+
const auditFile = node_path_1.default.join((0, state_js_1.getArtifactDir)(artifactDir), "audit.jsonl");
|
|
13
|
+
try {
|
|
14
|
+
const raw = await (0, promises_1.readFile)(auditFile, "utf-8");
|
|
15
|
+
const lines = raw.trim().split("\n").filter(Boolean);
|
|
16
|
+
const events = [];
|
|
17
|
+
for (const line of lines) {
|
|
18
|
+
try {
|
|
19
|
+
events.push(JSON.parse(line));
|
|
20
|
+
}
|
|
21
|
+
catch (parseErr) {
|
|
22
|
+
throw new Error(`Audit log corruption detected in ${auditFile}: ${parseErr instanceof Error ? parseErr.message : String(parseErr)}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return events;
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function findLastEvent(log, eventType, run_id, step_cursor) {
|
|
35
|
+
for (let i = log.length - 1; i >= 0; i--) {
|
|
36
|
+
const e = log[i];
|
|
37
|
+
if (e.event_type === eventType && e.run_id === run_id && e.step_cursor === step_cursor) {
|
|
38
|
+
return e;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.writeCheckpoint = writeCheckpoint;
|
|
7
|
+
exports.recoverFromCheckpoint = recoverFromCheckpoint;
|
|
8
|
+
const promises_1 = require("node:fs/promises");
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const logger_js_1 = require("./audit/logger.js");
|
|
11
|
+
const state_js_1 = require("./state.js");
|
|
12
|
+
function getCheckpointsDir(artifactDir) {
|
|
13
|
+
return node_path_1.default.join((0, state_js_1.getArtifactDir)(artifactDir), "checkpoints");
|
|
14
|
+
}
|
|
15
|
+
function buildCheckpointFilename(stepCursor, timestamp) {
|
|
16
|
+
// Sanitize step_cursor so it can safely appear in a filename
|
|
17
|
+
const safeCursor = stepCursor.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
18
|
+
// Collapse ISO timestamp to a filesystem-safe form: 20260525T202317Z
|
|
19
|
+
const safeTs = timestamp.replace(/[:.]/g, "").replace("Z", "Z").slice(0, 16) + "Z";
|
|
20
|
+
return `${safeCursor}-${safeTs}.json`;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Write a checkpoint for the current state of the given artifact directory.
|
|
24
|
+
*
|
|
25
|
+
* Loads the current state snapshot from disk (fresh read), writes it to
|
|
26
|
+
* `.taskchain_artifacts/{artifactDir}/checkpoints/{step_cursor}-{timestamp}.json`,
|
|
27
|
+
* and appends a `checkpoint_written` audit event.
|
|
28
|
+
*
|
|
29
|
+
* Throws if the state file cannot be read or the checkpoint cannot be written.
|
|
30
|
+
*/
|
|
31
|
+
async function writeCheckpoint(artifactDir, stepCursor) {
|
|
32
|
+
const state = await (0, state_js_1.loadState)(artifactDir);
|
|
33
|
+
if (state === null) {
|
|
34
|
+
throw new Error(`writeCheckpoint: cannot load state for artifact_dir="${artifactDir}" — current-state.json not found`);
|
|
35
|
+
}
|
|
36
|
+
const writtenAt = new Date().toISOString();
|
|
37
|
+
const filename = buildCheckpointFilename(stepCursor, writtenAt);
|
|
38
|
+
const checkpointsDir = getCheckpointsDir(artifactDir);
|
|
39
|
+
const checkpointPath = node_path_1.default.join(checkpointsDir, filename);
|
|
40
|
+
const record = {
|
|
41
|
+
artifact_dir: artifactDir,
|
|
42
|
+
step_cursor: stepCursor,
|
|
43
|
+
written_at: writtenAt,
|
|
44
|
+
state_snapshot: state,
|
|
45
|
+
};
|
|
46
|
+
await (0, promises_1.mkdir)(checkpointsDir, { recursive: true });
|
|
47
|
+
await (0, promises_1.writeFile)(checkpointPath, JSON.stringify(record, null, 2) + "\n", "utf-8");
|
|
48
|
+
await (0, logger_js_1.appendAuditEvent)(artifactDir, {
|
|
49
|
+
event_type: "checkpoint_written",
|
|
50
|
+
run_id: state.run_id,
|
|
51
|
+
step_cursor: stepCursor,
|
|
52
|
+
operator: "polaris-runtime",
|
|
53
|
+
operation: "write_checkpoint",
|
|
54
|
+
result: "ok",
|
|
55
|
+
metadata: { checkpoint_file: filename },
|
|
56
|
+
});
|
|
57
|
+
return record;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Find and return the latest checkpoint for the given artifact directory.
|
|
61
|
+
*
|
|
62
|
+
* Checkpoints are sorted lexicographically by filename; the last entry is the
|
|
63
|
+
* most recent. Returns null if the checkpoints directory does not exist or
|
|
64
|
+
* contains no `.json` files.
|
|
65
|
+
*/
|
|
66
|
+
async function recoverFromCheckpoint(artifactDir) {
|
|
67
|
+
const checkpointsDir = getCheckpointsDir(artifactDir);
|
|
68
|
+
let entries;
|
|
69
|
+
try {
|
|
70
|
+
const dirEntries = await (0, promises_1.readdir)(checkpointsDir);
|
|
71
|
+
entries = dirEntries.filter((name) => name.endsWith(".json")).sort();
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// Directory does not exist or is not readable — no checkpoints
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
if (entries.length === 0) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
const latestFile = entries[entries.length - 1];
|
|
81
|
+
const filePath = node_path_1.default.join(checkpointsDir, latestFile);
|
|
82
|
+
try {
|
|
83
|
+
const raw = await (0, promises_1.readFile)(filePath, "utf-8");
|
|
84
|
+
return JSON.parse(raw);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|