@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.
- package/commands/_shared-preamble.md +76 -0
- package/commands/massu-audit-deps.md +211 -0
- package/commands/massu-changelog.md +174 -0
- package/commands/massu-cleanup.md +315 -0
- package/commands/massu-commit.md +481 -0
- package/commands/massu-create-plan.md +752 -0
- package/commands/massu-dead-code.md +131 -0
- package/commands/massu-debug.md +484 -0
- package/commands/massu-deploy.md +91 -0
- package/commands/massu-deps.md +374 -0
- package/commands/massu-doc-gen.md +279 -0
- package/commands/massu-docs.md +364 -0
- package/commands/massu-estimate.md +313 -0
- package/commands/massu-golden-path.md +973 -0
- package/commands/massu-guide.md +167 -0
- package/commands/massu-hotfix.md +480 -0
- package/commands/massu-loop-playwright.md +837 -0
- package/commands/massu-loop.md +775 -0
- package/commands/massu-new-feature.md +511 -0
- package/commands/massu-parity.md +214 -0
- package/commands/massu-plan.md +456 -0
- package/commands/massu-push-light.md +207 -0
- package/commands/massu-push.md +434 -0
- package/commands/massu-refactor.md +410 -0
- package/commands/massu-release.md +363 -0
- package/commands/massu-review.md +238 -0
- package/commands/massu-simplify.md +281 -0
- package/commands/massu-status.md +278 -0
- package/commands/massu-tdd.md +201 -0
- package/commands/massu-test.md +516 -0
- package/commands/massu-verify-playwright.md +281 -0
- package/commands/massu-verify.md +667 -0
- package/dist/cli.js +7772 -3140
- package/dist/hooks/cost-tracker.js +103 -40
- package/dist/hooks/post-edit-context.js +74 -8
- package/dist/hooks/post-tool-use.js +268 -106
- package/dist/hooks/pre-compact.js +167 -43
- package/dist/hooks/pre-delete-check.js +159 -42
- package/dist/hooks/quality-event.js +103 -40
- package/dist/hooks/security-gate.js +29 -0
- package/dist/hooks/session-end.js +143 -84
- package/dist/hooks/session-start.js +186 -49
- package/dist/hooks/user-prompt.js +189 -43
- package/package.json +10 -15
- package/src/adr-generator.ts +9 -2
- package/src/analytics.ts +9 -3
- package/src/audit-trail.ts +10 -3
- package/src/backfill-sessions.ts +5 -4
- package/src/cli.ts +6 -0
- package/src/cloud-sync.ts +14 -18
- package/src/commands/doctor.ts +193 -6
- package/src/commands/init.ts +230 -5
- package/src/commands/install-commands.ts +137 -0
- package/src/config.ts +68 -2
- package/src/cost-tracker.ts +11 -6
- package/src/db.ts +115 -2
- package/src/dependency-scorer.ts +9 -2
- package/src/docs-tools.ts +21 -16
- package/src/hooks/post-edit-context.ts +4 -4
- package/src/hooks/post-tool-use.ts +130 -0
- package/src/hooks/pre-compact.ts +23 -1
- package/src/hooks/pre-delete-check.ts +92 -4
- package/src/hooks/security-gate.ts +32 -0
- package/src/hooks/session-end.ts +3 -3
- package/src/hooks/session-start.ts +99 -6
- package/src/hooks/user-prompt.ts +46 -1
- package/src/import-resolver.ts +2 -1
- package/src/knowledge-db.ts +169 -0
- package/src/knowledge-indexer.ts +704 -0
- package/src/knowledge-tools.ts +1413 -0
- package/src/license.ts +482 -0
- package/src/memory-db.ts +1364 -23
- package/src/memory-tools.ts +14 -15
- package/src/observability-tools.ts +13 -2
- package/src/observation-extractor.ts +11 -4
- package/src/page-deps.ts +3 -2
- package/src/prompt-analyzer.ts +9 -2
- package/src/python/coupling-detector.ts +124 -0
- package/src/python/domain-enforcer.ts +83 -0
- package/src/python/impact-analyzer.ts +95 -0
- package/src/python/import-parser.ts +244 -0
- package/src/python/import-resolver.ts +135 -0
- package/src/python/migration-indexer.ts +115 -0
- package/src/python/migration-parser.ts +332 -0
- package/src/python/model-indexer.ts +70 -0
- package/src/python/model-parser.ts +279 -0
- package/src/python/route-indexer.ts +58 -0
- package/src/python/route-parser.ts +317 -0
- package/src/python-tools.ts +629 -0
- package/src/regression-detector.ts +9 -3
- package/src/security-scorer.ts +9 -2
- package/src/sentinel-db.ts +45 -89
- package/src/sentinel-tools.ts +8 -11
- package/src/server.ts +29 -7
- package/src/session-archiver.ts +4 -5
- package/src/team-knowledge.ts +9 -2
- package/src/tools.ts +1032 -44
- package/src/validate-features-runner.ts +0 -1
- package/src/validation-engine.ts +9 -2
- package/README.md +0 -40
- package/dist/server.js +0 -7008
- package/src/__tests__/adr-generator.test.ts +0 -260
- package/src/__tests__/analytics.test.ts +0 -282
- package/src/__tests__/audit-trail.test.ts +0 -382
- package/src/__tests__/backfill-sessions.test.ts +0 -690
- package/src/__tests__/cli.test.ts +0 -290
- package/src/__tests__/cloud-sync.test.ts +0 -261
- package/src/__tests__/config-sections.test.ts +0 -359
- package/src/__tests__/config.test.ts +0 -732
- package/src/__tests__/cost-tracker.test.ts +0 -348
- package/src/__tests__/db.test.ts +0 -177
- package/src/__tests__/dependency-scorer.test.ts +0 -325
- package/src/__tests__/docs-integration.test.ts +0 -178
- package/src/__tests__/docs-tools.test.ts +0 -199
- package/src/__tests__/domains.test.ts +0 -236
- package/src/__tests__/hooks.test.ts +0 -221
- package/src/__tests__/import-resolver.test.ts +0 -95
- package/src/__tests__/integration/path-traversal.test.ts +0 -134
- package/src/__tests__/integration/pricing-consistency.test.ts +0 -88
- package/src/__tests__/integration/tool-registration.test.ts +0 -146
- package/src/__tests__/memory-db.test.ts +0 -404
- package/src/__tests__/memory-enhancements.test.ts +0 -316
- package/src/__tests__/memory-tools.test.ts +0 -199
- package/src/__tests__/middleware-tree.test.ts +0 -177
- package/src/__tests__/observability-tools.test.ts +0 -595
- package/src/__tests__/observability.test.ts +0 -437
- package/src/__tests__/observation-extractor.test.ts +0 -167
- package/src/__tests__/page-deps.test.ts +0 -60
- package/src/__tests__/prompt-analyzer.test.ts +0 -298
- package/src/__tests__/regression-detector.test.ts +0 -295
- package/src/__tests__/rules.test.ts +0 -87
- package/src/__tests__/schema-mapper.test.ts +0 -29
- package/src/__tests__/security-scorer.test.ts +0 -238
- package/src/__tests__/security-utils.test.ts +0 -175
- package/src/__tests__/sentinel-db.test.ts +0 -491
- package/src/__tests__/sentinel-scanner.test.ts +0 -750
- package/src/__tests__/sentinel-tools.test.ts +0 -324
- package/src/__tests__/sentinel-types.test.ts +0 -750
- package/src/__tests__/server.test.ts +0 -452
- package/src/__tests__/session-archiver.test.ts +0 -524
- package/src/__tests__/session-state-generator.test.ts +0 -900
- package/src/__tests__/team-knowledge.test.ts +0 -327
- package/src/__tests__/tools.test.ts +0 -340
- package/src/__tests__/transcript-parser.test.ts +0 -195
- package/src/__tests__/trpc-index.test.ts +0 -25
- package/src/__tests__/validate-features-runner.test.ts +0 -517
- package/src/__tests__/validation-engine.test.ts +0 -300
- package/src/core-tools.ts +0 -685
- package/src/memory-queries.ts +0 -804
- package/src/memory-schema.ts +0 -546
- 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, "
|
|
264
|
-
claudeMdPath: resolve(root, "
|
|
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-
|
|
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 '
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 '
|
|
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 '
|
|
588
|
+
project TEXT NOT NULL DEFAULT 'my-project',
|
|
517
589
|
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
518
590
|
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
519
591
|
cache_read_tokens INTEGER NOT NULL DEFAULT 0,
|
|
@@ -740,10 +812,16 @@ function initMemorySchema(db) {
|
|
|
740
812
|
);
|
|
741
813
|
CREATE INDEX IF NOT EXISTS idx_pending_sync_created ON pending_sync(created_at ASC);
|
|
742
814
|
`);
|
|
815
|
+
db.exec(`
|
|
816
|
+
CREATE TABLE IF NOT EXISTS license_cache (
|
|
817
|
+
api_key_hash TEXT PRIMARY KEY,
|
|
818
|
+
tier TEXT NOT NULL,
|
|
819
|
+
valid_until TEXT NOT NULL,
|
|
820
|
+
last_validated TEXT NOT NULL,
|
|
821
|
+
features TEXT DEFAULT '[]'
|
|
822
|
+
);
|
|
823
|
+
`);
|
|
743
824
|
}
|
|
744
|
-
|
|
745
|
-
// src/memory-queries.ts
|
|
746
|
-
import { basename } from "path";
|
|
747
825
|
function assignImportance(type, vrResult) {
|
|
748
826
|
switch (type) {
|
|
749
827
|
case "decision":
|
|
@@ -841,27 +919,9 @@ function deduplicateFailedAttempt(db, sessionId, title, detail, opts) {
|
|
|
841
919
|
});
|
|
842
920
|
}
|
|
843
921
|
|
|
844
|
-
// src/memory-db.ts
|
|
845
|
-
var initializedPaths = /* @__PURE__ */ new Set();
|
|
846
|
-
function getMemoryDb() {
|
|
847
|
-
const dbPath = getResolvedPaths().memoryDbPath;
|
|
848
|
-
const dir = dirname2(dbPath);
|
|
849
|
-
if (!existsSync2(dir)) {
|
|
850
|
-
mkdirSync(dir, { recursive: true });
|
|
851
|
-
}
|
|
852
|
-
const db = new Database(dbPath);
|
|
853
|
-
db.pragma("journal_mode = WAL");
|
|
854
|
-
db.pragma("foreign_keys = ON");
|
|
855
|
-
if (!initializedPaths.has(dbPath)) {
|
|
856
|
-
initMemorySchema(db);
|
|
857
|
-
initializedPaths.add(dbPath);
|
|
858
|
-
}
|
|
859
|
-
return db;
|
|
860
|
-
}
|
|
861
|
-
|
|
862
922
|
// src/transcript-parser.ts
|
|
863
|
-
function estimateTokens(
|
|
864
|
-
return Math.ceil(
|
|
923
|
+
function estimateTokens(text) {
|
|
924
|
+
return Math.ceil(text.length / 4);
|
|
865
925
|
}
|
|
866
926
|
|
|
867
927
|
// src/adr-generator.ts
|
|
@@ -869,13 +929,14 @@ var DEFAULT_DETECTION_PHRASES = ["chose", "decided", "switching to", "moving fro
|
|
|
869
929
|
function getDetectionPhrases() {
|
|
870
930
|
return getConfig().governance?.adr?.detection_phrases ?? DEFAULT_DETECTION_PHRASES;
|
|
871
931
|
}
|
|
872
|
-
function detectDecisionPatterns(
|
|
932
|
+
function detectDecisionPatterns(text) {
|
|
873
933
|
const phrases = getDetectionPhrases();
|
|
874
|
-
const lower =
|
|
934
|
+
const lower = text.toLowerCase();
|
|
875
935
|
return phrases.some((phrase) => lower.includes(phrase));
|
|
876
936
|
}
|
|
877
937
|
|
|
878
938
|
// src/observation-extractor.ts
|
|
939
|
+
import { homedir as homedir2 } from "os";
|
|
879
940
|
var PRIVATE_PATTERNS = [
|
|
880
941
|
/\/Users\/\w+/,
|
|
881
942
|
// Absolute macOS paths
|
|
@@ -895,9 +956,9 @@ var PRIVATE_PATTERNS = [
|
|
|
895
956
|
// Stripe keys
|
|
896
957
|
];
|
|
897
958
|
function classifyVisibility(title, detail) {
|
|
898
|
-
const
|
|
959
|
+
const text = `${title} ${detail ?? ""}`;
|
|
899
960
|
for (const pattern of PRIVATE_PATTERNS) {
|
|
900
|
-
if (pattern.test(
|
|
961
|
+
if (pattern.test(text)) return "private";
|
|
901
962
|
}
|
|
902
963
|
return "public";
|
|
903
964
|
}
|
|
@@ -954,7 +1015,9 @@ function classifyToolCall(tc) {
|
|
|
954
1015
|
}
|
|
955
1016
|
case "Read": {
|
|
956
1017
|
const filePath = tc.input.file_path ?? "unknown";
|
|
957
|
-
|
|
1018
|
+
const knowledgeSourceFiles = getConfig().conventions?.knowledgeSourceFiles ?? ["CLAUDE.md", "MEMORY.md", "corrections.md"];
|
|
1019
|
+
const plansDir = getResolvedPaths().plansDir;
|
|
1020
|
+
if (filePath.includes(plansDir) || knowledgeSourceFiles.some((f) => filePath.includes(f))) {
|
|
958
1021
|
const title = `Read: ${shortenPath(filePath)}`;
|
|
959
1022
|
return {
|
|
960
1023
|
type: "discovery",
|
|
@@ -1042,13 +1105,13 @@ function classifyToolCall(tc) {
|
|
|
1042
1105
|
return null;
|
|
1043
1106
|
}
|
|
1044
1107
|
}
|
|
1045
|
-
function extractLinkedReferences(
|
|
1108
|
+
function extractLinkedReferences(text) {
|
|
1046
1109
|
const result = {};
|
|
1047
|
-
const crMatch =
|
|
1110
|
+
const crMatch = text.match(/CR-(\d+)/);
|
|
1048
1111
|
if (crMatch) result.crRule = `CR-${crMatch[1]}`;
|
|
1049
|
-
const vrMatch =
|
|
1112
|
+
const vrMatch = text.match(/VR-([A-Z_]+)/);
|
|
1050
1113
|
if (vrMatch) result.vrType = `VR-${vrMatch[1]}`;
|
|
1051
|
-
const planMatch =
|
|
1114
|
+
const planMatch = text.match(/P(\d+)-(\d+)/);
|
|
1052
1115
|
if (planMatch) result.planItem = `P${planMatch[1]}-${planMatch[2]}`;
|
|
1053
1116
|
return result;
|
|
1054
1117
|
}
|
|
@@ -1064,7 +1127,11 @@ function shortenPath(filePath) {
|
|
|
1064
1127
|
if (filePath.startsWith(root + "/")) {
|
|
1065
1128
|
return filePath.slice(root.length + 1);
|
|
1066
1129
|
}
|
|
1067
|
-
|
|
1130
|
+
const home = homedir2();
|
|
1131
|
+
if (filePath.startsWith(home + "/")) {
|
|
1132
|
+
return "~/" + filePath.slice(home.length + 1);
|
|
1133
|
+
}
|
|
1134
|
+
return filePath;
|
|
1068
1135
|
}
|
|
1069
1136
|
function classifyRealTimeToolCall(toolName, toolInput, toolResponse, seenReads2) {
|
|
1070
1137
|
const tc = {
|
|
@@ -1175,53 +1242,7 @@ function trackModification(db, featureKey) {
|
|
|
1175
1242
|
|
|
1176
1243
|
// src/import-resolver.ts
|
|
1177
1244
|
import { readFileSync as readFileSync2, existsSync as existsSync3, statSync } from "fs";
|
|
1178
|
-
import { resolve as
|
|
1179
|
-
function resolveImportPath(specifier, fromFile) {
|
|
1180
|
-
if (!specifier.startsWith(".") && !specifier.startsWith("@/")) {
|
|
1181
|
-
return null;
|
|
1182
|
-
}
|
|
1183
|
-
let basePath;
|
|
1184
|
-
if (specifier.startsWith("@/")) {
|
|
1185
|
-
const paths = getResolvedPaths();
|
|
1186
|
-
basePath = resolve2(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
|
|
1187
|
-
} else {
|
|
1188
|
-
basePath = resolve2(dirname3(fromFile), specifier);
|
|
1189
|
-
}
|
|
1190
|
-
if (existsSync3(basePath) && !isDirectory(basePath)) {
|
|
1191
|
-
return toRelative(basePath);
|
|
1192
|
-
}
|
|
1193
|
-
const resolvedPaths = getResolvedPaths();
|
|
1194
|
-
for (const ext of resolvedPaths.extensions) {
|
|
1195
|
-
const withExt = basePath + ext;
|
|
1196
|
-
if (existsSync3(withExt)) {
|
|
1197
|
-
return toRelative(withExt);
|
|
1198
|
-
}
|
|
1199
|
-
}
|
|
1200
|
-
for (const indexFile of resolvedPaths.indexFiles) {
|
|
1201
|
-
const indexPath = join(basePath, indexFile);
|
|
1202
|
-
if (existsSync3(indexPath)) {
|
|
1203
|
-
return toRelative(indexPath);
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
return null;
|
|
1207
|
-
}
|
|
1208
|
-
function isDirectory(path) {
|
|
1209
|
-
try {
|
|
1210
|
-
return statSync(path).isDirectory();
|
|
1211
|
-
} catch {
|
|
1212
|
-
return false;
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
function toRelative(absPath) {
|
|
1216
|
-
const root = getProjectRoot();
|
|
1217
|
-
if (absPath.startsWith(root)) {
|
|
1218
|
-
return absPath.slice(root.length + 1);
|
|
1219
|
-
}
|
|
1220
|
-
return absPath;
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
// src/validation-engine.ts
|
|
1224
|
-
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
1245
|
+
import { resolve as resolve4, dirname as dirname3, join } from "path";
|
|
1225
1246
|
|
|
1226
1247
|
// src/security-utils.ts
|
|
1227
1248
|
import { resolve as resolve3, normalize } from "path";
|
|
@@ -1269,7 +1290,53 @@ function enforceSeverityFloors(configWeights, defaults) {
|
|
|
1269
1290
|
return result;
|
|
1270
1291
|
}
|
|
1271
1292
|
|
|
1293
|
+
// src/import-resolver.ts
|
|
1294
|
+
function resolveImportPath(specifier, fromFile) {
|
|
1295
|
+
if (!specifier.startsWith(".") && !specifier.startsWith("@/")) {
|
|
1296
|
+
return null;
|
|
1297
|
+
}
|
|
1298
|
+
let basePath;
|
|
1299
|
+
if (specifier.startsWith("@/")) {
|
|
1300
|
+
const paths = getResolvedPaths();
|
|
1301
|
+
basePath = resolve4(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
|
|
1302
|
+
} else {
|
|
1303
|
+
basePath = resolve4(dirname3(fromFile), specifier);
|
|
1304
|
+
}
|
|
1305
|
+
if (existsSync3(basePath) && !isDirectory(basePath)) {
|
|
1306
|
+
return toRelative(basePath);
|
|
1307
|
+
}
|
|
1308
|
+
const resolvedPaths = getResolvedPaths();
|
|
1309
|
+
for (const ext of resolvedPaths.extensions) {
|
|
1310
|
+
const withExt = basePath + ext;
|
|
1311
|
+
if (existsSync3(withExt)) {
|
|
1312
|
+
return toRelative(withExt);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
for (const indexFile of resolvedPaths.indexFiles) {
|
|
1316
|
+
const indexPath = join(basePath, indexFile);
|
|
1317
|
+
if (existsSync3(indexPath)) {
|
|
1318
|
+
return toRelative(indexPath);
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
return null;
|
|
1322
|
+
}
|
|
1323
|
+
function isDirectory(path) {
|
|
1324
|
+
try {
|
|
1325
|
+
return statSync(path).isDirectory();
|
|
1326
|
+
} catch {
|
|
1327
|
+
return false;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
function toRelative(absPath) {
|
|
1331
|
+
const root = getProjectRoot();
|
|
1332
|
+
if (absPath.startsWith(root)) {
|
|
1333
|
+
return absPath.slice(root.length + 1);
|
|
1334
|
+
}
|
|
1335
|
+
return absPath;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1272
1338
|
// src/validation-engine.ts
|
|
1339
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
1273
1340
|
function getValidationChecks() {
|
|
1274
1341
|
return getConfig().governance?.validation?.checks ?? {
|
|
1275
1342
|
rule_compliance: true,
|
|
@@ -1539,6 +1606,9 @@ function storeSecurityScore(db, sessionId, filePath, riskScore, findings) {
|
|
|
1539
1606
|
}
|
|
1540
1607
|
|
|
1541
1608
|
// src/hooks/post-tool-use.ts
|
|
1609
|
+
import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
|
|
1610
|
+
import { join as join2 } from "path";
|
|
1611
|
+
import { parse as parseYaml2 } from "yaml";
|
|
1542
1612
|
var seenReads = /* @__PURE__ */ new Set();
|
|
1543
1613
|
var currentSessionId = null;
|
|
1544
1614
|
async function main() {
|
|
@@ -1616,6 +1686,41 @@ async function main() {
|
|
|
1616
1686
|
}
|
|
1617
1687
|
} catch (_securityErr) {
|
|
1618
1688
|
}
|
|
1689
|
+
try {
|
|
1690
|
+
if (tool_name === "Edit" || tool_name === "Write") {
|
|
1691
|
+
const filePath = tool_input.file_path ?? "";
|
|
1692
|
+
if (filePath && filePath.endsWith("MEMORY.md") && filePath.includes("/memory/")) {
|
|
1693
|
+
const issues = checkMemoryFileIntegrity(filePath);
|
|
1694
|
+
if (issues.length > 0) {
|
|
1695
|
+
addObservation(
|
|
1696
|
+
db,
|
|
1697
|
+
session_id,
|
|
1698
|
+
"incident_near_miss",
|
|
1699
|
+
"MEMORY.md integrity issue detected",
|
|
1700
|
+
issues.join("; "),
|
|
1701
|
+
{ importance: 4 }
|
|
1702
|
+
);
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
} catch (_memoryErr) {
|
|
1707
|
+
}
|
|
1708
|
+
try {
|
|
1709
|
+
if (tool_name === "Edit" || tool_name === "Write") {
|
|
1710
|
+
const filePath = tool_input.file_path ?? "";
|
|
1711
|
+
if (filePath && isKnowledgeSourceFile(filePath)) {
|
|
1712
|
+
addObservation(
|
|
1713
|
+
db,
|
|
1714
|
+
session_id,
|
|
1715
|
+
"discovery",
|
|
1716
|
+
"Knowledge source file modified - index may be stale",
|
|
1717
|
+
`Edited ${filePath.split("/").pop() ?? filePath}. Run knowledge re-index to update.`,
|
|
1718
|
+
{ importance: 3 }
|
|
1719
|
+
);
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
} catch (_knowledgeErr) {
|
|
1723
|
+
}
|
|
1619
1724
|
} finally {
|
|
1620
1725
|
db.close();
|
|
1621
1726
|
}
|
|
@@ -1630,29 +1735,86 @@ function updatePlanProgress(db, sessionId, progress) {
|
|
|
1630
1735
|
if (existing) {
|
|
1631
1736
|
try {
|
|
1632
1737
|
const currentProgress = JSON.parse(existing.plan_progress);
|
|
1633
|
-
for (const
|
|
1634
|
-
currentProgress[
|
|
1738
|
+
for (const p of progress) {
|
|
1739
|
+
currentProgress[p.planItem] = p.status;
|
|
1635
1740
|
}
|
|
1636
1741
|
db.prepare("UPDATE session_summaries SET plan_progress = ? WHERE id = ?").run(JSON.stringify(currentProgress), existing.id);
|
|
1637
1742
|
} catch (_e) {
|
|
1638
1743
|
}
|
|
1639
1744
|
} else {
|
|
1640
1745
|
const progressMap = {};
|
|
1641
|
-
for (const
|
|
1642
|
-
progressMap[
|
|
1746
|
+
for (const p of progress) {
|
|
1747
|
+
progressMap[p.planItem] = p.status;
|
|
1643
1748
|
}
|
|
1644
1749
|
addSummary(db, sessionId, { planProgress: progressMap });
|
|
1645
1750
|
}
|
|
1646
1751
|
}
|
|
1647
1752
|
function readStdin() {
|
|
1648
|
-
return new Promise((
|
|
1753
|
+
return new Promise((resolve5) => {
|
|
1649
1754
|
let data = "";
|
|
1650
1755
|
process.stdin.setEncoding("utf-8");
|
|
1651
1756
|
process.stdin.on("data", (chunk) => {
|
|
1652
1757
|
data += chunk;
|
|
1653
1758
|
});
|
|
1654
|
-
process.stdin.on("end", () =>
|
|
1655
|
-
setTimeout(() =>
|
|
1759
|
+
process.stdin.on("end", () => resolve5(data));
|
|
1760
|
+
setTimeout(() => resolve5(data), 3e3);
|
|
1656
1761
|
});
|
|
1657
1762
|
}
|
|
1763
|
+
function readConventions(cwd) {
|
|
1764
|
+
const defaults = {
|
|
1765
|
+
knowledgeSourceFiles: ["CLAUDE.md", "MEMORY.md", "corrections.md"],
|
|
1766
|
+
claudeDirName: ".claude"
|
|
1767
|
+
};
|
|
1768
|
+
try {
|
|
1769
|
+
const projectRoot = cwd ?? process.cwd();
|
|
1770
|
+
const configPath = join2(projectRoot, "massu.config.yaml");
|
|
1771
|
+
if (!existsSync6(configPath)) return defaults;
|
|
1772
|
+
const content = readFileSync5(configPath, "utf-8");
|
|
1773
|
+
const parsed = parseYaml2(content);
|
|
1774
|
+
if (!parsed || typeof parsed !== "object") return defaults;
|
|
1775
|
+
const conventions = parsed.conventions;
|
|
1776
|
+
if (!conventions || typeof conventions !== "object") return defaults;
|
|
1777
|
+
return {
|
|
1778
|
+
knowledgeSourceFiles: Array.isArray(conventions.knowledgeSourceFiles) ? conventions.knowledgeSourceFiles : defaults.knowledgeSourceFiles,
|
|
1779
|
+
claudeDirName: typeof conventions.claudeDirName === "string" ? conventions.claudeDirName : defaults.claudeDirName
|
|
1780
|
+
};
|
|
1781
|
+
} catch {
|
|
1782
|
+
return defaults;
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
function isKnowledgeSourceFile(filePath) {
|
|
1786
|
+
const basename2 = filePath.split("/").pop() ?? "";
|
|
1787
|
+
const conventions = readConventions();
|
|
1788
|
+
const knowledgeSourcePatterns = [
|
|
1789
|
+
...conventions.knowledgeSourceFiles,
|
|
1790
|
+
"file-index.md",
|
|
1791
|
+
"knowledge-db.ts",
|
|
1792
|
+
"knowledge-indexer.ts",
|
|
1793
|
+
"knowledge-tools.ts"
|
|
1794
|
+
];
|
|
1795
|
+
return knowledgeSourcePatterns.some((p) => basename2 === p) || filePath.includes("/memory/") || filePath.includes(conventions.claudeDirName + "/");
|
|
1796
|
+
}
|
|
1797
|
+
function checkMemoryFileIntegrity(filePath) {
|
|
1798
|
+
const issues = [];
|
|
1799
|
+
try {
|
|
1800
|
+
if (!existsSync6(filePath)) {
|
|
1801
|
+
issues.push("MEMORY.md file does not exist after write");
|
|
1802
|
+
return issues;
|
|
1803
|
+
}
|
|
1804
|
+
const content = readFileSync5(filePath, "utf-8");
|
|
1805
|
+
const lines = content.split("\n");
|
|
1806
|
+
const MAX_LINES = 200;
|
|
1807
|
+
if (lines.length > MAX_LINES) {
|
|
1808
|
+
issues.push(`MEMORY.md exceeds ${MAX_LINES} lines (currently ${lines.length}). Consider archiving old entries.`);
|
|
1809
|
+
}
|
|
1810
|
+
const requiredSections = ["# Massu Memory", "## Key Learnings", "## Common Gotchas"];
|
|
1811
|
+
for (const section of requiredSections) {
|
|
1812
|
+
if (!content.includes(section)) {
|
|
1813
|
+
issues.push(`Missing required section: "${section}"`);
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
} catch (_e) {
|
|
1817
|
+
}
|
|
1818
|
+
return issues;
|
|
1819
|
+
}
|
|
1658
1820
|
main();
|