@massu/core 1.13.0 → 1.14.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/dist/cli.js CHANGED
@@ -622,8 +622,14 @@ __export(memory_db_exports, {
622
622
  createSession: () => createSession,
623
623
  deduplicateFailedAttempt: () => deduplicateFailedAttempt,
624
624
  dequeuePendingSync: () => dequeuePendingSync,
625
+ drainRulePromotionEvents: () => drainRulePromotionEvents,
626
+ drainTeamPromotions: () => drainTeamPromotions,
627
+ drainTeamRevocations: () => drainTeamRevocations,
625
628
  endSession: () => endSession,
629
+ enqueueRulePromotionEvent: () => enqueueRulePromotionEvent,
626
630
  enqueueSyncPayload: () => enqueueSyncPayload,
631
+ enqueueTeamPromotion: () => enqueueTeamPromotion,
632
+ enqueueTeamRevocation: () => enqueueTeamRevocation,
627
633
  getConversationTurns: () => getConversationTurns,
628
634
  getCrossTaskProgress: () => getCrossTaskProgress,
629
635
  getDecisionsAbout: () => getDecisionsAbout,
@@ -631,8 +637,10 @@ __export(memory_db_exports, {
631
637
  getFailureClasses: () => getFailureClasses,
632
638
  getLastProcessedLine: () => getLastProcessedLine,
633
639
  getMemoryDb: () => getMemoryDb,
640
+ getMemoryMeta: () => getMemoryMeta,
634
641
  getObservabilityDbSize: () => getObservabilityDbSize,
635
642
  getRecentObservations: () => getRecentObservations,
643
+ getRecurrenceCountForPromptHash: () => getRecurrenceCountForPromptHash,
636
644
  getSessionStats: () => getSessionStats,
637
645
  getSessionSummaries: () => getSessionSummaries,
638
646
  getSessionTimeline: () => getSessionTimeline,
@@ -645,12 +653,14 @@ __export(memory_db_exports, {
645
653
  pruneOldConversationTurns: () => pruneOldConversationTurns,
646
654
  pruneOldObservations: () => pruneOldObservations,
647
655
  pruneToolCostEvents: () => pruneToolCostEvents,
656
+ recordTelemetry: () => recordTelemetry,
648
657
  removePendingSync: () => removePendingSync,
649
658
  sanitizeFts5Query: () => sanitizeFts5Query,
650
659
  scoreFailureClasses: () => scoreFailureClasses,
651
660
  searchConversationTurns: () => searchConversationTurns,
652
661
  searchObservations: () => searchObservations,
653
- setLastProcessedLine: () => setLastProcessedLine
662
+ setLastProcessedLine: () => setLastProcessedLine,
663
+ setMemoryMeta: () => setMemoryMeta
654
664
  });
655
665
  import Database from "better-sqlite3";
656
666
  import { dirname as dirname2, basename } from "path";
@@ -1156,6 +1166,45 @@ function initMemorySchema(db) {
1156
1166
  CREATE INDEX IF NOT EXISTS idx_so_file ON shared_observations(file_path);
1157
1167
  CREATE INDEX IF NOT EXISTS idx_so_module ON shared_observations(module);
1158
1168
  `);
1169
+ db.exec(`
1170
+ CREATE TABLE IF NOT EXISTS team_promotion_outbound (
1171
+ prompt_hash TEXT PRIMARY KEY,
1172
+ destination TEXT NOT NULL,
1173
+ draft_text TEXT NOT NULL,
1174
+ score REAL,
1175
+ signals_json TEXT NOT NULL DEFAULT '[]',
1176
+ content_hash TEXT NOT NULL,
1177
+ -- PA3-004 (Phase 3 Stream A): hardened-destination publish carries the
1178
+ -- publisher's review attestation so the server CHECK (hardened rows need a
1179
+ -- review_attestation) is satisfiable. hardened=0 for the Phase-2 rows.
1180
+ hardened INTEGER NOT NULL DEFAULT 0,
1181
+ review_attestation_json TEXT,
1182
+ created_at TEXT DEFAULT (datetime('now'))
1183
+ );
1184
+ CREATE TABLE IF NOT EXISTS team_revocation_outbound (
1185
+ prompt_hash TEXT PRIMARY KEY,
1186
+ created_at TEXT DEFAULT (datetime('now'))
1187
+ );
1188
+ `);
1189
+ db.exec(`
1190
+ CREATE TABLE IF NOT EXISTS rule_promotion_events_outbound (
1191
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1192
+ prompt_hash TEXT NOT NULL,
1193
+ event_type TEXT NOT NULL,
1194
+ metadata_json TEXT NOT NULL DEFAULT '{}',
1195
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
1196
+ );
1197
+ CREATE INDEX IF NOT EXISTS idx_rpe_outbound_created ON rule_promotion_events_outbound(created_at);
1198
+ `);
1199
+ db.exec(`
1200
+ CREATE TABLE IF NOT EXISTS analytics_events (
1201
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1202
+ event_type TEXT NOT NULL,
1203
+ event_data TEXT NOT NULL DEFAULT '{}',
1204
+ created_at TEXT DEFAULT (datetime('now'))
1205
+ );
1206
+ CREATE INDEX IF NOT EXISTS idx_analytics_events_type ON analytics_events(event_type);
1207
+ `);
1159
1208
  db.exec(`
1160
1209
  CREATE TABLE IF NOT EXISTS knowledge_conflicts (
1161
1210
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -1236,6 +1285,13 @@ function initMemorySchema(db) {
1236
1285
  `ALTER TABLE license_cache ADD COLUMN signed_payload_json TEXT NOT NULL DEFAULT ''`
1237
1286
  );
1238
1287
  }
1288
+ const outboundCols = db.prepare(`PRAGMA table_info(team_promotion_outbound)`).all();
1289
+ if (!outboundCols.some((c2) => c2.name === "hardened")) {
1290
+ db.exec(`ALTER TABLE team_promotion_outbound ADD COLUMN hardened INTEGER NOT NULL DEFAULT 0`);
1291
+ }
1292
+ if (!outboundCols.some((c2) => c2.name === "review_attestation_json")) {
1293
+ db.exec(`ALTER TABLE team_promotion_outbound ADD COLUMN review_attestation_json TEXT`);
1294
+ }
1239
1295
  db.exec(`
1240
1296
  CREATE TABLE IF NOT EXISTS failure_classes (
1241
1297
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -1259,6 +1315,136 @@ function initMemorySchema(db) {
1259
1315
  function enqueueSyncPayload(db, payload) {
1260
1316
  db.prepare("INSERT INTO pending_sync (payload) VALUES (?)").run(payload);
1261
1317
  }
1318
+ function getMemoryMeta(db, key) {
1319
+ const row = db.prepare("SELECT value FROM memory_meta WHERE key = ?").get(key);
1320
+ return row ? row.value : null;
1321
+ }
1322
+ function setMemoryMeta(db, key, value) {
1323
+ db.prepare("INSERT OR REPLACE INTO memory_meta (key, value) VALUES (?, ?)").run(key, value);
1324
+ }
1325
+ function enqueueTeamPromotion(db, promo) {
1326
+ db.prepare(`
1327
+ INSERT OR REPLACE INTO team_promotion_outbound
1328
+ (prompt_hash, destination, draft_text, score, signals_json, content_hash, hardened, review_attestation_json, created_at)
1329
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
1330
+ `).run(
1331
+ promo.prompt_hash,
1332
+ promo.destination,
1333
+ promo.draft_text.slice(0, MAX_DRAFT_TEXT_LEN),
1334
+ promo.score ?? null,
1335
+ JSON.stringify(promo.signals ?? []),
1336
+ promo.content_hash,
1337
+ promo.hardened ? 1 : 0,
1338
+ promo.review_attestation !== void 0 ? JSON.stringify(promo.review_attestation) : null
1339
+ );
1340
+ }
1341
+ function drainTeamPromotions(db) {
1342
+ const rows = db.prepare(`
1343
+ SELECT prompt_hash, destination, draft_text, score, signals_json, content_hash, hardened, review_attestation_json
1344
+ FROM team_promotion_outbound ORDER BY created_at ASC LIMIT 1000
1345
+ `).all();
1346
+ if (rows.length === 0) return [];
1347
+ db.prepare("DELETE FROM team_promotion_outbound").run();
1348
+ return rows.map((r2) => ({
1349
+ prompt_hash: r2.prompt_hash,
1350
+ destination: r2.destination,
1351
+ draft_text: r2.draft_text,
1352
+ score: r2.score ?? void 0,
1353
+ signals: safeJsonArray(r2.signals_json),
1354
+ content_hash: r2.content_hash,
1355
+ hardened: r2.hardened === 1,
1356
+ review_attestation: r2.review_attestation_json ? safeJsonParse(r2.review_attestation_json) : void 0
1357
+ }));
1358
+ }
1359
+ function enqueueTeamRevocation(db, promptHash) {
1360
+ db.prepare(`
1361
+ INSERT OR REPLACE INTO team_revocation_outbound (prompt_hash, created_at)
1362
+ VALUES (?, datetime('now'))
1363
+ `).run(promptHash);
1364
+ }
1365
+ function drainTeamRevocations(db) {
1366
+ const rows = db.prepare(
1367
+ `SELECT prompt_hash FROM team_revocation_outbound ORDER BY created_at ASC LIMIT 1000`
1368
+ ).all();
1369
+ if (rows.length === 0) return [];
1370
+ db.prepare("DELETE FROM team_revocation_outbound").run();
1371
+ return rows.map((r2) => r2.prompt_hash);
1372
+ }
1373
+ function getRecurrenceCountForPromptHash(db, promptHash) {
1374
+ try {
1375
+ const row = db.prepare(`
1376
+ SELECT json_extract(metadata, '$.recurrence_count') AS rc
1377
+ FROM audit_log
1378
+ WHERE event_type = 'rule_promoted'
1379
+ AND json_extract(metadata, '$.prompt_hash') = ?
1380
+ LIMIT 1
1381
+ `).get(promptHash);
1382
+ if (!row || row.rc === null || row.rc === void 0) return null;
1383
+ return Number(row.rc);
1384
+ } catch {
1385
+ return null;
1386
+ }
1387
+ }
1388
+ function enqueueRulePromotionEvent(db, ev) {
1389
+ try {
1390
+ db.prepare(`
1391
+ INSERT INTO rule_promotion_events_outbound
1392
+ (prompt_hash, event_type, metadata_json, created_at)
1393
+ VALUES (?, ?, ?, ?)
1394
+ `).run(
1395
+ ev.prompt_hash,
1396
+ ev.event_type,
1397
+ JSON.stringify(ev.metadata ?? {}),
1398
+ ev.created_at
1399
+ );
1400
+ db.prepare(`
1401
+ DELETE FROM rule_promotion_events_outbound
1402
+ WHERE id NOT IN (
1403
+ SELECT id FROM rule_promotion_events_outbound ORDER BY id DESC LIMIT ?
1404
+ )
1405
+ `).run(FUNNEL_EVENT_OUTBOX_CAP);
1406
+ } catch {
1407
+ }
1408
+ }
1409
+ function drainRulePromotionEvents(db) {
1410
+ const rows = db.prepare(`
1411
+ SELECT id, prompt_hash, event_type, metadata_json, created_at
1412
+ FROM rule_promotion_events_outbound ORDER BY id ASC LIMIT ?
1413
+ `).all(FUNNEL_EVENT_DRAIN_LIMIT);
1414
+ if (rows.length === 0) return [];
1415
+ const maxId = rows[rows.length - 1].id;
1416
+ db.prepare("DELETE FROM rule_promotion_events_outbound WHERE id <= ?").run(maxId);
1417
+ return rows.map((r2) => ({
1418
+ prompt_hash: r2.prompt_hash,
1419
+ event_type: r2.event_type,
1420
+ created_at: r2.created_at,
1421
+ metadata: safeJsonParse(r2.metadata_json) ?? {}
1422
+ }));
1423
+ }
1424
+ function recordTelemetry(db, eventType, data) {
1425
+ try {
1426
+ db.prepare(`
1427
+ INSERT INTO analytics_events (event_type, event_data, created_at)
1428
+ VALUES (?, ?, datetime('now'))
1429
+ `).run(eventType, JSON.stringify(data));
1430
+ } catch {
1431
+ }
1432
+ }
1433
+ function safeJsonArray(json) {
1434
+ try {
1435
+ const v3 = JSON.parse(json);
1436
+ return Array.isArray(v3) ? v3 : [];
1437
+ } catch {
1438
+ return [];
1439
+ }
1440
+ }
1441
+ function safeJsonParse(json) {
1442
+ try {
1443
+ return JSON.parse(json);
1444
+ } catch {
1445
+ return void 0;
1446
+ }
1447
+ }
1262
1448
  function dequeuePendingSync(db, limit = 10) {
1263
1449
  const stale = db.prepare(
1264
1450
  "SELECT id, retry_count, last_error FROM pending_sync WHERE retry_count >= 10 LIMIT 10000"
@@ -1809,12 +1995,15 @@ function scoreFailureClasses(db, matchText, filePath, promptContext, weights) {
1809
1995
  }
1810
1996
  return bestMatch;
1811
1997
  }
1812
- var TOOL_COST_EVENTS_RETENTION_DAYS;
1998
+ var TOOL_COST_EVENTS_RETENTION_DAYS, MAX_DRAFT_TEXT_LEN, FUNNEL_EVENT_DRAIN_LIMIT, FUNNEL_EVENT_OUTBOX_CAP;
1813
1999
  var init_memory_db = __esm({
1814
2000
  "src/memory-db.ts"() {
1815
2001
  "use strict";
1816
2002
  init_config();
1817
2003
  TOOL_COST_EVENTS_RETENTION_DAYS = 90;
2004
+ MAX_DRAFT_TEXT_LEN = 16384;
2005
+ FUNNEL_EVENT_DRAIN_LIMIT = 5e3;
2006
+ FUNNEL_EVENT_OUTBOX_CAP = 2e4;
1818
2007
  }
1819
2008
  });
1820
2009
 
@@ -14344,26 +14533,13 @@ var init_init = __esm({
14344
14533
  }
14345
14534
  });
14346
14535
 
14347
- // src/security/license-pubkey.generated.ts
14348
- var LICENSE_PUBKEY_ED25519, LICENSE_PUBKEY_FINGERPRINT_HEX, KNOWN_LICENSE_PUBKEY_FINGERPRINTS;
14349
- var init_license_pubkey_generated = __esm({
14350
- "src/security/license-pubkey.generated.ts"() {
14351
- "use strict";
14352
- LICENSE_PUBKEY_ED25519 = new Uint8Array([39, 136, 108, 146, 85, 233, 119, 252, 223, 226, 123, 155, 234, 168, 200, 150, 36, 249, 174, 6, 130, 146, 125, 196, 136, 224, 202, 150, 53, 228, 114, 15]);
14353
- LICENSE_PUBKEY_FINGERPRINT_HEX = "18a63d64fdec9e5a368fc45feaa49bed6ced815967e582bc7b8af534f22a9475";
14354
- KNOWN_LICENSE_PUBKEY_FINGERPRINTS = /* @__PURE__ */ new Set([
14355
- "18a63d64fdec9e5a368fc45feaa49bed6ced815967e582bc7b8af534f22a9475"
14356
- ]);
14357
- }
14358
- });
14359
-
14360
- // src/security/license-response-verifier.ts
14536
+ // src/security/ed25519-envelope-verifier.ts
14361
14537
  import { createPublicKey, verify as cryptoVerify } from "crypto";
14362
- function verifyLicenseResponse(payload) {
14363
- if (!KNOWN_LICENSE_PUBKEY_FINGERPRINTS.has(LICENSE_PUBKEY_FINGERPRINT_HEX)) {
14538
+ function verifyEd25519SignedEnvelope(key, payload) {
14539
+ if (!key.knownFingerprints.has(key.fingerprintHex)) {
14364
14540
  return {
14365
14541
  kind: "error",
14366
- reason: `Bundled license pubkey fingerprint ${LICENSE_PUBKEY_FINGERPRINT_HEX} is not in the trusted allowlist. Possible build-time tamper.`
14542
+ reason: `Bundled ${key.keyLabel} pubkey fingerprint ${key.fingerprintHex} is not in the trusted allowlist. Possible build-time tamper.`
14367
14543
  };
14368
14544
  }
14369
14545
  const sig = payload._signature;
@@ -14379,7 +14555,7 @@ function verifyLicenseResponse(payload) {
14379
14555
  if (!Array.isArray(payloadKeys) || payloadKeys.length === 0) {
14380
14556
  return { kind: "error", reason: "Missing _signature_payload_keys" };
14381
14557
  }
14382
- if (typeof sigPubkey === "string" && sigPubkey !== LICENSE_PUBKEY_FINGERPRINT_HEX) {
14558
+ if (typeof sigPubkey === "string" && sigPubkey !== key.fingerprintHex) {
14383
14559
  return { kind: "unknown_pubkey", got: sigPubkey };
14384
14560
  }
14385
14561
  const canonicalObj = {};
@@ -14389,21 +14565,7 @@ function verifyLicenseResponse(payload) {
14389
14565
  }
14390
14566
  const canonical = JSON.stringify(canonicalObj, [...payloadKeys].sort());
14391
14567
  try {
14392
- const spkiPrefix = Buffer.from([
14393
- 48,
14394
- 42,
14395
- 48,
14396
- 5,
14397
- 6,
14398
- 3,
14399
- 43,
14400
- 101,
14401
- 112,
14402
- 3,
14403
- 33,
14404
- 0
14405
- ]);
14406
- const der = Buffer.concat([spkiPrefix, Buffer.from(LICENSE_PUBKEY_ED25519)]);
14568
+ const der = Buffer.concat([SPKI_ED25519_PREFIX, Buffer.from(key.pubkeyBytes)]);
14407
14569
  const pubkey = createPublicKey({ key: der, format: "der", type: "spki" });
14408
14570
  const ok = cryptoVerify(
14409
14571
  null,
@@ -14419,12 +14581,59 @@ function verifyLicenseResponse(payload) {
14419
14581
  };
14420
14582
  }
14421
14583
  }
14584
+ var SPKI_ED25519_PREFIX;
14585
+ var init_ed25519_envelope_verifier = __esm({
14586
+ "src/security/ed25519-envelope-verifier.ts"() {
14587
+ "use strict";
14588
+ SPKI_ED25519_PREFIX = Buffer.from([
14589
+ 48,
14590
+ 42,
14591
+ 48,
14592
+ 5,
14593
+ 6,
14594
+ 3,
14595
+ 43,
14596
+ 101,
14597
+ 112,
14598
+ 3,
14599
+ 33,
14600
+ 0
14601
+ ]);
14602
+ }
14603
+ });
14604
+
14605
+ // src/security/license-pubkey.generated.ts
14606
+ var LICENSE_PUBKEY_ED25519, LICENSE_PUBKEY_FINGERPRINT_HEX, KNOWN_LICENSE_PUBKEY_FINGERPRINTS;
14607
+ var init_license_pubkey_generated = __esm({
14608
+ "src/security/license-pubkey.generated.ts"() {
14609
+ "use strict";
14610
+ LICENSE_PUBKEY_ED25519 = new Uint8Array([39, 136, 108, 146, 85, 233, 119, 252, 223, 226, 123, 155, 234, 168, 200, 150, 36, 249, 174, 6, 130, 146, 125, 196, 136, 224, 202, 150, 53, 228, 114, 15]);
14611
+ LICENSE_PUBKEY_FINGERPRINT_HEX = "18a63d64fdec9e5a368fc45feaa49bed6ced815967e582bc7b8af534f22a9475";
14612
+ KNOWN_LICENSE_PUBKEY_FINGERPRINTS = /* @__PURE__ */ new Set([
14613
+ "18a63d64fdec9e5a368fc45feaa49bed6ced815967e582bc7b8af534f22a9475"
14614
+ ]);
14615
+ }
14616
+ });
14617
+
14618
+ // src/security/license-response-verifier.ts
14619
+ function verifyLicenseResponse(payload) {
14620
+ return verifyEd25519SignedEnvelope(
14621
+ {
14622
+ pubkeyBytes: LICENSE_PUBKEY_ED25519,
14623
+ fingerprintHex: LICENSE_PUBKEY_FINGERPRINT_HEX,
14624
+ knownFingerprints: KNOWN_LICENSE_PUBKEY_FINGERPRINTS,
14625
+ keyLabel: "license"
14626
+ },
14627
+ payload
14628
+ );
14629
+ }
14422
14630
  function isLicenseSignatureRequired() {
14423
14631
  return process.env.MASSU_REQUIRE_SIGNED_LICENSE === "true";
14424
14632
  }
14425
14633
  var init_license_response_verifier = __esm({
14426
14634
  "src/security/license-response-verifier.ts"() {
14427
14635
  "use strict";
14636
+ init_ed25519_envelope_verifier();
14428
14637
  init_license_pubkey_generated();
14429
14638
  }
14430
14639
  });
@@ -14540,7 +14749,7 @@ async function validateLicense(apiKey) {
14540
14749
  }
14541
14750
  if (cached && trusted) {
14542
14751
  const lastValidated = new Date(cached.last_validated);
14543
- const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3);
14752
+ const sevenDaysAgo = new Date(Date.now() - GRACE_PERIOD_MS);
14544
14753
  if (lastValidated > sevenDaysAgo) {
14545
14754
  return trusted;
14546
14755
  }
@@ -14591,7 +14800,8 @@ function readTrustedCache(cached) {
14591
14800
  const tier = verifiedPlan ? PLAN_TO_TIER_MAP[verifiedPlan] ?? "free" : verifiedTierField ?? "free";
14592
14801
  const validUntil = typeof parsed.validUntil === "string" ? parsed.validUntil : "";
14593
14802
  const features = Array.isArray(parsed.features) ? parsed.features : [];
14594
- return { tier, validUntil, features };
14803
+ const orgId = typeof parsed.orgId === "string" && parsed.orgId.length > 0 ? parsed.orgId : void 0;
14804
+ return { tier, validUntil, features, orgId };
14595
14805
  }
14596
14806
  async function getCurrentTier() {
14597
14807
  if (cachedTier && Date.now() - cachedTierTimestamp < IN_MEMORY_CACHE_TTL_MS) {
@@ -14609,6 +14819,64 @@ async function getCurrentTier() {
14609
14819
  cachedTierTimestamp = Date.now();
14610
14820
  return info.tier;
14611
14821
  }
14822
+ function getCachedTierReadOnly(memDb) {
14823
+ const config = getConfig();
14824
+ const apiKey = config.cloud?.apiKey;
14825
+ if (!apiKey) return "free";
14826
+ const ownsDb = !memDb;
14827
+ const db = memDb ?? getMemoryDb();
14828
+ try {
14829
+ const keyHash = createHash5("sha256").update(apiKey).digest("hex");
14830
+ const cached = db.prepare(
14831
+ "SELECT tier, valid_until, last_validated, features, signed_payload_json FROM license_cache WHERE api_key_hash = ?"
14832
+ ).get(keyHash);
14833
+ if (!cached) return "free";
14834
+ const trusted = readTrustedCache(cached);
14835
+ if (!trusted) return "free";
14836
+ const lastValidated = new Date(cached.last_validated);
14837
+ const sevenDaysAgo = new Date(Date.now() - GRACE_PERIOD_MS);
14838
+ if (!(lastValidated > sevenDaysAgo)) return "free";
14839
+ return trusted.tier;
14840
+ } catch {
14841
+ return "free";
14842
+ } finally {
14843
+ if (ownsDb) {
14844
+ try {
14845
+ db.close();
14846
+ } catch {
14847
+ }
14848
+ }
14849
+ }
14850
+ }
14851
+ function getCachedOrgId(memDb) {
14852
+ const config = getConfig();
14853
+ const apiKey = config.cloud?.apiKey;
14854
+ if (!apiKey) return null;
14855
+ const ownsDb = !memDb;
14856
+ const db = memDb ?? getMemoryDb();
14857
+ try {
14858
+ const keyHash = createHash5("sha256").update(apiKey).digest("hex");
14859
+ const cached = db.prepare(
14860
+ "SELECT tier, valid_until, last_validated, features, signed_payload_json FROM license_cache WHERE api_key_hash = ?"
14861
+ ).get(keyHash);
14862
+ if (!cached) return null;
14863
+ const trusted = readTrustedCache(cached);
14864
+ if (!trusted) return null;
14865
+ const lastValidated = new Date(cached.last_validated);
14866
+ const sevenDaysAgo = new Date(Date.now() - GRACE_PERIOD_MS);
14867
+ if (!(lastValidated > sevenDaysAgo)) return null;
14868
+ return trusted.orgId ?? null;
14869
+ } catch {
14870
+ return null;
14871
+ } finally {
14872
+ if (ownsDb) {
14873
+ try {
14874
+ db.close();
14875
+ } catch {
14876
+ }
14877
+ }
14878
+ }
14879
+ }
14612
14880
  async function getLicenseInfo() {
14613
14881
  if (!cachedTier || Date.now() - cachedTierTimestamp >= IN_MEMORY_CACHE_TTL_MS) {
14614
14882
  await getCurrentTier();
@@ -14675,7 +14943,7 @@ async function handleLicenseToolCall(name, _args, _memDb) {
14675
14943
  }
14676
14944
  return { content: [{ type: "text", text: `Unknown license tool: ${name}` }] };
14677
14945
  }
14678
- var _warnedLicenseSig, TIER_LEVELS, TOOL_TIER_MAP, PLAN_TO_TIER_MAP, TIER_LABELS, cachedTier, cachedTierTimestamp, IN_MEMORY_CACHE_TTL_MS;
14946
+ var _warnedLicenseSig, TIER_LEVELS, TOOL_TIER_MAP, PLAN_TO_TIER_MAP, TIER_LABELS, cachedTier, cachedTierTimestamp, IN_MEMORY_CACHE_TTL_MS, GRACE_PERIOD_MS;
14679
14947
  var init_license = __esm({
14680
14948
  "src/license.ts"() {
14681
14949
  "use strict";
@@ -14789,6 +15057,7 @@ var init_license = __esm({
14789
15057
  cachedTier = null;
14790
15058
  cachedTierTimestamp = 0;
14791
15059
  IN_MEMORY_CACHE_TTL_MS = 15 * 60 * 1e3;
15060
+ GRACE_PERIOD_MS = 7 * 24 * 60 * 60 * 1e3;
14792
15061
  }
14793
15062
  });
14794
15063
 
@@ -15307,26 +15576,12 @@ var __filename4, __dirname4, HOOK_NAME_TO_FILE;
15307
15576
  var init_hook_runner = __esm({
15308
15577
  "src/commands/hook-runner.ts"() {
15309
15578
  "use strict";
15579
+ init_hook_registry();
15310
15580
  __filename4 = fileURLToPath4(import.meta.url);
15311
15581
  __dirname4 = dirname8(__filename4);
15312
- HOOK_NAME_TO_FILE = {
15313
- "session-start": "session-start.js",
15314
- "session-end": "session-end.js",
15315
- "security-gate": "security-gate.js",
15316
- "pre-delete-check": "pre-delete-check.js",
15317
- "post-tool-use": "post-tool-use.js",
15318
- "post-edit-context": "post-edit-context.js",
15319
- "quality-event": "quality-event.js",
15320
- "cost-tracker": "cost-tracker.js",
15321
- "fix-detector": "fix-detector.js",
15322
- "classify-failure": "classify-failure.js",
15323
- "incident-pipeline": "incident-pipeline.js",
15324
- "rule-enforcement-pipeline": "rule-enforcement-pipeline.js",
15325
- "auto-learning-pipeline": "auto-learning-pipeline.js",
15326
- "pre-compact": "pre-compact.js",
15327
- "user-prompt": "user-prompt.js",
15328
- "intent-suggester": "intent-suggester.js"
15329
- };
15582
+ HOOK_NAME_TO_FILE = Object.freeze(
15583
+ Object.fromEntries(REGISTERED_HOOKS.map((name) => [name, `${name}.js`]))
15584
+ );
15330
15585
  }
15331
15586
  });
15332
15587
 
@@ -15457,6 +15712,111 @@ var init_permissions2 = __esm({
15457
15712
  }
15458
15713
  });
15459
15714
 
15715
+ // src/auto-learning-entitlement.ts
15716
+ function autoLearningUpgradeMessage(currentTier) {
15717
+ return `Auto-learning (rule-candidate detection + /massu-rule promotion) is a Pro feature. Your tier: ${currentTier.toUpperCase()}. Upgrade at https://massu.ai/pricing`;
15718
+ }
15719
+ function entitledForTeamSharedPromotion(tier) {
15720
+ return tierLevel(tier) >= tierLevel(TEAM_SHARED_PROMOTION_MIN_TIER);
15721
+ }
15722
+ var TEAM_SHARED_PROMOTION_MIN_TIER;
15723
+ var init_auto_learning_entitlement = __esm({
15724
+ "src/auto-learning-entitlement.ts"() {
15725
+ "use strict";
15726
+ init_license();
15727
+ TEAM_SHARED_PROMOTION_MIN_TIER = "team";
15728
+ }
15729
+ });
15730
+
15731
+ // src/commands/license.ts
15732
+ var license_exports = {};
15733
+ __export(license_exports, {
15734
+ handleLicenseSubcommand: () => handleLicenseSubcommand,
15735
+ printLicenseHelp: () => printLicenseHelp
15736
+ });
15737
+ function isValidTier(value) {
15738
+ return VALID_TIERS.includes(value);
15739
+ }
15740
+ async function handleLicenseSubcommand(args2) {
15741
+ const sub = args2[0];
15742
+ switch (sub) {
15743
+ case "check": {
15744
+ const minIdx = args2.indexOf("--min");
15745
+ if (minIdx === -1 || minIdx === args2.length - 1) {
15746
+ process.stderr.write(
15747
+ "Usage: massu license check --min <free|pro|team|enterprise>\n"
15748
+ );
15749
+ return { exitCode: 2 };
15750
+ }
15751
+ const minRaw = args2[minIdx + 1];
15752
+ if (!isValidTier(minRaw)) {
15753
+ process.stderr.write(
15754
+ `massu: invalid --min tier "${minRaw}" (expected one of: ${VALID_TIERS.join(", ")})
15755
+ `
15756
+ );
15757
+ return { exitCode: 2 };
15758
+ }
15759
+ const min = minRaw;
15760
+ let current;
15761
+ try {
15762
+ current = await getCurrentTier();
15763
+ } catch (err) {
15764
+ process.stderr.write(
15765
+ `massu: could not resolve license tier (${err instanceof Error ? err.message : String(err)})
15766
+ `
15767
+ );
15768
+ return { exitCode: 3 };
15769
+ }
15770
+ if (tierLevel(current) >= tierLevel(min)) {
15771
+ return { exitCode: 0 };
15772
+ }
15773
+ process.stderr.write(autoLearningUpgradeMessage(current) + "\n");
15774
+ return { exitCode: 3 };
15775
+ }
15776
+ case "--help":
15777
+ case "-h":
15778
+ case void 0: {
15779
+ printLicenseHelp();
15780
+ return { exitCode: 0 };
15781
+ }
15782
+ default: {
15783
+ process.stderr.write(`massu: unknown license subcommand: ${sub}
15784
+ `);
15785
+ printLicenseHelp();
15786
+ return { exitCode: 1 };
15787
+ }
15788
+ }
15789
+ }
15790
+ function printLicenseHelp() {
15791
+ process.stdout.write(`
15792
+ massu license <subcommand>
15793
+
15794
+ Subcommands:
15795
+ check --min <tier> Exit 0 if the current license tier is at or above <tier>,
15796
+ else exit 3 with a generic upgrade message on stderr.
15797
+ <tier> is one of: free | pro | team | enterprise.
15798
+ Fail-closed \u2014 any error resolving the tier exits non-zero.
15799
+
15800
+ Use as the first step of a gated command to hard-fail:
15801
+ npx massu license check --min pro || exit 1
15802
+
15803
+ Examples:
15804
+ npx massu license check --min pro
15805
+ npx massu license check --min team
15806
+
15807
+ Documentation: https://massu.ai/docs/reference/license-tiers
15808
+ `);
15809
+ }
15810
+ var VALID_TIERS;
15811
+ var init_license2 = __esm({
15812
+ "src/commands/license.ts"() {
15813
+ "use strict";
15814
+ init_license();
15815
+ init_auto_learning_entitlement();
15816
+ VALID_TIERS = ["free", "pro", "team", "enterprise"];
15817
+ }
15818
+ });
15819
+
15460
15820
  // src/changelog-generator.ts
15461
15821
  import { existsSync as existsSync14, readFileSync as readFileSync13, readdirSync as readdirSync12 } from "fs";
15462
15822
  import { resolve as resolve11 } from "path";
@@ -15696,12 +16056,397 @@ var init_changelog = __esm({
15696
16056
  }
15697
16057
  });
15698
16058
 
16059
+ // src/security/promotion-pubkey.generated.ts
16060
+ var PROMOTION_PUBKEY_ED25519, PROMOTION_PUBKEY_FINGERPRINT_HEX, KNOWN_PROMOTION_PUBKEY_FINGERPRINTS;
16061
+ var init_promotion_pubkey_generated = __esm({
16062
+ "src/security/promotion-pubkey.generated.ts"() {
16063
+ "use strict";
16064
+ PROMOTION_PUBKEY_ED25519 = new Uint8Array([107, 161, 33, 17, 189, 44, 193, 128, 252, 155, 188, 236, 100, 163, 23, 146, 219, 155, 216, 139, 134, 72, 211, 182, 151, 122, 209, 151, 135, 65, 167, 26]);
16065
+ PROMOTION_PUBKEY_FINGERPRINT_HEX = "b14e2a73e23c02891e976ec161d339da6c930266c0202828d3187a3bd6e5d83f";
16066
+ KNOWN_PROMOTION_PUBKEY_FINGERPRINTS = /* @__PURE__ */ new Set([
16067
+ "b14e2a73e23c02891e976ec161d339da6c930266c0202828d3187a3bd6e5d83f"
16068
+ ]);
16069
+ }
16070
+ });
16071
+
16072
+ // src/security/promotion-envelope-verifier.ts
16073
+ function verifyPromotionEnvelope(payload) {
16074
+ return verifyEd25519SignedEnvelope(
16075
+ {
16076
+ pubkeyBytes: PROMOTION_PUBKEY_ED25519,
16077
+ fingerprintHex: PROMOTION_PUBKEY_FINGERPRINT_HEX,
16078
+ knownFingerprints: KNOWN_PROMOTION_PUBKEY_FINGERPRINTS,
16079
+ keyLabel: "promotion"
16080
+ },
16081
+ payload
16082
+ );
16083
+ }
16084
+ var init_promotion_envelope_verifier = __esm({
16085
+ "src/security/promotion-envelope-verifier.ts"() {
16086
+ "use strict";
16087
+ init_ed25519_envelope_verifier();
16088
+ init_promotion_pubkey_generated();
16089
+ }
16090
+ });
16091
+
16092
+ // src/rule-candidate-hardened.ts
16093
+ var TEAM_HARDENED_SHAREABLE_DESTINATIONS;
16094
+ var init_rule_candidate_hardened = __esm({
16095
+ "src/rule-candidate-hardened.ts"() {
16096
+ "use strict";
16097
+ TEAM_HARDENED_SHAREABLE_DESTINATIONS = [
16098
+ "pattern-scanner",
16099
+ "custom-destination"
16100
+ ];
16101
+ }
16102
+ });
16103
+
16104
+ // src/rule-pack-schema.ts
16105
+ function isRuleDestination(value) {
16106
+ return typeof value === "string" && RULE_DESTINATIONS.includes(value);
16107
+ }
16108
+ function isExecutableDestination(destination) {
16109
+ return TEAM_HARDENED_SHAREABLE_DESTINATIONS.includes(destination);
16110
+ }
16111
+ function isNonEmptyString(value) {
16112
+ return typeof value === "string" && value.trim().length > 0;
16113
+ }
16114
+ function validateRulePackRule(rule) {
16115
+ const errors = [];
16116
+ if (typeof rule !== "object" || rule === null || Array.isArray(rule)) {
16117
+ return { valid: false, errors: ["rule must be a non-null object"], requiresHardened: false };
16118
+ }
16119
+ const r2 = rule;
16120
+ if (!isNonEmptyString(r2.title)) {
16121
+ errors.push("missing required field: title");
16122
+ }
16123
+ if (!isNonEmptyString(r2.description)) {
16124
+ errors.push("missing required field: description");
16125
+ }
16126
+ if (!isNonEmptyString(r2.severity)) {
16127
+ errors.push("missing required field: severity");
16128
+ }
16129
+ const destinationKnown = isRuleDestination(r2.destination);
16130
+ if (!destinationKnown) {
16131
+ errors.push(
16132
+ `invalid destination: ${JSON.stringify(r2.destination)} is not a known RuleDestination (one of ${RULE_DESTINATIONS.join(", ")})`
16133
+ );
16134
+ }
16135
+ if (destinationKnown) {
16136
+ const destination = r2.destination;
16137
+ const hasPattern = isNonEmptyString(r2.pattern);
16138
+ const hasCheck = isNonEmptyString(r2.check);
16139
+ const hasTextBody = isNonEmptyString(r2.description);
16140
+ switch (destination) {
16141
+ case "pattern-scanner":
16142
+ if (!hasPattern && !hasCheck) {
16143
+ errors.push(
16144
+ "inert rule: pattern-scanner destination requires a non-empty `pattern` (or a `check` command)"
16145
+ );
16146
+ }
16147
+ break;
16148
+ case "custom-destination":
16149
+ if (!hasPattern && !hasCheck) {
16150
+ errors.push(
16151
+ "inert rule: custom-destination requires a deterministic enforcement body (`pattern` or `check`)"
16152
+ );
16153
+ }
16154
+ break;
16155
+ case "corrections-md":
16156
+ case "claude-md-cr":
16157
+ if (!hasTextBody && !hasPattern && !hasCheck) {
16158
+ errors.push(
16159
+ `inert rule: ${destination} requires a non-empty text body (description)`
16160
+ );
16161
+ }
16162
+ break;
16163
+ }
16164
+ }
16165
+ const requiresHardened = destinationKnown && isExecutableDestination(r2.destination);
16166
+ return { valid: errors.length === 0, errors, requiresHardened };
16167
+ }
16168
+ var RULE_DESTINATIONS;
16169
+ var init_rule_pack_schema = __esm({
16170
+ "src/rule-pack-schema.ts"() {
16171
+ "use strict";
16172
+ init_rule_candidate_hardened();
16173
+ RULE_DESTINATIONS = [
16174
+ "corrections-md",
16175
+ "pattern-scanner",
16176
+ "claude-md-cr",
16177
+ "custom-destination"
16178
+ ];
16179
+ }
16180
+ });
16181
+
16182
+ // src/rule-pack-sync.ts
16183
+ import { existsSync as existsSync16, writeFileSync as writeFileSync3, mkdirSync as mkdirSync6 } from "fs";
16184
+ import { join as join11, dirname as dirname9 } from "path";
16185
+ async function pullInstalledPackRules(db, opts = {}) {
16186
+ const config = getConfig();
16187
+ const cloud = config.cloud;
16188
+ const projectRoot = opts.projectRoot ?? getProjectRoot();
16189
+ const tier = opts.tier ?? getCachedTierReadOnly(db);
16190
+ if (!entitledForTeamSharedPromotion(tier)) return { ...ZERO };
16191
+ const endpoint = opts.endpoint ?? cloud?.endpoint;
16192
+ const apiKey = opts.apiKey ?? cloud?.apiKey;
16193
+ if (!endpoint || !apiKey) return { ...ZERO };
16194
+ const ownOrgId = opts.orgId !== void 0 ? opts.orgId : getCachedOrgId(db);
16195
+ const fetchImpl = opts.fetchImpl ?? fetch;
16196
+ const timeoutMs = opts.timeoutMs ?? cloud?.requestTimeoutMs ?? DEFAULT_TIMEOUT_MS;
16197
+ const url = `${endpoint}/installed-rules${opts.updatesOnly ? "?updates_only=1" : ""}`;
16198
+ let envelope;
16199
+ try {
16200
+ const res = await fetchImpl(url, {
16201
+ method: "GET",
16202
+ headers: { Authorization: `Bearer ${apiKey}` },
16203
+ signal: AbortSignal.timeout(timeoutMs)
16204
+ });
16205
+ if (!res.ok) return { ...ZERO };
16206
+ envelope = await res.json();
16207
+ } catch {
16208
+ return { ...ZERO };
16209
+ }
16210
+ const verdict = verifyPromotionEnvelope(envelope);
16211
+ if (verdict.kind !== "valid") {
16212
+ emitDropTelemetry(db, "pack_rules_envelope_dropped", { reason: verdict.kind });
16213
+ return { ...ZERO, dropped_unverified: countUntrusted(envelope) };
16214
+ }
16215
+ const signedKeys = Array.isArray(envelope._signature_payload_keys) ? envelope._signature_payload_keys : [];
16216
+ if (!signedKeys.includes("orgId") || !signedKeys.includes("packs_json")) {
16217
+ emitDropTelemetry(db, "pack_rules_unsigned_field", {
16218
+ orgId_signed: signedKeys.includes("orgId"),
16219
+ packs_json_signed: signedKeys.includes("packs_json")
16220
+ });
16221
+ return { ...ZERO, dropped_unverified: countUntrusted(envelope) };
16222
+ }
16223
+ const signedOrgId = typeof envelope.orgId === "string" ? envelope.orgId : null;
16224
+ if (!signedOrgId || !ownOrgId || signedOrgId !== ownOrgId) {
16225
+ emitDropTelemetry(db, "pack_rules_org_mismatch", {
16226
+ signed_org_present: !!signedOrgId,
16227
+ own_org_present: !!ownOrgId
16228
+ });
16229
+ return { ...ZERO, dropped_unverified: countUntrusted(envelope) };
16230
+ }
16231
+ const packs = parsePacks(envelope.packs_json);
16232
+ const result = { ...ZERO };
16233
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
16234
+ for (const pack of packs) {
16235
+ if (!isValidInstalledPack(pack)) continue;
16236
+ for (const rawRule of pack.rules) {
16237
+ const validation = validateRulePackRule(rawRule);
16238
+ if (!validation.valid) {
16239
+ result.skipped += 1;
16240
+ continue;
16241
+ }
16242
+ const rule = rawRule;
16243
+ result.pulled += 1;
16244
+ const promptHash = packRulePromptHash(pack.slug, rule);
16245
+ const candidatePath = sidecarPath(projectRoot, promptHash);
16246
+ if (existsSync16(candidatePath) || alreadyApplied(db, promptHash)) {
16247
+ result.skipped += 1;
16248
+ continue;
16249
+ }
16250
+ materializeCandidate(projectRoot, candidatePath, promptHash, rule, pack, signedOrgId, nowIso);
16251
+ result.materialized += 1;
16252
+ }
16253
+ }
16254
+ return result;
16255
+ }
16256
+ function parsePacks(json) {
16257
+ if (typeof json !== "string") return [];
16258
+ try {
16259
+ const v3 = JSON.parse(json);
16260
+ return Array.isArray(v3) ? v3 : [];
16261
+ } catch {
16262
+ return [];
16263
+ }
16264
+ }
16265
+ function countUntrusted(envelope) {
16266
+ const packs = parsePacks(envelope.packs_json);
16267
+ let n = 0;
16268
+ for (const p19 of packs) {
16269
+ if (p19 && Array.isArray(p19.rules)) n += p19.rules.length;
16270
+ }
16271
+ return n > 0 ? n : 1;
16272
+ }
16273
+ function isValidInstalledPack(p19) {
16274
+ if (!p19 || typeof p19 !== "object") return false;
16275
+ const r2 = p19;
16276
+ return typeof r2.slug === "string" && r2.slug.length > 0 && Array.isArray(r2.rules);
16277
+ }
16278
+ function packRulePromptHash(slug, rule) {
16279
+ const { createHash: createHash10 } = __require("node:crypto");
16280
+ const body = `${slug}\0${rule.destination}\0${rule.title}\0${rule.pattern ?? rule.check ?? rule.description}`;
16281
+ return createHash10("sha256").update(body, "utf-8").digest("hex").slice(0, 16);
16282
+ }
16283
+ function sidecarPath(projectRoot, promptHash) {
16284
+ return join11(projectRoot, ".massu", "rule-candidates", `${promptHash}.json`);
16285
+ }
16286
+ function alreadyApplied(db, promptHash) {
16287
+ try {
16288
+ const row = db.prepare(
16289
+ `SELECT 1 FROM audit_log WHERE event_type = 'rule_promoted'
16290
+ AND json_extract(metadata, '$.prompt_hash') = ? LIMIT 1`
16291
+ ).get(promptHash);
16292
+ return !!row;
16293
+ } catch {
16294
+ return false;
16295
+ }
16296
+ }
16297
+ function materializeCandidate(projectRoot, candidatePath, promptHash, rule, pack, orgId, nowIso) {
16298
+ const promptText = `${rule.title}: ${rule.description}`.replace(/\n+/g, " ").slice(0, 200) || `pack rule ${promptHash}`;
16299
+ const draftText = rule.pattern ?? rule.check ?? rule.description;
16300
+ const hardened = isExecutableDestination(rule.destination);
16301
+ const sidecar = {
16302
+ // Standard RuleCandidatePayload fields (so `/massu-rule approve` → readCandidate
16303
+ // → validateCandidatePayload passes), synthesized from the pack rule.
16304
+ prompt: promptText,
16305
+ prompt_hash: promptHash,
16306
+ score: 0,
16307
+ signals: [],
16308
+ prior_turn_files: [],
16309
+ timestamp: nowIso,
16310
+ session_id: `pack:${pack.slug}`,
16311
+ // Provenance (P2-002/P2-003): the applier's team-origin / hardened apply-gate
16312
+ // keys on this. `origin:'pack'` rides the SAME gate as a team origin
16313
+ // (tier≥Team + signature_verified + shareable/hardened destination). A
16314
+ // hardened (executable-destination) materialization sets `hardened:true` so
16315
+ // the operator's `/massu-rule review` (render-only preview + a distinct
16316
+ // second-operator attestation) is required before `approve`.
16317
+ provenance: {
16318
+ origin: "pack",
16319
+ org_id: orgId,
16320
+ promoted_by: `pack:${pack.slug}`,
16321
+ promoted_at: nowIso,
16322
+ signature_verified: true,
16323
+ pack_slug: pack.slug,
16324
+ pack_version: pack.current_version,
16325
+ ...hardened ? { hardened: true } : {}
16326
+ },
16327
+ // Extra fields the `/massu-rule approve` flow reads to drive the apply (the
16328
+ // pack author already decided destination + body). validateCandidatePayload
16329
+ // ignores unknown keys.
16330
+ destination: rule.destination,
16331
+ draft_text: draftText
16332
+ };
16333
+ const dir = dirname9(candidatePath);
16334
+ if (!existsSync16(dir)) mkdirSync6(dir, { recursive: true });
16335
+ writeFileSync3(candidatePath, JSON.stringify(sidecar, null, 2), "utf-8");
16336
+ }
16337
+ function emitDropTelemetry(db, eventType, data) {
16338
+ recordTelemetry(db, eventType, data);
16339
+ process.stderr.write(
16340
+ `[massu] installed-pack rules pull: dropped envelope (${eventType}). A signed/org-matched response is required \u2014 see massu.ai for details.
16341
+ `
16342
+ );
16343
+ }
16344
+ var DEFAULT_TIMEOUT_MS, ZERO;
16345
+ var init_rule_pack_sync = __esm({
16346
+ "src/rule-pack-sync.ts"() {
16347
+ "use strict";
16348
+ init_config();
16349
+ init_license();
16350
+ init_auto_learning_entitlement();
16351
+ init_promotion_envelope_verifier();
16352
+ init_rule_pack_schema();
16353
+ init_memory_db();
16354
+ DEFAULT_TIMEOUT_MS = 2e3;
16355
+ ZERO = {
16356
+ pulled: 0,
16357
+ materialized: 0,
16358
+ skipped: 0,
16359
+ dropped_unverified: 0
16360
+ };
16361
+ }
16362
+ });
16363
+
16364
+ // src/commands/rule.ts
16365
+ var rule_exports = {};
16366
+ __export(rule_exports, {
16367
+ handleRuleSubcommand: () => handleRuleSubcommand
16368
+ });
16369
+ async function handleRuleSubcommand(args2) {
16370
+ const sub = args2[0];
16371
+ switch (sub) {
16372
+ case "record-shown": {
16373
+ const promptHash = args2[1];
16374
+ if (!promptHash || !PROMPT_HASH_RE.test(promptHash)) {
16375
+ process.stderr.write(
16376
+ "Usage: massu rule record-shown <prompt_hash> (16-hex)\n"
16377
+ );
16378
+ return { exitCode: 2 };
16379
+ }
16380
+ try {
16381
+ const db = getMemoryDb();
16382
+ try {
16383
+ if (entitledForTeamSharedPromotion(getCachedTierReadOnly(db))) {
16384
+ enqueueRulePromotionEvent(db, {
16385
+ prompt_hash: promptHash,
16386
+ event_type: "shown",
16387
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
16388
+ metadata: {}
16389
+ });
16390
+ }
16391
+ } finally {
16392
+ db.close();
16393
+ }
16394
+ } catch {
16395
+ }
16396
+ return { exitCode: 0 };
16397
+ }
16398
+ case "packs": {
16399
+ try {
16400
+ const db = getMemoryDb();
16401
+ try {
16402
+ const res = await pullInstalledPackRules(db);
16403
+ process.stdout.write(
16404
+ `pack rules: pulled=${res.pulled} materialized=${res.materialized} skipped=${res.skipped} dropped_unverified=${res.dropped_unverified}
16405
+ `
16406
+ );
16407
+ } finally {
16408
+ db.close();
16409
+ }
16410
+ } catch {
16411
+ process.stdout.write(
16412
+ "pack rules: pulled=0 materialized=0 skipped=0 dropped_unverified=0\n"
16413
+ );
16414
+ }
16415
+ return { exitCode: 0 };
16416
+ }
16417
+ case "--help":
16418
+ case "-h":
16419
+ case void 0: {
16420
+ process.stdout.write(
16421
+ "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"
16422
+ );
16423
+ return { exitCode: 0 };
16424
+ }
16425
+ default: {
16426
+ process.stderr.write(`massu: unknown rule subcommand: ${sub}
16427
+ `);
16428
+ return { exitCode: 2 };
16429
+ }
16430
+ }
16431
+ }
16432
+ var PROMPT_HASH_RE;
16433
+ var init_rule = __esm({
16434
+ "src/commands/rule.ts"() {
16435
+ "use strict";
16436
+ init_memory_db();
16437
+ init_license();
16438
+ init_auto_learning_entitlement();
16439
+ init_rule_pack_sync();
16440
+ PROMPT_HASH_RE = /^[0-9a-f]{16}$/;
16441
+ }
16442
+ });
16443
+
15699
16444
  // src/commands/show-template.ts
15700
16445
  var show_template_exports = {};
15701
16446
  __export(show_template_exports, {
15702
16447
  runShowTemplate: () => runShowTemplate
15703
16448
  });
15704
- import { existsSync as existsSync16, readFileSync as readFileSync15 } from "fs";
16449
+ import { existsSync as existsSync17, readFileSync as readFileSync15 } from "fs";
15705
16450
  import { resolve as resolve13 } from "path";
15706
16451
  function normalizeBaseName(input) {
15707
16452
  return input.endsWith(".md") ? input.slice(0, -".md".length) : input;
@@ -15731,7 +16476,7 @@ async function runShowTemplate(args2) {
15731
16476
  }
15732
16477
  const suffix = choice.kind === "hit" ? choice.suffix : "";
15733
16478
  const file = suffix === "" ? resolve13(sourceDir, `${baseName}.md`) : resolve13(sourceDir, `${baseName}${suffix}.md`);
15734
- if (!existsSync16(file)) {
16479
+ if (!existsSync17(file)) {
15735
16480
  process.stderr.write(`massu: resolved template "${file}" no longer exists
15736
16481
  `);
15737
16482
  process.exit(1);
@@ -15790,8 +16535,8 @@ var init_passthrough = __esm({
15790
16535
  });
15791
16536
 
15792
16537
  // src/lib/fileLock.ts
15793
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync16, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
15794
- import { dirname as dirname9 } from "path";
16538
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync16, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
16539
+ import { dirname as dirname10 } from "path";
15795
16540
  import * as lockfile from "proper-lockfile";
15796
16541
  function readLockHolderPid(lockPath) {
15797
16542
  try {
@@ -15814,7 +16559,7 @@ function busyWaitSync(ms) {
15814
16559
  Atomics.wait(view, 0, 0, ms);
15815
16560
  }
15816
16561
  function withFileLockSync(lockPath, fn, opts = {}) {
15817
- mkdirSync6(dirname9(lockPath), { recursive: true });
16562
+ mkdirSync7(dirname10(lockPath), { recursive: true });
15818
16563
  const staleMs = opts.staleMs ?? 3e4;
15819
16564
  const blockMs = opts.retries === 0 ? 0 : opts.blockMs ?? 3e4;
15820
16565
  const pollIntervalMs = opts.pollIntervalMs ?? 100;
@@ -15831,7 +16576,7 @@ function withFileLockSync(lockPath, fn, opts = {}) {
15831
16576
  realpath: false
15832
16577
  });
15833
16578
  try {
15834
- writeFileSync3(`${lockPath}.pid`, String(process.pid), "utf-8");
16579
+ writeFileSync4(`${lockPath}.pid`, String(process.pid), "utf-8");
15835
16580
  } catch {
15836
16581
  }
15837
16582
  break;
@@ -15920,7 +16665,7 @@ __export(config_refresh_exports, {
15920
16665
  mergeRefresh: () => mergeRefresh,
15921
16666
  runConfigRefresh: () => runConfigRefresh
15922
16667
  });
15923
- import { existsSync as existsSync17, readFileSync as readFileSync17, rmSync as rmSync4 } from "fs";
16668
+ import { existsSync as existsSync18, readFileSync as readFileSync17, rmSync as rmSync4 } from "fs";
15924
16669
  import { resolve as resolve15 } from "path";
15925
16670
  import { parse as parseYaml5 } from "yaml";
15926
16671
  function flatten(obj, prefix3 = "") {
@@ -16052,7 +16797,7 @@ async function runConfigRefresh(opts = {}) {
16052
16797
  const configPath = resolve15(cwd, "massu.config.yaml");
16053
16798
  const log = opts.silent ? () => {
16054
16799
  } : (s) => process.stdout.write(s);
16055
- if (!existsSync17(configPath)) {
16800
+ if (!existsSync18(configPath)) {
16056
16801
  const message = "massu.config.yaml not found. Run: npx massu init";
16057
16802
  if (!opts.silent) process.stderr.write(message + "\n");
16058
16803
  return { exitCode: 1, applied: false, dryRun: !!opts.dryRun, diff: [], message };
@@ -16133,7 +16878,7 @@ async function runConfigRefresh(opts = {}) {
16133
16878
  const stackResolved = installResult.totalInstalled > 0 || installResult.totalUpdated > 0;
16134
16879
  if (stackResolved) {
16135
16880
  const placeholderPath = resolve15(installResult.claudeDir, "commands", "_massu-needs-stack.md");
16136
- if (existsSync17(placeholderPath)) {
16881
+ if (existsSync18(placeholderPath)) {
16137
16882
  try {
16138
16883
  rmSync4(placeholderPath, { force: true });
16139
16884
  log("Removed _massu-needs-stack.md (stack now declared).\n");
@@ -16242,12 +16987,12 @@ var init_gitToplevel = __esm({
16242
16987
  });
16243
16988
 
16244
16989
  // src/watch/lockfile-detector.ts
16245
- import { existsSync as existsSync18, statSync as statSync8 } from "fs";
16990
+ import { existsSync as existsSync19, statSync as statSync8 } from "fs";
16246
16991
  import { resolve as resolve16 } from "path";
16247
16992
  function lockfileMidWrite(projectRoot, now = Date.now(), windowMs = LOCKFILE_WINDOW_MS) {
16248
16993
  for (const lf of KNOWN_LOCKFILES) {
16249
16994
  const p19 = resolve16(projectRoot, lf);
16250
- if (!existsSync18(p19)) continue;
16995
+ if (!existsSync19(p19)) continue;
16251
16996
  try {
16252
16997
  const stat = statSync8(p19);
16253
16998
  const delta = now - stat.mtimeMs;
@@ -16260,7 +17005,7 @@ function lockfileMidWrite(projectRoot, now = Date.now(), windowMs = LOCKFILE_WIN
16260
17005
  function gitMidOperation(projectRoot) {
16261
17006
  const sentinels = ["MERGE_HEAD", "REBASE_HEAD", "CHERRY_PICK_HEAD", "rebase-apply", "rebase-merge"];
16262
17007
  for (const s of sentinels) {
16263
- if (existsSync18(resolve16(projectRoot, ".git", s))) return true;
17008
+ if (existsSync19(resolve16(projectRoot, ".git", s))) return true;
16264
17009
  }
16265
17010
  return false;
16266
17011
  }
@@ -16457,8 +17202,8 @@ var init_paths = __esm({
16457
17202
  });
16458
17203
 
16459
17204
  // src/watch/state.ts
16460
- import { closeSync as closeSync3, existsSync as existsSync19, fsyncSync as fsyncSync3, mkdirSync as mkdirSync7, openSync as openSync3, readFileSync as readFileSync18, renameSync as renameSync4, rmSync as rmSync5, writeFileSync as writeFileSync4, writeSync as writeSync3 } from "fs";
16461
- import { dirname as dirname10, resolve as resolve17 } from "path";
17205
+ import { closeSync as closeSync3, existsSync as existsSync20, fsyncSync as fsyncSync3, mkdirSync as mkdirSync8, openSync as openSync3, readFileSync as readFileSync18, renameSync as renameSync4, rmSync as rmSync5, writeFileSync as writeFileSync5, writeSync as writeSync3 } from "fs";
17206
+ import { dirname as dirname11, resolve as resolve17 } from "path";
16462
17207
  function watchStatePath(projectRoot) {
16463
17208
  return resolve17(projectRoot, ".massu", "watch-state.json");
16464
17209
  }
@@ -16467,7 +17212,7 @@ function backupStatePath(projectRoot) {
16467
17212
  }
16468
17213
  function readState(projectRoot) {
16469
17214
  const path = watchStatePath(projectRoot);
16470
- if (!existsSync19(path)) return { ...DEFAULT_STATE };
17215
+ if (!existsSync20(path)) return { ...DEFAULT_STATE };
16471
17216
  const content = readFileSync18(path, "utf-8");
16472
17217
  let raw;
16473
17218
  try {
@@ -16510,14 +17255,14 @@ function readState(projectRoot) {
16510
17255
  }
16511
17256
  function archiveCorrupt(projectRoot, content) {
16512
17257
  const bak = backupStatePath(projectRoot);
16513
- mkdirSync7(dirname10(bak), { recursive: true });
16514
- writeFileSync4(bak, content, "utf-8");
17258
+ mkdirSync8(dirname11(bak), { recursive: true });
17259
+ writeFileSync5(bak, content, "utf-8");
16515
17260
  }
16516
17261
  function writeStateAtomic(projectRoot, state) {
16517
17262
  const path = watchStatePath(projectRoot);
16518
17263
  writeStateAtomicCounter = writeStateAtomicCounter + 1 >>> 0;
16519
17264
  const tmp = `${path}.${process.pid}.${writeStateAtomicCounter}.tmp`;
16520
- mkdirSync7(dirname10(path), { recursive: true });
17265
+ mkdirSync8(dirname11(path), { recursive: true });
16521
17266
  let renamed = false;
16522
17267
  try {
16523
17268
  const fd = openSync3(tmp, "w");
@@ -16531,7 +17276,7 @@ function writeStateAtomic(projectRoot, state) {
16531
17276
  renameSync4(tmp, path);
16532
17277
  renamed = true;
16533
17278
  } finally {
16534
- if (!renamed && existsSync19(tmp)) {
17279
+ if (!renamed && existsSync20(tmp)) {
16535
17280
  try {
16536
17281
  rmSync5(tmp, { force: true });
16537
17282
  } catch {
@@ -16811,8 +17556,8 @@ __export(watch_exports, {
16811
17556
  runWatch: () => runWatch
16812
17557
  });
16813
17558
  import { spawnSync as spawnSync3 } from "child_process";
16814
- import { basename as basename5, dirname as dirname11, resolve as resolve18 } from "path";
16815
- import { appendFileSync, existsSync as existsSync20, mkdirSync as mkdirSync8, readFileSync as readFileSync19 } from "fs";
17559
+ import { basename as basename5, dirname as dirname12, resolve as resolve18 } from "path";
17560
+ import { appendFileSync, existsSync as existsSync21, mkdirSync as mkdirSync9, readFileSync as readFileSync19 } from "fs";
16816
17561
  function parseFlags(args2) {
16817
17562
  const out = {
16818
17563
  foreground: false,
@@ -16838,7 +17583,7 @@ function parseFlags(args2) {
16838
17583
  function findClaudeBg() {
16839
17584
  const home = process.env.HOME ?? "";
16840
17585
  const fixed = home ? resolve18(home, ".claude", "bin", "claude-bg") : null;
16841
- if (fixed && existsSync20(fixed)) return fixed;
17586
+ if (fixed && existsSync21(fixed)) return fixed;
16842
17587
  const which = spawnSync3("which", ["claude-bg"], { encoding: "utf-8" });
16843
17588
  if (which.status === 0 && which.stdout) {
16844
17589
  const p19 = which.stdout.trim();
@@ -16899,7 +17644,7 @@ async function runWatch(args2) {
16899
17644
  }
16900
17645
  function runStatus(root) {
16901
17646
  const path = watchStatePath(root);
16902
- if (!existsSync20(path)) {
17647
+ if (!existsSync21(path)) {
16903
17648
  process.stdout.write("massu watch: not running (no state file)\n");
16904
17649
  return { exitCode: 0 };
16905
17650
  }
@@ -17057,14 +17802,14 @@ function refreshLogPath(projectRoot) {
17057
17802
  function appendRefreshLog(projectRoot, event) {
17058
17803
  const path = refreshLogPath(projectRoot);
17059
17804
  try {
17060
- mkdirSync8(dirname11(path), { recursive: true });
17805
+ mkdirSync9(dirname12(path), { recursive: true });
17061
17806
  appendFileSync(path, JSON.stringify(event) + "\n", "utf-8");
17062
17807
  } catch {
17063
17808
  }
17064
17809
  }
17065
17810
  function readRefreshLog(projectRoot, limit = 10, opts = {}) {
17066
17811
  const path = refreshLogPath(projectRoot);
17067
- if (!existsSync20(path)) return [];
17812
+ if (!existsSync21(path)) return [];
17068
17813
  const warn = opts.warn ?? ((s) => {
17069
17814
  process.stderr.write(s);
17070
17815
  });
@@ -17141,26 +17886,26 @@ var init_refresh_log = __esm({
17141
17886
  import {
17142
17887
  chmodSync as chmodSync3,
17143
17888
  closeSync as closeSync4,
17144
- existsSync as existsSync21,
17889
+ existsSync as existsSync22,
17145
17890
  fsyncSync as fsyncSync4,
17146
- mkdirSync as mkdirSync9,
17891
+ mkdirSync as mkdirSync10,
17147
17892
  openSync as openSync4,
17148
17893
  renameSync as renameSync5,
17149
17894
  rmSync as rmSync6,
17150
17895
  statSync as statSync9,
17151
17896
  writeSync as writeSync4
17152
17897
  } from "node:fs";
17153
- import { dirname as dirname12 } from "node:path";
17898
+ import { dirname as dirname13 } from "node:path";
17154
17899
  function atomicWrite(path, content, opts = {}) {
17155
17900
  const tmpPath = `${path}.tmp`;
17156
- const parentDir = dirname12(path);
17901
+ const parentDir = dirname13(path);
17157
17902
  try {
17158
- if (!existsSync21(parentDir)) {
17903
+ if (!existsSync22(parentDir)) {
17159
17904
  const mkdirOpts = { recursive: true };
17160
17905
  if (opts.ensureParentDirMode !== void 0) {
17161
17906
  mkdirOpts.mode = opts.ensureParentDirMode;
17162
17907
  }
17163
- mkdirSync9(parentDir, mkdirOpts);
17908
+ mkdirSync10(parentDir, mkdirOpts);
17164
17909
  }
17165
17910
  const buf = typeof content === "string" ? Buffer.from(content, "utf-8") : content;
17166
17911
  const openMode = opts.mode ?? 420;
@@ -17177,7 +17922,7 @@ function atomicWrite(path, content, opts = {}) {
17177
17922
  renameSync5(tmpPath, path);
17178
17923
  return { written: true };
17179
17924
  } catch (err) {
17180
- if (existsSync21(tmpPath)) {
17925
+ if (existsSync22(tmpPath)) {
17181
17926
  try {
17182
17927
  rmSync6(tmpPath, { force: true });
17183
17928
  } catch {
@@ -17404,7 +18149,7 @@ var init_adapter_verifier = __esm({
17404
18149
  // src/security/fetcher.ts
17405
18150
  async function fetchUrl(url, opts = {}) {
17406
18151
  const allowedHosts = opts.allowedHosts ?? ALLOWED_HOSTS;
17407
- const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
18152
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
17408
18153
  const maxBodyBytes = opts.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
17409
18154
  let parsed;
17410
18155
  try {
@@ -17478,11 +18223,11 @@ async function fetchUrl(url, opts = {}) {
17478
18223
  clearTimeout(timeoutHandle);
17479
18224
  }
17480
18225
  }
17481
- var DEFAULT_TIMEOUT_MS, DEFAULT_MAX_BODY_BYTES, ALLOWED_HOSTS, FetchAllowlistError, FetchTimeoutError, FetchBodySizeError;
18226
+ var DEFAULT_TIMEOUT_MS2, DEFAULT_MAX_BODY_BYTES, ALLOWED_HOSTS, FetchAllowlistError, FetchTimeoutError, FetchBodySizeError;
17482
18227
  var init_fetcher = __esm({
17483
18228
  "src/security/fetcher.ts"() {
17484
18229
  "use strict";
17485
- DEFAULT_TIMEOUT_MS = 1e4;
18230
+ DEFAULT_TIMEOUT_MS2 = 1e4;
17486
18231
  DEFAULT_MAX_BODY_BYTES = 10 * 1024 * 1024;
17487
18232
  ALLOWED_HOSTS = [
17488
18233
  "registry.massu.ai",
@@ -17520,7 +18265,7 @@ var init_fetcher = __esm({
17520
18265
  });
17521
18266
 
17522
18267
  // src/security/manifest-cache.ts
17523
- import { existsSync as existsSync22, readFileSync as readFileSync20, statSync as statSync10 } from "node:fs";
18268
+ import { existsSync as existsSync23, readFileSync as readFileSync20, statSync as statSync10 } from "node:fs";
17524
18269
  import { homedir as homedir5 } from "node:os";
17525
18270
  import { resolve as resolve19 } from "node:path";
17526
18271
  import { z as z4 } from "zod";
@@ -17532,7 +18277,7 @@ function defaultCachePaths() {
17532
18277
  };
17533
18278
  }
17534
18279
  function loadCachedManifest(paths = defaultCachePaths()) {
17535
- if (!existsSync22(paths.cachePath)) {
18280
+ if (!existsSync23(paths.cachePath)) {
17536
18281
  return { kind: "absent" };
17537
18282
  }
17538
18283
  let raw;
@@ -17726,7 +18471,7 @@ var init_adapter_origin = __esm({
17726
18471
  });
17727
18472
 
17728
18473
  // src/security/local-fingerprint.ts
17729
- import { existsSync as existsSync23, readFileSync as readFileSync21, lstatSync as lstatSync5 } from "node:fs";
18474
+ import { existsSync as existsSync24, readFileSync as readFileSync21, lstatSync as lstatSync5 } from "node:fs";
17730
18475
  import { homedir as homedir6 } from "node:os";
17731
18476
  import { resolve as resolve20, isAbsolute } from "node:path";
17732
18477
  import { createHash as createHash7 } from "node:crypto";
@@ -17755,7 +18500,7 @@ function computeLocalFingerprint(localPaths, projectRoot) {
17755
18500
  return createHash7("sha256").update(canonical).digest("hex");
17756
18501
  }
17757
18502
  function readFingerprintSentinel(path = FINGERPRINT_PATH) {
17758
- if (!existsSync23(path)) return null;
18503
+ if (!existsSync24(path)) return null;
17759
18504
  let raw;
17760
18505
  try {
17761
18506
  raw = JSON.parse(readFileSync21(path, "utf-8"));
@@ -17822,15 +18567,15 @@ var init_local_fingerprint = __esm({
17822
18567
  });
17823
18568
 
17824
18569
  // src/security/install-tracking.ts
17825
- import { readFileSync as readFileSync22, readdirSync as readdirSync13, lstatSync as lstatSync6, existsSync as existsSync24 } from "node:fs";
17826
- import { join as join11, relative as relative5, sep } from "node:path";
18570
+ import { readFileSync as readFileSync22, readdirSync as readdirSync13, lstatSync as lstatSync6, existsSync as existsSync25 } from "node:fs";
18571
+ import { join as join12, relative as relative5, sep } from "node:path";
17827
18572
  import { homedir as homedir7 } from "node:os";
17828
18573
  import { resolve as resolve21 } from "node:path";
17829
18574
  import { createHash as createHash8 } from "node:crypto";
17830
18575
  import { z as z6 } from "zod";
17831
18576
  function containsHiddenDirs(packageDir) {
17832
18577
  for (const hidden of EXCLUDED_DIR_NAMES) {
17833
- if (existsSync24(`${packageDir}/${hidden}`)) {
18578
+ if (existsSync25(`${packageDir}/${hidden}`)) {
17834
18579
  return hidden;
17835
18580
  }
17836
18581
  }
@@ -17847,7 +18592,7 @@ function sha256OfDir(dir, opts = {}) {
17847
18592
  return;
17848
18593
  }
17849
18594
  for (const entry of entries.sort()) {
17850
- const absPath = join11(currentDir, entry);
18595
+ const absPath = join12(currentDir, entry);
17851
18596
  let lst;
17852
18597
  try {
17853
18598
  lst = lstatSync6(absPath);
@@ -17885,7 +18630,7 @@ function sha256OfDir(dir, opts = {}) {
17885
18630
  return top.digest("hex");
17886
18631
  }
17887
18632
  function readInstalledManifest(path = INSTALLED_MANIFEST_PATH) {
17888
- if (!existsSync24(path)) return {};
18633
+ if (!existsSync25(path)) return {};
17889
18634
  let raw;
17890
18635
  try {
17891
18636
  raw = JSON.parse(readFileSync22(path, "utf-8"));
@@ -17927,8 +18672,8 @@ function removeInstalledManifestEntry(packageName, path = INSTALLED_MANIFEST_PAT
17927
18672
  }
17928
18673
  return { written: true };
17929
18674
  }
17930
- function verifyInstalledIntegrity(packageName, packageDir, sidecarPath = INSTALLED_MANIFEST_PATH) {
17931
- const installed = readInstalledManifest(sidecarPath);
18675
+ function verifyInstalledIntegrity(packageName, packageDir, sidecarPath2 = INSTALLED_MANIFEST_PATH) {
18676
+ const installed = readInstalledManifest(sidecarPath2);
17932
18677
  const entry = installed[packageName];
17933
18678
  if (!entry) {
17934
18679
  return {
@@ -17975,12 +18720,12 @@ var init_install_tracking = __esm({
17975
18720
  });
17976
18721
 
17977
18722
  // src/detect/adapters/discover.ts
17978
- import { existsSync as existsSync25, readdirSync as readdirSync14, readFileSync as readFileSync23, lstatSync as lstatSync7 } from "node:fs";
18723
+ import { existsSync as existsSync26, readdirSync as readdirSync14, readFileSync as readFileSync23, lstatSync as lstatSync7 } from "node:fs";
17979
18724
  import { resolve as resolve22, isAbsolute as isAbsolute2 } from "node:path";
17980
18725
  import { z as z7 } from "zod";
17981
18726
  function walkNodeModules(projectRoot, warnings) {
17982
18727
  const nodeModulesDir = resolve22(projectRoot, "node_modules");
17983
- if (!existsSync25(nodeModulesDir)) {
18728
+ if (!existsSync26(nodeModulesDir)) {
17984
18729
  return [];
17985
18730
  }
17986
18731
  const candidates = [];
@@ -18035,7 +18780,7 @@ function walkNodeModules(projectRoot, warnings) {
18035
18780
  }
18036
18781
  function tryReadAdapterPackage(packageDir, warnings) {
18037
18782
  const pkgJsonPath = resolve22(packageDir, "package.json");
18038
- if (!existsSync25(pkgJsonPath)) return null;
18783
+ if (!existsSync26(pkgJsonPath)) return null;
18039
18784
  let raw;
18040
18785
  try {
18041
18786
  raw = JSON.parse(readFileSync23(pkgJsonPath, "utf-8"));
@@ -18173,7 +18918,7 @@ function discoverAdapters(opts) {
18173
18918
  for (const localPath of opts.configLocalPaths) {
18174
18919
  if (seenIds.has(localPath)) continue;
18175
18920
  const absPath = isAbsolute2(localPath) ? localPath : resolve22(opts.projectRoot, localPath);
18176
- if (!existsSync25(absPath)) {
18921
+ if (!existsSync26(absPath)) {
18177
18922
  warnings.push(
18178
18923
  `local adapter file not found: ${localPath} (resolved to ${absPath}). Remove via: massu adapters remove-local ${localPath}`
18179
18924
  );
@@ -18255,7 +19000,7 @@ __export(adapters_exports, {
18255
19000
  runAdaptersResyncLocalFingerprint: () => runAdaptersResyncLocalFingerprint,
18256
19001
  runAdaptersSearch: () => runAdaptersSearch
18257
19002
  });
18258
- import { existsSync as existsSync26, readFileSync as readFileSync24 } from "node:fs";
19003
+ import { existsSync as existsSync27, readFileSync as readFileSync24 } from "node:fs";
18259
19004
  import { resolve as resolve23 } from "node:path";
18260
19005
  import { parseDocument } from "yaml";
18261
19006
  async function handleAdaptersSubcommand(args2) {
@@ -18524,7 +19269,7 @@ function mutateLocalArray(mutator, command) {
18524
19269
  return { exitCode: 2 };
18525
19270
  }
18526
19271
  const yamlPath = resolve23(projectRoot, "massu.config.yaml");
18527
- if (!existsSync26(yamlPath)) {
19272
+ if (!existsSync27(yamlPath)) {
18528
19273
  process.stderr.write(
18529
19274
  `${command}: massu.config.yaml not found at ${yamlPath}. Run \`massu init\` first.
18530
19275
  `
@@ -18624,7 +19369,7 @@ async function runAdaptersInstall(args2) {
18624
19369
  return { exitCode: 1 };
18625
19370
  }
18626
19371
  const packageDir = resolve23(projectRoot, "node_modules", ...packageName.split("/"));
18627
- if (!existsSync26(packageDir)) {
19372
+ if (!existsSync27(packageDir)) {
18628
19373
  process.stderr.write(
18629
19374
  `install: ${packageName} is not installed in node_modules. Run \`npm install ${packageName}\` first.
18630
19375
  `
@@ -18632,7 +19377,7 @@ async function runAdaptersInstall(args2) {
18632
19377
  return { exitCode: 1 };
18633
19378
  }
18634
19379
  const pkgJsonPath = resolve23(packageDir, "package.json");
18635
- if (!existsSync26(pkgJsonPath)) {
19380
+ if (!existsSync27(pkgJsonPath)) {
18636
19381
  process.stderr.write(`install: ${packageName} has no package.json at ${pkgJsonPath}
18637
19382
  `);
18638
19383
  return { exitCode: 1 };
@@ -18759,7 +19504,7 @@ async function runAdaptersResign(_args) {
18759
19504
  continue;
18760
19505
  }
18761
19506
  const packageDir = resolve23(projectRoot, "node_modules", ...name.split("/"));
18762
- if (!existsSync26(packageDir)) {
19507
+ if (!existsSync27(packageDir)) {
18763
19508
  removed++;
18764
19509
  warnings.push(`${name}@${entry.version}: not present in node_modules \u2014 REMOVED from sidecar`);
18765
19510
  removeInstalledManifestEntry(name);
@@ -18857,11 +19602,11 @@ var init_sql_table_names = __esm({
18857
19602
 
18858
19603
  // src/db.ts
18859
19604
  import Database2 from "better-sqlite3";
18860
- import { dirname as dirname13, join as join12 } from "path";
18861
- import { existsSync as existsSync27, mkdirSync as mkdirSync10, readdirSync as readdirSync15, statSync as statSync11 } from "fs";
19605
+ import { dirname as dirname14, join as join13 } from "path";
19606
+ import { existsSync as existsSync28, mkdirSync as mkdirSync11, readdirSync as readdirSync15, statSync as statSync11 } from "fs";
18862
19607
  function getCodeGraphDb() {
18863
19608
  const dbPath = getResolvedPaths().codegraphDbPath;
18864
- if (!existsSync27(dbPath)) {
19609
+ if (!existsSync28(dbPath)) {
18865
19610
  throw new CodegraphDbNotInitializedError(dbPath);
18866
19611
  }
18867
19612
  const db = new Database2(dbPath, { readonly: true });
@@ -18870,9 +19615,9 @@ function getCodeGraphDb() {
18870
19615
  }
18871
19616
  function getDataDb() {
18872
19617
  const dbPath = getResolvedPaths().dataDbPath;
18873
- const dir = dirname13(dbPath);
18874
- if (!existsSync27(dir)) {
18875
- mkdirSync10(dir, { recursive: true });
19618
+ const dir = dirname14(dbPath);
19619
+ if (!existsSync28(dir)) {
19620
+ mkdirSync11(dir, { recursive: true });
18876
19621
  }
18877
19622
  const db = new Database2(dbPath);
18878
19623
  db.pragma("journal_mode = WAL");
@@ -19144,7 +19889,7 @@ function isPythonDataStale(dataDb, pythonRoot) {
19144
19889
  try {
19145
19890
  const entries = readdirSync15(dir, { withFileTypes: true });
19146
19891
  for (const entry of entries) {
19147
- const fullPath = join12(dir, entry.name);
19892
+ const fullPath = join13(dir, entry.name);
19148
19893
  if (entry.isDirectory()) {
19149
19894
  if (["__pycache__", ".venv", "venv", "node_modules", ".mypy_cache", ".pytest_cache"].includes(entry.name)) continue;
19150
19895
  if (checkDir(fullPath)) return true;
@@ -19180,13 +19925,13 @@ var init_db = __esm({
19180
19925
 
19181
19926
  // src/knowledge-db.ts
19182
19927
  import Database3 from "better-sqlite3";
19183
- import { dirname as dirname14 } from "path";
19184
- import { existsSync as existsSync28, mkdirSync as mkdirSync11 } from "fs";
19928
+ import { dirname as dirname15 } from "path";
19929
+ import { existsSync as existsSync29, mkdirSync as mkdirSync12 } from "fs";
19185
19930
  function getKnowledgeDb() {
19186
19931
  const dbPath = getResolvedPaths().knowledgeDbPath;
19187
- const dir = dirname14(dbPath);
19188
- if (!existsSync28(dir)) {
19189
- mkdirSync11(dir, { recursive: true });
19932
+ const dir = dirname15(dbPath);
19933
+ if (!existsSync29(dir)) {
19934
+ mkdirSync12(dir, { recursive: true });
19190
19935
  }
19191
19936
  const db = new Database3(dbPath);
19192
19937
  db.pragma("journal_mode = WAL");
@@ -19433,8 +20178,8 @@ var init_rules = __esm({
19433
20178
  });
19434
20179
 
19435
20180
  // src/import-resolver.ts
19436
- import { readFileSync as readFileSync25, existsSync as existsSync29, statSync as statSync12 } from "fs";
19437
- import { resolve as resolve25, dirname as dirname15, join as join13 } from "path";
20181
+ import { readFileSync as readFileSync25, existsSync as existsSync30, statSync as statSync12 } from "fs";
20182
+ import { resolve as resolve25, dirname as dirname16, join as join14 } from "path";
19438
20183
  function parseImports(source) {
19439
20184
  const imports = [];
19440
20185
  const lines = source.split("\n");
@@ -19492,21 +20237,21 @@ function resolveImportPath(specifier, fromFile) {
19492
20237
  const paths = getResolvedPaths();
19493
20238
  basePath = resolve25(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
19494
20239
  } else {
19495
- basePath = resolve25(dirname15(fromFile), specifier);
20240
+ basePath = resolve25(dirname16(fromFile), specifier);
19496
20241
  }
19497
- if (existsSync29(basePath) && !isDirectory(basePath)) {
20242
+ if (existsSync30(basePath) && !isDirectory(basePath)) {
19498
20243
  return toRelative(basePath);
19499
20244
  }
19500
20245
  const resolvedPaths = getResolvedPaths();
19501
20246
  for (const ext of resolvedPaths.extensions) {
19502
20247
  const withExt = basePath + ext;
19503
- if (existsSync29(withExt)) {
20248
+ if (existsSync30(withExt)) {
19504
20249
  return toRelative(withExt);
19505
20250
  }
19506
20251
  }
19507
20252
  for (const indexFile of resolvedPaths.indexFiles) {
19508
- const indexPath = join13(basePath, indexFile);
19509
- if (existsSync29(indexPath)) {
20253
+ const indexPath = join14(basePath, indexFile);
20254
+ if (existsSync30(indexPath)) {
19510
20255
  return toRelative(indexPath);
19511
20256
  }
19512
20257
  }
@@ -19543,7 +20288,7 @@ function buildImportIndex(dataDb, codegraphDb) {
19543
20288
  let batch = [];
19544
20289
  for (const file of files) {
19545
20290
  const absPath = ensureWithinRoot(resolve25(projectRoot, file.path), projectRoot);
19546
- if (!existsSync29(absPath)) continue;
20291
+ if (!existsSync30(absPath)) continue;
19547
20292
  let source;
19548
20293
  try {
19549
20294
  source = readFileSync25(absPath, "utf-8");
@@ -19583,12 +20328,12 @@ var init_import_resolver = __esm({
19583
20328
  });
19584
20329
 
19585
20330
  // src/trpc-index.ts
19586
- import { readFileSync as readFileSync26, existsSync as existsSync30, readdirSync as readdirSync16 } from "fs";
19587
- import { resolve as resolve26, join as join14 } from "path";
20331
+ import { readFileSync as readFileSync26, existsSync as existsSync31, readdirSync as readdirSync16 } from "fs";
20332
+ import { resolve as resolve26, join as join15 } from "path";
19588
20333
  function parseRootRouter() {
19589
20334
  const paths = getResolvedPaths();
19590
20335
  const rootPath = paths.rootRouterPath;
19591
- if (!existsSync30(rootPath)) {
20336
+ if (!existsSync31(rootPath)) {
19592
20337
  throw new Error(`Root router not found at ${rootPath}`);
19593
20338
  }
19594
20339
  const source = readFileSync26(rootPath, "utf-8");
@@ -19603,12 +20348,12 @@ function parseRootRouter() {
19603
20348
  for (const ext of [".ts", ".tsx", ""]) {
19604
20349
  const candidate = fullPath + ext;
19605
20350
  const routersRelPath = getConfig().paths.routers ?? "src/server/api/routers";
19606
- if (existsSync30(candidate)) {
20351
+ if (existsSync31(candidate)) {
19607
20352
  filePath = routersRelPath + "/" + filePath + ext;
19608
20353
  break;
19609
20354
  }
19610
- const indexCandidate = join14(fullPath, "index.ts");
19611
- if (existsSync30(indexCandidate)) {
20355
+ const indexCandidate = join15(fullPath, "index.ts");
20356
+ if (existsSync31(indexCandidate)) {
19612
20357
  filePath = routersRelPath + "/" + filePath + "/index.ts";
19613
20358
  break;
19614
20359
  }
@@ -19628,7 +20373,7 @@ function parseRootRouter() {
19628
20373
  }
19629
20374
  function extractProcedures(routerFilePath) {
19630
20375
  const absPath = resolve26(getProjectRoot(), routerFilePath);
19631
- if (!existsSync30(absPath)) return [];
20376
+ if (!existsSync31(absPath)) return [];
19632
20377
  const source = readFileSync26(absPath, "utf-8");
19633
20378
  const procedures = [];
19634
20379
  const seen = /* @__PURE__ */ new Set();
@@ -19658,7 +20403,7 @@ function findUICallSites(routerKey, procedureName) {
19658
20403
  ];
19659
20404
  const searchPattern = `api.${routerKey}.${procedureName}`;
19660
20405
  for (const dir of searchDirs) {
19661
- if (!existsSync30(dir)) continue;
20406
+ if (!existsSync31(dir)) continue;
19662
20407
  searchDirectory(dir, searchPattern, callSites);
19663
20408
  }
19664
20409
  return callSites;
@@ -19666,7 +20411,7 @@ function findUICallSites(routerKey, procedureName) {
19666
20411
  function searchDirectory(dir, pattern, results) {
19667
20412
  const entries = readdirSync16(dir, { withFileTypes: true });
19668
20413
  for (const entry of entries) {
19669
- const fullPath = join14(dir, entry.name);
20414
+ const fullPath = join15(dir, entry.name);
19670
20415
  if (entry.isDirectory()) {
19671
20416
  if (entry.name === "node_modules" || entry.name === ".next") continue;
19672
20417
  searchDirectory(fullPath, pattern, results);
@@ -19736,7 +20481,7 @@ var init_trpc_index = __esm({
19736
20481
  });
19737
20482
 
19738
20483
  // src/page-deps.ts
19739
- import { readFileSync as readFileSync27, existsSync as existsSync31 } from "fs";
20484
+ import { readFileSync as readFileSync27, existsSync as existsSync32 } from "fs";
19740
20485
  import { resolve as resolve27 } from "path";
19741
20486
  function deriveRoute(pageFile) {
19742
20487
  let route = pageFile.replace(/^src\/app/, "").replace(/\/page\.tsx?$/, "").replace(/\/page\.jsx?$/, "");
@@ -19776,7 +20521,7 @@ function findRouterCalls(files) {
19776
20521
  const projectRoot = getProjectRoot();
19777
20522
  for (const file of files) {
19778
20523
  const absPath = ensureWithinRoot(resolve27(projectRoot, file), projectRoot);
19779
- if (!existsSync31(absPath)) continue;
20524
+ if (!existsSync32(absPath)) continue;
19780
20525
  try {
19781
20526
  const source = readFileSync27(absPath, "utf-8");
19782
20527
  const apiCallRegex = /api\.(\w+)\.\w+/g;
@@ -19797,7 +20542,7 @@ function findTablesFromRouters(routerNames, dataDb) {
19797
20542
  ).all(routerName);
19798
20543
  for (const proc of procs) {
19799
20544
  const absPath = ensureWithinRoot(resolve27(getProjectRoot(), proc.router_file), getProjectRoot());
19800
- if (!existsSync31(absPath)) continue;
20545
+ if (!existsSync32(absPath)) continue;
19801
20546
  try {
19802
20547
  const source = readFileSync27(absPath, "utf-8");
19803
20548
  const dbPattern = getConfig().dbAccessPattern ?? "ctx.db.{table}";
@@ -20071,11 +20816,11 @@ var init_domains = __esm({
20071
20816
  });
20072
20817
 
20073
20818
  // src/schema-mapper.ts
20074
- import { readFileSync as readFileSync28, existsSync as existsSync32, readdirSync as readdirSync17 } from "fs";
20075
- import { join as join15 } from "path";
20819
+ import { readFileSync as readFileSync28, existsSync as existsSync33, readdirSync as readdirSync17 } from "fs";
20820
+ import { join as join16 } from "path";
20076
20821
  function parsePrismaSchema() {
20077
20822
  const schemaPath = getResolvedPaths().prismaSchemaPath;
20078
- if (!existsSync32(schemaPath)) {
20823
+ if (!existsSync33(schemaPath)) {
20079
20824
  throw new Error(`Prisma schema not found at ${schemaPath}`);
20080
20825
  }
20081
20826
  const source = readFileSync28(schemaPath, "utf-8");
@@ -20136,14 +20881,14 @@ function toSnakeCase(str) {
20136
20881
  function findColumnUsageInRouters(tableName) {
20137
20882
  const usage = /* @__PURE__ */ new Map();
20138
20883
  const routersDir = getResolvedPaths().routersDir;
20139
- if (!existsSync32(routersDir)) return usage;
20884
+ if (!existsSync33(routersDir)) return usage;
20140
20885
  scanDirectory(routersDir, tableName, usage);
20141
20886
  return usage;
20142
20887
  }
20143
20888
  function scanDirectory(dir, tableName, usage) {
20144
20889
  const entries = readdirSync17(dir, { withFileTypes: true });
20145
20890
  for (const entry of entries) {
20146
- const fullPath = join15(dir, entry.name);
20891
+ const fullPath = join16(dir, entry.name);
20147
20892
  if (entry.isDirectory()) {
20148
20893
  scanDirectory(fullPath, tableName, usage);
20149
20894
  } else if (entry.name.endsWith(".ts")) {
@@ -20198,10 +20943,10 @@ function detectMismatches(models) {
20198
20943
  }
20199
20944
  function findFilesUsingColumn(dir, column, tableName) {
20200
20945
  const result = [];
20201
- if (!existsSync32(dir)) return result;
20946
+ if (!existsSync33(dir)) return result;
20202
20947
  const entries = readdirSync17(dir, { withFileTypes: true });
20203
20948
  for (const entry of entries) {
20204
- const fullPath = join15(dir, entry.name);
20949
+ const fullPath = join16(dir, entry.name);
20205
20950
  if (entry.isDirectory()) {
20206
20951
  result.push(...findFilesUsingColumn(fullPath, column, tableName));
20207
20952
  } else if (entry.name.endsWith(".ts")) {
@@ -20351,34 +21096,34 @@ var init_import_parser = __esm({
20351
21096
  });
20352
21097
 
20353
21098
  // src/python/import-resolver.ts
20354
- import { readFileSync as readFileSync29, existsSync as existsSync33, readdirSync as readdirSync18 } from "fs";
20355
- import { resolve as resolve29, join as join16, relative as relative6, dirname as dirname16 } from "path";
21099
+ import { readFileSync as readFileSync29, existsSync as existsSync34, readdirSync as readdirSync18 } from "fs";
21100
+ import { resolve as resolve29, join as join17, relative as relative6, dirname as dirname17 } from "path";
20356
21101
  function resolvePythonModulePath(module, fromFile, pythonRoot, level) {
20357
21102
  const projectRoot = getProjectRoot();
20358
21103
  if (level > 0) {
20359
- let baseDir = dirname16(resolve29(projectRoot, fromFile));
21104
+ let baseDir = dirname17(resolve29(projectRoot, fromFile));
20360
21105
  for (let i = 1; i < level; i++) {
20361
- baseDir = dirname16(baseDir);
21106
+ baseDir = dirname17(baseDir);
20362
21107
  }
20363
21108
  const modulePart = module.replace(/^\.+/, "");
20364
21109
  if (modulePart) {
20365
21110
  const parts2 = modulePart.split(".");
20366
- return tryResolvePythonPath(join16(baseDir, ...parts2), projectRoot);
21111
+ return tryResolvePythonPath(join17(baseDir, ...parts2), projectRoot);
20367
21112
  }
20368
21113
  return tryResolvePythonPath(baseDir, projectRoot);
20369
21114
  }
20370
21115
  const parts = module.split(".");
20371
- const candidate = join16(resolve29(projectRoot, pythonRoot), ...parts);
21116
+ const candidate = join17(resolve29(projectRoot, pythonRoot), ...parts);
20372
21117
  return tryResolvePythonPath(candidate, projectRoot);
20373
21118
  }
20374
21119
  function tryResolvePythonPath(basePath, projectRoot) {
20375
- if (existsSync33(basePath + ".py")) {
21120
+ if (existsSync34(basePath + ".py")) {
20376
21121
  return relative6(projectRoot, basePath + ".py");
20377
21122
  }
20378
- if (existsSync33(join16(basePath, "__init__.py"))) {
20379
- return relative6(projectRoot, join16(basePath, "__init__.py"));
21123
+ if (existsSync34(join17(basePath, "__init__.py"))) {
21124
+ return relative6(projectRoot, join17(basePath, "__init__.py"));
20380
21125
  }
20381
- if (basePath.endsWith(".py") && existsSync33(basePath)) {
21126
+ if (basePath.endsWith(".py") && existsSync34(basePath)) {
20382
21127
  return relative6(projectRoot, basePath);
20383
21128
  }
20384
21129
  return null;
@@ -20390,9 +21135,9 @@ function walkPythonFiles(dir, excludeDirs) {
20390
21135
  for (const entry of entries) {
20391
21136
  if (entry.isDirectory()) {
20392
21137
  if (excludeDirs.includes(entry.name)) continue;
20393
- files.push(...walkPythonFiles(join16(dir, entry.name), excludeDirs));
21138
+ files.push(...walkPythonFiles(join17(dir, entry.name), excludeDirs));
20394
21139
  } else if (entry.name.endsWith(".py")) {
20395
- files.push(join16(dir, entry.name));
21140
+ files.push(join17(dir, entry.name));
20396
21141
  }
20397
21142
  }
20398
21143
  } catch {
@@ -20686,7 +21431,7 @@ var init_route_parser = __esm({
20686
21431
 
20687
21432
  // src/python/route-indexer.ts
20688
21433
  import { readFileSync as readFileSync30, readdirSync as readdirSync19 } from "fs";
20689
- import { join as join17, relative as relative7 } from "path";
21434
+ import { join as join18, relative as relative7 } from "path";
20690
21435
  function walkPyFiles(dir, excludeDirs) {
20691
21436
  const files = [];
20692
21437
  try {
@@ -20694,9 +21439,9 @@ function walkPyFiles(dir, excludeDirs) {
20694
21439
  for (const entry of entries) {
20695
21440
  if (entry.isDirectory()) {
20696
21441
  if (excludeDirs.includes(entry.name)) continue;
20697
- files.push(...walkPyFiles(join17(dir, entry.name), excludeDirs));
21442
+ files.push(...walkPyFiles(join18(dir, entry.name), excludeDirs));
20698
21443
  } else if (entry.name.endsWith(".py")) {
20699
- files.push(join17(dir, entry.name));
21444
+ files.push(join18(dir, entry.name));
20700
21445
  }
20701
21446
  }
20702
21447
  } catch {
@@ -20705,7 +21450,7 @@ function walkPyFiles(dir, excludeDirs) {
20705
21450
  }
20706
21451
  function buildPythonRouteIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]) {
20707
21452
  const projectRoot = getProjectRoot();
20708
- const absRoot = join17(projectRoot, pythonRoot);
21453
+ const absRoot = join18(projectRoot, pythonRoot);
20709
21454
  dataDb.exec(`DELETE FROM ${t("py_routes")}`);
20710
21455
  dataDb.exec(`DELETE FROM ${t("py_route_callers")}`);
20711
21456
  const insertStmt = dataDb.prepare(
@@ -20931,7 +21676,7 @@ var init_model_parser = __esm({
20931
21676
 
20932
21677
  // src/python/model-indexer.ts
20933
21678
  import { readFileSync as readFileSync31, readdirSync as readdirSync20 } from "fs";
20934
- import { join as join18, relative as relative8 } from "path";
21679
+ import { join as join19, relative as relative8 } from "path";
20935
21680
  function walkPyFiles2(dir, excludeDirs) {
20936
21681
  const files = [];
20937
21682
  try {
@@ -20939,9 +21684,9 @@ function walkPyFiles2(dir, excludeDirs) {
20939
21684
  for (const entry of entries) {
20940
21685
  if (entry.isDirectory()) {
20941
21686
  if (excludeDirs.includes(entry.name)) continue;
20942
- files.push(...walkPyFiles2(join18(dir, entry.name), excludeDirs));
21687
+ files.push(...walkPyFiles2(join19(dir, entry.name), excludeDirs));
20943
21688
  } else if (entry.name.endsWith(".py")) {
20944
- files.push(join18(dir, entry.name));
21689
+ files.push(join19(dir, entry.name));
20945
21690
  }
20946
21691
  }
20947
21692
  } catch {
@@ -20950,7 +21695,7 @@ function walkPyFiles2(dir, excludeDirs) {
20950
21695
  }
20951
21696
  function buildPythonModelIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]) {
20952
21697
  const projectRoot = getProjectRoot();
20953
- const absRoot = join18(projectRoot, pythonRoot);
21698
+ const absRoot = join19(projectRoot, pythonRoot);
20954
21699
  dataDb.exec(`DELETE FROM ${t("py_models")}`);
20955
21700
  dataDb.exec(`DELETE FROM ${t("py_fk_edges")}`);
20956
21701
  const insertModel = dataDb.prepare(
@@ -21228,18 +21973,18 @@ var init_migration_parser = __esm({
21228
21973
 
21229
21974
  // src/python/migration-indexer.ts
21230
21975
  import { readFileSync as readFileSync32, readdirSync as readdirSync21 } from "fs";
21231
- import { join as join19, relative as relative9 } from "path";
21976
+ import { join as join20, relative as relative9 } from "path";
21232
21977
  function buildPythonMigrationIndex(dataDb, alembicDir) {
21233
21978
  const projectRoot = getProjectRoot();
21234
- const absDir = join19(projectRoot, alembicDir);
21979
+ const absDir = join20(projectRoot, alembicDir);
21235
21980
  dataDb.exec(`DELETE FROM ${t("py_migrations")}`);
21236
- const versionsDir = join19(absDir, "versions");
21981
+ const versionsDir = join20(absDir, "versions");
21237
21982
  let files = [];
21238
21983
  try {
21239
- files = readdirSync21(versionsDir).filter((f2) => f2.endsWith(".py")).map((f2) => join19(versionsDir, f2));
21984
+ files = readdirSync21(versionsDir).filter((f2) => f2.endsWith(".py")).map((f2) => join20(versionsDir, f2));
21240
21985
  } catch {
21241
21986
  try {
21242
- files = readdirSync21(absDir).filter((f2) => f2.endsWith(".py") && f2 !== "env.py").map((f2) => join19(absDir, f2));
21987
+ files = readdirSync21(absDir).filter((f2) => f2.endsWith(".py") && f2 !== "env.py").map((f2) => join20(absDir, f2));
21243
21988
  } catch {
21244
21989
  }
21245
21990
  }
@@ -21289,11 +22034,11 @@ var init_migration_indexer = __esm({
21289
22034
 
21290
22035
  // src/python/coupling-detector.ts
21291
22036
  import { readFileSync as readFileSync33, readdirSync as readdirSync22 } from "fs";
21292
- import { join as join20, relative as relative10 } from "path";
22037
+ import { join as join21, relative as relative10 } from "path";
21293
22038
  function buildPythonCouplingIndex(dataDb) {
21294
22039
  const projectRoot = getProjectRoot();
21295
22040
  const config = getConfig();
21296
- const srcDir = join20(projectRoot, config.paths.source);
22041
+ const srcDir = join21(projectRoot, config.paths.source);
21297
22042
  const routes = dataDb.prepare(`SELECT id, method, path FROM ${t("py_routes")}`).all();
21298
22043
  if (routes.length === 0) return 0;
21299
22044
  dataDb.exec(`DELETE FROM ${t("py_route_callers")}`);
@@ -21357,9 +22102,9 @@ function walkFrontendFiles(dir) {
21357
22102
  for (const entry of entries) {
21358
22103
  if (entry.isDirectory()) {
21359
22104
  if (exclude.includes(entry.name)) continue;
21360
- files.push(...walkFrontendFiles(join20(dir, entry.name)));
22105
+ files.push(...walkFrontendFiles(join21(dir, entry.name)));
21361
22106
  } else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
21362
- files.push(join20(dir, entry.name));
22107
+ files.push(join21(dir, entry.name));
21363
22108
  }
21364
22109
  }
21365
22110
  } catch {
@@ -21731,7 +22476,7 @@ var init_memory_tools = __esm({
21731
22476
  });
21732
22477
 
21733
22478
  // src/docs-tools.ts
21734
- import { readFileSync as readFileSync34, existsSync as existsSync34 } from "fs";
22479
+ import { readFileSync as readFileSync34, existsSync as existsSync35 } from "fs";
21735
22480
  import { resolve as resolve30, basename as basename6 } from "path";
21736
22481
  function p3(baseName) {
21737
22482
  return `${getConfig().toolPrefix}_${baseName}`;
@@ -21787,7 +22532,7 @@ function handleDocsToolCall(name, args2) {
21787
22532
  }
21788
22533
  function loadDocsMap() {
21789
22534
  const mapPath = getResolvedPaths().docsMapPath;
21790
- if (!existsSync34(mapPath)) {
22535
+ if (!existsSync35(mapPath)) {
21791
22536
  throw new Error(`docs-map.json not found at ${mapPath}`);
21792
22537
  }
21793
22538
  return JSON.parse(readFileSync34(mapPath, "utf-8"));
@@ -21861,9 +22606,9 @@ function extractFrontmatter(content) {
21861
22606
  function extractProcedureNames(routerPath) {
21862
22607
  const root = getProjectRoot();
21863
22608
  const absPath = ensureWithinRoot(resolve30(getResolvedPaths().srcDir, "..", routerPath), root);
21864
- if (!existsSync34(absPath)) {
22609
+ if (!existsSync35(absPath)) {
21865
22610
  const altPath = ensureWithinRoot(resolve30(getResolvedPaths().srcDir, "../server/api/routers", basename6(routerPath)), root);
21866
- if (!existsSync34(altPath)) return [];
22611
+ if (!existsSync35(altPath)) return [];
21867
22612
  return extractProcedureNamesFromContent(readFileSync34(altPath, "utf-8"));
21868
22613
  }
21869
22614
  return extractProcedureNamesFromContent(readFileSync34(absPath, "utf-8"));
@@ -21907,7 +22652,7 @@ function handleDocsAudit(args2) {
21907
22652
  const mapping = docsMap.mappings.find((m3) => m3.id === mappingId);
21908
22653
  if (!mapping) continue;
21909
22654
  const helpPagePath = ensureWithinRoot(resolve30(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
21910
- if (!existsSync34(helpPagePath)) {
22655
+ if (!existsSync35(helpPagePath)) {
21911
22656
  results.push({
21912
22657
  helpPage: mapping.helpPage,
21913
22658
  mappingId,
@@ -21960,7 +22705,7 @@ function handleDocsAudit(args2) {
21960
22705
  for (const [guideName, parentId] of Object.entries(docsMap.userGuideInheritance.examples)) {
21961
22706
  if (parentId === mappingId) {
21962
22707
  const guidePath = ensureWithinRoot(resolve30(getResolvedPaths().helpSitePath, `pages/user-guides/${guideName}/index.mdx`), getProjectRoot());
21963
- if (existsSync34(guidePath)) {
22708
+ if (existsSync35(guidePath)) {
21964
22709
  const guideContent = readFileSync34(guidePath, "utf-8");
21965
22710
  const guideFrontmatter = extractFrontmatter(guideContent);
21966
22711
  if (!guideFrontmatter?.lastVerified || status === "STALE") {
@@ -21995,7 +22740,7 @@ function handleDocsCoverage(args2) {
21995
22740
  const mappings = filterDomain ? docsMap.mappings.filter((m3) => m3.id === filterDomain) : docsMap.mappings;
21996
22741
  for (const mapping of mappings) {
21997
22742
  const helpPagePath = ensureWithinRoot(resolve30(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
21998
- const exists = existsSync34(helpPagePath);
22743
+ const exists = existsSync35(helpPagePath);
21999
22744
  let hasContent = false;
22000
22745
  let lineCount = 0;
22001
22746
  let lastVerified = null;
@@ -22342,7 +23087,7 @@ var init_observability_tools = __esm({
22342
23087
  });
22343
23088
 
22344
23089
  // src/sentinel-db.ts
22345
- import { existsSync as existsSync35 } from "fs";
23090
+ import { existsSync as existsSync36 } from "fs";
22346
23091
  import { resolve as resolve31 } from "path";
22347
23092
  function parsePortalScope(raw) {
22348
23093
  if (!raw) return [];
@@ -22580,22 +23325,22 @@ function validateFeatures(db, domainFilter) {
22580
23325
  const missingPages = [];
22581
23326
  for (const comp of components) {
22582
23327
  const absPath = resolve31(PROJECT_ROOT, comp.component_file);
22583
- if (!existsSync35(absPath)) {
23328
+ if (!existsSync36(absPath)) {
22584
23329
  missingComponents.push(comp.component_file);
22585
23330
  }
22586
23331
  }
22587
23332
  for (const proc of procedures) {
22588
23333
  const routerPath = resolve31(PROJECT_ROOT, `src/server/api/routers/${proc.router_name}.ts`);
22589
- if (!existsSync35(routerPath)) {
23334
+ if (!existsSync36(routerPath)) {
22590
23335
  missingProcedures.push({ router: proc.router_name, procedure: proc.procedure_name });
22591
23336
  }
22592
23337
  }
22593
23338
  for (const page of pages) {
22594
23339
  const routeToPath = page.page_route.replace(/^\/(portal-[^/]+\/)?/, "src/app/").replace(/\/$/, "") + "/page.tsx";
22595
23340
  const absPath = resolve31(PROJECT_ROOT, routeToPath);
22596
- if (page.page_route.startsWith("/") && !existsSync35(absPath)) {
23341
+ if (page.page_route.startsWith("/") && !existsSync36(absPath)) {
22597
23342
  const altPath = resolve31(PROJECT_ROOT, `src/app${page.page_route}/page.tsx`);
22598
- if (!existsSync35(altPath)) {
23343
+ if (!existsSync36(altPath)) {
22599
23344
  missingPages.push(page.page_route);
22600
23345
  }
22601
23346
  }
@@ -23121,8 +23866,8 @@ var init_sentinel_tools = __esm({
23121
23866
  });
23122
23867
 
23123
23868
  // src/sentinel-scanner.ts
23124
- import { readFileSync as readFileSync35, existsSync as existsSync36, readdirSync as readdirSync23, statSync as statSync13 } from "fs";
23125
- import { resolve as resolve32, join as join21, basename as basename7, dirname as dirname17, relative as relative11 } from "path";
23869
+ import { readFileSync as readFileSync35, existsSync as existsSync37, readdirSync as readdirSync23, statSync as statSync13 } from "fs";
23870
+ import { resolve as resolve32, join as join22, basename as basename7, dirname as dirname18, relative as relative11 } from "path";
23126
23871
  function inferDomain(filePath) {
23127
23872
  const domains = getConfig().domains;
23128
23873
  const path = filePath.toLowerCase();
@@ -23254,7 +23999,7 @@ function scanComponentExports(dataDb) {
23254
23999
  const componentsBase = config.paths.components ?? config.paths.source + "/components";
23255
24000
  const componentDirs = [];
23256
24001
  const basePath = resolve32(projectRoot, componentsBase);
23257
- if (existsSync36(basePath)) {
24002
+ if (existsSync37(basePath)) {
23258
24003
  try {
23259
24004
  const entries = readdirSync23(basePath, { withFileTypes: true });
23260
24005
  for (const entry of entries) {
@@ -23267,7 +24012,7 @@ function scanComponentExports(dataDb) {
23267
24012
  }
23268
24013
  for (const dir of componentDirs) {
23269
24014
  const absDir = resolve32(projectRoot, dir);
23270
- if (!existsSync36(absDir)) continue;
24015
+ if (!existsSync37(absDir)) continue;
23271
24016
  const files = walkDir2(absDir).filter((f2) => f2.endsWith(".tsx") || f2.endsWith(".ts"));
23272
24017
  for (const file of files) {
23273
24018
  const relPath = relative11(projectRoot, file);
@@ -23297,7 +24042,7 @@ function scanComponentExports(dataDb) {
23297
24042
  if (hasHandlers && exportMatch) {
23298
24043
  const componentName = exportMatch[1];
23299
24044
  const domain = inferDomain(relPath);
23300
- const subdomain = basename7(dirname17(relPath));
24045
+ const subdomain = basename7(dirname18(relPath));
23301
24046
  const featureKey = `component.${subdomain}.${componentName.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "")}`;
23302
24047
  if (!annotations.some((a2) => a2.featureKey === featureKey)) {
23303
24048
  features.push({
@@ -23322,7 +24067,7 @@ function walkDir2(dir) {
23322
24067
  try {
23323
24068
  const entries = readdirSync23(dir);
23324
24069
  for (const entry of entries) {
23325
- const fullPath = join21(dir, entry);
24070
+ const fullPath = join22(dir, entry);
23326
24071
  try {
23327
24072
  const stat = statSync13(fullPath);
23328
24073
  if (stat.isDirectory()) {
@@ -24314,7 +25059,7 @@ var init_audit_trail = __esm({
24314
25059
  });
24315
25060
 
24316
25061
  // src/validation-engine.ts
24317
- import { existsSync as existsSync37, readFileSync as readFileSync36 } from "fs";
25062
+ import { existsSync as existsSync38, readFileSync as readFileSync36 } from "fs";
24318
25063
  function p10(baseName) {
24319
25064
  return `${getConfig().toolPrefix}_${baseName}`;
24320
25065
  }
@@ -24345,7 +25090,7 @@ function validateFile(filePath, projectRoot) {
24345
25090
  });
24346
25091
  return checks;
24347
25092
  }
24348
- if (!existsSync37(absPath)) {
25093
+ if (!existsSync38(absPath)) {
24349
25094
  checks.push({
24350
25095
  name: "file_exists",
24351
25096
  severity: "error",
@@ -24800,7 +25545,7 @@ var init_adr_generator = __esm({
24800
25545
  });
24801
25546
 
24802
25547
  // src/security-scorer.ts
24803
- import { existsSync as existsSync38, readFileSync as readFileSync37 } from "fs";
25548
+ import { existsSync as existsSync39, readFileSync as readFileSync37 } from "fs";
24804
25549
  function p12(baseName) {
24805
25550
  return `${getConfig().toolPrefix}_${baseName}`;
24806
25551
  }
@@ -24824,7 +25569,7 @@ function scoreFileSecurity(filePath, projectRoot) {
24824
25569
  }]
24825
25570
  };
24826
25571
  }
24827
- if (!existsSync38(absPath)) {
25572
+ if (!existsSync39(absPath)) {
24828
25573
  return { riskScore: 0, findings: [] };
24829
25574
  }
24830
25575
  let source;
@@ -25119,7 +25864,7 @@ var init_security_scorer = __esm({
25119
25864
  });
25120
25865
 
25121
25866
  // src/dependency-scorer.ts
25122
- import { existsSync as existsSync39, readFileSync as readFileSync38 } from "fs";
25867
+ import { existsSync as existsSync40, readFileSync as readFileSync38 } from "fs";
25123
25868
  import { resolve as resolve33 } from "path";
25124
25869
  function p13(baseName) {
25125
25870
  return `${getConfig().toolPrefix}_${baseName}`;
@@ -25153,7 +25898,7 @@ function calculateDepRisk(factors) {
25153
25898
  }
25154
25899
  function getInstalledPackages(projectRoot) {
25155
25900
  const pkgPath = resolve33(projectRoot, "package.json");
25156
- if (!existsSync39(pkgPath)) return /* @__PURE__ */ new Map();
25901
+ if (!existsSync40(pkgPath)) return /* @__PURE__ */ new Map();
25157
25902
  try {
25158
25903
  const pkg = JSON.parse(readFileSync38(pkgPath, "utf-8"));
25159
25904
  const packages = /* @__PURE__ */ new Map();
@@ -25761,7 +26506,7 @@ var init_regression_detector = __esm({
25761
26506
 
25762
26507
  // src/knowledge-indexer.ts
25763
26508
  import { createHash as createHash9 } from "crypto";
25764
- import { readFileSync as readFileSync39, readdirSync as readdirSync24, statSync as statSync14, existsSync as existsSync40 } from "fs";
26509
+ import { readFileSync as readFileSync39, readdirSync as readdirSync24, statSync as statSync14, existsSync as existsSync41 } from "fs";
25765
26510
  import { resolve as resolve34, relative as relative12, basename as basename8, extname as extname2 } from "path";
25766
26511
  function getKnowledgePaths() {
25767
26512
  const resolved = getResolvedPaths();
@@ -26069,11 +26814,11 @@ function indexAllKnowledge(db) {
26069
26814
  files.push(...memFiles);
26070
26815
  } catch {
26071
26816
  }
26072
- if (existsSync40(paths.plansDir)) {
26817
+ if (existsSync41(paths.plansDir)) {
26073
26818
  const planFiles = discoverMarkdownFiles(paths.plansDir);
26074
26819
  files.push(...planFiles);
26075
26820
  }
26076
- if (existsSync40(paths.docsDir)) {
26821
+ if (existsSync41(paths.docsDir)) {
26077
26822
  const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
26078
26823
  const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f2) => !f2.includes("/plans/") && !excludePatterns.some((p19) => f2.includes(p19)));
26079
26824
  files.push(...docsFiles);
@@ -26116,7 +26861,7 @@ function indexAllKnowledge(db) {
26116
26861
  } catch {
26117
26862
  }
26118
26863
  for (const filePath of files) {
26119
- if (!existsSync40(filePath)) continue;
26864
+ if (!existsSync41(filePath)) continue;
26120
26865
  const content = readFileSync39(filePath, "utf-8");
26121
26866
  const hash = hashContent2(content);
26122
26867
  const relPath = filePath.startsWith(paths.claudeDir) ? relative12(paths.claudeDir, filePath) : filePath.startsWith(paths.plansDir) ? "plans/" + relative12(paths.plansDir, filePath) : filePath.startsWith(paths.docsDir) ? "docs/" + relative12(paths.docsDir, filePath) : filePath.startsWith(paths.memoryDir) ? `memory/${relative12(paths.memoryDir, filePath)}` : basename8(filePath);
@@ -26252,10 +26997,10 @@ function isKnowledgeStale(db) {
26252
26997
  files.push(...discoverMarkdownFiles(paths.memoryDir));
26253
26998
  } catch {
26254
26999
  }
26255
- if (existsSync40(paths.plansDir)) {
27000
+ if (existsSync41(paths.plansDir)) {
26256
27001
  files.push(...discoverMarkdownFiles(paths.plansDir));
26257
27002
  }
26258
- if (existsSync40(paths.docsDir)) {
27003
+ if (existsSync41(paths.docsDir)) {
26259
27004
  const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
26260
27005
  const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f2) => !f2.includes("/plans/") && !excludePatterns.some((p19) => f2.includes(p19)));
26261
27006
  files.push(...docsFiles);
@@ -26284,7 +27029,7 @@ var init_knowledge_indexer = __esm({
26284
27029
  });
26285
27030
 
26286
27031
  // src/knowledge-tools.ts
26287
- import { readFileSync as readFileSync40, writeFileSync as writeFileSync5, appendFileSync as appendFileSync2, readdirSync as readdirSync25 } from "fs";
27032
+ import { readFileSync as readFileSync40, writeFileSync as writeFileSync6, appendFileSync as appendFileSync2, readdirSync as readdirSync25 } from "fs";
26288
27033
  import { resolve as resolve35, basename as basename9 } from "path";
26289
27034
  function p16(baseName) {
26290
27035
  return `${getConfig().toolPrefix}_${baseName}`;
@@ -27044,7 +27789,7 @@ ${crRule ? `- **CR**: ${crRule}
27044
27789
  if (archiveIdx > 0) {
27045
27790
  const before = existing.slice(0, archiveIdx);
27046
27791
  const after = existing.slice(archiveIdx);
27047
- writeFileSync5(correctionsPath, before + entry + after);
27792
+ writeFileSync6(correctionsPath, before + entry + after);
27048
27793
  } else {
27049
27794
  appendFileSync2(correctionsPath, entry);
27050
27795
  }
@@ -27992,7 +28737,7 @@ var init_framework_supports = __esm({
27992
28737
  });
27993
28738
 
27994
28739
  // src/tools.ts
27995
- import { readFileSync as readFileSync41, existsSync as existsSync42 } from "fs";
28740
+ import { readFileSync as readFileSync41, existsSync as existsSync43 } from "fs";
27996
28741
  import { resolve as resolve36, basename as basename10 } from "path";
27997
28742
  function prefix2() {
27998
28743
  return getConfig().toolPrefix;
@@ -28412,7 +29157,7 @@ function handleContext(file, dataDb, codegraphDb) {
28412
29157
  const resolvedPaths = getResolvedPaths();
28413
29158
  const root = getProjectRoot();
28414
29159
  const absFilePath = ensureWithinRoot(resolve36(resolvedPaths.srcDir, "..", file), root);
28415
- if (existsSync42(absFilePath)) {
29160
+ if (existsSync43(absFilePath)) {
28416
29161
  const fileContent = readFileSync41(absFilePath, "utf-8").slice(0, 3e3);
28417
29162
  const keywords = [];
28418
29163
  if (fileContent.includes("ctx.db")) keywords.push("database", "schema");
@@ -28851,7 +29596,7 @@ function handleSchema(args2) {
28851
29596
  lines.push("");
28852
29597
  const projectRoot = getProjectRoot();
28853
29598
  const absPath = ensureWithinRoot(resolve36(projectRoot, file), projectRoot);
28854
- if (!existsSync42(absPath)) {
29599
+ if (!existsSync43(absPath)) {
28855
29600
  return text17(`File not found: ${file}`);
28856
29601
  }
28857
29602
  const source = readFileSync41(absPath, "utf-8");
@@ -29260,7 +30005,7 @@ var init_server_dispatch = __esm({
29260
30005
  // src/server.ts
29261
30006
  var server_exports = {};
29262
30007
  import { readFileSync as readFileSync42 } from "fs";
29263
- import { resolve as resolve37, dirname as dirname18 } from "path";
30008
+ import { resolve as resolve37, dirname as dirname19 } from "path";
29264
30009
  import { fileURLToPath as fileURLToPath5 } from "url";
29265
30010
  function pruneMemoryOnStartup() {
29266
30011
  try {
@@ -29292,7 +30037,7 @@ var init_server = __esm({
29292
30037
  init_memory_db();
29293
30038
  init_license();
29294
30039
  init_server_dispatch();
29295
- __dirname5 = dirname18(fileURLToPath5(import.meta.url));
30040
+ __dirname5 = dirname19(fileURLToPath5(import.meta.url));
29296
30041
  PKG_VERSION = (() => {
29297
30042
  try {
29298
30043
  const pkg = JSON.parse(readFileSync42(resolve37(__dirname5, "..", "package.json"), "utf-8"));
@@ -29581,7 +30326,7 @@ var config_upgrade_exports = {};
29581
30326
  __export(config_upgrade_exports, {
29582
30327
  runConfigUpgrade: () => runConfigUpgrade
29583
30328
  });
29584
- import { existsSync as existsSync43, readFileSync as readFileSync43, writeFileSync as writeFileSync6, copyFileSync, unlinkSync as unlinkSync2 } from "fs";
30329
+ import { existsSync as existsSync44, readFileSync as readFileSync43, writeFileSync as writeFileSync7, copyFileSync, unlinkSync as unlinkSync2 } from "fs";
29585
30330
  import { resolve as resolve38 } from "path";
29586
30331
  import { parse as parseYaml6 } from "yaml";
29587
30332
  async function runConfigUpgrade(opts = {}) {
@@ -29593,7 +30338,7 @@ async function runConfigUpgrade(opts = {}) {
29593
30338
  const err = opts.silent ? () => {
29594
30339
  } : (s) => process.stderr.write(s);
29595
30340
  if (opts.rollback) {
29596
- if (!existsSync43(bakPath)) {
30341
+ if (!existsSync44(bakPath)) {
29597
30342
  const message = `No backup found at ${bakPath}`;
29598
30343
  err(message + "\n");
29599
30344
  return { exitCode: 1, action: "none", message };
@@ -29609,7 +30354,7 @@ async function runConfigUpgrade(opts = {}) {
29609
30354
  return { exitCode: 2, action: "none", message };
29610
30355
  }
29611
30356
  }
29612
- if (!existsSync43(configPath)) {
30357
+ if (!existsSync44(configPath)) {
29613
30358
  const message = "massu.config.yaml not found. Run: npx massu init";
29614
30359
  err(message + "\n");
29615
30360
  return { exitCode: 1, action: "none", message };
@@ -29640,7 +30385,7 @@ async function runConfigUpgrade(opts = {}) {
29640
30385
  };
29641
30386
  try {
29642
30387
  const original = readFileSync43(configPath, "utf-8");
29643
- writeFileSync6(bakPath, original, "utf-8");
30388
+ writeFileSync7(bakPath, original, "utf-8");
29644
30389
  } catch (e2) {
29645
30390
  const message = `Failed to write backup: ${e2 instanceof Error ? e2.message : String(e2)}`;
29646
30391
  err(message + "\n");
@@ -29673,7 +30418,7 @@ var config_check_drift_exports = {};
29673
30418
  __export(config_check_drift_exports, {
29674
30419
  runConfigCheckDrift: () => runConfigCheckDrift
29675
30420
  });
29676
- import { existsSync as existsSync44, readFileSync as readFileSync44 } from "fs";
30421
+ import { existsSync as existsSync45, readFileSync as readFileSync44 } from "fs";
29677
30422
  import { resolve as resolve39 } from "path";
29678
30423
  import { parse as parseYaml7 } from "yaml";
29679
30424
  function renderChanges(changes) {
@@ -29687,7 +30432,7 @@ async function runConfigCheckDrift(opts = {}) {
29687
30432
  } : (s) => process.stdout.write(s);
29688
30433
  const err = opts.silent ? () => {
29689
30434
  } : (s) => process.stderr.write(s);
29690
- if (!existsSync44(configPath)) {
30435
+ if (!existsSync45(configPath)) {
29691
30436
  const message = "massu.config.yaml not found. Run: npx massu init";
29692
30437
  err(message + "\n");
29693
30438
  return {
@@ -29769,10 +30514,10 @@ var init_config_check_drift = __esm({
29769
30514
 
29770
30515
  // src/cli.ts
29771
30516
  import { readFileSync as readFileSync45 } from "fs";
29772
- import { resolve as resolve40, dirname as dirname19 } from "path";
30517
+ import { resolve as resolve40, dirname as dirname20 } from "path";
29773
30518
  import { fileURLToPath as fileURLToPath6 } from "url";
29774
30519
  var __filename5 = fileURLToPath6(import.meta.url);
29775
- var __dirname6 = dirname19(__filename5);
30520
+ var __dirname6 = dirname20(__filename5);
29776
30521
  var args = process.argv.slice(2);
29777
30522
  var subcommand = args[0];
29778
30523
  async function main() {
@@ -29809,12 +30554,24 @@ async function main() {
29809
30554
  process.exit(result.exitCode);
29810
30555
  return;
29811
30556
  }
30557
+ case "license": {
30558
+ const { handleLicenseSubcommand: handleLicenseSubcommand2 } = await Promise.resolve().then(() => (init_license2(), license_exports));
30559
+ const result = await handleLicenseSubcommand2(args.slice(1));
30560
+ process.exit(result.exitCode);
30561
+ return;
30562
+ }
29812
30563
  case "changelog": {
29813
30564
  const { handleChangelogSubcommand: handleChangelogSubcommand2 } = await Promise.resolve().then(() => (init_changelog(), changelog_exports));
29814
30565
  const result = await handleChangelogSubcommand2(args.slice(1));
29815
30566
  process.exit(result.exitCode);
29816
30567
  return;
29817
30568
  }
30569
+ case "rule": {
30570
+ const { handleRuleSubcommand: handleRuleSubcommand2 } = await Promise.resolve().then(() => (init_rule(), rule_exports));
30571
+ const result = await handleRuleSubcommand2(args.slice(1));
30572
+ process.exit(result.exitCode);
30573
+ return;
30574
+ }
29818
30575
  case "show-template": {
29819
30576
  const { runShowTemplate: runShowTemplate2 } = await Promise.resolve().then(() => (init_show_template(), show_template_exports));
29820
30577
  await runShowTemplate2(args.slice(1));
@@ -29943,8 +30700,10 @@ Commands:
29943
30700
  validate-config Validate massu.config.yaml (alias: config validate)
29944
30701
  config <sub> Config lifecycle: refresh | validate | upgrade | doctor | check-drift
29945
30702
  permissions <sub> MCP permission lifecycle: install | verify | check-drift
30703
+ license <sub> License/tier gate: check --min <free|pro|team|enterprise>
29946
30704
  changelog <sub> CHANGELOG generation / verification: generate | verify
29947
30705
  adapters <sub> Third-party adapter registry: list | refresh | search | add-local | remove-local | install | resign
30706
+ rule <sub> Auto-learning rule-candidate side-effects: record-shown <prompt_hash>
29948
30707
 
29949
30708
  Options:
29950
30709
  --help, -h Show this help message