@lsctech/polaris 0.3.29 → 0.4.0
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/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/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/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/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
|
@@ -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) {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadOperatorContext = loadOperatorContext;
|
|
4
|
+
exports.saveOperatorContext = saveOperatorContext;
|
|
5
|
+
exports.createEmptyOperatorContext = createEmptyOperatorContext;
|
|
6
|
+
const node_fs_1 = require("node:fs");
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
const ADOPTION_DIR = (0, node_path_1.join)(".polaris", "adoption");
|
|
9
|
+
const CONTEXT_FILE = "operator-context.json";
|
|
10
|
+
function contextPath(repoRoot) {
|
|
11
|
+
return (0, node_path_1.join)(repoRoot, ADOPTION_DIR, CONTEXT_FILE);
|
|
12
|
+
}
|
|
13
|
+
function loadOperatorContext(repoRoot) {
|
|
14
|
+
const path = contextPath(repoRoot);
|
|
15
|
+
if (!(0, node_fs_1.existsSync)(path))
|
|
16
|
+
return null;
|
|
17
|
+
try {
|
|
18
|
+
const parsed = JSON.parse((0, node_fs_1.readFileSync)(path, "utf-8"));
|
|
19
|
+
if (!Array.isArray(parsed?.trusted_docs) ||
|
|
20
|
+
!Array.isArray(parsed?.never_touch) ||
|
|
21
|
+
typeof parsed?.instruction_file_intent !== "object" ||
|
|
22
|
+
parsed?.instruction_file_intent === null ||
|
|
23
|
+
Array.isArray(parsed?.instruction_file_intent)) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
return parsed;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function saveOperatorContext(repoRoot, context) {
|
|
33
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(repoRoot, ADOPTION_DIR), { recursive: true });
|
|
34
|
+
(0, node_fs_1.writeFileSync)(contextPath(repoRoot), `${JSON.stringify(context, null, 2)}\n`, "utf-8");
|
|
35
|
+
}
|
|
36
|
+
function createEmptyOperatorContext(now) {
|
|
37
|
+
return {
|
|
38
|
+
schema_version: "1.0",
|
|
39
|
+
answered_at: (now ?? new Date()).toISOString(),
|
|
40
|
+
trusted_docs: [],
|
|
41
|
+
stale_docs: [],
|
|
42
|
+
never_touch: [],
|
|
43
|
+
priority_systems: [],
|
|
44
|
+
instruction_file_intent: {},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -11,14 +11,49 @@ function formatPlanId(isoTimestamp) {
|
|
|
11
11
|
function normalizePath(path) {
|
|
12
12
|
return path.endsWith("/") ? path.slice(0, -1) : path;
|
|
13
13
|
}
|
|
14
|
+
function isTrusted(path, operatorContext) {
|
|
15
|
+
const normalized = normalizePath(path);
|
|
16
|
+
return operatorContext?.trusted_docs.some((p) => normalizePath(p) === normalized) ?? false;
|
|
17
|
+
}
|
|
18
|
+
function isStale(path, operatorContext) {
|
|
19
|
+
const normalized = normalizePath(path);
|
|
20
|
+
return operatorContext?.stale_docs.some((p) => normalizePath(p) === normalized) ?? false;
|
|
21
|
+
}
|
|
22
|
+
function isNeverTouch(path, operatorContext) {
|
|
23
|
+
const normalized = normalizePath(path);
|
|
24
|
+
return operatorContext?.never_touch.some((p) => normalizePath(p) === normalized) ?? false;
|
|
25
|
+
}
|
|
26
|
+
function instructionIntent(path, operatorContext) {
|
|
27
|
+
return operatorContext?.instruction_file_intent[path];
|
|
28
|
+
}
|
|
29
|
+
function deriveSmartDocsRouting(candidate, operatorContext) {
|
|
30
|
+
const evidence_refs = [`scan:smartdocs_candidate:${candidate.path}`];
|
|
31
|
+
const operator_refs = [];
|
|
32
|
+
if (isNeverTouch(candidate.path, operatorContext) || isStale(candidate.path, operatorContext)) {
|
|
33
|
+
if (isNeverTouch(candidate.path, operatorContext))
|
|
34
|
+
operator_refs.push(`operator:never_touch:${candidate.path}`);
|
|
35
|
+
if (isStale(candidate.path, operatorContext))
|
|
36
|
+
operator_refs.push(`operator:stale_docs:${candidate.path}`);
|
|
37
|
+
return { routing: "hold", evidence_refs, operator_refs };
|
|
38
|
+
}
|
|
39
|
+
if (isTrusted(candidate.path, operatorContext)) {
|
|
40
|
+
operator_refs.push(`operator:trusted_docs:${candidate.path}`);
|
|
41
|
+
return { routing: "candidate", evidence_refs, operator_refs };
|
|
42
|
+
}
|
|
43
|
+
return { routing: "review-required", evidence_refs, operator_refs };
|
|
44
|
+
}
|
|
14
45
|
function createStep(order, partial) {
|
|
46
|
+
const { evidence_refs, operator_refs, routing, ...rest } = partial;
|
|
15
47
|
return {
|
|
16
|
-
|
|
48
|
+
evidence_refs: evidence_refs ?? [],
|
|
49
|
+
operator_refs: operator_refs ?? [],
|
|
50
|
+
routing: routing ?? "candidate",
|
|
51
|
+
...rest,
|
|
17
52
|
order,
|
|
18
53
|
status: "pending",
|
|
19
54
|
};
|
|
20
55
|
}
|
|
21
|
-
function buildSteps(inventory) {
|
|
56
|
+
function buildSteps(inventory, operatorContext) {
|
|
22
57
|
const steps = [];
|
|
23
58
|
let order = 1;
|
|
24
59
|
steps.push(createStep(order++, {
|
|
@@ -31,6 +66,9 @@ function buildSteps(inventory) {
|
|
|
31
66
|
destructive: true,
|
|
32
67
|
requires_approval: false,
|
|
33
68
|
estimated_risk: "low",
|
|
69
|
+
routing: "candidate",
|
|
70
|
+
evidence_refs: ["adoption:step:provider-config-lock"],
|
|
71
|
+
operator_refs: [],
|
|
34
72
|
}));
|
|
35
73
|
steps.push(createStep(order++, {
|
|
36
74
|
step_id: "adoption-scaffold",
|
|
@@ -42,6 +80,9 @@ function buildSteps(inventory) {
|
|
|
42
80
|
destructive: false,
|
|
43
81
|
requires_approval: false,
|
|
44
82
|
estimated_risk: "low",
|
|
83
|
+
routing: "candidate",
|
|
84
|
+
evidence_refs: ["adoption:step:adoption-scaffold"],
|
|
85
|
+
operator_refs: [],
|
|
45
86
|
}));
|
|
46
87
|
steps.push(createStep(order++, {
|
|
47
88
|
step_id: "workspace-root-surfaces",
|
|
@@ -53,10 +94,15 @@ function buildSteps(inventory) {
|
|
|
53
94
|
destructive: false,
|
|
54
95
|
requires_approval: false,
|
|
55
96
|
estimated_risk: "low",
|
|
97
|
+
routing: "candidate",
|
|
98
|
+
evidence_refs: ["adoption:step:workspace-root-surfaces"],
|
|
99
|
+
operator_refs: [],
|
|
56
100
|
}));
|
|
57
101
|
for (const candidate of inventory.smartdocs_candidates) {
|
|
58
|
-
|
|
59
|
-
|
|
102
|
+
const { routing, evidence_refs, operator_refs } = deriveSmartDocsRouting(candidate, operatorContext);
|
|
103
|
+
const stepOrder = order++;
|
|
104
|
+
steps.push(createStep(stepOrder, {
|
|
105
|
+
step_id: `smartdocs-migrate-${stepOrder.toString().padStart(3, "0")}`,
|
|
60
106
|
phase: "C",
|
|
61
107
|
category: "smartdocs-migrate",
|
|
62
108
|
action: "move",
|
|
@@ -66,12 +112,27 @@ function buildSteps(inventory) {
|
|
|
66
112
|
destructive: true,
|
|
67
113
|
requires_approval: true,
|
|
68
114
|
estimated_risk: candidate.estimated_risk,
|
|
115
|
+
routing,
|
|
116
|
+
evidence_refs,
|
|
117
|
+
operator_refs,
|
|
69
118
|
}));
|
|
70
119
|
}
|
|
71
120
|
for (const folder of inventory.likely_canonical_folders) {
|
|
72
121
|
const normalizedFolder = normalizePath(folder);
|
|
73
|
-
|
|
74
|
-
|
|
122
|
+
const evidence_refs = [`scan:canonical_folder:${normalizedFolder}`];
|
|
123
|
+
const operator_refs = [];
|
|
124
|
+
let routing = "raw";
|
|
125
|
+
if (isNeverTouch(normalizedFolder, operatorContext)) {
|
|
126
|
+
routing = "hold";
|
|
127
|
+
operator_refs.push(`operator:never_touch:${normalizedFolder}`);
|
|
128
|
+
}
|
|
129
|
+
else if (isTrusted(normalizedFolder, operatorContext)) {
|
|
130
|
+
routing = "candidate";
|
|
131
|
+
operator_refs.push(`operator:trusted_docs:${normalizedFolder}`);
|
|
132
|
+
}
|
|
133
|
+
const stepOrder = order++;
|
|
134
|
+
steps.push(createStep(stepOrder, {
|
|
135
|
+
step_id: `cognition-generate-${stepOrder.toString().padStart(3, "0")}`,
|
|
75
136
|
phase: "C",
|
|
76
137
|
category: "cognition-generate",
|
|
77
138
|
action: "create",
|
|
@@ -80,6 +141,9 @@ function buildSteps(inventory) {
|
|
|
80
141
|
destructive: false,
|
|
81
142
|
requires_approval: true,
|
|
82
143
|
estimated_risk: "medium",
|
|
144
|
+
routing,
|
|
145
|
+
evidence_refs,
|
|
146
|
+
operator_refs,
|
|
83
147
|
}));
|
|
84
148
|
}
|
|
85
149
|
for (const file of inventory.agent_instruction_files) {
|
|
@@ -90,8 +154,24 @@ function buildSteps(inventory) {
|
|
|
90
154
|
: file.recommendation === "thin-adapter"
|
|
91
155
|
? "medium"
|
|
92
156
|
: "low";
|
|
93
|
-
|
|
94
|
-
|
|
157
|
+
const evidence_refs = [`scan:agent_instruction_file:${file.path}`];
|
|
158
|
+
const operator_refs = [];
|
|
159
|
+
const intent = instructionIntent(file.path, operatorContext);
|
|
160
|
+
let routing;
|
|
161
|
+
if (intent === "preserve") {
|
|
162
|
+
routing = "hold";
|
|
163
|
+
operator_refs.push(`operator:instruction_file_intent:${file.path}:preserve`);
|
|
164
|
+
}
|
|
165
|
+
else if (intent === "migrate" || intent === "thin-adapter") {
|
|
166
|
+
routing = "candidate";
|
|
167
|
+
operator_refs.push(`operator:instruction_file_intent:${file.path}:${intent}`);
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
routing = "review-required";
|
|
171
|
+
}
|
|
172
|
+
const stepOrder = order++;
|
|
173
|
+
steps.push(createStep(stepOrder, {
|
|
174
|
+
step_id: `instruction-refactor-${stepOrder.toString().padStart(3, "0")}`,
|
|
95
175
|
phase: "C",
|
|
96
176
|
category: "instruction-refactor",
|
|
97
177
|
action,
|
|
@@ -105,11 +185,15 @@ function buildSteps(inventory) {
|
|
|
105
185
|
destructive,
|
|
106
186
|
requires_approval: destructive,
|
|
107
187
|
estimated_risk: risk,
|
|
188
|
+
routing,
|
|
189
|
+
evidence_refs,
|
|
190
|
+
operator_refs,
|
|
108
191
|
}));
|
|
109
192
|
}
|
|
110
193
|
for (const ignorePath of inventory.ignore_candidates) {
|
|
111
|
-
|
|
112
|
-
|
|
194
|
+
const stepOrder = order++;
|
|
195
|
+
steps.push(createStep(stepOrder, {
|
|
196
|
+
step_id: `ignore-rules-${stepOrder.toString().padStart(3, "0")}`,
|
|
113
197
|
phase: "C",
|
|
114
198
|
category: "ignore-rules",
|
|
115
199
|
action: "append",
|
|
@@ -118,6 +202,9 @@ function buildSteps(inventory) {
|
|
|
118
202
|
destructive: true,
|
|
119
203
|
requires_approval: true,
|
|
120
204
|
estimated_risk: "low",
|
|
205
|
+
routing: "candidate",
|
|
206
|
+
evidence_refs: [`scan:ignore_candidate:${ignorePath}`],
|
|
207
|
+
operator_refs: [],
|
|
121
208
|
}));
|
|
122
209
|
}
|
|
123
210
|
steps.push(createStep(order++, {
|
|
@@ -130,6 +217,9 @@ function buildSteps(inventory) {
|
|
|
130
217
|
destructive: true,
|
|
131
218
|
requires_approval: true,
|
|
132
219
|
estimated_risk: "medium",
|
|
220
|
+
routing: "candidate",
|
|
221
|
+
evidence_refs: ["adoption:step:atlas-generate"],
|
|
222
|
+
operator_refs: [],
|
|
133
223
|
}));
|
|
134
224
|
steps.push(createStep(order++, {
|
|
135
225
|
step_id: "stage-adoption",
|
|
@@ -141,6 +231,9 @@ function buildSteps(inventory) {
|
|
|
141
231
|
destructive: true,
|
|
142
232
|
requires_approval: true,
|
|
143
233
|
estimated_risk: "medium",
|
|
234
|
+
routing: "candidate",
|
|
235
|
+
evidence_refs: ["adoption:step:stage-adoption"],
|
|
236
|
+
operator_refs: [],
|
|
144
237
|
}));
|
|
145
238
|
return steps;
|
|
146
239
|
}
|
|
@@ -160,7 +253,7 @@ function buildImpactSummary(steps, inventory) {
|
|
|
160
253
|
}
|
|
161
254
|
function generateAdoptionPlan(inventory, options = {}) {
|
|
162
255
|
const generatedAt = (options.now ?? new Date()).toISOString();
|
|
163
|
-
const steps = buildSteps(inventory);
|
|
256
|
+
const steps = buildSteps(inventory, options.operatorContext);
|
|
164
257
|
return {
|
|
165
258
|
plan_id: formatPlanId(generatedAt),
|
|
166
259
|
generated_at: generatedAt,
|
|
@@ -206,7 +299,7 @@ function renderAdoptionPlanMarkdown(plan) {
|
|
|
206
299
|
for (const step of phaseSteps) {
|
|
207
300
|
const source = step.source_path ? ` source: \`${step.source_path}\`` : "";
|
|
208
301
|
const dest = step.dest_path ? ` destination: \`${step.dest_path}\`` : "";
|
|
209
|
-
lines.push(`- **${step.order}. ${step.step_id}** — ${step.category} / ${step.action} / approval ${step.requires_approval ? "required" : "not required"} / risk ${step.estimated_risk} / status ${step.status}${source}${dest}`);
|
|
302
|
+
lines.push(`- **${step.order}. ${step.step_id}** — ${step.category} / ${step.action} / approval ${step.requires_approval ? "required" : "not required"} / risk ${step.estimated_risk} / routing ${step.routing} / status ${step.status}${source}${dest}`);
|
|
210
303
|
lines.push(` - ${step.description}`);
|
|
211
304
|
}
|
|
212
305
|
lines.push("");
|
package/dist/cli/agent-setup.js
CHANGED
|
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.resolveForeman = resolveForeman;
|
|
36
37
|
exports.runAgentSetup = runAgentSetup;
|
|
37
38
|
const node_child_process_1 = require("node:child_process");
|
|
38
39
|
const readline = __importStar(require("node:readline"));
|
|
@@ -86,6 +87,52 @@ async function selectProvider(role, detected, currentProviders) {
|
|
|
86
87
|
});
|
|
87
88
|
});
|
|
88
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Resolves the Foreman provider for init/adopt.
|
|
92
|
+
*
|
|
93
|
+
* If `execution.providerPolicy.foreman.providers[0]` is already set in config,
|
|
94
|
+
* returns a binding immediately (no prompt). Otherwise prompts once, persists
|
|
95
|
+
* the choice to polaris.config.json, and returns the binding.
|
|
96
|
+
*
|
|
97
|
+
* The binding always references `.polaris/roles/foreman.md` — role text is not
|
|
98
|
+
* duplicated here.
|
|
99
|
+
*/
|
|
100
|
+
async function resolveForeman(repoRoot, config, opts) {
|
|
101
|
+
const execution = config.execution ?? {};
|
|
102
|
+
const providerPolicy = execution.providerPolicy ?? {};
|
|
103
|
+
const foremanPolicy = providerPolicy.foreman ?? {};
|
|
104
|
+
const existing = foremanPolicy.providers?.[0];
|
|
105
|
+
const roleFile = ".polaris/roles/foreman.md";
|
|
106
|
+
if (existing) {
|
|
107
|
+
return { provider: existing, roleFile };
|
|
108
|
+
}
|
|
109
|
+
// Prompt once for Foreman provider
|
|
110
|
+
const detected = opts?.detectProviders?.() ?? detectProviders();
|
|
111
|
+
const chosen = opts?.selectProvider
|
|
112
|
+
? await opts.selectProvider(detected)
|
|
113
|
+
: await selectProvider("foreman", detected, []);
|
|
114
|
+
if (!chosen) {
|
|
115
|
+
throw new Error("No supported agent installed. Cannot assign a Foreman. " +
|
|
116
|
+
"Install one of: " + SUPPORTED_PROVIDERS.map((p) => p.name).join(", "));
|
|
117
|
+
}
|
|
118
|
+
// Persist choice
|
|
119
|
+
const updatedPolicy = {
|
|
120
|
+
...providerPolicy,
|
|
121
|
+
foreman: { ...foremanPolicy, providers: [chosen] },
|
|
122
|
+
};
|
|
123
|
+
const updatedConfig = {
|
|
124
|
+
...config,
|
|
125
|
+
execution: { ...execution, providerPolicy: updatedPolicy },
|
|
126
|
+
};
|
|
127
|
+
const configPath = (0, node_path_1.resolve)(repoRoot, "polaris.config.json");
|
|
128
|
+
if (opts?.writeConfig) {
|
|
129
|
+
opts.writeConfig(configPath, updatedConfig);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
(0, node_fs_1.writeFileSync)(configPath, JSON.stringify(updatedConfig, null, 2) + "\n");
|
|
133
|
+
}
|
|
134
|
+
return { provider: chosen, roleFile };
|
|
135
|
+
}
|
|
89
136
|
async function runAgentSetup(repoRoot) {
|
|
90
137
|
console.log("\nPolaris Agent Setup — configure one provider per role\n");
|
|
91
138
|
const detected = detectProviders();
|
package/dist/cli/init.js
CHANGED
|
@@ -21,9 +21,15 @@ const index_js_1 = require("../map/index.js");
|
|
|
21
21
|
const adopt_instructions_js_1 = require("./adopt-instructions.js");
|
|
22
22
|
const adopt_workspace_js_1 = require("./adopt-workspace.js");
|
|
23
23
|
const adopt_assets_js_1 = require("./adopt-assets.js");
|
|
24
|
+
const agent_setup_js_1 = require("./agent-setup.js");
|
|
25
|
+
const runner_js_1 = require("./setup-interview/runner.js");
|
|
26
|
+
const generate_js_1 = require("./setup-interview/generate.js");
|
|
27
|
+
const generator_js_1 = require("../skill-packet/generator.js");
|
|
28
|
+
const foreman_dispatch_js_1 = require("../loop/adapters/foreman-dispatch.js");
|
|
24
29
|
const adopt_genesis_js_1 = require("./adopt-genesis.js");
|
|
25
30
|
const adopt_report_js_1 = require("./adopt-report.js");
|
|
26
31
|
const artifact_policy_js_1 = require("../finalize/artifact-policy.js");
|
|
32
|
+
const report_js_1 = require("./setup-interview/report.js");
|
|
27
33
|
var init_detect_js_2 = require("./init-detect.js");
|
|
28
34
|
Object.defineProperty(exports, "detectRepoState", { enumerable: true, get: function () { return init_detect_js_2.detectRepoState; } });
|
|
29
35
|
const PLAN_COMPLETE_STATUSES = new Set(["completed", "skipped"]);
|
|
@@ -377,6 +383,50 @@ async function runInit(options = {}) {
|
|
|
377
383
|
process.stdout.write("This repo has existing content. Run `polaris init --adopt` to begin adoption.\nThis repo has existing content. Run polaris init --adopt to begin adoption.\n");
|
|
378
384
|
return;
|
|
379
385
|
}
|
|
386
|
+
// New/empty repo: run the setup interview and generate artifacts behind an approval gate.
|
|
387
|
+
if (!options.adopt && (repoState === "empty" || repoState === "new")) {
|
|
388
|
+
const interviewFn = options.runInterview ?? runner_js_1.runInterview;
|
|
389
|
+
let record;
|
|
390
|
+
try {
|
|
391
|
+
record = await interviewFn(repoRoot, { now: options.now });
|
|
392
|
+
}
|
|
393
|
+
catch (err) {
|
|
394
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
395
|
+
process.stdout.write(`${msg}\n`);
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
const detected = detectCompaction(repoRoot);
|
|
399
|
+
const detectedRepoAnalysis = detectRepoAnalysis(repoRoot);
|
|
400
|
+
const generateFn = options.generateSetupArtifacts ?? generate_js_1.generateSetupArtifacts;
|
|
401
|
+
await generateFn(record, {
|
|
402
|
+
repoRoot,
|
|
403
|
+
dryRun: options.dryRun,
|
|
404
|
+
yes: options.yes,
|
|
405
|
+
now: options.now,
|
|
406
|
+
detectedProviders: detected,
|
|
407
|
+
detectedRepoAnalysis,
|
|
408
|
+
scaffoldRootSurfaces: options.scaffoldRootSurfaces,
|
|
409
|
+
generatePolarisRules: options.generatePolarisRules,
|
|
410
|
+
migrateSmartDocs: options.migrateSmartDocs,
|
|
411
|
+
runMapIndex: options.runMapIndex,
|
|
412
|
+
});
|
|
413
|
+
// Post-setup validation and checkpoint report
|
|
414
|
+
if (!options.dryRun) {
|
|
415
|
+
const checkpointReport = (0, report_js_1.buildCheckpointReport)({
|
|
416
|
+
repoRoot,
|
|
417
|
+
record,
|
|
418
|
+
now: options.now,
|
|
419
|
+
});
|
|
420
|
+
(0, report_js_1.writeCheckpointReport)(repoRoot, checkpointReport);
|
|
421
|
+
(0, report_js_1.printCheckpointReport)(checkpointReport);
|
|
422
|
+
// Check for validation failures
|
|
423
|
+
const failedValidations = Object.entries(checkpointReport.validationResults).filter(([_, result]) => !result.passed);
|
|
424
|
+
if (failedValidations.length > 0) {
|
|
425
|
+
process.stdout.write(`\nWarning: ${failedValidations.length} file(s) failed validation. Check the report above for details.\n`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
380
430
|
// Load existing config (if any) so we preserve user-authored fields.
|
|
381
431
|
let existing = {};
|
|
382
432
|
if ((0, node_fs_1.existsSync)(configPath)) {
|
|
@@ -415,6 +465,24 @@ async function runInit(options = {}) {
|
|
|
415
465
|
process.stdout.write(`polaris.config.json written to ${configPath}\n${providerSummary}\n`);
|
|
416
466
|
}
|
|
417
467
|
if (!options.adopt) {
|
|
468
|
+
// Launch Foreman with setup-bootstrap packet when execution providers are configured.
|
|
469
|
+
const initExecution = asRecord(updated.execution);
|
|
470
|
+
const initProviders = asRecord(initExecution.providers);
|
|
471
|
+
if (Object.keys(initProviders).length > 0) {
|
|
472
|
+
try {
|
|
473
|
+
const binding = await (0, agent_setup_js_1.resolveForeman)(repoRoot, updated);
|
|
474
|
+
const setupPacket = (0, generator_js_1.generateSetupBootstrapPacket)("init");
|
|
475
|
+
await (0, foreman_dispatch_js_1.dispatchForeman)({
|
|
476
|
+
packet: setupPacket,
|
|
477
|
+
provider: binding.provider,
|
|
478
|
+
executionConfig: updated.execution,
|
|
479
|
+
dryRun: options.dryRun,
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
catch {
|
|
483
|
+
// Foreman dispatch is best-effort during init — don't block setup.
|
|
484
|
+
}
|
|
485
|
+
}
|
|
418
486
|
return;
|
|
419
487
|
}
|
|
420
488
|
const adoptionPlanPath = (0, node_path_1.join)(repoRoot, ".polaris", "adoption-plan.json");
|
|
@@ -548,7 +616,7 @@ function createInitCommand(options = {}) {
|
|
|
548
616
|
.option("--status", "detect and print current repository state without writing files")
|
|
549
617
|
.option("--adopt", "run existing repository adoption flow")
|
|
550
618
|
.option("--resume", "resume an approved adoption plan without prompting")
|
|
551
|
-
.option("--yes", "auto-approve adoption plan
|
|
619
|
+
.option("--yes", "auto-approve setup or adoption plan without prompting")
|
|
552
620
|
.option("--commit", "create an adoption commit when used with --adopt")
|
|
553
621
|
.action(async (cmdOptions) => {
|
|
554
622
|
await runInit({
|