@lsctech/polaris 0.5.12 → 0.5.13

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.
@@ -6,6 +6,7 @@ const node_path_1 = require("node:path");
6
6
  const node_child_process_1 = require("node:child_process");
7
7
  const loader_js_1 = require("../config/loader.js");
8
8
  const librarian_dispatch_js_1 = require("../smartdocs-engine/librarian-dispatch.js");
9
+ const seed_instructions_js_1 = require("../smartdocs-engine/seed-instructions.js");
9
10
  const SKIP_DIRS = new Set(["node_modules", ".git", "dist", "build", ".polaris", "smartdocs"]);
10
11
  function loadInventoryCanonicalFolders(repoRoot) {
11
12
  const inventoryPath = (0, node_path_1.join)(repoRoot, ".polaris", "adoption-inventory.json");
@@ -29,7 +30,7 @@ function scaffoldDraftSummaryFiles(repoRoot, canonicalFolders) {
29
30
  if ((0, node_fs_1.existsSync)(summaryPath))
30
31
  continue;
31
32
  (0, node_fs_1.mkdirSync)(dir, { recursive: true });
32
- (0, node_fs_1.writeFileSync)(summaryPath, `# ${folder}\n\n<!-- polaris:draft -->\n`, "utf-8");
33
+ (0, node_fs_1.writeFileSync)(summaryPath, (0, seed_instructions_js_1.generateSummaryDraft)(folder, repoRoot, {}), "utf-8");
33
34
  console.log(` Scaffolded draft SUMMARY.md: ${folder}`);
34
35
  }
35
36
  }
@@ -90,13 +91,19 @@ function injectLinkedDocs(content, docs, summaryLines) {
90
91
  const headingIdx = lines.findIndex((l) => l.startsWith("#"));
91
92
  if (headingIdx === -1)
92
93
  return content;
94
+ const heading = lines[headingIdx];
95
+ const hasSummaryContent = summaryLines.length > 0;
93
96
  const yamlLines = ["", "---", ...buildLinkedDocsBlock(docs), "---"];
94
- const before = lines.slice(0, headingIdx + 1);
95
- // If agent provided summary content, append after YAML block
96
- const after = summaryLines.length > 0
97
- ? ["", ...summaryLines]
98
- : lines.slice(headingIdx + 1);
99
- return [...before, ...yamlLines, ...after].join("\n");
97
+ const bodyLines = hasSummaryContent
98
+ ? [...yamlLines, "", ...summaryLines]
99
+ : [...yamlLines, ...lines.slice(headingIdx + 1).filter((l) => {
100
+ const t = l.trim();
101
+ return t !== seed_instructions_js_1.DRAFT_MARKER && t !== seed_instructions_js_1.GENERATED_START_MARKER && t !== seed_instructions_js_1.GENERATED_END_MARKER;
102
+ })];
103
+ const generatedRegion = [heading, ...bodyLines].join("\n");
104
+ return hasSummaryContent
105
+ ? [seed_instructions_js_1.GENERATED_START_MARKER, generatedRegion, seed_instructions_js_1.GENERATED_END_MARKER].join("\n")
106
+ : [seed_instructions_js_1.DRAFT_MARKER, seed_instructions_js_1.GENERATED_START_MARKER, generatedRegion, seed_instructions_js_1.GENERATED_END_MARKER].join("\n");
100
107
  }
