@massu/core 0.4.2 → 0.6.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/README.md +40 -0
- package/agents/massu-architecture-reviewer.md +104 -0
- package/agents/massu-blast-radius-analyzer.md +84 -0
- package/agents/massu-competitive-scorer.md +126 -0
- package/agents/massu-help-sync.md +73 -0
- package/agents/massu-migration-writer.md +94 -0
- package/agents/massu-output-scorer.md +87 -0
- package/agents/massu-pattern-reviewer.md +84 -0
- package/agents/massu-plan-auditor.md +170 -0
- package/agents/massu-schema-sync-verifier.md +70 -0
- package/agents/massu-security-reviewer.md +98 -0
- package/agents/massu-ux-reviewer.md +106 -0
- package/commands/_shared-preamble.md +53 -23
- package/commands/_shared-references/auto-learning-protocol.md +71 -0
- package/commands/_shared-references/blast-radius-protocol.md +76 -0
- package/commands/_shared-references/security-pre-screen.md +64 -0
- package/commands/_shared-references/test-first-protocol.md +87 -0
- package/commands/_shared-references/verification-table.md +52 -0
- package/commands/massu-article-review.md +343 -0
- package/commands/massu-autoresearch/references/eval-runner.md +84 -0
- package/commands/massu-autoresearch/references/safety-rails.md +125 -0
- package/commands/massu-autoresearch/references/scoring-protocol.md +151 -0
- package/commands/massu-autoresearch.md +258 -0
- package/commands/massu-batch.md +44 -12
- package/commands/massu-bearings.md +42 -8
- package/commands/massu-checkpoint.md +588 -0
- package/commands/massu-ci-fix.md +2 -2
- package/commands/massu-command-health.md +132 -0
- package/commands/massu-command-improve.md +232 -0
- package/commands/massu-commit.md +205 -44
- package/commands/massu-create-plan.md +239 -57
- package/commands/massu-data/references/common-queries.md +79 -0
- package/commands/massu-data/references/table-guide.md +50 -0
- package/commands/massu-data.md +66 -0
- package/commands/massu-dead-code.md +29 -34
- package/commands/massu-debug/references/auto-learning.md +61 -0
- package/commands/massu-debug/references/codegraph-tracing.md +80 -0
- package/commands/massu-debug/references/common-shortcuts.md +98 -0
- package/commands/massu-debug/references/investigation-phases.md +294 -0
- package/commands/massu-debug/references/report-format.md +107 -0
- package/commands/massu-debug.md +105 -386
- package/commands/massu-docs.md +1 -1
- package/commands/massu-full-audit.md +61 -0
- package/commands/massu-gap-enhancement-analyzer.md +276 -16
- package/commands/massu-golden-path/references/approval-points.md +216 -0
- package/commands/massu-golden-path/references/competitive-mode.md +273 -0
- package/commands/massu-golden-path/references/error-handling.md +121 -0
- package/commands/massu-golden-path/references/phase-0-requirements.md +53 -0
- package/commands/massu-golden-path/references/phase-1-plan-creation.md +168 -0
- package/commands/massu-golden-path/references/phase-2-implementation.md +397 -0
- package/commands/massu-golden-path/references/phase-2.5-gap-analyzer.md +156 -0
- package/commands/massu-golden-path/references/phase-3-simplify.md +40 -0
- package/commands/massu-golden-path/references/phase-4-commit.md +94 -0
- package/commands/massu-golden-path/references/phase-5-push.md +116 -0
- package/commands/massu-golden-path/references/phase-5.5-production-verify.md +170 -0
- package/commands/massu-golden-path/references/phase-6-completion.md +113 -0
- package/commands/massu-golden-path/references/qa-evaluator-spec.md +137 -0
- package/commands/massu-golden-path/references/sprint-contract-protocol.md +117 -0
- package/commands/massu-golden-path/references/vr-visual-calibration.md +73 -0
- package/commands/massu-golden-path.md +114 -848
- package/commands/massu-guide.md +72 -69
- package/commands/massu-hooks.md +27 -12
- package/commands/massu-hotfix.md +221 -144
- package/commands/massu-incident.md +49 -20
- package/commands/massu-infra-audit.md +187 -0
- package/commands/massu-learning-audit.md +211 -0
- package/commands/massu-loop/references/auto-learning.md +49 -0
- package/commands/massu-loop/references/checkpoint-audit.md +40 -0
- package/commands/massu-loop/references/guardrails.md +17 -0
- package/commands/massu-loop/references/iteration-structure.md +115 -0
- package/commands/massu-loop/references/loop-controller.md +188 -0
- package/commands/massu-loop/references/plan-extraction.md +78 -0
- package/commands/massu-loop/references/vr-plan-spec.md +140 -0
- package/commands/massu-loop-playwright.md +9 -9
- package/commands/massu-loop.md +115 -670
- package/commands/massu-new-pattern.md +423 -0
- package/commands/massu-perf.md +422 -0
- package/commands/massu-plan-audit.md +1 -1
- package/commands/massu-plan.md +389 -122
- package/commands/massu-production-verify.md +433 -0
- package/commands/massu-push.md +62 -378
- package/commands/massu-recap.md +29 -3
- package/commands/massu-rollback.md +613 -0
- package/commands/massu-scaffold-hook.md +2 -4
- package/commands/massu-scaffold-page.md +2 -3
- package/commands/massu-scaffold-router.md +1 -2
- package/commands/massu-security.md +619 -0
- package/commands/massu-simplify.md +115 -85
- package/commands/massu-squirrels.md +2 -2
- package/commands/massu-tdd.md +38 -22
- package/commands/massu-test.md +3 -3
- package/commands/massu-type-mismatch-audit.md +469 -0
- package/commands/massu-ui-audit.md +587 -0
- package/commands/massu-verify-playwright.md +287 -32
- package/commands/massu-verify.md +150 -46
- package/dist/cli.js +1451 -1047
- package/dist/hooks/post-tool-use.js +75 -6
- package/dist/hooks/user-prompt.js +16 -0
- package/package.json +6 -2
- package/patterns/build-patterns.md +302 -0
- package/patterns/component-patterns.md +246 -0
- package/patterns/display-patterns.md +185 -0
- package/patterns/form-patterns.md +890 -0
- package/patterns/integration-testing-checklist.md +445 -0
- package/patterns/security-patterns.md +219 -0
- package/patterns/testing-patterns.md +569 -0
- package/patterns/tool-routing.md +81 -0
- package/patterns/ui-patterns.md +371 -0
- package/protocols/plan-implementation.md +267 -0
- package/protocols/recovery.md +225 -0
- package/protocols/verification.md +404 -0
- package/reference/command-taxonomy.md +178 -0
- package/reference/cr-rules-reference.md +76 -0
- package/reference/hook-execution-order.md +148 -0
- package/reference/lessons-learned.md +175 -0
- package/reference/patterns-quickref.md +208 -0
- package/reference/standards.md +135 -0
- package/reference/subagents-reference.md +17 -0
- package/reference/vr-verification-reference.md +867 -0
- package/src/commands/init.ts +27 -0
- package/src/commands/install-commands.ts +149 -53
- package/src/hooks/post-tool-use.ts +17 -0
- package/src/hooks/user-prompt.ts +21 -0
- package/src/memory-file-ingest.ts +127 -0
- package/src/memory-tools.ts +34 -1
package/dist/cli.js
CHANGED
|
@@ -344,687 +344,188 @@ var init_config = __esm({
|
|
|
344
344
|
}
|
|
345
345
|
});
|
|
346
346
|
|
|
347
|
-
// src/
|
|
348
|
-
var
|
|
349
|
-
__export(
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
347
|
+
// src/memory-db.ts
|
|
348
|
+
var memory_db_exports = {};
|
|
349
|
+
__export(memory_db_exports, {
|
|
350
|
+
addConversationTurn: () => addConversationTurn,
|
|
351
|
+
addObservation: () => addObservation,
|
|
352
|
+
addSummary: () => addSummary,
|
|
353
|
+
addToolCallDetail: () => addToolCallDetail,
|
|
354
|
+
addUserPrompt: () => addUserPrompt,
|
|
355
|
+
assignImportance: () => assignImportance,
|
|
356
|
+
autoDetectTaskId: () => autoDetectTaskId,
|
|
357
|
+
createSession: () => createSession,
|
|
358
|
+
deduplicateFailedAttempt: () => deduplicateFailedAttempt,
|
|
359
|
+
dequeuePendingSync: () => dequeuePendingSync,
|
|
360
|
+
endSession: () => endSession,
|
|
361
|
+
enqueueSyncPayload: () => enqueueSyncPayload,
|
|
362
|
+
getConversationTurns: () => getConversationTurns,
|
|
363
|
+
getCrossTaskProgress: () => getCrossTaskProgress,
|
|
364
|
+
getDecisionsAbout: () => getDecisionsAbout,
|
|
365
|
+
getFailedAttempts: () => getFailedAttempts,
|
|
366
|
+
getLastProcessedLine: () => getLastProcessedLine,
|
|
367
|
+
getMemoryDb: () => getMemoryDb,
|
|
368
|
+
getObservabilityDbSize: () => getObservabilityDbSize,
|
|
369
|
+
getRecentObservations: () => getRecentObservations,
|
|
370
|
+
getSessionStats: () => getSessionStats,
|
|
371
|
+
getSessionSummaries: () => getSessionSummaries,
|
|
372
|
+
getSessionTimeline: () => getSessionTimeline,
|
|
373
|
+
getSessionsByTask: () => getSessionsByTask,
|
|
374
|
+
getToolPatterns: () => getToolPatterns,
|
|
375
|
+
incrementRetryCount: () => incrementRetryCount,
|
|
376
|
+
initMemorySchema: () => initMemorySchema,
|
|
377
|
+
linkSessionToTask: () => linkSessionToTask,
|
|
378
|
+
pruneOldConversationTurns: () => pruneOldConversationTurns,
|
|
379
|
+
pruneOldObservations: () => pruneOldObservations,
|
|
380
|
+
removePendingSync: () => removePendingSync,
|
|
381
|
+
sanitizeFts5Query: () => sanitizeFts5Query,
|
|
382
|
+
searchConversationTurns: () => searchConversationTurns,
|
|
383
|
+
searchObservations: () => searchObservations,
|
|
384
|
+
setLastProcessedLine: () => setLastProcessedLine
|
|
353
385
|
});
|
|
354
|
-
import
|
|
355
|
-
import {
|
|
356
|
-
import {
|
|
357
|
-
function
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
}
|
|
363
|
-
const distRelPath = resolve2(__dirname, "../commands");
|
|
364
|
-
if (existsSync2(distRelPath)) {
|
|
365
|
-
return distRelPath;
|
|
366
|
-
}
|
|
367
|
-
const srcRelPath = resolve2(__dirname, "../../commands");
|
|
368
|
-
if (existsSync2(srcRelPath)) {
|
|
369
|
-
return srcRelPath;
|
|
370
|
-
}
|
|
371
|
-
return null;
|
|
372
|
-
}
|
|
373
|
-
function installCommands(projectRoot) {
|
|
374
|
-
const claudeDirName = getConfig().conventions?.claudeDirName ?? ".claude";
|
|
375
|
-
const targetDir = resolve2(projectRoot, claudeDirName, "commands");
|
|
376
|
-
if (!existsSync2(targetDir)) {
|
|
377
|
-
mkdirSync(targetDir, { recursive: true });
|
|
378
|
-
}
|
|
379
|
-
const sourceDir = resolveCommandsDir();
|
|
380
|
-
if (!sourceDir) {
|
|
381
|
-
console.error(" ERROR: Could not find massu commands directory.");
|
|
382
|
-
console.error(" Try reinstalling: npm install @massu/core");
|
|
383
|
-
return { installed: 0, updated: 0, skipped: 0, commandsDir: targetDir };
|
|
384
|
-
}
|
|
385
|
-
const sourceFiles = readdirSync(sourceDir).filter((f) => f.endsWith(".md"));
|
|
386
|
-
let installed = 0;
|
|
387
|
-
let updated = 0;
|
|
388
|
-
let skipped = 0;
|
|
389
|
-
for (const file of sourceFiles) {
|
|
390
|
-
const sourcePath = resolve2(sourceDir, file);
|
|
391
|
-
const targetPath = resolve2(targetDir, file);
|
|
392
|
-
const sourceContent = readFileSync2(sourcePath, "utf-8");
|
|
393
|
-
if (existsSync2(targetPath)) {
|
|
394
|
-
const existingContent = readFileSync2(targetPath, "utf-8");
|
|
395
|
-
if (existingContent === sourceContent) {
|
|
396
|
-
skipped++;
|
|
397
|
-
continue;
|
|
398
|
-
}
|
|
399
|
-
writeFileSync(targetPath, sourceContent, "utf-8");
|
|
400
|
-
updated++;
|
|
401
|
-
} else {
|
|
402
|
-
writeFileSync(targetPath, sourceContent, "utf-8");
|
|
403
|
-
installed++;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
return { installed, updated, skipped, commandsDir: targetDir };
|
|
386
|
+
import Database from "better-sqlite3";
|
|
387
|
+
import { dirname as dirname2, basename } from "path";
|
|
388
|
+
import { existsSync as existsSync2, mkdirSync } from "fs";
|
|
389
|
+
function sanitizeFts5Query(raw) {
|
|
390
|
+
const trimmed = raw.trim();
|
|
391
|
+
if (!trimmed) return '""';
|
|
392
|
+
const tokens = trimmed.replace(/"/g, "").split(/\s+/).filter(Boolean);
|
|
393
|
+
return tokens.map((t) => `"${t}"`).join(" ");
|
|
407
394
|
}
|
|
408
|
-
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
console.log("");
|
|
414
|
-
const result = installCommands(projectRoot);
|
|
415
|
-
if (result.installed > 0) {
|
|
416
|
-
console.log(` Installed ${result.installed} new commands`);
|
|
417
|
-
}
|
|
418
|
-
if (result.updated > 0) {
|
|
419
|
-
console.log(` Updated ${result.updated} existing commands`);
|
|
420
|
-
}
|
|
421
|
-
if (result.skipped > 0) {
|
|
422
|
-
console.log(` ${result.skipped} commands already up to date`);
|
|
395
|
+
function getMemoryDb() {
|
|
396
|
+
const dbPath = getResolvedPaths().memoryDbPath;
|
|
397
|
+
const dir = dirname2(dbPath);
|
|
398
|
+
if (!existsSync2(dir)) {
|
|
399
|
+
mkdirSync(dir, { recursive: true });
|
|
423
400
|
}
|
|
424
|
-
const
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
console.log("");
|
|
401
|
+
const db = new Database(dbPath);
|
|
402
|
+
db.pragma("journal_mode = WAL");
|
|
403
|
+
db.pragma("foreign_keys = ON");
|
|
404
|
+
initMemorySchema(db);
|
|
405
|
+
return db;
|
|
430
406
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
407
|
+
function initMemorySchema(db) {
|
|
408
|
+
db.exec(`
|
|
409
|
+
-- Sessions table (linked to Claude Code session IDs)
|
|
410
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
411
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
412
|
+
session_id TEXT UNIQUE NOT NULL,
|
|
413
|
+
project TEXT NOT NULL DEFAULT 'my-project',
|
|
414
|
+
git_branch TEXT,
|
|
415
|
+
started_at TEXT NOT NULL,
|
|
416
|
+
started_at_epoch INTEGER NOT NULL,
|
|
417
|
+
ended_at TEXT,
|
|
418
|
+
ended_at_epoch INTEGER,
|
|
419
|
+
status TEXT CHECK(status IN ('active', 'completed', 'abandoned')) NOT NULL DEFAULT 'active',
|
|
420
|
+
plan_file TEXT,
|
|
421
|
+
plan_phase TEXT,
|
|
422
|
+
task_id TEXT
|
|
423
|
+
);
|
|
440
424
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
425
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_session_id ON sessions(session_id);
|
|
426
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_started ON sessions(started_at_epoch DESC);
|
|
427
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_task_id ON sessions(task_id);
|
|
428
|
+
|
|
429
|
+
-- Observations table (structured knowledge from tool usage)
|
|
430
|
+
CREATE TABLE IF NOT EXISTS observations (
|
|
431
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
432
|
+
session_id TEXT NOT NULL,
|
|
433
|
+
type TEXT NOT NULL CHECK(type IN (
|
|
434
|
+
'decision', 'bugfix', 'feature', 'refactor', 'discovery',
|
|
435
|
+
'cr_violation', 'vr_check', 'pattern_compliance', 'failed_attempt',
|
|
436
|
+
'file_change', 'incident_near_miss'
|
|
437
|
+
)),
|
|
438
|
+
title TEXT NOT NULL,
|
|
439
|
+
detail TEXT,
|
|
440
|
+
files_involved TEXT DEFAULT '[]',
|
|
441
|
+
plan_item TEXT,
|
|
442
|
+
cr_rule TEXT,
|
|
443
|
+
vr_type TEXT,
|
|
444
|
+
evidence TEXT,
|
|
445
|
+
importance INTEGER NOT NULL DEFAULT 3 CHECK(importance BETWEEN 1 AND 5),
|
|
446
|
+
recurrence_count INTEGER NOT NULL DEFAULT 1,
|
|
447
|
+
original_tokens INTEGER DEFAULT 0,
|
|
448
|
+
created_at TEXT NOT NULL,
|
|
449
|
+
created_at_epoch INTEGER NOT NULL,
|
|
450
|
+
FOREIGN KEY(session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
|
|
451
|
+
);
|
|
452
|
+
|
|
453
|
+
CREATE INDEX IF NOT EXISTS idx_observations_session ON observations(session_id);
|
|
454
|
+
CREATE INDEX IF NOT EXISTS idx_observations_type ON observations(type);
|
|
455
|
+
CREATE INDEX IF NOT EXISTS idx_observations_created ON observations(created_at_epoch DESC);
|
|
456
|
+
CREATE INDEX IF NOT EXISTS idx_observations_plan_item ON observations(plan_item);
|
|
457
|
+
CREATE INDEX IF NOT EXISTS idx_observations_cr_rule ON observations(cr_rule);
|
|
458
|
+
CREATE INDEX IF NOT EXISTS idx_observations_importance ON observations(importance DESC);
|
|
459
|
+
`);
|
|
468
460
|
try {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
else if (allDeps["nuxt"]) result.ui = "nuxt";
|
|
478
|
-
else if (allDeps["@angular/core"]) result.ui = "angular";
|
|
479
|
-
else if (allDeps["vue"]) result.ui = "vue";
|
|
480
|
-
else if (allDeps["react"]) result.ui = "react";
|
|
481
|
-
if (allDeps["@trpc/server"]) result.router = "trpc";
|
|
482
|
-
else if (allDeps["graphql"] || allDeps["@apollo/server"]) result.router = "graphql";
|
|
483
|
-
else if (allDeps["express"] || allDeps["fastify"] || allDeps["hono"]) result.router = "rest";
|
|
484
|
-
if (allDeps["@prisma/client"] || allDeps["prisma"]) result.orm = "prisma";
|
|
485
|
-
else if (allDeps["drizzle-orm"]) result.orm = "drizzle";
|
|
486
|
-
else if (allDeps["typeorm"]) result.orm = "typeorm";
|
|
487
|
-
else if (allDeps["sequelize"]) result.orm = "sequelize";
|
|
488
|
-
else if (allDeps["mongoose"]) result.orm = "mongoose";
|
|
489
|
-
} catch {
|
|
461
|
+
db.exec(`
|
|
462
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS observations_fts USING fts5(
|
|
463
|
+
title, detail, evidence,
|
|
464
|
+
content='observations',
|
|
465
|
+
content_rowid='id'
|
|
466
|
+
);
|
|
467
|
+
`);
|
|
468
|
+
} catch (_e) {
|
|
490
469
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
}
|
|
551
|
-
if (!result.root) {
|
|
552
|
-
result.root = ".";
|
|
553
|
-
}
|
|
554
|
-
return result;
|
|
555
|
-
}
|
|
556
|
-
function parsePyprojectDeps(content) {
|
|
557
|
-
const deps = [];
|
|
558
|
-
const lower = content.toLowerCase();
|
|
559
|
-
if (lower.includes("fastapi")) deps.push("fastapi");
|
|
560
|
-
if (lower.includes("sqlalchemy")) deps.push("sqlalchemy");
|
|
561
|
-
return deps;
|
|
562
|
-
}
|
|
563
|
-
function parseRequirementsDeps(content) {
|
|
564
|
-
const deps = [];
|
|
565
|
-
const lower = content.toLowerCase();
|
|
566
|
-
for (const line of lower.split("\n")) {
|
|
567
|
-
const trimmed = line.trim();
|
|
568
|
-
if (trimmed.startsWith("fastapi")) deps.push("fastapi");
|
|
569
|
-
if (trimmed.startsWith("sqlalchemy")) deps.push("sqlalchemy");
|
|
570
|
-
}
|
|
571
|
-
return deps;
|
|
572
|
-
}
|
|
573
|
-
function parseSetupPyDeps(content) {
|
|
574
|
-
const deps = [];
|
|
575
|
-
const lower = content.toLowerCase();
|
|
576
|
-
if (lower.includes("fastapi")) deps.push("fastapi");
|
|
577
|
-
if (lower.includes("sqlalchemy")) deps.push("sqlalchemy");
|
|
578
|
-
return deps;
|
|
579
|
-
}
|
|
580
|
-
function parsePipfileDeps(content) {
|
|
581
|
-
const deps = [];
|
|
582
|
-
const lower = content.toLowerCase();
|
|
583
|
-
if (lower.includes("fastapi")) deps.push("fastapi");
|
|
584
|
-
if (lower.includes("sqlalchemy")) deps.push("sqlalchemy");
|
|
585
|
-
return deps;
|
|
586
|
-
}
|
|
587
|
-
function generateConfig(projectRoot, framework) {
|
|
588
|
-
const configPath = resolve3(projectRoot, "massu.config.yaml");
|
|
589
|
-
if (existsSync3(configPath)) {
|
|
590
|
-
return false;
|
|
591
|
-
}
|
|
592
|
-
const projectName = basename(projectRoot);
|
|
593
|
-
const config = {
|
|
594
|
-
project: {
|
|
595
|
-
name: projectName,
|
|
596
|
-
root: "auto"
|
|
597
|
-
},
|
|
598
|
-
framework: {
|
|
599
|
-
type: framework.type,
|
|
600
|
-
router: framework.router,
|
|
601
|
-
orm: framework.orm,
|
|
602
|
-
ui: framework.ui
|
|
603
|
-
},
|
|
604
|
-
paths: {
|
|
605
|
-
source: "src",
|
|
606
|
-
aliases: { "@": "src" }
|
|
607
|
-
},
|
|
608
|
-
toolPrefix: "massu",
|
|
609
|
-
domains: [],
|
|
610
|
-
rules: [
|
|
611
|
-
{
|
|
612
|
-
pattern: "src/**/*.ts",
|
|
613
|
-
rules: ["Use ESM imports, not CommonJS"]
|
|
614
|
-
}
|
|
615
|
-
]
|
|
616
|
-
};
|
|
617
|
-
const python = detectPython(projectRoot);
|
|
618
|
-
if (python.detected) {
|
|
619
|
-
const pythonConfig = {
|
|
620
|
-
root: python.root,
|
|
621
|
-
exclude_dirs: ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]
|
|
622
|
-
};
|
|
623
|
-
if (python.hasFastapi) pythonConfig.framework = "fastapi";
|
|
624
|
-
if (python.hasSqlalchemy) pythonConfig.orm = "sqlalchemy";
|
|
625
|
-
if (python.hasAlembic && python.alembicDir) {
|
|
626
|
-
pythonConfig.alembic_dir = python.alembicDir;
|
|
627
|
-
}
|
|
628
|
-
config.python = pythonConfig;
|
|
629
|
-
}
|
|
630
|
-
const yamlContent = `# Massu AI Configuration
|
|
631
|
-
# Generated by: npx massu init
|
|
632
|
-
# Documentation: https://massu.ai/docs/getting-started/configuration
|
|
633
|
-
|
|
634
|
-
${yamlStringify(config)}`;
|
|
635
|
-
writeFileSync2(configPath, yamlContent, "utf-8");
|
|
636
|
-
return true;
|
|
637
|
-
}
|
|
638
|
-
function registerMcpServer(projectRoot) {
|
|
639
|
-
const mcpPath = resolve3(projectRoot, ".mcp.json");
|
|
640
|
-
let existing = {};
|
|
641
|
-
if (existsSync3(mcpPath)) {
|
|
642
|
-
try {
|
|
643
|
-
existing = JSON.parse(readFileSync3(mcpPath, "utf-8"));
|
|
644
|
-
} catch {
|
|
645
|
-
existing = {};
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
const servers = existing.mcpServers ?? {};
|
|
649
|
-
if (servers.massu) {
|
|
650
|
-
return false;
|
|
651
|
-
}
|
|
652
|
-
servers.massu = {
|
|
653
|
-
type: "stdio",
|
|
654
|
-
command: "npx",
|
|
655
|
-
args: ["-y", "@massu/core"]
|
|
656
|
-
};
|
|
657
|
-
existing.mcpServers = servers;
|
|
658
|
-
writeFileSync2(mcpPath, JSON.stringify(existing, null, 2) + "\n", "utf-8");
|
|
659
|
-
return true;
|
|
660
|
-
}
|
|
661
|
-
function resolveHooksDir() {
|
|
662
|
-
const cwd = process.cwd();
|
|
663
|
-
const nodeModulesPath = resolve3(cwd, "node_modules/@massu/core/dist/hooks");
|
|
664
|
-
if (existsSync3(nodeModulesPath)) {
|
|
665
|
-
return "node_modules/@massu/core/dist/hooks";
|
|
666
|
-
}
|
|
667
|
-
const localPath = resolve3(__dirname2, "../dist/hooks");
|
|
668
|
-
if (existsSync3(localPath)) {
|
|
669
|
-
return localPath;
|
|
670
|
-
}
|
|
671
|
-
return "node_modules/@massu/core/dist/hooks";
|
|
672
|
-
}
|
|
673
|
-
function hookCmd(hooksDir, hookFile) {
|
|
674
|
-
return `node ${hooksDir}/${hookFile}`;
|
|
675
|
-
}
|
|
676
|
-
function buildHooksConfig(hooksDir) {
|
|
677
|
-
return {
|
|
678
|
-
SessionStart: [
|
|
679
|
-
{
|
|
680
|
-
hooks: [
|
|
681
|
-
{ type: "command", command: hookCmd(hooksDir, "session-start.js"), timeout: 10 }
|
|
682
|
-
]
|
|
683
|
-
}
|
|
684
|
-
],
|
|
685
|
-
PreToolUse: [
|
|
686
|
-
{
|
|
687
|
-
matcher: "Bash",
|
|
688
|
-
hooks: [
|
|
689
|
-
{ type: "command", command: hookCmd(hooksDir, "security-gate.js"), timeout: 5 }
|
|
690
|
-
]
|
|
691
|
-
},
|
|
692
|
-
{
|
|
693
|
-
matcher: "Bash|Write",
|
|
694
|
-
hooks: [
|
|
695
|
-
{ type: "command", command: hookCmd(hooksDir, "pre-delete-check.js"), timeout: 5 }
|
|
696
|
-
]
|
|
697
|
-
}
|
|
698
|
-
],
|
|
699
|
-
PostToolUse: [
|
|
700
|
-
{
|
|
701
|
-
hooks: [
|
|
702
|
-
{ type: "command", command: hookCmd(hooksDir, "post-tool-use.js"), timeout: 10 },
|
|
703
|
-
{ type: "command", command: hookCmd(hooksDir, "quality-event.js"), timeout: 5 },
|
|
704
|
-
{ type: "command", command: hookCmd(hooksDir, "cost-tracker.js"), timeout: 5 }
|
|
705
|
-
]
|
|
706
|
-
},
|
|
707
|
-
{
|
|
708
|
-
matcher: "Edit|Write",
|
|
709
|
-
hooks: [
|
|
710
|
-
{ type: "command", command: hookCmd(hooksDir, "post-edit-context.js"), timeout: 5 }
|
|
711
|
-
]
|
|
712
|
-
}
|
|
713
|
-
],
|
|
714
|
-
Stop: [
|
|
715
|
-
{
|
|
716
|
-
hooks: [
|
|
717
|
-
{ type: "command", command: hookCmd(hooksDir, "session-end.js"), timeout: 15 }
|
|
718
|
-
]
|
|
719
|
-
}
|
|
720
|
-
],
|
|
721
|
-
PreCompact: [
|
|
722
|
-
{
|
|
723
|
-
hooks: [
|
|
724
|
-
{ type: "command", command: hookCmd(hooksDir, "pre-compact.js"), timeout: 10 }
|
|
725
|
-
]
|
|
726
|
-
}
|
|
727
|
-
],
|
|
728
|
-
UserPromptSubmit: [
|
|
729
|
-
{
|
|
730
|
-
hooks: [
|
|
731
|
-
{ type: "command", command: hookCmd(hooksDir, "user-prompt.js"), timeout: 5 },
|
|
732
|
-
{ type: "command", command: hookCmd(hooksDir, "intent-suggester.js"), timeout: 5 }
|
|
733
|
-
]
|
|
734
|
-
}
|
|
735
|
-
]
|
|
736
|
-
};
|
|
737
|
-
}
|
|
738
|
-
function installHooks(projectRoot) {
|
|
739
|
-
const claudeDirName = getConfig().conventions?.claudeDirName ?? ".claude";
|
|
740
|
-
const claudeDir = resolve3(projectRoot, claudeDirName);
|
|
741
|
-
const settingsPath = resolve3(claudeDir, "settings.local.json");
|
|
742
|
-
if (!existsSync3(claudeDir)) {
|
|
743
|
-
mkdirSync2(claudeDir, { recursive: true });
|
|
744
|
-
}
|
|
745
|
-
let settings = {};
|
|
746
|
-
if (existsSync3(settingsPath)) {
|
|
747
|
-
try {
|
|
748
|
-
settings = JSON.parse(readFileSync3(settingsPath, "utf-8"));
|
|
749
|
-
} catch {
|
|
750
|
-
settings = {};
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
const hooksDir = resolveHooksDir();
|
|
754
|
-
const hooksConfig = buildHooksConfig(hooksDir);
|
|
755
|
-
let hookCount = 0;
|
|
756
|
-
for (const groups of Object.values(hooksConfig)) {
|
|
757
|
-
for (const group of groups) {
|
|
758
|
-
hookCount += group.hooks.length;
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
settings.hooks = hooksConfig;
|
|
762
|
-
writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
763
|
-
return { installed: true, count: hookCount };
|
|
764
|
-
}
|
|
765
|
-
function initMemoryDir(projectRoot) {
|
|
766
|
-
const encodedRoot = "-" + projectRoot.replace(/\//g, "-");
|
|
767
|
-
const memoryDir = resolve3(homedir2(), `.claude/projects/${encodedRoot}/memory`);
|
|
768
|
-
let created = false;
|
|
769
|
-
if (!existsSync3(memoryDir)) {
|
|
770
|
-
mkdirSync2(memoryDir, { recursive: true });
|
|
771
|
-
created = true;
|
|
772
|
-
}
|
|
773
|
-
const memoryMdPath = resolve3(memoryDir, "MEMORY.md");
|
|
774
|
-
let memoryMdCreated = false;
|
|
775
|
-
if (!existsSync3(memoryMdPath)) {
|
|
776
|
-
const projectName = basename(projectRoot);
|
|
777
|
-
const memoryContent = `# ${projectName} - Massu Memory
|
|
778
|
-
|
|
779
|
-
## Key Learnings
|
|
780
|
-
<!-- Important patterns and conventions discovered during development -->
|
|
781
|
-
|
|
782
|
-
## Common Gotchas
|
|
783
|
-
<!-- Non-obvious issues and how to avoid them -->
|
|
784
|
-
|
|
785
|
-
## Corrections
|
|
786
|
-
<!-- Wrong behaviors that were corrected and how to prevent them -->
|
|
787
|
-
|
|
788
|
-
## File Index
|
|
789
|
-
<!-- Significant files and directories -->
|
|
790
|
-
`;
|
|
791
|
-
writeFileSync2(memoryMdPath, memoryContent, "utf-8");
|
|
792
|
-
memoryMdCreated = true;
|
|
793
|
-
}
|
|
794
|
-
return { created, memoryMdCreated };
|
|
795
|
-
}
|
|
796
|
-
async function runInit() {
|
|
797
|
-
const projectRoot = process.cwd();
|
|
798
|
-
console.log("");
|
|
799
|
-
console.log("Massu AI - Project Setup");
|
|
800
|
-
console.log("========================");
|
|
801
|
-
console.log("");
|
|
802
|
-
const framework = detectFramework(projectRoot);
|
|
803
|
-
const frameworkParts = [];
|
|
804
|
-
if (framework.type !== "javascript") frameworkParts.push(capitalize(framework.type));
|
|
805
|
-
if (framework.ui !== "none") frameworkParts.push(formatName(framework.ui));
|
|
806
|
-
if (framework.orm !== "none") frameworkParts.push(capitalize(framework.orm));
|
|
807
|
-
if (framework.router !== "none") frameworkParts.push(framework.router.toUpperCase());
|
|
808
|
-
const detected = frameworkParts.length > 0 ? frameworkParts.join(", ") : "JavaScript";
|
|
809
|
-
console.log(` Detected: ${detected}`);
|
|
810
|
-
const python = detectPython(projectRoot);
|
|
811
|
-
if (python.detected) {
|
|
812
|
-
const pyParts = ["Python"];
|
|
813
|
-
if (python.hasFastapi) pyParts.push("FastAPI");
|
|
814
|
-
if (python.hasSqlalchemy) pyParts.push("SQLAlchemy");
|
|
815
|
-
if (python.hasAlembic) pyParts.push("Alembic");
|
|
816
|
-
console.log(` Detected: ${pyParts.join(", ")} (root: ${python.root})`);
|
|
817
|
-
}
|
|
818
|
-
const configCreated = generateConfig(projectRoot, framework);
|
|
819
|
-
if (configCreated) {
|
|
820
|
-
console.log(" Created massu.config.yaml");
|
|
821
|
-
} else {
|
|
822
|
-
console.log(" massu.config.yaml already exists (preserved)");
|
|
823
|
-
}
|
|
824
|
-
const mcpRegistered = registerMcpServer(projectRoot);
|
|
825
|
-
if (mcpRegistered) {
|
|
826
|
-
console.log(" Registered MCP server in .mcp.json");
|
|
827
|
-
} else {
|
|
828
|
-
console.log(" MCP server already registered in .mcp.json");
|
|
829
|
-
}
|
|
830
|
-
const { count: hooksCount } = installHooks(projectRoot);
|
|
831
|
-
console.log(` Installed ${hooksCount} hooks in .claude/settings.local.json`);
|
|
832
|
-
const cmdResult = installCommands(projectRoot);
|
|
833
|
-
const cmdTotal = cmdResult.installed + cmdResult.updated + cmdResult.skipped;
|
|
834
|
-
if (cmdResult.installed > 0 || cmdResult.updated > 0) {
|
|
835
|
-
console.log(` Installed ${cmdTotal} slash commands (${cmdResult.installed} new, ${cmdResult.updated} updated)`);
|
|
836
|
-
} else {
|
|
837
|
-
console.log(` ${cmdTotal} slash commands already up to date`);
|
|
838
|
-
}
|
|
839
|
-
const { created: memDirCreated, memoryMdCreated } = initMemoryDir(projectRoot);
|
|
840
|
-
if (memDirCreated) {
|
|
841
|
-
console.log(" Created memory directory (~/.claude/projects/.../memory/)");
|
|
842
|
-
} else {
|
|
843
|
-
console.log(" Memory directory already exists");
|
|
844
|
-
}
|
|
845
|
-
if (memoryMdCreated) {
|
|
846
|
-
console.log(" Created initial MEMORY.md");
|
|
847
|
-
}
|
|
848
|
-
console.log(" Databases will auto-create on first session");
|
|
849
|
-
console.log("");
|
|
850
|
-
console.log("Massu AI is ready. Start a Claude Code session to begin.");
|
|
851
|
-
console.log("");
|
|
852
|
-
console.log("Next steps:");
|
|
853
|
-
console.log(" claude # Start a session (hooks activate automatically)");
|
|
854
|
-
console.log(" npx massu doctor # Verify installation health");
|
|
855
|
-
console.log("");
|
|
856
|
-
console.log("Documentation: https://massu.ai/docs");
|
|
857
|
-
console.log("");
|
|
858
|
-
}
|
|
859
|
-
function capitalize(str) {
|
|
860
|
-
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
861
|
-
}
|
|
862
|
-
function formatName(name) {
|
|
863
|
-
const names = {
|
|
864
|
-
nextjs: "Next.js",
|
|
865
|
-
sveltekit: "SvelteKit",
|
|
866
|
-
nuxt: "Nuxt",
|
|
867
|
-
angular: "Angular",
|
|
868
|
-
vue: "Vue",
|
|
869
|
-
react: "React"
|
|
870
|
-
};
|
|
871
|
-
return names[name] ?? capitalize(name);
|
|
872
|
-
}
|
|
873
|
-
var __filename2, __dirname2;
|
|
874
|
-
var init_init = __esm({
|
|
875
|
-
"src/commands/init.ts"() {
|
|
876
|
-
"use strict";
|
|
877
|
-
init_config();
|
|
878
|
-
init_install_commands();
|
|
879
|
-
__filename2 = fileURLToPath2(import.meta.url);
|
|
880
|
-
__dirname2 = dirname3(__filename2);
|
|
881
|
-
}
|
|
882
|
-
});
|
|
883
|
-
|
|
884
|
-
// src/memory-db.ts
|
|
885
|
-
import Database from "better-sqlite3";
|
|
886
|
-
import { dirname as dirname4, basename as basename2 } from "path";
|
|
887
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
888
|
-
function sanitizeFts5Query(raw) {
|
|
889
|
-
const trimmed = raw.trim();
|
|
890
|
-
if (!trimmed) return '""';
|
|
891
|
-
const tokens = trimmed.replace(/"/g, "").split(/\s+/).filter(Boolean);
|
|
892
|
-
return tokens.map((t) => `"${t}"`).join(" ");
|
|
893
|
-
}
|
|
894
|
-
function getMemoryDb() {
|
|
895
|
-
const dbPath = getResolvedPaths().memoryDbPath;
|
|
896
|
-
const dir = dirname4(dbPath);
|
|
897
|
-
if (!existsSync4(dir)) {
|
|
898
|
-
mkdirSync3(dir, { recursive: true });
|
|
899
|
-
}
|
|
900
|
-
const db = new Database(dbPath);
|
|
901
|
-
db.pragma("journal_mode = WAL");
|
|
902
|
-
db.pragma("foreign_keys = ON");
|
|
903
|
-
initMemorySchema(db);
|
|
904
|
-
return db;
|
|
905
|
-
}
|
|
906
|
-
function initMemorySchema(db) {
|
|
907
|
-
db.exec(`
|
|
908
|
-
-- Sessions table (linked to Claude Code session IDs)
|
|
909
|
-
CREATE TABLE IF NOT EXISTS sessions (
|
|
910
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
911
|
-
session_id TEXT UNIQUE NOT NULL,
|
|
912
|
-
project TEXT NOT NULL DEFAULT 'my-project',
|
|
913
|
-
git_branch TEXT,
|
|
914
|
-
started_at TEXT NOT NULL,
|
|
915
|
-
started_at_epoch INTEGER NOT NULL,
|
|
916
|
-
ended_at TEXT,
|
|
917
|
-
ended_at_epoch INTEGER,
|
|
918
|
-
status TEXT CHECK(status IN ('active', 'completed', 'abandoned')) NOT NULL DEFAULT 'active',
|
|
919
|
-
plan_file TEXT,
|
|
920
|
-
plan_phase TEXT,
|
|
921
|
-
task_id TEXT
|
|
922
|
-
);
|
|
923
|
-
|
|
924
|
-
CREATE INDEX IF NOT EXISTS idx_sessions_session_id ON sessions(session_id);
|
|
925
|
-
CREATE INDEX IF NOT EXISTS idx_sessions_started ON sessions(started_at_epoch DESC);
|
|
926
|
-
CREATE INDEX IF NOT EXISTS idx_sessions_task_id ON sessions(task_id);
|
|
927
|
-
|
|
928
|
-
-- Observations table (structured knowledge from tool usage)
|
|
929
|
-
CREATE TABLE IF NOT EXISTS observations (
|
|
930
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
931
|
-
session_id TEXT NOT NULL,
|
|
932
|
-
type TEXT NOT NULL CHECK(type IN (
|
|
933
|
-
'decision', 'bugfix', 'feature', 'refactor', 'discovery',
|
|
934
|
-
'cr_violation', 'vr_check', 'pattern_compliance', 'failed_attempt',
|
|
935
|
-
'file_change', 'incident_near_miss'
|
|
936
|
-
)),
|
|
937
|
-
title TEXT NOT NULL,
|
|
938
|
-
detail TEXT,
|
|
939
|
-
files_involved TEXT DEFAULT '[]',
|
|
940
|
-
plan_item TEXT,
|
|
941
|
-
cr_rule TEXT,
|
|
942
|
-
vr_type TEXT,
|
|
943
|
-
evidence TEXT,
|
|
944
|
-
importance INTEGER NOT NULL DEFAULT 3 CHECK(importance BETWEEN 1 AND 5),
|
|
945
|
-
recurrence_count INTEGER NOT NULL DEFAULT 1,
|
|
946
|
-
original_tokens INTEGER DEFAULT 0,
|
|
947
|
-
created_at TEXT NOT NULL,
|
|
948
|
-
created_at_epoch INTEGER NOT NULL,
|
|
949
|
-
FOREIGN KEY(session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
|
|
950
|
-
);
|
|
951
|
-
|
|
952
|
-
CREATE INDEX IF NOT EXISTS idx_observations_session ON observations(session_id);
|
|
953
|
-
CREATE INDEX IF NOT EXISTS idx_observations_type ON observations(type);
|
|
954
|
-
CREATE INDEX IF NOT EXISTS idx_observations_created ON observations(created_at_epoch DESC);
|
|
955
|
-
CREATE INDEX IF NOT EXISTS idx_observations_plan_item ON observations(plan_item);
|
|
956
|
-
CREATE INDEX IF NOT EXISTS idx_observations_cr_rule ON observations(cr_rule);
|
|
957
|
-
CREATE INDEX IF NOT EXISTS idx_observations_importance ON observations(importance DESC);
|
|
958
|
-
`);
|
|
959
|
-
try {
|
|
960
|
-
db.exec(`
|
|
961
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS observations_fts USING fts5(
|
|
962
|
-
title, detail, evidence,
|
|
963
|
-
content='observations',
|
|
964
|
-
content_rowid='id'
|
|
965
|
-
);
|
|
966
|
-
`);
|
|
967
|
-
} catch (_e) {
|
|
968
|
-
}
|
|
969
|
-
db.exec(`
|
|
970
|
-
CREATE TRIGGER IF NOT EXISTS observations_ai AFTER INSERT ON observations BEGIN
|
|
971
|
-
INSERT INTO observations_fts(rowid, title, detail, evidence)
|
|
972
|
-
VALUES (new.id, new.title, new.detail, new.evidence);
|
|
973
|
-
END;
|
|
974
|
-
|
|
975
|
-
CREATE TRIGGER IF NOT EXISTS observations_ad AFTER DELETE ON observations BEGIN
|
|
976
|
-
INSERT INTO observations_fts(observations_fts, rowid, title, detail, evidence)
|
|
977
|
-
VALUES ('delete', old.id, old.title, old.detail, old.evidence);
|
|
978
|
-
END;
|
|
979
|
-
|
|
980
|
-
CREATE TRIGGER IF NOT EXISTS observations_au AFTER UPDATE ON observations BEGIN
|
|
981
|
-
INSERT INTO observations_fts(observations_fts, rowid, title, detail, evidence)
|
|
982
|
-
VALUES ('delete', old.id, old.title, old.detail, old.evidence);
|
|
983
|
-
INSERT INTO observations_fts(rowid, title, detail, evidence)
|
|
984
|
-
VALUES (new.id, new.title, new.detail, new.evidence);
|
|
985
|
-
END;
|
|
986
|
-
`);
|
|
987
|
-
db.exec(`
|
|
988
|
-
CREATE TABLE IF NOT EXISTS session_summaries (
|
|
989
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
990
|
-
session_id TEXT NOT NULL,
|
|
991
|
-
request TEXT,
|
|
992
|
-
investigated TEXT,
|
|
993
|
-
decisions TEXT,
|
|
994
|
-
completed TEXT,
|
|
995
|
-
failed_attempts TEXT,
|
|
996
|
-
next_steps TEXT,
|
|
997
|
-
files_created TEXT DEFAULT '[]',
|
|
998
|
-
files_modified TEXT DEFAULT '[]',
|
|
999
|
-
verification_results TEXT DEFAULT '{}',
|
|
1000
|
-
plan_progress TEXT DEFAULT '{}',
|
|
1001
|
-
created_at TEXT NOT NULL,
|
|
1002
|
-
created_at_epoch INTEGER NOT NULL,
|
|
1003
|
-
FOREIGN KEY(session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
|
|
1004
|
-
);
|
|
1005
|
-
|
|
1006
|
-
CREATE INDEX IF NOT EXISTS idx_summaries_session ON session_summaries(session_id);
|
|
1007
|
-
`);
|
|
1008
|
-
db.exec(`
|
|
1009
|
-
CREATE TABLE IF NOT EXISTS user_prompts (
|
|
1010
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1011
|
-
session_id TEXT NOT NULL,
|
|
1012
|
-
prompt_text TEXT NOT NULL,
|
|
1013
|
-
prompt_number INTEGER NOT NULL DEFAULT 1,
|
|
1014
|
-
created_at TEXT NOT NULL,
|
|
1015
|
-
created_at_epoch INTEGER NOT NULL,
|
|
1016
|
-
FOREIGN KEY(session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
|
|
1017
|
-
);
|
|
1018
|
-
`);
|
|
1019
|
-
try {
|
|
1020
|
-
db.exec(`
|
|
1021
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS user_prompts_fts USING fts5(
|
|
1022
|
-
prompt_text,
|
|
1023
|
-
content='user_prompts',
|
|
1024
|
-
content_rowid='id'
|
|
1025
|
-
);
|
|
1026
|
-
`);
|
|
1027
|
-
} catch (_e) {
|
|
470
|
+
db.exec(`
|
|
471
|
+
CREATE TRIGGER IF NOT EXISTS observations_ai AFTER INSERT ON observations BEGIN
|
|
472
|
+
INSERT INTO observations_fts(rowid, title, detail, evidence)
|
|
473
|
+
VALUES (new.id, new.title, new.detail, new.evidence);
|
|
474
|
+
END;
|
|
475
|
+
|
|
476
|
+
CREATE TRIGGER IF NOT EXISTS observations_ad AFTER DELETE ON observations BEGIN
|
|
477
|
+
INSERT INTO observations_fts(observations_fts, rowid, title, detail, evidence)
|
|
478
|
+
VALUES ('delete', old.id, old.title, old.detail, old.evidence);
|
|
479
|
+
END;
|
|
480
|
+
|
|
481
|
+
CREATE TRIGGER IF NOT EXISTS observations_au AFTER UPDATE ON observations BEGIN
|
|
482
|
+
INSERT INTO observations_fts(observations_fts, rowid, title, detail, evidence)
|
|
483
|
+
VALUES ('delete', old.id, old.title, old.detail, old.evidence);
|
|
484
|
+
INSERT INTO observations_fts(rowid, title, detail, evidence)
|
|
485
|
+
VALUES (new.id, new.title, new.detail, new.evidence);
|
|
486
|
+
END;
|
|
487
|
+
`);
|
|
488
|
+
db.exec(`
|
|
489
|
+
CREATE TABLE IF NOT EXISTS session_summaries (
|
|
490
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
491
|
+
session_id TEXT NOT NULL,
|
|
492
|
+
request TEXT,
|
|
493
|
+
investigated TEXT,
|
|
494
|
+
decisions TEXT,
|
|
495
|
+
completed TEXT,
|
|
496
|
+
failed_attempts TEXT,
|
|
497
|
+
next_steps TEXT,
|
|
498
|
+
files_created TEXT DEFAULT '[]',
|
|
499
|
+
files_modified TEXT DEFAULT '[]',
|
|
500
|
+
verification_results TEXT DEFAULT '{}',
|
|
501
|
+
plan_progress TEXT DEFAULT '{}',
|
|
502
|
+
created_at TEXT NOT NULL,
|
|
503
|
+
created_at_epoch INTEGER NOT NULL,
|
|
504
|
+
FOREIGN KEY(session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
|
|
505
|
+
);
|
|
506
|
+
|
|
507
|
+
CREATE INDEX IF NOT EXISTS idx_summaries_session ON session_summaries(session_id);
|
|
508
|
+
`);
|
|
509
|
+
db.exec(`
|
|
510
|
+
CREATE TABLE IF NOT EXISTS user_prompts (
|
|
511
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
512
|
+
session_id TEXT NOT NULL,
|
|
513
|
+
prompt_text TEXT NOT NULL,
|
|
514
|
+
prompt_number INTEGER NOT NULL DEFAULT 1,
|
|
515
|
+
created_at TEXT NOT NULL,
|
|
516
|
+
created_at_epoch INTEGER NOT NULL,
|
|
517
|
+
FOREIGN KEY(session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
|
|
518
|
+
);
|
|
519
|
+
`);
|
|
520
|
+
try {
|
|
521
|
+
db.exec(`
|
|
522
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS user_prompts_fts USING fts5(
|
|
523
|
+
prompt_text,
|
|
524
|
+
content='user_prompts',
|
|
525
|
+
content_rowid='id'
|
|
526
|
+
);
|
|
527
|
+
`);
|
|
528
|
+
} catch (_e) {
|
|
1028
529
|
}
|
|
1029
530
|
db.exec(`
|
|
1030
531
|
CREATE TRIGGER IF NOT EXISTS prompts_ai AFTER INSERT ON user_prompts BEGIN
|
|
@@ -1378,6 +879,29 @@ function initMemorySchema(db) {
|
|
|
1378
879
|
);
|
|
1379
880
|
`);
|
|
1380
881
|
}
|
|
882
|
+
function enqueueSyncPayload(db, payload) {
|
|
883
|
+
db.prepare("INSERT INTO pending_sync (payload) VALUES (?)").run(payload);
|
|
884
|
+
}
|
|
885
|
+
function dequeuePendingSync(db, limit = 10) {
|
|
886
|
+
const stale = db.prepare(
|
|
887
|
+
"SELECT id FROM pending_sync WHERE retry_count >= 10"
|
|
888
|
+
).all();
|
|
889
|
+
if (stale.length > 0) {
|
|
890
|
+
const ids = stale.map((s) => s.id);
|
|
891
|
+
db.prepare(`DELETE FROM pending_sync WHERE id IN (${ids.map(() => "?").join(",")})`).run(...ids);
|
|
892
|
+
}
|
|
893
|
+
return db.prepare(
|
|
894
|
+
"SELECT id, payload, retry_count FROM pending_sync ORDER BY created_at ASC LIMIT ?"
|
|
895
|
+
).all(limit);
|
|
896
|
+
}
|
|
897
|
+
function removePendingSync(db, id) {
|
|
898
|
+
db.prepare("DELETE FROM pending_sync WHERE id = ?").run(id);
|
|
899
|
+
}
|
|
900
|
+
function incrementRetryCount(db, id, error) {
|
|
901
|
+
db.prepare(
|
|
902
|
+
"UPDATE pending_sync SET retry_count = retry_count + 1, last_error = ? WHERE id = ?"
|
|
903
|
+
).run(error, id);
|
|
904
|
+
}
|
|
1381
905
|
function assignImportance(type, vrResult) {
|
|
1382
906
|
switch (type) {
|
|
1383
907
|
case "decision":
|
|
@@ -1399,230 +923,1082 @@ function assignImportance(type, vrResult) {
|
|
|
1399
923
|
case "discovery":
|
|
1400
924
|
return 1;
|
|
1401
925
|
default:
|
|
1402
|
-
return 3;
|
|
926
|
+
return 3;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
function autoDetectTaskId(planFile) {
|
|
930
|
+
if (!planFile) return null;
|
|
931
|
+
const base = basename(planFile);
|
|
932
|
+
return base.replace(/\.md$/, "");
|
|
933
|
+
}
|
|
934
|
+
function createSession(db, sessionId, opts) {
|
|
935
|
+
const now = /* @__PURE__ */ new Date();
|
|
936
|
+
const taskId = autoDetectTaskId(opts?.planFile);
|
|
937
|
+
db.prepare(`
|
|
938
|
+
INSERT OR IGNORE INTO sessions (session_id, git_branch, plan_file, task_id, started_at, started_at_epoch)
|
|
939
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
940
|
+
`).run(sessionId, opts?.branch ?? null, opts?.planFile ?? null, taskId, now.toISOString(), Math.floor(now.getTime() / 1e3));
|
|
941
|
+
}
|
|
942
|
+
function endSession(db, sessionId, status = "completed") {
|
|
943
|
+
const now = /* @__PURE__ */ new Date();
|
|
944
|
+
db.prepare(`
|
|
945
|
+
UPDATE sessions SET status = ?, ended_at = ?, ended_at_epoch = ? WHERE session_id = ?
|
|
946
|
+
`).run(status, now.toISOString(), Math.floor(now.getTime() / 1e3), sessionId);
|
|
947
|
+
}
|
|
948
|
+
function addObservation(db, sessionId, type, title, detail, opts) {
|
|
949
|
+
const now = /* @__PURE__ */ new Date();
|
|
950
|
+
const importance = opts?.importance ?? assignImportance(type, opts?.evidence?.includes("PASS") ? "PASS" : void 0);
|
|
951
|
+
const result = db.prepare(`
|
|
952
|
+
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)
|
|
953
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
954
|
+
`).run(
|
|
955
|
+
sessionId,
|
|
956
|
+
type,
|
|
957
|
+
title,
|
|
958
|
+
detail,
|
|
959
|
+
JSON.stringify(opts?.filesInvolved ?? []),
|
|
960
|
+
opts?.planItem ?? null,
|
|
961
|
+
opts?.crRule ?? null,
|
|
962
|
+
opts?.vrType ?? null,
|
|
963
|
+
opts?.evidence ?? null,
|
|
964
|
+
importance,
|
|
965
|
+
opts?.originalTokens ?? 0,
|
|
966
|
+
now.toISOString(),
|
|
967
|
+
Math.floor(now.getTime() / 1e3)
|
|
968
|
+
);
|
|
969
|
+
return Number(result.lastInsertRowid);
|
|
970
|
+
}
|
|
971
|
+
function addSummary(db, sessionId, summary) {
|
|
972
|
+
const now = /* @__PURE__ */ new Date();
|
|
973
|
+
db.prepare(`
|
|
974
|
+
INSERT INTO session_summaries (session_id, request, investigated, decisions, completed, failed_attempts, next_steps, files_created, files_modified, verification_results, plan_progress, created_at, created_at_epoch)
|
|
975
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
976
|
+
`).run(
|
|
977
|
+
sessionId,
|
|
978
|
+
summary.request ?? null,
|
|
979
|
+
summary.investigated ?? null,
|
|
980
|
+
summary.decisions ?? null,
|
|
981
|
+
summary.completed ?? null,
|
|
982
|
+
summary.failedAttempts ?? null,
|
|
983
|
+
summary.nextSteps ?? null,
|
|
984
|
+
JSON.stringify(summary.filesCreated ?? []),
|
|
985
|
+
JSON.stringify(summary.filesModified ?? []),
|
|
986
|
+
JSON.stringify(summary.verificationResults ?? {}),
|
|
987
|
+
JSON.stringify(summary.planProgress ?? {}),
|
|
988
|
+
now.toISOString(),
|
|
989
|
+
Math.floor(now.getTime() / 1e3)
|
|
990
|
+
);
|
|
991
|
+
}
|
|
992
|
+
function addUserPrompt(db, sessionId, text18, promptNumber) {
|
|
993
|
+
const now = /* @__PURE__ */ new Date();
|
|
994
|
+
db.prepare(`
|
|
995
|
+
INSERT INTO user_prompts (session_id, prompt_text, prompt_number, created_at, created_at_epoch)
|
|
996
|
+
VALUES (?, ?, ?, ?, ?)
|
|
997
|
+
`).run(sessionId, text18, promptNumber, now.toISOString(), Math.floor(now.getTime() / 1e3));
|
|
998
|
+
}
|
|
999
|
+
function searchObservations(db, query, opts) {
|
|
1000
|
+
const limit = opts?.limit ?? 20;
|
|
1001
|
+
let sql = `
|
|
1002
|
+
SELECT o.id, o.type, o.title, o.created_at, o.session_id, o.importance,
|
|
1003
|
+
rank
|
|
1004
|
+
FROM observations_fts
|
|
1005
|
+
JOIN observations o ON observations_fts.rowid = o.id
|
|
1006
|
+
WHERE observations_fts MATCH ?
|
|
1007
|
+
`;
|
|
1008
|
+
const params = [sanitizeFts5Query(query)];
|
|
1009
|
+
if (opts?.type) {
|
|
1010
|
+
sql += " AND o.type = ?";
|
|
1011
|
+
params.push(opts.type);
|
|
1012
|
+
}
|
|
1013
|
+
if (opts?.crRule) {
|
|
1014
|
+
sql += " AND o.cr_rule = ?";
|
|
1015
|
+
params.push(opts.crRule);
|
|
1016
|
+
}
|
|
1017
|
+
if (opts?.dateFrom) {
|
|
1018
|
+
sql += " AND o.created_at >= ?";
|
|
1019
|
+
params.push(opts.dateFrom);
|
|
1020
|
+
}
|
|
1021
|
+
sql += " ORDER BY rank LIMIT ?";
|
|
1022
|
+
params.push(limit);
|
|
1023
|
+
return db.prepare(sql).all(...params);
|
|
1024
|
+
}
|
|
1025
|
+
function getRecentObservations(db, limit = 20, sessionId) {
|
|
1026
|
+
if (sessionId) {
|
|
1027
|
+
return db.prepare(`
|
|
1028
|
+
SELECT id, type, title, detail, importance, created_at, session_id
|
|
1029
|
+
FROM observations WHERE session_id = ?
|
|
1030
|
+
ORDER BY created_at_epoch DESC LIMIT ?
|
|
1031
|
+
`).all(sessionId, limit);
|
|
1032
|
+
}
|
|
1033
|
+
return db.prepare(`
|
|
1034
|
+
SELECT id, type, title, detail, importance, created_at, session_id
|
|
1035
|
+
FROM observations
|
|
1036
|
+
ORDER BY created_at_epoch DESC LIMIT ?
|
|
1037
|
+
`).all(limit);
|
|
1038
|
+
}
|
|
1039
|
+
function getSessionSummaries(db, limit = 10) {
|
|
1040
|
+
return db.prepare(`
|
|
1041
|
+
SELECT session_id, request, completed, failed_attempts, plan_progress, created_at
|
|
1042
|
+
FROM session_summaries
|
|
1043
|
+
ORDER BY created_at_epoch DESC LIMIT ?
|
|
1044
|
+
`).all(limit);
|
|
1045
|
+
}
|
|
1046
|
+
function getSessionTimeline(db, sessionId) {
|
|
1047
|
+
const session = db.prepare("SELECT * FROM sessions WHERE session_id = ?").get(sessionId);
|
|
1048
|
+
const observations = db.prepare("SELECT * FROM observations WHERE session_id = ? ORDER BY created_at_epoch ASC").all(sessionId);
|
|
1049
|
+
const summary = db.prepare("SELECT * FROM session_summaries WHERE session_id = ? ORDER BY created_at_epoch DESC LIMIT 1").get(sessionId);
|
|
1050
|
+
const prompts = db.prepare("SELECT * FROM user_prompts WHERE session_id = ? ORDER BY prompt_number ASC").all(sessionId);
|
|
1051
|
+
return {
|
|
1052
|
+
session: session ?? null,
|
|
1053
|
+
observations,
|
|
1054
|
+
summary: summary ?? null,
|
|
1055
|
+
prompts
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
function getFailedAttempts(db, query, limit = 20) {
|
|
1059
|
+
if (query) {
|
|
1060
|
+
return db.prepare(`
|
|
1061
|
+
SELECT o.id, o.title, o.detail, o.session_id, o.recurrence_count, o.created_at
|
|
1062
|
+
FROM observations_fts
|
|
1063
|
+
JOIN observations o ON observations_fts.rowid = o.id
|
|
1064
|
+
WHERE observations_fts MATCH ? AND o.type = 'failed_attempt'
|
|
1065
|
+
ORDER BY o.recurrence_count DESC, rank LIMIT ?
|
|
1066
|
+
`).all(sanitizeFts5Query(query), limit);
|
|
1067
|
+
}
|
|
1068
|
+
return db.prepare(`
|
|
1069
|
+
SELECT id, title, detail, session_id, recurrence_count, created_at
|
|
1070
|
+
FROM observations WHERE type = 'failed_attempt'
|
|
1071
|
+
ORDER BY recurrence_count DESC, created_at_epoch DESC LIMIT ?
|
|
1072
|
+
`).all(limit);
|
|
1073
|
+
}
|
|
1074
|
+
function getDecisionsAbout(db, query, limit = 20) {
|
|
1075
|
+
return db.prepare(`
|
|
1076
|
+
SELECT o.id, o.title, o.detail, o.session_id, o.created_at
|
|
1077
|
+
FROM observations_fts
|
|
1078
|
+
JOIN observations o ON observations_fts.rowid = o.id
|
|
1079
|
+
WHERE observations_fts MATCH ? AND o.type = 'decision'
|
|
1080
|
+
ORDER BY rank LIMIT ?
|
|
1081
|
+
`).all(sanitizeFts5Query(query), limit);
|
|
1082
|
+
}
|
|
1083
|
+
function pruneOldObservations(db, retentionDays = 90) {
|
|
1084
|
+
const cutoffEpoch = Math.floor(Date.now() / 1e3) - retentionDays * 86400;
|
|
1085
|
+
const result = db.prepare("DELETE FROM observations WHERE created_at_epoch < ?").run(cutoffEpoch);
|
|
1086
|
+
return result.changes;
|
|
1087
|
+
}
|
|
1088
|
+
function deduplicateFailedAttempt(db, sessionId, title, detail, opts) {
|
|
1089
|
+
const existing = db.prepare(`
|
|
1090
|
+
SELECT id, recurrence_count FROM observations
|
|
1091
|
+
WHERE type = 'failed_attempt' AND title = ?
|
|
1092
|
+
ORDER BY created_at_epoch DESC LIMIT 1
|
|
1093
|
+
`).get(title);
|
|
1094
|
+
if (existing) {
|
|
1095
|
+
db.prepare("UPDATE observations SET recurrence_count = recurrence_count + 1, detail = COALESCE(?, detail) WHERE id = ?").run(detail, existing.id);
|
|
1096
|
+
return existing.id;
|
|
1097
|
+
}
|
|
1098
|
+
return addObservation(db, sessionId, "failed_attempt", title, detail, {
|
|
1099
|
+
...opts,
|
|
1100
|
+
importance: 5
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
function getSessionsByTask(db, taskId) {
|
|
1104
|
+
return db.prepare(`
|
|
1105
|
+
SELECT session_id, status, started_at, ended_at, plan_phase
|
|
1106
|
+
FROM sessions WHERE task_id = ?
|
|
1107
|
+
ORDER BY started_at_epoch DESC
|
|
1108
|
+
`).all(taskId);
|
|
1109
|
+
}
|
|
1110
|
+
function getCrossTaskProgress(db, taskId) {
|
|
1111
|
+
const sessions = db.prepare(`
|
|
1112
|
+
SELECT session_id FROM sessions WHERE task_id = ?
|
|
1113
|
+
`).all(taskId);
|
|
1114
|
+
const merged = {};
|
|
1115
|
+
for (const session of sessions) {
|
|
1116
|
+
const summaries = db.prepare(`
|
|
1117
|
+
SELECT plan_progress FROM session_summaries WHERE session_id = ?
|
|
1118
|
+
`).all(session.session_id);
|
|
1119
|
+
for (const summary of summaries) {
|
|
1120
|
+
try {
|
|
1121
|
+
const progress = JSON.parse(summary.plan_progress);
|
|
1122
|
+
for (const [key, value] of Object.entries(progress)) {
|
|
1123
|
+
if (!merged[key] || value === "complete" || value === "in_progress" && merged[key] === "pending") {
|
|
1124
|
+
merged[key] = value;
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
} catch (_e) {
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
return merged;
|
|
1132
|
+
}
|
|
1133
|
+
function linkSessionToTask(db, sessionId, taskId) {
|
|
1134
|
+
db.prepare("UPDATE sessions SET task_id = ? WHERE session_id = ?").run(taskId, sessionId);
|
|
1135
|
+
}
|
|
1136
|
+
function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantResponse, toolCallsJson, toolCallCount, promptTokens, responseTokens) {
|
|
1137
|
+
const result = db.prepare(`
|
|
1138
|
+
INSERT INTO conversation_turns (session_id, turn_number, user_prompt, assistant_response, tool_calls_json, tool_call_count, prompt_tokens, response_tokens)
|
|
1139
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
1140
|
+
`).run(
|
|
1141
|
+
sessionId,
|
|
1142
|
+
turnNumber,
|
|
1143
|
+
userPrompt,
|
|
1144
|
+
assistantResponse ? assistantResponse.slice(0, 1e4) : null,
|
|
1145
|
+
toolCallsJson,
|
|
1146
|
+
toolCallCount,
|
|
1147
|
+
promptTokens,
|
|
1148
|
+
responseTokens
|
|
1149
|
+
);
|
|
1150
|
+
return Number(result.lastInsertRowid);
|
|
1151
|
+
}
|
|
1152
|
+
function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, inputSize, outputSize, success, filesInvolved) {
|
|
1153
|
+
db.prepare(`
|
|
1154
|
+
INSERT INTO tool_call_details (session_id, turn_number, tool_name, tool_input_summary, tool_input_size, tool_output_size, tool_success, files_involved)
|
|
1155
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
1156
|
+
`).run(
|
|
1157
|
+
sessionId,
|
|
1158
|
+
turnNumber,
|
|
1159
|
+
toolName,
|
|
1160
|
+
inputSummary ? inputSummary.slice(0, 500) : null,
|
|
1161
|
+
inputSize,
|
|
1162
|
+
outputSize,
|
|
1163
|
+
success ? 1 : 0,
|
|
1164
|
+
filesInvolved ? JSON.stringify(filesInvolved) : null
|
|
1165
|
+
);
|
|
1166
|
+
}
|
|
1167
|
+
function getLastProcessedLine(db, sessionId) {
|
|
1168
|
+
const row = db.prepare("SELECT value FROM memory_meta WHERE key = ?").get(`last_processed_line:${sessionId}`);
|
|
1169
|
+
return row ? parseInt(row.value, 10) : 0;
|
|
1170
|
+
}
|
|
1171
|
+
function setLastProcessedLine(db, sessionId, lineNumber) {
|
|
1172
|
+
db.prepare("INSERT OR REPLACE INTO memory_meta (key, value) VALUES (?, ?)").run(`last_processed_line:${sessionId}`, String(lineNumber));
|
|
1173
|
+
}
|
|
1174
|
+
function pruneOldConversationTurns(db, retentionDays = 90) {
|
|
1175
|
+
const cutoffEpoch = Math.floor(Date.now() / 1e3) - retentionDays * 86400;
|
|
1176
|
+
const turnsResult = db.prepare("DELETE FROM conversation_turns WHERE created_at_epoch < ?").run(cutoffEpoch);
|
|
1177
|
+
const detailsResult = db.prepare("DELETE FROM tool_call_details WHERE created_at_epoch < ?").run(cutoffEpoch);
|
|
1178
|
+
return { turnsDeleted: turnsResult.changes, detailsDeleted: detailsResult.changes };
|
|
1179
|
+
}
|
|
1180
|
+
function getConversationTurns(db, sessionId, opts) {
|
|
1181
|
+
let sql = "SELECT id, turn_number, user_prompt, assistant_response, tool_calls_json, tool_call_count, prompt_tokens, response_tokens, created_at FROM conversation_turns WHERE session_id = ?";
|
|
1182
|
+
const params = [sessionId];
|
|
1183
|
+
if (opts?.turnFrom !== void 0) {
|
|
1184
|
+
sql += " AND turn_number >= ?";
|
|
1185
|
+
params.push(opts.turnFrom);
|
|
1186
|
+
}
|
|
1187
|
+
if (opts?.turnTo !== void 0) {
|
|
1188
|
+
sql += " AND turn_number <= ?";
|
|
1189
|
+
params.push(opts.turnTo);
|
|
1190
|
+
}
|
|
1191
|
+
sql += " ORDER BY turn_number ASC";
|
|
1192
|
+
return db.prepare(sql).all(...params);
|
|
1193
|
+
}
|
|
1194
|
+
function searchConversationTurns(db, query, opts) {
|
|
1195
|
+
const limit = opts?.limit ?? 20;
|
|
1196
|
+
let sql = `
|
|
1197
|
+
SELECT ct.id, ct.session_id, ct.turn_number, ct.user_prompt, ct.tool_call_count, ct.response_tokens, ct.created_at, rank
|
|
1198
|
+
FROM conversation_turns_fts
|
|
1199
|
+
JOIN conversation_turns ct ON conversation_turns_fts.rowid = ct.id
|
|
1200
|
+
WHERE conversation_turns_fts MATCH ?
|
|
1201
|
+
`;
|
|
1202
|
+
const params = [sanitizeFts5Query(query)];
|
|
1203
|
+
if (opts?.sessionId) {
|
|
1204
|
+
sql += " AND ct.session_id = ?";
|
|
1205
|
+
params.push(opts.sessionId);
|
|
1206
|
+
}
|
|
1207
|
+
if (opts?.dateFrom) {
|
|
1208
|
+
sql += " AND ct.created_at >= ?";
|
|
1209
|
+
params.push(opts.dateFrom);
|
|
1210
|
+
}
|
|
1211
|
+
if (opts?.dateTo) {
|
|
1212
|
+
sql += " AND ct.created_at <= ?";
|
|
1213
|
+
params.push(opts.dateTo);
|
|
1214
|
+
}
|
|
1215
|
+
if (opts?.minToolCalls !== void 0) {
|
|
1216
|
+
sql += " AND ct.tool_call_count >= ?";
|
|
1217
|
+
params.push(opts.minToolCalls);
|
|
1218
|
+
}
|
|
1219
|
+
sql += " ORDER BY rank LIMIT ?";
|
|
1220
|
+
params.push(limit);
|
|
1221
|
+
return db.prepare(sql).all(...params);
|
|
1222
|
+
}
|
|
1223
|
+
function getToolPatterns(db, opts) {
|
|
1224
|
+
const groupBy = opts?.groupBy ?? "tool";
|
|
1225
|
+
const params = [];
|
|
1226
|
+
let whereClause = "";
|
|
1227
|
+
const conditions = [];
|
|
1228
|
+
if (opts?.sessionId) {
|
|
1229
|
+
conditions.push("session_id = ?");
|
|
1230
|
+
params.push(opts.sessionId);
|
|
1231
|
+
}
|
|
1232
|
+
if (opts?.toolName) {
|
|
1233
|
+
conditions.push("tool_name = ?");
|
|
1234
|
+
params.push(opts.toolName);
|
|
1235
|
+
}
|
|
1236
|
+
if (opts?.dateFrom) {
|
|
1237
|
+
conditions.push("created_at >= ?");
|
|
1238
|
+
params.push(opts.dateFrom);
|
|
1239
|
+
}
|
|
1240
|
+
if (conditions.length > 0) {
|
|
1241
|
+
whereClause = "WHERE " + conditions.join(" AND ");
|
|
1242
|
+
}
|
|
1243
|
+
let sql;
|
|
1244
|
+
switch (groupBy) {
|
|
1245
|
+
case "session":
|
|
1246
|
+
sql = `SELECT session_id, COUNT(*) as call_count, COUNT(DISTINCT tool_name) as unique_tools,
|
|
1247
|
+
SUM(CASE WHEN tool_success = 1 THEN 1 ELSE 0 END) as successes,
|
|
1248
|
+
SUM(CASE WHEN tool_success = 0 THEN 1 ELSE 0 END) as failures,
|
|
1249
|
+
AVG(tool_output_size) as avg_output_size
|
|
1250
|
+
FROM tool_call_details ${whereClause}
|
|
1251
|
+
GROUP BY session_id ORDER BY call_count DESC`;
|
|
1252
|
+
break;
|
|
1253
|
+
case "day":
|
|
1254
|
+
sql = `SELECT date(created_at) as day, COUNT(*) as call_count, COUNT(DISTINCT tool_name) as unique_tools,
|
|
1255
|
+
SUM(CASE WHEN tool_success = 1 THEN 1 ELSE 0 END) as successes
|
|
1256
|
+
FROM tool_call_details ${whereClause}
|
|
1257
|
+
GROUP BY date(created_at) ORDER BY day DESC`;
|
|
1258
|
+
break;
|
|
1259
|
+
default:
|
|
1260
|
+
sql = `SELECT tool_name, COUNT(*) as call_count,
|
|
1261
|
+
SUM(CASE WHEN tool_success = 1 THEN 1 ELSE 0 END) as successes,
|
|
1262
|
+
SUM(CASE WHEN tool_success = 0 THEN 1 ELSE 0 END) as failures,
|
|
1263
|
+
AVG(tool_output_size) as avg_output_size,
|
|
1264
|
+
AVG(tool_input_size) as avg_input_size
|
|
1265
|
+
FROM tool_call_details ${whereClause}
|
|
1266
|
+
GROUP BY tool_name ORDER BY call_count DESC`;
|
|
1267
|
+
break;
|
|
1403
1268
|
}
|
|
1269
|
+
return db.prepare(sql).all(...params);
|
|
1404
1270
|
}
|
|
1405
|
-
function
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1271
|
+
function getSessionStats(db, opts) {
|
|
1272
|
+
if (opts?.sessionId) {
|
|
1273
|
+
const turns = db.prepare("SELECT COUNT(*) as turn_count, SUM(tool_call_count) as total_tool_calls, SUM(prompt_tokens) as total_prompt_tokens, SUM(response_tokens) as total_response_tokens FROM conversation_turns WHERE session_id = ?").get(opts.sessionId);
|
|
1274
|
+
const toolBreakdown = db.prepare("SELECT tool_name, COUNT(*) as count FROM tool_call_details WHERE session_id = ? GROUP BY tool_name ORDER BY count DESC").all(opts.sessionId);
|
|
1275
|
+
const session = db.prepare("SELECT * FROM sessions WHERE session_id = ?").get(opts.sessionId);
|
|
1276
|
+
return [{
|
|
1277
|
+
session_id: opts.sessionId,
|
|
1278
|
+
status: session?.status ?? "unknown",
|
|
1279
|
+
started_at: session?.started_at ?? null,
|
|
1280
|
+
ended_at: session?.ended_at ?? null,
|
|
1281
|
+
...turns,
|
|
1282
|
+
tool_breakdown: toolBreakdown
|
|
1283
|
+
}];
|
|
1284
|
+
}
|
|
1285
|
+
const limit = opts?.limit ?? 10;
|
|
1286
|
+
return db.prepare(`
|
|
1287
|
+
SELECT s.session_id, s.status, s.started_at, s.ended_at,
|
|
1288
|
+
COUNT(ct.id) as turn_count,
|
|
1289
|
+
COALESCE(SUM(ct.tool_call_count), 0) as total_tool_calls,
|
|
1290
|
+
COALESCE(SUM(ct.prompt_tokens), 0) as total_prompt_tokens,
|
|
1291
|
+
COALESCE(SUM(ct.response_tokens), 0) as total_response_tokens
|
|
1292
|
+
FROM sessions s
|
|
1293
|
+
LEFT JOIN conversation_turns ct ON s.session_id = ct.session_id
|
|
1294
|
+
GROUP BY s.session_id
|
|
1295
|
+
ORDER BY s.started_at_epoch DESC
|
|
1296
|
+
LIMIT ?
|
|
1297
|
+
`).all(limit);
|
|
1298
|
+
}
|
|
1299
|
+
function getObservabilityDbSize(db) {
|
|
1300
|
+
const turnsCount = db.prepare("SELECT COUNT(*) as c FROM conversation_turns").get().c;
|
|
1301
|
+
const detailsCount = db.prepare("SELECT COUNT(*) as c FROM tool_call_details").get().c;
|
|
1302
|
+
const obsCount = db.prepare("SELECT COUNT(*) as c FROM observations").get().c;
|
|
1303
|
+
const pageCount = db.pragma("page_count")[0]?.page_count ?? 0;
|
|
1304
|
+
const pageSize = db.pragma("page_size")[0]?.page_size ?? 4096;
|
|
1305
|
+
return {
|
|
1306
|
+
conversation_turns_count: turnsCount,
|
|
1307
|
+
tool_call_details_count: detailsCount,
|
|
1308
|
+
observations_count: obsCount,
|
|
1309
|
+
db_page_count: pageCount,
|
|
1310
|
+
db_page_size: pageSize,
|
|
1311
|
+
estimated_size_mb: Math.round(pageCount * pageSize / (1024 * 1024) * 100) / 100
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
var init_memory_db = __esm({
|
|
1315
|
+
"src/memory-db.ts"() {
|
|
1316
|
+
"use strict";
|
|
1317
|
+
init_config();
|
|
1318
|
+
}
|
|
1319
|
+
});
|
|
1320
|
+
|
|
1321
|
+
// src/memory-file-ingest.ts
|
|
1322
|
+
import { readFileSync as readFileSync2, existsSync as existsSync3, readdirSync } from "fs";
|
|
1323
|
+
import { join } from "path";
|
|
1324
|
+
import { parse as parseYaml2 } from "yaml";
|
|
1325
|
+
function ingestMemoryFile(db, sessionId, filePath) {
|
|
1326
|
+
if (!existsSync3(filePath)) return "skipped";
|
|
1327
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
1328
|
+
const basename8 = (filePath.split("/").pop() ?? "").replace(".md", "");
|
|
1329
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
1330
|
+
let name = basename8;
|
|
1331
|
+
let description = "";
|
|
1332
|
+
let type = "discovery";
|
|
1333
|
+
let confidence;
|
|
1334
|
+
if (frontmatterMatch) {
|
|
1335
|
+
try {
|
|
1336
|
+
const fm = parseYaml2(frontmatterMatch[1]);
|
|
1337
|
+
name = fm.name ?? basename8;
|
|
1338
|
+
description = fm.description ?? "";
|
|
1339
|
+
type = fm.type ?? "discovery";
|
|
1340
|
+
confidence = fm.confidence != null ? Number(fm.confidence) : void 0;
|
|
1341
|
+
} catch {
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
const obsType = mapMemoryTypeToObservationType(type);
|
|
1345
|
+
const importance = confidence != null ? Math.max(1, Math.min(5, Math.round(confidence * 4 + 1))) : 4;
|
|
1346
|
+
const bodyMatch = content.match(/^---\n[\s\S]*?\n---\n([\s\S]*)/);
|
|
1347
|
+
const body = bodyMatch ? bodyMatch[1].trim().slice(0, 500) : "";
|
|
1348
|
+
const title = `[memory-file] ${name}`;
|
|
1349
|
+
const detail = description ? `${description}
|
|
1350
|
+
|
|
1351
|
+
${body}` : body;
|
|
1352
|
+
const existing = db.prepare(
|
|
1353
|
+
"SELECT id FROM observations WHERE title = ? LIMIT 1"
|
|
1354
|
+
).get(title);
|
|
1355
|
+
if (existing) {
|
|
1356
|
+
db.prepare("UPDATE observations SET detail = ?, importance = ? WHERE id = ?").run(detail, importance, existing.id);
|
|
1357
|
+
return "updated";
|
|
1358
|
+
} else {
|
|
1359
|
+
addObservation(db, sessionId, obsType, title, detail, { importance });
|
|
1360
|
+
return "inserted";
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
function backfillMemoryFiles(db, memoryDir, sessionId) {
|
|
1364
|
+
const stats = { inserted: 0, updated: 0, skipped: 0, total: 0 };
|
|
1365
|
+
if (!existsSync3(memoryDir)) return stats;
|
|
1366
|
+
const files = readdirSync(memoryDir).filter(
|
|
1367
|
+
(f) => f.endsWith(".md") && f !== "MEMORY.md"
|
|
1425
1368
|
);
|
|
1426
|
-
|
|
1369
|
+
stats.total = files.length;
|
|
1370
|
+
const sid = sessionId ?? `backfill-${Date.now()}`;
|
|
1371
|
+
for (const file of files) {
|
|
1372
|
+
const result = ingestMemoryFile(db, sid, join(memoryDir, file));
|
|
1373
|
+
stats[result]++;
|
|
1374
|
+
}
|
|
1375
|
+
return stats;
|
|
1427
1376
|
}
|
|
1428
|
-
function
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1377
|
+
function mapMemoryTypeToObservationType(memoryType) {
|
|
1378
|
+
switch (memoryType) {
|
|
1379
|
+
case "user":
|
|
1380
|
+
case "feedback":
|
|
1381
|
+
return "decision";
|
|
1382
|
+
case "project":
|
|
1383
|
+
return "feature";
|
|
1384
|
+
case "reference":
|
|
1385
|
+
return "discovery";
|
|
1386
|
+
default:
|
|
1387
|
+
return "discovery";
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
var init_memory_file_ingest = __esm({
|
|
1391
|
+
"src/memory-file-ingest.ts"() {
|
|
1392
|
+
"use strict";
|
|
1393
|
+
init_memory_db();
|
|
1394
|
+
}
|
|
1395
|
+
});
|
|
1396
|
+
|
|
1397
|
+
// src/commands/install-commands.ts
|
|
1398
|
+
var install_commands_exports = {};
|
|
1399
|
+
__export(install_commands_exports, {
|
|
1400
|
+
installAll: () => installAll,
|
|
1401
|
+
installCommands: () => installCommands,
|
|
1402
|
+
resolveAssetDir: () => resolveAssetDir,
|
|
1403
|
+
resolveCommandsDir: () => resolveCommandsDir,
|
|
1404
|
+
runInstallCommands: () => runInstallCommands
|
|
1405
|
+
});
|
|
1406
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync, mkdirSync as mkdirSync2, readdirSync as readdirSync2, statSync } from "fs";
|
|
1407
|
+
import { resolve as resolve3, dirname as dirname3 } from "path";
|
|
1408
|
+
import { fileURLToPath } from "url";
|
|
1409
|
+
function resolveAssetDir(assetName) {
|
|
1410
|
+
const cwd = process.cwd();
|
|
1411
|
+
const nodeModulesPath = resolve3(cwd, "node_modules/@massu/core", assetName);
|
|
1412
|
+
if (existsSync4(nodeModulesPath)) {
|
|
1413
|
+
return nodeModulesPath;
|
|
1414
|
+
}
|
|
1415
|
+
const distRelPath = resolve3(__dirname, "..", assetName);
|
|
1416
|
+
if (existsSync4(distRelPath)) {
|
|
1417
|
+
return distRelPath;
|
|
1418
|
+
}
|
|
1419
|
+
const srcRelPath = resolve3(__dirname, "../..", assetName);
|
|
1420
|
+
if (existsSync4(srcRelPath)) {
|
|
1421
|
+
return srcRelPath;
|
|
1422
|
+
}
|
|
1423
|
+
return null;
|
|
1424
|
+
}
|
|
1425
|
+
function resolveCommandsDir() {
|
|
1426
|
+
return resolveAssetDir("commands");
|
|
1427
|
+
}
|
|
1428
|
+
function syncDirectory(sourceDir, targetDir) {
|
|
1429
|
+
const stats = { installed: 0, updated: 0, skipped: 0 };
|
|
1430
|
+
if (!existsSync4(targetDir)) {
|
|
1431
|
+
mkdirSync2(targetDir, { recursive: true });
|
|
1432
|
+
}
|
|
1433
|
+
const entries = readdirSync2(sourceDir);
|
|
1434
|
+
for (const entry of entries) {
|
|
1435
|
+
const sourcePath = resolve3(sourceDir, entry);
|
|
1436
|
+
const targetPath = resolve3(targetDir, entry);
|
|
1437
|
+
const entryStat = statSync(sourcePath);
|
|
1438
|
+
if (entryStat.isDirectory()) {
|
|
1439
|
+
const subStats = syncDirectory(sourcePath, targetPath);
|
|
1440
|
+
stats.installed += subStats.installed;
|
|
1441
|
+
stats.updated += subStats.updated;
|
|
1442
|
+
stats.skipped += subStats.skipped;
|
|
1443
|
+
} else if (entry.endsWith(".md")) {
|
|
1444
|
+
const sourceContent = readFileSync3(sourcePath, "utf-8");
|
|
1445
|
+
if (existsSync4(targetPath)) {
|
|
1446
|
+
const existingContent = readFileSync3(targetPath, "utf-8");
|
|
1447
|
+
if (existingContent === sourceContent) {
|
|
1448
|
+
stats.skipped++;
|
|
1449
|
+
continue;
|
|
1450
|
+
}
|
|
1451
|
+
writeFileSync(targetPath, sourceContent, "utf-8");
|
|
1452
|
+
stats.updated++;
|
|
1453
|
+
} else {
|
|
1454
|
+
writeFileSync(targetPath, sourceContent, "utf-8");
|
|
1455
|
+
stats.installed++;
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
return stats;
|
|
1460
|
+
}
|
|
1461
|
+
function installCommands(projectRoot) {
|
|
1462
|
+
const claudeDirName = getConfig().conventions?.claudeDirName ?? ".claude";
|
|
1463
|
+
const targetDir = resolve3(projectRoot, claudeDirName, "commands");
|
|
1464
|
+
if (!existsSync4(targetDir)) {
|
|
1465
|
+
mkdirSync2(targetDir, { recursive: true });
|
|
1466
|
+
}
|
|
1467
|
+
const sourceDir = resolveAssetDir("commands");
|
|
1468
|
+
if (!sourceDir) {
|
|
1469
|
+
console.error(" ERROR: Could not find massu commands directory.");
|
|
1470
|
+
console.error(" Try reinstalling: npm install @massu/core");
|
|
1471
|
+
return { installed: 0, updated: 0, skipped: 0, commandsDir: targetDir };
|
|
1472
|
+
}
|
|
1473
|
+
const stats = syncDirectory(sourceDir, targetDir);
|
|
1474
|
+
return { ...stats, commandsDir: targetDir };
|
|
1475
|
+
}
|
|
1476
|
+
function installAll(projectRoot) {
|
|
1477
|
+
const claudeDirName = getConfig().conventions?.claudeDirName ?? ".claude";
|
|
1478
|
+
const claudeDir = resolve3(projectRoot, claudeDirName);
|
|
1479
|
+
const assets = {};
|
|
1480
|
+
let totalInstalled = 0;
|
|
1481
|
+
let totalUpdated = 0;
|
|
1482
|
+
let totalSkipped = 0;
|
|
1483
|
+
for (const assetType of ASSET_TYPES) {
|
|
1484
|
+
const sourceDir = resolveAssetDir(assetType.name);
|
|
1485
|
+
if (!sourceDir) {
|
|
1486
|
+
continue;
|
|
1487
|
+
}
|
|
1488
|
+
const targetDir = resolve3(claudeDir, assetType.targetSubdir);
|
|
1489
|
+
const stats = syncDirectory(sourceDir, targetDir);
|
|
1490
|
+
assets[assetType.name] = stats;
|
|
1491
|
+
totalInstalled += stats.installed;
|
|
1492
|
+
totalUpdated += stats.updated;
|
|
1493
|
+
totalSkipped += stats.skipped;
|
|
1494
|
+
}
|
|
1495
|
+
return { assets, totalInstalled, totalUpdated, totalSkipped, claudeDir };
|
|
1496
|
+
}
|
|
1497
|
+
async function runInstallCommands() {
|
|
1498
|
+
const projectRoot = process.cwd();
|
|
1499
|
+
console.log("");
|
|
1500
|
+
console.log("Massu AI - Install Project Assets");
|
|
1501
|
+
console.log("==================================");
|
|
1502
|
+
console.log("");
|
|
1503
|
+
const result = installAll(projectRoot);
|
|
1504
|
+
for (const assetType of ASSET_TYPES) {
|
|
1505
|
+
const stats = result.assets[assetType.name];
|
|
1506
|
+
if (!stats) {
|
|
1507
|
+
continue;
|
|
1508
|
+
}
|
|
1509
|
+
const total = stats.installed + stats.updated + stats.skipped;
|
|
1510
|
+
if (total === 0) continue;
|
|
1511
|
+
const parts = [];
|
|
1512
|
+
if (stats.installed > 0) parts.push(`${stats.installed} new`);
|
|
1513
|
+
if (stats.updated > 0) parts.push(`${stats.updated} updated`);
|
|
1514
|
+
if (stats.skipped > 0) parts.push(`${stats.skipped} current`);
|
|
1515
|
+
const description = assetType.description;
|
|
1516
|
+
console.log(` ${description}: ${parts.join(", ")} (${total} total)`);
|
|
1517
|
+
}
|
|
1518
|
+
const grandTotal = result.totalInstalled + result.totalUpdated + result.totalSkipped;
|
|
1519
|
+
console.log("");
|
|
1520
|
+
console.log(` ${grandTotal} total files synced to ${result.claudeDir}`);
|
|
1521
|
+
console.log("");
|
|
1522
|
+
console.log(" Restart your Claude Code session to use them.");
|
|
1523
|
+
console.log("");
|
|
1524
|
+
}
|
|
1525
|
+
var __filename, __dirname, ASSET_TYPES;
|
|
1526
|
+
var init_install_commands = __esm({
|
|
1527
|
+
"src/commands/install-commands.ts"() {
|
|
1528
|
+
"use strict";
|
|
1529
|
+
init_config();
|
|
1530
|
+
__filename = fileURLToPath(import.meta.url);
|
|
1531
|
+
__dirname = dirname3(__filename);
|
|
1532
|
+
ASSET_TYPES = [
|
|
1533
|
+
{ name: "commands", targetSubdir: "commands", description: "slash commands" },
|
|
1534
|
+
{ name: "agents", targetSubdir: "agents", description: "agent definitions" },
|
|
1535
|
+
{ name: "patterns", targetSubdir: "patterns", description: "pattern files" },
|
|
1536
|
+
{ name: "protocols", targetSubdir: "protocols", description: "protocol files" },
|
|
1537
|
+
{ name: "reference", targetSubdir: "reference", description: "reference files" }
|
|
1538
|
+
];
|
|
1539
|
+
}
|
|
1540
|
+
});
|
|
1541
|
+
|
|
1542
|
+
// src/commands/init.ts
|
|
1543
|
+
var init_exports = {};
|
|
1544
|
+
__export(init_exports, {
|
|
1545
|
+
buildHooksConfig: () => buildHooksConfig,
|
|
1546
|
+
detectFramework: () => detectFramework,
|
|
1547
|
+
detectPython: () => detectPython,
|
|
1548
|
+
generateConfig: () => generateConfig,
|
|
1549
|
+
initMemoryDir: () => initMemoryDir,
|
|
1550
|
+
installHooks: () => installHooks,
|
|
1551
|
+
registerMcpServer: () => registerMcpServer,
|
|
1552
|
+
resolveHooksDir: () => resolveHooksDir,
|
|
1553
|
+
runInit: () => runInit
|
|
1554
|
+
});
|
|
1555
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, readdirSync as readdirSync3 } from "fs";
|
|
1556
|
+
import { resolve as resolve4, basename as basename2, dirname as dirname4 } from "path";
|
|
1557
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1558
|
+
import { homedir as homedir2 } from "os";
|
|
1559
|
+
import { stringify as yamlStringify } from "yaml";
|
|
1560
|
+
function detectFramework(projectRoot) {
|
|
1561
|
+
const result = {
|
|
1562
|
+
type: "javascript",
|
|
1563
|
+
router: "none",
|
|
1564
|
+
orm: "none",
|
|
1565
|
+
ui: "none"
|
|
1566
|
+
};
|
|
1567
|
+
const pkgPath = resolve4(projectRoot, "package.json");
|
|
1568
|
+
if (!existsSync5(pkgPath)) return result;
|
|
1569
|
+
try {
|
|
1570
|
+
const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
|
|
1571
|
+
const allDeps = {
|
|
1572
|
+
...pkg.dependencies,
|
|
1573
|
+
...pkg.devDependencies
|
|
1574
|
+
};
|
|
1575
|
+
if (allDeps["typescript"]) result.type = "typescript";
|
|
1576
|
+
if (allDeps["next"]) result.ui = "nextjs";
|
|
1577
|
+
else if (allDeps["@sveltejs/kit"]) result.ui = "sveltekit";
|
|
1578
|
+
else if (allDeps["nuxt"]) result.ui = "nuxt";
|
|
1579
|
+
else if (allDeps["@angular/core"]) result.ui = "angular";
|
|
1580
|
+
else if (allDeps["vue"]) result.ui = "vue";
|
|
1581
|
+
else if (allDeps["react"]) result.ui = "react";
|
|
1582
|
+
if (allDeps["@trpc/server"]) result.router = "trpc";
|
|
1583
|
+
else if (allDeps["graphql"] || allDeps["@apollo/server"]) result.router = "graphql";
|
|
1584
|
+
else if (allDeps["express"] || allDeps["fastify"] || allDeps["hono"]) result.router = "rest";
|
|
1585
|
+
if (allDeps["@prisma/client"] || allDeps["prisma"]) result.orm = "prisma";
|
|
1586
|
+
else if (allDeps["drizzle-orm"]) result.orm = "drizzle";
|
|
1587
|
+
else if (allDeps["typeorm"]) result.orm = "typeorm";
|
|
1588
|
+
else if (allDeps["sequelize"]) result.orm = "sequelize";
|
|
1589
|
+
else if (allDeps["mongoose"]) result.orm = "mongoose";
|
|
1590
|
+
} catch {
|
|
1591
|
+
}
|
|
1592
|
+
return result;
|
|
1593
|
+
}
|
|
1594
|
+
function detectPython(projectRoot) {
|
|
1595
|
+
const result = {
|
|
1596
|
+
detected: false,
|
|
1597
|
+
root: "",
|
|
1598
|
+
hasFastapi: false,
|
|
1599
|
+
hasSqlalchemy: false,
|
|
1600
|
+
hasAlembic: false,
|
|
1601
|
+
alembicDir: null
|
|
1602
|
+
};
|
|
1603
|
+
const markers = ["pyproject.toml", "setup.py", "requirements.txt", "Pipfile"];
|
|
1604
|
+
const hasMarker = markers.some((m) => existsSync5(resolve4(projectRoot, m)));
|
|
1605
|
+
if (!hasMarker) return result;
|
|
1606
|
+
result.detected = true;
|
|
1607
|
+
const depFiles = [
|
|
1608
|
+
{ file: "pyproject.toml", parser: parsePyprojectDeps },
|
|
1609
|
+
{ file: "requirements.txt", parser: parseRequirementsDeps },
|
|
1610
|
+
{ file: "setup.py", parser: parseSetupPyDeps },
|
|
1611
|
+
{ file: "Pipfile", parser: parsePipfileDeps }
|
|
1612
|
+
];
|
|
1613
|
+
for (const { file, parser } of depFiles) {
|
|
1614
|
+
const filePath = resolve4(projectRoot, file);
|
|
1615
|
+
if (existsSync5(filePath)) {
|
|
1616
|
+
try {
|
|
1617
|
+
const content = readFileSync4(filePath, "utf-8");
|
|
1618
|
+
const deps = parser(content);
|
|
1619
|
+
if (deps.includes("fastapi")) result.hasFastapi = true;
|
|
1620
|
+
if (deps.includes("sqlalchemy")) result.hasSqlalchemy = true;
|
|
1621
|
+
} catch {
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
if (existsSync5(resolve4(projectRoot, "alembic.ini"))) {
|
|
1626
|
+
result.hasAlembic = true;
|
|
1627
|
+
if (existsSync5(resolve4(projectRoot, "alembic"))) {
|
|
1628
|
+
result.alembicDir = "alembic";
|
|
1629
|
+
}
|
|
1630
|
+
} else if (existsSync5(resolve4(projectRoot, "alembic"))) {
|
|
1631
|
+
result.hasAlembic = true;
|
|
1632
|
+
result.alembicDir = "alembic";
|
|
1441
1633
|
}
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1634
|
+
const candidateRoots = ["app", "src", "backend", "api"];
|
|
1635
|
+
for (const candidate of candidateRoots) {
|
|
1636
|
+
const candidatePath = resolve4(projectRoot, candidate);
|
|
1637
|
+
if (existsSync5(candidatePath) && existsSync5(resolve4(candidatePath, "__init__.py"))) {
|
|
1638
|
+
result.root = candidate;
|
|
1639
|
+
break;
|
|
1640
|
+
}
|
|
1641
|
+
if (existsSync5(candidatePath)) {
|
|
1642
|
+
try {
|
|
1643
|
+
const files = readdirSync3(candidatePath);
|
|
1644
|
+
if (files.some((f) => f.endsWith(".py"))) {
|
|
1645
|
+
result.root = candidate;
|
|
1646
|
+
break;
|
|
1647
|
+
}
|
|
1648
|
+
} catch {
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1445
1651
|
}
|
|
1446
|
-
if (
|
|
1447
|
-
|
|
1448
|
-
params.push(opts.dateFrom);
|
|
1652
|
+
if (!result.root) {
|
|
1653
|
+
result.root = ".";
|
|
1449
1654
|
}
|
|
1450
|
-
|
|
1451
|
-
params.push(limit);
|
|
1452
|
-
return db.prepare(sql).all(...params);
|
|
1655
|
+
return result;
|
|
1453
1656
|
}
|
|
1454
|
-
function
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1657
|
+
function parsePyprojectDeps(content) {
|
|
1658
|
+
const deps = [];
|
|
1659
|
+
const lower = content.toLowerCase();
|
|
1660
|
+
if (lower.includes("fastapi")) deps.push("fastapi");
|
|
1661
|
+
if (lower.includes("sqlalchemy")) deps.push("sqlalchemy");
|
|
1662
|
+
return deps;
|
|
1460
1663
|
}
|
|
1461
|
-
function
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
ORDER BY o.recurrence_count DESC, rank LIMIT ?
|
|
1469
|
-
`).all(sanitizeFts5Query(query), limit);
|
|
1664
|
+
function parseRequirementsDeps(content) {
|
|
1665
|
+
const deps = [];
|
|
1666
|
+
const lower = content.toLowerCase();
|
|
1667
|
+
for (const line of lower.split("\n")) {
|
|
1668
|
+
const trimmed = line.trim();
|
|
1669
|
+
if (trimmed.startsWith("fastapi")) deps.push("fastapi");
|
|
1670
|
+
if (trimmed.startsWith("sqlalchemy")) deps.push("sqlalchemy");
|
|
1470
1671
|
}
|
|
1471
|
-
return
|
|
1472
|
-
SELECT id, title, detail, session_id, recurrence_count, created_at
|
|
1473
|
-
FROM observations WHERE type = 'failed_attempt'
|
|
1474
|
-
ORDER BY recurrence_count DESC, created_at_epoch DESC LIMIT ?
|
|
1475
|
-
`).all(limit);
|
|
1672
|
+
return deps;
|
|
1476
1673
|
}
|
|
1477
|
-
function
|
|
1478
|
-
const
|
|
1479
|
-
const
|
|
1480
|
-
|
|
1674
|
+
function parseSetupPyDeps(content) {
|
|
1675
|
+
const deps = [];
|
|
1676
|
+
const lower = content.toLowerCase();
|
|
1677
|
+
if (lower.includes("fastapi")) deps.push("fastapi");
|
|
1678
|
+
if (lower.includes("sqlalchemy")) deps.push("sqlalchemy");
|
|
1679
|
+
return deps;
|
|
1481
1680
|
}
|
|
1482
|
-
function
|
|
1483
|
-
const
|
|
1484
|
-
const
|
|
1485
|
-
|
|
1486
|
-
|
|
1681
|
+
function parsePipfileDeps(content) {
|
|
1682
|
+
const deps = [];
|
|
1683
|
+
const lower = content.toLowerCase();
|
|
1684
|
+
if (lower.includes("fastapi")) deps.push("fastapi");
|
|
1685
|
+
if (lower.includes("sqlalchemy")) deps.push("sqlalchemy");
|
|
1686
|
+
return deps;
|
|
1487
1687
|
}
|
|
1488
|
-
function
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
sql += " AND turn_number >= ?";
|
|
1493
|
-
params.push(opts.turnFrom);
|
|
1688
|
+
function generateConfig(projectRoot, framework) {
|
|
1689
|
+
const configPath = resolve4(projectRoot, "massu.config.yaml");
|
|
1690
|
+
if (existsSync5(configPath)) {
|
|
1691
|
+
return false;
|
|
1494
1692
|
}
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1693
|
+
const projectName = basename2(projectRoot);
|
|
1694
|
+
const config = {
|
|
1695
|
+
project: {
|
|
1696
|
+
name: projectName,
|
|
1697
|
+
root: "auto"
|
|
1698
|
+
},
|
|
1699
|
+
framework: {
|
|
1700
|
+
type: framework.type,
|
|
1701
|
+
router: framework.router,
|
|
1702
|
+
orm: framework.orm,
|
|
1703
|
+
ui: framework.ui
|
|
1704
|
+
},
|
|
1705
|
+
paths: {
|
|
1706
|
+
source: "src",
|
|
1707
|
+
aliases: { "@": "src" }
|
|
1708
|
+
},
|
|
1709
|
+
toolPrefix: "massu",
|
|
1710
|
+
domains: [],
|
|
1711
|
+
rules: [
|
|
1712
|
+
{
|
|
1713
|
+
pattern: "src/**/*.ts",
|
|
1714
|
+
rules: ["Use ESM imports, not CommonJS"]
|
|
1715
|
+
}
|
|
1716
|
+
]
|
|
1717
|
+
};
|
|
1718
|
+
const python = detectPython(projectRoot);
|
|
1719
|
+
if (python.detected) {
|
|
1720
|
+
const pythonConfig = {
|
|
1721
|
+
root: python.root,
|
|
1722
|
+
exclude_dirs: ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]
|
|
1723
|
+
};
|
|
1724
|
+
if (python.hasFastapi) pythonConfig.framework = "fastapi";
|
|
1725
|
+
if (python.hasSqlalchemy) pythonConfig.orm = "sqlalchemy";
|
|
1726
|
+
if (python.hasAlembic && python.alembicDir) {
|
|
1727
|
+
pythonConfig.alembic_dir = python.alembicDir;
|
|
1728
|
+
}
|
|
1729
|
+
config.python = pythonConfig;
|
|
1498
1730
|
}
|
|
1499
|
-
|
|
1500
|
-
|
|
1731
|
+
const yamlContent = `# Massu AI Configuration
|
|
1732
|
+
# Generated by: npx massu init
|
|
1733
|
+
# Documentation: https://massu.ai/docs/getting-started/configuration
|
|
1734
|
+
|
|
1735
|
+
${yamlStringify(config)}`;
|
|
1736
|
+
writeFileSync2(configPath, yamlContent, "utf-8");
|
|
1737
|
+
return true;
|
|
1501
1738
|
}
|
|
1502
|
-
function
|
|
1503
|
-
const
|
|
1504
|
-
let
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
if (opts?.sessionId) {
|
|
1512
|
-
sql += " AND ct.session_id = ?";
|
|
1513
|
-
params.push(opts.sessionId);
|
|
1739
|
+
function registerMcpServer(projectRoot) {
|
|
1740
|
+
const mcpPath = resolve4(projectRoot, ".mcp.json");
|
|
1741
|
+
let existing = {};
|
|
1742
|
+
if (existsSync5(mcpPath)) {
|
|
1743
|
+
try {
|
|
1744
|
+
existing = JSON.parse(readFileSync4(mcpPath, "utf-8"));
|
|
1745
|
+
} catch {
|
|
1746
|
+
existing = {};
|
|
1747
|
+
}
|
|
1514
1748
|
}
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1749
|
+
const servers = existing.mcpServers ?? {};
|
|
1750
|
+
if (servers.massu) {
|
|
1751
|
+
return false;
|
|
1752
|
+
}
|
|
1753
|
+
servers.massu = {
|
|
1754
|
+
type: "stdio",
|
|
1755
|
+
command: "npx",
|
|
1756
|
+
args: ["-y", "@massu/core"]
|
|
1757
|
+
};
|
|
1758
|
+
existing.mcpServers = servers;
|
|
1759
|
+
writeFileSync2(mcpPath, JSON.stringify(existing, null, 2) + "\n", "utf-8");
|
|
1760
|
+
return true;
|
|
1761
|
+
}
|
|
1762
|
+
function resolveHooksDir() {
|
|
1763
|
+
const cwd = process.cwd();
|
|
1764
|
+
const nodeModulesPath = resolve4(cwd, "node_modules/@massu/core/dist/hooks");
|
|
1765
|
+
if (existsSync5(nodeModulesPath)) {
|
|
1766
|
+
return "node_modules/@massu/core/dist/hooks";
|
|
1767
|
+
}
|
|
1768
|
+
const localPath = resolve4(__dirname2, "../dist/hooks");
|
|
1769
|
+
if (existsSync5(localPath)) {
|
|
1770
|
+
return localPath;
|
|
1771
|
+
}
|
|
1772
|
+
return "node_modules/@massu/core/dist/hooks";
|
|
1773
|
+
}
|
|
1774
|
+
function hookCmd(hooksDir, hookFile) {
|
|
1775
|
+
return `node ${hooksDir}/${hookFile}`;
|
|
1776
|
+
}
|
|
1777
|
+
function buildHooksConfig(hooksDir) {
|
|
1778
|
+
return {
|
|
1779
|
+
SessionStart: [
|
|
1780
|
+
{
|
|
1781
|
+
hooks: [
|
|
1782
|
+
{ type: "command", command: hookCmd(hooksDir, "session-start.js"), timeout: 10 }
|
|
1783
|
+
]
|
|
1784
|
+
}
|
|
1785
|
+
],
|
|
1786
|
+
PreToolUse: [
|
|
1787
|
+
{
|
|
1788
|
+
matcher: "Bash",
|
|
1789
|
+
hooks: [
|
|
1790
|
+
{ type: "command", command: hookCmd(hooksDir, "security-gate.js"), timeout: 5 }
|
|
1791
|
+
]
|
|
1792
|
+
},
|
|
1793
|
+
{
|
|
1794
|
+
matcher: "Bash|Write",
|
|
1795
|
+
hooks: [
|
|
1796
|
+
{ type: "command", command: hookCmd(hooksDir, "pre-delete-check.js"), timeout: 5 }
|
|
1797
|
+
]
|
|
1798
|
+
}
|
|
1799
|
+
],
|
|
1800
|
+
PostToolUse: [
|
|
1801
|
+
{
|
|
1802
|
+
hooks: [
|
|
1803
|
+
{ type: "command", command: hookCmd(hooksDir, "post-tool-use.js"), timeout: 10 },
|
|
1804
|
+
{ type: "command", command: hookCmd(hooksDir, "quality-event.js"), timeout: 5 },
|
|
1805
|
+
{ type: "command", command: hookCmd(hooksDir, "cost-tracker.js"), timeout: 5 }
|
|
1806
|
+
]
|
|
1807
|
+
},
|
|
1808
|
+
{
|
|
1809
|
+
matcher: "Edit|Write",
|
|
1810
|
+
hooks: [
|
|
1811
|
+
{ type: "command", command: hookCmd(hooksDir, "post-edit-context.js"), timeout: 5 }
|
|
1812
|
+
]
|
|
1813
|
+
}
|
|
1814
|
+
],
|
|
1815
|
+
Stop: [
|
|
1816
|
+
{
|
|
1817
|
+
hooks: [
|
|
1818
|
+
{ type: "command", command: hookCmd(hooksDir, "session-end.js"), timeout: 15 }
|
|
1819
|
+
]
|
|
1820
|
+
}
|
|
1821
|
+
],
|
|
1822
|
+
PreCompact: [
|
|
1823
|
+
{
|
|
1824
|
+
hooks: [
|
|
1825
|
+
{ type: "command", command: hookCmd(hooksDir, "pre-compact.js"), timeout: 10 }
|
|
1826
|
+
]
|
|
1827
|
+
}
|
|
1828
|
+
],
|
|
1829
|
+
UserPromptSubmit: [
|
|
1830
|
+
{
|
|
1831
|
+
hooks: [
|
|
1832
|
+
{ type: "command", command: hookCmd(hooksDir, "user-prompt.js"), timeout: 5 },
|
|
1833
|
+
{ type: "command", command: hookCmd(hooksDir, "intent-suggester.js"), timeout: 5 }
|
|
1834
|
+
]
|
|
1835
|
+
}
|
|
1836
|
+
]
|
|
1837
|
+
};
|
|
1838
|
+
}
|
|
1839
|
+
function installHooks(projectRoot) {
|
|
1840
|
+
const claudeDirName = getConfig().conventions?.claudeDirName ?? ".claude";
|
|
1841
|
+
const claudeDir = resolve4(projectRoot, claudeDirName);
|
|
1842
|
+
const settingsPath = resolve4(claudeDir, "settings.local.json");
|
|
1843
|
+
if (!existsSync5(claudeDir)) {
|
|
1844
|
+
mkdirSync3(claudeDir, { recursive: true });
|
|
1845
|
+
}
|
|
1846
|
+
let settings = {};
|
|
1847
|
+
if (existsSync5(settingsPath)) {
|
|
1848
|
+
try {
|
|
1849
|
+
settings = JSON.parse(readFileSync4(settingsPath, "utf-8"));
|
|
1850
|
+
} catch {
|
|
1851
|
+
settings = {};
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
const hooksDir = resolveHooksDir();
|
|
1855
|
+
const hooksConfig = buildHooksConfig(hooksDir);
|
|
1856
|
+
let hookCount = 0;
|
|
1857
|
+
for (const groups of Object.values(hooksConfig)) {
|
|
1858
|
+
for (const group of groups) {
|
|
1859
|
+
hookCount += group.hooks.length;
|
|
1860
|
+
}
|
|
1518
1861
|
}
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1862
|
+
settings.hooks = hooksConfig;
|
|
1863
|
+
writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
1864
|
+
return { installed: true, count: hookCount };
|
|
1865
|
+
}
|
|
1866
|
+
function initMemoryDir(projectRoot) {
|
|
1867
|
+
const encodedRoot = "-" + projectRoot.replace(/\//g, "-");
|
|
1868
|
+
const memoryDir = resolve4(homedir2(), `.claude/projects/${encodedRoot}/memory`);
|
|
1869
|
+
let created = false;
|
|
1870
|
+
if (!existsSync5(memoryDir)) {
|
|
1871
|
+
mkdirSync3(memoryDir, { recursive: true });
|
|
1872
|
+
created = true;
|
|
1522
1873
|
}
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1874
|
+
const memoryMdPath = resolve4(memoryDir, "MEMORY.md");
|
|
1875
|
+
let memoryMdCreated = false;
|
|
1876
|
+
if (!existsSync5(memoryMdPath)) {
|
|
1877
|
+
const projectName = basename2(projectRoot);
|
|
1878
|
+
const memoryContent = `# ${projectName} - Massu Memory
|
|
1879
|
+
|
|
1880
|
+
## Key Learnings
|
|
1881
|
+
<!-- Important patterns and conventions discovered during development -->
|
|
1882
|
+
|
|
1883
|
+
## Common Gotchas
|
|
1884
|
+
<!-- Non-obvious issues and how to avoid them -->
|
|
1885
|
+
|
|
1886
|
+
## Corrections
|
|
1887
|
+
<!-- Wrong behaviors that were corrected and how to prevent them -->
|
|
1888
|
+
|
|
1889
|
+
## File Index
|
|
1890
|
+
<!-- Significant files and directories -->
|
|
1891
|
+
`;
|
|
1892
|
+
writeFileSync2(memoryMdPath, memoryContent, "utf-8");
|
|
1893
|
+
memoryMdCreated = true;
|
|
1526
1894
|
}
|
|
1527
|
-
|
|
1528
|
-
params.push(limit);
|
|
1529
|
-
return db.prepare(sql).all(...params);
|
|
1895
|
+
return { created, memoryMdCreated };
|
|
1530
1896
|
}
|
|
1531
|
-
function
|
|
1532
|
-
const
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1897
|
+
async function runInit() {
|
|
1898
|
+
const projectRoot = process.cwd();
|
|
1899
|
+
console.log("");
|
|
1900
|
+
console.log("Massu AI - Project Setup");
|
|
1901
|
+
console.log("========================");
|
|
1902
|
+
console.log("");
|
|
1903
|
+
const framework = detectFramework(projectRoot);
|
|
1904
|
+
const frameworkParts = [];
|
|
1905
|
+
if (framework.type !== "javascript") frameworkParts.push(capitalize(framework.type));
|
|
1906
|
+
if (framework.ui !== "none") frameworkParts.push(formatName(framework.ui));
|
|
1907
|
+
if (framework.orm !== "none") frameworkParts.push(capitalize(framework.orm));
|
|
1908
|
+
if (framework.router !== "none") frameworkParts.push(framework.router.toUpperCase());
|
|
1909
|
+
const detected = frameworkParts.length > 0 ? frameworkParts.join(", ") : "JavaScript";
|
|
1910
|
+
console.log(` Detected: ${detected}`);
|
|
1911
|
+
const python = detectPython(projectRoot);
|
|
1912
|
+
if (python.detected) {
|
|
1913
|
+
const pyParts = ["Python"];
|
|
1914
|
+
if (python.hasFastapi) pyParts.push("FastAPI");
|
|
1915
|
+
if (python.hasSqlalchemy) pyParts.push("SQLAlchemy");
|
|
1916
|
+
if (python.hasAlembic) pyParts.push("Alembic");
|
|
1917
|
+
console.log(` Detected: ${pyParts.join(", ")} (root: ${python.root})`);
|
|
1539
1918
|
}
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1919
|
+
const configCreated = generateConfig(projectRoot, framework);
|
|
1920
|
+
if (configCreated) {
|
|
1921
|
+
console.log(" Created massu.config.yaml");
|
|
1922
|
+
} else {
|
|
1923
|
+
console.log(" massu.config.yaml already exists (preserved)");
|
|
1543
1924
|
}
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1925
|
+
const mcpRegistered = registerMcpServer(projectRoot);
|
|
1926
|
+
if (mcpRegistered) {
|
|
1927
|
+
console.log(" Registered MCP server in .mcp.json");
|
|
1928
|
+
} else {
|
|
1929
|
+
console.log(" MCP server already registered in .mcp.json");
|
|
1547
1930
|
}
|
|
1548
|
-
|
|
1549
|
-
|
|
1931
|
+
const { count: hooksCount } = installHooks(projectRoot);
|
|
1932
|
+
console.log(` Installed ${hooksCount} hooks in .claude/settings.local.json`);
|
|
1933
|
+
const cmdResult = installCommands(projectRoot);
|
|
1934
|
+
const cmdTotal = cmdResult.installed + cmdResult.updated + cmdResult.skipped;
|
|
1935
|
+
if (cmdResult.installed > 0 || cmdResult.updated > 0) {
|
|
1936
|
+
console.log(` Installed ${cmdTotal} slash commands (${cmdResult.installed} new, ${cmdResult.updated} updated)`);
|
|
1937
|
+
} else {
|
|
1938
|
+
console.log(` ${cmdTotal} slash commands already up to date`);
|
|
1550
1939
|
}
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
SUM(CASE WHEN tool_success = 0 THEN 1 ELSE 0 END) as failures,
|
|
1557
|
-
AVG(tool_output_size) as avg_output_size
|
|
1558
|
-
FROM tool_call_details ${whereClause}
|
|
1559
|
-
GROUP BY session_id ORDER BY call_count DESC`;
|
|
1560
|
-
break;
|
|
1561
|
-
case "day":
|
|
1562
|
-
sql = `SELECT date(created_at) as day, COUNT(*) as call_count, COUNT(DISTINCT tool_name) as unique_tools,
|
|
1563
|
-
SUM(CASE WHEN tool_success = 1 THEN 1 ELSE 0 END) as successes
|
|
1564
|
-
FROM tool_call_details ${whereClause}
|
|
1565
|
-
GROUP BY date(created_at) ORDER BY day DESC`;
|
|
1566
|
-
break;
|
|
1567
|
-
default:
|
|
1568
|
-
sql = `SELECT tool_name, COUNT(*) as call_count,
|
|
1569
|
-
SUM(CASE WHEN tool_success = 1 THEN 1 ELSE 0 END) as successes,
|
|
1570
|
-
SUM(CASE WHEN tool_success = 0 THEN 1 ELSE 0 END) as failures,
|
|
1571
|
-
AVG(tool_output_size) as avg_output_size,
|
|
1572
|
-
AVG(tool_input_size) as avg_input_size
|
|
1573
|
-
FROM tool_call_details ${whereClause}
|
|
1574
|
-
GROUP BY tool_name ORDER BY call_count DESC`;
|
|
1575
|
-
break;
|
|
1940
|
+
const { created: memDirCreated, memoryMdCreated } = initMemoryDir(projectRoot);
|
|
1941
|
+
if (memDirCreated) {
|
|
1942
|
+
console.log(" Created memory directory (~/.claude/projects/.../memory/)");
|
|
1943
|
+
} else {
|
|
1944
|
+
console.log(" Memory directory already exists");
|
|
1576
1945
|
}
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
function getSessionStats(db, opts) {
|
|
1580
|
-
if (opts?.sessionId) {
|
|
1581
|
-
const turns = db.prepare("SELECT COUNT(*) as turn_count, SUM(tool_call_count) as total_tool_calls, SUM(prompt_tokens) as total_prompt_tokens, SUM(response_tokens) as total_response_tokens FROM conversation_turns WHERE session_id = ?").get(opts.sessionId);
|
|
1582
|
-
const toolBreakdown = db.prepare("SELECT tool_name, COUNT(*) as count FROM tool_call_details WHERE session_id = ? GROUP BY tool_name ORDER BY count DESC").all(opts.sessionId);
|
|
1583
|
-
const session = db.prepare("SELECT * FROM sessions WHERE session_id = ?").get(opts.sessionId);
|
|
1584
|
-
return [{
|
|
1585
|
-
session_id: opts.sessionId,
|
|
1586
|
-
status: session?.status ?? "unknown",
|
|
1587
|
-
started_at: session?.started_at ?? null,
|
|
1588
|
-
ended_at: session?.ended_at ?? null,
|
|
1589
|
-
...turns,
|
|
1590
|
-
tool_breakdown: toolBreakdown
|
|
1591
|
-
}];
|
|
1946
|
+
if (memoryMdCreated) {
|
|
1947
|
+
console.log(" Created initial MEMORY.md");
|
|
1592
1948
|
}
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1949
|
+
try {
|
|
1950
|
+
const claudeDirName = ".claude";
|
|
1951
|
+
const encodedRoot = projectRoot.replace(/\//g, "-");
|
|
1952
|
+
const computedMemoryDir = resolve4(homedir2(), claudeDirName, "projects", encodedRoot, "memory");
|
|
1953
|
+
const memFiles = existsSync5(computedMemoryDir) ? readdirSync3(computedMemoryDir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md") : [];
|
|
1954
|
+
if (memFiles.length > 0) {
|
|
1955
|
+
const { getMemoryDb: getMemoryDb2 } = await Promise.resolve().then(() => (init_memory_db(), memory_db_exports));
|
|
1956
|
+
const db = getMemoryDb2();
|
|
1957
|
+
try {
|
|
1958
|
+
const stats = backfillMemoryFiles(db, computedMemoryDir, `init-${Date.now()}`);
|
|
1959
|
+
if (stats.inserted > 0 || stats.updated > 0) {
|
|
1960
|
+
console.log(` Backfilled ${stats.inserted + stats.updated} memory files into database (${stats.inserted} new, ${stats.updated} updated)`);
|
|
1961
|
+
}
|
|
1962
|
+
} finally {
|
|
1963
|
+
db.close();
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
} catch (_backfillErr) {
|
|
1967
|
+
}
|
|
1968
|
+
console.log(" Databases will auto-create on first session");
|
|
1969
|
+
console.log("");
|
|
1970
|
+
console.log("Massu AI is ready. Start a Claude Code session to begin.");
|
|
1971
|
+
console.log("");
|
|
1972
|
+
console.log("Next steps:");
|
|
1973
|
+
console.log(" claude # Start a session (hooks activate automatically)");
|
|
1974
|
+
console.log(" npx massu doctor # Verify installation health");
|
|
1975
|
+
console.log("");
|
|
1976
|
+
console.log("Documentation: https://massu.ai/docs");
|
|
1977
|
+
console.log("");
|
|
1606
1978
|
}
|
|
1607
|
-
function
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
const
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
db_page_size: pageSize,
|
|
1619
|
-
estimated_size_mb: Math.round(pageCount * pageSize / (1024 * 1024) * 100) / 100
|
|
1979
|
+
function capitalize(str) {
|
|
1980
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
1981
|
+
}
|
|
1982
|
+
function formatName(name) {
|
|
1983
|
+
const names = {
|
|
1984
|
+
nextjs: "Next.js",
|
|
1985
|
+
sveltekit: "SvelteKit",
|
|
1986
|
+
nuxt: "Nuxt",
|
|
1987
|
+
angular: "Angular",
|
|
1988
|
+
vue: "Vue",
|
|
1989
|
+
react: "React"
|
|
1620
1990
|
};
|
|
1991
|
+
return names[name] ?? capitalize(name);
|
|
1621
1992
|
}
|
|
1622
|
-
var
|
|
1623
|
-
|
|
1993
|
+
var __filename2, __dirname2;
|
|
1994
|
+
var init_init = __esm({
|
|
1995
|
+
"src/commands/init.ts"() {
|
|
1624
1996
|
"use strict";
|
|
1997
|
+
init_memory_file_ingest();
|
|
1625
1998
|
init_config();
|
|
1999
|
+
init_install_commands();
|
|
2000
|
+
__filename2 = fileURLToPath2(import.meta.url);
|
|
2001
|
+
__dirname2 = dirname4(__filename2);
|
|
1626
2002
|
}
|
|
1627
2003
|
});
|
|
1628
2004
|
|
|
@@ -1921,18 +2297,18 @@ __export(doctor_exports, {
|
|
|
1921
2297
|
runDoctor: () => runDoctor,
|
|
1922
2298
|
runValidateConfig: () => runValidateConfig
|
|
1923
2299
|
});
|
|
1924
|
-
import { existsSync as
|
|
2300
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync as readdirSync4 } from "fs";
|
|
1925
2301
|
import { resolve as resolve5, dirname as dirname5 } from "path";
|
|
1926
2302
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1927
|
-
import { parse as
|
|
2303
|
+
import { parse as parseYaml3 } from "yaml";
|
|
1928
2304
|
function checkConfig(projectRoot) {
|
|
1929
2305
|
const configPath = resolve5(projectRoot, "massu.config.yaml");
|
|
1930
|
-
if (!
|
|
2306
|
+
if (!existsSync6(configPath)) {
|
|
1931
2307
|
return { name: "Configuration", status: "fail", detail: "massu.config.yaml not found. Run: npx massu init" };
|
|
1932
2308
|
}
|
|
1933
2309
|
try {
|
|
1934
|
-
const content =
|
|
1935
|
-
const parsed =
|
|
2310
|
+
const content = readFileSync5(configPath, "utf-8");
|
|
2311
|
+
const parsed = parseYaml3(content);
|
|
1936
2312
|
if (!parsed || typeof parsed !== "object") {
|
|
1937
2313
|
return { name: "Configuration", status: "fail", detail: "massu.config.yaml is empty or invalid YAML" };
|
|
1938
2314
|
}
|
|
@@ -1943,11 +2319,11 @@ function checkConfig(projectRoot) {
|
|
|
1943
2319
|
}
|
|
1944
2320
|
function checkMcpServer(projectRoot) {
|
|
1945
2321
|
const mcpPath = getResolvedPaths().mcpJsonPath;
|
|
1946
|
-
if (!
|
|
2322
|
+
if (!existsSync6(mcpPath)) {
|
|
1947
2323
|
return { name: "MCP Server", status: "fail", detail: ".mcp.json not found. Run: npx massu init" };
|
|
1948
2324
|
}
|
|
1949
2325
|
try {
|
|
1950
|
-
const content = JSON.parse(
|
|
2326
|
+
const content = JSON.parse(readFileSync5(mcpPath, "utf-8"));
|
|
1951
2327
|
const servers = content.mcpServers ?? {};
|
|
1952
2328
|
if (!servers.massu) {
|
|
1953
2329
|
return { name: "MCP Server", status: "fail", detail: "massu not registered in .mcp.json. Run: npx massu init" };
|
|
@@ -1959,11 +2335,11 @@ function checkMcpServer(projectRoot) {
|
|
|
1959
2335
|
}
|
|
1960
2336
|
function checkHooksConfig(projectRoot) {
|
|
1961
2337
|
const settingsPath = getResolvedPaths().settingsLocalPath;
|
|
1962
|
-
if (!
|
|
2338
|
+
if (!existsSync6(settingsPath)) {
|
|
1963
2339
|
return { name: "Hooks Config", status: "fail", detail: ".claude/settings.local.json not found. Run: npx massu init" };
|
|
1964
2340
|
}
|
|
1965
2341
|
try {
|
|
1966
|
-
const content = JSON.parse(
|
|
2342
|
+
const content = JSON.parse(readFileSync5(settingsPath, "utf-8"));
|
|
1967
2343
|
if (!content.hooks) {
|
|
1968
2344
|
return { name: "Hooks Config", status: "fail", detail: "No hooks configured. Run: npx massu install-hooks" };
|
|
1969
2345
|
}
|
|
@@ -1989,9 +2365,9 @@ function checkHooksConfig(projectRoot) {
|
|
|
1989
2365
|
function checkHookFiles(projectRoot) {
|
|
1990
2366
|
const nodeModulesHooksDir = resolve5(projectRoot, "node_modules/@massu/core/dist/hooks");
|
|
1991
2367
|
let hooksDir = nodeModulesHooksDir;
|
|
1992
|
-
if (!
|
|
2368
|
+
if (!existsSync6(nodeModulesHooksDir)) {
|
|
1993
2369
|
const devHooksDir = resolve5(__dirname3, "../../dist/hooks");
|
|
1994
|
-
if (
|
|
2370
|
+
if (existsSync6(devHooksDir)) {
|
|
1995
2371
|
hooksDir = devHooksDir;
|
|
1996
2372
|
} else {
|
|
1997
2373
|
return { name: "Hook Files", status: "fail", detail: "Compiled hooks not found. Run: npm install @massu/core" };
|
|
@@ -1999,7 +2375,7 @@ function checkHookFiles(projectRoot) {
|
|
|
1999
2375
|
}
|
|
2000
2376
|
const missing = [];
|
|
2001
2377
|
for (const hookFile of EXPECTED_HOOKS) {
|
|
2002
|
-
if (!
|
|
2378
|
+
if (!existsSync6(resolve5(hooksDir, hookFile))) {
|
|
2003
2379
|
missing.push(hookFile);
|
|
2004
2380
|
}
|
|
2005
2381
|
}
|
|
@@ -2026,7 +2402,7 @@ function checkNodeVersion() {
|
|
|
2026
2402
|
}
|
|
2027
2403
|
async function checkGitRepo(projectRoot) {
|
|
2028
2404
|
const gitDir = resolve5(projectRoot, ".git");
|
|
2029
|
-
if (!
|
|
2405
|
+
if (!existsSync6(gitDir)) {
|
|
2030
2406
|
return { name: "Git Repository", status: "warn", detail: "Not a git repository (optional but recommended)" };
|
|
2031
2407
|
}
|
|
2032
2408
|
try {
|
|
@@ -2044,7 +2420,7 @@ async function checkGitRepo(projectRoot) {
|
|
|
2044
2420
|
}
|
|
2045
2421
|
function checkKnowledgeDb(projectRoot) {
|
|
2046
2422
|
const knowledgeDbPath = getResolvedPaths().memoryDbPath;
|
|
2047
|
-
if (!
|
|
2423
|
+
if (!existsSync6(knowledgeDbPath)) {
|
|
2048
2424
|
return {
|
|
2049
2425
|
name: "Knowledge DB",
|
|
2050
2426
|
status: "warn",
|
|
@@ -2055,7 +2431,7 @@ function checkKnowledgeDb(projectRoot) {
|
|
|
2055
2431
|
}
|
|
2056
2432
|
function checkMemoryDir(_projectRoot2) {
|
|
2057
2433
|
const memoryDir = getResolvedPaths().memoryDir;
|
|
2058
|
-
if (!
|
|
2434
|
+
if (!existsSync6(memoryDir)) {
|
|
2059
2435
|
return {
|
|
2060
2436
|
name: "Memory Directory",
|
|
2061
2437
|
status: "warn",
|
|
@@ -2066,7 +2442,7 @@ function checkMemoryDir(_projectRoot2) {
|
|
|
2066
2442
|
}
|
|
2067
2443
|
function checkShellHooksWired(_projectRoot2) {
|
|
2068
2444
|
const settingsPath = getResolvedPaths().settingsLocalPath;
|
|
2069
|
-
if (!
|
|
2445
|
+
if (!existsSync6(settingsPath)) {
|
|
2070
2446
|
return {
|
|
2071
2447
|
name: "Shell Hooks",
|
|
2072
2448
|
status: "fail",
|
|
@@ -2074,7 +2450,7 @@ function checkShellHooksWired(_projectRoot2) {
|
|
|
2074
2450
|
};
|
|
2075
2451
|
}
|
|
2076
2452
|
try {
|
|
2077
|
-
const content = JSON.parse(
|
|
2453
|
+
const content = JSON.parse(readFileSync5(settingsPath, "utf-8"));
|
|
2078
2454
|
const hooks = content.hooks ?? {};
|
|
2079
2455
|
const hasSessionStart = Array.isArray(hooks.SessionStart) && hooks.SessionStart.length > 0;
|
|
2080
2456
|
const hasPreToolUse = Array.isArray(hooks.PreToolUse) && hooks.PreToolUse.length > 0;
|
|
@@ -2126,7 +2502,7 @@ function checkPythonHealth(projectRoot) {
|
|
|
2126
2502
|
const config = getConfig();
|
|
2127
2503
|
if (!config.python?.root) return null;
|
|
2128
2504
|
const pythonRoot = resolve5(projectRoot, config.python.root);
|
|
2129
|
-
if (!
|
|
2505
|
+
if (!existsSync6(pythonRoot)) {
|
|
2130
2506
|
return {
|
|
2131
2507
|
name: "Python",
|
|
2132
2508
|
status: "fail",
|
|
@@ -2140,15 +2516,15 @@ function checkPythonHealth(projectRoot) {
|
|
|
2140
2516
|
function scanDir(dir, depth) {
|
|
2141
2517
|
if (depth > 5) return;
|
|
2142
2518
|
try {
|
|
2143
|
-
const entries =
|
|
2519
|
+
const entries = readdirSync4(dir, { withFileTypes: true });
|
|
2144
2520
|
for (const entry of entries) {
|
|
2145
2521
|
if (entry.isDirectory()) {
|
|
2146
2522
|
const excludeDirs = config.python?.exclude_dirs || ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"];
|
|
2147
2523
|
if (!excludeDirs.includes(entry.name)) {
|
|
2148
2524
|
const subdir = resolve5(dir, entry.name);
|
|
2149
|
-
if (depth <= 2 && !
|
|
2525
|
+
if (depth <= 2 && !existsSync6(resolve5(subdir, "__init__.py"))) {
|
|
2150
2526
|
try {
|
|
2151
|
-
const subEntries =
|
|
2527
|
+
const subEntries = readdirSync4(subdir);
|
|
2152
2528
|
if (subEntries.some((f) => f.endsWith(".py") && f !== "__init__.py")) {
|
|
2153
2529
|
initPyMissing.push(entry.name);
|
|
2154
2530
|
}
|
|
@@ -2242,15 +2618,15 @@ async function runDoctor() {
|
|
|
2242
2618
|
async function runValidateConfig() {
|
|
2243
2619
|
const projectRoot = process.cwd();
|
|
2244
2620
|
const configPath = resolve5(projectRoot, "massu.config.yaml");
|
|
2245
|
-
if (!
|
|
2621
|
+
if (!existsSync6(configPath)) {
|
|
2246
2622
|
console.error("Error: massu.config.yaml not found in current directory");
|
|
2247
2623
|
console.error("Run: npx massu init");
|
|
2248
2624
|
process.exit(1);
|
|
2249
2625
|
return;
|
|
2250
2626
|
}
|
|
2251
2627
|
try {
|
|
2252
|
-
const content =
|
|
2253
|
-
const parsed =
|
|
2628
|
+
const content = readFileSync5(configPath, "utf-8");
|
|
2629
|
+
const parsed = parseYaml3(content);
|
|
2254
2630
|
if (!parsed || typeof parsed !== "object") {
|
|
2255
2631
|
console.error("Error: massu.config.yaml is empty or not a valid YAML object");
|
|
2256
2632
|
process.exit(1);
|
|
@@ -2331,11 +2707,11 @@ var init_install_hooks = __esm({
|
|
|
2331
2707
|
|
|
2332
2708
|
// src/db.ts
|
|
2333
2709
|
import Database2 from "better-sqlite3";
|
|
2334
|
-
import { dirname as dirname6, join } from "path";
|
|
2335
|
-
import { existsSync as
|
|
2710
|
+
import { dirname as dirname6, join as join3 } from "path";
|
|
2711
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readdirSync as readdirSync5, statSync as statSync2 } from "fs";
|
|
2336
2712
|
function getCodeGraphDb() {
|
|
2337
2713
|
const dbPath = getResolvedPaths().codegraphDbPath;
|
|
2338
|
-
if (!
|
|
2714
|
+
if (!existsSync7(dbPath)) {
|
|
2339
2715
|
throw new Error(`CodeGraph database not found at ${dbPath}. Run 'npx @colbymchenry/codegraph sync' first.`);
|
|
2340
2716
|
}
|
|
2341
2717
|
const db = new Database2(dbPath, { readonly: true });
|
|
@@ -2345,7 +2721,7 @@ function getCodeGraphDb() {
|
|
|
2345
2721
|
function getDataDb() {
|
|
2346
2722
|
const dbPath = getResolvedPaths().dataDbPath;
|
|
2347
2723
|
const dir = dirname6(dbPath);
|
|
2348
|
-
if (!
|
|
2724
|
+
if (!existsSync7(dir)) {
|
|
2349
2725
|
mkdirSync4(dir, { recursive: true });
|
|
2350
2726
|
}
|
|
2351
2727
|
const db = new Database2(dbPath);
|
|
@@ -2616,14 +2992,14 @@ function isPythonDataStale(dataDb2, pythonRoot) {
|
|
|
2616
2992
|
const lastBuildTime = new Date(lastBuild.value).getTime();
|
|
2617
2993
|
function checkDir(dir) {
|
|
2618
2994
|
try {
|
|
2619
|
-
const entries =
|
|
2995
|
+
const entries = readdirSync5(dir, { withFileTypes: true });
|
|
2620
2996
|
for (const entry of entries) {
|
|
2621
|
-
const fullPath =
|
|
2997
|
+
const fullPath = join3(dir, entry.name);
|
|
2622
2998
|
if (entry.isDirectory()) {
|
|
2623
2999
|
if (["__pycache__", ".venv", "venv", "node_modules", ".mypy_cache", ".pytest_cache"].includes(entry.name)) continue;
|
|
2624
3000
|
if (checkDir(fullPath)) return true;
|
|
2625
3001
|
} else if (entry.name.endsWith(".py")) {
|
|
2626
|
-
if (
|
|
3002
|
+
if (statSync2(fullPath).mtimeMs > lastBuildTime) return true;
|
|
2627
3003
|
}
|
|
2628
3004
|
}
|
|
2629
3005
|
} catch {
|
|
@@ -2719,8 +3095,8 @@ var init_rules = __esm({
|
|
|
2719
3095
|
});
|
|
2720
3096
|
|
|
2721
3097
|
// src/import-resolver.ts
|
|
2722
|
-
import { readFileSync as
|
|
2723
|
-
import { resolve as resolve7, dirname as dirname7, join as
|
|
3098
|
+
import { readFileSync as readFileSync6, existsSync as existsSync8, statSync as statSync3 } from "fs";
|
|
3099
|
+
import { resolve as resolve7, dirname as dirname7, join as join4 } from "path";
|
|
2724
3100
|
function parseImports(source) {
|
|
2725
3101
|
const imports = [];
|
|
2726
3102
|
const lines = source.split("\n");
|
|
@@ -2780,19 +3156,19 @@ function resolveImportPath(specifier, fromFile) {
|
|
|
2780
3156
|
} else {
|
|
2781
3157
|
basePath = resolve7(dirname7(fromFile), specifier);
|
|
2782
3158
|
}
|
|
2783
|
-
if (
|
|
3159
|
+
if (existsSync8(basePath) && !isDirectory(basePath)) {
|
|
2784
3160
|
return toRelative(basePath);
|
|
2785
3161
|
}
|
|
2786
3162
|
const resolvedPaths = getResolvedPaths();
|
|
2787
3163
|
for (const ext of resolvedPaths.extensions) {
|
|
2788
3164
|
const withExt = basePath + ext;
|
|
2789
|
-
if (
|
|
3165
|
+
if (existsSync8(withExt)) {
|
|
2790
3166
|
return toRelative(withExt);
|
|
2791
3167
|
}
|
|
2792
3168
|
}
|
|
2793
3169
|
for (const indexFile of resolvedPaths.indexFiles) {
|
|
2794
|
-
const indexPath =
|
|
2795
|
-
if (
|
|
3170
|
+
const indexPath = join4(basePath, indexFile);
|
|
3171
|
+
if (existsSync8(indexPath)) {
|
|
2796
3172
|
return toRelative(indexPath);
|
|
2797
3173
|
}
|
|
2798
3174
|
}
|
|
@@ -2800,7 +3176,7 @@ function resolveImportPath(specifier, fromFile) {
|
|
|
2800
3176
|
}
|
|
2801
3177
|
function isDirectory(path) {
|
|
2802
3178
|
try {
|
|
2803
|
-
return
|
|
3179
|
+
return statSync3(path).isDirectory();
|
|
2804
3180
|
} catch {
|
|
2805
3181
|
return false;
|
|
2806
3182
|
}
|
|
@@ -2829,10 +3205,10 @@ function buildImportIndex(dataDb2, codegraphDb2) {
|
|
|
2829
3205
|
let batch = [];
|
|
2830
3206
|
for (const file of files) {
|
|
2831
3207
|
const absPath = ensureWithinRoot(resolve7(projectRoot, file.path), projectRoot);
|
|
2832
|
-
if (!
|
|
3208
|
+
if (!existsSync8(absPath)) continue;
|
|
2833
3209
|
let source;
|
|
2834
3210
|
try {
|
|
2835
|
-
source =
|
|
3211
|
+
source = readFileSync6(absPath, "utf-8");
|
|
2836
3212
|
} catch {
|
|
2837
3213
|
continue;
|
|
2838
3214
|
}
|
|
@@ -2868,15 +3244,15 @@ var init_import_resolver = __esm({
|
|
|
2868
3244
|
});
|
|
2869
3245
|
|
|
2870
3246
|
// src/trpc-index.ts
|
|
2871
|
-
import { readFileSync as
|
|
2872
|
-
import { resolve as resolve8, join as
|
|
3247
|
+
import { readFileSync as readFileSync7, existsSync as existsSync9, readdirSync as readdirSync6 } from "fs";
|
|
3248
|
+
import { resolve as resolve8, join as join5 } from "path";
|
|
2873
3249
|
function parseRootRouter() {
|
|
2874
3250
|
const paths = getResolvedPaths();
|
|
2875
3251
|
const rootPath = paths.rootRouterPath;
|
|
2876
|
-
if (!
|
|
3252
|
+
if (!existsSync9(rootPath)) {
|
|
2877
3253
|
throw new Error(`Root router not found at ${rootPath}`);
|
|
2878
3254
|
}
|
|
2879
|
-
const source =
|
|
3255
|
+
const source = readFileSync7(rootPath, "utf-8");
|
|
2880
3256
|
const mappings = [];
|
|
2881
3257
|
const importMap = /* @__PURE__ */ new Map();
|
|
2882
3258
|
const importRegex = /import\s+\{[^}]*?(\w+Router)[^}]*\}\s+from\s+['"]\.\/routers\/([^'"]+)['"]/g;
|
|
@@ -2888,12 +3264,12 @@ function parseRootRouter() {
|
|
|
2888
3264
|
for (const ext of [".ts", ".tsx", ""]) {
|
|
2889
3265
|
const candidate = fullPath + ext;
|
|
2890
3266
|
const routersRelPath = getConfig().paths.routers ?? "src/server/api/routers";
|
|
2891
|
-
if (
|
|
3267
|
+
if (existsSync9(candidate)) {
|
|
2892
3268
|
filePath = routersRelPath + "/" + filePath + ext;
|
|
2893
3269
|
break;
|
|
2894
3270
|
}
|
|
2895
|
-
const indexCandidate =
|
|
2896
|
-
if (
|
|
3271
|
+
const indexCandidate = join5(fullPath, "index.ts");
|
|
3272
|
+
if (existsSync9(indexCandidate)) {
|
|
2897
3273
|
filePath = routersRelPath + "/" + filePath + "/index.ts";
|
|
2898
3274
|
break;
|
|
2899
3275
|
}
|
|
@@ -2913,8 +3289,8 @@ function parseRootRouter() {
|
|
|
2913
3289
|
}
|
|
2914
3290
|
function extractProcedures(routerFilePath) {
|
|
2915
3291
|
const absPath = resolve8(getProjectRoot(), routerFilePath);
|
|
2916
|
-
if (!
|
|
2917
|
-
const source =
|
|
3292
|
+
if (!existsSync9(absPath)) return [];
|
|
3293
|
+
const source = readFileSync7(absPath, "utf-8");
|
|
2918
3294
|
const procedures = [];
|
|
2919
3295
|
const seen = /* @__PURE__ */ new Set();
|
|
2920
3296
|
const procRegex = /(\w+)\s*:\s*(protected|public)Procedure/g;
|
|
@@ -2943,21 +3319,21 @@ function findUICallSites(routerKey, procedureName) {
|
|
|
2943
3319
|
];
|
|
2944
3320
|
const searchPattern = `api.${routerKey}.${procedureName}`;
|
|
2945
3321
|
for (const dir of searchDirs) {
|
|
2946
|
-
if (!
|
|
3322
|
+
if (!existsSync9(dir)) continue;
|
|
2947
3323
|
searchDirectory(dir, searchPattern, callSites);
|
|
2948
3324
|
}
|
|
2949
3325
|
return callSites;
|
|
2950
3326
|
}
|
|
2951
3327
|
function searchDirectory(dir, pattern, results) {
|
|
2952
|
-
const entries =
|
|
3328
|
+
const entries = readdirSync6(dir, { withFileTypes: true });
|
|
2953
3329
|
for (const entry of entries) {
|
|
2954
|
-
const fullPath =
|
|
3330
|
+
const fullPath = join5(dir, entry.name);
|
|
2955
3331
|
if (entry.isDirectory()) {
|
|
2956
3332
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
2957
3333
|
searchDirectory(fullPath, pattern, results);
|
|
2958
3334
|
} else if (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) {
|
|
2959
3335
|
try {
|
|
2960
|
-
const source =
|
|
3336
|
+
const source = readFileSync7(fullPath, "utf-8");
|
|
2961
3337
|
const lines = source.split("\n");
|
|
2962
3338
|
for (let i = 0; i < lines.length; i++) {
|
|
2963
3339
|
if (lines[i].includes(pattern)) {
|
|
@@ -3020,7 +3396,7 @@ var init_trpc_index = __esm({
|
|
|
3020
3396
|
});
|
|
3021
3397
|
|
|
3022
3398
|
// src/page-deps.ts
|
|
3023
|
-
import { readFileSync as
|
|
3399
|
+
import { readFileSync as readFileSync8, existsSync as existsSync10 } from "fs";
|
|
3024
3400
|
import { resolve as resolve9 } from "path";
|
|
3025
3401
|
function deriveRoute(pageFile) {
|
|
3026
3402
|
let route = pageFile.replace(/^src\/app/, "").replace(/\/page\.tsx?$/, "").replace(/\/page\.jsx?$/, "");
|
|
@@ -3060,9 +3436,9 @@ function findRouterCalls(files) {
|
|
|
3060
3436
|
const projectRoot = getProjectRoot();
|
|
3061
3437
|
for (const file of files) {
|
|
3062
3438
|
const absPath = ensureWithinRoot(resolve9(projectRoot, file), projectRoot);
|
|
3063
|
-
if (!
|
|
3439
|
+
if (!existsSync10(absPath)) continue;
|
|
3064
3440
|
try {
|
|
3065
|
-
const source =
|
|
3441
|
+
const source = readFileSync8(absPath, "utf-8");
|
|
3066
3442
|
const apiCallRegex = /api\.(\w+)\.\w+/g;
|
|
3067
3443
|
let match;
|
|
3068
3444
|
while ((match = apiCallRegex.exec(source)) !== null) {
|
|
@@ -3081,9 +3457,9 @@ function findTablesFromRouters(routerNames, dataDb2) {
|
|
|
3081
3457
|
).all(routerName);
|
|
3082
3458
|
for (const proc of procs) {
|
|
3083
3459
|
const absPath = ensureWithinRoot(resolve9(getProjectRoot(), proc.router_file), getProjectRoot());
|
|
3084
|
-
if (!
|
|
3460
|
+
if (!existsSync10(absPath)) continue;
|
|
3085
3461
|
try {
|
|
3086
|
-
const source =
|
|
3462
|
+
const source = readFileSync8(absPath, "utf-8");
|
|
3087
3463
|
const dbPattern = getConfig().dbAccessPattern ?? "ctx.db.{table}";
|
|
3088
3464
|
const regexStr = dbPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace("\\{table\\}", "(\\w+)");
|
|
3089
3465
|
const tableRegex = new RegExp(regexStr + "\\.", "g");
|
|
@@ -3352,14 +3728,14 @@ var init_domains = __esm({
|
|
|
3352
3728
|
});
|
|
3353
3729
|
|
|
3354
3730
|
// src/schema-mapper.ts
|
|
3355
|
-
import { readFileSync as
|
|
3356
|
-
import { join as
|
|
3731
|
+
import { readFileSync as readFileSync9, existsSync as existsSync11, readdirSync as readdirSync7 } from "fs";
|
|
3732
|
+
import { join as join6 } from "path";
|
|
3357
3733
|
function parsePrismaSchema() {
|
|
3358
3734
|
const schemaPath = getResolvedPaths().prismaSchemaPath;
|
|
3359
|
-
if (!
|
|
3735
|
+
if (!existsSync11(schemaPath)) {
|
|
3360
3736
|
throw new Error(`Prisma schema not found at ${schemaPath}`);
|
|
3361
3737
|
}
|
|
3362
|
-
const source =
|
|
3738
|
+
const source = readFileSync9(schemaPath, "utf-8");
|
|
3363
3739
|
const models = [];
|
|
3364
3740
|
const sourceLines = source.split("\n");
|
|
3365
3741
|
let i = 0;
|
|
@@ -3417,14 +3793,14 @@ function toSnakeCase(str) {
|
|
|
3417
3793
|
function findColumnUsageInRouters(tableName) {
|
|
3418
3794
|
const usage = /* @__PURE__ */ new Map();
|
|
3419
3795
|
const routersDir = getResolvedPaths().routersDir;
|
|
3420
|
-
if (!
|
|
3796
|
+
if (!existsSync11(routersDir)) return usage;
|
|
3421
3797
|
scanDirectory(routersDir, tableName, usage);
|
|
3422
3798
|
return usage;
|
|
3423
3799
|
}
|
|
3424
3800
|
function scanDirectory(dir, tableName, usage) {
|
|
3425
|
-
const entries =
|
|
3801
|
+
const entries = readdirSync7(dir, { withFileTypes: true });
|
|
3426
3802
|
for (const entry of entries) {
|
|
3427
|
-
const fullPath =
|
|
3803
|
+
const fullPath = join6(dir, entry.name);
|
|
3428
3804
|
if (entry.isDirectory()) {
|
|
3429
3805
|
scanDirectory(fullPath, tableName, usage);
|
|
3430
3806
|
} else if (entry.name.endsWith(".ts")) {
|
|
@@ -3434,7 +3810,7 @@ function scanDirectory(dir, tableName, usage) {
|
|
|
3434
3810
|
}
|
|
3435
3811
|
function scanFile(absPath, tableName, usage) {
|
|
3436
3812
|
try {
|
|
3437
|
-
const source =
|
|
3813
|
+
const source = readFileSync9(absPath, "utf-8");
|
|
3438
3814
|
if (!source.includes(tableName)) return;
|
|
3439
3815
|
const relPath = absPath.slice(getProjectRoot().length + 1);
|
|
3440
3816
|
const lines = source.split("\n");
|
|
@@ -3479,15 +3855,15 @@ function detectMismatches(models) {
|
|
|
3479
3855
|
}
|
|
3480
3856
|
function findFilesUsingColumn(dir, column, tableName) {
|
|
3481
3857
|
const result = [];
|
|
3482
|
-
if (!
|
|
3483
|
-
const entries =
|
|
3858
|
+
if (!existsSync11(dir)) return result;
|
|
3859
|
+
const entries = readdirSync7(dir, { withFileTypes: true });
|
|
3484
3860
|
for (const entry of entries) {
|
|
3485
|
-
const fullPath =
|
|
3861
|
+
const fullPath = join6(dir, entry.name);
|
|
3486
3862
|
if (entry.isDirectory()) {
|
|
3487
3863
|
result.push(...findFilesUsingColumn(fullPath, column, tableName));
|
|
3488
3864
|
} else if (entry.name.endsWith(".ts")) {
|
|
3489
3865
|
try {
|
|
3490
|
-
const source =
|
|
3866
|
+
const source = readFileSync9(fullPath, "utf-8");
|
|
3491
3867
|
if (source.includes(tableName) && source.includes(column)) {
|
|
3492
3868
|
result.push(fullPath.slice(getProjectRoot().length + 1));
|
|
3493
3869
|
}
|
|
@@ -3632,8 +4008,8 @@ var init_import_parser = __esm({
|
|
|
3632
4008
|
});
|
|
3633
4009
|
|
|
3634
4010
|
// src/python/import-resolver.ts
|
|
3635
|
-
import { readFileSync as
|
|
3636
|
-
import { resolve as resolve11, join as
|
|
4011
|
+
import { readFileSync as readFileSync10, existsSync as existsSync12, readdirSync as readdirSync8 } from "fs";
|
|
4012
|
+
import { resolve as resolve11, join as join7, relative as relative2, dirname as dirname8 } from "path";
|
|
3637
4013
|
function resolvePythonModulePath(module, fromFile, pythonRoot, level) {
|
|
3638
4014
|
const projectRoot = getProjectRoot();
|
|
3639
4015
|
if (level > 0) {
|
|
@@ -3644,36 +4020,36 @@ function resolvePythonModulePath(module, fromFile, pythonRoot, level) {
|
|
|
3644
4020
|
const modulePart = module.replace(/^\.+/, "");
|
|
3645
4021
|
if (modulePart) {
|
|
3646
4022
|
const parts2 = modulePart.split(".");
|
|
3647
|
-
return tryResolvePythonPath(
|
|
4023
|
+
return tryResolvePythonPath(join7(baseDir, ...parts2), projectRoot);
|
|
3648
4024
|
}
|
|
3649
4025
|
return tryResolvePythonPath(baseDir, projectRoot);
|
|
3650
4026
|
}
|
|
3651
4027
|
const parts = module.split(".");
|
|
3652
|
-
const candidate =
|
|
4028
|
+
const candidate = join7(resolve11(projectRoot, pythonRoot), ...parts);
|
|
3653
4029
|
return tryResolvePythonPath(candidate, projectRoot);
|
|
3654
4030
|
}
|
|
3655
4031
|
function tryResolvePythonPath(basePath, projectRoot) {
|
|
3656
|
-
if (
|
|
3657
|
-
return
|
|
4032
|
+
if (existsSync12(basePath + ".py")) {
|
|
4033
|
+
return relative2(projectRoot, basePath + ".py");
|
|
3658
4034
|
}
|
|
3659
|
-
if (
|
|
3660
|
-
return
|
|
4035
|
+
if (existsSync12(join7(basePath, "__init__.py"))) {
|
|
4036
|
+
return relative2(projectRoot, join7(basePath, "__init__.py"));
|
|
3661
4037
|
}
|
|
3662
|
-
if (basePath.endsWith(".py") &&
|
|
3663
|
-
return
|
|
4038
|
+
if (basePath.endsWith(".py") && existsSync12(basePath)) {
|
|
4039
|
+
return relative2(projectRoot, basePath);
|
|
3664
4040
|
}
|
|
3665
4041
|
return null;
|
|
3666
4042
|
}
|
|
3667
4043
|
function walkPythonFiles(dir, excludeDirs) {
|
|
3668
4044
|
const files = [];
|
|
3669
4045
|
try {
|
|
3670
|
-
const entries =
|
|
4046
|
+
const entries = readdirSync8(dir, { withFileTypes: true });
|
|
3671
4047
|
for (const entry of entries) {
|
|
3672
4048
|
if (entry.isDirectory()) {
|
|
3673
4049
|
if (excludeDirs.includes(entry.name)) continue;
|
|
3674
|
-
files.push(...walkPythonFiles(
|
|
4050
|
+
files.push(...walkPythonFiles(join7(dir, entry.name), excludeDirs));
|
|
3675
4051
|
} else if (entry.name.endsWith(".py")) {
|
|
3676
|
-
files.push(
|
|
4052
|
+
files.push(join7(dir, entry.name));
|
|
3677
4053
|
}
|
|
3678
4054
|
}
|
|
3679
4055
|
} catch {
|
|
@@ -3696,10 +4072,10 @@ function buildPythonImportIndex(dataDb2, pythonRoot, excludeDirs = ["__pycache__
|
|
|
3696
4072
|
});
|
|
3697
4073
|
const batch = [];
|
|
3698
4074
|
for (const absFile of files) {
|
|
3699
|
-
const relFile =
|
|
4075
|
+
const relFile = relative2(projectRoot, absFile);
|
|
3700
4076
|
let source;
|
|
3701
4077
|
try {
|
|
3702
|
-
source =
|
|
4078
|
+
source = readFileSync10(absFile, "utf-8");
|
|
3703
4079
|
} catch {
|
|
3704
4080
|
continue;
|
|
3705
4081
|
}
|
|
@@ -3965,18 +4341,18 @@ var init_route_parser = __esm({
|
|
|
3965
4341
|
});
|
|
3966
4342
|
|
|
3967
4343
|
// src/python/route-indexer.ts
|
|
3968
|
-
import { readFileSync as
|
|
3969
|
-
import { join as
|
|
4344
|
+
import { readFileSync as readFileSync11, readdirSync as readdirSync9 } from "fs";
|
|
4345
|
+
import { join as join8, relative as relative3 } from "path";
|
|
3970
4346
|
function walkPyFiles(dir, excludeDirs) {
|
|
3971
4347
|
const files = [];
|
|
3972
4348
|
try {
|
|
3973
|
-
const entries =
|
|
4349
|
+
const entries = readdirSync9(dir, { withFileTypes: true });
|
|
3974
4350
|
for (const entry of entries) {
|
|
3975
4351
|
if (entry.isDirectory()) {
|
|
3976
4352
|
if (excludeDirs.includes(entry.name)) continue;
|
|
3977
|
-
files.push(...walkPyFiles(
|
|
4353
|
+
files.push(...walkPyFiles(join8(dir, entry.name), excludeDirs));
|
|
3978
4354
|
} else if (entry.name.endsWith(".py")) {
|
|
3979
|
-
files.push(
|
|
4355
|
+
files.push(join8(dir, entry.name));
|
|
3980
4356
|
}
|
|
3981
4357
|
}
|
|
3982
4358
|
} catch {
|
|
@@ -3985,7 +4361,7 @@ function walkPyFiles(dir, excludeDirs) {
|
|
|
3985
4361
|
}
|
|
3986
4362
|
function buildPythonRouteIndex(dataDb2, pythonRoot, excludeDirs = ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]) {
|
|
3987
4363
|
const projectRoot = getProjectRoot();
|
|
3988
|
-
const absRoot =
|
|
4364
|
+
const absRoot = join8(projectRoot, pythonRoot);
|
|
3989
4365
|
dataDb2.exec("DELETE FROM massu_py_routes");
|
|
3990
4366
|
dataDb2.exec("DELETE FROM massu_py_route_callers");
|
|
3991
4367
|
const insertStmt = dataDb2.prepare(
|
|
@@ -3995,10 +4371,10 @@ function buildPythonRouteIndex(dataDb2, pythonRoot, excludeDirs = ["__pycache__"
|
|
|
3995
4371
|
let count = 0;
|
|
3996
4372
|
dataDb2.transaction(() => {
|
|
3997
4373
|
for (const absFile of files) {
|
|
3998
|
-
const relFile =
|
|
4374
|
+
const relFile = relative3(projectRoot, absFile);
|
|
3999
4375
|
let source;
|
|
4000
4376
|
try {
|
|
4001
|
-
source =
|
|
4377
|
+
source = readFileSync11(absFile, "utf-8");
|
|
4002
4378
|
} catch {
|
|
4003
4379
|
continue;
|
|
4004
4380
|
}
|
|
@@ -4209,18 +4585,18 @@ var init_model_parser = __esm({
|
|
|
4209
4585
|
});
|
|
4210
4586
|
|
|
4211
4587
|
// src/python/model-indexer.ts
|
|
4212
|
-
import { readFileSync as
|
|
4213
|
-
import { join as
|
|
4588
|
+
import { readFileSync as readFileSync12, readdirSync as readdirSync10 } from "fs";
|
|
4589
|
+
import { join as join9, relative as relative4 } from "path";
|
|
4214
4590
|
function walkPyFiles2(dir, excludeDirs) {
|
|
4215
4591
|
const files = [];
|
|
4216
4592
|
try {
|
|
4217
|
-
const entries =
|
|
4593
|
+
const entries = readdirSync10(dir, { withFileTypes: true });
|
|
4218
4594
|
for (const entry of entries) {
|
|
4219
4595
|
if (entry.isDirectory()) {
|
|
4220
4596
|
if (excludeDirs.includes(entry.name)) continue;
|
|
4221
|
-
files.push(...walkPyFiles2(
|
|
4597
|
+
files.push(...walkPyFiles2(join9(dir, entry.name), excludeDirs));
|
|
4222
4598
|
} else if (entry.name.endsWith(".py")) {
|
|
4223
|
-
files.push(
|
|
4599
|
+
files.push(join9(dir, entry.name));
|
|
4224
4600
|
}
|
|
4225
4601
|
}
|
|
4226
4602
|
} catch {
|
|
@@ -4229,7 +4605,7 @@ function walkPyFiles2(dir, excludeDirs) {
|
|
|
4229
4605
|
}
|
|
4230
4606
|
function buildPythonModelIndex(dataDb2, pythonRoot, excludeDirs = ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]) {
|
|
4231
4607
|
const projectRoot = getProjectRoot();
|
|
4232
|
-
const absRoot =
|
|
4608
|
+
const absRoot = join9(projectRoot, pythonRoot);
|
|
4233
4609
|
dataDb2.exec("DELETE FROM massu_py_models");
|
|
4234
4610
|
dataDb2.exec("DELETE FROM massu_py_fk_edges");
|
|
4235
4611
|
const insertModel = dataDb2.prepare(
|
|
@@ -4242,10 +4618,10 @@ function buildPythonModelIndex(dataDb2, pythonRoot, excludeDirs = ["__pycache__"
|
|
|
4242
4618
|
let count = 0;
|
|
4243
4619
|
dataDb2.transaction(() => {
|
|
4244
4620
|
for (const absFile of files) {
|
|
4245
|
-
const relFile =
|
|
4621
|
+
const relFile = relative4(projectRoot, absFile);
|
|
4246
4622
|
let source;
|
|
4247
4623
|
try {
|
|
4248
|
-
source =
|
|
4624
|
+
source = readFileSync12(absFile, "utf-8");
|
|
4249
4625
|
} catch {
|
|
4250
4626
|
continue;
|
|
4251
4627
|
}
|
|
@@ -4505,19 +4881,19 @@ var init_migration_parser = __esm({
|
|
|
4505
4881
|
});
|
|
4506
4882
|
|
|
4507
4883
|
// src/python/migration-indexer.ts
|
|
4508
|
-
import { readFileSync as
|
|
4509
|
-
import { join as
|
|
4884
|
+
import { readFileSync as readFileSync13, readdirSync as readdirSync11 } from "fs";
|
|
4885
|
+
import { join as join10, relative as relative5 } from "path";
|
|
4510
4886
|
function buildPythonMigrationIndex(dataDb2, alembicDir) {
|
|
4511
4887
|
const projectRoot = getProjectRoot();
|
|
4512
|
-
const absDir =
|
|
4888
|
+
const absDir = join10(projectRoot, alembicDir);
|
|
4513
4889
|
dataDb2.exec("DELETE FROM massu_py_migrations");
|
|
4514
|
-
const versionsDir =
|
|
4890
|
+
const versionsDir = join10(absDir, "versions");
|
|
4515
4891
|
let files = [];
|
|
4516
4892
|
try {
|
|
4517
|
-
files =
|
|
4893
|
+
files = readdirSync11(versionsDir).filter((f) => f.endsWith(".py")).map((f) => join10(versionsDir, f));
|
|
4518
4894
|
} catch {
|
|
4519
4895
|
try {
|
|
4520
|
-
files =
|
|
4896
|
+
files = readdirSync11(absDir).filter((f) => f.endsWith(".py") && f !== "env.py").map((f) => join10(absDir, f));
|
|
4521
4897
|
} catch {
|
|
4522
4898
|
}
|
|
4523
4899
|
}
|
|
@@ -4531,7 +4907,7 @@ function buildPythonMigrationIndex(dataDb2, alembicDir) {
|
|
|
4531
4907
|
for (const absFile of files) {
|
|
4532
4908
|
let source;
|
|
4533
4909
|
try {
|
|
4534
|
-
source =
|
|
4910
|
+
source = readFileSync13(absFile, "utf-8");
|
|
4535
4911
|
} catch {
|
|
4536
4912
|
continue;
|
|
4537
4913
|
}
|
|
@@ -4542,7 +4918,7 @@ function buildPythonMigrationIndex(dataDb2, alembicDir) {
|
|
|
4542
4918
|
rows.push({
|
|
4543
4919
|
revision: parsed.revision,
|
|
4544
4920
|
downRevision: parsed.downRevision,
|
|
4545
|
-
file:
|
|
4921
|
+
file: relative5(projectRoot, absFile),
|
|
4546
4922
|
description: parsed.description,
|
|
4547
4923
|
operations: JSON.stringify(parsed.operations)
|
|
4548
4924
|
});
|
|
@@ -4565,12 +4941,12 @@ var init_migration_indexer = __esm({
|
|
|
4565
4941
|
});
|
|
4566
4942
|
|
|
4567
4943
|
// src/python/coupling-detector.ts
|
|
4568
|
-
import { readFileSync as
|
|
4569
|
-
import { join as
|
|
4944
|
+
import { readFileSync as readFileSync14, readdirSync as readdirSync12 } from "fs";
|
|
4945
|
+
import { join as join11, relative as relative6 } from "path";
|
|
4570
4946
|
function buildPythonCouplingIndex(dataDb2) {
|
|
4571
4947
|
const projectRoot = getProjectRoot();
|
|
4572
4948
|
const config = getConfig();
|
|
4573
|
-
const srcDir =
|
|
4949
|
+
const srcDir = join11(projectRoot, config.paths.source);
|
|
4574
4950
|
const routes = dataDb2.prepare("SELECT id, method, path FROM massu_py_routes").all();
|
|
4575
4951
|
if (routes.length === 0) return 0;
|
|
4576
4952
|
dataDb2.exec("DELETE FROM massu_py_route_callers");
|
|
@@ -4599,10 +4975,10 @@ function buildPythonCouplingIndex(dataDb2) {
|
|
|
4599
4975
|
];
|
|
4600
4976
|
dataDb2.transaction(() => {
|
|
4601
4977
|
for (const absFile of frontendFiles) {
|
|
4602
|
-
const relFile =
|
|
4978
|
+
const relFile = relative6(projectRoot, absFile);
|
|
4603
4979
|
let source;
|
|
4604
4980
|
try {
|
|
4605
|
-
source =
|
|
4981
|
+
source = readFileSync14(absFile, "utf-8");
|
|
4606
4982
|
} catch {
|
|
4607
4983
|
continue;
|
|
4608
4984
|
}
|
|
@@ -4630,13 +5006,13 @@ function walkFrontendFiles(dir) {
|
|
|
4630
5006
|
const files = [];
|
|
4631
5007
|
const exclude = ["node_modules", ".next", "dist", ".git", "__pycache__", ".venv", "venv"];
|
|
4632
5008
|
try {
|
|
4633
|
-
const entries =
|
|
5009
|
+
const entries = readdirSync12(dir, { withFileTypes: true });
|
|
4634
5010
|
for (const entry of entries) {
|
|
4635
5011
|
if (entry.isDirectory()) {
|
|
4636
5012
|
if (exclude.includes(entry.name)) continue;
|
|
4637
|
-
files.push(...walkFrontendFiles(
|
|
5013
|
+
files.push(...walkFrontendFiles(join11(dir, entry.name)));
|
|
4638
5014
|
} else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
|
|
4639
|
-
files.push(
|
|
5015
|
+
files.push(join11(dir, entry.name));
|
|
4640
5016
|
}
|
|
4641
5017
|
}
|
|
4642
5018
|
} catch {
|
|
@@ -4742,6 +5118,16 @@ function getMemoryToolDefinitions() {
|
|
|
4742
5118
|
required: []
|
|
4743
5119
|
}
|
|
4744
5120
|
},
|
|
5121
|
+
// P4-007: memory_backfill
|
|
5122
|
+
{
|
|
5123
|
+
name: p("memory_backfill"),
|
|
5124
|
+
description: "Scan all memory/*.md files and ingest into database. Run after massu init or to recover from DB loss. Parses YAML frontmatter and deduplicates by title.",
|
|
5125
|
+
inputSchema: {
|
|
5126
|
+
type: "object",
|
|
5127
|
+
properties: {},
|
|
5128
|
+
required: []
|
|
5129
|
+
}
|
|
5130
|
+
},
|
|
4745
5131
|
// P4-006: memory_ingest
|
|
4746
5132
|
{
|
|
4747
5133
|
name: p("memory_ingest"),
|
|
@@ -4786,6 +5172,8 @@ function handleMemoryToolCall(name, args2, memoryDb) {
|
|
|
4786
5172
|
return handleFailures(args2, memoryDb);
|
|
4787
5173
|
case "memory_ingest":
|
|
4788
5174
|
return handleIngest(args2, memoryDb);
|
|
5175
|
+
case "memory_backfill":
|
|
5176
|
+
return handleBackfill(memoryDb);
|
|
4789
5177
|
default:
|
|
4790
5178
|
return text(`Unknown memory tool: ${name}`);
|
|
4791
5179
|
}
|
|
@@ -4960,6 +5348,21 @@ Title: ${title}
|
|
|
4960
5348
|
Importance: ${importance}
|
|
4961
5349
|
Session: ${activeSession.session_id.slice(0, 8)}...`);
|
|
4962
5350
|
}
|
|
5351
|
+
function handleBackfill(db) {
|
|
5352
|
+
const memoryDir = getResolvedPaths().memoryDir;
|
|
5353
|
+
const stats = backfillMemoryFiles(db, memoryDir);
|
|
5354
|
+
const lines = [
|
|
5355
|
+
"## Memory Backfill Results",
|
|
5356
|
+
"",
|
|
5357
|
+
`- **Total files scanned**: ${stats.total}`,
|
|
5358
|
+
`- **Inserted (new)**: ${stats.inserted}`,
|
|
5359
|
+
`- **Updated (existing)**: ${stats.updated}`,
|
|
5360
|
+
`- **Skipped (not found)**: ${stats.skipped}`,
|
|
5361
|
+
"",
|
|
5362
|
+
stats.total === 0 ? "No memory files found in memory directory." : `Successfully processed ${stats.inserted + stats.updated} of ${stats.total} memory files.`
|
|
5363
|
+
];
|
|
5364
|
+
return text(lines.join("\n"));
|
|
5365
|
+
}
|
|
4963
5366
|
function text(content) {
|
|
4964
5367
|
return { content: [{ type: "text", text: content }] };
|
|
4965
5368
|
}
|
|
@@ -4975,11 +5378,12 @@ var init_memory_tools = __esm({
|
|
|
4975
5378
|
"use strict";
|
|
4976
5379
|
init_memory_db();
|
|
4977
5380
|
init_config();
|
|
5381
|
+
init_memory_file_ingest();
|
|
4978
5382
|
}
|
|
4979
5383
|
});
|
|
4980
5384
|
|
|
4981
5385
|
// src/docs-tools.ts
|
|
4982
|
-
import { readFileSync as
|
|
5386
|
+
import { readFileSync as readFileSync15, existsSync as existsSync13 } from "fs";
|
|
4983
5387
|
import { resolve as resolve12, basename as basename3 } from "path";
|
|
4984
5388
|
function p2(baseName) {
|
|
4985
5389
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
@@ -5035,10 +5439,10 @@ function handleDocsToolCall(name, args2) {
|
|
|
5035
5439
|
}
|
|
5036
5440
|
function loadDocsMap() {
|
|
5037
5441
|
const mapPath = getResolvedPaths().docsMapPath;
|
|
5038
|
-
if (!
|
|
5442
|
+
if (!existsSync13(mapPath)) {
|
|
5039
5443
|
throw new Error(`docs-map.json not found at ${mapPath}`);
|
|
5040
5444
|
}
|
|
5041
|
-
return JSON.parse(
|
|
5445
|
+
return JSON.parse(readFileSync15(mapPath, "utf-8"));
|
|
5042
5446
|
}
|
|
5043
5447
|
function matchesPattern(filePath, pattern) {
|
|
5044
5448
|
const regexStr = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\{\{GLOBSTAR\}\}/g, ".*");
|
|
@@ -5109,12 +5513,12 @@ function extractFrontmatter(content) {
|
|
|
5109
5513
|
function extractProcedureNames(routerPath) {
|
|
5110
5514
|
const root = getProjectRoot();
|
|
5111
5515
|
const absPath = ensureWithinRoot(resolve12(getResolvedPaths().srcDir, "..", routerPath), root);
|
|
5112
|
-
if (!
|
|
5516
|
+
if (!existsSync13(absPath)) {
|
|
5113
5517
|
const altPath = ensureWithinRoot(resolve12(getResolvedPaths().srcDir, "../server/api/routers", basename3(routerPath)), root);
|
|
5114
|
-
if (!
|
|
5115
|
-
return extractProcedureNamesFromContent(
|
|
5518
|
+
if (!existsSync13(altPath)) return [];
|
|
5519
|
+
return extractProcedureNamesFromContent(readFileSync15(altPath, "utf-8"));
|
|
5116
5520
|
}
|
|
5117
|
-
return extractProcedureNamesFromContent(
|
|
5521
|
+
return extractProcedureNamesFromContent(readFileSync15(absPath, "utf-8"));
|
|
5118
5522
|
}
|
|
5119
5523
|
function extractProcedureNamesFromContent(content) {
|
|
5120
5524
|
const procRegex = /\.(?:query|mutation)\s*\(/g;
|
|
@@ -5155,7 +5559,7 @@ function handleDocsAudit(args2) {
|
|
|
5155
5559
|
const mapping = docsMap.mappings.find((m) => m.id === mappingId);
|
|
5156
5560
|
if (!mapping) continue;
|
|
5157
5561
|
const helpPagePath = ensureWithinRoot(resolve12(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
|
|
5158
|
-
if (!
|
|
5562
|
+
if (!existsSync13(helpPagePath)) {
|
|
5159
5563
|
results.push({
|
|
5160
5564
|
helpPage: mapping.helpPage,
|
|
5161
5565
|
mappingId,
|
|
@@ -5167,7 +5571,7 @@ function handleDocsAudit(args2) {
|
|
|
5167
5571
|
});
|
|
5168
5572
|
continue;
|
|
5169
5573
|
}
|
|
5170
|
-
const content =
|
|
5574
|
+
const content = readFileSync15(helpPagePath, "utf-8");
|
|
5171
5575
|
const sections = extractSections(content);
|
|
5172
5576
|
const frontmatter = extractFrontmatter(content);
|
|
5173
5577
|
const staleReasons = [];
|
|
@@ -5208,8 +5612,8 @@ function handleDocsAudit(args2) {
|
|
|
5208
5612
|
for (const [guideName, parentId] of Object.entries(docsMap.userGuideInheritance.examples)) {
|
|
5209
5613
|
if (parentId === mappingId) {
|
|
5210
5614
|
const guidePath = ensureWithinRoot(resolve12(getResolvedPaths().helpSitePath, `pages/user-guides/${guideName}/index.mdx`), getProjectRoot());
|
|
5211
|
-
if (
|
|
5212
|
-
const guideContent =
|
|
5615
|
+
if (existsSync13(guidePath)) {
|
|
5616
|
+
const guideContent = readFileSync15(guidePath, "utf-8");
|
|
5213
5617
|
const guideFrontmatter = extractFrontmatter(guideContent);
|
|
5214
5618
|
if (!guideFrontmatter?.lastVerified || status === "STALE") {
|
|
5215
5619
|
results.push({
|
|
@@ -5243,13 +5647,13 @@ function handleDocsCoverage(args2) {
|
|
|
5243
5647
|
const mappings = filterDomain ? docsMap.mappings.filter((m) => m.id === filterDomain) : docsMap.mappings;
|
|
5244
5648
|
for (const mapping of mappings) {
|
|
5245
5649
|
const helpPagePath = ensureWithinRoot(resolve12(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
|
|
5246
|
-
const exists =
|
|
5650
|
+
const exists = existsSync13(helpPagePath);
|
|
5247
5651
|
let hasContent = false;
|
|
5248
5652
|
let lineCount = 0;
|
|
5249
5653
|
let lastVerified = null;
|
|
5250
5654
|
let status = null;
|
|
5251
5655
|
if (exists) {
|
|
5252
|
-
const content =
|
|
5656
|
+
const content = readFileSync15(helpPagePath, "utf-8");
|
|
5253
5657
|
lineCount = content.split("\n").length;
|
|
5254
5658
|
hasContent = lineCount > 10;
|
|
5255
5659
|
const frontmatter = extractFrontmatter(content);
|
|
@@ -5590,7 +5994,7 @@ var init_observability_tools = __esm({
|
|
|
5590
5994
|
});
|
|
5591
5995
|
|
|
5592
5996
|
// src/sentinel-db.ts
|
|
5593
|
-
import { existsSync as
|
|
5997
|
+
import { existsSync as existsSync14 } from "fs";
|
|
5594
5998
|
import { resolve as resolve13 } from "path";
|
|
5595
5999
|
function parsePortalScope(raw) {
|
|
5596
6000
|
if (!raw) return [];
|
|
@@ -5828,22 +6232,22 @@ function validateFeatures(db, domainFilter) {
|
|
|
5828
6232
|
const missingPages = [];
|
|
5829
6233
|
for (const comp of components) {
|
|
5830
6234
|
const absPath = resolve13(PROJECT_ROOT, comp.component_file);
|
|
5831
|
-
if (!
|
|
6235
|
+
if (!existsSync14(absPath)) {
|
|
5832
6236
|
missingComponents.push(comp.component_file);
|
|
5833
6237
|
}
|
|
5834
6238
|
}
|
|
5835
6239
|
for (const proc of procedures) {
|
|
5836
6240
|
const routerPath = resolve13(PROJECT_ROOT, `src/server/api/routers/${proc.router_name}.ts`);
|
|
5837
|
-
if (!
|
|
6241
|
+
if (!existsSync14(routerPath)) {
|
|
5838
6242
|
missingProcedures.push({ router: proc.router_name, procedure: proc.procedure_name });
|
|
5839
6243
|
}
|
|
5840
6244
|
}
|
|
5841
6245
|
for (const page of pages) {
|
|
5842
6246
|
const routeToPath = page.page_route.replace(/^\/(portal-[^/]+\/)?/, "src/app/").replace(/\/$/, "") + "/page.tsx";
|
|
5843
6247
|
const absPath = resolve13(PROJECT_ROOT, routeToPath);
|
|
5844
|
-
if (page.page_route.startsWith("/") && !
|
|
6248
|
+
if (page.page_route.startsWith("/") && !existsSync14(absPath)) {
|
|
5845
6249
|
const altPath = resolve13(PROJECT_ROOT, `src/app${page.page_route}/page.tsx`);
|
|
5846
|
-
if (!
|
|
6250
|
+
if (!existsSync14(altPath)) {
|
|
5847
6251
|
missingPages.push(page.page_route);
|
|
5848
6252
|
}
|
|
5849
6253
|
}
|
|
@@ -6367,8 +6771,8 @@ var init_sentinel_tools = __esm({
|
|
|
6367
6771
|
});
|
|
6368
6772
|
|
|
6369
6773
|
// src/sentinel-scanner.ts
|
|
6370
|
-
import { readFileSync as
|
|
6371
|
-
import { resolve as resolve14, join as
|
|
6774
|
+
import { readFileSync as readFileSync16, existsSync as existsSync15, readdirSync as readdirSync13, statSync as statSync4 } from "fs";
|
|
6775
|
+
import { resolve as resolve14, join as join12, basename as basename4, dirname as dirname9, relative as relative7 } from "path";
|
|
6372
6776
|
function inferDomain(filePath) {
|
|
6373
6777
|
const domains = getConfig().domains;
|
|
6374
6778
|
const path = filePath.toLowerCase();
|
|
@@ -6498,9 +6902,9 @@ function scanComponentExports(dataDb2) {
|
|
|
6498
6902
|
const componentsBase = config.paths.components ?? config.paths.source + "/components";
|
|
6499
6903
|
const componentDirs = [];
|
|
6500
6904
|
const basePath = resolve14(projectRoot, componentsBase);
|
|
6501
|
-
if (
|
|
6905
|
+
if (existsSync15(basePath)) {
|
|
6502
6906
|
try {
|
|
6503
|
-
const entries =
|
|
6907
|
+
const entries = readdirSync13(basePath, { withFileTypes: true });
|
|
6504
6908
|
for (const entry of entries) {
|
|
6505
6909
|
if (entry.isDirectory()) {
|
|
6506
6910
|
componentDirs.push(componentsBase + "/" + entry.name);
|
|
@@ -6511,11 +6915,11 @@ function scanComponentExports(dataDb2) {
|
|
|
6511
6915
|
}
|
|
6512
6916
|
for (const dir of componentDirs) {
|
|
6513
6917
|
const absDir = resolve14(projectRoot, dir);
|
|
6514
|
-
if (!
|
|
6918
|
+
if (!existsSync15(absDir)) continue;
|
|
6515
6919
|
const files = walkDir(absDir).filter((f) => f.endsWith(".tsx") || f.endsWith(".ts"));
|
|
6516
6920
|
for (const file of files) {
|
|
6517
|
-
const relPath =
|
|
6518
|
-
const source =
|
|
6921
|
+
const relPath = relative7(projectRoot, file);
|
|
6922
|
+
const source = readFileSync16(file, "utf-8");
|
|
6519
6923
|
const annotations = parseFeatureAnnotations(source);
|
|
6520
6924
|
if (annotations.length > 0) {
|
|
6521
6925
|
for (const ann of annotations) {
|
|
@@ -6564,11 +6968,11 @@ function scanComponentExports(dataDb2) {
|
|
|
6564
6968
|
function walkDir(dir) {
|
|
6565
6969
|
const results = [];
|
|
6566
6970
|
try {
|
|
6567
|
-
const entries =
|
|
6971
|
+
const entries = readdirSync13(dir);
|
|
6568
6972
|
for (const entry of entries) {
|
|
6569
|
-
const fullPath =
|
|
6973
|
+
const fullPath = join12(dir, entry);
|
|
6570
6974
|
try {
|
|
6571
|
-
const stat =
|
|
6975
|
+
const stat = statSync4(fullPath);
|
|
6572
6976
|
if (stat.isDirectory()) {
|
|
6573
6977
|
results.push(...walkDir(fullPath));
|
|
6574
6978
|
} else {
|
|
@@ -7554,7 +7958,7 @@ var init_audit_trail = __esm({
|
|
|
7554
7958
|
});
|
|
7555
7959
|
|
|
7556
7960
|
// src/validation-engine.ts
|
|
7557
|
-
import { existsSync as
|
|
7961
|
+
import { existsSync as existsSync16, readFileSync as readFileSync17 } from "fs";
|
|
7558
7962
|
function p9(baseName) {
|
|
7559
7963
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
7560
7964
|
}
|
|
@@ -7585,7 +7989,7 @@ function validateFile(filePath, projectRoot) {
|
|
|
7585
7989
|
});
|
|
7586
7990
|
return checks;
|
|
7587
7991
|
}
|
|
7588
|
-
if (!
|
|
7992
|
+
if (!existsSync16(absPath)) {
|
|
7589
7993
|
checks.push({
|
|
7590
7994
|
name: "file_exists",
|
|
7591
7995
|
severity: "error",
|
|
@@ -7594,7 +7998,7 @@ function validateFile(filePath, projectRoot) {
|
|
|
7594
7998
|
});
|
|
7595
7999
|
return checks;
|
|
7596
8000
|
}
|
|
7597
|
-
const source =
|
|
8001
|
+
const source = readFileSync17(absPath, "utf-8");
|
|
7598
8002
|
const lines = source.split("\n");
|
|
7599
8003
|
if (activeChecks.rule_compliance !== false) {
|
|
7600
8004
|
for (const ruleSet of config.rules) {
|
|
@@ -8039,7 +8443,7 @@ var init_adr_generator = __esm({
|
|
|
8039
8443
|
});
|
|
8040
8444
|
|
|
8041
8445
|
// src/security-scorer.ts
|
|
8042
|
-
import { existsSync as
|
|
8446
|
+
import { existsSync as existsSync17, readFileSync as readFileSync18 } from "fs";
|
|
8043
8447
|
function p11(baseName) {
|
|
8044
8448
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
8045
8449
|
}
|
|
@@ -8063,12 +8467,12 @@ function scoreFileSecurity(filePath, projectRoot) {
|
|
|
8063
8467
|
}]
|
|
8064
8468
|
};
|
|
8065
8469
|
}
|
|
8066
|
-
if (!
|
|
8470
|
+
if (!existsSync17(absPath)) {
|
|
8067
8471
|
return { riskScore: 0, findings: [] };
|
|
8068
8472
|
}
|
|
8069
8473
|
let source;
|
|
8070
8474
|
try {
|
|
8071
|
-
source =
|
|
8475
|
+
source = readFileSync18(absPath, "utf-8");
|
|
8072
8476
|
} catch {
|
|
8073
8477
|
return { riskScore: 0, findings: [] };
|
|
8074
8478
|
}
|
|
@@ -8357,7 +8761,7 @@ var init_security_scorer = __esm({
|
|
|
8357
8761
|
});
|
|
8358
8762
|
|
|
8359
8763
|
// src/dependency-scorer.ts
|
|
8360
|
-
import { existsSync as
|
|
8764
|
+
import { existsSync as existsSync18, readFileSync as readFileSync19 } from "fs";
|
|
8361
8765
|
import { resolve as resolve15 } from "path";
|
|
8362
8766
|
function p12(baseName) {
|
|
8363
8767
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
@@ -8391,9 +8795,9 @@ function calculateDepRisk(factors) {
|
|
|
8391
8795
|
}
|
|
8392
8796
|
function getInstalledPackages(projectRoot) {
|
|
8393
8797
|
const pkgPath = resolve15(projectRoot, "package.json");
|
|
8394
|
-
if (!
|
|
8798
|
+
if (!existsSync18(pkgPath)) return /* @__PURE__ */ new Map();
|
|
8395
8799
|
try {
|
|
8396
|
-
const pkg = JSON.parse(
|
|
8800
|
+
const pkg = JSON.parse(readFileSync19(pkgPath, "utf-8"));
|
|
8397
8801
|
const packages = /* @__PURE__ */ new Map();
|
|
8398
8802
|
for (const [name, version] of Object.entries(pkg.dependencies ?? {})) {
|
|
8399
8803
|
packages.set(name, version);
|
|
@@ -8996,8 +9400,8 @@ var init_regression_detector = __esm({
|
|
|
8996
9400
|
|
|
8997
9401
|
// src/knowledge-indexer.ts
|
|
8998
9402
|
import { createHash as createHash2 } from "crypto";
|
|
8999
|
-
import { readFileSync as
|
|
9000
|
-
import { resolve as resolve16, relative as
|
|
9403
|
+
import { readFileSync as readFileSync20, readdirSync as readdirSync14, statSync as statSync5, existsSync as existsSync19 } from "fs";
|
|
9404
|
+
import { resolve as resolve16, relative as relative8, basename as basename5, extname } from "path";
|
|
9001
9405
|
function getKnowledgePaths() {
|
|
9002
9406
|
const resolved = getResolvedPaths();
|
|
9003
9407
|
const config = getConfig();
|
|
@@ -9023,7 +9427,7 @@ function discoverMarkdownFiles(baseDir) {
|
|
|
9023
9427
|
const files = [];
|
|
9024
9428
|
function walk(dir) {
|
|
9025
9429
|
try {
|
|
9026
|
-
const entries =
|
|
9430
|
+
const entries = readdirSync14(dir, { withFileTypes: true });
|
|
9027
9431
|
for (const entry of entries) {
|
|
9028
9432
|
const fullPath = resolve16(dir, entry.name);
|
|
9029
9433
|
if (entry.isDirectory()) {
|
|
@@ -9045,7 +9449,7 @@ function categorizeFile(filePath) {
|
|
|
9045
9449
|
const paths = getKnowledgePaths();
|
|
9046
9450
|
if (filePath.startsWith(paths.plansDir)) return "plan";
|
|
9047
9451
|
if (filePath.startsWith(paths.docsDir)) {
|
|
9048
|
-
const relFromDocs =
|
|
9452
|
+
const relFromDocs = relative8(paths.docsDir, filePath).replace(/\\/g, "/").toLowerCase();
|
|
9049
9453
|
if (relFromDocs.startsWith("plans/")) return "plan";
|
|
9050
9454
|
if (relFromDocs.includes("architecture")) return "architecture";
|
|
9051
9455
|
if (relFromDocs.includes("security")) return "security";
|
|
@@ -9061,7 +9465,7 @@ function categorizeFile(filePath) {
|
|
|
9061
9465
|
}
|
|
9062
9466
|
const claudeDirName = getConfig().conventions?.claudeDirName ?? ".claude";
|
|
9063
9467
|
if (filePath.includes(`${claudeDirName}/projects/`) && filePath.includes("/memory/")) return "memory";
|
|
9064
|
-
const rel =
|
|
9468
|
+
const rel = relative8(paths.claudeDir, filePath).replace(/\\/g, "/");
|
|
9065
9469
|
const firstDir = rel.split("/")[0];
|
|
9066
9470
|
const knownCategories = getConfig().conventions?.knowledgeCategories ?? [
|
|
9067
9471
|
"patterns",
|
|
@@ -9303,11 +9707,11 @@ function indexAllKnowledge(db) {
|
|
|
9303
9707
|
files.push(...memFiles);
|
|
9304
9708
|
} catch {
|
|
9305
9709
|
}
|
|
9306
|
-
if (
|
|
9710
|
+
if (existsSync19(paths.plansDir)) {
|
|
9307
9711
|
const planFiles = discoverMarkdownFiles(paths.plansDir);
|
|
9308
9712
|
files.push(...planFiles);
|
|
9309
9713
|
}
|
|
9310
|
-
if (
|
|
9714
|
+
if (existsSync19(paths.docsDir)) {
|
|
9311
9715
|
const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
|
|
9312
9716
|
const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f) => !f.includes("/plans/") && !excludePatterns.some((p18) => f.includes(p18)));
|
|
9313
9717
|
files.push(...docsFiles);
|
|
@@ -9350,10 +9754,10 @@ function indexAllKnowledge(db) {
|
|
|
9350
9754
|
} catch {
|
|
9351
9755
|
}
|
|
9352
9756
|
for (const filePath of files) {
|
|
9353
|
-
if (!
|
|
9354
|
-
const content =
|
|
9757
|
+
if (!existsSync19(filePath)) continue;
|
|
9758
|
+
const content = readFileSync20(filePath, "utf-8");
|
|
9355
9759
|
const hash = hashContent(content);
|
|
9356
|
-
const relPath = filePath.startsWith(paths.claudeDir) ?
|
|
9760
|
+
const relPath = filePath.startsWith(paths.claudeDir) ? relative8(paths.claudeDir, filePath) : filePath.startsWith(paths.plansDir) ? "plans/" + relative8(paths.plansDir, filePath) : filePath.startsWith(paths.docsDir) ? "docs/" + relative8(paths.docsDir, filePath) : filePath.startsWith(paths.memoryDir) ? `memory/${relative8(paths.memoryDir, filePath)}` : basename5(filePath);
|
|
9357
9761
|
const category = categorizeFile(filePath);
|
|
9358
9762
|
const title = extractTitle(content, filePath);
|
|
9359
9763
|
const description = extractDescription2(content);
|
|
@@ -9471,17 +9875,17 @@ function isKnowledgeStale(db) {
|
|
|
9471
9875
|
files.push(...discoverMarkdownFiles(paths.memoryDir));
|
|
9472
9876
|
} catch {
|
|
9473
9877
|
}
|
|
9474
|
-
if (
|
|
9878
|
+
if (existsSync19(paths.plansDir)) {
|
|
9475
9879
|
files.push(...discoverMarkdownFiles(paths.plansDir));
|
|
9476
9880
|
}
|
|
9477
|
-
if (
|
|
9881
|
+
if (existsSync19(paths.docsDir)) {
|
|
9478
9882
|
const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
|
|
9479
9883
|
const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f) => !f.includes("/plans/") && !excludePatterns.some((p18) => f.includes(p18)));
|
|
9480
9884
|
files.push(...docsFiles);
|
|
9481
9885
|
}
|
|
9482
9886
|
for (const filePath of files) {
|
|
9483
9887
|
try {
|
|
9484
|
-
const stat =
|
|
9888
|
+
const stat = statSync5(filePath);
|
|
9485
9889
|
if (stat.mtimeMs > lastIndexTime) return true;
|
|
9486
9890
|
} catch {
|
|
9487
9891
|
continue;
|
|
@@ -9503,7 +9907,7 @@ var init_knowledge_indexer = __esm({
|
|
|
9503
9907
|
});
|
|
9504
9908
|
|
|
9505
9909
|
// src/knowledge-tools.ts
|
|
9506
|
-
import { readFileSync as
|
|
9910
|
+
import { readFileSync as readFileSync21, writeFileSync as writeFileSync3, appendFileSync, readdirSync as readdirSync15 } from "fs";
|
|
9507
9911
|
import { resolve as resolve17, basename as basename6 } from "path";
|
|
9508
9912
|
function p15(baseName) {
|
|
9509
9913
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
@@ -10254,7 +10658,7 @@ ${crRule ? `- **CR**: ${crRule}
|
|
|
10254
10658
|
`;
|
|
10255
10659
|
let existing = "";
|
|
10256
10660
|
try {
|
|
10257
|
-
existing =
|
|
10661
|
+
existing = readFileSync21(correctionsPath, "utf-8");
|
|
10258
10662
|
} catch {
|
|
10259
10663
|
}
|
|
10260
10664
|
const archiveIdx = existing.indexOf("## Archived");
|
|
@@ -10436,7 +10840,7 @@ function handleGaps(db, args2) {
|
|
|
10436
10840
|
} else if (checkType === "routers") {
|
|
10437
10841
|
try {
|
|
10438
10842
|
const routersDir = getResolvedPaths().routersDir;
|
|
10439
|
-
const routerFiles =
|
|
10843
|
+
const routerFiles = readdirSync15(routersDir).filter((f) => f.endsWith(".ts") && !f.startsWith("_"));
|
|
10440
10844
|
lines.push(`| Router | Knowledge Hits | Status |`);
|
|
10441
10845
|
lines.push(`|--------|----------------|--------|`);
|
|
10442
10846
|
for (const file of routerFiles) {
|
|
@@ -10593,11 +10997,11 @@ var init_knowledge_tools = __esm({
|
|
|
10593
10997
|
// src/knowledge-db.ts
|
|
10594
10998
|
import Database3 from "better-sqlite3";
|
|
10595
10999
|
import { dirname as dirname10 } from "path";
|
|
10596
|
-
import { existsSync as
|
|
11000
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync5 } from "fs";
|
|
10597
11001
|
function getKnowledgeDb() {
|
|
10598
11002
|
const dbPath = getResolvedPaths().knowledgeDbPath;
|
|
10599
11003
|
const dir = dirname10(dbPath);
|
|
10600
|
-
if (!
|
|
11004
|
+
if (!existsSync21(dir)) {
|
|
10601
11005
|
mkdirSync5(dir, { recursive: true });
|
|
10602
11006
|
}
|
|
10603
11007
|
const db = new Database3(dbPath);
|
|
@@ -11331,7 +11735,7 @@ var init_python_tools = __esm({
|
|
|
11331
11735
|
});
|
|
11332
11736
|
|
|
11333
11737
|
// src/tools.ts
|
|
11334
|
-
import { readFileSync as
|
|
11738
|
+
import { readFileSync as readFileSync22, existsSync as existsSync22 } from "fs";
|
|
11335
11739
|
import { resolve as resolve18, basename as basename7 } from "path";
|
|
11336
11740
|
function prefix() {
|
|
11337
11741
|
return getConfig().toolPrefix;
|
|
@@ -11767,8 +12171,8 @@ function handleContext(file, dataDb2, codegraphDb2) {
|
|
|
11767
12171
|
const resolvedPaths = getResolvedPaths();
|
|
11768
12172
|
const root = getProjectRoot();
|
|
11769
12173
|
const absFilePath = ensureWithinRoot(resolve18(resolvedPaths.srcDir, "..", file), root);
|
|
11770
|
-
if (
|
|
11771
|
-
const fileContent =
|
|
12174
|
+
if (existsSync22(absFilePath)) {
|
|
12175
|
+
const fileContent = readFileSync22(absFilePath, "utf-8").slice(0, 3e3);
|
|
11772
12176
|
const keywords = [];
|
|
11773
12177
|
if (fileContent.includes("ctx.db")) keywords.push("database", "schema");
|
|
11774
12178
|
if (fileContent.includes("BigInt") || fileContent.includes("Decimal")) keywords.push("BigInt", "serialization");
|
|
@@ -12193,10 +12597,10 @@ function handleSchema(args2) {
|
|
|
12193
12597
|
lines.push("");
|
|
12194
12598
|
const projectRoot = getProjectRoot();
|
|
12195
12599
|
const absPath = ensureWithinRoot(resolve18(projectRoot, file), projectRoot);
|
|
12196
|
-
if (!
|
|
12600
|
+
if (!existsSync22(absPath)) {
|
|
12197
12601
|
return text17(`File not found: ${file}`);
|
|
12198
12602
|
}
|
|
12199
|
-
const source =
|
|
12603
|
+
const source = readFileSync22(absPath, "utf-8");
|
|
12200
12604
|
const config = getConfig();
|
|
12201
12605
|
const dbPattern = config.dbAccessPattern ?? "ctx.db.{table}";
|
|
12202
12606
|
const regexStr = dbPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace("\\{table\\}", "(\\w+)");
|
|
@@ -12274,7 +12678,7 @@ var init_tools = __esm({
|
|
|
12274
12678
|
|
|
12275
12679
|
// src/server.ts
|
|
12276
12680
|
var server_exports = {};
|
|
12277
|
-
import { readFileSync as
|
|
12681
|
+
import { readFileSync as readFileSync23 } from "fs";
|
|
12278
12682
|
import { resolve as resolve19, dirname as dirname11 } from "path";
|
|
12279
12683
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
12280
12684
|
function getDb() {
|
|
@@ -12370,7 +12774,7 @@ var init_server = __esm({
|
|
|
12370
12774
|
__dirname4 = dirname11(fileURLToPath4(import.meta.url));
|
|
12371
12775
|
PKG_VERSION = (() => {
|
|
12372
12776
|
try {
|
|
12373
|
-
const pkg = JSON.parse(
|
|
12777
|
+
const pkg = JSON.parse(readFileSync23(resolve19(__dirname4, "..", "package.json"), "utf-8"));
|
|
12374
12778
|
return pkg.version ?? "0.0.0";
|
|
12375
12779
|
} catch {
|
|
12376
12780
|
return "0.0.0";
|
|
@@ -12434,7 +12838,7 @@ var init_server = __esm({
|
|
|
12434
12838
|
});
|
|
12435
12839
|
|
|
12436
12840
|
// src/cli.ts
|
|
12437
|
-
import { readFileSync as
|
|
12841
|
+
import { readFileSync as readFileSync24 } from "fs";
|
|
12438
12842
|
import { resolve as resolve20, dirname as dirname12 } from "path";
|
|
12439
12843
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
12440
12844
|
var __filename4 = fileURLToPath5(import.meta.url);
|
|
@@ -12509,7 +12913,7 @@ Documentation: https://massu.ai/docs
|
|
|
12509
12913
|
}
|
|
12510
12914
|
function printVersion() {
|
|
12511
12915
|
try {
|
|
12512
|
-
const pkg = JSON.parse(
|
|
12916
|
+
const pkg = JSON.parse(readFileSync24(resolve20(__dirname5, "../package.json"), "utf-8"));
|
|
12513
12917
|
console.log(`massu v${pkg.version}`);
|
|
12514
12918
|
} catch {
|
|
12515
12919
|
console.log("massu v0.1.0");
|