@lsctech/polaris 0.5.4 → 0.5.6

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.
@@ -1,19 +1,26 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createAutoresearchCommand = createAutoresearchCommand;
3
+ exports.createAutoresearchCommand = void 0;
4
+ exports.createSolCommand = createSolCommand;
4
5
  const node_path_1 = require("node:path");
5
6
  const commander_1 = require("commander");
6
7
  const dev_gate_js_1 = require("../autoresearch/dev-gate.js");
7
8
  const score_js_1 = require("../autoresearch/score.js");
8
9
  const proposal_js_1 = require("../autoresearch/proposal.js");
9
10
  const routing_js_1 = require("../autoresearch/routing.js");
10
- function createAutoresearchCommand(options) {
11
+ const sol_evidence_loader_js_1 = require("../autoresearch/sol-evidence-loader.js");
12
+ const sol_scorer_js_1 = require("../autoresearch/sol-scorer.js");
13
+ const sol_history_js_1 = require("../autoresearch/sol-history.js");
14
+ const sol_report_js_1 = require("../autoresearch/sol-report.js");
15
+ const sol_recommendations_js_1 = require("../autoresearch/sol-recommendations.js");
16
+ function createSolCommand(options) {
11
17
  const repoRoot = options.repoRoot;
12
- const autoresearch = new commander_1.Command("autoresearch")
13
- .description("Autoresearch tools (dev-gated — Polaris development context only)")
18
+ const sol = new commander_1.Command("sol")
19
+ .alias("autoresearch")
20
+ .description("Self-Optimization Loop (SOL) tools — autoresearch compatibility alias (dev-gated — Polaris development context only)")
14
21
  .showHelpAfterError()
15
22
  .showSuggestionAfterError();
16
- autoresearch
23
+ sol
17
24
  .command("score <run-id>")
18
25
  .description("Score a completed Polaris run against the binary gate scorecard and output a diagnosis report")
19
26
  .option("-r, --repo-root <path>", "Repository root", repoRoot)
@@ -35,11 +42,39 @@ function createAutoresearchCommand(options) {
35
42
  process.stdout.write(`${output}\n`);
36
43
  }
37
44
  catch (err) {
38
- process.stderr.write(`autoresearch score error: ${err instanceof Error ? err.message : String(err)}\n`);
45
+ process.stderr.write(`sol score error: ${err instanceof Error ? err.message : String(err)}\n`);
39
46
  process.exit(1);
40
47
  }
41
48
  });
42
- autoresearch
49
+ sol
50
+ .command("score-report <run-id>")
51
+ .description("Compute a SOL score report with diagnostic sub-scores for Foreman and Workers")
52
+ .option("-r, --repo-root <path>", "Repository root", repoRoot)
53
+ .option("--json", "Output raw JSON (default: pretty-printed)")
54
+ .action((runId, cmdOptions) => {
55
+ const root = (0, node_path_1.resolve)(cmdOptions.repoRoot ?? repoRoot);
56
+ try {
57
+ (0, dev_gate_js_1.assertPolarisDevContext)(root);
58
+ }
59
+ catch (err) {
60
+ process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
61
+ process.exit(1);
62
+ }
63
+ try {
64
+ const artifacts = (0, score_js_1.loadRunArtifacts)(root, runId);
65
+ const evidence = (0, sol_evidence_loader_js_1.aggregateSolEvidence)(artifacts);
66
+ const report = (0, sol_scorer_js_1.computeSolScoreReport)(evidence);
67
+ const output = cmdOptions.json
68
+ ? JSON.stringify(report)
69
+ : JSON.stringify(report, null, 2);
70
+ process.stdout.write(`${output}\n`);
71
+ }
72
+ catch (err) {
73
+ process.stderr.write(`sol score-report error: ${err instanceof Error ? err.message : String(err)}\n`);
74
+ process.exit(1);
75
+ }
76
+ });
77
+ sol
43
78
  .command("propose <diagnosis-file>")
44
79
  .description("File Linear improvement proposals from a diagnosis report (dev-gated — never auto-applied)")