101
108
  function parseCanonResponse(text) {
102
109
  const trimmed = text.trim();
@@ -249,12 +256,19 @@ async function enrichCanonFiles(repoRoot) {
249
256
  if ((response.polaris_lines ?? []).length > 0) {
250
257
  const polarisPath = (0, node_path_1.join)(dir, "POLARIS.md");
251
258
  const polarisContent = [
259
+ seed_instructions_js_1.GENERATED_START_MARKER,
252
260
  `# POLARIS — ${routeFolder}`,
253
261
  "",
254
262
  ...response.polaris_lines,
255
263
  "",
264
+ seed_instructions_js_1.GENERATED_END_MARKER,
256
265
  ].join("\n");
257
- (0, node_fs_1.writeFileSync)(polarisPath, polarisContent, "utf-8");
266
+ if (!(0, node_fs_1.existsSync)(polarisPath) || (0, seed_instructions_js_1.hasDraftMarker)(polarisPath)) {
267
+ (0, node_fs_1.writeFileSync)(polarisPath, polarisContent, "utf-8");
268
+ }
269
+ else {
270
+ console.log(` Skipping POLARIS.md (existing non-draft): ${routeFolder}`);
271
+ }
258
272
  }
259
273
  enrichedCount++;
260
274
  console.log(` ✓ Enriched ${routeFolder} with ${response.relevant_docs.length} linked docs`);
@@ -24,23 +24,6 @@ const LEGACY_RUN_ARTIFACTS = new Set([
24
24
  ".polaris/runs/run-report.md",
25
25
  ".polaris/runs/current-state.pre-pol-198.json",
26
26
  ]);
27
- function isPromotedRunArchivePath(relativePath) {
28
- if (!relativePath.startsWith(".polaris/runs/")) {
29
- return false;
30
- }
31
- const rest = relativePath.slice(".polaris/runs/".length);
32
- if (rest === "" || rest === "evo-run-archive") {
33
- return false;
34
- }
35
- const slashIndex = rest.indexOf("/");
36
- if (slashIndex === -1) {
37
- // The path is a directory-style run archive path (e.g. `.polaris/runs/<run-id>`),
38
- // not a top-level file like `ledger.jsonl`.
39
- return !rest.includes(".") && !rest.startsWith(".");
40
- }
41
- const firstSegment = rest.slice(0, slashIndex);
42
- return firstSegment !== "evo-run-archive";
43
- }
44
27
  function normalizeArtifactPath(filePath) {
45
28
  const normalized = node_path_1.default.posix.normalize(filePath.replace(/\\/g, "/"));
46
29
  return normalized.startsWith("./") ? normalized.slice(2) : normalized;
@@ -78,9 +61,6 @@ function classifyArtifactPath(filePath, activeClusterId) {
78
61
  if (relativePath === PROMOTED_RUN_LEDGER) {
79
62
  return "promoted-run-ledger";
80
63
  }
81
- if (isPromotedRunArchivePath(relativePath)) {
82
- return "promoted-run-archive";
83
- }
84
64
  if (relativePath.startsWith(PROMOTED_COGNITION_ARCHIVE_PREFIX)) {
85
65
  return "promoted-cognition-archive";
86
66
  }
@@ -102,7 +82,6 @@ function isPromotedArtifactPath(filePath, activeClusterId) {
102
82
  const classification = classifyArtifactPath(filePath, activeClusterId);
103
83
  return (classification === "promoted-cluster-artifact"
104
84
  || classification === "promoted-run-ledger"
105
- || classification === "promoted-run-archive"
106
85
  || classification === "promoted-cognition-archive"
107
86
  || classification === "promoted-map-artifact");
108
87
  }
@@ -114,8 +93,6 @@ function explainArtifactPolicy(filePath, activeClusterId) {
114
93
  return "active cluster evidence is eligible for promotion into finalize commits";
115
94
  case "promoted-run-ledger":
116
95
  return "the run ledger is durable audit evidence and stays commit-eligible";
117
- case "promoted-run-archive":
118
- return "archived run snapshots under .polaris/runs/<run-id>/ are durable audit evidence and stay commit-eligible";
119
96
  case "promoted-cognition-archive":
120
97
  return "archived cognition reconciliation notes are durable provenance and stay commit-eligible";
121
98
  case "promoted-map-artifact":
@@ -139,7 +116,6 @@ function findArtifactPromotionViolations(stagedPaths, activeClusterId) {
139
116
  if (classification === "non-artifact"
140
117
  || classification === "promoted-cluster-artifact"
141
118
  || classification === "promoted-run-ledger"
142
- || classification === "promoted-run-archive"
143
119
  || classification === "promoted-cognition-archive"
144
120
  || classification === "promoted-map-artifact") {
145
121
  continue;
@@ -194,6 +170,7 @@ function getGitignorePatterns() {
194
170
  ".polaris/runs/current-state.json",
195
171
  ".polaris/runs/run-report.md",
196
172
  ".polaris/runs/current-state.pre-pol-198.json",
173
+ ".polaris/runs/*/",
197
174
  ".polaris/runs/evo-run-archive/**",
198
175
  ".polaris/bootstrap/**",
199
176
  ".polaris/session-type",
@@ -246,7 +246,7 @@ function checkLibrarianGate(repoRoot, clusterId) {
246
246
  }
247
247
  function resolveQcTelemetryFile(state, repoRoot) {
248
248
  const artifactDir = state.artifact_dir ?? (0, node_path_1.join)(repoRoot, ".taskchain_artifacts", "polaris-run");
249
- return (0, node_path_1.join)(artifactDir, "runs", state.run_id, "telemetry.jsonl");
249
+ return (0, node_path_1.resolve)(repoRoot, artifactDir, "runs", state.run_id, "telemetry.jsonl");
250
250
  }
251
251
  // ── QC repair-loop terminal state gate ────────────────────────────────────────
252
252
  /**
@@ -628,7 +628,7 @@ async function runFinalize(options) {
628
628
  throw new Error(`Canon check cannot proceed: git diff failed: ${msg}`);
629
629
  }
630
630
  const artifactDirForCheck = state.artifact_dir ?? (0, node_path_1.join)(repoRoot, ".taskchain_artifacts", "polaris-run");
631
- const telemetryFileForCheck = (0, node_path_1.join)(artifactDirForCheck, "runs", state.run_id, "telemetry.jsonl");
631
+ const telemetryFileForCheck = (0, node_path_1.resolve)(repoRoot, artifactDirForCheck, "runs", state.run_id, "telemetry.jsonl");
632
632
  const canonResult = (0, canon_check_js_1.runCanonCheck)({
633
633
  repoRoot,
634
634
  changedFiles,
@@ -867,6 +867,13 @@ async function runFinalize(options) {
867
867
  console.log("[8/14] Committing durable Polaris state + map..."); // Step count updated
868
868
  const resolvedStateFile = (0, node_path_1.resolve)(stateFile);
869
869
  (0, _06_commit_js_1.stepCommit)(repoRoot, state, resolvedStateFile, reportPath);
870
+ // The run's local state file is the authoritative place for post-PR state
871
+ // (pr_url, status: complete). When the state file passed to finalize is the
872
+ // tracked cluster snapshot, redirect post-PR writes to the untracked per-run
873
+ // archive so the cluster snapshot is never written after PR creation.
874
+ const runLocalStateFile = isClusterStateSnapshotFile(resolvedStateFile)
875
+ ? (0, node_path_1.resolve)(repoRoot, ".polaris", "runs", state.run_id, "current-state.json")
876
+ : resolvedStateFile;
870
877
  if (skipDelivery) {
871
878
  console.log("[9–14/14] Delivery skipped (--skip-delivery).");
872
879
  console.log("polaris finalize steps 1–8 complete.");
@@ -890,24 +897,13 @@ async function runFinalize(options) {
890
897
  prUrl,
891
898
  stepLabel: "[10.5/14]",
892
899
  });
893
- // Step 11: Write PR URL to current-state.json
900
+ // Step 11: Write PR URL to the run's local state file
894
901
  console.log("[11/14] Writing PR URL to state...");
895
- state = (0, _09_update_state_js_1.stepUpdateState)(resolvedStateFile, state, prUrl);
896
- // Promote the authoritative run state into the cluster snapshot so that
897
- // `.polaris/clusters/<id>/state.json` preserves completed children, the
898
- // dispatch boundary, QC repair-loop terminal state, and the PR URL.
899
- try {
900
- const clusterStateSnapshotPath = (0, node_path_1.join)(repoRoot, ".polaris", "clusters", state.cluster_id, "state.json");
901
- (0, checkpoint_js_1.writeStateAtomic)(clusterStateSnapshotPath, state);
902
- }
903
- catch (snapshotError) {
904
- process.stderr.write(`[11/14] WARNING: Failed to write cluster state snapshot: ${snapshotError instanceof Error ? snapshotError.message : String(snapshotError)}\n`);
905
- process.stderr.write(`Delivery will proceed, but cluster state snapshot at .polaris/clusters/${state.cluster_id}/state.json may be incomplete.\n`);
906
- }
902
+ state = (0, _09_update_state_js_1.stepUpdateState)(runLocalStateFile, state, prUrl);
907
903
  // Step 12: Append JSONL events
908
904
  console.log("[12/14] Appending JSONL events...");
909
905
  const artifactDir = state.artifact_dir ?? (0, node_path_1.join)(repoRoot, ".taskchain_artifacts", "polaris-run");
910
- const telemetryFile = (0, node_path_1.join)(artifactDir, "runs", state.run_id, "telemetry.jsonl");
906
+ const telemetryFile = (0, node_path_1.resolve)(repoRoot, artifactDir, "runs", state.run_id, "telemetry.jsonl");
911
907
  (0, _10_append_jsonl_js_1.stepAppendJsonl)(telemetryFile, state, prUrl);
912
908
  // Step 13: Update Linear parent issue
913
909
  console.log("[13/14] Updating Linear...");
@@ -916,7 +912,7 @@ async function runFinalize(options) {
916
912
  await (0, _11_update_linear_js_1.stepUpdateLinear)(state, branch, prUrl, true, linearEnabled, state.cluster_id, lifecyclePolicy, authChildResult.authoritativeCount);
917
913
  // Step 14: Archive run snapshot
918
914
  console.log("[14/14] Archiving run snapshot...");
919
- (0, _12_archive_js_1.stepArchive)(repoRoot, state, resolvedStateFile, reportPath);
915
+ (0, _12_archive_js_1.stepArchive)(repoRoot, state, runLocalStateFile, reportPath);
920
916
  console.log("polaris finalize complete.");
921
917
  }
922
918
  function failMissingSubcommand(command, commandName) {
@@ -155,9 +155,9 @@ function renderRoutingSection(state, telemetryEvents) {
155
155
  : "No routing anomalies or evidence gaps detected.";
156
156
  return `### Provider distribution
157
157
 
158
- | Child | Provider | Selection reason | Mode | Policy order | Candidates | Fallbacks | Status |
159
- |---|---|---|---|---|---|---|---|
160
- ${rows || "| _No children recorded_ | — | — | — | — | — | — | — |"}
158
+ | Child | Title | Provider | Selection reason | Mode | Policy order | Candidates | Fallbacks | Status |
159
+ |---|---|---|---|---|---|---|---|---|
160
+ ${rows || "| _No children recorded_ | — | — | — | — | — | — | — | — |"}
161
161
 
162
162
  **Provider summary:** ${distribution}
163
163
 
@@ -7,8 +7,7 @@ const node_path_1 = require("node:path");
7
7
  const artifact_policy_js_1 = require("../artifact-policy.js");
8
8
  function stepCommit(repoRoot, state, _stateFile, _reportPath) {
9
9
  const msg = `polaris finalize: ${state.run_id}\n\nChildren: ${state.completed_children.length} completed\nBranch: ${getBranch(repoRoot)}`;
10
- const runArchiveTarget = `.polaris/runs/${state.run_id}`;
11
- const promotedTargets = [...(0, artifact_policy_js_1.getArtifactPromotionStageTargets)(state.cluster_id), runArchiveTarget]
10
+ const promotedTargets = (0, artifact_policy_js_1.getArtifactPromotionStageTargets)(state.cluster_id)
12
11
  .filter((target) => (0, node_fs_1.existsSync)((0, node_path_1.join)(repoRoot, target)));
13
12
  if (promotedTargets.length > 0) {
14
13
  (0, node_child_process_1.execFileSync)("git", ["add", "--", ...promotedTargets], { cwd: repoRoot, stdio: "inherit" });
@@ -19,7 +19,7 @@ function stepArchive(repoRoot, state, stateFile, reportPath) {
19
19
  }
20
20
  const artifactDir = state.artifact_dir
21
21
  ?? (0, node_path_1.join)(repoRoot, ".taskchain_artifacts", "polaris-run");
22
- const telemetryFile = (0, node_path_1.join)(artifactDir, "runs", state.run_id, "telemetry.jsonl");
22
+ const telemetryFile = (0, node_path_1.resolve)(repoRoot, artifactDir, "runs", state.run_id, "telemetry.jsonl");
23
23
  if ((0, node_fs_1.existsSync)(telemetryFile)) {
24
24
  (0, node_fs_1.copyFileSync)(telemetryFile, (0, node_path_1.join)(archiveDir, "telemetry.jsonl"));
25
25
  }
@@ -498,15 +498,42 @@ class TerminalCliAdapter {
498
498
  const compact = buildCompactReturnForSealedResult(normalized, exitCode);
499
499
  const compactReturnErrors = (0, compact_return_js_1.validateCompactReturn)(compact);
500
500
  const isValidCompactReturn = compactReturnErrors.length === 0;
501
- const finalStatus = isValidCompactReturn
501
+ let finalStatus = isValidCompactReturn
502
502
  ? compact["status"]
503
503
  : "failed";
504
- const finalValidation = isValidCompactReturn
504
+ let finalValidation = isValidCompactReturn
505
505
  ? compact["validation"]
506
506
  : "failed";
507
- const finalNext = isValidCompactReturn
507
+ let finalNext = isValidCompactReturn
508
508
  ? compact["next_recommended_action"]
509
509
  : "stop";
510
+ // Reconcile validation/next_recommended_action with run-health symptoms.
511
+ // A validation-failed symptom overrides a passing validation string and
512
+ // prevents a continue recommendation when the build/tests did not pass.
513
+ const rawSymptoms = compact["run_health_symptoms"];
514
+ const hasValidationFailedSymptom = Array.isArray(rawSymptoms) &&
515
+ rawSymptoms.some((s) => typeof s === "object" &&
516
+ s !== null &&
517
+ s["category"] === "validation-failed");
518
+ if (hasValidationFailedSymptom) {
519
+ finalValidation = "failed";
520
+ finalNext = "stop";
521
+ }
522
+ // "continue" requires a passing validation result. If validation failed,
523
+ // the recommendation must be a terminal stop. If validation was skipped,
524
+ // treat the successful exit as a pass for the completion contract.
525
+ if (finalStatus === "done" && finalNext === "continue") {
526
+ if (finalValidation === "failed") {
527
+ finalNext = "stop";
528
+ }
529
+ else if (finalValidation === "skipped") {
530
+ finalValidation = "passed";
531
+ }
532
+ }
533
+ // A failed status should never recommend continuation.
534
+ if ((finalStatus === "failed" || finalStatus === "blocked") && finalNext === "continue") {
535
+ finalNext = "stop";
536
+ }
510
537
  const errorMessage = finalStatus === "failed" || finalStatus === "blocked"
511
538
  ? isValidCompactReturn
512
539
  ? typeof compact["error_message"] === "string"
@@ -518,6 +545,7 @@ class TerminalCliAdapter {
518
545
  : undefined;
519
546
  const sealedResult = {
520
547
  run_id: packet.run_id,
548
+ cluster_id: packet.cluster_id,
521
549
  child_id: compact["child_id"],
522
550
  status: finalStatus,
523
551
  commit: compact["commit"],
@@ -168,6 +168,10 @@ function normalizeDirectoryPattern(pattern) {
168
168
  // Extract the final path segment to check for extensions
169
169
  const lastSlashIndex = pattern.lastIndexOf('/');
170
170
  const finalSegment = lastSlashIndex >= 0 ? pattern.slice(lastSlashIndex + 1) : pattern;
171
+ // Dot-files that are files, not directories, must not get a trailing `/**`.
172
+ if (finalSegment === '.gitignore' || finalSegment === '.gitattributes') {
173
+ return pattern;
174
+ }
171
175
  // Check if final segment has a file extension (not counting leading dot in dot-directories)
172
176
  const hasExtension = finalSegment.startsWith('.')
173
177
  ? finalSegment.slice(1).includes('.') // For .github, .vscode: no extension. For .env.local: has extension
@@ -215,7 +215,7 @@ function appendTelemetryEvent(telemetryFile, event) {
215
215
  }
216
216
  function resolveTelemetryFilePath(state, repoRoot) {
217
217
  const artifactDir = state.artifact_dir ?? (0, node_path_1.join)(repoRoot, ".taskchain_artifacts", "polaris-run");
218
- return (0, node_path_1.join)(artifactDir, "runs", state.run_id, "telemetry.jsonl");
218
+ return (0, node_path_1.resolve)(repoRoot, artifactDir, "runs", state.run_id, "telemetry.jsonl");
219
219
  }
220
220
  function hasValidationEvidence(validation) {
221
221
  if (validation === undefined || validation === null)
@@ -383,7 +383,7 @@ function runLoopContinue(options) {
383
383
  // If no dispatch was recorded (dispatch_epoch === continue_epoch),
384
384
  // reject immediately and do NOT mutate any state.
385
385
  const artifactDirForTelemetry = state.artifact_dir ?? (0, node_path_1.join)(repoRoot, ".taskchain_artifacts", "polaris-run");
386
- const telemetryFileForCheck = (0, node_path_1.join)(artifactDirForTelemetry, "runs", state.run_id, "telemetry.jsonl");
386
+ const telemetryFileForCheck = (0, node_path_1.resolve)(repoRoot, artifactDirForTelemetry, "runs", state.run_id, "telemetry.jsonl");
387
387
  try {
388
388
  (0, dispatch_boundary_js_1.assertContinueRequiresDispatch)(state, telemetryFileForCheck);
389
389
  }
@@ -622,7 +622,7 @@ function runLoopContinue(options) {
622
622
  appendContinueLedgerEvents(repoRoot, updatedState, completedChild);
623
623
  // Step 2: Append JSONL checkpoint event
624
624
  const artifactDir = state.artifact_dir ?? (0, node_path_1.join)(repoRoot, ".taskchain_artifacts", "polaris-run");
625
- const telemetryFile = (0, node_path_1.join)(artifactDir, "runs", state.run_id, "telemetry.jsonl");
625
+ const telemetryFile = (0, node_path_1.resolve)(repoRoot, artifactDir, "runs", state.run_id, "telemetry.jsonl");
626
626
  (0, checkpoint_js_1.appendCheckpointEvent)(telemetryFile, {
627
627
  event: "loop-checkpoint",
628
628
  run_id: state.run_id,
@@ -373,7 +373,7 @@ function appendTelemetry(telemetryFile, event) {
373
373
  }
374
374
  function resolveTelemetryFile(state, repoRoot) {
375
375
  const artifactDir = state.artifact_dir ?? (0, node_path_1.join)(repoRoot, ".taskchain_artifacts", "polaris-run");
376
- return (0, node_path_1.join)(artifactDir, "runs", state.run_id, "telemetry.jsonl");
376
+ return (0, node_path_1.resolve)(repoRoot, artifactDir, "runs", state.run_id, "telemetry.jsonl");
377
377
  }
378
378
  function canonicalPath(path) {
379
379
  try {
@@ -177,12 +177,13 @@ function resetForRedispatch(stateFile, state, childId) {
177
177
  if (!childMeta?.dispatch_record)
178
178
  return (0, node_crypto_1.randomUUID)();
179
179
  const newDispatchId = (0, node_crypto_1.randomUUID)();
180
- // Clear dispatch record but retain audit trail via telemetry
180
+ // Clear dispatch record and result_file to prevent stale artifact reads
181
181
  const updatedMeta = {
182
182
  ...state.open_children_meta,
183
183
  [childId]: {
184
184
  ...childMeta,
185
185
  dispatch_record: undefined,
186
+ result_file: undefined,
186
187
  },
187
188
  };
188
189
  // Reset active_child so a new dispatch can proceed
@@ -120,7 +120,7 @@ function appendTelemetry(telemetryFile, event) {
120
120
  }
121
121
  function resolveTelemetryFile(state, repoRoot) {
122
122
  const artifactDir = state.artifact_dir ?? (0, node_path_1.join)(repoRoot, ".taskchain_artifacts", "polaris-run");
123
- return (0, node_path_1.join)(artifactDir, "runs", state.run_id, "telemetry.jsonl");
123
+ return (0, node_path_1.resolve)(repoRoot, artifactDir, "runs", state.run_id, "telemetry.jsonl");
124
124
  }
125
125
  function estimateTokensFromBytes(bytes) {
126
126
  return Math.round(bytes / 4);
@@ -685,6 +685,23 @@ async function runParentLoop(options) {
685
685
  if (!dryRun) {
686
686
  appendRunStartedLedgerEvent(repoRoot, state);
687
687
  }
688
+ // Keep the canonical cluster snapshot in sync with the taskchain state so
689
+ // `polaris status`, `polaris continue`, and `polaris finalize` always see
690
+ // the freshest state, even if the parent loop is interrupted before it
691
+ // reaches the final cluster-complete write.
692
+ const canonicalStatePath = (0, node_path_1.join)(repoRoot, ".polaris", "clusters", state.cluster_id, "state.json");
693
+ const writeStateAtomic = (filePath, stateToWrite) => {
694
+ (0, checkpoint_js_1.writeStateAtomic)(filePath, stateToWrite);
695
+ if ((0, node_path_1.resolve)(filePath) !== (0, node_path_1.resolve)(canonicalStatePath)) {
696
+ try {
697
+ (0, checkpoint_js_1.writeStateAtomic)(canonicalStatePath, stateToWrite);
698
+ }
699
+ catch (err) {
700
+ const msg = err instanceof Error ? err.message : String(err);
701
+ process.stderr.write(`[polaris] canonical cluster state snapshot at ${canonicalStatePath} failed to update and remains stale: ${msg}\n`);
702
+ }
703
+ }
704
+ };
688
705
  // Load config for adapter and provider resolution
689
706
  const config = (0, loader_js_1.loadConfig)(repoRoot);
690
707
  const legacyOrchestrationMode = state["orchestration_mode"];
@@ -1053,11 +1070,11 @@ async function runParentLoop(options) {
1053
1070
  onStateUpdate: (loopState) => {
1054
1071
  // Persist loop state to the state file on each mutation.
1055
1072
  const stateWithLoop = { ...state, qc_repair_loop: loopState };
1056
- (0, checkpoint_js_1.writeStateAtomic)(stateFile, stateWithLoop);
1073
+ writeStateAtomic(stateFile, stateWithLoop);
1057
1074
  },
1058
1075
  });
1059
1076
  state = { ...state, qc_repair_loop: repairLoopResult.loop_state };
1060
- (0, checkpoint_js_1.writeStateAtomic)(stateFile, state);
1077
+ writeStateAtomic(stateFile, state);
1061
1078
  appendTelemetry(telemetryFile, {
1062
1079
  event: "qc-repair-loop-finished",
1063
1080
  run_id: state.run_id,
@@ -1179,8 +1196,7 @@ async function runParentLoop(options) {
1179
1196
  if (!dryRun) {
1180
1197
  logStatus(notificationFormat, "COMPLETE");
1181
1198
  const clusterCompleteState = { ...state, status: "cluster-complete" };
1182
- (0, checkpoint_js_1.writeStateAtomic)(stateFile, clusterCompleteState);
1183
- (0, checkpoint_js_1.writeStateAtomic)((0, node_path_1.join)(repoRoot, '.polaris', 'clusters', state.cluster_id, 'state.json'), clusterCompleteState);
1199
+ writeStateAtomic(stateFile, clusterCompleteState);
1184
1200
  appendTelemetry(telemetryFile, {
1185
1201
  event: "cluster-complete",
1186
1202
  run_id: state.run_id,
@@ -1240,7 +1256,7 @@ async function runParentLoop(options) {
1240
1256
  if (budgetCheck.status === 'exhausted') {
1241
1257
  // Write checkpoint before halting
1242
1258
  if (!dryRun) {
1243
- (0, checkpoint_js_1.writeStateAtomic)(stateFile, {
1259
+ writeStateAtomic(stateFile, {
1244
1260
  ...state,
1245
1261
  status: "budget-exhausted",
1246
1262
  step_cursor: "budget-check",
@@ -1514,7 +1530,7 @@ async function runParentLoop(options) {
1514
1530
  step_cursor: "dispatch",
1515
1531
  dispatch_boundary: (0, dispatch_boundary_js_1.advanceDispatchEpoch)(state.dispatch_boundary, nextChild),
1516
1532
  };
1517
- (0, checkpoint_js_1.writeStateAtomic)(stateFile, stateWithDispatch);
1533
+ writeStateAtomic(stateFile, stateWithDispatch);
1518
1534
  state = stateWithDispatch;
1519
1535
  stateBeforeDispatch = stateWithDispatch;
1520
1536
  try {
@@ -1617,7 +1633,7 @@ async function runParentLoop(options) {
1617
1633
  // No result file was written. Roll back dispatch state so the run is
1618
1634
  // cleanly resumable without manual `loop abort` intervention.
1619
1635
  if (dispatchResult.pre_dispatch_failure && !dryRun) {
1620
- (0, checkpoint_js_1.writeStateAtomic)(stateFile, statePreDispatch);
1636
+ writeStateAtomic(stateFile, statePreDispatch);
1621
1637
  appendTelemetry(telemetryFile, {
1622
1638
  event: "pre-dispatch-failure",
1623
1639
  run_id: state.run_id,
@@ -1768,7 +1784,7 @@ async function runParentLoop(options) {
1768
1784
  timestamp: new Date().toISOString(),
1769
1785
  });
1770
1786
  // Write checkpoint with blocker information
1771
- (0, checkpoint_js_1.writeStateAtomic)(stateFile, {
1787
+ writeStateAtomic(stateFile, {
1772
1788
  ...state,
1773
1789
  status: "blocked",
1774
1790
  step_cursor: "blocked",
@@ -2153,7 +2169,7 @@ async function runParentLoop(options) {
2153
2169
  childrenDispatched += 1;
2154
2170
  // Worker did not write its own completion — orchestrator fills the gap.
2155
2171
  if (!dryRun) {
2156
- (0, checkpoint_js_1.writeStateAtomic)(stateFile, state);
2172
+ writeStateAtomic(stateFile, state);
2157
2173
  }
2158
2174
  }
2159
2175
  else {
@@ -2169,7 +2185,7 @@ async function runParentLoop(options) {
2169
2185
  }, nextChild);
2170
2186
  childrenDispatched += 1;
2171
2187
  if (!dryRun) {
2172
- (0, checkpoint_js_1.writeStateAtomic)(stateFile, state);
2188
+ writeStateAtomic(stateFile, state);
2173
2189
  }
2174
2190
  }
2175
2191
  // Orchestrator checkpoint event — always emitted after a successful child.
@@ -2269,7 +2285,7 @@ async function runParentLoop(options) {
2269
2285
  if (postBudgetCheck.status === 'exhausted') {
2270
2286
  const nextPending = state.open_children[0] ?? null;
2271
2287
  if (!dryRun) {
2272
- (0, checkpoint_js_1.writeStateAtomic)(stateFile, {
2288
+ writeStateAtomic(stateFile, {
2273
2289
  ...state,
2274
2290
  status: "budget-exhausted",
2275
2291
  step_cursor: "budget-check",
@@ -141,6 +141,8 @@ function runLoopBootstrapInit(options) {
141
141
  const runId = options.runId ?? deriveRunId(clusterId);
142
142
  const seal = createBootstrapSeal(runId, clusterId, openChildren);
143
143
  const deliveryBranch = branch ?? (0, git_custody_js_1.buildDeliveryBranchName)(clusterId);
144
+ const resolvedArtifactDir = (0, node_path_1.resolve)(repoRoot, artifactDir ?? (0, node_path_1.join)(repoRoot, ".taskchain_artifacts", "polaris-run"));
145
+ const artifactDirValue = (0, node_path_1.relative)(repoRoot, resolvedArtifactDir);
144
146
  const initialState = {
145
147
  schema_version: "1.0",
146
148
  run_id: runId,
@@ -159,7 +161,7 @@ function runLoopBootstrapInit(options) {
159
161
  },
160
162
  status: "running",
161
163
  next_open_child: openChildren[0] ?? null,
162
- artifact_dir: artifactDir ?? (0, node_path_1.join)(repoRoot, ".taskchain_artifacts", "polaris-run"),
164
+ artifact_dir: artifactDirValue,
163
165
  dispatch_boundary: (0, dispatch_boundary_js_2.initialDispatchBoundary)(),
164
166
  run_bootstrap_seal: seal,
165
167
  };
@@ -17,7 +17,10 @@ function inferRoute(filePath, repoRoot, config, knownRoutes, branchName) {
17
17
  const subdir = rest.split("/")[0];
18
18
  if (subdir && rest.includes("/")) {
19
19
  domain = subdir;
20
- route = `${sourceRoot}/${subdir}`;
20
+ // Route identity follows the file's own containing directory, not just the
21
+ // top-level domain subdir — otherwise nested folders (e.g. src/runtime/continuation)
22
+ // incorrectly report the parent domain's route (e.g. src/runtime).
23
+ route = (0, node_path_1.dirname)(filePath).replace(/\\/g, "/");
21
24
  taskchain = `polaris-${subdir}`;
22
25
  confidence += 0.85;
23
26
  tags.push(subdir);
@@ -114,11 +114,12 @@ function markInProgress(report, chartRef, repoRoot) {
114
114
  (0, index_js_1.markMedicDecision)(report.run_id, { status: "in-progress", chartRefs: [chartRef] }, repoRoot);
115
115
  }
116
116
  function markResolved(report, chartRef, treatmentRefs, outcome, repoRoot) {
117
+ const success = outcome === "treatment-success" || outcome === "no-treatment-needed";
117
118
  (0, index_js_1.markMedicDecision)(report.run_id, {
118
- status: "resolved",
119
+ status: success ? "resolved" : "in-progress",
119
120
  chartRefs: [chartRef],
120
121
  treatmentPacketRefs: treatmentRefs,
121
- resolvedAt: new Date().toISOString(),
122
+ resolvedAt: success ? new Date().toISOString() : undefined,
122
123
  resolutionNotes: `Terminal outcome: ${outcome}`,
123
124
  }, repoRoot);
124
125
  }
@@ -355,7 +356,7 @@ async function runMedicRunHealthConsult(input) {
355
356
  run_id: packet.run_id,
356
357
  cluster_id: packet.cluster_id,
357
358
  dispatch_id: packet.dispatch_id,
358
- status: "resolved",
359
+ status: "blocked",
359
360
  chart_id: chart.chart_id,
360
361
  decision: "terminal",
361
362
  treatment_packet_refs: allTreatmentRefs,
@@ -371,7 +372,7 @@ async function runMedicRunHealthConsult(input) {
371
372
  run_id: packet.run_id,
372
373
  cluster_id: packet.cluster_id,
373
374
  dispatch_id: packet.dispatch_id,
374
- status: "resolved",
375
+ status: remainingSymptoms.length === 0 ? "resolved" : "blocked",
375
376
  chart_id: chart.chart_id,
376
377
  decision: "terminal",
377
378
  treatment_packet_refs: allTreatmentRefs,
@@ -59,7 +59,7 @@ function buildTreatmentPackets(input) {
59
59
  return treated.map((symptom) => {
60
60
  const packetId = buildTreatmentPacketId(report.run_id, round, symptom.id);
61
61
  const dispatchId = (0, node_crypto_1.randomUUID)();
62
- const resultFile = (0, node_path_1.join)(getMedicDir(report.cluster_id, repoRoot), `${packetId}-result.json`);
62
+ const resultFile = (0, node_path_1.relative)(repoRoot, (0, node_path_1.join)(getMedicDir(report.cluster_id, repoRoot), `${packetId}-result.json`));
63
63
  return {
64
64
  packet_id: packetId,
65
65
  run_id: report.run_id,
@@ -154,12 +154,16 @@ async function dispatchTreatmentWorker(input) {
154
154
  const result = await dispatch(workerPacket);
155
155
  const summary = parseTreatmentWorkerSummary(result.summary);
156
156
  if (result.exit_code === 0 && summary?.status === "done") {
157
+ const completedTreatment = { ...treatment, status: "completed" };
158
+ writeTreatmentPacket({ treatment: completedTreatment, repoRoot });
157
159
  return {
158
160
  packet_id: treatment.packet_id,
159
161
  status: "success",
160
162
  commit_sha: summary.commit,
161
163
  };
162
164
  }
165
+ const failedTreatment = { ...treatment, status: "failed" };
166
+ writeTreatmentPacket({ treatment: failedTreatment, repoRoot });
163
167
  return {
164
168
  packet_id: treatment.packet_id,
165
169
  status: "failure",
@@ -148,6 +148,8 @@ function isErrorRecord(record) {
148
148
  return true;
149
149
  if (record.error === true || record.error === "true")
150
150
  return true;
151
+ if (typeof record.status === "string" && record.status.toLowerCase() === "error")
152
+ return true;
151
153
  return false;
152
154
  }
153
155
  function errorTypeFromRecord(record) {
@@ -150,6 +150,18 @@ function appendSymptom(runId, symptom, repoRoot) {
150
150
  symptoms: [...existing.symptoms, symptom],
151
151
  updated_at: new Date().toISOString(),
152
152
  };
153
+ // A later high/critical symptom means a previously resolved Medic consult is
154
+ // no longer satisfied; reopen it so closeout/medic gates do not pass on a
155
+ // stale resolved decision.
156
+ if (existing.medic_consult?.status === "resolved" &&
157
+ (symptom.severity === "critical" || symptom.severity === "high")) {
158
+ updated.medic_consult = {
159
+ ...existing.medic_consult,
160
+ status: "in-progress",
161
+ resolved_at: undefined,
162
+ resolution_notes: undefined,
163
+ };
164
+ }
153
165
  const validation = (0, schema_js_1.validateRunHealthReport)(updated);
154
166
  if (!validation.valid) {
155
167
  throw new Error(`Appended symptom produces invalid report:\n${validation.errors.join("\n")}`);