@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,40 @@ 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
+ `);
824
+ }
825
+ function assignImportance(type, vrResult) {
826
+ switch (type) {
827
+ case "decision":
828
+ case "failed_attempt":
829
+ return 5;
830
+ case "cr_violation":
831
+ case "incident_near_miss":
832
+ return 4;
833
+ case "vr_check":
834
+ return vrResult === "PASS" ? 2 : 4;
835
+ case "pattern_compliance":
836
+ return vrResult === "PASS" ? 2 : 4;
837
+ case "feature":
838
+ case "bugfix":
839
+ return 3;
840
+ case "refactor":
841
+ return 2;
842
+ case "file_change":
843
+ case "discovery":
844
+ return 1;
845
+ default:
846
+ return 3;
847
+ }
743
848
  }
744
-
745
- // src/memory-queries.ts
746
- import { basename } from "path";
747
849
  function autoDetectTaskId(planFile) {
748
850
  if (!planFile) return null;
749
851
  const base = basename(planFile);
@@ -757,6 +859,29 @@ function createSession(db, sessionId, opts) {
757
859
  VALUES (?, ?, ?, ?, ?, ?)
758
860
  `).run(sessionId, opts?.branch ?? null, opts?.planFile ?? null, taskId, now.toISOString(), Math.floor(now.getTime() / 1e3));
759
861
  }
