@uzysjung/agent-harness 26.166.0 → 26.167.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 +9 -9
- package/README.md +9 -9
- package/dist/{chunk-AHAAF4HS.js → chunk-KVXCTS6D.js} +98 -81
- package/dist/chunk-KVXCTS6D.js.map +1 -0
- package/dist/index.js +661 -373
- package/dist/index.js.map +1 -1
- package/dist/trust-tier-drift.js +6 -3
- package/dist/trust-tier-drift.js.map +1 -1
- package/package.json +1 -1
- package/templates/codex/config.toml.template +0 -3
- package/templates/github-workflows/ci-node.yml +1 -1
- package/templates/github-workflows/ci-python.yml +1 -1
- package/templates/github-workflows/e2e.yml +1 -1
- package/templates/mcp.json +1 -11
- package/templates/rules/ui-components.md +13 -0
- package/templates/skills/gh-issue-workflow/SKILL.md +1 -1
- package/templates/track-mcp-map.tsv +10 -5
- package/dist/chunk-AHAAF4HS.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
INTERNAL_BUNDLED_SKILL_IDS,
|
|
18
18
|
LEGACY_INSTALL_LOG_DIR,
|
|
19
19
|
POLICY_DIRS,
|
|
20
|
+
REMOVED_CATALOG_ASSETS,
|
|
20
21
|
RENAMED_SKILL_IDS,
|
|
21
22
|
RETIRED_AGENTS,
|
|
22
23
|
RETIRED_AGENT_IDS,
|
|
@@ -102,7 +103,7 @@ import {
|
|
|
102
103
|
targetsInclude,
|
|
103
104
|
upsertHarnessImport,
|
|
104
105
|
writeInstallLog
|
|
105
|
-
} from "./chunk-
|
|
106
|
+
} from "./chunk-KVXCTS6D.js";
|
|
106
107
|
|
|
107
108
|
// node_modules/sisteransi/src/index.js
|
|
108
109
|
var require_src = __commonJS({
|
|
@@ -787,7 +788,7 @@ var cac = (name = "") => new CAC(name);
|
|
|
787
788
|
// src/commands/install.ts
|
|
788
789
|
init_esm_shims();
|
|
789
790
|
import { rmSync as rmSync4 } from "fs";
|
|
790
|
-
import { join as
|
|
791
|
+
import { join as join32, resolve as resolve2 } from "path";
|
|
791
792
|
import { fileURLToPath } from "url";
|
|
792
793
|
|
|
793
794
|
// src/adapters/index.ts
|
|
@@ -2992,8 +2993,8 @@ function padDisplay(s, width) {
|
|
|
2992
2993
|
|
|
2993
2994
|
// src/install-writes.ts
|
|
2994
2995
|
init_esm_shims();
|
|
2995
|
-
import { existsSync as
|
|
2996
|
-
import { dirname, join as
|
|
2996
|
+
import { existsSync as existsSync5, mkdirSync, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
2997
|
+
import { dirname, join as join6, relative, sep } from "path";
|
|
2997
2998
|
|
|
2998
2999
|
// src/env-files.ts
|
|
2999
3000
|
init_esm_shims();
|
|
@@ -3169,6 +3170,200 @@ function advisory(input) {
|
|
|
3169
3170
|
return j("create", LINES.scaffoldWrote, "advisory");
|
|
3170
3171
|
}
|
|
3171
3172
|
|
|
3173
|
+
// src/mcp-merge.ts
|
|
3174
|
+
init_esm_shims();
|
|
3175
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
3176
|
+
import { join as join5 } from "path";
|
|
3177
|
+
|
|
3178
|
+
// src/mcp-prior.ts
|
|
3179
|
+
init_esm_shims();
|
|
3180
|
+
var PRIOR_MCP_SERVERS = [
|
|
3181
|
+
{
|
|
3182
|
+
name: "github",
|
|
3183
|
+
value: { type: "stdio", command: "npx", args: ["-y", "@modelcontextprotocol/server-github"] },
|
|
3184
|
+
retired: "the old @modelcontextprotocol/server-github is deprecated upstream and ran without a token. The agent uses the gh CLI for GitHub; to use GitHub's official MCP instead: agent-harness install \u2026 --with github-mcp (needs GITHUB_PAT)"
|
|
3185
|
+
},
|
|
3186
|
+
{
|
|
3187
|
+
name: "railway-mcp-server",
|
|
3188
|
+
value: { type: "stdio", command: "npx", args: ["-y", "@railway/mcp-server"] },
|
|
3189
|
+
updated: "now Railway's remote server \u2014 sign in on first use, see NEXT"
|
|
3190
|
+
},
|
|
3191
|
+
{
|
|
3192
|
+
name: "chrome-devtools",
|
|
3193
|
+
value: { type: "stdio", command: "npx", args: ["-y", "chrome-devtools-mcp@latest"] },
|
|
3194
|
+
updated: "usage statistics off"
|
|
3195
|
+
}
|
|
3196
|
+
];
|
|
3197
|
+
function priorMcpValues(name) {
|
|
3198
|
+
return PRIOR_MCP_SERVERS.filter((p2) => p2.name === name).map((p2) => p2.value);
|
|
3199
|
+
}
|
|
3200
|
+
function retiredMcpNames() {
|
|
3201
|
+
return PRIOR_MCP_SERVERS.filter((p2) => p2.retired !== void 0).map((p2) => p2.name);
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
// src/mcp-merge.ts
|
|
3205
|
+
var ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
3206
|
+
function parseRemote(raw) {
|
|
3207
|
+
let v2;
|
|
3208
|
+
try {
|
|
3209
|
+
v2 = JSON.parse(raw);
|
|
3210
|
+
} catch {
|
|
3211
|
+
return null;
|
|
3212
|
+
}
|
|
3213
|
+
if (v2 === null || typeof v2 !== "object" || Array.isArray(v2)) return null;
|
|
3214
|
+
const { url, bearerEnv } = v2;
|
|
3215
|
+
if (typeof url !== "string" || !url.startsWith("https://")) return null;
|
|
3216
|
+
if (bearerEnv === void 0) return { type: "http", url };
|
|
3217
|
+
if (typeof bearerEnv !== "string" || !ENV_NAME.test(bearerEnv)) return null;
|
|
3218
|
+
return { type: "http", url, auth: { bearerEnv } };
|
|
3219
|
+
}
|
|
3220
|
+
function parseTrackMcpMap(raw) {
|
|
3221
|
+
const rows = [];
|
|
3222
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
3223
|
+
const trimmed = line.trim();
|
|
3224
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
3225
|
+
continue;
|
|
3226
|
+
}
|
|
3227
|
+
const parts = line.split(" ");
|
|
3228
|
+
if (parts.length < 4) {
|
|
3229
|
+
continue;
|
|
3230
|
+
}
|
|
3231
|
+
const [name, pattern, command, argsJson] = parts;
|
|
3232
|
+
if (!name || !pattern || !command) {
|
|
3233
|
+
continue;
|
|
3234
|
+
}
|
|
3235
|
+
if (command === "url") {
|
|
3236
|
+
const server = parseRemote(argsJson ?? "");
|
|
3237
|
+
if (server !== null) rows.push({ name, pattern, server });
|
|
3238
|
+
continue;
|
|
3239
|
+
}
|
|
3240
|
+
let args;
|
|
3241
|
+
try {
|
|
3242
|
+
args = JSON.parse(argsJson ?? "[]");
|
|
3243
|
+
} catch {
|
|
3244
|
+
continue;
|
|
3245
|
+
}
|
|
3246
|
+
if (!Array.isArray(args) || !args.every((a) => typeof a === "string")) {
|
|
3247
|
+
continue;
|
|
3248
|
+
}
|
|
3249
|
+
rows.push({ name, pattern, server: { type: "stdio", command, args } });
|
|
3250
|
+
}
|
|
3251
|
+
return rows;
|
|
3252
|
+
}
|
|
3253
|
+
function toClaudeServer(cfg) {
|
|
3254
|
+
if (cfg.type === "http") {
|
|
3255
|
+
return cfg.auth === void 0 ? { type: "http", url: cfg.url } : {
|
|
3256
|
+
type: "http",
|
|
3257
|
+
url: cfg.url,
|
|
3258
|
+
headers: { Authorization: `Bearer \${${cfg.auth.bearerEnv}}` }
|
|
3259
|
+
};
|
|
3260
|
+
}
|
|
3261
|
+
const out = {
|
|
3262
|
+
type: cfg.type ?? "stdio",
|
|
3263
|
+
command: cfg.command,
|
|
3264
|
+
args: cfg.args
|
|
3265
|
+
};
|
|
3266
|
+
if (cfg.env && Object.keys(cfg.env).length > 0) out.env = { ...cfg.env };
|
|
3267
|
+
return out;
|
|
3268
|
+
}
|
|
3269
|
+
function mergeMcpServers(base, rows, tracks, chosen = [], dropped = []) {
|
|
3270
|
+
const out = {
|
|
3271
|
+
...base,
|
|
3272
|
+
mcpServers: { ...base.mcpServers }
|
|
3273
|
+
};
|
|
3274
|
+
for (const row of rows) {
|
|
3275
|
+
if (dropped.includes(row.name)) {
|
|
3276
|
+
continue;
|
|
3277
|
+
}
|
|
3278
|
+
if (!anyTrack(tracks, row.pattern) && !chosen.includes(row.name)) {
|
|
3279
|
+
continue;
|
|
3280
|
+
}
|
|
3281
|
+
if (out.mcpServers[row.name]) {
|
|
3282
|
+
continue;
|
|
3283
|
+
}
|
|
3284
|
+
out.mcpServers[row.name] = row.server;
|
|
3285
|
+
}
|
|
3286
|
+
delete out._comment;
|
|
3287
|
+
return out;
|
|
3288
|
+
}
|
|
3289
|
+
function composeMcpJson(opts) {
|
|
3290
|
+
const base = JSON.parse(readFileSync3(opts.templateMcpPath, "utf8"));
|
|
3291
|
+
const mapRaw = existsSync4(opts.trackMapPath) ? readFileSync3(opts.trackMapPath, "utf8") : "";
|
|
3292
|
+
const rows = parseTrackMcpMap(mapRaw);
|
|
3293
|
+
return mergeMcpServers(base, rows, opts.tracks, opts.chosen(rows), opts.dropped?.(rows) ?? []);
|
|
3294
|
+
}
|
|
3295
|
+
function selectableMcpServers(rows) {
|
|
3296
|
+
const names = new Set(rows.map((r) => r.name));
|
|
3297
|
+
const out = /* @__PURE__ */ new Map();
|
|
3298
|
+
for (const a of EXTERNAL_ASSETS) {
|
|
3299
|
+
if (a.method.kind === "internal" && names.has(a.method.key)) out.set(a.method.key, a.id);
|
|
3300
|
+
}
|
|
3301
|
+
return out;
|
|
3302
|
+
}
|
|
3303
|
+
function selectedMcpServers(rows, choice) {
|
|
3304
|
+
const recorded2 = new Set(
|
|
3305
|
+
(choice.previousLog?.portions ?? []).filter((p2) => p2.path === ".mcp.json").map((p2) => p2.key)
|
|
3306
|
+
);
|
|
3307
|
+
const legacy = new Set(
|
|
3308
|
+
choice.projectDir === void 0 ? [] : legacyMcpServers(choice.projectDir, choice.previousLog, rows)
|
|
3309
|
+
);
|
|
3310
|
+
const dropped = new Set(droppedMcpServers(rows, choice.excluded));
|
|
3311
|
+
const out = [];
|
|
3312
|
+
for (const [name, assetId] of selectableMcpServers(rows)) {
|
|
3313
|
+
if (dropped.has(name)) continue;
|
|
3314
|
+
const key = `mcpServers.${name}`;
|
|
3315
|
+
if (isAssetSelected(assetId, choice.spec) || recorded2.has(key) || legacy.has(name)) {
|
|
3316
|
+
out.push(name);
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
return out;
|
|
3320
|
+
}
|
|
3321
|
+
function legacyMcpServers(projectDir, previousLog, rows) {
|
|
3322
|
+
if (previousLog === null || previousLog.records === "writer") return [];
|
|
3323
|
+
if ((previousLog.portions ?? []).some((p2) => p2.path === ".mcp.json")) return [];
|
|
3324
|
+
const created = (previousLog.rootFiles ?? []).some(
|
|
3325
|
+
(f) => f.path === ".mcp.json" && f.change === "created"
|
|
3326
|
+
);
|
|
3327
|
+
if (!created) return [];
|
|
3328
|
+
const servers = readMcpServers(join5(projectDir, ".mcp.json"));
|
|
3329
|
+
if (servers === null) return [];
|
|
3330
|
+
const out = [];
|
|
3331
|
+
for (const name of selectableMcpServers(rows).keys()) {
|
|
3332
|
+
if (!Object.hasOwn(servers, name)) continue;
|
|
3333
|
+
const disk = jsonSha(servers[name]);
|
|
3334
|
+
const row = rows.find((r) => r.name === name);
|
|
3335
|
+
const known = [...row ? [row.server] : [], ...priorMcpValues(name)];
|
|
3336
|
+
if (known.some((v2) => jsonSha(toClaudeServer(v2)) === disk)) out.push(name);
|
|
3337
|
+
}
|
|
3338
|
+
return out;
|
|
3339
|
+
}
|
|
3340
|
+
function readMcpServers(path) {
|
|
3341
|
+
if (!existsSync4(path)) return null;
|
|
3342
|
+
try {
|
|
3343
|
+
const parsed = JSON.parse(readFileSync3(path, "utf8"));
|
|
3344
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
3345
|
+
const servers = parsed.mcpServers;
|
|
3346
|
+
if (servers === null || typeof servers !== "object" || Array.isArray(servers)) return null;
|
|
3347
|
+
return servers;
|
|
3348
|
+
} catch {
|
|
3349
|
+
return null;
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
function droppedMcpServers(rows, excluded) {
|
|
3353
|
+
return [...selectableMcpServers(rows)].filter(([name, assetId]) => {
|
|
3354
|
+
const id = keyId(".mcp.json", `mcpServers.${name}`);
|
|
3355
|
+
return excluded.has(assetId) || id !== null && excluded.has(id);
|
|
3356
|
+
}).map(([name]) => name);
|
|
3357
|
+
}
|
|
3358
|
+
function recordedMcpAssetIds(log) {
|
|
3359
|
+
const recorded2 = new Set(
|
|
3360
|
+
(log?.portions ?? []).filter((p2) => p2.path === ".mcp.json").map((p2) => p2.key)
|
|
3361
|
+
);
|
|
3362
|
+
return EXTERNAL_ASSETS.filter(
|
|
3363
|
+
(a) => a.method.kind === "internal" && recorded2.has(`mcpServers.${a.method.key}`)
|
|
3364
|
+
).map((a) => a.id);
|
|
3365
|
+
}
|
|
3366
|
+
|
|
3172
3367
|
// src/recorded.ts
|
|
3173
3368
|
init_esm_shims();
|
|
3174
3369
|
|
|
@@ -3325,10 +3520,10 @@ function createInstallWriter(args) {
|
|
|
3325
3520
|
else external.set(path, sha256);
|
|
3326
3521
|
}
|
|
3327
3522
|
function harness(path, from, opts = {}) {
|
|
3328
|
-
const abs =
|
|
3523
|
+
const abs = join6(projectDir, path);
|
|
3329
3524
|
if (outside.skip(abs)) return null;
|
|
3330
|
-
const next = "source" in from ?
|
|
3331
|
-
const disk =
|
|
3525
|
+
const next = "source" in from ? readFileSync4(from.source, "utf8") : from.content;
|
|
3526
|
+
const disk = existsSync5(abs) ? readFileSync4(abs, "utf8") : null;
|
|
3332
3527
|
const j4 = judge({
|
|
3333
3528
|
op: "write",
|
|
3334
3529
|
kind: "harness",
|
|
@@ -3390,8 +3585,8 @@ function createInstallWriter(args) {
|
|
|
3390
3585
|
const file = SHARED_FILES[path];
|
|
3391
3586
|
if (file === void 0) throw new Error(`install: ${path} is not a shared file`);
|
|
3392
3587
|
const adapter = ADAPTERS[file.adapter];
|
|
3393
|
-
const abs =
|
|
3394
|
-
const disk =
|
|
3588
|
+
const abs = join6(projectDir, path);
|
|
3589
|
+
const disk = existsSync5(abs) ? readFileSync4(abs, "utf8") : null;
|
|
3395
3590
|
const own = new Map(
|
|
3396
3591
|
(previousLog?.portions ?? []).filter((p2) => p2.path === path).map((p2) => [p2.key, p2.sha256])
|
|
3397
3592
|
);
|
|
@@ -3471,8 +3666,10 @@ function createInstallWriter(args) {
|
|
|
3471
3666
|
const wrote = onlyRemoved ? "removed the harness part" : j4.line;
|
|
3472
3667
|
const line = res.changed ? removedEdited.length > 0 ? wrote.replace(" \u2014 yours stays", "") : wrote : valueKeys2.length > 0 ? "kept \u2014 the harness part is already in place" : "nothing written \u2014 yours already has these";
|
|
3473
3668
|
const retired = names(res.removed.filter((k2) => !out.has(k2) && !render.has(k2)));
|
|
3669
|
+
const replaced = names(res.replaced);
|
|
3474
3670
|
return result(j4.verdict, line, {
|
|
3475
3671
|
...retired.length > 0 ? { retired } : {},
|
|
3672
|
+
...replaced.length > 0 ? { replaced } : {},
|
|
3476
3673
|
changed: res.changed,
|
|
3477
3674
|
harness: names(valueKeys2),
|
|
3478
3675
|
added: names(addedKeys),
|
|
@@ -3655,7 +3852,7 @@ function legacySettingsSeed(text, render, projectDir) {
|
|
|
3655
3852
|
} catch {
|
|
3656
3853
|
return seed;
|
|
3657
3854
|
}
|
|
3658
|
-
const claudeDir =
|
|
3855
|
+
const claudeDir = join6(projectDir, ".claude");
|
|
3659
3856
|
const candidates = new Set([...render.keys()].filter((k2) => k2.startsWith("hooks.")));
|
|
3660
3857
|
const hooks = isObject2(root) && isObject2(root.hooks) ? root.hooks : {};
|
|
3661
3858
|
for (const [event, groups] of Object.entries(hooks)) {
|
|
@@ -3685,8 +3882,14 @@ function legacyMcpSeed(text, render, previous) {
|
|
|
3685
3882
|
(f) => f.path === ".mcp.json" && f.change === "created"
|
|
3686
3883
|
);
|
|
3687
3884
|
if (!created) return seed;
|
|
3688
|
-
const
|
|
3689
|
-
|
|
3885
|
+
const retired = retiredMcpNames().map((n) => `mcpServers.${n}`);
|
|
3886
|
+
const candidates = [...render.keys(), ...retired.filter((k2) => !render.has(k2))];
|
|
3887
|
+
const present2 = ADAPTERS["json-keys"].read(text, candidates) ?? /* @__PURE__ */ new Map();
|
|
3888
|
+
for (const [key, disk] of present2) {
|
|
3889
|
+
const prior = priorMcpValues(key.slice("mcpServers.".length)).map((v2) => jsonSha(toClaudeServer(v2))).find((sha) => sha === disk);
|
|
3890
|
+
if (render.has(key)) seed.set(key, prior ?? jsonSha(render.get(key)));
|
|
3891
|
+
else if (prior !== void 0) seed.set(key, prior);
|
|
3892
|
+
}
|
|
3690
3893
|
seed.set("mcpServers{}", "legacy");
|
|
3691
3894
|
return seed;
|
|
3692
3895
|
}
|
|
@@ -3701,16 +3904,39 @@ function writeSettingsShared(writer, templateText, projectDir, previousLog, hook
|
|
|
3701
3904
|
createdNote: "Claude Code \uC124\uC815 \u2014 \uD558\uB124\uC2A4 \uBAAB\uB9CC(\uD6C5 \xB7 statusLine)"
|
|
3702
3905
|
});
|
|
3703
3906
|
}
|
|
3704
|
-
function writeMcpShared(writer, mcp, previousLog, opts = {}) {
|
|
3907
|
+
function writeMcpShared(writer, mcp, previousLog, opts = {}, defaults) {
|
|
3705
3908
|
const servers = mcp.mcpServers;
|
|
3706
3909
|
const render = new Map(
|
|
3707
|
-
Object.entries(servers).map(([name, cfg]) => [`mcpServers.${name}`, cfg])
|
|
3910
|
+
Object.entries(servers).map(([name, cfg]) => [`mcpServers.${name}`, toClaudeServer(cfg)])
|
|
3708
3911
|
);
|
|
3709
|
-
|
|
3912
|
+
const res = writer.shared(".mcp.json", render, {
|
|
3710
3913
|
...opts,
|
|
3711
3914
|
legacySeed: (text) => legacyMcpSeed(text, render, previousLog),
|
|
3712
3915
|
createdNote: "MCP \uC11C\uBC84 \uC815\uC758 \uC0DD\uC131"
|
|
3713
3916
|
});
|
|
3917
|
+
const inFile = new Set(res.harness);
|
|
3918
|
+
const replaced = res.replaced ?? [];
|
|
3919
|
+
const notes = {};
|
|
3920
|
+
for (const p2 of PRIOR_MCP_SERVERS) {
|
|
3921
|
+
if (p2.retired !== void 0 && (res.retired ?? []).includes(p2.name)) notes[p2.name] = p2.retired;
|
|
3922
|
+
if (p2.updated !== void 0 && replaced.includes(p2.name)) notes[p2.name] = p2.updated;
|
|
3923
|
+
}
|
|
3924
|
+
if (defaults !== void 0) {
|
|
3925
|
+
for (const name of replaced) {
|
|
3926
|
+
const assetId = EXTERNAL_ASSETS.find(
|
|
3927
|
+
(a) => a.method.kind === "internal" && a.method.key === name
|
|
3928
|
+
)?.id;
|
|
3929
|
+
if (assetId === void 0 || defaults.names.has(name)) continue;
|
|
3930
|
+
const why = `not a default on ${defaults.tracks.join(", ")} \u2014 kept because your install record has it; drop it: agent-harness install \u2026 --without ${assetId}`;
|
|
3931
|
+
notes[name] = notes[name] === void 0 ? why : `${notes[name]}; ${why}`;
|
|
3932
|
+
}
|
|
3933
|
+
}
|
|
3934
|
+
const remote = Object.entries(servers).flatMap(
|
|
3935
|
+
([name, cfg]) => cfg.type === "http" && inFile.has(name) ? [cfg.auth === void 0 ? { name } : { name, bearerEnv: cfg.auth.bearerEnv }] : []
|
|
3936
|
+
);
|
|
3937
|
+
if (Object.keys(notes).length > 0) res.notes = notes;
|
|
3938
|
+
if (remote.length > 0) res.remote = remote;
|
|
3939
|
+
return res;
|
|
3714
3940
|
}
|
|
3715
3941
|
function writeGitignoreShared(writer, previousLog) {
|
|
3716
3942
|
const render = gitignoreRender();
|
|
@@ -3740,12 +3966,12 @@ function legacyGitignoreSeed(text, render, previous) {
|
|
|
3740
3966
|
// src/installer.ts
|
|
3741
3967
|
init_esm_shims();
|
|
3742
3968
|
import { chmodSync as chmodSync3, existsSync as existsSync21, readdirSync as readdirSync4, readFileSync as readFileSync22, writeFileSync as writeFileSync8 } from "fs";
|
|
3743
|
-
import { basename as basename4, join as
|
|
3969
|
+
import { basename as basename4, join as join29, relative as relative3, resolve, sep as sep4 } from "path";
|
|
3744
3970
|
|
|
3745
3971
|
// src/anchor-seed.ts
|
|
3746
3972
|
init_esm_shims();
|
|
3747
|
-
import { existsSync as
|
|
3748
|
-
import { join as
|
|
3973
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
3974
|
+
import { join as join7 } from "path";
|
|
3749
3975
|
|
|
3750
3976
|
// src/agents-md-render.ts
|
|
3751
3977
|
init_esm_shims();
|
|
@@ -3829,9 +4055,9 @@ ${CODEX_SIZE_NOTE}` : RULES_NOTE : "",
|
|
|
3829
4055
|
|
|
3830
4056
|
// src/anchor-seed.ts
|
|
3831
4057
|
function readIfExists(path) {
|
|
3832
|
-
if (!
|
|
4058
|
+
if (!existsSync6(path)) return null;
|
|
3833
4059
|
try {
|
|
3834
|
-
return
|
|
4060
|
+
return readFileSync5(path, "utf8");
|
|
3835
4061
|
} catch {
|
|
3836
4062
|
return null;
|
|
3837
4063
|
}
|
|
@@ -3852,7 +4078,7 @@ function compose(body, into) {
|
|
|
3852
4078
|
${trimmed.join("\n")}`;
|
|
3853
4079
|
}
|
|
3854
4080
|
function seedAgentsMdProjectContext(projectDir) {
|
|
3855
|
-
const root = readIfExists(
|
|
4081
|
+
const root = readIfExists(join7(projectDir, "CLAUDE.md"));
|
|
3856
4082
|
if (root === null) return null;
|
|
3857
4083
|
const withoutImport = stripHarnessImport(root) ?? root;
|
|
3858
4084
|
const body = dropHarnessQuoteBlocks(dropTitle(withoutImport.split("\n")));
|
|
@@ -3860,9 +4086,9 @@ function seedAgentsMdProjectContext(projectDir) {
|
|
|
3860
4086
|
return compose(body, "agents-md");
|
|
3861
4087
|
}
|
|
3862
4088
|
function seedRootClaudeProjectContext(projectDir, harnessRoot) {
|
|
3863
|
-
const agents = readIfExists(
|
|
4089
|
+
const agents = readIfExists(join7(projectDir, "AGENTS.md"));
|
|
3864
4090
|
if (agents === null) return null;
|
|
3865
|
-
const template = readIfExists(
|
|
4091
|
+
const template = readIfExists(join7(harnessRoot, AGENTS_MD_TEMPLATE));
|
|
3866
4092
|
if (template === null) return null;
|
|
3867
4093
|
const installer = projectContextInstallerLines({ existing: agents, template });
|
|
3868
4094
|
if (installer === null) return null;
|
|
@@ -3873,8 +4099,8 @@ function seedRootClaudeProjectContext(projectDir, harnessRoot) {
|
|
|
3873
4099
|
|
|
3874
4100
|
// src/ci-scaffold.ts
|
|
3875
4101
|
init_esm_shims();
|
|
3876
|
-
import { existsSync as
|
|
3877
|
-
import { join as
|
|
4102
|
+
import { existsSync as existsSync7 } from "fs";
|
|
4103
|
+
import { join as join8 } from "path";
|
|
3878
4104
|
var PYTHON_TRACKS = "data|csr-fastapi|full";
|
|
3879
4105
|
var VARIANTS = [
|
|
3880
4106
|
{
|
|
@@ -3898,12 +4124,12 @@ function installCiScaffold(ctx) {
|
|
|
3898
4124
|
for (const v2 of VARIANTS) {
|
|
3899
4125
|
if (!v2.applies(ctx.tracks)) continue;
|
|
3900
4126
|
const relTarget = `.github/workflows/${v2.target}`;
|
|
3901
|
-
const target =
|
|
3902
|
-
if (
|
|
4127
|
+
const target = join8(ctx.projectDir, relTarget);
|
|
4128
|
+
if (existsSync7(target)) {
|
|
3903
4129
|
report.skippedExisting.push(relTarget);
|
|
3904
4130
|
continue;
|
|
3905
4131
|
}
|
|
3906
|
-
copyFile(
|
|
4132
|
+
copyFile(join8(ctx.harnessRoot, "templates/github-workflows", v2.source), target);
|
|
3907
4133
|
report.written.push(relTarget);
|
|
3908
4134
|
}
|
|
3909
4135
|
return report;
|
|
@@ -3912,16 +4138,16 @@ function installCiScaffold(ctx) {
|
|
|
3912
4138
|
// src/cli-transforms.ts
|
|
3913
4139
|
init_esm_shims();
|
|
3914
4140
|
import { existsSync as existsSync16, readFileSync as readFileSync16 } from "fs";
|
|
3915
|
-
import { join as
|
|
4141
|
+
import { join as join21 } from "path";
|
|
3916
4142
|
|
|
3917
4143
|
// src/agents-md-transform.ts
|
|
3918
4144
|
init_esm_shims();
|
|
3919
|
-
import { existsSync as
|
|
3920
|
-
import { basename, join as
|
|
4145
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
|
|
4146
|
+
import { basename, join as join13 } from "path";
|
|
3921
4147
|
|
|
3922
4148
|
// src/agents-skill-targets.ts
|
|
3923
4149
|
init_esm_shims();
|
|
3924
|
-
import { join as
|
|
4150
|
+
import { join as join9, sep as sep2 } from "path";
|
|
3925
4151
|
function bundledSkillTargets(projectDir) {
|
|
3926
4152
|
const log = readInstallLog(projectDir);
|
|
3927
4153
|
if (log === null) return [];
|
|
@@ -3936,7 +4162,7 @@ function bundledSkillTargets(projectDir) {
|
|
|
3936
4162
|
}
|
|
3937
4163
|
function allowSkillCreation(writer, projectDir, ids) {
|
|
3938
4164
|
if (ids.length === 0) return writer;
|
|
3939
|
-
const roots = ids.map((id) => `${
|
|
4165
|
+
const roots = ids.map((id) => `${join9(projectDir, ".agents", "skills", id)}${sep2}`);
|
|
3940
4166
|
return {
|
|
3941
4167
|
write(absPath, content, opts) {
|
|
3942
4168
|
const inTarget = roots.some((root) => absPath.startsWith(root));
|
|
@@ -3957,24 +4183,24 @@ function agentsSkillSlot(params) {
|
|
|
3957
4183
|
|
|
3958
4184
|
// src/owned-write.ts
|
|
3959
4185
|
init_esm_shims();
|
|
3960
|
-
import { existsSync as
|
|
4186
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
3961
4187
|
import { dirname as dirname2, relative as relative2, sep as sep3 } from "path";
|
|
3962
4188
|
|
|
3963
4189
|
// src/foreign-slot.ts
|
|
3964
4190
|
init_esm_shims();
|
|
3965
4191
|
import { lstatSync, realpathSync, statSync } from "fs";
|
|
3966
|
-
import { join as
|
|
4192
|
+
import { join as join10 } from "path";
|
|
3967
4193
|
var SKILL_SLOT = /^((?:\.claude|\.agents)\/skills\/[^/]+)(?:\/|$)/;
|
|
3968
4194
|
function occupiedByNonDirectory(path) {
|
|
3969
4195
|
const stat = lstatSync(path, { throwIfNoEntry: false });
|
|
3970
4196
|
return stat !== void 0 && !stat.isDirectory();
|
|
3971
4197
|
}
|
|
3972
4198
|
function linksToProjectSharedSkill(projectDir, id) {
|
|
3973
|
-
const slot =
|
|
4199
|
+
const slot = join10(projectDir, ".claude/skills", id);
|
|
3974
4200
|
if (lstatSync(slot, { throwIfNoEntry: false })?.isSymbolicLink() !== true) return false;
|
|
3975
4201
|
try {
|
|
3976
4202
|
const real = realpathSync(slot);
|
|
3977
|
-
return real ===
|
|
4203
|
+
return real === join10(realpathSync(projectDir), ".agents/skills", id) && statSync(real).isDirectory();
|
|
3978
4204
|
} catch {
|
|
3979
4205
|
return false;
|
|
3980
4206
|
}
|
|
@@ -3982,15 +4208,15 @@ function linksToProjectSharedSkill(projectDir, id) {
|
|
|
3982
4208
|
function foreignOwnedTarget(projectDir, rel) {
|
|
3983
4209
|
const slot = SKILL_SLOT.exec(rel)?.[1];
|
|
3984
4210
|
if (slot === void 0) return null;
|
|
3985
|
-
if (occupiedByNonDirectory(
|
|
4211
|
+
if (occupiedByNonDirectory(join10(projectDir, slot))) return slot;
|
|
3986
4212
|
if (rel !== slot) {
|
|
3987
4213
|
const parts = rel.slice(slot.length + 1).split("/");
|
|
3988
4214
|
let cursor = slot;
|
|
3989
4215
|
for (const part of parts.slice(0, -1)) {
|
|
3990
4216
|
cursor = `${cursor}/${part}`;
|
|
3991
|
-
if (occupiedByNonDirectory(
|
|
4217
|
+
if (occupiedByNonDirectory(join10(projectDir, cursor))) return cursor;
|
|
3992
4218
|
}
|
|
3993
|
-
const leaf = lstatSync(
|
|
4219
|
+
const leaf = lstatSync(join10(projectDir, rel), { throwIfNoEntry: false });
|
|
3994
4220
|
if (leaf !== void 0 && !leaf.isFile()) return rel;
|
|
3995
4221
|
}
|
|
3996
4222
|
return null;
|
|
@@ -4016,7 +4242,7 @@ function createOwnedWriter(projectDir, baseline, options = {}) {
|
|
|
4016
4242
|
}
|
|
4017
4243
|
if (outside.skip(absPath)) return false;
|
|
4018
4244
|
const digest = hashContent(content);
|
|
4019
|
-
if (!
|
|
4245
|
+
if (!existsSync8(absPath)) {
|
|
4020
4246
|
const recordedPath = baseline.has(rel);
|
|
4021
4247
|
if (refreshOnly && !opts?.createInRefresh && !recordedPath) return false;
|
|
4022
4248
|
mkdirSync2(dirname2(absPath), { recursive: true });
|
|
@@ -4024,7 +4250,7 @@ function createOwnedWriter(projectDir, baseline, options = {}) {
|
|
|
4024
4250
|
updated++;
|
|
4025
4251
|
if (refreshOnly && recordedPath) restored.push(rel);
|
|
4026
4252
|
} else {
|
|
4027
|
-
const current =
|
|
4253
|
+
const current = readFileSync6(absPath, "utf8");
|
|
4028
4254
|
if (current !== content) {
|
|
4029
4255
|
if (!isHarnessOwned(baseline, rel, current)) {
|
|
4030
4256
|
const backup = backupFile(absPath, now);
|
|
@@ -4058,8 +4284,8 @@ function createOwnedWriter(projectDir, baseline, options = {}) {
|
|
|
4058
4284
|
|
|
4059
4285
|
// src/rules-port.ts
|
|
4060
4286
|
init_esm_shims();
|
|
4061
|
-
import { existsSync as
|
|
4062
|
-
import { join as
|
|
4287
|
+
import { existsSync as existsSync9, readdirSync, readFileSync as readFileSync7 } from "fs";
|
|
4288
|
+
import { join as join11 } from "path";
|
|
4063
4289
|
function claudePaths(source) {
|
|
4064
4290
|
if (!source.startsWith("---\n")) return [];
|
|
4065
4291
|
const end = source.indexOf("\n---", 4);
|
|
@@ -4087,16 +4313,16 @@ function stripClaudeFrontmatter(source) {
|
|
|
4087
4313
|
function portRules(harnessRoot, ruleNames) {
|
|
4088
4314
|
const ported = [];
|
|
4089
4315
|
for (const name of ruleNames) {
|
|
4090
|
-
const src =
|
|
4091
|
-
if (!
|
|
4092
|
-
const source =
|
|
4316
|
+
const src = join11(harnessRoot, "templates/rules", `${name}.md`);
|
|
4317
|
+
if (!existsSync9(src)) continue;
|
|
4318
|
+
const source = readFileSync7(src, "utf8");
|
|
4093
4319
|
ported.push({ name, body: stripClaudeFrontmatter(source), paths: claudePaths(source) });
|
|
4094
4320
|
}
|
|
4095
4321
|
return ported;
|
|
4096
4322
|
}
|
|
4097
4323
|
function ruleHeadings(harnessRoot) {
|
|
4098
|
-
const dir =
|
|
4099
|
-
if (!
|
|
4324
|
+
const dir = join11(harnessRoot, "templates/rules");
|
|
4325
|
+
if (!existsSync9(dir)) return /* @__PURE__ */ new Set();
|
|
4100
4326
|
const names = readdirSync(dir).filter((f) => f.endsWith(".md")).map((f) => f.slice(0, -3));
|
|
4101
4327
|
const block = renderRulesBlock(portRules(harnessRoot, names));
|
|
4102
4328
|
return new Set(
|
|
@@ -4110,8 +4336,8 @@ function renderRulesBlock(rules) {
|
|
|
4110
4336
|
|
|
4111
4337
|
// src/shared-write.ts
|
|
4112
4338
|
init_esm_shims();
|
|
4113
|
-
import { existsSync as
|
|
4114
|
-
import { dirname as dirname3, join as
|
|
4339
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync8, rmSync, statSync as statSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
4340
|
+
import { dirname as dirname3, join as join12 } from "path";
|
|
4115
4341
|
function toPortions(path, portions) {
|
|
4116
4342
|
const adapter = adapterFor(path);
|
|
4117
4343
|
if (adapter === null) throw new Error(`shared-write: ${path} is not a shared file`);
|
|
@@ -4126,8 +4352,8 @@ function writeShared(params) {
|
|
|
4126
4352
|
const { projectDir, path, render, record, baseline, writer, refreshOnly } = params;
|
|
4127
4353
|
const adapter = adapterFor(path);
|
|
4128
4354
|
if (adapter === null) throw new Error(`shared-write: ${path} is not a shared file`);
|
|
4129
|
-
const abs =
|
|
4130
|
-
const onDisk =
|
|
4355
|
+
const abs = join12(projectDir, path);
|
|
4356
|
+
const onDisk = existsSync10(abs) ? readFileSync8(abs, "utf8") : null;
|
|
4131
4357
|
const recorded2 = recordedFor(path, record);
|
|
4132
4358
|
const excluded = excludedKeys(path, record.excluded ?? []);
|
|
4133
4359
|
const result = (action2, rest = {}) => ({
|
|
@@ -4246,8 +4472,8 @@ function agentsMdModel(disk, recordedByHarness, recordedBlock = false) {
|
|
|
4246
4472
|
return "block";
|
|
4247
4473
|
}
|
|
4248
4474
|
function writeAgentsMd(params) {
|
|
4249
|
-
const abs =
|
|
4250
|
-
const disk =
|
|
4475
|
+
const abs = join12(params.projectDir, AGENTS_MD);
|
|
4476
|
+
const disk = existsSync10(abs) ? readFileSync8(abs, "utf8") : null;
|
|
4251
4477
|
const recordedBlock = (params.record.portions ?? []).some((p2) => p2.path === AGENTS_MD);
|
|
4252
4478
|
if (agentsMdModel(disk, params.baseline.has(AGENTS_MD), recordedBlock) === "sections") {
|
|
4253
4479
|
params.writer.write(
|
|
@@ -4300,9 +4526,9 @@ function stripShared(params) {
|
|
|
4300
4526
|
portions: [],
|
|
4301
4527
|
...rest
|
|
4302
4528
|
});
|
|
4303
|
-
const abs =
|
|
4304
|
-
if (!
|
|
4305
|
-
const disk =
|
|
4529
|
+
const abs = join12(projectDir, path);
|
|
4530
|
+
if (!existsSync10(abs) || !statSync2(abs).isFile()) return out("none");
|
|
4531
|
+
const disk = readFileSync8(abs, "utf8");
|
|
4306
4532
|
const recorded2 = new Map(
|
|
4307
4533
|
params.portions.filter((p2) => p2.path === path).map((p2) => [p2.key, p2.sha256])
|
|
4308
4534
|
);
|
|
@@ -4355,16 +4581,16 @@ function runAgentsMdTransform(params) {
|
|
|
4355
4581
|
refreshOnly,
|
|
4356
4582
|
...params.journal === void 0 ? {} : { journal: params.journal }
|
|
4357
4583
|
});
|
|
4358
|
-
const claudeMd = readRequired(
|
|
4359
|
-
const template = readRequired(
|
|
4584
|
+
const claudeMd = readRequired(join13(harnessRoot, "templates/CLAUDE.md"));
|
|
4585
|
+
const template = readRequired(join13(harnessRoot, AGENTS_MD_TEMPLATE));
|
|
4360
4586
|
const { skillIds } = agentsSkillSlot({
|
|
4361
4587
|
projectDir,
|
|
4362
4588
|
selectedInternalSkills: params.selectedInternalSkills,
|
|
4363
4589
|
refreshOnly,
|
|
4364
4590
|
writer
|
|
4365
4591
|
});
|
|
4366
|
-
const agentsMdPath =
|
|
4367
|
-
const exists =
|
|
4592
|
+
const agentsMdPath = join13(projectDir, "AGENTS.md");
|
|
4593
|
+
const exists = existsSync11(agentsMdPath);
|
|
4368
4594
|
const creates = !exists && (!refreshOnly || params.installedByRecord === true);
|
|
4369
4595
|
const legacy = params.legacyContext ?? null;
|
|
4370
4596
|
const fromRoot = creates ? seedAgentsMdProjectContext(projectDir) : null;
|
|
@@ -4383,7 +4609,7 @@ function runAgentsMdTransform(params) {
|
|
|
4383
4609
|
harnessRules: renderRulesBlock(portRules(harnessRoot, params.rules)),
|
|
4384
4610
|
clis
|
|
4385
4611
|
});
|
|
4386
|
-
const diskBefore = exists ?
|
|
4612
|
+
const diskBefore = exists ? readFileSync9(agentsMdPath, "utf8") : null;
|
|
4387
4613
|
const agentsMd = writeAgentsMd({
|
|
4388
4614
|
projectDir,
|
|
4389
4615
|
rendered,
|
|
@@ -4406,22 +4632,22 @@ function runAgentsMdTransform(params) {
|
|
|
4406
4632
|
};
|
|
4407
4633
|
}
|
|
4408
4634
|
function seededInto(path, before, seed, from) {
|
|
4409
|
-
if (seed === null || !
|
|
4410
|
-
const after =
|
|
4635
|
+
if (seed === null || !existsSync11(path)) return null;
|
|
4636
|
+
const after = readFileSync9(path, "utf8");
|
|
4411
4637
|
const body = renameSlashes(seed.slice(seed.indexOf("\n\n") + 2));
|
|
4412
4638
|
return after !== before && after.includes(body) ? from : null;
|
|
4413
4639
|
}
|
|
4414
4640
|
function readRequired(path) {
|
|
4415
|
-
if (!
|
|
4641
|
+
if (!existsSync11(path)) {
|
|
4416
4642
|
throw new Error(`AGENTS.md transform: required source missing: ${path}`);
|
|
4417
4643
|
}
|
|
4418
|
-
return
|
|
4644
|
+
return readFileSync9(path, "utf8");
|
|
4419
4645
|
}
|
|
4420
4646
|
|
|
4421
4647
|
// src/antigravity/retire-anchor.ts
|
|
4422
4648
|
init_esm_shims();
|
|
4423
|
-
import { lstatSync as lstatSync2, readFileSync as
|
|
4424
|
-
import { join as
|
|
4649
|
+
import { lstatSync as lstatSync2, readFileSync as readFileSync10, rmSync as rmSync2 } from "fs";
|
|
4650
|
+
import { join as join14 } from "path";
|
|
4425
4651
|
var LEGACY_ANTIGRAVITY_ANCHOR_SECTIONS = ["Project Context", "Project Rules", "Protected Files"];
|
|
4426
4652
|
function anchorRecord(previous) {
|
|
4427
4653
|
return previous.find((f) => f.path === LEGACY_ANTIGRAVITY_ANCHOR) ?? null;
|
|
@@ -4430,7 +4656,7 @@ function readRegular(abs) {
|
|
|
4430
4656
|
const stat = lstatSync2(abs, { throwIfNoEntry: false });
|
|
4431
4657
|
if (stat === void 0 || !stat.isFile()) return null;
|
|
4432
4658
|
try {
|
|
4433
|
-
return
|
|
4659
|
+
return readFileSync10(abs, "utf8");
|
|
4434
4660
|
} catch {
|
|
4435
4661
|
return null;
|
|
4436
4662
|
}
|
|
@@ -4449,7 +4675,7 @@ function legacyContextLines(text) {
|
|
|
4449
4675
|
}
|
|
4450
4676
|
function readLegacyAntigravityContext(projectDir, previous) {
|
|
4451
4677
|
if (anchorRecord(previous) === null) return null;
|
|
4452
|
-
const text = readRegular(
|
|
4678
|
+
const text = readRegular(join14(projectDir, LEGACY_ANTIGRAVITY_ANCHOR));
|
|
4453
4679
|
if (text === null) return null;
|
|
4454
4680
|
const lines2 = legacyContextLines(text);
|
|
4455
4681
|
if (lines2 === null) return null;
|
|
@@ -4473,7 +4699,7 @@ function retireAntigravityAnchor(args) {
|
|
|
4473
4699
|
const rec = anchorRecord(args.previous);
|
|
4474
4700
|
if (rec === null) return null;
|
|
4475
4701
|
const path = LEGACY_ANTIGRAVITY_ANCHOR;
|
|
4476
|
-
const abs =
|
|
4702
|
+
const abs = join14(args.projectDir, path);
|
|
4477
4703
|
const left = (why) => ({
|
|
4478
4704
|
path,
|
|
4479
4705
|
action: "left",
|
|
@@ -4484,7 +4710,7 @@ function retireAntigravityAnchor(args) {
|
|
|
4484
4710
|
if (args.outside.skip(abs)) return left("it points outside this project");
|
|
4485
4711
|
const stat = lstatSync2(abs, { throwIfNoEntry: false });
|
|
4486
4712
|
if (stat !== void 0 && !stat.isFile()) return left("it is a link or folder, so it is yours");
|
|
4487
|
-
const disk = stat === void 0 ? null :
|
|
4713
|
+
const disk = stat === void 0 ? null : readFileSync10(abs, "utf8");
|
|
4488
4714
|
const verdict = judge({
|
|
4489
4715
|
op: "remove",
|
|
4490
4716
|
kind: "harness",
|
|
@@ -4511,12 +4737,12 @@ function relativeBackup(projectDir, backup) {
|
|
|
4511
4737
|
|
|
4512
4738
|
// src/antigravity/transform.ts
|
|
4513
4739
|
init_esm_shims();
|
|
4514
|
-
import { join as
|
|
4740
|
+
import { join as join16 } from "path";
|
|
4515
4741
|
|
|
4516
4742
|
// src/codex/skills.ts
|
|
4517
4743
|
init_esm_shims();
|
|
4518
|
-
import { chmodSync, existsSync as
|
|
4519
|
-
import { join as
|
|
4744
|
+
import { chmodSync, existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
4745
|
+
import { join as join15 } from "path";
|
|
4520
4746
|
function renderBundledSkill(source) {
|
|
4521
4747
|
const trimmed = source.trimEnd();
|
|
4522
4748
|
const lines2 = trimmed.split(/\r?\n/);
|
|
@@ -4548,14 +4774,14 @@ function writeBundledSkillDirs(params) {
|
|
|
4548
4774
|
const { harnessRoot, projectDir, skillIds, writer } = params;
|
|
4549
4775
|
const written = [];
|
|
4550
4776
|
for (const id of skillIds) {
|
|
4551
|
-
const srcDir =
|
|
4552
|
-
const srcSkill =
|
|
4553
|
-
if (!
|
|
4777
|
+
const srcDir = join15(harnessRoot, "templates/skills", id);
|
|
4778
|
+
const srcSkill = join15(srcDir, "SKILL.md");
|
|
4779
|
+
if (!existsSync12(srcSkill)) {
|
|
4554
4780
|
continue;
|
|
4555
4781
|
}
|
|
4556
|
-
const targetDir =
|
|
4557
|
-
const targetSkill =
|
|
4558
|
-
if (!writer.write(targetSkill, renderBundledSkill(
|
|
4782
|
+
const targetDir = join15(projectDir, ".agents", "skills", id);
|
|
4783
|
+
const targetSkill = join15(targetDir, "SKILL.md");
|
|
4784
|
+
if (!writer.write(targetSkill, renderBundledSkill(readFileSync11(srcSkill, "utf8")))) {
|
|
4559
4785
|
continue;
|
|
4560
4786
|
}
|
|
4561
4787
|
written.push(targetSkill);
|
|
@@ -4563,10 +4789,10 @@ function writeBundledSkillDirs(params) {
|
|
|
4563
4789
|
if (rel === "SKILL.md") continue;
|
|
4564
4790
|
if (rel.split("/").some((segment2) => segment2.startsWith("."))) continue;
|
|
4565
4791
|
const segments = rel.split("/");
|
|
4566
|
-
const raw =
|
|
4792
|
+
const raw = readFileSync11(join15(srcDir, ...segments), "utf8");
|
|
4567
4793
|
const ported = rel.endsWith(".md") || rel.endsWith(".sh") ? `${portBody(raw)}
|
|
4568
4794
|
` : raw;
|
|
4569
|
-
const target =
|
|
4795
|
+
const target = join15(targetDir, ...segments);
|
|
4570
4796
|
if (!writer.write(target, ported, { createInRefresh: true })) continue;
|
|
4571
4797
|
if (rel.endsWith(".sh")) chmodSync(target, 493);
|
|
4572
4798
|
written.push(target);
|
|
@@ -4607,7 +4833,7 @@ function runAntigravityTransform(params) {
|
|
|
4607
4833
|
const antigravityInstalled = params.installedByRecord === true;
|
|
4608
4834
|
const harnessRuleFiles = [];
|
|
4609
4835
|
for (const rule of portRules(harnessRoot, rules)) {
|
|
4610
|
-
const target =
|
|
4836
|
+
const target = join16(projectDir, ".agents", "rules", `${rule.name}.md`);
|
|
4611
4837
|
const content = `${antigravityFrontmatter(rule)}${rule.body}
|
|
4612
4838
|
`;
|
|
4613
4839
|
if (!writer.write(target, content, { createInRefresh: antigravityInstalled })) {
|
|
@@ -4631,11 +4857,11 @@ function runAntigravityTransform(params) {
|
|
|
4631
4857
|
// src/codex/opt-in.ts
|
|
4632
4858
|
init_esm_shims();
|
|
4633
4859
|
import { homedir } from "os";
|
|
4634
|
-
import { join as
|
|
4860
|
+
import { join as join17 } from "path";
|
|
4635
4861
|
|
|
4636
4862
|
// src/codex/trust-entry.ts
|
|
4637
4863
|
init_esm_shims();
|
|
4638
|
-
import { existsSync as
|
|
4864
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync12, realpathSync as realpathSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
4639
4865
|
import { dirname as dirname4 } from "path";
|
|
4640
4866
|
var UNSAFE_APPEND = "adding the entry would make this file unreadable TOML \u2014 left untouched";
|
|
4641
4867
|
function trustKeysFor(projectDir) {
|
|
@@ -4678,7 +4904,7 @@ function registerTrustEntry(opts) {
|
|
|
4678
4904
|
const keys = trustKeysFor(projectDir);
|
|
4679
4905
|
const key = keys[0] ?? projectDir;
|
|
4680
4906
|
try {
|
|
4681
|
-
const existing =
|
|
4907
|
+
const existing = existsSync13(configPath) ? readFileSync12(configPath, "utf8") : "";
|
|
4682
4908
|
let parsed;
|
|
4683
4909
|
try {
|
|
4684
4910
|
parsed = parse(existing);
|
|
@@ -4729,8 +4955,8 @@ function firstLine(e2) {
|
|
|
4729
4955
|
|
|
4730
4956
|
// src/codex/opt-in.ts
|
|
4731
4957
|
function runCodexOptIn(ctx) {
|
|
4732
|
-
const codexHome = ctx.codexHome ??
|
|
4733
|
-
const configPath =
|
|
4958
|
+
const codexHome = ctx.codexHome ?? join17(homedir(), ".codex");
|
|
4959
|
+
const configPath = join17(codexHome, "config.toml");
|
|
4734
4960
|
const result = registerTrustEntry({ configPath, projectDir: ctx.projectDir });
|
|
4735
4961
|
return {
|
|
4736
4962
|
trustEntry: {
|
|
@@ -4745,13 +4971,13 @@ function runCodexOptIn(ctx) {
|
|
|
4745
4971
|
|
|
4746
4972
|
// src/codex/transform.ts
|
|
4747
4973
|
init_esm_shims();
|
|
4748
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
4749
|
-
import { basename as basename2, join as
|
|
4974
|
+
import { chmodSync as chmodSync2, existsSync as existsSync14, readFileSync as readFileSync14 } from "fs";
|
|
4975
|
+
import { basename as basename2, join as join19 } from "path";
|
|
4750
4976
|
|
|
4751
4977
|
// src/codex/config-toml.ts
|
|
4752
4978
|
init_esm_shims();
|
|
4753
|
-
import { readFileSync as
|
|
4754
|
-
import { join as
|
|
4979
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
4980
|
+
import { join as join18 } from "path";
|
|
4755
4981
|
var TABLES_REGION = "tables";
|
|
4756
4982
|
function configRegions(rendered) {
|
|
4757
4983
|
const lines2 = rendered.split(/\r?\n/);
|
|
@@ -4796,7 +5022,7 @@ function retiredFeatureKeys(disk) {
|
|
|
4796
5022
|
function retiredFeaturesLeftBehind(projectDir, r) {
|
|
4797
5023
|
if (r.action !== "left" && !r.leftAsIs.includes(TABLES_REGION)) return [];
|
|
4798
5024
|
try {
|
|
4799
|
-
return retiredFeatureKeys(
|
|
5025
|
+
return retiredFeatureKeys(readFileSync13(join18(projectDir, ".codex/config.toml"), "utf8"));
|
|
4800
5026
|
} catch {
|
|
4801
5027
|
return [];
|
|
4802
5028
|
}
|
|
@@ -4814,7 +5040,7 @@ function isChangedLegacyConfig(disk, baselineSha) {
|
|
|
4814
5040
|
return parsed !== null && callsHarnessHook(parsed);
|
|
4815
5041
|
}
|
|
4816
5042
|
function renderConfigToml(params) {
|
|
4817
|
-
const substituted = params.template.replaceAll("{PROJECT_NAME}", params.projectName).replaceAll("{PROJECT_DIR}", tomlBasicString(params.projectDir).slice(1, -1))
|
|
5043
|
+
const substituted = params.template.replaceAll("{PROJECT_NAME}", params.projectName).replaceAll("{PROJECT_DIR}", tomlBasicString(params.projectDir).slice(1, -1));
|
|
4818
5044
|
if (!params.mcp) {
|
|
4819
5045
|
return substituted;
|
|
4820
5046
|
}
|
|
@@ -4826,6 +5052,12 @@ ${renderMcpServers(params.mcp)}
|
|
|
4826
5052
|
function renderMcpServers(mcp) {
|
|
4827
5053
|
const blocks = Object.entries(mcp.mcpServers).map(([name, cfg]) => {
|
|
4828
5054
|
const lines2 = [`[mcp_servers.${quoteIfNeeded(name)}]`];
|
|
5055
|
+
if (cfg.type === "http") {
|
|
5056
|
+
lines2.push(`url = ${jsonString(cfg.url)}`);
|
|
5057
|
+
if (cfg.auth !== void 0)
|
|
5058
|
+
lines2.push(`bearer_token_env_var = ${jsonString(cfg.auth.bearerEnv)}`);
|
|
5059
|
+
return lines2.join("\n");
|
|
5060
|
+
}
|
|
4829
5061
|
lines2.push(`command = ${jsonString(cfg.command)}`);
|
|
4830
5062
|
lines2.push(`args = ${JSON.stringify(cfg.args)}`);
|
|
4831
5063
|
if (cfg.env && Object.keys(cfg.env).length > 0) {
|
|
@@ -4860,7 +5092,7 @@ function runCodexTransform(params) {
|
|
|
4860
5092
|
refreshOnly: refreshOnly ?? false,
|
|
4861
5093
|
...params.journal === void 0 ? {} : { journal: params.journal }
|
|
4862
5094
|
});
|
|
4863
|
-
const configTemplate = readRequired2(
|
|
5095
|
+
const configTemplate = readRequired2(join19(harnessRoot, "templates/codex/config.toml.template"));
|
|
4864
5096
|
const projectName = basename2(projectDir);
|
|
4865
5097
|
const { skillIds, skillWriter } = agentsSkillSlot({
|
|
4866
5098
|
projectDir,
|
|
@@ -4868,7 +5100,7 @@ function runCodexTransform(params) {
|
|
|
4868
5100
|
refreshOnly: refreshOnly ?? false,
|
|
4869
5101
|
writer
|
|
4870
5102
|
});
|
|
4871
|
-
const configTomlPath =
|
|
5103
|
+
const configTomlPath = join19(projectDir, CONFIG_TOML);
|
|
4872
5104
|
const configToml = writeConfigToml({
|
|
4873
5105
|
projectDir,
|
|
4874
5106
|
regions: configRegions(
|
|
@@ -4879,15 +5111,15 @@ function runCodexTransform(params) {
|
|
|
4879
5111
|
record: shared2,
|
|
4880
5112
|
refreshOnly: refreshOnly ?? false
|
|
4881
5113
|
});
|
|
4882
|
-
const hookDir =
|
|
5114
|
+
const hookDir = join19(projectDir, ".codex/hooks");
|
|
4883
5115
|
const hookFiles = [];
|
|
4884
5116
|
for (const hook of HOOK_NAMES) {
|
|
4885
|
-
const src =
|
|
4886
|
-
if (!
|
|
5117
|
+
const src = join19(harnessRoot, "templates/hooks", `${hook}.sh`);
|
|
5118
|
+
if (!existsSync14(src)) {
|
|
4887
5119
|
continue;
|
|
4888
5120
|
}
|
|
4889
|
-
const ported =
|
|
4890
|
-
const target =
|
|
5121
|
+
const ported = readFileSync14(src, "utf8").replace(ENV_VAR_RENAME, "CODEX_PROJECT_DIR");
|
|
5122
|
+
const target = join19(hookDir, `${hook}.sh`);
|
|
4891
5123
|
if (!writer.write(target, ported)) continue;
|
|
4892
5124
|
chmodSync2(target, 493);
|
|
4893
5125
|
hookFiles.push(target);
|
|
@@ -4909,8 +5141,8 @@ function runCodexTransform(params) {
|
|
|
4909
5141
|
var CONFIG_TOML = ".codex/config.toml";
|
|
4910
5142
|
var LEGACY_CHANGED = "written by an earlier harness version and changed since \u2014 left as is; harness part not added";
|
|
4911
5143
|
function writeConfigToml(args) {
|
|
4912
|
-
const abs =
|
|
4913
|
-
const disk =
|
|
5144
|
+
const abs = join19(args.projectDir, CONFIG_TOML);
|
|
5145
|
+
const disk = existsSync14(abs) ? readFileSync14(abs, "utf8") : null;
|
|
4914
5146
|
const regions = disk === null ? null : ADAPTERS["toml-region"].read(disk);
|
|
4915
5147
|
if (disk !== null && regions?.size === 0 && isChangedLegacyConfig(disk, args.baseline.get(CONFIG_TOML))) {
|
|
4916
5148
|
return {
|
|
@@ -4940,131 +5172,42 @@ function writeConfigToml(args) {
|
|
|
4940
5172
|
});
|
|
4941
5173
|
}
|
|
4942
5174
|
function readRequired2(path) {
|
|
4943
|
-
if (!
|
|
5175
|
+
if (!existsSync14(path)) {
|
|
4944
5176
|
throw new Error(`Codex transform: required source missing: ${path}`);
|
|
4945
5177
|
}
|
|
4946
|
-
return
|
|
4947
|
-
}
|
|
4948
|
-
|
|
4949
|
-
// src/mcp-merge.ts
|
|
4950
|
-
init_esm_shims();
|
|
4951
|
-
import { existsSync as existsSync14, readFileSync as readFileSync14 } from "fs";
|
|
4952
|
-
function parseTrackMcpMap(raw) {
|
|
4953
|
-
const rows = [];
|
|
4954
|
-
for (const line of raw.split(/\r?\n/)) {
|
|
4955
|
-
const trimmed = line.trim();
|
|
4956
|
-
if (!trimmed || trimmed.startsWith("#")) {
|
|
4957
|
-
continue;
|
|
4958
|
-
}
|
|
4959
|
-
const parts = line.split(" ");
|
|
4960
|
-
if (parts.length < 4) {
|
|
4961
|
-
continue;
|
|
4962
|
-
}
|
|
4963
|
-
const [name, pattern, command, argsJson] = parts;
|
|
4964
|
-
if (!name || !pattern || !command) {
|
|
4965
|
-
continue;
|
|
4966
|
-
}
|
|
4967
|
-
let args;
|
|
4968
|
-
try {
|
|
4969
|
-
args = JSON.parse(argsJson ?? "[]");
|
|
4970
|
-
} catch {
|
|
4971
|
-
continue;
|
|
4972
|
-
}
|
|
4973
|
-
if (!Array.isArray(args) || !args.every((a) => typeof a === "string")) {
|
|
4974
|
-
continue;
|
|
4975
|
-
}
|
|
4976
|
-
rows.push({ name, pattern, command, args });
|
|
4977
|
-
}
|
|
4978
|
-
return rows;
|
|
4979
|
-
}
|
|
4980
|
-
function mergeMcpServers(base, rows, tracks, chosen = [], dropped = []) {
|
|
4981
|
-
const out = {
|
|
4982
|
-
...base,
|
|
4983
|
-
mcpServers: { ...base.mcpServers }
|
|
4984
|
-
};
|
|
4985
|
-
for (const row of rows) {
|
|
4986
|
-
if (dropped.includes(row.name)) {
|
|
4987
|
-
continue;
|
|
4988
|
-
}
|
|
4989
|
-
if (!anyTrack(tracks, row.pattern) && !chosen.includes(row.name)) {
|
|
4990
|
-
continue;
|
|
4991
|
-
}
|
|
4992
|
-
if (out.mcpServers[row.name]) {
|
|
4993
|
-
continue;
|
|
4994
|
-
}
|
|
4995
|
-
out.mcpServers[row.name] = {
|
|
4996
|
-
type: "stdio",
|
|
4997
|
-
command: row.command,
|
|
4998
|
-
args: row.args
|
|
4999
|
-
};
|
|
5000
|
-
}
|
|
5001
|
-
delete out._comment;
|
|
5002
|
-
return out;
|
|
5003
|
-
}
|
|
5004
|
-
function composeMcpJson(opts) {
|
|
5005
|
-
const base = JSON.parse(readFileSync14(opts.templateMcpPath, "utf8"));
|
|
5006
|
-
const mapRaw = existsSync14(opts.trackMapPath) ? readFileSync14(opts.trackMapPath, "utf8") : "";
|
|
5007
|
-
const rows = parseTrackMcpMap(mapRaw);
|
|
5008
|
-
return mergeMcpServers(base, rows, opts.tracks, opts.chosen(rows), opts.dropped?.(rows) ?? []);
|
|
5009
|
-
}
|
|
5010
|
-
function selectableMcpServers(rows) {
|
|
5011
|
-
const names = new Set(rows.map((r) => r.name));
|
|
5012
|
-
const out = /* @__PURE__ */ new Map();
|
|
5013
|
-
for (const a of EXTERNAL_ASSETS) {
|
|
5014
|
-
if (a.method.kind === "internal" && names.has(a.method.key)) out.set(a.method.key, a.id);
|
|
5015
|
-
}
|
|
5016
|
-
return out;
|
|
5017
|
-
}
|
|
5018
|
-
function selectedMcpServers(rows, choice) {
|
|
5019
|
-
const recorded2 = new Set(
|
|
5020
|
-
(choice.previousLog?.portions ?? []).filter((p2) => p2.path === ".mcp.json").map((p2) => p2.key)
|
|
5021
|
-
);
|
|
5022
|
-
const dropped = new Set(droppedMcpServers(rows, choice.excluded));
|
|
5023
|
-
const out = [];
|
|
5024
|
-
for (const [name, assetId] of selectableMcpServers(rows)) {
|
|
5025
|
-
if (dropped.has(name)) continue;
|
|
5026
|
-
const key = `mcpServers.${name}`;
|
|
5027
|
-
if (isAssetSelected(assetId, choice.spec) || recorded2.has(key)) out.push(name);
|
|
5028
|
-
}
|
|
5029
|
-
return out;
|
|
5030
|
-
}
|
|
5031
|
-
function droppedMcpServers(rows, excluded) {
|
|
5032
|
-
return [...selectableMcpServers(rows)].filter(([name, assetId]) => {
|
|
5033
|
-
const id = keyId(".mcp.json", `mcpServers.${name}`);
|
|
5034
|
-
return excluded.has(assetId) || id !== null && excluded.has(id);
|
|
5035
|
-
}).map(([name]) => name);
|
|
5036
|
-
}
|
|
5037
|
-
function recordedMcpAssetIds(log) {
|
|
5038
|
-
const recorded2 = new Set(
|
|
5039
|
-
(log?.portions ?? []).filter((p2) => p2.path === ".mcp.json").map((p2) => p2.key)
|
|
5040
|
-
);
|
|
5041
|
-
return EXTERNAL_ASSETS.filter(
|
|
5042
|
-
(a) => a.method.kind === "internal" && recorded2.has(`mcpServers.${a.method.key}`)
|
|
5043
|
-
).map((a) => a.id);
|
|
5178
|
+
return readFileSync14(path, "utf8");
|
|
5044
5179
|
}
|
|
5045
5180
|
|
|
5046
5181
|
// src/opencode/transform.ts
|
|
5047
5182
|
init_esm_shims();
|
|
5048
5183
|
import { existsSync as existsSync15, readdirSync as readdirSync2, readFileSync as readFileSync15, unlinkSync } from "fs";
|
|
5049
|
-
import { join as
|
|
5184
|
+
import { join as join20 } from "path";
|
|
5050
5185
|
|
|
5051
5186
|
// src/opencode/opencode-json.ts
|
|
5052
5187
|
init_esm_shims();
|
|
5053
|
-
function
|
|
5188
|
+
function toOpencode(cfg) {
|
|
5189
|
+
if (cfg.type === "http") {
|
|
5190
|
+
return cfg.auth === void 0 ? { type: "remote", url: cfg.url } : {
|
|
5191
|
+
type: "remote",
|
|
5192
|
+
url: cfg.url,
|
|
5193
|
+
oauth: false,
|
|
5194
|
+
headers: { Authorization: `Bearer {env:${cfg.auth.bearerEnv}}` }
|
|
5195
|
+
};
|
|
5196
|
+
}
|
|
5054
5197
|
const local = { type: "local", command: [cfg.command, ...cfg.args] };
|
|
5055
5198
|
if (cfg.env && Object.keys(cfg.env).length > 0) local.environment = { ...cfg.env };
|
|
5056
5199
|
return local;
|
|
5057
5200
|
}
|
|
5058
5201
|
function renderOpencodeMcp(mcp) {
|
|
5059
5202
|
return new Map(
|
|
5060
|
-
Object.entries(mcp.mcpServers).map(([name, cfg]) => [`mcp.${name}`,
|
|
5203
|
+
Object.entries(mcp.mcpServers).map(([name, cfg]) => [`mcp.${name}`, toOpencode(cfg)])
|
|
5061
5204
|
);
|
|
5062
5205
|
}
|
|
5063
5206
|
function renderOpencodeJson(params) {
|
|
5064
5207
|
const config = parseTemplate(params.template);
|
|
5065
5208
|
if (params.mcp) {
|
|
5066
5209
|
config.mcp = Object.fromEntries(
|
|
5067
|
-
Object.entries(params.mcp.mcpServers).map(([name, cfg]) => [name,
|
|
5210
|
+
Object.entries(params.mcp.mcpServers).map(([name, cfg]) => [name, toOpencode(cfg)])
|
|
5068
5211
|
);
|
|
5069
5212
|
}
|
|
5070
5213
|
return `${JSON.stringify(config, null, 2)}
|
|
@@ -5095,7 +5238,7 @@ function runOpencodeTransform(params) {
|
|
|
5095
5238
|
...params.journal === void 0 ? {} : { journal: params.journal }
|
|
5096
5239
|
});
|
|
5097
5240
|
const opencodeTemplate = readRequired3(
|
|
5098
|
-
|
|
5241
|
+
join20(harnessRoot, "templates/opencode/opencode.json.template")
|
|
5099
5242
|
);
|
|
5100
5243
|
const { skillIds, skillWriter } = agentsSkillSlot({
|
|
5101
5244
|
projectDir,
|
|
@@ -5104,7 +5247,7 @@ function runOpencodeTransform(params) {
|
|
|
5104
5247
|
writer
|
|
5105
5248
|
});
|
|
5106
5249
|
ensureDir(projectDir);
|
|
5107
|
-
const opencodeJsonPath =
|
|
5250
|
+
const opencodeJsonPath = join20(projectDir, OPENCODE_JSON);
|
|
5108
5251
|
const render = renderOpencodeMcp(mcp);
|
|
5109
5252
|
const opencodeJson = writeShared({
|
|
5110
5253
|
projectDir,
|
|
@@ -5123,13 +5266,13 @@ function runOpencodeTransform(params) {
|
|
|
5123
5266
|
writer: skillWriter
|
|
5124
5267
|
});
|
|
5125
5268
|
const retiredCommands = [];
|
|
5126
|
-
const cmdDir =
|
|
5269
|
+
const cmdDir = join20(projectDir, ".opencode/commands");
|
|
5127
5270
|
if (existsSync15(cmdDir)) {
|
|
5128
5271
|
for (const entry of readdirSync2(cmdDir)) {
|
|
5129
5272
|
if (!entry.endsWith(".md")) continue;
|
|
5130
5273
|
const id = entry.slice(0, -3);
|
|
5131
|
-
if (!existsSync15(
|
|
5132
|
-
const victim =
|
|
5274
|
+
if (!existsSync15(join20(harnessRoot, "templates/skills", id, "SKILL.md"))) continue;
|
|
5275
|
+
const victim = join20(cmdDir, entry);
|
|
5133
5276
|
if (writer.skipOutside(victim)) continue;
|
|
5134
5277
|
try {
|
|
5135
5278
|
backupFile(victim);
|
|
@@ -5172,6 +5315,14 @@ function trackDefaultMcpAssetIds(harnessRoot, tracks) {
|
|
|
5172
5315
|
return id !== void 0 && anyTrack(tracks, r.pattern) ? [id] : [];
|
|
5173
5316
|
});
|
|
5174
5317
|
}
|
|
5318
|
+
function trackDefaultMcpNames(harnessRoot, tracks) {
|
|
5319
|
+
const { templateMcpPath } = mcpSources(harnessRoot);
|
|
5320
|
+
const base = existsSync16(templateMcpPath) ? Object.keys(JSON.parse(readFileSync16(templateMcpPath, "utf8")).mcpServers) : [];
|
|
5321
|
+
return /* @__PURE__ */ new Set([
|
|
5322
|
+
...base,
|
|
5323
|
+
...trackRows(harnessRoot).filter((r) => anyTrack(tracks, r.pattern)).map((r) => r.name)
|
|
5324
|
+
]);
|
|
5325
|
+
}
|
|
5175
5326
|
function trackRows(harnessRoot) {
|
|
5176
5327
|
const { trackMapPath } = mcpSources(harnessRoot);
|
|
5177
5328
|
return existsSync16(trackMapPath) ? parseTrackMcpMap(readFileSync16(trackMapPath, "utf8")) : [];
|
|
@@ -5194,8 +5345,8 @@ function adapterExcluded(harnessRoot, excluded) {
|
|
|
5194
5345
|
}
|
|
5195
5346
|
function mcpSources(harnessRoot) {
|
|
5196
5347
|
return {
|
|
5197
|
-
templateMcpPath:
|
|
5198
|
-
trackMapPath:
|
|
5348
|
+
templateMcpPath: join21(harnessRoot, "templates/mcp.json"),
|
|
5349
|
+
trackMapPath: join21(harnessRoot, "templates/track-mcp-map.tsv")
|
|
5199
5350
|
};
|
|
5200
5351
|
}
|
|
5201
5352
|
var ALL_CLI_TARGETS = CLI_BASES;
|
|
@@ -5358,7 +5509,7 @@ function runCliTransforms(params) {
|
|
|
5358
5509
|
// src/excluded-still-there.ts
|
|
5359
5510
|
init_esm_shims();
|
|
5360
5511
|
import { existsSync as existsSync17 } from "fs";
|
|
5361
|
-
import { join as
|
|
5512
|
+
import { join as join22 } from "path";
|
|
5362
5513
|
function excludedStillThere(projectDir, excluded, baselineOnDisk, previousLog) {
|
|
5363
5514
|
const out = [];
|
|
5364
5515
|
const add = (id, catalog) => {
|
|
@@ -5371,10 +5522,11 @@ function excludedStillThere(projectDir, excluded, baselineOnDisk, previousLog) {
|
|
|
5371
5522
|
for (const id of excluded) {
|
|
5372
5523
|
if (INTERNAL_BUNDLED_SKILL_IDS.includes(id)) {
|
|
5373
5524
|
const there = [".claude/skills", ".agents/skills"].some(
|
|
5374
|
-
(d2) => existsSync17(
|
|
5525
|
+
(d2) => existsSync17(join22(projectDir, d2, id))
|
|
5375
5526
|
);
|
|
5376
5527
|
if (there) add(id, false);
|
|
5377
5528
|
} else {
|
|
5529
|
+
if (Object.hasOwn(REMOVED_CATALOG_ASSETS, id)) continue;
|
|
5378
5530
|
const asset = previousLog?.assets.find((a) => a.id === id);
|
|
5379
5531
|
if (asset !== void 0 && assetStillThere(projectDir, asset)) add(id, true);
|
|
5380
5532
|
}
|
|
@@ -5384,9 +5536,9 @@ function excludedStillThere(projectDir, excluded, baselineOnDisk, previousLog) {
|
|
|
5384
5536
|
function assetStillThere(projectDir, asset) {
|
|
5385
5537
|
if (asset.method !== "skill" || asset.scope === "global") return true;
|
|
5386
5538
|
if (asset.files !== void 0)
|
|
5387
|
-
return asset.files.some((f) => existsSync17(
|
|
5539
|
+
return asset.files.some((f) => existsSync17(join22(projectDir, f.path)));
|
|
5388
5540
|
const dir = asset.detail.skill ?? asset.id;
|
|
5389
|
-
return [".claude/skills", ".agents/skills"].some((d2) => existsSync17(
|
|
5541
|
+
return [".claude/skills", ".agents/skills"].some((d2) => existsSync17(join22(projectDir, d2, dir)));
|
|
5390
5542
|
}
|
|
5391
5543
|
|
|
5392
5544
|
// src/external-installer.ts
|
|
@@ -5394,7 +5546,7 @@ init_esm_shims();
|
|
|
5394
5546
|
import { spawnSync } from "child_process";
|
|
5395
5547
|
import { existsSync as existsSync18, readFileSync as readFileSync17 } from "fs";
|
|
5396
5548
|
import { homedir as homedir2 } from "os";
|
|
5397
|
-
import { join as
|
|
5549
|
+
import { join as join23 } from "path";
|
|
5398
5550
|
var SKILL_ROOTS = [".claude/skills", ".agents/skills"];
|
|
5399
5551
|
function skillRootOf(cli) {
|
|
5400
5552
|
return cli === "claude" ? ".claude/skills" : ".agents/skills";
|
|
@@ -5402,11 +5554,11 @@ function skillRootOf(cli) {
|
|
|
5402
5554
|
function snapshotSkillRoots(projectDir) {
|
|
5403
5555
|
const out = /* @__PURE__ */ new Map();
|
|
5404
5556
|
for (const root of SKILL_ROOTS) {
|
|
5405
|
-
if (outsideProjectTarget(projectDir,
|
|
5406
|
-
for (const rel of listFilesRecursive(
|
|
5557
|
+
if (outsideProjectTarget(projectDir, join23(projectDir, root)) !== null) continue;
|
|
5558
|
+
for (const rel of listFilesRecursive(join23(projectDir, root))) {
|
|
5407
5559
|
const path = `${root}/${rel}`;
|
|
5408
5560
|
try {
|
|
5409
|
-
out.set(path, hashContent(readFileSync17(
|
|
5561
|
+
out.set(path, hashContent(readFileSync17(join23(projectDir, path), "utf8")));
|
|
5410
5562
|
} catch {
|
|
5411
5563
|
}
|
|
5412
5564
|
}
|
|
@@ -5483,7 +5635,7 @@ function installOne(asset, ctx) {
|
|
|
5483
5635
|
return runSpawn(asset, ctx.spawn, "npx", buildSkillArgs(method, ctx.cli, ctx.scope), cwd);
|
|
5484
5636
|
}
|
|
5485
5637
|
const outside = SKILL_ROOTS.flatMap((root) => {
|
|
5486
|
-
const hit = outsideProjectTarget(cwd,
|
|
5638
|
+
const hit = outsideProjectTarget(cwd, join23(cwd, root));
|
|
5487
5639
|
return hit === null ? [] : [{ root, target: hit.target }];
|
|
5488
5640
|
});
|
|
5489
5641
|
const outRoots = new Set(outside.map((o) => o.root));
|
|
@@ -5664,7 +5816,7 @@ function detectVersion(method, spawn, scope, projectDir) {
|
|
|
5664
5816
|
case "plugin":
|
|
5665
5817
|
return detectPluginVersion(method.pluginId, scope, projectDir);
|
|
5666
5818
|
case "npm": {
|
|
5667
|
-
const pkgJson = scope === "global" ? detectGlobalNpmPackageJsonPath(method.pkg, spawn) :
|
|
5819
|
+
const pkgJson = scope === "global" ? detectGlobalNpmPackageJsonPath(method.pkg, spawn) : join23(projectDir, "node_modules", method.pkg, "package.json");
|
|
5668
5820
|
if (!pkgJson || !existsSync18(pkgJson)) return void 0;
|
|
5669
5821
|
const parsed = JSON.parse(readFileSync17(pkgJson, "utf8"));
|
|
5670
5822
|
return parsed.version;
|
|
@@ -5678,7 +5830,7 @@ function detectVersion(method, spawn, scope, projectDir) {
|
|
|
5678
5830
|
}
|
|
5679
5831
|
function detectPluginVersion(pluginId, scope, projectDir) {
|
|
5680
5832
|
const claudeScope = scope === "global" ? "user" : "project";
|
|
5681
|
-
const path =
|
|
5833
|
+
const path = join23(homedir2(), ".claude/plugins/installed_plugins.json");
|
|
5682
5834
|
if (!existsSync18(path)) return void 0;
|
|
5683
5835
|
const data = JSON.parse(readFileSync17(path, "utf8"));
|
|
5684
5836
|
const entries = data.plugins?.[pluginId] ?? [];
|
|
@@ -5693,7 +5845,7 @@ function detectPluginVersion(pluginId, scope, projectDir) {
|
|
|
5693
5845
|
}
|
|
5694
5846
|
function detectGlobalNpmPackageJsonPath(pkg, spawn) {
|
|
5695
5847
|
const npmRoot = getNpmGlobalRoot(spawn);
|
|
5696
|
-
return npmRoot ?
|
|
5848
|
+
return npmRoot ? join23(npmRoot, pkg, "package.json") : void 0;
|
|
5697
5849
|
}
|
|
5698
5850
|
var npmGlobalRootCache;
|
|
5699
5851
|
function getNpmGlobalRoot(spawn) {
|
|
@@ -5713,7 +5865,7 @@ function getNpmGlobalRoot(spawn) {
|
|
|
5713
5865
|
// src/key-ids.ts
|
|
5714
5866
|
init_esm_shims();
|
|
5715
5867
|
import { readFileSync as readFileSync18 } from "fs";
|
|
5716
|
-
import { join as
|
|
5868
|
+
import { join as join24 } from "path";
|
|
5717
5869
|
function renderedKeyIds(harnessRoot, input, clis) {
|
|
5718
5870
|
const out = /* @__PURE__ */ new Set();
|
|
5719
5871
|
const add = (path, key) => {
|
|
@@ -5736,7 +5888,7 @@ function renderedKeyIds(harnessRoot, input, clis) {
|
|
|
5736
5888
|
for (const name of Object.keys(mcp.mcpServers)) add(".mcp.json", `mcpServers.${name}`);
|
|
5737
5889
|
for (const key of gitignoreRender().keys()) add(".gitignore", key);
|
|
5738
5890
|
if (clis.includes("claude")) {
|
|
5739
|
-
const template = readFileSync18(
|
|
5891
|
+
const template = readFileSync18(join24(harnessRoot, "templates/settings.json"), "utf8");
|
|
5740
5892
|
for (const key of renderSettingsPortion(template, () => true).keys()) {
|
|
5741
5893
|
add(".claude/settings.json", key);
|
|
5742
5894
|
}
|
|
@@ -5777,7 +5929,7 @@ function withoutAccepts(harnessRoot, spec, previous) {
|
|
|
5777
5929
|
|
|
5778
5930
|
// src/linked-skill-bodies.ts
|
|
5779
5931
|
init_esm_shims();
|
|
5780
|
-
import { join as
|
|
5932
|
+
import { join as join25 } from "path";
|
|
5781
5933
|
function refreshLinkedSkillBodies(params) {
|
|
5782
5934
|
const { harnessRoot, projectDir, ids, baseline, journal } = params;
|
|
5783
5935
|
const bodyKey = (id) => `.agents/skills/${id}/SKILL.md`;
|
|
@@ -5788,7 +5940,7 @@ function refreshLinkedSkillBodies(params) {
|
|
|
5788
5940
|
);
|
|
5789
5941
|
const result = writer.result();
|
|
5790
5942
|
return {
|
|
5791
|
-
updated: ours.filter((id) => written.has(
|
|
5943
|
+
updated: ours.filter((id) => written.has(join25(projectDir, bodyKey(id)))),
|
|
5792
5944
|
notOurs: ids.filter((id) => !baseline.has(bodyKey(id))),
|
|
5793
5945
|
files: result.files,
|
|
5794
5946
|
backupPaths: result.backupPaths,
|
|
@@ -5800,7 +5952,7 @@ function refreshLinkedSkillBodies(params) {
|
|
|
5800
5952
|
// src/out-of-track.ts
|
|
5801
5953
|
init_esm_shims();
|
|
5802
5954
|
import { lstatSync as lstatSync3, readFileSync as readFileSync19, rmSync as rmSync3 } from "fs";
|
|
5803
|
-
import { join as
|
|
5955
|
+
import { join as join26 } from "path";
|
|
5804
5956
|
var AGENTS_RULE = /^\.agents\/rules\/([^/]+)\.md$/;
|
|
5805
5957
|
var ANCHOR_RULE = "uzys-harness";
|
|
5806
5958
|
function reclaimOutOfTrack(args) {
|
|
@@ -5820,7 +5972,7 @@ function reclaimOutOfTrack(args) {
|
|
|
5820
5972
|
const name = AGENTS_RULE.exec(f.path)?.[1];
|
|
5821
5973
|
if (name === void 0 || name === ANCHOR_RULE || rendered.has(name)) continue;
|
|
5822
5974
|
if (isBaselineExcluded(`.claude/rules/${name}.md`, args.excluded)) continue;
|
|
5823
|
-
const abs =
|
|
5975
|
+
const abs = join26(projectDir, f.path);
|
|
5824
5976
|
if (args.outside.skip(abs)) continue;
|
|
5825
5977
|
const stat = lstatSync3(abs, { throwIfNoEntry: false });
|
|
5826
5978
|
if (stat !== void 0 && !stat.isFile()) continue;
|
|
@@ -5862,14 +6014,14 @@ function tracksBringing(rule) {
|
|
|
5862
6014
|
// src/state.ts
|
|
5863
6015
|
init_esm_shims();
|
|
5864
6016
|
import { existsSync as existsSync19, readFileSync as readFileSync20 } from "fs";
|
|
5865
|
-
import { join as
|
|
6017
|
+
import { join as join27 } from "path";
|
|
5866
6018
|
var META_FILE = ".claude/.installed-tracks";
|
|
5867
6019
|
var LEGACY_ANCHOR_FILE = ".claude/CLAUDE.md";
|
|
5868
6020
|
var TRACE_RULES = ["htmx", "nextjs", "data-analysis", "pyside6", "cli-development"].map(
|
|
5869
6021
|
(r) => `.claude/rules/${r}.md`
|
|
5870
6022
|
);
|
|
5871
6023
|
function detectInstallState(projectDir) {
|
|
5872
|
-
const hasClaudeDir = existsSync19(
|
|
6024
|
+
const hasClaudeDir = existsSync19(join27(projectDir, ".claude"));
|
|
5873
6025
|
const read = readInstallLogStatus(projectDir);
|
|
5874
6026
|
if (read.status === "ok") {
|
|
5875
6027
|
return {
|
|
@@ -5889,7 +6041,7 @@ function tracksFromLog(tracks) {
|
|
|
5889
6041
|
return [...new Set(tracks.filter(isTrack))].sort();
|
|
5890
6042
|
}
|
|
5891
6043
|
function collectTraces(projectDir) {
|
|
5892
|
-
const at5 = (rel) =>
|
|
6044
|
+
const at5 = (rel) => join27(projectDir, rel);
|
|
5893
6045
|
const contains = (rel, needle) => existsSync19(at5(rel)) && readFileSync20(at5(rel), "utf8").includes(needle);
|
|
5894
6046
|
const traces = [];
|
|
5895
6047
|
if (existsSync19(at5(META_FILE)))
|
|
@@ -5958,7 +6110,7 @@ import {
|
|
|
5958
6110
|
unlinkSync as unlinkSync2,
|
|
5959
6111
|
writeFileSync as writeFileSync7
|
|
5960
6112
|
} from "fs";
|
|
5961
|
-
import { basename as basename3, dirname as dirname5, join as
|
|
6113
|
+
import { basename as basename3, dirname as dirname5, join as join28 } from "path";
|
|
5962
6114
|
var LEGACY_ANCHOR_FILE2 = ".claude/CLAUDE.md";
|
|
5963
6115
|
function buildUpdateSpec(projectDir, tracks, only) {
|
|
5964
6116
|
const log = readInstallLog(projectDir);
|
|
@@ -5980,7 +6132,7 @@ function parseUpdateOnly(raw) {
|
|
|
5980
6132
|
return { ok: true, groups: [...new Set(raw)] };
|
|
5981
6133
|
}
|
|
5982
6134
|
function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
5983
|
-
const claudeDir =
|
|
6135
|
+
const claudeDir = join28(projectDir, ".claude");
|
|
5984
6136
|
const startedAt = Date.now();
|
|
5985
6137
|
const backupsAtStart = new Set(
|
|
5986
6138
|
collectRunBackups(projectDir, Number.NEGATIVE_INFINITY).map((b2) => b2.backup)
|
|
@@ -6026,8 +6178,8 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
|
6026
6178
|
const policyBase = policyBaseline(projectDir);
|
|
6027
6179
|
for (const { dir, ext } of POLICY_DIRS) {
|
|
6028
6180
|
if (!claudeManaged || !wants(dir === "hooks" ? "hooks" : "rules")) continue;
|
|
6029
|
-
const target =
|
|
6030
|
-
const source =
|
|
6181
|
+
const target = join28(claudeDir, dir);
|
|
6182
|
+
const source = join28(templatesDir, dir);
|
|
6031
6183
|
const label = `.claude/${dir}`;
|
|
6032
6184
|
const ctx = { prefix: dir, baseline: policyBase, outside, excluded: excludedIds(logAtStart) };
|
|
6033
6185
|
const synced = updateDir(target, source, ext, ctx);
|
|
@@ -6041,8 +6193,8 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
|
6041
6193
|
if (claudeManaged && syncedDirs.length > 0)
|
|
6042
6194
|
refreshPolicyBaseline(projectDir, templatesDir, syncedDirs, outsidePaths(outside, ".claude/"));
|
|
6043
6195
|
const skillSync = claudeManaged && wants("skills") ? syncSkills(
|
|
6044
|
-
|
|
6045
|
-
|
|
6196
|
+
join28(claudeDir, "skills"),
|
|
6197
|
+
join28(templatesDir, "skills"),
|
|
6046
6198
|
skillBaseline(projectDir),
|
|
6047
6199
|
/* @__PURE__ */ new Date(),
|
|
6048
6200
|
(relInSkills) => foreignOwnedTarget(projectDir, `.claude/skills/${relInSkills}`),
|
|
@@ -6067,7 +6219,9 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
|
6067
6219
|
const harnessMcp = renderHarnessMcp(harnessRoot, {
|
|
6068
6220
|
spec: { tracks: installedTracks(projectDir), options: DEFAULT_OPTIONS },
|
|
6069
6221
|
excluded: excludedIds(logAtStart),
|
|
6070
|
-
previousLog: logAtStart
|
|
6222
|
+
previousLog: logAtStart,
|
|
6223
|
+
// #722 (ADR-106) — 옛 로그면 하네스가 만든 `.mcp.json` 에서 하네스 값 그대로인 고를 수 있는 서버를 선택으로 본다
|
|
6224
|
+
projectDir
|
|
6071
6225
|
});
|
|
6072
6226
|
const sharedWrites = writeUpdateSharedFiles({
|
|
6073
6227
|
projectDir,
|
|
@@ -6078,7 +6232,7 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
|
6078
6232
|
droppedKeys: legacyDroppedKeys(logAtStart),
|
|
6079
6233
|
harnessMcp
|
|
6080
6234
|
});
|
|
6081
|
-
const settingsPath =
|
|
6235
|
+
const settingsPath = join28(claudeDir, "settings.json");
|
|
6082
6236
|
if (claudeManaged && wants("hooks") && existsSync20(settingsPath) && !outside.skip(settingsPath)) {
|
|
6083
6237
|
report.staleHookRefs = cleanStaleHookRefs(
|
|
6084
6238
|
settingsPath,
|
|
@@ -6123,6 +6277,13 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
|
6123
6277
|
report.restoredFiles = external.restoredFiles;
|
|
6124
6278
|
report.excludedKeys = external.excludedKeys;
|
|
6125
6279
|
report.sharedWrites = sharedWrites.writes;
|
|
6280
|
+
const remoteMcp = changedRemoteMcp(sharedWrites.writes);
|
|
6281
|
+
if (remoteMcp.length > 0) {
|
|
6282
|
+
report.remoteMcp = {
|
|
6283
|
+
servers: remoteMcp,
|
|
6284
|
+
clis: logAtStart === null ? [] : installedClis(logAtStart)
|
|
6285
|
+
};
|
|
6286
|
+
}
|
|
6126
6287
|
report.legacyReleasedCatalog = [...legacyReleasedCatalog(logAtStart)];
|
|
6127
6288
|
report.excludedStillThere = excludedStillThere(
|
|
6128
6289
|
projectDir,
|
|
@@ -6165,6 +6326,9 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
|
6165
6326
|
const skillRefresh = wants("external") ? (deps.refreshSkills ?? refreshExternalSkills)(projectDir) : { attempted: 0, refreshed: 0, failed: [], notInCatalog: [], unknown: false };
|
|
6166
6327
|
report.externalSkillsRefreshed = skillRefresh.refreshed;
|
|
6167
6328
|
report.externalSkillsFailed = skillRefresh.failed;
|
|
6329
|
+
const removedOnRecord = wants("external") ? catalogRemovedOnRecord(logAtStart) : [];
|
|
6330
|
+
if (removedOnRecord.length > 0) report.catalogRemoved = removedOnRecord;
|
|
6331
|
+
const saidAsRemoved = new Set(removedOnRecord.map((e2) => e2.id));
|
|
6168
6332
|
report.externalSkillsNotInCatalog = [
|
|
6169
6333
|
...skillRefresh.notInCatalog,
|
|
6170
6334
|
...staleSkillDirs(
|
|
@@ -6172,7 +6336,7 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
|
6172
6336
|
claudeDir,
|
|
6173
6337
|
logAtStart === null ? [] : installedClis(logAtStart)
|
|
6174
6338
|
).filter((id) => !skillRefresh.notInCatalog.includes(id))
|
|
6175
|
-
];
|
|
6339
|
+
].filter((id) => !saidAsRemoved.has(id));
|
|
6176
6340
|
report.externalSkillsUnknown = skillRefresh.unknown;
|
|
6177
6341
|
const outsideLinks = mergeOutside(outside.list(), sharedWrites.outside, external.externalOutside);
|
|
6178
6342
|
if (outsideLinks.length > 0) report.outsideLinks = outsideLinks;
|
|
@@ -6271,7 +6435,7 @@ function retiredFeaturesPart(projectDir, f) {
|
|
|
6271
6435
|
}
|
|
6272
6436
|
function legacyHookOnDisk(projectDir, rel) {
|
|
6273
6437
|
try {
|
|
6274
|
-
return hasLegacyHarnessHook(readFileSync21(
|
|
6438
|
+
return hasLegacyHarnessHook(readFileSync21(join28(projectDir, rel), "utf8"));
|
|
6275
6439
|
} catch {
|
|
6276
6440
|
return false;
|
|
6277
6441
|
}
|
|
@@ -6297,7 +6461,7 @@ function writeUpdateSharedFiles(args) {
|
|
|
6297
6461
|
excluded: adapterExcluded(harnessRoot, excluded)
|
|
6298
6462
|
});
|
|
6299
6463
|
const tracks = installedTracks(projectDir);
|
|
6300
|
-
const settingsSource =
|
|
6464
|
+
const settingsSource = join28(templatesDir, "settings.json");
|
|
6301
6465
|
if (args.claudeManaged && wants("hooks") && existsSync20(settingsSource)) {
|
|
6302
6466
|
const spec = buildAssetSpec({ tracks, options: DEFAULT_OPTIONS });
|
|
6303
6467
|
const manifest = buildManifest(spec);
|
|
@@ -6308,12 +6472,20 @@ function writeUpdateSharedFiles(args) {
|
|
|
6308
6472
|
log,
|
|
6309
6473
|
(script) => {
|
|
6310
6474
|
const target = `.claude/hooks/${script}`;
|
|
6311
|
-
return manifest.some((e2) => e2.target === target && e2.applies(spec)) && !isBaselineExcluded(target, excluded) && existsSync20(
|
|
6475
|
+
return manifest.some((e2) => e2.target === target && e2.applies(spec)) && !isBaselineExcluded(target, excluded) && existsSync20(join28(projectDir, target));
|
|
6312
6476
|
}
|
|
6313
6477
|
// 기록이 claude 를 깔린 CLI 로 말한다 = install 이 이 파일을 썼다 — 없으면 만든다(훅 배선이 여기 산다)
|
|
6314
6478
|
);
|
|
6315
6479
|
}
|
|
6316
|
-
if (wants("external"))
|
|
6480
|
+
if (wants("external")) {
|
|
6481
|
+
writeMcpShared(
|
|
6482
|
+
writer,
|
|
6483
|
+
args.harnessMcp,
|
|
6484
|
+
log,
|
|
6485
|
+
{ onlyIfRecorded: true },
|
|
6486
|
+
{ tracks, names: trackDefaultMcpNames(harnessRoot, tracks) }
|
|
6487
|
+
);
|
|
6488
|
+
}
|
|
6317
6489
|
if (wants("rules")) writeGitignoreShared(writer, log);
|
|
6318
6490
|
const ledger = writer.ledger();
|
|
6319
6491
|
if (ledger.portionPaths.length > 0 || ledger.rootFiles.length > 0) {
|
|
@@ -6344,10 +6516,28 @@ function writeUpdateSharedFiles(args) {
|
|
|
6344
6516
|
outside: ledger.outside
|
|
6345
6517
|
};
|
|
6346
6518
|
}
|
|
6519
|
+
function catalogRemovedOnRecord(log) {
|
|
6520
|
+
if (log === null) return [];
|
|
6521
|
+
const clis = installedClis(log);
|
|
6522
|
+
const out = [];
|
|
6523
|
+
for (const a of log.assets) {
|
|
6524
|
+
if (!Object.hasOwn(REMOVED_CATALOG_ASSETS, a.id)) continue;
|
|
6525
|
+
if (out.some((e2) => e2.id === a.id)) continue;
|
|
6526
|
+
const replacedBy = REMOVED_CATALOG_ASSETS[a.id]?.replacedBy;
|
|
6527
|
+
const entry = { id: a.id, method: a.method };
|
|
6528
|
+
if (replacedBy !== void 0) {
|
|
6529
|
+
entry.replacedBy = replacedBy;
|
|
6530
|
+
const next = EXTERNAL_ASSETS.find((e2) => e2.id === replacedBy);
|
|
6531
|
+
if (next !== void 0) entry.replacementReaches = assetReachesCli(next, clis);
|
|
6532
|
+
}
|
|
6533
|
+
out.push(entry);
|
|
6534
|
+
}
|
|
6535
|
+
return out;
|
|
6536
|
+
}
|
|
6347
6537
|
function staleSkillDirs(projectDir, claudeDir, clis) {
|
|
6348
6538
|
const stale = (id) => RENAMED_SKILL_IDS[id] !== void 0 || RETIRED_SKILL_IDS.includes(id);
|
|
6349
|
-
const dirs = [
|
|
6350
|
-
if (clis.some((cli) => cli !== "claude")) dirs.push(
|
|
6539
|
+
const dirs = [join28(claudeDir, "skills")];
|
|
6540
|
+
if (clis.some((cli) => cli !== "claude")) dirs.push(join28(projectDir, ".agents", "skills"));
|
|
6351
6541
|
const ids = /* @__PURE__ */ new Set();
|
|
6352
6542
|
for (const dir of dirs) {
|
|
6353
6543
|
if (!existsSync20(dir)) continue;
|
|
@@ -6358,13 +6548,13 @@ function staleSkillDirs(projectDir, claudeDir, clis) {
|
|
|
6358
6548
|
return [...ids];
|
|
6359
6549
|
}
|
|
6360
6550
|
function staleAgentFiles(claudeDir) {
|
|
6361
|
-
const dir =
|
|
6551
|
+
const dir = join28(claudeDir, "agents");
|
|
6362
6552
|
if (!existsSync20(dir)) return [];
|
|
6363
6553
|
return readdirSync3(dir, { withFileTypes: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".md")).map((e2) => basename3(e2.name, ".md")).filter((id) => RETIRED_AGENT_IDS.includes(id));
|
|
6364
6554
|
}
|
|
6365
6555
|
function demotedAgentFiles(claudeDir, tracks) {
|
|
6366
6556
|
if (tracks.length === 0) return [];
|
|
6367
|
-
const dir =
|
|
6557
|
+
const dir = join28(claudeDir, "agents");
|
|
6368
6558
|
if (!existsSync20(dir)) return [];
|
|
6369
6559
|
const demoted = TRACK_AGENTS.filter(([, pattern]) => !anyTrack(tracks, pattern)).map(
|
|
6370
6560
|
([id]) => id
|
|
@@ -6387,14 +6577,14 @@ function installNewAssets(projectDir, templatesDir, wants, outside) {
|
|
|
6387
6577
|
if (!wants(group)) continue;
|
|
6388
6578
|
if (entry.target === HARNESS_ANCHOR_FILE) continue;
|
|
6389
6579
|
if (isBaselineExcluded(entry.target, baselineExcluded)) continue;
|
|
6390
|
-
const target =
|
|
6580
|
+
const target = join28(projectDir, entry.target);
|
|
6391
6581
|
const foreign = foreignOwnedTarget(projectDir, entry.target);
|
|
6392
6582
|
if (foreign !== null) {
|
|
6393
6583
|
if (!foreignOwned.includes(foreign)) foreignOwned.push(foreign);
|
|
6394
6584
|
continue;
|
|
6395
6585
|
}
|
|
6396
6586
|
if (existsSync20(target)) continue;
|
|
6397
|
-
const source =
|
|
6587
|
+
const source = join28(templatesDir, entry.source);
|
|
6398
6588
|
if (!existsSync20(source)) continue;
|
|
6399
6589
|
if (outside.skip(target)) continue;
|
|
6400
6590
|
if (entry.target.startsWith(".claude/")) {
|
|
@@ -6421,20 +6611,20 @@ function collectRunBackups(projectDir, startedAt, exclude) {
|
|
|
6421
6611
|
const consider = (rel) => {
|
|
6422
6612
|
if (!BACKUP_SUFFIX.test(rel)) return;
|
|
6423
6613
|
if (exclude?.has(rel)) return;
|
|
6424
|
-
const abs =
|
|
6614
|
+
const abs = join28(projectDir, rel);
|
|
6425
6615
|
if (statSync3(abs).mtimeMs < startedAt - 1e3) return;
|
|
6426
6616
|
out.push({ path: rel.replace(BACKUP_SUFFIX, ""), backup: rel });
|
|
6427
6617
|
};
|
|
6428
6618
|
for (const e2 of readdirSync3(projectDir, { withFileTypes: true }))
|
|
6429
6619
|
if (e2.isFile()) consider(e2.name);
|
|
6430
6620
|
for (const dir of BACKUP_SCAN_DIRS) {
|
|
6431
|
-
for (const rel of listFilesRecursive(
|
|
6621
|
+
for (const rel of listFilesRecursive(join28(projectDir, dir))) consider(`${dir}/${rel}`);
|
|
6432
6622
|
}
|
|
6433
6623
|
return out.sort((a, b2) => a.path.localeCompare(b2.path));
|
|
6434
6624
|
}
|
|
6435
6625
|
function writeBackupList(projectDir, backups, noChecksum = /* @__PURE__ */ new Set()) {
|
|
6436
|
-
const dir =
|
|
6437
|
-
const file =
|
|
6626
|
+
const dir = join28(projectDir, ".uzys-agent-harness");
|
|
6627
|
+
const file = join28(dir, "update-backups.json");
|
|
6438
6628
|
try {
|
|
6439
6629
|
if (backups.length === 0) {
|
|
6440
6630
|
if (existsSync20(file)) unlinkSync2(file);
|
|
@@ -6472,14 +6662,14 @@ function installNewSkillDirs(projectDir, templatesDir, tracks, outside) {
|
|
|
6472
6662
|
if (entry.type !== "dir" || !entry.target.startsWith(".claude/skills/")) continue;
|
|
6473
6663
|
if (!entry.applies(spec) || isBaselineExcluded(entry.target, excluded)) continue;
|
|
6474
6664
|
if (skillExcluded.has(entry.target.slice(".claude/skills/".length))) continue;
|
|
6475
|
-
const target =
|
|
6665
|
+
const target = join28(projectDir, entry.target);
|
|
6476
6666
|
const foreign = foreignOwnedTarget(projectDir, entry.target);
|
|
6477
6667
|
if (foreign !== null) {
|
|
6478
6668
|
if (!foreignOwned.includes(foreign)) foreignOwned.push(foreign);
|
|
6479
6669
|
continue;
|
|
6480
6670
|
}
|
|
6481
6671
|
if (existsSync20(target)) continue;
|
|
6482
|
-
const source =
|
|
6672
|
+
const source = join28(templatesDir, entry.source);
|
|
6483
6673
|
if (!existsSync20(source)) continue;
|
|
6484
6674
|
if (outside.skip(target)) continue;
|
|
6485
6675
|
copyDir(source, target, (rel) => foreignOwnedTarget(projectDir, `${entry.target}/${rel}`));
|
|
@@ -6510,12 +6700,12 @@ function trackOnlyFileAssets(tracks) {
|
|
|
6510
6700
|
);
|
|
6511
6701
|
}
|
|
6512
6702
|
function syncHarnessAnchor(projectDir, templatesDir, report, outside, now = /* @__PURE__ */ new Date()) {
|
|
6513
|
-
if (existsSync20(
|
|
6703
|
+
if (existsSync20(join28(projectDir, LEGACY_ANCHOR_FILE2))) {
|
|
6514
6704
|
report.legacyAnchor = LEGACY_ANCHOR_FILE2;
|
|
6515
6705
|
}
|
|
6516
|
-
const templateMd =
|
|
6706
|
+
const templateMd = join28(templatesDir, "CLAUDE.md");
|
|
6517
6707
|
if (!existsSync20(templateMd)) return;
|
|
6518
|
-
const anchor =
|
|
6708
|
+
const anchor = join28(projectDir, HARNESS_ANCHOR_FILE);
|
|
6519
6709
|
if (outside.skip(anchor)) return;
|
|
6520
6710
|
const existed = existsSync20(anchor);
|
|
6521
6711
|
if (existed) {
|
|
@@ -6541,7 +6731,7 @@ function syncHarnessAnchor(projectDir, templatesDir, report, outside, now = /* @
|
|
|
6541
6731
|
recordAnchorBaseline(projectDir, anchor);
|
|
6542
6732
|
}
|
|
6543
6733
|
function upsertRootImport(projectDir, outside) {
|
|
6544
|
-
const target =
|
|
6734
|
+
const target = join28(projectDir, "CLAUDE.md");
|
|
6545
6735
|
if (outside.skip(target)) return false;
|
|
6546
6736
|
const existing = existsSync20(target) ? readFileSync21(target, "utf8") : null;
|
|
6547
6737
|
const next = upsertHarnessImport(existing, {
|
|
@@ -6550,7 +6740,7 @@ function upsertRootImport(projectDir, outside) {
|
|
|
6550
6740
|
// ADR-085 — update 는 선택 목록을 인자로 받지 않는다. `syncSkills` 와 같은 판정을 쓴다:
|
|
6551
6741
|
// `.claude/skills/<id>` 가 있으면 그 스킬은 이 설치가 고른 것이다.
|
|
6552
6742
|
continuousSkills: CONTINUOUS_SKILLS.map((s) => s.id).filter(
|
|
6553
|
-
(id) => existsSync20(
|
|
6743
|
+
(id) => existsSync20(join28(projectDir, ".claude/skills", id))
|
|
6554
6744
|
)
|
|
6555
6745
|
});
|
|
6556
6746
|
if (next === existing) return false;
|
|
@@ -6585,7 +6775,7 @@ function excludedBaselineOnDisk(projectDir, log) {
|
|
|
6585
6775
|
if (excluded.size === 0) return [];
|
|
6586
6776
|
const spec = buildAssetSpec({ tracks: installedTracks(projectDir), options: DEFAULT_OPTIONS });
|
|
6587
6777
|
return buildManifest(spec).filter(
|
|
6588
|
-
(e2) => e2.applies(spec) && isBaselineExcluded(e2.target, excluded) && existsSync20(
|
|
6778
|
+
(e2) => e2.applies(spec) && isBaselineExcluded(e2.target, excluded) && existsSync20(join28(projectDir, e2.target))
|
|
6589
6779
|
).map((e2) => e2.target);
|
|
6590
6780
|
}
|
|
6591
6781
|
function excludedSkillOf(log) {
|
|
@@ -6612,7 +6802,7 @@ function recordedRenderInputs(projectDir, log) {
|
|
|
6612
6802
|
}
|
|
6613
6803
|
function installedBundledSkills(projectDir) {
|
|
6614
6804
|
return INTERNAL_BUNDLED_SKILL_IDS.filter(
|
|
6615
|
-
(id) => existsSync20(
|
|
6805
|
+
(id) => existsSync20(join28(projectDir, ".claude/skills", id)) || existsSync20(join28(projectDir, ".agents/skills", id))
|
|
6616
6806
|
);
|
|
6617
6807
|
}
|
|
6618
6808
|
function installedCliTargets(log) {
|
|
@@ -6642,9 +6832,9 @@ var SHARED_SKILLS_DIR = ".agents/skills";
|
|
|
6642
6832
|
var SHARED_SKILL_FILE = /^\.agents\/skills\/([^/]+)\//;
|
|
6643
6833
|
var SHARED_SKILL_MD = /^\.agents\/skills\/([^/]+)\/SKILL\.md$/;
|
|
6644
6834
|
function sharedSkillIdsOnDisk(projectDir) {
|
|
6645
|
-
const root =
|
|
6835
|
+
const root = join28(projectDir, SHARED_SKILLS_DIR);
|
|
6646
6836
|
if (!existsSync20(root)) return /* @__PURE__ */ new Set();
|
|
6647
|
-
return new Set(readdirSync3(root).filter((id) => existsSync20(
|
|
6837
|
+
return new Set(readdirSync3(root).filter((id) => existsSync20(join28(root, id, "SKILL.md"))));
|
|
6648
6838
|
}
|
|
6649
6839
|
var CREATED_SHARED_HOW = {
|
|
6650
6840
|
"opencode.json": "created from template (harness part + template defaults)",
|
|
@@ -6661,18 +6851,24 @@ function agentsMdNews(report, existedBefore, priorPaths) {
|
|
|
6661
6851
|
if (!created && report.agentsMdSeededFrom === null) return null;
|
|
6662
6852
|
return { created, clis: [...report.clis], seededFrom: report.agentsMdSeededFrom };
|
|
6663
6853
|
}
|
|
6854
|
+
function changedRemoteMcp(writes) {
|
|
6855
|
+
const mcp = writes.find((w3) => w3.path === ".mcp.json");
|
|
6856
|
+
if (mcp === void 0) return [];
|
|
6857
|
+
const changed = /* @__PURE__ */ new Set([...mcp.added, ...mcp.replaced ?? []]);
|
|
6858
|
+
return (mcp.remote ?? []).filter((r) => changed.has(r.name));
|
|
6859
|
+
}
|
|
6664
6860
|
function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp) {
|
|
6665
6861
|
const log = readInstallLog(projectDir);
|
|
6666
6862
|
const presentBefore = sharedSkillIdsOnDisk(projectDir);
|
|
6667
6863
|
const rulesBefore = /* @__PURE__ */ new Set();
|
|
6668
|
-
const rulesDir =
|
|
6864
|
+
const rulesDir = join28(projectDir, ".agents", "rules");
|
|
6669
6865
|
if (existsSync20(rulesDir)) {
|
|
6670
6866
|
try {
|
|
6671
6867
|
for (const f of readdirSync3(rulesDir)) rulesBefore.add(f);
|
|
6672
6868
|
} catch {
|
|
6673
6869
|
}
|
|
6674
6870
|
}
|
|
6675
|
-
const agentsMdBefore = existsSync20(
|
|
6871
|
+
const agentsMdBefore = existsSync20(join28(projectDir, "AGENTS.md"));
|
|
6676
6872
|
const result = runCliTransforms({
|
|
6677
6873
|
harnessRoot,
|
|
6678
6874
|
projectDir,
|
|
@@ -6814,8 +7010,8 @@ function updateDir(target, source, ext, ctx, now = /* @__PURE__ */ new Date()) {
|
|
|
6814
7010
|
const backedUp = [];
|
|
6815
7011
|
for (const file of readdirSync3(target)) {
|
|
6816
7012
|
if (!file.endsWith(ext)) continue;
|
|
6817
|
-
const targetFile =
|
|
6818
|
-
const sourceFile =
|
|
7013
|
+
const targetFile = join28(target, file);
|
|
7014
|
+
const sourceFile = join28(source, file);
|
|
6819
7015
|
if (!existsSync20(sourceFile)) continue;
|
|
6820
7016
|
if (ctx.outside?.skip(targetFile)) continue;
|
|
6821
7017
|
if (ctx.excluded && isBaselineExcluded(`.claude/${ctx.prefix}/${file}`, ctx.excluded)) continue;
|
|
@@ -6841,22 +7037,22 @@ function syncSkills(targetDir, sourceDir, baseline, now = /* @__PURE__ */ new Da
|
|
|
6841
7037
|
const pruned = [];
|
|
6842
7038
|
for (const skill of readdirSync3(sourceDir, { withFileTypes: true })) {
|
|
6843
7039
|
if (!skill.isDirectory()) continue;
|
|
6844
|
-
const targetSkill =
|
|
7040
|
+
const targetSkill = join28(targetDir, skill.name);
|
|
6845
7041
|
if (occupiedByNonDirectory(targetSkill)) {
|
|
6846
7042
|
skippedLinks.push(skill.name);
|
|
6847
7043
|
continue;
|
|
6848
7044
|
}
|
|
6849
7045
|
if (!existsSync20(targetSkill)) continue;
|
|
6850
7046
|
if (excludedSkill?.(skill.name)) continue;
|
|
6851
|
-
for (const rel of listFilesRecursive(
|
|
6852
|
-
const targetFile =
|
|
7047
|
+
for (const rel of listFilesRecursive(join28(sourceDir, skill.name))) {
|
|
7048
|
+
const targetFile = join28(targetSkill, rel);
|
|
6853
7049
|
const foreign = foreignOf(`${skill.name}/${rel}`);
|
|
6854
7050
|
if (foreign !== null) {
|
|
6855
7051
|
if (!foreignOwned.includes(foreign)) foreignOwned.push(foreign);
|
|
6856
7052
|
continue;
|
|
6857
7053
|
}
|
|
6858
7054
|
if (outside?.skip(targetFile)) continue;
|
|
6859
|
-
const next = readFileSync21(
|
|
7055
|
+
const next = readFileSync21(join28(sourceDir, skill.name, rel), "utf8");
|
|
6860
7056
|
if (!existsSync20(targetFile)) {
|
|
6861
7057
|
mkdirSync5(dirname5(targetFile), { recursive: true });
|
|
6862
7058
|
writeFileSync7(targetFile, next);
|
|
@@ -6873,11 +7069,11 @@ function syncSkills(targetDir, sourceDir, baseline, now = /* @__PURE__ */ new Da
|
|
|
6873
7069
|
writeFileSync7(targetFile, next);
|
|
6874
7070
|
updated++;
|
|
6875
7071
|
}
|
|
6876
|
-
const bundled = new Set(listFilesRecursive(
|
|
7072
|
+
const bundled = new Set(listFilesRecursive(join28(sourceDir, skill.name)));
|
|
6877
7073
|
for (const rel of listFilesRecursive(targetSkill)) {
|
|
6878
7074
|
if (bundled.has(rel) || basename3(rel).includes(".backup-")) continue;
|
|
6879
7075
|
if (foreignOf(`${skill.name}/${rel}`) !== null) continue;
|
|
6880
|
-
const targetFile =
|
|
7076
|
+
const targetFile = join28(targetSkill, rel);
|
|
6881
7077
|
if (outside?.skip(targetFile)) continue;
|
|
6882
7078
|
const recorded2 = baseline.get(`${skill.name}/${rel}`);
|
|
6883
7079
|
if (recorded2 === void 0 || hashContent(readFileSync21(targetFile, "utf8")) !== recorded2) {
|
|
@@ -6944,9 +7140,9 @@ function pruneOrphans(target, source, ext, ctx, now = /* @__PURE__ */ new Date()
|
|
|
6944
7140
|
const removed = [];
|
|
6945
7141
|
for (const file of readdirSync3(target)) {
|
|
6946
7142
|
if (!file.endsWith(ext)) continue;
|
|
6947
|
-
const sourceFile =
|
|
7143
|
+
const sourceFile = join28(source, file);
|
|
6948
7144
|
if (!existsSync20(sourceFile)) {
|
|
6949
|
-
const targetFile =
|
|
7145
|
+
const targetFile = join28(target, file);
|
|
6950
7146
|
if (!ctx.baseline.has(`${ctx.prefix}/${file}`)) continue;
|
|
6951
7147
|
if (ctx.outside?.skip(targetFile)) continue;
|
|
6952
7148
|
try {
|
|
@@ -6961,7 +7157,7 @@ function pruneOrphans(target, source, ext, ctx, now = /* @__PURE__ */ new Date()
|
|
|
6961
7157
|
return removed;
|
|
6962
7158
|
}
|
|
6963
7159
|
function retireMcpAllowlist(projectDir, now = /* @__PURE__ */ new Date()) {
|
|
6964
|
-
const target =
|
|
7160
|
+
const target = join28(projectDir, ".mcp-allowlist");
|
|
6965
7161
|
if (!existsSync20(target)) return null;
|
|
6966
7162
|
try {
|
|
6967
7163
|
const backup = backupFile(target, now);
|
|
@@ -6977,8 +7173,8 @@ function refreshNeutralHelpers(projectDir, templatesDir, report) {
|
|
|
6977
7173
|
for (const entry of buildManifest(spec)) {
|
|
6978
7174
|
if (entry.type !== "file" || !isCliNeutralTarget(entry.target)) continue;
|
|
6979
7175
|
if (!entry.applies(spec)) continue;
|
|
6980
|
-
const abs =
|
|
6981
|
-
const source =
|
|
7176
|
+
const abs = join28(projectDir, entry.target);
|
|
7177
|
+
const source = join28(templatesDir, entry.source);
|
|
6982
7178
|
if (!existsSync20(source)) continue;
|
|
6983
7179
|
let st2;
|
|
6984
7180
|
try {
|
|
@@ -7034,7 +7230,7 @@ function dropHealedHookPortions(projectDir, healedFiles) {
|
|
|
7034
7230
|
const log = readInstallLog(projectDir);
|
|
7035
7231
|
if (log === null) return;
|
|
7036
7232
|
const portions = (log.portions ?? []).filter(
|
|
7037
|
-
(p2) => !(p2.path ===
|
|
7233
|
+
(p2) => !(p2.path === join28(".claude", "settings.json") && isHealedKey(p2.key))
|
|
7038
7234
|
);
|
|
7039
7235
|
if (portions.length > 0) log.portions = portions;
|
|
7040
7236
|
else delete log.portions;
|
|
@@ -7048,11 +7244,11 @@ function dropHealedHookPortions(projectDir, healedFiles) {
|
|
|
7048
7244
|
function runInstall(ctx) {
|
|
7049
7245
|
const { harnessRoot, projectDir } = ctx;
|
|
7050
7246
|
const mode = ctx.mode ?? "fresh";
|
|
7051
|
-
const templatesDir =
|
|
7247
|
+
const templatesDir = join29(harnessRoot, "templates");
|
|
7052
7248
|
if (!existsSync21(templatesDir)) {
|
|
7053
7249
|
throw new Error(`Templates dir not found: ${templatesDir}`);
|
|
7054
7250
|
}
|
|
7055
|
-
const claudeDir =
|
|
7251
|
+
const claudeDir = join29(projectDir, ".claude");
|
|
7056
7252
|
const previousLog = readInstallLog(projectDir);
|
|
7057
7253
|
const legacyAnchor = mode === "update" ? null : legacyAnchorTrace(projectDir, ctx.spec);
|
|
7058
7254
|
if (mode === "update" && previousLog === null) {
|
|
@@ -7081,7 +7277,7 @@ function runInstall(ctx) {
|
|
|
7081
7277
|
const manifestSpec = buildManifestSpec(spec);
|
|
7082
7278
|
const baselineExcluded = new Set(spec.baselineExclude ?? []);
|
|
7083
7279
|
if (spec.cli.includes("claude")) {
|
|
7084
|
-
const settingsPath =
|
|
7280
|
+
const settingsPath = join29(projectDir, ".claude", "settings.json");
|
|
7085
7281
|
if (existsSync21(settingsPath)) {
|
|
7086
7282
|
try {
|
|
7087
7283
|
JSON.parse(readFileSync22(settingsPath, "utf8"));
|
|
@@ -7140,8 +7336,11 @@ function runInstallStages(ctx, args) {
|
|
|
7140
7336
|
installCliNeutralAssets(manifestSpec, templatesDir, baselineExcluded, writer)
|
|
7141
7337
|
);
|
|
7142
7338
|
stage.rootImportWritten = base.rootImportWritten;
|
|
7143
|
-
const harnessMcp = renderHarnessMcp(harnessRoot, { spec, excluded, previousLog });
|
|
7144
|
-
const mcp = writeMcpPortion(writer, harnessMcp, previousLog
|
|
7339
|
+
const harnessMcp = renderHarnessMcp(harnessRoot, { spec, excluded, previousLog, projectDir });
|
|
7340
|
+
const mcp = writeMcpPortion(writer, harnessMcp, previousLog, {
|
|
7341
|
+
tracks: spec.tracks,
|
|
7342
|
+
names: trackDefaultMcpNames(harnessRoot, spec.tracks)
|
|
7343
|
+
});
|
|
7145
7344
|
const ciScaffold = isAssetSelected("ci-scaffold", {
|
|
7146
7345
|
tracks: spec.tracks,
|
|
7147
7346
|
options: spec.options,
|
|
@@ -7358,7 +7557,7 @@ function releasedThisRun(args) {
|
|
|
7358
7557
|
}
|
|
7359
7558
|
if (id.startsWith(BASELINE_PREFIX)) {
|
|
7360
7559
|
const again2 = manifest.some(
|
|
7361
|
-
(e2) => e2.applies(manifestSpec) && classifyBaselineTarget(e2.target)?.id === id && existsSync21(
|
|
7560
|
+
(e2) => e2.applies(manifestSpec) && classifyBaselineTarget(e2.target)?.id === id && existsSync21(join29(projectDir, e2.target))
|
|
7362
7561
|
);
|
|
7363
7562
|
return { id, again: again2, tail: "the harness manages it again" };
|
|
7364
7563
|
}
|
|
@@ -7367,7 +7566,7 @@ function releasedThisRun(args) {
|
|
|
7367
7566
|
options: spec.options,
|
|
7368
7567
|
...spec.userOverride ? { userOverride: spec.userOverride } : {}
|
|
7369
7568
|
});
|
|
7370
|
-
const recorded2 = (previousLog?.assets.some((a) => a.id === id) ?? false) || [".claude/skills", ".agents/skills"].some((d2) => existsSync21(
|
|
7569
|
+
const recorded2 = (previousLog?.assets.some((a) => a.id === id) ?? false) || [".claude/skills", ".agents/skills"].some((d2) => existsSync21(join29(projectDir, d2, id)));
|
|
7371
7570
|
return {
|
|
7372
7571
|
id,
|
|
7373
7572
|
again,
|
|
@@ -7379,10 +7578,10 @@ function pendingKeyExcludes(projectDir, keyExclude, touched) {
|
|
|
7379
7578
|
const done = new Set(touched);
|
|
7380
7579
|
return keyExclude.flatMap((id) => {
|
|
7381
7580
|
const path = sharedPathOfKeyId(id);
|
|
7382
|
-
if (path === null || done.has(path) || !existsSync21(
|
|
7581
|
+
if (path === null || done.has(path) || !existsSync21(join29(projectDir, path))) return [];
|
|
7383
7582
|
const name = adapterFor(path);
|
|
7384
7583
|
const keys = [...excludedKeys(path, [id])];
|
|
7385
|
-
const present2 = name === null ? null : ADAPTERS[name].read(readFileSync22(
|
|
7584
|
+
const present2 = name === null ? null : ADAPTERS[name].read(readFileSync22(join29(projectDir, path), "utf8"), keys, projectDir);
|
|
7386
7585
|
return present2 !== null && present2.size === 0 ? [] : [{ id, path }];
|
|
7387
7586
|
});
|
|
7388
7587
|
}
|
|
@@ -7426,7 +7625,7 @@ function installCliNeutralAssets(manifestSpec, templatesDir, baselineExcluded, w
|
|
|
7426
7625
|
continue;
|
|
7427
7626
|
}
|
|
7428
7627
|
if (!isCliNeutralTarget(entry.target)) continue;
|
|
7429
|
-
const source =
|
|
7628
|
+
const source = join29(templatesDir, entry.source);
|
|
7430
7629
|
if (!existsSync21(source)) {
|
|
7431
7630
|
result.skipped += 1;
|
|
7432
7631
|
continue;
|
|
@@ -7447,13 +7646,13 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
|
|
|
7447
7646
|
if (!entry.applies(manifestSpec)) {
|
|
7448
7647
|
continue;
|
|
7449
7648
|
}
|
|
7450
|
-
const target =
|
|
7649
|
+
const target = join29(projectDir, entry.target);
|
|
7451
7650
|
if (isBaselineExcluded(entry.target, baselineExcluded)) {
|
|
7452
7651
|
result.excluded.push(entry.target);
|
|
7453
7652
|
if (existsSync21(target)) result.excludedOnDisk.push(entry.target);
|
|
7454
7653
|
continue;
|
|
7455
7654
|
}
|
|
7456
|
-
const source =
|
|
7655
|
+
const source = join29(templatesDir, entry.source);
|
|
7457
7656
|
if (!existsSync21(source)) {
|
|
7458
7657
|
result.skipped += 1;
|
|
7459
7658
|
continue;
|
|
@@ -7484,18 +7683,18 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
|
|
|
7484
7683
|
if (!result.foreignOwned.includes(slot)) result.foreignOwned.push(slot);
|
|
7485
7684
|
continue;
|
|
7486
7685
|
}
|
|
7487
|
-
if (writer.skipOutside(
|
|
7686
|
+
if (writer.skipOutside(join29(projectDir, entry.target, rel))) {
|
|
7488
7687
|
leftOutside += 1;
|
|
7489
7688
|
continue;
|
|
7490
7689
|
}
|
|
7491
|
-
writer.harness(`${entry.target}/${rel}`, { source:
|
|
7690
|
+
writer.harness(`${entry.target}/${rel}`, { source: join29(source, rel) });
|
|
7492
7691
|
}
|
|
7493
7692
|
if (leftOutside > 0) continue;
|
|
7494
7693
|
result.dirsCopied += 1;
|
|
7495
7694
|
}
|
|
7496
7695
|
accumulateCategory(result.categories, entry);
|
|
7497
7696
|
}
|
|
7498
|
-
const hookDir =
|
|
7697
|
+
const hookDir = join29(projectDir, ".claude/hooks");
|
|
7499
7698
|
if (existsSync21(hookDir)) {
|
|
7500
7699
|
chmodHooksSync(hookDir, projectDir);
|
|
7501
7700
|
}
|
|
@@ -7512,12 +7711,12 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
|
|
|
7512
7711
|
if (settingsSource !== null) {
|
|
7513
7712
|
writeSettingsPortion(writer, settingsSource, projectDir, previousLog, (script) => {
|
|
7514
7713
|
const target = `${HOOKS_PREFIX}${script}`;
|
|
7515
|
-
return manifest.some((e2) => e2.target === target && e2.applies(manifestSpec)) && !isBaselineExcluded(target, baselineExcluded) && existsSync21(
|
|
7714
|
+
return manifest.some((e2) => e2.target === target && e2.applies(manifestSpec)) && !isBaselineExcluded(target, baselineExcluded) && existsSync21(join29(projectDir, target));
|
|
7516
7715
|
});
|
|
7517
7716
|
result.keptHookRefs.push(
|
|
7518
7717
|
...findStaleHookRefs(
|
|
7519
|
-
|
|
7520
|
-
|
|
7718
|
+
join29(projectDir, ".claude", "settings.json"),
|
|
7719
|
+
join29(projectDir, ".claude")
|
|
7521
7720
|
)
|
|
7522
7721
|
);
|
|
7523
7722
|
}
|
|
@@ -7527,7 +7726,7 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
|
|
|
7527
7726
|
// ADR-085 · update(`upsertRootImport`)와 같은 판정 — 실제로 깔린 스킬만 안내한다. 밖 링크라 쓰지 않은
|
|
7528
7727
|
// 스킬(ADR-098)을 "installed" 라 적으면 다음 update 가 그 절을 걷어 내 두 동작이 서로 반대로 쓴다.
|
|
7529
7728
|
(manifestSpec.selectedInternalSkills ?? []).filter(
|
|
7530
|
-
(id) => existsSync21(
|
|
7729
|
+
(id) => existsSync21(join29(projectDir, ".claude", "skills", id))
|
|
7531
7730
|
),
|
|
7532
7731
|
harnessRoot,
|
|
7533
7732
|
writer
|
|
@@ -7549,8 +7748,8 @@ function writeSettingsPortion(writer, source, projectDir, previousLog, hookInsta
|
|
|
7549
7748
|
hookInstalled
|
|
7550
7749
|
);
|
|
7551
7750
|
}
|
|
7552
|
-
function writeMcpPortion(writer, harnessMcp, previousLog) {
|
|
7553
|
-
return [...writeMcpShared(writer, harnessMcp, previousLog).harness].sort();
|
|
7751
|
+
function writeMcpPortion(writer, harnessMcp, previousLog, defaults) {
|
|
7752
|
+
return [...writeMcpShared(writer, harnessMcp, previousLog, {}, defaults).harness].sort();
|
|
7554
7753
|
}
|
|
7555
7754
|
function installedTracksText(tracks) {
|
|
7556
7755
|
return `${[...new Set(tracks)].sort().join("\n")}
|
|
@@ -7799,7 +7998,7 @@ function accumulateCategory(cats, entry) {
|
|
|
7799
7998
|
}
|
|
7800
7999
|
}
|
|
7801
8000
|
function writeRootClaudeMd(projectDir, tracks, continuousSkills, harnessRoot, writer) {
|
|
7802
|
-
const target =
|
|
8001
|
+
const target = join29(projectDir, "CLAUDE.md");
|
|
7803
8002
|
if (writer.skipOutside(target)) return null;
|
|
7804
8003
|
const existing = existsSync21(target) ? readFileSync22(target, "utf-8") : null;
|
|
7805
8004
|
const seeded = existing === null ? seedRootClaudeProjectContext(projectDir, harnessRoot) : null;
|
|
@@ -7836,7 +8035,7 @@ function listHookFiles(hookDir) {
|
|
|
7836
8035
|
init_esm_shims();
|
|
7837
8036
|
import { homedir as homedir3 } from "os";
|
|
7838
8037
|
import { existsSync as existsSync23, readdirSync as readdirSync5, readFileSync as readFileSync24 } from "fs";
|
|
7839
|
-
import { join as
|
|
8038
|
+
import { join as join31, relative as relative4 } from "path";
|
|
7840
8039
|
|
|
7841
8040
|
// src/preset-recommend.ts
|
|
7842
8041
|
init_esm_shims();
|
|
@@ -7873,9 +8072,9 @@ function groupAssetsByCategory(assetIds) {
|
|
|
7873
8072
|
// src/resident-entries.ts
|
|
7874
8073
|
init_esm_shims();
|
|
7875
8074
|
import { existsSync as existsSync22, readFileSync as readFileSync23 } from "fs";
|
|
7876
|
-
import { basename as basename5, join as
|
|
8075
|
+
import { basename as basename5, join as join30 } from "path";
|
|
7877
8076
|
function residentEntries(spec) {
|
|
7878
|
-
const skillThere = (id) => INTERNAL_BUNDLED_SKILL_IDS.includes(id) && [".claude/skills", ".agents/skills"].some((d2) => existsSync22(
|
|
8077
|
+
const skillThere = (id) => INTERNAL_BUNDLED_SKILL_IDS.includes(id) && [".claude/skills", ".agents/skills"].some((d2) => existsSync22(join30(spec.projectDir, d2, id)));
|
|
7879
8078
|
const forceExclude = (spec.userOverride?.forceExclude ?? []).filter((id) => !skillThere(id));
|
|
7880
8079
|
const assetSpec = buildManifestSpec(
|
|
7881
8080
|
spec.userOverride ? { ...spec, userOverride: { ...spec.userOverride, forceExclude } } : spec
|
|
@@ -7901,7 +8100,7 @@ function residentEntries(spec) {
|
|
|
7901
8100
|
function residentCostFor(spec, entries = residentEntries(spec), contextFiles) {
|
|
7902
8101
|
if (contextFiles !== void 0) return residentCost(entries, void 0, spec.cli, contextFiles);
|
|
7903
8102
|
const cost = residentCost(entries, void 0, spec.cli, plannedClaudeContext(spec));
|
|
7904
|
-
if (anchorSurface(spec.cli).scaffold === "agents-md" && existsSync22(
|
|
8103
|
+
if (anchorSurface(spec.cli).scaffold === "agents-md" && existsSync22(join30(spec.projectDir, "AGENTS.md"))) {
|
|
7905
8104
|
return makeResidentCost({
|
|
7906
8105
|
...cost,
|
|
7907
8106
|
contextNote: "planned, not counting text already in AGENTS.md"
|
|
@@ -7912,7 +8111,7 @@ function residentCostFor(spec, entries = residentEntries(spec), contextFiles) {
|
|
|
7912
8111
|
function plannedClaudeContext(spec) {
|
|
7913
8112
|
if (!spec.cli.includes("claude")) return void 0;
|
|
7914
8113
|
const root = resolveBundleRoot();
|
|
7915
|
-
const rootClaude =
|
|
8114
|
+
const rootClaude = join30(spec.projectDir, "CLAUDE.md");
|
|
7916
8115
|
const existing = existsSync22(rootClaude) ? readFileSync23(rootClaude, "utf-8") : null;
|
|
7917
8116
|
const seeded = existing === null ? seedRootClaudeProjectContext(spec.projectDir, root) : null;
|
|
7918
8117
|
if (existing === null && seeded === null) return void 0;
|
|
@@ -7923,14 +8122,14 @@ function plannedClaudeContext(spec) {
|
|
|
7923
8122
|
continuousSkills: manifestSpec.selectedInternalSkills ?? [],
|
|
7924
8123
|
...seeded === null ? {} : { projectContext: seeded }
|
|
7925
8124
|
});
|
|
7926
|
-
return [
|
|
8125
|
+
return [join30(root, "templates", "CLAUDE.md"), { text }];
|
|
7927
8126
|
}
|
|
7928
8127
|
function onDiskTargets(spec, assetSpec) {
|
|
7929
8128
|
const out = /* @__PURE__ */ new Set();
|
|
7930
8129
|
for (const e2 of buildManifest(assetSpec)) {
|
|
7931
8130
|
if (!e2.applies(assetSpec)) continue;
|
|
7932
8131
|
const t = classifyBaselineTarget(e2.target);
|
|
7933
|
-
if (t !== null && existsSync22(
|
|
8132
|
+
if (t !== null && existsSync22(join30(spec.projectDir, e2.target))) out.add(t.id);
|
|
7934
8133
|
}
|
|
7935
8134
|
return out;
|
|
7936
8135
|
}
|
|
@@ -8052,6 +8251,11 @@ function renderUpdateSummary(log, spec, report) {
|
|
|
8052
8251
|
summarizeContextCost(finalSelectedAssets(spec.tracks, spec.userOverride)).unmeasuredCount
|
|
8053
8252
|
);
|
|
8054
8253
|
if (cost) log(infoRow("CONTEXT", cost));
|
|
8254
|
+
const remote = report.updateMode?.remoteMcp;
|
|
8255
|
+
const remoteLines = remote === void 0 ? [] : remoteMcpNextLines(remote.servers, remote.clis);
|
|
8256
|
+
remoteLines.forEach((line, i) => {
|
|
8257
|
+
log(i === 0 ? infoRow("NEXT", line) : `${" ".repeat(16)} ${line}`);
|
|
8258
|
+
});
|
|
8055
8259
|
const backups = report.updateMode?.backups ?? [];
|
|
8056
8260
|
if (backups.length > 0) {
|
|
8057
8261
|
const unmeasured = report.updateMode?.noChecksum?.length ?? 0;
|
|
@@ -8079,23 +8283,23 @@ function residentEntriesOnDisk(spec) {
|
|
|
8079
8283
|
const hasClaude = spec.cli.includes("claude");
|
|
8080
8284
|
const surface = anchorSurface(spec.cli).scaffold;
|
|
8081
8285
|
const mdFiles = (dir, kind, skip) => {
|
|
8082
|
-
const abs =
|
|
8286
|
+
const abs = join31(spec.projectDir, dir);
|
|
8083
8287
|
return existsSync23(abs) ? readdirSync5(abs, { withFileTypes: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".md") && e2.name !== skip).map((e2) => ({
|
|
8084
8288
|
source: `${kind}/${e2.name}`,
|
|
8085
8289
|
// `target` 은 `residentCost` 의 분류 키다 — 실제 자리는 `file` 이 말한다.
|
|
8086
8290
|
target: `.claude/${kind}/${e2.name}`,
|
|
8087
8291
|
// 배포판이 아니라 **이 프로젝트의 파일**을 잰다 — 사용자가 고친 본문도,
|
|
8088
8292
|
// 배포판에 더는 없는 파일도 실제로 상주하는 것은 이쪽이다.
|
|
8089
|
-
file:
|
|
8293
|
+
file: join31(abs, e2.name)
|
|
8090
8294
|
})) : [];
|
|
8091
8295
|
};
|
|
8092
|
-
const rules = surface === "claude" ? mdFiles(
|
|
8093
|
-
const slot = hasClaude ?
|
|
8296
|
+
const rules = surface === "claude" ? mdFiles(join31(".claude", "rules"), "rules") : rulesInline(spec.cli) ? planned.filter((e2) => e2.target.startsWith(".claude/rules/")) : mdFiles(join31(".agents", "rules"), "rules", "uzys-harness.md");
|
|
8297
|
+
const slot = hasClaude ? join31(".claude", "skills") : join31(".agents", "skills");
|
|
8094
8298
|
const skills = planned.filter((e2) => e2.target.startsWith(".claude/skills/")).map((e2) => ({
|
|
8095
8299
|
...e2,
|
|
8096
|
-
file:
|
|
8300
|
+
file: join31(spec.projectDir, slot, e2.target.slice(".claude/skills/".length))
|
|
8097
8301
|
})).filter((e2) => existsSync23(e2.file));
|
|
8098
|
-
const agents = hasClaude ? mdFiles(
|
|
8302
|
+
const agents = hasClaude ? mdFiles(join31(".claude", "agents"), "agents") : [];
|
|
8099
8303
|
return [
|
|
8100
8304
|
...planned.filter(
|
|
8101
8305
|
(e2) => !e2.target.startsWith(".claude/rules/") && !e2.target.startsWith(".claude/agents/") && !e2.target.startsWith(".claude/skills/")
|
|
@@ -8107,8 +8311,8 @@ function residentEntriesOnDisk(spec) {
|
|
|
8107
8311
|
}
|
|
8108
8312
|
function contextFilesOnDisk(spec) {
|
|
8109
8313
|
const surface = anchorSurface(spec.cli).scaffold;
|
|
8110
|
-
const files = surface === "claude" ? [HARNESS_ANCHOR_FILE, "CLAUDE.md",
|
|
8111
|
-
return files.map((f) =>
|
|
8314
|
+
const files = surface === "claude" ? [HARNESS_ANCHOR_FILE, "CLAUDE.md", join31(".claude", "CLAUDE.md")] : ["AGENTS.md"];
|
|
8315
|
+
return files.map((f) => join31(spec.projectDir, f));
|
|
8112
8316
|
}
|
|
8113
8317
|
function rulesInsideAgentsMd(spec, cost) {
|
|
8114
8318
|
if (anchorSurface(spec.cli).scaffold !== "agents-md" || !rulesInline(spec.cli)) return cost;
|
|
@@ -8362,8 +8566,8 @@ function renderFinalSummary(log, spec, report, fromWizard) {
|
|
|
8362
8566
|
const codexTrusted = report.codexOptIn?.trustEntry.status === "registered" || report.codexOptIn?.trustEntry.status === "already-present";
|
|
8363
8567
|
const alreadyTrustedGlobally = (() => {
|
|
8364
8568
|
try {
|
|
8365
|
-
const home = process.env.CODEX_HOME ??
|
|
8366
|
-
const configPath =
|
|
8569
|
+
const home = process.env.CODEX_HOME ?? join31(homedir3(), ".codex");
|
|
8570
|
+
const configPath = join31(home, "config.toml");
|
|
8367
8571
|
if (!existsSync23(configPath)) return false;
|
|
8368
8572
|
return hasTrustEntry(readFileSync24(configPath, "utf8"), spec.projectDir);
|
|
8369
8573
|
} catch {
|
|
@@ -8387,8 +8591,10 @@ function renderFinalSummary(log, spec, report, fromWizard) {
|
|
|
8387
8591
|
`${" ".repeat(16)} ${c.bold("Codex")} also runs the harness session-start hook only after you review it: at first launch it warns that hooks need review \u2014 open ${c.bold("/hooks")} and trust it. The harness does not approve it for you.`
|
|
8388
8592
|
);
|
|
8389
8593
|
}
|
|
8594
|
+
const remote = (report.shared ?? []).find((f) => f.path === ".mcp.json")?.remote ?? [];
|
|
8595
|
+
for (const line of remoteMcpNextLines(remote, spec.cli)) log(`${" ".repeat(16)} ${line}`);
|
|
8390
8596
|
const scaffoldFiles = scaffoldFilesForCli(spec.cli).filter(
|
|
8391
|
-
(f) => f === "CLAUDE.md" && report.rootClaudeMd?.created === true || hasFillPrompt(
|
|
8597
|
+
(f) => f === "CLAUDE.md" && report.rootClaudeMd?.created === true || hasFillPrompt(join31(spec.projectDir, f))
|
|
8392
8598
|
);
|
|
8393
8599
|
if (scaffoldFiles.length > 0) {
|
|
8394
8600
|
const auditInstalled = buildManifestSpec(spec).selectedInternalSkills?.includes("audit-harness-fit");
|
|
@@ -8734,6 +8940,7 @@ function renderPhase1Rows(log, baseline, verbose = false) {
|
|
|
8734
8940
|
const meta = failed.length === 1 ? `${head.id}: ${head.message}` : `${failed.map((f) => f.id).join(", ")} (${failed.length}\uAC74) \xB7 \uC608: ${head.message}`;
|
|
8735
8941
|
log(assetRow("skip", "external skills", `${meta} \xB7 \uB2E4\uC74C update \uC5D0\uC11C \uB2E4\uC2DC \uC2DC\uB3C4\uD55C\uB2E4`));
|
|
8736
8942
|
}
|
|
8943
|
+
for (const row of catalogRemovedRows(baseline.updateMode.catalogRemoved ?? [])) log(row);
|
|
8737
8944
|
if (baseline.updateMode.externalSkillsNotInCatalog.length > 0) {
|
|
8738
8945
|
const ids = baseline.updateMode.externalSkillsNotInCatalog;
|
|
8739
8946
|
for (const id of ids) {
|
|
@@ -8979,6 +9186,7 @@ function sharedFileRow(f) {
|
|
|
8979
9186
|
if (f.verdict === "leave+advise") return ` ${c.yellow(symbol.skip)} left ${f.path} \u2014 ${f.line}`;
|
|
8980
9187
|
const parts = [f.line];
|
|
8981
9188
|
if (f.added.length > 0) parts.push(`added: ${f.added.join(", ")}`);
|
|
9189
|
+
if (f.path === ".mcp.json") parts.push(...mcpChangeParts(f));
|
|
8982
9190
|
if (f.kept.length > 0) parts.push(`kept yours: ${f.kept.join(", ")}`);
|
|
8983
9191
|
if (f.restored.length > 0) parts.push(restoredKeysPart(f.restored));
|
|
8984
9192
|
parts.push(...excludedKeyParts(f));
|
|
@@ -8989,6 +9197,47 @@ ${assetRow("success", f.path, GITIGNORE_RECORD_DIR_NOTE, 28)}`;
|
|
|
8989
9197
|
}
|
|
8990
9198
|
return row;
|
|
8991
9199
|
}
|
|
9200
|
+
function mcpChangeParts(f) {
|
|
9201
|
+
const why = (name) => f.notes?.[name];
|
|
9202
|
+
const parts = [];
|
|
9203
|
+
const replaced = f.replaced ?? [];
|
|
9204
|
+
if (replaced.length > 0) {
|
|
9205
|
+
parts.push(
|
|
9206
|
+
`updated: ${replaced.map((n) => why(n) === void 0 ? n : `${n} (${why(n)})`).join(", ")}`
|
|
9207
|
+
);
|
|
9208
|
+
}
|
|
9209
|
+
for (const n of f.retired ?? []) {
|
|
9210
|
+
parts.push(why(n) === void 0 ? `retired: ${n}` : `retired: ${n} \u2014 ${why(n)}`);
|
|
9211
|
+
}
|
|
9212
|
+
return parts;
|
|
9213
|
+
}
|
|
9214
|
+
function remoteMcpNextLines(servers, clis) {
|
|
9215
|
+
const reads = clis.filter((c3) => c3 === "claude" || c3 === "codex" || c3 === "opencode");
|
|
9216
|
+
if (servers.length === 0 || reads.length === 0) return [];
|
|
9217
|
+
const oauth = servers.filter((s) => s.bearerEnv === void 0).map((s) => s.name);
|
|
9218
|
+
const lines2 = [];
|
|
9219
|
+
const one = oauth[0];
|
|
9220
|
+
if (one !== void 0) {
|
|
9221
|
+
lines2.push(`Remote MCP servers sign in on first use \u2014 ${oauth.join(" \xB7 ")}`);
|
|
9222
|
+
if (reads.includes("claude"))
|
|
9223
|
+
lines2.push(
|
|
9224
|
+
` Claude Code: approve the project's .mcp.json servers when asked, then /mcp \u2192 Authenticate (or: claude mcp login ${one})`
|
|
9225
|
+
);
|
|
9226
|
+
if (reads.includes("codex"))
|
|
9227
|
+
lines2.push(
|
|
9228
|
+
` Codex: ${oauth.map((n) => `codex mcp login ${n}`).join(" \xB7 ")} (after trusting this folder)`
|
|
9229
|
+
);
|
|
9230
|
+
if (reads.includes("opencode"))
|
|
9231
|
+
lines2.push(` OpenCode: opens the browser by itself (or: opencode mcp auth ${one})`);
|
|
9232
|
+
}
|
|
9233
|
+
for (const s of servers) {
|
|
9234
|
+
if (s.bearerEnv === void 0) continue;
|
|
9235
|
+
lines2.push(
|
|
9236
|
+
`${s.name} needs ${s.bearerEnv} in the shell you start the CLI from \u2014 a GitHub personal access token (fine-grained: https://github.com/settings/personal-access-tokens/new). The harness writes only the variable name, never the token`
|
|
9237
|
+
);
|
|
9238
|
+
}
|
|
9239
|
+
return lines2;
|
|
9240
|
+
}
|
|
8992
9241
|
var GITIGNORE_RECORD_DIR_LINE = ".uzys-agent-harness/";
|
|
8993
9242
|
var GITIGNORE_RECORD_DIR_NOTE = "now ignores only the harness's runtime files \u2014 commit .uzys-agent-harness/ so teammates get the install record";
|
|
8994
9243
|
function excludedKeyParts(f) {
|
|
@@ -9009,6 +9258,27 @@ function excludedKeyParts(f) {
|
|
|
9009
9258
|
}
|
|
9010
9259
|
return parts;
|
|
9011
9260
|
}
|
|
9261
|
+
function catalogRemovedRows(items) {
|
|
9262
|
+
return items.map((e2) => {
|
|
9263
|
+
const parts = [
|
|
9264
|
+
e2.replacedBy === void 0 ? "\uCE74\uD0C8\uB85C\uADF8\uC5D0\uC11C \uBE60\uC84C\uB2E4" : `${e2.replacedBy} \uB85C \uBC14\uB00C\uC5C8\uB2E4`,
|
|
9265
|
+
e2.method === "skill" ? "\uAE50 \uAC83\uC740 \uADF8\uB300\uB85C \uB0A8\uACE0 update \uAC00 \uB354\uB294 \uAC31\uC2E0\uD558\uC9C0 \uC54A\uB294\uB2E4" : "\uAE50 \uAC83\uC740 \uADF8\uB300\uB85C \uB0A8\uB294\uB2E4"
|
|
9266
|
+
];
|
|
9267
|
+
if (e2.replacedBy !== void 0) {
|
|
9268
|
+
if (e2.replacementReaches === false) {
|
|
9269
|
+
const next = EXTERNAL_ASSETS.find((a) => a.id === e2.replacedBy);
|
|
9270
|
+
const only = next ? assetCliSupport(next).map((cli) => CLI_DISPLAY_NAMES[cli]).join(" \xB7 ") : "\uB2E4\uB978 CLI";
|
|
9271
|
+
parts.push(`${e2.replacedBy} \uB294 ${only} \uC804\uC6A9\uC774\uB77C \uC774 \uC124\uCE58\uBCF8\uC758 CLI \uC5D0\uB294 \uAE54\uB9AC\uC9C0 \uC54A\uB294\uB2E4`);
|
|
9272
|
+
} else {
|
|
9273
|
+
parts.push(`\uC0C8 \uAC83\uC740 agent-harness install --with ${e2.replacedBy}`);
|
|
9274
|
+
}
|
|
9275
|
+
parts.push(`\uC61B \uAC83\uC744 \uCE58\uC6B0\uB824\uBA74 agent-harness uninstall --only ${e2.id}`);
|
|
9276
|
+
} else {
|
|
9277
|
+
parts.push(`\uCE58\uC6B0\uB824\uBA74 agent-harness uninstall --only ${e2.id}`);
|
|
9278
|
+
}
|
|
9279
|
+
return assetRow("skip", e2.id, parts.join(" \xB7 "));
|
|
9280
|
+
});
|
|
9281
|
+
}
|
|
9012
9282
|
function excludedStillThereRows(items) {
|
|
9013
9283
|
return items.map(
|
|
9014
9284
|
(e2) => e2.catalog ? ` ${c.yellow(symbol.skip)} ${e2.id} \u2014 excluded, so the harness no longer updates it (still installed). To manage it again: run install (without --without ${e2.id}; add --with ${e2.id} if it is opt-in) \xB7 remove it: agent-harness uninstall --only ${e2.id}` : ` ${c.yellow(symbol.skip)} ${e2.id} \u2014 excluded (still on disk \u2014 an earlier install put it there; the harness does not delete it. Remove the file yourself, or run uninstall) \xB7 to manage it again: run install without --without ${e2.id}`
|
|
@@ -9347,7 +9617,7 @@ function executeSpec(requested, deps = {}) {
|
|
|
9347
9617
|
return;
|
|
9348
9618
|
}
|
|
9349
9619
|
if (deps.mode === "update" && report.backup !== null) {
|
|
9350
|
-
const liveClaude =
|
|
9620
|
+
const liveClaude = join32(spec.projectDir, ".claude");
|
|
9351
9621
|
if (dirTreesIdentical(liveClaude, report.backup)) {
|
|
9352
9622
|
try {
|
|
9353
9623
|
rmSync4(report.backup, { recursive: true, force: true });
|
|
@@ -9435,7 +9705,7 @@ function registerInstallCommand(cli) {
|
|
|
9435
9705
|
// src/commands/list.ts
|
|
9436
9706
|
init_esm_shims();
|
|
9437
9707
|
import { existsSync as existsSync24, readFileSync as readFileSync25 } from "fs";
|
|
9438
|
-
import { join as
|
|
9708
|
+
import { join as join33, resolve as resolve3 } from "path";
|
|
9439
9709
|
function listAction(options = {}, deps = {}) {
|
|
9440
9710
|
const log = deps.log ?? console.log;
|
|
9441
9711
|
const err = deps.err ?? console.error;
|
|
@@ -9538,7 +9808,7 @@ function formatHarnessFileRows(log, projectDir) {
|
|
|
9538
9808
|
const rows = [` ${c.dim(tops.length > 0 ? tops.join(" ") : "(none recorded)")}`];
|
|
9539
9809
|
const rootMd = log.templates.rootClaudeMd;
|
|
9540
9810
|
if (rootMd) {
|
|
9541
|
-
const path =
|
|
9811
|
+
const path = join33(projectDir, rootMd.path);
|
|
9542
9812
|
const modified = existsSync24(path) && hashContent(readFileSync25(path, "utf8")) !== rootMd.sha256;
|
|
9543
9813
|
rows.push(
|
|
9544
9814
|
modified ? ` ${c.dim(rootMd.path)} ${c.yellow("(\uC218\uC815\uB428 \u2014 uninstall \uC2DC \uBCF4\uC874)")}` : ` ${c.dim(rootMd.path)}`
|
|
@@ -9553,7 +9823,7 @@ var ROOT_FILE_CHANGE = {
|
|
|
9553
9823
|
displaced: "\uBE44\uCF1C \uB460"
|
|
9554
9824
|
};
|
|
9555
9825
|
function formatRootFileRows(rootFiles, projectDir) {
|
|
9556
|
-
const present2 = rootFiles.filter((f) => existsSync24(
|
|
9826
|
+
const present2 = rootFiles.filter((f) => existsSync24(join33(projectDir, f.path)));
|
|
9557
9827
|
const width = Math.max(0, ...present2.map((f) => f.path.length));
|
|
9558
9828
|
return present2.map(
|
|
9559
9829
|
(f) => ` ${c.dim(padDisplay(f.path, width))} ${c.dim(ROOT_FILE_CHANGE[f.change])} ${c.dim(f.notes.join(" / "))}`
|
|
@@ -9583,7 +9853,7 @@ import {
|
|
|
9583
9853
|
statSync as statSync4,
|
|
9584
9854
|
writeFileSync as writeFileSync9
|
|
9585
9855
|
} from "fs";
|
|
9586
|
-
import { dirname as dirname6, join as
|
|
9856
|
+
import { dirname as dirname6, join as join34, relative as relative5, resolve as resolve4, sep as sep5 } from "path";
|
|
9587
9857
|
|
|
9588
9858
|
// src/uninstall-interactive.ts
|
|
9589
9859
|
init_esm_shims();
|
|
@@ -10746,7 +11016,11 @@ function buildPageGroups(cats, initialSet, installedSet = /* @__PURE__ */ new Se
|
|
|
10746
11016
|
);
|
|
10747
11017
|
for (const a of catAssets) {
|
|
10748
11018
|
const tier = assetTrustTier(a.id);
|
|
10749
|
-
const badge = tier === "official" ? " \u2605 official" : tier === "experimental" ?
|
|
11019
|
+
const badge = tier === "official" ? " \u2605 official" : tier === "experimental" ? (
|
|
11020
|
+
// #713 — 트랙 기본으로 체크된 experimental(트랙 표의 railway 행)을 "opt-in" 이라 부르면 화면이 체크 상태와 어긋난다.
|
|
11021
|
+
// 이미 깔린 것은 `● installed` 가 말하므로 여기서는 이번 트랙의 기본만 가른다.
|
|
11022
|
+
initialSet.has(`asset:${a.id}`) && !installedSet.has(`asset:${a.id}`) ? " \u26A0 experimental (default on your tracks)" : " \u26A0 experimental (opt-in)"
|
|
11023
|
+
) : "";
|
|
10750
11024
|
items.push({
|
|
10751
11025
|
value: `asset:${a.id}`,
|
|
10752
11026
|
label: ` ${a.id} [${a.source}]${badge}${installedMark(`asset:${a.id}`)}`,
|
|
@@ -11199,7 +11473,7 @@ Proceed?`, initialValue: false });
|
|
|
11199
11473
|
|
|
11200
11474
|
// src/commands/uninstall.ts
|
|
11201
11475
|
function templateDirPath(projectDir, rel) {
|
|
11202
|
-
return
|
|
11476
|
+
return join34(projectDir, rel.replace(/\/+$/, ""));
|
|
11203
11477
|
}
|
|
11204
11478
|
function outsideTemplateLink(projectDir, rel) {
|
|
11205
11479
|
return outsideLinkAt(projectDir, rel.replace(/\/+$/, ""));
|
|
@@ -11422,7 +11696,7 @@ function unwritablePaths(ctx) {
|
|
|
11422
11696
|
const project = ctx.targetAssets.filter((a) => a.scope === "project");
|
|
11423
11697
|
if (project.some((a) => a.method === "npm")) need.add(".");
|
|
11424
11698
|
const parentOf = (path) => {
|
|
11425
|
-
const abs =
|
|
11699
|
+
const abs = join34(projectDir, path);
|
|
11426
11700
|
if (!safeExists(abs)) return;
|
|
11427
11701
|
const real = safeRealpath(abs);
|
|
11428
11702
|
if (real !== null && isOutsideProject(projectDir, real)) return;
|
|
@@ -11433,7 +11707,7 @@ function unwritablePaths(ctx) {
|
|
|
11433
11707
|
if (!ctx.keepTemplates) {
|
|
11434
11708
|
const recordedShared = new Set((installLog.portions ?? []).map((p2) => p2.path));
|
|
11435
11709
|
for (const p2 of ctx.sharedPaths) {
|
|
11436
|
-
if (recordedShared.has(p2) && existsSync25(
|
|
11710
|
+
if (recordedShared.has(p2) && existsSync25(join34(projectDir, p2))) need.add(p2);
|
|
11437
11711
|
}
|
|
11438
11712
|
for (const f of installLog.externalFiles ?? []) {
|
|
11439
11713
|
if (!underAny(f.path, ctx.movedDirs)) parentOf(f.path);
|
|
@@ -11442,7 +11716,7 @@ function unwritablePaths(ctx) {
|
|
|
11442
11716
|
}
|
|
11443
11717
|
return [...need].filter((p2) => {
|
|
11444
11718
|
try {
|
|
11445
|
-
accessSync(
|
|
11719
|
+
accessSync(join34(projectDir, p2), fsConstants.W_OK);
|
|
11446
11720
|
return false;
|
|
11447
11721
|
} catch (e2) {
|
|
11448
11722
|
return e2.code !== "ENOENT";
|
|
@@ -11526,7 +11800,7 @@ function settleLog(ctx, io) {
|
|
|
11526
11800
|
return true;
|
|
11527
11801
|
}
|
|
11528
11802
|
} else {
|
|
11529
|
-
io.rm(
|
|
11803
|
+
io.rm(join34(projectDir, INSTALL_LOG_DIR));
|
|
11530
11804
|
if (outsideTemplateLink(projectDir, LEGACY_INSTALL_LOG_DIR) === null) {
|
|
11531
11805
|
io.rm(legacyInstallLogPath(projectDir));
|
|
11532
11806
|
}
|
|
@@ -11552,7 +11826,7 @@ function dryRunLines(plan, installLog, projectDir, keepTemplates, rootFiles, har
|
|
|
11552
11826
|
for (const rel of dirs) {
|
|
11553
11827
|
const kept = outsideTemplateLink(projectDir, rel);
|
|
11554
11828
|
if (kept) lines2.push(keptOutsideLinkLine(kept, installLog));
|
|
11555
|
-
else if (existsSync25(
|
|
11829
|
+
else if (existsSync25(join34(projectDir, rel))) lines2.push(MOVE_ASIDE_PREVIEW(rel));
|
|
11556
11830
|
}
|
|
11557
11831
|
if (dirs.length === 0) lines2.push(" \u25CB remove templates: (none)");
|
|
11558
11832
|
const rootMd = installLog.templates.rootClaudeMd;
|
|
@@ -11610,7 +11884,7 @@ function backupArtifactAdvisoryLines(projectDir) {
|
|
|
11610
11884
|
found.push(e2.name);
|
|
11611
11885
|
}
|
|
11612
11886
|
}
|
|
11613
|
-
const agents =
|
|
11887
|
+
const agents = join34(projectDir, ".agents");
|
|
11614
11888
|
if (existsSync25(agents)) {
|
|
11615
11889
|
for (const rel of listFilesRecursive(agents)) {
|
|
11616
11890
|
if (stampRe.test(rel)) found.push(`.agents/${rel}`);
|
|
@@ -11626,11 +11900,11 @@ function backupArtifactAdvisoryLines(projectDir) {
|
|
|
11626
11900
|
}
|
|
11627
11901
|
function rootFileAdvisoryLines(rootFiles, projectDir, keepTemplates = false) {
|
|
11628
11902
|
const header = keepTemplates ? "[LEFT] uninstall \uD6C4 \uB0A8\uB294 \uAC83 \u2014 templates \uBCF4\uC874(.claude/ \uB4F1) + \uB8E8\uD2B8 \uD30C\uC77C (\uC790\uB3D9\uC73C\uB85C \uC9C0\uC6B0\uC9C0 \uC54A\uB294\uB2E4):" : "[ROOT] `.claude/` \uBC16\uC5D0 \uB0A8\uB294 \uAC83 (\uC790\uB3D9\uC73C\uB85C \uC9C0\uC6B0\uC9C0 \uC54A\uB294\uB2E4):";
|
|
11629
|
-
const present2 = rootFiles.filter((f) => existsSync25(
|
|
11903
|
+
const present2 = rootFiles.filter((f) => existsSync25(join34(projectDir, f.path)));
|
|
11630
11904
|
if (present2.length === 0) return [];
|
|
11631
11905
|
const outside = /* @__PURE__ */ new Map();
|
|
11632
11906
|
const inside = present2.filter((f) => {
|
|
11633
|
-
const o = outsideProjectTarget(projectDir,
|
|
11907
|
+
const o = outsideProjectTarget(projectDir, join34(projectDir, f.path));
|
|
11634
11908
|
if (o === null) return true;
|
|
11635
11909
|
const group = outside.get(o.link) ?? { link: o, files: [] };
|
|
11636
11910
|
group.files.push(f.path);
|
|
@@ -11669,7 +11943,7 @@ function rootFileMeaning(f, projectDir) {
|
|
|
11669
11943
|
case "displaced": {
|
|
11670
11944
|
const backup = f.notes[0];
|
|
11671
11945
|
if (backup === void 0) return "\uD558\uB124\uC2A4 \uD310\uACFC \uAC19\uC740 \uC124\uCE58\uC790 \uD30C\uC77C\uC774 \uC788\uB358 \uC790\uB9AC (\uADF8\uB300\uB85C \uB450\uC5C8\uB2E4)";
|
|
11672
|
-
return existsSync25(
|
|
11946
|
+
return existsSync25(join34(projectDir, backup)) ? `\uD558\uB124\uC2A4\uAC00 \uC4F4 \uC790\uB9AC \u2014 \uC6D0\uB798 \uC788\uB358 \uC124\uCE58\uC790 \uD30C\uC77C\uC740 ${backup} \uC5D0 \uC788\uB2E4` : `\uD558\uB124\uC2A4\uAC00 \uC4F4 \uC790\uB9AC \u2014 \uC6D0\uB798 \uC788\uB358 \uC124\uCE58\uC790 \uD30C\uC77C\uC758 \uBC31\uC5C5(${backup})\uC774 \uB354\uB294 \uC5C6\uB2E4`;
|
|
11673
11947
|
}
|
|
11674
11948
|
}
|
|
11675
11949
|
}
|
|
@@ -11748,7 +12022,7 @@ function removeCliDryRunLines(plan, ctx, remaining) {
|
|
|
11748
12022
|
for (const dir of plan.dirs) {
|
|
11749
12023
|
const kept = outsideTemplateLink(projectDir, dir);
|
|
11750
12024
|
if (kept) lines2.push(keptOutsideLinkLine(kept, installLog));
|
|
11751
|
-
else if (existsSync25(
|
|
12025
|
+
else if (existsSync25(join34(projectDir, dir))) lines2.push(MOVE_ASIDE_PREVIEW(dir));
|
|
11752
12026
|
}
|
|
11753
12027
|
lines2.push(...previewExternalLines(plan.scoped, projectDir, harnessRoot));
|
|
11754
12028
|
lines2.push(
|
|
@@ -11758,7 +12032,7 @@ function removeCliDryRunLines(plan, ctx, remaining) {
|
|
|
11758
12032
|
)
|
|
11759
12033
|
);
|
|
11760
12034
|
const rootMd = plan.anchor ? installLog.templates.rootClaudeMd : void 0;
|
|
11761
|
-
if (rootMd && existsSync25(
|
|
12035
|
+
if (rootMd && existsSync25(join34(projectDir, rootMd.path))) {
|
|
11762
12036
|
lines2.push(
|
|
11763
12037
|
rootClaudeMdModified(installLog, projectDir) ? ` \u25CB keep ${rootMd.path} (modified since install \u2014 preserved)` : ` \u25CB remove ${rootMd.path}`
|
|
11764
12038
|
);
|
|
@@ -11781,7 +12055,7 @@ function agentsMdRerenderSet(target, remaining, log, projectDir) {
|
|
|
11781
12055
|
const set = agentsMdClis(remaining);
|
|
11782
12056
|
if (set.length === 0) return null;
|
|
11783
12057
|
const recorded2 = (log.externalFiles ?? []).some((f) => f.path === AGENTS_MD2) || (log.portions ?? []).some((p2) => p2.path === AGENTS_MD2);
|
|
11784
|
-
if (!recorded2 || !existsSync25(
|
|
12058
|
+
if (!recorded2 || !existsSync25(join34(projectDir, AGENTS_MD2))) return null;
|
|
11785
12059
|
return set;
|
|
11786
12060
|
}
|
|
11787
12061
|
function rerenderAgentsMd(ctx, set) {
|
|
@@ -11825,14 +12099,14 @@ function rerenderLines(r) {
|
|
|
11825
12099
|
function removeHarnessAnchor(ctx, io) {
|
|
11826
12100
|
const { installLog, projectDir } = ctx;
|
|
11827
12101
|
const rootMd = installLog.templates.rootClaudeMd;
|
|
11828
|
-
if (!rootMd || !existsSync25(
|
|
12102
|
+
if (!rootMd || !existsSync25(join34(projectDir, rootMd.path))) return false;
|
|
11829
12103
|
if (rootClaudeMdModified(installLog, projectDir)) {
|
|
11830
12104
|
io.log(
|
|
11831
12105
|
` ${c.yellow("\u2298")} ${rootMd.path} kept \u2014 modified since install. Remove manually if intended.`
|
|
11832
12106
|
);
|
|
11833
12107
|
return false;
|
|
11834
12108
|
}
|
|
11835
|
-
io.rm(
|
|
12109
|
+
io.rm(join34(projectDir, rootMd.path));
|
|
11836
12110
|
io.log(` ${status.success(`${rootMd.path} removed`)}`);
|
|
11837
12111
|
return true;
|
|
11838
12112
|
}
|
|
@@ -11890,7 +12164,7 @@ function removeCliAction(ctx, io) {
|
|
|
11890
12164
|
io.log(keptOutsideLinkLine(kept, installLog));
|
|
11891
12165
|
continue;
|
|
11892
12166
|
}
|
|
11893
|
-
if (!existsSync25(
|
|
12167
|
+
if (!existsSync25(join34(projectDir, dir))) continue;
|
|
11894
12168
|
const backup = io.moveAside(templateDirPath(projectDir, dir));
|
|
11895
12169
|
if (backup) io.log(movedAsideLine(projectDir, dir, backup));
|
|
11896
12170
|
}
|
|
@@ -11931,7 +12205,7 @@ var SKILLS_LOCK = "skills-lock.json";
|
|
|
11931
12205
|
function withSkillsLock(rootFiles, assets, projectDir) {
|
|
11932
12206
|
const out = [...rootFiles];
|
|
11933
12207
|
const skills = assets.some((a) => a.method === "skill" && a.scope === "project");
|
|
11934
|
-
if (skills && !out.some((f) => f.path === SKILLS_LOCK) && existsSync25(
|
|
12208
|
+
if (skills && !out.some((f) => f.path === SKILLS_LOCK) && existsSync25(join34(projectDir, SKILLS_LOCK))) {
|
|
11935
12209
|
out.push({ path: SKILLS_LOCK, change: "advisory", notes: [] });
|
|
11936
12210
|
}
|
|
11937
12211
|
return out;
|
|
@@ -11949,9 +12223,9 @@ function remnantFor(path, log, harnessRoot) {
|
|
|
11949
12223
|
} catch {
|
|
11950
12224
|
return [];
|
|
11951
12225
|
}
|
|
11952
|
-
const
|
|
11953
|
-
const present2 = ADAPTERS["json-keys"].read(disk,
|
|
11954
|
-
return [...present2].filter(([k2, sha]) =>
|
|
12226
|
+
const known = knownServerValues(servers, (v2) => toClaudeServer(v2));
|
|
12227
|
+
const present2 = ADAPTERS["json-keys"].read(disk, known.keys()) ?? /* @__PURE__ */ new Map();
|
|
12228
|
+
return [...present2].filter(([k2, sha]) => known.get(k2)?.has(sha) === true).map(([k2]) => k2.slice("mcpServers.".length));
|
|
11955
12229
|
};
|
|
11956
12230
|
}
|
|
11957
12231
|
if (path === AGENTS_MD2) {
|
|
@@ -11960,18 +12234,32 @@ function remnantFor(path, log, harnessRoot) {
|
|
|
11960
12234
|
}
|
|
11961
12235
|
if (!clis.includes("opencode")) return () => [];
|
|
11962
12236
|
return (disk) => {
|
|
11963
|
-
let
|
|
12237
|
+
let known;
|
|
11964
12238
|
try {
|
|
11965
|
-
|
|
11966
|
-
renderEverySelectableMcp(harnessRoot, log.spec.tracks.filter(isTrack))
|
|
12239
|
+
known = knownServerValues(
|
|
12240
|
+
renderEverySelectableMcp(harnessRoot, log.spec.tracks.filter(isTrack)).mcpServers,
|
|
12241
|
+
(v2) => [...renderOpencodeMcp({ mcpServers: { x: v2 } }).values()][0],
|
|
12242
|
+
"mcp."
|
|
11967
12243
|
);
|
|
11968
12244
|
} catch {
|
|
11969
12245
|
return [];
|
|
11970
12246
|
}
|
|
11971
|
-
const present2 = ADAPTERS["json-keys"].read(disk,
|
|
11972
|
-
return [...present2].filter(([k2, sha]) =>
|
|
12247
|
+
const present2 = ADAPTERS["json-keys"].read(disk, known.keys()) ?? /* @__PURE__ */ new Map();
|
|
12248
|
+
return [...present2].filter(([k2, sha]) => known.get(k2)?.has(sha) === true).map(([k2]) => k2);
|
|
11973
12249
|
};
|
|
11974
12250
|
}
|
|
12251
|
+
function knownServerValues(servers, form, prefix = "mcpServers.") {
|
|
12252
|
+
const out = /* @__PURE__ */ new Map();
|
|
12253
|
+
const add = (name, v2) => {
|
|
12254
|
+
const key = `${prefix}${name}`;
|
|
12255
|
+
const set = out.get(key) ?? /* @__PURE__ */ new Set();
|
|
12256
|
+
set.add(jsonSha(form(v2)));
|
|
12257
|
+
out.set(key, set);
|
|
12258
|
+
};
|
|
12259
|
+
for (const [name, v2] of Object.entries(servers)) add(name, v2);
|
|
12260
|
+
for (const p2 of PRIOR_MCP_SERVERS) add(p2.name, p2.value);
|
|
12261
|
+
return out;
|
|
12262
|
+
}
|
|
11975
12263
|
function remnantLine(path) {
|
|
11976
12264
|
if (path === ".mcp.json") {
|
|
11977
12265
|
return (names) => `may still hold the harness's MCP servers (not on record): ${names.join(" \xB7 ")} \u2014 delete them by hand if they are not yours`;
|
|
@@ -12055,7 +12343,7 @@ function noReverseHints(asset, ctx) {
|
|
|
12055
12343
|
}
|
|
12056
12344
|
if (asset.method !== "skill") return [];
|
|
12057
12345
|
const name = asset.detail.skill;
|
|
12058
|
-
const left = name === void 0 ? [] : [".claude/skills", ".agents/skills"].map((root) => `${root}/${name}/`).filter((p2) => !underAny(p2, ctx.movedDirs) && existsSync25(
|
|
12346
|
+
const left = name === void 0 ? [] : [".claude/skills", ".agents/skills"].map((root) => `${root}/${name}/`).filter((p2) => !underAny(p2, ctx.movedDirs) && existsSync25(join34(ctx.projectDir, p2)));
|
|
12059
12347
|
return [
|
|
12060
12348
|
left.length > 0 ? `files it put down are not on record (installed by an older version) \u2014 left: ${left.join(" \xB7 ")} (delete by hand if the harness installed it)` : "files it put down are not on record (installed by an older version) \u2014 nothing removed"
|
|
12061
12349
|
];
|
|
@@ -12162,14 +12450,14 @@ function removeTemplates(log, projectDir, io, harnessRoot) {
|
|
|
12162
12450
|
if (rootMd) {
|
|
12163
12451
|
if (rootClaudeMdModified(log, projectDir))
|
|
12164
12452
|
return { rootClaudeMdKept: true, importStripped, external, moved, keptLinks };
|
|
12165
|
-
rm(
|
|
12453
|
+
rm(join34(projectDir, rootMd.path));
|
|
12166
12454
|
}
|
|
12167
12455
|
return { rootClaudeMdKept: false, importStripped, external, moved, keptLinks };
|
|
12168
12456
|
}
|
|
12169
12457
|
var AGENTS_MD2 = "AGENTS.md";
|
|
12170
12458
|
function readAgentsMdTemplate(harnessRoot) {
|
|
12171
12459
|
try {
|
|
12172
|
-
return readFileSync26(
|
|
12460
|
+
return readFileSync26(join34(harnessRoot, AGENTS_MD_TEMPLATE), "utf8");
|
|
12173
12461
|
} catch {
|
|
12174
12462
|
return null;
|
|
12175
12463
|
}
|
|
@@ -12180,7 +12468,7 @@ function recoverMovedSymlinkPortions(log, projectDir, dirs) {
|
|
|
12180
12468
|
for (const path of paths) {
|
|
12181
12469
|
if (path.startsWith(".agents/skills/")) continue;
|
|
12182
12470
|
if (!dirs.some((d2) => path.startsWith(d2))) continue;
|
|
12183
|
-
const abs =
|
|
12471
|
+
const abs = join34(projectDir, path);
|
|
12184
12472
|
if (!safeIsSymlink(abs)) continue;
|
|
12185
12473
|
const target = safeRealpath(abs);
|
|
12186
12474
|
if (target !== null && isOutsideProject(projectDir, target)) {
|
|
@@ -12209,7 +12497,7 @@ function recoverMovedSymlinkTargets(log, projectDir, dirs, rm) {
|
|
|
12209
12497
|
for (const { path, sha256 } of log.externalFiles ?? []) {
|
|
12210
12498
|
if (path.startsWith(".agents/skills/")) continue;
|
|
12211
12499
|
if (!dirs.some((d2) => path.startsWith(d2))) continue;
|
|
12212
|
-
const abs =
|
|
12500
|
+
const abs = join34(projectDir, path);
|
|
12213
12501
|
if (!safeIsSymlink(abs)) continue;
|
|
12214
12502
|
let target;
|
|
12215
12503
|
try {
|
|
@@ -12271,7 +12559,7 @@ function removeExternalFiles(log, projectDir, rm, harnessRoot) {
|
|
|
12271
12559
|
const unjudged = [];
|
|
12272
12560
|
const outside = [];
|
|
12273
12561
|
for (const { path, sha256 } of log.externalFiles ?? []) {
|
|
12274
|
-
const abs =
|
|
12562
|
+
const abs = join34(projectDir, path);
|
|
12275
12563
|
if (!existsSync25(abs)) continue;
|
|
12276
12564
|
let current;
|
|
12277
12565
|
let effective = abs;
|
|
@@ -12320,8 +12608,8 @@ function removeExternalFiles(log, projectDir, rm, harnessRoot) {
|
|
|
12320
12608
|
}
|
|
12321
12609
|
removed.push(path);
|
|
12322
12610
|
}
|
|
12323
|
-
for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname6(
|
|
12324
|
-
const decisions =
|
|
12611
|
+
for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname6(join34(projectDir, path)));
|
|
12612
|
+
const decisions = join34(projectDir, "docs", "decisions");
|
|
12325
12613
|
try {
|
|
12326
12614
|
if (existsSync25(decisions) && readdirSync6(decisions).length === 0)
|
|
12327
12615
|
rmSync5(decisions, { recursive: true });
|
|
@@ -12334,7 +12622,7 @@ function removeToolFiles(files, projectDir, rm) {
|
|
|
12334
12622
|
const removed = [];
|
|
12335
12623
|
let failed = 0;
|
|
12336
12624
|
for (const { path, sha256 } of files) {
|
|
12337
|
-
const abs =
|
|
12625
|
+
const abs = join34(projectDir, path);
|
|
12338
12626
|
let isLink;
|
|
12339
12627
|
try {
|
|
12340
12628
|
isLink = lstatSync5(abs).isSymbolicLink();
|
|
@@ -12362,7 +12650,7 @@ function removeToolFiles(files, projectDir, rm) {
|
|
|
12362
12650
|
notes.push(`left ${path} \u2014 ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
12363
12651
|
}
|
|
12364
12652
|
}
|
|
12365
|
-
for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname6(
|
|
12653
|
+
for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname6(join34(projectDir, path)));
|
|
12366
12654
|
return failed > 0 ? { ok: false, message: `${failed} file(s) could not be removed`, notes } : { ok: true, notes };
|
|
12367
12655
|
}
|
|
12368
12656
|
function pruneEmptyDirsUpward(projectDir, startDir) {
|
|
@@ -12384,8 +12672,8 @@ function stripRootImport(projectDir) {
|
|
|
12384
12672
|
const stripped = current === null ? null : stripHarnessImport(current);
|
|
12385
12673
|
if (stripped === null) return false;
|
|
12386
12674
|
try {
|
|
12387
|
-
backupIfLossyUtf8(
|
|
12388
|
-
writeFileSync9(
|
|
12675
|
+
backupIfLossyUtf8(join34(projectDir, "CLAUDE.md"));
|
|
12676
|
+
writeFileSync9(join34(projectDir, "CLAUDE.md"), stripped, "utf8");
|
|
12389
12677
|
return true;
|
|
12390
12678
|
} catch {
|
|
12391
12679
|
return false;
|
|
@@ -12395,7 +12683,7 @@ function previewExternalLines(installLog, projectDir, harnessRoot, movedDirs = [
|
|
|
12395
12683
|
const lines2 = [];
|
|
12396
12684
|
let removable = 0;
|
|
12397
12685
|
for (const { path, sha256 } of installLog.externalFiles ?? []) {
|
|
12398
|
-
const abs =
|
|
12686
|
+
const abs = join34(projectDir, path);
|
|
12399
12687
|
const underMoved = underAny(path, movedDirs);
|
|
12400
12688
|
const isLink = !underMoved || existsSync25(abs) ? safeIsSymlink(abs) : false;
|
|
12401
12689
|
if (underMoved && !isLink) continue;
|
|
@@ -12479,7 +12767,7 @@ function hasRootImport(projectDir) {
|
|
|
12479
12767
|
return current !== null && stripHarnessImport(current) !== null;
|
|
12480
12768
|
}
|
|
12481
12769
|
function readRootClaudeMd(projectDir) {
|
|
12482
|
-
const path =
|
|
12770
|
+
const path = join34(projectDir, "CLAUDE.md");
|
|
12483
12771
|
if (!existsSync25(path)) return null;
|
|
12484
12772
|
try {
|
|
12485
12773
|
return readFileSync26(path, "utf8");
|
|
@@ -12490,7 +12778,7 @@ function readRootClaudeMd(projectDir) {
|
|
|
12490
12778
|
function rootClaudeMdModified(log, projectDir) {
|
|
12491
12779
|
const rootMd = log.templates.rootClaudeMd;
|
|
12492
12780
|
if (!rootMd) return false;
|
|
12493
|
-
const path =
|
|
12781
|
+
const path = join34(projectDir, rootMd.path);
|
|
12494
12782
|
if (!existsSync25(path)) return false;
|
|
12495
12783
|
return hashContent(readFileSync26(path, "utf8")) !== rootMd.sha256;
|
|
12496
12784
|
}
|
|
@@ -13032,7 +13320,7 @@ function computeUserOverride(tracks, assetIds) {
|
|
|
13032
13320
|
const recommended = new Set(wizardRecommended(tracks));
|
|
13033
13321
|
const selected = new Set(assetIds);
|
|
13034
13322
|
const forceExclude = [...recommended].filter((id) => !selected.has(id)).sort();
|
|
13035
|
-
const forceInclude = [...selected].filter((id) => !recommended.has(id)).sort();
|
|
13323
|
+
const forceInclude = [...selected].filter((id) => !recommended.has(id) && EXTERNAL_ASSETS.some((a) => a.id === id)).sort();
|
|
13036
13324
|
if (forceInclude.length === 0 && forceExclude.length === 0) return void 0;
|
|
13037
13325
|
return { forceInclude, forceExclude };
|
|
13038
13326
|
}
|