45
80
  .option("-r, --repo-root <path>", "Repository root", repoRoot)
@@ -60,7 +95,7 @@ function createAutoresearchCommand(options) {
60
95
  report = (0, proposal_js_1.loadDiagnosisReport)((0, node_path_1.resolve)(diagnosisFile));
61
96
  }
62
97
  catch (err) {
63
- process.stderr.write(`autoresearch propose: invalid diagnosis file: ${err instanceof Error ? err.message : String(err)}\n`);
98
+ process.stderr.write(`sol propose: invalid diagnosis file: ${err instanceof Error ? err.message : String(err)}\n`);
64
99
  process.exit(1);
65
100
  }
66
101
  const proposals = (0, proposal_js_1.buildProposals)(report);
@@ -70,7 +105,7 @@ function createAutoresearchCommand(options) {
70
105
  }
71
106
  const apiKey = process.env["LINEAR_API_KEY"];
72
107
  if (!apiKey && !cmdOptions.dryRun) {
73
- process.stderr.write("autoresearch propose: LINEAR_API_KEY environment variable is required.\n");
108
+ process.stderr.write("sol propose: LINEAR_API_KEY environment variable is required.\n");
74
109
  process.exit(1);
75
110
  }
76
111
  try {
@@ -88,9 +123,148 @@ function createAutoresearchCommand(options) {
88
123
  }
89
124
  }
