@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.
- package/dist/cognition/closeout-librarian-types.js +3 -0
- package/dist/config/validator.js +1 -0
- package/dist/finalize/index.js +10 -6
- package/dist/loop/abort.js +2 -2
- package/dist/loop/dispatch-boundary.js +3 -3
- package/dist/loop/run-bootstrap.js +2 -2
- package/dist/loop/run-preflight.js +33 -4
- package/package.json +1 -1
|
@@ -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
|
}
|
package/dist/config/validator.js
CHANGED
package/dist/finalize/index.js
CHANGED
|
@@ -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:
|
|
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:
|
|
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:
|
|
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
|
|
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,
|
|
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)`;
|
package/dist/loop/abort.js
CHANGED
|
@@ -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:
|
|
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:
|
|
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
|
|
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 `
|
|
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 `
|
|
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:
|
|
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:
|
|
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 '
|
|
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
|
-
|
|
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,
|