@uzysjung/agent-harness 26.159.0 → 26.160.1
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/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
CATEGORY_TITLES,
|
|
6
6
|
CLI_BASES,
|
|
7
7
|
CONTINUOUS_SKILLS,
|
|
8
|
+
CONTINUOUS_SKILLS_HEADING,
|
|
8
9
|
DEFAULT_OPTIONS,
|
|
9
10
|
EXTERNAL_ASSETS,
|
|
10
11
|
HARNESS_ANCHOR_FILE,
|
|
@@ -57,6 +58,7 @@ import {
|
|
|
57
58
|
listFilesRecursive,
|
|
58
59
|
mergeExternalFiles,
|
|
59
60
|
readInstallLog,
|
|
61
|
+
renderContinuousSkillsNote,
|
|
60
62
|
renderFillScaffold,
|
|
61
63
|
residentCost,
|
|
62
64
|
resolveBundleRoot,
|
|
@@ -67,7 +69,7 @@ import {
|
|
|
67
69
|
upsertHarnessImport,
|
|
68
70
|
withContinuousSkillsNote,
|
|
69
71
|
writeInstallLog
|
|
70
|
-
} from "./chunk-
|
|
72
|
+
} from "./chunk-ZXWOY5VO.js";
|
|
71
73
|
|
|
72
74
|
// node_modules/sisteransi/src/index.js
|
|
73
75
|
var require_src = __commonJS({
|
|
@@ -752,7 +754,7 @@ var cac = (name = "") => new CAC(name);
|
|
|
752
754
|
// package.json
|
|
753
755
|
var package_default = {
|
|
754
756
|
name: "@uzysjung/agent-harness",
|
|
755
|
-
version: "26.
|
|
757
|
+
version: "26.160.1",
|
|
756
758
|
description: "Curate vetted AI-coding skills & plugins by your tech stack \u2014 install only what you need, across Claude Code, Codex, OpenCode & Antigravity",
|
|
757
759
|
type: "module",
|
|
758
760
|
publishConfig: {
|
|
@@ -1084,11 +1086,170 @@ import { basename, join as join6 } from "path";
|
|
|
1084
1086
|
|
|
1085
1087
|
// src/codex/agents-md.ts
|
|
1086
1088
|
init_esm_shims();
|
|
1089
|
+
|
|
1090
|
+
// src/agents-md-merge.ts
|
|
1091
|
+
init_esm_shims();
|
|
1092
|
+
var marker = (name) => ({
|
|
1093
|
+
start: `<!-- uzys-harness:${name}:start -->`,
|
|
1094
|
+
end: `<!-- uzys-harness:${name}:end -->`
|
|
1095
|
+
});
|
|
1096
|
+
var ANCHOR_BLOCK = marker("anchor");
|
|
1097
|
+
var SKILLS_BLOCK = marker("skills");
|
|
1098
|
+
var CONTEXT_SECTION = "Project Context";
|
|
1099
|
+
var RULES_SECTION = "Project Rules";
|
|
1100
|
+
function wrapHarnessBlock(m, body) {
|
|
1101
|
+
return `${m.start}
|
|
1102
|
+
${body}
|
|
1103
|
+
${m.end}`;
|
|
1104
|
+
}
|
|
1105
|
+
function withMarkedContinuousSkillsNote(scaffold, selectedInternalSkills) {
|
|
1106
|
+
const note = renderContinuousSkillsNote(selectedInternalSkills);
|
|
1107
|
+
return note ? `${scaffold}
|
|
1108
|
+
|
|
1109
|
+
${wrapHarnessBlock(SKILLS_BLOCK, note)}` : scaffold;
|
|
1110
|
+
}
|
|
1111
|
+
function sectionNames(template) {
|
|
1112
|
+
return new Set([...template.matchAll(/^## (.+)$/gm)].map((m) => (m[1] ?? "").trim()));
|
|
1113
|
+
}
|
|
1114
|
+
function headingName(line, names) {
|
|
1115
|
+
const name = /^## (.+?)\s*$/.exec(line.replace(/\r$/, ""))?.[1];
|
|
1116
|
+
return name !== void 0 && names.has(name) ? name : null;
|
|
1117
|
+
}
|
|
1118
|
+
function sectionBody(text, names, want) {
|
|
1119
|
+
const lines = text.split("\n");
|
|
1120
|
+
let start = -1;
|
|
1121
|
+
for (const [i, line] of lines.entries()) {
|
|
1122
|
+
const name = headingName(line, names);
|
|
1123
|
+
if (name === null) continue;
|
|
1124
|
+
if (start === -1) {
|
|
1125
|
+
if (name === want) start = i;
|
|
1126
|
+
} else {
|
|
1127
|
+
return lines.slice(start + 1, i);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
return start === -1 ? null : lines.slice(start + 1);
|
|
1131
|
+
}
|
|
1132
|
+
function assemble(rendered, names, bodies) {
|
|
1133
|
+
const out = [];
|
|
1134
|
+
let replacing = false;
|
|
1135
|
+
for (const line of rendered.split("\n")) {
|
|
1136
|
+
const name = headingName(line, names);
|
|
1137
|
+
if (name !== null) {
|
|
1138
|
+
out.push(line);
|
|
1139
|
+
const override = bodies.get(name);
|
|
1140
|
+
replacing = override !== void 0;
|
|
1141
|
+
if (override !== void 0) out.push(...override);
|
|
1142
|
+
continue;
|
|
1143
|
+
}
|
|
1144
|
+
if (!replacing) out.push(line);
|
|
1145
|
+
}
|
|
1146
|
+
return out.join("\n");
|
|
1147
|
+
}
|
|
1148
|
+
function blockRange(lines, m) {
|
|
1149
|
+
const start = lines.findIndex((l) => l.trim() === m.start);
|
|
1150
|
+
if (start === -1) return null;
|
|
1151
|
+
const end = lines.findIndex((l, i) => i > start && l.trim() === m.end);
|
|
1152
|
+
return end === -1 ? null : [start, end];
|
|
1153
|
+
}
|
|
1154
|
+
function trimTrailingBlanks(lines) {
|
|
1155
|
+
const out = [...lines];
|
|
1156
|
+
while (out.length > 0 && (out.at(-1) ?? "").trim() === "") out.pop();
|
|
1157
|
+
return out;
|
|
1158
|
+
}
|
|
1159
|
+
function refreshBlock(existing, rendered, m, legacyHeading) {
|
|
1160
|
+
const at2 = blockRange(rendered, m);
|
|
1161
|
+
const fresh = at2 === null ? null : rendered.slice(at2[0], at2[1] + 1);
|
|
1162
|
+
const here = blockRange(existing, m);
|
|
1163
|
+
if (here !== null) {
|
|
1164
|
+
const head2 = existing.slice(0, here[0]);
|
|
1165
|
+
const tail2 = existing.slice(here[1] + 1);
|
|
1166
|
+
if (fresh === null) {
|
|
1167
|
+
const kept = trimTrailingBlanks(head2);
|
|
1168
|
+
return tail2.length > 0 ? [...kept, ...tail2] : [...kept, ""];
|
|
1169
|
+
}
|
|
1170
|
+
return [...head2, ...fresh, ...tail2];
|
|
1171
|
+
}
|
|
1172
|
+
const legacyAt = legacyHeading === void 0 ? -1 : existing.findIndex((l) => l.replace(/\r$/, "").trim() === legacyHeading);
|
|
1173
|
+
if (legacyAt === -1) {
|
|
1174
|
+
const kept = trimTrailingBlanks(existing);
|
|
1175
|
+
return fresh === null ? [...kept, ""] : [...kept, "", ...fresh, ""];
|
|
1176
|
+
}
|
|
1177
|
+
const head = trimTrailingBlanks(existing.slice(0, legacyAt));
|
|
1178
|
+
const tail = existing.slice(legacyNoteEnd(existing, legacyAt));
|
|
1179
|
+
while (tail.length > 0 && (tail[0] ?? "").trim() === "") tail.shift();
|
|
1180
|
+
const body = fresh === null ? [...head] : [...head, "", ...fresh];
|
|
1181
|
+
return tail.length > 0 ? [...body, "", ...tail] : [...body, ""];
|
|
1182
|
+
}
|
|
1183
|
+
function legacyNoteEnd(lines, headingAt) {
|
|
1184
|
+
let i = headingAt + 1;
|
|
1185
|
+
while (i < lines.length) {
|
|
1186
|
+
const l = (lines[i] ?? "").replace(/\r$/, "").trim();
|
|
1187
|
+
const isNote = l === "" || l.startsWith("- `") || l.startsWith("These installed skills apply");
|
|
1188
|
+
if (!isNote) break;
|
|
1189
|
+
i++;
|
|
1190
|
+
}
|
|
1191
|
+
return i;
|
|
1192
|
+
}
|
|
1193
|
+
function mergeAgentsMd(params) {
|
|
1194
|
+
const { rendered, existing, template } = params;
|
|
1195
|
+
if (existing === null) return rendered;
|
|
1196
|
+
const names = sectionNames(template);
|
|
1197
|
+
const bodies = /* @__PURE__ */ new Map();
|
|
1198
|
+
const contextNow = sectionBody(existing, names, CONTEXT_SECTION);
|
|
1199
|
+
const contextNew = sectionBody(rendered, names, CONTEXT_SECTION);
|
|
1200
|
+
if (contextNow !== null && contextNew !== null) {
|
|
1201
|
+
bodies.set(
|
|
1202
|
+
CONTEXT_SECTION,
|
|
1203
|
+
refreshBlock(contextNow, contextNew, SKILLS_BLOCK, CONTINUOUS_SKILLS_HEADING)
|
|
1204
|
+
);
|
|
1205
|
+
}
|
|
1206
|
+
const rulesNow = sectionBody(existing, names, RULES_SECTION);
|
|
1207
|
+
const rulesNew = sectionBody(rendered, names, RULES_SECTION);
|
|
1208
|
+
if (rulesNow !== null && rulesNew !== null && blockRange(rulesNow, ANCHOR_BLOCK) !== null) {
|
|
1209
|
+
bodies.set(RULES_SECTION, refreshBlock(rulesNow, rulesNew, ANCHOR_BLOCK));
|
|
1210
|
+
}
|
|
1211
|
+
return bodies.size === 0 ? rendered : assemble(rendered, names, bodies);
|
|
1212
|
+
}
|
|
1213
|
+
function installerLines(body, m) {
|
|
1214
|
+
if (body === null) return [];
|
|
1215
|
+
const at2 = blockRange(body, m);
|
|
1216
|
+
return at2 === null ? [...body] : [...body.slice(0, at2[0]), ...body.slice(at2[1] + 1)];
|
|
1217
|
+
}
|
|
1218
|
+
function compact(lines) {
|
|
1219
|
+
return lines.map((l) => l.replace(/\r$/, "").trimEnd()).filter((l) => l.trim() !== "");
|
|
1220
|
+
}
|
|
1221
|
+
function trimBlankEdges(lines) {
|
|
1222
|
+
const out = trimTrailingBlanks(lines);
|
|
1223
|
+
while (out.length > 0 && (out[0] ?? "").trim() === "") out.shift();
|
|
1224
|
+
return out;
|
|
1225
|
+
}
|
|
1226
|
+
function stripHarnessFromAgentsMd(params) {
|
|
1227
|
+
const { existing, template } = params;
|
|
1228
|
+
const lines = existing.split("\n");
|
|
1229
|
+
if (blockRange(lines, ANCHOR_BLOCK) === null && blockRange(lines, SKILLS_BLOCK) === null) {
|
|
1230
|
+
return null;
|
|
1231
|
+
}
|
|
1232
|
+
const names = sectionNames(template);
|
|
1233
|
+
const context = installerLines(sectionBody(existing, names, CONTEXT_SECTION), SKILLS_BLOCK);
|
|
1234
|
+
const rules = installerLines(sectionBody(existing, names, RULES_SECTION), ANCHOR_BLOCK);
|
|
1235
|
+
const scaffold = compact(renderFillScaffold("agents-md").split("\n"));
|
|
1236
|
+
const contextFilled = compact(context).join("\n") !== scaffold.join("\n");
|
|
1237
|
+
const rulesFilled = compact(rules).length > 0;
|
|
1238
|
+
if (!contextFilled && !rulesFilled) return null;
|
|
1239
|
+
const out = [];
|
|
1240
|
+
const title = (lines[0] ?? "").replace(/\r$/, "");
|
|
1241
|
+
if (/^# /.test(title)) out.push(title, "");
|
|
1242
|
+
out.push(`## ${CONTEXT_SECTION}`, "", ...trimBlankEdges(context), "");
|
|
1243
|
+
if (rulesFilled) out.push(`## ${RULES_SECTION}`, "", ...trimBlankEdges(rules), "");
|
|
1244
|
+
return out.join("\n");
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
// src/codex/agents-md.ts
|
|
1087
1248
|
function renameSlashes(text) {
|
|
1088
1249
|
return text.replaceAll("/uzys:", "/uzys-");
|
|
1089
1250
|
}
|
|
1090
1251
|
function renderAgentsMd(params) {
|
|
1091
|
-
const body = params.claudeMd.replace(/^#\s+.*\r?\n/, "").trim();
|
|
1252
|
+
const body = wrapHarnessBlock(ANCHOR_BLOCK, params.claudeMd.replace(/^#\s+.*\r?\n/, "").trim());
|
|
1092
1253
|
const replaced = params.template.replaceAll("{PROJECT_NAME}", params.projectName).replaceAll("{PROJECT_RULES}", body).replaceAll("{PROJECT_CONTEXT}", params.projectContext).replaceAll("{HARNESS_RULES}", params.harnessRules ?? "");
|
|
1093
1254
|
return renameSlashes(replaced);
|
|
1094
1255
|
}
|
|
@@ -1312,7 +1473,8 @@ function writeRules(harnessRoot, projectDir, writer, selectedInternalSkills) {
|
|
|
1312
1473
|
selectedInternalSkills
|
|
1313
1474
|
)
|
|
1314
1475
|
});
|
|
1315
|
-
|
|
1476
|
+
const withoutMarkers = rulesOut.split("\n").filter((l) => !/^<!-- uzys-harness:(?:anchor|skills):(?:start|end) -->$/.test(l.trim())).join("\n");
|
|
1477
|
+
return writer.write(target, withoutMarkers) ? target : null;
|
|
1316
1478
|
}
|
|
1317
1479
|
|
|
1318
1480
|
// src/codex/opt-in.ts
|
|
@@ -1457,14 +1619,22 @@ function runCodexTransform(params) {
|
|
|
1457
1619
|
claudeMd,
|
|
1458
1620
|
projectName,
|
|
1459
1621
|
// ADR-085 — 상시 스킬 안내는 앵커가 아니라 여기(프로젝트 맥락)에, 깔린 것만.
|
|
1460
|
-
|
|
1622
|
+
// #503 — 그 조각은 설치자 소유 절 안에 사니 마커로 감싼다.
|
|
1623
|
+
projectContext: withMarkedContinuousSkillsNote(
|
|
1461
1624
|
renderFillScaffold("agents-md"),
|
|
1462
1625
|
selectedInternalSkills
|
|
1463
1626
|
),
|
|
1464
1627
|
// Codex 는 룰 디렉터리가 없다 — 룰이 AGENTS.md 본문에 들어가야 도달한다(§Harness Rules).
|
|
1465
1628
|
harnessRules: renderRulesBlock(portRules(harnessRoot, rules))
|
|
1466
1629
|
});
|
|
1467
|
-
writer.write(
|
|
1630
|
+
writer.write(
|
|
1631
|
+
agentsMdPath,
|
|
1632
|
+
mergeAgentsMd({
|
|
1633
|
+
rendered: agentsMdOut,
|
|
1634
|
+
existing: existsSync8(agentsMdPath) ? readFileSync7(agentsMdPath, "utf8") : null,
|
|
1635
|
+
template: agentsTemplate
|
|
1636
|
+
})
|
|
1637
|
+
);
|
|
1468
1638
|
const configTomlPath = join8(projectDir, ".codex/config.toml");
|
|
1469
1639
|
writer.write(
|
|
1470
1640
|
configTomlPath,
|
|
@@ -1530,7 +1700,7 @@ function renameSlashes2(text) {
|
|
|
1530
1700
|
return text.replaceAll("/uzys:", "/uzys-");
|
|
1531
1701
|
}
|
|
1532
1702
|
function renderAgentsMd2(params) {
|
|
1533
|
-
const body = params.claudeMd.replace(/^#\s+.*\r?\n/, "").trim();
|
|
1703
|
+
const body = wrapHarnessBlock(ANCHOR_BLOCK, params.claudeMd.replace(/^#\s+.*\r?\n/, "").trim());
|
|
1534
1704
|
const replaced = params.template.replaceAll("{PROJECT_NAME}", params.projectName).replaceAll("{PROJECT_RULES}", body).replaceAll("{PROJECT_CONTEXT}", params.projectContext).replaceAll("{HARNESS_RULES}", params.harnessRules ?? "");
|
|
1535
1705
|
return renameSlashes2(replaced);
|
|
1536
1706
|
}
|
|
@@ -1579,7 +1749,8 @@ function runOpencodeTransform(params) {
|
|
|
1579
1749
|
claudeMd,
|
|
1580
1750
|
projectName,
|
|
1581
1751
|
// ADR-085 — 상시 스킬 안내는 앵커가 아니라 여기(프로젝트 맥락)에, 깔린 것만.
|
|
1582
|
-
|
|
1752
|
+
// #503 — 그 조각은 설치자 소유 절 안에 사니 마커로 감싼다 (codex 와 같은 마커).
|
|
1753
|
+
projectContext: withMarkedContinuousSkillsNote(
|
|
1583
1754
|
renderFillScaffold("agents-md"),
|
|
1584
1755
|
selectedInternalSkills
|
|
1585
1756
|
),
|
|
@@ -1588,7 +1759,14 @@ function runOpencodeTransform(params) {
|
|
|
1588
1759
|
// (독립 검증 C-1 실측). 같은 내용을 쓰면 순서가 결과를 바꾸지 않는다.
|
|
1589
1760
|
harnessRules: renderRulesBlock(portRules(harnessRoot, rules))
|
|
1590
1761
|
});
|
|
1591
|
-
writer.write(
|
|
1762
|
+
writer.write(
|
|
1763
|
+
agentsMdPath,
|
|
1764
|
+
mergeAgentsMd({
|
|
1765
|
+
rendered: agentsMdOut,
|
|
1766
|
+
existing: existsSync9(agentsMdPath) ? readFileSync8(agentsMdPath, "utf8") : null,
|
|
1767
|
+
template: agentsTemplate
|
|
1768
|
+
})
|
|
1769
|
+
);
|
|
1592
1770
|
const opencodeJsonPath = join9(projectDir, "opencode.json");
|
|
1593
1771
|
writer.write(opencodeJsonPath, renderOpencodeJson({ template: opencodeTemplate, mcp }));
|
|
1594
1772
|
const skillFiles = writeBundledSkillDirs({
|
|
@@ -2366,11 +2544,13 @@ function installNewSkillDirs(projectDir, templatesDir, tracks) {
|
|
|
2366
2544
|
const foreignOwned = [];
|
|
2367
2545
|
const log = readInstallLog(projectDir);
|
|
2368
2546
|
const excluded = new Set(log?.spec.baselineExclude ?? []);
|
|
2547
|
+
const skillExcluded = new Set(log?.spec.skillExclude ?? []);
|
|
2369
2548
|
if (!(log?.spec.cli ?? ["claude"]).includes("claude")) return { installed, foreignOwned };
|
|
2370
2549
|
const spec = buildAssetSpec({ tracks, options: DEFAULT_OPTIONS });
|
|
2371
2550
|
for (const entry of buildManifest(spec)) {
|
|
2372
2551
|
if (entry.type !== "dir" || !entry.target.startsWith(".claude/skills/")) continue;
|
|
2373
2552
|
if (!entry.applies(spec) || isBaselineExcluded(entry.target, excluded)) continue;
|
|
2553
|
+
if (skillExcluded.has(entry.target.slice(".claude/skills/".length))) continue;
|
|
2374
2554
|
const target = join12(projectDir, entry.target);
|
|
2375
2555
|
const foreign = foreignOwnedTarget(projectDir, entry.target);
|
|
2376
2556
|
if (foreign !== null) {
|
|
@@ -2473,15 +2653,31 @@ function recordAnchorBaseline(projectDir, anchor) {
|
|
|
2473
2653
|
} catch {
|
|
2474
2654
|
}
|
|
2475
2655
|
}
|
|
2656
|
+
function installedBundledSkills(projectDir) {
|
|
2657
|
+
return INTERNAL_BUNDLED_SKILL_IDS.filter(
|
|
2658
|
+
(id) => existsSync13(join12(projectDir, ".claude/skills", id)) || existsSync13(join12(projectDir, ".agents/skills", id))
|
|
2659
|
+
);
|
|
2660
|
+
}
|
|
2661
|
+
function installedCliTargets(log) {
|
|
2662
|
+
if (log === null) return ALL_CLI_TARGETS;
|
|
2663
|
+
return ALL_CLI_TARGETS.filter((cli2) => {
|
|
2664
|
+
if (cli2 === "codex") return log.templates.codexDir !== void 0;
|
|
2665
|
+
if (cli2 === "opencode") return log.templates.opencodeDir !== void 0;
|
|
2666
|
+
return true;
|
|
2667
|
+
});
|
|
2668
|
+
}
|
|
2476
2669
|
function refreshExternalCli(projectDir, harnessRoot) {
|
|
2477
2670
|
const log = readInstallLog(projectDir);
|
|
2478
2671
|
const baselineExcluded = new Set(log?.spec.baselineExclude ?? []);
|
|
2479
2672
|
const result = runCliTransforms({
|
|
2480
2673
|
harnessRoot,
|
|
2481
2674
|
projectDir,
|
|
2482
|
-
cli:
|
|
2483
|
-
|
|
2484
|
-
//
|
|
2675
|
+
cli: installedCliTargets(log),
|
|
2676
|
+
// 스킬은 **디스크에 있는 것만** 넘긴다. 스킬 *파일*은 전체 목록을 넘겨도 refreshOnly 가
|
|
2677
|
+
// 없는 것을 건너뛰지만, `AGENTS.md` 의 상시 스킬 안내(ADR-085)는 이 목록 그대로 렌더돼
|
|
2678
|
+
// `--without` 으로 뺀 스킬(#505)·opt-in 스킬을 "열어라"고 적었다(실측 2026-09-21). 판정은
|
|
2679
|
+
// `upsertRootImport` 와 같다 — `.claude/skills/<id>` 또는 `.agents/skills/<id>` 가 있으면 깔린 것.
|
|
2680
|
+
selectedInternalSkills: installedBundledSkills(projectDir),
|
|
2485
2681
|
rules: ALL_RULES.filter((r) => !isBaselineExcluded(`.claude/rules/${r}.md`, baselineExcluded)),
|
|
2486
2682
|
previousExternal: log?.externalFiles ?? [],
|
|
2487
2683
|
refreshOnly: true
|
|
@@ -4107,10 +4303,10 @@ function formatAssetRows(assets) {
|
|
|
4107
4303
|
const idWidth = Math.max(0, ...assets.map((a) => a.id.length));
|
|
4108
4304
|
const methodWidth = Math.max(0, ...assets.map((a) => a.method.length));
|
|
4109
4305
|
return assets.map((a) => {
|
|
4110
|
-
const
|
|
4306
|
+
const marker2 = a.scope === "global" ? c.yellow("!") : c.green("\u2713");
|
|
4111
4307
|
const version = a.version ? c.dim(` v${a.version}`) : "";
|
|
4112
4308
|
const note = a.scope === "global" ? c.yellow(" (manual removal \u2014 D16)") : "";
|
|
4113
|
-
return ` ${
|
|
4309
|
+
return ` ${marker2} ${padDisplay(a.id, idWidth)} ${c.dim(padDisplay(a.method, methodWidth))} ${c.dim(a.scope)}${version}${note}`;
|
|
4114
4310
|
});
|
|
4115
4311
|
}
|
|
4116
4312
|
function formatTemplateRows(log, projectDir) {
|
|
@@ -5255,6 +5451,7 @@ function uninstallAction(options, deps = {}) {
|
|
|
5255
5451
|
const spawn = deps.spawn ?? defaultSpawn2;
|
|
5256
5452
|
const rm = deps.rm ?? defaultRm;
|
|
5257
5453
|
const writeLog = deps.writeLog ?? writeInstallLog;
|
|
5454
|
+
const harnessRoot = (deps.resolveHarnessRoot ?? defaultHarnessRoot)();
|
|
5258
5455
|
const projectDir = resolve4(options.projectDir ?? process.cwd());
|
|
5259
5456
|
const installLog = readInstallLog(projectDir);
|
|
5260
5457
|
if (!installLog) {
|
|
@@ -5283,7 +5480,14 @@ function uninstallAction(options, deps = {}) {
|
|
|
5283
5480
|
const plan = planReverse(targetAssets, spawn);
|
|
5284
5481
|
for (const line of headerLines(installLog, selectedIds, targetAssets.length)) log(line);
|
|
5285
5482
|
if (options.dryRun) {
|
|
5286
|
-
for (const line of dryRunLines(
|
|
5483
|
+
for (const line of dryRunLines(
|
|
5484
|
+
plan,
|
|
5485
|
+
installLog,
|
|
5486
|
+
projectDir,
|
|
5487
|
+
keepTemplates,
|
|
5488
|
+
rootFiles,
|
|
5489
|
+
harnessRoot
|
|
5490
|
+
)) {
|
|
5287
5491
|
log(line);
|
|
5288
5492
|
}
|
|
5289
5493
|
exit(0);
|
|
@@ -5295,7 +5499,8 @@ function uninstallAction(options, deps = {}) {
|
|
|
5295
5499
|
const { rootClaudeMdKept, importStripped, external } = removeTemplates(
|
|
5296
5500
|
installLog,
|
|
5297
5501
|
projectDir,
|
|
5298
|
-
rm
|
|
5502
|
+
rm,
|
|
5503
|
+
harnessRoot
|
|
5299
5504
|
);
|
|
5300
5505
|
log(` ${status.success(`templates removed: ${formatTemplateList(installLog)}`)}`);
|
|
5301
5506
|
for (const line of externalRemovalLines(external)) log(line);
|
|
@@ -5402,7 +5607,7 @@ function settleLog(ctx, io) {
|
|
|
5402
5607
|
}
|
|
5403
5608
|
return false;
|
|
5404
5609
|
}
|
|
5405
|
-
function dryRunLines(plan, installLog, projectDir, keepTemplates, rootFiles) {
|
|
5610
|
+
function dryRunLines(plan, installLog, projectDir, keepTemplates, rootFiles, harnessRoot) {
|
|
5406
5611
|
const lines = [c.yellow("[DRY RUN] reverse list (\uC2E4\uC81C \uBCC0\uACBD \uC5C6\uC74C):"), ""];
|
|
5407
5612
|
if (plan.reverseSteps.length === 0) {
|
|
5408
5613
|
lines.push(c.dim(" (no project-scope assets to reverse)"));
|
|
@@ -5424,7 +5629,7 @@ function dryRunLines(plan, installLog, projectDir, keepTemplates, rootFiles) {
|
|
|
5424
5629
|
if (hasRootImport(projectDir)) {
|
|
5425
5630
|
lines.push(" \u25CB strip harness @import from CLAUDE.md (\uBCF8\uBB38 \uBCF4\uC874)");
|
|
5426
5631
|
}
|
|
5427
|
-
lines.push(...previewExternalLines(installLog, projectDir));
|
|
5632
|
+
lines.push(...previewExternalLines(installLog, projectDir, harnessRoot));
|
|
5428
5633
|
}
|
|
5429
5634
|
lines.push(...advisoryLines(plan, projectDir, rootFiles), "");
|
|
5430
5635
|
return lines;
|
|
@@ -5546,11 +5751,11 @@ function buildGlobalAdvisoryCmd(asset) {
|
|
|
5546
5751
|
return "(no standard reverse \u2014 manual)";
|
|
5547
5752
|
}
|
|
5548
5753
|
}
|
|
5549
|
-
function removeTemplates(log, projectDir, rm) {
|
|
5754
|
+
function removeTemplates(log, projectDir, rm, harnessRoot) {
|
|
5550
5755
|
rm(join16(projectDir, log.templates.claudeDir));
|
|
5551
5756
|
if (log.templates.codexDir) rm(join16(projectDir, log.templates.codexDir));
|
|
5552
5757
|
if (log.templates.opencodeDir) rm(join16(projectDir, log.templates.opencodeDir));
|
|
5553
|
-
const external = removeExternalFiles(log, projectDir, rm);
|
|
5758
|
+
const external = removeExternalFiles(log, projectDir, rm, harnessRoot);
|
|
5554
5759
|
const importStripped = stripRootImport(projectDir);
|
|
5555
5760
|
const rootMd = log.templates.rootClaudeMd;
|
|
5556
5761
|
if (rootMd) {
|
|
@@ -5560,22 +5765,55 @@ function removeTemplates(log, projectDir, rm) {
|
|
|
5560
5765
|
}
|
|
5561
5766
|
return { rootClaudeMdKept: false, importStripped, external };
|
|
5562
5767
|
}
|
|
5563
|
-
|
|
5768
|
+
var AGENTS_MD = "AGENTS.md";
|
|
5769
|
+
function readAgentsMdTemplate(harnessRoot, log) {
|
|
5770
|
+
const flavor = log.templates.opencodeDir ? "opencode" : "codex";
|
|
5771
|
+
try {
|
|
5772
|
+
return readFileSync15(join16(harnessRoot, "templates", flavor, "AGENTS.md.template"), "utf8");
|
|
5773
|
+
} catch {
|
|
5774
|
+
return null;
|
|
5775
|
+
}
|
|
5776
|
+
}
|
|
5777
|
+
function agentsMdRemainder(current, harnessRoot, log) {
|
|
5778
|
+
const template = readAgentsMdTemplate(harnessRoot, log);
|
|
5779
|
+
if (template === null) return { ok: false };
|
|
5780
|
+
return { ok: true, remainder: stripHarnessFromAgentsMd({ existing: current, template }) };
|
|
5781
|
+
}
|
|
5782
|
+
function removeExternalFiles(log, projectDir, rm, harnessRoot) {
|
|
5564
5783
|
const removed = [];
|
|
5565
5784
|
const kept = [];
|
|
5785
|
+
const stripped = [];
|
|
5786
|
+
const unjudged = [];
|
|
5566
5787
|
for (const { path, sha256 } of log.externalFiles ?? []) {
|
|
5567
5788
|
const abs = join16(projectDir, path);
|
|
5568
5789
|
if (!existsSync17(abs)) continue;
|
|
5569
5790
|
if (!lstatSync2(abs).isFile()) continue;
|
|
5570
|
-
|
|
5791
|
+
const current = readFileSync15(abs, "utf8");
|
|
5792
|
+
if (hashContent(current) !== sha256) {
|
|
5571
5793
|
kept.push(path);
|
|
5572
5794
|
continue;
|
|
5573
5795
|
}
|
|
5796
|
+
if (path === AGENTS_MD) {
|
|
5797
|
+
const verdict = agentsMdRemainder(current, harnessRoot, log);
|
|
5798
|
+
if (!verdict.ok) {
|
|
5799
|
+
unjudged.push(path);
|
|
5800
|
+
continue;
|
|
5801
|
+
}
|
|
5802
|
+
if (verdict.remainder !== null) {
|
|
5803
|
+
try {
|
|
5804
|
+
writeFileSync7(abs, verdict.remainder, "utf8");
|
|
5805
|
+
stripped.push(path);
|
|
5806
|
+
} catch {
|
|
5807
|
+
unjudged.push(path);
|
|
5808
|
+
}
|
|
5809
|
+
continue;
|
|
5810
|
+
}
|
|
5811
|
+
}
|
|
5574
5812
|
rm(abs);
|
|
5575
5813
|
removed.push(path);
|
|
5576
5814
|
}
|
|
5577
5815
|
for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname5(join16(projectDir, path)));
|
|
5578
|
-
return { removed, kept };
|
|
5816
|
+
return { removed, kept, stripped, unjudged };
|
|
5579
5817
|
}
|
|
5580
5818
|
function pruneEmptyDirsUpward(projectDir, startDir) {
|
|
5581
5819
|
let cursor = startDir;
|
|
@@ -5602,16 +5840,28 @@ function stripRootImport(projectDir) {
|
|
|
5602
5840
|
return false;
|
|
5603
5841
|
}
|
|
5604
5842
|
}
|
|
5605
|
-
function previewExternalLines(installLog, projectDir) {
|
|
5843
|
+
function previewExternalLines(installLog, projectDir, harnessRoot) {
|
|
5606
5844
|
const lines = [];
|
|
5607
5845
|
let removable = 0;
|
|
5608
5846
|
for (const { path, sha256 } of installLog.externalFiles ?? []) {
|
|
5609
5847
|
const abs = join16(projectDir, path);
|
|
5610
5848
|
if (!existsSync17(abs)) continue;
|
|
5611
|
-
|
|
5849
|
+
const current = readFileSync15(abs, "utf8");
|
|
5850
|
+
if (hashContent(current) !== sha256) {
|
|
5612
5851
|
lines.push(` \u25CB keep ${path} (modified since install \u2014 preserved)`);
|
|
5613
5852
|
continue;
|
|
5614
5853
|
}
|
|
5854
|
+
if (path === AGENTS_MD) {
|
|
5855
|
+
const verdict = agentsMdRemainder(current, harnessRoot, installLog);
|
|
5856
|
+
if (!verdict.ok) {
|
|
5857
|
+
lines.push(` \u25CB keep ${path} (harness sections could not be separated \u2014 preserved)`);
|
|
5858
|
+
continue;
|
|
5859
|
+
}
|
|
5860
|
+
if (verdict.remainder !== null) {
|
|
5861
|
+
lines.push(` \u25CB strip harness sections from ${path} (\uBCF8\uBB38 \uBCF4\uC874)`);
|
|
5862
|
+
continue;
|
|
5863
|
+
}
|
|
5864
|
+
}
|
|
5615
5865
|
removable += 1;
|
|
5616
5866
|
}
|
|
5617
5867
|
if (removable > 0) lines.unshift(` \u25CB remove ${removable} CLI output file(s)`);
|
|
@@ -5622,6 +5872,14 @@ function externalRemovalLines(external) {
|
|
|
5622
5872
|
if (external.removed.length > 0) {
|
|
5623
5873
|
lines.push(` ${status.success(`CLI outputs removed: ${external.removed.length} file(s)`)}`);
|
|
5624
5874
|
}
|
|
5875
|
+
for (const path of external.stripped) {
|
|
5876
|
+
lines.push(` ${status.success(`${path} \u2014 harness sections removed (\uBCF8\uBB38 \uBCF4\uC874)`)}`);
|
|
5877
|
+
}
|
|
5878
|
+
for (const path of external.unjudged) {
|
|
5879
|
+
lines.push(
|
|
5880
|
+
` ${c.yellow("\u2298")} ${path} kept \u2014 harness sections could not be separated (template unreadable or write failed). Remove manually if intended.`
|
|
5881
|
+
);
|
|
5882
|
+
}
|
|
5625
5883
|
for (const path of external.kept) {
|
|
5626
5884
|
lines.push(
|
|
5627
5885
|
` ${c.yellow("\u2298")} ${path} kept \u2014 modified since install. Remove manually if intended.`
|