@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.
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runInterview = runInterview;
4
+ const promises_1 = require("node:readline/promises");
5
+ const store_js_1 = require("./store.js");
6
+ const QUESTION_BANK = [
7
+ {
8
+ key: "project_purpose",
9
+ prompt: "What is the purpose of this project? (one sentence)",
10
+ parse: (raw) => raw.trim(),
11
+ },
12
+ {
13
+ key: "source_roots",
14
+ prompt: "Source root directories (comma-separated, e.g. src,lib):",
15
+ parse: (raw) => splitCsv(raw),
16
+ },
17
+ {
18
+ key: "languages",
19
+ prompt: "Primary languages/frameworks (comma-separated, e.g. typescript,react):",
20
+ parse: (raw) => splitCsv(raw),
21
+ },
22
+ {
23
+ key: "canonical_doc_folders",
24
+ prompt: "Canonical documentation folders (comma-separated, e.g. docs,smartdocs):",
25
+ parse: (raw) => splitCsv(raw),
26
+ },
27
+ {
28
+ key: "never_touch",
29
+ prompt: "Paths agents should never modify (comma-separated, or leave blank):",
30
+ parse: (raw) => splitCsv(raw),
31
+ },
32
+ {
33
+ key: "providers_by_role",
34
+ prompt: "Agent providers by role — format: role:provider,role:provider (or leave blank):",
35
+ parse: parseProvidersByRole,
36
+ },
37
+ ];
38
+ function splitCsv(raw) {
39
+ return raw
40
+ .split(",")
41
+ .map((s) => s.trim())
42
+ .filter(Boolean);
43
+ }
44
+ function parseProvidersByRole(raw) {
45
+ const result = {};
46
+ for (const pair of raw.split(",")) {
47
+ const [role, provider] = pair.split(":").map((s) => s.trim());
48
+ if (role && provider) {
49
+ result[role] = provider;
50
+ }
51
+ }
52
+ return result;
53
+ }
54
+ /**
55
+ * Run the setup interview for an empty/new repo.
56
+ *
57
+ * - Loads or creates the persisted record.
58
+ * - Asks only unanswered questions (resume-safe).
59
+ * - Saves after every answer.
60
+ * - In non-interactive mode without stored answers, throws a clear error.
61
+ *
62
+ * Returns the final record (all questions answered) or throws.
63
+ */
64
+ async function runInterview(repoRoot, opts = {}) {
65
+ const now = opts.now ?? new Date();
66
+ let record = (0, store_js_1.loadOrCreate)(repoRoot, now);
67
+ // Check whether the interview is already complete.
68
+ if ((0, store_js_1.nextUnansweredQuestion)(record) === null) {
69
+ return record;
70
+ }
71
+ // Non-interactive guard: if stdin is not a TTY and no readline override, refuse.
72
+ if (opts.nonInteractive || (!opts.rl && !process.stdin.isTTY)) {
73
+ throw new Error("polaris init: interview requires an interactive terminal.\n" +
74
+ "Provide answers via --resume with a stored interview file, or run interactively.");
75
+ }
76
+ const rl = opts.rl ??
77
+ (() => {
78
+ const iface = (0, promises_1.createInterface)({ input: process.stdin, output: process.stdout });
79
+ return {
80
+ question: (prompt) => iface.question(prompt),
81
+ close: () => iface.close(),
82
+ };
83
+ })();
84
+ try {
85
+ for (const q of QUESTION_BANK) {
86
+ if (record.answers[q.key] !== undefined) {
87
+ // Already answered — skip (resume path).
88
+ continue;
89
+ }
90
+ const raw = await rl.question(`\n${q.prompt}\n> `);
91
+ // Parse and apply — save immediately so a crash loses at most one answer.
92
+ const parsed = q.parse(raw);
93
+ record = {
94
+ ...record,
95
+ answers: { ...record.answers, [q.key]: parsed },
96
+ status: "answered",
97
+ };
98
+ (0, store_js_1.saveInterview)(repoRoot, record);
99
+ }
100
+ }
101
+ finally {
102
+ rl.close();
103
+ }
104
+ return record;
105
+ }
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ /** Typed schema for .polaris/setup/interview.json */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.createInterviewRecord = createInterviewRecord;
5
+ /** Create a fresh interview record (status: in-progress). */
6
+ function createInterviewRecord(now = new Date()) {
7
+ return {
8
+ schema_version: "1.0",
9
+ mode: "init",
10
+ status: "in-progress",
11
+ started_at: now.toISOString(),
12
+ answers: {},
13
+ generation_plan: null,
14
+ approved_at: null,
15
+ };
16
+ }
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadInterview = loadInterview;
4
+ exports.saveInterview = saveInterview;
5
+ exports.loadOrCreate = loadOrCreate;
6
+ exports.applyAnswers = applyAnswers;
7
+ exports.markApproved = markApproved;
8
+ exports.nextUnansweredQuestion = nextUnansweredQuestion;
9
+ const node_fs_1 = require("node:fs");
10
+ const node_path_1 = require("node:path");
11
+ const schema_js_1 = require("./schema.js");
12
+ function interviewPath(repoRoot) {
13
+ return (0, node_path_1.join)(repoRoot, ".polaris", "setup", "interview.json");
14
+ }
15
+ /**
16
+ * Load an existing interview record from disk.
17
+ * Returns null if the file does not exist or cannot be parsed.
18
+ */
19
+ function loadInterview(repoRoot) {
20
+ const path = interviewPath(repoRoot);
21
+ if (!(0, node_fs_1.existsSync)(path))
22
+ return null;
23
+ try {
24
+ return JSON.parse((0, node_fs_1.readFileSync)(path, "utf-8"));
25
+ }
26
+ catch {
27
+ return null;
28
+ }
29
+ }
30
+ /** Persist an interview record to .polaris/setup/interview.json. */
31
+ function saveInterview(repoRoot, record) {
32
+ const path = interviewPath(repoRoot);
33
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(path), { recursive: true });
34
+ (0, node_fs_1.writeFileSync)(path, `${JSON.stringify(record, null, 2)}\n`, "utf-8");
35
+ }
36
+ /**
37
+ * Load an existing record or create a fresh one.
38
+ * Supports --resume: caller passes the return value to continueInterview.
39
+ */
40
+ function loadOrCreate(repoRoot, now = new Date()) {
41
+ return loadInterview(repoRoot) ?? (0, schema_js_1.createInterviewRecord)(now);
42
+ }
43
+ /** Merge new answers into the record and transition status to "answered" if appropriate. */
44
+ function applyAnswers(record, answers) {
45
+ return {
46
+ ...record,
47
+ answers: { ...record.answers, ...answers },
48
+ status: record.status === "approved" ? "approved" : "answered",
49
+ };
50
+ }
51
+ /** Transition record to "approved" status, recording the approval timestamp. */
52
+ function markApproved(record, now = new Date()) {
53
+ return {
54
+ ...record,
55
+ status: "approved",
56
+ approved_at: now.toISOString(),
57
+ };
58
+ }
59
+ /**
60
+ * Return the next unanswered required question key, or null if all are answered.
61
+ * Used to resume at the right question after a partial run.
62
+ */
63
+ function nextUnansweredQuestion(record) {
64
+ const required = [
65
+ "project_purpose",
66
+ "source_roots",
67
+ "languages",
68
+ "canonical_doc_folders",
69
+ "never_touch",
70
+ "providers_by_role",
71
+ ];
72
+ return required.find((key) => record.answers[key] === undefined) ?? null;
73
+ }
@@ -42,7 +42,7 @@ function getBranch(repoRoot) {
42
42
  }
