@lsctech/polaris 0.4.0 → 0.4.2
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/autoresearch/dev-gate.js +51 -0
- package/dist/autoresearch/gates.js +310 -0
- package/dist/autoresearch/index.js +20 -0
- package/dist/autoresearch/proposal.js +121 -0
- package/dist/autoresearch/routing.js +180 -0
- package/dist/autoresearch/score.js +213 -0
- package/dist/cli/adopt-rules.js +28 -0
- package/dist/cli/autoresearch.js +96 -0
- package/dist/cli/index.js +4 -0
- package/dist/cli/upgrade-command.js +26 -0
- package/dist/cli/worker.js +18 -8
- package/dist/loop/bootstrap-packet.js +1 -1
- package/dist/loop/checkpoint.js +120 -1
- package/dist/loop/continue.js +13 -5
- package/dist/loop/parent.js +129 -1
- package/dist/loop/resume.js +14 -8
- package/dist/workspace/.polaris/skills/closeout-librarian/steps/08-librarian-commit.md +2 -0
- package/dist/workspace/.polaris/skills/closeout-librarian/steps/09-sealed-result.md +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Autoresearch scoring engine.
|
|
4
|
+
*
|
|
5
|
+
* Reads run artifacts, evaluates binary gates, and produces a structured
|
|
6
|
+
* diagnosis report:
|
|
7
|
+
*
|
|
8
|
+
* {
|
|
9
|
+
* run_id, cluster_id, evaluated_at,
|
|
10
|
+
* gate_results: GateResult[],
|
|
11
|
+
* failed_gates: string[],
|
|
12
|
+
* score: number (0.0–1.0),
|
|
13
|
+
* diagnosis_hints: { gate, fix_zone, hint }[]
|
|
14
|
+
* }
|
|
15
|
+
*
|
|
16
|
+
* Score = passed_gates / evaluable_gates (skipped gates are not counted).
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.loadRunArtifacts = loadRunArtifacts;
|
|
20
|
+
exports.computeScore = computeScore;
|
|
21
|
+
exports.buildDiagnosisHints = buildDiagnosisHints;
|
|
22
|
+
exports.scoreRun = scoreRun;
|
|
23
|
+
const node_fs_1 = require("node:fs");
|
|
24
|
+
const node_path_1 = require("node:path");
|
|
25
|
+
const gates_js_1 = require("./gates.js");
|
|
26
|
+
// ──────────────────────────────────────────────
|
|
27
|
+
// Diagnosis hints table
|
|
28
|
+
// ──────────────────────────────────────────────
|
|
29
|
+
const HINTS = {
|
|
30
|
+
"user-intervened": {
|
|
31
|
+
fix_zone: "worker-prompt / packet-scope",
|
|
32
|
+
hint: "User pushed commits after polaris-finalize. Review the worker packet scope and instructions for ambiguity.",
|
|
33
|
+
},
|
|
34
|
+
"foreman-resent-packet": {
|
|
35
|
+
fix_zone: "foreman-dispatch / dispatch-boundary",
|
|
36
|
+
hint: "Foreman dispatched the same child more than once. Check for state-machine bugs in dispatch-boundary.ts or loop continue logic.",
|
|
37
|
+
},
|
|
38
|
+
"foreman-fixed-worker-output": {
|
|
39
|
+
fix_zone: "worker-prompt / scope-contract",
|
|
40
|
+
hint: "Foreman made corrective commits after child-complete. Tighten the worker packet's allowed_scope and acceptance criteria.",
|
|
41
|
+
},
|
|
42
|
+
"worker-output-required-fixing": {
|
|
43
|
+
fix_zone: "worker-prompt / validation-commands",
|
|
44
|
+
hint: "Post-finalize commits indicate worker output was insufficient. Add stricter validation commands to the worker packet.",
|
|
45
|
+
},
|
|
46
|
+
"validation-failed": {
|
|
47
|
+
fix_zone: "worker-validation / build-setup",
|
|
48
|
+
hint: "One or more children failed validation. Check the validation_commands in the worker packet and the worker's implementation.",
|
|
49
|
+
},
|
|
50
|
+
"worker-went-out-of-scope": {
|
|
51
|
+
fix_zone: "worker-packet / scope-contract",
|
|
52
|
+
hint: "Worker emitted out-of-scope blocks or returned blocked status. Narrow allowed_scope in the worker packet.",
|
|
53
|
+
},
|
|
54
|
+
"foreman-token-burn-over-budget": {
|
|
55
|
+
fix_zone: "bootstrap-context / prompt-size",
|
|
56
|
+
hint: "Foreman bootstrap context exceeds token budget. Reduce current-state.json or worker packet size before dispatch.",
|
|
57
|
+
},
|
|
58
|
+
"state-repair-required": {
|
|
59
|
+
fix_zone: "medic / cluster-state",
|
|
60
|
+
hint: "Medic artifacts detected — state repair was required during this run. Review the medic chart and root cause.",
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
// ──────────────────────────────────────────────
|
|
64
|
+
// Artifact loader
|
|
65
|
+
// ──────────────────────────────────────────────
|
|
66
|
+
function findRunDir(repoRoot, runId) {
|
|
67
|
+
// Standard taskchain location
|
|
68
|
+
const taskchainPath = (0, node_path_1.join)(repoRoot, ".taskchain_artifacts", "polaris-run", "runs", runId);
|
|
69
|
+
if ((0, node_fs_1.existsSync)(taskchainPath))
|
|
70
|
+
return taskchainPath;
|
|
71
|
+
// Legacy .polaris/runs location
|
|
72
|
+
const polarisPath = (0, node_path_1.join)(repoRoot, ".polaris", "runs", runId);
|
|
73
|
+
if ((0, node_fs_1.existsSync)(polarisPath))
|
|
74
|
+
return polarisPath;
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
function findClusterDir(repoRoot, clusterId) {
|
|
78
|
+
const path = (0, node_path_1.join)(repoRoot, ".polaris", "clusters", clusterId);
|
|
79
|
+
return (0, node_fs_1.existsSync)(path) ? path : null;
|
|
80
|
+
}
|
|
81
|
+
function readJson(filePath) {
|
|
82
|
+
try {
|
|
83
|
+
return JSON.parse((0, node_fs_1.readFileSync)(filePath, "utf-8"));
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function safeReaddir(dir) {
|
|
90
|
+
try {
|
|
91
|
+
return (0, node_fs_1.readdirSync)(dir);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function readResultPackets(clusterDir) {
|
|
98
|
+
if (!clusterDir)
|
|
99
|
+
return [];
|
|
100
|
+
const resultsDir = (0, node_path_1.join)(clusterDir, "results");
|
|
101
|
+
if (!(0, node_fs_1.existsSync)(resultsDir))
|
|
102
|
+
return [];
|
|
103
|
+
return safeReaddir(resultsDir)
|
|
104
|
+
.filter((f) => f.endsWith(".json"))
|
|
105
|
+
.map((f) => readJson((0, node_path_1.join)(resultsDir, f)))
|
|
106
|
+
.filter((v) => v !== undefined);
|
|
107
|
+
}
|
|
108
|
+
function extractWorkerResultContracts(resultPackets) {
|
|
109
|
+
const contracts = [];
|
|
110
|
+
for (const packet of resultPackets) {
|
|
111
|
+
const rec = packet;
|
|
112
|
+
// WorkerResultContract has packet_hash, worker_id, role as distinguishing fields
|
|
113
|
+
if (rec && typeof rec["packet_hash"] === "string" && typeof rec["worker_id"] === "string") {
|
|
114
|
+
contracts.push(rec);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return contracts;
|
|
118
|
+
}
|
|
119
|
+
function loadRunArtifacts(repoRoot, runId) {
|
|
120
|
+
const runDir = findRunDir(repoRoot, runId);
|
|
121
|
+
// Derive cluster from current-state.json in the run dir, or fallback to the default
|
|
122
|
+
let clusterId = null;
|
|
123
|
+
let currentState = null;
|
|
124
|
+
// Try run dir's current-state.json first
|
|
125
|
+
if (runDir) {
|
|
126
|
+
const runStatePath = (0, node_path_1.join)(runDir, "current-state.json");
|
|
127
|
+
if ((0, node_fs_1.existsSync)(runStatePath)) {
|
|
128
|
+
currentState = readJson(runStatePath);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Fall back to the active current-state.json
|
|
132
|
+
if (!currentState) {
|
|
133
|
+
const taskchainState = (0, node_path_1.join)(repoRoot, ".taskchain_artifacts", "polaris-run", "current-state.json");
|
|
134
|
+
const polarisState = (0, node_path_1.join)(repoRoot, ".polaris", "runs", "current-state.json");
|
|
135
|
+
const statePath = (0, node_fs_1.existsSync)(taskchainState) ? taskchainState : polarisState;
|
|
136
|
+
if ((0, node_fs_1.existsSync)(statePath)) {
|
|
137
|
+
currentState = readJson(statePath);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (currentState && typeof currentState === "object" && currentState !== null) {
|
|
141
|
+
const rec = currentState;
|
|
142
|
+
if (typeof rec["cluster_id"] === "string")
|
|
143
|
+
clusterId = rec["cluster_id"];
|
|
144
|
+
if (typeof rec["run_id"] === "string" && rec["run_id"] !== runId) {
|
|
145
|
+
// State belongs to a different run — don't use it for cluster_id derivation
|
|
146
|
+
clusterId = null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const clusterDir = clusterId ? findClusterDir(repoRoot, clusterId) : null;
|
|
150
|
+
// Ledger
|
|
151
|
+
const ledgerPath = (0, node_path_1.join)(repoRoot, ".polaris", "runs", "ledger.jsonl");
|
|
152
|
+
const ledgerEvents = (0, gates_js_1.readJsonLines)(ledgerPath).filter((e) => {
|
|
153
|
+
const rec = e;
|
|
154
|
+
return rec?.["run_id"] === runId;
|
|
155
|
+
});
|
|
156
|
+
// Telemetry — prefer the run dir; some older runs have it in .polaris/runs/<runId>/
|
|
157
|
+
const telemetryPath = runDir ? (0, node_path_1.join)(runDir, "telemetry.jsonl") : null;
|
|
158
|
+
const telemetryEvents = telemetryPath ? (0, gates_js_1.readJsonLines)(telemetryPath) : [];
|
|
159
|
+
// Result packets from cluster results dir
|
|
160
|
+
const resultPackets = readResultPackets(clusterDir);
|
|
161
|
+
// Also check for WorkerResultContracts inside result packets
|
|
162
|
+
const workerResultContracts = extractWorkerResultContracts(resultPackets);
|
|
163
|
+
return {
|
|
164
|
+
runId,
|
|
165
|
+
runDir,
|
|
166
|
+
clusterDir,
|
|
167
|
+
currentState,
|
|
168
|
+
ledgerEvents,
|
|
169
|
+
resultPackets,
|
|
170
|
+
workerResultContracts,
|
|
171
|
+
telemetryEvents,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
// ──────────────────────────────────────────────
|
|
175
|
+
// Scoring
|
|
176
|
+
// ──────────────────────────────────────────────
|
|
177
|
+
function computeScore(gateResults) {
|
|
178
|
+
const evaluable = gateResults.filter((g) => g.outcome !== "skipped");
|
|
179
|
+
if (evaluable.length === 0)
|
|
180
|
+
return 1.0; // nothing to evaluate — no known problems
|
|
181
|
+
const passed = evaluable.filter((g) => g.outcome === "passed").length;
|
|
182
|
+
return passed / evaluable.length;
|
|
183
|
+
}
|
|
184
|
+
function buildDiagnosisHints(failedGateNames) {
|
|
185
|
+
return failedGateNames.map((gate) => {
|
|
186
|
+
const h = HINTS[gate] ?? { fix_zone: "unknown", hint: `Gate ${gate} failed — no hint available.` };
|
|
187
|
+
return { gate, ...h };
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
// ──────────────────────────────────────────────
|
|
191
|
+
// Main entry point
|
|
192
|
+
// ──────────────────────────────────────────────
|
|
193
|
+
function scoreRun(repoRoot, runId) {
|
|
194
|
+
const artifacts = loadRunArtifacts(repoRoot, runId);
|
|
195
|
+
const gateResults = gates_js_1.ALL_GATES.map((evaluator) => evaluator(artifacts));
|
|
196
|
+
const failedGates = gateResults.filter((g) => g.outcome === "failed").map((g) => g.gate);
|
|
197
|
+
const score = computeScore(gateResults);
|
|
198
|
+
const diagnosisHints = buildDiagnosisHints(failedGates);
|
|
199
|
+
const clusterId = artifacts.currentState &&
|
|
200
|
+
typeof artifacts.currentState === "object" &&
|
|
201
|
+
!Array.isArray(artifacts.currentState)
|
|
202
|
+
? artifacts.currentState["cluster_id"] ?? null
|
|
203
|
+
: null;
|
|
204
|
+
return {
|
|
205
|
+
run_id: runId,
|
|
206
|
+
cluster_id: clusterId,
|
|
207
|
+
evaluated_at: new Date().toISOString(),
|
|
208
|
+
gate_results: gateResults,
|
|
209
|
+
failed_gates: failedGates,
|
|
210
|
+
score,
|
|
211
|
+
diagnosis_hints: diagnosisHints,
|
|
212
|
+
};
|
|
213
|
+
}
|
package/dist/cli/adopt-rules.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractRepoOverview = extractRepoOverview;
|
|
4
|
+
exports.refreshPolarisRules = refreshPolarisRules;
|
|
3
5
|
exports.generatePolarisRules = generatePolarisRules;
|
|
4
6
|
const node_fs_1 = require("node:fs");
|
|
5
7
|
const node_path_1 = require("node:path");
|
|
@@ -186,6 +188,32 @@ function buildContentInline(overview) {
|
|
|
186
188
|
"",
|
|
187
189
|
].join("\n");
|
|
188
190
|
}
|
|
191
|
+
function extractRepoOverview(content) {
|
|
192
|
+
const match = content.match(/(?:^|\n)## Repository Overview\r?\n([\s\S]*?)(?:\r?\n## |$)/);
|
|
193
|
+
if (!match)
|
|
194
|
+
return "Repository managed by Polaris.";
|
|
195
|
+
const overview = match[1].trim().replace(/(^|\n)---\s*$/, "").trim();
|
|
196
|
+
return overview || "Repository managed by Polaris.";
|
|
197
|
+
}
|
|
198
|
+
function refreshPolarisRules(repoRoot, currentVersion) {
|
|
199
|
+
const outputPath = (0, node_path_1.join)(repoRoot, "POLARIS_RULES.md");
|
|
200
|
+
let content = "";
|
|
201
|
+
if ((0, node_fs_1.existsSync)(outputPath)) {
|
|
202
|
+
content = (0, node_fs_1.readFileSync)(outputPath, "utf-8");
|
|
203
|
+
}
|
|
204
|
+
const firstLine = content.split(/\r?\n/)[0] ?? "";
|
|
205
|
+
const versionMatch = firstLine.match(/^<!-- polaris-version: ([\d.]+-?[\w.]*) -->$/);
|
|
206
|
+
const existingVersion = versionMatch?.[1];
|
|
207
|
+
if (existingVersion === currentVersion) {
|
|
208
|
+
return { status: "skipped", path: outputPath };
|
|
209
|
+
}
|
|
210
|
+
const overview = extractRepoOverview(content);
|
|
211
|
+
const newContent = `<!-- polaris-version: ${currentVersion} -->\n${buildContentInline(overview)}`;
|
|
212
|
+
const dir = (0, node_path_1.dirname)(outputPath);
|
|
213
|
+
(0, node_fs_1.mkdirSync)(dir, { recursive: true });
|
|
214
|
+
(0, node_fs_1.writeFileSync)(outputPath, newContent, "utf-8");
|
|
215
|
+
return { status: "updated", path: outputPath };
|
|
216
|
+
}
|
|
189
217
|
async function generatePolarisRules(repoRoot, inventory, options = {}) {
|
|
190
218
|
const { overwrite = false, workspaceDir } = options;
|
|
191
219
|
const outputPath = (0, node_path_1.join)(repoRoot, "POLARIS_RULES.md");
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createAutoresearchCommand = createAutoresearchCommand;
|
|
4
|
+
const node_path_1 = require("node:path");
|
|
5
|
+
const commander_1 = require("commander");
|
|
6
|
+
const dev_gate_js_1 = require("../autoresearch/dev-gate.js");
|
|
7
|
+
const score_js_1 = require("../autoresearch/score.js");
|
|
8
|
+
const proposal_js_1 = require("../autoresearch/proposal.js");
|
|
9
|
+
const routing_js_1 = require("../autoresearch/routing.js");
|
|
10
|
+
function createAutoresearchCommand(options) {
|
|
11
|
+
const repoRoot = options.repoRoot;
|
|
12
|
+
const autoresearch = new commander_1.Command("autoresearch")
|
|
13
|
+
.description("Autoresearch tools (dev-gated — Polaris development context only)")
|
|
14
|
+
.showHelpAfterError()
|
|
15
|
+
.showSuggestionAfterError();
|
|
16
|
+
autoresearch
|
|
17
|
+
.command("score <run-id>")
|
|
18
|
+
.description("Score a completed Polaris run against the binary gate scorecard and output a diagnosis report")
|
|
19
|
+
.option("-r, --repo-root <path>", "Repository root", repoRoot)
|
|
20
|
+
.option("--json", "Output raw JSON (default: pretty-printed)")
|
|
21
|
+
.action((runId, cmdOptions) => {
|
|
22
|
+
const root = (0, node_path_1.resolve)(cmdOptions.repoRoot ?? repoRoot);
|
|
23
|
+
try {
|
|
24
|
+
(0, dev_gate_js_1.assertPolarisDevContext)(root);
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const report = (0, score_js_1.scoreRun)(root, runId);
|
|
32
|
+
const output = cmdOptions.json
|
|
33
|
+
? JSON.stringify(report)
|
|
34
|
+
: JSON.stringify(report, null, 2);
|
|
35
|
+
process.stdout.write(`${output}\n`);
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
process.stderr.write(`autoresearch score error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
autoresearch
|
|
43
|
+
.command("propose <diagnosis-file>")
|
|
44
|
+
.description("File Linear improvement proposals from a diagnosis report (dev-gated — never auto-applied)")
|
|
45
|
+
.option("-r, --repo-root <path>", "Repository root", repoRoot)
|
|
46
|
+
.option("--team <team>", "Linear team name or ID to file issues in", "Polaris")
|
|
47
|
+
.option("--dry-run", "Log what would be created without calling Linear")
|
|
48
|
+
.option("--json", "Output raw JSON (default: pretty-printed)")
|
|
49
|
+
.action(async (diagnosisFile, cmdOptions) => {
|
|
50
|
+
const root = (0, node_path_1.resolve)(cmdOptions.repoRoot ?? repoRoot);
|
|
51
|
+
try {
|
|
52
|
+
(0, dev_gate_js_1.assertPolarisDevContext)(root);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
let report;
|
|
59
|
+
try {
|
|
60
|
+
report = (0, proposal_js_1.loadDiagnosisReport)((0, node_path_1.resolve)(diagnosisFile));
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
process.stderr.write(`autoresearch propose: invalid diagnosis file: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
const proposals = (0, proposal_js_1.buildProposals)(report);
|
|
67
|
+
if (proposals.length === 0) {
|
|
68
|
+
process.stdout.write("No failed gates with fix zone mappings — nothing to propose.\n");
|
|
69
|
+
process.exit(0);
|
|
70
|
+
}
|
|
71
|
+
const apiKey = process.env["LINEAR_API_KEY"];
|
|
72
|
+
if (!apiKey && !cmdOptions.dryRun) {
|
|
73
|
+
process.stderr.write("autoresearch propose: LINEAR_API_KEY environment variable is required.\n");
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const result = await (0, routing_js_1.routeProposals)(proposals, {
|
|
78
|
+
apiKey: apiKey ?? "",
|
|
79
|
+
teamKey: cmdOptions.team,
|
|
80
|
+
dryRun: cmdOptions.dryRun,
|
|
81
|
+
});
|
|
82
|
+
const output = cmdOptions.json
|
|
83
|
+
? JSON.stringify(result)
|
|
84
|
+
: JSON.stringify(result, null, 2);
|
|
85
|
+
process.stdout.write(`${output}\n`);
|
|
86
|
+
if (result.total_errors > 0) {
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
process.stderr.write(`autoresearch propose error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
return autoresearch;
|
|
96
|
+
}
|
package/dist/cli/index.js
CHANGED
|
@@ -27,7 +27,9 @@ const librarian_js_1 = require("./librarian.js");
|
|
|
27
27
|
const medic_js_1 = require("./medic.js");
|
|
28
28
|
const welfare_js_1 = require("../map/welfare.js");
|
|
29
29
|
const adopt_command_js_1 = require("./adopt-command.js");
|
|
30
|
+
const upgrade_command_js_1 = require("./upgrade-command.js");
|
|
30
31
|
const simplicity_js_1 = require("../loop/simplicity.js");
|
|
32
|
+
const autoresearch_js_1 = require("./autoresearch.js");
|
|
31
33
|
function resolveStateFile(repoRoot, explicit) {
|
|
32
34
|
if (explicit)
|
|
33
35
|
return (0, node_path_1.resolve)(explicit);
|
|
@@ -109,6 +111,8 @@ function createPolarisCommand(options = {}) {
|
|
|
109
111
|
repoRoot,
|
|
110
112
|
}));
|
|
111
113
|
program.addCommand((0, adopt_command_js_1.createAdoptCommand)({ repoRoot }));
|
|
114
|
+
program.addCommand((0, upgrade_command_js_1.createUpgradeCommand)({ repoRoot }));
|
|
115
|
+
program.addCommand((0, autoresearch_js_1.createAutoresearchCommand)({ repoRoot }));
|
|
112
116
|
program
|
|
113
117
|
.command("simplicity")
|
|
114
118
|
.description("View or override the simplicity discipline mode for the active run")
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createUpgradeCommand = createUpgradeCommand;
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const version_js_1 = require("./version.js");
|
|
6
|
+
const adopt_rules_js_1 = require("./adopt-rules.js");
|
|
7
|
+
function createUpgradeCommand(options = {}) {
|
|
8
|
+
const repoRoot = options.repoRoot ?? process.cwd();
|
|
9
|
+
const doRefresh = options.refresh ?? adopt_rules_js_1.refreshPolarisRules;
|
|
10
|
+
const getCurrentVersion = options.getVersion ?? version_js_1.getVersion;
|
|
11
|
+
const cmd = new commander_1.Command("upgrade")
|
|
12
|
+
.description("Refresh POLARIS_RULES.md to the current Polaris CLI version")
|
|
13
|
+
.option("-r, --repo-root <path>", "Repository root", repoRoot)
|
|
14
|
+
.action(async (cmdOptions) => {
|
|
15
|
+
const targetRoot = cmdOptions.repoRoot ?? repoRoot;
|
|
16
|
+
const version = getCurrentVersion();
|
|
17
|
+
const result = doRefresh(targetRoot, version);
|
|
18
|
+
if (result.status === "updated") {
|
|
19
|
+
process.stdout.write(`POLARIS_RULES.md updated to version ${version}\n`);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
process.stdout.write(`Already up to date (${version})\n`);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
return cmd;
|
|
26
|
+
}
|
package/dist/cli/worker.js
CHANGED
|
@@ -102,17 +102,27 @@ function validateWorkerCompletionResult(repoRoot, packet, resultFile) {
|
|
|
102
102
|
},
|
|
103
103
|
};
|
|
104
104
|
}
|
|
105
|
-
function updateCompletionState(state, childId, commit) {
|
|
105
|
+
function updateCompletionState(state, childId, commit, packet, repoRoot) {
|
|
106
106
|
const completedChildren = Array.from(new Set([...state.completed_children, childId]));
|
|
107
107
|
const remainingOpenChildren = state.open_children.filter((child) => child !== childId);
|
|
108
|
+
const dispatchRecord = state.open_children_meta?.[childId]?.dispatch_record;
|
|
109
|
+
const resultFile = packet.result_file_contract.result_file;
|
|
110
|
+
const telemetryFile = resolveRepoPath(repoRoot, packet.telemetry_file);
|
|
111
|
+
const packetPath = dispatchRecord?.packet_path ?? "";
|
|
112
|
+
const workerResult = (0, checkpoint_js_1.buildWorkerResultContract)({
|
|
113
|
+
state,
|
|
114
|
+
childId,
|
|
115
|
+
resultFile,
|
|
116
|
+
telemetryFile,
|
|
117
|
+
lastCommit: commit,
|
|
118
|
+
validation: "passed",
|
|
119
|
+
packetHash: packetPath ? (0, checkpoint_js_1.computePacketHashFromPath)(packetPath) : "",
|
|
120
|
+
status: "done",
|
|
121
|
+
nextRecommendedAction: "continue",
|
|
122
|
+
});
|
|
108
123
|
const completedChildrenResults = {
|
|
109
124
|
...(state.completed_children_results ?? {}),
|
|
110
|
-
[childId]:
|
|
111
|
-
status: "done",
|
|
112
|
-
validation: "passed",
|
|
113
|
-
commit,
|
|
114
|
-
next_recommended_action: "continue",
|
|
115
|
-
},
|
|
125
|
+
[childId]: workerResult,
|
|
116
126
|
};
|
|
117
127
|
return {
|
|
118
128
|
...state,
|
|
@@ -236,7 +246,7 @@ function createWorkerCommand(options) {
|
|
|
236
246
|
return;
|
|
237
247
|
}
|
|
238
248
|
const state = (0, checkpoint_js_1.readState)(stateFile);
|
|
239
|
-
const updatedState = updateCompletionState(state, packet.active_child, validation.result.commit);
|
|
249
|
+
const updatedState = updateCompletionState(state, packet.active_child, validation.result.commit, packet, options.repoRoot);
|
|
240
250
|
(0, checkpoint_js_1.writeStateAtomic)(stateFile, updatedState);
|
|
241
251
|
// Sync cluster-state.json so finalize and the parent loop see a consistent view.
|
|
242
252
|
try {
|
|
@@ -71,7 +71,7 @@ function buildBootstrapPacket(state, stateFile, currentStateSha, repoRoot, compl
|
|
|
71
71
|
last_completed_step: state.step_cursor,
|
|
72
72
|
last_completed_child: completedChild,
|
|
73
73
|
next_step: nextChild ? "03-execute-child" : "CLUSTER-COMPLETE",
|
|
74
|
-
open_children: state.open_children,
|
|
74
|
+
open_children: { next_child: state.open_children[0] ?? null, remaining_count: state.open_children.length },
|
|
75
75
|
artifact_pointers: {
|
|
76
76
|
current_state: relStateFile,
|
|
77
77
|
telemetry: relTelemetryFile,
|
package/dist/loop/checkpoint.js
CHANGED
|
@@ -9,6 +9,10 @@ exports.appendWorkerScopeFidelityEvent = appendWorkerScopeFidelityEvent;
|
|
|
9
9
|
exports.appendBoundaryEvent = appendBoundaryEvent;
|
|
10
10
|
exports.appendAbortEvent = appendAbortEvent;
|
|
11
11
|
exports.appendStaleDispatchAbortedEvent = appendStaleDispatchAbortedEvent;
|
|
12
|
+
exports.toValidationStatus = toValidationStatus;
|
|
13
|
+
exports.countTelemetryEvents = countTelemetryEvents;
|
|
14
|
+
exports.computePacketHashFromPath = computePacketHashFromPath;
|
|
15
|
+
exports.buildWorkerResultContract = buildWorkerResultContract;
|
|
12
16
|
exports.readBodyFromClusterSnapshot = readBodyFromClusterSnapshot;
|
|
13
17
|
const node_fs_1 = require("node:fs");
|
|
14
18
|
const node_crypto_1 = require("node:crypto");
|
|
@@ -268,7 +272,16 @@ function validateState(state) {
|
|
|
268
272
|
return errors;
|
|
269
273
|
}
|
|
270
274
|
function writeStateAtomic(stateFile, state) {
|
|
271
|
-
const
|
|
275
|
+
const nextChild = state.open_children[0];
|
|
276
|
+
const slimmedState = state.open_children_meta
|
|
277
|
+
? {
|
|
278
|
+
...state,
|
|
279
|
+
open_children_meta: Object.fromEntries(Object.entries(state.open_children_meta).map(([id, meta]) => id === nextChild
|
|
280
|
+
? [id, meta]
|
|
281
|
+
: [id, (({ body: _b, ...rest }) => rest)(meta)])),
|
|
282
|
+
}
|
|
283
|
+
: state;
|
|
284
|
+
const content = JSON.stringify(slimmedState, null, 2);
|
|
272
285
|
const tmp = `${stateFile}.tmp`;
|
|
273
286
|
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(stateFile), { recursive: true });
|
|
274
287
|
(0, node_fs_1.writeFileSync)(tmp, content, "utf-8");
|
|
@@ -305,6 +318,112 @@ function appendStaleDispatchAbortedEvent(telemetryFile, event) {
|
|
|
305
318
|
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(telemetryFile), { recursive: true });
|
|
306
319
|
(0, node_fs_1.appendFileSync)(telemetryFile, JSON.stringify(timestampedEvent) + "\n", "utf-8");
|
|
307
320
|
}
|
|
321
|
+
/**
|
|
322
|
+
* Normalize a raw validation value to one of the canonical result states.
|
|
323
|
+
*/
|
|
324
|
+
function toValidationStatus(validation) {
|
|
325
|
+
if (typeof validation === "string") {
|
|
326
|
+
const v = validation.trim().toLowerCase();
|
|
327
|
+
if (v === "passed" || v === "failed" || v === "skipped")
|
|
328
|
+
return v;
|
|
329
|
+
if (["pass", "success", "ok"].includes(v))
|
|
330
|
+
return "passed";
|
|
331
|
+
}
|
|
332
|
+
if (validation === true)
|
|
333
|
+
return "passed";
|
|
334
|
+
if (validation === false)
|
|
335
|
+
return "failed";
|
|
336
|
+
if (typeof validation === "object" && validation !== null && !Array.isArray(validation)) {
|
|
337
|
+
const r = validation;
|
|
338
|
+
if (Array.isArray(r["passed"]) && r["passed"].length > 0)
|
|
339
|
+
return "passed";
|
|
340
|
+
if (Array.isArray(r["failed"]) && r["failed"].length > 0)
|
|
341
|
+
return "failed";
|
|
342
|
+
}
|
|
343
|
+
return "skipped";
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Count telemetry events of a specific name for a given child.
|
|
347
|
+
* Returns 0 when the telemetry file is missing or unreadable.
|
|
348
|
+
*/
|
|
349
|
+
function countTelemetryEvents(telemetryFile, eventName, childId) {
|
|
350
|
+
if (!(0, node_fs_1.existsSync)(telemetryFile))
|
|
351
|
+
return 0;
|
|
352
|
+
try {
|
|
353
|
+
const content = (0, node_fs_1.readFileSync)(telemetryFile, "utf-8").trim();
|
|
354
|
+
if (!content)
|
|
355
|
+
return 0;
|
|
356
|
+
let count = 0;
|
|
357
|
+
for (const line of content.split("\n")) {
|
|
358
|
+
if (!line.trim())
|
|
359
|
+
continue;
|
|
360
|
+
try {
|
|
361
|
+
const event = JSON.parse(line);
|
|
362
|
+
if (event["event"] === eventName && event["child_id"] === childId) {
|
|
363
|
+
count += 1;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
// Skip malformed lines
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return count;
|
|
371
|
+
}
|
|
372
|
+
catch {
|
|
373
|
+
return 0;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Compute the SHA-256 hash of a packet file. Returns an empty string when
|
|
378
|
+
* the file is missing or unreadable so evidence never blocks completion.
|
|
379
|
+
*/
|
|
380
|
+
function computePacketHashFromPath(packetPath) {
|
|
381
|
+
try {
|
|
382
|
+
const raw = (0, node_fs_1.readFileSync)(packetPath, "utf-8");
|
|
383
|
+
return (0, node_crypto_1.createHash)("sha256").update(raw, "utf-8").digest("hex");
|
|
384
|
+
}
|
|
385
|
+
catch {
|
|
386
|
+
return "";
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Build a WorkerResultContract from the durable dispatch state and telemetry.
|
|
391
|
+
* Missing optional values are filled with safe defaults so the record is always
|
|
392
|
+
* complete enough for retroactive scoring.
|
|
393
|
+
*/
|
|
394
|
+
function buildWorkerResultContract(args) {
|
|
395
|
+
const dispatchRecord = args.state.open_children_meta?.[args.childId]?.dispatch_record;
|
|
396
|
+
const role = dispatchRecord?.role ?? "worker";
|
|
397
|
+
const provider = dispatchRecord?.provider ?? "unknown";
|
|
398
|
+
const workerId = dispatchRecord?.worker_id ?? dispatchRecord?.dispatch_id ?? "unknown";
|
|
399
|
+
const packetPath = dispatchRecord?.packet_path ?? "";
|
|
400
|
+
const heartbeatCount = countTelemetryEvents(args.telemetryFile, "worker-heartbeat", args.childId);
|
|
401
|
+
const escalationCount = countTelemetryEvents(args.telemetryFile, "worker-blocked", args.childId);
|
|
402
|
+
return {
|
|
403
|
+
child_id: args.childId,
|
|
404
|
+
status: args.status ?? "done",
|
|
405
|
+
validation: toValidationStatus(args.validation),
|
|
406
|
+
commit: args.lastCommit,
|
|
407
|
+
next_recommended_action: args.nextRecommendedAction ?? "continue",
|
|
408
|
+
run_id: args.state.run_id,
|
|
409
|
+
cluster_id: args.state.cluster_id,
|
|
410
|
+
skill_name: args.state.skill ?? null,
|
|
411
|
+
role,
|
|
412
|
+
provider,
|
|
413
|
+
packet_hash: args.packetHash,
|
|
414
|
+
worker_id: workerId,
|
|
415
|
+
escalation_count: escalationCount,
|
|
416
|
+
heartbeat_count: heartbeatCount,
|
|
417
|
+
result_artifact_path: args.resultFile,
|
|
418
|
+
packet_path: packetPath,
|
|
419
|
+
telemetry_path: args.telemetryFile,
|
|
420
|
+
user_intervened: null,
|
|
421
|
+
foreman_intervened: null,
|
|
422
|
+
dispatch_epoch: args.state.dispatch_boundary?.dispatch_epoch,
|
|
423
|
+
session_pointer: null,
|
|
424
|
+
result_data: args.resultData,
|
|
425
|
+
};
|
|
426
|
+
}
|
|
308
427
|
/**
|
|
309
428
|
* Reads the issue body for a node from the durable cluster snapshot at
|
|
310
429
|
* `.polaris/clusters/<clusterId>/clusters.json`. Returns undefined when the
|
package/dist/loop/continue.js
CHANGED
|
@@ -482,12 +482,20 @@ function runLoopContinue(options) {
|
|
|
482
482
|
...(state.completed_children_results ?? {}),
|
|
483
483
|
};
|
|
484
484
|
if (completedChild && completionResultFile) {
|
|
485
|
-
|
|
485
|
+
const telemetryFile = resolveTelemetryFilePath(state, repoRoot);
|
|
486
|
+
const dispatchRecord = state.open_children_meta?.[completedChild]?.dispatch_record;
|
|
487
|
+
const packetPath = dispatchRecord?.packet_path ?? "";
|
|
488
|
+
updatedCompletedChildrenResults[completedChild] = (0, checkpoint_js_1.buildWorkerResultContract)({
|
|
489
|
+
state,
|
|
490
|
+
childId: completedChild,
|
|
491
|
+
resultFile: completionResultFile,
|
|
492
|
+
telemetryFile,
|
|
493
|
+
lastCommit: completionCommit || null,
|
|
494
|
+
validation: completionValidation,
|
|
495
|
+
packetHash: packetPath ? (0, checkpoint_js_1.computePacketHashFromPath)(packetPath) : "",
|
|
486
496
|
status: "done",
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
next_recommended_action: "continue",
|
|
490
|
-
};
|
|
497
|
+
nextRecommendedAction: "continue",
|
|
498
|
+
});
|
|
491
499
|
}
|
|
492
500
|
const updatedState = {
|
|
493
501
|
...state,
|