@massu/core 1.10.8 → 1.12.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.
Files changed (178) hide show
  1. package/CHANGELOG.md +1303 -0
  2. package/dist/adapter.js +6 -4
  3. package/dist/cli.js +517 -397
  4. package/dist/detect/adapters/.bundle-shasums.json +4 -7
  5. package/dist/detect/adapters/rails.js +6 -4
  6. package/dist/detect/adapters/spring.js +6 -4
  7. package/dist/detect/adapters/tree-sitter-loader.js +6 -4
  8. package/dist/hooks/auto-learning-pipeline.js +24 -1
  9. package/dist/hooks/classify-failure.js +32 -3
  10. package/dist/hooks/cost-tracker.js +24 -0
  11. package/dist/hooks/fix-detector.js +97 -1
  12. package/dist/hooks/incident-pipeline.js +30 -1
  13. package/dist/hooks/intent-suggester.js +6 -3
  14. package/dist/hooks/post-edit-context.js +24 -1
  15. package/dist/hooks/post-tool-use.js +66 -18
  16. package/dist/hooks/pre-compact.js +27 -2
  17. package/dist/hooks/pre-delete-check.js +48 -22
  18. package/dist/hooks/pre-tool-use-gate.js +962 -0
  19. package/dist/hooks/quality-event.js +24 -0
  20. package/dist/hooks/rule-enforcement-pipeline.js +24 -1
  21. package/dist/hooks/security-gate.js +29 -16
  22. package/dist/hooks/session-end.js +24 -0
  23. package/dist/hooks/session-start.js +8710 -8460
  24. package/dist/hooks/user-prompt.js +24 -0
  25. package/package.json +15 -18
  26. package/commands/README.md +0 -134
  27. package/src/adapter.ts +0 -121
  28. package/src/adr-generator.ts +0 -292
  29. package/src/analytics.ts +0 -373
  30. package/src/audit-trail.ts +0 -450
  31. package/src/backfill-sessions.ts +0 -182
  32. package/src/changelog-generator.ts +0 -178
  33. package/src/cli.ts +0 -246
  34. package/src/cloud-sync.ts +0 -234
  35. package/src/commands/adapters.ts +0 -824
  36. package/src/commands/changelog.ts +0 -165
  37. package/src/commands/config-check-drift.ts +0 -133
  38. package/src/commands/config-refresh.ts +0 -396
  39. package/src/commands/config-upgrade.ts +0 -127
  40. package/src/commands/doctor.ts +0 -485
  41. package/src/commands/hook-runner.ts +0 -145
  42. package/src/commands/init.ts +0 -1823
  43. package/src/commands/install-commands.ts +0 -692
  44. package/src/commands/install-hooks.ts +0 -27
  45. package/src/commands/permissions.ts +0 -150
  46. package/src/commands/refresh-log.ts +0 -37
  47. package/src/commands/show-template.ts +0 -65
  48. package/src/commands/template-engine.ts +0 -301
  49. package/src/commands/watch.ts +0 -430
  50. package/src/config.ts +0 -765
  51. package/src/cost-tracker.ts +0 -355
  52. package/src/db.ts +0 -370
  53. package/src/dependency-scorer.ts +0 -337
  54. package/src/detect/adapters/aspnet.ts +0 -4
  55. package/src/detect/adapters/discover.ts +0 -469
  56. package/src/detect/adapters/file-sampler.ts +0 -225
  57. package/src/detect/adapters/go-chi.ts +0 -4
  58. package/src/detect/adapters/index.ts +0 -51
  59. package/src/detect/adapters/nextjs-trpc.ts +0 -166
  60. package/src/detect/adapters/parse-guard.ts +0 -133
  61. package/src/detect/adapters/phoenix.ts +0 -4
  62. package/src/detect/adapters/python-django.ts +0 -208
  63. package/src/detect/adapters/python-fastapi.ts +0 -223
  64. package/src/detect/adapters/python-flask.ts +0 -235
  65. package/src/detect/adapters/query-helpers.ts +0 -170
  66. package/src/detect/adapters/rails.ts +0 -4
  67. package/src/detect/adapters/runner.ts +0 -284
  68. package/src/detect/adapters/spring.ts +0 -4
  69. package/src/detect/adapters/swift-swiftui.ts +0 -171
  70. package/src/detect/adapters/tree-sitter-loader.ts +0 -517
  71. package/src/detect/adapters/types.ts +0 -191
  72. package/src/detect/codebase-introspector.ts +0 -210
  73. package/src/detect/domain-inferrer.ts +0 -197
  74. package/src/detect/drift.ts +0 -199
  75. package/src/detect/framework-detector.ts +0 -307
  76. package/src/detect/index.ts +0 -209
  77. package/src/detect/manifest-registry.ts +0 -261
  78. package/src/detect/migrate.ts +0 -362
  79. package/src/detect/monorepo-detector.ts +0 -348
  80. package/src/detect/package-detector.ts +0 -828
  81. package/src/detect/passthrough.ts +0 -108
  82. package/src/detect/regex-fallback.ts +0 -449
  83. package/src/detect/source-dir-detector.ts +0 -297
  84. package/src/detect/vr-command-map.ts +0 -167
  85. package/src/docs-map.json +0 -100
  86. package/src/docs-tools.ts +0 -519
  87. package/src/domains.ts +0 -182
  88. package/src/hooks/auto-learning-pipeline.ts +0 -228
  89. package/src/hooks/classify-failure.ts +0 -259
  90. package/src/hooks/cost-tracker.ts +0 -66
  91. package/src/hooks/fix-detector.ts +0 -186
  92. package/src/hooks/incident-pipeline.ts +0 -190
  93. package/src/hooks/intent-suggester.ts +0 -131
  94. package/src/hooks/post-edit-context.ts +0 -91
  95. package/src/hooks/post-tool-use.ts +0 -413
  96. package/src/hooks/pre-compact.ts +0 -168
  97. package/src/hooks/pre-delete-check.ts +0 -243
  98. package/src/hooks/quality-event.ts +0 -127
  99. package/src/hooks/rule-enforcement-pipeline.ts +0 -159
  100. package/src/hooks/security-gate.ts +0 -153
  101. package/src/hooks/session-end.ts +0 -467
  102. package/src/hooks/session-start.ts +0 -436
  103. package/src/hooks/user-prompt.ts +0 -157
  104. package/src/import-resolver.ts +0 -226
  105. package/src/knowledge-db.ts +0 -176
  106. package/src/knowledge-indexer.ts +0 -708
  107. package/src/knowledge-tools.ts +0 -1414
  108. package/src/lib/fileLock.ts +0 -203
  109. package/src/lib/framework-supports.ts +0 -85
  110. package/src/lib/gitToplevel.ts +0 -22
  111. package/src/lib/hook-registry.ts +0 -43
  112. package/src/lib/installLock.ts +0 -66
  113. package/src/lib/memory-path.ts +0 -49
  114. package/src/lib/pidLiveness.ts +0 -67
  115. package/src/lib/settings-local.ts +0 -110
  116. package/src/lib/sql-table-names.ts +0 -71
  117. package/src/license.ts +0 -547
  118. package/src/lsp/auto-detect.ts +0 -98
  119. package/src/lsp/client.ts +0 -776
  120. package/src/lsp/enrich.ts +0 -127
  121. package/src/lsp/types.ts +0 -221
  122. package/src/memory-db.ts +0 -1614
  123. package/src/memory-file-ingest.ts +0 -128
  124. package/src/memory-tools.ts +0 -424
  125. package/src/middleware-tree.ts +0 -71
  126. package/src/observability-tools.ts +0 -343
  127. package/src/observation-extractor.ts +0 -418
  128. package/src/page-deps.ts +0 -285
  129. package/src/permissions.ts +0 -422
  130. package/src/prompt-analyzer.ts +0 -332
  131. package/src/python/coupling-detector.ts +0 -125
  132. package/src/python/domain-enforcer.ts +0 -84
  133. package/src/python/impact-analyzer.ts +0 -96
  134. package/src/python/import-parser.ts +0 -244
  135. package/src/python/import-resolver.ts +0 -136
  136. package/src/python/migration-indexer.ts +0 -116
  137. package/src/python/migration-parser.ts +0 -332
  138. package/src/python/model-indexer.ts +0 -71
  139. package/src/python/model-parser.ts +0 -279
  140. package/src/python/route-indexer.ts +0 -59
  141. package/src/python/route-parser.ts +0 -317
  142. package/src/python-tools.ts +0 -630
  143. package/src/regression-detector.ts +0 -319
  144. package/src/rules.ts +0 -57
  145. package/src/schema-mapper.ts +0 -232
  146. package/src/security/adapter-origin.ts +0 -130
  147. package/src/security/adapter-verifier.ts +0 -319
  148. package/src/security/atomic-write.ts +0 -164
  149. package/src/security/fetcher.ts +0 -200
  150. package/src/security/install-tracking.ts +0 -319
  151. package/src/security/license-pubkey.generated.ts +0 -30
  152. package/src/security/license-response-verifier.ts +0 -149
  153. package/src/security/local-fingerprint.ts +0 -225
  154. package/src/security/manifest-cache.ts +0 -333
  155. package/src/security/manifest-schema.ts +0 -129
  156. package/src/security/registry-pubkey.generated.ts +0 -35
  157. package/src/security/telemetry.ts +0 -320
  158. package/src/security-scorer.ts +0 -405
  159. package/src/security-utils.ts +0 -133
  160. package/src/sentinel-db.ts +0 -580
  161. package/src/sentinel-scanner.ts +0 -406
  162. package/src/sentinel-tools.ts +0 -513
  163. package/src/sentinel-types.ts +0 -140
  164. package/src/server-dispatch.ts +0 -225
  165. package/src/server.ts +0 -107
  166. package/src/session-archiver.ts +0 -111
  167. package/src/session-state-generator.ts +0 -174
  168. package/src/team-knowledge.ts +0 -407
  169. package/src/tool-db-needs.ts +0 -232
  170. package/src/tools.ts +0 -1206
  171. package/src/transcript-parser.ts +0 -458
  172. package/src/trpc-index.ts +0 -215
  173. package/src/validate-features-runner.ts +0 -107
  174. package/src/validation-engine.ts +0 -358
  175. package/src/watch/daemon.ts +0 -385
  176. package/src/watch/lockfile-detector.ts +0 -65
  177. package/src/watch/paths.ts +0 -279
  178. package/src/watch/state.ts +0 -178
package/dist/cli.js CHANGED
@@ -146,6 +146,8 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
146
146
  accessScopes: parsed.accessScopes,
147
147
  domains: parsed.domains,
148
148
  rules: parsed.rules,
149
+ // P-M-036: customer-authored CR-style governance rules.
150
+ governance_rules: parsed.governance_rules,
149
151
  analytics: parsed.analytics,
150
152
  governance: parsed.governance,
151
153
  security: parsed.security,