43
43
  }
44
44
  function normalizeBranchName(branch) {
45
- return branch.toLowerCase().replace(/_/g, "-");
45
+ return branch.toLowerCase().replace(/[_/]/g, "-");
46
46
  }
47
47
  function extractClusterSlug(clusterId) {
48
48
  const match = clusterId.match(/([A-Z]+-\d+)/);
@@ -161,13 +161,14 @@ function checkLibrarianGate(repoRoot, clusterId) {
161
161
  const filesCommitted = result.files_committed ?? [];
162
162
  for (const file of filesCommitted) {
163
163
  const absFile = (0, node_path_1.resolve)(repoRoot, file);
164
+ // Allowed takes precedence over prohibited — a specific allowed path wins over a broad prohibition.
165
+ const allowedMatch = allowedWritePaths.find((pattern) => (0, git_custody_js_1.patternMatchesPath)(pattern, absFile));
166
+ if (allowedMatch)
167
+ continue;
164
168
  const prohibitedMatch = prohibitedWritePaths.find((pattern) => (0, git_custody_js_1.patternMatchesPath)(pattern, absFile));
165
169
  if (prohibitedMatch) {
166
170
  return `Librarian wrote to prohibited path: ${file} (matched pattern: ${prohibitedMatch})`;
167
171
  }
168
- const allowedMatch = allowedWritePaths.find((pattern) => (0, git_custody_js_1.patternMatchesPath)(pattern, absFile));
169
- if (allowedMatch)
170
- continue; // explicitly allowed
171
172
  return `Librarian wrote to out-of-scope path: ${file} (not in allowed_write_paths)`;
172
173
  }
173
174
  try {
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ /**
3
+ * Foreman dispatch — launches the assigned Foreman with a setup-bootstrap
4
+ * packet through the configured execution adapter.
5
+ *
6
+ * Provider selection comes from config (`execution.providerPolicy.foreman`);
7
+ * no provider-specific logic lives here — the adapter handles that.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.dispatchForeman = dispatchForeman;
11
+ const terminal_cli_js_1 = require("./terminal-cli.js");
12
+ /**
13
+ * Validate that the setup-bootstrap packet has checkpoint gate enforcement.
14
+ *
15
+ * This is the "by construction" guard: a packet without a valid checkpoint_gate
16
+ * (or with self_approval_prohibited !== true) cannot be dispatched. Because
17
+ * `generateSetupBootstrapPacket` always sets self_approval_prohibited to true,
18
+ * any packet that fails this check was manually constructed — and is rejected.
19
+ *
20
+ * @throws {Error} if checkpoint_gate is absent or self_approval_prohibited is not true.
21
+ */
22
+ function assertCheckpointGateEnforced(packet) {
23
+ if (!packet.checkpoint_gate || packet.checkpoint_gate.self_approval_prohibited !== true) {
24
+ throw new Error(`Cannot dispatch setup-bootstrap packet "${packet.packet_id}": ` +
25
+ `checkpoint_gate.self_approval_prohibited must be true. ` +
26
+ `Use generateSetupBootstrapPacket() to produce a valid packet.`);
27
+ }
28
+ }
29
+ /**
30
+ * Wraps a SetupBootstrapPacket into a BootstrapPacket-compatible shape
31
+ * that existing adapters can dispatch. The setup-bootstrap fields are
32
+ * carried as context so the foreman receives the full packet.
33
+ */
34
+ function toBootstrapPacket(packet) {
35
+ return {
36
+ schema_version: "1.0",
37
+ run_id: `setup-${packet.mode}-${packet.packet_id}`,
38
+ cluster_id: `setup-${packet.mode}`,
39
+ active_child: "",
40
+ state_file: "",
41
+ telemetry_file: "",
42
+ context: {
43
+ setup_bootstrap: packet,
44
+ },
45
+ };
46
+ }
47
+ /**
48
+ * Dispatch the Foreman with the setup-bootstrap packet.
49
+ *
50
+ * Enforces checkpoint gate presence before dispatch — a packet without
51
+ * self_approval_prohibited: true is rejected, making self-approval
52
+ * impossible by construction.
53
+ *
54
+ * Uses the existing TerminalCliAdapter so provider-specific logic stays
55
+ * in provider configs — this function is provider-neutral.
56
+ */
57
+ async function dispatchForeman(input) {
58
+ assertCheckpointGateEnforced(input.packet);
59
+ const adapter = new terminal_cli_js_1.TerminalCliAdapter(input.executionConfig);
60
+ const bootstrapPacket = toBootstrapPacket(input.packet);
61
+ return adapter.dispatch(bootstrapPacket, {
62
+ provider: input.provider,
63
+ dryRun: input.dryRun,
64
+ });
65
+ }
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createAdapter = exports.AgentSubtaskAdapter = exports.TerminalCliAdapter = void 0;
3
+ exports.dispatchForeman = exports.createAdapter = exports.AgentSubtaskAdapter = exports.TerminalCliAdapter = void 0;
4
4
  var terminal_cli_js_1 = require("./terminal-cli.js");
5
5
  Object.defineProperty(exports, "TerminalCliAdapter", { enumerable: true, get: function () { return terminal_cli_js_1.TerminalCliAdapter; } });
6
6
  var agent_subtask_js_1 = require("./agent-subtask.js");
7
7
  Object.defineProperty(exports, "AgentSubtaskAdapter", { enumerable: true, get: function () { return agent_subtask_js_1.AgentSubtaskAdapter; } });
8
8
  var registry_js_1 = require("./registry.js");
9
9
  Object.defineProperty(exports, "createAdapter", { enumerable: true, get: function () { return registry_js_1.createAdapter; } });
10
+ var foreman_dispatch_js_1 = require("./foreman-dispatch.js");
11
+ Object.defineProperty(exports, "dispatchForeman", { enumerable: true, get: function () { return foreman_dispatch_js_1.dispatchForeman; } });
@@ -15,6 +15,11 @@ const body_parser_js_1 = require("./body-parser.js");
15
15
  const loader_js_1 = require("../config/loader.js");
16
16
  const store_js_1 = require("../cluster-state/store.js");
17
17
  const worker_prompt_js_1 = require("./worker-prompt.js");
18
+ function resolveSimplicityMode(state, config) {
19
+ if (state.simplicity_bypass)
20
+ return "off";
21
+ return config?.simplicity?.mode ?? "full";
22
+ }
18
23
  const dispatch_boundary_js_1 = require("./dispatch-boundary.js");
19
24
  const run_bootstrap_js_1 = require("./run-bootstrap.js");
20
25
  const ledger_js_1 = require("./ledger.js");
@@ -659,7 +664,7 @@ function selectChild(state, requestedChild) {
659
664
  * @param resultFile - Optional path for the expected result file; non-absolute paths are resolved against `repoRoot`
660
665
  * @returns The constructed WorkerPacket ready for dispatch
661
666
  */
662
- function buildPacket(state, childId, stateFile, telemetryFile, repoRoot, resultFile) {
667
+ function buildPacket(state, childId, stateFile, telemetryFile, repoRoot, resultFile, config) {
663
668
  const childMeta = state.open_children_meta?.[childId];
664
669
  // Hydrate body from cluster snapshot when runtime state lacks it.
665
670
  const cachedBody = childMeta?.body;
@@ -694,6 +699,7 @@ function buildPacket(state, childId, stateFile, telemetryFile, repoRoot, resultF
694
699
  allowedScope: resolvedScope.length > 0 ? resolvedScope : undefined,
695
700
  maxConcurrentWorkers: 1,
696
701
  promptMode: (0, worker_prompt_js_1.selectPromptMode)(childId, state),
702
+ simplicityMode: resolveSimplicityMode(state, config),
697
703
  resultFile: canonicalPath(absoluteResultFile(repoRoot, resultFile)),
698
704
  });
699
705
  }
@@ -912,7 +918,7 @@ function runLoopDispatch(options) {
912
918
  step_cursor: "dispatch",
913
919
  dispatch_boundary: (0, dispatch_boundary_js_1.advanceDispatchEpoch)(state.dispatch_boundary, childId),
914
920
  };
915
- const packet = buildPacket(preliminaryState, childId, options.stateFile, telemetryFile, options.repoRoot, canonicalResultFile);
921
+ const packet = buildPacket(preliminaryState, childId, options.stateFile, telemetryFile, options.repoRoot, canonicalResultFile, loadedConfig);
916
922
  const packetFailure = detectPacketGenerationFailure(packet);
917
923
  if (packetFailure) {
918
924
  appendTelemetry(telemetryFile, {
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runSimplicityCommand = runSimplicityCommand;
4
+ const checkpoint_js_1 = require("./checkpoint.js");
5
+ /**
6
+ * Handles `polaris simplicity [--bypass | --restore]`.
7
+ *
8
+ * --bypass: sets simplicity_bypass: true for the active run (omits discipline ladder from worker prompts).
9
+ * --restore: clears simplicity_bypass (re-enables the ladder).
10
+ * bare: prints current bypass status without mutating state.
11
+ */
12
+ function runSimplicityCommand(opts) {
13
+ if (!opts.bypass && !opts.restore) {
14
+ const state = (0, checkpoint_js_1.readState)(opts.stateFile);
15
+ if (state.simplicity_bypass) {
16
+ console.log("Simplicity bypass: on — discipline ladder omitted for this run.");
17
+ }
18
+ else {
19
+ console.log("Simplicity bypass: off — discipline mode comes from project config/default.");
20
+ }
21
+ return;
22
+ }
23
+ const state = (0, checkpoint_js_1.readState)(opts.stateFile);
24
+ if (opts.bypass) {
25
+ state.simplicity_bypass = true;
26
+ (0, checkpoint_js_1.writeStateAtomic)(opts.stateFile, state);
27
+ console.log("Simplicity bypass enabled — discipline ladder will be omitted from worker prompts for this run.");
28
+ return;
29
+ }
30
+ // restore
31
+ state.simplicity_bypass = false;
32
+ (0, checkpoint_js_1.writeStateAtomic)(opts.stateFile, state);
33
+ console.log("Simplicity bypass cleared — discipline ladder will be injected into worker prompts.");
34
+ }
@@ -250,7 +250,7 @@ function compileImplPacket(input) {
250
250
  `Create exactly ONE git commit: [${input.childId}] ${childTitle}.`,
251
251
  `Do NOT modify open_children or completed_children in ${input.stateFile}. The parent runtime (loop continue) owns that state transition.`,
252
252
  `Append a telemetry event to ${input.telemetryFile}.`,
253
- `Write compact return JSON to stdout (fields: ${exports.IMPL_RETURN_CONTRACT.join(', ')}).`,
253
+ `Write compact return JSON to stdout (fields: ${exports.IMPL_RETURN_CONTRACT.join(', ')}). next_recommended_action MUST be exactly "continue" on success, "stop" on unresolvable blocker, or "investigate" if manual review is needed.`,
254
254
  `TERMINATE SESSION IMMEDIATELY. Do not select or execute the next child.`,
255
255
  ];
256
256
  // Build compact or full prompt; primary_goal uses the structured template.
@@ -265,6 +265,7 @@ function compileImplPacket(input) {
265
265
  allowedScope: resolvedScope,
266
266
  validationCommands: resolvedValidation,
267
267
  mode: promptMode,
268
+ simplicityMode: input.simplicityMode,
268
269
  });
269
270
  return {
270
271
  schema_version: '2.1',
@@ -57,6 +57,29 @@ function selectPromptMode(childId, state, override) {
57
57
  return override;
58
58
  return isNarrowChild(childId, state) ? 'compact' : 'full';
59
59
  }
60
+ // ── Discipline section ────────────────────────────────────────────────────────
61
+ function buildDisciplineSection(mode) {
62
+ if (mode === "off")
63
+ return [];
64
+ const lines = [
65
+ "## Implementation Discipline",
66
+ "Before writing code, stop at the first rung that holds:",
67
+ "",
68
+ "1. Does this need to exist? → no: skip it (YAGNI)",
69
+ "2. Stdlib does it? → use it",
70
+ "3. Native platform feature? → use it",
71
+ "4. Installed dependency? → use it",
72
+ "5. One line? → one line",
73
+ "6. Only then: the minimum that works",
74
+ "",
75
+ "Lazy, not negligent: validation, error handling, security, and accessibility are never on the chopping block.",
76
+ ];
77
+ if (mode === "full") {
78
+ lines.push("", "When you defer a simplification for later, mark it inline:", " // ponytail: <what you deferred and why>");
79
+ }
80
+ lines.push("");
81
+ return lines;
82
+ }
60
83
  // ── Builder ───────────────────────────────────────────────────────────────────
61
84
  /**
62
85
  * Build a compact or full worker dispatch prompt.
@@ -80,6 +103,10 @@ function buildWorkerPrompt(input) {
80
103
  lines.push('## Goal');
81
104
  lines.push(input.goal.trim());
82
105
  lines.push('');
106
+ // ── Implementation Discipline ─────────────────────────────────────────────
107
+ for (const line of buildDisciplineSection(input.simplicityMode ?? 'full')) {
108
+ lines.push(line);
109
+ }
83
110
  // ── Scope ─────────────────────────────────────────────────────────────────
84
111
  lines.push('## Scope');
85
112
  if (input.scopeTouch.length > 0) {
@@ -256,5 +283,6 @@ function buildPromptFromPacketInput(input) {
256
283
  telemetryFile: input.telemetryFile,
257
284
  issueContext: input.issueContext,
258
285
  mode: input.mode,
286
+ simplicityMode: input.simplicityMode,
259
287
  });
260
288
  }
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SUPPORTED_SKILLS = exports.SKILL_ROLE_MAP = void 0;
4
+ exports.generateSetupBootstrapPacket = generateSetupBootstrapPacket;
4
5
  exports.generateSkillPacket = generateSkillPacket;
5
6
  const node_crypto_1 = require("node:crypto");
6
7
  exports.SKILL_ROLE_MAP = {
@@ -10,6 +11,8 @@ exports.SKILL_ROLE_MAP = {
10
11
  promote: "Librarian",
11
12
  triage: "Librarian",
12
13
  review: "Librarian",
14
+ catalog: "Librarian",
15
+ reconcile: "Librarian",
13
16
  };
14
17
  const ROLE_SUMMARIES = {
15
18
  Analyst: "The Analyst gathers evidence, assesses feasibility, and produces implementation-ready plans. The Analyst shapes work but never executes it.",
@@ -258,6 +261,136 @@ function buildReviewPacket() {
258
261
  ],
259
262
  };
260
263
  }
264
+ const SETUP_BOOTSTRAP_CHECKPOINTS = [
265
+ "canon",
266
+ "doc-movement",
267
+ "instruction-files",
268
+ "graph-root",
269
+ "route-scaffold",
270
+ "source-mutation",
271
+ ];
272
+ /**
273
+ * Builds the checkpoint gate that is embedded in every setup-bootstrap packet.
274
+ *
275
+ * Each checkpoint gate entry is a halt instruction — the Foreman MUST stop
276
+ * and surface the gate to the operator before proceeding. Self-approval is
277
+ * structurally impossible: `self_approval_prohibited` is typed as `true` and
278
+ * this function never sets it to anything else.
279
+ */
280
+ function buildCheckpointGate() {
281
+ const gateInstruction = (name) => `HALT at checkpoint "${name}": stop, surface this gate to the operator, and wait for explicit approval before proceeding. You may not self-approve.`;
282
+ return {
283
+ gates: {
284
+ canon: gateInstruction("canon"),
285
+ "doc-movement": gateInstruction("doc-movement"),
286
+ "instruction-files": gateInstruction("instruction-files"),
287
+ "graph-root": gateInstruction("graph-root"),
288
+ "route-scaffold": gateInstruction("route-scaffold"),
289
+ "source-mutation": gateInstruction("source-mutation"),
290
+ },
291
+ self_approval_prohibited: true,
292
+ enforcement_note: "Checkpoint gates are non-optional. The Foreman must halt and surface each gate to the operator. " +
293
+ "Proceeding without explicit operator approval is a protocol violation.",
294
+ };
295
+ }
296
+ function generateSetupBootstrapPacket(mode) {
297
+ return {
298
+ packet_id: (0, node_crypto_1.randomUUID)(),
299
+ packet_kind: "setup-bootstrap",
300
+ active_role: "Foreman",
301
+ role_file: ".polaris/skills/polaris-run/SKILL.md",
302
+ mode,
303
+ authority_boundaries: [
304
+ "Read repository structure and existing configuration files",
305
+ "Create or update Polaris configuration and scaffold files",
306
+ "Dispatch Workers for bounded setup sub-tasks",
307
+ "Coordinate approval checkpoints before advancing to the next setup phase",
308
+ "Write to .polaris/ directories as part of init or adopt setup",
309
+ "Update POLARIS.md and SUMMARY.md within the project root",
310
+ ],
311
+ prohibited_actions: [
312
+ "Mutate source files without an approved checkpoint (unapproved mutation is forbidden)",
313
+ "Implement features directly — all implementation must be delegated to a Worker",
314
+ "Advance past an approval checkpoint without explicit user confirmation",
315
+ "Self-approve any checkpoint — operator approval is mandatory and cannot be delegated to the Foreman",
316
+ "Modify tracker state or issue descriptions during setup",
317
+ ],
318
+ approval_checkpoints: SETUP_BOOTSTRAP_CHECKPOINTS,
319
+ checkpoint_gate: buildCheckpointGate(),
320
+ stop_conditions: [
321
+ "All setup phases complete and scaffold is valid",
322
+ "An approval checkpoint is reached — pause and await user confirmation",
323
+ "A required configuration value is missing and cannot be inferred",
324
+ "A Worker returns a failure result during setup",
325
+ ],
326
+ generated_at: new Date().toISOString(),
327
+ };
328
+ }
329
+ function buildCatalogPacket() {
330
+ return {
331
+ authority_boundaries: [
332
+ "Read packet-scoped POLARIS.md and SUMMARY.md files",
333
+ "Update cognition files only within packet-allowed write paths",
334
+ "Read smartdocs/raw/ and classify documents through supported Polaris CLI commands",
335
+ "Auto-place only high-confidence documents",
336
+ "Leave low-confidence documents in raw when unattended or request operator direction",
337
+ ],
338
+ prohibited_actions: [
339
+ "Modify implementation source code, tests, or configuration",
340
+ "Write outside packet-allowed paths",
341
+ "Auto-place low-confidence documents",
342
+ "Move or copy SmartDocs directly instead of using supported CLI commands",
343
+ "Call polaris loop continue or polaris finalize",
344
+ "Git push or create a pull request",
345
+ ],
346
+ allowed_outputs: [
347
+ "Updated POLARIS.md and SUMMARY.md files in packet-allowed paths",
348
+ "Classified documents placed through supported Polaris CLI commands",
349
+ "A sealed local cognition and document commit",
350
+ "A report of deferred low-confidence documents",
351
+ ],
352
+ deliverables: [
353
+ "Packet-scoped cognition reconciled",
354
+ "Raw documents classified or explicitly deferred",
355
+ "All changes recorded in one sealed local commit",
356
+ ],
357
+ stop_conditions: [
358
+ "All packet-scoped cognition and raw documents processed",
359
+ "A required command is unsupported by the installed CLI",
360
+ "A requested write falls outside packet-allowed paths",
361
+ "An unresolved conflict requires operator direction",
362
+ ],
363
+ };
364
+ }
365
+ function buildReconcilePacket() {
366
+ return {
367
+ authority_boundaries: [
368
+ "Read packet-scoped folders and their POLARIS.md and SUMMARY.md files",
369
+ "Update cognition files only within packet-allowed write paths",
370
+ "Create one sealed local cognition commit",
371
+ ],
372
+ prohibited_actions: [
373
+ "Modify implementation source code, tests, or configuration",
374
+ "Move, ingest, classify, or promote documents",
375
+ "Write outside packet-allowed paths",
376
+ "Call polaris loop continue or polaris finalize",
377
+ "Git push or create a pull request",
378
+ ],
379
+ allowed_outputs: [
380
+ "Updated POLARIS.md and SUMMARY.md files in packet-allowed paths",
381
+ "A sealed local cognition commit",
382
+ ],
383
+ deliverables: [
384
+ "Packet-scoped cognition reconciled with completed work",
385
+ "All cognition changes recorded in one sealed local commit",
386
+ ],
387
+ stop_conditions: [
388
+ "All packet-scoped cognition reconciled",
389
+ "A requested write falls outside packet-allowed paths",
390
+ "Work evidence is missing or contradictory",
391
+ ],
392
+ };
393
+ }
261
394
  function generateSkillPacket(skillName, config) {
262
395
  const active_role = exports.SKILL_ROLE_MAP[skillName];
263
396
  const role_summary = ROLE_SUMMARIES[active_role];
@@ -288,6 +421,12 @@ function generateSkillPacket(skillName, config) {
288
421
  case "review":
289
422
  body = buildReviewPacket();
290
423
  break;
424
+ case "catalog":
425
+ body = buildCatalogPacket();
426
+ break;
427
+ case "reconcile":
428
+ body = buildReconcilePacket();
429
+ break;
291
430
  }
292
431
  return {
293
432
  packet_id,
@@ -299,4 +438,13 @@ function generateSkillPacket(skillName, config) {
299
438
  generated_at,
300
439
  };
301
440
  }
302
- exports.SUPPORTED_SKILLS = ["analyze", "run", "ingest", "promote", "triage", "review"];
441
+ exports.SUPPORTED_SKILLS = [
442
+ "analyze",
443
+ "run",
444
+ "ingest",
445
+ "promote",
446
+ "triage",
447
+ "review",
448
+ "catalog",
449
+ "reconcile",
450
+ ];