@massu/core 0.1.2 → 0.4.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 (84) 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 +12521 -0
  34. package/dist/hooks/cost-tracker.js +80 -5
  35. package/dist/hooks/post-edit-context.js +72 -6
  36. package/dist/hooks/post-tool-use.js +234 -57
  37. package/dist/hooks/pre-compact.js +144 -5
  38. package/dist/hooks/pre-delete-check.js +141 -11
  39. package/dist/hooks/quality-event.js +80 -5
  40. package/dist/hooks/security-gate.js +29 -0
  41. package/dist/hooks/session-end.js +83 -8
  42. package/dist/hooks/session-start.js +153 -7
  43. package/dist/hooks/user-prompt.js +166 -5
  44. package/package.json +6 -5
  45. package/src/backfill-sessions.ts +5 -4
  46. package/src/cli.ts +6 -1
  47. package/src/commands/doctor.ts +193 -6
  48. package/src/commands/init.ts +235 -6
  49. package/src/commands/install-commands.ts +137 -0
  50. package/src/config.ts +68 -2
  51. package/src/db.ts +115 -2
  52. package/src/docs-tools.ts +8 -6
  53. package/src/hooks/post-edit-context.ts +1 -1
  54. package/src/hooks/post-tool-use.ts +130 -0
  55. package/src/hooks/pre-compact.ts +23 -1
  56. package/src/hooks/pre-delete-check.ts +92 -4
  57. package/src/hooks/security-gate.ts +32 -0
  58. package/src/hooks/session-start.ts +97 -4
  59. package/src/hooks/user-prompt.ts +46 -1
  60. package/src/import-resolver.ts +2 -1
  61. package/src/knowledge-db.ts +169 -0
  62. package/src/knowledge-indexer.ts +704 -0
  63. package/src/knowledge-tools.ts +1413 -0
  64. package/src/license.ts +482 -0
  65. package/src/memory-db.ts +14 -1
  66. package/src/observation-extractor.ts +11 -4
  67. package/src/page-deps.ts +3 -2
  68. package/src/python/coupling-detector.ts +124 -0
  69. package/src/python/domain-enforcer.ts +83 -0
  70. package/src/python/impact-analyzer.ts +95 -0
  71. package/src/python/import-parser.ts +244 -0
  72. package/src/python/import-resolver.ts +135 -0
  73. package/src/python/migration-indexer.ts +115 -0
  74. package/src/python/migration-parser.ts +332 -0
  75. package/src/python/model-indexer.ts +70 -0
  76. package/src/python/model-parser.ts +279 -0
  77. package/src/python/route-indexer.ts +58 -0
  78. package/src/python/route-parser.ts +317 -0
  79. package/src/python-tools.ts +629 -0
  80. package/src/sentinel-db.ts +2 -1
  81. package/src/server.ts +29 -6
  82. package/src/session-archiver.ts +4 -5
  83. package/src/tools.ts +283 -31
  84. package/README.md +0 -40
@@ -9,6 +9,7 @@ import { existsSync as existsSync2, mkdirSync } from "fs";
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,11 +317,20 @@ 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
 
@@ -746,6 +812,15 @@ function initMemorySchema(db) {
746
812
  );
747
813
  CREATE INDEX IF NOT EXISTS idx_pending_sync_created ON pending_sync(created_at ASC);
748
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
+ `);
749
824
  }
750
825
  function enqueueSyncPayload(db, payload) {
751
826
  db.prepare("INSERT INTO pending_sync (payload) VALUES (?)").run(payload);
@@ -996,10 +1071,10 @@ function safeParseJson(json, fallback) {
996
1071
  }
997
1072
 
998
1073
  // src/session-archiver.ts
999
- var PROJECT_ROOT = getProjectRoot();
1000
1074
  function archiveAndRegenerate(db, sessionId) {
1001
- const currentMdPath = resolve3(PROJECT_ROOT, ".claude/session-state/CURRENT.md");
1002
- const archiveDir = resolve3(PROJECT_ROOT, ".claude/session-state/archive");
1075
+ const resolved = getResolvedPaths();
1076
+ const currentMdPath = resolved.sessionStatePath;
1077
+ const archiveDir = resolved.sessionArchivePath;
1003
1078
  let archived = false;
1004
1079
  let archivePath;
1005
1080
  if (existsSync3(currentMdPath)) {
@@ -9,6 +9,7 @@ import { existsSync as existsSync2, mkdirSync } from "fs";
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,11 +317,20 @@ 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
 
@@ -752,6 +818,15 @@ function initMemorySchema(db) {
752
818
  );
753
819
  CREATE INDEX IF NOT EXISTS idx_pending_sync_created ON pending_sync(created_at ASC);
754
820
  `);