90
125
  catch (err) {
91
- process.stderr.write(`autoresearch propose error: ${err instanceof Error ? err.message : String(err)}\n`);
126
+ process.stderr.write(`sol propose error: ${err instanceof Error ? err.message : String(err)}\n`);
127
+ process.exit(1);
128
+ }
129
+ });
130
+ sol
131
+ .command("recommend")
132
+ .description("Generate SOL routing recommendations from historical snapshots (advisory by default; filing is Polaris-dev only)")
133
+ .option("-r, --repo-root <path>", "Repository root", repoRoot)
134
+ .option("--history-path <path>", "Custom history directory (relative to repo root)")
135
+ .option("--group-by <dims>", "Comma-separated grouping dimensions (provider,model,role,route,task_type,repo,risk,worker_id,run_id,time_window)", "provider,model,role,route,task_type")
136
+ .option("--threshold <n>", "Mean composite threshold below which a recommendation is triggered", "0.7")
137
+ .option("--min-samples <n>", "Minimum snapshots per group before recommending", "2")
138
+ .option("--file", "File review-gated tracker proposals (requires Polaris dev context)")
139
+ .option("--team <team>", "Tracker team name or ID", "Polaris")
140
+ .option("--dry-run", "Show tracker mutations without writing to the tracker")
141
+ .option("--json", "Output raw JSON (default: human-readable)")
142
+ .action(async (cmdOptions) => {
143
+ const root = (0, node_path_1.resolve)(cmdOptions.repoRoot ?? repoRoot);
144
+ let snapshots;
145
+ try {
146
+ snapshots = (0, sol_history_js_1.loadSnapshots)(root, cmdOptions.historyPath);
147
+ }
148
+ catch (err) {
149
+ process.stderr.write(`sol recommend error: ${err instanceof Error ? err.message : String(err)}\n`);
150
+ process.exit(1);
151
+ }
152
+ const groupBy = cmdOptions.groupBy.split(",").filter(Boolean);
153
+ const threshold = parseFloat(cmdOptions.threshold) || 0.7;
154
+ const minSamples = parseInt(cmdOptions.minSamples, 10) || 2;
155
+ const report = (0, sol_recommendations_js_1.generateRecommendations)(snapshots, { groupBy, threshold, minSamples });
156
+ // Advisory mode: never touches the tracker or filesystem.
157
+ if (!cmdOptions.file) {
158
+ if (cmdOptions.json) {
159
+ process.stdout.write(JSON.stringify(report) + "\n");
160
+ }
161
+ else {
162
+ process.stdout.write((0, sol_recommendations_js_1.formatRecommendationsCli)(report));
163
+ }
164
+ process.exit(0);
165
+ }
166
+ // Filing mode: Polaris dev context only.
167
+ try {
168
+ (0, dev_gate_js_1.assertPolarisDevContext)(root);
169
+ }
170
+ catch (err) {
171
+ process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
172
+ process.exit(1);
173
+ }
174
+ if (report.recommendations.length === 0) {
175
+ process.stdout.write("No underperforming groups — nothing to file.\n");
176
+ process.exit(0);
177
+ }
178
+ const apiKey = process.env["LINEAR_API_KEY"];
179
+ if (!apiKey && !cmdOptions.dryRun) {
180
+ process.stderr.write("sol recommend: LINEAR_API_KEY environment variable is required.\n");
181
+ process.exit(1);
182
+ }
183
+ const proposals = (0, sol_recommendations_js_1.recommendationsToProposals)(report.recommendations);
184
+ try {
185
+ const result = await (0, routing_js_1.routeProposals)(proposals, {
186
+ apiKey: apiKey ?? "",
187
+ teamKey: cmdOptions.team,
188
+ dryRun: cmdOptions.dryRun,
189
+ });
190
+ const output = cmdOptions.json ? JSON.stringify(result) : JSON.stringify(result, null, 2);
191
+ process.stdout.write(`${output}\n`);
192
+ if (result.total_errors > 0) {
193
+ process.exit(1);
194
+ }
195
+ }
196
+ catch (err) {
197
+ process.stderr.write(`sol recommend error: ${err instanceof Error ? err.message : String(err)}\n`);
198
+ process.exit(1);
199
+ }
200
+ });
201
+ // ── history subcommand group ──
202
+ const history = new commander_1.Command("history")
203
+ .description("SOL historical performance storage and reports");
204
+ history
205
+ .command("save <run-id>")
206
+ .description("Score a run and persist the SOL score snapshot to local history")
207
+ .option("-r, --repo-root <path>", "Repository root", repoRoot)
208
+ .option("--history-path <path>", "Custom history directory (relative to repo root)")
209
+ .action((runId, cmdOptions) => {
210
+ const root = (0, node_path_1.resolve)(cmdOptions.repoRoot ?? repoRoot);
211
+ try {
212
+ (0, dev_gate_js_1.assertPolarisDevContext)(root);
213
+ }
214
+ catch (err) {
215
+ process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
216
+ process.exit(1);
217
+ }
218
+ try {
219
+ const artifacts = (0, score_js_1.loadRunArtifacts)(root, runId);
220
+ const evidence = (0, sol_evidence_loader_js_1.aggregateSolEvidence)(artifacts);
221
+ const report = (0, sol_scorer_js_1.computeSolScoreReport)(evidence);
222
+ const workerIds = evidence.children.map((c) => c.worker_id);
223
+ const snapshot = (0, sol_history_js_1.buildSnapshot)(report, evidence.grouping_keys, workerIds);
224
+ const path = (0, sol_history_js_1.appendSnapshot)(root, snapshot, cmdOptions.historyPath);
225
+ process.stdout.write(`Snapshot saved to ${path}\n`);
226
+ }
227
+ catch (err) {
228
+ process.stderr.write(`sol history save error: ${err instanceof Error ? err.message : String(err)}\n`);
229
+ process.exit(1);
230
+ }
231
+ });
232
+ history
233
+ .command("report")
234
+ .description("Generate a report from SOL historical snapshots")
235
+ .option("-r, --repo-root <path>", "Repository root", repoRoot)
236
+ .option("--history-path <path>", "Custom history directory (relative to repo root)")
237
+ .option("--group-by <dims>", "Comma-separated grouping dimensions (repo,route,task_type,role,risk,provider,model,worker_id,run_id,time_window)", "run_id")
238
+ .option("--window-days <days>", "Time window size in days for time_window grouping", "7")
239
+ .option("--json", "Output raw JSON (default: human-readable table)")
240
+ .action((cmdOptions) => {
241
+ const root = (0, node_path_1.resolve)(cmdOptions.repoRoot ?? repoRoot);
242
+ try {
243
+ (0, dev_gate_js_1.assertPolarisDevContext)(root);
244
+ }
245
+ catch (err) {
246
+ process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
247
+ process.exit(1);
248
+ }
249
+ try {
250
+ const snapshots = (0, sol_history_js_1.loadSnapshots)(root, cmdOptions.historyPath);
251
+ const groupBy = cmdOptions.groupBy.split(",").filter(Boolean);
252
+ const windowDays = parseInt(cmdOptions.windowDays, 10) || 7;
253
+ const report = (0, sol_report_js_1.generateReport)(snapshots, { groupBy, windowDays });
254
+ if (cmdOptions.json) {
255
+ process.stdout.write(JSON.stringify(report) + "\n");
256
+ }
257
+ else {
258
+ process.stdout.write((0, sol_report_js_1.formatReportCli)(report));
259
+ }
260
+ }
261
+ catch (err) {
262
+ process.stderr.write(`sol history report error: ${err instanceof Error ? err.message : String(err)}\n`);
92
263
  process.exit(1);
93
264
  }
94
265
  });
