@massu/core 1.10.8 → 1.12.1

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 +1334 -0
  2. package/dist/adapter.js +6 -4
  3. package/dist/cli.js +581 -448
  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 +67 -19
  16. package/dist/hooks/pre-compact.js +28 -3
  17. package/dist/hooks/pre-delete-check.js +53 -27
  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 +31 -6
  23. package/dist/hooks/session-start.js +8709 -8459
  24. package/dist/hooks/user-prompt.js +25 -1
  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
@@ -1,5 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import{createRequire as __cr}from"module";const require=__cr(import.meta.url);
3
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
4
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
5
+ }) : x)(function(x) {
6
+ if (typeof require !== "undefined") return require.apply(this, arguments);
7
+ throw Error('Dynamic require of "' + x + '" is not supported');
8
+ });
3
9
 
4
10
  // src/memory-db.ts
5
11
  import Database from "better-sqlite3";
@@ -348,6 +354,22 @@ var RawConfigSchema = z.object({
348
354
  accessScopes: z.array(z.string()).optional(),
349
355
  domains: z.array(DomainConfigSchema).default([]),
350
356
  rules: z.array(PatternRuleConfigSchema).default([]),
357
+ // P-M-036 (plan-stage-d-medium-sweep): customer-authored CR-style
358
+ // governance rules. DISTINCT from `rules:` above (path-scoped lint hints
359
+ // used by pattern-scanner). At config-refresh time these entries are
360
+ // loaded into the `knowledge_rules` SQLite table with
361
+ // `source = 'customer-config'` so `massu_knowledge_rule` and the
362
+ // governance docs surface customer-defined rules alongside framework CRs.
363
+ governance_rules: z.array(
364
+ z.object({
365
+ id: z.string().min(1, "governance_rules[].id is required"),
366
+ title: z.string().min(1, "governance_rules[].title is required"),
367
+ description: z.string().min(1, "governance_rules[].description is required"),
368
+ vr_type: z.string().default("VR-CUSTOM"),
369
+ reference_path: z.string().optional(),
370
+ severity: z.enum(["critical", "high", "medium", "low", "info"]).default("medium")
371
+ }).passthrough()
372
+ ).default([]),
351
373
  analytics: AnalyticsConfigSchema,
352
374
  governance: GovernanceConfigSchema,
353
375
  security: SecurityConfigSchema,
@@ -466,6 +488,8 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
466
488
  accessScopes: parsed.accessScopes,
467
489
  domains: parsed.domains,
468
490
  rules: parsed.rules,
491
+ // P-M-036: customer-authored CR-style governance rules.
492
+ governance_rules: parsed.governance_rules,
469
493
  analytics: parsed.analytics,
470
494
  governance: parsed.governance,
471
495
  security: parsed.security,
@@ -1018,6 +1042,12 @@ function initMemorySchema(db) {
1018
1042
  features TEXT DEFAULT '[]'
1019
1043
  );
1020
1044
  `);
1045
+ const licenseCacheCols = db.prepare(`PRAGMA table_info(license_cache)`).all();
1046
+ if (!licenseCacheCols.some((c) => c.name === "signed_payload_json")) {
1047
+ db.exec(
1048
+ `ALTER TABLE license_cache ADD COLUMN signed_payload_json TEXT NOT NULL DEFAULT ''`
1049
+ );
1050
+ }
1021
1051
  db.exec(`
1022
1052
  CREATE TABLE IF NOT EXISTS failure_classes (
1023
1053
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -1848,9 +1878,8 @@ function storeSecurityScore(db, sessionId, filePath, riskScore, findings) {
1848
1878
  }
1849
1879
 
1850
1880
  // src/hooks/post-tool-use.ts
1851
- import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
1881
+ import { readFileSync as readFileSync6, existsSync as existsSync7, statSync as statSync2 } from "fs";
1852
1882
  import { join as join2 } from "path";
1853
- import { parse as parseYaml3 } from "yaml";
1854
1883
 
1855
1884
  // src/memory-file-ingest.ts
1856
1885
  import { readFileSync as readFileSync5, existsSync as existsSync6, readdirSync } from "fs";
@@ -1908,6 +1937,13 @@ function mapMemoryTypeToObservationType(memoryType) {
1908
1937
  }
1909
1938
 
1910
1939
  // src/hooks/post-tool-use.ts
1940
+ var _yamlParser = null;
1941
+ var parseYaml3 = (content) => {
1942
+ if (!_yamlParser) {
1943
+ _yamlParser = __require("yaml").parse;
1944
+ }
1945
+ return _yamlParser(content);
1946
+ };
1911
1947
  var seenReads = /* @__PURE__ */ new Set();
1912
1948
  var currentSessionId = null;
1913
1949
  async function main() {
@@ -1992,7 +2028,7 @@ async function main() {
1992
2028
  const counts = parseTestRunOutput(tool_response ?? "");
1993
2029
  if (counts) {
1994
2030
  const modifiedFeatures = db.prepare(
1995
- "SELECT feature_key FROM feature_health WHERE modifications_since_test > 0"
2031
+ "SELECT feature_key FROM feature_health WHERE modifications_since_test > 0 LIMIT 10000"
1996
2032
  ).all();
1997
2033
  for (const row of modifiedFeatures) {
1998
2034
  recordTestResult(db, row.feature_key, counts.passing, counts.failing);
@@ -2135,26 +2171,37 @@ function readStdin() {
2135
2171
  setTimeout(() => resolve5(data), 3e3);
2136
2172
  });
2137
2173
  }
2174
+ var _cachedConventions = null;
2175
+ var _cachedConventionsPath = null;
2176
+ var _cachedConventionsMtimeMs = 0;
2177
+ var _conventionDefaults = {
2178
+ knowledgeSourceFiles: ["CLAUDE.md", "MEMORY.md", "corrections.md"],
2179
+ claudeDirName: ".claude"
2180
+ };
2138
2181
  function readConventions(cwd) {
2139
- const defaults = {
2140
- knowledgeSourceFiles: ["CLAUDE.md", "MEMORY.md", "corrections.md"],
2141
- claudeDirName: ".claude"
2142
- };
2143
2182
  try {
2144
2183
  const projectRoot = cwd ?? process.cwd();
2145
2184
  const configPath = join2(projectRoot, "massu.config.yaml");
2146
- if (!existsSync7(configPath)) return defaults;
2185
+ if (!existsSync7(configPath)) return _conventionDefaults;
2186
+ const mtimeMs = statSync2(configPath).mtimeMs;
2187
+ if (_cachedConventions !== null && _cachedConventionsPath === configPath && _cachedConventionsMtimeMs === mtimeMs) {
2188
+ return _cachedConventions;
2189
+ }
2147
2190
  const content = readFileSync6(configPath, "utf-8");
2148
2191
  const parsed = parseYaml3(content);
2149
- if (!parsed || typeof parsed !== "object") return defaults;
2192
+ if (!parsed || typeof parsed !== "object") return _conventionDefaults;
2150
2193
  const conventions = parsed.conventions;
2151
- if (!conventions || typeof conventions !== "object") return defaults;
2152
- return {
2153
- knowledgeSourceFiles: Array.isArray(conventions.knowledgeSourceFiles) ? conventions.knowledgeSourceFiles : defaults.knowledgeSourceFiles,
2154
- claudeDirName: typeof conventions.claudeDirName === "string" ? conventions.claudeDirName : defaults.claudeDirName
2194
+ if (!conventions || typeof conventions !== "object") return _conventionDefaults;
2195
+ const resolved = {
2196
+ knowledgeSourceFiles: Array.isArray(conventions.knowledgeSourceFiles) ? conventions.knowledgeSourceFiles : _conventionDefaults.knowledgeSourceFiles,
2197
+ claudeDirName: typeof conventions.claudeDirName === "string" ? conventions.claudeDirName : _conventionDefaults.claudeDirName
2155
2198
  };
2199
+ _cachedConventions = resolved;
2200
+ _cachedConventionsPath = configPath;
2201
+ _cachedConventionsMtimeMs = mtimeMs;
2202
+ return resolved;
2156
2203
  } catch {
2157
- return defaults;
2204
+ return _conventionDefaults;
2158
2205
  }
2159
2206
  }
2160
2207
  function isKnowledgeSourceFile(filePath) {
@@ -2182,11 +2229,12 @@ function checkMemoryFileIntegrity(filePath) {
2182
2229
  if (lines.length > MAX_LINES) {
2183
2230
  issues.push(`MEMORY.md exceeds ${MAX_LINES} lines (currently ${lines.length}). Consider archiving old entries.`);
2184
2231
  }
2185
- const requiredSections = ["# Massu Memory", "## Key Learnings", "## Common Gotchas"];
2186
- for (const section of requiredSections) {
2187
- if (!content.includes(section)) {
2188
- issues.push(`Missing required section: "${section}"`);
2189
- }
2232
+ if (!/^#\s+Memory(\s+Index)?\s*$/m.test(content)) {
2233
+ issues.push("MEMORY.md missing top-level `# Memory Index` heading");
2234
+ }
2235
+ const linkLineCount = (content.match(/^- \[[^\]]+\]\([^)]+\.md\)/mg) ?? []).length;
2236
+ if (linkLineCount === 0) {
2237
+ issues.push("MEMORY.md has no `- [Title](file.md) \u2014 hook` index lines");
2190
2238
  }
2191
2239
  } catch (_e) {
2192
2240
  }
@@ -348,6 +348,22 @@ var RawConfigSchema = z.object({
348
348
  accessScopes: z.array(z.string()).optional(),
349
349
  domains: z.array(DomainConfigSchema).default([]),
350
350
  rules: z.array(PatternRuleConfigSchema).default([]),
351
+ // P-M-036 (plan-stage-d-medium-sweep): customer-authored CR-style
352
+ // governance rules. DISTINCT from `rules:` above (path-scoped lint hints
353
+ // used by pattern-scanner). At config-refresh time these entries are
354
+ // loaded into the `knowledge_rules` SQLite table with
355
+ // `source = 'customer-config'` so `massu_knowledge_rule` and the
356
+ // governance docs surface customer-defined rules alongside framework CRs.
357
+ governance_rules: z.array(
358
+ z.object({
359
+ id: z.string().min(1, "governance_rules[].id is required"),
360
+ title: z.string().min(1, "governance_rules[].title is required"),
361
+ description: z.string().min(1, "governance_rules[].description is required"),
362
+ vr_type: z.string().default("VR-CUSTOM"),
363
+ reference_path: z.string().optional(),
364
+ severity: z.enum(["critical", "high", "medium", "low", "info"]).default("medium")
365
+ }).passthrough()
366
+ ).default([]),
351
367
  analytics: AnalyticsConfigSchema,
352
368
  governance: GovernanceConfigSchema,
353
369
  security: SecurityConfigSchema,
@@ -466,6 +482,8 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
466
482
  accessScopes: parsed.accessScopes,
467
483
  domains: parsed.domains,
468
484
  rules: parsed.rules,
485
+ // P-M-036: customer-authored CR-style governance rules.
486
+ governance_rules: parsed.governance_rules,
469
487
  analytics: parsed.analytics,
470
488
  governance: parsed.governance,
471
489
  security: parsed.security,
@@ -1018,6 +1036,12 @@ function initMemorySchema(db) {
1018
1036
  features TEXT DEFAULT '[]'
1019
1037
  );
1020
1038
  `);
1039
+ const licenseCacheCols = db.prepare(`PRAGMA table_info(license_cache)`).all();
1040
+ if (!licenseCacheCols.some((c) => c.name === "signed_payload_json")) {
1041
+ db.exec(
1042
+ `ALTER TABLE license_cache ADD COLUMN signed_payload_json TEXT NOT NULL DEFAULT ''`
1043
+ );
1044
+ }
1021
1045
  db.exec(`
1022
1046
  CREATE TABLE IF NOT EXISTS failure_classes (
1023
1047
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -1148,11 +1172,12 @@ async function main() {
1148
1172
  const db = getMemoryDb();
1149
1173
  try {
1150
1174
  createSession(db, session_id);
1151
- const observations = db.prepare(
1152
- "SELECT * FROM observations WHERE session_id = ? ORDER BY created_at_epoch ASC"
1175
+ const observationsDesc = db.prepare(
1176
+ "SELECT * FROM observations WHERE session_id = ? ORDER BY created_at_epoch DESC LIMIT 1000"
1153
1177
  ).all(session_id);
1178
+ const observations = observationsDesc.reverse();
1154
1179
  const prompts = db.prepare(
1155
- "SELECT prompt_text FROM user_prompts WHERE session_id = ? ORDER BY prompt_number ASC"
1180
+ "SELECT prompt_text FROM user_prompts WHERE session_id = ? ORDER BY prompt_number ASC LIMIT 10000"
1156
1181
  ).all(session_id);
1157
1182
  const summary = buildSnapshotSummary(observations, prompts);
1158
1183
  addSummary(db, session_id, summary);
@@ -347,6 +347,22 @@ var RawConfigSchema = z.object({
347
347
  accessScopes: z.array(z.string()).optional(),
348
348
  domains: z.array(DomainConfigSchema).default([]),
349
349
  rules: z.array(PatternRuleConfigSchema).default([]),
350
+ // P-M-036 (plan-stage-d-medium-sweep): customer-authored CR-style
351
+ // governance rules. DISTINCT from `rules:` above (path-scoped lint hints
352
+ // used by pattern-scanner). At config-refresh time these entries are
353
+ // loaded into the `knowledge_rules` SQLite table with
354
+ // `source = 'customer-config'` so `massu_knowledge_rule` and the
355
+ // governance docs surface customer-defined rules alongside framework CRs.
356
+ governance_rules: z.array(
357
+ z.object({
358
+ id: z.string().min(1, "governance_rules[].id is required"),
359
+ title: z.string().min(1, "governance_rules[].title is required"),
360
+ description: z.string().min(1, "governance_rules[].description is required"),
361
+ vr_type: z.string().default("VR-CUSTOM"),
362
+ reference_path: z.string().optional(),
363
+ severity: z.enum(["critical", "high", "medium", "low", "info"]).default("medium")
364
+ }).passthrough()
365
+ ).default([]),
350
366
  analytics: AnalyticsConfigSchema,
351
367
  governance: GovernanceConfigSchema,
352
368
  security: SecurityConfigSchema,
@@ -465,6 +481,8 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
465
481
  accessScopes: parsed.accessScopes,
466
482
  domains: parsed.domains,
467
483
  rules: parsed.rules,
484
+ // P-M-036: customer-authored CR-style governance rules.
485
+ governance_rules: parsed.governance_rules,
468
486
  analytics: parsed.analytics,
469
487
  governance: parsed.governance,
470
488
  security: parsed.security,
@@ -574,7 +592,7 @@ function getFeatureImpact(db, filePaths) {
574
592
  const affectedFeatureIds = /* @__PURE__ */ new Set();
575
593
  for (const filePath of filePaths) {
576
594
  const links = db.prepare(
577
- `SELECT feature_id FROM ${t("sentinel_components")} WHERE component_file = ?`
595
+ `SELECT feature_id FROM ${t("sentinel_components")} WHERE component_file = ? LIMIT 10000`
578
596
  ).all(filePath);
579
597
  for (const link of links) {
580
598
  affectedFeatureIds.add(link.feature_id);
@@ -587,7 +605,7 @@ function getFeatureImpact(db, filePaths) {
587
605
  const feature = getFeatureById(db, featureId);
588
606
  if (!feature || feature.status !== "active") continue;
589
607
  const allComponents = db.prepare(
590
- `SELECT component_file, is_primary FROM ${t("sentinel_components")} WHERE feature_id = ?`
608
+ `SELECT component_file, is_primary FROM ${t("sentinel_components")} WHERE feature_id = ? LIMIT 10000`
591
609
  ).all(featureId);
592
610
  const affected = allComponents.filter((c) => fileSet.has(c.component_file));
593
611
  const remaining = allComponents.filter((c) => !fileSet.has(c.component_file));
@@ -620,6 +638,11 @@ function getFeatureImpact(db, filePaths) {
620
638
  };
621
639
  }
622
640
 
641
+ // src/hooks/lib/write-hook-message.ts
642
+ function writeHookMessage(message) {
643
+ process.stdout.write(JSON.stringify({ message }) + "\n");
644
+ }
645
+
623
646
  // src/hooks/pre-delete-check.ts
624
647
  var PROJECT_ROOT2 = getProjectRoot();
625
648
  var KNOWLEDGE_PROTECTED_FILES = [
@@ -688,35 +711,24 @@ function extractDeletedFiles(input) {
688
711
  }
689
712
  return files;
690
713
  }
691
- async function main() {
714
+ function runPreDeleteChecks(hookInput) {
715
+ const messages = [];
692
716
  try {
693
- const input = await readStdin();
694
- const hookInput = JSON.parse(input);
695
717
  const knowledgeWarning = checkKnowledgeFileProtection(hookInput);
696
718
  if (knowledgeWarning) {
697
- process.stdout.write(JSON.stringify({ message: knowledgeWarning }));
698
- process.exit(0);
699
- return;
719
+ messages.push(knowledgeWarning);
720
+ return messages;
700
721
  }
701
722
  const deletedFiles = extractDeletedFiles(hookInput);
702
- if (deletedFiles.length === 0) {
703
- process.exit(0);
704
- return;
705
- }
723
+ if (deletedFiles.length === 0) return messages;
706
724
  const db = getDataDb();
707
- if (!db) {
708
- process.exit(0);
709
- return;
710
- }
725
+ if (!db) return messages;
711
726
  const SENTINEL_TABLE = t("sentinel");
712
727
  try {
713
728
  const tableExists = db.prepare(
714
729
  `SELECT name FROM sqlite_master WHERE type='table' AND name=?`
715
730
  ).get(SENTINEL_TABLE);
716
- if (!tableExists) {
717
- process.exit(0);
718
- return;
719
- }
731
+ if (!tableExists) return messages;
720
732
  const impact = getFeatureImpact(db, deletedFiles);
721
733
  if (impact.blocked) {
722
734
  const msg = [
@@ -737,28 +749,27 @@ async function main() {
737
749
  }
738
750
  msg.push("");
739
751
  msg.push("Create a migration plan before deleting these files.");
740
- process.stdout.write(JSON.stringify({ message: msg.join("\n") }));
752
+ messages.push(msg.join("\n"));
741
753
  }
742
754
  const pyFiles = deletedFiles.filter((f) => f.endsWith(".py"));
743
755
  if (pyFiles.length > 0) {
744
756
  try {
745
757
  for (const pyFile of pyFiles) {
746
758
  const importers = db.prepare(
747
- `SELECT source_file FROM ${t("py_imports")} WHERE target_file = ?`
759
+ `SELECT source_file FROM ${t("py_imports")} WHERE target_file = ? LIMIT 10000`
748
760
  ).all(pyFile);
749
761
  const routes = db.prepare(
750
- `SELECT method, path FROM ${t("py_routes")} WHERE file = ?`
762
+ `SELECT method, path FROM ${t("py_routes")} WHERE file = ? LIMIT 10000`
751
763
  ).all(pyFile);
752
764
  const models = db.prepare(
753
- `SELECT class_name FROM ${t("py_models")} WHERE file = ?`
765
+ `SELECT class_name FROM ${t("py_models")} WHERE file = ? LIMIT 10000`
754
766
  ).all(pyFile);
755
767
  if (importers.length > 0 || routes.length > 0 || models.length > 0) {
756
768
  const parts = [];
757
769
  if (importers.length > 0) parts.push(`imported by ${importers.length} files`);
758
770
  if (routes.length > 0) parts.push(`defines ${routes.length} routes`);
759
771
  if (models.length > 0) parts.push(`defines ${models.length} models`);
760
- const msg = `PYTHON IMPACT: "${pyFile}" ${parts.join(", ")}. Check dependents before deleting.`;
761
- process.stdout.write(JSON.stringify({ message: msg }));
772
+ messages.push(`PYTHON IMPACT: "${pyFile}" ${parts.join(", ")}. Check dependents before deleting.`);
762
773
  }
763
774
  }
764
775
  } catch {
@@ -769,6 +780,16 @@ async function main() {
769
780
  }
770
781
  } catch {
771
782
  }
783
+ return messages;
784
+ }
785
+ async function main() {
786
+ try {
787
+ const input = await readStdin();
788
+ const hookInput = JSON.parse(input);
789
+ const messages = runPreDeleteChecks(hookInput);
790
+ for (const msg of messages) writeHookMessage(msg);
791
+ } catch {
792
+ }
772
793
  process.exit(0);
773
794
  }
774
795
  function readStdin() {
@@ -782,4 +803,9 @@ function readStdin() {
782
803
  setTimeout(() => resolve2(data), 400);
783
804
  });
784
805
  }
785
- main();
806
+ if (process.argv[1]?.endsWith("pre-delete-check.js") || process.argv[1]?.endsWith("pre-delete-check")) {
807
+ main();
808
+ }
809
+ export {
810
+ runPreDeleteChecks
811
+ };