821
+ db.exec(`
822
+ CREATE TABLE IF NOT EXISTS license_cache (
823
+ api_key_hash TEXT PRIMARY KEY,
824
+ tier TEXT NOT NULL,
825
+ valid_until TEXT NOT NULL,
826
+ last_validated TEXT NOT NULL,
827
+ features TEXT DEFAULT '[]'
828
+ );
829
+ `);
755
830
  }
756
831
  function autoDetectTaskId(planFile) {
757
832
  if (!planFile) return null;
@@ -831,6 +906,8 @@ function linkSessionToTask(db, sessionId, taskId) {
831
906
  }
832
907
 
833
908
  // src/hooks/session-start.ts
909
+ import { readFileSync as readFileSync2, existsSync as existsSync3 } from "fs";
910
+ import { join } from "path";
834
911
  async function main() {
835
912
  try {
836
913
  const input = await readStdin();
@@ -857,7 +934,7 @@ Session memory, code intelligence, and governance are now active.
857
934
  `
858
935
  );
859
936
  }
860
- const context = buildContext(db, session_id, source ?? "startup", tokenBudget, session?.task_id ?? null);
937
+ const context = await buildContext(db, session_id, source ?? "startup", tokenBudget, session?.task_id ?? null);
861
938
  if (context.trim()) {
862
939
  process.stdout.write(context);
863
940
  }
@@ -882,7 +959,7 @@ function getTokenBudget(source) {
882
959
  return 2e3;
883
960
  }
884
961
  }
885
- function buildContext(db, sessionId, source, tokenBudget, taskId) {
962
+ async function buildContext(db, sessionId, source, tokenBudget, taskId) {
886
963
  const sections = [];
887
964
  const failures = getFailedAttempts(db, void 0, 10);
888
965
  if (failures.length > 0) {
@@ -939,6 +1016,47 @@ function buildContext(db, sessionId, source, tokenBudget, taskId) {
939
1016
  sections.push({ text: progressText, importance: 8 });
940
1017
  }
941
1018
  }
1019
+ const preventionRules = loadCorrectionsPreventionRules();
1020
+ if (preventionRules.length > 0) {
1021
+ let rulesText = "### Active Prevention Rules (from corrections.md)\n";
1022
+ for (const rule of preventionRules) {
1023
+ rulesText += `- ${rule}
1024
+ `;
1025
+ }
1026
+ sections.push({ text: rulesText, importance: 9 });
1027
+ }
1028
+ try {
1029
+ const knowledgeDbPath = getResolvedPaths().knowledgeDbPath;
1030
+ if (existsSync3(knowledgeDbPath)) {
1031
+ const Database2 = (await import("better-sqlite3")).default;
1032
+ const kdb = new Database2(knowledgeDbPath, { readonly: true });
1033
+ try {
1034
+ const stats = kdb.prepare(
1035
+ "SELECT COUNT(*) as doc_count, MAX(indexed_at) as last_indexed FROM knowledge_documents"
1036
+ ).get();
1037
+ if (stats.doc_count > 0 && stats.last_indexed) {
1038
+ const ageMs = Date.now() - new Date(stats.last_indexed).getTime();
1039
+ const ageHours = Math.round(ageMs / 36e5);
1040
+ if (ageHours > 24) {
1041
+ sections.push({
1042
+ text: `### Knowledge Index Status
1043
+ Index has ${stats.doc_count} documents, last indexed ${ageHours}h ago. Consider re-indexing.
1044
+ `,
1045
+ importance: 3
1046
+ });
1047
+ }
1048
+ } else if (stats.doc_count === 0) {
1049
+ sections.push({
1050
+ text: "### Knowledge Index Status\nKnowledge index is empty. Run knowledge indexing to populate it.\n",
1051
+ importance: 2
1052
+ });
1053
+ }
1054
+ } finally {
1055
+ kdb.close();
1056
+ }
1057
+ }
1058
+ } catch (_knowledgeErr) {
1059
+ }
942
1060
  const recentObs = getRecentObservations(db, 20);
943
1061
  if (recentObs.length > 0) {
944
1062
  let obsText = "### Recent Observations\n";
@@ -1002,4 +1120,32 @@ function safeParseJson(json) {
1002
1120
  return null;
1003
1121
  }
1004
1122
  }
1123
+ function loadCorrectionsPreventionRules() {
1124
+ try {
1125
+ const homeDir = process.env.HOME ?? process.env.USERPROFILE ?? "";
1126
+ const cwd = process.cwd();
1127
+ const config = getConfig();
1128
+ const claudeDirName = config.conventions?.claudeDirName ?? ".claude";
1129
+ const projectDirName = cwd.replace(/\//g, "-").replace(/^-/, "");
1130
+ const correctionsPath = join(homeDir, claudeDirName, "projects", projectDirName, "memory", "corrections.md");
1131
+ if (!existsSync3(correctionsPath)) return [];
1132
+ const content = readFileSync2(correctionsPath, "utf-8");
1133
+ const lines = content.split("\n");
1134
+ const rules = [];
1135
+ for (const line of lines) {
1136
+ const trimmed = line.trim();
1137
+ if (!trimmed.startsWith("|") || !trimmed.endsWith("|")) continue;
1138
+ const cells = trimmed.split("|").map((c) => c.trim()).filter((c) => c.length > 0);
1139
+ if (cells.length < 4) continue;
1140
+ if (cells[0] === "Date" || cells[0].startsWith("-")) continue;
1141
+ const preventionRule = cells[3];
1142
+ if (preventionRule && !preventionRule.startsWith("-") && !preventionRule.startsWith("<!--")) {
1143
+ rules.push(preventionRule);
1144
+ }
1145
+ }
1146
+ return rules;
1147
+ } catch (_e) {
1148
+ return [];
1149
+ }
1150
+ }
1005
1151
  main();
@@ -9,6 +9,7 @@ import { existsSync as existsSync2, mkdirSync } from "fs";
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,11 +317,20 @@ 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
 
@@ -746,6 +812,39 @@ function initMemorySchema(db) {
746
812
  );
747
813
  CREATE INDEX IF NOT EXISTS idx_pending_sync_created ON pending_sync(created_at ASC);
748
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
+ }
749
848
  }
750
849
  function autoDetectTaskId(planFile) {
751
850
  if (!planFile) return null;
@@ -760,6 +859,29 @@ function createSession(db, sessionId, opts) {
760
859
  VALUES (?, ?, ?, ?, ?, ?)
761
860
  `).run(sessionId, opts?.branch ?? null, opts?.planFile ?? null, taskId, now.toISOString(), Math.floor(now.getTime() / 1e3));
762
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
+ }
763
885
  function addUserPrompt(db, sessionId, text, promptNumber) {
764
886
  const now = /* @__PURE__ */ new Date();
765
887
  db.prepare(`
@@ -772,6 +894,7 @@ function linkSessionToTask(db, sessionId, taskId) {
772
894
  }
773
895
 
774
896
  // src/hooks/user-prompt.ts
897
+ import { existsSync as existsSync3 } from "fs";
775
898
  async function main() {
776
899
  try {
777
900
  const input = await readStdin();
@@ -799,6 +922,35 @@ async function main() {
799
922
  ).get(session_id);
800
923
  const promptNumber = countResult.count + 1;
801
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
+ }
802
954
  } finally {
803
955
  db.close();
804
956
  }
@@ -806,6 +958,15 @@ async function main() {
806
958
  }
807
959
  process.exit(0);
808
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
+ }
809
970
  async function getGitBranch() {
810
971
  try {
811
972
  const { spawnSync } = await import("child_process");
package/package.json CHANGED
@@ -1,21 +1,21 @@
1
1
  {
2
2
  "name": "@massu/core",
3
- "version": "0.1.2",
3
+ "version": "0.4.1",
4
4
  "type": "module",
5
- "description": "AI Engineering Governance MCP Server - Session memory, feature registry, code intelligence, and rule enforcement",
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
6
  "main": "src/server.ts",
7
7
  "bin": {
8
- "massu": "./src/cli.ts"
8
+ "massu": "./dist/cli.js"
9
9
  },
10
10
  "scripts": {
11
11
  "start": "npx tsx src/server.ts",
12
12
  "test": "vitest run",
13
- "build": "tsc --noEmit && npm run build:hooks",
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);'",
14
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);'",
15
16
  "prepublishOnly": "bash ../../scripts/prepublish-check.sh && npm run build"
16
17
  },
17
18
  "dependencies": {
18
- "@modelcontextprotocol/sdk": "^1.0.0",
19
19
  "better-sqlite3": "^12.6.2",
20
20
  "yaml": "^2.4.0",
21
21
  "zod": "^3.23.0"
@@ -31,6 +31,7 @@
31
31
  "src/**/*",
32
32
  "!src/__tests__/**",
33
33
  "dist/**/*",
34
+ "commands/**/*",
34
35
  "LICENSE"
35
36
  ],
36
37
  "keywords": [