@massu/core 0.1.1 → 0.4.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 (151) hide show
  1. package/commands/_shared-preamble.md +76 -0
  2. package/commands/massu-audit-deps.md +211 -0
  3. package/commands/massu-changelog.md +174 -0
  4. package/commands/massu-cleanup.md +315 -0
  5. package/commands/massu-commit.md +481 -0
  6. package/commands/massu-create-plan.md +752 -0
  7. package/commands/massu-dead-code.md +131 -0
  8. package/commands/massu-debug.md +484 -0
  9. package/commands/massu-deploy.md +91 -0
  10. package/commands/massu-deps.md +374 -0
  11. package/commands/massu-doc-gen.md +279 -0
  12. package/commands/massu-docs.md +364 -0
  13. package/commands/massu-estimate.md +313 -0
  14. package/commands/massu-golden-path.md +973 -0
  15. package/commands/massu-guide.md +167 -0
  16. package/commands/massu-hotfix.md +480 -0
  17. package/commands/massu-loop-playwright.md +837 -0
  18. package/commands/massu-loop.md +775 -0
  19. package/commands/massu-new-feature.md +511 -0
  20. package/commands/massu-parity.md +214 -0
  21. package/commands/massu-plan.md +456 -0
  22. package/commands/massu-push-light.md +207 -0
  23. package/commands/massu-push.md +434 -0
  24. package/commands/massu-refactor.md +410 -0
  25. package/commands/massu-release.md +363 -0
  26. package/commands/massu-review.md +238 -0
  27. package/commands/massu-simplify.md +281 -0
  28. package/commands/massu-status.md +278 -0
  29. package/commands/massu-tdd.md +201 -0
  30. package/commands/massu-test.md +516 -0
  31. package/commands/massu-verify-playwright.md +281 -0
  32. package/commands/massu-verify.md +667 -0
  33. package/dist/cli.js +7772 -3140
  34. package/dist/hooks/cost-tracker.js +103 -40
  35. package/dist/hooks/post-edit-context.js +74 -8
  36. package/dist/hooks/post-tool-use.js +268 -106
  37. package/dist/hooks/pre-compact.js +167 -43
  38. package/dist/hooks/pre-delete-check.js +159 -42
  39. package/dist/hooks/quality-event.js +103 -40
  40. package/dist/hooks/security-gate.js +29 -0
  41. package/dist/hooks/session-end.js +143 -84
  42. package/dist/hooks/session-start.js +186 -49
  43. package/dist/hooks/user-prompt.js +189 -43
  44. package/package.json +10 -15
  45. package/src/adr-generator.ts +9 -2
  46. package/src/analytics.ts +9 -3
  47. package/src/audit-trail.ts +10 -3
  48. package/src/backfill-sessions.ts +5 -4
  49. package/src/cli.ts +6 -0
  50. package/src/cloud-sync.ts +14 -18
  51. package/src/commands/doctor.ts +193 -6
  52. package/src/commands/init.ts +230 -5
  53. package/src/commands/install-commands.ts +137 -0
  54. package/src/config.ts +68 -2
  55. package/src/cost-tracker.ts +11 -6
  56. package/src/db.ts +115 -2
  57. package/src/dependency-scorer.ts +9 -2
  58. package/src/docs-tools.ts +21 -16
  59. package/src/hooks/post-edit-context.ts +4 -4
  60. package/src/hooks/post-tool-use.ts +130 -0
  61. package/src/hooks/pre-compact.ts +23 -1
  62. package/src/hooks/pre-delete-check.ts +92 -4
  63. package/src/hooks/security-gate.ts +32 -0
  64. package/src/hooks/session-end.ts +3 -3
  65. package/src/hooks/session-start.ts +99 -6
  66. package/src/hooks/user-prompt.ts +46 -1
  67. package/src/import-resolver.ts +2 -1
  68. package/src/knowledge-db.ts +169 -0
  69. package/src/knowledge-indexer.ts +704 -0
  70. package/src/knowledge-tools.ts +1413 -0
  71. package/src/license.ts +482 -0
  72. package/src/memory-db.ts +1364 -23
  73. package/src/memory-tools.ts +14 -15
  74. package/src/observability-tools.ts +13 -2
  75. package/src/observation-extractor.ts +11 -4
  76. package/src/page-deps.ts +3 -2
  77. package/src/prompt-analyzer.ts +9 -2
  78. package/src/python/coupling-detector.ts +124 -0
  79. package/src/python/domain-enforcer.ts +83 -0
  80. package/src/python/impact-analyzer.ts +95 -0
  81. package/src/python/import-parser.ts +244 -0
  82. package/src/python/import-resolver.ts +135 -0
  83. package/src/python/migration-indexer.ts +115 -0
  84. package/src/python/migration-parser.ts +332 -0
  85. package/src/python/model-indexer.ts +70 -0
  86. package/src/python/model-parser.ts +279 -0
  87. package/src/python/route-indexer.ts +58 -0
  88. package/src/python/route-parser.ts +317 -0
  89. package/src/python-tools.ts +629 -0
  90. package/src/regression-detector.ts +9 -3
  91. package/src/security-scorer.ts +9 -2
  92. package/src/sentinel-db.ts +45 -89
  93. package/src/sentinel-tools.ts +8 -11
  94. package/src/server.ts +29 -7
  95. package/src/session-archiver.ts +4 -5
  96. package/src/team-knowledge.ts +9 -2
  97. package/src/tools.ts +1032 -44
  98. package/src/validate-features-runner.ts +0 -1
  99. package/src/validation-engine.ts +9 -2
  100. package/README.md +0 -40
  101. package/dist/server.js +0 -7008
  102. package/src/__tests__/adr-generator.test.ts +0 -260
  103. package/src/__tests__/analytics.test.ts +0 -282
  104. package/src/__tests__/audit-trail.test.ts +0 -382
  105. package/src/__tests__/backfill-sessions.test.ts +0 -690
  106. package/src/__tests__/cli.test.ts +0 -290
  107. package/src/__tests__/cloud-sync.test.ts +0 -261
  108. package/src/__tests__/config-sections.test.ts +0 -359
  109. package/src/__tests__/config.test.ts +0 -732
  110. package/src/__tests__/cost-tracker.test.ts +0 -348
  111. package/src/__tests__/db.test.ts +0 -177
  112. package/src/__tests__/dependency-scorer.test.ts +0 -325
  113. package/src/__tests__/docs-integration.test.ts +0 -178
  114. package/src/__tests__/docs-tools.test.ts +0 -199
  115. package/src/__tests__/domains.test.ts +0 -236
  116. package/src/__tests__/hooks.test.ts +0 -221
  117. package/src/__tests__/import-resolver.test.ts +0 -95
  118. package/src/__tests__/integration/path-traversal.test.ts +0 -134
  119. package/src/__tests__/integration/pricing-consistency.test.ts +0 -88
  120. package/src/__tests__/integration/tool-registration.test.ts +0 -146
  121. package/src/__tests__/memory-db.test.ts +0 -404
  122. package/src/__tests__/memory-enhancements.test.ts +0 -316
  123. package/src/__tests__/memory-tools.test.ts +0 -199
  124. package/src/__tests__/middleware-tree.test.ts +0 -177
  125. package/src/__tests__/observability-tools.test.ts +0 -595
  126. package/src/__tests__/observability.test.ts +0 -437
  127. package/src/__tests__/observation-extractor.test.ts +0 -167
  128. package/src/__tests__/page-deps.test.ts +0 -60
  129. package/src/__tests__/prompt-analyzer.test.ts +0 -298
  130. package/src/__tests__/regression-detector.test.ts +0 -295
  131. package/src/__tests__/rules.test.ts +0 -87
  132. package/src/__tests__/schema-mapper.test.ts +0 -29
  133. package/src/__tests__/security-scorer.test.ts +0 -238
  134. package/src/__tests__/security-utils.test.ts +0 -175
  135. package/src/__tests__/sentinel-db.test.ts +0 -491
  136. package/src/__tests__/sentinel-scanner.test.ts +0 -750
  137. package/src/__tests__/sentinel-tools.test.ts +0 -324
  138. package/src/__tests__/sentinel-types.test.ts +0 -750
  139. package/src/__tests__/server.test.ts +0 -452
  140. package/src/__tests__/session-archiver.test.ts +0 -524
  141. package/src/__tests__/session-state-generator.test.ts +0 -900
  142. package/src/__tests__/team-knowledge.test.ts +0 -327
  143. package/src/__tests__/tools.test.ts +0 -340
  144. package/src/__tests__/transcript-parser.test.ts +0 -195
  145. package/src/__tests__/trpc-index.test.ts +0 -25
  146. package/src/__tests__/validate-features-runner.test.ts +0 -517
  147. package/src/__tests__/validation-engine.test.ts +0 -300
  148. package/src/core-tools.ts +0 -685
  149. package/src/memory-queries.ts +0 -804
  150. package/src/memory-schema.ts +0 -546
  151. package/src/tool-helpers.ts +0 -41
