@massu/core 0.9.2 → 1.0.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 (50) hide show
  1. package/dist/cli.js +10519 -1661
  2. package/dist/hooks/auto-learning-pipeline.js +99 -19
  3. package/dist/hooks/classify-failure.js +99 -19
  4. package/dist/hooks/cost-tracker.js +97 -11
  5. package/dist/hooks/fix-detector.js +99 -19
  6. package/dist/hooks/incident-pipeline.js +97 -11
  7. package/dist/hooks/post-edit-context.js +97 -11
  8. package/dist/hooks/post-tool-use.js +101 -20
  9. package/dist/hooks/pre-compact.js +97 -11
  10. package/dist/hooks/pre-delete-check.js +97 -11
  11. package/dist/hooks/quality-event.js +97 -11
  12. package/dist/hooks/rule-enforcement-pipeline.js +97 -11
  13. package/dist/hooks/session-end.js +97 -11
  14. package/dist/hooks/session-start.js +98 -12
  15. package/dist/hooks/user-prompt.js +98 -43
  16. package/package.json +13 -3
  17. package/reference/hook-execution-order.md +17 -25
  18. package/src/cli.ts +2 -1
  19. package/src/commands/doctor.ts +1 -29
  20. package/src/commands/init.ts +752 -216
  21. package/src/config.ts +168 -12
  22. package/src/detect/domain-inferrer.ts +142 -0
  23. package/src/detect/drift.ts +199 -0
  24. package/src/detect/framework-detector.ts +281 -0
  25. package/src/detect/index.ts +174 -0
  26. package/src/detect/migrate.ts +278 -0
  27. package/src/detect/monorepo-detector.ts +347 -0
  28. package/src/detect/package-detector.ts +728 -0
  29. package/src/detect/source-dir-detector.ts +264 -0
  30. package/src/detect/vr-command-map.ts +167 -0
  31. package/src/hooks/auto-learning-pipeline.ts +2 -2
  32. package/src/hooks/classify-failure.ts +2 -2
  33. package/src/hooks/fix-detector.ts +2 -2
  34. package/src/hooks/session-start.ts +1 -1
  35. package/src/hooks/user-prompt.ts +1 -21
  36. package/src/knowledge-indexer.ts +1 -1
  37. package/src/license.ts +1 -2
  38. package/src/memory-db.ts +0 -5
  39. package/src/memory-file-ingest.ts +6 -13
  40. package/src/tools.ts +0 -8
  41. package/templates/multi-runtime/massu.config.yaml +80 -0
  42. package/templates/python-django/massu.config.yaml +51 -0
  43. package/templates/python-fastapi/massu.config.yaml +50 -0
  44. package/templates/rust-actix/massu.config.yaml +38 -0
  45. package/templates/swift-ios/massu.config.yaml +37 -0
  46. package/templates/ts-nestjs/massu.config.yaml +43 -0
  47. package/templates/ts-nextjs/massu.config.yaml +43 -0
  48. package/README.md +0 -40
  49. package/src/claude-md-templates.ts +0 -342
  50. package/src/mcp-bridge-tools.ts +0 -458
