@lsctech/polaris 0.5.11 → 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.
Files changed (41) hide show
  1. package/dist/autoresearch/proposal.js +61 -0
  2. package/dist/autoresearch/score.js +124 -0
  3. package/dist/cli/adopt-canon.js +22 -8
  4. package/dist/cli/index.js +4 -0
  5. package/dist/cli/qc.js +97 -0
  6. package/dist/config/validator.js +16 -6
  7. package/dist/finalize/artifact-policy.js +12 -3
  8. package/dist/finalize/index.js +36 -21
  9. package/dist/finalize/run-report.js +189 -19
  10. package/dist/finalize/steps/05-generate-report.js +5 -1
  11. package/dist/finalize/steps/12-archive.js +6 -0
  12. package/dist/loop/adapters/terminal-cli.js +159 -25
  13. package/dist/loop/body-parser.js +73 -6
  14. package/dist/loop/continue.js +22 -4
  15. package/dist/loop/dispatch.js +103 -68
  16. package/dist/loop/orphan-recovery.js +2 -1
  17. package/dist/loop/parent.js +50 -15
  18. package/dist/loop/router/engine.js +1 -0
  19. package/dist/loop/run-bootstrap.js +3 -1
  20. package/dist/loop/run-preflight.js +4 -1
  21. package/dist/loop/worker-packet.js +81 -2
  22. package/dist/map/inference.js +4 -1
  23. package/dist/medic/routing-signals.js +60 -0
  24. package/dist/medic/run-health-consult.js +5 -4
  25. package/dist/medic/treatment-packets.js +5 -1
  26. package/dist/qc/policy.js +2 -0
  27. package/dist/qc/providers/coderabbit.js +140 -10
  28. package/dist/qc/repair-loop.js +147 -1
  29. package/dist/qc/repair-packets.js +16 -3
  30. package/dist/qc/routing.js +6 -0
  31. package/dist/qc/types.js +3 -0
  32. package/dist/run-health/index.js +12 -0
  33. package/dist/skill-packet/generator.js +234 -3
  34. package/dist/smartdocs-engine/canon-check.js +5 -5
  35. package/dist/smartdocs-engine/index.js +54 -0
  36. package/dist/smartdocs-engine/seed-instructions.js +159 -4
  37. package/dist/smartdocs-engine/validate-instructions.js +46 -4
  38. package/dist/tracker/local-graph.js +106 -3
  39. package/dist/workspace/POLARIS.md +4 -0
  40. package/dist/workspace/SUMMARY.md +56 -0
  41. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DRAFT_MARKER = void 0;
3
+ exports.TEMPLATE_VERSION_STAMP = exports.TEMPLATE_VERSION = exports.GENERATED_END_MARKER = exports.GENERATED_START_MARKER = exports.DRAFT_MARKER = void 0;
4
4
  exports.hasDraftMarker = hasDraftMarker;
5
5
  exports.generateDraft = generateDraft;
6
6
  exports.generateSummaryDraft = generateSummaryDraft;
@@ -12,6 +12,7 @@ exports.seedInstructionsAll = seedInstructionsAll;
12
12
  exports.seedSummaryAll = seedSummaryAll;
13
13
  exports.seedIndex = seedIndex;
14
14
  exports.seedIndexAll = seedIndexAll;
15
+ exports.reconcileAll = reconcileAll;
15
16
  const node_fs_1 = require("node:fs");
16
17
  const node_path_1 = require("node:path");
17
18
  const loader_js_1 = require("../config/loader.js");
@@ -19,6 +20,10 @@ const atlas_js_1 = require("../map/atlas.js");
19
20
  const smartdoc_ignore_js_1 = require("./smartdoc-ignore.js");
20
21
  const doctrine_js_1 = require("./doctrine.js");
21
22
  exports.DRAFT_MARKER = "<!-- polaris:draft -->";