@@ -3,12 +3,13 @@ import{createRequire as __cr}from"module";const require=__cr(import.meta.url);
3
3
 
4
4
  // src/memory-db.ts
5
5
  import Database from "better-sqlite3";
6
- import { dirname as dirname2 } from "path";
6
+ import { dirname as dirname2, basename } from "path";
7
7
  import { existsSync as existsSync2, mkdirSync } from "fs";
8
8
 
9
9
  // src/config.ts
10
10
  import { resolve, dirname } from "path";
11
11
  import { existsSync, readFileSync } from "fs";
12
+ import { homedir } from "os";
12
13
  import { parse as parseYaml } from "yaml";
13
14
  import { z } from "zod";
14
15
  var DomainConfigSchema = z.object({
@@ -140,6 +141,49 @@ var CloudConfigSchema = z.object({
140
141
  audit: z.boolean().default(true)
141
142
  }).default({ memory: true, analytics: true, audit: true })
142
143
  }).optional();
144
+ var ConventionsConfigSchema = z.object({
145
+ claudeDirName: z.string().default(".claude").refine(
146
+ (s) => !s.includes("..") && !s.startsWith("/"),
147
+ { message: 'claudeDirName must not contain ".." or start with "/"' }
148
+ ),
149
+ sessionStatePath: z.string().default(".claude/session-state/CURRENT.md").refine(
150
+ (s) => !s.includes("..") && !s.startsWith("/"),
151
+ { message: 'sessionStatePath must not contain ".." or start with "/"' }
152
+ ),
153
+ sessionArchivePath: z.string().default(".claude/session-state/archive").refine(
154
+ (s) => !s.includes("..") && !s.startsWith("/"),
155
+ { message: 'sessionArchivePath must not contain ".." or start with "/"' }
156
+ ),
157
+ knowledgeCategories: z.array(z.string()).default([
158
+ "patterns",
159
+ "commands",
160
+ "incidents",
161
+ "reference",
162
+ "protocols",
163
+ "checklists",
164
+ "playbooks",
165
+ "critical",
166
+ "scripts",
167
+ "status",
168
+ "templates",
169
+ "loop-state",
170
+ "session-state",
171
+ "agents"
172
+ ]),
173
+ knowledgeSourceFiles: z.array(z.string()).default(["CLAUDE.md", "MEMORY.md", "corrections.md"]),
174
+ excludePatterns: z.array(z.string()).default(["/ARCHIVE/", "/SESSION-HISTORY/"])
175
+ }).optional();
176
+ var PythonDomainConfigSchema = z.object({
177
+ name: z.string(),
178
+ packages: z.array(z.string()),
179
+ allowed_imports_from: z.array(z.string()).default([])
180
+ });
181
+ var PythonConfigSchema = z.object({
182
+ root: z.string(),
183
+ alembic_dir: z.string().optional(),
184
+ domains: z.array(PythonDomainConfigSchema).default([]),
185
+ exclude_dirs: z.array(z.string()).default(["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"])
186
+ }).optional();
143
187
  var PathsConfigSchema = z.object({
144
188
  source: z.string().default("src"),
145
189
  aliases: z.record(z.string(), z.string()).default({ "@": "src" }),
@@ -174,7 +218,9 @@ var RawConfigSchema = z.object({
174
218
  security: SecurityConfigSchema,
175
219
  team: TeamConfigSchema,
176
220
  regression: RegressionConfigSchema,
177
- cloud: CloudConfigSchema
221
+ cloud: CloudConfigSchema,
222
+ conventions: ConventionsConfigSchema,
223
+ python: PythonConfigSchema
178
224
  }).passthrough();
179
225
  var _config = null;
180
226
  var _projectRoot = null;
@@ -238,13 +284,24 @@ function getConfig() {
238
284
  security: parsed.security,
239
285
  team: parsed.team,
240
286
  regression: parsed.regression,
241
- cloud: parsed.cloud
287
+ cloud: parsed.cloud,
288
+ conventions: parsed.conventions,
289
+ python: parsed.python
242
290
  };
291
+ if (!_config.cloud?.apiKey && process.env.MASSU_API_KEY) {
292
+ _config.cloud = {
293
+ enabled: true,
294
+ sync: { memory: true, analytics: true, audit: true },
295
+ ..._config.cloud,
296
+ apiKey: process.env.MASSU_API_KEY
297
+ };
298
+ }
243
299
  return _config;
244
300
  }
245
301
  function getResolvedPaths() {
246
302
  const config = getConfig();
247
303
  const root = getProjectRoot();
304
+ const claudeDirName = config.conventions?.claudeDirName ?? ".claude";
248
305
  return {
249
306
  codegraphDbPath: resolve(root, ".codegraph/codegraph.db"),
250
307
  dataDbPath: resolve(root, ".massu/data.db"),
@@ -260,22 +317,43 @@ function getResolvedPaths() {
260
317
  ),
261
318
  extensions: [".ts", ".tsx", ".js", ".jsx"],
262
319
  indexFiles: ["index.ts", "index.tsx", "index.js", "index.jsx"],
263
- patternsDir: resolve(root, ".claude/patterns"),
264
- claudeMdPath: resolve(root, ".claude/CLAUDE.md"),
320
+ patternsDir: resolve(root, claudeDirName, "patterns"),
321
+ claudeMdPath: resolve(root, claudeDirName, "CLAUDE.md"),
265
322
  docsMapPath: resolve(root, ".massu/docs-map.json"),
266
323
  helpSitePath: resolve(root, "../" + config.project.name + "-help"),
267
- memoryDbPath: resolve(root, ".massu/memory.db")
324
+ memoryDbPath: resolve(root, ".massu/memory.db"),
325
+ knowledgeDbPath: resolve(root, ".massu/knowledge.db"),
326
+ plansDir: resolve(root, "docs/plans"),
327
+ docsDir: resolve(root, "docs"),
328
+ claudeDir: resolve(root, claudeDirName),
329
+ memoryDir: resolve(homedir(), claudeDirName, "projects", root.replace(/\//g, "-"), "memory"),
330
+ sessionStatePath: resolve(root, config.conventions?.sessionStatePath ?? `${claudeDirName}/session-state/CURRENT.md`),
331
+ sessionArchivePath: resolve(root, config.conventions?.sessionArchivePath ?? `${claudeDirName}/session-state/archive`),
332
+ mcpJsonPath: resolve(root, ".mcp.json"),
333
+ settingsLocalPath: resolve(root, claudeDirName, "settings.local.json")
268
334
  };
269
335
  }
270
336
 
271
- // src/memory-schema.ts
337
+ // src/memory-db.ts
338
+ function getMemoryDb() {
339
+ const dbPath = getResolvedPaths().memoryDbPath;
340
+ const dir = dirname2(dbPath);
341
+ if (!existsSync2(dir)) {
342
+ mkdirSync(dir, { recursive: true });
343
+ }
344
+ const db = new Database(dbPath);
345
+ db.pragma("journal_mode = WAL");
346
+ db.pragma("foreign_keys = ON");
347
+ initMemorySchema(db);
348
+ return db;
349
+ }
272
350
  function initMemorySchema(db) {
273
351
  db.exec(`
274
352
  -- Sessions table (linked to Claude Code session IDs)
275
353
  CREATE TABLE IF NOT EXISTS sessions (
276
354
  id INTEGER PRIMARY KEY AUTOINCREMENT,
277
355
  session_id TEXT UNIQUE NOT NULL,
278
- project TEXT NOT NULL DEFAULT 'unknown',
356
+ project TEXT NOT NULL DEFAULT 'my-project',
279
357
  git_branch TEXT,
280
358
  started_at TEXT NOT NULL,
281
359
  started_at_epoch INTEGER NOT NULL,
@@ -330,9 +408,7 @@ function initMemorySchema(db) {
330
408
  content_rowid='id'
331
409
  );
332
410
  `);
333
- } catch (e) {
334
- process.stderr.write(`FTS5 setup warning: ${e instanceof Error ? e.message : String(e)}
335
- `);
411
+ } catch (_e) {
336
412
  }
337
413
  db.exec(`
338
414
  CREATE TRIGGER IF NOT EXISTS observations_ai AFTER INSERT ON observations BEGIN
@@ -392,9 +468,7 @@ function initMemorySchema(db) {
392
468
  content_rowid='id'
393
469
  );
394
470
  `);
395
- } catch (e) {
396
- process.stderr.write(`FTS5 setup warning: ${e instanceof Error ? e.message : String(e)}
397
- `);
471
+ } catch (_e) {
398
472
  }
399
473
  db.exec(`
400
474
  CREATE TRIGGER IF NOT EXISTS prompts_ai AFTER INSERT ON user_prompts BEGIN
@@ -464,9 +538,7 @@ function initMemorySchema(db) {
464
538
  content_rowid=id
465
539
  );
466
540
  `);
467
- } catch (e) {
468
- process.stderr.write(`FTS5 setup warning: ${e instanceof Error ? e.message : String(e)}
469
- `);
541
+ } catch (_e) {
470
542
  }
471
543
  db.exec(`
472
544
  CREATE TRIGGER IF NOT EXISTS ct_fts_insert AFTER INSERT ON conversation_turns BEGIN
@@ -490,7 +562,7 @@ function initMemorySchema(db) {
490
562
  CREATE TABLE IF NOT EXISTS session_quality_scores (
491
563
  id INTEGER PRIMARY KEY AUTOINCREMENT,
492
564
  session_id TEXT NOT NULL UNIQUE,
493
- project TEXT NOT NULL DEFAULT 'unknown',
565
+ project TEXT NOT NULL DEFAULT 'my-project',
494
566
  score INTEGER NOT NULL DEFAULT 100,
495
567
  security_score INTEGER NOT NULL DEFAULT 100,
496
568
  architecture_score INTEGER NOT NULL DEFAULT 100,
@@ -513,7 +585,7 @@ function initMemorySchema(db) {
513
585
  CREATE TABLE IF NOT EXISTS session_costs (
514
586
  id INTEGER PRIMARY KEY AUTOINCREMENT,
515
587
  session_id TEXT NOT NULL UNIQUE,
516
- project TEXT NOT NULL DEFAULT 'unknown',
588
+ project TEXT NOT NULL DEFAULT 'my-project',
517
589
  input_tokens INTEGER NOT NULL DEFAULT 0,
518
590
  output_tokens INTEGER NOT NULL DEFAULT 0,
519
591
  cache_read_tokens INTEGER NOT NULL DEFAULT 0,
@@ -740,10 +812,16 @@ function initMemorySchema(db) {
740
812
  );
741
813
  CREATE INDEX IF NOT EXISTS idx_pending_sync_created ON pending_sync(created_at ASC);
742
814
  `);
815
+ db.exec(`
816
+ CREATE TABLE IF NOT EXISTS license_cache (
817
+ api_key_hash TEXT PRIMARY KEY,
818
+ tier TEXT NOT NULL,
819
+ valid_until TEXT NOT NULL,
820
+ last_validated TEXT NOT NULL,
821
+ features TEXT DEFAULT '[]'
822
+ );
823
+ `);
743
824
  }
