@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,295 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.classifyDoc = classifyDoc;
|
|
4
|
+
exports.ingestDocs = ingestDocs;
|
|
5
|
+
exports.printIngestResults = printIngestResults;
|
|
6
|
+
const node_fs_1 = require("node:fs");
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
const loader_js_1 = require("../config/loader.js");
|
|
9
|
+
const atlas_js_1 = require("../map/atlas.js");
|
|
10
|
+
const DEFAULT_BATCH_LIMIT = 4;
|
|
11
|
+
const TARGET_DIRS = {
|
|
12
|
+
"runtime-summary": "docs/runtime/summaries",
|
|
13
|
+
"run-report": "docs/runtime/run-reports",
|
|
14
|
+
"spec-raw": "docs/specs/raw",
|
|
15
|
+
"spec-active": "docs/specs/active",
|
|
16
|
+
"audit-finding": "docs/audits/findings",
|
|
17
|
+
"doctrine-candidate": "docs/doctrine/candidate",
|
|
18
|
+
architecture: "docs/architecture",
|
|
19
|
+
decision: "docs/decisions",
|
|
20
|
+
"deprecated-noise": "docs/runtime/generated",
|
|
21
|
+
};
|
|
22
|
+
const APPROVAL_REQUIRED = new Set(["spec-active", "architecture", "decision"]);
|
|
23
|
+
function readJson(filePath, fallback) {
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse((0, node_fs_1.readFileSync)(filePath, "utf-8"));
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
29
|
+
return fallback;
|
|
30
|
+
}
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function frontMatterValue(content, key) {
|
|
35
|
+
if (!content.startsWith("---\n"))
|
|
36
|
+
return undefined;
|
|
37
|
+
const end = content.indexOf("\n---", 4);
|
|
38
|
+
if (end === -1)
|
|
39
|
+
return undefined;
|
|
40
|
+
const lines = content.slice(4, end).split(/\r?\n/);
|
|
41
|
+
const found = lines.find((line) => line.toLowerCase().startsWith(`${key.toLowerCase()}:`));
|
|
42
|
+
return found?.slice(found.indexOf(":") + 1).trim().replace(/^["']|["']$/g, "");
|
|
43
|
+
}
|
|
44
|
+
function classifyDoc(content, filePath = "") {
|
|
45
|
+
const lower = `${filePath}\n${content}`.toLowerCase();
|
|
46
|
+
const status = frontMatterValue(content, "status")?.toLowerCase();
|
|
47
|
+
const authority = frontMatterValue(content, "authority")?.toLowerCase();
|
|
48
|
+
if (status === "deprecated" || lower.includes("deprecated noise") || lower.includes("obsolete")) {
|
|
49
|
+
return "deprecated-noise";
|
|
50
|
+
}
|
|
51
|
+
if (lower.includes("run report") || lower.includes("run-report"))
|
|
52
|
+
return "run-report";
|
|
53
|
+
if (lower.includes("runtime summary") || lower.includes("session summary"))
|
|
54
|
+
return "runtime-summary";
|
|
55
|
+
if (lower.includes("audit finding") || lower.includes("vulnerability") || lower.includes("security audit")) {
|
|
56
|
+
return "audit-finding";
|
|
57
|
+
}
|
|
58
|
+
if (authority === "doctrine" || lower.includes("doctrine") || lower.includes("must always") || lower.includes("never silently")) {
|
|
59
|
+
return "doctrine-candidate";
|
|
60
|
+
}
|
|
61
|
+
if (lower.includes("architecture decision record") || /^#\s*adr[:\s-]/im.test(content))
|
|
62
|
+
return "decision";
|
|
63
|
+
if (authority === "architecture" || lower.includes("architecture") || lower.includes("structural design"))
|
|
64
|
+
return "architecture";
|
|
65
|
+
if (status === "active" || lower.includes("active spec"))
|
|
66
|
+
return "spec-active";
|
|
67
|
+
if (lower.includes("spec") || lower.includes("acceptance criteria") || lower.includes("implementation plan")) {
|
|
68
|
+
return "spec-raw";
|
|
69
|
+
}
|
|
70
|
+
return "spec-raw";
|
|
71
|
+
}
|
|
72
|
+
function ensureDocsScaffold(repoRoot) {
|
|
73
|
+
for (const dir of Object.values(TARGET_DIRS)) {
|
|
74
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.resolve)(repoRoot, dir), { recursive: true });
|
|
75
|
+
}
|
|
76
|
+
for (const dir of [
|
|
77
|
+
"docs/raw",
|
|
78
|
+
"docs/specs/implemented",
|
|
79
|
+
"docs/specs/superseded",
|
|
80
|
+
"docs/audits/raw",
|
|
81
|
+
"docs/audits/resolved",
|
|
82
|
+
"docs/doctrine/raw",
|
|
83
|
+
"docs/doctrine/active",
|
|
84
|
+
"docs/doctrine/deprecated",
|
|
85
|
+
]) {
|
|
86
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.resolve)(repoRoot, dir), { recursive: true });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function readCurrentState(repoRoot) {
|
|
90
|
+
return readJson((0, node_path_1.resolve)(repoRoot, ".taskchain_artifacts/polaris-run/current-state.json"), {});
|
|
91
|
+
}
|
|
92
|
+
function telemetryPath(repoRoot, runId) {
|
|
93
|
+
if (!runId)
|
|
94
|
+
return null;
|
|
95
|
+
return (0, node_path_1.resolve)(repoRoot, ".taskchain_artifacts/polaris-run/runs", runId, "telemetry.jsonl");
|
|
96
|
+
}
|
|
97
|
+
function emitTelemetry(repoRoot, runId, event) {
|
|
98
|
+
const path = telemetryPath(repoRoot, runId);
|
|
99
|
+
if (!path)
|
|
100
|
+
return;
|
|
101
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(path), { recursive: true });
|
|
102
|
+
(0, node_fs_1.appendFileSync)(path, `${JSON.stringify({ ...event, run_id: runId, timestamp: new Date().toISOString() })}\n`, "utf-8");
|
|
103
|
+
}
|
|
104
|
+
function uniqueDestination(filePath) {
|
|
105
|
+
if (!(0, node_fs_1.existsSync)(filePath))
|
|
106
|
+
return filePath;
|
|
107
|
+
const dir = (0, node_path_1.dirname)(filePath);
|
|
108
|
+
const ext = (0, node_path_1.extname)(filePath);
|
|
109
|
+
const stem = (0, node_path_1.basename)(filePath, ext);
|
|
110
|
+
let index = 2;
|
|
111
|
+
let candidate = (0, node_path_1.join)(dir, `${stem}-${index}${ext}`);
|
|
112
|
+
while ((0, node_fs_1.existsSync)(candidate)) {
|
|
113
|
+
index += 1;
|
|
114
|
+
candidate = (0, node_path_1.join)(dir, `${stem}-${index}${ext}`);
|
|
115
|
+
}
|
|
116
|
+
return candidate;
|
|
117
|
+
}
|
|
118
|
+
function addCandidateFrontMatter(content, originalPath) {
|
|
119
|
+
if (content.startsWith("---\n"))
|
|
120
|
+
return content;
|
|
121
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
122
|
+
return [
|
|
123
|
+
"---",
|
|
124
|
+
"status: candidate",
|
|
125
|
+
`candidate-since: ${today}`,
|
|
126
|
+
`source: ${originalPath}`,
|
|
127
|
+
"---",
|
|
128
|
+
"",
|
|
129
|
+
content,
|
|
130
|
+
].join("\n");
|
|
131
|
+
}
|
|
132
|
+
function deriveLinkedArea(content, routes) {
|
|
133
|
+
const entries = Object.entries(routes);
|
|
134
|
+
const explicit = entries.find(([filePath]) => content.includes(filePath));
|
|
135
|
+
if (explicit) {
|
|
136
|
+
return { label: explicit[1].route || explicit[0], entry: explicit[1] };
|
|
137
|
+
}
|
|
138
|
+
const area = content.match(/\b(src\/[A-Za-z0-9_/-]+)/)?.[1];
|
|
139
|
+
if (area) {
|
|
140
|
+
const matched = entries.find(([filePath]) => filePath.startsWith(area));
|
|
141
|
+
if (matched)
|
|
142
|
+
return { label: matched[1].route || area, entry: matched[1] };
|
|
143
|
+
return { label: area, entry: null };
|
|
144
|
+
}
|
|
145
|
+
return { label: null, entry: null };
|
|
146
|
+
}
|
|
147
|
+
function updateMapEntry(repoRoot, destinationPath, linkedEntry) {
|
|
148
|
+
const config = (0, loader_js_1.loadConfig)(repoRoot);
|
|
149
|
+
const atlasPath = (0, node_path_1.resolve)(repoRoot, config.repo.sidecarOutputPath ?? ".polaris/map");
|
|
150
|
+
const routes = (0, atlas_js_1.readFileRoutes)(atlasPath);
|
|
151
|
+
const relDestination = (0, node_path_1.relative)(repoRoot, destinationPath).replace(/\\/g, "/");
|
|
152
|
+
const now = new Date().toISOString();
|
|
153
|
+
routes[relDestination] = {
|
|
154
|
+
domain: linkedEntry?.domain ?? "docs",
|
|
155
|
+
route: linkedEntry?.route ?? (0, node_path_1.dirname)(relDestination),
|
|
156
|
+
taskchain: linkedEntry?.taskchain ?? "polaris-docs",
|
|
157
|
+
confidence: linkedEntry ? 0.85 : 0.7,
|
|
158
|
+
classification: "indexed",
|
|
159
|
+
last_updated: now,
|
|
160
|
+
updated_by: "polaris-docs-ingest",
|
|
161
|
+
tags: ["docs", "ingested"],
|
|
162
|
+
instructionFile: linkedEntry?.instructionFile,
|
|
163
|
+
};
|
|
164
|
+
(0, atlas_js_1.writeFileRoutes)(atlasPath, routes);
|
|
165
|
+
const needsReview = (0, atlas_js_1.readNeedsReview)(atlasPath);
|
|
166
|
+
const entries = { ...routes, ...needsReview };
|
|
167
|
+
const instructionCoverage = (0, atlas_js_1.computeInstructionCoverage)(entries);
|
|
168
|
+
(0, atlas_js_1.writeAtlasIndex)(atlasPath, {
|
|
169
|
+
scan_date: now,
|
|
170
|
+
file_count: Object.keys(entries).length,
|
|
171
|
+
coverage_pct: Math.round((Object.values(entries).filter((e) => e.classification === "indexed").length / Object.keys(entries).length) * 100),
|
|
172
|
+
instructionCoverage,
|
|
173
|
+
entries,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
function ingestDocs(files, options) {
|
|
177
|
+
const repoRoot = (0, node_path_1.resolve)(options.repoRoot);
|
|
178
|
+
const limit = options.maxFiles ?? DEFAULT_BATCH_LIMIT;
|
|
179
|
+
if (files.length === 0)
|
|
180
|
+
throw new Error("polaris docs ingest: provide at least one file");
|
|
181
|
+
if (files.length > limit)
|
|
182
|
+
throw new Error(`polaris docs ingest: batch limit is ${limit} files`);
|
|
183
|
+
ensureDocsScaffold(repoRoot);
|
|
184
|
+
const state = readCurrentState(repoRoot);
|
|
185
|
+
const runId = state.run_id ?? null;
|
|
186
|
+
const clusterId = options.clusterId ?? state.cluster_id ?? null;
|
|
187
|
+
const config = (0, loader_js_1.loadConfig)(repoRoot);
|
|
188
|
+
const atlasPath = (0, node_path_1.resolve)(repoRoot, config.repo.sidecarOutputPath ?? ".polaris/map");
|
|
189
|
+
const routes = {
|
|
190
|
+
...(0, atlas_js_1.readFileRoutes)(atlasPath),
|
|
191
|
+
...(0, atlas_js_1.readNeedsReview)(atlasPath),
|
|
192
|
+
};
|
|
193
|
+
emitTelemetry(repoRoot, runId, {
|
|
194
|
+
event: "docs-ingest-start",
|
|
195
|
+
file: files[0],
|
|
196
|
+
count: files.length,
|
|
197
|
+
cluster_id: clusterId,
|
|
198
|
+
});
|
|
199
|
+
const results = [];
|
|
200
|
+
for (const source of files) {
|
|
201
|
+
const absSource = (0, node_path_1.resolve)(repoRoot, source);
|
|
202
|
+
// Path traversal check: ensure absSource is within repoRoot
|
|
203
|
+
const relCheck = (0, node_path_1.relative)(repoRoot, absSource);
|
|
204
|
+
if (relCheck.startsWith("..") || relCheck.startsWith("/")) {
|
|
205
|
+
throw new Error(`polaris docs ingest: path traversal detected, file outside repo: ${source}`);
|
|
206
|
+
}
|
|
207
|
+
if (!(0, node_fs_1.existsSync)(absSource))
|
|
208
|
+
throw new Error(`polaris docs ingest: file not found: ${source}`);
|
|
209
|
+
const content = (0, node_fs_1.readFileSync)(absSource, "utf-8");
|
|
210
|
+
const relSource = (0, node_path_1.relative)(repoRoot, absSource).replace(/\\/g, "/");
|
|
211
|
+
const classification = classifyDoc(content, relSource);
|
|
212
|
+
if (APPROVAL_REQUIRED.has(classification) && !options.approveAuthority) {
|
|
213
|
+
throw new Error(`polaris docs ingest: ${classification} requires explicit approval; rerun with --approve-authority`);
|
|
214
|
+
}
|
|
215
|
+
const { label: linkedMapArea, entry: linkedEntry } = deriveLinkedArea(content, routes);
|
|
216
|
+
const targetDir = (0, node_path_1.resolve)(repoRoot, TARGET_DIRS[classification]);
|
|
217
|
+
(0, node_fs_1.mkdirSync)(targetDir, { recursive: true });
|
|
218
|
+
const destination = uniqueDestination((0, node_path_1.join)(targetDir, (0, node_path_1.basename)(absSource)));
|
|
219
|
+
const relDestination = (0, node_path_1.relative)(repoRoot, destination).replace(/\\/g, "/");
|
|
220
|
+
// Fix: only replace .md suffix if present, otherwise append .provenance.json
|
|
221
|
+
const provenancePath = /\.md$/i.test(destination)
|
|
222
|
+
? destination.replace(/\.md$/i, ".provenance.json")
|
|
223
|
+
: `${destination}.provenance.json`;
|
|
224
|
+
emitTelemetry(repoRoot, runId, {
|
|
225
|
+
event: "docs-ingest-classified",
|
|
226
|
+
file: relSource,
|
|
227
|
+
classification,
|
|
228
|
+
destination: relDestination,
|
|
229
|
+
linked_map_area: linkedMapArea,
|
|
230
|
+
cluster_id: clusterId,
|
|
231
|
+
});
|
|
232
|
+
if (!options.dryRun) {
|
|
233
|
+
const output = classification === "doctrine-candidate" ? addCandidateFrontMatter(content, relSource) : content;
|
|
234
|
+
if (output !== content) {
|
|
235
|
+
(0, node_fs_1.writeFileSync)(absSource, output, "utf-8");
|
|
236
|
+
}
|
|
237
|
+
(0, node_fs_1.renameSync)(absSource, destination);
|
|
238
|
+
(0, node_fs_1.writeFileSync)(provenancePath, JSON.stringify({
|
|
239
|
+
currentPath: relDestination,
|
|
240
|
+
originalPath: relSource,
|
|
241
|
+
ingestedAt: new Date().toISOString(),
|
|
242
|
+
ingestRunId: runId,
|
|
243
|
+
ingestClusterId: clusterId,
|
|
244
|
+
relatedRunId: runId,
|
|
245
|
+
relatedIssue: state.cluster_id ?? null,
|
|
246
|
+
classifiedAs: classification,
|
|
247
|
+
linkedMapArea,
|
|
248
|
+
conflictsDetected: false,
|
|
249
|
+
}, null, 2) + "\n", "utf-8");
|
|
250
|
+
updateMapEntry(repoRoot, destination, linkedEntry);
|
|
251
|
+
}
|
|
252
|
+
if (classification === "doctrine-candidate") {
|
|
253
|
+
emitTelemetry(repoRoot, runId, {
|
|
254
|
+
event: "doctrine-candidate-proposed",
|
|
255
|
+
file: relDestination,
|
|
256
|
+
cluster_id: clusterId,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
emitTelemetry(repoRoot, runId, {
|
|
260
|
+
event: "docs-ingest",
|
|
261
|
+
file: relSource,
|
|
262
|
+
classification,
|
|
263
|
+
destination: relDestination,
|
|
264
|
+
linked_map_area: linkedMapArea,
|
|
265
|
+
cluster_id: clusterId,
|
|
266
|
+
});
|
|
267
|
+
results.push({
|
|
268
|
+
sourcePath: relSource,
|
|
269
|
+
destinationPath: relDestination,
|
|
270
|
+
classification,
|
|
271
|
+
linkedMapArea,
|
|
272
|
+
runId,
|
|
273
|
+
provenancePath: options.dryRun ? null : (0, node_path_1.relative)(repoRoot, provenancePath).replace(/\\/g, "/"),
|
|
274
|
+
dryRun: Boolean(options.dryRun),
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
emitTelemetry(repoRoot, runId, {
|
|
278
|
+
event: "docs-ingest-complete",
|
|
279
|
+
file: files[files.length - 1],
|
|
280
|
+
count: files.length,
|
|
281
|
+
cluster_id: clusterId,
|
|
282
|
+
});
|
|
283
|
+
return results;
|
|
284
|
+
}
|
|
285
|
+
function printIngestResults(results) {
|
|
286
|
+
for (const result of results) {
|
|
287
|
+
const prefix = result.dryRun ? "[dry-run] " : "";
|
|
288
|
+
console.log(`${prefix}${result.sourcePath} -> ${result.destinationPath}`);
|
|
289
|
+
console.log(`classification: ${result.classification}`);
|
|
290
|
+
console.log(`linked_map_area: ${result.linkedMapArea ?? "none"}`);
|
|
291
|
+
console.log(`run_id: ${result.runId ?? "none"}`);
|
|
292
|
+
if (result.provenancePath)
|
|
293
|
+
console.log(`provenance: ${result.provenancePath}`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const node_fs_1 = require("node:fs");
|
|
4
|
+
const node_path_1 = require("node:path");
|
|
5
|
+
const node_fs_2 = require("node:fs");
|
|
6
|
+
const node_os_1 = require("node:os");
|
|
7
|
+
const vitest_1 = require("vitest");
|
|
8
|
+
const ingest_js_1 = require("./ingest.js");
|
|
9
|
+
function makeRepo() {
|
|
10
|
+
const repoRoot = (0, node_fs_2.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "polaris-docs-ingest-"));
|
|
11
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(repoRoot, "docs", "raw"), { recursive: true });
|
|
12
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(repoRoot, ".polaris", "map"), { recursive: true });
|
|
13
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(repoRoot, ".taskchain_artifacts", "polaris-run"), { recursive: true });
|
|
14
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(repoRoot, "polaris.config.json"), JSON.stringify({ repo: { sidecarOutputPath: ".polaris/map" } }), "utf-8");
|
|
15
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(repoRoot, ".polaris", "map", "file-routes.json"), JSON.stringify({
|
|
16
|
+
"src/docs/index.ts": {
|
|
17
|
+
domain: "docs",
|
|
18
|
+
route: "src/docs",
|
|
19
|
+
taskchain: "polaris-docs",
|
|
20
|
+
confidence: 0.95,
|
|
21
|
+
classification: "indexed",
|
|
22
|
+
last_updated: "",
|
|
23
|
+
updated_by: "",
|
|
24
|
+
tags: ["docs"],
|
|
25
|
+
instructionFile: "src/docs/POLARIS.md",
|
|
26
|
+
},
|
|
27
|
+
}), "utf-8");
|
|
28
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(repoRoot, ".polaris", "map", "needs-review.json"), "{}\n", "utf-8");
|
|
29
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(repoRoot, ".taskchain_artifacts", "polaris-run", "current-state.json"), JSON.stringify({ run_id: "test-run-001", cluster_id: "POL-42" }), "utf-8");
|
|
30
|
+
return repoRoot;
|
|
31
|
+
}
|
|
32
|
+
(0, vitest_1.describe)("classifyDoc", () => {
|
|
33
|
+
(0, vitest_1.it)("classifies docs from explicit content signals", () => {
|
|
34
|
+
(0, vitest_1.expect)((0, ingest_js_1.classifyDoc)("# Runtime Summary\n\nSession summary")).toBe("runtime-summary");
|
|
35
|
+
(0, vitest_1.expect)((0, ingest_js_1.classifyDoc)("# Audit Finding\n\nSecurity audit result")).toBe("audit-finding");
|
|
36
|
+
(0, vitest_1.expect)((0, ingest_js_1.classifyDoc)("# Doctrine\n\nAgents must always preserve state")).toBe("doctrine-candidate");
|
|
37
|
+
(0, vitest_1.expect)((0, ingest_js_1.classifyDoc)("# Feature Spec\n\nAcceptance Criteria")).toBe("spec-raw");
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
(0, vitest_1.describe)("ingestDocs", () => {
|
|
41
|
+
(0, vitest_1.it)("moves a raw spec, writes provenance, links map area, and emits telemetry", () => {
|
|
42
|
+
const repoRoot = makeRepo();
|
|
43
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(repoRoot, "docs", "raw", "ingest-plan.md"), "# Ingest Spec\n\nAcceptance Criteria\n\nTouches src/docs/index.ts.", "utf-8");
|
|
44
|
+
const [result] = (0, ingest_js_1.ingestDocs)(["docs/raw/ingest-plan.md"], { repoRoot });
|
|
45
|
+
(0, vitest_1.expect)(result.classification).toBe("spec-raw");
|
|
46
|
+
(0, vitest_1.expect)(result.destinationPath).toBe("docs/specs/raw/ingest-plan.md");
|
|
47
|
+
(0, vitest_1.expect)(result.linkedMapArea).toBe("src/docs");
|
|
48
|
+
(0, vitest_1.expect)((0, node_fs_1.existsSync)((0, node_path_1.join)(repoRoot, "docs", "specs", "raw", "ingest-plan.md"))).toBe(true);
|
|
49
|
+
(0, vitest_1.expect)((0, node_fs_1.existsSync)((0, node_path_1.join)(repoRoot, "docs", "specs", "raw", "ingest-plan.provenance.json"))).toBe(true);
|
|
50
|
+
const telemetry = (0, node_fs_1.readFileSync)((0, node_path_1.join)(repoRoot, ".taskchain_artifacts", "polaris-run", "runs", "test-run-001", "telemetry.jsonl"), "utf-8");
|
|
51
|
+
(0, vitest_1.expect)(telemetry).toContain("\"event\":\"docs-ingest\"");
|
|
52
|
+
});
|
|
53
|
+
(0, vitest_1.it)("rejects batches above the bounded file limit", () => {
|
|
54
|
+
const repoRoot = makeRepo();
|
|
55
|
+
for (const name of ["a.md", "b.md", "c.md", "d.md", "e.md"]) {
|
|
56
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(repoRoot, "docs", "raw", name), "# Spec\n\nAcceptance Criteria", "utf-8");
|
|
57
|
+
}
|
|
58
|
+
(0, vitest_1.expect)(() => (0, ingest_js_1.ingestDocs)([
|
|
59
|
+
"docs/raw/a.md",
|
|
60
|
+
"docs/raw/b.md",
|
|
61
|
+
"docs/raw/c.md",
|
|
62
|
+
"docs/raw/d.md",
|
|
63
|
+
"docs/raw/e.md",
|
|
64
|
+
], { repoRoot })).toThrow("batch limit is 4 files");
|
|
65
|
+
});
|
|
66
|
+
(0, vitest_1.it)("does not promote high-authority architecture docs without approval", () => {
|
|
67
|
+
const repoRoot = makeRepo();
|
|
68
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(repoRoot, "docs", "raw", "architecture.md"), "# Architecture\n\nStructural design", "utf-8");
|
|
69
|
+
(0, vitest_1.expect)(() => (0, ingest_js_1.ingestDocs)(["docs/raw/architecture.md"], { repoRoot })).toThrow("requires explicit approval");
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isAllowedException = isAllowedException;
|
|
4
|
+
exports.migrateDocs = migrateDocs;
|
|
5
|
+
exports.printMigrateResults = printMigrateResults;
|
|
6
|
+
const node_child_process_1 = require("node:child_process");
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_path_1 = require("node:path");
|
|
9
|
+
/** Basenames that are always allowed regardless of location. */
|
|
10
|
+
const ALLOWED_BASENAMES = new Set(["README.md", "POLARIS.md", "CHANGELOG.md", "LICENSE.md"]);
|
|
11
|
+
/**
|
|
12
|
+
* Directory prefixes (relative to repo root, forward-slash notation) where
|
|
13
|
+
* markdown files may live without being migrated.
|
|
14
|
+
*/
|
|
15
|
+
const ALLOWED_DIR_PREFIXES = [
|
|
16
|
+
"docs/",
|
|
17
|
+
".agents/",
|
|
18
|
+
".codex/",
|
|
19
|
+
".claude/",
|
|
20
|
+
".taskchain_artifacts/",
|
|
21
|
+
];
|
|
22
|
+
function isAllowedException(relPath) {
|
|
23
|
+
const name = (0, node_path_1.basename)(relPath);
|
|
24
|
+
if (ALLOWED_BASENAMES.has(name)) {
|
|
25
|
+
return { allowed: true, reason: `allowed basename: ${name}` };
|
|
26
|
+
}
|
|
27
|
+
for (const prefix of ALLOWED_DIR_PREFIXES) {
|
|
28
|
+
if (relPath.startsWith(prefix)) {
|
|
29
|
+
return { allowed: true, reason: `in allowed directory: ${prefix}` };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return { allowed: false };
|
|
33
|
+
}
|
|
34
|
+
function findMarkdownFiles(repoRoot) {
|
|
35
|
+
try {
|
|
36
|
+
const output = (0, node_child_process_1.execFileSync)("git", ["ls-files", "--cached", "--others", "--exclude-standard", "*.md"], { cwd: repoRoot, encoding: "utf-8" });
|
|
37
|
+
return output
|
|
38
|
+
.split("\n")
|
|
39
|
+
.map((l) => l.trim())
|
|
40
|
+
.filter((l) => l.length > 0 && l.endsWith(".md"));
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Fallback if not a git repo: use find
|
|
44
|
+
const output = (0, node_child_process_1.execFileSync)("find", [".", "-name", "*.md", "-not", "-path", "./.git/*"], { cwd: repoRoot, encoding: "utf-8" });
|
|
45
|
+
return output
|
|
46
|
+
.split("\n")
|
|
47
|
+
.map((l) => l.trim().replace(/^\.\//, ""))
|
|
48
|
+
.filter((l) => l.length > 0 && l.endsWith(".md"));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function uniqueDestination(filePath) {
|
|
52
|
+
if (!(0, node_fs_1.existsSync)(filePath))
|
|
53
|
+
return filePath;
|
|
54
|
+
const dir = (0, node_path_1.dirname)(filePath);
|
|
55
|
+
const name = (0, node_path_1.basename)(filePath, ".md");
|
|
56
|
+
let index = 2;
|
|
57
|
+
let candidate = (0, node_path_1.join)(dir, `${name}-${index}.md`);
|
|
58
|
+
while ((0, node_fs_1.existsSync)(candidate)) {
|
|
59
|
+
index += 1;
|
|
60
|
+
candidate = (0, node_path_1.join)(dir, `${name}-${index}.md`);
|
|
61
|
+
}
|
|
62
|
+
return candidate;
|
|
63
|
+
}
|
|
64
|
+
function makeMigrationRunId() {
|
|
65
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
66
|
+
const seq = String(Date.now()).slice(-4);
|
|
67
|
+
return `polaris-docs-migrate-${date}-${seq}`;
|
|
68
|
+
}
|
|
69
|
+
function batchFiles(files, batchSize = 4) {
|
|
70
|
+
const batches = [];
|
|
71
|
+
for (let i = 0; i < files.length; i += batchSize) {
|
|
72
|
+
batches.push(files.slice(i, i + batchSize));
|
|
73
|
+
}
|
|
74
|
+
return batches;
|
|
75
|
+
}
|
|
76
|
+
function migrateDocs(options) {
|
|
77
|
+
const repoRoot = (0, node_path_1.resolve)(options.repoRoot);
|
|
78
|
+
const migrationRunId = options.migrationRunId ?? makeMigrationRunId();
|
|
79
|
+
const rawDir = (0, node_path_1.resolve)(repoRoot, "docs/raw");
|
|
80
|
+
const allMd = findMarkdownFiles(repoRoot);
|
|
81
|
+
const results = [];
|
|
82
|
+
const toMigrate = [];
|
|
83
|
+
for (const relPath of allMd) {
|
|
84
|
+
const check = isAllowedException(relPath);
|
|
85
|
+
if (check.allowed) {
|
|
86
|
+
results.push({
|
|
87
|
+
originalPath: relPath,
|
|
88
|
+
currentPath: relPath,
|
|
89
|
+
classification: "allowed-exception",
|
|
90
|
+
exceptionReason: check.reason,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
toMigrate.push(relPath);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (!options.dryRun && toMigrate.length > 0) {
|
|
98
|
+
(0, node_fs_1.mkdirSync)(rawDir, { recursive: true });
|
|
99
|
+
}
|
|
100
|
+
const provenanceRecords = [];
|
|
101
|
+
for (const relPath of toMigrate) {
|
|
102
|
+
const absSource = (0, node_path_1.resolve)(repoRoot, relPath);
|
|
103
|
+
const destFile = uniqueDestination((0, node_path_1.join)(rawDir, (0, node_path_1.basename)(relPath)));
|
|
104
|
+
const relDest = (0, node_path_1.relative)(repoRoot, destFile).replace(/\\/g, "/");
|
|
105
|
+
if (!options.dryRun) {
|
|
106
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(destFile), { recursive: true });
|
|
107
|
+
(0, node_fs_1.renameSync)(absSource, destFile);
|
|
108
|
+
provenanceRecords.push({
|
|
109
|
+
originalPath: relPath,
|
|
110
|
+
currentPath: relDest,
|
|
111
|
+
migratedAt: new Date().toISOString(),
|
|
112
|
+
migrationRunId,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
results.push({
|
|
116
|
+
originalPath: relPath,
|
|
117
|
+
currentPath: options.dryRun ? relPath : relDest,
|
|
118
|
+
classification: "migrated",
|
|
119
|
+
destination: relDest,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
let provenancePath = null;
|
|
123
|
+
if (!options.dryRun && provenanceRecords.length > 0) {
|
|
124
|
+
const provDir = (0, node_path_1.resolve)(repoRoot, ".taskchain_artifacts", "polaris-docs-migrate", migrationRunId);
|
|
125
|
+
(0, node_fs_1.mkdirSync)(provDir, { recursive: true });
|
|
126
|
+
const absProvPath = (0, node_path_1.join)(provDir, "provenance.json");
|
|
127
|
+
(0, node_fs_1.writeFileSync)(absProvPath, JSON.stringify(provenanceRecords, null, 2) + "\n", "utf-8");
|
|
128
|
+
provenancePath = (0, node_path_1.relative)(repoRoot, absProvPath).replace(/\\/g, "/");
|
|
129
|
+
}
|
|
130
|
+
// Build ingest batch paths: where files will land in docs/raw/
|
|
131
|
+
const migratedDestPaths = results
|
|
132
|
+
.filter((r) => r.classification === "migrated")
|
|
133
|
+
.map((r) => r.destination ?? `docs/raw/${(0, node_path_1.basename)(r.originalPath)}`);
|
|
134
|
+
const ingestBatches = batchFiles(migratedDestPaths, 4);
|
|
135
|
+
return { results, migrationRunId, provenancePath, ingestBatches, dryRun: Boolean(options.dryRun) };
|
|
136
|
+
}
|
|
137
|
+
function printMigrateResults(result) {
|
|
138
|
+
const prefix = result.dryRun ? "[dry-run] " : "";
|
|
139
|
+
const migrated = result.results.filter((r) => r.classification === "migrated");
|
|
140
|
+
const exceptions = result.results.filter((r) => r.classification === "allowed-exception");
|
|
141
|
+
console.log(`\n${prefix}Migration run: ${result.migrationRunId}`);
|
|
142
|
+
console.log(`${prefix}Files to migrate: ${migrated.length}`);
|
|
143
|
+
for (const r of migrated) {
|
|
144
|
+
const dest = r.destination ?? `docs/raw/${(0, node_path_1.basename)(r.originalPath)}`;
|
|
145
|
+
console.log(` ${r.originalPath} -> ${dest}`);
|
|
146
|
+
}
|
|
147
|
+
if (exceptions.length > 0) {
|
|
148
|
+
console.log(`\n${prefix}Allowed exceptions (not moved): ${exceptions.length}`);
|
|
149
|
+
for (const r of exceptions) {
|
|
150
|
+
console.log(` ${r.originalPath} [${r.exceptionReason}]`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (result.ingestBatches.length > 0) {
|
|
154
|
+
console.log(`\n${prefix}Ingest cluster batches (${result.ingestBatches.length} batch${result.ingestBatches.length === 1 ? "" : "es"}):`);
|
|
155
|
+
result.ingestBatches.forEach((batch, i) => {
|
|
156
|
+
console.log(` Batch ${i + 1}:`);
|
|
157
|
+
for (const f of batch) {
|
|
158
|
+
console.log(` polaris docs ingest --file ${f}`);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
if (result.provenancePath) {
|
|
163
|
+
console.log(`\nProvenance written to: ${result.provenancePath}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const node_child_process_1 = require("node:child_process");
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const node_fs_2 = require("node:fs");
|
|
7
|
+
const node_os_1 = require("node:os");
|
|
8
|
+
const vitest_1 = require("vitest");
|
|
9
|
+
const migrate_js_1 = require("./migrate.js");
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// isAllowedException
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
(0, vitest_1.describe)("isAllowedException", () => {
|
|
14
|
+
(0, vitest_1.it)("allows README.md in any directory", () => {
|
|
15
|
+
(0, vitest_1.expect)((0, migrate_js_1.isAllowedException)("README.md").allowed).toBe(true);
|
|
16
|
+
(0, vitest_1.expect)((0, migrate_js_1.isAllowedException)("src/README.md").allowed).toBe(true);
|
|
17
|
+
(0, vitest_1.expect)((0, migrate_js_1.isAllowedException)("some/deep/path/README.md").allowed).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
(0, vitest_1.it)("allows POLARIS.md in any directory", () => {
|
|
20
|
+
(0, vitest_1.expect)((0, migrate_js_1.isAllowedException)("POLARIS.md").allowed).toBe(true);
|
|
21
|
+
(0, vitest_1.expect)((0, migrate_js_1.isAllowedException)("src/cli/POLARIS.md").allowed).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
(0, vitest_1.it)("allows CHANGELOG.md and LICENSE.md", () => {
|
|
24
|
+
(0, vitest_1.expect)((0, migrate_js_1.isAllowedException)("CHANGELOG.md").allowed).toBe(true);
|
|
25
|
+
(0, vitest_1.expect)((0, migrate_js_1.isAllowedException)("LICENSE.md").allowed).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
(0, vitest_1.it)("allows anything under docs/", () => {
|
|
28
|
+
(0, vitest_1.expect)((0, migrate_js_1.isAllowedException)("docs/spec/foo.md").allowed).toBe(true);
|
|
29
|
+
(0, vitest_1.expect)((0, migrate_js_1.isAllowedException)("docs/raw/bar.md").allowed).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
(0, vitest_1.it)("allows anything under .agents/, .codex/, .claude/, .taskchain_artifacts/", () => {
|
|
32
|
+
(0, vitest_1.expect)((0, migrate_js_1.isAllowedException)(".agents/instructions.md").allowed).toBe(true);
|
|
33
|
+
(0, vitest_1.expect)((0, migrate_js_1.isAllowedException)(".codex/skills/foo/SKILL.md").allowed).toBe(true);
|
|
34
|
+
(0, vitest_1.expect)((0, migrate_js_1.isAllowedException)(".claude/CLAUDE.md").allowed).toBe(true);
|
|
35
|
+
(0, vitest_1.expect)((0, migrate_js_1.isAllowedException)(".taskchain_artifacts/run/notes.md").allowed).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
(0, vitest_1.it)("does NOT allow arbitrary markdown files", () => {
|
|
38
|
+
(0, vitest_1.expect)((0, migrate_js_1.isAllowedException)("src/some-feature/NOTES.md").allowed).toBe(false);
|
|
39
|
+
(0, vitest_1.expect)((0, migrate_js_1.isAllowedException)("planning/ideas.md").allowed).toBe(false);
|
|
40
|
+
(0, vitest_1.expect)((0, migrate_js_1.isAllowedException)("scratch.md").allowed).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// migrateDocs — dry-run
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
function makeGitRepo() {
|
|
47
|
+
const repoRoot = (0, node_fs_2.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "polaris-migrate-"));
|
|
48
|
+
(0, node_child_process_1.execFileSync)("git", ["init"], { cwd: repoRoot });
|
|
49
|
+
(0, node_child_process_1.execFileSync)("git", ["config", "user.email", "test@test.com"], { cwd: repoRoot });
|
|
50
|
+
(0, node_child_process_1.execFileSync)("git", ["config", "user.name", "Test"], { cwd: repoRoot });
|
|
51
|
+
return repoRoot;
|
|
52
|
+
}
|
|
53
|
+
function addTrackedFile(repoRoot, relPath, content = "# Doc\n\nContent") {
|
|
54
|
+
const absPath = (0, node_path_1.join)(repoRoot, relPath);
|
|
55
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(repoRoot, relPath.split("/").slice(0, -1).join("/")), { recursive: true });
|
|
56
|
+
(0, node_fs_1.writeFileSync)(absPath, content, "utf-8");
|
|
57
|
+
(0, node_child_process_1.execFileSync)("git", ["add", relPath], { cwd: repoRoot });
|
|
58
|
+
}
|
|
59
|
+
(0, vitest_1.describe)("migrateDocs --dry-run", () => {
|
|
60
|
+
let repoRoot;
|
|
61
|
+
(0, vitest_1.beforeEach)(() => {
|
|
62
|
+
repoRoot = makeGitRepo();
|
|
63
|
+
});
|
|
64
|
+
(0, vitest_1.it)("reports files to migrate and allowed exceptions, does not move files", () => {
|
|
65
|
+
addTrackedFile(repoRoot, "README.md");
|
|
66
|
+
addTrackedFile(repoRoot, "planning/ideas.md");
|
|
67
|
+
addTrackedFile(repoRoot, "docs/spec/existing.md");
|
|
68
|
+
const result = (0, migrate_js_1.migrateDocs)({ repoRoot, dryRun: true, migrationRunId: "test-migrate-001" });
|
|
69
|
+
(0, vitest_1.expect)(result.dryRun).toBe(true);
|
|
70
|
+
(0, vitest_1.expect)(result.migrationRunId).toBe("test-migrate-001");
|
|
71
|
+
const migrated = result.results.filter((r) => r.classification === "migrated");
|
|
72
|
+
const exceptions = result.results.filter((r) => r.classification === "allowed-exception");
|
|
73
|
+
(0, vitest_1.expect)(migrated).toHaveLength(1);
|
|
74
|
+
(0, vitest_1.expect)(migrated[0].originalPath).toBe("planning/ideas.md");
|
|
75
|
+
(0, vitest_1.expect)(migrated[0].destination).toBe("docs/raw/ideas.md");
|
|
76
|
+
(0, vitest_1.expect)(exceptions.length).toBeGreaterThanOrEqual(2);
|
|
77
|
+
// File must NOT have been moved
|
|
78
|
+
(0, vitest_1.expect)((0, node_fs_1.existsSync)((0, node_path_1.join)(repoRoot, "planning/ideas.md"))).toBe(true);
|
|
79
|
+
(0, vitest_1.expect)((0, node_fs_1.existsSync)((0, node_path_1.join)(repoRoot, "docs/raw/ideas.md"))).toBe(false);
|
|
80
|
+
// No provenance written in dry-run
|
|
81
|
+
(0, vitest_1.expect)(result.provenancePath).toBeNull();
|
|
82
|
+
});
|
|
83
|
+
(0, vitest_1.it)("produces ingest batches of at most 4 files", () => {
|
|
84
|
+
for (let i = 0; i < 9; i++) {
|
|
85
|
+
addTrackedFile(repoRoot, `planning/doc${i}.md`);
|
|
86
|
+
}
|
|
87
|
+
const result = (0, migrate_js_1.migrateDocs)({ repoRoot, dryRun: true, migrationRunId: "test-migrate-002" });
|
|
88
|
+
const allBatched = result.ingestBatches.flat();
|
|
89
|
+
(0, vitest_1.expect)(allBatched).toHaveLength(9);
|
|
90
|
+
for (const batch of result.ingestBatches) {
|
|
91
|
+
(0, vitest_1.expect)(batch.length).toBeLessThanOrEqual(4);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
// migrateDocs — actual migration
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
(0, vitest_1.describe)("migrateDocs (live)", () => {
|
|
99
|
+
let repoRoot;
|
|
100
|
+
(0, vitest_1.beforeEach)(() => {
|
|
101
|
+
repoRoot = makeGitRepo();
|
|
102
|
+
});
|
|
103
|
+
(0, vitest_1.it)("moves files to docs/raw/, writes provenance JSON", () => {
|
|
104
|
+
addTrackedFile(repoRoot, "README.md");
|
|
105
|
+
addTrackedFile(repoRoot, "scratch/notes.md", "# Notes");
|
|
106
|
+
const result = (0, migrate_js_1.migrateDocs)({ repoRoot, dryRun: false, migrationRunId: "test-migrate-live-001" });
|
|
107
|
+
(0, vitest_1.expect)(result.dryRun).toBe(false);
|
|
108
|
+
const migrated = result.results.filter((r) => r.classification === "migrated");
|
|
109
|
+
(0, vitest_1.expect)(migrated).toHaveLength(1);
|
|
110
|
+
(0, vitest_1.expect)(migrated[0].originalPath).toBe("scratch/notes.md");
|
|
111
|
+
(0, vitest_1.expect)(migrated[0].currentPath).toBe("docs/raw/notes.md");
|
|
112
|
+
// File should now exist in docs/raw/
|
|
113
|
+
(0, vitest_1.expect)((0, node_fs_1.existsSync)((0, node_path_1.join)(repoRoot, "docs/raw/notes.md"))).toBe(true);
|
|
114
|
+
// File should no longer exist at original location
|
|
115
|
+
(0, vitest_1.expect)((0, node_fs_1.existsSync)((0, node_path_1.join)(repoRoot, "scratch/notes.md"))).toBe(false);
|
|
116
|
+
// Provenance written
|
|
117
|
+
(0, vitest_1.expect)(result.provenancePath).not.toBeNull();
|
|
118
|
+
const provContent = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(repoRoot, result.provenancePath), "utf-8"));
|
|
119
|
+
(0, vitest_1.expect)(provContent).toHaveLength(1);
|
|
120
|
+
(0, vitest_1.expect)(provContent[0].originalPath).toBe("scratch/notes.md");
|
|
121
|
+
(0, vitest_1.expect)(provContent[0].currentPath).toBe("docs/raw/notes.md");
|
|
122
|
+
(0, vitest_1.expect)(provContent[0].migrationRunId).toBe("test-migrate-live-001");
|
|
123
|
+
(0, vitest_1.expect)(provContent[0].migratedAt).toBeTruthy();
|
|
124
|
+
});
|
|
125
|
+
(0, vitest_1.it)("handles filename collisions by uniquifying destination", () => {
|
|
126
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(repoRoot, "docs/raw"), { recursive: true });
|
|
127
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(repoRoot, "docs/raw/notes.md"), "# Existing", "utf-8");
|
|
128
|
+
(0, node_child_process_1.execFileSync)("git", ["add", "docs/raw/notes.md"], { cwd: repoRoot });
|
|
129
|
+
addTrackedFile(repoRoot, "scratch/notes.md", "# Incoming");
|
|
130
|
+
const result = (0, migrate_js_1.migrateDocs)({ repoRoot, dryRun: false, migrationRunId: "test-migrate-live-002" });
|
|
131
|
+
const migrated = result.results.filter((r) => r.classification === "migrated");
|
|
132
|
+
(0, vitest_1.expect)(migrated).toHaveLength(1);
|
|
133
|
+
(0, vitest_1.expect)(migrated[0].currentPath).toBe("docs/raw/notes-2.md");
|
|
134
|
+
(0, vitest_1.expect)((0, node_fs_1.existsSync)((0, node_path_1.join)(repoRoot, "docs/raw/notes-2.md"))).toBe(true);
|
|
135
|
+
});
|
|
136
|
+
});
|