862
+ function addObservation(db, sessionId, type, title, detail, opts) {
863
+ const now = /* @__PURE__ */ new Date();
864
+ const importance = opts?.importance ?? assignImportance(type, opts?.evidence?.includes("PASS") ? "PASS" : void 0);
865
+ const result = db.prepare(`
866
+ INSERT INTO observations (session_id, type, title, detail, files_involved, plan_item, cr_rule, vr_type, evidence, importance, original_tokens, created_at, created_at_epoch)
867
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
868
+ `).run(
869
+ sessionId,
870
+ type,
871
+ title,
872
+ detail,
873
+ JSON.stringify(opts?.filesInvolved ?? []),
874
+ opts?.planItem ?? null,
875
+ opts?.crRule ?? null,
876
+ opts?.vrType ?? null,
877
+ opts?.evidence ?? null,
878
+ importance,
879
+ opts?.originalTokens ?? 0,
880
+ now.toISOString(),
881
+ Math.floor(now.getTime() / 1e3)
882
+ );
883
+ return Number(result.lastInsertRowid);
884
+ }
760
885
  function addUserPrompt(db, sessionId, text, promptNumber) {
761
886
  const now = /* @__PURE__ */ new Date();
762
887
  db.prepare(`
@@ -768,25 +893,8 @@ function linkSessionToTask(db, sessionId, taskId) {
768
893
  db.prepare("UPDATE sessions SET task_id = ? WHERE session_id = ?").run(taskId, sessionId);
769
894
  }
770
895
 
771
- // src/memory-db.ts
772
- var initializedPaths = /* @__PURE__ */ new Set();
773
- function getMemoryDb() {
774
- const dbPath = getResolvedPaths().memoryDbPath;
775
- const dir = dirname2(dbPath);
776
- if (!existsSync2(dir)) {
777
- mkdirSync(dir, { recursive: true });
778
- }
779
- const db = new Database(dbPath);
780
- db.pragma("journal_mode = WAL");
781
- db.pragma("foreign_keys = ON");
782
- if (!initializedPaths.has(dbPath)) {
783
- initMemorySchema(db);
784
- initializedPaths.add(dbPath);
785
- }
786
- return db;
787
- }
788
-
789
896
  // src/hooks/user-prompt.ts
897
+ import { existsSync as existsSync3 } from "fs";
790
898
  async function main() {
791
899
  try {
792
900
  const input = await readStdin();
@@ -814,6 +922,35 @@ async function main() {
814
922
  ).get(session_id);
815
923
  const promptNumber = countResult.count + 1;
816
924
  addUserPrompt(db, session_id, prompt.trim(), promptNumber);
925
+ try {
926
+ const fileRefs = extractFileReferences(prompt);
927
+ if (fileRefs.length > 0) {
928
+ const knowledgeDbPath = getResolvedPaths().knowledgeDbPath;
929
+ if (knowledgeDbPath && existsSync3(knowledgeDbPath)) {
930
+ const Database2 = (await import("better-sqlite3")).default;
931
+ const kdb = new Database2(knowledgeDbPath, { readonly: true });
932
+ try {
933
+ const placeholders = fileRefs.map(() => "?").join(",");
934
+ const matches = kdb.prepare(
935
+ `SELECT DISTINCT file_path FROM knowledge_documents WHERE file_path IN (${placeholders})`
936
+ ).all(...fileRefs);
937
+ if (matches.length > 0) {
938
+ addObservation(
939
+ db,
940
+ session_id,
941
+ "discovery",
942
+ `Knowledge entries exist for referenced files`,
943
+ `Files with knowledge context: ${matches.map((m) => m.file_path).join(", ")}`,
944
+ { importance: 2 }
945
+ );
946
+ }
947
+ } finally {
948
+ kdb.close();
949
+ }
950
+ }
951
+ }
952
+ } catch (_knowledgeErr) {
953
+ }
817
954
  } finally {
818
955
  db.close();
819
956
  }
@@ -821,6 +958,15 @@ async function main() {
821
958
  }
822
959
  process.exit(0);
823
960
  }
961
+ function extractFileReferences(prompt) {
962
+ const filePattern = /(?:^|\s)((?:src|packages|lib)\/[\w./-]+\.(?:ts|tsx|js|jsx|md))/g;
963
+ const matches = [];
964
+ let match;
965
+ while ((match = filePattern.exec(prompt)) !== null) {
966
+ matches.push(match[1]);
967
+ }
968
+ return [...new Set(matches)];
969
+ }
824
970
  async function getGitBranch() {
825
971
  try {
826
972
  const { spawnSync } = await import("child_process");
@@ -835,14 +981,14 @@ async function getGitBranch() {
835
981
  }
836
982
  }
837
983
  function readStdin() {
838
- return new Promise((resolve2) => {
984
+ return new Promise((resolve3) => {
839
985
  let data = "";
840
986
  process.stdin.setEncoding("utf-8");
841
987
  process.stdin.on("data", (chunk) => {
842
988
  data += chunk;
843
989
  });
844
- process.stdin.on("end", () => resolve2(data));
845
- setTimeout(() => resolve2(data), 3e3);
990
+ process.stdin.on("end", () => resolve3(data));
991
+ setTimeout(() => resolve3(data), 3e3);
846
992
  });
847
993
  }
848
994
  main();
package/package.json CHANGED
@@ -1,25 +1,18 @@
1
1
  {
2
2
  "name": "@massu/core",
3
- "version": "0.1.1",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
- "description": "AI Engineering Governance MCP Server - Session memory, feature registry, code intelligence, and rule enforcement",
6
- "license": "BSL-1.1",
7
- "author": "Massu AI <hello@massu.ai>",
8
- "main": "dist/server.js",
9
- "exports": {
10
- ".": "./dist/server.js",
11
- "./cli": "./dist/cli.js"
12
- },
5
+ "description": "AI Engineering Governance MCP Server - Session memory, knowledge system, feature registry, code intelligence, rule enforcement, tiered tooling (12 free / 72 total), 31 workflow commands",
6
+ "main": "src/server.ts",
13
7
  "bin": {
14
- "massu": "dist/cli.js"
8
+ "massu": "./dist/cli.js"
15
9
  },
16
10
  "scripts": {
17
11
  "start": "npx tsx src/server.ts",
18
12
  "test": "vitest run",
19
- "build": "tsc --noEmit && npm run build:server && npm run build:hooks",
20
- "build:server": "esbuild --bundle --platform=node --format=esm --outdir=dist --entry-names=[name] src/server.ts src/cli.ts --external:better-sqlite3 --external:yaml --external:zod --banner:js='import{createRequire as __cr}from\"module\";const require=__cr(import.meta.url);'",
13
+ "build": "tsc --noEmit && npm run build:cli && npm run build:hooks",
14
+ "build:cli": "esbuild --bundle --platform=node --format=esm --outfile=dist/cli.js src/cli.ts --external:better-sqlite3 --external:yaml --external:zod --banner:js='#!/usr/bin/env node\nimport{createRequire as __cr}from\"module\";const require=__cr(import.meta.url);'",
21
15
  "build:hooks": "esbuild --bundle --platform=node --format=esm --outdir=dist/hooks src/hooks/*.ts --external:better-sqlite3 --external:yaml --external:zod --banner:js='import{createRequire as __cr}from\"module\";const require=__cr(import.meta.url);'",
22
- "test:integration": "vitest run src/__tests__/integration/",
23
16
  "prepublishOnly": "bash ../../scripts/prepublish-check.sh && npm run build"
24
17
  },
25
18
  "dependencies": {
@@ -36,7 +29,9 @@
36
29
  },
37
30
  "files": [
38
31
  "src/**/*",
32
+ "!src/__tests__/**",
39
33
  "dist/**/*",
34
+ "commands/**/*",
40
35
  "LICENSE"
41
36
  ],
42
37
  "keywords": [
@@ -52,11 +47,11 @@
52
47
  },
53
48
  "repository": {
54
49
  "type": "git",
55
- "url": "git+https://github.com/massu-ai/massu.git",
50
+ "url": "https://github.com/massu-ai/massu.git",
56
51
  "directory": "packages/core"
57
52
  },
58
53
  "bugs": {
59
54
  "url": "https://github.com/massu-ai/massu/issues"
60
55
  },
61
- "homepage": "https://massu.ai"
56
+ "homepage": "https://github.com/massu-ai/massu#readme"
62
57
  }
@@ -2,14 +2,18 @@
2
2
  // Licensed under BSL 1.1 - see LICENSE file for details.
3
3
 
4
4
  import type Database from 'better-sqlite3';
5
- import type { ToolDefinition, ToolResult } from './tool-helpers.ts';
6
- import { p, text } from './tool-helpers.ts';
5
+ import type { ToolDefinition, ToolResult } from './tools.ts';
7
6
  import { getConfig } from './config.ts';
8
7
 
9
8
  // ============================================================
10
9
  // ADR (Architecture Decision Record) Auto-Generation
11
10
  // ============================================================
12
11
 
12
+ /** Prefix a base tool name with the configured tool prefix. */
13
+ function p(baseName: string): string {
14
+ return `${getConfig().toolPrefix}_${baseName}`;
15
+ }
16
+
13
17
  /** Default decision detection phrases. Configurable via governance.adr.detection_phrases */
14
18
  const DEFAULT_DETECTION_PHRASES = ['chose', 'decided', 'switching to', 'moving from', 'going with'];
15
19
 
@@ -283,3 +287,6 @@ function handleAdrGenerate(args: Record<string, unknown>, db: Database.Database)
283
287
  return text(lines.filter(Boolean).join('\n'));
284
288
  }
285
289
 
290
+ function text(content: string): ToolResult {
291
+ return { content: [{ type: 'text', text: content }] };
292
+ }
package/src/analytics.ts CHANGED
@@ -2,14 +2,18 @@
2
2
  // Licensed under BSL 1.1 - see LICENSE file for details.
3
3
 
4
4
  import type Database from 'better-sqlite3';
5
- import type { ToolDefinition, ToolResult } from './tool-helpers.ts';
6
- import { p, text } from './tool-helpers.ts';
5
+ import type { ToolDefinition, ToolResult } from './tools.ts';
7
6
  import { getConfig } from './config.ts';
8
7
 
9
8
  // ============================================================
10
9
  // Quality Trend Analytics
11
10
  // ============================================================
12
11
 
12
+ /** Prefix a base tool name with the configured tool prefix. */
13
+ function p(baseName: string): string {
14
+ return `${getConfig().toolPrefix}_${baseName}`;
15
+ }
16
+
13
17
  export interface QualityBreakdown {
14
18
  security: number;
15
19
  architecture: number;
@@ -118,7 +122,6 @@ export function backfillQualityScores(db: Database.Database): number {
118
122
  FROM sessions s
119
123
  LEFT JOIN session_quality_scores q ON s.session_id = q.session_id
120
124
  WHERE q.session_id IS NULL
121
- LIMIT 1000
122
125
  `).all() as Array<{ session_id: string }>;
123
126
 
124
127
  let backfilled = 0;
@@ -365,3 +368,6 @@ function handleQualityReport(args: Record<string, unknown>, db: Database.Databas
365
368
  return text(lines.join('\n'));
366
369
  }
367
370
 
371
+ function text(content: string): ToolResult {
372
+ return { content: [{ type: 'text', text: content }] };
373
+ }
@@ -2,14 +2,18 @@
2
2
  // Licensed under BSL 1.1 - see LICENSE file for details.
3
3
 
4
4
  import type Database from 'better-sqlite3';
5
- import type { ToolDefinition, ToolResult } from './tool-helpers.ts';
6
- import { p, text } from './tool-helpers.ts';
5
+ import type { ToolDefinition, ToolResult } from './tools.ts';
7
6
  import { getConfig } from './config.ts';
8
7
 
9
8
  // ============================================================
10
9
  // Compliance Audit Trail
11
10
  // ============================================================
12
11
 
12
+ /** Prefix a base tool name with the configured tool prefix. */
13
+ function p(baseName: string): string {
14
+ return `${getConfig().toolPrefix}_${baseName}`;
15
+ }
16
+
13
17
  export interface AuditEntry {
14
18
  eventType: 'code_change' | 'rule_enforced' | 'approval' | 'review' | 'commit' | 'compaction';
15
19
  actor: 'ai' | 'human' | 'hook' | 'agent';
@@ -129,7 +133,7 @@ export function backfillAuditLog(db: Database.Database): number {
129
133
  FROM observations o
130
134
  LEFT JOIN audit_log a ON a.evidence = o.detail AND a.session_id = o.session_id
131
135
  WHERE a.id IS NULL
132
- AND o.type IN ('bugfix', 'cr_violation', 'vr_check', 'incident_near_miss', 'decision')
136
+ AND o.type IN ('bugfix', 'cr_violation', 'vr_check', 'incident', 'decision')
133
137
  ORDER BY o.created_at ASC
134
138
  LIMIT 1000
135
139
  `).all() as Array<Record<string, unknown>>;
@@ -441,3 +445,6 @@ function handleAuditChain(args: Record<string, unknown>, db: Database.Database):
441
445
  return text(lines.join('\n'));
442
446
  }
443
447
 
448
+ function text(content: string): ToolResult {
449
+ return { content: [{ type: 'text', text: content }] };
450
+ }
@@ -13,7 +13,7 @@ import { resolve, basename } from 'path';
13
13
  import { getMemoryDb, createSession, addObservation, addSummary, addUserPrompt, deduplicateFailedAttempt } from './memory-db.ts';
14
14
  import { parseTranscript, extractUserMessages, getLastAssistantMessage } from './transcript-parser.ts';
15
15
  import { extractObservationsFromEntries } from './observation-extractor.ts';
16
- import { getProjectRoot } from './config.ts';
16
+ import { getProjectRoot, getConfig } from './config.ts';
17
17
 
18
18
  /**
19
19
  * Auto-detect the Claude Code project transcript directory.
@@ -22,12 +22,13 @@ import { getProjectRoot } from './config.ts';
22
22
  function findTranscriptDir(): string {
23
23
  const home = process.env.HOME ?? '~';
24
24
  const projectRoot = getProjectRoot();
25
+ const claudeDirName = getConfig().conventions?.claudeDirName ?? '.claude';
25
26
  // Claude Code escapes the path by replacing / with -
26
27
  const escapedPath = projectRoot.replace(/\//g, '-');
27
- const candidate = resolve(home, '.claude/projects', escapedPath);
28
+ const candidate = resolve(home, `${claudeDirName}/projects`, escapedPath);
28
29
  if (existsSync(candidate)) return candidate;
29
- // Fallback: scan .claude/projects/ for directories matching the project name
30
- const projectsDir = resolve(home, '.claude/projects');
30
+ // Fallback: scan projects dir for directories matching the project name
31
+ const projectsDir = resolve(home, `${claudeDirName}/projects`);
31
32
  if (existsSync(projectsDir)) {
32
33
  try {
33
34
  const entries = readdirSync(projectsDir);
package/src/cli.ts CHANGED
@@ -43,6 +43,11 @@ async function main(): Promise<void> {
43
43
  await runInstallHooks();
44
44
  break;
45
45
  }
46
+ case 'install-commands': {
47
+ const { runInstallCommands } = await import('./commands/install-commands.ts');
48
+ await runInstallCommands();
49
+ break;
50
+ }
46
51
  case 'validate-config': {
47
52
  const { runValidateConfig } = await import('./commands/doctor.ts');
48
53
  await runValidateConfig();
@@ -77,6 +82,7 @@ Commands:
77
82
  init Set up Massu AI in your project (one command, full setup)
78
83
  doctor Check installation health
79
84
  install-hooks Install/update Claude Code hooks
85
+ install-commands Install/update slash commands
80
86
  validate-config Validate massu.config.yaml
81
87
 
82
88
  Options:
package/src/cloud-sync.ts CHANGED
@@ -166,26 +166,22 @@ export async function syncToCloud(
166
166
  * Successfully synced items are removed; failed items get their retry count incremented.
167
167
  */
168
168
  export async function drainSyncQueue(db: Database.Database): Promise<void> {
169
- try {
170
- const config = getConfig();
171
- if (!config.cloud?.enabled || !config.cloud?.apiKey) return;
172
-
173
- const pending = dequeuePendingSync(db, 10);
174
- for (const item of pending) {
175
- try {
176
- const payload = JSON.parse(item.payload) as SyncPayload;
177
- const result = await syncToCloud(db, payload);
178
- if (result.success) {
179
- removePendingSync(db, item.id);
180
- } else {
181
- incrementRetryCount(db, item.id, result.error ?? 'Unknown error');
182
- }
183
- } catch (err) {
184
- incrementRetryCount(db, item.id, err instanceof Error ? err.message : String(err));
169
+ const config = getConfig();
170
+ if (!config.cloud?.enabled || !config.cloud?.apiKey) return;
171
+
172
+ const pending = dequeuePendingSync(db, 10);
173
+ for (const item of pending) {
174
+ try {
175
+ const payload = JSON.parse(item.payload) as SyncPayload;
176
+ const result = await syncToCloud(db, payload);
177
+ if (result.success) {
178
+ removePendingSync(db, item.id);
179
+ } else {
180
+ incrementRetryCount(db, item.id, result.error ?? 'Unknown error');
185
181
  }
182
+ } catch (err) {
183
+ incrementRetryCount(db, item.id, err instanceof Error ? err.message : String(err));
186
184
  }
187
- } catch (err) {
188
- process.stderr.write(`massu: drainSyncQueue failed: ${err instanceof Error ? err.message : String(err)}\n`);
189
185
  }
190
186
  }
191
187