@massu/core 1.13.1 → 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
 
@@ -15443,6 +15712,111 @@ var init_permissions2 = __esm({
15443
15712
  }
15444
15713
  });
15445
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
+
15446
15820
  // src/changelog-generator.ts
15447
15821
  import { existsSync as existsSync14, readFileSync as readFileSync13, readdirSync as readdirSync12 } from "fs";
15448
15822
  import { resolve as resolve11 } from "path";
@@ -15682,12 +16056,397 @@ var init_changelog = __esm({
15682
16056
  }
15683
16057
  });
15684
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
+
15685
16444
  // src/commands/show-template.ts
15686
16445
  var show_template_exports = {};
15687
16446
  __export(show_template_exports, {
15688
16447
  runShowTemplate: () => runShowTemplate
15689
16448
  });
15690
- import { existsSync as existsSync16, readFileSync as readFileSync15 } from "fs";
16449
+ import { existsSync as existsSync17, readFileSync as readFileSync15 } from "fs";
15691
16450
  import { resolve as resolve13 } from "path";
15692
16451
  function normalizeBaseName(input) {
15693
16452
  return input.endsWith(".md") ? input.slice(0, -".md".length) : input;
@@ -15717,7 +16476,7 @@ async function runShowTemplate(args2) {
15717
16476
  }
15718
16477
  const suffix = choice.kind === "hit" ? choice.suffix : "";
15719
16478
  const file = suffix === "" ? resolve13(sourceDir, `${baseName}.md`) : resolve13(sourceDir, `${baseName}${suffix}.md`);
15720
- if (!existsSync16(file)) {
16479
+ if (!existsSync17(file)) {
15721
16480
  process.stderr.write(`massu: resolved template "${file}" no longer exists
15722
16481
  `);
15723
16482
  process.exit(1);
@@ -15776,8 +16535,8 @@ var init_passthrough = __esm({
15776
16535
  });
15777
16536
 
15778
16537
  // src/lib/fileLock.ts
15779
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync16, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
15780
- 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";
15781
16540
  import * as lockfile from "proper-lockfile";
15782
16541
  function readLockHolderPid(lockPath) {
15783
16542
  try {
@@ -15800,7 +16559,7 @@ function busyWaitSync(ms) {
15800
16559
  Atomics.wait(view, 0, 0, ms);
15801
16560
  }
15802
16561
  function withFileLockSync(lockPath, fn, opts = {}) {
15803
- mkdirSync6(dirname9(lockPath), { recursive: true });
16562
+ mkdirSync7(dirname10(lockPath), { recursive: true });
15804
16563
  const staleMs = opts.staleMs ?? 3e4;
15805
16564
  const blockMs = opts.retries === 0 ? 0 : opts.blockMs ?? 3e4;
15806
16565
  const pollIntervalMs = opts.pollIntervalMs ?? 100;
@@ -15817,7 +16576,7 @@ function withFileLockSync(lockPath, fn, opts = {}) {
15817
16576
  realpath: false
15818
16577
  });
15819
16578
  try {
15820
- writeFileSync3(`${lockPath}.pid`, String(process.pid), "utf-8");
16579
+ writeFileSync4(`${lockPath}.pid`, String(process.pid), "utf-8");
15821
16580
  } catch {
15822
16581
  }
15823
16582
  break;
@@ -15906,7 +16665,7 @@ __export(config_refresh_exports, {
15906
16665
  mergeRefresh: () => mergeRefresh,
15907
16666
  runConfigRefresh: () => runConfigRefresh
15908
16667
  });
15909
- 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";
15910
16669
  import { resolve as resolve15 } from "path";
15911
16670
  import { parse as parseYaml5 } from "yaml";
15912
16671
  function flatten(obj, prefix3 = "") {
@@ -16038,7 +16797,7 @@ async function runConfigRefresh(opts = {}) {
16038
16797
  const configPath = resolve15(cwd, "massu.config.yaml");
16039
16798
  const log = opts.silent ? () => {
16040
16799
  } : (s) => process.stdout.write(s);
16041
- if (!existsSync17(configPath)) {
16800
+ if (!existsSync18(configPath)) {
16042
16801
  const message = "massu.config.yaml not found. Run: npx massu init";
16043
16802
  if (!opts.silent) process.stderr.write(message + "\n");
16044
16803
  return { exitCode: 1, applied: false, dryRun: !!opts.dryRun, diff: [], message };
@@ -16119,7 +16878,7 @@ async function runConfigRefresh(opts = {}) {
16119
16878
  const stackResolved = installResult.totalInstalled > 0 || installResult.totalUpdated > 0;
16120
16879
  if (stackResolved) {
16121
16880
  const placeholderPath = resolve15(installResult.claudeDir, "commands", "_massu-needs-stack.md");
16122
- if (existsSync17(placeholderPath)) {
16881
+ if (existsSync18(placeholderPath)) {
16123
16882
  try {
16124
16883
  rmSync4(placeholderPath, { force: true });
16125
16884
  log("Removed _massu-needs-stack.md (stack now declared).\n");
@@ -16228,12 +16987,12 @@ var init_gitToplevel = __esm({
16228
16987
  });
16229
16988
 
16230
16989
  // src/watch/lockfile-detector.ts
16231
- import { existsSync as existsSync18, statSync as statSync8 } from "fs";
16990
+ import { existsSync as existsSync19, statSync as statSync8 } from "fs";
16232
16991
  import { resolve as resolve16 } from "path";
16233
16992
  function lockfileMidWrite(projectRoot, now = Date.now(), windowMs = LOCKFILE_WINDOW_MS) {
16234
16993
  for (const lf of KNOWN_LOCKFILES) {
16235
16994
  const p19 = resolve16(projectRoot, lf);
16236
- if (!existsSync18(p19)) continue;
16995
+ if (!existsSync19(p19)) continue;
16237
16996
  try {
16238
16997
  const stat = statSync8(p19);
16239
16998
  const delta = now - stat.mtimeMs;
@@ -16246,7 +17005,7 @@ function lockfileMidWrite(projectRoot, now = Date.now(), windowMs = LOCKFILE_WIN
16246
17005
  function gitMidOperation(projectRoot) {
16247
17006
  const sentinels = ["MERGE_HEAD", "REBASE_HEAD", "CHERRY_PICK_HEAD", "rebase-apply", "rebase-merge"];
16248
17007
  for (const s of sentinels) {
16249
- if (existsSync18(resolve16(projectRoot, ".git", s))) return true;
17008
+ if (existsSync19(resolve16(projectRoot, ".git", s))) return true;
16250
17009
  }
16251
17010
  return false;
16252
17011
  }
@@ -16443,8 +17202,8 @@ var init_paths = __esm({
16443
17202
  });
16444
17203
 
16445
17204
  // src/watch/state.ts
16446
- 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";
16447
- 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";
16448
17207
  function watchStatePath(projectRoot) {
16449
17208
  return resolve17(projectRoot, ".massu", "watch-state.json");
16450
17209
  }
@@ -16453,7 +17212,7 @@ function backupStatePath(projectRoot) {
16453
17212
  }
16454
17213
  function readState(projectRoot) {
16455
17214
  const path = watchStatePath(projectRoot);
16456
- if (!existsSync19(path)) return { ...DEFAULT_STATE };
17215
+ if (!existsSync20(path)) return { ...DEFAULT_STATE };
16457
17216
  const content = readFileSync18(path, "utf-8");
16458
17217
  let raw;
16459
17218
  try {
@@ -16496,14 +17255,14 @@ function readState(projectRoot) {
16496
17255
  }
16497
17256
  function archiveCorrupt(projectRoot, content) {
16498
17257
  const bak = backupStatePath(projectRoot);
16499
- mkdirSync7(dirname10(bak), { recursive: true });
16500
- writeFileSync4(bak, content, "utf-8");
17258
+ mkdirSync8(dirname11(bak), { recursive: true });
17259
+ writeFileSync5(bak, content, "utf-8");
16501
17260
  }
16502
17261
  function writeStateAtomic(projectRoot, state) {
16503
17262
  const path = watchStatePath(projectRoot);
16504
17263
  writeStateAtomicCounter = writeStateAtomicCounter + 1 >>> 0;
16505
17264
  const tmp = `${path}.${process.pid}.${writeStateAtomicCounter}.tmp`;
16506
- mkdirSync7(dirname10(path), { recursive: true });
17265
+ mkdirSync8(dirname11(path), { recursive: true });
16507
17266
  let renamed = false;
16508
17267
  try {
16509
17268
  const fd = openSync3(tmp, "w");
@@ -16517,7 +17276,7 @@ function writeStateAtomic(projectRoot, state) {
16517
17276
  renameSync4(tmp, path);
16518
17277
  renamed = true;
16519
17278
  } finally {
16520
- if (!renamed && existsSync19(tmp)) {
17279
+ if (!renamed && existsSync20(tmp)) {
16521
17280
  try {
16522
17281
  rmSync5(tmp, { force: true });
16523
17282
  } catch {
@@ -16797,8 +17556,8 @@ __export(watch_exports, {
16797
17556
  runWatch: () => runWatch
16798
17557
  });
16799
17558
  import { spawnSync as spawnSync3 } from "child_process";
16800
- import { basename as basename5, dirname as dirname11, resolve as resolve18 } from "path";
16801
- 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";
16802
17561
  function parseFlags(args2) {
16803
17562
  const out = {
16804
17563
  foreground: false,
@@ -16824,7 +17583,7 @@ function parseFlags(args2) {
16824
17583
  function findClaudeBg() {
16825
17584
  const home = process.env.HOME ?? "";
16826
17585
  const fixed = home ? resolve18(home, ".claude", "bin", "claude-bg") : null;
16827
- if (fixed && existsSync20(fixed)) return fixed;
17586
+ if (fixed && existsSync21(fixed)) return fixed;
16828
17587
  const which = spawnSync3("which", ["claude-bg"], { encoding: "utf-8" });
16829
17588
  if (which.status === 0 && which.stdout) {
16830
17589
  const p19 = which.stdout.trim();
@@ -16885,7 +17644,7 @@ async function runWatch(args2) {
16885
17644
  }
16886
17645
  function runStatus(root) {
16887
17646
  const path = watchStatePath(root);
16888
- if (!existsSync20(path)) {
17647
+ if (!existsSync21(path)) {
16889
17648
  process.stdout.write("massu watch: not running (no state file)\n");
16890
17649
  return { exitCode: 0 };
16891
17650
  }
@@ -17043,14 +17802,14 @@ function refreshLogPath(projectRoot) {
17043
17802
  function appendRefreshLog(projectRoot, event) {
17044
17803
  const path = refreshLogPath(projectRoot);
17045
17804
  try {
17046
- mkdirSync8(dirname11(path), { recursive: true });
17805
+ mkdirSync9(dirname12(path), { recursive: true });
17047
17806
  appendFileSync(path, JSON.stringify(event) + "\n", "utf-8");
17048
17807
  } catch {
17049
17808
  }
17050
17809
  }
17051
17810
  function readRefreshLog(projectRoot, limit = 10, opts = {}) {
17052
17811
  const path = refreshLogPath(projectRoot);
17053
- if (!existsSync20(path)) return [];
17812
+ if (!existsSync21(path)) return [];
17054
17813
  const warn = opts.warn ?? ((s) => {
17055
17814
  process.stderr.write(s);
17056
17815
  });
@@ -17127,26 +17886,26 @@ var init_refresh_log = __esm({
17127
17886
  import {
17128
17887
  chmodSync as chmodSync3,
17129
17888
  closeSync as closeSync4,
17130
- existsSync as existsSync21,
17889
+ existsSync as existsSync22,
17131
17890
  fsyncSync as fsyncSync4,
17132
- mkdirSync as mkdirSync9,
17891
+ mkdirSync as mkdirSync10,
17133
17892
  openSync as openSync4,
17134
17893
  renameSync as renameSync5,
17135
17894
  rmSync as rmSync6,
17136
17895
  statSync as statSync9,
17137
17896
  writeSync as writeSync4
17138
17897
  } from "node:fs";
17139
- import { dirname as dirname12 } from "node:path";
17898
+ import { dirname as dirname13 } from "node:path";
17140
17899
  function atomicWrite(path, content, opts = {}) {
17141
17900
  const tmpPath = `${path}.tmp`;
17142
- const parentDir = dirname12(path);
17901
+ const parentDir = dirname13(path);
17143
17902
  try {
17144
- if (!existsSync21(parentDir)) {
17903
+ if (!existsSync22(parentDir)) {
17145
17904
  const mkdirOpts = { recursive: true };
17146
17905
  if (opts.ensureParentDirMode !== void 0) {
17147
17906
  mkdirOpts.mode = opts.ensureParentDirMode;
17148
17907
  }
17149
- mkdirSync9(parentDir, mkdirOpts);
17908
+ mkdirSync10(parentDir, mkdirOpts);
17150
17909
  }
17151
17910
  const buf = typeof content === "string" ? Buffer.from(content, "utf-8") : content;
17152
17911
  const openMode = opts.mode ?? 420;
@@ -17163,7 +17922,7 @@ function atomicWrite(path, content, opts = {}) {
17163
17922
  renameSync5(tmpPath, path);
17164
17923
  return { written: true };
17165
17924
  } catch (err) {
17166
- if (existsSync21(tmpPath)) {
17925
+ if (existsSync22(tmpPath)) {
17167
17926
  try {
17168
17927
  rmSync6(tmpPath, { force: true });
17169
17928
  } catch {
@@ -17390,7 +18149,7 @@ var init_adapter_verifier = __esm({
17390
18149
  // src/security/fetcher.ts
17391
18150
  async function fetchUrl(url, opts = {}) {
17392
18151
  const allowedHosts = opts.allowedHosts ?? ALLOWED_HOSTS;
17393
- const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
18152
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
17394
18153
  const maxBodyBytes = opts.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
17395
18154
  let parsed;
17396
18155
  try {
@@ -17464,11 +18223,11 @@ async function fetchUrl(url, opts = {}) {
17464
18223
  clearTimeout(timeoutHandle);
17465
18224
  }
17466
18225
  }
17467
- 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;
17468
18227
  var init_fetcher = __esm({
17469
18228
  "src/security/fetcher.ts"() {
17470
18229
  "use strict";
17471
- DEFAULT_TIMEOUT_MS = 1e4;
18230
+ DEFAULT_TIMEOUT_MS2 = 1e4;
17472
18231
  DEFAULT_MAX_BODY_BYTES = 10 * 1024 * 1024;
17473
18232
  ALLOWED_HOSTS = [
17474
18233
  "registry.massu.ai",
@@ -17506,7 +18265,7 @@ var init_fetcher = __esm({
17506
18265
  });
17507
18266
 
17508
18267
  // src/security/manifest-cache.ts
17509
- 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";
17510
18269
  import { homedir as homedir5 } from "node:os";
17511
18270
  import { resolve as resolve19 } from "node:path";
17512
18271
  import { z as z4 } from "zod";
@@ -17518,7 +18277,7 @@ function defaultCachePaths() {
17518
18277
  };
17519
18278
  }
17520
18279
  function loadCachedManifest(paths = defaultCachePaths()) {
17521
- if (!existsSync22(paths.cachePath)) {
18280
+ if (!existsSync23(paths.cachePath)) {
17522
18281
  return { kind: "absent" };
17523
18282
  }
17524
18283
  let raw;
@@ -17712,7 +18471,7 @@ var init_adapter_origin = __esm({
17712
18471
  });
17713
18472
 
17714
18473
  // src/security/local-fingerprint.ts
17715
- 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";
17716
18475
  import { homedir as homedir6 } from "node:os";
17717
18476
  import { resolve as resolve20, isAbsolute } from "node:path";
17718
18477
  import { createHash as createHash7 } from "node:crypto";
@@ -17741,7 +18500,7 @@ function computeLocalFingerprint(localPaths, projectRoot) {
17741
18500
  return createHash7("sha256").update(canonical).digest("hex");
17742
18501
  }
17743
18502
  function readFingerprintSentinel(path = FINGERPRINT_PATH) {
17744
- if (!existsSync23(path)) return null;
18503
+ if (!existsSync24(path)) return null;
17745
18504
  let raw;
17746
18505
  try {
17747
18506
  raw = JSON.parse(readFileSync21(path, "utf-8"));
@@ -17808,15 +18567,15 @@ var init_local_fingerprint = __esm({
17808
18567
  });
17809
18568
 
17810
18569
  // src/security/install-tracking.ts
17811
- import { readFileSync as readFileSync22, readdirSync as readdirSync13, lstatSync as lstatSync6, existsSync as existsSync24 } from "node:fs";
17812
- 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";
17813
18572
  import { homedir as homedir7 } from "node:os";
17814
18573
  import { resolve as resolve21 } from "node:path";
17815
18574
  import { createHash as createHash8 } from "node:crypto";
17816
18575
  import { z as z6 } from "zod";
17817
18576
  function containsHiddenDirs(packageDir) {
17818
18577
  for (const hidden of EXCLUDED_DIR_NAMES) {
17819
- if (existsSync24(`${packageDir}/${hidden}`)) {
18578
+ if (existsSync25(`${packageDir}/${hidden}`)) {
17820
18579
  return hidden;
17821
18580
  }
17822
18581
  }
@@ -17833,7 +18592,7 @@ function sha256OfDir(dir, opts = {}) {
17833
18592
  return;
17834
18593
  }
17835
18594
  for (const entry of entries.sort()) {
17836
- const absPath = join11(currentDir, entry);
18595
+ const absPath = join12(currentDir, entry);
17837
18596
  let lst;
17838
18597
  try {
17839
18598
  lst = lstatSync6(absPath);
@@ -17871,7 +18630,7 @@ function sha256OfDir(dir, opts = {}) {
17871
18630
  return top.digest("hex");
17872
18631
  }
17873
18632
  function readInstalledManifest(path = INSTALLED_MANIFEST_PATH) {
17874
- if (!existsSync24(path)) return {};
18633
+ if (!existsSync25(path)) return {};
17875
18634
  let raw;
17876
18635
  try {
17877
18636
  raw = JSON.parse(readFileSync22(path, "utf-8"));
@@ -17913,8 +18672,8 @@ function removeInstalledManifestEntry(packageName, path = INSTALLED_MANIFEST_PAT
17913
18672
  }
17914
18673
  return { written: true };
17915
18674
  }
17916
- function verifyInstalledIntegrity(packageName, packageDir, sidecarPath = INSTALLED_MANIFEST_PATH) {
17917
- const installed = readInstalledManifest(sidecarPath);
18675
+ function verifyInstalledIntegrity(packageName, packageDir, sidecarPath2 = INSTALLED_MANIFEST_PATH) {
18676
+ const installed = readInstalledManifest(sidecarPath2);
17918
18677
  const entry = installed[packageName];
17919
18678
  if (!entry) {
17920
18679
  return {
@@ -17961,12 +18720,12 @@ var init_install_tracking = __esm({
17961
18720
  });
17962
18721
 
17963
18722
  // src/detect/adapters/discover.ts
17964
- 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";
17965
18724
  import { resolve as resolve22, isAbsolute as isAbsolute2 } from "node:path";
17966
18725
  import { z as z7 } from "zod";
17967
18726
  function walkNodeModules(projectRoot, warnings) {
17968
18727
  const nodeModulesDir = resolve22(projectRoot, "node_modules");
17969
- if (!existsSync25(nodeModulesDir)) {
18728
+ if (!existsSync26(nodeModulesDir)) {
17970
18729
  return [];
17971
18730
  }
17972
18731
  const candidates = [];
@@ -18021,7 +18780,7 @@ function walkNodeModules(projectRoot, warnings) {
18021
18780
  }
18022
18781
  function tryReadAdapterPackage(packageDir, warnings) {
18023
18782
  const pkgJsonPath = resolve22(packageDir, "package.json");
18024
- if (!existsSync25(pkgJsonPath)) return null;
18783
+ if (!existsSync26(pkgJsonPath)) return null;
18025
18784
  let raw;
18026
18785
  try {
18027
18786
  raw = JSON.parse(readFileSync23(pkgJsonPath, "utf-8"));
@@ -18159,7 +18918,7 @@ function discoverAdapters(opts) {
18159
18918
  for (const localPath of opts.configLocalPaths) {
18160
18919
  if (seenIds.has(localPath)) continue;
18161
18920
  const absPath = isAbsolute2(localPath) ? localPath : resolve22(opts.projectRoot, localPath);
18162
- if (!existsSync25(absPath)) {
18921
+ if (!existsSync26(absPath)) {
18163
18922
  warnings.push(
18164
18923
  `local adapter file not found: ${localPath} (resolved to ${absPath}). Remove via: massu adapters remove-local ${localPath}`
18165
18924
  );
@@ -18241,7 +19000,7 @@ __export(adapters_exports, {
18241
19000
  runAdaptersResyncLocalFingerprint: () => runAdaptersResyncLocalFingerprint,
18242
19001
  runAdaptersSearch: () => runAdaptersSearch
18243
19002
  });
18244
- import { existsSync as existsSync26, readFileSync as readFileSync24 } from "node:fs";
19003
+ import { existsSync as existsSync27, readFileSync as readFileSync24 } from "node:fs";
18245
19004
  import { resolve as resolve23 } from "node:path";
18246
19005
  import { parseDocument } from "yaml";
18247
19006
  async function handleAdaptersSubcommand(args2) {
@@ -18510,7 +19269,7 @@ function mutateLocalArray(mutator, command) {
18510
19269
  return { exitCode: 2 };
18511
19270
  }
18512
19271
  const yamlPath = resolve23(projectRoot, "massu.config.yaml");
18513
- if (!existsSync26(yamlPath)) {
19272
+ if (!existsSync27(yamlPath)) {
18514
19273
  process.stderr.write(
18515
19274
  `${command}: massu.config.yaml not found at ${yamlPath}. Run \`massu init\` first.
18516
19275
  `
@@ -18610,7 +19369,7 @@ async function runAdaptersInstall(args2) {
18610
19369
  return { exitCode: 1 };
18611
19370
  }
18612
19371
  const packageDir = resolve23(projectRoot, "node_modules", ...packageName.split("/"));
18613
- if (!existsSync26(packageDir)) {
19372
+ if (!existsSync27(packageDir)) {
18614
19373
  process.stderr.write(
18615
19374
  `install: ${packageName} is not installed in node_modules. Run \`npm install ${packageName}\` first.
18616
19375
  `
@@ -18618,7 +19377,7 @@ async function runAdaptersInstall(args2) {
18618
19377
  return { exitCode: 1 };
18619
19378
  }
18620
19379
  const pkgJsonPath = resolve23(packageDir, "package.json");
18621
- if (!existsSync26(pkgJsonPath)) {
19380
+ if (!existsSync27(pkgJsonPath)) {
18622
19381
  process.stderr.write(`install: ${packageName} has no package.json at ${pkgJsonPath}
18623
19382
  `);
18624
19383
  return { exitCode: 1 };
@@ -18745,7 +19504,7 @@ async function runAdaptersResign(_args) {
18745
19504
  continue;
18746
19505
  }
18747
19506
  const packageDir = resolve23(projectRoot, "node_modules", ...name.split("/"));
18748
- if (!existsSync26(packageDir)) {
19507
+ if (!existsSync27(packageDir)) {
18749
19508
  removed++;
18750
19509
  warnings.push(`${name}@${entry.version}: not present in node_modules \u2014 REMOVED from sidecar`);
18751
19510
  removeInstalledManifestEntry(name);
@@ -18843,11 +19602,11 @@ var init_sql_table_names = __esm({
18843
19602
 
18844
19603
  // src/db.ts
18845
19604
  import Database2 from "better-sqlite3";
18846
- import { dirname as dirname13, join as join12 } from "path";
18847
- 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";
18848
19607
  function getCodeGraphDb() {
18849
19608
  const dbPath = getResolvedPaths().codegraphDbPath;
18850
- if (!existsSync27(dbPath)) {
19609
+ if (!existsSync28(dbPath)) {
18851
19610
  throw new CodegraphDbNotInitializedError(dbPath);
18852
19611
  }
18853
19612
  const db = new Database2(dbPath, { readonly: true });
@@ -18856,9 +19615,9 @@ function getCodeGraphDb() {
18856
19615
  }
18857
19616
  function getDataDb() {
18858
19617
  const dbPath = getResolvedPaths().dataDbPath;
18859
- const dir = dirname13(dbPath);
18860
- if (!existsSync27(dir)) {
18861
- mkdirSync10(dir, { recursive: true });
19618
+ const dir = dirname14(dbPath);
19619
+ if (!existsSync28(dir)) {
19620
+ mkdirSync11(dir, { recursive: true });
18862
19621
  }
18863
19622
  const db = new Database2(dbPath);
18864
19623
  db.pragma("journal_mode = WAL");
@@ -19130,7 +19889,7 @@ function isPythonDataStale(dataDb, pythonRoot) {
19130
19889
  try {
19131
19890
  const entries = readdirSync15(dir, { withFileTypes: true });
19132
19891
  for (const entry of entries) {
19133
- const fullPath = join12(dir, entry.name);
19892
+ const fullPath = join13(dir, entry.name);
19134
19893
  if (entry.isDirectory()) {
19135
19894
  if (["__pycache__", ".venv", "venv", "node_modules", ".mypy_cache", ".pytest_cache"].includes(entry.name)) continue;
19136
19895
  if (checkDir(fullPath)) return true;
@@ -19166,13 +19925,13 @@ var init_db = __esm({
19166
19925
 
19167
19926
  // src/knowledge-db.ts
19168
19927
  import Database3 from "better-sqlite3";
19169
- import { dirname as dirname14 } from "path";
19170
- 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";
19171
19930
  function getKnowledgeDb() {
19172
19931
  const dbPath = getResolvedPaths().knowledgeDbPath;
19173
- const dir = dirname14(dbPath);
19174
- if (!existsSync28(dir)) {
19175
- mkdirSync11(dir, { recursive: true });
19932
+ const dir = dirname15(dbPath);
19933
+ if (!existsSync29(dir)) {
19934
+ mkdirSync12(dir, { recursive: true });
19176
19935
  }
19177
19936
  const db = new Database3(dbPath);
19178
19937
  db.pragma("journal_mode = WAL");
@@ -19419,8 +20178,8 @@ var init_rules = __esm({
19419
20178
  });
19420
20179
 
19421
20180
  // src/import-resolver.ts
19422
- import { readFileSync as readFileSync25, existsSync as existsSync29, statSync as statSync12 } from "fs";
19423
- 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";
19424
20183
  function parseImports(source) {
19425
20184
  const imports = [];
19426
20185
  const lines = source.split("\n");
@@ -19478,21 +20237,21 @@ function resolveImportPath(specifier, fromFile) {
19478
20237
  const paths = getResolvedPaths();
19479
20238
  basePath = resolve25(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
19480
20239
  } else {
19481
- basePath = resolve25(dirname15(fromFile), specifier);
20240
+ basePath = resolve25(dirname16(fromFile), specifier);
19482
20241
  }
19483
- if (existsSync29(basePath) && !isDirectory(basePath)) {
20242
+ if (existsSync30(basePath) && !isDirectory(basePath)) {
19484
20243
  return toRelative(basePath);
19485
20244
  }
19486
20245
  const resolvedPaths = getResolvedPaths();
19487
20246
  for (const ext of resolvedPaths.extensions) {
19488
20247
  const withExt = basePath + ext;
19489
- if (existsSync29(withExt)) {
20248
+ if (existsSync30(withExt)) {
19490
20249
  return toRelative(withExt);
19491
20250
  }
19492
20251
  }
19493
20252
  for (const indexFile of resolvedPaths.indexFiles) {
19494
- const indexPath = join13(basePath, indexFile);
19495
- if (existsSync29(indexPath)) {
20253
+ const indexPath = join14(basePath, indexFile);
20254
+ if (existsSync30(indexPath)) {
19496
20255
  return toRelative(indexPath);
19497
20256
  }
19498
20257
  }
@@ -19529,7 +20288,7 @@ function buildImportIndex(dataDb, codegraphDb) {
19529
20288
  let batch = [];
19530
20289
  for (const file of files) {
19531
20290
  const absPath = ensureWithinRoot(resolve25(projectRoot, file.path), projectRoot);
19532
- if (!existsSync29(absPath)) continue;
20291
+ if (!existsSync30(absPath)) continue;
19533
20292
  let source;
19534
20293
  try {
19535
20294
  source = readFileSync25(absPath, "utf-8");
@@ -19569,12 +20328,12 @@ var init_import_resolver = __esm({
19569
20328
  });
19570
20329
 
19571
20330
  // src/trpc-index.ts
19572
- import { readFileSync as readFileSync26, existsSync as existsSync30, readdirSync as readdirSync16 } from "fs";
19573
- 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";
19574
20333
  function parseRootRouter() {
19575
20334
  const paths = getResolvedPaths();
19576
20335
  const rootPath = paths.rootRouterPath;
19577
- if (!existsSync30(rootPath)) {
20336
+ if (!existsSync31(rootPath)) {
19578
20337
  throw new Error(`Root router not found at ${rootPath}`);
19579
20338
  }
19580
20339
  const source = readFileSync26(rootPath, "utf-8");
@@ -19589,12 +20348,12 @@ function parseRootRouter() {
19589
20348
  for (const ext of [".ts", ".tsx", ""]) {
19590
20349
  const candidate = fullPath + ext;
19591
20350
  const routersRelPath = getConfig().paths.routers ?? "src/server/api/routers";
19592
- if (existsSync30(candidate)) {
20351
+ if (existsSync31(candidate)) {
19593
20352
  filePath = routersRelPath + "/" + filePath + ext;
19594
20353
  break;
19595
20354
  }
19596
- const indexCandidate = join14(fullPath, "index.ts");
19597
- if (existsSync30(indexCandidate)) {
20355
+ const indexCandidate = join15(fullPath, "index.ts");
20356
+ if (existsSync31(indexCandidate)) {
19598
20357
  filePath = routersRelPath + "/" + filePath + "/index.ts";
19599
20358
  break;
19600
20359
  }
@@ -19614,7 +20373,7 @@ function parseRootRouter() {
19614
20373
  }
19615
20374
  function extractProcedures(routerFilePath) {
19616
20375
  const absPath = resolve26(getProjectRoot(), routerFilePath);
19617
- if (!existsSync30(absPath)) return [];
20376
+ if (!existsSync31(absPath)) return [];
19618
20377
  const source = readFileSync26(absPath, "utf-8");
19619
20378
  const procedures = [];
19620
20379
  const seen = /* @__PURE__ */ new Set();
@@ -19644,7 +20403,7 @@ function findUICallSites(routerKey, procedureName) {
19644
20403
  ];
19645
20404
  const searchPattern = `api.${routerKey}.${procedureName}`;
19646
20405
  for (const dir of searchDirs) {
19647
- if (!existsSync30(dir)) continue;
20406
+ if (!existsSync31(dir)) continue;
19648
20407
  searchDirectory(dir, searchPattern, callSites);
19649
20408
  }
19650
20409
  return callSites;
@@ -19652,7 +20411,7 @@ function findUICallSites(routerKey, procedureName) {
19652
20411
  function searchDirectory(dir, pattern, results) {
19653
20412
  const entries = readdirSync16(dir, { withFileTypes: true });
19654
20413
  for (const entry of entries) {
19655
- const fullPath = join14(dir, entry.name);
20414
+ const fullPath = join15(dir, entry.name);
19656
20415
  if (entry.isDirectory()) {
19657
20416
  if (entry.name === "node_modules" || entry.name === ".next") continue;
19658
20417
  searchDirectory(fullPath, pattern, results);
@@ -19722,7 +20481,7 @@ var init_trpc_index = __esm({
19722
20481
  });
19723
20482
 
19724
20483
  // src/page-deps.ts
19725
- import { readFileSync as readFileSync27, existsSync as existsSync31 } from "fs";
20484
+ import { readFileSync as readFileSync27, existsSync as existsSync32 } from "fs";
19726
20485
  import { resolve as resolve27 } from "path";
19727
20486
  function deriveRoute(pageFile) {
19728
20487
  let route = pageFile.replace(/^src\/app/, "").replace(/\/page\.tsx?$/, "").replace(/\/page\.jsx?$/, "");
@@ -19762,7 +20521,7 @@ function findRouterCalls(files) {
19762
20521
  const projectRoot = getProjectRoot();
19763
20522
  for (const file of files) {
19764
20523
  const absPath = ensureWithinRoot(resolve27(projectRoot, file), projectRoot);
19765
- if (!existsSync31(absPath)) continue;
20524
+ if (!existsSync32(absPath)) continue;
19766
20525
  try {
19767
20526
  const source = readFileSync27(absPath, "utf-8");
19768
20527
  const apiCallRegex = /api\.(\w+)\.\w+/g;
@@ -19783,7 +20542,7 @@ function findTablesFromRouters(routerNames, dataDb) {
19783
20542
  ).all(routerName);
19784
20543
  for (const proc of procs) {
19785
20544
  const absPath = ensureWithinRoot(resolve27(getProjectRoot(), proc.router_file), getProjectRoot());
19786
- if (!existsSync31(absPath)) continue;
20545
+ if (!existsSync32(absPath)) continue;
19787
20546
  try {
19788
20547
  const source = readFileSync27(absPath, "utf-8");
19789
20548
  const dbPattern = getConfig().dbAccessPattern ?? "ctx.db.{table}";
@@ -20057,11 +20816,11 @@ var init_domains = __esm({
20057
20816
  });
20058
20817
 
20059
20818
  // src/schema-mapper.ts
20060
- import { readFileSync as readFileSync28, existsSync as existsSync32, readdirSync as readdirSync17 } from "fs";
20061
- 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";
20062
20821
  function parsePrismaSchema() {
20063
20822
  const schemaPath = getResolvedPaths().prismaSchemaPath;
20064
- if (!existsSync32(schemaPath)) {
20823
+ if (!existsSync33(schemaPath)) {
20065
20824
  throw new Error(`Prisma schema not found at ${schemaPath}`);
20066
20825
  }
20067
20826
  const source = readFileSync28(schemaPath, "utf-8");
@@ -20122,14 +20881,14 @@ function toSnakeCase(str) {
20122
20881
  function findColumnUsageInRouters(tableName) {
20123
20882
  const usage = /* @__PURE__ */ new Map();
20124
20883
  const routersDir = getResolvedPaths().routersDir;
20125
- if (!existsSync32(routersDir)) return usage;
20884
+ if (!existsSync33(routersDir)) return usage;
20126
20885
  scanDirectory(routersDir, tableName, usage);
20127
20886
  return usage;
20128
20887
  }
20129
20888
  function scanDirectory(dir, tableName, usage) {
20130
20889
  const entries = readdirSync17(dir, { withFileTypes: true });
20131
20890
  for (const entry of entries) {
20132
- const fullPath = join15(dir, entry.name);
20891
+ const fullPath = join16(dir, entry.name);
20133
20892
  if (entry.isDirectory()) {
20134
20893
  scanDirectory(fullPath, tableName, usage);
20135
20894
  } else if (entry.name.endsWith(".ts")) {
@@ -20184,10 +20943,10 @@ function detectMismatches(models) {
20184
20943
  }
20185
20944
  function findFilesUsingColumn(dir, column, tableName) {
20186
20945
  const result = [];
20187
- if (!existsSync32(dir)) return result;
20946
+ if (!existsSync33(dir)) return result;
20188
20947
  const entries = readdirSync17(dir, { withFileTypes: true });
20189
20948
  for (const entry of entries) {
20190
- const fullPath = join15(dir, entry.name);
20949
+ const fullPath = join16(dir, entry.name);
20191
20950
  if (entry.isDirectory()) {
20192
20951
  result.push(...findFilesUsingColumn(fullPath, column, tableName));
20193
20952
  } else if (entry.name.endsWith(".ts")) {
@@ -20337,34 +21096,34 @@ var init_import_parser = __esm({
20337
21096
  });
20338
21097
 
20339
21098
  // src/python/import-resolver.ts
20340
- import { readFileSync as readFileSync29, existsSync as existsSync33, readdirSync as readdirSync18 } from "fs";
20341
- 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";
20342
21101
  function resolvePythonModulePath(module, fromFile, pythonRoot, level) {
20343
21102
  const projectRoot = getProjectRoot();
20344
21103
  if (level > 0) {
20345
- let baseDir = dirname16(resolve29(projectRoot, fromFile));
21104
+ let baseDir = dirname17(resolve29(projectRoot, fromFile));
20346
21105
  for (let i = 1; i < level; i++) {
20347
- baseDir = dirname16(baseDir);
21106
+ baseDir = dirname17(baseDir);
20348
21107
  }
20349
21108
  const modulePart = module.replace(/^\.+/, "");
20350
21109
  if (modulePart) {
20351
21110
  const parts2 = modulePart.split(".");
20352
- return tryResolvePythonPath(join16(baseDir, ...parts2), projectRoot);
21111
+ return tryResolvePythonPath(join17(baseDir, ...parts2), projectRoot);
20353
21112
  }
20354
21113
  return tryResolvePythonPath(baseDir, projectRoot);
20355
21114
  }
20356
21115
  const parts = module.split(".");
20357
- const candidate = join16(resolve29(projectRoot, pythonRoot), ...parts);
21116
+ const candidate = join17(resolve29(projectRoot, pythonRoot), ...parts);
20358
21117
  return tryResolvePythonPath(candidate, projectRoot);
20359
21118
  }
20360
21119
  function tryResolvePythonPath(basePath, projectRoot) {
20361
- if (existsSync33(basePath + ".py")) {
21120
+ if (existsSync34(basePath + ".py")) {
20362
21121
  return relative6(projectRoot, basePath + ".py");
20363
21122
  }
20364
- if (existsSync33(join16(basePath, "__init__.py"))) {
20365
- return relative6(projectRoot, join16(basePath, "__init__.py"));
21123
+ if (existsSync34(join17(basePath, "__init__.py"))) {
21124
+ return relative6(projectRoot, join17(basePath, "__init__.py"));
20366
21125
  }
20367
- if (basePath.endsWith(".py") && existsSync33(basePath)) {
21126
+ if (basePath.endsWith(".py") && existsSync34(basePath)) {
20368
21127
  return relative6(projectRoot, basePath);
20369
21128
  }
20370
21129
  return null;
@@ -20376,9 +21135,9 @@ function walkPythonFiles(dir, excludeDirs) {
20376
21135
  for (const entry of entries) {
20377
21136
  if (entry.isDirectory()) {
20378
21137
  if (excludeDirs.includes(entry.name)) continue;
20379
- files.push(...walkPythonFiles(join16(dir, entry.name), excludeDirs));
21138
+ files.push(...walkPythonFiles(join17(dir, entry.name), excludeDirs));
20380
21139
  } else if (entry.name.endsWith(".py")) {
20381
- files.push(join16(dir, entry.name));
21140
+ files.push(join17(dir, entry.name));
20382
21141
  }
20383
21142
  }
20384
21143
  } catch {
@@ -20672,7 +21431,7 @@ var init_route_parser = __esm({
20672
21431
 
20673
21432
  // src/python/route-indexer.ts
20674
21433
  import { readFileSync as readFileSync30, readdirSync as readdirSync19 } from "fs";
20675
- import { join as join17, relative as relative7 } from "path";
21434
+ import { join as join18, relative as relative7 } from "path";
20676
21435
  function walkPyFiles(dir, excludeDirs) {
20677
21436
  const files = [];
20678
21437
  try {
@@ -20680,9 +21439,9 @@ function walkPyFiles(dir, excludeDirs) {
20680
21439
  for (const entry of entries) {
20681
21440
  if (entry.isDirectory()) {
20682
21441
  if (excludeDirs.includes(entry.name)) continue;
20683
- files.push(...walkPyFiles(join17(dir, entry.name), excludeDirs));
21442
+ files.push(...walkPyFiles(join18(dir, entry.name), excludeDirs));
20684
21443
  } else if (entry.name.endsWith(".py")) {
20685
- files.push(join17(dir, entry.name));
21444
+ files.push(join18(dir, entry.name));
20686
21445
  }
20687
21446
  }
20688
21447
  } catch {
@@ -20691,7 +21450,7 @@ function walkPyFiles(dir, excludeDirs) {
20691
21450
  }
20692
21451
  function buildPythonRouteIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]) {
20693
21452
  const projectRoot = getProjectRoot();
20694
- const absRoot = join17(projectRoot, pythonRoot);
21453
+ const absRoot = join18(projectRoot, pythonRoot);
20695
21454
  dataDb.exec(`DELETE FROM ${t("py_routes")}`);
20696
21455
  dataDb.exec(`DELETE FROM ${t("py_route_callers")}`);
20697
21456
  const insertStmt = dataDb.prepare(
@@ -20917,7 +21676,7 @@ var init_model_parser = __esm({
20917
21676
 
20918
21677
  // src/python/model-indexer.ts
20919
21678
  import { readFileSync as readFileSync31, readdirSync as readdirSync20 } from "fs";
20920
- import { join as join18, relative as relative8 } from "path";
21679
+ import { join as join19, relative as relative8 } from "path";
20921
21680
  function walkPyFiles2(dir, excludeDirs) {
20922
21681
  const files = [];
20923
21682
  try {
@@ -20925,9 +21684,9 @@ function walkPyFiles2(dir, excludeDirs) {
20925
21684
  for (const entry of entries) {
20926
21685
  if (entry.isDirectory()) {
20927
21686
  if (excludeDirs.includes(entry.name)) continue;
20928
- files.push(...walkPyFiles2(join18(dir, entry.name), excludeDirs));
21687
+ files.push(...walkPyFiles2(join19(dir, entry.name), excludeDirs));
20929
21688
  } else if (entry.name.endsWith(".py")) {
20930
- files.push(join18(dir, entry.name));
21689
+ files.push(join19(dir, entry.name));
20931
21690
  }
20932
21691
  }
20933
21692
  } catch {
@@ -20936,7 +21695,7 @@ function walkPyFiles2(dir, excludeDirs) {
20936
21695
  }
20937
21696
  function buildPythonModelIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]) {
20938
21697
  const projectRoot = getProjectRoot();
20939
- const absRoot = join18(projectRoot, pythonRoot);
21698
+ const absRoot = join19(projectRoot, pythonRoot);
20940
21699
  dataDb.exec(`DELETE FROM ${t("py_models")}`);
20941
21700
  dataDb.exec(`DELETE FROM ${t("py_fk_edges")}`);
20942
21701
  const insertModel = dataDb.prepare(
@@ -21214,18 +21973,18 @@ var init_migration_parser = __esm({
21214
21973
 
21215
21974
  // src/python/migration-indexer.ts
21216
21975
  import { readFileSync as readFileSync32, readdirSync as readdirSync21 } from "fs";
21217
- import { join as join19, relative as relative9 } from "path";
21976
+ import { join as join20, relative as relative9 } from "path";
21218
21977
  function buildPythonMigrationIndex(dataDb, alembicDir) {
21219
21978
  const projectRoot = getProjectRoot();
21220
- const absDir = join19(projectRoot, alembicDir);
21979
+ const absDir = join20(projectRoot, alembicDir);
21221
21980
  dataDb.exec(`DELETE FROM ${t("py_migrations")}`);
21222
- const versionsDir = join19(absDir, "versions");
21981
+ const versionsDir = join20(absDir, "versions");
21223
21982
  let files = [];
21224
21983
  try {
21225
- 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));
21226
21985
  } catch {
21227
21986
  try {
21228
- 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));
21229
21988
  } catch {
21230
21989
  }
21231
21990
  }
@@ -21275,11 +22034,11 @@ var init_migration_indexer = __esm({
21275
22034
 
21276
22035
  // src/python/coupling-detector.ts
21277
22036
  import { readFileSync as readFileSync33, readdirSync as readdirSync22 } from "fs";
21278
- import { join as join20, relative as relative10 } from "path";
22037
+ import { join as join21, relative as relative10 } from "path";
21279
22038
  function buildPythonCouplingIndex(dataDb) {
21280
22039
  const projectRoot = getProjectRoot();
21281
22040
  const config = getConfig();
21282
- const srcDir = join20(projectRoot, config.paths.source);
22041
+ const srcDir = join21(projectRoot, config.paths.source);
21283
22042
  const routes = dataDb.prepare(`SELECT id, method, path FROM ${t("py_routes")}`).all();
21284
22043
  if (routes.length === 0) return 0;
21285
22044
  dataDb.exec(`DELETE FROM ${t("py_route_callers")}`);
@@ -21343,9 +22102,9 @@ function walkFrontendFiles(dir) {
21343
22102
  for (const entry of entries) {
21344
22103
  if (entry.isDirectory()) {
21345
22104
  if (exclude.includes(entry.name)) continue;
21346
- files.push(...walkFrontendFiles(join20(dir, entry.name)));
22105
+ files.push(...walkFrontendFiles(join21(dir, entry.name)));
21347
22106
  } else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
21348
- files.push(join20(dir, entry.name));
22107
+ files.push(join21(dir, entry.name));
21349
22108
  }
21350
22109
  }
21351
22110
  } catch {
@@ -21717,7 +22476,7 @@ var init_memory_tools = __esm({
21717
22476
  });
21718
22477
 
21719
22478
  // src/docs-tools.ts
21720
- import { readFileSync as readFileSync34, existsSync as existsSync34 } from "fs";
22479
+ import { readFileSync as readFileSync34, existsSync as existsSync35 } from "fs";
21721
22480
  import { resolve as resolve30, basename as basename6 } from "path";
21722
22481
  function p3(baseName) {
21723
22482
  return `${getConfig().toolPrefix}_${baseName}`;
@@ -21773,7 +22532,7 @@ function handleDocsToolCall(name, args2) {
21773
22532
  }
21774
22533
  function loadDocsMap() {
21775
22534
  const mapPath = getResolvedPaths().docsMapPath;
21776
- if (!existsSync34(mapPath)) {
22535
+ if (!existsSync35(mapPath)) {
21777
22536
  throw new Error(`docs-map.json not found at ${mapPath}`);
21778
22537
  }
21779
22538
  return JSON.parse(readFileSync34(mapPath, "utf-8"));
@@ -21847,9 +22606,9 @@ function extractFrontmatter(content) {
21847
22606
  function extractProcedureNames(routerPath) {
21848
22607
  const root = getProjectRoot();
21849
22608
  const absPath = ensureWithinRoot(resolve30(getResolvedPaths().srcDir, "..", routerPath), root);
21850
- if (!existsSync34(absPath)) {
22609
+ if (!existsSync35(absPath)) {
21851
22610
  const altPath = ensureWithinRoot(resolve30(getResolvedPaths().srcDir, "../server/api/routers", basename6(routerPath)), root);
21852
- if (!existsSync34(altPath)) return [];
22611
+ if (!existsSync35(altPath)) return [];
21853
22612
  return extractProcedureNamesFromContent(readFileSync34(altPath, "utf-8"));
21854
22613
  }
21855
22614
  return extractProcedureNamesFromContent(readFileSync34(absPath, "utf-8"));
@@ -21893,7 +22652,7 @@ function handleDocsAudit(args2) {
21893
22652
  const mapping = docsMap.mappings.find((m3) => m3.id === mappingId);
21894
22653
  if (!mapping) continue;
21895
22654
  const helpPagePath = ensureWithinRoot(resolve30(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
21896
- if (!existsSync34(helpPagePath)) {
22655
+ if (!existsSync35(helpPagePath)) {
21897
22656
  results.push({
21898
22657
  helpPage: mapping.helpPage,
21899
22658
  mappingId,
@@ -21946,7 +22705,7 @@ function handleDocsAudit(args2) {
21946
22705
  for (const [guideName, parentId] of Object.entries(docsMap.userGuideInheritance.examples)) {
21947
22706
  if (parentId === mappingId) {
21948
22707
  const guidePath = ensureWithinRoot(resolve30(getResolvedPaths().helpSitePath, `pages/user-guides/${guideName}/index.mdx`), getProjectRoot());
21949
- if (existsSync34(guidePath)) {
22708
+ if (existsSync35(guidePath)) {
21950
22709
  const guideContent = readFileSync34(guidePath, "utf-8");
21951
22710
  const guideFrontmatter = extractFrontmatter(guideContent);
21952
22711
  if (!guideFrontmatter?.lastVerified || status === "STALE") {
@@ -21981,7 +22740,7 @@ function handleDocsCoverage(args2) {
21981
22740
  const mappings = filterDomain ? docsMap.mappings.filter((m3) => m3.id === filterDomain) : docsMap.mappings;
21982
22741
  for (const mapping of mappings) {
21983
22742
  const helpPagePath = ensureWithinRoot(resolve30(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
21984
- const exists = existsSync34(helpPagePath);
22743
+ const exists = existsSync35(helpPagePath);
21985
22744
  let hasContent = false;
21986
22745
  let lineCount = 0;
21987
22746
  let lastVerified = null;
@@ -22328,7 +23087,7 @@ var init_observability_tools = __esm({
22328
23087
  });
22329
23088
 
22330
23089
  // src/sentinel-db.ts
22331
- import { existsSync as existsSync35 } from "fs";
23090
+ import { existsSync as existsSync36 } from "fs";
22332
23091
  import { resolve as resolve31 } from "path";
22333
23092
  function parsePortalScope(raw) {
22334
23093
  if (!raw) return [];
@@ -22566,22 +23325,22 @@ function validateFeatures(db, domainFilter) {
22566
23325
  const missingPages = [];
22567
23326
  for (const comp of components) {
22568
23327
  const absPath = resolve31(PROJECT_ROOT, comp.component_file);
22569
- if (!existsSync35(absPath)) {
23328
+ if (!existsSync36(absPath)) {
22570
23329
  missingComponents.push(comp.component_file);
22571
23330
  }
22572
23331
  }
22573
23332
  for (const proc of procedures) {
22574
23333
  const routerPath = resolve31(PROJECT_ROOT, `src/server/api/routers/${proc.router_name}.ts`);
22575
- if (!existsSync35(routerPath)) {
23334
+ if (!existsSync36(routerPath)) {
22576
23335
  missingProcedures.push({ router: proc.router_name, procedure: proc.procedure_name });
22577
23336
  }
22578
23337
  }
22579
23338
  for (const page of pages) {
22580
23339
  const routeToPath = page.page_route.replace(/^\/(portal-[^/]+\/)?/, "src/app/").replace(/\/$/, "") + "/page.tsx";
22581
23340
  const absPath = resolve31(PROJECT_ROOT, routeToPath);
22582
- if (page.page_route.startsWith("/") && !existsSync35(absPath)) {
23341
+ if (page.page_route.startsWith("/") && !existsSync36(absPath)) {
22583
23342
  const altPath = resolve31(PROJECT_ROOT, `src/app${page.page_route}/page.tsx`);
22584
- if (!existsSync35(altPath)) {
23343
+ if (!existsSync36(altPath)) {
22585
23344
  missingPages.push(page.page_route);
22586
23345
  }
22587
23346
  }
@@ -23107,8 +23866,8 @@ var init_sentinel_tools = __esm({
23107
23866
  });
23108
23867
 
23109
23868
  // src/sentinel-scanner.ts
23110
- import { readFileSync as readFileSync35, existsSync as existsSync36, readdirSync as readdirSync23, statSync as statSync13 } from "fs";
23111
- 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";
23112
23871
  function inferDomain(filePath) {
23113
23872
  const domains = getConfig().domains;
23114
23873
  const path = filePath.toLowerCase();
@@ -23240,7 +23999,7 @@ function scanComponentExports(dataDb) {
23240
23999
  const componentsBase = config.paths.components ?? config.paths.source + "/components";
23241
24000
  const componentDirs = [];
23242
24001
  const basePath = resolve32(projectRoot, componentsBase);
23243
- if (existsSync36(basePath)) {
24002
+ if (existsSync37(basePath)) {
23244
24003
  try {
23245
24004
  const entries = readdirSync23(basePath, { withFileTypes: true });
23246
24005
  for (const entry of entries) {
@@ -23253,7 +24012,7 @@ function scanComponentExports(dataDb) {
23253
24012
  }
23254
24013
  for (const dir of componentDirs) {
23255
24014
  const absDir = resolve32(projectRoot, dir);
23256
- if (!existsSync36(absDir)) continue;
24015
+ if (!existsSync37(absDir)) continue;
23257
24016
  const files = walkDir2(absDir).filter((f2) => f2.endsWith(".tsx") || f2.endsWith(".ts"));
23258
24017
  for (const file of files) {
23259
24018
  const relPath = relative11(projectRoot, file);
@@ -23283,7 +24042,7 @@ function scanComponentExports(dataDb) {
23283
24042
  if (hasHandlers && exportMatch) {
23284
24043
  const componentName = exportMatch[1];
23285
24044
  const domain = inferDomain(relPath);
23286
- const subdomain = basename7(dirname17(relPath));
24045
+ const subdomain = basename7(dirname18(relPath));
23287
24046
  const featureKey = `component.${subdomain}.${componentName.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "")}`;
23288
24047
  if (!annotations.some((a2) => a2.featureKey === featureKey)) {
23289
24048
  features.push({
@@ -23308,7 +24067,7 @@ function walkDir2(dir) {
23308
24067
  try {
23309
24068
  const entries = readdirSync23(dir);
23310
24069
  for (const entry of entries) {
23311
- const fullPath = join21(dir, entry);
24070
+ const fullPath = join22(dir, entry);
23312
24071
  try {
23313
24072
  const stat = statSync13(fullPath);
23314
24073
  if (stat.isDirectory()) {
@@ -24300,7 +25059,7 @@ var init_audit_trail = __esm({
24300
25059
  });
24301
25060
 
24302
25061
  // src/validation-engine.ts
24303
- import { existsSync as existsSync37, readFileSync as readFileSync36 } from "fs";
25062
+ import { existsSync as existsSync38, readFileSync as readFileSync36 } from "fs";
24304
25063
  function p10(baseName) {
24305
25064
  return `${getConfig().toolPrefix}_${baseName}`;
24306
25065
  }
@@ -24331,7 +25090,7 @@ function validateFile(filePath, projectRoot) {
24331
25090
  });
24332
25091
  return checks;
24333
25092
  }
24334
- if (!existsSync37(absPath)) {
25093
+ if (!existsSync38(absPath)) {
24335
25094
  checks.push({
24336
25095
  name: "file_exists",
24337
25096
  severity: "error",
@@ -24786,7 +25545,7 @@ var init_adr_generator = __esm({
24786
25545
  });
24787
25546
 
24788
25547
  // src/security-scorer.ts
24789
- import { existsSync as existsSync38, readFileSync as readFileSync37 } from "fs";
25548
+ import { existsSync as existsSync39, readFileSync as readFileSync37 } from "fs";
24790
25549
  function p12(baseName) {
24791
25550
  return `${getConfig().toolPrefix}_${baseName}`;
24792
25551
  }
@@ -24810,7 +25569,7 @@ function scoreFileSecurity(filePath, projectRoot) {
24810
25569
  }]
24811
25570
  };
24812
25571
  }
24813
- if (!existsSync38(absPath)) {
25572
+ if (!existsSync39(absPath)) {
24814
25573
  return { riskScore: 0, findings: [] };
24815
25574
  }
24816
25575
  let source;
@@ -25105,7 +25864,7 @@ var init_security_scorer = __esm({
25105
25864
  });
25106
25865
 
25107
25866
  // src/dependency-scorer.ts
25108
- import { existsSync as existsSync39, readFileSync as readFileSync38 } from "fs";
25867
+ import { existsSync as existsSync40, readFileSync as readFileSync38 } from "fs";
25109
25868
  import { resolve as resolve33 } from "path";
25110
25869
  function p13(baseName) {
25111
25870
  return `${getConfig().toolPrefix}_${baseName}`;
@@ -25139,7 +25898,7 @@ function calculateDepRisk(factors) {
25139
25898
  }
25140
25899
  function getInstalledPackages(projectRoot) {
25141
25900
  const pkgPath = resolve33(projectRoot, "package.json");
25142
- if (!existsSync39(pkgPath)) return /* @__PURE__ */ new Map();
25901
+ if (!existsSync40(pkgPath)) return /* @__PURE__ */ new Map();
25143
25902
  try {
25144
25903
  const pkg = JSON.parse(readFileSync38(pkgPath, "utf-8"));
25145
25904
  const packages = /* @__PURE__ */ new Map();
@@ -25747,7 +26506,7 @@ var init_regression_detector = __esm({
25747
26506
 
25748
26507
  // src/knowledge-indexer.ts
25749
26508
  import { createHash as createHash9 } from "crypto";
25750
- 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";
25751
26510
  import { resolve as resolve34, relative as relative12, basename as basename8, extname as extname2 } from "path";
25752
26511
  function getKnowledgePaths() {
25753
26512
  const resolved = getResolvedPaths();
@@ -26055,11 +26814,11 @@ function indexAllKnowledge(db) {
26055
26814
  files.push(...memFiles);
26056
26815
  } catch {
26057
26816
  }
26058
- if (existsSync40(paths.plansDir)) {
26817
+ if (existsSync41(paths.plansDir)) {
26059
26818
  const planFiles = discoverMarkdownFiles(paths.plansDir);
26060
26819
  files.push(...planFiles);
26061
26820
  }
26062
- if (existsSync40(paths.docsDir)) {
26821
+ if (existsSync41(paths.docsDir)) {
26063
26822
  const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
26064
26823
  const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f2) => !f2.includes("/plans/") && !excludePatterns.some((p19) => f2.includes(p19)));
26065
26824
  files.push(...docsFiles);
@@ -26102,7 +26861,7 @@ function indexAllKnowledge(db) {
26102
26861
  } catch {
26103
26862
  }
26104
26863
  for (const filePath of files) {
26105
- if (!existsSync40(filePath)) continue;
26864
+ if (!existsSync41(filePath)) continue;
26106
26865
  const content = readFileSync39(filePath, "utf-8");
26107
26866
  const hash = hashContent2(content);
26108
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);
@@ -26238,10 +26997,10 @@ function isKnowledgeStale(db) {
26238
26997
  files.push(...discoverMarkdownFiles(paths.memoryDir));
26239
26998
  } catch {
26240
26999
  }
26241
- if (existsSync40(paths.plansDir)) {
27000
+ if (existsSync41(paths.plansDir)) {
26242
27001
  files.push(...discoverMarkdownFiles(paths.plansDir));
26243
27002
  }
26244
- if (existsSync40(paths.docsDir)) {
27003
+ if (existsSync41(paths.docsDir)) {
26245
27004
  const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
26246
27005
  const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f2) => !f2.includes("/plans/") && !excludePatterns.some((p19) => f2.includes(p19)));
26247
27006
  files.push(...docsFiles);
@@ -26270,7 +27029,7 @@ var init_knowledge_indexer = __esm({
26270
27029
  });
26271
27030
 
26272
27031
  // src/knowledge-tools.ts
26273
- 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";
26274
27033
  import { resolve as resolve35, basename as basename9 } from "path";
26275
27034
  function p16(baseName) {
26276
27035
  return `${getConfig().toolPrefix}_${baseName}`;
@@ -27030,7 +27789,7 @@ ${crRule ? `- **CR**: ${crRule}
27030
27789
  if (archiveIdx > 0) {
27031
27790
  const before = existing.slice(0, archiveIdx);
27032
27791
  const after = existing.slice(archiveIdx);
27033
- writeFileSync5(correctionsPath, before + entry + after);
27792
+ writeFileSync6(correctionsPath, before + entry + after);
27034
27793
  } else {
27035
27794
  appendFileSync2(correctionsPath, entry);
27036
27795
  }
@@ -27978,7 +28737,7 @@ var init_framework_supports = __esm({
27978
28737
  });
27979
28738
 
27980
28739
  // src/tools.ts
27981
- import { readFileSync as readFileSync41, existsSync as existsSync42 } from "fs";
28740
+ import { readFileSync as readFileSync41, existsSync as existsSync43 } from "fs";
27982
28741
  import { resolve as resolve36, basename as basename10 } from "path";
27983
28742
  function prefix2() {
27984
28743
  return getConfig().toolPrefix;
@@ -28398,7 +29157,7 @@ function handleContext(file, dataDb, codegraphDb) {
28398
29157
  const resolvedPaths = getResolvedPaths();
28399
29158
  const root = getProjectRoot();
28400
29159
  const absFilePath = ensureWithinRoot(resolve36(resolvedPaths.srcDir, "..", file), root);
28401
- if (existsSync42(absFilePath)) {
29160
+ if (existsSync43(absFilePath)) {
28402
29161
  const fileContent = readFileSync41(absFilePath, "utf-8").slice(0, 3e3);
28403
29162
  const keywords = [];
28404
29163
  if (fileContent.includes("ctx.db")) keywords.push("database", "schema");
@@ -28837,7 +29596,7 @@ function handleSchema(args2) {
28837
29596
  lines.push("");
28838
29597
  const projectRoot = getProjectRoot();
28839
29598
  const absPath = ensureWithinRoot(resolve36(projectRoot, file), projectRoot);
28840
- if (!existsSync42(absPath)) {
29599
+ if (!existsSync43(absPath)) {
28841
29600
  return text17(`File not found: ${file}`);
28842
29601
  }
28843
29602
  const source = readFileSync41(absPath, "utf-8");
@@ -29246,7 +30005,7 @@ var init_server_dispatch = __esm({
29246
30005
  // src/server.ts
29247
30006
  var server_exports = {};
29248
30007
  import { readFileSync as readFileSync42 } from "fs";
29249
- import { resolve as resolve37, dirname as dirname18 } from "path";
30008
+ import { resolve as resolve37, dirname as dirname19 } from "path";
29250
30009
  import { fileURLToPath as fileURLToPath5 } from "url";
29251
30010
  function pruneMemoryOnStartup() {
29252
30011
  try {
@@ -29278,7 +30037,7 @@ var init_server = __esm({
29278
30037
  init_memory_db();
29279
30038
  init_license();
29280
30039
  init_server_dispatch();
29281
- __dirname5 = dirname18(fileURLToPath5(import.meta.url));
30040
+ __dirname5 = dirname19(fileURLToPath5(import.meta.url));
29282
30041
  PKG_VERSION = (() => {
29283
30042
  try {
29284
30043
  const pkg = JSON.parse(readFileSync42(resolve37(__dirname5, "..", "package.json"), "utf-8"));
@@ -29567,7 +30326,7 @@ var config_upgrade_exports = {};
29567
30326
  __export(config_upgrade_exports, {
29568
30327
  runConfigUpgrade: () => runConfigUpgrade
29569
30328
  });
29570
- 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";
29571
30330
  import { resolve as resolve38 } from "path";
29572
30331
  import { parse as parseYaml6 } from "yaml";
29573
30332
  async function runConfigUpgrade(opts = {}) {
@@ -29579,7 +30338,7 @@ async function runConfigUpgrade(opts = {}) {
29579
30338
  const err = opts.silent ? () => {
29580
30339
  } : (s) => process.stderr.write(s);
29581
30340
  if (opts.rollback) {
29582
- if (!existsSync43(bakPath)) {
30341
+ if (!existsSync44(bakPath)) {
29583
30342
  const message = `No backup found at ${bakPath}`;
29584
30343
  err(message + "\n");
29585
30344
  return { exitCode: 1, action: "none", message };
@@ -29595,7 +30354,7 @@ async function runConfigUpgrade(opts = {}) {
29595
30354
  return { exitCode: 2, action: "none", message };
29596
30355
  }
29597
30356
  }
29598
- if (!existsSync43(configPath)) {
30357
+ if (!existsSync44(configPath)) {
29599
30358
  const message = "massu.config.yaml not found. Run: npx massu init";
29600
30359
  err(message + "\n");
29601
30360
  return { exitCode: 1, action: "none", message };
@@ -29626,7 +30385,7 @@ async function runConfigUpgrade(opts = {}) {
29626
30385
  };
29627
30386
  try {
29628
30387
  const original = readFileSync43(configPath, "utf-8");
29629
- writeFileSync6(bakPath, original, "utf-8");
30388
+ writeFileSync7(bakPath, original, "utf-8");
29630
30389
  } catch (e2) {
29631
30390
  const message = `Failed to write backup: ${e2 instanceof Error ? e2.message : String(e2)}`;
29632
30391
  err(message + "\n");
@@ -29659,7 +30418,7 @@ var config_check_drift_exports = {};
29659
30418
  __export(config_check_drift_exports, {
29660
30419
  runConfigCheckDrift: () => runConfigCheckDrift
29661
30420
  });
29662
- import { existsSync as existsSync44, readFileSync as readFileSync44 } from "fs";
30421
+ import { existsSync as existsSync45, readFileSync as readFileSync44 } from "fs";
29663
30422
  import { resolve as resolve39 } from "path";
29664
30423
  import { parse as parseYaml7 } from "yaml";
29665
30424
  function renderChanges(changes) {
@@ -29673,7 +30432,7 @@ async function runConfigCheckDrift(opts = {}) {
29673
30432
  } : (s) => process.stdout.write(s);
29674
30433
  const err = opts.silent ? () => {
29675
30434
  } : (s) => process.stderr.write(s);
29676
- if (!existsSync44(configPath)) {
30435
+ if (!existsSync45(configPath)) {
29677
30436
  const message = "massu.config.yaml not found. Run: npx massu init";
29678
30437
  err(message + "\n");
29679
30438
  return {
@@ -29755,10 +30514,10 @@ var init_config_check_drift = __esm({
29755
30514
 
29756
30515
  // src/cli.ts
29757
30516
  import { readFileSync as readFileSync45 } from "fs";
29758
- import { resolve as resolve40, dirname as dirname19 } from "path";
30517
+ import { resolve as resolve40, dirname as dirname20 } from "path";
29759
30518
  import { fileURLToPath as fileURLToPath6 } from "url";
29760
30519
  var __filename5 = fileURLToPath6(import.meta.url);
29761
- var __dirname6 = dirname19(__filename5);
30520
+ var __dirname6 = dirname20(__filename5);
29762
30521
  var args = process.argv.slice(2);
29763
30522
  var subcommand = args[0];
29764
30523
  async function main() {
@@ -29795,12 +30554,24 @@ async function main() {
29795
30554
  process.exit(result.exitCode);
29796
30555
  return;
29797
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
+ }
29798
30563
  case "changelog": {
29799
30564
  const { handleChangelogSubcommand: handleChangelogSubcommand2 } = await Promise.resolve().then(() => (init_changelog(), changelog_exports));
29800
30565
  const result = await handleChangelogSubcommand2(args.slice(1));
29801
30566
  process.exit(result.exitCode);
29802
30567
  return;
29803
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
+ }
29804
30575
  case "show-template": {
29805
30576
  const { runShowTemplate: runShowTemplate2 } = await Promise.resolve().then(() => (init_show_template(), show_template_exports));
29806
30577
  await runShowTemplate2(args.slice(1));
@@ -29929,8 +30700,10 @@ Commands:
29929
30700
  validate-config Validate massu.config.yaml (alias: config validate)
29930
30701
  config <sub> Config lifecycle: refresh | validate | upgrade | doctor | check-drift
29931
30702
  permissions <sub> MCP permission lifecycle: install | verify | check-drift
30703
+ license <sub> License/tier gate: check --min <free|pro|team|enterprise>
29932
30704
  changelog <sub> CHANGELOG generation / verification: generate | verify
29933
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>
29934
30707
 
29935
30708
  Options:
29936
30709
  --help, -h Show this help message