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