@massu/core 2.5.2 → 2.5.4

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.
Files changed (93) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/agents/massu-output-scorer.md +2 -2
  3. package/agents/massu-plan-auditor.md +1 -1
  4. package/commands/_shared-references/test-first-protocol.md +4 -4
  5. package/commands/_shared-references/verification-table.md +5 -5
  6. package/commands/massu-audit-deps.md +2 -2
  7. package/commands/massu-batch.md +3 -3
  8. package/commands/massu-checkpoint.md +7 -7
  9. package/commands/massu-ci-fix.md +1 -1
  10. package/commands/massu-cleanup.md +16 -16
  11. package/commands/massu-commit.md +7 -7
  12. package/commands/massu-create-plan.md +7 -7
  13. package/commands/massu-dead-code.md +3 -3
  14. package/commands/massu-debug/references/common-shortcuts.md +1 -1
  15. package/commands/massu-debug/references/investigation-phases.md +4 -4
  16. package/commands/massu-debug/references/report-format.md +2 -2
  17. package/commands/massu-deploy.md +1 -1
  18. package/commands/massu-deps.md +5 -5
  19. package/commands/massu-doc-gen.md +3 -3
  20. package/commands/massu-docs.md +6 -6
  21. package/commands/massu-estimate.md +3 -3
  22. package/commands/massu-golden-path/references/competitive-mode.md +2 -2
  23. package/commands/massu-golden-path/references/phase-1-plan-creation.md +1 -1
  24. package/commands/massu-golden-path/references/phase-2-implementation.md +2 -2
  25. package/commands/massu-golden-path/references/phase-4-commit.md +3 -3
  26. package/commands/massu-golden-path/references/phase-5-push.md +5 -5
  27. package/commands/massu-guide.md +3 -3
  28. package/commands/massu-hotfix.md +14 -14
  29. package/commands/massu-loop/references/checkpoint-audit.md +3 -3
  30. package/commands/massu-loop/references/iteration-structure.md +3 -3
  31. package/commands/massu-loop/references/loop-controller.md +5 -5
  32. package/commands/massu-loop/references/plan-extraction.md +4 -4
  33. package/commands/massu-loop/references/vr-plan-spec.md +3 -3
  34. package/commands/massu-loop-playwright.md +5 -5
  35. package/commands/massu-loop.md +1 -1
  36. package/commands/massu-new-feature.md +6 -24
  37. package/commands/massu-perf.md +1 -1
  38. package/commands/massu-plan.md +21 -21
  39. package/commands/massu-push-light.md +9 -9
  40. package/commands/massu-recap.md +1 -1
  41. package/commands/massu-refactor.md +8 -8
  42. package/commands/massu-release.md +3 -3
  43. package/commands/massu-review.md +1 -1
  44. package/commands/massu-rollback.md +6 -6
  45. package/commands/massu-scaffold-page.md +1 -1
  46. package/commands/massu-scaffold-router.md +1 -1
  47. package/commands/massu-status.md +9 -9
  48. package/commands/massu-tdd.md +2 -2
  49. package/commands/massu-test.md +13 -13
  50. package/commands/massu-verify-playwright.md +1 -1
  51. package/commands/massu-verify.md +20 -20
  52. package/dist/cli.js +1265 -876
  53. package/dist/hooks/auto-learning-pipeline.js +517 -56
  54. package/dist/hooks/classify-failure.js +495 -47
  55. package/dist/hooks/cost-tracker.js +65 -27
  56. package/dist/hooks/fix-detector.js +496 -47
  57. package/dist/hooks/incident-pipeline.js +72 -28
  58. package/dist/hooks/intent-suggester.js +65 -27
  59. package/dist/hooks/memory-recall.js +65 -27
  60. package/dist/hooks/post-edit-context.js +65 -27
  61. package/dist/hooks/post-tool-use.js +70 -29
  62. package/dist/hooks/pre-compact.js +65 -27
  63. package/dist/hooks/pre-delete-check.js +65 -27
  64. package/dist/hooks/pre-tool-use-gate.js +193 -37
  65. package/dist/hooks/quality-event.js +65 -27
  66. package/dist/hooks/rule-enforcement-pipeline.js +65 -27
  67. package/dist/hooks/security-gate.js +197 -38
  68. package/dist/hooks/session-end.js +68 -27
  69. package/dist/hooks/session-start.js +116 -61
  70. package/dist/hooks/user-prompt.js +65 -27
  71. package/docs/SECURITY.md +1 -1
  72. package/fragments/feature-components.md +15 -0
  73. package/fragments/feature-components.python.md +15 -0
  74. package/fragments/feature-components.typescript.md +15 -0
  75. package/package.json +6 -5
  76. package/patterns/testing-patterns.md +8 -8
  77. package/protocols/plan-implementation.md +2 -2
  78. package/protocols/verification.md +4 -4
  79. package/reference/lessons-learned.md +4 -4
  80. package/reference/vr-verification-reference.md +9 -9
  81. package/templates/aspnet/massu.config.yaml +2 -0
  82. package/templates/go-chi/massu.config.yaml +2 -0
  83. package/templates/multi-runtime/massu.config.yaml +4 -2
  84. package/templates/phoenix/massu.config.yaml +2 -0
  85. package/templates/python-django/massu.config.yaml +3 -1
  86. package/templates/python-fastapi/massu.config.yaml +3 -1
  87. package/templates/python-flask/massu.config.yaml +3 -1
  88. package/templates/rails/massu.config.yaml +4 -2
  89. package/templates/rust-actix/massu.config.yaml +2 -0
  90. package/templates/spring/massu.config.yaml +2 -0
  91. package/templates/swift-ios/massu.config.yaml +3 -1
  92. package/templates/ts-nestjs/massu.config.yaml +2 -0
  93. package/templates/ts-nextjs/massu.config.yaml +2 -0
package/dist/cli.js CHANGED
@@ -743,6 +743,42 @@ var init_memory_path = __esm({
743
743
  }
744
744
  });
745
745
 
746
+ // src/lib/corrections-md.ts
747
+ function formatCorrectionEntry(e2) {
748
+ const one = (s) => s.replace(/\s*\n+\s*/g, " ").trim();
749
+ const lines = [`### ${e2.date} - ${one(e2.title)}`];
750
+ if (e2.wrong) lines.push(`- **Wrong**: ${one(e2.wrong)}`);
751
+ if (e2.correction) lines.push(`- **Correction**: ${one(e2.correction)}`);
752
+ lines.push(`- **Rule**: ${one(e2.rule)}`);
753
+ for (const [k3, v3] of Object.entries(e2.extra ?? {})) {
754
+ lines.push(`- ${k3}: ${one(String(v3))}`);
755
+ }
756
+ return `
757
+ ${lines.join("\n")}
758
+ `;
759
+ }
760
+ function correctionsPath(memoryDir) {
761
+ return memoryDir.endsWith("/") ? `${memoryDir}${CORRECTIONS_FILENAME}` : `${memoryDir}/${CORRECTIONS_FILENAME}`;
762
+ }
763
+ function insertCorrectionEntry(existing, entry) {
764
+ const rendered = formatCorrectionEntry(entry);
765
+ const base = existing.length > 0 ? existing : CORRECTIONS_HEADER;
766
+ const archiveIdx = base.indexOf(ARCHIVE_MARKER);
767
+ if (archiveIdx > 0) {
768
+ return base.slice(0, archiveIdx) + rendered + base.slice(archiveIdx);
769
+ }
770
+ return base + rendered;
771
+ }
772
+ var CORRECTIONS_FILENAME, CORRECTIONS_HEADER, ARCHIVE_MARKER;
773
+ var init_corrections_md = __esm({
774
+ "src/lib/corrections-md.ts"() {
775
+ "use strict";
776
+ CORRECTIONS_FILENAME = "corrections.md";
777
+ CORRECTIONS_HEADER = "# Corrections\n";
778
+ ARCHIVE_MARKER = "## Archived";
779
+ }
780
+ });
781
+
746
782
  // src/config-memory-schema.ts
747
783
  import { z } from "zod";
748
784
  var MemoryConfigSchema;
@@ -776,7 +812,7 @@ var init_config_memory_schema = __esm({
776
812
  message: "memory.recall.embedEndpoint must be an http(s) URL"
777
813
  }).optional(),
778
814
  embedModel: z.string().min(1).optional()
779
- }).default({}),
815
+ }).prefault({}),
780
816
  // --- Contradiction / supersede gate (plan-living-memory-slice-2-temporal-model, P5-001) ---
781
817
  // When a new high-value memory (decision/correction) is written, find
782
818
  // semantically-related existing records and, if the new one contradicts an
@@ -805,7 +841,7 @@ var init_config_memory_schema = __esm({
805
841
  annotateSuperseded: z.boolean().default(false),
806
842
  // Time budget (ms) for the contradiction check; exceeded → fail-open (ADD).
807
843
  budgetMs: z.number().int().positive().default(800)
808
- }).default({}),
844
+ }).prefault({}),
809
845
  // --- Background consolidation, the "sleep-time" pass
810
846
  // (plan-living-memory-slice-3-consolidation) ---
811
847
  // Keeps memory sharp over years: dedupes, distills dying sessions into
@@ -851,7 +887,7 @@ var init_config_memory_schema = __esm({
851
887
  // Surface optional upgrades (e.g. "a local model was detected") in chat.
852
888
  suggestUpgrades: z.boolean().default(true),
853
889
  suggestIntervalDays: z.number().int().positive().default(30)
854
- }).default({}),
890
+ }).prefault({}),
855
891
  // A-20 (Slice 4) — memory FILES: the mirror, and the one switch that decides whether
856
892
  // Massu may ever WRITE into the user's memory directory.
857
893
  //
@@ -876,7 +912,7 @@ var init_config_memory_schema = __esm({
876
912
  // turn of EVERY session, so an unbounded index is a permanent context tax — and the
877
913
  // per-session cap bounds only the RATE, never the total.
878
914
  indexMaxLines: z.number().int().min(1).default(50)
879
- }).default({}),
915
+ }).prefault({}),
880
916
  // --- Cross-repo memory surfacing (plan-living-memory-slice-5-cross-repo-surfacing) ---
881
917
  // A decision made in one of your repos can surface in another — opt-in per
882
918
  // decision AND opt-in per repo, signed, verified, and materialized ONLY on
@@ -901,8 +937,8 @@ var init_config_memory_schema = __esm({
901
937
  enabled: z.boolean().default(true),
902
938
  maxCrossRepoItems: z.number().int().min(0).default(1),
903
939
  minScore: z.number().optional()
904
- }).default({})
905
- }).default({})
940
+ }).prefault({})
941
+ }).prefault({})
906
942
  }).optional();
907
943
  }
908
944
  });
@@ -911,7 +947,11 @@ var init_config_memory_schema = __esm({
911
947
  var config_exports = {};
912
948
  __export(config_exports, {
913
949
  AdapterLocalPathSchema: () => AdapterLocalPathSchema,
950
+ AutoLearningConfigSchema: () => AutoLearningConfigSchema,
951
+ DEFAULT_KNOWLEDGE_SOURCE_FILES: () => DEFAULT_KNOWLEDGE_SOURCE_FILES,
914
952
  LSPConfigSchema: () => LSPConfigSchema,
953
+ RawConfigSchema: () => RawConfigSchema,
954
+ VerificationEntrySchema: () => VerificationEntrySchema,
915
955
  getConfig: () => getConfig,
916
956
  getProjectRoot: () => getProjectRoot,
917
957
  getResolvedApiKeySource: () => getResolvedApiKeySource,
@@ -1101,13 +1141,19 @@ function resetConfig() {
1101
1141
  _projectRoot = null;
1102
1142
  _resolvedApiKeySource = "none";
1103
1143
  }
1104
- var DomainConfigSchema, PatternRuleConfigSchema, CostModelSchema, AnalyticsConfigSchema, CustomPatternSchema, GovernanceConfigSchema, SecurityPatternSchema, SecurityConfigSchema, TeamConfigSchema, RegressionConfigSchema, AutoLearningConfigSchema, CloudConfigSchema, ConventionsConfigSchema, PythonDomainConfigSchema, PythonConfigSchema, PathsConfigSchema, LanguageFrameworkEntrySchema, FrameworkConfigSchema, DetectedConfigSchema, VerificationEntrySchema, VerificationConfigSchema, CanonicalPathsSchema, VerificationTypesSchema, DetectionRuleEntrySchema, DetectionConfigSchema, WatchConfigSchema, AdapterLocalPathSchema, AdaptersConfigSchema, TelemetryConfigSchema, LSPConfigSchema, RawConfigSchema, _config, _resolvedApiKeySource, _projectRoot;
1144
+ var DEFAULT_KNOWLEDGE_SOURCE_FILES, DomainConfigSchema, PatternRuleConfigSchema, CostModelSchema, AnalyticsConfigSchema, CustomPatternSchema, GovernanceConfigSchema, SecurityPatternSchema, SecurityConfigSchema, TeamConfigSchema, RegressionConfigSchema, AutoLearningConfigSchema, CloudConfigSchema, ConventionsConfigSchema, PythonDomainConfigSchema, PythonConfigSchema, PathsConfigSchema, LanguageFrameworkEntrySchema, FrameworkConfigSchema, DetectedConfigSchema, VerificationEntrySchema, VerificationConfigSchema, CanonicalPathsSchema, VerificationTypesSchema, DetectionRuleEntrySchema, DetectionConfigSchema, WatchConfigSchema, AdapterLocalPathSchema, AdaptersConfigSchema, TelemetryConfigSchema, LSPConfigSchema, RawConfigSchema, _config, _resolvedApiKeySource, _projectRoot;
1105
1145
  var init_config = __esm({
1106
1146
  "src/config.ts"() {
1107
1147
  "use strict";
1108
1148
  init_memory_path();
1149
+ init_corrections_md();
1109
1150
  init_credentials();
1110
1151
  init_config_memory_schema();
1152
+ DEFAULT_KNOWLEDGE_SOURCE_FILES = [
1153
+ "CLAUDE.md",
1154
+ "MEMORY.md",
1155
+ CORRECTIONS_FILENAME
1156
+ ];
1111
1157
  DomainConfigSchema = z2.object({
1112
1158
  name: z2.string().default("Unknown"),
1113
1159
  routers: z2.array(z2.string()).default([]),
@@ -1236,34 +1282,40 @@ var init_config = __esm({
1236
1282
  fixDetection: z2.object({
1237
1283
  enabled: z2.boolean().default(true),
1238
1284
  lookbackDays: z2.number().default(7),
1239
- signals: z2.array(z2.string()).default([
1240
- "removed_broken_code",
1241
- "added_error_handling",
1242
- "method_name_correction",
1243
- "auth_fix",
1244
- "nil_handling_fix",
1245
- "concurrency_fix",
1246
- "async_pattern_fix",
1247
- "added_missing_import"
1248
- ])
1249
- }).default({}),
1285
+ // ⛔ NO DEFAULT LIST HERE, DELIBERATELY — the heuristics are the SSOT.
1286
+ //
1287
+ // This carried a hand-typed copy of all eight signal names while
1288
+ // `fix-detector.ts` already read `config…signals ?? FIX_HEURISTICS.map(h => h.name)`.
1289
+ // Because zod ALWAYS materialised the default, that fallback — the correct expression
1290
+ // — was unreachable, and the duplicate was the only thing in effect. Two authoring
1291
+ // sites for one vocabulary, and the drift is SILENT: a name listed here that no longer
1292
+ // exists simply never matches `enabledSignals.has(...)`, so a retired heuristic lingers
1293
+ // as a dead entry with no error. Retiring `method_name_correction` would have landed at
1294
+ // one site of two (CR-74).
1295
+ //
1296
+ // Optional, so an ABSENT value reaches the `??` and the heuristic list defines itself
1297
+ // (Rule 25). An explicit list in massu.config.yaml still overrides, which is the only
1298
+ // behaviour anyone actually configured — measured: `fix-detector.ts:267` is the sole
1299
+ // consumer and no config file in the repo sets it.
1300
+ signals: z2.array(z2.string()).optional()
1301
+ }).prefault({}),
1250
1302
  failureClassification: z2.object({
1251
1303
  enabled: z2.boolean().default(true),
1252
1304
  thresholds: z2.object({
1253
1305
  known: z2.number().default(5),
1254
1306
  similar: z2.number().default(3)
1255
- }).default({}),
1307
+ }).prefault({}),
1256
1308
  scoring: z2.object({
1257
1309
  diffPatternWeight: z2.number().default(3),
1258
1310
  filePatternWeight: z2.number().default(2),
1259
1311
  promptKeywordWeight: z2.number().default(2)
1260
- }).default({})
1261
- }).default({}),
1312
+ }).prefault({})
1313
+ }).prefault({}),
1262
1314
  pipeline: z2.object({
1263
1315
  requireIncidentReport: z2.boolean().default(true),
1264
1316
  requirePreventionRule: z2.boolean().default(true),
1265
1317
  requireEnforcement: z2.boolean().default(true)
1266
- }).default({}),
1318
+ }).prefault({}),
1267
1319
  // plan-v0.2-interactive-rule-approval P-D-008 / P-D-009: project-configured
1268
1320
  // custom destinations for the rule-candidate funnel. The classifier matches
1269
1321
  // a candidate to one of these entries when none of the framework
@@ -1296,7 +1348,7 @@ var init_config = __esm({
1296
1348
  memory: z2.boolean().default(true),
1297
1349
  analytics: z2.boolean().default(true),
1298
1350
  audit: z2.boolean().default(true)
1299
- }).default({ memory: true, analytics: true, audit: true })
1351
+ }).prefault({ memory: true, analytics: true, audit: true })
1300
1352
  }).optional();