95
- return autoresearch;
266
+ sol.addCommand(history);
267
+ return sol;
96
268
  }
269
+ /** @deprecated Use {@link createSolCommand}. */
270
+ exports.createAutoresearchCommand = createSolCommand;
package/dist/cli/index.js CHANGED
@@ -35,8 +35,25 @@ function resolveStateFile(repoRoot, explicit) {
35
35
  return (0, node_path_1.resolve)(explicit);
36
36
  const taskchainPath = (0, node_path_1.join)(repoRoot, ".taskchain_artifacts", "polaris-run", "current-state.json");
37
37
  const polarisPath = (0, node_path_1.join)(repoRoot, ".polaris", "runs", "current-state.json");
38
- if ((0, node_fs_1.existsSync)(taskchainPath))
38
+ // Prefer canonical cluster-scoped state.json when it exists — finalize
39
+ // rejects the compatibility/debug taskchain path as the sole state source.
40
+ // Read cluster_id from the taskchain path if available, then check the
41
+ // canonical path bootstrapped alongside it.
42
+ if ((0, node_fs_1.existsSync)(taskchainPath)) {
43
+ try {
44
+ const raw = JSON.parse((0, node_fs_1.readFileSync)(taskchainPath, "utf-8"));
45
+ const clusterId = typeof raw["cluster_id"] === "string" ? raw["cluster_id"] : undefined;
46
+ if (clusterId) {
47
+ const canonicalPath = (0, node_path_1.join)(repoRoot, ".polaris", "clusters", clusterId, "state.json");
48
+ if ((0, node_fs_1.existsSync)(canonicalPath))
49
+ return canonicalPath;
50
+ }
51
+ }
52
+ catch {
53
+ // Fall through to taskchain path
54
+ }
39
55
  return taskchainPath;
56
+ }
40
57
  if ((0, node_fs_1.existsSync)(polarisPath))
41
58
  return polarisPath;
42
59
  return taskchainPath;
@@ -112,7 +129,7 @@ function createPolarisCommand(options = {}) {
112
129
  }));
113
130
  program.addCommand((0, adopt_command_js_1.createAdoptCommand)({ repoRoot }));
114
131
  program.addCommand((0, upgrade_command_js_1.createUpgradeCommand)({ repoRoot }));
115
- program.addCommand((0, autoresearch_js_1.createAutoresearchCommand)({ repoRoot }));
132
+ program.addCommand((0, autoresearch_js_1.createSolCommand)({ repoRoot }));
116
133
  program
117
134
  .command("simplicity")
118
135
  .description("View or override the simplicity discipline mode for the active run")
