@lsctech/polaris 0.3.10 → 0.3.12
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/cli/adopt-canon.js +163 -27
- package/package.json +1 -1
package/dist/cli/adopt-canon.js
CHANGED
|
@@ -3,9 +3,35 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.enrichCanonFiles = enrichCanonFiles;
|
|
4
4
|
const node_fs_1 = require("node:fs");
|
|
5
5
|
const node_path_1 = require("node:path");
|
|
6
|
+
const node_child_process_1 = require("node:child_process");
|
|
7
|
+
const loader_js_1 = require("../config/loader.js");
|
|
8
|
+
const librarian_dispatch_js_1 = require("../smartdocs-engine/librarian-dispatch.js");
|
|
6
9
|
const SKIP_DIRS = new Set(["node_modules", ".git", "dist", "build", ".polaris", "smartdocs"]);
|
|
7
|
-
function
|
|
8
|
-
|
|
10
|
+
function loadInventoryCanonicalFolders(repoRoot) {
|
|
11
|
+
const inventoryPath = (0, node_path_1.join)(repoRoot, ".polaris", "adoption-inventory.json");
|
|
12
|
+
if (!(0, node_fs_1.existsSync)(inventoryPath))
|
|
13
|
+
return [];
|
|
14
|
+
try {
|
|
15
|
+
const raw = JSON.parse((0, node_fs_1.readFileSync)(inventoryPath, "utf-8"));
|
|
16
|
+
const folders = raw["likely_canonical_folders"];
|
|
17
|
+
return Array.isArray(folders) ? folders : [];
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function scaffoldDraftSummaryFiles(repoRoot, canonicalFolders) {
|
|
24
|
+
for (const folder of canonicalFolders) {
|
|
25
|
+
const dir = (0, node_path_1.join)(repoRoot, folder);
|
|
26
|
+
if (!(0, node_fs_1.existsSync)(dir))
|
|
27
|
+
continue;
|
|
28
|
+
const summaryPath = (0, node_path_1.join)(dir, "SUMMARY.md");
|
|
29
|
+
if ((0, node_fs_1.existsSync)(summaryPath))
|
|
30
|
+
continue;
|
|
31
|
+
(0, node_fs_1.mkdirSync)(dir, { recursive: true });
|
|
32
|
+
(0, node_fs_1.writeFileSync)(summaryPath, `# ${folder}\n\n<!-- polaris:draft -->\n`, "utf-8");
|
|
33
|
+
console.log(` Scaffolded draft SUMMARY.md: ${folder}`);
|
|
34
|
+
}
|
|
9
35
|
}
|
|
10
36
|
function walkForSummaryDirs(dir, repoRoot, results) {
|
|
11
37
|
let entries;
|
|
@@ -27,51 +53,161 @@ function walkForSummaryDirs(dir, repoRoot, results) {
|
|
|
27
53
|
walkForSummaryDirs((0, node_path_1.join)(dir, entry.name), repoRoot, results);
|
|
28
54
|
}
|
|
29
55
|
}
|
|
30
|
-
function
|
|
56
|
+
function listActiveDoctrineDocs(repoRoot) {
|
|
57
|
+
const activeDir = (0, node_path_1.join)(repoRoot, "smartdocs", "doctrine", "active");
|
|
58
|
+
if (!(0, node_fs_1.existsSync)(activeDir))
|
|
59
|
+
return [];
|
|
60
|
+
const docs = [];
|
|
61
|
+
try {
|
|
62
|
+
const files = (0, node_fs_1.readdirSync)(activeDir, { withFileTypes: true });
|
|
63
|
+
for (const f of files) {
|
|
64
|
+
if (!f.isFile() || !f.name.endsWith(".md"))
|
|
65
|
+
continue;
|
|
66
|
+
const filePath = (0, node_path_1.join)(activeDir, f.name);
|
|
67
|
+
const content = (0, node_fs_1.readFileSync)(filePath, "utf-8");
|
|
68
|
+
const titleMatch = content.match(/^#\s+(.+)/m);
|
|
69
|
+
const title = titleMatch ? titleMatch[1].trim() : f.name.replace(/\.md$/, "");
|
|
70
|
+
docs.push({ path: (0, node_path_1.relative)(repoRoot, filePath), title });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// ignore
|
|
75
|
+
}
|
|
76
|
+
return docs;
|
|
77
|
+
}
|
|
78
|
+
function buildLinkedDocsBlock(docs) {
|
|
31
79
|
const lines = ["linked_docs:"];
|
|
32
|
-
for (const
|
|
33
|
-
const path =
|
|
34
|
-
const title =
|
|
80
|
+
for (const doc of docs) {
|
|
81
|
+
const path = doc.path.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
82
|
+
const title = doc.title.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
35
83
|
lines.push(` - path: "${path}"`);
|
|
36
84
|
lines.push(` title: "${title}"`);
|
|
37
85
|
}
|
|
38
86
|
return lines;
|
|
39
87
|
}
|
|
40
|
-
function injectLinkedDocs(content,
|
|
88
|
+
function injectLinkedDocs(content, docs, summaryLines) {
|
|
41
89
|
const lines = content.split("\n");
|
|
42
90
|
const headingIdx = lines.findIndex((l) => l.startsWith("#"));
|
|
43
91
|
if (headingIdx === -1)
|
|
44
92
|
return content;
|
|
45
|
-
const yamlLines = ["", "---", ...buildLinkedDocsBlock(
|
|
93
|
+
const yamlLines = ["", "---", ...buildLinkedDocsBlock(docs), "---"];
|
|
46
94
|
const before = lines.slice(0, headingIdx + 1);
|
|
47
|
-
|
|
95
|
+
// If agent provided summary content, append after YAML block
|
|
96
|
+
const after = summaryLines.length > 0
|
|
97
|
+
? ["", ...summaryLines]
|
|
98
|
+
: lines.slice(headingIdx + 1);
|
|
48
99
|
return [...before, ...yamlLines, ...after].join("\n");
|
|
49
100
|
}
|
|
101
|
+
function dispatchCanonAgent(options) {
|
|
102
|
+
const { repoRoot, routeFolder, doctrineDocs, providers, providerOrder } = options;
|
|
103
|
+
const providerName = (0, librarian_dispatch_js_1.resolveLibrarianProvider)(providers, providerOrder);
|
|
104
|
+
if (!providerName)
|
|
105
|
+
return null;
|
|
106
|
+
const cfg = providers[providerName];
|
|
107
|
+
const docList = doctrineDocs
|
|
108
|
+
.map((d, i) => `${i + 1}. [${d.title}] path: ${d.path}`)
|
|
109
|
+
.join("\n");
|
|
110
|
+
const prompt = `You are a Polaris librarian generating context files for an agent work area.
|
|
111
|
+
|
|
112
|
+
Route folder: ${routeFolder}
|
|
113
|
+
Repo root: ${repoRoot}
|
|
114
|
+
|
|
115
|
+
Available doctrine documents:
|
|
116
|
+
${docList}
|
|
117
|
+
|
|
118
|
+
Generate two outputs for this route area:
|
|
119
|
+
|
|
120
|
+
1. SUMMARY.md content — a navigation index. Select relevant doctrine docs and write 2-4 lines describing what this area covers.
|
|
121
|
+
|
|
122
|
+
2. POLARIS.md content — operational instructions for agents entering this work area. Write 4-8 lines covering: what this area is responsible for, key patterns/conventions agents should follow, what to avoid, and which doctrine docs to consult for specific concerns. Be concise and directive — agents read this cold before starting work.
|
|
123
|
+
|
|
124
|
+
Respond with ONLY valid JSON on a single line:
|
|
125
|
+
{"relevant_docs":[{"path":"<doc path>","title":"<doc title>"}],"summary_lines":["line1","line2"],"polaris_lines":["line1","line2"]}
|
|
126
|
+
|
|
127
|
+
If no doctrine docs are relevant, return an empty array for relevant_docs.`;
|
|
128
|
+
const args = (cfg.args ?? []).map((a) => (a === "{{worker_prompt}}" ? prompt : a));
|
|
129
|
+
const result = (0, node_child_process_1.spawnSync)(cfg.command, args, {
|
|
130
|
+
encoding: "utf-8",
|
|
131
|
+
timeout: 60000,
|
|
132
|
+
cwd: repoRoot,
|
|
133
|
+
});
|
|
134
|
+
const stdout = (result.stdout ?? "").trim();
|
|
135
|
+
const jsonLine = stdout
|
|
136
|
+
.split("\n")
|
|
137
|
+
.reverse()
|
|
138
|
+
.find((l) => l.trim().startsWith("{") && l.includes("relevant_docs"));
|
|
139
|
+
if (!jsonLine)
|
|
140
|
+
return null;
|
|
141
|
+
try {
|
|
142
|
+
return JSON.parse(jsonLine);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
50
148
|
async function enrichCanonFiles(repoRoot) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
149
|
+
let config = null;
|
|
150
|
+
try {
|
|
151
|
+
config = (0, loader_js_1.loadConfig)(repoRoot);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
throw new Error("polaris agent setup required: could not load polaris.config.json.\n" +
|
|
155
|
+
"Run `polaris agent setup` or configure at least a foreman agent.");
|
|
156
|
+
}
|
|
157
|
+
const providers = (config.execution?.providers ?? {});
|
|
158
|
+
const librarianOrder = config.execution?.providerPolicy?.librarian?.providers ?? [];
|
|
159
|
+
const foremanOrder = config.execution?.providerPolicy?.foreman?.providers ?? [];
|
|
160
|
+
// Prefer librarian, fall back to foreman
|
|
161
|
+
const providerOrder = (0, librarian_dispatch_js_1.resolveLibrarianProvider)(providers, librarianOrder) != null
|
|
162
|
+
? librarianOrder
|
|
163
|
+
: foremanOrder;
|
|
164
|
+
if ((0, librarian_dispatch_js_1.resolveLibrarianProvider)(providers, providerOrder) == null) {
|
|
165
|
+
const configured = [...new Set([...librarianOrder, ...foremanOrder])];
|
|
166
|
+
const msg = configured.length > 0
|
|
167
|
+
? `Configured agents (${configured.join(", ")}) are not installed on this machine.`
|
|
168
|
+
: "No librarian or foreman agents are configured.";
|
|
169
|
+
throw new Error(`polaris agent setup required: ${msg}\n` +
|
|
170
|
+
"Run `polaris agent setup` to configure an available agent.");
|
|
171
|
+
}
|
|
172
|
+
const doctrineDocs = listActiveDoctrineDocs(repoRoot);
|
|
173
|
+
const canonicalFolders = loadInventoryCanonicalFolders(repoRoot);
|
|
174
|
+
if (canonicalFolders.length > 0) {
|
|
175
|
+
scaffoldDraftSummaryFiles(repoRoot, canonicalFolders);
|
|
176
|
+
}
|
|
64
177
|
const summaryDirs = [];
|
|
65
178
|
walkForSummaryDirs(repoRoot, repoRoot, summaryDirs);
|
|
179
|
+
let enrichedCount = 0;
|
|
66
180
|
for (const dir of summaryDirs) {
|
|
67
|
-
const route = normalizeRoute((0, node_path_1.relative)(repoRoot, dir));
|
|
68
|
-
const matched = entries.filter((e) => normalizeRoute(e.route) === route);
|
|
69
|
-
if (matched.length === 0)
|
|
70
|
-
continue;
|
|
71
181
|
const summaryPath = (0, node_path_1.join)(dir, "SUMMARY.md");
|
|
72
182
|
const content = (0, node_fs_1.readFileSync)(summaryPath, "utf-8");
|
|
73
183
|
if (!content.includes("<!-- polaris:draft -->"))
|
|
74
184
|
continue;
|
|
75
|
-
(0,
|
|
185
|
+
const routeFolder = (0, node_path_1.relative)(repoRoot, dir);
|
|
186
|
+
console.log(` Dispatching librarian for: ${routeFolder}`);
|
|
187
|
+
const response = dispatchCanonAgent({
|
|
188
|
+
repoRoot,
|
|
189
|
+
routeFolder,
|
|
190
|
+
doctrineDocs,
|
|
191
|
+
providers,
|
|
192
|
+
providerOrder,
|
|
193
|
+
});
|
|
194
|
+
if (!response) {
|
|
195
|
+
console.log(` ⚠ No response for ${routeFolder}, skipping`);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
(0, node_fs_1.writeFileSync)(summaryPath, injectLinkedDocs(content, response.relevant_docs, response.summary_lines), "utf-8");
|
|
199
|
+
if ((response.polaris_lines ?? []).length > 0) {
|
|
200
|
+
const polarisPath = (0, node_path_1.join)(dir, "POLARIS.md");
|
|
201
|
+
const polarisContent = [
|
|
202
|
+
`# POLARIS — ${routeFolder}`,
|
|
203
|
+
"",
|
|
204
|
+
...response.polaris_lines,
|
|
205
|
+
"",
|
|
206
|
+
].join("\n");
|
|
207
|
+
(0, node_fs_1.writeFileSync)(polarisPath, polarisContent, "utf-8");
|
|
208
|
+
}
|
|
209
|
+
enrichedCount++;
|
|
210
|
+
console.log(` ✓ Enriched ${routeFolder} with ${response.relevant_docs.length} linked docs`);
|
|
76
211
|
}
|
|
212
|
+
console.log(`\nEnriched ${enrichedCount} SUMMARY.md files.`);
|
|
77
213
|
}
|