@lsctech/polaris 0.3.28 → 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/index.js +14 -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/dispatch.js +8 -2
- package/dist/loop/simplicity.js +34 -0
- package/dist/loop/worker-packet.js +2 -1
- package/dist/loop/worker-prompt.js +28 -0
- 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/index.js
CHANGED
|
@@ -27,6 +27,7 @@ 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 simplicity_js_1 = require("../loop/simplicity.js");
|
|
30
31
|
function resolveStateFile(repoRoot, explicit) {
|
|
31
32
|
if (explicit)
|
|
32
33
|
return (0, node_path_1.resolve)(explicit);
|
|
@@ -108,6 +109,19 @@ function createPolarisCommand(options = {}) {
|
|
|
108
109
|
repoRoot,
|
|
109
110
|
}));
|
|
110
111
|
program.addCommand((0, adopt_command_js_1.createAdoptCommand)({ repoRoot }));
|
|
112
|
+
program
|
|
113
|
+
.command("simplicity")
|
|
114
|
+
.description("View or override the simplicity discipline mode for the active run")
|
|
115
|
+
.option("--bypass", "Omit the discipline ladder from worker prompts for this run")
|
|
116
|
+
.option("--restore", "Re-enable the discipline ladder (clear bypass)")
|
|
117
|
+
.option("--state-file <path>", "Override path to current-state.json")
|
|
118
|
+
.action((cmdOptions) => {
|
|
119
|
+
(0, simplicity_js_1.runSimplicityCommand)({
|
|
120
|
+
bypass: !!cmdOptions.bypass,
|
|
121
|
+
restore: !!cmdOptions.restore,
|
|
122
|
+
stateFile: resolveStateFile(repoRoot, cmdOptions.stateFile),
|
|
123
|
+
});
|
|
124
|
+
});
|
|
111
125
|
const agentCmd = new commander_1.Command("agent").description("Manage Polaris agent provider configuration");
|
|
112
126
|
agentCmd.addCommand(new commander_1.Command("setup")
|
|
113
127
|
.description("Configure agent providers per role (librarian, foreman, worker, analyst)")
|