@lsctech/polaris 0.1.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/README.md +87 -0
- package/dist/__mocks__/tool-server-linear.js +34 -0
- package/dist/cli/adopt-approve.js +60 -0
- package/dist/cli/adopt-cognition.js +199 -0
- package/dist/cli/adopt-instructions.js +143 -0
- package/dist/cli/adopt-rules.js +169 -0
- package/dist/cli/adopt-scan.js +25 -0
- package/dist/cli/adopt-smartdocs.js +192 -0
- package/dist/cli/adoption-inventory.js +350 -0
- package/dist/cli/adoption-plan.js +222 -0
- package/dist/cli/args.js +28 -0
- package/dist/cli/args.test.js +30 -0
- package/dist/cli/branding.js +41 -0
- package/dist/cli/graph.js +358 -0
- package/dist/cli/index.js +127 -0
- package/dist/cli/index.test.js +162 -0
- package/dist/cli/init-detect.js +61 -0
- package/dist/cli/init.js +492 -0
- package/dist/cli/init.test.js +259 -0
- package/dist/cli/librarian.js +32 -0
- package/dist/cli/medic.js +96 -0
- package/dist/cli/startup.test.js +23 -0
- package/dist/cli/tracker.js +130 -0
- package/dist/cli/tracker.test.js +126 -0
- package/dist/cli/version.js +10 -0
- package/dist/cli/version.test.js +11 -0
- package/dist/cli/worker.js +287 -0
- package/dist/cluster-state/fixtures/test-clusters.json +19 -0
- package/dist/cluster-state/store.js +302 -0
- package/dist/cluster-state/store.test.js +145 -0
- package/dist/cluster-state/types.js +2 -0
- package/dist/cognition/archive.js +158 -0
- package/dist/cognition/closeout-librarian-types.js +161 -0
- package/dist/cognition/cognition.test.js +623 -0
- package/dist/cognition/index.js +41 -0
- package/dist/cognition/librarian-dispatch.js +391 -0
- package/dist/cognition/librarian-dispatch.test.js +247 -0
- package/dist/cognition/librarian-packet.js +313 -0
- package/dist/cognition/librarian-types.js +57 -0
- package/dist/cognition/route-cognition-delta.js +350 -0
- package/dist/cognition/summary-delta.js +279 -0
- package/dist/cognition/validate.js +162 -0
- package/dist/config/defaults.js +98 -0
- package/dist/config/doctor.js +273 -0
- package/dist/config/index.js +14 -0
- package/dist/config/loader.js +58 -0
- package/dist/config/provider-detect.js +60 -0
- package/dist/config/provider-detect.test.js +119 -0
- package/dist/config/schema.js +2 -0
- package/dist/config/show.js +44 -0
- package/dist/config/show.test.js +42 -0
- package/dist/config/validator.js +675 -0
- package/dist/config/validator.test.js +77 -0
- package/dist/docs/canon-check.js +334 -0
- package/dist/docs/canon-check.test.js +207 -0
- package/dist/docs/doctrine.js +133 -0
- package/dist/docs/doctrine.test.js +139 -0
- package/dist/docs/index.js +168 -0
- package/dist/docs/index.test.js +83 -0
- package/dist/docs/ingest.js +295 -0
- package/dist/docs/ingest.test.js +71 -0
- package/dist/docs/migrate.js +165 -0
- package/dist/docs/migrate.test.js +136 -0
- package/dist/docs/seed-instructions.js +147 -0
- package/dist/docs/seed-instructions.test.js +137 -0
- package/dist/docs/validate-instructions.js +267 -0
- package/dist/docs/validate-instructions.test.js +238 -0
- package/dist/finalize/artifact-policy.js +214 -0
- package/dist/finalize/artifact-policy.test.js +84 -0
- package/dist/finalize/delivery-integrity.js +141 -0
- package/dist/finalize/finalize.test.js +210 -0
- package/dist/finalize/github.js +41 -0
- package/dist/finalize/index.js +474 -0
- package/dist/finalize/linear.js +213 -0
- package/dist/finalize/run-report.js +47 -0
- package/dist/finalize/steps/01-map-update.js +11 -0
- package/dist/finalize/steps/02-map-validate.js +11 -0
- package/dist/finalize/steps/03-schema-validate.js +11 -0
- package/dist/finalize/steps/04-run-checks.js +118 -0
- package/dist/finalize/steps/05-generate-report.js +14 -0
- package/dist/finalize/steps/06-commit.js +39 -0
- package/dist/finalize/steps/07-push.js +10 -0
- package/dist/finalize/steps/08-create-pr.js +9 -0
- package/dist/finalize/steps/09-update-state.js +9 -0
- package/dist/finalize/steps/10-append-jsonl.js +23 -0
- package/dist/finalize/steps/11-update-linear.js +22 -0
- package/dist/finalize/steps/12-archive.js +21 -0
- package/dist/graph/adapter/c/adapter.js +45 -0
- package/dist/graph/adapter/c/extract.js +135 -0
- package/dist/graph/adapter/c/index.js +10 -0
- package/dist/graph/adapter/c/runtime.js +67 -0
- package/dist/graph/adapter/cpp/adapter.js +45 -0
- package/dist/graph/adapter/cpp/extract.js +159 -0
- package/dist/graph/adapter/cpp/index.js +10 -0
- package/dist/graph/adapter/cpp/runtime.js +61 -0
- package/dist/graph/adapter/csharp/adapter.js +45 -0
- package/dist/graph/adapter/csharp/extract.js +127 -0
- package/dist/graph/adapter/csharp/index.js +10 -0
- package/dist/graph/adapter/csharp/runtime.js +61 -0
- package/dist/graph/adapter/dart/adapter.js +56 -0
- package/dist/graph/adapter/dart/extract.js +130 -0
- package/dist/graph/adapter/dart/index.js +10 -0
- package/dist/graph/adapter/dart/runtime.js +61 -0
- package/dist/graph/adapter/go/adapter.js +45 -0
- package/dist/graph/adapter/go/extract.js +112 -0
- package/dist/graph/adapter/go/index.js +10 -0
- package/dist/graph/adapter/go/runtime.js +58 -0
- package/dist/graph/adapter/kotlin-java/extract-common.js +83 -0
- package/dist/graph/adapter/kotlin-java/index.js +15 -0
- package/dist/graph/adapter/kotlin-java/java-adapter.js +45 -0
- package/dist/graph/adapter/kotlin-java/java-extract.js +42 -0
- package/dist/graph/adapter/kotlin-java/kotlin-adapter.js +48 -0
- package/dist/graph/adapter/kotlin-java/kotlin-extract.js +58 -0
- package/dist/graph/adapter/kotlin-java/runtime.js +75 -0
- package/dist/graph/adapter/python/adapter.js +45 -0
- package/dist/graph/adapter/python/extract.js +121 -0
- package/dist/graph/adapter/python/index.js +10 -0
- package/dist/graph/adapter/python/runtime.js +70 -0
- package/dist/graph/adapter/registry.js +82 -0
- package/dist/graph/adapter/rust/adapter.js +45 -0
- package/dist/graph/adapter/rust/extract.js +110 -0
- package/dist/graph/adapter/rust/index.js +10 -0
- package/dist/graph/adapter/rust/runtime.js +74 -0
- package/dist/graph/adapter/shell/adapter.js +34 -0
- package/dist/graph/adapter/shell/extract.js +98 -0
- package/dist/graph/adapter/shell/index.js +8 -0
- package/dist/graph/adapter/svelte/adapter.js +91 -0
- package/dist/graph/adapter/svelte/extract.js +41 -0
- package/dist/graph/adapter/svelte/index.js +8 -0
- package/dist/graph/adapter/swift/adapter.js +45 -0
- package/dist/graph/adapter/swift/extract.js +120 -0
- package/dist/graph/adapter/swift/index.js +10 -0
- package/dist/graph/adapter/swift/runtime.js +70 -0
- package/dist/graph/adapter/types.js +2 -0
- package/dist/graph/adapter/typescript-javascript/adapter.js +72 -0
- package/dist/graph/adapter/typescript-javascript/extract.js +115 -0
- package/dist/graph/adapter/typescript-javascript/index.js +10 -0
- package/dist/graph/adapter/typescript-javascript/runtime.js +97 -0
- package/dist/graph/capability/index.js +105 -0
- package/dist/graph/governance.js +104 -0
- package/dist/graph/parser/extract.js +5 -0
- package/dist/graph/parser/loader.js +2 -0
- package/dist/graph/parser/pipeline.js +194 -0
- package/dist/graph/query/index.js +277 -0
- package/dist/graph/query/types.js +5 -0
- package/dist/graph/resolver/build-edges.js +211 -0
- package/dist/graph/resolver/index.js +23 -0
- package/dist/graph/resolver/resolve-imports.js +168 -0
- package/dist/graph/store/adapter.js +132 -0
- package/dist/graph/store/queries.js +124 -0
- package/dist/graph/store/types.js +4 -0
- package/dist/ignore/defaults.js +41 -0
- package/dist/ignore/parser.js +39 -0
- package/dist/lint/map-reference-validator.js +72 -0
- package/dist/loop/abort.js +188 -0
- package/dist/loop/abort.test.js +151 -0
- package/dist/loop/adapters/__tests__/terminal-cli.test.js +92 -0
- package/dist/loop/adapters/agent-subtask.js +137 -0
- package/dist/loop/adapters/agent-subtask.test.js +151 -0
- package/dist/loop/adapters/cli-subtask-bridge.js +108 -0
- package/dist/loop/adapters/index.js +9 -0
- package/dist/loop/adapters/registry.js +17 -0
- package/dist/loop/adapters/terminal-cli.js +403 -0
- package/dist/loop/adapters/types.js +2 -0
- package/dist/loop/adapters/worker-instructions.js +85 -0
- package/dist/loop/body-parser.js +226 -0
- package/dist/loop/bootstrap-packet.js +97 -0
- package/dist/loop/bootstrap-packet.test.js +171 -0
- package/dist/loop/branch-governance.js +54 -0
- package/dist/loop/budget.js +63 -0
- package/dist/loop/budget.test.js +163 -0
- package/dist/loop/checkpoint.js +325 -0
- package/dist/loop/compact-return.js +59 -0
- package/dist/loop/continue.js +645 -0
- package/dist/loop/continue.test.js +337 -0
- package/dist/loop/dispatch-boundary.js +335 -0
- package/dist/loop/dispatch-state.js +223 -0
- package/dist/loop/dispatch-state.test.js +186 -0
- package/dist/loop/dispatch.js +1236 -0
- package/dist/loop/dispatch.test.js +499 -0
- package/dist/loop/ephemeral-smoke.test.js +160 -0
- package/dist/loop/evidence-backfill.js +216 -0
- package/dist/loop/execution-adapter.js +141 -0
- package/dist/loop/execution-adapter.test.js +66 -0
- package/dist/loop/finalize-evidence.js +169 -0
- package/dist/loop/git-custody.js +250 -0
- package/dist/loop/impl-packet-input.js +38 -0
- package/dist/loop/index.js +270 -0
- package/dist/loop/ledger.js +49 -0
- package/dist/loop/ledger.test.js +127 -0
- package/dist/loop/lifecycle-dispatch.js +348 -0
- package/dist/loop/lifecycle-dispatch.test.js +318 -0
- package/dist/loop/lifecycle.js +93 -0
- package/dist/loop/lifecycle.test.js +150 -0
- package/dist/loop/orphan-recovery.js +276 -0
- package/dist/loop/orphan-recovery.test.js +236 -0
- package/dist/loop/parent.js +1566 -0
- package/dist/loop/parent.test.js +738 -0
- package/dist/loop/provider-sealed-packet.test.js +164 -0
- package/dist/loop/resume.js +295 -0
- package/dist/loop/resume.test.js +172 -0
- package/dist/loop/run-bootstrap.js +234 -0
- package/dist/loop/run-preflight.js +203 -0
- package/dist/loop/status.js +665 -0
- package/dist/loop/status.test.js +235 -0
- package/dist/loop/wait.js +86 -0
- package/dist/loop/worker-packet.js +406 -0
- package/dist/loop/worker-packet.test.js +243 -0
- package/dist/loop/worker-prompt.js +260 -0
- package/dist/loop/worker-prompt.test.js +247 -0
- package/dist/loop/worker.js +498 -0
- package/dist/loop/worker.test.js +372 -0
- package/dist/map/atlas.js +77 -0
- package/dist/map/backfill.js +171 -0
- package/dist/map/backfill.test.js +120 -0
- package/dist/map/index.js +255 -0
- package/dist/map/inference.js +73 -0
- package/dist/map/inference.test.js +35 -0
- package/dist/map/query.js +157 -0
- package/dist/map/query.test.js +174 -0
- package/dist/map/update.js +355 -0
- package/dist/map/update.test.js +67 -0
- package/dist/map/validate.js +153 -0
- package/dist/map/validate.test.js +90 -0
- package/dist/map/welfare.js +86 -0
- package/dist/mcp/lib/invoke.js +35 -0
- package/dist/mcp/lib/redact.js +24 -0
- package/dist/mcp/lib/redact.test.js +112 -0
- package/dist/mcp/lib/root.js +41 -0
- package/dist/mcp/lib/root.test.js +114 -0
- package/dist/mcp/server.js +15 -0
- package/dist/mcp/tools/claim-child.js +119 -0
- package/dist/mcp/tools/claim-child.test.js +69 -0
- package/dist/mcp/tools/current-state.js +73 -0
- package/dist/mcp/tools/current-state.test.js +114 -0
- package/dist/mcp/tools/dispatch-result.js +186 -0
- package/dist/mcp/tools/dispatch-result.test.js +109 -0
- package/dist/mcp/tools/index.js +222 -0
- package/dist/mcp/tools/index.test.js +172 -0
- package/dist/mcp/tools/linear.js +74 -0
- package/dist/mcp/tools/loop-continue.js +159 -0
- package/dist/mcp/tools/loop-continue.test.js +186 -0
- package/dist/mcp/tools/loop-dry-run.js +15 -0
- package/dist/mcp/tools/status.js +65 -0
- package/dist/medic/chart-id.js +75 -0
- package/dist/medic/chart-schema.js +139 -0
- package/dist/runs/index.js +119 -0
- package/dist/runs/index.test.js +184 -0
- package/dist/runs/ledger-tail.js +15 -0
- package/dist/runs/list.js +29 -0
- package/dist/runs/reconcile.js +74 -0
- package/dist/runs/show.js +10 -0
- package/dist/runtime/audit/logger.js +15 -0
- package/dist/runtime/audit/reader.js +42 -0
- package/dist/runtime/checkpoint.js +89 -0
- package/dist/runtime/continuation/confirmed.js +225 -0
- package/dist/runtime/continuation/confirmed.test.js +421 -0
- package/dist/runtime/continuation/dry-run.js +74 -0
- package/dist/runtime/execution-window.js +106 -0
- package/dist/runtime/scheduling/child-selector.js +16 -0
- package/dist/runtime/state.js +43 -0
- package/dist/runtime/status.js +46 -0
- package/dist/runtime/verification/envelope.js +80 -0
- package/dist/runtime/verification/fingerprint.js +26 -0
- package/dist/skill-packet/cli.test.js +105 -0
- package/dist/skill-packet/generator.js +225 -0
- package/dist/skill-packet/generator.test.js +159 -0
- package/dist/skill-packet/index.js +49 -0
- package/dist/skill-packet/types.js +2 -0
- package/dist/smartdocs-engine/audit.js +194 -0
- package/dist/smartdocs-engine/audit.test.js +122 -0
- package/dist/smartdocs-engine/canon-check.js +340 -0
- package/dist/smartdocs-engine/canon-check.test.js +234 -0
- package/dist/smartdocs-engine/doctrine.js +633 -0
- package/dist/smartdocs-engine/doctrine.test.js +374 -0
- package/dist/smartdocs-engine/index.js +456 -0
- package/dist/smartdocs-engine/index.test.js +82 -0
- package/dist/smartdocs-engine/ingest.js +495 -0
- package/dist/smartdocs-engine/ingest.test.js +163 -0
- package/dist/smartdocs-engine/migrate.js +217 -0
- package/dist/smartdocs-engine/migrate.test.js +200 -0
- package/dist/smartdocs-engine/seed-instructions.js +335 -0
- package/dist/smartdocs-engine/seed-instructions.test.js +275 -0
- package/dist/smartdocs-engine/smartdoc-ignore.js +258 -0
- package/dist/smartdocs-engine/smartdoc-ignore.test.js +146 -0
- package/dist/smartdocs-engine/validate-instructions.js +366 -0
- package/dist/smartdocs-engine/validate-instructions.test.js +283 -0
- package/dist/tracker/adapters/github/index.js +305 -0
- package/dist/tracker/adapters/jira/index.js +293 -0
- package/dist/tracker/adapters/linear/http-errors.test.js +51 -0
- package/dist/tracker/adapters/linear/index.js +621 -0
- package/dist/tracker/adapters/linear/index.test.js +238 -0
- package/dist/tracker/adapters/linear.js +50 -0
- package/dist/tracker/adapters/local/index.js +10 -0
- package/dist/tracker/adapters/local-file/index.js +159 -0
- package/dist/tracker/adapters/mcp-bridge.js +158 -0
- package/dist/tracker/adapters/mcp-bridge.test.js +12 -0
- package/dist/tracker/adapters/spec/index.js +146 -0
- package/dist/tracker/capabilities.js +2 -0
- package/dist/tracker/index.js +49 -0
- package/dist/tracker/lifecycle-policy.js +139 -0
- package/dist/tracker/lifecycle-transition.js +164 -0
- package/dist/tracker/local-graph.js +98 -0
- package/dist/tracker/migration.js +56 -0
- package/dist/tracker/schema.js +86 -0
- package/dist/tracker/sync/index.js +404 -0
- package/dist/tracker/sync/queue-store.js +95 -0
- package/dist/tracker/sync/queue-store.test.js +104 -0
- package/dist/tracker/types.js +11 -0
- package/dist/types/linear.js +3 -0
- package/dist/types/result-packet.js +12 -0
- package/dist/types/runtime-state.js +2 -0
- package/dist/types/work-contract.js +2 -0
- package/dist/utils/monotonic-timestamp.js +13 -0
- package/dist/utils/monotonic-timestamp.test.js +19 -0
- package/package.json +64 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getLastGitModDate = getLastGitModDate;
|
|
4
|
+
exports.getFilesChangedAfter = getFilesChangedAfter;
|
|
5
|
+
exports.parseReadBeforeEditingLinks = parseReadBeforeEditingLinks;
|
|
6
|
+
exports.validateDir = validateDir;
|
|
7
|
+
exports.validateInstructions = validateInstructions;
|
|
8
|
+
exports.printReport = printReport;
|
|
9
|
+
const node_fs_1 = require("node:fs");
|
|
10
|
+
const node_path_1 = require("node:path");
|
|
11
|
+
const node_child_process_1 = require("node:child_process");
|
|
12
|
+
const loader_js_1 = require("../config/loader.js");
|
|
13
|
+
const atlas_js_1 = require("../map/atlas.js");
|
|
14
|
+
const seed_instructions_js_1 = require("./seed-instructions.js");
|
|
15
|
+
const smartdoc_ignore_js_1 = require("./smartdoc-ignore.js");
|
|
16
|
+
/**
|
|
17
|
+
* Get the last git modification date of a file.
|
|
18
|
+
* Returns null if the file is untracked or git is unavailable.
|
|
19
|
+
*/
|
|
20
|
+
function getLastGitModDate(filePath, repoRoot) {
|
|
21
|
+
try {
|
|
22
|
+
const rel = (0, node_path_1.relative)(repoRoot, filePath).replace(/\\/g, "/");
|
|
23
|
+
const out = (0, node_child_process_1.execFileSync)("git", ["-C", repoRoot, "log", "-1", "--format=%cI", "--", rel], {
|
|
24
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
25
|
+
encoding: "utf-8",
|
|
26
|
+
}).trim();
|
|
27
|
+
if (!out)
|
|
28
|
+
return null;
|
|
29
|
+
return new Date(out);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Get files in a directory that were modified after a given date (via git log).
|
|
37
|
+
* Only considers files directly in the directory (not subdirs).
|
|
38
|
+
*/
|
|
39
|
+
function getFilesChangedAfter(dir, since, repoRoot) {
|
|
40
|
+
try {
|
|
41
|
+
const relDir = (0, node_path_1.relative)(repoRoot, dir).replace(/\\/g, "/");
|
|
42
|
+
const pattern = relDir ? `${relDir}/*` : "*";
|
|
43
|
+
const isoDate = since.toISOString();
|
|
44
|
+
const out = (0, node_child_process_1.execFileSync)("git", ["-C", repoRoot, "log", `--after=${isoDate}`, "--name-only", "--format=", "--", pattern], { stdio: ["pipe", "pipe", "pipe"], encoding: "utf-8" }).trim();
|
|
45
|
+
if (!out)
|
|
46
|
+
return [];
|
|
47
|
+
const files = out
|
|
48
|
+
.split("\n")
|
|
49
|
+
.map((f) => f.trim())
|
|
50
|
+
.filter(Boolean)
|
|
51
|
+
.filter((f) => {
|
|
52
|
+
// Only files directly in the target dir (not nested subdirs)
|
|
53
|
+
const fileDir = (0, node_path_1.dirname)(f);
|
|
54
|
+
const normRelDir = relDir || ".";
|
|
55
|
+
return fileDir === normRelDir || (normRelDir === "." && !f.includes("/"));
|
|
56
|
+
});
|
|
57
|
+
// Deduplicate
|
|
58
|
+
return [...new Set(files)];
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Parse "Read before editing" section of a POLARIS.md and extract linked file paths.
|
|
66
|
+
* Looks for markdown links: [text](path)
|
|
67
|
+
*/
|
|
68
|
+
function parseReadBeforeEditingLinks(content) {
|
|
69
|
+
const links = [];
|
|
70
|
+
const sectionRe = /^##\s+Read before editing/im;
|
|
71
|
+
const nextSectionRe = /^##\s+/im;
|
|
72
|
+
const sectionMatch = sectionRe.exec(content);
|
|
73
|
+
if (!sectionMatch)
|
|
74
|
+
return links;
|
|
75
|
+
const sectionStart = sectionMatch.index + sectionMatch[0].length;
|
|
76
|
+
const remainder = content.slice(sectionStart);
|
|
77
|
+
const nextMatch = nextSectionRe.exec(remainder);
|
|
78
|
+
const sectionText = nextMatch ? remainder.slice(0, nextMatch.index) : remainder;
|
|
79
|
+
// Match markdown links: [text](path)
|
|
80
|
+
const mdLinkRe = /\[([^\]]*)\]\(([^)]+)\)/g;
|
|
81
|
+
let m;
|
|
82
|
+
while ((m = mdLinkRe.exec(sectionText)) !== null) {
|
|
83
|
+
const href = m[2].trim();
|
|
84
|
+
// Skip external URLs
|
|
85
|
+
if (!href.startsWith("http://") && !href.startsWith("https://")) {
|
|
86
|
+
links.push(href);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return links;
|
|
90
|
+
}
|
|
91
|
+
function getRequiredDirs(repoRoot) {
|
|
92
|
+
const config = (0, loader_js_1.loadConfig)(repoRoot);
|
|
93
|
+
// The config type may not yet have a docs key; access dynamically
|
|
94
|
+
const docsConfig = config["docs"];
|
|
95
|
+
return docsConfig?.instructionFiles?.required ?? [];
|
|
96
|
+
}
|
|
97
|
+
function collectDirs(dir, root, eligibilityOpts = {}, result = { eligible: [], ineligible: [] }) {
|
|
98
|
+
try {
|
|
99
|
+
for (const entry of (0, node_fs_1.readdirSync)(dir, { withFileTypes: true })) {
|
|
100
|
+
if (!entry.isDirectory())
|
|
101
|
+
continue;
|
|
102
|
+
const full = (0, node_path_1.join)(dir, entry.name);
|
|
103
|
+
const rel = (0, node_path_1.relative)(root, full).replace(/\\/g, "/");
|
|
104
|
+
// Check if directory is eligible for Smart Docs coverage
|
|
105
|
+
const eligibility = (0, smartdoc_ignore_js_1.isDirectoryEligible)(full, root, eligibilityOpts);
|
|
106
|
+
if (!eligibility.eligible) {
|
|
107
|
+
result.ineligible.push({
|
|
108
|
+
path: rel,
|
|
109
|
+
reason: eligibility.reason || "unknown",
|
|
110
|
+
category: eligibility.category === "eligible" ? undefined : eligibility.category,
|
|
111
|
+
});
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
result.eligible.push(rel);
|
|
115
|
+
collectDirs(full, root, eligibilityOpts, result);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// ignore unreadable dirs
|
|
120
|
+
}
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Validate a single directory's POLARIS.md.
|
|
125
|
+
*/
|
|
126
|
+
function validateDir(relDir, repoRoot, allRoutes) {
|
|
127
|
+
const absDir = (0, node_path_1.resolve)(repoRoot, relDir);
|
|
128
|
+
const polarisFile = (0, node_path_1.join)(absDir, "POLARIS.md");
|
|
129
|
+
const polarisRel = (0, node_path_1.relative)(repoRoot, polarisFile).replace(/\\/g, "/");
|
|
130
|
+
if (!(0, node_fs_1.existsSync)(polarisFile)) {
|
|
131
|
+
return {
|
|
132
|
+
dir: relDir,
|
|
133
|
+
polarisFile: null,
|
|
134
|
+
status: "MISSING",
|
|
135
|
+
findings: [{ severity: "MISSING", message: `No POLARIS.md in ${relDir || "."}` }],
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
const content = (0, node_fs_1.readFileSync)(polarisFile, "utf-8");
|
|
139
|
+
const findings = [];
|
|
140
|
+
// Signal 1: ≥3 files in directory changed since last POLARIS.md git modification
|
|
141
|
+
const lastMod = getLastGitModDate(polarisFile, repoRoot);
|
|
142
|
+
if (lastMod !== null) {
|
|
143
|
+
const changed = getFilesChangedAfter(absDir, lastMod, repoRoot);
|
|
144
|
+
const relevant = changed.filter((f) => !f.endsWith("POLARIS.md") && !f.endsWith("POLARIS.draft.md"));
|
|
145
|
+
if (relevant.length >= 3) {
|
|
146
|
+
findings.push({
|
|
147
|
+
severity: "WARN",
|
|
148
|
+
message: `${relevant.length} files changed since last POLARIS.md update`,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// Signal 2: broken links in "Read before editing"
|
|
153
|
+
const links = parseReadBeforeEditingLinks(content);
|
|
154
|
+
for (const link of links) {
|
|
155
|
+
const absLink = (0, node_path_1.resolve)(absDir, link);
|
|
156
|
+
if (!(0, node_fs_1.existsSync)(absLink)) {
|
|
157
|
+
findings.push({
|
|
158
|
+
severity: "ERROR",
|
|
159
|
+
message: `Broken link in "Read before editing": ${link}`,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Signal 3: instructionFile pointer in atlas references a non-existent path
|
|
164
|
+
const normRelDir = relDir.replace(/\\/g, "/");
|
|
165
|
+
for (const [filePath, entry] of Object.entries(allRoutes)) {
|
|
166
|
+
const fileDir = (0, node_path_1.dirname)(filePath).replace(/\\/g, "/");
|
|
167
|
+
if (fileDir !== normRelDir && !(normRelDir === "." && !filePath.includes("/")))
|
|
168
|
+
continue;
|
|
169
|
+
if (!entry.instructionFile)
|
|
170
|
+
continue;
|
|
171
|
+
const absInstr = (0, node_path_1.resolve)(repoRoot, entry.instructionFile);
|
|
172
|
+
if (!(0, node_fs_1.existsSync)(absInstr)) {
|
|
173
|
+
findings.push({
|
|
174
|
+
severity: "ERROR",
|
|
175
|
+
message: `Atlas entry "${filePath}" has instructionFile "${entry.instructionFile}" that does not exist`,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// Enforce POLARIS.md role boundaries (required and forbidden sections)
|
|
180
|
+
const headingRe = /^##\s+(.+)$/gm;
|
|
181
|
+
const parsedHeadings = [];
|
|
182
|
+
let headingMatch;
|
|
183
|
+
while ((headingMatch = headingRe.exec(content)) !== null) {
|
|
184
|
+
const original = headingMatch[1].trim();
|
|
185
|
+
parsedHeadings.push({
|
|
186
|
+
normalized: original.toLowerCase(),
|
|
187
|
+
original,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
const requiredSections = [
|
|
191
|
+
{ key: "purpose", name: "Purpose" },
|
|
192
|
+
{ key: "what belongs here", alternative: "files", name: "What belongs here" },
|
|
193
|
+
{ key: "what does not belong here", name: "What does not belong here" },
|
|
194
|
+
{ key: "editing rules", name: "Editing rules" },
|
|
195
|
+
{ key: "architecture assumptions", name: "Architecture assumptions" },
|
|
196
|
+
{ key: "read before editing", name: "Read before editing" },
|
|
197
|
+
{ key: "related routes", name: "Related routes" },
|
|
198
|
+
];
|
|
199
|
+
for (const section of requiredSections) {
|
|
200
|
+
const hasSection = parsedHeadings.some((h) => h.normalized === section.key || (section.alternative && h.normalized === section.alternative));
|
|
201
|
+
if (!hasSection) {
|
|
202
|
+
findings.push({
|
|
203
|
+
severity: "WARN",
|
|
204
|
+
message: `Missing required section: "${section.name}"`,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
for (const h of parsedHeadings) {
|
|
209
|
+
if (h.normalized.includes("doctrine")) {
|
|
210
|
+
findings.push({
|
|
211
|
+
severity: "ERROR",
|
|
212
|
+
message: `POLARIS.md must not contain doctrine (found section: "## ${h.original}")`,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
if (h.normalized.includes("spec") || h.normalized.includes("specification")) {
|
|
216
|
+
if (!h.normalized.includes("read before editing") && !h.normalized.includes("nearby doc")) {
|
|
217
|
+
findings.push({
|
|
218
|
+
severity: "ERROR",
|
|
219
|
+
message: `POLARIS.md must not contain architecture specs (found section: "## ${h.original}")`,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (h.normalized.includes("history") ||
|
|
224
|
+
h.normalized.includes("run summary") ||
|
|
225
|
+
h.normalized.includes("run history") ||
|
|
226
|
+
h.normalized.includes("session")) {
|
|
227
|
+
findings.push({
|
|
228
|
+
severity: "ERROR",
|
|
229
|
+
message: `POLARIS.md must not contain session history or run summaries (found section: "## ${h.original}")`,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// Signal 5: SUMMARY.md presence
|
|
234
|
+
const summaryFile = (0, node_path_1.join)(absDir, "SUMMARY.md");
|
|
235
|
+
if (!(0, node_fs_1.existsSync)(summaryFile)) {
|
|
236
|
+
// Only warn if the directory has >= 5 files in the atlas
|
|
237
|
+
const atlasFiles = Object.keys(allRoutes).filter((filePath) => {
|
|
238
|
+
const fileDir = (0, node_path_1.dirname)(filePath).replace(/\\/g, "/");
|
|
239
|
+
return fileDir === normRelDir || (normRelDir === "." && !filePath.includes("/"));
|
|
240
|
+
});
|
|
241
|
+
if (atlasFiles.length >= 5) {
|
|
242
|
+
findings.push({
|
|
243
|
+
severity: "WARN",
|
|
244
|
+
message: `Missing SUMMARY.md in ${relDir || "."}`,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
// Signal 6: SUMMARY.md doctrine-bleed scan
|
|
250
|
+
const summaryContent = (0, node_fs_1.readFileSync)(summaryFile, "utf-8");
|
|
251
|
+
const modalVerbs = ["must", "never", "always"];
|
|
252
|
+
const lines = summaryContent.split("\n");
|
|
253
|
+
for (let i = 0; i < lines.length; i++) {
|
|
254
|
+
const line = lines[i].toLowerCase();
|
|
255
|
+
for (const verb of modalVerbs) {
|
|
256
|
+
if (new RegExp(`\\b${verb}\\b`, 'i').test(line)) {
|
|
257
|
+
findings.push({
|
|
258
|
+
severity: "ERROR",
|
|
259
|
+
message: `SUMMARY.md doctrine bleed risk: found "${verb}" on line ${i + 1}`,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (findings.length === 0) {
|
|
266
|
+
return { dir: relDir, polarisFile: polarisRel, status: "OK", findings: [] };
|
|
267
|
+
}
|
|
268
|
+
const hasError = findings.some((f) => f.severity === "ERROR");
|
|
269
|
+
return {
|
|
270
|
+
dir: relDir,
|
|
271
|
+
polarisFile: polarisRel,
|
|
272
|
+
status: hasError ? "ERROR" : "WARN",
|
|
273
|
+
findings,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
function validateInstructions(opts = {}) {
|
|
277
|
+
const repoRoot = opts.repoRoot ?? process.cwd();
|
|
278
|
+
const config = (0, loader_js_1.loadConfig)(repoRoot);
|
|
279
|
+
const atlasPath = (0, node_path_1.resolve)(repoRoot, config.repo.sidecarOutputPath ?? ".polaris/map");
|
|
280
|
+
const allRoutes = {
|
|
281
|
+
...(0, atlas_js_1.readFileRoutes)(atlasPath),
|
|
282
|
+
...(0, atlas_js_1.readNeedsReview)(atlasPath),
|
|
283
|
+
};
|
|
284
|
+
const requiredDirs = getRequiredDirs(repoRoot);
|
|
285
|
+
let dirsToCheck;
|
|
286
|
+
if (opts.path) {
|
|
287
|
+
const absPath = (0, node_path_1.resolve)(repoRoot, opts.path);
|
|
288
|
+
const relPath = (0, node_path_1.relative)(repoRoot, absPath).replace(/\\/g, "/");
|
|
289
|
+
// Path traversal check
|
|
290
|
+
if (relPath.startsWith("..") || relPath.startsWith("/")) {
|
|
291
|
+
throw new Error(`Path traversal detected: path is outside repo root: ${opts.path}`);
|
|
292
|
+
}
|
|
293
|
+
dirsToCheck = [relPath];
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
// Include root dir and all eligible subdirs
|
|
297
|
+
// Note: for validation, we include root and use default eligibility (skip agent folders, etc.)
|
|
298
|
+
const { eligible: dirs } = collectDirs(repoRoot, repoRoot, {
|
|
299
|
+
includeAgentFolders: false,
|
|
300
|
+
includeHidden: false,
|
|
301
|
+
skipRoot: true,
|
|
302
|
+
});
|
|
303
|
+
dirsToCheck = [".", ...dirs];
|
|
304
|
+
}
|
|
305
|
+
const results = [];
|
|
306
|
+
for (const relDir of dirsToCheck) {
|
|
307
|
+
const result = validateDir(relDir, repoRoot, allRoutes);
|
|
308
|
+
// Upgrade MISSING → ERROR for required dirs
|
|
309
|
+
if (result.status === "MISSING" && requiredDirs.includes(relDir)) {
|
|
310
|
+
result.status = "ERROR";
|
|
311
|
+
}
|
|
312
|
+
results.push(result);
|
|
313
|
+
}
|
|
314
|
+
if (opts.fix) {
|
|
315
|
+
for (const result of results) {
|
|
316
|
+
if (result.status !== "OK") {
|
|
317
|
+
const absDir = (0, node_path_1.resolve)(repoRoot, result.dir);
|
|
318
|
+
const draftPath = (0, node_path_1.join)(absDir, "POLARIS.draft.md");
|
|
319
|
+
// Path traversal check for draft path
|
|
320
|
+
const relDraft = (0, node_path_1.relative)(repoRoot, draftPath);
|
|
321
|
+
if (relDraft.startsWith("..") || relDraft.startsWith("/")) {
|
|
322
|
+
result.findings.push({
|
|
323
|
+
severity: "ERROR",
|
|
324
|
+
message: "Cannot write draft: path traversal detected",
|
|
325
|
+
});
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
// Don't clobber existing drafts in fix mode
|
|
329
|
+
if ((0, node_fs_1.existsSync)(draftPath)) {
|
|
330
|
+
result.findings.push({
|
|
331
|
+
severity: "WARN",
|
|
332
|
+
message: "Draft already exists at POLARIS.draft.md; skipping write",
|
|
333
|
+
});
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
const content = (0, seed_instructions_js_1.generateDraft)(result.dir, repoRoot, allRoutes);
|
|
337
|
+
(0, node_fs_1.writeFileSync)(draftPath, content, "utf-8");
|
|
338
|
+
result.findings.push({
|
|
339
|
+
severity: "WARN",
|
|
340
|
+
message: "Draft written to POLARIS.draft.md",
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
const hasErrors = results.some((r) => r.status === "ERROR");
|
|
346
|
+
return { results, hasErrors };
|
|
347
|
+
}
|
|
348
|
+
const DRAFT_WRITTEN_MSG = "Draft written to POLARIS.draft.md";
|
|
349
|
+
function printReport(report) {
|
|
350
|
+
console.log("POLARIS.md validation report:");
|
|
351
|
+
for (const result of report.results) {
|
|
352
|
+
const label = result.polarisFile ?? `${result.dir || "."}/`;
|
|
353
|
+
const statusPad = result.status.padEnd(7);
|
|
354
|
+
console.log(` ${label.padEnd(40)} ${statusPad}`);
|
|
355
|
+
for (const finding of result.findings) {
|
|
356
|
+
if (finding.message === DRAFT_WRITTEN_MSG)
|
|
357
|
+
continue;
|
|
358
|
+
const prefix = finding.severity === "ERROR" ? "ERROR:" : "WARN: ";
|
|
359
|
+
console.log(` ${prefix} ${finding.message}`);
|
|
360
|
+
}
|
|
361
|
+
const draftFinding = result.findings.find((f) => f.message === DRAFT_WRITTEN_MSG);
|
|
362
|
+
if (draftFinding) {
|
|
363
|
+
console.log(` → Draft written to POLARIS.draft.md`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const validate_instructions_js_1 = require("./validate-instructions.js");
|
|
7
|
+
const TMP = (0, node_path_1.join)(process.cwd(), ".test-validate-instructions-tmp");
|
|
8
|
+
function makeEntry(overrides = {}) {
|
|
9
|
+
return {
|
|
10
|
+
domain: "test-domain",
|
|
11
|
+
route: "test-route",
|
|
12
|
+
taskchain: "test-chain",
|
|
13
|
+
confidence: 0.9,
|
|
14
|
+
classification: "indexed",
|
|
15
|
+
last_updated: "2026-01-01T00:00:00Z",
|
|
16
|
+
updated_by: "test",
|
|
17
|
+
tags: [],
|
|
18
|
+
...overrides,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function setup() {
|
|
22
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(TMP, "src/map"), { recursive: true });
|
|
23
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(TMP, ".polaris/map"), { recursive: true });
|
|
24
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(TMP, "polaris.config.json"), JSON.stringify({ repo: {}, map: {} }));
|
|
25
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(TMP, ".polaris/map/file-routes.json"), JSON.stringify({}));
|
|
26
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(TMP, ".polaris/map/needs-review.json"), JSON.stringify({}));
|
|
27
|
+
}
|
|
28
|
+
function teardown() {
|
|
29
|
+
(0, node_fs_1.rmSync)(TMP, { recursive: true, force: true });
|
|
30
|
+
}
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// parseReadBeforeEditingLinks
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
(0, vitest_1.describe)("parseReadBeforeEditingLinks", () => {
|
|
35
|
+
(0, vitest_1.it)("returns empty array when no section present", () => {
|
|
36
|
+
const content = "# Title\n\n## Purpose\nsome text";
|
|
37
|
+
(0, vitest_1.expect)((0, validate_instructions_js_1.parseReadBeforeEditingLinks)(content)).toEqual([]);
|
|
38
|
+
});
|
|
39
|
+
(0, vitest_1.it)("extracts local markdown links from section", () => {
|
|
40
|
+
const content = [
|
|
41
|
+
"# Title",
|
|
42
|
+
"",
|
|
43
|
+
"## Read before editing",
|
|
44
|
+
"",
|
|
45
|
+
"- [Design spec](../docs/spec.md)",
|
|
46
|
+
"- [Atlas](../map/atlas.ts)",
|
|
47
|
+
"",
|
|
48
|
+
"## Other section",
|
|
49
|
+
].join("\n");
|
|
50
|
+
const links = (0, validate_instructions_js_1.parseReadBeforeEditingLinks)(content);
|
|
51
|
+
(0, vitest_1.expect)(links).toContain("../docs/spec.md");
|
|
52
|
+
(0, vitest_1.expect)(links).toContain("../map/atlas.ts");
|
|
53
|
+
});
|
|
54
|
+
(0, vitest_1.it)("ignores external URLs", () => {
|
|
55
|
+
const content = [
|
|
56
|
+
"## Read before editing",
|
|
57
|
+
"",
|
|
58
|
+
"- [External](https://example.com/doc)",
|
|
59
|
+
"- [Local](./local.md)",
|
|
60
|
+
].join("\n");
|
|
61
|
+
const links = (0, validate_instructions_js_1.parseReadBeforeEditingLinks)(content);
|
|
62
|
+
(0, vitest_1.expect)(links).not.toContain("https://example.com/doc");
|
|
63
|
+
(0, vitest_1.expect)(links).toContain("./local.md");
|
|
64
|
+
});
|
|
65
|
+
(0, vitest_1.it)("does not bleed into next section", () => {
|
|
66
|
+
const content = [
|
|
67
|
+
"## Read before editing",
|
|
68
|
+
"",
|
|
69
|
+
"- [Good](./good.md)",
|
|
70
|
+
"",
|
|
71
|
+
"## Related routes",
|
|
72
|
+
"",
|
|
73
|
+
"- [Other](./other.md)",
|
|
74
|
+
].join("\n");
|
|
75
|
+
const links = (0, validate_instructions_js_1.parseReadBeforeEditingLinks)(content);
|
|
76
|
+
(0, vitest_1.expect)(links).toContain("./good.md");
|
|
77
|
+
(0, vitest_1.expect)(links).not.toContain("./other.md");
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// validateDir — MISSING
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
(0, vitest_1.describe)("validateDir - MISSING", () => {
|
|
84
|
+
(0, vitest_1.beforeEach)(setup);
|
|
85
|
+
(0, vitest_1.afterEach)(teardown);
|
|
86
|
+
(0, vitest_1.it)("returns MISSING when no POLARIS.md exists", () => {
|
|
87
|
+
const result = (0, validate_instructions_js_1.validateDir)("src/map", TMP, {});
|
|
88
|
+
(0, vitest_1.expect)(result.status).toBe("MISSING");
|
|
89
|
+
(0, vitest_1.expect)(result.polarisFile).toBeNull();
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// validateDir — OK
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
function validPolarisContent(extra) {
|
|
96
|
+
return [
|
|
97
|
+
"# map",
|
|
98
|
+
"",
|
|
99
|
+
"## Purpose",
|
|
100
|
+
"Some text.",
|
|
101
|
+
"",
|
|
102
|
+
"## What belongs here",
|
|
103
|
+
"- files",
|
|
104
|
+
"",
|
|
105
|
+
"## What does not belong here",
|
|
106
|
+
"none",
|
|
107
|
+
"",
|
|
108
|
+
"## Editing rules",
|
|
109
|
+
"none",
|
|
110
|
+
"",
|
|
111
|
+
"## Architecture assumptions",
|
|
112
|
+
"none",
|
|
113
|
+
"",
|
|
114
|
+
"## Read before editing",
|
|
115
|
+
extra ?? "none",
|
|
116
|
+
"",
|
|
117
|
+
"## Related routes",
|
|
118
|
+
"none",
|
|
119
|
+
].join("\n");
|
|
120
|
+
}
|
|
121
|
+
(0, vitest_1.describe)("validateDir - OK", () => {
|
|
122
|
+
(0, vitest_1.beforeEach)(setup);
|
|
123
|
+
(0, vitest_1.afterEach)(teardown);
|
|
124
|
+
(0, vitest_1.it)("returns OK for a POLARIS.md with no issues (no git history, no links, no atlas)", () => {
|
|
125
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(TMP, "src/map/POLARIS.md"), validPolarisContent());
|
|
126
|
+
const result = (0, validate_instructions_js_1.validateDir)("src/map", TMP, {});
|
|
127
|
+
(0, vitest_1.expect)(result.status).toBe("OK");
|
|
128
|
+
(0, vitest_1.expect)(result.findings).toHaveLength(0);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
// validateDir — SUMMARY.md (WARN/ERROR)
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
(0, vitest_1.describe)("validateDir - SUMMARY.md", () => {
|
|
135
|
+
(0, vitest_1.beforeEach)(setup);
|
|
136
|
+
(0, vitest_1.afterEach)(teardown);
|
|
137
|
+
(0, vitest_1.it)("reports WARN when SUMMARY.md is missing", () => {
|
|
138
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(TMP, "src/map/POLARIS.md"), validPolarisContent());
|
|
139
|
+
// Add 5 files to atlas to trigger Signal 5
|
|
140
|
+
const routes = {
|
|
141
|
+
"src/map/f1.ts": makeEntry(),
|
|
142
|
+
"src/map/f2.ts": makeEntry(),
|
|
143
|
+
"src/map/f3.ts": makeEntry(),
|
|
144
|
+
"src/map/f4.ts": makeEntry(),
|
|
145
|
+
"src/map/f5.ts": makeEntry(),
|
|
146
|
+
};
|
|
147
|
+
const result = (0, validate_instructions_js_1.validateDir)("src/map", TMP, routes);
|
|
148
|
+
(0, vitest_1.expect)(result.status).toBe("WARN");
|
|
149
|
+
const warnFinding = result.findings.find((f) => f.severity === "WARN");
|
|
150
|
+
(0, vitest_1.expect)(warnFinding?.message).toContain("Missing SUMMARY.md");
|
|
151
|
+
});
|
|
152
|
+
(0, vitest_1.it)("reports OK when SUMMARY.md exists and is clean", () => {
|
|
153
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(TMP, "src/map/POLARIS.md"), validPolarisContent());
|
|
154
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(TMP, "src/map/SUMMARY.md"), "# Summary\nNo modal verbs here.");
|
|
155
|
+
const result = (0, validate_instructions_js_1.validateDir)("src/map", TMP, {});
|
|
156
|
+
(0, vitest_1.expect)(result.status).toBe("OK");
|
|
157
|
+
});
|
|
158
|
+
(0, vitest_1.it)("reports ERROR when SUMMARY.md has doctrine bleed (must/never/always)", () => {
|
|
159
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(TMP, "src/map/POLARIS.md"), validPolarisContent());
|
|
160
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(TMP, "src/map/SUMMARY.md"), "# Summary\nAgents must always be polite.");
|
|
161
|
+
const result = (0, validate_instructions_js_1.validateDir)("src/map", TMP, {});
|
|
162
|
+
(0, vitest_1.expect)(result.status).toBe("ERROR");
|
|
163
|
+
const errorFindings = result.findings.filter((f) => f.severity === "ERROR");
|
|
164
|
+
(0, vitest_1.expect)(errorFindings).toHaveLength(2); // must and always
|
|
165
|
+
(0, vitest_1.expect)(errorFindings[0]?.message).toContain("doctrine bleed risk");
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
// validateDir — broken links (ERROR)
|
|
170
|
+
// ---------------------------------------------------------------------------
|
|
171
|
+
(0, vitest_1.describe)("validateDir - broken links", () => {
|
|
172
|
+
(0, vitest_1.beforeEach)(setup);
|
|
173
|
+
(0, vitest_1.afterEach)(teardown);
|
|
174
|
+
(0, vitest_1.it)("reports ERROR for a broken link in Read before editing", () => {
|
|
175
|
+
const content = validPolarisContent("- [Missing spec](../nonexistent/spec.md)");
|
|
176
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(TMP, "src/map/POLARIS.md"), content);
|
|
177
|
+
const result = (0, validate_instructions_js_1.validateDir)("src/map", TMP, {});
|
|
178
|
+
(0, vitest_1.expect)(result.status).toBe("ERROR");
|
|
179
|
+
const errorFinding = result.findings.find((f) => f.severity === "ERROR");
|
|
180
|
+
(0, vitest_1.expect)(errorFinding?.message).toContain("Broken link");
|
|
181
|
+
(0, vitest_1.expect)(errorFinding?.message).toContain("../nonexistent/spec.md");
|
|
182
|
+
});
|
|
183
|
+
(0, vitest_1.it)("does not report error for an existing linked file", () => {
|
|
184
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(TMP, "src/map/atlas.ts"), "// file");
|
|
185
|
+
const content = validPolarisContent("- [Atlas](./atlas.ts)");
|
|
186
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(TMP, "src/map/POLARIS.md"), content);
|
|
187
|
+
const result = (0, validate_instructions_js_1.validateDir)("src/map", TMP, {});
|
|
188
|
+
(0, vitest_1.expect)(result.status).toBe("OK");
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
// validateDir — instructionFile pointer (ERROR)
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
(0, vitest_1.describe)("validateDir - instructionFile pointer", () => {
|
|
195
|
+
(0, vitest_1.beforeEach)(setup);
|
|
196
|
+
(0, vitest_1.afterEach)(teardown);
|
|
197
|
+
(0, vitest_1.it)("reports ERROR when atlas instructionFile pointer references a non-existent file", () => {
|
|
198
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(TMP, "src/map/POLARIS.md"), validPolarisContent());
|
|
199
|
+
const routes = {
|
|
200
|
+
"src/map/atlas.ts": makeEntry({ instructionFile: "src/map/POLARIS.md" }),
|
|
201
|
+
"src/map/update.ts": makeEntry({ instructionFile: "src/map/GHOST.md" }),
|
|
202
|
+
};
|
|
203
|
+
const result = (0, validate_instructions_js_1.validateDir)("src/map", TMP, routes);
|
|
204
|
+
(0, vitest_1.expect)(result.status).toBe("ERROR");
|
|
205
|
+
const errorFinding = result.findings.find((f) => f.severity === "ERROR");
|
|
206
|
+
(0, vitest_1.expect)(errorFinding?.message).toContain("src/map/GHOST.md");
|
|
207
|
+
});
|
|
208
|
+
(0, vitest_1.it)("does not report error when instructionFile exists", () => {
|
|
209
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(TMP, "src/map/POLARIS.md"), validPolarisContent());
|
|
210
|
+
const routes = {
|
|
211
|
+
"src/map/atlas.ts": makeEntry({ instructionFile: "src/map/POLARIS.md" }),
|
|
212
|
+
};
|
|
213
|
+
const result = (0, validate_instructions_js_1.validateDir)("src/map", TMP, routes);
|
|
214
|
+
(0, vitest_1.expect)(result.status).toBe("OK");
|
|
215
|
+
});
|
|
216
|
+
(0, vitest_1.it)("ignores atlas entries for other directories", () => {
|
|
217
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(TMP, "src/map/POLARIS.md"), validPolarisContent());
|
|
218
|
+
const routes = {
|
|
219
|
+
"src/cli/index.ts": makeEntry({ instructionFile: "src/cli/GHOST.md" }),
|
|
220
|
+
};
|
|
221
|
+
const result = (0, validate_instructions_js_1.validateDir)("src/map", TMP, routes);
|
|
222
|
+
(0, vitest_1.expect)(result.status).toBe("OK");
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
// ---------------------------------------------------------------------------
|
|
226
|
+
// validateInstructions — overall
|
|
227
|
+
// ---------------------------------------------------------------------------
|
|
228
|
+
(0, vitest_1.describe)("validateInstructions", () => {
|
|
229
|
+
(0, vitest_1.beforeEach)(setup);
|
|
230
|
+
(0, vitest_1.afterEach)(teardown);
|
|
231
|
+
(0, vitest_1.it)("returns hasErrors=false when all checks pass", () => {
|
|
232
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(TMP, "src/map/POLARIS.md"), validPolarisContent());
|
|
233
|
+
const report = (0, validate_instructions_js_1.validateInstructions)({ repoRoot: TMP, path: "src/map" });
|
|
234
|
+
(0, vitest_1.expect)(report.hasErrors).toBe(false);
|
|
235
|
+
});
|
|
236
|
+
(0, vitest_1.it)("returns hasErrors=true when there is an ERROR", () => {
|
|
237
|
+
const content = validPolarisContent("- [Missing](./nonexistent.md)");
|
|
238
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(TMP, "src/map/POLARIS.md"), content);
|
|
239
|
+
const report = (0, validate_instructions_js_1.validateInstructions)({ repoRoot: TMP, path: "src/map" });
|
|
240
|
+
(0, vitest_1.expect)(report.hasErrors).toBe(true);
|
|
241
|
+
});
|
|
242
|
+
(0, vitest_1.it)("reports MISSING for a dir without POLARIS.md", () => {
|
|
243
|
+
const report = (0, validate_instructions_js_1.validateInstructions)({ repoRoot: TMP, path: "src/map" });
|
|
244
|
+
(0, vitest_1.expect)(report.results[0]?.status).toBe("MISSING");
|
|
245
|
+
});
|
|
246
|
+
(0, vitest_1.it)("--fix writes POLARIS.draft.md for MISSING dir", () => {
|
|
247
|
+
const report = (0, validate_instructions_js_1.validateInstructions)({ repoRoot: TMP, path: "src/map", fix: true });
|
|
248
|
+
const result = report.results[0];
|
|
249
|
+
(0, vitest_1.expect)(result?.status).toBe("MISSING");
|
|
250
|
+
(0, vitest_1.expect)((0, node_fs_1.existsSync)((0, node_path_1.join)(TMP, "src/map/POLARIS.draft.md"))).toBe(true);
|
|
251
|
+
const draftFinding = result?.findings.find((f) => f.message === "Draft written to POLARIS.draft.md");
|
|
252
|
+
(0, vitest_1.expect)(draftFinding).toBeDefined();
|
|
253
|
+
});
|
|
254
|
+
(0, vitest_1.it)("--fix does not overwrite existing POLARIS.md", () => {
|
|
255
|
+
const original = "# Original\n";
|
|
256
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(TMP, "src/map/POLARIS.md"), original);
|
|
257
|
+
(0, validate_instructions_js_1.validateInstructions)({ repoRoot: TMP, path: "src/map", fix: true });
|
|
258
|
+
// POLARIS.md should be untouched — only POLARIS.draft.md is written
|
|
259
|
+
(0, vitest_1.expect)((0, node_fs_1.readFileSync)((0, node_path_1.join)(TMP, "src/map/POLARIS.md"), "utf-8")).toBe(original);
|
|
260
|
+
});
|
|
261
|
+
(0, vitest_1.it)("scopes validation to --path", () => {
|
|
262
|
+
// Only src/map checked; other dirs are irrelevant
|
|
263
|
+
const report = (0, validate_instructions_js_1.validateInstructions)({ repoRoot: TMP, path: "src/map" });
|
|
264
|
+
(0, vitest_1.expect)(report.results).toHaveLength(1);
|
|
265
|
+
(0, vitest_1.expect)(report.results[0]?.dir).toBe("src/map");
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
// ---------------------------------------------------------------------------
|
|
269
|
+
// getLastGitModDate / getFilesChangedAfter — graceful fallback
|
|
270
|
+
// ---------------------------------------------------------------------------
|
|
271
|
+
(0, vitest_1.describe)("getLastGitModDate", () => {
|
|
272
|
+
(0, vitest_1.it)("returns null for a non-existent path in a real repo (graceful)", () => {
|
|
273
|
+
const result = (0, validate_instructions_js_1.getLastGitModDate)("/tmp/nonexistent-file.md", "/tmp");
|
|
274
|
+
(0, vitest_1.expect)(result).toBeNull();
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
(0, vitest_1.describe)("getFilesChangedAfter", () => {
|
|
278
|
+
(0, vitest_1.it)("returns empty array when no files changed (graceful)", () => {
|
|
279
|
+
const futureDate = new Date(Date.now() + 1_000_000_000);
|
|
280
|
+
const result = (0, validate_instructions_js_1.getFilesChangedAfter)("/tmp", futureDate, "/tmp");
|
|
281
|
+
(0, vitest_1.expect)(result).toEqual([]);
|
|
282
|
+
});
|
|
283
|
+
});
|