@massu/core 1.15.6 → 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 +103 -0
- package/agents/massu-architecture-reviewer.md +20 -0
- package/agents/massu-blast-radius-analyzer.md +20 -0
- package/agents/massu-competitive-scorer.md +20 -0
- package/agents/massu-help-sync.md +20 -0
- package/agents/massu-migration-writer.md +20 -0
- package/agents/massu-output-scorer.md +20 -0
- package/agents/massu-pattern-reviewer.md +20 -0
- package/agents/massu-plan-auditor.md +20 -0
- package/agents/massu-schema-sync-verifier.md +20 -0
- package/agents/massu-security-reviewer.md +20 -0
- package/agents/massu-ux-reviewer.md +20 -0
- package/commands/_shared-preamble.md +21 -0
- package/commands/_shared-references/auto-learning-protocol.md +2 -0
- package/commands/_shared-references/blast-radius-protocol.md +2 -0
- package/commands/_shared-references/security-pre-screen.md +2 -0
- package/commands/_shared-references/test-first-protocol.md +2 -0
- package/commands/_shared-references/verification-table.md +2 -0
- package/commands/_verification-laws.md +131 -0
- package/commands/_verification-laws.md.sha256 +1 -0
- package/commands/massu-data/references/common-queries.md +2 -0
- package/commands/massu-data/references/table-guide.md +2 -0
- package/commands/massu-data.md +2 -0
- package/commands/massu-debug/references/auto-learning.md +2 -0
- package/commands/massu-debug/references/codegraph-tracing.md +2 -0
- package/commands/massu-debug/references/common-shortcuts.md +2 -0
- package/commands/massu-debug/references/investigation-phases.md +2 -0
- package/commands/massu-debug/references/report-format.md +2 -0
- package/commands/massu-deploy.python-docker.md +2 -0
- package/commands/massu-deploy.python-fly.md +2 -0
- package/commands/massu-deploy.python-launchd.md +2 -0
- package/commands/massu-deploy.python-systemd.md +2 -0
- package/commands/massu-full-audit.md +2 -0
- package/commands/massu-golden-path/references/approval-points.md +2 -0
- package/commands/massu-golden-path/references/phase-0-requirements.md +2 -0
- package/commands/massu-golden-path/references/phase-1-plan-creation.md +2 -0
- package/commands/massu-golden-path/references/phase-2-implementation.md +2 -0
- package/commands/massu-golden-path/references/phase-2.5-gap-analyzer.md +2 -0
- package/commands/massu-golden-path/references/phase-3-simplify.md +2 -0
- package/commands/massu-golden-path/references/phase-3.5-security-audit.md +2 -0
- package/commands/massu-golden-path/references/phase-4-commit.md +2 -0
- package/commands/massu-golden-path/references/phase-5-push.md +2 -0
- package/commands/massu-golden-path/references/phase-5.5-production-verify.md +2 -0
- package/commands/massu-golden-path/references/phase-6-completion.md +2 -0
- package/commands/massu-golden-path/references/qa-evaluator-spec.md +2 -0
- package/commands/massu-golden-path/references/sprint-contract-protocol.md +2 -0
- package/commands/massu-golden-path/references/vr-visual-calibration.md +2 -0
- package/commands/massu-guide.md +2 -0
- package/commands/massu-hooks.md +2 -0
- package/commands/massu-learning-audit.md +2 -0
- package/commands/massu-loop/references/auto-learning.md +2 -0
- package/commands/massu-loop/references/checkpoint-audit.md +2 -0
- package/commands/massu-loop/references/guardrails.md +2 -0
- package/commands/massu-loop/references/iteration-structure.md +2 -0
- package/commands/massu-loop/references/loop-controller.md +2 -0
- package/commands/massu-loop/references/plan-extraction.md +2 -0
- package/commands/massu-loop/references/vr-plan-spec.md +2 -0
- package/commands/massu-parity.md +2 -0
- package/commands/massu-push.md +2 -0
- package/commands/massu-scaffold-hook.md +2 -0
- package/commands/massu-scaffold-page.md +2 -0
- package/commands/massu-scaffold-page.swift.md +2 -0
- package/commands/massu-scaffold-router.md +2 -0
- package/commands/massu-scaffold-router.python-django.md +2 -0
- package/commands/massu-scaffold-router.python-fastapi.md +2 -0
- package/commands/massu-scaffold-router.python.md +2 -0
- package/dist/cli.js +2012 -787
- 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 +4 -3
- package/patterns/build-patterns.md +2 -0
- package/patterns/component-patterns.md +2 -0
- package/patterns/display-patterns.md +2 -0
- package/patterns/form-patterns.md +2 -0
- package/patterns/integration-testing-checklist.md +2 -0
- package/patterns/security-patterns.md +2 -0
- package/patterns/testing-patterns.md +2 -0
- package/patterns/tool-routing.md +2 -0
- package/patterns/ui-patterns.md +2 -0
- package/protocols/plan-implementation.md +2 -0
- package/protocols/recovery.md +2 -0
- package/protocols/verification.md +2 -0
- package/reference/cr-rules-reference.md +2 -0
- package/reference/hook-execution-order.md +2 -0
- package/reference/lessons-learned.md +2 -0
- package/reference/patterns-quickref.md +2 -0
- package/reference/standards.md +2 -0
- package/reference/subagents-reference.md +2 -0
- package/reference/vr-verification-reference.md +2 -0
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";
|
|
@@ -4132,11 +4253,15 @@ var init_permissions = __esm({
|
|
|
4132
4253
|
// src/commands/install-commands.ts
|
|
4133
4254
|
var install_commands_exports = {};
|
|
4134
4255
|
__export(install_commands_exports, {
|
|
4256
|
+
MASSU_OWNED_PATHS: () => MASSU_OWNED_PATHS,
|
|
4135
4257
|
buildTemplateVars: () => buildTemplateVars,
|
|
4258
|
+
canonicalizeOwned: () => canonicalizeOwned,
|
|
4136
4259
|
hashContent: () => hashContent,
|
|
4137
4260
|
installAll: () => installAll,
|
|
4138
4261
|
installCommands: () => installCommands,
|
|
4262
|
+
isMassuOwned: () => isMassuOwned,
|
|
4139
4263
|
loadManifest: () => loadManifest,
|
|
4264
|
+
massuOwnedSourceIsValid: () => massuOwnedSourceIsValid,
|
|
4140
4265
|
pickVariant: () => pickVariant,
|
|
4141
4266
|
resolveAssetDir: () => resolveAssetDir,
|
|
4142
4267
|
resolveCommandsDir: () => resolveCommandsDir,
|
|
@@ -4155,25 +4280,51 @@ import {
|
|
|
4155
4280
|
import { resolve as resolve6, dirname as dirname7, join as join6 } from "path";
|
|
4156
4281
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4157
4282
|
import { createHash as createHash3 } from "crypto";
|
|
4283
|
+
function dirHoldsAssets(dir) {
|
|
4284
|
+
if (!existsSync9(dir)) return false;
|
|
4285
|
+
try {
|
|
4286
|
+
return readdirSync3(dir).some(
|
|
4287
|
+
(e2) => e2.endsWith(".md") || statSync2(resolve6(dir, e2)).isDirectory()
|
|
4288
|
+
);
|
|
4289
|
+
} catch {
|
|
4290
|
+
return false;
|
|
4291
|
+
}
|
|
4292
|
+
}
|
|
4158
4293
|
function resolveAssetDir(assetName) {
|
|
4159
4294
|
const cwd = process.cwd();
|
|
4160
|
-
const
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
const
|
|
4169
|
-
|
|
4170
|
-
return srcRelPath;
|
|
4295
|
+
const candidates = [
|
|
4296
|
+
// 1. npm-installed: node_modules/@massu/core/{assetName}
|
|
4297
|
+
resolve6(cwd, "node_modules/@massu/core", assetName),
|
|
4298
|
+
// 2. Relative to compiled dist/cli.js → ../{assetName}
|
|
4299
|
+
resolve6(__dirname, "..", assetName),
|
|
4300
|
+
// 3. Relative to source src/commands/ → ../../{assetName}
|
|
4301
|
+
resolve6(__dirname, "../..", assetName)
|
|
4302
|
+
];
|
|
4303
|
+
for (const c2 of candidates) {
|
|
4304
|
+
if (dirHoldsAssets(c2)) return c2;
|
|
4171
4305
|
}
|
|
4172
4306
|
return null;
|
|
4173
4307
|
}
|
|
4174
4308
|
function resolveCommandsDir() {
|
|
4175
4309
|
return resolveAssetDir("commands");
|
|
4176
4310
|
}
|
|
4311
|
+
function isMassuOwned(manifestKey) {
|
|
4312
|
+
return MASSU_OWNED_PATHS.includes(manifestKey);
|
|
4313
|
+
}
|
|
4314
|
+
function canonicalizeOwned(content) {
|
|
4315
|
+
return content.replace(/\r\n/g, "\n").split("\n").map((l2) => l2.replace(/\s+$/, "")).join("\n").trim();
|
|
4316
|
+
}
|
|
4317
|
+
function massuOwnedSourceIsValid(resolvedSourcePath, content) {
|
|
4318
|
+
const sidecar = `${resolvedSourcePath}.sha256`;
|
|
4319
|
+
if (!existsSync9(sidecar)) return false;
|
|
4320
|
+
let expected;
|
|
4321
|
+
try {
|
|
4322
|
+
expected = readFileSync6(sidecar, "utf-8").trim();
|
|
4323
|
+
} catch {
|
|
4324
|
+
return false;
|
|
4325
|
+
}
|
|
4326
|
+
return hashContent(canonicalizeOwned(content)) === expected;
|
|
4327
|
+
}
|
|
4177
4328
|
function hashContent(content) {
|
|
4178
4329
|
return createHash3("sha256").update(content, "utf-8").digest("hex");
|
|
4179
4330
|
}
|
|
@@ -4349,13 +4500,38 @@ function syncDirectory(sourceDir, targetDir, framework, manifest, manifestKeyPre
|
|
|
4349
4500
|
const manifestKey = manifestKeyPrefix === "" ? targetFilename : `${manifestKeyPrefix}/${targetFilename}`;
|
|
4350
4501
|
const lastInstalledHash = manifest.entries[manifestKey];
|
|
4351
4502
|
if (existsSync9(targetPath)) {
|
|
4352
|
-
|
|
4503
|
+
let existingContent;
|
|
4504
|
+
try {
|
|
4505
|
+
if (!statSync2(targetPath).isFile()) throw new Error("not a regular file");
|
|
4506
|
+
existingContent = readFileSync6(targetPath, "utf-8");
|
|
4507
|
+
} catch (err) {
|
|
4508
|
+
process.stderr.write(
|
|
4509
|
+
`massu: skipping ${manifestKey}: target is not a readable file (${err instanceof Error ? err.message : String(err)}). Remove it and re-install.
|
|
4510
|
+
`
|
|
4511
|
+
);
|
|
4512
|
+
stats.skipped++;
|
|
4513
|
+
continue;
|
|
4514
|
+
}
|
|
4353
4515
|
const existingHash = hashContent(existingContent);
|
|
4354
4516
|
if (existingHash === sourceHash) {
|
|
4355
4517
|
manifest.entries[manifestKey] = sourceHash;
|
|
4356
4518
|
stats.skipped++;
|
|
4357
4519
|
continue;
|
|
4358
4520
|
}
|
|
4521
|
+
if (isMassuOwned(manifestKey)) {
|
|
4522
|
+
if (!massuOwnedSourceIsValid(resolvedSourcePath, sourceContent)) {
|
|
4523
|
+
process.stderr.write(
|
|
4524
|
+
`massu: REFUSING to overwrite massu-owned ${manifestKey} \u2014 the source failed its integrity hash (${resolvedSourcePath}.sha256 missing or mismatched). Your installed @massu/core may be stale or tampered; run \`npm i @massu/core@latest\` and re-install. Kept the existing file.
|
|
4525
|
+
`
|
|
4526
|
+
);
|
|
4527
|
+
stats.kept++;
|
|
4528
|
+
continue;
|
|
4529
|
+
}
|
|
4530
|
+
atomicWriteFile(targetPath, sourceContent);
|
|
4531
|
+
manifest.entries[manifestKey] = sourceHash;
|
|
4532
|
+
stats.updated++;
|
|
4533
|
+
continue;
|
|
4534
|
+
}
|
|
4359
4535
|
if (lastInstalledHash === void 0) {
|
|
4360
4536
|
process.stderr.write(
|
|
4361
4537
|
`First-install: keeping existing ${targetPath} (differs from upstream, and massu has no record of writing it).
|
|
@@ -4531,7 +4707,7 @@ async function runInstallCommands() {
|
|
|
4531
4707
|
console.log(" Restart your Claude Code session to use them.");
|
|
4532
4708
|
console.log("");
|
|
4533
4709
|
}
|
|
4534
|
-
var __filename, __dirname, ASSET_TYPES, MANIFEST_VERSION, MANIFEST_RELPATH, PASSTHROUGH_LANG_KEYS;
|
|
4710
|
+
var __filename, __dirname, ASSET_TYPES, MANIFEST_VERSION, MANIFEST_RELPATH, MASSU_OWNED_PATHS, PASSTHROUGH_LANG_KEYS;
|
|
4535
4711
|
var init_install_commands = __esm({
|
|
4536
4712
|
"src/commands/install-commands.ts"() {
|
|
4537
4713
|
"use strict";
|
|
@@ -4550,6 +4726,7 @@ var init_install_commands = __esm({
|
|
|
4550
4726
|
];
|
|
4551
4727
|
MANIFEST_VERSION = 1;
|
|
4552
4728
|
MANIFEST_RELPATH = join6(".massu", "install-manifest.json");
|
|
4729
|
+
MASSU_OWNED_PATHS = ["commands/_verification-laws.md"];
|
|
4553
4730
|
PASSTHROUGH_LANG_KEYS = [
|
|
4554
4731
|
"typescript",
|
|
4555
4732
|
"javascript",
|
|
@@ -9103,21 +9280,21 @@ var require_pattern = __commonJS({
|
|
|
9103
9280
|
exports.removeDuplicateSlashes = removeDuplicateSlashes;
|
|
9104
9281
|
function partitionAbsoluteAndRelative(patterns) {
|
|
9105
9282
|
const absolute = [];
|
|
9106
|
-
const
|
|
9283
|
+
const relative15 = [];
|
|
9107
9284
|
for (const pattern of patterns) {
|
|
9108
|
-
if (
|
|
9285
|
+
if (isAbsolute5(pattern)) {
|
|
9109
9286
|
absolute.push(pattern);
|
|
9110
9287
|
} else {
|
|
9111
|
-
|
|
9288
|
+
relative15.push(pattern);
|
|
9112
9289
|
}
|
|
9113
9290
|
}
|
|
9114
|
-
return [absolute,
|
|
9291
|
+
return [absolute, relative15];
|
|
9115
9292
|
}
|
|
9116
9293
|
exports.partitionAbsoluteAndRelative = partitionAbsoluteAndRelative;
|
|
9117
|
-
function
|
|
9294
|
+
function isAbsolute5(pattern) {
|
|
9118
9295
|
return path.isAbsolute(pattern);
|
|
9119
9296
|
}
|
|
9120
|
-
exports.isAbsolute =
|
|
9297
|
+
exports.isAbsolute = isAbsolute5;
|
|
9121
9298
|
}
|
|
9122
9299
|
});
|
|
9123
9300
|
|
|
@@ -17919,6 +18096,28 @@ function autoLearningUpgradeMessage(currentTier) {
|
|
|
17919
18096
|
function entitledForTeamSharedPromotion(tier) {
|
|
17920
18097
|
return tierLevel(tier) >= tierLevel(TEAM_SHARED_PROMOTION_MIN_TIER);
|
|
17921
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
|
+
}
|
|
17922
18121
|
var AUTO_LEARNING_MIN_TIER, TEAM_SHARED_PROMOTION_MIN_TIER;
|
|
17923
18122
|
var init_auto_learning_entitlement = __esm({
|
|
17924
18123
|
"src/auto-learning-entitlement.ts"() {
|
|
@@ -19284,6 +19483,12 @@ function assertOutsideRegionUnchanged(pre, post) {
|
|
|
19284
19483
|
);
|
|
19285
19484
|
}
|
|
19286
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
|
+
}
|
|
19287
19492
|
function memoryIndexLockPath(memoryDir) {
|
|
19288
19493
|
return join16(dirname15(memoryDir), ".massu-memory-index.lock");
|
|
19289
19494
|
}
|
|
@@ -19308,7 +19513,7 @@ function writeMemoryIndex(indexPath, pre, post) {
|
|
|
19308
19513
|
assertOutsideRegionUnchanged(pre, post);
|
|
19309
19514
|
atomicWriteFileSync(indexPath, post);
|
|
19310
19515
|
}
|
|
19311
|
-
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;
|
|
19312
19517
|
var init_memory_index_region = __esm({
|
|
19313
19518
|
"src/memory-index-region.ts"() {
|
|
19314
19519
|
"use strict";
|
|
@@ -19316,6 +19521,7 @@ var init_memory_index_region = __esm({
|
|
|
19316
19521
|
init_safe_write();
|
|
19317
19522
|
BEGIN_SENTINEL = "<!-- massu:learned:begin -->";
|
|
19318
19523
|
END_SENTINEL = "<!-- massu:learned:end -->";
|
|
19524
|
+
DEFAULT_MAX_REGION_LINES = 50;
|
|
19319
19525
|
RegionRefused = class extends Error {
|
|
19320
19526
|
constructor(message, reason) {
|
|
19321
19527
|
super(message);
|
|
@@ -19324,6 +19530,12 @@ var init_memory_index_region = __esm({
|
|
|
19324
19530
|
}
|
|
19325
19531
|
reason;
|
|
19326
19532
|
};
|
|
19533
|
+
MemoryIndexMissing = class extends Error {
|
|
19534
|
+
constructor(message) {
|
|
19535
|
+
super(message);
|
|
19536
|
+
this.name = "MemoryIndexMissing";
|
|
19537
|
+
}
|
|
19538
|
+
};
|
|
19327
19539
|
LOCK_BLOCK_MS = 2e3;
|
|
19328
19540
|
LOCK_STALE_MS = 3e4;
|
|
19329
19541
|
MemoryIndexLockBusy = class extends Error {
|
|
@@ -20008,6 +20220,1149 @@ var init_memory_render_cli_entry = __esm({
|
|
|
20008
20220
|
}
|
|
20009
20221
|
});
|
|
20010
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
|
+
|
|
20011
21366
|
// src/security/promotion-pubkey.generated.ts
|
|
20012
21367
|
var PROMOTION_PUBKEY_ED25519, PROMOTION_PUBKEY_FINGERPRINT_HEX, KNOWN_PROMOTION_PUBKEY_FINGERPRINTS;
|
|
20013
21368
|
var init_promotion_pubkey_generated = __esm({
|
|
@@ -20041,20 +21396,8 @@ var init_promotion_envelope_verifier = __esm({
|
|
|
20041
21396
|
}
|
|
20042
21397
|
});
|
|
20043
21398
|
|
|
20044
|
-
// src/rule-candidate-hardened.ts
|
|
20045
|
-
var TEAM_HARDENED_SHAREABLE_DESTINATIONS;
|
|
20046
|
-
var init_rule_candidate_hardened = __esm({
|
|
20047
|
-
"src/rule-candidate-hardened.ts"() {
|
|
20048
|
-
"use strict";
|
|
20049
|
-
TEAM_HARDENED_SHAREABLE_DESTINATIONS = [
|
|
20050
|
-
"pattern-scanner",
|
|
20051
|
-
"custom-destination"
|
|
20052
|
-
];
|
|
20053
|
-
}
|
|
20054
|
-
});
|
|
20055
|
-
|
|
20056
21399
|
// src/rule-pack-schema.ts
|
|
20057
|
-
function
|
|
21400
|
+
function isRuleDestination2(value) {
|
|
20058
21401
|
return typeof value === "string" && RULE_DESTINATIONS.includes(value);
|
|
20059
21402
|
}
|
|
20060
21403
|
function isExecutableDestination(destination) {
|
|
@@ -20078,7 +21421,7 @@ function validateRulePackRule(rule) {
|
|
|
20078
21421
|
if (!isNonEmptyString(r2.severity)) {
|
|
20079
21422
|
errors.push("missing required field: severity");
|
|
20080
21423
|
}
|
|
20081
|
-
const destinationKnown =
|
|
21424
|
+
const destinationKnown = isRuleDestination2(r2.destination);
|
|
20082
21425
|
if (!destinationKnown) {
|
|
20083
21426
|
errors.push(
|
|
20084
21427
|
`invalid destination: ${JSON.stringify(r2.destination)} is not a known RuleDestination (one of ${RULE_DESTINATIONS.join(", ")})`
|
|
@@ -20132,8 +21475,8 @@ var init_rule_pack_schema = __esm({
|
|
|
20132
21475
|
});
|
|
20133
21476
|
|
|
20134
21477
|
// src/rule-pack-sync.ts
|
|
20135
|
-
import { existsSync as
|
|
20136
|
-
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";
|
|
20137
21480
|
async function pullInstalledPackRules(db, opts = {}) {
|
|
20138
21481
|
const config = getConfig();
|
|
20139
21482
|
const cloud = config.cloud;
|
|
@@ -20195,7 +21538,7 @@ async function pullInstalledPackRules(db, opts = {}) {
|
|
|
20195
21538
|
result.pulled += 1;
|
|
20196
21539
|
const promptHash = packRulePromptHash(pack.slug, rule);
|
|
20197
21540
|
const candidatePath = sidecarPath(projectRoot, promptHash);
|
|
20198
|
-
if (
|
|
21541
|
+
if (existsSync34(candidatePath) || alreadyApplied(db, promptHash)) {
|
|
20199
21542
|
result.skipped += 1;
|
|
20200
21543
|
continue;
|
|
20201
21544
|
}
|
|
@@ -20228,12 +21571,12 @@ function isValidInstalledPack(p19) {
|
|
|
20228
21571
|
return typeof r2.slug === "string" && r2.slug.length > 0 && Array.isArray(r2.rules);
|
|
20229
21572
|
}
|
|
20230
21573
|
function packRulePromptHash(slug, rule) {
|
|
20231
|
-
const { createHash:
|
|
21574
|
+
const { createHash: createHash15 } = __require("node:crypto");
|
|
20232
21575
|
const body = `${slug}\0${rule.destination}\0${rule.title}\0${rule.pattern ?? rule.check ?? rule.description}`;
|
|
20233
|
-
return
|
|
21576
|
+
return createHash15("sha256").update(body, "utf-8").digest("hex").slice(0, 16);
|
|
20234
21577
|
}
|
|
20235
21578
|
function sidecarPath(projectRoot, promptHash) {
|
|
20236
|
-
return
|
|
21579
|
+
return join21(projectRoot, ".massu", "rule-candidates", `${promptHash}.json`);
|
|
20237
21580
|
}
|
|
20238
21581
|
function alreadyApplied(db, promptHash) {
|
|
20239
21582
|
try {
|
|
@@ -20282,9 +21625,9 @@ function materializeCandidate(projectRoot, candidatePath, promptHash, rule, pack
|
|
|
20282
21625
|
destination: rule.destination,
|
|
20283
21626
|
draft_text: draftText
|
|
20284
21627
|
};
|
|
20285
|
-
const dir =
|
|
20286
|
-
if (!
|
|
20287
|
-
|
|
21628
|
+
const dir = dirname18(candidatePath);
|
|
21629
|
+
if (!existsSync34(dir)) mkdirSync15(dir, { recursive: true });
|
|
21630
|
+
writeFileSync11(candidatePath, JSON.stringify(sidecar, null, 2), "utf-8");
|
|
20288
21631
|
}
|
|
20289
21632
|
function emitDropTelemetry(db, eventType, data) {
|
|
20290
21633
|
recordTelemetry(db, eventType, data);
|
|
@@ -20318,6 +21661,12 @@ var rule_exports = {};
|
|
|
20318
21661
|
__export(rule_exports, {
|
|
20319
21662
|
handleRuleSubcommand: () => handleRuleSubcommand
|
|
20320
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
|
+
}
|
|
20321
21670
|
async function handleRuleSubcommand(args2) {
|
|
20322
21671
|
const sub = args2[0];
|
|
20323
21672
|
switch (sub) {
|
|
@@ -20366,11 +21715,179 @@ async function handleRuleSubcommand(args2) {
|
|
|
20366
21715
|
}
|
|
20367
21716
|
return { exitCode: 0 };
|
|
20368
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
|
+
}
|
|
20369
21886
|
case "--help":
|
|
20370
21887
|
case "-h":
|
|
20371
21888
|
case void 0: {
|
|
20372
21889
|
process.stdout.write(
|
|
20373
|
-
"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"
|
|
20374
21891
|
);
|
|
20375
21892
|
return { exitCode: 0 };
|
|
20376
21893
|
}
|
|
@@ -20381,14 +21898,24 @@ async function handleRuleSubcommand(args2) {
|
|
|
20381
21898
|
}
|
|
20382
21899
|
}
|
|
20383
21900
|
}
|
|
20384
|
-
var PROMPT_HASH_RE;
|
|
21901
|
+
var RULE_DESTINATIONS2, PROMPT_HASH_RE;
|
|
20385
21902
|
var init_rule = __esm({
|
|
20386
21903
|
"src/commands/rule.ts"() {
|
|
20387
21904
|
"use strict";
|
|
20388
21905
|
init_memory_db();
|
|
21906
|
+
init_config();
|
|
21907
|
+
init_rule_delivery();
|
|
21908
|
+
init_rule_candidate_applier();
|
|
21909
|
+
init_rule_candidate_preview();
|
|
20389
21910
|
init_license();
|
|
20390
21911
|
init_auto_learning_entitlement();
|
|
20391
21912
|
init_rule_pack_sync();
|
|
21913
|
+
RULE_DESTINATIONS2 = [
|
|
21914
|
+
"corrections-md",
|
|
21915
|
+
"claude-md-cr",
|
|
21916
|
+
"pattern-scanner",
|
|
21917
|
+
"custom-destination"
|
|
21918
|
+
];
|
|
20392
21919
|
PROMPT_HASH_RE = /^[0-9a-f]{16}$/;
|
|
20393
21920
|
}
|
|
20394
21921
|
});
|
|
@@ -20882,9 +22409,9 @@ var init_memory_supersede = __esm({
|
|
|
20882
22409
|
});
|
|
20883
22410
|
|
|
20884
22411
|
// src/memory-consolidate.ts
|
|
20885
|
-
import { createHash as
|
|
20886
|
-
import { existsSync as
|
|
20887
|
-
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";
|
|
20888
22415
|
function emptyResult() {
|
|
20889
22416
|
return {
|
|
20890
22417
|
deduped: 0,
|
|
@@ -21046,7 +22573,7 @@ ${r2.detail ?? ""}`.trim());
|
|
|
21046
22573
|
}
|
|
21047
22574
|
clusters.push(cluster);
|
|
21048
22575
|
}
|
|
21049
|
-
const candidateDir =
|
|
22576
|
+
const candidateDir = join23(projectRoot, ".massu", "rule-candidates");
|
|
21050
22577
|
let promoted = 0;
|
|
21051
22578
|
for (const cluster of clusters) {
|
|
21052
22579
|
const occurrences = cluster.reduce((n, c2) => n + (c2.recurrence_count || 1), 0);
|
|
@@ -21058,15 +22585,15 @@ ${r2.detail ?? ""}`.trim());
|
|
|
21058
22585
|
const promptText = `${representative.title}${representative.detail ? `
|
|
21059
22586
|
${representative.detail}` : ""}`;
|
|
21060
22587
|
const clusterKey = cluster.map((c2) => c2.title.toLowerCase().replace(/\s+/g, " ").trim()).sort().join("|");
|
|
21061
|
-
const promptHash =
|
|
21062
|
-
const candidatePath =
|
|
21063
|
-
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;
|
|
21064
22591
|
if (dryRun) {
|
|
21065
22592
|
promoted++;
|
|
21066
22593
|
continue;
|
|
21067
22594
|
}
|
|
21068
|
-
|
|
21069
|
-
|
|
22595
|
+
mkdirSync16(candidateDir, { recursive: true });
|
|
22596
|
+
writeFileSync12(
|
|
21070
22597
|
candidatePath,
|
|
21071
22598
|
JSON.stringify(
|
|
21072
22599
|
{
|
|
@@ -21336,7 +22863,7 @@ var show_template_exports = {};
|
|
|
21336
22863
|
__export(show_template_exports, {
|
|
21337
22864
|
runShowTemplate: () => runShowTemplate
|
|
21338
22865
|
});
|
|
21339
|
-
import { existsSync as
|
|
22866
|
+
import { existsSync as existsSync36, readFileSync as readFileSync28 } from "fs";
|
|
21340
22867
|
import { resolve as resolve17 } from "path";
|
|
21341
22868
|
function normalizeBaseName(input) {
|
|
21342
22869
|
return input.endsWith(".md") ? input.slice(0, -".md".length) : input;
|
|
@@ -21366,13 +22893,13 @@ async function runShowTemplate(args2) {
|
|
|
21366
22893
|
}
|
|
21367
22894
|
const suffix = choice.kind === "hit" ? choice.suffix : "";
|
|
21368
22895
|
const file = suffix === "" ? resolve17(sourceDir, `${baseName}.md`) : resolve17(sourceDir, `${baseName}${suffix}.md`);
|
|
21369
|
-
if (!
|
|
22896
|
+
if (!existsSync36(file)) {
|
|
21370
22897
|
process.stderr.write(`massu: resolved template "${file}" no longer exists
|
|
21371
22898
|
`);
|
|
21372
22899
|
process.exit(1);
|
|
21373
22900
|
return;
|
|
21374
22901
|
}
|
|
21375
|
-
process.stdout.write(
|
|
22902
|
+
process.stdout.write(readFileSync28(file, "utf-8"));
|
|
21376
22903
|
}
|
|
21377
22904
|
var init_show_template = __esm({
|
|
21378
22905
|
"src/commands/show-template.ts"() {
|
|
@@ -21467,7 +22994,7 @@ __export(config_refresh_exports, {
|
|
|
21467
22994
|
mergeRefresh: () => mergeRefresh,
|
|
21468
22995
|
runConfigRefresh: () => runConfigRefresh
|
|
21469
22996
|
});
|
|
21470
|
-
import { existsSync as
|
|
22997
|
+
import { existsSync as existsSync37, readFileSync as readFileSync29, rmSync as rmSync8 } from "fs";
|
|
21471
22998
|
import { resolve as resolve19 } from "path";
|
|
21472
22999
|
import { parse as parseYaml5 } from "yaml";
|
|
21473
23000
|
function flatten(obj, prefix3 = "") {
|
|
@@ -21599,14 +23126,14 @@ async function runConfigRefresh(opts = {}) {
|
|
|
21599
23126
|
const configPath = resolve19(cwd, "massu.config.yaml");
|
|
21600
23127
|
const log = opts.silent ? () => {
|
|
21601
23128
|
} : (s) => process.stdout.write(s);
|
|
21602
|
-
if (!
|
|
23129
|
+
if (!existsSync37(configPath)) {
|
|
21603
23130
|
const message = "massu.config.yaml not found. Run: npx massu init";
|
|
21604
23131
|
if (!opts.silent) process.stderr.write(message + "\n");
|
|
21605
23132
|
return { exitCode: 1, applied: false, dryRun: !!opts.dryRun, diff: [], message };
|
|
21606
23133
|
}
|
|
21607
23134
|
let existing;
|
|
21608
23135
|
try {
|
|
21609
|
-
const content =
|
|
23136
|
+
const content = readFileSync29(configPath, "utf-8");
|
|
21610
23137
|
const parsed = parseYaml5(content);
|
|
21611
23138
|
if (!parsed || typeof parsed !== "object") {
|
|
21612
23139
|
throw new Error("config is not a YAML object");
|
|
@@ -21680,7 +23207,7 @@ async function runConfigRefresh(opts = {}) {
|
|
|
21680
23207
|
const stackResolved = installResult.totalInstalled > 0 || installResult.totalUpdated > 0;
|
|
21681
23208
|
if (stackResolved) {
|
|
21682
23209
|
const placeholderPath = resolve19(installResult.claudeDir, "commands", "_massu-needs-stack.md");
|
|
21683
|
-
if (
|
|
23210
|
+
if (existsSync37(placeholderPath)) {
|
|
21684
23211
|
try {
|
|
21685
23212
|
rmSync8(placeholderPath, { force: true });
|
|
21686
23213
|
log("Removed _massu-needs-stack.md (stack now declared).\n");
|
|
@@ -21789,12 +23316,12 @@ var init_gitToplevel = __esm({
|
|
|
21789
23316
|
});
|
|
21790
23317
|
|
|
21791
23318
|
// src/watch/lockfile-detector.ts
|
|
21792
|
-
import { existsSync as
|
|
23319
|
+
import { existsSync as existsSync38, statSync as statSync14 } from "fs";
|
|
21793
23320
|
import { resolve as resolve20 } from "path";
|
|
21794
23321
|
function lockfileMidWrite(projectRoot, now = Date.now(), windowMs = LOCKFILE_WINDOW_MS) {
|
|
21795
23322
|
for (const lf of KNOWN_LOCKFILES) {
|
|
21796
23323
|
const p19 = resolve20(projectRoot, lf);
|
|
21797
|
-
if (!
|
|
23324
|
+
if (!existsSync38(p19)) continue;
|
|
21798
23325
|
try {
|
|
21799
23326
|
const stat = statSync14(p19);
|
|
21800
23327
|
const delta = now - stat.mtimeMs;
|
|
@@ -21807,7 +23334,7 @@ function lockfileMidWrite(projectRoot, now = Date.now(), windowMs = LOCKFILE_WIN
|
|
|
21807
23334
|
function gitMidOperation(projectRoot) {
|
|
21808
23335
|
const sentinels = ["MERGE_HEAD", "REBASE_HEAD", "CHERRY_PICK_HEAD", "rebase-apply", "rebase-merge"];
|
|
21809
23336
|
for (const s of sentinels) {
|
|
21810
|
-
if (
|
|
23337
|
+
if (existsSync38(resolve20(projectRoot, ".git", s))) return true;
|
|
21811
23338
|
}
|
|
21812
23339
|
return false;
|
|
21813
23340
|
}
|
|
@@ -22006,8 +23533,8 @@ var init_paths = __esm({
|
|
|
22006
23533
|
});
|
|
22007
23534
|
|
|
22008
23535
|
// src/watch/state.ts
|
|
22009
|
-
import { closeSync as closeSync4, existsSync as
|
|
22010
|
-
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";
|
|
22011
23538
|
function watchStatePath(projectRoot) {
|
|
22012
23539
|
return resolve21(projectRoot, ".massu", "watch-state.json");
|
|
22013
23540
|
}
|
|
@@ -22016,8 +23543,8 @@ function backupStatePath(projectRoot) {
|
|
|
22016
23543
|
}
|
|
22017
23544
|
function readState(projectRoot) {
|
|
22018
23545
|
const path = watchStatePath(projectRoot);
|
|
22019
|
-
if (!
|
|
22020
|
-
const content =
|
|
23546
|
+
if (!existsSync39(path)) return { ...DEFAULT_STATE };
|
|
23547
|
+
const content = readFileSync30(path, "utf-8");
|
|
22021
23548
|
let raw;
|
|
22022
23549
|
try {
|
|
22023
23550
|
raw = JSON.parse(content);
|
|
@@ -22059,14 +23586,14 @@ function readState(projectRoot) {
|
|
|
22059
23586
|
}
|
|
22060
23587
|
function archiveCorrupt(projectRoot, content) {
|
|
22061
23588
|
const bak = backupStatePath(projectRoot);
|
|
22062
|
-
|
|
22063
|
-
|
|
23589
|
+
mkdirSync17(dirname19(bak), { recursive: true });
|
|
23590
|
+
writeFileSync13(bak, content, "utf-8");
|
|
22064
23591
|
}
|
|
22065
23592
|
function writeStateAtomic(projectRoot, state) {
|
|
22066
23593
|
const path = watchStatePath(projectRoot);
|
|
22067
23594
|
writeStateAtomicCounter = writeStateAtomicCounter + 1 >>> 0;
|
|
22068
23595
|
const tmp = `${path}.${process.pid}.${writeStateAtomicCounter}.tmp`;
|
|
22069
|
-
|
|
23596
|
+
mkdirSync17(dirname19(path), { recursive: true });
|
|
22070
23597
|
let renamed = false;
|
|
22071
23598
|
try {
|
|
22072
23599
|
const fd = openSync4(tmp, "w");
|
|
@@ -22080,7 +23607,7 @@ function writeStateAtomic(projectRoot, state) {
|
|
|
22080
23607
|
renameSync5(tmp, path);
|
|
22081
23608
|
renamed = true;
|
|
22082
23609
|
} finally {
|
|
22083
|
-
if (!renamed &&
|
|
23610
|
+
if (!renamed && existsSync39(tmp)) {
|
|
22084
23611
|
try {
|
|
22085
23612
|
rmSync9(tmp, { force: true });
|
|
22086
23613
|
} catch {
|
|
@@ -22362,8 +23889,8 @@ __export(watch_exports, {
|
|
|
22362
23889
|
runWatch: () => runWatch
|
|
22363
23890
|
});
|
|
22364
23891
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
22365
|
-
import { basename as basename8, dirname as
|
|
22366
|
-
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";
|
|
22367
23894
|
function parseFlags(args2) {
|
|
22368
23895
|
const out = {
|
|
22369
23896
|
foreground: false,
|
|
@@ -22389,7 +23916,7 @@ function parseFlags(args2) {
|
|
|
22389
23916
|
function findClaudeBg() {
|
|
22390
23917
|
const home = process.env.HOME ?? "";
|
|
22391
23918
|
const fixed = home ? resolve22(home, ".claude", "bin", "claude-bg") : null;
|
|
22392
|
-
if (fixed &&
|
|
23919
|
+
if (fixed && existsSync40(fixed)) return fixed;
|
|
22393
23920
|
const which = spawnSync3("which", ["claude-bg"], { encoding: "utf-8" });
|
|
22394
23921
|
if (which.status === 0 && which.stdout) {
|
|
22395
23922
|
const p19 = which.stdout.trim();
|
|
@@ -22450,7 +23977,7 @@ async function runWatch(args2) {
|
|
|
22450
23977
|
}
|
|
22451
23978
|
function runStatus(root) {
|
|
22452
23979
|
const path = watchStatePath(root);
|
|
22453
|
-
if (!
|
|
23980
|
+
if (!existsSync40(path)) {
|
|
22454
23981
|
process.stdout.write("massu watch: not running (no state file)\n");
|
|
22455
23982
|
return { exitCode: 0 };
|
|
22456
23983
|
}
|
|
@@ -22608,18 +24135,18 @@ function refreshLogPath(projectRoot) {
|
|
|
22608
24135
|
function appendRefreshLog(projectRoot, event) {
|
|
22609
24136
|
const path = refreshLogPath(projectRoot);
|
|
22610
24137
|
try {
|
|
22611
|
-
|
|
24138
|
+
mkdirSync18(dirname20(path), { recursive: true });
|
|
22612
24139
|
appendFileSync3(path, JSON.stringify(event) + "\n", "utf-8");
|
|
22613
24140
|
} catch {
|
|
22614
24141
|
}
|
|
22615
24142
|
}
|
|
22616
24143
|
function readRefreshLog(projectRoot, limit = 10, opts = {}) {
|
|
22617
24144
|
const path = refreshLogPath(projectRoot);
|
|
22618
|
-
if (!
|
|
24145
|
+
if (!existsSync40(path)) return [];
|
|
22619
24146
|
const warn = opts.warn ?? ((s) => {
|
|
22620
24147
|
process.stderr.write(s);
|
|
22621
24148
|
});
|
|
22622
|
-
const lines =
|
|
24149
|
+
const lines = readFileSync31(path, "utf-8").split("\n").filter(Boolean);
|
|
22623
24150
|
const tail = lines.slice(-limit);
|
|
22624
24151
|
const out = [];
|
|
22625
24152
|
let corrupt = 0;
|
|
@@ -22692,26 +24219,26 @@ var init_refresh_log = __esm({
|
|
|
22692
24219
|
import {
|
|
22693
24220
|
chmodSync as chmodSync6,
|
|
22694
24221
|
closeSync as closeSync5,
|
|
22695
|
-
existsSync as
|
|
24222
|
+
existsSync as existsSync41,
|
|
22696
24223
|
fsyncSync as fsyncSync5,
|
|
22697
|
-
mkdirSync as
|
|
24224
|
+
mkdirSync as mkdirSync19,
|
|
22698
24225
|
openSync as openSync5,
|
|
22699
24226
|
renameSync as renameSync6,
|
|
22700
24227
|
rmSync as rmSync10,
|
|
22701
24228
|
statSync as statSync15,
|
|
22702
24229
|
writeSync as writeSync4
|
|
22703
24230
|
} from "node:fs";
|
|
22704
|
-
import { dirname as
|
|
24231
|
+
import { dirname as dirname21 } from "node:path";
|
|
22705
24232
|
function atomicWrite(path, content, opts = {}) {
|
|
22706
24233
|
const tmpPath = `${path}.tmp`;
|
|
22707
|
-
const parentDir =
|
|
24234
|
+
const parentDir = dirname21(path);
|
|
22708
24235
|
try {
|
|
22709
|
-
if (!
|
|
24236
|
+
if (!existsSync41(parentDir)) {
|
|
22710
24237
|
const mkdirOpts = { recursive: true };
|
|
22711
24238
|
if (opts.ensureParentDirMode !== void 0) {
|
|
22712
24239
|
mkdirOpts.mode = opts.ensureParentDirMode;
|
|
22713
24240
|
}
|
|
22714
|
-
|
|
24241
|
+
mkdirSync19(parentDir, mkdirOpts);
|
|
22715
24242
|
}
|
|
22716
24243
|
const buf = typeof content === "string" ? Buffer.from(content, "utf-8") : content;
|
|
22717
24244
|
const openMode = opts.mode ?? 420;
|
|
@@ -22728,7 +24255,7 @@ function atomicWrite(path, content, opts = {}) {
|
|
|
22728
24255
|
renameSync6(tmpPath, path);
|
|
22729
24256
|
return { written: true };
|
|
22730
24257
|
} catch (err) {
|
|
22731
|
-
if (
|
|
24258
|
+
if (existsSync41(tmpPath)) {
|
|
22732
24259
|
try {
|
|
22733
24260
|
rmSync10(tmpPath, { force: true });
|
|
22734
24261
|
} catch {
|
|
@@ -22808,10 +24335,10 @@ var init_registry_pubkey_generated = __esm({
|
|
|
22808
24335
|
});
|
|
22809
24336
|
|
|
22810
24337
|
// src/security/adapter-verifier.ts
|
|
22811
|
-
import { createHash as
|
|
24338
|
+
import { createHash as createHash11 } from "node:crypto";
|
|
22812
24339
|
import nacl from "tweetnacl";
|
|
22813
24340
|
function sha256Hex2(bytes) {
|
|
22814
|
-
return
|
|
24341
|
+
return createHash11("sha256").update(bytes).digest("hex");
|
|
22815
24342
|
}
|
|
22816
24343
|
function reviver(key, value) {
|
|
22817
24344
|
if (key === "__proto__" || key === "constructor" || key === "prototype") {
|
|
@@ -23077,24 +24604,24 @@ var init_fetcher = __esm({
|
|
|
23077
24604
|
});
|
|
23078
24605
|
|
|
23079
24606
|
// src/security/manifest-cache.ts
|
|
23080
|
-
import { existsSync as
|
|
23081
|
-
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";
|
|
23082
24609
|
import { resolve as resolve23 } from "node:path";
|
|
23083
24610
|
import { z as z5 } from "zod";
|
|
23084
24611
|
function defaultCachePaths() {
|
|
23085
|
-
const dir = resolve23(
|
|
24612
|
+
const dir = resolve23(homedir13(), ".massu");
|
|
23086
24613
|
return {
|
|
23087
24614
|
cachePath: resolve23(dir, "adapter-manifest.json"),
|
|
23088
24615
|
lockPath: resolve23(dir, ".adapter-manifest.lock")
|
|
23089
24616
|
};
|
|
23090
24617
|
}
|
|
23091
24618
|
function loadCachedManifest(paths = defaultCachePaths()) {
|
|
23092
|
-
if (!
|
|
24619
|
+
if (!existsSync42(paths.cachePath)) {
|
|
23093
24620
|
return { kind: "absent" };
|
|
23094
24621
|
}
|
|
23095
24622
|
let raw;
|
|
23096
24623
|
try {
|
|
23097
|
-
const content =
|
|
24624
|
+
const content = readFileSync32(paths.cachePath, "utf-8");
|
|
23098
24625
|
raw = JSON.parse(content);
|
|
23099
24626
|
} catch (err) {
|
|
23100
24627
|
return {
|
|
@@ -23283,15 +24810,15 @@ var init_adapter_origin = __esm({
|
|
|
23283
24810
|
});
|
|
23284
24811
|
|
|
23285
24812
|
// src/security/local-fingerprint.ts
|
|
23286
|
-
import { existsSync as
|
|
23287
|
-
import { homedir as
|
|
23288
|
-
import { resolve as resolve24, isAbsolute as
|
|
23289
|
-
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";
|
|
23290
24817
|
import { z as z6 } from "zod";
|
|
23291
24818
|
function computeLocalFingerprint(localPaths, projectRoot) {
|
|
23292
24819
|
const tuples = [];
|
|
23293
24820
|
for (const p19 of localPaths) {
|
|
23294
|
-
const abs =
|
|
24821
|
+
const abs = isAbsolute3(p19) ? p19 : resolve24(projectRoot, p19);
|
|
23295
24822
|
let contentTag;
|
|
23296
24823
|
try {
|
|
23297
24824
|
const lst = lstatSync5(abs);
|
|
@@ -23300,7 +24827,7 @@ function computeLocalFingerprint(localPaths, projectRoot) {
|
|
|
23300
24827
|
} else if (!lst.isFile()) {
|
|
23301
24828
|
contentTag = "<not-a-file>";
|
|
23302
24829
|
} else {
|
|
23303
|
-
contentTag =
|
|
24830
|
+
contentTag = createHash12("sha256").update(readFileSync33(abs)).digest("hex");
|
|
23304
24831
|
}
|
|
23305
24832
|
} catch {
|
|
23306
24833
|
contentTag = "<missing>";
|
|
@@ -23309,13 +24836,13 @@ function computeLocalFingerprint(localPaths, projectRoot) {
|
|
|
23309
24836
|
}
|
|
23310
24837
|
tuples.sort((a2, b2) => a2.path < b2.path ? -1 : a2.path > b2.path ? 1 : 0);
|
|
23311
24838
|
const canonical = JSON.stringify(tuples);
|
|
23312
|
-
return
|
|
24839
|
+
return createHash12("sha256").update(canonical).digest("hex");
|
|
23313
24840
|
}
|
|
23314
24841
|
function readFingerprintSentinel(path = FINGERPRINT_PATH) {
|
|
23315
|
-
if (!
|
|
24842
|
+
if (!existsSync43(path)) return null;
|
|
23316
24843
|
let raw;
|
|
23317
24844
|
try {
|
|
23318
|
-
raw = JSON.parse(
|
|
24845
|
+
raw = JSON.parse(readFileSync33(path, "utf-8"));
|
|
23319
24846
|
} catch {
|
|
23320
24847
|
return null;
|
|
23321
24848
|
}
|
|
@@ -23363,7 +24890,7 @@ var init_local_fingerprint = __esm({
|
|
|
23363
24890
|
"use strict";
|
|
23364
24891
|
init_atomic_write();
|
|
23365
24892
|
init_manifest_schema();
|
|
23366
|
-
FINGERPRINT_PATH = resolve24(
|
|
24893
|
+
FINGERPRINT_PATH = resolve24(homedir14(), ".massu", "adapters-local-fingerprint.json");
|
|
23367
24894
|
FingerprintSentinelSchema = z6.object({
|
|
23368
24895
|
fingerprint: z6.string().regex(/^[0-9a-f]{64}$/),
|
|
23369
24896
|
source: z6.enum(["cli", "cli-resync"]),
|
|
@@ -23379,15 +24906,15 @@ var init_local_fingerprint = __esm({
|
|
|
23379
24906
|
});
|
|
23380
24907
|
|
|
23381
24908
|
// src/security/install-tracking.ts
|
|
23382
|
-
import { readFileSync as
|
|
23383
|
-
import { join as
|
|
23384
|
-
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";
|
|
23385
24912
|
import { resolve as resolve25 } from "node:path";
|
|
23386
|
-
import { createHash as
|
|
24913
|
+
import { createHash as createHash13 } from "node:crypto";
|
|
23387
24914
|
import { z as z7 } from "zod";
|
|
23388
24915
|
function containsHiddenDirs(packageDir) {
|
|
23389
24916
|
for (const hidden of EXCLUDED_DIR_NAMES) {
|
|
23390
|
-
if (
|
|
24917
|
+
if (existsSync44(`${packageDir}/${hidden}`)) {
|
|
23391
24918
|
return hidden;
|
|
23392
24919
|
}
|
|
23393
24920
|
}
|
|
@@ -23404,7 +24931,7 @@ function sha256OfDir(dir, opts = {}) {
|
|
|
23404
24931
|
return;
|
|
23405
24932
|
}
|
|
23406
24933
|
for (const entry of entries.sort()) {
|
|
23407
|
-
const absPath =
|
|
24934
|
+
const absPath = join24(currentDir, entry);
|
|
23408
24935
|
let lst;
|
|
23409
24936
|
try {
|
|
23410
24937
|
lst = lstatSync6(absPath);
|
|
@@ -23425,15 +24952,15 @@ function sha256OfDir(dir, opts = {}) {
|
|
|
23425
24952
|
`sha256OfDir: file ${absPath} exceeds maxFileBytes (${lst.size} > ${maxFileBytes}); adapter packages should not ship files this large.`
|
|
23426
24953
|
);
|
|
23427
24954
|
}
|
|
23428
|
-
const rel =
|
|
24955
|
+
const rel = relative7(dir, absPath).split(sep2).join("/");
|
|
23429
24956
|
files.push({ relativePath: rel, absPath });
|
|
23430
24957
|
}
|
|
23431
24958
|
}
|
|
23432
24959
|
walk(dir);
|
|
23433
24960
|
files.sort((a2, b2) => a2.relativePath < b2.relativePath ? -1 : a2.relativePath > b2.relativePath ? 1 : 0);
|
|
23434
|
-
const top =
|
|
24961
|
+
const top = createHash13("sha256");
|
|
23435
24962
|
for (const f2 of files) {
|
|
23436
|
-
const fileHash =
|
|
24963
|
+
const fileHash = createHash13("sha256").update(readFileSync34(f2.absPath)).digest("hex");
|
|
23437
24964
|
top.update(f2.relativePath, "utf-8");
|
|
23438
24965
|
top.update("\0", "utf-8");
|
|
23439
24966
|
top.update(fileHash, "utf-8");
|
|
@@ -23442,10 +24969,10 @@ function sha256OfDir(dir, opts = {}) {
|
|
|
23442
24969
|
return top.digest("hex");
|
|
23443
24970
|
}
|
|
23444
24971
|
function readInstalledManifest(path = INSTALLED_MANIFEST_PATH) {
|
|
23445
|
-
if (!
|
|
24972
|
+
if (!existsSync44(path)) return {};
|
|
23446
24973
|
let raw;
|
|
23447
24974
|
try {
|
|
23448
|
-
raw = JSON.parse(
|
|
24975
|
+
raw = JSON.parse(readFileSync34(path, "utf-8"));
|
|
23449
24976
|
} catch {
|
|
23450
24977
|
return {};
|
|
23451
24978
|
}
|
|
@@ -23509,7 +25036,7 @@ var init_install_tracking = __esm({
|
|
|
23509
25036
|
"src/security/install-tracking.ts"() {
|
|
23510
25037
|
"use strict";
|
|
23511
25038
|
init_atomic_write();
|
|
23512
|
-
INSTALLED_MANIFEST_PATH = resolve25(
|
|
25039
|
+
INSTALLED_MANIFEST_PATH = resolve25(homedir15(), ".massu", "adapter-manifest-installed.json");
|
|
23513
25040
|
DEFAULT_MAX_FILE_BYTES = 64 * 1024 * 1024;
|
|
23514
25041
|
EXCLUDED_DIR_NAMES = /* @__PURE__ */ new Set([".git", "node_modules", ".cache", ".tmp"]);
|
|
23515
25042
|
InstallEntrySchema = z7.object({
|
|
@@ -23532,12 +25059,12 @@ var init_install_tracking = __esm({
|
|
|
23532
25059
|
});
|
|
23533
25060
|
|
|
23534
25061
|
// src/detect/adapters/discover.ts
|
|
23535
|
-
import { existsSync as
|
|
23536
|
-
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";
|
|
23537
25064
|
import { z as z8 } from "zod";
|
|
23538
25065
|
function walkNodeModules(projectRoot, warnings) {
|
|
23539
25066
|
const nodeModulesDir = resolve26(projectRoot, "node_modules");
|
|
23540
|
-
if (!
|
|
25067
|
+
if (!existsSync45(nodeModulesDir)) {
|
|
23541
25068
|
return [];
|
|
23542
25069
|
}
|
|
23543
25070
|
const candidates = [];
|
|
@@ -23592,10 +25119,10 @@ function walkNodeModules(projectRoot, warnings) {
|
|
|
23592
25119
|
}
|
|
23593
25120
|
function tryReadAdapterPackage(packageDir, warnings) {
|
|
23594
25121
|
const pkgJsonPath = resolve26(packageDir, "package.json");
|
|
23595
|
-
if (!
|
|
25122
|
+
if (!existsSync45(pkgJsonPath)) return null;
|
|
23596
25123
|
let raw;
|
|
23597
25124
|
try {
|
|
23598
|
-
raw = JSON.parse(
|
|
25125
|
+
raw = JSON.parse(readFileSync35(pkgJsonPath, "utf-8"));
|
|
23599
25126
|
} catch (err) {
|
|
23600
25127
|
warnings.push(
|
|
23601
25128
|
`skipping ${packageDir}: package.json parse failed (${err instanceof Error ? err.message : String(err)})`
|
|
@@ -23729,8 +25256,8 @@ function discoverAdapters(opts) {
|
|
|
23729
25256
|
const localSet = new Set(opts.configLocalPaths);
|
|
23730
25257
|
for (const localPath of opts.configLocalPaths) {
|
|
23731
25258
|
if (seenIds.has(localPath)) continue;
|
|
23732
|
-
const absPath =
|
|
23733
|
-
if (!
|
|
25259
|
+
const absPath = isAbsolute4(localPath) ? localPath : resolve26(opts.projectRoot, localPath);
|
|
25260
|
+
if (!existsSync45(absPath)) {
|
|
23734
25261
|
warnings.push(
|
|
23735
25262
|
`local adapter file not found: ${localPath} (resolved to ${absPath}). Remove via: massu adapters remove-local ${localPath}`
|
|
23736
25263
|
);
|
|
@@ -23812,7 +25339,7 @@ __export(adapters_exports, {
|
|
|
23812
25339
|
runAdaptersResyncLocalFingerprint: () => runAdaptersResyncLocalFingerprint,
|
|
23813
25340
|
runAdaptersSearch: () => runAdaptersSearch
|
|
23814
25341
|
});
|
|
23815
|
-
import { existsSync as
|
|
25342
|
+
import { existsSync as existsSync46, readFileSync as readFileSync36 } from "node:fs";
|
|
23816
25343
|
import { resolve as resolve27 } from "node:path";
|
|
23817
25344
|
import { parseDocument } from "yaml";
|
|
23818
25345
|
async function handleAdaptersSubcommand(args2) {
|
|
@@ -24081,7 +25608,7 @@ function mutateLocalArray(mutator, command) {
|
|
|
24081
25608
|
return { exitCode: 2 };
|
|
24082
25609
|
}
|
|
24083
25610
|
const yamlPath = resolve27(projectRoot, "massu.config.yaml");
|
|
24084
|
-
if (!
|
|
25611
|
+
if (!existsSync46(yamlPath)) {
|
|
24085
25612
|
process.stderr.write(
|
|
24086
25613
|
`${command}: massu.config.yaml not found at ${yamlPath}. Run \`massu init\` first.
|
|
24087
25614
|
`
|
|
@@ -24090,7 +25617,7 @@ function mutateLocalArray(mutator, command) {
|
|
|
24090
25617
|
}
|
|
24091
25618
|
let yamlText;
|
|
24092
25619
|
try {
|
|
24093
|
-
yamlText =
|
|
25620
|
+
yamlText = readFileSync36(yamlPath, "utf-8");
|
|
24094
25621
|
} catch (err) {
|
|
24095
25622
|
process.stderr.write(`${command}: failed to read ${yamlPath}: ${err instanceof Error ? err.message : String(err)}
|
|
24096
25623
|
`);
|
|
@@ -24181,7 +25708,7 @@ async function runAdaptersInstall(args2) {
|
|
|
24181
25708
|
return { exitCode: 1 };
|
|
24182
25709
|
}
|
|
24183
25710
|
const packageDir = resolve27(projectRoot, "node_modules", ...packageName.split("/"));
|
|
24184
|
-
if (!
|
|
25711
|
+
if (!existsSync46(packageDir)) {
|
|
24185
25712
|
process.stderr.write(
|
|
24186
25713
|
`install: ${packageName} is not installed in node_modules. Run \`npm install ${packageName}\` first.
|
|
24187
25714
|
`
|
|
@@ -24189,14 +25716,14 @@ async function runAdaptersInstall(args2) {
|
|
|
24189
25716
|
return { exitCode: 1 };
|
|
24190
25717
|
}
|
|
24191
25718
|
const pkgJsonPath = resolve27(packageDir, "package.json");
|
|
24192
|
-
if (!
|
|
25719
|
+
if (!existsSync46(pkgJsonPath)) {
|
|
24193
25720
|
process.stderr.write(`install: ${packageName} has no package.json at ${pkgJsonPath}
|
|
24194
25721
|
`);
|
|
24195
25722
|
return { exitCode: 1 };
|
|
24196
25723
|
}
|
|
24197
25724
|
let pkgJson;
|
|
24198
25725
|
try {
|
|
24199
|
-
pkgJson = JSON.parse(
|
|
25726
|
+
pkgJson = JSON.parse(readFileSync36(pkgJsonPath, "utf-8"));
|
|
24200
25727
|
} catch (err) {
|
|
24201
25728
|
process.stderr.write(`install: ${packageName} has malformed package.json: ${err instanceof Error ? err.message : String(err)}
|
|
24202
25729
|
`);
|
|
@@ -24316,7 +25843,7 @@ async function runAdaptersResign(_args) {
|
|
|
24316
25843
|
continue;
|
|
24317
25844
|
}
|
|
24318
25845
|
const packageDir = resolve27(projectRoot, "node_modules", ...name.split("/"));
|
|
24319
|
-
if (!
|
|
25846
|
+
if (!existsSync46(packageDir)) {
|
|
24320
25847
|
removed++;
|
|
24321
25848
|
warnings.push(`${name}@${entry.version}: not present in node_modules \u2014 REMOVED from sidecar`);
|
|
24322
25849
|
removeInstalledManifestEntry(name);
|
|
@@ -24403,10 +25930,10 @@ var init_adapters2 = __esm({
|
|
|
24403
25930
|
|
|
24404
25931
|
// src/preflight.ts
|
|
24405
25932
|
import Database4 from "better-sqlite3";
|
|
24406
|
-
import { existsSync as
|
|
25933
|
+
import { existsSync as existsSync47 } from "fs";
|
|
24407
25934
|
function assertCodegraphUsable(dbPath) {
|
|
24408
25935
|
const path = dbPath ?? getResolvedPaths().codegraphDbPath;
|
|
24409
|
-
if (!
|
|
25936
|
+
if (!existsSync47(path)) {
|
|
24410
25937
|
throw new CodegraphDbUnusableError(path, "missing");
|
|
24411
25938
|
}
|
|
24412
25939
|
let db = null;
|
|
@@ -24495,8 +26022,8 @@ var init_sql_table_names = __esm({
|
|
|
24495
26022
|
|
|
24496
26023
|
// src/db.ts
|
|
24497
26024
|
import Database5 from "better-sqlite3";
|
|
24498
|
-
import { dirname as
|
|
24499
|
-
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";
|
|
24500
26027
|
function getCodeGraphDb() {
|
|
24501
26028
|
const dbPath = getResolvedPaths().codegraphDbPath;
|
|
24502
26029
|
assertCodegraphUsable(dbPath);
|
|
@@ -24506,9 +26033,9 @@ function getCodeGraphDb() {
|
|
|
24506
26033
|
}
|
|
24507
26034
|
function getDataDb() {
|
|
24508
26035
|
const dbPath = getResolvedPaths().dataDbPath;
|
|
24509
|
-
const dir =
|
|
24510
|
-
if (!
|
|
24511
|
-
|
|
26036
|
+
const dir = dirname22(dbPath);
|
|
26037
|
+
if (!existsSync48(dir)) {
|
|
26038
|
+
mkdirSync20(dir, { recursive: true });
|
|
24512
26039
|
}
|
|
24513
26040
|
const db = new Database5(dbPath);
|
|
24514
26041
|
db.pragma("journal_mode = WAL");
|
|
@@ -24780,7 +26307,7 @@ function isPythonDataStale(dataDb, pythonRoot) {
|
|
|
24780
26307
|
try {
|
|
24781
26308
|
const entries = readdirSync18(dir, { withFileTypes: true });
|
|
24782
26309
|
for (const entry of entries) {
|
|
24783
|
-
const fullPath =
|
|
26310
|
+
const fullPath = join25(dir, entry.name);
|
|
24784
26311
|
if (entry.isDirectory()) {
|
|
24785
26312
|
if (["__pycache__", ".venv", "venv", "node_modules", ".mypy_cache", ".pytest_cache"].includes(entry.name)) continue;
|
|
24786
26313
|
if (checkDir(fullPath)) return true;
|
|
@@ -24884,8 +26411,8 @@ var init_rules = __esm({
|
|
|
24884
26411
|
});
|
|
24885
26412
|
|
|
24886
26413
|
// src/import-resolver.ts
|
|
24887
|
-
import { readFileSync as
|
|
24888
|
-
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";
|
|
24889
26416
|
function parseImports(source) {
|
|
24890
26417
|
const imports = [];
|
|
24891
26418
|
const lines = source.split("\n");
|
|
@@ -24943,21 +26470,21 @@ function resolveImportPath(specifier, fromFile) {
|
|
|
24943
26470
|
const paths = getResolvedPaths();
|
|
24944
26471
|
basePath = resolve29(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
|
|
24945
26472
|
} else {
|
|
24946
|
-
basePath = resolve29(
|
|
26473
|
+
basePath = resolve29(dirname23(fromFile), specifier);
|
|
24947
26474
|
}
|
|
24948
|
-
if (
|
|
26475
|
+
if (existsSync49(basePath) && !isDirectory(basePath)) {
|
|
24949
26476
|
return toRelative(basePath);
|
|
24950
26477
|
}
|
|
24951
26478
|
const resolvedPaths = getResolvedPaths();
|
|
24952
26479
|
for (const ext of resolvedPaths.extensions) {
|
|
24953
26480
|
const withExt = basePath + ext;
|
|
24954
|
-
if (
|
|
26481
|
+
if (existsSync49(withExt)) {
|
|
24955
26482
|
return toRelative(withExt);
|
|
24956
26483
|
}
|
|
24957
26484
|
}
|
|
24958
26485
|
for (const indexFile of resolvedPaths.indexFiles) {
|
|
24959
|
-
const indexPath =
|
|
24960
|
-
if (
|
|
26486
|
+
const indexPath = join26(basePath, indexFile);
|
|
26487
|
+
if (existsSync49(indexPath)) {
|
|
24961
26488
|
return toRelative(indexPath);
|
|
24962
26489
|
}
|
|
24963
26490
|
}
|
|
@@ -24994,10 +26521,10 @@ function buildImportIndex(dataDb, codegraphDb) {
|
|
|
24994
26521
|
let batch = [];
|
|
24995
26522
|
for (const file of files) {
|
|
24996
26523
|
const absPath = ensureWithinRoot(resolve29(projectRoot, file.path), projectRoot);
|
|
24997
|
-
if (!
|
|
26524
|
+
if (!existsSync49(absPath)) continue;
|
|
24998
26525
|
let source;
|
|
24999
26526
|
try {
|
|
25000
|
-
source =
|
|
26527
|
+
source = readFileSync37(absPath, "utf-8");
|
|
25001
26528
|
} catch {
|
|
25002
26529
|
continue;
|
|
25003
26530
|
}
|
|
@@ -25034,15 +26561,15 @@ var init_import_resolver = __esm({
|
|
|
25034
26561
|
});
|
|
25035
26562
|
|
|
25036
26563
|
// src/trpc-index.ts
|
|
25037
|
-
import { readFileSync as
|
|
25038
|
-
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";
|
|
25039
26566
|
function parseRootRouter() {
|
|
25040
26567
|
const paths = getResolvedPaths();
|
|
25041
26568
|
const rootPath = paths.rootRouterPath;
|
|
25042
|
-
if (!
|
|
26569
|
+
if (!existsSync50(rootPath)) {
|
|
25043
26570
|
throw new Error(`Root router not found at ${rootPath}`);
|
|
25044
26571
|
}
|
|
25045
|
-
const source =
|
|
26572
|
+
const source = readFileSync38(rootPath, "utf-8");
|
|
25046
26573
|
const mappings = [];
|
|
25047
26574
|
const importMap = /* @__PURE__ */ new Map();
|
|
25048
26575
|
const importRegex = /import\s+\{[^}]*?(\w+Router)[^}]*\}\s+from\s+['"]\.\/routers\/([^'"]+)['"]/g;
|
|
@@ -25054,12 +26581,12 @@ function parseRootRouter() {
|
|
|
25054
26581
|
for (const ext of [".ts", ".tsx", ""]) {
|
|
25055
26582
|
const candidate = fullPath + ext;
|
|
25056
26583
|
const routersRelPath = getConfig().paths.routers ?? "src/server/api/routers";
|
|
25057
|
-
if (
|
|
26584
|
+
if (existsSync50(candidate)) {
|
|
25058
26585
|
filePath = routersRelPath + "/" + filePath + ext;
|
|
25059
26586
|
break;
|
|
25060
26587
|
}
|
|
25061
|
-
const indexCandidate =
|
|
25062
|
-
if (
|
|
26588
|
+
const indexCandidate = join27(fullPath, "index.ts");
|
|
26589
|
+
if (existsSync50(indexCandidate)) {
|
|
25063
26590
|
filePath = routersRelPath + "/" + filePath + "/index.ts";
|
|
25064
26591
|
break;
|
|
25065
26592
|
}
|
|
@@ -25079,8 +26606,8 @@ function parseRootRouter() {
|
|
|
25079
26606
|
}
|
|
25080
26607
|
function extractProcedures(routerFilePath) {
|
|
25081
26608
|
const absPath = resolve30(getProjectRoot(), routerFilePath);
|
|
25082
|
-
if (!
|
|
25083
|
-
const source =
|
|
26609
|
+
if (!existsSync50(absPath)) return [];
|
|
26610
|
+
const source = readFileSync38(absPath, "utf-8");
|
|
25084
26611
|
const procedures = [];
|
|
25085
26612
|
const seen = /* @__PURE__ */ new Set();
|
|
25086
26613
|
const procRegex = /(\w+)\s*:\s*(protected|public)Procedure/g;
|
|
@@ -25109,7 +26636,7 @@ function findUICallSites(routerKey, procedureName) {
|
|
|
25109
26636
|
];
|
|
25110
26637
|
const searchPattern = `api.${routerKey}.${procedureName}`;
|
|
25111
26638
|
for (const dir of searchDirs) {
|
|
25112
|
-
if (!
|
|
26639
|
+
if (!existsSync50(dir)) continue;
|
|
25113
26640
|
searchDirectory(dir, searchPattern, callSites);
|
|
25114
26641
|
}
|
|
25115
26642
|
return callSites;
|
|
@@ -25117,13 +26644,13 @@ function findUICallSites(routerKey, procedureName) {
|
|
|
25117
26644
|
function searchDirectory(dir, pattern, results) {
|
|
25118
26645
|
const entries = readdirSync19(dir, { withFileTypes: true });
|
|
25119
26646
|
for (const entry of entries) {
|
|
25120
|
-
const fullPath =
|
|
26647
|
+
const fullPath = join27(dir, entry.name);
|
|
25121
26648
|
if (entry.isDirectory()) {
|
|
25122
26649
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
25123
26650
|
searchDirectory(fullPath, pattern, results);
|
|
25124
26651
|
} else if (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) {
|
|
25125
26652
|
try {
|
|
25126
|
-
const source =
|
|
26653
|
+
const source = readFileSync38(fullPath, "utf-8");
|
|
25127
26654
|
const lines = source.split("\n");
|
|
25128
26655
|
for (let i = 0; i < lines.length; i++) {
|
|
25129
26656
|
if (lines[i].includes(pattern)) {
|
|
@@ -25187,7 +26714,7 @@ var init_trpc_index = __esm({
|
|
|
25187
26714
|
});
|
|
25188
26715
|
|
|
25189
26716
|
// src/page-deps.ts
|
|
25190
|
-
import { readFileSync as
|
|
26717
|
+
import { readFileSync as readFileSync39, existsSync as existsSync51 } from "fs";
|
|
25191
26718
|
import { resolve as resolve31 } from "path";
|
|
25192
26719
|
function deriveRoute(pageFile) {
|
|
25193
26720
|
let route = pageFile.replace(/^src\/app/, "").replace(/\/page\.tsx?$/, "").replace(/\/page\.jsx?$/, "");
|
|
@@ -25227,9 +26754,9 @@ function findRouterCalls(files) {
|
|
|
25227
26754
|
const projectRoot = getProjectRoot();
|
|
25228
26755
|
for (const file of files) {
|
|
25229
26756
|
const absPath = ensureWithinRoot(resolve31(projectRoot, file), projectRoot);
|
|
25230
|
-
if (!
|
|
26757
|
+
if (!existsSync51(absPath)) continue;
|
|
25231
26758
|
try {
|
|
25232
|
-
const source =
|
|
26759
|
+
const source = readFileSync39(absPath, "utf-8");
|
|
25233
26760
|
const apiCallRegex = /api\.(\w+)\.\w+/g;
|
|
25234
26761
|
let match;
|
|
25235
26762
|
while ((match = apiCallRegex.exec(source)) !== null) {
|
|
@@ -25248,9 +26775,9 @@ function findTablesFromRouters(routerNames, dataDb) {
|
|
|
25248
26775
|
).all(routerName);
|
|
25249
26776
|
for (const proc of procs) {
|
|
25250
26777
|
const absPath = ensureWithinRoot(resolve31(getProjectRoot(), proc.router_file), getProjectRoot());
|
|
25251
|
-
if (!
|
|
26778
|
+
if (!existsSync51(absPath)) continue;
|
|
25252
26779
|
try {
|
|
25253
|
-
const source =
|
|
26780
|
+
const source = readFileSync39(absPath, "utf-8");
|
|
25254
26781
|
const dbPattern = getConfig().dbAccessPattern ?? "ctx.db.{table}";
|
|
25255
26782
|
const regexStr = dbPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace("\\{table\\}", "(\\w+)");
|
|
25256
26783
|
const tableRegex = new RegExp(regexStr + "\\.", "g");
|
|
@@ -25522,14 +27049,14 @@ var init_domains = __esm({
|
|
|
25522
27049
|
});
|
|
25523
27050
|
|
|
25524
27051
|
// src/schema-mapper.ts
|
|
25525
|
-
import { readFileSync as
|
|
25526
|
-
import { join as
|
|
27052
|
+
import { readFileSync as readFileSync40, existsSync as existsSync52, readdirSync as readdirSync20 } from "fs";
|
|
27053
|
+
import { join as join28 } from "path";
|
|
25527
27054
|
function parsePrismaSchema() {
|
|
25528
27055
|
const schemaPath = getResolvedPaths().prismaSchemaPath;
|
|
25529
|
-
if (!
|
|
27056
|
+
if (!existsSync52(schemaPath)) {
|
|
25530
27057
|
throw new Error(`Prisma schema not found at ${schemaPath}`);
|
|
25531
27058
|
}
|
|
25532
|
-
const source =
|
|
27059
|
+
const source = readFileSync40(schemaPath, "utf-8");
|
|
25533
27060
|
const models = [];
|
|
25534
27061
|
const sourceLines = source.split("\n");
|
|
25535
27062
|
let i = 0;
|
|
@@ -25587,14 +27114,14 @@ function toSnakeCase(str) {
|
|
|
25587
27114
|
function findColumnUsageInRouters(tableName) {
|
|
25588
27115
|
const usage2 = /* @__PURE__ */ new Map();
|
|
25589
27116
|
const routersDir = getResolvedPaths().routersDir;
|
|
25590
|
-
if (!
|
|
27117
|
+
if (!existsSync52(routersDir)) return usage2;
|
|
25591
27118
|
scanDirectory(routersDir, tableName, usage2);
|
|
25592
27119
|
return usage2;
|
|
25593
27120
|
}
|
|
25594
27121
|
function scanDirectory(dir, tableName, usage2) {
|
|
25595
27122
|
const entries = readdirSync20(dir, { withFileTypes: true });
|
|
25596
27123
|
for (const entry of entries) {
|
|
25597
|
-
const fullPath =
|
|
27124
|
+
const fullPath = join28(dir, entry.name);
|
|
25598
27125
|
if (entry.isDirectory()) {
|
|
25599
27126
|
scanDirectory(fullPath, tableName, usage2);
|
|
25600
27127
|
} else if (entry.name.endsWith(".ts")) {
|
|
@@ -25604,7 +27131,7 @@ function scanDirectory(dir, tableName, usage2) {
|
|
|
25604
27131
|
}
|
|
25605
27132
|
function scanFile(absPath, tableName, usage2) {
|
|
25606
27133
|
try {
|
|
25607
|
-
const source =
|
|
27134
|
+
const source = readFileSync40(absPath, "utf-8");
|
|
25608
27135
|
if (!source.includes(tableName)) return;
|
|
25609
27136
|
const relPath = absPath.slice(getProjectRoot().length + 1);
|
|
25610
27137
|
const lines = source.split("\n");
|
|
@@ -25649,15 +27176,15 @@ function detectMismatches(models) {
|
|
|
25649
27176
|
}
|
|
25650
27177
|
function findFilesUsingColumn(dir, column, tableName) {
|
|
25651
27178
|
const result = [];
|
|
25652
|
-
if (!
|
|
27179
|
+
if (!existsSync52(dir)) return result;
|
|
25653
27180
|
const entries = readdirSync20(dir, { withFileTypes: true });
|
|
25654
27181
|
for (const entry of entries) {
|
|
25655
|
-
const fullPath =
|
|
27182
|
+
const fullPath = join28(dir, entry.name);
|
|
25656
27183
|
if (entry.isDirectory()) {
|
|
25657
27184
|
result.push(...findFilesUsingColumn(fullPath, column, tableName));
|
|
25658
27185
|
} else if (entry.name.endsWith(".ts")) {
|
|
25659
27186
|
try {
|
|
25660
|
-
const source =
|
|
27187
|
+
const source = readFileSync40(fullPath, "utf-8");
|
|
25661
27188
|
if (source.includes(tableName) && source.includes(column)) {
|
|
25662
27189
|
result.push(fullPath.slice(getProjectRoot().length + 1));
|
|
25663
27190
|
}
|
|
@@ -25802,35 +27329,35 @@ var init_import_parser = __esm({
|
|
|
25802
27329
|
});
|
|
25803
27330
|
|
|
25804
27331
|
// src/python/import-resolver.ts
|
|
25805
|
-
import { readFileSync as
|
|
25806
|
-
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";
|
|
25807
27334
|
function resolvePythonModulePath(module, fromFile, pythonRoot, level) {
|
|
25808
27335
|
const projectRoot = getProjectRoot();
|
|
25809
27336
|
if (level > 0) {
|
|
25810
|
-
let baseDir =
|
|
27337
|
+
let baseDir = dirname24(resolve33(projectRoot, fromFile));
|
|
25811
27338
|
for (let i = 1; i < level; i++) {
|
|
25812
|
-
baseDir =
|
|
27339
|
+
baseDir = dirname24(baseDir);
|
|
25813
27340
|
}
|
|
25814
27341
|
const modulePart = module.replace(/^\.+/, "");
|
|
25815
27342
|
if (modulePart) {
|
|
25816
27343
|
const parts2 = modulePart.split(".");
|
|
25817
|
-
return tryResolvePythonPath(
|
|
27344
|
+
return tryResolvePythonPath(join29(baseDir, ...parts2), projectRoot);
|
|
25818
27345
|
}
|
|
25819
27346
|
return tryResolvePythonPath(baseDir, projectRoot);
|
|
25820
27347
|
}
|
|
25821
27348
|
const parts = module.split(".");
|
|
25822
|
-
const candidate =
|
|
27349
|
+
const candidate = join29(resolve33(projectRoot, pythonRoot), ...parts);
|
|
25823
27350
|
return tryResolvePythonPath(candidate, projectRoot);
|
|
25824
27351
|
}
|
|
25825
27352
|
function tryResolvePythonPath(basePath, projectRoot) {
|
|
25826
|
-
if (
|
|
25827
|
-
return
|
|
27353
|
+
if (existsSync53(basePath + ".py")) {
|
|
27354
|
+
return relative8(projectRoot, basePath + ".py");
|
|
25828
27355
|
}
|
|
25829
|
-
if (
|
|
25830
|
-
return
|
|
27356
|
+
if (existsSync53(join29(basePath, "__init__.py"))) {
|
|
27357
|
+
return relative8(projectRoot, join29(basePath, "__init__.py"));
|
|
25831
27358
|
}
|
|
25832
|
-
if (basePath.endsWith(".py") &&
|
|
25833
|
-
return
|
|
27359
|
+
if (basePath.endsWith(".py") && existsSync53(basePath)) {
|
|
27360
|
+
return relative8(projectRoot, basePath);
|
|
25834
27361
|
}
|
|
25835
27362
|
return null;
|
|
25836
27363
|
}
|
|
@@ -25841,9 +27368,9 @@ function walkPythonFiles(dir, excludeDirs) {
|
|
|
25841
27368
|
for (const entry of entries) {
|
|
25842
27369
|
if (entry.isDirectory()) {
|
|
25843
27370
|
if (excludeDirs.includes(entry.name)) continue;
|
|
25844
|
-
files.push(...walkPythonFiles(
|
|
27371
|
+
files.push(...walkPythonFiles(join29(dir, entry.name), excludeDirs));
|
|
25845
27372
|
} else if (entry.name.endsWith(".py")) {
|
|
25846
|
-
files.push(
|
|
27373
|
+
files.push(join29(dir, entry.name));
|
|
25847
27374
|
}
|
|
25848
27375
|
}
|
|
25849
27376
|
} catch {
|
|
@@ -25866,10 +27393,10 @@ function buildPythonImportIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__"
|
|
|
25866
27393
|
});
|
|
25867
27394
|
const batch = [];
|
|
25868
27395
|
for (const absFile of files) {
|
|
25869
|
-
const relFile =
|
|
27396
|
+
const relFile = relative8(projectRoot, absFile);
|
|
25870
27397
|
let source;
|
|
25871
27398
|
try {
|
|
25872
|
-
source =
|
|
27399
|
+
source = readFileSync41(absFile, "utf-8");
|
|
25873
27400
|
} catch {
|
|
25874
27401
|
continue;
|
|
25875
27402
|
}
|
|
@@ -26136,8 +27663,8 @@ var init_route_parser = __esm({
|
|
|
26136
27663
|
});
|
|
26137
27664
|
|
|
26138
27665
|
// src/python/route-indexer.ts
|
|
26139
|
-
import { readFileSync as
|
|
26140
|
-
import { join as
|
|
27666
|
+
import { readFileSync as readFileSync42, readdirSync as readdirSync22 } from "fs";
|
|
27667
|
+
import { join as join30, relative as relative9 } from "path";
|
|
26141
27668
|
function walkPyFiles(dir, excludeDirs) {
|
|
26142
27669
|
const files = [];
|
|
26143
27670
|
try {
|
|
@@ -26145,9 +27672,9 @@ function walkPyFiles(dir, excludeDirs) {
|
|
|
26145
27672
|
for (const entry of entries) {
|
|
26146
27673
|
if (entry.isDirectory()) {
|
|
26147
27674
|
if (excludeDirs.includes(entry.name)) continue;
|
|
26148
|
-
files.push(...walkPyFiles(
|
|
27675
|
+
files.push(...walkPyFiles(join30(dir, entry.name), excludeDirs));
|
|
26149
27676
|
} else if (entry.name.endsWith(".py")) {
|
|
26150
|
-
files.push(
|
|
27677
|
+
files.push(join30(dir, entry.name));
|
|
26151
27678
|
}
|
|
26152
27679
|
}
|
|
26153
27680
|
} catch {
|
|
@@ -26156,7 +27683,7 @@ function walkPyFiles(dir, excludeDirs) {
|
|
|
26156
27683
|
}
|
|
26157
27684
|
function buildPythonRouteIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]) {
|
|
26158
27685
|
const projectRoot = getProjectRoot();
|
|
26159
|
-
const absRoot =
|
|
27686
|
+
const absRoot = join30(projectRoot, pythonRoot);
|
|
26160
27687
|
dataDb.exec(`DELETE FROM ${t("py_routes")}`);
|
|
26161
27688
|
dataDb.exec(`DELETE FROM ${t("py_route_callers")}`);
|
|
26162
27689
|
const insertStmt = dataDb.prepare(
|
|
@@ -26166,10 +27693,10 @@ function buildPythonRouteIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__",
|
|
|
26166
27693
|
let count = 0;
|
|
26167
27694
|
dataDb.transaction(() => {
|
|
26168
27695
|
for (const absFile of files) {
|
|
26169
|
-
const relFile =
|
|
27696
|
+
const relFile = relative9(projectRoot, absFile);
|
|
26170
27697
|
let source;
|
|
26171
27698
|
try {
|
|
26172
|
-
source =
|
|
27699
|
+
source = readFileSync42(absFile, "utf-8");
|
|
26173
27700
|
} catch {
|
|
26174
27701
|
continue;
|
|
26175
27702
|
}
|
|
@@ -26276,7 +27803,7 @@ function parsePythonModels(source) {
|
|
|
26276
27803
|
const bases = classMatch[2];
|
|
26277
27804
|
const classLine = i + 1;
|
|
26278
27805
|
let isModel = isModelClass(bases);
|
|
26279
|
-
const
|
|
27806
|
+
const columns2 = [];
|
|
26280
27807
|
const relationships = [];
|
|
26281
27808
|
const foreignKeys = [];
|
|
26282
27809
|
let tableName = null;
|
|
@@ -26319,7 +27846,7 @@ function parsePythonModels(source) {
|
|
|
26319
27846
|
const colType = extractColumnType(argsStr);
|
|
26320
27847
|
const nullable = extractNullable(argsStr);
|
|
26321
27848
|
const primaryKey = extractPrimaryKey(argsStr);
|
|
26322
|
-
|
|
27849
|
+
columns2.push({ name: colName, type: colType, nullable, primaryKey });
|
|
26323
27850
|
foreignKeys.push(...extractForeignKeys(colName, argsStr));
|
|
26324
27851
|
}
|
|
26325
27852
|
i++;
|
|
@@ -26333,7 +27860,7 @@ function parsePythonModels(source) {
|
|
|
26333
27860
|
const mappedType = extractMappedType(mappedAnnotation);
|
|
26334
27861
|
const nullable = mappedType.startsWith("Optional") || extractNullable(argsStr || "");
|
|
26335
27862
|
const primaryKey = extractPrimaryKey(argsStr || "");
|
|
26336
|
-
|
|
27863
|
+
columns2.push({ name: colName, type: mappedType, nullable, primaryKey });
|
|
26337
27864
|
if (argsStr) {
|
|
26338
27865
|
foreignKeys.push(...extractForeignKeys(colName, argsStr));
|
|
26339
27866
|
}
|
|
@@ -26363,7 +27890,7 @@ function parsePythonModels(source) {
|
|
|
26363
27890
|
models.push({
|
|
26364
27891
|
className,
|
|
26365
27892
|
tableName,
|
|
26366
|
-
columns,
|
|
27893
|
+
columns: columns2,
|
|
26367
27894
|
relationships,
|
|
26368
27895
|
foreignKeys,
|
|
26369
27896
|
line: classLine
|
|
@@ -26381,8 +27908,8 @@ var init_model_parser = __esm({
|
|
|
26381
27908
|
});
|
|
26382
27909
|
|
|
26383
27910
|
// src/python/model-indexer.ts
|
|
26384
|
-
import { readFileSync as
|
|
26385
|
-
import { join as
|
|
27911
|
+
import { readFileSync as readFileSync43, readdirSync as readdirSync23 } from "fs";
|
|
27912
|
+
import { join as join31, relative as relative10 } from "path";
|
|
26386
27913
|
function walkPyFiles2(dir, excludeDirs) {
|
|
26387
27914
|
const files = [];
|
|
26388
27915
|
try {
|
|
@@ -26390,9 +27917,9 @@ function walkPyFiles2(dir, excludeDirs) {
|
|
|
26390
27917
|
for (const entry of entries) {
|
|
26391
27918
|
if (entry.isDirectory()) {
|
|
26392
27919
|
if (excludeDirs.includes(entry.name)) continue;
|
|
26393
|
-
files.push(...walkPyFiles2(
|
|
27920
|
+
files.push(...walkPyFiles2(join31(dir, entry.name), excludeDirs));
|
|
26394
27921
|
} else if (entry.name.endsWith(".py")) {
|
|
26395
|
-
files.push(
|
|
27922
|
+
files.push(join31(dir, entry.name));
|
|
26396
27923
|
}
|
|
26397
27924
|
}
|
|
26398
27925
|
} catch {
|
|
@@ -26401,7 +27928,7 @@ function walkPyFiles2(dir, excludeDirs) {
|
|
|
26401
27928
|
}
|
|
26402
27929
|
function buildPythonModelIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]) {
|
|
26403
27930
|
const projectRoot = getProjectRoot();
|
|
26404
|
-
const absRoot =
|
|
27931
|
+
const absRoot = join31(projectRoot, pythonRoot);
|
|
26405
27932
|
dataDb.exec(`DELETE FROM ${t("py_models")}`);
|
|
26406
27933
|
dataDb.exec(`DELETE FROM ${t("py_fk_edges")}`);
|
|
26407
27934
|
const insertModel = dataDb.prepare(
|
|
@@ -26414,10 +27941,10 @@ function buildPythonModelIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__",
|
|
|
26414
27941
|
let count = 0;
|
|
26415
27942
|
dataDb.transaction(() => {
|
|
26416
27943
|
for (const absFile of files) {
|
|
26417
|
-
const relFile =
|
|
27944
|
+
const relFile = relative10(projectRoot, absFile);
|
|
26418
27945
|
let source;
|
|
26419
27946
|
try {
|
|
26420
|
-
source =
|
|
27947
|
+
source = readFileSync43(absFile, "utf-8");
|
|
26421
27948
|
} catch {
|
|
26422
27949
|
continue;
|
|
26423
27950
|
}
|
|
@@ -26678,19 +28205,19 @@ var init_migration_parser = __esm({
|
|
|
26678
28205
|
});
|
|
26679
28206
|
|
|
26680
28207
|
// src/python/migration-indexer.ts
|
|
26681
|
-
import { readFileSync as
|
|
26682
|
-
import { join as
|
|
28208
|
+
import { readFileSync as readFileSync44, readdirSync as readdirSync24 } from "fs";
|
|
28209
|
+
import { join as join32, relative as relative11 } from "path";
|
|
26683
28210
|
function buildPythonMigrationIndex(dataDb, alembicDir) {
|
|
26684
28211
|
const projectRoot = getProjectRoot();
|
|
26685
|
-
const absDir =
|
|
28212
|
+
const absDir = join32(projectRoot, alembicDir);
|
|
26686
28213
|
dataDb.exec(`DELETE FROM ${t("py_migrations")}`);
|
|
26687
|
-
const versionsDir =
|
|
28214
|
+
const versionsDir = join32(absDir, "versions");
|
|
26688
28215
|
let files = [];
|
|
26689
28216
|
try {
|
|
26690
|
-
files = readdirSync24(versionsDir).filter((f2) => f2.endsWith(".py")).map((f2) =>
|
|
28217
|
+
files = readdirSync24(versionsDir).filter((f2) => f2.endsWith(".py")).map((f2) => join32(versionsDir, f2));
|
|
26691
28218
|
} catch {
|
|
26692
28219
|
try {
|
|
26693
|
-
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));
|
|
26694
28221
|
} catch {
|
|
26695
28222
|
}
|
|
26696
28223
|
}
|
|
@@ -26704,7 +28231,7 @@ function buildPythonMigrationIndex(dataDb, alembicDir) {
|
|
|
26704
28231
|
for (const absFile of files) {
|
|
26705
28232
|
let source;
|
|
26706
28233
|
try {
|
|
26707
|
-
source =
|
|
28234
|
+
source = readFileSync44(absFile, "utf-8");
|
|
26708
28235
|
} catch {
|
|
26709
28236
|
continue;
|
|
26710
28237
|
}
|
|
@@ -26715,7 +28242,7 @@ function buildPythonMigrationIndex(dataDb, alembicDir) {
|
|
|
26715
28242
|
rows.push({
|
|
26716
28243
|
revision: parsed.revision,
|
|
26717
28244
|
downRevision: parsed.downRevision,
|
|
26718
|
-
file:
|
|
28245
|
+
file: relative11(projectRoot, absFile),
|
|
26719
28246
|
description: parsed.description,
|
|
26720
28247
|
operations: JSON.stringify(parsed.operations)
|
|
26721
28248
|
});
|
|
@@ -26739,12 +28266,12 @@ var init_migration_indexer = __esm({
|
|
|
26739
28266
|
});
|
|
26740
28267
|
|
|
26741
28268
|
// src/python/coupling-detector.ts
|
|
26742
|
-
import { readFileSync as
|
|
26743
|
-
import { join as
|
|
28269
|
+
import { readFileSync as readFileSync45, readdirSync as readdirSync25 } from "fs";
|
|
28270
|
+
import { join as join33, relative as relative12 } from "path";
|
|
26744
28271
|
function buildPythonCouplingIndex(dataDb) {
|
|
26745
28272
|
const projectRoot = getProjectRoot();
|
|
26746
28273
|
const config = getConfig();
|
|
26747
|
-
const srcDir =
|
|
28274
|
+
const srcDir = join33(projectRoot, config.paths.source);
|
|
26748
28275
|
const routes = dataDb.prepare(`SELECT id, method, path FROM ${t("py_routes")}`).all();
|
|
26749
28276
|
if (routes.length === 0) return 0;
|
|
26750
28277
|
dataDb.exec(`DELETE FROM ${t("py_route_callers")}`);
|
|
@@ -26773,10 +28300,10 @@ function buildPythonCouplingIndex(dataDb) {
|
|
|
26773
28300
|
];
|
|
26774
28301
|
dataDb.transaction(() => {
|
|
26775
28302
|
for (const absFile of frontendFiles) {
|
|
26776
|
-
const relFile =
|
|
28303
|
+
const relFile = relative12(projectRoot, absFile);
|
|
26777
28304
|
let source;
|
|
26778
28305
|
try {
|
|
26779
|
-
source =
|
|
28306
|
+
source = readFileSync45(absFile, "utf-8");
|
|
26780
28307
|
} catch {
|
|
26781
28308
|
continue;
|
|
26782
28309
|
}
|
|
@@ -26808,9 +28335,9 @@ function walkFrontendFiles(dir) {
|
|
|
26808
28335
|
for (const entry of entries) {
|
|
26809
28336
|
if (entry.isDirectory()) {
|
|
26810
28337
|
if (exclude.includes(entry.name)) continue;
|
|
26811
|
-
files.push(...walkFrontendFiles(
|
|
28338
|
+
files.push(...walkFrontendFiles(join33(dir, entry.name)));
|
|
26812
28339
|
} else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
|
|
26813
|
-
files.push(
|
|
28340
|
+
files.push(join33(dir, entry.name));
|
|
26814
28341
|
}
|
|
26815
28342
|
}
|
|
26816
28343
|
} catch {
|
|
@@ -26840,14 +28367,14 @@ var init_coupling_detector = __esm({
|
|
|
26840
28367
|
});
|
|
26841
28368
|
|
|
26842
28369
|
// src/memory-tools.ts
|
|
26843
|
-
function
|
|
28370
|
+
function p3(baseName) {
|
|
26844
28371
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
26845
28372
|
}
|
|
26846
28373
|
function getMemoryToolDefinitions() {
|
|
26847
28374
|
return [
|
|
26848
28375
|
// P4-001: memory_search
|
|
26849
28376
|
{
|
|
26850
|
-
name:
|
|
28377
|
+
name: p3("memory_search"),
|
|
26851
28378
|
description: "Search past session observations and decisions using full-text search. Returns compact index of matching observations.",
|
|
26852
28379
|
inputSchema: {
|
|
26853
28380
|
type: "object",
|
|
@@ -26863,7 +28390,7 @@ function getMemoryToolDefinitions() {
|
|
|
26863
28390
|
},
|
|
26864
28391
|
// P4-002: memory_timeline
|
|
26865
28392
|
{
|
|
26866
|
-
name:
|
|
28393
|
+
name: p3("memory_timeline"),
|
|
26867
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.",
|
|
26868
28395
|
inputSchema: {
|
|
26869
28396
|
type: "object",
|
|
@@ -26877,7 +28404,7 @@ function getMemoryToolDefinitions() {
|
|
|
26877
28404
|
},
|
|
26878
28405
|
// P4-003: memory_detail
|
|
26879
28406
|
{
|
|
26880
|
-
name:
|
|
28407
|
+
name: p3("memory_detail"),
|
|
26881
28408
|
description: "Get full observation details by IDs (batch). Includes evidence, files, plan items.",
|
|
26882
28409
|
inputSchema: {
|
|
26883
28410
|
type: "object",
|
|
@@ -26893,7 +28420,7 @@ function getMemoryToolDefinitions() {
|
|
|
26893
28420
|
},
|
|
26894
28421
|
// P4-004: memory_sessions
|
|
26895
28422
|
{
|
|
26896
|
-
name:
|
|
28423
|
+
name: p3("memory_sessions"),
|
|
26897
28424
|
description: "List recent sessions with summaries.",
|
|
26898
28425
|
inputSchema: {
|
|
26899
28426
|
type: "object",
|
|
@@ -26906,7 +28433,7 @@ function getMemoryToolDefinitions() {
|
|
|
26906
28433
|
},
|
|
26907
28434
|
// P4-005: memory_failures
|
|
26908
28435
|
{
|
|
26909
|
-
name:
|
|
28436
|
+
name: p3("memory_failures"),
|
|
26910
28437
|
description: "Get all failed attempts (DON'T RETRY warnings). Check before attempting a fix to see if it was already tried.",
|
|
26911
28438
|
inputSchema: {
|
|
26912
28439
|
type: "object",
|
|
@@ -26919,7 +28446,7 @@ function getMemoryToolDefinitions() {
|
|
|
26919
28446
|
},
|
|
26920
28447
|
// P4-007: memory_backfill
|
|
26921
28448
|
{
|
|
26922
|
-
name:
|
|
28449
|
+
name: p3("memory_backfill"),
|
|
26923
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.",
|
|
26924
28451
|
inputSchema: {
|
|
26925
28452
|
type: "object",
|
|
@@ -26929,7 +28456,7 @@ function getMemoryToolDefinitions() {
|
|
|
26929
28456
|
},
|
|
26930
28457
|
// P4-006: memory_ingest
|
|
26931
28458
|
{
|
|
26932
|
-
name:
|
|
28459
|
+
name: p3("memory_ingest"),
|
|
26933
28460
|
description: "Manually record an observation that hooks cannot auto-detect. Use for significant decisions, discoveries, or failed attempts mid-session.",
|
|
26934
28461
|
inputSchema: {
|
|
26935
28462
|
type: "object",
|
|
@@ -26974,15 +28501,15 @@ function handleMemoryToolCall(name, args2, memoryDb) {
|
|
|
26974
28501
|
case "memory_backfill":
|
|
26975
28502
|
return handleBackfill(memoryDb);
|
|
26976
28503
|
default:
|
|
26977
|
-
return
|
|
28504
|
+
return text2(`Unknown memory tool: ${name}`);
|
|
26978
28505
|
}
|
|
26979
28506
|
} catch (error) {
|
|
26980
|
-
return
|
|
28507
|
+
return text2(`Error in ${name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
26981
28508
|
}
|
|
26982
28509
|
}
|
|
26983
28510
|
function handleSearch(args2, db) {
|
|
26984
28511
|
const query = args2.query;
|
|
26985
|
-
if (!query) return
|
|
28512
|
+
if (!query) return text2("Error: query is required");
|
|
26986
28513
|
const results = searchObservations(db, query, {
|
|
26987
28514
|
type: args2.type,
|
|
26988
28515
|
crRule: args2.cr_rule,
|
|
@@ -26990,7 +28517,7 @@ function handleSearch(args2, db) {
|
|
|
26990
28517
|
limit: args2.limit
|
|
26991
28518
|
});
|
|
26992
28519
|
if (results.length === 0) {
|
|
26993
|
-
return
|
|
28520
|
+
return text2(`No observations found for "${query}".`);
|
|
26994
28521
|
}
|
|
26995
28522
|
const lines = [`## Search Results for "${query}" (${results.length} matches)`, ""];
|
|
26996
28523
|
lines.push("| ID | Type | Title | Date | Importance |");
|
|
@@ -26999,17 +28526,17 @@ function handleSearch(args2, db) {
|
|
|
26999
28526
|
lines.push(`| ${r2.id} | ${r2.type} | ${r2.title.slice(0, 80)} | ${r2.created_at.split("T")[0]} | ${r2.importance} |`);
|
|
27000
28527
|
}
|
|
27001
28528
|
lines.push("");
|
|
27002
|
-
lines.push(`Use ${
|
|
27003
|
-
lines.push(`Use ${
|
|
27004
|
-
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"));
|
|
27005
28532
|
}
|
|
27006
28533
|
function handleTimeline(args2, db) {
|
|
27007
28534
|
const observationId = args2.observation_id;
|
|
27008
|
-
if (!observationId) return
|
|
28535
|
+
if (!observationId) return text2("Error: observation_id is required");
|
|
27009
28536
|
const depthBefore = args2.depth_before ?? 5;
|
|
27010
28537
|
const depthAfter = args2.depth_after ?? 5;
|
|
27011
28538
|
const anchor = db.prepare("SELECT * FROM observations WHERE id = ?").get(observationId);
|
|
27012
|
-
if (!anchor) return
|
|
28539
|
+
if (!anchor) return text2(`Observation ${observationId} not found.`);
|
|
27013
28540
|
const before = db.prepare(
|
|
27014
28541
|
"SELECT id, type, title, created_at, importance FROM observations WHERE session_id = ? AND created_at_epoch < ? ORDER BY created_at_epoch DESC LIMIT ?"
|
|
27015
28542
|
).all(anchor.session_id, anchor.created_at_epoch, depthBefore);
|
|
@@ -27024,17 +28551,17 @@ function handleTimeline(args2, db) {
|
|
|
27024
28551
|
for (const o of after) {
|
|
27025
28552
|
lines.push(` ${o.id} | ${o.type} | ${o.title.slice(0, 60)} | ${o.created_at.split("T")[0]}`);
|
|
27026
28553
|
}
|
|
27027
|
-
return
|
|
28554
|
+
return text2(lines.join("\n"));
|
|
27028
28555
|
}
|
|
27029
28556
|
function handleDetail(args2, db) {
|
|
27030
28557
|
const ids = args2.ids;
|
|
27031
|
-
if (!ids || ids.length === 0) return
|
|
28558
|
+
if (!ids || ids.length === 0) return text2("Error: ids array is required");
|
|
27032
28559
|
const placeholders = ids.map(() => "?").join(",");
|
|
27033
28560
|
const observations = db.prepare(
|
|
27034
28561
|
`SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch ASC LIMIT 10000`
|
|
27035
28562
|
).all(...ids);
|
|
27036
28563
|
if (observations.length === 0) {
|
|
27037
|
-
return
|
|
28564
|
+
return text2("No observations found for the given IDs.");
|
|
27038
28565
|
}
|
|
27039
28566
|
const lines = [];
|
|
27040
28567
|
for (const o of observations) {
|
|
@@ -27054,7 +28581,7 @@ function handleDetail(args2, db) {
|
|
|
27054
28581
|
if (o.recurrence_count > 1) lines.push(`**Recurrence**: ${o.recurrence_count}x`);
|
|
27055
28582
|
lines.push("");
|
|
27056
28583
|
}
|
|
27057
|
-
return
|
|
28584
|
+
return text2(lines.join("\n"));
|
|
27058
28585
|
}
|
|
27059
28586
|
function handleSessions(args2, db) {
|
|
27060
28587
|
const limit = args2.limit ?? 10;
|
|
@@ -27069,7 +28596,7 @@ function handleSessions(args2, db) {
|
|
|
27069
28596
|
params.push(limit);
|
|
27070
28597
|
const sessions = db.prepare(sql).all(...params);
|
|
27071
28598
|
if (sessions.length === 0) {
|
|
27072
|
-
return
|
|
28599
|
+
return text2("No sessions found.");
|
|
27073
28600
|
}
|
|
27074
28601
|
const lines = ["## Recent Sessions", ""];
|
|
27075
28602
|
lines.push("| Session ID | Status | Branch | Started | Plan |");
|
|
@@ -27089,14 +28616,14 @@ function handleSessions(args2, db) {
|
|
|
27089
28616
|
if (summary.completed) lines.push(`**Completed**: ${summary.completed.slice(0, 200)}`);
|
|
27090
28617
|
}
|
|
27091
28618
|
}
|
|
27092
|
-
return
|
|
28619
|
+
return text2(lines.join("\n"));
|
|
27093
28620
|
}
|
|
27094
28621
|
function handleFailures(args2, db) {
|
|
27095
28622
|
const query = args2.query;
|
|
27096
28623
|
const limit = args2.limit ?? 20;
|
|
27097
28624
|
const failures = getFailedAttempts(db, query, limit);
|
|
27098
28625
|
if (failures.length === 0) {
|
|
27099
|
-
return
|
|
28626
|
+
return text2(query ? `No failed attempts found for "${query}".` : "No failed attempts recorded.");
|
|
27100
28627
|
}
|
|
27101
28628
|
const lines = ["## Failed Attempts (DO NOT RETRY)", ""];
|
|
27102
28629
|
for (const f2 of failures) {
|
|
@@ -27106,12 +28633,12 @@ function handleFailures(args2, db) {
|
|
|
27106
28633
|
lines.push(`Session: ${f2.session_id.slice(0, 8)}... | Date: ${f2.created_at.split("T")[0]}`);
|
|
27107
28634
|
lines.push("");
|
|
27108
28635
|
}
|
|
27109
|
-
return
|
|
28636
|
+
return text2(lines.join("\n"));
|
|
27110
28637
|
}
|
|
27111
28638
|
function handleIngest(args2, db) {
|
|
27112
28639
|
const type = args2.type;
|
|
27113
28640
|
const title = args2.title;
|
|
27114
|
-
if (!type || !title) return
|
|
28641
|
+
if (!type || !title) return text2("Error: type and title are required");
|
|
27115
28642
|
const validTypes = [
|
|
27116
28643
|
"decision",
|
|
27117
28644
|
"bugfix",
|
|
@@ -27126,13 +28653,13 @@ function handleIngest(args2, db) {
|
|
|
27126
28653
|
"incident_near_miss"
|
|
27127
28654
|
];
|
|
27128
28655
|
if (!validTypes.includes(type)) {
|
|
27129
|
-
return
|
|
28656
|
+
return text2(`Error: invalid type "${type}". Valid types: ${validTypes.join(", ")}`);
|
|
27130
28657
|
}
|
|
27131
28658
|
const activeSession = db.prepare(
|
|
27132
28659
|
"SELECT session_id FROM sessions WHERE status = 'active' ORDER BY started_at_epoch DESC LIMIT 1"
|
|
27133
28660
|
).get();
|
|
27134
28661
|
if (!activeSession) {
|
|
27135
|
-
return
|
|
28662
|
+
return text2("Error: no active session found. Start a session first.");
|
|
27136
28663
|
}
|
|
27137
28664
|
const importance = args2.importance ?? assignImportance(type);
|
|
27138
28665
|
const id = addObservation(db, activeSession.session_id, type, title, args2.detail ?? null, {
|
|
@@ -27141,7 +28668,7 @@ function handleIngest(args2, db) {
|
|
|
27141
28668
|
planItem: args2.plan_item,
|
|
27142
28669
|
filesInvolved: args2.files
|
|
27143
28670
|
});
|
|
27144
|
-
return
|
|
28671
|
+
return text2(`Observation #${id} recorded successfully.
|
|
27145
28672
|
Type: ${type}
|
|
27146
28673
|
Title: ${title}
|
|
27147
28674
|
Importance: ${importance}
|
|
@@ -27160,9 +28687,9 @@ function handleBackfill(db) {
|
|
|
27160
28687
|
"",
|
|
27161
28688
|
stats.total === 0 ? "No memory files found in memory directory." : `Successfully processed ${stats.inserted + stats.updated} of ${stats.total} memory files.`
|
|
27162
28689
|
];
|
|
27163
|
-
return
|
|
28690
|
+
return text2(lines.join("\n"));
|
|
27164
28691
|
}
|
|
27165
|
-
function
|
|
28692
|
+
function text2(content) {
|
|
27166
28693
|
return { content: [{ type: "text", text: content }] };
|
|
27167
28694
|
}
|
|
27168
28695
|
function safeParseJson(json, fallback) {
|
|
@@ -27182,15 +28709,15 @@ var init_memory_tools = __esm({
|
|
|
27182
28709
|
});
|
|
27183
28710
|
|
|
27184
28711
|
// src/docs-tools.ts
|
|
27185
|
-
import { readFileSync as
|
|
28712
|
+
import { readFileSync as readFileSync46, existsSync as existsSync54 } from "fs";
|
|
27186
28713
|
import { resolve as resolve34, basename as basename9 } from "path";
|
|
27187
|
-
function
|
|
28714
|
+
function p4(baseName) {
|
|
27188
28715
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
27189
28716
|
}
|
|
27190
28717
|
function getDocsToolDefinitions() {
|
|
27191
28718
|
return [
|
|
27192
28719
|
{
|
|
27193
|
-
name:
|
|
28720
|
+
name: p4("docs_audit"),
|
|
27194
28721
|
description: "Audit which help site pages need updating based on changed files. Maps code changes to affected documentation pages using docs-map.json.",
|
|
27195
28722
|
inputSchema: {
|
|
27196
28723
|
type: "object",
|
|
@@ -27209,7 +28736,7 @@ function getDocsToolDefinitions() {
|
|
|
27209
28736
|
}
|
|
27210
28737
|
},
|
|
27211
28738
|
{
|
|
27212
|
-
name:
|
|
28739
|
+
name: p4("docs_coverage"),
|
|
27213
28740
|
description: "Report docs coverage: which help pages exist, have content, and are up-to-date. Identifies documentation gaps.",
|
|
27214
28741
|
inputSchema: {
|
|
27215
28742
|
type: "object",
|
|
@@ -27233,15 +28760,15 @@ function handleDocsToolCall(name, args2) {
|
|
|
27233
28760
|
case "docs_coverage":
|
|
27234
28761
|
return handleDocsCoverage(args2);
|
|
27235
28762
|
default:
|
|
27236
|
-
return
|
|
28763
|
+
return text3(`Unknown docs tool: ${name}`);
|
|
27237
28764
|
}
|
|
27238
28765
|
}
|
|
27239
28766
|
function loadDocsMap() {
|
|
27240
28767
|
const mapPath = getResolvedPaths().docsMapPath;
|
|
27241
|
-
if (!
|
|
28768
|
+
if (!existsSync54(mapPath)) {
|
|
27242
28769
|
throw new Error(`docs-map.json not found at ${mapPath}`);
|
|
27243
28770
|
}
|
|
27244
|
-
return JSON.parse(
|
|
28771
|
+
return JSON.parse(readFileSync46(mapPath, "utf-8"));
|
|
27245
28772
|
}
|
|
27246
28773
|
function matchesPattern(filePath, pattern) {
|
|
27247
28774
|
const regexStr = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\{\{GLOBSTAR\}\}/g, ".*");
|
|
@@ -27312,12 +28839,12 @@ function extractFrontmatter(content) {
|
|
|
27312
28839
|
function extractProcedureNames(routerPath) {
|
|
27313
28840
|
const root = getProjectRoot();
|
|
27314
28841
|
const absPath = ensureWithinRoot(resolve34(getResolvedPaths().srcDir, "..", routerPath), root);
|
|
27315
|
-
if (!
|
|
28842
|
+
if (!existsSync54(absPath)) {
|
|
27316
28843
|
const altPath = ensureWithinRoot(resolve34(getResolvedPaths().srcDir, "../server/api/routers", basename9(routerPath)), root);
|
|
27317
|
-
if (!
|
|
27318
|
-
return extractProcedureNamesFromContent(
|
|
28844
|
+
if (!existsSync54(altPath)) return [];
|
|
28845
|
+
return extractProcedureNamesFromContent(readFileSync46(altPath, "utf-8"));
|
|
27319
28846
|
}
|
|
27320
|
-
return extractProcedureNamesFromContent(
|
|
28847
|
+
return extractProcedureNamesFromContent(readFileSync46(absPath, "utf-8"));
|
|
27321
28848
|
}
|
|
27322
28849
|
function extractProcedureNamesFromContent(content) {
|
|
27323
28850
|
const procRegex = /\.(?:query|mutation)\s*\(/g;
|
|
@@ -27343,12 +28870,12 @@ function handleDocsAudit(args2) {
|
|
|
27343
28870
|
const changedFiles = args2.changed_files;
|
|
27344
28871
|
const commitMessage = args2.commit_message || "";
|
|
27345
28872
|
if (!changedFiles || changedFiles.length === 0) {
|
|
27346
|
-
return
|
|
28873
|
+
return text3(JSON.stringify({ affectedPages: [], summary: "No changed files provided." }));
|
|
27347
28874
|
}
|
|
27348
28875
|
const docsMap = loadDocsMap();
|
|
27349
28876
|
const affectedMappings = findAffectedMappings(docsMap, changedFiles);
|
|
27350
28877
|
if (affectedMappings.size === 0) {
|
|
27351
|
-
return
|
|
28878
|
+
return text3(JSON.stringify({
|
|
27352
28879
|
affectedPages: [],
|
|
27353
28880
|
summary: `0 help pages affected by ${changedFiles.length} changed files. No docs update needed.`
|
|
27354
28881
|
}));
|
|
@@ -27358,7 +28885,7 @@ function handleDocsAudit(args2) {
|
|
|
27358
28885
|
const mapping = docsMap.mappings.find((m3) => m3.id === mappingId);
|
|
27359
28886
|
if (!mapping) continue;
|
|
27360
28887
|
const helpPagePath = ensureWithinRoot(resolve34(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
|
|
27361
|
-
if (!
|
|
28888
|
+
if (!existsSync54(helpPagePath)) {
|
|
27362
28889
|
results.push({
|
|
27363
28890
|
helpPage: mapping.helpPage,
|
|
27364
28891
|
mappingId,
|
|
@@ -27370,7 +28897,7 @@ function handleDocsAudit(args2) {
|
|
|
27370
28897
|
});
|
|
27371
28898
|
continue;
|
|
27372
28899
|
}
|
|
27373
|
-
const content =
|
|
28900
|
+
const content = readFileSync46(helpPagePath, "utf-8");
|
|
27374
28901
|
const sections = extractSections(content);
|
|
27375
28902
|
const frontmatter = extractFrontmatter(content);
|
|
27376
28903
|
const staleReasons = [];
|
|
@@ -27411,8 +28938,8 @@ function handleDocsAudit(args2) {
|
|
|
27411
28938
|
for (const [guideName, parentId] of Object.entries(docsMap.userGuideInheritance.examples)) {
|
|
27412
28939
|
if (parentId === mappingId) {
|
|
27413
28940
|
const guidePath = ensureWithinRoot(resolve34(getResolvedPaths().helpSitePath, `pages/user-guides/${guideName}/index.mdx`), getProjectRoot());
|
|
27414
|
-
if (
|
|
27415
|
-
const guideContent =
|
|
28941
|
+
if (existsSync54(guidePath)) {
|
|
28942
|
+
const guideContent = readFileSync46(guidePath, "utf-8");
|
|
27416
28943
|
const guideFrontmatter = extractFrontmatter(guideContent);
|
|
27417
28944
|
if (!guideFrontmatter?.lastVerified || status === "STALE") {
|
|
27418
28945
|
results.push({
|
|
@@ -27436,7 +28963,7 @@ function handleDocsAudit(args2) {
|
|
|
27436
28963
|
affectedPages: results,
|
|
27437
28964
|
summary: `${results.length} pages checked: ${staleCount} STALE, ${newCount} NEW, ${okCount} OK. ${staleCount + newCount > 0 ? `${staleCount + newCount} pages need updates.` : "All docs are current."}`
|
|
27438
28965
|
};
|
|
27439
|
-
return
|
|
28966
|
+
return text3(JSON.stringify(report, null, 2));
|
|
27440
28967
|
}
|
|
27441
28968
|
function handleDocsCoverage(args2) {
|
|
27442
28969
|
const filterDomain = args2.domain;
|
|
@@ -27446,13 +28973,13 @@ function handleDocsCoverage(args2) {
|
|
|
27446
28973
|
const mappings = filterDomain ? docsMap.mappings.filter((m3) => m3.id === filterDomain) : docsMap.mappings;
|
|
27447
28974
|
for (const mapping of mappings) {
|
|
27448
28975
|
const helpPagePath = ensureWithinRoot(resolve34(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
|
|
27449
|
-
const exists =
|
|
28976
|
+
const exists = existsSync54(helpPagePath);
|
|
27450
28977
|
let hasContent = false;
|
|
27451
28978
|
let lineCount = 0;
|
|
27452
28979
|
let lastVerified = null;
|
|
27453
28980
|
let status = null;
|
|
27454
28981
|
if (exists) {
|
|
27455
|
-
const content =
|
|
28982
|
+
const content = readFileSync46(helpPagePath, "utf-8");
|
|
27456
28983
|
lineCount = content.split("\n").length;
|
|
27457
28984
|
hasContent = lineCount > 10;
|
|
27458
28985
|
const frontmatter = extractFrontmatter(content);
|
|
@@ -27503,9 +29030,9 @@ function handleDocsCoverage(args2) {
|
|
|
27503
29030
|
const icon = entry.hasContent ? "OK" : entry.exists ? "THIN" : "MISSING";
|
|
27504
29031
|
lines.push(`- [${icon}] ${entry.id}: ${entry.helpPage}${verified}${pageStatus} (${entry.lineCount} lines)`);
|
|
27505
29032
|
}
|
|
27506
|
-
return
|
|
29033
|
+
return text3(lines.join("\n"));
|
|
27507
29034
|
}
|
|
27508
|
-
function
|
|
29035
|
+
function text3(content) {
|
|
27509
29036
|
return { content: [{ type: "text", text: content }] };
|
|
27510
29037
|
}
|
|
27511
29038
|
var init_docs_tools = __esm({
|
|
@@ -27517,7 +29044,7 @@ var init_docs_tools = __esm({
|
|
|
27517
29044
|
});
|
|
27518
29045
|
|
|
27519
29046
|
// src/observability-tools.ts
|
|
27520
|
-
function
|
|
29047
|
+
function p5(baseName) {
|
|
27521
29048
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
27522
29049
|
}
|
|
27523
29050
|
function isObservabilityTool(name) {
|
|
@@ -27529,7 +29056,7 @@ function getObservabilityToolDefinitions() {
|
|
|
27529
29056
|
return [
|
|
27530
29057
|
// P3-001: session_replay
|
|
27531
29058
|
{
|
|
27532
|
-
name:
|
|
29059
|
+
name: p5("session_replay"),
|
|
27533
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.",
|
|
27534
29061
|
inputSchema: {
|
|
27535
29062
|
type: "object",
|
|
@@ -27544,7 +29071,7 @@ function getObservabilityToolDefinitions() {
|
|
|
27544
29071
|
},
|
|
27545
29072
|
// P3-002: prompt_analysis
|
|
27546
29073
|
{
|
|
27547
|
-
name:
|
|
29074
|
+
name: p5("prompt_analysis"),
|
|
27548
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).",
|
|
27549
29076
|
inputSchema: {
|
|
27550
29077
|
type: "object",
|
|
@@ -27561,7 +29088,7 @@ function getObservabilityToolDefinitions() {
|
|
|
27561
29088
|
},
|
|
27562
29089
|
// P3-003: tool_patterns
|
|
27563
29090
|
{
|
|
27564
|
-
name:
|
|
29091
|
+
name: p5("tool_patterns"),
|
|
27565
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.",
|
|
27566
29093
|
inputSchema: {
|
|
27567
29094
|
type: "object",
|
|
@@ -27576,7 +29103,7 @@ function getObservabilityToolDefinitions() {
|
|
|
27576
29103
|
},
|
|
27577
29104
|
// P3-004: session_stats
|
|
27578
29105
|
{
|
|
27579
|
-
name:
|
|
29106
|
+
name: p5("session_stats"),
|
|
27580
29107
|
description: "Get per-session statistics: turn count, tool call breakdown, token usage, duration. Includes database size monitoring for observability data.",
|
|
27581
29108
|
inputSchema: {
|
|
27582
29109
|
type: "object",
|
|
@@ -27603,15 +29130,15 @@ function handleObservabilityToolCall(name, args2, memoryDb) {
|
|
|
27603
29130
|
case "session_stats":
|
|
27604
29131
|
return handleSessionStats(args2, memoryDb);
|
|
27605
29132
|
default:
|
|
27606
|
-
return
|
|
29133
|
+
return text4(`Unknown observability tool: ${name}`);
|
|
27607
29134
|
}
|
|
27608
29135
|
} catch (error) {
|
|
27609
|
-
return
|
|
29136
|
+
return text4(`Error in ${name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
27610
29137
|
}
|
|
27611
29138
|
}
|
|
27612
29139
|
function handleSessionReplay(args2, db) {
|
|
27613
29140
|
const sessionId = args2.session_id;
|
|
27614
|
-
if (!sessionId) return
|
|
29141
|
+
if (!sessionId) return text4("Error: session_id is required");
|
|
27615
29142
|
const includeToolCalls = args2.include_tool_calls ?? false;
|
|
27616
29143
|
const turns = getConversationTurns(db, sessionId, {
|
|
27617
29144
|
turnFrom: args2.turn_from,
|
|
@@ -27619,7 +29146,7 @@ function handleSessionReplay(args2, db) {
|
|
|
27619
29146
|
includeToolCalls
|
|
27620
29147
|
});
|
|
27621
29148
|
if (turns.length === 0) {
|
|
27622
|
-
return
|
|
29149
|
+
return text4(`No conversation turns found for session ${sessionId.slice(0, 8)}...
|
|
27623
29150
|
Note: Conversation data is captured by the session-end hook. Only sessions that have ended will have replay data.`);
|
|
27624
29151
|
}
|
|
27625
29152
|
const lines = [`## Session Replay: ${sessionId.slice(0, 12)}...`, `Turns: ${turns.length}`, ""];
|
|
@@ -27651,7 +29178,7 @@ Note: Conversation data is captured by the session-end hook. Only sessions that
|
|
|
27651
29178
|
lines.push("---");
|
|
27652
29179
|
lines.push("");
|
|
27653
29180
|
}
|
|
27654
|
-
return
|
|
29181
|
+
return text4(lines.join("\n"));
|
|
27655
29182
|
}
|
|
27656
29183
|
function handlePromptAnalysis(args2, db) {
|
|
27657
29184
|
const query = args2.query;
|
|
@@ -27660,7 +29187,7 @@ function handlePromptAnalysis(args2, db) {
|
|
|
27660
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 ?"
|
|
27661
29188
|
).all(args2.limit ?? 20);
|
|
27662
29189
|
if (recentTurns.length === 0) {
|
|
27663
|
-
return
|
|
29190
|
+
return text4("No conversation turns recorded yet.");
|
|
27664
29191
|
}
|
|
27665
29192
|
const lines2 = ["## Recent Prompts", ""];
|
|
27666
29193
|
lines2.push("| Session | Turn | Prompt (truncated) | Tools | Response Tokens | Date |");
|
|
@@ -27668,7 +29195,7 @@ function handlePromptAnalysis(args2, db) {
|
|
|
27668
29195
|
for (const t2 of recentTurns) {
|
|
27669
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]} |`);
|
|
27670
29197
|
}
|
|
27671
|
-
return
|
|
29198
|
+
return text4(lines2.join("\n"));
|
|
27672
29199
|
}
|
|
27673
29200
|
const results = searchConversationTurns(db, query, {
|
|
27674
29201
|
sessionId: args2.session_id,
|
|
@@ -27678,7 +29205,7 @@ function handlePromptAnalysis(args2, db) {
|
|
|
27678
29205
|
limit: args2.limit
|
|
27679
29206
|
});
|
|
27680
29207
|
if (results.length === 0) {
|
|
27681
|
-
return
|
|
29208
|
+
return text4(`No prompts found matching "${query}".`);
|
|
27682
29209
|
}
|
|
27683
29210
|
const lines = [`## Prompt Search: "${query}" (${results.length} results)`, ""];
|
|
27684
29211
|
lines.push("| Session | Turn | Prompt (truncated) | Tools | Response Tokens | Date |");
|
|
@@ -27686,7 +29213,7 @@ function handlePromptAnalysis(args2, db) {
|
|
|
27686
29213
|
for (const r2 of results) {
|
|
27687
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]} |`);
|
|
27688
29215
|
}
|
|
27689
|
-
return
|
|
29216
|
+
return text4(lines.join("\n"));
|
|
27690
29217
|
}
|
|
27691
29218
|
function handleToolPatterns(args2, db) {
|
|
27692
29219
|
const groupBy = args2.group_by ?? "tool";
|
|
@@ -27697,7 +29224,7 @@ function handleToolPatterns(args2, db) {
|
|
|
27697
29224
|
groupBy
|
|
27698
29225
|
});
|
|
27699
29226
|
if (patterns.length === 0) {
|
|
27700
|
-
return
|
|
29227
|
+
return text4("No tool usage data recorded yet.");
|
|
27701
29228
|
}
|
|
27702
29229
|
const lines = [`## Tool Usage Patterns (grouped by ${groupBy})`, ""];
|
|
27703
29230
|
switch (groupBy) {
|
|
@@ -27727,7 +29254,7 @@ function handleToolPatterns(args2, db) {
|
|
|
27727
29254
|
}
|
|
27728
29255
|
break;
|
|
27729
29256
|
}
|
|
27730
|
-
return
|
|
29257
|
+
return text4(lines.join("\n"));
|
|
27731
29258
|
}
|
|
27732
29259
|
function handleSessionStats(args2, db) {
|
|
27733
29260
|
const stats = getSessionStats(db, {
|
|
@@ -27735,7 +29262,7 @@ function handleSessionStats(args2, db) {
|
|
|
27735
29262
|
limit: args2.limit
|
|
27736
29263
|
});
|
|
27737
29264
|
if (stats.length === 0) {
|
|
27738
|
-
return
|
|
29265
|
+
return text4("No session stats available.");
|
|
27739
29266
|
}
|
|
27740
29267
|
const lines = ["## Session Statistics", ""];
|
|
27741
29268
|
if (args2.session_id) {
|
|
@@ -27772,9 +29299,9 @@ function handleSessionStats(args2, db) {
|
|
|
27772
29299
|
lines.push(`- Tool call details: ${dbSize.tool_call_details_count}`);
|
|
27773
29300
|
lines.push(`- Observations: ${dbSize.observations_count}`);
|
|
27774
29301
|
lines.push(`- Database size: ~${dbSize.estimated_size_mb} MB`);
|
|
27775
|
-
return
|
|
29302
|
+
return text4(lines.join("\n"));
|
|
27776
29303
|
}
|
|
27777
|
-
function
|
|
29304
|
+
function text4(content) {
|
|
27778
29305
|
return { content: [{ type: "text", text: content }] };
|
|
27779
29306
|
}
|
|
27780
29307
|
var OBSERVABILITY_BASE_NAMES;
|
|
@@ -27793,7 +29320,7 @@ var init_observability_tools = __esm({
|
|
|
27793
29320
|
});
|
|
27794
29321
|
|
|
27795
29322
|
// src/sentinel-db.ts
|
|
27796
|
-
import { existsSync as
|
|
29323
|
+
import { existsSync as existsSync55 } from "fs";
|
|
27797
29324
|
import { resolve as resolve35 } from "path";
|
|
27798
29325
|
function parsePortalScope(raw) {
|
|
27799
29326
|
if (!raw) return [];
|
|
@@ -28031,22 +29558,22 @@ function validateFeatures(db, domainFilter) {
|
|
|
28031
29558
|
const missingPages = [];
|
|
28032
29559
|
for (const comp of components) {
|
|
28033
29560
|
const absPath = resolve35(PROJECT_ROOT, comp.component_file);
|
|
28034
|
-
if (!
|
|
29561
|
+
if (!existsSync55(absPath)) {
|
|
28035
29562
|
missingComponents.push(comp.component_file);
|
|
28036
29563
|
}
|
|
28037
29564
|
}
|
|
28038
29565
|
for (const proc of procedures) {
|
|
28039
29566
|
const routerPath = resolve35(PROJECT_ROOT, `src/server/api/routers/${proc.router_name}.ts`);
|
|
28040
|
-
if (!
|
|
29567
|
+
if (!existsSync55(routerPath)) {
|
|
28041
29568
|
missingProcedures.push({ router: proc.router_name, procedure: proc.procedure_name });
|
|
28042
29569
|
}
|
|
28043
29570
|
}
|
|
28044
29571
|
for (const page of pages) {
|
|
28045
29572
|
const routeToPath = page.page_route.replace(/^\/(portal-[^/]+\/)?/, "src/app/").replace(/\/$/, "") + "/page.tsx";
|
|
28046
29573
|
const absPath = resolve35(PROJECT_ROOT, routeToPath);
|
|
28047
|
-
if (page.page_route.startsWith("/") && !
|
|
29574
|
+
if (page.page_route.startsWith("/") && !existsSync55(absPath)) {
|
|
28048
29575
|
const altPath = resolve35(PROJECT_ROOT, `src/app${page.page_route}/page.tsx`);
|
|
28049
|
-
if (!
|
|
29576
|
+
if (!existsSync55(altPath)) {
|
|
28050
29577
|
missingPages.push(page.page_route);
|
|
28051
29578
|
}
|
|
28052
29579
|
}
|
|
@@ -28142,17 +29669,17 @@ var init_sentinel_db = __esm({
|
|
|
28142
29669
|
});
|
|
28143
29670
|
|
|
28144
29671
|
// src/sentinel-tools.ts
|
|
28145
|
-
function
|
|
29672
|
+
function p6(baseName) {
|
|
28146
29673
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
28147
29674
|
}
|
|
28148
|
-
function
|
|
29675
|
+
function text5(content) {
|
|
28149
29676
|
return { content: [{ type: "text", text: content }] };
|
|
28150
29677
|
}
|
|
28151
29678
|
function getSentinelToolDefinitions() {
|
|
28152
29679
|
return [
|
|
28153
29680
|
// P2-001: sentinel_search
|
|
28154
29681
|
{
|
|
28155
|
-
name:
|
|
29682
|
+
name: p6("sentinel_search"),
|
|
28156
29683
|
description: "Search and list features in the Sentinel feature registry. Supports FTS5 full-text search and domain/status/portal filters.",
|
|
28157
29684
|
inputSchema: {
|
|
28158
29685
|
type: "object",
|
|
@@ -28169,7 +29696,7 @@ function getSentinelToolDefinitions() {
|
|
|
28169
29696
|
},
|
|
28170
29697
|
// P2-002: sentinel_detail
|
|
28171
29698
|
{
|
|
28172
|
-
name:
|
|
29699
|
+
name: p6("sentinel_detail"),
|
|
28173
29700
|
description: "Get full feature details including all linked components, procedures, pages, dependencies, and changelog.",
|
|
28174
29701
|
inputSchema: {
|
|
28175
29702
|
type: "object",
|
|
@@ -28182,7 +29709,7 @@ function getSentinelToolDefinitions() {
|
|
|
28182
29709
|
},
|
|
28183
29710
|
// P2-003: sentinel_impact
|
|
28184
29711
|
{
|
|
28185
|
-
name:
|
|
29712
|
+
name: p6("sentinel_impact"),
|
|
28186
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.",
|
|
28187
29714
|
inputSchema: {
|
|
28188
29715
|
type: "object",
|
|
@@ -28198,7 +29725,7 @@ function getSentinelToolDefinitions() {
|
|
|
28198
29725
|
},
|
|
28199
29726
|
// P2-004: sentinel_validate
|
|
28200
29727
|
{
|
|
28201
|
-
name:
|
|
29728
|
+
name: p6("sentinel_validate"),
|
|
28202
29729
|
description: "Validate all active features have living implementation files. Reports alive, orphaned, and degraded features.",
|
|
28203
29730
|
inputSchema: {
|
|
28204
29731
|
type: "object",
|
|
@@ -28211,7 +29738,7 @@ function getSentinelToolDefinitions() {
|
|
|
28211
29738
|
},
|
|
28212
29739
|
// P2-005: sentinel_register
|
|
28213
29740
|
{
|
|
28214
|
-
name:
|
|
29741
|
+
name: p6("sentinel_register"),
|
|
28215
29742
|
description: "Register or update a feature in the sentinel registry. Links components, procedures, and pages.",
|
|
28216
29743
|
inputSchema: {
|
|
28217
29744
|
type: "object",
|
|
@@ -28268,7 +29795,7 @@ function getSentinelToolDefinitions() {
|
|
|
28268
29795
|
},
|
|
28269
29796
|
// P2-006: sentinel_parity
|
|
28270
29797
|
{
|
|
28271
|
-
name:
|
|
29798
|
+
name: p6("sentinel_parity"),
|
|
28272
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).",
|
|
28273
29800
|
inputSchema: {
|
|
28274
29801
|
type: "object",
|
|
@@ -28306,7 +29833,7 @@ function handleSentinelToolCall(name, args2, dataDb) {
|
|
|
28306
29833
|
case "sentinel_parity":
|
|
28307
29834
|
return handleParityCheck(args2, dataDb);
|
|
28308
29835
|
default:
|
|
28309
|
-
return
|
|
29836
|
+
return text5(`Unknown sentinel tool: ${name}`);
|
|
28310
29837
|
}
|
|
28311
29838
|
}
|
|
28312
29839
|
function handleSearch2(args2, db) {
|
|
@@ -28329,7 +29856,7 @@ function handleSearch2(args2, db) {
|
|
|
28329
29856
|
lines.push("");
|
|
28330
29857
|
if (results.length === 0) {
|
|
28331
29858
|
lines.push("No features found matching criteria.");
|
|
28332
|
-
return
|
|
29859
|
+
return text5(lines.join("\n"));
|
|
28333
29860
|
}
|
|
28334
29861
|
let currentDomain = "";
|
|
28335
29862
|
for (const f2 of results) {
|
|
@@ -28341,17 +29868,17 @@ function handleSearch2(args2, db) {
|
|
|
28341
29868
|
const priority = f2.priority === "critical" ? " [CRITICAL]" : "";
|
|
28342
29869
|
lines.push(`- **${f2.feature_key}** ${sub}${priority}: ${f2.title} (${f2.component_count}C/${f2.procedure_count}P/${f2.page_count}R) [${f2.status}]`);
|
|
28343
29870
|
}
|
|
28344
|
-
return
|
|
29871
|
+
return text5(lines.join("\n"));
|
|
28345
29872
|
}
|
|
28346
29873
|
function handleDetail2(args2, db) {
|
|
28347
29874
|
const key = args2.feature_key;
|
|
28348
29875
|
const id = args2.feature_id;
|
|
28349
29876
|
if (!key && !id) {
|
|
28350
|
-
return
|
|
29877
|
+
return text5("Error: Provide either feature_key or feature_id");
|
|
28351
29878
|
}
|
|
28352
29879
|
const detail = getFeatureDetail(db, id || key);
|
|
28353
29880
|
if (!detail) {
|
|
28354
|
-
return
|
|
29881
|
+
return text5(`Feature not found: ${key || id}`);
|
|
28355
29882
|
}
|
|
28356
29883
|
const lines = [];
|
|
28357
29884
|
lines.push(`## Feature: ${detail.feature_key}`);
|
|
@@ -28397,12 +29924,12 @@ function handleDetail2(args2, db) {
|
|
|
28397
29924
|
lines.push(`- [${c2.created_at}] ${c2.change_type}: ${c2.change_detail || "(no detail)"}${c2.commit_hash ? " @" + c2.commit_hash.substring(0, 8) : ""}`);
|
|
28398
29925
|
}
|
|
28399
29926
|
}
|
|
28400
|
-
return
|
|
29927
|
+
return text5(lines.join("\n"));
|
|
28401
29928
|
}
|
|
28402
29929
|
function handleImpact(args2, db) {
|
|
28403
29930
|
const files = args2.files;
|
|
28404
29931
|
if (!files || files.length === 0) {
|
|
28405
|
-
return
|
|
29932
|
+
return text5("Error: Provide files array");
|
|
28406
29933
|
}
|
|
28407
29934
|
const report = getFeatureImpact(db, files);
|
|
28408
29935
|
const lines = [];
|
|
@@ -28438,7 +29965,7 @@ function handleImpact(args2, db) {
|
|
|
28438
29965
|
}
|
|
28439
29966
|
lines.push("");
|
|
28440
29967
|
}
|
|
28441
|
-
return
|
|
29968
|
+
return text5(lines.join("\n"));
|
|
28442
29969
|
}
|
|
28443
29970
|
function handleValidate(args2, db) {
|
|
28444
29971
|
const domain = args2.domain;
|
|
@@ -28483,14 +30010,14 @@ function handleValidate(args2, db) {
|
|
|
28483
30010
|
}
|
|
28484
30011
|
const result = report.orphaned === 0 ? "PASS" : "FAIL";
|
|
28485
30012
|
lines.push(`### RESULT: ${result}`);
|
|
28486
|
-
return
|
|
30013
|
+
return text5(lines.join("\n"));
|
|
28487
30014
|
}
|
|
28488
30015
|
function handleRegister(args2, db) {
|
|
28489
30016
|
const featureKey = args2.feature_key;
|
|
28490
30017
|
const domain = args2.domain;
|
|
28491
30018
|
const title = args2.title;
|
|
28492
30019
|
if (!featureKey || !domain || !title) {
|
|
28493
|
-
return
|
|
30020
|
+
return text5("Error: feature_key, domain, and title are required");
|
|
28494
30021
|
}
|
|
28495
30022
|
const featureId = upsertFeature(db, {
|
|
28496
30023
|
feature_key: featureKey,
|
|
@@ -28520,15 +30047,15 @@ function handleRegister(args2, db) {
|
|
|
28520
30047
|
linkPage(db, featureId, p19.route, p19.portal);
|
|
28521
30048
|
}
|
|
28522
30049
|
}
|
|
28523
|
-
logChange(db, featureId, "created", `Registered via ${
|
|
28524
|
-
return
|
|
30050
|
+
logChange(db, featureId, "created", `Registered via ${p6("sentinel_register")}`);
|
|
30051
|
+
return text5(`Feature registered: ${featureKey} (ID: ${featureId})
|
|
28525
30052
|
Components: ${components?.length || 0}, Procedures: ${procedures?.length || 0}, Pages: ${pages?.length || 0}`);
|
|
28526
30053
|
}
|
|
28527
30054
|
function handleParityCheck(args2, db) {
|
|
28528
30055
|
const oldFiles = args2.old_files;
|
|
28529
30056
|
const newFiles = args2.new_files;
|
|
28530
30057
|
if (!oldFiles || !newFiles) {
|
|
28531
|
-
return
|
|
30058
|
+
return text5("Error: Provide both old_files and new_files arrays");
|
|
28532
30059
|
}
|
|
28533
30060
|
const report = checkParity(db, oldFiles, newFiles);
|
|
28534
30061
|
const lines = [];
|
|
@@ -28560,7 +30087,7 @@ function handleParityCheck(args2, db) {
|
|
|
28560
30087
|
}
|
|
28561
30088
|
const result = report.gaps.length === 0 ? "PASS" : `FAIL (${report.gaps.length} gaps)`;
|
|
28562
30089
|
lines.push(`### RESULT: ${result}`);
|
|
28563
|
-
return
|
|
30090
|
+
return text5(lines.join("\n"));
|
|
28564
30091
|
}
|
|
28565
30092
|
var init_sentinel_tools = __esm({
|
|
28566
30093
|
"src/sentinel-tools.ts"() {
|
|
@@ -28572,8 +30099,8 @@ var init_sentinel_tools = __esm({
|
|
|
28572
30099
|
});
|
|
28573
30100
|
|
|
28574
30101
|
// src/sentinel-scanner.ts
|
|
28575
|
-
import { readFileSync as
|
|
28576
|
-
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";
|
|
28577
30104
|
function inferDomain(filePath) {
|
|
28578
30105
|
const domains = getConfig().domains;
|
|
28579
30106
|
const path = filePath.toLowerCase();
|
|
@@ -28705,7 +30232,7 @@ function scanComponentExports(dataDb) {
|
|
|
28705
30232
|
const componentsBase = config.paths.components ?? config.paths.source + "/components";
|
|
28706
30233
|
const componentDirs = [];
|
|
28707
30234
|
const basePath = resolve36(projectRoot, componentsBase);
|
|
28708
|
-
if (
|
|
30235
|
+
if (existsSync56(basePath)) {
|
|
28709
30236
|
try {
|
|
28710
30237
|
const entries = readdirSync26(basePath, { withFileTypes: true });
|
|
28711
30238
|
for (const entry of entries) {
|
|
@@ -28718,11 +30245,11 @@ function scanComponentExports(dataDb) {
|
|
|
28718
30245
|
}
|
|
28719
30246
|
for (const dir of componentDirs) {
|
|
28720
30247
|
const absDir = resolve36(projectRoot, dir);
|
|
28721
|
-
if (!
|
|
30248
|
+
if (!existsSync56(absDir)) continue;
|
|
28722
30249
|
const files = walkDir2(absDir).filter((f2) => f2.endsWith(".tsx") || f2.endsWith(".ts"));
|
|
28723
30250
|
for (const file of files) {
|
|
28724
|
-
const relPath =
|
|
28725
|
-
const source =
|
|
30251
|
+
const relPath = relative13(projectRoot, file);
|
|
30252
|
+
const source = readFileSync47(file, "utf-8");
|
|
28726
30253
|
const annotations = parseFeatureAnnotations(source);
|
|
28727
30254
|
if (annotations.length > 0) {
|
|
28728
30255
|
for (const ann of annotations) {
|
|
@@ -28748,7 +30275,7 @@ function scanComponentExports(dataDb) {
|
|
|
28748
30275
|
if (hasHandlers && exportMatch) {
|
|
28749
30276
|
const componentName = exportMatch[1];
|
|
28750
30277
|
const domain = inferDomain(relPath);
|
|
28751
|
-
const subdomain = basename10(
|
|
30278
|
+
const subdomain = basename10(dirname25(relPath));
|
|
28752
30279
|
const featureKey = `component.${subdomain}.${componentName.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "")}`;
|
|
28753
30280
|
if (!annotations.some((a2) => a2.featureKey === featureKey)) {
|
|
28754
30281
|
features.push({
|
|
@@ -28773,7 +30300,7 @@ function walkDir2(dir) {
|
|
|
28773
30300
|
try {
|
|
28774
30301
|
const entries = readdirSync26(dir);
|
|
28775
30302
|
for (const entry of entries) {
|
|
28776
|
-
const fullPath =
|
|
30303
|
+
const fullPath = join34(dir, entry);
|
|
28777
30304
|
try {
|
|
28778
30305
|
const stat = statSync19(fullPath);
|
|
28779
30306
|
if (stat.isDirectory()) {
|
|
@@ -28844,7 +30371,7 @@ var init_sentinel_scanner = __esm({
|
|
|
28844
30371
|
});
|
|
28845
30372
|
|
|
28846
30373
|
// src/analytics.ts
|
|
28847
|
-
function
|
|
30374
|
+
function p7(baseName) {
|
|
28848
30375
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
28849
30376
|
}
|
|
28850
30377
|
function getWeights() {
|
|
@@ -28912,7 +30439,7 @@ function backfillQualityScores(db) {
|
|
|
28912
30439
|
function getAnalyticsToolDefinitions() {
|
|
28913
30440
|
return [
|
|
28914
30441
|
{
|
|
28915
|
-
name:
|
|
30442
|
+
name: p7("quality_score"),
|
|
28916
30443
|
description: "Calculate and store quality score for a session based on observations. Shows breakdown by category.",
|
|
28917
30444
|
inputSchema: {
|
|
28918
30445
|
type: "object",
|
|
@@ -28923,7 +30450,7 @@ function getAnalyticsToolDefinitions() {
|
|
|
28923
30450
|
}
|
|
28924
30451
|
},
|
|
28925
30452
|
{
|
|
28926
|
-
name:
|
|
30453
|
+
name: p7("quality_trend"),
|
|
28927
30454
|
description: "Quality trend over recent sessions. Shows score progression and identifies improving/declining areas.",
|
|
28928
30455
|
inputSchema: {
|
|
28929
30456
|
type: "object",
|
|
@@ -28934,7 +30461,7 @@ function getAnalyticsToolDefinitions() {
|
|
|
28934
30461
|
}
|
|
28935
30462
|
},
|
|
28936
30463
|
{
|
|
28937
|
-
name:
|
|
30464
|
+
name: p7("quality_report"),
|
|
28938
30465
|
description: "Comprehensive quality report with averages, trends, and recommendations.",
|
|
28939
30466
|
inputSchema: {
|
|
28940
30467
|
type: "object",
|
|
@@ -28963,17 +30490,17 @@ function handleAnalyticsToolCall(name, args2, memoryDb) {
|
|
|
28963
30490
|
case "quality_report":
|
|
28964
30491
|
return handleQualityReport(args2, memoryDb);
|
|
28965
30492
|
default:
|
|
28966
|
-
return
|
|
30493
|
+
return text6(`Unknown analytics tool: ${name}`);
|
|
28967
30494
|
}
|
|
28968
30495
|
} catch (error) {
|
|
28969
|
-
return
|
|
30496
|
+
return text6(`Error in ${name}: ${error instanceof Error ? error.message : String(error)}
|
|
28970
30497
|
|
|
28971
|
-
Usage: ${
|
|
30498
|
+
Usage: ${p7("quality_score")} { session_id: "..." }, ${p7("quality_trend")} { days: 30 }`);
|
|
28972
30499
|
}
|
|
28973
30500
|
}
|
|
28974
30501
|
function handleQualityScore(args2, db) {
|
|
28975
30502
|
const sessionId = args2.session_id;
|
|
28976
|
-
if (!sessionId) return
|
|
30503
|
+
if (!sessionId) return text6(`Usage: ${p7("quality_score")} { session_id: "abc123" } - Calculate quality score for a specific session.`);
|
|
28977
30504
|
const { score, breakdown } = calculateQualityScore(db, sessionId);
|
|
28978
30505
|
storeQualityScore(db, sessionId, score, breakdown);
|
|
28979
30506
|
const categories = getCategories();
|
|
@@ -28989,7 +30516,7 @@ function handleQualityScore(args2, db) {
|
|
|
28989
30516
|
const indicator = impact > 0 ? "+" : impact < 0 ? "" : " ";
|
|
28990
30517
|
lines.push(`| ${category} | ${indicator}${impact} |`);
|
|
28991
30518
|
}
|
|
28992
|
-
return
|
|
30519
|
+
return text6(lines.join("\n"));
|
|
28993
30520
|
}
|
|
28994
30521
|
function handleQualityTrend(args2, db, retried = false) {
|
|
28995
30522
|
const days = args2.days ?? 30;
|
|
@@ -29007,7 +30534,7 @@ function handleQualityTrend(args2, db, retried = false) {
|
|
|
29007
30534
|
return handleQualityTrend(args2, db, true);
|
|
29008
30535
|
}
|
|
29009
30536
|
}
|
|
29010
|
-
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 }.`);
|
|
29011
30538
|
}
|
|
29012
30539
|
const avg = scores.reduce((sum, s) => sum + s.score, 0) / scores.length;
|
|
29013
30540
|
const recent = scores.slice(-5);
|
|
@@ -29026,7 +30553,7 @@ function handleQualityTrend(args2, db, retried = false) {
|
|
|
29026
30553
|
for (const s of scores.slice(-10)) {
|
|
29027
30554
|
lines.push(`| ${s.session_id.slice(0, 8)}... | ${s.score} | ${s.created_at} |`);
|
|
29028
30555
|
}
|
|
29029
|
-
return
|
|
30556
|
+
return text6(lines.join("\n"));
|
|
29030
30557
|
}
|
|
29031
30558
|
function handleQualityReport(args2, db, retried = false) {
|
|
29032
30559
|
const days = args2.days ?? 30;
|
|
@@ -29043,7 +30570,7 @@ function handleQualityReport(args2, db, retried = false) {
|
|
|
29043
30570
|
return handleQualityReport(args2, db, true);
|
|
29044
30571
|
}
|
|
29045
30572
|
}
|
|
29046
|
-
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 }.`);
|
|
29047
30574
|
}
|
|
29048
30575
|
const avg = scores.reduce((sum, s) => sum + s.score, 0) / scores.length;
|
|
29049
30576
|
const max = Math.max(...scores.map((s) => s.score));
|
|
@@ -29100,9 +30627,9 @@ function handleQualityReport(args2, db, retried = false) {
|
|
|
29100
30627
|
if (avg >= 70) {
|
|
29101
30628
|
lines.push("- Quality is good. Maintain current practices and focus on consistency.");
|
|
29102
30629
|
}
|
|
29103
|
-
return
|
|
30630
|
+
return text6(lines.join("\n"));
|
|
29104
30631
|
}
|
|
29105
|
-
function
|
|
30632
|
+
function text6(content) {
|
|
29106
30633
|
return { content: [{ type: "text", text: content }] };
|
|
29107
30634
|
}
|
|
29108
30635
|
var DEFAULT_WEIGHTS, DEFAULT_CATEGORIES, ANALYTICS_BASE_NAMES;
|
|
@@ -29125,7 +30652,7 @@ var init_analytics = __esm({
|
|
|
29125
30652
|
});
|
|
29126
30653
|
|
|
29127
30654
|
// src/cost-tracker.ts
|
|
29128
|
-
function
|
|
30655
|
+
function p8(baseName) {
|
|
29129
30656
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
29130
30657
|
}
|
|
29131
30658
|
function getModelPricing() {
|
|
@@ -29134,7 +30661,7 @@ function getModelPricing() {
|
|
|
29134
30661
|
function getCostToolDefinitions() {
|
|
29135
30662
|
return [
|
|
29136
30663
|
{
|
|
29137
|
-
name:
|
|
30664
|
+
name: p8("cost_session"),
|
|
29138
30665
|
description: "Show cost breakdown for a session. Includes token counts, model pricing, and cost by category.",
|
|
29139
30666
|
inputSchema: {
|
|
29140
30667
|
type: "object",
|
|
@@ -29145,7 +30672,7 @@ function getCostToolDefinitions() {
|
|
|
29145
30672
|
}
|
|
29146
30673
|
},
|
|
29147
30674
|
{
|
|
29148
|
-
name:
|
|
30675
|
+
name: p8("cost_trend"),
|
|
29149
30676
|
description: "Cost trend over time. Shows daily/weekly spending and identifies cost drivers.",
|
|
29150
30677
|
inputSchema: {
|
|
29151
30678
|
type: "object",
|
|
@@ -29157,7 +30684,7 @@ function getCostToolDefinitions() {
|
|
|
29157
30684
|
}
|
|
29158
30685
|
},
|
|
29159
30686
|
{
|
|
29160
|
-
name:
|
|
30687
|
+
name: p8("cost_feature"),
|
|
29161
30688
|
description: "Cost attribution by feature. Shows which features consume the most tokens.",
|
|
29162
30689
|
inputSchema: {
|
|
29163
30690
|
type: "object",
|
|
@@ -29186,22 +30713,22 @@ function handleCostToolCall(name, args2, memoryDb) {
|
|
|
29186
30713
|
case "cost_feature":
|
|
29187
30714
|
return handleCostFeature(args2, memoryDb);
|
|
29188
30715
|
default:
|
|
29189
|
-
return
|
|
30716
|
+
return text7(`Unknown cost tool: ${name}`);
|
|
29190
30717
|
}
|
|
29191
30718
|
} catch (error) {
|
|
29192
|
-
return
|
|
30719
|
+
return text7(`Error in ${name}: ${error instanceof Error ? error.message : String(error)}
|
|
29193
30720
|
|
|
29194
|
-
Usage: ${
|
|
30721
|
+
Usage: ${p8("cost_session")} { session_id: "..." }, ${p8("cost_trend")} { days: 30 }`);
|
|
29195
30722
|
}
|
|
29196
30723
|
}
|
|
29197
30724
|
function handleCostSession(args2, db) {
|
|
29198
30725
|
const sessionId = args2.session_id;
|
|
29199
|
-
if (!sessionId) return
|
|
30726
|
+
if (!sessionId) return text7(`Usage: ${p8("cost_session")} { session_id: "abc123" } - Show cost breakdown for a specific session.`);
|
|
29200
30727
|
const cost = db.prepare(
|
|
29201
30728
|
"SELECT * FROM session_costs WHERE session_id = ? ORDER BY created_at DESC LIMIT 1"
|
|
29202
30729
|
).get(sessionId);
|
|
29203
30730
|
if (!cost) {
|
|
29204
|
-
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.`);
|
|
29205
30732
|
}
|
|
29206
30733
|
const modelPricing = getModelPricing();
|
|
29207
30734
|
const mp = modelPricing[cost.model] ?? modelPricing["claude-sonnet-4-5"] ?? { input_per_million: 3, output_per_million: 15 };
|
|
@@ -29222,13 +30749,13 @@ function handleCostSession(args2, db) {
|
|
|
29222
30749
|
`| Cache Write | ${cost.cache_write_tokens.toLocaleString()} | $${cacheWriteCost.toFixed(4)} |`,
|
|
29223
30750
|
`| **Total** | ${cost.total_tokens.toLocaleString()} | **$${cost.estimated_cost_usd.toFixed(4)}** |`
|
|
29224
30751
|
];
|
|
29225
|
-
return
|
|
30752
|
+
return text7(lines.join("\n"));
|
|
29226
30753
|
}
|
|
29227
30754
|
function handleCostTrend(args2, db) {
|
|
29228
30755
|
const days = args2.days ?? 30;
|
|
29229
30756
|
const groupBy = args2.group_by ?? "day";
|
|
29230
30757
|
if (!["day", "week"].includes(groupBy)) {
|
|
29231
|
-
return
|
|
30758
|
+
return text7(`Invalid group_by value: "${groupBy}". Use "day" or "week". Example: ${p8("cost_trend")} { days: 30, group_by: "week" }`);
|
|
29232
30759
|
}
|
|
29233
30760
|
const sql = groupBy === "week" ? `SELECT strftime('%Y-W%W', created_at) as period,
|
|
29234
30761
|
COUNT(*) as sessions,
|
|
@@ -29249,7 +30776,7 @@ function handleCostTrend(args2, db) {
|
|
|
29249
30776
|
ORDER BY period ASC`;
|
|
29250
30777
|
const rows = db.prepare(sql).all(`-${days} days`);
|
|
29251
30778
|
if (rows.length === 0) {
|
|
29252
|
-
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.`);
|
|
29253
30780
|
}
|
|
29254
30781
|
const totalCost = rows.reduce((sum, r2) => sum + r2.total_cost, 0);
|
|
29255
30782
|
const totalSessions = rows.reduce((sum, r2) => sum + r2.sessions, 0);
|
|
@@ -29267,7 +30794,7 @@ function handleCostTrend(args2, db) {
|
|
|
29267
30794
|
`| ${row.period} | ${row.sessions} | $${row.total_cost.toFixed(2)} | $${row.avg_cost.toFixed(4)} |`
|
|
29268
30795
|
);
|
|
29269
30796
|
}
|
|
29270
|
-
return
|
|
30797
|
+
return text7(lines.join("\n"));
|
|
29271
30798
|
}
|
|
29272
30799
|
function handleCostFeature(args2, db) {
|
|
29273
30800
|
const days = args2.days ?? 30;
|
|
@@ -29279,7 +30806,7 @@ function handleCostFeature(args2, db) {
|
|
|
29279
30806
|
ORDER BY total_cost DESC
|
|
29280
30807
|
`).all(`-${days} days`);
|
|
29281
30808
|
if (rows.length === 0) {
|
|
29282
|
-
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.`);
|
|
29283
30810
|
}
|
|
29284
30811
|
const totalCost = rows.reduce((sum, r2) => sum + r2.total_cost, 0);
|
|
29285
30812
|
const lines = [
|
|
@@ -29295,9 +30822,9 @@ function handleCostFeature(args2, db) {
|
|
|
29295
30822
|
`| ${row.feature_key} | $${row.total_cost.toFixed(2)} | ${row.total_tokens.toLocaleString()} | ${row.entries} | ${pct}% |`
|
|
29296
30823
|
);
|
|
29297
30824
|
}
|
|
29298
|
-
return
|
|
30825
|
+
return text7(lines.join("\n"));
|
|
29299
30826
|
}
|
|
29300
|
-
function
|
|
30827
|
+
function text7(content) {
|
|
29301
30828
|
return { content: [{ type: "text", text: content }] };
|
|
29302
30829
|
}
|
|
29303
30830
|
var DEFAULT_MODEL_PRICING, COST_BASE_NAMES;
|
|
@@ -29317,7 +30844,7 @@ var init_cost_tracker = __esm({
|
|
|
29317
30844
|
});
|
|
29318
30845
|
|
|
29319
30846
|
// src/prompt-analyzer.ts
|
|
29320
|
-
function
|
|
30847
|
+
function p9(baseName) {
|
|
29321
30848
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
29322
30849
|
}
|
|
29323
30850
|
function categorizePrompt(promptText) {
|
|
@@ -29332,7 +30859,7 @@ function categorizePrompt(promptText) {
|
|
|
29332
30859
|
function getPromptToolDefinitions() {
|
|
29333
30860
|
return [
|
|
29334
30861
|
{
|
|
29335
|
-
name:
|
|
30862
|
+
name: p9("prompt_effectiveness"),
|
|
29336
30863
|
description: "Prompt effectiveness statistics by category. Shows success rates, average corrections needed, and best-performing prompt patterns.",
|
|
29337
30864
|
inputSchema: {
|
|
29338
30865
|
type: "object",
|
|
@@ -29347,7 +30874,7 @@ function getPromptToolDefinitions() {
|
|
|
29347
30874
|
}
|
|
29348
30875
|
},
|
|
29349
30876
|
{
|
|
29350
|
-
name:
|
|
30877
|
+
name: p9("prompt_suggestions"),
|
|
29351
30878
|
description: "Suggest improvements for a prompt based on past outcomes. Finds similar prompts ranked by success rate.",
|
|
29352
30879
|
inputSchema: {
|
|
29353
30880
|
type: "object",
|
|
@@ -29374,12 +30901,12 @@ function handlePromptToolCall(name, args2, memoryDb) {
|
|
|
29374
30901
|
case "prompt_suggestions":
|
|
29375
30902
|
return handleSuggestions(args2, memoryDb);
|
|
29376
30903
|
default:
|
|
29377
|
-
return
|
|
30904
|
+
return text8(`Unknown prompt tool: ${name}`);
|
|
29378
30905
|
}
|
|
29379
30906
|
} catch (error) {
|
|
29380
|
-
return
|
|
30907
|
+
return text8(`Error in ${name}: ${error instanceof Error ? error.message : String(error)}
|
|
29381
30908
|
|
|
29382
|
-
Usage: ${
|
|
30909
|
+
Usage: ${p9("prompt_effectiveness")} { days: 30 }, ${p9("prompt_suggestions")} { prompt: "..." }`);
|
|
29383
30910
|
}
|
|
29384
30911
|
}
|
|
29385
30912
|
function handleEffectiveness(args2, db) {
|
|
@@ -29405,7 +30932,7 @@ function handleEffectiveness(args2, db) {
|
|
|
29405
30932
|
sql += " GROUP BY prompt_category ORDER BY total DESC";
|
|
29406
30933
|
const rows = db.prepare(sql).all(...params);
|
|
29407
30934
|
if (rows.length === 0) {
|
|
29408
|
-
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.`);
|
|
29409
30936
|
}
|
|
29410
30937
|
const lines = [
|
|
29411
30938
|
`## Prompt Effectiveness (${days} days)`,
|
|
@@ -29420,11 +30947,11 @@ function handleEffectiveness(args2, db) {
|
|
|
29420
30947
|
`| ${row.prompt_category} | ${total} | ${successRate}% | ${row.partials} | ${row.failures} | ${row.abandoned} | ${row.avg_corrections.toFixed(1)} | ${Math.round(row.avg_word_count)} |`
|
|
29421
30948
|
);
|
|
29422
30949
|
}
|
|
29423
|
-
return
|
|
30950
|
+
return text8(lines.join("\n"));
|
|
29424
30951
|
}
|
|
29425
30952
|
function handleSuggestions(args2, db) {
|
|
29426
30953
|
const prompt = args2.prompt;
|
|
29427
|
-
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.`);
|
|
29428
30955
|
const category = categorizePrompt(prompt);
|
|
29429
30956
|
const wordCount = prompt.split(/\s+/).length;
|
|
29430
30957
|
const similar = db.prepare(`
|
|
@@ -29467,9 +30994,9 @@ function handleSuggestions(args2, db) {
|
|
|
29467
30994
|
lines.push(`- Success rate: ${Math.round(stats.successes / stats.total * 100)}%`);
|
|
29468
30995
|
lines.push(`- Avg corrections needed: ${stats.avg_corrections.toFixed(1)}`);
|
|
29469
30996
|
}
|
|
29470
|
-
return
|
|
30997
|
+
return text8(lines.join("\n"));
|
|
29471
30998
|
}
|
|
29472
|
-
function
|
|
30999
|
+
function text8(content) {
|
|
29473
31000
|
return { content: [{ type: "text", text: content }] };
|
|
29474
31001
|
}
|
|
29475
31002
|
var PROMPT_BASE_NAMES;
|
|
@@ -29482,290 +31009,8 @@ var init_prompt_analyzer = __esm({
|
|
|
29482
31009
|
}
|
|
29483
31010
|
});
|
|
29484
31011
|
|
|
29485
|
-
// src/audit-trail.ts
|
|
29486
|
-
function p9(baseName) {
|
|
29487
|
-
return `${getConfig().toolPrefix}_${baseName}`;
|
|
29488
|
-
}
|
|
29489
|
-
function getAuditFormats() {
|
|
29490
|
-
return getConfig().governance?.audit?.formats ?? DEFAULT_FORMATS;
|
|
29491
|
-
}
|
|
29492
|
-
function queryAuditLog(db, options) {
|
|
29493
|
-
let sql = "SELECT * FROM audit_log WHERE 1=1";
|
|
29494
|
-
const params = [];
|
|
29495
|
-
if (options.eventType) {
|
|
29496
|
-
sql += " AND event_type = ?";
|
|
29497
|
-
params.push(options.eventType);
|
|
29498
|
-
}
|
|
29499
|
-
if (options.actor) {
|
|
29500
|
-
sql += " AND actor = ?";
|
|
29501
|
-
params.push(options.actor);
|
|
29502
|
-
}
|
|
29503
|
-
if (options.days) {
|
|
29504
|
-
sql += " AND timestamp >= datetime('now', ?)";
|
|
29505
|
-
params.push(`-${options.days} days`);
|
|
29506
|
-
}
|
|
29507
|
-
if (options.filePath) {
|
|
29508
|
-
sql += " AND file_path = ?";
|
|
29509
|
-
params.push(options.filePath);
|
|
29510
|
-
}
|
|
29511
|
-
if (options.changeType) {
|
|
29512
|
-
sql += " AND change_type = ?";
|
|
29513
|
-
params.push(options.changeType);
|
|
29514
|
-
}
|
|
29515
|
-
sql += " ORDER BY timestamp DESC";
|
|
29516
|
-
if (options.limit) {
|
|
29517
|
-
sql += " LIMIT ?";
|
|
29518
|
-
params.push(options.limit);
|
|
29519
|
-
}
|
|
29520
|
-
return db.prepare(sql).all(...params);
|
|
29521
|
-
}
|
|
29522
|
-
function getFileChain(db, filePath) {
|
|
29523
|
-
return db.prepare(
|
|
29524
|
-
"SELECT * FROM audit_log WHERE file_path = ? ORDER BY timestamp ASC LIMIT 10000"
|
|
29525
|
-
).all(filePath);
|
|
29526
|
-
}
|
|
29527
|
-
function getAuditToolDefinitions() {
|
|
29528
|
-
return [
|
|
29529
|
-
{
|
|
29530
|
-
name: p9("audit_log"),
|
|
29531
|
-
description: "Query the audit log. Filter by event type, actor, file, or time range.",
|
|
29532
|
-
inputSchema: {
|
|
29533
|
-
type: "object",
|
|
29534
|
-
properties: {
|
|
29535
|
-
event_type: { type: "string", description: "Filter by event type: code_change, rule_enforced, approval, review, commit, compaction" },
|
|
29536
|
-
actor: { type: "string", description: "Filter by actor: ai, human, hook, agent" },
|
|
29537
|
-
file_path: { type: "string", description: "Filter by file path" },
|
|
29538
|
-
change_type: { type: "string", description: "Filter by change type: create, edit, delete" },
|
|
29539
|
-
days: { type: "number", description: "Days to look back (default: 30)" },
|
|
29540
|
-
limit: { type: "number", description: "Max results (default: 50)" }
|
|
29541
|
-
},
|
|
29542
|
-
required: []
|
|
29543
|
-
}
|
|
29544
|
-
},
|
|
29545
|
-
{
|
|
29546
|
-
name: p9("audit_report"),
|
|
29547
|
-
description: "Generate an audit report in a specified format (summary, detailed, soc2).",
|
|
29548
|
-
inputSchema: {
|
|
29549
|
-
type: "object",
|
|
29550
|
-
properties: {
|
|
29551
|
-
format: { type: "string", description: "Report format: summary, detailed, soc2 (default: summary)" },
|
|
29552
|
-
days: { type: "number", description: "Days to cover (default: 30)" }
|
|
29553
|
-
},
|
|
29554
|
-
required: []
|
|
29555
|
-
}
|
|
29556
|
-
},
|
|
29557
|
-
{
|
|
29558
|
-
name: p9("audit_chain"),
|
|
29559
|
-
description: "Get the complete audit trail for a specific file. Shows all changes, reviews, and decisions chronologically.",
|
|
29560
|
-
inputSchema: {
|
|
29561
|
-
type: "object",
|
|
29562
|
-
properties: {
|
|
29563
|
-
file: { type: "string", description: "File path to trace" }
|
|
29564
|
-
},
|
|
29565
|
-
required: ["file"]
|
|
29566
|
-
}
|
|
29567
|
-
}
|
|
29568
|
-
];
|
|
29569
|
-
}
|
|
29570
|
-
function isAuditTool(name) {
|
|
29571
|
-
const pfx = getConfig().toolPrefix + "_";
|
|
29572
|
-
const baseName = name.startsWith(pfx) ? name.slice(pfx.length) : name;
|
|
29573
|
-
return AUDIT_BASE_NAMES.has(baseName);
|
|
29574
|
-
}
|
|
29575
|
-
function handleAuditToolCall(name, args2, memoryDb) {
|
|
29576
|
-
try {
|
|
29577
|
-
const pfx = getConfig().toolPrefix + "_";
|
|
29578
|
-
const baseName = name.startsWith(pfx) ? name.slice(pfx.length) : name;
|
|
29579
|
-
switch (baseName) {
|
|
29580
|
-
case "audit_log":
|
|
29581
|
-
return handleAuditLog(args2, memoryDb);
|
|
29582
|
-
case "audit_report":
|
|
29583
|
-
return handleAuditReport(args2, memoryDb);
|
|
29584
|
-
case "audit_chain":
|
|
29585
|
-
return handleAuditChain(args2, memoryDb);
|
|
29586
|
-
default:
|
|
29587
|
-
return text8(`Unknown audit tool: ${name}`);
|
|
29588
|
-
}
|
|
29589
|
-
} catch (error) {
|
|
29590
|
-
return text8(`Error in ${name}: ${error instanceof Error ? error.message : String(error)}
|
|
29591
|
-
|
|
29592
|
-
Usage: ${p9("audit_log")} { severity: "critical" }, ${p9("audit_report")} { format: "soc2" }`);
|
|
29593
|
-
}
|
|
29594
|
-
}
|
|
29595
|
-
function handleAuditLog(args2, db) {
|
|
29596
|
-
const entries = queryAuditLog(db, {
|
|
29597
|
-
eventType: args2.event_type,
|
|
29598
|
-
actor: args2.actor,
|
|
29599
|
-
filePath: args2.file_path,
|
|
29600
|
-
changeType: args2.change_type,
|
|
29601
|
-
days: args2.days ?? 30,
|
|
29602
|
-
limit: args2.limit ?? 50
|
|
29603
|
-
});
|
|
29604
|
-
if (entries.length === 0) {
|
|
29605
|
-
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.`);
|
|
29606
|
-
}
|
|
29607
|
-
const lines = [
|
|
29608
|
-
`## Audit Log (${entries.length} entries)`,
|
|
29609
|
-
"",
|
|
29610
|
-
"| Timestamp | Event | Actor | Change | File | Evidence |",
|
|
29611
|
-
"|-----------|-------|-------|--------|------|----------|"
|
|
29612
|
-
];
|
|
29613
|
-
for (const entry of entries) {
|
|
29614
|
-
const file = entry.file_path ? entry.file_path.split("/").pop() : "-";
|
|
29615
|
-
const evidence = entry.evidence ? entry.evidence.slice(0, 50) : "-";
|
|
29616
|
-
lines.push(
|
|
29617
|
-
`| ${entry.timestamp.slice(0, 16)} | ${entry.event_type} | ${entry.actor} | ${entry.change_type ?? "-"} | ${file} | ${evidence} |`
|
|
29618
|
-
);
|
|
29619
|
-
}
|
|
29620
|
-
return text8(lines.join("\n"));
|
|
29621
|
-
}
|
|
29622
|
-
function handleAuditReport(args2, db) {
|
|
29623
|
-
const format = args2.format ?? "summary";
|
|
29624
|
-
const days = args2.days ?? 30;
|
|
29625
|
-
const availableFormats = getAuditFormats();
|
|
29626
|
-
if (!availableFormats.includes(format)) {
|
|
29627
|
-
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.`);
|
|
29628
|
-
}
|
|
29629
|
-
const entries = queryAuditLog(db, { days });
|
|
29630
|
-
if (entries.length === 0) {
|
|
29631
|
-
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.`);
|
|
29632
|
-
}
|
|
29633
|
-
const byEventType = {};
|
|
29634
|
-
const byActor = {};
|
|
29635
|
-
for (const e2 of entries) {
|
|
29636
|
-
const et = e2.event_type;
|
|
29637
|
-
byEventType[et] = (byEventType[et] ?? 0) + 1;
|
|
29638
|
-
const actor = e2.actor;
|
|
29639
|
-
byActor[actor] = (byActor[actor] ?? 0) + 1;
|
|
29640
|
-
}
|
|
29641
|
-
if (format === "soc2") {
|
|
29642
|
-
return generateSoc2Report(entries, byEventType, byActor, days);
|
|
29643
|
-
}
|
|
29644
|
-
if (format === "detailed") {
|
|
29645
|
-
return generateDetailedReport(entries, byEventType, byActor, days);
|
|
29646
|
-
}
|
|
29647
|
-
const lines = [
|
|
29648
|
-
`## Audit Summary Report (${days} days)`,
|
|
29649
|
-
`Generated: ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)}`,
|
|
29650
|
-
"",
|
|
29651
|
-
"### By Event Type",
|
|
29652
|
-
`| Event Type | Count |`,
|
|
29653
|
-
`|------------|-------|`
|
|
29654
|
-
];
|
|
29655
|
-
for (const [et, count] of Object.entries(byEventType).sort((a2, b2) => b2[1] - a2[1])) {
|
|
29656
|
-
lines.push(`| ${et} | ${count} |`);
|
|
29657
|
-
}
|
|
29658
|
-
lines.push(`| **Total** | **${entries.length}** |`);
|
|
29659
|
-
lines.push("");
|
|
29660
|
-
lines.push("### By Actor");
|
|
29661
|
-
lines.push(`| Actor | Count |`);
|
|
29662
|
-
lines.push(`|-------|-------|`);
|
|
29663
|
-
for (const [actor, count] of Object.entries(byActor).sort((a2, b2) => b2[1] - a2[1])) {
|
|
29664
|
-
lines.push(`| ${actor} | ${count} |`);
|
|
29665
|
-
}
|
|
29666
|
-
return text8(lines.join("\n"));
|
|
29667
|
-
}
|
|
29668
|
-
function generateSoc2Report(entries, byEventType, byActor, days) {
|
|
29669
|
-
const lines = [
|
|
29670
|
-
`# SOC 2 Compliance Report`,
|
|
29671
|
-
`Period: Last ${days} days`,
|
|
29672
|
-
`Generated: ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
29673
|
-
"",
|
|
29674
|
-
"## 1. Control Environment",
|
|
29675
|
-
`Total audit events: ${entries.length}`,
|
|
29676
|
-
`Human-initiated: ${byActor["human"] ?? 0}`,
|
|
29677
|
-
`AI-initiated: ${byActor["ai"] ?? 0}`,
|
|
29678
|
-
`Hook-triggered: ${byActor["hook"] ?? 0}`,
|
|
29679
|
-
"",
|
|
29680
|
-
"## 2. Change Management",
|
|
29681
|
-
`Code changes logged: ${byEventType["code_change"] ?? 0}`,
|
|
29682
|
-
`Rule enforcements: ${byEventType["rule_enforced"] ?? 0}`,
|
|
29683
|
-
`Approvals recorded: ${byEventType["approval"] ?? 0}`,
|
|
29684
|
-
`Reviews: ${byEventType["review"] ?? 0}`,
|
|
29685
|
-
`Commits: ${byEventType["commit"] ?? 0}`,
|
|
29686
|
-
"",
|
|
29687
|
-
"## 3. Approval Status"
|
|
29688
|
-
];
|
|
29689
|
-
const pendingApprovals = entries.filter((e2) => e2.approval_status === "pending");
|
|
29690
|
-
const deniedApprovals = entries.filter((e2) => e2.approval_status === "denied");
|
|
29691
|
-
lines.push(`Pending approvals: ${pendingApprovals.length}`);
|
|
29692
|
-
lines.push(`Denied approvals: ${deniedApprovals.length}`);
|
|
29693
|
-
lines.push("");
|
|
29694
|
-
lines.push("## 4. Events Requiring Review");
|
|
29695
|
-
const reviewEntries = entries.filter((e2) => e2.approval_status === "denied" || e2.event_type === "rule_enforced");
|
|
29696
|
-
if (reviewEntries.length > 0) {
|
|
29697
|
-
for (const e2 of reviewEntries.slice(0, 20)) {
|
|
29698
|
-
lines.push(`- [${e2.timestamp.slice(0, 16)}] ${e2.event_type}: ${(e2.evidence ?? "").slice(0, 100)}`);
|
|
29699
|
-
}
|
|
29700
|
-
} else {
|
|
29701
|
-
lines.push("No events requiring review in this period.");
|
|
29702
|
-
}
|
|
29703
|
-
return text8(lines.join("\n"));
|
|
29704
|
-
}
|
|
29705
|
-
function generateDetailedReport(entries, byEventType, byActor, days) {
|
|
29706
|
-
const lines = [
|
|
29707
|
-
`## Detailed Audit Report (${days} days)`,
|
|
29708
|
-
`Total events: ${entries.length}`,
|
|
29709
|
-
"",
|
|
29710
|
-
"### Event Type Distribution"
|
|
29711
|
-
];
|
|
29712
|
-
for (const [et, count] of Object.entries(byEventType).sort((a2, b2) => b2[1] - a2[1])) {
|
|
29713
|
-
lines.push(`- ${et}: ${count}`);
|
|
29714
|
-
}
|
|
29715
|
-
lines.push("");
|
|
29716
|
-
lines.push("### Actor Distribution");
|
|
29717
|
-
for (const [actor, count] of Object.entries(byActor).sort((a2, b2) => b2[1] - a2[1])) {
|
|
29718
|
-
lines.push(`- ${actor}: ${count}`);
|
|
29719
|
-
}
|
|
29720
|
-
lines.push("");
|
|
29721
|
-
lines.push("### All Events");
|
|
29722
|
-
for (const e2 of entries.slice(0, 100)) {
|
|
29723
|
-
lines.push(`#### ${e2.timestamp.slice(0, 16)} [${e2.event_type}]`);
|
|
29724
|
-
lines.push(`Actor: ${e2.actor} | Change: ${e2.change_type ?? "-"} | Approval: ${e2.approval_status ?? "-"}`);
|
|
29725
|
-
if (e2.file_path) lines.push(`File: ${e2.file_path}`);
|
|
29726
|
-
if (e2.evidence) lines.push(`Evidence: ${e2.evidence}`);
|
|
29727
|
-
lines.push("");
|
|
29728
|
-
}
|
|
29729
|
-
if (entries.length > 100) {
|
|
29730
|
-
lines.push(`... and ${entries.length - 100} more entries`);
|
|
29731
|
-
}
|
|
29732
|
-
return text8(lines.join("\n"));
|
|
29733
|
-
}
|
|
29734
|
-
function handleAuditChain(args2, db) {
|
|
29735
|
-
const filePath = args2.file;
|
|
29736
|
-
if (!filePath) return text8(`Usage: ${p9("audit_chain")} { file: "src/path/to/file.ts" } - Get complete audit history for a file.`);
|
|
29737
|
-
const chain = getFileChain(db, filePath);
|
|
29738
|
-
if (chain.length === 0) {
|
|
29739
|
-
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.`);
|
|
29740
|
-
}
|
|
29741
|
-
const lines = [
|
|
29742
|
-
`## Audit Chain: ${filePath}`,
|
|
29743
|
-
`Total events: ${chain.length}`,
|
|
29744
|
-
""
|
|
29745
|
-
];
|
|
29746
|
-
for (const entry of chain) {
|
|
29747
|
-
lines.push(`### ${entry.timestamp.slice(0, 16)} [${entry.event_type}]`);
|
|
29748
|
-
lines.push(`**${entry.actor}** (${entry.change_type ?? "unknown"})`);
|
|
29749
|
-
if (entry.evidence) lines.push(entry.evidence);
|
|
29750
|
-
lines.push("");
|
|
29751
|
-
}
|
|
29752
|
-
return text8(lines.join("\n"));
|
|
29753
|
-
}
|
|
29754
|
-
function text8(content) {
|
|
29755
|
-
return { content: [{ type: "text", text: content }] };
|
|
29756
|
-
}
|
|
29757
|
-
var DEFAULT_FORMATS, AUDIT_BASE_NAMES;
|
|
29758
|
-
var init_audit_trail = __esm({
|
|
29759
|
-
"src/audit-trail.ts"() {
|
|
29760
|
-
"use strict";
|
|
29761
|
-
init_config();
|
|
29762
|
-
DEFAULT_FORMATS = ["summary", "detailed", "soc2"];
|
|
29763
|
-
AUDIT_BASE_NAMES = /* @__PURE__ */ new Set(["audit_log", "audit_report", "audit_chain"]);
|
|
29764
|
-
}
|
|
29765
|
-
});
|
|
29766
|
-
|
|
29767
31012
|
// src/validation-engine.ts
|
|
29768
|
-
import { existsSync as
|
|
31013
|
+
import { existsSync as existsSync57, readFileSync as readFileSync48 } from "fs";
|
|
29769
31014
|
function p10(baseName) {
|
|
29770
31015
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
29771
31016
|
}
|
|
@@ -29796,7 +31041,7 @@ function validateFile(filePath, projectRoot) {
|
|
|
29796
31041
|
});
|
|
29797
31042
|
return checks;
|
|
29798
31043
|
}
|
|
29799
|
-
if (!
|
|
31044
|
+
if (!existsSync57(absPath)) {
|
|
29800
31045
|
checks.push({
|
|
29801
31046
|
name: "file_exists",
|
|
29802
31047
|
severity: "error",
|
|
@@ -29805,7 +31050,7 @@ function validateFile(filePath, projectRoot) {
|
|
|
29805
31050
|
});
|
|
29806
31051
|
return checks;
|
|
29807
31052
|
}
|
|
29808
|
-
const source =
|
|
31053
|
+
const source = readFileSync48(absPath, "utf-8");
|
|
29809
31054
|
const lines = source.split("\n");
|
|
29810
31055
|
if (activeChecks.rule_compliance !== false) {
|
|
29811
31056
|
for (const ruleSet of config.rules) {
|
|
@@ -30264,7 +31509,7 @@ var init_adr_generator = __esm({
|
|
|
30264
31509
|
});
|
|
30265
31510
|
|
|
30266
31511
|
// src/security-scorer.ts
|
|
30267
|
-
import { existsSync as
|
|
31512
|
+
import { existsSync as existsSync58, readFileSync as readFileSync49 } from "fs";
|
|
30268
31513
|
function p12(baseName) {
|
|
30269
31514
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
30270
31515
|
}
|
|
@@ -30288,12 +31533,12 @@ function scoreFileSecurity(filePath, projectRoot) {
|
|
|
30288
31533
|
}]
|
|
30289
31534
|
};
|
|
30290
31535
|
}
|
|
30291
|
-
if (!
|
|
31536
|
+
if (!existsSync58(absPath)) {
|
|
30292
31537
|
return { riskScore: 0, findings: [] };
|
|
30293
31538
|
}
|
|
30294
31539
|
let source;
|
|
30295
31540
|
try {
|
|
30296
|
-
source =
|
|
31541
|
+
source = readFileSync49(absPath, "utf-8");
|
|
30297
31542
|
} catch {
|
|
30298
31543
|
return { riskScore: 0, findings: [] };
|
|
30299
31544
|
}
|
|
@@ -30583,7 +31828,7 @@ var init_security_scorer = __esm({
|
|
|
30583
31828
|
});
|
|
30584
31829
|
|
|
30585
31830
|
// src/dependency-scorer.ts
|
|
30586
|
-
import { existsSync as
|
|
31831
|
+
import { existsSync as existsSync59, readFileSync as readFileSync50 } from "fs";
|
|
30587
31832
|
import { resolve as resolve37 } from "path";
|
|
30588
31833
|
function p13(baseName) {
|
|
30589
31834
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
@@ -30617,9 +31862,9 @@ function calculateDepRisk(factors) {
|
|
|
30617
31862
|
}
|
|
30618
31863
|
function getInstalledPackages(projectRoot) {
|
|
30619
31864
|
const pkgPath = resolve37(projectRoot, "package.json");
|
|
30620
|
-
if (!
|
|
31865
|
+
if (!existsSync59(pkgPath)) return /* @__PURE__ */ new Map();
|
|
30621
31866
|
try {
|
|
30622
|
-
const pkg = JSON.parse(
|
|
31867
|
+
const pkg = JSON.parse(readFileSync50(pkgPath, "utf-8"));
|
|
30623
31868
|
const packages = /* @__PURE__ */ new Map();
|
|
30624
31869
|
for (const [name, version] of Object.entries(pkg.dependencies ?? {})) {
|
|
30625
31870
|
packages.set(name, version);
|
|
@@ -31223,30 +32468,10 @@ var init_regression_detector = __esm({
|
|
|
31223
32468
|
}
|
|
31224
32469
|
});
|
|
31225
32470
|
|
|
31226
|
-
// src/lib/corrections-md.ts
|
|
31227
|
-
function formatCorrectionEntry(e2) {
|
|
31228
|
-
const one = (s) => s.replace(/\s*\n+\s*/g, " ").trim();
|
|
31229
|
-
const lines = [`### ${e2.date} - ${one(e2.title)}`];
|
|
31230
|
-
if (e2.wrong) lines.push(`- **Wrong**: ${one(e2.wrong)}`);
|
|
31231
|
-
if (e2.correction) lines.push(`- **Correction**: ${one(e2.correction)}`);
|
|
31232
|
-
lines.push(`- **Rule**: ${one(e2.rule)}`);
|
|
31233
|
-
for (const [k3, v3] of Object.entries(e2.extra ?? {})) {
|
|
31234
|
-
lines.push(`- ${k3}: ${one(String(v3))}`);
|
|
31235
|
-
}
|
|
31236
|
-
return `
|
|
31237
|
-
${lines.join("\n")}
|
|
31238
|
-
`;
|
|
31239
|
-
}
|
|
31240
|
-
var init_corrections_md = __esm({
|
|
31241
|
-
"src/lib/corrections-md.ts"() {
|
|
31242
|
-
"use strict";
|
|
31243
|
-
}
|
|
31244
|
-
});
|
|
31245
|
-
|
|
31246
32471
|
// src/knowledge-indexer.ts
|
|
31247
|
-
import { createHash as
|
|
31248
|
-
import { readFileSync as
|
|
31249
|
-
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";
|
|
31250
32475
|
function getKnowledgePaths() {
|
|
31251
32476
|
const resolved = getResolvedPaths();
|
|
31252
32477
|
const config = getConfig();
|
|
@@ -31294,7 +32519,7 @@ function categorizeFile(filePath) {
|
|
|
31294
32519
|
const paths = getKnowledgePaths();
|
|
31295
32520
|
if (filePath.startsWith(paths.plansDir)) return "plan";
|
|
31296
32521
|
if (filePath.startsWith(paths.docsDir)) {
|
|
31297
|
-
const relFromDocs =
|
|
32522
|
+
const relFromDocs = relative14(paths.docsDir, filePath).replace(/\\/g, "/").toLowerCase();
|
|
31298
32523
|
if (relFromDocs.startsWith("plans/")) return "plan";
|
|
31299
32524
|
if (relFromDocs.includes("architecture")) return "architecture";
|
|
31300
32525
|
if (relFromDocs.includes("security")) return "security";
|
|
@@ -31310,7 +32535,7 @@ function categorizeFile(filePath) {
|
|
|
31310
32535
|
}
|
|
31311
32536
|
const claudeDirName = getConfig().conventions?.claudeDirName ?? ".claude";
|
|
31312
32537
|
if (filePath.includes(`${claudeDirName}/projects/`) && filePath.includes("/memory/")) return "memory";
|
|
31313
|
-
const rel =
|
|
32538
|
+
const rel = relative14(paths.claudeDir, filePath).replace(/\\/g, "/");
|
|
31314
32539
|
const firstDir = rel.split("/")[0];
|
|
31315
32540
|
const knownCategories = getConfig().conventions?.knowledgeCategories ?? [
|
|
31316
32541
|
"patterns",
|
|
@@ -31332,7 +32557,7 @@ function categorizeFile(filePath) {
|
|
|
31332
32557
|
return "root";
|
|
31333
32558
|
}
|
|
31334
32559
|
function hashContent2(content) {
|
|
31335
|
-
return
|
|
32560
|
+
return createHash14("sha256").update(content).digest("hex");
|
|
31336
32561
|
}
|
|
31337
32562
|
function parseCRTable(content) {
|
|
31338
32563
|
const rules = [];
|
|
@@ -31553,11 +32778,11 @@ function indexAllKnowledge(db) {
|
|
|
31553
32778
|
files.push(...memFiles);
|
|
31554
32779
|
} catch {
|
|
31555
32780
|
}
|
|
31556
|
-
if (
|
|
32781
|
+
if (existsSync60(paths.plansDir)) {
|
|
31557
32782
|
const planFiles = discoverMarkdownFiles(paths.plansDir);
|
|
31558
32783
|
files.push(...planFiles);
|
|
31559
32784
|
}
|
|
31560
|
-
if (
|
|
32785
|
+
if (existsSync60(paths.docsDir)) {
|
|
31561
32786
|
const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
|
|
31562
32787
|
const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f2) => !f2.includes("/plans/") && !excludePatterns.some((p19) => f2.includes(p19)));
|
|
31563
32788
|
files.push(...docsFiles);
|
|
@@ -31600,10 +32825,10 @@ function indexAllKnowledge(db) {
|
|
|
31600
32825
|
} catch {
|
|
31601
32826
|
}
|
|
31602
32827
|
for (const filePath of files) {
|
|
31603
|
-
if (!
|
|
31604
|
-
const content =
|
|
32828
|
+
if (!existsSync60(filePath)) continue;
|
|
32829
|
+
const content = readFileSync51(filePath, "utf-8");
|
|
31605
32830
|
const hash = hashContent2(content);
|
|
31606
|
-
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);
|
|
31607
32832
|
const category = categorizeFile(filePath);
|
|
31608
32833
|
const title = extractTitle(content, filePath);
|
|
31609
32834
|
const description = extractDescription2(content);
|
|
@@ -31736,10 +32961,10 @@ function isKnowledgeStale(db) {
|
|
|
31736
32961
|
files.push(...discoverMarkdownFiles(paths.memoryDir));
|
|
31737
32962
|
} catch {
|
|
31738
32963
|
}
|
|
31739
|
-
if (
|
|
32964
|
+
if (existsSync60(paths.plansDir)) {
|
|
31740
32965
|
files.push(...discoverMarkdownFiles(paths.plansDir));
|
|
31741
32966
|
}
|
|
31742
|
-
if (
|
|
32967
|
+
if (existsSync60(paths.docsDir)) {
|
|
31743
32968
|
const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
|
|
31744
32969
|
const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f2) => !f2.includes("/plans/") && !excludePatterns.some((p19) => f2.includes(p19)));
|
|
31745
32970
|
files.push(...docsFiles);
|
|
@@ -31768,7 +32993,7 @@ var init_knowledge_indexer = __esm({
|
|
|
31768
32993
|
});
|
|
31769
32994
|
|
|
31770
32995
|
// src/knowledge-tools.ts
|
|
31771
|
-
import { readFileSync as
|
|
32996
|
+
import { readFileSync as readFileSync52, writeFileSync as writeFileSync14, appendFileSync as appendFileSync4, readdirSync as readdirSync28 } from "fs";
|
|
31772
32997
|
import { resolve as resolve39, basename as basename12 } from "path";
|
|
31773
32998
|
function p16(baseName) {
|
|
31774
32999
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
@@ -32521,14 +33746,14 @@ function handleCorrect(db, args2) {
|
|
|
32521
33746
|
});
|
|
32522
33747
|
let existing = "";
|
|
32523
33748
|
try {
|
|
32524
|
-
existing =
|
|
33749
|
+
existing = readFileSync52(correctionsPath, "utf-8");
|
|
32525
33750
|
} catch {
|
|
32526
33751
|
}
|
|
32527
33752
|
const archiveIdx = existing.indexOf("## Archived");
|
|
32528
33753
|
if (archiveIdx > 0) {
|
|
32529
33754
|
const before = existing.slice(0, archiveIdx);
|
|
32530
33755
|
const after = existing.slice(archiveIdx);
|
|
32531
|
-
|
|
33756
|
+
writeFileSync14(correctionsPath, before + entry + after);
|
|
32532
33757
|
} else {
|
|
32533
33758
|
appendFileSync4(correctionsPath, entry);
|
|
32534
33759
|
}
|
|
@@ -33477,7 +34702,7 @@ var init_framework_supports = __esm({
|
|
|
33477
34702
|
});
|
|
33478
34703
|
|
|
33479
34704
|
// src/tools.ts
|
|
33480
|
-
import { readFileSync as
|
|
34705
|
+
import { readFileSync as readFileSync53, existsSync as existsSync62 } from "fs";
|
|
33481
34706
|
import { resolve as resolve40, basename as basename13 } from "path";
|
|
33482
34707
|
function prefix2() {
|
|
33483
34708
|
return getConfig().toolPrefix;
|
|
@@ -33897,8 +35122,8 @@ function handleContext(file, dataDb, codegraphDb) {
|
|
|
33897
35122
|
const resolvedPaths = getResolvedPaths();
|
|
33898
35123
|
const root = getProjectRoot();
|
|
33899
35124
|
const absFilePath = ensureWithinRoot(resolve40(resolvedPaths.srcDir, "..", file), root);
|
|
33900
|
-
if (
|
|
33901
|
-
const fileContent =
|
|
35125
|
+
if (existsSync62(absFilePath)) {
|
|
35126
|
+
const fileContent = readFileSync53(absFilePath, "utf-8").slice(0, 3e3);
|
|
33902
35127
|
const keywords = [];
|
|
33903
35128
|
if (fileContent.includes("ctx.db")) keywords.push("database", "schema");
|
|
33904
35129
|
if (fileContent.includes("BigInt") || fileContent.includes("Decimal")) keywords.push("BigInt", "serialization");
|
|
@@ -34336,10 +35561,10 @@ function handleSchema(args2) {
|
|
|
34336
35561
|
lines.push("");
|
|
34337
35562
|
const projectRoot = getProjectRoot();
|
|
34338
35563
|
const absPath = ensureWithinRoot(resolve40(projectRoot, file), projectRoot);
|
|
34339
|
-
if (!
|
|
35564
|
+
if (!existsSync62(absPath)) {
|
|
34340
35565
|
return text17(`File not found: ${file}`);
|
|
34341
35566
|
}
|
|
34342
|
-
const source =
|
|
35567
|
+
const source = readFileSync53(absPath, "utf-8");
|
|
34343
35568
|
const config = getConfig();
|
|
34344
35569
|
const dbPattern = config.dbAccessPattern ?? "ctx.db.{table}";
|
|
34345
35570
|
const regexStr = dbPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace("\\{table\\}", "(\\w+)");
|
|
@@ -34744,8 +35969,8 @@ var init_server_dispatch = __esm({
|
|
|
34744
35969
|
|
|
34745
35970
|
// src/server.ts
|
|
34746
35971
|
var server_exports = {};
|
|
34747
|
-
import { readFileSync as
|
|
34748
|
-
import { resolve as resolve41, dirname as
|
|
35972
|
+
import { readFileSync as readFileSync54 } from "fs";
|
|
35973
|
+
import { resolve as resolve41, dirname as dirname26 } from "path";
|
|
34749
35974
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
34750
35975
|
function pruneMemoryOnStartup() {
|
|
34751
35976
|
try {
|
|
@@ -34787,10 +36012,10 @@ var init_server = __esm({
|
|
|
34787
36012
|
init_consolidation_config();
|
|
34788
36013
|
init_license();
|
|
34789
36014
|
init_server_dispatch();
|
|
34790
|
-
__dirname5 =
|
|
36015
|
+
__dirname5 = dirname26(fileURLToPath6(import.meta.url));
|
|
34791
36016
|
PKG_VERSION = (() => {
|
|
34792
36017
|
try {
|
|
34793
|
-
const pkg = JSON.parse(
|
|
36018
|
+
const pkg = JSON.parse(readFileSync54(resolve41(__dirname5, "..", "package.json"), "utf-8"));
|
|
34794
36019
|
return pkg.version ?? "0.0.0";
|
|
34795
36020
|
} catch {
|
|
34796
36021
|
return "0.0.0";
|
|
@@ -35076,7 +36301,7 @@ var config_upgrade_exports = {};
|
|
|
35076
36301
|
__export(config_upgrade_exports, {
|
|
35077
36302
|
runConfigUpgrade: () => runConfigUpgrade
|
|
35078
36303
|
});
|
|
35079
|
-
import { existsSync as
|
|
36304
|
+
import { existsSync as existsSync63, readFileSync as readFileSync55, writeFileSync as writeFileSync15, copyFileSync as copyFileSync3, unlinkSync as unlinkSync6 } from "fs";
|
|
35080
36305
|
import { resolve as resolve42 } from "path";
|
|
35081
36306
|
import { parse as parseYaml6 } from "yaml";
|
|
35082
36307
|
async function runConfigUpgrade(opts = {}) {
|
|
@@ -35088,14 +36313,14 @@ async function runConfigUpgrade(opts = {}) {
|
|
|
35088
36313
|
const err = opts.silent ? () => {
|
|
35089
36314
|
} : (s) => process.stderr.write(s);
|
|
35090
36315
|
if (opts.rollback) {
|
|
35091
|
-
if (!
|
|
36316
|
+
if (!existsSync63(bakPath)) {
|
|
35092
36317
|
const message = `No backup found at ${bakPath}`;
|
|
35093
36318
|
err(message + "\n");
|
|
35094
36319
|
return { exitCode: 1, action: "none", message };
|
|
35095
36320
|
}
|
|
35096
36321
|
try {
|
|
35097
36322
|
copyFileSync3(bakPath, configPath);
|
|
35098
|
-
|
|
36323
|
+
unlinkSync6(bakPath);
|
|
35099
36324
|
log("Config restored from backup.\n");
|
|
35100
36325
|
return { exitCode: 0, action: "rolled-back" };
|
|
35101
36326
|
} catch (e2) {
|
|
@@ -35104,14 +36329,14 @@ async function runConfigUpgrade(opts = {}) {
|
|
|
35104
36329
|
return { exitCode: 2, action: "none", message };
|
|
35105
36330
|
}
|
|
35106
36331
|
}
|
|
35107
|
-
if (!
|
|
36332
|
+
if (!existsSync63(configPath)) {
|
|
35108
36333
|
const message = "massu.config.yaml not found. Run: npx massu init";
|
|
35109
36334
|
err(message + "\n");
|
|
35110
36335
|
return { exitCode: 1, action: "none", message };
|
|
35111
36336
|
}
|
|
35112
36337
|
let existing;
|
|
35113
36338
|
try {
|
|
35114
|
-
const content =
|
|
36339
|
+
const content = readFileSync55(configPath, "utf-8");
|
|
35115
36340
|
const parsed = parseYaml6(content);
|
|
35116
36341
|
if (!parsed || typeof parsed !== "object") {
|
|
35117
36342
|
throw new Error("config is not a YAML object");
|
|
@@ -35134,8 +36359,8 @@ async function runConfigUpgrade(opts = {}) {
|
|
|
35134
36359
|
fingerprint: computeFingerprint(detection)
|
|
35135
36360
|
};
|
|
35136
36361
|
try {
|
|
35137
|
-
const original =
|
|
35138
|
-
|
|
36362
|
+
const original = readFileSync55(configPath, "utf-8");
|
|
36363
|
+
writeFileSync15(bakPath, original, "utf-8");
|
|
35139
36364
|
} catch (e2) {
|
|
35140
36365
|
const message = `Failed to write backup: ${e2 instanceof Error ? e2.message : String(e2)}`;
|
|
35141
36366
|
err(message + "\n");
|
|
@@ -35168,7 +36393,7 @@ var config_check_drift_exports = {};
|
|
|
35168
36393
|
__export(config_check_drift_exports, {
|
|
35169
36394
|
runConfigCheckDrift: () => runConfigCheckDrift
|
|
35170
36395
|
});
|
|
35171
|
-
import { existsSync as
|
|
36396
|
+
import { existsSync as existsSync64, readFileSync as readFileSync56 } from "fs";
|
|
35172
36397
|
import { resolve as resolve43 } from "path";
|
|
35173
36398
|
import { parse as parseYaml7 } from "yaml";
|
|
35174
36399
|
function renderChanges(changes) {
|
|
@@ -35182,7 +36407,7 @@ async function runConfigCheckDrift(opts = {}) {
|
|
|
35182
36407
|
} : (s) => process.stdout.write(s);
|
|
35183
36408
|
const err = opts.silent ? () => {
|
|
35184
36409
|
} : (s) => process.stderr.write(s);
|
|
35185
|
-
if (!
|
|
36410
|
+
if (!existsSync64(configPath)) {
|
|
35186
36411
|
const message = "massu.config.yaml not found. Run: npx massu init";
|
|
35187
36412
|
err(message + "\n");
|
|
35188
36413
|
return {
|
|
@@ -35196,7 +36421,7 @@ async function runConfigCheckDrift(opts = {}) {
|
|
|
35196
36421
|
}
|
|
35197
36422
|
let config;
|
|
35198
36423
|
try {
|
|
35199
|
-
const content =
|
|
36424
|
+
const content = readFileSync56(configPath, "utf-8");
|
|
35200
36425
|
const parsed = parseYaml7(content);
|
|
35201
36426
|
if (!parsed || typeof parsed !== "object") {
|
|
35202
36427
|
throw new Error("config is not a YAML object");
|
|
@@ -35263,11 +36488,11 @@ var init_config_check_drift = __esm({
|
|
|
35263
36488
|
});
|
|
35264
36489
|
|
|
35265
36490
|
// src/cli.ts
|
|
35266
|
-
import { readFileSync as
|
|
35267
|
-
import { resolve as resolve44, dirname as
|
|
36491
|
+
import { readFileSync as readFileSync57 } from "fs";
|
|
36492
|
+
import { resolve as resolve44, dirname as dirname27 } from "path";
|
|
35268
36493
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
35269
36494
|
var __filename5 = fileURLToPath7(import.meta.url);
|
|
35270
|
-
var __dirname6 =
|
|
36495
|
+
var __dirname6 = dirname27(__filename5);
|
|
35271
36496
|
var args = process.argv.slice(2);
|
|
35272
36497
|
var subcommand = args[0];
|
|
35273
36498
|
async function main() {
|
|
@@ -35545,7 +36770,7 @@ Examples:
|
|
|
35545
36770
|
}
|
|
35546
36771
|
function printVersion() {
|
|
35547
36772
|
try {
|
|
35548
|
-
const pkg = JSON.parse(
|
|
36773
|
+
const pkg = JSON.parse(readFileSync57(resolve44(__dirname6, "../package.json"), "utf-8"));
|
|
35549
36774
|
console.log(`massu v${pkg.version}`);
|
|
35550
36775
|
} catch {
|
|
35551
36776
|
console.log("massu v0.1.0");
|