@codex-agent/cli 0.1.0-main.13.sha3ad2ccf → 0.1.0-main.14.shac4b2c28

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,17 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.mjs
4
- import fs10 from "node:fs";
5
- import path10 from "node:path";
4
+ import fs11 from "node:fs";
5
+ import path11 from "node:path";
6
6
 
7
7
  // src/core.mjs
8
- import fs9 from "node:fs";
9
- import path9 from "node:path";
8
+ import fs10 from "node:fs";
9
+ import path10 from "node:path";
10
10
 
11
11
  // ../../plugins/codex-agent/scripts/context-project.mjs
12
12
  import crypto4 from "node:crypto";
13
- import fs5 from "node:fs";
14
- import path5 from "node:path";
13
+ import fs6 from "node:fs";
14
+ import path6 from "node:path";
15
15
 
16
16
  // ../../plugins/codex-agent/generated/agent-profiles.mjs
17
17
  var agentProfiles = [
@@ -170,13 +170,13 @@ var listTreeFiles = (root, { includeDirectories = false } = {}) => {
170
170
  const visit = (directory) => {
171
171
  for (const entry of fs.readdirSync(directory, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name))) {
172
172
  const absolute = path.join(directory, entry.name);
173
- const relative2 = slash(path.relative(absoluteRoot, absolute));
174
- if (entry.isSymbolicLink()) throw new Error(`Refusing to traverse symbolic link: ${relative2}`);
173
+ const relative3 = slash(path.relative(absoluteRoot, absolute));
174
+ if (entry.isSymbolicLink()) throw new Error(`Refusing to traverse symbolic link: ${relative3}`);
175
175
  if (entry.isDirectory()) {
176
- if (includeDirectories) entries.push({ absolute, relative: relative2, type: "directory" });
176
+ if (includeDirectories) entries.push({ absolute, relative: relative3, type: "directory" });
177
177
  visit(absolute);
178
- } else if (entry.isFile()) entries.push({ absolute, relative: relative2, type: "file" });
179
- else throw new Error(`Unsupported filesystem entry in context catalog: ${relative2}`);
178
+ } else if (entry.isFile()) entries.push({ absolute, relative: relative3, type: "file" });
179
+ else throw new Error(`Unsupported filesystem entry in context catalog: ${relative3}`);
180
180
  }
181
181
  };
182
182
  visit(absoluteRoot);
@@ -295,13 +295,13 @@ var lstat = (target) => {
295
295
  };
296
296
  var filesystemErrorCode = (error) => typeof error?.code === "string" ? error.code : "UNKNOWN";
297
297
  var isInside = (root, target) => {
298
- const relative2 = path2.relative(root, target);
299
- return relative2 === "" || !relative2.startsWith(`..${path2.sep}`) && relative2 !== ".." && !path2.isAbsolute(relative2);
298
+ const relative3 = path2.relative(root, target);
299
+ return relative3 === "" || !relative3.startsWith(`..${path2.sep}`) && relative3 !== ".." && !path2.isAbsolute(relative3);
300
300
  };
