@lsctech/polaris 0.3.2 → 0.3.4

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.
@@ -52,14 +52,15 @@ function groupAndSort(packets) {
52
52
  * Write _review-queue.json (canonical) and _review-queue.md (display-only) to outputDir.
53
53
  * Markdown is regenerated from JSON — never parse markdown to recover decisions.
54
54
  */
55
- function writeReviewQueue(packets, runId, outputDir) {
55
+ function writeReviewQueue(packets, runId, outputDir, filename = "_review-queue.json") {
56
56
  (0, node_fs_1.mkdirSync)(outputDir, { recursive: true });
57
57
  const queueFile = {
58
58
  generated_at: new Date().toISOString(),
59
59
  run_id: runId,
60
60
  packets,
61
61
  };
62
- (0, node_fs_1.writeFileSync)((0, node_path_1.join)(outputDir, "_review-queue.json"), JSON.stringify(queueFile, null, 2) + "\n", "utf-8");
62
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(outputDir, filename), JSON.stringify(queueFile, null, 2) + "\n", "utf-8");
63
+ const mdFilename = filename.replace(/\.json$/, ".md");
63
64
  const sorted = groupAndSort(packets);
64
65
  const sections = sorted.map(renderPacketMarkdown).join("\n\n---\n\n");
65
66
  const md = [
@@ -69,21 +70,21 @@ function writeReviewQueue(packets, runId, outputDir) {
69
70
  `**Generated:** ${queueFile.generated_at}`,
70
71
  `**Pending review:** ${packets.length} document(s)`,
71
72
  ``,
72
- `> Markdown is display-only. Edit \`_review-queue.json\` to set \`reviewDecision\` fields.`,
73
+ `> Markdown is display-only. Edit \`${filename}\` to set \`reviewDecision\` fields.`,
73
74
  `> Rerun \`polaris docs ingest\` to apply decisions.`,
74
75
  ``,
75
76
  `---`,
76
77
  ``,
77
78
  sections,
78
79
  ].join("\n");
79
- (0, node_fs_1.writeFileSync)((0, node_path_1.join)(outputDir, "_review-queue.md"), md, "utf-8");
80
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(outputDir, mdFilename), md, "utf-8");
80
81
  }
81
82
  /**
82
83
  * Read review queue from JSON. Returns empty array if no queue file exists.
83
84
  * Never reads markdown.
84
85
  */
85
- function readReviewQueue(outputDir) {
86
- const jsonPath = (0, node_path_1.join)(outputDir, "_review-queue.json");
86
+ function readReviewQueue(outputDir, filename = "_review-queue.json") {
87
+ const jsonPath = (0, node_path_1.join)(outputDir, filename);
87
88
  if (!(0, node_fs_1.existsSync)(jsonPath))
88
89
  return [];
89
90
  try {
@@ -9,6 +9,7 @@ exports.SKILL_ROLE_MAP = {
9
9
  ingest: "Librarian",
10
10
  promote: "Librarian",
11
11
  triage: "Librarian",
12
+ review: "Librarian",
12
13
  };
13
14
  const ROLE_SUMMARIES = {
14
15
  Analyst: "The Analyst gathers evidence, assesses feasibility, and produces implementation-ready plans. The Analyst shapes work but never executes it.",
@@ -221,6 +222,41 @@ function buildTriagePacket() {
221
222
  ],
222
223
  };
223
224
  }
225
+ function buildReviewPacket() {
226
+ return {
227
+ authority_boundaries: [
228
+ "Read smartdocs/raw/_triage-queue.json to load pending review decisions",
229
+ "Read candidate documents from smartdocs/doctrine/candidate/ to inform decisions",
230
+ "Evaluate each flagged packet: read the doc, consider the flag type and stale symbols, decide approve/reject/defer",
231
+ "Write decisions back to the triage queue by calling polaris docs review --write-decision <sourcePath> <decision>",
232
+ "Call polaris docs ingest to apply approved/rejected decisions after review is complete",
233
+ "Emit telemetry events",
234
+ ],
235
+ prohibited_actions: [
236
+ "Move, promote, or delete documents directly — decisions must go through polaris docs ingest",
237
+ "Auto-approve every packet without reading the document content",
238
+ "Mutate source files (src/, tests, config)",
239
+ "Call polaris loop continue or polaris finalize",
240
+ "Suppress or skip flagged packets without recording a decision",
241
+ ],
242
+ allowed_outputs: [
243
+ "Review decisions written to smartdocs/raw/_triage-queue.json",
244
+ "Promoted documents (via polaris docs ingest for approved packets)",
245
+ "Deprecated documents (via polaris docs ingest for rejected packets)",
246
+ "Telemetry events",
247
+ ],
248
+ deliverables: [
249
+ "All pending packets in _triage-queue.json reviewed with approve/reject/defer decision",
250
+ "Approved and rejected packets processed by polaris docs ingest",
251
+ "Summary of decisions reported to user",
252
+ ],
253
+ stop_conditions: [
254
+ "All packets reviewed",
255
+ "Ambiguous packet that requires explicit user input",
256
+ "Ingest error on apply — report and wait for instruction",
257
+ ],
258
+ };
259
+ }
224
260
  function generateSkillPacket(skillName, config) {
225
261
  const active_role = exports.SKILL_ROLE_MAP[skillName];
226
262
  const role_summary = ROLE_SUMMARIES[active_role];
@@ -248,6 +284,9 @@ function generateSkillPacket(skillName, config) {
248
284
  case "triage":
249
285
  body = buildTriagePacket();
250
286
  break;
287
+ case "review":
288
+ body = buildReviewPacket();
289
+ break;
251
290
  }
252
291
  return {
253
292
  packet_id,
@@ -259,4 +298,4 @@ function generateSkillPacket(skillName, config) {
259
298
  generated_at,
260
299
  };
261
300
  }
262
- exports.SUPPORTED_SKILLS = ["analyze", "run", "ingest", "promote", "triage"];
301
+ exports.SUPPORTED_SKILLS = ["analyze", "run", "ingest", "promote", "triage", "review"];
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.agenticDecideKey = agenticDecideKey;
4
+ const node_fs_1 = require("node:fs");
5
+ async function agenticDecideKey(packet) {
6
+ let docContent = "";
7
+ try {
8
+ if ((0, node_fs_1.existsSync)(packet.sourcePath)) {
9
+ docContent = (0, node_fs_1.readFileSync)(packet.sourcePath, "utf-8").slice(0, 3000);
10
+ }
11
+ }
12
+ catch { /* ignore */ }
13
+ // @ts-ignore: @anthropic-ai/sdk may not be installed as a dependency
14
+ const mod = await import("@anthropic-ai/sdk");
15
+ const Anthropic = mod.default ?? mod.Anthropic;
16
+ const client = new Anthropic();
17
+ const staleSymbols = packet.staleSymbols;
18
+ const triageFlag = packet.triageFlag;
19
+ const prompt = `You are reviewing a candidate documentation file flagged during Polaris docs triage.
20
+
21
+ Flag type: ${String(triageFlag ?? "unknown")}
22
+ Authority risk: ${packet.authorityRisk}
23
+ Recommendation from triage: ${packet.recommendation}
24
+ Reasoning: ${packet.reasoning.join("; ")}
25
+ ${Array.isArray(staleSymbols) ? `Stale symbols (not found in codebase graph): ${staleSymbols.join(", ")}` : ""}
26
+
27
+ Document content:
28
+ ---
29
+ ${docContent}
30
+ ---
31
+
32
+ Decide whether this document should be:
33
+ - approved: doc is valid doctrine and should be promoted to active
34
+ - rejected: doc is outdated, incorrect, or superseded and should be deprecated
35
+ - deferred: uncertain — needs human review
36
+
37
+ Respond with exactly one word: approve, reject, or defer.`;
38
+ const response = await client.messages.create({
39
+ model: "claude-haiku-4-5-20251001",
40
+ max_tokens: 10,
41
+ messages: [{ role: "user", content: prompt }],
42
+ });
43
+ const text = (response.content[0].text ?? "defer").trim().toLowerCase();
44
+ if (text.startsWith("approve"))
45
+ return "a";
46
+ if (text.startsWith("reject"))
47
+ return "r";
48
+ return "d";
49
+ }
@@ -142,12 +142,24 @@ function createDocsCommand(options = {}) {
142
142
  .description("Interactively review pending governance decisions in the review queue")
143
143
  .option("--queue <path>", "path to _review-queue.json (default: smartdocs/raw/_review-queue.json)")
144
144
  .option("-r, --repo-root <path>", "Repository root", defaultRepoRoot)
145
+ .option("--agentic", "use LLM agent to make review decisions automatically")
146
+ .option("--triage", "review the triage queue (_triage-queue.json) instead of the review queue")
145
147
  .action(async (opts) => {
148
+ const repoRoot = opts.repoRoot;
149
+ const queueFilename = opts.triage ? "_triage-queue.json" : "_review-queue.json";
150
+ const queueDir = opts.queue
151
+ ? (0, node_path_1.resolve)(opts.repoRoot, opts.queue)
152
+ .replace(/_review-queue\.json$/, "")
153
+ .replace(/_triage-queue\.json$/, "")
154
+ .replace(/\/$/, "")
155
+ : (0, node_path_1.resolve)(repoRoot, "smartdocs", "raw");
156
+ let readKey;
157
+ if (opts.agentic) {
158
+ const { agenticDecideKey } = await import("./agentic-review.js");
159
+ readKey = agenticDecideKey;
160
+ }
146
161
  try {
147
- const queueDir = opts.queue
148
- ? (0, node_path_1.resolve)(opts.repoRoot, opts.queue).replace(/_review-queue\.json$/, "").replace(/\/$/, "")
149
- : (0, node_path_1.resolve)(opts.repoRoot, "smartdocs", "raw");
150
- await (0, review_js_1.runReviewSession)({ repoRoot: opts.repoRoot, queueDir });
162
+ await (0, review_js_1.runReviewSession)({ repoRoot, queueDir, queueFilename, readKey });
151
163
  }
152
164
  catch (err) {
153
165
  console.error(`polaris docs review: ${err instanceof Error ? err.message : String(err)}`);
@@ -84,7 +84,8 @@ function readSingleKey() {
84
84
  async function runReviewSession(options) {
85
85
  const { repoRoot, readKey, getReviewedBy = defaultGetReviewedBy, output = (msg) => process.stdout.write(msg + "\n"), } = options;
86
86
  const queueDir = options.queueDir ?? (0, node_path_1.resolve)(repoRoot, "smartdocs", "raw");
87
- const packets = (0, index_js_1.readReviewQueue)(queueDir);
87
+ const queueFilename = options.queueFilename;
88
+ const packets = (0, index_js_1.readReviewQueue)(queueDir, queueFilename);
88
89
  if (packets.length === 0) {
89
90
  output("No review queue found. Run polaris docs ingest first.");
90
91
  return;
@@ -98,7 +99,7 @@ async function runReviewSession(options) {
98
99
  for (let i = 0; i < undecided.length; i++) {
99
100
  const packet = undecided[i];
100
101
  output(formatPacketCard(packet, i + 1, undecided.length));
101
- const key = readKey ? await readKey() : await readSingleKey();
102
+ const key = readKey ? await readKey(packet) : await readSingleKey();
102
103
  if (key === "q") {
103
104
  const remaining = undecided.length - decided;
104
105
  output(`\nSession ended. ${decided} decision(s) saved, ${remaining} packet(s) still pending.`);
@@ -126,7 +127,7 @@ async function runReviewSession(options) {
126
127
  reviewedBy: getReviewedBy(),
127
128
  };
128
129
  }
129
- (0, index_js_1.writeReviewQueue)(packets, "review-session", queueDir);
130
+ (0, index_js_1.writeReviewQueue)(packets, "review-session", queueDir, queueFilename);
130
131
  decided++;
131
132
  }
132
133
  const approved = packets.filter((p) => p.reviewDecision === "approve").length;
@@ -35,6 +35,8 @@ message whose primary instruction is to invoke a named Polaris skill.
35
35
  | `run docs-ingest` | docs-ingest | `.polaris/skills/docs-ingest/` |
36
36
  | `docs-triage` | docs-triage | `.polaris/skills/docs-triage/` |
37
37
  | `run docs-triage` | docs-triage | `.polaris/skills/docs-triage/` |
38
+ | `docs-review` | docs-review | `.polaris/skills/docs-review/` |
39
+ | `run docs-review` | docs-review | `.polaris/skills/docs-review/` |
38
40
  | `polaris-reconcile <CLUSTER-ID>` | polaris-reconcile | `.polaris/skills/polaris-reconcile/` |
39
41
  | `run polaris-reconcile on [issue] <CLUSTER-ID>` | polaris-reconcile | `.polaris/skills/polaris-reconcile/` |
40
42
  | `polaris-catalog <CLUSTER-ID>` | polaris-catalog | `.polaris/skills/polaris-catalog/` |
@@ -0,0 +1,58 @@
1
+ ---
2
+ name: docs-review
3
+ description: Agentically review the triage queue — read each flagged candidate doc, evaluate the flag, and record approve/reject/defer decisions. Applies approved and rejected decisions via polaris docs ingest.
4
+ role: librarian
5
+ role_file: .polaris/roles/librarian.md
6
+ ---
7
+
8
+ ## Polaris Skill Bootloader
9
+
10
+ **Before proceeding, you must obtain a skill packet from the Polaris runtime.**
11
+
12
+ Run the following command:
13
+
14
+ ```
15
+ npm run polaris -- skill packet review
16
+ ```
17
+
18
+ - Do not begin work until a packet is returned.
19
+ - Treat the packet as your authoritative instruction source.
20
+ - The packet defines your active role, authority boundaries, prohibited actions, deliverables, and stop conditions.
21
+ - If no packet is produced, stop and report: **Polaris could not authorize this run.**
22
+
23
+ ---
24
+
25
+ # docs-review
26
+
27
+ Use this skill to agentically walk through the triage queue and make approve/reject/defer decisions on each flagged candidate document. You are the reviewer — read each doc, evaluate the flag, and decide.
28
+
29
+ ## When to use
30
+
31
+ - "Run docs review"
32
+ - "Review the triage queue"
33
+ - "Process the flagged candidates"
34
+ - "docs-review"
35
+
36
+ ## How to execute
37
+
38
+ 1. Read `chain.md` — step order, traversal rules, output contract.
39
+ 2. Execute steps in the order `chain.md` defines. Do not skip steps.
40
+ 3. You make all decisions by reading the flagged doc content and the flag metadata.
41
+
42
+ ## Hard rules — what docs-review may do
43
+
44
+ - Read `smartdocs/raw/_triage-queue.json` to load pending items
45
+ - Read candidate documents from `smartdocs/doctrine/candidate/`
46
+ - Record decisions (approve/reject/defer) back to `_triage-queue.json`
47
+ - Call `polaris docs ingest` to apply approved/rejected decisions
48
+ - Emit telemetry events
49
+
50
+ ## Hard rules — what docs-review must NOT do
51
+
52
+ - Move, rename, or delete documents directly — ingest handles that
53
+ - Auto-approve every packet without reading the document
54
+ - Mutate source files (`src/`, tests, config)
55
+ - Call `polaris loop continue` or `polaris finalize`
56
+ - Skip packets without recording a decision
57
+
58
+ **Docs-review reads, evaluates, and decides. Ingest executes.**
@@ -0,0 +1,95 @@
1
+ ---
2
+ name: docs-review-chain
3
+ description: Route map for docs-review — step order, stop conditions, output contract, and decision protocol.
4
+ ---
5
+
6
+ # docs-review chain
7
+
8
+ ## Authority
9
+
10
+ **Polaris runtime state is authoritative. Chat reasoning is not authoritative.**
11
+
12
+ Query `smartdocs/raw/_triage-queue.json` before acting. Do not infer queue state from conversation context.
13
+
14
+ ## Decision protocol
15
+
16
+ For each flagged packet you evaluate:
17
+
18
+ 1. Read the source document at `sourcePath`
19
+ 2. Consider the flag metadata: `triageFlag`, `staleSymbols`, `authorityRisk`, `reasoning`
20
+ 3. Apply this decision rubric:
21
+ - **approve**: doc content is still valid doctrine; stale symbols are incidental (generic English words, external brand names, or aspirational references that don't affect correctness)
22
+ - **reject**: doc is clearly outdated, contradicted by current code, or describes systems that no longer exist
23
+ - **defer**: cannot determine from doc content alone — needs human review
24
+
25
+ 4. Record your decision by editing `_triage-queue.json` directly: set `reviewDecision` to `"approve"`, `"reject"`, or `"defer"`, and set `reviewedAt` to the current ISO timestamp and `reviewedBy` to `"docs-review-agent"`.
26
+
27
+ ## Output contract
28
+
29
+ All decision output written to `smartdocs/raw/`:
30
+
31
+ | File | Purpose |
32
+ |------|---------|
33
+ | `_triage-queue.json` | Updated with `reviewDecision` fields — source of truth for ingest |
34
+
35
+ ## Step traversal order
36
+
37
+ ```text
38
+ 01-load-queue ← read _triage-queue.json; count pending items; emit run-start telemetry
39
+ 02-review-packets ← for each pending packet: read doc → evaluate → record decision
40
+ 03-apply-decisions ← run polaris docs ingest to apply approved/rejected packets
41
+ 04-hand-off ← report decision summary (approved / rejected / deferred); emit triage-review-complete telemetry
42
+ ```
43
+
44
+ ## Stop conditions
45
+
46
+ **Step 01:**
47
+ - `_triage-queue.json` not found → halt: "Run docs-triage first to generate the triage queue"
48
+ - Zero pending items → report "Nothing to review — all decisions already recorded" and stop
49
+
50
+ **Step 02:**
51
+ - If a document at `sourcePath` cannot be read → record `defer` and log the path
52
+ - Process all packets before moving to step 03 — do not apply decisions mid-loop
53
+
54
+ **Step 03:**
55
+ - If no packets were approved or rejected → skip ingest, report counts only
56
+ - Ingest error → report the error; do not retry automatically
57
+
58
+ ## Run ID format
59
+
60
+ Format: `docs-review-<date>-<seq>`
61
+ - `<date>`: `YYYY-MM-DD`
62
+ - `<seq>`: zero-padded sequential number per day (`001`, `002`, …)
63
+
64
+ Example: `docs-review-2026-06-14-001`
65
+
66
+ ## Telemetry
67
+
68
+ Telemetry file: `.taskchain_artifacts/docs-review/runs/<run-id>/telemetry.jsonl` (append-only).
69
+
70
+ | Event | Trigger |
71
+ |-------|---------|
72
+ | `run-start` | Begin step 01 |
73
+ | `step-complete` | End of every step |
74
+ | `review-complete` | All packets evaluated |
75
+ | `apply-complete` | Ingest finished |
76
+
77
+ Required fields on every event: `event`, `run_id`, `timestamp`.
78
+
79
+ ## Execution reporting
80
+
81
+ After each completed step, emit a checkpoint:
82
+
83
+ ```text
84
+ **[step-name]** done | blocked | needs-input
85
+ Changed: <outputs written> or none
86
+ Validated: <checks passed> or none
87
+ Blockers: none | <explicit blocker>
88
+ ```
89
+
90
+ ### Never compressed
91
+
92
+ Always write in full:
93
+ - Decision counts (approved / rejected / deferred) after step 02
94
+ - Any packets that could not be read (defaulted to defer)
95
+ - Ingest results after step 03
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lsctech/polaris",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "Polaris — standalone taskchain orchestration framework for governed AI agent workflows",
5
5
  "bin": {
6
6
  "polaris-cli": "dist/cli/index.js"