@uzysjung/agent-harness 26.147.0 → 26.148.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 +5 -5
- package/README.md +3 -3
- package/dist/{chunk-GPH5AWLX.js → chunk-PTUM2NI3.js} +25 -20
- package/dist/chunk-PTUM2NI3.js.map +1 -0
- package/dist/index.js +224 -123
- package/dist/index.js.map +1 -1
- package/dist/trust-tier-drift.js +1 -1
- package/package.json +2 -1
- package/templates/rules/ship-checklist.md +1 -0
- package/templates/scripts/check-absence.sh +17 -3
- package/dist/chunk-GPH5AWLX.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-PTUM2NI3.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.148.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: {
|
|
@@ -775,6 +775,7 @@ var package_default = {
|
|
|
775
775
|
prepare: "[ -d dist ] || npm run build",
|
|
776
776
|
"cost:report": "npm run build && node scripts/context-cost-report.mjs",
|
|
777
777
|
"cost:baseline": "npm run build && node scripts/context-cost-baseline.mjs",
|
|
778
|
+
"assets:history": "bash scripts/asset-history.sh",
|
|
778
779
|
security: "npm audit --omit=dev --audit-level=high && npx --yes ecc-agentshield@1.4.0 scan --baseline security-baseline.json --gate",
|
|
779
780
|
"security:baseline": "npx --yes ecc-agentshield@1.4.0 scan --save-baseline security-baseline.json"
|
|
780
781
|
},
|
|
@@ -985,7 +986,7 @@ import {
|
|
|
985
986
|
unlinkSync as unlinkSync2,
|
|
986
987
|
writeFileSync as writeFileSync6
|
|
987
988
|
} from "fs";
|
|
988
|
-
import { basename as basename5, dirname as dirname4, join as
|
|
989
|
+
import { basename as basename5, dirname as dirname4, join as join12, resolve } from "path";
|
|
989
990
|
|
|
990
991
|
// src/ci-scaffold.ts
|
|
991
992
|
init_esm_shims();
|
|
@@ -1031,7 +1032,7 @@ init_esm_shims();
|
|
|
1031
1032
|
// src/antigravity/transform.ts
|
|
1032
1033
|
init_esm_shims();
|
|
1033
1034
|
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
1034
|
-
import { basename, join as
|
|
1035
|
+
import { basename, join as join4 } from "path";
|
|
1035
1036
|
|
|
1036
1037
|
// src/codex/agents-md.ts
|
|
1037
1038
|
init_esm_shims();
|
|
@@ -1078,15 +1079,49 @@ function portBody(body) {
|
|
|
1078
1079
|
init_esm_shims();
|
|
1079
1080
|
import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
1080
1081
|
import { dirname, relative, sep } from "path";
|
|
1082
|
+
|
|
1083
|
+
// src/foreign-slot.ts
|
|
1084
|
+
init_esm_shims();
|
|
1085
|
+
import { lstatSync } from "fs";
|
|
1086
|
+
import { join as join2 } from "path";
|
|
1087
|
+
var SKILL_SLOT = /^((?:\.claude|\.agents)\/skills\/[^/]+)(?:\/|$)/;
|
|
1088
|
+
function occupiedByNonDirectory(path) {
|
|
1089
|
+
const stat = lstatSync(path, { throwIfNoEntry: false });
|
|
1090
|
+
return stat !== void 0 && !stat.isDirectory();
|
|
1091
|
+
}
|
|
1092
|
+
function foreignOwnedTarget(projectDir, rel) {
|
|
1093
|
+
const slot = SKILL_SLOT.exec(rel)?.[1];
|
|
1094
|
+
if (slot === void 0) return null;
|
|
1095
|
+
if (occupiedByNonDirectory(join2(projectDir, slot))) return slot;
|
|
1096
|
+
if (rel !== slot) {
|
|
1097
|
+
const parts = rel.slice(slot.length + 1).split("/");
|
|
1098
|
+
let cursor = slot;
|
|
1099
|
+
for (const part of parts.slice(0, -1)) {
|
|
1100
|
+
cursor = `${cursor}/${part}`;
|
|
1101
|
+
if (occupiedByNonDirectory(join2(projectDir, cursor))) return cursor;
|
|
1102
|
+
}
|
|
1103
|
+
const leaf = lstatSync(join2(projectDir, rel), { throwIfNoEntry: false });
|
|
1104
|
+
if (leaf !== void 0 && !leaf.isFile()) return rel;
|
|
1105
|
+
}
|
|
1106
|
+
return null;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// src/owned-write.ts
|
|
1081
1110
|
function createOwnedWriter(projectDir, baseline, options = {}) {
|
|
1082
1111
|
const { now = /* @__PURE__ */ new Date(), refreshOnly = false } = options;
|
|
1083
1112
|
const written = /* @__PURE__ */ new Map();
|
|
1084
1113
|
const backedUp = [];
|
|
1085
1114
|
const backupPaths = [];
|
|
1115
|
+
const foreignOwned = [];
|
|
1086
1116
|
let updated = 0;
|
|
1087
1117
|
return {
|
|
1088
1118
|
write(absPath, content, opts) {
|
|
1089
1119
|
const rel = relative(projectDir, absPath).split(sep).join("/");
|
|
1120
|
+
const foreign = foreignOwnedTarget(projectDir, rel);
|
|
1121
|
+
if (foreign !== null) {
|
|
1122
|
+
if (!foreignOwned.includes(foreign)) foreignOwned.push(foreign);
|
|
1123
|
+
return false;
|
|
1124
|
+
}
|
|
1090
1125
|
const digest = hashContent(content);
|
|
1091
1126
|
if (!existsSync2(absPath)) {
|
|
1092
1127
|
if (refreshOnly && !opts?.createInRefresh) return false;
|
|
@@ -1109,6 +1144,7 @@ function createOwnedWriter(projectDir, baseline, options = {}) {
|
|
|
1109
1144
|
},
|
|
1110
1145
|
result() {
|
|
1111
1146
|
return {
|
|
1147
|
+
foreignOwned: [...foreignOwned],
|
|
1112
1148
|
files: [...written].map(([path, sha256]) => ({ path, sha256 })),
|
|
1113
1149
|
backedUp: [...backedUp],
|
|
1114
1150
|
backupPaths: [...backupPaths],
|
|
@@ -1121,7 +1157,7 @@ function createOwnedWriter(projectDir, baseline, options = {}) {
|
|
|
1121
1157
|
// src/rules-port.ts
|
|
1122
1158
|
init_esm_shims();
|
|
1123
1159
|
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
1124
|
-
import { join as
|
|
1160
|
+
import { join as join3 } from "path";
|
|
1125
1161
|
function stripClaudeFrontmatter(source) {
|
|
1126
1162
|
if (!source.startsWith("---\n")) return source.trim();
|
|
1127
1163
|
const end = source.indexOf("\n---", 4);
|
|
@@ -1131,7 +1167,7 @@ function stripClaudeFrontmatter(source) {
|
|
|
1131
1167
|
function portRules(harnessRoot, ruleNames) {
|
|
1132
1168
|
const ported = [];
|
|
1133
1169
|
for (const name of ruleNames) {
|
|
1134
|
-
const src =
|
|
1170
|
+
const src = join3(harnessRoot, "templates/rules", `${name}.md`);
|
|
1135
1171
|
if (!existsSync3(src)) continue;
|
|
1136
1172
|
ported.push({ name, body: stripClaudeFrontmatter(readFileSync2(src, "utf8")) });
|
|
1137
1173
|
}
|
|
@@ -1157,7 +1193,7 @@ function runAntigravityTransform(params) {
|
|
|
1157
1193
|
const antigravityInstalled = rulesFile !== null;
|
|
1158
1194
|
const harnessRuleFiles = [];
|
|
1159
1195
|
for (const rule of portRules(harnessRoot, rules)) {
|
|
1160
|
-
const target =
|
|
1196
|
+
const target = join4(projectDir, ".agents", "rules", `${rule.name}.md`);
|
|
1161
1197
|
if (!writer.write(target, `${rule.body}
|
|
1162
1198
|
`, { createInRefresh: antigravityInstalled })) {
|
|
1163
1199
|
continue;
|
|
@@ -1166,11 +1202,11 @@ function runAntigravityTransform(params) {
|
|
|
1166
1202
|
}
|
|
1167
1203
|
const skillFiles = [];
|
|
1168
1204
|
for (const id of selectedInternalSkills) {
|
|
1169
|
-
const src =
|
|
1205
|
+
const src = join4(harnessRoot, "templates/skills", id, "SKILL.md");
|
|
1170
1206
|
if (!existsSync4(src)) {
|
|
1171
1207
|
continue;
|
|
1172
1208
|
}
|
|
1173
|
-
const target =
|
|
1209
|
+
const target = join4(projectDir, ".agents", "skills", id, "SKILL.md");
|
|
1174
1210
|
if (!writer.write(target, renderBundledSkill(readFileSync3(src, "utf8")))) continue;
|
|
1175
1211
|
skillFiles.push(target);
|
|
1176
1212
|
}
|
|
@@ -1182,14 +1218,14 @@ function runAntigravityTransform(params) {
|
|
|
1182
1218
|
};
|
|
1183
1219
|
}
|
|
1184
1220
|
function writeRules(harnessRoot, projectDir, writer) {
|
|
1185
|
-
const claudeMdPath =
|
|
1186
|
-
const templatePath =
|
|
1221
|
+
const claudeMdPath = join4(harnessRoot, "templates/CLAUDE.md");
|
|
1222
|
+
const templatePath = join4(harnessRoot, "templates/antigravity/AGENTS.md.template");
|
|
1187
1223
|
if (!existsSync4(claudeMdPath) || !existsSync4(templatePath)) {
|
|
1188
1224
|
return null;
|
|
1189
1225
|
}
|
|
1190
1226
|
const claudeMd = readFileSync3(claudeMdPath, "utf8");
|
|
1191
1227
|
const template = readFileSync3(templatePath, "utf8");
|
|
1192
|
-
const target =
|
|
1228
|
+
const target = join4(projectDir, ".agents", "rules", "uzys-harness.md");
|
|
1193
1229
|
const rulesOut = renderAgentsMd({
|
|
1194
1230
|
template,
|
|
1195
1231
|
claudeMd,
|
|
@@ -1202,7 +1238,7 @@ function writeRules(harnessRoot, projectDir, writer) {
|
|
|
1202
1238
|
// src/codex/opt-in.ts
|
|
1203
1239
|
init_esm_shims();
|
|
1204
1240
|
import { homedir } from "os";
|
|
1205
|
-
import { join as
|
|
1241
|
+
import { join as join5 } from "path";
|
|
1206
1242
|
|
|
1207
1243
|
// src/codex/trust-entry.ts
|
|
1208
1244
|
init_esm_shims();
|
|
@@ -1237,8 +1273,8 @@ function hasTrustEntry(configContent, projectDir) {
|
|
|
1237
1273
|
|
|
1238
1274
|
// src/codex/opt-in.ts
|
|
1239
1275
|
function runCodexOptIn(ctx) {
|
|
1240
|
-
const codexHome = ctx.codexHome ??
|
|
1241
|
-
const configPath =
|
|
1276
|
+
const codexHome = ctx.codexHome ?? join5(homedir(), ".codex");
|
|
1277
|
+
const configPath = join5(codexHome, "config.toml");
|
|
1242
1278
|
const result = registerTrustEntry({ configPath, projectDir: ctx.projectDir });
|
|
1243
1279
|
return {
|
|
1244
1280
|
trustEntry: {
|
|
@@ -1252,7 +1288,7 @@ function runCodexOptIn(ctx) {
|
|
|
1252
1288
|
// src/codex/transform.ts
|
|
1253
1289
|
init_esm_shims();
|
|
1254
1290
|
import { chmodSync, existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
1255
|
-
import { basename as basename2, join as
|
|
1291
|
+
import { basename as basename2, join as join6 } from "path";
|
|
1256
1292
|
|
|
1257
1293
|
// src/codex/config-toml.ts
|
|
1258
1294
|
init_esm_shims();
|
|
@@ -1329,12 +1365,12 @@ function runCodexTransform(params) {
|
|
|
1329
1365
|
refreshOnly
|
|
1330
1366
|
} = params;
|
|
1331
1367
|
const writer = createOwnedWriter(projectDir, baseline, { refreshOnly: refreshOnly ?? false });
|
|
1332
|
-
const claudeMd = readRequired(
|
|
1333
|
-
const agentsTemplate = readRequired(
|
|
1334
|
-
const configTemplate = readRequired(
|
|
1368
|
+
const claudeMd = readRequired(join6(harnessRoot, "templates/CLAUDE.md"));
|
|
1369
|
+
const agentsTemplate = readRequired(join6(harnessRoot, "templates/codex/AGENTS.md.template"));
|
|
1370
|
+
const configTemplate = readRequired(join6(harnessRoot, "templates/codex/config.toml.template"));
|
|
1335
1371
|
const projectName = basename2(projectDir);
|
|
1336
|
-
const mcp = readOptionalJson(
|
|
1337
|
-
const agentsMdPath =
|
|
1372
|
+
const mcp = readOptionalJson(join6(harnessRoot, ".mcp.json"));
|
|
1373
|
+
const agentsMdPath = join6(projectDir, "AGENTS.md");
|
|
1338
1374
|
ensureDir(projectDir);
|
|
1339
1375
|
const agentsMdOut = renderAgentsMd({
|
|
1340
1376
|
template: agentsTemplate,
|
|
@@ -1345,7 +1381,7 @@ function runCodexTransform(params) {
|
|
|
1345
1381
|
harnessRules: renderRulesBlock(portRules(harnessRoot, rules))
|
|
1346
1382
|
});
|
|
1347
1383
|
writer.write(agentsMdPath, agentsMdOut);
|
|
1348
|
-
const configTomlPath =
|
|
1384
|
+
const configTomlPath = join6(projectDir, ".codex/config.toml");
|
|
1349
1385
|
writer.write(
|
|
1350
1386
|
configTomlPath,
|
|
1351
1387
|
renderConfigToml({
|
|
@@ -1355,26 +1391,26 @@ function runCodexTransform(params) {
|
|
|
1355
1391
|
mcp
|
|
1356
1392
|
})
|
|
1357
1393
|
);
|
|
1358
|
-
const hookDir =
|
|
1394
|
+
const hookDir = join6(projectDir, ".codex/hooks");
|
|
1359
1395
|
const hookFiles = [];
|
|
1360
1396
|
for (const hook of HOOK_NAMES) {
|
|
1361
|
-
const src =
|
|
1397
|
+
const src = join6(harnessRoot, "templates/hooks", `${hook}.sh`);
|
|
1362
1398
|
if (!existsSync6(src)) {
|
|
1363
1399
|
continue;
|
|
1364
1400
|
}
|
|
1365
1401
|
const ported = readFileSync5(src, "utf8").replace(ENV_VAR_RENAME, "CODEX_PROJECT_DIR");
|
|
1366
|
-
const target =
|
|
1402
|
+
const target = join6(hookDir, `${hook}.sh`);
|
|
1367
1403
|
if (!writer.write(target, ported)) continue;
|
|
1368
1404
|
chmodSync(target, 493);
|
|
1369
1405
|
hookFiles.push(target);
|
|
1370
1406
|
}
|
|
1371
1407
|
const skillFiles = [];
|
|
1372
1408
|
for (const id of selectedInternalSkills) {
|
|
1373
|
-
const src =
|
|
1409
|
+
const src = join6(harnessRoot, "templates/skills", id, "SKILL.md");
|
|
1374
1410
|
if (!existsSync6(src)) {
|
|
1375
1411
|
continue;
|
|
1376
1412
|
}
|
|
1377
|
-
const target =
|
|
1413
|
+
const target = join6(projectDir, ".agents", "skills", id, "SKILL.md");
|
|
1378
1414
|
if (!writer.write(target, renderBundledSkill(readFileSync5(src, "utf8")))) continue;
|
|
1379
1415
|
skillFiles.push(target);
|
|
1380
1416
|
}
|
|
@@ -1406,7 +1442,7 @@ function readOptionalJson(path) {
|
|
|
1406
1442
|
// src/opencode/transform.ts
|
|
1407
1443
|
init_esm_shims();
|
|
1408
1444
|
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
|
|
1409
|
-
import { basename as basename3, join as
|
|
1445
|
+
import { basename as basename3, join as join7 } from "path";
|
|
1410
1446
|
|
|
1411
1447
|
// src/opencode/agents-md.ts
|
|
1412
1448
|
init_esm_shims();
|
|
@@ -1515,15 +1551,15 @@ function runOpencodeTransform(params) {
|
|
|
1515
1551
|
refreshOnly
|
|
1516
1552
|
} = params;
|
|
1517
1553
|
const writer = createOwnedWriter(projectDir, baseline, { refreshOnly: refreshOnly ?? false });
|
|
1518
|
-
const claudeMd = readRequired2(
|
|
1519
|
-
const agentsTemplate = readRequired2(
|
|
1554
|
+
const claudeMd = readRequired2(join7(harnessRoot, "templates/CLAUDE.md"));
|
|
1555
|
+
const agentsTemplate = readRequired2(join7(harnessRoot, "templates/opencode/AGENTS.md.template"));
|
|
1520
1556
|
const opencodeTemplate = readRequired2(
|
|
1521
|
-
|
|
1557
|
+
join7(harnessRoot, "templates/opencode/opencode.json.template")
|
|
1522
1558
|
);
|
|
1523
1559
|
const projectName = basename3(projectDir);
|
|
1524
|
-
const mcp = readOptionalJson2(
|
|
1560
|
+
const mcp = readOptionalJson2(join7(harnessRoot, ".mcp.json"));
|
|
1525
1561
|
ensureDir(projectDir);
|
|
1526
|
-
const agentsMdPath =
|
|
1562
|
+
const agentsMdPath = join7(projectDir, "AGENTS.md");
|
|
1527
1563
|
const agentsMdOut = renderAgentsMd2({
|
|
1528
1564
|
template: agentsTemplate,
|
|
1529
1565
|
claudeMd,
|
|
@@ -1535,18 +1571,18 @@ function runOpencodeTransform(params) {
|
|
|
1535
1571
|
harnessRules: renderRulesBlock(portRules(harnessRoot, rules))
|
|
1536
1572
|
});
|
|
1537
1573
|
writer.write(agentsMdPath, agentsMdOut);
|
|
1538
|
-
const opencodeJsonPath =
|
|
1574
|
+
const opencodeJsonPath = join7(projectDir, "opencode.json");
|
|
1539
1575
|
writer.write(opencodeJsonPath, renderOpencodeJson({ template: opencodeTemplate, mcp }));
|
|
1540
|
-
const cmdDir =
|
|
1576
|
+
const cmdDir = join7(projectDir, ".opencode/commands");
|
|
1541
1577
|
if (!refreshOnly) ensureDir(cmdDir);
|
|
1542
1578
|
const commandFiles = [];
|
|
1543
1579
|
for (const id of selectedInternalSkills) {
|
|
1544
|
-
const src =
|
|
1580
|
+
const src = join7(harnessRoot, "templates/skills", id, "SKILL.md");
|
|
1545
1581
|
if (!existsSync7(src)) {
|
|
1546
1582
|
continue;
|
|
1547
1583
|
}
|
|
1548
|
-
const target =
|
|
1549
|
-
const shellDependent = existsSync7(
|
|
1584
|
+
const target = join7(cmdDir, `${id}.md`);
|
|
1585
|
+
const shellDependent = existsSync7(join7(harnessRoot, "templates/skills", id, "scripts"));
|
|
1550
1586
|
const wrote = writer.write(
|
|
1551
1587
|
target,
|
|
1552
1588
|
renderCommandFromSkill(readFileSync6(src, "utf8"), id, { shellDependent })
|
|
@@ -1595,6 +1631,7 @@ function runCliTransforms(params) {
|
|
|
1595
1631
|
const externalFiles = [];
|
|
1596
1632
|
const externalBackups = [];
|
|
1597
1633
|
const externalBackedUp = [];
|
|
1634
|
+
const externalForeignOwned = [];
|
|
1598
1635
|
let externalUpdated = 0;
|
|
1599
1636
|
const absorb = (report) => {
|
|
1600
1637
|
for (const f of report.ownership.files) {
|
|
@@ -1603,6 +1640,9 @@ function runCliTransforms(params) {
|
|
|
1603
1640
|
}
|
|
1604
1641
|
externalBackups.push(...report.ownership.backupPaths);
|
|
1605
1642
|
externalBackedUp.push(...report.ownership.backedUp);
|
|
1643
|
+
for (const f of report.ownership.foreignOwned) {
|
|
1644
|
+
if (!externalForeignOwned.includes(f)) externalForeignOwned.push(f);
|
|
1645
|
+
}
|
|
1606
1646
|
externalUpdated += report.ownership.updated;
|
|
1607
1647
|
};
|
|
1608
1648
|
let codex = null;
|
|
@@ -1653,14 +1693,15 @@ function runCliTransforms(params) {
|
|
|
1653
1693
|
externalFiles,
|
|
1654
1694
|
externalBackups,
|
|
1655
1695
|
externalUpdated,
|
|
1656
|
-
externalBackedUp
|
|
1696
|
+
externalBackedUp,
|
|
1697
|
+
externalForeignOwned
|
|
1657
1698
|
};
|
|
1658
1699
|
}
|
|
1659
1700
|
|
|
1660
1701
|
// src/env-files.ts
|
|
1661
1702
|
init_esm_shims();
|
|
1662
1703
|
import { appendFileSync, existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
|
|
1663
|
-
import { join as
|
|
1704
|
+
import { join as join8 } from "path";
|
|
1664
1705
|
var ENV_EXAMPLE_BODY = `# .env.example \u2014 csr-supabase Track
|
|
1665
1706
|
# Copy to .env (gitignored) and fill in values: cp .env.example .env
|
|
1666
1707
|
|
|
@@ -1697,7 +1738,7 @@ function writeEnvExample(projectDir, tracks) {
|
|
|
1697
1738
|
if (!tracks.some((t) => ENV_EXAMPLE_TRACKS.includes(t))) {
|
|
1698
1739
|
return false;
|
|
1699
1740
|
}
|
|
1700
|
-
const path =
|
|
1741
|
+
const path = join8(projectDir, ".env.example");
|
|
1701
1742
|
if (existsSync8(path)) {
|
|
1702
1743
|
return false;
|
|
1703
1744
|
}
|
|
@@ -1705,7 +1746,7 @@ function writeEnvExample(projectDir, tracks) {
|
|
|
1705
1746
|
return true;
|
|
1706
1747
|
}
|
|
1707
1748
|
function addGitignoreEnv(projectDir) {
|
|
1708
|
-
const path =
|
|
1749
|
+
const path = join8(projectDir, ".gitignore");
|
|
1709
1750
|
if (!existsSync8(path)) {
|
|
1710
1751
|
return false;
|
|
1711
1752
|
}
|
|
@@ -1723,7 +1764,7 @@ function addGitignoreEnv(projectDir) {
|
|
|
1723
1764
|
var AGENT_ARTIFACT_DIRS = [".factory/", ".goose/", ".uzys-agent-harness/"];
|
|
1724
1765
|
var GITIGNORE_AGENT_ARTIFACT_HEADER = "# agent CLI / harness \uC790\uB3D9 \uC0DD\uC131\uBB3C (auto-added by agent-harness)";
|
|
1725
1766
|
function addGitignoreAgentArtifacts(projectDir) {
|
|
1726
|
-
const path =
|
|
1767
|
+
const path = join8(projectDir, ".gitignore");
|
|
1727
1768
|
if (!existsSync8(path)) {
|
|
1728
1769
|
return [];
|
|
1729
1770
|
}
|
|
@@ -1748,7 +1789,7 @@ init_esm_shims();
|
|
|
1748
1789
|
import { spawnSync } from "child_process";
|
|
1749
1790
|
import { existsSync as existsSync9, readdirSync, readFileSync as readFileSync8 } from "fs";
|
|
1750
1791
|
import { homedir as homedir2 } from "os";
|
|
1751
|
-
import { join as
|
|
1792
|
+
import { join as join9 } from "path";
|
|
1752
1793
|
var DEFAULT_SPAWN_TIMEOUT_MS = 12e4;
|
|
1753
1794
|
function selectExternalTargets(assets, ctx) {
|
|
1754
1795
|
const conditionPassed = filterApplicableAssets(assets, ctx).filter(
|
|
@@ -1824,7 +1865,7 @@ function installOne(asset, ctx) {
|
|
|
1824
1865
|
cwd
|
|
1825
1866
|
);
|
|
1826
1867
|
case "shell-script": {
|
|
1827
|
-
const scriptPath =
|
|
1868
|
+
const scriptPath = join9(ctx.harnessRoot, method.script);
|
|
1828
1869
|
if (!existsSync9(scriptPath)) {
|
|
1829
1870
|
return {
|
|
1830
1871
|
asset,
|
|
@@ -1915,7 +1956,7 @@ function detectVersion(method, spawn) {
|
|
|
1915
1956
|
if (at2 <= 0) return void 0;
|
|
1916
1957
|
const plugin = method.pluginId.slice(0, at2);
|
|
1917
1958
|
const marketplaceShort = method.pluginId.slice(at2 + 1);
|
|
1918
|
-
const cacheBase =
|
|
1959
|
+
const cacheBase = join9(homedir2(), ".claude/plugins/cache", marketplaceShort, plugin);
|
|
1919
1960
|
if (!existsSync9(cacheBase)) return void 0;
|
|
1920
1961
|
const versions = readdirSync(cacheBase).filter((v2) => /^\d/.test(v2)).sort();
|
|
1921
1962
|
return versions.at(-1);
|
|
@@ -1923,7 +1964,7 @@ function detectVersion(method, spawn) {
|
|
|
1923
1964
|
case "npm": {
|
|
1924
1965
|
const npmRoot = getNpmGlobalRoot(spawn);
|
|
1925
1966
|
if (!npmRoot) return void 0;
|
|
1926
|
-
const pkgJson =
|
|
1967
|
+
const pkgJson = join9(npmRoot, method.pkg, "package.json");
|
|
1927
1968
|
if (!existsSync9(pkgJson)) return void 0;
|
|
1928
1969
|
const parsed = JSON.parse(readFileSync8(pkgJson, "utf8"));
|
|
1929
1970
|
return parsed.version;
|
|
@@ -2028,7 +2069,7 @@ function writeMcpJson(path, mcp) {
|
|
|
2028
2069
|
// src/superseded.ts
|
|
2029
2070
|
init_esm_shims();
|
|
2030
2071
|
import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
|
|
2031
|
-
import { join as
|
|
2072
|
+
import { join as join10 } from "path";
|
|
2032
2073
|
function sameTracks(a, b2) {
|
|
2033
2074
|
if (a.length !== b2.length) return false;
|
|
2034
2075
|
const sa = [...a].sort();
|
|
@@ -2052,7 +2093,7 @@ function findSuperseded(projectDir, spec, previous) {
|
|
|
2052
2093
|
if (!id) continue;
|
|
2053
2094
|
const key = policyKey(entry.target.slice(".claude/".length));
|
|
2054
2095
|
if (!key) continue;
|
|
2055
|
-
const abs =
|
|
2096
|
+
const abs = join10(projectDir, entry.target);
|
|
2056
2097
|
if (!existsSync11(abs)) continue;
|
|
2057
2098
|
if (!isHarnessOwned(baseline, key, readFileSync10(abs, "utf-8"))) continue;
|
|
2058
2099
|
out.push({ target: entry.target, id, source: entry.source });
|
|
@@ -2065,14 +2106,13 @@ init_esm_shims();
|
|
|
2065
2106
|
import {
|
|
2066
2107
|
copyFileSync,
|
|
2067
2108
|
existsSync as existsSync12,
|
|
2068
|
-
lstatSync,
|
|
2069
2109
|
mkdirSync as mkdirSync3,
|
|
2070
2110
|
readdirSync as readdirSync2,
|
|
2071
2111
|
readFileSync as readFileSync11,
|
|
2072
2112
|
unlinkSync,
|
|
2073
2113
|
writeFileSync as writeFileSync5
|
|
2074
2114
|
} from "fs";
|
|
2075
|
-
import { basename as basename4, dirname as dirname3, join as
|
|
2115
|
+
import { basename as basename4, dirname as dirname3, join as join11 } from "path";
|
|
2076
2116
|
var LEGACY_ANCHOR_FILE = ".claude/CLAUDE.md";
|
|
2077
2117
|
function buildUpdateSpec(projectDir, tracks) {
|
|
2078
2118
|
return {
|
|
@@ -2083,7 +2123,7 @@ function buildUpdateSpec(projectDir, tracks) {
|
|
|
2083
2123
|
};
|
|
2084
2124
|
}
|
|
2085
2125
|
function runUpdateMode(projectDir, templatesDir, harnessRoot) {
|
|
2086
|
-
const claudeDir =
|
|
2126
|
+
const claudeDir = join11(projectDir, ".claude");
|
|
2087
2127
|
const report = {
|
|
2088
2128
|
updated: {},
|
|
2089
2129
|
pruned: {},
|
|
@@ -2097,6 +2137,7 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot) {
|
|
|
2097
2137
|
policyBackedUp: [],
|
|
2098
2138
|
externalUpdated: 0,
|
|
2099
2139
|
externalBackedUp: [],
|
|
2140
|
+
foreignOwned: [],
|
|
2100
2141
|
installedNew: [],
|
|
2101
2142
|
restored: [],
|
|
2102
2143
|
needsReinstall: [],
|
|
@@ -2108,8 +2149,8 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot) {
|
|
|
2108
2149
|
report.needsReinstall = fresh.needsReinstall;
|
|
2109
2150
|
const policyBase = policyBaseline(projectDir);
|
|
2110
2151
|
for (const { dir, ext } of POLICY_DIRS) {
|
|
2111
|
-
const target =
|
|
2112
|
-
const source =
|
|
2152
|
+
const target = join11(claudeDir, dir);
|
|
2153
|
+
const source = join11(templatesDir, dir);
|
|
2113
2154
|
const label = `.claude/${dir}`;
|
|
2114
2155
|
const ctx = { prefix: dir, baseline: policyBase };
|
|
2115
2156
|
const synced = updateDir(target, source, ext, ctx);
|
|
@@ -2119,9 +2160,11 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot) {
|
|
|
2119
2160
|
}
|
|
2120
2161
|
refreshPolicyBaseline(projectDir, templatesDir);
|
|
2121
2162
|
const skillSync = syncSkills(
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
skillBaseline(projectDir)
|
|
2163
|
+
join11(claudeDir, "skills"),
|
|
2164
|
+
join11(templatesDir, "skills"),
|
|
2165
|
+
skillBaseline(projectDir),
|
|
2166
|
+
/* @__PURE__ */ new Date(),
|
|
2167
|
+
(relInSkills) => foreignOwnedTarget(projectDir, `.claude/skills/${relInSkills}`)
|
|
2125
2168
|
);
|
|
2126
2169
|
report.updated[".claude/skills"] = skillSync.updated;
|
|
2127
2170
|
report.skillsBackedUp = skillSync.backedUp;
|
|
@@ -2131,7 +2174,7 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot) {
|
|
|
2131
2174
|
if (existsSync12(claudeDir) && (installLog === null || installLog.spec.cli.includes("claude"))) {
|
|
2132
2175
|
syncHarnessAnchor(projectDir, templatesDir, report);
|
|
2133
2176
|
}
|
|
2134
|
-
const settingsPath =
|
|
2177
|
+
const settingsPath = join11(claudeDir, "settings.json");
|
|
2135
2178
|
if (existsSync12(settingsPath)) {
|
|
2136
2179
|
report.staleHookRefs = cleanStaleHookRefs(settingsPath, claudeDir);
|
|
2137
2180
|
}
|
|
@@ -2139,12 +2182,23 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot) {
|
|
|
2139
2182
|
const external = refreshExternalCli(projectDir, harnessRoot);
|
|
2140
2183
|
report.externalUpdated = external.externalUpdated;
|
|
2141
2184
|
report.externalBackedUp = external.externalBackedUp;
|
|
2185
|
+
const saidBySlotRow = new Set(report.skillsSkippedLinks.map((id) => `.claude/skills/${id}`));
|
|
2186
|
+
const merged = [];
|
|
2187
|
+
for (const f of [
|
|
2188
|
+
...skillSync.foreignOwned,
|
|
2189
|
+
...fresh.foreignOwned,
|
|
2190
|
+
...external.externalForeignOwned
|
|
2191
|
+
]) {
|
|
2192
|
+
if (!saidBySlotRow.has(f) && !merged.includes(f)) merged.push(f);
|
|
2193
|
+
}
|
|
2194
|
+
report.foreignOwned = merged;
|
|
2142
2195
|
return report;
|
|
2143
2196
|
}
|
|
2144
2197
|
function installNewAssets(projectDir, templatesDir) {
|
|
2145
2198
|
const installed = [];
|
|
2146
2199
|
const restored = [];
|
|
2147
2200
|
const needsReinstall = [];
|
|
2201
|
+
const foreignOwned = [];
|
|
2148
2202
|
const log = readInstallLog(projectDir);
|
|
2149
2203
|
const claudeSelected = log?.spec.cli.includes("claude") ?? false;
|
|
2150
2204
|
const baselineExcluded = new Set(log?.spec.baselineExclude ?? []);
|
|
@@ -2152,9 +2206,14 @@ function installNewAssets(projectDir, templatesDir) {
|
|
|
2152
2206
|
for (const entry of trackOnlyFileAssets(installedTracks(projectDir))) {
|
|
2153
2207
|
if (entry.target === HARNESS_ANCHOR_FILE) continue;
|
|
2154
2208
|
if (isBaselineExcluded(entry.target, baselineExcluded)) continue;
|
|
2155
|
-
const target =
|
|
2209
|
+
const target = join11(projectDir, entry.target);
|
|
2210
|
+
const foreign = foreignOwnedTarget(projectDir, entry.target);
|
|
2211
|
+
if (foreign !== null) {
|
|
2212
|
+
if (!foreignOwned.includes(foreign)) foreignOwned.push(foreign);
|
|
2213
|
+
continue;
|
|
2214
|
+
}
|
|
2156
2215
|
if (existsSync12(target)) continue;
|
|
2157
|
-
const source =
|
|
2216
|
+
const source = join11(templatesDir, entry.source);
|
|
2158
2217
|
if (!existsSync12(source)) continue;
|
|
2159
2218
|
if (entry.target.startsWith(".claude/")) {
|
|
2160
2219
|
if (!claudeSelected) continue;
|
|
@@ -2169,7 +2228,7 @@ function installNewAssets(projectDir, templatesDir) {
|
|
|
2169
2228
|
if (priorBaseline.has(recordedAs)) restored.push(entry.target);
|
|
2170
2229
|
else installed.push(entry.target);
|
|
2171
2230
|
}
|
|
2172
|
-
return { installed, restored, needsReinstall };
|
|
2231
|
+
return { installed, restored, needsReinstall, foreignOwned };
|
|
2173
2232
|
}
|
|
2174
2233
|
function trackOnlyFileAssets(tracks) {
|
|
2175
2234
|
const base = { tracks, selectedInternalSkills: [] };
|
|
@@ -2180,12 +2239,12 @@ function trackOnlyFileAssets(tracks) {
|
|
|
2180
2239
|
);
|
|
2181
2240
|
}
|
|
2182
2241
|
function syncHarnessAnchor(projectDir, templatesDir, report) {
|
|
2183
|
-
if (existsSync12(
|
|
2242
|
+
if (existsSync12(join11(projectDir, LEGACY_ANCHOR_FILE))) {
|
|
2184
2243
|
report.legacyAnchor = LEGACY_ANCHOR_FILE;
|
|
2185
2244
|
}
|
|
2186
|
-
const templateMd =
|
|
2245
|
+
const templateMd = join11(templatesDir, "CLAUDE.md");
|
|
2187
2246
|
if (!existsSync12(templateMd)) return;
|
|
2188
|
-
const anchor =
|
|
2247
|
+
const anchor = join11(projectDir, HARNESS_ANCHOR_FILE);
|
|
2189
2248
|
const existed = existsSync12(anchor);
|
|
2190
2249
|
copyFileSync(templateMd, anchor);
|
|
2191
2250
|
if (existed) {
|
|
@@ -2197,7 +2256,7 @@ function syncHarnessAnchor(projectDir, templatesDir, report) {
|
|
|
2197
2256
|
recordAnchorBaseline(projectDir, anchor);
|
|
2198
2257
|
}
|
|
2199
2258
|
function upsertRootImport(projectDir) {
|
|
2200
|
-
const target =
|
|
2259
|
+
const target = join11(projectDir, "CLAUDE.md");
|
|
2201
2260
|
const existing = existsSync12(target) ? readFileSync11(target, "utf8") : null;
|
|
2202
2261
|
const next = upsertHarnessImport(existing, {
|
|
2203
2262
|
projectName: basename4(projectDir),
|
|
@@ -2254,7 +2313,8 @@ function refreshExternalCli(projectDir, harnessRoot) {
|
|
|
2254
2313
|
}
|
|
2255
2314
|
return {
|
|
2256
2315
|
externalUpdated: result.externalUpdated,
|
|
2257
|
-
externalBackedUp: result.externalBackedUp
|
|
2316
|
+
externalBackedUp: result.externalBackedUp,
|
|
2317
|
+
externalForeignOwned: result.externalForeignOwned
|
|
2258
2318
|
};
|
|
2259
2319
|
}
|
|
2260
2320
|
function isHarnessOwned2(ctx, rel, current) {
|
|
@@ -2266,8 +2326,8 @@ function updateDir(target, source, ext, ctx, now = /* @__PURE__ */ new Date()) {
|
|
|
2266
2326
|
const backedUp = [];
|
|
2267
2327
|
for (const file of readdirSync2(target)) {
|
|
2268
2328
|
if (!file.endsWith(ext)) continue;
|
|
2269
|
-
const targetFile =
|
|
2270
|
-
const sourceFile =
|
|
2329
|
+
const targetFile = join11(target, file);
|
|
2330
|
+
const sourceFile = join11(source, file);
|
|
2271
2331
|
if (!existsSync12(sourceFile)) continue;
|
|
2272
2332
|
const next = readFileSync11(sourceFile, "utf8");
|
|
2273
2333
|
const current = readFileSync11(targetFile, "utf8");
|
|
@@ -2281,23 +2341,29 @@ function updateDir(target, source, ext, ctx, now = /* @__PURE__ */ new Date()) {
|
|
|
2281
2341
|
}
|
|
2282
2342
|
return { updated, backedUp };
|
|
2283
2343
|
}
|
|
2284
|
-
function syncSkills(targetDir, sourceDir, baseline, now = /* @__PURE__ */ new Date()) {
|
|
2344
|
+
function syncSkills(targetDir, sourceDir, baseline, now = /* @__PURE__ */ new Date(), foreignOf) {
|
|
2285
2345
|
if (!existsSync12(targetDir) || !existsSync12(sourceDir))
|
|
2286
|
-
return { updated: 0, backedUp: [], skippedLinks: [] };
|
|
2346
|
+
return { updated: 0, backedUp: [], skippedLinks: [], foreignOwned: [] };
|
|
2287
2347
|
let updated = 0;
|
|
2288
2348
|
const backedUp = [];
|
|
2289
2349
|
const skippedLinks = [];
|
|
2350
|
+
const foreignOwned = [];
|
|
2290
2351
|
for (const skill of readdirSync2(sourceDir, { withFileTypes: true })) {
|
|
2291
2352
|
if (!skill.isDirectory()) continue;
|
|
2292
|
-
const targetSkill =
|
|
2293
|
-
if (
|
|
2294
|
-
if (lstatSync(targetSkill).isSymbolicLink()) {
|
|
2353
|
+
const targetSkill = join11(targetDir, skill.name);
|
|
2354
|
+
if (occupiedByNonDirectory(targetSkill)) {
|
|
2295
2355
|
skippedLinks.push(skill.name);
|
|
2296
2356
|
continue;
|
|
2297
2357
|
}
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
const
|
|
2358
|
+
if (!existsSync12(targetSkill)) continue;
|
|
2359
|
+
for (const rel of listFilesRecursive(join11(sourceDir, skill.name))) {
|
|
2360
|
+
const targetFile = join11(targetSkill, rel);
|
|
2361
|
+
const foreign = foreignOf(`${skill.name}/${rel}`);
|
|
2362
|
+
if (foreign !== null) {
|
|
2363
|
+
if (!foreignOwned.includes(foreign)) foreignOwned.push(foreign);
|
|
2364
|
+
continue;
|
|
2365
|
+
}
|
|
2366
|
+
const next = readFileSync11(join11(sourceDir, skill.name, rel), "utf8");
|
|
2301
2367
|
if (!existsSync12(targetFile)) {
|
|
2302
2368
|
mkdirSync3(dirname3(targetFile), { recursive: true });
|
|
2303
2369
|
writeFileSync5(targetFile, next);
|
|
@@ -2315,7 +2381,7 @@ function syncSkills(targetDir, sourceDir, baseline, now = /* @__PURE__ */ new Da
|
|
|
2315
2381
|
updated++;
|
|
2316
2382
|
}
|
|
2317
2383
|
}
|
|
2318
|
-
return { updated, backedUp, skippedLinks };
|
|
2384
|
+
return { updated, backedUp, skippedLinks, foreignOwned };
|
|
2319
2385
|
}
|
|
2320
2386
|
function skillBaseline(projectDir) {
|
|
2321
2387
|
const log = readInstallLog(projectDir);
|
|
@@ -2354,9 +2420,9 @@ function pruneOrphans(target, source, ext, ctx, now = /* @__PURE__ */ new Date()
|
|
|
2354
2420
|
const removed = [];
|
|
2355
2421
|
for (const file of readdirSync2(target)) {
|
|
2356
2422
|
if (!file.endsWith(ext)) continue;
|
|
2357
|
-
const sourceFile =
|
|
2423
|
+
const sourceFile = join11(source, file);
|
|
2358
2424
|
if (!existsSync12(sourceFile)) {
|
|
2359
|
-
const targetFile =
|
|
2425
|
+
const targetFile = join11(target, file);
|
|
2360
2426
|
if (!ctx.baseline.has(`${ctx.prefix}/${file}`)) continue;
|
|
2361
2427
|
try {
|
|
2362
2428
|
const current = readFileSync11(targetFile, "utf8");
|
|
@@ -2370,7 +2436,7 @@ function pruneOrphans(target, source, ext, ctx, now = /* @__PURE__ */ new Date()
|
|
|
2370
2436
|
return removed;
|
|
2371
2437
|
}
|
|
2372
2438
|
function retireMcpAllowlist(projectDir, now = /* @__PURE__ */ new Date()) {
|
|
2373
|
-
const target =
|
|
2439
|
+
const target = join11(projectDir, ".mcp-allowlist");
|
|
2374
2440
|
if (!existsSync12(target)) return null;
|
|
2375
2441
|
try {
|
|
2376
2442
|
const backup = backupFile(target, now);
|
|
@@ -2410,7 +2476,7 @@ function cleanStaleHookRefs(settingsPath, claudeDir) {
|
|
|
2410
2476
|
function keepHookRef(hook, claudeDir, removed) {
|
|
2411
2477
|
const relPath = projectAnchoredRef(hook?.command ?? "", claudeDir);
|
|
2412
2478
|
if (relPath === void 0) return true;
|
|
2413
|
-
const exists = existsSync12(
|
|
2479
|
+
const exists = existsSync12(join11(claudeDir, relPath));
|
|
2414
2480
|
if (!exists && !removed.includes(relPath)) removed.push(relPath);
|
|
2415
2481
|
return exists;
|
|
2416
2482
|
}
|
|
@@ -2437,11 +2503,11 @@ function projectAnchoredRef(command, claudeDir) {
|
|
|
2437
2503
|
function runInstall(ctx) {
|
|
2438
2504
|
const { harnessRoot, projectDir, spec } = ctx;
|
|
2439
2505
|
const mode = ctx.mode ?? "fresh";
|
|
2440
|
-
const templatesDir =
|
|
2506
|
+
const templatesDir = join12(harnessRoot, "templates");
|
|
2441
2507
|
if (!existsSync13(templatesDir)) {
|
|
2442
2508
|
throw new Error(`Templates dir not found: ${templatesDir}`);
|
|
2443
2509
|
}
|
|
2444
|
-
const claudeDir =
|
|
2510
|
+
const claudeDir = join12(projectDir, ".claude");
|
|
2445
2511
|
const previousLog = readInstallLog(projectDir);
|
|
2446
2512
|
const claudeWasSelected = previousLog?.spec.cli.includes("claude") ?? false;
|
|
2447
2513
|
if (mode === "update" && !existsSync13(claudeDir) && (previousLog === null || claudeWasSelected)) {
|
|
@@ -2474,6 +2540,7 @@ function runInstall(ctx) {
|
|
|
2474
2540
|
externalBackups,
|
|
2475
2541
|
externalUpdated: _externalUpdated,
|
|
2476
2542
|
externalBackedUp: _externalBackedUp,
|
|
2543
|
+
externalForeignOwned,
|
|
2477
2544
|
...cliTransforms
|
|
2478
2545
|
} = runCliTransforms({
|
|
2479
2546
|
harnessRoot,
|
|
@@ -2507,6 +2574,11 @@ function runInstall(ctx) {
|
|
|
2507
2574
|
backups: [...base.backups, ...externalBackups],
|
|
2508
2575
|
baselineExcluded: base.excluded,
|
|
2509
2576
|
baselineExcludedOnDisk: base.excludedOnDisk,
|
|
2577
|
+
// `.claude/` baseline 과 외부 CLI 산출물의 같은 판정을 **한 목록으로** 낸다.
|
|
2578
|
+
baselineForeignOwned: [
|
|
2579
|
+
...base.foreignOwned,
|
|
2580
|
+
...externalForeignOwned.filter((f) => !base.foreignOwned.includes(f))
|
|
2581
|
+
],
|
|
2510
2582
|
// 밀려난 자산 정리는 **manifest 복사가 끝난 뒤**에 한다. 앞에서 지우면 이번 설치가 다시
|
|
2511
2583
|
// 깔 수도 있는 파일을 지웠다 되돌리는 셈이라, 무엇이 최종 상태인지 보고가 흐려진다.
|
|
2512
2584
|
superseded: applySupersededCleanup(spec, projectDir, supersededFound)
|
|
@@ -2532,7 +2604,7 @@ function applySupersededCleanup(spec, projectDir, found) {
|
|
|
2532
2604
|
const kept = [];
|
|
2533
2605
|
for (const asset of found) {
|
|
2534
2606
|
try {
|
|
2535
|
-
unlinkSync2(
|
|
2607
|
+
unlinkSync2(join12(projectDir, asset.target));
|
|
2536
2608
|
removed.push(asset.target);
|
|
2537
2609
|
} catch {
|
|
2538
2610
|
kept.push(asset.target);
|
|
@@ -2542,9 +2614,9 @@ function applySupersededCleanup(spec, projectDir, found) {
|
|
|
2542
2614
|
}
|
|
2543
2615
|
function healStaleHookRefs(spec, projectDir) {
|
|
2544
2616
|
if (!spec.cli.includes("claude")) return [];
|
|
2545
|
-
const settingsPath =
|
|
2617
|
+
const settingsPath = join12(projectDir, ".claude/settings.json");
|
|
2546
2618
|
if (!existsSync13(settingsPath)) return [];
|
|
2547
|
-
return cleanStaleHookRefs(settingsPath,
|
|
2619
|
+
return cleanStaleHookRefs(settingsPath, join12(projectDir, ".claude"));
|
|
2548
2620
|
}
|
|
2549
2621
|
function resolveBackupPath(ctx, mode, claudeDir) {
|
|
2550
2622
|
const wantBackup = ctx.backup ?? (mode === "update" || mode === "reinstall");
|
|
@@ -2559,6 +2631,7 @@ function runUpdateInstall(ctx, templatesDir, backupPath) {
|
|
|
2559
2631
|
skipped: 0,
|
|
2560
2632
|
baselineExcluded: [],
|
|
2561
2633
|
baselineExcludedOnDisk: [],
|
|
2634
|
+
baselineForeignOwned: [],
|
|
2562
2635
|
// update 는 밀려난 자산을 다루지 않는다 — 그 판정은 사용자가 옵션을 고르는 설치 경로의
|
|
2563
2636
|
// 것이고, update 는 무엇을 골랐는지 다시 묻지 않는다.
|
|
2564
2637
|
superseded: { removed: [], kept: [] },
|
|
@@ -2613,7 +2686,8 @@ function emptyClaudeBaseline() {
|
|
|
2613
2686
|
rootClaudeMdLog: null,
|
|
2614
2687
|
backups: [],
|
|
2615
2688
|
excluded: [],
|
|
2616
|
-
excludedOnDisk: []
|
|
2689
|
+
excludedOnDisk: [],
|
|
2690
|
+
foreignOwned: []
|
|
2617
2691
|
};
|
|
2618
2692
|
}
|
|
2619
2693
|
function installCliNeutralAssets(manifestSpec, projectDir, templatesDir, baselineExcluded) {
|
|
@@ -2625,12 +2699,12 @@ function installCliNeutralAssets(manifestSpec, projectDir, templatesDir, baselin
|
|
|
2625
2699
|
continue;
|
|
2626
2700
|
}
|
|
2627
2701
|
if (!isCliNeutralTarget(entry.target)) continue;
|
|
2628
|
-
const source =
|
|
2702
|
+
const source = join12(templatesDir, entry.source);
|
|
2629
2703
|
if (!existsSync13(source)) {
|
|
2630
2704
|
result.skipped += 1;
|
|
2631
2705
|
continue;
|
|
2632
2706
|
}
|
|
2633
|
-
copyFile(source,
|
|
2707
|
+
copyFile(source, join12(projectDir, entry.target));
|
|
2634
2708
|
result.filesCopied += 1;
|
|
2635
2709
|
}
|
|
2636
2710
|
return result;
|
|
@@ -2657,17 +2731,22 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, policyBas
|
|
|
2657
2731
|
if (!entry.applies(manifestSpec)) {
|
|
2658
2732
|
continue;
|
|
2659
2733
|
}
|
|
2660
|
-
const target =
|
|
2734
|
+
const target = join12(projectDir, entry.target);
|
|
2661
2735
|
if (isBaselineExcluded(entry.target, baselineExcluded)) {
|
|
2662
2736
|
result.excluded.push(entry.target);
|
|
2663
2737
|
if (existsSync13(target)) result.excludedOnDisk.push(entry.target);
|
|
2664
2738
|
continue;
|
|
2665
2739
|
}
|
|
2666
|
-
const source =
|
|
2740
|
+
const source = join12(templatesDir, entry.source);
|
|
2667
2741
|
if (!existsSync13(source)) {
|
|
2668
2742
|
result.skipped += 1;
|
|
2669
2743
|
continue;
|
|
2670
2744
|
}
|
|
2745
|
+
const foreign = foreignOwnedTarget(projectDir, entry.target);
|
|
2746
|
+
if (foreign !== null) {
|
|
2747
|
+
if (!result.foreignOwned.includes(foreign)) result.foreignOwned.push(foreign);
|
|
2748
|
+
continue;
|
|
2749
|
+
}
|
|
2671
2750
|
if (entry.type === "file") {
|
|
2672
2751
|
if (entry.target === ".claude/settings.json") {
|
|
2673
2752
|
const backup = backupFileIfChanged(target, readFileSync12(source, "utf-8"));
|
|
@@ -2683,12 +2762,18 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, policyBas
|
|
|
2683
2762
|
copyFile(source, target);
|
|
2684
2763
|
result.filesCopied += 1;
|
|
2685
2764
|
} else {
|
|
2686
|
-
copyDir(
|
|
2765
|
+
for (const foreign2 of copyDir(
|
|
2766
|
+
source,
|
|
2767
|
+
target,
|
|
2768
|
+
(relFile) => foreignOwnedTarget(projectDir, `${entry.target}/${relFile}`)
|
|
2769
|
+
)) {
|
|
2770
|
+
if (!result.foreignOwned.includes(foreign2)) result.foreignOwned.push(foreign2);
|
|
2771
|
+
}
|
|
2687
2772
|
result.dirsCopied += 1;
|
|
2688
2773
|
}
|
|
2689
2774
|
accumulateCategory(result.categories, entry);
|
|
2690
2775
|
}
|
|
2691
|
-
const hookDir =
|
|
2776
|
+
const hookDir = join12(projectDir, ".claude/hooks");
|
|
2692
2777
|
if (existsSync13(hookDir)) {
|
|
2693
2778
|
chmodHooksSync(hookDir);
|
|
2694
2779
|
}
|
|
@@ -2699,7 +2784,7 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, policyBas
|
|
|
2699
2784
|
return result;
|
|
2700
2785
|
}
|
|
2701
2786
|
function harnessAnchorLog(projectDir) {
|
|
2702
|
-
const anchor =
|
|
2787
|
+
const anchor = join12(projectDir, HARNESS_ANCHOR_FILE);
|
|
2703
2788
|
if (!existsSync13(anchor)) return null;
|
|
2704
2789
|
return { path: HARNESS_ANCHOR_FILE, sha256: hashContent(readFileSync12(anchor, "utf-8")) };
|
|
2705
2790
|
}
|
|
@@ -2760,7 +2845,7 @@ function writeInstallLogSafe(ctx, externalFiles, external, rootClaudeMdLog, prev
|
|
|
2760
2845
|
rootFiles
|
|
2761
2846
|
);
|
|
2762
2847
|
const skillFiles = collectSkillHashes(ctx.projectDir);
|
|
2763
|
-
const policyFiles = collectPolicyHashes(ctx.projectDir,
|
|
2848
|
+
const policyFiles = collectPolicyHashes(ctx.projectDir, join12(ctx.harnessRoot, "templates"));
|
|
2764
2849
|
const merged = mergeExternalFiles(ctx.projectDir, previousLog?.externalFiles, externalFiles);
|
|
2765
2850
|
writeInstallLog(ctx.projectDir, {
|
|
2766
2851
|
...log,
|
|
@@ -2776,11 +2861,11 @@ function writeInstallLogSafe(ctx, externalFiles, external, rootClaudeMdLog, prev
|
|
|
2776
2861
|
}
|
|
2777
2862
|
}
|
|
2778
2863
|
function composeAndWriteMcp(harnessRoot, projectDir, spec) {
|
|
2779
|
-
const mcpPath =
|
|
2864
|
+
const mcpPath = join12(projectDir, ".mcp.json");
|
|
2780
2865
|
const created = !existsSync13(mcpPath);
|
|
2781
2866
|
const composed = composeMcpJson({
|
|
2782
|
-
templateMcpPath:
|
|
2783
|
-
trackMapPath:
|
|
2867
|
+
templateMcpPath: join12(harnessRoot, "templates/mcp.json"),
|
|
2868
|
+
trackMapPath: join12(harnessRoot, "templates/track-mcp-map.tsv"),
|
|
2784
2869
|
existingPath: mcpPath,
|
|
2785
2870
|
tracks: spec.tracks
|
|
2786
2871
|
});
|
|
@@ -2833,14 +2918,14 @@ function accumulateCategory(cats, entry) {
|
|
|
2833
2918
|
}
|
|
2834
2919
|
}
|
|
2835
2920
|
function writeInstalledTracks(projectDir, tracks) {
|
|
2836
|
-
const path =
|
|
2921
|
+
const path = join12(projectDir, ".claude/.installed-tracks");
|
|
2837
2922
|
mkdirSync4(dirname4(path), { recursive: true });
|
|
2838
2923
|
const sorted = [...new Set(tracks)].sort().join("\n");
|
|
2839
2924
|
writeFileSync6(path, `${sorted}
|
|
2840
2925
|
`);
|
|
2841
2926
|
}
|
|
2842
2927
|
function writeRootClaudeMd(projectDir, tracks) {
|
|
2843
|
-
const target =
|
|
2928
|
+
const target = join12(projectDir, "CLAUDE.md");
|
|
2844
2929
|
const existing = existsSync13(target) ? readFileSync12(target, "utf-8") : null;
|
|
2845
2930
|
const content = upsertHarnessImport(existing, { projectName: basename5(projectDir), tracks });
|
|
2846
2931
|
if (content !== existing) {
|
|
@@ -3205,8 +3290,17 @@ function renderPhase1Rows(log, baseline, verbose = false, withEcc = false, claud
|
|
|
3205
3290
|
log(
|
|
3206
3291
|
assetRow(
|
|
3207
3292
|
"skip",
|
|
3208
|
-
".claude/skills
|
|
3209
|
-
`${baseline.updateMode.skillsSkippedLinks.join(", ")} \xB7
|
|
3293
|
+
".claude/skills owned by another tool",
|
|
3294
|
+
`${baseline.updateMode.skillsSkippedLinks.join(", ")} \xB7 \uADF8 \uC790\uB9AC\uAC00 \uC6B0\uB9AC \uB514\uB809\uD130\uB9AC\uAC00 \uC544\uB2C8\uB77C \uAC31\uC2E0\uD558\uC9C0 \uC54A\uC558\uB2E4`
|
|
3295
|
+
)
|
|
3296
|
+
);
|
|
3297
|
+
}
|
|
3298
|
+
if (baseline.updateMode.foreignOwned.length > 0) {
|
|
3299
|
+
log(
|
|
3300
|
+
assetRow(
|
|
3301
|
+
"skip",
|
|
3302
|
+
"owned by another tool",
|
|
3303
|
+
`${baseline.updateMode.foreignOwned.join(", ")} \xB7 \uADF8 \uC790\uB9AC\uAC00 \uC6B0\uB9AC \uAC83\uC774 \uC544\uB2C8\uB77C \uAC74\uB4DC\uB9AC\uC9C0 \uC54A\uC558\uB2E4`
|
|
3210
3304
|
)
|
|
3211
3305
|
);
|
|
3212
3306
|
}
|
|
@@ -3326,6 +3420,15 @@ function renderPhase1Rows(log, baseline, verbose = false, withEcc = false, claud
|
|
|
3326
3420
|
)
|
|
3327
3421
|
);
|
|
3328
3422
|
}
|
|
3423
|
+
if (baseline.baselineForeignOwned.length > 0) {
|
|
3424
|
+
log(
|
|
3425
|
+
assetRow(
|
|
3426
|
+
"skip",
|
|
3427
|
+
"owned by another tool",
|
|
3428
|
+
`${baseline.baselineForeignOwned.length} \u2014 ${baseline.baselineForeignOwned.join(", ")} \xB7 \uADF8 \uC790\uB9AC\uAC00 \uC6B0\uB9AC \uAC83\uC774 \uC544\uB2C8\uB77C \uAC74\uB4DC\uB9AC\uC9C0 \uC54A\uC558\uB2E4 \xB7 \uD558\uB124\uC2A4 \uD310\uBCF8\uC744 \uBC1B\uC73C\uB824\uBA74 \uADF8 \uC790\uB9AC\uB97C \uC62E\uAE30\uACE0 \uC7AC\uC124\uCE58`
|
|
3429
|
+
)
|
|
3430
|
+
);
|
|
3431
|
+
}
|
|
3329
3432
|
if (baseline.superseded.removed.length > 0) {
|
|
3330
3433
|
log(
|
|
3331
3434
|
assetRow(
|
|
@@ -3381,9 +3484,7 @@ function renderPhase1Rows(log, baseline, verbose = false, withEcc = false, claud
|
|
|
3381
3484
|
log(assetRow("success", ".mcp.json", mcpList, TEMPLATES_COL));
|
|
3382
3485
|
if (!withEcc && baseline.categories) {
|
|
3383
3486
|
log("");
|
|
3384
|
-
log(
|
|
3385
|
-
` ${c.dim("\xB7")} ${c.dim("ECC plugin not selected \u2014 cherry-pick fallback active (up to 4 agents + 8 skills + 3 commands)")}`
|
|
3386
|
-
);
|
|
3487
|
+
log(` ${c.dim("\xB7")} ${c.dim("ECC plugin not selected \u2014 cherry-pick fallback active")}`);
|
|
3387
3488
|
if (claudeSelected) {
|
|
3388
3489
|
log(` ${c.dim("\xB7")} ${c.dim("Use --with ecc-plugin to install ECC plugin instead")}`);
|
|
3389
3490
|
}
|
|
@@ -3626,7 +3727,7 @@ function registerInstallCommand(cli2) {
|
|
|
3626
3727
|
// src/commands/list.ts
|
|
3627
3728
|
init_esm_shims();
|
|
3628
3729
|
import { existsSync as existsSync14, readFileSync as readFileSync13 } from "fs";
|
|
3629
|
-
import { join as
|
|
3730
|
+
import { join as join13, resolve as resolve3 } from "path";
|
|
3630
3731
|
function listAction(options = {}, deps = {}) {
|
|
3631
3732
|
const log = deps.log ?? console.log;
|
|
3632
3733
|
const err = deps.err ?? console.error;
|
|
@@ -3689,7 +3790,7 @@ function formatTemplateRows(log, projectDir) {
|
|
|
3689
3790
|
const rows = [` ${c.dim(dirs.join(" "))}`];
|
|
3690
3791
|
const rootMd = log.templates.rootClaudeMd;
|
|
3691
3792
|
if (rootMd) {
|
|
3692
|
-
const path =
|
|
3793
|
+
const path = join13(projectDir, rootMd.path);
|
|
3693
3794
|
const modified = existsSync14(path) && hashContent(readFileSync13(path, "utf8")) !== rootMd.sha256;
|
|
3694
3795
|
rows.push(
|
|
3695
3796
|
modified ? ` ${c.dim(rootMd.path)} ${c.yellow("(\uC218\uC815\uB428 \u2014 uninstall \uC2DC \uBCF4\uC874)")}` : ` ${c.dim(rootMd.path)}`
|
|
@@ -3698,7 +3799,7 @@ function formatTemplateRows(log, projectDir) {
|
|
|
3698
3799
|
return rows;
|
|
3699
3800
|
}
|
|
3700
3801
|
function formatRootFileRows(rootFiles, projectDir) {
|
|
3701
|
-
const present = rootFiles.filter((f) => existsSync14(
|
|
3802
|
+
const present = rootFiles.filter((f) => existsSync14(join13(projectDir, f.path)));
|
|
3702
3803
|
const width = Math.max(0, ...present.map((f) => f.path.length));
|
|
3703
3804
|
return present.map(
|
|
3704
3805
|
(f) => ` ${c.dim(padDisplay(f.path, width))} ${c.dim(f.change === "created" ? "\uC0DD\uC131" : "\uBCD1\uD569")} ${c.dim(f.notes.join(" / "))}`
|
|
@@ -3716,7 +3817,7 @@ function registerListCommand(cli2) {
|
|
|
3716
3817
|
init_esm_shims();
|
|
3717
3818
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
3718
3819
|
import { existsSync as existsSync15, readFileSync as readFileSync14, rmSync, writeFileSync as writeFileSync7 } from "fs";
|
|
3719
|
-
import { join as
|
|
3820
|
+
import { join as join14, resolve as resolve4 } from "path";
|
|
3720
3821
|
|
|
3721
3822
|
// src/uninstall-interactive.ts
|
|
3722
3823
|
init_esm_shims();
|
|
@@ -4951,7 +5052,7 @@ function settleLog(ctx, io) {
|
|
|
4951
5052
|
return true;
|
|
4952
5053
|
}
|
|
4953
5054
|
} else {
|
|
4954
|
-
io.rm(
|
|
5055
|
+
io.rm(join14(projectDir, INSTALL_LOG_DIR));
|
|
4955
5056
|
io.rm(legacyInstallLogPath(projectDir));
|
|
4956
5057
|
io.log(` ${status.success("install log removed")}`);
|
|
4957
5058
|
}
|
|
@@ -5000,7 +5101,7 @@ function advisoryLines(plan, projectDir, rootFiles) {
|
|
|
5000
5101
|
return lines;
|
|
5001
5102
|
}
|
|
5002
5103
|
function rootFileAdvisoryLines(rootFiles, projectDir) {
|
|
5003
|
-
const present = rootFiles.filter((f) => existsSync15(
|
|
5104
|
+
const present = rootFiles.filter((f) => existsSync15(join14(projectDir, f.path)));
|
|
5004
5105
|
if (present.length === 0) return [];
|
|
5005
5106
|
return [
|
|
5006
5107
|
"",
|
|
@@ -5101,14 +5202,14 @@ function buildGlobalAdvisoryCmd(asset) {
|
|
|
5101
5202
|
}
|
|
5102
5203
|
}
|
|
5103
5204
|
function removeTemplates(log, projectDir, rm) {
|
|
5104
|
-
rm(
|
|
5105
|
-
if (log.templates.codexDir) rm(
|
|
5106
|
-
if (log.templates.opencodeDir) rm(
|
|
5205
|
+
rm(join14(projectDir, log.templates.claudeDir));
|
|
5206
|
+
if (log.templates.codexDir) rm(join14(projectDir, log.templates.codexDir));
|
|
5207
|
+
if (log.templates.opencodeDir) rm(join14(projectDir, log.templates.opencodeDir));
|
|
5107
5208
|
const importStripped = stripRootImport(projectDir);
|
|
5108
5209
|
const rootMd = log.templates.rootClaudeMd;
|
|
5109
5210
|
if (rootMd) {
|
|
5110
5211
|
if (rootClaudeMdModified(log, projectDir)) return { rootClaudeMdKept: true, importStripped };
|
|
5111
|
-
rm(
|
|
5212
|
+
rm(join14(projectDir, rootMd.path));
|
|
5112
5213
|
}
|
|
5113
5214
|
return { rootClaudeMdKept: false, importStripped };
|
|
5114
5215
|
}
|
|
@@ -5117,7 +5218,7 @@ function stripRootImport(projectDir) {
|
|
|
5117
5218
|
const stripped = current === null ? null : stripHarnessImport(current);
|
|
5118
5219
|
if (stripped === null) return false;
|
|
5119
5220
|
try {
|
|
5120
|
-
writeFileSync7(
|
|
5221
|
+
writeFileSync7(join14(projectDir, "CLAUDE.md"), stripped, "utf8");
|
|
5121
5222
|
return true;
|
|
5122
5223
|
} catch {
|
|
5123
5224
|
return false;
|
|
@@ -5128,7 +5229,7 @@ function hasRootImport(projectDir) {
|
|
|
5128
5229
|
return current !== null && stripHarnessImport(current) !== null;
|
|
5129
5230
|
}
|
|
5130
5231
|
function readRootClaudeMd(projectDir) {
|
|
5131
|
-
const path =
|
|
5232
|
+
const path = join14(projectDir, "CLAUDE.md");
|
|
5132
5233
|
if (!existsSync15(path)) return null;
|
|
5133
5234
|
try {
|
|
5134
5235
|
return readFileSync14(path, "utf8");
|
|
@@ -5139,7 +5240,7 @@ function readRootClaudeMd(projectDir) {
|
|
|
5139
5240
|
function rootClaudeMdModified(log, projectDir) {
|
|
5140
5241
|
const rootMd = log.templates.rootClaudeMd;
|
|
5141
5242
|
if (!rootMd) return false;
|
|
5142
|
-
const path =
|
|
5243
|
+
const path = join14(projectDir, rootMd.path);
|
|
5143
5244
|
if (!existsSync15(path)) return false;
|
|
5144
5245
|
return hashContent(readFileSync14(path, "utf8")) !== rootMd.sha256;
|
|
5145
5246
|
}
|
|
@@ -5202,7 +5303,7 @@ import { resolve as resolve5 } from "path";
|
|
|
5202
5303
|
// src/state.ts
|
|
5203
5304
|
init_esm_shims();
|
|
5204
5305
|
import { existsSync as existsSync16, readFileSync as readFileSync15 } from "fs";
|
|
5205
|
-
import { join as
|
|
5306
|
+
import { join as join15 } from "path";
|
|
5206
5307
|
var META_FILE = ".claude/.installed-tracks";
|
|
5207
5308
|
var LEGACY_SIGNATURES = [
|
|
5208
5309
|
{ rule: "htmx.md", track: "ssr-htmx" },
|
|
@@ -5212,10 +5313,10 @@ var LEGACY_SIGNATURES = [
|
|
|
5212
5313
|
{ rule: "cli-development.md", track: "tooling" }
|
|
5213
5314
|
];
|
|
5214
5315
|
function detectInstallState(projectDir) {
|
|
5215
|
-
const claudeDir =
|
|
5316
|
+
const claudeDir = join15(projectDir, ".claude");
|
|
5216
5317
|
const hasClaudeDir = existsSync16(claudeDir);
|
|
5217
5318
|
if (hasClaudeDir) {
|
|
5218
|
-
const metaPath =
|
|
5319
|
+
const metaPath = join15(projectDir, META_FILE);
|
|
5219
5320
|
if (existsSync16(metaPath)) {
|
|
5220
5321
|
const tracks2 = readMetafile(metaPath);
|
|
5221
5322
|
return { state: "existing", tracks: tracks2, source: "metafile", hasClaudeDir: true };
|
|
@@ -5249,13 +5350,13 @@ function readMetafile(path) {
|
|
|
5249
5350
|
return [...seen].sort();
|
|
5250
5351
|
}
|
|
5251
5352
|
function inferFromLegacySignatures(projectDir) {
|
|
5252
|
-
const rulesDir =
|
|
5353
|
+
const rulesDir = join15(projectDir, ".claude/rules");
|
|
5253
5354
|
if (!existsSync16(rulesDir)) {
|
|
5254
5355
|
return [];
|
|
5255
5356
|
}
|
|
5256
5357
|
const found = /* @__PURE__ */ new Set();
|
|
5257
5358
|
for (const sig of LEGACY_SIGNATURES) {
|
|
5258
|
-
if (existsSync16(
|
|
5359
|
+
if (existsSync16(join15(rulesDir, sig.rule))) {
|
|
5259
5360
|
found.add(sig.track);
|
|
5260
5361
|
}
|
|
5261
5362
|
}
|