@@ -305,7 +305,9 @@ function detectPacketGenerationFailure(packet) {
305
305
  if (allowedScope.length === 0) {
306
306
  return {
307
307
  missingField: "allowed_scope",
308
- message: `Packet generation failed for ${childId}: missing actionable allowed_scope.`,
308
+ message: `Packet generation failed for ${childId}: missing actionable allowed_scope. ` +
309
+ `Add a '## Scope' section to the child issue body in your tracker, ` +
310
+ `then run 'polaris tracker sync-in <cluster-id>' to pull the update.`,
309
311
  };
310
312
  }
311
313
  const issueBody = packet.instructions.issue_context?.body?.trim() ?? "";
@@ -95,7 +95,12 @@ function backfillClusterStateEvidence(options) {
95
95
  const packetsDir = (0, node_path_1.join)(repoRoot, ".polaris", "clusters", clusterId, "packets");
96
96
  const backfilled = [];
97
97
  const skipped = [];
98
- for (const childId of state.completed_children) {
98
+ // Scan both completed and open children — recovery may have committed work
99
+ // for children that were never checkpointed into completed_children.
100
+ const seen = new Set();
101
+ const allChildren = [...state.completed_children, ...state.open_children].filter((id) => { if (seen.has(id))
102
+ return false; seen.add(id); return true; });
103
+ for (const childId of allChildren) {
99
104
  // Resolve result file — mirrors continue.ts priority:
100
105
  // 1. result_file on child meta (explicit override, e.g. --result-file flag)
101
106
  // 2. dispatch_record.expected_result_path
@@ -132,10 +137,12 @@ function backfillClusterStateEvidence(options) {
132
137
  continue;
133
138
  }
134
139
  // Guard: result must indicate completion.
135
- if (result["status"] !== undefined && result["status"] !== "done") {
140
+ // Accept "done" (backfill artifact) and "success" (SuccessResultPacket schema).
141
+ const resultStatus = result["status"];
142
+ if (resultStatus !== undefined && resultStatus !== "done" && resultStatus !== "success") {
136
143
  skipped.push({
137
144
  childId,
138
- reason: `result status is not done: ${String(result["status"])}`,
145
+ reason: `result status is not done or success: ${String(resultStatus)}`,
139
146
  });
140
147
  continue;
141
148
  }
@@ -776,6 +776,52 @@ async function runParentLoop(options) {
776
776
  // persisted to disk, so a fresh loop start always begins clean.
777
777
  const lifecycle = new lifecycle_js_1.WorkerLifecycleManager(maxConcurrentWorkers);
778
778
  lifecycle.forceReleaseAll();
779
+ // ── Auto-sync pre-flight: fetch missing issue bodies from tracker ────────
780
+ // When child issue bodies are absent or lack a ## Scope section, dispatch
781
+ // hard-fails with "empty allowed_scope". Attempt a silent tracker sync-in
782
+ // before the loop starts so operators don't need to run it manually.
783
+ if (!dryRun) {
784
+ const openChildrenNeedingScope = state.open_children.filter((childId) => {
785
+ // Skip analyze children — they never need scope (no impl packets)
786
+ if (isAnalyzeChild(childId, state)) {
787
+ return false;
788
+ }
789
+ // Resolve child body: prefer cached meta body, then fall back to snapshot
790
+ const cachedChildBody = state.open_children_meta?.[childId]?.body;
791
+ const childBody = (cachedChildBody && cachedChildBody.trim().length > 0)
792
+ ? cachedChildBody
793
+ : (0, checkpoint_js_1.readBodyFromClusterSnapshot)(state.cluster_id, childId, repoRoot) ?? '';
794
+ // If body is completely absent, treat as needing scope sync-in
795
+ if (childBody.trim().length === 0) {
796
+ return true;
797
+ }
798
+ // Parse child body for scope
799
+ const { scope: childScope } = (0, body_parser_js_1.parseIssueBody)(childBody);
800
+ if (childScope.length > 0) {
801
+ return false; // Child has scope, no sync needed
802
+ }
803
+ // Fallback: check parent/cluster-root body for scope
804
+ const cachedParentBody = state.open_children_meta?.[state.cluster_id]?.body;
805
+ const parentBody = (cachedParentBody && cachedParentBody.trim().length > 0)
806
+ ? cachedParentBody
807
+ : (0, checkpoint_js_1.readBodyFromClusterSnapshot)(state.cluster_id, state.cluster_id, repoRoot) ?? '';
808
+ const { scope: parentScope } = (0, body_parser_js_1.parseIssueBody)(parentBody);
809
+ // If parent has scope, child can inherit — no sync needed
810
+ // Otherwise, child truly needs scope sync-in
811
+ return parentScope.length === 0;
812
+ });
813
+ if (openChildrenNeedingScope.length > 0) {
814
+ process.stderr.write(`[polaris] ${openChildrenNeedingScope.length} children missing scope — attempting tracker sync-in...\n`);
815
+ try {
816
+ await (0, index_js_2.loadTrackerGraph)(config, state.cluster_id);
817
+ process.stderr.write(`[polaris] sync-in complete.\n`);
818
+ }
819
+ catch (syncErr) {
820
+ process.stderr.write(`[polaris] sync-in failed (${syncErr instanceof Error ? syncErr.message : String(syncErr)}). ` +
821
+ `Run 'polaris tracker sync-in ${state.cluster_id}' manually and retry.\n`);
822
+ }
823
+ }
824
+ }
779
825
  // ── Main dispatch loop ───────────────────────────────────────────────────
780
826
  // eslint-disable-next-line no-constant-condition
781
827
  while (true) {
@@ -216,6 +216,19 @@ function runLoopBootstrapInit(options) {
216
216
  // avoid circular deps; validateState is called by dispatch which is downstream)
217
217
  (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(stateFile), { recursive: true });
218
218
  const sha = (0, checkpoint_js_1.writeStateAtomic)(stateFile, initialState);
219
+ // Also write to the canonical cluster-scoped path so `polaris finalize`
220
+ // can locate it without requiring --state-file. finalize rejects the
221
+ // .taskchain_artifacts compatibility path as the sole state source.
222
+ const canonicalStatePath = (0, node_path_1.resolve)(repoRoot, ".polaris", "clusters", clusterId, "state.json");
223
+ if ((0, node_path_1.resolve)(stateFile) !== canonicalStatePath) {
224
+ try {
225
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(canonicalStatePath), { recursive: true });
226
+ (0, checkpoint_js_1.writeStateAtomic)(canonicalStatePath, initialState);
227
+ }
228
+ catch (error) {
229
+ process.stderr.write(`Warning: Could not write canonical state to ${canonicalStatePath}: ${error instanceof Error ? error.message : String(error)}\n`);
230
+ }
231
+ }
219
232
  const summary = {
220
233
  run_id: runId,
221
234
  cluster_id: clusterId,
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ /**
3
+ * SOL evidence schema.
4
+ *
5
+ * Typed inputs for the Self-Optimization Loop (SOL) scoring pipeline.
6
+ * These types represent the normalized read model over existing durable
7
+ * run artifacts: state, telemetry, result packets, QC results, and
8
+ * (future) router decision evidence.
9
+ *
10
+ * Design rules:
11
+ * - All top-level inputs are optional unless the field documents a
12
+ * required identity key (run_id, cluster_id).
13
+ * - Future fields from POL-469 (router evidence) and POL-476 (QC
14
+ * metrics) are marked with an `availability` tag explaining when
15
+ * they will be populated.
16
+ * - Nothing in this file mutates run artifacts or triggers side effects.
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ /**
3
+ * SOL scoring model types.
4
+ *
5
+ * Defines the typed output of the SOL score reports for Foremen and Workers.
6
+ * Each dimension has a score, confidence, and optional skipped reason so that
7
+ * callers can preserve the full diagnostic signal rather than a single opaque
8
+ * number.
9
+ *
10
+ * Design rules:
11
+ * - score is 0.0–1.0 where 1.0 = optimal and 0.0 = worst observed behavior.
12
+ * - confidence reflects how trustworthy the score is given the evidence.
13
+ * - skipped_reason is set when evidence was absent or insufficient.
14
+ * - All dimensions are optional at the top level; only present when the
15
+ * relevant evidence was observed for this run.
16
+ * - The existing binary gate diagnosis is preserved alongside SOL scores.
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lsctech/polaris",
3
- "version": "0.5.4",
3
+ "version": "0.5.6",
4
4
  "description": "Polaris — standalone taskchain orchestration framework for governed AI agent workflows",
5
5
  "bin": {
6
6
  "polaris": "dist/cli/index.js",