1301
1353
  ConventionsConfigSchema = z2.object({
1302
1354
  claudeDirName: z2.string().default(".claude").refine(
@@ -1327,7 +1379,7 @@ var init_config = __esm({
1327
1379
  "session-state",
1328
1380
  "agents"
1329
1381
  ]),
1330
- knowledgeSourceFiles: z2.array(z2.string()).default(["CLAUDE.md", "MEMORY.md", "corrections.md"]),
1382
+ knowledgeSourceFiles: z2.array(z2.string()).default([...DEFAULT_KNOWLEDGE_SOURCE_FILES]),
1331
1383
  excludePatterns: z2.array(z2.string()).default(["/ARCHIVE/", "/SESSION-HISTORY/"])
1332
1384
  }).optional();
1333
1385
  PythonDomainConfigSchema = z2.object({
@@ -1343,6 +1395,23 @@ var init_config = __esm({
1343
1395
  }).optional();
1344
1396
  PathsConfigSchema = z2.object({
1345
1397
  source: z2.string().default("src"),
1398
+ /**
1399
+ * Where this stack's tests live, relative to the project root.
1400
+ *
1401
+ * Added 2026-09-20 for P4.3 of docs/plans/2026-09-20-stack-conformant-asset-rendering.md.
1402
+ * ~25 shipped assets told every customer their tests belong in
1403
+ * `packages/core/src/__tests__/` — massu's OWN layout — and no vocabulary could express
1404
+ * the alternative because none existed. A test directory is a first-class property of
1405
+ * every stack (`tests/` on FastAPI, `test/` on Rails, `src/test/java` on Spring,
1406
+ * `Tests/` on SwiftPM, and Go keeps `_test.go` beside the source), so it belongs here
1407
+ * rather than in prose.
1408
+ *
1409
+ * OPTIONAL, so every config written before this key existed keeps loading unchanged; an
1410
+ * asset that needs it uses `{{paths.tests | default("...")}}`. All 13 shipped templates
1411
+ * declare it, each with the convention it follows recorded at the site (G32 option 2),
1412
+ * and `stack-template-conformance.test.ts` asserts that.
1413
+ */
1414
+ tests: z2.string().optional(),
1346
1415
  aliases: z2.record(z2.string(), z2.string()).default({ "@": "src" }),
1347
1416
  monorepo_roots: z2.array(z2.string()).optional(),
1348
1417
  routers: z2.string().optional(),
@@ -1452,14 +1521,14 @@ var init_config = __esm({
1452
1521
  project: z2.object({
1453
1522
  name: z2.string().default("my-project"),
1454
1523
  root: z2.string().default("auto")
1455
- }).default({ name: "my-project", root: "auto" }),
1456
- framework: FrameworkConfigSchema.default({
1524
+ }).prefault({ name: "my-project", root: "auto" }),
1525
+ framework: FrameworkConfigSchema.prefault({
1457
1526
  type: "typescript",
1458
1527
  router: "none",
1459
1528
  orm: "none",
1460
1529
  ui: "none"
1461
1530
  }),
1462
- paths: PathsConfigSchema.default({ source: "src", aliases: { "@": "src" } }),
1531
+ paths: PathsConfigSchema.prefault({ source: "src", aliases: { "@": "src" } }),
1463
1532
  toolPrefix: z2.string().default("massu"),
1464
1533
  dbAccessPattern: z2.string().optional(),
1465
1534
  knownMismatches: z2.record(z2.string(), z2.record(z2.string(), z2.string())).optional(),
@@ -4508,8 +4577,188 @@ var init_memory_file_ingest = __esm({
4508
4577
  }
4509
4578
  });
4510
4579
 
4580
+ // src/shipped-template-schema.ts
4581
+ import { z as z3 } from "zod";
4582
+ function validateShippedTemplate(raw, stack) {
4583
+ const result = ShippedTemplateConfigSchema.safeParse(raw);
4584
+ if (result.success) return { ok: true, stack, issues: [] };
4585
+ return {
4586
+ ok: false,
4587
+ stack,
4588
+ issues: result.error.issues.map((issue) => {
4589
+ const path = issue.path.length > 0 ? issue.path.join(".") : "(root)";
4590
+ return `${stack}: ${path}: ${issue.message}`;
4591
+ })
4592
+ };
4593
+ }
4594
+ var REQUIRED_TEMPLATE_VERIFY_KEYS, ShippedTemplateConfigSchema;
4595
+ var init_shipped_template_schema = __esm({
4596
+ "src/shipped-template-schema.ts"() {
4597
+ "use strict";
4598
+ init_config();
4599
+ REQUIRED_TEMPLATE_VERIFY_KEYS = ["test", "type", "lint"];
4600
+ ShippedTemplateConfigSchema = RawConfigSchema.extend({
4601
+ // Optional for a customer; MANDATORY for anything massu ships.
4602
+ verification: z3.record(z3.string(), VerificationEntrySchema)
4603
+ }).superRefine((cfg, ctx) => {
4604
+ const framework = cfg.framework;
4605
+ const primary = framework?.primary ?? framework?.type;
4606
+ if (typeof primary !== "string" || primary.length === 0) {
4607
+ ctx.addIssue({
4608
+ code: z3.ZodIssueCode.custom,
4609
+ path: ["framework", "primary"],
4610
+ message: "a shipped template must resolve a primary language (framework.primary or framework.type)"
4611
+ });
4612
+ return;
4613
+ }
4614
+ const primaryEntry = cfg.verification?.[primary];
4615
+ if (primaryEntry === void 0) {
4616
+ ctx.addIssue({
4617
+ code: z3.ZodIssueCode.custom,
4618
+ path: ["verification", primary],
4619
+ message: `a shipped template must declare verification commands for its primary language '${primary}'`
4620
+ });
4621
+ return;
4622
+ }
4623
+ for (const required of REQUIRED_TEMPLATE_VERIFY_KEYS) {
4624
+ const value = primaryEntry[required];
4625
+ if (typeof value !== "string" || value.length === 0) {
4626
+ ctx.addIssue({
4627
+ code: z3.ZodIssueCode.custom,
4628
+ path: ["verification", primary, required],
4629
+ message: `a shipped template must declare a non-empty '${required}' command for '${primary}'`
4630
+ });
4631
+ }
4632
+ }
4633
+ });
4634
+ }
4635
+ });
4636
+
4637
+ // src/commands/asset-resolution.ts
4638
+ import { existsSync as existsSync11, readdirSync as readdirSync4, statSync as statSync3 } from "fs";
4639
+ import { resolve as resolve5, dirname as dirname8 } from "path";
4640
+ import { fileURLToPath as fileURLToPath2 } from "url";
4641
+ function dirHoldsAssets(dir) {
4642
+ if (!existsSync11(dir)) return false;
4643
+ try {
4644
+ return readdirSync4(dir).some(
4645
+ (e2) => e2.endsWith(".md") || statSync3(resolve5(dir, e2)).isDirectory()
4646
+ );
4647
+ } catch {
4648
+ return false;
4649
+ }
4650
+ }
4651
+ function resolveAssetDir(assetName) {
4652
+ const cwd = process.cwd();
4653
+ const candidates = [
4654
+ // 1. npm-installed: node_modules/@massu/core/{assetName}
4655
+ resolve5(cwd, "node_modules/@massu/core", assetName),
4656
+ // 2. Relative to compiled dist/cli.js → ../{assetName}
4657
+ resolve5(__dirname, "..", assetName),
4658
+ // 3. Relative to source src/commands/ → ../../{assetName}
4659
+ resolve5(__dirname, "../..", assetName)
4660
+ ];
4661
+ for (const c2 of candidates) {
4662
+ if (dirHoldsAssets(c2)) return c2;
4663
+ }
4664
+ return null;
4665
+ }
4666
+ function pickVariant(baseName, sourceDir, framework) {
4667
+ const candidates = [];
4668
+ const seenLangs = /* @__PURE__ */ new Set();
4669
+ function pushCandidate(lang, sub) {
4670
+ if (seenLangs.has(lang)) return;
4671
+ seenLangs.add(lang);
4672
+ candidates.push({ lang, subFramework: sub && sub.length > 0 ? sub : void 0 });
4673
+ }
4674
+ const primary = framework.primary ?? framework.type;
4675
+ if (primary && primary !== "multi") {
4676
+ let primarySub;
4677
+ if (framework.languages && framework.languages[primary]?.framework) {
4678
+ primarySub = framework.languages[primary].framework;
4679
+ } else {
4680
+ const passthrough2 = framework;
4681
+ const block = passthrough2[primary];
4682
+ if (block && typeof block === "object") {
4683
+ const fw = block.framework;
4684
+ if (typeof fw === "string" && fw.length > 0) primarySub = fw;
4685
+ }
4686
+ }
4687
+ pushCandidate(primary, primarySub);
4688
+ }
4689
+ if (framework.languages) {
4690
+ for (const lang of Object.keys(framework.languages)) {
4691
+ const entry = framework.languages[lang];
4692
+ if (entry && typeof entry.framework === "string" && entry.framework.length > 0) {
4693
+ pushCandidate(lang, entry.framework);
4694
+ }
4695
+ }
4696
+ }
4697
+ const passthrough = framework;
4698
+ for (const lang of PASSTHROUGH_LANG_KEYS) {
4699
+ if (seenLangs.has(lang)) continue;
4700
+ const block = passthrough[lang];
4701
+ if (block && typeof block === "object") {
4702
+ const fw = block.framework;
4703
+ if (typeof fw === "string" && fw.length > 0) {
4704
+ pushCandidate(lang, fw);
4705
+ }
4706
+ }
4707
+ }
4708
+ for (const cand of candidates) {
4709
+ if (cand.subFramework) {
4710
+ const subPath = resolve5(sourceDir, `${baseName}.${cand.lang}-${cand.subFramework}.md`);
4711
+ if (existsSync11(subPath)) {
4712
+ return { kind: "hit", suffix: `.${cand.lang}-${cand.subFramework}` };
4713
+ }
4714
+ }
4715
+ const langPath = resolve5(sourceDir, `${baseName}.${cand.lang}.md`);
4716
+ if (existsSync11(langPath)) {
4717
+ return { kind: "hit", suffix: `.${cand.lang}` };
4718
+ }
4719
+ }
4720
+ const defaultPath = resolve5(sourceDir, `${baseName}.md`);
4721
+ if (existsSync11(defaultPath)) {
4722
+ return { kind: "hit", suffix: "" };
4723
+ }
4724
+ if (framework.type === "multi" && !framework.primary) {
4725
+ process.stderr.write(
4726
+ "massu: warning - framework.type=multi but framework.primary is undefined; falling back to default templates\n"
4727
+ );
4728
+ return { kind: "fallback", reason: "multi-without-primary" };
4729
+ }
4730
+ return { kind: "miss" };
4731
+ }
4732
+ function isVariantFilename(entry) {
4733
+ return /^[^.]+\.[^.]+\.md$/.test(entry);
4734
+ }
4735
+ function variantFilename(baseName, choice) {
4736
+ if (choice.kind === "miss") return null;
4737
+ const suffix = choice.kind === "hit" ? choice.suffix : "";
4738
+ return suffix === "" ? `${baseName}.md` : `${baseName}${suffix}.md`;
4739
+ }
4740
+ var __filename, __dirname, PASSTHROUGH_LANG_KEYS;
4741
+ var init_asset_resolution = __esm({
4742
+ "src/commands/asset-resolution.ts"() {
4743
+ "use strict";
4744
+ __filename = fileURLToPath2(import.meta.url);
4745
+ __dirname = dirname8(__filename);
4746
+ PASSTHROUGH_LANG_KEYS = [
4747
+ "typescript",
4748
+ "javascript",
4749
+ "python",
4750
+ "swift",
4751
+ "rust",
4752
+ "go"
4753
+ ];
4754
+ }
4755
+ });
4756
+
4511
4757
  // src/commands/template-engine.ts
4512
- function renderTemplate(template, vars) {
4758
+ function fragmentDiagnostic(name) {
4759
+ return `<!-- massu: no fragment "${name}" for this stack, and no default -->`;
4760
+ }
4761
+ function renderTemplate(template, vars, opts = {}) {
4513
4762
  const out = [];
4514
4763
  const len = template.length;
4515
4764
  let i = 0;
@@ -4527,6 +4776,18 @@ function renderTemplate(template, vars) {
4527
4776
  throw new TemplateParseError("unclosed `{{` (no matching `}}`)", tokenStart);
4528
4777
  }
4529
4778
  const inner = template.slice(i + 2, closeIdx);
4779
+ const fragmentName = matchFragmentToken(inner);
4780
+ if (fragmentName !== null) {
4781
+ const body = opts.resolveFragment ? opts.resolveFragment(fragmentName) : null;
4782
+ if (body === null) {
4783
+ out.push(fragmentDiagnostic(fragmentName));
4784
+ } else {
4785
+ if (matchFragmentAnywhere(body)) throw new FragmentNestingError(fragmentName);
4786
+ out.push(renderTemplate(body, vars, {}));
4787
+ }
4788
+ i = closeIdx + 2;
4789
+ continue;
4790
+ }
4530
4791
  if (isJsxPassThrough(inner)) {
4531
4792
  out.push("{{");
4532
4793
  out.push(inner);
@@ -4663,7 +4924,14 @@ function stringify(value) {
4663
4924
  if (typeof value === "string") return value;
4664
4925
  return String(value);
4665
4926
  }
4666
- var MissingVariableError, TemplateParseError;
4927
+ function matchFragmentToken(inner) {
4928
+ const m3 = /^>\s*([A-Za-z0-9][A-Za-z0-9_-]*)\s*$/.exec(inner);
4929
+ return m3 ? m3[1] : null;
4930
+ }
4931
+ function matchFragmentAnywhere(body) {
4932
+ return /\{\{\s*>\s*[A-Za-z0-9]/.test(body);
4933
+ }
4934
+ var MissingVariableError, TemplateParseError, FragmentNestingError;
4667
4935
  var init_template_engine = __esm({
4668
4936
  "src/commands/template-engine.ts"() {
4669
4937
  "use strict";
@@ -4683,22 +4951,264 @@ var init_template_engine = __esm({
4683
4951
  this.position = position;
4684
4952
  }
4685
4953
  };
4954
+ FragmentNestingError = class extends Error {
4955
+ constructor(fragmentName) {
4956
+ super(
4957
+ `fragment "${fragmentName}" contains a nested {{> \u2026 }}. Fragments may not include other fragments: one level keeps resolution total and removes cycles by construction.`
4958
+ );
4959
+ this.fragmentName = fragmentName;
4960
+ this.name = "FragmentNestingError";
4961
+ }
4962
+ fragmentName;
4963
+ };
4964
+ }
4965
+ });
4966
+
4967
+ // src/commands/fragments.ts
4968
+ import { existsSync as existsSync12, readFileSync as readFileSync7 } from "fs";
4969
+ import { resolve as resolve6 } from "path";
4970
+ function makeFragmentResolver(framework, stats) {
4971
+ const fragmentsDir = resolveAssetDir("fragments");
4972
+ return (name) => {
4973
+ if (!fragmentsDir) {
4974
+ stats.missing++;
4975
+ return null;
4976
+ }
4977
+ const choice = pickVariant(name, fragmentsDir, framework);
4978
+ if (choice.kind !== "hit") {
4979
+ stats.missing++;
4980
+ return null;
4981
+ }
4982
+ const file = resolve6(fragmentsDir, `${name}${choice.suffix}.md`);
4983
+ if (!existsSync12(file)) {
4984
+ stats.missing++;
4985
+ return null;
4986
+ }
4987
+ if (choice.suffix === "") stats.defaulted++;
4988
+ else stats.resolved++;
4989
+ return readFileSync7(file, "utf-8");
4990
+ };
4991
+ }
4992
+ var init_fragments = __esm({
4993
+ "src/commands/fragments.ts"() {
4994
+ "use strict";
4995
+ init_asset_resolution();
4996
+ }
4997
+ });
4998
+
4999
+ // src/stack-capabilities.ts
5000
+ function stackLabel(config) {
5001
+ const name = config.project?.name;
5002
+ if (typeof name === "string" && name.length > 0 && name !== "{{PROJECT_NAME}}") return name;
5003
+ return config.framework?.primary ?? config.framework?.type ?? "<unnamed stack>";
5004
+ }
5005
+ function isVerifyKey(candidate) {
5006
+ return ALL_VERIFY_KEYS.includes(candidate);
5007
+ }
5008
+ function resolveStackCapabilities(config) {
5009
+ const label = stackLabel(config);
5010
+ const primaryLanguage = config.framework?.primary ?? config.framework?.type;
5011
+ if (typeof primaryLanguage !== "string" || primaryLanguage.length === 0) {
5012
+ throw new StackContractError(
5013
+ label,
5014
+ `${label}: cannot resolve a primary language \u2014 neither framework.primary nor framework.type is set.`
5015
+ );
5016
+ }
5017
+ const verification = config.verification;
5018
+ if (verification === void 0 || verification === null) {
5019
+ throw new StackContractError(
5020
+ label,
5021
+ `${label}: no top-level 'verification:' block. Every stack must declare ${REQUIRED_VERIFY_KEYS.join("/")} for its primary language (${primaryLanguage}).`
5022
+ );
5023
+ }
5024
+ const roster = /* @__PURE__ */ new Set([primaryLanguage]);
5025
+ for (const lang of Object.keys(config.framework?.languages ?? {})) roster.add(lang);
5026
+ for (const lang of Object.keys(verification)) roster.add(lang);
5027
+ const verifyByLang = {};
5028
+ for (const lang of roster) {
5029
+ const entry = verification[lang];
5030
+ if (!entry) continue;
5031
+ const resolved = {};
5032
+ for (const [candidate, value] of Object.entries(entry)) {
5033
+ if (!isVerifyKey(candidate)) continue;
5034
+ if (typeof value === "string" && value.length > 0) resolved[candidate] = value;
5035
+ }
5036
+ if (Object.keys(resolved).length > 0) verifyByLang[lang] = resolved;
5037
+ }
5038
+ const primaryEntry = verifyByLang[primaryLanguage];
5039
+ if (primaryEntry === void 0) {
5040
+ throw new StackContractError(
5041
+ label,
5042
+ `${label}: 'verification:' declares no commands for the primary language '${primaryLanguage}'. Declared languages: ${Object.keys(verifyByLang).join(", ") || "(none)"}.`
5043
+ );
5044
+ }
5045
+ for (const required of REQUIRED_VERIFY_KEYS) {
5046
+ if (primaryEntry[required] === void 0) {
5047
+ throw new StackContractError(
5048
+ label,
5049
+ `${label}: required verification key '${required}' is not declared for the primary language '${primaryLanguage}'. Required: ${REQUIRED_VERIFY_KEYS.join(", ")}.`,
5050
+ required
5051
+ );
5052
+ }
5053
+ }
5054
+ const verify = {};
5055
+ for (const declaredKey of ALL_VERIFY_KEYS) verify[declaredKey] = primaryEntry[declaredKey];
5056
+ const declared = new Set(
5057
+ ALL_VERIFY_KEYS.filter((candidate) => primaryEntry[candidate] !== void 0)
5058
+ );
5059
+ const languages = [
5060
+ primaryLanguage,
5061
+ ...[...roster].filter((lang) => lang !== primaryLanguage).sort()
5062
+ ];
5063
+ return {
5064
+ contractVersion: STACK_CONTRACT_VERSION,
5065
+ primaryLanguage,
5066
+ languages,
5067
+ verify,
5068
+ verifyByLang,
5069
+ paths: config.paths,
5070
+ declared
5071
+ };
5072
+ }
5073
+ var STACK_CONTRACT_VERSION, REQUIRED_VERIFY_KEYS, OPTIONAL_VERIFY_KEYS, ALL_VERIFY_KEYS, StackContractError;
5074
+ var init_stack_capabilities = __esm({
5075
+ "src/stack-capabilities.ts"() {
5076
+ "use strict";
5077
+ STACK_CONTRACT_VERSION = 1;
5078
+ REQUIRED_VERIFY_KEYS = ["test", "type", "lint"];
5079
+ OPTIONAL_VERIFY_KEYS = ["build", "syntax"];
5080
+ ALL_VERIFY_KEYS = [
5081
+ ...REQUIRED_VERIFY_KEYS,
5082
+ ...OPTIONAL_VERIFY_KEYS
5083
+ ];
5084
+ StackContractError = class extends Error {
5085
+ stackName;
5086
+ verifyKey;
5087
+ constructor(stackName, message, verifyKey) {
5088
+ super(message);
5089
+ this.name = "StackContractError";
5090
+ this.stackName = stackName;
5091
+ this.verifyKey = verifyKey;
5092
+ }
5093
+ };
5094
+ }
5095
+ });
5096
+
5097
+ // src/commands/asset-render.ts
5098
+ import { readFileSync as readFileSync8 } from "fs";
5099
+ import { resolve as resolve7 } from "path";
5100
+ function buildTemplateVars() {
5101
+ const config = getConfig();
5102
+ let stack;
5103
+ let verifyVars;
5104
+ try {
5105
+ const caps = resolveStackCapabilities(config);
5106
+ stack = caps;
5107
+ verifyVars = { verify: caps.verify, verifyByLang: caps.verifyByLang };
5108
+ } catch (err) {
5109
+ if (!(err instanceof StackContractError)) throw err;
5110
+ stack = {
5111
+ contractVersion: STACK_CONTRACT_VERSION,
5112
+ satisfied: false,
5113
+ reason: err.message,
5114
+ stackName: err.stackName,
5115
+ unsatisfiedKey: err.verifyKey
5116
+ };
5117
+ process.stderr.write(
5118
+ `massu: stack capability contract UNSATISFIED \u2014 ${err.message}
5119
+ massu: assets that need a verification command will be reported by \`massu doctor\`; add a \`verification:\` block to massu.config.yaml to resolve them.
5120
+ `
5121
+ );
5122
+ verifyVars = {};
5123
+ }
5124
+ return {
5125
+ framework: config.framework,
5126
+ paths: config.paths,
5127
+ detected: config.detected ?? {},
5128
+ config,
5129
+ /**
5130
+ * Present only when the contract is satisfied. Absent is deliberate: an asset that
5131
+ * needs a command it cannot get must fail ITS OWN file loudly rather than render an
5132
+ * empty string, which is a check that runs nothing and exits 0.
5133
+ */
5134
+ ...verifyVars,
5135
+ /** Always present, satisfied or not, so a consumer can ask WHICH state it is in. */
5136
+ stack,
5137
+ // P-H006 (plan-stage-c-high-batch): RESERVED CLAUDE CODE PLACEHOLDER.
5138
+ // Claude Code reads `{{ARGUMENTS}}` as a runtime placeholder inside
5139
+ // slash-command files. The Massu template engine has no native concept
5140
+ // of reserved literals; we model the placeholder as a variable whose
5141
+ // value IS the literal `{{ARGUMENTS}}` string. Because the engine never
5142
+ // re-renders output, this passes through verbatim. Closes the bug class
5143
+ // where `/massu-article-review`, `/massu-autoresearch`, etc. silently
5144
+ // failed to install because the engine threw MissingVariableError on
5145
+ // their {{ARGUMENTS}} usage.
5146
+ ARGUMENTS: "{{ARGUMENTS}}"
5147
+ };
5148
+ }
5149
+ function makeRenderContext(framework) {
5150
+ const fragments = { resolved: 0, defaulted: 0, missing: 0 };
5151
+ return {
5152
+ vars: buildTemplateVars(),
5153
+ resolveFragment: makeFragmentResolver(framework, fragments),
5154
+ fragments
5155
+ };
5156
+ }
5157
+ function renderResolvedAsset(baseName, sourceDir, framework, ctx) {
5158
+ const choice = pickVariant(baseName, sourceDir, framework);
5159
+ const sourceFilename = variantFilename(baseName, choice);
5160
+ if (sourceFilename === null) return { kind: "miss" };
5161
+ return renderAssetFile(resolve7(sourceDir, sourceFilename), sourceFilename, ctx);
5162
+ }
5163
+ function renderAssetFile(sourcePath, sourceFilename, ctx) {
5164
+ let rawContent;
5165
+ try {
5166
+ rawContent = readFileSync8(sourcePath, "utf-8");
5167
+ } catch (err) {
5168
+ return {
5169
+ kind: "error",
5170
+ sourcePath,
5171
+ message: err instanceof Error ? err.message : String(err)
5172
+ };
5173
+ }
5174
+ try {
5175
+ return {
5176
+ kind: "ok",
5177
+ sourcePath,
5178
+ sourceFilename,
5179
+ content: renderTemplate(rawContent, ctx.vars, { resolveFragment: ctx.resolveFragment })
5180
+ };
5181
+ } catch (err) {
5182
+ if (err instanceof MissingVariableError || err instanceof TemplateParseError) {
5183
+ return { kind: "error", sourcePath, message: err.message };
5184
+ }
5185
+ throw err;
5186
+ }
5187
+ }
5188
+ var init_asset_render = __esm({
5189
+ "src/commands/asset-render.ts"() {
5190
+ "use strict";
5191
+ init_config();
5192
+ init_template_engine();
5193
+ init_fragments();
5194
+ init_asset_resolution();
5195
+ init_stack_capabilities();
4686
5196
  }
4687
5197
  });
4688
5198
 
4689
5199
  // src/lib/settings-local.ts
4690
5200
  import {
4691
5201
  closeSync as closeSync2,
4692
- existsSync as existsSync11,
5202
+ existsSync as existsSync13,
4693
5203
  fsyncSync as fsyncSync2,
4694
5204
  mkdirSync as mkdirSync7,
4695
5205
  openSync as openSync2,
4696
- readFileSync as readFileSync7,
5206
+ readFileSync as readFileSync9,
4697
5207
  renameSync,
4698
5208
  rmSync as rmSync5,
4699
5209
  writeSync
4700
5210
  } from "fs";
4701
- import { dirname as dirname8, resolve as resolve5 } from "path";
5211
+ import { dirname as dirname9, resolve as resolve8 } from "path";
4702
5212
  function atomicWriteFile(targetPath, content, mode = 420) {
4703
5213
  const tmpPath = `${targetPath}.${process.pid}.tmp`;
4704
5214
  try {
@@ -4712,7 +5222,7 @@ function atomicWriteFile(targetPath, content, mode = 420) {
4712
5222
  }
4713
5223
  renameSync(tmpPath, targetPath);
4714
5224
  } catch (err) {
4715
- if (existsSync11(tmpPath)) {
5225
+ if (existsSync13(tmpPath)) {
4716
5226
  try {
4717
5227
  rmSync5(tmpPath, { force: true });
4718
5228
  } catch {
@@ -4722,11 +5232,11 @@ function atomicWriteFile(targetPath, content, mode = 420) {
4722
5232
  }
4723
5233
  }
4724
5234
  function readSettingsAtPath(absolutePath) {
4725
- if (!existsSync11(absolutePath)) {
5235
+ if (!existsSync13(absolutePath)) {
4726
5236
  return {};
4727
5237
  }
4728
5238
  try {
4729
- const raw = readFileSync7(absolutePath, "utf-8");
5239
+ const raw = readFileSync9(absolutePath, "utf-8");
4730
5240
  const parsed = JSON.parse(raw);
4731
5241
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
4732
5242
  return {};
@@ -4737,12 +5247,12 @@ function readSettingsAtPath(absolutePath) {
4737
5247
  }
4738
5248
  }
4739
5249
  function readSettingsLocal(claudeDir) {
4740
- return readSettingsAtPath(resolve5(claudeDir, "settings.local.json"));
5250
+ return readSettingsAtPath(resolve8(claudeDir, "settings.local.json"));
4741
5251
  }
4742
5252
  function writeSettingsLocalAtomic(claudeDir, settings) {
4743
- const targetPath = resolve5(claudeDir, "settings.local.json");
4744
- const dir = dirname8(targetPath);
4745
- if (!existsSync11(dir)) {
5253
+ const targetPath = resolve8(claudeDir, "settings.local.json");
5254
+ const dir = dirname9(targetPath);
5255
+ if (!existsSync13(dir)) {
4746
5256
  mkdirSync7(dir, { recursive: true });
4747
5257
  }
4748
5258
  const content = JSON.stringify(settings, null, 2) + "\n";
@@ -4967,7 +5477,6 @@ var init_permissions = __esm({
4967
5477
  var install_commands_exports = {};
4968
5478
  __export(install_commands_exports, {
4969
5479
  MASSU_OWNED_PATHS: () => MASSU_OWNED_PATHS,
4970
- buildTemplateVars: () => buildTemplateVars,
4971
5480
  canonicalizeOwned: () => canonicalizeOwned,
4972
5481
  hashContent: () => hashContent,
4973
5482
  installAll: () => installAll,
@@ -4975,52 +5484,20 @@ __export(install_commands_exports, {
4975
5484
  isMassuOwned: () => isMassuOwned,
4976
5485
  loadManifest: () => loadManifest,
4977
5486
  massuOwnedSourceIsValid: () => massuOwnedSourceIsValid,
4978
- pickVariant: () => pickVariant,
4979
- resolveAssetDir: () => resolveAssetDir,
4980
- resolveCommandsDir: () => resolveCommandsDir,
4981
5487
  runInstallCommands: () => runInstallCommands,
4982
5488
  runWithManifest: () => runWithManifest,
4983
5489
  saveManifest: () => saveManifest,
4984
5490
  syncDirectory: () => syncDirectory
4985
5491
  });
4986
5492
  import {
4987
- existsSync as existsSync12,
4988
- readFileSync as readFileSync8,
5493
+ existsSync as existsSync14,
5494
+ readFileSync as readFileSync10,
4989
5495
  mkdirSync as mkdirSync8,
4990
- readdirSync as readdirSync4,
4991
- statSync as statSync3
5496
+ readdirSync as readdirSync5,
5497
+ statSync as statSync4
4992
5498
  } from "fs";
4993
- import { resolve as resolve6, dirname as dirname9, join as join8 } from "path";
4994
- import { fileURLToPath as fileURLToPath2 } from "url";
5499
+ import { resolve as resolve9, join as join8 } from "path";
4995
5500
  import { createHash as createHash3 } from "crypto";
4996
- function dirHoldsAssets(dir) {
4997
- if (!existsSync12(dir)) return false;
4998
- try {
4999
- return readdirSync4(dir).some(
5000
- (e2) => e2.endsWith(".md") || statSync3(resolve6(dir, e2)).isDirectory()
5001
- );
5002
- } catch {
5003
- return false;
5004
- }
5005
- }
5006
- function resolveAssetDir(assetName) {
5007
- const cwd = process.cwd();
5008
- const candidates = [
5009
- // 1. npm-installed: node_modules/@massu/core/{assetName}
5010
- resolve6(cwd, "node_modules/@massu/core", assetName),
5011
- // 2. Relative to compiled dist/cli.js → ../{assetName}
5012
- resolve6(__dirname, "..", assetName),
5013
- // 3. Relative to source src/commands/ → ../../{assetName}
5014
- resolve6(__dirname, "../..", assetName)
5015
- ];
5016
- for (const c2 of candidates) {
5017
- if (dirHoldsAssets(c2)) return c2;
5018
- }
5019
- return null;
5020
- }
5021
- function resolveCommandsDir() {
5022
- return resolveAssetDir("commands");
5023
- }
5024
5501
  function isMassuOwned(manifestKey) {
5025
5502
  return MASSU_OWNED_PATHS.includes(manifestKey);
5026
5503
  }
@@ -5029,10 +5506,10 @@ function canonicalizeOwned(content) {
5029
5506
  }
5030
5507
  function massuOwnedSourceIsValid(resolvedSourcePath, content) {
5031
5508
  const sidecar = `${resolvedSourcePath}.sha256`;
5032
- if (!existsSync12(sidecar)) return false;
5509
+ if (!existsSync14(sidecar)) return false;
5033
5510
  let expected;
5034
5511
  try {
5035
- expected = readFileSync8(sidecar, "utf-8").trim();
5512
+ expected = readFileSync10(sidecar, "utf-8").trim();
5036
5513
  } catch {
5037
5514
  return false;
5038
5515
  }
@@ -5042,12 +5519,12 @@ function hashContent(content) {
5042
5519
  return createHash3("sha256").update(content, "utf-8").digest("hex");
5043
5520
  }
5044
5521
  function loadManifest(claudeDir) {
5045
- const path = resolve6(claudeDir, MANIFEST_RELPATH);
5046
- if (!existsSync12(path)) {
5522
+ const path = resolve9(claudeDir, MANIFEST_RELPATH);
5523
+ if (!existsSync14(path)) {
5047
5524
  return emptyManifest();
5048
5525
  }
5049
5526
  try {
5050
- const raw = readFileSync8(path, "utf-8");
5527
+ const raw = readFileSync10(path, "utf-8");
5051
5528
  const parsed = JSON.parse(raw);
5052
5529
  if (!parsed || typeof parsed !== "object" || !parsed.entries) {
5053
5530
  return emptyManifest();
@@ -5058,11 +5535,11 @@ function loadManifest(claudeDir) {
5058
5535
  }
5059
5536
  }
5060
5537
  function saveManifest(claudeDir, manifest) {
5061
- const dir = resolve6(claudeDir, ".massu");
5062
- if (!existsSync12(dir)) {
5538
+ const dir = resolve9(claudeDir, ".massu");
5539
+ if (!existsSync14(dir)) {
5063
5540
  mkdirSync8(dir, { recursive: true });
5064
5541
  }
5065
- const finalPath = resolve6(dir, "install-manifest.json");
5542
+ const finalPath = resolve9(dir, "install-manifest.json");
5066
5543
  manifest.generatedAt = (/* @__PURE__ */ new Date()).toISOString();
5067
5544
  atomicWriteFile(finalPath, JSON.stringify(manifest, null, 2));
5068
5545
  }
@@ -5080,86 +5557,17 @@ function runWithManifest(claudeDir, fn) {
5080
5557
  saveManifest(claudeDir, manifest);
5081
5558
  return result;
5082
5559
  }
5083
- function pickVariant(baseName, sourceDir, framework) {
5084
- const candidates = [];
5085
- const seenLangs = /* @__PURE__ */ new Set();
5086
- function pushCandidate(lang, sub) {
5087
- if (seenLangs.has(lang)) return;
5088
- seenLangs.add(lang);
5089
- candidates.push({ lang, subFramework: sub && sub.length > 0 ? sub : void 0 });
5090
- }
5091
- const primary = framework.primary ?? framework.type;
5092
- if (primary && primary !== "multi") {
5093
- let primarySub;
5094
- if (framework.languages && framework.languages[primary]?.framework) {
5095
- primarySub = framework.languages[primary].framework;
5096
- } else {
5097
- const passthrough2 = framework;
5098
- const block = passthrough2[primary];
5099
- if (block && typeof block === "object") {
5100
- const fw = block.framework;
5101
- if (typeof fw === "string" && fw.length > 0) primarySub = fw;
5102
- }
5103
- }
5104
- pushCandidate(primary, primarySub);
5105
- }
5106
- if (framework.languages) {
5107
- for (const lang of Object.keys(framework.languages)) {
5108
- const entry = framework.languages[lang];
5109
- if (entry && typeof entry.framework === "string" && entry.framework.length > 0) {
5110
- pushCandidate(lang, entry.framework);
5111
- }
5112
- }
5113
- }
5114
- const passthrough = framework;
5115
- for (const lang of PASSTHROUGH_LANG_KEYS) {
5116
- if (seenLangs.has(lang)) continue;
5117
- const block = passthrough[lang];
5118
- if (block && typeof block === "object") {
5119
- const fw = block.framework;
5120
- if (typeof fw === "string" && fw.length > 0) {
5121
- pushCandidate(lang, fw);
5122
- }
5123
- }
5124
- }
5125
- for (const cand of candidates) {
5126
- if (cand.subFramework) {
5127
- const subPath = resolve6(sourceDir, `${baseName}.${cand.lang}-${cand.subFramework}.md`);
5128
- if (existsSync12(subPath)) {
5129
- return { kind: "hit", suffix: `.${cand.lang}-${cand.subFramework}` };
5130
- }
5131
- }
5132
- const langPath = resolve6(sourceDir, `${baseName}.${cand.lang}.md`);
5133
- if (existsSync12(langPath)) {
5134
- return { kind: "hit", suffix: `.${cand.lang}` };
5135
- }
5136
- }
5137
- const defaultPath = resolve6(sourceDir, `${baseName}.md`);
5138
- if (existsSync12(defaultPath)) {
5139
- return { kind: "hit", suffix: "" };
5140
- }
5141
- if (framework.type === "multi" && !framework.primary) {
5142
- process.stderr.write(
5143
- "massu: warning - framework.type=multi but framework.primary is undefined; falling back to default templates\n"
5144
- );
5145
- return { kind: "fallback", reason: "multi-without-primary" };
5146
- }
5147
- return { kind: "miss" };
5148
- }
5149
- function isVariantFilename(entry) {
5150
- return /^[^.]+\.[^.]+\.md$/.test(entry);
5151
- }
5152
- function syncDirectory(sourceDir, targetDir, framework, manifest, manifestKeyPrefix, topLevel = true, templateVars = {}) {
5560
+ function syncDirectory(sourceDir, targetDir, framework, manifest, manifestKeyPrefix, topLevel = true, templateVars = {}, resolveFragment) {
5153
5561
  const stats = { installed: 0, updated: 0, skipped: 0, kept: 0 };
5154
- if (!existsSync12(targetDir)) {
5562
+ if (!existsSync14(targetDir)) {
5155
5563
  mkdirSync8(targetDir, { recursive: true });
5156
5564
  }
5157
- const entries = readdirSync4(sourceDir);
5565
+ const entries = readdirSync5(sourceDir);
5158
5566
  for (const entry of entries) {
5159
- const sourcePath = resolve6(sourceDir, entry);
5160
- const entryStat = statSync3(sourcePath);
5567
+ const sourcePath = resolve9(sourceDir, entry);
5568
+ const entryStat = statSync4(sourcePath);
5161
5569
  if (entryStat.isDirectory()) {
5162
- const subTargetDir = resolve6(targetDir, entry);
5570
+ const subTargetDir = resolve9(targetDir, entry);
5163
5571
  const subPrefix = manifestKeyPrefix === "" ? entry : `${manifestKeyPrefix}/${entry}`;
5164
5572
  const subStats = syncDirectory(
5165
5573
  sourcePath,
@@ -5168,7 +5576,8 @@ function syncDirectory(sourceDir, targetDir, framework, manifest, manifestKeyPre
5168
5576
  manifest,
5169
5577
  subPrefix,
5170
5578
  false,
5171
- templateVars
5579
+ templateVars,
5580
+ resolveFragment
5172
5581
  );
5173
5582
  stats.installed += subStats.installed;
5174
5583
  stats.updated += subStats.updated;
@@ -5177,46 +5586,39 @@ function syncDirectory(sourceDir, targetDir, framework, manifest, manifestKeyPre
5177
5586
  continue;
5178
5587
  }
5179
5588
  if (!entry.endsWith(".md")) continue;
5589
+ if (entry === "README.md") continue;
5180
5590
  let sourceFilename = entry;
5181
5591
  let baseName = entry.slice(0, -".md".length);
5182
5592
  if (topLevel) {
5183
5593
  if (isVariantFilename(entry)) continue;
5184
- const choice = pickVariant(baseName, sourceDir, framework);
5185
- if (choice.kind === "miss") {
5186
- continue;
5187
- }
5188
- const suffix = choice.kind === "hit" ? choice.suffix : "";
5189
- sourceFilename = suffix === "" ? `${baseName}.md` : `${baseName}${suffix}.md`;
5190
- }
5191
- const resolvedSourcePath = resolve6(sourceDir, sourceFilename);
5192
- if (!existsSync12(resolvedSourcePath)) {
5193
- continue;
5594
+ const selected = variantFilename(baseName, pickVariant(baseName, sourceDir, framework));
5595
+ if (selected === null) continue;
5596
+ sourceFilename = selected;
5194
5597
  }
5598
+ const resolvedSourcePath = resolve9(sourceDir, sourceFilename);
5195
5599
  const targetFilename = topLevel ? `${baseName}.md` : entry;
5196
- const targetPath = resolve6(targetDir, targetFilename);
5197
- const rawContent = readFileSync8(resolvedSourcePath, "utf-8");
5198
- let sourceContent;
5199
- try {
5200
- sourceContent = renderTemplate(rawContent, templateVars);
5201
- } catch (err) {
5202
- if (err instanceof MissingVariableError || err instanceof TemplateParseError) {
5203
- process.stderr.write(
5204
- `massu: skipping ${resolvedSourcePath}: ${err.message}
5205
- `
5206
- );
5600
+ const targetPath = resolve9(targetDir, targetFilename);
5601
+ const rendered = renderAssetFile(resolvedSourcePath, sourceFilename, {
5602
+ vars: templateVars,
5603
+ resolveFragment
5604
+ });
5605
+ if (rendered.kind !== "ok") {
5606
+ if (rendered.kind === "error") {
5607
+ process.stderr.write(`massu: skipping ${rendered.sourcePath}: ${rendered.message}
5608
+ `);
5207
5609
  stats.skipped++;
5208
- continue;
5209
5610
  }
5210
- throw err;
5611
+ continue;
5211
5612
  }
5613
+ const sourceContent = rendered.content;
5212
5614
  const sourceHash = hashContent(sourceContent);
5213
5615
  const manifestKey = manifestKeyPrefix === "" ? targetFilename : `${manifestKeyPrefix}/${targetFilename}`;
5214
5616
  const lastInstalledHash = manifest.entries[manifestKey];
5215
- if (existsSync12(targetPath)) {
5617
+ if (existsSync14(targetPath)) {
5216
5618
  let existingContent;
5217
5619
  try {
5218
- if (!statSync3(targetPath).isFile()) throw new Error("not a regular file");
5219
- existingContent = readFileSync8(targetPath, "utf-8");
5620
+ if (!statSync4(targetPath).isFile()) throw new Error("not a regular file");
5621
+ existingContent = readFileSync10(targetPath, "utf-8");
5220
5622
  } catch (err) {
5221
5623
  process.stderr.write(
5222
5624
  `massu: skipping ${manifestKey}: target is not a readable file (${err instanceof Error ? err.message : String(err)}). Remove it and re-install.
@@ -5276,30 +5678,11 @@ function syncDirectory(sourceDir, targetDir, framework, manifest, manifestKeyPre
5276
5678
  }
5277
5679
  return stats;
5278
5680
  }
5279
- function buildTemplateVars() {
5280
- const config = getConfig();
5281
- return {
5282
- framework: config.framework,
5283
- paths: config.paths,
5284
- detected: config.detected ?? {},
5285
- config,
5286
- // P-H006 (plan-stage-c-high-batch): RESERVED CLAUDE CODE PLACEHOLDER.
5287
- // Claude Code reads `{{ARGUMENTS}}` as a runtime placeholder inside
5288
- // slash-command files. The Massu template engine has no native concept
5289
- // of reserved literals; we model the placeholder as a variable whose
5290
- // value IS the literal `{{ARGUMENTS}}` string. Because the engine never
5291
- // re-renders output, this passes through verbatim. Closes the bug class
5292
- // where `/massu-article-review`, `/massu-autoresearch`, etc. silently
5293
- // failed to install because the engine threw MissingVariableError on
5294
- // their {{ARGUMENTS}} usage.
5295
- ARGUMENTS: "{{ARGUMENTS}}"
5296
- };
5297
- }
5298
5681
  function installCommands(projectRoot, opts = {}) {
5299
5682
  const claudeDirName = getConfig().conventions?.claudeDirName ?? ".claude";
5300
- const claudeDir = resolve6(projectRoot, claudeDirName);
5301
- const targetDir = resolve6(claudeDir, "commands");
5302
- if (!existsSync12(targetDir)) {
5683
+ const claudeDir = resolve9(projectRoot, claudeDirName);
5684
+ const targetDir = resolve9(claudeDir, "commands");
5685
+ if (!existsSync14(targetDir)) {
5303
5686
  mkdirSync8(targetDir, { recursive: true });
5304
5687
  }
5305
5688
  const sourceDir = resolveAssetDir("commands");
@@ -5309,7 +5692,8 @@ function installCommands(projectRoot, opts = {}) {
5309
5692
  return { installed: 0, updated: 0, skipped: 0, kept: 0, commandsDir: targetDir };
5310
5693
  }
5311
5694
  const framework = getConfig().framework;
5312
- const templateVars = buildTemplateVars();
5695
+ const ctx = makeRenderContext(framework);
5696
+ const { vars: templateVars, resolveFragment, fragments: fragmentStats } = ctx;
5313
5697
  const stats = runWithManifest(claudeDir, (manifest) => {
5314
5698
  const syncStats = syncDirectory(
5315
5699
  sourceDir,
@@ -5318,7 +5702,8 @@ function installCommands(projectRoot, opts = {}) {
5318
5702
  manifest,
5319
5703
  "commands",
5320
5704
  true,
5321
- templateVars
5705
+ templateVars,
5706
+ resolveFragment
5322
5707
  );
5323
5708
  if (!opts.skipPermissions) {
5324
5709
  installPermissions(claudeDir, manifest, { silent: true });
@@ -5329,7 +5714,7 @@ function installCommands(projectRoot, opts = {}) {
5329
5714
  }
5330
5715
  function installAll(projectRoot, opts = {}) {
5331
5716
  const claudeDirName = getConfig().conventions?.claudeDirName ?? ".claude";
5332
- const claudeDir = resolve6(projectRoot, claudeDirName);
5717
+ const claudeDir = resolve9(projectRoot, claudeDirName);
5333
5718
  const assets = {};
5334
5719
  let totalInstalled = 0;
5335
5720
  let totalUpdated = 0;
@@ -5337,12 +5722,13 @@ function installAll(projectRoot, opts = {}) {
5337
5722
  let totalKept = 0;
5338
5723
  let permissionsResult;
5339
5724
  const framework = getConfig().framework;
5340
- const templateVars = buildTemplateVars();
5725
+ const ctx = makeRenderContext(framework);
5726
+ const { vars: templateVars, resolveFragment, fragments: fragmentStats } = ctx;
5341
5727
  runWithManifest(claudeDir, (manifest) => {
5342
5728
  for (const assetType of ASSET_TYPES) {
5343
5729
  const sourceDir = resolveAssetDir(assetType.name);
5344
5730
  if (!sourceDir) continue;
5345
- const targetDir = resolve6(claudeDir, assetType.targetSubdir);
5731
+ const targetDir = resolve9(claudeDir, assetType.targetSubdir);
5346
5732
  const stats = syncDirectory(
5347
5733
  sourceDir,
5348
5734
  targetDir,
@@ -5350,7 +5736,8 @@ function installAll(projectRoot, opts = {}) {
5350
5736
  manifest,
5351
5737
  assetType.targetSubdir,
5352
5738
  true,
5353
- templateVars
5739
+ templateVars,
5740
+ resolveFragment
5354
5741
  );
5355
5742
  assets[assetType.name] = stats;
5356
5743
  totalInstalled += stats.installed;
@@ -5369,6 +5756,7 @@ function installAll(projectRoot, opts = {}) {
5369
5756
  totalSkipped,
5370
5757
  totalKept,
5371
5758
  claudeDir,
5759
+ fragments: fragmentStats,
5372
5760
  permissions: permissionsResult
5373
5761
  };
5374
5762
  }
@@ -5420,16 +5808,15 @@ async function runInstallCommands() {
5420
5808
  console.log(" Restart your Claude Code session to use them.");
5421
5809
  console.log("");
5422
5810
  }
5423
- var __filename, __dirname, ASSET_TYPES, MANIFEST_VERSION, MANIFEST_RELPATH, MASSU_OWNED_PATHS, PASSTHROUGH_LANG_KEYS;
5811
+ var ASSET_TYPES, MANIFEST_VERSION, MANIFEST_RELPATH, MASSU_OWNED_PATHS;
5424
5812
  var init_install_commands = __esm({
5425
5813
  "src/commands/install-commands.ts"() {
5426
5814
  "use strict";
5427
5815
  init_config();
5428
- init_template_engine();
5816
+ init_asset_resolution();
5817
+ init_asset_render();
5429
5818
  init_settings_local();
5430
5819
  init_permissions();
5431
- __filename = fileURLToPath2(import.meta.url);
5432
- __dirname = dirname9(__filename);
5433
5820
  ASSET_TYPES = [
5434
5821
  { name: "commands", targetSubdir: "commands", description: "slash commands" },
5435
5822
  { name: "agents", targetSubdir: "agents", description: "agent definitions" },
@@ -5440,14 +5827,6 @@ var init_install_commands = __esm({
5440
5827
  MANIFEST_VERSION = 1;
5441
5828
  MANIFEST_RELPATH = join8(".massu", "install-manifest.json");
5442
5829
  MASSU_OWNED_PATHS = ["commands/_verification-laws.md"];
5443
- PASSTHROUGH_LANG_KEYS = [
5444
- "typescript",
5445
- "javascript",
5446
- "python",
5447
- "swift",
5448
- "rust",
5449
- "go"
5450
- ];
5451
5830
  }
5452
5831
  });
5453
5832
 
@@ -5651,17 +6030,17 @@ var init_manifest_registry = __esm({
5651
6030
  });
5652
6031
 
5653
6032
  // src/detect/package-detector.ts
5654
- import { readFileSync as readFileSync9, existsSync as existsSync13, statSync as statSync4, lstatSync, readdirSync as readdirSync5 } from "fs";
6033
+ import { readFileSync as readFileSync11, existsSync as existsSync15, statSync as statSync5, lstatSync, readdirSync as readdirSync6 } from "fs";
5655
6034
  import { join as join9, relative as relative2 } from "path";
5656
6035
  import { parse as parseToml } from "smol-toml";
5657
6036
  function safeRead(path) {
5658
6037
  try {
5659
- if (!existsSync13(path)) return null;
6038
+ if (!existsSync15(path)) return null;
5660
6039
  const ls = lstatSync(path);
5661
6040
  if (ls.isSymbolicLink()) return null;
5662
- const st = statSync4(path);
6041
+ const st = statSync5(path);
5663
6042
  if (!st.isFile()) return null;
5664
- return readFileSync9(path, "utf-8");
6043
+ return readFileSync11(path, "utf-8");
5665
6044
  } catch {
5666
6045
  return null;
5667
6046
  }
@@ -5692,7 +6071,7 @@ function parsePackageJson(path, directory, root, warnings) {
5692
6071
  const peer = Object.keys(
5693
6072
  pkg.peerDependencies ?? {}
5694
6073
  );
5695
- const hasTs = deps.includes("typescript") || devDeps.includes("typescript") || existsSync13(join9(directory, "tsconfig.json"));
6074
+ const hasTs = deps.includes("typescript") || devDeps.includes("typescript") || existsSync15(join9(directory, "tsconfig.json"));
5696
6075
  const language = hasTs ? "typescript" : "javascript";
5697
6076
  const scripts = Object.keys(
5698
6077
  pkg.scripts ?? {}
@@ -6119,13 +6498,13 @@ function detectManifestsInDir(dir, root, warnings) {
6119
6498
  for (const entry of getManifestRegistry2()) {
6120
6499
  if (!entry.pattern.startsWith("*")) {
6121
6500
  const path = join9(dir, entry.pattern);
6122
- if (!existsSync13(path)) continue;
6501
+ if (!existsSync15(path)) continue;
6123
6502
  const m3 = entry.parse(path, dir, root, warnings);
6124
6503
  if (m3 !== null) out.push(m3);
6125
6504
  } else {
6126
6505
  if (dirEntries === null) {
6127
6506
  try {
6128
- dirEntries = readdirSync5(dir);
6507
+ dirEntries = readdirSync6(dir);
6129
6508
  } catch {
6130
6509
  dirEntries = [];
6131
6510
  }
@@ -6133,7 +6512,7 @@ function detectManifestsInDir(dir, root, warnings) {
6133
6512
  for (const fname of dirEntries) {
6134
6513
  if (!matchManifestPattern2(fname, entry.pattern)) continue;
6135
6514
  const path = join9(dir, fname);
6136
- if (!existsSync13(path)) continue;
6515
+ if (!existsSync15(path)) continue;
6137
6516
  const m3 = entry.parse(path, dir, root, warnings);
6138
6517
  if (m3 !== null) out.push(m3);
6139
6518
  }
@@ -6143,7 +6522,7 @@ function detectManifestsInDir(dir, root, warnings) {
6143
6522
  }
6144
6523
  function listSubdirs(dir) {
6145
6524
  try {
6146
- return readdirSync5(dir, { withFileTypes: true }).filter((e2) => e2.isDirectory() && !IGNORED_DIRS.has(e2.name)).map((e2) => join9(dir, e2.name));
6525
+ return readdirSync6(dir, { withFileTypes: true }).filter((e2) => e2.isDirectory() && !IGNORED_DIRS.has(e2.name)).map((e2) => join9(dir, e2.name));
6147
6526
  } catch {
6148
6527
  return [];
6149
6528
  }
@@ -6154,7 +6533,7 @@ function detectPackageManifests(projectRoot) {
6154
6533
  manifests.push(...detectManifestsInDir(projectRoot, projectRoot, warnings));
6155
6534
  for (const ws of WORKSPACE_DIRS) {
6156
6535
  const wsRoot = join9(projectRoot, ws);
6157
- if (!existsSync13(wsRoot)) continue;
6536
+ if (!existsSync15(wsRoot)) continue;
6158
6537
  for (const sub of listSubdirs(wsRoot)) {
6159
6538
  manifests.push(...detectManifestsInDir(sub, projectRoot, warnings));
6160
6539
  for (const sub2 of listSubdirs(sub)) {
@@ -6406,22 +6785,22 @@ import {
6406
6785
  openSync as openSync3,
6407
6786
  fsyncSync as fsyncSync3,
6408
6787
  closeSync as closeSync3,
6409
- existsSync as existsSync14,
6788
+ existsSync as existsSync16,
6410
6789
  realpathSync as realpathSync2,
6411
6790
  unlinkSync as unlinkSync2,
6412
6791
  accessSync as accessSync2,
6413
- statSync as statSync5,
6792
+ statSync as statSync6,
6414
6793
  chmodSync as chmodSync3,
6415
6794
  constants
6416
6795
  } from "fs";
6417
- import { dirname as dirname10, resolve as resolve7, relative as relative3, isAbsolute, basename as basename3, sep } from "path";
6796
+ import { dirname as dirname10, resolve as resolve10, relative as relative3, isAbsolute, basename as basename3, sep } from "path";
6418
6797
  import { createHash as createHash4 } from "crypto";
6419
6798
  function assertContainedIn(rootDir, candidate, opts = {}) {
6420
6799
  if (candidate.includes("\0")) {
6421
6800
  throw new PathEscapeError("path contains a NUL byte");
6422
6801
  }
6423
- const root = resolve7(rootDir);
6424
- const abs = resolve7(root, candidate);
6802
+ const root = resolve10(rootDir);
6803
+ const abs = resolve10(root, candidate);
6425
6804
  if (!opts.allowNested) {
6426
6805
  const rel = relative3(root, abs);
6427
6806
  if (rel.includes(sep)) {
@@ -6440,13 +6819,13 @@ function assertContainedIn(rootDir, candidate, opts = {}) {
6440
6819
  }
6441
6820
  function isContainedIn(rootDir, candidate, opts = {}) {
6442
6821
  if (candidate.includes("\0")) return false;
6443
- const root = resolve7(rootDir);
6444
- const abs = resolve7(root, candidate);
6822
+ const root = resolve10(rootDir);
6823
+ const abs = resolve10(root, candidate);
6445
6824
  const rel = relative3(root, abs);
6446
6825
  if (rel === "" && !opts.allowRoot) return false;
6447
6826
  if (rel.startsWith("..") || isAbsolute(rel)) return false;
6448
6827
  let probe = abs;
6449
- while (!existsSync14(probe) && probe.length > 1) {
6828
+ while (!existsSync16(probe) && probe.length > 1) {
6450
6829
  const parent = dirname10(probe);
6451
6830
  if (parent === probe) break;
6452
6831
  probe = parent;
@@ -6465,11 +6844,11 @@ function isContainedIn(rootDir, candidate, opts = {}) {
6465
6844
  }
6466
6845
  function atomicWriteFileSync(destPath, contents) {
6467
6846
  const dir = dirname10(destPath);
6468
- const tmp = resolve7(dir, `.${basename3(destPath)}.massu-tmp-${process.pid}`);
6847
+ const tmp = resolve10(dir, `.${basename3(destPath)}.massu-tmp-${process.pid}`);
6469
6848
  let destMode;
6470
- if (existsSync14(destPath)) {
6849
+ if (existsSync16(destPath)) {
6471
6850
  accessSync2(destPath, constants.W_OK);
6472
- destMode = statSync5(destPath).mode & 511;
6851
+ destMode = statSync6(destPath).mode & 511;
6473
6852
  }
6474
6853
  let fd;
6475
6854
  try {
@@ -6488,7 +6867,7 @@ function atomicWriteFileSync(destPath, contents) {
6488
6867
  }
6489
6868
  }
6490
6869
  try {
6491
- if (existsSync14(tmp)) unlinkSync2(tmp);
6870
+ if (existsSync16(tmp)) unlinkSync2(tmp);
6492
6871
  } catch {
6493
6872
  }
6494
6873
  throw err;
@@ -10422,11 +10801,11 @@ var require_out = __commonJS({
10422
10801
  async.read(path, getSettings(optionsOrSettingsOrCallback), callback);
10423
10802
  }
10424
10803
  exports.stat = stat;
10425
- function statSync22(path, optionsOrSettings) {
10804
+ function statSync23(path, optionsOrSettings) {
10426
10805
  const settings = getSettings(optionsOrSettings);
10427
10806
  return sync.read(path, settings);
10428
10807
  }
10429
- exports.statSync = statSync22;
10808
+ exports.statSync = statSync23;
10430
10809
  function getSettings(settingsOrOptions = {}) {
10431
10810
  if (settingsOrOptions instanceof settings_1.default) {
10432
10811
  return settingsOrOptions;
@@ -11091,41 +11470,41 @@ var require_queue = __commonJS({
11091
11470
  queue.drained = drained;
11092
11471
  return queue;
11093
11472
  function push(value) {
11094
- var p19 = new Promise(function(resolve47, reject) {
11473
+ var p19 = new Promise(function(resolve49, reject) {
11095
11474
  pushCb(value, function(err, result) {
11096
11475
  if (err) {
11097
11476
  reject(err);
11098
11477
  return;
11099
11478
  }
11100
- resolve47(result);
11479
+ resolve49(result);
11101
11480
  });
11102
11481
  });
11103
11482
  p19.catch(noop);
11104
11483
  return p19;
11105
11484
  }
11106
11485
  function unshift(value) {
11107
- var p19 = new Promise(function(resolve47, reject) {
11486
+ var p19 = new Promise(function(resolve49, reject) {
11108
11487
  unshiftCb(value, function(err, result) {
11109
11488
  if (err) {
11110
11489
  reject(err);
11111
11490
  return;
11112
11491
  }
11113
- resolve47(result);
11492
+ resolve49(result);
11114
11493
  });
11115
11494
  });
11116
11495
  p19.catch(noop);
11117
11496
  return p19;
11118
11497
  }
11119
11498
  function drained() {
11120
- var p19 = new Promise(function(resolve47) {
11499
+ var p19 = new Promise(function(resolve49) {
11121
11500
  process.nextTick(function() {
11122
11501
  if (queue.idle()) {
11123
- resolve47();
11502
+ resolve49();
11124
11503
  } else {
11125
11504
  var previousDrain = queue.drain;
11126
11505
  queue.drain = function() {
11127
11506
  if (typeof previousDrain === "function") previousDrain();
11128
- resolve47();
11507
+ resolve49();
11129
11508
  queue.drain = previousDrain;
11130
11509
  };
11131
11510
  }
@@ -11611,9 +11990,9 @@ var require_stream3 = __commonJS({
11611
11990
  });
11612
11991
  }
11613
11992
  _getStat(filepath) {
11614
- return new Promise((resolve47, reject) => {
11993
+ return new Promise((resolve49, reject) => {
11615
11994
  this._stat(filepath, this._fsStatSettings, (error, stats) => {
11616
- return error === null ? resolve47(stats) : reject(error);
11995
+ return error === null ? resolve49(stats) : reject(error);
11617
11996
  });
11618
11997
  });
11619
11998
  }
@@ -11637,10 +12016,10 @@ var require_async5 = __commonJS({
11637
12016
  this._readerStream = new stream_1.default(this._settings);
11638
12017
  }
11639
12018
  dynamic(root, options) {
11640
- return new Promise((resolve47, reject) => {
12019
+ return new Promise((resolve49, reject) => {
11641
12020
  this._walkAsync(root, options, (error, entries) => {
11642
12021
  if (error === null) {
11643
- resolve47(entries);
12022
+ resolve49(entries);
11644
12023
  } else {
11645
12024
  reject(error);
11646
12025
  }
@@ -11650,10 +12029,10 @@ var require_async5 = __commonJS({
11650
12029
  async static(patterns, options) {
11651
12030
  const entries = [];
11652
12031
  const stream = this._readerStream.static(patterns, options);
11653
- return new Promise((resolve47, reject) => {
12032
+ return new Promise((resolve49, reject) => {
11654
12033
  stream.once("error", reject);
11655
12034
  stream.on("data", (entry) => entries.push(entry));
11656
- stream.once("end", () => resolve47(entries));
12035
+ stream.once("end", () => resolve49(entries));
11657
12036
  });
11658
12037
  }
11659
12038
  };
@@ -12499,25 +12878,25 @@ var init_source_dir_detector = __esm({
12499
12878
  });
12500
12879
 
12501
12880
  // src/detect/monorepo-detector.ts
12502
- import { readFileSync as readFileSync10, existsSync as existsSync15, statSync as statSync6, lstatSync as lstatSync2, readdirSync as readdirSync6 } from "fs";
12881
+ import { readFileSync as readFileSync12, existsSync as existsSync17, statSync as statSync7, lstatSync as lstatSync2, readdirSync as readdirSync7 } from "fs";
12503
12882
  import { join as join10, relative as relative4 } from "path";
12504
12883
  import { parse as parseYaml3 } from "yaml";
12505
12884
  import { parse as parseToml2 } from "smol-toml";
12506
12885
  function safeReadText(path) {
12507
12886
  try {
12508
- if (!existsSync15(path)) return null;
12887
+ if (!existsSync17(path)) return null;
12509
12888
  const ls = lstatSync2(path);
12510
12889
  if (ls.isSymbolicLink()) return null;
12511
- const st = statSync6(path);
12890
+ const st = statSync7(path);
12512
12891
  if (!st.isFile()) return null;
12513
- return readFileSync10(path, "utf-8");
12892
+ return readFileSync12(path, "utf-8");
12514
12893
  } catch {
12515
12894
  return null;
12516
12895
  }
12517
12896
  }
12518
12897
  function firstManifestIn(dir) {
12519
12898
  for (const m3 of MANIFEST_PRIORITY) {
12520
- if (existsSync15(join10(dir, m3))) return m3;
12899
+ if (existsSync17(join10(dir, m3))) return m3;
12521
12900
  }
12522
12901
  return null;
12523
12902
  }
@@ -12573,7 +12952,7 @@ function pkgFromDir(root, dir) {
12573
12952
  }
12574
12953
  function listSubdirs2(dir) {
12575
12954
  try {
12576
- return readdirSync6(dir, { withFileTypes: true }).filter((e2) => e2.isDirectory() && !IGNORED_DIRS2.has(e2.name)).map((e2) => join10(dir, e2.name));
12955
+ return readdirSync7(dir, { withFileTypes: true }).filter((e2) => e2.isDirectory() && !IGNORED_DIRS2.has(e2.name)).map((e2) => join10(dir, e2.name));
12577
12956
  } catch {
12578
12957
  return [];
12579
12958
  }
@@ -12582,7 +12961,7 @@ function genericWorkspaces(root) {
12582
12961
  const out = [];
12583
12962
  for (const parent of CONVENTIONAL_WORKSPACE_PARENTS) {
12584
12963
  const p19 = join10(root, parent);
12585
- if (!existsSync15(p19)) continue;
12964
+ if (!existsSync17(p19)) continue;
12586
12965
  for (const sub of listSubdirs2(p19)) {
12587
12966
  const pkg = pkgFromDir(root, sub);
12588
12967
  if (pkg) out.push(pkg);
@@ -12623,7 +13002,7 @@ function expandWorkspaceGlobs(root, globs) {
12623
13002
  const parts = pattern.split("/");
12624
13003
  if (parts.length === 2 && (parts[1] === "*" || parts[1] === "**")) {
12625
13004
  const parent = join10(root, parts[0]);
12626
- if (!existsSync15(parent)) continue;
13005
+ if (!existsSync17(parent)) continue;
12627
13006
  for (const sub of listSubdirs2(parent)) {
12628
13007
  const pkg = pkgFromDir(root, sub);
12629
13008
  if (pkg && !seen.has(pkg.path)) {
@@ -12634,7 +13013,7 @@ function expandWorkspaceGlobs(root, globs) {
12634
13013
  continue;
12635
13014
  }
12636
13015
  const direct = join10(root, pattern);
12637
- if (existsSync15(direct)) {
13016
+ if (existsSync17(direct)) {
12638
13017
  const pkg = pkgFromDir(root, direct);
12639
13018
  if (pkg && !seen.has(pkg.path)) {
12640
13019
  seen.add(pkg.path);
@@ -12645,16 +13024,16 @@ function expandWorkspaceGlobs(root, globs) {
12645
13024
  return out;
12646
13025
  }
12647
13026
  function hasTurbo(root) {
12648
- return existsSync15(join10(root, "turbo.json"));
13027
+ return existsSync17(join10(root, "turbo.json"));
12649
13028
  }
12650
13029
  function hasNx(root) {
12651
- return existsSync15(join10(root, "nx.json"));
13030
+ return existsSync17(join10(root, "nx.json"));
12652
13031
  }
12653
13032
  function hasLerna(root) {
12654
- return existsSync15(join10(root, "lerna.json"));
13033
+ return existsSync17(join10(root, "lerna.json"));
12655
13034
  }
12656
13035
  function hasBazel(root) {
12657
- return existsSync15(join10(root, "WORKSPACE")) || existsSync15(join10(root, "WORKSPACE.bazel")) || existsSync15(join10(root, "MODULE.bazel"));
13036
+ return existsSync17(join10(root, "WORKSPACE")) || existsSync17(join10(root, "WORKSPACE.bazel")) || existsSync17(join10(root, "MODULE.bazel"));
12658
13037
  }
12659
13038
  function detectMonorepo(projectRoot) {
12660
13039
  const nested = [];
@@ -12753,7 +13132,11 @@ function defaultsFor(language, fw, dir) {
12753
13132
  test: testFw === "unittest" ? prefix(dir, "python3 -m unittest") : prefix(dir, "python3 -m pytest -q"),
12754
13133
  type: prefix(dir, "python3 -m mypy ."),
12755
13134
  build: null,
12756
- syntax: prefix(dir, "python3 -m py_compile"),
13135
+ // `python3 -m py_compile` with NO filenames exits 2 ('the following arguments are
13136
+ // required: filenames') — a check that can never pass, i.e. a brick, and a brick
13137
+ // gets bypassed. `compileall -q .` measured exit 1 on a tree with a syntax error
13138
+ // and exit 0 on a clean tree (2026-09-20).
13139
+ syntax: prefix(dir, "python3 -m compileall -q ."),
12757
13140
  lint: prefix(dir, "python3 -m ruff check .")
12758
13141
  };
12759
13142
  }
@@ -12791,7 +13174,12 @@ function defaultsFor(language, fw, dir) {
12791
13174
  return {
12792
13175
  test: prefix(dir, "swift test"),
12793
13176
  type: prefix(dir, "swift build"),
12794
- build: prefix(dir, "xcodebuild build"),
13177
+ // `xcodebuild build` requires an .xcodeproj/.xcworkspace, but this block's other
13178
+ // commands (`swift test`, `swift build`) and the swift-ios template's declared
13179
+ // layout (Sources/) are SwiftPM. One stack cannot be SwiftPM for two keys and
13180
+ // Xcode for a third. Operator-approved 2026-09-20; the three anti-vacuity plants
13181
+ // that target this literal were re-pointed in the same change.
13182
+ build: prefix(dir, "swift build -c release"),
12795
13183
  syntax: null,
12796
13184
  lint: prefix(dir, "swiftlint")
12797
13185
  };
@@ -12816,10 +13204,17 @@ function defaultsFor(language, fw, dir) {
12816
13204
  }
12817
13205
  case "ruby": {
12818
13206
  return {
12819
- test: prefix(dir, "bundle exec rspec"),
13207
+ // Minitest is Rails' default; `bin/rails test` works on a fresh `rails new`.
13208
+ // `bundle exec rspec` requires adding rspec-rails to the Gemfile first.
13209
+ // https://guides.rubyonrails.org/testing.html
13210
+ test: prefix(dir, "bin/rails test"),
12820
13211
  type: null,
12821
13212
  build: null,
12822
- syntax: prefix(dir, "ruby -c"),
13213
+ // `ruby -c` with NO file argument reads STDIN: on empty stdin it prints
13214
+ // 'Syntax OK' and exits 0 without inspecting one project file, and it BLOCKS on a
13215
+ // TTY. A check that cannot fail is a blind gate. Measured 2026-09-20; the
13216
+ // replacement exits 1 on a tree with a syntax error and 0 on a clean one.
13217
+ syntax: prefix(dir, "find app lib -name '*.rb' -print0 | xargs -0 -n1 ruby -c > /dev/null"),
12823
13218
  lint: prefix(dir, "bundle exec rubocop")
12824
13219
  };
12825
13220
  }
@@ -12845,7 +13240,7 @@ var init_vr_command_map = __esm({
12845
13240
  });
12846
13241
 
12847
13242
  // src/detect/domain-inferrer.ts
12848
- import { existsSync as existsSync16, readdirSync as readdirSync7 } from "fs";
13243
+ import { existsSync as existsSync18, readdirSync as readdirSync8 } from "fs";
12849
13244
  import { join as join11 } from "path";
12850
13245
  function titleCase(s) {
12851
13246
  if (!s) return s;
@@ -12867,9 +13262,9 @@ function topLevelSrcSubdirs(root, sourceDirs) {
12867
13262
  const seen = /* @__PURE__ */ new Set();
12868
13263
  for (const rel of effective) {
12869
13264
  const abs = join11(root, rel);
12870
- if (!existsSync16(abs)) continue;
13265
+ if (!existsSync18(abs)) continue;
12871
13266
  try {
12872
- for (const e2 of readdirSync7(abs, { withFileTypes: true })) {
13267
+ for (const e2 of readdirSync8(abs, { withFileTypes: true })) {
12873
13268
  if (!e2.isDirectory()) continue;
12874
13269
  if (IGNORED_SUBDIRS.has(e2.name)) continue;
12875
13270
  seen.add(e2.name);
@@ -12954,8 +13349,8 @@ var init_domain_inferrer = __esm({
12954
13349
  });
12955
13350
 
12956
13351
  // src/detect/regex-fallback.ts
12957
- import { existsSync as existsSync17, readdirSync as readdirSync8, readFileSync as readFileSync11, statSync as statSync7 } from "fs";
12958
- import { resolve as resolve8, join as join12, basename as basename4 } from "path";
13352
+ import { existsSync as existsSync19, readdirSync as readdirSync9, readFileSync as readFileSync13, statSync as statSync8 } from "fs";
13353
+ import { resolve as resolve11, join as join12, basename as basename4 } from "path";
12959
13354
  function introspectPython(detection, projectRoot) {
12960
13355
  const sourceDir = resolveSourceDir(detection, "python", projectRoot);
12961
13356
  if (!sourceDir) return null;
@@ -13121,10 +13516,10 @@ function resolveSourceDir(detection, lang, projectRoot) {
13121
13516
  const list = info?.source_dirs ?? [];
13122
13517
  if (list.length > 0) {
13123
13518
  const first = list[0];
13124
- const abs = resolve8(projectRoot, first);
13125
- return existsSync17(abs) ? abs : null;
13519
+ const abs = resolve11(projectRoot, first);
13520
+ return existsSync19(abs) ? abs : null;
13126
13521
  }
13127
- return existsSync17(projectRoot) ? projectRoot : null;
13522
+ return existsSync19(projectRoot) ? projectRoot : null;
13128
13523
  }
13129
13524
  function sampleFiles(dir, nameRegex, pathFilter) {
13130
13525
  const out = [];
@@ -13134,7 +13529,7 @@ function sampleFiles(dir, nameRegex, pathFilter) {
13134
13529
  if (depth > MAX_DIR_DEPTH) continue;
13135
13530
  let entries;
13136
13531
  try {
13137
- entries = readdirSync8(path);
13532
+ entries = readdirSync9(path);
13138
13533
  } catch {
13139
13534
  continue;
13140
13535
  }
@@ -13147,7 +13542,7 @@ function sampleFiles(dir, nameRegex, pathFilter) {
13147
13542
  const child = join12(path, entry);
13148
13543
  let st;
13149
13544
  try {
13150
- st = statSync7(child);
13545
+ st = statSync8(child);
13151
13546
  } catch {
13152
13547
  continue;
13153
13548
  }
@@ -13167,9 +13562,9 @@ function sampleFiles(dir, nameRegex, pathFilter) {
13167
13562
  }
13168
13563
  function readSafe(path) {
13169
13564
  try {
13170
- const st = statSync7(path);
13565
+ const st = statSync8(path);
13171
13566
  if (st.size > MAX_FILE_BYTES) return null;
13172
- return readFileSync11(path, "utf-8");
13567
+ return readFileSync13(path, "utf-8");
13173
13568
  } catch {
13174
13569
  return null;
13175
13570
  }
@@ -13254,7 +13649,7 @@ var init_parse_guard = __esm({
13254
13649
 
13255
13650
  // src/detect/adapters/runner.ts
13256
13651
  import { basename as basename5, relative as relative5 } from "path";
13257
- import { existsSync as existsSync18, readdirSync as readdirSync9, readFileSync as readFileSync12, statSync as statSync8 } from "fs";
13652
+ import { existsSync as existsSync20, readdirSync as readdirSync10, readFileSync as readFileSync14, statSync as statSync9 } from "fs";
13258
13653
  import { join as join13 } from "path";
13259
13654
  async function runAdapters(adapters, rootDir, signals, options = {}) {
13260
13655
  const out = {
@@ -13349,10 +13744,10 @@ function buildDetectionSignals(rootDir) {
13349
13744
  const presentDirs = /* @__PURE__ */ new Set();
13350
13745
  const presentFiles = /* @__PURE__ */ new Set();
13351
13746
  try {
13352
- for (const entry of readdirSync9(rootDir)) {
13747
+ for (const entry of readdirSync10(rootDir)) {
13353
13748
  if (entry.startsWith(".")) continue;
13354
13749
  try {
13355
- const st = statSync8(join13(rootDir, entry));
13750
+ const st = statSync9(join13(rootDir, entry));
13356
13751
  if (st.isDirectory()) presentDirs.add(entry);
13357
13752
  else if (st.isFile()) presentFiles.add(entry);
13358
13753
  } catch {
@@ -13389,9 +13784,9 @@ function tryReadGradleBuild(rootDir, presentFiles) {
13389
13784
  return void 0;
13390
13785
  }
13391
13786
  function tryReadString(path) {
13392
- if (!existsSync18(path)) return void 0;
13787
+ if (!existsSync20(path)) return void 0;
13393
13788
  try {
13394
- return readFileSync12(path, "utf-8");
13789
+ return readFileSync14(path, "utf-8");
13395
13790
  } catch {
13396
13791
  return void 0;
13397
13792
  }
@@ -13509,8 +13904,8 @@ ${querySource}`
13509
13904
  import { createHash as createHash5 } from "crypto";
13510
13905
  import {
13511
13906
  mkdirSync as mkdirSync9,
13512
- readdirSync as readdirSync10,
13513
- readFileSync as readFileSync13,
13907
+ readdirSync as readdirSync11,
13908
+ readFileSync as readFileSync15,
13514
13909
  writeFileSync as writeFileSync4,
13515
13910
  renameSync as renameSync3,
13516
13911
  unlinkSync as unlinkSync3,
@@ -13546,7 +13941,7 @@ function evictBeyondRetainCount(retain = getCacheRetainCount()) {
13546
13941
  const dir = getCacheDir();
13547
13942
  let entries;
13548
13943
  try {
13549
- entries = readdirSync10(dir);
13944
+ entries = readdirSync11(dir);
13550
13945
  } catch {
13551
13946
  return;
13552
13947
  }
@@ -13610,7 +14005,7 @@ async function loadGrammar(language, options = {}) {
13610
14005
  }
13611
14006
  let bytes;
13612
14007
  try {
13613
- bytes = readFileSync13(cachePath);
14008
+ bytes = readFileSync15(cachePath);
13614
14009
  } catch (e2) {
13615
14010
  bytes = new Uint8Array(0);
13616
14011
  }
@@ -14528,7 +14923,7 @@ __export(file_sampler_exports, {
14528
14923
  SAMPLE_TEST_FILE_PATTERNS: () => SAMPLE_TEST_FILE_PATTERNS,
14529
14924
  sampleFilesForAdapter: () => sampleFilesForAdapter
14530
14925
  });
14531
- import { readdirSync as readdirSync11, readFileSync as readFileSync14, lstatSync as lstatSync4 } from "node:fs";
14926
+ import { readdirSync as readdirSync12, readFileSync as readFileSync16, lstatSync as lstatSync4 } from "node:fs";
14532
14927
  import { join as join15, extname } from "node:path";
14533
14928
  function sampleFilesForAdapter(adapter, projectRoot, detection, options = {}) {
14534
14929
  const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
@@ -14560,7 +14955,7 @@ function walkDir(dir, exts, testPatterns, lang, maxDepth, curDepth, out, seen, m
14560
14955
  if (out.length >= maxFiles) return;
14561
14956
  let entries;
14562
14957
  try {
14563
- entries = readdirSync11(dir);
14958
+ entries = readdirSync12(dir);
14564
14959
  } catch {
14565
14960
  return;
14566
14961
  }
@@ -14589,7 +14984,7 @@ function walkDir(dir, exts, testPatterns, lang, maxDepth, curDepth, out, seen, m
14589
14984
  seen.add(fullPath);
14590
14985
  let content;
14591
14986
  try {
14592
- content = readFileSync14(fullPath, "utf-8");
14987
+ content = readFileSync16(fullPath, "utf-8");
14593
14988
  } catch {
14594
14989
  continue;
14595
14990
  }
@@ -15201,7 +15596,7 @@ function cD({ input: t2 = $, output: u2 = j, overwrite: F = true, hideCursor: e2
15201
15596
  t2.off("keypress", i), e2 && u2.write(import_sisteransi.cursor.show), t2.isTTY && !xD && t2.setRawMode(false), s.terminal = false, s.close();
15202
15597
  };
15203
15598
  }
15204
- var import_sisteransi, import_picocolors, Q, P, X, DD, uD, FD, m, L, N, I, r, tD, eD, iD, v, CD, w, W, rD, R, y, V, z3, ED, _, nD, oD, aD, c, xD, S, AD, pD, h, x, fD, gD, vD, K, dD, bD, mD, Y, wD, yD, _D, Z, kD, SD, $D, q, jD, MD, TD, H, OD, PD;
15599
+ var import_sisteransi, import_picocolors, Q, P, X, DD, uD, FD, m, L, N, I, r, tD, eD, iD, v, CD, w, W, rD, R, y, V, z4, ED, _, nD, oD, aD, c, xD, S, AD, pD, h, x, fD, gD, vD, K, dD, bD, mD, Y, wD, yD, _D, Z, kD, SD, $D, q, jD, MD, TD, H, OD, PD;
15205
15600
  var init_dist = __esm({
15206
15601
  "../../node_modules/@clack/core/dist/index.mjs"() {
15207
15602
  import_sisteransi = __toESM(require_src(), 1);
@@ -15258,7 +15653,7 @@ var init_dist = __esm({
15258
15653
  R = "m";
15259
15654
  y = `${rD}8;;`;
15260
15655
  V = (t2) => `${v.values().next().value}${W}${t2}${R}`;
15261
- z3 = (t2) => `${v.values().next().value}${y}${t2}${w}`;
15656
+ z4 = (t2) => `${v.values().next().value}${y}${t2}${w}`;
15262
15657
  ED = (t2) => t2.split(" ").map((u2) => A(u2));
15263
15658
  _ = (t2, u2, F) => {
15264
15659
  const e2 = [...u2];
@@ -15318,8 +15713,8 @@ var init_dist = __esm({
15318
15713
  }
15319
15714
  const n = iD.codes.get(Number(s));
15320
15715
  o[E2 + 1] === `
15321
- ` ? (i && (e2 += z3("")), s && n && (e2 += V(n))) : a2 === `
15322
- ` && (s && n && (e2 += V(s)), i && (e2 += z3(i)));
15716
+ ` ? (i && (e2 += z4("")), s && n && (e2 += V(n))) : a2 === `
15717
+ ` && (s && n && (e2 += V(s)), i && (e2 += z4(i)));
15323
15718
  }
15324
15719
  return e2;
15325
15720
  };
@@ -16022,8 +16417,8 @@ __export(init_exports, {
16022
16417
  validateWrittenConfig: () => validateWrittenConfig,
16023
16418
  writeConfigAtomic: () => writeConfigAtomic
16024
16419
  });
16025
- import { closeSync as closeSync4, existsSync as existsSync19, fsyncSync as fsyncSync4, openSync as openSync4, readFileSync as readFileSync15, writeFileSync as writeFileSync5, writeSync as writeSync2, mkdirSync as mkdirSync10, readdirSync as readdirSync12, renameSync as renameSync4, rmSync as rmSync6, statSync as statSync10, chmodSync as chmodSync5 } from "fs";
16026
- import { resolve as resolve9, basename as basename6, dirname as dirname12 } from "path";
16420
+ import { closeSync as closeSync4, existsSync as existsSync21, fsyncSync as fsyncSync4, openSync as openSync4, readFileSync as readFileSync17, writeFileSync as writeFileSync5, writeSync as writeSync2, mkdirSync as mkdirSync10, readdirSync as readdirSync13, renameSync as renameSync4, rmSync as rmSync6, statSync as statSync11, chmodSync as chmodSync5 } from "fs";
16421
+ import { resolve as resolve12, basename as basename6, dirname as dirname12 } from "path";
16027
16422
  import { fileURLToPath as fileURLToPath3 } from "url";
16028
16423
  import { homedir as homedir7 } from "os";
16029
16424
  import { execFileSync as execFileSync2 } from "child_process";
@@ -16035,10 +16430,10 @@ function detectFramework(projectRoot) {
16035
16430
  orm: "none",
16036
16431
  ui: "none"
16037
16432
  };
16038
- const pkgPath = resolve9(projectRoot, "package.json");
16039
- if (!existsSync19(pkgPath)) return result;
16433
+ const pkgPath = resolve12(projectRoot, "package.json");
16434
+ if (!existsSync21(pkgPath)) return result;
16040
16435
  try {
16041
- const pkg = JSON.parse(readFileSync15(pkgPath, "utf-8"));
16436
+ const pkg = JSON.parse(readFileSync17(pkgPath, "utf-8"));
16042
16437
  const allDeps = {
16043
16438
  ...pkg.dependencies,
16044
16439
  ...pkg.devDependencies
@@ -16072,7 +16467,7 @@ function detectPython(projectRoot) {
16072
16467
  alembicDir: null
16073
16468
  };
16074
16469
  const markers = ["pyproject.toml", "setup.py", "requirements.txt", "Pipfile"];
16075
- const hasMarker = markers.some((m3) => existsSync19(resolve9(projectRoot, m3)));
16470
+ const hasMarker = markers.some((m3) => existsSync21(resolve12(projectRoot, m3)));
16076
16471
  if (!hasMarker) return result;
16077
16472
  result.detected = true;
16078
16473
  const depFiles = [
@@ -16082,35 +16477,35 @@ function detectPython(projectRoot) {
16082
16477
  { file: "Pipfile" }
16083
16478
  ];
16084
16479
  for (const { file } of depFiles) {
16085
- const filePath = resolve9(projectRoot, file);
16086
- if (existsSync19(filePath)) {
16480
+ const filePath = resolve12(projectRoot, file);
16481
+ if (existsSync21(filePath)) {
16087
16482
  try {
16088
- const content = readFileSync15(filePath, "utf-8").toLowerCase();
16483
+ const content = readFileSync17(filePath, "utf-8").toLowerCase();
16089
16484
  if (content.includes("fastapi")) result.hasFastapi = true;
16090
16485
  if (content.includes("sqlalchemy")) result.hasSqlalchemy = true;
16091
16486
  } catch {
16092
16487
  }
16093
16488
  }
16094
16489
  }
16095
- if (existsSync19(resolve9(projectRoot, "alembic.ini"))) {
16490
+ if (existsSync21(resolve12(projectRoot, "alembic.ini"))) {
16096
16491
  result.hasAlembic = true;
16097
- if (existsSync19(resolve9(projectRoot, "alembic"))) {
16492
+ if (existsSync21(resolve12(projectRoot, "alembic"))) {
16098
16493
  result.alembicDir = "alembic";
16099
16494
  }
16100
- } else if (existsSync19(resolve9(projectRoot, "alembic"))) {
16495
+ } else if (existsSync21(resolve12(projectRoot, "alembic"))) {
16101
16496
  result.hasAlembic = true;
16102
16497
  result.alembicDir = "alembic";
16103
16498
  }
16104
16499
  const candidateRoots = ["app", "src", "backend", "api"];
16105
16500
  for (const candidate of candidateRoots) {
16106
- const candidatePath = resolve9(projectRoot, candidate);
16107
- if (existsSync19(candidatePath) && existsSync19(resolve9(candidatePath, "__init__.py"))) {
16501
+ const candidatePath = resolve12(projectRoot, candidate);
16502
+ if (existsSync21(candidatePath) && existsSync21(resolve12(candidatePath, "__init__.py"))) {
16108
16503
  result.root = candidate;
16109
16504
  break;
16110
16505
  }
16111
- if (existsSync19(candidatePath)) {
16506
+ if (existsSync21(candidatePath)) {
16112
16507
  try {
16113
- const files = readdirSync12(candidatePath);
16508
+ const files = readdirSync13(candidatePath);
16114
16509
  if (files.some((f2) => f2.endsWith(".py"))) {
16115
16510
  result.root = candidate;
16116
16511
  break;
@@ -16128,8 +16523,8 @@ function generateConfig(projectRoot, framework) {
16128
16523
  console.warn(
16129
16524
  "[@massu/core] generateConfig() is deprecated since 1.2.1 \u2014 use buildConfigFromDetection instead. It cannot produce valid configs for monorepos."
16130
16525
  );
16131
- const configPath = resolve9(projectRoot, "massu.config.yaml");
16132
- if (existsSync19(configPath)) {
16526
+ const configPath = resolve12(projectRoot, "massu.config.yaml");
16527
+ if (existsSync21(configPath)) {
16133
16528
  return false;
16134
16529
  }
16135
16530
  const projectName = basename6(projectRoot);
@@ -16240,10 +16635,10 @@ function buildConfigFromDetection(opts) {
16240
16635
  pathsSource = monorepoCommonRoot(detection.monorepo.packages);
16241
16636
  }
16242
16637
  }
16243
- if (pathsSource === "src" && !existsSync19(resolve9(projectRoot, "src"))) {
16638
+ if (pathsSource === "src" && !existsSync21(resolve12(projectRoot, "src"))) {
16244
16639
  const fallbacks = ["app", "pages", "."];
16245
16640
  for (const fallback of fallbacks) {
16246
- if (fallback === "." || existsSync19(resolve9(projectRoot, fallback))) {
16641
+ if (fallback === "." || existsSync21(resolve12(projectRoot, fallback))) {
16247
16642
  pathsSource = fallback;
16248
16643
  break;
16249
16644
  }
@@ -16327,7 +16722,7 @@ function buildConfigFromDetection(opts) {
16327
16722
  };
16328
16723
  if (pyFw?.framework) pythonBlock.framework = pyFw.framework;
16329
16724
  if (pyFw?.orm) pythonBlock.orm = pyFw.orm;
16330
- if (existsSync19(resolve9(projectRoot, "alembic.ini")) || existsSync19(resolve9(projectRoot, "alembic"))) {
16725
+ if (existsSync21(resolve12(projectRoot, "alembic.ini")) || existsSync21(resolve12(projectRoot, "alembic"))) {
16331
16726
  pythonBlock.alembic_dir = "alembic";
16332
16727
  }
16333
16728
  config.python = pythonBlock;
@@ -16351,13 +16746,24 @@ function applyVariantTemplate(config, templatesDir) {
16351
16746
  }
16352
16747
  }
16353
16748
  if (templateId === null) return config;
16354
- const templatePath = resolve9(templatesDir, templateId, "massu.config.yaml");
16355
- if (!existsSync19(templatePath)) return config;
16749
+ const templatePath = resolve12(templatesDir, templateId, "massu.config.yaml");
16750
+ if (!existsSync21(templatePath)) return config;
16356
16751
  let template;
16357
16752
  try {
16358
- template = yamlParse(readFileSync15(templatePath, "utf-8"));
16359
- } catch {
16360
- return config;
16753
+ template = yamlParse(readFileSync17(templatePath, "utf-8"));
16754
+ } catch (err) {
16755
+ throw new Error(
16756
+ `massu: the shipped stack template '${templateId}' is unreadable at ${templatePath}: ${err instanceof Error ? err.message : String(err)}
16757
+ This is a defect in @massu/core, not in your project. Please report it.`
16758
+ );
16759
+ }
16760
+ const validation = validateShippedTemplate(template, templateId);
16761
+ if (!validation.ok) {
16762
+ throw new Error(
16763
+ `massu: the shipped stack template '${templateId}' does not satisfy the template contract:
16764
+ ${validation.issues.map((i) => ` - ${i}`).join("\n")}
16765
+ This is a defect in @massu/core, not in your project. Please report it.`
16766
+ );
16361
16767
  }
16362
16768
  const out = { ...config };
16363
16769
  const tplFw = template.framework;
@@ -16414,9 +16820,9 @@ ${yamlStringify(config)}`;
16414
16820
  function writeConfigAtomic(configPath, content) {
16415
16821
  const tmpPath = `${configPath}.tmp`;
16416
16822
  let existingMode;
16417
- if (existsSync19(configPath)) {
16823
+ if (existsSync21(configPath)) {
16418
16824
  try {
16419
- existingMode = statSync10(configPath).mode;
16825
+ existingMode = statSync11(configPath).mode;
16420
16826
  } catch {
16421
16827
  existingMode = void 0;
16422
16828
  }
@@ -16444,7 +16850,7 @@ function writeConfigAtomic(configPath, content) {
16444
16850
  }
16445
16851
  return { validated: true };
16446
16852
  } catch (err) {
16447
- if (existsSync19(tmpPath)) {
16853
+ if (existsSync21(tmpPath)) {
16448
16854
  try {
16449
16855
  rmSync6(tmpPath, { force: true });
16450
16856
  } catch {
@@ -16455,8 +16861,8 @@ function writeConfigAtomic(configPath, content) {
16455
16861
  }
16456
16862
  function validateWrittenConfig(configPath, projectRoot, checkPaths = true) {
16457
16863
  try {
16458
- if (!existsSync19(configPath)) return "Config file does not exist after write";
16459
- const content = readFileSync15(configPath, "utf-8");
16864
+ if (!existsSync21(configPath)) return "Config file does not exist after write";
16865
+ const content = readFileSync17(configPath, "utf-8");
16460
16866
  const parsed = yamlParse(content);
16461
16867
  if (parsed === null || typeof parsed !== "object") {
16462
16868
  return "Config is not a valid YAML object";
@@ -16476,8 +16882,8 @@ function validateWrittenConfig(configPath, projectRoot, checkPaths = true) {
16476
16882
  if (checkPaths) {
16477
16883
  const src = cfg.paths.source;
16478
16884
  if (src && src !== ".") {
16479
- const srcAbs = resolve9(projectRoot, src);
16480
- if (!existsSync19(srcAbs)) {
16885
+ const srcAbs = resolve12(projectRoot, src);
16886
+ if (!existsSync21(srcAbs)) {
16481
16887
  return `paths.source '${src}' does not exist on disk`;
16482
16888
  }
16483
16889
  }
@@ -16487,8 +16893,8 @@ function validateWrittenConfig(configPath, projectRoot, checkPaths = true) {
16487
16893
  if (!Array.isArray(rawDirs)) continue;
16488
16894
  for (const d2 of rawDirs) {
16489
16895
  if (typeof d2 !== "string" || d2 === ".") continue;
16490
- const abs = resolve9(projectRoot, d2);
16491
- if (!existsSync19(abs)) {
16896
+ const abs = resolve12(projectRoot, d2);
16897
+ if (!existsSync21(abs)) {
16492
16898
  return `framework.languages.${lang}.source_dirs '${d2}' does not exist on disk`;
16493
16899
  }
16494
16900
  }
@@ -16497,7 +16903,7 @@ function validateWrittenConfig(configPath, projectRoot, checkPaths = true) {
16497
16903
  if (Array.isArray(mRoots)) {
16498
16904
  for (const r2 of mRoots) {
16499
16905
  if (typeof r2 !== "string" || r2 === ".") continue;
16500
- if (!existsSync19(resolve9(projectRoot, r2))) {
16906
+ if (!existsSync21(resolve12(projectRoot, r2))) {
16501
16907
  return `paths.monorepo_roots '${r2}' does not exist on disk`;
16502
16908
  }
16503
16909
  }
@@ -16529,21 +16935,21 @@ function resolveTemplatesDir() {
16529
16935
  const cwd = process.cwd();
16530
16936
  const candidates = [
16531
16937
  // Project-local install: `<project>/node_modules/@massu/core/templates`.
16532
- resolve9(cwd, "node_modules/@massu/core/templates"),
16938
+ resolve12(cwd, "node_modules/@massu/core/templates"),
16533
16939
  // Bundled cli.js layout: cli.js sits at `<package>/dist/cli.js`, so
16534
16940
  // templates live one level up at `<package>/templates`. (Plan 1.5.1
16535
16941
  // bug discovery: pre-existing layout assumed `dist/commands/init.js`
16536
16942
  // depth which never matched the bundled cli, so resolveTemplatesDir
16537
16943
  // returned null in production for both `--template` mode AND the
16538
16944
  // applyVariantTemplate path.)
16539
- resolve9(__dirname2, "../templates"),
16945
+ resolve12(__dirname2, "../templates"),
16540
16946
  // Legacy nested layouts retained as fallbacks (in case a future
16541
16947
  // build moves cli.js back into a subdirectory).
16542
- resolve9(__dirname2, "../../templates"),
16543
- resolve9(__dirname2, "../../../templates")
16948
+ resolve12(__dirname2, "../../templates"),
16949
+ resolve12(__dirname2, "../../../templates")
16544
16950
  ];
16545
16951
  for (const c2 of candidates) {
16546
- if (existsSync19(c2)) return c2;
16952
+ if (existsSync21(c2)) return c2;
16547
16953
  }
16548
16954
  return null;
16549
16955
  }
@@ -16552,12 +16958,12 @@ function copyTemplateConfig(templateName, targetPath, projectName) {
16552
16958
  if (!templatesDir) {
16553
16959
  return { success: false, error: `Templates directory not found (looked in node_modules and dist/src)` };
16554
16960
  }
16555
- const srcPath = resolve9(templatesDir, templateName, "massu.config.yaml");
16556
- if (!existsSync19(srcPath)) {
16961
+ const srcPath = resolve12(templatesDir, templateName, "massu.config.yaml");
16962
+ if (!existsSync21(srcPath)) {
16557
16963
  return { success: false, error: `Template '${templateName}' not found at ${srcPath}` };
16558
16964
  }
16559
16965
  try {
16560
- let content = readFileSync15(srcPath, "utf-8");
16966
+ let content = readFileSync17(srcPath, "utf-8");
16561
16967
  content = content.replace(/\{\{PROJECT_NAME\}\}/g, projectName);
16562
16968
  writeFileSync5(targetPath, content, "utf-8");
16563
16969
  return { success: true };
@@ -16567,13 +16973,13 @@ function copyTemplateConfig(templateName, targetPath, projectName) {
16567
16973
  }
16568
16974
  function getInstallerVersion() {
16569
16975
  const candidates = [
16570
- resolve9(__dirname2, "../package.json"),
16571
- resolve9(__dirname2, "../../package.json")
16976
+ resolve12(__dirname2, "../package.json"),
16977
+ resolve12(__dirname2, "../../package.json")
16572
16978
  ];
16573
16979
  for (const candidate of candidates) {
16574
- if (existsSync19(candidate)) {
16980
+ if (existsSync21(candidate)) {
16575
16981
  try {
16576
- const pkg = JSON.parse(readFileSync15(candidate, "utf-8"));
16982
+ const pkg = JSON.parse(readFileSync17(candidate, "utf-8"));
16577
16983
  if (typeof pkg.version === "string" && pkg.version.length > 0 && pkg.name === "@massu/core") {
16578
16984
  return pkg.version;
16579
16985
  }
@@ -16586,11 +16992,11 @@ function getInstallerVersion() {
16586
16992
  );
16587
16993
  }
16588
16994
  function registerMcpServer(projectRoot) {
16589
- const mcpPath = resolve9(projectRoot, ".mcp.json");
16995
+ const mcpPath = resolve12(projectRoot, ".mcp.json");
16590
16996
  let existing = {};
16591
- if (existsSync19(mcpPath)) {
16997
+ if (existsSync21(mcpPath)) {
16592
16998
  try {
16593
- existing = JSON.parse(readFileSync15(mcpPath, "utf-8"));
16999
+ existing = JSON.parse(readFileSync17(mcpPath, "utf-8"));
16594
17000
  } catch {
16595
17001
  existing = {};
16596
17002
  }
@@ -16608,26 +17014,26 @@ function registerMcpServer(projectRoot) {
16608
17014
  }
16609
17015
  function resolveHooksDir() {
16610
17016
  const cwd = process.cwd();
16611
- const nodeModulesPath = resolve9(cwd, "node_modules/@massu/core/dist/hooks");
16612
- if (existsSync19(nodeModulesPath)) {
17017
+ const nodeModulesPath = resolve12(cwd, "node_modules/@massu/core/dist/hooks");
17018
+ if (existsSync21(nodeModulesPath)) {
16613
17019
  return "node_modules/@massu/core/dist/hooks";
16614
17020
  }
16615
- const localPath = resolve9(__dirname2, "../dist/hooks");
16616
- if (existsSync19(localPath)) {
17021
+ const localPath = resolve12(__dirname2, "../dist/hooks");
17022
+ if (existsSync21(localPath)) {
16617
17023
  return localPath;
16618
17024
  }
16619
17025
  return "node_modules/@massu/core/dist/hooks";
16620
17026
  }
16621
17027
  function massuRuntimeDir(version, home = homedir7()) {
16622
- return resolve9(home, ".massu", "runtime", version);
17028
+ return resolve12(home, ".massu", "runtime", version);
16623
17029
  }
16624
17030
  function massuRuntimeCliPath(version, home = homedir7()) {
16625
- return resolve9(massuRuntimeDir(version, home), "node_modules", "@massu", "core", "dist", "cli.js");
17031
+ return resolve12(massuRuntimeDir(version, home), "node_modules", "@massu", "core", "dist", "cli.js");
16626
17032
  }
16627
17033
  function resolveMassuRuntimeCli(version, home = homedir7()) {
16628
17034
  if (process.env.MASSU_NO_NODE_DIRECT === "1") return null;
16629
17035
  const cli = massuRuntimeCliPath(version, home);
16630
- if (!existsSync19(cli)) return null;
17036
+ if (!existsSync21(cli)) return null;
16631
17037
  try {
16632
17038
  execFileSync2(process.execPath, [cli, "--version"], { stdio: "ignore", timeout: 2e4 });
16633
17039
  return cli;
@@ -16652,7 +17058,7 @@ function materializeMassuRuntime(version, home = homedir7()) {
16652
17058
  return resolveMassuRuntimeCli(version, home) !== null;
16653
17059
  }
16654
17060
  function massuShimPath(home = homedir7()) {
16655
- return resolve9(home, ".massu", "bin", "massu-hook");
17061
+ return resolve12(home, ".massu", "bin", "massu-hook");
16656
17062
  }
16657
17063
  function massuShimBody() {
16658
17064
  return `#!/bin/sh
@@ -16700,7 +17106,7 @@ function resolveMassuShim(home = homedir7()) {
16700
17106
  if (process.env.MASSU_NO_NODE_DIRECT === "1") return null;
16701
17107
  if (process.platform === "win32") return null;
16702
17108
  const shim = massuShimPath(home);
16703
- if (!existsSync19(shim)) return null;
17109
+ if (!existsSync21(shim)) return null;
16704
17110
  try {
16705
17111
  execFileSync2(shim, [], { stdio: "ignore", timeout: 1e4 });
16706
17112
  return null;
@@ -16863,8 +17269,8 @@ function installHooks(projectRoot) {
16863
17269
  } catch {
16864
17270
  claudeDirName = ".claude";
16865
17271
  }
16866
- const claudeDir = resolve9(projectRoot, claudeDirName);
16867
- if (!existsSync19(claudeDir)) {
17272
+ const claudeDir = resolve12(projectRoot, claudeDirName);
17273
+ if (!existsSync21(claudeDir)) {
16868
17274
  mkdirSync10(claudeDir, { recursive: true });
16869
17275
  }
16870
17276
  const settings = readSettingsLocal(claudeDir);
@@ -16883,16 +17289,16 @@ function installHooks(projectRoot) {
16883
17289
  }
16884
17290
  function initMemoryDir(projectRoot) {
16885
17291
  const encodedRoot = encodeMemoryDirName(projectRoot);
16886
- const memoryDir = resolve9(homedir7(), `.claude/projects/${encodedRoot}/memory`);
17292
+ const memoryDir = resolve12(homedir7(), `.claude/projects/${encodedRoot}/memory`);
16887
17293
  let migratedFromLegacy = false;
16888
- const legacyDir = resolve9(homedir7(), `.claude/projects/-${encodedRoot}/memory`);
16889
- if (existsSync19(legacyDir) && !existsSync19(memoryDir)) {
17294
+ const legacyDir = resolve12(homedir7(), `.claude/projects/-${encodedRoot}/memory`);
17295
+ if (existsSync21(legacyDir) && !existsSync21(memoryDir)) {
16890
17296
  try {
16891
- mkdirSync10(resolve9(memoryDir, ".."), { recursive: true });
17297
+ mkdirSync10(resolve12(memoryDir, ".."), { recursive: true });
16892
17298
  renameSync4(legacyDir, memoryDir);
16893
17299
  try {
16894
- const legacyParent = resolve9(legacyDir, "..");
16895
- if (existsSync19(legacyParent) && readdirSync12(legacyParent).length === 0) {
17300
+ const legacyParent = resolve12(legacyDir, "..");
17301
+ if (existsSync21(legacyParent) && readdirSync13(legacyParent).length === 0) {
16896
17302
  rmSync6(legacyParent, { recursive: false });
16897
17303
  }
16898
17304
  } catch {
@@ -16902,13 +17308,13 @@ function initMemoryDir(projectRoot) {
16902
17308
  }
16903
17309
  }
16904
17310
  let created = false;
16905
- if (!existsSync19(memoryDir)) {
17311
+ if (!existsSync21(memoryDir)) {
16906
17312
  mkdirSync10(memoryDir, { recursive: true });
16907
17313
  created = true;
16908
17314
  }
16909
- const memoryMdPath = resolve9(memoryDir, "MEMORY.md");
17315
+ const memoryMdPath = resolve12(memoryDir, "MEMORY.md");
16910
17316
  let memoryMdCreated = false;
16911
- if (!existsSync19(memoryMdPath)) {
17317
+ if (!existsSync21(memoryMdPath)) {
16912
17318
  const projectName = basename6(projectRoot);
16913
17319
  const memoryContent = `# ${projectName} - Massu Memory
16914
17320
 
@@ -17022,8 +17428,8 @@ async function runInit(argv, overrides) {
17022
17428
  log("Massu AI - Project Setup");
17023
17429
  log("========================");
17024
17430
  log("");
17025
- const configPath = resolve9(projectRoot, "massu.config.yaml");
17026
- if (existsSync19(configPath)) {
17431
+ const configPath = resolve12(projectRoot, "massu.config.yaml");
17432
+ if (existsSync21(configPath)) {
17027
17433
  if (opts.ci && !opts.force) {
17028
17434
  errLog(`error: massu.config.yaml already exists at ${configPath}`);
17029
17435
  errLog(" rerun with --force to overwrite, or remove the file first");
@@ -17162,8 +17568,8 @@ function installSideEffects(projectRoot, log, skipCommands = false, emptyStack =
17162
17568
  const commandStats = cmdResult.assets.commands;
17163
17569
  const stackResolved = !emptyStack && commandStats && (commandStats.installed > 0 || commandStats.updated > 0 || commandStats.kept > 0);
17164
17570
  if (!stackResolved) {
17165
- const placeholderPath = resolve9(cmdResult.claudeDir, "commands", "_massu-needs-stack.md");
17166
- if (!existsSync19(placeholderPath)) {
17571
+ const placeholderPath = resolve12(cmdResult.claudeDir, "commands", "_massu-needs-stack.md");
17572
+ if (!existsSync21(placeholderPath)) {
17167
17573
  const placeholderBody = [
17168
17574
  "# Massu \u2014 stack not yet detected",
17169
17575
  "",
@@ -17184,7 +17590,7 @@ function installSideEffects(projectRoot, log, skipCommands = false, emptyStack =
17184
17590
  "\u2014 Massu"
17185
17591
  ].join("\n");
17186
17592
  try {
17187
- mkdirSync10(resolve9(cmdResult.claudeDir, "commands"), { recursive: true });
17593
+ mkdirSync10(resolve12(cmdResult.claudeDir, "commands"), { recursive: true });
17188
17594
  writeFileSync5(placeholderPath, placeholderBody, "utf-8");
17189
17595
  log(" Wrote _massu-needs-stack.md placeholder (no stack detected yet)");
17190
17596
  } catch {
@@ -17207,8 +17613,8 @@ function installSideEffects(projectRoot, log, skipCommands = false, emptyStack =
17207
17613
  (async () => {
17208
17614
  try {
17209
17615
  const encodedRoot = encodeMemoryDirName(projectRoot);
17210
- const memoryDir = resolve9(homedir7(), ".claude", "projects", encodedRoot, "memory");
17211
- const memFiles = existsSync19(memoryDir) ? readdirSync12(memoryDir).filter((f2) => f2.endsWith(".md") && f2 !== "MEMORY.md") : [];
17616
+ const memoryDir = resolve12(homedir7(), ".claude", "projects", encodedRoot, "memory");
17617
+ const memFiles = existsSync21(memoryDir) ? readdirSync13(memoryDir).filter((f2) => f2.endsWith(".md") && f2 !== "MEMORY.md") : [];
17212
17618
  if (memFiles.length > 0) {
17213
17619
  const { getMemoryDb: getMemoryDb2 } = await Promise.resolve().then(() => (init_memory_db(), memory_db_exports));
17214
17620
  const db = getMemoryDb2();
@@ -17264,6 +17670,7 @@ var init_init = __esm({
17264
17670
  "use strict";
17265
17671
  init_memory_file_ingest();
17266
17672
  init_config();
17673
+ init_shipped_template_schema();
17267
17674
  init_install_commands();
17268
17675
  init_settings_local();
17269
17676
  init_memory_path();
@@ -17890,10 +18297,10 @@ var init_hook_registry = __esm({
17890
18297
  });
17891
18298
 
17892
18299
  // src/preflight.ts
17893
- import { existsSync as existsSync20 } from "fs";
18300
+ import { existsSync as existsSync22 } from "fs";
17894
18301
  function assertCodegraphUsable(dbPath) {
17895
18302
  const path = dbPath ?? getResolvedPaths().codegraphDbPath;
17896
- if (!existsSync20(path)) {
18303
+ if (!existsSync22(path)) {
17897
18304
  throw new CodegraphDbUnusableError(path, "missing");
17898
18305
  }
17899
18306
  let db = null;
@@ -17978,8 +18385,8 @@ __export(hook_runner_exports, {
17978
18385
  resolveHookFile: () => resolveHookFile,
17979
18386
  runHookRunner: () => runHookRunner
17980
18387
  });
17981
- import { existsSync as existsSync21 } from "fs";
17982
- import { resolve as resolve10, dirname as dirname13 } from "path";
18388
+ import { existsSync as existsSync23 } from "fs";
18389
+ import { resolve as resolve13, dirname as dirname13 } from "path";
17983
18390
  import { fileURLToPath as fileURLToPath4 } from "url";
17984
18391
  import { spawn } from "child_process";
17985
18392
  function resolveHookFile(hookName) {
@@ -17991,14 +18398,14 @@ function resolveHookFile(hookName) {
17991
18398
  }
17992
18399
  const candidates = [
17993
18400
  // Bundled compiled layout: dist/cli.js → ./hooks/<file>.js
17994
- resolve10(__dirname3, "hooks", file),
18401
+ resolve13(__dirname3, "hooks", file),
17995
18402
  // TS-source dev / sibling layout: src/commands/ → ../hooks/<file>
17996
- resolve10(__dirname3, "../hooks", file),
18403
+ resolve13(__dirname3, "../hooks", file),
17997
18404
  // TS-source dev fallback: src/commands/ → ../../dist/hooks/<file>
17998
- resolve10(__dirname3, "../../dist/hooks", file)
18405
+ resolve13(__dirname3, "../../dist/hooks", file)
17999
18406
  ];
18000
18407
  for (const candidate of candidates) {
18001
- if (existsSync21(candidate)) {
18408
+ if (existsSync23(candidate)) {
18002
18409
  return candidate;
18003
18410
  }
18004
18411
  }
@@ -18071,17 +18478,17 @@ __export(doctor_exports, {
18071
18478
  runDoctor: () => runDoctor,
18072
18479
  runValidateConfig: () => runValidateConfig
18073
18480
  });
18074
- import { existsSync as existsSync22, readFileSync as readFileSync16, readdirSync as readdirSync13 } from "fs";
18075
- import { resolve as resolve11, dirname as dirname14 } from "path";
18481
+ import { existsSync as existsSync24, readFileSync as readFileSync18, readdirSync as readdirSync14 } from "fs";
18482
+ import { resolve as resolve14, dirname as dirname14 } from "path";
18076
18483
  import { fileURLToPath as fileURLToPath5 } from "url";
18077
18484
  import { parse as parseYaml4 } from "yaml";
18078
18485
  function checkConfig(projectRoot) {
18079
- const configPath = resolve11(projectRoot, "massu.config.yaml");
18080
- if (!existsSync22(configPath)) {
18486
+ const configPath = resolve14(projectRoot, "massu.config.yaml");
18487
+ if (!existsSync24(configPath)) {
18081
18488
  return { name: "Configuration", status: "fail", detail: "massu.config.yaml not found. Run: npx massu init" };
18082
18489
  }
18083
18490
  try {
18084
- const content = readFileSync16(configPath, "utf-8");
18491
+ const content = readFileSync18(configPath, "utf-8");
18085
18492
  const parsed = parseYaml4(content);
18086
18493
  if (!parsed || typeof parsed !== "object") {
18087
18494
  return { name: "Configuration", status: "fail", detail: "massu.config.yaml is empty or invalid YAML" };
@@ -18093,11 +18500,11 @@ function checkConfig(projectRoot) {
18093
18500
  }
18094
18501
  function checkMcpServer(projectRoot) {
18095
18502
  const mcpPath = getResolvedPaths().mcpJsonPath;
18096
- if (!existsSync22(mcpPath)) {
18503
+ if (!existsSync24(mcpPath)) {
18097
18504
  return { name: "MCP Server", status: "fail", detail: ".mcp.json not found. Run: npx massu init" };
18098
18505
  }
18099
18506
  try {
18100
- const content = JSON.parse(readFileSync16(mcpPath, "utf-8"));
18507
+ const content = JSON.parse(readFileSync18(mcpPath, "utf-8"));
18101
18508
  const servers = content.mcpServers ?? {};
18102
18509
  if (!servers.massu) {
18103
18510
  return { name: "MCP Server", status: "fail", detail: "massu not registered in .mcp.json. Run: npx massu init" };
@@ -18111,7 +18518,7 @@ function readHookSources() {
18111
18518
  const { settingsPath, settingsLocalPath } = getResolvedPaths();
18112
18519
  const out = [];
18113
18520
  for (const [label, p19] of [["settings.json", settingsPath], ["settings.local.json", settingsLocalPath]]) {
18114
- if (!existsSync22(p19)) continue;
18521
+ if (!existsSync24(p19)) continue;
18115
18522
  const content = readSettingsAtPath(p19);
18116
18523
  out.push({ label, hooks: content.hooks });
18117
18524
  }
@@ -18150,11 +18557,11 @@ function checkHooksConfig(_projectRoot2) {
18150
18557
  }
18151
18558
  }
18152
18559
  function checkHookFiles(projectRoot) {
18153
- const nodeModulesHooksDir = resolve11(projectRoot, "node_modules/@massu/core/dist/hooks");
18560
+ const nodeModulesHooksDir = resolve14(projectRoot, "node_modules/@massu/core/dist/hooks");
18154
18561
  let hooksDir = nodeModulesHooksDir;
18155
- if (!existsSync22(nodeModulesHooksDir)) {
18156
- const devHooksDir = resolve11(__dirname4, "../../dist/hooks");
18157
- if (existsSync22(devHooksDir)) {
18562
+ if (!existsSync24(nodeModulesHooksDir)) {
18563
+ const devHooksDir = resolve14(__dirname4, "../../dist/hooks");
18564
+ if (existsSync24(devHooksDir)) {
18158
18565
  hooksDir = devHooksDir;
18159
18566
  } else {
18160
18567
  return { name: "Hook Files", status: "fail", detail: "Compiled hooks not found. Run: npm install @massu/core" };
@@ -18162,7 +18569,7 @@ function checkHookFiles(projectRoot) {
18162
18569
  }
18163
18570
  const missing = [];
18164
18571
  for (const hookFile of EXPECTED_HOOKS) {
18165
- if (!existsSync22(resolve11(hooksDir, hookFile))) {
18572
+ if (!existsSync24(resolve14(hooksDir, hookFile))) {
18166
18573
  missing.push(hookFile);
18167
18574
  }
18168
18575
  }
@@ -18188,12 +18595,12 @@ async function checkHookExecution(projectRoot, opts = {}) {
18188
18595
  detail: `Canary hook file unresolved: ${err instanceof Error ? err.message : String(err)}. Re-run: npx -y @massu/core init`
18189
18596
  };
18190
18597
  }
18191
- const tmpCwd = mkdtempSync2(resolve11(tmpdir2(), "massu-hook-canary-"));
18598
+ const tmpCwd = mkdtempSync2(resolve14(tmpdir2(), "massu-hook-canary-"));
18192
18599
  try {
18193
- const realConfig = resolve11(projectRoot, "massu.config.yaml");
18194
- if (existsSync22(realConfig)) {
18600
+ const realConfig = resolve14(projectRoot, "massu.config.yaml");
18601
+ if (existsSync24(realConfig)) {
18195
18602
  try {
18196
- copyFileSync4(realConfig, resolve11(tmpCwd, "massu.config.yaml"));
18603
+ copyFileSync4(realConfig, resolve14(tmpCwd, "massu.config.yaml"));
18197
18604
  } catch {
18198
18605
  }
18199
18606
  }
@@ -18256,8 +18663,8 @@ function checkNodeVersion2() {
18256
18663
  return { name: "Node.js", status: "fail", detail: `v${version} \u2014 Node.js >= ${floor} is required (node:sqlite engine)` };
18257
18664
  }
18258
18665
  async function checkGitRepo(projectRoot) {
18259
- const gitDir = resolve11(projectRoot, ".git");
18260
- if (!existsSync22(gitDir)) {
18666
+ const gitDir = resolve14(projectRoot, ".git");
18667
+ if (!existsSync24(gitDir)) {
18261
18668
  return { name: "Git Repository", status: "warn", detail: "Not a git repository (optional but recommended)" };
18262
18669
  }
18263
18670
  try {
@@ -18275,7 +18682,7 @@ async function checkGitRepo(projectRoot) {
18275
18682
  }
18276
18683
  function checkKnowledgeDb(projectRoot) {
18277
18684
  const knowledgeDbPath = getResolvedPaths().memoryDbPath;
18278
- if (!existsSync22(knowledgeDbPath)) {
18685
+ if (!existsSync24(knowledgeDbPath)) {
18279
18686
  return {
18280
18687
  name: "Knowledge DB",
18281
18688
  status: "warn",
@@ -18286,7 +18693,7 @@ function checkKnowledgeDb(projectRoot) {
18286
18693
  }
18287
18694
  function checkMemoryDir(_projectRoot2) {
18288
18695
  const memoryDir = getResolvedPaths().memoryDir;
18289
- if (!existsSync22(memoryDir)) {
18696
+ if (!existsSync24(memoryDir)) {
18290
18697
  return {
18291
18698
  name: "Memory Directory",
18292
18699
  status: "warn",
@@ -18396,8 +18803,8 @@ async function checkApiKeyHygiene(projectRoot) {
18396
18803
  function checkPythonHealth(projectRoot) {
18397
18804
  const config = getConfig();
18398
18805
  if (!config.python?.root) return null;
18399
- const pythonRoot = resolve11(projectRoot, config.python.root);
18400
- if (!existsSync22(pythonRoot)) {
18806
+ const pythonRoot = resolve14(projectRoot, config.python.root);
18807
+ if (!existsSync24(pythonRoot)) {
18401
18808
  return {
18402
18809
  name: "Python",
18403
18810
  status: "fail",
@@ -18411,15 +18818,15 @@ function checkPythonHealth(projectRoot) {
18411
18818
  function scanDir(dir, depth) {
18412
18819
  if (depth > 5) return;
18413
18820
  try {
18414
- const entries = readdirSync13(dir, { withFileTypes: true });
18821
+ const entries = readdirSync14(dir, { withFileTypes: true });
18415
18822
  for (const entry of entries) {
18416
18823
  if (entry.isDirectory()) {
18417
18824
  const excludeDirs = config.python?.exclude_dirs || ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"];
18418
18825
  if (!excludeDirs.includes(entry.name)) {
18419
- const subdir = resolve11(dir, entry.name);
18420
- if (depth <= 2 && !existsSync22(resolve11(subdir, "__init__.py"))) {
18826
+ const subdir = resolve14(dir, entry.name);
18827
+ if (depth <= 2 && !existsSync24(resolve14(subdir, "__init__.py"))) {
18421
18828
  try {
18422
- const subEntries = readdirSync13(subdir);
18829
+ const subEntries = readdirSync14(subdir);
18423
18830
  if (subEntries.some((f2) => f2.endsWith(".py") && f2 !== "__init__.py")) {
18424
18831
  initPyMissing.push(entry.name);
18425
18832
  }
@@ -18515,15 +18922,15 @@ async function runDoctor() {
18515
18922
  }
18516
18923
  async function runValidateConfig() {
18517
18924
  const projectRoot = process.cwd();
18518
- const configPath = resolve11(projectRoot, "massu.config.yaml");
18519
- if (!existsSync22(configPath)) {
18925
+ const configPath = resolve14(projectRoot, "massu.config.yaml");
18926
+ if (!existsSync24(configPath)) {
18520
18927
  console.error("Error: massu.config.yaml not found in current directory");
18521
18928
  console.error("Run: npx massu init");
18522
18929
  process.exit(1);
18523
18930
  return;
18524
18931
  }
18525
18932
  try {
18526
- const content = readFileSync16(configPath, "utf-8");
18933
+ const content = readFileSync18(configPath, "utf-8");
18527
18934
  const parsed = parseYaml4(content);
18528
18935
  if (!parsed || typeof parsed !== "object") {
18529
18936
  console.error("Error: massu.config.yaml is empty or not a valid YAML object");
@@ -18873,7 +19280,7 @@ __export(permissions_exports, {
18873
19280
  handlePermissionsSubcommand: () => handlePermissionsSubcommand,
18874
19281
  printPermissionsHelp: () => printPermissionsHelp
18875
19282
  });
18876
- import { resolve as resolve12 } from "path";
19283
+ import { resolve as resolve15 } from "path";
18877
19284
  function resolveClaudeDir2() {
18878
19285
  let claudeDirName = ".claude";
18879
19286
  try {
@@ -18881,7 +19288,7 @@ function resolveClaudeDir2() {
18881
19288
  } catch {
18882
19289
  claudeDirName = ".claude";
18883
19290
  }
18884
- return resolve12(process.cwd(), claudeDirName);
19291
+ return resolve15(process.cwd(), claudeDirName);
18885
19292
  }
18886
19293
  async function handlePermissionsSubcommand(args2) {
18887
19294
  const sub = args2[0];
@@ -18999,7 +19406,7 @@ var db_exports = {};
18999
19406
  __export(db_exports, {
19000
19407
  handleDbSubcommand: () => handleDbSubcommand
19001
19408
  });
19002
- import { existsSync as existsSync23, statSync as statSync11 } from "fs";
19409
+ import { existsSync as existsSync25, statSync as statSync12 } from "fs";
19003
19410
  import { basename as basename7 } from "path";
19004
19411
  function human(bytes) {
19005
19412
  if (bytes < 1024) return `${bytes} B`;
@@ -19044,7 +19451,7 @@ async function handleDbSubcommand(args2) {
19044
19451
 
19045
19452
  `);
19046
19453
  for (const store of allStores()) {
19047
- if (!existsSync23(store.path)) {
19454
+ if (!existsSync25(store.path)) {
19048
19455
  process.stdout.write(` ${"\u2014"} ${store.name.padEnd(10)} not present, skipped
19049
19456
  `);
19050
19457
  continue;
@@ -19142,12 +19549,12 @@ Restore with: massu db restore --latest memory
19142
19549
  target = found.path;
19143
19550
  }
19144
19551
  try {
19145
- const before = existsSync23(target) ? statSync11(target).size : 0;
19552
+ const before = existsSync25(target) ? statSync12(target).size : 0;
19146
19553
  const res = restoreDb(backupPath, target);
19147
19554
  process.stdout.write(
19148
19555
  `Restored ${basename7(target)}
19149
19556
  from : ${res.restoredFrom}
19150
- size : ${human(before)} -> ${human(statSync11(target).size)}
19557
+ size : ${human(before)} -> ${human(statSync12(target).size)}
19151
19558
  ` + (res.preservedOriginal ? ` the previous file was NOT destroyed \u2014 kept at:
19152
19559
  ${res.preservedOriginal}
19153
19560
  ` : "") + ` integrity_check: ok (verified before AND after the restore)
@@ -19308,8 +19715,8 @@ var init_license2 = __esm({
19308
19715
  });
19309
19716
 
19310
19717
  // src/changelog-generator.ts
19311
- import { existsSync as existsSync24, readFileSync as readFileSync17, readdirSync as readdirSync14 } from "fs";
19312
- import { resolve as resolve13 } from "path";
19718
+ import { existsSync as existsSync26, readFileSync as readFileSync19, readdirSync as readdirSync15 } from "fs";
19719
+ import { resolve as resolve16 } from "path";
19313
19720
  function parseCommitsForPlanTokens(subjects) {
19314
19721
  const tokens = /* @__PURE__ */ new Set();
19315
19722
  const maintenance = [];
@@ -19326,16 +19733,16 @@ function parseCommitsForPlanTokens(subjects) {
19326
19733
  function loadPlanSummaries(tokens, planDir) {
19327
19734
  const result = /* @__PURE__ */ new Map();
19328
19735
  if (tokens.size === 0) return result;
19329
- if (!existsSync24(planDir)) {
19736
+ if (!existsSync26(planDir)) {
19330
19737
  throw new Error(`Plan directory does not exist: ${planDir}`);
19331
19738
  }
19332
- const files = readdirSync14(planDir).filter((f2) => f2.endsWith(".md"));
19739
+ const files = readdirSync15(planDir).filter((f2) => f2.endsWith(".md"));
19333
19740
  for (const token of tokens) {
19334
19741
  let matchedFile = null;
19335
19742
  let content = "";
19336
19743
  for (const file of files) {
19337
- const path = resolve13(planDir, file);
19338
- const text18 = readFileSync17(path, "utf-8");
19744
+ const path = resolve16(planDir, file);
19745
+ const text18 = readFileSync19(path, "utf-8");
19339
19746
  const tokenRe = new RegExp(
19340
19747
  `^\\*\\*Plan Token\\*\\*:\\s*\`?${token.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\$&")}\`?(\\s|$)`,
19341
19748
  "m"
@@ -19416,8 +19823,8 @@ __export(changelog_exports, {
19416
19823
  printChangelogHelp: () => printChangelogHelp
19417
19824
  });
19418
19825
  import { execSync } from "child_process";
19419
- import { existsSync as existsSync25, readFileSync as readFileSync18 } from "fs";
19420
- import { resolve as resolve14 } from "path";
19826
+ import { existsSync as existsSync27, readFileSync as readFileSync20 } from "fs";
19827
+ import { resolve as resolve17 } from "path";
19421
19828
  function resolveRepoRoot() {
19422
19829
  try {
19423
19830
  return execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
@@ -19441,25 +19848,25 @@ function getCommitSubjects(range) {
19441
19848
  }
19442
19849
  }
19443
19850
  function getCurrentVersion(repoRoot) {
19444
- const pkgPath = resolve14(repoRoot, "packages/core/package.json");
19445
- if (!existsSync25(pkgPath)) return "0.0.0";
19446
- const pkg = JSON.parse(readFileSync18(pkgPath, "utf-8"));
19851
+ const pkgPath = resolve17(repoRoot, "packages/core/package.json");
19852
+ if (!existsSync27(pkgPath)) return "0.0.0";
19853
+ const pkg = JSON.parse(readFileSync20(pkgPath, "utf-8"));
19447
19854
  return pkg.version || "0.0.0";
19448
19855
  }
19449
19856
  function todayDate() {
19450
19857
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
19451
19858
  }
19452
19859
  function getLatestChangelogEntryBody(repoRoot) {
19453
- const path = resolve14(repoRoot, "CHANGELOG.md");
19454
- if (!existsSync25(path)) return "";
19455
- const content = readFileSync18(path, "utf-8");
19860
+ const path = resolve17(repoRoot, "CHANGELOG.md");
19861
+ if (!existsSync27(path)) return "";
19862
+ const content = readFileSync20(path, "utf-8");
19456
19863
  const m3 = content.match(/^## \[[\d.]+\][^\n]*\n([\s\S]*?)(?=\n## \[|$)/m);
19457
19864
  return m3 ? m3[1] : "";
19458
19865
  }
19459
19866
  async function handleChangelogSubcommand(args2) {
19460
19867
  const sub = args2[0];
19461
19868
  const repoRoot = resolveRepoRoot();
19462
- const planDir = resolve14(repoRoot, "docs/plans");
19869
+ const planDir = resolve17(repoRoot, "docs/plans");
19463
19870
  switch (sub) {
19464
19871
  case "generate": {
19465
19872
  const lastTag = getLastTag();
@@ -19548,11 +19955,11 @@ var init_changelog = __esm({
19548
19955
 
19549
19956
  // src/knowledge-db.ts
19550
19957
  import { dirname as dirname15 } from "path";
19551
- import { existsSync as existsSync26, mkdirSync as mkdirSync11 } from "fs";
19958
+ import { existsSync as existsSync28, mkdirSync as mkdirSync11 } from "fs";
19552
19959
  function getKnowledgeDb() {
19553
19960
  const dbPath = getResolvedPaths().knowledgeDbPath;
19554
19961
  const dir = dirname15(dbPath);
19555
- if (!existsSync26(dir)) {
19962
+ if (!existsSync28(dir)) {
19556
19963
  mkdirSync11(dir, { recursive: true });
19557
19964
  }
19558
19965
  const db = openDatabase2(dbPath);
@@ -19779,7 +20186,7 @@ var memory_embed_backfill_exports = {};
19779
20186
  __export(memory_embed_backfill_exports, {
19780
20187
  runMemoryEmbedBackfill: () => runMemoryEmbedBackfill
19781
20188
  });
19782
- import { existsSync as existsSync27 } from "fs";
20189
+ import { existsSync as existsSync29 } from "fs";
19783
20190
  async function runMemoryEmbedBackfill(_args = []) {
19784
20191
  await embed("massu memory embed backfill probe");
19785
20192
  const active = getActiveEmbedModel();
@@ -19806,7 +20213,7 @@ async function runMemoryEmbedBackfill(_args = []) {
19806
20213
  );
19807
20214
  let chunks = { embedded: 0, scanned: 0 };
19808
20215
  const knowledgeDbPath = getResolvedPaths().knowledgeDbPath;
19809
- if (existsSync27(knowledgeDbPath)) {
20216
+ if (existsSync29(knowledgeDbPath)) {
19810
20217
  const knowledgeDb = getKnowledgeDb();
19811
20218
  try {
19812
20219
  chunks = await embedMissingChunks(knowledgeDb);
@@ -19839,17 +20246,17 @@ var init_memory_embed_backfill = __esm({
19839
20246
 
19840
20247
  // src/memory-authorship.ts
19841
20248
  import { createHmac, randomBytes, timingSafeEqual } from "crypto";
19842
- import { readFileSync as readFileSync19, writeFileSync as writeFileSync6, existsSync as existsSync28, mkdirSync as mkdirSync12, chmodSync as chmodSync6, statSync as statSync12 } from "fs";
20249
+ import { readFileSync as readFileSync21, writeFileSync as writeFileSync6, existsSync as existsSync30, mkdirSync as mkdirSync12, chmodSync as chmodSync6, statSync as statSync13 } from "fs";
19843
20250
  import { homedir as homedir8 } from "os";
19844
- import { resolve as resolve15 } from "path";
20251
+ import { resolve as resolve18 } from "path";
19845
20252
  function renderKeyPath(home = homedir8()) {
19846
- return resolve15(home, ".massu", "render-key");
20253
+ return resolve18(home, ".massu", "render-key");
19847
20254
  }
19848
20255
  function readRenderKey(home = homedir8()) {
19849
20256
  const p19 = renderKeyPath(home);
19850
20257
  try {
19851
- if (!existsSync28(p19)) return void 0;
19852
- const raw = readFileSync19(p19);
20258
+ if (!existsSync30(p19)) return void 0;
20259
+ const raw = readFileSync21(p19);
19853
20260
  if (raw.length !== 32) return void 0;
19854
20261
  return raw;
19855
20262
  } catch {
@@ -19861,7 +20268,7 @@ function ensureRenderKey(home = homedir8()) {
19861
20268
  if (existing) return existing;
19862
20269
  const p19 = renderKeyPath(home);
19863
20270
  try {
19864
- const dir = resolve15(home, ".massu");
20271
+ const dir = resolve18(home, ".massu");
19865
20272
  mkdirSync12(dir, { recursive: true, mode: 448 });
19866
20273
  try {
19867
20274
  chmodSync6(dir, 448);
@@ -19919,11 +20326,11 @@ var init_memory_authorship = __esm({
19919
20326
  });
19920
20327
 
19921
20328
  // src/rule-candidate-snapshot.ts
19922
- import { existsSync as existsSync29, readFileSync as readFileSync20, writeFileSync as writeFileSync7, unlinkSync as unlinkSync4 } from "fs";
20329
+ import { existsSync as existsSync31, readFileSync as readFileSync22, writeFileSync as writeFileSync7, unlinkSync as unlinkSync4 } from "fs";
19923
20330
  function takeSnapshots(paths) {
19924
20331
  const out = /* @__PURE__ */ new Map();
19925
20332
  for (const p19 of paths) {
19926
- if (existsSync29(p19)) out.set(p19, readFileSync20(p19, "utf-8"));
20333
+ if (existsSync31(p19)) out.set(p19, readFileSync22(p19, "utf-8"));
19927
20334
  else out.set(p19, null);
19928
20335
  }
19929
20336
  return out;
@@ -19933,7 +20340,7 @@ function restoreSnapshots(snapshot) {
19933
20340
  for (const [path, content] of snapshot) {
19934
20341
  try {
19935
20342
  if (content === null) {
19936
- if (existsSync29(path)) unlinkSync4(path);
20343
+ if (existsSync31(path)) unlinkSync4(path);
19937
20344
  } else {
19938
20345
  writeFileSync7(path, content, "utf-8");
19939
20346
  }
@@ -20013,7 +20420,7 @@ var init_memory_render_path = __esm({
20013
20420
  });
20014
20421
 
20015
20422
  // src/memory-tombstones.ts
20016
- import { readFileSync as readFileSync21, appendFileSync as appendFileSync3, existsSync as existsSync30 } from "fs";
20423
+ import { readFileSync as readFileSync23, appendFileSync as appendFileSync3, existsSync as existsSync32 } from "fs";
20017
20424
  import { join as join16 } from "path";
20018
20425
  function tombstoneLedgerPath(memoryDir) {
20019
20426
  return join16(memoryDir, TOMBSTONE_LEDGER);
@@ -20021,10 +20428,10 @@ function tombstoneLedgerPath(memoryDir) {
20021
20428
  function readTombstones(memoryDir) {
20022
20429
  const out = /* @__PURE__ */ new Map();
20023
20430
  const p19 = tombstoneLedgerPath(memoryDir);
20024
- if (!existsSync30(p19)) return out;
20431
+ if (!existsSync32(p19)) return out;
20025
20432
  let raw;
20026
20433
  try {
20027
- raw = readFileSync21(p19, "utf8");
20434
+ raw = readFileSync23(p19, "utf8");
20028
20435
  } catch {
20029
20436
  return out;
20030
20437
  }
@@ -20263,29 +20670,29 @@ var init_memory_llm = __esm({
20263
20670
 
20264
20671
  // src/memory-backup.ts
20265
20672
  import {
20266
- readdirSync as readdirSync15,
20673
+ readdirSync as readdirSync16,
20267
20674
  mkdirSync as mkdirSync13,
20268
20675
  copyFileSync as copyFileSync2,
20269
- statSync as statSync13,
20270
- existsSync as existsSync31,
20676
+ statSync as statSync14,
20677
+ existsSync as existsSync33,
20271
20678
  rmSync as rmSync7,
20272
- readFileSync as readFileSync22
20679
+ readFileSync as readFileSync24
20273
20680
  } from "fs";
20274
20681
  import { homedir as homedir9 } from "os";
20275
- import { join as join17, resolve as resolve16 } from "path";
20682
+ import { join as join17, resolve as resolve19 } from "path";
20276
20683
  function backupsRoot(home = homedir9()) {
20277
- return resolve16(home, ".massu", "memory-backups");
20684
+ return resolve19(home, ".massu", "memory-backups");
20278
20685
  }
20279
20686
  function backupStamp2(nowMs) {
20280
20687
  return new Date(nowMs).toISOString().replace(/:/g, "-");
20281
20688
  }
20282
20689
  function newestMtimeMs(dir) {
20283
- if (!existsSync31(dir)) return 0;
20690
+ if (!existsSync33(dir)) return 0;
20284
20691
  let newest = 0;
20285
- for (const entry of readdirSync15(dir)) {
20692
+ for (const entry of readdirSync16(dir)) {
20286
20693
  const p19 = join17(dir, entry);
20287
20694
  try {
20288
- const st = statSync13(p19);
20695
+ const st = statSync14(p19);
20289
20696
  if (st.isFile() && st.mtimeMs > newest) newest = st.mtimeMs;
20290
20697
  } catch {
20291
20698
  continue;
@@ -20295,12 +20702,12 @@ function newestMtimeMs(dir) {
20295
20702
  }
20296
20703
  function listBackups(home = homedir9()) {
20297
20704
  const root = backupsRoot(home);
20298
- if (!existsSync31(root)) return [];
20705
+ if (!existsSync33(root)) return [];
20299
20706
  const out = [];
20300
- for (const stamp of readdirSync15(root)) {
20707
+ for (const stamp of readdirSync16(root)) {
20301
20708
  const dir = join17(root, stamp);
20302
20709
  try {
20303
- const st = statSync13(dir);
20710
+ const st = statSync14(dir);
20304
20711
  if (!st.isDirectory()) continue;
20305
20712
  out.push({ stamp, dir, createdMs: st.mtimeMs });
20306
20713
  } catch {
@@ -20316,17 +20723,17 @@ function hasFreshBackup(memoryDir, home = homedir9()) {
20316
20723
  return backups[0].createdMs >= newestChange;
20317
20724
  }
20318
20725
  function takeBackup(memoryDir, nowMs, home = homedir9(), retention = DEFAULT_RETENTION2) {
20319
- if (!existsSync31(memoryDir)) {
20726
+ if (!existsSync33(memoryDir)) {
20320
20727
  throw new BackupError(`memory directory does not exist: ${memoryDir}`);
20321
20728
  }
20322
20729
  const stamp = backupStamp2(nowMs);
20323
20730
  const dir = join17(backupsRoot(home), stamp);
20324
20731
  try {
20325
20732
  mkdirSync13(dir, { recursive: true, mode: 448 });
20326
- for (const entry of readdirSync15(memoryDir)) {
20733
+ for (const entry of readdirSync16(memoryDir)) {
20327
20734
  const srcPath = join17(memoryDir, entry);
20328
20735
  try {
20329
- if (!statSync13(srcPath).isFile()) continue;
20736
+ if (!statSync14(srcPath).isFile()) continue;
20330
20737
  } catch {
20331
20738
  continue;
20332
20739
  }
@@ -20354,16 +20761,16 @@ function restoreBackup(memoryDir, opts = {}) {
20354
20761
  const chosen = opts.from ? backups.find((b2) => b2.stamp === opts.from) : backups[0];
20355
20762
  if (!chosen) throw new BackupError(`no backup with stamp: ${opts.from}`);
20356
20763
  const wouldRestore = [];
20357
- for (const entry of readdirSync15(chosen.dir)) {
20764
+ for (const entry of readdirSync16(chosen.dir)) {
20358
20765
  try {
20359
- if (statSync13(join17(chosen.dir, entry)).isFile()) wouldRestore.push(entry);
20766
+ if (statSync14(join17(chosen.dir, entry)).isFile()) wouldRestore.push(entry);
20360
20767
  } catch {
20361
20768
  continue;
20362
20769
  }
20363
20770
  }
20364
- const currentFiles = existsSync31(memoryDir) ? readdirSync15(memoryDir).filter((e2) => {
20771
+ const currentFiles = existsSync33(memoryDir) ? readdirSync16(memoryDir).filter((e2) => {
20365
20772
  try {
20366
- return statSync13(join17(memoryDir, e2)).isFile();
20773
+ return statSync14(join17(memoryDir, e2)).isFile();
20367
20774
  } catch {
20368
20775
  return false;
20369
20776
  }
@@ -20407,7 +20814,7 @@ var init_memory_backup = __esm({
20407
20814
  });
20408
20815
 
20409
20816
  // src/memory-index-region.ts
20410
- import { readFileSync as readFileSync23, existsSync as existsSync32 } from "fs";
20817
+ import { readFileSync as readFileSync25, existsSync as existsSync34 } from "fs";
20411
20818
  import { dirname as dirname16, join as join18 } from "path";
20412
20819
  function parseRegion(content) {
20413
20820
  const begins = countOccurrences(content, BEGIN_SENTINEL);
@@ -20512,9 +20919,9 @@ function withMemoryIndexLock(memoryDir, fn) {
20512
20919
  });
20513
20920
  }
20514
20921
  function readMemoryIndex(indexPath) {
20515
- if (!existsSync32(indexPath)) return void 0;
20922
+ if (!existsSync34(indexPath)) return void 0;
20516
20923
  try {
20517
- return readFileSync23(indexPath, "utf8");
20924
+ return readFileSync25(indexPath, "utf8");
20518
20925
  } catch {
20519
20926
  return void 0;
20520
20927
  }
@@ -20585,7 +20992,7 @@ var init_memory_files_config = __esm({
20585
20992
  });
20586
20993
 
20587
20994
  // src/memory-renderer.ts
20588
- import { existsSync as existsSync33, readFileSync as readFileSync24 } from "fs";
20995
+ import { existsSync as existsSync35, readFileSync as readFileSync26 } from "fs";
20589
20996
  import { join as join19 } from "path";
20590
20997
  import { homedir as homedir10 } from "os";
20591
20998
  import { createHash as createHash8 } from "crypto";
@@ -20600,7 +21007,7 @@ function renderMemoryFiles(db, candidates, opts) {
20600
21007
  const audit = opts.audit ?? (() => {
20601
21008
  });
20602
21009
  const { memoryDir } = opts;
20603
- if (!existsSync33(memoryDir)) {
21010
+ if (!existsSync35(memoryDir)) {
20604
21011
  return EMPTY("no_memory_dir", candidates.length);
20605
21012
  }
20606
21013
  try {
@@ -20671,8 +21078,8 @@ function renderLocked(db, candidates, o) {
20671
21078
  refusals.push({ name: c2.name, reason: "tombstoned" });
20672
21079
  continue;
20673
21080
  }
20674
- if (existsSync33(absPath)) {
20675
- const existing = readFileSync24(absPath, "utf8");
21081
+ if (existsSync35(absPath)) {
21082
+ const existing = readFileSync26(absPath, "utf8");
20676
21083
  const fm = readMemoryFileFrontmatter(existing);
20677
21084
  const storeRow = db.prepare(
20678
21085
  `SELECT massu_authored, massu_render_mac, adopted_human_at_epoch
@@ -20932,16 +21339,16 @@ var init_memory_renderer = __esm({
20932
21339
  });
20933
21340
 
20934
21341
  // src/commands/memory-render-cli.ts
20935
- import { readdirSync as readdirSync16, readFileSync as readFileSync25, existsSync as existsSync34, rmSync as rmSync8, statSync as statSync14 } from "fs";
21342
+ import { readdirSync as readdirSync17, readFileSync as readFileSync27, existsSync as existsSync36, rmSync as rmSync8, statSync as statSync15 } from "fs";
20936
21343
  import { join as join20 } from "path";
20937
21344
  import { homedir as homedir11 } from "os";
20938
21345
  function memoryFiles(memoryDir) {
20939
- if (!existsSync34(memoryDir)) return [];
20940
- return readdirSync16(memoryDir).filter((f2) => {
21346
+ if (!existsSync36(memoryDir)) return [];
21347
+ return readdirSync17(memoryDir).filter((f2) => {
20941
21348
  if (!f2.endsWith(".md")) return false;
20942
21349
  if (f2 === "MEMORY.md") return false;
20943
21350
  try {
20944
- return statSync14(join20(memoryDir, f2)).isFile();
21351
+ return statSync15(join20(memoryDir, f2)).isFile();
20945
21352
  } catch {
20946
21353
  return false;
20947
21354
  }
@@ -20952,7 +21359,7 @@ function massuAuthoredFiles(db, memoryDir, home = homedir11()) {
20952
21359
  for (const f2 of memoryFiles(memoryDir)) {
20953
21360
  let content;
20954
21361
  try {
20955
- content = readFileSync25(join20(memoryDir, f2), "utf8");
21362
+ content = readFileSync27(join20(memoryDir, f2), "utf8");
20956
21363
  } catch {
20957
21364
  continue;
20958
21365
  }
@@ -20980,7 +21387,7 @@ function memoryAdopt(db, memoryDir, opts = {}) {
20980
21387
  for (const f2 of memoryFiles(memoryDir)) {
20981
21388
  const row = db.prepare(`SELECT id FROM memory_files WHERE rel_path = ? COLLATE NOCASE`).get(f2);
20982
21389
  if (row) continue;
20983
- const content = readFileSync25(join20(memoryDir, f2), "utf8");
21390
+ const content = readFileSync27(join20(memoryDir, f2), "utf8");
20984
21391
  if (verifyAuthorship(stripFrontmatter(content), readMemoryFileFrontmatter(content), null, home)) {
20985
21392
  candidates.push(f2);
20986
21393
  }
@@ -21010,7 +21417,7 @@ function memoryAdopt(db, memoryDir, opts = {}) {
21010
21417
  db.exec("BEGIN");
21011
21418
  try {
21012
21419
  for (const f2 of candidates) {
21013
- const content = readFileSync25(join20(memoryDir, f2), "utf8");
21420
+ const content = readFileSync27(join20(memoryDir, f2), "utf8");
21014
21421
  const fm = readMemoryFileFrontmatter(content);
21015
21422
  insert.run(
21016
21423
  f2,
@@ -21175,7 +21582,7 @@ var init_memory_render_candidates = __esm({
21175
21582
  });
21176
21583
 
21177
21584
  // src/capability-advisor.ts
21178
- import { existsSync as existsSync35, mkdirSync as mkdirSync14, readFileSync as readFileSync26, writeFileSync as writeFileSync8 } from "fs";
21585
+ import { existsSync as existsSync37, mkdirSync as mkdirSync14, readFileSync as readFileSync28, writeFileSync as writeFileSync8 } from "fs";
21179
21586
  import { homedir as homedir12 } from "os";
21180
21587
  import { dirname as dirname17, join as join21 } from "path";
21181
21588
  function advisorStatePath(home = homedir12()) {
@@ -21184,8 +21591,8 @@ function advisorStatePath(home = homedir12()) {
21184
21591
  function readAdvisorState(home) {
21185
21592
  try {
21186
21593
  const p19 = advisorStatePath(home);
21187
- if (!existsSync35(p19)) return {};
21188
- return JSON.parse(readFileSync26(p19, "utf-8"));
21594
+ if (!existsSync37(p19)) return {};
21595
+ return JSON.parse(readFileSync28(p19, "utf-8"));
21189
21596
  } catch {
21190
21597
  return {};
21191
21598
  }
@@ -21433,9 +21840,9 @@ var init_memory_repo_identity = __esm({
21433
21840
  // src/memory-repos-registry.ts
21434
21841
  import {
21435
21842
  chmodSync as chmodSync7,
21436
- existsSync as existsSync36,
21843
+ existsSync as existsSync38,
21437
21844
  mkdirSync as mkdirSync15,
21438
- readFileSync as readFileSync27,
21845
+ readFileSync as readFileSync29,
21439
21846
  writeFileSync as writeFileSync9
21440
21847
  } from "fs";
21441
21848
  import { homedir as homedir13 } from "os";
@@ -21448,9 +21855,9 @@ function emptyRegistry() {
21448
21855
  }
21449
21856
  function readReposRegistry(home = homedir13()) {
21450
21857
  const p19 = reposRegistryPath(home);
21451
- if (!existsSync36(p19)) return emptyRegistry();
21858
+ if (!existsSync38(p19)) return emptyRegistry();
21452
21859
  try {
21453
- const parsed = JSON.parse(readFileSync27(p19, "utf-8"));
21860
+ const parsed = JSON.parse(readFileSync29(p19, "utf-8"));
21454
21861
  if (!parsed || typeof parsed !== "object") return emptyRegistry();
21455
21862
  const r2 = parsed;
21456
21863
  if (!Array.isArray(r2.repos)) return emptyRegistry();
@@ -21498,9 +21905,9 @@ var init_shared_memory_envelope = __esm({
21498
21905
  // src/security/local-share-signer.ts
21499
21906
  import {
21500
21907
  chmodSync as chmodSync8,
21501
- existsSync as existsSync37,
21908
+ existsSync as existsSync39,
21502
21909
  mkdirSync as mkdirSync16,
21503
- readFileSync as readFileSync28,
21910
+ readFileSync as readFileSync30,
21504
21911
  writeFileSync as writeFileSync10
21505
21912
  } from "fs";
21506
21913
  import { homedir as homedir14 } from "os";
@@ -21519,7 +21926,7 @@ function localSharePubKeyPath(home = homedir14()) {
21519
21926
  return join23(localShareKeyDir(home), "local-share.pub");
21520
21927
  }
21521
21928
  function readLocalSharePublicKeyRaw(home = homedir14()) {
21522
- const pubPem = readFileSync28(localSharePubKeyPath(home), "utf-8");
21929
+ const pubPem = readFileSync30(localSharePubKeyPath(home), "utf-8");
21523
21930
  const der = createPublicKey2(pubPem).export({ format: "der", type: "spki" });
21524
21931
  return Uint8Array.from(der.subarray(SPKI_ED25519_PREFIX_LEN));
21525
21932
  }
@@ -21938,26 +22345,6 @@ var init_memory_share_cli = __esm({
21938
22345
  }
21939
22346
  });
21940
22347
 
21941
- // src/lib/corrections-md.ts
21942
- function formatCorrectionEntry(e2) {
21943
- const one = (s) => s.replace(/\s*\n+\s*/g, " ").trim();
21944
- const lines = [`### ${e2.date} - ${one(e2.title)}`];
21945
- if (e2.wrong) lines.push(`- **Wrong**: ${one(e2.wrong)}`);
21946
- if (e2.correction) lines.push(`- **Correction**: ${one(e2.correction)}`);
21947
- lines.push(`- **Rule**: ${one(e2.rule)}`);
21948
- for (const [k3, v3] of Object.entries(e2.extra ?? {})) {
21949
- lines.push(`- ${k3}: ${one(String(v3))}`);
21950
- }
21951
- return `
21952
- ${lines.join("\n")}
21953
- `;
21954
- }
21955
- var init_corrections_md = __esm({
21956
- "src/lib/corrections-md.ts"() {
21957
- "use strict";
21958
- }
21959
- });
21960
-
21961
22348
  // src/audit-trail.ts
21962
22349
  function p2(baseName) {
21963
22350
  return `${getConfig().toolPrefix}_${baseName}`;
@@ -22458,7 +22845,7 @@ var init_promotion_apply_mac = __esm({
22458
22845
  });
22459
22846
 
22460
22847
  // src/rule-candidate-applier.ts
22461
- import { readFileSync as readFileSync29, writeFileSync as writeFileSync11, existsSync as existsSync38, unlinkSync as unlinkSync5, mkdirSync as mkdirSync17 } from "fs";
22848
+ import { readFileSync as readFileSync31, writeFileSync as writeFileSync11, existsSync as existsSync40, unlinkSync as unlinkSync5, mkdirSync as mkdirSync17 } from "fs";
22462
22849
  import { join as join24, dirname as dirname19, isAbsolute as isAbsolute2 } from "path";
22463
22850
  import { homedir as homedir19 } from "os";
22464
22851
  import { createHash as createHash11 } from "crypto";
@@ -22491,7 +22878,7 @@ function writeIndexLineInRegion(indexPath, line) {
22491
22878
  function correctionsMdTargets(slug, projectRoot, home) {
22492
22879
  const dir = resolveMemoryDir(projectRoot, home);
22493
22880
  return {
22494
- correctionsMd: join24(dir, "corrections.md"),
22881
+ correctionsMd: correctionsPath(dir),
22495
22882
  feedbackMd: join24(dir, `feedback_${slug}.md`),
22496
22883
  memoryIndex: join24(dir, "MEMORY.md")
22497
22884
  };
@@ -22501,8 +22888,8 @@ function readCandidate(candidateId, projectRoot = getProjectRoot()) {
22501
22888
  throw new InvalidCandidateIdError(candidateId);
22502
22889
  }
22503
22890
  const path = join24(projectRoot, ".massu", "rule-candidates", `${candidateId}.json`);
22504
- if (!existsSync38(path)) throw new CandidateNotFoundError(path);
22505
- const raw = readFileSync29(path, "utf-8");
22891
+ if (!existsSync40(path)) throw new CandidateNotFoundError(path);
22892
+ const raw = readFileSync31(path, "utf-8");
22506
22893
  let parsed;
22507
22894
  try {
22508
22895
  parsed = JSON.parse(raw);
@@ -22645,7 +23032,7 @@ async function applyRuleCandidate(db, opts) {
22645
23032
  throw new InvalidSlugError(slug);
22646
23033
  }
22647
23034
  const memoryIndex = resolveMemoryIndexPath(projectRoot, home);
22648
- if (!existsSync38(memoryIndex)) {
23035
+ if (!existsSync40(memoryIndex)) {
22649
23036
  throw new MemoryIndexMissingError(memoryIndex);
22650
23037
  }
22651
23038
  let destTargets;
@@ -22701,7 +23088,7 @@ async function applyRuleCandidate(db, opts) {
22701
23088
  throw err;
22702
23089
  }
22703
23090
  }
22704
- if (existsSync38(candidatePath)) unlinkSync5(candidatePath);
23091
+ if (existsSync40(candidatePath)) unlinkSync5(candidatePath);
22705
23092
  let teamShared = false;
22706
23093
  const localOrigin = candidate.provenance === void 0;
22707
23094
  const entitledTeam = entitledForTeamSharedPromotion(ent.currentTier);
@@ -22792,7 +23179,7 @@ function writeDestination(opts, candidate, slug, targets, auditRowId) {
22792
23179
  function writeCorrectionsMd(opts, candidate, slug, targets, auditRowId) {
22793
23180
  const [correctionsMd, feedbackMd] = targets.paths;
22794
23181
  const dateIso = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
22795
- const correctionsEntry = formatCorrectionEntry({
23182
+ const correctionsEntryData = {
22796
23183
  date: dateIso,
22797
23184
  title: slug,
22798
23185
  rule: opts.draftText,
@@ -22801,10 +23188,10 @@ function writeCorrectionsMd(opts, candidate, slug, targets, auditRowId) {
22801
23188
  score: candidate.score,
22802
23189
  audit_log_id: auditRowId
22803
23190
  }
22804
- });
22805
- if (!existsSync38(dirname19(correctionsMd))) mkdirSync17(dirname19(correctionsMd), { recursive: true });
22806
- const correctionsPre = existsSync38(correctionsMd) ? readFileSync29(correctionsMd, "utf-8") : "# Corrections\n";
22807
- writeFileSync11(correctionsMd, correctionsPre + correctionsEntry, "utf-8");
23191
+ };
23192
+ if (!existsSync40(dirname19(correctionsMd))) mkdirSync17(dirname19(correctionsMd), { recursive: true });
23193
+ const correctionsPre = existsSync40(correctionsMd) ? readFileSync31(correctionsMd, "utf-8") : "";
23194
+ writeFileSync11(correctionsMd, insertCorrectionEntry(correctionsPre, correctionsEntryData), "utf-8");
22808
23195
  const body = `${opts.draftText}
22809
23196
  `;
22810
23197
  const mac = mintAuthorship(body);
@@ -22829,7 +23216,7 @@ function writeCorrectionsMd(opts, candidate, slug, targets, auditRowId) {
22829
23216
  function writePatternScanner(opts, candidate, slug, targets) {
22830
23217
  const [scannerPath, driftGuardPath] = targets.paths;
22831
23218
  const checkNumber = opts.patternScannerCheckNumber;
22832
- const scannerPre = existsSync38(scannerPath) ? readFileSync29(scannerPath, "utf-8") : "";
23219
+ const scannerPre = existsSync40(scannerPath) ? readFileSync31(scannerPath, "utf-8") : "";
22833
23220
  if (scannerPre.includes(`${SCANNER_AUTOGEN_MARKER_PREFIX} ${candidate.prompt_hash}`)) {
22834
23221
  throw new DestinationConfigError(
22835
23222
  `scanner already contains a check auto-generated for prompt_hash ${candidate.prompt_hash}`
@@ -22865,13 +23252,13 @@ function writePatternScanner(opts, candidate, slug, targets) {
22865
23252
  `});`,
22866
23253
  ""
22867
23254
  ].join("\n");
22868
- if (!existsSync38(dirname19(driftGuardPath))) mkdirSync17(dirname19(driftGuardPath), { recursive: true });
23255
+ if (!existsSync40(dirname19(driftGuardPath))) mkdirSync17(dirname19(driftGuardPath), { recursive: true });
22869
23256
  writeFileSync11(driftGuardPath, driftGuard, "utf-8");
22870
23257
  }
22871
23258
  function writeClaudeMdCr(opts, candidate, slug, targets) {
22872
23259
  const [claudePath] = targets.paths;
22873
23260
  const crNumber = opts.claudeMdCrNumber;
22874
- const pre = existsSync38(claudePath) ? readFileSync29(claudePath, "utf-8") : "";
23261
+ const pre = existsSync40(claudePath) ? readFileSync31(claudePath, "utf-8") : "";
22875
23262
  if (new RegExp(`\\bCR-${crNumber}\\b`).test(pre)) {
22876
23263
  throw new DestinationConfigError(`CLAUDE.md already contains a CR-${crNumber} entry`);
22877
23264
  }
@@ -22913,8 +23300,8 @@ function writeCustomDestination(opts, candidate, slug, targets) {
22913
23300
  destination_name: entry.name
22914
23301
  };
22915
23302
  const rendered = renderTemplate2(entry.template, vars);
22916
- if (!existsSync38(dirname19(destPath))) mkdirSync17(dirname19(destPath), { recursive: true });
22917
- const pre = existsSync38(destPath) ? readFileSync29(destPath, "utf-8") : "";
23303
+ if (!existsSync40(dirname19(destPath))) mkdirSync17(dirname19(destPath), { recursive: true });
23304
+ const pre = existsSync40(destPath) ? readFileSync31(destPath, "utf-8") : "";
22918
23305
  const sep4 = pre && !pre.endsWith("\n") ? "\n" : "";
22919
23306
  writeFileSync11(destPath, pre + sep4 + rendered + (rendered.endsWith("\n") ? "" : "\n"), "utf-8");
22920
23307
  }
@@ -22967,8 +23354,8 @@ function dismissRuleCandidate(db, opts) {
22967
23354
  }
22968
23355
  try {
22969
23356
  const dismissedLog = join24(projectRoot, ".massu", "rule-candidates", ".dismissed.jsonl");
22970
- if (!existsSync38(dirname19(dismissedLog))) mkdirSync17(dirname19(dismissedLog), { recursive: true });
22971
- const pre = existsSync38(dismissedLog) ? readFileSync29(dismissedLog, "utf-8") : "";
23357
+ if (!existsSync40(dirname19(dismissedLog))) mkdirSync17(dirname19(dismissedLog), { recursive: true });
23358
+ const pre = existsSync40(dismissedLog) ? readFileSync31(dismissedLog, "utf-8") : "";
22972
23359
  const sep4 = pre && !pre.endsWith("\n") ? "\n" : "";
22973
23360
  writeFileSync11(dismissedLog, pre + sep4 + JSON.stringify({
22974
23361
  id: opts.candidateId,
@@ -22979,7 +23366,7 @@ function dismissRuleCandidate(db, opts) {
22979
23366
  audit_log_id: auditRowId,
22980
23367
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
22981
23368
  }) + "\n", "utf-8");
22982
- if (existsSync38(candidatePath)) unlinkSync5(candidatePath);
23369
+ if (existsSync40(candidatePath)) unlinkSync5(candidatePath);
22983
23370
  } catch (err) {
22984
23371
  appendFailureLog(projectRoot, {
22985
23372
  candidate_id: opts.candidateId,
@@ -22998,9 +23385,9 @@ function dismissRuleCandidate(db, opts) {
22998
23385
  function appendFailureLog(projectRoot, entry) {
22999
23386
  try {
23000
23387
  const dir = join24(projectRoot, ".massu", "rule-candidates");
23001
- if (!existsSync38(dir)) mkdirSync17(dir, { recursive: true });
23388
+ if (!existsSync40(dir)) mkdirSync17(dir, { recursive: true });
23002
23389
  const logPath = join24(dir, ".failures.jsonl");
23003
- const pre = existsSync38(logPath) ? readFileSync29(logPath, "utf-8") : "";
23390
+ const pre = existsSync40(logPath) ? readFileSync31(logPath, "utf-8") : "";
23004
23391
  const sep4 = pre && !pre.endsWith("\n") ? "\n" : "";
23005
23392
  writeFileSync11(logPath, pre + sep4 + JSON.stringify(entry) + "\n", "utf-8");
23006
23393
  } catch {
@@ -23085,7 +23472,7 @@ var init_rule_candidate_applier = __esm({
23085
23472
  });
23086
23473
 
23087
23474
  // src/rule-candidate-preview.ts
23088
- import { readFileSync as readFileSync30, writeFileSync as writeFileSync12, existsSync as existsSync39 } from "node:fs";
23475
+ import { readFileSync as readFileSync32, writeFileSync as writeFileSync12, existsSync as existsSync41 } from "node:fs";
23089
23476
  function renderHardenedPreview(destination, body) {
23090
23477
  const rendered = String(body ?? "");
23091
23478
  const riskFindings = RISK_PATTERNS.filter((p19) => p19.re.test(rendered)).map((p19) => p19.label);
@@ -23113,11 +23500,11 @@ function validateReviewAttestation(att) {
23113
23500
  };
23114
23501
  }
23115
23502
  function recordHardenedReviewAttestation(sidecarPath2, attestation) {
23116
- if (!existsSync39(sidecarPath2)) {
23503
+ if (!existsSync41(sidecarPath2)) {
23117
23504
  throw new Error(`candidate sidecar not found: ${sidecarPath2}`);
23118
23505
  }
23119
23506
  const validated = validateReviewAttestation(attestation);
23120
- const raw = readFileSync30(sidecarPath2, "utf-8");
23507
+ const raw = readFileSync32(sidecarPath2, "utf-8");
23121
23508
  const sidecar = JSON.parse(raw);
23122
23509
  const prov = sidecar.provenance;
23123
23510
  if (!prov || prov.origin !== "team" || prov.hardened !== true) {
@@ -23225,7 +23612,7 @@ var init_rule_candidate_renderer = __esm({
23225
23612
  });
23226
23613
 
23227
23614
  // src/rule-promotion-effectiveness.ts
23228
- import { readFileSync as readFileSync31, existsSync as existsSync40 } from "fs";
23615
+ import { readFileSync as readFileSync33, existsSync as existsSync42 } from "fs";
23229
23616
  function evaluateCr53Effectiveness(inputs) {
23230
23617
  const { db, failureLogPath, knownLimitations } = inputs;
23231
23618
  const allowlist = new Set(knownLimitations.map((k3) => k3.promptHash));
@@ -23256,9 +23643,9 @@ function evaluateCr53Effectiveness(inputs) {
23256
23643
  });
23257
23644
  }
23258
23645
  const failureLogViolations = [];
23259
- if (existsSync40(failureLogPath)) {
23646
+ if (existsSync42(failureLogPath)) {
23260
23647
  try {
23261
- const raw = readFileSync31(failureLogPath, "utf-8");
23648
+ const raw = readFileSync33(failureLogPath, "utf-8");
23262
23649
  const cutoff = Date.now() - SEVEN_DAYS_MS;
23263
23650
  for (const line of raw.split("\n")) {
23264
23651
  const trimmed = line.trim();
@@ -23435,7 +23822,7 @@ var init_rule_pack_schema = __esm({
23435
23822
  });
23436
23823
 
23437
23824
  // src/rule-pack-sync.ts
23438
- import { existsSync as existsSync41, writeFileSync as writeFileSync13, mkdirSync as mkdirSync18 } from "fs";
23825
+ import { existsSync as existsSync43, writeFileSync as writeFileSync13, mkdirSync as mkdirSync18 } from "fs";
23439
23826
  import { homedir as homedir20 } from "os";
23440
23827
  import { join as join25, dirname as dirname20 } from "path";
23441
23828
  async function pullInstalledPackRules(db, opts = {}) {
@@ -23500,7 +23887,7 @@ async function pullInstalledPackRules(db, opts = {}) {
23500
23887
  result.pulled += 1;
23501
23888
  const promptHash = packRulePromptHash(pack.slug, rule);
23502
23889
  const candidatePath = sidecarPath(projectRoot, promptHash);
23503
- if (existsSync41(candidatePath) || alreadyApplied(db, promptHash)) {
23890
+ if (existsSync43(candidatePath) || alreadyApplied(db, promptHash)) {
23504
23891
  result.skipped += 1;
23505
23892
  continue;
23506
23893
  }
@@ -23593,7 +23980,7 @@ function materializeCandidate(projectRoot, candidatePath, promptHash, rule, pack
23593
23980
  draft_text: draftText
23594
23981
  };
23595
23982
  const dir = dirname20(candidatePath);
23596
- if (!existsSync41(dir)) mkdirSync18(dir, { recursive: true });
23983
+ if (!existsSync43(dir)) mkdirSync18(dir, { recursive: true });
23597
23984
  writeFileSync13(candidatePath, JSON.stringify(sidecar, null, 2), "utf-8");
23598
23985
  }
23599
23986
  function emitDropTelemetry(db, eventType, data) {
@@ -24465,7 +24852,7 @@ var init_memory_supersede = __esm({
24465
24852
 
24466
24853
  // src/memory-consolidate.ts
24467
24854
  import { createHash as createHash12 } from "crypto";
24468
- import { existsSync as existsSync42, mkdirSync as mkdirSync19, writeFileSync as writeFileSync14 } from "fs";
24855
+ import { existsSync as existsSync44, mkdirSync as mkdirSync19, writeFileSync as writeFileSync14 } from "fs";
24469
24856
  import { join as join27 } from "path";
24470
24857
  function emptyResult() {
24471
24858
  return {
@@ -24642,7 +25029,7 @@ ${representative.detail}` : ""}`;
24642
25029
  const clusterKey = cluster.map((c2) => c2.title.toLowerCase().replace(/\s+/g, " ").trim()).sort().join("|");
24643
25030
  const promptHash = createHash12("sha256").update(clusterKey).digest("hex").slice(0, 16);
24644
25031
  const candidatePath = join27(candidateDir, `${promptHash}.json`);
24645
- if (existsSync42(candidatePath)) continue;
25032
+ if (existsSync44(candidatePath)) continue;
24646
25033
  if (dryRun) {
24647
25034
  promoted++;
24648
25035
  continue;
@@ -24935,8 +25322,6 @@ var show_template_exports = {};
24935
25322
  __export(show_template_exports, {
24936
25323
  runShowTemplate: () => runShowTemplate
24937
25324
  });
24938
- import { existsSync as existsSync43, readFileSync as readFileSync32 } from "fs";
24939
- import { resolve as resolve17 } from "path";
24940
25325
  function normalizeBaseName(input) {
24941
25326
  return input.endsWith(".md") ? input.slice(0, -".md".length) : input;
24942
25327
  }
@@ -24956,28 +25341,34 @@ async function runShowTemplate(args2) {
24956
25341
  return;
24957
25342
  }
24958
25343
  const framework = getConfig().framework;
24959
- const choice = pickVariant(baseName, sourceDir, framework);
24960
- if (choice.kind === "miss") {
25344
+ const rendered = renderResolvedAsset(
25345
+ baseName,
25346
+ sourceDir,
25347
+ framework,
25348
+ makeRenderContext(framework)
25349
+ );
25350
+ if (rendered.kind === "miss") {
24961
25351
  process.stderr.write(`massu: no template named "${baseName}" found
24962
25352
  `);
24963
25353
  process.exit(1);
24964
25354
  return;
24965
25355
  }
24966
- const suffix = choice.kind === "hit" ? choice.suffix : "";
24967
- const file = suffix === "" ? resolve17(sourceDir, `${baseName}.md`) : resolve17(sourceDir, `${baseName}${suffix}.md`);
24968
- if (!existsSync43(file)) {
24969
- process.stderr.write(`massu: resolved template "${file}" no longer exists
24970
- `);
25356
+ if (rendered.kind === "error") {
25357
+ process.stderr.write(
25358
+ `massu: template "${baseName}" could not be rendered: ${rendered.message}
25359
+ `
25360
+ );
24971
25361
  process.exit(1);
24972
25362
  return;
24973
25363
  }
24974
- process.stdout.write(readFileSync32(file, "utf-8"));
25364
+ process.stdout.write(rendered.content);
24975
25365
  }
24976
25366
  var init_show_template = __esm({
24977
25367
  "src/commands/show-template.ts"() {
24978
25368
  "use strict";
24979
25369
  init_config();
24980
- init_install_commands();
25370
+ init_asset_resolution();
25371
+ init_asset_render();
24981
25372
  }
24982
25373
  });
24983
25374
 
@@ -25024,9 +25415,9 @@ var init_passthrough = __esm({
25024
25415
  });
25025
25416
 
25026
25417
  // src/lib/installLock.ts
25027
- import { resolve as resolve18 } from "path";
25418
+ import { resolve as resolve20 } from "path";
25028
25419
  function withInstallLock(projectRoot, fn, opts = {}) {
25029
- const lockPath = resolve18(projectRoot, ".massu", "installAll.lock");
25420
+ const lockPath = resolve20(projectRoot, ".massu", "installAll.lock");
25030
25421
  return withFileLockSync(
25031
25422
  lockPath,
25032
25423
  fn,
@@ -25066,8 +25457,8 @@ __export(config_refresh_exports, {
25066
25457
  mergeRefresh: () => mergeRefresh,
25067
25458
  runConfigRefresh: () => runConfigRefresh
25068
25459
  });
25069
- import { existsSync as existsSync44, readFileSync as readFileSync33, rmSync as rmSync9 } from "fs";
25070
- import { resolve as resolve19 } from "path";
25460
+ import { existsSync as existsSync45, readFileSync as readFileSync34, rmSync as rmSync9 } from "fs";
25461
+ import { resolve as resolve21 } from "path";
25071
25462
  import { parse as parseYaml5 } from "yaml";
25072
25463
  function flatten(obj, prefix3 = "") {
25073
25464
  const out = {};
@@ -25195,17 +25586,17 @@ function renderDiff(diff) {
25195
25586
  }
25196
25587
  async function runConfigRefresh(opts = {}) {
25197
25588
  const cwd = opts.cwd ?? process.cwd();
25198
- const configPath = resolve19(cwd, "massu.config.yaml");
25589
+ const configPath = resolve21(cwd, "massu.config.yaml");
25199
25590
  const log = opts.silent ? () => {
25200
25591
  } : (s) => process.stdout.write(s);
25201
- if (!existsSync44(configPath)) {
25592
+ if (!existsSync45(configPath)) {
25202
25593
  const message = "massu.config.yaml not found. Run: npx massu init";
25203
25594
  if (!opts.silent) process.stderr.write(message + "\n");
25204
25595
  return { exitCode: 1, applied: false, dryRun: !!opts.dryRun, diff: [], message };
25205
25596
  }
25206
25597
  let existing;
25207
25598
  try {
25208
- const content = readFileSync33(configPath, "utf-8");
25599
+ const content = readFileSync34(configPath, "utf-8");
25209
25600
  const parsed = parseYaml5(content);
25210
25601
  if (!parsed || typeof parsed !== "object") {
25211
25602
  throw new Error("config is not a YAML object");
@@ -25278,8 +25669,8 @@ async function runConfigRefresh(opts = {}) {
25278
25669
  `);
25279
25670
  const stackResolved = installResult.totalInstalled > 0 || installResult.totalUpdated > 0;
25280
25671
  if (stackResolved) {
25281
- const placeholderPath = resolve19(installResult.claudeDir, "commands", "_massu-needs-stack.md");
25282
- if (existsSync44(placeholderPath)) {
25672
+ const placeholderPath = resolve21(installResult.claudeDir, "commands", "_massu-needs-stack.md");
25673
+ if (existsSync45(placeholderPath)) {
25283
25674
  try {
25284
25675
  rmSync9(placeholderPath, { force: true });
25285
25676
  log("Removed _massu-needs-stack.md (stack now declared).\n");
@@ -25389,7 +25780,7 @@ var init_gitToplevel = __esm({
25389
25780
 
25390
25781
  // src/watch/canary.ts
25391
25782
  import { mkdirSync as mkdirSync20, rmSync as rmSync10, writeFileSync as writeFileSync15 } from "fs";
25392
- import { isAbsolute as isAbsolute3, relative as relative7, resolve as resolve20, sep as sep2 } from "path";
25783
+ import { isAbsolute as isAbsolute3, relative as relative7, resolve as resolve22, sep as sep2 } from "path";
25393
25784
  function toRelative(projectRoot, p19) {
25394
25785
  const raw = String(p19);
25395
25786
  const rel = isAbsolute3(raw) ? relative7(projectRoot, raw) : raw;
@@ -25409,9 +25800,9 @@ function canaryAwareIgnore(ignore, projectRoot) {
25409
25800
  return [...withoutMassu, massuButNotCanary];
25410
25801
  }
25411
25802
  function createWatcherCanary(opts) {
25412
- const projectRoot = resolve20(opts.projectRoot);
25803
+ const projectRoot = resolve22(opts.projectRoot);
25413
25804
  const now = opts.now ?? Date.now;
25414
- const filePath = resolve20(projectRoot, CANARY_FILE_REL);
25805
+ const filePath = resolve22(projectRoot, CANARY_FILE_REL);
25415
25806
  let outstanding = false;
25416
25807
  let echoed = false;
25417
25808
  let consecutiveDead = 0;
@@ -25425,7 +25816,7 @@ function createWatcherCanary(opts) {
25425
25816
  arm() {
25426
25817
  if (outstanding) return "armed";
25427
25818
  try {
25428
- mkdirSync20(resolve20(projectRoot, CANARY_DIR_REL), { recursive: true });
25819
+ mkdirSync20(resolve22(projectRoot, CANARY_DIR_REL), { recursive: true });
25429
25820
  writeFileSync15(filePath, `${now()}
25430
25821
  `, "utf-8");
25431
25822
  } catch {
@@ -25478,14 +25869,14 @@ var init_canary = __esm({
25478
25869
  });
25479
25870
 
25480
25871
  // src/watch/lockfile-detector.ts
25481
- import { existsSync as existsSync45, statSync as statSync15 } from "fs";
25482
- import { resolve as resolve21 } from "path";
25872
+ import { existsSync as existsSync46, statSync as statSync16 } from "fs";
25873
+ import { resolve as resolve23 } from "path";
25483
25874
  function lockfileMidWrite(projectRoot, now = Date.now(), windowMs = LOCKFILE_WINDOW_MS) {
25484
25875
  for (const lf of KNOWN_LOCKFILES) {
25485
- const p19 = resolve21(projectRoot, lf);
25486
- if (!existsSync45(p19)) continue;
25876
+ const p19 = resolve23(projectRoot, lf);
25877
+ if (!existsSync46(p19)) continue;
25487
25878
  try {
25488
- const stat = statSync15(p19);
25879
+ const stat = statSync16(p19);
25489
25880
  const delta = now - stat.mtimeMs;
25490
25881
  if (delta >= 0 && delta < windowMs) return true;
25491
25882
  } catch {
@@ -25496,7 +25887,7 @@ function lockfileMidWrite(projectRoot, now = Date.now(), windowMs = LOCKFILE_WIN
25496
25887
  function gitMidOperation(projectRoot) {
25497
25888
  const sentinels = ["MERGE_HEAD", "REBASE_HEAD", "CHERRY_PICK_HEAD", "rebase-apply", "rebase-merge"];
25498
25889
  for (const s of sentinels) {
25499
- if (existsSync45(resolve21(projectRoot, ".git", s))) return true;
25890
+ if (existsSync46(resolve23(projectRoot, ".git", s))) return true;
25500
25891
  }
25501
25892
  return false;
25502
25893
  }
@@ -25695,18 +26086,18 @@ var init_paths = __esm({
25695
26086
  });
25696
26087
 
25697
26088
  // src/watch/state.ts
25698
- import { closeSync as closeSync5, existsSync as existsSync46, fsyncSync as fsyncSync5, mkdirSync as mkdirSync21, openSync as openSync5, readFileSync as readFileSync34, renameSync as renameSync5, rmSync as rmSync11, writeFileSync as writeFileSync16, writeSync as writeSync3 } from "fs";
25699
- import { dirname as dirname21, resolve as resolve22 } from "path";
26089
+ import { closeSync as closeSync5, existsSync as existsSync47, fsyncSync as fsyncSync5, mkdirSync as mkdirSync21, openSync as openSync5, readFileSync as readFileSync35, renameSync as renameSync5, rmSync as rmSync11, writeFileSync as writeFileSync16, writeSync as writeSync3 } from "fs";
26090
+ import { dirname as dirname21, resolve as resolve24 } from "path";
25700
26091
  function watchStatePath(projectRoot) {
25701
- return resolve22(projectRoot, ".massu", "watch-state.json");
26092
+ return resolve24(projectRoot, ".massu", "watch-state.json");
25702
26093
  }
25703
26094
  function backupStatePath(projectRoot) {
25704
- return resolve22(projectRoot, ".massu", "watch-state.v0.bak.json");
26095
+ return resolve24(projectRoot, ".massu", "watch-state.v0.bak.json");
25705
26096
  }
25706
26097
  function readState(projectRoot) {
25707
26098
  const path = watchStatePath(projectRoot);
25708
- if (!existsSync46(path)) return { ...DEFAULT_STATE };
25709
- const content = readFileSync34(path, "utf-8");
26099
+ if (!existsSync47(path)) return { ...DEFAULT_STATE };
26100
+ const content = readFileSync35(path, "utf-8");
25710
26101
  let raw;
25711
26102
  try {
25712
26103
  raw = JSON.parse(content);
@@ -25769,7 +26160,7 @@ function writeStateAtomic(projectRoot, state) {
25769
26160
  renameSync5(tmp, path);
25770
26161
  renamed = true;
25771
26162
  } finally {
25772
- if (!renamed && existsSync46(tmp)) {
26163
+ if (!renamed && existsSync47(tmp)) {
25773
26164
  try {
25774
26165
  rmSync11(tmp, { force: true });
25775
26166
  } catch {
@@ -25815,7 +26206,7 @@ var init_state = __esm({
25815
26206
  // src/watch/daemon.ts
25816
26207
  import * as chokidar from "chokidar";
25817
26208
  import { mkdirSync as mkdirSync22 } from "fs";
25818
- import { resolve as resolve23 } from "path";
26209
+ import { resolve as resolve25 } from "path";
25819
26210
  function readDaemonConfig(projectRoot) {
25820
26211
  const cfg = getConfig();
25821
26212
  const w2 = cfg.watch ?? {};
@@ -26106,7 +26497,7 @@ async function startDaemon(projectRoot, hooks) {
26106
26497
  );
26107
26498
  canary = createWatcherCanary({ projectRoot: cfg.projectRoot, now });
26108
26499
  try {
26109
- mkdirSync22(resolve23(cfg.projectRoot, canary.dirRel), { recursive: true });
26500
+ mkdirSync22(resolve25(cfg.projectRoot, canary.dirRel), { recursive: true });
26110
26501
  } catch {
26111
26502
  }
26112
26503
  watchSpec = {
@@ -26157,8 +26548,8 @@ __export(watch_exports, {
26157
26548
  runWatch: () => runWatch
26158
26549
  });
26159
26550
  import { spawnSync as spawnSync5 } from "child_process";
26160
- import { basename as basename8, dirname as dirname22, resolve as resolve24 } from "path";
26161
- import { appendFileSync as appendFileSync4, existsSync as existsSync47, mkdirSync as mkdirSync23, readFileSync as readFileSync35 } from "fs";
26551
+ import { basename as basename8, dirname as dirname22, resolve as resolve26 } from "path";
26552
+ import { appendFileSync as appendFileSync4, existsSync as existsSync48, mkdirSync as mkdirSync23, readFileSync as readFileSync36 } from "fs";
26162
26553
  function parseFlags(args2) {
26163
26554
  const out = {
26164
26555
  foreground: false,
@@ -26183,8 +26574,8 @@ function parseFlags(args2) {
26183
26574
  }
26184
26575
  function findClaudeBg() {
26185
26576
  const home = process.env.HOME ?? "";
26186
- const fixed = home ? resolve24(home, ".claude", "bin", "claude-bg") : null;
26187
- if (fixed && existsSync47(fixed)) return fixed;
26577
+ const fixed = home ? resolve26(home, ".claude", "bin", "claude-bg") : null;
26578
+ if (fixed && existsSync48(fixed)) return fixed;
26188
26579
  const which = spawnSync5("which", ["claude-bg"], { encoding: "utf-8" });
26189
26580
  if (which.status === 0 && which.stdout) {
26190
26581
  const p19 = which.stdout.trim();
@@ -26245,7 +26636,7 @@ async function runWatch(args2) {
26245
26636
  }
26246
26637
  function runStatus(root) {
26247
26638
  const path = watchStatePath(root);
26248
- if (!existsSync47(path)) {
26639
+ if (!existsSync48(path)) {
26249
26640
  process.stdout.write("massu watch: not running (no state file)\n");
26250
26641
  return { exitCode: 0 };
26251
26642
  }
@@ -26327,7 +26718,7 @@ async function runForeground(root) {
26327
26718
  }
26328
26719
  throw err;
26329
26720
  }
26330
- return new Promise((resolve47) => {
26721
+ return new Promise((resolve49) => {
26331
26722
  const shutdown = async () => {
26332
26723
  if (stopped) return;
26333
26724
  stopped = true;
@@ -26337,7 +26728,7 @@ async function runForeground(root) {
26337
26728
  process.chdir(priorCwd);
26338
26729
  } catch {
26339
26730
  }
26340
- resolve47({ exitCode: 0 });
26731
+ resolve49({ exitCode: 0 });
26341
26732
  };
26342
26733
  process.on("SIGINT", () => {
26343
26734
  void shutdown();
@@ -26398,7 +26789,7 @@ async function runOnQuiescent(projectRoot) {
26398
26789
  );
26399
26790
  }
26400
26791
  function refreshLogPath(projectRoot) {
26401
- return resolve24(projectRoot, ".massu", "refresh-log.jsonl");
26792
+ return resolve26(projectRoot, ".massu", "refresh-log.jsonl");
26402
26793
  }
26403
26794
  function appendRefreshLog(projectRoot, event) {
26404
26795
  const path = refreshLogPath(projectRoot);
@@ -26410,11 +26801,11 @@ function appendRefreshLog(projectRoot, event) {
26410
26801
  }
26411
26802
  function readRefreshLog(projectRoot, limit = 10, opts = {}) {
26412
26803
  const path = refreshLogPath(projectRoot);
26413
- if (!existsSync47(path)) return [];
26804
+ if (!existsSync48(path)) return [];
26414
26805
  const warn = opts.warn ?? ((s) => {
26415
26806
  process.stderr.write(s);
26416
26807
  });
26417
- const lines = readFileSync35(path, "utf-8").split("\n").filter(Boolean);
26808
+ const lines = readFileSync36(path, "utf-8").split("\n").filter(Boolean);
26418
26809
  const tail = lines.slice(-limit);
26419
26810
  const out = [];
26420
26811
  let corrupt = 0;
@@ -26487,13 +26878,13 @@ var init_refresh_log = __esm({
26487
26878
  import {
26488
26879
  chmodSync as chmodSync9,
26489
26880
  closeSync as closeSync6,
26490
- existsSync as existsSync48,
26881
+ existsSync as existsSync49,
26491
26882
  fsyncSync as fsyncSync6,
26492
26883
  mkdirSync as mkdirSync24,
26493
26884
  openSync as openSync6,
26494
26885
  renameSync as renameSync6,
26495
26886
  rmSync as rmSync12,
26496
- statSync as statSync16,
26887
+ statSync as statSync17,
26497
26888
  writeSync as writeSync4
26498
26889
  } from "node:fs";
26499
26890
  import { dirname as dirname23 } from "node:path";
@@ -26501,7 +26892,7 @@ function atomicWrite(path, content, opts = {}) {
26501
26892
  const tmpPath = `${path}.tmp`;
26502
26893
  const parentDir = dirname23(path);
26503
26894
  try {
26504
- if (!existsSync48(parentDir)) {
26895
+ if (!existsSync49(parentDir)) {
26505
26896
  const mkdirOpts = { recursive: true };
26506
26897
  if (opts.ensureParentDirMode !== void 0) {
26507
26898
  mkdirOpts.mode = opts.ensureParentDirMode;
@@ -26523,7 +26914,7 @@ function atomicWrite(path, content, opts = {}) {
26523
26914
  renameSync6(tmpPath, path);
26524
26915
  return { written: true };
26525
26916
  } catch (err) {
26526
- if (existsSync48(tmpPath)) {
26917
+ if (existsSync49(tmpPath)) {
26527
26918
  try {
26528
26919
  rmSync12(tmpPath, { force: true });
26529
26920
  } catch {
@@ -26539,23 +26930,23 @@ var init_atomic_write = __esm({
26539
26930
  });
26540
26931
 
26541
26932
  // src/security/manifest-schema.ts
26542
- import { z as z4 } from "zod";
26933
+ import { z as z5 } from "zod";
26543
26934
  var MIN_KNOWN_SCHEMA_VERSION, KNOWN_MAX_SCHEMA_VERSION, Sha256HexSchema, PrintableAsciiStringSchema, Base64Schema, AdapterEntrySchema, ManifestBodySchema, EnvelopeSchema;
26544
26935
  var init_manifest_schema = __esm({
26545
26936
  "src/security/manifest-schema.ts"() {
26546
26937
  "use strict";
26547
26938
  MIN_KNOWN_SCHEMA_VERSION = 1;
26548
26939
  KNOWN_MAX_SCHEMA_VERSION = 1;
26549
- Sha256HexSchema = z4.string().regex(/^[0-9a-f]{64}$/, "sha256 hex must be 64 lowercase hex chars");
26550
- PrintableAsciiStringSchema = z4.string().min(1).regex(
26940
+ Sha256HexSchema = z5.string().regex(/^[0-9a-f]{64}$/, "sha256 hex must be 64 lowercase hex chars");
26941
+ PrintableAsciiStringSchema = z5.string().min(1).regex(
26551
26942
  /^[\x20-\x7e]+$/,
26552
26943
  "must be printable ASCII (0x20-0x7e); control characters like ESC, tab, newline, and non-ASCII are rejected to prevent log injection"
26553
26944
  );
26554
- Base64Schema = z4.string().regex(
26945
+ Base64Schema = z5.string().regex(
26555
26946
  /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$/,
26556
26947
  "must be standard base64 (RFC 4648; length divisible by 4 with optional == or = padding)"
26557
26948
  ).min(1);
26558
- AdapterEntrySchema = z4.object({
26949
+ AdapterEntrySchema = z5.object({
26559
26950
  // CR-9 iter-4 audit LOW-NEW4-3 fix: every string field rendered in CLI
26560
26951
  // output (discover.ts deprecation warning + adapters.ts search status)
26561
26952
  // uses PrintableAsciiStringSchema to prevent log-injection from a
@@ -26566,24 +26957,24 @@ var init_manifest_schema = __esm({
26566
26957
  version: PrintableAsciiStringSchema,
26567
26958
  sha256: Sha256HexSchema,
26568
26959
  signing_key_id: Sha256HexSchema,
26569
- deprecated: z4.object({
26960
+ deprecated: z5.object({
26570
26961
  since: PrintableAsciiStringSchema,
26571
26962
  replacement: PrintableAsciiStringSchema.nullable().optional(),
26572
26963
  reason: PrintableAsciiStringSchema
26573
26964
  }).optional(),
26574
- unpublished: z4.boolean().optional()
26965
+ unpublished: z5.boolean().optional()
26575
26966
  }).passthrough();
26576
- ManifestBodySchema = z4.object({
26577
- manifest_schema_version: z4.number().int().positive().default(1),
26578
- issued_at: z4.string().min(1),
26579
- adapters: z4.array(AdapterEntrySchema)
26967
+ ManifestBodySchema = z5.object({
26968
+ manifest_schema_version: z5.number().int().positive().default(1),
26969
+ issued_at: z5.string().min(1),
26970
+ adapters: z5.array(AdapterEntrySchema)
26580
26971
  }).passthrough();
26581
- EnvelopeSchema = z4.object({
26972
+ EnvelopeSchema = z5.object({
26582
26973
  manifest: ManifestBodySchema,
26583
26974
  manifest_b64: Base64Schema,
26584
26975
  signature: Base64Schema,
26585
26976
  manifest_sha256: Sha256HexSchema,
26586
- signed_at: z4.string().min(1),
26977
+ signed_at: z5.string().min(1),
26587
26978
  signing_key_id: Sha256HexSchema
26588
26979
  }).passthrough();
26589
26980
  }
@@ -26872,24 +27263,24 @@ var init_fetcher = __esm({
26872
27263
  });
26873
27264
 
26874
27265
  // src/security/manifest-cache.ts
26875
- import { existsSync as existsSync49, readFileSync as readFileSync36, statSync as statSync17 } from "node:fs";
27266
+ import { existsSync as existsSync50, readFileSync as readFileSync37, statSync as statSync18 } from "node:fs";
26876
27267
  import { homedir as homedir21 } from "node:os";
26877
- import { resolve as resolve25 } from "node:path";
26878
- import { z as z5 } from "zod";
27268
+ import { resolve as resolve27 } from "node:path";
27269
+ import { z as z6 } from "zod";
26879
27270
  function defaultCachePaths() {
26880
- const dir = resolve25(homedir21(), ".massu");
27271
+ const dir = resolve27(homedir21(), ".massu");
26881
27272
  return {
26882
- cachePath: resolve25(dir, "adapter-manifest.json"),
26883
- lockPath: resolve25(dir, ".adapter-manifest.lock")
27273
+ cachePath: resolve27(dir, "adapter-manifest.json"),
27274
+ lockPath: resolve27(dir, ".adapter-manifest.lock")
26884
27275
  };
26885
27276
  }
26886
27277
  function loadCachedManifest(paths = defaultCachePaths()) {
26887
- if (!existsSync49(paths.cachePath)) {
27278
+ if (!existsSync50(paths.cachePath)) {
26888
27279
  return { kind: "absent" };
26889
27280
  }
26890
27281
  let raw;
26891
27282
  try {
26892
- const content = readFileSync36(paths.cachePath, "utf-8");
27283
+ const content = readFileSync37(paths.cachePath, "utf-8");
26893
27284
  raw = JSON.parse(content);
26894
27285
  } catch (err) {
26895
27286
  return {
@@ -27036,7 +27427,7 @@ var init_manifest_cache = __esm({
27036
27427
  MAX_FRESH_MS = 24 * 60 * 60 * 1e3;
27037
27428
  MAX_STALE_MS = 7 * 24 * 60 * 60 * 1e3;
27038
27429
  REGISTRY_MANIFEST_URL = "https://registry.massu.ai/adapters/manifest.json";
27039
- CacheWrapperSchema = z5.object({
27430
+ CacheWrapperSchema = z6.object({
27040
27431
  envelope: EnvelopeSchema,
27041
27432
  // CR-9 iter-6 audit LOW-NEW6-1 fix: fetched_at flows into the
27042
27433
  // 'fetched_at not parseable as Date' reason render at line 163 → wrapped
@@ -27047,7 +27438,7 @@ var init_manifest_cache = __esm({
27047
27438
  // drift-guard test (test_security_schemas_printable_ascii_drift.test.ts)
27048
27439
  // makes this class of bug structurally impossible to recur.
27049
27440
  fetched_at: PrintableAsciiStringSchema,
27050
- bundled_pubkey_fingerprint: z5.string().regex(/^[0-9a-f]{64}$/)
27441
+ bundled_pubkey_fingerprint: z6.string().regex(/^[0-9a-f]{64}$/)
27051
27442
  }).strict();
27052
27443
  }
27053
27444
  });
@@ -27078,15 +27469,15 @@ var init_adapter_origin = __esm({
27078
27469
  });
27079
27470
 
27080
27471
  // src/security/local-fingerprint.ts
27081
- import { existsSync as existsSync50, readFileSync as readFileSync37, lstatSync as lstatSync5 } from "node:fs";
27472
+ import { existsSync as existsSync51, readFileSync as readFileSync38, lstatSync as lstatSync5 } from "node:fs";
27082
27473
  import { homedir as homedir22 } from "node:os";
27083
- import { resolve as resolve26, isAbsolute as isAbsolute4 } from "node:path";
27474
+ import { resolve as resolve28, isAbsolute as isAbsolute4 } from "node:path";
27084
27475
  import { createHash as createHash14 } from "node:crypto";
27085
- import { z as z6 } from "zod";
27476
+ import { z as z7 } from "zod";
27086
27477
  function computeLocalFingerprint(localPaths, projectRoot) {
27087
27478
  const tuples = [];
27088
27479
  for (const p19 of localPaths) {
27089
- const abs = isAbsolute4(p19) ? p19 : resolve26(projectRoot, p19);
27480
+ const abs = isAbsolute4(p19) ? p19 : resolve28(projectRoot, p19);
27090
27481
  let contentTag;
27091
27482
  try {
27092
27483
  const lst = lstatSync5(abs);
@@ -27095,7 +27486,7 @@ function computeLocalFingerprint(localPaths, projectRoot) {
27095
27486
  } else if (!lst.isFile()) {
27096
27487
  contentTag = "<not-a-file>";
27097
27488
  } else {
27098
- contentTag = createHash14("sha256").update(readFileSync37(abs)).digest("hex");
27489
+ contentTag = createHash14("sha256").update(readFileSync38(abs)).digest("hex");
27099
27490
  }
27100
27491
  } catch {
27101
27492
  contentTag = "<missing>";
@@ -27107,10 +27498,10 @@ function computeLocalFingerprint(localPaths, projectRoot) {
27107
27498
  return createHash14("sha256").update(canonical2).digest("hex");
27108
27499
  }
27109
27500
  function readFingerprintSentinel(path = FINGERPRINT_PATH) {
27110
- if (!existsSync50(path)) return null;
27501
+ if (!existsSync51(path)) return null;
27111
27502
  let raw;
27112
27503
  try {
27113
- raw = JSON.parse(readFileSync37(path, "utf-8"));
27504
+ raw = JSON.parse(readFileSync38(path, "utf-8"));
27114
27505
  } catch {
27115
27506
  return null;
27116
27507
  }
@@ -27158,10 +27549,10 @@ var init_local_fingerprint = __esm({
27158
27549
  "use strict";
27159
27550
  init_atomic_write();
27160
27551
  init_manifest_schema();
27161
- FINGERPRINT_PATH = resolve26(homedir22(), ".massu", "adapters-local-fingerprint.json");
27162
- FingerprintSentinelSchema = z6.object({
27163
- fingerprint: z6.string().regex(/^[0-9a-f]{64}$/),
27164
- source: z6.enum(["cli", "cli-resync"]),
27552
+ FINGERPRINT_PATH = resolve28(homedir22(), ".massu", "adapters-local-fingerprint.json");
27553
+ FingerprintSentinelSchema = z7.object({
27554
+ fingerprint: z7.string().regex(/^[0-9a-f]{64}$/),
27555
+ source: z7.enum(["cli", "cli-resync"]),
27165
27556
  // CR-9 iter-5 audit LOW-NEW5-1 fix: ts is rendered to stderr in the
27166
27557
  // drift warning at line 214 (`${sentinel.source} at ${sentinel.ts}`);
27167
27558
  // a same-user attacker writing the sentinel file with control chars
@@ -27174,15 +27565,15 @@ var init_local_fingerprint = __esm({
27174
27565
  });
27175
27566
 
27176
27567
  // src/security/install-tracking.ts
27177
- import { readFileSync as readFileSync38, readdirSync as readdirSync17, lstatSync as lstatSync6, existsSync as existsSync51 } from "node:fs";
27568
+ import { readFileSync as readFileSync39, readdirSync as readdirSync18, lstatSync as lstatSync6, existsSync as existsSync52 } from "node:fs";
27178
27569
  import { join as join28, relative as relative8, sep as sep3 } from "node:path";
27179
27570
  import { homedir as homedir23 } from "node:os";
27180
- import { resolve as resolve27 } from "node:path";
27571
+ import { resolve as resolve29 } from "node:path";
27181
27572
  import { createHash as createHash15 } from "node:crypto";
27182
- import { z as z7 } from "zod";
27573
+ import { z as z8 } from "zod";
27183
27574
  function containsHiddenDirs(packageDir) {
27184
27575
  for (const hidden of EXCLUDED_DIR_NAMES) {
27185
- if (existsSync51(`${packageDir}/${hidden}`)) {
27576
+ if (existsSync52(`${packageDir}/${hidden}`)) {
27186
27577
  return hidden;
27187
27578
  }
27188
27579
  }
@@ -27194,7 +27585,7 @@ function sha256OfDir(dir, opts = {}) {
27194
27585
  function walk(currentDir) {
27195
27586
  let entries;
27196
27587
  try {
27197
- entries = readdirSync17(currentDir);
27588
+ entries = readdirSync18(currentDir);
27198
27589
  } catch {
27199
27590
  return;
27200
27591
  }
@@ -27228,7 +27619,7 @@ function sha256OfDir(dir, opts = {}) {
27228
27619
  files.sort((a2, b2) => a2.relativePath < b2.relativePath ? -1 : a2.relativePath > b2.relativePath ? 1 : 0);
27229
27620
  const top = createHash15("sha256");
27230
27621
  for (const f2 of files) {
27231
- const fileHash = createHash15("sha256").update(readFileSync38(f2.absPath)).digest("hex");
27622
+ const fileHash = createHash15("sha256").update(readFileSync39(f2.absPath)).digest("hex");
27232
27623
  top.update(f2.relativePath, "utf-8");
27233
27624
  top.update("\0", "utf-8");
27234
27625
  top.update(fileHash, "utf-8");
@@ -27237,10 +27628,10 @@ function sha256OfDir(dir, opts = {}) {
27237
27628
  return top.digest("hex");
27238
27629
  }
27239
27630
  function readInstalledManifest(path = INSTALLED_MANIFEST_PATH) {
27240
- if (!existsSync51(path)) return {};
27631
+ if (!existsSync52(path)) return {};
27241
27632
  let raw;
27242
27633
  try {
27243
- raw = JSON.parse(readFileSync38(path, "utf-8"));
27634
+ raw = JSON.parse(readFileSync39(path, "utf-8"));
27244
27635
  } catch {
27245
27636
  return {};
27246
27637
  }
@@ -27304,10 +27695,10 @@ var init_install_tracking = __esm({
27304
27695
  "src/security/install-tracking.ts"() {
27305
27696
  "use strict";
27306
27697
  init_atomic_write();
27307
- INSTALLED_MANIFEST_PATH = resolve27(homedir23(), ".massu", "adapter-manifest-installed.json");
27698
+ INSTALLED_MANIFEST_PATH = resolve29(homedir23(), ".massu", "adapter-manifest-installed.json");
27308
27699
  DEFAULT_MAX_FILE_BYTES = 64 * 1024 * 1024;
27309
27700
  EXCLUDED_DIR_NAMES = /* @__PURE__ */ new Set([".git", "node_modules", ".cache", ".tmp"]);
27310
- InstallEntrySchema = z7.object({
27701
+ InstallEntrySchema = z8.object({
27311
27702
  // CR-9 iter-3 audit LOW-NEW3-1 fix: reject control characters in version.
27312
27703
  // Without this, a same-user attacker writing the install-tracking
27313
27704
  // sidecar could embed ANSI escapes (or other control chars) in version,
@@ -27317,35 +27708,35 @@ var init_install_tracking = __esm({
27317
27708
  // rejected at parse time. Schema-level validation closes the vector
27318
27709
  // at every callsite that reads the sidecar — no per-callsite escaping
27319
27710
  // gymnastics needed.
27320
- version: z7.string().min(1).regex(/^[\x20-\x7e]+$/, "version must be printable ASCII (no control characters)"),
27321
- installed_sha256: z7.string().regex(/^[0-9a-f]{64}$/),
27322
- manifest_sha256: z7.string().regex(/^[0-9a-f]{64}$/),
27323
- ts: z7.string().min(1).regex(/^[\x20-\x7e]+$/, "ts must be printable ASCII (no control characters)")
27711
+ version: z8.string().min(1).regex(/^[\x20-\x7e]+$/, "version must be printable ASCII (no control characters)"),
27712
+ installed_sha256: z8.string().regex(/^[0-9a-f]{64}$/),
27713
+ manifest_sha256: z8.string().regex(/^[0-9a-f]{64}$/),
27714
+ ts: z8.string().min(1).regex(/^[\x20-\x7e]+$/, "ts must be printable ASCII (no control characters)")
27324
27715
  }).strict();
27325
- InstalledManifestSchema = z7.record(z7.string(), InstallEntrySchema);
27716
+ InstalledManifestSchema = z8.record(z8.string(), InstallEntrySchema);
27326
27717
  }
27327
27718
  });
27328
27719
 
27329
27720
  // src/detect/adapters/discover.ts
27330
- import { existsSync as existsSync52, readdirSync as readdirSync18, readFileSync as readFileSync39, lstatSync as lstatSync7 } from "node:fs";
27331
- import { resolve as resolve28, isAbsolute as isAbsolute5 } from "node:path";
27332
- import { z as z8 } from "zod";
27721
+ import { existsSync as existsSync53, readdirSync as readdirSync19, readFileSync as readFileSync40, lstatSync as lstatSync7 } from "node:fs";
27722
+ import { resolve as resolve30, isAbsolute as isAbsolute5 } from "node:path";
27723
+ import { z as z9 } from "zod";
27333
27724
  function walkNodeModules(projectRoot, warnings) {
27334
- const nodeModulesDir = resolve28(projectRoot, "node_modules");
27335
- if (!existsSync52(nodeModulesDir)) {
27725
+ const nodeModulesDir = resolve30(projectRoot, "node_modules");
27726
+ if (!existsSync53(nodeModulesDir)) {
27336
27727
  return [];
27337
27728
  }
27338
27729
  const candidates = [];
27339
27730
  let topLevelEntries;
27340
27731
  try {
27341
- topLevelEntries = readdirSync18(nodeModulesDir);
27732
+ topLevelEntries = readdirSync19(nodeModulesDir);
27342
27733
  } catch (err) {
27343
27734
  warnings.push(`failed to read node_modules: ${err instanceof Error ? err.message : String(err)}`);
27344
27735
  return [];
27345
27736
  }
27346
27737
  for (const entry of topLevelEntries) {
27347
27738
  if (entry.startsWith(".")) continue;
27348
- const entryPath = resolve28(nodeModulesDir, entry);
27739
+ const entryPath = resolve30(nodeModulesDir, entry);
27349
27740
  let entryStat;
27350
27741
  try {
27351
27742
  entryStat = lstatSync7(entryPath);
@@ -27362,12 +27753,12 @@ function walkNodeModules(projectRoot, warnings) {
27362
27753
  if (entry.startsWith("@")) {
27363
27754
  let scopedEntries;
27364
27755
  try {
27365
- scopedEntries = readdirSync18(entryPath);
27756
+ scopedEntries = readdirSync19(entryPath);
27366
27757
  } catch {
27367
27758
  continue;
27368
27759
  }
27369
27760
  for (const sub of scopedEntries) {
27370
- const subPath = resolve28(entryPath, sub);
27761
+ const subPath = resolve30(entryPath, sub);
27371
27762
  let subStat;
27372
27763
  try {
27373
27764
  subStat = lstatSync7(subPath);
@@ -27386,11 +27777,11 @@ function walkNodeModules(projectRoot, warnings) {
27386
27777
  return candidates;
27387
27778
  }
27388
27779
  function tryReadAdapterPackage(packageDir, warnings) {
27389
- const pkgJsonPath = resolve28(packageDir, "package.json");
27390
- if (!existsSync52(pkgJsonPath)) return null;
27780
+ const pkgJsonPath = resolve30(packageDir, "package.json");
27781
+ if (!existsSync53(pkgJsonPath)) return null;
27391
27782
  let raw;
27392
27783
  try {
27393
- raw = JSON.parse(readFileSync39(pkgJsonPath, "utf-8"));
27784
+ raw = JSON.parse(readFileSync40(pkgJsonPath, "utf-8"));
27394
27785
  } catch (err) {
27395
27786
  warnings.push(
27396
27787
  `skipping ${packageDir}: package.json parse failed (${err instanceof Error ? err.message : String(err)})`
@@ -27524,8 +27915,8 @@ function discoverAdapters(opts) {
27524
27915
  const localSet = new Set(opts.configLocalPaths);
27525
27916
  for (const localPath of opts.configLocalPaths) {
27526
27917
  if (seenIds.has(localPath)) continue;
27527
- const absPath = isAbsolute5(localPath) ? localPath : resolve28(opts.projectRoot, localPath);
27528
- if (!existsSync52(absPath)) {
27918
+ const absPath = isAbsolute5(localPath) ? localPath : resolve30(opts.projectRoot, localPath);
27919
+ if (!existsSync53(absPath)) {
27529
27920
  warnings.push(
27530
27921
  `local adapter file not found: ${localPath} (resolved to ${absPath}). Remove via: massu adapters remove-local ${localPath}`
27531
27922
  );
@@ -27556,7 +27947,7 @@ var init_discover = __esm({
27556
27947
  init_manifest_schema();
27557
27948
  init_local_fingerprint();
27558
27949
  init_install_tracking();
27559
- AdapterPackageJsonSchema = z8.object({
27950
+ AdapterPackageJsonSchema = z9.object({
27560
27951
  // CR-9 iter-4 audit LOW-NEW4-2 fix: name/version are rendered in stderr
27561
27952
  // warnings via `${pkg.name}@${pkg.version}` strings; without the
27562
27953
  // printable-ASCII regex, a malicious local node_modules/<pkg>/package.json
@@ -27566,9 +27957,9 @@ var init_discover = __esm({
27566
27957
  version: PrintableAsciiStringSchema,
27567
27958
  // Plan 3c gap-31 + gap-50: `"massu-adapter": true` is the explicit opt-in
27568
27959
  // marker. @massu/adapter-* packages also declare this.
27569
- "massu-adapter": z8.union([z8.boolean(), z8.literal(void 0)]).optional(),
27960
+ "massu-adapter": z9.union([z9.boolean(), z9.literal(void 0)]).optional(),
27570
27961
  // Plan 3c gap-31: api version. Loader refuses incompatible major (caller-side).
27571
- "massu-adapter-api-version": z8.union([z8.string(), z8.number(), z8.literal(void 0)]).optional()
27962
+ "massu-adapter-api-version": z9.union([z9.string(), z9.number(), z9.literal(void 0)]).optional()
27572
27963
  }).passthrough();
27573
27964
  }
27574
27965
  });
@@ -27607,8 +27998,8 @@ __export(adapters_exports, {
27607
27998
  runAdaptersResyncLocalFingerprint: () => runAdaptersResyncLocalFingerprint,
27608
27999
  runAdaptersSearch: () => runAdaptersSearch
27609
28000
  });
27610
- import { existsSync as existsSync53, readFileSync as readFileSync40 } from "node:fs";
27611
- import { resolve as resolve29 } from "node:path";
28001
+ import { existsSync as existsSync54, readFileSync as readFileSync41 } from "node:fs";
28002
+ import { resolve as resolve31 } from "node:path";
27612
28003
  import { parseDocument } from "yaml";
27613
28004
  async function handleAdaptersSubcommand(args2) {
27614
28005
  const sub = args2[0];
@@ -27875,8 +28266,8 @@ function mutateLocalArray(mutator, command) {
27875
28266
  );
27876
28267
  return { exitCode: 2 };
27877
28268
  }
27878
- const yamlPath = resolve29(projectRoot, "massu.config.yaml");
27879
- if (!existsSync53(yamlPath)) {
28269
+ const yamlPath = resolve31(projectRoot, "massu.config.yaml");
28270
+ if (!existsSync54(yamlPath)) {
27880
28271
  process.stderr.write(
27881
28272
  `${command}: massu.config.yaml not found at ${yamlPath}. Run \`massu init\` first.
27882
28273
  `
@@ -27885,7 +28276,7 @@ function mutateLocalArray(mutator, command) {
27885
28276
  }
27886
28277
  let yamlText;
27887
28278
  try {
27888
- yamlText = readFileSync40(yamlPath, "utf-8");
28279
+ yamlText = readFileSync41(yamlPath, "utf-8");
27889
28280
  } catch (err) {
27890
28281
  process.stderr.write(`${command}: failed to read ${yamlPath}: ${err instanceof Error ? err.message : String(err)}
27891
28282
  `);
@@ -27906,7 +28297,7 @@ function mutateLocalArray(mutator, command) {
27906
28297
  if (next === null) {
27907
28298
  return { exitCode: 0 };
27908
28299
  }
27909
- const lockPath = resolve29(projectRoot, ".massu", "adapters-local-mutate.lock");
28300
+ const lockPath = resolve31(projectRoot, ".massu", "adapters-local-mutate.lock");
27910
28301
  return withFileLockSync(lockPath, () => {
27911
28302
  doc.setIn(["adapters", "local"], next);
27912
28303
  const newYaml = doc.toString();
@@ -27975,23 +28366,23 @@ async function runAdaptersInstall(args2) {
27975
28366
  `);
27976
28367
  return { exitCode: 1 };
27977
28368
  }
27978
- const packageDir = resolve29(projectRoot, "node_modules", ...packageName.split("/"));
27979
- if (!existsSync53(packageDir)) {
28369
+ const packageDir = resolve31(projectRoot, "node_modules", ...packageName.split("/"));
28370
+ if (!existsSync54(packageDir)) {
27980
28371
  process.stderr.write(
27981
28372
  `install: ${packageName} is not installed in node_modules. Run \`npm install ${packageName}\` first.
27982
28373
  `
27983
28374
  );
27984
28375
  return { exitCode: 1 };
27985
28376
  }
27986
- const pkgJsonPath = resolve29(packageDir, "package.json");
27987
- if (!existsSync53(pkgJsonPath)) {
28377
+ const pkgJsonPath = resolve31(packageDir, "package.json");
28378
+ if (!existsSync54(pkgJsonPath)) {
27988
28379
  process.stderr.write(`install: ${packageName} has no package.json at ${pkgJsonPath}
27989
28380
  `);
27990
28381
  return { exitCode: 1 };
27991
28382
  }
27992
28383
  let pkgJson;
27993
28384
  try {
27994
- pkgJson = JSON.parse(readFileSync40(pkgJsonPath, "utf-8"));
28385
+ pkgJson = JSON.parse(readFileSync41(pkgJsonPath, "utf-8"));
27995
28386
  } catch (err) {
27996
28387
  process.stderr.write(`install: ${packageName} has malformed package.json: ${err instanceof Error ? err.message : String(err)}
27997
28388
  `);
@@ -28110,8 +28501,8 @@ async function runAdaptersResign(_args) {
28110
28501
  removeInstalledManifestEntry(name);
28111
28502
  continue;
28112
28503
  }
28113
- const packageDir = resolve29(projectRoot, "node_modules", ...name.split("/"));
28114
- if (!existsSync53(packageDir)) {
28504
+ const packageDir = resolve31(projectRoot, "node_modules", ...name.split("/"));
28505
+ if (!existsSync54(packageDir)) {
28115
28506
  removed++;
28116
28507
  warnings.push(`${name}@${entry.version}: not present in node_modules \u2014 REMOVED from sidecar`);
28117
28508
  removeInstalledManifestEntry(name);
@@ -28209,7 +28600,7 @@ var init_sql_table_names = __esm({
28209
28600
 
28210
28601
  // src/db.ts
28211
28602
  import { dirname as dirname24, join as join29 } from "path";
28212
- import { existsSync as existsSync54, mkdirSync as mkdirSync25, readdirSync as readdirSync19, statSync as statSync18 } from "fs";
28603
+ import { existsSync as existsSync55, mkdirSync as mkdirSync25, readdirSync as readdirSync20, statSync as statSync19 } from "fs";
28213
28604
  function getCodeGraphDb() {
28214
28605
  const dbPath = getResolvedPaths().codegraphDbPath;
28215
28606
  assertCodegraphUsable(dbPath);
@@ -28220,7 +28611,7 @@ function getCodeGraphDb() {
28220
28611
  function getDataDb() {
28221
28612
  const dbPath = getResolvedPaths().dataDbPath;
28222
28613
  const dir = dirname24(dbPath);
28223
- if (!existsSync54(dir)) {
28614
+ if (!existsSync55(dir)) {
28224
28615
  mkdirSync25(dir, { recursive: true });
28225
28616
  }
28226
28617
  const db = openDatabase2(dbPath);
@@ -28502,14 +28893,14 @@ function isPythonDataStale(dataDb, pythonRoot) {
28502
28893
  const lastBuildTime = new Date(lastBuild.value).getTime();
28503
28894
  function checkDir(dir) {
28504
28895
  try {
28505
- const entries = readdirSync19(dir, { withFileTypes: true });
28896
+ const entries = readdirSync20(dir, { withFileTypes: true });
28506
28897
  for (const entry of entries) {
28507
28898
  const fullPath = join29(dir, entry.name);
28508
28899
  if (entry.isDirectory()) {
28509
28900
  if (["__pycache__", ".venv", "venv", "node_modules", ".mypy_cache", ".pytest_cache"].includes(entry.name)) continue;
28510
28901
  if (checkDir(fullPath)) return true;
28511
28902
  } else if (entry.name.endsWith(".py")) {
28512
- if (statSync18(fullPath).mtimeMs > lastBuildTime) return true;
28903
+ if (statSync19(fullPath).mtimeMs > lastBuildTime) return true;
28513
28904
  }
28514
28905
  }
28515
28906
  } catch {
@@ -28536,10 +28927,10 @@ var init_db2 = __esm({
28536
28927
  });
28537
28928
 
28538
28929
  // src/security-utils.ts
28539
- import { resolve as resolve30, normalize } from "path";
28930
+ import { resolve as resolve32, normalize } from "path";
28540
28931
  function ensureWithinRoot(filePath, projectRoot) {
28541
- const resolvedRoot = resolve30(projectRoot);
28542
- const resolvedPath = resolve30(resolvedRoot, filePath);
28932
+ const resolvedRoot = resolve32(projectRoot);
28933
+ const resolvedPath = resolve32(resolvedRoot, filePath);
28543
28934
  const normalizedPath = normalize(resolvedPath);
28544
28935
  const normalizedRoot = normalize(resolvedRoot);
28545
28936
  if (!normalizedPath.startsWith(normalizedRoot + "/") && normalizedPath !== normalizedRoot) {
@@ -28728,8 +29119,8 @@ var init_source_layout = __esm({
28728
29119
  });
28729
29120
 
28730
29121
  // src/import-resolver.ts
28731
- import { readFileSync as readFileSync41, existsSync as existsSync55, statSync as statSync19 } from "fs";
28732
- import { resolve as resolve31, dirname as dirname25, join as join30 } from "path";
29122
+ import { readFileSync as readFileSync42, existsSync as existsSync56, statSync as statSync20 } from "fs";
29123
+ import { resolve as resolve33, dirname as dirname25, join as join30 } from "path";
28733
29124
  function parseImports(source) {
28734
29125
  const imports = [];
28735
29126
  const lines = source.split("\n");
@@ -28785,23 +29176,23 @@ function resolveImportPath(specifier, fromFile) {
28785
29176
  let basePath;
28786
29177
  if (specifier.startsWith("@/")) {
28787
29178
  const paths = getResolvedPaths();
28788
- basePath = resolve31(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
29179
+ basePath = resolve33(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
28789
29180
  } else {
28790
- basePath = resolve31(dirname25(fromFile), specifier);
29181
+ basePath = resolve33(dirname25(fromFile), specifier);
28791
29182
  }
28792
- if (existsSync55(basePath) && !isDirectory(basePath)) {
29183
+ if (existsSync56(basePath) && !isDirectory(basePath)) {
28793
29184
  return toRelative2(basePath);
28794
29185
  }
28795
29186
  const resolvedPaths = getResolvedPaths();
28796
29187
  for (const ext of resolvedPaths.extensions) {
28797
29188
  const withExt = basePath + ext;
28798
- if (existsSync55(withExt)) {
29189
+ if (existsSync56(withExt)) {
28799
29190
  return toRelative2(withExt);
28800
29191
  }
28801
29192
  }
28802
29193
  for (const indexFile of resolvedPaths.indexFiles) {
28803
29194
  const indexPath = join30(basePath, indexFile);
28804
- if (existsSync55(indexPath)) {
29195
+ if (existsSync56(indexPath)) {
28805
29196
  return toRelative2(indexPath);
28806
29197
  }
28807
29198
  }
@@ -28809,7 +29200,7 @@ function resolveImportPath(specifier, fromFile) {
28809
29200
  }
28810
29201
  function isDirectory(path) {
28811
29202
  try {
28812
- return statSync19(path).isDirectory();
29203
+ return statSync20(path).isDirectory();
28813
29204
  } catch {
28814
29205
  return false;
28815
29206
  }
@@ -28845,11 +29236,11 @@ function buildImportIndex(dataDb, codegraphDb) {
28845
29236
  const batchSize = 500;
28846
29237
  let batch = [];
28847
29238
  for (const file of files) {
28848
- const absPath = ensureWithinRoot(resolve31(projectRoot, file.path), projectRoot);
28849
- if (!existsSync55(absPath)) continue;
29239
+ const absPath = ensureWithinRoot(resolve33(projectRoot, file.path), projectRoot);
29240
+ if (!existsSync56(absPath)) continue;
28850
29241
  let source;
28851
29242
  try {
28852
- source = readFileSync41(absPath, "utf-8");
29243
+ source = readFileSync42(absPath, "utf-8");
28853
29244
  } catch {
28854
29245
  continue;
28855
29246
  }
@@ -28887,15 +29278,15 @@ var init_import_resolver = __esm({
28887
29278
  });
28888
29279
 
28889
29280
  // src/trpc-index.ts
28890
- import { readFileSync as readFileSync42, existsSync as existsSync56, readdirSync as readdirSync20 } from "fs";
28891
- import { resolve as resolve32, join as join31 } from "path";
29281
+ import { readFileSync as readFileSync43, existsSync as existsSync57, readdirSync as readdirSync21 } from "fs";
29282
+ import { resolve as resolve34, join as join31 } from "path";
28892
29283
  function parseRootRouter() {
28893
29284
  const paths = getResolvedPaths();
28894
29285
  const rootPath = paths.rootRouterPath;
28895
- if (!existsSync56(rootPath)) {
29286
+ if (!existsSync57(rootPath)) {
28896
29287
  throw new Error(`Root router not found at ${rootPath}`);
28897
29288
  }
28898
- const source = readFileSync42(rootPath, "utf-8");
29289
+ const source = readFileSync43(rootPath, "utf-8");
28899
29290
  const mappings = [];
28900
29291
  const importMap = /* @__PURE__ */ new Map();
28901
29292
  const importRegex = /import\s+\{[^}]*?(\w+Router)[^}]*\}\s+from\s+['"]\.\/routers\/([^'"]+)['"]/g;
@@ -28903,16 +29294,16 @@ function parseRootRouter() {
28903
29294
  while ((match = importRegex.exec(source)) !== null) {
28904
29295
  const variable = match[1];
28905
29296
  let filePath = match[2];
28906
- const fullPath = resolve32(paths.routersDir, filePath);
29297
+ const fullPath = resolve34(paths.routersDir, filePath);
28907
29298
  for (const ext of [".ts", ".tsx", ""]) {
28908
29299
  const candidate = fullPath + ext;
28909
29300
  const routersRelPath = getConfig().paths.routers ?? "src/server/api/routers";
28910
- if (existsSync56(candidate)) {
29301
+ if (existsSync57(candidate)) {
28911
29302
  filePath = routersRelPath + "/" + filePath + ext;
28912
29303
  break;
28913
29304
  }
28914
29305
  const indexCandidate = join31(fullPath, "index.ts");
28915
- if (existsSync56(indexCandidate)) {
29306
+ if (existsSync57(indexCandidate)) {
28916
29307
  filePath = routersRelPath + "/" + filePath + "/index.ts";
28917
29308
  break;
28918
29309
  }
@@ -28931,9 +29322,9 @@ function parseRootRouter() {
28931
29322
  return mappings;
28932
29323
  }
28933
29324
  function extractProcedures(routerFilePath) {
28934
- const absPath = resolve32(getProjectRoot(), routerFilePath);
28935
- if (!existsSync56(absPath)) return [];
28936
- const source = readFileSync42(absPath, "utf-8");
29325
+ const absPath = resolve34(getProjectRoot(), routerFilePath);
29326
+ if (!existsSync57(absPath)) return [];
29327
+ const source = readFileSync43(absPath, "utf-8");
28937
29328
  const procedures = [];
28938
29329
  const seen = /* @__PURE__ */ new Set();
28939
29330
  const procRegex = /(\w+)\s*:\s*(protected|public)Procedure/g;
@@ -28956,19 +29347,19 @@ function findUICallSites(routerKey, procedureName) {
28956
29347
  const root = getProjectRoot();
28957
29348
  const src = config.paths.source;
28958
29349
  const searchDirs = [
28959
- resolve32(root, config.paths.pages ?? src + "/app"),
28960
- resolve32(root, config.paths.components ?? src + "/components"),
28961
- resolve32(root, config.paths.hooks ?? src + "/hooks")
29350
+ resolve34(root, config.paths.pages ?? src + "/app"),
29351
+ resolve34(root, config.paths.components ?? src + "/components"),
29352
+ resolve34(root, config.paths.hooks ?? src + "/hooks")
28962
29353
  ];
28963
29354
  const searchPattern = `api.${routerKey}.${procedureName}`;
28964
29355
  for (const dir of searchDirs) {
28965
- if (!existsSync56(dir)) continue;
29356
+ if (!existsSync57(dir)) continue;
28966
29357
  searchDirectory(dir, searchPattern, callSites);
28967
29358
  }
28968
29359
  return callSites;
28969
29360
  }
28970
29361
  function searchDirectory(dir, pattern, results) {
28971
- const entries = readdirSync20(dir, { withFileTypes: true });
29362
+ const entries = readdirSync21(dir, { withFileTypes: true });
28972
29363
  for (const entry of entries) {
28973
29364
  const fullPath = join31(dir, entry.name);
28974
29365
  if (entry.isDirectory()) {
@@ -28976,7 +29367,7 @@ function searchDirectory(dir, pattern, results) {
28976
29367
  searchDirectory(fullPath, pattern, results);
28977
29368
  } else if (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) {
28978
29369
  try {
28979
- const source = readFileSync42(fullPath, "utf-8");
29370
+ const source = readFileSync43(fullPath, "utf-8");
28980
29371
  const lines = source.split("\n");
28981
29372
  for (let i = 0; i < lines.length; i++) {
28982
29373
  if (lines[i].includes(pattern)) {
@@ -29040,8 +29431,8 @@ var init_trpc_index = __esm({
29040
29431
  });
29041
29432
 
29042
29433
  // src/page-deps.ts
29043
- import { readFileSync as readFileSync43, existsSync as existsSync57 } from "fs";
29044
- import { resolve as resolve33 } from "path";
29434
+ import { readFileSync as readFileSync44, existsSync as existsSync58 } from "fs";
29435
+ import { resolve as resolve35 } from "path";
29045
29436
  function deriveRoute(pageFile) {
29046
29437
  const pagesDir = getSourceLayout().pagesDir;
29047
29438
  const prefix3 = pagesDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -29081,10 +29472,10 @@ function findRouterCalls(files) {
29081
29472
  const routers = /* @__PURE__ */ new Set();
29082
29473
  const projectRoot = getProjectRoot();
29083
29474
  for (const file of files) {
29084
- const absPath = ensureWithinRoot(resolve33(projectRoot, file), projectRoot);
29085
- if (!existsSync57(absPath)) continue;
29475
+ const absPath = ensureWithinRoot(resolve35(projectRoot, file), projectRoot);
29476
+ if (!existsSync58(absPath)) continue;
29086
29477
  try {
29087
- const source = readFileSync43(absPath, "utf-8");
29478
+ const source = readFileSync44(absPath, "utf-8");
29088
29479
  const apiCallRegex = /api\.(\w+)\.\w+/g;
29089
29480
  let match;
29090
29481
  while ((match = apiCallRegex.exec(source)) !== null) {
@@ -29102,10 +29493,10 @@ function findTablesFromRouters(routerNames, dataDb) {
29102
29493
  `SELECT DISTINCT router_file FROM ${t("trpc_procedures")} WHERE router_name = ? LIMIT 10000`
29103
29494
  ).all(routerName);
29104
29495
  for (const proc of procs) {
29105
- const absPath = ensureWithinRoot(resolve33(getProjectRoot(), proc.router_file), getProjectRoot());
29106
- if (!existsSync57(absPath)) continue;
29496
+ const absPath = ensureWithinRoot(resolve35(getProjectRoot(), proc.router_file), getProjectRoot());
29497
+ if (!existsSync58(absPath)) continue;
29107
29498
  try {
29108
- const source = readFileSync43(absPath, "utf-8");
29499
+ const source = readFileSync44(absPath, "utf-8");
29109
29500
  const dbPattern = getConfig().dbAccessPattern ?? "ctx.db.{table}";
29110
29501
  const regexStr = dbPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace("\\{table\\}", "(\\w+)");
29111
29502
  const tableRegex = new RegExp(regexStr + "\\.", "g");
@@ -29380,14 +29771,14 @@ var init_domains = __esm({
29380
29771
  });
29381
29772
 
29382
29773
  // src/schema-mapper.ts
29383
- import { readFileSync as readFileSync44, existsSync as existsSync58, readdirSync as readdirSync21 } from "fs";
29774
+ import { readFileSync as readFileSync45, existsSync as existsSync59, readdirSync as readdirSync22 } from "fs";
29384
29775
  import { join as join32 } from "path";
29385
29776
  function parsePrismaSchema() {
29386
29777
  const schemaPath = getResolvedPaths().prismaSchemaPath;
29387
- if (!existsSync58(schemaPath)) {
29778
+ if (!existsSync59(schemaPath)) {
29388
29779
  throw new Error(`Prisma schema not found at ${schemaPath}`);
29389
29780
  }
29390
- const source = readFileSync44(schemaPath, "utf-8");
29781
+ const source = readFileSync45(schemaPath, "utf-8");
29391
29782
  const models = [];
29392
29783
  const sourceLines = source.split("\n");
29393
29784
  let i = 0;
@@ -29445,12 +29836,12 @@ function toSnakeCase(str) {
29445
29836
  function findColumnUsageInRouters(tableName) {
29446
29837
  const usage2 = /* @__PURE__ */ new Map();
29447
29838
  const routersDir = getResolvedPaths().routersDir;
29448
- if (!existsSync58(routersDir)) return usage2;
29839
+ if (!existsSync59(routersDir)) return usage2;
29449
29840
  scanDirectory(routersDir, tableName, usage2);
29450
29841
  return usage2;
29451
29842
  }
29452
29843
  function scanDirectory(dir, tableName, usage2) {
29453
- const entries = readdirSync21(dir, { withFileTypes: true });
29844
+ const entries = readdirSync22(dir, { withFileTypes: true });
29454
29845
  for (const entry of entries) {
29455
29846
  const fullPath = join32(dir, entry.name);
29456
29847
  if (entry.isDirectory()) {
@@ -29462,7 +29853,7 @@ function scanDirectory(dir, tableName, usage2) {
29462
29853
  }
29463
29854
  function scanFile(absPath, tableName, usage2) {
29464
29855
  try {
29465
- const source = readFileSync44(absPath, "utf-8");
29856
+ const source = readFileSync45(absPath, "utf-8");
29466
29857
  if (!source.includes(tableName)) return;
29467
29858
  const relPath = absPath.slice(getProjectRoot().length + 1);
29468
29859
  const lines = source.split("\n");
@@ -29507,15 +29898,15 @@ function detectMismatches(models) {
29507
29898
  }
29508
29899
  function findFilesUsingColumn(dir, column, tableName) {
29509
29900
  const result = [];
29510
- if (!existsSync58(dir)) return result;
29511
- const entries = readdirSync21(dir, { withFileTypes: true });
29901
+ if (!existsSync59(dir)) return result;
29902
+ const entries = readdirSync22(dir, { withFileTypes: true });
29512
29903
  for (const entry of entries) {
29513
29904
  const fullPath = join32(dir, entry.name);
29514
29905
  if (entry.isDirectory()) {
29515
29906
  result.push(...findFilesUsingColumn(fullPath, column, tableName));
29516
29907
  } else if (entry.name.endsWith(".ts")) {
29517
29908
  try {
29518
- const source = readFileSync44(fullPath, "utf-8");
29909
+ const source = readFileSync45(fullPath, "utf-8");
29519
29910
  if (source.includes(tableName) && source.includes(column)) {
29520
29911
  result.push(fullPath.slice(getProjectRoot().length + 1));
29521
29912
  }
@@ -29660,12 +30051,12 @@ var init_import_parser = __esm({
29660
30051
  });
29661
30052
 
29662
30053
  // src/python/import-resolver.ts
29663
- import { readFileSync as readFileSync45, existsSync as existsSync59, readdirSync as readdirSync22 } from "fs";
29664
- import { resolve as resolve35, join as join33, relative as relative9, dirname as dirname26 } from "path";
30054
+ import { readFileSync as readFileSync46, existsSync as existsSync60, readdirSync as readdirSync23 } from "fs";
30055
+ import { resolve as resolve37, join as join33, relative as relative9, dirname as dirname26 } from "path";
29665
30056
  function resolvePythonModulePath(module, fromFile, pythonRoot, level) {
29666
30057
  const projectRoot = getProjectRoot();
29667
30058
  if (level > 0) {
29668
- let baseDir = dirname26(resolve35(projectRoot, fromFile));
30059
+ let baseDir = dirname26(resolve37(projectRoot, fromFile));
29669
30060
  for (let i = 1; i < level; i++) {
29670
30061
  baseDir = dirname26(baseDir);
29671
30062
  }
@@ -29677,17 +30068,17 @@ function resolvePythonModulePath(module, fromFile, pythonRoot, level) {
29677
30068
  return tryResolvePythonPath(baseDir, projectRoot);
29678
30069
  }
29679
30070
  const parts = module.split(".");
29680
- const candidate = join33(resolve35(projectRoot, pythonRoot), ...parts);
30071
+ const candidate = join33(resolve37(projectRoot, pythonRoot), ...parts);
29681
30072
  return tryResolvePythonPath(candidate, projectRoot);
29682
30073
  }
29683
30074
  function tryResolvePythonPath(basePath, projectRoot) {
29684
- if (existsSync59(basePath + ".py")) {
30075
+ if (existsSync60(basePath + ".py")) {
29685
30076
  return relative9(projectRoot, basePath + ".py");
29686
30077
  }
29687
- if (existsSync59(join33(basePath, "__init__.py"))) {
30078
+ if (existsSync60(join33(basePath, "__init__.py"))) {
29688
30079
  return relative9(projectRoot, join33(basePath, "__init__.py"));
29689
30080
  }
29690
- if (basePath.endsWith(".py") && existsSync59(basePath)) {
30081
+ if (basePath.endsWith(".py") && existsSync60(basePath)) {
29691
30082
  return relative9(projectRoot, basePath);
29692
30083
  }
29693
30084
  return null;
@@ -29695,7 +30086,7 @@ function tryResolvePythonPath(basePath, projectRoot) {
29695
30086
  function walkPythonFiles(dir, excludeDirs) {
29696
30087
  const files = [];
29697
30088
  try {
29698
- const entries = readdirSync22(dir, { withFileTypes: true });
30089
+ const entries = readdirSync23(dir, { withFileTypes: true });
29699
30090
  for (const entry of entries) {
29700
30091
  if (entry.isDirectory()) {
29701
30092
  if (excludeDirs.includes(entry.name)) continue;
@@ -29710,7 +30101,7 @@ function walkPythonFiles(dir, excludeDirs) {
29710
30101
  }
29711
30102
  function buildPythonImportIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]) {
29712
30103
  const projectRoot = getProjectRoot();
29713
- const absRoot = resolve35(projectRoot, pythonRoot);
30104
+ const absRoot = resolve37(projectRoot, pythonRoot);
29714
30105
  dataDb.exec(`DELETE FROM ${t("py_imports")}`);
29715
30106
  const insertStmt = dataDb.prepare(
29716
30107
  `INSERT INTO ${t("py_imports")} (source_file, target_file, import_type, imported_names, line) VALUES (?, ?, ?, ?, ?)`
@@ -29727,7 +30118,7 @@ function buildPythonImportIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__"
29727
30118
  const relFile = relative9(projectRoot, absFile);
29728
30119
  let source;
29729
30120
  try {
29730
- source = readFileSync45(absFile, "utf-8");
30121
+ source = readFileSync46(absFile, "utf-8");
29731
30122
  } catch {
29732
30123
  continue;
29733
30124
  }
@@ -29994,12 +30385,12 @@ var init_route_parser = __esm({
29994
30385
  });
29995
30386
 
29996
30387
  // src/python/route-indexer.ts
29997
- import { readFileSync as readFileSync46, readdirSync as readdirSync23 } from "fs";
30388
+ import { readFileSync as readFileSync47, readdirSync as readdirSync24 } from "fs";
29998
30389
  import { join as join34, relative as relative10 } from "path";
29999
30390
  function walkPyFiles(dir, excludeDirs) {
30000
30391
  const files = [];
30001
30392
  try {
30002
- const entries = readdirSync23(dir, { withFileTypes: true });
30393
+ const entries = readdirSync24(dir, { withFileTypes: true });
30003
30394
  for (const entry of entries) {
30004
30395
  if (entry.isDirectory()) {
30005
30396
  if (excludeDirs.includes(entry.name)) continue;
@@ -30027,7 +30418,7 @@ function buildPythonRouteIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__",
30027
30418
  const relFile = relative10(projectRoot, absFile);
30028
30419
  let source;
30029
30420
  try {
30030
- source = readFileSync46(absFile, "utf-8");
30421
+ source = readFileSync47(absFile, "utf-8");
30031
30422
  } catch {
30032
30423
  continue;
30033
30424
  }
@@ -30239,12 +30630,12 @@ var init_model_parser = __esm({
30239
30630
  });
30240
30631
 
30241
30632
  // src/python/model-indexer.ts
30242
- import { readFileSync as readFileSync47, readdirSync as readdirSync24 } from "fs";
30633
+ import { readFileSync as readFileSync48, readdirSync as readdirSync25 } from "fs";
30243
30634
  import { join as join35, relative as relative11 } from "path";
30244
30635
  function walkPyFiles2(dir, excludeDirs) {
30245
30636
  const files = [];
30246
30637
  try {
30247
- const entries = readdirSync24(dir, { withFileTypes: true });
30638
+ const entries = readdirSync25(dir, { withFileTypes: true });
30248
30639
  for (const entry of entries) {
30249
30640
  if (entry.isDirectory()) {
30250
30641
  if (excludeDirs.includes(entry.name)) continue;
@@ -30275,7 +30666,7 @@ function buildPythonModelIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__",
30275
30666
  const relFile = relative11(projectRoot, absFile);
30276
30667
  let source;
30277
30668
  try {
30278
- source = readFileSync47(absFile, "utf-8");
30669
+ source = readFileSync48(absFile, "utf-8");
30279
30670
  } catch {
30280
30671
  continue;
30281
30672
  }
@@ -30536,7 +30927,7 @@ var init_migration_parser = __esm({
30536
30927
  });
30537
30928
 
30538
30929
  // src/python/migration-indexer.ts
30539
- import { readFileSync as readFileSync48, readdirSync as readdirSync25 } from "fs";
30930
+ import { readFileSync as readFileSync49, readdirSync as readdirSync26 } from "fs";
30540
30931
  import { join as join36, relative as relative12 } from "path";
30541
30932
  function buildPythonMigrationIndex(dataDb, alembicDir) {
30542
30933
  const projectRoot = getProjectRoot();
@@ -30545,10 +30936,10 @@ function buildPythonMigrationIndex(dataDb, alembicDir) {
30545
30936
  const versionsDir = join36(absDir, "versions");
30546
30937
  let files = [];
30547
30938
  try {
30548
- files = readdirSync25(versionsDir).filter((f2) => f2.endsWith(".py")).map((f2) => join36(versionsDir, f2));
30939
+ files = readdirSync26(versionsDir).filter((f2) => f2.endsWith(".py")).map((f2) => join36(versionsDir, f2));
30549
30940
  } catch {
30550
30941
  try {
30551
- files = readdirSync25(absDir).filter((f2) => f2.endsWith(".py") && f2 !== "env.py").map((f2) => join36(absDir, f2));
30942
+ files = readdirSync26(absDir).filter((f2) => f2.endsWith(".py") && f2 !== "env.py").map((f2) => join36(absDir, f2));
30552
30943
  } catch {
30553
30944
  }
30554
30945
  }
@@ -30562,7 +30953,7 @@ function buildPythonMigrationIndex(dataDb, alembicDir) {
30562
30953
  for (const absFile of files) {
30563
30954
  let source;
30564
30955
  try {
30565
- source = readFileSync48(absFile, "utf-8");
30956
+ source = readFileSync49(absFile, "utf-8");
30566
30957
  } catch {
30567
30958
  continue;
30568
30959
  }
@@ -30597,7 +30988,7 @@ var init_migration_indexer = __esm({
30597
30988
  });
30598
30989
 
30599
30990
  // src/python/coupling-detector.ts
30600
- import { readFileSync as readFileSync49, readdirSync as readdirSync26 } from "fs";
30991
+ import { readFileSync as readFileSync50, readdirSync as readdirSync27 } from "fs";
30601
30992
  import { join as join37, relative as relative13 } from "path";
30602
30993
  function buildPythonCouplingIndex(dataDb) {
30603
30994
  const projectRoot = getProjectRoot();
@@ -30634,7 +31025,7 @@ function buildPythonCouplingIndex(dataDb) {
30634
31025
  const relFile = relative13(projectRoot, absFile);
30635
31026
  let source;
30636
31027
  try {
30637
- source = readFileSync49(absFile, "utf-8");
31028
+ source = readFileSync50(absFile, "utf-8");
30638
31029
  } catch {
30639
31030
  continue;
30640
31031
  }
@@ -30662,7 +31053,7 @@ function walkFrontendFiles(dir) {
30662
31053
  const files = [];
30663
31054
  const exclude = ["node_modules", ".next", "dist", ".git", "__pycache__", ".venv", "venv"];
30664
31055
  try {
30665
- const entries = readdirSync26(dir, { withFileTypes: true });
31056
+ const entries = readdirSync27(dir, { withFileTypes: true });
30666
31057
  for (const entry of entries) {
30667
31058
  if (entry.isDirectory()) {
30668
31059
  if (exclude.includes(entry.name)) continue;
@@ -31040,8 +31431,8 @@ var init_memory_tools = __esm({
31040
31431
  });
31041
31432
 
31042
31433
  // src/docs-tools.ts
31043
- import { readFileSync as readFileSync50, existsSync as existsSync60 } from "fs";
31044
- import { resolve as resolve36, basename as basename9 } from "path";
31434
+ import { readFileSync as readFileSync51, existsSync as existsSync61 } from "fs";
31435
+ import { resolve as resolve38, basename as basename9 } from "path";
31045
31436
  function p4(baseName) {
31046
31437
  return `${getConfig().toolPrefix}_${baseName}`;
31047
31438
  }
@@ -31096,10 +31487,10 @@ function handleDocsToolCall(name, args2) {
31096
31487
  }
31097
31488
  function loadDocsMap() {
31098
31489
  const mapPath = getResolvedPaths().docsMapPath;
31099
- if (!existsSync60(mapPath)) {
31490
+ if (!existsSync61(mapPath)) {
31100
31491
  throw new Error(`docs-map.json not found at ${mapPath}`);
31101
31492
  }
31102
- return JSON.parse(readFileSync50(mapPath, "utf-8"));
31493
+ return JSON.parse(readFileSync51(mapPath, "utf-8"));
31103
31494
  }
31104
31495
  function matchesPattern(filePath, pattern) {
31105
31496
  const regexStr = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\{\{GLOBSTAR\}\}/g, ".*");
@@ -31169,13 +31560,13 @@ function extractFrontmatter(content) {
31169
31560
  }
31170
31561
  function extractProcedureNames(routerPath) {
31171
31562
  const root = getProjectRoot();
31172
- const absPath = ensureWithinRoot(resolve36(getResolvedPaths().srcDir, "..", routerPath), root);
31173
- if (!existsSync60(absPath)) {
31174
- const altPath = ensureWithinRoot(resolve36(getResolvedPaths().srcDir, "../server/api/routers", basename9(routerPath)), root);
31175
- if (!existsSync60(altPath)) return [];
31176
- return extractProcedureNamesFromContent(readFileSync50(altPath, "utf-8"));
31563
+ const absPath = ensureWithinRoot(resolve38(getResolvedPaths().srcDir, "..", routerPath), root);
31564
+ if (!existsSync61(absPath)) {
31565
+ const altPath = ensureWithinRoot(resolve38(getResolvedPaths().srcDir, "../server/api/routers", basename9(routerPath)), root);
31566
+ if (!existsSync61(altPath)) return [];
31567
+ return extractProcedureNamesFromContent(readFileSync51(altPath, "utf-8"));
31177
31568
  }
31178
- return extractProcedureNamesFromContent(readFileSync50(absPath, "utf-8"));
31569
+ return extractProcedureNamesFromContent(readFileSync51(absPath, "utf-8"));
31179
31570
  }
31180
31571
  function extractProcedureNamesFromContent(content) {
31181
31572
  const procRegex = /\.(?:query|mutation)\s*\(/g;
@@ -31215,8 +31606,8 @@ function handleDocsAudit(args2) {
31215
31606
  for (const [mappingId, triggeringFiles] of affectedMappings) {
31216
31607
  const mapping = docsMap.mappings.find((m3) => m3.id === mappingId);
31217
31608
  if (!mapping) continue;
31218
- const helpPagePath = ensureWithinRoot(resolve36(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
31219
- if (!existsSync60(helpPagePath)) {
31609
+ const helpPagePath = ensureWithinRoot(resolve38(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
31610
+ if (!existsSync61(helpPagePath)) {
31220
31611
  results.push({
31221
31612
  helpPage: mapping.helpPage,
31222
31613
  mappingId,
@@ -31228,7 +31619,7 @@ function handleDocsAudit(args2) {
31228
31619
  });
31229
31620
  continue;
31230
31621
  }
31231
- const content = readFileSync50(helpPagePath, "utf-8");
31622
+ const content = readFileSync51(helpPagePath, "utf-8");
31232
31623
  const sections = extractSections(content);
31233
31624
  const frontmatter = extractFrontmatter(content);
31234
31625
  const staleReasons = [];
@@ -31268,9 +31659,9 @@ function handleDocsAudit(args2) {
31268
31659
  });
31269
31660
  for (const [guideName, parentId] of Object.entries(docsMap.userGuideInheritance.examples)) {
31270
31661
  if (parentId === mappingId) {
31271
- const guidePath = ensureWithinRoot(resolve36(getResolvedPaths().helpSitePath, `pages/user-guides/${guideName}/index.mdx`), getProjectRoot());
31272
- if (existsSync60(guidePath)) {
31273
- const guideContent = readFileSync50(guidePath, "utf-8");
31662
+ const guidePath = ensureWithinRoot(resolve38(getResolvedPaths().helpSitePath, `pages/user-guides/${guideName}/index.mdx`), getProjectRoot());
31663
+ if (existsSync61(guidePath)) {
31664
+ const guideContent = readFileSync51(guidePath, "utf-8");
31274
31665
  const guideFrontmatter = extractFrontmatter(guideContent);
31275
31666
  if (!guideFrontmatter?.lastVerified || status === "STALE") {
31276
31667
  results.push({
@@ -31303,14 +31694,14 @@ function handleDocsCoverage(args2) {
31303
31694
  const gaps = [];
31304
31695
  const mappings = filterDomain ? docsMap.mappings.filter((m3) => m3.id === filterDomain) : docsMap.mappings;
31305
31696
  for (const mapping of mappings) {
31306
- const helpPagePath = ensureWithinRoot(resolve36(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
31307
- const exists = existsSync60(helpPagePath);
31697
+ const helpPagePath = ensureWithinRoot(resolve38(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
31698
+ const exists = existsSync61(helpPagePath);
31308
31699
  let hasContent = false;
31309
31700
  let lineCount = 0;
31310
31701
  let lastVerified = null;
31311
31702
  let status = null;
31312
31703
  if (exists) {
31313
- const content = readFileSync50(helpPagePath, "utf-8");
31704
+ const content = readFileSync51(helpPagePath, "utf-8");
31314
31705
  lineCount = content.split("\n").length;
31315
31706
  hasContent = lineCount > 10;
31316
31707
  const frontmatter = extractFrontmatter(content);
@@ -31651,8 +32042,8 @@ var init_observability_tools = __esm({
31651
32042
  });
31652
32043
 
31653
32044
  // src/sentinel-db.ts
31654
- import { existsSync as existsSync61 } from "fs";
31655
- import { resolve as resolve37 } from "path";
32045
+ import { existsSync as existsSync62 } from "fs";
32046
+ import { resolve as resolve39 } from "path";
31656
32047
  function parsePortalScope(raw) {
31657
32048
  if (!raw) return [];
31658
32049
  try {
@@ -31888,23 +32279,23 @@ function validateFeatures(db, domainFilter) {
31888
32279
  const missingProcedures = [];
31889
32280
  const missingPages = [];
31890
32281
  for (const comp of components) {
31891
- const absPath = resolve37(PROJECT_ROOT, comp.component_file);
31892
- if (!existsSync61(absPath)) {
32282
+ const absPath = resolve39(PROJECT_ROOT, comp.component_file);
32283
+ if (!existsSync62(absPath)) {
31893
32284
  missingComponents.push(comp.component_file);
31894
32285
  }
31895
32286
  }
31896
32287
  for (const proc of procedures) {
31897
- const routerPath = resolve37(PROJECT_ROOT, `src/server/api/routers/${proc.router_name}.ts`);
31898
- if (!existsSync61(routerPath)) {
32288
+ const routerPath = resolve39(PROJECT_ROOT, `src/server/api/routers/${proc.router_name}.ts`);
32289
+ if (!existsSync62(routerPath)) {
31899
32290
  missingProcedures.push({ router: proc.router_name, procedure: proc.procedure_name });
31900
32291
  }
31901
32292
  }
31902
32293
  for (const page of pages) {
31903
32294
  const routeToPath = page.page_route.replace(/^\/(portal-[^/]+\/)?/, "src/app/").replace(/\/$/, "") + "/page.tsx";
31904
- const absPath = resolve37(PROJECT_ROOT, routeToPath);
31905
- if (page.page_route.startsWith("/") && !existsSync61(absPath)) {
31906
- const altPath = resolve37(PROJECT_ROOT, `src/app${page.page_route}/page.tsx`);
31907
- if (!existsSync61(altPath)) {
32295
+ const absPath = resolve39(PROJECT_ROOT, routeToPath);
32296
+ if (page.page_route.startsWith("/") && !existsSync62(absPath)) {
32297
+ const altPath = resolve39(PROJECT_ROOT, `src/app${page.page_route}/page.tsx`);
32298
+ if (!existsSync62(altPath)) {
31908
32299
  missingPages.push(page.page_route);
31909
32300
  }
31910
32301
  }
@@ -32430,8 +32821,8 @@ var init_sentinel_tools = __esm({
32430
32821
  });
32431
32822
 
32432
32823
  // src/sentinel-scanner.ts
32433
- import { readFileSync as readFileSync51, existsSync as existsSync62, readdirSync as readdirSync27, statSync as statSync20 } from "fs";
32434
- import { resolve as resolve38, join as join38, basename as basename10, dirname as dirname27, relative as relative14 } from "path";
32824
+ import { readFileSync as readFileSync52, existsSync as existsSync63, readdirSync as readdirSync28, statSync as statSync21 } from "fs";
32825
+ import { resolve as resolve40, join as join38, basename as basename10, dirname as dirname27, relative as relative14 } from "path";
32435
32826
  function inferDomain(filePath) {
32436
32827
  const domains = getConfig().domains;
32437
32828
  const path = filePath.toLowerCase();
@@ -32562,10 +32953,10 @@ function scanComponentExports(dataDb) {
32562
32953
  const projectRoot = getProjectRoot();
32563
32954
  const componentsBase = config.paths.components ?? config.paths.source + "/components";
32564
32955
  const componentDirs = [];
32565
- const basePath = resolve38(projectRoot, componentsBase);
32566
- if (existsSync62(basePath)) {
32956
+ const basePath = resolve40(projectRoot, componentsBase);
32957
+ if (existsSync63(basePath)) {
32567
32958
  try {
32568
- const entries = readdirSync27(basePath, { withFileTypes: true });
32959
+ const entries = readdirSync28(basePath, { withFileTypes: true });
32569
32960
  for (const entry of entries) {
32570
32961
  if (entry.isDirectory()) {
32571
32962
  componentDirs.push(componentsBase + "/" + entry.name);
@@ -32575,12 +32966,12 @@ function scanComponentExports(dataDb) {
32575
32966
  }
32576
32967
  }
32577
32968
  for (const dir of componentDirs) {
32578
- const absDir = resolve38(projectRoot, dir);
32579
- if (!existsSync62(absDir)) continue;
32969
+ const absDir = resolve40(projectRoot, dir);
32970
+ if (!existsSync63(absDir)) continue;
32580
32971
  const files = walkDir2(absDir).filter((f2) => f2.endsWith(".tsx") || f2.endsWith(".ts"));
32581
32972
  for (const file of files) {
32582
32973
  const relPath = relative14(projectRoot, file);
32583
- const source = readFileSync51(file, "utf-8");
32974
+ const source = readFileSync52(file, "utf-8");
32584
32975
  const annotations = parseFeatureAnnotations(source);
32585
32976
  if (annotations.length > 0) {
32586
32977
  for (const ann of annotations) {
@@ -32629,11 +33020,11 @@ function scanComponentExports(dataDb) {
32629
33020
  function walkDir2(dir) {
32630
33021
  const results = [];
32631
33022
  try {
32632
- const entries = readdirSync27(dir);
33023
+ const entries = readdirSync28(dir);
32633
33024
  for (const entry of entries) {
32634
33025
  const fullPath = join38(dir, entry);
32635
33026
  try {
32636
- const stat = statSync20(fullPath);
33027
+ const stat = statSync21(fullPath);
32637
33028
  if (stat.isDirectory()) {
32638
33029
  results.push(...walkDir2(fullPath));
32639
33030
  } else {
@@ -33341,7 +33732,7 @@ var init_prompt_analyzer = __esm({
33341
33732
  });
33342
33733
 
33343
33734
  // src/validation-engine.ts
33344
- import { existsSync as existsSync63, readFileSync as readFileSync52 } from "fs";
33735
+ import { existsSync as existsSync64, readFileSync as readFileSync53 } from "fs";
33345
33736
  function p10(baseName) {
33346
33737
  return `${getConfig().toolPrefix}_${baseName}`;
33347
33738
  }
@@ -33372,7 +33763,7 @@ function validateFile(filePath, projectRoot) {
33372
33763
  });
33373
33764
  return checks;
33374
33765
  }
33375
- if (!existsSync63(absPath)) {
33766
+ if (!existsSync64(absPath)) {
33376
33767
  checks.push({
33377
33768
  name: "file_exists",
33378
33769
  severity: "error",
@@ -33381,7 +33772,7 @@ function validateFile(filePath, projectRoot) {
33381
33772
  });
33382
33773
  return checks;
33383
33774
  }
33384
- const source = readFileSync52(absPath, "utf-8");
33775
+ const source = readFileSync53(absPath, "utf-8");
33385
33776
  const lines = source.split("\n");
33386
33777
  if (activeChecks.rule_compliance !== false) {
33387
33778
  for (const ruleSet of config.rules) {
@@ -33840,7 +34231,7 @@ var init_adr_generator = __esm({
33840
34231
  });
33841
34232
 
33842
34233
  // src/security-scorer.ts
33843
- import { existsSync as existsSync64, readFileSync as readFileSync53 } from "fs";
34234
+ import { existsSync as existsSync65, readFileSync as readFileSync54 } from "fs";
33844
34235
  function p12(baseName) {
33845
34236
  return `${getConfig().toolPrefix}_${baseName}`;
33846
34237
  }
@@ -33864,12 +34255,12 @@ function scoreFileSecurity(filePath, projectRoot) {
33864
34255
  }]
33865
34256
  };
33866
34257
  }
33867
- if (!existsSync64(absPath)) {
34258
+ if (!existsSync65(absPath)) {
33868
34259
  return { riskScore: 0, findings: [] };
33869
34260
  }
33870
34261
  let source;
33871
34262
  try {
33872
- source = readFileSync53(absPath, "utf-8");
34263
+ source = readFileSync54(absPath, "utf-8");
33873
34264
  } catch {
33874
34265
  return { riskScore: 0, findings: [] };
33875
34266
  }
@@ -34159,8 +34550,8 @@ var init_security_scorer = __esm({
34159
34550
  });
34160
34551
 
34161
34552
  // src/dependency-scorer.ts
34162
- import { existsSync as existsSync65, readFileSync as readFileSync54 } from "fs";
34163
- import { resolve as resolve39 } from "path";
34553
+ import { existsSync as existsSync66, readFileSync as readFileSync55 } from "fs";
34554
+ import { resolve as resolve41 } from "path";
34164
34555
  function p13(baseName) {
34165
34556
  return `${getConfig().toolPrefix}_${baseName}`;
34166
34557
  }
@@ -34192,10 +34583,10 @@ function calculateDepRisk(factors) {
34192
34583
  return Math.min(100, risk);
34193
34584
  }
34194
34585
  function getInstalledPackages(projectRoot) {
34195
- const pkgPath = resolve39(projectRoot, "package.json");
34196
- if (!existsSync65(pkgPath)) return /* @__PURE__ */ new Map();
34586
+ const pkgPath = resolve41(projectRoot, "package.json");
34587
+ if (!existsSync66(pkgPath)) return /* @__PURE__ */ new Map();
34197
34588
  try {
34198
- const pkg = JSON.parse(readFileSync54(pkgPath, "utf-8"));
34589
+ const pkg = JSON.parse(readFileSync55(pkgPath, "utf-8"));
34199
34590
  const packages = /* @__PURE__ */ new Map();
34200
34591
  for (const [name, version] of Object.entries(pkg.dependencies ?? {})) {
34201
34592
  packages.set(name, version);
@@ -34801,8 +35192,8 @@ var init_regression_detector = __esm({
34801
35192
 
34802
35193
  // src/knowledge-indexer.ts
34803
35194
  import { createHash as createHash16 } from "crypto";
34804
- import { readFileSync as readFileSync55, readdirSync as readdirSync28, statSync as statSync21, existsSync as existsSync66 } from "fs";
34805
- import { resolve as resolve40, relative as relative15, basename as basename11, extname as extname2 } from "path";
35195
+ import { readFileSync as readFileSync56, readdirSync as readdirSync29, statSync as statSync22, existsSync as existsSync67 } from "fs";
35196
+ import { resolve as resolve42, relative as relative15, basename as basename11, extname as extname2 } from "path";
34806
35197
  function getKnowledgePaths() {
34807
35198
  const resolved = getResolvedPaths();
34808
35199
  const config = getConfig();
@@ -34828,9 +35219,9 @@ function discoverMarkdownFiles(baseDir) {
34828
35219
  const files = [];
34829
35220
  function walk(dir) {
34830
35221
  try {
34831
- const entries = readdirSync28(dir, { withFileTypes: true });
35222
+ const entries = readdirSync29(dir, { withFileTypes: true });
34832
35223
  for (const entry of entries) {
34833
- const fullPath = resolve40(dir, entry.name);
35224
+ const fullPath = resolve42(dir, entry.name);
34834
35225
  if (entry.isDirectory()) {
34835
35226
  if (entry.name === "archive" && dir.includes("session-state")) continue;
34836
35227
  if (entry.name === "archive" && dir.includes("status")) continue;
@@ -34892,14 +35283,17 @@ function hashContent2(content) {
34892
35283
  }
34893
35284
  function parseCRTable(content) {
34894
35285
  const rules = [];
34895
- const tableRegex = /\|\s*(CR-\d+)\s*\|\s*([^|]+)\|\s*([^|]+)\|\s*\[?([^\]|]+)\]?[^|]*\|/g;
34896
- let match;
34897
- while ((match = tableRegex.exec(content)) !== null) {
35286
+ for (const line of content.split("\n")) {
35287
+ const trimmed = line.trim();
35288
+ if (!trimmed.startsWith("|")) continue;
35289
+ const cells = trimmed.split("|").slice(1, -1).map((c2) => c2.trim());
35290
+ if (cells.length < 3) continue;
35291
+ if (!/^CR-\d+$/.test(cells[0])) continue;
34898
35292
  rules.push({
34899
- rule_id: match[1].trim(),
34900
- rule_text: match[2].trim(),
34901
- vr_type: match[3].trim(),
34902
- reference_path: match[4].trim().replace(/\(.*\)/, "").trim()
35293
+ rule_id: cells[0],
35294
+ rule_text: cells[1],
35295
+ vr_type: cells[2],
35296
+ reference_path: (cells[3] ?? "").replace(/^\[|\]$/g, "").replace(/\(.*\)/, "").trim()
34903
35297
  });
34904
35298
  }
34905
35299
  return rules;
@@ -35082,7 +35476,7 @@ function buildCrossReferences(db) {
35082
35476
  return edgeCount;
35083
35477
  }
35084
35478
  function indexAllKnowledge(db) {
35085
- const stats = { filesIndexed: 0, chunksCreated: 0, edgesCreated: 0 };
35479
+ const stats = { filesIndexed: 0, chunksCreated: 0, edgesCreated: 0, indexedPaths: [] };
35086
35480
  const paths = getKnowledgePaths();
35087
35481
  const insertDoc = db.prepare(
35088
35482
  "INSERT INTO knowledge_documents (file_path, category, title, description, content_hash, indexed_at, indexed_at_epoch) VALUES (?, ?, ?, ?, ?, ?, ?)"
@@ -35109,11 +35503,11 @@ function indexAllKnowledge(db) {
35109
35503
  files.push(...memFiles);
35110
35504
  } catch {
35111
35505
  }
35112
- if (existsSync66(paths.plansDir)) {
35506
+ if (existsSync67(paths.plansDir)) {
35113
35507
  const planFiles = discoverMarkdownFiles(paths.plansDir);
35114
35508
  files.push(...planFiles);
35115
35509
  }
35116
- if (existsSync66(paths.docsDir)) {
35510
+ if (existsSync67(paths.docsDir)) {
35117
35511
  const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
35118
35512
  const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f2) => !f2.includes("/plans/") && !excludePatterns.some((p19) => f2.includes(p19)));
35119
35513
  files.push(...docsFiles);
@@ -35156,8 +35550,8 @@ function indexAllKnowledge(db) {
35156
35550
  } catch {
35157
35551
  }
35158
35552
  for (const filePath of files) {
35159
- if (!existsSync66(filePath)) continue;
35160
- const content = readFileSync55(filePath, "utf-8");
35553
+ if (!existsSync67(filePath)) continue;
35554
+ const content = readFileSync56(filePath, "utf-8");
35161
35555
  const hash = hashContent2(content);
35162
35556
  const relPath = filePath.startsWith(paths.claudeDir) ? relative15(paths.claudeDir, filePath) : filePath.startsWith(paths.plansDir) ? "plans/" + relative15(paths.plansDir, filePath) : filePath.startsWith(paths.docsDir) ? "docs/" + relative15(paths.docsDir, filePath) : filePath.startsWith(paths.memoryDir) ? `memory/${relative15(paths.memoryDir, filePath)}` : basename11(filePath);
35163
35557
  const category = categorizeFile(filePath);
@@ -35166,6 +35560,7 @@ function indexAllKnowledge(db) {
35166
35560
  const result = insertDoc.run(relPath, category, title, description, hash, nowIso2, nowEpoch);
35167
35561
  const docId = result.lastInsertRowid;
35168
35562
  stats.filesIndexed++;
35563
+ stats.indexedPaths.push(filePath);
35169
35564
  const sections = parseSections(content, filePath);
35170
35565
  for (const section of sections) {
35171
35566
  if (section.content.length > 10) {
@@ -35234,7 +35629,7 @@ function indexAllKnowledge(db) {
35234
35629
  stats.chunksCreated++;
35235
35630
  }
35236
35631
  }
35237
- if (fileNameLower === "corrections.md") {
35632
+ if (fileNameLower === CORRECTIONS_FILENAME) {
35238
35633
  const corrections = parseCorrections(content);
35239
35634
  for (const c2 of corrections) {
35240
35635
  insertChunk.run(
@@ -35292,17 +35687,17 @@ function isKnowledgeStale(db) {
35292
35687
  files.push(...discoverMarkdownFiles(paths.memoryDir));
35293
35688
  } catch {
35294
35689
  }
35295
- if (existsSync66(paths.plansDir)) {
35690
+ if (existsSync67(paths.plansDir)) {
35296
35691
  files.push(...discoverMarkdownFiles(paths.plansDir));
35297
35692
  }
35298
- if (existsSync66(paths.docsDir)) {
35693
+ if (existsSync67(paths.docsDir)) {
35299
35694
  const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
35300
35695
  const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f2) => !f2.includes("/plans/") && !excludePatterns.some((p19) => f2.includes(p19)));
35301
35696
  files.push(...docsFiles);
35302
35697
  }
35303
35698
  for (const filePath of files) {
35304
35699
  try {
35305
- const stat = statSync21(filePath);
35700
+ const stat = statSync22(filePath);
35306
35701
  if (stat.mtimeMs > lastIndexTime) return true;
35307
35702
  } catch {
35308
35703
  continue;
@@ -35314,18 +35709,19 @@ function indexIfStale(db) {
35314
35709
  if (isKnowledgeStale(db)) {
35315
35710
  return indexAllKnowledge(db);
35316
35711
  }
35317
- return { filesIndexed: 0, chunksCreated: 0, edgesCreated: 0 };
35712
+ return { filesIndexed: 0, chunksCreated: 0, edgesCreated: 0, indexedPaths: [] };
35318
35713
  }
35319
35714
  var init_knowledge_indexer = __esm({
35320
35715
  "src/knowledge-indexer.ts"() {
35321
35716
  "use strict";
35717
+ init_corrections_md();
35322
35718
  init_config();
35323
35719
  }
35324
35720
  });
35325
35721
 
35326
35722
  // src/knowledge-tools.ts
35327
- import { readFileSync as readFileSync56, writeFileSync as writeFileSync17, appendFileSync as appendFileSync5, readdirSync as readdirSync29 } from "fs";
35328
- import { resolve as resolve41, basename as basename12 } from "path";
35723
+ import { readFileSync as readFileSync57, writeFileSync as writeFileSync17, readdirSync as readdirSync30 } from "fs";
35724
+ import { basename as basename12 } from "path";
35329
35725
  function p16(baseName) {
35330
35726
  return `${getConfig().toolPrefix}_${baseName}`;
35331
35727
  }
@@ -36080,30 +36476,23 @@ function handleCorrect(db, args2) {
36080
36476
  if (!wrong || !correction || !rule) {
36081
36477
  return text15("Error: wrong, correction, and rule are all required.");
36082
36478
  }
36083
- const correctionsPath = resolve41(getResolvedPaths().memoryDir, "corrections.md");
36479
+ const correctionsFile = correctionsPath(getResolvedPaths().memoryDir);
36084
36480
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
36085
36481
  const title = rule.slice(0, 60);
36086
- const entry = formatCorrectionEntry({
36482
+ const correctionEntry = {
36087
36483
  date: today,
36088
36484
  title,
36089
36485
  wrong,
36090
36486
  correction,
36091
36487
  rule,
36092
36488
  ...crRule ? { extra: { CR: crRule } } : {}
36093
- });
36489
+ };
36094
36490
  let existing = "";
36095
36491
  try {
36096
- existing = readFileSync56(correctionsPath, "utf-8");
36492
+ existing = readFileSync57(correctionsFile, "utf-8");
36097
36493
  } catch {
36098
36494
  }
36099
- const archiveIdx = existing.indexOf("## Archived");
36100
- if (archiveIdx > 0) {
36101
- const before = existing.slice(0, archiveIdx);
36102
- const after = existing.slice(archiveIdx);
36103
- writeFileSync17(correctionsPath, before + entry + after);
36104
- } else {
36105
- appendFileSync5(correctionsPath, entry);
36106
- }
36495
+ writeFileSync17(correctionsFile, insertCorrectionEntry(existing, correctionEntry));
36107
36496
  const doc = db.prepare("SELECT id FROM knowledge_documents WHERE file_path LIKE ?").get("%corrections.md");
36108
36497
  if (doc) {
36109
36498
  db.prepare("INSERT INTO knowledge_chunks (document_id, chunk_type, heading, content, metadata) VALUES (?, ?, ?, ?, ?)").run(
@@ -36275,7 +36664,7 @@ function handleGaps(db, args2) {
36275
36664
  } else if (checkType === "routers") {
36276
36665
  try {
36277
36666
  const routersDir = getResolvedPaths().routersDir;
36278
- const routerFiles = readdirSync29(routersDir).filter((f2) => f2.endsWith(".ts") && !f2.startsWith("_"));
36667
+ const routerFiles = readdirSync30(routersDir).filter((f2) => f2.endsWith(".ts") && !f2.startsWith("_"));
36279
36668
  lines.push(`| Router | Knowledge Hits | Status |`);
36280
36669
  lines.push(`|--------|----------------|--------|`);
36281
36670
  for (const file of routerFiles) {
@@ -37050,8 +37439,8 @@ var init_framework_supports = __esm({
37050
37439
  });
37051
37440
 
37052
37441
  // src/tools.ts
37053
- import { readFileSync as readFileSync57, existsSync as existsSync68 } from "fs";
37054
- import { resolve as resolve42, basename as basename13 } from "path";
37442
+ import { readFileSync as readFileSync58, existsSync as existsSync69 } from "fs";
37443
+ import { resolve as resolve44, basename as basename13 } from "path";
37055
37444
  function prefix2() {
37056
37445
  return getConfig().toolPrefix;
37057
37446
  }
@@ -37086,7 +37475,7 @@ function ensureIndexes(dataDb, codegraphDb, force = false) {
37086
37475
  if (config.python?.root) {
37087
37476
  const pythonRoot = config.python.root;
37088
37477
  const excludeDirs = config.python.exclude_dirs || ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"];
37089
- if (force || isPythonDataStale(dataDb, resolve42(getProjectRoot(), pythonRoot))) {
37478
+ if (force || isPythonDataStale(dataDb, resolve44(getProjectRoot(), pythonRoot))) {
37090
37479
  const pyImports = buildPythonImportIndex(dataDb, pythonRoot, excludeDirs);
37091
37480
  results.push(`Python imports: ${pyImports}`);
37092
37481
  const pyRoutes = buildPythonRouteIndex(dataDb, pythonRoot, excludeDirs);
@@ -37469,9 +37858,9 @@ function handleContext(file, dataDb, codegraphDb) {
37469
37858
  try {
37470
37859
  const resolvedPaths = getResolvedPaths();
37471
37860
  const root = getProjectRoot();
37472
- const absFilePath = ensureWithinRoot(resolve42(resolvedPaths.srcDir, "..", file), root);
37473
- if (existsSync68(absFilePath)) {
37474
- const fileContent = readFileSync57(absFilePath, "utf-8").slice(0, 3e3);
37861
+ const absFilePath = ensureWithinRoot(resolve44(resolvedPaths.srcDir, "..", file), root);
37862
+ if (existsSync69(absFilePath)) {
37863
+ const fileContent = readFileSync58(absFilePath, "utf-8").slice(0, 3e3);
37475
37864
  const keywords = [];
37476
37865
  if (fileContent.includes("ctx.db")) keywords.push("database", "schema");
37477
37866
  if (fileContent.includes("BigInt") || fileContent.includes("Decimal")) keywords.push("BigInt", "serialization");
@@ -37910,11 +38299,11 @@ function handleSchema(args2) {
37910
38299
  lines.push("Checking all column references against Prisma schema...");
37911
38300
  lines.push("");
37912
38301
  const projectRoot = getProjectRoot();
37913
- const absPath = ensureWithinRoot(resolve42(projectRoot, file), projectRoot);
37914
- if (!existsSync68(absPath)) {
38302
+ const absPath = ensureWithinRoot(resolve44(projectRoot, file), projectRoot);
38303
+ if (!existsSync69(absPath)) {
37915
38304
  return text17(`File not found: ${file}`);
37916
38305
  }
37917
- const source = readFileSync57(absPath, "utf-8");
38306
+ const source = readFileSync58(absPath, "utf-8");
37918
38307
  const config = getConfig();
37919
38308
  const dbPattern = config.dbAccessPattern ?? "ctx.db.{table}";
37920
38309
  const regexStr = dbPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace("\\{table\\}", "(\\w+)");
@@ -38347,8 +38736,8 @@ var init_startup_health = __esm({
38347
38736
 
38348
38737
  // src/server.ts
38349
38738
  var server_exports = {};
38350
- import { readFileSync as readFileSync58 } from "fs";
38351
- import { resolve as resolve43, dirname as dirname28 } from "path";
38739
+ import { readFileSync as readFileSync59 } from "fs";
38740
+ import { resolve as resolve45, dirname as dirname28 } from "path";
38352
38741
  import { fileURLToPath as fileURLToPath6 } from "url";
38353
38742
  function pruneMemoryOnStartup() {
38354
38743
  try {
@@ -38396,7 +38785,7 @@ var init_server = __esm({
38396
38785
  __dirname5 = dirname28(fileURLToPath6(import.meta.url));
38397
38786
  PKG_VERSION = (() => {
38398
38787
  try {
38399
- const pkg = JSON.parse(readFileSync58(resolve43(__dirname5, "..", "package.json"), "utf-8"));
38788
+ const pkg = JSON.parse(readFileSync59(resolve45(__dirname5, "..", "package.json"), "utf-8"));
38400
38789
  return pkg.version ?? "0.0.0";
38401
38790
  } catch {
38402
38791
  return "0.0.0";
@@ -38699,19 +39088,19 @@ var config_upgrade_exports = {};
38699
39088
  __export(config_upgrade_exports, {
38700
39089
  runConfigUpgrade: () => runConfigUpgrade
38701
39090
  });
38702
- import { existsSync as existsSync69, readFileSync as readFileSync59, writeFileSync as writeFileSync18, copyFileSync as copyFileSync3, unlinkSync as unlinkSync6 } from "fs";
38703
- import { resolve as resolve44 } from "path";
39091
+ import { existsSync as existsSync70, readFileSync as readFileSync60, writeFileSync as writeFileSync18, copyFileSync as copyFileSync3, unlinkSync as unlinkSync6 } from "fs";
39092
+ import { resolve as resolve46 } from "path";
38704
39093
  import { parse as parseYaml6 } from "yaml";
38705
39094
  async function runConfigUpgrade(opts = {}) {
38706
39095
  const cwd = opts.cwd ?? process.cwd();
38707
- const configPath = resolve44(cwd, "massu.config.yaml");
39096
+ const configPath = resolve46(cwd, "massu.config.yaml");
38708
39097
  const bakPath = `${configPath}.bak`;
38709
39098
  const log = opts.silent ? () => {
38710
39099
  } : (s) => process.stdout.write(s);
38711
39100
  const err = opts.silent ? () => {
38712
39101
  } : (s) => process.stderr.write(s);
38713
39102
  if (opts.rollback) {
38714
- if (!existsSync69(bakPath)) {
39103
+ if (!existsSync70(bakPath)) {
38715
39104
  const message = `No backup found at ${bakPath}`;
38716
39105
  err(message + "\n");
38717
39106
  return { exitCode: 1, action: "none", message };
@@ -38727,14 +39116,14 @@ async function runConfigUpgrade(opts = {}) {
38727
39116
  return { exitCode: 2, action: "none", message };
38728
39117
  }
38729
39118
  }
38730
- if (!existsSync69(configPath)) {
39119
+ if (!existsSync70(configPath)) {
38731
39120
  const message = "massu.config.yaml not found. Run: npx massu init";
38732
39121
  err(message + "\n");
38733
39122
  return { exitCode: 1, action: "none", message };
38734
39123
  }
38735
39124
  let existing;
38736
39125
  try {
38737
- const content = readFileSync59(configPath, "utf-8");
39126
+ const content = readFileSync60(configPath, "utf-8");
38738
39127
  const parsed = parseYaml6(content);
38739
39128
  if (!parsed || typeof parsed !== "object") {
38740
39129
  throw new Error("config is not a YAML object");
@@ -38757,7 +39146,7 @@ async function runConfigUpgrade(opts = {}) {
38757
39146
  fingerprint: computeFingerprint(detection)
38758
39147
  };
38759
39148
  try {
38760
- const original = readFileSync59(configPath, "utf-8");
39149
+ const original = readFileSync60(configPath, "utf-8");
38761
39150
  writeFileSync18(bakPath, original, "utf-8");
38762
39151
  } catch (e2) {
38763
39152
  const message = `Failed to write backup: ${e2 instanceof Error ? e2.message : String(e2)}`;
@@ -38791,8 +39180,8 @@ var config_check_drift_exports = {};
38791
39180
  __export(config_check_drift_exports, {
38792
39181
  runConfigCheckDrift: () => runConfigCheckDrift
38793
39182
  });
38794
- import { existsSync as existsSync70, readFileSync as readFileSync60 } from "fs";
38795
- import { resolve as resolve45 } from "path";
39183
+ import { existsSync as existsSync71, readFileSync as readFileSync61 } from "fs";
39184
+ import { resolve as resolve47 } from "path";
38796
39185
  import { parse as parseYaml7 } from "yaml";
38797
39186
  function renderChanges(changes) {
38798
39187
  if (changes.length === 0) return "(none)\n";
@@ -38800,12 +39189,12 @@ function renderChanges(changes) {
38800
39189
  }
38801
39190
  async function runConfigCheckDrift(opts = {}) {
38802
39191
  const cwd = opts.cwd ?? process.cwd();
38803
- const configPath = resolve45(cwd, "massu.config.yaml");
39192
+ const configPath = resolve47(cwd, "massu.config.yaml");
38804
39193
  const log = opts.silent ? () => {
38805
39194
  } : (s) => process.stdout.write(s);
38806
39195
  const err = opts.silent ? () => {
38807
39196
  } : (s) => process.stderr.write(s);
38808
- if (!existsSync70(configPath)) {
39197
+ if (!existsSync71(configPath)) {
38809
39198
  const message = "massu.config.yaml not found. Run: npx massu init";
38810
39199
  err(message + "\n");
38811
39200
  return {
@@ -38819,7 +39208,7 @@ async function runConfigCheckDrift(opts = {}) {
38819
39208
  }
38820
39209
  let config;
38821
39210
  try {
38822
- const content = readFileSync60(configPath, "utf-8");
39211
+ const content = readFileSync61(configPath, "utf-8");
38823
39212
  const parsed = parseYaml7(content);
38824
39213
  if (!parsed || typeof parsed !== "object") {
38825
39214
  throw new Error("config is not a YAML object");
@@ -38886,8 +39275,8 @@ var init_config_check_drift = __esm({
38886
39275
  });
38887
39276
 
38888
39277
  // src/cli.ts
38889
- import { readFileSync as readFileSync61 } from "fs";
38890
- import { resolve as resolve46, dirname as dirname29 } from "path";
39278
+ import { readFileSync as readFileSync62 } from "fs";
39279
+ import { resolve as resolve48, dirname as dirname29 } from "path";
38891
39280
  import { fileURLToPath as fileURLToPath7 } from "url";
38892
39281
 
38893
39282
  // src/lib/node-bootstrap.ts
@@ -39389,7 +39778,7 @@ Examples:
39389
39778
  }
39390
39779
  function printVersion() {
39391
39780
  try {
39392
- const pkg = JSON.parse(readFileSync61(resolve46(__dirname6, "../package.json"), "utf-8"));
39781
+ const pkg = JSON.parse(readFileSync62(resolve48(__dirname6, "../package.json"), "utf-8"));
39393
39782
  console.log(`massu v${pkg.version}`);
39394
39783
  } catch {
39395
39784
  console.log("massu v0.1.0");