@@ -548,6 +550,22 @@ var init_config = __esm({
548
550
  accessScopes: z.array(z.string()).optional(),
549
551
  domains: z.array(DomainConfigSchema).default([]),
550
552
  rules: z.array(PatternRuleConfigSchema).default([]),
553
+ // P-M-036 (plan-stage-d-medium-sweep): customer-authored CR-style
554
+ // governance rules. DISTINCT from `rules:` above (path-scoped lint hints
555
+ // used by pattern-scanner). At config-refresh time these entries are
556
+ // loaded into the `knowledge_rules` SQLite table with
557
+ // `source = 'customer-config'` so `massu_knowledge_rule` and the
558
+ // governance docs surface customer-defined rules alongside framework CRs.
559
+ governance_rules: z.array(
560
+ z.object({
561
+ id: z.string().min(1, "governance_rules[].id is required"),
562
+ title: z.string().min(1, "governance_rules[].title is required"),
563
+ description: z.string().min(1, "governance_rules[].description is required"),
564
+ vr_type: z.string().default("VR-CUSTOM"),
565
+ reference_path: z.string().optional(),
566
+ severity: z.enum(["critical", "high", "medium", "low", "info"]).default("medium")
567
+ }).passthrough()
568
+ ).default([]),
551
569
  analytics: AnalyticsConfigSchema,
552
570
  governance: GovernanceConfigSchema,
553
571
  security: SecurityConfigSchema,
@@ -581,6 +599,7 @@ var init_config = __esm({
581
599
  // src/memory-db.ts
582
600
  var memory_db_exports = {};
583
601
  __export(memory_db_exports, {
602
+ TOOL_COST_EVENTS_RETENTION_DAYS: () => TOOL_COST_EVENTS_RETENTION_DAYS,
584
603
  addConversationTurn: () => addConversationTurn,
585
604
  addFailureClass: () => addFailureClass,
586
605
  addObservation: () => addObservation,
@@ -614,6 +633,7 @@ __export(memory_db_exports, {
614
633
  linkSessionToTask: () => linkSessionToTask,
615
634
  pruneOldConversationTurns: () => pruneOldConversationTurns,
616
635
  pruneOldObservations: () => pruneOldObservations,
636
+ pruneToolCostEvents: () => pruneToolCostEvents,
617
637
  removePendingSync: () => removePendingSync,
618
638
  sanitizeFts5Query: () => sanitizeFts5Query,
619
639
  scoreFailureClasses: () => scoreFailureClasses,
@@ -642,6 +662,12 @@ function getMemoryDb() {
642
662
  initMemorySchema(db);
643
663
  return db;
644
664
  }
665
+ function pruneToolCostEvents(db) {
666
+ const result = db.prepare(
667
+ `DELETE FROM tool_cost_events WHERE created_at < datetime('now', '-' || ? || ' days')`
668
+ ).run(TOOL_COST_EVENTS_RETENTION_DAYS);
669
+ return result.changes;
670
+ }
645
671
  function initMemorySchema(db) {
646
672
  db.exec(`
647
673
  -- Sessions table (linked to Claude Code session IDs)
@@ -1116,6 +1142,12 @@ function initMemorySchema(db) {
1116
1142
  features TEXT DEFAULT '[]'
1117
1143
  );
1118
1144
  `);
1145
+ const licenseCacheCols = db.prepare(`PRAGMA table_info(license_cache)`).all();
1146
+ if (!licenseCacheCols.some((c2) => c2.name === "signed_payload_json")) {
1147
+ db.exec(
1148
+ `ALTER TABLE license_cache ADD COLUMN signed_payload_json TEXT NOT NULL DEFAULT ''`
1149
+ );
1150
+ }
1119
1151
  db.exec(`
1120
1152
  CREATE TABLE IF NOT EXISTS failure_classes (
1121
1153
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -1688,10 +1720,12 @@ function scoreFailureClasses(db, matchText, filePath, promptContext, weights) {
1688
1720
  }
1689
1721
  return bestMatch;
1690
1722
  }
1723
+ var TOOL_COST_EVENTS_RETENTION_DAYS;
1691
1724
  var init_memory_db = __esm({
1692
1725
  "src/memory-db.ts"() {
1693
1726
  "use strict";
1694
1727
  init_config();
1728
+ TOOL_COST_EVENTS_RETENTION_DAYS = 90;
1695
1729
  }
1696
1730
  });
1697
1731
 
@@ -2658,6 +2692,40 @@ var init_install_commands = __esm({
2658
2692
  }
2659
2693
  });
2660
2694
 
2695
+ // src/lib/hook-timeouts.ts
2696
+ var HOOK_TIMEOUTS;
2697
+ var init_hook_timeouts = __esm({
2698
+ "src/lib/hook-timeouts.ts"() {
2699
+ "use strict";
2700
+ HOOK_TIMEOUTS = {
2701
+ // Session lifecycle
2702
+ "session-start": 10,
2703
+ "session-end": 15,
2704
+ "pre-compact": 10,
2705
+ // PreToolUse hooks
2706
+ // P-E-019: `pre-tool-use-gate` is the consolidated PreToolUse hook
2707
+ // (combines security-gate + pre-delete-check into one spawn). The
2708
+ // individual entries below are kept for back-compat with operator
2709
+ // settings.json files that still reference them directly.
2710
+ "pre-tool-use-gate": 5,
2711
+ "security-gate": 5,
2712
+ "pre-delete-check": 5,
2713
+ // PostToolUse hooks
2714
+ "post-tool-use": 10,
2715
+ "quality-event": 5,
2716
+ "cost-tracker": 5,
2717
+ "post-edit-context": 5,
2718
+ // Failure-handling hooks
2719
+ "fix-detector": 5,
2720
+ "classify-failure": 5,
2721
+ "incident-pipeline": 5,
2722
+ "rule-enforcement-pipeline": 5,
2723
+ // Auto-learning
2724
+ "auto-learning-pipeline": 10
2725
+ };
2726
+ }
2727
+ });
2728
+
2661
2729
  // src/detect/manifest-registry.ts
2662
2730
  var manifest_registry_exports = {};
2663
2731
  __export(manifest_registry_exports, {
@@ -10567,8 +10635,9 @@ function evictBeyondRetainCount(retain = getCacheRetainCount()) {
10567
10635
  continue;
10568
10636
  }
10569
10637
  if (stat.isSymbolicLink() || !stat.isFile()) {
10570
- console.error(
10571
- `[tree-sitter-loader] cache eviction skipped non-regular file: ${path} (possible symlink attack \u2014 see Phase 3.5 finding F-008).`
10638
+ process.stderr.write(
10639
+ `[tree-sitter-loader] cache eviction skipped non-regular file: ${path} (possible symlink attack \u2014 see Phase 3.5 finding F-008).
10640
+ `
10572
10641
  );
10573
10642
  continue;
10574
10643
  }
@@ -10681,8 +10750,9 @@ async function loadGrammar(language, options = {}) {
10681
10750
  }
10682
10751
  evictBeyondRetainCount();
10683
10752
  } catch (e2) {
10684
- console.error(
10685
- `[tree-sitter-loader] cache write failed for ${language}: ${e2 instanceof Error ? e2.message : String(e2)} \u2014 loading directly from memory.`
10753
+ process.stderr.write(
10754
+ `[tree-sitter-loader] cache write failed for ${language}: ${e2 instanceof Error ? e2.message : String(e2)} \u2014 loading directly from memory.
10755
+ `
10686
10756
  );
10687
10757
  }
10688
10758
  const lang = await Language.load(body);
@@ -11509,14 +11579,6 @@ var init_python_flask = __esm({
11509
11579
  }
11510
11580
  });
11511
11581
 
11512
- // src/detect/adapters/go-chi.ts
11513
- import { goChiAdapter } from "@massu/adapter-go-chi";
11514
- var init_go_chi = __esm({
11515
- "src/detect/adapters/go-chi.ts"() {
11516
- "use strict";
11517
- }
11518
- });
11519
-
11520
11582
  // src/detect/adapters/rails.ts
11521
11583
  import { railsAdapter } from "@massu/adapter-rails";
11522
11584
  var init_rails = __esm({
@@ -11525,22 +11587,6 @@ var init_rails = __esm({
11525
11587
  }
11526
11588
  });
11527
11589
 
11528
- // src/detect/adapters/phoenix.ts
11529
- import { phoenixAdapter } from "@massu/adapter-phoenix";
11530
- var init_phoenix = __esm({
11531
- "src/detect/adapters/phoenix.ts"() {
11532
- "use strict";
11533
- }
11534
- });
11535
-
11536
- // src/detect/adapters/aspnet.ts
11537
- import { aspnetAdapter } from "@massu/adapter-aspnet";
11538
- var init_aspnet = __esm({
11539
- "src/detect/adapters/aspnet.ts"() {
11540
- "use strict";
11541
- }
11542
- });
11543
-
11544
11590
  // src/detect/adapters/spring.ts
11545
11591
  import { springAdapter } from "@massu/adapter-spring";
11546
11592
  var init_spring = __esm({
@@ -11773,10 +11819,7 @@ var init_codebase_introspector = __esm({
11773
11819
  init_nextjs_trpc();
11774
11820
  init_swift_swiftui();
11775
11821
  init_python_flask();
11776
- init_go_chi();
11777
11822
  init_rails();
11778
- init_phoenix();
11779
- init_aspnet();
11780
11823
  init_spring();
11781
11824
  FIRST_PARTY_ADAPTERS = [
11782
11825
  pythonFastApiAdapter,
@@ -11784,10 +11827,7 @@ var init_codebase_introspector = __esm({
11784
11827
  pythonFlaskAdapter,
11785
11828
  nextjsTrpcAdapter,
11786
11829
  swiftSwiftUiAdapter,
11787
- goChiAdapter,
11788
11830
  railsAdapter,
11789
- phoenixAdapter,
11790
- aspnetAdapter,
11791
11831
  springAdapter
11792
11832
  ];
11793
11833
  }
@@ -13648,46 +13688,50 @@ function resolveHooksDir() {
13648
13688
  function hookCmd(version, hookName) {
13649
13689
  return `npx -y @massu/core@${version} hook-runner ${hookName}`;
13650
13690
  }
13691
+ function getHookTimeout(name) {
13692
+ return HOOK_TIMEOUTS[name] ?? 5;
13693
+ }
13651
13694
  function buildHooksConfig(_hooksDir) {
13652
13695
  const version = getInstallerVersion();
13653
13696
  return {
13654
13697
  SessionStart: [
13655
13698
  {
13656
13699
  hooks: [
13657
- { type: "command", command: hookCmd(version, "session-start"), timeout: 10 }
13700
+ { type: "command", command: hookCmd(version, "session-start"), timeout: getHookTimeout("session-start") }
13658
13701
  ]
13659
13702
  }
13660
13703
  ],
13661
13704
  PreToolUse: [
13705
+ // P-E-019 (plan-stage-e-low-info-sweep): consolidated PreToolUse
13706
+ // gate. Single hook spawn covers BOTH security-gate (Bash
13707
+ // dangerous-pattern + Write/Edit protected-path + Python
13708
+ // dangerous-code) AND pre-delete-check (sentinel feature impact +
13709
+ // Python import graph). Cuts ~200ms of cold-start spawn overhead
13710
+ // per tool call. Matcher includes both Bash + Write so the single
13711
+ // hook fires once for every operation either check cares about.
13662
13712
  {
13663
- matcher: "Bash",
13664
- hooks: [
13665
- { type: "command", command: hookCmd(version, "security-gate"), timeout: 5 }
13666
- ]
13667
- },
13668
- {
13669
- matcher: "Bash|Write",
13713
+ matcher: "Bash|Write|Edit",
13670
13714
  hooks: [
13671
- { type: "command", command: hookCmd(version, "pre-delete-check"), timeout: 5 }
13715
+ { type: "command", command: hookCmd(version, "pre-tool-use-gate"), timeout: getHookTimeout("pre-tool-use-gate") }
13672
13716
  ]
13673
13717
  }
13674
13718
  ],
13675
13719
  PostToolUse: [
13676
13720
  {
13677
13721
  hooks: [
13678
- { type: "command", command: hookCmd(version, "post-tool-use"), timeout: 10 },
13679
- { type: "command", command: hookCmd(version, "quality-event"), timeout: 5 },
13680
- { type: "command", command: hookCmd(version, "cost-tracker"), timeout: 5 }
13722
+ { type: "command", command: hookCmd(version, "post-tool-use"), timeout: getHookTimeout("post-tool-use") },
13723
+ { type: "command", command: hookCmd(version, "quality-event"), timeout: getHookTimeout("quality-event") },
13724
+ { type: "command", command: hookCmd(version, "cost-tracker"), timeout: getHookTimeout("cost-tracker") }
13681
13725
  ]
13682
13726
  },
13683
13727
  {
13684
13728
  matcher: "Edit|Write",
13685
13729
  hooks: [
13686
- { type: "command", command: hookCmd(version, "post-edit-context"), timeout: 5 },
13730
+ { type: "command", command: hookCmd(version, "post-edit-context"), timeout: getHookTimeout("post-edit-context") },
13687
13731
  // Auto-learning pipeline — classifies failures and detects fixes on
13688
13732
  // file changes. See Phase 5-6 of the autodetect plan.
13689
- { type: "command", command: hookCmd(version, "fix-detector"), timeout: 5 },
13690
- { type: "command", command: hookCmd(version, "classify-failure"), timeout: 5 }
13733
+ { type: "command", command: hookCmd(version, "fix-detector"), timeout: getHookTimeout("fix-detector") },
13734
+ { type: "command", command: hookCmd(version, "classify-failure"), timeout: getHookTimeout("classify-failure") }
13691
13735
  ]
13692
13736
  },
13693
13737
  {
@@ -13695,32 +13739,32 @@ function buildHooksConfig(_hooksDir) {
13695
13739
  hooks: [
13696
13740
  // Incident + rule enforcement pipelines fire on Write-only (incidents
13697
13741
  // are authored as .md files; rules are enforced after new-file drops).
13698
- { type: "command", command: hookCmd(version, "incident-pipeline"), timeout: 5 },
13699
- { type: "command", command: hookCmd(version, "rule-enforcement-pipeline"), timeout: 5 }
13742
+ { type: "command", command: hookCmd(version, "incident-pipeline"), timeout: getHookTimeout("incident-pipeline") },
13743
+ { type: "command", command: hookCmd(version, "rule-enforcement-pipeline"), timeout: getHookTimeout("rule-enforcement-pipeline") }
13700
13744
  ]
13701
13745
  }
13702
13746
  ],
13703
13747
  Stop: [
13704
13748
  {
13705
13749
  hooks: [
13706
- { type: "command", command: hookCmd(version, "session-end"), timeout: 15 },
13750
+ { type: "command", command: hookCmd(version, "session-end"), timeout: getHookTimeout("session-end") },
13707
13751
  // Session-end auto-learning aggregation (failure-class roll-up).
13708
- { type: "command", command: hookCmd(version, "auto-learning-pipeline"), timeout: 10 }
13752
+ { type: "command", command: hookCmd(version, "auto-learning-pipeline"), timeout: getHookTimeout("auto-learning-pipeline") }
13709
13753
  ]
13710
13754
  }
13711
13755
  ],
13712
13756
  PreCompact: [
13713
13757
  {
13714
13758
  hooks: [
13715
- { type: "command", command: hookCmd(version, "pre-compact"), timeout: 10 }
13759
+ { type: "command", command: hookCmd(version, "pre-compact"), timeout: getHookTimeout("pre-compact") }
13716
13760
  ]
13717
13761
  }
13718
13762
  ],
13719
13763
  UserPromptSubmit: [
13720
13764
  {
13721
13765
  hooks: [
13722
- { type: "command", command: hookCmd(version, "user-prompt"), timeout: 5 },
13723
- { type: "command", command: hookCmd(version, "intent-suggester"), timeout: 5 }
13766
+ { type: "command", command: hookCmd(version, "user-prompt"), timeout: getHookTimeout("user-prompt") },
13767
+ { type: "command", command: hookCmd(version, "intent-suggester"), timeout: getHookTimeout("intent-suggester") }
13724
13768
  ]
13725
13769
  }
13726
13770
  ]
@@ -13905,7 +13949,15 @@ function summarizeDetection2(detection) {
13905
13949
  const dirs = detection.sourceDirs[lang]?.source_dirs ?? [];
13906
13950
  const dirSuffix = dirs.length > 0 ? ` in ${dirs.join(",")}` : "";
13907
13951
  const fwName = fw?.framework ?? "no-framework";
13908
- parts.push(`${capitalize(lang)}/${fwName}${dirSuffix}`);
13952
+ const slotParts = [];
13953
+ const router = fw?.router;
13954
+ const orm = fw?.orm;
13955
+ const ui = fw?.ui;
13956
+ if (router && router !== "none") slotParts.push(`router=${router}`);
13957
+ if (orm && orm !== "none") slotParts.push(`orm=${orm}`);
13958
+ if (ui && ui !== "none") slotParts.push(`ui=${ui}`);
13959
+ const slotSuffix = slotParts.length > 0 ? ` (${slotParts.join(", ")})` : "";
13960
+ parts.push(`${capitalize(lang)}/${fwName}${slotSuffix}${dirSuffix}`);
13909
13961
  }
13910
13962
  const mono = detection.monorepo.type;
13911
13963
  const monoSuffix = mono && mono !== "single" ? ` [${mono} monorepo]` : "";
@@ -14178,6 +14230,7 @@ var init_init = __esm({
14178
14230
  init_install_commands();
14179
14231
  init_settings_local();
14180
14232
  init_memory_path();
14233
+ init_hook_timeouts();
14181
14234
  init_detect();
14182
14235
  init_drift();
14183
14236
  __filename2 = fileURLToPath2(import.meta.url);
@@ -14216,7 +14269,7 @@ var init_license_pubkey_generated = __esm({
14216
14269
  });
14217
14270
 
14218
14271
  // src/security/license-response-verifier.ts
14219
- import { createPublicKey } from "crypto";
14272
+ import { createPublicKey, verify as cryptoVerify } from "crypto";
14220
14273
  function verifyLicenseResponse(payload) {
14221
14274
  if (!KNOWN_LICENSE_PUBKEY_FINGERPRINTS.has(LICENSE_PUBKEY_FINGERPRINT_HEX)) {
14222
14275
  return {
@@ -14263,8 +14316,7 @@ function verifyLicenseResponse(payload) {
14263
14316
  ]);
14264
14317
  const der = Buffer.concat([spkiPrefix, Buffer.from(LICENSE_PUBKEY_ED25519)]);
14265
14318
  const pubkey = createPublicKey({ key: der, format: "der", type: "spki" });
14266
- const { verify } = __require("crypto");
14267
- const ok = verify(
14319
+ const ok = cryptoVerify(
14268
14320
  null,
14269
14321
  Buffer.from(canonical, "utf-8"),
14270
14322
  pubkey,
@@ -14311,13 +14363,31 @@ function isToolAllowed(toolName, userTier) {
14311
14363
  return tierLevel(userTier) >= tierLevel(requiredTier);
14312
14364
  }
14313
14365
  function annotateToolDefinitions(defs) {
14366
+ for (const def of defs) {
14367
+ const expectedTier = getToolTier(def.name);
14368
+ const incomingTier = def.annotations?.tier;
14369
+ if (incomingTier !== void 0 && incomingTier !== expectedTier) {
14370
+ throw new Error(
14371
+ `TOOL_TIER_MAP bijection violation (P-M-033): tool "${def.name}" has annotations.tier="${incomingTier}" but TOOL_TIER_MAP says "${expectedTier}". TOOL_TIER_MAP is the SoT \u2014 fix the source instead.`
14372
+ );
14373
+ }
14374
+ const expectedPrefix = TIER_LABELS[expectedTier];
14375
+ for (const otherPrefix of Object.values(TIER_LABELS)) {
14376
+ if (otherPrefix && otherPrefix !== expectedPrefix && def.description.startsWith(otherPrefix)) {
14377
+ throw new Error(
14378
+ `TOOL_TIER_MAP bijection violation (P-M-033): tool "${def.name}" description starts with "${otherPrefix}" but TOOL_TIER_MAP tier is "${expectedTier}" (expected prefix "${expectedPrefix}").`
14379
+ );
14380
+ }
14381
+ }
14382
+ }
14314
14383
  return defs.map((def) => {
14315
14384
  const tier = getToolTier(def.name);
14316
14385
  const label = TIER_LABELS[tier];
14386
+ const stripped = label && def.description.startsWith(label) ? def.description.slice(label.length) : def.description;
14317
14387
  return {
14318
14388
  ...def,
14319
14389
  annotations: { ...def.annotations ?? {}, tier },
14320
- description: label ? `${label}${def.description}` : def.description
14390
+ description: label ? `${label}${stripped}` : stripped
14321
14391
  };
14322
14392
  });
14323
14393
  }
@@ -14329,17 +14399,14 @@ async function validateLicense(apiKey) {
14329
14399
  const memDb = getMemoryDb();
14330
14400
  try {
14331
14401
  const cached = memDb.prepare(
14332
- "SELECT tier, valid_until, last_validated, features FROM license_cache WHERE api_key_hash = ?"
14402
+ "SELECT tier, valid_until, last_validated, features, signed_payload_json FROM license_cache WHERE api_key_hash = ?"
14333
14403
  ).get(keyHash);
14334
- if (cached) {
14404
+ const trusted = cached ? readTrustedCache(cached) : null;
14405
+ if (cached && trusted) {
14335
14406
  const lastValidated = new Date(cached.last_validated);
14336
14407
  const hourAgo = new Date(Date.now() - 60 * 60 * 1e3);
14337
14408
  if (lastValidated > hourAgo) {
14338
- return {
14339
- tier: cached.tier,
14340
- validUntil: cached.valid_until,
14341
- features: JSON.parse(cached.features || "[]")
14342
- };
14409
+ return trusted;
14343
14410
  }
14344
14411
  }
14345
14412
  const config = getConfig();
@@ -14368,7 +14435,13 @@ async function validateLicense(apiKey) {
14368
14435
  const tier = data.plan ? PLAN_TO_TIER_MAP[data.plan] ?? "free" : data.tier ?? "free";
14369
14436
  const validUntil = data.validUntil ?? "";
14370
14437
  const features = data.features ?? [];
14371
- updateLicenseCache(apiKey, tier, validUntil, features);
14438
+ updateLicenseCache(
14439
+ apiKey,
14440
+ tier,
14441
+ validUntil,
14442
+ features,
14443
+ sigResult.kind === "valid" ? data : null
14444
+ );
14372
14445
  return { tier, validUntil, features };
14373
14446
  }
14374
14447
  return { tier: "free", validUntil: "", features: [] };
@@ -14376,15 +14449,11 @@ async function validateLicense(apiKey) {
14376
14449
  } catch {
14377
14450
  }
14378
14451
  }
14379
- if (cached) {
14452
+ if (cached && trusted) {
14380
14453
  const lastValidated = new Date(cached.last_validated);
14381
14454
  const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3);
14382
14455
  if (lastValidated > sevenDaysAgo) {
14383
- return {
14384
- tier: cached.tier,
14385
- validUntil: cached.valid_until,
14386
- features: JSON.parse(cached.features || "[]")
14387
- };
14456
+ return trusted;
14388
14457
  }
14389
14458
  }
14390
14459
  return { tier: "free", validUntil: "", features: [] };
@@ -14392,18 +14461,49 @@ async function validateLicense(apiKey) {
14392
14461
  memDb.close();
14393
14462
  }
14394
14463
  }
14395
- function updateLicenseCache(apiKey, tier, validUntil, features = []) {
14464
+ function updateLicenseCache(apiKey, tier, validUntil, features = [], signedPayload = null) {
14396
14465
  const keyHash = createHash5("sha256").update(apiKey).digest("hex");
14397
14466
  const memDb = getMemoryDb();
14398
14467
  try {
14399
14468
  memDb.prepare(`
14400
- INSERT OR REPLACE INTO license_cache (api_key_hash, tier, valid_until, last_validated, features)
14401
- VALUES (?, ?, ?, datetime('now'), ?)
14402
- `).run(keyHash, tier, validUntil, JSON.stringify(features));
14469
+ INSERT OR REPLACE INTO license_cache (api_key_hash, tier, valid_until, last_validated, features, signed_payload_json)
14470
+ VALUES (?, ?, ?, datetime('now'), ?, ?)
14471
+ `).run(
14472
+ keyHash,
14473
+ tier,
14474
+ validUntil,
14475
+ JSON.stringify(features),
14476
+ signedPayload ? JSON.stringify(signedPayload) : ""
14477
+ );
14403
14478
  } finally {
14404
14479
  memDb.close();
14405
14480
  }
14406
14481
  }
14482
+ function readTrustedCache(cached) {
14483
+ if (!cached.signed_payload_json) {
14484
+ if (isLicenseSignatureRequired()) return null;
14485
+ warnLicenseSigOnce("cache_unsigned_transition");
14486
+ return {
14487
+ tier: cached.tier,
14488
+ validUntil: cached.valid_until,
14489
+ features: JSON.parse(cached.features || "[]")
14490
+ };
14491
+ }
14492
+ let parsed;
14493
+ try {
14494
+ parsed = JSON.parse(cached.signed_payload_json);
14495
+ } catch {
14496
+ return null;
14497
+ }
14498
+ const result = verifyLicenseResponse(parsed);
14499
+ if (result.kind !== "valid") return null;
14500
+ const verifiedPlan = typeof parsed.plan === "string" ? parsed.plan : null;
14501
+ const verifiedTierField = typeof parsed.tier === "string" ? parsed.tier : null;
14502
+ const tier = verifiedPlan ? PLAN_TO_TIER_MAP[verifiedPlan] ?? "free" : verifiedTierField ?? "free";
14503
+ const validUntil = typeof parsed.validUntil === "string" ? parsed.validUntil : "";
14504
+ const features = Array.isArray(parsed.features) ? parsed.features : [];
14505
+ return { tier, validUntil, features };
14506
+ }
14407
14507
  async function getCurrentTier() {
14408
14508
  if (cachedTier && Date.now() - cachedTierTimestamp < IN_MEMORY_CACHE_TTL_MS) {
14409
14509
  return cachedTier.tier;
@@ -14511,6 +14611,12 @@ var init_license = __esm({
14511
14611
  coupling_check: "free",
14512
14612
  memory_search: "free",
14513
14613
  memory_ingest: "free",
14614
+ // P-M-042 (plan-stage-d-medium-sweep): memory_backfill serves the
14615
+ // customer's MEMORY.md ingestion workflow and was registered in
14616
+ // memory-tools.ts but missing from TOOL_TIER_MAP. The bijection
14617
+ // drift-guard (P-M-033) would have caught it on next add; we land it
14618
+ // explicitly as Free since it's a customer-owned-data ingestion path.
14619
+ memory_backfill: "free",
14514
14620
  regression_risk: "free",
14515
14621
  feature_health: "free",
14516
14622
  license_status: "free",
@@ -14586,7 +14692,7 @@ var init_license = __esm({
14586
14692
  cloud_enterprise: "enterprise"
14587
14693
  };
14588
14694
  TIER_LABELS = {
14589
- free: "",
14695
+ free: "[FREE] ",
14590
14696
  pro: "[PRO] ",
14591
14697
  team: "[TEAM] ",
14592
14698
  enterprise: "[ENTERPRISE] "
@@ -14615,7 +14721,12 @@ var init_hook_registry = __esm({
14615
14721
  "post-edit-context",
14616
14722
  "post-tool-use",
14617
14723
  "pre-compact",
14724
+ // P-E-019 (1.12.0): security-gate + pre-delete-check are kept registered
14725
+ // for back-compat (operator settings.json may still reference them
14726
+ // individually), but new installs emit only `pre-tool-use-gate` —
14727
+ // the consolidated single-spawn PreToolUse hook.
14618
14728
  "pre-delete-check",
14729
+ "pre-tool-use-gate",
14619
14730
  "quality-event",
14620
14731
  "rule-enforcement-pipeline",
14621
14732
  "security-gate",
@@ -18027,10 +18138,11 @@ var init_adapters = __esm({
18027
18138
  "src/detect/adapters/index.ts"() {
18028
18139
  "use strict";
18029
18140
  CORE_BUNDLED_IDS = /* @__PURE__ */ new Set([
18030
- "aspnet",
18031
- "go-chi",
18141
+ // P-M-032 (plan-stage-d-medium-sweep): aspnet, go-chi, phoenix REMOVED
18142
+ // — zero downstream consumers (each had a single 1-line re-export shim).
18143
+ // Rails (5 consumers) + Spring (1 consumer) deferred to Stage E as the
18144
+ // JVM/Ruby ecosystem-watch pair per operator decision.
18032
18145
  "nextjs-trpc",
18033
- "phoenix",
18034
18146
  "python-django",
18035
18147
  "python-fastapi",
18036
18148
  "python-flask",
@@ -18977,6 +19089,184 @@ var init_db = __esm({
18977
19089
  }
18978
19090
  });
18979
19091
 
19092
+ // src/knowledge-db.ts
19093
+ import Database3 from "better-sqlite3";
19094
+ import { dirname as dirname14 } from "path";
19095
+ import { existsSync as existsSync28, mkdirSync as mkdirSync11 } from "fs";
19096
+ function getKnowledgeDb() {
19097
+ const dbPath = getResolvedPaths().knowledgeDbPath;
19098
+ const dir = dirname14(dbPath);
19099
+ if (!existsSync28(dir)) {
19100
+ mkdirSync11(dir, { recursive: true });
19101
+ }
19102
+ const db = new Database3(dbPath);
19103
+ db.pragma("journal_mode = WAL");
19104
+ db.pragma("foreign_keys = ON");
19105
+ initKnowledgeSchema(db);
19106
+ return db;
19107
+ }
19108
+ function initKnowledgeSchema(db) {
19109
+ db.exec(`
19110
+ -- Core document chunks (parsed from .claude/**/*.md)
19111
+ CREATE TABLE IF NOT EXISTS knowledge_documents (
19112
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
19113
+ file_path TEXT NOT NULL,
19114
+ category TEXT NOT NULL,
19115
+ title TEXT NOT NULL,
19116
+ description TEXT,
19117
+ content_hash TEXT NOT NULL,
19118
+ indexed_at TEXT NOT NULL,
19119
+ indexed_at_epoch INTEGER NOT NULL
19120
+ );
19121
+
19122
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_kd_filepath ON knowledge_documents(file_path);
19123
+ CREATE INDEX IF NOT EXISTS idx_kd_category ON knowledge_documents(category);
19124
+
19125
+ -- Structured chunks within documents
19126
+ CREATE TABLE IF NOT EXISTS knowledge_chunks (
19127
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
19128
+ document_id INTEGER NOT NULL REFERENCES knowledge_documents(id) ON DELETE CASCADE,
19129
+ chunk_type TEXT NOT NULL CHECK(chunk_type IN (
19130
+ 'section', 'table_row', 'code_block', 'rule', 'incident', 'pattern', 'command', 'mismatch'
19131
+ )),
19132
+ heading TEXT,
19133
+ content TEXT NOT NULL,
19134
+ line_start INTEGER,
19135
+ line_end INTEGER,
19136
+ metadata TEXT DEFAULT '{}'
19137
+ );
19138
+
19139
+ CREATE INDEX IF NOT EXISTS idx_kc_doc ON knowledge_chunks(document_id);
19140
+ CREATE INDEX IF NOT EXISTS idx_kc_type ON knowledge_chunks(chunk_type);
19141
+ CREATE INDEX IF NOT EXISTS idx_kc_heading ON knowledge_chunks(heading);
19142
+
19143
+ -- Canonical Rules index
19144
+ CREATE TABLE IF NOT EXISTS knowledge_rules (
19145
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
19146
+ rule_id TEXT UNIQUE NOT NULL,
19147
+ rule_text TEXT NOT NULL,
19148
+ vr_type TEXT,
19149
+ reference_path TEXT,
19150
+ severity TEXT DEFAULT 'HIGH',
19151
+ prevention_summary TEXT
19152
+ );
19153
+
19154
+ CREATE INDEX IF NOT EXISTS idx_kr_id ON knowledge_rules(rule_id);
19155
+ `);
19156
+ const rulesCols = db.prepare(`PRAGMA table_info(knowledge_rules)`).all();
19157
+ if (!rulesCols.some((c2) => c2.name === "source")) {
19158
+ db.exec(
19159
+ `ALTER TABLE knowledge_rules ADD COLUMN source TEXT NOT NULL DEFAULT 'framework'`
19160
+ );
19161
+ }
19162
+ db.exec(`
19163
+
19164
+ -- Verification Requirements index
19165
+ CREATE TABLE IF NOT EXISTS knowledge_verifications (
19166
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
19167
+ vr_type TEXT UNIQUE NOT NULL,
19168
+ command TEXT NOT NULL,
19169
+ expected TEXT NOT NULL,
19170
+ use_when TEXT NOT NULL,
19171
+ catches TEXT,
19172
+ category TEXT
19173
+ );
19174
+
19175
+ CREATE INDEX IF NOT EXISTS idx_kv_type ON knowledge_verifications(vr_type);
19176
+ CREATE INDEX IF NOT EXISTS idx_kv_category ON knowledge_verifications(category);
19177
+
19178
+ -- Incident index
19179
+ CREATE TABLE IF NOT EXISTS knowledge_incidents (
19180
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
19181
+ incident_num INTEGER UNIQUE NOT NULL,
19182
+ date TEXT NOT NULL,
19183
+ type TEXT NOT NULL,
19184
+ gap_found TEXT NOT NULL,
19185
+ prevention TEXT NOT NULL,
19186
+ cr_added TEXT,
19187
+ root_cause TEXT,
19188
+ user_quote TEXT
19189
+ );
19190
+
19191
+ CREATE INDEX IF NOT EXISTS idx_ki_num ON knowledge_incidents(incident_num);
19192
+ CREATE INDEX IF NOT EXISTS idx_ki_type ON knowledge_incidents(type);
19193
+ CREATE INDEX IF NOT EXISTS idx_ki_cr ON knowledge_incidents(cr_added);
19194
+
19195
+ -- Schema mismatch quick lookup
19196
+ -- P-H013 (plan-stage-c-high-batch): the source column no longer has a
19197
+ -- SQL DEFAULT. Previously the value was a JS template-literal
19198
+ -- interpolation baked into the customer SQLite at schema creation
19199
+ -- time, so later config changes were ignored. Default is now applied
19200
+ -- at INSERT time via getConfig().conventions.knowledgeSourceFiles[0]
19201
+ -- in knowledge-indexer.ts -- a runtime config lookup that reflects
19202
+ -- the customers current config.
19203
+ CREATE TABLE IF NOT EXISTS knowledge_schema_mismatches (
19204
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
19205
+ table_name TEXT NOT NULL,
19206
+ wrong_column TEXT NOT NULL,
19207
+ correct_column TEXT NOT NULL,
19208
+ source TEXT NOT NULL
19209
+ );
19210
+
19211
+ CREATE INDEX IF NOT EXISTS idx_ksm_table ON knowledge_schema_mismatches(table_name);
19212
+
19213
+ -- Cross-reference graph
19214
+ CREATE TABLE IF NOT EXISTS knowledge_edges (
19215
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
19216
+ source_type TEXT NOT NULL,
19217
+ source_id TEXT NOT NULL,
19218
+ target_type TEXT NOT NULL,
19219
+ target_id TEXT NOT NULL,
19220
+ edge_type TEXT NOT NULL
19221
+ );
19222
+
19223
+ CREATE INDEX IF NOT EXISTS idx_ke_source ON knowledge_edges(source_type, source_id);
19224
+ CREATE INDEX IF NOT EXISTS idx_ke_target ON knowledge_edges(target_type, target_id);
19225
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_ke_unique ON knowledge_edges(source_type, source_id, target_type, target_id, edge_type);
19226
+
19227
+ -- Staleness tracking
19228
+ CREATE TABLE IF NOT EXISTS knowledge_meta (
19229
+ key TEXT PRIMARY KEY,
19230
+ value TEXT NOT NULL
19231
+ );
19232
+ `);
19233
+ try {
19234
+ db.exec(`
19235
+ CREATE VIRTUAL TABLE IF NOT EXISTS knowledge_fts USING fts5(
19236
+ heading, content, chunk_type, file_path
19237
+ );
19238
+
19239
+ CREATE TRIGGER IF NOT EXISTS kc_fts_insert AFTER INSERT ON knowledge_chunks BEGIN
19240
+ INSERT INTO knowledge_fts(rowid, heading, content, chunk_type, file_path)
19241
+ SELECT new.id, new.heading, new.content, new.chunk_type, kd.file_path
19242
+ FROM knowledge_documents kd WHERE kd.id = new.document_id;
19243
+ END;
19244
+
19245
+ CREATE TRIGGER IF NOT EXISTS kc_fts_delete AFTER DELETE ON knowledge_chunks BEGIN
19246
+ INSERT INTO knowledge_fts(knowledge_fts, rowid, heading, content, chunk_type, file_path)
19247
+ SELECT 'delete', old.id, old.heading, old.content, old.chunk_type, kd.file_path
19248
+ FROM knowledge_documents kd WHERE kd.id = old.document_id;
19249
+ END;
19250
+
19251
+ CREATE TRIGGER IF NOT EXISTS kc_fts_update AFTER UPDATE ON knowledge_chunks BEGIN
19252
+ INSERT INTO knowledge_fts(knowledge_fts, rowid, heading, content, chunk_type, file_path)
19253
+ SELECT 'delete', old.id, old.heading, old.content, old.chunk_type, kd.file_path
19254
+ FROM knowledge_documents kd WHERE kd.id = old.document_id;
19255
+ INSERT INTO knowledge_fts(rowid, heading, content, chunk_type, file_path)
19256
+ SELECT new.id, new.heading, new.content, new.chunk_type, kd.file_path
19257
+ FROM knowledge_documents kd WHERE kd.id = new.document_id;
19258
+ END;
19259
+ `);
19260
+ } catch {
19261
+ }
19262
+ }
19263
+ var init_knowledge_db = __esm({
19264
+ "src/knowledge-db.ts"() {
19265
+ "use strict";
19266
+ init_config();
19267
+ }
19268
+ });
19269
+
18980
19270
  // src/security-utils.ts
18981
19271
  import { resolve as resolve24, normalize } from "path";
18982
19272
  function ensureWithinRoot(filePath, projectRoot) {
@@ -19054,8 +19344,8 @@ var init_rules = __esm({
19054
19344
  });
19055
19345
 
19056
19346
  // src/import-resolver.ts
19057
- import { readFileSync as readFileSync25, existsSync as existsSync28, statSync as statSync12 } from "fs";
19058
- import { resolve as resolve25, dirname as dirname14, join as join13 } from "path";
19347
+ import { readFileSync as readFileSync25, existsSync as existsSync29, statSync as statSync12 } from "fs";
19348
+ import { resolve as resolve25, dirname as dirname15, join as join13 } from "path";
19059
19349
  function parseImports(source) {
19060
19350
  const imports = [];
19061
19351
  const lines = source.split("\n");
@@ -19113,21 +19403,21 @@ function resolveImportPath(specifier, fromFile) {
19113
19403
  const paths = getResolvedPaths();
19114
19404
  basePath = resolve25(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
19115
19405
  } else {
19116
- basePath = resolve25(dirname14(fromFile), specifier);
19406
+ basePath = resolve25(dirname15(fromFile), specifier);
19117
19407
  }
19118
- if (existsSync28(basePath) && !isDirectory(basePath)) {
19408
+ if (existsSync29(basePath) && !isDirectory(basePath)) {
19119
19409
  return toRelative(basePath);
19120
19410
  }
19121
19411
  const resolvedPaths = getResolvedPaths();
19122
19412
  for (const ext of resolvedPaths.extensions) {
19123
19413
  const withExt = basePath + ext;
19124
- if (existsSync28(withExt)) {
19414
+ if (existsSync29(withExt)) {
19125
19415
  return toRelative(withExt);
19126
19416
  }
19127
19417
  }
19128
19418
  for (const indexFile of resolvedPaths.indexFiles) {
19129
19419
  const indexPath = join13(basePath, indexFile);
19130
- if (existsSync28(indexPath)) {
19420
+ if (existsSync29(indexPath)) {
19131
19421
  return toRelative(indexPath);
19132
19422
  }
19133
19423
  }
@@ -19164,7 +19454,7 @@ function buildImportIndex(dataDb, codegraphDb) {
19164
19454
  let batch = [];
19165
19455
  for (const file of files) {
19166
19456
  const absPath = ensureWithinRoot(resolve25(projectRoot, file.path), projectRoot);
19167
- if (!existsSync28(absPath)) continue;
19457
+ if (!existsSync29(absPath)) continue;
19168
19458
  let source;
19169
19459
  try {
19170
19460
  source = readFileSync25(absPath, "utf-8");
@@ -19204,12 +19494,12 @@ var init_import_resolver = __esm({
19204
19494
  });
19205
19495
 
19206
19496
  // src/trpc-index.ts
19207
- import { readFileSync as readFileSync26, existsSync as existsSync29, readdirSync as readdirSync16 } from "fs";
19497
+ import { readFileSync as readFileSync26, existsSync as existsSync30, readdirSync as readdirSync16 } from "fs";
19208
19498
  import { resolve as resolve26, join as join14 } from "path";
19209
19499
  function parseRootRouter() {
19210
19500
  const paths = getResolvedPaths();
19211
19501
  const rootPath = paths.rootRouterPath;
19212
- if (!existsSync29(rootPath)) {
19502
+ if (!existsSync30(rootPath)) {
19213
19503
  throw new Error(`Root router not found at ${rootPath}`);
19214
19504
  }
19215
19505
  const source = readFileSync26(rootPath, "utf-8");
@@ -19224,12 +19514,12 @@ function parseRootRouter() {
19224
19514
  for (const ext of [".ts", ".tsx", ""]) {
19225
19515
  const candidate = fullPath + ext;
19226
19516
  const routersRelPath = getConfig().paths.routers ?? "src/server/api/routers";
19227
- if (existsSync29(candidate)) {
19517
+ if (existsSync30(candidate)) {
19228
19518
  filePath = routersRelPath + "/" + filePath + ext;
19229
19519
  break;
19230
19520
  }
19231
19521
  const indexCandidate = join14(fullPath, "index.ts");
19232
- if (existsSync29(indexCandidate)) {
19522
+ if (existsSync30(indexCandidate)) {
19233
19523
  filePath = routersRelPath + "/" + filePath + "/index.ts";
19234
19524
  break;
19235
19525
  }
@@ -19249,7 +19539,7 @@ function parseRootRouter() {
19249
19539
  }
19250
19540
  function extractProcedures(routerFilePath) {
19251
19541
  const absPath = resolve26(getProjectRoot(), routerFilePath);
19252
- if (!existsSync29(absPath)) return [];
19542
+ if (!existsSync30(absPath)) return [];
19253
19543
  const source = readFileSync26(absPath, "utf-8");
19254
19544
  const procedures = [];
19255
19545
  const seen = /* @__PURE__ */ new Set();
@@ -19279,7 +19569,7 @@ function findUICallSites(routerKey, procedureName) {
19279
19569
  ];
19280
19570
  const searchPattern = `api.${routerKey}.${procedureName}`;
19281
19571
  for (const dir of searchDirs) {
19282
- if (!existsSync29(dir)) continue;
19572
+ if (!existsSync30(dir)) continue;
19283
19573
  searchDirectory(dir, searchPattern, callSites);
19284
19574
  }
19285
19575
  return callSites;
@@ -19357,7 +19647,7 @@ var init_trpc_index = __esm({
19357
19647
  });
19358
19648
 
19359
19649
  // src/page-deps.ts
19360
- import { readFileSync as readFileSync27, existsSync as existsSync30 } from "fs";
19650
+ import { readFileSync as readFileSync27, existsSync as existsSync31 } from "fs";
19361
19651
  import { resolve as resolve27 } from "path";
19362
19652
  function deriveRoute(pageFile) {
19363
19653
  let route = pageFile.replace(/^src\/app/, "").replace(/\/page\.tsx?$/, "").replace(/\/page\.jsx?$/, "");
@@ -19397,7 +19687,7 @@ function findRouterCalls(files) {
19397
19687
  const projectRoot = getProjectRoot();
19398
19688
  for (const file of files) {
19399
19689
  const absPath = ensureWithinRoot(resolve27(projectRoot, file), projectRoot);
19400
- if (!existsSync30(absPath)) continue;
19690
+ if (!existsSync31(absPath)) continue;
19401
19691
  try {
19402
19692
  const source = readFileSync27(absPath, "utf-8");
19403
19693
  const apiCallRegex = /api\.(\w+)\.\w+/g;
@@ -19418,7 +19708,7 @@ function findTablesFromRouters(routerNames, dataDb) {
19418
19708
  ).all(routerName);
19419
19709
  for (const proc of procs) {
19420
19710
  const absPath = ensureWithinRoot(resolve27(getProjectRoot(), proc.router_file), getProjectRoot());
19421
- if (!existsSync30(absPath)) continue;
19711
+ if (!existsSync31(absPath)) continue;
19422
19712
  try {
19423
19713
  const source = readFileSync27(absPath, "utf-8");
19424
19714
  const dbPattern = getConfig().dbAccessPattern ?? "ctx.db.{table}";
@@ -19692,11 +19982,11 @@ var init_domains = __esm({
19692
19982
  });
19693
19983
 
19694
19984
  // src/schema-mapper.ts
19695
- import { readFileSync as readFileSync28, existsSync as existsSync31, readdirSync as readdirSync17 } from "fs";
19985
+ import { readFileSync as readFileSync28, existsSync as existsSync32, readdirSync as readdirSync17 } from "fs";
19696
19986
  import { join as join15 } from "path";
19697
19987
  function parsePrismaSchema() {
19698
19988
  const schemaPath = getResolvedPaths().prismaSchemaPath;
19699
- if (!existsSync31(schemaPath)) {
19989
+ if (!existsSync32(schemaPath)) {
19700
19990
  throw new Error(`Prisma schema not found at ${schemaPath}`);
19701
19991
  }
19702
19992
  const source = readFileSync28(schemaPath, "utf-8");
@@ -19757,7 +20047,7 @@ function toSnakeCase(str) {
19757
20047
  function findColumnUsageInRouters(tableName) {
19758
20048
  const usage = /* @__PURE__ */ new Map();
19759
20049
  const routersDir = getResolvedPaths().routersDir;
19760
- if (!existsSync31(routersDir)) return usage;
20050
+ if (!existsSync32(routersDir)) return usage;
19761
20051
  scanDirectory(routersDir, tableName, usage);
19762
20052
  return usage;
19763
20053
  }
@@ -19819,7 +20109,7 @@ function detectMismatches(models) {
19819
20109
  }
19820
20110
  function findFilesUsingColumn(dir, column, tableName) {
19821
20111
  const result = [];
19822
- if (!existsSync31(dir)) return result;
20112
+ if (!existsSync32(dir)) return result;
19823
20113
  const entries = readdirSync17(dir, { withFileTypes: true });
19824
20114
  for (const entry of entries) {
19825
20115
  const fullPath = join15(dir, entry.name);
@@ -19972,14 +20262,14 @@ var init_import_parser = __esm({
19972
20262
  });
19973
20263
 
19974
20264
  // src/python/import-resolver.ts
19975
- import { readFileSync as readFileSync29, existsSync as existsSync32, readdirSync as readdirSync18 } from "fs";
19976
- import { resolve as resolve29, join as join16, relative as relative6, dirname as dirname15 } from "path";
20265
+ import { readFileSync as readFileSync29, existsSync as existsSync33, readdirSync as readdirSync18 } from "fs";
20266
+ import { resolve as resolve29, join as join16, relative as relative6, dirname as dirname16 } from "path";
19977
20267
  function resolvePythonModulePath(module, fromFile, pythonRoot, level) {
19978
20268
  const projectRoot = getProjectRoot();
19979
20269
  if (level > 0) {
19980
- let baseDir = dirname15(resolve29(projectRoot, fromFile));
20270
+ let baseDir = dirname16(resolve29(projectRoot, fromFile));
19981
20271
  for (let i = 1; i < level; i++) {
19982
- baseDir = dirname15(baseDir);
20272
+ baseDir = dirname16(baseDir);
19983
20273
  }
19984
20274
  const modulePart = module.replace(/^\.+/, "");
19985
20275
  if (modulePart) {
@@ -19993,13 +20283,13 @@ function resolvePythonModulePath(module, fromFile, pythonRoot, level) {
19993
20283
  return tryResolvePythonPath(candidate, projectRoot);
19994
20284
  }
19995
20285
  function tryResolvePythonPath(basePath, projectRoot) {
19996
- if (existsSync32(basePath + ".py")) {
20286
+ if (existsSync33(basePath + ".py")) {
19997
20287
  return relative6(projectRoot, basePath + ".py");
19998
20288
  }
19999
- if (existsSync32(join16(basePath, "__init__.py"))) {
20289
+ if (existsSync33(join16(basePath, "__init__.py"))) {
20000
20290
  return relative6(projectRoot, join16(basePath, "__init__.py"));
20001
20291
  }
20002
- if (basePath.endsWith(".py") && existsSync32(basePath)) {
20292
+ if (basePath.endsWith(".py") && existsSync33(basePath)) {
20003
20293
  return relative6(projectRoot, basePath);
20004
20294
  }
20005
20295
  return null;
@@ -21352,7 +21642,7 @@ var init_memory_tools = __esm({
21352
21642
  });
21353
21643
 
21354
21644
  // src/docs-tools.ts
21355
- import { readFileSync as readFileSync34, existsSync as existsSync33 } from "fs";
21645
+ import { readFileSync as readFileSync34, existsSync as existsSync34 } from "fs";
21356
21646
  import { resolve as resolve30, basename as basename6 } from "path";
21357
21647
  function p3(baseName) {
21358
21648
  return `${getConfig().toolPrefix}_${baseName}`;
@@ -21408,7 +21698,7 @@ function handleDocsToolCall(name, args2) {
21408
21698
  }
21409
21699
  function loadDocsMap() {
21410
21700
  const mapPath = getResolvedPaths().docsMapPath;
21411
- if (!existsSync33(mapPath)) {
21701
+ if (!existsSync34(mapPath)) {
21412
21702
  throw new Error(`docs-map.json not found at ${mapPath}`);
21413
21703
  }
21414
21704
  return JSON.parse(readFileSync34(mapPath, "utf-8"));
@@ -21482,9 +21772,9 @@ function extractFrontmatter(content) {
21482
21772
  function extractProcedureNames(routerPath) {
21483
21773
  const root = getProjectRoot();
21484
21774
  const absPath = ensureWithinRoot(resolve30(getResolvedPaths().srcDir, "..", routerPath), root);
21485
- if (!existsSync33(absPath)) {
21775
+ if (!existsSync34(absPath)) {
21486
21776
  const altPath = ensureWithinRoot(resolve30(getResolvedPaths().srcDir, "../server/api/routers", basename6(routerPath)), root);
21487
- if (!existsSync33(altPath)) return [];
21777
+ if (!existsSync34(altPath)) return [];
21488
21778
  return extractProcedureNamesFromContent(readFileSync34(altPath, "utf-8"));
21489
21779
  }
21490
21780
  return extractProcedureNamesFromContent(readFileSync34(absPath, "utf-8"));
@@ -21528,7 +21818,7 @@ function handleDocsAudit(args2) {
21528
21818
  const mapping = docsMap.mappings.find((m3) => m3.id === mappingId);
21529
21819
  if (!mapping) continue;
21530
21820
  const helpPagePath = ensureWithinRoot(resolve30(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
21531
- if (!existsSync33(helpPagePath)) {
21821
+ if (!existsSync34(helpPagePath)) {
21532
21822
  results.push({
21533
21823
  helpPage: mapping.helpPage,
21534
21824
  mappingId,
@@ -21581,7 +21871,7 @@ function handleDocsAudit(args2) {
21581
21871
  for (const [guideName, parentId] of Object.entries(docsMap.userGuideInheritance.examples)) {
21582
21872
  if (parentId === mappingId) {
21583
21873
  const guidePath = ensureWithinRoot(resolve30(getResolvedPaths().helpSitePath, `pages/user-guides/${guideName}/index.mdx`), getProjectRoot());
21584
- if (existsSync33(guidePath)) {
21874
+ if (existsSync34(guidePath)) {
21585
21875
  const guideContent = readFileSync34(guidePath, "utf-8");
21586
21876
  const guideFrontmatter = extractFrontmatter(guideContent);
21587
21877
  if (!guideFrontmatter?.lastVerified || status === "STALE") {
@@ -21616,7 +21906,7 @@ function handleDocsCoverage(args2) {
21616
21906
  const mappings = filterDomain ? docsMap.mappings.filter((m3) => m3.id === filterDomain) : docsMap.mappings;
21617
21907
  for (const mapping of mappings) {
21618
21908
  const helpPagePath = ensureWithinRoot(resolve30(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
21619
- const exists = existsSync33(helpPagePath);
21909
+ const exists = existsSync34(helpPagePath);
21620
21910
  let hasContent = false;
21621
21911
  let lineCount = 0;
21622
21912
  let lastVerified = null;
@@ -21963,7 +22253,7 @@ var init_observability_tools = __esm({
21963
22253
  });
21964
22254
 
21965
22255
  // src/sentinel-db.ts
21966
- import { existsSync as existsSync34 } from "fs";
22256
+ import { existsSync as existsSync35 } from "fs";
21967
22257
  import { resolve as resolve31 } from "path";
21968
22258
  function parsePortalScope(raw) {
21969
22259
  if (!raw) return [];
@@ -22201,22 +22491,22 @@ function validateFeatures(db, domainFilter) {
22201
22491
  const missingPages = [];
22202
22492
  for (const comp of components) {
22203
22493
  const absPath = resolve31(PROJECT_ROOT, comp.component_file);
22204
- if (!existsSync34(absPath)) {
22494
+ if (!existsSync35(absPath)) {
22205
22495
  missingComponents.push(comp.component_file);
22206
22496
  }
22207
22497
  }
22208
22498
  for (const proc of procedures) {
22209
22499
  const routerPath = resolve31(PROJECT_ROOT, `src/server/api/routers/${proc.router_name}.ts`);
22210
- if (!existsSync34(routerPath)) {
22500
+ if (!existsSync35(routerPath)) {
22211
22501
  missingProcedures.push({ router: proc.router_name, procedure: proc.procedure_name });
22212
22502
  }
22213
22503
  }
22214
22504
  for (const page of pages) {
22215
22505
  const routeToPath = page.page_route.replace(/^\/(portal-[^/]+\/)?/, "src/app/").replace(/\/$/, "") + "/page.tsx";
22216
22506
  const absPath = resolve31(PROJECT_ROOT, routeToPath);
22217
- if (page.page_route.startsWith("/") && !existsSync34(absPath)) {
22507
+ if (page.page_route.startsWith("/") && !existsSync35(absPath)) {
22218
22508
  const altPath = resolve31(PROJECT_ROOT, `src/app${page.page_route}/page.tsx`);
22219
- if (!existsSync34(altPath)) {
22509
+ if (!existsSync35(altPath)) {
22220
22510
  missingPages.push(page.page_route);
22221
22511
  }
22222
22512
  }
@@ -22742,8 +23032,8 @@ var init_sentinel_tools = __esm({
22742
23032
  });
22743
23033
 
22744
23034
  // src/sentinel-scanner.ts
22745
- import { readFileSync as readFileSync35, existsSync as existsSync35, readdirSync as readdirSync23, statSync as statSync13 } from "fs";
22746
- import { resolve as resolve32, join as join21, basename as basename7, dirname as dirname16, relative as relative11 } from "path";
23035
+ import { readFileSync as readFileSync35, existsSync as existsSync36, readdirSync as readdirSync23, statSync as statSync13 } from "fs";
23036
+ import { resolve as resolve32, join as join21, basename as basename7, dirname as dirname17, relative as relative11 } from "path";
22747
23037
  function inferDomain(filePath) {
22748
23038
  const domains = getConfig().domains;
22749
23039
  const path = filePath.toLowerCase();
@@ -22873,7 +23163,7 @@ function scanComponentExports(dataDb) {
22873
23163
  const componentsBase = config.paths.components ?? config.paths.source + "/components";
22874
23164
  const componentDirs = [];
22875
23165
  const basePath = resolve32(projectRoot, componentsBase);
22876
- if (existsSync35(basePath)) {
23166
+ if (existsSync36(basePath)) {
22877
23167
  try {
22878
23168
  const entries = readdirSync23(basePath, { withFileTypes: true });
22879
23169
  for (const entry of entries) {
@@ -22886,7 +23176,7 @@ function scanComponentExports(dataDb) {
22886
23176
  }
22887
23177
  for (const dir of componentDirs) {
22888
23178
  const absDir = resolve32(projectRoot, dir);
22889
- if (!existsSync35(absDir)) continue;
23179
+ if (!existsSync36(absDir)) continue;
22890
23180
  const files = walkDir2(absDir).filter((f2) => f2.endsWith(".tsx") || f2.endsWith(".ts"));
22891
23181
  for (const file of files) {
22892
23182
  const relPath = relative11(projectRoot, file);
@@ -22916,7 +23206,7 @@ function scanComponentExports(dataDb) {
22916
23206
  if (hasHandlers && exportMatch) {
22917
23207
  const componentName = exportMatch[1];
22918
23208
  const domain = inferDomain(relPath);
22919
- const subdomain = basename7(dirname16(relPath));
23209
+ const subdomain = basename7(dirname17(relPath));
22920
23210
  const featureKey = `component.${subdomain}.${componentName.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "")}`;
22921
23211
  if (!annotations.some((a2) => a2.featureKey === featureKey)) {
22922
23212
  features.push({
@@ -23930,7 +24220,7 @@ var init_audit_trail = __esm({
23930
24220
  });
23931
24221
 
23932
24222
  // src/validation-engine.ts
23933
- import { existsSync as existsSync36, readFileSync as readFileSync36 } from "fs";
24223
+ import { existsSync as existsSync37, readFileSync as readFileSync36 } from "fs";
23934
24224
  function p10(baseName) {
23935
24225
  return `${getConfig().toolPrefix}_${baseName}`;
23936
24226
  }
@@ -23961,7 +24251,7 @@ function validateFile(filePath, projectRoot) {
23961
24251
  });
23962
24252
  return checks;
23963
24253
  }
23964
- if (!existsSync36(absPath)) {
24254
+ if (!existsSync37(absPath)) {
23965
24255
  checks.push({
23966
24256
  name: "file_exists",
23967
24257
  severity: "error",
@@ -24415,7 +24705,7 @@ var init_adr_generator = __esm({
24415
24705
  });
24416
24706
 
24417
24707
  // src/security-scorer.ts
24418
- import { existsSync as existsSync37, readFileSync as readFileSync37 } from "fs";
24708
+ import { existsSync as existsSync38, readFileSync as readFileSync37 } from "fs";
24419
24709
  function p12(baseName) {
24420
24710
  return `${getConfig().toolPrefix}_${baseName}`;
24421
24711
  }
@@ -24439,7 +24729,7 @@ function scoreFileSecurity(filePath, projectRoot) {
24439
24729
  }]
24440
24730
  };
24441
24731
  }
24442
- if (!existsSync37(absPath)) {
24732
+ if (!existsSync38(absPath)) {
24443
24733
  return { riskScore: 0, findings: [] };
24444
24734
  }
24445
24735
  let source;
@@ -24733,7 +25023,7 @@ var init_security_scorer = __esm({
24733
25023
  });
24734
25024
 
24735
25025
  // src/dependency-scorer.ts
24736
- import { existsSync as existsSync38, readFileSync as readFileSync38 } from "fs";
25026
+ import { existsSync as existsSync39, readFileSync as readFileSync38 } from "fs";
24737
25027
  import { resolve as resolve33 } from "path";
24738
25028
  function p13(baseName) {
24739
25029
  return `${getConfig().toolPrefix}_${baseName}`;
@@ -24767,7 +25057,7 @@ function calculateDepRisk(factors) {
24767
25057
  }
24768
25058
  function getInstalledPackages(projectRoot) {
24769
25059
  const pkgPath = resolve33(projectRoot, "package.json");
24770
- if (!existsSync38(pkgPath)) return /* @__PURE__ */ new Map();
25060
+ if (!existsSync39(pkgPath)) return /* @__PURE__ */ new Map();
24771
25061
  try {
24772
25062
  const pkg = JSON.parse(readFileSync38(pkgPath, "utf-8"));
24773
25063
  const packages = /* @__PURE__ */ new Map();
@@ -25372,7 +25662,7 @@ var init_regression_detector = __esm({
25372
25662
 
25373
25663
  // src/knowledge-indexer.ts
25374
25664
  import { createHash as createHash9 } from "crypto";
25375
- import { readFileSync as readFileSync39, readdirSync as readdirSync24, statSync as statSync14, existsSync as existsSync39 } from "fs";
25665
+ import { readFileSync as readFileSync39, readdirSync as readdirSync24, statSync as statSync14, existsSync as existsSync40 } from "fs";
25376
25666
  import { resolve as resolve34, relative as relative12, basename as basename8, extname as extname2 } from "path";
25377
25667
  function getKnowledgePaths() {
25378
25668
  const resolved = getResolvedPaths();
@@ -25680,11 +25970,11 @@ function indexAllKnowledge(db) {
25680
25970
  files.push(...memFiles);
25681
25971
  } catch {
25682
25972
  }
25683
- if (existsSync39(paths.plansDir)) {
25973
+ if (existsSync40(paths.plansDir)) {
25684
25974
  const planFiles = discoverMarkdownFiles(paths.plansDir);
25685
25975
  files.push(...planFiles);
25686
25976
  }
25687
- if (existsSync39(paths.docsDir)) {
25977
+ if (existsSync40(paths.docsDir)) {
25688
25978
  const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
25689
25979
  const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f2) => !f2.includes("/plans/") && !excludePatterns.some((p19) => f2.includes(p19)));
25690
25980
  files.push(...docsFiles);
@@ -25727,7 +26017,7 @@ function indexAllKnowledge(db) {
25727
26017
  } catch {
25728
26018
  }
25729
26019
  for (const filePath of files) {
25730
- if (!existsSync39(filePath)) continue;
26020
+ if (!existsSync40(filePath)) continue;
25731
26021
  const content = readFileSync39(filePath, "utf-8");
25732
26022
  const hash = hashContent2(content);
25733
26023
  const relPath = filePath.startsWith(paths.claudeDir) ? relative12(paths.claudeDir, filePath) : filePath.startsWith(paths.plansDir) ? "plans/" + relative12(paths.plansDir, filePath) : filePath.startsWith(paths.docsDir) ? "docs/" + relative12(paths.docsDir, filePath) : filePath.startsWith(paths.memoryDir) ? `memory/${relative12(paths.memoryDir, filePath)}` : basename8(filePath);
@@ -25826,6 +26116,21 @@ Rule: ${c2.rule}`,
25826
26116
  }
25827
26117
  }
25828
26118
  }
26119
+ const customerRules = getConfig().governance_rules ?? [];
26120
+ const insertCustomerRule = db.prepare(
26121
+ `INSERT OR REPLACE INTO knowledge_rules (rule_id, rule_text, vr_type, reference_path, severity, source)
26122
+ VALUES (?, ?, ?, ?, ?, 'customer-config')`
26123
+ );
26124
+ for (const rule of customerRules) {
26125
+ const namespacedId = rule.id.startsWith("customer:") ? rule.id : `customer:${rule.id}`;
26126
+ insertCustomerRule.run(
26127
+ namespacedId,
26128
+ `${rule.title}: ${rule.description}`,
26129
+ rule.vr_type ?? "VR-CUSTOM",
26130
+ rule.reference_path ?? null,
26131
+ rule.severity?.toUpperCase() ?? "MEDIUM"
26132
+ );
26133
+ }
25829
26134
  stats.edgesCreated = buildCrossReferences(db);
25830
26135
  const finalNow = /* @__PURE__ */ new Date();
25831
26136
  const finalIso = finalNow.toISOString();
@@ -25848,10 +26153,10 @@ function isKnowledgeStale(db) {
25848
26153
  files.push(...discoverMarkdownFiles(paths.memoryDir));
25849
26154
  } catch {
25850
26155
  }
25851
- if (existsSync39(paths.plansDir)) {
26156
+ if (existsSync40(paths.plansDir)) {
25852
26157
  files.push(...discoverMarkdownFiles(paths.plansDir));
25853
26158
  }
25854
- if (existsSync39(paths.docsDir)) {
26159
+ if (existsSync40(paths.docsDir)) {
25855
26160
  const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
25856
26161
  const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f2) => !f2.includes("/plans/") && !excludePatterns.some((p19) => f2.includes(p19)));
25857
26162
  files.push(...docsFiles);
@@ -26968,176 +27273,6 @@ var init_knowledge_tools = __esm({
26968
27273
  }
26969
27274
  });
26970
27275
 
26971
- // src/knowledge-db.ts
26972
- import Database3 from "better-sqlite3";
26973
- import { dirname as dirname17 } from "path";
26974
- import { existsSync as existsSync41, mkdirSync as mkdirSync11 } from "fs";
26975
- function getKnowledgeDb() {
26976
- const dbPath = getResolvedPaths().knowledgeDbPath;
26977
- const dir = dirname17(dbPath);
26978
- if (!existsSync41(dir)) {
26979
- mkdirSync11(dir, { recursive: true });
26980
- }
26981
- const db = new Database3(dbPath);
26982
- db.pragma("journal_mode = WAL");
26983
- db.pragma("foreign_keys = ON");
26984
- initKnowledgeSchema(db);
26985
- return db;
26986
- }
26987
- function initKnowledgeSchema(db) {
26988
- db.exec(`
26989
- -- Core document chunks (parsed from .claude/**/*.md)
26990
- CREATE TABLE IF NOT EXISTS knowledge_documents (
26991
- id INTEGER PRIMARY KEY AUTOINCREMENT,
26992
- file_path TEXT NOT NULL,
26993
- category TEXT NOT NULL,
26994
- title TEXT NOT NULL,
26995
- description TEXT,
26996
- content_hash TEXT NOT NULL,
26997
- indexed_at TEXT NOT NULL,
26998
- indexed_at_epoch INTEGER NOT NULL
26999
- );
27000
-
27001
- CREATE UNIQUE INDEX IF NOT EXISTS idx_kd_filepath ON knowledge_documents(file_path);
27002
- CREATE INDEX IF NOT EXISTS idx_kd_category ON knowledge_documents(category);
27003
-
27004
- -- Structured chunks within documents
27005
- CREATE TABLE IF NOT EXISTS knowledge_chunks (
27006
- id INTEGER PRIMARY KEY AUTOINCREMENT,
27007
- document_id INTEGER NOT NULL REFERENCES knowledge_documents(id) ON DELETE CASCADE,
27008
- chunk_type TEXT NOT NULL CHECK(chunk_type IN (
27009
- 'section', 'table_row', 'code_block', 'rule', 'incident', 'pattern', 'command', 'mismatch'
27010
- )),
27011
- heading TEXT,
27012
- content TEXT NOT NULL,
27013
- line_start INTEGER,
27014
- line_end INTEGER,
27015
- metadata TEXT DEFAULT '{}'
27016
- );
27017
-
27018
- CREATE INDEX IF NOT EXISTS idx_kc_doc ON knowledge_chunks(document_id);
27019
- CREATE INDEX IF NOT EXISTS idx_kc_type ON knowledge_chunks(chunk_type);
27020
- CREATE INDEX IF NOT EXISTS idx_kc_heading ON knowledge_chunks(heading);
27021
-
27022
- -- Canonical Rules index
27023
- CREATE TABLE IF NOT EXISTS knowledge_rules (
27024
- id INTEGER PRIMARY KEY AUTOINCREMENT,
27025
- rule_id TEXT UNIQUE NOT NULL,
27026
- rule_text TEXT NOT NULL,
27027
- vr_type TEXT,
27028
- reference_path TEXT,
27029
- severity TEXT DEFAULT 'HIGH',
27030
- prevention_summary TEXT
27031
- );
27032
-
27033
- CREATE INDEX IF NOT EXISTS idx_kr_id ON knowledge_rules(rule_id);
27034
-
27035
- -- Verification Requirements index
27036
- CREATE TABLE IF NOT EXISTS knowledge_verifications (
27037
- id INTEGER PRIMARY KEY AUTOINCREMENT,
27038
- vr_type TEXT UNIQUE NOT NULL,
27039
- command TEXT NOT NULL,
27040
- expected TEXT NOT NULL,
27041
- use_when TEXT NOT NULL,
27042
- catches TEXT,
27043
- category TEXT
27044
- );
27045
-
27046
- CREATE INDEX IF NOT EXISTS idx_kv_type ON knowledge_verifications(vr_type);
27047
- CREATE INDEX IF NOT EXISTS idx_kv_category ON knowledge_verifications(category);
27048
-
27049
- -- Incident index
27050
- CREATE TABLE IF NOT EXISTS knowledge_incidents (
27051
- id INTEGER PRIMARY KEY AUTOINCREMENT,
27052
- incident_num INTEGER UNIQUE NOT NULL,
27053
- date TEXT NOT NULL,
27054
- type TEXT NOT NULL,
27055
- gap_found TEXT NOT NULL,
27056
- prevention TEXT NOT NULL,
27057
- cr_added TEXT,
27058
- root_cause TEXT,
27059
- user_quote TEXT
27060
- );
27061
-
27062
- CREATE INDEX IF NOT EXISTS idx_ki_num ON knowledge_incidents(incident_num);
27063
- CREATE INDEX IF NOT EXISTS idx_ki_type ON knowledge_incidents(type);
27064
- CREATE INDEX IF NOT EXISTS idx_ki_cr ON knowledge_incidents(cr_added);
27065
-
27066
- -- Schema mismatch quick lookup
27067
- -- P-H013 (plan-stage-c-high-batch): the source column no longer has a
27068
- -- SQL DEFAULT. Previously the value was a JS template-literal
27069
- -- interpolation baked into the customer SQLite at schema creation
27070
- -- time, so later config changes were ignored. Default is now applied
27071
- -- at INSERT time via getConfig().conventions.knowledgeSourceFiles[0]
27072
- -- in knowledge-indexer.ts -- a runtime config lookup that reflects
27073
- -- the customers current config.
27074
- CREATE TABLE IF NOT EXISTS knowledge_schema_mismatches (
27075
- id INTEGER PRIMARY KEY AUTOINCREMENT,
27076
- table_name TEXT NOT NULL,
27077
- wrong_column TEXT NOT NULL,
27078
- correct_column TEXT NOT NULL,
27079
- source TEXT NOT NULL
27080
- );
27081
-
27082
- CREATE INDEX IF NOT EXISTS idx_ksm_table ON knowledge_schema_mismatches(table_name);
27083
-
27084
- -- Cross-reference graph
27085
- CREATE TABLE IF NOT EXISTS knowledge_edges (
27086
- id INTEGER PRIMARY KEY AUTOINCREMENT,
27087
- source_type TEXT NOT NULL,
27088
- source_id TEXT NOT NULL,
27089
- target_type TEXT NOT NULL,
27090
- target_id TEXT NOT NULL,
27091
- edge_type TEXT NOT NULL
27092
- );
27093
-
27094
- CREATE INDEX IF NOT EXISTS idx_ke_source ON knowledge_edges(source_type, source_id);
27095
- CREATE INDEX IF NOT EXISTS idx_ke_target ON knowledge_edges(target_type, target_id);
27096
- CREATE UNIQUE INDEX IF NOT EXISTS idx_ke_unique ON knowledge_edges(source_type, source_id, target_type, target_id, edge_type);
27097
-
27098
- -- Staleness tracking
27099
- CREATE TABLE IF NOT EXISTS knowledge_meta (
27100
- key TEXT PRIMARY KEY,
27101
- value TEXT NOT NULL
27102
- );
27103
- `);
27104
- try {
27105
- db.exec(`
27106
- CREATE VIRTUAL TABLE IF NOT EXISTS knowledge_fts USING fts5(
27107
- heading, content, chunk_type, file_path
27108
- );
27109
-
27110
- CREATE TRIGGER IF NOT EXISTS kc_fts_insert AFTER INSERT ON knowledge_chunks BEGIN
27111
- INSERT INTO knowledge_fts(rowid, heading, content, chunk_type, file_path)
27112
- SELECT new.id, new.heading, new.content, new.chunk_type, kd.file_path
27113
- FROM knowledge_documents kd WHERE kd.id = new.document_id;
27114
- END;
27115
-
27116
- CREATE TRIGGER IF NOT EXISTS kc_fts_delete AFTER DELETE ON knowledge_chunks BEGIN
27117
- INSERT INTO knowledge_fts(knowledge_fts, rowid, heading, content, chunk_type, file_path)
27118
- SELECT 'delete', old.id, old.heading, old.content, old.chunk_type, kd.file_path
27119
- FROM knowledge_documents kd WHERE kd.id = old.document_id;
27120
- END;
27121
-
27122
- CREATE TRIGGER IF NOT EXISTS kc_fts_update AFTER UPDATE ON knowledge_chunks BEGIN
27123
- INSERT INTO knowledge_fts(knowledge_fts, rowid, heading, content, chunk_type, file_path)
27124
- SELECT 'delete', old.id, old.heading, old.content, old.chunk_type, kd.file_path
27125
- FROM knowledge_documents kd WHERE kd.id = old.document_id;
27126
- INSERT INTO knowledge_fts(rowid, heading, content, chunk_type, file_path)
27127
- SELECT new.id, new.heading, new.content, new.chunk_type, kd.file_path
27128
- FROM knowledge_documents kd WHERE kd.id = new.document_id;
27129
- END;
27130
- `);
27131
- } catch {
27132
- }
27133
- }
27134
- var init_knowledge_db = __esm({
27135
- "src/knowledge-db.ts"() {
27136
- "use strict";
27137
- init_config();
27138
- }
27139
- });
27140
-
27141
27276
  // src/python/domain-enforcer.ts
27142
27277
  function classifyPythonFileDomain(file) {
27143
27278
  const config = getConfig();
@@ -27946,29 +28081,37 @@ function assertCodegraphDb(codegraphDb, toolName) {
27946
28081
  }
27947
28082
  return codegraphDb;
27948
28083
  }
27949
- async function handleToolCall(name, args2, dataDb, codegraphDb) {
28084
+ async function handleToolCall(name, args2, dataDb, codegraphDb, memoryDbCache, knowledgeDbCache) {
27950
28085
  const userTier = await getCurrentTier();
27951
28086
  const requiredTier = getToolTier(name);
27952
28087
  if (!isToolAllowed(name, userTier)) {
27953
28088
  return text17(`This tool requires ${requiredTier} tier. Current tier: ${userTier}. Upgrade at https://massu.ai/pricing`);
27954
28089
  }
27955
28090
  const pfx = prefix2();
28091
+ async function withMemDb(fn) {
28092
+ if (memoryDbCache) return fn(memoryDbCache);
28093
+ const db = getMemoryDb();
28094
+ try {
28095
+ return await fn(db);
28096
+ } finally {
28097
+ db.close();
28098
+ }
28099
+ }
28100
+ async function withKnowledgeDb(fn) {
28101
+ if (knowledgeDbCache) return fn(knowledgeDbCache);
28102
+ const db = getKnowledgeDb();
28103
+ try {
28104
+ return await fn(db);
28105
+ } finally {
28106
+ db.close();
28107
+ }
28108
+ }
27956
28109
  try {
27957
28110
  if (name.startsWith(pfx + "_memory_")) {
27958
- const memDb = getMemoryDb();
27959
- try {
27960
- return handleMemoryToolCall(name, args2, memDb);
27961
- } finally {
27962
- memDb.close();
27963
- }
28111
+ return withMemDb((memDb) => handleMemoryToolCall(name, args2, memDb));
27964
28112
  }
27965
28113
  if (isObservabilityTool(name)) {
27966
- const memDb = getMemoryDb();
27967
- try {
27968
- return handleObservabilityToolCall(name, args2, memDb);
27969
- } finally {
27970
- memDb.close();
27971
- }
28114
+ return withMemDb((memDb) => handleObservabilityToolCall(name, args2, memDb));
27972
28115
  }
27973
28116
  if (name.startsWith(pfx + "_docs_")) {
27974
28117
  return handleDocsToolCall(name, args2);
@@ -27977,92 +28120,37 @@ async function handleToolCall(name, args2, dataDb, codegraphDb) {
27977
28120
  return handleSentinelToolCall(name, args2, assertDataDb(dataDb, name));
27978
28121
  }
27979
28122
  if (isAnalyticsTool(name)) {
27980
- const memDb = getMemoryDb();
27981
- try {
27982
- return handleAnalyticsToolCall(name, args2, memDb);
27983
- } finally {
27984
- memDb.close();
27985
- }
28123
+ return withMemDb((memDb) => handleAnalyticsToolCall(name, args2, memDb));
27986
28124
  }
27987
28125
  if (isCostTool(name)) {
27988
- const memDb = getMemoryDb();
27989
- try {
27990
- return handleCostToolCall(name, args2, memDb);
27991
- } finally {
27992
- memDb.close();
27993
- }
28126
+ return withMemDb((memDb) => handleCostToolCall(name, args2, memDb));
27994
28127
  }
27995
28128
  if (isPromptTool(name)) {
27996
- const memDb = getMemoryDb();
27997
- try {
27998
- return handlePromptToolCall(name, args2, memDb);
27999
- } finally {
28000
- memDb.close();
28001
- }
28129
+ return withMemDb((memDb) => handlePromptToolCall(name, args2, memDb));
28002
28130
  }
28003
28131
  if (isAuditTool(name)) {
28004
- const memDb = getMemoryDb();
28005
- try {
28006
- return handleAuditToolCall(name, args2, memDb);
28007
- } finally {
28008
- memDb.close();
28009
- }
28132
+ return withMemDb((memDb) => handleAuditToolCall(name, args2, memDb));
28010
28133
  }
28011
28134
  if (isValidationTool(name)) {
28012
- const memDb = getMemoryDb();
28013
- try {
28014
- return handleValidationToolCall(name, args2, memDb);
28015
- } finally {
28016
- memDb.close();
28017
- }
28135
+ return withMemDb((memDb) => handleValidationToolCall(name, args2, memDb));
28018
28136
  }
28019
28137
  if (isAdrTool(name)) {
28020
- const memDb = getMemoryDb();
28021
- try {
28022
- return handleAdrToolCall(name, args2, memDb);
28023
- } finally {
28024
- memDb.close();
28025
- }
28138
+ return withMemDb((memDb) => handleAdrToolCall(name, args2, memDb));
28026
28139
  }
28027
28140
  if (isSecurityTool(name)) {
28028
- const memDb = getMemoryDb();
28029
- try {
28030
- return handleSecurityToolCall(name, args2, memDb);
28031
- } finally {
28032
- memDb.close();
28033
- }
28141
+ return withMemDb((memDb) => handleSecurityToolCall(name, args2, memDb));
28034
28142
  }
28035
28143
  if (isDependencyTool(name)) {
28036
- const memDb = getMemoryDb();
28037
- try {
28038
- return handleDependencyToolCall(name, args2, memDb);
28039
- } finally {
28040
- memDb.close();
28041
- }
28144
+ return withMemDb((memDb) => handleDependencyToolCall(name, args2, memDb));
28042
28145
  }
28043
28146
  if (isTeamTool(name) && isCloudFeatureAvailable()) {
28044
- const memDb = getMemoryDb();
28045
- try {
28046
- return handleTeamToolCall(name, args2, memDb);
28047
- } finally {
28048
- memDb.close();
28049
- }
28147
+ return withMemDb((memDb) => handleTeamToolCall(name, args2, memDb));
28050
28148
  }
28051
28149
  if (isRegressionTool(name)) {
28052
- const memDb = getMemoryDb();
28053
- try {
28054
- return handleRegressionToolCall(name, args2, memDb);
28055
- } finally {
28056
- memDb.close();
28057
- }
28150
+ return withMemDb((memDb) => handleRegressionToolCall(name, args2, memDb));
28058
28151
  }
28059
28152
  if (isKnowledgeTool(name)) {
28060
- const knowledgeDb = getKnowledgeDb();
28061
- try {
28062
- return handleKnowledgeToolCall(name, args2, knowledgeDb);
28063
- } finally {
28064
- knowledgeDb.close();
28065
- }
28153
+ return withKnowledgeDb((kdb) => handleKnowledgeToolCall(name, args2, kdb));
28066
28154
  }
28067
28155
  if (isPythonTool(name)) {
28068
28156
  const pyDataDb = assertDataDb(dataDb, name);
@@ -28070,12 +28158,7 @@ async function handleToolCall(name, args2, dataDb, codegraphDb) {
28070
28158
  return handlePythonToolCall(name, args2, pyDataDb);
28071
28159
  }
28072
28160
  if (isLicenseTool(name)) {
28073
- const memDb = getMemoryDb();
28074
- try {
28075
- return await handleLicenseToolCall(name, args2, memDb);
28076
- } finally {
28077
- memDb.close();
28078
- }
28161
+ return withMemDb((memDb) => handleLicenseToolCall(name, args2, memDb));
28079
28162
  }
28080
28163
  const baseName = stripPrefix3(name);
28081
28164
  switch (baseName) {
@@ -28897,10 +28980,14 @@ var init_tool_db_needs = __esm({
28897
28980
  function createDispatcher(options) {
28898
28981
  let codegraphDbCache = null;
28899
28982
  let dataDbCache = null;
28983
+ let memoryDbCache = null;
28984
+ let knowledgeDbCache = null;
28900
28985
  function resolveDbsForTool(toolName) {
28901
28986
  const needs = getToolDbNeeds(toolName, getConfig().toolPrefix);
28902
28987
  let dataDbResolved;
28903
28988
  let codegraphDbResolved;
28989
+ let memoryDbResolved;
28990
+ let knowledgeDbResolved;
28904
28991
  if (needs.includes("data")) {
28905
28992
  if (!dataDbCache) dataDbCache = getDataDb();
28906
28993
  dataDbResolved = dataDbCache;
@@ -28909,7 +28996,21 @@ function createDispatcher(options) {
28909
28996
  if (!codegraphDbCache) codegraphDbCache = getCodeGraphDb();
28910
28997
  codegraphDbResolved = codegraphDbCache;
28911
28998
  }
28912
- return { needs, dataDb: dataDbResolved, codegraphDb: codegraphDbResolved };
28999
+ if (needs.includes("memory")) {
29000
+ if (!memoryDbCache) memoryDbCache = getMemoryDb();
29001
+ memoryDbResolved = memoryDbCache;
29002
+ }
29003
+ if (needs.includes("knowledge")) {
29004
+ if (!knowledgeDbCache) knowledgeDbCache = getKnowledgeDb();
29005
+ knowledgeDbResolved = knowledgeDbCache;
29006
+ }
29007
+ return {
29008
+ needs,
29009
+ dataDb: dataDbResolved,
29010
+ codegraphDb: codegraphDbResolved,
29011
+ memoryDb: memoryDbResolved,
29012
+ knowledgeDb: knowledgeDbResolved
29013
+ };
28913
29014
  }
28914
29015
  async function handleRequest(request) {
28915
29016
  const { method, params, id } = request;
@@ -28939,8 +29040,8 @@ function createDispatcher(options) {
28939
29040
  const toolName = params?.name ?? "";
28940
29041
  const toolArgs = params?.arguments ?? {};
28941
29042
  try {
28942
- const { dataDb, codegraphDb } = resolveDbsForTool(toolName);
28943
- const result = await handleToolCall(toolName, toolArgs, dataDb, codegraphDb);
29043
+ const { dataDb, codegraphDb, memoryDb, knowledgeDb } = resolveDbsForTool(toolName);
29044
+ const result = await handleToolCall(toolName, toolArgs, dataDb, codegraphDb, memoryDb, knowledgeDb);
28944
29045
  return { jsonrpc: "2.0", id: id ?? null, result };
28945
29046
  } catch (err) {
28946
29047
  if (err instanceof CodegraphDbNotInitializedError) {
@@ -29032,6 +29133,14 @@ function createDispatcher(options) {
29032
29133
  dataDbCache.close();
29033
29134
  dataDbCache = null;
29034
29135
  }
29136
+ if (memoryDbCache) {
29137
+ memoryDbCache.close();
29138
+ memoryDbCache = null;
29139
+ }
29140
+ if (knowledgeDbCache) {
29141
+ knowledgeDbCache.close();
29142
+ knowledgeDbCache = null;
29143
+ }
29035
29144
  }
29036
29145
  return { handleRequest, processLine, closeCachedDbs };
29037
29146
  }
@@ -29039,6 +29148,8 @@ var init_server_dispatch = __esm({
29039
29148
  "src/server-dispatch.ts"() {
29040
29149
  "use strict";
29041
29150
  init_db();
29151
+ init_memory_db();
29152
+ init_knowledge_db();
29042
29153
  init_config();
29043
29154
  init_tools();
29044
29155
  init_tool_db_needs();
@@ -29646,6 +29757,15 @@ async function main() {
29646
29757
  break;
29647
29758
  }
29648
29759
  default: {
29760
+ if (subcommand && !subcommand.startsWith("-")) {
29761
+ process.stderr.write(
29762
+ `Error: Unknown subcommand '${subcommand}'.
29763
+ Run \`npx @massu/core --help\` for the list of available subcommands.
29764
+ To start the MCP server, run without a subcommand (e.g. \`npx @massu/core\`).
29765
+ `
29766
+ );
29767
+ process.exit(2);
29768
+ }
29649
29769
  await Promise.resolve().then(() => (init_server(), server_exports));
29650
29770
  }
29651
29771
  }