@massu/core 0.7.0 → 0.8.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 +2 -2
- package/dist/cli.js +151 -3
- package/dist/hooks/auto-learning-pipeline.js +14 -2
- package/dist/hooks/classify-failure.js +1146 -0
- package/dist/hooks/cost-tracker.js +31 -0
- package/dist/hooks/fix-detector.js +12 -0
- package/dist/hooks/incident-pipeline.js +698 -10
- package/dist/hooks/post-edit-context.js +12 -0
- package/dist/hooks/post-tool-use.js +31 -0
- package/dist/hooks/pre-compact.js +31 -0
- package/dist/hooks/pre-delete-check.js +12 -0
- package/dist/hooks/quality-event.js +31 -0
- package/dist/hooks/rule-enforcement-pipeline.js +14 -1
- package/dist/hooks/session-end.js +31 -0
- package/dist/hooks/session-start.js +32 -1
- package/dist/hooks/user-prompt.js +63 -1
- package/package.json +1 -1
- package/reference/hook-execution-order.md +25 -17
- package/src/commands/doctor.ts +1 -1
- package/src/commands/init.ts +11 -1
- package/src/config.ts +12 -0
- package/src/hooks/auto-learning-pipeline.ts +3 -3
- package/src/hooks/classify-failure.ts +259 -0
- package/src/hooks/incident-pipeline.ts +42 -0
- package/src/hooks/rule-enforcement-pipeline.ts +2 -1
- package/src/hooks/session-start.ts +1 -1
- package/src/hooks/user-prompt.ts +21 -1
- package/src/license.ts +2 -1
- package/src/mcp-bridge-tools.ts +1 -1
- package/src/memory-db.ts +201 -0
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
import{createRequire as __cr}from"module";const require=__cr(import.meta.url);
|
|
3
3
|
|
|
4
4
|
// src/hooks/incident-pipeline.ts
|
|
5
|
-
import { existsSync as
|
|
6
|
-
import { basename, resolve as
|
|
5
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync } from "fs";
|
|
6
|
+
import { basename as basename2, resolve as resolve3 } from "path";
|
|
7
7
|
|
|
8
8
|
// src/config.ts
|
|
9
9
|
import { resolve, dirname } from "path";
|
|
10
10
|
import { existsSync, readFileSync } from "fs";
|
|
11
|
+
import { homedir } from "os";
|
|
11
12
|
import { parse as parseYaml } from "yaml";
|
|
12
13
|
import { z } from "zod";
|
|
13
14
|
var DomainConfigSchema = z.object({
|
|
@@ -149,6 +150,18 @@ var AutoLearningConfigSchema = z.object({
|
|
|
149
150
|
"added_missing_import"
|
|
150
151
|
])
|
|
151
152
|
}).default({}),
|
|
153
|
+
failureClassification: z.object({
|
|
154
|
+
enabled: z.boolean().default(true),
|
|
155
|
+
thresholds: z.object({
|
|
156
|
+
known: z.number().default(5),
|
|
157
|
+
similar: z.number().default(3)
|
|
158
|
+
}).default({}),
|
|
159
|
+
scoring: z.object({
|
|
160
|
+
diffPatternWeight: z.number().default(3),
|
|
161
|
+
filePatternWeight: z.number().default(2),
|
|
162
|
+
promptKeywordWeight: z.number().default(2)
|
|
163
|
+
}).default({})
|
|
164
|
+
}).default({}),
|
|
152
165
|
pipeline: z.object({
|
|
153
166
|
requireIncidentReport: z.boolean().default(true),
|
|
154
167
|
requirePreventionRule: z.boolean().default(true),
|
|
@@ -323,6 +336,654 @@ function getConfig() {
|
|
|
323
336
|
}
|
|
324
337
|
return _config;
|
|
325
338
|
}
|
|
339
|
+
function getResolvedPaths() {
|
|
340
|
+
const config = getConfig();
|
|
341
|
+
const root = getProjectRoot();
|
|
342
|
+
const claudeDirName = config.conventions?.claudeDirName ?? ".claude";
|
|
343
|
+
return {
|
|
344
|
+
codegraphDbPath: resolve(root, ".codegraph/codegraph.db"),
|
|
345
|
+
dataDbPath: resolve(root, ".massu/data.db"),
|
|
346
|
+
prismaSchemaPath: resolve(root, config.paths.schema ?? "prisma/schema.prisma"),
|
|
347
|
+
rootRouterPath: resolve(root, config.paths.routerRoot ?? "src/server/api/root.ts"),
|
|
348
|
+
routersDir: resolve(root, config.paths.routers ?? "src/server/api/routers"),
|
|
349
|
+
srcDir: resolve(root, config.paths.source),
|
|
350
|
+
pathAlias: Object.fromEntries(
|
|
351
|
+
Object.entries(config.paths.aliases).map(([alias, target]) => [
|
|
352
|
+
alias,
|
|
353
|
+
resolve(root, target)
|
|
354
|
+
])
|
|
355
|
+
),
|
|
356
|
+
extensions: [".ts", ".tsx", ".js", ".jsx"],
|
|
357
|
+
indexFiles: ["index.ts", "index.tsx", "index.js", "index.jsx"],
|
|
358
|
+
patternsDir: resolve(root, claudeDirName, "patterns"),
|
|
359
|
+
claudeMdPath: resolve(root, claudeDirName, "CLAUDE.md"),
|
|
360
|
+
docsMapPath: resolve(root, ".massu/docs-map.json"),
|
|
361
|
+
helpSitePath: resolve(root, "../" + config.project.name + "-help"),
|
|
362
|
+
memoryDbPath: resolve(root, ".massu/memory.db"),
|
|
363
|
+
knowledgeDbPath: resolve(root, ".massu/knowledge.db"),
|
|
364
|
+
plansDir: resolve(root, "docs/plans"),
|
|
365
|
+
docsDir: resolve(root, "docs"),
|
|
366
|
+
claudeDir: resolve(root, claudeDirName),
|
|
367
|
+
memoryDir: resolve(homedir(), claudeDirName, "projects", root.replace(/\//g, "-"), "memory"),
|
|
368
|
+
sessionStatePath: resolve(root, config.conventions?.sessionStatePath ?? `${claudeDirName}/session-state/CURRENT.md`),
|
|
369
|
+
sessionArchivePath: resolve(root, config.conventions?.sessionArchivePath ?? `${claudeDirName}/session-state/archive`),
|
|
370
|
+
mcpJsonPath: resolve(root, ".mcp.json"),
|
|
371
|
+
settingsLocalPath: resolve(root, claudeDirName, "settings.local.json")
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// src/memory-db.ts
|
|
376
|
+
import Database from "better-sqlite3";
|
|
377
|
+
import { dirname as dirname2, basename } from "path";
|
|
378
|
+
import { existsSync as existsSync2, mkdirSync } from "fs";
|
|
379
|
+
function getMemoryDb() {
|
|
380
|
+
const dbPath = getResolvedPaths().memoryDbPath;
|
|
381
|
+
const dir = dirname2(dbPath);
|
|
382
|
+
if (!existsSync2(dir)) {
|
|
383
|
+
mkdirSync(dir, { recursive: true });
|
|
384
|
+
}
|
|
385
|
+
const db = new Database(dbPath);
|
|
386
|
+
db.pragma("journal_mode = WAL");
|
|
387
|
+
db.pragma("foreign_keys = ON");
|
|
388
|
+
initMemorySchema(db);
|
|
389
|
+
return db;
|
|
390
|
+
}
|
|
391
|
+
function initMemorySchema(db) {
|
|
392
|
+
db.exec(`
|
|
393
|
+
-- Sessions table (linked to Claude Code session IDs)
|
|
394
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
395
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
396
|
+
session_id TEXT UNIQUE NOT NULL,
|
|
397
|
+
project TEXT NOT NULL DEFAULT 'my-project',
|
|
398
|
+
git_branch TEXT,
|
|
399
|
+
started_at TEXT NOT NULL,
|
|
400
|
+
started_at_epoch INTEGER NOT NULL,
|
|
401
|
+
ended_at TEXT,
|
|
402
|
+
ended_at_epoch INTEGER,
|
|
403
|
+
status TEXT CHECK(status IN ('active', 'completed', 'abandoned')) NOT NULL DEFAULT 'active',
|
|
404
|
+
plan_file TEXT,
|
|
405
|
+
plan_phase TEXT,
|
|
406
|
+
task_id TEXT
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_session_id ON sessions(session_id);
|
|
410
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_started ON sessions(started_at_epoch DESC);
|
|
411
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_task_id ON sessions(task_id);
|
|
412
|
+
|
|
413
|
+
-- Observations table (structured knowledge from tool usage)
|
|
414
|
+
CREATE TABLE IF NOT EXISTS observations (
|
|
415
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
416
|
+
session_id TEXT NOT NULL,
|
|
417
|
+
type TEXT NOT NULL CHECK(type IN (
|
|
418
|
+
'decision', 'bugfix', 'feature', 'refactor', 'discovery',
|
|
419
|
+
'cr_violation', 'vr_check', 'pattern_compliance', 'failed_attempt',
|
|
420
|
+
'file_change', 'incident_near_miss'
|
|
421
|
+
)),
|
|
422
|
+
title TEXT NOT NULL,
|
|
423
|
+
detail TEXT,
|
|
424
|
+
files_involved TEXT DEFAULT '[]',
|
|
425
|
+
plan_item TEXT,
|
|
426
|
+
cr_rule TEXT,
|
|
427
|
+
vr_type TEXT,
|
|
428
|
+
evidence TEXT,
|
|
429
|
+
importance INTEGER NOT NULL DEFAULT 3 CHECK(importance BETWEEN 1 AND 5),
|
|
430
|
+
recurrence_count INTEGER NOT NULL DEFAULT 1,
|
|
431
|
+
original_tokens INTEGER DEFAULT 0,
|
|
432
|
+
created_at TEXT NOT NULL,
|
|
433
|
+
created_at_epoch INTEGER NOT NULL,
|
|
434
|
+
FOREIGN KEY(session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
CREATE INDEX IF NOT EXISTS idx_observations_session ON observations(session_id);
|
|
438
|
+
CREATE INDEX IF NOT EXISTS idx_observations_type ON observations(type);
|
|
439
|
+
CREATE INDEX IF NOT EXISTS idx_observations_created ON observations(created_at_epoch DESC);
|
|
440
|
+
CREATE INDEX IF NOT EXISTS idx_observations_plan_item ON observations(plan_item);
|
|
441
|
+
CREATE INDEX IF NOT EXISTS idx_observations_cr_rule ON observations(cr_rule);
|
|
442
|
+
CREATE INDEX IF NOT EXISTS idx_observations_importance ON observations(importance DESC);
|
|
443
|
+
`);
|
|
444
|
+
try {
|
|
445
|
+
db.exec(`
|
|
446
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS observations_fts USING fts5(
|
|
447
|
+
title, detail, evidence,
|
|
448
|
+
content='observations',
|
|
449
|
+
content_rowid='id'
|
|
450
|
+
);
|
|
451
|
+
`);
|
|
452
|
+
} catch (_e) {
|
|
453
|
+
}
|
|
454
|
+
db.exec(`
|
|
455
|
+
CREATE TRIGGER IF NOT EXISTS observations_ai AFTER INSERT ON observations BEGIN
|
|
456
|
+
INSERT INTO observations_fts(rowid, title, detail, evidence)
|
|
457
|
+
VALUES (new.id, new.title, new.detail, new.evidence);
|
|
458
|
+
END;
|
|
459
|
+
|
|
460
|
+
CREATE TRIGGER IF NOT EXISTS observations_ad AFTER DELETE ON observations BEGIN
|
|
461
|
+
INSERT INTO observations_fts(observations_fts, rowid, title, detail, evidence)
|
|
462
|
+
VALUES ('delete', old.id, old.title, old.detail, old.evidence);
|
|
463
|
+
END;
|
|
464
|
+
|
|
465
|
+
CREATE TRIGGER IF NOT EXISTS observations_au AFTER UPDATE ON observations BEGIN
|
|
466
|
+
INSERT INTO observations_fts(observations_fts, rowid, title, detail, evidence)
|
|
467
|
+
VALUES ('delete', old.id, old.title, old.detail, old.evidence);
|
|
468
|
+
INSERT INTO observations_fts(rowid, title, detail, evidence)
|
|
469
|
+
VALUES (new.id, new.title, new.detail, new.evidence);
|
|
470
|
+
END;
|
|
471
|
+
`);
|
|
472
|
+
db.exec(`
|
|
473
|
+
CREATE TABLE IF NOT EXISTS session_summaries (
|
|
474
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
475
|
+
session_id TEXT NOT NULL,
|
|
476
|
+
request TEXT,
|
|
477
|
+
investigated TEXT,
|
|
478
|
+
decisions TEXT,
|
|
479
|
+
completed TEXT,
|
|
480
|
+
failed_attempts TEXT,
|
|
481
|
+
next_steps TEXT,
|
|
482
|
+
files_created TEXT DEFAULT '[]',
|
|
483
|
+
files_modified TEXT DEFAULT '[]',
|
|
484
|
+
verification_results TEXT DEFAULT '{}',
|
|
485
|
+
plan_progress TEXT DEFAULT '{}',
|
|
486
|
+
created_at TEXT NOT NULL,
|
|
487
|
+
created_at_epoch INTEGER NOT NULL,
|
|
488
|
+
FOREIGN KEY(session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
CREATE INDEX IF NOT EXISTS idx_summaries_session ON session_summaries(session_id);
|
|
492
|
+
`);
|
|
493
|
+
db.exec(`
|
|
494
|
+
CREATE TABLE IF NOT EXISTS user_prompts (
|
|
495
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
496
|
+
session_id TEXT NOT NULL,
|
|
497
|
+
prompt_text TEXT NOT NULL,
|
|
498
|
+
prompt_number INTEGER NOT NULL DEFAULT 1,
|
|
499
|
+
created_at TEXT NOT NULL,
|
|
500
|
+
created_at_epoch INTEGER NOT NULL,
|
|
501
|
+
FOREIGN KEY(session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
|
|
502
|
+
);
|
|
503
|
+
`);
|
|
504
|
+
try {
|
|
505
|
+
db.exec(`
|
|
506
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS user_prompts_fts USING fts5(
|
|
507
|
+
prompt_text,
|
|
508
|
+
content='user_prompts',
|
|
509
|
+
content_rowid='id'
|
|
510
|
+
);
|
|
511
|
+
`);
|
|
512
|
+
} catch (_e) {
|
|
513
|
+
}
|
|
514
|
+
db.exec(`
|
|
515
|
+
CREATE TRIGGER IF NOT EXISTS prompts_ai AFTER INSERT ON user_prompts BEGIN
|
|
516
|
+
INSERT INTO user_prompts_fts(rowid, prompt_text) VALUES (new.id, new.prompt_text);
|
|
517
|
+
END;
|
|
518
|
+
|
|
519
|
+
CREATE TRIGGER IF NOT EXISTS prompts_ad AFTER DELETE ON user_prompts BEGIN
|
|
520
|
+
INSERT INTO user_prompts_fts(user_prompts_fts, rowid, prompt_text)
|
|
521
|
+
VALUES ('delete', old.id, old.prompt_text);
|
|
522
|
+
END;
|
|
523
|
+
`);
|
|
524
|
+
db.exec(`
|
|
525
|
+
CREATE TABLE IF NOT EXISTS memory_meta (
|
|
526
|
+
key TEXT PRIMARY KEY,
|
|
527
|
+
value TEXT NOT NULL
|
|
528
|
+
);
|
|
529
|
+
`);
|
|
530
|
+
db.exec(`
|
|
531
|
+
CREATE TABLE IF NOT EXISTS conversation_turns (
|
|
532
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
533
|
+
session_id TEXT NOT NULL,
|
|
534
|
+
turn_number INTEGER NOT NULL,
|
|
535
|
+
user_prompt TEXT NOT NULL,
|
|
536
|
+
assistant_response TEXT,
|
|
537
|
+
tool_calls_json TEXT,
|
|
538
|
+
tool_call_count INTEGER DEFAULT 0,
|
|
539
|
+
model_used TEXT,
|
|
540
|
+
duration_ms INTEGER,
|
|
541
|
+
prompt_tokens INTEGER,
|
|
542
|
+
response_tokens INTEGER,
|
|
543
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
544
|
+
created_at_epoch INTEGER DEFAULT (unixepoch()),
|
|
545
|
+
FOREIGN KEY (session_id) REFERENCES sessions(session_id)
|
|
546
|
+
);
|
|
547
|
+
|
|
548
|
+
CREATE INDEX IF NOT EXISTS idx_ct_session ON conversation_turns(session_id);
|
|
549
|
+
CREATE INDEX IF NOT EXISTS idx_ct_created ON conversation_turns(created_at DESC);
|
|
550
|
+
CREATE INDEX IF NOT EXISTS idx_ct_turn ON conversation_turns(session_id, turn_number);
|
|
551
|
+
`);
|
|
552
|
+
db.exec(`
|
|
553
|
+
CREATE TABLE IF NOT EXISTS tool_call_details (
|
|
554
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
555
|
+
session_id TEXT NOT NULL,
|
|
556
|
+
turn_number INTEGER NOT NULL,
|
|
557
|
+
tool_name TEXT NOT NULL,
|
|
558
|
+
tool_input_summary TEXT,
|
|
559
|
+
tool_input_size INTEGER,
|
|
560
|
+
tool_output_size INTEGER,
|
|
561
|
+
tool_success INTEGER DEFAULT 1,
|
|
562
|
+
duration_ms INTEGER,
|
|
563
|
+
files_involved TEXT,
|
|
564
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
565
|
+
created_at_epoch INTEGER DEFAULT (unixepoch()),
|
|
566
|
+
FOREIGN KEY (session_id) REFERENCES sessions(session_id)
|
|
567
|
+
);
|
|
568
|
+
|
|
569
|
+
CREATE INDEX IF NOT EXISTS idx_tcd_session ON tool_call_details(session_id);
|
|
570
|
+
CREATE INDEX IF NOT EXISTS idx_tcd_tool ON tool_call_details(tool_name);
|
|
571
|
+
CREATE INDEX IF NOT EXISTS idx_tcd_created ON tool_call_details(created_at DESC);
|
|
572
|
+
`);
|
|
573
|
+
try {
|
|
574
|
+
db.exec(`
|
|
575
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS conversation_turns_fts USING fts5(
|
|
576
|
+
user_prompt,
|
|
577
|
+
assistant_response,
|
|
578
|
+
content=conversation_turns,
|
|
579
|
+
content_rowid=id
|
|
580
|
+
);
|
|
581
|
+
`);
|
|
582
|
+
} catch (_e) {
|
|
583
|
+
}
|
|
584
|
+
db.exec(`
|
|
585
|
+
CREATE TRIGGER IF NOT EXISTS ct_fts_insert AFTER INSERT ON conversation_turns BEGIN
|
|
586
|
+
INSERT INTO conversation_turns_fts(rowid, user_prompt, assistant_response)
|
|
587
|
+
VALUES (new.id, new.user_prompt, new.assistant_response);
|
|
588
|
+
END;
|
|
589
|
+
|
|
590
|
+
CREATE TRIGGER IF NOT EXISTS ct_fts_delete AFTER DELETE ON conversation_turns BEGIN
|
|
591
|
+
INSERT INTO conversation_turns_fts(conversation_turns_fts, rowid, user_prompt, assistant_response)
|
|
592
|
+
VALUES ('delete', old.id, old.user_prompt, old.assistant_response);
|
|
593
|
+
END;
|
|
594
|
+
|
|
595
|
+
CREATE TRIGGER IF NOT EXISTS ct_fts_update AFTER UPDATE ON conversation_turns BEGIN
|
|
596
|
+
INSERT INTO conversation_turns_fts(conversation_turns_fts, rowid, user_prompt, assistant_response)
|
|
597
|
+
VALUES ('delete', old.id, old.user_prompt, old.assistant_response);
|
|
598
|
+
INSERT INTO conversation_turns_fts(rowid, user_prompt, assistant_response)
|
|
599
|
+
VALUES (new.id, new.user_prompt, new.assistant_response);
|
|
600
|
+
END;
|
|
601
|
+
`);
|
|
602
|
+
db.exec(`
|
|
603
|
+
CREATE TABLE IF NOT EXISTS session_quality_scores (
|
|
604
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
605
|
+
session_id TEXT NOT NULL UNIQUE,
|
|
606
|
+
project TEXT NOT NULL DEFAULT 'my-project',
|
|
607
|
+
score INTEGER NOT NULL DEFAULT 100,
|
|
608
|
+
security_score INTEGER NOT NULL DEFAULT 100,
|
|
609
|
+
architecture_score INTEGER NOT NULL DEFAULT 100,
|
|
610
|
+
coupling_score INTEGER NOT NULL DEFAULT 100,
|
|
611
|
+
test_score INTEGER NOT NULL DEFAULT 100,
|
|
612
|
+
rule_compliance_score INTEGER NOT NULL DEFAULT 100,
|
|
613
|
+
observations_total INTEGER NOT NULL DEFAULT 0,
|
|
614
|
+
bugs_found INTEGER NOT NULL DEFAULT 0,
|
|
615
|
+
bugs_fixed INTEGER NOT NULL DEFAULT 0,
|
|
616
|
+
vr_checks_passed INTEGER NOT NULL DEFAULT 0,
|
|
617
|
+
vr_checks_failed INTEGER NOT NULL DEFAULT 0,
|
|
618
|
+
incidents_triggered INTEGER NOT NULL DEFAULT 0,
|
|
619
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
620
|
+
FOREIGN KEY (session_id) REFERENCES sessions(session_id)
|
|
621
|
+
);
|
|
622
|
+
CREATE INDEX IF NOT EXISTS idx_sqs_session ON session_quality_scores(session_id);
|
|
623
|
+
CREATE INDEX IF NOT EXISTS idx_sqs_project ON session_quality_scores(project);
|
|
624
|
+
`);
|
|
625
|
+
db.exec(`
|
|
626
|
+
CREATE TABLE IF NOT EXISTS session_costs (
|
|
627
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
628
|
+
session_id TEXT NOT NULL UNIQUE,
|
|
629
|
+
project TEXT NOT NULL DEFAULT 'my-project',
|
|
630
|
+
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
631
|
+
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
632
|
+
cache_read_tokens INTEGER NOT NULL DEFAULT 0,
|
|
633
|
+
cache_write_tokens INTEGER NOT NULL DEFAULT 0,
|
|
634
|
+
total_tokens INTEGER NOT NULL DEFAULT 0,
|
|
635
|
+
estimated_cost_usd REAL NOT NULL DEFAULT 0.0,
|
|
636
|
+
model TEXT,
|
|
637
|
+
duration_minutes REAL NOT NULL DEFAULT 0.0,
|
|
638
|
+
tool_calls INTEGER NOT NULL DEFAULT 0,
|
|
639
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
640
|
+
FOREIGN KEY (session_id) REFERENCES sessions(session_id)
|
|
641
|
+
);
|
|
642
|
+
CREATE INDEX IF NOT EXISTS idx_sc_session ON session_costs(session_id);
|
|
643
|
+
`);
|
|
644
|
+
db.exec(`
|
|
645
|
+
CREATE TABLE IF NOT EXISTS feature_costs (
|
|
646
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
647
|
+
feature_key TEXT NOT NULL,
|
|
648
|
+
session_id TEXT NOT NULL,
|
|
649
|
+
tokens_used INTEGER NOT NULL DEFAULT 0,
|
|
650
|
+
estimated_cost_usd REAL NOT NULL DEFAULT 0.0,
|
|
651
|
+
commit_hash TEXT,
|
|
652
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
653
|
+
FOREIGN KEY (session_id) REFERENCES sessions(session_id)
|
|
654
|
+
);
|
|
655
|
+
CREATE INDEX IF NOT EXISTS idx_fc_feature ON feature_costs(feature_key);
|
|
656
|
+
CREATE INDEX IF NOT EXISTS idx_fc_session ON feature_costs(session_id);
|
|
657
|
+
`);
|
|
658
|
+
db.exec(`
|
|
659
|
+
CREATE TABLE IF NOT EXISTS prompt_outcomes (
|
|
660
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
661
|
+
session_id TEXT NOT NULL,
|
|
662
|
+
prompt_hash TEXT NOT NULL,
|
|
663
|
+
prompt_text TEXT NOT NULL,
|
|
664
|
+
prompt_category TEXT NOT NULL DEFAULT 'feature',
|
|
665
|
+
word_count INTEGER NOT NULL DEFAULT 0,
|
|
666
|
+
outcome TEXT NOT NULL DEFAULT 'success' CHECK(outcome IN ('success', 'partial', 'failure', 'abandoned')),
|
|
667
|
+
corrections_needed INTEGER NOT NULL DEFAULT 0,
|
|
668
|
+
follow_up_prompts INTEGER NOT NULL DEFAULT 0,
|
|
669
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
670
|
+
FOREIGN KEY (session_id) REFERENCES sessions(session_id)
|
|
671
|
+
);
|
|
672
|
+
CREATE INDEX IF NOT EXISTS idx_po_session ON prompt_outcomes(session_id);
|
|
673
|
+
CREATE INDEX IF NOT EXISTS idx_po_category ON prompt_outcomes(prompt_category);
|
|
674
|
+
`);
|
|
675
|
+
db.exec(`
|
|
676
|
+
CREATE TABLE IF NOT EXISTS audit_log (
|
|
677
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
678
|
+
session_id TEXT NOT NULL,
|
|
679
|
+
timestamp TEXT DEFAULT (datetime('now')),
|
|
680
|
+
event_type TEXT NOT NULL CHECK(event_type IN ('code_change', 'rule_enforced', 'approval', 'review', 'commit', 'compaction')),
|
|
681
|
+
actor TEXT NOT NULL DEFAULT 'ai' CHECK(actor IN ('ai', 'human', 'hook', 'agent')),
|
|
682
|
+
model_id TEXT,
|
|
683
|
+
file_path TEXT,
|
|
684
|
+
change_type TEXT CHECK(change_type IN ('create', 'edit', 'delete')),
|
|
685
|
+
rules_in_effect TEXT,
|
|
686
|
+
approval_status TEXT CHECK(approval_status IN ('auto_approved', 'human_approved', 'pending', 'denied')),
|
|
687
|
+
evidence TEXT,
|
|
688
|
+
metadata TEXT,
|
|
689
|
+
FOREIGN KEY (session_id) REFERENCES sessions(session_id)
|
|
690
|
+
);
|
|
691
|
+
CREATE INDEX IF NOT EXISTS idx_al_session ON audit_log(session_id);
|
|
692
|
+
CREATE INDEX IF NOT EXISTS idx_al_file ON audit_log(file_path);
|
|
693
|
+
CREATE INDEX IF NOT EXISTS idx_al_event ON audit_log(event_type);
|
|
694
|
+
CREATE INDEX IF NOT EXISTS idx_al_timestamp ON audit_log(timestamp DESC);
|
|
695
|
+
`);
|
|
696
|
+
db.exec(`
|
|
697
|
+
CREATE TABLE IF NOT EXISTS validation_results (
|
|
698
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
699
|
+
session_id TEXT NOT NULL,
|
|
700
|
+
file_path TEXT NOT NULL,
|
|
701
|
+
validation_type TEXT NOT NULL,
|
|
702
|
+
passed INTEGER NOT NULL DEFAULT 1,
|
|
703
|
+
details TEXT,
|
|
704
|
+
rules_violated TEXT,
|
|
705
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
706
|
+
FOREIGN KEY (session_id) REFERENCES sessions(session_id)
|
|
707
|
+
);
|
|
708
|
+
CREATE INDEX IF NOT EXISTS idx_vr_session ON validation_results(session_id);
|
|
709
|
+
CREATE INDEX IF NOT EXISTS idx_vr_file ON validation_results(file_path);
|
|
710
|
+
`);
|
|
711
|
+
db.exec(`
|
|
712
|
+
CREATE TABLE IF NOT EXISTS architecture_decisions (
|
|
713
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
714
|
+
session_id TEXT NOT NULL,
|
|
715
|
+
title TEXT NOT NULL,
|
|
716
|
+
context TEXT,
|
|
717
|
+
decision TEXT NOT NULL,
|
|
718
|
+
status TEXT NOT NULL DEFAULT 'accepted' CHECK(status IN ('accepted', 'superseded', 'deprecated')),
|
|
719
|
+
alternatives TEXT,
|
|
720
|
+
consequences TEXT,
|
|
721
|
+
affected_files TEXT,
|
|
722
|
+
commit_hash TEXT,
|
|
723
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
724
|
+
FOREIGN KEY (session_id) REFERENCES sessions(session_id)
|
|
725
|
+
);
|
|
726
|
+
CREATE INDEX IF NOT EXISTS idx_ad_session ON architecture_decisions(session_id);
|
|
727
|
+
CREATE INDEX IF NOT EXISTS idx_ad_status ON architecture_decisions(status);
|
|
728
|
+
`);
|
|
729
|
+
db.exec(`
|
|
730
|
+
CREATE TABLE IF NOT EXISTS security_scores (
|
|
731
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
732
|
+
session_id TEXT NOT NULL,
|
|
733
|
+
file_path TEXT NOT NULL,
|
|
734
|
+
risk_score INTEGER NOT NULL DEFAULT 0,
|
|
735
|
+
findings TEXT,
|
|
736
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
737
|
+
FOREIGN KEY (session_id) REFERENCES sessions(session_id)
|
|
738
|
+
);
|
|
739
|
+
CREATE INDEX IF NOT EXISTS idx_ss_session ON security_scores(session_id);
|
|
740
|
+
CREATE INDEX IF NOT EXISTS idx_ss_file ON security_scores(file_path);
|
|
741
|
+
`);
|
|
742
|
+
db.exec(`
|
|
743
|
+
CREATE TABLE IF NOT EXISTS dependency_assessments (
|
|
744
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
745
|
+
package_name TEXT NOT NULL,
|
|
746
|
+
version TEXT,
|
|
747
|
+
risk_score INTEGER NOT NULL DEFAULT 0,
|
|
748
|
+
vulnerabilities INTEGER NOT NULL DEFAULT 0,
|
|
749
|
+
last_publish_days INTEGER,
|
|
750
|
+
weekly_downloads INTEGER,
|
|
751
|
+
license TEXT,
|
|
752
|
+
bundle_size_kb INTEGER,
|
|
753
|
+
previous_removals INTEGER NOT NULL DEFAULT 0,
|
|
754
|
+
assessed_at TEXT DEFAULT (datetime('now'))
|
|
755
|
+
);
|
|
756
|
+
CREATE INDEX IF NOT EXISTS idx_da_package ON dependency_assessments(package_name);
|
|
757
|
+
`);
|
|
758
|
+
db.exec(`
|
|
759
|
+
CREATE TABLE IF NOT EXISTS developer_expertise (
|
|
760
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
761
|
+
developer_id TEXT NOT NULL,
|
|
762
|
+
module TEXT NOT NULL,
|
|
763
|
+
session_count INTEGER NOT NULL DEFAULT 0,
|
|
764
|
+
observation_count INTEGER NOT NULL DEFAULT 0,
|
|
765
|
+
expertise_score INTEGER NOT NULL DEFAULT 0,
|
|
766
|
+
last_active TEXT DEFAULT (datetime('now')),
|
|
767
|
+
UNIQUE(developer_id, module)
|
|
768
|
+
);
|
|
769
|
+
CREATE INDEX IF NOT EXISTS idx_de_developer ON developer_expertise(developer_id);
|
|
770
|
+
CREATE INDEX IF NOT EXISTS idx_de_module ON developer_expertise(module);
|
|
771
|
+
`);
|
|
772
|
+
db.exec(`
|
|
773
|
+
CREATE TABLE IF NOT EXISTS shared_observations (
|
|
774
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
775
|
+
original_id INTEGER,
|
|
776
|
+
developer_id TEXT NOT NULL,
|
|
777
|
+
project TEXT NOT NULL,
|
|
778
|
+
observation_type TEXT NOT NULL,
|
|
779
|
+
summary TEXT NOT NULL,
|
|
780
|
+
file_path TEXT,
|
|
781
|
+
module TEXT,
|
|
782
|
+
severity INTEGER NOT NULL DEFAULT 3,
|
|
783
|
+
is_shared INTEGER NOT NULL DEFAULT 0,
|
|
784
|
+
shared_at TEXT,
|
|
785
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
786
|
+
);
|
|
787
|
+
CREATE INDEX IF NOT EXISTS idx_so_developer ON shared_observations(developer_id);
|
|
788
|
+
CREATE INDEX IF NOT EXISTS idx_so_file ON shared_observations(file_path);
|
|
789
|
+
CREATE INDEX IF NOT EXISTS idx_so_module ON shared_observations(module);
|
|
790
|
+
`);
|
|
791
|
+
db.exec(`
|
|
792
|
+
CREATE TABLE IF NOT EXISTS knowledge_conflicts (
|
|
793
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
794
|
+
file_path TEXT NOT NULL,
|
|
795
|
+
developer_a TEXT NOT NULL,
|
|
796
|
+
developer_b TEXT NOT NULL,
|
|
797
|
+
conflict_type TEXT NOT NULL DEFAULT 'concurrent_edit',
|
|
798
|
+
resolved INTEGER NOT NULL DEFAULT 0,
|
|
799
|
+
detected_at TEXT DEFAULT (datetime('now'))
|
|
800
|
+
);
|
|
801
|
+
CREATE INDEX IF NOT EXISTS idx_kc_file ON knowledge_conflicts(file_path);
|
|
802
|
+
`);
|
|
803
|
+
db.exec(`
|
|
804
|
+
CREATE TABLE IF NOT EXISTS feature_health (
|
|
805
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
806
|
+
feature_key TEXT NOT NULL UNIQUE,
|
|
807
|
+
health_score INTEGER NOT NULL DEFAULT 100,
|
|
808
|
+
tests_passing INTEGER NOT NULL DEFAULT 0,
|
|
809
|
+
tests_failing INTEGER NOT NULL DEFAULT 0,
|
|
810
|
+
test_coverage_pct REAL,
|
|
811
|
+
modifications_since_test INTEGER NOT NULL DEFAULT 0,
|
|
812
|
+
last_modified TEXT,
|
|
813
|
+
last_tested TEXT,
|
|
814
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
815
|
+
);
|
|
816
|
+
CREATE INDEX IF NOT EXISTS idx_fh_feature ON feature_health(feature_key);
|
|
817
|
+
CREATE INDEX IF NOT EXISTS idx_fh_health ON feature_health(health_score);
|
|
818
|
+
`);
|
|
819
|
+
db.exec(`
|
|
820
|
+
CREATE TABLE IF NOT EXISTS tool_cost_events (
|
|
821
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
822
|
+
session_id TEXT NOT NULL,
|
|
823
|
+
tool_name TEXT NOT NULL,
|
|
824
|
+
estimated_input_tokens INTEGER DEFAULT 0,
|
|
825
|
+
estimated_output_tokens INTEGER DEFAULT 0,
|
|
826
|
+
model TEXT DEFAULT '',
|
|
827
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
828
|
+
);
|
|
829
|
+
CREATE INDEX IF NOT EXISTS idx_tce_session ON tool_cost_events(session_id);
|
|
830
|
+
CREATE INDEX IF NOT EXISTS idx_tce_tool ON tool_cost_events(tool_name);
|
|
831
|
+
CREATE INDEX IF NOT EXISTS idx_tce_created ON tool_cost_events(created_at DESC);
|
|
832
|
+
`);
|
|
833
|
+
db.exec(`
|
|
834
|
+
CREATE TABLE IF NOT EXISTS quality_events (
|
|
835
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
836
|
+
session_id TEXT NOT NULL,
|
|
837
|
+
event_type TEXT NOT NULL,
|
|
838
|
+
tool_name TEXT NOT NULL,
|
|
839
|
+
details TEXT DEFAULT '',
|
|
840
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
841
|
+
);
|
|
842
|
+
CREATE INDEX IF NOT EXISTS idx_qe_session ON quality_events(session_id);
|
|
843
|
+
CREATE INDEX IF NOT EXISTS idx_qe_event_type ON quality_events(event_type);
|
|
844
|
+
CREATE INDEX IF NOT EXISTS idx_qe_created ON quality_events(created_at DESC);
|
|
845
|
+
`);
|
|
846
|
+
db.exec(`
|
|
847
|
+
CREATE TABLE IF NOT EXISTS pending_sync (
|
|
848
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
849
|
+
payload TEXT NOT NULL,
|
|
850
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
851
|
+
retry_count INTEGER NOT NULL DEFAULT 0,
|
|
852
|
+
last_error TEXT
|
|
853
|
+
);
|
|
854
|
+
CREATE INDEX IF NOT EXISTS idx_pending_sync_created ON pending_sync(created_at ASC);
|
|
855
|
+
`);
|
|
856
|
+
db.exec(`
|
|
857
|
+
CREATE TABLE IF NOT EXISTS license_cache (
|
|
858
|
+
api_key_hash TEXT PRIMARY KEY,
|
|
859
|
+
tier TEXT NOT NULL,
|
|
860
|
+
valid_until TEXT NOT NULL,
|
|
861
|
+
last_validated TEXT NOT NULL,
|
|
862
|
+
features TEXT DEFAULT '[]'
|
|
863
|
+
);
|
|
864
|
+
`);
|
|
865
|
+
db.exec(`
|
|
866
|
+
CREATE TABLE IF NOT EXISTS failure_classes (
|
|
867
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
868
|
+
name TEXT NOT NULL UNIQUE,
|
|
869
|
+
description TEXT NOT NULL,
|
|
870
|
+
diff_patterns TEXT NOT NULL DEFAULT '[]',
|
|
871
|
+
file_patterns TEXT NOT NULL DEFAULT '[]',
|
|
872
|
+
prompt_keywords TEXT NOT NULL DEFAULT '[]',
|
|
873
|
+
incidents TEXT NOT NULL DEFAULT '[]',
|
|
874
|
+
rules TEXT NOT NULL DEFAULT '[]',
|
|
875
|
+
scanner_checks TEXT NOT NULL DEFAULT '[]',
|
|
876
|
+
known_message TEXT NOT NULL DEFAULT '',
|
|
877
|
+
needs_review INTEGER NOT NULL DEFAULT 0,
|
|
878
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
879
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
880
|
+
);
|
|
881
|
+
CREATE INDEX IF NOT EXISTS idx_fc_name ON failure_classes(name);
|
|
882
|
+
CREATE INDEX IF NOT EXISTS idx_fc_needs_review ON failure_classes(needs_review);
|
|
883
|
+
`);
|
|
884
|
+
}
|
|
885
|
+
function addFailureClass(db, opts) {
|
|
886
|
+
const result = db.prepare(`
|
|
887
|
+
INSERT OR IGNORE INTO failure_classes (name, description, diff_patterns, file_patterns, prompt_keywords, incidents, rules, scanner_checks, known_message, needs_review)
|
|
888
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
889
|
+
`).run(
|
|
890
|
+
opts.name,
|
|
891
|
+
opts.description,
|
|
892
|
+
JSON.stringify(opts.diffPatterns ?? []),
|
|
893
|
+
JSON.stringify(opts.filePatterns ?? []),
|
|
894
|
+
JSON.stringify(opts.promptKeywords ?? []),
|
|
895
|
+
JSON.stringify(opts.incidents ?? []),
|
|
896
|
+
JSON.stringify(opts.rules ?? []),
|
|
897
|
+
JSON.stringify(opts.scannerChecks ?? []),
|
|
898
|
+
opts.knownMessage ?? "",
|
|
899
|
+
opts.needsReview ? 1 : 0
|
|
900
|
+
);
|
|
901
|
+
return Number(result.lastInsertRowid);
|
|
902
|
+
}
|
|
903
|
+
function getFailureClasses(db) {
|
|
904
|
+
const rows = db.prepare("SELECT * FROM failure_classes ORDER BY name").all();
|
|
905
|
+
return rows.map((row) => ({
|
|
906
|
+
id: row.id,
|
|
907
|
+
name: row.name,
|
|
908
|
+
description: row.description,
|
|
909
|
+
diff_patterns: JSON.parse(row.diff_patterns || "[]"),
|
|
910
|
+
file_patterns: JSON.parse(row.file_patterns || "[]"),
|
|
911
|
+
prompt_keywords: JSON.parse(row.prompt_keywords || "[]"),
|
|
912
|
+
incidents: JSON.parse(row.incidents || "[]"),
|
|
913
|
+
rules: JSON.parse(row.rules || "[]"),
|
|
914
|
+
scanner_checks: JSON.parse(row.scanner_checks || "[]"),
|
|
915
|
+
known_message: row.known_message,
|
|
916
|
+
needs_review: !!row.needs_review
|
|
917
|
+
}));
|
|
918
|
+
}
|
|
919
|
+
function appendIncidentToFailureClass(db, className, incidentId) {
|
|
920
|
+
const row = db.prepare("SELECT incidents FROM failure_classes WHERE name = ?").get(className);
|
|
921
|
+
if (!row) return;
|
|
922
|
+
const incidents = JSON.parse(row.incidents || "[]");
|
|
923
|
+
if (!incidents.includes(incidentId)) {
|
|
924
|
+
incidents.push(incidentId);
|
|
925
|
+
db.prepare("UPDATE failure_classes SET incidents = ?, updated_at = datetime('now') WHERE name = ?").run(JSON.stringify(incidents), className);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
function scoreFailureClasses(db, matchText, filePath, promptContext, weights) {
|
|
929
|
+
const classes = getFailureClasses(db);
|
|
930
|
+
if (classes.length === 0) return null;
|
|
931
|
+
const diffWeight = weights?.diffPatternWeight ?? 3;
|
|
932
|
+
const fileWeight = weights?.filePatternWeight ?? 2;
|
|
933
|
+
const promptWeight = weights?.promptKeywordWeight ?? 2;
|
|
934
|
+
let bestMatch = null;
|
|
935
|
+
for (const fc of classes) {
|
|
936
|
+
let score = 0;
|
|
937
|
+
for (const pattern of fc.diff_patterns) {
|
|
938
|
+
if (!pattern) continue;
|
|
939
|
+
try {
|
|
940
|
+
if (new RegExp(pattern, "i").test(matchText)) {
|
|
941
|
+
score += diffWeight;
|
|
942
|
+
}
|
|
943
|
+
} catch {
|
|
944
|
+
if (matchText.toLowerCase().includes(pattern.toLowerCase())) {
|
|
945
|
+
score += diffWeight;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
for (const pattern of fc.file_patterns) {
|
|
950
|
+
if (!pattern) continue;
|
|
951
|
+
try {
|
|
952
|
+
if (new RegExp(pattern).test(filePath)) {
|
|
953
|
+
score += fileWeight;
|
|
954
|
+
}
|
|
955
|
+
} catch {
|
|
956
|
+
if (filePath.includes(pattern)) {
|
|
957
|
+
score += fileWeight;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
if (promptContext) {
|
|
962
|
+
for (const keyword of fc.prompt_keywords) {
|
|
963
|
+
if (!keyword) continue;
|
|
964
|
+
try {
|
|
965
|
+
if (new RegExp(keyword, "i").test(promptContext)) {
|
|
966
|
+
score += promptWeight;
|
|
967
|
+
}
|
|
968
|
+
} catch {
|
|
969
|
+
if (promptContext.toLowerCase().includes(keyword.toLowerCase())) {
|
|
970
|
+
score += promptWeight;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
if (!bestMatch || score > bestMatch.score) {
|
|
976
|
+
bestMatch = {
|
|
977
|
+
name: fc.name,
|
|
978
|
+
score,
|
|
979
|
+
incidentCount: fc.incidents.length,
|
|
980
|
+
rules: fc.rules,
|
|
981
|
+
knownMessage: fc.known_message
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
return bestMatch;
|
|
986
|
+
}
|
|
326
987
|
|
|
327
988
|
// src/hooks/incident-pipeline.ts
|
|
328
989
|
async function main() {
|
|
@@ -347,17 +1008,17 @@ async function main() {
|
|
|
347
1008
|
process.exit(0);
|
|
348
1009
|
return;
|
|
349
1010
|
}
|
|
350
|
-
if (!
|
|
1011
|
+
if (!existsSync3(filePath)) {
|
|
351
1012
|
process.exit(0);
|
|
352
1013
|
return;
|
|
353
1014
|
}
|
|
354
1015
|
const content = readFileSync2(filePath, "utf-8");
|
|
355
1016
|
const titleMatch = content.match(/^#\s+(.+)/m);
|
|
356
|
-
const title = titleMatch?.[1] ??
|
|
357
|
-
const slug =
|
|
358
|
-
const memoryDirAbs =
|
|
1017
|
+
const title = titleMatch?.[1] ?? basename2(filePath, ".md");
|
|
1018
|
+
const slug = basename2(filePath, ".md").replace(/^\d{4}-\d{2}-\d{2}-?/, "").toLowerCase();
|
|
1019
|
+
const memoryDirAbs = resolve3(root, memoryDir);
|
|
359
1020
|
let hasExistingRule = false;
|
|
360
|
-
if (
|
|
1021
|
+
if (existsSync3(memoryDirAbs)) {
|
|
361
1022
|
const ruleFiles = readdirSync(memoryDirAbs).filter((f) => f.startsWith("feedback_"));
|
|
362
1023
|
for (const ruleFile of ruleFiles) {
|
|
363
1024
|
if (ruleFile.toLowerCase().includes(slug.slice(0, 20))) {
|
|
@@ -408,19 +1069,46 @@ async function main() {
|
|
|
408
1069
|
lines.push("============================================================================");
|
|
409
1070
|
lines.push("");
|
|
410
1071
|
console.log(lines.join("\n"));
|
|
1072
|
+
try {
|
|
1073
|
+
const taxonomyConfig = config.autoLearning?.failureClassification;
|
|
1074
|
+
if (taxonomyConfig?.enabled !== false) {
|
|
1075
|
+
const db = getMemoryDb();
|
|
1076
|
+
try {
|
|
1077
|
+
const thresholds = taxonomyConfig?.thresholds ?? { known: 5, similar: 3 };
|
|
1078
|
+
const scoringWeights = taxonomyConfig?.scoring;
|
|
1079
|
+
const incidentNumMatch = basename2(filePath, ".md").match(/(\d+)/);
|
|
1080
|
+
const incidentId = incidentNumMatch ? incidentNumMatch[1] : basename2(filePath, ".md");
|
|
1081
|
+
const bestMatch = scoreFailureClasses(db, content, filePath, title, scoringWeights);
|
|
1082
|
+
if (bestMatch && bestMatch.score >= thresholds.similar) {
|
|
1083
|
+
appendIncidentToFailureClass(db, bestMatch.name, incidentId);
|
|
1084
|
+
} else {
|
|
1085
|
+
const stubName = `auto_${slug.replace(/[^a-z0-9_]/g, "_").slice(0, 50)}`;
|
|
1086
|
+
addFailureClass(db, {
|
|
1087
|
+
name: stubName,
|
|
1088
|
+
description: `Auto-created from incident ${incidentId}: ${title.slice(0, 100)}`,
|
|
1089
|
+
incidents: [incidentId],
|
|
1090
|
+
needsReview: true
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
} finally {
|
|
1094
|
+
db.close();
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
} catch {
|
|
1098
|
+
}
|
|
411
1099
|
} catch {
|
|
412
1100
|
}
|
|
413
1101
|
process.exit(0);
|
|
414
1102
|
}
|
|
415
1103
|
function readStdin() {
|
|
416
|
-
return new Promise((
|
|
1104
|
+
return new Promise((resolve4) => {
|
|
417
1105
|
let data = "";
|
|
418
1106
|
process.stdin.setEncoding("utf-8");
|
|
419
1107
|
process.stdin.on("data", (chunk) => {
|
|
420
1108
|
data += chunk;
|
|
421
1109
|
});
|
|
422
|
-
process.stdin.on("end", () =>
|
|
423
|
-
setTimeout(() =>
|
|
1110
|
+
process.stdin.on("end", () => resolve4(data));
|
|
1111
|
+
setTimeout(() => resolve4(data), 3e3);
|
|
424
1112
|
});
|
|
425
1113
|
}
|
|
426
1114
|
main();
|