301
301
  var inspectAncestry = (root, target) => {
302
302
  let current = root;
303
- const relative2 = path2.relative(root, target);
304
- const segments = relative2 ? relative2.split(path2.sep).filter(Boolean) : [];
303
+ const relative3 = path2.relative(root, target);
304
+ const segments = relative3 ? relative3.split(path2.sep).filter(Boolean) : [];
305
305
  for (const [position, segment] of segments.entries()) {
306
306
  current = path2.join(current, segment);
307
307
  const inspected = lstat(current);
@@ -1193,15 +1193,15 @@ var applyLockedTransaction = ({ root, documents, indexContent, backupPaths = []
1193
1193
  const stagedIndex = path3.join(stagedRoot, "index.json");
1194
1194
  fs3.writeFileSync(stagedIndex, indexContent, { flag: "wx" });
1195
1195
  const backedUp = [];
1196
- const existingBackups = normalizedBackups.filter((relative2) => fs3.existsSync(path3.join(contextRoot, ...relative2.split("/"))));
1196
+ const existingBackups = normalizedBackups.filter((relative3) => fs3.existsSync(path3.join(contextRoot, ...relative3.split("/"))));
1197
1197
  if (existingBackups.length) {
1198
1198
  const backupRoot = path3.join(codexAgentRoot, "backups", timestamp(), ".codex-agent", "context");
1199
1199
  assertNoSymlink(projectRoot, backupRoot, "Context backup directory");
1200
- for (const relative2 of existingBackups) {
1201
- const source = path3.join(contextRoot, ...relative2.split("/"));
1200
+ for (const relative3 of existingBackups) {
1201
+ const source = path3.join(contextRoot, ...relative3.split("/"));
1202
1202
  assertInside(contextRoot, source, "Context backup source");
1203
1203
  assertNoSymlink(projectRoot, source, "Context backup source");
1204
- const destination = path3.join(backupRoot, ...relative2.split("/"));
1204
+ const destination = path3.join(backupRoot, ...relative3.split("/"));
1205
1205
  fs3.mkdirSync(path3.dirname(destination), { recursive: true });
1206
1206
  fs3.copyFileSync(source, destination, fs3.constants.COPYFILE_EXCL);
1207
1207
  backedUp.push(slash(path3.relative(projectRoot, destination)));
@@ -1233,13 +1233,13 @@ var applyLockedTransaction = ({ root, documents, indexContent, backupPaths = []
1233
1233
  items: manifestItems
1234
1234
  };
1235
1235
  writeRecoveryManifest({ transactionRoot, manifest });
1236
- const promote = (relative2) => {
1237
- const staged = path3.join(stagedRoot, ...relative2.split("/"));
1238
- const destination = path3.join(contextRoot, ...relative2.split("/"));
1236
+ const promote = (relative3) => {
1237
+ const staged = path3.join(stagedRoot, ...relative3.split("/"));
1238
+ const destination = path3.join(contextRoot, ...relative3.split("/"));
1239
1239
  assertInside(contextRoot, destination, "Context transaction destination");
1240
1240
  assertNoSymlink(projectRoot, destination, "Context transaction destination");
1241
1241
  fs3.mkdirSync(path3.dirname(destination), { recursive: true });
1242
- const rollback = path3.join(rollbackRoot, ...relative2.split("/"));
1242
+ const rollback = path3.join(rollbackRoot, ...relative3.split("/"));
1243
1243
  const existed = fs3.existsSync(destination);
1244
1244
  if (existed) {
1245
1245
  fs3.mkdirSync(path3.dirname(rollback), { recursive: true });
@@ -1547,61 +1547,25 @@ var inspectContextCatalogPath = ({ root, relativePath, kind = "recovery" }) => {
1547
1547
  return inspectCatalog({ projectRoot, contextRoot, kind });
1548
1548
  };
1549
1549
 
1550
- // ../../plugins/codex-agent/scripts/context-project.mjs
1551
- var ANALYSIS_VERSION = 1;
1552
- var CONTEXT_LIFECYCLE_VERSION = 1;
1553
- var HASH_PATTERN2 = /^[a-f0-9]{64}$/;
1554
- var LIFECYCLE_PHASES = /* @__PURE__ */ new Set(["prepared", "migration-applied", "managed-running", "managed-applied"]);
1555
- var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
1556
- ".git",
1557
- ".codex-agent",
1558
- ".next",
1559
- ".nuxt",
1560
- ".turbo",
1561
- ".venv",
1562
- "build",
1563
- "coverage",
1564
- "dist",
1565
- "node_modules",
1566
- "target",
1567
- "vendor"
1568
- ]);
1569
- var MANAGED_CONTEXT = [
1570
- ["architecture", "architecture/system.md", "System architecture, modules, entrypoints, and detected boundaries.", ["architecture", "modules", "entrypoints"], "high", ["modules", "entrypoints", "conventions.boundaries"]],
1571
- ["code-quality", "standards/code-quality.md", "Detected source layout, naming, and engineering conventions.", ["code", "quality", "conventions"], "critical", ["conventions", "languages"]],
1572
- ["testing", "standards/testing.md", "Detected test tooling, locations, and repository commands.", ["test", "verification", "commands"], "high", ["testing", "commands"]],
1573
- ["security", "standards/security.md", "Detected security-sensitive boundaries and baseline safeguards.", ["security", "auth", "secrets"], "critical", ["security"]],
1574
- ["project-intelligence", "project-intelligence/project.md", "Detected stack, package tooling, CI, and project intelligence.", ["project", "stack", "ci"], "medium", ["project", "packageManager", "languages", "frameworks", "ciCd"]]
1575
- ];
1576
- var CODEX_AGENT_IGNORE_RULES = [
1577
- ".codex-agent/analysis.json",
1578
- ".codex-agent/sessions/",
1579
- ".codex-agent/backups/",
1580
- ".codex-agent/.locks/",
1581
- ".codex-agent/.transactions/",
1582
- ".codex-agent/**/*.tmp-*"
1583
- ];
1584
- var BROAD_CODEX_AGENT_IGNORE_RULES = /* @__PURE__ */ new Set([
1585
- ".codex-agent",
1586
- ".codex-agent/",
1587
- ".codex-agent/**",
1588
- "/.codex-agent",
1589
- "/.codex-agent/",
1590
- "/.codex-agent/**"
1591
- ]);
1592
- var MANAGED_CODEX_AGENT_IGNORE_RULES = new Set(CODEX_AGENT_IGNORE_RULES);
1550
+ // ../../plugins/codex-agent/scripts/lib/project-analysis.mjs
1551
+ import fs5 from "node:fs";
1552
+ import path5 from "node:path";
1553
+ var ANALYSIS_VERSION = 2;
1593
1554
  var slash2 = (value) => value.split(path5.sep).join("/");
1594
1555
  var unique = (items) => [...new Set(items.filter(Boolean))];
1595
1556
  var relative = (root, file) => slash2(path5.relative(root, file));
1596
- var signal = (value, evidence = [], confidence = "unknown", status = "unknown") => ({
1597
- value,
1598
- evidence: unique(evidence),
1599
- confidence,
1600
- status
1601
- });
1557
+ var signal = (value, evidence = [], confidence = "unknown", status = "unknown") => ({ value, evidence: unique(evidence), confidence, status });
1602
1558
  var detected = (value, evidence, confidence = "high") => signal(value, evidence, confidence, "detected");
1603
1559
  var inferred = (value, evidence, confidence = "medium") => signal(value, evidence, confidence, "inferred");
1604
1560
  var unknown = (empty) => signal(empty, [], "unknown", "unknown");
1561
+ var readText = (file, limit = 2 * 1024 * 1024) => {
1562
+ try {
1563
+ if (fs5.statSync(file).size > limit) return "";
1564
+ return fs5.readFileSync(file, "utf8");
1565
+ } catch {
1566
+ return "";
1567
+ }
1568
+ };
1605
1569
  var readJson = (file) => {
1606
1570
  try {
1607
1571
  return JSON.parse(fs5.readFileSync(file, "utf8"));
@@ -1609,10 +1573,127 @@ var readJson = (file) => {
1609
1573
  return null;
1610
1574
  }
1611
1575
  };
1612
- var walk = (root, limit = 6e3) => {
1576
+ var attributes = (tag) => Object.fromEntries([...tag.matchAll(/([A-Za-z_][\w:.-]*)\s*=\s*"([^"]*)"/g)].map((match) => [match[1].toLowerCase(), match[2]]));
1577
+ var major = (version) => String(version ?? "").replace(/^[^\d]*/, "").match(/^\d+/)?.[0] ?? null;
1578
+ var escapeRegex = (value) => value.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
1579
+ var globRegex = (pattern) => {
1580
+ let source = "";
1581
+ for (let index = 0; index < pattern.length; index += 1) {
1582
+ const character = pattern[index];
1583
+ if (character === "*" && pattern[index + 1] === "*") {
1584
+ source += ".*";
1585
+ index += 1;
1586
+ } else if (character === "*") source += "[^/]*";
1587
+ else if (character === "?") source += "[^/]";
1588
+ else source += escapeRegex(character);
1589
+ }
1590
+ return source;
1591
+ };
1592
+ var parseIgnoreRules = (root) => [".gitignore", ".tfignore", ".ignore"].flatMap((name) => {
1593
+ const file = path5.join(root, name);
1594
+ if (!fs5.existsSync(file) || !fs5.statSync(file).isFile()) return [];
1595
+ return readText(file).split(/\r?\n/).flatMap((line) => {
1596
+ const trimmed = line.trim();
1597
+ if (!trimmed || trimmed.startsWith("#")) return [];
1598
+ const negated = trimmed.startsWith("!");
1599
+ let body = (negated ? trimmed.slice(1) : trimmed).replace(/\\/g, "/");
1600
+ const anchored = body.startsWith("/");
1601
+ if (anchored) body = body.slice(1);
1602
+ const directory = body.endsWith("/");
1603
+ body = body.replace(/\/$/, "");
1604
+ if (!body) return [];
1605
+ const prefix = anchored || body.includes("/") ? "^" : "(?:^|/)";
1606
+ return [{ source: name, negated, regex: new RegExp(`${prefix}${globRegex(body)}${directory ? "(?:/|$)" : "$"}`, "i") }];
1607
+ });
1608
+ });
1609
+ var rootFiles = (root) => fs5.readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => entry.name).sort();
1610
+ var expandRootGlob = (root, pattern) => {
1611
+ const normalized = slash2(pattern).replace(/^\.\//, "").replace(/\/$/, "");
1612
+ const segments = normalized.split("/");
1613
+ const results = [];
1614
+ const visit = (directory, index) => {
1615
+ if (index === segments.length) {
1616
+ results.push(relative(root, directory) || ".");
1617
+ return;
1618
+ }
1619
+ const segment = segments[index];
1620
+ if (!segment.includes("*") && !segment.includes("?")) {
1621
+ visit(path5.join(directory, segment), index + 1);
1622
+ return;
1623
+ }
1624
+ if (!fs5.existsSync(directory)) return;
1625
+ const matcher = new RegExp(`^${globRegex(segment)}$`);
1626
+ for (const entry of fs5.readdirSync(directory, { withFileTypes: true })) if (entry.isDirectory() && matcher.test(entry.name)) visit(path5.join(directory, entry.name), index + 1);
1627
+ };
1628
+ visit(root, 0);
1629
+ return results.filter((item) => fs5.existsSync(path5.join(root, item)));
1630
+ };
1631
+ var rootContainerMembers = (root) => {
1632
+ const members = [];
1633
+ const evidence = [];
1634
+ const add = (member, source) => {
1635
+ const normalized = slash2(member).replace(/^\.\//, "").replace(/\/$/, "");
1636
+ if (!normalized || normalized.startsWith("../") || path5.isAbsolute(normalized)) return;
1637
+ for (const expanded of normalized.includes("*") || normalized.includes("?") ? expandRootGlob(root, normalized) : [normalized]) {
1638
+ if (!members.includes(expanded)) members.push(expanded);
1639
+ }
1640
+ evidence.push(source);
1641
+ };
1642
+ for (const file of rootFiles(root)) {
1643
+ const absolute = path5.join(root, file);
1644
+ const lower = file.toLowerCase();
1645
+ const content = readText(absolute);
1646
+ if (lower.endsWith(".sln")) for (const match of content.matchAll(/^Project\("[^"]+"\)\s*=\s*"[^"]+",\s*"([^"]+\.(?:cs|fs|vb)proj)"/gmi)) add(path5.dirname(match[1].replace(/\\/g, "/")), file);
1647
+ else if (lower === "package.json") {
1648
+ const manifest = readJson(absolute);
1649
+ const workspaces = Array.isArray(manifest?.workspaces) ? manifest.workspaces : manifest?.workspaces?.packages;
1650
+ for (const member of workspaces ?? []) add(member, "package.json#workspaces");
1651
+ } else if (lower === "cargo.toml") {
1652
+ const workspaceBlock = content.match(/^\s*\[workspace\]\s*$([\s\S]*?)(?=^\s*\[|(?![\s\S]))/m)?.[1] ?? "";
1653
+ const workspace = workspaceBlock.match(/members\s*=\s*\[([\s\S]*?)\]/m)?.[1] ?? "";
1654
+ for (const match of workspace.matchAll(/["']([^"']+)["']/g)) add(match[1], "Cargo.toml#workspace.members");
1655
+ } else if (lower === "go.work") {
1656
+ for (const match of content.matchAll(/^\s*use\s+([^\s()]+)\s*$/gm)) add(match[1], "go.work#use");
1657
+ for (const block of content.matchAll(/^\s*use\s*\(\s*$([\s\S]*?)^\s*\)\s*$/gm)) {
1658
+ for (const line of block[1].split(/\r?\n/)) {
1659
+ const member = line.replace(/\/\/.*$/, "").trim();
1660
+ if (member) add(member, "go.work#use");
1661
+ }
1662
+ }
1663
+ } else if (lower === "pom.xml") for (const match of content.matchAll(/<module>([^<]+)<\/module>/g)) add(match[1], "pom.xml#modules");
1664
+ else if (["settings.gradle", "settings.gradle.kts"].includes(lower)) {
1665
+ for (const statement of content.matchAll(/^\s*(include|includeBuild)\s*(?:\(([^)]*)\)|(.+))$/gm)) {
1666
+ for (const match of (statement[2] ?? statement[3] ?? "").matchAll(/["']:?([^"']+)["']/g)) add(match[1].replace(/:/g, "/"), `${file}#${statement[1]}`);
1667
+ }
1668
+ }
1669
+ }
1670
+ return { members: unique(members), evidence: unique(evidence) };
1671
+ };
1672
+ var INTERNAL_METADATA = /* @__PURE__ */ new Set([".agents", ".codex", ".codex-agent", ".git", ".hg", ".opencode", ".svn"]);
1673
+ var FALLBACK_TRANSIENT = /* @__PURE__ */ new Set([".cache", ".idea", ".tmp", ".venv", ".vs", "bin", "coverage", "dist", "node_modules", "obj", "testresults"]);
1674
+ var inventoryRepository = (root, declaredRoots, limit = 5e4) => {
1675
+ const rules = parseIgnoreRules(root);
1613
1676
  const files = [];
1677
+ const excluded = [];
1678
+ let truncated = false;
1679
+ const declared = unique(declaredRoots.map((item) => slash2(item).replace(/^\.\//, "").replace(/\/$/, "")));
1680
+ const requiredToReachDeclared = (candidate) => declared.some((item) => candidate === item || item.startsWith(`${candidate}/`));
1681
+ const declaredPathPrefixes = (item) => item.split("/").map((_, index, segments) => segments.slice(0, index + 1).join("/"));
1682
+ const ruleWouldHideDeclared = (rule, candidate) => declared.filter((item) => candidate === item || candidate.startsWith(`${item}/`) || item.startsWith(`${candidate}/`)).some((item) => declaredPathPrefixes(item).some((prefix) => rule.regex.test(prefix) || rule.regex.test(`${prefix}/`)));
1683
+ const ignoredByRule = (candidate, directory) => {
1684
+ let ignored = false;
1685
+ let source = null;
1686
+ for (const rule of rules) if ((rule.regex.test(candidate) || directory && rule.regex.test(`${candidate}/`)) && !ruleWouldHideDeclared(rule, candidate)) {
1687
+ ignored = !rule.negated;
1688
+ source = rule.source;
1689
+ }
1690
+ return ignored ? source : null;
1691
+ };
1614
1692
  const visit = (directory) => {
1615
- if (files.length >= limit) return;
1693
+ if (files.length >= limit) {
1694
+ truncated = true;
1695
+ return;
1696
+ }
1616
1697
  let entries = [];
1617
1698
  try {
1618
1699
  entries = fs5.readdirSync(directory, { withFileTypes: true });
@@ -1620,26 +1701,56 @@ var walk = (root, limit = 6e3) => {
1620
1701
  return;
1621
1702
  }
1622
1703
  for (const entry of entries) {
1623
- if (files.length >= limit) break;
1624
- if (entry.isDirectory() && IGNORED_DIRECTORIES.has(entry.name)) continue;
1704
+ if (files.length >= limit) {
1705
+ truncated = true;
1706
+ break;
1707
+ }
1625
1708
  const absolute = path5.join(directory, entry.name);
1626
- if (entry.isDirectory()) visit(absolute);
1627
- else if (entry.isFile()) files.push(absolute);
1709
+ const candidate = relative(root, absolute);
1710
+ if (entry.isDirectory()) {
1711
+ if (INTERNAL_METADATA.has(entry.name.toLowerCase())) continue;
1712
+ const requiredPath = requiredToReachDeclared(candidate);
1713
+ const ignoreSource = ignoredByRule(candidate, true);
1714
+ const fallback = requiredPath ? false : FALLBACK_TRANSIENT.has(entry.name.toLowerCase());
1715
+ if (ignoreSource || fallback) {
1716
+ excluded.push({ path: candidate, role: fallback ? "transient" : "ignored", source: ignoreSource ?? "fallback" });
1717
+ continue;
1718
+ }
1719
+ visit(absolute);
1720
+ } else if (entry.isFile() && !ignoredByRule(candidate, false)) files.push(absolute);
1628
1721
  }
1629
1722
  };
1630
1723
  visit(root);
1631
- return files.sort();
1632
- };
1633
- var dependencyNames = (manifest) => new Set(Object.keys({
1634
- ...manifest?.dependencies ?? {},
1635
- ...manifest?.devDependencies ?? {},
1636
- ...manifest?.peerDependencies ?? {}
1637
- }));
1638
- var packageCommand = (manager, script) => {
1639
- if (manager === "npm" && script === "test") return "npm test";
1640
- if (manager === "yarn") return `yarn ${script}`;
1641
- return `${manager || "npm"} run ${script}`;
1642
- };
1724
+ return { files: files.sort(), excluded, truncated, limit };
1725
+ };
1726
+ var extensionLanguages = /* @__PURE__ */ new Map([
1727
+ [".js", "JavaScript"],
1728
+ [".mjs", "JavaScript"],
1729
+ [".cjs", "JavaScript"],
1730
+ [".jsx", "JavaScript"],
1731
+ [".ts", "TypeScript"],
1732
+ [".tsx", "TypeScript"],
1733
+ [".py", "Python"],
1734
+ [".go", "Go"],
1735
+ [".rs", "Rust"],
1736
+ [".java", "Java"],
1737
+ [".kt", "Kotlin"],
1738
+ [".swift", "Swift"],
1739
+ [".rb", "Ruby"],
1740
+ [".php", "PHP"],
1741
+ [".cs", "C#"],
1742
+ [".fs", "F#"],
1743
+ [".vb", "Visual Basic"],
1744
+ [".cpp", "C++"],
1745
+ [".cc", "C++"],
1746
+ [".c", "C"],
1747
+ [".vue", "Vue"],
1748
+ [".svelte", "Svelte"],
1749
+ [".cshtml", "Razor"],
1750
+ [".razor", "Razor"],
1751
+ [".css", "CSS"],
1752
+ [".scss", "Sass"]
1753
+ ]);
1643
1754
  var classifyNaming = (name) => {
1644
1755
  if (/^[a-z][a-z0-9]*(?:-[a-z0-9]+)+$/.test(name)) return "kebab-case";
1645
1756
  if (/^[a-z][A-Za-z0-9]*$/.test(name) && /[A-Z]/.test(name)) return "camelCase";
@@ -1647,53 +1758,148 @@ var classifyNaming = (name) => {
1647
1758
  if (/^[a-z][a-z0-9]*(?:_[a-z0-9]+)+$/.test(name)) return "snake_case";
1648
1759
  return null;
1649
1760
  };
1650
- var analyzeProject = ({ root }) => {
1761
+ var dependencyTechnologies = /* @__PURE__ */ new Map([
1762
+ ["next", ["Next.js"]],
1763
+ ["react", ["React"]],
1764
+ ["vue", ["Vue"]],
1765
+ ["@angular/core", ["Angular"]],
1766
+ ["svelte", ["Svelte"]],
1767
+ ["@sveltejs/kit", ["SvelteKit"]],
1768
+ ["express", ["Express"]],
1769
+ ["fastify", ["Fastify"]],
1770
+ ["@nestjs/core", ["NestJS"]],
1771
+ ["django", ["Django"]],
1772
+ ["flask", ["Flask"]],
1773
+ ["fastapi", ["FastAPI"]],
1774
+ ["spring-boot-starter-web", ["Spring Boot"]],
1775
+ ["rails", ["Ruby on Rails"]],
1776
+ ["laravel/framework", ["Laravel"]],
1777
+ ["github.com/gin-gonic/gin", ["Gin"]],
1778
+ ["actix-web", ["Actix Web"]],
1779
+ ["axum", ["Axum"]],
1780
+ ["microsoft.aspnet.mvc", ["ASP.NET MVC", "major"]],
1781
+ ["microsoft.aspnet.webapi.core", ["ASP.NET Web API"]],
1782
+ ["entityframework", ["Entity Framework", "major"]],
1783
+ ["unity", ["Unity"]],
1784
+ ["topshelf", ["Topshelf"]],
1785
+ ["serilog", ["Serilog"]],
1786
+ ["jquery", ["jQuery"]],
1787
+ ["bootstrap", ["Bootstrap", "major"]],
1788
+ ["vitest", ["Vitest"]],
1789
+ ["jest", ["Jest"]],
1790
+ ["pytest", ["pytest"]],
1791
+ ["junit", ["JUnit"]],
1792
+ ["xunit", ["xUnit"]]
1793
+ ]);
1794
+ var dependencyRecord = (ecosystem, name, version, evidence) => ({ ecosystem, name: String(name), version: String(version ?? ""), evidence });
1795
+ var parseDependencies = (root, file) => {
1796
+ const absolute = path5.join(root, file);
1797
+ const lower = path5.basename(file).toLowerCase();
1798
+ const content = readText(absolute);
1799
+ const result = [];
1800
+ const add = (ecosystem, name, version) => {
1801
+ if (name) result.push(dependencyRecord(ecosystem, name, version, `${file}#${name}`));
1802
+ };
1803
+ if (["package.json", "composer.json"].includes(lower)) {
1804
+ const manifest = readJson(absolute);
1805
+ for (const field of ["dependencies", "devDependencies", "peerDependencies", "require", "require-dev"]) for (const [name, version] of Object.entries(manifest?.[field] ?? {})) add(lower === "composer.json" ? "composer" : "node", name, version);
1806
+ } else if (lower === "packages.config") for (const match of content.matchAll(/<package\b[^>]*>/gi)) {
1807
+ const item = attributes(match[0]);
1808
+ add("nuget", item.id, item.version);
1809
+ }
1810
+ else if (lower === "requirements.txt") for (const line of content.split(/\r?\n/)) {
1811
+ const match = line.trim().match(/^([A-Za-z0-9_.-]+)\s*(?:==|~=|>=|<=|>|<)?\s*([^;\s#]*)/);
1812
+ if (match) add("python", match[1], match[2]);
1813
+ }
1814
+ else if (lower === "cargo.toml") {
1815
+ let section2 = "";
1816
+ for (const line of content.split(/\r?\n/)) {
1817
+ const header = line.match(/^\s*\[([^\]]+)\]\s*(?:#.*)?$/);
1818
+ if (header) {
1819
+ section2 = header[1].toLowerCase();
1820
+ continue;
1821
+ }
1822
+ if (!/(^|\.)dependencies$/.test(section2)) continue;
1823
+ const match = line.match(/^\s*([A-Za-z0-9_.-]+)\s*=\s*(?:["']([^"']+)["']|\{[^}]*version\s*=\s*["']([^"']+))/);
1824
+ if (match) add("cargo", match[1], match[2] ?? match[3]);
1825
+ }
1826
+ } else if (lower === "pyproject.toml") {
1827
+ let section2 = "";
1828
+ for (const line of content.split(/\r?\n/)) {
1829
+ const header = line.match(/^\s*\[([^\]]+)\]\s*(?:#.*)?$/);
1830
+ if (header) {
1831
+ section2 = header[1].toLowerCase();
1832
+ continue;
1833
+ }
1834
+ if (section2 === "project") {
1835
+ const list = line.match(/^\s*dependencies\s*=\s*\[(.*)\]\s*(?:#.*)?$/);
1836
+ for (const item of list?.[1]?.matchAll(/["']([A-Za-z0-9_.-]+)(?:\[[^\]]+\])?\s*([^"']*)["']/g) ?? []) add("python", item[1], item[2]);
1837
+ } else if (section2 === "tool.poetry.dependencies" || section2.startsWith("tool.poetry.group.") && section2.endsWith(".dependencies")) {
1838
+ const match = line.match(/^\s*([A-Za-z0-9_.-]+)\s*=\s*(?:["']([^"']+)["']|\{[^}]*version\s*=\s*["']([^"']+))/);
1839
+ if (match && match[1].toLowerCase() !== "python") add("python", match[1], match[2] ?? match[3]);
1840
+ }
1841
+ }
1842
+ } else if (lower === "go.mod") for (const match of content.matchAll(/^\s*([A-Za-z0-9_.\-/]+)\s+v([^\s]+)\s*$/gm)) add("go", match[1], match[2]);
1843
+ else if (lower === "pom.xml") for (const match of content.matchAll(/<dependency>[\s\S]*?<artifactId>([^<]+)<\/artifactId>[\s\S]*?(?:<version>([^<]+)<\/version>)?[\s\S]*?<\/dependency>/gi)) add("maven", match[1], match[2]);
1844
+ else if (["build.gradle", "build.gradle.kts"].includes(lower)) for (const match of content.matchAll(/["']([^:"']+):([^:"']+):([^"']+)["']/g)) add("gradle", match[2], match[3]);
1845
+ else if (lower === "gemfile" || lower.endsWith(".gemspec")) for (const match of content.matchAll(/\bgem\s+["']([^"']+)["'](?:\s*,\s*["']([^"']+)["'])?/g)) add("ruby", match[1], match[2]);
1846
+ return result;
1847
+ };
1848
+ var manifestKinds = [
1849
+ { id: "node", names: ["package.json"], toolchain: "Node package tooling" },
1850
+ { id: "dotnet", pattern: /\.(?:cs|fs|vb)proj$/i, toolchain: "MSBuild" },
1851
+ { id: "python", names: ["pyproject.toml", "requirements.txt", "setup.py"], toolchain: "Python packaging" },
1852
+ { id: "jvm", names: ["pom.xml", "build.gradle", "build.gradle.kts"], toolchain: "JVM build tooling" },
1853
+ { id: "go", names: ["go.mod"], toolchain: "Go modules" },
1854
+ { id: "rust", names: ["Cargo.toml"], toolchain: "Cargo" },
1855
+ { id: "php", names: ["composer.json"], toolchain: "Composer" },
1856
+ { id: "ruby", names: ["Gemfile"], pattern: /\.gemspec$/i, toolchain: "Bundler" }
1857
+ ];
1858
+ var manifestKind = (file) => manifestKinds.find((kind) => kind.names?.some((name) => name.toLowerCase() === path5.basename(file).toLowerCase()) || kind.pattern?.test(file));
1859
+ var resolveUnit = (root, manifest, declaredMembers) => {
1860
+ const kind = manifestKind(manifest);
1861
+ if (!kind) return null;
1862
+ const unitPath = path5.posix.dirname(manifest) === "." ? "." : path5.posix.dirname(manifest);
1863
+ const base = path5.basename(manifest).toLowerCase();
1864
+ let name = path5.posix.basename(unitPath === "." ? root : unitPath);
1865
+ if (base === "package.json") name = readJson(path5.join(root, manifest))?.name ?? name;
1866
+ else if (/\.(?:cs|fs|vb)proj$/i.test(manifest)) name = path5.basename(manifest, path5.extname(manifest));
1867
+ else if (base === "go.mod") name = readText(path5.join(root, manifest)).match(/^module\s+(.+)$/m)?.[1] ?? name;
1868
+ return { name, path: unitPath, kind: kind.id, manifest, declared: declaredMembers.includes(unitPath), evidence: [manifest] };
1869
+ };
1870
+ var belongsToUnit = (file, unit) => unit.path === "." || file === unit.path || file.startsWith(`${unit.path}/`);
1871
+ var isVendoredAsset = (file) => /(^|\/)(?:vendor|third[_-]?party|node_modules)(\/|$)|\.min\.(?:js|css)$/i.test(file) || /(^|\/)scripts\/(?:tinymce|bootstrap|jquery|modernizr|respond)(\/|$)/i.test(file) || /(^|\/)content\/(?:bootstrap|tinymce)(\/|$)/i.test(file);
1872
+ var targetFramework = (value) => {
1873
+ const target = String(value ?? "").trim().replace(/^v/i, "");
1874
+ if (/^\d+(?:\.\d+)+$/.test(target)) return `.NET Framework ${target}`;
1875
+ const legacy = target.match(/^net(\d)(\d)(\d?)$/i);
1876
+ if (legacy && Number(legacy[1]) <= 4) return `.NET Framework ${legacy[1]}.${legacy[2]}${legacy[3] ? `.${legacy[3]}` : ""}`;
1877
+ const standard = target.match(/^netstandard(\d+)\.(\d+)/i);
1878
+ if (standard) return `.NET Standard ${standard[1]}.${standard[2]}`;
1879
+ const modern = target.match(/^net(\d+)(?:\.(\d+))?/i);
1880
+ return modern ? `.NET ${modern[1]}${modern[2] ? `.${modern[2]}` : ""}` : null;
1881
+ };
1882
+ var scriptCommand = (manager, name) => {
1883
+ if (manager === "npm" && name === "test") return "npm test";
1884
+ if (manager === "yarn") return `yarn ${name}`;
1885
+ return `${manager} run ${name}`;
1886
+ };
1887
+ var detectorRegistry = Object.freeze(manifestKinds.map((detector) => Object.freeze({ ...detector })));
1888
+ var analyzeRepository = ({ root, scanLimit = 5e4 }) => {
1889
+ if (!Number.isSafeInteger(scanLimit) || scanLimit < 1) throw new Error("scanLimit must be a positive integer");
1651
1890
  const requestedRoot = path5.resolve(root);
1652
1891
  if (!fs5.existsSync(requestedRoot)) throw new Error(`Project root not found: ${requestedRoot}`);
1653
1892
  const projectRoot = fs5.realpathSync(requestedRoot);
1654
- const files = walk(projectRoot);
1655
- const paths = files.map((file) => relative(projectRoot, file));
1893
+ const container = rootContainerMembers(projectRoot);
1894
+ const inventory = inventoryRepository(projectRoot, container.members, scanLimit);
1895
+ const paths = inventory.files.map((file) => relative(projectRoot, file));
1656
1896
  const pathSet = new Set(paths);
1657
- const manifestPath = path5.join(projectRoot, "package.json");
1658
- const manifest = fs5.existsSync(manifestPath) ? readJson(manifestPath) : null;
1659
- const dependencies = dependencyNames(manifest);
1660
- const lockManagers = [
1661
- ["pnpm-lock.yaml", "pnpm"],
1662
- ["yarn.lock", "yarn"],
1663
- ["bun.lockb", "bun"],
1664
- ["bun.lock", "bun"],
1665
- ["package-lock.json", "npm"]
1666
- ].filter(([file]) => pathSet.has(file));
1667
- const declaredManager = typeof manifest?.packageManager === "string" ? manifest.packageManager.split("@")[0] : null;
1668
- const manager = declaredManager || lockManagers[0]?.[1] || (manifest ? "npm" : null);
1669
- const managerEvidence = [
1670
- ...declaredManager ? ["package.json#packageManager"] : [],
1671
- ...lockManagers.filter(([, name]) => !declaredManager || name === declaredManager).map(([file]) => file),
1672
- ...!declaredManager && !lockManagers.length && manifest ? ["package.json"] : []
1673
- ];
1674
- const extensionLanguages = /* @__PURE__ */ new Map([
1675
- [".js", "JavaScript"],
1676
- [".mjs", "JavaScript"],
1677
- [".cjs", "JavaScript"],
1678
- [".jsx", "JavaScript"],
1679
- [".ts", "TypeScript"],
1680
- [".tsx", "TypeScript"],
1681
- [".py", "Python"],
1682
- [".go", "Go"],
1683
- [".rs", "Rust"],
1684
- [".java", "Java"],
1685
- [".kt", "Kotlin"],
1686
- [".swift", "Swift"],
1687
- [".rb", "Ruby"],
1688
- [".php", "PHP"],
1689
- [".cs", "C#"],
1690
- [".cpp", "C++"],
1691
- [".c", "C"],
1692
- [".vue", "Vue"],
1693
- [".svelte", "Svelte"]
1694
- ]);
1897
+ const manifests = paths.filter((file) => manifestKind(file));
1898
+ const units = manifests.map((file) => resolveUnit(projectRoot, file, container.members)).filter(Boolean).filter((unit) => container.members.length === 0 || unit.declared || unit.path === ".").filter((unit, index, all) => all.findIndex((candidate) => candidate.path === unit.path && candidate.kind === unit.kind) === index).sort((left, right) => left.path.localeCompare(right.path));
1899
+ const unitFiles = paths.filter((file) => units.length === 0 || units.some((unit) => belongsToUnit(file, unit)));
1900
+ const ownedFiles = unitFiles.filter((file) => !isVendoredAsset(file));
1695
1901
  const languageEvidence = /* @__PURE__ */ new Map();
1696
- for (const file of paths) {
1902
+ for (const file of ownedFiles) {
1697
1903
  const language = extensionLanguages.get(path5.extname(file).toLowerCase());
1698
1904
  if (!language) continue;
1699
1905
  const evidence = languageEvidence.get(language) ?? [];
@@ -1701,58 +1907,77 @@ var analyzeProject = ({ root }) => {
1701
1907
  languageEvidence.set(language, evidence);
1702
1908
  }
1703
1909
  const languages = [...languageEvidence.keys()].sort();
1704
- const frameworkPackages = /* @__PURE__ */ new Map([
1705
- ["next", "Next.js"],
1706
- ["react", "React"],
1707
- ["vue", "Vue"],
1708
- ["@angular/core", "Angular"],
1709
- ["svelte", "Svelte"],
1710
- ["@sveltejs/kit", "SvelteKit"],
1711
- ["express", "Express"],
1712
- ["fastify", "Fastify"],
1713
- ["nestjs", "NestJS"],
1714
- ["@nestjs/core", "NestJS"],
1715
- ["vitest", "Vitest"],
1716
- ["jest", "Jest"],
1717
- ["playwright", "Playwright"],
1718
- ["@playwright/test", "Playwright"],
1719
- ["cypress", "Cypress"]
1720
- ]);
1721
- const frameworks = unique([...frameworkPackages].filter(([name]) => dependencies.has(name)).map(([, label]) => label));
1722
- const frameworkEvidence = [...frameworkPackages].filter(([name]) => dependencies.has(name)).map(([name]) => `package.json#${name}`);
1723
- const scripts = manifest?.scripts ?? {};
1724
- const commandOrder = ["install", "setup", "dev", "start", "build", "lint", "typecheck", "check", "test", "test:unit", "test:e2e"];
1725
- const commands = [];
1726
- for (const name of commandOrder) {
1727
- if (typeof scripts[name] === "string") commands.push({ name, command: packageCommand(manager, name), source: `package.json#scripts.${name}` });
1910
+ const toolchainMap = /* @__PURE__ */ new Map();
1911
+ for (const unit of units) {
1912
+ const detector = manifestKinds.find((item) => item.id === unit.kind);
1913
+ const current = toolchainMap.get(detector.toolchain) ?? [];
1914
+ current.push(unit.manifest);
1915
+ toolchainMap.set(detector.toolchain, current);
1916
+ }
1917
+ const lockToolchains = [
1918
+ ["pnpm-lock.yaml", "pnpm"],
1919
+ ["yarn.lock", "Yarn"],
1920
+ ["bun.lock", "Bun"],
1921
+ ["bun.lockb", "Bun"],
1922
+ ["package-lock.json", "npm"],
1923
+ ["uv.lock", "uv"],
1924
+ ["poetry.lock", "Poetry"],
1925
+ ["Pipfile.lock", "Pipenv"],
1926
+ ["Cargo.lock", "Cargo"],
1927
+ ["go.sum", "Go modules"],
1928
+ ["composer.lock", "Composer"],
1929
+ ["Gemfile.lock", "Bundler"]
1930
+ ];
1931
+ for (const [file, name] of lockToolchains) if (pathSet.has(file)) toolchainMap.set(name, unique([...toolchainMap.get(name) ?? [], file]));
1932
+ const packagesConfig = ownedFiles.find((file) => path5.basename(file).toLowerCase() === "packages.config");
1933
+ if (packagesConfig) toolchainMap.set("NuGet", unique([...toolchainMap.get("NuGet") ?? [], packagesConfig]));
1934
+ const rootPackage = pathSet.has("package.json") ? readJson(path5.join(projectRoot, "package.json")) : null;
1935
+ if (rootPackage?.packageManager) toolchainMap.set(rootPackage.packageManager.split("@")[0], unique([...toolchainMap.get(rootPackage.packageManager.split("@")[0]) ?? [], "package.json#packageManager"]));
1936
+ const toolchains = [...toolchainMap].map(([name, evidence]) => ({ name, evidence: unique(evidence) })).sort((left, right) => left.name.localeCompare(right.name));
1937
+ const dependencyManifests = ownedFiles.filter((file) => ["package.json", "composer.json", "packages.config", "requirements.txt", "cargo.toml", "pyproject.toml", "go.mod", "pom.xml", "build.gradle", "build.gradle.kts", "gemfile"].includes(path5.basename(file).toLowerCase()) || /\.gemspec$/i.test(file));
1938
+ const dependencies = dependencyManifests.flatMap((file) => parseDependencies(projectRoot, file));
1939
+ const technologyMap = /* @__PURE__ */ new Map();
1940
+ const addTechnology = (name, evidence) => {
1941
+ const versioned = name.match(/^(.*)\s+\d+(?:\.\d+)*$/);
1942
+ if (versioned) technologyMap.delete(versioned[1]);
1943
+ else if ([...technologyMap.keys()].some((item) => item.startsWith(`${name} `) && /^\d/.test(item.slice(name.length + 1)))) return;
1944
+ technologyMap.set(name, unique([...technologyMap.get(name) ?? [], evidence]));
1945
+ };
1946
+ for (const dependency of dependencies) {
1947
+ const mapping = dependencyTechnologies.get(dependency.name.toLowerCase());
1948
+ if (!mapping) continue;
1949
+ const label = mapping[1] === "major" && major(dependency.version) ? `${mapping[0]} ${major(dependency.version)}` : mapping[0];
1950
+ addTechnology(label, dependency.evidence);
1951
+ }
1952
+ for (const file of unitFiles) {
1953
+ if (/(^|\/)jquery-\d+(?:\.\d+)+(?:\.min)?\.js$/i.test(file)) addTechnology("jQuery", file);
1954
+ if (/(^|\/)bootstrap(?:\.min)?\.(?:css|js)$/i.test(file)) {
1955
+ const detectedMajor = readText(path5.join(projectRoot, file)).match(/\bBootstrap\s+v(\d+)/i)?.[1];
1956
+ addTechnology(detectedMajor ? `Bootstrap ${detectedMajor}` : "Bootstrap", file);
1957
+ }
1728
1958
  }
1729
- for (const name of Object.keys(scripts).sort()) {
1730
- if (!commands.some((item) => item.name === name) && /^(build|lint|typecheck|test|check)(:|$)/.test(name)) {
1731
- commands.push({ name, command: packageCommand(manager, name), source: `package.json#scripts.${name}` });
1959
+ for (const unit of units.filter((item) => item.kind === "dotnet")) {
1960
+ const content = readText(path5.join(projectRoot, unit.manifest));
1961
+ for (const match of content.matchAll(/<(TargetFrameworkVersion|TargetFramework|TargetFrameworks)>([^<]+)<\/\1>/gi)) for (const value of match[2].split(";")) {
1962
+ const label = targetFramework(value);
1963
+ if (label) addTechnology(label, `${unit.manifest}#${match[1]}`);
1732
1964
  }
1733
1965
  }
1966
+ const technologies = [...technologyMap].map(([name, evidence]) => ({ name, evidence: unique(evidence) })).sort((left, right) => left.name.localeCompare(right.name));
1967
+ const commands = [];
1968
+ const scripts = rootPackage?.scripts ?? {};
1969
+ for (const name of Object.keys(scripts).sort()) if (/^(?:build|check|dev|install|lint|setup|start|test|typecheck)(?::|$)/.test(name)) {
1970
+ const manager = rootPackage.packageManager?.split("@")[0] ?? (pathSet.has("pnpm-lock.yaml") ? "pnpm" : pathSet.has("yarn.lock") ? "yarn" : "npm");
1971
+ commands.push({ name, command: scriptCommand(manager, name), source: `package.json#scripts.${name}` });
1972
+ }
1734
1973
  const entrypoints = [];
1735
- for (const [field, value] of [["main", manifest?.main], ["module", manifest?.module]]) {
1736
- if (typeof value === "string") entrypoints.push({ path: value, source: `package.json#${field}` });
1737
- }
1738
- if (typeof manifest?.bin === "string") entrypoints.push({ path: manifest.bin, source: "package.json#bin" });
1739
- else for (const value of Object.values(manifest?.bin ?? {})) if (typeof value === "string") entrypoints.push({ path: value, source: "package.json#bin" });
1740
- for (const candidate of ["src/index.ts", "src/index.js", "src/main.ts", "src/main.js", "app/page.tsx", "app/page.jsx", "main.go", "Cargo.toml"]) {
1741
- if (pathSet.has(candidate) && !entrypoints.some((item) => item.path === candidate)) entrypoints.push({ path: candidate, source: candidate });
1742
- }
1743
- const modules = [];
1744
- const moduleRoots = unique(paths.filter((file) => extensionLanguages.has(path5.extname(file).toLowerCase())).map((file) => file.split("/")[0]).filter((name) => name && !name.startsWith(".")));
1745
- for (const name of moduleRoots.slice(0, 20)) {
1746
- const evidence = paths.filter((file) => file.startsWith(`${name}/`) && extensionLanguages.has(path5.extname(file).toLowerCase())).slice(0, 3);
1747
- modules.push({ name, path: name, evidence });
1748
- }
1749
- const testFiles = paths.filter((file) => /(^|\/)(__tests__\/|tests?\/|[^/]+\.(?:test|spec)\.[^.]+$)/.test(file));
1750
- const testConfigs = paths.filter((file) => /(^|\/)(vitest|jest|playwright|cypress)[^/]*\.(?:js|mjs|cjs|ts|json)$/.test(file));
1751
- const ciFiles = paths.filter((file) => file.startsWith(".github/workflows/") || [".gitlab-ci.yml", "Jenkinsfile", "azure-pipelines.yml"].includes(file));
1752
- const deploymentFiles = paths.filter((file) => /(^|\/)(Dockerfile|docker-compose\.ya?ml|vercel\.json|netlify\.toml|fly\.toml)$/.test(file));
1753
- const sourceFiles = paths.filter((file) => extensionLanguages.has(path5.extname(file).toLowerCase()));
1974
+ for (const [field, value] of [["main", rootPackage?.main], ["module", rootPackage?.module]]) if (typeof value === "string") entrypoints.push({ path: value, source: `package.json#${field}` });
1975
+ for (const file of ownedFiles.filter((item) => /(^|\/)(?:program\.cs|main\.(?:go|rs|py|js|ts)|global\.asax|app\/page\.(?:jsx?|tsx?))$/i.test(item)).slice(0, 30)) entrypoints.push({ path: file, source: file });
1976
+ const testUnits = units.filter((unit) => /(^|[._-])tests?([._-]|$)/i.test(unit.name) || /(^|\/)tests?([/_-]|$)/i.test(unit.path));
1977
+ const testFiles = ownedFiles.filter((file) => extensionLanguages.has(path5.extname(file).toLowerCase())).filter((file) => testUnits.some((unit) => belongsToUnit(file, unit)) || /(^|\/)(?:__tests__\/|tests?\/|[^/]+\.(?:test|spec)\.[^.]+$)/i.test(file)).filter((file) => !isVendoredAsset(file)).slice(0, 40);
1978
+ const testConfigs = paths.filter((file) => /(^|\/)(?:vitest|jest|playwright|cypress)[^/]*\.(?:js|mjs|cjs|ts|json)$/i.test(file)).slice(0, 20);
1754
1979
  const namingEvidence = /* @__PURE__ */ new Map();
1755
- for (const file of sourceFiles) {
1980
+ for (const file of ownedFiles.filter((item) => extensionLanguages.has(path5.extname(item).toLowerCase()))) {
1756
1981
  const style = classifyNaming(path5.basename(file, path5.extname(file)));
1757
1982
  if (!style) continue;
1758
1983
  const evidence = namingEvidence.get(style) ?? [];
@@ -1760,48 +1985,102 @@ var analyzeProject = ({ root }) => {
1760
1985
  namingEvidence.set(style, evidence);
1761
1986
  }
1762
1987
  const naming = [...namingEvidence].filter(([, evidence]) => evidence.length >= 3).sort((left, right) => right[1].length - left[1].length)[0];
1763
- const sourceRoots = ["src", "app", "lib", "packages", "apps", "services"].filter((directory) => paths.some((file) => file.startsWith(`${directory}/`)));
1764
- const securityEvidence = paths.filter(
1765
- (file) => !/^(?:\.agents\/context|\.codex-agent\/context|docs|templates)\//.test(file) && /(^|\/)(auth|security|permissions?|secrets?|\.env\.example)(\/|\.|$)/i.test(file)
1766
- ).slice(0, 20);
1767
- const boundaryDirectories = {
1768
- api: ["api", "routes", "controllers"],
1769
- ui: ["components", "views", "pages", "app"],
1770
- persistence: ["db", "database", "models", "repositories", "migrations"]
1771
- };
1772
- const boundaries = Object.fromEntries(Object.entries(boundaryDirectories).map(([kind, candidates]) => {
1773
- const found = candidates.filter((candidate) => paths.some((file) => file.split("/").includes(candidate)));
1988
+ const ownedRoots = unique(units.map((unit) => unit.path === "." ? "." : unit.path.split("/")[0]));
1989
+ const securityEvidence = ownedFiles.filter((file) => /(^|\/)(?:auth|security|permissions?|secrets?|\.env\.example)(\/|\.|$)/i.test(file)).slice(0, 20);
1990
+ const boundaryCandidates = { api: ["api", "routes", "controllers"], ui: ["components", "views", "pages", "app"], persistence: ["db", "database", "models", "repositories", "migrations"] };
1991
+ const boundaries = Object.fromEntries(Object.entries(boundaryCandidates).map(([kind, candidates]) => {
1992
+ const found = unique(ownedFiles.flatMap((file) => {
1993
+ const segments = file.split("/");
1994
+ const index = segments.findIndex((segment) => candidates.includes(segment.toLowerCase()));
1995
+ return index < 0 ? [] : [segments.slice(0, index + 1).join("/")];
1996
+ })).sort();
1774
1997
  return [kind, found.length ? detected(found, found.map((item) => `directory:${item}`), "medium") : unknown([])];
1775
1998
  }));
1999
+ const ciFiles = paths.filter((file) => file.startsWith(".github/workflows/") || [".gitlab-ci.yml", "Jenkinsfile", "azure-pipelines.yml"].includes(file));
2000
+ const deploymentFiles = paths.filter((file) => /(^|\/)(?:Dockerfile|docker-compose\.ya?ml|vercel\.json|netlify\.toml|fly\.toml)$/i.test(file));
2001
+ const rootSolution = rootFiles(projectRoot).find((file) => /\.sln$/i.test(file));
2002
+ const projectName = rootPackage?.name ?? (rootSolution ? path5.basename(rootSolution, path5.extname(rootSolution)) : path5.basename(projectRoot));
2003
+ const projectEvidence = rootPackage?.name ? ["package.json#name"] : rootSolution ? [rootSolution] : ["repository directory name"];
1776
2004
  return {
1777
2005
  $schema: "project-analysis.schema.json",
1778
2006
  version: ANALYSIS_VERSION,
1779
2007
  root: projectRoot,
1780
- project: manifest?.name ? detected({ name: manifest.name, private: Boolean(manifest.private) }, ["package.json#name"]) : inferred({ name: path5.basename(projectRoot) }, ["repository directory name"], "low"),
1781
- packageManager: manager ? declaredManager || lockManagers.length ? detected(manager, managerEvidence, "high") : inferred(manager, managerEvidence, "low") : unknown(null),
2008
+ project: rootPackage?.name || rootSolution ? detected({ name: projectName, private: Boolean(rootPackage?.private) }, projectEvidence) : inferred({ name: projectName }, projectEvidence, "low"),
1782
2009
  languages: languages.length ? detected(languages, [...languageEvidence.values()].flat(), "high") : unknown([]),
1783
- frameworks: frameworks.length ? detected(frameworks, frameworkEvidence, "high") : unknown([]),
2010
+ toolchains: toolchains.length ? detected(toolchains, toolchains.flatMap((item) => item.evidence), "high") : unknown([]),
2011
+ technologies: technologies.length ? detected(technologies, technologies.flatMap((item) => item.evidence), "high") : unknown([]),
1784
2012
  commands: commands.length ? detected(commands, commands.map((item) => item.source), "high") : unknown([]),
1785
- modules: modules.length ? inferred(modules, modules.flatMap((item) => item.evidence), modules.length > 1 ? "medium" : "low") : unknown([]),
2013
+ units: units.length ? detected(units, unique([...container.evidence, ...units.flatMap((item) => item.evidence)]), container.evidence.length ? "high" : "medium") : unknown([]),
1786
2014
  entrypoints: entrypoints.length ? detected(entrypoints, entrypoints.map((item) => item.source), "high") : unknown([]),
1787
2015
  conventions: {
1788
- sourceLayout: sourceRoots.length ? detected(sourceRoots, sourceRoots.map((item) => `directory:${item}`), "high") : unknown([]),
2016
+ sourceLayout: ownedRoots.length ? detected(ownedRoots, units.flatMap((item) => item.evidence), "high") : unknown([]),
1789
2017
  fileNaming: naming ? inferred(naming[0], naming[1].slice(0, 8), "medium") : unknown(null),
1790
2018
  boundaries
1791
2019
  },
1792
2020
  security: securityEvidence.length ? detected(securityEvidence, securityEvidence, "medium") : unknown([]),
1793
- testing: testFiles.length || testConfigs.length ? detected({ files: testFiles.slice(0, 30), configs: testConfigs }, [...testFiles.slice(0, 10), ...testConfigs], "high") : unknown({ files: [], configs: [] }),
2021
+ testing: testFiles.length || testConfigs.length || testUnits.length ? detected({ units: testUnits.map((unit) => unit.path), files: testFiles, configs: testConfigs }, unique([...testUnits.flatMap((unit) => unit.evidence), ...testFiles.slice(0, 10), ...testConfigs]), "high") : unknown({ units: [], files: [], configs: [] }),
1794
2022
  ciCd: ciFiles.length || deploymentFiles.length ? detected({ ci: ciFiles, deployment: deploymentFiles }, [...ciFiles, ...deploymentFiles], "high") : unknown({ ci: [], deployment: [] }),
1795
- existingGuidance: pathSet.has("AGENTS.md") ? detected(true, ["AGENTS.md"], "high") : detected(false, ["AGENTS.md not found"], "high")
2023
+ scan: detected({ files: paths.length, truncated: inventory.truncated, limit: inventory.limit, excluded: inventory.excluded.slice(0, 100) }, unique(["repository directory name", ...parseIgnoreRules(projectRoot).map((rule) => rule.source), ...container.evidence]), inventory.truncated ? "medium" : "high"),
2024
+ existingGuidance: fs5.existsSync(path5.join(projectRoot, "AGENTS.md")) ? detected(true, ["AGENTS.md"], "high") : detected(false, ["AGENTS.md not found"], "high")
1796
2025
  };
1797
2026
  };
2027
+
2028
+ // ../../plugins/codex-agent/scripts/context-project.mjs
2029
+ var CONTEXT_LIFECYCLE_VERSION = 1;
2030
+ var HASH_PATTERN2 = /^[a-f0-9]{64}$/;
2031
+ var LIFECYCLE_PHASES = /* @__PURE__ */ new Set(["prepared", "migration-applied", "managed-running", "managed-applied"]);
2032
+ var MANAGED_CONTEXT = [
2033
+ ["architecture", "architecture/system.md", "System architecture, project units, entrypoints, and detected boundaries.", ["architecture", "units", "entrypoints"], "high", ["units", "entrypoints", "conventions.boundaries"]],
2034
+ ["code-quality", "standards/code-quality.md", "Detected source layout, naming, and engineering conventions.", ["code", "quality", "conventions"], "critical", ["conventions", "languages"]],
2035
+ ["testing", "standards/testing.md", "Detected test tooling, locations, and repository commands.", ["test", "verification", "commands"], "high", ["testing", "commands"]],
2036
+ ["security", "standards/security.md", "Detected security-sensitive boundaries and baseline safeguards.", ["security", "auth", "secrets"], "critical", ["security"]],
2037
+ ["project-intelligence", "project-intelligence/project.md", "Detected stack, toolchains, CI, and project intelligence.", ["project", "stack", "ci"], "medium", ["project", "languages", "toolchains", "technologies", "ciCd", "scan"]]
2038
+ ];
2039
+ var CODEX_AGENT_IGNORE_RULES = [
2040
+ ".codex-agent/analysis.json",
2041
+ ".codex-agent/sessions/",
2042
+ ".codex-agent/backups/",
2043
+ ".codex-agent/.locks/",
2044
+ ".codex-agent/.transactions/",
2045
+ ".codex-agent/**/*.tmp-*"
2046
+ ];
2047
+ var BROAD_CODEX_AGENT_IGNORE_RULES = /* @__PURE__ */ new Set([
2048
+ ".codex-agent",
2049
+ ".codex-agent/",
2050
+ ".codex-agent/**",
2051
+ "/.codex-agent",
2052
+ "/.codex-agent/",
2053
+ "/.codex-agent/**"
2054
+ ]);
2055
+ var MANAGED_CODEX_AGENT_IGNORE_RULES = new Set(CODEX_AGENT_IGNORE_RULES);
2056
+ var EXCLUDABLE_MANAGED_FILES = /* @__PURE__ */ new Set([".codex/config.toml", ".gitignore"]);
2057
+ var slash3 = (value) => value.split(path6.sep).join("/");
2058
+ var unique2 = (items) => [...new Set(items.filter(Boolean))];
2059
+ var relative2 = (root, file) => slash3(path6.relative(root, file));
2060
+ var normalizeManagedExclusions = (items = []) => {
2061
+ if (!Array.isArray(items)) throw new Error("Managed exclusions must be an array");
2062
+ const normalized = unique2(items.map((item) => safeRelativePath(item, "Managed exclusion"))).sort();
2063
+ const unsupported = normalized.filter((item) => !EXCLUDABLE_MANAGED_FILES.has(item));
2064
+ if (unsupported.length) {
2065
+ throw new Error(`Unsupported managed exclusion: ${unsupported.join(", ")}. Supported paths: ${[...EXCLUDABLE_MANAGED_FILES].sort().join(", ")}`);
2066
+ }
2067
+ return normalized;
2068
+ };
2069
+ var readJson2 = (file) => {
2070
+ try {
2071
+ return JSON.parse(fs6.readFileSync(file, "utf8"));
2072
+ } catch {
2073
+ return null;
2074
+ }
2075
+ };
2076
+ var analyzeProject = analyzeRepository;
1798
2077
  var isSignal = (value) => value && typeof value === "object" && "value" in value && Array.isArray(value.evidence);
1799
2078
  var validateProjectAnalysis = (analysis) => {
1800
2079
  const errors = [];
1801
2080
  if (!analysis || typeof analysis !== "object" || Array.isArray(analysis)) return { ok: false, errors: ["analysis must be an object"] };
1802
2081
  if (analysis.version !== ANALYSIS_VERSION) errors.push(`version must be ${ANALYSIS_VERSION}`);
1803
2082
  if (typeof analysis.root !== "string" || !analysis.root) errors.push("root must be a non-empty string");
1804
- for (const field of ["project", "packageManager", "languages", "frameworks", "commands", "modules", "entrypoints", "security", "testing", "ciCd", "existingGuidance"]) {
2083
+ for (const field of ["project", "languages", "toolchains", "technologies", "commands", "units", "entrypoints", "security", "testing", "ciCd", "scan", "existingGuidance"]) {
1805
2084
  if (!isSignal(analysis[field])) errors.push(`${field} must contain value, evidence, confidence, and status`);
1806
2085
  }
1807
2086
  if (!analysis.conventions || typeof analysis.conventions !== "object") errors.push("conventions must be an object");
@@ -1827,7 +2106,7 @@ var validateProjectAnalysis = (analysis) => {
1827
2106
  return { ok: errors.length === 0, errors };
1828
2107
  };
1829
2108
  var validateAnalysisEvidence = (analysis, root) => {
1830
- const projectRoot = path5.resolve(root);
2109
+ const projectRoot = path6.resolve(root);
1831
2110
  const errors = [];
1832
2111
  const visit = (value, location) => {
1833
2112
  if (isSignal(value)) {
@@ -1836,17 +2115,17 @@ var validateAnalysisEvidence = (analysis, root) => {
1836
2115
  if (item === "repository directory name") continue;
1837
2116
  const missing = item.endsWith(" not found");
1838
2117
  const raw = (missing ? item.slice(0, -10) : item).replace(/^directory:/, "").split("#")[0];
1839
- if (!raw || path5.isAbsolute(raw)) {
2118
+ if (!raw || path6.isAbsolute(raw)) {
1840
2119
  errors.push(`${location} has invalid evidence: ${item}`);
1841
2120
  continue;
1842
2121
  }
1843
- const target = path5.resolve(projectRoot, raw);
1844
- if (target !== projectRoot && !target.startsWith(`${projectRoot}${path5.sep}`)) {
2122
+ const target = path6.resolve(projectRoot, raw);
2123
+ if (target !== projectRoot && !target.startsWith(`${projectRoot}${path6.sep}`)) {
1845
2124
  errors.push(`${location} evidence escapes the project: ${item}`);
1846
2125
  } else {
1847
2126
  try {
1848
2127
  assertNoSymlink(projectRoot, target, `${location} evidence`);
1849
- if (missing ? fs5.existsSync(target) : !fs5.existsSync(target)) {
2128
+ if (missing ? fs6.existsSync(target) : !fs6.existsSync(target)) {
1850
2129
  errors.push(`${location} evidence does not match the repository: ${item}`);
1851
2130
  }
1852
2131
  } catch (error) {
@@ -1920,6 +2199,10 @@ var renderAgents = (analysis) => {
1920
2199
  if (present(analysis.commands)) {
1921
2200
  parts.push(section("Repository commands", bullets(analysis.commands.value.map((item) => `${mdCode(item.command)} \u2014 ${safeText(item.name)} (${safeText(item.source)})`))));
1922
2201
  }
2202
+ const stack = [];
2203
+ if (present(analysis.languages)) stack.push(`Languages: ${analysis.languages.value.join(", ")}.${evidenceSuffix(analysis.languages)}`);
2204
+ if (present(analysis.technologies)) stack.push(`Technologies: ${analysis.technologies.value.map((item) => safeText(item.name)).join(", ")}.${evidenceSuffix(analysis.technologies)}`);
2205
+ if (stack.length) parts.push(section("Detected stack", bullets(stack)));
1923
2206
  const conventions = [];
1924
2207
  if (present(analysis.conventions?.sourceLayout)) conventions.push(`Source roots: ${analysis.conventions.sourceLayout.value.map((item) => mdCode(`${item}/`)).join(", ")}.${evidenceSuffix(analysis.conventions.sourceLayout)}`);
1925
2208
  if (present(analysis.conventions?.fileNaming)) conventions.push(`Observed file naming: ${mdCode(analysis.conventions.fileNaming.value)}. Apply it only where nearby files confirm the pattern.${evidenceSuffix(analysis.conventions.fileNaming)}`);
@@ -1937,7 +2220,7 @@ var renderAgents = (analysis) => {
1937
2220
  };
1938
2221
  var renderArchitecture = (analysis) => {
1939
2222
  const parts = [];
1940
- if (present(analysis.modules)) parts.push(section("Observed modules", bullets(analysis.modules.value.map((item) => `${mdCode(`${item.path}/`)} (${item.evidence.slice(0, 2).map(safeText).join(", ")})`))));
2223
+ if (present(analysis.units)) parts.push(section("Project units", bullets(analysis.units.value.map((item) => `${mdCode(item.path === "." ? "." : `${item.path}/`)} \u2014 ${safeText(item.name)} (${safeText(item.kind)}; ${item.evidence.slice(0, 2).map(safeText).join(", ")})`))));
1941
2224
  if (present(analysis.entrypoints)) parts.push(section("Entrypoints", bullets(analysis.entrypoints.value.map((item) => `${mdCode(item.path)} from ${safeText(item.source)}`))));
1942
2225
  const boundaries = Object.entries(analysis.conventions?.boundaries ?? {}).filter(([, item]) => present(item));
1943
2226
  if (boundaries.length) parts.push(section("Observed boundaries", bullets(boundaries.map(([kind, item]) => `${kind}: ${item.value.map((value) => mdCode(`${value}/`)).join(", ")}${evidenceSuffix(item)}`))));
@@ -1956,6 +2239,7 @@ var renderTesting = (analysis) => {
1956
2239
  if (testCommands.length) parts.push(section("Commands", bullets(testCommands.map((item) => `${mdCode(item.command)} (${safeText(item.source)})`))));
1957
2240
  }
1958
2241
  if (present(analysis.testing)) {
2242
+ if (analysis.testing.value.units?.length) parts.push(section("Test units", bullets(analysis.testing.value.units.map(mdCode))));
1959
2243
  if (analysis.testing.value.configs.length) parts.push(section("Configuration", bullets(analysis.testing.value.configs.map(mdCode))));
1960
2244
  if (analysis.testing.value.files.length) parts.push(section("Observed tests", bullets(analysis.testing.value.files.slice(0, 20).map(mdCode))));
1961
2245
  }
@@ -1972,15 +2256,16 @@ var renderSecurity = (analysis) => {
1972
2256
  };
1973
2257
  var renderProject = (analysis) => {
1974
2258
  const stack = [];
1975
- if (present(analysis.packageManager)) stack.push(`Package manager: ${mdCode(analysis.packageManager.value)}.${evidenceSuffix(analysis.packageManager)}`);
1976
2259
  if (present(analysis.languages)) stack.push(`Languages: ${analysis.languages.value.join(", ")}.${evidenceSuffix(analysis.languages)}`);
1977
- if (present(analysis.frameworks)) stack.push(`Frameworks/tooling: ${analysis.frameworks.value.join(", ")}.${evidenceSuffix(analysis.frameworks)}`);
2260
+ if (present(analysis.toolchains)) stack.push(`Toolchains: ${analysis.toolchains.value.map((item) => safeText(item.name)).join(", ")}.${evidenceSuffix(analysis.toolchains)}`);
2261
+ if (present(analysis.technologies)) stack.push(`Technologies: ${analysis.technologies.value.map((item) => safeText(item.name)).join(", ")}.${evidenceSuffix(analysis.technologies)}`);
1978
2262
  const parts = [];
1979
2263
  if (stack.length) parts.push(section("Detected stack", bullets(stack)));
1980
2264
  if (present(analysis.ciCd)) {
1981
2265
  const items = [...analysis.ciCd.value.ci.map((item) => `CI: ${mdCode(item)}`), ...analysis.ciCd.value.deployment.map((item) => `Deployment: ${mdCode(item)}`)];
1982
2266
  parts.push(section("CI and deployment", bullets(items)));
1983
2267
  }
2268
+ if (present(analysis.scan) && analysis.scan.value.truncated) parts.push(section("Discovery limits", `Repository scan reached its ${analysis.scan.value.limit}-file budget; review a refined analysis before applying context.`));
1984
2269
  return parts.join("\n\n") || "No stack or CI facts were detected with sufficient evidence.";
1985
2270
  };
1986
2271
  var markdownBlock = (id, body) => `<!-- codex-agent:managed:start ${id} -->
@@ -2014,22 +2299,22 @@ var collectSignalEvidence = (value) => {
2014
2299
  ];
2015
2300
  };
2016
2301
  var managedEvidence = (analysis, id, signalPaths) => {
2017
- const projectRoot = path5.resolve(analysis.root);
2018
- const locators = unique(signalPaths.flatMap((signalPath) => collectSignalEvidence(nestedValue(analysis, signalPath)))).map((locator) => String(locator).split("#")[0]).filter((locator) => locator && !path5.isAbsolute(locator));
2019
- return unique(locators).flatMap((locator) => {
2020
- const target = path5.resolve(projectRoot, locator);
2021
- const relativePath = relative(projectRoot, target);
2022
- if (relativePath.startsWith("../") || path5.isAbsolute(relativePath) || relativePath.startsWith(".codex-agent/context/") || relativePath.startsWith(".agents/context/")) return [];
2302
+ const projectRoot = path6.resolve(analysis.root);
2303
+ const locators = unique2(signalPaths.flatMap((signalPath) => collectSignalEvidence(nestedValue(analysis, signalPath)))).map((locator) => String(locator).split("#")[0]).filter((locator) => locator && !path6.isAbsolute(locator));
2304
+ return unique2(locators).flatMap((locator) => {
2305
+ const target = path6.resolve(projectRoot, locator);
2306
+ const relativePath = relative2(projectRoot, target);
2307
+ if (relativePath.startsWith("../") || path6.isAbsolute(relativePath) || relativePath.startsWith(".codex-agent/context/") || relativePath.startsWith(".agents/context/")) return [];
2023
2308
  try {
2024
2309
  assertNoSymlink(projectRoot, target, `Managed context evidence ${locator}`);
2025
- if (!fs5.existsSync(target) || !fs5.statSync(target).isFile()) return [];
2026
- return [{ type: "repository", locator: relativePath, note: `Repository evidence used to generate ${id}.`, sha256: sha256(fs5.readFileSync(target)) }];
2310
+ if (!fs6.existsSync(target) || !fs6.statSync(target).isFile()) return [];
2311
+ return [{ type: "repository", locator: relativePath, note: `Repository evidence used to generate ${id}.`, sha256: sha256(fs6.readFileSync(target)) }];
2027
2312
  } catch {
2028
2313
  return [];
2029
2314
  }
2030
2315
  }).slice(0, 20);
2031
2316
  };
2032
- var renderProjectFiles = (analysis, existingIndex = null) => {
2317
+ var renderProjectFiles = (analysis, existingIndex = null, { excludeManaged = [] } = {}) => {
2033
2318
  if (containsSensitiveContent(JSON.stringify({ analysis, existingIndex }))) {
2034
2319
  throw new Error("Project context rendering input appears to contain a secret or credential");
2035
2320
  }
@@ -2073,6 +2358,7 @@ var renderProjectFiles = (analysis, existingIndex = null) => {
2073
2358
  };
2074
2359
  files.set(".codex-agent/context/index.json", { kind: "json", content: `${JSON.stringify(index, null, 2)}
2075
2360
  ` });
2361
+ for (const file of normalizeManagedExclusions(excludeManaged)) files.delete(file);
2076
2362
  for (const [file, descriptor] of files) {
2077
2363
  if (containsSensitiveContent(descriptor.content ?? descriptor.body ?? "")) {
2078
2364
  throw new Error(`Generated managed content for ${file} appears to contain a secret or credential`);
@@ -2130,13 +2416,13 @@ var lineDiff = (before, after) => {
2130
2416
  const added = newLines.slice(prefix, newLines.length - suffix).map((line) => `+ ${line}`);
2131
2417
  return [`@@ line ${prefix + 1} @@`, ...removed, ...added].join("\n");
2132
2418
  };
2133
- var analysisPathFor = (root) => path5.join(root, ".codex-agent", "analysis.json");
2419
+ var analysisPathFor = (root) => path6.join(root, ".codex-agent", "analysis.json");
2134
2420
  var readProjectText = (projectRoot, target, label) => {
2135
2421
  assertInside(projectRoot, target, label);
2136
2422
  assertNoSymlink(projectRoot, target, label);
2137
- if (!fs5.existsSync(target)) return null;
2138
- if (!fs5.lstatSync(target).isFile()) throw new Error(`${label} is not a file: ${relative(projectRoot, target)}`);
2139
- const content = fs5.readFileSync(target, "utf8");
2423
+ if (!fs6.existsSync(target)) return null;
2424
+ if (!fs6.lstatSync(target).isFile()) throw new Error(`${label} is not a file: ${relative2(projectRoot, target)}`);
2425
+ const content = fs6.readFileSync(target, "utf8");
2140
2426
  if (containsSensitiveContent(content)) throw new Error(`${label} appears to contain a secret or credential`);
2141
2427
  return content;
2142
2428
  };
@@ -2157,8 +2443,8 @@ var readableCatalog = (root) => {
2157
2443
  };
2158
2444
  var catalogIndex = (catalog) => {
2159
2445
  if (catalog?.index && typeof catalog.index === "object") return catalog.index;
2160
- const indexPath = catalog?.indexPath ?? (catalog?.root ? path5.join(catalog.root, "index.json") : null);
2161
- return indexPath && fs5.existsSync(indexPath) ? readJson(indexPath) : null;
2446
+ const indexPath = catalog?.indexPath ?? (catalog?.root ? path6.join(catalog.root, "index.json") : null);
2447
+ return indexPath && fs6.existsSync(indexPath) ? readJson2(indexPath) : null;
2162
2448
  };
2163
2449
  var migrationChanges = (migration) => (migration?.changes ?? []).map((change) => typeof change === "string" ? { path: change, status: "migrate", phase: "catalog-migration" } : {
2164
2450
  ...change,
@@ -2167,7 +2453,7 @@ var migrationChanges = (migration) => (migration?.changes ?? []).map((change) =>
2167
2453
  phase: "catalog-migration"
2168
2454
  });
2169
2455
  var uniqueStrings = (items) => [...new Set(items.filter((item) => typeof item === "string" && item))];
2170
- var planManagedFiles = ({ projectRoot, analysis, existingIndex, force, contextBaselineRoot = null }) => {
2456
+ var planManagedFiles = ({ projectRoot, analysis, existingIndex, force, excludeManaged = [], contextBaselineRoot = null }) => {
2171
2457
  const ownershipConflicts = managedIndexOwnershipConflicts(existingIndex);
2172
2458
  if (ownershipConflicts.length) {
2173
2459
  return {
@@ -2180,7 +2466,7 @@ var planManagedFiles = ({ projectRoot, analysis, existingIndex, force, contextBa
2180
2466
  writePlan: []
2181
2467
  };
2182
2468
  }
2183
- const rendered = renderProjectFiles(analysis, existingIndex);
2469
+ const rendered = renderProjectFiles(analysis, existingIndex, { excludeManaged });
2184
2470
  for (const [file, descriptor] of rendered) {
2185
2471
  if (containsSensitiveContent(descriptor.content ?? descriptor.body ?? "")) {
2186
2472
  throw new Error(`Generated managed content for ${file} appears to contain a secret or credential`);
@@ -2189,12 +2475,12 @@ var planManagedFiles = ({ projectRoot, analysis, existingIndex, force, contextBa
2189
2475
  const changes = [];
2190
2476
  const conflicts = [];
2191
2477
  const writePlan = [];
2192
- const canonicalContextRoot = path5.join(projectRoot, ".codex-agent", "context");
2478
+ const canonicalContextRoot = path6.join(projectRoot, ".codex-agent", "context");
2193
2479
  for (const [file, descriptor] of rendered) {
2194
- const destination = path5.join(projectRoot, file);
2195
- const contextRelative = path5.relative(canonicalContextRoot, destination);
2196
- const usesContextBaseline = contextBaselineRoot !== null && contextRelative !== "" && contextRelative !== ".." && !contextRelative.startsWith(`..${path5.sep}`) && !path5.isAbsolute(contextRelative);
2197
- const currentPath = usesContextBaseline ? path5.join(contextBaselineRoot, contextRelative) : destination;
2480
+ const destination = path6.join(projectRoot, file);
2481
+ const contextRelative = path6.relative(canonicalContextRoot, destination);
2482
+ const usesContextBaseline = contextBaselineRoot !== null && contextRelative !== "" && contextRelative !== ".." && !contextRelative.startsWith(`..${path6.sep}`) && !path6.isAbsolute(contextRelative);
2483
+ const currentPath = usesContextBaseline ? path6.join(contextBaselineRoot, contextRelative) : destination;
2198
2484
  const current = readProjectText(projectRoot, currentPath, `Managed project file ${file}`);
2199
2485
  const merged = replaceManaged(current, descriptor, force);
2200
2486
  if (merged.conflict && !force) {
@@ -2210,7 +2496,7 @@ var planManagedFiles = ({ projectRoot, analysis, existingIndex, force, contextBa
2210
2496
  };
2211
2497
  var applyManagedFiles = ({ projectRoot, writePlan, analysis, analysisCurrent, catalogPreconditions = [], lock = true }) => {
2212
2498
  const analysisPath = analysisPathFor(projectRoot);
2213
- const analysisRelative = relative(projectRoot, analysisPath);
2499
+ const analysisRelative = relative2(projectRoot, analysisPath);
2214
2500
  const preconditionMap = new Map(catalogPreconditions.map((item) => [item.path, item.expected]));
2215
2501
  for (const { file, current } of writePlan) preconditionMap.set(file, current);
2216
2502
  preconditionMap.set(analysisRelative, analysisCurrent);
@@ -2252,7 +2538,7 @@ var snapshotCatalogPreconditions = ({ projectRoot, expected }) => {
2252
2538
  const current = assertCatalogPrecondition({ projectRoot, expected });
2253
2539
  if (current.state !== "canonical-only") return [];
2254
2540
  const preconditions = listTreeFiles(current.canonical.path).map((item) => ({
2255
- path: relative(projectRoot, item.absolute),
2541
+ path: relative2(projectRoot, item.absolute),
2256
2542
  expected: readProjectText(projectRoot, item.absolute, `Canonical context file ${item.relative}`)
2257
2543
  }));
2258
2544
  assertCatalogPrecondition({ projectRoot, expected });
@@ -2261,7 +2547,7 @@ var snapshotCatalogPreconditions = ({ projectRoot, expected }) => {
2261
2547
  var assertManagedPreconditions = ({ projectRoot, writePlan, analysisCurrent, expectedCatalog }) => {
2262
2548
  assertCatalogPrecondition({ projectRoot, expected: expectedCatalog });
2263
2549
  for (const { file, current } of writePlan) {
2264
- const destination = path5.join(projectRoot, file);
2550
+ const destination = path6.join(projectRoot, file);
2265
2551
  if (readProjectText(projectRoot, destination, `Managed project file ${file}`) !== current) {
2266
2552
  throw new Error(`Context managed-file precondition changed after preview: ${file}`);
2267
2553
  }
@@ -2282,10 +2568,11 @@ var managedPlanContract = (plan) => ({
2282
2568
  conflicts: [...plan.conflicts]
2283
2569
  });
2284
2570
  var managedPlanHash = (plan) => sha256(JSON.stringify(managedPlanContract(plan)));
2285
- var lifecyclePlanHash = ({ operation, force, lifecycleCatalog, migrationPreview, analysis, analysisCurrent, analysisContent, plan, conflicts }) => sha256(JSON.stringify({
2571
+ var lifecyclePlanHash = ({ operation, force, excludedManaged, lifecycleCatalog, migrationPreview, analysis, analysisCurrent, analysisContent, plan, conflicts }) => sha256(JSON.stringify({
2286
2572
  version: CONTEXT_LIFECYCLE_VERSION,
2287
2573
  operation,
2288
2574
  force,
2575
+ excludedManaged,
2289
2576
  catalog: {
2290
2577
  state: lifecycleCatalog.state,
2291
2578
  canonicalHash: lifecycleCatalog.canonical.hash,
@@ -2298,41 +2585,41 @@ var lifecyclePlanHash = ({ operation, force, lifecycleCatalog, migrationPreview,
2298
2585
  managed: managedPlanContract(plan),
2299
2586
  conflicts
2300
2587
  }));
2301
- var lifecycleTransactionRoot = (projectRoot) => path5.join(projectRoot, ".codex-agent", ".transactions");
2588
+ var lifecycleTransactionRoot = (projectRoot) => path6.join(projectRoot, ".codex-agent", ".transactions");
2302
2589
  var lifecycleFileHash = ({ projectRoot, relativePath, label }) => {
2303
2590
  const normalized = safeRelativePath(relativePath, label);
2304
- const target = path5.join(projectRoot, ...normalized.split("/"));
2591
+ const target = path6.join(projectRoot, ...normalized.split("/"));
2305
2592
  assertInside(projectRoot, target, label);
2306
2593
  assertNoSymlink(projectRoot, target, label);
2307
- if (!fs5.existsSync(target)) return null;
2308
- if (!fs5.lstatSync(target).isFile()) throw new Error(`${label} is not a regular file: ${normalized}`);
2309
- return sha256(fs5.readFileSync(target));
2594
+ if (!fs6.existsSync(target)) return null;
2595
+ if (!fs6.lstatSync(target).isFile()) throw new Error(`${label} is not a regular file: ${normalized}`);
2596
+ return sha256(fs6.readFileSync(target));
2310
2597
  };
2311
2598
  var writeNewLifecycleJson = (destination, value) => {
2312
- const temporary = path5.join(path5.dirname(destination), `.${path5.basename(destination)}.tmp-${crypto4.randomUUID()}`);
2599
+ const temporary = path6.join(path6.dirname(destination), `.${path6.basename(destination)}.tmp-${crypto4.randomUUID()}`);
2313
2600
  let descriptor;
2314
2601
  try {
2315
- descriptor = fs5.openSync(temporary, "wx", 384);
2316
- fs5.writeFileSync(descriptor, `${JSON.stringify(value, null, 2)}
2602
+ descriptor = fs6.openSync(temporary, "wx", 384);
2603
+ fs6.writeFileSync(descriptor, `${JSON.stringify(value, null, 2)}
2317
2604
  `);
2318
- fs5.fsyncSync(descriptor);
2319
- fs5.closeSync(descriptor);
2605
+ fs6.fsyncSync(descriptor);
2606
+ fs6.closeSync(descriptor);
2320
2607
  descriptor = void 0;
2321
- fs5.renameSync(temporary, destination);
2608
+ fs6.renameSync(temporary, destination);
2322
2609
  } catch (error) {
2323
2610
  if (descriptor !== void 0) try {
2324
- fs5.closeSync(descriptor);
2611
+ fs6.closeSync(descriptor);
2325
2612
  } catch {
2326
2613
  }
2327
2614
  try {
2328
- if (fs5.existsSync(temporary)) fs5.unlinkSync(temporary);
2615
+ if (fs6.existsSync(temporary)) fs6.unlinkSync(temporary);
2329
2616
  } catch {
2330
2617
  }
2331
2618
  throw error;
2332
2619
  }
2333
2620
  };
2334
2621
  var validateLifecycleManifest = ({ projectRoot, transactionRoot, manifest }) => {
2335
- const transactionId2 = path5.basename(transactionRoot);
2622
+ const transactionId2 = path6.basename(transactionRoot);
2336
2623
  if (!manifest || typeof manifest !== "object" || Array.isArray(manifest) || manifest.version !== CONTEXT_LIFECYCLE_VERSION || manifest.type !== "context-lifecycle" || manifest.transactionId !== transactionId2 || manifest.operation !== "context.init" || manifest.phase !== "prepared" || manifest.revision !== 1 || !HASH_PATTERN2.test(manifest.planHash ?? "")) {
2337
2624
  throw new Error(`Invalid context lifecycle recovery manifest: ${transactionId2}`);
2338
2625
  }
@@ -2375,16 +2662,16 @@ var lifecyclePhase = ({ projectRoot, transactionRoot, transactionId: transaction
2375
2662
  let phase = "prepared";
2376
2663
  let missingEarlier = false;
2377
2664
  for (const candidate of ["migration-applied", "managed-running", "managed-applied"]) {
2378
- const markerPath = path5.join(transactionRoot, `${candidate}.json`);
2665
+ const markerPath = path6.join(transactionRoot, `${candidate}.json`);
2379
2666
  assertNoSymlink(projectRoot, markerPath, `Lifecycle phase marker ${candidate}`);
2380
- if (!fs5.existsSync(markerPath)) {
2667
+ if (!fs6.existsSync(markerPath)) {
2381
2668
  missingEarlier = true;
2382
2669
  continue;
2383
2670
  }
2384
2671
  if (missingEarlier) throw new Error(`Lifecycle phase markers are not contiguous: ${transactionId2}`);
2385
2672
  let marker;
2386
2673
  try {
2387
- marker = JSON.parse(fs5.readFileSync(markerPath, "utf8"));
2674
+ marker = JSON.parse(fs6.readFileSync(markerPath, "utf8"));
2388
2675
  } catch {
2389
2676
  throw new Error(`Invalid lifecycle phase marker: ${candidate}`);
2390
2677
  }
@@ -2396,13 +2683,13 @@ var lifecyclePhase = ({ projectRoot, transactionRoot, transactionId: transaction
2396
2683
  return phase;
2397
2684
  };
2398
2685
  var readLifecycleJournal = ({ projectRoot, transactionRoot }) => {
2399
- const manifestPath = path5.join(transactionRoot, "manifest.json");
2686
+ const manifestPath = path6.join(transactionRoot, "manifest.json");
2400
2687
  assertNoSymlink(projectRoot, manifestPath, "Lifecycle recovery manifest");
2401
2688
  let manifest;
2402
2689
  try {
2403
- manifest = JSON.parse(fs5.readFileSync(manifestPath, "utf8"));
2690
+ manifest = JSON.parse(fs6.readFileSync(manifestPath, "utf8"));
2404
2691
  } catch {
2405
- throw new Error(`Invalid context lifecycle recovery manifest: ${path5.basename(transactionRoot)}`);
2692
+ throw new Error(`Invalid context lifecycle recovery manifest: ${path6.basename(transactionRoot)}`);
2406
2693
  }
2407
2694
  const normalized = validateLifecycleManifest({ projectRoot, transactionRoot, manifest });
2408
2695
  return {
@@ -2418,7 +2705,7 @@ var markLifecyclePhase = (journal, phase) => {
2418
2705
  if (order.indexOf(phase) !== order.indexOf(journal.phase) + 1) {
2419
2706
  throw new Error(`Invalid lifecycle phase transition: ${journal.phase} -> ${phase}`);
2420
2707
  }
2421
- writeNewLifecycleJson(path5.join(journal.transactionRoot, `${phase}.json`), {
2708
+ writeNewLifecycleJson(path6.join(journal.transactionRoot, `${phase}.json`), {
2422
2709
  version: CONTEXT_LIFECYCLE_VERSION,
2423
2710
  transactionId: journal.normalized.transactionId,
2424
2711
  phase
@@ -2431,7 +2718,7 @@ var managedLifecycleItems = ({ plan, projectRoot, analysis, analysisCurrent }) =
2431
2718
  beforeHash: current === null ? null : sha256(current),
2432
2719
  afterHash: sha256(merged.content)
2433
2720
  }));
2434
- const analysisPath = relative(projectRoot, analysisPathFor(projectRoot));
2721
+ const analysisPath = relative2(projectRoot, analysisPathFor(projectRoot));
2435
2722
  items.push({
2436
2723
  path: analysisPath,
2437
2724
  beforeHash: analysisCurrent === null ? null : sha256(analysisCurrent),
@@ -2443,9 +2730,9 @@ var managedLifecycleItems = ({ plan, projectRoot, analysis, analysisCurrent }) =
2443
2730
  var prepareLifecycleJournal = ({ projectRoot, lifecycleCatalog, planHash, plan, analysis, analysisCurrent }) => {
2444
2731
  const id = `lifecycle-${process.pid}-${Date.now()}-${crypto4.randomUUID()}`;
2445
2732
  const transactionsRoot = ensureDirectory(projectRoot, lifecycleTransactionRoot(projectRoot), "Context transaction directory");
2446
- const transactionRoot = path5.join(transactionsRoot, id);
2447
- const preparationRoot = path5.join(transactionsRoot, `.lifecycle-tmp-${id}`);
2448
- fs5.mkdirSync(preparationRoot, { recursive: false });
2733
+ const transactionRoot = path6.join(transactionsRoot, id);
2734
+ const preparationRoot = path6.join(transactionsRoot, `.lifecycle-tmp-${id}`);
2735
+ fs6.mkdirSync(preparationRoot, { recursive: false });
2449
2736
  const manifest = {
2450
2737
  version: CONTEXT_LIFECYCLE_VERSION,
2451
2738
  type: "context-lifecycle",
@@ -2468,12 +2755,12 @@ var prepareLifecycleJournal = ({ projectRoot, lifecycleCatalog, planHash, plan,
2468
2755
  managed: managedLifecycleItems({ plan, projectRoot, analysis, analysisCurrent })
2469
2756
  };
2470
2757
  try {
2471
- writeNewLifecycleJson(path5.join(preparationRoot, "manifest.json"), manifest);
2472
- fs5.renameSync(preparationRoot, transactionRoot);
2758
+ writeNewLifecycleJson(path6.join(preparationRoot, "manifest.json"), manifest);
2759
+ fs6.renameSync(preparationRoot, transactionRoot);
2473
2760
  return readLifecycleJournal({ projectRoot, transactionRoot });
2474
2761
  } catch (error) {
2475
- fs5.rmSync(preparationRoot, { recursive: true, force: true });
2476
- fs5.rmSync(transactionRoot, { recursive: true, force: true });
2762
+ fs6.rmSync(preparationRoot, { recursive: true, force: true });
2763
+ fs6.rmSync(transactionRoot, { recursive: true, force: true });
2477
2764
  throw error;
2478
2765
  }
2479
2766
  };
@@ -2489,38 +2776,38 @@ var inspectLifecycleCatalog = ({ projectRoot, relativePath, expectedHash, label,
2489
2776
  return inspected;
2490
2777
  };
2491
2778
  var cleanupLifecycleJournal = ({ projectRoot, journal }) => {
2492
- const catalogTransactionRoot = path5.join(projectRoot, ...journal.normalized.paths.catalogTransaction.split("/"));
2779
+ const catalogTransactionRoot = path6.join(projectRoot, ...journal.normalized.paths.catalogTransaction.split("/"));
2493
2780
  assertInside(projectRoot, catalogTransactionRoot, "Lifecycle catalog transaction directory");
2494
2781
  assertNoSymlink(projectRoot, catalogTransactionRoot, "Lifecycle catalog transaction directory");
2495
- if (fs5.existsSync(catalogTransactionRoot)) fs5.rmSync(catalogTransactionRoot, { recursive: true, force: true });
2782
+ if (fs6.existsSync(catalogTransactionRoot)) fs6.rmSync(catalogTransactionRoot, { recursive: true, force: true });
2496
2783
  assertNoSymlink(projectRoot, journal.transactionRoot, "Lifecycle transaction directory");
2497
- fs5.rmSync(journal.transactionRoot, { recursive: true, force: true });
2784
+ fs6.rmSync(journal.transactionRoot, { recursive: true, force: true });
2498
2785
  };
2499
2786
  var rollbackLifecycleMigration = ({ projectRoot, journal }) => {
2500
2787
  const { initial, paths } = journal.normalized;
2501
- const canonical = path5.join(projectRoot, ...paths.canonical.split("/"));
2502
- const legacy = path5.join(projectRoot, ...paths.legacy.split("/"));
2503
- const backup = path5.join(projectRoot, ...paths.backup.split("/"));
2504
- const quarantine = path5.join(journal.transactionRoot, "canonical-rollback");
2788
+ const canonical = path6.join(projectRoot, ...paths.canonical.split("/"));
2789
+ const legacy = path6.join(projectRoot, ...paths.legacy.split("/"));
2790
+ const backup = path6.join(projectRoot, ...paths.backup.split("/"));
2791
+ const quarantine = path6.join(journal.transactionRoot, "canonical-rollback");
2505
2792
  if (initial.state === "legacy-only") {
2506
- if (fs5.existsSync(canonical)) {
2793
+ if (fs6.existsSync(canonical)) {
2507
2794
  inspectLifecycleCatalog({ projectRoot, relativePath: paths.canonical, expectedHash: initial.sourceHash, label: "Promoted canonical catalog" });
2508
- if (fs5.existsSync(quarantine)) throw new Error("Lifecycle canonical rollback quarantine already exists");
2509
- fs5.renameSync(canonical, quarantine);
2510
- } else if (fs5.existsSync(quarantine)) {
2511
- const quarantinePath = relative(projectRoot, quarantine);
2795
+ if (fs6.existsSync(quarantine)) throw new Error("Lifecycle canonical rollback quarantine already exists");
2796
+ fs6.renameSync(canonical, quarantine);
2797
+ } else if (fs6.existsSync(quarantine)) {
2798
+ const quarantinePath = relative2(projectRoot, quarantine);
2512
2799
  inspectLifecycleCatalog({ projectRoot, relativePath: quarantinePath, expectedHash: initial.sourceHash, label: "Quarantined canonical catalog" });
2513
2800
  }
2514
2801
  } else {
2515
2802
  inspectLifecycleCatalog({ projectRoot, relativePath: paths.canonical, expectedHash: initial.sourceHash, label: "Original canonical catalog" });
2516
2803
  }
2517
- if (!fs5.existsSync(legacy)) {
2804
+ if (!fs6.existsSync(legacy)) {
2518
2805
  inspectLifecycleCatalog({ projectRoot, relativePath: paths.backup, expectedHash: initial.sourceHash, label: "Legacy catalog backup" });
2519
- ensureDirectory(projectRoot, path5.dirname(legacy), "Legacy context parent");
2520
- fs5.renameSync(backup, legacy);
2806
+ ensureDirectory(projectRoot, path6.dirname(legacy), "Legacy context parent");
2807
+ fs6.renameSync(backup, legacy);
2521
2808
  } else {
2522
2809
  inspectLifecycleCatalog({ projectRoot, relativePath: paths.legacy, expectedHash: initial.sourceHash, label: "Legacy context catalog" });
2523
- if (fs5.existsSync(backup)) throw new Error("Lifecycle recovery found both the legacy catalog and its migration backup");
2810
+ if (fs6.existsSync(backup)) throw new Error("Lifecycle recovery found both the legacy catalog and its migration backup");
2524
2811
  }
2525
2812
  const restored = resolveContextCatalog({ root: projectRoot });
2526
2813
  if (restored.state !== initial.state || restored.legacy.hash !== initial.sourceHash) {
@@ -2561,26 +2848,75 @@ var recoverLifecycleJournal = ({ projectRoot, journal }) => {
2561
2848
  var pendingLifecycleDirectories = (projectRoot) => {
2562
2849
  const transactionsRoot = lifecycleTransactionRoot(projectRoot);
2563
2850
  assertNoSymlink(projectRoot, transactionsRoot, "Context transaction directory");
2564
- if (!fs5.existsSync(transactionsRoot)) return [];
2565
- return fs5.readdirSync(transactionsRoot, { withFileTypes: true }).filter((entry) => entry.name.startsWith("lifecycle-")).sort((left, right) => left.name.localeCompare(right.name)).map((entry) => {
2851
+ if (!fs6.existsSync(transactionsRoot)) return [];
2852
+ return fs6.readdirSync(transactionsRoot, { withFileTypes: true }).filter((entry) => entry.name.startsWith("lifecycle-")).sort((left, right) => left.name.localeCompare(right.name)).map((entry) => {
2566
2853
  if (entry.isSymbolicLink() || !entry.isDirectory()) throw new Error(`Invalid lifecycle recovery entry: ${entry.name}`);
2567
- return path5.join(transactionsRoot, entry.name);
2854
+ return path6.join(transactionsRoot, entry.name);
2568
2855
  });
2569
2856
  };
2570
2857
  var recoverPendingLifecycleTransactions = (projectRoot) => pendingLifecycleDirectories(projectRoot).map((transactionRoot) => {
2571
2858
  const journal = readLifecycleJournal({ projectRoot, transactionRoot });
2572
2859
  return { transactionId: journal.normalized.transactionId, outcome: recoverLifecycleJournal({ projectRoot, journal }) };
2573
2860
  });
2861
+ var approvalPlanFor = ({ operation, projectRoot, analysis, excludedManaged, changes, conflicts, planHash, applied }) => {
2862
+ const activeChanges = changes.filter((change) => change.status !== "unchanged").map(({ path: file, status, phase }) => ({
2863
+ path: file,
2864
+ action: status,
2865
+ phase
2866
+ }));
2867
+ const unchanged = changes.filter((change) => change.status === "unchanged").map((change) => change.path);
2868
+ const names = (signal2) => Array.isArray(signal2?.value) ? signal2.value.map((item) => typeof item === "string" ? item : item?.name).filter(Boolean) : [];
2869
+ const testUnits = Array.isArray(analysis.testing?.value?.units) ? analysis.testing.value.units : [];
2870
+ const scan = analysis.scan?.value ?? {};
2871
+ return {
2872
+ title: operation === "context.init" ? "Initialize managed project context" : "Refresh managed project context",
2873
+ outcome: operation === "context.init" ? "Create the first evidence-backed managed guidance and canonical context catalog." : "Reconcile managed guidance and canonical context with current repository evidence.",
2874
+ status: conflicts.length ? "conflicts-require-resolution" : applied ? "applied" : "approval-required",
2875
+ target: projectRoot,
2876
+ analysis: {
2877
+ project: analysis.project?.value?.name ?? path6.basename(projectRoot),
2878
+ languages: names(analysis.languages),
2879
+ toolchains: names(analysis.toolchains),
2880
+ technologies: names(analysis.technologies),
2881
+ units: Array.isArray(analysis.units?.value) ? analysis.units.value.length : 0,
2882
+ testUnits: testUnits.length,
2883
+ scan: {
2884
+ files: Number.isSafeInteger(scan.files) ? scan.files : null,
2885
+ truncated: Boolean(scan.truncated),
2886
+ excluded: Array.isArray(scan.excluded) ? scan.excluded.map((item) => item.path).filter(Boolean) : []
2887
+ }
2888
+ },
2889
+ changes: activeChanges,
2890
+ preserved: {
2891
+ unchanged,
2892
+ excludedManaged: [...excludedManaged],
2893
+ manualContent: "Content outside Codex-managed markers remains unchanged."
2894
+ },
2895
+ conflicts: [...conflicts],
2896
+ safeguards: [
2897
+ "Updated managed files are backed up during apply.",
2898
+ "Repository or catalog drift invalidates this plan before any write."
2899
+ ],
2900
+ residualRisks: [
2901
+ ...scan.truncated ? ["Repository discovery reached its scan limit, so the plan may omit facts beyond that boundary."] : [],
2902
+ "Automated discovery can omit behavior that is not evidenced by repository files."
2903
+ ],
2904
+ approval: conflicts.length ? "Resolve the listed conflicts and review a new plan before approval." : applied ? "The reviewed plan was applied successfully." : "Approve this displayed plan or request changes. The integrity ID is retained by Codex and does not need to be copied.",
2905
+ integrityId: planHash
2906
+ };
2907
+ };
2574
2908
  var runContextLifecycleOperation = ({
2575
2909
  operation,
2576
2910
  root,
2577
2911
  apply = false,
2578
2912
  force = false,
2913
+ excludeManaged = [],
2579
2914
  analysis: suppliedAnalysis = null,
2580
2915
  expectedPlanHash = null,
2581
2916
  lockHeld = false
2582
2917
  }) => {
2583
- const projectRoot = fs5.realpathSync(path5.resolve(root));
2918
+ const projectRoot = fs6.realpathSync(path6.resolve(root));
2919
+ const excludedManaged = normalizeManagedExclusions(excludeManaged);
2584
2920
  const isInit = operation === "context.init";
2585
2921
  const lifecycleCatalog = resolveContextCatalog({ root: projectRoot });
2586
2922
  if (isInit && lifecycleCatalog.state === "canonical-only") {
@@ -2602,9 +2938,9 @@ var runContextLifecycleOperation = ({
2602
2938
  - ${validation.errors.join("\n- ")}`);
2603
2939
  let suppliedRoot;
2604
2940
  try {
2605
- suppliedRoot = fs5.realpathSync(path5.resolve(analysis.root));
2941
+ suppliedRoot = fs6.realpathSync(path6.resolve(analysis.root));
2606
2942
  } catch {
2607
- suppliedRoot = path5.resolve(analysis.root);
2943
+ suppliedRoot = path6.resolve(analysis.root);
2608
2944
  }
2609
2945
  if (suppliedRoot !== projectRoot) throw new Error(`Analysis root does not match target root: ${analysis.root}`);
2610
2946
  const evidenceValidation = validateAnalysisEvidence(analysis, projectRoot);
@@ -2626,13 +2962,15 @@ var runContextLifecycleOperation = ({
2626
2962
  analysis,
2627
2963
  existingIndex: catalogIndex(activeCatalog),
2628
2964
  force,
2629
- contextBaselineRoot: isInit && lifecycleCatalog.state === "legacy-only" ? path5.join(projectRoot, ".agents", "context") : null
2965
+ excludeManaged: excludedManaged,
2966
+ contextBaselineRoot: isInit && lifecycleCatalog.state === "legacy-only" ? path6.join(projectRoot, ".agents", "context") : null
2630
2967
  });
2631
2968
  let conflicts = uniqueStrings([...migrationPreview.conflicts ?? [], ...plan.conflicts]);
2632
2969
  let backups = [...migrationPreview.backedUp ?? []];
2633
2970
  const planHash = lifecyclePlanHash({
2634
2971
  operation,
2635
2972
  force,
2973
+ excludedManaged,
2636
2974
  lifecycleCatalog,
2637
2975
  migrationPreview,
2638
2976
  analysis,
@@ -2670,7 +3008,7 @@ var runContextLifecycleOperation = ({
2670
3008
  expectedHash: lifecycleCatalog.legacy.hash,
2671
3009
  ...lifecycleJournal ? {
2672
3010
  backupPath: lifecycleJournal.normalized.paths.backup,
2673
- transactionId: path5.basename(lifecycleJournal.normalized.paths.catalogTransaction)
3011
+ transactionId: path6.basename(lifecycleJournal.normalized.paths.catalogTransaction)
2674
3012
  } : {},
2675
3013
  lock: !lockHeld
2676
3014
  });
@@ -2687,7 +3025,8 @@ var runContextLifecycleOperation = ({
2687
3025
  projectRoot,
2688
3026
  analysis,
2689
3027
  existingIndex: catalogIndex(activeCatalog),
2690
- force
3028
+ force,
3029
+ excludeManaged: excludedManaged
2691
3030
  });
2692
3031
  if (migration.applied && managedPlanHash(canonicalPlan) !== reviewedManagedPlanHash) {
2693
3032
  throw new Error("Context managed-file plan changed after preview; review a fresh preview");
@@ -2722,7 +3061,7 @@ var runContextLifecycleOperation = ({
2722
3061
  }
2723
3062
  }
2724
3063
  } catch (error) {
2725
- if (lifecycleJournal && fs5.existsSync(lifecycleJournal.transactionRoot)) {
3064
+ if (lifecycleJournal && fs6.existsSync(lifecycleJournal.transactionRoot)) {
2726
3065
  try {
2727
3066
  const currentJournal = readLifecycleJournal({ projectRoot, transactionRoot: lifecycleJournal.transactionRoot });
2728
3067
  if (managedTransactionApplied) recoverLifecycleJournal({ projectRoot, journal: currentJournal });
@@ -2737,29 +3076,41 @@ var runContextLifecycleOperation = ({
2737
3076
  const migrationPlan = migrationChanges(apply ? migration : migrationPreview);
2738
3077
  const managedChanges = plan.changes.map((change) => ({ ...change, phase: "managed-context" }));
2739
3078
  const analysisChange = {
2740
- path: relative(projectRoot, analysisPath),
3079
+ path: relative2(projectRoot, analysisPath),
2741
3080
  status: analysisCurrent === null ? "create" : analysisCurrent === analysisContent ? "unchanged" : "update",
2742
3081
  diff: analysisCurrent === analysisContent ? "" : lineDiff(analysisCurrent, analysisContent),
2743
3082
  phase: "analysis"
2744
3083
  };
2745
3084
  const applied = apply && conflicts.length === 0;
3085
+ const changes = [...migrationPlan, ...managedChanges, analysisChange];
2746
3086
  return {
2747
3087
  schemaVersion: CONTEXT_LIFECYCLE_VERSION,
2748
3088
  operation,
2749
3089
  root: projectRoot,
2750
3090
  mode: apply ? "apply" : "preview",
2751
- analysisPath: relative(projectRoot, analysisPathFor(projectRoot)),
3091
+ analysisPath: relative2(projectRoot, analysisPathFor(projectRoot)),
2752
3092
  analysis,
2753
- changes: [...migrationPlan, ...managedChanges, analysisChange],
3093
+ excludedManaged,
3094
+ changes,
2754
3095
  conflicts,
2755
3096
  backups,
2756
3097
  planHash,
2757
3098
  applied,
2758
- catalogMigration: migration
3099
+ catalogMigration: migration,
3100
+ approvalPlan: approvalPlanFor({
3101
+ operation,
3102
+ projectRoot,
3103
+ analysis,
3104
+ excludedManaged,
3105
+ changes,
3106
+ conflicts,
3107
+ planHash,
3108
+ applied
3109
+ })
2759
3110
  };
2760
3111
  };
2761
3112
  var runContextLifecycle = (options) => {
2762
- const projectRoot = fs5.realpathSync(path5.resolve(options.root));
3113
+ const projectRoot = fs6.realpathSync(path6.resolve(options.root));
2763
3114
  const hasPendingLifecycle = pendingLifecycleDirectories(projectRoot).length > 0;
2764
3115
  if (!options.apply && !hasPendingLifecycle) {
2765
3116
  return runContextLifecycleOperation({ ...options, root: projectRoot, lockHeld: false });
@@ -2773,8 +3124,8 @@ var initializeContext = (options) => runContextLifecycle({ ...options, operation
2773
3124
  var refreshContext = (options) => runContextLifecycle({ ...options, operation: "context.refresh" });
2774
3125
 
2775
3126
  // ../../plugins/codex-agent/skills/context-curation/scripts/context-save.mjs
2776
- import fs6 from "node:fs";
2777
- import path6 from "node:path";
3127
+ import fs7 from "node:fs";
3128
+ import path7 from "node:path";
2778
3129
  import { pathToFileURL } from "node:url";
2779
3130
  var KINDS2 = {
2780
3131
  decision: "decisions",
@@ -2801,7 +3152,7 @@ var PROPOSAL_FIELDS = /* @__PURE__ */ new Set([
2801
3152
  "aliases",
2802
3153
  "supersedes"
2803
3154
  ]);
2804
- var unique2 = (items) => [...new Set(items)];
3155
+ var unique3 = (items) => [...new Set(items)];
2805
3156
  var safeText2 = (value, limit = 300) => String(value).replace(/[\r\n]+/g, " ").replace(/`/g, "'").trim().slice(0, limit);
2806
3157
  var mdCode2 = (value) => `\`${safeText2(value)}\``;
2807
3158
  var normalizeForComparison = (value) => String(value).toLowerCase().replace(/\s+/g, " ").trim();
@@ -2822,16 +3173,16 @@ var firstParagraph = (content, fallback) => {
2822
3173
  return candidate.length >= 10 ? candidate : String(fallback).replace(/\s+/g, " ").slice(0, 240);
2823
3174
  };
2824
3175
  var listMarkdown = (root) => {
2825
- if (!fs6.existsSync(root)) return [];
3176
+ if (!fs7.existsSync(root)) return [];
2826
3177
  return listTreeFiles(root).filter((entry) => entry.relative.toLowerCase().endsWith(".md")).map((entry) => entry.absolute);
2827
3178
  };
2828
3179
  var prepareContextIndex = ({ root, pendingDocuments = [] }) => {
2829
3180
  const writable = assertWritableContextCatalog({ root });
2830
3181
  const projectRoot = writable.root;
2831
3182
  const contextRoot = writable.contextRoot;
2832
- if (!fs6.existsSync(contextRoot) && pendingDocuments.length === 0) throw new Error(`Context directory not found: ${contextRoot}`);
3183
+ if (!fs7.existsSync(contextRoot) && pendingDocuments.length === 0) throw new Error(`Context directory not found: ${contextRoot}`);
2833
3184
  assertNoSymlink(projectRoot, contextRoot, "Canonical context root");
2834
- const indexPath = path6.join(contextRoot, "index.json");
3185
+ const indexPath = path7.join(contextRoot, "index.json");
2835
3186
  const prior = upgradeContextIndex(writable.index);
2836
3187
  const priorByPath = new Map(prior.entries.map((entry) => [entry.path, entry]));
2837
3188
  const pending = /* @__PURE__ */ new Map();
@@ -2840,36 +3191,36 @@ var prepareContextIndex = ({ root, pendingDocuments = [] }) => {
2840
3191
  throw new Error(`Pending context document ${index2} is invalid`);
2841
3192
  }
2842
3193
  assertSafeMarkdownContent(document.content, `Pending context document ${index2}`);
2843
- const target = path6.resolve(contextRoot, document.path);
3194
+ const target = path7.resolve(contextRoot, document.path);
2844
3195
  assertInside(contextRoot, target, `Pending context document ${index2}`);
2845
- const relative2 = slash(path6.relative(contextRoot, target));
2846
- if (!relative2 || relative2.startsWith("../") || relative2 === "index.json" || !relative2.endsWith(".md")) {
3196
+ const relative3 = slash(path7.relative(contextRoot, target));
3197
+ if (!relative3 || relative3.startsWith("../") || relative3 === "index.json" || !relative3.endsWith(".md")) {
2847
3198
  throw new Error(`Pending context document ${index2} has an invalid path: ${document.path}`);
2848
3199
  }
2849
- pending.set(relative2, document.content);
2850
- }
2851
- const diskPaths = listMarkdown(contextRoot).map((file) => slash(path6.relative(contextRoot, file)));
2852
- const entries = [.../* @__PURE__ */ new Set([...diskPaths, ...pending.keys()])].sort().map((relative2) => {
2853
- const content2 = pending.has(relative2) ? pending.get(relative2) : fs6.readFileSync(path6.join(contextRoot, ...relative2.split("/")), "utf8");
2854
- assertSafeMarkdownContent(content2, `Context Markdown ${relative2}`);
2855
- const title = firstHeading(content2, path6.basename(relative2, ".md"));
2856
- const existing = priorByPath.get(relative2);
2857
- const tags = unique2([
2858
- ...relative2.replace(/\.md$/, "").split("/"),
3200
+ pending.set(relative3, document.content);
3201
+ }
3202
+ const diskPaths = listMarkdown(contextRoot).map((file) => slash(path7.relative(contextRoot, file)));
3203
+ const entries = [.../* @__PURE__ */ new Set([...diskPaths, ...pending.keys()])].sort().map((relative3) => {
3204
+ const content2 = pending.has(relative3) ? pending.get(relative3) : fs7.readFileSync(path7.join(contextRoot, ...relative3.split("/")), "utf8");
3205
+ assertSafeMarkdownContent(content2, `Context Markdown ${relative3}`);
3206
+ const title = firstHeading(content2, path7.basename(relative3, ".md"));
3207
+ const existing = priorByPath.get(relative3);
3208
+ const tags = unique3([
3209
+ ...relative3.replace(/\.md$/, "").split("/"),
2859
3210
  ...title.toLowerCase().split(/[^a-z0-9_-]+/).filter((term) => term.length > 2)
2860
3211
  ].map(slug).filter(Boolean)).slice(0, 10);
2861
3212
  return upgradeContextIndexEntry({
2862
3213
  ...existing,
2863
- id: existing?.id || slug(relative2.replace(/\.md$/, "").replaceAll("/", "-")),
2864
- path: relative2,
3214
+ id: existing?.id || slug(relative3.replace(/\.md$/, "").replaceAll("/", "-")),
3215
+ path: relative3,
2865
3216
  summary: existing?.summary || firstParagraph(content2, `${title} project context.`),
2866
3217
  tags: existing?.tags?.length ? existing.tags : tags,
2867
3218
  priority: existing?.priority || "medium"
2868
3219
  });
2869
3220
  }).sort((left, right) => left.path.localeCompare(right.path));
2870
- const schemaPath = path6.join(projectRoot, "schemas", "context-index.schema.json");
3221
+ const schemaPath = path7.join(projectRoot, "schemas", "context-index.schema.json");
2871
3222
  const index = {
2872
- ...fs6.existsSync(schemaPath) ? { $schema: "../../schemas/context-index.schema.json" } : prior.$schema ? { $schema: prior.$schema } : {},
3223
+ ...fs7.existsSync(schemaPath) ? { $schema: "../../schemas/context-index.schema.json" } : prior.$schema ? { $schema: prior.$schema } : {},
2873
3224
  version: 2,
2874
3225
  entries
2875
3226
  };
@@ -2887,7 +3238,7 @@ var buildContextIndex = ({ root, dryRun = false }) => {
2887
3238
  root: prepared.projectRoot,
2888
3239
  documents: [],
2889
3240
  indexContent: prepared.content,
2890
- backupPaths: fs6.existsSync(prepared.path) ? ["index.json"] : [],
3241
+ backupPaths: fs7.existsSync(prepared.path) ? ["index.json"] : [],
2891
3242
  lock: false
2892
3243
  });
2893
3244
  return { path: prepared.path, index: prepared.index, content: prepared.content, dryRun: false };
@@ -2932,7 +3283,7 @@ var validateContextProposal = (proposal, { root } = {}) => {
2932
3283
  } catch {
2933
3284
  errors.push(`evidence[${index}].url must be an absolute https URL`);
2934
3285
  }
2935
- } else if (typeof item.path !== "string" || !item.path || item.path.length > 300 || path6.isAbsolute(item.path)) {
3286
+ } else if (typeof item.path !== "string" || !item.path || item.path.length > 300 || path7.isAbsolute(item.path)) {
2936
3287
  errors.push(`evidence[${index}].path must be repository-relative`);
2937
3288
  }
2938
3289
  if (typeof item.note !== "string" || item.note.trim().length < 5 || item.note.length > 300) errors.push(`evidence[${index}].note is invalid`);
@@ -2950,11 +3301,11 @@ var validateContextProposal = (proposal, { root } = {}) => {
2950
3301
  if (combined.includes("codex-agent:context:start") || combined.includes("codex-agent:context:end")) errors.push("proposal must not contain managed marker text");
2951
3302
  if (containsSensitiveContent(combined)) errors.push("proposal appears to contain a secret or credential");
2952
3303
  if (root && Array.isArray(proposal.evidence)) {
2953
- const projectRoot = fs6.realpathSync(path6.resolve(root));
3304
+ const projectRoot = fs7.realpathSync(path7.resolve(root));
2954
3305
  for (const [index, item] of proposal.evidence.entries()) {
2955
- if (!item || item.type === "external" || typeof item.path !== "string" || path6.isAbsolute(item.path)) continue;
2956
- const target = path6.resolve(projectRoot, item.path);
2957
- if (target !== projectRoot && !target.startsWith(`${projectRoot}${path6.sep}`)) {
3306
+ if (!item || item.type === "external" || typeof item.path !== "string" || path7.isAbsolute(item.path)) continue;
3307
+ const target = path7.resolve(projectRoot, item.path);
3308
+ if (target !== projectRoot && !target.startsWith(`${projectRoot}${path7.sep}`)) {
2958
3309
  errors.push(`evidence[${index}].path escapes the repository`);
2959
3310
  continue;
2960
3311
  }
@@ -2964,11 +3315,11 @@ var validateContextProposal = (proposal, { root } = {}) => {
2964
3315
  errors.push(`evidence[${index}].path must not traverse a symbolic link`);
2965
3316
  continue;
2966
3317
  }
2967
- const relative2 = slash(path6.relative(projectRoot, target));
2968
- if (relative2 === ".codex-agent/context" || relative2.startsWith(".codex-agent/context/") || relative2 === ".agents/context" || relative2.startsWith(".agents/context/") || relative2.startsWith(".codex-agent/sessions/") || relative2.startsWith(".codex-agent/backups/")) {
3318
+ const relative3 = slash(path7.relative(projectRoot, target));
3319
+ if (relative3 === ".codex-agent/context" || relative3.startsWith(".codex-agent/context/") || relative3 === ".agents/context" || relative3.startsWith(".agents/context/") || relative3.startsWith(".codex-agent/sessions/") || relative3.startsWith(".codex-agent/backups/")) {
2969
3320
  errors.push(`evidence[${index}].path must reference primary repository evidence, not derived context`);
2970
- } else if (!fs6.existsSync(target)) errors.push(`evidence[${index}].path does not exist: ${item.path}`);
2971
- else if (!fs6.statSync(target).isFile()) errors.push(`evidence[${index}].path must be a file`);
3321
+ } else if (!fs7.existsSync(target)) errors.push(`evidence[${index}].path does not exist: ${item.path}`);
3322
+ else if (!fs7.statSync(target).isFile()) errors.push(`evidence[${index}].path must be a file`);
2972
3323
  }
2973
3324
  }
2974
3325
  return { ok: errors.length === 0, errors };
@@ -3000,8 +3351,8 @@ var normalizeContextProposal = (proposal) => ({
3000
3351
  confidence: proposal.confidence,
3001
3352
  ...proposal.reviewWhen?.length ? { reviewWhen: proposal.reviewWhen.map((item) => item.trim()) } : {},
3002
3353
  ...proposal.reviewAfter ? { reviewAfter: proposal.reviewAfter } : {},
3003
- ...proposal.aliases?.length ? { aliases: unique2(proposal.aliases.map((item) => item.trim())) } : {},
3004
- ...proposal.supersedes?.length ? { supersedes: unique2(proposal.supersedes) } : {}
3354
+ ...proposal.aliases?.length ? { aliases: unique3(proposal.aliases.map((item) => item.trim())) } : {},
3355
+ ...proposal.supersedes?.length ? { supersedes: unique3(proposal.supersedes) } : {}
3005
3356
  });
3006
3357
  var renderContextProposal = (proposal, { recordedAt = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10) } = {}) => {
3007
3358
  const id = `${proposal.kind}-${slug(proposal.title)}`;
@@ -3069,16 +3420,16 @@ var prepareContextProposal = ({ root, proposal, apply, update }) => {
3069
3420
  const writable = assertWritableContextCatalog({ root: projectRoot });
3070
3421
  const contextRoot = writable.contextRoot;
3071
3422
  const relativePath = `${KINDS2[normalized.kind]}/${slug(normalized.title)}.md`;
3072
- const destination = path6.join(contextRoot, ...relativePath.split("/"));
3423
+ const destination = path7.join(contextRoot, ...relativePath.split("/"));
3073
3424
  assertInside(contextRoot, destination, "context destination");
3074
3425
  assertNoSymlink(projectRoot, contextRoot, "Canonical context root");
3075
3426
  assertNoSymlink(projectRoot, destination, "Context destination");
3076
3427
  const indexPath = writable.indexPath;
3077
3428
  const index = upgradeContextIndex(writable.index);
3078
- const currentIndexContent = fs6.existsSync(indexPath) ? fs6.readFileSync(indexPath, "utf8") : null;
3429
+ const currentIndexContent = fs7.existsSync(indexPath) ? fs7.readFileSync(indexPath, "utf8") : null;
3079
3430
  const duplicate = index.entries.find((entry) => entry.path !== relativePath && !(normalized.supersedes ?? []).includes(entry.id) && (entry.id === rendered.id || normalizeForComparison(entry.summary) === normalizeForComparison(normalized.summary)));
3080
3431
  if (duplicate) throw new Error(`Duplicate context candidate: ${duplicate.path}`);
3081
- const current = fs6.existsSync(destination) ? fs6.readFileSync(destination, "utf8") : null;
3432
+ const current = fs7.existsSync(destination) ? fs7.readFileSync(destination, "utf8") : null;
3082
3433
  const merge = mergeManaged(current, rendered, update);
3083
3434
  const priorEntry = index.entries.find((entry) => entry.path === relativePath || entry.id === rendered.id);
3084
3435
  if (priorEntry && priorEntry.path !== relativePath) throw new Error(`Context id already belongs to another path: ${priorEntry.path}`);
@@ -3094,7 +3445,7 @@ var prepareContextProposal = ({ root, proposal, apply, update }) => {
3094
3445
  type: item.type,
3095
3446
  locator: item.path,
3096
3447
  note: item.note,
3097
- sha256: sha256(fs6.readFileSync(path6.resolve(projectRoot, item.path))),
3448
+ sha256: sha256(fs7.readFileSync(path7.resolve(projectRoot, item.path))),
3098
3449
  ...item.decisionId ? { decisionId: item.decisionId } : {},
3099
3450
  ...item.decidedAt ? { decidedAt: item.decidedAt } : {}
3100
3451
  });
@@ -3103,7 +3454,7 @@ var prepareContextProposal = ({ root, proposal, apply, update }) => {
3103
3454
  id: rendered.id,
3104
3455
  path: relativePath,
3105
3456
  summary: normalized.summary,
3106
- tags: unique2([normalized.kind, ...normalized.tags]).slice(0, 10),
3457
+ tags: unique3([normalized.kind, ...normalized.tags]).slice(0, 10),
3107
3458
  priority: normalized.priority,
3108
3459
  kind: normalized.kind,
3109
3460
  scope: normalized.scope,
@@ -3119,7 +3470,7 @@ var prepareContextProposal = ({ root, proposal, apply, update }) => {
3119
3470
  if (supersededId === rendered.id) throw new Error("Context proposal must not supersede itself");
3120
3471
  if (!index.entries.some((entry) => entry.id === supersededId)) throw new Error(`Context proposal supersedes unknown id: ${supersededId}`);
3121
3472
  }
3122
- const supersededEntries = index.entries.map((entry) => (normalized.supersedes ?? []).includes(entry.id) ? upgradeContextIndexEntry({ ...entry, status: "superseded", supersededBy: unique2([...entry.supersededBy ?? [], rendered.id]) }) : entry);
3473
+ const supersededEntries = index.entries.map((entry) => (normalized.supersedes ?? []).includes(entry.id) ? upgradeContextIndexEntry({ ...entry, status: "superseded", supersededBy: unique3([...entry.supersededBy ?? [], rendered.id]) }) : entry);
3123
3474
  const nextIndex = {
3124
3475
  ...index.$schema ? { $schema: index.$schema } : {},
3125
3476
  version: 2,
@@ -3181,16 +3532,16 @@ var option = (args, name, fallback) => {
3181
3532
  var main = (args = process.argv.slice(2)) => {
3182
3533
  const proposalFile = option(args, "--proposal");
3183
3534
  if (!proposalFile) throw new Error("context save requires --proposal FILE");
3184
- const absolute = path6.resolve(proposalFile);
3185
- if (!fs6.existsSync(absolute)) throw new Error(`Proposal file not found: ${absolute}`);
3535
+ const absolute = path7.resolve(proposalFile);
3536
+ if (!fs7.existsSync(absolute)) throw new Error(`Proposal file not found: ${absolute}`);
3186
3537
  let proposal;
3187
3538
  try {
3188
- proposal = JSON.parse(fs6.readFileSync(absolute, "utf8"));
3539
+ proposal = JSON.parse(fs7.readFileSync(absolute, "utf8"));
3189
3540
  } catch (error) {
3190
3541
  throw new Error(`Could not parse proposal file: ${error instanceof Error ? error.message : String(error)}`);
3191
3542
  }
3192
3543
  const result = saveContextProposal({
3193
- root: path6.resolve(option(args, "--root", process.cwd())),
3544
+ root: path7.resolve(option(args, "--root", process.cwd())),
3194
3545
  proposal,
3195
3546
  apply: args.includes("--apply"),
3196
3547
  update: args.includes("--update")
@@ -3199,7 +3550,7 @@ var main = (args = process.argv.slice(2)) => {
3199
3550
  `);
3200
3551
  if (result.conflicts.length) process.exitCode = 2;
3201
3552
  };
3202
- if (process.argv[1] && path6.basename(process.argv[1]) === "context-save.mjs" && import.meta.url === pathToFileURL(process.argv[1]).href) {
3553
+ if (process.argv[1] && path7.basename(process.argv[1]) === "context-save.mjs" && import.meta.url === pathToFileURL(process.argv[1]).href) {
3203
3554
  try {
3204
3555
  main();
3205
3556
  } catch (error) {
@@ -3210,8 +3561,8 @@ if (process.argv[1] && path6.basename(process.argv[1]) === "context-save.mjs" &&
3210
3561
  }
3211
3562
 
3212
3563
  // ../../plugins/codex-agent/skills/context-curation/scripts/navigation-migrate.mjs
3213
- import fs7 from "node:fs";
3214
- import path7 from "node:path";
3564
+ import fs8 from "node:fs";
3565
+ import path8 from "node:path";
3215
3566
  import { pathToFileURL as pathToFileURL2 } from "node:url";
3216
3567
  var PRIORITIES3 = /* @__PURE__ */ new Set(["critical", "high", "medium", "low"]);
3217
3568
  var MAX_FILES = 1e3;
@@ -3219,52 +3570,52 @@ var MAX_FILE_BYTES = 512 * 1024;
3219
3570
  var MAX_TOTAL_BYTES = 20 * 1024 * 1024;
3220
3571
  var MANAGED_START = (id) => `<!-- codex-agent:migrated:start ${id} -->`;
3221
3572
  var MANAGED_END = (id) => `<!-- codex-agent:migrated:end ${id} -->`;
3222
- var unique3 = (items) => [...new Set(items.filter(Boolean))];
3573
+ var unique4 = (items) => [...new Set(items.filter(Boolean))];
3223
3574
  var safeText3 = (value, limit = 300) => String(value).replace(/[\r\n]+/g, " ").trim().slice(0, limit);
3224
- var readJson2 = (file, label) => {
3575
+ var readJson3 = (file, label) => {
3225
3576
  try {
3226
- return JSON.parse(fs7.readFileSync(file, "utf8"));
3577
+ return JSON.parse(fs8.readFileSync(file, "utf8"));
3227
3578
  } catch (error) {
3228
3579
  throw new Error(`Invalid ${label}: ${error instanceof Error ? error.message : String(error)}`);
3229
3580
  }
3230
3581
  };
3231
- var hasNavigation = (directory) => fs7.existsSync(path7.join(directory, "navigation.md")) || fs7.existsSync(path7.join(directory, "index.md"));
3582
+ var hasNavigation = (directory) => fs8.existsSync(path8.join(directory, "navigation.md")) || fs8.existsSync(path8.join(directory, "index.md"));
3232
3583
  var discoverNavigationContext = ({ source }) => {
3233
3584
  if (!source) throw new Error("navigation migration requires --from PATH");
3234
- const requested = path7.resolve(source);
3235
- if (!fs7.existsSync(requested)) throw new Error(`Migration source not found: ${requested}`);
3236
- if (!fs7.statSync(requested).isDirectory()) throw new Error("Navigation migration source must be a directory");
3237
- const sourceRoot = fs7.realpathSync(requested);
3585
+ const requested = path8.resolve(source);
3586
+ if (!fs8.existsSync(requested)) throw new Error(`Migration source not found: ${requested}`);
3587
+ if (!fs8.statSync(requested).isDirectory()) throw new Error("Navigation migration source must be a directory");
3588
+ const sourceRoot = fs8.realpathSync(requested);
3238
3589
  const candidates = [];
3239
- const configPath = path7.join(sourceRoot, ".oac.json");
3240
- if (fs7.existsSync(configPath)) {
3241
- const config = readJson2(configPath, ".oac.json");
3590
+ const configPath = path8.join(sourceRoot, ".oac.json");
3591
+ if (fs8.existsSync(configPath)) {
3592
+ const config = readJson3(configPath, ".oac.json");
3242
3593
  const configured = config?.context?.root;
3243
3594
  if (typeof configured === "string" && configured.trim()) {
3244
- if (path7.isAbsolute(configured)) throw new Error("Configured context root must be project-relative; pass a global context directory directly");
3245
- const target = path7.resolve(sourceRoot, configured);
3595
+ if (path8.isAbsolute(configured)) throw new Error("Configured context root must be project-relative; pass a global context directory directly");
3596
+ const target = path8.resolve(sourceRoot, configured);
3246
3597
  assertInside(sourceRoot, target, "Configured context root");
3247
3598
  candidates.push({ path: target, detectedBy: ".oac.json" });
3248
3599
  }
3249
3600
  }
3250
3601
  candidates.push(
3251
- { path: path7.join(sourceRoot, ".claude", "context"), detectedBy: ".claude/context" },
3252
- { path: path7.join(sourceRoot, "context"), detectedBy: "context" },
3253
- { path: path7.join(sourceRoot, ".opencode", "context"), detectedBy: ".opencode/context" },
3602
+ { path: path8.join(sourceRoot, ".claude", "context"), detectedBy: ".claude/context" },
3603
+ { path: path8.join(sourceRoot, "context"), detectedBy: "context" },
3604
+ { path: path8.join(sourceRoot, ".opencode", "context"), detectedBy: ".opencode/context" },
3254
3605
  { path: sourceRoot, detectedBy: "source directory" }
3255
3606
  );
3256
- const selected = candidates.find((candidate, index) => candidates.findIndex((item) => item.path === candidate.path) === index && fs7.existsSync(candidate.path) && fs7.statSync(candidate.path).isDirectory() && hasNavigation(candidate.path));
3607
+ const selected = candidates.find((candidate, index) => candidates.findIndex((item) => item.path === candidate.path) === index && fs8.existsSync(candidate.path) && fs8.statSync(candidate.path).isDirectory() && hasNavigation(candidate.path));
3257
3608
  if (!selected) throw new Error("Could not find a navigation-based context root. Pass the context directory directly or provide a valid .oac.json context.root");
3258
3609
  assertNoSymlink(sourceRoot, selected.path, "Context root");
3259
- const contextRoot = fs7.realpathSync(selected.path);
3610
+ const contextRoot = fs8.realpathSync(selected.path);
3260
3611
  assertInside(sourceRoot, contextRoot, "Context root");
3261
3612
  const manifestCandidates = [
3262
- path7.join(contextRoot, ".context-manifest.json"),
3263
- path7.join(path7.dirname(contextRoot), ".context-manifest.json"),
3264
- path7.join(sourceRoot, ".context-manifest.json")
3613
+ path8.join(contextRoot, ".context-manifest.json"),
3614
+ path8.join(path8.dirname(contextRoot), ".context-manifest.json"),
3615
+ path8.join(sourceRoot, ".context-manifest.json")
3265
3616
  ];
3266
- const manifestPath = unique3(manifestCandidates).find((file) => fs7.existsSync(file));
3267
- const manifest = manifestPath ? readJson2(manifestPath, ".context-manifest.json") : null;
3617
+ const manifestPath = unique4(manifestCandidates).find((file) => fs8.existsSync(file));
3618
+ const manifest = manifestPath ? readJson3(manifestPath, ".context-manifest.json") : null;
3268
3619
  return {
3269
3620
  sourceRoot,
3270
3621
  contextRoot,
@@ -3282,16 +3633,16 @@ var walkMarkdown = (root) => {
3282
3633
  const skipped = [];
3283
3634
  let totalBytes = 0;
3284
3635
  const visit = (directory) => {
3285
- for (const entry of fs7.readdirSync(directory, { withFileTypes: true })) {
3286
- const absolute = path7.join(directory, entry.name);
3636
+ for (const entry of fs8.readdirSync(directory, { withFileTypes: true })) {
3637
+ const absolute = path8.join(directory, entry.name);
3287
3638
  if (entry.isSymbolicLink()) {
3288
- skipped.push({ source: slash(path7.relative(root, absolute)), reason: "symbolic-link" });
3639
+ skipped.push({ source: slash(path8.relative(root, absolute)), reason: "symbolic-link" });
3289
3640
  continue;
3290
3641
  }
3291
3642
  if (entry.isDirectory()) visit(absolute);
3292
3643
  else if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
3293
- const size = fs7.statSync(absolute).size;
3294
- if (size > MAX_FILE_BYTES) throw new Error(`Source context file exceeds ${MAX_FILE_BYTES} bytes: ${slash(path7.relative(root, absolute))}`);
3644
+ const size = fs8.statSync(absolute).size;
3645
+ if (size > MAX_FILE_BYTES) throw new Error(`Source context file exceeds ${MAX_FILE_BYTES} bytes: ${slash(path8.relative(root, absolute))}`);
3295
3646
  totalBytes += size;
3296
3647
  if (totalBytes > MAX_TOTAL_BYTES) throw new Error(`Source context exceeds ${MAX_TOTAL_BYTES} bytes`);
3297
3648
  files.push(absolute);
@@ -3327,9 +3678,9 @@ var runtimeReferenceCount = (content) => {
3327
3678
  ];
3328
3679
  return patterns.reduce((total, pattern) => total + (content.match(pattern)?.length ?? 0), 0);
3329
3680
  };
3330
- var classify = ({ relative: relative2, content, includeNavigation, includeTemplates, includeWorkflows }) => {
3331
- const normalized = relative2.toLowerCase();
3332
- const basename = path7.posix.basename(normalized);
3681
+ var classify = ({ relative: relative3, content, includeNavigation, includeTemplates, includeWorkflows }) => {
3682
+ const normalized = relative3.toLowerCase();
3683
+ const basename = path8.posix.basename(normalized);
3333
3684
  const segments = normalized.split("/");
3334
3685
  if (!includeNavigation && (["navigation.md", "index.md"].includes(basename) || basename.endsWith("-navigation.md"))) return "navigation";
3335
3686
  if (/deprecated/i.test(content.slice(0, 1200))) return "deprecated";
@@ -3351,9 +3702,9 @@ var summary = (content, title) => {
3351
3702
  return safeText3(paragraphs[0] || `${title} migrated project context.`, 240);
3352
3703
  };
3353
3704
  var rewriteContent = (content) => content.replace(/\[([^\]]+)\]\([^)]*navigation\.md(?:#[^)]*)?\)/gi, "$1 (catalog: `.codex-agent/context/index.json`)").replace(/@?(?:\.opencode|\.claude)\/context\//g, ".codex-agent/context/migrated/").replace(/`(?:\.opencode|\.claude)\/context`/g, "`.codex-agent/context/migrated`").trim();
3354
- var entryTags = ({ relative: relative2, metadata, title }) => unique3([
3705
+ var entryTags = ({ relative: relative3, metadata, title }) => unique4([
3355
3706
  "migrated",
3356
- ...relative2.replace(/\.md$/i, "").split("/"),
3707
+ ...relative3.replace(/\.md$/i, "").split("/"),
3357
3708
  ...metadata.context ? metadata.context.split(/[\/\s]+/) : [],
3358
3709
  ...title.toLowerCase().split(/[^a-z0-9_-]+/).filter((term) => term.length > 2)
3359
3710
  ].map(slug)).slice(0, 10);
@@ -3394,50 +3745,50 @@ var prepareNavigationContext = ({
3394
3745
  const discovery = discoverNavigationContext({ source });
3395
3746
  const writableCatalog = assertWritableContextCatalog({ root: projectRoot });
3396
3747
  const contextRoot = writableCatalog.contextRoot;
3397
- const destinationRoot = path7.join(contextRoot, "migrated");
3748
+ const destinationRoot = path8.join(contextRoot, "migrated");
3398
3749
  assertNoSymlink(projectRoot, contextRoot, "Target context root");
3399
3750
  const sourceWalk = walkMarkdown(discovery.contextRoot);
3400
3751
  const sourceFiles = sourceWalk.files;
3401
3752
  const skipped = [...sourceWalk.skipped];
3402
3753
  const candidates = [];
3403
3754
  for (const sourceFile of sourceFiles) {
3404
- const relative2 = slash(path7.relative(discovery.contextRoot, sourceFile));
3405
- const original = fs7.readFileSync(sourceFile, "utf8");
3755
+ const relative3 = slash(path8.relative(discovery.contextRoot, sourceFile));
3756
+ const original = fs8.readFileSync(sourceFile, "utf8");
3406
3757
  const metadata = parseMetadata(original);
3407
- const reason = containsSensitiveContent(original) ? "sensitive-content" : classify({ relative: relative2, content: metadata.content, includeNavigation, includeTemplates, includeWorkflows });
3758
+ const reason = containsSensitiveContent(original) ? "sensitive-content" : classify({ relative: relative3, content: metadata.content, includeNavigation, includeTemplates, includeWorkflows });
3408
3759
  if (reason) {
3409
- skipped.push({ source: relative2, reason });
3760
+ skipped.push({ source: relative3, reason });
3410
3761
  continue;
3411
3762
  }
3412
3763
  const transformed = rewriteContent(metadata.content);
3413
3764
  if (!transformed) {
3414
- skipped.push({ source: relative2, reason: "empty" });
3765
+ skipped.push({ source: relative3, reason: "empty" });
3415
3766
  continue;
3416
3767
  }
3417
- const destinationRelative = `migrated/${relative2}`;
3418
- const id = slug(`migrated-${relative2.replace(/\.md$/i, "")}`);
3419
- const title = firstHeading2(transformed, path7.basename(relative2, ".md"));
3768
+ const destinationRelative = `migrated/${relative3}`;
3769
+ const id = slug(`migrated-${relative3.replace(/\.md$/i, "")}`);
3770
+ const title = firstHeading2(transformed, path8.basename(relative3, ".md"));
3420
3771
  const managed = `${MANAGED_START(id)}
3421
- <!-- source: ${relative2} -->
3772
+ <!-- source: ${relative3} -->
3422
3773
  ${transformed}
3423
3774
  ${MANAGED_END(id)}`;
3424
3775
  candidates.push({
3425
3776
  sourceFile,
3426
- source: relative2,
3777
+ source: relative3,
3427
3778
  destinationRelative,
3428
- destination: path7.join(destinationRoot, ...relative2.split("/")),
3779
+ destination: path8.join(destinationRoot, ...relative3.split("/")),
3429
3780
  id,
3430
3781
  title,
3431
3782
  summary: summary(transformed, title),
3432
- tags: entryTags({ relative: relative2, metadata, title }),
3783
+ tags: entryTags({ relative: relative3, metadata, title }),
3433
3784
  priority: PRIORITIES3.has(metadata.priority) ? metadata.priority : "medium",
3434
3785
  metadata: { context: metadata.context, version: metadata.version, updated: metadata.updated },
3435
3786
  managed
3436
3787
  });
3437
3788
  }
3438
- const indexPath = path7.join(contextRoot, "index.json");
3789
+ const indexPath = path8.join(contextRoot, "index.json");
3439
3790
  const targetIndex = upgradeContextIndex(writableCatalog.index);
3440
- const priorIndexContent = fs7.existsSync(indexPath) ? fs7.readFileSync(indexPath, "utf8") : null;
3791
+ const priorIndexContent = fs8.existsSync(indexPath) ? fs8.readFileSync(indexPath, "utf8") : null;
3441
3792
  const changes = [];
3442
3793
  const conflicts = [];
3443
3794
  const migrationEntries = [];
@@ -3450,7 +3801,7 @@ ${MANAGED_END(id)}`;
3450
3801
  for (const candidate of candidates) {
3451
3802
  assertInside(destinationRoot, candidate.destination, `Migration destination ${candidate.destinationRelative}`);
3452
3803
  assertNoSymlink(projectRoot, candidate.destination, "Migration destination");
3453
- const current = fs7.existsSync(candidate.destination) ? fs7.readFileSync(candidate.destination, "utf8") : null;
3804
+ const current = fs8.existsSync(candidate.destination) ? fs8.readFileSync(candidate.destination, "utf8") : null;
3454
3805
  const merge = mergeManaged2(current, candidate.id, candidate.managed, force);
3455
3806
  if (candidateIdOwners.get(candidate.id).size > 1) {
3456
3807
  conflicts.push(candidate.destinationRelative);
@@ -3522,14 +3873,14 @@ ${MANAGED_END(id)}`;
3522
3873
  mode: apply ? "apply" : "preview",
3523
3874
  format: "navigation-markdown",
3524
3875
  source: {
3525
- requested: path7.resolve(source),
3876
+ requested: path8.resolve(source),
3526
3877
  contextRoot: discovery.contextRoot,
3527
3878
  detectedBy: discovery.detectedBy,
3528
3879
  manifest: discovery.manifest
3529
3880
  },
3530
3881
  changes: changes.map(({ destination, before, content, backup, ...change }) => change),
3531
3882
  skipped,
3532
- conflicts: unique3(conflicts),
3883
+ conflicts: unique4(conflicts),
3533
3884
  backedUp: [],
3534
3885
  index: { path: ".codex-agent/context/index.json", entries: nextIndex.entries.length, diff: diff2(priorIndexContent, indexContent) },
3535
3886
  applied: false,
@@ -3572,7 +3923,7 @@ var option2 = (args, name, fallback) => {
3572
3923
  };
3573
3924
  var main2 = (args = process.argv.slice(2)) => {
3574
3925
  const result = migrateNavigationContext({
3575
- root: path7.resolve(option2(args, "--root", process.cwd())),
3926
+ root: path8.resolve(option2(args, "--root", process.cwd())),
3576
3927
  source: option2(args, "--from"),
3577
3928
  apply: args.includes("--apply"),
3578
3929
  force: args.includes("--force"),
@@ -3584,7 +3935,7 @@ var main2 = (args = process.argv.slice(2)) => {
3584
3935
  `);
3585
3936
  if (result.conflicts.length) process.exitCode = 2;
3586
3937
  };
3587
- if (process.argv[1] && path7.basename(process.argv[1]) === "navigation-migrate.mjs" && import.meta.url === pathToFileURL2(process.argv[1]).href) {
3938
+ if (process.argv[1] && path8.basename(process.argv[1]) === "navigation-migrate.mjs" && import.meta.url === pathToFileURL2(process.argv[1]).href) {
3588
3939
  try {
3589
3940
  main2();
3590
3941
  } catch (error) {
@@ -3595,8 +3946,8 @@ if (process.argv[1] && path7.basename(process.argv[1]) === "navigation-migrate.m
3595
3946
  }
3596
3947
 
3597
3948
  // ../../plugins/codex-agent/skills/context-lint/scripts/context-lint.mjs
3598
- import fs8 from "node:fs";
3599
- import path8 from "node:path";
3949
+ import fs9 from "node:fs";
3950
+ import path9 from "node:path";
3600
3951
  import { pathToFileURL as pathToFileURL3 } from "node:url";
3601
3952
  var healthOrder = ["conflict", "orphan", "duplicate", "insufficient-evidence", "review-due", "healthy"];
3602
3953
  var normalize = (value) => String(value ?? "").normalize("NFKC").toLocaleLowerCase("en-US").replace(/\s+/g, " ").trim();
@@ -3650,13 +4001,13 @@ var lintContext = ({ root, strict = false, now = /* @__PURE__ */ new Date() }) =
3650
4001
  add("warning", "evidence-derived-only", entry, "external evidence alone cannot establish repository-specific context", "insufficient-evidence");
3651
4002
  }
3652
4003
  for (const evidence of localEvidence) {
3653
- const target = path8.resolve(catalog.root, evidence.locator);
3654
- const relative2 = slash(path8.relative(catalog.root, target));
3655
- if (relative2.startsWith("../") || path8.isAbsolute(relative2)) {
4004
+ const target = path9.resolve(catalog.root, evidence.locator);
4005
+ const relative3 = slash(path9.relative(catalog.root, target));
4006
+ if (relative3.startsWith("../") || path9.isAbsolute(relative3)) {
3656
4007
  add("error", "evidence-escape", entry, `evidence escapes repository: ${evidence.locator}`, "insufficient-evidence");
3657
4008
  continue;
3658
4009
  }
3659
- if (relative2 === ".codex-agent/context" || relative2.startsWith(".codex-agent/context/") || relative2 === ".agents/context" || relative2.startsWith(".agents/context/")) {
4010
+ if (relative3 === ".codex-agent/context" || relative3.startsWith(".codex-agent/context/") || relative3 === ".agents/context" || relative3.startsWith(".agents/context/")) {
3660
4011
  add("warning", "evidence-derived", entry, `evidence points to derived context: ${evidence.locator}`, "insufficient-evidence");
3661
4012
  continue;
3662
4013
  }
@@ -3668,13 +4019,13 @@ var lintContext = ({ root, strict = false, now = /* @__PURE__ */ new Date() }) =
3668
4019
  continue;
3669
4020
  }
3670
4021
  try {
3671
- stat = fs8.lstatSync(target);
4022
+ stat = fs9.lstatSync(target);
3672
4023
  } catch {
3673
4024
  stat = null;
3674
4025
  }
3675
4026
  if (!stat) add("error", "evidence-missing-file", entry, `evidence file is missing: ${evidence.locator}`, "insufficient-evidence");
3676
4027
  else if (stat.isSymbolicLink() || !stat.isFile()) add("error", "evidence-invalid-file", entry, `evidence is not a regular file: ${evidence.locator}`, "insufficient-evidence");
3677
- else if (sha256(fs8.readFileSync(target)) !== evidence.sha256) add("error", "evidence-digest-mismatch", entry, `evidence changed: ${evidence.locator}`, "conflict");
4028
+ else if (sha256(fs9.readFileSync(target)) !== evidence.sha256) add("error", "evidence-digest-mismatch", entry, `evidence changed: ${evidence.locator}`, "conflict");
3678
4029
  }
3679
4030
  for (const relatedId of entry.related ?? []) {
3680
4031
  const related = catalog.index.entries.find((item) => item.id === relatedId);
@@ -3685,7 +4036,7 @@ var lintContext = ({ root, strict = false, now = /* @__PURE__ */ new Date() }) =
3685
4036
  const contentOwners = /* @__PURE__ */ new Map();
3686
4037
  for (const entry of catalog.index.entries.filter((item) => item.status !== "superseded")) {
3687
4038
  const summaryKey = normalize(entry.summary);
3688
- const contentKey = sha256(fs8.readFileSync(path8.join(catalog.contextRoot, ...entry.path.split("/"))));
4039
+ const contentKey = sha256(fs9.readFileSync(path9.join(catalog.contextRoot, ...entry.path.split("/"))));
3689
4040
  for (const [key, owners, label] of [[summaryKey, summaryOwners, "summary"], [contentKey, contentOwners, "content"]]) {
3690
4041
  const owner = owners.get(key);
3691
4042
  if (owner && !(entry.supersedes ?? []).includes(owner.id) && !(owner.supersedes ?? []).includes(entry.id)) {
@@ -3721,12 +4072,12 @@ var option3 = (args, name, fallback) => {
3721
4072
  return index >= 0 && args[index + 1] ? args[index + 1] : fallback;
3722
4073
  };
3723
4074
  var main3 = (args = process.argv.slice(2)) => {
3724
- const result = lintContext({ root: path8.resolve(option3(args, "--root", process.cwd())), strict: args.includes("--strict") });
4075
+ const result = lintContext({ root: path9.resolve(option3(args, "--root", process.cwd())), strict: args.includes("--strict") });
3725
4076
  process.stdout.write(`${JSON.stringify(result, null, 2)}
3726
4077
  `);
3727
4078
  if (!result.ok) process.exitCode = 1;
3728
4079
  };
3729
- if (process.argv[1] && path8.basename(process.argv[1]) === "context-lint.mjs" && import.meta.url === pathToFileURL3(process.argv[1]).href) {
4080
+ if (process.argv[1] && path9.basename(process.argv[1]) === "context-lint.mjs" && import.meta.url === pathToFileURL3(process.argv[1]).href) {
3730
4081
  try {
3731
4082
  main3();
3732
4083
  } catch (error) {
@@ -3738,11 +4089,11 @@ if (process.argv[1] && path8.basename(process.argv[1]) === "context-lint.mjs" &&
3738
4089
 
3739
4090
  // src/core.mjs
3740
4091
  var listFiles = (root) => {
3741
- if (!fs9.existsSync(root)) return [];
4092
+ if (!fs10.existsSync(root)) return [];
3742
4093
  const files = [];
3743
4094
  const visit = (directory) => {
3744
- for (const entry of fs9.readdirSync(directory, { withFileTypes: true })) {
3745
- const absolute = path9.join(directory, entry.name);
4095
+ for (const entry of fs10.readdirSync(directory, { withFileTypes: true })) {
4096
+ const absolute = path10.join(directory, entry.name);
3746
4097
  if (entry.isDirectory()) visit(absolute);
3747
4098
  else if (entry.isFile()) files.push(absolute);
3748
4099
  }
@@ -3752,41 +4103,41 @@ var listFiles = (root) => {
3752
4103
  };
3753
4104
  var prepareContextMigration = ({ root, source, dryRun = false, force = false }) => {
3754
4105
  if (!source) throw new Error("migrate requires --from PATH");
3755
- const projectRoot = path9.resolve(root);
3756
- const sourceRoot = path9.resolve(source);
3757
- if (!fs9.existsSync(sourceRoot)) throw new Error(`Migration source not found: ${sourceRoot}`);
3758
- const sourceFiles = (fs9.statSync(sourceRoot).isDirectory() ? listFiles(sourceRoot) : [sourceRoot]).filter((file) => file.endsWith(".md"));
4106
+ const projectRoot = path10.resolve(root);
4107
+ const sourceRoot = path10.resolve(source);
4108
+ if (!fs10.existsSync(sourceRoot)) throw new Error(`Migration source not found: ${sourceRoot}`);
4109
+ const sourceFiles = (fs10.statSync(sourceRoot).isDirectory() ? listFiles(sourceRoot) : [sourceRoot]).filter((file) => file.endsWith(".md"));
3759
4110
  if (!sourceFiles.length) throw new Error("Migration source contains no Markdown context files.");
3760
4111
  const writableCatalog = assertWritableContextCatalog({ root: projectRoot });
3761
4112
  const catalogProjectRoot = writableCatalog.root;
3762
4113
  const contextRoot = writableCatalog.contextRoot;
3763
4114
  if (!contextRoot) throw new Error("Writable context catalog did not resolve a destination root.");
3764
- const destinationRoot = path9.join(contextRoot, "imported");
4115
+ const destinationRoot = path10.join(contextRoot, "imported");
3765
4116
  const result = { imported: [], unchanged: [], conflicts: [], backedUp: [], dryRun, applied: false };
3766
4117
  const documents = [];
3767
4118
  const backupPaths = [];
3768
4119
  for (const sourceFile of sourceFiles) {
3769
- const relative2 = fs9.statSync(sourceRoot).isDirectory() ? path9.relative(sourceRoot, sourceFile) : path9.basename(sourceFile);
3770
- const destination = path9.join(destinationRoot, relative2);
3771
- const content = fs9.readFileSync(sourceFile);
4120
+ const relative3 = fs10.statSync(sourceRoot).isDirectory() ? path10.relative(sourceRoot, sourceFile) : path10.basename(sourceFile);
4121
+ const destination = path10.join(destinationRoot, relative3);
4122
+ const content = fs10.readFileSync(sourceFile);
3772
4123
  if (containsSensitiveContent(content.toString("utf8"))) {
3773
4124
  throw new Error(`Migration source appears to contain a secret or credential: ${sourceFile}`);
3774
4125
  }
3775
- if (!fs9.existsSync(destination)) {
3776
- result.imported.push(path9.relative(catalogProjectRoot, destination));
3777
- documents.push({ path: path9.relative(contextRoot, destination).split(path9.sep).join("/"), content: content.toString("utf8") });
4126
+ if (!fs10.existsSync(destination)) {
4127
+ result.imported.push(path10.relative(catalogProjectRoot, destination));
4128
+ documents.push({ path: path10.relative(contextRoot, destination).split(path10.sep).join("/"), content: content.toString("utf8") });
3778
4129
  continue;
3779
4130
  }
3780
- if (content.equals(fs9.readFileSync(destination))) {
3781
- result.unchanged.push(path9.relative(catalogProjectRoot, destination));
4131
+ if (content.equals(fs10.readFileSync(destination))) {
4132
+ result.unchanged.push(path10.relative(catalogProjectRoot, destination));
3782
4133
  continue;
3783
4134
  }
3784
4135
  if (!force) {
3785
- result.conflicts.push(path9.relative(catalogProjectRoot, destination));
4136
+ result.conflicts.push(path10.relative(catalogProjectRoot, destination));
3786
4137
  continue;
3787
4138
  }
3788
- result.imported.push(path9.relative(catalogProjectRoot, destination));
3789
- const documentPath = path9.relative(contextRoot, destination).split(path9.sep).join("/");
4139
+ result.imported.push(path10.relative(catalogProjectRoot, destination));
4140
+ const documentPath = path10.relative(contextRoot, destination).split(path10.sep).join("/");
3790
4141
  documents.push({ path: documentPath, content: content.toString("utf8") });
3791
4142
  backupPaths.push(documentPath);
3792
4143
  }
@@ -3798,7 +4149,7 @@ var prepareContextMigration = ({ root, source, dryRun = false, force = false })
3798
4149
  index,
3799
4150
  backupPaths: [
3800
4151
  ...backupPaths,
3801
- ...index && fs9.existsSync(index.path) ? ["index.json"] : []
4152
+ ...index && fs10.existsSync(index.path) ? ["index.json"] : []
3802
4153
  ]
3803
4154
  };
3804
4155
  };
@@ -3823,23 +4174,23 @@ var migrateContext = (options) => {
3823
4174
  var check = (checks, name, ok, detail) => checks.push({ name, ok: Boolean(ok), detail });
3824
4175
  var parseJson = (file) => {
3825
4176
  try {
3826
- return { value: JSON.parse(fs9.readFileSync(file, "utf8")) };
4177
+ return { value: JSON.parse(fs10.readFileSync(file, "utf8")) };
3827
4178
  } catch (error) {
3828
4179
  return { error: error instanceof Error ? error.message : String(error) };
3829
4180
  }
3830
4181
  };
3831
4182
  var diagnoseProject = ({ root }) => {
3832
- const projectRoot = path9.resolve(root);
4183
+ const projectRoot = path10.resolve(root);
3833
4184
  const checks = [];
3834
4185
  const nodeMajor = Number.parseInt(process.versions.node.split(".")[0], 10);
3835
4186
  check(checks, "node", nodeMajor >= 20, `Node.js ${process.versions.node}; requires 20 or newer`);
3836
- const manifest = path9.join(projectRoot, "plugins", "codex-agent", ".codex-plugin", "plugin.json");
3837
- const isSourceWorkspace = fs9.existsSync(manifest);
4187
+ const manifest = path10.join(projectRoot, "plugins", "codex-agent", ".codex-plugin", "plugin.json");
4188
+ const isSourceWorkspace = fs10.existsSync(manifest);
3838
4189
  check(checks, "mode", true, isSourceWorkspace ? "plugin source workspace" : "initialized consumer project");
3839
4190
  if (isSourceWorkspace) {
3840
- const marketplace = path9.join(projectRoot, ".agents", "plugins", "marketplace.json");
3841
- check(checks, "marketplace", fs9.existsSync(marketplace), marketplace);
3842
- if (fs9.existsSync(marketplace)) {
4191
+ const marketplace = path10.join(projectRoot, ".agents", "plugins", "marketplace.json");
4192
+ check(checks, "marketplace", fs10.existsSync(marketplace), marketplace);
4193
+ if (fs10.existsSync(marketplace)) {
3843
4194
  const parsed2 = parseJson(marketplace);
3844
4195
  check(checks, "marketplace-json", !parsed2.error, parsed2.error || parsed2.value.name);
3845
4196
  check(
@@ -3854,10 +4205,10 @@ var diagnoseProject = ({ root }) => {
3854
4205
  check(checks, "plugin-json", !parsed.error, parsed.error || parsed.value.name);
3855
4206
  check(checks, "plugin-name", parsed.value?.name === "codex-agent", parsed.value?.name || "missing");
3856
4207
  } else {
3857
- const config = path9.join(projectRoot, ".codex", "config.toml");
3858
- const agents = path9.join(projectRoot, ".codex", "agents");
4208
+ const config = path10.join(projectRoot, ".codex", "config.toml");
4209
+ const agents = path10.join(projectRoot, ".codex", "agents");
3859
4210
  const profiles = listFiles(agents).filter((file) => file.endsWith(".toml"));
3860
- check(checks, "project-config", fs9.existsSync(config), config);
4211
+ check(checks, "project-config", fs10.existsSync(config), config);
3861
4212
  check(checks, "project-agents", profiles.length === agentProfiles.length, `${profiles.length}/${agentProfiles.length} profiles in ${agents}`);
3862
4213
  }
3863
4214
  const resolvedCatalog = resolveContextCatalog({ root: projectRoot });
@@ -3868,38 +4219,38 @@ var diagnoseProject = ({ root }) => {
3868
4219
  } catch (error) {
3869
4220
  check(checks, "context-readable", false, error instanceof Error ? error.message : String(error));
3870
4221
  }
3871
- const contextIndex = readableCatalog2?.indexPath ?? (readableCatalog2?.root ? path9.join(readableCatalog2.root, "index.json") : null);
3872
- check(checks, "context-index", Boolean(contextIndex && fs9.existsSync(contextIndex)), contextIndex ?? "context index not found");
3873
- if (contextIndex && fs9.existsSync(contextIndex)) {
4222
+ const contextIndex = readableCatalog2?.indexPath ?? (readableCatalog2?.root ? path10.join(readableCatalog2.root, "index.json") : null);
4223
+ check(checks, "context-index", Boolean(contextIndex && fs10.existsSync(contextIndex)), contextIndex ?? "context index not found");
4224
+ if (contextIndex && fs10.existsSync(contextIndex)) {
3874
4225
  const parsed = parseJson(contextIndex);
3875
4226
  check(checks, "context-json", !parsed.error, parsed.error || `${parsed.value.entries?.length ?? 0} entries`);
3876
- const contextRoot = path9.dirname(contextIndex);
4227
+ const contextRoot = path10.dirname(contextIndex);
3877
4228
  const invalid = (parsed.value?.entries ?? []).filter((entry) => {
3878
- const target = path9.resolve(contextRoot, entry.path || "");
3879
- return !target.startsWith(`${contextRoot}${path9.sep}`) || !fs9.existsSync(target);
4229
+ const target = path10.resolve(contextRoot, entry.path || "");
4230
+ return !target.startsWith(`${contextRoot}${path10.sep}`) || !fs10.existsSync(target);
3880
4231
  });
3881
4232
  check(checks, "context-paths", invalid.length === 0, invalid.map((entry) => entry.path).join(", ") || "all paths valid");
3882
4233
  }
3883
4234
  if (isSourceWorkspace) {
3884
- const skillsRoot = path9.join(projectRoot, "plugins", "codex-agent", "skills");
3885
- const skillFiles = listFiles(skillsRoot).filter((file) => file.endsWith(`${path9.sep}SKILL.md`));
3886
- const skillDirectories = fs9.readdirSync(skillsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).length;
4235
+ const skillsRoot = path10.join(projectRoot, "plugins", "codex-agent", "skills");
4236
+ const skillFiles = listFiles(skillsRoot).filter((file) => file.endsWith(`${path10.sep}SKILL.md`));
4237
+ const skillDirectories = fs10.readdirSync(skillsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).length;
3887
4238
  check(checks, "skills", skillFiles.length > 0 && skillFiles.length === skillDirectories, `${skillFiles.length}/${skillDirectories} skill entrypoints`);
3888
- const agentRoot = path9.join(projectRoot, "plugins", "codex-agent", "agents");
4239
+ const agentRoot = path10.join(projectRoot, "plugins", "codex-agent", "agents");
3889
4240
  check(checks, "plugin-agents", listFiles(agentRoot).filter((file) => file.endsWith(".md")).length === agentProfiles.length, `${agentProfiles.length} canonical profiles in ${agentRoot}`);
3890
- const hooks = path9.join(projectRoot, "plugins", "codex-agent", "hooks", "hooks.json");
3891
- check(checks, "hooks", fs9.existsSync(hooks) && !parseJson(hooks).error, hooks);
4241
+ const hooks = path10.join(projectRoot, "plugins", "codex-agent", "hooks", "hooks.json");
4242
+ check(checks, "hooks", fs10.existsSync(hooks) && !parseJson(hooks).error, hooks);
3892
4243
  }
3893
4244
  return { root: projectRoot, ok: checks.every((item) => item.ok), checks };
3894
4245
  };
3895
4246
  var evaluateRouting = ({ root }) => {
3896
- const projectRoot = path9.resolve(root);
3897
- const suitePath = path9.join(projectRoot, "evals", "skill-routing.json");
3898
- if (!fs9.existsSync(suitePath)) throw new Error(`Routing suite not found: ${suitePath}`);
3899
- const suite = JSON.parse(fs9.readFileSync(suitePath, "utf8"));
3900
- const skillsRoot = path9.join(projectRoot, "plugins", "codex-agent", "skills");
4247
+ const projectRoot = path10.resolve(root);
4248
+ const suitePath = path10.join(projectRoot, "evals", "skill-routing.json");
4249
+ if (!fs10.existsSync(suitePath)) throw new Error(`Routing suite not found: ${suitePath}`);
4250
+ const suite = JSON.parse(fs10.readFileSync(suitePath, "utf8"));
4251
+ const skillsRoot = path10.join(projectRoot, "plugins", "codex-agent", "skills");
3901
4252
  const available = new Set(
3902
- fs9.readdirSync(skillsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name)
4253
+ fs10.readdirSync(skillsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name)
3903
4254
  );
3904
4255
  const ids = /* @__PURE__ */ new Set();
3905
4256
  const failures = [];
@@ -3937,13 +4288,13 @@ var evaluateRouting = ({ root }) => {
3937
4288
  return { ok: failures.length === 0, scenarios: suite.cases?.length ?? 0, skills: available.size, byKind, failures };
3938
4289
  };
3939
4290
  var evaluateBehaviorContracts = ({ root }) => {
3940
- const projectRoot = path9.resolve(root);
3941
- const suitePath = path9.join(projectRoot, "evals", "behavior-contracts.json");
3942
- if (!fs9.existsSync(suitePath)) throw new Error(`Behavior suite not found: ${suitePath}`);
3943
- const suite = JSON.parse(fs9.readFileSync(suitePath, "utf8"));
3944
- const skillsRoot = path9.join(projectRoot, "plugins", "codex-agent", "skills");
4291
+ const projectRoot = path10.resolve(root);
4292
+ const suitePath = path10.join(projectRoot, "evals", "behavior-contracts.json");
4293
+ if (!fs10.existsSync(suitePath)) throw new Error(`Behavior suite not found: ${suitePath}`);
4294
+ const suite = JSON.parse(fs10.readFileSync(suitePath, "utf8"));
4295
+ const skillsRoot = path10.join(projectRoot, "plugins", "codex-agent", "skills");
3945
4296
  const availableSkills = new Set(
3946
- fs9.readdirSync(skillsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name)
4297
+ fs10.readdirSync(skillsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name)
3947
4298
  );
3948
4299
  const availableAgents = new Set(agentProfiles.map((profile) => profile.name));
3949
4300
  const coveredSkills = /* @__PURE__ */ new Set();
@@ -3992,15 +4343,17 @@ Run "codex-agent context" to list context commands.`;
3992
4343
  var contextUsage = `Codex Agent context commands
3993
4344
 
3994
4345
  Usage:
3995
- codex-agent context init [--root PATH] [--analysis FILE] [--apply --plan-hash HASH] [--force] [--json]
3996
- codex-agent context refresh [--root PATH] [--analysis FILE] [--apply --plan-hash HASH] [--force] [--json]
4346
+ codex-agent context init [--root PATH] [--analysis FILE] [--exclude-managed PATH ...] [--apply --plan-hash HASH] [--force] [--json]
4347
+ codex-agent context refresh [--root PATH] [--analysis FILE] [--exclude-managed PATH ...] [--apply --plan-hash HASH] [--force] [--json]
3997
4348
  codex-agent context index [--root PATH] [--dry-run] [--json]
3998
4349
  codex-agent context lint [--root PATH] [--strict] [--json]
3999
4350
  codex-agent context save --proposal FILE [--root PATH] [--apply] [--update] [--json]
4000
4351
 
4001
- Both init and refresh preview by default. Apply the exact reviewed preview by passing --apply with its planHash.`;
4352
+ Both init and refresh show a human-readable plan by default. Approve the displayed plan; the plan hash remains an internal integrity check for apply.
4353
+ Repeat --exclude-managed for optional managed paths that must remain untouched. Supported paths: .codex/config.toml and .gitignore.`;
4002
4354
  var OPTION_KEYS = /* @__PURE__ */ new Map([
4003
4355
  ["--analysis", "analysisFile"],
4356
+ ["--exclude-managed", "excludeManaged"],
4004
4357
  ["--from", "source"],
4005
4358
  ["--plan-hash", "expectedPlanHash"],
4006
4359
  ["--proposal", "proposalFile"],
@@ -4021,16 +4374,19 @@ var parseOptions = (args, { command, options = [], flags = [] }) => {
4021
4374
  const allowedOptions = new Set(options);
4022
4375
  const allowedFlags = new Set(flags);
4023
4376
  const seen = /* @__PURE__ */ new Set();
4377
+ const repeatableOptions = /* @__PURE__ */ new Set(["--exclude-managed"]);
4024
4378
  const parsed = {};
4025
4379
  for (let index = 0; index < args.length; index += 1) {
4026
4380
  const argument = args[index];
4027
4381
  if (!argument.startsWith("-")) throw new Error(`Unexpected argument for ${command}: ${argument}`);
4028
- if (seen.has(argument)) throw new Error(`Duplicate option for ${command}: ${argument}`);
4029
- seen.add(argument);
4382
+ if (seen.has(argument) && !repeatableOptions.has(argument)) throw new Error(`Duplicate option for ${command}: ${argument}`);
4383
+ if (!repeatableOptions.has(argument)) seen.add(argument);
4030
4384
  if (allowedOptions.has(argument)) {
4031
4385
  const value = args[index + 1];
4032
4386
  if (!value || value.startsWith("-")) throw new Error(`${argument} requires a value for ${command}`);
4033
- parsed[OPTION_KEYS.get(argument)] = value;
4387
+ const key = OPTION_KEYS.get(argument);
4388
+ if (repeatableOptions.has(argument)) parsed[key] = [...parsed[key] ?? [], value];
4389
+ else parsed[key] = value;
4034
4390
  index += 1;
4035
4391
  continue;
4036
4392
  }
@@ -4040,13 +4396,13 @@ var parseOptions = (args, { command, options = [], flags = [] }) => {
4040
4396
  }
4041
4397
  throw new Error(`Unknown option for ${command}: ${argument}`);
4042
4398
  }
4043
- parsed.root = path10.resolve(parsed.root ?? process.cwd());
4399
+ parsed.root = path11.resolve(parsed.root ?? process.cwd());
4044
4400
  return parsed;
4045
4401
  };
4046
4402
  var readJsonFile = (file, label) => {
4047
- const absolute = path10.resolve(file);
4048
- if (!fs10.existsSync(absolute)) throw new Error(`${label} file not found: ${absolute}`);
4049
- return JSON.parse(fs10.readFileSync(absolute, "utf8"));
4403
+ const absolute = path11.resolve(file);
4404
+ if (!fs11.existsSync(absolute)) throw new Error(`${label} file not found: ${absolute}`);
4405
+ return JSON.parse(fs11.readFileSync(absolute, "utf8"));
4050
4406
  };
4051
4407
  var write = (value, json) => {
4052
4408
  if (json) {
@@ -4059,8 +4415,48 @@ var write = (value, json) => {
4059
4415
  else process.stdout.write(`${JSON.stringify(value, null, 2)}
4060
4416
  `);
4061
4417
  };
4418
+ var listLine = (items, empty = "None") => items.length ? items.join(", ") : empty;
4419
+ var actionLabel = (action) => ({ create: "Create", update: "Update", preserve: "Preserve", remove: "Remove", migrate: "Migrate", conflict: "Conflict" })[action] ?? action;
4420
+ var formatContextLifecyclePlan = (result) => {
4421
+ const plan = result.approvalPlan;
4422
+ if (!plan) return JSON.stringify(result, null, 2);
4423
+ const lines = [
4424
+ plan.status === "applied" ? `Context operation applied: ${plan.title}` : `Context approval plan: ${plan.title}`,
4425
+ "",
4426
+ `Outcome: ${plan.outcome}`,
4427
+ `Target: ${plan.target}`,
4428
+ `Status: ${plan.status}`,
4429
+ "",
4430
+ "Repository evidence",
4431
+ `- Project: ${plan.analysis.project}`,
4432
+ `- Languages: ${listLine(plan.analysis.languages)}`,
4433
+ `- Toolchains: ${listLine(plan.analysis.toolchains)}`,
4434
+ `- Technologies: ${listLine(plan.analysis.technologies)}`,
4435
+ `- Units: ${plan.analysis.units}; test units: ${plan.analysis.testUnits}`,
4436
+ `- Scan: ${plan.analysis.scan.files ?? "unknown"} files; ${plan.analysis.scan.truncated ? "truncated" : "complete"}`,
4437
+ "",
4438
+ "Planned changes"
4439
+ ];
4440
+ if (plan.changes.length) for (const change of plan.changes) lines.push(`- ${actionLabel(change.action)} ${change.path}`);
4441
+ else lines.push("- No file changes");
4442
+ lines.push("", "Preserved");
4443
+ lines.push(`- ${plan.preserved.manualContent}`);
4444
+ lines.push(`- ${plan.preserved.unchanged.length} managed files are unchanged.`);
4445
+ lines.push(`- Excluded managed paths: ${listLine(plan.preserved.excludedManaged)}`);
4446
+ const excluded = plan.analysis.scan.excluded;
4447
+ lines.push(`- Scan exclusions: ${listLine(excluded.slice(0, 12))}${excluded.length > 12 ? `, and ${excluded.length - 12} more` : ""}`);
4448
+ lines.push("", "Conflicts");
4449
+ if (plan.conflicts.length) for (const conflict of plan.conflicts) lines.push(`- ${conflict}`);
4450
+ else lines.push("- None");
4451
+ lines.push("", "Safeguards");
4452
+ for (const safeguard of plan.safeguards) lines.push(`- ${safeguard}`);
4453
+ lines.push("", "Residual risks");
4454
+ for (const risk of plan.residualRisks) lines.push(`- ${risk}`);
4455
+ lines.push("", `Approval: ${plan.approval}`, `Plan integrity: ${plan.integrityId}`);
4456
+ return lines.join("\n");
4457
+ };
4062
4458
  var finishWithConflicts = (result, json) => {
4063
- write(result, json);
4459
+ write(json || !result.approvalPlan ? result : formatContextLifecyclePlan(result), json);
4064
4460
  if (result.conflicts.length) process.exitCode = 2;
4065
4461
  };
4066
4462
  var main4 = async (args) => {
@@ -4089,7 +4485,7 @@ ${contextUsage}`);
4089
4485
  if (subcommand === "init" || subcommand === "refresh") {
4090
4486
  const options = parseOptions(contextArgs, {
4091
4487
  command: `context ${subcommand}`,
4092
- options: ["--root", "--analysis", "--plan-hash"],
4488
+ options: ["--root", "--analysis", "--exclude-managed", "--plan-hash"],
4093
4489
  flags: ["--apply", "--force", "--json"]
4094
4490
  });
4095
4491
  const analysis = options.analysisFile ? readJsonFile(options.analysisFile, "Analysis") : null;