@uzysjung/agent-harness 26.164.0 → 26.166.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +18 -12
- package/README.md +18 -12
- package/dist/{chunk-X7NSQHXL.js → chunk-AHAAF4HS.js} +77 -12
- package/dist/{chunk-X7NSQHXL.js.map → chunk-AHAAF4HS.js.map} +1 -1
- package/dist/index.js +1231 -733
- package/dist/index.js.map +1 -1
- package/dist/trust-tier-drift.js +1 -1
- package/package.json +1 -1
- package/templates/{antigravity/AGENTS.md.template → AGENTS.md.template} +14 -2
- package/templates/codex/README.md +6 -5
- package/templates/codex/config.toml.template +0 -11
- package/templates/opencode/README.md +1 -2
- package/templates/codex/AGENTS.md.template +0 -31
- package/templates/opencode/AGENTS.md.template +0 -23
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
CATEGORY_TITLES,
|
|
5
5
|
CLI_BASES,
|
|
6
6
|
CLI_BASE_SORT_ORDER,
|
|
7
|
+
CLI_DISPLAY_NAMES,
|
|
7
8
|
CONTINUOUS_SKILLS,
|
|
8
9
|
CONTINUOUS_SKILLS_HEADING,
|
|
9
10
|
DEFAULT_OPTIONS,
|
|
@@ -27,6 +28,7 @@ import {
|
|
|
27
28
|
UPDATE_GROUPS,
|
|
28
29
|
__commonJS,
|
|
29
30
|
__toESM,
|
|
31
|
+
anchorSurface,
|
|
30
32
|
anyTrack,
|
|
31
33
|
appendSelection,
|
|
32
34
|
assetCliSupport,
|
|
@@ -43,12 +45,15 @@ import {
|
|
|
43
45
|
claudeTargets,
|
|
44
46
|
collectPolicyHashes,
|
|
45
47
|
collectSkillHashes,
|
|
48
|
+
continuousNoteEnd,
|
|
46
49
|
copyBackupDir,
|
|
47
50
|
copyDir,
|
|
48
51
|
copyFile,
|
|
49
52
|
corruptedInstallLogMessage,
|
|
50
53
|
createOutsideGuard,
|
|
51
54
|
dirTreesIdentical,
|
|
55
|
+
dropContinuousSkillsNote,
|
|
56
|
+
dropHarnessQuoteBlocks,
|
|
52
57
|
ensureDir,
|
|
53
58
|
ensureProjectSkeleton,
|
|
54
59
|
experimentalOptInCandidates,
|
|
@@ -68,10 +73,12 @@ import {
|
|
|
68
73
|
isKeyId,
|
|
69
74
|
isOutsideProject,
|
|
70
75
|
isTrack,
|
|
76
|
+
isUnfilledScaffold,
|
|
71
77
|
legacyDroppedKeys,
|
|
72
78
|
legacyInstallLogPath,
|
|
73
79
|
legacyReleasedCatalog,
|
|
74
80
|
listFilesRecursive,
|
|
81
|
+
makeResidentCost,
|
|
75
82
|
memoTargets,
|
|
76
83
|
mergeExternalFiles,
|
|
77
84
|
mergeOutside,
|
|
@@ -94,9 +101,8 @@ import {
|
|
|
94
101
|
summarizeContextCost,
|
|
95
102
|
targetsInclude,
|
|
96
103
|
upsertHarnessImport,
|
|
97
|
-
withContinuousSkillsNote,
|
|
98
104
|
writeInstallLog
|
|
99
|
-
} from "./chunk-
|
|
105
|
+
} from "./chunk-AHAAF4HS.js";
|
|
100
106
|
|
|
101
107
|
// node_modules/sisteransi/src/index.js
|
|
102
108
|
var require_src = __commonJS({
|
|
@@ -780,8 +786,8 @@ var cac = (name = "") => new CAC(name);
|
|
|
780
786
|
|
|
781
787
|
// src/commands/install.ts
|
|
782
788
|
init_esm_shims();
|
|
783
|
-
import { rmSync as
|
|
784
|
-
import { join as
|
|
789
|
+
import { rmSync as rmSync4 } from "fs";
|
|
790
|
+
import { join as join31, resolve as resolve2 } from "path";
|
|
785
791
|
import { fileURLToPath } from "url";
|
|
786
792
|
|
|
787
793
|
// src/adapters/index.ts
|
|
@@ -1427,6 +1433,7 @@ var AGENTS_BLOCK_NAME = "agents";
|
|
|
1427
1433
|
var AGENTS_BLOCK = marker(AGENTS_BLOCK_NAME);
|
|
1428
1434
|
var CONTEXT_SECTION = "Project Context";
|
|
1429
1435
|
var RULES_SECTION = "Project Rules";
|
|
1436
|
+
var HARNESS_RULES_SECTION = "Harness Rules";
|
|
1430
1437
|
function wrapHarnessBlock(m, body) {
|
|
1431
1438
|
return `${m.start}
|
|
1432
1439
|
${body}
|
|
@@ -1505,21 +1512,11 @@ function refreshBlock(existing, rendered, m, legacyHeading) {
|
|
|
1505
1512
|
return fresh === null ? [...kept, ""] : [...kept, "", ...fresh, ""];
|
|
1506
1513
|
}
|
|
1507
1514
|
const head = trimTrailingBlanks(existing.slice(0, legacyAt));
|
|
1508
|
-
const tail = existing.slice(
|
|
1515
|
+
const tail = existing.slice(continuousNoteEnd(existing, legacyAt));
|
|
1509
1516
|
while (tail.length > 0 && (tail[0] ?? "").trim() === "") tail.shift();
|
|
1510
1517
|
const body = fresh === null ? [...head] : [...head, "", ...fresh];
|
|
1511
1518
|
return tail.length > 0 ? [...body, "", ...tail] : [...body, ""];
|
|
1512
1519
|
}
|
|
1513
|
-
function legacyNoteEnd(lines2, headingAt) {
|
|
1514
|
-
let i = headingAt + 1;
|
|
1515
|
-
while (i < lines2.length) {
|
|
1516
|
-
const l = (lines2[i] ?? "").replace(/\r$/, "").trim();
|
|
1517
|
-
const isNote = l === "" || l.startsWith("- `") || l.startsWith("These installed skills apply");
|
|
1518
|
-
if (!isNote) break;
|
|
1519
|
-
i++;
|
|
1520
|
-
}
|
|
1521
|
-
return i;
|
|
1522
|
-
}
|
|
1523
1520
|
var ANCHOR_TITLE_FALLBACK = "Working Principles";
|
|
1524
1521
|
function anchorTitle(claudeMd) {
|
|
1525
1522
|
const first = claudeMd.split("\n")[0] ?? "";
|
|
@@ -1555,7 +1552,8 @@ function mergeAgentsMd(params) {
|
|
|
1555
1552
|
const bodies = /* @__PURE__ */ new Map();
|
|
1556
1553
|
const contextNow = sectionBody(existing, names, CONTEXT_SECTION);
|
|
1557
1554
|
const contextNew = sectionBody(rendered, names, CONTEXT_SECTION);
|
|
1558
|
-
|
|
1555
|
+
const seedOverUnfilled = params.replaceUnfilledContext === true && contextNow !== null && isUnfilledScaffold(installerLines(contextNow, SKILLS_BLOCK));
|
|
1556
|
+
if (contextNow !== null && contextNew !== null && !seedOverUnfilled) {
|
|
1559
1557
|
bodies.set(
|
|
1560
1558
|
CONTEXT_SECTION,
|
|
1561
1559
|
refreshBlock(contextNow, contextNew, SKILLS_BLOCK, CONTINUOUS_SKILLS_HEADING)
|
|
@@ -1568,24 +1566,30 @@ function mergeAgentsMd(params) {
|
|
|
1568
1566
|
}
|
|
1569
1567
|
const merged = bodies.size === 0 ? rendered : assemble(rendered, names, bodies);
|
|
1570
1568
|
const mergedText = merged.replace(/\r/g, "");
|
|
1571
|
-
const extras = extraTopLevelSections(
|
|
1572
|
-
|
|
1573
|
-
|
|
1569
|
+
const extras = extraTopLevelSections(
|
|
1570
|
+
existing,
|
|
1571
|
+
names,
|
|
1572
|
+
rendered,
|
|
1573
|
+
template,
|
|
1574
|
+
params.harnessRuleHeadings ?? /* @__PURE__ */ new Set()
|
|
1575
|
+
).filter((e2) => !mergedText.includes(e2.join("\n").replace(/\r/g, "")));
|
|
1574
1576
|
if (extras.length === 0) return merged;
|
|
1575
1577
|
return `${merged.replace(/\n+$/, "\n")}${extras.map((e2) => e2.join("\n")).join("\n")}`.replace(
|
|
1576
1578
|
/\n+$/,
|
|
1577
1579
|
"\n"
|
|
1578
1580
|
);
|
|
1579
1581
|
}
|
|
1580
|
-
function extraTopLevelSections(existing, names, rendered, template) {
|
|
1582
|
+
function extraTopLevelSections(existing, names, rendered, template, harnessRuleHeadings) {
|
|
1581
1583
|
const out = [];
|
|
1582
1584
|
let current = null;
|
|
1585
|
+
let inHarnessRules = false;
|
|
1583
1586
|
for (const line of existing.split("\n")) {
|
|
1584
1587
|
const bare = line.replace(/\r$/, "");
|
|
1585
1588
|
const name = /^## (.+?)\s*$/.exec(bare)?.[1];
|
|
1586
1589
|
if (name !== void 0) {
|
|
1587
1590
|
if (current !== null) out.push(current);
|
|
1588
|
-
|
|
1591
|
+
if (names.has(name)) inHarnessRules = name === HARNESS_RULES_SECTION;
|
|
1592
|
+
const harnessAuthored = names.has(name) || inHarnessRules && harnessRuleHeadings.has(bare.trim()) || rendered.includes(bare) || template.includes(bare) || line.trim() === CONTINUOUS_SKILLS_HEADING;
|
|
1589
1593
|
current = harnessAuthored ? null : [line];
|
|
1590
1594
|
continue;
|
|
1591
1595
|
}
|
|
@@ -1616,6 +1620,30 @@ function projectContextInstallerLines(params) {
|
|
|
1616
1620
|
const body = sectionBody(existing, sectionNames(template), CONTEXT_SECTION);
|
|
1617
1621
|
return body === null ? null : installerLines(body, SKILLS_BLOCK);
|
|
1618
1622
|
}
|
|
1623
|
+
function installerSectionsInHarnessArea(existing, names, harnessRuleHeadings) {
|
|
1624
|
+
const out = [];
|
|
1625
|
+
let current = null;
|
|
1626
|
+
let region = null;
|
|
1627
|
+
for (const line of existing.split("\n")) {
|
|
1628
|
+
const bare = line.replace(/\r$/, "");
|
|
1629
|
+
const name = /^## (.+?)\s*$/.exec(bare)?.[1];
|
|
1630
|
+
if (name !== void 0) {
|
|
1631
|
+
if (current !== null) out.push(current);
|
|
1632
|
+
current = null;
|
|
1633
|
+
if (names.has(name)) {
|
|
1634
|
+
region = name;
|
|
1635
|
+
continue;
|
|
1636
|
+
}
|
|
1637
|
+
if (region === CONTEXT_SECTION || region === RULES_SECTION) continue;
|
|
1638
|
+
const harnessRule = region === HARNESS_RULES_SECTION && harnessRuleHeadings.has(bare.trim());
|
|
1639
|
+
if (!harnessRule) current = [bare];
|
|
1640
|
+
continue;
|
|
1641
|
+
}
|
|
1642
|
+
if (current !== null) current.push(bare);
|
|
1643
|
+
}
|
|
1644
|
+
if (current !== null) out.push(current);
|
|
1645
|
+
return out.map(trimTrailingBlanks);
|
|
1646
|
+
}
|
|
1619
1647
|
function stripHarnessFromAgentsMd(params) {
|
|
1620
1648
|
const { existing, template } = params;
|
|
1621
1649
|
const lines2 = existing.split("\n");
|
|
@@ -1625,15 +1653,22 @@ function stripHarnessFromAgentsMd(params) {
|
|
|
1625
1653
|
const names = sectionNames(template);
|
|
1626
1654
|
const context = installerLines(sectionBody(existing, names, CONTEXT_SECTION), SKILLS_BLOCK);
|
|
1627
1655
|
const rules = installerLines(sectionBody(existing, names, RULES_SECTION), ANCHOR_BLOCK);
|
|
1628
|
-
const
|
|
1629
|
-
const contextFilled = compact(context).join("\n") !== scaffold.join("\n");
|
|
1656
|
+
const contextFilled = !isUnfilledScaffold(context);
|
|
1630
1657
|
const rulesFilled = compact(rules).length > 0;
|
|
1631
|
-
|
|
1658
|
+
const extras = installerSectionsInHarnessArea(
|
|
1659
|
+
existing,
|
|
1660
|
+
names,
|
|
1661
|
+
params.harnessRuleHeadings ?? /* @__PURE__ */ new Set()
|
|
1662
|
+
);
|
|
1663
|
+
if (!contextFilled && !rulesFilled && extras.length === 0) return null;
|
|
1632
1664
|
const out = [];
|
|
1633
1665
|
const title = (lines2[0] ?? "").replace(/\r$/, "");
|
|
1634
1666
|
if (/^# /.test(title)) out.push(title, "");
|
|
1635
|
-
|
|
1667
|
+
if (contextFilled || rulesFilled) {
|
|
1668
|
+
out.push(`## ${CONTEXT_SECTION}`, "", ...trimBlankEdges(context), "");
|
|
1669
|
+
}
|
|
1636
1670
|
if (rulesFilled) out.push(`## ${RULES_SECTION}`, "", ...trimBlankEdges(rules), "");
|
|
1671
|
+
for (const section of extras) out.push(...section, "");
|
|
1637
1672
|
return out.join("\n");
|
|
1638
1673
|
}
|
|
1639
1674
|
|
|
@@ -2592,6 +2627,33 @@ function segment(body) {
|
|
|
2592
2627
|
}
|
|
2593
2628
|
return items;
|
|
2594
2629
|
}
|
|
2630
|
+
function same(a, b2) {
|
|
2631
|
+
if (a instanceof Date || b2 instanceof Date) {
|
|
2632
|
+
return a instanceof Date && b2 instanceof Date && a.toISOString() === b2.toISOString();
|
|
2633
|
+
}
|
|
2634
|
+
if (Array.isArray(a)) {
|
|
2635
|
+
return Array.isArray(b2) && a.length === b2.length && a.every((v2, i) => same(v2, b2[i]));
|
|
2636
|
+
}
|
|
2637
|
+
if (isTable(a)) {
|
|
2638
|
+
const keys = Object.keys(a);
|
|
2639
|
+
return isTable(b2) && keys.length === Object.keys(b2).length && keys.every((k2) => Object.hasOwn(b2, k2) && same(a[k2], b2[k2]));
|
|
2640
|
+
}
|
|
2641
|
+
return Object.is(a, b2);
|
|
2642
|
+
}
|
|
2643
|
+
function unchangedUnder(before, after) {
|
|
2644
|
+
if (isTable(before)) {
|
|
2645
|
+
return isTable(after) && Object.keys(before).every(
|
|
2646
|
+
(k2) => Object.hasOwn(after, k2) && unchangedUnder(before[k2], after[k2])
|
|
2647
|
+
);
|
|
2648
|
+
}
|
|
2649
|
+
if (Array.isArray(before)) {
|
|
2650
|
+
return Array.isArray(after) && after.length >= before.length && before.every((v2, i) => same(v2, after[i]));
|
|
2651
|
+
}
|
|
2652
|
+
return same(before, after);
|
|
2653
|
+
}
|
|
2654
|
+
function startsWith(path, prefix) {
|
|
2655
|
+
return prefix.length > 0 && prefix.length <= path.length && prefix.every((s, i) => path[i] === s);
|
|
2656
|
+
}
|
|
2595
2657
|
function hasPath(obj, path) {
|
|
2596
2658
|
let cur = obj;
|
|
2597
2659
|
for (const seg of path) {
|
|
@@ -2617,19 +2679,26 @@ function filterRender(render, outside) {
|
|
|
2617
2679
|
throw new Error(`toml-region: region ${name} must start with a [table] header`);
|
|
2618
2680
|
}
|
|
2619
2681
|
const mine = [];
|
|
2682
|
+
const dropped = [];
|
|
2620
2683
|
for (const item of items) {
|
|
2684
|
+
if (dropped.some((p2) => startsWith(item.path, p2))) {
|
|
2685
|
+
kept.push(item.name);
|
|
2686
|
+
continue;
|
|
2687
|
+
}
|
|
2621
2688
|
const taken = item.header !== "array" && hasPath(installer, item.path);
|
|
2622
2689
|
side.push(item.text);
|
|
2623
|
-
|
|
2690
|
+
const merged = taken ? null : readToml(doc());
|
|
2691
|
+
if (merged === null || !unchangedUnder(installer, merged)) {
|
|
2624
2692
|
side.pop();
|
|
2625
2693
|
kept.push(item.name);
|
|
2694
|
+
dropped.push(item.path);
|
|
2626
2695
|
continue;
|
|
2627
2696
|
}
|
|
2628
2697
|
mine.push(item.text);
|
|
2629
2698
|
}
|
|
2630
2699
|
if (mine.length > 0) out.set(name, mine.join("\n"));
|
|
2631
2700
|
}
|
|
2632
|
-
return { render: out, kept };
|
|
2701
|
+
return { render: out, kept: [...new Set(kept)] };
|
|
2633
2702
|
}
|
|
2634
2703
|
function regionText(name, body) {
|
|
2635
2704
|
return `# uzys-harness:${name}:start
|
|
@@ -3122,22 +3191,22 @@ var CLI_OWNERSHIP = {
|
|
|
3122
3191
|
],
|
|
3123
3192
|
codex: [
|
|
3124
3193
|
{ path: ".codex/", kind: "dir", sharedWith: [] },
|
|
3125
|
-
{ path: "AGENTS.md", kind: "recorded", sharedWith: ["opencode"] },
|
|
3194
|
+
{ path: "AGENTS.md", kind: "recorded", sharedWith: ["opencode", "antigravity"] },
|
|
3126
3195
|
{ path: ".agents/skills/", kind: "recorded", sharedWith: ["opencode", "antigravity"] },
|
|
3127
3196
|
...SHARED_BY_ALL("codex")
|
|
3128
3197
|
],
|
|
3129
3198
|
opencode: [
|
|
3130
3199
|
{ path: ".opencode/", kind: "dir", sharedWith: [] },
|
|
3131
3200
|
{ path: "opencode.json", kind: "recorded", sharedWith: [] },
|
|
3132
|
-
{ path: "AGENTS.md", kind: "recorded", sharedWith: ["codex"] },
|
|
3201
|
+
{ path: "AGENTS.md", kind: "recorded", sharedWith: ["codex", "antigravity"] },
|
|
3133
3202
|
{ path: ".agents/skills/", kind: "recorded", sharedWith: ["codex", "antigravity"] },
|
|
3134
3203
|
...SHARED_BY_ALL("opencode")
|
|
3135
3204
|
],
|
|
3136
3205
|
antigravity: [
|
|
3137
|
-
|
|
3138
|
-
//
|
|
3139
|
-
|
|
3140
|
-
//
|
|
3206
|
+
// ADR-103 — Antigravity 도 루트 `AGENTS.md` 를 앵커로 읽는다(옛 앵커 `.agents/rules/uzys-harness.md` 는 은퇴 — 은퇴 전
|
|
3207
|
+
// 설치본의 그 기록은 아래 접두 행이 덮는다).
|
|
3208
|
+
{ path: "AGENTS.md", kind: "recorded", sharedWith: ["codex", "opencode"] },
|
|
3209
|
+
// #596 — 룰 파일들도 antigravity 소유다(transform 이 externalFiles 에 기록한다).
|
|
3141
3210
|
{ path: ".agents/rules/", kind: "recorded", sharedWith: [] },
|
|
3142
3211
|
{ path: ".agents/skills/", kind: "recorded", sharedWith: ["codex", "opencode"] },
|
|
3143
3212
|
...SHARED_BY_ALL("antigravity")
|
|
@@ -3670,15 +3739,95 @@ function legacyGitignoreSeed(text, render, previous) {
|
|
|
3670
3739
|
|
|
3671
3740
|
// src/installer.ts
|
|
3672
3741
|
init_esm_shims();
|
|
3673
|
-
import { chmodSync as chmodSync3, existsSync as existsSync21, readdirSync as
|
|
3674
|
-
import { basename as
|
|
3742
|
+
import { chmodSync as chmodSync3, existsSync as existsSync21, readdirSync as readdirSync4, readFileSync as readFileSync22, writeFileSync as writeFileSync8 } from "fs";
|
|
3743
|
+
import { basename as basename4, join as join28, relative as relative3, resolve, sep as sep4 } from "path";
|
|
3675
3744
|
|
|
3676
3745
|
// src/anchor-seed.ts
|
|
3677
3746
|
init_esm_shims();
|
|
3678
3747
|
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
3679
3748
|
import { join as join6 } from "path";
|
|
3680
|
-
|
|
3681
|
-
|
|
3749
|
+
|
|
3750
|
+
// src/agents-md-render.ts
|
|
3751
|
+
init_esm_shims();
|
|
3752
|
+
var AGENTS_MD_TEMPLATE = "templates/AGENTS.md.template";
|
|
3753
|
+
var AGENTS_MD_CLIS = ["codex", "opencode", "antigravity"];
|
|
3754
|
+
function isAgentsMdCli(cli) {
|
|
3755
|
+
return AGENTS_MD_CLIS.includes(cli);
|
|
3756
|
+
}
|
|
3757
|
+
function agentsMdClis(clis) {
|
|
3758
|
+
return [...new Set(clis.filter(isAgentsMdCli))].sort(
|
|
3759
|
+
(a, b2) => CLI_BASE_SORT_ORDER[a] - CLI_BASE_SORT_ORDER[b2]
|
|
3760
|
+
);
|
|
3761
|
+
}
|
|
3762
|
+
function renameSlashes(text) {
|
|
3763
|
+
return text.replaceAll("/uzys:", "/uzys-");
|
|
3764
|
+
}
|
|
3765
|
+
var RULES_NOTE = [
|
|
3766
|
+
"> \uD558\uB124\uC2A4\uAC00 \uC124\uCE58\uD55C \uB8F0\uC774 \uC5EC\uAE30 \uBCF8\uBB38\uC73C\uB85C \uB4E4\uC5B4\uC628\uB2E4. \uC774 \uD504\uB85C\uC81D\uD2B8\uC5D0\uC11C\uB9CC \uC9C0\uD0AC \uADDC\uCE59\uC744 \uC138\uC6B0\uBA74 \uC774 \uBB38\uC11C\uC5D0",
|
|
3767
|
+
"> \uC774\uC5B4 \uC4F0\uAC70\uB098, \uD558\uC704 \uB514\uB809\uD130\uB9AC\uC5D0 `AGENTS.md` \uB97C \uB450\uBA74 \uADF8 \uBC94\uC704\uC5D0\uC11C \uB354 \uAD6C\uCCB4\uC801\uC778 \uADDC\uCE59\uC774 \uB41C\uB2E4."
|
|
3768
|
+
].join("\n");
|
|
3769
|
+
var CODEX_SIZE_NOTE = [
|
|
3770
|
+
">",
|
|
3771
|
+
"> Codex \uB294 **\uD569\uCE5C \uD06C\uAE30**\uAC00 `project_doc_max_bytes`(\uAE30\uBCF8 32 KiB)\uC5D0 \uB2FF\uC73C\uBA74 **\uAC70\uAE30\uC11C \uC77D\uAE30\uB97C \uBA48\uCD98\uB2E4** \u2014 \uB4A4\uC5D0 \uC624\uB294",
|
|
3772
|
+
"> \uD30C\uC77C\uC774 \uC870\uC6A9\uD788 \uBE60\uC9C0\uACE0, \uD558\uC704 \uD30C\uC77C\uC774 \uB4A4\uC5D0 \uC628\uB2E4. \uB098\uB220 \uC4F0\uAE30 \uC804\uC5D0 \uCD1D\uB7C9\uC744 \uBA3C\uC800 \uBCF8\uB2E4."
|
|
3773
|
+
].join("\n");
|
|
3774
|
+
var CODEX_SESSION_START = "`docs/SPEC.md` \uAC00 \uC788\uC73C\uBA74 \uC138\uC158 \uC2DC\uC791 \uB54C \uBA3C\uC800 \uC77D\uB294\uB2E4 \u2014 \uD604\uC7AC \uBC94\uC704\uC640 \uC644\uB8CC \uAE30\uC900\uC758 \uC575\uCEE4\uB2E4. \uC5C6\uC73C\uBA74 \uC774 \uC904\uC740 \uD574\uB2F9 \uC5C6\uB2E4.";
|
|
3775
|
+
var PROTECTED_LEAD = {
|
|
3776
|
+
codex: 'Codex `sandbox_mode = "workspace-write"` + `approval_policy = "on-request"` \uAC00 1\uCC28 \uBC29\uC5B4\uB2E4.',
|
|
3777
|
+
opencode: "OpenCode `permission` \uC124\uC815\uC774 1\uCC28 \uBC29\uC5B4\uB2E4."
|
|
3778
|
+
};
|
|
3779
|
+
function pruneTemplate(template, empty) {
|
|
3780
|
+
const lines2 = template.split("\n");
|
|
3781
|
+
const kept = [];
|
|
3782
|
+
for (let i = 0; i < lines2.length; i++) {
|
|
3783
|
+
const line = lines2[i] ?? "";
|
|
3784
|
+
if (empty.has(line.trim())) {
|
|
3785
|
+
if ((lines2[i + 1] ?? "").trim() === "") i++;
|
|
3786
|
+
continue;
|
|
3787
|
+
}
|
|
3788
|
+
kept.push(line);
|
|
3789
|
+
}
|
|
3790
|
+
const names = new Set([...template.matchAll(/^## (.+)$/gm)].map((m) => (m[1] ?? "").trim()));
|
|
3791
|
+
const isKnownHeading = (l) => {
|
|
3792
|
+
const name = /^## (.+?)\s*$/.exec(l)?.[1];
|
|
3793
|
+
return name !== void 0 && names.has(name);
|
|
3794
|
+
};
|
|
3795
|
+
const out = [];
|
|
3796
|
+
for (let i = 0; i < kept.length; i++) {
|
|
3797
|
+
const line = kept[i] ?? "";
|
|
3798
|
+
if (isKnownHeading(line)) {
|
|
3799
|
+
let j4 = i + 1;
|
|
3800
|
+
while (j4 < kept.length && !isKnownHeading(kept[j4] ?? "")) j4++;
|
|
3801
|
+
if (kept.slice(i + 1, j4).every((l) => l.trim() === "")) {
|
|
3802
|
+
i = j4 - 1;
|
|
3803
|
+
continue;
|
|
3804
|
+
}
|
|
3805
|
+
}
|
|
3806
|
+
out.push(line);
|
|
3807
|
+
}
|
|
3808
|
+
return out.join("\n");
|
|
3809
|
+
}
|
|
3810
|
+
function renderAgentsMd(params) {
|
|
3811
|
+
const clis = agentsMdClis(params.clis);
|
|
3812
|
+
if (clis.length === 0) throw new Error("renderAgentsMd: empty CLI set");
|
|
3813
|
+
const inlineRules = clis.includes("codex") || clis.includes("opencode");
|
|
3814
|
+
const leads = clis.flatMap((c3) => PROTECTED_LEAD[c3] ?? []);
|
|
3815
|
+
const values = {
|
|
3816
|
+
"{HARNESS_RULES_NOTE}": inlineRules ? clis.includes("codex") ? `${RULES_NOTE}
|
|
3817
|
+
${CODEX_SIZE_NOTE}` : RULES_NOTE : "",
|
|
3818
|
+
"{HARNESS_RULES}": inlineRules ? params.harnessRules : "",
|
|
3819
|
+
"{SESSION_START}": clis.includes("codex") ? CODEX_SESSION_START : "",
|
|
3820
|
+
"{PROTECTED_FILES_LEAD}": leads.length === 0 ? "" : `${leads.join("\n")} \uADF8 \uC704\uC5D0:`
|
|
3821
|
+
};
|
|
3822
|
+
const empty = new Set(Object.keys(values).filter((k2) => (values[k2] ?? "").trim() === ""));
|
|
3823
|
+
const body = wrapHarnessBlock(ANCHOR_BLOCK, params.claudeMd.replace(/^#\s+.*\r?\n/, "").trim());
|
|
3824
|
+
let out = pruneTemplate(params.template, empty).replaceAll("{PROJECT_NAME}", params.projectName).replaceAll("{CLI_NAMES}", clis.map((c3) => CLI_DISPLAY_NAMES[c3]).join(" \xB7 "));
|
|
3825
|
+
for (const [k2, v2] of Object.entries(values)) out = out.replaceAll(k2, v2);
|
|
3826
|
+
out = out.replaceAll("{PROJECT_RULES}", body).replaceAll("{PROJECT_CONTEXT}", params.projectContext);
|
|
3827
|
+
return renameSlashes(out);
|
|
3828
|
+
}
|
|
3829
|
+
|
|
3830
|
+
// src/anchor-seed.ts
|
|
3682
3831
|
function readIfExists(path) {
|
|
3683
3832
|
if (!existsSync5(path)) return null;
|
|
3684
3833
|
try {
|
|
@@ -3687,37 +3836,12 @@ function readIfExists(path) {
|
|
|
3687
3836
|
return null;
|
|
3688
3837
|
}
|
|
3689
3838
|
}
|
|
3690
|
-
function dropHarnessQuoteBlocks(lines2) {
|
|
3691
|
-
const out = [];
|
|
3692
|
-
let i = 0;
|
|
3693
|
-
while (i < lines2.length) {
|
|
3694
|
-
if (!(lines2[i] ?? "").trim().startsWith(">")) {
|
|
3695
|
-
out.push(lines2[i] ?? "");
|
|
3696
|
-
i += 1;
|
|
3697
|
-
continue;
|
|
3698
|
-
}
|
|
3699
|
-
let end = i;
|
|
3700
|
-
while (end < lines2.length && (lines2[end] ?? "").trim().startsWith(">")) end++;
|
|
3701
|
-
const run = lines2.slice(i, end);
|
|
3702
|
-
const text = run.join("\n");
|
|
3703
|
-
if (!text.includes(SCAFFOLD_MARK) && !text.includes(TRACK_NOTE_MARK)) out.push(...run);
|
|
3704
|
-
i = end;
|
|
3705
|
-
}
|
|
3706
|
-
return out;
|
|
3707
|
-
}
|
|
3708
3839
|
function dropTitle(lines2) {
|
|
3709
3840
|
const out = [...lines2];
|
|
3710
3841
|
while (out.length > 0 && (out[0] ?? "").trim() === "") out.shift();
|
|
3711
3842
|
if (out.length > 0 && /^#\s/.test(out[0] ?? "")) out.shift();
|
|
3712
3843
|
return out;
|
|
3713
3844
|
}
|
|
3714
|
-
function compact2(lines2) {
|
|
3715
|
-
return lines2.map((l) => l.replace(/\r$/, "").trimEnd()).filter((l) => l.trim() !== "");
|
|
3716
|
-
}
|
|
3717
|
-
function isPristineScaffold(body, surface) {
|
|
3718
|
-
const pristine = compact2(dropHarnessQuoteBlocks(renderFillScaffold(surface).split("\n")));
|
|
3719
|
-
return compact2(body).join("\n") === pristine.join("\n");
|
|
3720
|
-
}
|
|
3721
3845
|
function compose(body, into) {
|
|
3722
3846
|
const trimmed = [...body];
|
|
3723
3847
|
while (trimmed.length > 0 && (trimmed.at(-1) ?? "").trim() === "") trimmed.pop();
|
|
@@ -3732,18 +3856,18 @@ function seedAgentsMdProjectContext(projectDir) {
|
|
|
3732
3856
|
if (root === null) return null;
|
|
3733
3857
|
const withoutImport = stripHarnessImport(root) ?? root;
|
|
3734
3858
|
const body = dropHarnessQuoteBlocks(dropTitle(withoutImport.split("\n")));
|
|
3735
|
-
if (
|
|
3859
|
+
if (isUnfilledScaffold(body)) return null;
|
|
3736
3860
|
return compose(body, "agents-md");
|
|
3737
3861
|
}
|
|
3738
3862
|
function seedRootClaudeProjectContext(projectDir, harnessRoot) {
|
|
3739
3863
|
const agents = readIfExists(join6(projectDir, "AGENTS.md"));
|
|
3740
3864
|
if (agents === null) return null;
|
|
3741
|
-
const template = readIfExists(join6(harnessRoot,
|
|
3865
|
+
const template = readIfExists(join6(harnessRoot, AGENTS_MD_TEMPLATE));
|
|
3742
3866
|
if (template === null) return null;
|
|
3743
3867
|
const installer = projectContextInstallerLines({ existing: agents, template });
|
|
3744
3868
|
if (installer === null) return null;
|
|
3745
3869
|
const body = dropHarnessQuoteBlocks(installer);
|
|
3746
|
-
if (
|
|
3870
|
+
if (isUnfilledScaffold(body)) return null;
|
|
3747
3871
|
return compose(body, "claude");
|
|
3748
3872
|
}
|
|
3749
3873
|
|
|
@@ -3787,10 +3911,10 @@ function installCiScaffold(ctx) {
|
|
|
3787
3911
|
|
|
3788
3912
|
// src/cli-transforms.ts
|
|
3789
3913
|
init_esm_shims();
|
|
3790
|
-
import { existsSync as existsSync16, readFileSync as
|
|
3791
|
-
import { join as
|
|
3914
|
+
import { existsSync as existsSync16, readFileSync as readFileSync16 } from "fs";
|
|
3915
|
+
import { join as join20 } from "path";
|
|
3792
3916
|
|
|
3793
|
-
// src/
|
|
3917
|
+
// src/agents-md-transform.ts
|
|
3794
3918
|
init_esm_shims();
|
|
3795
3919
|
import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
|
|
3796
3920
|
import { basename, join as join12 } from "path";
|
|
@@ -3831,99 +3955,26 @@ function agentsSkillSlot(params) {
|
|
|
3831
3955
|
};
|
|
3832
3956
|
}
|
|
3833
3957
|
|
|
3834
|
-
// src/codex/agents-md.ts
|
|
3835
|
-
init_esm_shims();
|
|
3836
|
-
function renameSlashes(text) {
|
|
3837
|
-
return text.replaceAll("/uzys:", "/uzys-");
|
|
3838
|
-
}
|
|
3839
|
-
function renderAgentsMd(params) {
|
|
3840
|
-
const body = wrapHarnessBlock(ANCHOR_BLOCK, params.claudeMd.replace(/^#\s+.*\r?\n/, "").trim());
|
|
3841
|
-
const replaced = params.template.replaceAll("{PROJECT_NAME}", params.projectName).replaceAll("{PROJECT_RULES}", body).replaceAll("{PROJECT_CONTEXT}", params.projectContext).replaceAll("{HARNESS_RULES}", params.harnessRules ?? "");
|
|
3842
|
-
return renameSlashes(replaced);
|
|
3843
|
-
}
|
|
3844
|
-
|
|
3845
|
-
// src/codex/skills.ts
|
|
3846
|
-
init_esm_shims();
|
|
3847
|
-
import { chmodSync, existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
|
|
3848
|
-
import { join as join9 } from "path";
|
|
3849
|
-
function renderBundledSkill(source) {
|
|
3850
|
-
const trimmed = source.trimEnd();
|
|
3851
|
-
const lines2 = trimmed.split(/\r?\n/);
|
|
3852
|
-
if (lines2[0] !== "---") {
|
|
3853
|
-
return `${portBody(trimmed)}
|
|
3854
|
-
`;
|
|
3855
|
-
}
|
|
3856
|
-
let secondDelimAt = -1;
|
|
3857
|
-
for (let i = 1; i < lines2.length; i++) {
|
|
3858
|
-
if (lines2[i] === "---") {
|
|
3859
|
-
secondDelimAt = i;
|
|
3860
|
-
break;
|
|
3861
|
-
}
|
|
3862
|
-
}
|
|
3863
|
-
if (secondDelimAt < 0) {
|
|
3864
|
-
return `${portBody(trimmed)}
|
|
3865
|
-
`;
|
|
3866
|
-
}
|
|
3867
|
-
const frontmatter = lines2.slice(0, secondDelimAt + 1).join("\n");
|
|
3868
|
-
const body = lines2.slice(secondDelimAt + 1).join("\n");
|
|
3869
|
-
return `${frontmatter}
|
|
3870
|
-
${portBody(body)}
|
|
3871
|
-
`;
|
|
3872
|
-
}
|
|
3873
|
-
function portBody(body) {
|
|
3874
|
-
return renameSlashes(body).replace(/CLAUDE_PROJECT_DIR/g, "CODEX_PROJECT_DIR").trimEnd();
|
|
3875
|
-
}
|
|
3876
|
-
function writeBundledSkillDirs(params) {
|
|
3877
|
-
const { harnessRoot, projectDir, skillIds, writer } = params;
|
|
3878
|
-
const written = [];
|
|
3879
|
-
for (const id of skillIds) {
|
|
3880
|
-
const srcDir = join9(harnessRoot, "templates/skills", id);
|
|
3881
|
-
const srcSkill = join9(srcDir, "SKILL.md");
|
|
3882
|
-
if (!existsSync7(srcSkill)) {
|
|
3883
|
-
continue;
|
|
3884
|
-
}
|
|
3885
|
-
const targetDir = join9(projectDir, ".agents", "skills", id);
|
|
3886
|
-
const targetSkill = join9(targetDir, "SKILL.md");
|
|
3887
|
-
if (!writer.write(targetSkill, renderBundledSkill(readFileSync5(srcSkill, "utf8")))) {
|
|
3888
|
-
continue;
|
|
3889
|
-
}
|
|
3890
|
-
written.push(targetSkill);
|
|
3891
|
-
for (const rel of listFilesRecursive(srcDir)) {
|
|
3892
|
-
if (rel === "SKILL.md") continue;
|
|
3893
|
-
if (rel.split("/").some((segment2) => segment2.startsWith("."))) continue;
|
|
3894
|
-
const segments = rel.split("/");
|
|
3895
|
-
const raw = readFileSync5(join9(srcDir, ...segments), "utf8");
|
|
3896
|
-
const ported = rel.endsWith(".md") || rel.endsWith(".sh") ? `${portBody(raw)}
|
|
3897
|
-
` : raw;
|
|
3898
|
-
const target = join9(targetDir, ...segments);
|
|
3899
|
-
if (!writer.write(target, ported, { createInRefresh: true })) continue;
|
|
3900
|
-
if (rel.endsWith(".sh")) chmodSync(target, 493);
|
|
3901
|
-
written.push(target);
|
|
3902
|
-
}
|
|
3903
|
-
}
|
|
3904
|
-
return written;
|
|
3905
|
-
}
|
|
3906
|
-
|
|
3907
3958
|
// src/owned-write.ts
|
|
3908
3959
|
init_esm_shims();
|
|
3909
|
-
import { existsSync as
|
|
3960
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
3910
3961
|
import { dirname as dirname2, relative as relative2, sep as sep3 } from "path";
|
|
3911
3962
|
|
|
3912
3963
|
// src/foreign-slot.ts
|
|
3913
3964
|
init_esm_shims();
|
|
3914
3965
|
import { lstatSync, realpathSync, statSync } from "fs";
|
|
3915
|
-
import { join as
|
|
3966
|
+
import { join as join9 } from "path";
|
|
3916
3967
|
var SKILL_SLOT = /^((?:\.claude|\.agents)\/skills\/[^/]+)(?:\/|$)/;
|
|
3917
3968
|
function occupiedByNonDirectory(path) {
|
|
3918
3969
|
const stat = lstatSync(path, { throwIfNoEntry: false });
|
|
3919
3970
|
return stat !== void 0 && !stat.isDirectory();
|
|
3920
3971
|
}
|
|
3921
3972
|
function linksToProjectSharedSkill(projectDir, id) {
|
|
3922
|
-
const slot =
|
|
3973
|
+
const slot = join9(projectDir, ".claude/skills", id);
|
|
3923
3974
|
if (lstatSync(slot, { throwIfNoEntry: false })?.isSymbolicLink() !== true) return false;
|
|
3924
3975
|
try {
|
|
3925
3976
|
const real = realpathSync(slot);
|
|
3926
|
-
return real ===
|
|
3977
|
+
return real === join9(realpathSync(projectDir), ".agents/skills", id) && statSync(real).isDirectory();
|
|
3927
3978
|
} catch {
|
|
3928
3979
|
return false;
|
|
3929
3980
|
}
|
|
@@ -3931,15 +3982,15 @@ function linksToProjectSharedSkill(projectDir, id) {
|
|
|
3931
3982
|
function foreignOwnedTarget(projectDir, rel) {
|
|
3932
3983
|
const slot = SKILL_SLOT.exec(rel)?.[1];
|
|
3933
3984
|
if (slot === void 0) return null;
|
|
3934
|
-
if (occupiedByNonDirectory(
|
|
3985
|
+
if (occupiedByNonDirectory(join9(projectDir, slot))) return slot;
|
|
3935
3986
|
if (rel !== slot) {
|
|
3936
3987
|
const parts = rel.slice(slot.length + 1).split("/");
|
|
3937
3988
|
let cursor = slot;
|
|
3938
3989
|
for (const part of parts.slice(0, -1)) {
|
|
3939
3990
|
cursor = `${cursor}/${part}`;
|
|
3940
|
-
if (occupiedByNonDirectory(
|
|
3991
|
+
if (occupiedByNonDirectory(join9(projectDir, cursor))) return cursor;
|
|
3941
3992
|
}
|
|
3942
|
-
const leaf = lstatSync(
|
|
3993
|
+
const leaf = lstatSync(join9(projectDir, rel), { throwIfNoEntry: false });
|
|
3943
3994
|
if (leaf !== void 0 && !leaf.isFile()) return rel;
|
|
3944
3995
|
}
|
|
3945
3996
|
return null;
|
|
@@ -3965,7 +4016,7 @@ function createOwnedWriter(projectDir, baseline, options = {}) {
|
|
|
3965
4016
|
}
|
|
3966
4017
|
if (outside.skip(absPath)) return false;
|
|
3967
4018
|
const digest = hashContent(content);
|
|
3968
|
-
if (!
|
|
4019
|
+
if (!existsSync7(absPath)) {
|
|
3969
4020
|
const recordedPath = baseline.has(rel);
|
|
3970
4021
|
if (refreshOnly && !opts?.createInRefresh && !recordedPath) return false;
|
|
3971
4022
|
mkdirSync2(dirname2(absPath), { recursive: true });
|
|
@@ -3973,7 +4024,7 @@ function createOwnedWriter(projectDir, baseline, options = {}) {
|
|
|
3973
4024
|
updated++;
|
|
3974
4025
|
if (refreshOnly && recordedPath) restored.push(rel);
|
|
3975
4026
|
} else {
|
|
3976
|
-
const current =
|
|
4027
|
+
const current = readFileSync5(absPath, "utf8");
|
|
3977
4028
|
if (current !== content) {
|
|
3978
4029
|
if (!isHarnessOwned(baseline, rel, current)) {
|
|
3979
4030
|
const backup = backupFile(absPath, now);
|
|
@@ -4007,8 +4058,26 @@ function createOwnedWriter(projectDir, baseline, options = {}) {
|
|
|
4007
4058
|
|
|
4008
4059
|
// src/rules-port.ts
|
|
4009
4060
|
init_esm_shims();
|
|
4010
|
-
import { existsSync as
|
|
4011
|
-
import { join as
|
|
4061
|
+
import { existsSync as existsSync8, readdirSync, readFileSync as readFileSync6 } from "fs";
|
|
4062
|
+
import { join as join10 } from "path";
|
|
4063
|
+
function claudePaths(source) {
|
|
4064
|
+
if (!source.startsWith("---\n")) return [];
|
|
4065
|
+
const end = source.indexOf("\n---", 4);
|
|
4066
|
+
if (end === -1) return [];
|
|
4067
|
+
const lines2 = source.slice(4, end).split("\n");
|
|
4068
|
+
const unquote = (v2) => v2.trim().replace(/^(["'])(.*)\1$/, "$2");
|
|
4069
|
+
const at5 = lines2.findIndex((l) => /^paths:/.test(l));
|
|
4070
|
+
if (at5 === -1) return [];
|
|
4071
|
+
const inline = (lines2[at5] ?? "").slice("paths:".length).trim();
|
|
4072
|
+
if (inline !== "") return [unquote(inline)].filter((p2) => p2 !== "");
|
|
4073
|
+
const out = [];
|
|
4074
|
+
for (const l of lines2.slice(at5 + 1)) {
|
|
4075
|
+
const m = /^\s+-\s+(.+)$/.exec(l);
|
|
4076
|
+
if (m === null) break;
|
|
4077
|
+
out.push(unquote(m[1] ?? ""));
|
|
4078
|
+
}
|
|
4079
|
+
return out.filter((p2) => p2 !== "");
|
|
4080
|
+
}
|
|
4012
4081
|
function stripClaudeFrontmatter(source) {
|
|
4013
4082
|
if (!source.startsWith("---\n")) return source.trim();
|
|
4014
4083
|
const end = source.indexOf("\n---", 4);
|
|
@@ -4018,156 +4087,31 @@ function stripClaudeFrontmatter(source) {
|
|
|
4018
4087
|
function portRules(harnessRoot, ruleNames) {
|
|
4019
4088
|
const ported = [];
|
|
4020
4089
|
for (const name of ruleNames) {
|
|
4021
|
-
const src =
|
|
4022
|
-
if (!
|
|
4023
|
-
|
|
4090
|
+
const src = join10(harnessRoot, "templates/rules", `${name}.md`);
|
|
4091
|
+
if (!existsSync8(src)) continue;
|
|
4092
|
+
const source = readFileSync6(src, "utf8");
|
|
4093
|
+
ported.push({ name, body: stripClaudeFrontmatter(source), paths: claudePaths(source) });
|
|
4024
4094
|
}
|
|
4025
4095
|
return ported;
|
|
4026
4096
|
}
|
|
4097
|
+
function ruleHeadings(harnessRoot) {
|
|
4098
|
+
const dir = join10(harnessRoot, "templates/rules");
|
|
4099
|
+
if (!existsSync8(dir)) return /* @__PURE__ */ new Set();
|
|
4100
|
+
const names = readdirSync(dir).filter((f) => f.endsWith(".md")).map((f) => f.slice(0, -3));
|
|
4101
|
+
const block = renderRulesBlock(portRules(harnessRoot, names));
|
|
4102
|
+
return new Set(
|
|
4103
|
+
block.split("\n").filter((l) => /^## /.test(l)).map((l) => l.trim())
|
|
4104
|
+
);
|
|
4105
|
+
}
|
|
4027
4106
|
function renderRulesBlock(rules) {
|
|
4028
4107
|
if (rules.length === 0) return "";
|
|
4029
4108
|
return rules.map((r) => r.body.replace(/^#\s+/, "## ")).join("\n\n---\n\n");
|
|
4030
4109
|
}
|
|
4031
4110
|
|
|
4032
|
-
// src/antigravity/transform.ts
|
|
4033
|
-
function runAntigravityTransform(params) {
|
|
4034
|
-
const {
|
|
4035
|
-
harnessRoot,
|
|
4036
|
-
projectDir,
|
|
4037
|
-
selectedInternalSkills = [],
|
|
4038
|
-
rules = [],
|
|
4039
|
-
baseline,
|
|
4040
|
-
refreshOnly
|
|
4041
|
-
} = params;
|
|
4042
|
-
const writer = createOwnedWriter(projectDir, baseline, {
|
|
4043
|
-
refreshOnly: refreshOnly ?? false,
|
|
4044
|
-
...params.journal === void 0 ? {} : { journal: params.journal }
|
|
4045
|
-
});
|
|
4046
|
-
const { skillIds, skillWriter } = agentsSkillSlot({
|
|
4047
|
-
projectDir,
|
|
4048
|
-
selectedInternalSkills,
|
|
4049
|
-
refreshOnly: refreshOnly ?? false,
|
|
4050
|
-
writer
|
|
4051
|
-
});
|
|
4052
|
-
const installedByRecord = params.installedByRecord === true;
|
|
4053
|
-
const rulesFile = writeRules(harnessRoot, projectDir, writer, skillIds, installedByRecord);
|
|
4054
|
-
const antigravityInstalled = installedByRecord || rulesFile !== null;
|
|
4055
|
-
const harnessRuleFiles = [];
|
|
4056
|
-
for (const rule of portRules(harnessRoot, rules)) {
|
|
4057
|
-
const target = join12(projectDir, ".agents", "rules", `${rule.name}.md`);
|
|
4058
|
-
if (!writer.write(target, `${rule.body}
|
|
4059
|
-
`, { createInRefresh: antigravityInstalled })) {
|
|
4060
|
-
continue;
|
|
4061
|
-
}
|
|
4062
|
-
harnessRuleFiles.push(target);
|
|
4063
|
-
}
|
|
4064
|
-
const skillFiles = writeBundledSkillDirs({
|
|
4065
|
-
harnessRoot,
|
|
4066
|
-
projectDir,
|
|
4067
|
-
skillIds,
|
|
4068
|
-
writer: skillWriter
|
|
4069
|
-
});
|
|
4070
|
-
return {
|
|
4071
|
-
rulesFile,
|
|
4072
|
-
harnessRuleFiles,
|
|
4073
|
-
skillFiles,
|
|
4074
|
-
ownership: writer.result()
|
|
4075
|
-
};
|
|
4076
|
-
}
|
|
4077
|
-
function writeRules(harnessRoot, projectDir, writer, selectedInternalSkills, createInRefresh) {
|
|
4078
|
-
const claudeMdPath = join12(harnessRoot, "templates/CLAUDE.md");
|
|
4079
|
-
const templatePath = join12(harnessRoot, "templates/antigravity/AGENTS.md.template");
|
|
4080
|
-
if (!existsSync10(claudeMdPath) || !existsSync10(templatePath)) {
|
|
4081
|
-
return null;
|
|
4082
|
-
}
|
|
4083
|
-
const claudeMd = readFileSync8(claudeMdPath, "utf8");
|
|
4084
|
-
const template = readFileSync8(templatePath, "utf8");
|
|
4085
|
-
const target = join12(projectDir, ".agents", "rules", "uzys-harness.md");
|
|
4086
|
-
const rulesOut = renderAgentsMd({
|
|
4087
|
-
template,
|
|
4088
|
-
claudeMd,
|
|
4089
|
-
projectName: basename(projectDir),
|
|
4090
|
-
// ADR-085 — 상시 스킬 안내는 앵커가 아니라 여기(프로젝트 맥락)에, 깔린 것만.
|
|
4091
|
-
projectContext: withContinuousSkillsNote(
|
|
4092
|
-
renderFillScaffold("antigravity-rule"),
|
|
4093
|
-
selectedInternalSkills
|
|
4094
|
-
)
|
|
4095
|
-
});
|
|
4096
|
-
const withoutMarkers = rulesOut.split("\n").filter((l) => !/^<!-- uzys-harness:(?:anchor|skills):(?:start|end) -->$/.test(l.trim())).join("\n");
|
|
4097
|
-
return writer.write(target, withoutMarkers, { createInRefresh }) ? target : null;
|
|
4098
|
-
}
|
|
4099
|
-
|
|
4100
|
-
// src/codex/opt-in.ts
|
|
4101
|
-
init_esm_shims();
|
|
4102
|
-
import { homedir } from "os";
|
|
4103
|
-
import { join as join13 } from "path";
|
|
4104
|
-
|
|
4105
|
-
// src/codex/trust-entry.ts
|
|
4106
|
-
init_esm_shims();
|
|
4107
|
-
import { existsSync as existsSync11, mkdirSync as mkdirSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
|
|
4108
|
-
import { dirname as dirname3 } from "path";
|
|
4109
|
-
var TRUST_BLOCK_REGEX = /\[projects\."([^"]+)"\]/g;
|
|
4110
|
-
function registerTrustEntry(opts) {
|
|
4111
|
-
const { configPath, projectDir } = opts;
|
|
4112
|
-
try {
|
|
4113
|
-
const existing = existsSync11(configPath) ? readFileSync9(configPath, "utf8") : "";
|
|
4114
|
-
try {
|
|
4115
|
-
parse(existing);
|
|
4116
|
-
} catch (e2) {
|
|
4117
|
-
return {
|
|
4118
|
-
status: "unreadable",
|
|
4119
|
-
message: `not valid TOML (${firstLine(e2)}) \u2014 left untouched`
|
|
4120
|
-
};
|
|
4121
|
-
}
|
|
4122
|
-
if (hasTrustEntry(existing, projectDir)) {
|
|
4123
|
-
return { status: "already-present" };
|
|
4124
|
-
}
|
|
4125
|
-
mkdirSync3(dirname3(configPath), { recursive: true });
|
|
4126
|
-
const block = `
|
|
4127
|
-
[projects."${projectDir}"]
|
|
4128
|
-
trust_level = "trusted"
|
|
4129
|
-
`;
|
|
4130
|
-
writeFileSync5(configPath, existing + block);
|
|
4131
|
-
return { status: "registered" };
|
|
4132
|
-
} catch (e2) {
|
|
4133
|
-
return {
|
|
4134
|
-
status: "error",
|
|
4135
|
-
message: e2 instanceof Error ? e2.message : String(e2)
|
|
4136
|
-
};
|
|
4137
|
-
}
|
|
4138
|
-
}
|
|
4139
|
-
function hasTrustEntry(configContent, projectDir) {
|
|
4140
|
-
const matches = [...configContent.matchAll(TRUST_BLOCK_REGEX)].map((m) => m[1]);
|
|
4141
|
-
return matches.includes(projectDir);
|
|
4142
|
-
}
|
|
4143
|
-
function firstLine(e2) {
|
|
4144
|
-
return (e2 instanceof Error ? e2.message : String(e2)).split("\n")[0] ?? "";
|
|
4145
|
-
}
|
|
4146
|
-
|
|
4147
|
-
// src/codex/opt-in.ts
|
|
4148
|
-
function runCodexOptIn(ctx) {
|
|
4149
|
-
const codexHome = ctx.codexHome ?? join13(homedir(), ".codex");
|
|
4150
|
-
const configPath = join13(codexHome, "config.toml");
|
|
4151
|
-
const result = registerTrustEntry({ configPath, projectDir: ctx.projectDir });
|
|
4152
|
-
return {
|
|
4153
|
-
trustEntry: {
|
|
4154
|
-
enabled: true,
|
|
4155
|
-
status: result.status,
|
|
4156
|
-
configPath,
|
|
4157
|
-
...result.message ? { message: result.message } : {}
|
|
4158
|
-
}
|
|
4159
|
-
};
|
|
4160
|
-
}
|
|
4161
|
-
|
|
4162
|
-
// src/codex/transform.ts
|
|
4163
|
-
init_esm_shims();
|
|
4164
|
-
import { chmodSync as chmodSync2, existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
|
|
4165
|
-
import { basename as basename2, join as join15 } from "path";
|
|
4166
|
-
|
|
4167
4111
|
// src/shared-write.ts
|
|
4168
4112
|
init_esm_shims();
|
|
4169
|
-
import { existsSync as
|
|
4170
|
-
import { dirname as
|
|
4113
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync7, rmSync, statSync as statSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
4114
|
+
import { dirname as dirname3, join as join11 } from "path";
|
|
4171
4115
|
function toPortions(path, portions) {
|
|
4172
4116
|
const adapter = adapterFor(path);
|
|
4173
4117
|
if (adapter === null) throw new Error(`shared-write: ${path} is not a shared file`);
|
|
@@ -4182,8 +4126,8 @@ function writeShared(params) {
|
|
|
4182
4126
|
const { projectDir, path, render, record, baseline, writer, refreshOnly } = params;
|
|
4183
4127
|
const adapter = adapterFor(path);
|
|
4184
4128
|
if (adapter === null) throw new Error(`shared-write: ${path} is not a shared file`);
|
|
4185
|
-
const abs =
|
|
4186
|
-
const onDisk =
|
|
4129
|
+
const abs = join11(projectDir, path);
|
|
4130
|
+
const onDisk = existsSync9(abs) ? readFileSync7(abs, "utf8") : null;
|
|
4187
4131
|
const recorded2 = recordedFor(path, record);
|
|
4188
4132
|
const excluded = excludedKeys(path, record.excluded ?? []);
|
|
4189
4133
|
const result = (action2, rest = {}) => ({
|
|
@@ -4258,13 +4202,13 @@ function writeShared(params) {
|
|
|
4258
4202
|
);
|
|
4259
4203
|
const ownCreated = params.ownCreated !== false;
|
|
4260
4204
|
if (onDisk === null && !ownCreated) {
|
|
4261
|
-
|
|
4262
|
-
|
|
4205
|
+
mkdirSync3(dirname3(abs), { recursive: true });
|
|
4206
|
+
writeFileSync5(abs, upserted.text);
|
|
4263
4207
|
} else if (harnessMade) {
|
|
4264
4208
|
writer.write(abs, upserted.text, { createInRefresh: true });
|
|
4265
4209
|
} else if (upserted.changed) {
|
|
4266
|
-
|
|
4267
|
-
|
|
4210
|
+
mkdirSync3(dirname3(abs), { recursive: true });
|
|
4211
|
+
writeFileSync5(abs, upserted.text);
|
|
4268
4212
|
}
|
|
4269
4213
|
writer.journal?.portions(path, portions);
|
|
4270
4214
|
const action = onDisk === null ? "created" : upserted.text !== onDisk ? "updated" : "unchanged";
|
|
@@ -4302,13 +4246,20 @@ function agentsMdModel(disk, recordedByHarness, recordedBlock = false) {
|
|
|
4302
4246
|
return "block";
|
|
4303
4247
|
}
|
|
4304
4248
|
function writeAgentsMd(params) {
|
|
4305
|
-
const abs =
|
|
4306
|
-
const disk =
|
|
4249
|
+
const abs = join11(params.projectDir, AGENTS_MD);
|
|
4250
|
+
const disk = existsSync9(abs) ? readFileSync7(abs, "utf8") : null;
|
|
4307
4251
|
const recordedBlock = (params.record.portions ?? []).some((p2) => p2.path === AGENTS_MD);
|
|
4308
4252
|
if (agentsMdModel(disk, params.baseline.has(AGENTS_MD), recordedBlock) === "sections") {
|
|
4309
4253
|
params.writer.write(
|
|
4310
4254
|
abs,
|
|
4311
|
-
mergeAgentsMd({
|
|
4255
|
+
mergeAgentsMd({
|
|
4256
|
+
rendered: params.rendered,
|
|
4257
|
+
existing: disk,
|
|
4258
|
+
template: params.template,
|
|
4259
|
+
...params.replaceUnfilledContext === true ? { replaceUnfilledContext: true } : {},
|
|
4260
|
+
...params.harnessRuleHeadings === void 0 ? {} : { harnessRuleHeadings: params.harnessRuleHeadings }
|
|
4261
|
+
}),
|
|
4262
|
+
params.createInRefresh === true ? { createInRefresh: true } : void 0
|
|
4312
4263
|
);
|
|
4313
4264
|
return { model: "sections", shared: null };
|
|
4314
4265
|
}
|
|
@@ -4349,9 +4300,9 @@ function stripShared(params) {
|
|
|
4349
4300
|
portions: [],
|
|
4350
4301
|
...rest
|
|
4351
4302
|
});
|
|
4352
|
-
const abs =
|
|
4353
|
-
if (!
|
|
4354
|
-
const disk =
|
|
4303
|
+
const abs = join11(projectDir, path);
|
|
4304
|
+
if (!existsSync9(abs) || !statSync2(abs).isFile()) return out("none");
|
|
4305
|
+
const disk = readFileSync7(abs, "utf8");
|
|
4355
4306
|
const recorded2 = new Map(
|
|
4356
4307
|
params.portions.filter((p2) => p2.path === path).map((p2) => [p2.key, p2.sha256])
|
|
4357
4308
|
);
|
|
@@ -4363,39 +4314,444 @@ function stripShared(params) {
|
|
|
4363
4314
|
next: null,
|
|
4364
4315
|
adapter
|
|
4365
4316
|
});
|
|
4366
|
-
if (verdict.verdict === "leave+advise") {
|
|
4367
|
-
return out("left", { line: verdict.line, portions: toPortions(path, recorded2) });
|
|
4317
|
+
if (verdict.verdict === "leave+advise") {
|
|
4318
|
+
return out("left", { line: verdict.line, portions: toPortions(path, recorded2) });
|
|
4319
|
+
}
|
|
4320
|
+
if (recorded2.size === 0) {
|
|
4321
|
+
const names = params.remnant(disk);
|
|
4322
|
+
return names.length === 0 ? out("none") : out("left", { line: params.remnantLine(names) });
|
|
4323
|
+
}
|
|
4324
|
+
const impl = ADAPTERS[adapter];
|
|
4325
|
+
const res = impl.strip(disk, {
|
|
4326
|
+
recorded: recorded2,
|
|
4327
|
+
excluded: excludedKeys(path, params.excluded),
|
|
4328
|
+
projectDir
|
|
4329
|
+
});
|
|
4330
|
+
if (!res.ok) {
|
|
4331
|
+
return out("left", {
|
|
4332
|
+
line: `could not read it (${res.reason}) \u2014 harness part not removed`,
|
|
4333
|
+
portions: toPortions(path, recorded2)
|
|
4334
|
+
});
|
|
4335
|
+
}
|
|
4336
|
+
const fileRemoved = params.removeIfEmpty === true && res.removed.length > 0 && res.empty;
|
|
4337
|
+
if (params.write && fileRemoved) rmSync(abs);
|
|
4338
|
+
else if (params.write && res.changed) writeFileSync5(abs, res.text);
|
|
4339
|
+
const action = res.removed.length > 0 ? "removed" : res.kept.length > 0 ? "left" : "none";
|
|
4340
|
+
return out(action, {
|
|
4341
|
+
removed: res.removed,
|
|
4342
|
+
kept: res.kept,
|
|
4343
|
+
portions: toPortions(path, res.portions),
|
|
4344
|
+
...fileRemoved ? { fileRemoved } : {}
|
|
4345
|
+
});
|
|
4346
|
+
}
|
|
4347
|
+
|
|
4348
|
+
// src/agents-md-transform.ts
|
|
4349
|
+
var LEGACY_ANTIGRAVITY_ANCHOR = ".agents/rules/uzys-harness.md";
|
|
4350
|
+
function runAgentsMdTransform(params) {
|
|
4351
|
+
const { harnessRoot, projectDir, baseline, shared: shared2 = {} } = params;
|
|
4352
|
+
const refreshOnly = params.refreshOnly === true;
|
|
4353
|
+
const clis = agentsMdClis(params.clis);
|
|
4354
|
+
const writer = createOwnedWriter(projectDir, baseline, {
|
|
4355
|
+
refreshOnly,
|
|
4356
|
+
...params.journal === void 0 ? {} : { journal: params.journal }
|
|
4357
|
+
});
|
|
4358
|
+
const claudeMd = readRequired(join12(harnessRoot, "templates/CLAUDE.md"));
|
|
4359
|
+
const template = readRequired(join12(harnessRoot, AGENTS_MD_TEMPLATE));
|
|
4360
|
+
const { skillIds } = agentsSkillSlot({
|
|
4361
|
+
projectDir,
|
|
4362
|
+
selectedInternalSkills: params.selectedInternalSkills,
|
|
4363
|
+
refreshOnly,
|
|
4364
|
+
writer
|
|
4365
|
+
});
|
|
4366
|
+
const agentsMdPath = join12(projectDir, "AGENTS.md");
|
|
4367
|
+
const exists = existsSync10(agentsMdPath);
|
|
4368
|
+
const creates = !exists && (!refreshOnly || params.installedByRecord === true);
|
|
4369
|
+
const legacy = params.legacyContext ?? null;
|
|
4370
|
+
const fromRoot = creates ? seedAgentsMdProjectContext(projectDir) : null;
|
|
4371
|
+
const seed = fromRoot ?? legacy;
|
|
4372
|
+
const seededFrom = fromRoot !== null ? "CLAUDE.md" : legacy !== null ? LEGACY_ANTIGRAVITY_ANCHOR : null;
|
|
4373
|
+
ensureDir(projectDir);
|
|
4374
|
+
const rendered = renderAgentsMd({
|
|
4375
|
+
template,
|
|
4376
|
+
claudeMd,
|
|
4377
|
+
projectName: basename(projectDir),
|
|
4378
|
+
// ADR-085 — 상시 스킬 안내는 앵커가 아니라 프로젝트 맥락에, 깔린 것만(#503 — 설치자 절 안이라 마커로 감싼다).
|
|
4379
|
+
projectContext: withMarkedContinuousSkillsNote(
|
|
4380
|
+
seed ?? renderFillScaffold("agents-md"),
|
|
4381
|
+
skillIds
|
|
4382
|
+
),
|
|
4383
|
+
harnessRules: renderRulesBlock(portRules(harnessRoot, params.rules)),
|
|
4384
|
+
clis
|
|
4385
|
+
});
|
|
4386
|
+
const diskBefore = exists ? readFileSync8(agentsMdPath, "utf8") : null;
|
|
4387
|
+
const agentsMd = writeAgentsMd({
|
|
4388
|
+
projectDir,
|
|
4389
|
+
rendered,
|
|
4390
|
+
template,
|
|
4391
|
+
anchorTitle: anchorTitle(claudeMd),
|
|
4392
|
+
writer,
|
|
4393
|
+
baseline,
|
|
4394
|
+
record: shared2,
|
|
4395
|
+
refreshOnly,
|
|
4396
|
+
...params.installedByRecord === true ? { createInRefresh: true } : {},
|
|
4397
|
+
...legacy !== null && fromRoot === null ? { replaceUnfilledContext: true } : {},
|
|
4398
|
+
harnessRuleHeadings: ruleHeadings(harnessRoot)
|
|
4399
|
+
});
|
|
4400
|
+
return {
|
|
4401
|
+
agentsMdPath,
|
|
4402
|
+
agentsMd,
|
|
4403
|
+
agentsMdSeededFrom: seededFrom === null ? null : seededInto(agentsMdPath, diskBefore, seed, seededFrom),
|
|
4404
|
+
clis,
|
|
4405
|
+
ownership: writer.result()
|
|
4406
|
+
};
|
|
4407
|
+
}
|
|
4408
|
+
function seededInto(path, before, seed, from) {
|
|
4409
|
+
if (seed === null || !existsSync10(path)) return null;
|
|
4410
|
+
const after = readFileSync8(path, "utf8");
|
|
4411
|
+
const body = renameSlashes(seed.slice(seed.indexOf("\n\n") + 2));
|
|
4412
|
+
return after !== before && after.includes(body) ? from : null;
|
|
4413
|
+
}
|
|
4414
|
+
function readRequired(path) {
|
|
4415
|
+
if (!existsSync10(path)) {
|
|
4416
|
+
throw new Error(`AGENTS.md transform: required source missing: ${path}`);
|
|
4417
|
+
}
|
|
4418
|
+
return readFileSync8(path, "utf8");
|
|
4419
|
+
}
|
|
4420
|
+
|
|
4421
|
+
// src/antigravity/retire-anchor.ts
|
|
4422
|
+
init_esm_shims();
|
|
4423
|
+
import { lstatSync as lstatSync2, readFileSync as readFileSync9, rmSync as rmSync2 } from "fs";
|
|
4424
|
+
import { join as join13 } from "path";
|
|
4425
|
+
var LEGACY_ANTIGRAVITY_ANCHOR_SECTIONS = ["Project Context", "Project Rules", "Protected Files"];
|
|
4426
|
+
function anchorRecord(previous) {
|
|
4427
|
+
return previous.find((f) => f.path === LEGACY_ANTIGRAVITY_ANCHOR) ?? null;
|
|
4428
|
+
}
|
|
4429
|
+
function readRegular(abs) {
|
|
4430
|
+
const stat = lstatSync2(abs, { throwIfNoEntry: false });
|
|
4431
|
+
if (stat === void 0 || !stat.isFile()) return null;
|
|
4432
|
+
try {
|
|
4433
|
+
return readFileSync9(abs, "utf8");
|
|
4434
|
+
} catch {
|
|
4435
|
+
return null;
|
|
4436
|
+
}
|
|
4437
|
+
}
|
|
4438
|
+
function legacyContextLines(text) {
|
|
4439
|
+
const lines2 = text.split("\n");
|
|
4440
|
+
const heading = (l) => {
|
|
4441
|
+
const name = /^## (.+?)\s*$/.exec(l.replace(/\r$/, ""))?.[1];
|
|
4442
|
+
return name !== void 0 && LEGACY_ANTIGRAVITY_ANCHOR_SECTIONS.includes(name) ? name : null;
|
|
4443
|
+
};
|
|
4444
|
+
const start = lines2.findIndex((l) => heading(l) === "Project Context");
|
|
4445
|
+
if (start === -1) return null;
|
|
4446
|
+
const rest = lines2.slice(start + 1);
|
|
4447
|
+
const end = rest.findIndex((l) => heading(l) !== null);
|
|
4448
|
+
return end === -1 ? rest : rest.slice(0, end);
|
|
4449
|
+
}
|
|
4450
|
+
function readLegacyAntigravityContext(projectDir, previous) {
|
|
4451
|
+
if (anchorRecord(previous) === null) return null;
|
|
4452
|
+
const text = readRegular(join13(projectDir, LEGACY_ANTIGRAVITY_ANCHOR));
|
|
4453
|
+
if (text === null) return null;
|
|
4454
|
+
const lines2 = legacyContextLines(text);
|
|
4455
|
+
if (lines2 === null) return null;
|
|
4456
|
+
const body = dropContinuousSkillsNote(dropHarnessQuoteBlocks(lines2));
|
|
4457
|
+
if (isUnfilledScaffold(body)) return null;
|
|
4458
|
+
return compose(currentFillPrompts(body), "agents-md");
|
|
4459
|
+
}
|
|
4460
|
+
function currentFillPrompts(body) {
|
|
4461
|
+
const fillId = (l) => /^<!-- FILL:([a-z-]+) — .*-->$/.exec(l.trim())?.[1] ?? null;
|
|
4462
|
+
const current = /* @__PURE__ */ new Map();
|
|
4463
|
+
for (const l of renderFillScaffold("agents-md").split("\n")) {
|
|
4464
|
+
const id = fillId(l);
|
|
4465
|
+
if (id !== null) current.set(id, l);
|
|
4466
|
+
}
|
|
4467
|
+
return body.map((l) => {
|
|
4468
|
+
const id = fillId(l);
|
|
4469
|
+
return id === null ? l : current.get(id) ?? l;
|
|
4470
|
+
});
|
|
4471
|
+
}
|
|
4472
|
+
function retireAntigravityAnchor(args) {
|
|
4473
|
+
const rec = anchorRecord(args.previous);
|
|
4474
|
+
if (rec === null) return null;
|
|
4475
|
+
const path = LEGACY_ANTIGRAVITY_ANCHOR;
|
|
4476
|
+
const abs = join13(args.projectDir, path);
|
|
4477
|
+
const left = (why) => ({
|
|
4478
|
+
path,
|
|
4479
|
+
action: "left",
|
|
4480
|
+
backup: null,
|
|
4481
|
+
forget: false,
|
|
4482
|
+
line: `left \u2014 ${why}; Antigravity reads AGENTS.md now, so this file can go`
|
|
4483
|
+
});
|
|
4484
|
+
if (args.outside.skip(abs)) return left("it points outside this project");
|
|
4485
|
+
const stat = lstatSync2(abs, { throwIfNoEntry: false });
|
|
4486
|
+
if (stat !== void 0 && !stat.isFile()) return left("it is a link or folder, so it is yours");
|
|
4487
|
+
const disk = stat === void 0 ? null : readFileSync9(abs, "utf8");
|
|
4488
|
+
const verdict = judge({
|
|
4489
|
+
op: "remove",
|
|
4490
|
+
kind: "harness",
|
|
4491
|
+
rec: { state: "sha", sha256: rec.sha256 },
|
|
4492
|
+
disk,
|
|
4493
|
+
next: null
|
|
4494
|
+
});
|
|
4495
|
+
if (disk === null) {
|
|
4496
|
+
return { path, action: "gone", backup: null, forget: true, line: "" };
|
|
4497
|
+
}
|
|
4498
|
+
const keepCopy = verdict.verdict === "backup+remove" || args.hadContent && !args.moved && verdict.verdict === "remove";
|
|
4499
|
+
if (verdict.verdict !== "remove" && verdict.verdict !== "backup+remove") {
|
|
4500
|
+
return left("could not judge it");
|
|
4501
|
+
}
|
|
4502
|
+
const backup = keepCopy ? backupFile(abs, args.now) : null;
|
|
4503
|
+
rmSync2(abs);
|
|
4504
|
+
const saved = backup === null ? "" : relativeBackup(args.projectDir, backup);
|
|
4505
|
+
const line = args.moved ? `retired \u2014 Antigravity reads AGENTS.md; your Project Context moved there${saved ? ` \xB7 previous file saved as ${saved}` : ""}` : args.hadContent ? `retired \u2014 AGENTS.md already has its own Project Context, so nothing was merged; your filled text is in ${saved}` : `retired \u2014 Antigravity reads AGENTS.md${saved ? ` \xB7 previous file saved as ${saved}` : ""}`;
|
|
4506
|
+
return { path, action: "removed", backup, forget: true, line };
|
|
4507
|
+
}
|
|
4508
|
+
function relativeBackup(projectDir, backup) {
|
|
4509
|
+
return backup.startsWith(`${projectDir}/`) ? backup.slice(projectDir.length + 1) : backup;
|
|
4510
|
+
}
|
|
4511
|
+
|
|
4512
|
+
// src/antigravity/transform.ts
|
|
4513
|
+
init_esm_shims();
|
|
4514
|
+
import { join as join15 } from "path";
|
|
4515
|
+
|
|
4516
|
+
// src/codex/skills.ts
|
|
4517
|
+
init_esm_shims();
|
|
4518
|
+
import { chmodSync, existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
|
|
4519
|
+
import { join as join14 } from "path";
|
|
4520
|
+
function renderBundledSkill(source) {
|
|
4521
|
+
const trimmed = source.trimEnd();
|
|
4522
|
+
const lines2 = trimmed.split(/\r?\n/);
|
|
4523
|
+
if (lines2[0] !== "---") {
|
|
4524
|
+
return `${portBody(trimmed)}
|
|
4525
|
+
`;
|
|
4526
|
+
}
|
|
4527
|
+
let secondDelimAt = -1;
|
|
4528
|
+
for (let i = 1; i < lines2.length; i++) {
|
|
4529
|
+
if (lines2[i] === "---") {
|
|
4530
|
+
secondDelimAt = i;
|
|
4531
|
+
break;
|
|
4532
|
+
}
|
|
4533
|
+
}
|
|
4534
|
+
if (secondDelimAt < 0) {
|
|
4535
|
+
return `${portBody(trimmed)}
|
|
4536
|
+
`;
|
|
4537
|
+
}
|
|
4538
|
+
const frontmatter = lines2.slice(0, secondDelimAt + 1).join("\n");
|
|
4539
|
+
const body = lines2.slice(secondDelimAt + 1).join("\n");
|
|
4540
|
+
return `${frontmatter}
|
|
4541
|
+
${portBody(body)}
|
|
4542
|
+
`;
|
|
4543
|
+
}
|
|
4544
|
+
function portBody(body) {
|
|
4545
|
+
return renameSlashes(body).replace(/CLAUDE_PROJECT_DIR/g, "CODEX_PROJECT_DIR").trimEnd();
|
|
4546
|
+
}
|
|
4547
|
+
function writeBundledSkillDirs(params) {
|
|
4548
|
+
const { harnessRoot, projectDir, skillIds, writer } = params;
|
|
4549
|
+
const written = [];
|
|
4550
|
+
for (const id of skillIds) {
|
|
4551
|
+
const srcDir = join14(harnessRoot, "templates/skills", id);
|
|
4552
|
+
const srcSkill = join14(srcDir, "SKILL.md");
|
|
4553
|
+
if (!existsSync11(srcSkill)) {
|
|
4554
|
+
continue;
|
|
4555
|
+
}
|
|
4556
|
+
const targetDir = join14(projectDir, ".agents", "skills", id);
|
|
4557
|
+
const targetSkill = join14(targetDir, "SKILL.md");
|
|
4558
|
+
if (!writer.write(targetSkill, renderBundledSkill(readFileSync10(srcSkill, "utf8")))) {
|
|
4559
|
+
continue;
|
|
4560
|
+
}
|
|
4561
|
+
written.push(targetSkill);
|
|
4562
|
+
for (const rel of listFilesRecursive(srcDir)) {
|
|
4563
|
+
if (rel === "SKILL.md") continue;
|
|
4564
|
+
if (rel.split("/").some((segment2) => segment2.startsWith("."))) continue;
|
|
4565
|
+
const segments = rel.split("/");
|
|
4566
|
+
const raw = readFileSync10(join14(srcDir, ...segments), "utf8");
|
|
4567
|
+
const ported = rel.endsWith(".md") || rel.endsWith(".sh") ? `${portBody(raw)}
|
|
4568
|
+
` : raw;
|
|
4569
|
+
const target = join14(targetDir, ...segments);
|
|
4570
|
+
if (!writer.write(target, ported, { createInRefresh: true })) continue;
|
|
4571
|
+
if (rel.endsWith(".sh")) chmodSync(target, 493);
|
|
4572
|
+
written.push(target);
|
|
4573
|
+
}
|
|
4574
|
+
}
|
|
4575
|
+
return written;
|
|
4576
|
+
}
|
|
4577
|
+
|
|
4578
|
+
// src/antigravity/transform.ts
|
|
4579
|
+
function antigravityFrontmatter(rule) {
|
|
4580
|
+
if (rule.paths.length === 0) return "---\ntrigger: always_on\n---\n\n";
|
|
4581
|
+
return `---
|
|
4582
|
+
trigger: glob
|
|
4583
|
+
globs: "${rule.paths.join(", ")}"
|
|
4584
|
+
---
|
|
4585
|
+
|
|
4586
|
+
`;
|
|
4587
|
+
}
|
|
4588
|
+
function runAntigravityTransform(params) {
|
|
4589
|
+
const {
|
|
4590
|
+
harnessRoot,
|
|
4591
|
+
projectDir,
|
|
4592
|
+
selectedInternalSkills = [],
|
|
4593
|
+
rules = [],
|
|
4594
|
+
baseline,
|
|
4595
|
+
refreshOnly
|
|
4596
|
+
} = params;
|
|
4597
|
+
const writer = createOwnedWriter(projectDir, baseline, {
|
|
4598
|
+
refreshOnly: refreshOnly ?? false,
|
|
4599
|
+
...params.journal === void 0 ? {} : { journal: params.journal }
|
|
4600
|
+
});
|
|
4601
|
+
const { skillIds, skillWriter } = agentsSkillSlot({
|
|
4602
|
+
projectDir,
|
|
4603
|
+
selectedInternalSkills,
|
|
4604
|
+
refreshOnly: refreshOnly ?? false,
|
|
4605
|
+
writer
|
|
4606
|
+
});
|
|
4607
|
+
const antigravityInstalled = params.installedByRecord === true;
|
|
4608
|
+
const harnessRuleFiles = [];
|
|
4609
|
+
for (const rule of portRules(harnessRoot, rules)) {
|
|
4610
|
+
const target = join15(projectDir, ".agents", "rules", `${rule.name}.md`);
|
|
4611
|
+
const content = `${antigravityFrontmatter(rule)}${rule.body}
|
|
4612
|
+
`;
|
|
4613
|
+
if (!writer.write(target, content, { createInRefresh: antigravityInstalled })) {
|
|
4614
|
+
continue;
|
|
4615
|
+
}
|
|
4616
|
+
harnessRuleFiles.push(target);
|
|
4617
|
+
}
|
|
4618
|
+
const skillFiles = writeBundledSkillDirs({
|
|
4619
|
+
harnessRoot,
|
|
4620
|
+
projectDir,
|
|
4621
|
+
skillIds,
|
|
4622
|
+
writer: skillWriter
|
|
4623
|
+
});
|
|
4624
|
+
return {
|
|
4625
|
+
harnessRuleFiles,
|
|
4626
|
+
skillFiles,
|
|
4627
|
+
ownership: writer.result()
|
|
4628
|
+
};
|
|
4629
|
+
}
|
|
4630
|
+
|
|
4631
|
+
// src/codex/opt-in.ts
|
|
4632
|
+
init_esm_shims();
|
|
4633
|
+
import { homedir } from "os";
|
|
4634
|
+
import { join as join16 } from "path";
|
|
4635
|
+
|
|
4636
|
+
// src/codex/trust-entry.ts
|
|
4637
|
+
init_esm_shims();
|
|
4638
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync11, realpathSync as realpathSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
4639
|
+
import { dirname as dirname4 } from "path";
|
|
4640
|
+
var UNSAFE_APPEND = "adding the entry would make this file unreadable TOML \u2014 left untouched";
|
|
4641
|
+
function trustKeysFor(projectDir) {
|
|
4642
|
+
let canonical = projectDir;
|
|
4643
|
+
try {
|
|
4644
|
+
canonical = realpathSync2.native(projectDir);
|
|
4645
|
+
} catch {
|
|
4368
4646
|
}
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4647
|
+
const keys = [canonical, projectDir];
|
|
4648
|
+
const out = process.platform === "win32" ? keys.map((k2) => k2.replace(/[A-Z]/g, (ch) => ch.toLowerCase())) : keys;
|
|
4649
|
+
return [...new Set(out)];
|
|
4650
|
+
}
|
|
4651
|
+
function tomlBasicString(s) {
|
|
4652
|
+
let out = '"';
|
|
4653
|
+
for (const ch of s) {
|
|
4654
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
4655
|
+
if (ch === "\\") out += "\\\\";
|
|
4656
|
+
else if (ch === '"') out += '\\"';
|
|
4657
|
+
else if (code <= 31 || code === 127)
|
|
4658
|
+
out += `\\u${code.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
4659
|
+
else out += ch;
|
|
4372
4660
|
}
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4661
|
+
return `${out}"`;
|
|
4662
|
+
}
|
|
4663
|
+
function isTable2(v2) {
|
|
4664
|
+
return typeof v2 === "object" && v2 !== null && !Array.isArray(v2) && !(v2 instanceof Date);
|
|
4665
|
+
}
|
|
4666
|
+
function findEntry(parsed, keys) {
|
|
4667
|
+
const projects = parsed.projects;
|
|
4668
|
+
if (!isTable2(projects)) return null;
|
|
4669
|
+
for (const key of keys) {
|
|
4670
|
+
if (!Object.hasOwn(projects, key)) continue;
|
|
4671
|
+
const entry = projects[key];
|
|
4672
|
+
return { key, level: isTable2(entry) ? entry.trust_level : void 0 };
|
|
4673
|
+
}
|
|
4674
|
+
return null;
|
|
4675
|
+
}
|
|
4676
|
+
function registerTrustEntry(opts) {
|
|
4677
|
+
const { configPath, projectDir } = opts;
|
|
4678
|
+
const keys = trustKeysFor(projectDir);
|
|
4679
|
+
const key = keys[0] ?? projectDir;
|
|
4680
|
+
try {
|
|
4681
|
+
const existing = existsSync12(configPath) ? readFileSync11(configPath, "utf8") : "";
|
|
4682
|
+
let parsed;
|
|
4683
|
+
try {
|
|
4684
|
+
parsed = parse(existing);
|
|
4685
|
+
} catch (e2) {
|
|
4686
|
+
return {
|
|
4687
|
+
status: "unreadable",
|
|
4688
|
+
message: `not valid TOML (${firstLine(e2)}) \u2014 left untouched`
|
|
4689
|
+
};
|
|
4690
|
+
}
|
|
4691
|
+
const found = findEntry(parsed, keys);
|
|
4692
|
+
if (found !== null) {
|
|
4693
|
+
if (found.level === "trusted") return { status: "already-present", key: found.key };
|
|
4694
|
+
const level = typeof found.level === "string" ? `trust_level = ${tomlBasicString(found.level)}` : "no trust_level";
|
|
4695
|
+
return { status: "left", key: found.key, message: `already present (${level}) \u2014 left as is` };
|
|
4696
|
+
}
|
|
4697
|
+
const next = `${existing}
|
|
4698
|
+
[projects.${tomlBasicString(key)}]
|
|
4699
|
+
trust_level = "trusted"
|
|
4700
|
+
`;
|
|
4701
|
+
if (findEntry(readable(next) ?? {}, [key])?.level !== "trusted") {
|
|
4702
|
+
return { status: "error", key, message: UNSAFE_APPEND };
|
|
4703
|
+
}
|
|
4704
|
+
mkdirSync4(dirname4(configPath), { recursive: true });
|
|
4705
|
+
writeFileSync6(configPath, next);
|
|
4706
|
+
return { status: "registered", key };
|
|
4707
|
+
} catch (e2) {
|
|
4708
|
+
return {
|
|
4709
|
+
status: "error",
|
|
4710
|
+
key,
|
|
4711
|
+
message: e2 instanceof Error ? e2.message : String(e2)
|
|
4712
|
+
};
|
|
4713
|
+
}
|
|
4714
|
+
}
|
|
4715
|
+
function hasTrustEntry(configContent, projectDir) {
|
|
4716
|
+
const parsed = readable(configContent);
|
|
4717
|
+
return parsed !== null && findEntry(parsed, trustKeysFor(projectDir))?.level === "trusted";
|
|
4718
|
+
}
|
|
4719
|
+
function readable(text) {
|
|
4720
|
+
try {
|
|
4721
|
+
return parse(text);
|
|
4722
|
+
} catch {
|
|
4723
|
+
return null;
|
|
4384
4724
|
}
|
|
4385
|
-
const fileRemoved = params.removeIfEmpty === true && res.removed.length > 0 && res.empty;
|
|
4386
|
-
if (params.write && fileRemoved) rmSync(abs);
|
|
4387
|
-
else if (params.write && res.changed) writeFileSync6(abs, res.text);
|
|
4388
|
-
const action = res.removed.length > 0 ? "removed" : res.kept.length > 0 ? "left" : "none";
|
|
4389
|
-
return out(action, {
|
|
4390
|
-
removed: res.removed,
|
|
4391
|
-
kept: res.kept,
|
|
4392
|
-
portions: toPortions(path, res.portions),
|
|
4393
|
-
...fileRemoved ? { fileRemoved } : {}
|
|
4394
|
-
});
|
|
4395
4725
|
}
|
|
4726
|
+
function firstLine(e2) {
|
|
4727
|
+
return (e2 instanceof Error ? e2.message : String(e2)).split("\n")[0] ?? "";
|
|
4728
|
+
}
|
|
4729
|
+
|
|
4730
|
+
// src/codex/opt-in.ts
|
|
4731
|
+
function runCodexOptIn(ctx) {
|
|
4732
|
+
const codexHome = ctx.codexHome ?? join16(homedir(), ".codex");
|
|
4733
|
+
const configPath = join16(codexHome, "config.toml");
|
|
4734
|
+
const result = registerTrustEntry({ configPath, projectDir: ctx.projectDir });
|
|
4735
|
+
return {
|
|
4736
|
+
trustEntry: {
|
|
4737
|
+
enabled: true,
|
|
4738
|
+
status: result.status,
|
|
4739
|
+
configPath,
|
|
4740
|
+
...result.message ? { message: result.message } : {},
|
|
4741
|
+
...result.key ? { key: result.key } : {}
|
|
4742
|
+
}
|
|
4743
|
+
};
|
|
4744
|
+
}
|
|
4745
|
+
|
|
4746
|
+
// src/codex/transform.ts
|
|
4747
|
+
init_esm_shims();
|
|
4748
|
+
import { chmodSync as chmodSync2, existsSync as existsSync13, readFileSync as readFileSync13 } from "fs";
|
|
4749
|
+
import { basename as basename2, join as join18 } from "path";
|
|
4396
4750
|
|
|
4397
4751
|
// src/codex/config-toml.ts
|
|
4398
4752
|
init_esm_shims();
|
|
4753
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
4754
|
+
import { join as join17 } from "path";
|
|
4399
4755
|
var TABLES_REGION = "tables";
|
|
4400
4756
|
function configRegions(rendered) {
|
|
4401
4757
|
const lines2 = rendered.split(/\r?\n/);
|
|
@@ -4431,6 +4787,20 @@ function hasLegacyHarnessHook(disk) {
|
|
|
4431
4787
|
const { session_start: _flat, ...rest } = hooks;
|
|
4432
4788
|
return flat && !callsHarnessHook({ ...parsed, hooks: rest });
|
|
4433
4789
|
}
|
|
4790
|
+
var RETIRED_FEATURE_KEYS = ["codex_hooks", "child_agents_md"];
|
|
4791
|
+
function retiredFeatureKeys(disk) {
|
|
4792
|
+
const features = readToml(disk)?.features;
|
|
4793
|
+
if (typeof features !== "object" || features === null || Array.isArray(features)) return [];
|
|
4794
|
+
return RETIRED_FEATURE_KEYS.filter((k2) => Object.hasOwn(features, k2));
|
|
4795
|
+
}
|
|
4796
|
+
function retiredFeaturesLeftBehind(projectDir, r) {
|
|
4797
|
+
if (r.action !== "left" && !r.leftAsIs.includes(TABLES_REGION)) return [];
|
|
4798
|
+
try {
|
|
4799
|
+
return retiredFeatureKeys(readFileSync12(join17(projectDir, ".codex/config.toml"), "utf8"));
|
|
4800
|
+
} catch {
|
|
4801
|
+
return [];
|
|
4802
|
+
}
|
|
4803
|
+
}
|
|
4434
4804
|
function flatHookCommands(value) {
|
|
4435
4805
|
if (!Array.isArray(value)) return [];
|
|
4436
4806
|
return value.flatMap((entry) => {
|
|
@@ -4444,7 +4814,7 @@ function isChangedLegacyConfig(disk, baselineSha) {
|
|
|
4444
4814
|
return parsed !== null && callsHarnessHook(parsed);
|
|
4445
4815
|
}
|
|
4446
4816
|
function renderConfigToml(params) {
|
|
4447
|
-
const substituted = params.template.replaceAll("{PROJECT_NAME}", params.projectName).replaceAll("{PROJECT_DIR}", params.projectDir).replaceAll("{GITHUB_TOKEN}", "${GITHUB_TOKEN}");
|
|
4817
|
+
const substituted = params.template.replaceAll("{PROJECT_NAME}", params.projectName).replaceAll("{PROJECT_DIR}", tomlBasicString(params.projectDir).slice(1, -1)).replaceAll("{GITHUB_TOKEN}", "${GITHUB_TOKEN}");
|
|
4448
4818
|
if (!params.mcp) {
|
|
4449
4819
|
return substituted;
|
|
4450
4820
|
}
|
|
@@ -4481,20 +4851,16 @@ function runCodexTransform(params) {
|
|
|
4481
4851
|
harnessRoot,
|
|
4482
4852
|
projectDir,
|
|
4483
4853
|
selectedInternalSkills = [],
|
|
4484
|
-
rules = [],
|
|
4485
4854
|
mcp,
|
|
4486
4855
|
baseline,
|
|
4487
4856
|
refreshOnly,
|
|
4488
|
-
writeAgentsMd: writesAgentsMd = true,
|
|
4489
4857
|
shared: shared2 = {}
|
|
4490
4858
|
} = params;
|
|
4491
4859
|
const writer = createOwnedWriter(projectDir, baseline, {
|
|
4492
4860
|
refreshOnly: refreshOnly ?? false,
|
|
4493
4861
|
...params.journal === void 0 ? {} : { journal: params.journal }
|
|
4494
4862
|
});
|
|
4495
|
-
const
|
|
4496
|
-
const agentsTemplate = readRequired(join15(harnessRoot, "templates/codex/AGENTS.md.template"));
|
|
4497
|
-
const configTemplate = readRequired(join15(harnessRoot, "templates/codex/config.toml.template"));
|
|
4863
|
+
const configTemplate = readRequired2(join18(harnessRoot, "templates/codex/config.toml.template"));
|
|
4498
4864
|
const projectName = basename2(projectDir);
|
|
4499
4865
|
const { skillIds, skillWriter } = agentsSkillSlot({
|
|
4500
4866
|
projectDir,
|
|
@@ -4502,37 +4868,7 @@ function runCodexTransform(params) {
|
|
|
4502
4868
|
refreshOnly: refreshOnly ?? false,
|
|
4503
4869
|
writer
|
|
4504
4870
|
});
|
|
4505
|
-
const
|
|
4506
|
-
const seededContext = !writesAgentsMd || refreshOnly || existsSync13(agentsMdPath) ? null : seedAgentsMdProjectContext(projectDir);
|
|
4507
|
-
ensureDir(projectDir);
|
|
4508
|
-
let agentsMd = null;
|
|
4509
|
-
if (writesAgentsMd) {
|
|
4510
|
-
const agentsMdOut = renderAgentsMd({
|
|
4511
|
-
template: agentsTemplate,
|
|
4512
|
-
claudeMd,
|
|
4513
|
-
projectName,
|
|
4514
|
-
// ADR-085 — 상시 스킬 안내는 앵커가 아니라 여기(프로젝트 맥락)에, 깔린 것만.
|
|
4515
|
-
// #503 — 그 조각은 설치자 소유 절 안에 사니 마커로 감싼다.
|
|
4516
|
-
// #530 — `skillIds` 를 쓴다(= 깔린 것 + **이번 실행이 만들 것**).
|
|
4517
|
-
projectContext: withMarkedContinuousSkillsNote(
|
|
4518
|
-
seededContext ?? renderFillScaffold("agents-md"),
|
|
4519
|
-
skillIds
|
|
4520
|
-
),
|
|
4521
|
-
// Codex 는 룰 디렉터리가 없다 — 룰이 AGENTS.md 본문에 들어가야 도달한다(§Harness Rules).
|
|
4522
|
-
harnessRules: renderRulesBlock(portRules(harnessRoot, rules))
|
|
4523
|
-
});
|
|
4524
|
-
agentsMd = writeAgentsMd({
|
|
4525
|
-
projectDir,
|
|
4526
|
-
rendered: agentsMdOut,
|
|
4527
|
-
template: agentsTemplate,
|
|
4528
|
-
anchorTitle: anchorTitle(claudeMd),
|
|
4529
|
-
writer,
|
|
4530
|
-
baseline,
|
|
4531
|
-
record: shared2,
|
|
4532
|
-
refreshOnly: refreshOnly ?? false
|
|
4533
|
-
});
|
|
4534
|
-
}
|
|
4535
|
-
const configTomlPath = join15(projectDir, CONFIG_TOML);
|
|
4871
|
+
const configTomlPath = join18(projectDir, CONFIG_TOML);
|
|
4536
4872
|
const configToml = writeConfigToml({
|
|
4537
4873
|
projectDir,
|
|
4538
4874
|
regions: configRegions(
|
|
@@ -4543,15 +4879,15 @@ function runCodexTransform(params) {
|
|
|
4543
4879
|
record: shared2,
|
|
4544
4880
|
refreshOnly: refreshOnly ?? false
|
|
4545
4881
|
});
|
|
4546
|
-
const hookDir =
|
|
4882
|
+
const hookDir = join18(projectDir, ".codex/hooks");
|
|
4547
4883
|
const hookFiles = [];
|
|
4548
4884
|
for (const hook of HOOK_NAMES) {
|
|
4549
|
-
const src =
|
|
4885
|
+
const src = join18(harnessRoot, "templates/hooks", `${hook}.sh`);
|
|
4550
4886
|
if (!existsSync13(src)) {
|
|
4551
4887
|
continue;
|
|
4552
4888
|
}
|
|
4553
|
-
const ported =
|
|
4554
|
-
const target =
|
|
4889
|
+
const ported = readFileSync13(src, "utf8").replace(ENV_VAR_RENAME, "CODEX_PROJECT_DIR");
|
|
4890
|
+
const target = join18(hookDir, `${hook}.sh`);
|
|
4555
4891
|
if (!writer.write(target, ported)) continue;
|
|
4556
4892
|
chmodSync2(target, 493);
|
|
4557
4893
|
hookFiles.push(target);
|
|
@@ -4563,11 +4899,8 @@ function runCodexTransform(params) {
|
|
|
4563
4899
|
writer: skillWriter
|
|
4564
4900
|
});
|
|
4565
4901
|
return {
|
|
4566
|
-
agentsMdPath,
|
|
4567
|
-
agentsMdSeededFrom: seededContext === null ? null : "CLAUDE.md",
|
|
4568
4902
|
configTomlPath,
|
|
4569
4903
|
configToml,
|
|
4570
|
-
agentsMd,
|
|
4571
4904
|
hookFiles,
|
|
4572
4905
|
skillFiles,
|
|
4573
4906
|
ownership: writer.result()
|
|
@@ -4576,8 +4909,8 @@ function runCodexTransform(params) {
|
|
|
4576
4909
|
var CONFIG_TOML = ".codex/config.toml";
|
|
4577
4910
|
var LEGACY_CHANGED = "written by an earlier harness version and changed since \u2014 left as is; harness part not added";
|
|
4578
4911
|
function writeConfigToml(args) {
|
|
4579
|
-
const abs =
|
|
4580
|
-
const disk = existsSync13(abs) ?
|
|
4912
|
+
const abs = join18(args.projectDir, CONFIG_TOML);
|
|
4913
|
+
const disk = existsSync13(abs) ? readFileSync13(abs, "utf8") : null;
|
|
4581
4914
|
const regions = disk === null ? null : ADAPTERS["toml-region"].read(disk);
|
|
4582
4915
|
if (disk !== null && regions?.size === 0 && isChangedLegacyConfig(disk, args.baseline.get(CONFIG_TOML))) {
|
|
4583
4916
|
return {
|
|
@@ -4606,16 +4939,16 @@ function writeConfigToml(args) {
|
|
|
4606
4939
|
refreshOnly: args.refreshOnly
|
|
4607
4940
|
});
|
|
4608
4941
|
}
|
|
4609
|
-
function
|
|
4942
|
+
function readRequired2(path) {
|
|
4610
4943
|
if (!existsSync13(path)) {
|
|
4611
4944
|
throw new Error(`Codex transform: required source missing: ${path}`);
|
|
4612
4945
|
}
|
|
4613
|
-
return
|
|
4946
|
+
return readFileSync13(path, "utf8");
|
|
4614
4947
|
}
|
|
4615
4948
|
|
|
4616
4949
|
// src/mcp-merge.ts
|
|
4617
4950
|
init_esm_shims();
|
|
4618
|
-
import { existsSync as existsSync14, readFileSync as
|
|
4951
|
+
import { existsSync as existsSync14, readFileSync as readFileSync14 } from "fs";
|
|
4619
4952
|
function parseTrackMcpMap(raw) {
|
|
4620
4953
|
const rows = [];
|
|
4621
4954
|
for (const line of raw.split(/\r?\n/)) {
|
|
@@ -4669,8 +5002,8 @@ function mergeMcpServers(base, rows, tracks, chosen = [], dropped = []) {
|
|
|
4669
5002
|
return out;
|
|
4670
5003
|
}
|
|
4671
5004
|
function composeMcpJson(opts) {
|
|
4672
|
-
const base = JSON.parse(
|
|
4673
|
-
const mapRaw = existsSync14(opts.trackMapPath) ?
|
|
5005
|
+
const base = JSON.parse(readFileSync14(opts.templateMcpPath, "utf8"));
|
|
5006
|
+
const mapRaw = existsSync14(opts.trackMapPath) ? readFileSync14(opts.trackMapPath, "utf8") : "";
|
|
4674
5007
|
const rows = parseTrackMcpMap(mapRaw);
|
|
4675
5008
|
return mergeMcpServers(base, rows, opts.tracks, opts.chosen(rows), opts.dropped?.(rows) ?? []);
|
|
4676
5009
|
}
|
|
@@ -4712,19 +5045,8 @@ function recordedMcpAssetIds(log) {
|
|
|
4712
5045
|
|
|
4713
5046
|
// src/opencode/transform.ts
|
|
4714
5047
|
init_esm_shims();
|
|
4715
|
-
import { existsSync as existsSync15, readdirSync, readFileSync as
|
|
4716
|
-
import {
|
|
4717
|
-
|
|
4718
|
-
// src/opencode/agents-md.ts
|
|
4719
|
-
init_esm_shims();
|
|
4720
|
-
function renameSlashes2(text) {
|
|
4721
|
-
return text.replaceAll("/uzys:", "/uzys-");
|
|
4722
|
-
}
|
|
4723
|
-
function renderAgentsMd2(params) {
|
|
4724
|
-
const body = wrapHarnessBlock(ANCHOR_BLOCK, params.claudeMd.replace(/^#\s+.*\r?\n/, "").trim());
|
|
4725
|
-
const replaced = params.template.replaceAll("{PROJECT_NAME}", params.projectName).replaceAll("{PROJECT_RULES}", body).replaceAll("{PROJECT_CONTEXT}", params.projectContext).replaceAll("{HARNESS_RULES}", params.harnessRules ?? "");
|
|
4726
|
-
return renameSlashes2(replaced);
|
|
4727
|
-
}
|
|
5048
|
+
import { existsSync as existsSync15, readdirSync as readdirSync2, readFileSync as readFileSync15, unlinkSync } from "fs";
|
|
5049
|
+
import { join as join19 } from "path";
|
|
4728
5050
|
|
|
4729
5051
|
// src/opencode/opencode-json.ts
|
|
4730
5052
|
init_esm_shims();
|
|
@@ -4763,7 +5085,6 @@ function runOpencodeTransform(params) {
|
|
|
4763
5085
|
harnessRoot,
|
|
4764
5086
|
projectDir,
|
|
4765
5087
|
selectedInternalSkills = [],
|
|
4766
|
-
rules = [],
|
|
4767
5088
|
mcp,
|
|
4768
5089
|
baseline,
|
|
4769
5090
|
refreshOnly,
|
|
@@ -4773,12 +5094,9 @@ function runOpencodeTransform(params) {
|
|
|
4773
5094
|
refreshOnly: refreshOnly ?? false,
|
|
4774
5095
|
...params.journal === void 0 ? {} : { journal: params.journal }
|
|
4775
5096
|
});
|
|
4776
|
-
const
|
|
4777
|
-
|
|
4778
|
-
const opencodeTemplate = readRequired2(
|
|
4779
|
-
join16(harnessRoot, "templates/opencode/opencode.json.template")
|
|
5097
|
+
const opencodeTemplate = readRequired3(
|
|
5098
|
+
join19(harnessRoot, "templates/opencode/opencode.json.template")
|
|
4780
5099
|
);
|
|
4781
|
-
const projectName = basename3(projectDir);
|
|
4782
5100
|
const { skillIds, skillWriter } = agentsSkillSlot({
|
|
4783
5101
|
projectDir,
|
|
4784
5102
|
selectedInternalSkills,
|
|
@@ -4786,36 +5104,7 @@ function runOpencodeTransform(params) {
|
|
|
4786
5104
|
writer
|
|
4787
5105
|
});
|
|
4788
5106
|
ensureDir(projectDir);
|
|
4789
|
-
const
|
|
4790
|
-
const seededContext = refreshOnly || existsSync15(agentsMdPath) ? null : seedAgentsMdProjectContext(projectDir);
|
|
4791
|
-
const agentsMdOut = renderAgentsMd2({
|
|
4792
|
-
template: agentsTemplate,
|
|
4793
|
-
claudeMd,
|
|
4794
|
-
projectName,
|
|
4795
|
-
// ADR-085 — 상시 스킬 안내는 앵커가 아니라 여기(프로젝트 맥락)에, 깔린 것만.
|
|
4796
|
-
// #503 — 그 조각은 설치자 소유 절 안에 사니 마커로 감싼다 (codex 와 같은 마커).
|
|
4797
|
-
// #531 — `skillIds` 를 쓴다(= 깔린 것 + **이번 실행이 만들 것**). 안 그러면 이번에 새로
|
|
4798
|
-
// 깐 상시 스킬이 다음 update 까지 안내에서 빠져 설치자 에이전트가 영영 안 연다.
|
|
4799
|
-
projectContext: withMarkedContinuousSkillsNote(
|
|
4800
|
-
seededContext ?? renderFillScaffold("agents-md"),
|
|
4801
|
-
skillIds
|
|
4802
|
-
),
|
|
4803
|
-
// codex 와 **같은 파일**(프로젝트 루트 `AGENTS.md`)이다. 두 transform 이 서로 다른 본문을
|
|
4804
|
-
// 쓰면 나중에 도는 쪽이 앞선 쪽을 덮어써, codex+opencode 조합에서 룰이 통째로 사라진다
|
|
4805
|
-
// (독립 검증 C-1 실측). 같은 내용을 쓰면 순서가 결과를 바꾸지 않는다.
|
|
4806
|
-
harnessRules: renderRulesBlock(portRules(harnessRoot, rules))
|
|
4807
|
-
});
|
|
4808
|
-
const agentsMd = writeAgentsMd({
|
|
4809
|
-
projectDir,
|
|
4810
|
-
rendered: agentsMdOut,
|
|
4811
|
-
template: agentsTemplate,
|
|
4812
|
-
anchorTitle: anchorTitle(claudeMd),
|
|
4813
|
-
writer,
|
|
4814
|
-
baseline,
|
|
4815
|
-
record: shared2,
|
|
4816
|
-
refreshOnly: refreshOnly ?? false
|
|
4817
|
-
});
|
|
4818
|
-
const opencodeJsonPath = join16(projectDir, OPENCODE_JSON);
|
|
5107
|
+
const opencodeJsonPath = join19(projectDir, OPENCODE_JSON);
|
|
4819
5108
|
const render = renderOpencodeMcp(mcp);
|
|
4820
5109
|
const opencodeJson = writeShared({
|
|
4821
5110
|
projectDir,
|
|
@@ -4834,13 +5123,13 @@ function runOpencodeTransform(params) {
|
|
|
4834
5123
|
writer: skillWriter
|
|
4835
5124
|
});
|
|
4836
5125
|
const retiredCommands = [];
|
|
4837
|
-
const cmdDir =
|
|
5126
|
+
const cmdDir = join19(projectDir, ".opencode/commands");
|
|
4838
5127
|
if (existsSync15(cmdDir)) {
|
|
4839
|
-
for (const entry of
|
|
5128
|
+
for (const entry of readdirSync2(cmdDir)) {
|
|
4840
5129
|
if (!entry.endsWith(".md")) continue;
|
|
4841
5130
|
const id = entry.slice(0, -3);
|
|
4842
|
-
if (!existsSync15(
|
|
4843
|
-
const victim =
|
|
5131
|
+
if (!existsSync15(join19(harnessRoot, "templates/skills", id, "SKILL.md"))) continue;
|
|
5132
|
+
const victim = join19(cmdDir, entry);
|
|
4844
5133
|
if (writer.skipOutside(victim)) continue;
|
|
4845
5134
|
try {
|
|
4846
5135
|
backupFile(victim);
|
|
@@ -4851,22 +5140,19 @@ function runOpencodeTransform(params) {
|
|
|
4851
5140
|
}
|
|
4852
5141
|
}
|
|
4853
5142
|
return {
|
|
4854
|
-
agentsMdPath,
|
|
4855
|
-
agentsMdSeededFrom: seededContext === null ? null : "CLAUDE.md",
|
|
4856
5143
|
opencodeJsonPath,
|
|
4857
5144
|
opencodeJson,
|
|
4858
|
-
agentsMd,
|
|
4859
5145
|
skillFiles,
|
|
4860
5146
|
retiredCommands,
|
|
4861
5147
|
ownership: writer.result()
|
|
4862
5148
|
};
|
|
4863
5149
|
}
|
|
4864
5150
|
var OPENCODE_JSON = "opencode.json";
|
|
4865
|
-
function
|
|
5151
|
+
function readRequired3(path) {
|
|
4866
5152
|
if (!existsSync15(path)) {
|
|
4867
5153
|
throw new Error(`OpenCode transform: required source missing: ${path}`);
|
|
4868
5154
|
}
|
|
4869
|
-
return
|
|
5155
|
+
return readFileSync15(path, "utf8");
|
|
4870
5156
|
}
|
|
4871
5157
|
|
|
4872
5158
|
// src/cli-transforms.ts
|
|
@@ -4888,7 +5174,7 @@ function trackDefaultMcpAssetIds(harnessRoot, tracks) {
|
|
|
4888
5174
|
}
|
|
4889
5175
|
function trackRows(harnessRoot) {
|
|
4890
5176
|
const { trackMapPath } = mcpSources(harnessRoot);
|
|
4891
|
-
return existsSync16(trackMapPath) ? parseTrackMcpMap(
|
|
5177
|
+
return existsSync16(trackMapPath) ? parseTrackMcpMap(readFileSync16(trackMapPath, "utf8")) : [];
|
|
4892
5178
|
}
|
|
4893
5179
|
function renderEverySelectableMcp(harnessRoot, tracks) {
|
|
4894
5180
|
return composeMcpJson({
|
|
@@ -4908,8 +5194,8 @@ function adapterExcluded(harnessRoot, excluded) {
|
|
|
4908
5194
|
}
|
|
4909
5195
|
function mcpSources(harnessRoot) {
|
|
4910
5196
|
return {
|
|
4911
|
-
templateMcpPath:
|
|
4912
|
-
trackMapPath:
|
|
5197
|
+
templateMcpPath: join20(harnessRoot, "templates/mcp.json"),
|
|
5198
|
+
trackMapPath: join20(harnessRoot, "templates/track-mcp-map.tsv")
|
|
4913
5199
|
};
|
|
4914
5200
|
}
|
|
4915
5201
|
var ALL_CLI_TARGETS = CLI_BASES;
|
|
@@ -4929,6 +5215,8 @@ function runCliTransforms(params) {
|
|
|
4929
5215
|
journal
|
|
4930
5216
|
} = params;
|
|
4931
5217
|
const journalParam = journal === void 0 ? {} : { journal };
|
|
5218
|
+
const legacyContext = readLegacyAntigravityContext(projectDir, previousExternal);
|
|
5219
|
+
const outside = createOutsideGuard(projectDir);
|
|
4932
5220
|
const baseline = new Map(previousExternal.map((f) => [f.path, f.sha256]));
|
|
4933
5221
|
const externalFiles = [];
|
|
4934
5222
|
const externalBackups = [];
|
|
@@ -4968,19 +5256,14 @@ function runCliTransforms(params) {
|
|
|
4968
5256
|
harnessRoot,
|
|
4969
5257
|
projectDir,
|
|
4970
5258
|
selectedInternalSkills,
|
|
4971
|
-
rules,
|
|
4972
5259
|
mcp,
|
|
4973
5260
|
baseline,
|
|
4974
5261
|
refreshOnly,
|
|
4975
5262
|
shared: shared2,
|
|
4976
|
-
// #550 — 같은 `AGENTS.md` 를 한 실행에서 쓰는 쪽은 하나다. opencode 가 뒤에서 자기 템플릿으로
|
|
4977
|
-
// 쓰면 codex 판은 어차피 같은 실행 안에서 덮였고(최종 파일 = opencode 판), 매 실행 codex 가
|
|
4978
|
-
// opencode 판을 자기 판으로 뒤집는 한 번의 쓰기가 설치자 편집분을 백업으로 쌓았다.
|
|
4979
|
-
writeAgentsMd: !cli.includes("opencode"),
|
|
4980
5263
|
...journalParam
|
|
4981
5264
|
});
|
|
4982
5265
|
absorb(codex);
|
|
4983
|
-
absorbShared(codex, [codex.
|
|
5266
|
+
absorbShared(codex, [codex.configToml]);
|
|
4984
5267
|
if (codexTrust) {
|
|
4985
5268
|
codexOptIn = runCodexOptIn({ projectDir });
|
|
4986
5269
|
journal?.trust(codexOptIn);
|
|
@@ -4993,7 +5276,6 @@ function runCliTransforms(params) {
|
|
|
4993
5276
|
harnessRoot,
|
|
4994
5277
|
projectDir,
|
|
4995
5278
|
selectedInternalSkills,
|
|
4996
|
-
rules,
|
|
4997
5279
|
mcp,
|
|
4998
5280
|
baseline,
|
|
4999
5281
|
refreshOnly,
|
|
@@ -5001,7 +5283,7 @@ function runCliTransforms(params) {
|
|
|
5001
5283
|
...journalParam
|
|
5002
5284
|
});
|
|
5003
5285
|
absorb(opencode);
|
|
5004
|
-
absorbShared(opencode, [opencode.
|
|
5286
|
+
absorbShared(opencode, [opencode.opencodeJson]);
|
|
5005
5287
|
journal?.done("opencode");
|
|
5006
5288
|
}
|
|
5007
5289
|
let antigravity = null;
|
|
@@ -5019,11 +5301,47 @@ function runCliTransforms(params) {
|
|
|
5019
5301
|
absorb(antigravity);
|
|
5020
5302
|
journal?.done("antigravity");
|
|
5021
5303
|
}
|
|
5304
|
+
const agentsSet = agentsMdClis([...cli, ...recordedClis2]);
|
|
5305
|
+
let agentsMd = null;
|
|
5306
|
+
if (agentsSet.length > 0) {
|
|
5307
|
+
const beyondThisRun = agentsSet.some((c3) => !cli.includes(c3));
|
|
5308
|
+
agentsMd = runAgentsMdTransform({
|
|
5309
|
+
harnessRoot,
|
|
5310
|
+
projectDir,
|
|
5311
|
+
clis: agentsSet,
|
|
5312
|
+
selectedInternalSkills,
|
|
5313
|
+
rules: beyondThisRun && params.cumulativeRules !== void 0 ? params.cumulativeRules : rules,
|
|
5314
|
+
baseline,
|
|
5315
|
+
refreshOnly,
|
|
5316
|
+
// ADR-099 R2 — 기록이 이 파일을 읽는 CLI 를 깔린 것으로 말하면 없어도 만든다(26.164.0 이하 Antigravity 단독 설치본은
|
|
5317
|
+
// 첫 update 에 이 파일이 생긴다)
|
|
5318
|
+
installedByRecord: agentsSet.some((c3) => recordedClis2.includes(c3)),
|
|
5319
|
+
shared: shared2,
|
|
5320
|
+
legacyContext,
|
|
5321
|
+
...journalParam
|
|
5322
|
+
});
|
|
5323
|
+
absorb(agentsMd);
|
|
5324
|
+
absorbShared(agentsMd, [agentsMd.agentsMd.shared]);
|
|
5325
|
+
}
|
|
5326
|
+
const anchorRetired = retireAntigravityAnchor({
|
|
5327
|
+
projectDir,
|
|
5328
|
+
previous: previousExternal,
|
|
5329
|
+
outside,
|
|
5330
|
+
hadContent: legacyContext !== null,
|
|
5331
|
+
moved: agentsMd?.agentsMdSeededFrom === LEGACY_ANTIGRAVITY_ANCHOR
|
|
5332
|
+
});
|
|
5333
|
+
if (anchorRetired?.backup) {
|
|
5334
|
+
externalBackups.push(anchorRetired.backup);
|
|
5335
|
+
externalBackedUp.push(anchorRetired.path);
|
|
5336
|
+
}
|
|
5337
|
+
externalOutside = mergeOutside(externalOutside, outside.list());
|
|
5022
5338
|
return {
|
|
5023
5339
|
codex,
|
|
5024
5340
|
codexOptIn,
|
|
5025
5341
|
opencode,
|
|
5026
5342
|
antigravity,
|
|
5343
|
+
agentsMd,
|
|
5344
|
+
anchorRetired,
|
|
5027
5345
|
externalFiles,
|
|
5028
5346
|
externalBackups,
|
|
5029
5347
|
externalUpdated,
|
|
@@ -5040,7 +5358,7 @@ function runCliTransforms(params) {
|
|
|
5040
5358
|
// src/excluded-still-there.ts
|
|
5041
5359
|
init_esm_shims();
|
|
5042
5360
|
import { existsSync as existsSync17 } from "fs";
|
|
5043
|
-
import { join as
|
|
5361
|
+
import { join as join21 } from "path";
|
|
5044
5362
|
function excludedStillThere(projectDir, excluded, baselineOnDisk, previousLog) {
|
|
5045
5363
|
const out = [];
|
|
5046
5364
|
const add = (id, catalog) => {
|
|
@@ -5053,7 +5371,7 @@ function excludedStillThere(projectDir, excluded, baselineOnDisk, previousLog) {
|
|
|
5053
5371
|
for (const id of excluded) {
|
|
5054
5372
|
if (INTERNAL_BUNDLED_SKILL_IDS.includes(id)) {
|
|
5055
5373
|
const there = [".claude/skills", ".agents/skills"].some(
|
|
5056
|
-
(d2) => existsSync17(
|
|
5374
|
+
(d2) => existsSync17(join21(projectDir, d2, id))
|
|
5057
5375
|
);
|
|
5058
5376
|
if (there) add(id, false);
|
|
5059
5377
|
} else {
|
|
@@ -5066,17 +5384,17 @@ function excludedStillThere(projectDir, excluded, baselineOnDisk, previousLog) {
|
|
|
5066
5384
|
function assetStillThere(projectDir, asset) {
|
|
5067
5385
|
if (asset.method !== "skill" || asset.scope === "global") return true;
|
|
5068
5386
|
if (asset.files !== void 0)
|
|
5069
|
-
return asset.files.some((f) => existsSync17(
|
|
5387
|
+
return asset.files.some((f) => existsSync17(join21(projectDir, f.path)));
|
|
5070
5388
|
const dir = asset.detail.skill ?? asset.id;
|
|
5071
|
-
return [".claude/skills", ".agents/skills"].some((d2) => existsSync17(
|
|
5389
|
+
return [".claude/skills", ".agents/skills"].some((d2) => existsSync17(join21(projectDir, d2, dir)));
|
|
5072
5390
|
}
|
|
5073
5391
|
|
|
5074
5392
|
// src/external-installer.ts
|
|
5075
5393
|
init_esm_shims();
|
|
5076
5394
|
import { spawnSync } from "child_process";
|
|
5077
|
-
import { existsSync as existsSync18, readFileSync as
|
|
5395
|
+
import { existsSync as existsSync18, readFileSync as readFileSync17 } from "fs";
|
|
5078
5396
|
import { homedir as homedir2 } from "os";
|
|
5079
|
-
import { join as
|
|
5397
|
+
import { join as join22 } from "path";
|
|
5080
5398
|
var SKILL_ROOTS = [".claude/skills", ".agents/skills"];
|
|
5081
5399
|
function skillRootOf(cli) {
|
|
5082
5400
|
return cli === "claude" ? ".claude/skills" : ".agents/skills";
|
|
@@ -5084,11 +5402,11 @@ function skillRootOf(cli) {
|
|
|
5084
5402
|
function snapshotSkillRoots(projectDir) {
|
|
5085
5403
|
const out = /* @__PURE__ */ new Map();
|
|
5086
5404
|
for (const root of SKILL_ROOTS) {
|
|
5087
|
-
if (outsideProjectTarget(projectDir,
|
|
5088
|
-
for (const rel of listFilesRecursive(
|
|
5405
|
+
if (outsideProjectTarget(projectDir, join22(projectDir, root)) !== null) continue;
|
|
5406
|
+
for (const rel of listFilesRecursive(join22(projectDir, root))) {
|
|
5089
5407
|
const path = `${root}/${rel}`;
|
|
5090
5408
|
try {
|
|
5091
|
-
out.set(path, hashContent(
|
|
5409
|
+
out.set(path, hashContent(readFileSync17(join22(projectDir, path), "utf8")));
|
|
5092
5410
|
} catch {
|
|
5093
5411
|
}
|
|
5094
5412
|
}
|
|
@@ -5165,7 +5483,7 @@ function installOne(asset, ctx) {
|
|
|
5165
5483
|
return runSpawn(asset, ctx.spawn, "npx", buildSkillArgs(method, ctx.cli, ctx.scope), cwd);
|
|
5166
5484
|
}
|
|
5167
5485
|
const outside = SKILL_ROOTS.flatMap((root) => {
|
|
5168
|
-
const hit = outsideProjectTarget(cwd,
|
|
5486
|
+
const hit = outsideProjectTarget(cwd, join22(cwd, root));
|
|
5169
5487
|
return hit === null ? [] : [{ root, target: hit.target }];
|
|
5170
5488
|
});
|
|
5171
5489
|
const outRoots = new Set(outside.map((o) => o.root));
|
|
@@ -5346,9 +5664,9 @@ function detectVersion(method, spawn, scope, projectDir) {
|
|
|
5346
5664
|
case "plugin":
|
|
5347
5665
|
return detectPluginVersion(method.pluginId, scope, projectDir);
|
|
5348
5666
|
case "npm": {
|
|
5349
|
-
const pkgJson = scope === "global" ? detectGlobalNpmPackageJsonPath(method.pkg, spawn) :
|
|
5667
|
+
const pkgJson = scope === "global" ? detectGlobalNpmPackageJsonPath(method.pkg, spawn) : join22(projectDir, "node_modules", method.pkg, "package.json");
|
|
5350
5668
|
if (!pkgJson || !existsSync18(pkgJson)) return void 0;
|
|
5351
|
-
const parsed = JSON.parse(
|
|
5669
|
+
const parsed = JSON.parse(readFileSync17(pkgJson, "utf8"));
|
|
5352
5670
|
return parsed.version;
|
|
5353
5671
|
}
|
|
5354
5672
|
default:
|
|
@@ -5360,9 +5678,9 @@ function detectVersion(method, spawn, scope, projectDir) {
|
|
|
5360
5678
|
}
|
|
5361
5679
|
function detectPluginVersion(pluginId, scope, projectDir) {
|
|
5362
5680
|
const claudeScope = scope === "global" ? "user" : "project";
|
|
5363
|
-
const path =
|
|
5681
|
+
const path = join22(homedir2(), ".claude/plugins/installed_plugins.json");
|
|
5364
5682
|
if (!existsSync18(path)) return void 0;
|
|
5365
|
-
const data = JSON.parse(
|
|
5683
|
+
const data = JSON.parse(readFileSync17(path, "utf8"));
|
|
5366
5684
|
const entries = data.plugins?.[pluginId] ?? [];
|
|
5367
5685
|
const matches = entries.filter(
|
|
5368
5686
|
(e2) => claudeScope === "user" ? e2.scope === "user" : e2.scope === "project" && e2.projectPath === projectDir
|
|
@@ -5375,7 +5693,7 @@ function detectPluginVersion(pluginId, scope, projectDir) {
|
|
|
5375
5693
|
}
|
|
5376
5694
|
function detectGlobalNpmPackageJsonPath(pkg, spawn) {
|
|
5377
5695
|
const npmRoot = getNpmGlobalRoot(spawn);
|
|
5378
|
-
return npmRoot ?
|
|
5696
|
+
return npmRoot ? join22(npmRoot, pkg, "package.json") : void 0;
|
|
5379
5697
|
}
|
|
5380
5698
|
var npmGlobalRootCache;
|
|
5381
5699
|
function getNpmGlobalRoot(spawn) {
|
|
@@ -5394,8 +5712,8 @@ function getNpmGlobalRoot(spawn) {
|
|
|
5394
5712
|
|
|
5395
5713
|
// src/key-ids.ts
|
|
5396
5714
|
init_esm_shims();
|
|
5397
|
-
import { readFileSync as
|
|
5398
|
-
import { join as
|
|
5715
|
+
import { readFileSync as readFileSync18 } from "fs";
|
|
5716
|
+
import { join as join23 } from "path";
|
|
5399
5717
|
function renderedKeyIds(harnessRoot, input, clis) {
|
|
5400
5718
|
const out = /* @__PURE__ */ new Set();
|
|
5401
5719
|
const add = (path, key) => {
|
|
@@ -5418,7 +5736,7 @@ function renderedKeyIds(harnessRoot, input, clis) {
|
|
|
5418
5736
|
for (const name of Object.keys(mcp.mcpServers)) add(".mcp.json", `mcpServers.${name}`);
|
|
5419
5737
|
for (const key of gitignoreRender().keys()) add(".gitignore", key);
|
|
5420
5738
|
if (clis.includes("claude")) {
|
|
5421
|
-
const template =
|
|
5739
|
+
const template = readFileSync18(join23(harnessRoot, "templates/settings.json"), "utf8");
|
|
5422
5740
|
for (const key of renderSettingsPortion(template, () => true).keys()) {
|
|
5423
5741
|
add(".claude/settings.json", key);
|
|
5424
5742
|
}
|
|
@@ -5430,7 +5748,7 @@ function renderedKeyIds(harnessRoot, input, clis) {
|
|
|
5430
5748
|
if (clis.includes("opencode")) {
|
|
5431
5749
|
for (const key of renderOpencodeMcp(mcp).keys()) add("opencode.json", key);
|
|
5432
5750
|
}
|
|
5433
|
-
if (clis.
|
|
5751
|
+
if (clis.some(isAgentsMdCli)) add("AGENTS.md", AGENTS_BLOCK_NAME);
|
|
5434
5752
|
return out;
|
|
5435
5753
|
}
|
|
5436
5754
|
function withoutAccepts(harnessRoot, spec, previous) {
|
|
@@ -5459,7 +5777,7 @@ function withoutAccepts(harnessRoot, spec, previous) {
|
|
|
5459
5777
|
|
|
5460
5778
|
// src/linked-skill-bodies.ts
|
|
5461
5779
|
init_esm_shims();
|
|
5462
|
-
import { join as
|
|
5780
|
+
import { join as join24 } from "path";
|
|
5463
5781
|
function refreshLinkedSkillBodies(params) {
|
|
5464
5782
|
const { harnessRoot, projectDir, ids, baseline, journal } = params;
|
|
5465
5783
|
const bodyKey = (id) => `.agents/skills/${id}/SKILL.md`;
|
|
@@ -5470,7 +5788,7 @@ function refreshLinkedSkillBodies(params) {
|
|
|
5470
5788
|
);
|
|
5471
5789
|
const result = writer.result();
|
|
5472
5790
|
return {
|
|
5473
|
-
updated: ours.filter((id) => written.has(
|
|
5791
|
+
updated: ours.filter((id) => written.has(join24(projectDir, bodyKey(id)))),
|
|
5474
5792
|
notOurs: ids.filter((id) => !baseline.has(bodyKey(id))),
|
|
5475
5793
|
files: result.files,
|
|
5476
5794
|
backupPaths: result.backupPaths,
|
|
@@ -5481,8 +5799,8 @@ function refreshLinkedSkillBodies(params) {
|
|
|
5481
5799
|
|
|
5482
5800
|
// src/out-of-track.ts
|
|
5483
5801
|
init_esm_shims();
|
|
5484
|
-
import { lstatSync as
|
|
5485
|
-
import { join as
|
|
5802
|
+
import { lstatSync as lstatSync3, readFileSync as readFileSync19, rmSync as rmSync3 } from "fs";
|
|
5803
|
+
import { join as join25 } from "path";
|
|
5486
5804
|
var AGENTS_RULE = /^\.agents\/rules\/([^/]+)\.md$/;
|
|
5487
5805
|
var ANCHOR_RULE = "uzys-harness";
|
|
5488
5806
|
function reclaimOutOfTrack(args) {
|
|
@@ -5502,16 +5820,16 @@ function reclaimOutOfTrack(args) {
|
|
|
5502
5820
|
const name = AGENTS_RULE.exec(f.path)?.[1];
|
|
5503
5821
|
if (name === void 0 || name === ANCHOR_RULE || rendered.has(name)) continue;
|
|
5504
5822
|
if (isBaselineExcluded(`.claude/rules/${name}.md`, args.excluded)) continue;
|
|
5505
|
-
const abs =
|
|
5823
|
+
const abs = join25(projectDir, f.path);
|
|
5506
5824
|
if (args.outside.skip(abs)) continue;
|
|
5507
|
-
const stat =
|
|
5825
|
+
const stat = lstatSync3(abs, { throwIfNoEntry: false });
|
|
5508
5826
|
if (stat !== void 0 && !stat.isFile()) continue;
|
|
5509
5827
|
if (stat !== void 0 && claudeCopy.has(`rules/${name}.md`)) {
|
|
5510
5828
|
out.kept.push(f.path);
|
|
5511
5829
|
out.bringBack[f.path] = tracksBringing(name);
|
|
5512
5830
|
continue;
|
|
5513
5831
|
}
|
|
5514
|
-
const disk = stat === void 0 ? null :
|
|
5832
|
+
const disk = stat === void 0 ? null : readFileSync19(abs, "utf8");
|
|
5515
5833
|
const verdict = judge({
|
|
5516
5834
|
op: "remove",
|
|
5517
5835
|
kind: "harness",
|
|
@@ -5524,11 +5842,11 @@ function reclaimOutOfTrack(args) {
|
|
|
5524
5842
|
out.bringBack[f.path] = tracksBringing(name);
|
|
5525
5843
|
}
|
|
5526
5844
|
if (verdict.verdict === "remove") {
|
|
5527
|
-
|
|
5845
|
+
rmSync3(abs);
|
|
5528
5846
|
out.removed.push(f.path);
|
|
5529
5847
|
} else if (verdict.verdict === "backup+remove") {
|
|
5530
5848
|
const backup = backupFile(abs, args.now);
|
|
5531
|
-
|
|
5849
|
+
rmSync3(abs);
|
|
5532
5850
|
out.backedUp.push({ path: f.path, backup });
|
|
5533
5851
|
}
|
|
5534
5852
|
}
|
|
@@ -5543,15 +5861,15 @@ function tracksBringing(rule) {
|
|
|
5543
5861
|
|
|
5544
5862
|
// src/state.ts
|
|
5545
5863
|
init_esm_shims();
|
|
5546
|
-
import { existsSync as existsSync19, readFileSync as
|
|
5547
|
-
import { join as
|
|
5864
|
+
import { existsSync as existsSync19, readFileSync as readFileSync20 } from "fs";
|
|
5865
|
+
import { join as join26 } from "path";
|
|
5548
5866
|
var META_FILE = ".claude/.installed-tracks";
|
|
5549
5867
|
var LEGACY_ANCHOR_FILE = ".claude/CLAUDE.md";
|
|
5550
5868
|
var TRACE_RULES = ["htmx", "nextjs", "data-analysis", "pyside6", "cli-development"].map(
|
|
5551
5869
|
(r) => `.claude/rules/${r}.md`
|
|
5552
5870
|
);
|
|
5553
5871
|
function detectInstallState(projectDir) {
|
|
5554
|
-
const hasClaudeDir = existsSync19(
|
|
5872
|
+
const hasClaudeDir = existsSync19(join26(projectDir, ".claude"));
|
|
5555
5873
|
const read = readInstallLogStatus(projectDir);
|
|
5556
5874
|
if (read.status === "ok") {
|
|
5557
5875
|
return {
|
|
@@ -5571,8 +5889,8 @@ function tracksFromLog(tracks) {
|
|
|
5571
5889
|
return [...new Set(tracks.filter(isTrack))].sort();
|
|
5572
5890
|
}
|
|
5573
5891
|
function collectTraces(projectDir) {
|
|
5574
|
-
const at5 = (rel) =>
|
|
5575
|
-
const contains = (rel, needle) => existsSync19(at5(rel)) &&
|
|
5892
|
+
const at5 = (rel) => join26(projectDir, rel);
|
|
5893
|
+
const contains = (rel, needle) => existsSync19(at5(rel)) && readFileSync20(at5(rel), "utf8").includes(needle);
|
|
5576
5894
|
const traces = [];
|
|
5577
5895
|
if (existsSync19(at5(META_FILE)))
|
|
5578
5896
|
traces.push({ path: META_FILE, tracks: readMetafile(at5(META_FILE)) });
|
|
@@ -5587,7 +5905,7 @@ function collectTraces(projectDir) {
|
|
|
5587
5905
|
}
|
|
5588
5906
|
function readMetafile(path) {
|
|
5589
5907
|
const seen = /* @__PURE__ */ new Set();
|
|
5590
|
-
for (const word of
|
|
5908
|
+
for (const word of readFileSync20(path, "utf8").split(/\s+/)) {
|
|
5591
5909
|
if (isTrack(word)) seen.add(word);
|
|
5592
5910
|
}
|
|
5593
5911
|
return [...seen].sort();
|
|
@@ -5631,16 +5949,16 @@ init_esm_shims();
|
|
|
5631
5949
|
import {
|
|
5632
5950
|
copyFileSync,
|
|
5633
5951
|
existsSync as existsSync20,
|
|
5634
|
-
lstatSync as
|
|
5952
|
+
lstatSync as lstatSync4,
|
|
5635
5953
|
mkdirSync as mkdirSync5,
|
|
5636
|
-
readdirSync as
|
|
5637
|
-
readFileSync as
|
|
5954
|
+
readdirSync as readdirSync3,
|
|
5955
|
+
readFileSync as readFileSync21,
|
|
5638
5956
|
rmdirSync,
|
|
5639
5957
|
statSync as statSync3,
|
|
5640
5958
|
unlinkSync as unlinkSync2,
|
|
5641
5959
|
writeFileSync as writeFileSync7
|
|
5642
5960
|
} from "fs";
|
|
5643
|
-
import { basename as
|
|
5961
|
+
import { basename as basename3, dirname as dirname5, join as join27 } from "path";
|
|
5644
5962
|
var LEGACY_ANCHOR_FILE2 = ".claude/CLAUDE.md";
|
|
5645
5963
|
function buildUpdateSpec(projectDir, tracks, only) {
|
|
5646
5964
|
const log = readInstallLog(projectDir);
|
|
@@ -5662,7 +5980,7 @@ function parseUpdateOnly(raw) {
|
|
|
5662
5980
|
return { ok: true, groups: [...new Set(raw)] };
|
|
5663
5981
|
}
|
|
5664
5982
|
function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
5665
|
-
const claudeDir =
|
|
5983
|
+
const claudeDir = join27(projectDir, ".claude");
|
|
5666
5984
|
const startedAt = Date.now();
|
|
5667
5985
|
const backupsAtStart = new Set(
|
|
5668
5986
|
collectRunBackups(projectDir, Number.NEGATIVE_INFINITY).map((b2) => b2.backup)
|
|
@@ -5708,8 +6026,8 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
|
5708
6026
|
const policyBase = policyBaseline(projectDir);
|
|
5709
6027
|
for (const { dir, ext } of POLICY_DIRS) {
|
|
5710
6028
|
if (!claudeManaged || !wants(dir === "hooks" ? "hooks" : "rules")) continue;
|
|
5711
|
-
const target =
|
|
5712
|
-
const source =
|
|
6029
|
+
const target = join27(claudeDir, dir);
|
|
6030
|
+
const source = join27(templatesDir, dir);
|
|
5713
6031
|
const label = `.claude/${dir}`;
|
|
5714
6032
|
const ctx = { prefix: dir, baseline: policyBase, outside, excluded: excludedIds(logAtStart) };
|
|
5715
6033
|
const synced = updateDir(target, source, ext, ctx);
|
|
@@ -5723,8 +6041,8 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
|
5723
6041
|
if (claudeManaged && syncedDirs.length > 0)
|
|
5724
6042
|
refreshPolicyBaseline(projectDir, templatesDir, syncedDirs, outsidePaths(outside, ".claude/"));
|
|
5725
6043
|
const skillSync = claudeManaged && wants("skills") ? syncSkills(
|
|
5726
|
-
|
|
5727
|
-
|
|
6044
|
+
join27(claudeDir, "skills"),
|
|
6045
|
+
join27(templatesDir, "skills"),
|
|
5728
6046
|
skillBaseline(projectDir),
|
|
5729
6047
|
/* @__PURE__ */ new Date(),
|
|
5730
6048
|
(relInSkills) => foreignOwnedTarget(projectDir, `.claude/skills/${relInSkills}`),
|
|
@@ -5760,7 +6078,7 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
|
5760
6078
|
droppedKeys: legacyDroppedKeys(logAtStart),
|
|
5761
6079
|
harnessMcp
|
|
5762
6080
|
});
|
|
5763
|
-
const settingsPath =
|
|
6081
|
+
const settingsPath = join27(claudeDir, "settings.json");
|
|
5764
6082
|
if (claudeManaged && wants("hooks") && existsSync20(settingsPath) && !outside.skip(settingsPath)) {
|
|
5765
6083
|
report.staleHookRefs = cleanStaleHookRefs(
|
|
5766
6084
|
settingsPath,
|
|
@@ -5787,6 +6105,8 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
|
5787
6105
|
restoredKeys: [],
|
|
5788
6106
|
restoredFiles: [],
|
|
5789
6107
|
legacyRestored: [],
|
|
6108
|
+
anchorRetired: null,
|
|
6109
|
+
agentsMd: null,
|
|
5790
6110
|
excludedKeys: [],
|
|
5791
6111
|
sharedLeft: []
|
|
5792
6112
|
};
|
|
@@ -5796,6 +6116,8 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
|
5796
6116
|
report.outOfTrack = outOfTrack;
|
|
5797
6117
|
}
|
|
5798
6118
|
report.externalUpdated = external.externalUpdated;
|
|
6119
|
+
if (external.anchorRetired !== null) report.anchorRetired = external.anchorRetired;
|
|
6120
|
+
if (external.agentsMd !== null) report.agentsMd = external.agentsMd;
|
|
5799
6121
|
if (external.sharedLeft.length > 0) report.sharedLeft = external.sharedLeft;
|
|
5800
6122
|
report.restoredKeys = external.restoredKeys;
|
|
5801
6123
|
report.restoredFiles = external.restoredFiles;
|
|
@@ -5942,9 +6264,14 @@ function checksumOnRecord(log) {
|
|
|
5942
6264
|
if (anchor !== void 0) paths.add(anchor.path);
|
|
5943
6265
|
return (path) => paths.has(path);
|
|
5944
6266
|
}
|
|
6267
|
+
function retiredFeaturesPart(projectDir, f) {
|
|
6268
|
+
if (f.path !== ".codex/config.toml") return {};
|
|
6269
|
+
const keys = retiredFeaturesLeftBehind(projectDir, f);
|
|
6270
|
+
return keys.length > 0 ? { retiredFeatures: keys } : {};
|
|
6271
|
+
}
|
|
5945
6272
|
function legacyHookOnDisk(projectDir, rel) {
|
|
5946
6273
|
try {
|
|
5947
|
-
return hasLegacyHarnessHook(
|
|
6274
|
+
return hasLegacyHarnessHook(readFileSync21(join27(projectDir, rel), "utf8"));
|
|
5948
6275
|
} catch {
|
|
5949
6276
|
return false;
|
|
5950
6277
|
}
|
|
@@ -5970,18 +6297,18 @@ function writeUpdateSharedFiles(args) {
|
|
|
5970
6297
|
excluded: adapterExcluded(harnessRoot, excluded)
|
|
5971
6298
|
});
|
|
5972
6299
|
const tracks = installedTracks(projectDir);
|
|
5973
|
-
const settingsSource =
|
|
6300
|
+
const settingsSource = join27(templatesDir, "settings.json");
|
|
5974
6301
|
if (args.claudeManaged && wants("hooks") && existsSync20(settingsSource)) {
|
|
5975
6302
|
const spec = buildAssetSpec({ tracks, options: DEFAULT_OPTIONS });
|
|
5976
6303
|
const manifest = buildManifest(spec);
|
|
5977
6304
|
writeSettingsShared(
|
|
5978
6305
|
writer,
|
|
5979
|
-
|
|
6306
|
+
readFileSync21(settingsSource, "utf8"),
|
|
5980
6307
|
projectDir,
|
|
5981
6308
|
log,
|
|
5982
6309
|
(script) => {
|
|
5983
6310
|
const target = `.claude/hooks/${script}`;
|
|
5984
|
-
return manifest.some((e2) => e2.target === target && e2.applies(spec)) && !isBaselineExcluded(target, excluded) && existsSync20(
|
|
6311
|
+
return manifest.some((e2) => e2.target === target && e2.applies(spec)) && !isBaselineExcluded(target, excluded) && existsSync20(join27(projectDir, target));
|
|
5985
6312
|
}
|
|
5986
6313
|
// 기록이 claude 를 깔린 CLI 로 말한다 = install 이 이 파일을 썼다 — 없으면 만든다(훅 배선이 여기 산다)
|
|
5987
6314
|
);
|
|
@@ -6019,30 +6346,30 @@ function writeUpdateSharedFiles(args) {
|
|
|
6019
6346
|
}
|
|
6020
6347
|
function staleSkillDirs(projectDir, claudeDir, clis) {
|
|
6021
6348
|
const stale = (id) => RENAMED_SKILL_IDS[id] !== void 0 || RETIRED_SKILL_IDS.includes(id);
|
|
6022
|
-
const dirs = [
|
|
6023
|
-
if (clis.some((cli) => cli !== "claude")) dirs.push(
|
|
6349
|
+
const dirs = [join27(claudeDir, "skills")];
|
|
6350
|
+
if (clis.some((cli) => cli !== "claude")) dirs.push(join27(projectDir, ".agents", "skills"));
|
|
6024
6351
|
const ids = /* @__PURE__ */ new Set();
|
|
6025
6352
|
for (const dir of dirs) {
|
|
6026
6353
|
if (!existsSync20(dir)) continue;
|
|
6027
|
-
for (const e2 of
|
|
6354
|
+
for (const e2 of readdirSync3(dir, { withFileTypes: true })) {
|
|
6028
6355
|
if (e2.isDirectory() && stale(e2.name)) ids.add(e2.name);
|
|
6029
6356
|
}
|
|
6030
6357
|
}
|
|
6031
6358
|
return [...ids];
|
|
6032
6359
|
}
|
|
6033
6360
|
function staleAgentFiles(claudeDir) {
|
|
6034
|
-
const dir =
|
|
6361
|
+
const dir = join27(claudeDir, "agents");
|
|
6035
6362
|
if (!existsSync20(dir)) return [];
|
|
6036
|
-
return
|
|
6363
|
+
return readdirSync3(dir, { withFileTypes: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".md")).map((e2) => basename3(e2.name, ".md")).filter((id) => RETIRED_AGENT_IDS.includes(id));
|
|
6037
6364
|
}
|
|
6038
6365
|
function demotedAgentFiles(claudeDir, tracks) {
|
|
6039
6366
|
if (tracks.length === 0) return [];
|
|
6040
|
-
const dir =
|
|
6367
|
+
const dir = join27(claudeDir, "agents");
|
|
6041
6368
|
if (!existsSync20(dir)) return [];
|
|
6042
6369
|
const demoted = TRACK_AGENTS.filter(([, pattern]) => !anyTrack(tracks, pattern)).map(
|
|
6043
6370
|
([id]) => id
|
|
6044
6371
|
);
|
|
6045
|
-
return
|
|
6372
|
+
return readdirSync3(dir, { withFileTypes: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".md")).map((e2) => basename3(e2.name, ".md")).filter((id) => demoted.includes(id));
|
|
6046
6373
|
}
|
|
6047
6374
|
function installNewAssets(projectDir, templatesDir, wants, outside) {
|
|
6048
6375
|
const installed = [];
|
|
@@ -6060,14 +6387,14 @@ function installNewAssets(projectDir, templatesDir, wants, outside) {
|
|
|
6060
6387
|
if (!wants(group)) continue;
|
|
6061
6388
|
if (entry.target === HARNESS_ANCHOR_FILE) continue;
|
|
6062
6389
|
if (isBaselineExcluded(entry.target, baselineExcluded)) continue;
|
|
6063
|
-
const target =
|
|
6390
|
+
const target = join27(projectDir, entry.target);
|
|
6064
6391
|
const foreign = foreignOwnedTarget(projectDir, entry.target);
|
|
6065
6392
|
if (foreign !== null) {
|
|
6066
6393
|
if (!foreignOwned.includes(foreign)) foreignOwned.push(foreign);
|
|
6067
6394
|
continue;
|
|
6068
6395
|
}
|
|
6069
6396
|
if (existsSync20(target)) continue;
|
|
6070
|
-
const source =
|
|
6397
|
+
const source = join27(templatesDir, entry.source);
|
|
6071
6398
|
if (!existsSync20(source)) continue;
|
|
6072
6399
|
if (outside.skip(target)) continue;
|
|
6073
6400
|
if (entry.target.startsWith(".claude/")) {
|
|
@@ -6094,20 +6421,20 @@ function collectRunBackups(projectDir, startedAt, exclude) {
|
|
|
6094
6421
|
const consider = (rel) => {
|
|
6095
6422
|
if (!BACKUP_SUFFIX.test(rel)) return;
|
|
6096
6423
|
if (exclude?.has(rel)) return;
|
|
6097
|
-
const abs =
|
|
6424
|
+
const abs = join27(projectDir, rel);
|
|
6098
6425
|
if (statSync3(abs).mtimeMs < startedAt - 1e3) return;
|
|
6099
6426
|
out.push({ path: rel.replace(BACKUP_SUFFIX, ""), backup: rel });
|
|
6100
6427
|
};
|
|
6101
|
-
for (const e2 of
|
|
6428
|
+
for (const e2 of readdirSync3(projectDir, { withFileTypes: true }))
|
|
6102
6429
|
if (e2.isFile()) consider(e2.name);
|
|
6103
6430
|
for (const dir of BACKUP_SCAN_DIRS) {
|
|
6104
|
-
for (const rel of listFilesRecursive(
|
|
6431
|
+
for (const rel of listFilesRecursive(join27(projectDir, dir))) consider(`${dir}/${rel}`);
|
|
6105
6432
|
}
|
|
6106
6433
|
return out.sort((a, b2) => a.path.localeCompare(b2.path));
|
|
6107
6434
|
}
|
|
6108
6435
|
function writeBackupList(projectDir, backups, noChecksum = /* @__PURE__ */ new Set()) {
|
|
6109
|
-
const dir =
|
|
6110
|
-
const file =
|
|
6436
|
+
const dir = join27(projectDir, ".uzys-agent-harness");
|
|
6437
|
+
const file = join27(dir, "update-backups.json");
|
|
6111
6438
|
try {
|
|
6112
6439
|
if (backups.length === 0) {
|
|
6113
6440
|
if (existsSync20(file)) unlinkSync2(file);
|
|
@@ -6145,14 +6472,14 @@ function installNewSkillDirs(projectDir, templatesDir, tracks, outside) {
|
|
|
6145
6472
|
if (entry.type !== "dir" || !entry.target.startsWith(".claude/skills/")) continue;
|
|
6146
6473
|
if (!entry.applies(spec) || isBaselineExcluded(entry.target, excluded)) continue;
|
|
6147
6474
|
if (skillExcluded.has(entry.target.slice(".claude/skills/".length))) continue;
|
|
6148
|
-
const target =
|
|
6475
|
+
const target = join27(projectDir, entry.target);
|
|
6149
6476
|
const foreign = foreignOwnedTarget(projectDir, entry.target);
|
|
6150
6477
|
if (foreign !== null) {
|
|
6151
6478
|
if (!foreignOwned.includes(foreign)) foreignOwned.push(foreign);
|
|
6152
6479
|
continue;
|
|
6153
6480
|
}
|
|
6154
6481
|
if (existsSync20(target)) continue;
|
|
6155
|
-
const source =
|
|
6482
|
+
const source = join27(templatesDir, entry.source);
|
|
6156
6483
|
if (!existsSync20(source)) continue;
|
|
6157
6484
|
if (outside.skip(target)) continue;
|
|
6158
6485
|
copyDir(source, target, (rel) => foreignOwnedTarget(projectDir, `${entry.target}/${rel}`));
|
|
@@ -6183,17 +6510,17 @@ function trackOnlyFileAssets(tracks) {
|
|
|
6183
6510
|
);
|
|
6184
6511
|
}
|
|
6185
6512
|
function syncHarnessAnchor(projectDir, templatesDir, report, outside, now = /* @__PURE__ */ new Date()) {
|
|
6186
|
-
if (existsSync20(
|
|
6513
|
+
if (existsSync20(join27(projectDir, LEGACY_ANCHOR_FILE2))) {
|
|
6187
6514
|
report.legacyAnchor = LEGACY_ANCHOR_FILE2;
|
|
6188
6515
|
}
|
|
6189
|
-
const templateMd =
|
|
6516
|
+
const templateMd = join27(templatesDir, "CLAUDE.md");
|
|
6190
6517
|
if (!existsSync20(templateMd)) return;
|
|
6191
|
-
const anchor =
|
|
6518
|
+
const anchor = join27(projectDir, HARNESS_ANCHOR_FILE);
|
|
6192
6519
|
if (outside.skip(anchor)) return;
|
|
6193
6520
|
const existed = existsSync20(anchor);
|
|
6194
6521
|
if (existed) {
|
|
6195
|
-
const current =
|
|
6196
|
-
if (current !==
|
|
6522
|
+
const current = readFileSync21(anchor, "utf8");
|
|
6523
|
+
if (current !== readFileSync21(templateMd, "utf8")) {
|
|
6197
6524
|
const recorded2 = readInstallLog(projectDir)?.templates.rootClaudeMd;
|
|
6198
6525
|
const owned = recorded2?.path === HARNESS_ANCHOR_FILE && hashContent(current) === recorded2.sha256;
|
|
6199
6526
|
if (!owned) {
|
|
@@ -6214,16 +6541,16 @@ function syncHarnessAnchor(projectDir, templatesDir, report, outside, now = /* @
|
|
|
6214
6541
|
recordAnchorBaseline(projectDir, anchor);
|
|
6215
6542
|
}
|
|
6216
6543
|
function upsertRootImport(projectDir, outside) {
|
|
6217
|
-
const target =
|
|
6544
|
+
const target = join27(projectDir, "CLAUDE.md");
|
|
6218
6545
|
if (outside.skip(target)) return false;
|
|
6219
|
-
const existing = existsSync20(target) ?
|
|
6546
|
+
const existing = existsSync20(target) ? readFileSync21(target, "utf8") : null;
|
|
6220
6547
|
const next = upsertHarnessImport(existing, {
|
|
6221
|
-
projectName:
|
|
6548
|
+
projectName: basename3(projectDir),
|
|
6222
6549
|
tracks: installedTracks(projectDir),
|
|
6223
6550
|
// ADR-085 — update 는 선택 목록을 인자로 받지 않는다. `syncSkills` 와 같은 판정을 쓴다:
|
|
6224
6551
|
// `.claude/skills/<id>` 가 있으면 그 스킬은 이 설치가 고른 것이다.
|
|
6225
6552
|
continuousSkills: CONTINUOUS_SKILLS.map((s) => s.id).filter(
|
|
6226
|
-
(id) => existsSync20(
|
|
6553
|
+
(id) => existsSync20(join27(projectDir, ".claude/skills", id))
|
|
6227
6554
|
)
|
|
6228
6555
|
});
|
|
6229
6556
|
if (next === existing) return false;
|
|
@@ -6244,7 +6571,7 @@ function recordAnchorBaseline(projectDir, anchor) {
|
|
|
6244
6571
|
...log.templates,
|
|
6245
6572
|
rootClaudeMd: {
|
|
6246
6573
|
path: HARNESS_ANCHOR_FILE,
|
|
6247
|
-
sha256: hashContent(
|
|
6574
|
+
sha256: hashContent(readFileSync21(anchor, "utf8"))
|
|
6248
6575
|
}
|
|
6249
6576
|
}
|
|
6250
6577
|
};
|
|
@@ -6258,16 +6585,34 @@ function excludedBaselineOnDisk(projectDir, log) {
|
|
|
6258
6585
|
if (excluded.size === 0) return [];
|
|
6259
6586
|
const spec = buildAssetSpec({ tracks: installedTracks(projectDir), options: DEFAULT_OPTIONS });
|
|
6260
6587
|
return buildManifest(spec).filter(
|
|
6261
|
-
(e2) => e2.applies(spec) && isBaselineExcluded(e2.target, excluded) && existsSync20(
|
|
6588
|
+
(e2) => e2.applies(spec) && isBaselineExcluded(e2.target, excluded) && existsSync20(join27(projectDir, e2.target))
|
|
6262
6589
|
).map((e2) => e2.target);
|
|
6263
6590
|
}
|
|
6264
6591
|
function excludedSkillOf(log) {
|
|
6265
6592
|
const excluded = excludedIds(log);
|
|
6266
6593
|
return (id) => excluded.has(id) || isBaselineExcluded(`.claude/skills/${id}`, excluded);
|
|
6267
6594
|
}
|
|
6595
|
+
function recordedRenderInputs(projectDir, log) {
|
|
6596
|
+
const excluded = excludedIds(log);
|
|
6597
|
+
return {
|
|
6598
|
+
// 스킬은 **디스크에 있는 것만** 넘긴다. 스킬 *파일*은 전체 목록을 넘겨도 refreshOnly 가
|
|
6599
|
+
// 없는 것을 건너뛰지만, `AGENTS.md` 의 상시 스킬 안내(ADR-085)는 이 목록 그대로 렌더돼
|
|
6600
|
+
// `--without` 으로 뺀 스킬(#505)·opt-in 스킬을 "열어라"고 적었다(실측 2026-09-21). 판정은
|
|
6601
|
+
// `upsertRootImport` 와 같다 — `.claude/skills/<id>` 또는 `.agents/skills/<id>` 가 있으면 깔린 것.
|
|
6602
|
+
// ADR-099 R3 — 뺀 스킬은 디스크에 남아 있어도 갱신하지도 안내하지도 않는다(install 이 내는 것과 같은 목록)
|
|
6603
|
+
selectedInternalSkills: installedBundledSkills(projectDir).filter(
|
|
6604
|
+
(id) => !excludedSkillOf(log)(id)
|
|
6605
|
+
),
|
|
6606
|
+
// #601 — install 과 같은 SSOT(resolveRules)를 설치된 트랙으로. 렌더 경로(AGENTS.md 절 · antigravity 룰 생성)는
|
|
6607
|
+
// 디스크 게이트가 없어, data 트랙 설치에서 update 만으로 cli-development(제6룰)가 생겼다.
|
|
6608
|
+
rules: resolveRules({ tracks: installedTracks(projectDir) }).filter(
|
|
6609
|
+
(r) => !isBaselineExcluded(`.claude/rules/${r}.md`, excluded)
|
|
6610
|
+
)
|
|
6611
|
+
};
|
|
6612
|
+
}
|
|
6268
6613
|
function installedBundledSkills(projectDir) {
|
|
6269
6614
|
return INTERNAL_BUNDLED_SKILL_IDS.filter(
|
|
6270
|
-
(id) => existsSync20(
|
|
6615
|
+
(id) => existsSync20(join27(projectDir, ".claude/skills", id)) || existsSync20(join27(projectDir, ".agents/skills", id))
|
|
6271
6616
|
);
|
|
6272
6617
|
}
|
|
6273
6618
|
function installedCliTargets(log) {
|
|
@@ -6297,9 +6642,9 @@ var SHARED_SKILLS_DIR = ".agents/skills";
|
|
|
6297
6642
|
var SHARED_SKILL_FILE = /^\.agents\/skills\/([^/]+)\//;
|
|
6298
6643
|
var SHARED_SKILL_MD = /^\.agents\/skills\/([^/]+)\/SKILL\.md$/;
|
|
6299
6644
|
function sharedSkillIdsOnDisk(projectDir) {
|
|
6300
|
-
const root =
|
|
6645
|
+
const root = join27(projectDir, SHARED_SKILLS_DIR);
|
|
6301
6646
|
if (!existsSync20(root)) return /* @__PURE__ */ new Set();
|
|
6302
|
-
return new Set(
|
|
6647
|
+
return new Set(readdirSync3(root).filter((id) => existsSync20(join27(root, id, "SKILL.md"))));
|
|
6303
6648
|
}
|
|
6304
6649
|
var CREATED_SHARED_HOW = {
|
|
6305
6650
|
"opencode.json": "created from template (harness part + template defaults)",
|
|
@@ -6307,40 +6652,33 @@ var CREATED_SHARED_HOW = {
|
|
|
6307
6652
|
"AGENTS.md": "wrote the harness block only"
|
|
6308
6653
|
};
|
|
6309
6654
|
function cliArtifactOwners(path, installed) {
|
|
6310
|
-
const owners = path.startsWith(".codex/") ? ["codex"] : path === "opencode.json" || path.startsWith(".opencode/") ? ["opencode"] : path === "AGENTS.md" ? [
|
|
6655
|
+
const owners = path.startsWith(".codex/") ? ["codex"] : path === "opencode.json" || path.startsWith(".opencode/") ? ["opencode"] : path === "AGENTS.md" ? CLI_BASES.filter((c3) => CLI_OWNERSHIP[c3].some((o) => o.path === "AGENTS.md")) : path.startsWith(".agents/rules/") ? ["antigravity"] : [];
|
|
6311
6656
|
return owners.filter((c3) => installed.includes(c3));
|
|
6312
6657
|
}
|
|
6658
|
+
function agentsMdNews(report, existedBefore, priorPaths) {
|
|
6659
|
+
if (report === null) return null;
|
|
6660
|
+
const created = !existedBefore && !priorPaths.has("AGENTS.md") && report.ownership.files.some((f) => f.path === "AGENTS.md");
|
|
6661
|
+
if (!created && report.agentsMdSeededFrom === null) return null;
|
|
6662
|
+
return { created, clis: [...report.clis], seededFrom: report.agentsMdSeededFrom };
|
|
6663
|
+
}
|
|
6313
6664
|
function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp) {
|
|
6314
6665
|
const log = readInstallLog(projectDir);
|
|
6315
|
-
const baselineExcluded = excludedIds(log);
|
|
6316
6666
|
const presentBefore = sharedSkillIdsOnDisk(projectDir);
|
|
6317
6667
|
const rulesBefore = /* @__PURE__ */ new Set();
|
|
6318
|
-
const rulesDir =
|
|
6668
|
+
const rulesDir = join27(projectDir, ".agents", "rules");
|
|
6319
6669
|
if (existsSync20(rulesDir)) {
|
|
6320
6670
|
try {
|
|
6321
|
-
for (const f of
|
|
6671
|
+
for (const f of readdirSync3(rulesDir)) rulesBefore.add(f);
|
|
6322
6672
|
} catch {
|
|
6323
6673
|
}
|
|
6324
6674
|
}
|
|
6675
|
+
const agentsMdBefore = existsSync20(join27(projectDir, "AGENTS.md"));
|
|
6325
6676
|
const result = runCliTransforms({
|
|
6326
6677
|
harnessRoot,
|
|
6327
6678
|
projectDir,
|
|
6328
6679
|
cli: installedCliTargets(log),
|
|
6329
|
-
//
|
|
6330
|
-
|
|
6331
|
-
// `--without` 으로 뺀 스킬(#505)·opt-in 스킬을 "열어라"고 적었다(실측 2026-09-21). 판정은
|
|
6332
|
-
// `upsertRootImport` 와 같다 — `.claude/skills/<id>` 또는 `.agents/skills/<id>` 가 있으면 깔린 것.
|
|
6333
|
-
// ADR-099 R3 — 뺀 스킬은 디스크에 남아 있어도 갱신하지도 안내하지도 않는다(install 이 내는 것과 같은 목록)
|
|
6334
|
-
selectedInternalSkills: installedBundledSkills(projectDir).filter(
|
|
6335
|
-
(id) => !excludedSkillOf(log)(id)
|
|
6336
|
-
),
|
|
6337
|
-
// #601 — install 과 같은 SSOT(resolveRules)를 설치된 트랙으로. ALL_RULES 문서는
|
|
6338
|
-
// "refreshOnly 가 디스크로 대신 판정한다"고 했지만 렌더 경로(AGENTS.md 절 · antigravity 룰
|
|
6339
|
-
// 생성)는 디스크 게이트가 없어, data 트랙 설치에서 update 만으로 cli-development(제6룰)가
|
|
6340
|
-
// 생겼다. claude 레인은 처음부터 resolveRules 로 흐르므로 이쪽만 맞춘다.
|
|
6341
|
-
rules: resolveRules({ tracks: installedTracks(projectDir) }).filter(
|
|
6342
|
-
(r) => !isBaselineExcluded(`.claude/rules/${r}.md`, baselineExcluded)
|
|
6343
|
-
),
|
|
6680
|
+
// 스킬(디스크에 있는 것만) · 룰(기록 트랙) — `uninstall --cli` 의 `AGENTS.md` 재렌더와 같은 값(`recordedRenderInputs`)
|
|
6681
|
+
...recordedRenderInputs(projectDir, log),
|
|
6344
6682
|
// #568 — MCP 서버는 `.mcp.json` 과 같은 값(기록 트랙 · 기록된 선택 — 기록이 없으면 기본 서버만, 지어내지 않는다).
|
|
6345
6683
|
mcp: harnessMcp,
|
|
6346
6684
|
previousExternal: log?.externalFiles ?? [],
|
|
@@ -6353,8 +6691,11 @@ function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp) {
|
|
|
6353
6691
|
recordedClis: log === null ? [] : installedClis(log)
|
|
6354
6692
|
});
|
|
6355
6693
|
if (log) {
|
|
6356
|
-
const
|
|
6357
|
-
const
|
|
6694
|
+
const retiredPath = result.anchorRetired?.forget === true ? result.anchorRetired.path : null;
|
|
6695
|
+
const merged = mergeExternalFiles(log.externalFiles, result.externalFiles).filter(
|
|
6696
|
+
(f) => f.path !== retiredPath
|
|
6697
|
+
);
|
|
6698
|
+
const next = retiredPath === null ? { ...log } : { ...log, spec: { ...log.spec, clis: [...installedClis(log)] } };
|
|
6358
6699
|
if (merged.length > 0) next.externalFiles = merged;
|
|
6359
6700
|
else delete next.externalFiles;
|
|
6360
6701
|
const touched = new Set(result.portionPaths);
|
|
@@ -6443,6 +6784,8 @@ function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp) {
|
|
|
6443
6784
|
result.sharedFiles.flatMap((f) => [...f.restored, ...f.added]).filter((id) => dropped.has(id))
|
|
6444
6785
|
)
|
|
6445
6786
|
],
|
|
6787
|
+
anchorRetired: result.anchorRetired,
|
|
6788
|
+
agentsMd: agentsMdNews(result.agentsMd, agentsMdBefore, priorPaths),
|
|
6446
6789
|
// #625 — install 화면(`sharedRow`)이 말하는 "남긴 것" 을 update 도 말한다. 고친 구간과 기록이 없어 모르는 구간은
|
|
6447
6790
|
// 다른 사실이라 기록(`portions`)으로 가른다 — 어댑터는 기록된 키가 고쳐졌으면 남기고 기록 sha 를 잇는다(계약 ⓑ)
|
|
6448
6791
|
sharedLeft: result.sharedFiles.filter((f) => f.action === "left" || f.leftAsIs.length + f.kept.length > 0).map((f) => {
|
|
@@ -6456,7 +6799,8 @@ function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp) {
|
|
|
6456
6799
|
kept: [...f.kept],
|
|
6457
6800
|
...f.action === "left" ? { left: f.line } : {},
|
|
6458
6801
|
// #625 — 남긴 구간이 옛 훅 형식이면 현행 Codex 가 무시해 세션 시작 훅이 돌지 않는다(#627) — 화면이 덧붙인다
|
|
6459
|
-
...f.path === ".codex/config.toml" && legacyHookOnDisk(projectDir, f.path) ? { legacyHook: true } : {}
|
|
6802
|
+
...f.path === ".codex/config.toml" && legacyHookOnDisk(projectDir, f.path) ? { legacyHook: true } : {},
|
|
6803
|
+
...retiredFeaturesPart(projectDir, f)
|
|
6460
6804
|
};
|
|
6461
6805
|
})
|
|
6462
6806
|
};
|
|
@@ -6468,15 +6812,15 @@ function updateDir(target, source, ext, ctx, now = /* @__PURE__ */ new Date()) {
|
|
|
6468
6812
|
if (!existsSync20(target) || !existsSync20(source)) return { updated: 0, backedUp: [] };
|
|
6469
6813
|
let updated = 0;
|
|
6470
6814
|
const backedUp = [];
|
|
6471
|
-
for (const file of
|
|
6815
|
+
for (const file of readdirSync3(target)) {
|
|
6472
6816
|
if (!file.endsWith(ext)) continue;
|
|
6473
|
-
const targetFile =
|
|
6474
|
-
const sourceFile =
|
|
6817
|
+
const targetFile = join27(target, file);
|
|
6818
|
+
const sourceFile = join27(source, file);
|
|
6475
6819
|
if (!existsSync20(sourceFile)) continue;
|
|
6476
6820
|
if (ctx.outside?.skip(targetFile)) continue;
|
|
6477
6821
|
if (ctx.excluded && isBaselineExcluded(`.claude/${ctx.prefix}/${file}`, ctx.excluded)) continue;
|
|
6478
|
-
const next =
|
|
6479
|
-
const current =
|
|
6822
|
+
const next = readFileSync21(sourceFile, "utf8");
|
|
6823
|
+
const current = readFileSync21(targetFile, "utf8");
|
|
6480
6824
|
if (current === next) continue;
|
|
6481
6825
|
if (!isHarnessOwned2(ctx, file, current)) {
|
|
6482
6826
|
backupFile(targetFile, now);
|
|
@@ -6495,31 +6839,31 @@ function syncSkills(targetDir, sourceDir, baseline, now = /* @__PURE__ */ new Da
|
|
|
6495
6839
|
const skippedLinks = [];
|
|
6496
6840
|
const foreignOwned = [];
|
|
6497
6841
|
const pruned = [];
|
|
6498
|
-
for (const skill of
|
|
6842
|
+
for (const skill of readdirSync3(sourceDir, { withFileTypes: true })) {
|
|
6499
6843
|
if (!skill.isDirectory()) continue;
|
|
6500
|
-
const targetSkill =
|
|
6844
|
+
const targetSkill = join27(targetDir, skill.name);
|
|
6501
6845
|
if (occupiedByNonDirectory(targetSkill)) {
|
|
6502
6846
|
skippedLinks.push(skill.name);
|
|
6503
6847
|
continue;
|
|
6504
6848
|
}
|
|
6505
6849
|
if (!existsSync20(targetSkill)) continue;
|
|
6506
6850
|
if (excludedSkill?.(skill.name)) continue;
|
|
6507
|
-
for (const rel of listFilesRecursive(
|
|
6508
|
-
const targetFile =
|
|
6851
|
+
for (const rel of listFilesRecursive(join27(sourceDir, skill.name))) {
|
|
6852
|
+
const targetFile = join27(targetSkill, rel);
|
|
6509
6853
|
const foreign = foreignOf(`${skill.name}/${rel}`);
|
|
6510
6854
|
if (foreign !== null) {
|
|
6511
6855
|
if (!foreignOwned.includes(foreign)) foreignOwned.push(foreign);
|
|
6512
6856
|
continue;
|
|
6513
6857
|
}
|
|
6514
6858
|
if (outside?.skip(targetFile)) continue;
|
|
6515
|
-
const next =
|
|
6859
|
+
const next = readFileSync21(join27(sourceDir, skill.name, rel), "utf8");
|
|
6516
6860
|
if (!existsSync20(targetFile)) {
|
|
6517
6861
|
mkdirSync5(dirname5(targetFile), { recursive: true });
|
|
6518
6862
|
writeFileSync7(targetFile, next);
|
|
6519
6863
|
updated++;
|
|
6520
6864
|
continue;
|
|
6521
6865
|
}
|
|
6522
|
-
const current =
|
|
6866
|
+
const current = readFileSync21(targetFile, "utf8");
|
|
6523
6867
|
if (current === next) continue;
|
|
6524
6868
|
const recorded2 = baseline.get(`${skill.name}/${rel}`);
|
|
6525
6869
|
if (recorded2 === void 0 || hashContent(current) !== recorded2) {
|
|
@@ -6529,20 +6873,20 @@ function syncSkills(targetDir, sourceDir, baseline, now = /* @__PURE__ */ new Da
|
|
|
6529
6873
|
writeFileSync7(targetFile, next);
|
|
6530
6874
|
updated++;
|
|
6531
6875
|
}
|
|
6532
|
-
const bundled = new Set(listFilesRecursive(
|
|
6876
|
+
const bundled = new Set(listFilesRecursive(join27(sourceDir, skill.name)));
|
|
6533
6877
|
for (const rel of listFilesRecursive(targetSkill)) {
|
|
6534
|
-
if (bundled.has(rel) ||
|
|
6878
|
+
if (bundled.has(rel) || basename3(rel).includes(".backup-")) continue;
|
|
6535
6879
|
if (foreignOf(`${skill.name}/${rel}`) !== null) continue;
|
|
6536
|
-
const targetFile =
|
|
6880
|
+
const targetFile = join27(targetSkill, rel);
|
|
6537
6881
|
if (outside?.skip(targetFile)) continue;
|
|
6538
6882
|
const recorded2 = baseline.get(`${skill.name}/${rel}`);
|
|
6539
|
-
if (recorded2 === void 0 || hashContent(
|
|
6883
|
+
if (recorded2 === void 0 || hashContent(readFileSync21(targetFile, "utf8")) !== recorded2) {
|
|
6540
6884
|
backupFile(targetFile, now);
|
|
6541
6885
|
backedUp.push(`${skill.name}/${rel}`);
|
|
6542
6886
|
}
|
|
6543
6887
|
unlinkSync2(targetFile);
|
|
6544
6888
|
pruned.push(`${skill.name}/${rel}`);
|
|
6545
|
-
for (let d2 = dirname5(targetFile); d2 !== targetSkill &&
|
|
6889
|
+
for (let d2 = dirname5(targetFile); d2 !== targetSkill && readdirSync3(d2).length === 0; d2 = dirname5(d2))
|
|
6546
6890
|
rmdirSync(d2);
|
|
6547
6891
|
}
|
|
6548
6892
|
}
|
|
@@ -6598,15 +6942,15 @@ function refreshSkillBaseline(projectDir, templatesDir, outside) {
|
|
|
6598
6942
|
function pruneOrphans(target, source, ext, ctx, now = /* @__PURE__ */ new Date()) {
|
|
6599
6943
|
if (!existsSync20(target) || !existsSync20(source)) return [];
|
|
6600
6944
|
const removed = [];
|
|
6601
|
-
for (const file of
|
|
6945
|
+
for (const file of readdirSync3(target)) {
|
|
6602
6946
|
if (!file.endsWith(ext)) continue;
|
|
6603
|
-
const sourceFile =
|
|
6947
|
+
const sourceFile = join27(source, file);
|
|
6604
6948
|
if (!existsSync20(sourceFile)) {
|
|
6605
|
-
const targetFile =
|
|
6949
|
+
const targetFile = join27(target, file);
|
|
6606
6950
|
if (!ctx.baseline.has(`${ctx.prefix}/${file}`)) continue;
|
|
6607
6951
|
if (ctx.outside?.skip(targetFile)) continue;
|
|
6608
6952
|
try {
|
|
6609
|
-
const current =
|
|
6953
|
+
const current = readFileSync21(targetFile, "utf8");
|
|
6610
6954
|
if (!isHarnessOwned2(ctx, file, current)) backupFile(targetFile, now);
|
|
6611
6955
|
unlinkSync2(targetFile);
|
|
6612
6956
|
removed.push(file);
|
|
@@ -6617,7 +6961,7 @@ function pruneOrphans(target, source, ext, ctx, now = /* @__PURE__ */ new Date()
|
|
|
6617
6961
|
return removed;
|
|
6618
6962
|
}
|
|
6619
6963
|
function retireMcpAllowlist(projectDir, now = /* @__PURE__ */ new Date()) {
|
|
6620
|
-
const target =
|
|
6964
|
+
const target = join27(projectDir, ".mcp-allowlist");
|
|
6621
6965
|
if (!existsSync20(target)) return null;
|
|
6622
6966
|
try {
|
|
6623
6967
|
const backup = backupFile(target, now);
|
|
@@ -6633,12 +6977,12 @@ function refreshNeutralHelpers(projectDir, templatesDir, report) {
|
|
|
6633
6977
|
for (const entry of buildManifest(spec)) {
|
|
6634
6978
|
if (entry.type !== "file" || !isCliNeutralTarget(entry.target)) continue;
|
|
6635
6979
|
if (!entry.applies(spec)) continue;
|
|
6636
|
-
const abs =
|
|
6637
|
-
const source =
|
|
6980
|
+
const abs = join27(projectDir, entry.target);
|
|
6981
|
+
const source = join27(templatesDir, entry.source);
|
|
6638
6982
|
if (!existsSync20(source)) continue;
|
|
6639
6983
|
let st2;
|
|
6640
6984
|
try {
|
|
6641
|
-
st2 =
|
|
6985
|
+
st2 = lstatSync4(abs);
|
|
6642
6986
|
} catch {
|
|
6643
6987
|
continue;
|
|
6644
6988
|
}
|
|
@@ -6649,8 +6993,8 @@ function refreshNeutralHelpers(projectDir, templatesDir, report) {
|
|
|
6649
6993
|
if (!st2.isFile()) continue;
|
|
6650
6994
|
const log2 = readInstallLog(projectDir);
|
|
6651
6995
|
const recorded2 = (log2?.externalFiles ?? []).find((f) => f.path === entry.target);
|
|
6652
|
-
const diskSha = hashContent(
|
|
6653
|
-
const freshContent =
|
|
6996
|
+
const diskSha = hashContent(readFileSync21(abs, "utf8"));
|
|
6997
|
+
const freshContent = readFileSync21(source, "utf8");
|
|
6654
6998
|
const freshSha = hashContent(freshContent);
|
|
6655
6999
|
if (diskSha === freshSha) {
|
|
6656
7000
|
if (recorded2?.sha256 !== freshSha) refreshed.set(entry.target, freshSha);
|
|
@@ -6690,7 +7034,7 @@ function dropHealedHookPortions(projectDir, healedFiles) {
|
|
|
6690
7034
|
const log = readInstallLog(projectDir);
|
|
6691
7035
|
if (log === null) return;
|
|
6692
7036
|
const portions = (log.portions ?? []).filter(
|
|
6693
|
-
(p2) => !(p2.path ===
|
|
7037
|
+
(p2) => !(p2.path === join27(".claude", "settings.json") && isHealedKey(p2.key))
|
|
6694
7038
|
);
|
|
6695
7039
|
if (portions.length > 0) log.portions = portions;
|
|
6696
7040
|
else delete log.portions;
|
|
@@ -6704,11 +7048,11 @@ function dropHealedHookPortions(projectDir, healedFiles) {
|
|
|
6704
7048
|
function runInstall(ctx) {
|
|
6705
7049
|
const { harnessRoot, projectDir } = ctx;
|
|
6706
7050
|
const mode = ctx.mode ?? "fresh";
|
|
6707
|
-
const templatesDir =
|
|
7051
|
+
const templatesDir = join28(harnessRoot, "templates");
|
|
6708
7052
|
if (!existsSync21(templatesDir)) {
|
|
6709
7053
|
throw new Error(`Templates dir not found: ${templatesDir}`);
|
|
6710
7054
|
}
|
|
6711
|
-
const claudeDir =
|
|
7055
|
+
const claudeDir = join28(projectDir, ".claude");
|
|
6712
7056
|
const previousLog = readInstallLog(projectDir);
|
|
6713
7057
|
const legacyAnchor = mode === "update" ? null : legacyAnchorTrace(projectDir, ctx.spec);
|
|
6714
7058
|
if (mode === "update" && previousLog === null) {
|
|
@@ -6737,10 +7081,10 @@ function runInstall(ctx) {
|
|
|
6737
7081
|
const manifestSpec = buildManifestSpec(spec);
|
|
6738
7082
|
const baselineExcluded = new Set(spec.baselineExclude ?? []);
|
|
6739
7083
|
if (spec.cli.includes("claude")) {
|
|
6740
|
-
const settingsPath =
|
|
7084
|
+
const settingsPath = join28(projectDir, ".claude", "settings.json");
|
|
6741
7085
|
if (existsSync21(settingsPath)) {
|
|
6742
7086
|
try {
|
|
6743
|
-
JSON.parse(
|
|
7087
|
+
JSON.parse(readFileSync22(settingsPath, "utf8"));
|
|
6744
7088
|
} catch {
|
|
6745
7089
|
throw new Error(
|
|
6746
7090
|
`${relative3(process.cwd(), settingsPath)} is not valid JSON \u2014 left untouched, so no hooks were wired. Fix or delete that file, then run the install again`
|
|
@@ -6830,9 +7174,16 @@ function runInstallStages(ctx, args) {
|
|
|
6830
7174
|
rules: resolveRules(manifestSpec).filter(
|
|
6831
7175
|
(r) => !isBaselineExcluded(`.claude/rules/${r}.md`, baselineExcluded)
|
|
6832
7176
|
),
|
|
7177
|
+
// v26.165.0 NOTE-5 — `AGENTS.md` 가 이번 선택 밖의 기록 CLI 몫까지 다시 렌더될 때의 룰: 기록 트랙 ∪ 이번 트랙(update 와 같은 식)
|
|
7178
|
+
cumulativeRules: resolveRules({
|
|
7179
|
+
...manifestSpec,
|
|
7180
|
+
tracks: [.../* @__PURE__ */ new Set([...(previousLog?.spec.tracks ?? []).filter(isTrack), ...spec.tracks])]
|
|
7181
|
+
}).filter((r) => !isBaselineExcluded(`.claude/rules/${r}.md`, baselineExcluded)),
|
|
6833
7182
|
// #568 — Codex · OpenCode 의 MCP 서버는 `.mcp.json` 에 쓴 것과 같은 값이다.
|
|
6834
7183
|
mcp: harnessMcp,
|
|
6835
7184
|
previousExternal: previousLog?.externalFiles ?? [],
|
|
7185
|
+
// #642 (ADR-096 D2) — CLI 집합은 누적이다: 기록된 CLI 도 `AGENTS.md` 집합에 들어간다(`--reinstall` 도 누적)
|
|
7186
|
+
recordedClis: previousLog === null ? [] : installedClis(previousLog),
|
|
6836
7187
|
// ADR-097 결정 2 — 범위 조건 없이 `--with-codex-trust` 하나로 정한다.
|
|
6837
7188
|
codexTrust: spec.options.withCodexTrust,
|
|
6838
7189
|
// #551 R2 — `.codex/config.toml` · `opencode.json` · 첫 접촉 `AGENTS.md` 의 몫도 `.mcp.json` 과 같은 왕복을
|
|
@@ -6859,9 +7210,14 @@ function runInstallStages(ctx, args) {
|
|
|
6859
7210
|
excluded,
|
|
6860
7211
|
outside: reclaimOutside
|
|
6861
7212
|
});
|
|
6862
|
-
const
|
|
7213
|
+
const anchorRetired = cliTransforms.anchorRetired;
|
|
7214
|
+
const forgotten = /* @__PURE__ */ new Set([
|
|
7215
|
+
...outOfTrack.forget,
|
|
7216
|
+
...anchorRetired?.forget === true ? [anchorRetired.path] : []
|
|
7217
|
+
]);
|
|
6863
7218
|
const logBase = previousLog === null || forgotten.size === 0 ? previousLog : {
|
|
6864
7219
|
...previousLog,
|
|
7220
|
+
spec: { ...previousLog.spec, clis: [...installedClis(previousLog)] },
|
|
6865
7221
|
externalFiles: (previousLog.externalFiles ?? []).filter((f) => !forgotten.has(f.path))
|
|
6866
7222
|
};
|
|
6867
7223
|
stage.envExampleCreated = writeEnvExample(projectDir, spec.tracks);
|
|
@@ -7002,7 +7358,7 @@ function releasedThisRun(args) {
|
|
|
7002
7358
|
}
|
|
7003
7359
|
if (id.startsWith(BASELINE_PREFIX)) {
|
|
7004
7360
|
const again2 = manifest.some(
|
|
7005
|
-
(e2) => e2.applies(manifestSpec) && classifyBaselineTarget(e2.target)?.id === id && existsSync21(
|
|
7361
|
+
(e2) => e2.applies(manifestSpec) && classifyBaselineTarget(e2.target)?.id === id && existsSync21(join28(projectDir, e2.target))
|
|
7006
7362
|
);
|
|
7007
7363
|
return { id, again: again2, tail: "the harness manages it again" };
|
|
7008
7364
|
}
|
|
@@ -7011,7 +7367,7 @@ function releasedThisRun(args) {
|
|
|
7011
7367
|
options: spec.options,
|
|
7012
7368
|
...spec.userOverride ? { userOverride: spec.userOverride } : {}
|
|
7013
7369
|
});
|
|
7014
|
-
const recorded2 = (previousLog?.assets.some((a) => a.id === id) ?? false) || [".claude/skills", ".agents/skills"].some((d2) => existsSync21(
|
|
7370
|
+
const recorded2 = (previousLog?.assets.some((a) => a.id === id) ?? false) || [".claude/skills", ".agents/skills"].some((d2) => existsSync21(join28(projectDir, d2, id)));
|
|
7015
7371
|
return {
|
|
7016
7372
|
id,
|
|
7017
7373
|
again,
|
|
@@ -7023,10 +7379,10 @@ function pendingKeyExcludes(projectDir, keyExclude, touched) {
|
|
|
7023
7379
|
const done = new Set(touched);
|
|
7024
7380
|
return keyExclude.flatMap((id) => {
|
|
7025
7381
|
const path = sharedPathOfKeyId(id);
|
|
7026
|
-
if (path === null || done.has(path) || !existsSync21(
|
|
7382
|
+
if (path === null || done.has(path) || !existsSync21(join28(projectDir, path))) return [];
|
|
7027
7383
|
const name = adapterFor(path);
|
|
7028
7384
|
const keys = [...excludedKeys(path, [id])];
|
|
7029
|
-
const present2 = name === null ? null : ADAPTERS[name].read(
|
|
7385
|
+
const present2 = name === null ? null : ADAPTERS[name].read(readFileSync22(join28(projectDir, path), "utf8"), keys, projectDir);
|
|
7030
7386
|
return present2 !== null && present2.size === 0 ? [] : [{ id, path }];
|
|
7031
7387
|
});
|
|
7032
7388
|
}
|
|
@@ -7070,7 +7426,7 @@ function installCliNeutralAssets(manifestSpec, templatesDir, baselineExcluded, w
|
|
|
7070
7426
|
continue;
|
|
7071
7427
|
}
|
|
7072
7428
|
if (!isCliNeutralTarget(entry.target)) continue;
|
|
7073
|
-
const source =
|
|
7429
|
+
const source = join28(templatesDir, entry.source);
|
|
7074
7430
|
if (!existsSync21(source)) {
|
|
7075
7431
|
result.skipped += 1;
|
|
7076
7432
|
continue;
|
|
@@ -7091,13 +7447,13 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
|
|
|
7091
7447
|
if (!entry.applies(manifestSpec)) {
|
|
7092
7448
|
continue;
|
|
7093
7449
|
}
|
|
7094
|
-
const target =
|
|
7450
|
+
const target = join28(projectDir, entry.target);
|
|
7095
7451
|
if (isBaselineExcluded(entry.target, baselineExcluded)) {
|
|
7096
7452
|
result.excluded.push(entry.target);
|
|
7097
7453
|
if (existsSync21(target)) result.excludedOnDisk.push(entry.target);
|
|
7098
7454
|
continue;
|
|
7099
7455
|
}
|
|
7100
|
-
const source =
|
|
7456
|
+
const source = join28(templatesDir, entry.source);
|
|
7101
7457
|
if (!existsSync21(source)) {
|
|
7102
7458
|
result.skipped += 1;
|
|
7103
7459
|
continue;
|
|
@@ -7128,18 +7484,18 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
|
|
|
7128
7484
|
if (!result.foreignOwned.includes(slot)) result.foreignOwned.push(slot);
|
|
7129
7485
|
continue;
|
|
7130
7486
|
}
|
|
7131
|
-
if (writer.skipOutside(
|
|
7487
|
+
if (writer.skipOutside(join28(projectDir, entry.target, rel))) {
|
|
7132
7488
|
leftOutside += 1;
|
|
7133
7489
|
continue;
|
|
7134
7490
|
}
|
|
7135
|
-
writer.harness(`${entry.target}/${rel}`, { source:
|
|
7491
|
+
writer.harness(`${entry.target}/${rel}`, { source: join28(source, rel) });
|
|
7136
7492
|
}
|
|
7137
7493
|
if (leftOutside > 0) continue;
|
|
7138
7494
|
result.dirsCopied += 1;
|
|
7139
7495
|
}
|
|
7140
7496
|
accumulateCategory(result.categories, entry);
|
|
7141
7497
|
}
|
|
7142
|
-
const hookDir =
|
|
7498
|
+
const hookDir = join28(projectDir, ".claude/hooks");
|
|
7143
7499
|
if (existsSync21(hookDir)) {
|
|
7144
7500
|
chmodHooksSync(hookDir, projectDir);
|
|
7145
7501
|
}
|
|
@@ -7156,12 +7512,12 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
|
|
|
7156
7512
|
if (settingsSource !== null) {
|
|
7157
7513
|
writeSettingsPortion(writer, settingsSource, projectDir, previousLog, (script) => {
|
|
7158
7514
|
const target = `${HOOKS_PREFIX}${script}`;
|
|
7159
|
-
return manifest.some((e2) => e2.target === target && e2.applies(manifestSpec)) && !isBaselineExcluded(target, baselineExcluded) && existsSync21(
|
|
7515
|
+
return manifest.some((e2) => e2.target === target && e2.applies(manifestSpec)) && !isBaselineExcluded(target, baselineExcluded) && existsSync21(join28(projectDir, target));
|
|
7160
7516
|
});
|
|
7161
7517
|
result.keptHookRefs.push(
|
|
7162
7518
|
...findStaleHookRefs(
|
|
7163
|
-
|
|
7164
|
-
|
|
7519
|
+
join28(projectDir, ".claude", "settings.json"),
|
|
7520
|
+
join28(projectDir, ".claude")
|
|
7165
7521
|
)
|
|
7166
7522
|
);
|
|
7167
7523
|
}
|
|
@@ -7171,7 +7527,7 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
|
|
|
7171
7527
|
// ADR-085 · update(`upsertRootImport`)와 같은 판정 — 실제로 깔린 스킬만 안내한다. 밖 링크라 쓰지 않은
|
|
7172
7528
|
// 스킬(ADR-098)을 "installed" 라 적으면 다음 update 가 그 절을 걷어 내 두 동작이 서로 반대로 쓴다.
|
|
7173
7529
|
(manifestSpec.selectedInternalSkills ?? []).filter(
|
|
7174
|
-
(id) => existsSync21(
|
|
7530
|
+
(id) => existsSync21(join28(projectDir, ".claude", "skills", id))
|
|
7175
7531
|
),
|
|
7176
7532
|
harnessRoot,
|
|
7177
7533
|
writer
|
|
@@ -7187,7 +7543,7 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
|
|
|
7187
7543
|
function writeSettingsPortion(writer, source, projectDir, previousLog, hookInstalled) {
|
|
7188
7544
|
return writeSettingsShared(
|
|
7189
7545
|
writer,
|
|
7190
|
-
|
|
7546
|
+
readFileSync22(source, "utf8"),
|
|
7191
7547
|
projectDir,
|
|
7192
7548
|
previousLog,
|
|
7193
7549
|
hookInstalled
|
|
@@ -7342,7 +7698,10 @@ function recordInterruptedInstall(ctx, error, run) {
|
|
|
7342
7698
|
}
|
|
7343
7699
|
}
|
|
7344
7700
|
function registeredTrust(codexOptIn, projectDir) {
|
|
7345
|
-
return codexOptIn?.trustEntry.status === "registered" && codexOptIn.trustEntry.configPath ? {
|
|
7701
|
+
return codexOptIn?.trustEntry.status === "registered" && codexOptIn.trustEntry.configPath ? {
|
|
7702
|
+
configPath: codexOptIn.trustEntry.configPath,
|
|
7703
|
+
projectDir: codexOptIn.trustEntry.key ?? projectDir
|
|
7704
|
+
} : void 0;
|
|
7346
7705
|
}
|
|
7347
7706
|
function writtenSoFar(ledger, cliFiles, rootFiles, rootImportWritten) {
|
|
7348
7707
|
const files = /* @__PURE__ */ new Set([
|
|
@@ -7440,12 +7799,12 @@ function accumulateCategory(cats, entry) {
|
|
|
7440
7799
|
}
|
|
7441
7800
|
}
|
|
7442
7801
|
function writeRootClaudeMd(projectDir, tracks, continuousSkills, harnessRoot, writer) {
|
|
7443
|
-
const target =
|
|
7802
|
+
const target = join28(projectDir, "CLAUDE.md");
|
|
7444
7803
|
if (writer.skipOutside(target)) return null;
|
|
7445
|
-
const existing = existsSync21(target) ?
|
|
7804
|
+
const existing = existsSync21(target) ? readFileSync22(target, "utf-8") : null;
|
|
7446
7805
|
const seeded = existing === null ? seedRootClaudeProjectContext(projectDir, harnessRoot) : null;
|
|
7447
7806
|
const content = upsertHarnessImport(existing, {
|
|
7448
|
-
projectName:
|
|
7807
|
+
projectName: basename4(projectDir),
|
|
7449
7808
|
tracks,
|
|
7450
7809
|
continuousSkills,
|
|
7451
7810
|
...seeded === null ? {} : { projectContext: seeded }
|
|
@@ -7470,14 +7829,14 @@ function chmodHooksSync(hookDir, projectDir) {
|
|
|
7470
7829
|
}
|
|
7471
7830
|
}
|
|
7472
7831
|
function listHookFiles(hookDir) {
|
|
7473
|
-
return
|
|
7832
|
+
return readdirSync4(hookDir, { withFileTypes: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".sh")).map((e2) => resolve(hookDir, e2.name));
|
|
7474
7833
|
}
|
|
7475
7834
|
|
|
7476
7835
|
// src/commands/install-render.ts
|
|
7477
7836
|
init_esm_shims();
|
|
7478
7837
|
import { homedir as homedir3 } from "os";
|
|
7479
|
-
import { existsSync as existsSync23, readdirSync as
|
|
7480
|
-
import { join as
|
|
7838
|
+
import { existsSync as existsSync23, readdirSync as readdirSync5, readFileSync as readFileSync24 } from "fs";
|
|
7839
|
+
import { join as join30, relative as relative4 } from "path";
|
|
7481
7840
|
|
|
7482
7841
|
// src/preset-recommend.ts
|
|
7483
7842
|
init_esm_shims();
|
|
@@ -7513,10 +7872,10 @@ function groupAssetsByCategory(assetIds) {
|
|
|
7513
7872
|
|
|
7514
7873
|
// src/resident-entries.ts
|
|
7515
7874
|
init_esm_shims();
|
|
7516
|
-
import { existsSync as existsSync22 } from "fs";
|
|
7517
|
-
import { join as
|
|
7875
|
+
import { existsSync as existsSync22, readFileSync as readFileSync23 } from "fs";
|
|
7876
|
+
import { basename as basename5, join as join29 } from "path";
|
|
7518
7877
|
function residentEntries(spec) {
|
|
7519
|
-
const skillThere = (id) => INTERNAL_BUNDLED_SKILL_IDS.includes(id) && [".claude/skills", ".agents/skills"].some((d2) => existsSync22(
|
|
7878
|
+
const skillThere = (id) => INTERNAL_BUNDLED_SKILL_IDS.includes(id) && [".claude/skills", ".agents/skills"].some((d2) => existsSync22(join29(spec.projectDir, d2, id)));
|
|
7520
7879
|
const forceExclude = (spec.userOverride?.forceExclude ?? []).filter((id) => !skillThere(id));
|
|
7521
7880
|
const assetSpec = buildManifestSpec(
|
|
7522
7881
|
spec.userOverride ? { ...spec, userOverride: { ...spec.userOverride, forceExclude } } : spec
|
|
@@ -7540,25 +7899,44 @@ function residentEntries(spec) {
|
|
|
7540
7899
|
});
|
|
7541
7900
|
}
|
|
7542
7901
|
function residentCostFor(spec, entries = residentEntries(spec), contextFiles) {
|
|
7543
|
-
return residentCost(entries, void 0, spec.cli, contextFiles);
|
|
7902
|
+
if (contextFiles !== void 0) return residentCost(entries, void 0, spec.cli, contextFiles);
|
|
7903
|
+
const cost = residentCost(entries, void 0, spec.cli, plannedClaudeContext(spec));
|
|
7904
|
+
if (anchorSurface(spec.cli).scaffold === "agents-md" && existsSync22(join29(spec.projectDir, "AGENTS.md"))) {
|
|
7905
|
+
return makeResidentCost({
|
|
7906
|
+
...cost,
|
|
7907
|
+
contextNote: "planned, not counting text already in AGENTS.md"
|
|
7908
|
+
});
|
|
7909
|
+
}
|
|
7910
|
+
return cost;
|
|
7911
|
+
}
|
|
7912
|
+
function plannedClaudeContext(spec) {
|
|
7913
|
+
if (!spec.cli.includes("claude")) return void 0;
|
|
7914
|
+
const root = resolveBundleRoot();
|
|
7915
|
+
const rootClaude = join29(spec.projectDir, "CLAUDE.md");
|
|
7916
|
+
const existing = existsSync22(rootClaude) ? readFileSync23(rootClaude, "utf-8") : null;
|
|
7917
|
+
const seeded = existing === null ? seedRootClaudeProjectContext(spec.projectDir, root) : null;
|
|
7918
|
+
if (existing === null && seeded === null) return void 0;
|
|
7919
|
+
const manifestSpec = buildManifestSpec(spec);
|
|
7920
|
+
const text = upsertHarnessImport(existing, {
|
|
7921
|
+
projectName: basename5(spec.projectDir),
|
|
7922
|
+
tracks: manifestSpec.tracks,
|
|
7923
|
+
continuousSkills: manifestSpec.selectedInternalSkills ?? [],
|
|
7924
|
+
...seeded === null ? {} : { projectContext: seeded }
|
|
7925
|
+
});
|
|
7926
|
+
return [join29(root, "templates", "CLAUDE.md"), { text }];
|
|
7544
7927
|
}
|
|
7545
7928
|
function onDiskTargets(spec, assetSpec) {
|
|
7546
7929
|
const out = /* @__PURE__ */ new Set();
|
|
7547
7930
|
for (const e2 of buildManifest(assetSpec)) {
|
|
7548
7931
|
if (!e2.applies(assetSpec)) continue;
|
|
7549
7932
|
const t = classifyBaselineTarget(e2.target);
|
|
7550
|
-
if (t !== null && existsSync22(
|
|
7933
|
+
if (t !== null && existsSync22(join29(spec.projectDir, e2.target))) out.add(t.id);
|
|
7551
7934
|
}
|
|
7552
7935
|
return out;
|
|
7553
7936
|
}
|
|
7554
7937
|
|
|
7555
7938
|
// src/commands/install-render.ts
|
|
7556
|
-
var CLI_SUMMARY_LABELS =
|
|
7557
|
-
claude: "Claude",
|
|
7558
|
-
codex: "Codex",
|
|
7559
|
-
opencode: "OpenCode",
|
|
7560
|
-
antigravity: "Antigravity"
|
|
7561
|
-
};
|
|
7939
|
+
var CLI_SUMMARY_LABELS = CLI_DISPLAY_NAMES;
|
|
7562
7940
|
var LEGACY_ANCHOR_NOTE = `legacy anchor \xB7 no longer updated \u2014 content now in ${HARNESS_ANCHOR_FILE}; delete it when you no longer need it`;
|
|
7563
7941
|
function renderInstallHeader(log, spec, mode) {
|
|
7564
7942
|
const headerLabel = mode === "update" ? "uzys-agent-harness \xB7 update" : mode === "add" ? "uzys-agent-harness \xB7 add" : mode === "reinstall" ? "uzys-agent-harness \xB7 reinstall" : "uzys-agent-harness \xB7 install";
|
|
@@ -7667,7 +8045,10 @@ function renderUpdateSummary(log, spec, report) {
|
|
|
7667
8045
|
log(infoRow("ROLLBACK", `rm -rf .claude && mv ${shellQuotePath(report.backup)} .claude`));
|
|
7668
8046
|
}
|
|
7669
8047
|
const cost = formatResidentCostLine(
|
|
7670
|
-
|
|
8048
|
+
rulesInsideAgentsMd(
|
|
8049
|
+
spec,
|
|
8050
|
+
residentCostFor(spec, residentEntriesOnDisk(spec), contextFilesOnDisk(spec))
|
|
8051
|
+
),
|
|
7671
8052
|
summarizeContextCost(finalSelectedAssets(spec.tracks, spec.userOverride)).unmeasuredCount
|
|
7672
8053
|
);
|
|
7673
8054
|
if (cost) log(infoRow("CONTEXT", cost));
|
|
@@ -7695,44 +8076,66 @@ function renderUpdateSummary(log, spec, report) {
|
|
|
7695
8076
|
}
|
|
7696
8077
|
function residentEntriesOnDisk(spec) {
|
|
7697
8078
|
const planned = residentEntries(spec);
|
|
7698
|
-
|
|
7699
|
-
|
|
7700
|
-
|
|
7701
|
-
|
|
7702
|
-
|
|
7703
|
-
return existsSync23(dir) ? readdirSync4(dir, { withFileTypes: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".md")).map((e2) => ({
|
|
8079
|
+
const hasClaude = spec.cli.includes("claude");
|
|
8080
|
+
const surface = anchorSurface(spec.cli).scaffold;
|
|
8081
|
+
const mdFiles = (dir, kind, skip) => {
|
|
8082
|
+
const abs = join30(spec.projectDir, dir);
|
|
8083
|
+
return existsSync23(abs) ? readdirSync5(abs, { withFileTypes: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".md") && e2.name !== skip).map((e2) => ({
|
|
7704
8084
|
source: `${kind}/${e2.name}`,
|
|
8085
|
+
// `target` 은 `residentCost` 의 분류 키다 — 실제 자리는 `file` 이 말한다.
|
|
7705
8086
|
target: `.claude/${kind}/${e2.name}`,
|
|
7706
8087
|
// 배포판이 아니라 **이 프로젝트의 파일**을 잰다 — 사용자가 고친 본문도,
|
|
7707
8088
|
// 배포판에 더는 없는 파일도 실제로 상주하는 것은 이쪽이다.
|
|
7708
|
-
file:
|
|
8089
|
+
file: join30(abs, e2.name)
|
|
7709
8090
|
})) : [];
|
|
7710
8091
|
};
|
|
7711
|
-
const
|
|
8092
|
+
const rules = surface === "claude" ? mdFiles(join30(".claude", "rules"), "rules") : rulesInline(spec.cli) ? planned.filter((e2) => e2.target.startsWith(".claude/rules/")) : mdFiles(join30(".agents", "rules"), "rules", "uzys-harness.md");
|
|
8093
|
+
const slot = hasClaude ? join30(".claude", "skills") : join30(".agents", "skills");
|
|
8094
|
+
const skills = planned.filter((e2) => e2.target.startsWith(".claude/skills/")).map((e2) => ({
|
|
8095
|
+
...e2,
|
|
8096
|
+
file: join30(spec.projectDir, slot, e2.target.slice(".claude/skills/".length))
|
|
8097
|
+
})).filter((e2) => existsSync23(e2.file));
|
|
8098
|
+
const agents = hasClaude ? mdFiles(join30(".claude", "agents"), "agents") : [];
|
|
7712
8099
|
return [
|
|
7713
8100
|
...planned.filter(
|
|
7714
8101
|
(e2) => !e2.target.startsWith(".claude/rules/") && !e2.target.startsWith(".claude/agents/") && !e2.target.startsWith(".claude/skills/")
|
|
7715
8102
|
),
|
|
7716
|
-
...
|
|
8103
|
+
...rules,
|
|
7717
8104
|
...skills,
|
|
7718
|
-
...
|
|
8105
|
+
...agents
|
|
7719
8106
|
];
|
|
7720
8107
|
}
|
|
7721
8108
|
function contextFilesOnDisk(spec) {
|
|
7722
|
-
|
|
7723
|
-
|
|
7724
|
-
|
|
7725
|
-
|
|
8109
|
+
const surface = anchorSurface(spec.cli).scaffold;
|
|
8110
|
+
const files = surface === "claude" ? [HARNESS_ANCHOR_FILE, "CLAUDE.md", join30(".claude", "CLAUDE.md")] : ["AGENTS.md"];
|
|
8111
|
+
return files.map((f) => join30(spec.projectDir, f));
|
|
8112
|
+
}
|
|
8113
|
+
function rulesInsideAgentsMd(spec, cost) {
|
|
8114
|
+
if (anchorSurface(spec.cli).scaffold !== "agents-md" || !rulesInline(spec.cli)) return cost;
|
|
8115
|
+
const rendered = spec.updateOnly === void 0 || spec.updateOnly.includes("external");
|
|
8116
|
+
return makeResidentCost({
|
|
8117
|
+
...cost,
|
|
8118
|
+
rules: 0,
|
|
8119
|
+
rulesInline: rendered ? "rendered" : "planned",
|
|
8120
|
+
contextNote: "whole file, incl. your text"
|
|
8121
|
+
});
|
|
8122
|
+
}
|
|
8123
|
+
function rulesInline(cli) {
|
|
8124
|
+
return cli.includes("codex") || cli.includes("opencode");
|
|
7726
8125
|
}
|
|
7727
8126
|
function renderCliArtifacts(log, spec, report) {
|
|
7728
|
-
const
|
|
7729
|
-
const
|
|
8127
|
+
const agentsReport = report.agentsMd ?? null;
|
|
8128
|
+
const hasArtifacts = Boolean(
|
|
8129
|
+
report.codex || report.opencode || report.antigravity || agentsReport
|
|
8130
|
+
);
|
|
8131
|
+
const shown = [...spec.cli, ...agentsReport?.clis ?? []];
|
|
8132
|
+
const cliSelected = targetsInclude(shown, "codex") || targetsInclude(shown, "opencode") || targetsInclude(shown, "antigravity");
|
|
7730
8133
|
if (!hasArtifacts || !cliSelected) {
|
|
7731
8134
|
return;
|
|
7732
8135
|
}
|
|
7733
|
-
log(unifiedSection(formatCliPhaseTitle(
|
|
8136
|
+
log(unifiedSection(formatCliPhaseTitle(shown)));
|
|
7734
8137
|
log("");
|
|
7735
|
-
const agentsMd =
|
|
8138
|
+
const agentsMd = agentsReport?.agentsMd ?? null;
|
|
7736
8139
|
const agentsBlock = agentsMd?.model === "block" ? agentsMd.shared : null;
|
|
7737
8140
|
if (agentsBlock) {
|
|
7738
8141
|
const row = sharedRow(
|
|
@@ -7740,12 +8143,15 @@ function renderCliArtifacts(log, spec, report) {
|
|
|
7740
8143
|
harnessKeysInFile(agentsBlock).length > 0 ? "one harness block at the end (your text kept as-is)" : "no harness block in the file (your text kept as-is)"
|
|
7741
8144
|
);
|
|
7742
8145
|
if (row) log(row);
|
|
7743
|
-
} else if (
|
|
7744
|
-
|
|
7745
|
-
|
|
7746
|
-
|
|
7747
|
-
|
|
7748
|
-
|
|
8146
|
+
} else if (agentsReport && agentsReport.clis.length > 1) {
|
|
8147
|
+
const names = agentsReport.clis.map((c3) => CLI_DISPLAY_NAMES[c3]).join(" \xB7 ");
|
|
8148
|
+
log(assetRow("success", "AGENTS.md", `shared (${names})`));
|
|
8149
|
+
} else if (agentsReport) {
|
|
8150
|
+
const only = agentsReport.clis[0];
|
|
8151
|
+
const who = only === void 0 ? "" : ` (${CLI_DISPLAY_NAMES[only]})`;
|
|
8152
|
+
log(assetRow("success", "AGENTS.md", `from ${HARNESS_ANCHOR_FILE}${who}`));
|
|
8153
|
+
}
|
|
8154
|
+
const agentsSeededFrom = agentsReport?.agentsMdSeededFrom ?? null;
|
|
7749
8155
|
if (agentsSeededFrom) {
|
|
7750
8156
|
log(
|
|
7751
8157
|
assetRow(
|
|
@@ -7758,6 +8164,8 @@ function renderCliArtifacts(log, spec, report) {
|
|
|
7758
8164
|
if (report.codex) {
|
|
7759
8165
|
const configRow = report.codex.configToml ? sharedRow(report.codex.configToml, configRegionsPart(report.codex.configToml)) : assetRow("success", ".codex/config.toml", "settings + [mcp_servers.*]");
|
|
7760
8166
|
if (configRow) log(configRow);
|
|
8167
|
+
const retired2 = report.codex.configToml ? retiredFeaturesLeftBehind(spec.projectDir, report.codex.configToml) : [];
|
|
8168
|
+
if (retired2.length > 0) log(retiredFeaturesRow(".codex/config.toml", retired2));
|
|
7761
8169
|
log(assetRow("success", ".codex/hooks/", `${report.codex.hookFiles.length} files`));
|
|
7762
8170
|
if (report.codex.skillFiles.length > 0) {
|
|
7763
8171
|
log(
|
|
@@ -7770,7 +8178,7 @@ function renderCliArtifacts(log, spec, report) {
|
|
|
7770
8178
|
}
|
|
7771
8179
|
if (report.codexOptIn?.trustEntry.enabled) {
|
|
7772
8180
|
const trust = report.codexOptIn.trustEntry;
|
|
7773
|
-
const kind = trust.status === "error" || trust.status === "unreadable" ? "skip" : "success";
|
|
8181
|
+
const kind = trust.status === "error" || trust.status === "unreadable" || trust.status === "left" ? "skip" : "success";
|
|
7774
8182
|
const meta = trust.status === "registered" ? '[projects."<dir>"] trust_level="trusted"' : trust.status === "already-present" ? "already present" : trust.message ?? "error";
|
|
7775
8183
|
log(assetRow(kind, "~/.codex/config.toml trust entry", meta));
|
|
7776
8184
|
}
|
|
@@ -7799,22 +8207,20 @@ function renderCliArtifacts(log, spec, report) {
|
|
|
7799
8207
|
}
|
|
7800
8208
|
}
|
|
7801
8209
|
if (report.antigravity) {
|
|
7802
|
-
const anchor = report.antigravity.rulesFile;
|
|
7803
8210
|
const rules = report.antigravity.harnessRuleFiles.length;
|
|
7804
|
-
|
|
7805
|
-
if (total > 0) {
|
|
7806
|
-
const parts = [
|
|
7807
|
-
...anchor ? [`uzys-harness.md from ${HARNESS_ANCHOR_FILE}`] : [],
|
|
7808
|
-
...rules > 0 ? [`${rules} harness rule${rules === 1 ? "" : "s"}`] : []
|
|
7809
|
-
];
|
|
8211
|
+
if (rules > 0) {
|
|
7810
8212
|
log(
|
|
7811
8213
|
assetRow(
|
|
7812
8214
|
"success",
|
|
7813
8215
|
".agents/rules/",
|
|
7814
|
-
`${
|
|
8216
|
+
`${rules} harness rule${rules === 1 ? "" : "s"} (with Antigravity trigger frontmatter)`
|
|
7815
8217
|
)
|
|
7816
8218
|
);
|
|
7817
8219
|
}
|
|
8220
|
+
}
|
|
8221
|
+
const retired = anchorRetireRow(report.anchorRetired ?? null);
|
|
8222
|
+
if (retired) log(retired);
|
|
8223
|
+
if (report.antigravity) {
|
|
7818
8224
|
if (report.antigravity.skillFiles.length > 0) {
|
|
7819
8225
|
log(
|
|
7820
8226
|
assetRow(
|
|
@@ -7827,6 +8233,21 @@ function renderCliArtifacts(log, spec, report) {
|
|
|
7827
8233
|
}
|
|
7828
8234
|
log("");
|
|
7829
8235
|
}
|
|
8236
|
+
function agentsMdNewsRow(n) {
|
|
8237
|
+
if (n === void 0) return null;
|
|
8238
|
+
const names = n.clis.map((c3) => CLI_DISPLAY_NAMES[c3]).join(" \xB7 ");
|
|
8239
|
+
const seeded = n.seededFrom === null ? "" : `Project Context seeded from ${n.seededFrom}`;
|
|
8240
|
+
if (!n.created) return seeded === "" ? null : assetRow("success", "AGENTS.md", seeded);
|
|
8241
|
+
return assetRow(
|
|
8242
|
+
"success",
|
|
8243
|
+
"AGENTS.md",
|
|
8244
|
+
`created (${names})${seeded === "" ? "" : ` \xB7 ${seeded}`}`
|
|
8245
|
+
);
|
|
8246
|
+
}
|
|
8247
|
+
function anchorRetireRow(r) {
|
|
8248
|
+
if (r === null || r.line === "") return null;
|
|
8249
|
+
return assetRow(r.action === "removed" ? "success" : "skip", r.path, r.line);
|
|
8250
|
+
}
|
|
7830
8251
|
function sharedRow(r, part) {
|
|
7831
8252
|
if (r.action === "skipped") return null;
|
|
7832
8253
|
if (r.action === "left") return assetRow("skip", r.path, `left \u2014 ${r.line}`);
|
|
@@ -7839,6 +8260,10 @@ function sharedRow(r, part) {
|
|
|
7839
8260
|
const out = excludedKeyParts(r).map((p2) => ` \xB7 ${p2}`).join("");
|
|
7840
8261
|
return assetRow("success", r.path, `${verb} \xB7 ${part}${kept}${left}${restored}${out}`);
|
|
7841
8262
|
}
|
|
8263
|
+
function retiredFeaturesRow(path, keys) {
|
|
8264
|
+
const meta = `[features] ${keys.join(" \xB7 ")} make${keys.length === 1 ? "s" : ""} Codex warn at every start ("deprecated" / "ignoring unrecognized configuration") \u2014 delete those lines; if they sit inside the harness region, delete the whole region and run update`;
|
|
8265
|
+
return ` ${c.yellow(symbol.warn)} ${path.padEnd(40, " ")} ${c.dim(meta)}`;
|
|
8266
|
+
}
|
|
7842
8267
|
function sharedLeftRow(f) {
|
|
7843
8268
|
const legacy = f.legacyHook === true ? " \xB7 its [[hooks.session_start]] is the old format \u2014 current Codex ignores it, so the session-start hook does not run" : "";
|
|
7844
8269
|
if (f.left !== void 0) return assetRow("skip", f.path, `left \u2014 ${f.left}${legacy}`);
|
|
@@ -7937,10 +8362,10 @@ function renderFinalSummary(log, spec, report, fromWizard) {
|
|
|
7937
8362
|
const codexTrusted = report.codexOptIn?.trustEntry.status === "registered" || report.codexOptIn?.trustEntry.status === "already-present";
|
|
7938
8363
|
const alreadyTrustedGlobally = (() => {
|
|
7939
8364
|
try {
|
|
7940
|
-
const home = process.env.CODEX_HOME ??
|
|
7941
|
-
const configPath =
|
|
8365
|
+
const home = process.env.CODEX_HOME ?? join30(homedir3(), ".codex");
|
|
8366
|
+
const configPath = join30(home, "config.toml");
|
|
7942
8367
|
if (!existsSync23(configPath)) return false;
|
|
7943
|
-
return hasTrustEntry(
|
|
8368
|
+
return hasTrustEntry(readFileSync24(configPath, "utf8"), spec.projectDir);
|
|
7944
8369
|
} catch {
|
|
7945
8370
|
return false;
|
|
7946
8371
|
}
|
|
@@ -7952,7 +8377,9 @@ function renderFinalSummary(log, spec, report, fromWizard) {
|
|
|
7952
8377
|
`${c.bold("Codex")} turns on .codex/config.toml (MCP \xB7 hooks \xB7 sandbox \xB7 approval) only after you trust this folder:`
|
|
7953
8378
|
)
|
|
7954
8379
|
);
|
|
7955
|
-
const
|
|
8380
|
+
const trust = report.codexOptIn?.trustEntry;
|
|
8381
|
+
const entry = `[projects.${tomlBasicString(trust?.key ?? spec.projectDir)}]`;
|
|
8382
|
+
const headless = trust?.status === "unreadable" ? "headless: fix ~/.codex/config.toml (not valid TOML), then run with --with-codex-trust again" : trust?.status === "error" ? `headless: add ${entry} trust_level = "trusted" to ~/.codex/config.toml by hand (the harness could not append it safely)` : trust?.status === "left" ? `headless: ~/.codex/config.toml already has ${entry} with a trust_level other than "trusted" \u2014 the harness left your setting; change it by hand` : "headless: agent-harness install \u2026 --with-codex-trust";
|
|
7956
8383
|
log(cont(`open Codex here \u2192 ${c.bold('"Trust and continue"')} ${c.dim(`(${headless})`)}`));
|
|
7957
8384
|
}
|
|
7958
8385
|
if (spec.cli.includes("codex")) {
|
|
@@ -7961,7 +8388,7 @@ function renderFinalSummary(log, spec, report, fromWizard) {
|
|
|
7961
8388
|
);
|
|
7962
8389
|
}
|
|
7963
8390
|
const scaffoldFiles = scaffoldFilesForCli(spec.cli).filter(
|
|
7964
|
-
(f) => f === "CLAUDE.md" && report.rootClaudeMd?.created === true || hasFillPrompt(
|
|
8391
|
+
(f) => f === "CLAUDE.md" && report.rootClaudeMd?.created === true || hasFillPrompt(join30(spec.projectDir, f))
|
|
7965
8392
|
);
|
|
7966
8393
|
if (scaffoldFiles.length > 0) {
|
|
7967
8394
|
const auditInstalled = buildManifestSpec(spec).selectedInternalSkills?.includes("audit-harness-fit");
|
|
@@ -7977,7 +8404,7 @@ function renderFinalSummary(log, spec, report, fromWizard) {
|
|
|
7977
8404
|
}
|
|
7978
8405
|
function hasFillPrompt(path) {
|
|
7979
8406
|
try {
|
|
7980
|
-
return
|
|
8407
|
+
return readFileSync24(path, "utf8").includes("<!-- FILL:");
|
|
7981
8408
|
} catch {
|
|
7982
8409
|
return false;
|
|
7983
8410
|
}
|
|
@@ -7995,7 +8422,7 @@ function scaffoldFilesForCli(cli) {
|
|
|
7995
8422
|
if (cli.includes("claude")) {
|
|
7996
8423
|
files.push("CLAUDE.md");
|
|
7997
8424
|
}
|
|
7998
|
-
if (cli.includes("codex") || cli.includes("opencode")) {
|
|
8425
|
+
if (cli.includes("codex") || cli.includes("opencode") || cli.includes("antigravity")) {
|
|
7999
8426
|
files.push("AGENTS.md");
|
|
8000
8427
|
}
|
|
8001
8428
|
return files;
|
|
@@ -8083,12 +8510,19 @@ function renderPhase1Rows(log, baseline, verbose = false) {
|
|
|
8083
8510
|
for (const f of baseline.updateMode.restoredFiles ?? []) {
|
|
8084
8511
|
log(assetRow("success", f.path, restoredFilePart(f)));
|
|
8085
8512
|
}
|
|
8513
|
+
const agentsNews = agentsMdNewsRow(baseline.updateMode.agentsMd);
|
|
8514
|
+
if (agentsNews) log(agentsNews);
|
|
8515
|
+
const retired = anchorRetireRow(baseline.updateMode.anchorRetired ?? null);
|
|
8516
|
+
if (retired) log(retired);
|
|
8086
8517
|
for (const row of outOfTrackRows(baseline.updateMode.outOfTrack)) log(row);
|
|
8087
8518
|
for (const f of baseline.updateMode.sharedWrites ?? []) {
|
|
8088
8519
|
const row = sharedFileRow(f);
|
|
8089
8520
|
if (row !== null) log(row);
|
|
8090
8521
|
}
|
|
8091
|
-
for (const f of baseline.updateMode.sharedLeft ?? [])
|
|
8522
|
+
for (const f of baseline.updateMode.sharedLeft ?? []) {
|
|
8523
|
+
log(sharedLeftRow(f));
|
|
8524
|
+
if (f.retiredFeatures !== void 0) log(retiredFeaturesRow(f.path, f.retiredFeatures));
|
|
8525
|
+
}
|
|
8092
8526
|
for (const row of excludedStillThereRows(baseline.updateMode.excludedStillThere ?? []))
|
|
8093
8527
|
log(row);
|
|
8094
8528
|
for (const r of baseline.updateMode.excludedKeys ?? []) {
|
|
@@ -8913,10 +9347,10 @@ function executeSpec(requested, deps = {}) {
|
|
|
8913
9347
|
return;
|
|
8914
9348
|
}
|
|
8915
9349
|
if (deps.mode === "update" && report.backup !== null) {
|
|
8916
|
-
const liveClaude =
|
|
9350
|
+
const liveClaude = join31(spec.projectDir, ".claude");
|
|
8917
9351
|
if (dirTreesIdentical(liveClaude, report.backup)) {
|
|
8918
9352
|
try {
|
|
8919
|
-
|
|
9353
|
+
rmSync4(report.backup, { recursive: true, force: true });
|
|
8920
9354
|
report.backup = null;
|
|
8921
9355
|
} catch {
|
|
8922
9356
|
}
|
|
@@ -8940,8 +9374,8 @@ function resolveScopeOption(value, err) {
|
|
|
8940
9374
|
}
|
|
8941
9375
|
function unknownKeyWarning(id, flag, keys) {
|
|
8942
9376
|
const prefix = id.slice(0, id.indexOf(":") + 1);
|
|
8943
|
-
const
|
|
8944
|
-
const shown =
|
|
9377
|
+
const same2 = [...keys].filter((k2) => k2.startsWith(prefix)).sort();
|
|
9378
|
+
const shown = same2.length > 0 ? same2 : [...keys].sort();
|
|
8945
9379
|
return shown.length > 0 ? `[WARN] Unknown key id '${id}' (${flag}). Skipping. Key ids this install writes: ${shown.join(", ")}` : `[WARN] Unknown key id '${id}' (${flag}). Skipping. This install writes no harness part with that name`;
|
|
8946
9380
|
}
|
|
8947
9381
|
function conflictingIds(options) {
|
|
@@ -9000,8 +9434,8 @@ function registerInstallCommand(cli) {
|
|
|
9000
9434
|
|
|
9001
9435
|
// src/commands/list.ts
|
|
9002
9436
|
init_esm_shims();
|
|
9003
|
-
import { existsSync as existsSync24, readFileSync as
|
|
9004
|
-
import { join as
|
|
9437
|
+
import { existsSync as existsSync24, readFileSync as readFileSync25 } from "fs";
|
|
9438
|
+
import { join as join32, resolve as resolve3 } from "path";
|
|
9005
9439
|
function listAction(options = {}, deps = {}) {
|
|
9006
9440
|
const log = deps.log ?? console.log;
|
|
9007
9441
|
const err = deps.err ?? console.error;
|
|
@@ -9104,8 +9538,8 @@ function formatHarnessFileRows(log, projectDir) {
|
|
|
9104
9538
|
const rows = [` ${c.dim(tops.length > 0 ? tops.join(" ") : "(none recorded)")}`];
|
|
9105
9539
|
const rootMd = log.templates.rootClaudeMd;
|
|
9106
9540
|
if (rootMd) {
|
|
9107
|
-
const path =
|
|
9108
|
-
const modified = existsSync24(path) && hashContent(
|
|
9541
|
+
const path = join32(projectDir, rootMd.path);
|
|
9542
|
+
const modified = existsSync24(path) && hashContent(readFileSync25(path, "utf8")) !== rootMd.sha256;
|
|
9109
9543
|
rows.push(
|
|
9110
9544
|
modified ? ` ${c.dim(rootMd.path)} ${c.yellow("(\uC218\uC815\uB428 \u2014 uninstall \uC2DC \uBCF4\uC874)")}` : ` ${c.dim(rootMd.path)}`
|
|
9111
9545
|
);
|
|
@@ -9119,7 +9553,7 @@ var ROOT_FILE_CHANGE = {
|
|
|
9119
9553
|
displaced: "\uBE44\uCF1C \uB460"
|
|
9120
9554
|
};
|
|
9121
9555
|
function formatRootFileRows(rootFiles, projectDir) {
|
|
9122
|
-
const present2 = rootFiles.filter((f) => existsSync24(
|
|
9556
|
+
const present2 = rootFiles.filter((f) => existsSync24(join32(projectDir, f.path)));
|
|
9123
9557
|
const width = Math.max(0, ...present2.map((f) => f.path.length));
|
|
9124
9558
|
return present2.map(
|
|
9125
9559
|
(f) => ` ${c.dim(padDisplay(f.path, width))} ${c.dim(ROOT_FILE_CHANGE[f.change])} ${c.dim(f.notes.join(" / "))}`
|
|
@@ -9140,16 +9574,16 @@ import {
|
|
|
9140
9574
|
accessSync,
|
|
9141
9575
|
existsSync as existsSync25,
|
|
9142
9576
|
constants as fsConstants,
|
|
9143
|
-
lstatSync as
|
|
9144
|
-
readdirSync as
|
|
9145
|
-
readFileSync as
|
|
9146
|
-
realpathSync as
|
|
9577
|
+
lstatSync as lstatSync5,
|
|
9578
|
+
readdirSync as readdirSync6,
|
|
9579
|
+
readFileSync as readFileSync26,
|
|
9580
|
+
realpathSync as realpathSync3,
|
|
9147
9581
|
rmdirSync as rmdirSync2,
|
|
9148
|
-
rmSync as
|
|
9582
|
+
rmSync as rmSync5,
|
|
9149
9583
|
statSync as statSync4,
|
|
9150
9584
|
writeFileSync as writeFileSync9
|
|
9151
9585
|
} from "fs";
|
|
9152
|
-
import { dirname as dirname6, join as
|
|
9586
|
+
import { dirname as dirname6, join as join33, relative as relative5, resolve as resolve4, sep as sep5 } from "path";
|
|
9153
9587
|
|
|
9154
9588
|
// src/uninstall-interactive.ts
|
|
9155
9589
|
init_esm_shims();
|
|
@@ -10765,7 +11199,7 @@ Proceed?`, initialValue: false });
|
|
|
10765
11199
|
|
|
10766
11200
|
// src/commands/uninstall.ts
|
|
10767
11201
|
function templateDirPath(projectDir, rel) {
|
|
10768
|
-
return
|
|
11202
|
+
return join33(projectDir, rel.replace(/\/+$/, ""));
|
|
10769
11203
|
}
|
|
10770
11204
|
function outsideTemplateLink(projectDir, rel) {
|
|
10771
11205
|
return outsideLinkAt(projectDir, rel.replace(/\/+$/, ""));
|
|
@@ -10988,7 +11422,7 @@ function unwritablePaths(ctx) {
|
|
|
10988
11422
|
const project = ctx.targetAssets.filter((a) => a.scope === "project");
|
|
10989
11423
|
if (project.some((a) => a.method === "npm")) need.add(".");
|
|
10990
11424
|
const parentOf = (path) => {
|
|
10991
|
-
const abs =
|
|
11425
|
+
const abs = join33(projectDir, path);
|
|
10992
11426
|
if (!safeExists(abs)) return;
|
|
10993
11427
|
const real = safeRealpath(abs);
|
|
10994
11428
|
if (real !== null && isOutsideProject(projectDir, real)) return;
|
|
@@ -10999,7 +11433,7 @@ function unwritablePaths(ctx) {
|
|
|
10999
11433
|
if (!ctx.keepTemplates) {
|
|
11000
11434
|
const recordedShared = new Set((installLog.portions ?? []).map((p2) => p2.path));
|
|
11001
11435
|
for (const p2 of ctx.sharedPaths) {
|
|
11002
|
-
if (recordedShared.has(p2) && existsSync25(
|
|
11436
|
+
if (recordedShared.has(p2) && existsSync25(join33(projectDir, p2))) need.add(p2);
|
|
11003
11437
|
}
|
|
11004
11438
|
for (const f of installLog.externalFiles ?? []) {
|
|
11005
11439
|
if (!underAny(f.path, ctx.movedDirs)) parentOf(f.path);
|
|
@@ -11008,7 +11442,7 @@ function unwritablePaths(ctx) {
|
|
|
11008
11442
|
}
|
|
11009
11443
|
return [...need].filter((p2) => {
|
|
11010
11444
|
try {
|
|
11011
|
-
accessSync(
|
|
11445
|
+
accessSync(join33(projectDir, p2), fsConstants.W_OK);
|
|
11012
11446
|
return false;
|
|
11013
11447
|
} catch (e2) {
|
|
11014
11448
|
return e2.code !== "ENOENT";
|
|
@@ -11017,7 +11451,7 @@ function unwritablePaths(ctx) {
|
|
|
11017
11451
|
}
|
|
11018
11452
|
function safeExists(abs) {
|
|
11019
11453
|
try {
|
|
11020
|
-
|
|
11454
|
+
lstatSync5(abs);
|
|
11021
11455
|
return true;
|
|
11022
11456
|
} catch {
|
|
11023
11457
|
return false;
|
|
@@ -11092,7 +11526,7 @@ function settleLog(ctx, io) {
|
|
|
11092
11526
|
return true;
|
|
11093
11527
|
}
|
|
11094
11528
|
} else {
|
|
11095
|
-
io.rm(
|
|
11529
|
+
io.rm(join33(projectDir, INSTALL_LOG_DIR));
|
|
11096
11530
|
if (outsideTemplateLink(projectDir, LEGACY_INSTALL_LOG_DIR) === null) {
|
|
11097
11531
|
io.rm(legacyInstallLogPath(projectDir));
|
|
11098
11532
|
}
|
|
@@ -11118,7 +11552,7 @@ function dryRunLines(plan, installLog, projectDir, keepTemplates, rootFiles, har
|
|
|
11118
11552
|
for (const rel of dirs) {
|
|
11119
11553
|
const kept = outsideTemplateLink(projectDir, rel);
|
|
11120
11554
|
if (kept) lines2.push(keptOutsideLinkLine(kept, installLog));
|
|
11121
|
-
else if (existsSync25(
|
|
11555
|
+
else if (existsSync25(join33(projectDir, rel))) lines2.push(MOVE_ASIDE_PREVIEW(rel));
|
|
11122
11556
|
}
|
|
11123
11557
|
if (dirs.length === 0) lines2.push(" \u25CB remove templates: (none)");
|
|
11124
11558
|
const rootMd = installLog.templates.rootClaudeMd;
|
|
@@ -11148,7 +11582,7 @@ function advisoryLines(plan, projectDir, rootFiles, keepTemplates = false, globa
|
|
|
11148
11582
|
"",
|
|
11149
11583
|
c.yellow("[GLOBAL] Codex trust entry \u2014 remove by hand (never deleted automatically):"),
|
|
11150
11584
|
c.dim(` \xB7 ${globalTrust.configPath}`),
|
|
11151
|
-
c.dim(` [projects
|
|
11585
|
+
c.dim(` [projects.${tomlBasicString(globalTrust.projectDir)}]`)
|
|
11152
11586
|
);
|
|
11153
11587
|
}
|
|
11154
11588
|
if (plan.globalAdvisories.length > 0) {
|
|
@@ -11169,14 +11603,14 @@ function advisoryLines(plan, projectDir, rootFiles, keepTemplates = false, globa
|
|
|
11169
11603
|
function backupArtifactAdvisoryLines(projectDir) {
|
|
11170
11604
|
const stampRe = /\.backup-\d{8}T\d{6}$/;
|
|
11171
11605
|
const found = [];
|
|
11172
|
-
for (const e2 of
|
|
11606
|
+
for (const e2 of readdirSync6(projectDir, { withFileTypes: true })) {
|
|
11173
11607
|
if ([".claude", ".codex", ".opencode"].some((d2) => e2.name.startsWith(`${d2}.backup-`))) {
|
|
11174
11608
|
found.push(e2.name);
|
|
11175
11609
|
} else if (e2.isFile() && stampRe.test(e2.name)) {
|
|
11176
11610
|
found.push(e2.name);
|
|
11177
11611
|
}
|
|
11178
11612
|
}
|
|
11179
|
-
const agents =
|
|
11613
|
+
const agents = join33(projectDir, ".agents");
|
|
11180
11614
|
if (existsSync25(agents)) {
|
|
11181
11615
|
for (const rel of listFilesRecursive(agents)) {
|
|
11182
11616
|
if (stampRe.test(rel)) found.push(`.agents/${rel}`);
|
|
@@ -11192,11 +11626,11 @@ function backupArtifactAdvisoryLines(projectDir) {
|
|
|
11192
11626
|
}
|
|
11193
11627
|
function rootFileAdvisoryLines(rootFiles, projectDir, keepTemplates = false) {
|
|
11194
11628
|
const header = keepTemplates ? "[LEFT] uninstall \uD6C4 \uB0A8\uB294 \uAC83 \u2014 templates \uBCF4\uC874(.claude/ \uB4F1) + \uB8E8\uD2B8 \uD30C\uC77C (\uC790\uB3D9\uC73C\uB85C \uC9C0\uC6B0\uC9C0 \uC54A\uB294\uB2E4):" : "[ROOT] `.claude/` \uBC16\uC5D0 \uB0A8\uB294 \uAC83 (\uC790\uB3D9\uC73C\uB85C \uC9C0\uC6B0\uC9C0 \uC54A\uB294\uB2E4):";
|
|
11195
|
-
const present2 = rootFiles.filter((f) => existsSync25(
|
|
11629
|
+
const present2 = rootFiles.filter((f) => existsSync25(join33(projectDir, f.path)));
|
|
11196
11630
|
if (present2.length === 0) return [];
|
|
11197
11631
|
const outside = /* @__PURE__ */ new Map();
|
|
11198
11632
|
const inside = present2.filter((f) => {
|
|
11199
|
-
const o = outsideProjectTarget(projectDir,
|
|
11633
|
+
const o = outsideProjectTarget(projectDir, join33(projectDir, f.path));
|
|
11200
11634
|
if (o === null) return true;
|
|
11201
11635
|
const group = outside.get(o.link) ?? { link: o, files: [] };
|
|
11202
11636
|
group.files.push(f.path);
|
|
@@ -11235,7 +11669,7 @@ function rootFileMeaning(f, projectDir) {
|
|
|
11235
11669
|
case "displaced": {
|
|
11236
11670
|
const backup = f.notes[0];
|
|
11237
11671
|
if (backup === void 0) return "\uD558\uB124\uC2A4 \uD310\uACFC \uAC19\uC740 \uC124\uCE58\uC790 \uD30C\uC77C\uC774 \uC788\uB358 \uC790\uB9AC (\uADF8\uB300\uB85C \uB450\uC5C8\uB2E4)";
|
|
11238
|
-
return existsSync25(
|
|
11672
|
+
return existsSync25(join33(projectDir, backup)) ? `\uD558\uB124\uC2A4\uAC00 \uC4F4 \uC790\uB9AC \u2014 \uC6D0\uB798 \uC788\uB358 \uC124\uCE58\uC790 \uD30C\uC77C\uC740 ${backup} \uC5D0 \uC788\uB2E4` : `\uD558\uB124\uC2A4\uAC00 \uC4F4 \uC790\uB9AC \u2014 \uC6D0\uB798 \uC788\uB358 \uC124\uCE58\uC790 \uD30C\uC77C\uC758 \uBC31\uC5C5(${backup})\uC774 \uB354\uB294 \uC5C6\uB2E4`;
|
|
11239
11673
|
}
|
|
11240
11674
|
}
|
|
11241
11675
|
}
|
|
@@ -11289,6 +11723,7 @@ function planCliRemoval(target, remaining, installLog) {
|
|
|
11289
11723
|
(f) => underAny(f.path, recorded2)
|
|
11290
11724
|
);
|
|
11291
11725
|
return {
|
|
11726
|
+
target,
|
|
11292
11727
|
dirs: owned.filter((o) => o.kind === "dir").map((o) => o.path),
|
|
11293
11728
|
shared: CLI_SHARED.filter((p2) => recorded2.includes(p2)),
|
|
11294
11729
|
scoped: { ...installLog, externalFiles: scopedFiles },
|
|
@@ -11313,7 +11748,7 @@ function removeCliDryRunLines(plan, ctx, remaining) {
|
|
|
11313
11748
|
for (const dir of plan.dirs) {
|
|
11314
11749
|
const kept = outsideTemplateLink(projectDir, dir);
|
|
11315
11750
|
if (kept) lines2.push(keptOutsideLinkLine(kept, installLog));
|
|
11316
|
-
else if (existsSync25(
|
|
11751
|
+
else if (existsSync25(join33(projectDir, dir))) lines2.push(MOVE_ASIDE_PREVIEW(dir));
|
|
11317
11752
|
}
|
|
11318
11753
|
lines2.push(...previewExternalLines(plan.scoped, projectDir, harnessRoot));
|
|
11319
11754
|
lines2.push(
|
|
@@ -11323,7 +11758,7 @@ function removeCliDryRunLines(plan, ctx, remaining) {
|
|
|
11323
11758
|
)
|
|
11324
11759
|
);
|
|
11325
11760
|
const rootMd = plan.anchor ? installLog.templates.rootClaudeMd : void 0;
|
|
11326
|
-
if (rootMd && existsSync25(
|
|
11761
|
+
if (rootMd && existsSync25(join33(projectDir, rootMd.path))) {
|
|
11327
11762
|
lines2.push(
|
|
11328
11763
|
rootClaudeMdModified(installLog, projectDir) ? ` \u25CB keep ${rootMd.path} (modified since install \u2014 preserved)` : ` \u25CB remove ${rootMd.path}`
|
|
11329
11764
|
);
|
|
@@ -11331,20 +11766,73 @@ function removeCliDryRunLines(plan, ctx, remaining) {
|
|
|
11331
11766
|
if (plan.importBlock && hasRootImport(projectDir)) {
|
|
11332
11767
|
lines2.push(" \u25CB strip harness @import from CLAUDE.md (\uBCF8\uBB38 \uBCF4\uC874)");
|
|
11333
11768
|
}
|
|
11769
|
+
const rerender = agentsMdRerenderSet(plan.target, remaining, installLog, projectDir);
|
|
11770
|
+
if (rerender !== null) {
|
|
11771
|
+
lines2.push(` \u25CB would re-render AGENTS.md for ${displayNames(rerender)} (your sections kept)`);
|
|
11772
|
+
}
|
|
11334
11773
|
lines2.push(` \u25CB install log updated (clis: ${remaining.join(", ")})`, "");
|
|
11335
11774
|
return lines2;
|
|
11336
11775
|
}
|
|
11776
|
+
function displayNames(clis) {
|
|
11777
|
+
return clis.map((c3) => CLI_DISPLAY_NAMES[c3]).join(" \xB7 ");
|
|
11778
|
+
}
|
|
11779
|
+
function agentsMdRerenderSet(target, remaining, log, projectDir) {
|
|
11780
|
+
if (!isAgentsMdCli(target)) return null;
|
|
11781
|
+
const set = agentsMdClis(remaining);
|
|
11782
|
+
if (set.length === 0) return null;
|
|
11783
|
+
const recorded2 = (log.externalFiles ?? []).some((f) => f.path === AGENTS_MD2) || (log.portions ?? []).some((p2) => p2.path === AGENTS_MD2);
|
|
11784
|
+
if (!recorded2 || !existsSync25(join33(projectDir, AGENTS_MD2))) return null;
|
|
11785
|
+
return set;
|
|
11786
|
+
}
|
|
11787
|
+
function rerenderAgentsMd(ctx, set) {
|
|
11788
|
+
const { installLog, projectDir, harnessRoot } = ctx;
|
|
11789
|
+
return runAgentsMdTransform({
|
|
11790
|
+
harnessRoot,
|
|
11791
|
+
projectDir,
|
|
11792
|
+
clis: agentsMdClis(set),
|
|
11793
|
+
...recordedRenderInputs(projectDir, installLog),
|
|
11794
|
+
baseline: new Map((installLog.externalFiles ?? []).map((f) => [f.path, f.sha256])),
|
|
11795
|
+
refreshOnly: true,
|
|
11796
|
+
installedByRecord: true,
|
|
11797
|
+
shared: { portions: installLog.portions ?? [], excluded: [...excludedIds(installLog)] }
|
|
11798
|
+
});
|
|
11799
|
+
}
|
|
11800
|
+
function withRerender(next, r) {
|
|
11801
|
+
const out = { ...next };
|
|
11802
|
+
const files = mergeExternalFiles(next.externalFiles, r.ownership.files);
|
|
11803
|
+
if (files.length > 0) out.externalFiles = files;
|
|
11804
|
+
const block = r.agentsMd.shared;
|
|
11805
|
+
if (block !== null && block.portions !== null) {
|
|
11806
|
+
const portions = [
|
|
11807
|
+
...(next.portions ?? []).filter((p2) => p2.path !== AGENTS_MD2),
|
|
11808
|
+
...block.portions
|
|
11809
|
+
];
|
|
11810
|
+
if (portions.length > 0) out.portions = portions;
|
|
11811
|
+
else delete out.portions;
|
|
11812
|
+
}
|
|
11813
|
+
return out;
|
|
11814
|
+
}
|
|
11815
|
+
function rerenderLines(r) {
|
|
11816
|
+
const names = displayNames(r.clis);
|
|
11817
|
+
const lines2 = r.agentsMd.model === "block" ? [` ${status.success(`AGENTS.md \u2014 harness block re-rendered for ${names} (yours stays)`)}`] : [` ${status.success(`AGENTS.md \u2014 re-rendered for ${names} (your sections kept)`)}`];
|
|
11818
|
+
for (const b2 of r.ownership.backedUp) {
|
|
11819
|
+
lines2.push(
|
|
11820
|
+
` ${c.yellow("\u2298")} ${b2} \u2014 changed since the harness last wrote it; saved a copy as ${b2}.backup-<time> \xB7 your sections are kept`
|
|
11821
|
+
);
|
|
11822
|
+
}
|
|
11823
|
+
return lines2;
|
|
11824
|
+
}
|
|
11337
11825
|
function removeHarnessAnchor(ctx, io) {
|
|
11338
11826
|
const { installLog, projectDir } = ctx;
|
|
11339
11827
|
const rootMd = installLog.templates.rootClaudeMd;
|
|
11340
|
-
if (!rootMd || !existsSync25(
|
|
11828
|
+
if (!rootMd || !existsSync25(join33(projectDir, rootMd.path))) return false;
|
|
11341
11829
|
if (rootClaudeMdModified(installLog, projectDir)) {
|
|
11342
11830
|
io.log(
|
|
11343
11831
|
` ${c.yellow("\u2298")} ${rootMd.path} kept \u2014 modified since install. Remove manually if intended.`
|
|
11344
11832
|
);
|
|
11345
11833
|
return false;
|
|
11346
11834
|
}
|
|
11347
|
-
io.rm(
|
|
11835
|
+
io.rm(join33(projectDir, rootMd.path));
|
|
11348
11836
|
io.log(` ${status.success(`${rootMd.path} removed`)}`);
|
|
11349
11837
|
return true;
|
|
11350
11838
|
}
|
|
@@ -11402,7 +11890,7 @@ function removeCliAction(ctx, io) {
|
|
|
11402
11890
|
io.log(keptOutsideLinkLine(kept, installLog));
|
|
11403
11891
|
continue;
|
|
11404
11892
|
}
|
|
11405
|
-
if (!existsSync25(
|
|
11893
|
+
if (!existsSync25(join33(projectDir, dir))) continue;
|
|
11406
11894
|
const backup = io.moveAside(templateDirPath(projectDir, dir));
|
|
11407
11895
|
if (backup) io.log(movedAsideLine(projectDir, dir, backup));
|
|
11408
11896
|
}
|
|
@@ -11414,8 +11902,12 @@ function removeCliAction(ctx, io) {
|
|
|
11414
11902
|
io.log(` ${status.success("CLAUDE.md \u2014 harness @import removed (\uBCF8\uBB38 \uBCF4\uC874)")}`);
|
|
11415
11903
|
}
|
|
11416
11904
|
const anchorRemoved = plan.anchor ? removeHarnessAnchor(ctx, io) : false;
|
|
11905
|
+
const rerenderSet = agentsMdRerenderSet(target, remaining, installLog, projectDir);
|
|
11906
|
+
const rerender = rerenderSet === null ? null : rerenderAgentsMd(ctx, rerenderSet);
|
|
11907
|
+
if (rerender !== null) for (const line of rerenderLines(rerender)) io.log(line);
|
|
11417
11908
|
const recovered = /* @__PURE__ */ new Set([...external.removed, ...external.stripped]);
|
|
11418
|
-
const
|
|
11909
|
+
const settled = settleCliLog(ctx, target, remaining, recovered, anchorRemoved, plan.dirs, shared2);
|
|
11910
|
+
const next = rerender === null ? settled : withRerender(settled, rerender);
|
|
11419
11911
|
try {
|
|
11420
11912
|
io.writeLog(projectDir, next);
|
|
11421
11913
|
io.log(` ${status.success(`install log updated (clis: ${remaining.join(", ")})`)}`);
|
|
@@ -11439,7 +11931,7 @@ var SKILLS_LOCK = "skills-lock.json";
|
|
|
11439
11931
|
function withSkillsLock(rootFiles, assets, projectDir) {
|
|
11440
11932
|
const out = [...rootFiles];
|
|
11441
11933
|
const skills = assets.some((a) => a.method === "skill" && a.scope === "project");
|
|
11442
|
-
if (skills && !out.some((f) => f.path === SKILLS_LOCK) && existsSync25(
|
|
11934
|
+
if (skills && !out.some((f) => f.path === SKILLS_LOCK) && existsSync25(join33(projectDir, SKILLS_LOCK))) {
|
|
11443
11935
|
out.push({ path: SKILLS_LOCK, change: "advisory", notes: [] });
|
|
11444
11936
|
}
|
|
11445
11937
|
return out;
|
|
@@ -11463,7 +11955,7 @@ function remnantFor(path, log, harnessRoot) {
|
|
|
11463
11955
|
};
|
|
11464
11956
|
}
|
|
11465
11957
|
if (path === AGENTS_MD2) {
|
|
11466
|
-
if (!clis.
|
|
11958
|
+
if (!clis.some(isAgentsMdCli)) return () => [];
|
|
11467
11959
|
return (disk) => ADAPTERS["marker-md"].read(disk, [AGENTS_BLOCK_NAME])?.has(AGENTS_BLOCK_NAME) ? [AGENTS_BLOCK_NAME] : [];
|
|
11468
11960
|
}
|
|
11469
11961
|
if (!clis.includes("opencode")) return () => [];
|
|
@@ -11563,7 +12055,7 @@ function noReverseHints(asset, ctx) {
|
|
|
11563
12055
|
}
|
|
11564
12056
|
if (asset.method !== "skill") return [];
|
|
11565
12057
|
const name = asset.detail.skill;
|
|
11566
|
-
const left = name === void 0 ? [] : [".claude/skills", ".agents/skills"].map((root) => `${root}/${name}/`).filter((p2) => !underAny(p2, ctx.movedDirs) && existsSync25(
|
|
12058
|
+
const left = name === void 0 ? [] : [".claude/skills", ".agents/skills"].map((root) => `${root}/${name}/`).filter((p2) => !underAny(p2, ctx.movedDirs) && existsSync25(join33(ctx.projectDir, p2)));
|
|
11567
12059
|
return [
|
|
11568
12060
|
left.length > 0 ? `files it put down are not on record (installed by an older version) \u2014 left: ${left.join(" \xB7 ")} (delete by hand if the harness installed it)` : "files it put down are not on record (installed by an older version) \u2014 nothing removed"
|
|
11569
12061
|
];
|
|
@@ -11670,15 +12162,14 @@ function removeTemplates(log, projectDir, io, harnessRoot) {
|
|
|
11670
12162
|
if (rootMd) {
|
|
11671
12163
|
if (rootClaudeMdModified(log, projectDir))
|
|
11672
12164
|
return { rootClaudeMdKept: true, importStripped, external, moved, keptLinks };
|
|
11673
|
-
rm(
|
|
12165
|
+
rm(join33(projectDir, rootMd.path));
|
|
11674
12166
|
}
|
|
11675
12167
|
return { rootClaudeMdKept: false, importStripped, external, moved, keptLinks };
|
|
11676
12168
|
}
|
|
11677
12169
|
var AGENTS_MD2 = "AGENTS.md";
|
|
11678
|
-
function readAgentsMdTemplate(harnessRoot
|
|
11679
|
-
const flavor = log.templates.opencodeDir ? "opencode" : "codex";
|
|
12170
|
+
function readAgentsMdTemplate(harnessRoot) {
|
|
11680
12171
|
try {
|
|
11681
|
-
return
|
|
12172
|
+
return readFileSync26(join33(harnessRoot, AGENTS_MD_TEMPLATE), "utf8");
|
|
11682
12173
|
} catch {
|
|
11683
12174
|
return null;
|
|
11684
12175
|
}
|
|
@@ -11689,7 +12180,7 @@ function recoverMovedSymlinkPortions(log, projectDir, dirs) {
|
|
|
11689
12180
|
for (const path of paths) {
|
|
11690
12181
|
if (path.startsWith(".agents/skills/")) continue;
|
|
11691
12182
|
if (!dirs.some((d2) => path.startsWith(d2))) continue;
|
|
11692
|
-
const abs =
|
|
12183
|
+
const abs = join33(projectDir, path);
|
|
11693
12184
|
if (!safeIsSymlink(abs)) continue;
|
|
11694
12185
|
const target = safeRealpath(abs);
|
|
11695
12186
|
if (target !== null && isOutsideProject(projectDir, target)) {
|
|
@@ -11718,11 +12209,11 @@ function recoverMovedSymlinkTargets(log, projectDir, dirs, rm) {
|
|
|
11718
12209
|
for (const { path, sha256 } of log.externalFiles ?? []) {
|
|
11719
12210
|
if (path.startsWith(".agents/skills/")) continue;
|
|
11720
12211
|
if (!dirs.some((d2) => path.startsWith(d2))) continue;
|
|
11721
|
-
const abs =
|
|
12212
|
+
const abs = join33(projectDir, path);
|
|
11722
12213
|
if (!safeIsSymlink(abs)) continue;
|
|
11723
12214
|
let target;
|
|
11724
12215
|
try {
|
|
11725
|
-
target =
|
|
12216
|
+
target = realpathSync3(abs);
|
|
11726
12217
|
if (!statSync4(target).isFile()) continue;
|
|
11727
12218
|
} catch {
|
|
11728
12219
|
continue;
|
|
@@ -11732,7 +12223,7 @@ function recoverMovedSymlinkTargets(log, projectDir, dirs, rm) {
|
|
|
11732
12223
|
continue;
|
|
11733
12224
|
}
|
|
11734
12225
|
try {
|
|
11735
|
-
if (hashContent(
|
|
12226
|
+
if (hashContent(readFileSync26(target, "utf8")) !== sha256) {
|
|
11736
12227
|
kept.push(path);
|
|
11737
12228
|
continue;
|
|
11738
12229
|
}
|
|
@@ -11746,14 +12237,14 @@ function recoverMovedSymlinkTargets(log, projectDir, dirs, rm) {
|
|
|
11746
12237
|
}
|
|
11747
12238
|
function safeRealpath(abs) {
|
|
11748
12239
|
try {
|
|
11749
|
-
return
|
|
12240
|
+
return realpathSync3(abs);
|
|
11750
12241
|
} catch {
|
|
11751
12242
|
return null;
|
|
11752
12243
|
}
|
|
11753
12244
|
}
|
|
11754
12245
|
function safeIsSymlink(abs) {
|
|
11755
12246
|
try {
|
|
11756
|
-
return
|
|
12247
|
+
return lstatSync5(abs).isSymbolicLink();
|
|
11757
12248
|
} catch {
|
|
11758
12249
|
return false;
|
|
11759
12250
|
}
|
|
@@ -11761,10 +12252,17 @@ function safeIsSymlink(abs) {
|
|
|
11761
12252
|
function neutralizeScaffoldBanner(text) {
|
|
11762
12253
|
return text.split("\n").filter((line) => !line.includes("SCAFFOLD \u2014 not filled in yet")).join("\n");
|
|
11763
12254
|
}
|
|
11764
|
-
function agentsMdRemainder(current, harnessRoot
|
|
11765
|
-
const template = readAgentsMdTemplate(harnessRoot
|
|
12255
|
+
function agentsMdRemainder(current, harnessRoot) {
|
|
12256
|
+
const template = readAgentsMdTemplate(harnessRoot);
|
|
11766
12257
|
if (template === null) return { ok: false };
|
|
11767
|
-
return {
|
|
12258
|
+
return {
|
|
12259
|
+
ok: true,
|
|
12260
|
+
remainder: stripHarnessFromAgentsMd({
|
|
12261
|
+
existing: current,
|
|
12262
|
+
template,
|
|
12263
|
+
harnessRuleHeadings: ruleHeadings(harnessRoot)
|
|
12264
|
+
})
|
|
12265
|
+
};
|
|
11768
12266
|
}
|
|
11769
12267
|
function removeExternalFiles(log, projectDir, rm, harnessRoot) {
|
|
11770
12268
|
const removed = [];
|
|
@@ -11773,22 +12271,22 @@ function removeExternalFiles(log, projectDir, rm, harnessRoot) {
|
|
|
11773
12271
|
const unjudged = [];
|
|
11774
12272
|
const outside = [];
|
|
11775
12273
|
for (const { path, sha256 } of log.externalFiles ?? []) {
|
|
11776
|
-
const abs =
|
|
12274
|
+
const abs = join33(projectDir, path);
|
|
11777
12275
|
if (!existsSync25(abs)) continue;
|
|
11778
12276
|
let current;
|
|
11779
12277
|
let effective = abs;
|
|
11780
12278
|
const foreignSkillSlot = path.startsWith(".agents/skills/");
|
|
11781
12279
|
try {
|
|
11782
|
-
const isLink =
|
|
12280
|
+
const isLink = lstatSync5(abs).isSymbolicLink();
|
|
11783
12281
|
if (isLink && foreignSkillSlot) continue;
|
|
11784
|
-
const resolved =
|
|
12282
|
+
const resolved = realpathSync3(abs);
|
|
11785
12283
|
if (!statSync4(resolved).isFile()) continue;
|
|
11786
12284
|
if (isOutsideProject(projectDir, resolved)) {
|
|
11787
12285
|
outside.push({ path, target: resolved });
|
|
11788
12286
|
continue;
|
|
11789
12287
|
}
|
|
11790
12288
|
if (isLink) effective = resolved;
|
|
11791
|
-
current =
|
|
12289
|
+
current = readFileSync26(effective, "utf8");
|
|
11792
12290
|
} catch {
|
|
11793
12291
|
unjudged.push(path);
|
|
11794
12292
|
continue;
|
|
@@ -11798,7 +12296,7 @@ function removeExternalFiles(log, projectDir, rm, harnessRoot) {
|
|
|
11798
12296
|
continue;
|
|
11799
12297
|
}
|
|
11800
12298
|
if (path === AGENTS_MD2) {
|
|
11801
|
-
const verdict = agentsMdRemainder(current, harnessRoot
|
|
12299
|
+
const verdict = agentsMdRemainder(current, harnessRoot);
|
|
11802
12300
|
if (!verdict.ok) {
|
|
11803
12301
|
unjudged.push(path);
|
|
11804
12302
|
continue;
|
|
@@ -11822,11 +12320,11 @@ function removeExternalFiles(log, projectDir, rm, harnessRoot) {
|
|
|
11822
12320
|
}
|
|
11823
12321
|
removed.push(path);
|
|
11824
12322
|
}
|
|
11825
|
-
for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname6(
|
|
11826
|
-
const decisions =
|
|
12323
|
+
for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname6(join33(projectDir, path)));
|
|
12324
|
+
const decisions = join33(projectDir, "docs", "decisions");
|
|
11827
12325
|
try {
|
|
11828
|
-
if (existsSync25(decisions) &&
|
|
11829
|
-
|
|
12326
|
+
if (existsSync25(decisions) && readdirSync6(decisions).length === 0)
|
|
12327
|
+
rmSync5(decisions, { recursive: true });
|
|
11830
12328
|
} catch {
|
|
11831
12329
|
}
|
|
11832
12330
|
return { removed, kept, stripped, unjudged, outside };
|
|
@@ -11836,10 +12334,10 @@ function removeToolFiles(files, projectDir, rm) {
|
|
|
11836
12334
|
const removed = [];
|
|
11837
12335
|
let failed = 0;
|
|
11838
12336
|
for (const { path, sha256 } of files) {
|
|
11839
|
-
const abs =
|
|
12337
|
+
const abs = join33(projectDir, path);
|
|
11840
12338
|
let isLink;
|
|
11841
12339
|
try {
|
|
11842
|
-
isLink =
|
|
12340
|
+
isLink = lstatSync5(abs).isSymbolicLink();
|
|
11843
12341
|
} catch {
|
|
11844
12342
|
continue;
|
|
11845
12343
|
}
|
|
@@ -11853,7 +12351,7 @@ function removeToolFiles(files, projectDir, rm) {
|
|
|
11853
12351
|
continue;
|
|
11854
12352
|
}
|
|
11855
12353
|
try {
|
|
11856
|
-
if (hashContent(
|
|
12354
|
+
if (hashContent(readFileSync26(abs, "utf8")) !== sha256) {
|
|
11857
12355
|
const backup = backupFile(abs);
|
|
11858
12356
|
notes.push(`backed up ${path} \u2192 ${relative5(projectDir, backup)} (changed since install)`);
|
|
11859
12357
|
}
|
|
@@ -11864,7 +12362,7 @@ function removeToolFiles(files, projectDir, rm) {
|
|
|
11864
12362
|
notes.push(`left ${path} \u2014 ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
11865
12363
|
}
|
|
11866
12364
|
}
|
|
11867
|
-
for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname6(
|
|
12365
|
+
for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname6(join33(projectDir, path)));
|
|
11868
12366
|
return failed > 0 ? { ok: false, message: `${failed} file(s) could not be removed`, notes } : { ok: true, notes };
|
|
11869
12367
|
}
|
|
11870
12368
|
function pruneEmptyDirsUpward(projectDir, startDir) {
|
|
@@ -11873,7 +12371,7 @@ function pruneEmptyDirsUpward(projectDir, startDir) {
|
|
|
11873
12371
|
const rel = relative5(projectDir, cursor);
|
|
11874
12372
|
if (rel === "" || rel.startsWith(`..${sep5}`) || rel === "..") return;
|
|
11875
12373
|
try {
|
|
11876
|
-
if (
|
|
12374
|
+
if (readdirSync6(cursor).length > 0) return;
|
|
11877
12375
|
rmdirSync2(cursor);
|
|
11878
12376
|
} catch {
|
|
11879
12377
|
return;
|
|
@@ -11886,8 +12384,8 @@ function stripRootImport(projectDir) {
|
|
|
11886
12384
|
const stripped = current === null ? null : stripHarnessImport(current);
|
|
11887
12385
|
if (stripped === null) return false;
|
|
11888
12386
|
try {
|
|
11889
|
-
backupIfLossyUtf8(
|
|
11890
|
-
writeFileSync9(
|
|
12387
|
+
backupIfLossyUtf8(join33(projectDir, "CLAUDE.md"));
|
|
12388
|
+
writeFileSync9(join33(projectDir, "CLAUDE.md"), stripped, "utf8");
|
|
11891
12389
|
return true;
|
|
11892
12390
|
} catch {
|
|
11893
12391
|
return false;
|
|
@@ -11897,7 +12395,7 @@ function previewExternalLines(installLog, projectDir, harnessRoot, movedDirs = [
|
|
|
11897
12395
|
const lines2 = [];
|
|
11898
12396
|
let removable = 0;
|
|
11899
12397
|
for (const { path, sha256 } of installLog.externalFiles ?? []) {
|
|
11900
|
-
const abs =
|
|
12398
|
+
const abs = join33(projectDir, path);
|
|
11901
12399
|
const underMoved = underAny(path, movedDirs);
|
|
11902
12400
|
const isLink = !underMoved || existsSync25(abs) ? safeIsSymlink(abs) : false;
|
|
11903
12401
|
if (underMoved && !isLink) continue;
|
|
@@ -11905,9 +12403,9 @@ function previewExternalLines(installLog, projectDir, harnessRoot, movedDirs = [
|
|
|
11905
12403
|
let effective = abs;
|
|
11906
12404
|
const foreignSkillSlot = path.startsWith(".agents/skills/");
|
|
11907
12405
|
try {
|
|
11908
|
-
const isLinkPath =
|
|
12406
|
+
const isLinkPath = lstatSync5(abs).isSymbolicLink();
|
|
11909
12407
|
if (isLinkPath && foreignSkillSlot) continue;
|
|
11910
|
-
const resolved =
|
|
12408
|
+
const resolved = realpathSync3(abs);
|
|
11911
12409
|
if (!statSync4(resolved).isFile()) continue;
|
|
11912
12410
|
if (isOutsideProject(projectDir, resolved)) {
|
|
11913
12411
|
lines2.push(` \u25CB keep ${path} (link target outside project: ${resolved} \u2014 preserved)`);
|
|
@@ -11917,13 +12415,13 @@ function previewExternalLines(installLog, projectDir, harnessRoot, movedDirs = [
|
|
|
11917
12415
|
} catch {
|
|
11918
12416
|
continue;
|
|
11919
12417
|
}
|
|
11920
|
-
const current =
|
|
12418
|
+
const current = readFileSync26(effective, "utf8");
|
|
11921
12419
|
if (hashContent(current) !== sha256) {
|
|
11922
12420
|
lines2.push(` \u25CB keep ${path} (modified since install \u2014 preserved)`);
|
|
11923
12421
|
continue;
|
|
11924
12422
|
}
|
|
11925
12423
|
if (path === AGENTS_MD2) {
|
|
11926
|
-
const verdict = agentsMdRemainder(current, harnessRoot
|
|
12424
|
+
const verdict = agentsMdRemainder(current, harnessRoot);
|
|
11927
12425
|
if (!verdict.ok) {
|
|
11928
12426
|
lines2.push(` \u25CB keep ${path} (harness sections could not be separated \u2014 preserved)`);
|
|
11929
12427
|
continue;
|
|
@@ -11981,10 +12479,10 @@ function hasRootImport(projectDir) {
|
|
|
11981
12479
|
return current !== null && stripHarnessImport(current) !== null;
|
|
11982
12480
|
}
|
|
11983
12481
|
function readRootClaudeMd(projectDir) {
|
|
11984
|
-
const path =
|
|
12482
|
+
const path = join33(projectDir, "CLAUDE.md");
|
|
11985
12483
|
if (!existsSync25(path)) return null;
|
|
11986
12484
|
try {
|
|
11987
|
-
return
|
|
12485
|
+
return readFileSync26(path, "utf8");
|
|
11988
12486
|
} catch {
|
|
11989
12487
|
return null;
|
|
11990
12488
|
}
|
|
@@ -11992,16 +12490,16 @@ function readRootClaudeMd(projectDir) {
|
|
|
11992
12490
|
function rootClaudeMdModified(log, projectDir) {
|
|
11993
12491
|
const rootMd = log.templates.rootClaudeMd;
|
|
11994
12492
|
if (!rootMd) return false;
|
|
11995
|
-
const path =
|
|
12493
|
+
const path = join33(projectDir, rootMd.path);
|
|
11996
12494
|
if (!existsSync25(path)) return false;
|
|
11997
|
-
return hashContent(
|
|
12495
|
+
return hashContent(readFileSync26(path, "utf8")) !== rootMd.sha256;
|
|
11998
12496
|
}
|
|
11999
12497
|
function defaultSpawn2(cmd, args) {
|
|
12000
12498
|
return spawnSync2(cmd, [...args], { encoding: "utf8", stdio: "pipe", timeout: 12e4 });
|
|
12001
12499
|
}
|
|
12002
12500
|
function defaultRm(path) {
|
|
12003
12501
|
if (existsSync25(path)) {
|
|
12004
|
-
|
|
12502
|
+
rmSync5(path, { recursive: true, force: true });
|
|
12005
12503
|
}
|
|
12006
12504
|
}
|
|
12007
12505
|
function registerUninstallCommand(cli) {
|