744
-
745
- // src/memory-queries.ts
746
- import { basename } from "path";
747
825
  function assignImportance(type, vrResult) {
748
826
  switch (type) {
749
827
  case "decision":
@@ -841,27 +919,9 @@ function deduplicateFailedAttempt(db, sessionId, title, detail, opts) {
841
919
  });
842
920
  }
843
921
 
844
- // src/memory-db.ts
845
- var initializedPaths = /* @__PURE__ */ new Set();
846
- function getMemoryDb() {
847
- const dbPath = getResolvedPaths().memoryDbPath;
848
- const dir = dirname2(dbPath);
849
- if (!existsSync2(dir)) {
850
- mkdirSync(dir, { recursive: true });
851
- }
852
- const db = new Database(dbPath);
853
- db.pragma("journal_mode = WAL");
854
- db.pragma("foreign_keys = ON");
855
- if (!initializedPaths.has(dbPath)) {
856
- initMemorySchema(db);
857
- initializedPaths.add(dbPath);
858
- }
859
- return db;
860
- }
861
-
862
922
  // src/transcript-parser.ts
863
- function estimateTokens(text2) {
864
- return Math.ceil(text2.length / 4);
923
+ function estimateTokens(text) {
924
+ return Math.ceil(text.length / 4);
865
925
  }
866
926
 
867
927
  // src/adr-generator.ts
@@ -869,13 +929,14 @@ var DEFAULT_DETECTION_PHRASES = ["chose", "decided", "switching to", "moving fro
869
929
  function getDetectionPhrases() {
870
930
  return getConfig().governance?.adr?.detection_phrases ?? DEFAULT_DETECTION_PHRASES;
871
931
  }
872
- function detectDecisionPatterns(text2) {
932
+ function detectDecisionPatterns(text) {
873
933
  const phrases = getDetectionPhrases();
874
- const lower = text2.toLowerCase();
934
+ const lower = text.toLowerCase();
875
935
  return phrases.some((phrase) => lower.includes(phrase));
876
936
  }
877
937
 
878
938
  // src/observation-extractor.ts
939
+ import { homedir as homedir2 } from "os";
879
940
  var PRIVATE_PATTERNS = [
880
941
  /\/Users\/\w+/,
881
942
  // Absolute macOS paths
@@ -895,9 +956,9 @@ var PRIVATE_PATTERNS = [
895
956
  // Stripe keys
896
957
  ];
897
958
  function classifyVisibility(title, detail) {
898
- const text2 = `${title} ${detail ?? ""}`;
959
+ const text = `${title} ${detail ?? ""}`;
899
960
  for (const pattern of PRIVATE_PATTERNS) {
900
- if (pattern.test(text2)) return "private";
961
+ if (pattern.test(text)) return "private";
901
962
  }
902
963
  return "public";
903
964
  }
@@ -954,7 +1015,9 @@ function classifyToolCall(tc) {
954
1015
  }
955
1016
  case "Read": {
956
1017
  const filePath = tc.input.file_path ?? "unknown";
957
- if (filePath.includes("/plans/") || filePath.includes("CLAUDE.md") || filePath.includes("CURRENT.md")) {
1018
+ const knowledgeSourceFiles = getConfig().conventions?.knowledgeSourceFiles ?? ["CLAUDE.md", "MEMORY.md", "corrections.md"];
1019
+ const plansDir = getResolvedPaths().plansDir;
1020
+ if (filePath.includes(plansDir) || knowledgeSourceFiles.some((f) => filePath.includes(f))) {
958
1021
  const title = `Read: ${shortenPath(filePath)}`;
959
1022
  return {
960
1023
  type: "discovery",
@@ -1042,13 +1105,13 @@ function classifyToolCall(tc) {
1042
1105
  return null;
1043
1106
  }
1044
1107
  }
1045
- function extractLinkedReferences(text2) {
1108
+ function extractLinkedReferences(text) {
1046
1109
  const result = {};
1047
- const crMatch = text2.match(/CR-(\d+)/);
1110
+ const crMatch = text.match(/CR-(\d+)/);
1048
1111
  if (crMatch) result.crRule = `CR-${crMatch[1]}`;
1049
- const vrMatch = text2.match(/VR-([A-Z_]+)/);
1112
+ const vrMatch = text.match(/VR-([A-Z_]+)/);
1050
1113
  if (vrMatch) result.vrType = `VR-${vrMatch[1]}`;
1051
- const planMatch = text2.match(/P(\d+)-(\d+)/);
1114
+ const planMatch = text.match(/P(\d+)-(\d+)/);
1052
1115
  if (planMatch) result.planItem = `P${planMatch[1]}-${planMatch[2]}`;
1053
1116
  return result;
1054
1117
  }
@@ -1064,7 +1127,11 @@ function shortenPath(filePath) {
1064
1127
  if (filePath.startsWith(root + "/")) {
1065
1128
  return filePath.slice(root.length + 1);
1066
1129
  }
1067
- return filePath.replace(/^\/Users\/\w+\//, "~/");
1130
+ const home = homedir2();
1131
+ if (filePath.startsWith(home + "/")) {
1132
+ return "~/" + filePath.slice(home.length + 1);
1133
+ }
1134
+ return filePath;
1068
1135
  }
1069
1136
  function classifyRealTimeToolCall(toolName, toolInput, toolResponse, seenReads2) {
1070
1137
  const tc = {
@@ -1175,53 +1242,7 @@ function trackModification(db, featureKey) {
1175
1242
 
1176
1243
  // src/import-resolver.ts
1177
1244
  import { readFileSync as readFileSync2, existsSync as existsSync3, statSync } from "fs";
1178
- import { resolve as resolve2, dirname as dirname3, join } from "path";
1179
- function resolveImportPath(specifier, fromFile) {
1180
- if (!specifier.startsWith(".") && !specifier.startsWith("@/")) {
1181
- return null;
1182
- }
1183
- let basePath;
1184
- if (specifier.startsWith("@/")) {
1185
- const paths = getResolvedPaths();
1186
- basePath = resolve2(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
1187
- } else {
1188
- basePath = resolve2(dirname3(fromFile), specifier);
1189
- }
1190
- if (existsSync3(basePath) && !isDirectory(basePath)) {
1191
- return toRelative(basePath);
1192
- }
1193
- const resolvedPaths = getResolvedPaths();
1194
- for (const ext of resolvedPaths.extensions) {
1195
- const withExt = basePath + ext;
1196
- if (existsSync3(withExt)) {
1197
- return toRelative(withExt);
1198
- }
1199
- }
1200
- for (const indexFile of resolvedPaths.indexFiles) {
1201
- const indexPath = join(basePath, indexFile);
1202
- if (existsSync3(indexPath)) {
1203
- return toRelative(indexPath);
1204
- }
1205
- }
1206
- return null;
1207
- }
1208
- function isDirectory(path) {
1209
- try {
1210
- return statSync(path).isDirectory();
1211
- } catch {
1212
- return false;
1213
- }
1214
- }
1215
- function toRelative(absPath) {
1216
- const root = getProjectRoot();
1217
- if (absPath.startsWith(root)) {
1218
- return absPath.slice(root.length + 1);
1219
- }
1220
- return absPath;
1221
- }
1222
-
1223
- // src/validation-engine.ts
1224
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
1245
+ import { resolve as resolve4, dirname as dirname3, join } from "path";
1225
1246
 
1226
1247
  // src/security-utils.ts
1227
1248
  import { resolve as resolve3, normalize } from "path";
@@ -1269,7 +1290,53 @@ function enforceSeverityFloors(configWeights, defaults) {
1269
1290
  return result;
1270
1291
  }
1271
1292
 
1293
+ // src/import-resolver.ts
1294
+ function resolveImportPath(specifier, fromFile) {
1295
+ if (!specifier.startsWith(".") && !specifier.startsWith("@/")) {
1296
+ return null;
1297
+ }
1298
+ let basePath;
1299
+ if (specifier.startsWith("@/")) {
1300
+ const paths = getResolvedPaths();
1301
+ basePath = resolve4(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
1302
+ } else {
1303
+ basePath = resolve4(dirname3(fromFile), specifier);
1304
+ }
1305
+ if (existsSync3(basePath) && !isDirectory(basePath)) {
1306
+ return toRelative(basePath);
1307
+ }
1308
+ const resolvedPaths = getResolvedPaths();
1309
+ for (const ext of resolvedPaths.extensions) {
1310
+ const withExt = basePath + ext;
1311
+ if (existsSync3(withExt)) {
1312
+ return toRelative(withExt);
1313
+ }
1314
+ }
1315
+ for (const indexFile of resolvedPaths.indexFiles) {
1316
+ const indexPath = join(basePath, indexFile);
1317
+ if (existsSync3(indexPath)) {
1318
+ return toRelative(indexPath);
1319
+ }
1320
+ }
1321
+ return null;
1322
+ }
1323
+ function isDirectory(path) {
1324
+ try {
1325
+ return statSync(path).isDirectory();
1326
+ } catch {
1327
+ return false;
1328
+ }
1329
+ }
1330
+ function toRelative(absPath) {
1331
+ const root = getProjectRoot();
1332
+ if (absPath.startsWith(root)) {
1333
+ return absPath.slice(root.length + 1);
1334
+ }
1335
+ return absPath;
1336
+ }
1337
+
1272
1338
  // src/validation-engine.ts
1339
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
1273
1340
  function getValidationChecks() {
1274
1341
  return getConfig().governance?.validation?.checks ?? {
1275
1342
  rule_compliance: true,
@@ -1539,6 +1606,9 @@ function storeSecurityScore(db, sessionId, filePath, riskScore, findings) {
1539
1606
  }
1540
1607
 
1541
1608
  // src/hooks/post-tool-use.ts
1609
+ import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
1610
+ import { join as join2 } from "path";
1611
+ import { parse as parseYaml2 } from "yaml";
1542
1612
  var seenReads = /* @__PURE__ */ new Set();
1543
1613
  var currentSessionId = null;
1544
1614
  async function main() {
@@ -1616,6 +1686,41 @@ async function main() {
1616
1686
  }
1617
1687
  } catch (_securityErr) {
1618
1688
  }
1689
+ try {
1690
+ if (tool_name === "Edit" || tool_name === "Write") {
1691
+ const filePath = tool_input.file_path ?? "";
1692
+ if (filePath && filePath.endsWith("MEMORY.md") && filePath.includes("/memory/")) {
1693
+ const issues = checkMemoryFileIntegrity(filePath);
1694
+ if (issues.length > 0) {
1695
+ addObservation(
1696
+ db,
1697
+ session_id,
1698
+ "incident_near_miss",
1699
+ "MEMORY.md integrity issue detected",
1700
+ issues.join("; "),
1701
+ { importance: 4 }
1702
+ );
1703
+ }
1704
+ }
1705
+ }
1706
+ } catch (_memoryErr) {
1707
+ }
1708
+ try {
1709
+ if (tool_name === "Edit" || tool_name === "Write") {
1710
+ const filePath = tool_input.file_path ?? "";
1711
+ if (filePath && isKnowledgeSourceFile(filePath)) {
1712
+ addObservation(
1713
+ db,
1714
+ session_id,
1715
+ "discovery",
1716
+ "Knowledge source file modified - index may be stale",
1717
+ `Edited ${filePath.split("/").pop() ?? filePath}. Run knowledge re-index to update.`,
1718
+ { importance: 3 }
1719
+ );
1720
+ }
1721
+ }
1722
+ } catch (_knowledgeErr) {
1723
+ }
1619
1724
  } finally {
1620
1725
  db.close();
1621
1726
  }
@@ -1630,29 +1735,86 @@ function updatePlanProgress(db, sessionId, progress) {
1630
1735
  if (existing) {
1631
1736
  try {
1632
1737
  const currentProgress = JSON.parse(existing.plan_progress);
1633
- for (const p2 of progress) {
1634
- currentProgress[p2.planItem] = p2.status;
1738
+ for (const p of progress) {
1739
+ currentProgress[p.planItem] = p.status;
1635
1740
  }
1636
1741
  db.prepare("UPDATE session_summaries SET plan_progress = ? WHERE id = ?").run(JSON.stringify(currentProgress), existing.id);
1637
1742
  } catch (_e) {
1638
1743
  }
1639
1744
  } else {
1640
1745
  const progressMap = {};
1641
- for (const p2 of progress) {
1642
- progressMap[p2.planItem] = p2.status;
1746
+ for (const p of progress) {
1747
+ progressMap[p.planItem] = p.status;
1643
1748
  }
1644
1749
  addSummary(db, sessionId, { planProgress: progressMap });
1645
1750
  }
1646
1751
  }
1647
1752
  function readStdin() {
1648
- return new Promise((resolve4) => {
1753
+ return new Promise((resolve5) => {
1649
1754
  let data = "";
1650
1755
  process.stdin.setEncoding("utf-8");
1651
1756
  process.stdin.on("data", (chunk) => {
1652
1757
  data += chunk;
1653
1758
  });
1654
- process.stdin.on("end", () => resolve4(data));
1655
- setTimeout(() => resolve4(data), 3e3);
1759
+ process.stdin.on("end", () => resolve5(data));
1760
+ setTimeout(() => resolve5(data), 3e3);
1656
1761
  });
1657
1762
  }
1763
+ function readConventions(cwd) {
1764
+ const defaults = {
1765
+ knowledgeSourceFiles: ["CLAUDE.md", "MEMORY.md", "corrections.md"],
1766
+ claudeDirName: ".claude"
1767
+ };
1768
+ try {
1769
+ const projectRoot = cwd ?? process.cwd();
1770
+ const configPath = join2(projectRoot, "massu.config.yaml");
1771
+ if (!existsSync6(configPath)) return defaults;
1772
+ const content = readFileSync5(configPath, "utf-8");
1773
+ const parsed = parseYaml2(content);
1774
+ if (!parsed || typeof parsed !== "object") return defaults;
1775
+ const conventions = parsed.conventions;
1776
+ if (!conventions || typeof conventions !== "object") return defaults;
1777
+ return {
1778
+ knowledgeSourceFiles: Array.isArray(conventions.knowledgeSourceFiles) ? conventions.knowledgeSourceFiles : defaults.knowledgeSourceFiles,
1779
+ claudeDirName: typeof conventions.claudeDirName === "string" ? conventions.claudeDirName : defaults.claudeDirName
1780
+ };
1781
+ } catch {
1782
+ return defaults;
1783
+ }
1784
+ }
1785
+ function isKnowledgeSourceFile(filePath) {
1786
+ const basename2 = filePath.split("/").pop() ?? "";
1787
+ const conventions = readConventions();
1788
+ const knowledgeSourcePatterns = [
1789
+ ...conventions.knowledgeSourceFiles,
1790
+ "file-index.md",
1791
+ "knowledge-db.ts",
1792
+ "knowledge-indexer.ts",
1793
+ "knowledge-tools.ts"
1794
+ ];
1795
+ return knowledgeSourcePatterns.some((p) => basename2 === p) || filePath.includes("/memory/") || filePath.includes(conventions.claudeDirName + "/");
1796
+ }
1797
+ function checkMemoryFileIntegrity(filePath) {
1798
+ const issues = [];
1799
+ try {
1800
+ if (!existsSync6(filePath)) {
1801
+ issues.push("MEMORY.md file does not exist after write");
1802
+ return issues;
1803
+ }
1804
+ const content = readFileSync5(filePath, "utf-8");
1805
+ const lines = content.split("\n");
1806
+ const MAX_LINES = 200;
1807
+ if (lines.length > MAX_LINES) {
1808
+ issues.push(`MEMORY.md exceeds ${MAX_LINES} lines (currently ${lines.length}). Consider archiving old entries.`);
1809
+ }
1810
+ const requiredSections = ["# Massu Memory", "## Key Learnings", "## Common Gotchas"];
1811
+ for (const section of requiredSections) {
1812
+ if (!content.includes(section)) {
1813
+ issues.push(`Missing required section: "${section}"`);
1814
+ }
1815
+ }
1816
+ } catch (_e) {
1817
+ }
1818
+ return issues;
1819
+ }
1658
1820
  main();