@lsctech/polaris 0.3.29 → 0.4.1
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/agent-plugin/args.js +77 -0
- package/dist/agent-plugin/claude-generator.js +158 -0
- package/dist/agent-plugin/commands.js +73 -0
- package/dist/agent-plugin/help.js +69 -0
- package/dist/agent-plugin/sync.js +108 -0
- 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-approve.js +124 -4
- package/dist/cli/adopt-assets.js +9 -1
- package/dist/cli/adopt-command.js +123 -11
- package/dist/cli/adopt-instructions.js +17 -2
- package/dist/cli/adopt-smartdocs.js +9 -0
- package/dist/cli/adoption-context.js +46 -0
- package/dist/cli/adoption-plan.js +105 -12
- package/dist/cli/agent-setup.js +47 -0
- package/dist/cli/autoresearch.js +96 -0
- package/dist/cli/index.js +2 -0
- package/dist/cli/init.js +69 -1
- package/dist/cli/setup-interview/generate.js +380 -0
- package/dist/cli/setup-interview/report.js +138 -0
- package/dist/cli/setup-interview/runner.js +105 -0
- package/dist/cli/setup-interview/schema.js +16 -0
- package/dist/cli/setup-interview/store.js +73 -0
- package/dist/cli/worker.js +18 -8
- package/dist/finalize/index.js +5 -4
- package/dist/loop/adapters/foreman-dispatch.js +65 -0
- package/dist/loop/adapters/index.js +3 -1
- 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/loop/worker-packet.js +1 -1
- package/dist/skill-packet/generator.js +149 -1
- package/dist/workspace/.polaris/skills/polaris-tools/tools.js +31 -22
- package/package.json +2 -2
|
@@ -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
|
+
}
|
|
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.persistApprovedAdoptionPlan = persistApprovedAdoptionPlan;
|
|
4
4
|
exports.logAdoptionApprovalTelemetry = logAdoptionApprovalTelemetry;
|
|
5
5
|
exports.promptApproval = promptApproval;
|
|
6
|
+
exports.promptCategoryApproval = promptCategoryApproval;
|
|
7
|
+
exports.requireApprovalGates = requireApprovalGates;
|
|
6
8
|
const node_fs_1 = require("node:fs");
|
|
7
9
|
const node_path_1 = require("node:path");
|
|
8
10
|
const promises_1 = require("node:readline/promises");
|
|
@@ -36,9 +38,10 @@ async function promptApproval(plan, options = {}) {
|
|
|
36
38
|
const stdout = options.stdout ?? process.stdout;
|
|
37
39
|
const stdin = options.stdin ?? process.stdin;
|
|
38
40
|
const markdownPath = adoptionPlanMarkdownPath(repoRoot);
|
|
39
|
-
const markdown =
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
const markdown = options.markdown ??
|
|
42
|
+
((0, node_fs_1.existsSync)(markdownPath)
|
|
43
|
+
? (0, node_fs_1.readFileSync)(markdownPath, "utf-8")
|
|
44
|
+
: (0, adoption_plan_js_1.renderAdoptionPlanMarkdown)(plan));
|
|
42
45
|
stdout.write(`${markdown.replace(/\s*$/, "")}\n\n`);
|
|
43
46
|
const rl = (0, promises_1.createInterface)({
|
|
44
47
|
input: stdin,
|
|
@@ -52,9 +55,126 @@ async function promptApproval(plan, options = {}) {
|
|
|
52
55
|
rl.close();
|
|
53
56
|
}
|
|
54
57
|
if (response.trim().toLowerCase() === "y") {
|
|
55
|
-
|
|
58
|
+
const approvalNow = options.now ?? new Date();
|
|
59
|
+
if (options.persist) {
|
|
60
|
+
options.persist(plan, repoRoot, approvalNow);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
persistApprovedAdoptionPlan(plan, repoRoot, approvalNow);
|
|
64
|
+
}
|
|
56
65
|
return true;
|
|
57
66
|
}
|
|
58
67
|
stdout.write("Adoption aborted: explicit approval required.\n");
|
|
59
68
|
return false;
|
|
60
69
|
}
|
|
70
|
+
/** Map of category → step categories in AdoptionStep */
|
|
71
|
+
const CATEGORY_STEP_MAP = {
|
|
72
|
+
"doc-movement": ["smartdocs-migrate"],
|
|
73
|
+
"instruction-file": ["instruction-refactor"],
|
|
74
|
+
"graph-root": ["cognition-generate", "atlas-generate"],
|
|
75
|
+
"route-scaffold": ["scaffold"],
|
|
76
|
+
};
|
|
77
|
+
const CATEGORY_LABELS = {
|
|
78
|
+
"doc-movement": "Document Movement",
|
|
79
|
+
"instruction-file": "Instruction-File Changes",
|
|
80
|
+
"graph-root": "Graph-Root / Cognition Changes",
|
|
81
|
+
"route-scaffold": "Route Scaffolding",
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Render a compact diff preview for the given steps.
|
|
85
|
+
*/
|
|
86
|
+
function renderStepDiff(steps) {
|
|
87
|
+
if (steps.length === 0)
|
|
88
|
+
return " (no steps)\n";
|
|
89
|
+
return steps
|
|
90
|
+
.map((s) => {
|
|
91
|
+
const src = s.source_path ? ` ${s.source_path} →` : "";
|
|
92
|
+
const dst = s.dest_path ? ` ${s.dest_path}` : "";
|
|
93
|
+
const routing = s.routing ? ` [${s.routing}]` : "";
|
|
94
|
+
return ` ${s.action.toUpperCase()}${src}${dst}${routing} — ${s.description}`;
|
|
95
|
+
})
|
|
96
|
+
.join("\n") + "\n";
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Ask the operator for approval of a single mutation category.
|
|
100
|
+
* Returns true if approved, false if declined.
|
|
101
|
+
* Logs the decision via adoption telemetry.
|
|
102
|
+
*/
|
|
103
|
+
async function promptCategoryApproval(category, steps, options = {}) {
|
|
104
|
+
const repoRoot = options.repoRoot ?? process.cwd();
|
|
105
|
+
const stdout = options.stdout ?? process.stdout;
|
|
106
|
+
const stdin = options.stdin ?? process.stdin;
|
|
107
|
+
const label = CATEGORY_LABELS[category];
|
|
108
|
+
const actionableSteps = steps.filter((s) => s.action !== "skip");
|
|
109
|
+
if (actionableSteps.length === 0)
|
|
110
|
+
return true;
|
|
111
|
+
stdout.write(`\n--- ${label} (${actionableSteps.length} step(s)) ---\n`);
|
|
112
|
+
stdout.write(renderStepDiff(actionableSteps));
|
|
113
|
+
const rl = (0, promises_1.createInterface)({ input: stdin, output: stdout });
|
|
114
|
+
let response = "";
|
|
115
|
+
try {
|
|
116
|
+
response = await rl.question(`Approve ${label}? [y/N] `);
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
rl.close();
|
|
120
|
+
}
|
|
121
|
+
const approved = response.trim().toLowerCase() === "y";
|
|
122
|
+
const now = (options.now ?? new Date()).toISOString();
|
|
123
|
+
logAdoptionApprovalTelemetry(repoRoot, {
|
|
124
|
+
event: "category-approval",
|
|
125
|
+
category,
|
|
126
|
+
approved,
|
|
127
|
+
step_count: actionableSteps.length,
|
|
128
|
+
timestamp: now,
|
|
129
|
+
});
|
|
130
|
+
if (!approved) {
|
|
131
|
+
stdout.write(`Adoption aborted: ${label} approval required.\n`);
|
|
132
|
+
}
|
|
133
|
+
return approved;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Fire approval gates for all four mutation categories in sequence.
|
|
137
|
+
* Returns false (and stops) at the first declined category.
|
|
138
|
+
*
|
|
139
|
+
* For non-interactive runs (no tty + no supplied stdin), returns false immediately
|
|
140
|
+
* unless `options.stdin` is explicitly set (which signals the caller supplied a context stream).
|
|
141
|
+
*/
|
|
142
|
+
async function requireApprovalGates(plan, options = {}) {
|
|
143
|
+
const stdin = options.stdin ?? process.stdin;
|
|
144
|
+
// Non-interactive guard: if stdin is not a TTY and the caller hasn't explicitly supplied one,
|
|
145
|
+
// block before mutation (acceptance criterion 2).
|
|
146
|
+
if (!options.nonInteractiveSafe && stdin === process.stdin && !process.stdin.isTTY) {
|
|
147
|
+
const stdout = options.stdout ?? process.stdout;
|
|
148
|
+
stdout.write("Adoption aborted: non-interactive run requires a supplied context file (--context) or explicit approval.\n");
|
|
149
|
+
logAdoptionApprovalTelemetry(options.repoRoot ?? process.cwd(), {
|
|
150
|
+
event: "category-approval-blocked",
|
|
151
|
+
reason: "non-interactive",
|
|
152
|
+
timestamp: new Date().toISOString(),
|
|
153
|
+
});
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
const categories = [
|
|
157
|
+
"route-scaffold",
|
|
158
|
+
"doc-movement",
|
|
159
|
+
"instruction-file",
|
|
160
|
+
"graph-root",
|
|
161
|
+
];
|
|
162
|
+
for (const category of categories) {
|
|
163
|
+
const stepCategories = CATEGORY_STEP_MAP[category];
|
|
164
|
+
const steps = plan.steps.filter((s) => {
|
|
165
|
+
if (!stepCategories.includes(s.category))
|
|
166
|
+
return false;
|
|
167
|
+
// Doc-movement gate only fires for steps that migrateSmartDocs will execute
|
|
168
|
+
if (category === "doc-movement" && s.routing !== "candidate")
|
|
169
|
+
return false;
|
|
170
|
+
return true;
|
|
171
|
+
});
|
|
172
|
+
// Skip gate if no actionable steps in this category
|
|
173
|
+
if (steps.filter((s) => s.action !== "skip").length === 0)
|
|
174
|
+
continue;
|
|
175
|
+
const approved = await promptCategoryApproval(category, steps, options);
|
|
176
|
+
if (!approved)
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
return true;
|
|
180
|
+
}
|
package/dist/cli/adopt-assets.js
CHANGED
|
@@ -6,6 +6,7 @@ exports.runGraphBuild = runGraphBuild;
|
|
|
6
6
|
const node_fs_1 = require("node:fs");
|
|
7
7
|
const node_child_process_1 = require("node:child_process");
|
|
8
8
|
const node_path_1 = require("node:path");
|
|
9
|
+
const sync_js_1 = require("../agent-plugin/sync.js");
|
|
9
10
|
function isAncestorSymlink(repoRoot, relPath) {
|
|
10
11
|
const parts = relPath.split("/").filter(Boolean);
|
|
11
12
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
@@ -168,7 +169,14 @@ function installWorkspaceAssets(repoRoot, workspaceDir) {
|
|
|
168
169
|
installed.push(rulesRel);
|
|
169
170
|
}
|
|
170
171
|
}
|
|
171
|
-
|
|
172
|
+
// 6. Agent-plugin shims (Claude Code commands)
|
|
173
|
+
const shimOutDir = (0, node_path_1.join)(repoRoot, ".claude", "commands");
|
|
174
|
+
if (isAncestorSymlink(repoRoot, ".claude/commands")) {
|
|
175
|
+
skipped.push(".claude/commands");
|
|
176
|
+
return { installed, alreadyPresent, skipped, conflicted };
|
|
177
|
+
}
|
|
178
|
+
const shimSync = (0, sync_js_1.syncShims)(shimOutDir);
|
|
179
|
+
return { installed, alreadyPresent, skipped, conflicted, shimSync };
|
|
172
180
|
}
|
|
173
181
|
function runGraphBuild(repoRoot) {
|
|
174
182
|
try {
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runAdoptionInterview = runAdoptionInterview;
|
|
3
4
|
exports.runAdoptPhase = runAdoptPhase;
|
|
4
5
|
exports.runFullAdoption = runFullAdoption;
|
|
5
6
|
exports.createAdoptCommand = createAdoptCommand;
|
|
6
7
|
const node_path_1 = require("node:path");
|
|
8
|
+
const node_readline_1 = require("node:readline");
|
|
9
|
+
const node_fs_1 = require("node:fs");
|
|
7
10
|
const commander_1 = require("commander");
|
|
8
11
|
const adopt_scan_js_1 = require("./adopt-scan.js");
|
|
9
12
|
const adoption_plan_js_1 = require("./adoption-plan.js");
|
|
@@ -14,6 +17,71 @@ const adopt_rules_js_1 = require("./adopt-rules.js");
|
|
|
14
17
|
const adopt_cognition_js_1 = require("./adopt-cognition.js");
|
|
15
18
|
const adopt_canon_js_1 = require("./adopt-canon.js");
|
|
16
19
|
const adopt_genesis_js_1 = require("./adopt-genesis.js");
|
|
20
|
+
const generator_js_1 = require("../skill-packet/generator.js");
|
|
21
|
+
const foreman_dispatch_js_1 = require("../loop/adapters/foreman-dispatch.js");
|
|
22
|
+
const adoption_context_js_1 = require("./adoption-context.js");
|
|
23
|
+
const adopt_approve_js_1 = require("./adopt-approve.js");
|
|
24
|
+
/** Prompt a single question via stdin and return the trimmed answer. */
|
|
25
|
+
async function prompt(question) {
|
|
26
|
+
const rl = (0, node_readline_1.createInterface)({ input: process.stdin, output: process.stdout });
|
|
27
|
+
return new Promise((resolve) => {
|
|
28
|
+
rl.question(question, (answer) => {
|
|
29
|
+
rl.close();
|
|
30
|
+
resolve(answer.trim());
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Run a Foreman-led interview seeded from inventory gaps.
|
|
36
|
+
* Skips questions already answered (resume support).
|
|
37
|
+
*/
|
|
38
|
+
async function runAdoptionInterview(repoRoot, inventory) {
|
|
39
|
+
const existing = (0, adoption_context_js_1.loadOperatorContext)(repoRoot);
|
|
40
|
+
const ctx = existing ?? (0, adoption_context_js_1.createEmptyOperatorContext)();
|
|
41
|
+
// Seed: untrusted docs (smartdocs candidates not yet classified as trusted/stale)
|
|
42
|
+
const candidatePaths = inventory.smartdocs_candidates.map((c) => c.path);
|
|
43
|
+
const unclassified = candidatePaths.filter((p) => !ctx.trusted_docs.includes(p) && !ctx.stale_docs.includes(p));
|
|
44
|
+
if (unclassified.length > 0) {
|
|
45
|
+
process.stdout.write(`\nFound ${unclassified.length} doc candidate(s) not yet classified:\n` +
|
|
46
|
+
unclassified.map((p) => ` - ${p}`).join("\n") +
|
|
47
|
+
"\n");
|
|
48
|
+
const trusted = await prompt("Which of these are authoritative/trusted? (comma-separated paths, or leave blank to skip): ");
|
|
49
|
+
const stale = await prompt("Which are stale/outdated? (comma-separated paths, or leave blank to skip): ");
|
|
50
|
+
if (trusted)
|
|
51
|
+
ctx.trusted_docs.push(...trusted.split(",").map((s) => s.trim()).filter(Boolean));
|
|
52
|
+
if (stale)
|
|
53
|
+
ctx.stale_docs.push(...stale.split(",").map((s) => s.trim()).filter(Boolean));
|
|
54
|
+
}
|
|
55
|
+
// Seed: ambiguous source roots (more than one detected)
|
|
56
|
+
if (inventory.source_roots.length > 1 && ctx.priority_systems.length === 0) {
|
|
57
|
+
process.stdout.write(`\nMultiple source roots detected: ${inventory.source_roots.join(", ")}\n`);
|
|
58
|
+
const priority = await prompt("Which are the primary/priority systems? (comma-separated, or leave blank): ");
|
|
59
|
+
if (priority)
|
|
60
|
+
ctx.priority_systems.push(...priority.split(",").map((s) => s.trim()).filter(Boolean));
|
|
61
|
+
}
|
|
62
|
+
// Seed: instruction files not yet assigned intent
|
|
63
|
+
const unintended = inventory.agent_instruction_files
|
|
64
|
+
.map((f) => f.path)
|
|
65
|
+
.filter((p) => !(p in ctx.instruction_file_intent));
|
|
66
|
+
for (const filePath of unintended) {
|
|
67
|
+
const file = inventory.agent_instruction_files.find((f) => f.path === filePath);
|
|
68
|
+
const suggestion = file?.recommendation ?? "preserve";
|
|
69
|
+
const answer = await prompt(`Instruction file "${filePath}" — intent? [preserve/migrate/thin-adapter] (default: ${suggestion}): `);
|
|
70
|
+
const intent = ["preserve", "migrate", "thin-adapter"].find((v) => v === answer) ?? suggestion;
|
|
71
|
+
ctx.instruction_file_intent[filePath] = intent;
|
|
72
|
+
}
|
|
73
|
+
// Seed: canonical-folder candidates not yet classified as trusted or never-touch
|
|
74
|
+
const uncheckedFolders = inventory.likely_canonical_folders.filter((f) => !ctx.trusted_docs.includes(f) && !ctx.never_touch.includes(f));
|
|
75
|
+
if (uncheckedFolders.length > 0) {
|
|
76
|
+
process.stdout.write(`\nLikely canonical folders: ${uncheckedFolders.join(", ")}\n`);
|
|
77
|
+
const neverTouch = await prompt("Any folders that should never be touched by adoption? (comma-separated, or leave blank): ");
|
|
78
|
+
if (neverTouch)
|
|
79
|
+
ctx.never_touch.push(...neverTouch.split(",").map((s) => s.trim()).filter(Boolean));
|
|
80
|
+
}
|
|
81
|
+
ctx.answered_at = new Date().toISOString();
|
|
82
|
+
(0, adoption_context_js_1.saveOperatorContext)(repoRoot, ctx);
|
|
83
|
+
return ctx;
|
|
84
|
+
}
|
|
17
85
|
function estimateTokenCost(inventory) {
|
|
18
86
|
const total = inventory.smartdocs_candidates.length +
|
|
19
87
|
inventory.likely_canonical_folders.length;
|
|
@@ -29,11 +97,17 @@ async function runAdoptPhase(phase, repoRoot, options = {}) {
|
|
|
29
97
|
switch (phase) {
|
|
30
98
|
case "scan": {
|
|
31
99
|
const inventory = await (0, adopt_scan_js_1.scanRepo)(repoRoot, { rescan: true });
|
|
32
|
-
const
|
|
33
|
-
(0, adoption_plan_js_1.generateAdoptionPlanArtifacts)(repoRoot, inventory);
|
|
100
|
+
const operatorContext = (0, adoption_context_js_1.loadOperatorContext)(repoRoot) ?? undefined;
|
|
101
|
+
(0, adoption_plan_js_1.generateAdoptionPlanArtifacts)(repoRoot, inventory, { operatorContext });
|
|
34
102
|
console.log(`Scan complete: ${inventory.smartdocs_candidates.length} doc(s), ${inventory.likely_canonical_folders.length} folder(s).`);
|
|
35
103
|
break;
|
|
36
104
|
}
|
|
105
|
+
case "interview": {
|
|
106
|
+
const inventory = options.inventory ?? (await (0, adopt_scan_js_1.scanRepo)(repoRoot, { rescan: false }));
|
|
107
|
+
await runAdoptionInterview(repoRoot, inventory);
|
|
108
|
+
console.log("Interview complete. Operator context saved.");
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
37
111
|
case "agents": {
|
|
38
112
|
if (options.inventory) {
|
|
39
113
|
const cost = estimateTokenCost(options.inventory);
|
|
@@ -42,6 +116,24 @@ async function runAdoptPhase(phase, repoRoot, options = {}) {
|
|
|
42
116
|
if (!options.skipAgents) {
|
|
43
117
|
await (0, agent_setup_js_1.runAgentSetup)(repoRoot);
|
|
44
118
|
}
|
|
119
|
+
// Dispatch the Foreman with the setup-bootstrap packet.
|
|
120
|
+
try {
|
|
121
|
+
const configPath = (0, node_path_1.join)(repoRoot, "polaris.config.json");
|
|
122
|
+
const config = JSON.parse((0, node_fs_1.readFileSync)(configPath, "utf-8"));
|
|
123
|
+
const execution = config.execution;
|
|
124
|
+
if (execution?.providers && Object.keys(execution.providers).length > 0) {
|
|
125
|
+
const binding = await (0, agent_setup_js_1.resolveForeman)(repoRoot, config);
|
|
126
|
+
const setupPacket = (0, generator_js_1.generateSetupBootstrapPacket)("adopt");
|
|
127
|
+
await (0, foreman_dispatch_js_1.dispatchForeman)({
|
|
128
|
+
packet: setupPacket,
|
|
129
|
+
provider: binding.provider,
|
|
130
|
+
executionConfig: execution,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// Foreman dispatch is best-effort — don't block adoption.
|
|
136
|
+
}
|
|
45
137
|
break;
|
|
46
138
|
}
|
|
47
139
|
case "consolidate": {
|
|
@@ -91,25 +183,34 @@ async function runAdoptPhase(phase, repoRoot, options = {}) {
|
|
|
91
183
|
}
|
|
92
184
|
}
|
|
93
185
|
async function runFullAdoption(repoRoot, options = {}) {
|
|
94
|
-
console.log("[1/
|
|
186
|
+
console.log("[1/8] scan");
|
|
95
187
|
await runAdoptPhase("scan", repoRoot);
|
|
96
|
-
// Re-read inventory
|
|
188
|
+
// Re-read inventory for later phases
|
|
97
189
|
const inventory = await (0, adopt_scan_js_1.scanRepo)(repoRoot, { rescan: false });
|
|
98
|
-
|
|
99
|
-
|
|
190
|
+
console.log("[2/8] interview");
|
|
191
|
+
await runAdoptPhase("interview", repoRoot, { inventory });
|
|
192
|
+
// Regenerate plan after interview to incorporate operator context
|
|
193
|
+
const operatorContext = (0, adoption_context_js_1.loadOperatorContext)(repoRoot) ?? undefined;
|
|
194
|
+
const plan = (0, adoption_plan_js_1.generateAdoptionPlan)(inventory, { operatorContext });
|
|
195
|
+
console.log("[3/8] agents");
|
|
100
196
|
await runAdoptPhase("agents", repoRoot, {
|
|
101
197
|
inventory,
|
|
102
198
|
skipAgents: options.skipAgents,
|
|
103
199
|
});
|
|
104
|
-
|
|
200
|
+
// Approval gates: one per mutation category before any broad mutation.
|
|
201
|
+
const gatesApproved = await (0, adopt_approve_js_1.requireApprovalGates)(plan, { repoRoot });
|
|
202
|
+
if (!gatesApproved) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
console.log("[4/8] consolidate");
|
|
105
206
|
await runAdoptPhase("consolidate", repoRoot, { inventory, plan });
|
|
106
|
-
console.log("[
|
|
207
|
+
console.log("[5/8] map");
|
|
107
208
|
await runAdoptPhase("map", repoRoot);
|
|
108
|
-
console.log("[
|
|
209
|
+
console.log("[6/8] skills");
|
|
109
210
|
await runAdoptPhase("skills", repoRoot);
|
|
110
|
-
console.log("[
|
|
211
|
+
console.log("[7/8] rules");
|
|
111
212
|
await runAdoptPhase("rules", repoRoot, { inventory });
|
|
112
|
-
console.log("[
|
|
213
|
+
console.log("[8/8] canon");
|
|
113
214
|
await runAdoptPhase("canon", repoRoot, { inventory, plan });
|
|
114
215
|
}
|
|
115
216
|
function createAdoptCommand(opts) {
|
|
@@ -124,6 +225,17 @@ function createAdoptCommand(opts) {
|
|
|
124
225
|
.action(async () => {
|
|
125
226
|
await runAdoptPhase("scan", repoRoot);
|
|
126
227
|
});
|
|
228
|
+
cmd
|
|
229
|
+
.command("interview")
|
|
230
|
+
.description("Run Foreman-led interview to capture operator context")
|
|
231
|
+
.action(async () => {
|
|
232
|
+
if (!process.stdin.isTTY) {
|
|
233
|
+
process.stderr.write("Error: the interview subcommand requires an interactive terminal.\n");
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
const inventory = await (0, adopt_scan_js_1.scanRepo)(repoRoot, { rescan: false });
|
|
237
|
+
await runAdoptPhase("interview", repoRoot, { inventory });
|
|
238
|
+
});
|
|
127
239
|
cmd
|
|
128
240
|
.command("agents")
|
|
129
241
|
.description("Set up agent instruction files")
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.handleInstructionFiles = handleInstructionFiles;
|
|
4
4
|
const node_fs_1 = require("node:fs");
|
|
5
5
|
const node_path_1 = require("node:path");
|
|
6
|
+
const adopt_approve_js_1 = require("./adopt-approve.js");
|
|
6
7
|
const DELEGATION_MARKERS = ["<!-- polaris:delegate", "POLARIS_RULES.md", "POLARIS.md", "Polaris runtime"];
|
|
7
8
|
function isSupportedInstructionPath(path) {
|
|
8
9
|
return (path === "CLAUDE.md" ||
|
|
@@ -100,9 +101,23 @@ function appendInstructionProvenance(repoRoot, records) {
|
|
|
100
101
|
};
|
|
101
102
|
(0, node_fs_1.writeFileSync)(provenancePath, `${JSON.stringify(updated, null, 2)}\n`, "utf-8");
|
|
102
103
|
}
|
|
103
|
-
function handleInstructionFiles(plan, inventory, repoRoot) {
|
|
104
|
+
async function handleInstructionFiles(plan, inventory, repoRoot, options = {}) {
|
|
104
105
|
if (plan.dry_run) {
|
|
105
|
-
return
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
// Approval gate: show instruction-refactor steps and ask before mutating,
|
|
109
|
+
// unless the overall plan is already approved or the caller opts out.
|
|
110
|
+
if (!options.skipApprovalGate && !plan.approved) {
|
|
111
|
+
const instructionSteps = plan.steps.filter((s) => s.category === "instruction-refactor");
|
|
112
|
+
if (instructionSteps.filter((s) => s.action !== "skip").length > 0) {
|
|
113
|
+
const approved = await (0, adopt_approve_js_1.promptCategoryApproval)("instruction-file", instructionSteps, {
|
|
114
|
+
repoRoot,
|
|
115
|
+
stdin: options.stdin,
|
|
116
|
+
stdout: options.stdout,
|
|
117
|
+
});
|
|
118
|
+
if (!approved)
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
106
121
|
}
|
|
107
122
|
const doctrineExists = hasDoctrine(repoRoot);
|
|
108
123
|
const now = new Date().toISOString();
|
|
@@ -120,6 +120,15 @@ function migrateSmartDocs(plan, repoRoot = (0, node_path_1.resolve)(process.cwd(
|
|
|
120
120
|
if (step.category !== "smartdocs-migrate" || COMPLETE_STATUSES.has(step.status)) {
|
|
121
121
|
continue;
|
|
122
122
|
}
|
|
123
|
+
if (step.routing !== undefined && step.routing !== "candidate") {
|
|
124
|
+
effectivePlan.steps[index] = {
|
|
125
|
+
...step,
|
|
126
|
+
status: "skipped",
|
|
127
|
+
completed_at: now,
|
|
128
|
+
error: `routing not candidate: ${step.routing}`,
|
|
129
|
+
};
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
123
132
|
const sourcePath = step.source_path ?? "";
|
|
124
133
|
const destPath = step.dest_path ?? `smartdocs/raw/${(0, node_path_1.basename)(sourcePath || `step-${step.order}.md`)}`;
|
|
125
134
|
if (!sourcePath) {
|