@lsctech/polaris 0.3.9 → 0.3.11

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.
@@ -3,10 +3,10 @@ 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 normalizeRoute(route) {
8
- return route.replace(/^\.\//, "").replace(/\/$/, "");
9
- }
10
10
  function walkForSummaryDirs(dir, repoRoot, results) {
11
11
  let entries;
12
12
  try {
@@ -27,42 +27,157 @@ function walkForSummaryDirs(dir, repoRoot, results) {
27
27
  walkForSummaryDirs((0, node_path_1.join)(dir, entry.name), repoRoot, results);
28
28
  }
29
29
  }
30
- function buildLinkedDocsBlock(entries) {
30
+ function listActiveDoctrineDocs(repoRoot) {
31
+ const activeDir = (0, node_path_1.join)(repoRoot, "smartdocs", "doctrine", "active");
32
+ if (!(0, node_fs_1.existsSync)(activeDir))
33
+ return [];
34
+ const docs = [];
35
+ try {
36
+ const files = (0, node_fs_1.readdirSync)(activeDir, { withFileTypes: true });
37
+ for (const f of files) {
38
+ if (!f.isFile() || !f.name.endsWith(".md"))
39
+ continue;
40
+ const filePath = (0, node_path_1.join)(activeDir, f.name);
41
+ const content = (0, node_fs_1.readFileSync)(filePath, "utf-8");
42
+ const titleMatch = content.match(/^#\s+(.+)/m);
43
+ const title = titleMatch ? titleMatch[1].trim() : f.name.replace(/\.md$/, "");
44
+ docs.push({ path: (0, node_path_1.relative)(repoRoot, filePath), title });
45
+ }
46
+ }
47
+ catch {
48
+ // ignore
49
+ }
50
+ return docs;
51
+ }
52
+ function buildLinkedDocsBlock(docs) {
31
53
  const lines = ["linked_docs:"];
32
- for (const entry of entries) {
33
- const path = entry.doc_path.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
34
- const title = (entry.title ?? "").replace(/\\/g, "\\\\").replace(/"/g, '\\"');
54
+ for (const doc of docs) {
55
+ const path = doc.path.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
56
+ const title = doc.title.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
35
57
  lines.push(` - path: "${path}"`);
36
58
  lines.push(` title: "${title}"`);
37
59
  }
38
60
  return lines;
39
61
  }
40
- function injectLinkedDocs(content, entries) {
62
+ function injectLinkedDocs(content, docs, summaryLines) {
41
63
  const lines = content.split("\n");
42
64
  const headingIdx = lines.findIndex((l) => l.startsWith("#"));
43
65
  if (headingIdx === -1)
44
66
  return content;
45
- const yamlLines = ["", "---", ...buildLinkedDocsBlock(entries), "---"];
67
+ const yamlLines = ["", "---", ...buildLinkedDocsBlock(docs), "---"];
46
68
  const before = lines.slice(0, headingIdx + 1);
47
- const after = lines.slice(headingIdx + 1);
69
+ // If agent provided summary content, append after YAML block
70
+ const after = summaryLines.length > 0
71
+ ? ["", ...summaryLines]
72
+ : lines.slice(headingIdx + 1);
48
73
  return [...before, ...yamlLines, ...after].join("\n");
49
74
  }
75
+ function dispatchCanonAgent(options) {
76
+ const { repoRoot, routeFolder, doctrineDocs, providers, providerOrder } = options;
77
+ const providerName = (0, librarian_dispatch_js_1.resolveLibrarianProvider)(providers, providerOrder);
78
+ if (!providerName)
79
+ return null;
80
+ const cfg = providers[providerName];
81
+ const docList = doctrineDocs
82
+ .map((d, i) => `${i + 1}. [${d.title}] path: ${d.path}`)
83
+ .join("\n");
84
+ const prompt = `You are a Polaris librarian generating context files for an agent work area.
85
+
86
+ Route folder: ${routeFolder}
87
+ Repo root: ${repoRoot}
88
+
89
+ Available doctrine documents:
90
+ ${docList}
91
+
92
+ Generate two outputs for this route area:
93
+
94
+ 1. SUMMARY.md content — a navigation index. Select relevant doctrine docs and write 2-4 lines describing what this area covers.
95
+
96
+ 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.
97
+
98
+ Respond with ONLY valid JSON on a single line:
99
+ {"relevant_docs":[{"path":"<doc path>","title":"<doc title>"}],"summary_lines":["line1","line2"],"polaris_lines":["line1","line2"]}
100
+
101
+ If no doctrine docs are relevant, return an empty array for relevant_docs.`;
102
+ const args = (cfg.args ?? []).map((a) => (a === "{{worker_prompt}}" ? prompt : a));
103
+ const result = (0, node_child_process_1.spawnSync)(cfg.command, args, {
104
+ encoding: "utf-8",
105
+ timeout: 60000,
106
+ cwd: repoRoot,
107
+ });
108
+ const stdout = (result.stdout ?? "").trim();
109
+ const jsonLine = stdout
110
+ .split("\n")
111
+ .reverse()
112
+ .find((l) => l.trim().startsWith("{") && l.includes("relevant_docs"));
113
+ if (!jsonLine)
114
+ return null;
115
+ try {
116
+ return JSON.parse(jsonLine);
117
+ }
118
+ catch {
119
+ return null;
120
+ }
121
+ }
50
122
  async function enrichCanonFiles(repoRoot) {
51
- const indexPath = (0, node_path_1.join)(repoRoot, ".polaris", "map", "index.json");
52
- if (!(0, node_fs_1.existsSync)(indexPath))
53
- return;
54
- const { entries } = JSON.parse((0, node_fs_1.readFileSync)(indexPath, "utf-8"));
123
+ let config = null;
124
+ try {
125
+ config = (0, loader_js_1.loadConfig)(repoRoot);
126
+ }
127
+ catch {
128
+ throw new Error("polaris agent setup required: could not load polaris.config.json.\n" +
129
+ "Run `polaris agent setup` or configure at least a foreman agent.");
130
+ }
131
+ const providers = (config.execution?.providers ?? {});
132
+ const librarianOrder = config.execution?.providerPolicy?.librarian?.providers ?? [];
133
+ const foremanOrder = config.execution?.providerPolicy?.foreman?.providers ?? [];
134
+ // Prefer librarian, fall back to foreman
135
+ const providerOrder = (0, librarian_dispatch_js_1.resolveLibrarianProvider)(providers, librarianOrder) != null
136
+ ? librarianOrder
137
+ : foremanOrder;
138
+ if ((0, librarian_dispatch_js_1.resolveLibrarianProvider)(providers, providerOrder) == null) {
139
+ const configured = [...new Set([...librarianOrder, ...foremanOrder])];
140
+ const msg = configured.length > 0
141
+ ? `Configured agents (${configured.join(", ")}) are not installed on this machine.`
142
+ : "No librarian or foreman agents are configured.";
143
+ throw new Error(`polaris agent setup required: ${msg}\n` +
144
+ "Run `polaris agent setup` to configure an available agent.");
145
+ }
146
+ const doctrineDocs = listActiveDoctrineDocs(repoRoot);
55
147
  const summaryDirs = [];
56
148
  walkForSummaryDirs(repoRoot, repoRoot, summaryDirs);
149
+ let enrichedCount = 0;
57
150
  for (const dir of summaryDirs) {
58
- const route = normalizeRoute((0, node_path_1.relative)(repoRoot, dir));
59
- const matched = entries.filter((e) => normalizeRoute(e.route) === route);
60
- if (matched.length === 0)
61
- continue;
62
151
  const summaryPath = (0, node_path_1.join)(dir, "SUMMARY.md");
63
152
  const content = (0, node_fs_1.readFileSync)(summaryPath, "utf-8");
64
153
  if (!content.includes("<!-- polaris:draft -->"))
65
154
  continue;
66
- (0, node_fs_1.writeFileSync)(summaryPath, injectLinkedDocs(content, matched), "utf-8");
155
+ const routeFolder = (0, node_path_1.relative)(repoRoot, dir);
156
+ console.log(` Dispatching librarian for: ${routeFolder}`);
157
+ const response = dispatchCanonAgent({
158
+ repoRoot,
159
+ routeFolder,
160
+ doctrineDocs,
161
+ providers,
162
+ providerOrder,
163
+ });
164
+ if (!response) {
165
+ console.log(` ⚠ No response for ${routeFolder}, skipping`);
166
+ continue;
167
+ }
168
+ (0, node_fs_1.writeFileSync)(summaryPath, injectLinkedDocs(content, response.relevant_docs, response.summary_lines), "utf-8");
169
+ if ((response.polaris_lines ?? []).length > 0) {
170
+ const polarisPath = (0, node_path_1.join)(dir, "POLARIS.md");
171
+ const polarisContent = [
172
+ `# POLARIS — ${routeFolder}`,
173
+ "",
174
+ ...response.polaris_lines,
175
+ "",
176
+ ].join("\n");
177
+ (0, node_fs_1.writeFileSync)(polarisPath, polarisContent, "utf-8");
178
+ }
179
+ enrichedCount++;
180
+ console.log(` ✓ Enriched ${routeFolder} with ${response.relevant_docs.length} linked docs`);
67
181
  }
182
+ console.log(`\nEnriched ${enrichedCount} SUMMARY.md files.`);
68
183
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lsctech/polaris",
3
- "version": "0.3.9",
3
+ "version": "0.3.11",
4
4
  "description": "Polaris — standalone taskchain orchestration framework for governed AI agent workflows",
5
5
  "bin": {
6
6
  "polaris-cli": "dist/cli/index.js"