@decantr/cli 2.5.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,58 @@
1
+ // src/bundled-content.ts
2
+ import { existsSync, readdirSync, readFileSync } from "fs";
3
+ import { dirname, join } from "path";
4
+ import { fileURLToPath } from "url";
5
+ function bundledDirCandidates(contentType) {
6
+ const currentDir = dirname(fileURLToPath(import.meta.url));
7
+ return [
8
+ join(currentDir, "bundled", contentType),
9
+ join(currentDir, "..", "src", "bundled", contentType),
10
+ join(currentDir, "..", "bundled", contentType)
11
+ ];
12
+ }
13
+ function getBundledContentPath(contentType, id) {
14
+ for (const dir of bundledDirCandidates(contentType)) {
15
+ const candidate = join(dir, `${id}.json`);
16
+ if (existsSync(candidate)) return candidate;
17
+ }
18
+ return null;
19
+ }
20
+ function loadBundledContentItem(contentType, id) {
21
+ const path = getBundledContentPath(contentType, id);
22
+ if (!path) return null;
23
+ try {
24
+ const data = JSON.parse(readFileSync(path, "utf-8"));
25
+ return { id, data, path };
26
+ } catch {
27
+ return null;
28
+ }
29
+ }
30
+ function loadBundledContentList(contentType) {
31
+ const entries = [];
32
+ const seen = /* @__PURE__ */ new Set();
33
+ for (const dir of bundledDirCandidates(contentType)) {
34
+ if (!existsSync(dir)) continue;
35
+ try {
36
+ for (const file of readdirSync(dir).filter((name) => name.endsWith(".json"))) {
37
+ const id = file.replace(/\.json$/, "");
38
+ if (seen.has(id)) continue;
39
+ const path = join(dir, file);
40
+ const data = JSON.parse(readFileSync(path, "utf-8"));
41
+ entries.push({ id, data, path });
42
+ seen.add(id);
43
+ }
44
+ } catch {
45
+ }
46
+ }
47
+ return entries;
48
+ }
49
+
1
50
  // src/telemetry.ts
2
51
  import { randomUUID } from "crypto";
3
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
52
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
4
53
  import { homedir } from "os";
5
- import { dirname, join, resolve } from "path";
6
- import { fileURLToPath } from "url";
54
+ import { dirname as dirname2, join as join2, resolve } from "path";
55
+ import { fileURLToPath as fileURLToPath2 } from "url";
7
56
  import {
8
57
  createFetchTelemetrySink,
9
58
  createTelemetryClient,
@@ -28,26 +77,26 @@ async function sendGuardMetrics(metrics) {
28
77
  }
29
78
  }
30
79
  function isOptedIn(projectRoot) {
31
- const projectJsonPath = join(projectRoot, ".decantr", "project.json");
32
- if (!existsSync(projectJsonPath)) return false;
80
+ const projectJsonPath = join2(projectRoot, ".decantr", "project.json");
81
+ if (!existsSync2(projectJsonPath)) return false;
33
82
  try {
34
- const data = JSON.parse(readFileSync(projectJsonPath, "utf-8"));
83
+ const data = JSON.parse(readFileSync2(projectJsonPath, "utf-8"));
35
84
  return data.telemetry === true;
36
85
  } catch {
37
86
  return false;
38
87
  }
39
88
  }
