@lsctech/polaris 0.3.27 → 0.3.28

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.
@@ -106,6 +106,9 @@ function validateCloseoutLibrarianResult(value) {
106
106
  if (r["commit_sha"] !== null && typeof r["commit_sha"] !== "string") {
107
107
  errors.push("commit_sha must be a string or null");
108
108
  }
109
+ if (typeof r["commit_message"] !== "string") {
110
+ errors.push("commit_message must be a string");
111
+ }
109
112
  if (!Array.isArray(r["files_committed"])) {
110
113
  errors.push("files_committed must be an array");
111
114
  }
@@ -28,6 +28,7 @@ const SUPPORTED_EXECUTION_ROLES = [
28
28
  "orchestrator",
29
29
  "startup",
30
30
  "worker",
31
+ "foreman",
31
32
  "analyst",
32
33
  "analysis",
33
34
  "repair",
@@ -111,7 +111,7 @@ function checkLibrarianGate(repoRoot, clusterId) {
111
111
  })[0];
112
112
  if (!latestPacket) {
113
113
  return ("Closeout Librarian has not been dispatched for this cluster.\n" +
114
- ` 1. Generate packet: npm run polaris -- librarian packet ${clusterId} --state-file <state-file>\n` +
114
+ ` 1. Generate packet: npx polaris librarian packet ${clusterId} --state-file <state-file>\n` +
115
115
  ` 2. Dispatch the Librarian with the generated packet path as the sole session prompt.\n` +
116
116
  ` 3. Wait for the Librarian to write its sealed result.\n` +
117
117
  ` 4. Re-run finalize.\n` +
@@ -122,11 +122,11 @@ function checkLibrarianGate(repoRoot, clusterId) {
122
122
  packetJson = JSON.parse((0, node_fs_1.readFileSync)(latestPacket, "utf-8"));
123
123
  }
124
124
  catch {
125
- return `Cannot read Librarian packet at ${latestPacket}. Regenerate with: npm run polaris -- librarian packet ${clusterId}`;
125
+ return `Cannot read Librarian packet at ${latestPacket}. Regenerate with: npx polaris librarian packet ${clusterId}`;
126
126
  }
127
127
  const resultPath = packetJson["result_path"];
128
128
  if (typeof resultPath !== "string") {
129
- return `Librarian packet is malformed: missing result_path. Regenerate with: npm run polaris -- librarian packet ${clusterId}`;
129
+ return `Librarian packet is malformed: missing result_path. Regenerate with: npx polaris librarian packet ${clusterId}`;
130
130
  }
131
131
  if (!(0, node_fs_1.existsSync)(resultPath)) {
132
132
  return ("Closeout Librarian has not written its sealed result yet.\n" +
@@ -152,16 +152,20 @@ function checkLibrarianGate(repoRoot, clusterId) {
152
152
  if (packetJson["run_id"] !== result.run_id) {
153
153
  return `Librarian result run_id mismatch (packet: ${packetJson["run_id"]}, result: ${result.run_id}).`;
154
154
  }
155
- // Validate files_committed against packet scope constraints
155
+ // Validate files_committed against packet scope constraints.
156
+ // allowed_write_paths and prohibited_write_paths in the packet are absolute paths.
157
+ // files_committed in the result are repo-relative paths (git output). Resolve each
158
+ // to absolute before comparing so the patterns match correctly.
156
159
  const allowedWritePaths = (packetJson["allowed_write_paths"] ?? []);
157
160
  const prohibitedWritePaths = (packetJson["prohibited_write_paths"] ?? []);
158
161
  const filesCommitted = result.files_committed ?? [];
159
162
  for (const file of filesCommitted) {
160
- const prohibitedMatch = prohibitedWritePaths.find((pattern) => (0, git_custody_js_1.patternMatchesPath)(pattern, file));
163
+ const absFile = (0, node_path_1.resolve)(repoRoot, file);
164
+ const prohibitedMatch = prohibitedWritePaths.find((pattern) => (0, git_custody_js_1.patternMatchesPath)(pattern, absFile));
161
165
  if (prohibitedMatch) {
162
166
  return `Librarian wrote to prohibited path: ${file} (matched pattern: ${prohibitedMatch})`;
163
167
  }
164
- const allowedMatch = allowedWritePaths.find((pattern) => (0, git_custody_js_1.patternMatchesPath)(pattern, file));
168
+ const allowedMatch = allowedWritePaths.find((pattern) => (0, git_custody_js_1.patternMatchesPath)(pattern, absFile));
165
169
  if (allowedMatch)
166
170
  continue; // explicitly allowed
167
171
  return `Librarian wrote to out-of-scope path: ${file} (not in allowed_write_paths)`;
@@ -31,7 +31,7 @@ function appendBlockedLedgerEvent(repoRoot, state, childId, reason) {
31
31
  timestamp: new Date().toISOString(),
32
32
  blocker: {
33
33
  summary: reason,
34
- unblock_condition: `Resolve blocker then run: npm run polaris -- loop run ${state.cluster_id}`,
34
+ unblock_condition: `Resolve blocker then run: npx polaris loop run ${state.cluster_id}`,
35
35
  },
36
36
  });
37
37
  }
@@ -183,6 +183,6 @@ function runLoopAbort(options) {
183
183
  reason,
184
184
  timestamp,
185
185
  });
186
- process.stderr.write(`Loop aborted. Reason: ${reason}. Resolve blocker then run: npm run polaris -- loop run ${updatedState.cluster_id}\n`);
186
+ process.stderr.write(`Loop aborted. Reason: ${reason}. Resolve blocker then run: npx polaris loop run ${updatedState.cluster_id}\n`);
187
187
  process.exit(0);
188
188
  }
@@ -49,7 +49,7 @@ const node_path_1 = require("node:path");
49
49
  * Error emitted when the parent/orchestrator attempts inline child execution.
50
50
  * This is the canonical error message for dispatch boundary violations.
51
51
  */
52
- exports.INLINE_EXECUTION_ERROR = "Child execution requires dispatch boundary. Parent/orchestrator may not implement child inline. Use npm run polaris -- loop dispatch.";
52
+ exports.INLINE_EXECUTION_ERROR = "Child execution requires dispatch boundary. Parent/orchestrator may not implement child inline. Use npx polaris loop dispatch.";
53
53
  /**
54
54
  * Error emitted when PR creation is attempted without a passing Librarian gate.
55
55
  */
@@ -57,7 +57,7 @@ exports.LIBRARIAN_GATE_ERROR = "PR creation requires Closeout Librarian gate. Ge
57
57
  /**
58
58
  * Error emitted when `polaris loop continue` is called without a prior dispatch.
59
59
  */
60
- exports.DISPATCH_REQUIRED_ERROR = "Dispatch required before continuation. Call `npm run polaris -- loop dispatch` first, then run the worker before calling continue.";
60
+ exports.DISPATCH_REQUIRED_ERROR = "Dispatch required before continuation. Call `npx polaris loop dispatch` first, then run the worker before calling continue.";
61
61
  /**
62
62
  * Allowed transitions in the dispatch state machine.
63
63
  * Format: [from, to, command-that-drives-transition]
@@ -186,7 +186,7 @@ function assertNoActiveChildBeforeDispatch(state, telemetryFile) {
186
186
  });
187
187
  throw new Error(`active_child is already set to "${state.active_child}". Previous dispatch was not completed. ` +
188
188
  exports.INLINE_EXECUTION_ERROR +
189
- " Use `npm run polaris -- loop abort` to reset blocked state.");
189
+ " Use `npx polaris loop abort` to reset blocked state.");
190
190
  }
191
191
  }
192
192
  /**
@@ -41,7 +41,7 @@ const git_custody_js_1 = require("./git-custody.js");
41
41
  // ──────────────────────────────────────────────────────────────────────────────
42
42
  exports.BOOTSTRAP_REQUIRED_ERROR = "Run state must be initialized through 'polaris loop bootstrap' before dispatch. " +
43
43
  "Parent sessions may not hand-create run state. " +
44
- "Use: npm run polaris -- loop bootstrap --cluster-id <ID> --children <CSV>";
44
+ "Use: npx polaris loop bootstrap --cluster-id <ID> --children <CSV>";
45
45
  // ──────────────────────────────────────────────────────────────────────────────
46
46
  // Seal helpers
47
47
  // ──────────────────────────────────────────────────────────────────────────────
@@ -229,6 +229,6 @@ function runLoopBootstrapInit(options) {
229
229
  process.stderr.write(`Bootstrap complete. State initialized at: ${stateFile}\n` +
230
230
  `Run ID: ${runId}\n` +
231
231
  `Children: ${openChildren.join(", ")}\n` +
232
- `Next: npm run polaris -- loop dispatch\n`);
232
+ `Next: npx polaris loop dispatch\n`);
233
233
  return clusterStateInitPromise;
234
234
  }
@@ -24,7 +24,7 @@ async function loadOrSyncGraph(clusterId, repoRoot) {
24
24
  }
25
25
  const config = (0, loader_js_1.loadConfig)(repoRoot);
26
26
  if (!config.tracker?.linear?.enabled) {
27
- throw new Error(`No local cluster graph found for ${clusterId}. Run 'npm run polaris -- tracker sync-in ${clusterId}' or bootstrap with --children.`);
27
+ throw new Error(`No local cluster graph found for ${clusterId}. Run 'npx polaris tracker sync-in ${clusterId}' or bootstrap with --children.`);
28
28
  }
29
29
  const graph = await new index_js_1.LinearAdapter(config).syncIn(clusterId);
30
30
  await graph.save(clusterId, repoRoot);
@@ -74,7 +74,7 @@ function topoSort(children, getDeps) {
74
74
  }
75
75
  return sorted;
76
76
  }
77
- function buildBootstrapPlan(clusterId, graph) {
77
+ function buildBootstrapPlan(clusterId, graph, repoRoot) {
78
78
  const activeCluster = graph.getActiveCluster();
79
79
  // Exclude the cluster root from runnable children when it has implementation
80
80
  // children. The root is a context/coordination node. If after filtering nothing
@@ -85,7 +85,13 @@ function buildBootstrapPlan(clusterId, graph) {
85
85
  ? activeCluster.children.filter((id) => id !== clusterRoot)
86
86
  : activeCluster.children;
87
87
  const unsortedChildren = runnableChildren.length > 0 ? runnableChildren : [clusterId];
88
- const openChildren = topoSort(unsortedChildren, (id) => graph.getDependencies(id));
88
+ // If a cluster-plan.json exists, use its children order as the preferred
89
+ // tiebreaker within each ready wave of the topological sort. The plan order
90
+ // reflects the analyst's intended sequencing; clusters.json order is whatever
91
+ // the tracker returned, which may be arbitrary.
92
+ const planOrder = readClusterPlanOrder(clusterId, repoRoot);
93
+ const preferredOrder = applyPlanOrder(unsortedChildren, planOrder);
94
+ const openChildren = topoSort(preferredOrder, (id) => graph.getDependencies(id));
89
95
  const openChildrenMeta = {};
90
96
  const clusterNode = graph.getNode(clusterId);
91
97
  openChildrenMeta[clusterId] = {
@@ -102,6 +108,29 @@ function buildBootstrapPlan(clusterId, graph) {
102
108
  }
103
109
  return { openChildren, openChildrenMeta };
104
110
  }
111
+ function readClusterPlanOrder(clusterId, repoRoot) {
112
+ const planPath = (0, node_path_1.join)(repoRoot, ".polaris", "clusters", clusterId, "cluster-plan.json");
113
+ if (!(0, node_fs_1.existsSync)(planPath))
114
+ return [];
115
+ try {
116
+ const plan = JSON.parse((0, node_fs_1.readFileSync)(planPath, "utf-8"));
117
+ if (Array.isArray(plan.children)) {
118
+ return plan.children.map((c) => c.id).filter(Boolean);
119
+ }
120
+ }
121
+ catch {
122
+ // malformed plan — fall back to no preferred order
123
+ }
124
+ return [];
125
+ }
126
+ function applyPlanOrder(children, planOrder) {
127
+ if (planOrder.length === 0)
128
+ return children;
129
+ const childSet = new Set(children);
130
+ const ordered = planOrder.filter((id) => childSet.has(id));
131
+ const remaining = children.filter((id) => !ordered.includes(id));
132
+ return [...ordered, ...remaining];
133
+ }
105
134
  function preserveMismatchedState(stateFile) {
106
135
  const suffix = (() => {
107
136
  try {
@@ -191,7 +220,7 @@ async function ensureClusterRunState(options) {
191
220
  }
192
221
  }
193
222
  }
194
- const { openChildren, openChildrenMeta } = buildBootstrapPlan(clusterId, await loadOrSyncGraph(clusterId, repoRoot));
223
+ const { openChildren, openChildrenMeta } = buildBootstrapPlan(clusterId, await loadOrSyncGraph(clusterId, repoRoot), repoRoot);
195
224
  await bootstrapHandler({
196
225
  clusterId,
197
226
  openChildren,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lsctech/polaris",
3
- "version": "0.3.27",
3
+ "version": "0.3.28",
4
4
  "description": "Polaris — standalone taskchain orchestration framework for governed AI agent workflows",
5
5
  "bin": {
6
6
  "polaris": "dist/cli/index.js",