@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,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runMapValidate = runMapValidate;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const loader_js_1 = require("../config/loader.js");
|
|
7
|
+
const defaults_js_1 = require("../ignore/defaults.js");
|
|
8
|
+
const atlas_js_1 = require("./atlas.js");
|
|
9
|
+
const SECRET_REGEXES = defaults_js_1.SECRET_PATTERNS.map((p) => new RegExp("^" + p.replace(/\./g, "\\.").replace(/\*/g, ".*") + "$"));
|
|
10
|
+
function isSecretPath(filePath) {
|
|
11
|
+
const parts = filePath.split("/");
|
|
12
|
+
const base = parts[parts.length - 1];
|
|
13
|
+
return SECRET_REGEXES.some((re) => re.test(base) || re.test(filePath));
|
|
14
|
+
}
|
|
15
|
+
function daysSince(iso) {
|
|
16
|
+
const then = new Date(iso).getTime();
|
|
17
|
+
return (Date.now() - then) / (1000 * 60 * 60 * 24);
|
|
18
|
+
}
|
|
19
|
+
function runMapValidate(repoRoot, staleThresholdDays, fixPath) {
|
|
20
|
+
const config = (0, loader_js_1.loadConfig)(repoRoot);
|
|
21
|
+
const outputPath = (0, node_path_1.resolve)(repoRoot, config.repo.sidecarOutputPath ?? ".polaris/map");
|
|
22
|
+
const confidenceThreshold = config.map.confidenceThreshold ?? 0.75;
|
|
23
|
+
const graphOutputPath = (config.graph?.outputPath ?? ".polaris/graph")
|
|
24
|
+
.replace(/\\/g, "/")
|
|
25
|
+
.replace(/\/+$/, "");
|
|
26
|
+
const isGraphArtifactPath = (filePath) => filePath === graphOutputPath || filePath.startsWith(`${graphOutputPath}/`);
|
|
27
|
+
const routes = (0, atlas_js_1.readFileRoutes)(outputPath);
|
|
28
|
+
const needsReview = (0, atlas_js_1.readNeedsReview)(outputPath);
|
|
29
|
+
const exemptions = (0, atlas_js_1.readExemptions)(outputPath);
|
|
30
|
+
// --fix: show current entry and remove if file is missing
|
|
31
|
+
if (fixPath) {
|
|
32
|
+
const entry = routes[fixPath] ?? needsReview[fixPath];
|
|
33
|
+
if (entry) {
|
|
34
|
+
console.log(`Current entry for ${fixPath}:`);
|
|
35
|
+
console.log(JSON.stringify(entry, null, 2));
|
|
36
|
+
if (!(0, node_fs_1.existsSync)((0, node_path_1.resolve)(repoRoot, fixPath))) {
|
|
37
|
+
if (routes[fixPath]) {
|
|
38
|
+
const updated = { ...routes };
|
|
39
|
+
delete updated[fixPath];
|
|
40
|
+
(0, atlas_js_1.writeFileRoutes)(outputPath, updated);
|
|
41
|
+
console.log(`Removed missing entry from routes: ${fixPath}`);
|
|
42
|
+
}
|
|
43
|
+
else if (needsReview[fixPath]) {
|
|
44
|
+
const updated = { ...needsReview };
|
|
45
|
+
delete updated[fixPath];
|
|
46
|
+
(0, atlas_js_1.writeNeedsReview)(outputPath, updated);
|
|
47
|
+
console.log(`Removed missing entry from needs-review: ${fixPath}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
console.log(`No entry found for: ${fixPath}`);
|
|
53
|
+
}
|
|
54
|
+
return { hasError: false, indexed: 0, stale: [], missing: [], sensitive: [], conflicted: [], needsReviewCount: 0, coveragePct: 0, invalidRoleOwner: [], identityIncomplete: [] };
|
|
55
|
+
}
|
|
56
|
+
const stale = [];
|
|
57
|
+
const missing = [];
|
|
58
|
+
const sensitive = [];
|
|
59
|
+
const conflicted = [];
|
|
60
|
+
const invalidRoleOwner = [];
|
|
61
|
+
const identityIncomplete = [];
|
|
62
|
+
// Route conflict detection: route → set of domains seen
|
|
63
|
+
const routeToDomains = new Map();
|
|
64
|
+
const routeToFiles = new Map();
|
|
65
|
+
// Combine routes and needsReview for validation checks
|
|
66
|
+
const allEntries = [...Object.entries(routes), ...Object.entries(needsReview)];
|
|
67
|
+
for (const [filePath, entry] of allEntries) {
|
|
68
|
+
// Graph output artifacts are generated runtime data and exempt from atlas validation.
|
|
69
|
+
if (isGraphArtifactPath(filePath)) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
// 2. Missing source files
|
|
73
|
+
if (!(0, node_fs_1.existsSync)((0, node_path_1.resolve)(repoRoot, filePath))) {
|
|
74
|
+
missing.push(filePath);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
// 1. Stale entries
|
|
78
|
+
if (daysSince(entry.last_updated) > staleThresholdDays) {
|
|
79
|
+
stale.push(filePath);
|
|
80
|
+
}
|
|
81
|
+
// 4. Sensitive pattern matches in atlas
|
|
82
|
+
if (isSecretPath(filePath)) {
|
|
83
|
+
sensitive.push(filePath);
|
|
84
|
+
}
|
|
85
|
+
// 6. role_owner validation: present and a known value
|
|
86
|
+
if (entry.role_owner !== undefined && !atlas_js_1.VALID_ROLE_OWNERS.includes(entry.role_owner)) {
|
|
87
|
+
invalidRoleOwner.push(filePath);
|
|
88
|
+
}
|
|
89
|
+
// 7. identity completeness: instructionFile and role_owner are required
|
|
90
|
+
if (entry.instructionFile === undefined || entry.role_owner === undefined) {
|
|
91
|
+
identityIncomplete.push(filePath);
|
|
92
|
+
}
|
|
93
|
+
// 5. Conflicted routes: same route claimed by different domains
|
|
94
|
+
const domains = routeToDomains.get(entry.route) ?? new Set();
|
|
95
|
+
domains.add(entry.domain);
|
|
96
|
+
routeToDomains.set(entry.route, domains);
|
|
97
|
+
const files = routeToFiles.get(entry.route) ?? [];
|
|
98
|
+
files.push(filePath);
|
|
99
|
+
routeToFiles.set(entry.route, files);
|
|
100
|
+
}
|
|
101
|
+
for (const [route, domains] of routeToDomains) {
|
|
102
|
+
if (domains.size > 1) {
|
|
103
|
+
conflicted.push(...(routeToFiles.get(route) ?? []));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// 3. Low-confidence (needs-review count)
|
|
107
|
+
const needsReviewCount = Object.keys(needsReview).filter((filePath) => !isGraphArtifactPath(filePath)).length;
|
|
108
|
+
// Coverage: indexed / (indexed + needs-review + tracked-not-indexed)
|
|
109
|
+
// Only count non-graph routes for indexedCount, excluding missing files.
|
|
110
|
+
const indexedCount = Object.keys(routes).filter((filePath) => !isGraphArtifactPath(filePath)).length
|
|
111
|
+
- missing.filter((filePath) => routes[filePath] && !isGraphArtifactPath(filePath)).length;
|
|
112
|
+
const trackedCount = Object.keys(exemptions).filter((filePath) => !isGraphArtifactPath(filePath)).length;
|
|
113
|
+
const total = indexedCount + needsReviewCount + trackedCount;
|
|
114
|
+
const coveragePct = total > 0 ? Math.round((indexedCount / total) * 100 * 10) / 10 : 0;
|
|
115
|
+
// Output
|
|
116
|
+
const successMark = "✓";
|
|
117
|
+
const okMark = "✓";
|
|
118
|
+
const warnMark = "⚠";
|
|
119
|
+
const errMark = "✗";
|
|
120
|
+
console.log(`${okMark} ${indexedCount} indexed files validated`);
|
|
121
|
+
if (stale.length > 0) {
|
|
122
|
+
console.log(`${warnMark} ${stale.length} stale entries (older than ${staleThresholdDays}d): ${stale.slice(0, 3).join(", ")}${stale.length > 3 ? "..." : ""}`);
|
|
123
|
+
}
|
|
124
|
+
for (const f of missing) {
|
|
125
|
+
console.log(`${errMark} 1 missing source file: ${f} (was indexed, file deleted)`);
|
|
126
|
+
}
|
|
127
|
+
if (sensitive.length > 0) {
|
|
128
|
+
console.log(`${errMark} HIGH: ${sensitive.length} sensitive file(s) in atlas: ${sensitive.join(", ")}`);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
console.log(`${successMark} HIGH: 0 sensitive files in atlas`);
|
|
132
|
+
}
|
|
133
|
+
if (needsReviewCount > 0) {
|
|
134
|
+
console.log(`${warnMark} ${needsReviewCount} files in needs-review queue`);
|
|
135
|
+
}
|
|
136
|
+
if (conflicted.length > 0) {
|
|
137
|
+
console.log(`${warnMark} ${conflicted.length} conflicted route entries: ${conflicted.slice(0, 3).join(", ")}${conflicted.length > 3 ? "..." : ""}`);
|
|
138
|
+
}
|
|
139
|
+
if (invalidRoleOwner.length > 0) {
|
|
140
|
+
console.log(`${errMark} ${invalidRoleOwner.length} entries with invalid role_owner value: ${invalidRoleOwner.slice(0, 3).join(", ")}${invalidRoleOwner.length > 3 ? "..." : ""}`);
|
|
141
|
+
}
|
|
142
|
+
if (identityIncomplete.length > 0) {
|
|
143
|
+
console.log(`${warnMark} ${identityIncomplete.length} routes missing identity fields (instructionFile or role_owner): ${identityIncomplete.slice(0, 3).join(", ")}${identityIncomplete.length > 3 ? "..." : ""}`);
|
|
144
|
+
}
|
|
145
|
+
// Low-confidence entries warning
|
|
146
|
+
const lowConfidence = Object.entries(routes).filter(([filePath, e]) => !isGraphArtifactPath(filePath) && e.confidence < confidenceThreshold);
|
|
147
|
+
if (lowConfidence.length > 0) {
|
|
148
|
+
console.log(`${warnMark} ${lowConfidence.length} indexed entries below confidence threshold`);
|
|
149
|
+
}
|
|
150
|
+
console.log(`\nCoverage: ${coveragePct}% (${indexedCount} / ${total} non-ignored files)`);
|
|
151
|
+
const hasError = missing.length > 0 || sensitive.length > 0 || invalidRoleOwner.length > 0;
|
|
152
|
+
return { hasError, indexed: indexedCount, stale, missing, sensitive, conflicted, needsReviewCount, coveragePct, invalidRoleOwner, identityIncomplete };
|
|
153
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Tests for map validation including role_owner field (POL-231).
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const vitest_1 = require("vitest");
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_path_1 = require("node:path");
|
|
9
|
+
const node_os_1 = require("node:os");
|
|
10
|
+
const validate_js_1 = require("./validate.js");
|
|
11
|
+
function makeTestDir() {
|
|
12
|
+
const dir = (0, node_path_1.join)((0, node_os_1.tmpdir)(), `polaris-validate-test-${Date.now()}-${Math.random().toString(16).slice(2)}`);
|
|
13
|
+
(0, node_fs_1.mkdirSync)(dir, { recursive: true });
|
|
14
|
+
return dir;
|
|
15
|
+
}
|
|
16
|
+
function writeRoutes(dir, routes) {
|
|
17
|
+
const mapDir = (0, node_path_1.join)(dir, ".polaris", "map");
|
|
18
|
+
(0, node_fs_1.mkdirSync)(mapDir, { recursive: true });
|
|
19
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(mapDir, "file-routes.json"), JSON.stringify(routes), "utf-8");
|
|
20
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(mapDir, "needs-review.json"), JSON.stringify({}), "utf-8");
|
|
21
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(mapDir, "exemptions.json"), JSON.stringify({}), "utf-8");
|
|
22
|
+
}
|
|
23
|
+
function makeEntry(overrides = {}) {
|
|
24
|
+
return {
|
|
25
|
+
domain: "cli",
|
|
26
|
+
route: "src/cli",
|
|
27
|
+
taskchain: "polaris-cli",
|
|
28
|
+
confidence: 0.9,
|
|
29
|
+
classification: "indexed",
|
|
30
|
+
last_updated: new Date().toISOString(),
|
|
31
|
+
updated_by: "test",
|
|
32
|
+
tags: ["cli"],
|
|
33
|
+
...overrides,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
(0, vitest_1.describe)("runMapValidate role_owner validation", () => {
|
|
37
|
+
let testDir;
|
|
38
|
+
(0, vitest_1.beforeEach)(() => {
|
|
39
|
+
testDir = makeTestDir();
|
|
40
|
+
});
|
|
41
|
+
(0, vitest_1.afterEach)(() => {
|
|
42
|
+
(0, node_fs_1.rmSync)(testDir, { recursive: true, force: true });
|
|
43
|
+
});
|
|
44
|
+
(0, vitest_1.it)("passes validation when role_owner is a valid value", () => {
|
|
45
|
+
const entryPath = (0, node_path_1.join)(testDir, "src/cli/index.ts");
|
|
46
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(testDir, "src/cli"), { recursive: true });
|
|
47
|
+
(0, node_fs_1.writeFileSync)(entryPath, "");
|
|
48
|
+
writeRoutes(testDir, {
|
|
49
|
+
"src/cli/index.ts": makeEntry({ role_owner: "worker" }),
|
|
50
|
+
});
|
|
51
|
+
const result = (0, validate_js_1.runMapValidate)(testDir, 9999);
|
|
52
|
+
(0, vitest_1.expect)(result.invalidRoleOwner).toHaveLength(0);
|
|
53
|
+
(0, vitest_1.expect)(result.hasError).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
(0, vitest_1.it)("fails validation when role_owner is an unknown value", () => {
|
|
56
|
+
const entryPath = (0, node_path_1.join)(testDir, "src/cli/index.ts");
|
|
57
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(testDir, "src/cli"), { recursive: true });
|
|
58
|
+
(0, node_fs_1.writeFileSync)(entryPath, "");
|
|
59
|
+
writeRoutes(testDir, {
|
|
60
|
+
"src/cli/index.ts": makeEntry({ role_owner: "unknown-role" }),
|
|
61
|
+
});
|
|
62
|
+
const result = (0, validate_js_1.runMapValidate)(testDir, 9999);
|
|
63
|
+
(0, vitest_1.expect)(result.invalidRoleOwner).toContain("src/cli/index.ts");
|
|
64
|
+
(0, vitest_1.expect)(result.hasError).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
(0, vitest_1.it)("passes validation when role_owner is absent (optional field)", () => {
|
|
67
|
+
const entryPath = (0, node_path_1.join)(testDir, "src/cli/index.ts");
|
|
68
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(testDir, "src/cli"), { recursive: true });
|
|
69
|
+
(0, node_fs_1.writeFileSync)(entryPath, "");
|
|
70
|
+
writeRoutes(testDir, {
|
|
71
|
+
"src/cli/index.ts": makeEntry(), // no role_owner
|
|
72
|
+
});
|
|
73
|
+
const result = (0, validate_js_1.runMapValidate)(testDir, 9999);
|
|
74
|
+
(0, vitest_1.expect)(result.invalidRoleOwner).toHaveLength(0);
|
|
75
|
+
});
|
|
76
|
+
(0, vitest_1.it)("accepts all valid role_owner values", () => {
|
|
77
|
+
const validRoles = ["worker", "foreman", "analyst", "librarian", "any"];
|
|
78
|
+
for (const role of validRoles) {
|
|
79
|
+
const entryPath = (0, node_path_1.join)(testDir, "src/cli/index.ts");
|
|
80
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(testDir, "src/cli"), { recursive: true });
|
|
81
|
+
(0, node_fs_1.writeFileSync)(entryPath, "");
|
|
82
|
+
writeRoutes(testDir, {
|
|
83
|
+
"src/cli/index.ts": makeEntry({ role_owner: role }),
|
|
84
|
+
});
|
|
85
|
+
const result = (0, validate_js_1.runMapValidate)(testDir, 9999);
|
|
86
|
+
(0, vitest_1.expect)(result.invalidRoleOwner).toHaveLength(0);
|
|
87
|
+
(0, vitest_1.expect)(result.hasError).toBe(false);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runWelfareCheck = runWelfareCheck;
|
|
4
|
+
exports.printWelfareCheckReport = printWelfareCheckReport;
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const loader_js_1 = require("../config/loader.js");
|
|
7
|
+
const atlas_js_1 = require("./atlas.js");
|
|
8
|
+
function daysSince(iso) {
|
|
9
|
+
const then = new Date(iso).getTime();
|
|
10
|
+
return (Date.now() - then) / (1000 * 60 * 60 * 24);
|
|
11
|
+
}
|
|
12
|
+
function isIdentityComplete(entry) {
|
|
13
|
+
return entry.instructionFile !== undefined && entry.role_owner !== undefined;
|
|
14
|
+
}
|
|
15
|
+
function assessRouteHealth(entry, staleThresholdDays) {
|
|
16
|
+
const daysSinceUpdate = daysSince(entry.last_updated);
|
|
17
|
+
const identityComplete = isIdentityComplete(entry);
|
|
18
|
+
if (daysSinceUpdate > staleThresholdDays) {
|
|
19
|
+
return "stale";
|
|
20
|
+
}
|
|
21
|
+
if (!identityComplete) {
|
|
22
|
+
return "known-issues";
|
|
23
|
+
}
|
|
24
|
+
return "healthy";
|
|
25
|
+
}
|
|
26
|
+
function determineActionRequired(identityComplete, healthState) {
|
|
27
|
+
if (identityComplete && healthState === "healthy") {
|
|
28
|
+
return "none";
|
|
29
|
+
}
|
|
30
|
+
// Note: !identityComplete && healthState === "healthy" is unreachable because
|
|
31
|
+
// assessRouteHealth returns "known-issues" whenever identityComplete is false
|
|
32
|
+
if (identityComplete && healthState !== "healthy") {
|
|
33
|
+
return "review-health";
|
|
34
|
+
}
|
|
35
|
+
return "review-both";
|
|
36
|
+
}
|
|
37
|
+
function runWelfareCheck(repoRoot, routePath) {
|
|
38
|
+
const config = (0, loader_js_1.loadConfig)(repoRoot);
|
|
39
|
+
const outputPath = (0, node_path_1.resolve)(repoRoot, config.repo.sidecarOutputPath ?? ".polaris/map");
|
|
40
|
+
const staleThresholdDays = 30;
|
|
41
|
+
const routes = (0, atlas_js_1.readFileRoutes)(outputPath);
|
|
42
|
+
const results = [];
|
|
43
|
+
for (const [filePath, entry] of Object.entries(routes)) {
|
|
44
|
+
if (routePath && !filePath.startsWith(routePath)) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const identityComplete = isIdentityComplete(entry);
|
|
48
|
+
const healthState = assessRouteHealth(entry, staleThresholdDays);
|
|
49
|
+
const actionRequired = determineActionRequired(identityComplete, healthState);
|
|
50
|
+
results.push({
|
|
51
|
+
route: filePath,
|
|
52
|
+
identityComplete,
|
|
53
|
+
healthState,
|
|
54
|
+
actionRequired,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
const healthyRoutes = results.filter((r) => r.actionRequired === "none").length;
|
|
58
|
+
const needsReview = results.filter((r) => r.actionRequired !== "none").length;
|
|
59
|
+
return {
|
|
60
|
+
routes: results,
|
|
61
|
+
totalRoutes: results.length,
|
|
62
|
+
healthyRoutes,
|
|
63
|
+
needsReview,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function printWelfareCheckReport(report) {
|
|
67
|
+
console.log(`Route Welfare Check Report`);
|
|
68
|
+
console.log(`========================`);
|
|
69
|
+
console.log(`Total routes: ${report.totalRoutes}`);
|
|
70
|
+
console.log(`Healthy: ${report.healthyRoutes}`);
|
|
71
|
+
console.log(`Needs review: ${report.needsReview}`);
|
|
72
|
+
console.log();
|
|
73
|
+
if (report.routes.length === 0) {
|
|
74
|
+
console.log("No routes found.");
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
for (const result of report.routes) {
|
|
78
|
+
const identityStatus = result.identityComplete ? "✓" : "✗";
|
|
79
|
+
const actionSymbol = result.actionRequired === "none" ? "✓" : "⚠";
|
|
80
|
+
console.log(`${actionSymbol} ${result.route}`);
|
|
81
|
+
console.log(` Identity complete: ${identityStatus}`);
|
|
82
|
+
console.log(` Health state: ${result.healthState}`);
|
|
83
|
+
console.log(` Action required: ${result.actionRequired}`);
|
|
84
|
+
console.log();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InvokeError = void 0;
|
|
4
|
+
exports.invokePolarisJson = invokePolarisJson;
|
|
5
|
+
const node_child_process_1 = require("node:child_process");
|
|
6
|
+
const node_path_1 = require("node:path");
|
|
7
|
+
class InvokeError extends Error {
|
|
8
|
+
stderr;
|
|
9
|
+
constructor(message, stderr) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.stderr = stderr;
|
|
12
|
+
this.name = "InvokeError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.InvokeError = InvokeError;
|
|
16
|
+
function invokePolarisJson(repoRoot, args) {
|
|
17
|
+
const result = (0, node_child_process_1.spawnSync)(process.execPath, [(0, node_path_1.join)("dist", "cli", "index.js"), ...args], {
|
|
18
|
+
cwd: repoRoot,
|
|
19
|
+
encoding: "utf-8",
|
|
20
|
+
timeout: 10_000,
|
|
21
|
+
shell: false,
|
|
22
|
+
});
|
|
23
|
+
if (result.error) {
|
|
24
|
+
throw new InvokeError(`Subprocess error: ${result.error.message}`, "");
|
|
25
|
+
}
|
|
26
|
+
if (result.status !== 0) {
|
|
27
|
+
throw new InvokeError(`polaris exited with status ${result.status ?? "null"}`, result.stderr ?? "");
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(result.stdout);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
throw new InvokeError("Failed to parse subprocess JSON output", result.stderr ?? "");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.redact = redact;
|
|
4
|
+
const SENSITIVE_PATTERN = /secret|token|key|password|credential/i;
|
|
5
|
+
const MAX_ARRAY_LENGTH = 50;
|
|
6
|
+
function redact(value) {
|
|
7
|
+
if (Array.isArray(value)) {
|
|
8
|
+
const truncated = value.slice(0, MAX_ARRAY_LENGTH).map(redact);
|
|
9
|
+
return truncated;
|
|
10
|
+
}
|
|
11
|
+
if (value !== null && typeof value === "object") {
|
|
12
|
+
const result = {};
|
|
13
|
+
for (const [k, v] of Object.entries(value)) {
|
|
14
|
+
if (SENSITIVE_PATTERN.test(k)) {
|
|
15
|
+
result[k] = "[redacted]";
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
result[k] = redact(v);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Unit tests for src/mcp/lib/redact.ts
|
|
4
|
+
*
|
|
5
|
+
* Covers:
|
|
6
|
+
* - strips keys matching sensitive pattern (secret, token, key, password, credential, API_KEY, etc.)
|
|
7
|
+
* - does NOT strip non-sensitive keys
|
|
8
|
+
* - recursively redacts nested objects
|
|
9
|
+
* - truncates arrays > 50 items to 50
|
|
10
|
+
* - returns non-sensitive primitives unchanged
|
|
11
|
+
* - replaces sensitive key values with "[redacted]"
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
const vitest_1 = require("vitest");
|
|
15
|
+
const redact_js_1 = require("./redact.js");
|
|
16
|
+
(0, vitest_1.describe)("redact()", () => {
|
|
17
|
+
(0, vitest_1.it)("replaces values of sensitive keys with [redacted]", () => {
|
|
18
|
+
const result = (0, redact_js_1.redact)({ secret: "mysecret", label: "visible" });
|
|
19
|
+
(0, vitest_1.expect)(result["secret"]).toBe("[redacted]");
|
|
20
|
+
(0, vitest_1.expect)(result["label"]).toBe("visible");
|
|
21
|
+
});
|
|
22
|
+
(0, vitest_1.it)("redacts keys matching: token", () => {
|
|
23
|
+
const result = (0, redact_js_1.redact)({ token: "abc123" });
|
|
24
|
+
(0, vitest_1.expect)(result["token"]).toBe("[redacted]");
|
|
25
|
+
});
|
|
26
|
+
(0, vitest_1.it)("redacts keys matching: key", () => {
|
|
27
|
+
const result = (0, redact_js_1.redact)({ key: "somevalue" });
|
|
28
|
+
(0, vitest_1.expect)(result["key"]).toBe("[redacted]");
|
|
29
|
+
});
|
|
30
|
+
(0, vitest_1.it)("redacts keys matching: password", () => {
|
|
31
|
+
const result = (0, redact_js_1.redact)({ password: "hunter2" });
|
|
32
|
+
(0, vitest_1.expect)(result["password"]).toBe("[redacted]");
|
|
33
|
+
});
|
|
34
|
+
(0, vitest_1.it)("redacts keys matching: credential", () => {
|
|
35
|
+
const result = (0, redact_js_1.redact)({ credential: "credvalue" });
|
|
36
|
+
(0, vitest_1.expect)(result["credential"]).toBe("[redacted]");
|
|
37
|
+
});
|
|
38
|
+
(0, vitest_1.it)("redacts keys matching: API_KEY (case-insensitive)", () => {
|
|
39
|
+
const result = (0, redact_js_1.redact)({ API_KEY: "myapikey" });
|
|
40
|
+
(0, vitest_1.expect)(result["API_KEY"]).toBe("[redacted]");
|
|
41
|
+
});
|
|
42
|
+
(0, vitest_1.it)("redacts keys matching: secretValue (substring match)", () => {
|
|
43
|
+
const result = (0, redact_js_1.redact)({ secretValue: "hidden" });
|
|
44
|
+
(0, vitest_1.expect)(result["secretValue"]).toBe("[redacted]");
|
|
45
|
+
});
|
|
46
|
+
(0, vitest_1.it)("redacts keys matching: accessToken (substring match)", () => {
|
|
47
|
+
const result = (0, redact_js_1.redact)({ accessToken: "tok_abc" });
|
|
48
|
+
(0, vitest_1.expect)(result["accessToken"]).toBe("[redacted]");
|
|
49
|
+
});
|
|
50
|
+
(0, vitest_1.it)("does NOT redact non-sensitive keys", () => {
|
|
51
|
+
const result = (0, redact_js_1.redact)({
|
|
52
|
+
name: "Alice",
|
|
53
|
+
status: "active",
|
|
54
|
+
count: 42,
|
|
55
|
+
enabled: true,
|
|
56
|
+
});
|
|
57
|
+
(0, vitest_1.expect)(result["name"]).toBe("Alice");
|
|
58
|
+
(0, vitest_1.expect)(result["status"]).toBe("active");
|
|
59
|
+
(0, vitest_1.expect)(result["count"]).toBe(42);
|
|
60
|
+
(0, vitest_1.expect)(result["enabled"]).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
(0, vitest_1.it)("recursively redacts nested objects", () => {
|
|
63
|
+
const input = {
|
|
64
|
+
outer: "visible",
|
|
65
|
+
nested: {
|
|
66
|
+
token: "should-be-redacted",
|
|
67
|
+
label: "still-visible",
|
|
68
|
+
deeper: {
|
|
69
|
+
password: "also-redacted",
|
|
70
|
+
info: "public",
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
const result = (0, redact_js_1.redact)(input);
|
|
75
|
+
(0, vitest_1.expect)(result.outer).toBe("visible");
|
|
76
|
+
(0, vitest_1.expect)(result.nested.token).toBe("[redacted]");
|
|
77
|
+
(0, vitest_1.expect)(result.nested.label).toBe("still-visible");
|
|
78
|
+
(0, vitest_1.expect)(result.nested.deeper.password).toBe("[redacted]");
|
|
79
|
+
(0, vitest_1.expect)(result.nested.deeper.info).toBe("public");
|
|
80
|
+
});
|
|
81
|
+
(0, vitest_1.it)("truncates arrays longer than 50 items to 50 items", () => {
|
|
82
|
+
const bigArray = Array.from({ length: 100 }, (_, i) => i);
|
|
83
|
+
const result = (0, redact_js_1.redact)(bigArray);
|
|
84
|
+
(0, vitest_1.expect)(result).toHaveLength(50);
|
|
85
|
+
(0, vitest_1.expect)(result[0]).toBe(0);
|
|
86
|
+
(0, vitest_1.expect)(result[49]).toBe(49);
|
|
87
|
+
});
|
|
88
|
+
(0, vitest_1.it)("does not truncate arrays with 50 or fewer items", () => {
|
|
89
|
+
const arr = Array.from({ length: 50 }, (_, i) => i);
|
|
90
|
+
const result = (0, redact_js_1.redact)(arr);
|
|
91
|
+
(0, vitest_1.expect)(result).toHaveLength(50);
|
|
92
|
+
});
|
|
93
|
+
(0, vitest_1.it)("recursively redacts items within arrays", () => {
|
|
94
|
+
const arr = [{ token: "secret-tok" }, { label: "visible" }];
|
|
95
|
+
const result = (0, redact_js_1.redact)(arr);
|
|
96
|
+
(0, vitest_1.expect)(result[0]?.["token"]).toBe("[redacted]");
|
|
97
|
+
(0, vitest_1.expect)(result[1]?.["label"]).toBe("visible");
|
|
98
|
+
});
|
|
99
|
+
(0, vitest_1.it)("returns primitive numbers unchanged", () => {
|
|
100
|
+
(0, vitest_1.expect)((0, redact_js_1.redact)(42)).toBe(42);
|
|
101
|
+
});
|
|
102
|
+
(0, vitest_1.it)("returns primitive strings unchanged", () => {
|
|
103
|
+
(0, vitest_1.expect)((0, redact_js_1.redact)("hello")).toBe("hello");
|
|
104
|
+
});
|
|
105
|
+
(0, vitest_1.it)("returns booleans unchanged", () => {
|
|
106
|
+
(0, vitest_1.expect)((0, redact_js_1.redact)(true)).toBe(true);
|
|
107
|
+
(0, vitest_1.expect)((0, redact_js_1.redact)(false)).toBe(false);
|
|
108
|
+
});
|
|
109
|
+
(0, vitest_1.it)("returns null unchanged", () => {
|
|
110
|
+
(0, vitest_1.expect)((0, redact_js_1.redact)(null)).toBeNull();
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveRepoRoot = resolveRepoRoot;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
function hasPackageJson(dir) {
|
|
7
|
+
const pkgPath = (0, node_path_1.join)(dir, "package.json");
|
|
8
|
+
if (!(0, node_fs_1.existsSync)(pkgPath))
|
|
9
|
+
return false;
|
|
10
|
+
try {
|
|
11
|
+
const pkg = JSON.parse((0, node_fs_1.readFileSync)(pkgPath, "utf-8"));
|
|
12
|
+
return pkg.name === "polaris";
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function walkUp(startDir) {
|
|
19
|
+
let current = (0, node_path_1.resolve)(startDir);
|
|
20
|
+
for (let i = 0; i < 20; i++) {
|
|
21
|
+
if (hasPackageJson(current))
|
|
22
|
+
return current;
|
|
23
|
+
const parent = (0, node_path_1.dirname)(current);
|
|
24
|
+
if (parent === current)
|
|
25
|
+
return null;
|
|
26
|
+
current = parent;
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
function resolveRepoRoot() {
|
|
31
|
+
if (process.env["POLARIS_ROOT"]) {
|
|
32
|
+
return (0, node_path_1.resolve)(process.env["POLARIS_ROOT"]);
|
|
33
|
+
}
|
|
34
|
+
const fromCwd = walkUp(process.cwd());
|
|
35
|
+
if (fromCwd)
|
|
36
|
+
return fromCwd;
|
|
37
|
+
const fromModule = walkUp((0, node_path_1.dirname)(__filename));
|
|
38
|
+
if (fromModule)
|
|
39
|
+
return fromModule;
|
|
40
|
+
throw new Error("Cannot locate Polaris repo root. Set POLARIS_ROOT env var or run from within the repo.");
|
|
41
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Unit tests for src/mcp/lib/root.ts
|
|
4
|
+
*
|
|
5
|
+
* Covers:
|
|
6
|
+
* - returns POLARIS_ROOT env var (resolve()d) when set
|
|
7
|
+
* - walks up from cwd and finds repo root when package.json has name=polaris
|
|
8
|
+
* - throws when no root found and POLARIS_ROOT not set
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
const vitest_1 = require("vitest");
|
|
12
|
+
const node_fs_1 = require("node:fs");
|
|
13
|
+
const node_path_1 = require("node:path");
|
|
14
|
+
const node_os_1 = require("node:os");
|
|
15
|
+
// Keep a reference to the original env and cwd
|
|
16
|
+
const originalEnv = process.env["POLARIS_ROOT"];
|
|
17
|
+
const originalCwd = process.cwd();
|
|
18
|
+
function makeTempDir() {
|
|
19
|
+
const dir = (0, node_path_1.join)((0, node_os_1.tmpdir)(), `polaris-root-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
20
|
+
(0, node_fs_1.mkdirSync)(dir, { recursive: true });
|
|
21
|
+
return dir;
|
|
22
|
+
}
|
|
23
|
+
(0, vitest_1.describe)("resolveRepoRoot()", () => {
|
|
24
|
+
let tempDir;
|
|
25
|
+
(0, vitest_1.beforeEach)(() => {
|
|
26
|
+
tempDir = makeTempDir();
|
|
27
|
+
// Ensure POLARIS_ROOT is unset before each test (restored per-test)
|
|
28
|
+
delete process.env["POLARIS_ROOT"];
|
|
29
|
+
});
|
|
30
|
+
(0, vitest_1.afterEach)(() => {
|
|
31
|
+
// Restore POLARIS_ROOT to whatever it was originally
|
|
32
|
+
if (originalEnv !== undefined) {
|
|
33
|
+
process.env["POLARIS_ROOT"] = originalEnv;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
delete process.env["POLARIS_ROOT"];
|
|
37
|
+
}
|
|
38
|
+
// Restore cwd
|
|
39
|
+
process.chdir(originalCwd);
|
|
40
|
+
(0, node_fs_1.rmSync)(tempDir, { recursive: true, force: true });
|
|
41
|
+
});
|
|
42
|
+
(0, vitest_1.it)("returns the resolved POLARIS_ROOT env var when set", async () => {
|
|
43
|
+
process.env["POLARIS_ROOT"] = tempDir;
|
|
44
|
+
// Dynamic import so we get a fresh call each time (module is already cached,
|
|
45
|
+
// but the function re-reads process.env at call time)
|
|
46
|
+
const { resolveRepoRoot } = await import("./root.js");
|
|
47
|
+
const result = resolveRepoRoot();
|
|
48
|
+
// Compare real paths to handle macOS symlinked temp directories
|
|
49
|
+
(0, vitest_1.expect)((0, node_fs_1.realpathSync)(result)).toBe((0, node_fs_1.realpathSync)((0, node_path_1.resolve)(tempDir)));
|
|
50
|
+
});
|
|
51
|
+
(0, vitest_1.it)("resolves a relative POLARIS_ROOT path to an absolute path", async () => {
|
|
52
|
+
// Create a real directory for relative path test (realpathSync requires path to exist)
|
|
53
|
+
const relativeDir = (0, node_path_1.join)(tempDir, "relative-path");
|
|
54
|
+
(0, node_fs_1.mkdirSync)(relativeDir, { recursive: true });
|
|
55
|
+
// Change to tempDir so relative path resolves correctly
|
|
56
|
+
process.chdir(tempDir);
|
|
57
|
+
process.env["POLARIS_ROOT"] = "./relative-path";
|
|
58
|
+
const { resolveRepoRoot } = await import("./root.js");
|
|
59
|
+
const result = resolveRepoRoot();
|
|
60
|
+
// Compare real paths to handle macOS symlinked temp directories
|
|
61
|
+
(0, vitest_1.expect)((0, node_fs_1.realpathSync)(result)).toBe((0, node_fs_1.realpathSync)(relativeDir));
|
|
62
|
+
});
|
|
63
|
+
(0, vitest_1.it)("walks up from cwd and finds the repo root when package.json has name=polaris", async () => {
|
|
64
|
+
// Write a valid polaris package.json into tempDir
|
|
65
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(tempDir, "package.json"), JSON.stringify({ name: "polaris" }));
|
|
66
|
+
// Create a subdirectory and chdir into it so walkUp must climb to tempDir
|
|
67
|
+
const subDir = (0, node_path_1.join)(tempDir, "sub", "deep");
|
|
68
|
+
(0, node_fs_1.mkdirSync)(subDir, { recursive: true });
|
|
69
|
+
process.chdir(subDir);
|
|
70
|
+
delete process.env["POLARIS_ROOT"];
|
|
71
|
+
const { resolveRepoRoot } = await import("./root.js");
|
|
72
|
+
const result = resolveRepoRoot();
|
|
73
|
+
// Compare real paths to handle macOS symlinked temp directories
|
|
74
|
+
(0, vitest_1.expect)((0, node_fs_1.realpathSync)(result)).toBe((0, node_fs_1.realpathSync)(tempDir));
|
|
75
|
+
});
|
|
76
|
+
(0, vitest_1.it)("throws when no polaris root can be found and POLARIS_ROOT is not set", async () => {
|
|
77
|
+
// tempDir has no package.json with name=polaris; chdir into it
|
|
78
|
+
process.chdir(tempDir);
|
|
79
|
+
delete process.env["POLARIS_ROOT"];
|
|
80
|
+
const { resolveRepoRoot } = await import("./root.js");
|
|
81
|
+
// The function walks up until it hits the filesystem root and throws
|
|
82
|
+
// NOTE: this may succeed if a parent directory happens to contain a polaris package.json;
|
|
83
|
+
// we accept that edge case by checking it either returns a string or throws the expected error.
|
|
84
|
+
try {
|
|
85
|
+
const result = resolveRepoRoot();
|
|
86
|
+
// If it didn't throw, it must have found a root — just verify it's a string
|
|
87
|
+
(0, vitest_1.expect)(typeof result).toBe("string");
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
(0, vitest_1.expect)(err).toBeInstanceOf(Error);
|
|
91
|
+
(0, vitest_1.expect)(err.message).toMatch(/Cannot locate Polaris repo root/);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
(0, vitest_1.it)("throws with helpful message mentioning POLARIS_ROOT env var", async () => {
|
|
95
|
+
// Use a deeply nested temp dir with no package.json to force failure
|
|
96
|
+
// We mock cwd to a path that has no polaris root
|
|
97
|
+
const isolatedDir = (0, node_path_1.join)((0, node_os_1.tmpdir)(), `isolated-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
98
|
+
(0, node_fs_1.mkdirSync)(isolatedDir, { recursive: true });
|
|
99
|
+
process.chdir(isolatedDir);
|
|
100
|
+
delete process.env["POLARIS_ROOT"];
|
|
101
|
+
const { resolveRepoRoot } = await import("./root.js");
|
|
102
|
+
try {
|
|
103
|
+
resolveRepoRoot();
|
|
104
|
+
// If it finds something (because a parent has polaris package.json), that's also OK
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
(0, vitest_1.expect)(err).toBeInstanceOf(Error);
|
|
108
|
+
(0, vitest_1.expect)(err.message).toContain("POLARIS_ROOT");
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
(0, node_fs_1.rmSync)(isolatedDir, { recursive: true, force: true });
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
});
|