40
89
  function optIn(projectRoot) {
41
- const projectJsonPath = join(projectRoot, ".decantr", "project.json");
90
+ const projectJsonPath = join2(projectRoot, ".decantr", "project.json");
42
91
  let data = {};
43
- if (existsSync(projectJsonPath)) {
92
+ if (existsSync2(projectJsonPath)) {
44
93
  try {
45
- data = JSON.parse(readFileSync(projectJsonPath, "utf-8"));
94
+ data = JSON.parse(readFileSync2(projectJsonPath, "utf-8"));
46
95
  } catch {
47
96
  }
48
97
  }
49
98
  data.telemetry = true;
50
- mkdirSync(dirname(projectJsonPath), { recursive: true });
99
+ mkdirSync(dirname2(projectJsonPath), { recursive: true });
51
100
  writeFileSync(projectJsonPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
52
101
  }
53
102
  async function captureCliTelemetryEvent(input) {
@@ -328,8 +377,8 @@ function collectMetrics(essence, issues) {
328
377
  };
329
378
  }
330
379
  function getCliTelemetryIdentityStatus(projectRoot, options = {}) {
331
- const projectJsonPath = join(projectRoot, ".decantr", "project.json");
332
- const hasProjectConfig = existsSync(projectJsonPath);
380
+ const projectJsonPath = join2(projectRoot, ".decantr", "project.json");
381
+ const hasProjectConfig = existsSync2(projectJsonPath);
333
382
  const identities = options.create ? ensureTelemetryIdentities(projectRoot) : null;
334
383
  const projectData = readProjectJson(projectRoot);
335
384
  return {
@@ -342,12 +391,12 @@ function getCliTelemetryIdentityStatus(projectRoot, options = {}) {
342
391
  }
343
392
  function ensureTelemetryIdentities(projectRoot) {
344
393
  const installId = getOrCreateInstallId();
345
- const projectJsonPath = join(projectRoot, ".decantr", "project.json");
346
- if (!existsSync(projectJsonPath)) {
394
+ const projectJsonPath = join2(projectRoot, ".decantr", "project.json");
395
+ if (!existsSync2(projectJsonPath)) {
347
396
  return null;
348
397
  }
349
398
  try {
350
- const data = JSON.parse(readFileSync(projectJsonPath, "utf-8"));
399
+ const data = JSON.parse(readFileSync2(projectJsonPath, "utf-8"));
351
400
  let projectId = typeof data.telemetryProjectId === "string" ? data.telemetryProjectId : void 0;
352
401
  if (!projectId) {
353
402
  projectId = `project_${randomUUID()}`;
@@ -361,10 +410,10 @@ function ensureTelemetryIdentities(projectRoot) {
361
410
  }
362
411
  function getOrCreateInstallId() {
363
412
  const configDir = getConfigDir();
364
- const configPath = join(configDir, "config.json");
413
+ const configPath = join2(configDir, "config.json");
365
414
  try {
366
- if (existsSync(configPath)) {
367
- const data = JSON.parse(readFileSync(configPath, "utf-8"));
415
+ if (existsSync2(configPath)) {
416
+ const data = JSON.parse(readFileSync2(configPath, "utf-8"));
368
417
  if (typeof data.telemetryInstallId === "string") {
369
418
  return data.telemetryInstallId;
370
419
  }
@@ -386,17 +435,17 @@ function getOrCreateInstallId() {
386
435
  }
387
436
  }
388
437
  function readExistingInstallId() {
389
- const configPath = join(getConfigDir(), "config.json");
390
- if (!existsSync(configPath)) return void 0;
438
+ const configPath = join2(getConfigDir(), "config.json");
439
+ if (!existsSync2(configPath)) return void 0;
391
440
  try {
392
- const data = JSON.parse(readFileSync(configPath, "utf-8"));
441
+ const data = JSON.parse(readFileSync2(configPath, "utf-8"));
393
442
  return readStringProperty(data, "telemetryInstallId");
394
443
  } catch {
395
444
  return void 0;
396
445
  }
397
446
  }
398
447
  function getConfigDir() {
399
- return process.env.DECANTR_CONFIG_DIR || join(homedir(), ".config", "decantr");
448
+ return process.env.DECANTR_CONFIG_DIR || join2(homedir(), ".config", "decantr");
400
449
  }
401
450
  function getTelemetryEventsEndpoint() {
402
451
  return process.env.DECANTR_TELEMETRY_ENDPOINT || DEFAULT_TELEMETRY_EVENTS_ENDPOINT;
@@ -422,13 +471,13 @@ function resolveCliTelemetryProjectRoot(projectRoot, args) {
422
471
  const projectFlag = inferFlagValue(args, "--project");
423
472
  if (!projectFlag) return projectRoot;
424
473
  const candidate = resolve(projectRoot, projectFlag);
425
- return existsSync(join(candidate, ".decantr", "project.json")) ? candidate : projectRoot;
474
+ return existsSync2(join2(candidate, ".decantr", "project.json")) ? candidate : projectRoot;
426
475
  }
427
476
  function readProjectJson(projectRoot) {
428
- const projectJsonPath = join(projectRoot, ".decantr", "project.json");
429
- if (!existsSync(projectJsonPath)) return null;
477
+ const projectJsonPath = join2(projectRoot, ".decantr", "project.json");
478
+ if (!existsSync2(projectJsonPath)) return null;
430
479
  try {
431
- return JSON.parse(readFileSync(projectJsonPath, "utf-8"));
480
+ return JSON.parse(readFileSync2(projectJsonPath, "utf-8"));
432
481
  } catch {
433
482
  return null;
434
483
  }
@@ -519,15 +568,15 @@ function isRegistrySource(value) {
519
568
  return value === "cache" || value === "custom" || value === "none" || value === "official" || value === "private";
520
569
  }
521
570
  function inferProjectScope(projectRoot) {
522
- return existsSync(join(projectRoot, "pnpm-workspace.yaml")) || existsSync(join(projectRoot, "turbo.json")) || existsSync(join(projectRoot, "lerna.json")) ? "workspace-app" : "single-app";
571
+ return existsSync2(join2(projectRoot, "pnpm-workspace.yaml")) || existsSync2(join2(projectRoot, "turbo.json")) || existsSync2(join2(projectRoot, "lerna.json")) ? "workspace-app" : "single-app";
523
572
  }
524
573
  function getCliVersion() {
525
574
  try {
526
- const here = dirname(fileURLToPath(import.meta.url));
527
- const candidates = [join(here, "..", "package.json"), join(here, "..", "..", "package.json")];
575
+ const here = dirname2(fileURLToPath2(import.meta.url));
576
+ const candidates = [join2(here, "..", "package.json"), join2(here, "..", "..", "package.json")];
528
577
  for (const candidate of candidates) {
529
- if (existsSync(candidate)) {
530
- const pkg = JSON.parse(readFileSync(candidate, "utf-8"));
578
+ if (existsSync2(candidate)) {
579
+ const pkg = JSON.parse(readFileSync2(candidate, "utf-8"));
531
580
  if (pkg.version) {
532
581
  return pkg.version;
533
582
  }
@@ -542,12 +591,12 @@ function isLoopbackHost(host) {
542
591
  }
543
592
 
544
593
  // src/guard-context.ts
545
- import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2 } from "fs";
546
- import { join as join2 } from "path";
594
+ import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
595
+ import { join as join3 } from "path";
547
596
  function loadJsonEntries(dir) {
548
- if (!existsSync2(dir)) return [];
597
+ if (!existsSync3(dir)) return [];
549
598
  try {
550
- return readdirSync(dir).filter((file) => file.endsWith(".json") && file !== "index.json").map((file) => JSON.parse(readFileSync2(join2(dir, file), "utf-8")));
599
+ return readdirSync2(dir).filter((file) => file.endsWith(".json") && file !== "index.json").map((file) => JSON.parse(readFileSync3(join3(dir, file), "utf-8")));
551
600
  } catch {
552
601
  return [];
553
602
  }
@@ -555,28 +604,35 @@ function loadJsonEntries(dir) {
555
604
  function buildGuardRegistryContext(projectRoot = process.cwd()) {
556
605
  const themeRegistry = /* @__PURE__ */ new Map();
557
606
  const patternRegistry = /* @__PURE__ */ new Map();
558
- const cacheDir = join2(projectRoot, ".decantr", "cache");
559
- const customDir = join2(projectRoot, ".decantr", "custom");
560
- for (const data of loadJsonEntries(join2(cacheDir, "@official", "themes"))) {
607
+ const cacheDir = join3(projectRoot, ".decantr", "cache");
608
+ const customDir = join3(projectRoot, ".decantr", "custom");
609
+ for (const data of loadJsonEntries(join3(cacheDir, "@official", "themes"))) {
561
610
  if (typeof data.id === "string" && !themeRegistry.has(data.id)) {
562
611
  themeRegistry.set(data.id, {
563
612
  modes: Array.isArray(data.modes) ? data.modes.filter((mode) => typeof mode === "string") : ["light", "dark"]
564
613
  });
565
614
  }
566
615
  }
567
- for (const data of loadJsonEntries(join2(customDir, "themes"))) {
616
+ for (const data of loadJsonEntries(join3(customDir, "themes"))) {
568
617
  if (typeof data.id === "string") {
569
618
  themeRegistry.set(`custom:${data.id}`, {
570
619
  modes: Array.isArray(data.modes) ? data.modes.filter((mode) => typeof mode === "string") : ["light", "dark"]
571
620
  });
572
621
  }
573
622
  }
574
- for (const data of loadJsonEntries(join2(cacheDir, "@official", "patterns"))) {
623
+ for (const data of loadJsonEntries(join3(cacheDir, "@official", "patterns"))) {
575
624
  if (typeof data.id === "string" && !patternRegistry.has(data.id)) {
576
625
  patternRegistry.set(data.id, data);
577
626
  }
578
627
  }
579
- for (const data of loadJsonEntries(join2(customDir, "patterns"))) {
628
+ for (const entry of loadBundledContentList("patterns")) {
629
+ const data = entry.data;
630
+ const id = typeof data.id === "string" ? data.id : entry.id;
631
+ if (!patternRegistry.has(id)) {
632
+ patternRegistry.set(id, data);
633
+ }
634
+ }
635
+ for (const data of loadJsonEntries(join3(customDir, "patterns"))) {
580
636
  if (typeof data.id === "string") {
581
637
  patternRegistry.set(data.id, data);
582
638
  }
@@ -585,8 +641,8 @@ function buildGuardRegistryContext(projectRoot = process.cwd()) {
585
641
  }
586
642
 
587
643
  // src/lib/scan-interactions.ts
588
- import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync3, statSync } from "fs";
589
- import { extname, join as join3 } from "path";
644
+ import { existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync4, statSync } from "fs";
645
+ import { extname, join as join4, relative } from "path";
590
646
  import { verifyInteractionsInSource } from "@decantr/verifier";
591
647
  var SCAN_EXTENSIONS = /* @__PURE__ */ new Set([".tsx", ".jsx", ".ts", ".js", ".html", ".mdx"]);
592
648
  var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
@@ -606,13 +662,13 @@ function walkSourceTree(rootDir) {
606
662
  function walk2(dir) {
607
663
  let entries;
608
664
  try {
609
- entries = readdirSync2(dir);
665
+ entries = readdirSync3(dir);
610
666
  } catch {
611
667
  return;
612
668
  }
613
669
  for (const entry of entries) {
614
670
  if (SKIP_DIRECTORIES.has(entry)) continue;
615
- const fullPath = join3(dir, entry);
671
+ const fullPath = join4(dir, entry);
616
672
  let s;
617
673
  try {
618
674
  s = statSync(fullPath);
@@ -624,7 +680,7 @@ function walkSourceTree(rootDir) {
624
680
  } else if (s.isFile() && SCAN_EXTENSIONS.has(extname(entry))) {
625
681
  if (s.size > MAX_FILE_SIZE) continue;
626
682
  try {
627
- sources.set(fullPath, readFileSync3(fullPath, "utf8"));
683
+ sources.set(relative(rootDir, fullPath) || entry, readFileSync4(fullPath, "utf8"));
628
684
  } catch {
629
685
  }
630
686
  }
@@ -634,23 +690,23 @@ function walkSourceTree(rootDir) {
634
690
  return sources;
635
691
  }
636
692
  function collectDeclaredInteractions(projectRoot) {
637
- const manifestPath = join3(projectRoot, ".decantr", "context", "pack-manifest.json");
638
- if (!existsSync3(manifestPath)) return [];
693
+ const manifestPath = join4(projectRoot, ".decantr", "context", "pack-manifest.json");
694
+ if (!existsSync4(manifestPath)) return [];
639
695
  let manifest;
640
696
  try {
641
- manifest = JSON.parse(readFileSync3(manifestPath, "utf8"));
697
+ manifest = JSON.parse(readFileSync4(manifestPath, "utf8"));
642
698
  } catch {
643
699
  return [];
644
700
  }
645
701
  const all = [];
646
702
  const pages = manifest.pages ?? [];
647
- const contextDir = join3(projectRoot, ".decantr", "context");
703
+ const contextDir = join4(projectRoot, ".decantr", "context");
648
704
  for (const page of pages) {
649
- const packPath = join3(contextDir, page.json);
650
- if (!existsSync3(packPath)) continue;
705
+ const packPath = join4(contextDir, page.json);
706
+ if (!existsSync4(packPath)) continue;
651
707
  let pack;
652
708
  try {
653
- pack = JSON.parse(readFileSync3(packPath, "utf8"));
709
+ pack = JSON.parse(readFileSync4(packPath, "utf8"));
654
710
  } catch {
655
711
  continue;
656
712
  }
@@ -669,82 +725,410 @@ function scanProjectInteractions(projectRoot) {
669
725
  const sources = walkSourceTree(projectRoot);
670
726
  if (sources.size === 0) return [];
671
727
  const missing = verifyInteractionsInSource(declared, sources);
672
- return missing.map(({ interaction, suggestion }) => `${interaction} \u2192 ${suggestion}`);
728
+ return missing.map(
729
+ ({ interaction, suggestion, scannedFiles, scannedLocations, expectedSignals }) => {
730
+ const evidence = [
731
+ scannedFiles ? `scanned ${scannedFiles} source file(s)` : null,
732
+ scannedLocations?.length ? `checked: ${scannedLocations.slice(0, 4).map((location) => `${location.file}:${location.startLine}-${location.endLine}`).join(", ")}` : null,
733
+ expectedSignals?.length ? `expected signals: ${expectedSignals.slice(0, 4).join(", ")}` : null
734
+ ].filter((entry) => Boolean(entry)).join("; ");
735
+ return `${interaction} \u2192 ${suggestion}${evidence ? ` (${evidence})` : ""}`;
736
+ }
737
+ );
673
738
  }
674
739
 
675
- // src/analyzers/routes.ts
676
- import { existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
677
- import { join as join4, relative } from "path";
678
- var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".next", ".git", "api", "_app", "_document"]);
740
+ // src/ambient-context.ts
741
+ import { existsSync as existsSync5, readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
742
+ import { basename, extname as extname2, join as join5, relative as relative2, sep } from "path";
743
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
744
+ ".decantr",
745
+ ".git",
746
+ ".next",
747
+ ".nuxt",
748
+ ".svelte-kit",
749
+ ".turbo",
750
+ ".vercel",
751
+ "build",
752
+ "coverage",
753
+ "dist",
754
+ "node_modules",
755
+ "playwright-report"
756
+ ]);
757
+ var ROOT_CONTEXT_FILES = /* @__PURE__ */ new Set([
758
+ "AGENTS.md",
759
+ "CLAUDE.md",
760
+ "GEMINI.md",
761
+ "README.md",
762
+ "copilot-instructions.md",
763
+ ".cursorrules",
764
+ ".windsurfrules",
765
+ ".cursorignore",
766
+ ".claudeignore",
767
+ "components.json",
768
+ "tailwind.config.js",
769
+ "tailwind.config.ts",
770
+ "tailwind.config.mjs",
771
+ "tailwind.config.cjs",
772
+ "next.config.js",
773
+ "next.config.ts",
774
+ "next.config.mjs",
775
+ "nuxt.config.js",
776
+ "nuxt.config.ts",
777
+ "astro.config.mjs",
778
+ "astro.config.ts",
779
+ "svelte.config.js",
780
+ "svelte.config.ts",
781
+ "angular.json",
782
+ "vite.config.js",
783
+ "vite.config.ts",
784
+ "vitest.config.ts",
785
+ "vitest.config.js",
786
+ "playwright.config.ts",
787
+ "playwright.config.js",
788
+ "tsconfig.json",
789
+ "package.json",
790
+ "decantr.essence.json"
791
+ ]);
792
+ var CONTEXT_DIRECTORIES = /* @__PURE__ */ new Set([
793
+ ".agents",
794
+ ".claude",
795
+ ".claude/initiatives",
796
+ ".claude/rules",
797
+ ".codex",
798
+ ".cursor",
799
+ ".cursor/rules",
800
+ ".github/workflows",
801
+ "docs",
802
+ "docs/initiatives",
803
+ "initiatives",
804
+ "memory",
805
+ "memories",
806
+ "project-memory",
807
+ "supabase"
808
+ ]);
679
809
  function shouldSkipDir(name) {
680
- return name.startsWith("_") || name.startsWith(".") || SKIP_DIRS.has(name);
810
+ return SKIP_DIRS.has(name);
681
811
  }
682
- function segmentToRoute(segment) {
683
- if (segment.startsWith("(") && segment.endsWith(")")) {
684
- return null;
812
+ function normalizedPath(relPath) {
813
+ return relPath.split(sep).join("/");
814
+ }
815
+ function isPotentialContextFile(relPath, name) {
816
+ const normalized2 = normalizedPath(relPath);
817
+ if (ROOT_CONTEXT_FILES.has(name)) return true;
818
+ if (name.startsWith(".env")) return true;
819
+ if (normalized2.startsWith(".claude/")) return true;
820
+ if (normalized2.startsWith(".agents/")) return true;
821
+ if (normalized2.startsWith(".codex/")) return true;
822
+ if (normalized2.startsWith(".cursor/")) return true;
823
+ if (normalized2.startsWith(".github/workflows/")) return true;
824
+ if (normalized2.startsWith("docs/")) return true;
825
+ if (normalized2.startsWith("initiatives/")) return true;
826
+ if (normalized2.startsWith("memory/")) return true;
827
+ if (normalized2.startsWith("memories/")) return true;
828
+ if (normalized2.startsWith("project-memory/")) return true;
829
+ if (normalized2.startsWith("supabase/")) return true;
830
+ if (normalized2.startsWith("migrations/")) return true;
831
+ if (normalized2.startsWith("db/")) return true;
832
+ if (normalized2.startsWith("ROLEMIGRATIONS/")) return true;
833
+ if (normalized2 === "src/middleware.ts" || normalized2 === "middleware.ts") return true;
834
+ if (normalized2.includes("/middleware.")) return true;
835
+ const ext = extname2(name).toLowerCase();
836
+ return ext === ".md" || ext === ".mdx" || ext === ".sql" || ext === ".yml" || ext === ".yaml";
837
+ }
838
+ function classifyContext(relPath) {
839
+ const normalized2 = normalizedPath(relPath);
840
+ const lower = normalized2.toLowerCase();
841
+ const name = basename(normalized2);
842
+ if (lower === "decantr.essence.json") {
843
+ return {
844
+ role: "architecture",
845
+ confidence: 0.82,
846
+ reason: "existing Decantr contract evidence"
847
+ };
685
848
  }
686
- if (segment.startsWith("[") && segment.endsWith("]")) {
687
- const param = segment.slice(1, -1);
688
- if (param.startsWith("...")) {
689
- return `:${param.slice(3)}*`;
690
- }
691
- if (param.startsWith("[...") && param.endsWith("]")) {
692
- return `:${param.slice(4, -1)}*`;
693
- }
694
- return `:${param}`;
849
+ if (lower === ".claude/initiatives" || lower === "docs/initiatives" || lower === "initiatives" || lower === "memory" || lower === "memories" || lower === "project-memory" || lower.startsWith(".claude/initiatives/") || lower.startsWith("docs/initiatives/") || lower.startsWith("initiatives/") || lower.startsWith("memory/") || lower.startsWith("memories/") || lower.startsWith("project-memory/") || lower.includes("/feature/") || lower.includes("feature") || lower.includes("rbac") || lower.includes("billing") || lower.includes("admin") || lower.includes("dashboard")) {
850
+ return {
851
+ role: "feature-business",
852
+ confidence: 0.78,
853
+ reason: "feature, initiative, memory, or business-domain evidence"
854
+ };
695
855
  }
696
- return segment;
856
+ if (lower === ".agents" || lower === ".claude" || lower === ".codex" || lower === ".cursor" || lower === "claude.md" || lower === "agents.md" || lower === "gemini.md" || lower === "copilot-instructions.md" || lower === ".cursorrules" || lower === ".windsurfrules" || lower.startsWith(".claude/") || lower.startsWith(".agents/") || lower.startsWith(".codex/") || lower.startsWith(".cursor/rules/")) {
857
+ return {
858
+ role: "assistant-specific",
859
+ confidence: 0.98,
860
+ reason: "assistant or AI-agent instruction surface"
861
+ };
862
+ }
863
+ if (lower.includes("security") || lower.includes("auth") || lower.includes("rls") || lower.includes("schema") || lower.includes("migration") || lower.startsWith("supabase/") || lower.startsWith("migrations/") || lower.startsWith("db/") || lower.startsWith("rolemigrations/") || lower.includes("middleware.")) {
864
+ return {
865
+ role: "security-data",
866
+ confidence: 0.9,
867
+ reason: "security, auth, schema, middleware, or data-governance evidence"
868
+ };
869
+ }
870
+ if (lower.includes("design-system") || lower === "components.json" || lower.startsWith("tailwind.config") || lower.includes("ui-components") || lower.includes("colors") || lower.includes("typography") || lower.includes("spacing")) {
871
+ return {
872
+ role: "design-system",
873
+ confidence: 0.88,
874
+ reason: "design system or styling convention evidence"
875
+ };
876
+ }
877
+ if (lower.startsWith(".github/workflows/") || lower.includes("workflow") || lower.includes("testing") || lower.includes("deployment") || lower.includes("vitest.config") || lower.includes("playwright.config") || lower === "package.json") {
878
+ return {
879
+ role: "workflow-ci",
880
+ confidence: 0.84,
881
+ reason: "workflow, CI, deployment, or validation command evidence"
882
+ };
883
+ }
884
+ if (lower === "docs" || lower.includes("architecture") || lower === "readme.md" || lower.includes("setup") || lower.includes("contributing") || name === "tsconfig.json" || lower.endsWith("config.ts") || lower.endsWith("config.js") || lower.endsWith("config.mjs")) {
885
+ return {
886
+ role: "architecture",
887
+ confidence: 0.72,
888
+ reason: "architecture, setup, or framework configuration evidence"
889
+ };
890
+ }
891
+ if (lower.includes("complete") || lower.includes("summary") || lower.includes("deprecated") || lower.includes("legacy") || lower.includes("migration")) {
892
+ return {
893
+ role: "stale-or-historical",
894
+ confidence: 0.64,
895
+ reason: "historical or possibly stale project documentation"
896
+ };
897
+ }
898
+ return { role: "unknown", confidence: 0.35, reason: "unclassified context candidate" };
697
899
  }
698
- function walkAppDir(dir, baseDir, segments) {
699
- const routes = [];
900
+ function isSafeToCite(relPath) {
901
+ const lower = normalizedPath(relPath).toLowerCase();
902
+ if (lower.startsWith(".env") && lower !== ".env.example" && lower !== ".env.sample") return false;
903
+ if (lower.includes("secret") || lower.includes("private-key") || lower.includes("credentials")) {
904
+ return false;
905
+ }
906
+ return true;
907
+ }
908
+ function addDirectoryContext(items, projectRoot, relPath) {
909
+ const fullPath = join5(projectRoot, relPath);
910
+ if (!existsSync5(fullPath)) return;
911
+ const stats = statSync2(fullPath);
912
+ const classified = classifyContext(relPath);
913
+ items.push({
914
+ path: normalizedPath(relPath),
915
+ type: "directory",
916
+ role: classified.role,
917
+ confidence: classified.confidence,
918
+ sizeBytes: stats.size,
919
+ safeToCite: isSafeToCite(relPath),
920
+ reason: classified.reason
921
+ });
922
+ }
923
+ function walk(projectRoot, dir, items, depth) {
924
+ if (depth > 6) return;
700
925
  let entries;
701
926
  try {
702
- entries = readdirSync3(dir);
927
+ entries = readdirSync4(dir);
703
928
  } catch {
704
- return routes;
705
- }
706
- const hasPage = entries.some(
707
- (e) => e === "page.tsx" || e === "page.ts" || e === "page.jsx" || e === "page.js"
708
- );
709
- const hasLayout = entries.some(
710
- (e) => e === "layout.tsx" || e === "layout.ts" || e === "layout.jsx" || e === "layout.js"
711
- );
712
- if (hasPage) {
713
- const routePath = "/" + segments.filter((s) => s !== "").join("/");
714
- const pageFile = entries.find((e) => e.startsWith("page."));
715
- routes.push({
716
- path: routePath || "/",
717
- file: relative(baseDir, join4(dir, pageFile)),
718
- hasLayout
719
- });
929
+ return;
720
930
  }
721
931
  for (const entry of entries) {
722
932
  if (shouldSkipDir(entry)) continue;
723
- const fullPath = join4(dir, entry);
933
+ const fullPath = join5(dir, entry);
934
+ const relPath = normalizedPath(relative2(projectRoot, fullPath));
935
+ let stats;
724
936
  try {
725
- if (!statSync2(fullPath).isDirectory()) continue;
937
+ stats = statSync2(fullPath);
726
938
  } catch {
727
939
  continue;
728
940
  }
729
- const routeSegment = segmentToRoute(entry);
730
- const nextSegments = routeSegment === null ? [...segments] : [...segments, routeSegment];
731
- routes.push(...walkAppDir(fullPath, baseDir, nextSegments));
941
+ if (stats.isDirectory()) {
942
+ if (CONTEXT_DIRECTORIES.has(relPath)) {
943
+ addDirectoryContext(items, projectRoot, relPath);
944
+ }
945
+ walk(projectRoot, fullPath, items, depth + 1);
946
+ continue;
947
+ }
948
+ if (!stats.isFile() || !isPotentialContextFile(relPath, entry)) continue;
949
+ const classified = classifyContext(relPath);
950
+ items.push({
951
+ path: relPath,
952
+ type: "file",
953
+ role: classified.role,
954
+ confidence: classified.confidence,
955
+ sizeBytes: stats.size,
956
+ safeToCite: isSafeToCite(relPath),
957
+ reason: classified.reason
958
+ });
732
959
  }
733
- return routes;
734
960
  }
735
- function walkPagesDir(dir, baseDir, segments, extensions = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx", "md", "mdx"])) {
736
- const routes = [];
737
- let entries;
738
- try {
739
- entries = readdirSync3(dir);
961
+ function summarize(items) {
962
+ const summary = {
963
+ "assistant-specific": 0,
964
+ "security-data": 0,
965
+ architecture: 0,
966
+ "design-system": 0,
967
+ "workflow-ci": 0,
968
+ "feature-business": 0,
969
+ "stale-or-historical": 0,
970
+ unknown: 0
971
+ };
972
+ for (const item of items) summary[item.role] += 1;
973
+ return summary;
974
+ }
975
+ function readSmallText(projectRoot, relPath) {
976
+ const fullPath = join5(projectRoot, relPath);
977
+ try {
978
+ const stat = statSync2(fullPath);
979
+ if (stat.size > 64e3) return "";
980
+ return readFileSync5(fullPath, "utf-8");
981
+ } catch {
982
+ return "";
983
+ }
984
+ }
985
+ function detectConflicts(projectRoot, items) {
986
+ const text = items.filter(
987
+ (item) => item.type === "file" && item.safeToCite && item.path.match(/\.(md|mdx|json|ts|js|yml|yaml)$/)
988
+ ).slice(0, 80).map((item) => readSmallText(projectRoot, item.path)).join("\n").toLowerCase();
989
+ const conflicts = [];
990
+ const frameworkSignals = [
991
+ ["next", /\bnext\.?js\b|\bapp router\b|\bpages router\b/],
992
+ ["angular", /\bangular\b/],
993
+ ["svelte", /\bsvelte\b|\bsveltekit\b/],
994
+ ["vue", /\bvue\b|\bnuxt\b/]
995
+ ].filter(([, pattern]) => pattern.test(text));
996
+ if (frameworkSignals.length > 1) {
997
+ conflicts.push(
998
+ `Multiple framework doctrines appear in ambient docs: ${frameworkSignals.map(([name]) => name).join(", ")}.`
999
+ );
1000
+ }
1001
+ const forbidsTailwind = /\b(do not|don't|avoid|forbid|forbidden)\s+use\s+tailwind\b|\bno\s+tailwind\b/.test(text);
1002
+ const endorsesTailwind = /\btailwind\.config\b|\btailwindcss\b|\b@tailwind\b|\btailwind\s+classes\b/.test(text);
1003
+ if (forbidsTailwind && endorsesTailwind) {
1004
+ conflicts.push("Ambient docs contain both Tailwind usage and anti-Tailwind language.");
1005
+ }
1006
+ if (/\bclient component\b/.test(text) && /\bserver components? only\b/.test(text)) {
1007
+ conflicts.push("Ambient docs may conflict on client vs server component boundaries.");
1008
+ }
1009
+ return conflicts;
1010
+ }
1011
+ function detectDecantrEssenceStaleRisk(projectRoot, items) {
1012
+ if (!items.some((item) => item.path === "decantr.essence.json")) return [];
1013
+ const content = readSmallText(projectRoot, "decantr.essence.json");
1014
+ if (!content) return [];
1015
+ try {
1016
+ const essence = JSON.parse(content);
1017
+ const risks = [];
1018
+ if (essence.version !== "4.0.0") {
1019
+ risks.push(
1020
+ `decantr.essence.json uses Decantr essence version ${essence.version ?? "unknown"}; run decantr migrate --to v4 or review before treating it as current brownfield doctrine.`
1021
+ );
1022
+ }
1023
+ if (essence.dna?.theme?.id === "luminarum" && essence.structure) {
1024
+ risks.push(
1025
+ "decantr.essence.json looks like an older Decantr default scaffold; verify before importing its theme or page layout as brownfield truth."
1026
+ );
1027
+ }
1028
+ return risks;
1029
+ } catch {
1030
+ return [
1031
+ "decantr.essence.json could not be parsed during ambient inventory; review before treating it as current doctrine."
1032
+ ];
1033
+ }
1034
+ }
1035
+ function detectStaleRisks(projectRoot, items) {
1036
+ const pathRisks = items.filter(
1037
+ (item) => item.role === "stale-or-historical" || /complete|summary|legacy|deprecated/i.test(item.path)
1038
+ ).slice(0, 12).map(
1039
+ (item) => `${item.path} may be historical; verify before treating it as current doctrine.`
1040
+ );
1041
+ return [...pathRisks, ...detectDecantrEssenceStaleRisk(projectRoot, items)];
1042
+ }
1043
+ function scanAmbientContext(projectRoot) {
1044
+ const items = [];
1045
+ walk(projectRoot, projectRoot, items, 0);
1046
+ const deduped = [...new Map(items.map((item) => [item.path, item])).values()].sort(
1047
+ (a, b) => a.path.localeCompare(b.path)
1048
+ );
1049
+ return {
1050
+ version: 1,
1051
+ scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
1052
+ items: deduped,
1053
+ summary: summarize(deduped),
1054
+ conflicts: detectConflicts(projectRoot, deduped),
1055
+ staleRisks: detectStaleRisks(projectRoot, deduped)
1056
+ };
1057
+ }
1058
+
1059
+ // src/analyzers/routes.ts
1060
+ import { existsSync as existsSync6, readdirSync as readdirSync5, readFileSync as readFileSync6, statSync as statSync3 } from "fs";
1061
+ import { join as join6, relative as relative3 } from "path";
1062
+ var SKIP_DIRS2 = /* @__PURE__ */ new Set(["node_modules", ".next", ".git", "api", "_app", "_document"]);
1063
+ function shouldSkipDir2(name) {
1064
+ return name.startsWith("_") || name.startsWith(".") || SKIP_DIRS2.has(name);
1065
+ }
1066
+ function segmentToRoute(segment) {
1067
+ if (segment.startsWith("(") && segment.endsWith(")")) {
1068
+ return null;
1069
+ }
1070
+ if (segment.startsWith("[") && segment.endsWith("]")) {
1071
+ const param = segment.slice(1, -1);
1072
+ if (param.startsWith("...")) {
1073
+ return `:${param.slice(3)}*`;
1074
+ }
1075
+ if (param.startsWith("[...") && param.endsWith("]")) {
1076
+ return `:${param.slice(4, -1)}*`;
1077
+ }
1078
+ return `:${param}`;
1079
+ }
1080
+ return segment;
1081
+ }
1082
+ function walkAppDir(dir, baseDir, segments) {
1083
+ const routes = [];
1084
+ let entries;
1085
+ try {
1086
+ entries = readdirSync5(dir);
740
1087
  } catch {
741
1088
  return routes;
742
1089
  }
1090
+ const hasPage = entries.some(
1091
+ (e) => e === "page.tsx" || e === "page.ts" || e === "page.jsx" || e === "page.js"
1092
+ );
1093
+ const hasLayout = entries.some(
1094
+ (e) => e === "layout.tsx" || e === "layout.ts" || e === "layout.jsx" || e === "layout.js"
1095
+ );
1096
+ if (hasPage) {
1097
+ const routePath = "/" + segments.filter((s) => s !== "").join("/");
1098
+ const pageFile = entries.find((e) => e.startsWith("page."));
1099
+ routes.push({
1100
+ path: routePath || "/",
1101
+ file: relative3(baseDir, join6(dir, pageFile)),
1102
+ hasLayout
1103
+ });
1104
+ }
743
1105
  for (const entry of entries) {
744
- if (shouldSkipDir(entry)) continue;
745
- const fullPath = join4(dir, entry);
1106
+ if (shouldSkipDir2(entry)) continue;
1107
+ const fullPath = join6(dir, entry);
746
1108
  try {
747
- const stat = statSync2(fullPath);
1109
+ if (!statSync3(fullPath).isDirectory()) continue;
1110
+ } catch {
1111
+ continue;
1112
+ }
1113
+ const routeSegment = segmentToRoute(entry);
1114
+ const nextSegments = routeSegment === null ? [...segments] : [...segments, routeSegment];
1115
+ routes.push(...walkAppDir(fullPath, baseDir, nextSegments));
1116
+ }
1117
+ return routes;
1118
+ }
1119
+ function walkPagesDir(dir, baseDir, segments, extensions = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx", "md", "mdx"])) {
1120
+ const routes = [];
1121
+ let entries;
1122
+ try {
1123
+ entries = readdirSync5(dir);
1124
+ } catch {
1125
+ return routes;
1126
+ }
1127
+ for (const entry of entries) {
1128
+ if (shouldSkipDir2(entry)) continue;
1129
+ const fullPath = join6(dir, entry);
1130
+ try {
1131
+ const stat = statSync3(fullPath);
748
1132
  if (stat.isDirectory()) {
749
1133
  const routeSegment = segmentToRoute(entry);
750
1134
  const nextSegments = routeSegment === null ? [...segments] : [...segments, routeSegment];
@@ -760,7 +1144,7 @@ function walkPagesDir(dir, baseDir, segments, extensions = /* @__PURE__ */ new S
760
1144
  const routePath = "/" + [...segments, routeSegment].filter((s) => s !== "").join("/");
761
1145
  routes.push({
762
1146
  path: routePath || "/",
763
- file: relative(baseDir, fullPath),
1147
+ file: relative3(baseDir, fullPath),
764
1148
  hasLayout: false
765
1149
  });
766
1150
  }
@@ -773,7 +1157,7 @@ function walkSvelteKitRoutes(dir, baseDir, segments) {
773
1157
  const routes = [];
774
1158
  let entries;
775
1159
  try {
776
- entries = readdirSync3(dir);
1160
+ entries = readdirSync5(dir);
777
1161
  } catch {
778
1162
  return routes;
779
1163
  }
@@ -783,15 +1167,15 @@ function walkSvelteKitRoutes(dir, baseDir, segments) {
783
1167
  const routePath = "/" + segments.filter((segment) => segment !== "").join("/");
784
1168
  routes.push({
785
1169
  path: routePath || "/",
786
- file: relative(baseDir, join4(dir, pageFile)),
1170
+ file: relative3(baseDir, join6(dir, pageFile)),
787
1171
  hasLayout
788
1172
  });
789
1173
  }
790
1174
  for (const entry of entries) {
791
- if (shouldSkipDir(entry)) continue;
792
- const fullPath = join4(dir, entry);
1175
+ if (shouldSkipDir2(entry)) continue;
1176
+ const fullPath = join6(dir, entry);
793
1177
  try {
794
- if (!statSync2(fullPath).isDirectory()) continue;
1178
+ if (!statSync3(fullPath).isDirectory()) continue;
795
1179
  } catch {
796
1180
  continue;
797
1181
  }
@@ -806,15 +1190,15 @@ function collectRouteCandidateFiles(dir, files, depth = 0) {
806
1190
  if (depth > 5) return;
807
1191
  let entries;
808
1192
  try {
809
- entries = readdirSync3(dir);
1193
+ entries = readdirSync5(dir);
810
1194
  } catch {
811
1195
  return;
812
1196
  }
813
1197
  for (const entry of entries) {
814
1198
  if (entry.startsWith(".") || entry === "node_modules") continue;
815
- const fullPath = join4(dir, entry);
1199
+ const fullPath = join6(dir, entry);
816
1200
  try {
817
- const stat = statSync2(fullPath);
1201
+ const stat = statSync3(fullPath);
818
1202
  if (stat.isDirectory()) {
819
1203
  collectRouteCandidateFiles(fullPath, files, depth + 1);
820
1204
  } else if (stat.isFile()) {
@@ -828,22 +1212,22 @@ function collectRouteCandidateFiles(dir, files, depth = 0) {
828
1212
  }
829
1213
  }
830
1214
  function scanReactRouter(projectRoot) {
831
- const candidateDirs = [join4(projectRoot, "src"), projectRoot];
1215
+ const candidateDirs = [join6(projectRoot, "src"), projectRoot];
832
1216
  const candidateFiles = [];
833
1217
  for (const dir of candidateDirs) {
834
- if (existsSync4(dir)) collectRouteCandidateFiles(dir, candidateFiles);
1218
+ if (existsSync6(dir)) collectRouteCandidateFiles(dir, candidateFiles);
835
1219
  }
836
1220
  const routeMap = /* @__PURE__ */ new Map();
837
1221
  for (const absolutePath of candidateFiles) {
838
1222
  let content;
839
1223
  try {
840
- content = readFileSync4(absolutePath, "utf-8");
1224
+ content = readFileSync6(absolutePath, "utf-8");
841
1225
  } catch {
842
1226
  continue;
843
1227
  }
844
1228
  const isReactRouterFile = content.includes("react-router-dom") || content.includes("react-router") || content.includes("<Routes") || content.includes("createBrowserRouter") || content.includes("createHashRouter") || content.includes("RouterProvider") || content.includes("HashRouter") || content.includes("BrowserRouter");
845
1229
  if (!isReactRouterFile) continue;
846
- const relativePath = relative(projectRoot, absolutePath);
1230
+ const relativePath = relative3(projectRoot, absolutePath);
847
1231
  const pathMatches = /* @__PURE__ */ new Set();
848
1232
  for (const match of content.matchAll(/<Route\b[^>]*\bpath=["'`]([^"'`]+)["'`]/g)) {
849
1233
  pathMatches.add(match[1]);
@@ -870,10 +1254,10 @@ function hasReactRouterDependency(projectRoot) {
870
1254
  return hasDependency(projectRoot, ["react-router", "react-router-dom"]);
871
1255
  }
872
1256
  function hasDependency(projectRoot, names) {
873
- const packageJsonPath = join4(projectRoot, "package.json");
874
- if (!existsSync4(packageJsonPath)) return false;
1257
+ const packageJsonPath = join6(projectRoot, "package.json");
1258
+ if (!existsSync6(packageJsonPath)) return false;
875
1259
  try {
876
- const pkg = JSON.parse(readFileSync4(packageJsonPath, "utf-8"));
1260
+ const pkg = JSON.parse(readFileSync6(packageJsonPath, "utf-8"));
877
1261
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
878
1262
  return names.some((name) => Boolean(deps[name]));
879
1263
  } catch {
@@ -881,7 +1265,7 @@ function hasDependency(projectRoot, names) {
881
1265
  }
882
1266
  }
883
1267
  function hasAnyFile(projectRoot, relPaths) {
884
- return relPaths.some((relPath) => existsSync4(join4(projectRoot, relPath)));
1268
+ return relPaths.some((relPath) => existsSync6(join6(projectRoot, relPath)));
885
1269
  }
886
1270
  function normalizeRoutePath(path) {
887
1271
  const cleaned = path.trim();
@@ -890,22 +1274,22 @@ function normalizeRoutePath(path) {
890
1274
  return cleaned.startsWith("/") ? cleaned : `/${cleaned}`;
891
1275
  }
892
1276
  function scanAngularRouter(projectRoot) {
893
- const candidateDirs = [join4(projectRoot, "src", "app"), join4(projectRoot, "src")];
1277
+ const candidateDirs = [join6(projectRoot, "src", "app"), join6(projectRoot, "src")];
894
1278
  const candidateFiles = [];
895
1279
  for (const dir of candidateDirs) {
896
- if (existsSync4(dir)) collectRouteCandidateFiles(dir, candidateFiles);
1280
+ if (existsSync6(dir)) collectRouteCandidateFiles(dir, candidateFiles);
897
1281
  }
898
1282
  const routeMap = /* @__PURE__ */ new Map();
899
1283
  for (const absolutePath of candidateFiles) {
900
1284
  let content;
901
1285
  try {
902
- content = readFileSync4(absolutePath, "utf-8");
1286
+ content = readFileSync6(absolutePath, "utf-8");
903
1287
  } catch {
904
1288
  continue;
905
1289
  }
906
1290
  const isRouterFile = content.includes("@angular/router") || content.includes("RouterModule.forRoot") || content.includes("provideRouter") || content.includes("Routes =");
907
1291
  if (!isRouterFile) continue;
908
- const relativePath = relative(projectRoot, absolutePath);
1292
+ const relativePath = relative3(projectRoot, absolutePath);
909
1293
  for (const match of content.matchAll(/\bpath\s*:\s*["'`]([^"'`]*)["'`]/g)) {
910
1294
  const routePath = normalizeRoutePath(match[1]);
911
1295
  if (!routePath || routeMap.has(routePath)) continue;
@@ -919,22 +1303,22 @@ function scanAngularRouter(projectRoot) {
919
1303
  return [...routeMap.values()];
920
1304
  }
921
1305
  function scanVueRouter(projectRoot) {
922
- const candidateDirs = [join4(projectRoot, "src"), projectRoot];
1306
+ const candidateDirs = [join6(projectRoot, "src"), projectRoot];
923
1307
  const candidateFiles = [];
924
1308
  for (const dir of candidateDirs) {
925
- if (existsSync4(dir)) collectRouteCandidateFiles(dir, candidateFiles);
1309
+ if (existsSync6(dir)) collectRouteCandidateFiles(dir, candidateFiles);
926
1310
  }
927
1311
  const routeMap = /* @__PURE__ */ new Map();
928
1312
  for (const absolutePath of candidateFiles) {
929
1313
  let content;
930
1314
  try {
931
- content = readFileSync4(absolutePath, "utf-8");
1315
+ content = readFileSync6(absolutePath, "utf-8");
932
1316
  } catch {
933
1317
  continue;
934
1318
  }
935
1319
  const isRouterFile = content.includes("vue-router") || content.includes("createRouter") || content.includes("createWebHistory") || content.includes("createWebHashHistory");
936
1320
  if (!isRouterFile) continue;
937
- const relativePath = relative(projectRoot, absolutePath);
1321
+ const relativePath = relative3(projectRoot, absolutePath);
938
1322
  for (const match of content.matchAll(/\bpath\s*:\s*["'`]([^"'`]+)["'`]/g)) {
939
1323
  const routePath = normalizeRoutePath(match[1]);
940
1324
  if (!routePath || routeMap.has(routePath)) continue;
@@ -953,13 +1337,13 @@ function scanRoutes(projectRoot) {
953
1337
  const hasNuxt = hasDependency(projectRoot, ["nuxt"]) || hasAnyFile(projectRoot, ["nuxt.config.js", "nuxt.config.ts"]);
954
1338
  const hasAngular = hasDependency(projectRoot, ["@angular/core", "@angular/router"]) || hasAnyFile(projectRoot, ["angular.json"]);
955
1339
  const hasVue = hasDependency(projectRoot, ["vue", "vue-router"]) || hasAnyFile(projectRoot, ["vite.config.js", "vite.config.ts"]);
956
- const appDirs = [join4(projectRoot, "src", "app"), join4(projectRoot, "app")];
1340
+ const appDirs = [join6(projectRoot, "src", "app"), join6(projectRoot, "app")];
957
1341
  const appRoutes = appDirs.flatMap(
958
- (appDir) => existsSync4(appDir) ? walkAppDir(appDir, projectRoot, []) : []
1342
+ (appDir) => existsSync6(appDir) ? walkAppDir(appDir, projectRoot, []) : []
959
1343
  );
960
- const pagesDirs = [join4(projectRoot, "src", "pages"), join4(projectRoot, "pages")];
1344
+ const pagesDirs = [join6(projectRoot, "src", "pages"), join6(projectRoot, "pages")];
961
1345
  const pagesRoutes = pagesDirs.flatMap(
962
- (pagesDir) => existsSync4(pagesDir) ? walkPagesDir(pagesDir, projectRoot, []) : []
1346
+ (pagesDir) => existsSync6(pagesDir) ? walkPagesDir(pagesDir, projectRoot, []) : []
963
1347
  );
964
1348
  if (hasNext) {
965
1349
  if (appRoutes.length > 0 && pagesRoutes.length > 0) {
@@ -971,16 +1355,16 @@ function scanRoutes(projectRoot) {
971
1355
  return { strategy: "app-router", routes: appRoutes };
972
1356
  }
973
1357
  if (hasSvelteKit) {
974
- const svelteRoutesDir = join4(projectRoot, "src", "routes");
975
- if (existsSync4(svelteRoutesDir)) {
1358
+ const svelteRoutesDir = join6(projectRoot, "src", "routes");
1359
+ if (existsSync6(svelteRoutesDir)) {
976
1360
  const routes = walkSvelteKitRoutes(svelteRoutesDir, projectRoot, []);
977
1361
  if (routes.length > 0) return { strategy: "sveltekit-router", routes };
978
1362
  }
979
1363
  }
980
1364
  if (hasNuxt) {
981
- const nuxtPagesDirs = [join4(projectRoot, "pages"), join4(projectRoot, "app", "pages")];
1365
+ const nuxtPagesDirs = [join6(projectRoot, "pages"), join6(projectRoot, "app", "pages")];
982
1366
  const routes = nuxtPagesDirs.flatMap(
983
- (pagesDir) => existsSync4(pagesDir) ? walkPagesDir(pagesDir, projectRoot, [], /* @__PURE__ */ new Set(["vue"])) : []
1367
+ (pagesDir) => existsSync6(pagesDir) ? walkPagesDir(pagesDir, projectRoot, [], /* @__PURE__ */ new Set(["vue"])) : []
984
1368
  );
985
1369
  if (routes.length > 0) return { strategy: "nuxt-router", routes };
986
1370
  }
@@ -1006,8 +1390,8 @@ function scanRoutes(projectRoot) {
1006
1390
  }
1007
1391
 
1008
1392
  // src/analyzers/styling.ts
1009
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
1010
- import { join as join5 } from "path";
1393
+ import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
1394
+ import { join as join7 } from "path";
1011
1395
  var TAILWIND_CONFIGS = [
1012
1396
  "tailwind.config.js",
1013
1397
  "tailwind.config.ts",
@@ -1074,10 +1458,10 @@ function detectDarkMode(projectRoot, cssContents) {
1074
1458
  "app/layout.jsx"
1075
1459
  ];
1076
1460
  for (const rel of layoutPaths) {
1077
- const fullPath = join5(projectRoot, rel);
1078
- if (existsSync5(fullPath)) {
1461
+ const fullPath = join7(projectRoot, rel);
1462
+ if (existsSync7(fullPath)) {
1079
1463
  try {
1080
- const layoutContent = readFileSync5(fullPath, "utf-8");
1464
+ const layoutContent = readFileSync7(fullPath, "utf-8");
1081
1465
  if (layoutContent.includes('className="dark"') || layoutContent.includes("className='dark'") || layoutContent.includes('class="dark"')) {
1082
1466
  return true;
1083
1467
  }
@@ -1085,10 +1469,10 @@ function detectDarkMode(projectRoot, cssContents) {
1085
1469
  }
1086
1470
  }
1087
1471
  }
1088
- const pkgPath = join5(projectRoot, "package.json");
1089
- if (existsSync5(pkgPath)) {
1472
+ const pkgPath = join7(projectRoot, "package.json");
1473
+ if (existsSync7(pkgPath)) {
1090
1474
  try {
1091
- const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
1475
+ const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
1092
1476
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
1093
1477
  if (allDeps["next-themes"] || allDeps["theme-toggle"] || allDeps["use-dark-mode"]) {
1094
1478
  return true;
@@ -1096,10 +1480,10 @@ function detectDarkMode(projectRoot, cssContents) {
1096
1480
  } catch {
1097
1481
  }
1098
1482
  }
1099
- const essencePath = join5(projectRoot, "decantr.essence.json");
1100
- if (existsSync5(essencePath)) {
1483
+ const essencePath = join7(projectRoot, "decantr.essence.json");
1484
+ if (existsSync7(essencePath)) {
1101
1485
  try {
1102
- const essence = JSON.parse(readFileSync5(essencePath, "utf-8"));
1486
+ const essence = JSON.parse(readFileSync7(essencePath, "utf-8"));
1103
1487
  const mode = essence.dna?.theme?.mode;
1104
1488
  if (mode === "dark" || mode === "auto") {
1105
1489
  return true;
@@ -1113,17 +1497,17 @@ function scanStyling(projectRoot) {
1113
1497
  let approach = "unknown";
1114
1498
  let configFile;
1115
1499
  let packageDeps = {};
1116
- const pkgPath = join5(projectRoot, "package.json");
1117
- if (existsSync5(pkgPath)) {
1500
+ const pkgPath = join7(projectRoot, "package.json");
1501
+ if (existsSync7(pkgPath)) {
1118
1502
  try {
1119
- const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
1503
+ const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
1120
1504
  packageDeps = { ...pkg.dependencies, ...pkg.devDependencies };
1121
1505
  } catch {
1122
1506
  packageDeps = {};
1123
1507
  }
1124
1508
  }
1125
1509
  for (const cfg of TAILWIND_CONFIGS) {
1126
- if (existsSync5(join5(projectRoot, cfg))) {
1510
+ if (existsSync7(join7(projectRoot, cfg))) {
1127
1511
  approach = "tailwind";
1128
1512
  configFile = cfg;
1129
1513
  break;
@@ -1151,27 +1535,27 @@ function scanStyling(projectRoot) {
1151
1535
  configFile = "package.json";
1152
1536
  }
1153
1537
  }
1154
- const decantrStyleFiles = DECANTR_STYLE_PATHS.filter((rel) => existsSync5(join5(projectRoot, rel)));
1538
+ const decantrStyleFiles = DECANTR_STYLE_PATHS.filter((rel) => existsSync7(join7(projectRoot, rel)));
1155
1539
  if (decantrStyleFiles.length >= 2) {
1156
1540
  approach = "decantr-css";
1157
1541
  configFile = decantrStyleFiles.join(" + ");
1158
1542
  }
1159
1543
  const cssContents = [];
1160
1544
  for (const rel of GLOBALS_CSS_PATHS) {
1161
- const fullPath = join5(projectRoot, rel);
1162
- if (existsSync5(fullPath)) {
1545
+ const fullPath = join7(projectRoot, rel);
1546
+ if (existsSync7(fullPath)) {
1163
1547
  try {
1164
- cssContents.push(readFileSync5(fullPath, "utf-8"));
1548
+ cssContents.push(readFileSync7(fullPath, "utf-8"));
1165
1549
  } catch {
1166
1550
  }
1167
1551
  }
1168
1552
  }
1169
1553
  for (const rel of DECANTR_STYLE_PATHS) {
1170
1554
  if (GLOBALS_CSS_PATHS.includes(rel)) continue;
1171
- const fullPath = join5(projectRoot, rel);
1172
- if (existsSync5(fullPath)) {
1555
+ const fullPath = join7(projectRoot, rel);
1556
+ if (existsSync7(fullPath)) {
1173
1557
  try {
1174
- cssContents.push(readFileSync5(fullPath, "utf-8"));
1558
+ cssContents.push(readFileSync7(fullPath, "utf-8"));
1175
1559
  } catch {
1176
1560
  }
1177
1561
  }
@@ -1187,7 +1571,7 @@ function scanStyling(projectRoot) {
1187
1571
  const darkMode = detectDarkMode(projectRoot, cssContents);
1188
1572
  if (approach === "unknown" && cssContents.length > 0) {
1189
1573
  approach = "css";
1190
- configFile = GLOBALS_CSS_PATHS.find((rel) => existsSync5(join5(projectRoot, rel)));
1574
+ configFile = GLOBALS_CSS_PATHS.find((rel) => existsSync7(join7(projectRoot, rel)));
1191
1575
  }
1192
1576
  return {
1193
1577
  approach,
@@ -1198,328 +1582,9 @@ function scanStyling(projectRoot) {
1198
1582
  };
1199
1583
  }
1200
1584
 
1201
- // src/ambient-context.ts
1202
- import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync3 } from "fs";
1203
- import { basename, extname as extname2, join as join6, relative as relative2, sep as sep2 } from "path";
1204
- var SKIP_DIRS2 = /* @__PURE__ */ new Set([
1205
- ".decantr",
1206
- ".git",
1207
- ".next",
1208
- ".nuxt",
1209
- ".svelte-kit",
1210
- ".turbo",
1211
- ".vercel",
1212
- "build",
1213
- "coverage",
1214
- "dist",
1215
- "node_modules",
1216
- "playwright-report"
1217
- ]);
1218
- var ROOT_CONTEXT_FILES = /* @__PURE__ */ new Set([
1219
- "AGENTS.md",
1220
- "CLAUDE.md",
1221
- "GEMINI.md",
1222
- "README.md",
1223
- "copilot-instructions.md",
1224
- ".cursorrules",
1225
- ".windsurfrules",
1226
- ".cursorignore",
1227
- ".claudeignore",
1228
- "components.json",
1229
- "tailwind.config.js",
1230
- "tailwind.config.ts",
1231
- "tailwind.config.mjs",
1232
- "tailwind.config.cjs",
1233
- "next.config.js",
1234
- "next.config.ts",
1235
- "next.config.mjs",
1236
- "nuxt.config.js",
1237
- "nuxt.config.ts",
1238
- "astro.config.mjs",
1239
- "astro.config.ts",
1240
- "svelte.config.js",
1241
- "svelte.config.ts",
1242
- "angular.json",
1243
- "vite.config.js",
1244
- "vite.config.ts",
1245
- "vitest.config.ts",
1246
- "vitest.config.js",
1247
- "playwright.config.ts",
1248
- "playwright.config.js",
1249
- "tsconfig.json",
1250
- "package.json",
1251
- "decantr.essence.json"
1252
- ]);
1253
- var CONTEXT_DIRECTORIES = /* @__PURE__ */ new Set([
1254
- ".agents",
1255
- ".claude",
1256
- ".claude/initiatives",
1257
- ".claude/rules",
1258
- ".codex",
1259
- ".cursor",
1260
- ".cursor/rules",
1261
- ".github/workflows",
1262
- "docs",
1263
- "docs/initiatives",
1264
- "initiatives",
1265
- "memory",
1266
- "memories",
1267
- "project-memory",
1268
- "supabase"
1269
- ]);
1270
- function shouldSkipDir2(name) {
1271
- return SKIP_DIRS2.has(name);
1272
- }
1273
- function normalizedPath(relPath) {
1274
- return relPath.split(sep2).join("/");
1275
- }
1276
- function isPotentialContextFile(relPath, name) {
1277
- const normalized2 = normalizedPath(relPath);
1278
- if (ROOT_CONTEXT_FILES.has(name)) return true;
1279
- if (name.startsWith(".env")) return true;
1280
- if (normalized2.startsWith(".claude/")) return true;
1281
- if (normalized2.startsWith(".agents/")) return true;
1282
- if (normalized2.startsWith(".codex/")) return true;
1283
- if (normalized2.startsWith(".cursor/")) return true;
1284
- if (normalized2.startsWith(".github/workflows/")) return true;
1285
- if (normalized2.startsWith("docs/")) return true;
1286
- if (normalized2.startsWith("initiatives/")) return true;
1287
- if (normalized2.startsWith("memory/")) return true;
1288
- if (normalized2.startsWith("memories/")) return true;
1289
- if (normalized2.startsWith("project-memory/")) return true;
1290
- if (normalized2.startsWith("supabase/")) return true;
1291
- if (normalized2.startsWith("migrations/")) return true;
1292
- if (normalized2.startsWith("db/")) return true;
1293
- if (normalized2.startsWith("ROLEMIGRATIONS/")) return true;
1294
- if (normalized2 === "src/middleware.ts" || normalized2 === "middleware.ts") return true;
1295
- if (normalized2.includes("/middleware.")) return true;
1296
- const ext = extname2(name).toLowerCase();
1297
- return ext === ".md" || ext === ".mdx" || ext === ".sql" || ext === ".yml" || ext === ".yaml";
1298
- }
1299
- function classifyContext(relPath) {
1300
- const normalized2 = normalizedPath(relPath);
1301
- const lower = normalized2.toLowerCase();
1302
- const name = basename(normalized2);
1303
- if (lower === "decantr.essence.json") {
1304
- return {
1305
- role: "architecture",
1306
- confidence: 0.82,
1307
- reason: "existing Decantr contract evidence"
1308
- };
1309
- }
1310
- if (lower === ".claude/initiatives" || lower === "docs/initiatives" || lower === "initiatives" || lower === "memory" || lower === "memories" || lower === "project-memory" || lower.startsWith(".claude/initiatives/") || lower.startsWith("docs/initiatives/") || lower.startsWith("initiatives/") || lower.startsWith("memory/") || lower.startsWith("memories/") || lower.startsWith("project-memory/") || lower.includes("/feature/") || lower.includes("feature") || lower.includes("rbac") || lower.includes("billing") || lower.includes("admin") || lower.includes("dashboard")) {
1311
- return {
1312
- role: "feature-business",
1313
- confidence: 0.78,
1314
- reason: "feature, initiative, memory, or business-domain evidence"
1315
- };
1316
- }
1317
- if (lower === ".agents" || lower === ".claude" || lower === ".codex" || lower === ".cursor" || lower === "claude.md" || lower === "agents.md" || lower === "gemini.md" || lower === "copilot-instructions.md" || lower === ".cursorrules" || lower === ".windsurfrules" || lower.startsWith(".claude/") || lower.startsWith(".agents/") || lower.startsWith(".codex/") || lower.startsWith(".cursor/rules/")) {
1318
- return {
1319
- role: "assistant-specific",
1320
- confidence: 0.98,
1321
- reason: "assistant or AI-agent instruction surface"
1322
- };
1323
- }
1324
- if (lower.includes("security") || lower.includes("auth") || lower.includes("rls") || lower.includes("schema") || lower.includes("migration") || lower.startsWith("supabase/") || lower.startsWith("migrations/") || lower.startsWith("db/") || lower.startsWith("rolemigrations/") || lower.includes("middleware.")) {
1325
- return {
1326
- role: "security-data",
1327
- confidence: 0.9,
1328
- reason: "security, auth, schema, middleware, or data-governance evidence"
1329
- };
1330
- }
1331
- if (lower.includes("design-system") || lower === "components.json" || lower.startsWith("tailwind.config") || lower.includes("ui-components") || lower.includes("colors") || lower.includes("typography") || lower.includes("spacing")) {
1332
- return {
1333
- role: "design-system",
1334
- confidence: 0.88,
1335
- reason: "design system or styling convention evidence"
1336
- };
1337
- }
1338
- if (lower.startsWith(".github/workflows/") || lower.includes("workflow") || lower.includes("testing") || lower.includes("deployment") || lower.includes("vitest.config") || lower.includes("playwright.config") || lower === "package.json") {
1339
- return {
1340
- role: "workflow-ci",
1341
- confidence: 0.84,
1342
- reason: "workflow, CI, deployment, or validation command evidence"
1343
- };
1344
- }
1345
- if (lower === "docs" || lower.includes("architecture") || lower === "readme.md" || lower.includes("setup") || lower.includes("contributing") || name === "tsconfig.json" || lower.endsWith("config.ts") || lower.endsWith("config.js") || lower.endsWith("config.mjs")) {
1346
- return {
1347
- role: "architecture",
1348
- confidence: 0.72,
1349
- reason: "architecture, setup, or framework configuration evidence"
1350
- };
1351
- }
1352
- if (lower.includes("complete") || lower.includes("summary") || lower.includes("deprecated") || lower.includes("legacy") || lower.includes("migration")) {
1353
- return {
1354
- role: "stale-or-historical",
1355
- confidence: 0.64,
1356
- reason: "historical or possibly stale project documentation"
1357
- };
1358
- }
1359
- return { role: "unknown", confidence: 0.35, reason: "unclassified context candidate" };
1360
- }
1361
- function isSafeToCite(relPath) {
1362
- const lower = normalizedPath(relPath).toLowerCase();
1363
- if (lower.startsWith(".env") && lower !== ".env.example" && lower !== ".env.sample") return false;
1364
- if (lower.includes("secret") || lower.includes("private-key") || lower.includes("credentials")) {
1365
- return false;
1366
- }
1367
- return true;
1368
- }
1369
- function addDirectoryContext(items, projectRoot, relPath) {
1370
- const fullPath = join6(projectRoot, relPath);
1371
- if (!existsSync6(fullPath)) return;
1372
- const stats = statSync3(fullPath);
1373
- const classified = classifyContext(relPath);
1374
- items.push({
1375
- path: normalizedPath(relPath),
1376
- type: "directory",
1377
- role: classified.role,
1378
- confidence: classified.confidence,
1379
- sizeBytes: stats.size,
1380
- safeToCite: isSafeToCite(relPath),
1381
- reason: classified.reason
1382
- });
1383
- }
1384
- function walk(projectRoot, dir, items, depth) {
1385
- if (depth > 6) return;
1386
- let entries;
1387
- try {
1388
- entries = readdirSync4(dir);
1389
- } catch {
1390
- return;
1391
- }
1392
- for (const entry of entries) {
1393
- if (shouldSkipDir2(entry)) continue;
1394
- const fullPath = join6(dir, entry);
1395
- const relPath = normalizedPath(relative2(projectRoot, fullPath));
1396
- let stats;
1397
- try {
1398
- stats = statSync3(fullPath);
1399
- } catch {
1400
- continue;
1401
- }
1402
- if (stats.isDirectory()) {
1403
- if (CONTEXT_DIRECTORIES.has(relPath)) {
1404
- addDirectoryContext(items, projectRoot, relPath);
1405
- }
1406
- walk(projectRoot, fullPath, items, depth + 1);
1407
- continue;
1408
- }
1409
- if (!stats.isFile() || !isPotentialContextFile(relPath, entry)) continue;
1410
- const classified = classifyContext(relPath);
1411
- items.push({
1412
- path: relPath,
1413
- type: "file",
1414
- role: classified.role,
1415
- confidence: classified.confidence,
1416
- sizeBytes: stats.size,
1417
- safeToCite: isSafeToCite(relPath),
1418
- reason: classified.reason
1419
- });
1420
- }
1421
- }
1422
- function summarize(items) {
1423
- const summary = {
1424
- "assistant-specific": 0,
1425
- "security-data": 0,
1426
- architecture: 0,
1427
- "design-system": 0,
1428
- "workflow-ci": 0,
1429
- "feature-business": 0,
1430
- "stale-or-historical": 0,
1431
- unknown: 0
1432
- };
1433
- for (const item of items) summary[item.role] += 1;
1434
- return summary;
1435
- }
1436
- function readSmallText(projectRoot, relPath) {
1437
- const fullPath = join6(projectRoot, relPath);
1438
- try {
1439
- const stat = statSync3(fullPath);
1440
- if (stat.size > 64e3) return "";
1441
- return readFileSync6(fullPath, "utf-8");
1442
- } catch {
1443
- return "";
1444
- }
1445
- }
1446
- function detectConflicts(projectRoot, items) {
1447
- const text = items.filter(
1448
- (item) => item.type === "file" && item.safeToCite && item.path.match(/\.(md|mdx|json|ts|js|yml|yaml)$/)
1449
- ).slice(0, 80).map((item) => readSmallText(projectRoot, item.path)).join("\n").toLowerCase();
1450
- const conflicts = [];
1451
- const frameworkSignals = [
1452
- ["next", /\bnext\.?js\b|\bapp router\b|\bpages router\b/],
1453
- ["angular", /\bangular\b/],
1454
- ["svelte", /\bsvelte\b|\bsveltekit\b/],
1455
- ["vue", /\bvue\b|\bnuxt\b/]
1456
- ].filter(([, pattern]) => pattern.test(text));
1457
- if (frameworkSignals.length > 1) {
1458
- conflicts.push(
1459
- `Multiple framework doctrines appear in ambient docs: ${frameworkSignals.map(([name]) => name).join(", ")}.`
1460
- );
1461
- }
1462
- const forbidsTailwind = /\b(do not|don't|avoid|forbid|forbidden)\s+use\s+tailwind\b|\bno\s+tailwind\b/.test(text);
1463
- const endorsesTailwind = /\btailwind\.config\b|\btailwindcss\b|\b@tailwind\b|\btailwind\s+classes\b/.test(text);
1464
- if (forbidsTailwind && endorsesTailwind) {
1465
- conflicts.push("Ambient docs contain both Tailwind usage and anti-Tailwind language.");
1466
- }
1467
- if (/\bclient component\b/.test(text) && /\bserver components? only\b/.test(text)) {
1468
- conflicts.push("Ambient docs may conflict on client vs server component boundaries.");
1469
- }
1470
- return conflicts;
1471
- }
1472
- function detectDecantrEssenceStaleRisk(projectRoot, items) {
1473
- if (!items.some((item) => item.path === "decantr.essence.json")) return [];
1474
- const content = readSmallText(projectRoot, "decantr.essence.json");
1475
- if (!content) return [];
1476
- try {
1477
- const essence = JSON.parse(content);
1478
- const risks = [];
1479
- if (essence.version !== "4.0.0") {
1480
- risks.push(
1481
- `decantr.essence.json uses Decantr essence version ${essence.version ?? "unknown"}; run decantr migrate --to v4 or review before treating it as current brownfield doctrine.`
1482
- );
1483
- }
1484
- if (essence.dna?.theme?.id === "luminarum" && essence.structure) {
1485
- risks.push(
1486
- "decantr.essence.json looks like an older Decantr default scaffold; verify before importing its theme or page layout as brownfield truth."
1487
- );
1488
- }
1489
- return risks;
1490
- } catch {
1491
- return [
1492
- "decantr.essence.json could not be parsed during ambient inventory; review before treating it as current doctrine."
1493
- ];
1494
- }
1495
- }
1496
- function detectStaleRisks(projectRoot, items) {
1497
- const pathRisks = items.filter(
1498
- (item) => item.role === "stale-or-historical" || /complete|summary|legacy|deprecated/i.test(item.path)
1499
- ).slice(0, 12).map(
1500
- (item) => `${item.path} may be historical; verify before treating it as current doctrine.`
1501
- );
1502
- return [...pathRisks, ...detectDecantrEssenceStaleRisk(projectRoot, items)];
1503
- }
1504
- function scanAmbientContext(projectRoot) {
1505
- const items = [];
1506
- walk(projectRoot, projectRoot, items, 0);
1507
- const deduped = [...new Map(items.map((item) => [item.path, item])).values()].sort(
1508
- (a, b) => a.path.localeCompare(b.path)
1509
- );
1510
- return {
1511
- version: 1,
1512
- scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
1513
- items: deduped,
1514
- summary: summarize(deduped),
1515
- conflicts: detectConflicts(projectRoot, deduped),
1516
- staleRisks: detectStaleRisks(projectRoot, deduped)
1517
- };
1518
- }
1519
-
1520
1585
  // src/doctrine-map.ts
1521
- import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "fs";
1522
- import { join as join7 } from "path";
1586
+ import { existsSync as existsSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "fs";
1587
+ import { join as join8 } from "path";
1523
1588
  var PRECEDENCE_ORDER = [
1524
1589
  "security-data",
1525
1590
  "architecture",
@@ -1672,16 +1737,16 @@ function createDoctrineMap(ambient) {
1672
1737
  };
1673
1738
  }
1674
1739
  function doctrineMapPath(projectRoot) {
1675
- return join7(projectRoot, ".decantr", "doctrine-map.json");
1740
+ return join8(projectRoot, ".decantr", "doctrine-map.json");
1676
1741
  }
1677
1742
  function writeDoctrineMap(projectRoot, doctrine) {
1678
1743
  writeFileSync2(doctrineMapPath(projectRoot), JSON.stringify(doctrine, null, 2) + "\n", "utf-8");
1679
1744
  }
1680
1745
  function readDoctrineMap(projectRoot) {
1681
1746
  const path = doctrineMapPath(projectRoot);
1682
- if (!existsSync7(path)) return null;
1747
+ if (!existsSync8(path)) return null;
1683
1748
  try {
1684
- const parsed = JSON.parse(readFileSync7(path, "utf-8"));
1749
+ const parsed = JSON.parse(readFileSync8(path, "utf-8"));
1685
1750
  if (parsed.version !== 1 || !Array.isArray(parsed.sources)) return null;
1686
1751
  return parsed;
1687
1752
  } catch {
@@ -1690,9 +1755,11 @@ function readDoctrineMap(projectRoot) {
1690
1755
  }
1691
1756
 
1692
1757
  export {
1758
+ loadBundledContentItem,
1759
+ loadBundledContentList,
1760
+ scanAmbientContext,
1693
1761
  scanRoutes,
1694
1762
  scanStyling,
1695
- scanAmbientContext,
1696
1763
  createDoctrineMap,
1697
1764
  writeDoctrineMap,
1698
1765
  readDoctrineMap,