23
+ exports.GENERATED_START_MARKER = "<!-- BEGIN POLARIS GENERATED -->";
24
+ exports.GENERATED_END_MARKER = "<!-- END POLARIS GENERATED -->";
25
+ exports.TEMPLATE_VERSION = 1;
26
+ exports.TEMPLATE_VERSION_STAMP = `<!-- polaris:template-version: ${exports.TEMPLATE_VERSION} -->`;
22
27
  function hasDraftMarker(filePath) {
23
28
  try {
24
29
  const content = (0, node_fs_1.readFileSync)(filePath, "utf-8");
@@ -121,7 +126,7 @@ function generateDraft(targetDir, repoRoot, allRoutes) {
121
126
  lines.push("## Related routes", "");
122
127
  lines.push("<!-- Atlas route pointer to sibling or parent folders. -->");
123
128
  lines.push("");
124
- return lines.join("\n");
129
+ return [exports.DRAFT_MARKER, exports.GENERATED_START_MARKER, exports.TEMPLATE_VERSION_STAMP, ...lines.slice(1), exports.GENERATED_END_MARKER].join("\n");
125
130
  }
126
131
  function generateSummaryDraft(targetDir, repoRoot, _allRoutes) {
127
132
  const dirLabel = (0, node_path_1.basename)(targetDir) || (0, node_path_1.basename)(repoRoot);
@@ -172,14 +177,51 @@ function generateSummaryDraft(targetDir, repoRoot, _allRoutes) {
172
177
  "```yaml",
173
178
  "canonical_docs:",
174
179
  " - POLARIS.md",
175
- "<!-- Add navigation paths to canonical docs, specs, or doctrine. These are retrieval paths, not reading assignments. -->",
176
180
  "```",
177
181
  "",
182
+ "<!-- Add navigation paths to canonical docs, specs, or doctrine. These are retrieval paths, not reading assignments. -->",
183
+ "",
178
184
  "## Known Drift",
179
185
  "<!-- Places where the summary may be stale (honesty field). -->",
180
186
  "",
181
187
  ];
182
- return lines.join("\n");
188
+ return [exports.DRAFT_MARKER, exports.GENERATED_START_MARKER, exports.TEMPLATE_VERSION_STAMP, ...lines.slice(1), exports.GENERATED_END_MARKER].join("\n");
189
+ }
190
+ function generateDraftRegion(targetDir, repoRoot, allRoutes) {
191
+ const full = generateDraft(targetDir, repoRoot, allRoutes);
192
+ const lines = full.split("\n");
193
+ // Remove the DRAFT marker, GENERATED_START_MARKER, and GENERATED_END_MARKER;
194
+ // the remaining lines are the region body, including the template-version stamp.
195
+ return lines.slice(2, -1).join("\n");
196
+ }
197
+ function generateSummaryDraftRegion(targetDir, repoRoot, allRoutes) {
198
+ const full = generateSummaryDraft(targetDir, repoRoot, allRoutes);
199
+ const lines = full.split("\n");
200
+ return lines.slice(2, -1).join("\n");
201
+ }
202
+ const TEMPLATE_VERSION_RE = /^<!-- polaris:template-version:\s*(\d+)\s*-->/;
203
+ function getGeneratedRegionVersion(content) {
204
+ const startIdx = content.indexOf(exports.GENERATED_START_MARKER);
205
+ if (startIdx === -1)
206
+ return null;
207
+ const startLineEnd = content.indexOf("\n", startIdx + exports.GENERATED_START_MARKER.length);
208
+ if (startLineEnd === -1)
209
+ return null;
210
+ const endIdx = content.indexOf(exports.GENERATED_END_MARKER, startLineEnd + 1);
211
+ if (endIdx === -1)
212
+ return null;
213
+ const region = content.slice(startLineEnd + 1, endIdx);
214
+ const firstLine = region.split("\n")[0] ?? "";
215
+ const match = TEMPLATE_VERSION_RE.exec(firstLine);
216
+ return match ? parseInt(match[1], 10) : undefined;
217
+ }
218
+ function replaceGeneratedRegion(content, newRegionBody) {
219
+ const startIdx = content.indexOf(exports.GENERATED_START_MARKER);
220
+ const startLineEnd = content.indexOf("\n", startIdx + exports.GENERATED_START_MARKER.length);
221
+ const endIdx = content.indexOf(exports.GENERATED_END_MARKER, startLineEnd + 1);
222
+ const before = content.slice(0, startLineEnd + 1);
223
+ const after = content.slice(endIdx);
224
+ return before + newRegionBody + "\n" + after;
183
225
  }
184
226
  const RESERVED_INDEX_NAMES = new Set(["index.md", "POLARIS.md", "SUMMARY.md", "log.md"]);
185
227
  function isReservedIndexName(name) {
@@ -569,3 +611,116 @@ function seedIndexAll(repoRoot, opts = {}) {
569
611
  }
570
612
  return { written, skippedExists, skippedDraft, skippedIneligible: [] };
571
613
  }
614
+ function reconcileFile(relDir, fileName, repoRoot, allRoutes, dryRun, generateFull, generateRegion) {
615
+ const absDir = (0, node_path_1.resolve)(repoRoot, relDir);
616
+ const relCheck = (0, node_path_1.relative)(repoRoot, absDir).replace(/\\/g, "/");
617
+ if (relCheck.startsWith("..") || relCheck.startsWith("/")) {
618
+ throw new Error(`Path traversal detected: target path is outside repo root`);
619
+ }
620
+ const outFile = (0, node_path_1.join)(absDir, fileName);
621
+ const relPath = (0, node_path_1.relative)(repoRoot, outFile).replace(/\\/g, "/");
622
+ if (!(0, node_fs_1.existsSync)(outFile)) {
623
+ const content = generateFull(relDir, repoRoot, allRoutes);
624
+ if (!dryRun) {
625
+ (0, node_fs_1.writeFileSync)(outFile, content, "utf-8");
626
+ }
627
+ return "created";
628
+ }
629
+ const content = (0, node_fs_1.readFileSync)(outFile, "utf-8");
630
+ if (content.includes(exports.DRAFT_MARKER)) {
631
+ const newContent = generateFull(relDir, repoRoot, allRoutes);
632
+ if (!dryRun) {
633
+ (0, node_fs_1.writeFileSync)(outFile, newContent, "utf-8");
634
+ }
635
+ return "regenerated";
636
+ }
637
+ const regionVersion = getGeneratedRegionVersion(content);
638
+ if (regionVersion !== null) {
639
+ if (regionVersion === exports.TEMPLATE_VERSION) {
640
+ return "skipped";
641
+ }
642
+ const newRegionBody = generateRegion(relDir, repoRoot, allRoutes);
643
+ if (!dryRun) {
644
+ const newContent = replaceGeneratedRegion(content, newRegionBody);
645
+ (0, node_fs_1.writeFileSync)(outFile, newContent, "utf-8");
646
+ }
647
+ return "regenerated-region";
648
+ }
649
+ return "blocked";
650
+ }
651
+ function reconcileAll(repoRoot, opts = {}) {
652
+ const config = (0, loader_js_1.loadConfig)(repoRoot);
653
+ const atlasPath = (0, node_path_1.resolve)(repoRoot, config.repo.sidecarOutputPath ?? ".polaris/map");
654
+ const allRoutes = {
655
+ ...(0, atlas_js_1.readFileRoutes)(atlasPath),
656
+ ...(0, atlas_js_1.readNeedsReview)(atlasPath),
657
+ };
658
+ // Root handling: skipped by default (root uses AGENTS.md/CLAUDE.md)
659
+ const rootEligibility = (0, smartdoc_ignore_js_1.isDirectoryEligible)(repoRoot, repoRoot, {
660
+ isRoot: true,
661
+ skipRoot: opts.includeRoot ? false : true,
662
+ });
663
+ let skippedRoot;
664
+ if (!rootEligibility.eligible) {
665
+ skippedRoot = { path: ".", reason: rootEligibility.reason || "root skipped" };
666
+ }
667
+ const eligibilityOpts = {
668
+ includeAgentFolders: opts.includeAgentFolders,
669
+ includeHidden: opts.includeHidden,
670
+ skipRoot: true,
671
+ };
672
+ const { eligible: dirs, ineligible: skippedIneligible } = collectDirs(repoRoot, repoRoot, eligibilityOpts);
673
+ const dirsToProcess = [];
674
+ if (rootEligibility.eligible) {
675
+ dirsToProcess.push(".");
676
+ }
677
+ dirsToProcess.push(...dirs);
678
+ const report = {
679
+ created: [],
680
+ regenerated: [],
681
+ regeneratedRegion: [],
682
+ skipped: [],
683
+ blocked: [],
684
+ skippedIneligible,
685
+ skippedRoot,
686
+ };
687
+ for (const relDir of dirsToProcess) {
688
+ const polarisStatus = reconcileFile(relDir, "POLARIS.md", repoRoot, allRoutes, opts.dryRun ?? false, generateDraft, generateDraftRegion);
689
+ switch (polarisStatus) {
690
+ case "created":
691
+ report.created.push(relDir === "." ? "POLARIS.md" : `${relDir}/POLARIS.md`);
692
+ break;
693
+ case "regenerated":
694
+ report.regenerated.push(relDir === "." ? "POLARIS.md" : `${relDir}/POLARIS.md`);
695
+ break;
696
+ case "regenerated-region":
697
+ report.regeneratedRegion.push(relDir === "." ? "POLARIS.md" : `${relDir}/POLARIS.md`);
698
+ break;
699
+ case "skipped":
700
+ report.skipped.push(relDir === "." ? "POLARIS.md" : `${relDir}/POLARIS.md`);
701
+ break;
702
+ case "blocked":
703
+ report.blocked.push(relDir === "." ? "POLARIS.md" : `${relDir}/POLARIS.md`);
704
+ break;
705
+ }
706
+ const summaryStatus = reconcileFile(relDir, "SUMMARY.md", repoRoot, allRoutes, opts.dryRun ?? false, generateSummaryDraft, generateSummaryDraftRegion);
707
+ switch (summaryStatus) {
708
+ case "created":
709
+ report.created.push(relDir === "." ? "SUMMARY.md" : `${relDir}/SUMMARY.md`);
710
+ break;
711
+ case "regenerated":
712
+ report.regenerated.push(relDir === "." ? "SUMMARY.md" : `${relDir}/SUMMARY.md`);
713
+ break;
714
+ case "regenerated-region":
715
+ report.regeneratedRegion.push(relDir === "." ? "SUMMARY.md" : `${relDir}/SUMMARY.md`);
716
+ break;
717
+ case "skipped":
718
+ report.skipped.push(relDir === "." ? "SUMMARY.md" : `${relDir}/SUMMARY.md`);
719
+ break;
720
+ case "blocked":
721
+ report.blocked.push(relDir === "." ? "SUMMARY.md" : `${relDir}/SUMMARY.md`);
722
+ break;
723
+ }
724
+ }
725
+ return report;
726
+ }
@@ -129,6 +129,32 @@ function parseReadBeforeEditingLinks(content) {
129
129
  }
130
130
  return links;
131
131
  }
132
+ function extractGeneratedRegion(content) {
133
+ const start = content.indexOf(seed_instructions_js_1.GENERATED_START_MARKER);
134
+ const end = content.indexOf(seed_instructions_js_1.GENERATED_END_MARKER);
135
+ if (start === -1 || end === -1 || start >= end)
136
+ return null;
137
+ return content.slice(start + seed_instructions_js_1.GENERATED_START_MARKER.length, end).trim();
138
+ }
139
+ function parseTemplateVersionStamp(content) {
140
+ const match = content.match(/<!--\s*polaris:template-version:\s*(\d+)\s*-->/);
141
+ if (!match)
142
+ return undefined;
143
+ return parseInt(match[1], 10);
144
+ }
145
+ function checkTemplateVersionStamp(content, fileLabel) {
146
+ const version = parseTemplateVersionStamp(content);
147
+ if (version === undefined) {
148
+ return { severity: "WARN", message: `${fileLabel} is unstamped (no template-version stamp)` };
149
+ }
150
+ if (version !== seed_instructions_js_1.TEMPLATE_VERSION) {
151
+ return {
152
+ severity: "WARN",
153
+ message: `${fileLabel} template version drift: found ${version}, expected ${seed_instructions_js_1.TEMPLATE_VERSION}`,
154
+ };
155
+ }
156
+ return null;
157
+ }
132
158
  function getRequiredDirs(repoRoot) {
133
159
  const config = (0, loader_js_1.loadConfig)(repoRoot);
134
160
  // The config type may not yet have a docs key; access dynamically
@@ -178,6 +204,14 @@ function validateDir(relDir, repoRoot, allRoutes, similarityThreshold = DEFAULT_
178
204
  }
179
205
  const content = (0, node_fs_1.readFileSync)(polarisFile, "utf-8");
180
206
  const findings = [];
207
+ const polarisGeneratedRaw = extractGeneratedRegion(content);
208
+ const polarisGenerated = polarisGeneratedRaw ?? content;
209
+ // Signal 0: template-version stamp inside generated region (only when present)
210
+ if (polarisGeneratedRaw !== null) {
211
+ const polarisStampFinding = checkTemplateVersionStamp(polarisGeneratedRaw, "POLARIS.md");
212
+ if (polarisStampFinding)
213
+ findings.push(polarisStampFinding);
214
+ }
181
215
  // Signal 1: ≥3 files in directory changed since last POLARIS.md git modification
182
216
  const lastMod = getLastGitModDate(polarisFile, repoRoot);
183
217
  if (lastMod !== null) {
@@ -191,7 +225,7 @@ function validateDir(relDir, repoRoot, allRoutes, similarityThreshold = DEFAULT_
191
225
  }
192
226
  }
193
227
  // Signal 2: broken links in "Read before editing"
194
- const links = parseReadBeforeEditingLinks(content);
228
+ const links = parseReadBeforeEditingLinks(polarisGenerated);
195
229
  for (const link of links) {
196
230
  const absLink = (0, node_path_1.resolve)(absDir, link);
197
231
  if (!(0, node_fs_1.existsSync)(absLink)) {
@@ -221,7 +255,7 @@ function validateDir(relDir, repoRoot, allRoutes, similarityThreshold = DEFAULT_
221
255
  const headingRe = /^##\s+(.+)$/gm;
222
256
  const parsedHeadings = [];
223
257
  let headingMatch;
224
- while ((headingMatch = headingRe.exec(content)) !== null) {
258
+ while ((headingMatch = headingRe.exec(polarisGenerated)) !== null) {
225
259
  const original = headingMatch[1].trim();
226
260
  parsedHeadings.push({
227
261
  normalized: original.toLowerCase(),
@@ -289,9 +323,17 @@ function validateDir(relDir, repoRoot, allRoutes, similarityThreshold = DEFAULT_
289
323
  else {
290
324
  // Signal 6: SUMMARY.md doctrine-bleed scan
291
325
  const summaryContent = (0, node_fs_1.readFileSync)(summaryFile, "utf-8");
326
+ const summaryGeneratedRaw = extractGeneratedRegion(summaryContent);
327
+ const summaryGenerated = summaryGeneratedRaw ?? summaryContent;
292
328
  const summaryRel = (0, node_path_1.relative)(repoRoot, summaryFile).replace(/\\/g, "/");
329
+ // Signal 5.5: SUMMARY.md template-version stamp (only inside generated region)
330
+ if (summaryGeneratedRaw !== null) {
331
+ const summaryStampFinding = checkTemplateVersionStamp(summaryGeneratedRaw, "SUMMARY.md");
332
+ if (summaryStampFinding)
333
+ findings.push(summaryStampFinding);
334
+ }
293
335
  const modalVerbs = ["must", "never", "always"];
294
- const lines = summaryContent.split("\n");
336
+ const lines = summaryGenerated.split("\n");
295
337
  for (let i = 0; i < lines.length; i++) {
296
338
  const line = lines[i].toLowerCase();
297
339
  for (const verb of modalVerbs) {
@@ -305,7 +347,7 @@ function validateDir(relDir, repoRoot, allRoutes, similarityThreshold = DEFAULT_
305
347
  }
306
348
  // Signal 7: pairwise POLARIS.md / SUMMARY.md drift
307
349
  const routeName = relDir === "." ? undefined : (0, node_path_1.basename)(relDir);
308
- const similarity = computeNormalizedSimilarity(content, summaryContent, routeName);
350
+ const similarity = computeNormalizedSimilarity(polarisGenerated, summaryGenerated, routeName);
309
351
  if (content === summaryContent) {
310
352
  findings.push({
311
353
  severity: "ERROR",
@@ -15,6 +15,7 @@ const migration_js_1 = require("./migration.js");
15
15
  */
16
16
  class LocalGraph {
17
17
  graph;
18
+ orderingDependenciesMerged = false;
18
19
  constructor(graph) {
19
20
  this.graph = graph;
20
21
  }
@@ -54,19 +55,76 @@ class LocalGraph {
54
55
  }
55
56
  /**
56
57
  * Persists the graph to `.polaris/clusters/{clusterId}/clusters.json`.
57
- * Creates the directory if it does not exist.
58
+ * Children are sorted topologically before writing so the saved cluster
59
+ * definition reflects dependency order.
58
60
  *
59
61
  * @param clusterId The cluster ID to use as the directory name (e.g., "POL-198").
60
62
  * @param repoRoot The root directory of the repository.
61
63
  * @returns The absolute path of the written file.
62
64
  */
63
65
  async save(clusterId, repoRoot = process.cwd()) {
66
+ this.mergeOrderingDependencies();
67
+ const persistedGraph = {
68
+ ...this.graph,
69
+ clusters: Object.fromEntries(Object.entries(this.graph.clusters).map(([id, cluster]) => [
70
+ id,
71
+ Array.isArray(cluster.children) && cluster.children.length > 1
72
+ ? { ...cluster, children: this.topoSortChildren(cluster.children) }
73
+ : cluster,
74
+ ])),
75
+ };
64
76
  const dir = node_path_1.default.join(repoRoot, ".polaris", "clusters", clusterId);
65
77
  await (0, promises_1.mkdir)(dir, { recursive: true });
66
78
  const filePath = node_path_1.default.join(dir, "clusters.json");
67
- await (0, promises_1.writeFile)(filePath, JSON.stringify(this.graph, null, 2), "utf-8");
79
+ await (0, promises_1.writeFile)(filePath, JSON.stringify(persistedGraph, null, 2), "utf-8");
68
80
  return filePath;
69
81
  }
82
+ /**
83
+ * Sort a list of children topologically using the graph dependencies.
84
+ * Children with no in-cluster dependencies come first; if a cycle exists,
85
+ * the remaining nodes are appended in their original order.
86
+ */
87
+ topoSortChildren(children) {
88
+ const childSet = new Set(children);
89
+ const inDegree = new Map();
90
+ const dependents = new Map();
91
+ for (const child of children) {
92
+ inDegree.set(child, 0);
93
+ dependents.set(child, []);
94
+ }
95
+ for (const child of children) {
96
+ for (const dep of this.getDependencies(child)) {
97
+ if (!childSet.has(dep))
98
+ continue;
99
+ inDegree.set(child, (inDegree.get(child) ?? 0) + 1);
100
+ dependents.get(dep).push(child);
101
+ }
102
+ }
103
+ const queue = [];
104
+ for (const [child, deg] of inDegree) {
105
+ if (deg === 0)
106
+ queue.push(child);
107
+ }
108
+ const sorted = [];
109
+ while (queue.length > 0) {
110
+ const node = queue.shift();
111
+ sorted.push(node);
112
+ for (const dependent of dependents.get(node) ?? []) {
113
+ const newDeg = (inDegree.get(dependent) ?? 1) - 1;
114
+ inDegree.set(dependent, newDeg);
115
+ if (newDeg === 0)
116
+ queue.push(dependent);
117
+ }
118
+ }
119
+ if (sorted.length < children.length) {
120
+ const sortedSet = new Set(sorted);
121
+ for (const child of children) {
122
+ if (!sortedSet.has(child))
123
+ sorted.push(child);
124
+ }
125
+ }
126
+ return sorted;
127
+ }
70
128
  /**
71
129
  * Returns the full v2 execution graph.
72
130
  */
@@ -87,12 +145,57 @@ class LocalGraph {
87
145
  return this.graph.nodes[id];
88
146
  }
89
147
  /**
90
- * Returns the dependencies for a given node.
148
+ * Returns the dependencies for a given node, merging any dependencies declared
149
+ * in the node's `## Ordering` body section.
91
150
  * @param id The ID of the node to get dependencies for.
92
151
  * @returns An array of node IDs that the given node is blocked by.
93
152
  */
94
153
  getDependencies(id) {
154
+ this.mergeOrderingDependencies();
95
155
  return this.graph.dependencies[id] ?? [];
96
156
  }
157
+ /**
158
+ * Extracts issue IDs from the `## Ordering` section of a node body.
159
+ *
160
+ * Only lines that explicitly declare ordering ("depends on" or "after" / "sequence after")
161
+ * contribute, and "before or after" clauses are ignored because they are not strict.
162
+ */
163
+ extractOrderingIds(body) {
164
+ const sectionMatch = body.match(/##\s*Ordering\b([\s\S]*?)(?:\n##\s|\n\n(?=\n##\s)|$)/i);
165
+ if (!sectionMatch)
166
+ return [];
167
+ const section = sectionMatch[1];
168
+ const cleaned = section.replace(/before\s+or\s+after\s*\[[^\]]*\]/gi, "");
169
+ const ids = new Set();
170
+ const re = /(?:depends\s+on|after)\s*\[?\s*(\w+-\d+)\b/gi;
171
+ for (const line of cleaned.split("\n")) {
172
+ if (!/^\s*[-*]\s/.test(line))
173
+ continue;
174
+ for (const match of line.matchAll(re)) {
175
+ ids.add(match[1]);
176
+ }
177
+ }
178
+ return [...ids];
179
+ }
180
+ /**
181
+ * Merges dependencies declared in node body `## Ordering` sections into the
182
+ * graph's explicit dependency map. This is idempotent for the loaded graph.
183
+ */
184
+ mergeOrderingDependencies() {
185
+ if (this.orderingDependenciesMerged)
186
+ return;
187
+ for (const node of Object.values(this.graph.nodes)) {
188
+ const orderingIds = this.extractOrderingIds(node.body ?? "");
189
+ if (orderingIds.length === 0)
190
+ continue;
191
+ const existing = new Set(this.graph.dependencies[node.id] ?? []);
192
+ for (const dep of orderingIds) {
193
+ if (dep !== node.id)
194
+ existing.add(dep);
195
+ }
196
+ this.graph.dependencies[node.id] = [...existing].sort();
197
+ }
198
+ this.orderingDependenciesMerged = true;
199
+ }
97
200
  }
98
201
  exports.LocalGraph = LocalGraph;
@@ -1,4 +1,6 @@
1
1
  <!-- polaris:draft -->
2
+ <!-- BEGIN POLARIS GENERATED -->
3
+ <!-- polaris:template-version: 1 -->
2
4
  # workspace
3
5
 
4
6
  > Polaris draft — review and remove the `<!-- polaris:draft -->` marker to promote.
@@ -38,3 +40,5 @@
38
40
  ## Related routes
39
41
 
40
42
  <!-- Atlas route pointer to sibling or parent folders. -->
43
+
44
+ <!-- END POLARIS GENERATED -->
@@ -0,0 +1,56 @@
1
+ <!-- polaris:draft -->
2
+ <!-- BEGIN POLARIS GENERATED -->
3
+ <!-- polaris:template-version: 1 -->
4
+ # Summary: workspace
5
+
6
+ > Polaris draft — review and remove the `<!-- polaris:draft -->` marker to promote.
7
+
8
+ ## Purpose
9
+ <!-- One-line statement of what this folder does. -->
10
+
11
+ ## Core Concepts
12
+ <!-- 3–7 key concepts a reader needs before diving into source. -->
13
+
14
+ ## Architectural Role
15
+ <!-- How this folder fits into the larger system. -->
16
+
17
+ ## Key Constraints
18
+ <!-- The most important non-obvious behavioral limits. -->
19
+
20
+ ## Important Relationships
21
+ <!-- Upstream/downstream dependencies on other folders. -->
22
+
23
+ ## Current State
24
+ <!-- What is implemented, what is not yet, known gaps. -->
25
+
26
+ ## Route Health
27
+ <!-- Current operational condition. Workers should understand route health in under 10 seconds. -->
28
+
29
+ ### Healthy
30
+ <!-- If the route is healthy, state why. Otherwise omit this subsection. -->
31
+
32
+ ### Monitoring
33
+ <!-- Any ongoing monitoring or observations. Omit if none. -->
34
+
35
+ ### Known Issues
36
+ <!-- Any known problems or risks. Omit if none. -->
37
+
38
+ ### Recent Treatments
39
+ <!-- Recent fixes or improvements, with chart references if applicable. Omit if none. -->
40
+
41
+ ### Improvement Opportunities
42
+ <!-- Potential future improvements. Omit if none. -->
43
+
44
+ ## Canonical References
45
+
46
+ ```yaml
47
+ canonical_docs:
48
+ - POLARIS.md
49
+ ```
50
+
51
+ <!-- Add navigation paths to canonical docs, specs, or doctrine. These are retrieval paths, not reading assignments. -->
52
+
53
+ ## Known Drift
54
+ <!-- Places where the summary may be stale (honesty field). -->
55
+
56
+ <!-- END POLARIS GENERATED -->
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lsctech/polaris",
3
- "version": "0.5.11",
3
+ "version": "0.5.13",
4
4
  "description": "Polaris — standalone taskchain orchestration framework for governed AI agent workflows",
5
5
  "bin": {
6
6
  "polaris": "dist/cli/index.js",