@lsctech/polaris 0.5.11 → 0.5.13
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/autoresearch/proposal.js +61 -0
- package/dist/autoresearch/score.js +124 -0
- package/dist/cli/adopt-canon.js +22 -8
- package/dist/cli/index.js +4 -0
- package/dist/cli/qc.js +97 -0
- package/dist/config/validator.js +16 -6
- package/dist/finalize/artifact-policy.js +12 -3
- package/dist/finalize/index.js +36 -21
- package/dist/finalize/run-report.js +189 -19
- package/dist/finalize/steps/05-generate-report.js +5 -1
- package/dist/finalize/steps/12-archive.js +6 -0
- package/dist/loop/adapters/terminal-cli.js +159 -25
- package/dist/loop/body-parser.js +73 -6
- package/dist/loop/continue.js +22 -4
- package/dist/loop/dispatch.js +103 -68
- package/dist/loop/orphan-recovery.js +2 -1
- package/dist/loop/parent.js +50 -15
- package/dist/loop/router/engine.js +1 -0
- package/dist/loop/run-bootstrap.js +3 -1
- package/dist/loop/run-preflight.js +4 -1
- package/dist/loop/worker-packet.js +81 -2
- package/dist/map/inference.js +4 -1
- package/dist/medic/routing-signals.js +60 -0
- package/dist/medic/run-health-consult.js +5 -4
- package/dist/medic/treatment-packets.js +5 -1
- package/dist/qc/policy.js +2 -0
- package/dist/qc/providers/coderabbit.js +140 -10
- package/dist/qc/repair-loop.js +147 -1
- package/dist/qc/repair-packets.js +16 -3
- package/dist/qc/routing.js +6 -0
- package/dist/qc/types.js +3 -0
- package/dist/run-health/index.js +12 -0
- package/dist/skill-packet/generator.js +234 -3
- package/dist/smartdocs-engine/canon-check.js +5 -5
- package/dist/smartdocs-engine/index.js +54 -0
- package/dist/smartdocs-engine/seed-instructions.js +159 -4
- package/dist/smartdocs-engine/validate-instructions.js +46 -4
- package/dist/tracker/local-graph.js +106 -3
- package/dist/workspace/POLARIS.md +4 -0
- package/dist/workspace/SUMMARY.md +56 -0
- package/package.json +1 -1
|
@@ -78,6 +78,16 @@ function subsystemFromFilePath(filePath) {
|
|
|
78
78
|
const lastSlash = filePath.lastIndexOf("/");
|
|
79
79
|
return lastSlash === -1 ? "root" : filePath.slice(0, lastSlash);
|
|
80
80
|
}
|
|
81
|
+
function deriveSourceQcRunId(finding, fallbackRunId) {
|
|
82
|
+
// Some findingId formats encode the source QC run as `<provider>-<index>-<runId>`
|
|
83
|
+
// (e.g. coderabbit-1-1783818427443). Prefer that provenance over the result
|
|
84
|
+
// run ID so packet sourceQcRunIds match the runs that emitted the findings.
|
|
85
|
+
const match = finding.findingId.match(/^(.+?)-(\d+)-(\d+)$/);
|
|
86
|
+
if (match) {
|
|
87
|
+
return `${match[1]}-${match[3]}`;
|
|
88
|
+
}
|
|
89
|
+
return fallbackRunId;
|
|
90
|
+
}
|
|
81
91
|
function enrichFinding(finding, config, sourceQcRunId) {
|
|
82
92
|
return {
|
|
83
93
|
...finding,
|
|
@@ -85,7 +95,7 @@ function enrichFinding(finding, config, sourceQcRunId) {
|
|
|
85
95
|
risk: riskLevel(finding),
|
|
86
96
|
confidenceBand: confidenceBand(finding),
|
|
87
97
|
subsystem: subsystemFromFilePath(finding.filePath),
|
|
88
|
-
sourceQcRunId,
|
|
98
|
+
sourceQcRunId: deriveSourceQcRunId(finding, sourceQcRunId),
|
|
89
99
|
};
|
|
90
100
|
}
|
|
91
101
|
/**
|
|
@@ -213,6 +223,8 @@ function buildPacket(group, index, clusterId, round, allRunIds, validationComman
|
|
|
213
223
|
const packetId = `pkt-${clusterId}-r${round}-${String(index).padStart(3, "0")}`;
|
|
214
224
|
const findingIds = group.map((f) => f.findingId).sort();
|
|
215
225
|
const sourceRunIds = [...new Set(group.map((f) => f.sourceQcRunId))].sort();
|
|
226
|
+
// Preserve the artifact run ID while also capturing the finding-level source run.
|
|
227
|
+
const combinedSourceRunIds = [...new Set([...allRunIds, ...sourceRunIds])].sort();
|
|
216
228
|
const severityFloor = group.reduce((acc, f) => (0, severity_js_1.maxSeverity)(acc, f.severity), "info");
|
|
217
229
|
const categories = [...new Set(group.map((f) => f.category).filter(Boolean))].sort();
|
|
218
230
|
const filePaths = group.map((f) => f.filePath).filter((f) => Boolean(f));
|
|
@@ -230,7 +242,7 @@ function buildPacket(group, index, clusterId, round, allRunIds, validationComman
|
|
|
230
242
|
packetId,
|
|
231
243
|
round,
|
|
232
244
|
clusterId,
|
|
233
|
-
sourceQcRunIds:
|
|
245
|
+
sourceQcRunIds: combinedSourceRunIds,
|
|
234
246
|
findingIds,
|
|
235
247
|
severityFloor,
|
|
236
248
|
rootCauseHint,
|
|
@@ -346,12 +358,13 @@ function compileRepairPackets(input) {
|
|
|
346
358
|
packets.push(buildPacket(group, packets.length, clusterId, round, allRunIds, validationCommands, compiledAt));
|
|
347
359
|
}
|
|
348
360
|
const finalPackets = assignParallelGroups(packets);
|
|
361
|
+
const manifestSourceRunIds = [...new Set(finalPackets.flatMap((p) => p.sourceQcRunIds))].sort();
|
|
349
362
|
const manifest = {
|
|
350
363
|
schemaVersion: "1.0",
|
|
351
364
|
clusterId,
|
|
352
365
|
round,
|
|
353
366
|
compiledAt,
|
|
354
|
-
sourceQcRunIds: allRunIds,
|
|
367
|
+
sourceQcRunIds: manifestSourceRunIds.length > 0 ? manifestSourceRunIds : allRunIds,
|
|
355
368
|
packets: finalPackets,
|
|
356
369
|
};
|
|
357
370
|
const manifestPath = getRepairPacketManifestPath(clusterId, round, repoRoot);
|
package/dist/qc/routing.js
CHANGED
|
@@ -12,6 +12,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
12
12
|
exports.decideRepairRouting = decideRepairRouting;
|
|
13
13
|
const security_category_js_1 = require("./security-category.js");
|
|
14
14
|
const severity_js_1 = require("./severity.js");
|
|
15
|
+
function isStateRepairCategory(category) {
|
|
16
|
+
return category === "state-repair";
|
|
17
|
+
}
|
|
15
18
|
function getBlockThreshold(config, context) {
|
|
16
19
|
const route = context.routeName ? config.routes?.[context.routeName] : undefined;
|
|
17
20
|
return route?.blockThreshold ?? config.severityThresholds?.block ?? "high";
|
|
@@ -34,6 +37,9 @@ function decideRepairRouting(finding, config, autofixEligible, context = {}) {
|
|
|
34
37
|
if ((0, security_category_js_1.isSecurityCategory)(finding.category)) {
|
|
35
38
|
return "operator-review";
|
|
36
39
|
}
|
|
40
|
+
if (isStateRepairCategory(finding.category)) {
|
|
41
|
+
return "operator-review";
|
|
42
|
+
}
|
|
37
43
|
const attribution = finding.attribution;
|
|
38
44
|
const clearAttribution = attribution.confidence === "high" || attribution.confidence === "medium";
|
|
39
45
|
if (autofixEligible && clearAttribution && attribution.childId) {
|
package/dist/qc/types.js
CHANGED
|
@@ -7,3 +7,6 @@
|
|
|
7
7
|
* shapes so that Polaris can support multiple QC backends without vendor lock-in.
|
|
8
8
|
*/
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.QC_RESOLUTION_OUTCOMES = void 0;
|
|
11
|
+
/** Allowed outcomes for an operator resolution artifact. */
|
|
12
|
+
exports.QC_RESOLUTION_OUTCOMES = ["pass", "no-repairable"];
|
package/dist/run-health/index.js
CHANGED
|
@@ -150,6 +150,18 @@ function appendSymptom(runId, symptom, repoRoot) {
|
|
|
150
150
|
symptoms: [...existing.symptoms, symptom],
|
|
151
151
|
updated_at: new Date().toISOString(),
|
|
152
152
|
};
|
|
153
|
+
// A later high/critical symptom means a previously resolved Medic consult is
|
|
154
|
+
// no longer satisfied; reopen it so closeout/medic gates do not pass on a
|
|
155
|
+
// stale resolved decision.
|
|
156
|
+
if (existing.medic_consult?.status === "resolved" &&
|
|
157
|
+
(symptom.severity === "critical" || symptom.severity === "high")) {
|
|
158
|
+
updated.medic_consult = {
|
|
159
|
+
...existing.medic_consult,
|
|
160
|
+
status: "in-progress",
|
|
161
|
+
resolved_at: undefined,
|
|
162
|
+
resolution_notes: undefined,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
153
165
|
const validation = (0, schema_js_1.validateRunHealthReport)(updated);
|
|
154
166
|
if (!validation.valid) {
|
|
155
167
|
throw new Error(`Appended symptom produces invalid report:\n${validation.errors.join("\n")}`);
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.SUPPORTED_SKILLS = exports.SKILL_ROLE_MAP = void 0;
|
|
4
7
|
exports.generateSetupBootstrapPacket = generateSetupBootstrapPacket;
|
|
5
8
|
exports.generateSkillPacket = generateSkillPacket;
|
|
6
9
|
const node_crypto_1 = require("node:crypto");
|
|
10
|
+
const node_child_process_1 = require("node:child_process");
|
|
11
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
12
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
7
13
|
exports.SKILL_ROLE_MAP = {
|
|
8
14
|
analyze: "Analyst",
|
|
9
15
|
run: "Foreman",
|
|
@@ -362,7 +368,37 @@ function buildCatalogPacket() {
|
|
|
362
368
|
],
|
|
363
369
|
};
|
|
364
370
|
}
|
|
365
|
-
function buildReconcilePacket() {
|
|
371
|
+
function buildReconcilePacket(_config, options) {
|
|
372
|
+
const repoRoot = node_path_1.default.resolve(options?.repoRoot ?? process.cwd());
|
|
373
|
+
const issueId = resolveIssueId(repoRoot, options?.issueId);
|
|
374
|
+
const runId = buildReconcileRunId(issueId);
|
|
375
|
+
const changedFiles = getReconcileChangedFiles(repoRoot);
|
|
376
|
+
const affectedFolders = resolveAffectedFolders(repoRoot, changedFiles);
|
|
377
|
+
if (affectedFolders.length === 0) {
|
|
378
|
+
return buildBlockedReconcilePacket(runId, issueId, repoRoot);
|
|
379
|
+
}
|
|
380
|
+
const allowedWritePaths = affectedFolders.flatMap((folder) => {
|
|
381
|
+
const paths = [node_path_1.default.join(repoRoot, folder, "POLARIS.md")];
|
|
382
|
+
const summaryPath = node_path_1.default.join(repoRoot, folder, "SUMMARY.md");
|
|
383
|
+
if (node_fs_1.default.existsSync(summaryPath)) {
|
|
384
|
+
paths.push(summaryPath);
|
|
385
|
+
}
|
|
386
|
+
return paths;
|
|
387
|
+
});
|
|
388
|
+
const workInventory = {
|
|
389
|
+
affected_folders: affectedFolders,
|
|
390
|
+
all_changed_files: changedFiles,
|
|
391
|
+
child_summaries: [],
|
|
392
|
+
pending_cognition_notes: [],
|
|
393
|
+
polaris_md_files: Object.fromEntries(affectedFolders.map((folder) => [
|
|
394
|
+
folder,
|
|
395
|
+
readFileOrNull(node_path_1.default.join(repoRoot, folder, "POLARIS.md")),
|
|
396
|
+
])),
|
|
397
|
+
summary_md_files: Object.fromEntries(affectedFolders.map((folder) => [
|
|
398
|
+
folder,
|
|
399
|
+
readFileOrNull(node_path_1.default.join(repoRoot, folder, "SUMMARY.md")),
|
|
400
|
+
])),
|
|
401
|
+
};
|
|
366
402
|
return {
|
|
367
403
|
authority_boundaries: [
|
|
368
404
|
"Read packet-scoped folders and their POLARIS.md and SUMMARY.md files",
|
|
@@ -389,9 +425,204 @@ function buildReconcilePacket() {
|
|
|
389
425
|
"A requested write falls outside packet-allowed paths",
|
|
390
426
|
"Work evidence is missing or contradictory",
|
|
391
427
|
],
|
|
428
|
+
packet_kind: "reconcile",
|
|
429
|
+
run_id: runId,
|
|
430
|
+
issue_id: issueId,
|
|
431
|
+
affected_folders: affectedFolders,
|
|
432
|
+
work_inventory: workInventory,
|
|
433
|
+
allowed_write_paths: allowedWritePaths,
|
|
434
|
+
prohibited_write_paths: [repoRoot],
|
|
435
|
+
constraints: {
|
|
436
|
+
max_summary_addition_lines: 50,
|
|
437
|
+
},
|
|
392
438
|
};
|
|
393
439
|
}
|
|
394
|
-
function
|
|
440
|
+
function buildBlockedReconcilePacket(runId, issueId, repoRoot) {
|
|
441
|
+
return {
|
|
442
|
+
authority_boundaries: [
|
|
443
|
+
"Read packet-scoped folders and their POLARIS.md and SUMMARY.md files",
|
|
444
|
+
"Update cognition files only within packet-allowed write paths",
|
|
445
|
+
"Create one sealed local cognition commit",
|
|
446
|
+
],
|
|
447
|
+
prohibited_actions: [
|
|
448
|
+
"Modify implementation source code, tests, or configuration",
|
|
449
|
+
"Move, ingest, classify, or promote documents",
|
|
450
|
+
"Write outside packet-allowed paths",
|
|
451
|
+
"Call polaris loop continue or polaris finalize",
|
|
452
|
+
"Git push or create a pull request",
|
|
453
|
+
"Fabricate affected folders or work inventory when no git diff is available",
|
|
454
|
+
],
|
|
455
|
+
allowed_outputs: [
|
|
456
|
+
"Updated POLARIS.md and SUMMARY.md files in packet-allowed paths",
|
|
457
|
+
"A sealed local cognition commit",
|
|
458
|
+
],
|
|
459
|
+
deliverables: [
|
|
460
|
+
"Packet-scoped cognition reconciled with completed work",
|
|
461
|
+
"All cognition changes recorded in one sealed local commit",
|
|
462
|
+
],
|
|
463
|
+
stop_conditions: [
|
|
464
|
+
"All packet-scoped cognition reconciled",
|
|
465
|
+
"A requested write falls outside packet-allowed paths",
|
|
466
|
+
"Work evidence is missing or contradictory",
|
|
467
|
+
"No git diff is available to scope reconciliation",
|
|
468
|
+
],
|
|
469
|
+
packet_kind: "reconcile",
|
|
470
|
+
run_id: runId,
|
|
471
|
+
issue_id: issueId,
|
|
472
|
+
affected_folders: [],
|
|
473
|
+
work_inventory: {
|
|
474
|
+
affected_folders: [],
|
|
475
|
+
all_changed_files: [],
|
|
476
|
+
child_summaries: [],
|
|
477
|
+
pending_cognition_notes: [],
|
|
478
|
+
polaris_md_files: {},
|
|
479
|
+
summary_md_files: {},
|
|
480
|
+
},
|
|
481
|
+
allowed_write_paths: [],
|
|
482
|
+
prohibited_write_paths: [repoRoot],
|
|
483
|
+
constraints: {
|
|
484
|
+
max_summary_addition_lines: 50,
|
|
485
|
+
},
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
// ── Reconcile helpers ─────────────────────────────────────────────────────────
|
|
489
|
+
function resolveIssueId(repoRoot, explicitIssueId) {
|
|
490
|
+
if (explicitIssueId)
|
|
491
|
+
return explicitIssueId;
|
|
492
|
+
const stateCandidates = [
|
|
493
|
+
node_path_1.default.join(repoRoot, ".taskchain_artifacts", "polaris-run", "current-state.json"),
|
|
494
|
+
node_path_1.default.join(repoRoot, ".polaris", "runs", "current-state.json"),
|
|
495
|
+
];
|
|
496
|
+
for (const candidate of stateCandidates) {
|
|
497
|
+
try {
|
|
498
|
+
const raw = JSON.parse(node_fs_1.default.readFileSync(candidate, "utf-8"));
|
|
499
|
+
const activeChild = raw["active_child"];
|
|
500
|
+
if (typeof activeChild === "string" && activeChild)
|
|
501
|
+
return activeChild;
|
|
502
|
+
const clusterId = raw["cluster_id"];
|
|
503
|
+
if (typeof clusterId === "string" && clusterId)
|
|
504
|
+
return clusterId;
|
|
505
|
+
}
|
|
506
|
+
catch {
|
|
507
|
+
// ignore missing or malformed state
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
try {
|
|
511
|
+
const branch = execGit(repoRoot, ["branch", "--show-current"]).join("").trim();
|
|
512
|
+
const match = /^pol-?(\d+)(?:-.*)?$/i.exec(branch);
|
|
513
|
+
if (match && match[1]) {
|
|
514
|
+
return `POL-${match[1]}`;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
catch {
|
|
518
|
+
// ignore
|
|
519
|
+
}
|
|
520
|
+
return "unknown";
|
|
521
|
+
}
|
|
522
|
+
function buildReconcileRunId(issueId) {
|
|
523
|
+
const slug = issueId.replace(/\s+/g, "-");
|
|
524
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
525
|
+
// Minimal sequence: 001 for now; each call is a distinct run by packet_id.
|
|
526
|
+
const seq = "001";
|
|
527
|
+
return `polaris-reconcile-${slug}-${date}-${seq}`;
|
|
528
|
+
}
|
|
529
|
+
function getReconcileChangedFiles(repoRoot) {
|
|
530
|
+
const files = new Set();
|
|
531
|
+
try {
|
|
532
|
+
for (const file of execGit(repoRoot, ["diff", "--name-only", "HEAD"])) {
|
|
533
|
+
if (file)
|
|
534
|
+
files.add(file);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
catch {
|
|
538
|
+
// ignore
|
|
539
|
+
}
|
|
540
|
+
try {
|
|
541
|
+
const base = resolveReconcileBase(repoRoot);
|
|
542
|
+
if (base) {
|
|
543
|
+
for (const file of execGit(repoRoot, ["diff", "--name-only", `${base}..HEAD`])) {
|
|
544
|
+
if (file)
|
|
545
|
+
files.add(file);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
catch {
|
|
550
|
+
// ignore
|
|
551
|
+
}
|
|
552
|
+
return [...files].sort();
|
|
553
|
+
}
|
|
554
|
+
function resolveReconcileBase(repoRoot) {
|
|
555
|
+
const candidates = [
|
|
556
|
+
resolveRemoteDefaultBranch(repoRoot),
|
|
557
|
+
"origin/main",
|
|
558
|
+
"origin/master",
|
|
559
|
+
"main",
|
|
560
|
+
"master",
|
|
561
|
+
];
|
|
562
|
+
for (const candidate of candidates) {
|
|
563
|
+
if (!candidate)
|
|
564
|
+
continue;
|
|
565
|
+
try {
|
|
566
|
+
const base = execGit(repoRoot, ["merge-base", "HEAD", candidate]).join("").trim();
|
|
567
|
+
if (base)
|
|
568
|
+
return base;
|
|
569
|
+
}
|
|
570
|
+
catch {
|
|
571
|
+
// try next candidate
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
576
|
+
function resolveRemoteDefaultBranch(repoRoot) {
|
|
577
|
+
try {
|
|
578
|
+
const ref = execGit(repoRoot, ["rev-parse", "--abbrev-ref", "refs/remotes/origin/HEAD"]).join("").trim();
|
|
579
|
+
return ref || null;
|
|
580
|
+
}
|
|
581
|
+
catch {
|
|
582
|
+
return null;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
function resolveAffectedFolders(repoRoot, changedFiles) {
|
|
586
|
+
const routeMap = loadFileRouteMap(repoRoot);
|
|
587
|
+
const folders = new Set();
|
|
588
|
+
for (const file of changedFiles) {
|
|
589
|
+
const entry = routeMap[file];
|
|
590
|
+
if (entry && typeof entry.route === "string") {
|
|
591
|
+
folders.add(entry.route.replace(/\/+$/, "") + "/");
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return [...folders].sort();
|
|
595
|
+
}
|
|
596
|
+
function loadFileRouteMap(repoRoot) {
|
|
597
|
+
const mapPath = node_path_1.default.join(repoRoot, ".polaris", "map", "file-routes.json");
|
|
598
|
+
try {
|
|
599
|
+
const raw = JSON.parse(node_fs_1.default.readFileSync(mapPath, "utf-8"));
|
|
600
|
+
const normalized = {};
|
|
601
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
602
|
+
if (value && typeof value === "object") {
|
|
603
|
+
const entry = value;
|
|
604
|
+
normalized[key] = { route: typeof entry.route === "string" ? entry.route : undefined };
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
return normalized;
|
|
608
|
+
}
|
|
609
|
+
catch {
|
|
610
|
+
return {};
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
function readFileOrNull(filePath) {
|
|
614
|
+
try {
|
|
615
|
+
return node_fs_1.default.readFileSync(filePath, "utf-8");
|
|
616
|
+
}
|
|
617
|
+
catch {
|
|
618
|
+
return null;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
function execGit(repoRoot, args) {
|
|
622
|
+
const output = (0, node_child_process_1.execFileSync)("git", args, { cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
623
|
+
return output.split("\n").filter(Boolean);
|
|
624
|
+
}
|
|
625
|
+
function generateSkillPacket(skillName, config, options) {
|
|
395
626
|
const active_role = exports.SKILL_ROLE_MAP[skillName];
|
|
396
627
|
const role_summary = ROLE_SUMMARIES[active_role];
|
|
397
628
|
const generated_at = new Date().toISOString();
|
|
@@ -425,7 +656,7 @@ function generateSkillPacket(skillName, config) {
|
|
|
425
656
|
body = buildCatalogPacket();
|
|
426
657
|
break;
|
|
427
658
|
case "reconcile":
|
|
428
|
-
body = buildReconcilePacket();
|
|
659
|
+
body = buildReconcilePacket(config, options);
|
|
429
660
|
break;
|
|
430
661
|
}
|
|
431
662
|
return {
|
|
@@ -43,7 +43,7 @@ function locateCanonFiles(changedFiles, repoRoot) {
|
|
|
43
43
|
canon.add(polarisMd);
|
|
44
44
|
}
|
|
45
45
|
// Active doctrine
|
|
46
|
-
const doctrineDir = (0, node_path_1.join)(repoRoot, "
|
|
46
|
+
const doctrineDir = (0, node_path_1.join)(repoRoot, "smartdocs", "doctrine", "active");
|
|
47
47
|
if ((0, node_fs_1.existsSync)(doctrineDir)) {
|
|
48
48
|
for (const f of (0, node_fs_1.readdirSync)(doctrineDir)) {
|
|
49
49
|
if (f.endsWith(".md") && (0, node_path_1.basename)(f).toLowerCase() !== "summary.md")
|
|
@@ -61,7 +61,7 @@ function locateCanonFiles(changedFiles, repoRoot) {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
for (const specSubdir of ["active", "implemented"]) {
|
|
64
|
-
const specDir = (0, node_path_1.join)(repoRoot, "
|
|
64
|
+
const specDir = (0, node_path_1.join)(repoRoot, "smartdocs", "specs", specSubdir);
|
|
65
65
|
if (!(0, node_fs_1.existsSync)(specDir))
|
|
66
66
|
continue;
|
|
67
67
|
for (const f of (0, node_fs_1.readdirSync)(specDir)) {
|
|
@@ -124,7 +124,7 @@ function checkPolarisMd(canonFile, changedFiles, repoRoot) {
|
|
|
124
124
|
// Determine conflict type based on rule text and changed file
|
|
125
125
|
let conflictType = "stale-implementation";
|
|
126
126
|
const ext = changedFile.split(".").pop()?.toLowerCase() ?? "";
|
|
127
|
-
const isDocFile = ext === "md" || changedFile.startsWith("
|
|
127
|
+
const isDocFile = ext === "md" || changedFile.startsWith("smartdocs/");
|
|
128
128
|
const hasDocKeywords = /\b(doc|documentation|readme|guide|spec)\b/i.test(lower);
|
|
129
129
|
const hasCandidateKeywords = /\b(candidate|divergence|proposal)\b/i.test(lower);
|
|
130
130
|
if (isDocFile || hasDocKeywords) {
|
|
@@ -282,7 +282,7 @@ function runCanonCheck(options) {
|
|
|
282
282
|
return result;
|
|
283
283
|
}
|
|
284
284
|
function writeStaleDraftDocs(conflicts, repoRoot, childId) {
|
|
285
|
-
const rawDir = (0, node_path_1.join)(repoRoot, "
|
|
285
|
+
const rawDir = (0, node_path_1.join)(repoRoot, "smartdocs", "raw");
|
|
286
286
|
try {
|
|
287
287
|
(0, node_fs_1.mkdirSync)(rawDir, { recursive: true });
|
|
288
288
|
for (const conflict of conflicts) {
|
|
@@ -309,7 +309,7 @@ function writeStaleDraftDocs(conflicts, repoRoot, childId) {
|
|
|
309
309
|
}
|
|
310
310
|
}
|
|
311
311
|
function writeCandidateDraftDocs(conflicts, repoRoot, childId) {
|
|
312
|
-
const candidateDir = (0, node_path_1.join)(repoRoot, "
|
|
312
|
+
const candidateDir = (0, node_path_1.join)(repoRoot, "smartdocs", "doctrine", "candidate");
|
|
313
313
|
try {
|
|
314
314
|
(0, node_fs_1.mkdirSync)(candidateDir, { recursive: true });
|
|
315
315
|
for (const conflict of conflicts) {
|
|
@@ -38,6 +38,32 @@ function printSeedAllResult(filename, dryRun, { written, skippedExists, skippedD
|
|
|
38
38
|
}
|
|
39
39
|
console.log(`${leadingBlankLine ? "\n" : ""}Done. ${written.length} written, ${skippedExists.length} skipped (exists), ${skippedDraft.length} skipped (draft)${extraSummary}.`);
|
|
40
40
|
}
|
|
41
|
+
function printReconcileReport(report, dryRun) {
|
|
42
|
+
for (const path of report.created) {
|
|
43
|
+
console.log(`${dryRun ? "[dry-run] would create" : "created"}: ${path}`);
|
|
44
|
+
}
|
|
45
|
+
for (const path of report.regenerated) {
|
|
46
|
+
console.log(`${dryRun ? "[dry-run] would regenerate" : "regenerated"}: ${path}`);
|
|
47
|
+
}
|
|
48
|
+
for (const path of report.regeneratedRegion) {
|
|
49
|
+
console.log(`${dryRun ? "[dry-run] would regenerate (region)" : "regenerated (region)"}: ${path}`);
|
|
50
|
+
}
|
|
51
|
+
for (const path of report.skipped) {
|
|
52
|
+
console.log(`skipped (up-to-date): ${path}`);
|
|
53
|
+
}
|
|
54
|
+
for (const path of report.blocked) {
|
|
55
|
+
console.log(`blocked (manual migration): ${path}`);
|
|
56
|
+
}
|
|
57
|
+
if (report.skippedRoot) {
|
|
58
|
+
console.log(`skipped (root): ${report.skippedRoot.path}/ (${report.skippedRoot.reason})`);
|
|
59
|
+
}
|
|
60
|
+
if (report.skippedIneligible.length > 0) {
|
|
61
|
+
for (const entry of report.skippedIneligible) {
|
|
62
|
+
console.log(`skipped (ineligible): ${entry.path}/ (${entry.reason})`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
console.log(`\nDone. ${report.created.length} created, ${report.regenerated.length} regenerated, ${report.regeneratedRegion.length} regenerated (region), ${report.skipped.length} skipped, ${report.blocked.length} blocked.`);
|
|
66
|
+
}
|
|
41
67
|
/**
|
|
42
68
|
* Build and return the top-level "docs" Commander command group for Polaris docs lifecycle workflows.
|
|
43
69
|
*
|
|
@@ -434,6 +460,34 @@ function createDocsCommand(options = {}) {
|
|
|
434
460
|
console.log(`skipped (draft exists): ${targetPath}/index.md`);
|
|
435
461
|
}
|
|
436
462
|
});
|
|
463
|
+
docs
|
|
464
|
+
.command("reconcile")
|
|
465
|
+
.description("Reconcile POLARIS.md and SUMMARY.md files across all eligible route folders")
|
|
466
|
+
.option("--all", "Reconcile all eligible directories")
|
|
467
|
+
.option("--dry-run", "Print what would be changed without writing files")
|
|
468
|
+
.option("--include-agent-folders", "Include .codex, .claude, .agents folders (default: skip)")
|
|
469
|
+
.option("--include-hidden", "Include all hidden directories starting with . (default: skip)")
|
|
470
|
+
.option("--include-root", "Include root directory (default: skip)")
|
|
471
|
+
.option("-r, --repo-root <path>", "Repository root", defaultRepoRoot)
|
|
472
|
+
.action((options) => {
|
|
473
|
+
if (!options.all) {
|
|
474
|
+
console.error("Error: provide --all to reconcile all eligible directories");
|
|
475
|
+
process.exit(1);
|
|
476
|
+
}
|
|
477
|
+
try {
|
|
478
|
+
const report = (0, seed_instructions_js_1.reconcileAll)(options.repoRoot, {
|
|
479
|
+
dryRun: options.dryRun,
|
|
480
|
+
includeAgentFolders: options.includeAgentFolders,
|
|
481
|
+
includeHidden: options.includeHidden,
|
|
482
|
+
includeRoot: options.includeRoot,
|
|
483
|
+
});
|
|
484
|
+
printReconcileReport(report, options.dryRun);
|
|
485
|
+
}
|
|
486
|
+
catch (err) {
|
|
487
|
+
console.error(`polaris docs reconcile: ${err instanceof Error ? err.message : String(err)}`);
|
|
488
|
+
process.exit(1);
|
|
489
|
+
}
|
|
490
|
+
});
|
|
437
491
|
docs
|
|
438
492
|
.command("backfill-type")
|
|
439
493
|
.description("Add OKF-conformant `type` frontmatter to existing smartdocs/ files that are missing it, " +
|