@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.
- package/README.md +12 -1
- package/dist/codex-agent.mjs +866 -470
- package/package.json +1 -1
package/dist/codex-agent.mjs
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.mjs
|
|
4
|
-
import
|
|
5
|
-
import
|
|
4
|
+
import fs11 from "node:fs";
|
|
5
|
+
import path11 from "node:path";
|
|
6
6
|
|
|
7
7
|
// src/core.mjs
|
|
8
|
-
import
|
|
9
|
-
import
|
|
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
|
|
14
|
-
import
|
|
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
|
|
174
|
-
if (entry.isSymbolicLink()) throw new Error(`Refusing to traverse symbolic link: ${
|
|
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:
|
|
176
|
+
if (includeDirectories) entries.push({ absolute, relative: relative3, type: "directory" });
|
|
177
177
|
visit(absolute);
|
|
178
|
-
} else if (entry.isFile()) entries.push({ absolute, relative:
|
|
179
|
-
else throw new Error(`Unsupported filesystem entry in context catalog: ${
|
|
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
|
|
299
|
-
return
|
|
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
|
|
304
|
-
const segments =
|
|
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((
|
|
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
|
|
1201
|
-
const source = path3.join(contextRoot, ...
|
|
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, ...
|
|
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 = (
|
|
1237
|
-
const staged = path3.join(stagedRoot, ...
|
|
1238
|
-
const destination = path3.join(contextRoot, ...
|
|
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, ...
|
|
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/
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
var
|
|
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
|
|
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)
|
|
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)
|
|
1624
|
-
|
|
1704
|
+
if (files.length >= limit) {
|
|
1705
|
+
truncated = true;
|
|
1706
|
+
break;
|
|
1707
|
+
}
|
|
1625
1708
|
const absolute = path5.join(directory, entry.name);
|
|
1626
|
-
|
|
1627
|
-
|
|
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
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
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
|
|
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
|
|
1655
|
-
const
|
|
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
|
|
1658
|
-
const
|
|
1659
|
-
const
|
|
1660
|
-
const
|
|
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
|
|
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
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
[
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
["
|
|
1713
|
-
["
|
|
1714
|
-
["
|
|
1715
|
-
["
|
|
1716
|
-
["
|
|
1717
|
-
["
|
|
1718
|
-
["
|
|
1719
|
-
["
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
const
|
|
1726
|
-
|
|
1727
|
-
|
|
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
|
|
1730
|
-
|
|
1731
|
-
|
|
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",
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
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
|
|
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
|
|
1764
|
-
const securityEvidence =
|
|
1765
|
-
|
|
1766
|
-
).
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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({
|
|
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
|
-
|
|
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", "
|
|
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 =
|
|
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 ||
|
|
2118
|
+
if (!raw || path6.isAbsolute(raw)) {
|
|
1840
2119
|
errors.push(`${location} has invalid evidence: ${item}`);
|
|
1841
2120
|
continue;
|
|
1842
2121
|
}
|
|
1843
|
-
const target =
|
|
1844
|
-
if (target !== projectRoot && !target.startsWith(`${projectRoot}${
|
|
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 ?
|
|
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.
|
|
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.
|
|
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 =
|
|
2018
|
-
const locators =
|
|
2019
|
-
return
|
|
2020
|
-
const target =
|
|
2021
|
-
const relativePath =
|
|
2022
|
-
if (relativePath.startsWith("../") ||
|
|
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 (!
|
|
2026
|
-
return [{ type: "repository", locator: relativePath, note: `Repository evidence used to generate ${id}.`, sha256: sha256(
|
|
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) =>
|
|
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 (!
|
|
2138
|
-
if (!
|
|
2139
|
-
const content =
|
|
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 ?
|
|
2161
|
-
return indexPath &&
|
|
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 =
|
|
2478
|
+
const canonicalContextRoot = path6.join(projectRoot, ".codex-agent", "context");
|
|
2193
2479
|
for (const [file, descriptor] of rendered) {
|
|
2194
|
-
const destination =
|
|
2195
|
-
const contextRelative =
|
|
2196
|
-
const usesContextBaseline = contextBaselineRoot !== null && contextRelative !== "" && contextRelative !== ".." && !contextRelative.startsWith(`..${
|
|
2197
|
-
const currentPath = usesContextBaseline ?
|
|
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 =
|
|
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:
|
|
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 =
|
|
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) =>
|
|
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 =
|
|
2591
|
+
const target = path6.join(projectRoot, ...normalized.split("/"));
|
|
2305
2592
|
assertInside(projectRoot, target, label);
|
|
2306
2593
|
assertNoSymlink(projectRoot, target, label);
|
|
2307
|
-
if (!
|
|
2308
|
-
if (!
|
|
2309
|
-
return sha256(
|
|
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 =
|
|
2599
|
+
const temporary = path6.join(path6.dirname(destination), `.${path6.basename(destination)}.tmp-${crypto4.randomUUID()}`);
|
|
2313
2600
|
let descriptor;
|
|
2314
2601
|
try {
|
|
2315
|
-
descriptor =
|
|
2316
|
-
|
|
2602
|
+
descriptor = fs6.openSync(temporary, "wx", 384);
|
|
2603
|
+
fs6.writeFileSync(descriptor, `${JSON.stringify(value, null, 2)}
|
|
2317
2604
|
`);
|
|
2318
|
-
|
|
2319
|
-
|
|
2605
|
+
fs6.fsyncSync(descriptor);
|
|
2606
|
+
fs6.closeSync(descriptor);
|
|
2320
2607
|
descriptor = void 0;
|
|
2321
|
-
|
|
2608
|
+
fs6.renameSync(temporary, destination);
|
|
2322
2609
|
} catch (error) {
|
|
2323
2610
|
if (descriptor !== void 0) try {
|
|
2324
|
-
|
|
2611
|
+
fs6.closeSync(descriptor);
|
|
2325
2612
|
} catch {
|
|
2326
2613
|
}
|
|
2327
2614
|
try {
|
|
2328
|
-
if (
|
|
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 =
|
|
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 =
|
|
2665
|
+
const markerPath = path6.join(transactionRoot, `${candidate}.json`);
|
|
2379
2666
|
assertNoSymlink(projectRoot, markerPath, `Lifecycle phase marker ${candidate}`);
|
|
2380
|
-
if (!
|
|
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(
|
|
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 =
|
|
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(
|
|
2690
|
+
manifest = JSON.parse(fs6.readFileSync(manifestPath, "utf8"));
|
|
2404
2691
|
} catch {
|
|
2405
|
-
throw new Error(`Invalid context lifecycle recovery manifest: ${
|
|
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(
|
|
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 =
|
|
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 =
|
|
2447
|
-
const preparationRoot =
|
|
2448
|
-
|
|
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(
|
|
2472
|
-
|
|
2758
|
+
writeNewLifecycleJson(path6.join(preparationRoot, "manifest.json"), manifest);
|
|
2759
|
+
fs6.renameSync(preparationRoot, transactionRoot);
|
|
2473
2760
|
return readLifecycleJournal({ projectRoot, transactionRoot });
|
|
2474
2761
|
} catch (error) {
|
|
2475
|
-
|
|
2476
|
-
|
|
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 =
|
|
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 (
|
|
2782
|
+
if (fs6.existsSync(catalogTransactionRoot)) fs6.rmSync(catalogTransactionRoot, { recursive: true, force: true });
|
|
2496
2783
|
assertNoSymlink(projectRoot, journal.transactionRoot, "Lifecycle transaction directory");
|
|
2497
|
-
|
|
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 =
|
|
2502
|
-
const legacy =
|
|
2503
|
-
const backup =
|
|
2504
|
-
const quarantine =
|
|
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 (
|
|
2793
|
+
if (fs6.existsSync(canonical)) {
|
|
2507
2794
|
inspectLifecycleCatalog({ projectRoot, relativePath: paths.canonical, expectedHash: initial.sourceHash, label: "Promoted canonical catalog" });
|
|
2508
|
-
if (
|
|
2509
|
-
|
|
2510
|
-
} else if (
|
|
2511
|
-
const quarantinePath =
|
|
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 (!
|
|
2804
|
+
if (!fs6.existsSync(legacy)) {
|
|
2518
2805
|
inspectLifecycleCatalog({ projectRoot, relativePath: paths.backup, expectedHash: initial.sourceHash, label: "Legacy catalog backup" });
|
|
2519
|
-
ensureDirectory(projectRoot,
|
|
2520
|
-
|
|
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 (
|
|
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 (!
|
|
2565
|
-
return
|
|
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
|
|
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 =
|
|
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 =
|
|
2941
|
+
suppliedRoot = fs6.realpathSync(path6.resolve(analysis.root));
|
|
2606
2942
|
} catch {
|
|
2607
|
-
suppliedRoot =
|
|
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
|
-
|
|
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:
|
|
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 &&
|
|
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:
|
|
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:
|
|
3091
|
+
analysisPath: relative2(projectRoot, analysisPathFor(projectRoot)),
|
|
2752
3092
|
analysis,
|
|
2753
|
-
|
|
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 =
|
|
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
|
|
2777
|
-
import
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
3194
|
+
const target = path7.resolve(contextRoot, document.path);
|
|
2844
3195
|
assertInside(contextRoot, target, `Pending context document ${index2}`);
|
|
2845
|
-
const
|
|
2846
|
-
if (!
|
|
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(
|
|
2850
|
-
}
|
|
2851
|
-
const diskPaths = listMarkdown(contextRoot).map((file) => slash(
|
|
2852
|
-
const entries = [.../* @__PURE__ */ new Set([...diskPaths, ...pending.keys()])].sort().map((
|
|
2853
|
-
const content2 = pending.has(
|
|
2854
|
-
assertSafeMarkdownContent(content2, `Context Markdown ${
|
|
2855
|
-
const title = firstHeading(content2,
|
|
2856
|
-
const existing = priorByPath.get(
|
|
2857
|
-
const tags =
|
|
2858
|
-
...
|
|
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(
|
|
2864
|
-
path:
|
|
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 =
|
|
3221
|
+
const schemaPath = path7.join(projectRoot, "schemas", "context-index.schema.json");
|
|
2871
3222
|
const index = {
|
|
2872
|
-
...
|
|
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:
|
|
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 ||
|
|
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 =
|
|
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" ||
|
|
2956
|
-
const target =
|
|
2957
|
-
if (target !== projectRoot && !target.startsWith(`${projectRoot}${
|
|
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
|
|
2968
|
-
if (
|
|
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 (!
|
|
2971
|
-
else if (!
|
|
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:
|
|
3004
|
-
...proposal.supersedes?.length ? { 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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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:
|
|
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:
|
|
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 =
|
|
3185
|
-
if (!
|
|
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(
|
|
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:
|
|
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] &&
|
|
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
|
|
3214
|
-
import
|
|
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
|
|
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
|
|
3575
|
+
var readJson3 = (file, label) => {
|
|
3225
3576
|
try {
|
|
3226
|
-
return JSON.parse(
|
|
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) =>
|
|
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 =
|
|
3235
|
-
if (!
|
|
3236
|
-
if (!
|
|
3237
|
-
const sourceRoot =
|
|
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 =
|
|
3240
|
-
if (
|
|
3241
|
-
const config =
|
|
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 (
|
|
3245
|
-
const target =
|
|
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:
|
|
3252
|
-
{ path:
|
|
3253
|
-
{ path:
|
|
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 &&
|
|
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 =
|
|
3610
|
+
const contextRoot = fs8.realpathSync(selected.path);
|
|
3260
3611
|
assertInside(sourceRoot, contextRoot, "Context root");
|
|
3261
3612
|
const manifestCandidates = [
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
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 =
|
|
3267
|
-
const manifest = manifestPath ?
|
|
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
|
|
3286
|
-
const absolute =
|
|
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(
|
|
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 =
|
|
3294
|
-
if (size > MAX_FILE_BYTES) throw new Error(`Source context file exceeds ${MAX_FILE_BYTES} bytes: ${slash(
|
|
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:
|
|
3331
|
-
const normalized =
|
|
3332
|
-
const basename =
|
|
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:
|
|
3705
|
+
var entryTags = ({ relative: relative3, metadata, title }) => unique4([
|
|
3355
3706
|
"migrated",
|
|
3356
|
-
...
|
|
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 =
|
|
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
|
|
3405
|
-
const original =
|
|
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:
|
|
3758
|
+
const reason = containsSensitiveContent(original) ? "sensitive-content" : classify({ relative: relative3, content: metadata.content, includeNavigation, includeTemplates, includeWorkflows });
|
|
3408
3759
|
if (reason) {
|
|
3409
|
-
skipped.push({ source:
|
|
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:
|
|
3765
|
+
skipped.push({ source: relative3, reason: "empty" });
|
|
3415
3766
|
continue;
|
|
3416
3767
|
}
|
|
3417
|
-
const destinationRelative = `migrated/${
|
|
3418
|
-
const id = slug(`migrated-${
|
|
3419
|
-
const title = firstHeading2(transformed,
|
|
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: ${
|
|
3772
|
+
<!-- source: ${relative3} -->
|
|
3422
3773
|
${transformed}
|
|
3423
3774
|
${MANAGED_END(id)}`;
|
|
3424
3775
|
candidates.push({
|
|
3425
3776
|
sourceFile,
|
|
3426
|
-
source:
|
|
3777
|
+
source: relative3,
|
|
3427
3778
|
destinationRelative,
|
|
3428
|
-
destination:
|
|
3779
|
+
destination: path8.join(destinationRoot, ...relative3.split("/")),
|
|
3429
3780
|
id,
|
|
3430
3781
|
title,
|
|
3431
3782
|
summary: summary(transformed, title),
|
|
3432
|
-
tags: entryTags({ relative:
|
|
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 =
|
|
3789
|
+
const indexPath = path8.join(contextRoot, "index.json");
|
|
3439
3790
|
const targetIndex = upgradeContextIndex(writableCatalog.index);
|
|
3440
|
-
const priorIndexContent =
|
|
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 =
|
|
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:
|
|
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:
|
|
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:
|
|
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] &&
|
|
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
|
|
3599
|
-
import
|
|
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 =
|
|
3654
|
-
const
|
|
3655
|
-
if (
|
|
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 (
|
|
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 =
|
|
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(
|
|
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(
|
|
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:
|
|
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] &&
|
|
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 (!
|
|
4092
|
+
if (!fs10.existsSync(root)) return [];
|
|
3742
4093
|
const files = [];
|
|
3743
4094
|
const visit = (directory) => {
|
|
3744
|
-
for (const entry of
|
|
3745
|
-
const absolute =
|
|
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 =
|
|
3756
|
-
const sourceRoot =
|
|
3757
|
-
if (!
|
|
3758
|
-
const sourceFiles = (
|
|
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 =
|
|
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
|
|
3770
|
-
const destination =
|
|
3771
|
-
const content =
|
|
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 (!
|
|
3776
|
-
result.imported.push(
|
|
3777
|
-
documents.push({ path:
|
|
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(
|
|
3781
|
-
result.unchanged.push(
|
|
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(
|
|
4136
|
+
result.conflicts.push(path10.relative(catalogProjectRoot, destination));
|
|
3786
4137
|
continue;
|
|
3787
4138
|
}
|
|
3788
|
-
result.imported.push(
|
|
3789
|
-
const documentPath =
|
|
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 &&
|
|
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(
|
|
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 =
|
|
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 =
|
|
3837
|
-
const isSourceWorkspace =
|
|
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 =
|
|
3841
|
-
check(checks, "marketplace",
|
|
3842
|
-
if (
|
|
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 =
|
|
3858
|
-
const 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",
|
|
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 ?
|
|
3872
|
-
check(checks, "context-index", Boolean(contextIndex &&
|
|
3873
|
-
if (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 =
|
|
4227
|
+
const contextRoot = path10.dirname(contextIndex);
|
|
3877
4228
|
const invalid = (parsed.value?.entries ?? []).filter((entry) => {
|
|
3878
|
-
const target =
|
|
3879
|
-
return !target.startsWith(`${contextRoot}${
|
|
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 =
|
|
3885
|
-
const skillFiles = listFiles(skillsRoot).filter((file) => file.endsWith(`${
|
|
3886
|
-
const skillDirectories =
|
|
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 =
|
|
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 =
|
|
3891
|
-
check(checks, "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 =
|
|
3897
|
-
const suitePath =
|
|
3898
|
-
if (!
|
|
3899
|
-
const suite = JSON.parse(
|
|
3900
|
-
const skillsRoot =
|
|
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
|
-
|
|
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 =
|
|
3941
|
-
const suitePath =
|
|
3942
|
-
if (!
|
|
3943
|
-
const suite = JSON.parse(
|
|
3944
|
-
const skillsRoot =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
4399
|
+
parsed.root = path11.resolve(parsed.root ?? process.cwd());
|
|
4044
4400
|
return parsed;
|
|
4045
4401
|
};
|
|
4046
4402
|
var readJsonFile = (file, label) => {
|
|
4047
|
-
const absolute =
|
|
4048
|
-
if (!
|
|
4049
|
-
return JSON.parse(
|
|
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;
|