@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,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const validator_js_1 = require("./validator.js");
|
|
5
|
+
(0, vitest_1.describe)("validateConfig — providers", () => {
|
|
6
|
+
(0, vitest_1.it)("accepts config with no providers field", () => {
|
|
7
|
+
const result = (0, validator_js_1.validateConfig)({ version: "1.0" });
|
|
8
|
+
(0, vitest_1.expect)(result.valid).toBe(true);
|
|
9
|
+
(0, vitest_1.expect)(result.errors).toHaveLength(0);
|
|
10
|
+
});
|
|
11
|
+
(0, vitest_1.it)("accepts empty providers object", () => {
|
|
12
|
+
const result = (0, validator_js_1.validateConfig)({ providers: {} });
|
|
13
|
+
(0, vitest_1.expect)(result.valid).toBe(true);
|
|
14
|
+
(0, vitest_1.expect)(result.errors).toHaveLength(0);
|
|
15
|
+
});
|
|
16
|
+
(0, vitest_1.it)("accepts valid providers.repoAnalysis with preferred and fallback", () => {
|
|
17
|
+
const result = (0, validator_js_1.validateConfig)({
|
|
18
|
+
providers: {
|
|
19
|
+
repoAnalysis: {
|
|
20
|
+
preferred: "gitnexus",
|
|
21
|
+
fallback: ["polaris-map", "ripgrep"],
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
(0, vitest_1.expect)(result.valid).toBe(true);
|
|
26
|
+
(0, vitest_1.expect)(result.errors).toHaveLength(0);
|
|
27
|
+
});
|
|
28
|
+
(0, vitest_1.it)("accepts providers.repoAnalysis with only preferred", () => {
|
|
29
|
+
const result = (0, validator_js_1.validateConfig)({
|
|
30
|
+
providers: { repoAnalysis: { preferred: "gitnexus" } },
|
|
31
|
+
});
|
|
32
|
+
(0, vitest_1.expect)(result.valid).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
(0, vitest_1.it)("accepts providers.repoAnalysis with only fallback", () => {
|
|
35
|
+
const result = (0, validator_js_1.validateConfig)({
|
|
36
|
+
providers: { repoAnalysis: { fallback: ["polaris-map"] } },
|
|
37
|
+
});
|
|
38
|
+
(0, vitest_1.expect)(result.valid).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
(0, vitest_1.it)("rejects providers that is not an object", () => {
|
|
41
|
+
const result = (0, validator_js_1.validateConfig)({ providers: "gitnexus" });
|
|
42
|
+
(0, vitest_1.expect)(result.valid).toBe(false);
|
|
43
|
+
(0, vitest_1.expect)(result.errors).toContain("providers must be an object");
|
|
44
|
+
});
|
|
45
|
+
(0, vitest_1.it)("rejects providers.repoAnalysis that is not an object", () => {
|
|
46
|
+
const result = (0, validator_js_1.validateConfig)({ providers: { repoAnalysis: 42 } });
|
|
47
|
+
(0, vitest_1.expect)(result.valid).toBe(false);
|
|
48
|
+
(0, vitest_1.expect)(result.errors).toContain("providers.repoAnalysis must be an object");
|
|
49
|
+
});
|
|
50
|
+
(0, vitest_1.it)("rejects providers.repoAnalysis.preferred that is not a string", () => {
|
|
51
|
+
const result = (0, validator_js_1.validateConfig)({
|
|
52
|
+
providers: { repoAnalysis: { preferred: 123 } },
|
|
53
|
+
});
|
|
54
|
+
(0, vitest_1.expect)(result.valid).toBe(false);
|
|
55
|
+
(0, vitest_1.expect)(result.errors).toContain("providers.repoAnalysis.preferred must be a string");
|
|
56
|
+
});
|
|
57
|
+
(0, vitest_1.it)("rejects providers.repoAnalysis.fallback that is not an array of strings", () => {
|
|
58
|
+
const result = (0, validator_js_1.validateConfig)({
|
|
59
|
+
providers: { repoAnalysis: { fallback: "polaris-map" } },
|
|
60
|
+
});
|
|
61
|
+
(0, vitest_1.expect)(result.valid).toBe(false);
|
|
62
|
+
(0, vitest_1.expect)(result.errors).toContain("providers.repoAnalysis.fallback must be an array of strings");
|
|
63
|
+
});
|
|
64
|
+
(0, vitest_1.it)("rejects providers.repoAnalysis.fallback with non-string elements", () => {
|
|
65
|
+
const result = (0, validator_js_1.validateConfig)({
|
|
66
|
+
providers: { repoAnalysis: { fallback: [1, 2] } },
|
|
67
|
+
});
|
|
68
|
+
(0, vitest_1.expect)(result.valid).toBe(false);
|
|
69
|
+
(0, vitest_1.expect)(result.errors).toContain("providers.repoAnalysis.fallback must be an array of strings");
|
|
70
|
+
});
|
|
71
|
+
(0, vitest_1.it)("does not warn on the providers key", () => {
|
|
72
|
+
const result = (0, validator_js_1.validateConfig)({
|
|
73
|
+
providers: { repoAnalysis: { preferred: "gitnexus" } },
|
|
74
|
+
});
|
|
75
|
+
(0, vitest_1.expect)(result.warnings).not.toContain('Unknown config field: "providers"');
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runCanonCheck = runCanonCheck;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
// Modal verbs that indicate behavioral assertions in doctrine/spec files
|
|
7
|
+
const MODAL_VERBS = /\b(must|never|always|should|required|requires|shall)\b/i;
|
|
8
|
+
// Domains matched by filename keywords
|
|
9
|
+
const DOMAIN_KEYWORDS = ["loop", "map", "finalize", "config", "cli", "docs"];
|
|
10
|
+
/** Walk up from `startDir` to `repoRoot` looking for the nearest POLARIS.md */
|
|
11
|
+
function findNearestPolarisMd(startDir, repoRoot) {
|
|
12
|
+
let dir = (0, node_path_1.resolve)(startDir);
|
|
13
|
+
const root = (0, node_path_1.resolve)(repoRoot);
|
|
14
|
+
let searching = true;
|
|
15
|
+
while (searching) {
|
|
16
|
+
const candidate = (0, node_path_1.join)(dir, "POLARIS.md");
|
|
17
|
+
if ((0, node_fs_1.existsSync)(candidate))
|
|
18
|
+
return candidate;
|
|
19
|
+
if (dir === root)
|
|
20
|
+
break;
|
|
21
|
+
const parent = (0, node_path_1.dirname)(dir);
|
|
22
|
+
if (parent === dir) {
|
|
23
|
+
searching = false;
|
|
24
|
+
break;
|
|
25
|
+
} // filesystem root
|
|
26
|
+
dir = parent;
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
/** Collect all relevant canon files for the given changed files */
|
|
31
|
+
function locateCanonFiles(changedFiles, repoRoot) {
|
|
32
|
+
const canon = new Set();
|
|
33
|
+
// POLARIS.md files — nearest ancestor for each changed file
|
|
34
|
+
for (const f of changedFiles) {
|
|
35
|
+
const absFile = (0, node_path_1.resolve)(repoRoot, f);
|
|
36
|
+
const startDir = (0, node_path_1.dirname)(absFile);
|
|
37
|
+
const polarisMd = findNearestPolarisMd(startDir, repoRoot);
|
|
38
|
+
if (polarisMd)
|
|
39
|
+
canon.add(polarisMd);
|
|
40
|
+
}
|
|
41
|
+
// Active doctrine
|
|
42
|
+
const doctrineDir = (0, node_path_1.join)(repoRoot, "docs", "doctrine", "active");
|
|
43
|
+
if ((0, node_fs_1.existsSync)(doctrineDir)) {
|
|
44
|
+
for (const f of (0, node_fs_1.readdirSync)(doctrineDir)) {
|
|
45
|
+
if (f.endsWith(".md"))
|
|
46
|
+
canon.add((0, node_path_1.join)(doctrineDir, f));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Active and implemented specs — filtered by domain keyword overlap
|
|
50
|
+
const changedDomains = new Set();
|
|
51
|
+
for (const f of changedFiles) {
|
|
52
|
+
for (const kw of DOMAIN_KEYWORDS) {
|
|
53
|
+
if (f.toLowerCase().includes(kw))
|
|
54
|
+
changedDomains.add(kw);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
for (const specSubdir of ["active", "implemented"]) {
|
|
58
|
+
const specDir = (0, node_path_1.join)(repoRoot, "docs", "specs", specSubdir);
|
|
59
|
+
if (!(0, node_fs_1.existsSync)(specDir))
|
|
60
|
+
continue;
|
|
61
|
+
for (const f of (0, node_fs_1.readdirSync)(specDir)) {
|
|
62
|
+
if (!f.endsWith(".md"))
|
|
63
|
+
continue;
|
|
64
|
+
const lower = f.toLowerCase();
|
|
65
|
+
const matches = changedDomains.size === 0 || Array.from(changedDomains).some((kw) => lower.includes(kw));
|
|
66
|
+
if (matches)
|
|
67
|
+
canon.add((0, node_path_1.join)(specDir, f));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return canon;
|
|
71
|
+
}
|
|
72
|
+
/** Extract bullet-point lines from a named markdown section */
|
|
73
|
+
function extractSection(content, sectionHeader) {
|
|
74
|
+
const lines = content.split("\n");
|
|
75
|
+
const result = [];
|
|
76
|
+
let inSection = false;
|
|
77
|
+
for (const line of lines) {
|
|
78
|
+
if (line.startsWith("##") && line.toLowerCase().includes(sectionHeader.toLowerCase())) {
|
|
79
|
+
inSection = true;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (inSection) {
|
|
83
|
+
if (line.startsWith("##"))
|
|
84
|
+
break; // next section
|
|
85
|
+
const trimmed = line.trim();
|
|
86
|
+
if (trimmed.startsWith("-") || trimmed.startsWith("*")) {
|
|
87
|
+
result.push(trimmed.replace(/^[-*]\s*/, ""));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Check if a POLARIS.md rule explicitly names a changed file as deleted/removed while it still exists.
|
|
95
|
+
* This is the primary `stale-implementation` trigger for MVP.
|
|
96
|
+
*/
|
|
97
|
+
function checkPolarisMd(canonFile, changedFiles, repoRoot) {
|
|
98
|
+
const conflicts = [];
|
|
99
|
+
let content;
|
|
100
|
+
try {
|
|
101
|
+
content = (0, node_fs_1.readFileSync)(canonFile, "utf-8");
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return conflicts;
|
|
105
|
+
}
|
|
106
|
+
const editingRules = extractSection(content, "editing rules");
|
|
107
|
+
const archAssumptions = extractSection(content, "architecture assumptions");
|
|
108
|
+
const allRules = [...editingRules, ...archAssumptions];
|
|
109
|
+
for (const rule of allRules) {
|
|
110
|
+
const lower = rule.toLowerCase();
|
|
111
|
+
// Detect rules that say something is deleted/removed but the file still exists
|
|
112
|
+
const isDeleted = /\b(deleted|removed|no longer exists|do not use)\b/.test(lower);
|
|
113
|
+
if (!isDeleted)
|
|
114
|
+
continue;
|
|
115
|
+
for (const changedFile of changedFiles) {
|
|
116
|
+
const fileBasename = (0, node_path_1.basename)(changedFile);
|
|
117
|
+
if (lower.includes(fileBasename.toLowerCase()) && (0, node_fs_1.existsSync)((0, node_path_1.resolve)(repoRoot, changedFile))) {
|
|
118
|
+
// Determine conflict type based on rule text and changed file
|
|
119
|
+
let conflictType = "stale-implementation";
|
|
120
|
+
const ext = changedFile.split(".").pop()?.toLowerCase() ?? "";
|
|
121
|
+
const isDocFile = ext === "md" || changedFile.startsWith("docs/");
|
|
122
|
+
const hasDocKeywords = /\b(doc|documentation|readme|guide|spec)\b/i.test(lower);
|
|
123
|
+
const hasCandidateKeywords = /\b(candidate|divergence|proposal)\b/i.test(lower);
|
|
124
|
+
if (isDocFile || hasDocKeywords) {
|
|
125
|
+
conflictType = "stale-docs";
|
|
126
|
+
}
|
|
127
|
+
else if (hasCandidateKeywords) {
|
|
128
|
+
conflictType = "candidate-divergence";
|
|
129
|
+
}
|
|
130
|
+
conflicts.push({
|
|
131
|
+
type: conflictType,
|
|
132
|
+
canonFile,
|
|
133
|
+
statement: rule,
|
|
134
|
+
changedFile,
|
|
135
|
+
detail: `POLARIS.md states "${fileBasename}" is deleted/removed but the file still exists`,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return conflicts;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Check doctrine/spec files for modal-verb assertions that directly contradict changed files.
|
|
144
|
+
* MVP: only flag stale-implementation when a named file/command is explicitly asserted to
|
|
145
|
+
* exist/be-required but is missing, or asserted as deleted while still present.
|
|
146
|
+
*/
|
|
147
|
+
function checkDocFile(canonFile, changedFiles, repoRoot) {
|
|
148
|
+
const conflicts = [];
|
|
149
|
+
let content;
|
|
150
|
+
try {
|
|
151
|
+
content = (0, node_fs_1.readFileSync)(canonFile, "utf-8");
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return conflicts;
|
|
155
|
+
}
|
|
156
|
+
const lines = content.split("\n");
|
|
157
|
+
for (const line of lines) {
|
|
158
|
+
if (!MODAL_VERBS.test(line))
|
|
159
|
+
continue;
|
|
160
|
+
const lower = line.toLowerCase();
|
|
161
|
+
// Only flag lines that name a specific file/command that should exist but doesn't
|
|
162
|
+
const deletedAssertion = /\b(deleted|removed|no longer exists)\b/.test(lower);
|
|
163
|
+
if (!deletedAssertion)
|
|
164
|
+
continue;
|
|
165
|
+
for (const changedFile of changedFiles) {
|
|
166
|
+
const fileBasename = (0, node_path_1.basename)(changedFile);
|
|
167
|
+
if (lower.includes(fileBasename.toLowerCase()) && (0, node_fs_1.existsSync)((0, node_path_1.resolve)(repoRoot, changedFile))) {
|
|
168
|
+
conflicts.push({
|
|
169
|
+
type: "stale-implementation",
|
|
170
|
+
canonFile,
|
|
171
|
+
statement: line.trim(),
|
|
172
|
+
changedFile,
|
|
173
|
+
detail: `Canon asserts "${fileBasename}" is deleted/removed but file still exists`,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return conflicts;
|
|
179
|
+
}
|
|
180
|
+
function appendTelemetry(telemetryFile, event) {
|
|
181
|
+
try {
|
|
182
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(telemetryFile), { recursive: true });
|
|
183
|
+
(0, node_fs_1.appendFileSync)(telemetryFile, JSON.stringify(event) + "\n", "utf-8");
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
// Non-fatal: telemetry write failure should not block operation
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Run the canon reconciliation check.
|
|
191
|
+
*
|
|
192
|
+
* Emits `canon-check-start` and either `canon-check-result` or `canon-conflict-halt`
|
|
193
|
+
* to the telemetry file. Returns the result for the caller to act on.
|
|
194
|
+
*
|
|
195
|
+
* Note: This function does NOT call process.exit. Callers are responsible for
|
|
196
|
+
* halting execution based on the returned outcome.
|
|
197
|
+
*/
|
|
198
|
+
function runCanonCheck(options) {
|
|
199
|
+
const { repoRoot, changedFiles, childId, runId, telemetryFile } = options;
|
|
200
|
+
const canonFilesSet = locateCanonFiles(changedFiles, repoRoot);
|
|
201
|
+
const canonFilesInspected = canonFilesSet.size;
|
|
202
|
+
appendTelemetry(telemetryFile, {
|
|
203
|
+
event: "canon-check-start",
|
|
204
|
+
run_id: runId,
|
|
205
|
+
child_id: childId ?? null,
|
|
206
|
+
canon_files_inspected: canonFilesInspected,
|
|
207
|
+
changed_files_count: changedFiles.length,
|
|
208
|
+
timestamp: new Date().toISOString(),
|
|
209
|
+
});
|
|
210
|
+
if (canonFilesInspected === 0 || changedFiles.length === 0) {
|
|
211
|
+
const result = { outcome: "aligned", conflicts: [], canonFilesInspected };
|
|
212
|
+
appendTelemetry(telemetryFile, {
|
|
213
|
+
event: "canon-check-result",
|
|
214
|
+
run_id: runId,
|
|
215
|
+
child_id: childId ?? null,
|
|
216
|
+
outcome: "aligned",
|
|
217
|
+
conflicts: [],
|
|
218
|
+
timestamp: new Date().toISOString(),
|
|
219
|
+
});
|
|
220
|
+
return result;
|
|
221
|
+
}
|
|
222
|
+
const allConflicts = [];
|
|
223
|
+
for (const canonFile of canonFilesSet) {
|
|
224
|
+
const fileBasename = (0, node_path_1.basename)(canonFile);
|
|
225
|
+
const isPolarisMd = fileBasename === "POLARIS.md";
|
|
226
|
+
const fileConflicts = isPolarisMd
|
|
227
|
+
? checkPolarisMd(canonFile, changedFiles, repoRoot)
|
|
228
|
+
: checkDocFile(canonFile, changedFiles, repoRoot);
|
|
229
|
+
allConflicts.push(...fileConflicts);
|
|
230
|
+
}
|
|
231
|
+
// Classify outcome: stale-implementation wins; then stale-docs; then candidate-divergence; else aligned
|
|
232
|
+
let outcome = "aligned";
|
|
233
|
+
if (allConflicts.some((c) => c.type === "stale-implementation")) {
|
|
234
|
+
outcome = "stale-implementation";
|
|
235
|
+
}
|
|
236
|
+
else if (allConflicts.some((c) => c.type === "stale-docs")) {
|
|
237
|
+
outcome = "stale-docs";
|
|
238
|
+
}
|
|
239
|
+
else if (allConflicts.some((c) => c.type === "candidate-divergence")) {
|
|
240
|
+
outcome = "candidate-divergence";
|
|
241
|
+
}
|
|
242
|
+
const result = { outcome, conflicts: allConflicts, canonFilesInspected };
|
|
243
|
+
if (outcome === "stale-implementation") {
|
|
244
|
+
const first = allConflicts.find((c) => c.type === "stale-implementation");
|
|
245
|
+
appendTelemetry(telemetryFile, {
|
|
246
|
+
event: "canon-conflict-halt",
|
|
247
|
+
run_id: runId,
|
|
248
|
+
child_id: childId ?? null,
|
|
249
|
+
reason: first.detail,
|
|
250
|
+
canon_file: first.canonFile,
|
|
251
|
+
conflicting_statement: first.statement,
|
|
252
|
+
missing_or_differing: first.changedFile,
|
|
253
|
+
suggested_resolution: "Update the canon file to reflect current state, or remove/fix the conflicting assertion",
|
|
254
|
+
timestamp: new Date().toISOString(),
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
appendTelemetry(telemetryFile, {
|
|
259
|
+
event: "canon-check-result",
|
|
260
|
+
run_id: runId,
|
|
261
|
+
child_id: childId ?? null,
|
|
262
|
+
outcome,
|
|
263
|
+
conflicts: allConflicts,
|
|
264
|
+
timestamp: new Date().toISOString(),
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
// For non-blocking outcomes, write draft docs as specified
|
|
268
|
+
if (outcome === "stale-docs") {
|
|
269
|
+
writeStaleDraftDocs(allConflicts.filter((c) => c.type === "stale-docs"), repoRoot, childId);
|
|
270
|
+
}
|
|
271
|
+
else if (outcome === "candidate-divergence") {
|
|
272
|
+
writeCandidateDraftDocs(allConflicts.filter((c) => c.type === "candidate-divergence"), repoRoot, childId);
|
|
273
|
+
}
|
|
274
|
+
return result;
|
|
275
|
+
}
|
|
276
|
+
function writeStaleDraftDocs(conflicts, repoRoot, childId) {
|
|
277
|
+
const rawDir = (0, node_path_1.join)(repoRoot, "docs", "raw");
|
|
278
|
+
try {
|
|
279
|
+
(0, node_fs_1.mkdirSync)(rawDir, { recursive: true });
|
|
280
|
+
for (const conflict of conflicts) {
|
|
281
|
+
const slug = (0, node_path_1.basename)(conflict.canonFile).replace(/\.md$/, "");
|
|
282
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
283
|
+
const filename = `stale-flag-${slug}-${date}.md`;
|
|
284
|
+
const path = (0, node_path_1.join)(rawDir, filename);
|
|
285
|
+
if (!(0, node_fs_1.existsSync)(path)) {
|
|
286
|
+
(0, node_fs_1.writeFileSync)(path, [
|
|
287
|
+
`# Stale Doc Flag`,
|
|
288
|
+
``,
|
|
289
|
+
`**Canon file:** ${conflict.canonFile}`,
|
|
290
|
+
`**Triggering file:** ${conflict.changedFile}`,
|
|
291
|
+
`**What appears outdated:** ${conflict.statement}`,
|
|
292
|
+
`**Detail:** ${conflict.detail}`,
|
|
293
|
+
`**Flagged by:** ${childId ?? "finalize"}`,
|
|
294
|
+
`**Date:** ${new Date().toISOString()}`,
|
|
295
|
+
].join("\n"), "utf-8");
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
// Non-fatal
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
function writeCandidateDraftDocs(conflicts, repoRoot, childId) {
|
|
304
|
+
const candidateDir = (0, node_path_1.join)(repoRoot, "docs", "doctrine", "candidate");
|
|
305
|
+
try {
|
|
306
|
+
(0, node_fs_1.mkdirSync)(candidateDir, { recursive: true });
|
|
307
|
+
for (const conflict of conflicts) {
|
|
308
|
+
const slug = (0, node_path_1.basename)(conflict.canonFile).replace(/\.md$/, "");
|
|
309
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
310
|
+
const filename = `${slug}-${date}.draft.md`;
|
|
311
|
+
const path = (0, node_path_1.join)(candidateDir, filename);
|
|
312
|
+
if (!(0, node_fs_1.existsSync)(path)) {
|
|
313
|
+
(0, node_fs_1.writeFileSync)(path, [
|
|
314
|
+
`---`,
|
|
315
|
+
`status: candidate`,
|
|
316
|
+
`source: ${conflict.canonFile}`,
|
|
317
|
+
`proposed-by: ${childId ?? "unknown"}`,
|
|
318
|
+
`proposed-at: ${new Date().toISOString()}`,
|
|
319
|
+
`---`,
|
|
320
|
+
``,
|
|
321
|
+
`# Candidate Divergence Draft`,
|
|
322
|
+
``,
|
|
323
|
+
`**Source canon file:** ${conflict.canonFile}`,
|
|
324
|
+
`**Conflicting statement:** ${conflict.statement}`,
|
|
325
|
+
`**Changed file:** ${conflict.changedFile}`,
|
|
326
|
+
`**Detail:** ${conflict.detail}`,
|
|
327
|
+
].join("\n"), "utf-8");
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
// Non-fatal
|
|
333
|
+
}
|
|
334
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const node_os_1 = require("node:os");
|
|
7
|
+
const canon_check_js_1 = require("./canon-check.js");
|
|
8
|
+
function makeTestDir() {
|
|
9
|
+
const dir = (0, node_path_1.join)((0, node_os_1.tmpdir)(), `canon-check-test-${Date.now()}`);
|
|
10
|
+
(0, node_fs_1.mkdirSync)(dir, { recursive: true });
|
|
11
|
+
return dir;
|
|
12
|
+
}
|
|
13
|
+
function makeTelemetryFile(dir) {
|
|
14
|
+
const telDir = (0, node_path_1.join)(dir, "telemetry");
|
|
15
|
+
(0, node_fs_1.mkdirSync)(telDir, { recursive: true });
|
|
16
|
+
return (0, node_path_1.join)(telDir, "telemetry.jsonl");
|
|
17
|
+
}
|
|
18
|
+
(0, vitest_1.describe)("runCanonCheck", () => {
|
|
19
|
+
let testDir;
|
|
20
|
+
let telemetryFile;
|
|
21
|
+
(0, vitest_1.beforeEach)(() => {
|
|
22
|
+
testDir = makeTestDir();
|
|
23
|
+
telemetryFile = makeTelemetryFile(testDir);
|
|
24
|
+
});
|
|
25
|
+
(0, vitest_1.afterEach)(() => {
|
|
26
|
+
(0, node_fs_1.rmSync)(testDir, { recursive: true, force: true });
|
|
27
|
+
});
|
|
28
|
+
(0, vitest_1.describe)("aligned outcome", () => {
|
|
29
|
+
(0, vitest_1.it)("returns aligned when no changed files are provided", () => {
|
|
30
|
+
const result = (0, canon_check_js_1.runCanonCheck)({
|
|
31
|
+
repoRoot: testDir,
|
|
32
|
+
changedFiles: [],
|
|
33
|
+
runId: "test-run-1",
|
|
34
|
+
telemetryFile,
|
|
35
|
+
});
|
|
36
|
+
(0, vitest_1.expect)(result.outcome).toBe("aligned");
|
|
37
|
+
(0, vitest_1.expect)(result.conflicts).toEqual([]);
|
|
38
|
+
});
|
|
39
|
+
(0, vitest_1.it)("returns aligned when no POLARIS.md or canon dirs exist", () => {
|
|
40
|
+
const result = (0, canon_check_js_1.runCanonCheck)({
|
|
41
|
+
repoRoot: testDir,
|
|
42
|
+
changedFiles: ["src/loop/continue.ts"],
|
|
43
|
+
runId: "test-run-1",
|
|
44
|
+
telemetryFile,
|
|
45
|
+
});
|
|
46
|
+
(0, vitest_1.expect)(result.outcome).toBe("aligned");
|
|
47
|
+
(0, vitest_1.expect)(result.conflicts).toEqual([]);
|
|
48
|
+
});
|
|
49
|
+
(0, vitest_1.it)("returns aligned when POLARIS.md has no rules mentioning changed files as deleted", () => {
|
|
50
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(testDir, "src", "loop"), { recursive: true });
|
|
51
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(testDir, "src", "loop", "continue.ts"), "// placeholder");
|
|
52
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(testDir, "POLARIS.md"), [
|
|
53
|
+
"# POLARIS.md",
|
|
54
|
+
"",
|
|
55
|
+
"## Editing rules",
|
|
56
|
+
"- State writes must use checkpoint.ts",
|
|
57
|
+
"- Keep loop files small",
|
|
58
|
+
"",
|
|
59
|
+
"## Architecture assumptions",
|
|
60
|
+
"- Bootstrap packets are immutable once written",
|
|
61
|
+
].join("\n"));
|
|
62
|
+
const result = (0, canon_check_js_1.runCanonCheck)({
|
|
63
|
+
repoRoot: testDir,
|
|
64
|
+
changedFiles: ["src/loop/continue.ts"],
|
|
65
|
+
runId: "test-run-1",
|
|
66
|
+
telemetryFile,
|
|
67
|
+
});
|
|
68
|
+
(0, vitest_1.expect)(result.outcome).toBe("aligned");
|
|
69
|
+
(0, vitest_1.expect)(result.conflicts).toEqual([]);
|
|
70
|
+
});
|
|
71
|
+
(0, vitest_1.it)("sets canonFilesInspected based on found canon files", () => {
|
|
72
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(testDir, "POLARIS.md"), "# POLARIS.md\n## Editing rules\n- No special rules\n");
|
|
73
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(testDir, "src"), { recursive: true });
|
|
74
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(testDir, "src", "foo.ts"), "// code");
|
|
75
|
+
const result = (0, canon_check_js_1.runCanonCheck)({
|
|
76
|
+
repoRoot: testDir,
|
|
77
|
+
changedFiles: ["src/foo.ts"],
|
|
78
|
+
runId: "test-run-1",
|
|
79
|
+
telemetryFile,
|
|
80
|
+
});
|
|
81
|
+
(0, vitest_1.expect)(result.canonFilesInspected).toBeGreaterThan(0);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
(0, vitest_1.describe)("stale-implementation outcome", () => {
|
|
85
|
+
(0, vitest_1.it)("returns stale-implementation when POLARIS.md says a file is deleted but it still exists", () => {
|
|
86
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(testDir, "src", "loop"), { recursive: true });
|
|
87
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(testDir, "src", "loop", "old-module.ts"), "// still here");
|
|
88
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(testDir, "POLARIS.md"), [
|
|
89
|
+
"# POLARIS.md",
|
|
90
|
+
"",
|
|
91
|
+
"## Architecture assumptions",
|
|
92
|
+
"- old-module.ts has been deleted and must not be used",
|
|
93
|
+
].join("\n"));
|
|
94
|
+
const result = (0, canon_check_js_1.runCanonCheck)({
|
|
95
|
+
repoRoot: testDir,
|
|
96
|
+
changedFiles: ["src/loop/old-module.ts"],
|
|
97
|
+
runId: "test-run-1",
|
|
98
|
+
telemetryFile,
|
|
99
|
+
});
|
|
100
|
+
(0, vitest_1.expect)(result.outcome).toBe("stale-implementation");
|
|
101
|
+
(0, vitest_1.expect)(result.conflicts.length).toBeGreaterThan(0);
|
|
102
|
+
(0, vitest_1.expect)(result.conflicts[0].type).toBe("stale-implementation");
|
|
103
|
+
});
|
|
104
|
+
(0, vitest_1.it)("emits canon-conflict-halt telemetry event on stale-implementation", () => {
|
|
105
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(testDir, "src"), { recursive: true });
|
|
106
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(testDir, "src", "legacy.ts"), "// still exists");
|
|
107
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(testDir, "POLARIS.md"), [
|
|
108
|
+
"# POLARIS.md",
|
|
109
|
+
"",
|
|
110
|
+
"## Architecture assumptions",
|
|
111
|
+
"- legacy.ts has been removed and should not be referenced",
|
|
112
|
+
].join("\n"));
|
|
113
|
+
(0, canon_check_js_1.runCanonCheck)({
|
|
114
|
+
repoRoot: testDir,
|
|
115
|
+
changedFiles: ["src/legacy.ts"],
|
|
116
|
+
runId: "test-run-halt",
|
|
117
|
+
telemetryFile,
|
|
118
|
+
childId: "POL-99",
|
|
119
|
+
});
|
|
120
|
+
const lines = (0, node_fs_1.readFileSync)(telemetryFile, "utf-8")
|
|
121
|
+
.trim()
|
|
122
|
+
.split("\n")
|
|
123
|
+
.map((l) => JSON.parse(l));
|
|
124
|
+
const halt = lines.find((e) => e.event === "canon-conflict-halt");
|
|
125
|
+
(0, vitest_1.expect)(halt).toBeTruthy();
|
|
126
|
+
(0, vitest_1.expect)(halt.run_id).toBe("test-run-halt");
|
|
127
|
+
(0, vitest_1.expect)(halt.child_id).toBe("POL-99");
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
(0, vitest_1.describe)("telemetry events", () => {
|
|
131
|
+
(0, vitest_1.it)("emits canon-check-start event", () => {
|
|
132
|
+
(0, canon_check_js_1.runCanonCheck)({
|
|
133
|
+
repoRoot: testDir,
|
|
134
|
+
changedFiles: ["src/foo.ts"],
|
|
135
|
+
runId: "test-run-tel",
|
|
136
|
+
telemetryFile,
|
|
137
|
+
childId: "POL-51",
|
|
138
|
+
});
|
|
139
|
+
const lines = (0, node_fs_1.readFileSync)(telemetryFile, "utf-8")
|
|
140
|
+
.trim()
|
|
141
|
+
.split("\n")
|
|
142
|
+
.map((l) => JSON.parse(l));
|
|
143
|
+
const start = lines.find((e) => e.event === "canon-check-start");
|
|
144
|
+
(0, vitest_1.expect)(start).toBeTruthy();
|
|
145
|
+
(0, vitest_1.expect)(start.run_id).toBe("test-run-tel");
|
|
146
|
+
(0, vitest_1.expect)(start.child_id).toBe("POL-51");
|
|
147
|
+
(0, vitest_1.expect)(typeof start.changed_files_count).toBe("number");
|
|
148
|
+
});
|
|
149
|
+
(0, vitest_1.it)("emits canon-check-result event for aligned outcome", () => {
|
|
150
|
+
(0, canon_check_js_1.runCanonCheck)({
|
|
151
|
+
repoRoot: testDir,
|
|
152
|
+
changedFiles: [],
|
|
153
|
+
runId: "test-run-aligned",
|
|
154
|
+
telemetryFile,
|
|
155
|
+
});
|
|
156
|
+
const lines = (0, node_fs_1.readFileSync)(telemetryFile, "utf-8")
|
|
157
|
+
.trim()
|
|
158
|
+
.split("\n")
|
|
159
|
+
.map((l) => JSON.parse(l));
|
|
160
|
+
const result = lines.find((e) => e.event === "canon-check-result");
|
|
161
|
+
(0, vitest_1.expect)(result).toBeTruthy();
|
|
162
|
+
(0, vitest_1.expect)(result.outcome).toBe("aligned");
|
|
163
|
+
(0, vitest_1.expect)(result.conflicts).toEqual([]);
|
|
164
|
+
});
|
|
165
|
+
(0, vitest_1.it)("sets child_id to null when not provided", () => {
|
|
166
|
+
(0, canon_check_js_1.runCanonCheck)({
|
|
167
|
+
repoRoot: testDir,
|
|
168
|
+
changedFiles: [],
|
|
169
|
+
runId: "test-run-no-child",
|
|
170
|
+
telemetryFile,
|
|
171
|
+
});
|
|
172
|
+
const lines = (0, node_fs_1.readFileSync)(telemetryFile, "utf-8")
|
|
173
|
+
.trim()
|
|
174
|
+
.split("\n")
|
|
175
|
+
.map((l) => JSON.parse(l));
|
|
176
|
+
const start = lines.find((e) => e.event === "canon-check-start");
|
|
177
|
+
(0, vitest_1.expect)(start.child_id).toBeNull();
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
(0, vitest_1.describe)("config flags", () => {
|
|
181
|
+
(0, vitest_1.it)("does not alter return value — callers control skip logic", () => {
|
|
182
|
+
// canon-check itself has no checkOnContinue/checkOnFinalize logic;
|
|
183
|
+
// skipping is the caller's responsibility. Just verify function is callable.
|
|
184
|
+
const checkResult = (0, canon_check_js_1.runCanonCheck)({
|
|
185
|
+
repoRoot: testDir,
|
|
186
|
+
changedFiles: [],
|
|
187
|
+
runId: "test-flag",
|
|
188
|
+
telemetryFile,
|
|
189
|
+
});
|
|
190
|
+
(0, vitest_1.expect)(checkResult).toBeDefined();
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
(0, vitest_1.describe)("POLARIS.md nearest ancestor lookup", () => {
|
|
194
|
+
(0, vitest_1.it)("finds POLARIS.md in a parent directory", () => {
|
|
195
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(testDir, "src", "loop", "deep"), { recursive: true });
|
|
196
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(testDir, "src", "loop", "deep", "file.ts"), "// code");
|
|
197
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(testDir, "src", "POLARIS.md"), "# POLARIS.md\n## Editing rules\n- no rules\n");
|
|
198
|
+
const result = (0, canon_check_js_1.runCanonCheck)({
|
|
199
|
+
repoRoot: testDir,
|
|
200
|
+
changedFiles: ["src/loop/deep/file.ts"],
|
|
201
|
+
runId: "test-ancestor",
|
|
202
|
+
telemetryFile,
|
|
203
|
+
});
|
|
204
|
+
(0, vitest_1.expect)(result.canonFilesInspected).toBeGreaterThan(0);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
});
|