@massu/core 1.15.7 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +63 -0
- package/dist/cli.js +1943 -774
- package/dist/hooks/auto-learning-pipeline.js +152 -67
- package/dist/hooks/classify-failure.js +152 -67
- package/dist/hooks/cost-tracker.js +152 -67
- package/dist/hooks/fix-detector.js +152 -67
- package/dist/hooks/incident-pipeline.js +152 -67
- package/dist/hooks/intent-suggester.js +152 -67
- package/dist/hooks/memory-recall.js +152 -67
- package/dist/hooks/post-edit-context.js +152 -67
- package/dist/hooks/post-tool-use.js +152 -67
- package/dist/hooks/pre-compact.js +152 -67
- package/dist/hooks/pre-delete-check.js +154 -69
- package/dist/hooks/pre-tool-use-gate.js +154 -69
- package/dist/hooks/quality-event.js +152 -67
- package/dist/hooks/rule-enforcement-pipeline.js +152 -67
- package/dist/hooks/security-gate.js +152 -67
- package/dist/hooks/session-end.js +346 -80
- package/dist/hooks/session-start.js +155 -67
- package/dist/hooks/user-prompt.js +222 -111
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1082,6 +1082,142 @@ var init_db_backup = __esm({
|
|
|
1082
1082
|
}
|
|
1083
1083
|
});
|
|
1084
1084
|
|
|
1085
|
+
// src/rule-delivery.ts
|
|
1086
|
+
function tableExists(db, table) {
|
|
1087
|
+
const row = db.prepare("SELECT 1 AS ok FROM sqlite_master WHERE type='table' AND name=?").get(table);
|
|
1088
|
+
return row !== void 0;
|
|
1089
|
+
}
|
|
1090
|
+
function columns(db, table) {
|
|
1091
|
+
const rows = db.prepare(`PRAGMA table_info(${table})`).all();
|
|
1092
|
+
return new Set(rows.map((r2) => r2.name));
|
|
1093
|
+
}
|
|
1094
|
+
function migrateRuleDelivery(db) {
|
|
1095
|
+
const ADDITIONS = [
|
|
1096
|
+
{ name: "attempts", decl: "INTEGER NOT NULL DEFAULT 0" },
|
|
1097
|
+
{ name: "lease_token", decl: "TEXT" },
|
|
1098
|
+
{ name: "leased_at_ms", decl: "INTEGER" },
|
|
1099
|
+
{ name: "last_error", decl: "TEXT" }
|
|
1100
|
+
];
|
|
1101
|
+
for (const table of ALL_OUTBOUND_TABLES) {
|
|
1102
|
+
if (!tableExists(db, table)) continue;
|
|
1103
|
+
const cols = columns(db, table);
|
|
1104
|
+
for (const add of ADDITIONS) {
|
|
1105
|
+
if (cols.has(add.name)) continue;
|
|
1106
|
+
db.exec(`ALTER TABLE ${table} ADD COLUMN ${add.name} ${add.decl}`);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
function undeliveredSnapshot(db) {
|
|
1111
|
+
const count = (t2) => {
|
|
1112
|
+
if (!tableExists(db, t2)) return 0;
|
|
1113
|
+
const r2 = db.prepare(`SELECT COUNT(*) AS n FROM ${t2}`).get();
|
|
1114
|
+
return r2.n;
|
|
1115
|
+
};
|
|
1116
|
+
const promotions = count("team_promotion_outbound");
|
|
1117
|
+
const revocations = count("team_revocation_outbound");
|
|
1118
|
+
const events = count(TELEMETRY_OUTBOUND_TABLE);
|
|
1119
|
+
let maxAttempts = 0;
|
|
1120
|
+
let oldest = null;
|
|
1121
|
+
for (const t2 of RULE_OUTBOUND_TABLES) {
|
|
1122
|
+
if (!tableExists(db, t2)) continue;
|
|
1123
|
+
if (!columns(db, t2).has("attempts")) continue;
|
|
1124
|
+
const r2 = db.prepare(`SELECT MAX(attempts) AS a, MIN(created_at) AS c FROM ${t2}`).get();
|
|
1125
|
+
if (r2.a !== null && r2.a > maxAttempts) maxAttempts = r2.a;
|
|
1126
|
+
if (r2.c !== null && (oldest === null || r2.c < oldest)) oldest = r2.c;
|
|
1127
|
+
}
|
|
1128
|
+
return {
|
|
1129
|
+
promotions,
|
|
1130
|
+
revocations,
|
|
1131
|
+
events,
|
|
1132
|
+
max_rule_attempts: maxAttempts,
|
|
1133
|
+
oldest_rule_created_at: oldest,
|
|
1134
|
+
stalled: promotions + revocations > 0 && maxAttempts >= STALL_ATTEMPTS
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
function capTelemetry(db, cap = TELEMETRY_OUTBOX_CAP) {
|
|
1138
|
+
if (!tableExists(db, TELEMETRY_OUTBOUND_TABLE)) return 0;
|
|
1139
|
+
const before = db.prepare(`SELECT COUNT(*) AS n FROM ${TELEMETRY_OUTBOUND_TABLE}`).get();
|
|
1140
|
+
if (before.n <= cap) return 0;
|
|
1141
|
+
const info = db.prepare(
|
|
1142
|
+
`DELETE FROM ${TELEMETRY_OUTBOUND_TABLE}
|
|
1143
|
+
WHERE id NOT IN (SELECT id FROM ${TELEMETRY_OUTBOUND_TABLE} ORDER BY id DESC LIMIT ?)`
|
|
1144
|
+
).run(cap);
|
|
1145
|
+
const dropped = info.changes;
|
|
1146
|
+
if (dropped > 0) {
|
|
1147
|
+
process.stderr.write(
|
|
1148
|
+
`[massu] NOTE: dropped ${dropped} oldest promotion-funnel telemetry row(s) at the ${cap}-row cap. No learned rules were affected (rules are never dropped).
|
|
1149
|
+
`
|
|
1150
|
+
);
|
|
1151
|
+
recordAnalytics(db, EVENT_TELEMETRY_CAPPED, { dropped, cap });
|
|
1152
|
+
}
|
|
1153
|
+
return dropped;
|
|
1154
|
+
}
|
|
1155
|
+
function recordAnalytics(db, eventType, data) {
|
|
1156
|
+
try {
|
|
1157
|
+
db.prepare(
|
|
1158
|
+
`INSERT INTO analytics_events (event_type, event_data, created_at)
|
|
1159
|
+
VALUES (?, ?, datetime('now'))`
|
|
1160
|
+
).run(eventType, JSON.stringify(data));
|
|
1161
|
+
} catch (err) {
|
|
1162
|
+
process.stderr.write(
|
|
1163
|
+
`[massu] WARNING: could not record '${eventType}' telemetry: ${err instanceof Error ? err.message : String(err)}
|
|
1164
|
+
`
|
|
1165
|
+
);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
var RULE_OUTBOUND_TABLES, TELEMETRY_OUTBOUND_TABLE, ALL_OUTBOUND_TABLES, TELEMETRY_OUTBOX_CAP, LEASE_TTL_MS, STALL_ATTEMPTS, EVENT_TELEMETRY_CAPPED;
|
|
1169
|
+
var init_rule_delivery = __esm({
|
|
1170
|
+
"src/rule-delivery.ts"() {
|
|
1171
|
+
"use strict";
|
|
1172
|
+
RULE_OUTBOUND_TABLES = [
|
|
1173
|
+
"team_promotion_outbound",
|
|
1174
|
+
"team_revocation_outbound"
|
|
1175
|
+
];
|
|
1176
|
+
TELEMETRY_OUTBOUND_TABLE = "rule_promotion_events_outbound";
|
|
1177
|
+
ALL_OUTBOUND_TABLES = [
|
|
1178
|
+
...RULE_OUTBOUND_TABLES,
|
|
1179
|
+
TELEMETRY_OUTBOUND_TABLE
|
|
1180
|
+
];
|
|
1181
|
+
TELEMETRY_OUTBOX_CAP = 2e4;
|
|
1182
|
+
LEASE_TTL_MS = 15 * 60 * 1e3;
|
|
1183
|
+
STALL_ATTEMPTS = 5;
|
|
1184
|
+
EVENT_TELEMETRY_CAPPED = "rule_telemetry_capped";
|
|
1185
|
+
}
|
|
1186
|
+
});
|
|
1187
|
+
|
|
1188
|
+
// src/rule-candidate-store.ts
|
|
1189
|
+
function ensureRuleCandidatesTable(db) {
|
|
1190
|
+
db.exec(`
|
|
1191
|
+
CREATE TABLE IF NOT EXISTS rule_candidates (
|
|
1192
|
+
prompt_hash TEXT PRIMARY KEY,
|
|
1193
|
+
status TEXT NOT NULL DEFAULT 'proposed'
|
|
1194
|
+
CHECK (status IN ('proposed','shown','promoted','dismissed')),
|
|
1195
|
+
origin TEXT NOT NULL DEFAULT 'local'
|
|
1196
|
+
CHECK (origin IN ('local','team','pack')),
|
|
1197
|
+
score REAL,
|
|
1198
|
+
destination TEXT,
|
|
1199
|
+
payload_json TEXT NOT NULL DEFAULT '{}',
|
|
1200
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1201
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1202
|
+
);
|
|
1203
|
+
CREATE INDEX IF NOT EXISTS idx_rule_candidates_status ON rule_candidates(status);
|
|
1204
|
+
CREATE INDEX IF NOT EXISTS idx_rule_candidates_created ON rule_candidates(created_at);
|
|
1205
|
+
`);
|
|
1206
|
+
}
|
|
1207
|
+
function setCandidateStatus(db, promptHash, status, destination) {
|
|
1208
|
+
ensureRuleCandidatesTable(db);
|
|
1209
|
+
db.prepare(`
|
|
1210
|
+
UPDATE rule_candidates
|
|
1211
|
+
SET status = ?, destination = COALESCE(?, destination), updated_at = datetime('now')
|
|
1212
|
+
WHERE prompt_hash = ?
|
|
1213
|
+
`).run(status, destination ?? null, promptHash);
|
|
1214
|
+
}
|
|
1215
|
+
var init_rule_candidate_store = __esm({
|
|
1216
|
+
"src/rule-candidate-store.ts"() {
|
|
1217
|
+
"use strict";
|
|
1218
|
+
}
|
|
1219
|
+
});
|
|
1220
|
+
|
|
1085
1221
|
// src/hooks/lib/hook-failure-signal.ts
|
|
1086
1222
|
import { appendFileSync, mkdirSync as mkdirSync3, existsSync as existsSync4 } from "fs";
|
|
1087
1223
|
import { join as join2, dirname as dirname3 } from "path";
|
|
@@ -1650,9 +1786,6 @@ __export(memory_db_exports, {
|
|
|
1650
1786
|
createSession: () => createSession,
|
|
1651
1787
|
deduplicateFailedAttempt: () => deduplicateFailedAttempt,
|
|
1652
1788
|
dequeuePendingSync: () => dequeuePendingSync,
|
|
1653
|
-
drainRulePromotionEvents: () => drainRulePromotionEvents,
|
|
1654
|
-
drainTeamPromotions: () => drainTeamPromotions,
|
|
1655
|
-
drainTeamRevocations: () => drainTeamRevocations,
|
|
1656
1789
|
embedMissingObservations: () => embedMissingObservations,
|
|
1657
1790
|
endSession: () => endSession,
|
|
1658
1791
|
enqueueRulePromotionEvent: () => enqueueRulePromotionEvent,
|
|
@@ -2559,6 +2692,8 @@ function initMemorySchema(db) {
|
|
|
2559
2692
|
`);
|
|
2560
2693
|
migrateObservationEmbeddingChunks(db);
|
|
2561
2694
|
migrateMemoryFilesFor4B(db);
|
|
2695
|
+
ensureRuleCandidatesTable(db);
|
|
2696
|
+
migrateRuleDelivery(db);
|
|
2562
2697
|
}
|
|
2563
2698
|
function enqueueSyncPayload(db, payload) {
|
|
2564
2699
|
db.prepare("INSERT INTO pending_sync (payload) VALUES (?)").run(payload);
|
|
@@ -2633,38 +2768,12 @@ function enqueueTeamPromotion(db, promo) {
|
|
|
2633
2768
|
promo.review_attestation !== void 0 ? JSON.stringify(promo.review_attestation) : null
|
|
2634
2769
|
);
|
|
2635
2770
|
}
|
|
2636
|
-
function drainTeamPromotions(db) {
|
|
2637
|
-
const rows = db.prepare(`
|
|
2638
|
-
SELECT prompt_hash, destination, draft_text, score, signals_json, content_hash, hardened, review_attestation_json
|
|
2639
|
-
FROM team_promotion_outbound ORDER BY created_at ASC LIMIT 1000
|
|
2640
|
-
`).all();
|
|
2641
|
-
if (rows.length === 0) return [];
|
|
2642
|
-
db.prepare("DELETE FROM team_promotion_outbound").run();
|
|
2643
|
-
return rows.map((r2) => ({
|
|
2644
|
-
prompt_hash: r2.prompt_hash,
|
|
2645
|
-
destination: r2.destination,
|
|
2646
|
-
draft_text: r2.draft_text,
|
|
2647
|
-
score: r2.score ?? void 0,
|
|
2648
|
-
signals: safeJsonArray(r2.signals_json),
|
|
2649
|
-
content_hash: r2.content_hash,
|
|
2650
|
-
hardened: r2.hardened === 1,
|
|
2651
|
-
review_attestation: r2.review_attestation_json ? safeJsonParse(r2.review_attestation_json) : void 0
|
|
2652
|
-
}));
|
|
2653
|
-
}
|
|
2654
2771
|
function enqueueTeamRevocation(db, promptHash) {
|
|
2655
2772
|
db.prepare(`
|
|
2656
2773
|
INSERT OR REPLACE INTO team_revocation_outbound (prompt_hash, created_at)
|
|
2657
2774
|
VALUES (?, datetime('now'))
|
|
2658
2775
|
`).run(promptHash);
|
|
2659
2776
|
}
|
|
2660
|
-
function drainTeamRevocations(db) {
|
|
2661
|
-
const rows = db.prepare(
|
|
2662
|
-
`SELECT prompt_hash FROM team_revocation_outbound ORDER BY created_at ASC LIMIT 1000`
|
|
2663
|
-
).all();
|
|
2664
|
-
if (rows.length === 0) return [];
|
|
2665
|
-
db.prepare("DELETE FROM team_revocation_outbound").run();
|
|
2666
|
-
return rows.map((r2) => r2.prompt_hash);
|
|
2667
|
-
}
|
|
2668
2777
|
function getRecurrenceCountForPromptHash(db, promptHash) {
|
|
2669
2778
|
try {
|
|
2670
2779
|
const row = db.prepare(`
|
|
@@ -2692,30 +2801,14 @@ function enqueueRulePromotionEvent(db, ev) {
|
|
|
2692
2801
|
JSON.stringify(ev.metadata ?? {}),
|
|
2693
2802
|
ev.created_at
|
|
2694
2803
|
);
|
|
2695
|
-
db
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
} catch {
|
|
2804
|
+
capTelemetry(db, FUNNEL_EVENT_OUTBOX_CAP);
|
|
2805
|
+
} catch (err) {
|
|
2806
|
+
process.stderr.write(
|
|
2807
|
+
`[massu] WARNING: failed to record promotion-funnel event (${ev.event_type} / ${ev.prompt_hash}): ${err instanceof Error ? err.message : String(err)}
|
|
2808
|
+
`
|
|
2809
|
+
);
|
|
2702
2810
|
}
|
|
2703
2811
|
}
|
|
2704
|
-
function drainRulePromotionEvents(db) {
|
|
2705
|
-
const rows = db.prepare(`
|
|
2706
|
-
SELECT id, prompt_hash, event_type, metadata_json, created_at
|
|
2707
|
-
FROM rule_promotion_events_outbound ORDER BY id ASC LIMIT ?
|
|
2708
|
-
`).all(FUNNEL_EVENT_DRAIN_LIMIT);
|
|
2709
|
-
if (rows.length === 0) return [];
|
|
2710
|
-
const maxId = rows[rows.length - 1].id;
|
|
2711
|
-
db.prepare("DELETE FROM rule_promotion_events_outbound WHERE id <= ?").run(maxId);
|
|
2712
|
-
return rows.map((r2) => ({
|
|
2713
|
-
prompt_hash: r2.prompt_hash,
|
|
2714
|
-
event_type: r2.event_type,
|
|
2715
|
-
created_at: r2.created_at,
|
|
2716
|
-
metadata: safeJsonParse(r2.metadata_json) ?? {}
|
|
2717
|
-
}));
|
|
2718
|
-
}
|
|
2719
2812
|
function recordTelemetry(db, eventType, data) {
|
|
2720
2813
|
try {
|
|
2721
2814
|
db.prepare(`
|
|
@@ -2725,28 +2818,55 @@ function recordTelemetry(db, eventType, data) {
|
|
|
2725
2818
|
} catch {
|
|
2726
2819
|
}
|
|
2727
2820
|
}
|
|
2728
|
-
function
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2821
|
+
function rescueLearningFromStalePayloads(db, stale) {
|
|
2822
|
+
let rescued = 0;
|
|
2823
|
+
for (const item of stale) {
|
|
2824
|
+
let payload;
|
|
2825
|
+
try {
|
|
2826
|
+
payload = JSON.parse(item.payload);
|
|
2827
|
+
} catch {
|
|
2828
|
+
continue;
|
|
2829
|
+
}
|
|
2830
|
+
try {
|
|
2831
|
+
const promotions = Array.isArray(payload.rule_promotions) ? payload.rule_promotions : [];
|
|
2832
|
+
for (const p19 of promotions) {
|
|
2833
|
+
enqueueTeamPromotion(db, p19);
|
|
2834
|
+
rescued++;
|
|
2835
|
+
}
|
|
2836
|
+
const revocations = Array.isArray(payload.rule_revocations) ? payload.rule_revocations : [];
|
|
2837
|
+
for (const r2 of revocations) {
|
|
2838
|
+
if (typeof r2?.prompt_hash === "string") {
|
|
2839
|
+
enqueueTeamRevocation(db, r2.prompt_hash);
|
|
2840
|
+
rescued++;
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
const events = Array.isArray(payload.rule_promotion_events) ? payload.rule_promotion_events : [];
|
|
2844
|
+
for (const e2 of events) {
|
|
2845
|
+
enqueueRulePromotionEvent(db, e2);
|
|
2846
|
+
}
|
|
2847
|
+
} catch (err) {
|
|
2848
|
+
process.stderr.write(
|
|
2849
|
+
`[massu] WARNING: failed to rescue learned rules from give-up payload ${item.id}: ${err instanceof Error ? err.message : String(err)}
|
|
2850
|
+
`
|
|
2851
|
+
);
|
|
2852
|
+
}
|
|
2741
2853
|
}
|
|
2854
|
+
return rescued;
|
|
2742
2855
|
}
|
|
2743
2856
|
function dequeuePendingSync(db, limit = 10) {
|
|
2744
2857
|
const stale = db.prepare(
|
|
2745
|
-
"SELECT id, retry_count, last_error FROM pending_sync WHERE retry_count >= 10 LIMIT 10000"
|
|
2858
|
+
"SELECT id, retry_count, last_error, payload FROM pending_sync WHERE retry_count >= 10 LIMIT 10000"
|
|
2746
2859
|
).all();
|
|
2747
2860
|
if (stale.length > 0) {
|
|
2861
|
+
const rescued = rescueLearningFromStalePayloads(db, stale);
|
|
2748
2862
|
const ids = stale.map((s) => s.id);
|
|
2749
2863
|
db.prepare(`DELETE FROM pending_sync WHERE id IN (${ids.map(() => "?").join(",")})`).run(...ids);
|
|
2864
|
+
if (rescued > 0) {
|
|
2865
|
+
process.stderr.write(
|
|
2866
|
+
`[massu] RESCUED ${rescued} learned rule(s) from ${stale.length} give-up payload(s) \u2014 they were re-queued for delivery, NOT discarded.
|
|
2867
|
+
`
|
|
2868
|
+
);
|
|
2869
|
+
}
|
|
2750
2870
|
const lastErrors = [...new Set(stale.map((s) => s.last_error).filter(Boolean))];
|
|
2751
2871
|
process.stderr.write(
|
|
2752
2872
|
`[massu] WARNING: ${stale.length} cloud-sync queue item(s) discarded after 10+ retries. Likely cause: invalid API key or unreachable endpoint. Recent errors: ${lastErrors.slice(0, 3).join("; ") || "(none recorded)"}
|
|
@@ -3438,19 +3558,20 @@ function scoreFailureClasses(db, matchText, filePath, promptContext, weights) {
|
|
|
3438
3558
|
}
|
|
3439
3559
|
return bestMatch;
|
|
3440
3560
|
}
|
|
3441
|
-
var MEMORY_SCHEMA_VERSION, TOOL_COST_EVENTS_RETENTION_DAYS, MAX_DRAFT_TEXT_LEN,
|
|
3561
|
+
var MEMORY_SCHEMA_VERSION, TOOL_COST_EVENTS_RETENTION_DAYS, MAX_DRAFT_TEXT_LEN, FUNNEL_EVENT_OUTBOX_CAP, _temporalColCache, CONSOLIDATION_LESSON_EVIDENCE, MEMORY_FILE_TITLE_PREFIX, MEMORY_FILE_TITLE_LIKE, USAGE_COUNTER_ARMED_KEY;
|
|
3442
3562
|
var init_memory_db = __esm({
|
|
3443
3563
|
"src/memory-db.ts"() {
|
|
3444
3564
|
"use strict";
|
|
3445
3565
|
init_config();
|
|
3446
3566
|
init_memory_vector();
|
|
3447
3567
|
init_db_backup();
|
|
3568
|
+
init_rule_delivery();
|
|
3569
|
+
init_rule_candidate_store();
|
|
3448
3570
|
init_hook_failure_signal();
|
|
3449
3571
|
init_memory_embed_sweep();
|
|
3450
3572
|
MEMORY_SCHEMA_VERSION = 1;
|
|
3451
3573
|
TOOL_COST_EVENTS_RETENTION_DAYS = 90;
|
|
3452
3574
|
MAX_DRAFT_TEXT_LEN = 16384;
|
|
3453
|
-
FUNNEL_EVENT_DRAIN_LIMIT = 5e3;
|
|
3454
3575
|
FUNNEL_EVENT_OUTBOX_CAP = 2e4;
|
|
3455
3576
|
_temporalColCache = /* @__PURE__ */ new WeakMap();
|
|
3456
3577
|
CONSOLIDATION_LESSON_EVIDENCE = "consolidation:session-summary";
|
|
@@ -9159,21 +9280,21 @@ var require_pattern = __commonJS({
|
|
|
9159
9280
|
exports.removeDuplicateSlashes = removeDuplicateSlashes;
|
|
9160
9281
|
function partitionAbsoluteAndRelative(patterns) {
|
|
9161
9282
|
const absolute = [];
|
|
9162
|
-
const
|
|
9283
|
+
const relative15 = [];
|
|
9163
9284
|
for (const pattern of patterns) {
|
|
9164
|
-
if (
|
|
9285
|
+
if (isAbsolute5(pattern)) {
|
|
9165
9286
|
absolute.push(pattern);
|
|
9166
9287
|
} else {
|
|
9167
|
-
|
|
9288
|
+
relative15.push(pattern);
|
|
9168
9289
|
}
|
|
9169
9290
|
}
|
|
9170
|
-
return [absolute,
|
|
9291
|
+
return [absolute, relative15];
|
|
9171
9292
|
}
|
|
9172
9293
|
exports.partitionAbsoluteAndRelative = partitionAbsoluteAndRelative;
|
|
9173
|
-
function
|
|
9294
|
+
function isAbsolute5(pattern) {
|
|
9174
9295
|
return path.isAbsolute(pattern);
|
|
9175
9296
|
}
|
|
9176
|
-
exports.isAbsolute =
|
|
9297
|
+
exports.isAbsolute = isAbsolute5;
|
|
9177
9298
|
}
|
|
9178
9299
|
});
|
|
9179
9300
|
|
|
@@ -17975,6 +18096,28 @@ function autoLearningUpgradeMessage(currentTier) {
|
|
|
17975
18096
|
function entitledForTeamSharedPromotion(tier) {
|
|
17976
18097
|
return tierLevel(tier) >= tierLevel(TEAM_SHARED_PROMOTION_MIN_TIER);
|
|
17977
18098
|
}
|
|
18099
|
+
function teamSharedPromotionUpgradeMessage(currentTier) {
|
|
18100
|
+
return `Team-shared rule promotion (your team learns as one \u2014 a promoted rule propagates to your org as a reviewable proposal) is a Team feature. Your tier: ${currentTier.toUpperCase()}. Upgrade at https://massu.ai/pricing`;
|
|
18101
|
+
}
|
|
18102
|
+
async function assertAutoLearningEntitled() {
|
|
18103
|
+
try {
|
|
18104
|
+
const currentTier = await getCurrentTier();
|
|
18105
|
+
if (entitledForAutoLearning(currentTier)) {
|
|
18106
|
+
return { entitled: true, currentTier };
|
|
18107
|
+
}
|
|
18108
|
+
return {
|
|
18109
|
+
entitled: false,
|
|
18110
|
+
currentTier,
|
|
18111
|
+
message: autoLearningUpgradeMessage(currentTier)
|
|
18112
|
+
};
|
|
18113
|
+
} catch {
|
|
18114
|
+
return {
|
|
18115
|
+
entitled: false,
|
|
18116
|
+
currentTier: "free",
|
|
18117
|
+
message: autoLearningUpgradeMessage("free")
|
|
18118
|
+
};
|
|
18119
|
+
}
|
|
18120
|
+
}
|
|
17978
18121
|
var AUTO_LEARNING_MIN_TIER, TEAM_SHARED_PROMOTION_MIN_TIER;
|
|
17979
18122
|
var init_auto_learning_entitlement = __esm({
|
|
17980
18123
|
"src/auto-learning-entitlement.ts"() {
|
|
@@ -19340,6 +19483,12 @@ function assertOutsideRegionUnchanged(pre, post) {
|
|
|
19340
19483
|
);
|
|
19341
19484
|
}
|
|
19342
19485
|
}
|
|
19486
|
+
function readRegionLines(content) {
|
|
19487
|
+
const state = parseRegion(content);
|
|
19488
|
+
if (state.kind !== "valid") return [];
|
|
19489
|
+
const inner = content.slice(state.beginIdx + BEGIN_SENTINEL.length, state.endIdx);
|
|
19490
|
+
return inner.split("\n").filter((l2) => l2.trim().length > 0);
|
|
19491
|
+
}
|
|
19343
19492
|
function memoryIndexLockPath(memoryDir) {
|
|
19344
19493
|
return join16(dirname15(memoryDir), ".massu-memory-index.lock");
|
|
19345
19494
|
}
|
|
@@ -19364,7 +19513,7 @@ function writeMemoryIndex(indexPath, pre, post) {
|
|
|
19364
19513
|
assertOutsideRegionUnchanged(pre, post);
|
|
19365
19514
|
atomicWriteFileSync(indexPath, post);
|
|
19366
19515
|
}
|
|
19367
|
-
var BEGIN_SENTINEL, END_SENTINEL, RegionRefused, LOCK_BLOCK_MS, LOCK_STALE_MS, MemoryIndexLockBusy;
|
|
19516
|
+
var BEGIN_SENTINEL, END_SENTINEL, DEFAULT_MAX_REGION_LINES, RegionRefused, MemoryIndexMissing, LOCK_BLOCK_MS, LOCK_STALE_MS, MemoryIndexLockBusy;
|
|
19368
19517
|
var init_memory_index_region = __esm({
|
|
19369
19518
|
"src/memory-index-region.ts"() {
|
|
19370
19519
|
"use strict";
|
|
@@ -19372,6 +19521,7 @@ var init_memory_index_region = __esm({
|
|
|
19372
19521
|
init_safe_write();
|
|
19373
19522
|
BEGIN_SENTINEL = "<!-- massu:learned:begin -->";
|
|
19374
19523
|
END_SENTINEL = "<!-- massu:learned:end -->";
|
|
19524
|
+
DEFAULT_MAX_REGION_LINES = 50;
|
|
19375
19525
|
RegionRefused = class extends Error {
|
|
19376
19526
|
constructor(message, reason) {
|
|
19377
19527
|
super(message);
|
|
@@ -19380,6 +19530,12 @@ var init_memory_index_region = __esm({
|
|
|
19380
19530
|
}
|
|
19381
19531
|
reason;
|
|
19382
19532
|
};
|
|
19533
|
+
MemoryIndexMissing = class extends Error {
|
|
19534
|
+
constructor(message) {
|
|
19535
|
+
super(message);
|
|
19536
|
+
this.name = "MemoryIndexMissing";
|
|
19537
|
+
}
|
|
19538
|
+
};
|
|
19383
19539
|
LOCK_BLOCK_MS = 2e3;
|
|
19384
19540
|
LOCK_STALE_MS = 3e4;
|
|
19385
19541
|
MemoryIndexLockBusy = class extends Error {
|
|
@@ -20064,6 +20220,1149 @@ var init_memory_render_cli_entry = __esm({
|
|
|
20064
20220
|
}
|
|
20065
20221
|
});
|
|
20066
20222
|
|
|
20223
|
+
// src/lib/corrections-md.ts
|
|
20224
|
+
function formatCorrectionEntry(e2) {
|
|
20225
|
+
const one = (s) => s.replace(/\s*\n+\s*/g, " ").trim();
|
|
20226
|
+
const lines = [`### ${e2.date} - ${one(e2.title)}`];
|
|
20227
|
+
if (e2.wrong) lines.push(`- **Wrong**: ${one(e2.wrong)}`);
|
|
20228
|
+
if (e2.correction) lines.push(`- **Correction**: ${one(e2.correction)}`);
|
|
20229
|
+
lines.push(`- **Rule**: ${one(e2.rule)}`);
|
|
20230
|
+
for (const [k3, v3] of Object.entries(e2.extra ?? {})) {
|
|
20231
|
+
lines.push(`- ${k3}: ${one(String(v3))}`);
|
|
20232
|
+
}
|
|
20233
|
+
return `
|
|
20234
|
+
${lines.join("\n")}
|
|
20235
|
+
`;
|
|
20236
|
+
}
|
|
20237
|
+
var init_corrections_md = __esm({
|
|
20238
|
+
"src/lib/corrections-md.ts"() {
|
|
20239
|
+
"use strict";
|
|
20240
|
+
}
|
|
20241
|
+
});
|
|
20242
|
+
|
|
20243
|
+
// src/audit-trail.ts
|
|
20244
|
+
function p2(baseName) {
|
|
20245
|
+
return `${getConfig().toolPrefix}_${baseName}`;
|
|
20246
|
+
}
|
|
20247
|
+
function getAuditFormats() {
|
|
20248
|
+
return getConfig().governance?.audit?.formats ?? DEFAULT_FORMATS;
|
|
20249
|
+
}
|
|
20250
|
+
function logAuditEntry(db, entry) {
|
|
20251
|
+
db.prepare(`
|
|
20252
|
+
INSERT INTO audit_log (session_id, event_type, actor, model_id, file_path, change_type, rules_in_effect, approval_status, evidence, metadata)
|
|
20253
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
20254
|
+
`).run(
|
|
20255
|
+
entry.sessionId ?? null,
|
|
20256
|
+
entry.eventType,
|
|
20257
|
+
entry.actor,
|
|
20258
|
+
entry.modelId ?? null,
|
|
20259
|
+
entry.filePath ?? null,
|
|
20260
|
+
entry.changeType ?? null,
|
|
20261
|
+
entry.rulesInEffect ?? null,
|
|
20262
|
+
entry.approvalStatus ?? null,
|
|
20263
|
+
entry.evidence ?? null,
|
|
20264
|
+
entry.metadata ? JSON.stringify(entry.metadata) : null
|
|
20265
|
+
);
|
|
20266
|
+
}
|
|
20267
|
+
function queryAuditLog(db, options) {
|
|
20268
|
+
let sql = "SELECT * FROM audit_log WHERE 1=1";
|
|
20269
|
+
const params = [];
|
|
20270
|
+
if (options.eventType) {
|
|
20271
|
+
sql += " AND event_type = ?";
|
|
20272
|
+
params.push(options.eventType);
|
|
20273
|
+
}
|
|
20274
|
+
if (options.actor) {
|
|
20275
|
+
sql += " AND actor = ?";
|
|
20276
|
+
params.push(options.actor);
|
|
20277
|
+
}
|
|
20278
|
+
if (options.days) {
|
|
20279
|
+
sql += " AND timestamp >= datetime('now', ?)";
|
|
20280
|
+
params.push(`-${options.days} days`);
|
|
20281
|
+
}
|
|
20282
|
+
if (options.filePath) {
|
|
20283
|
+
sql += " AND file_path = ?";
|
|
20284
|
+
params.push(options.filePath);
|
|
20285
|
+
}
|
|
20286
|
+
if (options.changeType) {
|
|
20287
|
+
sql += " AND change_type = ?";
|
|
20288
|
+
params.push(options.changeType);
|
|
20289
|
+
}
|
|
20290
|
+
sql += " ORDER BY timestamp DESC";
|
|
20291
|
+
if (options.limit) {
|
|
20292
|
+
sql += " LIMIT ?";
|
|
20293
|
+
params.push(options.limit);
|
|
20294
|
+
}
|
|
20295
|
+
return db.prepare(sql).all(...params);
|
|
20296
|
+
}
|
|
20297
|
+
function getFileChain(db, filePath) {
|
|
20298
|
+
return db.prepare(
|
|
20299
|
+
"SELECT * FROM audit_log WHERE file_path = ? ORDER BY timestamp ASC LIMIT 10000"
|
|
20300
|
+
).all(filePath);
|
|
20301
|
+
}
|
|
20302
|
+
function getAuditToolDefinitions() {
|
|
20303
|
+
return [
|
|
20304
|
+
{
|
|
20305
|
+
name: p2("audit_log"),
|
|
20306
|
+
description: "Query the audit log. Filter by event type, actor, file, or time range.",
|
|
20307
|
+
inputSchema: {
|
|
20308
|
+
type: "object",
|
|
20309
|
+
properties: {
|
|
20310
|
+
event_type: { type: "string", description: "Filter by event type: code_change, rule_enforced, approval, review, commit, compaction" },
|
|
20311
|
+
actor: { type: "string", description: "Filter by actor: ai, human, hook, agent" },
|
|
20312
|
+
file_path: { type: "string", description: "Filter by file path" },
|
|
20313
|
+
change_type: { type: "string", description: "Filter by change type: create, edit, delete" },
|
|
20314
|
+
days: { type: "number", description: "Days to look back (default: 30)" },
|
|
20315
|
+
limit: { type: "number", description: "Max results (default: 50)" }
|
|
20316
|
+
},
|
|
20317
|
+
required: []
|
|
20318
|
+
}
|
|
20319
|
+
},
|
|
20320
|
+
{
|
|
20321
|
+
name: p2("audit_report"),
|
|
20322
|
+
description: "Generate an audit report in a specified format (summary, detailed, soc2).",
|
|
20323
|
+
inputSchema: {
|
|
20324
|
+
type: "object",
|
|
20325
|
+
properties: {
|
|
20326
|
+
format: { type: "string", description: "Report format: summary, detailed, soc2 (default: summary)" },
|
|
20327
|
+
days: { type: "number", description: "Days to cover (default: 30)" }
|
|
20328
|
+
},
|
|
20329
|
+
required: []
|
|
20330
|
+
}
|
|
20331
|
+
},
|
|
20332
|
+
{
|
|
20333
|
+
name: p2("audit_chain"),
|
|
20334
|
+
description: "Get the complete audit trail for a specific file. Shows all changes, reviews, and decisions chronologically.",
|
|
20335
|
+
inputSchema: {
|
|
20336
|
+
type: "object",
|
|
20337
|
+
properties: {
|
|
20338
|
+
file: { type: "string", description: "File path to trace" }
|
|
20339
|
+
},
|
|
20340
|
+
required: ["file"]
|
|
20341
|
+
}
|
|
20342
|
+
}
|
|
20343
|
+
];
|
|
20344
|
+
}
|
|
20345
|
+
function isAuditTool(name) {
|
|
20346
|
+
const pfx = getConfig().toolPrefix + "_";
|
|
20347
|
+
const baseName = name.startsWith(pfx) ? name.slice(pfx.length) : name;
|
|
20348
|
+
return AUDIT_BASE_NAMES.has(baseName);
|
|
20349
|
+
}
|
|
20350
|
+
function handleAuditToolCall(name, args2, memoryDb) {
|
|
20351
|
+
try {
|
|
20352
|
+
const pfx = getConfig().toolPrefix + "_";
|
|
20353
|
+
const baseName = name.startsWith(pfx) ? name.slice(pfx.length) : name;
|
|
20354
|
+
switch (baseName) {
|
|
20355
|
+
case "audit_log":
|
|
20356
|
+
return handleAuditLog(args2, memoryDb);
|
|
20357
|
+
case "audit_report":
|
|
20358
|
+
return handleAuditReport(args2, memoryDb);
|
|
20359
|
+
case "audit_chain":
|
|
20360
|
+
return handleAuditChain(args2, memoryDb);
|
|
20361
|
+
default:
|
|
20362
|
+
return text(`Unknown audit tool: ${name}`);
|
|
20363
|
+
}
|
|
20364
|
+
} catch (error) {
|
|
20365
|
+
return text(`Error in ${name}: ${error instanceof Error ? error.message : String(error)}
|
|
20366
|
+
|
|
20367
|
+
Usage: ${p2("audit_log")} { severity: "critical" }, ${p2("audit_report")} { format: "soc2" }`);
|
|
20368
|
+
}
|
|
20369
|
+
}
|
|
20370
|
+
function handleAuditLog(args2, db) {
|
|
20371
|
+
const entries = queryAuditLog(db, {
|
|
20372
|
+
eventType: args2.event_type,
|
|
20373
|
+
actor: args2.actor,
|
|
20374
|
+
filePath: args2.file_path,
|
|
20375
|
+
changeType: args2.change_type,
|
|
20376
|
+
days: args2.days ?? 30,
|
|
20377
|
+
limit: args2.limit ?? 50
|
|
20378
|
+
});
|
|
20379
|
+
if (entries.length === 0) {
|
|
20380
|
+
return text(`No audit log entries found matching the filters. Audit entries are recorded automatically during code changes, rule checks, and commits. Try broadening your search: ${p2("audit_log")} { days: 90 } for a longer time range, or ${p2("audit_log")} {} with no filters.`);
|
|
20381
|
+
}
|
|
20382
|
+
const lines = [
|
|
20383
|
+
`## Audit Log (${entries.length} entries)`,
|
|
20384
|
+
"",
|
|
20385
|
+
"| Timestamp | Event | Actor | Change | File | Evidence |",
|
|
20386
|
+
"|-----------|-------|-------|--------|------|----------|"
|
|
20387
|
+
];
|
|
20388
|
+
for (const entry of entries) {
|
|
20389
|
+
const file = entry.file_path ? entry.file_path.split("/").pop() : "-";
|
|
20390
|
+
const evidence = entry.evidence ? entry.evidence.slice(0, 50) : "-";
|
|
20391
|
+
lines.push(
|
|
20392
|
+
`| ${entry.timestamp.slice(0, 16)} | ${entry.event_type} | ${entry.actor} | ${entry.change_type ?? "-"} | ${file} | ${evidence} |`
|
|
20393
|
+
);
|
|
20394
|
+
}
|
|
20395
|
+
return text(lines.join("\n"));
|
|
20396
|
+
}
|
|
20397
|
+
function handleAuditReport(args2, db) {
|
|
20398
|
+
const format = args2.format ?? "summary";
|
|
20399
|
+
const days = args2.days ?? 30;
|
|
20400
|
+
const availableFormats = getAuditFormats();
|
|
20401
|
+
if (!availableFormats.includes(format)) {
|
|
20402
|
+
return text(`Unknown report format: "${format}". Available formats: ${availableFormats.join(", ")}. Example: ${p2("audit_report")} { format: "soc2", days: 30 }. Configure additional formats via the governance.audit.formats config key.`);
|
|
20403
|
+
}
|
|
20404
|
+
const entries = queryAuditLog(db, { days });
|
|
20405
|
+
if (entries.length === 0) {
|
|
20406
|
+
return text(`No audit entries found in the last ${days} days. Audit entries are recorded automatically during code changes, reviews, and commits. Try: ${p2("audit_log")} { days: 90 } for a longer time range.`);
|
|
20407
|
+
}
|
|
20408
|
+
const byEventType = {};
|
|
20409
|
+
const byActor = {};
|
|
20410
|
+
for (const e2 of entries) {
|
|
20411
|
+
const et = e2.event_type;
|
|
20412
|
+
byEventType[et] = (byEventType[et] ?? 0) + 1;
|
|
20413
|
+
const actor = e2.actor;
|
|
20414
|
+
byActor[actor] = (byActor[actor] ?? 0) + 1;
|
|
20415
|
+
}
|
|
20416
|
+
if (format === "soc2") {
|
|
20417
|
+
return generateSoc2Report(entries, byEventType, byActor, days);
|
|
20418
|
+
}
|
|
20419
|
+
if (format === "detailed") {
|
|
20420
|
+
return generateDetailedReport(entries, byEventType, byActor, days);
|
|
20421
|
+
}
|
|
20422
|
+
const lines = [
|
|
20423
|
+
`## Audit Summary Report (${days} days)`,
|
|
20424
|
+
`Generated: ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)}`,
|
|
20425
|
+
"",
|
|
20426
|
+
"### By Event Type",
|
|
20427
|
+
`| Event Type | Count |`,
|
|
20428
|
+
`|------------|-------|`
|
|
20429
|
+
];
|
|
20430
|
+
for (const [et, count] of Object.entries(byEventType).sort((a2, b2) => b2[1] - a2[1])) {
|
|
20431
|
+
lines.push(`| ${et} | ${count} |`);
|
|
20432
|
+
}
|
|
20433
|
+
lines.push(`| **Total** | **${entries.length}** |`);
|
|
20434
|
+
lines.push("");
|
|
20435
|
+
lines.push("### By Actor");
|
|
20436
|
+
lines.push(`| Actor | Count |`);
|
|
20437
|
+
lines.push(`|-------|-------|`);
|
|
20438
|
+
for (const [actor, count] of Object.entries(byActor).sort((a2, b2) => b2[1] - a2[1])) {
|
|
20439
|
+
lines.push(`| ${actor} | ${count} |`);
|
|
20440
|
+
}
|
|
20441
|
+
return text(lines.join("\n"));
|
|
20442
|
+
}
|
|
20443
|
+
function generateSoc2Report(entries, byEventType, byActor, days) {
|
|
20444
|
+
const lines = [
|
|
20445
|
+
`# SOC 2 Compliance Report`,
|
|
20446
|
+
`Period: Last ${days} days`,
|
|
20447
|
+
`Generated: ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
20448
|
+
"",
|
|
20449
|
+
"## 1. Control Environment",
|
|
20450
|
+
`Total audit events: ${entries.length}`,
|
|
20451
|
+
`Human-initiated: ${byActor["human"] ?? 0}`,
|
|
20452
|
+
`AI-initiated: ${byActor["ai"] ?? 0}`,
|
|
20453
|
+
`Hook-triggered: ${byActor["hook"] ?? 0}`,
|
|
20454
|
+
"",
|
|
20455
|
+
"## 2. Change Management",
|
|
20456
|
+
`Code changes logged: ${byEventType["code_change"] ?? 0}`,
|
|
20457
|
+
`Rule enforcements: ${byEventType["rule_enforced"] ?? 0}`,
|
|
20458
|
+
`Approvals recorded: ${byEventType["approval"] ?? 0}`,
|
|
20459
|
+
`Reviews: ${byEventType["review"] ?? 0}`,
|
|
20460
|
+
`Commits: ${byEventType["commit"] ?? 0}`,
|
|
20461
|
+
"",
|
|
20462
|
+
"## 3. Approval Status"
|
|
20463
|
+
];
|
|
20464
|
+
const pendingApprovals = entries.filter((e2) => e2.approval_status === "pending");
|
|
20465
|
+
const deniedApprovals = entries.filter((e2) => e2.approval_status === "denied");
|
|
20466
|
+
lines.push(`Pending approvals: ${pendingApprovals.length}`);
|
|
20467
|
+
lines.push(`Denied approvals: ${deniedApprovals.length}`);
|
|
20468
|
+
lines.push("");
|
|
20469
|
+
lines.push("## 4. Events Requiring Review");
|
|
20470
|
+
const reviewEntries = entries.filter((e2) => e2.approval_status === "denied" || e2.event_type === "rule_enforced");
|
|
20471
|
+
if (reviewEntries.length > 0) {
|
|
20472
|
+
for (const e2 of reviewEntries.slice(0, 20)) {
|
|
20473
|
+
lines.push(`- [${e2.timestamp.slice(0, 16)}] ${e2.event_type}: ${(e2.evidence ?? "").slice(0, 100)}`);
|
|
20474
|
+
}
|
|
20475
|
+
} else {
|
|
20476
|
+
lines.push("No events requiring review in this period.");
|
|
20477
|
+
}
|
|
20478
|
+
return text(lines.join("\n"));
|
|
20479
|
+
}
|
|
20480
|
+
function generateDetailedReport(entries, byEventType, byActor, days) {
|
|
20481
|
+
const lines = [
|
|
20482
|
+
`## Detailed Audit Report (${days} days)`,
|
|
20483
|
+
`Total events: ${entries.length}`,
|
|
20484
|
+
"",
|
|
20485
|
+
"### Event Type Distribution"
|
|
20486
|
+
];
|
|
20487
|
+
for (const [et, count] of Object.entries(byEventType).sort((a2, b2) => b2[1] - a2[1])) {
|
|
20488
|
+
lines.push(`- ${et}: ${count}`);
|
|
20489
|
+
}
|
|
20490
|
+
lines.push("");
|
|
20491
|
+
lines.push("### Actor Distribution");
|
|
20492
|
+
for (const [actor, count] of Object.entries(byActor).sort((a2, b2) => b2[1] - a2[1])) {
|
|
20493
|
+
lines.push(`- ${actor}: ${count}`);
|
|
20494
|
+
}
|
|
20495
|
+
lines.push("");
|
|
20496
|
+
lines.push("### All Events");
|
|
20497
|
+
for (const e2 of entries.slice(0, 100)) {
|
|
20498
|
+
lines.push(`#### ${e2.timestamp.slice(0, 16)} [${e2.event_type}]`);
|
|
20499
|
+
lines.push(`Actor: ${e2.actor} | Change: ${e2.change_type ?? "-"} | Approval: ${e2.approval_status ?? "-"}`);
|
|
20500
|
+
if (e2.file_path) lines.push(`File: ${e2.file_path}`);
|
|
20501
|
+
if (e2.evidence) lines.push(`Evidence: ${e2.evidence}`);
|
|
20502
|
+
lines.push("");
|
|
20503
|
+
}
|
|
20504
|
+
if (entries.length > 100) {
|
|
20505
|
+
lines.push(`... and ${entries.length - 100} more entries`);
|
|
20506
|
+
}
|
|
20507
|
+
return text(lines.join("\n"));
|
|
20508
|
+
}
|
|
20509
|
+
function handleAuditChain(args2, db) {
|
|
20510
|
+
const filePath = args2.file;
|
|
20511
|
+
if (!filePath) return text(`Usage: ${p2("audit_chain")} { file: "src/path/to/file.ts" } - Get complete audit history for a file.`);
|
|
20512
|
+
const chain = getFileChain(db, filePath);
|
|
20513
|
+
if (chain.length === 0) {
|
|
20514
|
+
return text(`No audit entries found for "${filePath}". Audit entries are recorded automatically when files are modified, reviewed, or validated. Try: ${p2("audit_log")} {} to see all recent audit entries across all files.`);
|
|
20515
|
+
}
|
|
20516
|
+
const lines = [
|
|
20517
|
+
`## Audit Chain: ${filePath}`,
|
|
20518
|
+
`Total events: ${chain.length}`,
|
|
20519
|
+
""
|
|
20520
|
+
];
|
|
20521
|
+
for (const entry of chain) {
|
|
20522
|
+
lines.push(`### ${entry.timestamp.slice(0, 16)} [${entry.event_type}]`);
|
|
20523
|
+
lines.push(`**${entry.actor}** (${entry.change_type ?? "unknown"})`);
|
|
20524
|
+
if (entry.evidence) lines.push(entry.evidence);
|
|
20525
|
+
lines.push("");
|
|
20526
|
+
}
|
|
20527
|
+
return text(lines.join("\n"));
|
|
20528
|
+
}
|
|
20529
|
+
function text(content) {
|
|
20530
|
+
return { content: [{ type: "text", text: content }] };
|
|
20531
|
+
}
|
|
20532
|
+
var DEFAULT_FORMATS, AUDIT_BASE_NAMES;
|
|
20533
|
+
var init_audit_trail = __esm({
|
|
20534
|
+
"src/audit-trail.ts"() {
|
|
20535
|
+
"use strict";
|
|
20536
|
+
init_config();
|
|
20537
|
+
DEFAULT_FORMATS = ["summary", "detailed", "soc2"];
|
|
20538
|
+
AUDIT_BASE_NAMES = /* @__PURE__ */ new Set(["audit_log", "audit_report", "audit_chain"]);
|
|
20539
|
+
}
|
|
20540
|
+
});
|
|
20541
|
+
|
|
20542
|
+
// src/template-renderer.ts
|
|
20543
|
+
function renderTemplate2(template, vars) {
|
|
20544
|
+
return template.replace(INTERPOLATION_TOKEN, (_2, raw) => {
|
|
20545
|
+
const key = raw.trim();
|
|
20546
|
+
if (!IDENTIFIER_SHAPE.test(key)) {
|
|
20547
|
+
throw new TemplateAllowlistError(key, [...ALLOWED_VARS]);
|
|
20548
|
+
}
|
|
20549
|
+
if (!ALLOWED_VARS.has(key)) {
|
|
20550
|
+
throw new TemplateAllowlistError(key, [...ALLOWED_VARS]);
|
|
20551
|
+
}
|
|
20552
|
+
const v3 = vars[key];
|
|
20553
|
+
if (v3 === void 0) {
|
|
20554
|
+
throw new TemplateMissingValueError(key);
|
|
20555
|
+
}
|
|
20556
|
+
return String(v3);
|
|
20557
|
+
});
|
|
20558
|
+
}
|
|
20559
|
+
var ALLOWED_VARS, TemplateAllowlistError, TemplateMissingValueError, IDENTIFIER_SHAPE, INTERPOLATION_TOKEN;
|
|
20560
|
+
var init_template_renderer = __esm({
|
|
20561
|
+
"src/template-renderer.ts"() {
|
|
20562
|
+
"use strict";
|
|
20563
|
+
ALLOWED_VARS = /* @__PURE__ */ new Set([
|
|
20564
|
+
"date",
|
|
20565
|
+
"slug",
|
|
20566
|
+
"score",
|
|
20567
|
+
"signals_csv",
|
|
20568
|
+
"prompt_preview",
|
|
20569
|
+
"destination_name"
|
|
20570
|
+
]);
|
|
20571
|
+
TemplateAllowlistError = class extends Error {
|
|
20572
|
+
constructor(key, allowedVars) {
|
|
20573
|
+
super(`template variable "${key}" is not in the allowlist [${allowedVars.join(", ")}]`);
|
|
20574
|
+
this.key = key;
|
|
20575
|
+
this.allowedVars = allowedVars;
|
|
20576
|
+
this.name = "TemplateAllowlistError";
|
|
20577
|
+
}
|
|
20578
|
+
key;
|
|
20579
|
+
allowedVars;
|
|
20580
|
+
};
|
|
20581
|
+
TemplateMissingValueError = class extends Error {
|
|
20582
|
+
constructor(key) {
|
|
20583
|
+
super(`template variable "${key}" is allowed but no value was supplied`);
|
|
20584
|
+
this.key = key;
|
|
20585
|
+
this.name = "TemplateMissingValueError";
|
|
20586
|
+
}
|
|
20587
|
+
key;
|
|
20588
|
+
};
|
|
20589
|
+
IDENTIFIER_SHAPE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
20590
|
+
INTERPOLATION_TOKEN = /\$\{([^}]*)\}/g;
|
|
20591
|
+
}
|
|
20592
|
+
});
|
|
20593
|
+
|
|
20594
|
+
// src/rule-candidate-funnel.ts
|
|
20595
|
+
function recordApprovalFunnelEvent(db, promptHash, destination, score, entitledTeam) {
|
|
20596
|
+
if (!entitledTeam) return;
|
|
20597
|
+
enqueueRulePromotionEvent(db, {
|
|
20598
|
+
prompt_hash: promptHash,
|
|
20599
|
+
event_type: "approved",
|
|
20600
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
20601
|
+
metadata: { destination, score }
|
|
20602
|
+
});
|
|
20603
|
+
}
|
|
20604
|
+
function recordDismissalFunnelEvent(db, promptHash, score) {
|
|
20605
|
+
try {
|
|
20606
|
+
if (entitledForTeamSharedPromotion(getCachedTierReadOnly(db))) {
|
|
20607
|
+
enqueueRulePromotionEvent(db, {
|
|
20608
|
+
prompt_hash: promptHash,
|
|
20609
|
+
event_type: "dismissed",
|
|
20610
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
20611
|
+
metadata: { score }
|
|
20612
|
+
});
|
|
20613
|
+
}
|
|
20614
|
+
} catch {
|
|
20615
|
+
}
|
|
20616
|
+
}
|
|
20617
|
+
var init_rule_candidate_funnel = __esm({
|
|
20618
|
+
"src/rule-candidate-funnel.ts"() {
|
|
20619
|
+
"use strict";
|
|
20620
|
+
init_memory_db();
|
|
20621
|
+
init_license();
|
|
20622
|
+
init_auto_learning_entitlement();
|
|
20623
|
+
}
|
|
20624
|
+
});
|
|
20625
|
+
|
|
20626
|
+
// src/rule-candidate-hardened.ts
|
|
20627
|
+
function isHardenedShareableDestination(destination) {
|
|
20628
|
+
return TEAM_HARDENED_SHAREABLE_DESTINATIONS.includes(destination);
|
|
20629
|
+
}
|
|
20630
|
+
function roleRank(role) {
|
|
20631
|
+
switch (role) {
|
|
20632
|
+
case "owner":
|
|
20633
|
+
return 4;
|
|
20634
|
+
case "admin":
|
|
20635
|
+
return 3;
|
|
20636
|
+
case "developer":
|
|
20637
|
+
return 2;
|
|
20638
|
+
case "auditor":
|
|
20639
|
+
return 1;
|
|
20640
|
+
default:
|
|
20641
|
+
return 0;
|
|
20642
|
+
}
|
|
20643
|
+
}
|
|
20644
|
+
function validateGovernanceGate(policy, approvals, ctx) {
|
|
20645
|
+
if (policy.min_promoter_role != null && ctx?.promoterRole !== void 0) {
|
|
20646
|
+
if (roleRank(ctx.promoterRole) < roleRank(policy.min_promoter_role)) {
|
|
20647
|
+
return `promoter role '${ctx.promoterRole}' is below the org minimum '${policy.min_promoter_role}' \u2014 refusing to apply`;
|
|
20648
|
+
}
|
|
20649
|
+
}
|
|
20650
|
+
if (ctx?.destination !== void 0 && !policy.allowed_destinations.includes(ctx.destination)) {
|
|
20651
|
+
return `destination '${ctx.destination}' is not in the org's allowed destinations \u2014 refusing to apply`;
|
|
20652
|
+
}
|
|
20653
|
+
if (policy.require_hardened_review === true && (ctx?.destination === "pattern-scanner" || ctx?.destination === "custom-destination")) {
|
|
20654
|
+
if (!(ctx.hardened === true && ctx.hasAttestation === true)) {
|
|
20655
|
+
return "org policy requires hardened review for executable destinations \u2014 refusing to apply";
|
|
20656
|
+
}
|
|
20657
|
+
}
|
|
20658
|
+
const distinct = new Set(
|
|
20659
|
+
approvals.approver_user_ids.filter((id) => typeof id === "string" && id.length > 0 && id !== approvals.promoted_by)
|
|
20660
|
+
);
|
|
20661
|
+
if (distinct.size < policy.approvals_required) {
|
|
20662
|
+
return `needs ${policy.approvals_required} approval(s); ${distinct.size} recorded \u2014 refusing to apply`;
|
|
20663
|
+
}
|
|
20664
|
+
return null;
|
|
20665
|
+
}
|
|
20666
|
+
function validateHardenedApplyGate(prov) {
|
|
20667
|
+
if (prov.hardened !== true) {
|
|
20668
|
+
return "team-origin hardened-destination candidate missing hardened provenance flag \u2014 refusing to apply";
|
|
20669
|
+
}
|
|
20670
|
+
const att = prov.review_attestation;
|
|
20671
|
+
if (!att || typeof att !== "object") {
|
|
20672
|
+
return "team-origin hardened candidate missing review_attestation \u2014 refusing to apply";
|
|
20673
|
+
}
|
|
20674
|
+
if (typeof att.second_operator_id !== "string" || att.second_operator_id.length === 0) {
|
|
20675
|
+
return "team-origin hardened candidate review_attestation missing second_operator_id \u2014 refusing to apply";
|
|
20676
|
+
}
|
|
20677
|
+
const govErr = validateGovernanceGate(
|
|
20678
|
+
{ approvals_required: 1, allowed_destinations: [] },
|
|
20679
|
+
{ approver_user_ids: [att.second_operator_id], promoted_by: prov.promoted_by }
|
|
20680
|
+
);
|
|
20681
|
+
if (govErr) {
|
|
20682
|
+
return "team-origin hardened candidate second_operator_id equals the promoter \u2014 two-operator review not satisfied";
|
|
20683
|
+
}
|
|
20684
|
+
const ack = att.dry_run_ack;
|
|
20685
|
+
if (!ack || typeof ack !== "object" || ack.ack !== true || typeof ack.ran_at !== "string") {
|
|
20686
|
+
return "team-origin hardened candidate missing render-only dry_run_ack \u2014 refusing to apply";
|
|
20687
|
+
}
|
|
20688
|
+
return null;
|
|
20689
|
+
}
|
|
20690
|
+
var TEAM_HARDENED_SHAREABLE_DESTINATIONS;
|
|
20691
|
+
var init_rule_candidate_hardened = __esm({
|
|
20692
|
+
"src/rule-candidate-hardened.ts"() {
|
|
20693
|
+
"use strict";
|
|
20694
|
+
TEAM_HARDENED_SHAREABLE_DESTINATIONS = [
|
|
20695
|
+
"pattern-scanner",
|
|
20696
|
+
"custom-destination"
|
|
20697
|
+
];
|
|
20698
|
+
}
|
|
20699
|
+
});
|
|
20700
|
+
|
|
20701
|
+
// src/rule-candidate-applier.ts
|
|
20702
|
+
import { readFileSync as readFileSync26, writeFileSync as writeFileSync9, existsSync as existsSync32, unlinkSync as unlinkSync5, mkdirSync as mkdirSync14 } from "fs";
|
|
20703
|
+
import { join as join20, dirname as dirname17, isAbsolute as isAbsolute2 } from "path";
|
|
20704
|
+
import { homedir as homedir12 } from "os";
|
|
20705
|
+
import { createHash as createHash9 } from "crypto";
|
|
20706
|
+
function isRuleDestination(value) {
|
|
20707
|
+
return typeof value === "string" && ALL_RULE_DESTINATIONS.includes(value);
|
|
20708
|
+
}
|
|
20709
|
+
function isTeamShareableDestination(destination) {
|
|
20710
|
+
return TEAM_SHAREABLE_DESTINATIONS.includes(destination);
|
|
20711
|
+
}
|
|
20712
|
+
function resolveMemoryDir(projectRoot = getProjectRoot(), home = homedir12()) {
|
|
20713
|
+
return join20(home, ".claude", "projects", encodeMemoryDirName(projectRoot), "memory");
|
|
20714
|
+
}
|
|
20715
|
+
function resolveMemoryIndexPath(projectRoot = getProjectRoot(), home = homedir12()) {
|
|
20716
|
+
return join20(resolveMemoryDir(projectRoot, home), "MEMORY.md");
|
|
20717
|
+
}
|
|
20718
|
+
function deriveSlug2(prompt) {
|
|
20719
|
+
return deriveSlug(prompt);
|
|
20720
|
+
}
|
|
20721
|
+
function writeIndexLineInRegion(indexPath, line) {
|
|
20722
|
+
const pre = readMemoryIndex(indexPath);
|
|
20723
|
+
if (pre === void 0) {
|
|
20724
|
+
throw new MemoryIndexMissing("MEMORY.md does not exist \u2014 index line skipped (file still written)");
|
|
20725
|
+
}
|
|
20726
|
+
const existing = readRegionLines(pre);
|
|
20727
|
+
if (existing.includes(line)) return;
|
|
20728
|
+
const next = [...existing, line].slice(-DEFAULT_MAX_REGION_LINES);
|
|
20729
|
+
const post = renderRegion(pre, next);
|
|
20730
|
+
if (post !== pre) writeMemoryIndex(indexPath, pre, post);
|
|
20731
|
+
}
|
|
20732
|
+
function correctionsMdTargets(slug, projectRoot, home) {
|
|
20733
|
+
const dir = resolveMemoryDir(projectRoot, home);
|
|
20734
|
+
return {
|
|
20735
|
+
correctionsMd: join20(dir, "corrections.md"),
|
|
20736
|
+
feedbackMd: join20(dir, `feedback_${slug}.md`),
|
|
20737
|
+
memoryIndex: join20(dir, "MEMORY.md")
|
|
20738
|
+
};
|
|
20739
|
+
}
|
|
20740
|
+
function readCandidate(candidateId, projectRoot = getProjectRoot()) {
|
|
20741
|
+
if (!CANDIDATE_ID_PATTERN.test(candidateId)) {
|
|
20742
|
+
throw new InvalidCandidateIdError(candidateId);
|
|
20743
|
+
}
|
|
20744
|
+
const path = join20(projectRoot, ".massu", "rule-candidates", `${candidateId}.json`);
|
|
20745
|
+
if (!existsSync32(path)) throw new CandidateNotFoundError(path);
|
|
20746
|
+
const raw = readFileSync26(path, "utf-8");
|
|
20747
|
+
let parsed;
|
|
20748
|
+
try {
|
|
20749
|
+
parsed = JSON.parse(raw);
|
|
20750
|
+
} catch (err) {
|
|
20751
|
+
throw new CandidatePayloadValidationError(`JSON parse: ${err instanceof Error ? err.message : String(err)}`);
|
|
20752
|
+
}
|
|
20753
|
+
return validateCandidatePayload(parsed);
|
|
20754
|
+
}
|
|
20755
|
+
function validateCandidatePayload(raw) {
|
|
20756
|
+
if (!raw || typeof raw !== "object") {
|
|
20757
|
+
throw new CandidatePayloadValidationError("payload is not an object");
|
|
20758
|
+
}
|
|
20759
|
+
const r2 = raw;
|
|
20760
|
+
if (typeof r2.prompt !== "string" || r2.prompt.length === 0) {
|
|
20761
|
+
throw new CandidatePayloadValidationError("prompt must be a non-empty string");
|
|
20762
|
+
}
|
|
20763
|
+
if (typeof r2.prompt_hash !== "string" || !/^[a-f0-9]{16}$/.test(r2.prompt_hash)) {
|
|
20764
|
+
throw new CandidatePayloadValidationError("prompt_hash must be 16 hex chars");
|
|
20765
|
+
}
|
|
20766
|
+
if (typeof r2.score !== "number" || r2.score < -200 || r2.score > 200 || !Number.isFinite(r2.score)) {
|
|
20767
|
+
throw new CandidatePayloadValidationError("score must be a finite number in [-200, 200]");
|
|
20768
|
+
}
|
|
20769
|
+
if (!Array.isArray(r2.signals)) {
|
|
20770
|
+
throw new CandidatePayloadValidationError("signals must be an array");
|
|
20771
|
+
}
|
|
20772
|
+
for (const s of r2.signals) {
|
|
20773
|
+
if (!s || typeof s !== "object") throw new CandidatePayloadValidationError("signal entry not an object");
|
|
20774
|
+
const sig = s;
|
|
20775
|
+
if (typeof sig.name !== "string") throw new CandidatePayloadValidationError("signal.name must be a string");
|
|
20776
|
+
if (typeof sig.baseWeight !== "number") throw new CandidatePayloadValidationError("signal.baseWeight must be a number");
|
|
20777
|
+
if (typeof sig.applied !== "number") throw new CandidatePayloadValidationError("signal.applied must be a number");
|
|
20778
|
+
}
|
|
20779
|
+
if (!Array.isArray(r2.prior_turn_files)) {
|
|
20780
|
+
throw new CandidatePayloadValidationError("prior_turn_files must be an array");
|
|
20781
|
+
}
|
|
20782
|
+
if (typeof r2.timestamp !== "string") {
|
|
20783
|
+
throw new CandidatePayloadValidationError("timestamp must be a string");
|
|
20784
|
+
}
|
|
20785
|
+
if (typeof r2.session_id !== "string") {
|
|
20786
|
+
throw new CandidatePayloadValidationError("session_id must be a string");
|
|
20787
|
+
}
|
|
20788
|
+
if (r2.provenance !== void 0) {
|
|
20789
|
+
if (!r2.provenance || typeof r2.provenance !== "object") {
|
|
20790
|
+
throw new CandidatePayloadValidationError("provenance must be an object when present");
|
|
20791
|
+
}
|
|
20792
|
+
const p19 = r2.provenance;
|
|
20793
|
+
if (p19.origin !== "team" && p19.origin !== "pack") {
|
|
20794
|
+
throw new CandidatePayloadValidationError("provenance.origin must be 'team' or 'pack'");
|
|
20795
|
+
}
|
|
20796
|
+
if (typeof p19.org_id !== "string" || p19.org_id.length === 0) {
|
|
20797
|
+
throw new CandidatePayloadValidationError("provenance.org_id must be a non-empty string");
|
|
20798
|
+
}
|
|
20799
|
+
if (typeof p19.promoted_by !== "string" || p19.promoted_by.length === 0) {
|
|
20800
|
+
throw new CandidatePayloadValidationError("provenance.promoted_by must be a non-empty string");
|
|
20801
|
+
}
|
|
20802
|
+
if (typeof p19.promoted_at !== "string") {
|
|
20803
|
+
throw new CandidatePayloadValidationError("provenance.promoted_at must be a string");
|
|
20804
|
+
}
|
|
20805
|
+
if (typeof p19.signature_verified !== "boolean") {
|
|
20806
|
+
throw new CandidatePayloadValidationError("provenance.signature_verified must be a boolean");
|
|
20807
|
+
}
|
|
20808
|
+
if (!isRuleDestination(r2.destination)) {
|
|
20809
|
+
throw new CandidatePayloadValidationError("provenance-bearing candidate must declare a valid destination");
|
|
20810
|
+
}
|
|
20811
|
+
if (r2.draft_text !== void 0 && typeof r2.draft_text !== "string") {
|
|
20812
|
+
throw new CandidatePayloadValidationError("draft_text must be a string when present");
|
|
20813
|
+
}
|
|
20814
|
+
} else if (r2.destination !== void 0 && !isRuleDestination(r2.destination)) {
|
|
20815
|
+
throw new CandidatePayloadValidationError("destination, when present, must be a valid RuleDestination");
|
|
20816
|
+
}
|
|
20817
|
+
return r2;
|
|
20818
|
+
}
|
|
20819
|
+
async function applyRuleCandidate(db, opts) {
|
|
20820
|
+
const ent = await assertAutoLearningEntitled();
|
|
20821
|
+
if (!ent.entitled) {
|
|
20822
|
+
return { ok: false, error: ent.message, tier_refused: true };
|
|
20823
|
+
}
|
|
20824
|
+
if (!CANDIDATE_ID_PATTERN.test(opts.candidateId)) {
|
|
20825
|
+
throw new InvalidCandidateIdError(opts.candidateId);
|
|
20826
|
+
}
|
|
20827
|
+
const projectRoot = opts.projectRoot ?? getProjectRoot();
|
|
20828
|
+
const home = opts.home ?? homedir12();
|
|
20829
|
+
const candidate = readCandidate(opts.candidateId, projectRoot);
|
|
20830
|
+
if (candidate.provenance?.origin === "team" || candidate.provenance?.origin === "pack") {
|
|
20831
|
+
const provOrigin = candidate.provenance.origin;
|
|
20832
|
+
if (candidate.destination !== void 0 && opts.destination !== candidate.destination) {
|
|
20833
|
+
return {
|
|
20834
|
+
ok: false,
|
|
20835
|
+
error: "destination mismatch: provenance-bearing candidate must apply to its authored destination"
|
|
20836
|
+
};
|
|
20837
|
+
}
|
|
20838
|
+
if (!entitledForTeamSharedPromotion(ent.currentTier)) {
|
|
20839
|
+
return { ok: false, error: teamSharedPromotionUpgradeMessage(ent.currentTier), tier_refused: true };
|
|
20840
|
+
}
|
|
20841
|
+
if (candidate.provenance.signature_verified !== true) {
|
|
20842
|
+
return { ok: false, error: `${provOrigin}-origin rule candidate has unverified provenance \u2014 refusing to apply` };
|
|
20843
|
+
}
|
|
20844
|
+
if (isTeamShareableDestination(opts.destination)) {
|
|
20845
|
+
} else if (isHardenedShareableDestination(opts.destination)) {
|
|
20846
|
+
const gateErr = validateHardenedApplyGate(candidate.provenance);
|
|
20847
|
+
if (gateErr) return { ok: false, error: gateErr };
|
|
20848
|
+
} else {
|
|
20849
|
+
return { ok: false, error: `${provOrigin}-origin candidate has a non-shareable destination \u2014 refusing to apply` };
|
|
20850
|
+
}
|
|
20851
|
+
}
|
|
20852
|
+
const slug = opts.slug ? deriveSlug2(opts.slug) : deriveSlug2(candidate.prompt);
|
|
20853
|
+
if (!SLUG_ALLOWED2.test(slug)) {
|
|
20854
|
+
throw new InvalidSlugError(slug);
|
|
20855
|
+
}
|
|
20856
|
+
const memoryIndex = resolveMemoryIndexPath(projectRoot, home);
|
|
20857
|
+
if (!existsSync32(memoryIndex)) {
|
|
20858
|
+
throw new MemoryIndexMissingError(memoryIndex);
|
|
20859
|
+
}
|
|
20860
|
+
let destTargets;
|
|
20861
|
+
try {
|
|
20862
|
+
destTargets = computeDestinationTargets(opts, candidate, slug, projectRoot, home);
|
|
20863
|
+
} catch (err) {
|
|
20864
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
20865
|
+
}
|
|
20866
|
+
const candidatePath = join20(projectRoot, ".massu", "rule-candidates", `${opts.candidateId}.json`);
|
|
20867
|
+
const snapshot = takeSnapshots([
|
|
20868
|
+
...destTargets.paths,
|
|
20869
|
+
memoryIndex
|
|
20870
|
+
]);
|
|
20871
|
+
db.exec("BEGIN");
|
|
20872
|
+
let auditRowId;
|
|
20873
|
+
let indexLineSkipped;
|
|
20874
|
+
try {
|
|
20875
|
+
const auditEntry = {
|
|
20876
|
+
eventType: "rule_promoted",
|
|
20877
|
+
actor: "human",
|
|
20878
|
+
sessionId: candidate.session_id,
|
|
20879
|
+
filePath: destTargets.primary,
|
|
20880
|
+
metadata: {
|
|
20881
|
+
candidate_id: opts.candidateId,
|
|
20882
|
+
prompt_hash: candidate.prompt_hash,
|
|
20883
|
+
score: candidate.score,
|
|
20884
|
+
classification: opts.destination,
|
|
20885
|
+
cr_assigned: opts.claudeMdCrNumber ?? null,
|
|
20886
|
+
recurrence_count: 0
|
|
20887
|
+
}
|
|
20888
|
+
};
|
|
20889
|
+
try {
|
|
20890
|
+
logAuditEntry(db, auditEntry);
|
|
20891
|
+
auditRowId = db.prepare("SELECT last_insert_rowid() AS id").get().id;
|
|
20892
|
+
} catch (err) {
|
|
20893
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
20894
|
+
if (/UNIQUE constraint failed/i.test(msg)) {
|
|
20895
|
+
db.exec("ROLLBACK");
|
|
20896
|
+
return { ok: true, idempotent_noop: true };
|
|
20897
|
+
}
|
|
20898
|
+
throw err;
|
|
20899
|
+
}
|
|
20900
|
+
writeDestination(opts, candidate, slug, destTargets, auditRowId);
|
|
20901
|
+
const indexLine = assertSingleLine(
|
|
20902
|
+
`- [${slug}](feedback_${slug}.md) \u2014 ${candidate.prompt.replace(/\n+/g, " ").slice(0, 100)}`
|
|
20903
|
+
);
|
|
20904
|
+
try {
|
|
20905
|
+
writeIndexLineInRegion(memoryIndex, indexLine);
|
|
20906
|
+
} catch (err) {
|
|
20907
|
+
if (err instanceof RegionRefused || err instanceof MemoryIndexMissing) {
|
|
20908
|
+
indexLineSkipped = err.message;
|
|
20909
|
+
} else {
|
|
20910
|
+
throw err;
|
|
20911
|
+
}
|
|
20912
|
+
}
|
|
20913
|
+
if (existsSync32(candidatePath)) unlinkSync5(candidatePath);
|
|
20914
|
+
let teamShared = false;
|
|
20915
|
+
const localOrigin = candidate.provenance?.origin !== "team" && candidate.provenance?.origin !== "pack";
|
|
20916
|
+
const entitledTeam = entitledForTeamSharedPromotion(ent.currentTier);
|
|
20917
|
+
const nonHardenedShare = isTeamShareableDestination(opts.destination);
|
|
20918
|
+
const hardenedShare = isHardenedShareableDestination(opts.destination) && opts.reviewAttestation != null;
|
|
20919
|
+
if (localOrigin && entitledTeam && (nonHardenedShare || hardenedShare)) {
|
|
20920
|
+
enqueueTeamPromotion(db, {
|
|
20921
|
+
prompt_hash: candidate.prompt_hash,
|
|
20922
|
+
destination: opts.destination,
|
|
20923
|
+
draft_text: opts.draftText,
|
|
20924
|
+
score: candidate.score,
|
|
20925
|
+
signals: candidate.signals,
|
|
20926
|
+
content_hash: createHash9("sha256").update(`${opts.destination}
|
|
20927
|
+
${opts.draftText}`).digest("hex"),
|
|
20928
|
+
...hardenedShare ? { hardened: true, review_attestation: opts.reviewAttestation } : {}
|
|
20929
|
+
});
|
|
20930
|
+
teamShared = true;
|
|
20931
|
+
}
|
|
20932
|
+
setCandidateStatus(db, candidate.prompt_hash, "promoted", opts.destination);
|
|
20933
|
+
db.exec("COMMIT");
|
|
20934
|
+
if (localOrigin) recordApprovalFunnelEvent(db, candidate.prompt_hash, opts.destination, candidate.score, entitledTeam);
|
|
20935
|
+
return { ok: true, audit_log_id: auditRowId, team_shared: teamShared };
|
|
20936
|
+
} catch (err) {
|
|
20937
|
+
db.exec("ROLLBACK");
|
|
20938
|
+
restoreSnapshots(snapshot);
|
|
20939
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
20940
|
+
appendFailureLog(projectRoot, {
|
|
20941
|
+
candidate_id: opts.candidateId,
|
|
20942
|
+
destination: opts.destination,
|
|
20943
|
+
error: msg,
|
|
20944
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
20945
|
+
});
|
|
20946
|
+
return { ok: false, error: msg };
|
|
20947
|
+
}
|
|
20948
|
+
}
|
|
20949
|
+
function computeDestinationTargets(opts, candidate, slug, projectRoot, home) {
|
|
20950
|
+
if (opts.destination === "corrections-md") {
|
|
20951
|
+
const t2 = correctionsMdTargets(slug, projectRoot, home);
|
|
20952
|
+
return { primary: t2.feedbackMd, paths: [t2.correctionsMd, t2.feedbackMd] };
|
|
20953
|
+
}
|
|
20954
|
+
if (opts.destination === "pattern-scanner") {
|
|
20955
|
+
if (opts.patternScannerCheckNumber == null) {
|
|
20956
|
+
throw new DestinationConfigError("pattern-scanner destination requires patternScannerCheckNumber");
|
|
20957
|
+
}
|
|
20958
|
+
const scannerPath = join20(projectRoot, "scripts", "massu-pattern-scanner.sh");
|
|
20959
|
+
const driftGuardPath = join20(projectRoot, "packages", "core", "src", "__tests__", `${slug}-drift-guard.test.ts`);
|
|
20960
|
+
return { primary: scannerPath, paths: [scannerPath, driftGuardPath] };
|
|
20961
|
+
}
|
|
20962
|
+
if (opts.destination === "claude-md-cr") {
|
|
20963
|
+
if (opts.claudeMdCrNumber == null) {
|
|
20964
|
+
throw new DestinationConfigError("claude-md-cr destination requires claudeMdCrNumber");
|
|
20965
|
+
}
|
|
20966
|
+
const claudePath = join20(projectRoot, ".claude", "CLAUDE.md");
|
|
20967
|
+
return { primary: claudePath, paths: [claudePath] };
|
|
20968
|
+
}
|
|
20969
|
+
if (opts.destination === "custom-destination") {
|
|
20970
|
+
if (!opts.customDestination) {
|
|
20971
|
+
throw new DestinationConfigError("custom-destination requires customDestination config entry");
|
|
20972
|
+
}
|
|
20973
|
+
const raw = opts.customDestination.path;
|
|
20974
|
+
const absolute = isAbsolute2(raw) ? raw : join20(projectRoot, raw);
|
|
20975
|
+
try {
|
|
20976
|
+
assertContainedIn(projectRoot, absolute, { allowNested: true });
|
|
20977
|
+
} catch (err) {
|
|
20978
|
+
throw new DestinationConfigError(
|
|
20979
|
+
`custom destination path escapes project root: ${raw} (${err.message})`
|
|
20980
|
+
);
|
|
20981
|
+
}
|
|
20982
|
+
return { primary: absolute, paths: [absolute] };
|
|
20983
|
+
}
|
|
20984
|
+
throw new DestinationConfigError(`unknown destination "${opts.destination}"`);
|
|
20985
|
+
}
|
|
20986
|
+
function writeDestination(opts, candidate, slug, targets, auditRowId) {
|
|
20987
|
+
if (opts.destination === "corrections-md") {
|
|
20988
|
+
return writeCorrectionsMd(opts, candidate, slug, targets, auditRowId);
|
|
20989
|
+
}
|
|
20990
|
+
if (opts.destination === "pattern-scanner") {
|
|
20991
|
+
return writePatternScanner(opts, candidate, slug, targets);
|
|
20992
|
+
}
|
|
20993
|
+
if (opts.destination === "claude-md-cr") {
|
|
20994
|
+
return writeClaudeMdCr(opts, candidate, slug, targets);
|
|
20995
|
+
}
|
|
20996
|
+
if (opts.destination === "custom-destination") {
|
|
20997
|
+
return writeCustomDestination(opts, candidate, slug, targets);
|
|
20998
|
+
}
|
|
20999
|
+
throw new DestinationConfigError(`unknown destination "${opts.destination}"`);
|
|
21000
|
+
}
|
|
21001
|
+
function writeCorrectionsMd(opts, candidate, slug, targets, auditRowId) {
|
|
21002
|
+
const [correctionsMd, feedbackMd] = targets.paths;
|
|
21003
|
+
const dateIso = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
21004
|
+
const correctionsEntry = formatCorrectionEntry({
|
|
21005
|
+
date: dateIso,
|
|
21006
|
+
title: slug,
|
|
21007
|
+
rule: opts.draftText,
|
|
21008
|
+
extra: {
|
|
21009
|
+
prompt_hash: candidate.prompt_hash,
|
|
21010
|
+
score: candidate.score,
|
|
21011
|
+
audit_log_id: auditRowId
|
|
21012
|
+
}
|
|
21013
|
+
});
|
|
21014
|
+
if (!existsSync32(dirname17(correctionsMd))) mkdirSync14(dirname17(correctionsMd), { recursive: true });
|
|
21015
|
+
const correctionsPre = existsSync32(correctionsMd) ? readFileSync26(correctionsMd, "utf-8") : "# Corrections\n";
|
|
21016
|
+
writeFileSync9(correctionsMd, correctionsPre + correctionsEntry, "utf-8");
|
|
21017
|
+
const body = `${opts.draftText}
|
|
21018
|
+
`;
|
|
21019
|
+
const mac = mintAuthorship(body);
|
|
21020
|
+
const feedbackBody = [
|
|
21021
|
+
"---",
|
|
21022
|
+
`name: ${slug}`,
|
|
21023
|
+
`description: ${candidate.prompt.replace(/\n+/g, " ").slice(0, 200)}`,
|
|
21024
|
+
"type: feedback",
|
|
21025
|
+
"metadata:",
|
|
21026
|
+
` source: interactive`,
|
|
21027
|
+
` prompt_hash: ${candidate.prompt_hash}`,
|
|
21028
|
+
` audit_log_id: ${auditRowId}`,
|
|
21029
|
+
` score: ${candidate.score}`,
|
|
21030
|
+
// Display-only; never read for trust (drift-guarded). The MAC is the credential.
|
|
21031
|
+
...mac ? ["massu_authored: true", `${RENDER_MAC_KEY}: ${mac}`] : [],
|
|
21032
|
+
"---",
|
|
21033
|
+
"",
|
|
21034
|
+
body
|
|
21035
|
+
].join("\n");
|
|
21036
|
+
atomicWriteFileSync(feedbackMd, feedbackBody);
|
|
21037
|
+
}
|
|
21038
|
+
function writePatternScanner(opts, candidate, slug, targets) {
|
|
21039
|
+
const [scannerPath, driftGuardPath] = targets.paths;
|
|
21040
|
+
const checkNumber = opts.patternScannerCheckNumber;
|
|
21041
|
+
const scannerPre = existsSync32(scannerPath) ? readFileSync26(scannerPath, "utf-8") : "";
|
|
21042
|
+
if (scannerPre.includes(`${SCANNER_AUTOGEN_MARKER_PREFIX} ${candidate.prompt_hash}`)) {
|
|
21043
|
+
throw new DestinationConfigError(
|
|
21044
|
+
`scanner already contains a check auto-generated for prompt_hash ${candidate.prompt_hash}`
|
|
21045
|
+
);
|
|
21046
|
+
}
|
|
21047
|
+
const checkBlock = [
|
|
21048
|
+
"",
|
|
21049
|
+
`${SCANNER_AUTOGEN_MARKER_PREFIX} ${candidate.prompt_hash} (slug=${slug})`,
|
|
21050
|
+
`# Check ${checkNumber}: ${slug}`,
|
|
21051
|
+
'echo ""',
|
|
21052
|
+
`echo "Check ${checkNumber}: ${slug}"`,
|
|
21053
|
+
opts.draftText.trim(),
|
|
21054
|
+
""
|
|
21055
|
+
].join("\n");
|
|
21056
|
+
writeFileSync9(scannerPath, scannerPre + checkBlock, "utf-8");
|
|
21057
|
+
const driftGuard = [
|
|
21058
|
+
"// Copyright (c) 2026 Massu. All rights reserved.",
|
|
21059
|
+
"// Licensed under BSL 1.1 - see LICENSE file for details.",
|
|
21060
|
+
"",
|
|
21061
|
+
`// Drift-guard mirror for pattern-scanner Check ${checkNumber} (${slug}).`,
|
|
21062
|
+
`// Auto-generated by /massu-rule approve for prompt_hash ${candidate.prompt_hash}.`,
|
|
21063
|
+
"",
|
|
21064
|
+
"import { describe, it, expect } from 'vitest';",
|
|
21065
|
+
"import { readFileSync } from 'fs';",
|
|
21066
|
+
"import { join } from 'path';",
|
|
21067
|
+
"",
|
|
21068
|
+
`describe('${slug} drift-guard', () => {`,
|
|
21069
|
+
` it('pattern-scanner Check ${checkNumber} is present', () => {`,
|
|
21070
|
+
` const scanner = readFileSync(join(process.cwd(), 'scripts', 'massu-pattern-scanner.sh'), 'utf-8');`,
|
|
21071
|
+
` expect(scanner).toContain('Check ${checkNumber}: ${slug}');`,
|
|
21072
|
+
` expect(scanner).toContain('${candidate.prompt_hash}');`,
|
|
21073
|
+
` });`,
|
|
21074
|
+
`});`,
|
|
21075
|
+
""
|
|
21076
|
+
].join("\n");
|
|
21077
|
+
if (!existsSync32(dirname17(driftGuardPath))) mkdirSync14(dirname17(driftGuardPath), { recursive: true });
|
|
21078
|
+
writeFileSync9(driftGuardPath, driftGuard, "utf-8");
|
|
21079
|
+
}
|
|
21080
|
+
function writeClaudeMdCr(opts, candidate, slug, targets) {
|
|
21081
|
+
const [claudePath] = targets.paths;
|
|
21082
|
+
const crNumber = opts.claudeMdCrNumber;
|
|
21083
|
+
const pre = existsSync32(claudePath) ? readFileSync26(claudePath, "utf-8") : "";
|
|
21084
|
+
if (new RegExp(`\\bCR-${crNumber}\\b`).test(pre)) {
|
|
21085
|
+
throw new DestinationConfigError(`CLAUDE.md already contains a CR-${crNumber} entry`);
|
|
21086
|
+
}
|
|
21087
|
+
const tableHeaderRe = /(\| ID \| Rule \| Verification Type \|\s*\n\|[-| ]+\|\s*\n)((?:\| CR-\d+ \|[^\n]*\n)+)/;
|
|
21088
|
+
const tableMatch = pre.match(tableHeaderRe);
|
|
21089
|
+
if (!tableMatch) {
|
|
21090
|
+
throw new DestinationConfigError(
|
|
21091
|
+
"CLAUDE.md does not contain a recognizable Canonical Rules table \u2014 refusing to write CR row"
|
|
21092
|
+
);
|
|
21093
|
+
}
|
|
21094
|
+
const tableHeader = tableMatch[1];
|
|
21095
|
+
const existingRows = tableMatch[2];
|
|
21096
|
+
const newRow = `| CR-${crNumber} | ${opts.draftText.replace(/\n+/g, " ").trim().slice(0, 400)} | VR-NEW-${slug.toUpperCase()} |
|
|
21097
|
+
`;
|
|
21098
|
+
const updatedTable = tableHeader + existingRows + newRow;
|
|
21099
|
+
const afterTable = pre.replace(tableHeaderRe, updatedTable);
|
|
21100
|
+
const block = [
|
|
21101
|
+
"",
|
|
21102
|
+
"---",
|
|
21103
|
+
"",
|
|
21104
|
+
`### CR-${crNumber}: ${slug}`,
|
|
21105
|
+
"",
|
|
21106
|
+
`Auto-promoted from rule-candidate ${candidate.prompt_hash} (score=${candidate.score}).`,
|
|
21107
|
+
"",
|
|
21108
|
+
opts.draftText,
|
|
21109
|
+
""
|
|
21110
|
+
].join("\n");
|
|
21111
|
+
writeFileSync9(claudePath, afterTable + block, "utf-8");
|
|
21112
|
+
}
|
|
21113
|
+
function writeCustomDestination(opts, candidate, slug, targets) {
|
|
21114
|
+
const [destPath] = targets.paths;
|
|
21115
|
+
const entry = opts.customDestination;
|
|
21116
|
+
const vars = {
|
|
21117
|
+
date: (/* @__PURE__ */ new Date()).toISOString(),
|
|
21118
|
+
slug,
|
|
21119
|
+
score: candidate.score,
|
|
21120
|
+
signals_csv: candidate.signals.map((s) => s.name).join(","),
|
|
21121
|
+
prompt_preview: candidate.prompt.replace(/\n+/g, " ").slice(0, 120),
|
|
21122
|
+
destination_name: entry.name
|
|
21123
|
+
};
|
|
21124
|
+
const rendered = renderTemplate2(entry.template, vars);
|
|
21125
|
+
if (!existsSync32(dirname17(destPath))) mkdirSync14(dirname17(destPath), { recursive: true });
|
|
21126
|
+
const pre = existsSync32(destPath) ? readFileSync26(destPath, "utf-8") : "";
|
|
21127
|
+
const sep3 = pre && !pre.endsWith("\n") ? "\n" : "";
|
|
21128
|
+
writeFileSync9(destPath, pre + sep3 + rendered + (rendered.endsWith("\n") ? "" : "\n"), "utf-8");
|
|
21129
|
+
}
|
|
21130
|
+
function dismissRuleCandidate(db, opts) {
|
|
21131
|
+
if (!CANDIDATE_ID_PATTERN.test(opts.candidateId)) {
|
|
21132
|
+
throw new InvalidCandidateIdError(opts.candidateId);
|
|
21133
|
+
}
|
|
21134
|
+
const projectRoot = opts.projectRoot ?? getProjectRoot();
|
|
21135
|
+
const candidate = readCandidate(opts.candidateId, projectRoot);
|
|
21136
|
+
const candidatePath = join20(projectRoot, ".massu", "rule-candidates", `${opts.candidateId}.json`);
|
|
21137
|
+
db.exec("BEGIN");
|
|
21138
|
+
let auditRowId;
|
|
21139
|
+
let blacklistUpdates = 0;
|
|
21140
|
+
try {
|
|
21141
|
+
const upsert = db.prepare(`
|
|
21142
|
+
INSERT INTO prompt_outcomes_signal_blacklist (signal, dismissal_count, last_dismissed_at)
|
|
21143
|
+
VALUES (?, 1, datetime('now'))
|
|
21144
|
+
ON CONFLICT(signal) DO UPDATE SET
|
|
21145
|
+
dismissal_count = dismissal_count + 1,
|
|
21146
|
+
last_dismissed_at = datetime('now')
|
|
21147
|
+
`);
|
|
21148
|
+
for (const sig of candidate.signals) {
|
|
21149
|
+
if (sig.baseWeight <= 0) continue;
|
|
21150
|
+
const r2 = upsert.run(sig.name);
|
|
21151
|
+
blacklistUpdates += r2.changes;
|
|
21152
|
+
}
|
|
21153
|
+
const entry = {
|
|
21154
|
+
eventType: "rule_dismissed",
|
|
21155
|
+
actor: "human",
|
|
21156
|
+
sessionId: candidate.session_id,
|
|
21157
|
+
metadata: {
|
|
21158
|
+
candidate_id: opts.candidateId,
|
|
21159
|
+
prompt_hash: candidate.prompt_hash,
|
|
21160
|
+
score: candidate.score,
|
|
21161
|
+
reason: opts.reason,
|
|
21162
|
+
signals_fired: candidate.signals.map((s) => s.name)
|
|
21163
|
+
}
|
|
21164
|
+
};
|
|
21165
|
+
logAuditEntry(db, entry);
|
|
21166
|
+
auditRowId = db.prepare("SELECT last_insert_rowid() AS id").get().id;
|
|
21167
|
+
setCandidateStatus(db, candidate.prompt_hash, "dismissed");
|
|
21168
|
+
db.exec("COMMIT");
|
|
21169
|
+
} catch (err) {
|
|
21170
|
+
db.exec("ROLLBACK");
|
|
21171
|
+
return {
|
|
21172
|
+
ok: false,
|
|
21173
|
+
signals_blacklisted_or_incremented: 0,
|
|
21174
|
+
error: err instanceof Error ? err.message : String(err)
|
|
21175
|
+
};
|
|
21176
|
+
}
|
|
21177
|
+
try {
|
|
21178
|
+
const dismissedLog = join20(projectRoot, ".massu", "rule-candidates", ".dismissed.jsonl");
|
|
21179
|
+
if (!existsSync32(dirname17(dismissedLog))) mkdirSync14(dirname17(dismissedLog), { recursive: true });
|
|
21180
|
+
const pre = existsSync32(dismissedLog) ? readFileSync26(dismissedLog, "utf-8") : "";
|
|
21181
|
+
const sep3 = pre && !pre.endsWith("\n") ? "\n" : "";
|
|
21182
|
+
writeFileSync9(dismissedLog, pre + sep3 + JSON.stringify({
|
|
21183
|
+
id: opts.candidateId,
|
|
21184
|
+
prompt_hash: candidate.prompt_hash,
|
|
21185
|
+
score: candidate.score,
|
|
21186
|
+
signals_fired: candidate.signals.map((s) => s.name),
|
|
21187
|
+
reason: opts.reason,
|
|
21188
|
+
audit_log_id: auditRowId,
|
|
21189
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
21190
|
+
}) + "\n", "utf-8");
|
|
21191
|
+
if (existsSync32(candidatePath)) unlinkSync5(candidatePath);
|
|
21192
|
+
} catch (err) {
|
|
21193
|
+
appendFailureLog(projectRoot, {
|
|
21194
|
+
candidate_id: opts.candidateId,
|
|
21195
|
+
destination: "dismiss-postcommit",
|
|
21196
|
+
error: err instanceof Error ? err.message : String(err),
|
|
21197
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
21198
|
+
});
|
|
21199
|
+
}
|
|
21200
|
+
recordDismissalFunnelEvent(db, candidate.prompt_hash, candidate.score);
|
|
21201
|
+
return {
|
|
21202
|
+
ok: true,
|
|
21203
|
+
audit_log_id: auditRowId,
|
|
21204
|
+
signals_blacklisted_or_incremented: blacklistUpdates
|
|
21205
|
+
};
|
|
21206
|
+
}
|
|
21207
|
+
function appendFailureLog(projectRoot, entry) {
|
|
21208
|
+
try {
|
|
21209
|
+
const dir = join20(projectRoot, ".massu", "rule-candidates");
|
|
21210
|
+
if (!existsSync32(dir)) mkdirSync14(dir, { recursive: true });
|
|
21211
|
+
const logPath = join20(dir, ".failures.jsonl");
|
|
21212
|
+
const pre = existsSync32(logPath) ? readFileSync26(logPath, "utf-8") : "";
|
|
21213
|
+
const sep3 = pre && !pre.endsWith("\n") ? "\n" : "";
|
|
21214
|
+
writeFileSync9(logPath, pre + sep3 + JSON.stringify(entry) + "\n", "utf-8");
|
|
21215
|
+
} catch {
|
|
21216
|
+
}
|
|
21217
|
+
}
|
|
21218
|
+
var CANDIDATE_ID_PATTERN, SLUG_ALLOWED2, SCANNER_AUTOGEN_MARKER_PREFIX, InvalidCandidateIdError, InvalidSlugError, CandidatePayloadValidationError, ALL_RULE_DESTINATIONS, TEAM_SHAREABLE_DESTINATIONS, MemoryIndexMissingError, CandidateNotFoundError, DestinationConfigError;
|
|
21219
|
+
var init_rule_candidate_applier = __esm({
|
|
21220
|
+
"src/rule-candidate-applier.ts"() {
|
|
21221
|
+
"use strict";
|
|
21222
|
+
init_safe_write();
|
|
21223
|
+
init_corrections_md();
|
|
21224
|
+
init_memory_authorship();
|
|
21225
|
+
init_memory_index_region();
|
|
21226
|
+
init_config();
|
|
21227
|
+
init_memory_path();
|
|
21228
|
+
init_audit_trail();
|
|
21229
|
+
init_template_renderer();
|
|
21230
|
+
init_auto_learning_entitlement();
|
|
21231
|
+
init_memory_db();
|
|
21232
|
+
init_rule_candidate_funnel();
|
|
21233
|
+
init_rule_candidate_store();
|
|
21234
|
+
init_rule_candidate_hardened();
|
|
21235
|
+
init_rule_candidate_hardened();
|
|
21236
|
+
init_rule_candidate_snapshot();
|
|
21237
|
+
CANDIDATE_ID_PATTERN = /^[a-f0-9]{16}$/;
|
|
21238
|
+
SLUG_ALLOWED2 = /^[a-z0-9_]+$/;
|
|
21239
|
+
SCANNER_AUTOGEN_MARKER_PREFIX = "# auto-generated by /massu-rule approve";
|
|
21240
|
+
InvalidCandidateIdError = class extends Error {
|
|
21241
|
+
constructor(id) {
|
|
21242
|
+
super(`candidate id "${id}" is not a 16-hex-char sha-keyed identifier`);
|
|
21243
|
+
this.id = id;
|
|
21244
|
+
this.name = "InvalidCandidateIdError";
|
|
21245
|
+
}
|
|
21246
|
+
id;
|
|
21247
|
+
};
|
|
21248
|
+
InvalidSlugError = class extends Error {
|
|
21249
|
+
constructor(slug) {
|
|
21250
|
+
super(`slug "${slug}" contains characters outside [a-z0-9_]`);
|
|
21251
|
+
this.slug = slug;
|
|
21252
|
+
this.name = "InvalidSlugError";
|
|
21253
|
+
}
|
|
21254
|
+
slug;
|
|
21255
|
+
};
|
|
21256
|
+
CandidatePayloadValidationError = class extends Error {
|
|
21257
|
+
constructor(issue) {
|
|
21258
|
+
super(`candidate payload validation failed: ${issue}`);
|
|
21259
|
+
this.issue = issue;
|
|
21260
|
+
this.name = "CandidatePayloadValidationError";
|
|
21261
|
+
}
|
|
21262
|
+
issue;
|
|
21263
|
+
};
|
|
21264
|
+
ALL_RULE_DESTINATIONS = ["corrections-md", "pattern-scanner", "claude-md-cr", "custom-destination"];
|
|
21265
|
+
TEAM_SHAREABLE_DESTINATIONS = [
|
|
21266
|
+
"corrections-md",
|
|
21267
|
+
"claude-md-cr"
|
|
21268
|
+
];
|
|
21269
|
+
MemoryIndexMissingError = class extends Error {
|
|
21270
|
+
constructor(path) {
|
|
21271
|
+
super(`MEMORY.md missing at ${path} \u2014 v0.2 requires the canonical memory index. Create it via \`mkdir -p ${dirname17(path)} && echo '# Memory Index' > ${path}\` or run \`/massu-bearings\``);
|
|
21272
|
+
this.path = path;
|
|
21273
|
+
this.name = "MemoryIndexMissingError";
|
|
21274
|
+
}
|
|
21275
|
+
path;
|
|
21276
|
+
};
|
|
21277
|
+
CandidateNotFoundError = class extends Error {
|
|
21278
|
+
constructor(path) {
|
|
21279
|
+
super(`candidate sidecar not found: ${path}`);
|
|
21280
|
+
this.path = path;
|
|
21281
|
+
this.name = "CandidateNotFoundError";
|
|
21282
|
+
}
|
|
21283
|
+
path;
|
|
21284
|
+
};
|
|
21285
|
+
DestinationConfigError = class extends Error {
|
|
21286
|
+
constructor(msg) {
|
|
21287
|
+
super(msg);
|
|
21288
|
+
this.name = "DestinationConfigError";
|
|
21289
|
+
}
|
|
21290
|
+
};
|
|
21291
|
+
}
|
|
21292
|
+
});
|
|
21293
|
+
|
|
21294
|
+
// src/rule-candidate-preview.ts
|
|
21295
|
+
import { readFileSync as readFileSync27, writeFileSync as writeFileSync10, existsSync as existsSync33 } from "node:fs";
|
|
21296
|
+
function renderHardenedPreview(destination, body) {
|
|
21297
|
+
const rendered = String(body ?? "");
|
|
21298
|
+
const riskFindings = RISK_PATTERNS.filter((p19) => p19.re.test(rendered)).map((p19) => p19.label);
|
|
21299
|
+
return { destination, rendered, riskFindings };
|
|
21300
|
+
}
|
|
21301
|
+
function validateReviewAttestation(att) {
|
|
21302
|
+
if (!att || typeof att !== "object") {
|
|
21303
|
+
throw new InvalidReviewAttestationError("must be an object");
|
|
21304
|
+
}
|
|
21305
|
+
const a2 = att;
|
|
21306
|
+
if (typeof a2.second_operator_id !== "string" || a2.second_operator_id.length === 0) {
|
|
21307
|
+
throw new InvalidReviewAttestationError("second_operator_id must be a non-empty string");
|
|
21308
|
+
}
|
|
21309
|
+
const ack = a2.dry_run_ack;
|
|
21310
|
+
if (!ack || typeof ack !== "object" || ack.ack !== true || typeof ack.ran_at !== "string") {
|
|
21311
|
+
throw new InvalidReviewAttestationError("dry_run_ack must be { ran_at: string, ack: true }");
|
|
21312
|
+
}
|
|
21313
|
+
return {
|
|
21314
|
+
second_operator_id: a2.second_operator_id,
|
|
21315
|
+
dry_run_ack: {
|
|
21316
|
+
ran_at: ack.ran_at,
|
|
21317
|
+
...typeof ack.exit_code === "number" ? { exit_code: ack.exit_code } : {},
|
|
21318
|
+
ack: true
|
|
21319
|
+
}
|
|
21320
|
+
};
|
|
21321
|
+
}
|
|
21322
|
+
function recordHardenedReviewAttestation(sidecarPath2, attestation) {
|
|
21323
|
+
if (!existsSync33(sidecarPath2)) {
|
|
21324
|
+
throw new Error(`candidate sidecar not found: ${sidecarPath2}`);
|
|
21325
|
+
}
|
|
21326
|
+
const validated = validateReviewAttestation(attestation);
|
|
21327
|
+
const raw = readFileSync27(sidecarPath2, "utf-8");
|
|
21328
|
+
const sidecar = JSON.parse(raw);
|
|
21329
|
+
const prov = sidecar.provenance;
|
|
21330
|
+
if (!prov || prov.origin !== "team" || prov.hardened !== true) {
|
|
21331
|
+
throw new Error("sidecar is not a hardened team-origin candidate \u2014 refusing to attach an attestation");
|
|
21332
|
+
}
|
|
21333
|
+
if (validated.second_operator_id === prov.promoted_by) {
|
|
21334
|
+
throw new InvalidReviewAttestationError(
|
|
21335
|
+
"second_operator_id equals the promoter \u2014 two-operator review requires a distinct operator"
|
|
21336
|
+
);
|
|
21337
|
+
}
|
|
21338
|
+
prov.review_attestation = validated;
|
|
21339
|
+
writeFileSync10(sidecarPath2, JSON.stringify(sidecar, null, 2), "utf-8");
|
|
21340
|
+
}
|
|
21341
|
+
var RISK_PATTERNS, InvalidReviewAttestationError;
|
|
21342
|
+
var init_rule_candidate_preview = __esm({
|
|
21343
|
+
"src/rule-candidate-preview.ts"() {
|
|
21344
|
+
"use strict";
|
|
21345
|
+
RISK_PATTERNS = [
|
|
21346
|
+
{ re: /\brm\s+-[a-z]*r[a-z]*f?\b|\brm\s+-[a-z]*f[a-z]*r\b/i, label: "rm -rf (recursive/forced delete)" },
|
|
21347
|
+
{ re: /\|\s*(sh|bash|zsh|dash)\b/i, label: "pipe-to-shell (| sh)" },
|
|
21348
|
+
{ re: /\b(curl|wget|nc|ncat|telnet|ssh|scp)\b/i, label: "network tool (curl/wget/nc/ssh/...)" },
|
|
21349
|
+
{ re: /\beval\b/i, label: "eval" },
|
|
21350
|
+
{ re: /\$\([^)]*\)|`[^`]*`/, label: "command substitution $(...) / backticks" },
|
|
21351
|
+
{ re: />\s*(\/|~|\$HOME|\.\.)/i, label: "redirect to absolute / home / parent path" },
|
|
21352
|
+
{ re: /\bsudo\b/i, label: "sudo (privilege escalation)" },
|
|
21353
|
+
{ re: /\b(chmod|chown)\b/i, label: "permission change (chmod/chown)" },
|
|
21354
|
+
{ re: /\bdd\b\s|\bmkfs\b|\b:\(\)\s*\{/i, label: "destructive/forkbomb pattern" },
|
|
21355
|
+
{ re: /\b(base64|xxd)\b.*\|/i, label: "encoded payload piped" }
|
|
21356
|
+
];
|
|
21357
|
+
InvalidReviewAttestationError = class extends Error {
|
|
21358
|
+
constructor(issue) {
|
|
21359
|
+
super(`invalid review attestation: ${issue}`);
|
|
21360
|
+
this.name = "InvalidReviewAttestationError";
|
|
21361
|
+
}
|
|
21362
|
+
};
|
|
21363
|
+
}
|
|
21364
|
+
});
|
|
21365
|
+
|
|
20067
21366
|
// src/security/promotion-pubkey.generated.ts
|
|
20068
21367
|
var PROMOTION_PUBKEY_ED25519, PROMOTION_PUBKEY_FINGERPRINT_HEX, KNOWN_PROMOTION_PUBKEY_FINGERPRINTS;
|
|
20069
21368
|
var init_promotion_pubkey_generated = __esm({
|
|
@@ -20097,20 +21396,8 @@ var init_promotion_envelope_verifier = __esm({
|
|
|
20097
21396
|
}
|
|
20098
21397
|
});
|
|
20099
21398
|
|
|
20100
|
-
// src/rule-candidate-hardened.ts
|
|
20101
|
-
var TEAM_HARDENED_SHAREABLE_DESTINATIONS;
|
|
20102
|
-
var init_rule_candidate_hardened = __esm({
|
|
20103
|
-
"src/rule-candidate-hardened.ts"() {
|
|
20104
|
-
"use strict";
|
|
20105
|
-
TEAM_HARDENED_SHAREABLE_DESTINATIONS = [
|
|
20106
|
-
"pattern-scanner",
|
|
20107
|
-
"custom-destination"
|
|
20108
|
-
];
|
|
20109
|
-
}
|
|
20110
|
-
});
|
|
20111
|
-
|
|
20112
21399
|
// src/rule-pack-schema.ts
|
|
20113
|
-
function
|
|
21400
|
+
function isRuleDestination2(value) {
|
|
20114
21401
|
return typeof value === "string" && RULE_DESTINATIONS.includes(value);
|
|
20115
21402
|
}
|
|
20116
21403
|
function isExecutableDestination(destination) {
|
|
@@ -20134,7 +21421,7 @@ function validateRulePackRule(rule) {
|
|
|
20134
21421
|
if (!isNonEmptyString(r2.severity)) {
|
|
20135
21422
|
errors.push("missing required field: severity");
|
|
20136
21423
|
}
|
|
20137
|
-
const destinationKnown =
|
|
21424
|
+
const destinationKnown = isRuleDestination2(r2.destination);
|
|
20138
21425
|
if (!destinationKnown) {
|
|
20139
21426
|
errors.push(
|
|
20140
21427
|
`invalid destination: ${JSON.stringify(r2.destination)} is not a known RuleDestination (one of ${RULE_DESTINATIONS.join(", ")})`
|
|
@@ -20188,8 +21475,8 @@ var init_rule_pack_schema = __esm({
|
|
|
20188
21475
|
});
|
|
20189
21476
|
|
|
20190
21477
|
// src/rule-pack-sync.ts
|
|
20191
|
-
import { existsSync as
|
|
20192
|
-
import { join as
|
|
21478
|
+
import { existsSync as existsSync34, writeFileSync as writeFileSync11, mkdirSync as mkdirSync15 } from "fs";
|
|
21479
|
+
import { join as join21, dirname as dirname18 } from "path";
|
|
20193
21480
|
async function pullInstalledPackRules(db, opts = {}) {
|
|
20194
21481
|
const config = getConfig();
|
|
20195
21482
|
const cloud = config.cloud;
|
|
@@ -20251,7 +21538,7 @@ async function pullInstalledPackRules(db, opts = {}) {
|
|
|
20251
21538
|
result.pulled += 1;
|
|
20252
21539
|
const promptHash = packRulePromptHash(pack.slug, rule);
|
|
20253
21540
|
const candidatePath = sidecarPath(projectRoot, promptHash);
|
|
20254
|
-
if (
|
|
21541
|
+
if (existsSync34(candidatePath) || alreadyApplied(db, promptHash)) {
|
|
20255
21542
|
result.skipped += 1;
|
|
20256
21543
|
continue;
|
|
20257
21544
|
}
|
|
@@ -20284,12 +21571,12 @@ function isValidInstalledPack(p19) {
|
|
|
20284
21571
|
return typeof r2.slug === "string" && r2.slug.length > 0 && Array.isArray(r2.rules);
|
|
20285
21572
|
}
|
|
20286
21573
|
function packRulePromptHash(slug, rule) {
|
|
20287
|
-
const { createHash:
|
|
21574
|
+
const { createHash: createHash15 } = __require("node:crypto");
|
|
20288
21575
|
const body = `${slug}\0${rule.destination}\0${rule.title}\0${rule.pattern ?? rule.check ?? rule.description}`;
|
|
20289
|
-
return
|
|
21576
|
+
return createHash15("sha256").update(body, "utf-8").digest("hex").slice(0, 16);
|
|
20290
21577
|
}
|
|
20291
21578
|
function sidecarPath(projectRoot, promptHash) {
|
|
20292
|
-
return
|
|
21579
|
+
return join21(projectRoot, ".massu", "rule-candidates", `${promptHash}.json`);
|
|
20293
21580
|
}
|
|
20294
21581
|
function alreadyApplied(db, promptHash) {
|
|
20295
21582
|
try {
|
|
@@ -20338,9 +21625,9 @@ function materializeCandidate(projectRoot, candidatePath, promptHash, rule, pack
|
|
|
20338
21625
|
destination: rule.destination,
|
|
20339
21626
|
draft_text: draftText
|
|
20340
21627
|
};
|
|
20341
|
-
const dir =
|
|
20342
|
-
if (!
|
|
20343
|
-
|
|
21628
|
+
const dir = dirname18(candidatePath);
|
|
21629
|
+
if (!existsSync34(dir)) mkdirSync15(dir, { recursive: true });
|
|
21630
|
+
writeFileSync11(candidatePath, JSON.stringify(sidecar, null, 2), "utf-8");
|
|
20344
21631
|
}
|
|
20345
21632
|
function emitDropTelemetry(db, eventType, data) {
|
|
20346
21633
|
recordTelemetry(db, eventType, data);
|
|
@@ -20374,6 +21661,12 @@ var rule_exports = {};
|
|
|
20374
21661
|
__export(rule_exports, {
|
|
20375
21662
|
handleRuleSubcommand: () => handleRuleSubcommand
|
|
20376
21663
|
});
|
|
21664
|
+
import { join as join22 } from "node:path";
|
|
21665
|
+
function flagValue2(args2, flag) {
|
|
21666
|
+
const i = args2.indexOf(flag);
|
|
21667
|
+
if (i === -1 || i === args2.length - 1) return void 0;
|
|
21668
|
+
return args2[i + 1];
|
|
21669
|
+
}
|
|
20377
21670
|
async function handleRuleSubcommand(args2) {
|
|
20378
21671
|
const sub = args2[0];
|
|
20379
21672
|
switch (sub) {
|
|
@@ -20422,11 +21715,179 @@ async function handleRuleSubcommand(args2) {
|
|
|
20422
21715
|
}
|
|
20423
21716
|
return { exitCode: 0 };
|
|
20424
21717
|
}
|
|
21718
|
+
case "review": {
|
|
21719
|
+
const candidateId = args2[1];
|
|
21720
|
+
if (!candidateId) {
|
|
21721
|
+
process.stderr.write(
|
|
21722
|
+
"Usage: massu rule review <candidate_id> --destination <dest> --draft <text>\n [--attest-operator <id> --attest-note <text>]\n"
|
|
21723
|
+
);
|
|
21724
|
+
return { exitCode: 2 };
|
|
21725
|
+
}
|
|
21726
|
+
const destination = flagValue2(args2, "--destination");
|
|
21727
|
+
const draft = flagValue2(args2, "--draft");
|
|
21728
|
+
if (!destination || !isRuleDestination(destination) || draft === void 0) {
|
|
21729
|
+
process.stderr.write("massu: review requires --destination <dest> and --draft <text>\n");
|
|
21730
|
+
return { exitCode: 2 };
|
|
21731
|
+
}
|
|
21732
|
+
const preview = renderHardenedPreview(destination, draft);
|
|
21733
|
+
process.stdout.write(
|
|
21734
|
+
`Hardened promotion review \u2014 ${preview.destination}
|
|
21735
|
+
|
|
21736
|
+
This is EXACTLY what would be applied (it is displayed, never run):
|
|
21737
|
+
${"-".repeat(60)}
|
|
21738
|
+
${preview.rendered}
|
|
21739
|
+
${"-".repeat(60)}
|
|
21740
|
+
|
|
21741
|
+
`
|
|
21742
|
+
);
|
|
21743
|
+
if (preview.riskFindings.length > 0) {
|
|
21744
|
+
process.stdout.write(
|
|
21745
|
+
`RISK FINDINGS \u2014 ${preview.riskFindings.length} pattern(s) a second operator must weigh:
|
|
21746
|
+
` + preview.riskFindings.map((f2) => ` - ${f2}
|
|
21747
|
+
`).join("") + "\nThese are advisory, not auto-rejections. A human decides.\n\n"
|
|
21748
|
+
);
|
|
21749
|
+
} else {
|
|
21750
|
+
process.stdout.write("No static risk patterns matched.\n\n");
|
|
21751
|
+
}
|
|
21752
|
+
const operator = flagValue2(args2, "--attest-operator");
|
|
21753
|
+
if (!operator) {
|
|
21754
|
+
process.stdout.write(
|
|
21755
|
+
`To attest (required before a hardened rule can be shared with your team):
|
|
21756
|
+
massu rule review ${candidateId} --destination ${destination} --draft '<text>' \\
|
|
21757
|
+
--attest-operator <your-id> --attest-note '<why this is safe>'
|
|
21758
|
+
`
|
|
21759
|
+
);
|
|
21760
|
+
return { exitCode: 0 };
|
|
21761
|
+
}
|
|
21762
|
+
try {
|
|
21763
|
+
const attestation = validateReviewAttestation({
|
|
21764
|
+
second_operator_id: operator,
|
|
21765
|
+
reviewed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
21766
|
+
note: flagValue2(args2, "--attest-note") ?? ""
|
|
21767
|
+
});
|
|
21768
|
+
const sidecar = join22(getProjectRoot(), ".massu", "rule-candidates", `${candidateId}.json`);
|
|
21769
|
+
recordHardenedReviewAttestation(sidecar, attestation);
|
|
21770
|
+
process.stdout.write(`Attestation recorded by '${operator}'. This candidate may now be approved.
|
|
21771
|
+
`);
|
|
21772
|
+
return { exitCode: 0 };
|
|
21773
|
+
} catch (err) {
|
|
21774
|
+
process.stderr.write(
|
|
21775
|
+
`massu: attestation refused: ${err instanceof Error ? err.message : String(err)}
|
|
21776
|
+
`
|
|
21777
|
+
);
|
|
21778
|
+
return { exitCode: 1 };
|
|
21779
|
+
}
|
|
21780
|
+
}
|
|
21781
|
+
case "approve": {
|
|
21782
|
+
const candidateId = args2[1];
|
|
21783
|
+
if (!candidateId) {
|
|
21784
|
+
process.stderr.write(
|
|
21785
|
+
"Usage: massu rule approve <candidate_id> --destination <dest> --draft <text> [--slug <s>]\n"
|
|
21786
|
+
);
|
|
21787
|
+
return { exitCode: 2 };
|
|
21788
|
+
}
|
|
21789
|
+
const destination = flagValue2(args2, "--destination");
|
|
21790
|
+
const draftText = flagValue2(args2, "--draft");
|
|
21791
|
+
if (!destination || !isRuleDestination(destination)) {
|
|
21792
|
+
process.stderr.write(
|
|
21793
|
+
`massu: --destination must be one of: ${RULE_DESTINATIONS2.join(", ")}
|
|
21794
|
+
`
|
|
21795
|
+
);
|
|
21796
|
+
return { exitCode: 2 };
|
|
21797
|
+
}
|
|
21798
|
+
if (draftText === void 0) {
|
|
21799
|
+
process.stderr.write("massu: approve requires --draft <text>\n");
|
|
21800
|
+
return { exitCode: 2 };
|
|
21801
|
+
}
|
|
21802
|
+
const db = getMemoryDb();
|
|
21803
|
+
try {
|
|
21804
|
+
const result = await applyRuleCandidate(db, {
|
|
21805
|
+
candidateId,
|
|
21806
|
+
destination,
|
|
21807
|
+
draftText,
|
|
21808
|
+
...flagValue2(args2, "--slug") ? { slug: flagValue2(args2, "--slug") } : {}
|
|
21809
|
+
});
|
|
21810
|
+
if (!result.ok) {
|
|
21811
|
+
process.stderr.write(`massu: promotion refused: ${result.error}
|
|
21812
|
+
`);
|
|
21813
|
+
return { exitCode: result.tier_refused ? 3 : 1 };
|
|
21814
|
+
}
|
|
21815
|
+
process.stdout.write(
|
|
21816
|
+
`Rule promoted to ${destination}.
|
|
21817
|
+
` + (result.team_shared ? " Queued to share with your team. It will be delivered on the next sync \u2014 and it will NOT be deleted until your team's server confirms receipt.\n" : " Local only (team sharing needs a Team seat and a shareable destination).\n")
|
|
21818
|
+
);
|
|
21819
|
+
return { exitCode: 0 };
|
|
21820
|
+
} finally {
|
|
21821
|
+
db.close();
|
|
21822
|
+
}
|
|
21823
|
+
}
|
|
21824
|
+
case "dismiss": {
|
|
21825
|
+
const candidateId = args2[1];
|
|
21826
|
+
if (!candidateId) {
|
|
21827
|
+
process.stderr.write('Usage: massu rule dismiss <candidate_id> [--reason "..."]\n');
|
|
21828
|
+
return { exitCode: 2 };
|
|
21829
|
+
}
|
|
21830
|
+
const db = getMemoryDb();
|
|
21831
|
+
try {
|
|
21832
|
+
const result = dismissRuleCandidate(db, {
|
|
21833
|
+
candidateId,
|
|
21834
|
+
reason: flagValue2(args2, "--reason") ?? ""
|
|
21835
|
+
});
|
|
21836
|
+
if (!result.ok) {
|
|
21837
|
+
process.stderr.write(`massu: dismiss failed: ${result.error}
|
|
21838
|
+
`);
|
|
21839
|
+
return { exitCode: 1 };
|
|
21840
|
+
}
|
|
21841
|
+
process.stdout.write("Candidate dismissed.\n");
|
|
21842
|
+
return { exitCode: 0 };
|
|
21843
|
+
} finally {
|
|
21844
|
+
db.close();
|
|
21845
|
+
}
|
|
21846
|
+
}
|
|
21847
|
+
case "delivery-status": {
|
|
21848
|
+
const db = getMemoryDb();
|
|
21849
|
+
try {
|
|
21850
|
+
const snap = undeliveredSnapshot(db);
|
|
21851
|
+
const rules = snap.promotions + snap.revocations;
|
|
21852
|
+
const giveups = db.prepare(
|
|
21853
|
+
`SELECT COUNT(*) AS n, MAX(created_at) AS last
|
|
21854
|
+
FROM analytics_events WHERE event_type = 'cloud_sync_giveup'`
|
|
21855
|
+
).get();
|
|
21856
|
+
const lastErr = db.prepare(
|
|
21857
|
+
`SELECT last_error FROM team_promotion_outbound
|
|
21858
|
+
WHERE last_error IS NOT NULL ORDER BY attempts DESC LIMIT 1`
|
|
21859
|
+
).get();
|
|
21860
|
+
if (rules === 0 && snap.events === 0) {
|
|
21861
|
+
process.stdout.write("Rule delivery: everything has reached your team. Nothing queued.\n");
|
|
21862
|
+
} else {
|
|
21863
|
+
process.stdout.write(
|
|
21864
|
+
`Rule delivery: ${rules} learned rule(s) and ${snap.events} funnel event(s) have NOT yet reached your team.
|
|
21865
|
+
They are SAFE \u2014 nothing is ever deleted until the server confirms it.
|
|
21866
|
+
` + (snap.oldest_rule_created_at ? ` Oldest waiting since: ${snap.oldest_rule_created_at}
|
|
21867
|
+
` : "") + ` Delivery attempts so far: ${snap.max_rule_attempts}
|
|
21868
|
+
` + (lastErr?.last_error ? ` Last error: ${lastErr.last_error}
|
|
21869
|
+
` : "") + (snap.stalled ? ` STALLED \u2014 this is not retrying its way out. Check your API key and endpoint (\`massu doctor\`).
|
|
21870
|
+
` : "")
|
|
21871
|
+
);
|
|
21872
|
+
}
|
|
21873
|
+
if (giveups.n > 0) {
|
|
21874
|
+
process.stdout.write(
|
|
21875
|
+
`
|
|
21876
|
+
Cloud sync has given up ${giveups.n} time(s) (most recent: ${giveups.last}).
|
|
21877
|
+
Session/observation data from those attempts was dropped; learned rules were not.
|
|
21878
|
+
`
|
|
21879
|
+
);
|
|
21880
|
+
}
|
|
21881
|
+
return { exitCode: snap.stalled ? 1 : 0 };
|
|
21882
|
+
} finally {
|
|
21883
|
+
db.close();
|
|
21884
|
+
}
|
|
21885
|
+
}
|
|
20425
21886
|
case "--help":
|
|
20426
21887
|
case "-h":
|
|
20427
21888
|
case void 0: {
|
|
20428
21889
|
process.stdout.write(
|
|
20429
|
-
"massu rule <subcommand>\n\nSubcommands:\n record-shown <prompt_hash> Record a `shown` promotion-funnel event (Team-gated, best-effort).\n packs Pull installed rule-pack rules as reviewable candidates (Team-gated).\n"
|
|
21890
|
+
"massu rule <subcommand>\n\nSubcommands:\n approve <id> --destination <d> --draft <text>\n Promote a candidate to a rule (Pro+). Shares to your team at Team+.\n dismiss <id> [--reason ...] Dismiss a candidate and downweight its signals.\n review <id> --destination <d> --draft <text>\n Preview a HARDENED (executable) promotion + its risk findings;\n add --attest-operator/--attest-note to record a 2nd-operator review.\n record-shown <prompt_hash> Record a `shown` promotion-funnel event (Team-gated, best-effort).\n packs Pull installed rule-pack rules as reviewable candidates (Team-gated).\n delivery-status Show which learned rules have not yet reached your team, and why.\n"
|
|
20430
21891
|
);
|
|
20431
21892
|
return { exitCode: 0 };
|
|
20432
21893
|
}
|
|
@@ -20437,14 +21898,24 @@ async function handleRuleSubcommand(args2) {
|
|
|
20437
21898
|
}
|
|
20438
21899
|
}
|
|
20439
21900
|
}
|
|
20440
|
-
var PROMPT_HASH_RE;
|
|
21901
|
+
var RULE_DESTINATIONS2, PROMPT_HASH_RE;
|
|
20441
21902
|
var init_rule = __esm({
|
|
20442
21903
|
"src/commands/rule.ts"() {
|
|
20443
21904
|
"use strict";
|
|
20444
21905
|
init_memory_db();
|
|
21906
|
+
init_config();
|
|
21907
|
+
init_rule_delivery();
|
|
21908
|
+
init_rule_candidate_applier();
|
|
21909
|
+
init_rule_candidate_preview();
|
|
20445
21910
|
init_license();
|
|
20446
21911
|
init_auto_learning_entitlement();
|
|
20447
21912
|
init_rule_pack_sync();
|
|
21913
|
+
RULE_DESTINATIONS2 = [
|
|
21914
|
+
"corrections-md",
|
|
21915
|
+
"claude-md-cr",
|
|
21916
|
+
"pattern-scanner",
|
|
21917
|
+
"custom-destination"
|
|
21918
|
+
];
|
|
20448
21919
|
PROMPT_HASH_RE = /^[0-9a-f]{16}$/;
|
|
20449
21920
|
}
|
|
20450
21921
|
});
|
|
@@ -20938,9 +22409,9 @@ var init_memory_supersede = __esm({
|
|
|
20938
22409
|
});
|
|
20939
22410
|
|
|
20940
22411
|
// src/memory-consolidate.ts
|
|
20941
|
-
import { createHash as
|
|
20942
|
-
import { existsSync as
|
|
20943
|
-
import { join as
|
|
22412
|
+
import { createHash as createHash10 } from "crypto";
|
|
22413
|
+
import { existsSync as existsSync35, mkdirSync as mkdirSync16, writeFileSync as writeFileSync12 } from "fs";
|
|
22414
|
+
import { join as join23 } from "path";
|
|
20944
22415
|
function emptyResult() {
|
|
20945
22416
|
return {
|
|
20946
22417
|
deduped: 0,
|
|
@@ -21102,7 +22573,7 @@ ${r2.detail ?? ""}`.trim());
|
|
|
21102
22573
|
}
|
|
21103
22574
|
clusters.push(cluster);
|
|
21104
22575
|
}
|
|
21105
|
-
const candidateDir =
|
|
22576
|
+
const candidateDir = join23(projectRoot, ".massu", "rule-candidates");
|
|
21106
22577
|
let promoted = 0;
|
|
21107
22578
|
for (const cluster of clusters) {
|
|
21108
22579
|
const occurrences = cluster.reduce((n, c2) => n + (c2.recurrence_count || 1), 0);
|
|
@@ -21114,15 +22585,15 @@ ${r2.detail ?? ""}`.trim());
|
|
|
21114
22585
|
const promptText = `${representative.title}${representative.detail ? `
|
|
21115
22586
|
${representative.detail}` : ""}`;
|
|
21116
22587
|
const clusterKey = cluster.map((c2) => c2.title.toLowerCase().replace(/\s+/g, " ").trim()).sort().join("|");
|
|
21117
|
-
const promptHash =
|
|
21118
|
-
const candidatePath =
|
|
21119
|
-
if (
|
|
22588
|
+
const promptHash = createHash10("sha256").update(clusterKey).digest("hex").slice(0, 16);
|
|
22589
|
+
const candidatePath = join23(candidateDir, `${promptHash}.json`);
|
|
22590
|
+
if (existsSync35(candidatePath)) continue;
|
|
21120
22591
|
if (dryRun) {
|
|
21121
22592
|
promoted++;
|
|
21122
22593
|
continue;
|
|
21123
22594
|
}
|
|
21124
|
-
|
|
21125
|
-
|
|
22595
|
+
mkdirSync16(candidateDir, { recursive: true });
|
|
22596
|
+
writeFileSync12(
|
|
21126
22597
|
candidatePath,
|
|
21127
22598
|
JSON.stringify(
|
|
21128
22599
|
{
|
|
@@ -21392,7 +22863,7 @@ var show_template_exports = {};
|
|
|
21392
22863
|
__export(show_template_exports, {
|
|
21393
22864
|
runShowTemplate: () => runShowTemplate
|
|
21394
22865
|
});
|
|
21395
|
-
import { existsSync as
|
|
22866
|
+
import { existsSync as existsSync36, readFileSync as readFileSync28 } from "fs";
|
|
21396
22867
|
import { resolve as resolve17 } from "path";
|
|
21397
22868
|
function normalizeBaseName(input) {
|
|
21398
22869
|
return input.endsWith(".md") ? input.slice(0, -".md".length) : input;
|
|
@@ -21422,13 +22893,13 @@ async function runShowTemplate(args2) {
|
|
|
21422
22893
|
}
|
|
21423
22894
|
const suffix = choice.kind === "hit" ? choice.suffix : "";
|
|
21424
22895
|
const file = suffix === "" ? resolve17(sourceDir, `${baseName}.md`) : resolve17(sourceDir, `${baseName}${suffix}.md`);
|
|
21425
|
-
if (!
|
|
22896
|
+
if (!existsSync36(file)) {
|
|
21426
22897
|
process.stderr.write(`massu: resolved template "${file}" no longer exists
|
|
21427
22898
|
`);
|
|
21428
22899
|
process.exit(1);
|
|
21429
22900
|
return;
|
|
21430
22901
|
}
|
|
21431
|
-
process.stdout.write(
|
|
22902
|
+
process.stdout.write(readFileSync28(file, "utf-8"));
|
|
21432
22903
|
}
|
|
21433
22904
|
var init_show_template = __esm({
|
|
21434
22905
|
"src/commands/show-template.ts"() {
|
|
@@ -21523,7 +22994,7 @@ __export(config_refresh_exports, {
|
|
|
21523
22994
|
mergeRefresh: () => mergeRefresh,
|
|
21524
22995
|
runConfigRefresh: () => runConfigRefresh
|
|
21525
22996
|
});
|
|
21526
|
-
import { existsSync as
|
|
22997
|
+
import { existsSync as existsSync37, readFileSync as readFileSync29, rmSync as rmSync8 } from "fs";
|
|
21527
22998
|
import { resolve as resolve19 } from "path";
|
|
21528
22999
|
import { parse as parseYaml5 } from "yaml";
|
|
21529
23000
|
function flatten(obj, prefix3 = "") {
|
|
@@ -21655,14 +23126,14 @@ async function runConfigRefresh(opts = {}) {
|
|
|
21655
23126
|
const configPath = resolve19(cwd, "massu.config.yaml");
|
|
21656
23127
|
const log = opts.silent ? () => {
|
|
21657
23128
|
} : (s) => process.stdout.write(s);
|
|
21658
|
-
if (!
|
|
23129
|
+
if (!existsSync37(configPath)) {
|
|
21659
23130
|
const message = "massu.config.yaml not found. Run: npx massu init";
|
|
21660
23131
|
if (!opts.silent) process.stderr.write(message + "\n");
|
|
21661
23132
|
return { exitCode: 1, applied: false, dryRun: !!opts.dryRun, diff: [], message };
|
|
21662
23133
|
}
|
|
21663
23134
|
let existing;
|
|
21664
23135
|
try {
|
|
21665
|
-
const content =
|
|
23136
|
+
const content = readFileSync29(configPath, "utf-8");
|
|
21666
23137
|
const parsed = parseYaml5(content);
|
|
21667
23138
|
if (!parsed || typeof parsed !== "object") {
|
|
21668
23139
|
throw new Error("config is not a YAML object");
|
|
@@ -21736,7 +23207,7 @@ async function runConfigRefresh(opts = {}) {
|
|
|
21736
23207
|
const stackResolved = installResult.totalInstalled > 0 || installResult.totalUpdated > 0;
|
|
21737
23208
|
if (stackResolved) {
|
|
21738
23209
|
const placeholderPath = resolve19(installResult.claudeDir, "commands", "_massu-needs-stack.md");
|
|
21739
|
-
if (
|
|
23210
|
+
if (existsSync37(placeholderPath)) {
|
|
21740
23211
|
try {
|
|
21741
23212
|
rmSync8(placeholderPath, { force: true });
|
|
21742
23213
|
log("Removed _massu-needs-stack.md (stack now declared).\n");
|
|
@@ -21845,12 +23316,12 @@ var init_gitToplevel = __esm({
|
|
|
21845
23316
|
});
|
|
21846
23317
|
|
|
21847
23318
|
// src/watch/lockfile-detector.ts
|
|
21848
|
-
import { existsSync as
|
|
23319
|
+
import { existsSync as existsSync38, statSync as statSync14 } from "fs";
|
|
21849
23320
|
import { resolve as resolve20 } from "path";
|
|
21850
23321
|
function lockfileMidWrite(projectRoot, now = Date.now(), windowMs = LOCKFILE_WINDOW_MS) {
|
|
21851
23322
|
for (const lf of KNOWN_LOCKFILES) {
|
|
21852
23323
|
const p19 = resolve20(projectRoot, lf);
|
|
21853
|
-
if (!
|
|
23324
|
+
if (!existsSync38(p19)) continue;
|
|
21854
23325
|
try {
|
|
21855
23326
|
const stat = statSync14(p19);
|
|
21856
23327
|
const delta = now - stat.mtimeMs;
|
|
@@ -21863,7 +23334,7 @@ function lockfileMidWrite(projectRoot, now = Date.now(), windowMs = LOCKFILE_WIN
|
|
|
21863
23334
|
function gitMidOperation(projectRoot) {
|
|
21864
23335
|
const sentinels = ["MERGE_HEAD", "REBASE_HEAD", "CHERRY_PICK_HEAD", "rebase-apply", "rebase-merge"];
|
|
21865
23336
|
for (const s of sentinels) {
|
|
21866
|
-
if (
|
|
23337
|
+
if (existsSync38(resolve20(projectRoot, ".git", s))) return true;
|
|
21867
23338
|
}
|
|
21868
23339
|
return false;
|
|
21869
23340
|
}
|
|
@@ -22062,8 +23533,8 @@ var init_paths = __esm({
|
|
|
22062
23533
|
});
|
|
22063
23534
|
|
|
22064
23535
|
// src/watch/state.ts
|
|
22065
|
-
import { closeSync as closeSync4, existsSync as
|
|
22066
|
-
import { dirname as
|
|
23536
|
+
import { closeSync as closeSync4, existsSync as existsSync39, fsyncSync as fsyncSync4, mkdirSync as mkdirSync17, openSync as openSync4, readFileSync as readFileSync30, renameSync as renameSync5, rmSync as rmSync9, writeFileSync as writeFileSync13, writeSync as writeSync3 } from "fs";
|
|
23537
|
+
import { dirname as dirname19, resolve as resolve21 } from "path";
|
|
22067
23538
|
function watchStatePath(projectRoot) {
|
|
22068
23539
|
return resolve21(projectRoot, ".massu", "watch-state.json");
|
|
22069
23540
|
}
|
|
@@ -22072,8 +23543,8 @@ function backupStatePath(projectRoot) {
|
|
|
22072
23543
|
}
|
|
22073
23544
|
function readState(projectRoot) {
|
|
22074
23545
|
const path = watchStatePath(projectRoot);
|
|
22075
|
-
if (!
|
|
22076
|
-
const content =
|
|
23546
|
+
if (!existsSync39(path)) return { ...DEFAULT_STATE };
|
|
23547
|
+
const content = readFileSync30(path, "utf-8");
|
|
22077
23548
|
let raw;
|
|
22078
23549
|
try {
|
|
22079
23550
|
raw = JSON.parse(content);
|
|
@@ -22115,14 +23586,14 @@ function readState(projectRoot) {
|
|
|
22115
23586
|
}
|
|
22116
23587
|
function archiveCorrupt(projectRoot, content) {
|
|
22117
23588
|
const bak = backupStatePath(projectRoot);
|
|
22118
|
-
|
|
22119
|
-
|
|
23589
|
+
mkdirSync17(dirname19(bak), { recursive: true });
|
|
23590
|
+
writeFileSync13(bak, content, "utf-8");
|
|
22120
23591
|
}
|
|
22121
23592
|
function writeStateAtomic(projectRoot, state) {
|
|
22122
23593
|
const path = watchStatePath(projectRoot);
|
|
22123
23594
|
writeStateAtomicCounter = writeStateAtomicCounter + 1 >>> 0;
|
|
22124
23595
|
const tmp = `${path}.${process.pid}.${writeStateAtomicCounter}.tmp`;
|
|
22125
|
-
|
|
23596
|
+
mkdirSync17(dirname19(path), { recursive: true });
|
|
22126
23597
|
let renamed = false;
|
|
22127
23598
|
try {
|
|
22128
23599
|
const fd = openSync4(tmp, "w");
|
|
@@ -22136,7 +23607,7 @@ function writeStateAtomic(projectRoot, state) {
|
|
|
22136
23607
|
renameSync5(tmp, path);
|
|
22137
23608
|
renamed = true;
|
|
22138
23609
|
} finally {
|
|
22139
|
-
if (!renamed &&
|
|
23610
|
+
if (!renamed && existsSync39(tmp)) {
|
|
22140
23611
|
try {
|
|
22141
23612
|
rmSync9(tmp, { force: true });
|
|
22142
23613
|
} catch {
|
|
@@ -22418,8 +23889,8 @@ __export(watch_exports, {
|
|
|
22418
23889
|
runWatch: () => runWatch
|
|
22419
23890
|
});
|
|
22420
23891
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
22421
|
-
import { basename as basename8, dirname as
|
|
22422
|
-
import { appendFileSync as appendFileSync3, existsSync as
|
|
23892
|
+
import { basename as basename8, dirname as dirname20, resolve as resolve22 } from "path";
|
|
23893
|
+
import { appendFileSync as appendFileSync3, existsSync as existsSync40, mkdirSync as mkdirSync18, readFileSync as readFileSync31 } from "fs";
|
|
22423
23894
|
function parseFlags(args2) {
|
|
22424
23895
|
const out = {
|
|
22425
23896
|
foreground: false,
|
|
@@ -22445,7 +23916,7 @@ function parseFlags(args2) {
|
|
|
22445
23916
|
function findClaudeBg() {
|
|
22446
23917
|
const home = process.env.HOME ?? "";
|
|
22447
23918
|
const fixed = home ? resolve22(home, ".claude", "bin", "claude-bg") : null;
|
|
22448
|
-
if (fixed &&
|
|
23919
|
+
if (fixed && existsSync40(fixed)) return fixed;
|
|
22449
23920
|
const which = spawnSync3("which", ["claude-bg"], { encoding: "utf-8" });
|
|
22450
23921
|
if (which.status === 0 && which.stdout) {
|
|
22451
23922
|
const p19 = which.stdout.trim();
|
|
@@ -22506,7 +23977,7 @@ async function runWatch(args2) {
|
|
|
22506
23977
|
}
|
|
22507
23978
|
function runStatus(root) {
|
|
22508
23979
|
const path = watchStatePath(root);
|
|
22509
|
-
if (!
|
|
23980
|
+
if (!existsSync40(path)) {
|
|
22510
23981
|
process.stdout.write("massu watch: not running (no state file)\n");
|
|
22511
23982
|
return { exitCode: 0 };
|
|
22512
23983
|
}
|
|
@@ -22664,18 +24135,18 @@ function refreshLogPath(projectRoot) {
|
|
|
22664
24135
|
function appendRefreshLog(projectRoot, event) {
|
|
22665
24136
|
const path = refreshLogPath(projectRoot);
|
|
22666
24137
|
try {
|
|
22667
|
-
|
|
24138
|
+
mkdirSync18(dirname20(path), { recursive: true });
|
|
22668
24139
|
appendFileSync3(path, JSON.stringify(event) + "\n", "utf-8");
|
|
22669
24140
|
} catch {
|
|
22670
24141
|
}
|
|
22671
24142
|
}
|
|
22672
24143
|
function readRefreshLog(projectRoot, limit = 10, opts = {}) {
|
|
22673
24144
|
const path = refreshLogPath(projectRoot);
|
|
22674
|
-
if (!
|
|
24145
|
+
if (!existsSync40(path)) return [];
|
|
22675
24146
|
const warn = opts.warn ?? ((s) => {
|
|
22676
24147
|
process.stderr.write(s);
|
|
22677
24148
|
});
|
|
22678
|
-
const lines =
|
|
24149
|
+
const lines = readFileSync31(path, "utf-8").split("\n").filter(Boolean);
|
|
22679
24150
|
const tail = lines.slice(-limit);
|
|
22680
24151
|
const out = [];
|
|
22681
24152
|
let corrupt = 0;
|
|
@@ -22748,26 +24219,26 @@ var init_refresh_log = __esm({
|
|
|
22748
24219
|
import {
|
|
22749
24220
|
chmodSync as chmodSync6,
|
|
22750
24221
|
closeSync as closeSync5,
|
|
22751
|
-
existsSync as
|
|
24222
|
+
existsSync as existsSync41,
|
|
22752
24223
|
fsyncSync as fsyncSync5,
|
|
22753
|
-
mkdirSync as
|
|
24224
|
+
mkdirSync as mkdirSync19,
|
|
22754
24225
|
openSync as openSync5,
|
|
22755
24226
|
renameSync as renameSync6,
|
|
22756
24227
|
rmSync as rmSync10,
|
|
22757
24228
|
statSync as statSync15,
|
|
22758
24229
|
writeSync as writeSync4
|
|
22759
24230
|
} from "node:fs";
|
|
22760
|
-
import { dirname as
|
|
24231
|
+
import { dirname as dirname21 } from "node:path";
|
|
22761
24232
|
function atomicWrite(path, content, opts = {}) {
|
|
22762
24233
|
const tmpPath = `${path}.tmp`;
|
|
22763
|
-
const parentDir =
|
|
24234
|
+
const parentDir = dirname21(path);
|
|
22764
24235
|
try {
|
|
22765
|
-
if (!
|
|
24236
|
+
if (!existsSync41(parentDir)) {
|
|
22766
24237
|
const mkdirOpts = { recursive: true };
|
|
22767
24238
|
if (opts.ensureParentDirMode !== void 0) {
|
|
22768
24239
|
mkdirOpts.mode = opts.ensureParentDirMode;
|
|
22769
24240
|
}
|
|
22770
|
-
|
|
24241
|
+
mkdirSync19(parentDir, mkdirOpts);
|
|
22771
24242
|
}
|
|
22772
24243
|
const buf = typeof content === "string" ? Buffer.from(content, "utf-8") : content;
|
|
22773
24244
|
const openMode = opts.mode ?? 420;
|
|
@@ -22784,7 +24255,7 @@ function atomicWrite(path, content, opts = {}) {
|
|
|
22784
24255
|
renameSync6(tmpPath, path);
|
|
22785
24256
|
return { written: true };
|
|
22786
24257
|
} catch (err) {
|
|
22787
|
-
if (
|
|
24258
|
+
if (existsSync41(tmpPath)) {
|
|
22788
24259
|
try {
|
|
22789
24260
|
rmSync10(tmpPath, { force: true });
|
|
22790
24261
|
} catch {
|
|
@@ -22864,10 +24335,10 @@ var init_registry_pubkey_generated = __esm({
|
|
|
22864
24335
|
});
|
|
22865
24336
|
|
|
22866
24337
|
// src/security/adapter-verifier.ts
|
|
22867
|
-
import { createHash as
|
|
24338
|
+
import { createHash as createHash11 } from "node:crypto";
|
|
22868
24339
|
import nacl from "tweetnacl";
|
|
22869
24340
|
function sha256Hex2(bytes) {
|
|
22870
|
-
return
|
|
24341
|
+
return createHash11("sha256").update(bytes).digest("hex");
|
|
22871
24342
|
}
|
|
22872
24343
|
function reviver(key, value) {
|
|
22873
24344
|
if (key === "__proto__" || key === "constructor" || key === "prototype") {
|
|
@@ -23133,24 +24604,24 @@ var init_fetcher = __esm({
|
|
|
23133
24604
|
});
|
|
23134
24605
|
|
|
23135
24606
|
// src/security/manifest-cache.ts
|
|
23136
|
-
import { existsSync as
|
|
23137
|
-
import { homedir as
|
|
24607
|
+
import { existsSync as existsSync42, readFileSync as readFileSync32, statSync as statSync16 } from "node:fs";
|
|
24608
|
+
import { homedir as homedir13 } from "node:os";
|
|
23138
24609
|
import { resolve as resolve23 } from "node:path";
|
|
23139
24610
|
import { z as z5 } from "zod";
|
|
23140
24611
|
function defaultCachePaths() {
|
|
23141
|
-
const dir = resolve23(
|
|
24612
|
+
const dir = resolve23(homedir13(), ".massu");
|
|
23142
24613
|
return {
|
|
23143
24614
|
cachePath: resolve23(dir, "adapter-manifest.json"),
|
|
23144
24615
|
lockPath: resolve23(dir, ".adapter-manifest.lock")
|
|
23145
24616
|
};
|
|
23146
24617
|
}
|
|
23147
24618
|
function loadCachedManifest(paths = defaultCachePaths()) {
|
|
23148
|
-
if (!
|
|
24619
|
+
if (!existsSync42(paths.cachePath)) {
|
|
23149
24620
|
return { kind: "absent" };
|
|
23150
24621
|
}
|
|
23151
24622
|
let raw;
|
|
23152
24623
|
try {
|
|
23153
|
-
const content =
|
|
24624
|
+
const content = readFileSync32(paths.cachePath, "utf-8");
|
|
23154
24625
|
raw = JSON.parse(content);
|
|
23155
24626
|
} catch (err) {
|
|
23156
24627
|
return {
|
|
@@ -23339,15 +24810,15 @@ var init_adapter_origin = __esm({
|
|
|
23339
24810
|
});
|
|
23340
24811
|
|
|
23341
24812
|
// src/security/local-fingerprint.ts
|
|
23342
|
-
import { existsSync as
|
|
23343
|
-
import { homedir as
|
|
23344
|
-
import { resolve as resolve24, isAbsolute as
|
|
23345
|
-
import { createHash as
|
|
24813
|
+
import { existsSync as existsSync43, readFileSync as readFileSync33, lstatSync as lstatSync5 } from "node:fs";
|
|
24814
|
+
import { homedir as homedir14 } from "node:os";
|
|
24815
|
+
import { resolve as resolve24, isAbsolute as isAbsolute3 } from "node:path";
|
|
24816
|
+
import { createHash as createHash12 } from "node:crypto";
|
|
23346
24817
|
import { z as z6 } from "zod";
|
|
23347
24818
|
function computeLocalFingerprint(localPaths, projectRoot) {
|
|
23348
24819
|
const tuples = [];
|
|
23349
24820
|
for (const p19 of localPaths) {
|
|
23350
|
-
const abs =
|
|
24821
|
+
const abs = isAbsolute3(p19) ? p19 : resolve24(projectRoot, p19);
|
|
23351
24822
|
let contentTag;
|
|
23352
24823
|
try {
|
|
23353
24824
|
const lst = lstatSync5(abs);
|
|
@@ -23356,7 +24827,7 @@ function computeLocalFingerprint(localPaths, projectRoot) {
|
|
|
23356
24827
|
} else if (!lst.isFile()) {
|
|
23357
24828
|
contentTag = "<not-a-file>";
|
|
23358
24829
|
} else {
|
|
23359
|
-
contentTag =
|
|
24830
|
+
contentTag = createHash12("sha256").update(readFileSync33(abs)).digest("hex");
|
|
23360
24831
|
}
|
|
23361
24832
|
} catch {
|
|
23362
24833
|
contentTag = "<missing>";
|
|
@@ -23365,13 +24836,13 @@ function computeLocalFingerprint(localPaths, projectRoot) {
|
|
|
23365
24836
|
}
|
|
23366
24837
|
tuples.sort((a2, b2) => a2.path < b2.path ? -1 : a2.path > b2.path ? 1 : 0);
|
|
23367
24838
|
const canonical = JSON.stringify(tuples);
|
|
23368
|
-
return
|
|
24839
|
+
return createHash12("sha256").update(canonical).digest("hex");
|
|
23369
24840
|
}
|
|
23370
24841
|
function readFingerprintSentinel(path = FINGERPRINT_PATH) {
|
|
23371
|
-
if (!
|
|
24842
|
+
if (!existsSync43(path)) return null;
|
|
23372
24843
|
let raw;
|
|
23373
24844
|
try {
|
|
23374
|
-
raw = JSON.parse(
|
|
24845
|
+
raw = JSON.parse(readFileSync33(path, "utf-8"));
|
|
23375
24846
|
} catch {
|
|
23376
24847
|
return null;
|
|
23377
24848
|
}
|
|
@@ -23419,7 +24890,7 @@ var init_local_fingerprint = __esm({
|
|
|
23419
24890
|
"use strict";
|
|
23420
24891
|
init_atomic_write();
|
|
23421
24892
|
init_manifest_schema();
|
|
23422
|
-
FINGERPRINT_PATH = resolve24(
|
|
24893
|
+
FINGERPRINT_PATH = resolve24(homedir14(), ".massu", "adapters-local-fingerprint.json");
|
|
23423
24894
|
FingerprintSentinelSchema = z6.object({
|
|
23424
24895
|
fingerprint: z6.string().regex(/^[0-9a-f]{64}$/),
|
|
23425
24896
|
source: z6.enum(["cli", "cli-resync"]),
|
|
@@ -23435,15 +24906,15 @@ var init_local_fingerprint = __esm({
|
|
|
23435
24906
|
});
|
|
23436
24907
|
|
|
23437
24908
|
// src/security/install-tracking.ts
|
|
23438
|
-
import { readFileSync as
|
|
23439
|
-
import { join as
|
|
23440
|
-
import { homedir as
|
|
24909
|
+
import { readFileSync as readFileSync34, readdirSync as readdirSync16, lstatSync as lstatSync6, existsSync as existsSync44 } from "node:fs";
|
|
24910
|
+
import { join as join24, relative as relative7, sep as sep2 } from "node:path";
|
|
24911
|
+
import { homedir as homedir15 } from "node:os";
|
|
23441
24912
|
import { resolve as resolve25 } from "node:path";
|
|
23442
|
-
import { createHash as
|
|
24913
|
+
import { createHash as createHash13 } from "node:crypto";
|
|
23443
24914
|
import { z as z7 } from "zod";
|
|
23444
24915
|
function containsHiddenDirs(packageDir) {
|
|
23445
24916
|
for (const hidden of EXCLUDED_DIR_NAMES) {
|
|
23446
|
-
if (
|
|
24917
|
+
if (existsSync44(`${packageDir}/${hidden}`)) {
|
|
23447
24918
|
return hidden;
|
|
23448
24919
|
}
|
|
23449
24920
|
}
|
|
@@ -23460,7 +24931,7 @@ function sha256OfDir(dir, opts = {}) {
|
|
|
23460
24931
|
return;
|
|
23461
24932
|
}
|
|
23462
24933
|
for (const entry of entries.sort()) {
|
|
23463
|
-
const absPath =
|
|
24934
|
+
const absPath = join24(currentDir, entry);
|
|
23464
24935
|
let lst;
|
|
23465
24936
|
try {
|
|
23466
24937
|
lst = lstatSync6(absPath);
|
|
@@ -23481,15 +24952,15 @@ function sha256OfDir(dir, opts = {}) {
|
|
|
23481
24952
|
`sha256OfDir: file ${absPath} exceeds maxFileBytes (${lst.size} > ${maxFileBytes}); adapter packages should not ship files this large.`
|
|
23482
24953
|
);
|
|
23483
24954
|
}
|
|
23484
|
-
const rel =
|
|
24955
|
+
const rel = relative7(dir, absPath).split(sep2).join("/");
|
|
23485
24956
|
files.push({ relativePath: rel, absPath });
|
|
23486
24957
|
}
|
|
23487
24958
|
}
|
|
23488
24959
|
walk(dir);
|
|
23489
24960
|
files.sort((a2, b2) => a2.relativePath < b2.relativePath ? -1 : a2.relativePath > b2.relativePath ? 1 : 0);
|
|
23490
|
-
const top =
|
|
24961
|
+
const top = createHash13("sha256");
|
|
23491
24962
|
for (const f2 of files) {
|
|
23492
|
-
const fileHash =
|
|
24963
|
+
const fileHash = createHash13("sha256").update(readFileSync34(f2.absPath)).digest("hex");
|
|
23493
24964
|
top.update(f2.relativePath, "utf-8");
|
|
23494
24965
|
top.update("\0", "utf-8");
|
|
23495
24966
|
top.update(fileHash, "utf-8");
|
|
@@ -23498,10 +24969,10 @@ function sha256OfDir(dir, opts = {}) {
|
|
|
23498
24969
|
return top.digest("hex");
|
|
23499
24970
|
}
|
|
23500
24971
|
function readInstalledManifest(path = INSTALLED_MANIFEST_PATH) {
|
|
23501
|
-
if (!
|
|
24972
|
+
if (!existsSync44(path)) return {};
|
|
23502
24973
|
let raw;
|
|
23503
24974
|
try {
|
|
23504
|
-
raw = JSON.parse(
|
|
24975
|
+
raw = JSON.parse(readFileSync34(path, "utf-8"));
|
|
23505
24976
|
} catch {
|
|
23506
24977
|
return {};
|
|
23507
24978
|
}
|
|
@@ -23565,7 +25036,7 @@ var init_install_tracking = __esm({
|
|
|
23565
25036
|
"src/security/install-tracking.ts"() {
|
|
23566
25037
|
"use strict";
|
|
23567
25038
|
init_atomic_write();
|
|
23568
|
-
INSTALLED_MANIFEST_PATH = resolve25(
|
|
25039
|
+
INSTALLED_MANIFEST_PATH = resolve25(homedir15(), ".massu", "adapter-manifest-installed.json");
|
|
23569
25040
|
DEFAULT_MAX_FILE_BYTES = 64 * 1024 * 1024;
|
|
23570
25041
|
EXCLUDED_DIR_NAMES = /* @__PURE__ */ new Set([".git", "node_modules", ".cache", ".tmp"]);
|
|
23571
25042
|
InstallEntrySchema = z7.object({
|
|
@@ -23588,12 +25059,12 @@ var init_install_tracking = __esm({
|
|
|
23588
25059
|
});
|
|
23589
25060
|
|
|
23590
25061
|
// src/detect/adapters/discover.ts
|
|
23591
|
-
import { existsSync as
|
|
23592
|
-
import { resolve as resolve26, isAbsolute as
|
|
25062
|
+
import { existsSync as existsSync45, readdirSync as readdirSync17, readFileSync as readFileSync35, lstatSync as lstatSync7 } from "node:fs";
|
|
25063
|
+
import { resolve as resolve26, isAbsolute as isAbsolute4 } from "node:path";
|
|
23593
25064
|
import { z as z8 } from "zod";
|
|
23594
25065
|
function walkNodeModules(projectRoot, warnings) {
|
|
23595
25066
|
const nodeModulesDir = resolve26(projectRoot, "node_modules");
|
|
23596
|
-
if (!
|
|
25067
|
+
if (!existsSync45(nodeModulesDir)) {
|
|
23597
25068
|
return [];
|
|
23598
25069
|
}
|
|
23599
25070
|
const candidates = [];
|
|
@@ -23648,10 +25119,10 @@ function walkNodeModules(projectRoot, warnings) {
|
|
|
23648
25119
|
}
|
|
23649
25120
|
function tryReadAdapterPackage(packageDir, warnings) {
|
|
23650
25121
|
const pkgJsonPath = resolve26(packageDir, "package.json");
|
|
23651
|
-
if (!
|
|
25122
|
+
if (!existsSync45(pkgJsonPath)) return null;
|
|
23652
25123
|
let raw;
|
|
23653
25124
|
try {
|
|
23654
|
-
raw = JSON.parse(
|
|
25125
|
+
raw = JSON.parse(readFileSync35(pkgJsonPath, "utf-8"));
|
|
23655
25126
|
} catch (err) {
|
|
23656
25127
|
warnings.push(
|
|
23657
25128
|
`skipping ${packageDir}: package.json parse failed (${err instanceof Error ? err.message : String(err)})`
|
|
@@ -23785,8 +25256,8 @@ function discoverAdapters(opts) {
|
|
|
23785
25256
|
const localSet = new Set(opts.configLocalPaths);
|
|
23786
25257
|
for (const localPath of opts.configLocalPaths) {
|
|
23787
25258
|
if (seenIds.has(localPath)) continue;
|
|
23788
|
-
const absPath =
|
|
23789
|
-
if (!
|
|
25259
|
+
const absPath = isAbsolute4(localPath) ? localPath : resolve26(opts.projectRoot, localPath);
|
|
25260
|
+
if (!existsSync45(absPath)) {
|
|
23790
25261
|
warnings.push(
|
|
23791
25262
|
`local adapter file not found: ${localPath} (resolved to ${absPath}). Remove via: massu adapters remove-local ${localPath}`
|
|
23792
25263
|
);
|
|
@@ -23868,7 +25339,7 @@ __export(adapters_exports, {
|
|
|
23868
25339
|
runAdaptersResyncLocalFingerprint: () => runAdaptersResyncLocalFingerprint,
|
|
23869
25340
|
runAdaptersSearch: () => runAdaptersSearch
|
|
23870
25341
|
});
|
|
23871
|
-
import { existsSync as
|
|
25342
|
+
import { existsSync as existsSync46, readFileSync as readFileSync36 } from "node:fs";
|
|
23872
25343
|
import { resolve as resolve27 } from "node:path";
|
|
23873
25344
|
import { parseDocument } from "yaml";
|
|
23874
25345
|
async function handleAdaptersSubcommand(args2) {
|
|
@@ -24137,7 +25608,7 @@ function mutateLocalArray(mutator, command) {
|
|
|
24137
25608
|
return { exitCode: 2 };
|
|
24138
25609
|
}
|
|
24139
25610
|
const yamlPath = resolve27(projectRoot, "massu.config.yaml");
|
|
24140
|
-
if (!
|
|
25611
|
+
if (!existsSync46(yamlPath)) {
|
|
24141
25612
|
process.stderr.write(
|
|
24142
25613
|
`${command}: massu.config.yaml not found at ${yamlPath}. Run \`massu init\` first.
|
|
24143
25614
|
`
|
|
@@ -24146,7 +25617,7 @@ function mutateLocalArray(mutator, command) {
|
|
|
24146
25617
|
}
|
|
24147
25618
|
let yamlText;
|
|
24148
25619
|
try {
|
|
24149
|
-
yamlText =
|
|
25620
|
+
yamlText = readFileSync36(yamlPath, "utf-8");
|
|
24150
25621
|
} catch (err) {
|
|
24151
25622
|
process.stderr.write(`${command}: failed to read ${yamlPath}: ${err instanceof Error ? err.message : String(err)}
|
|
24152
25623
|
`);
|
|
@@ -24237,7 +25708,7 @@ async function runAdaptersInstall(args2) {
|
|
|
24237
25708
|
return { exitCode: 1 };
|
|
24238
25709
|
}
|
|
24239
25710
|
const packageDir = resolve27(projectRoot, "node_modules", ...packageName.split("/"));
|
|
24240
|
-
if (!
|
|
25711
|
+
if (!existsSync46(packageDir)) {
|
|
24241
25712
|
process.stderr.write(
|
|
24242
25713
|
`install: ${packageName} is not installed in node_modules. Run \`npm install ${packageName}\` first.
|
|
24243
25714
|
`
|
|
@@ -24245,14 +25716,14 @@ async function runAdaptersInstall(args2) {
|
|
|
24245
25716
|
return { exitCode: 1 };
|
|
24246
25717
|
}
|
|
24247
25718
|
const pkgJsonPath = resolve27(packageDir, "package.json");
|
|
24248
|
-
if (!
|
|
25719
|
+
if (!existsSync46(pkgJsonPath)) {
|
|
24249
25720
|
process.stderr.write(`install: ${packageName} has no package.json at ${pkgJsonPath}
|
|
24250
25721
|
`);
|
|
24251
25722
|
return { exitCode: 1 };
|
|
24252
25723
|
}
|
|
24253
25724
|
let pkgJson;
|
|
24254
25725
|
try {
|
|
24255
|
-
pkgJson = JSON.parse(
|
|
25726
|
+
pkgJson = JSON.parse(readFileSync36(pkgJsonPath, "utf-8"));
|
|
24256
25727
|
} catch (err) {
|
|
24257
25728
|
process.stderr.write(`install: ${packageName} has malformed package.json: ${err instanceof Error ? err.message : String(err)}
|
|
24258
25729
|
`);
|
|
@@ -24372,7 +25843,7 @@ async function runAdaptersResign(_args) {
|
|
|
24372
25843
|
continue;
|
|
24373
25844
|
}
|
|
24374
25845
|
const packageDir = resolve27(projectRoot, "node_modules", ...name.split("/"));
|
|
24375
|
-
if (!
|
|
25846
|
+
if (!existsSync46(packageDir)) {
|
|
24376
25847
|
removed++;
|
|
24377
25848
|
warnings.push(`${name}@${entry.version}: not present in node_modules \u2014 REMOVED from sidecar`);
|
|
24378
25849
|
removeInstalledManifestEntry(name);
|
|
@@ -24459,10 +25930,10 @@ var init_adapters2 = __esm({
|
|
|
24459
25930
|
|
|
24460
25931
|
// src/preflight.ts
|
|
24461
25932
|
import Database4 from "better-sqlite3";
|
|
24462
|
-
import { existsSync as
|
|
25933
|
+
import { existsSync as existsSync47 } from "fs";
|
|
24463
25934
|
function assertCodegraphUsable(dbPath) {
|
|
24464
25935
|
const path = dbPath ?? getResolvedPaths().codegraphDbPath;
|
|
24465
|
-
if (!
|
|
25936
|
+
if (!existsSync47(path)) {
|
|
24466
25937
|
throw new CodegraphDbUnusableError(path, "missing");
|
|
24467
25938
|
}
|
|
24468
25939
|
let db = null;
|
|
@@ -24551,8 +26022,8 @@ var init_sql_table_names = __esm({
|
|
|
24551
26022
|
|
|
24552
26023
|
// src/db.ts
|
|
24553
26024
|
import Database5 from "better-sqlite3";
|
|
24554
|
-
import { dirname as
|
|
24555
|
-
import { existsSync as
|
|
26025
|
+
import { dirname as dirname22, join as join25 } from "path";
|
|
26026
|
+
import { existsSync as existsSync48, mkdirSync as mkdirSync20, readdirSync as readdirSync18, statSync as statSync17 } from "fs";
|
|
24556
26027
|
function getCodeGraphDb() {
|
|
24557
26028
|
const dbPath = getResolvedPaths().codegraphDbPath;
|
|
24558
26029
|
assertCodegraphUsable(dbPath);
|
|
@@ -24562,9 +26033,9 @@ function getCodeGraphDb() {
|
|
|
24562
26033
|
}
|
|
24563
26034
|
function getDataDb() {
|
|
24564
26035
|
const dbPath = getResolvedPaths().dataDbPath;
|
|
24565
|
-
const dir =
|
|
24566
|
-
if (!
|
|
24567
|
-
|
|
26036
|
+
const dir = dirname22(dbPath);
|
|
26037
|
+
if (!existsSync48(dir)) {
|
|
26038
|
+
mkdirSync20(dir, { recursive: true });
|
|
24568
26039
|
}
|
|
24569
26040
|
const db = new Database5(dbPath);
|
|
24570
26041
|
db.pragma("journal_mode = WAL");
|
|
@@ -24836,7 +26307,7 @@ function isPythonDataStale(dataDb, pythonRoot) {
|
|
|
24836
26307
|
try {
|
|
24837
26308
|
const entries = readdirSync18(dir, { withFileTypes: true });
|
|
24838
26309
|
for (const entry of entries) {
|
|
24839
|
-
const fullPath =
|
|
26310
|
+
const fullPath = join25(dir, entry.name);
|
|
24840
26311
|
if (entry.isDirectory()) {
|
|
24841
26312
|
if (["__pycache__", ".venv", "venv", "node_modules", ".mypy_cache", ".pytest_cache"].includes(entry.name)) continue;
|
|
24842
26313
|
if (checkDir(fullPath)) return true;
|
|
@@ -24940,8 +26411,8 @@ var init_rules = __esm({
|
|
|
24940
26411
|
});
|
|
24941
26412
|
|
|
24942
26413
|
// src/import-resolver.ts
|
|
24943
|
-
import { readFileSync as
|
|
24944
|
-
import { resolve as resolve29, dirname as
|
|
26414
|
+
import { readFileSync as readFileSync37, existsSync as existsSync49, statSync as statSync18 } from "fs";
|
|
26415
|
+
import { resolve as resolve29, dirname as dirname23, join as join26 } from "path";
|
|
24945
26416
|
function parseImports(source) {
|
|
24946
26417
|
const imports = [];
|
|
24947
26418
|
const lines = source.split("\n");
|
|
@@ -24999,21 +26470,21 @@ function resolveImportPath(specifier, fromFile) {
|
|
|
24999
26470
|
const paths = getResolvedPaths();
|
|
25000
26471
|
basePath = resolve29(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
|
|
25001
26472
|
} else {
|
|
25002
|
-
basePath = resolve29(
|
|
26473
|
+
basePath = resolve29(dirname23(fromFile), specifier);
|
|
25003
26474
|
}
|
|
25004
|
-
if (
|
|
26475
|
+
if (existsSync49(basePath) && !isDirectory(basePath)) {
|
|
25005
26476
|
return toRelative(basePath);
|
|
25006
26477
|
}
|
|
25007
26478
|
const resolvedPaths = getResolvedPaths();
|
|
25008
26479
|
for (const ext of resolvedPaths.extensions) {
|
|
25009
26480
|
const withExt = basePath + ext;
|
|
25010
|
-
if (
|
|
26481
|
+
if (existsSync49(withExt)) {
|
|
25011
26482
|
return toRelative(withExt);
|
|
25012
26483
|
}
|
|
25013
26484
|
}
|
|
25014
26485
|
for (const indexFile of resolvedPaths.indexFiles) {
|
|
25015
|
-
const indexPath =
|
|
25016
|
-
if (
|
|
26486
|
+
const indexPath = join26(basePath, indexFile);
|
|
26487
|
+
if (existsSync49(indexPath)) {
|
|
25017
26488
|
return toRelative(indexPath);
|
|
25018
26489
|
}
|
|
25019
26490
|
}
|
|
@@ -25050,10 +26521,10 @@ function buildImportIndex(dataDb, codegraphDb) {
|
|
|
25050
26521
|
let batch = [];
|
|
25051
26522
|
for (const file of files) {
|
|
25052
26523
|
const absPath = ensureWithinRoot(resolve29(projectRoot, file.path), projectRoot);
|
|
25053
|
-
if (!
|
|
26524
|
+
if (!existsSync49(absPath)) continue;
|
|
25054
26525
|
let source;
|
|
25055
26526
|
try {
|
|
25056
|
-
source =
|
|
26527
|
+
source = readFileSync37(absPath, "utf-8");
|
|
25057
26528
|
} catch {
|
|
25058
26529
|
continue;
|
|
25059
26530
|
}
|
|
@@ -25090,15 +26561,15 @@ var init_import_resolver = __esm({
|
|
|
25090
26561
|
});
|
|
25091
26562
|
|
|
25092
26563
|
// src/trpc-index.ts
|
|
25093
|
-
import { readFileSync as
|
|
25094
|
-
import { resolve as resolve30, join as
|
|
26564
|
+
import { readFileSync as readFileSync38, existsSync as existsSync50, readdirSync as readdirSync19 } from "fs";
|
|
26565
|
+
import { resolve as resolve30, join as join27 } from "path";
|
|
25095
26566
|
function parseRootRouter() {
|
|
25096
26567
|
const paths = getResolvedPaths();
|
|
25097
26568
|
const rootPath = paths.rootRouterPath;
|
|
25098
|
-
if (!
|
|
26569
|
+
if (!existsSync50(rootPath)) {
|
|
25099
26570
|
throw new Error(`Root router not found at ${rootPath}`);
|
|
25100
26571
|
}
|
|
25101
|
-
const source =
|
|
26572
|
+
const source = readFileSync38(rootPath, "utf-8");
|
|
25102
26573
|
const mappings = [];
|
|
25103
26574
|
const importMap = /* @__PURE__ */ new Map();
|
|
25104
26575
|
const importRegex = /import\s+\{[^}]*?(\w+Router)[^}]*\}\s+from\s+['"]\.\/routers\/([^'"]+)['"]/g;
|
|
@@ -25110,12 +26581,12 @@ function parseRootRouter() {
|
|
|
25110
26581
|
for (const ext of [".ts", ".tsx", ""]) {
|
|
25111
26582
|
const candidate = fullPath + ext;
|
|
25112
26583
|
const routersRelPath = getConfig().paths.routers ?? "src/server/api/routers";
|
|
25113
|
-
if (
|
|
26584
|
+
if (existsSync50(candidate)) {
|
|
25114
26585
|
filePath = routersRelPath + "/" + filePath + ext;
|
|
25115
26586
|
break;
|
|
25116
26587
|
}
|
|
25117
|
-
const indexCandidate =
|
|
25118
|
-
if (
|
|
26588
|
+
const indexCandidate = join27(fullPath, "index.ts");
|
|
26589
|
+
if (existsSync50(indexCandidate)) {
|
|
25119
26590
|
filePath = routersRelPath + "/" + filePath + "/index.ts";
|
|
25120
26591
|
break;
|
|
25121
26592
|
}
|
|
@@ -25135,8 +26606,8 @@ function parseRootRouter() {
|
|
|
25135
26606
|
}
|
|
25136
26607
|
function extractProcedures(routerFilePath) {
|
|
25137
26608
|
const absPath = resolve30(getProjectRoot(), routerFilePath);
|
|
25138
|
-
if (!
|
|
25139
|
-
const source =
|
|
26609
|
+
if (!existsSync50(absPath)) return [];
|
|
26610
|
+
const source = readFileSync38(absPath, "utf-8");
|
|
25140
26611
|
const procedures = [];
|
|
25141
26612
|
const seen = /* @__PURE__ */ new Set();
|
|
25142
26613
|
const procRegex = /(\w+)\s*:\s*(protected|public)Procedure/g;
|
|
@@ -25165,7 +26636,7 @@ function findUICallSites(routerKey, procedureName) {
|
|
|
25165
26636
|
];
|
|
25166
26637
|
const searchPattern = `api.${routerKey}.${procedureName}`;
|
|
25167
26638
|
for (const dir of searchDirs) {
|
|
25168
|
-
if (!
|
|
26639
|
+
if (!existsSync50(dir)) continue;
|
|
25169
26640
|
searchDirectory(dir, searchPattern, callSites);
|
|
25170
26641
|
}
|
|
25171
26642
|
return callSites;
|
|
@@ -25173,13 +26644,13 @@ function findUICallSites(routerKey, procedureName) {
|
|
|
25173
26644
|
function searchDirectory(dir, pattern, results) {
|
|
25174
26645
|
const entries = readdirSync19(dir, { withFileTypes: true });
|
|
25175
26646
|
for (const entry of entries) {
|
|
25176
|
-
const fullPath =
|
|
26647
|
+
const fullPath = join27(dir, entry.name);
|
|
25177
26648
|
if (entry.isDirectory()) {
|
|
25178
26649
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
25179
26650
|
searchDirectory(fullPath, pattern, results);
|
|
25180
26651
|
} else if (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) {
|
|
25181
26652
|
try {
|
|
25182
|
-
const source =
|
|
26653
|
+
const source = readFileSync38(fullPath, "utf-8");
|
|
25183
26654
|
const lines = source.split("\n");
|
|
25184
26655
|
for (let i = 0; i < lines.length; i++) {
|
|
25185
26656
|
if (lines[i].includes(pattern)) {
|
|
@@ -25243,7 +26714,7 @@ var init_trpc_index = __esm({
|
|
|
25243
26714
|
});
|
|
25244
26715
|
|
|
25245
26716
|
// src/page-deps.ts
|
|
25246
|
-
import { readFileSync as
|
|
26717
|
+
import { readFileSync as readFileSync39, existsSync as existsSync51 } from "fs";
|
|
25247
26718
|
import { resolve as resolve31 } from "path";
|
|
25248
26719
|
function deriveRoute(pageFile) {
|
|
25249
26720
|
let route = pageFile.replace(/^src\/app/, "").replace(/\/page\.tsx?$/, "").replace(/\/page\.jsx?$/, "");
|
|
@@ -25283,9 +26754,9 @@ function findRouterCalls(files) {
|
|
|
25283
26754
|
const projectRoot = getProjectRoot();
|
|
25284
26755
|
for (const file of files) {
|
|
25285
26756
|
const absPath = ensureWithinRoot(resolve31(projectRoot, file), projectRoot);
|
|
25286
|
-
if (!
|
|
26757
|
+
if (!existsSync51(absPath)) continue;
|
|
25287
26758
|
try {
|
|
25288
|
-
const source =
|
|
26759
|
+
const source = readFileSync39(absPath, "utf-8");
|
|
25289
26760
|
const apiCallRegex = /api\.(\w+)\.\w+/g;
|
|
25290
26761
|
let match;
|
|
25291
26762
|
while ((match = apiCallRegex.exec(source)) !== null) {
|
|
@@ -25304,9 +26775,9 @@ function findTablesFromRouters(routerNames, dataDb) {
|
|
|
25304
26775
|
).all(routerName);
|
|
25305
26776
|
for (const proc of procs) {
|
|
25306
26777
|
const absPath = ensureWithinRoot(resolve31(getProjectRoot(), proc.router_file), getProjectRoot());
|
|
25307
|
-
if (!
|
|
26778
|
+
if (!existsSync51(absPath)) continue;
|
|
25308
26779
|
try {
|
|
25309
|
-
const source =
|
|
26780
|
+
const source = readFileSync39(absPath, "utf-8");
|
|
25310
26781
|
const dbPattern = getConfig().dbAccessPattern ?? "ctx.db.{table}";
|
|
25311
26782
|
const regexStr = dbPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace("\\{table\\}", "(\\w+)");
|
|
25312
26783
|
const tableRegex = new RegExp(regexStr + "\\.", "g");
|
|
@@ -25578,14 +27049,14 @@ var init_domains = __esm({
|
|
|
25578
27049
|
});
|
|
25579
27050
|
|
|
25580
27051
|
// src/schema-mapper.ts
|
|
25581
|
-
import { readFileSync as
|
|
25582
|
-
import { join as
|
|
27052
|
+
import { readFileSync as readFileSync40, existsSync as existsSync52, readdirSync as readdirSync20 } from "fs";
|
|
27053
|
+
import { join as join28 } from "path";
|
|
25583
27054
|
function parsePrismaSchema() {
|
|
25584
27055
|
const schemaPath = getResolvedPaths().prismaSchemaPath;
|
|
25585
|
-
if (!
|
|
27056
|
+
if (!existsSync52(schemaPath)) {
|
|
25586
27057
|
throw new Error(`Prisma schema not found at ${schemaPath}`);
|
|
25587
27058
|
}
|
|
25588
|
-
const source =
|
|
27059
|
+
const source = readFileSync40(schemaPath, "utf-8");
|
|
25589
27060
|
const models = [];
|
|
25590
27061
|
const sourceLines = source.split("\n");
|
|
25591
27062
|
let i = 0;
|
|
@@ -25643,14 +27114,14 @@ function toSnakeCase(str) {
|
|
|
25643
27114
|
function findColumnUsageInRouters(tableName) {
|
|
25644
27115
|
const usage2 = /* @__PURE__ */ new Map();
|
|
25645
27116
|
const routersDir = getResolvedPaths().routersDir;
|
|
25646
|
-
if (!
|
|
27117
|
+
if (!existsSync52(routersDir)) return usage2;
|
|
25647
27118
|
scanDirectory(routersDir, tableName, usage2);
|
|
25648
27119
|
return usage2;
|
|
25649
27120
|
}
|
|
25650
27121
|
function scanDirectory(dir, tableName, usage2) {
|
|
25651
27122
|
const entries = readdirSync20(dir, { withFileTypes: true });
|
|
25652
27123
|
for (const entry of entries) {
|
|
25653
|
-
const fullPath =
|
|
27124
|
+
const fullPath = join28(dir, entry.name);
|
|
25654
27125
|
if (entry.isDirectory()) {
|
|
25655
27126
|
scanDirectory(fullPath, tableName, usage2);
|
|
25656
27127
|
} else if (entry.name.endsWith(".ts")) {
|
|
@@ -25660,7 +27131,7 @@ function scanDirectory(dir, tableName, usage2) {
|
|
|
25660
27131
|
}
|
|
25661
27132
|
function scanFile(absPath, tableName, usage2) {
|
|
25662
27133
|
try {
|
|
25663
|
-
const source =
|
|
27134
|
+
const source = readFileSync40(absPath, "utf-8");
|
|
25664
27135
|
if (!source.includes(tableName)) return;
|
|
25665
27136
|
const relPath = absPath.slice(getProjectRoot().length + 1);
|
|
25666
27137
|
const lines = source.split("\n");
|
|
@@ -25705,15 +27176,15 @@ function detectMismatches(models) {
|
|
|
25705
27176
|
}
|
|
25706
27177
|
function findFilesUsingColumn(dir, column, tableName) {
|
|
25707
27178
|
const result = [];
|
|
25708
|
-
if (!
|
|
27179
|
+
if (!existsSync52(dir)) return result;
|
|
25709
27180
|
const entries = readdirSync20(dir, { withFileTypes: true });
|
|
25710
27181
|
for (const entry of entries) {
|
|
25711
|
-
const fullPath =
|
|
27182
|
+
const fullPath = join28(dir, entry.name);
|
|
25712
27183
|
if (entry.isDirectory()) {
|
|
25713
27184
|
result.push(...findFilesUsingColumn(fullPath, column, tableName));
|
|
25714
27185
|
} else if (entry.name.endsWith(".ts")) {
|
|
25715
27186
|
try {
|
|
25716
|
-
const source =
|
|
27187
|
+
const source = readFileSync40(fullPath, "utf-8");
|
|
25717
27188
|
if (source.includes(tableName) && source.includes(column)) {
|
|
25718
27189
|
result.push(fullPath.slice(getProjectRoot().length + 1));
|
|
25719
27190
|
}
|
|
@@ -25858,35 +27329,35 @@ var init_import_parser = __esm({
|
|
|
25858
27329
|
});
|
|
25859
27330
|
|
|
25860
27331
|
// src/python/import-resolver.ts
|
|
25861
|
-
import { readFileSync as
|
|
25862
|
-
import { resolve as resolve33, join as
|
|
27332
|
+
import { readFileSync as readFileSync41, existsSync as existsSync53, readdirSync as readdirSync21 } from "fs";
|
|
27333
|
+
import { resolve as resolve33, join as join29, relative as relative8, dirname as dirname24 } from "path";
|
|
25863
27334
|
function resolvePythonModulePath(module, fromFile, pythonRoot, level) {
|
|
25864
27335
|
const projectRoot = getProjectRoot();
|
|
25865
27336
|
if (level > 0) {
|
|
25866
|
-
let baseDir =
|
|
27337
|
+
let baseDir = dirname24(resolve33(projectRoot, fromFile));
|
|
25867
27338
|
for (let i = 1; i < level; i++) {
|
|
25868
|
-
baseDir =
|
|
27339
|
+
baseDir = dirname24(baseDir);
|
|
25869
27340
|
}
|
|
25870
27341
|
const modulePart = module.replace(/^\.+/, "");
|
|
25871
27342
|
if (modulePart) {
|
|
25872
27343
|
const parts2 = modulePart.split(".");
|
|
25873
|
-
return tryResolvePythonPath(
|
|
27344
|
+
return tryResolvePythonPath(join29(baseDir, ...parts2), projectRoot);
|
|
25874
27345
|
}
|
|
25875
27346
|
return tryResolvePythonPath(baseDir, projectRoot);
|
|
25876
27347
|
}
|
|
25877
27348
|
const parts = module.split(".");
|
|
25878
|
-
const candidate =
|
|
27349
|
+
const candidate = join29(resolve33(projectRoot, pythonRoot), ...parts);
|
|
25879
27350
|
return tryResolvePythonPath(candidate, projectRoot);
|
|
25880
27351
|
}
|
|
25881
27352
|
function tryResolvePythonPath(basePath, projectRoot) {
|
|
25882
|
-
if (
|
|
25883
|
-
return
|
|
27353
|
+
if (existsSync53(basePath + ".py")) {
|
|
27354
|
+
return relative8(projectRoot, basePath + ".py");
|
|
25884
27355
|
}
|
|
25885
|
-
if (
|
|
25886
|
-
return
|
|
27356
|
+
if (existsSync53(join29(basePath, "__init__.py"))) {
|
|
27357
|
+
return relative8(projectRoot, join29(basePath, "__init__.py"));
|
|
25887
27358
|
}
|
|
25888
|
-
if (basePath.endsWith(".py") &&
|
|
25889
|
-
return
|
|
27359
|
+
if (basePath.endsWith(".py") && existsSync53(basePath)) {
|
|
27360
|
+
return relative8(projectRoot, basePath);
|
|
25890
27361
|
}
|
|
25891
27362
|
return null;
|
|
25892
27363
|
}
|
|
@@ -25897,9 +27368,9 @@ function walkPythonFiles(dir, excludeDirs) {
|
|
|
25897
27368
|
for (const entry of entries) {
|
|
25898
27369
|
if (entry.isDirectory()) {
|
|
25899
27370
|
if (excludeDirs.includes(entry.name)) continue;
|
|
25900
|
-
files.push(...walkPythonFiles(
|
|
27371
|
+
files.push(...walkPythonFiles(join29(dir, entry.name), excludeDirs));
|
|
25901
27372
|
} else if (entry.name.endsWith(".py")) {
|
|
25902
|
-
files.push(
|
|
27373
|
+
files.push(join29(dir, entry.name));
|
|
25903
27374
|
}
|
|
25904
27375
|
}
|
|
25905
27376
|
} catch {
|
|
@@ -25922,10 +27393,10 @@ function buildPythonImportIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__"
|
|
|
25922
27393
|
});
|
|
25923
27394
|
const batch = [];
|
|
25924
27395
|
for (const absFile of files) {
|
|
25925
|
-
const relFile =
|
|
27396
|
+
const relFile = relative8(projectRoot, absFile);
|
|
25926
27397
|
let source;
|
|
25927
27398
|
try {
|
|
25928
|
-
source =
|
|
27399
|
+
source = readFileSync41(absFile, "utf-8");
|
|
25929
27400
|
} catch {
|
|
25930
27401
|
continue;
|
|
25931
27402
|
}
|
|
@@ -26192,8 +27663,8 @@ var init_route_parser = __esm({
|
|
|
26192
27663
|
});
|
|
26193
27664
|
|
|
26194
27665
|
// src/python/route-indexer.ts
|
|
26195
|
-
import { readFileSync as
|
|
26196
|
-
import { join as
|
|
27666
|
+
import { readFileSync as readFileSync42, readdirSync as readdirSync22 } from "fs";
|
|
27667
|
+
import { join as join30, relative as relative9 } from "path";
|
|
26197
27668
|
function walkPyFiles(dir, excludeDirs) {
|
|
26198
27669
|
const files = [];
|
|
26199
27670
|
try {
|
|
@@ -26201,9 +27672,9 @@ function walkPyFiles(dir, excludeDirs) {
|
|
|
26201
27672
|
for (const entry of entries) {
|
|
26202
27673
|
if (entry.isDirectory()) {
|
|
26203
27674
|
if (excludeDirs.includes(entry.name)) continue;
|
|
26204
|
-
files.push(...walkPyFiles(
|
|
27675
|
+
files.push(...walkPyFiles(join30(dir, entry.name), excludeDirs));
|
|
26205
27676
|
} else if (entry.name.endsWith(".py")) {
|
|
26206
|
-
files.push(
|
|
27677
|
+
files.push(join30(dir, entry.name));
|
|
26207
27678
|
}
|
|
26208
27679
|
}
|
|
26209
27680
|
} catch {
|
|
@@ -26212,7 +27683,7 @@ function walkPyFiles(dir, excludeDirs) {
|
|
|
26212
27683
|
}
|
|
26213
27684
|
function buildPythonRouteIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]) {
|
|
26214
27685
|
const projectRoot = getProjectRoot();
|
|
26215
|
-
const absRoot =
|
|
27686
|
+
const absRoot = join30(projectRoot, pythonRoot);
|
|
26216
27687
|
dataDb.exec(`DELETE FROM ${t("py_routes")}`);
|
|
26217
27688
|
dataDb.exec(`DELETE FROM ${t("py_route_callers")}`);
|
|
26218
27689
|
const insertStmt = dataDb.prepare(
|
|
@@ -26222,10 +27693,10 @@ function buildPythonRouteIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__",
|
|
|
26222
27693
|
let count = 0;
|
|
26223
27694
|
dataDb.transaction(() => {
|
|
26224
27695
|
for (const absFile of files) {
|
|
26225
|
-
const relFile =
|
|
27696
|
+
const relFile = relative9(projectRoot, absFile);
|
|
26226
27697
|
let source;
|
|
26227
27698
|
try {
|
|
26228
|
-
source =
|
|
27699
|
+
source = readFileSync42(absFile, "utf-8");
|
|
26229
27700
|
} catch {
|
|
26230
27701
|
continue;
|
|
26231
27702
|
}
|
|
@@ -26332,7 +27803,7 @@ function parsePythonModels(source) {
|
|
|
26332
27803
|
const bases = classMatch[2];
|
|
26333
27804
|
const classLine = i + 1;
|
|
26334
27805
|
let isModel = isModelClass(bases);
|
|
26335
|
-
const
|
|
27806
|
+
const columns2 = [];
|
|
26336
27807
|
const relationships = [];
|
|
26337
27808
|
const foreignKeys = [];
|
|
26338
27809
|
let tableName = null;
|
|
@@ -26375,7 +27846,7 @@ function parsePythonModels(source) {
|
|
|
26375
27846
|
const colType = extractColumnType(argsStr);
|
|
26376
27847
|
const nullable = extractNullable(argsStr);
|
|
26377
27848
|
const primaryKey = extractPrimaryKey(argsStr);
|
|
26378
|
-
|
|
27849
|
+
columns2.push({ name: colName, type: colType, nullable, primaryKey });
|
|
26379
27850
|
foreignKeys.push(...extractForeignKeys(colName, argsStr));
|
|
26380
27851
|
}
|
|
26381
27852
|
i++;
|
|
@@ -26389,7 +27860,7 @@ function parsePythonModels(source) {
|
|
|
26389
27860
|
const mappedType = extractMappedType(mappedAnnotation);
|
|
26390
27861
|
const nullable = mappedType.startsWith("Optional") || extractNullable(argsStr || "");
|
|
26391
27862
|
const primaryKey = extractPrimaryKey(argsStr || "");
|
|
26392
|
-
|
|
27863
|
+
columns2.push({ name: colName, type: mappedType, nullable, primaryKey });
|
|
26393
27864
|
if (argsStr) {
|
|
26394
27865
|
foreignKeys.push(...extractForeignKeys(colName, argsStr));
|
|
26395
27866
|
}
|
|
@@ -26419,7 +27890,7 @@ function parsePythonModels(source) {
|
|
|
26419
27890
|
models.push({
|
|
26420
27891
|
className,
|
|
26421
27892
|
tableName,
|
|
26422
|
-
columns,
|
|
27893
|
+
columns: columns2,
|
|
26423
27894
|
relationships,
|
|
26424
27895
|
foreignKeys,
|
|
26425
27896
|
line: classLine
|
|
@@ -26437,8 +27908,8 @@ var init_model_parser = __esm({
|
|
|
26437
27908
|
});
|
|
26438
27909
|
|
|
26439
27910
|
// src/python/model-indexer.ts
|
|
26440
|
-
import { readFileSync as
|
|
26441
|
-
import { join as
|
|
27911
|
+
import { readFileSync as readFileSync43, readdirSync as readdirSync23 } from "fs";
|
|
27912
|
+
import { join as join31, relative as relative10 } from "path";
|
|
26442
27913
|
function walkPyFiles2(dir, excludeDirs) {
|
|
26443
27914
|
const files = [];
|
|
26444
27915
|
try {
|
|
@@ -26446,9 +27917,9 @@ function walkPyFiles2(dir, excludeDirs) {
|
|
|
26446
27917
|
for (const entry of entries) {
|
|
26447
27918
|
if (entry.isDirectory()) {
|
|
26448
27919
|
if (excludeDirs.includes(entry.name)) continue;
|
|
26449
|
-
files.push(...walkPyFiles2(
|
|
27920
|
+
files.push(...walkPyFiles2(join31(dir, entry.name), excludeDirs));
|
|
26450
27921
|
} else if (entry.name.endsWith(".py")) {
|
|
26451
|
-
files.push(
|
|
27922
|
+
files.push(join31(dir, entry.name));
|
|
26452
27923
|
}
|
|
26453
27924
|
}
|
|
26454
27925
|
} catch {
|
|
@@ -26457,7 +27928,7 @@ function walkPyFiles2(dir, excludeDirs) {
|
|
|
26457
27928
|
}
|
|
26458
27929
|
function buildPythonModelIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]) {
|
|
26459
27930
|
const projectRoot = getProjectRoot();
|
|
26460
|
-
const absRoot =
|
|
27931
|
+
const absRoot = join31(projectRoot, pythonRoot);
|
|
26461
27932
|
dataDb.exec(`DELETE FROM ${t("py_models")}`);
|
|
26462
27933
|
dataDb.exec(`DELETE FROM ${t("py_fk_edges")}`);
|
|
26463
27934
|
const insertModel = dataDb.prepare(
|
|
@@ -26470,10 +27941,10 @@ function buildPythonModelIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__",
|
|
|
26470
27941
|
let count = 0;
|
|
26471
27942
|
dataDb.transaction(() => {
|
|
26472
27943
|
for (const absFile of files) {
|
|
26473
|
-
const relFile =
|
|
27944
|
+
const relFile = relative10(projectRoot, absFile);
|
|
26474
27945
|
let source;
|
|
26475
27946
|
try {
|
|
26476
|
-
source =
|
|
27947
|
+
source = readFileSync43(absFile, "utf-8");
|
|
26477
27948
|
} catch {
|
|
26478
27949
|
continue;
|
|
26479
27950
|
}
|
|
@@ -26734,19 +28205,19 @@ var init_migration_parser = __esm({
|
|
|
26734
28205
|
});
|
|
26735
28206
|
|
|
26736
28207
|
// src/python/migration-indexer.ts
|
|
26737
|
-
import { readFileSync as
|
|
26738
|
-
import { join as
|
|
28208
|
+
import { readFileSync as readFileSync44, readdirSync as readdirSync24 } from "fs";
|
|
28209
|
+
import { join as join32, relative as relative11 } from "path";
|
|
26739
28210
|
function buildPythonMigrationIndex(dataDb, alembicDir) {
|
|
26740
28211
|
const projectRoot = getProjectRoot();
|
|
26741
|
-
const absDir =
|
|
28212
|
+
const absDir = join32(projectRoot, alembicDir);
|
|
26742
28213
|
dataDb.exec(`DELETE FROM ${t("py_migrations")}`);
|
|
26743
|
-
const versionsDir =
|
|
28214
|
+
const versionsDir = join32(absDir, "versions");
|
|
26744
28215
|
let files = [];
|
|
26745
28216
|
try {
|
|
26746
|
-
files = readdirSync24(versionsDir).filter((f2) => f2.endsWith(".py")).map((f2) =>
|
|
28217
|
+
files = readdirSync24(versionsDir).filter((f2) => f2.endsWith(".py")).map((f2) => join32(versionsDir, f2));
|
|
26747
28218
|
} catch {
|
|
26748
28219
|
try {
|
|
26749
|
-
files = readdirSync24(absDir).filter((f2) => f2.endsWith(".py") && f2 !== "env.py").map((f2) =>
|
|
28220
|
+
files = readdirSync24(absDir).filter((f2) => f2.endsWith(".py") && f2 !== "env.py").map((f2) => join32(absDir, f2));
|
|
26750
28221
|
} catch {
|
|
26751
28222
|
}
|
|
26752
28223
|
}
|
|
@@ -26760,7 +28231,7 @@ function buildPythonMigrationIndex(dataDb, alembicDir) {
|
|
|
26760
28231
|
for (const absFile of files) {
|
|
26761
28232
|
let source;
|
|
26762
28233
|
try {
|
|
26763
|
-
source =
|
|
28234
|
+
source = readFileSync44(absFile, "utf-8");
|
|
26764
28235
|
} catch {
|
|
26765
28236
|
continue;
|
|
26766
28237
|
}
|
|
@@ -26771,7 +28242,7 @@ function buildPythonMigrationIndex(dataDb, alembicDir) {
|
|
|
26771
28242
|
rows.push({
|
|
26772
28243
|
revision: parsed.revision,
|
|
26773
28244
|
downRevision: parsed.downRevision,
|
|
26774
|
-
file:
|
|
28245
|
+
file: relative11(projectRoot, absFile),
|
|
26775
28246
|
description: parsed.description,
|
|
26776
28247
|
operations: JSON.stringify(parsed.operations)
|
|
26777
28248
|
});
|
|
@@ -26795,12 +28266,12 @@ var init_migration_indexer = __esm({
|
|
|
26795
28266
|
});
|
|
26796
28267
|
|
|
26797
28268
|
// src/python/coupling-detector.ts
|
|
26798
|
-
import { readFileSync as
|
|
26799
|
-
import { join as
|
|
28269
|
+
import { readFileSync as readFileSync45, readdirSync as readdirSync25 } from "fs";
|
|
28270
|
+
import { join as join33, relative as relative12 } from "path";
|
|
26800
28271
|
function buildPythonCouplingIndex(dataDb) {
|
|
26801
28272
|
const projectRoot = getProjectRoot();
|
|
26802
28273
|
const config = getConfig();
|
|
26803
|
-
const srcDir =
|
|
28274
|
+
const srcDir = join33(projectRoot, config.paths.source);
|
|
26804
28275
|
const routes = dataDb.prepare(`SELECT id, method, path FROM ${t("py_routes")}`).all();
|
|
26805
28276
|
if (routes.length === 0) return 0;
|
|
26806
28277
|
dataDb.exec(`DELETE FROM ${t("py_route_callers")}`);
|
|
@@ -26829,10 +28300,10 @@ function buildPythonCouplingIndex(dataDb) {
|
|
|
26829
28300
|
];
|
|
26830
28301
|
dataDb.transaction(() => {
|
|
26831
28302
|
for (const absFile of frontendFiles) {
|
|
26832
|
-
const relFile =
|
|
28303
|
+
const relFile = relative12(projectRoot, absFile);
|
|
26833
28304
|
let source;
|
|
26834
28305
|
try {
|
|
26835
|
-
source =
|
|
28306
|
+
source = readFileSync45(absFile, "utf-8");
|
|
26836
28307
|
} catch {
|
|
26837
28308
|
continue;
|
|
26838
28309
|
}
|
|
@@ -26864,9 +28335,9 @@ function walkFrontendFiles(dir) {
|
|
|
26864
28335
|
for (const entry of entries) {
|
|
26865
28336
|
if (entry.isDirectory()) {
|
|
26866
28337
|
if (exclude.includes(entry.name)) continue;
|
|
26867
|
-
files.push(...walkFrontendFiles(
|
|
28338
|
+
files.push(...walkFrontendFiles(join33(dir, entry.name)));
|
|
26868
28339
|
} else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
|
|
26869
|
-
files.push(
|
|
28340
|
+
files.push(join33(dir, entry.name));
|
|
26870
28341
|
}
|
|
26871
28342
|
}
|
|
26872
28343
|
} catch {
|
|
@@ -26896,14 +28367,14 @@ var init_coupling_detector = __esm({
|
|
|
26896
28367
|
});
|
|
26897
28368
|
|
|
26898
28369
|
// src/memory-tools.ts
|
|
26899
|
-
function
|
|
28370
|
+
function p3(baseName) {
|
|
26900
28371
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
26901
28372
|
}
|
|
26902
28373
|
function getMemoryToolDefinitions() {
|
|
26903
28374
|
return [
|
|
26904
28375
|
// P4-001: memory_search
|
|
26905
28376
|
{
|
|
26906
|
-
name:
|
|
28377
|
+
name: p3("memory_search"),
|
|
26907
28378
|
description: "Search past session observations and decisions using full-text search. Returns compact index of matching observations.",
|
|
26908
28379
|
inputSchema: {
|
|
26909
28380
|
type: "object",
|
|
@@ -26919,7 +28390,7 @@ function getMemoryToolDefinitions() {
|
|
|
26919
28390
|
},
|
|
26920
28391
|
// P4-002: memory_timeline
|
|
26921
28392
|
{
|
|
26922
|
-
name:
|
|
28393
|
+
name: p3("memory_timeline"),
|
|
26923
28394
|
description: "Retrieve episodic memory - chronological context around a specific event. Shows observations before and after the anchor point to reconstruct what happened and why.",
|
|
26924
28395
|
inputSchema: {
|
|
26925
28396
|
type: "object",
|
|
@@ -26933,7 +28404,7 @@ function getMemoryToolDefinitions() {
|
|
|
26933
28404
|
},
|
|
26934
28405
|
// P4-003: memory_detail
|
|
26935
28406
|
{
|
|
26936
|
-
name:
|
|
28407
|
+
name: p3("memory_detail"),
|
|
26937
28408
|
description: "Get full observation details by IDs (batch). Includes evidence, files, plan items.",
|
|
26938
28409
|
inputSchema: {
|
|
26939
28410
|
type: "object",
|
|
@@ -26949,7 +28420,7 @@ function getMemoryToolDefinitions() {
|
|
|
26949
28420
|
},
|
|
26950
28421
|
// P4-004: memory_sessions
|
|
26951
28422
|
{
|
|
26952
|
-
name:
|
|
28423
|
+
name: p3("memory_sessions"),
|
|
26953
28424
|
description: "List recent sessions with summaries.",
|
|
26954
28425
|
inputSchema: {
|
|
26955
28426
|
type: "object",
|
|
@@ -26962,7 +28433,7 @@ function getMemoryToolDefinitions() {
|
|
|
26962
28433
|
},
|
|
26963
28434
|
// P4-005: memory_failures
|
|
26964
28435
|
{
|
|
26965
|
-
name:
|
|
28436
|
+
name: p3("memory_failures"),
|
|
26966
28437
|
description: "Get all failed attempts (DON'T RETRY warnings). Check before attempting a fix to see if it was already tried.",
|
|
26967
28438
|
inputSchema: {
|
|
26968
28439
|
type: "object",
|
|
@@ -26975,7 +28446,7 @@ function getMemoryToolDefinitions() {
|
|
|
26975
28446
|
},
|
|
26976
28447
|
// P4-007: memory_backfill
|
|
26977
28448
|
{
|
|
26978
|
-
name:
|
|
28449
|
+
name: p3("memory_backfill"),
|
|
26979
28450
|
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.",
|
|
26980
28451
|
inputSchema: {
|
|
26981
28452
|
type: "object",
|
|
@@ -26985,7 +28456,7 @@ function getMemoryToolDefinitions() {
|
|
|
26985
28456
|
},
|
|
26986
28457
|
// P4-006: memory_ingest
|
|
26987
28458
|
{
|
|
26988
|
-
name:
|
|
28459
|
+
name: p3("memory_ingest"),
|
|
26989
28460
|
description: "Manually record an observation that hooks cannot auto-detect. Use for significant decisions, discoveries, or failed attempts mid-session.",
|
|
26990
28461
|
inputSchema: {
|
|
26991
28462
|
type: "object",
|
|
@@ -27030,15 +28501,15 @@ function handleMemoryToolCall(name, args2, memoryDb) {
|
|
|
27030
28501
|
case "memory_backfill":
|
|
27031
28502
|
return handleBackfill(memoryDb);
|
|
27032
28503
|
default:
|
|
27033
|
-
return
|
|
28504
|
+
return text2(`Unknown memory tool: ${name}`);
|
|
27034
28505
|
}
|
|
27035
28506
|
} catch (error) {
|
|
27036
|
-
return
|
|
28507
|
+
return text2(`Error in ${name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
27037
28508
|
}
|
|
27038
28509
|
}
|
|
27039
28510
|
function handleSearch(args2, db) {
|
|
27040
28511
|
const query = args2.query;
|
|
27041
|
-
if (!query) return
|
|
28512
|
+
if (!query) return text2("Error: query is required");
|
|
27042
28513
|
const results = searchObservations(db, query, {
|
|
27043
28514
|
type: args2.type,
|
|
27044
28515
|
crRule: args2.cr_rule,
|
|
@@ -27046,7 +28517,7 @@ function handleSearch(args2, db) {
|
|
|
27046
28517
|
limit: args2.limit
|
|
27047
28518
|
});
|
|
27048
28519
|
if (results.length === 0) {
|
|
27049
|
-
return
|
|
28520
|
+
return text2(`No observations found for "${query}".`);
|
|
27050
28521
|
}
|
|
27051
28522
|
const lines = [`## Search Results for "${query}" (${results.length} matches)`, ""];
|
|
27052
28523
|
lines.push("| ID | Type | Title | Date | Importance |");
|
|
@@ -27055,17 +28526,17 @@ function handleSearch(args2, db) {
|
|
|
27055
28526
|
lines.push(`| ${r2.id} | ${r2.type} | ${r2.title.slice(0, 80)} | ${r2.created_at.split("T")[0]} | ${r2.importance} |`);
|
|
27056
28527
|
}
|
|
27057
28528
|
lines.push("");
|
|
27058
|
-
lines.push(`Use ${
|
|
27059
|
-
lines.push(`Use ${
|
|
27060
|
-
return
|
|
28529
|
+
lines.push(`Use ${p3("memory_detail")} with IDs for full details.`);
|
|
28530
|
+
lines.push(`Use ${p3("memory_timeline")} with an ID for chronological context.`);
|
|
28531
|
+
return text2(lines.join("\n"));
|
|
27061
28532
|
}
|
|
27062
28533
|
function handleTimeline(args2, db) {
|
|
27063
28534
|
const observationId = args2.observation_id;
|
|
27064
|
-
if (!observationId) return
|
|
28535
|
+
if (!observationId) return text2("Error: observation_id is required");
|
|
27065
28536
|
const depthBefore = args2.depth_before ?? 5;
|
|
27066
28537
|
const depthAfter = args2.depth_after ?? 5;
|
|
27067
28538
|
const anchor = db.prepare("SELECT * FROM observations WHERE id = ?").get(observationId);
|
|
27068
|
-
if (!anchor) return
|
|
28539
|
+
if (!anchor) return text2(`Observation ${observationId} not found.`);
|
|
27069
28540
|
const before = db.prepare(
|
|
27070
28541
|
"SELECT id, type, title, created_at, importance FROM observations WHERE session_id = ? AND created_at_epoch < ? ORDER BY created_at_epoch DESC LIMIT ?"
|
|
27071
28542
|
).all(anchor.session_id, anchor.created_at_epoch, depthBefore);
|
|
@@ -27080,17 +28551,17 @@ function handleTimeline(args2, db) {
|
|
|
27080
28551
|
for (const o of after) {
|
|
27081
28552
|
lines.push(` ${o.id} | ${o.type} | ${o.title.slice(0, 60)} | ${o.created_at.split("T")[0]}`);
|
|
27082
28553
|
}
|
|
27083
|
-
return
|
|
28554
|
+
return text2(lines.join("\n"));
|
|
27084
28555
|
}
|
|
27085
28556
|
function handleDetail(args2, db) {
|
|
27086
28557
|
const ids = args2.ids;
|
|
27087
|
-
if (!ids || ids.length === 0) return
|
|
28558
|
+
if (!ids || ids.length === 0) return text2("Error: ids array is required");
|
|
27088
28559
|
const placeholders = ids.map(() => "?").join(",");
|
|
27089
28560
|
const observations = db.prepare(
|
|
27090
28561
|
`SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch ASC LIMIT 10000`
|
|
27091
28562
|
).all(...ids);
|
|
27092
28563
|
if (observations.length === 0) {
|
|
27093
|
-
return
|
|
28564
|
+
return text2("No observations found for the given IDs.");
|
|
27094
28565
|
}
|
|
27095
28566
|
const lines = [];
|
|
27096
28567
|
for (const o of observations) {
|
|
@@ -27110,7 +28581,7 @@ function handleDetail(args2, db) {
|
|
|
27110
28581
|
if (o.recurrence_count > 1) lines.push(`**Recurrence**: ${o.recurrence_count}x`);
|
|
27111
28582
|
lines.push("");
|
|
27112
28583
|
}
|
|
27113
|
-
return
|
|
28584
|
+
return text2(lines.join("\n"));
|
|
27114
28585
|
}
|
|
27115
28586
|
function handleSessions(args2, db) {
|
|
27116
28587
|
const limit = args2.limit ?? 10;
|
|
@@ -27125,7 +28596,7 @@ function handleSessions(args2, db) {
|
|
|
27125
28596
|
params.push(limit);
|
|
27126
28597
|
const sessions = db.prepare(sql).all(...params);
|
|
27127
28598
|
if (sessions.length === 0) {
|
|
27128
|
-
return
|
|
28599
|
+
return text2("No sessions found.");
|
|
27129
28600
|
}
|
|
27130
28601
|
const lines = ["## Recent Sessions", ""];
|
|
27131
28602
|
lines.push("| Session ID | Status | Branch | Started | Plan |");
|
|
@@ -27145,14 +28616,14 @@ function handleSessions(args2, db) {
|
|
|
27145
28616
|
if (summary.completed) lines.push(`**Completed**: ${summary.completed.slice(0, 200)}`);
|
|
27146
28617
|
}
|
|
27147
28618
|
}
|
|
27148
|
-
return
|
|
28619
|
+
return text2(lines.join("\n"));
|
|
27149
28620
|
}
|
|
27150
28621
|
function handleFailures(args2, db) {
|
|
27151
28622
|
const query = args2.query;
|
|
27152
28623
|
const limit = args2.limit ?? 20;
|
|
27153
28624
|
const failures = getFailedAttempts(db, query, limit);
|
|
27154
28625
|
if (failures.length === 0) {
|
|
27155
|
-
return
|
|
28626
|
+
return text2(query ? `No failed attempts found for "${query}".` : "No failed attempts recorded.");
|
|
27156
28627
|
}
|
|
27157
28628
|
const lines = ["## Failed Attempts (DO NOT RETRY)", ""];
|
|
27158
28629
|
for (const f2 of failures) {
|
|
@@ -27162,12 +28633,12 @@ function handleFailures(args2, db) {
|
|
|
27162
28633
|
lines.push(`Session: ${f2.session_id.slice(0, 8)}... | Date: ${f2.created_at.split("T")[0]}`);
|
|
27163
28634
|
lines.push("");
|
|
27164
28635
|
}
|
|
27165
|
-
return
|
|
28636
|
+
return text2(lines.join("\n"));
|
|
27166
28637
|
}
|
|
27167
28638
|
function handleIngest(args2, db) {
|
|
27168
28639
|
const type = args2.type;
|
|
27169
28640
|
const title = args2.title;
|
|
27170
|
-
if (!type || !title) return
|
|
28641
|
+
if (!type || !title) return text2("Error: type and title are required");
|
|
27171
28642
|
const validTypes = [
|
|
27172
28643
|
"decision",
|
|
27173
28644
|
"bugfix",
|
|
@@ -27182,13 +28653,13 @@ function handleIngest(args2, db) {
|
|
|
27182
28653
|
"incident_near_miss"
|
|
27183
28654
|
];
|
|
27184
28655
|
if (!validTypes.includes(type)) {
|
|
27185
|
-
return
|
|
28656
|
+
return text2(`Error: invalid type "${type}". Valid types: ${validTypes.join(", ")}`);
|
|
27186
28657
|
}
|
|
27187
28658
|
const activeSession = db.prepare(
|
|
27188
28659
|
"SELECT session_id FROM sessions WHERE status = 'active' ORDER BY started_at_epoch DESC LIMIT 1"
|
|
27189
28660
|
).get();
|
|
27190
28661
|
if (!activeSession) {
|
|
27191
|
-
return
|
|
28662
|
+
return text2("Error: no active session found. Start a session first.");
|
|
27192
28663
|
}
|
|
27193
28664
|
const importance = args2.importance ?? assignImportance(type);
|
|
27194
28665
|
const id = addObservation(db, activeSession.session_id, type, title, args2.detail ?? null, {
|
|
@@ -27197,7 +28668,7 @@ function handleIngest(args2, db) {
|
|
|
27197
28668
|
planItem: args2.plan_item,
|
|
27198
28669
|
filesInvolved: args2.files
|
|
27199
28670
|
});
|
|
27200
|
-
return
|
|
28671
|
+
return text2(`Observation #${id} recorded successfully.
|
|
27201
28672
|
Type: ${type}
|
|
27202
28673
|
Title: ${title}
|
|
27203
28674
|
Importance: ${importance}
|
|
@@ -27216,9 +28687,9 @@ function handleBackfill(db) {
|
|
|
27216
28687
|
"",
|
|
27217
28688
|
stats.total === 0 ? "No memory files found in memory directory." : `Successfully processed ${stats.inserted + stats.updated} of ${stats.total} memory files.`
|
|
27218
28689
|
];
|
|
27219
|
-
return
|
|
28690
|
+
return text2(lines.join("\n"));
|
|
27220
28691
|
}
|
|
27221
|
-
function
|
|
28692
|
+
function text2(content) {
|
|
27222
28693
|
return { content: [{ type: "text", text: content }] };
|
|
27223
28694
|
}
|
|
27224
28695
|
function safeParseJson(json, fallback) {
|
|
@@ -27238,15 +28709,15 @@ var init_memory_tools = __esm({
|
|
|
27238
28709
|
});
|
|
27239
28710
|
|
|
27240
28711
|
// src/docs-tools.ts
|
|
27241
|
-
import { readFileSync as
|
|
28712
|
+
import { readFileSync as readFileSync46, existsSync as existsSync54 } from "fs";
|
|
27242
28713
|
import { resolve as resolve34, basename as basename9 } from "path";
|
|
27243
|
-
function
|
|
28714
|
+
function p4(baseName) {
|
|
27244
28715
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
27245
28716
|
}
|
|
27246
28717
|
function getDocsToolDefinitions() {
|
|
27247
28718
|
return [
|
|
27248
28719
|
{
|
|
27249
|
-
name:
|
|
28720
|
+
name: p4("docs_audit"),
|
|
27250
28721
|
description: "Audit which help site pages need updating based on changed files. Maps code changes to affected documentation pages using docs-map.json.",
|
|
27251
28722
|
inputSchema: {
|
|
27252
28723
|
type: "object",
|
|
@@ -27265,7 +28736,7 @@ function getDocsToolDefinitions() {
|
|
|
27265
28736
|
}
|
|
27266
28737
|
},
|
|
27267
28738
|
{
|
|
27268
|
-
name:
|
|
28739
|
+
name: p4("docs_coverage"),
|
|
27269
28740
|
description: "Report docs coverage: which help pages exist, have content, and are up-to-date. Identifies documentation gaps.",
|
|
27270
28741
|
inputSchema: {
|
|
27271
28742
|
type: "object",
|
|
@@ -27289,15 +28760,15 @@ function handleDocsToolCall(name, args2) {
|
|
|
27289
28760
|
case "docs_coverage":
|
|
27290
28761
|
return handleDocsCoverage(args2);
|
|
27291
28762
|
default:
|
|
27292
|
-
return
|
|
28763
|
+
return text3(`Unknown docs tool: ${name}`);
|
|
27293
28764
|
}
|
|
27294
28765
|
}
|
|
27295
28766
|
function loadDocsMap() {
|
|
27296
28767
|
const mapPath = getResolvedPaths().docsMapPath;
|
|
27297
|
-
if (!
|
|
28768
|
+
if (!existsSync54(mapPath)) {
|
|
27298
28769
|
throw new Error(`docs-map.json not found at ${mapPath}`);
|
|
27299
28770
|
}
|
|
27300
|
-
return JSON.parse(
|
|
28771
|
+
return JSON.parse(readFileSync46(mapPath, "utf-8"));
|
|
27301
28772
|
}
|
|
27302
28773
|
function matchesPattern(filePath, pattern) {
|
|
27303
28774
|
const regexStr = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\{\{GLOBSTAR\}\}/g, ".*");
|
|
@@ -27368,12 +28839,12 @@ function extractFrontmatter(content) {
|
|
|
27368
28839
|
function extractProcedureNames(routerPath) {
|
|
27369
28840
|
const root = getProjectRoot();
|
|
27370
28841
|
const absPath = ensureWithinRoot(resolve34(getResolvedPaths().srcDir, "..", routerPath), root);
|
|
27371
|
-
if (!
|
|
28842
|
+
if (!existsSync54(absPath)) {
|
|
27372
28843
|
const altPath = ensureWithinRoot(resolve34(getResolvedPaths().srcDir, "../server/api/routers", basename9(routerPath)), root);
|
|
27373
|
-
if (!
|
|
27374
|
-
return extractProcedureNamesFromContent(
|
|
28844
|
+
if (!existsSync54(altPath)) return [];
|
|
28845
|
+
return extractProcedureNamesFromContent(readFileSync46(altPath, "utf-8"));
|
|
27375
28846
|
}
|
|
27376
|
-
return extractProcedureNamesFromContent(
|
|
28847
|
+
return extractProcedureNamesFromContent(readFileSync46(absPath, "utf-8"));
|
|
27377
28848
|
}
|
|
27378
28849
|
function extractProcedureNamesFromContent(content) {
|
|
27379
28850
|
const procRegex = /\.(?:query|mutation)\s*\(/g;
|
|
@@ -27399,12 +28870,12 @@ function handleDocsAudit(args2) {
|
|
|
27399
28870
|
const changedFiles = args2.changed_files;
|
|
27400
28871
|
const commitMessage = args2.commit_message || "";
|
|
27401
28872
|
if (!changedFiles || changedFiles.length === 0) {
|
|
27402
|
-
return
|
|
28873
|
+
return text3(JSON.stringify({ affectedPages: [], summary: "No changed files provided." }));
|
|
27403
28874
|
}
|
|
27404
28875
|
const docsMap = loadDocsMap();
|
|
27405
28876
|
const affectedMappings = findAffectedMappings(docsMap, changedFiles);
|
|
27406
28877
|
if (affectedMappings.size === 0) {
|
|
27407
|
-
return
|
|
28878
|
+
return text3(JSON.stringify({
|
|
27408
28879
|
affectedPages: [],
|
|
27409
28880
|
summary: `0 help pages affected by ${changedFiles.length} changed files. No docs update needed.`
|
|
27410
28881
|
}));
|
|
@@ -27414,7 +28885,7 @@ function handleDocsAudit(args2) {
|
|
|
27414
28885
|
const mapping = docsMap.mappings.find((m3) => m3.id === mappingId);
|
|
27415
28886
|
if (!mapping) continue;
|
|
27416
28887
|
const helpPagePath = ensureWithinRoot(resolve34(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
|
|
27417
|
-
if (!
|
|
28888
|
+
if (!existsSync54(helpPagePath)) {
|
|
27418
28889
|
results.push({
|
|
27419
28890
|
helpPage: mapping.helpPage,
|
|
27420
28891
|
mappingId,
|
|
@@ -27426,7 +28897,7 @@ function handleDocsAudit(args2) {
|
|
|
27426
28897
|
});
|
|
27427
28898
|
continue;
|
|
27428
28899
|
}
|
|
27429
|
-
const content =
|
|
28900
|
+
const content = readFileSync46(helpPagePath, "utf-8");
|
|
27430
28901
|
const sections = extractSections(content);
|
|
27431
28902
|
const frontmatter = extractFrontmatter(content);
|
|
27432
28903
|
const staleReasons = [];
|
|
@@ -27467,8 +28938,8 @@ function handleDocsAudit(args2) {
|
|
|
27467
28938
|
for (const [guideName, parentId] of Object.entries(docsMap.userGuideInheritance.examples)) {
|
|
27468
28939
|
if (parentId === mappingId) {
|
|
27469
28940
|
const guidePath = ensureWithinRoot(resolve34(getResolvedPaths().helpSitePath, `pages/user-guides/${guideName}/index.mdx`), getProjectRoot());
|
|
27470
|
-
if (
|
|
27471
|
-
const guideContent =
|
|
28941
|
+
if (existsSync54(guidePath)) {
|
|
28942
|
+
const guideContent = readFileSync46(guidePath, "utf-8");
|
|
27472
28943
|
const guideFrontmatter = extractFrontmatter(guideContent);
|
|
27473
28944
|
if (!guideFrontmatter?.lastVerified || status === "STALE") {
|
|
27474
28945
|
results.push({
|
|
@@ -27492,7 +28963,7 @@ function handleDocsAudit(args2) {
|
|
|
27492
28963
|
affectedPages: results,
|
|
27493
28964
|
summary: `${results.length} pages checked: ${staleCount} STALE, ${newCount} NEW, ${okCount} OK. ${staleCount + newCount > 0 ? `${staleCount + newCount} pages need updates.` : "All docs are current."}`
|
|
27494
28965
|
};
|
|
27495
|
-
return
|
|
28966
|
+
return text3(JSON.stringify(report, null, 2));
|
|
27496
28967
|
}
|
|
27497
28968
|
function handleDocsCoverage(args2) {
|
|
27498
28969
|
const filterDomain = args2.domain;
|
|
@@ -27502,13 +28973,13 @@ function handleDocsCoverage(args2) {
|
|
|
27502
28973
|
const mappings = filterDomain ? docsMap.mappings.filter((m3) => m3.id === filterDomain) : docsMap.mappings;
|
|
27503
28974
|
for (const mapping of mappings) {
|
|
27504
28975
|
const helpPagePath = ensureWithinRoot(resolve34(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
|
|
27505
|
-
const exists =
|
|
28976
|
+
const exists = existsSync54(helpPagePath);
|
|
27506
28977
|
let hasContent = false;
|
|
27507
28978
|
let lineCount = 0;
|
|
27508
28979
|
let lastVerified = null;
|
|
27509
28980
|
let status = null;
|
|
27510
28981
|
if (exists) {
|
|
27511
|
-
const content =
|
|
28982
|
+
const content = readFileSync46(helpPagePath, "utf-8");
|
|
27512
28983
|
lineCount = content.split("\n").length;
|
|
27513
28984
|
hasContent = lineCount > 10;
|
|
27514
28985
|
const frontmatter = extractFrontmatter(content);
|
|
@@ -27559,9 +29030,9 @@ function handleDocsCoverage(args2) {
|
|
|
27559
29030
|
const icon = entry.hasContent ? "OK" : entry.exists ? "THIN" : "MISSING";
|
|
27560
29031
|
lines.push(`- [${icon}] ${entry.id}: ${entry.helpPage}${verified}${pageStatus} (${entry.lineCount} lines)`);
|
|
27561
29032
|
}
|
|
27562
|
-
return
|
|
29033
|
+
return text3(lines.join("\n"));
|
|
27563
29034
|
}
|
|
27564
|
-
function
|
|
29035
|
+
function text3(content) {
|
|
27565
29036
|
return { content: [{ type: "text", text: content }] };
|
|
27566
29037
|
}
|
|
27567
29038
|
var init_docs_tools = __esm({
|
|
@@ -27573,7 +29044,7 @@ var init_docs_tools = __esm({
|
|
|
27573
29044
|
});
|
|
27574
29045
|
|
|
27575
29046
|
// src/observability-tools.ts
|
|
27576
|
-
function
|
|
29047
|
+
function p5(baseName) {
|
|
27577
29048
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
27578
29049
|
}
|
|
27579
29050
|
function isObservabilityTool(name) {
|
|
@@ -27585,7 +29056,7 @@ function getObservabilityToolDefinitions() {
|
|
|
27585
29056
|
return [
|
|
27586
29057
|
// P3-001: session_replay
|
|
27587
29058
|
{
|
|
27588
|
-
name:
|
|
29059
|
+
name: p5("session_replay"),
|
|
27589
29060
|
description: "Replay a past session as a chronological conversation with user prompts, assistant responses, and optional tool call details. Requires conversation data captured by the session-end hook.",
|
|
27590
29061
|
inputSchema: {
|
|
27591
29062
|
type: "object",
|
|
@@ -27600,7 +29071,7 @@ function getObservabilityToolDefinitions() {
|
|
|
27600
29071
|
},
|
|
27601
29072
|
// P3-002: prompt_analysis
|
|
27602
29073
|
{
|
|
27603
|
-
name:
|
|
29074
|
+
name: p5("prompt_analysis"),
|
|
27604
29075
|
description: "Search and analyze prompts across sessions using FTS5 full-text search on conversation_turns_fts. Find prompts by keyword, filter by date range or complexity (tool call count).",
|
|
27605
29076
|
inputSchema: {
|
|
27606
29077
|
type: "object",
|
|
@@ -27617,7 +29088,7 @@ function getObservabilityToolDefinitions() {
|
|
|
27617
29088
|
},
|
|
27618
29089
|
// P3-003: tool_patterns
|
|
27619
29090
|
{
|
|
27620
|
-
name:
|
|
29091
|
+
name: p5("tool_patterns"),
|
|
27621
29092
|
description: "Analyze tool usage patterns across sessions. Shows counts, success rates, average I/O sizes per tool. Can group by tool name, session, or day.",
|
|
27622
29093
|
inputSchema: {
|
|
27623
29094
|
type: "object",
|
|
@@ -27632,7 +29103,7 @@ function getObservabilityToolDefinitions() {
|
|
|
27632
29103
|
},
|
|
27633
29104
|
// P3-004: session_stats
|
|
27634
29105
|
{
|
|
27635
|
-
name:
|
|
29106
|
+
name: p5("session_stats"),
|
|
27636
29107
|
description: "Get per-session statistics: turn count, tool call breakdown, token usage, duration. Includes database size monitoring for observability data.",
|
|
27637
29108
|
inputSchema: {
|
|
27638
29109
|
type: "object",
|
|
@@ -27659,15 +29130,15 @@ function handleObservabilityToolCall(name, args2, memoryDb) {
|
|
|
27659
29130
|
case "session_stats":
|
|
27660
29131
|
return handleSessionStats(args2, memoryDb);
|
|
27661
29132
|
default:
|
|
27662
|
-
return
|
|
29133
|
+
return text4(`Unknown observability tool: ${name}`);
|
|
27663
29134
|
}
|
|
27664
29135
|
} catch (error) {
|
|
27665
|
-
return
|
|
29136
|
+
return text4(`Error in ${name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
27666
29137
|
}
|
|
27667
29138
|
}
|
|
27668
29139
|
function handleSessionReplay(args2, db) {
|
|
27669
29140
|
const sessionId = args2.session_id;
|
|
27670
|
-
if (!sessionId) return
|
|
29141
|
+
if (!sessionId) return text4("Error: session_id is required");
|
|
27671
29142
|
const includeToolCalls = args2.include_tool_calls ?? false;
|
|
27672
29143
|
const turns = getConversationTurns(db, sessionId, {
|
|
27673
29144
|
turnFrom: args2.turn_from,
|
|
@@ -27675,7 +29146,7 @@ function handleSessionReplay(args2, db) {
|
|
|
27675
29146
|
includeToolCalls
|
|
27676
29147
|
});
|
|
27677
29148
|
if (turns.length === 0) {
|
|
27678
|
-
return
|
|
29149
|
+
return text4(`No conversation turns found for session ${sessionId.slice(0, 8)}...
|
|
27679
29150
|
Note: Conversation data is captured by the session-end hook. Only sessions that have ended will have replay data.`);
|
|
27680
29151
|
}
|
|
27681
29152
|
const lines = [`## Session Replay: ${sessionId.slice(0, 12)}...`, `Turns: ${turns.length}`, ""];
|
|
@@ -27707,7 +29178,7 @@ Note: Conversation data is captured by the session-end hook. Only sessions that
|
|
|
27707
29178
|
lines.push("---");
|
|
27708
29179
|
lines.push("");
|
|
27709
29180
|
}
|
|
27710
|
-
return
|
|
29181
|
+
return text4(lines.join("\n"));
|
|
27711
29182
|
}
|
|
27712
29183
|
function handlePromptAnalysis(args2, db) {
|
|
27713
29184
|
const query = args2.query;
|
|
@@ -27716,7 +29187,7 @@ function handlePromptAnalysis(args2, db) {
|
|
|
27716
29187
|
"SELECT ct.session_id, ct.turn_number, ct.user_prompt, ct.tool_call_count, ct.response_tokens, ct.created_at FROM conversation_turns ct ORDER BY ct.created_at_epoch DESC LIMIT ?"
|
|
27717
29188
|
).all(args2.limit ?? 20);
|
|
27718
29189
|
if (recentTurns.length === 0) {
|
|
27719
|
-
return
|
|
29190
|
+
return text4("No conversation turns recorded yet.");
|
|
27720
29191
|
}
|
|
27721
29192
|
const lines2 = ["## Recent Prompts", ""];
|
|
27722
29193
|
lines2.push("| Session | Turn | Prompt (truncated) | Tools | Response Tokens | Date |");
|
|
@@ -27724,7 +29195,7 @@ function handlePromptAnalysis(args2, db) {
|
|
|
27724
29195
|
for (const t2 of recentTurns) {
|
|
27725
29196
|
lines2.push(`| ${t2.session_id.slice(0, 8)}... | ${t2.turn_number} | ${t2.user_prompt.slice(0, 60).replace(/\|/g, "\\|")} | ${t2.tool_call_count} | ${t2.response_tokens ?? "-"} | ${t2.created_at.split("T")[0]} |`);
|
|
27726
29197
|
}
|
|
27727
|
-
return
|
|
29198
|
+
return text4(lines2.join("\n"));
|
|
27728
29199
|
}
|
|
27729
29200
|
const results = searchConversationTurns(db, query, {
|
|
27730
29201
|
sessionId: args2.session_id,
|
|
@@ -27734,7 +29205,7 @@ function handlePromptAnalysis(args2, db) {
|
|
|
27734
29205
|
limit: args2.limit
|
|
27735
29206
|
});
|
|
27736
29207
|
if (results.length === 0) {
|
|
27737
|
-
return
|
|
29208
|
+
return text4(`No prompts found matching "${query}".`);
|
|
27738
29209
|
}
|
|
27739
29210
|
const lines = [`## Prompt Search: "${query}" (${results.length} results)`, ""];
|
|
27740
29211
|
lines.push("| Session | Turn | Prompt (truncated) | Tools | Response Tokens | Date |");
|
|
@@ -27742,7 +29213,7 @@ function handlePromptAnalysis(args2, db) {
|
|
|
27742
29213
|
for (const r2 of results) {
|
|
27743
29214
|
lines.push(`| ${r2.session_id.slice(0, 8)}... | ${r2.turn_number} | ${r2.user_prompt.slice(0, 60).replace(/\|/g, "\\|")} | ${r2.tool_call_count} | ${r2.response_tokens ?? "-"} | ${r2.created_at.split("T")[0]} |`);
|
|
27744
29215
|
}
|
|
27745
|
-
return
|
|
29216
|
+
return text4(lines.join("\n"));
|
|
27746
29217
|
}
|
|
27747
29218
|
function handleToolPatterns(args2, db) {
|
|
27748
29219
|
const groupBy = args2.group_by ?? "tool";
|
|
@@ -27753,7 +29224,7 @@ function handleToolPatterns(args2, db) {
|
|
|
27753
29224
|
groupBy
|
|
27754
29225
|
});
|
|
27755
29226
|
if (patterns.length === 0) {
|
|
27756
|
-
return
|
|
29227
|
+
return text4("No tool usage data recorded yet.");
|
|
27757
29228
|
}
|
|
27758
29229
|
const lines = [`## Tool Usage Patterns (grouped by ${groupBy})`, ""];
|
|
27759
29230
|
switch (groupBy) {
|
|
@@ -27783,7 +29254,7 @@ function handleToolPatterns(args2, db) {
|
|
|
27783
29254
|
}
|
|
27784
29255
|
break;
|
|
27785
29256
|
}
|
|
27786
|
-
return
|
|
29257
|
+
return text4(lines.join("\n"));
|
|
27787
29258
|
}
|
|
27788
29259
|
function handleSessionStats(args2, db) {
|
|
27789
29260
|
const stats = getSessionStats(db, {
|
|
@@ -27791,7 +29262,7 @@ function handleSessionStats(args2, db) {
|
|
|
27791
29262
|
limit: args2.limit
|
|
27792
29263
|
});
|
|
27793
29264
|
if (stats.length === 0) {
|
|
27794
|
-
return
|
|
29265
|
+
return text4("No session stats available.");
|
|
27795
29266
|
}
|
|
27796
29267
|
const lines = ["## Session Statistics", ""];
|
|
27797
29268
|
if (args2.session_id) {
|
|
@@ -27828,9 +29299,9 @@ function handleSessionStats(args2, db) {
|
|
|
27828
29299
|
lines.push(`- Tool call details: ${dbSize.tool_call_details_count}`);
|
|
27829
29300
|
lines.push(`- Observations: ${dbSize.observations_count}`);
|
|
27830
29301
|
lines.push(`- Database size: ~${dbSize.estimated_size_mb} MB`);
|
|
27831
|
-
return
|
|
29302
|
+
return text4(lines.join("\n"));
|
|
27832
29303
|
}
|
|
27833
|
-
function
|
|
29304
|
+
function text4(content) {
|
|
27834
29305
|
return { content: [{ type: "text", text: content }] };
|
|
27835
29306
|
}
|
|
27836
29307
|
var OBSERVABILITY_BASE_NAMES;
|
|
@@ -27849,7 +29320,7 @@ var init_observability_tools = __esm({
|
|
|
27849
29320
|
});
|
|
27850
29321
|
|
|
27851
29322
|
// src/sentinel-db.ts
|
|
27852
|
-
import { existsSync as
|
|
29323
|
+
import { existsSync as existsSync55 } from "fs";
|
|
27853
29324
|
import { resolve as resolve35 } from "path";
|
|
27854
29325
|
function parsePortalScope(raw) {
|
|
27855
29326
|
if (!raw) return [];
|
|
@@ -28087,22 +29558,22 @@ function validateFeatures(db, domainFilter) {
|
|
|
28087
29558
|
const missingPages = [];
|
|
28088
29559
|
for (const comp of components) {
|
|
28089
29560
|
const absPath = resolve35(PROJECT_ROOT, comp.component_file);
|
|
28090
|
-
if (!
|
|
29561
|
+
if (!existsSync55(absPath)) {
|
|
28091
29562
|
missingComponents.push(comp.component_file);
|
|
28092
29563
|
}
|
|
28093
29564
|
}
|
|
28094
29565
|
for (const proc of procedures) {
|
|
28095
29566
|
const routerPath = resolve35(PROJECT_ROOT, `src/server/api/routers/${proc.router_name}.ts`);
|
|
28096
|
-
if (!
|
|
29567
|
+
if (!existsSync55(routerPath)) {
|
|
28097
29568
|
missingProcedures.push({ router: proc.router_name, procedure: proc.procedure_name });
|
|
28098
29569
|
}
|
|
28099
29570
|
}
|
|
28100
29571
|
for (const page of pages) {
|
|
28101
29572
|
const routeToPath = page.page_route.replace(/^\/(portal-[^/]+\/)?/, "src/app/").replace(/\/$/, "") + "/page.tsx";
|
|
28102
29573
|
const absPath = resolve35(PROJECT_ROOT, routeToPath);
|
|
28103
|
-
if (page.page_route.startsWith("/") && !
|
|
29574
|
+
if (page.page_route.startsWith("/") && !existsSync55(absPath)) {
|
|
28104
29575
|
const altPath = resolve35(PROJECT_ROOT, `src/app${page.page_route}/page.tsx`);
|
|
28105
|
-
if (!
|
|
29576
|
+
if (!existsSync55(altPath)) {
|
|
28106
29577
|
missingPages.push(page.page_route);
|
|
28107
29578
|
}
|
|
28108
29579
|
}
|
|
@@ -28198,17 +29669,17 @@ var init_sentinel_db = __esm({
|
|
|
28198
29669
|
});
|
|
28199
29670
|
|
|
28200
29671
|
// src/sentinel-tools.ts
|
|
28201
|
-
function
|
|
29672
|
+
function p6(baseName) {
|
|
28202
29673
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
28203
29674
|
}
|
|
28204
|
-
function
|
|
29675
|
+
function text5(content) {
|
|
28205
29676
|
return { content: [{ type: "text", text: content }] };
|
|
28206
29677
|
}
|
|
28207
29678
|
function getSentinelToolDefinitions() {
|
|
28208
29679
|
return [
|
|
28209
29680
|
// P2-001: sentinel_search
|
|
28210
29681
|
{
|
|
28211
|
-
name:
|
|
29682
|
+
name: p6("sentinel_search"),
|
|
28212
29683
|
description: "Search and list features in the Sentinel feature registry. Supports FTS5 full-text search and domain/status/portal filters.",
|
|
28213
29684
|
inputSchema: {
|
|
28214
29685
|
type: "object",
|
|
@@ -28225,7 +29696,7 @@ function getSentinelToolDefinitions() {
|
|
|
28225
29696
|
},
|
|
28226
29697
|
// P2-002: sentinel_detail
|
|
28227
29698
|
{
|
|
28228
|
-
name:
|
|
29699
|
+
name: p6("sentinel_detail"),
|
|
28229
29700
|
description: "Get full feature details including all linked components, procedures, pages, dependencies, and changelog.",
|
|
28230
29701
|
inputSchema: {
|
|
28231
29702
|
type: "object",
|
|
@@ -28238,7 +29709,7 @@ function getSentinelToolDefinitions() {
|
|
|
28238
29709
|
},
|
|
28239
29710
|
// P2-003: sentinel_impact
|
|
28240
29711
|
{
|
|
28241
|
-
name:
|
|
29712
|
+
name: p6("sentinel_impact"),
|
|
28242
29713
|
description: "Pre-deletion impact analysis. Shows which features would be orphaned, degraded, or unaffected if specified files are deleted. BLOCKS if critical features would be orphaned.",
|
|
28243
29714
|
inputSchema: {
|
|
28244
29715
|
type: "object",
|
|
@@ -28254,7 +29725,7 @@ function getSentinelToolDefinitions() {
|
|
|
28254
29725
|
},
|
|
28255
29726
|
// P2-004: sentinel_validate
|
|
28256
29727
|
{
|
|
28257
|
-
name:
|
|
29728
|
+
name: p6("sentinel_validate"),
|
|
28258
29729
|
description: "Validate all active features have living implementation files. Reports alive, orphaned, and degraded features.",
|
|
28259
29730
|
inputSchema: {
|
|
28260
29731
|
type: "object",
|
|
@@ -28267,7 +29738,7 @@ function getSentinelToolDefinitions() {
|
|
|
28267
29738
|
},
|
|
28268
29739
|
// P2-005: sentinel_register
|
|
28269
29740
|
{
|
|
28270
|
-
name:
|
|
29741
|
+
name: p6("sentinel_register"),
|
|
28271
29742
|
description: "Register or update a feature in the sentinel registry. Links components, procedures, and pages.",
|
|
28272
29743
|
inputSchema: {
|
|
28273
29744
|
type: "object",
|
|
@@ -28324,7 +29795,7 @@ function getSentinelToolDefinitions() {
|
|
|
28324
29795
|
},
|
|
28325
29796
|
// P2-006: sentinel_parity
|
|
28326
29797
|
{
|
|
28327
|
-
name:
|
|
29798
|
+
name: p6("sentinel_parity"),
|
|
28328
29799
|
description: "Compare two sets of files for feature parity. Shows features in old but not new (GAPs), features in both (DONE), and features only in new (NEW).",
|
|
28329
29800
|
inputSchema: {
|
|
28330
29801
|
type: "object",
|
|
@@ -28362,7 +29833,7 @@ function handleSentinelToolCall(name, args2, dataDb) {
|
|
|
28362
29833
|
case "sentinel_parity":
|
|
28363
29834
|
return handleParityCheck(args2, dataDb);
|
|
28364
29835
|
default:
|
|
28365
|
-
return
|
|
29836
|
+
return text5(`Unknown sentinel tool: ${name}`);
|
|
28366
29837
|
}
|
|
28367
29838
|
}
|
|
28368
29839
|
function handleSearch2(args2, db) {
|
|
@@ -28385,7 +29856,7 @@ function handleSearch2(args2, db) {
|
|
|
28385
29856
|
lines.push("");
|
|
28386
29857
|
if (results.length === 0) {
|
|
28387
29858
|
lines.push("No features found matching criteria.");
|
|
28388
|
-
return
|
|
29859
|
+
return text5(lines.join("\n"));
|
|
28389
29860
|
}
|
|
28390
29861
|
let currentDomain = "";
|
|
28391
29862
|
for (const f2 of results) {
|
|
@@ -28397,17 +29868,17 @@ function handleSearch2(args2, db) {
|
|
|
28397
29868
|
const priority = f2.priority === "critical" ? " [CRITICAL]" : "";
|
|
28398
29869
|
lines.push(`- **${f2.feature_key}** ${sub}${priority}: ${f2.title} (${f2.component_count}C/${f2.procedure_count}P/${f2.page_count}R) [${f2.status}]`);
|
|
28399
29870
|
}
|
|
28400
|
-
return
|
|
29871
|
+
return text5(lines.join("\n"));
|
|
28401
29872
|
}
|
|
28402
29873
|
function handleDetail2(args2, db) {
|
|
28403
29874
|
const key = args2.feature_key;
|
|
28404
29875
|
const id = args2.feature_id;
|
|
28405
29876
|
if (!key && !id) {
|
|
28406
|
-
return
|
|
29877
|
+
return text5("Error: Provide either feature_key or feature_id");
|
|
28407
29878
|
}
|
|
28408
29879
|
const detail = getFeatureDetail(db, id || key);
|
|
28409
29880
|
if (!detail) {
|
|
28410
|
-
return
|
|
29881
|
+
return text5(`Feature not found: ${key || id}`);
|
|
28411
29882
|
}
|
|
28412
29883
|
const lines = [];
|
|
28413
29884
|
lines.push(`## Feature: ${detail.feature_key}`);
|
|
@@ -28453,12 +29924,12 @@ function handleDetail2(args2, db) {
|
|
|
28453
29924
|
lines.push(`- [${c2.created_at}] ${c2.change_type}: ${c2.change_detail || "(no detail)"}${c2.commit_hash ? " @" + c2.commit_hash.substring(0, 8) : ""}`);
|
|
28454
29925
|
}
|
|
28455
29926
|
}
|
|
28456
|
-
return
|
|
29927
|
+
return text5(lines.join("\n"));
|
|
28457
29928
|
}
|
|
28458
29929
|
function handleImpact(args2, db) {
|
|
28459
29930
|
const files = args2.files;
|
|
28460
29931
|
if (!files || files.length === 0) {
|
|
28461
|
-
return
|
|
29932
|
+
return text5("Error: Provide files array");
|
|
28462
29933
|
}
|
|
28463
29934
|
const report = getFeatureImpact(db, files);
|
|
28464
29935
|
const lines = [];
|
|
@@ -28494,7 +29965,7 @@ function handleImpact(args2, db) {
|
|
|
28494
29965
|
}
|
|
28495
29966
|
lines.push("");
|
|
28496
29967
|
}
|
|
28497
|
-
return
|
|
29968
|
+
return text5(lines.join("\n"));
|
|
28498
29969
|
}
|
|
28499
29970
|
function handleValidate(args2, db) {
|
|
28500
29971
|
const domain = args2.domain;
|
|
@@ -28539,14 +30010,14 @@ function handleValidate(args2, db) {
|
|
|
28539
30010
|
}
|
|
28540
30011
|
const result = report.orphaned === 0 ? "PASS" : "FAIL";
|
|
28541
30012
|
lines.push(`### RESULT: ${result}`);
|
|
28542
|
-
return
|
|
30013
|
+
return text5(lines.join("\n"));
|
|
28543
30014
|
}
|
|
28544
30015
|
function handleRegister(args2, db) {
|
|
28545
30016
|
const featureKey = args2.feature_key;
|
|
28546
30017
|
const domain = args2.domain;
|
|
28547
30018
|
const title = args2.title;
|
|
28548
30019
|
if (!featureKey || !domain || !title) {
|
|
28549
|
-
return
|
|
30020
|
+
return text5("Error: feature_key, domain, and title are required");
|
|
28550
30021
|
}
|
|
28551
30022
|
const featureId = upsertFeature(db, {
|
|
28552
30023
|
feature_key: featureKey,
|
|
@@ -28576,15 +30047,15 @@ function handleRegister(args2, db) {
|
|
|
28576
30047
|
linkPage(db, featureId, p19.route, p19.portal);
|
|
28577
30048
|
}
|
|
28578
30049
|
}
|
|
28579
|
-
logChange(db, featureId, "created", `Registered via ${
|
|
28580
|
-
return
|
|
30050
|
+
logChange(db, featureId, "created", `Registered via ${p6("sentinel_register")}`);
|
|
30051
|
+
return text5(`Feature registered: ${featureKey} (ID: ${featureId})
|
|
28581
30052
|
Components: ${components?.length || 0}, Procedures: ${procedures?.length || 0}, Pages: ${pages?.length || 0}`);
|
|
28582
30053
|
}
|
|
28583
30054
|
function handleParityCheck(args2, db) {
|
|
28584
30055
|
const oldFiles = args2.old_files;
|
|
28585
30056
|
const newFiles = args2.new_files;
|
|
28586
30057
|
if (!oldFiles || !newFiles) {
|
|
28587
|
-
return
|
|
30058
|
+
return text5("Error: Provide both old_files and new_files arrays");
|
|
28588
30059
|
}
|
|
28589
30060
|
const report = checkParity(db, oldFiles, newFiles);
|
|
28590
30061
|
const lines = [];
|
|
@@ -28616,7 +30087,7 @@ function handleParityCheck(args2, db) {
|
|
|
28616
30087
|
}
|
|
28617
30088
|
const result = report.gaps.length === 0 ? "PASS" : `FAIL (${report.gaps.length} gaps)`;
|
|
28618
30089
|
lines.push(`### RESULT: ${result}`);
|
|
28619
|
-
return
|
|
30090
|
+
return text5(lines.join("\n"));
|
|
28620
30091
|
}
|
|
28621
30092
|
var init_sentinel_tools = __esm({
|
|
28622
30093
|
"src/sentinel-tools.ts"() {
|
|
@@ -28628,8 +30099,8 @@ var init_sentinel_tools = __esm({
|
|
|
28628
30099
|
});
|
|
28629
30100
|
|
|
28630
30101
|
// src/sentinel-scanner.ts
|
|
28631
|
-
import { readFileSync as
|
|
28632
|
-
import { resolve as resolve36, join as
|
|
30102
|
+
import { readFileSync as readFileSync47, existsSync as existsSync56, readdirSync as readdirSync26, statSync as statSync19 } from "fs";
|
|
30103
|
+
import { resolve as resolve36, join as join34, basename as basename10, dirname as dirname25, relative as relative13 } from "path";
|
|
28633
30104
|
function inferDomain(filePath) {
|
|
28634
30105
|
const domains = getConfig().domains;
|
|
28635
30106
|
const path = filePath.toLowerCase();
|
|
@@ -28761,7 +30232,7 @@ function scanComponentExports(dataDb) {
|
|
|
28761
30232
|
const componentsBase = config.paths.components ?? config.paths.source + "/components";
|
|
28762
30233
|
const componentDirs = [];
|
|
28763
30234
|
const basePath = resolve36(projectRoot, componentsBase);
|
|
28764
|
-
if (
|
|
30235
|
+
if (existsSync56(basePath)) {
|
|
28765
30236
|
try {
|
|
28766
30237
|
const entries = readdirSync26(basePath, { withFileTypes: true });
|
|
28767
30238
|
for (const entry of entries) {
|
|
@@ -28774,11 +30245,11 @@ function scanComponentExports(dataDb) {
|
|
|
28774
30245
|
}
|
|
28775
30246
|
for (const dir of componentDirs) {
|
|
28776
30247
|
const absDir = resolve36(projectRoot, dir);
|
|
28777
|
-
if (!
|
|
30248
|
+
if (!existsSync56(absDir)) continue;
|
|
28778
30249
|
const files = walkDir2(absDir).filter((f2) => f2.endsWith(".tsx") || f2.endsWith(".ts"));
|
|
28779
30250
|
for (const file of files) {
|
|
28780
|
-
const relPath =
|
|
28781
|
-
const source =
|
|
30251
|
+
const relPath = relative13(projectRoot, file);
|
|
30252
|
+
const source = readFileSync47(file, "utf-8");
|
|
28782
30253
|
const annotations = parseFeatureAnnotations(source);
|
|
28783
30254
|
if (annotations.length > 0) {
|
|
28784
30255
|
for (const ann of annotations) {
|
|
@@ -28804,7 +30275,7 @@ function scanComponentExports(dataDb) {
|
|
|
28804
30275
|
if (hasHandlers && exportMatch) {
|
|
28805
30276
|
const componentName = exportMatch[1];
|
|
28806
30277
|
const domain = inferDomain(relPath);
|
|
28807
|
-
const subdomain = basename10(
|
|
30278
|
+
const subdomain = basename10(dirname25(relPath));
|
|
28808
30279
|
const featureKey = `component.${subdomain}.${componentName.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "")}`;
|
|
28809
30280
|
if (!annotations.some((a2) => a2.featureKey === featureKey)) {
|
|
28810
30281
|
features.push({
|
|
@@ -28829,7 +30300,7 @@ function walkDir2(dir) {
|
|
|
28829
30300
|
try {
|
|
28830
30301
|
const entries = readdirSync26(dir);
|
|
28831
30302
|
for (const entry of entries) {
|
|
28832
|
-
const fullPath =
|
|
30303
|
+
const fullPath = join34(dir, entry);
|
|
28833
30304
|
try {
|
|
28834
30305
|
const stat = statSync19(fullPath);
|
|
28835
30306
|
if (stat.isDirectory()) {
|
|
@@ -28900,7 +30371,7 @@ var init_sentinel_scanner = __esm({
|
|
|
28900
30371
|
});
|
|
28901
30372
|
|
|
28902
30373
|
// src/analytics.ts
|
|
28903
|
-
function
|
|
30374
|
+
function p7(baseName) {
|
|
28904
30375
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
28905
30376
|
}
|
|
28906
30377
|
function getWeights() {
|
|
@@ -28968,7 +30439,7 @@ function backfillQualityScores(db) {
|
|
|
28968
30439
|
function getAnalyticsToolDefinitions() {
|
|
28969
30440
|
return [
|
|
28970
30441
|
{
|
|
28971
|
-
name:
|
|
30442
|
+
name: p7("quality_score"),
|
|
28972
30443
|
description: "Calculate and store quality score for a session based on observations. Shows breakdown by category.",
|
|
28973
30444
|
inputSchema: {
|
|
28974
30445
|
type: "object",
|
|
@@ -28979,7 +30450,7 @@ function getAnalyticsToolDefinitions() {
|
|
|
28979
30450
|
}
|
|
28980
30451
|
},
|
|
28981
30452
|
{
|
|
28982
|
-
name:
|
|
30453
|
+
name: p7("quality_trend"),
|
|
28983
30454
|
description: "Quality trend over recent sessions. Shows score progression and identifies improving/declining areas.",
|
|
28984
30455
|
inputSchema: {
|
|
28985
30456
|
type: "object",
|
|
@@ -28990,7 +30461,7 @@ function getAnalyticsToolDefinitions() {
|
|
|
28990
30461
|
}
|
|
28991
30462
|
},
|
|
28992
30463
|
{
|
|
28993
|
-
name:
|
|
30464
|
+
name: p7("quality_report"),
|
|
28994
30465
|
description: "Comprehensive quality report with averages, trends, and recommendations.",
|
|
28995
30466
|
inputSchema: {
|
|
28996
30467
|
type: "object",
|
|
@@ -29019,17 +30490,17 @@ function handleAnalyticsToolCall(name, args2, memoryDb) {
|
|
|
29019
30490
|
case "quality_report":
|
|
29020
30491
|
return handleQualityReport(args2, memoryDb);
|
|
29021
30492
|
default:
|
|
29022
|
-
return
|
|
30493
|
+
return text6(`Unknown analytics tool: ${name}`);
|
|
29023
30494
|
}
|
|
29024
30495
|
} catch (error) {
|
|
29025
|
-
return
|
|
30496
|
+
return text6(`Error in ${name}: ${error instanceof Error ? error.message : String(error)}
|
|
29026
30497
|
|
|
29027
|
-
Usage: ${
|
|
30498
|
+
Usage: ${p7("quality_score")} { session_id: "..." }, ${p7("quality_trend")} { days: 30 }`);
|
|
29028
30499
|
}
|
|
29029
30500
|
}
|
|
29030
30501
|
function handleQualityScore(args2, db) {
|
|
29031
30502
|
const sessionId = args2.session_id;
|
|
29032
|
-
if (!sessionId) return
|
|
30503
|
+
if (!sessionId) return text6(`Usage: ${p7("quality_score")} { session_id: "abc123" } - Calculate quality score for a specific session.`);
|
|
29033
30504
|
const { score, breakdown } = calculateQualityScore(db, sessionId);
|
|
29034
30505
|
storeQualityScore(db, sessionId, score, breakdown);
|
|
29035
30506
|
const categories = getCategories();
|
|
@@ -29045,7 +30516,7 @@ function handleQualityScore(args2, db) {
|
|
|
29045
30516
|
const indicator = impact > 0 ? "+" : impact < 0 ? "" : " ";
|
|
29046
30517
|
lines.push(`| ${category} | ${indicator}${impact} |`);
|
|
29047
30518
|
}
|
|
29048
|
-
return
|
|
30519
|
+
return text6(lines.join("\n"));
|
|
29049
30520
|
}
|
|
29050
30521
|
function handleQualityTrend(args2, db, retried = false) {
|
|
29051
30522
|
const days = args2.days ?? 30;
|
|
@@ -29063,7 +30534,7 @@ function handleQualityTrend(args2, db, retried = false) {
|
|
|
29063
30534
|
return handleQualityTrend(args2, db, true);
|
|
29064
30535
|
}
|
|
29065
30536
|
}
|
|
29066
|
-
return
|
|
30537
|
+
return text6(`No quality scores found in the last ${days} days. Quality scores are calculated automatically from session observations (bugfixes, VR checks, incidents). Try: ${p7("quality_score")} { session_id: "..." } to score a specific session, or try a longer time range with { days: 90 }.`);
|
|
29067
30538
|
}
|
|
29068
30539
|
const avg = scores.reduce((sum, s) => sum + s.score, 0) / scores.length;
|
|
29069
30540
|
const recent = scores.slice(-5);
|
|
@@ -29082,7 +30553,7 @@ function handleQualityTrend(args2, db, retried = false) {
|
|
|
29082
30553
|
for (const s of scores.slice(-10)) {
|
|
29083
30554
|
lines.push(`| ${s.session_id.slice(0, 8)}... | ${s.score} | ${s.created_at} |`);
|
|
29084
30555
|
}
|
|
29085
|
-
return
|
|
30556
|
+
return text6(lines.join("\n"));
|
|
29086
30557
|
}
|
|
29087
30558
|
function handleQualityReport(args2, db, retried = false) {
|
|
29088
30559
|
const days = args2.days ?? 30;
|
|
@@ -29099,7 +30570,7 @@ function handleQualityReport(args2, db, retried = false) {
|
|
|
29099
30570
|
return handleQualityReport(args2, db, true);
|
|
29100
30571
|
}
|
|
29101
30572
|
}
|
|
29102
|
-
return
|
|
30573
|
+
return text6(`No quality data available for the last ${days} days. Quality scores are calculated from session observations (bugfixes, VR checks, incidents). Try: ${p7("quality_score")} { session_id: "..." } to score individual sessions first, or try a longer time range with { days: 90 }.`);
|
|
29103
30574
|
}
|
|
29104
30575
|
const avg = scores.reduce((sum, s) => sum + s.score, 0) / scores.length;
|
|
29105
30576
|
const max = Math.max(...scores.map((s) => s.score));
|
|
@@ -29156,9 +30627,9 @@ function handleQualityReport(args2, db, retried = false) {
|
|
|
29156
30627
|
if (avg >= 70) {
|
|
29157
30628
|
lines.push("- Quality is good. Maintain current practices and focus on consistency.");
|
|
29158
30629
|
}
|
|
29159
|
-
return
|
|
30630
|
+
return text6(lines.join("\n"));
|
|
29160
30631
|
}
|
|
29161
|
-
function
|
|
30632
|
+
function text6(content) {
|
|
29162
30633
|
return { content: [{ type: "text", text: content }] };
|
|
29163
30634
|
}
|
|
29164
30635
|
var DEFAULT_WEIGHTS, DEFAULT_CATEGORIES, ANALYTICS_BASE_NAMES;
|
|
@@ -29181,7 +30652,7 @@ var init_analytics = __esm({
|
|
|
29181
30652
|
});
|
|
29182
30653
|
|
|
29183
30654
|
// src/cost-tracker.ts
|
|
29184
|
-
function
|
|
30655
|
+
function p8(baseName) {
|
|
29185
30656
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
29186
30657
|
}
|
|
29187
30658
|
function getModelPricing() {
|
|
@@ -29190,7 +30661,7 @@ function getModelPricing() {
|
|
|
29190
30661
|
function getCostToolDefinitions() {
|
|
29191
30662
|
return [
|
|
29192
30663
|
{
|
|
29193
|
-
name:
|
|
30664
|
+
name: p8("cost_session"),
|
|
29194
30665
|
description: "Show cost breakdown for a session. Includes token counts, model pricing, and cost by category.",
|
|
29195
30666
|
inputSchema: {
|
|
29196
30667
|
type: "object",
|
|
@@ -29201,7 +30672,7 @@ function getCostToolDefinitions() {
|
|
|
29201
30672
|
}
|
|
29202
30673
|
},
|
|
29203
30674
|
{
|
|
29204
|
-
name:
|
|
30675
|
+
name: p8("cost_trend"),
|
|
29205
30676
|
description: "Cost trend over time. Shows daily/weekly spending and identifies cost drivers.",
|
|
29206
30677
|
inputSchema: {
|
|
29207
30678
|
type: "object",
|
|
@@ -29213,7 +30684,7 @@ function getCostToolDefinitions() {
|
|
|
29213
30684
|
}
|
|
29214
30685
|
},
|
|
29215
30686
|
{
|
|
29216
|
-
name:
|
|
30687
|
+
name: p8("cost_feature"),
|
|
29217
30688
|
description: "Cost attribution by feature. Shows which features consume the most tokens.",
|
|
29218
30689
|
inputSchema: {
|
|
29219
30690
|
type: "object",
|
|
@@ -29242,22 +30713,22 @@ function handleCostToolCall(name, args2, memoryDb) {
|
|
|
29242
30713
|
case "cost_feature":
|
|
29243
30714
|
return handleCostFeature(args2, memoryDb);
|
|
29244
30715
|
default:
|
|
29245
|
-
return
|
|
30716
|
+
return text7(`Unknown cost tool: ${name}`);
|
|
29246
30717
|
}
|
|
29247
30718
|
} catch (error) {
|
|
29248
|
-
return
|
|
30719
|
+
return text7(`Error in ${name}: ${error instanceof Error ? error.message : String(error)}
|
|
29249
30720
|
|
|
29250
|
-
Usage: ${
|
|
30721
|
+
Usage: ${p8("cost_session")} { session_id: "..." }, ${p8("cost_trend")} { days: 30 }`);
|
|
29251
30722
|
}
|
|
29252
30723
|
}
|
|
29253
30724
|
function handleCostSession(args2, db) {
|
|
29254
30725
|
const sessionId = args2.session_id;
|
|
29255
|
-
if (!sessionId) return
|
|
30726
|
+
if (!sessionId) return text7(`Usage: ${p8("cost_session")} { session_id: "abc123" } - Show cost breakdown for a specific session.`);
|
|
29256
30727
|
const cost = db.prepare(
|
|
29257
30728
|
"SELECT * FROM session_costs WHERE session_id = ? ORDER BY created_at DESC LIMIT 1"
|
|
29258
30729
|
).get(sessionId);
|
|
29259
30730
|
if (!cost) {
|
|
29260
|
-
return
|
|
30731
|
+
return text7(`No cost data found for session ${sessionId}. Cost data is recorded automatically when sessions end via the session-end hook. If the session is still active, data will appear after it completes. Try: ${p8("cost_trend")} { days: 30 } to see aggregate cost data instead.`);
|
|
29261
30732
|
}
|
|
29262
30733
|
const modelPricing = getModelPricing();
|
|
29263
30734
|
const mp = modelPricing[cost.model] ?? modelPricing["claude-sonnet-4-5"] ?? { input_per_million: 3, output_per_million: 15 };
|
|
@@ -29278,13 +30749,13 @@ function handleCostSession(args2, db) {
|
|
|
29278
30749
|
`| Cache Write | ${cost.cache_write_tokens.toLocaleString()} | $${cacheWriteCost.toFixed(4)} |`,
|
|
29279
30750
|
`| **Total** | ${cost.total_tokens.toLocaleString()} | **$${cost.estimated_cost_usd.toFixed(4)}** |`
|
|
29280
30751
|
];
|
|
29281
|
-
return
|
|
30752
|
+
return text7(lines.join("\n"));
|
|
29282
30753
|
}
|
|
29283
30754
|
function handleCostTrend(args2, db) {
|
|
29284
30755
|
const days = args2.days ?? 30;
|
|
29285
30756
|
const groupBy = args2.group_by ?? "day";
|
|
29286
30757
|
if (!["day", "week"].includes(groupBy)) {
|
|
29287
|
-
return
|
|
30758
|
+
return text7(`Invalid group_by value: "${groupBy}". Use "day" or "week". Example: ${p8("cost_trend")} { days: 30, group_by: "week" }`);
|
|
29288
30759
|
}
|
|
29289
30760
|
const sql = groupBy === "week" ? `SELECT strftime('%Y-W%W', created_at) as period,
|
|
29290
30761
|
COUNT(*) as sessions,
|
|
@@ -29305,7 +30776,7 @@ function handleCostTrend(args2, db) {
|
|
|
29305
30776
|
ORDER BY period ASC`;
|
|
29306
30777
|
const rows = db.prepare(sql).all(`-${days} days`);
|
|
29307
30778
|
if (rows.length === 0) {
|
|
29308
|
-
return
|
|
30779
|
+
return text7(`No cost data found in the last ${days} days. Cost tracking records token usage automatically at session end via hooks. Ensure session-end hooks are configured in your settings. Try: ${p8("cost_session")} { session_id: "..." } to check a specific session.`);
|
|
29309
30780
|
}
|
|
29310
30781
|
const totalCost = rows.reduce((sum, r2) => sum + r2.total_cost, 0);
|
|
29311
30782
|
const totalSessions = rows.reduce((sum, r2) => sum + r2.sessions, 0);
|
|
@@ -29323,7 +30794,7 @@ function handleCostTrend(args2, db) {
|
|
|
29323
30794
|
`| ${row.period} | ${row.sessions} | $${row.total_cost.toFixed(2)} | $${row.avg_cost.toFixed(4)} |`
|
|
29324
30795
|
);
|
|
29325
30796
|
}
|
|
29326
|
-
return
|
|
30797
|
+
return text7(lines.join("\n"));
|
|
29327
30798
|
}
|
|
29328
30799
|
function handleCostFeature(args2, db) {
|
|
29329
30800
|
const days = args2.days ?? 30;
|
|
@@ -29335,7 +30806,7 @@ function handleCostFeature(args2, db) {
|
|
|
29335
30806
|
ORDER BY total_cost DESC
|
|
29336
30807
|
`).all(`-${days} days`);
|
|
29337
30808
|
if (rows.length === 0) {
|
|
29338
|
-
return
|
|
30809
|
+
return text7(`No feature cost data found in the last ${days} days. Feature costs are attributed automatically when sessions work on registered features. Ensure features are registered, then costs are tracked per-session. Try: ${p8("cost_trend")} { days: 30 } for session-level cost trends instead.`);
|
|
29339
30810
|
}
|
|
29340
30811
|
const totalCost = rows.reduce((sum, r2) => sum + r2.total_cost, 0);
|
|
29341
30812
|
const lines = [
|
|
@@ -29351,9 +30822,9 @@ function handleCostFeature(args2, db) {
|
|
|
29351
30822
|
`| ${row.feature_key} | $${row.total_cost.toFixed(2)} | ${row.total_tokens.toLocaleString()} | ${row.entries} | ${pct}% |`
|
|
29352
30823
|
);
|
|
29353
30824
|
}
|
|
29354
|
-
return
|
|
30825
|
+
return text7(lines.join("\n"));
|
|
29355
30826
|
}
|
|
29356
|
-
function
|
|
30827
|
+
function text7(content) {
|
|
29357
30828
|
return { content: [{ type: "text", text: content }] };
|
|
29358
30829
|
}
|
|
29359
30830
|
var DEFAULT_MODEL_PRICING, COST_BASE_NAMES;
|
|
@@ -29373,7 +30844,7 @@ var init_cost_tracker = __esm({
|
|
|
29373
30844
|
});
|
|
29374
30845
|
|
|
29375
30846
|
// src/prompt-analyzer.ts
|
|
29376
|
-
function
|
|
30847
|
+
function p9(baseName) {
|
|
29377
30848
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
29378
30849
|
}
|
|
29379
30850
|
function categorizePrompt(promptText) {
|
|
@@ -29388,7 +30859,7 @@ function categorizePrompt(promptText) {
|
|
|
29388
30859
|
function getPromptToolDefinitions() {
|
|
29389
30860
|
return [
|
|
29390
30861
|
{
|
|
29391
|
-
name:
|
|
30862
|
+
name: p9("prompt_effectiveness"),
|
|
29392
30863
|
description: "Prompt effectiveness statistics by category. Shows success rates, average corrections needed, and best-performing prompt patterns.",
|
|
29393
30864
|
inputSchema: {
|
|
29394
30865
|
type: "object",
|
|
@@ -29403,7 +30874,7 @@ function getPromptToolDefinitions() {
|
|
|
29403
30874
|
}
|
|
29404
30875
|
},
|
|
29405
30876
|
{
|
|
29406
|
-
name:
|
|
30877
|
+
name: p9("prompt_suggestions"),
|
|
29407
30878
|
description: "Suggest improvements for a prompt based on past outcomes. Finds similar prompts ranked by success rate.",
|
|
29408
30879
|
inputSchema: {
|
|
29409
30880
|
type: "object",
|
|
@@ -29430,12 +30901,12 @@ function handlePromptToolCall(name, args2, memoryDb) {
|
|
|
29430
30901
|
case "prompt_suggestions":
|
|
29431
30902
|
return handleSuggestions(args2, memoryDb);
|
|
29432
30903
|
default:
|
|
29433
|
-
return
|
|
30904
|
+
return text8(`Unknown prompt tool: ${name}`);
|
|
29434
30905
|
}
|
|
29435
30906
|
} catch (error) {
|
|
29436
|
-
return
|
|
30907
|
+
return text8(`Error in ${name}: ${error instanceof Error ? error.message : String(error)}
|
|
29437
30908
|
|
|
29438
|
-
Usage: ${
|
|
30909
|
+
Usage: ${p9("prompt_effectiveness")} { days: 30 }, ${p9("prompt_suggestions")} { prompt: "..." }`);
|
|
29439
30910
|
}
|
|
29440
30911
|
}
|
|
29441
30912
|
function handleEffectiveness(args2, db) {
|
|
@@ -29461,7 +30932,7 @@ function handleEffectiveness(args2, db) {
|
|
|
29461
30932
|
sql += " GROUP BY prompt_category ORDER BY total DESC";
|
|
29462
30933
|
const rows = db.prepare(sql).all(...params);
|
|
29463
30934
|
if (rows.length === 0) {
|
|
29464
|
-
return
|
|
30935
|
+
return text8(`No prompt outcomes found in the last ${days} days. Prompt analysis runs automatically at session end. Try a longer time range: ${p9("prompt_effectiveness")} { days: 90 }, or use ${p9("prompt_suggestions")} { prompt: "your text" } to analyze a prompt directly.`);
|
|
29465
30936
|
}
|
|
29466
30937
|
const lines = [
|
|
29467
30938
|
`## Prompt Effectiveness (${days} days)`,
|
|
@@ -29476,11 +30947,11 @@ function handleEffectiveness(args2, db) {
|
|
|
29476
30947
|
`| ${row.prompt_category} | ${total} | ${successRate}% | ${row.partials} | ${row.failures} | ${row.abandoned} | ${row.avg_corrections.toFixed(1)} | ${Math.round(row.avg_word_count)} |`
|
|
29477
30948
|
);
|
|
29478
30949
|
}
|
|
29479
|
-
return
|
|
30950
|
+
return text8(lines.join("\n"));
|
|
29480
30951
|
}
|
|
29481
30952
|
function handleSuggestions(args2, db) {
|
|
29482
30953
|
const prompt = args2.prompt;
|
|
29483
|
-
if (!prompt) return
|
|
30954
|
+
if (!prompt) return text8(`Usage: ${p9("prompt_suggestions")} { prompt: "your prompt text here" } - Analyzes a prompt and suggests improvements based on past outcomes.`);
|
|
29484
30955
|
const category = categorizePrompt(prompt);
|
|
29485
30956
|
const wordCount = prompt.split(/\s+/).length;
|
|
29486
30957
|
const similar = db.prepare(`
|
|
@@ -29523,9 +30994,9 @@ function handleSuggestions(args2, db) {
|
|
|
29523
30994
|
lines.push(`- Success rate: ${Math.round(stats.successes / stats.total * 100)}%`);
|
|
29524
30995
|
lines.push(`- Avg corrections needed: ${stats.avg_corrections.toFixed(1)}`);
|
|
29525
30996
|
}
|
|
29526
|
-
return
|
|
30997
|
+
return text8(lines.join("\n"));
|
|
29527
30998
|
}
|
|
29528
|
-
function
|
|
30999
|
+
function text8(content) {
|
|
29529
31000
|
return { content: [{ type: "text", text: content }] };
|
|
29530
31001
|
}
|
|
29531
31002
|
var PROMPT_BASE_NAMES;
|
|
@@ -29538,290 +31009,8 @@ var init_prompt_analyzer = __esm({
|
|
|
29538
31009
|
}
|
|
29539
31010
|
});
|
|
29540
31011
|
|
|
29541
|
-
// src/audit-trail.ts
|
|
29542
|
-
function p9(baseName) {
|
|
29543
|
-
return `${getConfig().toolPrefix}_${baseName}`;
|
|
29544
|
-
}
|
|
29545
|
-
function getAuditFormats() {
|
|
29546
|
-
return getConfig().governance?.audit?.formats ?? DEFAULT_FORMATS;
|
|
29547
|
-
}
|
|
29548
|
-
function queryAuditLog(db, options) {
|
|
29549
|
-
let sql = "SELECT * FROM audit_log WHERE 1=1";
|
|
29550
|
-
const params = [];
|
|
29551
|
-
if (options.eventType) {
|
|
29552
|
-
sql += " AND event_type = ?";
|
|
29553
|
-
params.push(options.eventType);
|
|
29554
|
-
}
|
|
29555
|
-
if (options.actor) {
|
|
29556
|
-
sql += " AND actor = ?";
|
|
29557
|
-
params.push(options.actor);
|
|
29558
|
-
}
|
|
29559
|
-
if (options.days) {
|
|
29560
|
-
sql += " AND timestamp >= datetime('now', ?)";
|
|
29561
|
-
params.push(`-${options.days} days`);
|
|
29562
|
-
}
|
|
29563
|
-
if (options.filePath) {
|
|
29564
|
-
sql += " AND file_path = ?";
|
|
29565
|
-
params.push(options.filePath);
|
|
29566
|
-
}
|
|
29567
|
-
if (options.changeType) {
|
|
29568
|
-
sql += " AND change_type = ?";
|
|
29569
|
-
params.push(options.changeType);
|
|
29570
|
-
}
|
|
29571
|
-
sql += " ORDER BY timestamp DESC";
|
|
29572
|
-
if (options.limit) {
|
|
29573
|
-
sql += " LIMIT ?";
|
|
29574
|
-
params.push(options.limit);
|
|
29575
|
-
}
|
|
29576
|
-
return db.prepare(sql).all(...params);
|
|
29577
|
-
}
|
|
29578
|
-
function getFileChain(db, filePath) {
|
|
29579
|
-
return db.prepare(
|
|
29580
|
-
"SELECT * FROM audit_log WHERE file_path = ? ORDER BY timestamp ASC LIMIT 10000"
|
|
29581
|
-
).all(filePath);
|
|
29582
|
-
}
|
|
29583
|
-
function getAuditToolDefinitions() {
|
|
29584
|
-
return [
|
|
29585
|
-
{
|
|
29586
|
-
name: p9("audit_log"),
|
|
29587
|
-
description: "Query the audit log. Filter by event type, actor, file, or time range.",
|
|
29588
|
-
inputSchema: {
|
|
29589
|
-
type: "object",
|
|
29590
|
-
properties: {
|
|
29591
|
-
event_type: { type: "string", description: "Filter by event type: code_change, rule_enforced, approval, review, commit, compaction" },
|
|
29592
|
-
actor: { type: "string", description: "Filter by actor: ai, human, hook, agent" },
|
|
29593
|
-
file_path: { type: "string", description: "Filter by file path" },
|
|
29594
|
-
change_type: { type: "string", description: "Filter by change type: create, edit, delete" },
|
|
29595
|
-
days: { type: "number", description: "Days to look back (default: 30)" },
|
|
29596
|
-
limit: { type: "number", description: "Max results (default: 50)" }
|
|
29597
|
-
},
|
|
29598
|
-
required: []
|
|
29599
|
-
}
|
|
29600
|
-
},
|
|
29601
|
-
{
|
|
29602
|
-
name: p9("audit_report"),
|
|
29603
|
-
description: "Generate an audit report in a specified format (summary, detailed, soc2).",
|
|
29604
|
-
inputSchema: {
|
|
29605
|
-
type: "object",
|
|
29606
|
-
properties: {
|
|
29607
|
-
format: { type: "string", description: "Report format: summary, detailed, soc2 (default: summary)" },
|
|
29608
|
-
days: { type: "number", description: "Days to cover (default: 30)" }
|
|
29609
|
-
},
|
|
29610
|
-
required: []
|
|
29611
|
-
}
|
|
29612
|
-
},
|
|
29613
|
-
{
|
|
29614
|
-
name: p9("audit_chain"),
|
|
29615
|
-
description: "Get the complete audit trail for a specific file. Shows all changes, reviews, and decisions chronologically.",
|
|
29616
|
-
inputSchema: {
|
|
29617
|
-
type: "object",
|
|
29618
|
-
properties: {
|
|
29619
|
-
file: { type: "string", description: "File path to trace" }
|
|
29620
|
-
},
|
|
29621
|
-
required: ["file"]
|
|
29622
|
-
}
|
|
29623
|
-
}
|
|
29624
|
-
];
|
|
29625
|
-
}
|
|
29626
|
-
function isAuditTool(name) {
|
|
29627
|
-
const pfx = getConfig().toolPrefix + "_";
|
|
29628
|
-
const baseName = name.startsWith(pfx) ? name.slice(pfx.length) : name;
|
|
29629
|
-
return AUDIT_BASE_NAMES.has(baseName);
|
|
29630
|
-
}
|
|
29631
|
-
function handleAuditToolCall(name, args2, memoryDb) {
|
|
29632
|
-
try {
|
|
29633
|
-
const pfx = getConfig().toolPrefix + "_";
|
|
29634
|
-
const baseName = name.startsWith(pfx) ? name.slice(pfx.length) : name;
|
|
29635
|
-
switch (baseName) {
|
|
29636
|
-
case "audit_log":
|
|
29637
|
-
return handleAuditLog(args2, memoryDb);
|
|
29638
|
-
case "audit_report":
|
|
29639
|
-
return handleAuditReport(args2, memoryDb);
|
|
29640
|
-
case "audit_chain":
|
|
29641
|
-
return handleAuditChain(args2, memoryDb);
|
|
29642
|
-
default:
|
|
29643
|
-
return text8(`Unknown audit tool: ${name}`);
|
|
29644
|
-
}
|
|
29645
|
-
} catch (error) {
|
|
29646
|
-
return text8(`Error in ${name}: ${error instanceof Error ? error.message : String(error)}
|
|
29647
|
-
|
|
29648
|
-
Usage: ${p9("audit_log")} { severity: "critical" }, ${p9("audit_report")} { format: "soc2" }`);
|
|
29649
|
-
}
|
|
29650
|
-
}
|
|
29651
|
-
function handleAuditLog(args2, db) {
|
|
29652
|
-
const entries = queryAuditLog(db, {
|
|
29653
|
-
eventType: args2.event_type,
|
|
29654
|
-
actor: args2.actor,
|
|
29655
|
-
filePath: args2.file_path,
|
|
29656
|
-
changeType: args2.change_type,
|
|
29657
|
-
days: args2.days ?? 30,
|
|
29658
|
-
limit: args2.limit ?? 50
|
|
29659
|
-
});
|
|
29660
|
-
if (entries.length === 0) {
|
|
29661
|
-
return text8(`No audit log entries found matching the filters. Audit entries are recorded automatically during code changes, rule checks, and commits. Try broadening your search: ${p9("audit_log")} { days: 90 } for a longer time range, or ${p9("audit_log")} {} with no filters.`);
|
|
29662
|
-
}
|
|
29663
|
-
const lines = [
|
|
29664
|
-
`## Audit Log (${entries.length} entries)`,
|
|
29665
|
-
"",
|
|
29666
|
-
"| Timestamp | Event | Actor | Change | File | Evidence |",
|
|
29667
|
-
"|-----------|-------|-------|--------|------|----------|"
|
|
29668
|
-
];
|
|
29669
|
-
for (const entry of entries) {
|
|
29670
|
-
const file = entry.file_path ? entry.file_path.split("/").pop() : "-";
|
|
29671
|
-
const evidence = entry.evidence ? entry.evidence.slice(0, 50) : "-";
|
|
29672
|
-
lines.push(
|
|
29673
|
-
`| ${entry.timestamp.slice(0, 16)} | ${entry.event_type} | ${entry.actor} | ${entry.change_type ?? "-"} | ${file} | ${evidence} |`
|
|
29674
|
-
);
|
|
29675
|
-
}
|
|
29676
|
-
return text8(lines.join("\n"));
|
|
29677
|
-
}
|
|
29678
|
-
function handleAuditReport(args2, db) {
|
|
29679
|
-
const format = args2.format ?? "summary";
|
|
29680
|
-
const days = args2.days ?? 30;
|
|
29681
|
-
const availableFormats = getAuditFormats();
|
|
29682
|
-
if (!availableFormats.includes(format)) {
|
|
29683
|
-
return text8(`Unknown report format: "${format}". Available formats: ${availableFormats.join(", ")}. Example: ${p9("audit_report")} { format: "soc2", days: 30 }. Configure additional formats via the governance.audit.formats config key.`);
|
|
29684
|
-
}
|
|
29685
|
-
const entries = queryAuditLog(db, { days });
|
|
29686
|
-
if (entries.length === 0) {
|
|
29687
|
-
return text8(`No audit entries found in the last ${days} days. Audit entries are recorded automatically during code changes, reviews, and commits. Try: ${p9("audit_log")} { days: 90 } for a longer time range.`);
|
|
29688
|
-
}
|
|
29689
|
-
const byEventType = {};
|
|
29690
|
-
const byActor = {};
|
|
29691
|
-
for (const e2 of entries) {
|
|
29692
|
-
const et = e2.event_type;
|
|
29693
|
-
byEventType[et] = (byEventType[et] ?? 0) + 1;
|
|
29694
|
-
const actor = e2.actor;
|
|
29695
|
-
byActor[actor] = (byActor[actor] ?? 0) + 1;
|
|
29696
|
-
}
|
|
29697
|
-
if (format === "soc2") {
|
|
29698
|
-
return generateSoc2Report(entries, byEventType, byActor, days);
|
|
29699
|
-
}
|
|
29700
|
-
if (format === "detailed") {
|
|
29701
|
-
return generateDetailedReport(entries, byEventType, byActor, days);
|
|
29702
|
-
}
|
|
29703
|
-
const lines = [
|
|
29704
|
-
`## Audit Summary Report (${days} days)`,
|
|
29705
|
-
`Generated: ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)}`,
|
|
29706
|
-
"",
|
|
29707
|
-
"### By Event Type",
|
|
29708
|
-
`| Event Type | Count |`,
|
|
29709
|
-
`|------------|-------|`
|
|
29710
|
-
];
|
|
29711
|
-
for (const [et, count] of Object.entries(byEventType).sort((a2, b2) => b2[1] - a2[1])) {
|
|
29712
|
-
lines.push(`| ${et} | ${count} |`);
|
|
29713
|
-
}
|
|
29714
|
-
lines.push(`| **Total** | **${entries.length}** |`);
|
|
29715
|
-
lines.push("");
|
|
29716
|
-
lines.push("### By Actor");
|
|
29717
|
-
lines.push(`| Actor | Count |`);
|
|
29718
|
-
lines.push(`|-------|-------|`);
|
|
29719
|
-
for (const [actor, count] of Object.entries(byActor).sort((a2, b2) => b2[1] - a2[1])) {
|
|
29720
|
-
lines.push(`| ${actor} | ${count} |`);
|
|
29721
|
-
}
|
|
29722
|
-
return text8(lines.join("\n"));
|
|
29723
|
-
}
|
|
29724
|
-
function generateSoc2Report(entries, byEventType, byActor, days) {
|
|
29725
|
-
const lines = [
|
|
29726
|
-
`# SOC 2 Compliance Report`,
|
|
29727
|
-
`Period: Last ${days} days`,
|
|
29728
|
-
`Generated: ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
29729
|
-
"",
|
|
29730
|
-
"## 1. Control Environment",
|
|
29731
|
-
`Total audit events: ${entries.length}`,
|
|
29732
|
-
`Human-initiated: ${byActor["human"] ?? 0}`,
|
|
29733
|
-
`AI-initiated: ${byActor["ai"] ?? 0}`,
|
|
29734
|
-
`Hook-triggered: ${byActor["hook"] ?? 0}`,
|
|
29735
|
-
"",
|
|
29736
|
-
"## 2. Change Management",
|
|
29737
|
-
`Code changes logged: ${byEventType["code_change"] ?? 0}`,
|
|
29738
|
-
`Rule enforcements: ${byEventType["rule_enforced"] ?? 0}`,
|
|
29739
|
-
`Approvals recorded: ${byEventType["approval"] ?? 0}`,
|
|
29740
|
-
`Reviews: ${byEventType["review"] ?? 0}`,
|
|
29741
|
-
`Commits: ${byEventType["commit"] ?? 0}`,
|
|
29742
|
-
"",
|
|
29743
|
-
"## 3. Approval Status"
|
|
29744
|
-
];
|
|
29745
|
-
const pendingApprovals = entries.filter((e2) => e2.approval_status === "pending");
|
|
29746
|
-
const deniedApprovals = entries.filter((e2) => e2.approval_status === "denied");
|
|
29747
|
-
lines.push(`Pending approvals: ${pendingApprovals.length}`);
|
|
29748
|
-
lines.push(`Denied approvals: ${deniedApprovals.length}`);
|
|
29749
|
-
lines.push("");
|
|
29750
|
-
lines.push("## 4. Events Requiring Review");
|
|
29751
|
-
const reviewEntries = entries.filter((e2) => e2.approval_status === "denied" || e2.event_type === "rule_enforced");
|
|
29752
|
-
if (reviewEntries.length > 0) {
|
|
29753
|
-
for (const e2 of reviewEntries.slice(0, 20)) {
|
|
29754
|
-
lines.push(`- [${e2.timestamp.slice(0, 16)}] ${e2.event_type}: ${(e2.evidence ?? "").slice(0, 100)}`);
|
|
29755
|
-
}
|
|
29756
|
-
} else {
|
|
29757
|
-
lines.push("No events requiring review in this period.");
|
|
29758
|
-
}
|
|
29759
|
-
return text8(lines.join("\n"));
|
|
29760
|
-
}
|
|
29761
|
-
function generateDetailedReport(entries, byEventType, byActor, days) {
|
|
29762
|
-
const lines = [
|
|
29763
|
-
`## Detailed Audit Report (${days} days)`,
|
|
29764
|
-
`Total events: ${entries.length}`,
|
|
29765
|
-
"",
|
|
29766
|
-
"### Event Type Distribution"
|
|
29767
|
-
];
|
|
29768
|
-
for (const [et, count] of Object.entries(byEventType).sort((a2, b2) => b2[1] - a2[1])) {
|
|
29769
|
-
lines.push(`- ${et}: ${count}`);
|
|
29770
|
-
}
|
|
29771
|
-
lines.push("");
|
|
29772
|
-
lines.push("### Actor Distribution");
|
|
29773
|
-
for (const [actor, count] of Object.entries(byActor).sort((a2, b2) => b2[1] - a2[1])) {
|
|
29774
|
-
lines.push(`- ${actor}: ${count}`);
|
|
29775
|
-
}
|
|
29776
|
-
lines.push("");
|
|
29777
|
-
lines.push("### All Events");
|
|
29778
|
-
for (const e2 of entries.slice(0, 100)) {
|
|
29779
|
-
lines.push(`#### ${e2.timestamp.slice(0, 16)} [${e2.event_type}]`);
|
|
29780
|
-
lines.push(`Actor: ${e2.actor} | Change: ${e2.change_type ?? "-"} | Approval: ${e2.approval_status ?? "-"}`);
|
|
29781
|
-
if (e2.file_path) lines.push(`File: ${e2.file_path}`);
|
|
29782
|
-
if (e2.evidence) lines.push(`Evidence: ${e2.evidence}`);
|
|
29783
|
-
lines.push("");
|
|
29784
|
-
}
|
|
29785
|
-
if (entries.length > 100) {
|
|
29786
|
-
lines.push(`... and ${entries.length - 100} more entries`);
|
|
29787
|
-
}
|
|
29788
|
-
return text8(lines.join("\n"));
|
|
29789
|
-
}
|
|
29790
|
-
function handleAuditChain(args2, db) {
|
|
29791
|
-
const filePath = args2.file;
|
|
29792
|
-
if (!filePath) return text8(`Usage: ${p9("audit_chain")} { file: "src/path/to/file.ts" } - Get complete audit history for a file.`);
|
|
29793
|
-
const chain = getFileChain(db, filePath);
|
|
29794
|
-
if (chain.length === 0) {
|
|
29795
|
-
return text8(`No audit entries found for "${filePath}". Audit entries are recorded automatically when files are modified, reviewed, or validated. Try: ${p9("audit_log")} {} to see all recent audit entries across all files.`);
|
|
29796
|
-
}
|
|
29797
|
-
const lines = [
|
|
29798
|
-
`## Audit Chain: ${filePath}`,
|
|
29799
|
-
`Total events: ${chain.length}`,
|
|
29800
|
-
""
|
|
29801
|
-
];
|
|
29802
|
-
for (const entry of chain) {
|
|
29803
|
-
lines.push(`### ${entry.timestamp.slice(0, 16)} [${entry.event_type}]`);
|
|
29804
|
-
lines.push(`**${entry.actor}** (${entry.change_type ?? "unknown"})`);
|
|
29805
|
-
if (entry.evidence) lines.push(entry.evidence);
|
|
29806
|
-
lines.push("");
|
|
29807
|
-
}
|
|
29808
|
-
return text8(lines.join("\n"));
|
|
29809
|
-
}
|
|
29810
|
-
function text8(content) {
|
|
29811
|
-
return { content: [{ type: "text", text: content }] };
|
|
29812
|
-
}
|
|
29813
|
-
var DEFAULT_FORMATS, AUDIT_BASE_NAMES;
|
|
29814
|
-
var init_audit_trail = __esm({
|
|
29815
|
-
"src/audit-trail.ts"() {
|
|
29816
|
-
"use strict";
|
|
29817
|
-
init_config();
|
|
29818
|
-
DEFAULT_FORMATS = ["summary", "detailed", "soc2"];
|
|
29819
|
-
AUDIT_BASE_NAMES = /* @__PURE__ */ new Set(["audit_log", "audit_report", "audit_chain"]);
|
|
29820
|
-
}
|
|
29821
|
-
});
|
|
29822
|
-
|
|
29823
31012
|
// src/validation-engine.ts
|
|
29824
|
-
import { existsSync as
|
|
31013
|
+
import { existsSync as existsSync57, readFileSync as readFileSync48 } from "fs";
|
|
29825
31014
|
function p10(baseName) {
|
|
29826
31015
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
29827
31016
|
}
|
|
@@ -29852,7 +31041,7 @@ function validateFile(filePath, projectRoot) {
|
|
|
29852
31041
|
});
|
|
29853
31042
|
return checks;
|
|
29854
31043
|
}
|
|
29855
|
-
if (!
|
|
31044
|
+
if (!existsSync57(absPath)) {
|
|
29856
31045
|
checks.push({
|
|
29857
31046
|
name: "file_exists",
|
|
29858
31047
|
severity: "error",
|
|
@@ -29861,7 +31050,7 @@ function validateFile(filePath, projectRoot) {
|
|
|
29861
31050
|
});
|
|
29862
31051
|
return checks;
|
|
29863
31052
|
}
|
|
29864
|
-
const source =
|
|
31053
|
+
const source = readFileSync48(absPath, "utf-8");
|
|
29865
31054
|
const lines = source.split("\n");
|
|
29866
31055
|
if (activeChecks.rule_compliance !== false) {
|
|
29867
31056
|
for (const ruleSet of config.rules) {
|
|
@@ -30320,7 +31509,7 @@ var init_adr_generator = __esm({
|
|
|
30320
31509
|
});
|
|
30321
31510
|
|
|
30322
31511
|
// src/security-scorer.ts
|
|
30323
|
-
import { existsSync as
|
|
31512
|
+
import { existsSync as existsSync58, readFileSync as readFileSync49 } from "fs";
|
|
30324
31513
|
function p12(baseName) {
|
|
30325
31514
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
30326
31515
|
}
|
|
@@ -30344,12 +31533,12 @@ function scoreFileSecurity(filePath, projectRoot) {
|
|
|
30344
31533
|
}]
|
|
30345
31534
|
};
|
|
30346
31535
|
}
|
|
30347
|
-
if (!
|
|
31536
|
+
if (!existsSync58(absPath)) {
|
|
30348
31537
|
return { riskScore: 0, findings: [] };
|
|
30349
31538
|
}
|
|
30350
31539
|
let source;
|
|
30351
31540
|
try {
|
|
30352
|
-
source =
|
|
31541
|
+
source = readFileSync49(absPath, "utf-8");
|
|
30353
31542
|
} catch {
|
|
30354
31543
|
return { riskScore: 0, findings: [] };
|
|
30355
31544
|
}
|
|
@@ -30639,7 +31828,7 @@ var init_security_scorer = __esm({
|
|
|
30639
31828
|
});
|
|
30640
31829
|
|
|
30641
31830
|
// src/dependency-scorer.ts
|
|
30642
|
-
import { existsSync as
|
|
31831
|
+
import { existsSync as existsSync59, readFileSync as readFileSync50 } from "fs";
|
|
30643
31832
|
import { resolve as resolve37 } from "path";
|
|
30644
31833
|
function p13(baseName) {
|
|
30645
31834
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
@@ -30673,9 +31862,9 @@ function calculateDepRisk(factors) {
|
|
|
30673
31862
|
}
|
|
30674
31863
|
function getInstalledPackages(projectRoot) {
|
|
30675
31864
|
const pkgPath = resolve37(projectRoot, "package.json");
|
|
30676
|
-
if (!
|
|
31865
|
+
if (!existsSync59(pkgPath)) return /* @__PURE__ */ new Map();
|
|
30677
31866
|
try {
|
|
30678
|
-
const pkg = JSON.parse(
|
|
31867
|
+
const pkg = JSON.parse(readFileSync50(pkgPath, "utf-8"));
|
|
30679
31868
|
const packages = /* @__PURE__ */ new Map();
|
|
30680
31869
|
for (const [name, version] of Object.entries(pkg.dependencies ?? {})) {
|
|
30681
31870
|
packages.set(name, version);
|
|
@@ -31279,30 +32468,10 @@ var init_regression_detector = __esm({
|
|
|
31279
32468
|
}
|
|
31280
32469
|
});
|
|
31281
32470
|
|
|
31282
|
-
// src/lib/corrections-md.ts
|
|
31283
|
-
function formatCorrectionEntry(e2) {
|
|
31284
|
-
const one = (s) => s.replace(/\s*\n+\s*/g, " ").trim();
|
|
31285
|
-
const lines = [`### ${e2.date} - ${one(e2.title)}`];
|
|
31286
|
-
if (e2.wrong) lines.push(`- **Wrong**: ${one(e2.wrong)}`);
|
|
31287
|
-
if (e2.correction) lines.push(`- **Correction**: ${one(e2.correction)}`);
|
|
31288
|
-
lines.push(`- **Rule**: ${one(e2.rule)}`);
|
|
31289
|
-
for (const [k3, v3] of Object.entries(e2.extra ?? {})) {
|
|
31290
|
-
lines.push(`- ${k3}: ${one(String(v3))}`);
|
|
31291
|
-
}
|
|
31292
|
-
return `
|
|
31293
|
-
${lines.join("\n")}
|
|
31294
|
-
`;
|
|
31295
|
-
}
|
|
31296
|
-
var init_corrections_md = __esm({
|
|
31297
|
-
"src/lib/corrections-md.ts"() {
|
|
31298
|
-
"use strict";
|
|
31299
|
-
}
|
|
31300
|
-
});
|
|
31301
|
-
|
|
31302
32471
|
// src/knowledge-indexer.ts
|
|
31303
|
-
import { createHash as
|
|
31304
|
-
import { readFileSync as
|
|
31305
|
-
import { resolve as resolve38, relative as
|
|
32472
|
+
import { createHash as createHash14 } from "crypto";
|
|
32473
|
+
import { readFileSync as readFileSync51, readdirSync as readdirSync27, statSync as statSync20, existsSync as existsSync60 } from "fs";
|
|
32474
|
+
import { resolve as resolve38, relative as relative14, basename as basename11, extname as extname2 } from "path";
|
|
31306
32475
|
function getKnowledgePaths() {
|
|
31307
32476
|
const resolved = getResolvedPaths();
|
|
31308
32477
|
const config = getConfig();
|
|
@@ -31350,7 +32519,7 @@ function categorizeFile(filePath) {
|
|
|
31350
32519
|
const paths = getKnowledgePaths();
|
|
31351
32520
|
if (filePath.startsWith(paths.plansDir)) return "plan";
|
|
31352
32521
|
if (filePath.startsWith(paths.docsDir)) {
|
|
31353
|
-
const relFromDocs =
|
|
32522
|
+
const relFromDocs = relative14(paths.docsDir, filePath).replace(/\\/g, "/").toLowerCase();
|
|
31354
32523
|
if (relFromDocs.startsWith("plans/")) return "plan";
|
|
31355
32524
|
if (relFromDocs.includes("architecture")) return "architecture";
|
|
31356
32525
|
if (relFromDocs.includes("security")) return "security";
|
|
@@ -31366,7 +32535,7 @@ function categorizeFile(filePath) {
|
|
|
31366
32535
|
}
|
|
31367
32536
|
const claudeDirName = getConfig().conventions?.claudeDirName ?? ".claude";
|
|
31368
32537
|
if (filePath.includes(`${claudeDirName}/projects/`) && filePath.includes("/memory/")) return "memory";
|
|
31369
|
-
const rel =
|
|
32538
|
+
const rel = relative14(paths.claudeDir, filePath).replace(/\\/g, "/");
|
|
31370
32539
|
const firstDir = rel.split("/")[0];
|
|
31371
32540
|
const knownCategories = getConfig().conventions?.knowledgeCategories ?? [
|
|
31372
32541
|
"patterns",
|
|
@@ -31388,7 +32557,7 @@ function categorizeFile(filePath) {
|
|
|
31388
32557
|
return "root";
|
|
31389
32558
|
}
|
|
31390
32559
|
function hashContent2(content) {
|
|
31391
|
-
return
|
|
32560
|
+
return createHash14("sha256").update(content).digest("hex");
|
|
31392
32561
|
}
|
|
31393
32562
|
function parseCRTable(content) {
|
|
31394
32563
|
const rules = [];
|
|
@@ -31609,11 +32778,11 @@ function indexAllKnowledge(db) {
|
|
|
31609
32778
|
files.push(...memFiles);
|
|
31610
32779
|
} catch {
|
|
31611
32780
|
}
|
|
31612
|
-
if (
|
|
32781
|
+
if (existsSync60(paths.plansDir)) {
|
|
31613
32782
|
const planFiles = discoverMarkdownFiles(paths.plansDir);
|
|
31614
32783
|
files.push(...planFiles);
|
|
31615
32784
|
}
|
|
31616
|
-
if (
|
|
32785
|
+
if (existsSync60(paths.docsDir)) {
|
|
31617
32786
|
const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
|
|
31618
32787
|
const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f2) => !f2.includes("/plans/") && !excludePatterns.some((p19) => f2.includes(p19)));
|
|
31619
32788
|
files.push(...docsFiles);
|
|
@@ -31656,10 +32825,10 @@ function indexAllKnowledge(db) {
|
|
|
31656
32825
|
} catch {
|
|
31657
32826
|
}
|
|
31658
32827
|
for (const filePath of files) {
|
|
31659
|
-
if (!
|
|
31660
|
-
const content =
|
|
32828
|
+
if (!existsSync60(filePath)) continue;
|
|
32829
|
+
const content = readFileSync51(filePath, "utf-8");
|
|
31661
32830
|
const hash = hashContent2(content);
|
|
31662
|
-
const relPath = filePath.startsWith(paths.claudeDir) ?
|
|
32831
|
+
const relPath = filePath.startsWith(paths.claudeDir) ? relative14(paths.claudeDir, filePath) : filePath.startsWith(paths.plansDir) ? "plans/" + relative14(paths.plansDir, filePath) : filePath.startsWith(paths.docsDir) ? "docs/" + relative14(paths.docsDir, filePath) : filePath.startsWith(paths.memoryDir) ? `memory/${relative14(paths.memoryDir, filePath)}` : basename11(filePath);
|
|
31663
32832
|
const category = categorizeFile(filePath);
|
|
31664
32833
|
const title = extractTitle(content, filePath);
|
|
31665
32834
|
const description = extractDescription2(content);
|
|
@@ -31792,10 +32961,10 @@ function isKnowledgeStale(db) {
|
|
|
31792
32961
|
files.push(...discoverMarkdownFiles(paths.memoryDir));
|
|
31793
32962
|
} catch {
|
|
31794
32963
|
}
|
|
31795
|
-
if (
|
|
32964
|
+
if (existsSync60(paths.plansDir)) {
|
|
31796
32965
|
files.push(...discoverMarkdownFiles(paths.plansDir));
|
|
31797
32966
|
}
|
|
31798
|
-
if (
|
|
32967
|
+
if (existsSync60(paths.docsDir)) {
|
|
31799
32968
|
const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
|
|
31800
32969
|
const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f2) => !f2.includes("/plans/") && !excludePatterns.some((p19) => f2.includes(p19)));
|
|
31801
32970
|
files.push(...docsFiles);
|
|
@@ -31824,7 +32993,7 @@ var init_knowledge_indexer = __esm({
|
|
|
31824
32993
|
});
|
|
31825
32994
|
|
|
31826
32995
|
// src/knowledge-tools.ts
|
|
31827
|
-
import { readFileSync as
|
|
32996
|
+
import { readFileSync as readFileSync52, writeFileSync as writeFileSync14, appendFileSync as appendFileSync4, readdirSync as readdirSync28 } from "fs";
|
|
31828
32997
|
import { resolve as resolve39, basename as basename12 } from "path";
|
|
31829
32998
|
function p16(baseName) {
|
|
31830
32999
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
@@ -32577,14 +33746,14 @@ function handleCorrect(db, args2) {
|
|
|
32577
33746
|
});
|
|
32578
33747
|
let existing = "";
|
|
32579
33748
|
try {
|
|
32580
|
-
existing =
|
|
33749
|
+
existing = readFileSync52(correctionsPath, "utf-8");
|
|
32581
33750
|
} catch {
|
|
32582
33751
|
}
|
|
32583
33752
|
const archiveIdx = existing.indexOf("## Archived");
|
|
32584
33753
|
if (archiveIdx > 0) {
|
|
32585
33754
|
const before = existing.slice(0, archiveIdx);
|
|
32586
33755
|
const after = existing.slice(archiveIdx);
|
|
32587
|
-
|
|
33756
|
+
writeFileSync14(correctionsPath, before + entry + after);
|
|
32588
33757
|
} else {
|
|
32589
33758
|
appendFileSync4(correctionsPath, entry);
|
|
32590
33759
|
}
|
|
@@ -33533,7 +34702,7 @@ var init_framework_supports = __esm({
|
|
|
33533
34702
|
});
|
|
33534
34703
|
|
|
33535
34704
|
// src/tools.ts
|
|
33536
|
-
import { readFileSync as
|
|
34705
|
+
import { readFileSync as readFileSync53, existsSync as existsSync62 } from "fs";
|
|
33537
34706
|
import { resolve as resolve40, basename as basename13 } from "path";
|
|
33538
34707
|
function prefix2() {
|
|
33539
34708
|
return getConfig().toolPrefix;
|
|
@@ -33953,8 +35122,8 @@ function handleContext(file, dataDb, codegraphDb) {
|
|
|
33953
35122
|
const resolvedPaths = getResolvedPaths();
|
|
33954
35123
|
const root = getProjectRoot();
|
|
33955
35124
|
const absFilePath = ensureWithinRoot(resolve40(resolvedPaths.srcDir, "..", file), root);
|
|
33956
|
-
if (
|
|
33957
|
-
const fileContent =
|
|
35125
|
+
if (existsSync62(absFilePath)) {
|
|
35126
|
+
const fileContent = readFileSync53(absFilePath, "utf-8").slice(0, 3e3);
|
|
33958
35127
|
const keywords = [];
|
|
33959
35128
|
if (fileContent.includes("ctx.db")) keywords.push("database", "schema");
|
|
33960
35129
|
if (fileContent.includes("BigInt") || fileContent.includes("Decimal")) keywords.push("BigInt", "serialization");
|
|
@@ -34392,10 +35561,10 @@ function handleSchema(args2) {
|
|
|
34392
35561
|
lines.push("");
|
|
34393
35562
|
const projectRoot = getProjectRoot();
|
|
34394
35563
|
const absPath = ensureWithinRoot(resolve40(projectRoot, file), projectRoot);
|
|
34395
|
-
if (!
|
|
35564
|
+
if (!existsSync62(absPath)) {
|
|
34396
35565
|
return text17(`File not found: ${file}`);
|
|
34397
35566
|
}
|
|
34398
|
-
const source =
|
|
35567
|
+
const source = readFileSync53(absPath, "utf-8");
|
|
34399
35568
|
const config = getConfig();
|
|
34400
35569
|
const dbPattern = config.dbAccessPattern ?? "ctx.db.{table}";
|
|
34401
35570
|
const regexStr = dbPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace("\\{table\\}", "(\\w+)");
|
|
@@ -34800,8 +35969,8 @@ var init_server_dispatch = __esm({
|
|
|
34800
35969
|
|
|
34801
35970
|
// src/server.ts
|
|
34802
35971
|
var server_exports = {};
|
|
34803
|
-
import { readFileSync as
|
|
34804
|
-
import { resolve as resolve41, dirname as
|
|
35972
|
+
import { readFileSync as readFileSync54 } from "fs";
|
|
35973
|
+
import { resolve as resolve41, dirname as dirname26 } from "path";
|
|
34805
35974
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
34806
35975
|
function pruneMemoryOnStartup() {
|
|
34807
35976
|
try {
|
|
@@ -34843,10 +36012,10 @@ var init_server = __esm({
|
|
|
34843
36012
|
init_consolidation_config();
|
|
34844
36013
|
init_license();
|
|
34845
36014
|
init_server_dispatch();
|
|
34846
|
-
__dirname5 =
|
|
36015
|
+
__dirname5 = dirname26(fileURLToPath6(import.meta.url));
|
|
34847
36016
|
PKG_VERSION = (() => {
|
|
34848
36017
|
try {
|
|
34849
|
-
const pkg = JSON.parse(
|
|
36018
|
+
const pkg = JSON.parse(readFileSync54(resolve41(__dirname5, "..", "package.json"), "utf-8"));
|
|
34850
36019
|
return pkg.version ?? "0.0.0";
|
|
34851
36020
|
} catch {
|
|
34852
36021
|
return "0.0.0";
|
|
@@ -35132,7 +36301,7 @@ var config_upgrade_exports = {};
|
|
|
35132
36301
|
__export(config_upgrade_exports, {
|
|
35133
36302
|
runConfigUpgrade: () => runConfigUpgrade
|
|
35134
36303
|
});
|
|
35135
|
-
import { existsSync as
|
|
36304
|
+
import { existsSync as existsSync63, readFileSync as readFileSync55, writeFileSync as writeFileSync15, copyFileSync as copyFileSync3, unlinkSync as unlinkSync6 } from "fs";
|
|
35136
36305
|
import { resolve as resolve42 } from "path";
|
|
35137
36306
|
import { parse as parseYaml6 } from "yaml";
|
|
35138
36307
|
async function runConfigUpgrade(opts = {}) {
|
|
@@ -35144,14 +36313,14 @@ async function runConfigUpgrade(opts = {}) {
|
|
|
35144
36313
|
const err = opts.silent ? () => {
|
|
35145
36314
|
} : (s) => process.stderr.write(s);
|
|
35146
36315
|
if (opts.rollback) {
|
|
35147
|
-
if (!
|
|
36316
|
+
if (!existsSync63(bakPath)) {
|
|
35148
36317
|
const message = `No backup found at ${bakPath}`;
|
|
35149
36318
|
err(message + "\n");
|
|
35150
36319
|
return { exitCode: 1, action: "none", message };
|
|
35151
36320
|
}
|
|
35152
36321
|
try {
|
|
35153
36322
|
copyFileSync3(bakPath, configPath);
|
|
35154
|
-
|
|
36323
|
+
unlinkSync6(bakPath);
|
|
35155
36324
|
log("Config restored from backup.\n");
|
|
35156
36325
|
return { exitCode: 0, action: "rolled-back" };
|
|
35157
36326
|
} catch (e2) {
|
|
@@ -35160,14 +36329,14 @@ async function runConfigUpgrade(opts = {}) {
|
|
|
35160
36329
|
return { exitCode: 2, action: "none", message };
|
|
35161
36330
|
}
|
|
35162
36331
|
}
|
|
35163
|
-
if (!
|
|
36332
|
+
if (!existsSync63(configPath)) {
|
|
35164
36333
|
const message = "massu.config.yaml not found. Run: npx massu init";
|
|
35165
36334
|
err(message + "\n");
|
|
35166
36335
|
return { exitCode: 1, action: "none", message };
|
|
35167
36336
|
}
|
|
35168
36337
|
let existing;
|
|
35169
36338
|
try {
|
|
35170
|
-
const content =
|
|
36339
|
+
const content = readFileSync55(configPath, "utf-8");
|
|
35171
36340
|
const parsed = parseYaml6(content);
|
|
35172
36341
|
if (!parsed || typeof parsed !== "object") {
|
|
35173
36342
|
throw new Error("config is not a YAML object");
|
|
@@ -35190,8 +36359,8 @@ async function runConfigUpgrade(opts = {}) {
|
|
|
35190
36359
|
fingerprint: computeFingerprint(detection)
|
|
35191
36360
|
};
|
|
35192
36361
|
try {
|
|
35193
|
-
const original =
|
|
35194
|
-
|
|
36362
|
+
const original = readFileSync55(configPath, "utf-8");
|
|
36363
|
+
writeFileSync15(bakPath, original, "utf-8");
|
|
35195
36364
|
} catch (e2) {
|
|
35196
36365
|
const message = `Failed to write backup: ${e2 instanceof Error ? e2.message : String(e2)}`;
|
|
35197
36366
|
err(message + "\n");
|
|
@@ -35224,7 +36393,7 @@ var config_check_drift_exports = {};
|
|
|
35224
36393
|
__export(config_check_drift_exports, {
|
|
35225
36394
|
runConfigCheckDrift: () => runConfigCheckDrift
|
|
35226
36395
|
});
|
|
35227
|
-
import { existsSync as
|
|
36396
|
+
import { existsSync as existsSync64, readFileSync as readFileSync56 } from "fs";
|
|
35228
36397
|
import { resolve as resolve43 } from "path";
|
|
35229
36398
|
import { parse as parseYaml7 } from "yaml";
|
|
35230
36399
|
function renderChanges(changes) {
|
|
@@ -35238,7 +36407,7 @@ async function runConfigCheckDrift(opts = {}) {
|
|
|
35238
36407
|
} : (s) => process.stdout.write(s);
|
|
35239
36408
|
const err = opts.silent ? () => {
|
|
35240
36409
|
} : (s) => process.stderr.write(s);
|
|
35241
|
-
if (!
|
|
36410
|
+
if (!existsSync64(configPath)) {
|
|
35242
36411
|
const message = "massu.config.yaml not found. Run: npx massu init";
|
|
35243
36412
|
err(message + "\n");
|
|
35244
36413
|
return {
|
|
@@ -35252,7 +36421,7 @@ async function runConfigCheckDrift(opts = {}) {
|
|
|
35252
36421
|
}
|
|
35253
36422
|
let config;
|
|
35254
36423
|
try {
|
|
35255
|
-
const content =
|
|
36424
|
+
const content = readFileSync56(configPath, "utf-8");
|
|
35256
36425
|
const parsed = parseYaml7(content);
|
|
35257
36426
|
if (!parsed || typeof parsed !== "object") {
|
|
35258
36427
|
throw new Error("config is not a YAML object");
|
|
@@ -35319,11 +36488,11 @@ var init_config_check_drift = __esm({
|
|
|
35319
36488
|
});
|
|
35320
36489
|
|
|
35321
36490
|
// src/cli.ts
|
|
35322
|
-
import { readFileSync as
|
|
35323
|
-
import { resolve as resolve44, dirname as
|
|
36491
|
+
import { readFileSync as readFileSync57 } from "fs";
|
|
36492
|
+
import { resolve as resolve44, dirname as dirname27 } from "path";
|
|
35324
36493
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
35325
36494
|
var __filename5 = fileURLToPath7(import.meta.url);
|
|
35326
|
-
var __dirname6 =
|
|
36495
|
+
var __dirname6 = dirname27(__filename5);
|
|
35327
36496
|
var args = process.argv.slice(2);
|
|
35328
36497
|
var subcommand = args[0];
|
|
35329
36498
|
async function main() {
|
|
@@ -35601,7 +36770,7 @@ Examples:
|
|
|
35601
36770
|
}
|
|
35602
36771
|
function printVersion() {
|
|
35603
36772
|
try {
|
|
35604
|
-
const pkg = JSON.parse(
|
|
36773
|
+
const pkg = JSON.parse(readFileSync57(resolve44(__dirname6, "../package.json"), "utf-8"));
|
|
35605
36774
|
console.log(`massu v${pkg.version}`);
|
|
35606
36775
|
} catch {
|
|
35607
36776
|
console.log("massu v0.1.0");
|