@@ -0,0 +1,264 @@
1
+ // Copyright (c) 2026 Massu. All rights reserved.
2
+ // Licensed under BSL 1.1 - see LICENSE file for details.
3
+
4
+ /**
5
+ * Source Directory Detector (P1-003)
6
+ * ==================================
7
+ *
8
+ * For each detected language, glob files of that type repo-wide (honoring
9
+ * .gitignore and a hard-coded ignore set), then cluster by common prefix
10
+ * directory and pick the directory with the highest file density.
11
+ *
12
+ * Handles:
13
+ * - Colocated tests (src/**​/*.test.ts next to source). Sets `colocated: true`
14
+ * on the language entry when test files are <30% of a dir's file count
15
+ * AND no dedicated tests/__tests__/ dir exists.
16
+ * - Top-level `tests/` or `__tests__/` directories (honored as test_dirs).
17
+ * - Monorepo packages/apps/services/libs/modules subtrees (each treated as
18
+ * a workspace root).
19
+ *
20
+ * Security (CR-3, CR-9):
21
+ * - Glob roots strictly inside projectRoot. Symlinks escaping projectRoot
22
+ * are rejected.
23
+ * - Secret-ish files (.env*, *.pem, *.key, .aws/, .ssh/, credentials.json)
24
+ * are excluded from globs.
25
+ * - Only the filename is ever read — never file contents.
26
+ *
27
+ * Usage:
28
+ * ```ts
29
+ * import { detectSourceDirs } from './detect/source-dir-detector.ts';
30
+ * const map = detectSourceDirs('/repo', ['python', 'typescript']);
31
+ * ```
32
+ */
33
+
34
+ import { realpathSync } from 'fs';
35
+ import { resolve } from 'path';
36
+ import fg from 'fast-glob';
37
+ import type { SupportedLanguage } from './package-detector.ts';
38
+
39
+ export interface SourceDirInfo {
40
+ /** Source directories (relative to projectRoot, forward-slash). */
41
+ source_dirs: string[];
42
+ /** Test directories (relative to projectRoot, forward-slash). */
43
+ test_dirs: string[];
44
+ /** True when tests live next to source (no dedicated tests/ dir). */
45
+ colocated: boolean;
46
+ /** Number of source files detected for this language. */
47
+ file_count: number;
48
+ }
49
+
50
+ export type SourceDirMap = Partial<Record<SupportedLanguage, SourceDirInfo>>;
51
+
52
+ const IGNORE_PATTERNS: string[] = [
53
+ '**/node_modules/**',
54
+ '**/.venv/**',
55
+ '**/venv/**',
56
+ '**/__pycache__/**',
57
+ '**/dist/**',
58
+ '**/build/**',
59
+ '**/.build/**',
60
+ '**/target/**',
61
+ '**/.next/**',
62
+ '**/.nuxt/**',
63
+ '**/coverage/**',
64
+ '**/.git/**',
65
+ '**/.massu/**',
66
+ '**/.turbo/**',
67
+ '**/.cache/**',
68
+ '**/.pytest_cache/**',
69
+ '**/.mypy_cache/**',
70
+ '**/DerivedData/**',
71
+ '**/Pods/**',
72
+ // Secret-ish patterns
73
+ '**/.env',
74
+ '**/.env.*',
75
+ '**/*.pem',
76
+ '**/*.key',
77
+ '**/.aws/**',
78
+ '**/.ssh/**',
79
+ '**/credentials.json',
80
+ '**/*.p12',
81
+ '**/*.pfx',
82
+ ];
83
+
84
+ const EXTENSIONS: Record<SupportedLanguage, string[]> = {
85
+ python: ['py'],
86
+ typescript: ['ts', 'tsx'],
87
+ javascript: ['js', 'jsx', 'mjs', 'cjs'],
88
+ rust: ['rs'],
89
+ swift: ['swift'],
90
+ go: ['go'],
91
+ java: ['java', 'kt'],
92
+ ruby: ['rb'],
93
+ };
94
+
95
+ const TEST_FILE_PATTERNS: Record<SupportedLanguage, RegExp[]> = {
96
+ python: [/_test\.py$/, /test_[^/]*\.py$/],
97
+ typescript: [/\.test\.tsx?$/, /\.spec\.tsx?$/],
98
+ javascript: [/\.test\.[mc]?jsx?$/, /\.spec\.[mc]?jsx?$/],
99
+ rust: [/tests\/.*\.rs$/],
100
+ swift: [/Tests\//],
101
+ go: [/_test\.go$/],
102
+ java: [/Test[^/]*\.(java|kt)$/, /[^/]*Test\.(java|kt)$/],
103
+ ruby: [/_spec\.rb$/, /_test\.rb$/],
104
+ };
105
+
106
+ const TEST_DIR_KEYWORDS = ['tests', 'test', '__tests__', 'spec', 'specs'];
107
+
108
+ function extsFor(language: SupportedLanguage): string[] {
109
+ return EXTENSIONS[language] ?? [];
110
+ }
111
+
112
+ function isTestPath(language: SupportedLanguage, path: string): boolean {
113
+ // Any dedicated test-dir keyword in the path segments
114
+ const segments = path.split('/');
115
+ for (const seg of segments) {
116
+ if (TEST_DIR_KEYWORDS.includes(seg)) return true;
117
+ }
118
+ const patterns = TEST_FILE_PATTERNS[language] ?? [];
119
+ return patterns.some((re) => re.test(path));
120
+ }
121
+
122
+ /** Get the top-level directory segment of a relative path (or '.' for root). */
123
+ function topSegment(rel: string): string {
124
+ const parts = rel.split('/');
125
+ return parts.length > 1 ? parts[0] : '.';
126
+ }
127
+
128
+ /**
129
+ * Check that a path (after realpath) is inside the projectRoot.
130
+ * Symlinks escaping the tree are rejected.
131
+ */
132
+ function isInsideRoot(root: string, candidate: string): boolean {
133
+ try {
134
+ const realRoot = realpathSync(root);
135
+ const realCand = realpathSync(resolve(root, candidate));
136
+ return realCand === realRoot || realCand.startsWith(realRoot + '/');
137
+ } catch {
138
+ return false;
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Detect source and test directories per language.
144
+ *
145
+ * @param projectRoot absolute path to repo root
146
+ * @param languages list of languages to probe (derived from P1-001 manifests)
147
+ */
148
+ export function detectSourceDirs(
149
+ projectRoot: string,
150
+ languages: SupportedLanguage[]
151
+ ): SourceDirMap {
152
+ const out: SourceDirMap = {};
153
+ for (const lang of languages) {
154
+ const exts = extsFor(lang);
155
+ if (exts.length === 0) continue;
156
+ const patterns = exts.map((e) => `**/*.${e}`);
157
+ let files: string[];
158
+ try {
159
+ files = fg.sync(patterns, {
160
+ cwd: projectRoot,
161
+ dot: false,
162
+ ignore: IGNORE_PATTERNS,
163
+ followSymbolicLinks: false,
164
+ suppressErrors: true,
165
+ });
166
+ } catch {
167
+ files = [];
168
+ }
169
+
170
+ // Drop any file whose resolved realpath escapes the root (defence in depth).
171
+ files = files.filter((f) => isInsideRoot(projectRoot, f));
172
+
173
+ if (files.length === 0) {
174
+ continue;
175
+ }
176
+
177
+ // Split into source vs test files.
178
+ const sourceFiles: string[] = [];
179
+ const testFiles: string[] = [];
180
+ for (const f of files) {
181
+ if (isTestPath(lang, f)) testFiles.push(f);
182
+ else sourceFiles.push(f);
183
+ }
184
+
185
+ // Cluster by top segment.
186
+ const srcCluster = new Map<string, number>();
187
+ for (const f of sourceFiles) {
188
+ const k = topSegment(f);
189
+ srcCluster.set(k, (srcCluster.get(k) ?? 0) + 1);
190
+ }
191
+ const testCluster = new Map<string, number>();
192
+ for (const f of testFiles) {
193
+ const k = topSegment(f);
194
+ testCluster.set(k, (testCluster.get(k) ?? 0) + 1);
195
+ }
196
+
197
+ const source_dirs: string[] = [];
198
+ const test_dirs: string[] = [];
199
+
200
+ // Any top segment with at least one source file counts.
201
+ // Sort by density desc, then name asc for determinism.
202
+ const srcSorted = [...srcCluster.entries()].sort((a, b) => {
203
+ if (b[1] !== a[1]) return b[1] - a[1];
204
+ return a[0].localeCompare(b[0]);
205
+ });
206
+ for (const [seg] of srcSorted) source_dirs.push(seg);
207
+
208
+ // Test dirs: any top-level dir named in TEST_DIR_KEYWORDS that accrued files,
209
+ // plus any top segment with test files.
210
+ const testSet = new Set<string>();
211
+ for (const [seg] of testCluster.entries()) {
212
+ if (TEST_DIR_KEYWORDS.includes(seg)) testSet.add(seg);
213
+ }
214
+ // Also pick up "tests/" under each source dir — e.g. apps/ai-service/tests/
215
+ // Glob again just for test dirs this language may have.
216
+ let testDirHits: string[] = [];
217
+ try {
218
+ testDirHits = fg.sync(
219
+ TEST_DIR_KEYWORDS.map((k) => `**/${k}/**/*.${exts[0]}`),
220
+ {
221
+ cwd: projectRoot,
222
+ dot: false,
223
+ ignore: IGNORE_PATTERNS,
224
+ followSymbolicLinks: false,
225
+ suppressErrors: true,
226
+ }
227
+ );
228
+ } catch {
229
+ testDirHits = [];
230
+ }
231
+ const testPrefixes = new Set<string>();
232
+ for (const f of testDirHits) {
233
+ // Keep the prefix up to and including the test keyword dir
234
+ const segs = f.split('/');
235
+ for (let i = 0; i < segs.length; i++) {
236
+ if (TEST_DIR_KEYWORDS.includes(segs[i])) {
237
+ testPrefixes.add(segs.slice(0, i + 1).join('/'));
238
+ break;
239
+ }
240
+ }
241
+ }
242
+ for (const p of testPrefixes) testSet.add(p);
243
+ for (const seg of testSet) test_dirs.push(seg);
244
+ test_dirs.sort();
245
+
246
+ // Colocated if test_dirs set is empty AND test files exist AND testFiles/(src+test) < 0.3
247
+ const totalFiles = sourceFiles.length + testFiles.length;
248
+ const testRatio = totalFiles === 0 ? 0 : testFiles.length / totalFiles;
249
+ const hasDedicatedTestDir = test_dirs.length > 0;
250
+ const colocated =
251
+ !hasDedicatedTestDir && testFiles.length > 0 && testRatio < 0.3;
252
+ if (colocated) {
253
+ for (const s of source_dirs) if (!test_dirs.includes(s)) test_dirs.push(s);
254
+ }
255
+
256
+ out[lang] = {
257
+ source_dirs,
258
+ test_dirs,
259
+ colocated,
260
+ file_count: files.length,
261
+ };
262
+ }
263
+ return out;
264
+ }
@@ -0,0 +1,167 @@
1
+ // Copyright (c) 2026 Massu. All rights reserved.
2
+ // Licensed under BSL 1.1 - see LICENSE file for details.
3
+
4
+ /**
5
+ * VR Command Map (P1-005)
6
+ * =======================
7
+ *
8
+ * Given a language + framework info + workspace directory, produce the
9
+ * verification command set (VR-TEST, VR-TYPE, VR-BUILD, VR-SYNTAX, VR-LINT)
10
+ * that the skill runners should invoke.
11
+ *
12
+ * User overrides from `massu.config.yaml` `verification.<language>.*` take
13
+ * precedence over the built-in mapping.
14
+ *
15
+ * Usage:
16
+ * ```ts
17
+ * import { getVRCommands } from './detect/vr-command-map.ts';
18
+ * const cmds = getVRCommands('python', { test_framework: 'pytest', ... }, 'apps/ai-service');
19
+ * // => { test: 'cd apps/ai-service && python3 -m pytest -q', ... }
20
+ * ```
21
+ */
22
+
23
+ import type { SupportedLanguage } from './package-detector.ts';
24
+ import type { FrameworkInfo } from './framework-detector.ts';
25
+
26
+ export interface VRCommandSet {
27
+ /** VR-TEST command. */
28
+ test: string | null;
29
+ /** VR-TYPE command. */
30
+ type: string | null;
31
+ /** VR-BUILD command. */
32
+ build: string | null;
33
+ /** VR-SYNTAX command. */
34
+ syntax: string | null;
35
+ /** VR-LINT command. */
36
+ lint: string | null;
37
+ }
38
+
39
+ /** Shape read from `config.verification[<language>]`. */
40
+ export interface UserVerificationEntry {
41
+ type?: string;
42
+ test?: string;
43
+ syntax?: string;
44
+ lint?: string;
45
+ build?: string;
46
+ }
47
+
48
+ function prefix(dir: string, cmd: string): string {
49
+ if (!dir || dir === '.') return cmd;
50
+ return `cd ${dir} && ${cmd}`;
51
+ }
52
+
53
+ function defaultsFor(
54
+ language: SupportedLanguage,
55
+ fw: FrameworkInfo,
56
+ dir: string
57
+ ): VRCommandSet {
58
+ switch (language) {
59
+ case 'python': {
60
+ const testFw = fw.test_framework ?? 'pytest';
61
+ return {
62
+ test:
63
+ testFw === 'unittest'
64
+ ? prefix(dir, 'python3 -m unittest')
65
+ : prefix(dir, 'python3 -m pytest -q'),
66
+ type: prefix(dir, 'python3 -m mypy .'),
67
+ build: null,
68
+ syntax: prefix(dir, 'python3 -m py_compile'),
69
+ lint: prefix(dir, 'python3 -m ruff check .'),
70
+ };
71
+ }
72
+ case 'typescript': {
73
+ const testFw = fw.test_framework ?? 'vitest';
74
+ // Test command uses npm test (respecting package.json script); fallback mapping
75
+ // is fine because npm test routes through whatever runner is wired.
76
+ return {
77
+ test: prefix(dir, 'npm test'),
78
+ type: prefix(dir, 'npx tsc --noEmit'),
79
+ build: prefix(dir, 'npm run build'),
80
+ syntax: null,
81
+ lint: prefix(dir, 'npx eslint .'),
82
+ // testFw currently only affects defaults; npm test is runner-agnostic
83
+ ...(testFw === 'mocha'
84
+ ? { test: prefix(dir, 'npx mocha') }
85
+ : {}),
86
+ };
87
+ }
88
+ case 'javascript': {
89
+ return {
90
+ test: prefix(dir, 'npm test'),
91
+ type: null,
92
+ build: prefix(dir, 'npm run build'),
93
+ syntax: null,
94
+ lint: prefix(dir, 'npx eslint .'),
95
+ };
96
+ }
97
+ case 'rust': {
98
+ return {
99
+ test: prefix(dir, 'cargo test'),
100
+ type: prefix(dir, 'cargo check'),
101
+ build: prefix(dir, 'cargo build'),
102
+ syntax: null,
103
+ lint: prefix(dir, 'cargo clippy -- -D warnings'),
104
+ };
105
+ }
106
+ case 'swift': {
107
+ return {
108
+ test: prefix(dir, 'swift test'),
109
+ type: prefix(dir, 'swift build'),
110
+ build: prefix(dir, 'xcodebuild build'),
111
+ syntax: null,
112
+ lint: prefix(dir, 'swiftlint'),
113
+ };
114
+ }
115
+ case 'go': {
116
+ return {
117
+ test: prefix(dir, 'go test ./...'),
118
+ type: prefix(dir, 'go vet ./...'),
119
+ build: prefix(dir, 'go build ./...'),
120
+ syntax: null,
121
+ lint: prefix(dir, 'golangci-lint run'),
122
+ };
123
+ }
124
+ case 'java': {
125
+ return {
126
+ test: prefix(dir, 'mvn test'),
127
+ type: prefix(dir, 'mvn compile'),
128
+ build: prefix(dir, 'mvn package'),
129
+ syntax: null,
130
+ lint: null,
131
+ };
132
+ }
133
+ case 'ruby': {
134
+ return {
135
+ test: prefix(dir, 'bundle exec rspec'),
136
+ type: null,
137
+ build: null,
138
+ syntax: prefix(dir, 'ruby -c'),
139
+ lint: prefix(dir, 'bundle exec rubocop'),
140
+ };
141
+ }
142
+ default:
143
+ return { test: null, type: null, build: null, syntax: null, lint: null };
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Produce the VR command set for a language.
149
+ *
150
+ * User-provided entries (if any) override built-ins key-by-key.
151
+ */
152
+ export function getVRCommands(
153
+ language: SupportedLanguage,
154
+ framework: FrameworkInfo,
155
+ dir: string,
156
+ userOverrides?: UserVerificationEntry
157
+ ): VRCommandSet {
158
+ const built = defaultsFor(language, framework, dir);
159
+ if (!userOverrides) return built;
160
+ return {
161
+ test: userOverrides.test ?? built.test,
162
+ type: userOverrides.type ?? built.type,
163
+ build: userOverrides.build ?? built.build,
164
+ syntax: userOverrides.syntax ?? built.syntax,
165
+ lint: userOverrides.lint ?? built.lint,
166
+ };
167
+ }
@@ -17,7 +17,7 @@
17
17
  // ============================================================
18
18
 
19
19
  import { execSync } from 'child_process';
20
- import { existsSync, readFileSync, unlinkSync, readdirSync } from 'fs';
20
+ import { existsSync, readFileSync, unlinkSync, readdirSync, statSync } from 'fs';
21
21
  import { tmpdir } from 'os';
22
22
  import { join } from 'path';
23
23
  import { getProjectRoot, getConfig } from '../config.ts';
@@ -172,7 +172,7 @@ function cleanup(flagPath: string): void {
172
172
  for (const file of readdirSync(dir)) {
173
173
  const fullPath = join(dir, file);
174
174
  try {
175
- const stat = require('fs').statSync(fullPath);
175
+ const stat = statSync(fullPath);
176
176
  if (now - stat.mtimeMs > 86400000) {
177
177
  unlinkSync(fullPath);
178
178
  }
@@ -20,7 +20,7 @@
20
20
  // Must complete in <1000ms.
21
21
  // ============================================================
22
22
 
23
- import { existsSync, readFileSync, readdirSync, unlinkSync } from 'fs';
23
+ import { existsSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from 'fs';
24
24
  import { tmpdir } from 'os';
25
25
  import { join, basename } from 'path';
26
26
  import { getProjectRoot, getConfig } from '../config.ts';
@@ -166,7 +166,7 @@ async function main(): Promise<void> {
166
166
 
167
167
  // Mark that we've classified this file
168
168
  try {
169
- require('fs').writeFileSync(dedupeMarker, '1');
169
+ writeFileSync(dedupeMarker, '1');
170
170
  } catch { /* ignore */ }
171
171
 
172
172
  // Score against failure taxonomy in database
@@ -15,7 +15,7 @@
15
15
  // ============================================================
16
16
 
17
17
  import { execSync } from 'child_process';
18
- import { existsSync, appendFileSync, mkdirSync } from 'fs';
18
+ import { existsSync, appendFileSync, mkdirSync, readFileSync } from 'fs';
19
19
  import { tmpdir } from 'os';
20
20
  import { join } from 'path';
21
21
  import { getProjectRoot, getConfig } from '../config.ts';
@@ -158,7 +158,7 @@ async function main(): Promise<void> {
158
158
  appendFileSync(flagPath, JSON.stringify(signal) + '\n');
159
159
 
160
160
  // Count total fixes this session
161
- const lines = require('fs').readFileSync(flagPath, 'utf-8').split('\n').filter(Boolean);
161
+ const lines = readFileSync(flagPath, 'utf-8').split('\n').filter(Boolean);
162
162
  if (lines.length === 1) {
163
163
  // First fix detected — output advisory
164
164
  console.log(
@@ -52,7 +52,7 @@ async function main(): Promise<void> {
52
52
  process.stdout.write(
53
53
  '=== MASSU AI: Active ===\n' +
54
54
  'Session memory, code intelligence, and governance are now active.\n' +
55
- `15 hooks monitoring this session. Type "${getConfig().toolPrefix ?? 'massu'}_sync" to index your codebase.\n` +
55
+ `11 hooks monitoring this session. Type "${getConfig().toolPrefix ?? 'massu'}_sync" to index your codebase.\n` +
56
56
  '=== END MASSU ===\n\n'
57
57
  );
58
58
  }
@@ -8,9 +8,7 @@
8
8
  // ============================================================
9
9
 
10
10
  import { getMemoryDb, createSession, addUserPrompt, linkSessionToTask, autoDetectTaskId, addObservation } from '../memory-db.ts';
11
- import { existsSync, writeFileSync } from 'fs';
12
- import { tmpdir } from 'os';
13
- import { join } from 'path';
11
+ import { existsSync } from 'fs';
14
12
  import { getResolvedPaths } from '../config.ts';
15
13
 
16
14
  interface HookInput {
@@ -109,24 +107,6 @@ async function main(): Promise<void> {
109
107
  } catch (_memoryNagErr) {
110
108
  // Best-effort: never block prompt capture
111
109
  }
112
-
113
- // 7. Failure context markers: write detected failure keywords to temp file
114
- // so classify-failure.ts can use them for scoring
115
- try {
116
- const failureKeywords = [
117
- 'bug', 'broken', 'crash', 'error', 'fail', 'fix', 'wrong', 'missing',
118
- 'undefined', 'null', 'exception', 'stack trace', 'regression', 'revert',
119
- 'doesn\'t work', 'not working', 'stopped working', 'broke',
120
- ];
121
- const promptLower = prompt.toLowerCase();
122
- const matched = failureKeywords.filter(kw => promptLower.includes(kw));
123
- if (matched.length > 0) {
124
- const contextFile = join(tmpdir(), `massu-failure-context-${session_id.slice(0, 8)}-${Date.now()}`);
125
- writeFileSync(contextFile, matched.join(' '), 'utf-8');
126
- }
127
- } catch {
128
- // Best-effort: never block prompt capture
129
- }
130
110
  } finally {
131
111
  db.close();
132
112
  }
@@ -599,7 +599,7 @@ export function indexAllKnowledge(db: Database.Database): IndexStats {
599
599
  // Parse plan documents for structured metadata
600
600
  if (category === 'plan') {
601
601
  // Extract plan items (P1-001, P2-001, etc.)
602
- const planItemRegex = /^###\s+(P[-A-Z]*\d*-?\w+):\s+(.+)$/gm;
602
+ const planItemRegex = /^###\s+(P\d+-\d+):\s+(.+)$/gm;
603
603
  let planMatch;
604
604
  while ((planMatch = planItemRegex.exec(content)) !== null) {
605
605
  insertChunk.run(docId, 'pattern', planMatch[1], `${planMatch[1]}: ${planMatch[2]}`, null, null, JSON.stringify({ plan_item_id: planMatch[1] }));
package/src/license.ts CHANGED
@@ -54,7 +54,7 @@ export function tierLevel(tier: ToolTier): number {
54
54
  * Enterprise: audit, security, dependency
55
55
  */
56
56
  export const TOOL_TIER_MAP: Record<string, ToolTier> = {
57
- // --- Free tier (13 tools: core navigation + basic memory + regression + license) ---
57
+ // --- Free tier (12 tools: core navigation + basic memory + regression + license) ---
58
58
  sync: 'free',
59
59
  context: 'free',
60
60
  impact: 'free',
@@ -64,7 +64,6 @@ export const TOOL_TIER_MAP: Record<string, ToolTier> = {
64
64
  coupling_check: 'free',
65
65
  memory_search: 'free',
66
66
  memory_ingest: 'free',
67
- memory_backfill: 'free',
68
67
  regression_risk: 'free',
69
68
  feature_health: 'free',
70
69
  license_status: 'free',
package/src/memory-db.ts CHANGED
@@ -1509,11 +1509,6 @@ export interface FailureClassMatch {
1509
1509
  /**
1510
1510
  * Score all failure classes against provided match text, file path, and prompt context.
1511
1511
  * Returns the best match with its score.
1512
- *
1513
- * Scoring:
1514
- * +diffPatternWeight (default 3) per diff_pattern match
1515
- * +filePatternWeight (default 2) per file_pattern match
1516
- * +promptKeywordWeight (default 2) per prompt_keyword match
1517
1512
  */
1518
1513
  export function scoreFailureClasses(
1519
1514
  db: Database.Database,
@@ -11,6 +11,7 @@
11
11
  import type Database from 'better-sqlite3';
12
12
  import { readFileSync, existsSync, readdirSync } from 'fs';
13
13
  import { join } from 'path';
14
+ import { parse as parseYaml } from 'yaml';
14
15
  import { addObservation } from './memory-db.ts';
15
16
 
16
17
  export type IngestResult = 'inserted' | 'updated' | 'skipped';
@@ -41,21 +42,13 @@ export function ingestMemoryFile(
41
42
 
42
43
  if (frontmatterMatch) {
43
44
  try {
44
- // Simple key: value parser for memory file frontmatter
45
- // (avoids importing yaml library — frontmatter is flat key-value pairs)
46
- const fm: Record<string, string> = {};
47
- for (const line of frontmatterMatch[1].split('\n')) {
48
- const sep = line.indexOf(':');
49
- if (sep > 0) {
50
- fm[line.slice(0, sep).trim()] = line.slice(sep + 1).trim();
51
- }
52
- }
53
- name = fm.name ?? basename;
54
- description = fm.description ?? '';
55
- type = fm.type ?? 'discovery';
45
+ const fm = parseYaml(frontmatterMatch[1]) as Record<string, unknown>;
46
+ name = (fm.name as string) ?? basename;
47
+ description = (fm.description as string) ?? '';
48
+ type = (fm.type as string) ?? 'discovery';
56
49
  confidence = fm.confidence != null ? Number(fm.confidence) : undefined;
57
50
  } catch {
58
- // Use defaults if frontmatter parsing fails
51
+ // Use defaults if YAML parsing fails
59
52
  }
60
53
  }
61
54
 
package/src/tools.ts CHANGED
@@ -39,7 +39,6 @@ import { getKnowledgeDb } from './knowledge-db.ts';
39
39
  import { getPythonToolDefinitions, isPythonTool, handlePythonToolCall } from './python-tools.ts';
40
40
  import { getConfig, getProjectRoot, getResolvedPaths } from './config.ts';
41
41
  import { getCurrentTier, getToolTier, isToolAllowed, annotateToolDefinitions, getLicenseToolDefinitions, isLicenseTool, handleLicenseToolCall } from './license.ts';
42
- import { getMcpBridgeToolDefinitions, isMcpBridgeTool, handleMcpBridgeToolCall } from './mcp-bridge-tools.ts';
43
42
 
44
43
  export interface ToolDefinition {
45
44
  name: string;
@@ -167,8 +166,6 @@ export function getToolDefinitions(): ToolDefinition[] {
167
166
  ...getKnowledgeToolDefinitions(),
168
167
  // Python code intelligence tools
169
168
  ...getPythonToolDefinitions(),
170
- // MCP bridge tools (cross-project tool mesh)
171
- ...getMcpBridgeToolDefinitions(),
172
169
  // License tools (always available)
173
170
  ...getLicenseToolDefinitions(),
174
171
  // Core tools
@@ -383,11 +380,6 @@ export async function handleToolCall(
383
380
  return handlePythonToolCall(name, args, dataDb);
384
381
  }
385
382
 
386
- // Route MCP bridge tools (cross-project tool mesh)
387
- if (isMcpBridgeTool(name)) {
388
- return await handleMcpBridgeToolCall(name, args);
389
- }
390
-
391
383
  // Route license tools
392
384
  if (isLicenseTool(name)) {
393
385
  const memDb = getMemoryDb();