@uzysjung/agent-harness 26.148.1 → 26.149.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/dist/{chunk-PTUM2NI3.js → chunk-YSW3OLH4.js} +50 -9
- package/dist/chunk-YSW3OLH4.js.map +1 -0
- package/dist/index.js +142 -107
- package/dist/index.js.map +1 -1
- package/dist/trust-tier-drift.js +1 -1
- package/package.json +1 -1
- package/templates/rules/ship-checklist.md +4 -3
- package/templates/rules/test-policy.md +12 -3
- package/templates/skills/humanize-korean/SKILL.md +228 -0
- package/templates/skills/north-star/NORTH_STAR.template.md +106 -47
- package/templates/skills/north-star/SKILL.md +230 -74
- package/templates/skills/north-star/references/roadmap-method.md +62 -12
- package/templates/skills/self-hosted-github-runner/SKILL.md +328 -0
- package/templates/skills/verification-loop/SKILL.md +23 -0
- package/dist/chunk-PTUM2NI3.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -56,7 +56,7 @@ import {
|
|
|
56
56
|
summarizeContextCost,
|
|
57
57
|
upsertHarnessImport,
|
|
58
58
|
writeInstallLog
|
|
59
|
-
} from "./chunk-
|
|
59
|
+
} from "./chunk-YSW3OLH4.js";
|
|
60
60
|
|
|
61
61
|
// node_modules/sisteransi/src/index.js
|
|
62
62
|
var require_src = __commonJS({
|
|
@@ -741,7 +741,7 @@ var cac = (name = "") => new CAC(name);
|
|
|
741
741
|
// package.json
|
|
742
742
|
var package_default = {
|
|
743
743
|
name: "@uzysjung/agent-harness",
|
|
744
|
-
version: "26.
|
|
744
|
+
version: "26.149.0",
|
|
745
745
|
description: "Curate vetted AI-coding skills & plugins by your tech stack \u2014 install only what you need, across Claude Code, Codex, OpenCode & Antigravity",
|
|
746
746
|
type: "module",
|
|
747
747
|
publishConfig: {
|
|
@@ -982,9 +982,9 @@ import {
|
|
|
982
982
|
chmodSync as chmodSync2,
|
|
983
983
|
existsSync as existsSync13,
|
|
984
984
|
mkdirSync as mkdirSync4,
|
|
985
|
-
readdirSync as
|
|
985
|
+
readdirSync as readdirSync4,
|
|
986
986
|
readFileSync as readFileSync12,
|
|
987
|
-
unlinkSync as
|
|
987
|
+
unlinkSync as unlinkSync3,
|
|
988
988
|
writeFileSync as writeFileSync6
|
|
989
989
|
} from "fs";
|
|
990
990
|
import { basename as basename5, dirname as dirname4, join as join12, resolve } from "path";
|
|
@@ -1231,7 +1231,7 @@ function writeRules(harnessRoot, projectDir, writer) {
|
|
|
1231
1231
|
template,
|
|
1232
1232
|
claudeMd,
|
|
1233
1233
|
projectName: basename(projectDir),
|
|
1234
|
-
projectContext: renderFillScaffold()
|
|
1234
|
+
projectContext: renderFillScaffold("antigravity-rule")
|
|
1235
1235
|
});
|
|
1236
1236
|
return writer.write(target, rulesOut) ? target : null;
|
|
1237
1237
|
}
|
|
@@ -1377,7 +1377,7 @@ function runCodexTransform(params) {
|
|
|
1377
1377
|
template: agentsTemplate,
|
|
1378
1378
|
claudeMd,
|
|
1379
1379
|
projectName,
|
|
1380
|
-
projectContext: renderFillScaffold(),
|
|
1380
|
+
projectContext: renderFillScaffold("agents-md"),
|
|
1381
1381
|
// Codex 는 룰 디렉터리가 없다 — 룰이 AGENTS.md 본문에 들어가야 도달한다(§Harness Rules).
|
|
1382
1382
|
harnessRules: renderRulesBlock(portRules(harnessRoot, rules))
|
|
1383
1383
|
});
|
|
@@ -1442,7 +1442,7 @@ function readOptionalJson(path) {
|
|
|
1442
1442
|
|
|
1443
1443
|
// src/opencode/transform.ts
|
|
1444
1444
|
init_esm_shims();
|
|
1445
|
-
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
|
|
1445
|
+
import { existsSync as existsSync7, readdirSync, readFileSync as readFileSync6, unlinkSync } from "fs";
|
|
1446
1446
|
import { basename as basename3, join as join7 } from "path";
|
|
1447
1447
|
|
|
1448
1448
|
// src/opencode/agents-md.ts
|
|
@@ -1456,72 +1456,6 @@ function renderAgentsMd2(params) {
|
|
|
1456
1456
|
return renameSlashes2(replaced);
|
|
1457
1457
|
}
|
|
1458
1458
|
|
|
1459
|
-
// src/opencode/commands.ts
|
|
1460
|
-
init_esm_shims();
|
|
1461
|
-
function renderCommandFromSkill(source, id, opts) {
|
|
1462
|
-
const { description, body } = parseSkillFrontmatter(source);
|
|
1463
|
-
const finalDescription = description || `${id} (dev-method skill, OpenCode command fallback)`;
|
|
1464
|
-
const escapedDesc = finalDescription.replace(/"/g, '\\"');
|
|
1465
|
-
const renamedBody = renameSlashes2(body).trimEnd();
|
|
1466
|
-
const agent = opts?.shellDependent ? "build" : "plan";
|
|
1467
|
-
return [
|
|
1468
|
-
"---",
|
|
1469
|
-
`description: "${escapedDesc}"`,
|
|
1470
|
-
`agent: ${agent}`,
|
|
1471
|
-
"---",
|
|
1472
|
-
"",
|
|
1473
|
-
renamedBody,
|
|
1474
|
-
""
|
|
1475
|
-
].join("\n");
|
|
1476
|
-
}
|
|
1477
|
-
function parseSkillFrontmatter(source) {
|
|
1478
|
-
const lines = source.split(/\r?\n/);
|
|
1479
|
-
if (lines[0] !== "---") {
|
|
1480
|
-
const firstLine = lines[0] ?? "";
|
|
1481
|
-
return { description: firstLine.trim(), body: lines.slice(1).join("\n") };
|
|
1482
|
-
}
|
|
1483
|
-
let secondDelimAt = -1;
|
|
1484
|
-
let description = "";
|
|
1485
|
-
for (let i = 1; i < lines.length; i++) {
|
|
1486
|
-
const line = lines[i] ?? "";
|
|
1487
|
-
if (line === "---") {
|
|
1488
|
-
secondDelimAt = i;
|
|
1489
|
-
break;
|
|
1490
|
-
}
|
|
1491
|
-
const inline = line.match(/^description:\s*(.+)$/);
|
|
1492
|
-
if (!inline) {
|
|
1493
|
-
continue;
|
|
1494
|
-
}
|
|
1495
|
-
const raw = (inline[1] ?? "").trim();
|
|
1496
|
-
if (raw === ">-" || raw === ">" || raw === "|" || raw === "|-") {
|
|
1497
|
-
const collected = [];
|
|
1498
|
-
for (let j3 = i + 1; j3 < lines.length; j3++) {
|
|
1499
|
-
const next = lines[j3] ?? "";
|
|
1500
|
-
if (next === "---") {
|
|
1501
|
-
break;
|
|
1502
|
-
}
|
|
1503
|
-
if (next.trim() === "" || /^\s/.test(next)) {
|
|
1504
|
-
collected.push(next.trim());
|
|
1505
|
-
} else {
|
|
1506
|
-
break;
|
|
1507
|
-
}
|
|
1508
|
-
}
|
|
1509
|
-
description = collected.join(" ").replace(/\s+/g, " ").trim();
|
|
1510
|
-
} else {
|
|
1511
|
-
description = stripQuotes(raw);
|
|
1512
|
-
}
|
|
1513
|
-
}
|
|
1514
|
-
const body = secondDelimAt >= 0 ? lines.slice(secondDelimAt + 1).join("\n").replace(/^\n+/, "") : source;
|
|
1515
|
-
return { description, body };
|
|
1516
|
-
}
|
|
1517
|
-
function stripQuotes(raw) {
|
|
1518
|
-
const trimmed = raw.trim();
|
|
1519
|
-
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
1520
|
-
return trimmed.slice(1, -1);
|
|
1521
|
-
}
|
|
1522
|
-
return trimmed;
|
|
1523
|
-
}
|
|
1524
|
-
|
|
1525
1459
|
// src/opencode/opencode-json.ts
|
|
1526
1460
|
init_esm_shims();
|
|
1527
1461
|
function renderOpencodeJson(params) {
|
|
@@ -1565,7 +1499,7 @@ function runOpencodeTransform(params) {
|
|
|
1565
1499
|
template: agentsTemplate,
|
|
1566
1500
|
claudeMd,
|
|
1567
1501
|
projectName,
|
|
1568
|
-
projectContext: renderFillScaffold(),
|
|
1502
|
+
projectContext: renderFillScaffold("agents-md"),
|
|
1569
1503
|
// codex 와 **같은 파일**(프로젝트 루트 `AGENTS.md`)이다. 두 transform 이 서로 다른 본문을
|
|
1570
1504
|
// 쓰면 나중에 도는 쪽이 앞선 쪽을 덮어써, codex+opencode 조합에서 룰이 통째로 사라진다
|
|
1571
1505
|
// (독립 검증 C-1 실측). 같은 내용을 쓰면 순서가 결과를 바꾸지 않는다.
|
|
@@ -1574,27 +1508,37 @@ function runOpencodeTransform(params) {
|
|
|
1574
1508
|
writer.write(agentsMdPath, agentsMdOut);
|
|
1575
1509
|
const opencodeJsonPath = join7(projectDir, "opencode.json");
|
|
1576
1510
|
writer.write(opencodeJsonPath, renderOpencodeJson({ template: opencodeTemplate, mcp }));
|
|
1577
|
-
const
|
|
1578
|
-
if (!refreshOnly) ensureDir(cmdDir);
|
|
1579
|
-
const commandFiles = [];
|
|
1511
|
+
const skillFiles = [];
|
|
1580
1512
|
for (const id of selectedInternalSkills) {
|
|
1581
1513
|
const src = join7(harnessRoot, "templates/skills", id, "SKILL.md");
|
|
1582
1514
|
if (!existsSync7(src)) {
|
|
1583
1515
|
continue;
|
|
1584
1516
|
}
|
|
1585
|
-
const target = join7(
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1517
|
+
const target = join7(projectDir, ".agents", "skills", id, "SKILL.md");
|
|
1518
|
+
if (!writer.write(target, renderBundledSkill(readFileSync6(src, "utf8")))) continue;
|
|
1519
|
+
skillFiles.push(target);
|
|
1520
|
+
}
|
|
1521
|
+
const retiredCommands = [];
|
|
1522
|
+
const cmdDir = join7(projectDir, ".opencode/commands");
|
|
1523
|
+
if (existsSync7(cmdDir)) {
|
|
1524
|
+
for (const entry of readdirSync(cmdDir)) {
|
|
1525
|
+
if (!entry.endsWith(".md")) continue;
|
|
1526
|
+
const id = entry.slice(0, -3);
|
|
1527
|
+
if (!existsSync7(join7(harnessRoot, "templates/skills", id, "SKILL.md"))) continue;
|
|
1528
|
+
const victim = join7(cmdDir, entry);
|
|
1529
|
+
try {
|
|
1530
|
+
backupFile(victim);
|
|
1531
|
+
unlinkSync(victim);
|
|
1532
|
+
retiredCommands.push(victim);
|
|
1533
|
+
} catch {
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1593
1536
|
}
|
|
1594
1537
|
return {
|
|
1595
1538
|
agentsMdPath,
|
|
1596
1539
|
opencodeJsonPath,
|
|
1597
|
-
|
|
1540
|
+
skillFiles,
|
|
1541
|
+
retiredCommands,
|
|
1598
1542
|
ownership: writer.result()
|
|
1599
1543
|
};
|
|
1600
1544
|
}
|
|
@@ -1755,8 +1699,8 @@ function addGitignoreEnv(projectDir) {
|
|
|
1755
1699
|
if (GITIGNORE_ENV_PATTERN.test(content)) {
|
|
1756
1700
|
return false;
|
|
1757
1701
|
}
|
|
1758
|
-
const
|
|
1759
|
-
appendFileSync(path, `${
|
|
1702
|
+
const sep3 = content.endsWith("\n") ? "" : "\n";
|
|
1703
|
+
appendFileSync(path, `${sep3}
|
|
1760
1704
|
# Secret env (auto-added by agent-harness install)
|
|
1761
1705
|
.env
|
|
1762
1706
|
`);
|
|
@@ -1777,9 +1721,9 @@ function addGitignoreAgentArtifacts(projectDir) {
|
|
|
1777
1721
|
if (missing.length === 0) {
|
|
1778
1722
|
return [];
|
|
1779
1723
|
}
|
|
1780
|
-
const
|
|
1724
|
+
const sep3 = content.endsWith("\n") ? "" : "\n";
|
|
1781
1725
|
const block = [GITIGNORE_AGENT_ARTIFACT_HEADER, ...missing].join("\n");
|
|
1782
|
-
appendFileSync(path, `${
|
|
1726
|
+
appendFileSync(path, `${sep3}
|
|
1783
1727
|
${block}
|
|
1784
1728
|
`);
|
|
1785
1729
|
return [...missing];
|
|
@@ -1788,7 +1732,7 @@ ${block}
|
|
|
1788
1732
|
// src/external-installer.ts
|
|
1789
1733
|
init_esm_shims();
|
|
1790
1734
|
import { spawnSync } from "child_process";
|
|
1791
|
-
import { existsSync as existsSync9, readdirSync, readFileSync as readFileSync8 } from "fs";
|
|
1735
|
+
import { existsSync as existsSync9, readdirSync as readdirSync2, readFileSync as readFileSync8 } from "fs";
|
|
1792
1736
|
import { homedir as homedir2 } from "os";
|
|
1793
1737
|
import { join as join9 } from "path";
|
|
1794
1738
|
var DEFAULT_SPAWN_TIMEOUT_MS = 12e4;
|
|
@@ -1999,7 +1943,7 @@ function detectVersion(method, spawn) {
|
|
|
1999
1943
|
const marketplaceShort = method.pluginId.slice(at2 + 1);
|
|
2000
1944
|
const cacheBase = join9(homedir2(), ".claude/plugins/cache", marketplaceShort, plugin);
|
|
2001
1945
|
if (!existsSync9(cacheBase)) return void 0;
|
|
2002
|
-
const versions =
|
|
1946
|
+
const versions = readdirSync2(cacheBase).filter((v2) => /^\d/.test(v2)).sort();
|
|
2003
1947
|
return versions.at(-1);
|
|
2004
1948
|
}
|
|
2005
1949
|
case "npm": {
|
|
@@ -2148,9 +2092,9 @@ import {
|
|
|
2148
2092
|
copyFileSync,
|
|
2149
2093
|
existsSync as existsSync12,
|
|
2150
2094
|
mkdirSync as mkdirSync3,
|
|
2151
|
-
readdirSync as
|
|
2095
|
+
readdirSync as readdirSync3,
|
|
2152
2096
|
readFileSync as readFileSync11,
|
|
2153
|
-
unlinkSync,
|
|
2097
|
+
unlinkSync as unlinkSync2,
|
|
2154
2098
|
writeFileSync as writeFileSync5
|
|
2155
2099
|
} from "fs";
|
|
2156
2100
|
import { basename as basename4, dirname as dirname3, join as join11 } from "path";
|
|
@@ -2375,7 +2319,7 @@ function updateDir(target, source, ext, ctx, now = /* @__PURE__ */ new Date()) {
|
|
|
2375
2319
|
if (!existsSync12(target) || !existsSync12(source)) return { updated: 0, backedUp: [] };
|
|
2376
2320
|
let updated = 0;
|
|
2377
2321
|
const backedUp = [];
|
|
2378
|
-
for (const file of
|
|
2322
|
+
for (const file of readdirSync3(target)) {
|
|
2379
2323
|
if (!file.endsWith(ext)) continue;
|
|
2380
2324
|
const targetFile = join11(target, file);
|
|
2381
2325
|
const sourceFile = join11(source, file);
|
|
@@ -2399,7 +2343,7 @@ function syncSkills(targetDir, sourceDir, baseline, now = /* @__PURE__ */ new Da
|
|
|
2399
2343
|
const backedUp = [];
|
|
2400
2344
|
const skippedLinks = [];
|
|
2401
2345
|
const foreignOwned = [];
|
|
2402
|
-
for (const skill of
|
|
2346
|
+
for (const skill of readdirSync3(sourceDir, { withFileTypes: true })) {
|
|
2403
2347
|
if (!skill.isDirectory()) continue;
|
|
2404
2348
|
const targetSkill = join11(targetDir, skill.name);
|
|
2405
2349
|
if (occupiedByNonDirectory(targetSkill)) {
|
|
@@ -2469,7 +2413,7 @@ function refreshSkillBaseline(projectDir) {
|
|
|
2469
2413
|
function pruneOrphans(target, source, ext, ctx, now = /* @__PURE__ */ new Date()) {
|
|
2470
2414
|
if (!existsSync12(target) || !existsSync12(source)) return [];
|
|
2471
2415
|
const removed = [];
|
|
2472
|
-
for (const file of
|
|
2416
|
+
for (const file of readdirSync3(target)) {
|
|
2473
2417
|
if (!file.endsWith(ext)) continue;
|
|
2474
2418
|
const sourceFile = join11(source, file);
|
|
2475
2419
|
if (!existsSync12(sourceFile)) {
|
|
@@ -2478,7 +2422,7 @@ function pruneOrphans(target, source, ext, ctx, now = /* @__PURE__ */ new Date()
|
|
|
2478
2422
|
try {
|
|
2479
2423
|
const current = readFileSync11(targetFile, "utf8");
|
|
2480
2424
|
if (!isHarnessOwned2(ctx, file, current)) backupFile(targetFile, now);
|
|
2481
|
-
|
|
2425
|
+
unlinkSync2(targetFile);
|
|
2482
2426
|
removed.push(file);
|
|
2483
2427
|
} catch {
|
|
2484
2428
|
}
|
|
@@ -2491,7 +2435,7 @@ function retireMcpAllowlist(projectDir, now = /* @__PURE__ */ new Date()) {
|
|
|
2491
2435
|
if (!existsSync12(target)) return null;
|
|
2492
2436
|
try {
|
|
2493
2437
|
const backup = backupFile(target, now);
|
|
2494
|
-
|
|
2438
|
+
unlinkSync2(target);
|
|
2495
2439
|
return backup;
|
|
2496
2440
|
} catch {
|
|
2497
2441
|
return null;
|
|
@@ -2655,7 +2599,7 @@ function applySupersededCleanup(spec, projectDir, found) {
|
|
|
2655
2599
|
const kept = [];
|
|
2656
2600
|
for (const asset of found) {
|
|
2657
2601
|
try {
|
|
2658
|
-
|
|
2602
|
+
unlinkSync3(join12(projectDir, asset.target));
|
|
2659
2603
|
removed.push(asset.target);
|
|
2660
2604
|
} catch {
|
|
2661
2605
|
kept.push(asset.target);
|
|
@@ -2993,7 +2937,7 @@ function chmodHooksSync(hookDir) {
|
|
|
2993
2937
|
}
|
|
2994
2938
|
}
|
|
2995
2939
|
function listHookFiles(hookDir) {
|
|
2996
|
-
return
|
|
2940
|
+
return readdirSync4(hookDir, { withFileTypes: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".sh")).map((e2) => resolve(hookDir, e2.name));
|
|
2997
2941
|
}
|
|
2998
2942
|
|
|
2999
2943
|
// src/commands/install-render.ts
|
|
@@ -3167,7 +3111,24 @@ function renderCliArtifacts(log, spec, report) {
|
|
|
3167
3111
|
}
|
|
3168
3112
|
if (report.opencode) {
|
|
3169
3113
|
log(assetRow("success", "opencode.json", "$schema + 5 keys"));
|
|
3170
|
-
|
|
3114
|
+
if (report.opencode.skillFiles.length > 0) {
|
|
3115
|
+
log(
|
|
3116
|
+
assetRow(
|
|
3117
|
+
"success",
|
|
3118
|
+
".agents/skills/",
|
|
3119
|
+
`${report.opencode.skillFiles.length} dev-method skills (codex\xB7antigravity \uC640 \uAC19\uC740 \uC790\uB9AC)`
|
|
3120
|
+
)
|
|
3121
|
+
);
|
|
3122
|
+
}
|
|
3123
|
+
if (report.opencode.retiredCommands.length > 0) {
|
|
3124
|
+
log(
|
|
3125
|
+
assetRow(
|
|
3126
|
+
"skip",
|
|
3127
|
+
".opencode/commands/",
|
|
3128
|
+
`${report.opencode.retiredCommands.length} retired \xB7 \uAC19\uC740 \uC2A4\uD0AC\uC774 .agents/skills/ \uB85C \uC62E\uACA8\uC84C\uB2E4 (\uBC31\uC5C5 \uB0A8\uAE40)`
|
|
3129
|
+
)
|
|
3130
|
+
);
|
|
3131
|
+
}
|
|
3171
3132
|
}
|
|
3172
3133
|
if (report.antigravity) {
|
|
3173
3134
|
if (report.antigravity.rulesFile) {
|
|
@@ -3900,8 +3861,16 @@ function registerListCommand(cli2) {
|
|
|
3900
3861
|
// src/commands/uninstall.ts
|
|
3901
3862
|
init_esm_shims();
|
|
3902
3863
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
3903
|
-
import {
|
|
3904
|
-
|
|
3864
|
+
import {
|
|
3865
|
+
existsSync as existsSync15,
|
|
3866
|
+
lstatSync as lstatSync2,
|
|
3867
|
+
readdirSync as readdirSync5,
|
|
3868
|
+
readFileSync as readFileSync14,
|
|
3869
|
+
rmdirSync,
|
|
3870
|
+
rmSync,
|
|
3871
|
+
writeFileSync as writeFileSync7
|
|
3872
|
+
} from "fs";
|
|
3873
|
+
import { dirname as dirname5, join as join14, relative as relative2, resolve as resolve4, sep as sep2 } from "path";
|
|
3905
3874
|
|
|
3906
3875
|
// src/uninstall-interactive.ts
|
|
3907
3876
|
init_esm_shims();
|
|
@@ -5037,8 +5006,13 @@ function uninstallAction(options, deps = {}) {
|
|
|
5037
5006
|
const logSurvives = selectedIds !== null;
|
|
5038
5007
|
const { succeeded, failed, removedIds } = executeReverse(plan, log, logSurvives);
|
|
5039
5008
|
if (!keepTemplates) {
|
|
5040
|
-
const { rootClaudeMdKept, importStripped } = removeTemplates(
|
|
5009
|
+
const { rootClaudeMdKept, importStripped, external } = removeTemplates(
|
|
5010
|
+
installLog,
|
|
5011
|
+
projectDir,
|
|
5012
|
+
rm
|
|
5013
|
+
);
|
|
5041
5014
|
log(` ${status.success(`templates removed: ${formatTemplateList(installLog)}`)}`);
|
|
5015
|
+
for (const line of externalRemovalLines(external)) log(line);
|
|
5042
5016
|
if (importStripped) {
|
|
5043
5017
|
log(` ${status.success("CLAUDE.md \u2014 harness @import removed (\uBCF8\uBB38 \uBCF4\uC874)")}`);
|
|
5044
5018
|
}
|
|
@@ -5164,6 +5138,7 @@ function dryRunLines(plan, installLog, projectDir, keepTemplates, rootFiles) {
|
|
|
5164
5138
|
if (hasRootImport(projectDir)) {
|
|
5165
5139
|
lines.push(" \u25CB strip harness @import from CLAUDE.md (\uBCF8\uBB38 \uBCF4\uC874)");
|
|
5166
5140
|
}
|
|
5141
|
+
lines.push(...previewExternalLines(installLog, projectDir));
|
|
5167
5142
|
}
|
|
5168
5143
|
lines.push(...advisoryLines(plan, projectDir, rootFiles), "");
|
|
5169
5144
|
return lines;
|
|
@@ -5289,13 +5264,46 @@ function removeTemplates(log, projectDir, rm) {
|
|
|
5289
5264
|
rm(join14(projectDir, log.templates.claudeDir));
|
|
5290
5265
|
if (log.templates.codexDir) rm(join14(projectDir, log.templates.codexDir));
|
|
5291
5266
|
if (log.templates.opencodeDir) rm(join14(projectDir, log.templates.opencodeDir));
|
|
5267
|
+
const external = removeExternalFiles(log, projectDir, rm);
|
|
5292
5268
|
const importStripped = stripRootImport(projectDir);
|
|
5293
5269
|
const rootMd = log.templates.rootClaudeMd;
|
|
5294
5270
|
if (rootMd) {
|
|
5295
|
-
if (rootClaudeMdModified(log, projectDir))
|
|
5271
|
+
if (rootClaudeMdModified(log, projectDir))
|
|
5272
|
+
return { rootClaudeMdKept: true, importStripped, external };
|
|
5296
5273
|
rm(join14(projectDir, rootMd.path));
|
|
5297
5274
|
}
|
|
5298
|
-
return { rootClaudeMdKept: false, importStripped };
|
|
5275
|
+
return { rootClaudeMdKept: false, importStripped, external };
|
|
5276
|
+
}
|
|
5277
|
+
function removeExternalFiles(log, projectDir, rm) {
|
|
5278
|
+
const removed = [];
|
|
5279
|
+
const kept = [];
|
|
5280
|
+
for (const { path, sha256 } of log.externalFiles ?? []) {
|
|
5281
|
+
const abs = join14(projectDir, path);
|
|
5282
|
+
if (!existsSync15(abs)) continue;
|
|
5283
|
+
if (!lstatSync2(abs).isFile()) continue;
|
|
5284
|
+
if (hashContent(readFileSync14(abs, "utf8")) !== sha256) {
|
|
5285
|
+
kept.push(path);
|
|
5286
|
+
continue;
|
|
5287
|
+
}
|
|
5288
|
+
rm(abs);
|
|
5289
|
+
removed.push(path);
|
|
5290
|
+
}
|
|
5291
|
+
for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname5(join14(projectDir, path)));
|
|
5292
|
+
return { removed, kept };
|
|
5293
|
+
}
|
|
5294
|
+
function pruneEmptyDirsUpward(projectDir, startDir) {
|
|
5295
|
+
let cursor = startDir;
|
|
5296
|
+
while (true) {
|
|
5297
|
+
const rel = relative2(projectDir, cursor);
|
|
5298
|
+
if (rel === "" || rel.startsWith(`..${sep2}`) || rel === "..") return;
|
|
5299
|
+
try {
|
|
5300
|
+
if (readdirSync5(cursor).length > 0) return;
|
|
5301
|
+
rmdirSync(cursor);
|
|
5302
|
+
} catch {
|
|
5303
|
+
return;
|
|
5304
|
+
}
|
|
5305
|
+
cursor = dirname5(cursor);
|
|
5306
|
+
}
|
|
5299
5307
|
}
|
|
5300
5308
|
function stripRootImport(projectDir) {
|
|
5301
5309
|
const current = readRootClaudeMd(projectDir);
|
|
@@ -5308,6 +5316,33 @@ function stripRootImport(projectDir) {
|
|
|
5308
5316
|
return false;
|
|
5309
5317
|
}
|
|
5310
5318
|
}
|
|
5319
|
+
function previewExternalLines(installLog, projectDir) {
|
|
5320
|
+
const lines = [];
|
|
5321
|
+
let removable = 0;
|
|
5322
|
+
for (const { path, sha256 } of installLog.externalFiles ?? []) {
|
|
5323
|
+
const abs = join14(projectDir, path);
|
|
5324
|
+
if (!existsSync15(abs)) continue;
|
|
5325
|
+
if (hashContent(readFileSync14(abs, "utf8")) !== sha256) {
|
|
5326
|
+
lines.push(` \u25CB keep ${path} (modified since install \u2014 preserved)`);
|
|
5327
|
+
continue;
|
|
5328
|
+
}
|
|
5329
|
+
removable += 1;
|
|
5330
|
+
}
|
|
5331
|
+
if (removable > 0) lines.unshift(` \u25CB remove ${removable} CLI output file(s)`);
|
|
5332
|
+
return lines;
|
|
5333
|
+
}
|
|
5334
|
+
function externalRemovalLines(external) {
|
|
5335
|
+
const lines = [];
|
|
5336
|
+
if (external.removed.length > 0) {
|
|
5337
|
+
lines.push(` ${status.success(`CLI outputs removed: ${external.removed.length} file(s)`)}`);
|
|
5338
|
+
}
|
|
5339
|
+
for (const path of external.kept) {
|
|
5340
|
+
lines.push(
|
|
5341
|
+
` ${c.yellow("\u2298")} ${path} kept \u2014 modified since install. Remove manually if intended.`
|
|
5342
|
+
);
|
|
5343
|
+
}
|
|
5344
|
+
return lines;
|
|
5345
|
+
}
|
|
5311
5346
|
function hasRootImport(projectDir) {
|
|
5312
5347
|
const current = readRootClaudeMd(projectDir);
|
|
5313
5348
|
return current !== null && stripHarnessImport(current) !== null;
|