@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
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateSetupArtifacts = generateSetupArtifacts;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const adopt_approve_js_1 = require("../adopt-approve.js");
|
|
7
|
+
const adopt_rules_js_1 = require("../adopt-rules.js");
|
|
8
|
+
const adopt_smartdocs_js_1 = require("../adopt-smartdocs.js");
|
|
9
|
+
const adopt_workspace_js_1 = require("../adopt-workspace.js");
|
|
10
|
+
const index_js_1 = require("../../map/index.js");
|
|
11
|
+
const store_js_1 = require("./store.js");
|
|
12
|
+
function asRecord(value) {
|
|
13
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
14
|
+
? value
|
|
15
|
+
: {};
|
|
16
|
+
}
|
|
17
|
+
function buildSetupConfig(existing, record, detectedProviders, detectedRepoAnalysis) {
|
|
18
|
+
const updated = {
|
|
19
|
+
...existing,
|
|
20
|
+
version: typeof existing.version === "string" ? existing.version : "1.0",
|
|
21
|
+
};
|
|
22
|
+
const providers = {};
|
|
23
|
+
const existingProviders = asRecord(existing.providers);
|
|
24
|
+
if (detectedProviders.length > 0) {
|
|
25
|
+
providers.compactionProviders = detectedProviders;
|
|
26
|
+
}
|
|
27
|
+
else if (existingProviders.compactionProviders) {
|
|
28
|
+
providers.compactionProviders = existingProviders.compactionProviders;
|
|
29
|
+
}
|
|
30
|
+
if (detectedRepoAnalysis.length > 0) {
|
|
31
|
+
const existingRepoAnalysis = asRecord(existingProviders.repoAnalysis);
|
|
32
|
+
providers.repoAnalysis = { ...existingRepoAnalysis, preferred: detectedRepoAnalysis[0] };
|
|
33
|
+
}
|
|
34
|
+
else if (existingProviders.repoAnalysis) {
|
|
35
|
+
providers.repoAnalysis = existingProviders.repoAnalysis;
|
|
36
|
+
}
|
|
37
|
+
if (Object.keys(providers).length > 0) {
|
|
38
|
+
updated.providers = { ...existingProviders, ...providers };
|
|
39
|
+
}
|
|
40
|
+
const existingRepo = asRecord(existing.repo);
|
|
41
|
+
updated.repo = {
|
|
42
|
+
...existingRepo,
|
|
43
|
+
sourceRoots: record.answers.source_roots,
|
|
44
|
+
docsRoots: record.answers.canonical_doc_folders,
|
|
45
|
+
};
|
|
46
|
+
const providersByRole = record.answers.providers_by_role;
|
|
47
|
+
if (providersByRole && Object.keys(providersByRole).length > 0) {
|
|
48
|
+
const existingExecution = asRecord(existing.execution);
|
|
49
|
+
const existingExecutionProviders = asRecord(existingExecution.providers);
|
|
50
|
+
const executionProviders = { ...existingExecutionProviders };
|
|
51
|
+
for (const [role, provider] of Object.entries(providersByRole)) {
|
|
52
|
+
executionProviders[role] = { command: provider };
|
|
53
|
+
}
|
|
54
|
+
updated.execution = {
|
|
55
|
+
...existingExecution,
|
|
56
|
+
providers: executionProviders,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return updated;
|
|
60
|
+
}
|
|
61
|
+
function buildSetupInventory(record) {
|
|
62
|
+
const candidates = (record.answers.canonical_doc_folders ?? []).map((folder) => ({
|
|
63
|
+
path: folder,
|
|
64
|
+
kind: "doc",
|
|
65
|
+
suggested_destination: `smartdocs/raw/${folder}`,
|
|
66
|
+
confidence: 0.9,
|
|
67
|
+
has_frontmatter: false,
|
|
68
|
+
estimated_risk: "low",
|
|
69
|
+
}));
|
|
70
|
+
return {
|
|
71
|
+
scan_date: new Date().toISOString(),
|
|
72
|
+
repo_state: "new",
|
|
73
|
+
package_manager: null,
|
|
74
|
+
source_roots: record.answers.source_roots ?? [],
|
|
75
|
+
docs_roots: record.answers.canonical_doc_folders ?? [],
|
|
76
|
+
test_commands: [],
|
|
77
|
+
build_commands: [],
|
|
78
|
+
package_scripts: {},
|
|
79
|
+
generated_roots: [],
|
|
80
|
+
cache_roots: [],
|
|
81
|
+
fixture_roots: [],
|
|
82
|
+
agent_instruction_files: [],
|
|
83
|
+
existing_smartdocs_dirs: [],
|
|
84
|
+
architecture_notes: [record.answers.project_purpose ?? ""].filter(Boolean),
|
|
85
|
+
likely_canonical_folders: record.answers.canonical_doc_folders ?? [],
|
|
86
|
+
smartdocs_candidates: candidates,
|
|
87
|
+
ignore_candidates: record.answers.never_touch ?? [],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function* walkMarkdownFiles(dir, root) {
|
|
91
|
+
for (const entry of (0, node_fs_1.readdirSync)(dir, { withFileTypes: true })) {
|
|
92
|
+
const full = (0, node_path_1.join)(dir, entry.name);
|
|
93
|
+
if (entry.isDirectory()) {
|
|
94
|
+
yield* walkMarkdownFiles(full, root);
|
|
95
|
+
}
|
|
96
|
+
else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
97
|
+
yield (0, node_path_1.relative)(root, full).replace(/\\/g, "/");
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function scanCanonicalDocs(repoRoot, folders) {
|
|
102
|
+
const candidates = [];
|
|
103
|
+
for (const folder of folders) {
|
|
104
|
+
const absFolder = (0, node_path_1.join)(repoRoot, folder);
|
|
105
|
+
if (!(0, node_fs_1.existsSync)(absFolder))
|
|
106
|
+
continue;
|
|
107
|
+
for (const relPath of walkMarkdownFiles(absFolder, repoRoot)) {
|
|
108
|
+
candidates.push({
|
|
109
|
+
path: relPath,
|
|
110
|
+
kind: "doc",
|
|
111
|
+
suggested_destination: `smartdocs/raw/${relPath}`,
|
|
112
|
+
confidence: 0.9,
|
|
113
|
+
has_frontmatter: false,
|
|
114
|
+
estimated_risk: "low",
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return candidates;
|
|
119
|
+
}
|
|
120
|
+
function buildSetupPlan(record, repoRoot, now) {
|
|
121
|
+
const candidates = scanCanonicalDocs(repoRoot, record.answers.canonical_doc_folders ?? []);
|
|
122
|
+
const steps = [];
|
|
123
|
+
let order = 1;
|
|
124
|
+
steps.push({
|
|
125
|
+
step_id: "setup-genesis",
|
|
126
|
+
order: order++,
|
|
127
|
+
phase: "A",
|
|
128
|
+
category: "scaffold",
|
|
129
|
+
action: "create",
|
|
130
|
+
dest_path: "GENESIS.md",
|
|
131
|
+
description: "Write initial project genesis from interview answers.",
|
|
132
|
+
destructive: false,
|
|
133
|
+
requires_approval: false,
|
|
134
|
+
estimated_risk: "low",
|
|
135
|
+
status: "pending",
|
|
136
|
+
});
|
|
137
|
+
steps.push({
|
|
138
|
+
step_id: "setup-config",
|
|
139
|
+
order: order++,
|
|
140
|
+
phase: "A",
|
|
141
|
+
category: "provider-config",
|
|
142
|
+
action: "modify",
|
|
143
|
+
dest_path: "polaris.config.json",
|
|
144
|
+
description: "Write polaris.config.json from interview answers and detected providers.",
|
|
145
|
+
destructive: true,
|
|
146
|
+
requires_approval: false,
|
|
147
|
+
estimated_risk: "low",
|
|
148
|
+
status: "pending",
|
|
149
|
+
});
|
|
150
|
+
steps.push({
|
|
151
|
+
step_id: "setup-rules",
|
|
152
|
+
order: order++,
|
|
153
|
+
phase: "A",
|
|
154
|
+
category: "scaffold",
|
|
155
|
+
action: "create",
|
|
156
|
+
dest_path: "POLARIS_RULES.md",
|
|
157
|
+
description: "Generate Polaris rules from interview answers.",
|
|
158
|
+
destructive: false,
|
|
159
|
+
requires_approval: false,
|
|
160
|
+
estimated_risk: "low",
|
|
161
|
+
status: "pending",
|
|
162
|
+
});
|
|
163
|
+
steps.push({
|
|
164
|
+
step_id: "setup-root-surfaces",
|
|
165
|
+
order: order++,
|
|
166
|
+
phase: "A",
|
|
167
|
+
category: "scaffold",
|
|
168
|
+
action: "create",
|
|
169
|
+
dest_path: "POLARIS.md, SUMMARY.md, CLAUDE.md, AGENTS.md, .github/copilot-instructions.md",
|
|
170
|
+
description: "Create root route surfaces if missing.",
|
|
171
|
+
destructive: false,
|
|
172
|
+
requires_approval: false,
|
|
173
|
+
estimated_risk: "low",
|
|
174
|
+
status: "pending",
|
|
175
|
+
});
|
|
176
|
+
for (const candidate of candidates) {
|
|
177
|
+
steps.push({
|
|
178
|
+
step_id: `setup-smartdocs-migrate-${order.toString().padStart(3, "0")}`,
|
|
179
|
+
order: order++,
|
|
180
|
+
phase: "C",
|
|
181
|
+
category: "smartdocs-migrate",
|
|
182
|
+
action: "move",
|
|
183
|
+
source_path: candidate.path,
|
|
184
|
+
dest_path: candidate.suggested_destination,
|
|
185
|
+
description: `Move ${candidate.path} to ${candidate.suggested_destination}.`,
|
|
186
|
+
destructive: true,
|
|
187
|
+
requires_approval: true,
|
|
188
|
+
estimated_risk: "low",
|
|
189
|
+
status: "pending",
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
steps.push({
|
|
193
|
+
step_id: "setup-atlas-generate",
|
|
194
|
+
order: order++,
|
|
195
|
+
phase: "C",
|
|
196
|
+
category: "atlas-generate",
|
|
197
|
+
action: "modify",
|
|
198
|
+
dest_path: ".polaris/map/index.json",
|
|
199
|
+
description: "Run polaris map index to generate initial route atlas.",
|
|
200
|
+
destructive: true,
|
|
201
|
+
requires_approval: true,
|
|
202
|
+
estimated_risk: "medium",
|
|
203
|
+
status: "pending",
|
|
204
|
+
});
|
|
205
|
+
const filesToCreate = steps.filter((s) => s.action === "create").length;
|
|
206
|
+
const filesToMove = steps.filter((s) => s.action === "move").length;
|
|
207
|
+
const filesToModify = steps.filter((s) => s.action === "modify" || s.action === "append").length;
|
|
208
|
+
return {
|
|
209
|
+
plan_id: `setup-${now.toISOString().replaceAll(":", "-")}`,
|
|
210
|
+
generated_at: now.toISOString(),
|
|
211
|
+
repo_state: "new",
|
|
212
|
+
approved: false,
|
|
213
|
+
approved_at: null,
|
|
214
|
+
dry_run: false,
|
|
215
|
+
steps,
|
|
216
|
+
impact_summary: {
|
|
217
|
+
files_to_create: filesToCreate,
|
|
218
|
+
files_to_move: filesToMove,
|
|
219
|
+
files_to_modify: filesToModify,
|
|
220
|
+
instruction_files_affected: 0,
|
|
221
|
+
smartdocs_candidates_moved: candidates.length,
|
|
222
|
+
cognition_files_to_generate: 0,
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function renderSetupPlanMarkdown(record, plan) {
|
|
227
|
+
const a = record.answers;
|
|
228
|
+
const scaffoldSteps = plan.steps.filter((s) => s.category === "scaffold" && s.action === "create");
|
|
229
|
+
const scaffoldPaths = scaffoldSteps.map((s) => s.dest_path).join(", ");
|
|
230
|
+
const lines = [
|
|
231
|
+
"# Setup Plan",
|
|
232
|
+
"",
|
|
233
|
+
`**Project purpose:** ${a.project_purpose ?? "(not specified)"}`,
|
|
234
|
+
"",
|
|
235
|
+
`**Source roots:** ${(a.source_roots ?? []).join(", ") || "(none)"}`,
|
|
236
|
+
"",
|
|
237
|
+
`**Languages / frameworks:** ${(a.languages ?? []).join(", ") || "(none)"}`,
|
|
238
|
+
"",
|
|
239
|
+
`**Canonical documentation folders:** ${(a.canonical_doc_folders ?? []).join(", ") || "(none)"}`,
|
|
240
|
+
"",
|
|
241
|
+
`**Never touch:** ${(a.never_touch ?? []).join(", ") || "(none)"}`,
|
|
242
|
+
"",
|
|
243
|
+
`**Providers by role:** ${Object.entries(a.providers_by_role ?? {}).map(([k, v]) => `${k}: ${v}`).join(", ") || "(none)"}`,
|
|
244
|
+
"",
|
|
245
|
+
"## Files to generate",
|
|
246
|
+
"",
|
|
247
|
+
`- GENESIS.md`,
|
|
248
|
+
`- polaris.config.json`,
|
|
249
|
+
`- POLARIS_RULES.md`,
|
|
250
|
+
...scaffoldPaths.split(", ").map((p) => `- ${p.trim()}`),
|
|
251
|
+
"",
|
|
252
|
+
"## SmartDocs intake",
|
|
253
|
+
"",
|
|
254
|
+
`Markdown files in canonical documentation folders will be migrated to smartdocs/raw/ (${plan.impact_summary.smartdocs_candidates_moved} candidates).`,
|
|
255
|
+
"",
|
|
256
|
+
`**Operations:** ${plan.steps.length} steps (${plan.impact_summary.files_to_create} create, ${plan.impact_summary.files_to_move} move, ${plan.impact_summary.files_to_modify} modify).`,
|
|
257
|
+
];
|
|
258
|
+
return lines.join("\n");
|
|
259
|
+
}
|
|
260
|
+
function buildGenesisContent(record) {
|
|
261
|
+
const a = record.answers;
|
|
262
|
+
return [
|
|
263
|
+
"# Genesis",
|
|
264
|
+
"",
|
|
265
|
+
"> Initial project genesis generated by Polaris setup.",
|
|
266
|
+
"",
|
|
267
|
+
"## Purpose",
|
|
268
|
+
"",
|
|
269
|
+
a.project_purpose ?? "",
|
|
270
|
+
"",
|
|
271
|
+
"## Source Roots",
|
|
272
|
+
"",
|
|
273
|
+
...(a.source_roots ?? []).length > 0
|
|
274
|
+
? (a.source_roots ?? []).map((root) => `- ${root}`)
|
|
275
|
+
: ["- (none)"],
|
|
276
|
+
"",
|
|
277
|
+
"## Languages / Frameworks",
|
|
278
|
+
"",
|
|
279
|
+
...(a.languages ?? []).length > 0
|
|
280
|
+
? (a.languages ?? []).map((lang) => `- ${lang}`)
|
|
281
|
+
: ["- (none)"],
|
|
282
|
+
"",
|
|
283
|
+
"## Canonical Documentation Folders",
|
|
284
|
+
"",
|
|
285
|
+
...(a.canonical_doc_folders ?? []).length > 0
|
|
286
|
+
? (a.canonical_doc_folders ?? []).map((folder) => `- ${folder}`)
|
|
287
|
+
: ["- (none)"],
|
|
288
|
+
"",
|
|
289
|
+
"## Governance",
|
|
290
|
+
"",
|
|
291
|
+
"- POLARIS_RULES.md — Polaris routing and governance rules",
|
|
292
|
+
"- POLARIS.md — operational guidance",
|
|
293
|
+
"- SUMMARY.md — informational context",
|
|
294
|
+
"",
|
|
295
|
+
].join("\n");
|
|
296
|
+
}
|
|
297
|
+
function writeGenesis(repoRoot, record) {
|
|
298
|
+
const genesisPath = (0, node_path_1.join)(repoRoot, "GENESIS.md");
|
|
299
|
+
if ((0, node_fs_1.existsSync)(genesisPath))
|
|
300
|
+
return;
|
|
301
|
+
(0, node_fs_1.writeFileSync)(genesisPath, buildGenesisContent(record), "utf-8");
|
|
302
|
+
}
|
|
303
|
+
function writeSetupConfig(repoRoot, record, detectedProviders, detectedRepoAnalysis) {
|
|
304
|
+
const configPath = (0, node_path_1.join)(repoRoot, "polaris.config.json");
|
|
305
|
+
let existing = {};
|
|
306
|
+
if ((0, node_fs_1.existsSync)(configPath)) {
|
|
307
|
+
try {
|
|
308
|
+
existing = JSON.parse((0, node_fs_1.readFileSync)(configPath, "utf-8"));
|
|
309
|
+
}
|
|
310
|
+
catch {
|
|
311
|
+
existing = {};
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
const updated = buildSetupConfig(existing, record, detectedProviders, detectedRepoAnalysis);
|
|
315
|
+
(0, node_fs_1.writeFileSync)(configPath, `${JSON.stringify(updated, null, 2)}\n`, "utf-8");
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Generate setup artifacts from a completed interview record.
|
|
319
|
+
*
|
|
320
|
+
* - Builds a preview plan from the interview answers.
|
|
321
|
+
* - In dry-run mode, prints the plan and returns without writing.
|
|
322
|
+
* - With --yes, bypasses the approval prompt.
|
|
323
|
+
* - Otherwise prompts the operator for approval.
|
|
324
|
+
* - On approval, writes GENESIS.md, polaris.config.json, POLARIS_RULES.md,
|
|
325
|
+
* root route surfaces, migrates SmartDocs candidates, and runs the map index.
|
|
326
|
+
*/
|
|
327
|
+
async function generateSetupArtifacts(record, options = {}) {
|
|
328
|
+
const repoRoot = options.repoRoot ?? (0, node_path_1.resolve)(process.cwd());
|
|
329
|
+
const dryRun = options.dryRun ?? false;
|
|
330
|
+
const yes = options.yes ?? false;
|
|
331
|
+
const now = options.now ?? new Date();
|
|
332
|
+
const stdout = options.stdout ?? process.stdout;
|
|
333
|
+
const stdin = options.stdin ?? process.stdin;
|
|
334
|
+
const detectedProviders = options.detectedProviders ?? [];
|
|
335
|
+
const detectedRepoAnalysis = options.detectedRepoAnalysis ?? [];
|
|
336
|
+
const scaffoldFn = options.scaffoldRootSurfaces ?? adopt_workspace_js_1.scaffoldRootSurfaces;
|
|
337
|
+
const rulesFn = options.generatePolarisRules ?? adopt_rules_js_1.generatePolarisRules;
|
|
338
|
+
const migrateFn = options.migrateSmartDocs ?? adopt_smartdocs_js_1.migrateSmartDocs;
|
|
339
|
+
const mapFn = options.runMapIndex ?? index_js_1.runMapIndex;
|
|
340
|
+
const promptFn = options.promptApproval ?? adopt_approve_js_1.promptApproval;
|
|
341
|
+
const plan = buildSetupPlan(record, repoRoot, now);
|
|
342
|
+
const markdown = renderSetupPlanMarkdown(record, plan);
|
|
343
|
+
if (dryRun) {
|
|
344
|
+
stdout.write(`${markdown}\n\n`);
|
|
345
|
+
stdout.write("Setup dry run: no files written.\n");
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
let approved = false;
|
|
349
|
+
if (yes) {
|
|
350
|
+
stdout.write(`${markdown}\n\n`);
|
|
351
|
+
stdout.write("Setup approval bypassed via --yes.\n");
|
|
352
|
+
approved = true;
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
approved = await promptFn(plan, {
|
|
356
|
+
repoRoot,
|
|
357
|
+
stdin,
|
|
358
|
+
stdout,
|
|
359
|
+
now,
|
|
360
|
+
markdown,
|
|
361
|
+
persist: (_plan, root, approvalNow) => {
|
|
362
|
+
const approvedRecord = (0, store_js_1.markApproved)(record, approvalNow);
|
|
363
|
+
(0, store_js_1.saveInterview)(root, approvedRecord);
|
|
364
|
+
},
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
if (!approved) {
|
|
368
|
+
stdout.write("Setup aborted: explicit approval required.\n");
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
writeGenesis(repoRoot, record);
|
|
372
|
+
writeSetupConfig(repoRoot, record, detectedProviders, detectedRepoAnalysis);
|
|
373
|
+
await rulesFn(repoRoot, buildSetupInventory(record), {
|
|
374
|
+
workspaceDir: (0, node_path_1.resolve)(__dirname, "../../workspace"),
|
|
375
|
+
});
|
|
376
|
+
scaffoldFn(repoRoot);
|
|
377
|
+
await migrateFn(plan, repoRoot);
|
|
378
|
+
mapFn(repoRoot, false, false, { seedCognition: false, skipThreshold: true });
|
|
379
|
+
stdout.write("Setup generation complete.\n");
|
|
380
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildCheckpointReport = buildCheckpointReport;
|
|
4
|
+
exports.writeCheckpointReport = writeCheckpointReport;
|
|
5
|
+
exports.printCheckpointReport = printCheckpointReport;
|
|
6
|
+
const node_fs_1 = require("node:fs");
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
const node_child_process_1 = require("node:child_process");
|
|
9
|
+
function getGeneratedFiles(repoRoot) {
|
|
10
|
+
const files = [];
|
|
11
|
+
const candidates = [
|
|
12
|
+
"GENESIS.md",
|
|
13
|
+
"polaris.config.json",
|
|
14
|
+
"POLARIS_RULES.md",
|
|
15
|
+
"POLARIS.md",
|
|
16
|
+
"SUMMARY.md",
|
|
17
|
+
"CLAUDE.md",
|
|
18
|
+
"AGENTS.md",
|
|
19
|
+
".github/copilot-instructions.md",
|
|
20
|
+
".polaris/map/index.json",
|
|
21
|
+
];
|
|
22
|
+
for (const file of candidates) {
|
|
23
|
+
if ((0, node_fs_1.existsSync)((0, node_path_1.join)(repoRoot, file))) {
|
|
24
|
+
files.push(file);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// Check for SmartDocs migrated files
|
|
28
|
+
const smartdocsDir = (0, node_path_1.join)(repoRoot, "smartdocs", "raw");
|
|
29
|
+
if ((0, node_fs_1.existsSync)(smartdocsDir)) {
|
|
30
|
+
try {
|
|
31
|
+
const smartdocsFiles = (0, node_child_process_1.execFileSync)("find", ["smartdocs/raw", "-name", "*.md", "-type", "f"], {
|
|
32
|
+
cwd: repoRoot,
|
|
33
|
+
encoding: "utf-8",
|
|
34
|
+
})
|
|
35
|
+
.trim()
|
|
36
|
+
.split("\n")
|
|
37
|
+
.filter(Boolean);
|
|
38
|
+
files.push(...smartdocsFiles);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// find command failed, ignore
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return files.sort();
|
|
45
|
+
}
|
|
46
|
+
function validateGeneratedFiles(repoRoot, files) {
|
|
47
|
+
const results = {};
|
|
48
|
+
for (const file of files) {
|
|
49
|
+
try {
|
|
50
|
+
const filePath = (0, node_path_1.join)(repoRoot, file);
|
|
51
|
+
const content = (0, node_fs_1.readFileSync)(filePath, "utf-8");
|
|
52
|
+
if (content.trim().length === 0) {
|
|
53
|
+
results[file] = { passed: false, message: "File is empty" };
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
results[file] = { passed: true };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
results[file] = {
|
|
61
|
+
passed: false,
|
|
62
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return results;
|
|
67
|
+
}
|
|
68
|
+
function buildNextSteps(record) {
|
|
69
|
+
const steps = [];
|
|
70
|
+
steps.push("Review generated files in your repository");
|
|
71
|
+
steps.push("Run `polaris status` to verify setup");
|
|
72
|
+
if (record.answers.canonical_doc_folders && record.answers.canonical_doc_folders.length > 0) {
|
|
73
|
+
steps.push("Check migrated documentation in smartdocs/raw/");
|
|
74
|
+
}
|
|
75
|
+
steps.push("Start using Polaris: run `polaris analyze` to explore your codebase");
|
|
76
|
+
return steps;
|
|
77
|
+
}
|
|
78
|
+
function buildCheckpointReport(options) {
|
|
79
|
+
const { repoRoot, record, now } = options;
|
|
80
|
+
const timestamp = (now ?? new Date()).toISOString();
|
|
81
|
+
const createdFiles = getGeneratedFiles(repoRoot);
|
|
82
|
+
const validationResults = validateGeneratedFiles(repoRoot, createdFiles);
|
|
83
|
+
const nextSteps = buildNextSteps(record);
|
|
84
|
+
return {
|
|
85
|
+
timestamp,
|
|
86
|
+
interviewStatus: record.status,
|
|
87
|
+
createdFiles,
|
|
88
|
+
validationResults,
|
|
89
|
+
nextSteps,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function writeCheckpointReport(repoRoot, report) {
|
|
93
|
+
// Safe timestamp: replace ":" with "-" and "." with "-"
|
|
94
|
+
// e.g. "2026-06-09T12:00:00.000Z" → "checkpoint-report-2026-06-09T12-00-00-000Z.json"
|
|
95
|
+
const safeTimestamp = report.timestamp.replace(/:/g, "-").replace(/\./g, "-");
|
|
96
|
+
const fileName = `checkpoint-report-${safeTimestamp}.json`;
|
|
97
|
+
const runsDir = (0, node_path_1.join)(repoRoot, ".polaris", "runs");
|
|
98
|
+
if (!(0, node_fs_1.existsSync)(runsDir)) {
|
|
99
|
+
(0, node_fs_1.mkdirSync)(runsDir, { recursive: true });
|
|
100
|
+
}
|
|
101
|
+
const reportPath = (0, node_path_1.join)(runsDir, fileName);
|
|
102
|
+
(0, node_fs_1.writeFileSync)(reportPath, JSON.stringify(report, null, 2), "utf-8");
|
|
103
|
+
}
|
|
104
|
+
function printCheckpointReport(report) {
|
|
105
|
+
// Print structured summary to stdout
|
|
106
|
+
console.log("Setup Checkpoint Report:");
|
|
107
|
+
console.log(` Timestamp: ${report.timestamp}`);
|
|
108
|
+
console.log(` Interview Status: ${report.interviewStatus}`);
|
|
109
|
+
console.log("");
|
|
110
|
+
console.log("Generated Files:");
|
|
111
|
+
if (report.createdFiles.length === 0) {
|
|
112
|
+
console.log(" (none)");
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
for (const file of report.createdFiles) {
|
|
116
|
+
const validation = report.validationResults[file];
|
|
117
|
+
const status = validation.passed ? "✓" : "✗";
|
|
118
|
+
console.log(` ${status} ${file}`);
|
|
119
|
+
if (validation.message) {
|
|
120
|
+
console.log(` ${validation.message}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
console.log("");
|
|
125
|
+
const failedValidations = Object.entries(report.validationResults).filter(([_, result]) => !result.passed);
|
|
126
|
+
if (failedValidations.length > 0) {
|
|
127
|
+
console.log("Validation Failures:");
|
|
128
|
+
for (const [file, result] of failedValidations) {
|
|
129
|
+
console.log(` ${file}: ${result.message}`);
|
|
130
|
+
}
|
|
131
|
+
console.log("");
|
|
132
|
+
}
|
|
133
|
+
console.log("Next Steps:");
|
|
134
|
+
for (const step of report.nextSteps) {
|
|
135
|
+
console.log(` • ${step}`);
|
|
136
|
+
}
|
|
137
|
+
console.log("");
|
|
138
|
+
}
|
|
@@ -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
|
+
}
|