@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,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getVersion = getVersion;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
function getVersion() {
|
|
7
|
+
const packageJsonPath = (0, node_path_1.resolve)(__dirname, "../../package.json");
|
|
8
|
+
const packageJson = JSON.parse((0, node_fs_1.readFileSync)(packageJsonPath, "utf-8"));
|
|
9
|
+
return packageJson.version;
|
|
10
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const version_js_1 = require("./version.js");
|
|
5
|
+
(0, vitest_1.describe)("getVersion", () => {
|
|
6
|
+
(0, vitest_1.it)("returns a version string", () => {
|
|
7
|
+
const version = (0, version_js_1.getVersion)();
|
|
8
|
+
(0, vitest_1.expect)(typeof version).toBe("string");
|
|
9
|
+
(0, vitest_1.expect)(version.length).toBeGreaterThan(0);
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createWorkerCommand = createWorkerCommand;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const node_child_process_1 = require("node:child_process");
|
|
7
|
+
const node_crypto_1 = require("node:crypto");
|
|
8
|
+
const commander_1 = require("commander");
|
|
9
|
+
const worker_js_1 = require("../loop/worker.js");
|
|
10
|
+
const worker_packet_js_1 = require("../loop/worker-packet.js");
|
|
11
|
+
const git_custody_js_1 = require("../loop/git-custody.js");
|
|
12
|
+
const checkpoint_js_1 = require("../loop/checkpoint.js");
|
|
13
|
+
const store_js_1 = require("../cluster-state/store.js");
|
|
14
|
+
function appendTelemetry(telemetryFile, event) {
|
|
15
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(telemetryFile), { recursive: true });
|
|
16
|
+
(0, node_fs_1.appendFileSync)(telemetryFile, `${JSON.stringify(event)}\n`, "utf-8");
|
|
17
|
+
}
|
|
18
|
+
function failWithHelp(command, commandName) {
|
|
19
|
+
const subcommand = command.args[0];
|
|
20
|
+
const message = subcommand
|
|
21
|
+
? `error: unknown command '${subcommand}' for '${commandName}'. Run '${commandName} --help'.`
|
|
22
|
+
: `error: missing command for '${commandName}'. Run '${commandName} --help'.`;
|
|
23
|
+
command.error(message, {
|
|
24
|
+
code: "commander.missingCommand",
|
|
25
|
+
exitCode: 1,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function readActiveWorkerPacket() {
|
|
29
|
+
const packet = (0, worker_js_1.readBootstrapPacket)(process.argv);
|
|
30
|
+
if (!(0, worker_packet_js_1.isWorkerPacket)(packet)) {
|
|
31
|
+
throw new Error("Active worker packet is missing or invalid");
|
|
32
|
+
}
|
|
33
|
+
return packet;
|
|
34
|
+
}
|
|
35
|
+
function getCommitHash(repoRoot) {
|
|
36
|
+
return (0, node_child_process_1.execFileSync)("git", ["rev-parse", "HEAD"], {
|
|
37
|
+
cwd: repoRoot,
|
|
38
|
+
encoding: "utf-8",
|
|
39
|
+
}).trim();
|
|
40
|
+
}
|
|
41
|
+
function resolveRepoPath(repoRoot, path) {
|
|
42
|
+
return (0, node_path_1.isAbsolute)(path) ? path : (0, node_path_1.resolve)(repoRoot, path);
|
|
43
|
+
}
|
|
44
|
+
function validateWorkerCompletionResult(repoRoot, packet, resultFile) {
|
|
45
|
+
if (!resultFile) {
|
|
46
|
+
return { ok: false, reason: "missing result file path" };
|
|
47
|
+
}
|
|
48
|
+
if (!packet.active_child) {
|
|
49
|
+
return { ok: false, reason: "active worker packet has no active_child" };
|
|
50
|
+
}
|
|
51
|
+
const resolvedResultFile = resolveRepoPath(repoRoot, resultFile);
|
|
52
|
+
let parsed;
|
|
53
|
+
try {
|
|
54
|
+
const raw = JSON.parse((0, node_fs_1.readFileSync)(resolvedResultFile, "utf-8"));
|
|
55
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
56
|
+
return { ok: false, reason: "sealed result is not a JSON object" };
|
|
57
|
+
}
|
|
58
|
+
parsed = raw;
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
return {
|
|
62
|
+
ok: false,
|
|
63
|
+
reason: `failed to read sealed result (${error instanceof Error ? error.message : String(error)})`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const runId = String(parsed["run_id"] ?? "").trim();
|
|
67
|
+
if (runId !== packet.run_id) {
|
|
68
|
+
return { ok: false, reason: `sealed result run_id "${runId || "missing"}" does not match active packet` };
|
|
69
|
+
}
|
|
70
|
+
const childId = String(parsed["child_id"] ?? "").trim();
|
|
71
|
+
if (childId !== packet.active_child) {
|
|
72
|
+
return { ok: false, reason: `sealed result child_id "${childId || "missing"}" does not match active child "${packet.active_child}"` };
|
|
73
|
+
}
|
|
74
|
+
const status = String(parsed["status"] ?? "").trim().toLowerCase();
|
|
75
|
+
if (!["success", "done"].includes(status)) {
|
|
76
|
+
return { ok: false, reason: `sealed result status is "${status || "missing"}" (expected success)` };
|
|
77
|
+
}
|
|
78
|
+
const commit = String(parsed["commit"] ?? parsed["commit_hash"] ?? parsed["commit_sha"] ?? "").trim();
|
|
79
|
+
if (!commit) {
|
|
80
|
+
return { ok: false, reason: "sealed result is missing commit evidence" };
|
|
81
|
+
}
|
|
82
|
+
if (!/^[0-9a-f]{7,40}$/i.test(commit)) {
|
|
83
|
+
return { ok: false, reason: `sealed result commit "${commit}" is not a valid git hash` };
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
(0, node_child_process_1.execFileSync)("git", ["rev-parse", "--verify", "--quiet", `${commit}^{commit}`], {
|
|
87
|
+
cwd: repoRoot,
|
|
88
|
+
stdio: "ignore",
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return { ok: false, reason: `sealed result commit "${commit}" could not be verified in git` };
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
ok: true,
|
|
96
|
+
result: {
|
|
97
|
+
...parsed,
|
|
98
|
+
run_id: packet.run_id,
|
|
99
|
+
child_id: packet.active_child,
|
|
100
|
+
status: status === "done" ? "done" : "success",
|
|
101
|
+
commit,
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function updateCompletionState(state, childId, commit) {
|
|
106
|
+
const completedChildren = Array.from(new Set([...state.completed_children, childId]));
|
|
107
|
+
const remainingOpenChildren = state.open_children.filter((child) => child !== childId);
|
|
108
|
+
const completedChildrenResults = {
|
|
109
|
+
...(state.completed_children_results ?? {}),
|
|
110
|
+
[childId]: {
|
|
111
|
+
status: "done",
|
|
112
|
+
validation: "passed",
|
|
113
|
+
commit,
|
|
114
|
+
next_recommended_action: "continue",
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
return {
|
|
118
|
+
...state,
|
|
119
|
+
active_child: "",
|
|
120
|
+
open_children: remainingOpenChildren,
|
|
121
|
+
completed_children: completedChildren,
|
|
122
|
+
completed_children_results: completedChildrenResults,
|
|
123
|
+
next_open_child: remainingOpenChildren[0] ?? null,
|
|
124
|
+
step_cursor: "checkpoint",
|
|
125
|
+
status: remainingOpenChildren.length > 0 ? "running" : "cluster-complete",
|
|
126
|
+
last_commit: commit,
|
|
127
|
+
context_budget: {
|
|
128
|
+
...state.context_budget,
|
|
129
|
+
children_completed: completedChildren.length,
|
|
130
|
+
},
|
|
131
|
+
updated_at: new Date().toISOString(),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function emitWorkerCompletionTelemetry(telemetryFile, event) {
|
|
135
|
+
try {
|
|
136
|
+
appendTelemetry(telemetryFile, event);
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
process.stderr.write(`[polaris-worker] telemetry write failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function createWorkerCommand(options) {
|
|
143
|
+
const worker = new commander_1.Command("worker")
|
|
144
|
+
.description("mutating: worker-owned commit operations")
|
|
145
|
+
.showHelpAfterError()
|
|
146
|
+
.showSuggestionAfterError();
|
|
147
|
+
worker.action(() => failWithHelp(worker, "polaris worker"));
|
|
148
|
+
worker
|
|
149
|
+
.command("commit")
|
|
150
|
+
.description("mutating: validate the staged git index against the active worker packet and create one commit")
|
|
151
|
+
.action(() => {
|
|
152
|
+
try {
|
|
153
|
+
const packet = readActiveWorkerPacket();
|
|
154
|
+
const { allowedScope, prohibitedWritePaths } = (0, worker_packet_js_1.getWorkerCommitPolicy)(packet);
|
|
155
|
+
const validation = (0, git_custody_js_1.validateWorkerCommitScope)(options.repoRoot, allowedScope, prohibitedWritePaths);
|
|
156
|
+
if (validation.staged_files.length === 0) {
|
|
157
|
+
appendTelemetry(packet.telemetry_file, {
|
|
158
|
+
event: "worker-commit-rejected",
|
|
159
|
+
event_id: (0, node_crypto_1.randomUUID)(),
|
|
160
|
+
run_id: packet.run_id,
|
|
161
|
+
child_id: packet.active_child,
|
|
162
|
+
reason: "no-staged-files",
|
|
163
|
+
allowed_scope: allowedScope,
|
|
164
|
+
prohibited_write_paths: prohibitedWritePaths,
|
|
165
|
+
staged_files: [],
|
|
166
|
+
violations: [],
|
|
167
|
+
timestamp: new Date().toISOString(),
|
|
168
|
+
});
|
|
169
|
+
process.stderr.write("worker commit rejected: no staged files\n");
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
if (validation.violations.length > 0) {
|
|
173
|
+
appendTelemetry(packet.telemetry_file, {
|
|
174
|
+
event: "worker-commit-rejected",
|
|
175
|
+
event_id: (0, node_crypto_1.randomUUID)(),
|
|
176
|
+
run_id: packet.run_id,
|
|
177
|
+
child_id: packet.active_child,
|
|
178
|
+
reason: "scope-violation",
|
|
179
|
+
allowed_scope: allowedScope,
|
|
180
|
+
prohibited_write_paths: prohibitedWritePaths,
|
|
181
|
+
staged_files: validation.staged_files,
|
|
182
|
+
violations: validation.violations,
|
|
183
|
+
timestamp: new Date().toISOString(),
|
|
184
|
+
});
|
|
185
|
+
const summary = validation.violations
|
|
186
|
+
.map((violation) => `${violation.kind}:${violation.path}`)
|
|
187
|
+
.join(", ");
|
|
188
|
+
process.stderr.write(`worker commit rejected: ${summary}\n`);
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
(0, node_child_process_1.execFileSync)("git", ["commit", "-m", `polaris worker commit: ${packet.active_child || packet.run_id}`], {
|
|
192
|
+
cwd: options.repoRoot,
|
|
193
|
+
stdio: "ignore",
|
|
194
|
+
});
|
|
195
|
+
process.stdout.write(`${getCommitHash(options.repoRoot)}\n`);
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
if (error instanceof Error && error.message.startsWith("process.exit(")) {
|
|
199
|
+
throw error;
|
|
200
|
+
}
|
|
201
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
202
|
+
process.stderr.write(`worker commit failed: ${message}\n`);
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
worker
|
|
207
|
+
.command("complete")
|
|
208
|
+
.description("mutating: validate a sealed worker result and update current-state.json")
|
|
209
|
+
.argument("<result-file>", "Path to the sealed worker result JSON")
|
|
210
|
+
.action((resultFile) => {
|
|
211
|
+
let packet;
|
|
212
|
+
try {
|
|
213
|
+
packet = readActiveWorkerPacket();
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
process.stderr.write(`worker complete failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
217
|
+
process.exit(1);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
try {
|
|
221
|
+
const stateFile = resolveRepoPath(options.repoRoot, packet.state_file);
|
|
222
|
+
const telemetryFile = resolveRepoPath(options.repoRoot, packet.telemetry_file);
|
|
223
|
+
const validation = validateWorkerCompletionResult(options.repoRoot, packet, resultFile);
|
|
224
|
+
if (!validation.ok) {
|
|
225
|
+
emitWorkerCompletionTelemetry(telemetryFile, {
|
|
226
|
+
event: "worker-complete-failed",
|
|
227
|
+
event_id: (0, node_crypto_1.randomUUID)(),
|
|
228
|
+
run_id: packet.run_id,
|
|
229
|
+
child_id: packet.active_child,
|
|
230
|
+
result_file: resolveRepoPath(options.repoRoot, resultFile),
|
|
231
|
+
reason: validation.reason,
|
|
232
|
+
timestamp: new Date().toISOString(),
|
|
233
|
+
});
|
|
234
|
+
process.stderr.write(`worker complete rejected: ${validation.reason}\n`);
|
|
235
|
+
process.exit(1);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const state = (0, checkpoint_js_1.readState)(stateFile);
|
|
239
|
+
const updatedState = updateCompletionState(state, packet.active_child, validation.result.commit);
|
|
240
|
+
(0, checkpoint_js_1.writeStateAtomic)(stateFile, updatedState);
|
|
241
|
+
// Sync cluster-state.json so finalize and the parent loop see a consistent view.
|
|
242
|
+
try {
|
|
243
|
+
const clusterState = (0, store_js_1.readClusterStateSync)(packet.cluster_id, options.repoRoot);
|
|
244
|
+
if (clusterState) {
|
|
245
|
+
const childExists = clusterState.child_states.some((c) => c.id === packet.active_child);
|
|
246
|
+
const childStates = childExists
|
|
247
|
+
? clusterState.child_states.map((c) => c.id === packet.active_child ? { ...c, status: "done", commit: validation.result.commit } : c)
|
|
248
|
+
: [...clusterState.child_states, { id: packet.active_child, status: "done", commit: validation.result.commit }];
|
|
249
|
+
(0, store_js_1.writeClusterStateSync)(packet.cluster_id, {
|
|
250
|
+
...clusterState,
|
|
251
|
+
state_generation: clusterState.state_generation + 1,
|
|
252
|
+
child_states: childStates,
|
|
253
|
+
commits: { ...clusterState.commits, [packet.active_child]: validation.result.commit },
|
|
254
|
+
}, options.repoRoot);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
process.stderr.write(`[polaris-worker] cluster-state sync failed for ${packet.cluster_id} / ${packet.active_child}: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
259
|
+
}
|
|
260
|
+
emitWorkerCompletionTelemetry(telemetryFile, {
|
|
261
|
+
event: "worker-complete",
|
|
262
|
+
event_id: (0, node_crypto_1.randomUUID)(),
|
|
263
|
+
run_id: packet.run_id,
|
|
264
|
+
child_id: packet.active_child,
|
|
265
|
+
result_file: resolveRepoPath(options.repoRoot, resultFile),
|
|
266
|
+
status: validation.result.status,
|
|
267
|
+
commit: validation.result.commit,
|
|
268
|
+
timestamp: new Date().toISOString(),
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
const telemetryFile = resolveRepoPath(options.repoRoot, packet.telemetry_file);
|
|
273
|
+
emitWorkerCompletionTelemetry(telemetryFile, {
|
|
274
|
+
event: "worker-complete-failed",
|
|
275
|
+
event_id: (0, node_crypto_1.randomUUID)(),
|
|
276
|
+
run_id: packet.run_id,
|
|
277
|
+
child_id: packet.active_child,
|
|
278
|
+
result_file: resolveRepoPath(options.repoRoot, resultFile),
|
|
279
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
280
|
+
timestamp: new Date().toISOString(),
|
|
281
|
+
});
|
|
282
|
+
process.stderr.write(`worker complete failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
283
|
+
process.exit(1);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
return worker;
|
|
287
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "2.0",
|
|
3
|
+
"activeCluster": "POL-TEST-1",
|
|
4
|
+
"clusters": {
|
|
5
|
+
"POL-TEST-1": {
|
|
6
|
+
"id": "POL-TEST-1",
|
|
7
|
+
"title": "Test Cluster",
|
|
8
|
+
"children": ["CHILD-1", "CHILD-2"]
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"nodes": {
|
|
12
|
+
"POL-TEST-1": { "id": "POL-TEST-1", "type": "cluster" },
|
|
13
|
+
"CHILD-1": { "id": "CHILD-1", "type": "issue" },
|
|
14
|
+
"CHILD-2": { "id": "CHILD-2", "type": "issue" }
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"CHILD-2": ["CHILD-1"]
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.initializeClusterState = exports.writeClusterStateSync = exports.writeClusterState = exports.readClusterStateSync = exports.readClusterState = void 0;
|
|
37
|
+
const fs_1 = require("fs");
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const local_graph_1 = require("../tracker/local-graph");
|
|
40
|
+
const getClusterStatePath = (clusterId, repoRoot) => {
|
|
41
|
+
return path.join(repoRoot || process.cwd(), '.polaris', 'clusters', clusterId, 'cluster-state.json');
|
|
42
|
+
};
|
|
43
|
+
const normalizeClusterState = (state) => ({
|
|
44
|
+
...state,
|
|
45
|
+
tracker_mutations: state.tracker_mutations ?? {},
|
|
46
|
+
});
|
|
47
|
+
const readClusterState = async (clusterId, repoRoot) => {
|
|
48
|
+
const filePath = getClusterStatePath(clusterId, repoRoot);
|
|
49
|
+
try {
|
|
50
|
+
const data = await fs_1.promises.readFile(filePath, 'utf-8');
|
|
51
|
+
if (!data) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return normalizeClusterState(JSON.parse(data));
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
if (error.code === 'ENOENT') {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
exports.readClusterState = readClusterState;
|
|
64
|
+
const readClusterStateSync = (clusterId, repoRoot) => {
|
|
65
|
+
const filePath = getClusterStatePath(clusterId, repoRoot);
|
|
66
|
+
try {
|
|
67
|
+
const data = (0, fs_1.readFileSync)(filePath, 'utf-8');
|
|
68
|
+
if (!data) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
return normalizeClusterState(JSON.parse(data));
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (error.code === 'ENOENT') {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
exports.readClusterStateSync = readClusterStateSync;
|
|
81
|
+
const writeClusterState = async (clusterId, state, repoRoot) => {
|
|
82
|
+
const filePath = getClusterStatePath(clusterId, repoRoot);
|
|
83
|
+
const lockFilePath = filePath + '.lock';
|
|
84
|
+
const tempFilePath = `${filePath}.tmp.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}`;
|
|
85
|
+
// Acquire lock (simple lock file approach with retry)
|
|
86
|
+
let lockAcquired = false;
|
|
87
|
+
const maxRetries = 50;
|
|
88
|
+
const retryDelayMs = 100;
|
|
89
|
+
const staleLockThresholdMs = 30000; // 30 seconds
|
|
90
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
91
|
+
try {
|
|
92
|
+
// Try to create lock file exclusively (fails if it already exists)
|
|
93
|
+
await fs_1.promises.writeFile(lockFilePath, String(process.pid), { flag: 'wx' });
|
|
94
|
+
lockAcquired = true;
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
if (error.code === 'EEXIST') {
|
|
99
|
+
// Lock file exists, check if it's stale before retrying
|
|
100
|
+
try {
|
|
101
|
+
const lockStat = await fs_1.promises.stat(lockFilePath);
|
|
102
|
+
const lockAge = Date.now() - lockStat.mtimeMs;
|
|
103
|
+
if (lockAge > staleLockThresholdMs) {
|
|
104
|
+
// Lock is stale, try to read the PID
|
|
105
|
+
let isStale = true;
|
|
106
|
+
try {
|
|
107
|
+
const lockContent = await fs_1.promises.readFile(lockFilePath, 'utf-8');
|
|
108
|
+
const lockPid = parseInt(lockContent.trim(), 10);
|
|
109
|
+
// Check if the process is still running
|
|
110
|
+
if (!isNaN(lockPid)) {
|
|
111
|
+
try {
|
|
112
|
+
// process.kill with signal 0 checks existence without killing
|
|
113
|
+
process.kill(lockPid, 0);
|
|
114
|
+
// Process exists, lock is not stale
|
|
115
|
+
isStale = false;
|
|
116
|
+
}
|
|
117
|
+
catch (pidError) {
|
|
118
|
+
// Process doesn't exist (ESRCH) or no permission (EPERM)
|
|
119
|
+
// If EPERM, process exists but we can't signal it, so not stale
|
|
120
|
+
if (pidError.code === 'EPERM') {
|
|
121
|
+
isStale = false;
|
|
122
|
+
}
|
|
123
|
+
// Otherwise (ESRCH), process doesn't exist, lock is stale
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// If we can't read the lock file or parse PID, consider it stale based on age
|
|
129
|
+
}
|
|
130
|
+
if (isStale) {
|
|
131
|
+
console.warn(`Removing stale lock file for cluster ${clusterId} (age: ${lockAge}ms)`);
|
|
132
|
+
try {
|
|
133
|
+
await fs_1.promises.unlink(lockFilePath);
|
|
134
|
+
// Retry immediately after removing stale lock
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
catch (unlinkError) {
|
|
138
|
+
// Race condition: another process may have removed it
|
|
139
|
+
if (unlinkError.code !== 'ENOENT') {
|
|
140
|
+
throw unlinkError;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch (statError) {
|
|
147
|
+
// If lock file disappeared between EEXIST and stat, retry
|
|
148
|
+
if (statError.code === 'ENOENT') {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
// Other errors during stat, just retry with delay
|
|
152
|
+
}
|
|
153
|
+
// Lock file exists and is not stale, wait and retry
|
|
154
|
+
await new Promise(resolve => setTimeout(resolve, retryDelayMs));
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (!lockAcquired) {
|
|
162
|
+
throw new Error(`Failed to acquire lock for cluster state ${clusterId} after ${maxRetries} attempts`);
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
// Re-read current state under lock
|
|
166
|
+
const currentState = await (0, exports.readClusterState)(clusterId, repoRoot);
|
|
167
|
+
if (currentState && currentState.state_generation >= state.state_generation) {
|
|
168
|
+
throw new Error('Stale state: state_generation is not greater than current state.');
|
|
169
|
+
}
|
|
170
|
+
// Write to unique temp file and atomically rename
|
|
171
|
+
await fs_1.promises.writeFile(tempFilePath, JSON.stringify(state, null, 2));
|
|
172
|
+
await fs_1.promises.rename(tempFilePath, filePath);
|
|
173
|
+
}
|
|
174
|
+
finally {
|
|
175
|
+
// Release lock
|
|
176
|
+
try {
|
|
177
|
+
await fs_1.promises.unlink(lockFilePath);
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
// Ignore errors during lock cleanup
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
exports.writeClusterState = writeClusterState;
|
|
185
|
+
const writeClusterStateSync = (clusterId, state, repoRoot) => {
|
|
186
|
+
const filePath = getClusterStatePath(clusterId, repoRoot);
|
|
187
|
+
const lockFilePath = filePath + '.lock';
|
|
188
|
+
const tempFilePath = `${filePath}.tmp.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}`;
|
|
189
|
+
let lockAcquired = false;
|
|
190
|
+
const maxRetries = 50;
|
|
191
|
+
const retryDelayMs = 100;
|
|
192
|
+
const staleLockThresholdMs = 30000;
|
|
193
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
194
|
+
try {
|
|
195
|
+
(0, fs_1.mkdirSync)(path.dirname(lockFilePath), { recursive: true });
|
|
196
|
+
(0, fs_1.writeFileSync)(lockFilePath, String(process.pid), { flag: 'wx' });
|
|
197
|
+
lockAcquired = true;
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
if (error.code !== 'EEXIST') {
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
try {
|
|
205
|
+
const lockStat = (0, fs_1.statSync)(lockFilePath);
|
|
206
|
+
const lockAge = Date.now() - lockStat.mtimeMs;
|
|
207
|
+
if (lockAge > staleLockThresholdMs) {
|
|
208
|
+
let isStale = true;
|
|
209
|
+
try {
|
|
210
|
+
const lockContent = (0, fs_1.readFileSync)(lockFilePath, 'utf-8');
|
|
211
|
+
const lockPid = parseInt(lockContent.trim(), 10);
|
|
212
|
+
if (!isNaN(lockPid)) {
|
|
213
|
+
try {
|
|
214
|
+
process.kill(lockPid, 0);
|
|
215
|
+
isStale = false;
|
|
216
|
+
}
|
|
217
|
+
catch (pidError) {
|
|
218
|
+
if (pidError.code === 'EPERM') {
|
|
219
|
+
isStale = false;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
// Ignore parse/read failure and fall back to age-based stale handling.
|
|
226
|
+
}
|
|
227
|
+
if (isStale) {
|
|
228
|
+
try {
|
|
229
|
+
(0, fs_1.unlinkSync)(lockFilePath);
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
catch (unlinkError) {
|
|
233
|
+
if (unlinkError.code !== 'ENOENT') {
|
|
234
|
+
throw unlinkError;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
catch (statError) {
|
|
241
|
+
if (statError.code === 'ENOENT') {
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, retryDelayMs);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (!lockAcquired) {
|
|
249
|
+
throw new Error(`Failed to acquire lock for cluster state ${clusterId} after ${maxRetries} attempts`);
|
|
250
|
+
}
|
|
251
|
+
try {
|
|
252
|
+
const currentState = (0, exports.readClusterStateSync)(clusterId, repoRoot);
|
|
253
|
+
if (currentState && currentState.state_generation >= state.state_generation) {
|
|
254
|
+
throw new Error('Stale state: state_generation is not greater than current state.');
|
|
255
|
+
}
|
|
256
|
+
(0, fs_1.mkdirSync)(path.dirname(filePath), { recursive: true });
|
|
257
|
+
(0, fs_1.writeFileSync)(tempFilePath, JSON.stringify(state, null, 2));
|
|
258
|
+
(0, fs_1.renameSync)(tempFilePath, filePath);
|
|
259
|
+
}
|
|
260
|
+
finally {
|
|
261
|
+
try {
|
|
262
|
+
(0, fs_1.unlinkSync)(lockFilePath);
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
// Ignore errors during lock cleanup.
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
exports.writeClusterStateSync = writeClusterStateSync;
|
|
270
|
+
const initializeClusterState = async (clusterId, repoRoot) => {
|
|
271
|
+
const graph = await local_graph_1.LocalGraph.load(clusterId, repoRoot);
|
|
272
|
+
const activeCluster = graph.getActiveCluster();
|
|
273
|
+
if (!activeCluster) {
|
|
274
|
+
throw new Error(`Cluster ${clusterId} not found in graph.`);
|
|
275
|
+
}
|
|
276
|
+
// Exclude cluster root from child states — mirrors buildBootstrapPlan logic.
|
|
277
|
+
const clusterRoot = activeCluster.cluster_root;
|
|
278
|
+
const runnableChildren = clusterRoot
|
|
279
|
+
? activeCluster.children.filter((id) => id !== clusterRoot)
|
|
280
|
+
: activeCluster.children;
|
|
281
|
+
const childrenToInitialize = runnableChildren.length > 0 ? runnableChildren : [clusterId];
|
|
282
|
+
const childStates = childrenToInitialize.map(childId => ({
|
|
283
|
+
id: childId,
|
|
284
|
+
status: 'ready',
|
|
285
|
+
}));
|
|
286
|
+
const initialState = {
|
|
287
|
+
schema_version: '1.0',
|
|
288
|
+
cluster_id: clusterId,
|
|
289
|
+
state_generation: 1,
|
|
290
|
+
child_states: childStates,
|
|
291
|
+
claim_metadata: {},
|
|
292
|
+
packet_pointers: {},
|
|
293
|
+
result_pointers: {},
|
|
294
|
+
validation_results: {},
|
|
295
|
+
commits: {},
|
|
296
|
+
tracker_mutations: {},
|
|
297
|
+
blockers: [],
|
|
298
|
+
};
|
|
299
|
+
await (0, exports.writeClusterState)(clusterId, initialState, repoRoot);
|
|
300
|
+
return initialState;
|
|
301
|
+
};
|
|
302
|
+
exports.initializeClusterState = initializeClusterState;
|