@massu/core 1.10.8 → 1.12.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.
- package/CHANGELOG.md +1303 -0
- package/dist/adapter.js +6 -4
- package/dist/cli.js +517 -397
- package/dist/detect/adapters/.bundle-shasums.json +4 -7
- package/dist/detect/adapters/rails.js +6 -4
- package/dist/detect/adapters/spring.js +6 -4
- package/dist/detect/adapters/tree-sitter-loader.js +6 -4
- package/dist/hooks/auto-learning-pipeline.js +24 -1
- package/dist/hooks/classify-failure.js +32 -3
- package/dist/hooks/cost-tracker.js +24 -0
- package/dist/hooks/fix-detector.js +97 -1
- package/dist/hooks/incident-pipeline.js +30 -1
- package/dist/hooks/intent-suggester.js +6 -3
- package/dist/hooks/post-edit-context.js +24 -1
- package/dist/hooks/post-tool-use.js +66 -18
- package/dist/hooks/pre-compact.js +27 -2
- package/dist/hooks/pre-delete-check.js +48 -22
- package/dist/hooks/pre-tool-use-gate.js +962 -0
- package/dist/hooks/quality-event.js +24 -0
- package/dist/hooks/rule-enforcement-pipeline.js +24 -1
- package/dist/hooks/security-gate.js +29 -16
- package/dist/hooks/session-end.js +24 -0
- package/dist/hooks/session-start.js +8710 -8460
- package/dist/hooks/user-prompt.js +24 -0
- package/package.json +15 -18
- package/commands/README.md +0 -134
- package/src/adapter.ts +0 -121
- package/src/adr-generator.ts +0 -292
- package/src/analytics.ts +0 -373
- package/src/audit-trail.ts +0 -450
- package/src/backfill-sessions.ts +0 -182
- package/src/changelog-generator.ts +0 -178
- package/src/cli.ts +0 -246
- package/src/cloud-sync.ts +0 -234
- package/src/commands/adapters.ts +0 -824
- package/src/commands/changelog.ts +0 -165
- package/src/commands/config-check-drift.ts +0 -133
- package/src/commands/config-refresh.ts +0 -396
- package/src/commands/config-upgrade.ts +0 -127
- package/src/commands/doctor.ts +0 -485
- package/src/commands/hook-runner.ts +0 -145
- package/src/commands/init.ts +0 -1823
- package/src/commands/install-commands.ts +0 -692
- package/src/commands/install-hooks.ts +0 -27
- package/src/commands/permissions.ts +0 -150
- package/src/commands/refresh-log.ts +0 -37
- package/src/commands/show-template.ts +0 -65
- package/src/commands/template-engine.ts +0 -301
- package/src/commands/watch.ts +0 -430
- package/src/config.ts +0 -765
- package/src/cost-tracker.ts +0 -355
- package/src/db.ts +0 -370
- package/src/dependency-scorer.ts +0 -337
- package/src/detect/adapters/aspnet.ts +0 -4
- package/src/detect/adapters/discover.ts +0 -469
- package/src/detect/adapters/file-sampler.ts +0 -225
- package/src/detect/adapters/go-chi.ts +0 -4
- package/src/detect/adapters/index.ts +0 -51
- package/src/detect/adapters/nextjs-trpc.ts +0 -166
- package/src/detect/adapters/parse-guard.ts +0 -133
- package/src/detect/adapters/phoenix.ts +0 -4
- package/src/detect/adapters/python-django.ts +0 -208
- package/src/detect/adapters/python-fastapi.ts +0 -223
- package/src/detect/adapters/python-flask.ts +0 -235
- package/src/detect/adapters/query-helpers.ts +0 -170
- package/src/detect/adapters/rails.ts +0 -4
- package/src/detect/adapters/runner.ts +0 -284
- package/src/detect/adapters/spring.ts +0 -4
- package/src/detect/adapters/swift-swiftui.ts +0 -171
- package/src/detect/adapters/tree-sitter-loader.ts +0 -517
- package/src/detect/adapters/types.ts +0 -191
- package/src/detect/codebase-introspector.ts +0 -210
- package/src/detect/domain-inferrer.ts +0 -197
- package/src/detect/drift.ts +0 -199
- package/src/detect/framework-detector.ts +0 -307
- package/src/detect/index.ts +0 -209
- package/src/detect/manifest-registry.ts +0 -261
- package/src/detect/migrate.ts +0 -362
- package/src/detect/monorepo-detector.ts +0 -348
- package/src/detect/package-detector.ts +0 -828
- package/src/detect/passthrough.ts +0 -108
- package/src/detect/regex-fallback.ts +0 -449
- package/src/detect/source-dir-detector.ts +0 -297
- package/src/detect/vr-command-map.ts +0 -167
- package/src/docs-map.json +0 -100
- package/src/docs-tools.ts +0 -519
- package/src/domains.ts +0 -182
- package/src/hooks/auto-learning-pipeline.ts +0 -228
- package/src/hooks/classify-failure.ts +0 -259
- package/src/hooks/cost-tracker.ts +0 -66
- package/src/hooks/fix-detector.ts +0 -186
- package/src/hooks/incident-pipeline.ts +0 -190
- package/src/hooks/intent-suggester.ts +0 -131
- package/src/hooks/post-edit-context.ts +0 -91
- package/src/hooks/post-tool-use.ts +0 -413
- package/src/hooks/pre-compact.ts +0 -168
- package/src/hooks/pre-delete-check.ts +0 -243
- package/src/hooks/quality-event.ts +0 -127
- package/src/hooks/rule-enforcement-pipeline.ts +0 -159
- package/src/hooks/security-gate.ts +0 -153
- package/src/hooks/session-end.ts +0 -467
- package/src/hooks/session-start.ts +0 -436
- package/src/hooks/user-prompt.ts +0 -157
- package/src/import-resolver.ts +0 -226
- package/src/knowledge-db.ts +0 -176
- package/src/knowledge-indexer.ts +0 -708
- package/src/knowledge-tools.ts +0 -1414
- package/src/lib/fileLock.ts +0 -203
- package/src/lib/framework-supports.ts +0 -85
- package/src/lib/gitToplevel.ts +0 -22
- package/src/lib/hook-registry.ts +0 -43
- package/src/lib/installLock.ts +0 -66
- package/src/lib/memory-path.ts +0 -49
- package/src/lib/pidLiveness.ts +0 -67
- package/src/lib/settings-local.ts +0 -110
- package/src/lib/sql-table-names.ts +0 -71
- package/src/license.ts +0 -547
- package/src/lsp/auto-detect.ts +0 -98
- package/src/lsp/client.ts +0 -776
- package/src/lsp/enrich.ts +0 -127
- package/src/lsp/types.ts +0 -221
- package/src/memory-db.ts +0 -1614
- package/src/memory-file-ingest.ts +0 -128
- package/src/memory-tools.ts +0 -424
- package/src/middleware-tree.ts +0 -71
- package/src/observability-tools.ts +0 -343
- package/src/observation-extractor.ts +0 -418
- package/src/page-deps.ts +0 -285
- package/src/permissions.ts +0 -422
- package/src/prompt-analyzer.ts +0 -332
- package/src/python/coupling-detector.ts +0 -125
- package/src/python/domain-enforcer.ts +0 -84
- package/src/python/impact-analyzer.ts +0 -96
- package/src/python/import-parser.ts +0 -244
- package/src/python/import-resolver.ts +0 -136
- package/src/python/migration-indexer.ts +0 -116
- package/src/python/migration-parser.ts +0 -332
- package/src/python/model-indexer.ts +0 -71
- package/src/python/model-parser.ts +0 -279
- package/src/python/route-indexer.ts +0 -59
- package/src/python/route-parser.ts +0 -317
- package/src/python-tools.ts +0 -630
- package/src/regression-detector.ts +0 -319
- package/src/rules.ts +0 -57
- package/src/schema-mapper.ts +0 -232
- package/src/security/adapter-origin.ts +0 -130
- package/src/security/adapter-verifier.ts +0 -319
- package/src/security/atomic-write.ts +0 -164
- package/src/security/fetcher.ts +0 -200
- package/src/security/install-tracking.ts +0 -319
- package/src/security/license-pubkey.generated.ts +0 -30
- package/src/security/license-response-verifier.ts +0 -149
- package/src/security/local-fingerprint.ts +0 -225
- package/src/security/manifest-cache.ts +0 -333
- package/src/security/manifest-schema.ts +0 -129
- package/src/security/registry-pubkey.generated.ts +0 -35
- package/src/security/telemetry.ts +0 -320
- package/src/security-scorer.ts +0 -405
- package/src/security-utils.ts +0 -133
- package/src/sentinel-db.ts +0 -580
- package/src/sentinel-scanner.ts +0 -406
- package/src/sentinel-tools.ts +0 -513
- package/src/sentinel-types.ts +0 -140
- package/src/server-dispatch.ts +0 -225
- package/src/server.ts +0 -107
- package/src/session-archiver.ts +0 -111
- package/src/session-state-generator.ts +0 -174
- package/src/team-knowledge.ts +0 -407
- package/src/tool-db-needs.ts +0 -232
- package/src/tools.ts +0 -1206
- package/src/transcript-parser.ts +0 -458
- package/src/trpc-index.ts +0 -215
- package/src/validate-features-runner.ts +0 -107
- package/src/validation-engine.ts +0 -358
- package/src/watch/daemon.ts +0 -385
- package/src/watch/lockfile-detector.ts +0 -65
- package/src/watch/paths.ts +0 -279
- package/src/watch/state.ts +0 -178
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Copyright (c) 2026 Massu. All rights reserved.
|
|
3
|
-
// Licensed under BSL 1.1 - see LICENSE file for details.
|
|
4
|
-
|
|
5
|
-
// ============================================================
|
|
6
|
-
// PostToolUse Hook: Fix Detector
|
|
7
|
-
// Detects when a bug fix is being applied during a session by
|
|
8
|
-
// analyzing git diffs of edited files. Sets session-level state
|
|
9
|
-
// so the auto-learning pipeline can trigger at session end.
|
|
10
|
-
//
|
|
11
|
-
// Part of the Auto-Learning Pipeline:
|
|
12
|
-
// [Fix Detected] → Incident Report → Rule → Enforcement
|
|
13
|
-
//
|
|
14
|
-
// Must complete in <1000ms.
|
|
15
|
-
// ============================================================
|
|
16
|
-
|
|
17
|
-
import { execFileSync } from 'child_process';
|
|
18
|
-
import { existsSync, appendFileSync, mkdirSync, readFileSync } from 'fs';
|
|
19
|
-
import { tmpdir } from 'os';
|
|
20
|
-
import { join } from 'path';
|
|
21
|
-
import { getProjectRoot, getConfig } from '../config.ts';
|
|
22
|
-
|
|
23
|
-
interface HookInput {
|
|
24
|
-
session_id: string;
|
|
25
|
-
tool_name: string;
|
|
26
|
-
tool_input: { file_path?: string };
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
interface FixSignal {
|
|
30
|
-
file: string;
|
|
31
|
-
signals: string[];
|
|
32
|
-
timestamp: string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Fix detection heuristics — each returns true if the pattern matches
|
|
36
|
-
const FIX_HEURISTICS: Array<{ name: string; test: (diff: string) => boolean }> = [
|
|
37
|
-
{
|
|
38
|
-
name: 'removed_broken_code',
|
|
39
|
-
test: (diff) => /^-.*\b(bug|broken|wrong|incorrect|typo|crash|error|fail|miss|stale)\b/m.test(diff),
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
name: 'added_error_handling',
|
|
43
|
-
test: (diff) => {
|
|
44
|
-
const added = (diff.match(/^\+.*(try|except|catch|guard|if.*nil|if.*None|validate|assert|raise|throw)/gm) || []).length;
|
|
45
|
-
return added > 2;
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
name: 'method_name_correction',
|
|
50
|
-
test: (diff) => {
|
|
51
|
-
const removed = diff.match(/^-.*\.([a-z_]+)\(/m);
|
|
52
|
-
const added = diff.match(/^\+.*\.([a-z_]+)\(/m);
|
|
53
|
-
return !!(removed && added && removed[1] !== added[1]);
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
name: 'auth_fix',
|
|
58
|
-
test: (diff) => /^\+.*(token|auth|header|X-Service|Bearer|credential)/im.test(diff),
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
name: 'nil_handling_fix',
|
|
62
|
-
test: (diff) => /^\+.*(= nil|= None|\.isNil|is None|!= nil|is not None|guard let|if let|optional)/m.test(diff) && /^-/m.test(diff),
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
name: 'concurrency_fix',
|
|
66
|
-
test: (diff) => /^\+.*(timeout|semaphore|lock|mutex|throttle|rate.limit|max_conn)/im.test(diff),
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
name: 'async_pattern_fix',
|
|
70
|
-
test: (diff) => /^\+.*(@MainActor|async with|asyncio\.timeout|\.await)/.test(diff) && /^-/m.test(diff),
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
name: 'added_missing_import',
|
|
74
|
-
test: (diff) => /^\+.*(import|from.*import|require)/.test(diff) && !/^-.*(import|from.*import|require)/m.test(diff),
|
|
75
|
-
},
|
|
76
|
-
];
|
|
77
|
-
|
|
78
|
-
function getSessionFlagPath(sessionId: string): string {
|
|
79
|
-
const dir = join(tmpdir(), 'massu-auto-learning');
|
|
80
|
-
if (!existsSync(dir)) {
|
|
81
|
-
mkdirSync(dir, { recursive: true });
|
|
82
|
-
}
|
|
83
|
-
return join(dir, `fixes-${sessionId.slice(0, 12)}.jsonl`);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async function main(): Promise<void> {
|
|
87
|
-
try {
|
|
88
|
-
const input = await readStdin();
|
|
89
|
-
const hookInput = JSON.parse(input) as HookInput;
|
|
90
|
-
const filePath = hookInput.tool_input?.file_path;
|
|
91
|
-
|
|
92
|
-
if (!filePath || !existsSync(filePath)) {
|
|
93
|
-
process.exit(0);
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Only check code files
|
|
98
|
-
if (!/\.(py|swift|ts|tsx|js|jsx|rs|go|rb|sh)$/.test(filePath)) {
|
|
99
|
-
process.exit(0);
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Skip incident/memory/doc files (those ARE pipeline output)
|
|
104
|
-
const config = getConfig();
|
|
105
|
-
const incidentDir = config.autoLearning?.incidentDir ?? 'docs/incidents';
|
|
106
|
-
const memoryDir = config.autoLearning?.memoryDir ?? 'memory';
|
|
107
|
-
if (filePath.includes(incidentDir) || filePath.includes(memoryDir) || filePath.includes('MEMORY.md')) {
|
|
108
|
-
process.exit(0);
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Check if auto-learning is enabled
|
|
113
|
-
if (config.autoLearning?.enabled === false || config.autoLearning?.fixDetection?.enabled === false) {
|
|
114
|
-
process.exit(0);
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Get git diff for this file
|
|
119
|
-
const root = getProjectRoot();
|
|
120
|
-
let diff = '';
|
|
121
|
-
try {
|
|
122
|
-
diff = execFileSync('git', ['diff', '--', filePath], { cwd: root, timeout: 3000, encoding: 'utf-8' });
|
|
123
|
-
if (!diff) {
|
|
124
|
-
diff = execFileSync('git', ['diff', 'HEAD', '--', filePath], { cwd: root, timeout: 3000, encoding: 'utf-8' });
|
|
125
|
-
}
|
|
126
|
-
} catch {
|
|
127
|
-
process.exit(0);
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (!diff) {
|
|
132
|
-
process.exit(0);
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Run fix detection heuristics
|
|
137
|
-
const enabledSignals = new Set(config.autoLearning?.fixDetection?.signals ?? FIX_HEURISTICS.map(h => h.name));
|
|
138
|
-
const detected: string[] = [];
|
|
139
|
-
for (const heuristic of FIX_HEURISTICS) {
|
|
140
|
-
if (enabledSignals.has(heuristic.name) && heuristic.test(diff)) {
|
|
141
|
-
detected.push(heuristic.name);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (detected.length === 0) {
|
|
146
|
-
process.exit(0);
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Record the fix detection
|
|
151
|
-
const signal: FixSignal = {
|
|
152
|
-
file: filePath,
|
|
153
|
-
signals: detected,
|
|
154
|
-
timestamp: new Date().toISOString(),
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
const flagPath = getSessionFlagPath(hookInput.session_id);
|
|
158
|
-
appendFileSync(flagPath, JSON.stringify(signal) + '\n');
|
|
159
|
-
|
|
160
|
-
// Count total fixes this session
|
|
161
|
-
const lines = readFileSync(flagPath, 'utf-8').split('\n').filter(Boolean);
|
|
162
|
-
if (lines.length === 1) {
|
|
163
|
-
// First fix detected — output advisory
|
|
164
|
-
console.log(
|
|
165
|
-
`[Massu Auto-Learning] Bug fix detected in ${filePath} (signals: ${detected.join(', ')}). ` +
|
|
166
|
-
`The auto-learning pipeline will prompt you at session end to create an incident report, ` +
|
|
167
|
-
`derive a prevention rule, and add enforcement.`
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
} catch {
|
|
171
|
-
// Best-effort: never block Claude Code
|
|
172
|
-
}
|
|
173
|
-
process.exit(0);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function readStdin(): Promise<string> {
|
|
177
|
-
return new Promise((resolve) => {
|
|
178
|
-
let data = '';
|
|
179
|
-
process.stdin.setEncoding('utf-8');
|
|
180
|
-
process.stdin.on('data', (chunk: string) => { data += chunk; });
|
|
181
|
-
process.stdin.on('end', () => resolve(data));
|
|
182
|
-
setTimeout(() => resolve(data), 3000);
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
main();
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Copyright (c) 2026 Massu. All rights reserved.
|
|
3
|
-
// Licensed under BSL 1.1 - see LICENSE file for details.
|
|
4
|
-
|
|
5
|
-
// ============================================================
|
|
6
|
-
// PostToolUse Hook: Incident-to-Rule Pipeline
|
|
7
|
-
// When a new incident report is written, automatically triggers
|
|
8
|
-
// the next step: deriving a prevention rule.
|
|
9
|
-
//
|
|
10
|
-
// Part of the Auto-Learning Pipeline:
|
|
11
|
-
// Fix Detected → [Incident Report] → RULE DERIVATION → Enforcement
|
|
12
|
-
//
|
|
13
|
-
// Triggers on: Write to docs/incidents/*.md (configurable)
|
|
14
|
-
// Must complete in <500ms.
|
|
15
|
-
// ============================================================
|
|
16
|
-
|
|
17
|
-
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
18
|
-
import { basename, dirname, resolve } from 'path';
|
|
19
|
-
import { getProjectRoot, getConfig } from '../config.ts';
|
|
20
|
-
import { getMemoryDb, scoreFailureClasses, appendIncidentToFailureClass, addFailureClass } from '../memory-db.ts';
|
|
21
|
-
|
|
22
|
-
interface HookInput {
|
|
23
|
-
session_id: string;
|
|
24
|
-
tool_name: string;
|
|
25
|
-
tool_input: { file_path?: string; content?: string };
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async function main(): Promise<void> {
|
|
29
|
-
try {
|
|
30
|
-
const input = await readStdin();
|
|
31
|
-
const hookInput = JSON.parse(input) as HookInput;
|
|
32
|
-
const filePath = hookInput.tool_input?.file_path;
|
|
33
|
-
|
|
34
|
-
if (!filePath) {
|
|
35
|
-
process.exit(0);
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const config = getConfig();
|
|
40
|
-
if (config.autoLearning?.enabled === false) {
|
|
41
|
-
process.exit(0);
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const root = getProjectRoot();
|
|
46
|
-
const incidentDir = config.autoLearning?.incidentDir ?? 'docs/incidents';
|
|
47
|
-
const memoryDir = config.autoLearning?.memoryDir ?? 'memory';
|
|
48
|
-
// Only trigger on incident report files
|
|
49
|
-
const relPath = filePath.startsWith(root + '/') ? filePath.slice(root.length + 1) : filePath;
|
|
50
|
-
if (!relPath.startsWith(incidentDir) || !relPath.endsWith('.md')) {
|
|
51
|
-
process.exit(0);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (!existsSync(filePath)) {
|
|
56
|
-
process.exit(0);
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Extract title from the incident report
|
|
61
|
-
const content = readFileSync(filePath, 'utf-8');
|
|
62
|
-
const titleMatch = content.match(/^#\s+(.+)/m);
|
|
63
|
-
const title = titleMatch?.[1] ?? basename(filePath, '.md');
|
|
64
|
-
|
|
65
|
-
// Check if a corresponding rule already exists
|
|
66
|
-
const slug = basename(filePath, '.md')
|
|
67
|
-
.replace(/^\d{4}-\d{2}-\d{2}-?/, '')
|
|
68
|
-
.toLowerCase();
|
|
69
|
-
|
|
70
|
-
const memoryDirAbs = resolve(root, memoryDir);
|
|
71
|
-
let hasExistingRule = false;
|
|
72
|
-
if (existsSync(memoryDirAbs)) {
|
|
73
|
-
const ruleFiles = readdirSync(memoryDirAbs).filter(f => f.startsWith('feedback_'));
|
|
74
|
-
for (const ruleFile of ruleFiles) {
|
|
75
|
-
if (ruleFile.toLowerCase().includes(slug.slice(0, 20))) {
|
|
76
|
-
hasExistingRule = true;
|
|
77
|
-
break;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (hasExistingRule) {
|
|
83
|
-
// Rule already exists — no action needed
|
|
84
|
-
process.exit(0);
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Check if incident has prevention rules section
|
|
89
|
-
const hasPrevention = /## Prevention Rules|## Prevention|## Rules/i.test(content);
|
|
90
|
-
|
|
91
|
-
if (config.autoLearning?.pipeline?.requirePreventionRule === false) {
|
|
92
|
-
process.exit(0);
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Output rule derivation instructions
|
|
97
|
-
const lines: string[] = [];
|
|
98
|
-
lines.push('');
|
|
99
|
-
lines.push('============================================================================');
|
|
100
|
-
lines.push(' AUTO-LEARNING: Incident Report Created — Rule Derivation Required');
|
|
101
|
-
lines.push('============================================================================');
|
|
102
|
-
lines.push('');
|
|
103
|
-
lines.push(` Incident: ${title}`);
|
|
104
|
-
lines.push(` File: ${filePath}`);
|
|
105
|
-
lines.push('');
|
|
106
|
-
|
|
107
|
-
if (!hasPrevention) {
|
|
108
|
-
lines.push(' No "## Prevention Rules" section found in the incident report.');
|
|
109
|
-
lines.push(' Add one first, then proceed with rule derivation.');
|
|
110
|
-
lines.push('');
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
lines.push(' DERIVE A PREVENTION RULE:');
|
|
114
|
-
lines.push(` a) Read the incident root cause and prevention rules`);
|
|
115
|
-
lines.push(` b) Create: ${memoryDir}/feedback_<rule_name>.md`);
|
|
116
|
-
lines.push(' Template:');
|
|
117
|
-
lines.push(' ---');
|
|
118
|
-
lines.push(' name: <Rule Name>');
|
|
119
|
-
lines.push(' description: <one-line description>');
|
|
120
|
-
lines.push(' type: feedback');
|
|
121
|
-
lines.push(' ---');
|
|
122
|
-
lines.push(' <Rule statement>');
|
|
123
|
-
lines.push(' **Why:** <Root cause from incident>');
|
|
124
|
-
lines.push(' **How to apply:** <Concrete steps>');
|
|
125
|
-
lines.push('');
|
|
126
|
-
lines.push(` c) Add one-line entry to ${config.autoLearning?.memoryIndexFile ?? 'MEMORY.md'}`);
|
|
127
|
-
lines.push('');
|
|
128
|
-
lines.push(' This step is MANDATORY per the auto-learning pipeline.');
|
|
129
|
-
lines.push('============================================================================');
|
|
130
|
-
lines.push('');
|
|
131
|
-
|
|
132
|
-
console.log(lines.join('\n'));
|
|
133
|
-
|
|
134
|
-
// ============================================================
|
|
135
|
-
// Taxonomy Update: Score incident against failure classes
|
|
136
|
-
// If KNOWN match → append incident to existing class
|
|
137
|
-
// If NEW → create stub entry with needs_review=true
|
|
138
|
-
// ============================================================
|
|
139
|
-
try {
|
|
140
|
-
const taxonomyConfig = config.autoLearning?.failureClassification;
|
|
141
|
-
if (taxonomyConfig?.enabled !== false) {
|
|
142
|
-
const db = getMemoryDb();
|
|
143
|
-
try {
|
|
144
|
-
const thresholds = taxonomyConfig?.thresholds ?? { known: 5, similar: 3 };
|
|
145
|
-
const scoringWeights = taxonomyConfig?.scoring;
|
|
146
|
-
|
|
147
|
-
// Extract incident number from filename (e.g., "incident-042.md" → "42")
|
|
148
|
-
const incidentNumMatch = basename(filePath, '.md').match(/(\d+)/);
|
|
149
|
-
const incidentId = incidentNumMatch ? incidentNumMatch[1] : basename(filePath, '.md');
|
|
150
|
-
|
|
151
|
-
// Score incident content against taxonomy
|
|
152
|
-
const bestMatch = scoreFailureClasses(db, content, filePath, title, scoringWeights);
|
|
153
|
-
|
|
154
|
-
if (bestMatch && bestMatch.score >= thresholds.similar) {
|
|
155
|
-
// Known or similar — append incident to existing class
|
|
156
|
-
appendIncidentToFailureClass(db, bestMatch.name, incidentId);
|
|
157
|
-
} else {
|
|
158
|
-
// New pattern — create stub entry for review
|
|
159
|
-
const stubName = `auto_${slug.replace(/[^a-z0-9_]/g, '_').slice(0, 50)}`;
|
|
160
|
-
addFailureClass(db, {
|
|
161
|
-
name: stubName,
|
|
162
|
-
description: `Auto-created from incident ${incidentId}: ${title.slice(0, 100)}`,
|
|
163
|
-
incidents: [incidentId],
|
|
164
|
-
needsReview: true,
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
} finally {
|
|
168
|
-
db.close();
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
} catch {
|
|
172
|
-
// Best-effort: taxonomy update failure must not block the pipeline
|
|
173
|
-
}
|
|
174
|
-
} catch {
|
|
175
|
-
// Best-effort: never block Claude Code
|
|
176
|
-
}
|
|
177
|
-
process.exit(0);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function readStdin(): Promise<string> {
|
|
181
|
-
return new Promise((resolve) => {
|
|
182
|
-
let data = '';
|
|
183
|
-
process.stdin.setEncoding('utf-8');
|
|
184
|
-
process.stdin.on('data', (chunk: string) => { data += chunk; });
|
|
185
|
-
process.stdin.on('end', () => resolve(data));
|
|
186
|
-
setTimeout(() => resolve(data), 3000);
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
main();
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Copyright (c) 2026 Massu. All rights reserved.
|
|
3
|
-
// Licensed under BSL 1.1 - see LICENSE file for details.
|
|
4
|
-
|
|
5
|
-
// ============================================================
|
|
6
|
-
// UserPromptSubmit Hook: Intent Suggester
|
|
7
|
-
// Matches prompt keywords to relevant slash commands and
|
|
8
|
-
// surfaces suggestions to the user as a non-blocking hint.
|
|
9
|
-
// ============================================================
|
|
10
|
-
|
|
11
|
-
// Force module mode for TypeScript (no external deps needed)
|
|
12
|
-
export {};
|
|
13
|
-
|
|
14
|
-
interface HookInput {
|
|
15
|
-
session_id: string;
|
|
16
|
-
transcript_path: string;
|
|
17
|
-
cwd: string;
|
|
18
|
-
hook_event_name: string;
|
|
19
|
-
prompt: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
interface CommandMapping {
|
|
23
|
-
keywords: string[];
|
|
24
|
-
command: string;
|
|
25
|
-
description: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const COMMAND_MAPPINGS: CommandMapping[] = [
|
|
29
|
-
{
|
|
30
|
-
keywords: ['test', 'failing'],
|
|
31
|
-
command: '/massu-test',
|
|
32
|
-
description: 'Run and analyze tests',
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
keywords: ['debug', 'bug'],
|
|
36
|
-
command: '/massu-debug',
|
|
37
|
-
description: 'Debug an issue',
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
keywords: ['refactor'],
|
|
41
|
-
command: '/massu-refactor',
|
|
42
|
-
description: 'Guided refactoring workflow',
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
keywords: ['cleanup', 'dead code', 'unused'],
|
|
46
|
-
command: '/massu-cleanup',
|
|
47
|
-
description: 'Clean up dead code and unused exports',
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
keywords: ['document', 'jsdoc', 'readme'],
|
|
51
|
-
command: '/massu-doc-gen',
|
|
52
|
-
description: 'Generate documentation',
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
keywords: ['estimate', 'effort', 'how long'],
|
|
56
|
-
command: '/massu-estimate',
|
|
57
|
-
description: 'Estimate implementation effort',
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
keywords: ['release', 'deploy'],
|
|
61
|
-
command: '/massu-release',
|
|
62
|
-
description: 'Prepare a release',
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
keywords: ['commit'],
|
|
66
|
-
command: '/massu-commit',
|
|
67
|
-
description: 'Pre-commit verification gate',
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
keywords: ['push'],
|
|
71
|
-
command: '/massu-push',
|
|
72
|
-
description: 'Pre-push full verification gate',
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
keywords: ['plan'],
|
|
76
|
-
command: '/massu-create-plan',
|
|
77
|
-
description: 'Create an implementation plan',
|
|
78
|
-
},
|
|
79
|
-
];
|
|
80
|
-
|
|
81
|
-
function findMatchingCommand(prompt: string): CommandMapping | null {
|
|
82
|
-
const lowerPrompt = prompt.toLowerCase();
|
|
83
|
-
|
|
84
|
-
for (const mapping of COMMAND_MAPPINGS) {
|
|
85
|
-
for (const keyword of mapping.keywords) {
|
|
86
|
-
if (lowerPrompt.includes(keyword.toLowerCase())) {
|
|
87
|
-
return mapping;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
async function main(): Promise<void> {
|
|
96
|
-
try {
|
|
97
|
-
const input = await readStdin();
|
|
98
|
-
const hookInput = JSON.parse(input) as HookInput;
|
|
99
|
-
const { prompt } = hookInput;
|
|
100
|
-
|
|
101
|
-
if (!prompt || !prompt.trim()) {
|
|
102
|
-
process.exit(0);
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const match = findMatchingCommand(prompt);
|
|
107
|
-
if (!match) {
|
|
108
|
-
process.exit(0);
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
process.stdout.write(
|
|
113
|
-
`Tip: Use ${match.command} to ${match.description}.`
|
|
114
|
-
);
|
|
115
|
-
} catch (_e) {
|
|
116
|
-
// Best-effort: never block Claude Code
|
|
117
|
-
}
|
|
118
|
-
process.exit(0);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function readStdin(): Promise<string> {
|
|
122
|
-
return new Promise((resolve) => {
|
|
123
|
-
let data = '';
|
|
124
|
-
process.stdin.setEncoding('utf-8');
|
|
125
|
-
process.stdin.on('data', (chunk: string) => { data += chunk; });
|
|
126
|
-
process.stdin.on('end', () => resolve(data));
|
|
127
|
-
setTimeout(() => resolve(data), 3000);
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
main();
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Copyright (c) 2026 Massu. All rights reserved.
|
|
3
|
-
// Licensed under BSL 1.1 - see LICENSE file for details.
|
|
4
|
-
|
|
5
|
-
// ============================================================
|
|
6
|
-
// PostToolUse Context Hook
|
|
7
|
-
// Surfaces applicable CLAUDE.md rules and warnings when editing
|
|
8
|
-
// src/ files. Uses matchRules() and isInMiddlewareTree() from
|
|
9
|
-
// the codegraph index - no MCP server HTTP call needed.
|
|
10
|
-
// Must complete in <500ms.
|
|
11
|
-
// ============================================================
|
|
12
|
-
|
|
13
|
-
import Database from 'better-sqlite3';
|
|
14
|
-
import { matchRules } from '../rules.ts';
|
|
15
|
-
import { isInMiddlewareTree } from '../middleware-tree.ts';
|
|
16
|
-
import { getResolvedPaths, getProjectRoot } from '../config.ts';
|
|
17
|
-
|
|
18
|
-
interface HookInput {
|
|
19
|
-
session_id: string;
|
|
20
|
-
tool_name: string;
|
|
21
|
-
tool_input: { file_path?: string };
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
async function main(): Promise<void> {
|
|
25
|
-
try {
|
|
26
|
-
const input = await readStdin();
|
|
27
|
-
const hookInput = JSON.parse(input) as HookInput;
|
|
28
|
-
const filePath = hookInput.tool_input?.file_path;
|
|
29
|
-
|
|
30
|
-
if (!filePath) {
|
|
31
|
-
process.exit(0);
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Convert absolute path to relative
|
|
36
|
-
const root = getProjectRoot();
|
|
37
|
-
const rel = filePath.startsWith(root + '/') ? filePath.slice(root.length + 1) : filePath;
|
|
38
|
-
|
|
39
|
-
// Only process src/ files
|
|
40
|
-
if (!rel.startsWith('src/') && !rel.endsWith('.py')) {
|
|
41
|
-
process.exit(0);
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const warnings: string[] = [];
|
|
46
|
-
|
|
47
|
-
// 1. Check applicable rules (uses rules.ts PATTERN_RULES)
|
|
48
|
-
const rules = matchRules(rel);
|
|
49
|
-
for (const rule of rules) {
|
|
50
|
-
if (rule.severity === 'CRITICAL' || rule.severity === 'HIGH') {
|
|
51
|
-
for (const r of rule.rules) {
|
|
52
|
-
warnings.push(`[${rule.severity}] ${r}`);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// 2. Check middleware tree membership
|
|
58
|
-
try {
|
|
59
|
-
const dataDb = new Database(getResolvedPaths().dataDbPath, { readonly: true });
|
|
60
|
-
try {
|
|
61
|
-
if (isInMiddlewareTree(dataDb, rel)) {
|
|
62
|
-
warnings.push('[CRITICAL] This file is in the middleware import tree. No Node.js deps allowed.');
|
|
63
|
-
}
|
|
64
|
-
} finally {
|
|
65
|
-
dataDb.close();
|
|
66
|
-
}
|
|
67
|
-
} catch (_e) {
|
|
68
|
-
// DB may not exist yet - skip middleware check
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// 3. Output warnings if any
|
|
72
|
-
if (warnings.length > 0) {
|
|
73
|
-
console.log(`[Massu] ${warnings.join(' | ')}`);
|
|
74
|
-
}
|
|
75
|
-
} catch (_e) {
|
|
76
|
-
// Best-effort: never block Claude Code
|
|
77
|
-
}
|
|
78
|
-
process.exit(0);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function readStdin(): Promise<string> {
|
|
82
|
-
return new Promise((resolve) => {
|
|
83
|
-
let data = '';
|
|
84
|
-
process.stdin.setEncoding('utf-8');
|
|
85
|
-
process.stdin.on('data', (chunk: string) => { data += chunk; });
|
|
86
|
-
process.stdin.on('end', () => resolve(data));
|
|
87
|
-
setTimeout(() => resolve(data), 3000);
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
main();
|