@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,350 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Route-local cognition delta logic.
|
|
4
|
+
*
|
|
5
|
+
* Workers call applyRouteCognitionDelta() after implementation to determine
|
|
6
|
+
* whether POLARIS.md needs updating for the folders they touched. Updates are
|
|
7
|
+
* delta-only: only apply when operationally-relevant changes occurred.
|
|
8
|
+
*
|
|
9
|
+
* Root cognition surfaces (POLARIS.md at repo root) are skipped unless
|
|
10
|
+
* explicitly opted in. Bounded by locality — no repo-wide scanning.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.POLARIS_OWNED_COGNITION_FOLDERS = void 0;
|
|
14
|
+
exports.assessRouteHealth = assessRouteHealth;
|
|
15
|
+
exports.isPolarisOwnedFolder = isPolarisOwnedFolder;
|
|
16
|
+
exports.isUserCreatedCognitionSurface = isUserCreatedCognitionSurface;
|
|
17
|
+
exports.isCognitionSkippedFolder = isCognitionSkippedFolder;
|
|
18
|
+
exports.findNearestRoutePolarismd = findNearestRoutePolarismd;
|
|
19
|
+
exports.detectOperationalReasons = detectOperationalReasons;
|
|
20
|
+
exports.applyRouteCognitionDelta = applyRouteCognitionDelta;
|
|
21
|
+
exports.readRoutePolarismd = readRoutePolarismd;
|
|
22
|
+
const node_fs_1 = require("node:fs");
|
|
23
|
+
const node_path_1 = require("node:path");
|
|
24
|
+
const smartdoc_ignore_js_1 = require("../smartdocs-engine/smartdoc-ignore.js");
|
|
25
|
+
// ── Route Health Assessment ───────────────────────────────────────────────────
|
|
26
|
+
/**
|
|
27
|
+
* Assess the health state of a route based on observable signals.
|
|
28
|
+
*
|
|
29
|
+
* Signals used:
|
|
30
|
+
* - Staleness: entry older than threshold (default 90 days)
|
|
31
|
+
* - Identity completeness: missing instructionFile or role_owner
|
|
32
|
+
*
|
|
33
|
+
* Returns:
|
|
34
|
+
* - "stale": entry is stale (older than threshold)
|
|
35
|
+
* - "known-issues": identity incomplete (missing instructionFile or role_owner)
|
|
36
|
+
* - "healthy": route is fresh, identity complete
|
|
37
|
+
*/
|
|
38
|
+
function assessRouteHealth(route, repoRoot, staleThresholdDays = 90) {
|
|
39
|
+
const daysSinceUpdate = (Date.now() - new Date(route.last_updated).getTime()) / (1000 * 60 * 60 * 24);
|
|
40
|
+
// Staleness check (highest priority)
|
|
41
|
+
if (daysSinceUpdate > staleThresholdDays) {
|
|
42
|
+
return "stale";
|
|
43
|
+
}
|
|
44
|
+
// Identity completeness check
|
|
45
|
+
const hasInstructionFile = route.instructionFile !== undefined && route.instructionFile !== null && route.instructionFile.trim() !== "";
|
|
46
|
+
const hasRoleOwner = route.role_owner !== undefined && route.role_owner !== null && route.role_owner.trim() !== "";
|
|
47
|
+
if (!hasInstructionFile || !hasRoleOwner) {
|
|
48
|
+
return "known-issues";
|
|
49
|
+
}
|
|
50
|
+
// Cognition surface check: instructionFile configured but file missing on disk
|
|
51
|
+
const instructionFileAbs = (0, node_path_1.join)(repoRoot, route.instructionFile);
|
|
52
|
+
if (!(0, node_fs_1.existsSync)(instructionFileAbs)) {
|
|
53
|
+
return "monitoring";
|
|
54
|
+
}
|
|
55
|
+
// Recovery window: recently updated (7–threshold days ago)
|
|
56
|
+
if (daysSinceUpdate > 7) {
|
|
57
|
+
return "recovering";
|
|
58
|
+
}
|
|
59
|
+
return "healthy";
|
|
60
|
+
}
|
|
61
|
+
// ── Skipped folder patterns ───────────────────────────────────────────────────
|
|
62
|
+
const SKIP_FOLDER_PREFIXES = [
|
|
63
|
+
".git/",
|
|
64
|
+
"node_modules/",
|
|
65
|
+
"dist/",
|
|
66
|
+
".taskchain_artifacts/",
|
|
67
|
+
];
|
|
68
|
+
const AGENT_OPT_IN_FOLDERS = new Set([".claude", ".codex"]);
|
|
69
|
+
const POLARIS_RUNTIME_COGNITION_FOLDERS = new Set([
|
|
70
|
+
".polaris",
|
|
71
|
+
".polaris/bootstrap",
|
|
72
|
+
".polaris/clusters",
|
|
73
|
+
".polaris/map",
|
|
74
|
+
".polaris/runs",
|
|
75
|
+
]);
|
|
76
|
+
const POLARIS_RUNTIME_GENERATED_PREFIXES = [
|
|
77
|
+
".polaris/bootstrap/",
|
|
78
|
+
".polaris/clusters/",
|
|
79
|
+
".polaris/graph/",
|
|
80
|
+
".polaris/map/",
|
|
81
|
+
".polaris/runs/",
|
|
82
|
+
];
|
|
83
|
+
exports.POLARIS_OWNED_COGNITION_FOLDERS = [
|
|
84
|
+
".polaris",
|
|
85
|
+
"src",
|
|
86
|
+
"smartdocs/specs/active",
|
|
87
|
+
"smartdocs/doctrine/active",
|
|
88
|
+
];
|
|
89
|
+
function normalizeRelPath(input) {
|
|
90
|
+
return input.replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
|
|
91
|
+
}
|
|
92
|
+
function readManagedSurfaceManifest(repoRoot) {
|
|
93
|
+
const manifestPath = (0, node_path_1.join)(repoRoot, ".polaris", "cognition", "managed-surfaces.json");
|
|
94
|
+
if (!(0, node_fs_1.existsSync)(manifestPath))
|
|
95
|
+
return new Set();
|
|
96
|
+
const raw = (0, node_fs_1.readFileSync)(manifestPath, "utf-8");
|
|
97
|
+
const parsed = JSON.parse(raw);
|
|
98
|
+
const paths = [];
|
|
99
|
+
if (Array.isArray(parsed)) {
|
|
100
|
+
for (const value of parsed) {
|
|
101
|
+
if (typeof value === "string")
|
|
102
|
+
paths.push(value);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else if (parsed && typeof parsed === "object") {
|
|
106
|
+
const candidateArrays = [
|
|
107
|
+
parsed["surfaces"],
|
|
108
|
+
parsed["managed_surfaces"],
|
|
109
|
+
parsed["managedSurfaces"],
|
|
110
|
+
parsed["paths"],
|
|
111
|
+
];
|
|
112
|
+
for (const candidate of candidateArrays) {
|
|
113
|
+
if (!Array.isArray(candidate))
|
|
114
|
+
continue;
|
|
115
|
+
for (const value of candidate) {
|
|
116
|
+
if (typeof value === "string")
|
|
117
|
+
paths.push(value);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return new Set(paths.map((p) => normalizeRelPath(p)));
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Tier 1 folders that are always considered Polaris-owned cognition surfaces.
|
|
125
|
+
*
|
|
126
|
+
* Includes fixed roots and dynamic immediate children under src/<subdirectory>.
|
|
127
|
+
*/
|
|
128
|
+
function isPolarisOwnedFolder(folderRel) {
|
|
129
|
+
const normalized = normalizeRelPath(folderRel);
|
|
130
|
+
if (!normalized)
|
|
131
|
+
return false;
|
|
132
|
+
if (exports.POLARIS_OWNED_COGNITION_FOLDERS.includes(normalized)) {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
return /^src\/[^/]+$/.test(normalized);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Returns true when a cognition surface should be treated as user-created and
|
|
139
|
+
* therefore protected from worker overwrites.
|
|
140
|
+
*
|
|
141
|
+
* A surface is protected when either:
|
|
142
|
+
* 1) It predates Polaris initialization (.polaris birth/creation time), or
|
|
143
|
+
* 2) It is listed in .polaris/cognition/managed-surfaces.json.
|
|
144
|
+
*/
|
|
145
|
+
function isUserCreatedCognitionSurface(filePath, repoRoot) {
|
|
146
|
+
const absFile = (0, node_path_1.resolve)(repoRoot, filePath);
|
|
147
|
+
const relFile = normalizeRelPath((0, node_path_1.relative)(repoRoot, absFile));
|
|
148
|
+
if (!relFile || relFile.startsWith(".."))
|
|
149
|
+
return false;
|
|
150
|
+
const managed = readManagedSurfaceManifest(repoRoot);
|
|
151
|
+
if (managed.has(relFile))
|
|
152
|
+
return true;
|
|
153
|
+
if (!(0, node_fs_1.existsSync)(absFile))
|
|
154
|
+
return false;
|
|
155
|
+
const polarisDir = (0, node_path_1.join)(repoRoot, ".polaris");
|
|
156
|
+
if (!(0, node_fs_1.existsSync)(polarisDir))
|
|
157
|
+
return false;
|
|
158
|
+
const fileStat = (0, node_fs_1.statSync)(absFile);
|
|
159
|
+
const polarisStat = (0, node_fs_1.statSync)(polarisDir);
|
|
160
|
+
const polarisInitializedAt = polarisStat.birthtimeMs > 0 ? polarisStat.birthtimeMs : polarisStat.ctimeMs;
|
|
161
|
+
return fileStat.mtimeMs < polarisInitializedAt;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Returns true when a folder is skipped for route-local cognition:
|
|
165
|
+
* generated, runtime, ignored, or agent-only-with-no-opt-in.
|
|
166
|
+
*
|
|
167
|
+
* When repoRoot is provided, also consults .smartdocignore and the shared
|
|
168
|
+
* smartdoc-ignore eligibility rules (RUNTIME_EXCLUDED_DIR_PATTERNS,
|
|
169
|
+
* AGENT_COGNITION_FOLDERS, HIDDEN_SYSTEM_FOLDERS). This ensures cognition
|
|
170
|
+
* scanning honours the same boundaries as Smart Docs ingest.
|
|
171
|
+
*/
|
|
172
|
+
function isCognitionSkippedFolder(folderRel, repoRoot) {
|
|
173
|
+
// Fast-path: hardcoded runtime/bootstrap prefixes (no fs needed)
|
|
174
|
+
for (const prefix of SKIP_FOLDER_PREFIXES) {
|
|
175
|
+
if (folderRel === prefix.slice(0, -1) || folderRel.startsWith(prefix)) {
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (POLARIS_RUNTIME_COGNITION_FOLDERS.has(folderRel))
|
|
180
|
+
return false;
|
|
181
|
+
for (const prefix of POLARIS_RUNTIME_GENERATED_PREFIXES) {
|
|
182
|
+
const rootPath = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
183
|
+
if (folderRel === rootPath || folderRel.startsWith(prefix))
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
// Agent folders are opt-in only
|
|
187
|
+
const topLevel = folderRel.split("/")[0] ?? folderRel;
|
|
188
|
+
if (AGENT_OPT_IN_FOLDERS.has(topLevel))
|
|
189
|
+
return true;
|
|
190
|
+
// When repoRoot is available, defer to the smartdoc-ignore authority.
|
|
191
|
+
// This ensures .smartdocignore patterns and shared exclusion lists are
|
|
192
|
+
// honoured — POLARIS.md and SUMMARY.md are bounded agent/context files,
|
|
193
|
+
// not ingest candidates.
|
|
194
|
+
if (repoRoot) {
|
|
195
|
+
const eligibility = (0, smartdoc_ignore_js_1.isDirectoryEligible)(folderRel, repoRoot);
|
|
196
|
+
if (!eligibility.eligible)
|
|
197
|
+
return true;
|
|
198
|
+
// Also check .smartdocignore patterns directly for directory paths
|
|
199
|
+
if (!folderRel.startsWith(".polaris")) {
|
|
200
|
+
const ig = (0, smartdoc_ignore_js_1.parseSmartDocIgnore)(repoRoot);
|
|
201
|
+
if (ig.ignores(folderRel) || ig.ignores(`${folderRel}/`))
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
// ── Nearest route-local POLARIS.md ────────────────────────────────────────────
|
|
208
|
+
/**
|
|
209
|
+
* Walk upward from `filePath` (relative to repoRoot) and return the nearest
|
|
210
|
+
* non-root POLARIS.md. Returns null when only root or no POLARIS.md exists.
|
|
211
|
+
*/
|
|
212
|
+
function findNearestRoutePolarismd(filePath, repoRoot, skipRoot) {
|
|
213
|
+
const parts = filePath.split("/");
|
|
214
|
+
// Walk from deepest dir upward, excluding the root level when skipRoot=true
|
|
215
|
+
for (let i = parts.length - 1; i >= 1; i--) {
|
|
216
|
+
const dir = parts.slice(0, i).join("/");
|
|
217
|
+
if (isCognitionSkippedFolder(dir, repoRoot))
|
|
218
|
+
continue;
|
|
219
|
+
const candidate = (0, node_path_1.join)(repoRoot, dir, "POLARIS.md");
|
|
220
|
+
if ((0, node_fs_1.existsSync)(candidate)) {
|
|
221
|
+
return (0, node_path_1.relative)(repoRoot, candidate).replace(/\\/g, "/");
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// Fall back to root only when not skipping root
|
|
225
|
+
if (!skipRoot) {
|
|
226
|
+
const rootCandidate = (0, node_path_1.join)(repoRoot, "POLARIS.md");
|
|
227
|
+
if ((0, node_fs_1.existsSync)(rootCandidate))
|
|
228
|
+
return "POLARIS.md";
|
|
229
|
+
}
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
// ── Change signal detection ───────────────────────────────────────────────────
|
|
233
|
+
/** Signals in a file path that suggest operational relevance. */
|
|
234
|
+
const OPERATIONAL_SIGNALS = [
|
|
235
|
+
{ pattern: /\/index\.ts$/, reason: "folder-responsibilities-changed" },
|
|
236
|
+
{ pattern: /\/cli\//, reason: "commands-workflows-changed" },
|
|
237
|
+
{ pattern: /\/commands?\//, reason: "commands-workflows-changed" },
|
|
238
|
+
{ pattern: /scripts\//, reason: "commands-workflows-changed" },
|
|
239
|
+
{ pattern: /\/config\//, reason: "execution-constraints-changed" },
|
|
240
|
+
{ pattern: /\/schema\.ts$/, reason: "execution-constraints-changed" },
|
|
241
|
+
{ pattern: /\/loader\.ts$/, reason: "execution-constraints-changed" },
|
|
242
|
+
{ pattern: /\/map\//, reason: "ownership-routing-changed" },
|
|
243
|
+
{ pattern: /\/atlas\.ts$/, reason: "ownership-routing-changed" },
|
|
244
|
+
{ pattern: /\/inference\.ts$/, reason: "ownership-routing-changed" },
|
|
245
|
+
{ pattern: /\/dispatch/, reason: "operational-behavior-changed" },
|
|
246
|
+
{ pattern: /\/worker\.ts$/, reason: "operational-behavior-changed" },
|
|
247
|
+
{ pattern: /\/parent\.ts$/, reason: "operational-behavior-changed" },
|
|
248
|
+
{ pattern: /\/finalize\//, reason: "operational-behavior-changed" },
|
|
249
|
+
{ pattern: /\/cognition\//, reason: "operational-behavior-changed" },
|
|
250
|
+
];
|
|
251
|
+
/**
|
|
252
|
+
* Determine whether a set of touched files warrants a POLARIS.md update.
|
|
253
|
+
* Returns the reasons found; empty array means no update warranted.
|
|
254
|
+
*/
|
|
255
|
+
function detectOperationalReasons(touchedFiles) {
|
|
256
|
+
const reasonSet = new Set();
|
|
257
|
+
for (const file of touchedFiles) {
|
|
258
|
+
// Skip test files, comments-only changes, etc.
|
|
259
|
+
if (file.endsWith(".test.ts") || file.endsWith(".test.js"))
|
|
260
|
+
continue;
|
|
261
|
+
if (file.endsWith(".md") || file.endsWith(".json")) {
|
|
262
|
+
// Only config json or doc-linking md are operationally relevant
|
|
263
|
+
if (!file.includes("config") && !file.endsWith("POLARIS.md") && !file.endsWith("SUMMARY.md")) {
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
for (const signal of OPERATIONAL_SIGNALS) {
|
|
268
|
+
if (signal.pattern.test(file)) {
|
|
269
|
+
reasonSet.add(signal.reason);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return Array.from(reasonSet);
|
|
274
|
+
}
|
|
275
|
+
// ── Missing surface detection ─────────────────────────────────────────────────
|
|
276
|
+
/**
|
|
277
|
+
* For each touched file, check whether its nearest eligible folder has a
|
|
278
|
+
* POLARIS.md. Return folders that are newly eligible but missing one.
|
|
279
|
+
*/
|
|
280
|
+
function detectMissingCognitionSurfaces(touchedFiles, repoRoot, skipRoot) {
|
|
281
|
+
const missing = new Set();
|
|
282
|
+
for (const file of touchedFiles) {
|
|
283
|
+
const parts = file.split("/");
|
|
284
|
+
for (let i = parts.length - 1; i >= 1; i--) {
|
|
285
|
+
const dir = parts.slice(0, i).join("/");
|
|
286
|
+
if (isCognitionSkippedFolder(dir, repoRoot))
|
|
287
|
+
continue;
|
|
288
|
+
const candidate = (0, node_path_1.join)(repoRoot, dir, "POLARIS.md");
|
|
289
|
+
if (!(0, node_fs_1.existsSync)(candidate)) {
|
|
290
|
+
// Only report if there are source files in this folder (not empty dirs)
|
|
291
|
+
missing.add(dir);
|
|
292
|
+
}
|
|
293
|
+
break; // Only report the nearest ancestor folder per file
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return Array.from(missing);
|
|
297
|
+
}
|
|
298
|
+
// ── Main delta entry point ────────────────────────────────────────────────────
|
|
299
|
+
/**
|
|
300
|
+
* Apply route-local cognition delta logic after child implementation.
|
|
301
|
+
*
|
|
302
|
+
* Does NOT write POLARIS.md. Returns a result describing what was found so
|
|
303
|
+
* that the worker prompt can instruct the agent accordingly, or the caller can
|
|
304
|
+
* conditionally trigger an update.
|
|
305
|
+
*/
|
|
306
|
+
function applyRouteCognitionDelta(options) {
|
|
307
|
+
const { repoRoot, touchedFiles, skipRoot = true } = options;
|
|
308
|
+
const inspectedFolders = new Set();
|
|
309
|
+
const routeLocalTargets = new Set();
|
|
310
|
+
const missingCognitionSurfaces = [];
|
|
311
|
+
for (const file of touchedFiles) {
|
|
312
|
+
const dir = (0, node_path_1.dirname)(file);
|
|
313
|
+
if (dir && dir !== ".") {
|
|
314
|
+
if (!isCognitionSkippedFolder(dir)) {
|
|
315
|
+
inspectedFolders.add(dir);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
const target = findNearestRoutePolarismd(file, repoRoot, skipRoot);
|
|
319
|
+
if (target)
|
|
320
|
+
routeLocalTargets.add(target);
|
|
321
|
+
}
|
|
322
|
+
const reasons = detectOperationalReasons(touchedFiles);
|
|
323
|
+
const updateWarranted = reasons.length > 0;
|
|
324
|
+
const missing = detectMissingCognitionSurfaces(touchedFiles, repoRoot, skipRoot);
|
|
325
|
+
missingCognitionSurfaces.push(...missing);
|
|
326
|
+
return {
|
|
327
|
+
inspectedFolders: Array.from(inspectedFolders),
|
|
328
|
+
routeLocalTargets: Array.from(routeLocalTargets),
|
|
329
|
+
updateWarranted,
|
|
330
|
+
reasons,
|
|
331
|
+
missingCognitionSurfaces,
|
|
332
|
+
healthState: undefined,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
// ── POLARIS.md content read helper ────────────────────────────────────────────
|
|
336
|
+
/**
|
|
337
|
+
* Read the content of a route-local POLARIS.md (relative to repoRoot).
|
|
338
|
+
* Returns null when missing.
|
|
339
|
+
*/
|
|
340
|
+
function readRoutePolarismd(polarisRel, repoRoot) {
|
|
341
|
+
const abs = (0, node_path_1.resolve)(repoRoot, polarisRel);
|
|
342
|
+
if (!(0, node_fs_1.existsSync)(abs))
|
|
343
|
+
return null;
|
|
344
|
+
try {
|
|
345
|
+
return (0, node_fs_1.readFileSync)(abs, "utf-8");
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SUMMARY.md delta logic.
|
|
4
|
+
*
|
|
5
|
+
* SUMMARY.md is the informational/contextual compression surface for a route.
|
|
6
|
+
* It must stay short, remain informational only, and never become operational
|
|
7
|
+
* doctrine or a giant architecture dump.
|
|
8
|
+
*
|
|
9
|
+
* Workers and ingest pipelines call applySummaryDelta() to determine whether
|
|
10
|
+
* a SUMMARY.md update is warranted. Updates are delta-only: only spec/canon/
|
|
11
|
+
* architecture/doctrine-linkage changes trigger a SUMMARY.md refresh.
|
|
12
|
+
*
|
|
13
|
+
* Root SUMMARY.md is skipped — route-local cognition begins below root.
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.SUMMARY_MAX_BYTES = void 0;
|
|
17
|
+
exports.detectPrecedenceLevel = detectPrecedenceLevel;
|
|
18
|
+
exports.detectSummaryReasons = detectSummaryReasons;
|
|
19
|
+
exports.detectSourcePathSignals = detectSourcePathSignals;
|
|
20
|
+
exports.findNearestSummarymd = findNearestSummarymd;
|
|
21
|
+
exports.detectMissingSummaries = detectMissingSummaries;
|
|
22
|
+
exports.applySummaryDelta = applySummaryDelta;
|
|
23
|
+
exports.readSummarymd = readSummarymd;
|
|
24
|
+
exports.isSummaryOversized = isSummaryOversized;
|
|
25
|
+
exports.hasDoctrineBled = hasDoctrineBled;
|
|
26
|
+
const node_fs_1 = require("node:fs");
|
|
27
|
+
const node_path_1 = require("node:path");
|
|
28
|
+
const route_cognition_delta_js_1 = require("./route-cognition-delta.js");
|
|
29
|
+
const doctrine_js_1 = require("../smartdocs-engine/doctrine.js");
|
|
30
|
+
// ── Signals that trigger SUMMARY.md delta ─────────────────────────────────────
|
|
31
|
+
const SUMMARY_SIGNALS = [
|
|
32
|
+
{ pattern: /smartdocs\/docs\/specs\/raw\//, reason: "linked-docs-changed" },
|
|
33
|
+
{ pattern: /smartdocs\/docs\/specs\/active\//, reason: "linked-docs-changed" },
|
|
34
|
+
{ pattern: /smartdocs\/docs\/doctrine\/active\//, reason: "doctrine-spec-linkage-changed" },
|
|
35
|
+
{ pattern: /smartdocs\/docs\/architecture\//, reason: "architecture-meaning-changed" },
|
|
36
|
+
{ pattern: /smartdocs\/docs\/decisions\//, reason: "doctrine-spec-linkage-changed" },
|
|
37
|
+
{ pattern: /\/canon\//, reason: "canon-relationships-changed" },
|
|
38
|
+
{ pattern: /POLARIS\.md$/, reason: "canon-relationships-changed" },
|
|
39
|
+
{ pattern: /AGENTS\.md$/, reason: "canon-relationships-changed" },
|
|
40
|
+
{ pattern: /CLAUDE\.md$/, reason: "canon-relationships-changed" },
|
|
41
|
+
{ pattern: /polaris\.config\.json$/, reason: "canon-relationships-changed" },
|
|
42
|
+
];
|
|
43
|
+
/**
|
|
44
|
+
* Determine the highest-precedence cognition source for the given touched files.
|
|
45
|
+
*
|
|
46
|
+
* Priority (highest first):
|
|
47
|
+
* 1. promoted-doctrine — active doctrine files in smartdocs/doctrine/active/
|
|
48
|
+
* 2. spec-or-arch — linked specs, architecture, or decision docs
|
|
49
|
+
* 3. route-polaris-md — a route-local POLARIS.md was touched
|
|
50
|
+
* 4. source-inference — no above signals; fallback
|
|
51
|
+
*/
|
|
52
|
+
function detectPrecedenceLevel(touchedFiles) {
|
|
53
|
+
for (const file of touchedFiles) {
|
|
54
|
+
if (/smartdocs\/docs\/doctrine\/active\//.test(file))
|
|
55
|
+
return "promoted-doctrine";
|
|
56
|
+
}
|
|
57
|
+
for (const file of touchedFiles) {
|
|
58
|
+
if (/smartdocs\/docs\/specs\/active\//.test(file) ||
|
|
59
|
+
/smartdocs\/docs\/architecture\//.test(file) ||
|
|
60
|
+
/smartdocs\/docs\/decisions\//.test(file) ||
|
|
61
|
+
/smartdocs\/docs\/specs\/raw\//.test(file))
|
|
62
|
+
return "spec-or-arch";
|
|
63
|
+
}
|
|
64
|
+
for (const file of touchedFiles) {
|
|
65
|
+
if (/POLARIS\.md$/.test(file))
|
|
66
|
+
return "route-polaris-md";
|
|
67
|
+
}
|
|
68
|
+
return "source-inference";
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Determine which summary-related delta reasons are triggered by the provided file paths.
|
|
72
|
+
*
|
|
73
|
+
* @param touchedFiles - List of touched file paths (relative-like strings) to evaluate against summary signal patterns
|
|
74
|
+
* @returns An array of unique `SummaryDeltaReason` values that matched any signal pattern for the given files
|
|
75
|
+
*/
|
|
76
|
+
function detectSummaryReasons(touchedFiles) {
|
|
77
|
+
const reasonSet = new Set();
|
|
78
|
+
for (const file of touchedFiles) {
|
|
79
|
+
for (const signal of SUMMARY_SIGNALS) {
|
|
80
|
+
if (signal.pattern.test(file)) {
|
|
81
|
+
reasonSet.add(signal.reason);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return Array.from(reasonSet);
|
|
86
|
+
}
|
|
87
|
+
// ── source_paths enrichment ───────────────────────────────────────────────────
|
|
88
|
+
const SMARTDOC_SCAN_DIRS = [
|
|
89
|
+
"smartdocs/doctrine/active",
|
|
90
|
+
"smartdocs/specs/active",
|
|
91
|
+
];
|
|
92
|
+
/**
|
|
93
|
+
* Detects whether any active SmartDoc declares a `source_paths` entry that exactly matches a touched file.
|
|
94
|
+
*
|
|
95
|
+
* @param touchedFiles - Array of touched file paths (normalized-ish strings, typically repository-relative)
|
|
96
|
+
* @param repoRoot - Filesystem path to the repository root used to locate active SmartDocs
|
|
97
|
+
* @returns `true` if at least one `source_paths` entry in an active SmartDoc matches a touched file, `false` otherwise
|
|
98
|
+
*/
|
|
99
|
+
function detectSourcePathSignals(touchedFiles, repoRoot) {
|
|
100
|
+
if (touchedFiles.length === 0)
|
|
101
|
+
return false;
|
|
102
|
+
const touchedSet = new Set(touchedFiles.map((f) => f.replace(/\\/g, "/")));
|
|
103
|
+
for (const scanDir of SMARTDOC_SCAN_DIRS) {
|
|
104
|
+
const absDir = (0, node_path_1.join)(repoRoot, scanDir);
|
|
105
|
+
if (!(0, node_fs_1.existsSync)(absDir))
|
|
106
|
+
continue;
|
|
107
|
+
let entries;
|
|
108
|
+
try {
|
|
109
|
+
entries = (0, node_fs_1.readdirSync)(absDir);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
for (const entry of entries) {
|
|
115
|
+
if (!entry.endsWith(".md"))
|
|
116
|
+
continue;
|
|
117
|
+
const absFile = (0, node_path_1.join)(absDir, entry);
|
|
118
|
+
let content;
|
|
119
|
+
try {
|
|
120
|
+
content = (0, node_fs_1.readFileSync)(absFile, "utf-8");
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const fm = (0, doctrine_js_1.parseFrontMatter)(content);
|
|
126
|
+
const sourcePaths = fm["source_paths"];
|
|
127
|
+
if (!sourcePaths)
|
|
128
|
+
continue;
|
|
129
|
+
const paths = sourcePaths
|
|
130
|
+
.split(",")
|
|
131
|
+
.map((p) => p.trim())
|
|
132
|
+
.filter(Boolean);
|
|
133
|
+
for (const sp of paths) {
|
|
134
|
+
if (touchedSet.has(sp))
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
// ── Nearest route SUMMARY.md ──────────────────────────────────────────────────
|
|
142
|
+
/**
|
|
143
|
+
* Find the nearest SUMMARY.md relative to a given file (below root).
|
|
144
|
+
* Returns null when none found or only root exists.
|
|
145
|
+
*/
|
|
146
|
+
function findNearestSummarymd(filePath, repoRoot, skipRoot) {
|
|
147
|
+
const parts = filePath.split("/");
|
|
148
|
+
for (let i = parts.length - 1; i >= 1; i--) {
|
|
149
|
+
const dir = parts.slice(0, i).join("/");
|
|
150
|
+
if ((0, route_cognition_delta_js_1.isCognitionSkippedFolder)(dir, repoRoot))
|
|
151
|
+
continue;
|
|
152
|
+
const candidate = (0, node_path_1.join)(repoRoot, dir, "SUMMARY.md");
|
|
153
|
+
if ((0, node_fs_1.existsSync)(candidate)) {
|
|
154
|
+
return (0, node_path_1.relative)(repoRoot, candidate).replace(/\\/g, "/");
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (!skipRoot) {
|
|
158
|
+
const rootCandidate = (0, node_path_1.join)(repoRoot, "SUMMARY.md");
|
|
159
|
+
if ((0, node_fs_1.existsSync)(rootCandidate))
|
|
160
|
+
return "SUMMARY.md";
|
|
161
|
+
}
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
// ── Missing SUMMARY.md detection ──────────────────────────────────────────────
|
|
165
|
+
/**
|
|
166
|
+
* For each touched file, find folders that have a POLARIS.md but are missing a
|
|
167
|
+
* SUMMARY.md. These are eligible for a SUMMARY.md draft.
|
|
168
|
+
*
|
|
169
|
+
* Informational only — callers should surface this as a hint, not pressure.
|
|
170
|
+
* Workers must NOT create SUMMARY.md automatically from this result.
|
|
171
|
+
*/
|
|
172
|
+
function detectMissingSummaries(touchedFiles, repoRoot, skipRoot) {
|
|
173
|
+
const missing = new Set();
|
|
174
|
+
for (const file of touchedFiles) {
|
|
175
|
+
const parts = file.split("/");
|
|
176
|
+
for (let i = parts.length - 1; i >= 1; i--) {
|
|
177
|
+
const dir = parts.slice(0, i).join("/");
|
|
178
|
+
if ((0, route_cognition_delta_js_1.isCognitionSkippedFolder)(dir, repoRoot))
|
|
179
|
+
continue;
|
|
180
|
+
const hasPolarismd = (0, node_fs_1.existsSync)((0, node_path_1.join)(repoRoot, dir, "POLARIS.md"));
|
|
181
|
+
const hasSummarymd = (0, node_fs_1.existsSync)((0, node_path_1.join)(repoRoot, dir, "SUMMARY.md"));
|
|
182
|
+
if (hasPolarismd && !hasSummarymd) {
|
|
183
|
+
missing.add(dir);
|
|
184
|
+
}
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
if (!skipRoot) {
|
|
188
|
+
const hasPolarismd = (0, node_fs_1.existsSync)((0, node_path_1.join)(repoRoot, "POLARIS.md"));
|
|
189
|
+
const hasSummarymd = (0, node_fs_1.existsSync)((0, node_path_1.join)(repoRoot, "SUMMARY.md"));
|
|
190
|
+
if (hasPolarismd && !hasSummarymd) {
|
|
191
|
+
missing.add(".");
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return Array.from(missing);
|
|
196
|
+
}
|
|
197
|
+
// ── Main delta entry point ────────────────────────────────────────────────────
|
|
198
|
+
/**
|
|
199
|
+
* Determine whether a route-local SUMMARY.md should be updated based on the given touched files.
|
|
200
|
+
*
|
|
201
|
+
* Does not modify repository files; analyzes touched paths and repository state to decide
|
|
202
|
+
* whether a SUMMARY.md update is warranted and where updates or drafts might be targeted.
|
|
203
|
+
*
|
|
204
|
+
* @param options - Analysis options: `repoRoot` (repository root path), `touchedFiles` (list of touched file paths), and `skipRoot` (optional; when true, exclude root `SUMMARY.md` from consideration; defaults to `true`)
|
|
205
|
+
* @returns A `SummaryDeltaResult` containing:
|
|
206
|
+
* - `updateWarranted`: `true` if an update is recommended, `false` otherwise;
|
|
207
|
+
* - `reasons`: triggered `SummaryDeltaReason` values;
|
|
208
|
+
* - `summaryTargets`: relative paths of nearest `SUMMARY.md` candidates;
|
|
209
|
+
* - `missingSummaries`: directories that contain `POLARIS.md` but lack `SUMMARY.md`;
|
|
210
|
+
* - `precedenceSource`: computed `SummaryPrecedenceLevel`.
|
|
211
|
+
*/
|
|
212
|
+
function applySummaryDelta(options) {
|
|
213
|
+
const { repoRoot, touchedFiles, skipRoot = true } = options;
|
|
214
|
+
const reasons = detectSummaryReasons(touchedFiles);
|
|
215
|
+
// Enrich signals: if any touched file appears in source_paths of an active SmartDoc,
|
|
216
|
+
// treat this as a linked-docs-changed signal even if path patterns didn't match.
|
|
217
|
+
if (detectSourcePathSignals(touchedFiles, repoRoot)) {
|
|
218
|
+
if (!reasons.includes("linked-docs-changed")) {
|
|
219
|
+
reasons.push("linked-docs-changed");
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const updateWarranted = reasons.length > 0;
|
|
223
|
+
const precedenceSource = detectPrecedenceLevel(touchedFiles);
|
|
224
|
+
const summaryTargets = new Set();
|
|
225
|
+
for (const file of touchedFiles) {
|
|
226
|
+
const target = findNearestSummarymd(file, repoRoot, skipRoot);
|
|
227
|
+
if (target)
|
|
228
|
+
summaryTargets.add(target);
|
|
229
|
+
}
|
|
230
|
+
const missingSummaries = detectMissingSummaries(touchedFiles, repoRoot, skipRoot);
|
|
231
|
+
return {
|
|
232
|
+
updateWarranted,
|
|
233
|
+
reasons,
|
|
234
|
+
summaryTargets: Array.from(summaryTargets),
|
|
235
|
+
missingSummaries,
|
|
236
|
+
precedenceSource,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
// ── SUMMARY.md content guard ──────────────────────────────────────────────────
|
|
240
|
+
exports.SUMMARY_MAX_BYTES = 4096;
|
|
241
|
+
/**
|
|
242
|
+
* Read SUMMARY.md content (relative to repoRoot). Returns null when missing.
|
|
243
|
+
*/
|
|
244
|
+
function readSummarymd(summaryRel, repoRoot) {
|
|
245
|
+
const abs = (0, node_path_1.resolve)(repoRoot, summaryRel);
|
|
246
|
+
if (!(0, node_fs_1.existsSync)(abs))
|
|
247
|
+
return null;
|
|
248
|
+
try {
|
|
249
|
+
return (0, node_fs_1.readFileSync)(abs, "utf-8");
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Returns true when a SUMMARY.md content string exceeds the size guard.
|
|
257
|
+
*/
|
|
258
|
+
function isSummaryOversized(content) {
|
|
259
|
+
return Buffer.byteLength(content, "utf-8") > exports.SUMMARY_MAX_BYTES;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Returns true when a SUMMARY.md contains strong doctrine bleed patterns
|
|
263
|
+
* (operational imperatives that belong in POLARIS.md).
|
|
264
|
+
*
|
|
265
|
+
* Deliberately narrow to avoid false-positives from quoted text or normal
|
|
266
|
+
* prose that happens to contain "do not" or "never" in context.
|
|
267
|
+
* This check is WARN-only — it must never be a hard blocker.
|
|
268
|
+
*/
|
|
269
|
+
function hasDoctrineBled(content) {
|
|
270
|
+
const lower = content.toLowerCase();
|
|
271
|
+
// Only flag unambiguous operational section headings and strong imperatives
|
|
272
|
+
// that clearly belong in POLARIS.md rather than informational summaries.
|
|
273
|
+
return (lower.includes("## editing rules") ||
|
|
274
|
+
lower.includes("## constraints") ||
|
|
275
|
+
lower.includes("## stop rule") ||
|
|
276
|
+
lower.includes("## forbidden") ||
|
|
277
|
+
// Strong imperative pattern: "must always" or "must never" at line start
|
|
278
|
+
/^\s*[-*]?\s*\w[^.]*\bmust\s+(always|never)\b/m.test(lower));
|
|
279
|
+
}
|