@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,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
5
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
6
|
+
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
7
|
+
const index_js_2 = require("./tools/index.js");
|
|
8
|
+
const server = new index_js_1.Server({ name: "polaris", version: "0.1.0" }, { capabilities: { tools: {} } });
|
|
9
|
+
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({ tools: index_js_2.TOOLS }));
|
|
10
|
+
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (req) => (0, index_js_2.dispatchTool)(req.params.name, (req.params.arguments ?? {})));
|
|
11
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
12
|
+
server.connect(transport).catch((err) => {
|
|
13
|
+
process.stderr.write(`MCP server fatal error: ${String(err)}\n`);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handlePolarisClaimChild = handlePolarisClaimChild;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const pendingClaims = new Set();
|
|
7
|
+
function parseArgs(args) {
|
|
8
|
+
const artifactDir = args.artifact_dir ?? "polaris-run";
|
|
9
|
+
const childId = args.child_id;
|
|
10
|
+
if (typeof artifactDir !== "string" || !/^[\w][\w.-]*$/.test(artifactDir)) {
|
|
11
|
+
throw new Error("artifact_dir must be a safe artifact directory name");
|
|
12
|
+
}
|
|
13
|
+
if (typeof childId !== "string" || childId.trim() === "") {
|
|
14
|
+
throw new Error("child_id must be a non-empty string");
|
|
15
|
+
}
|
|
16
|
+
return { artifactDir, childId };
|
|
17
|
+
}
|
|
18
|
+
function statePathFor(artifactDir) {
|
|
19
|
+
return (0, node_path_1.join)(process.cwd(), ".taskchain_artifacts", artifactDir, "current-state.json");
|
|
20
|
+
}
|
|
21
|
+
function telemetryPathFor(artifactDir, runId) {
|
|
22
|
+
return (0, node_path_1.join)(process.cwd(), ".taskchain_artifacts", artifactDir, "runs", runId, "telemetry.jsonl");
|
|
23
|
+
}
|
|
24
|
+
function writeJsonAtomic(path, value) {
|
|
25
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(path), { recursive: true });
|
|
26
|
+
const tmp = `${path}.tmp`;
|
|
27
|
+
(0, node_fs_1.writeFileSync)(tmp, JSON.stringify(value, null, 2) + "\n", "utf-8");
|
|
28
|
+
(0, node_fs_1.renameSync)(tmp, path);
|
|
29
|
+
}
|
|
30
|
+
function appendTelemetry(artifactDir, state, childId) {
|
|
31
|
+
if (typeof state["run_id"] !== "string") {
|
|
32
|
+
throw new Error("run_id must be a string in state before emitting telemetry");
|
|
33
|
+
}
|
|
34
|
+
const runId = state["run_id"];
|
|
35
|
+
const event = {
|
|
36
|
+
event: "mcp-claim-child",
|
|
37
|
+
run_id: runId,
|
|
38
|
+
child_id: childId,
|
|
39
|
+
timestamp: new Date().toISOString(),
|
|
40
|
+
};
|
|
41
|
+
const telemetryPath = telemetryPathFor(artifactDir, runId);
|
|
42
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(telemetryPath), { recursive: true });
|
|
43
|
+
(0, node_fs_1.appendFileSync)(telemetryPath, JSON.stringify(event) + "\n", "utf-8");
|
|
44
|
+
}
|
|
45
|
+
async function handlePolarisClaimChild(args) {
|
|
46
|
+
let parsed;
|
|
47
|
+
try {
|
|
48
|
+
parsed = parseArgs(args);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
return {
|
|
52
|
+
ok: false,
|
|
53
|
+
error: "invalid_argument",
|
|
54
|
+
message: error instanceof Error ? error.message : String(error),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const lockKey = parsed.artifactDir;
|
|
58
|
+
if (pendingClaims.has(lockKey)) {
|
|
59
|
+
return {
|
|
60
|
+
ok: false,
|
|
61
|
+
error: "already_claimed",
|
|
62
|
+
message: `A claim is already in progress for artifact_dir=${parsed.artifactDir}`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
pendingClaims.add(lockKey);
|
|
66
|
+
try {
|
|
67
|
+
const statePath = statePathFor(parsed.artifactDir);
|
|
68
|
+
let state;
|
|
69
|
+
try {
|
|
70
|
+
state = JSON.parse((0, node_fs_1.readFileSync)(statePath, "utf-8"));
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return {
|
|
74
|
+
ok: false,
|
|
75
|
+
error: "state_not_found",
|
|
76
|
+
message: `State file not found or invalid: .taskchain_artifacts/${parsed.artifactDir}/current-state.json`,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (typeof state["active_child"] === "string" && state["active_child"].length > 0) {
|
|
80
|
+
return {
|
|
81
|
+
ok: false,
|
|
82
|
+
error: "already_claimed",
|
|
83
|
+
active_child: state["active_child"],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const openChildren = Array.isArray(state["open_children"]) ? state["open_children"] : [];
|
|
87
|
+
if (!openChildren.includes(parsed.childId)) {
|
|
88
|
+
return {
|
|
89
|
+
ok: false,
|
|
90
|
+
error: "child_not_open",
|
|
91
|
+
child_id: parsed.childId,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const updated = {
|
|
95
|
+
...state,
|
|
96
|
+
active_child: parsed.childId,
|
|
97
|
+
updated_at: new Date().toISOString(),
|
|
98
|
+
};
|
|
99
|
+
try {
|
|
100
|
+
writeJsonAtomic(statePath, updated);
|
|
101
|
+
appendTelemetry(parsed.artifactDir, updated, parsed.childId);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
return {
|
|
105
|
+
ok: false,
|
|
106
|
+
error: "telemetry_write_failed",
|
|
107
|
+
message: error instanceof Error ? error.message : String(error),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
ok: true,
|
|
112
|
+
child_id: parsed.childId,
|
|
113
|
+
active_child: parsed.childId,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
pendingClaims.delete(lockKey);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const node_os_1 = require("node:os");
|
|
7
|
+
const claim_child_js_1 = require("./claim-child.js");
|
|
8
|
+
const originalCwd = process.cwd();
|
|
9
|
+
function makeTempRepo() {
|
|
10
|
+
return (0, node_path_1.join)((0, node_os_1.tmpdir)(), `polaris-claim-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
11
|
+
}
|
|
12
|
+
function writeState(repoRoot, artifactDir, state) {
|
|
13
|
+
const dir = (0, node_path_1.join)(repoRoot, ".taskchain_artifacts", artifactDir);
|
|
14
|
+
(0, node_fs_1.mkdirSync)(dir, { recursive: true });
|
|
15
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, "current-state.json"), JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
16
|
+
}
|
|
17
|
+
function readState(repoRoot, artifactDir) {
|
|
18
|
+
return JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(repoRoot, ".taskchain_artifacts", artifactDir, "current-state.json"), "utf-8"));
|
|
19
|
+
}
|
|
20
|
+
(0, vitest_1.describe)("handlePolarisClaimChild()", () => {
|
|
21
|
+
let repoRoot;
|
|
22
|
+
(0, vitest_1.beforeEach)(() => {
|
|
23
|
+
repoRoot = makeTempRepo();
|
|
24
|
+
(0, node_fs_1.mkdirSync)(repoRoot, { recursive: true });
|
|
25
|
+
process.chdir(repoRoot);
|
|
26
|
+
});
|
|
27
|
+
(0, vitest_1.afterEach)(() => {
|
|
28
|
+
process.chdir(originalCwd);
|
|
29
|
+
(0, node_fs_1.rmSync)(repoRoot, { recursive: true, force: true });
|
|
30
|
+
});
|
|
31
|
+
(0, vitest_1.it)("sets active_child for an open child and appends telemetry", async () => {
|
|
32
|
+
writeState(repoRoot, "polaris-run", {
|
|
33
|
+
schema_version: "1.0",
|
|
34
|
+
run_id: "run-claim",
|
|
35
|
+
cluster_id: "POL-105",
|
|
36
|
+
active_child: null,
|
|
37
|
+
open_children: ["POL-111"],
|
|
38
|
+
completed_children: [],
|
|
39
|
+
context_budget: { children_completed: 0 },
|
|
40
|
+
status: "running",
|
|
41
|
+
step_cursor: "03-select-child",
|
|
42
|
+
});
|
|
43
|
+
const result = await (0, claim_child_js_1.handlePolarisClaimChild)({ child_id: "POL-111" });
|
|
44
|
+
(0, vitest_1.expect)(result["ok"]).toBe(true);
|
|
45
|
+
(0, vitest_1.expect)(result["active_child"]).toBe("POL-111");
|
|
46
|
+
(0, vitest_1.expect)(readState(repoRoot, "polaris-run")["active_child"]).toBe("POL-111");
|
|
47
|
+
const telemetry = (0, node_fs_1.readFileSync)((0, node_path_1.join)(repoRoot, ".taskchain_artifacts", "polaris-run", "runs", "run-claim", "telemetry.jsonl"), "utf-8");
|
|
48
|
+
(0, vitest_1.expect)(telemetry).toContain("\"event\":\"mcp-claim-child\"");
|
|
49
|
+
(0, vitest_1.expect)(telemetry).toContain("\"child_id\":\"POL-111\"");
|
|
50
|
+
});
|
|
51
|
+
(0, vitest_1.it)("returns already_claimed when active_child is already set", async () => {
|
|
52
|
+
writeState(repoRoot, "polaris-run", {
|
|
53
|
+
schema_version: "1.0",
|
|
54
|
+
run_id: "run-claim",
|
|
55
|
+
cluster_id: "POL-105",
|
|
56
|
+
active_child: "POL-110",
|
|
57
|
+
open_children: ["POL-111"],
|
|
58
|
+
completed_children: [],
|
|
59
|
+
context_budget: { children_completed: 0 },
|
|
60
|
+
status: "running",
|
|
61
|
+
step_cursor: "03-select-child",
|
|
62
|
+
});
|
|
63
|
+
const result = await (0, claim_child_js_1.handlePolarisClaimChild)({ child_id: "POL-111" });
|
|
64
|
+
(0, vitest_1.expect)(result["ok"]).toBe(false);
|
|
65
|
+
(0, vitest_1.expect)(result["error"]).toBe("already_claimed");
|
|
66
|
+
(0, vitest_1.expect)(result["active_child"]).toBe("POL-110");
|
|
67
|
+
(0, vitest_1.expect)(readState(repoRoot, "polaris-run")["active_child"]).toBe("POL-110");
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handlePolarisCurrentState = handlePolarisCurrentState;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const root_js_1 = require("../lib/root.js");
|
|
7
|
+
const redact_js_1 = require("../lib/redact.js");
|
|
8
|
+
function isValidDirName(name) {
|
|
9
|
+
return /^[\w][\w.-]*$/.test(name) && !name.includes("..");
|
|
10
|
+
}
|
|
11
|
+
async function handlePolarisCurrentState(args) {
|
|
12
|
+
const repoRoot = (0, root_js_1.resolveRepoRoot)();
|
|
13
|
+
const artifactDir = args.artifact_dir ?? "polaris-run";
|
|
14
|
+
if (typeof artifactDir !== "string") {
|
|
15
|
+
return {
|
|
16
|
+
ok: false,
|
|
17
|
+
error: "invalid_argument",
|
|
18
|
+
message: `artifact_dir must be a string, received ${typeof artifactDir}: ${JSON.stringify(artifactDir)}`,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
if (!isValidDirName(artifactDir)) {
|
|
22
|
+
return {
|
|
23
|
+
ok: false,
|
|
24
|
+
error: "invalid_argument",
|
|
25
|
+
message: `artifact_dir contains invalid characters: ${artifactDir}`,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const statePath = (0, node_path_1.resolve)((0, node_path_1.join)(repoRoot, ".taskchain_artifacts", artifactDir, "current-state.json"));
|
|
29
|
+
if (!statePath.startsWith(repoRoot)) {
|
|
30
|
+
return {
|
|
31
|
+
ok: false,
|
|
32
|
+
error: "invalid_argument",
|
|
33
|
+
message: "artifact_dir resolved outside repo root",
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
let raw;
|
|
37
|
+
try {
|
|
38
|
+
const realStatePath = (0, node_fs_1.realpathSync)(statePath);
|
|
39
|
+
const realRepoRoot = (0, node_fs_1.realpathSync)(repoRoot);
|
|
40
|
+
if (!realStatePath.startsWith(realRepoRoot + node_path_1.sep)) {
|
|
41
|
+
return {
|
|
42
|
+
ok: false,
|
|
43
|
+
error: "invalid_argument",
|
|
44
|
+
message: "artifact_dir resolved outside repo root (symlink detected)",
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
raw = (0, node_fs_1.readFileSync)(realStatePath, "utf-8");
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return {
|
|
51
|
+
ok: false,
|
|
52
|
+
error: "state_not_found",
|
|
53
|
+
message: `State file not found: .taskchain_artifacts/${artifactDir}/current-state.json`,
|
|
54
|
+
hint: "Run a polaris cluster session first to create current-state.json",
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
let parsed;
|
|
58
|
+
try {
|
|
59
|
+
parsed = JSON.parse(raw);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return {
|
|
63
|
+
ok: false,
|
|
64
|
+
error: "parse_error",
|
|
65
|
+
message: "current-state.json is not valid JSON",
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
ok: true,
|
|
70
|
+
artifact_dir: artifactDir,
|
|
71
|
+
state: (0, redact_js_1.redact)(parsed),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Integration tests for src/mcp/tools/current-state.ts
|
|
4
|
+
*
|
|
5
|
+
* Covers:
|
|
6
|
+
* - returns {ok:false, error:"state_not_found"} when file does not exist
|
|
7
|
+
* - returns {ok:true, artifact_dir, state} when file exists with valid JSON
|
|
8
|
+
* - returns {ok:false, error:"parse_error"} when file contains invalid JSON
|
|
9
|
+
* - returns {ok:false, error:"invalid_argument"} for artifact_dir with ".." path traversal
|
|
10
|
+
* - redacts sensitive keys from state
|
|
11
|
+
* - uses "polaris-run" as default artifact_dir
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
const vitest_1 = require("vitest");
|
|
15
|
+
const node_fs_1 = require("node:fs");
|
|
16
|
+
const node_path_1 = require("node:path");
|
|
17
|
+
const node_os_1 = require("node:os");
|
|
18
|
+
// We mock the root module so resolveRepoRoot() returns our temp dir
|
|
19
|
+
vitest_1.vi.mock("../lib/root.js", () => ({
|
|
20
|
+
resolveRepoRoot: vitest_1.vi.fn(),
|
|
21
|
+
}));
|
|
22
|
+
const root_js_1 = require("../lib/root.js");
|
|
23
|
+
const current_state_js_1 = require("./current-state.js");
|
|
24
|
+
const mockResolveRepoRoot = vitest_1.vi.mocked(root_js_1.resolveRepoRoot);
|
|
25
|
+
function makeTempDir() {
|
|
26
|
+
const dir = (0, node_path_1.join)((0, node_os_1.tmpdir)(), `polaris-cs-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
27
|
+
(0, node_fs_1.mkdirSync)(dir, { recursive: true });
|
|
28
|
+
return dir;
|
|
29
|
+
}
|
|
30
|
+
function writeStateFile(repoRoot, artifactDir, content) {
|
|
31
|
+
const artifactsDir = (0, node_path_1.join)(repoRoot, ".taskchain_artifacts", artifactDir);
|
|
32
|
+
(0, node_fs_1.mkdirSync)(artifactsDir, { recursive: true });
|
|
33
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(artifactsDir, "current-state.json"), content);
|
|
34
|
+
}
|
|
35
|
+
(0, vitest_1.describe)("handlePolarisCurrentState()", () => {
|
|
36
|
+
let tempDir;
|
|
37
|
+
(0, vitest_1.beforeEach)(() => {
|
|
38
|
+
tempDir = makeTempDir();
|
|
39
|
+
mockResolveRepoRoot.mockReturnValue(tempDir);
|
|
40
|
+
});
|
|
41
|
+
(0, vitest_1.afterEach)(() => {
|
|
42
|
+
(0, node_fs_1.rmSync)(tempDir, { recursive: true, force: true });
|
|
43
|
+
vitest_1.vi.clearAllMocks();
|
|
44
|
+
});
|
|
45
|
+
(0, vitest_1.it)("returns {ok:false, error:'state_not_found'} when the state file does not exist", async () => {
|
|
46
|
+
const result = await (0, current_state_js_1.handlePolarisCurrentState)({ artifact_dir: "my-run" });
|
|
47
|
+
(0, vitest_1.expect)(result["ok"]).toBe(false);
|
|
48
|
+
(0, vitest_1.expect)(result["error"]).toBe("state_not_found");
|
|
49
|
+
});
|
|
50
|
+
(0, vitest_1.it)("returns {ok:true, artifact_dir, state} when the file exists with valid JSON", async () => {
|
|
51
|
+
const state = { status: "running", run_id: "pol-1" };
|
|
52
|
+
writeStateFile(tempDir, "my-run", JSON.stringify(state));
|
|
53
|
+
const result = await (0, current_state_js_1.handlePolarisCurrentState)({ artifact_dir: "my-run" });
|
|
54
|
+
(0, vitest_1.expect)(result["ok"]).toBe(true);
|
|
55
|
+
(0, vitest_1.expect)(result["artifact_dir"]).toBe("my-run");
|
|
56
|
+
(0, vitest_1.expect)(result["state"]).toMatchObject({ status: "running", run_id: "pol-1" });
|
|
57
|
+
});
|
|
58
|
+
(0, vitest_1.it)("returns {ok:false, error:'parse_error'} when the file contains invalid JSON", async () => {
|
|
59
|
+
writeStateFile(tempDir, "my-run", "{ this is not valid json }");
|
|
60
|
+
const result = await (0, current_state_js_1.handlePolarisCurrentState)({ artifact_dir: "my-run" });
|
|
61
|
+
(0, vitest_1.expect)(result["ok"]).toBe(false);
|
|
62
|
+
(0, vitest_1.expect)(result["error"]).toBe("parse_error");
|
|
63
|
+
});
|
|
64
|
+
(0, vitest_1.it)("returns {ok:false, error:'invalid_argument'} for artifact_dir containing '..'", async () => {
|
|
65
|
+
const result = await (0, current_state_js_1.handlePolarisCurrentState)({ artifact_dir: "../escape" });
|
|
66
|
+
(0, vitest_1.expect)(result["ok"]).toBe(false);
|
|
67
|
+
(0, vitest_1.expect)(result["error"]).toBe("invalid_argument");
|
|
68
|
+
});
|
|
69
|
+
(0, vitest_1.it)("returns {ok:false, error:'invalid_argument'} for artifact_dir that is just '..'", async () => {
|
|
70
|
+
const result = await (0, current_state_js_1.handlePolarisCurrentState)({ artifact_dir: ".." });
|
|
71
|
+
(0, vitest_1.expect)(result["ok"]).toBe(false);
|
|
72
|
+
(0, vitest_1.expect)(result["error"]).toBe("invalid_argument");
|
|
73
|
+
});
|
|
74
|
+
(0, vitest_1.it)("redacts sensitive keys from the returned state", async () => {
|
|
75
|
+
const state = {
|
|
76
|
+
status: "running",
|
|
77
|
+
token: "super-secret-token",
|
|
78
|
+
password: "hunter2",
|
|
79
|
+
accessToken: "bearer-xyz",
|
|
80
|
+
label: "visible-value",
|
|
81
|
+
};
|
|
82
|
+
writeStateFile(tempDir, "my-run", JSON.stringify(state));
|
|
83
|
+
const result = await (0, current_state_js_1.handlePolarisCurrentState)({ artifact_dir: "my-run" });
|
|
84
|
+
(0, vitest_1.expect)(result["ok"]).toBe(true);
|
|
85
|
+
const returnedState = result["state"];
|
|
86
|
+
(0, vitest_1.expect)(returnedState["token"]).toBe("[redacted]");
|
|
87
|
+
(0, vitest_1.expect)(returnedState["password"]).toBe("[redacted]");
|
|
88
|
+
(0, vitest_1.expect)(returnedState["accessToken"]).toBe("[redacted]");
|
|
89
|
+
(0, vitest_1.expect)(returnedState["label"]).toBe("visible-value");
|
|
90
|
+
(0, vitest_1.expect)(returnedState["status"]).toBe("running");
|
|
91
|
+
});
|
|
92
|
+
(0, vitest_1.it)("uses 'polaris-run' as default artifact_dir when none provided", async () => {
|
|
93
|
+
// Write the file at the default path
|
|
94
|
+
const state = { run_id: "default-run" };
|
|
95
|
+
writeStateFile(tempDir, "polaris-run", JSON.stringify(state));
|
|
96
|
+
const result = await (0, current_state_js_1.handlePolarisCurrentState)({});
|
|
97
|
+
(0, vitest_1.expect)(result["ok"]).toBe(true);
|
|
98
|
+
(0, vitest_1.expect)(result["artifact_dir"]).toBe("polaris-run");
|
|
99
|
+
(0, vitest_1.expect)(result["state"]["run_id"]).toBe("default-run");
|
|
100
|
+
});
|
|
101
|
+
(0, vitest_1.it)("uses 'polaris-run' as default when artifact_dir is undefined", async () => {
|
|
102
|
+
const state = { cluster_id: "C-1" };
|
|
103
|
+
writeStateFile(tempDir, "polaris-run", JSON.stringify(state));
|
|
104
|
+
const result = await (0, current_state_js_1.handlePolarisCurrentState)({ artifact_dir: undefined });
|
|
105
|
+
(0, vitest_1.expect)(result["ok"]).toBe(true);
|
|
106
|
+
(0, vitest_1.expect)(result["artifact_dir"]).toBe("polaris-run");
|
|
107
|
+
});
|
|
108
|
+
(0, vitest_1.it)("returns state_not_found with a hint message", async () => {
|
|
109
|
+
const result = await (0, current_state_js_1.handlePolarisCurrentState)({ artifact_dir: "nonexistent-run" });
|
|
110
|
+
(0, vitest_1.expect)(result["ok"]).toBe(false);
|
|
111
|
+
(0, vitest_1.expect)(result["error"]).toBe("state_not_found");
|
|
112
|
+
(0, vitest_1.expect)(typeof result["hint"]).toBe("string");
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handlePolarisDispatchResult = handlePolarisDispatchResult;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const terminalStatuses = new Set(["done", "complete", "completed", "success", "passed"]);
|
|
7
|
+
function parseArgs(args) {
|
|
8
|
+
const artifactDir = args.artifact_dir ?? "polaris-run";
|
|
9
|
+
if (typeof artifactDir !== "string" || !/^[\w][\w.-]*$/.test(artifactDir)) {
|
|
10
|
+
throw new Error("artifact_dir must be a safe artifact directory name");
|
|
11
|
+
}
|
|
12
|
+
if (typeof args.child_id !== "string" || args.child_id.trim() === "") {
|
|
13
|
+
throw new Error("child_id must be a non-empty string");
|
|
14
|
+
}
|
|
15
|
+
if (typeof args.status !== "string" || args.status.trim() === "") {
|
|
16
|
+
throw new Error("status must be a non-empty string");
|
|
17
|
+
}
|
|
18
|
+
if (args.commit !== undefined && typeof args.commit !== "string") {
|
|
19
|
+
throw new Error("commit must be a string when provided");
|
|
20
|
+
}
|
|
21
|
+
const normalizedChildId = args.child_id.trim();
|
|
22
|
+
const normalizedStatus = args.status.trim();
|
|
23
|
+
return {
|
|
24
|
+
artifactDir,
|
|
25
|
+
childId: normalizedChildId,
|
|
26
|
+
status: normalizedStatus,
|
|
27
|
+
commit: args.commit,
|
|
28
|
+
validation: args.validation ?? null,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function statePathFor(artifactDir) {
|
|
32
|
+
return (0, node_path_1.join)(process.cwd(), ".taskchain_artifacts", artifactDir, "current-state.json");
|
|
33
|
+
}
|
|
34
|
+
function telemetryPathFor(artifactDir, runId) {
|
|
35
|
+
return (0, node_path_1.join)(process.cwd(), ".taskchain_artifacts", artifactDir, "runs", runId, "telemetry.jsonl");
|
|
36
|
+
}
|
|
37
|
+
function writeJsonAtomic(path, value) {
|
|
38
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(path), { recursive: true });
|
|
39
|
+
const tmp = `${path}.tmp`;
|
|
40
|
+
(0, node_fs_1.writeFileSync)(tmp, JSON.stringify(value, null, 2) + "\n", "utf-8");
|
|
41
|
+
(0, node_fs_1.renameSync)(tmp, path);
|
|
42
|
+
}
|
|
43
|
+
function appendTelemetry(artifactDir, runId, result) {
|
|
44
|
+
const event = {
|
|
45
|
+
event: "mcp-dispatch-result",
|
|
46
|
+
run_id: runId,
|
|
47
|
+
child_id: result.childId,
|
|
48
|
+
status: result.status,
|
|
49
|
+
commit: result.commit ?? null,
|
|
50
|
+
validation: result.validation,
|
|
51
|
+
timestamp: new Date().toISOString(),
|
|
52
|
+
};
|
|
53
|
+
const telemetryPath = telemetryPathFor(artifactDir, runId);
|
|
54
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(telemetryPath), { recursive: true });
|
|
55
|
+
(0, node_fs_1.appendFileSync)(telemetryPath, JSON.stringify(event) + "\n", "utf-8");
|
|
56
|
+
}
|
|
57
|
+
function withoutChild(values, childId) {
|
|
58
|
+
if (!Array.isArray(values))
|
|
59
|
+
return [];
|
|
60
|
+
return values.filter((value) => typeof value === "string" && value !== childId);
|
|
61
|
+
}
|
|
62
|
+
function withCompletedChild(values, childId) {
|
|
63
|
+
const completed = Array.isArray(values)
|
|
64
|
+
? values.filter((value) => typeof value === "string")
|
|
65
|
+
: [];
|
|
66
|
+
return completed.includes(childId) ? completed : [...completed, childId];
|
|
67
|
+
}
|
|
68
|
+
function updateChildMeta(state, childId, status) {
|
|
69
|
+
const meta = state["open_children_meta"];
|
|
70
|
+
if (typeof meta !== "object" || meta === null || Array.isArray(meta))
|
|
71
|
+
return undefined;
|
|
72
|
+
const child = meta[childId];
|
|
73
|
+
if (typeof child !== "object" || child === null || Array.isArray(child))
|
|
74
|
+
return meta;
|
|
75
|
+
return {
|
|
76
|
+
...meta,
|
|
77
|
+
[childId]: {
|
|
78
|
+
...child,
|
|
79
|
+
status,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
async function handlePolarisDispatchResult(args) {
|
|
84
|
+
let parsed;
|
|
85
|
+
try {
|
|
86
|
+
parsed = parseArgs(args);
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
return {
|
|
90
|
+
ok: false,
|
|
91
|
+
error: "invalid_argument",
|
|
92
|
+
message: error instanceof Error ? error.message : String(error),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const statePath = statePathFor(parsed.artifactDir);
|
|
96
|
+
let state;
|
|
97
|
+
try {
|
|
98
|
+
state = JSON.parse((0, node_fs_1.readFileSync)(statePath, "utf-8"));
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return {
|
|
102
|
+
ok: false,
|
|
103
|
+
error: "state_not_found",
|
|
104
|
+
message: `State file not found or invalid: .taskchain_artifacts/${parsed.artifactDir}/current-state.json`,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
if (state["active_child"] !== parsed.childId) {
|
|
108
|
+
return {
|
|
109
|
+
ok: false,
|
|
110
|
+
error: "active_child_mismatch",
|
|
111
|
+
expected: state["active_child"] ?? null,
|
|
112
|
+
actual: parsed.childId,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
// Validate run_id strictly before any persistence
|
|
116
|
+
if (typeof state["run_id"] !== "string" || state["run_id"].trim() === "") {
|
|
117
|
+
return {
|
|
118
|
+
ok: false,
|
|
119
|
+
error: "invalid_run_id",
|
|
120
|
+
message: "run_id must be a non-empty string in state",
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const runId = state["run_id"].trim();
|
|
124
|
+
const isUUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(runId);
|
|
125
|
+
const isRunIdFormat = /^[\w.\-:]+$/.test(runId);
|
|
126
|
+
if (!isUUID && !isRunIdFormat) {
|
|
127
|
+
return {
|
|
128
|
+
ok: false,
|
|
129
|
+
error: "invalid_run_id",
|
|
130
|
+
message: "run_id must be a valid UUID format or run-id format",
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const recordedAt = new Date().toISOString();
|
|
134
|
+
const completed = terminalStatuses.has(parsed.status.toLowerCase());
|
|
135
|
+
const openChildren = completed ? withoutChild(state["open_children"], parsed.childId) : state["open_children"];
|
|
136
|
+
const childMeta = completed ? updateChildMeta(state, parsed.childId, "Done") : updateChildMeta(state, parsed.childId, parsed.status);
|
|
137
|
+
const workerResult = {
|
|
138
|
+
child_id: parsed.childId,
|
|
139
|
+
status: parsed.status,
|
|
140
|
+
commit: parsed.commit ?? null,
|
|
141
|
+
validation: parsed.validation,
|
|
142
|
+
recorded_at: recordedAt,
|
|
143
|
+
};
|
|
144
|
+
const updated = {
|
|
145
|
+
...state,
|
|
146
|
+
active_child: completed ? null : parsed.childId,
|
|
147
|
+
completed_children: completed
|
|
148
|
+
? withCompletedChild(state["completed_children"], parsed.childId)
|
|
149
|
+
: state["completed_children"],
|
|
150
|
+
open_children: openChildren,
|
|
151
|
+
...(Array.isArray(openChildren) ? { next_open_child: openChildren[0] ?? null } : {}),
|
|
152
|
+
...(childMeta ? { open_children_meta: childMeta } : {}),
|
|
153
|
+
...(parsed.commit ? { last_commit: parsed.commit } : {}),
|
|
154
|
+
validation_status: parsed.status,
|
|
155
|
+
last_worker_result: workerResult,
|
|
156
|
+
updated_at: recordedAt,
|
|
157
|
+
};
|
|
158
|
+
try {
|
|
159
|
+
writeJsonAtomic(statePath, updated);
|
|
160
|
+
appendTelemetry(parsed.artifactDir, runId, {
|
|
161
|
+
childId: parsed.childId,
|
|
162
|
+
status: parsed.status,
|
|
163
|
+
commit: parsed.commit,
|
|
164
|
+
validation: parsed.validation,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
return {
|
|
169
|
+
ok: false,
|
|
170
|
+
error: {
|
|
171
|
+
message: err instanceof Error ? err.message : String(err),
|
|
172
|
+
name: err instanceof Error ? err.name : "Error",
|
|
173
|
+
meta: {
|
|
174
|
+
statePath,
|
|
175
|
+
childId: parsed.childId,
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
ok: true,
|
|
182
|
+
child_id: parsed.childId,
|
|
183
|
+
active_child: updated.active_child,
|
|
184
|
+
recorded: workerResult,
|
|
185
|
+
};
|
|
186
|
+
}
|