@uzysjung/agent-harness 26.119.0 → 26.125.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 +12 -0
- package/README.md +9 -0
- package/dist/{chunk-2VII24JD.js → chunk-ZSLMD5SP.js} +10 -4
- package/dist/{chunk-2VII24JD.js.map → chunk-ZSLMD5SP.js.map} +1 -1
- package/dist/index.js +737 -220
- package/dist/index.js.map +1 -1
- package/dist/trust-tier-drift.js +1 -1
- package/package.json +1 -1
- package/templates/hooks/session-start.sh +24 -3
- package/templates/hooks/spec-drift-check.sh +24 -1
- package/templates/rules/cli-development.md +16 -0
- package/templates/rules/git-policy.md +4 -0
- package/templates/rules/no-false-ship.md +55 -0
- package/templates/skills/continuous-learning-v2/SKILL.md +25 -29
- package/templates/skills/continuous-learning-v2/agents/observer-loop.sh +362 -0
- package/templates/skills/continuous-learning-v2/agents/observer.md +189 -0
- package/templates/skills/continuous-learning-v2/agents/session-guardian.sh +150 -0
- package/templates/skills/continuous-learning-v2/agents/start-observer.sh +252 -0
- package/templates/skills/continuous-learning-v2/hooks/observe.sh +189 -32
- package/templates/skills/continuous-learning-v2/scripts/detect-project.sh +113 -19
- package/templates/skills/continuous-learning-v2/scripts/instinct-cli.py +558 -28
- package/templates/skills/continuous-learning-v2/scripts/lib/homunculus-dir.sh +31 -0
- package/templates/skills/continuous-learning-v2/scripts/migrate-homunculus.sh +68 -0
- package/templates/skills/continuous-learning-v2/scripts/test_parse_instinct.py +1420 -0
- package/templates/skills/harness-health-audit/SKILL.md +14 -2
- package/templates/skills/harness-health-audit/scripts/observation-digest.mjs +212 -0
- package/templates/skills/model-orchestration/SKILL.md +7 -0
- package/templates/skills/multi-persona-review/SKILL.md +22 -0
- package/templates/skills/recurrence-prevention/SKILL.md +4 -0
- package/templates/skills/strategic-compact/SKILL.md +26 -12
package/dist/index.js
CHANGED
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
residentCost,
|
|
28
28
|
resolveScope,
|
|
29
29
|
summarizeContextCost
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-ZSLMD5SP.js";
|
|
31
31
|
|
|
32
32
|
// node_modules/sisteransi/src/index.js
|
|
33
33
|
var require_src = __commonJS({
|
|
@@ -712,7 +712,7 @@ var cac = (name = "") => new CAC(name);
|
|
|
712
712
|
// package.json
|
|
713
713
|
var package_default = {
|
|
714
714
|
name: "@uzysjung/agent-harness",
|
|
715
|
-
version: "26.
|
|
715
|
+
version: "26.125.0",
|
|
716
716
|
description: "Curate vetted AI-coding skills & plugins by your tech stack \u2014 install only what you need, across Claude Code, Codex, OpenCode & Antigravity",
|
|
717
717
|
type: "module",
|
|
718
718
|
publishConfig: {
|
|
@@ -1751,7 +1751,13 @@ function methodDetail(method) {
|
|
|
1751
1751
|
return { key: method.key };
|
|
1752
1752
|
}
|
|
1753
1753
|
}
|
|
1754
|
-
function buildInstallLog(spec, external, scope, rootClaudeMd) {
|
|
1754
|
+
function buildInstallLog(spec, external, scope, rootClaudeMd, previous, claudeDirMovedAside = false, rootFiles = []) {
|
|
1755
|
+
const templates = {
|
|
1756
|
+
claudeDir: ".claude/",
|
|
1757
|
+
...spec.cli.includes("codex") ? { codexDir: ".codex/" } : {},
|
|
1758
|
+
...spec.cli.includes("opencode") ? { opencodeDir: ".opencode/" } : {},
|
|
1759
|
+
...rootClaudeMd ? { rootClaudeMd } : {}
|
|
1760
|
+
};
|
|
1755
1761
|
const log = {
|
|
1756
1762
|
schemaVersion: INSTALL_LOG_VERSION,
|
|
1757
1763
|
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1760,16 +1766,63 @@ function buildInstallLog(spec, external, scope, rootClaudeMd) {
|
|
|
1760
1766
|
tracks: spec.tracks,
|
|
1761
1767
|
cli: spec.cli
|
|
1762
1768
|
},
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1769
|
+
// 이번 설치가 만든 항목이 이기고, 이번에 안 만든 항목은 이전 값을 그대로 둔다.
|
|
1770
|
+
// (예: claude 로 깔고 나중에 codex 만 추가 설치해도 root CLAUDE.md 기록이 살아남는다)
|
|
1771
|
+
templates: { ...previous?.templates, ...templates },
|
|
1772
|
+
assets: mergeAssets(
|
|
1773
|
+
claudeDirMovedAside ? previous?.assets?.filter(survivesClaudeDirRename) : previous?.assets,
|
|
1774
|
+
buildAssetEntries(external, scope)
|
|
1775
|
+
)
|
|
1770
1776
|
};
|
|
1777
|
+
const mergedRootFiles = mergeRootFiles(previous?.rootFiles, rootFiles);
|
|
1778
|
+
if (mergedRootFiles.length > 0) log.rootFiles = mergedRootFiles;
|
|
1771
1779
|
return log;
|
|
1772
1780
|
}
|
|
1781
|
+
function mergeRootFiles(previous, current) {
|
|
1782
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
1783
|
+
for (const file of [...previous ?? [], ...current]) {
|
|
1784
|
+
const prior = byPath.get(file.path);
|
|
1785
|
+
byPath.set(
|
|
1786
|
+
file.path,
|
|
1787
|
+
prior ? {
|
|
1788
|
+
path: file.path,
|
|
1789
|
+
change: prior.change === "created" ? "created" : file.change,
|
|
1790
|
+
notes: [.../* @__PURE__ */ new Set([...prior.notes, ...file.notes])]
|
|
1791
|
+
} : file
|
|
1792
|
+
);
|
|
1793
|
+
}
|
|
1794
|
+
return [...byPath.values()];
|
|
1795
|
+
}
|
|
1796
|
+
function survivesClaudeDirRename(asset) {
|
|
1797
|
+
if (asset.scope === "global") return true;
|
|
1798
|
+
switch (asset.method) {
|
|
1799
|
+
case "plugin":
|
|
1800
|
+
return true;
|
|
1801
|
+
// `~/.claude/plugins/cache` — 프로젝트 밖
|
|
1802
|
+
case "npm":
|
|
1803
|
+
return true;
|
|
1804
|
+
// `node_modules/`
|
|
1805
|
+
case "skill":
|
|
1806
|
+
return false;
|
|
1807
|
+
// `npx skills add` project scope → `.claude/skills/`
|
|
1808
|
+
case "shell-script":
|
|
1809
|
+
return false;
|
|
1810
|
+
// ecc-prune → `.claude/local-plugins/`
|
|
1811
|
+
case "npx-run":
|
|
1812
|
+
return false;
|
|
1813
|
+
case "internal":
|
|
1814
|
+
return false;
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
function mergeAssets(previous, current) {
|
|
1818
|
+
if (!previous || previous.length === 0) return [...current];
|
|
1819
|
+
const currentById = new Map(current.map((a) => [a.id, a]));
|
|
1820
|
+
const previousIds = new Set(previous.map((a) => a.id));
|
|
1821
|
+
return [
|
|
1822
|
+
...previous.map((a) => currentById.get(a.id) ?? a),
|
|
1823
|
+
...current.filter((a) => !previousIds.has(a.id))
|
|
1824
|
+
];
|
|
1825
|
+
}
|
|
1773
1826
|
function hashContent(content) {
|
|
1774
1827
|
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
1775
1828
|
}
|
|
@@ -2185,7 +2238,8 @@ function keepHookRef(hook, hooksDir, removed) {
|
|
|
2185
2238
|
}
|
|
2186
2239
|
|
|
2187
2240
|
// src/installer.ts
|
|
2188
|
-
var
|
|
2241
|
+
var KARPATHY_HOOK_RELPATH = ".claude/hooks/karpathy-gate.sh";
|
|
2242
|
+
var KARPATHY_HOOK_COMMAND = `bash "$CLAUDE_PROJECT_DIR/${KARPATHY_HOOK_RELPATH}"`;
|
|
2189
2243
|
var KARPATHY_ASSET_ID = "karpathy-coder";
|
|
2190
2244
|
function runInstall(ctx) {
|
|
2191
2245
|
const { harnessRoot, projectDir, spec } = ctx;
|
|
@@ -2198,6 +2252,7 @@ function runInstall(ctx) {
|
|
|
2198
2252
|
if (mode === "update" && !existsSync12(claudeDir)) {
|
|
2199
2253
|
throw new Error(`Update mode requires existing .claude/ at ${claudeDir}`);
|
|
2200
2254
|
}
|
|
2255
|
+
const previousLog = readInstallLog(projectDir);
|
|
2201
2256
|
const backupPath = resolveBackupPath(ctx, mode, claudeDir);
|
|
2202
2257
|
if (mode === "update") {
|
|
2203
2258
|
return runUpdateInstall(ctx, templatesDir, backupPath);
|
|
@@ -2229,7 +2284,14 @@ function runInstall(ctx) {
|
|
|
2229
2284
|
ctx.onProgress?.({ type: "baseline-complete", baseline });
|
|
2230
2285
|
const external = runExternalPhase(ctx);
|
|
2231
2286
|
const karpathyHook = wireKarpathyHook(spec, external, harnessRoot, projectDir);
|
|
2232
|
-
writeInstallLogSafe(
|
|
2287
|
+
writeInstallLogSafe(
|
|
2288
|
+
ctx,
|
|
2289
|
+
external,
|
|
2290
|
+
base.rootClaudeMdLog,
|
|
2291
|
+
previousLog,
|
|
2292
|
+
backupPath !== null,
|
|
2293
|
+
collectRootFiles(baseline.envFiles, ciScaffold, mcpResult.created)
|
|
2294
|
+
);
|
|
2233
2295
|
return { ...baseline, external, karpathyHook };
|
|
2234
2296
|
}
|
|
2235
2297
|
function resolveBackupPath(ctx, mode, claudeDir) {
|
|
@@ -2411,9 +2473,17 @@ function runExternalPhase(ctx) {
|
|
|
2411
2473
|
ctx.onProgress?.({ type: "external-complete", report: external });
|
|
2412
2474
|
return external;
|
|
2413
2475
|
}
|
|
2414
|
-
function writeInstallLogSafe(ctx, external, rootClaudeMdLog) {
|
|
2476
|
+
function writeInstallLogSafe(ctx, external, rootClaudeMdLog, previousLog, claudeDirMovedAside, rootFiles) {
|
|
2415
2477
|
try {
|
|
2416
|
-
const log = buildInstallLog(
|
|
2478
|
+
const log = buildInstallLog(
|
|
2479
|
+
ctx.spec,
|
|
2480
|
+
external,
|
|
2481
|
+
resolveScope(ctx.spec.scope),
|
|
2482
|
+
rootClaudeMdLog,
|
|
2483
|
+
previousLog,
|
|
2484
|
+
claudeDirMovedAside,
|
|
2485
|
+
rootFiles
|
|
2486
|
+
);
|
|
2417
2487
|
writeInstallLog(ctx.projectDir, log);
|
|
2418
2488
|
} catch (e2) {
|
|
2419
2489
|
ctx.onProgress?.({
|
|
@@ -2437,7 +2507,7 @@ function wireKarpathyHook(spec, external, harnessRoot, projectDir) {
|
|
|
2437
2507
|
return { wired: false, reason: "plugin-install-failed" };
|
|
2438
2508
|
}
|
|
2439
2509
|
const sourceHook = join11(harnessRoot, "templates/hooks/karpathy-gate.sh");
|
|
2440
|
-
const targetHook = join11(projectDir,
|
|
2510
|
+
const targetHook = join11(projectDir, KARPATHY_HOOK_RELPATH);
|
|
2441
2511
|
let hookScriptCopied = false;
|
|
2442
2512
|
if (existsSync12(sourceHook)) {
|
|
2443
2513
|
copyFile(sourceHook, targetHook);
|
|
@@ -2470,6 +2540,7 @@ function wireKarpathyHook(spec, external, harnessRoot, projectDir) {
|
|
|
2470
2540
|
}
|
|
2471
2541
|
function composeAndWriteMcp(harnessRoot, projectDir, spec) {
|
|
2472
2542
|
const mcpPath = join11(projectDir, ".mcp.json");
|
|
2543
|
+
const created = !existsSync12(mcpPath);
|
|
2473
2544
|
const composed = composeMcpJson({
|
|
2474
2545
|
templateMcpPath: join11(harnessRoot, "templates/mcp.json"),
|
|
2475
2546
|
trackMapPath: join11(harnessRoot, "templates/track-mcp-map.tsv"),
|
|
@@ -2477,7 +2548,41 @@ function composeAndWriteMcp(harnessRoot, projectDir, spec) {
|
|
|
2477
2548
|
tracks: spec.tracks
|
|
2478
2549
|
});
|
|
2479
2550
|
writeMcpJson(mcpPath, composed);
|
|
2480
|
-
return composed;
|
|
2551
|
+
return { ...composed, created };
|
|
2552
|
+
}
|
|
2553
|
+
function collectRootFiles(envFiles, ciScaffold, mcpCreated) {
|
|
2554
|
+
const files = [
|
|
2555
|
+
{
|
|
2556
|
+
path: ".mcp.json",
|
|
2557
|
+
change: mcpCreated ? "created" : "modified",
|
|
2558
|
+
notes: [mcpCreated ? "MCP \uC11C\uBC84 \uC815\uC758 \uC0DD\uC131" : "MCP \uC11C\uBC84 \uC815\uC758 \uBCD1\uD569 (\uAE30\uC874 \uD56D\uBAA9 \uBCF4\uC874)"]
|
|
2559
|
+
}
|
|
2560
|
+
];
|
|
2561
|
+
if (envFiles.envExampleCreated) {
|
|
2562
|
+
files.push({ path: ".env.example", change: "created", notes: ["Supabase \uD1A0\uD070 \uAC00\uC774\uB4DC"] });
|
|
2563
|
+
}
|
|
2564
|
+
if (envFiles.mcpAllowlist && envFiles.mcpAllowlist.length > 0) {
|
|
2565
|
+
files.push({
|
|
2566
|
+
path: ".mcp-allowlist",
|
|
2567
|
+
change: "created",
|
|
2568
|
+
notes: [`MCP allowlist (${envFiles.mcpAllowlist.length} server)`]
|
|
2569
|
+
});
|
|
2570
|
+
}
|
|
2571
|
+
const gitignoreAdded = [
|
|
2572
|
+
...envFiles.gitignoreEnvAdded ? [".env"] : [],
|
|
2573
|
+
...envFiles.gitignoreNpxSkillsAdded
|
|
2574
|
+
];
|
|
2575
|
+
if (gitignoreAdded.length > 0) {
|
|
2576
|
+
files.push({
|
|
2577
|
+
path: ".gitignore",
|
|
2578
|
+
change: "modified",
|
|
2579
|
+
notes: [`\uCD94\uAC00\uB41C \uC904: ${gitignoreAdded.join(", ")}`]
|
|
2580
|
+
});
|
|
2581
|
+
}
|
|
2582
|
+
for (const workflow of ciScaffold?.written ?? []) {
|
|
2583
|
+
files.push({ path: workflow, change: "created", notes: ["CI \uC6CC\uD06C\uD50C\uB85C \uC2A4\uCE90\uD3F4\uB4DC"] });
|
|
2584
|
+
}
|
|
2585
|
+
return files;
|
|
2481
2586
|
}
|
|
2482
2587
|
function accumulateCategory(cats, entry) {
|
|
2483
2588
|
const target = entry.target;
|
|
@@ -3169,228 +3274,102 @@ function registerInstallCommand(cli2) {
|
|
|
3169
3274
|
).option("--verbose", "[Misc] Show installed file lists per category (default: counts only)").example("install --track tooling --with karpathy-coder").example("install --track csr-supabase --cli claude --cli codex").example("install --track csr-supabase --without netlify-cli --with railway-skills").action((options) => installAction(options));
|
|
3170
3275
|
}
|
|
3171
3276
|
|
|
3172
|
-
// src/commands/
|
|
3277
|
+
// src/commands/list.ts
|
|
3173
3278
|
init_esm_shims();
|
|
3174
|
-
import {
|
|
3175
|
-
import { existsSync as existsSync13, readFileSync as readFileSync12, rmSync } from "fs";
|
|
3279
|
+
import { existsSync as existsSync13, readFileSync as readFileSync12 } from "fs";
|
|
3176
3280
|
import { join as join12, resolve as resolve3 } from "path";
|
|
3177
|
-
function
|
|
3281
|
+
function listAction(options = {}, deps = {}) {
|
|
3178
3282
|
const log = deps.log ?? console.log;
|
|
3179
3283
|
const err = deps.err ?? console.error;
|
|
3180
3284
|
const exit = deps.exit ?? ((code) => process.exit(code));
|
|
3181
|
-
const spawn = deps.spawn ?? defaultSpawn2;
|
|
3182
|
-
const rm = deps.rm ?? defaultRm;
|
|
3183
3285
|
const projectDir = resolve3(options.projectDir ?? process.cwd());
|
|
3184
3286
|
const installLog = readInstallLog(projectDir);
|
|
3185
3287
|
if (!installLog) {
|
|
3186
3288
|
err(status.failure(c.red(`ERROR: install log not found at ${installLogPath(projectDir)}`)));
|
|
3187
|
-
err(c.dim("
|
|
3289
|
+
err(c.dim(" Nothing installed here by agent-harness."));
|
|
3188
3290
|
exit(1);
|
|
3189
3291
|
return;
|
|
3190
3292
|
}
|
|
3191
|
-
const { reverseSteps, globalAdvisories } = planReverse(installLog, spawn, rm, projectDir);
|
|
3192
3293
|
log("");
|
|
3193
|
-
log(c.bold("uzys-agent-harness \xB7
|
|
3294
|
+
log(c.bold("uzys-agent-harness \xB7 installed"));
|
|
3194
3295
|
log("");
|
|
3195
3296
|
log(c.dim(` installed: ${installLog.installedAt}`));
|
|
3196
3297
|
log(c.dim(` scope: ${installLog.scope}`));
|
|
3197
|
-
log(c.dim(`
|
|
3298
|
+
log(c.dim(` tracks: ${installLog.spec.tracks.join(", ") || "(none)"}`));
|
|
3299
|
+
log(c.dim(` cli: ${installLog.spec.cli.join(", ") || "(none)"}`));
|
|
3198
3300
|
log("");
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
log("");
|
|
3202
|
-
if (reverseSteps.length === 0) {
|
|
3203
|
-
log(c.dim(" (no project-scope assets to reverse)"));
|
|
3204
|
-
}
|
|
3205
|
-
for (const step of reverseSteps) {
|
|
3206
|
-
log(` \u25CB ${step.label}`);
|
|
3207
|
-
}
|
|
3208
|
-
if (!options.keepTemplates) {
|
|
3209
|
-
log(` \u25CB remove templates: ${formatTemplateList(installLog)}`);
|
|
3210
|
-
if (installLog.templates.rootClaudeMd) {
|
|
3211
|
-
log(
|
|
3212
|
-
rootClaudeMdModified(installLog, projectDir) ? " \u25CB keep CLAUDE.md (modified since install \u2014 preserved)" : " \u25CB remove CLAUDE.md"
|
|
3213
|
-
);
|
|
3214
|
-
}
|
|
3215
|
-
}
|
|
3216
|
-
if (globalAdvisories.length > 0) {
|
|
3217
|
-
log("");
|
|
3218
|
-
log(
|
|
3219
|
-
c.yellow(
|
|
3220
|
-
`[GLOBAL] ${globalAdvisories.length} asset(s) at scope=global \u2014 manual removal required (D16):`
|
|
3221
|
-
)
|
|
3222
|
-
);
|
|
3223
|
-
for (const adv of globalAdvisories) {
|
|
3224
|
-
log(c.dim(` \xB7 ${adv.asset.id} (${adv.asset.method}) \u2192 ${adv.command}`));
|
|
3225
|
-
}
|
|
3226
|
-
}
|
|
3227
|
-
log("");
|
|
3228
|
-
exit(0);
|
|
3229
|
-
return;
|
|
3230
|
-
}
|
|
3231
|
-
let succeeded = 0;
|
|
3232
|
-
let failed = 0;
|
|
3233
|
-
for (const step of reverseSteps) {
|
|
3234
|
-
const result = step.execute();
|
|
3235
|
-
if (result.ok) {
|
|
3236
|
-
log(` ${status.success("\u2713")} ${step.label}`);
|
|
3237
|
-
succeeded++;
|
|
3238
|
-
} else {
|
|
3239
|
-
log(` ${c.yellow("\u2298")} ${step.label} (${result.message ?? "failed"})`);
|
|
3240
|
-
failed++;
|
|
3241
|
-
}
|
|
3301
|
+
log(c.bold(` Assets (${installLog.assets.length})`));
|
|
3302
|
+
if (installLog.assets.length === 0) {
|
|
3303
|
+
log(c.dim(" (none \u2014 \uB0B4\uBD80 \uD15C\uD50C\uB9BF\uB9CC \uC124\uCE58\uB428)"));
|
|
3242
3304
|
}
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
log(` ${status.success("\u2713")} templates removed: ${formatTemplateList(installLog)}`);
|
|
3246
|
-
if (rootClaudeMdKept) {
|
|
3247
|
-
log(
|
|
3248
|
-
` ${c.yellow("\u2298")} CLAUDE.md kept \u2014 modified since install. Remove manually if intended.`
|
|
3249
|
-
);
|
|
3250
|
-
}
|
|
3305
|
+
for (const line of formatAssetRows(installLog.assets)) {
|
|
3306
|
+
log(line);
|
|
3251
3307
|
}
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3308
|
+
log("");
|
|
3309
|
+
log(c.bold(" Templates"));
|
|
3310
|
+
for (const line of formatTemplateRows(installLog, projectDir)) {
|
|
3311
|
+
log(line);
|
|
3255
3312
|
}
|
|
3256
|
-
|
|
3313
|
+
const rootRows = formatRootFileRows(installLog.rootFiles ?? [], projectDir);
|
|
3314
|
+
if (rootRows.length > 0) {
|
|
3257
3315
|
log("");
|
|
3258
|
-
log(
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
)
|
|
3262
|
-
);
|
|
3263
|
-
for (const adv of globalAdvisories) {
|
|
3264
|
-
log(c.dim(` \xB7 ${adv.asset.id} (${adv.asset.method})`));
|
|
3265
|
-
log(c.dim(` ${adv.command}`));
|
|
3266
|
-
}
|
|
3316
|
+
log(c.bold(" Root files"));
|
|
3317
|
+
log(c.dim(" (uninstall \uC774 \uC9C0\uC6B0\uC9C0 \uC54A\uB294\uB2E4 \u2014 \uC0AC\uC6A9\uC790 \uB0B4\uC6A9\uC774 \uC11E\uC778\uB2E4)"));
|
|
3318
|
+
for (const line of rootRows) log(line);
|
|
3267
3319
|
}
|
|
3268
3320
|
log("");
|
|
3269
|
-
log(
|
|
3270
|
-
|
|
3271
|
-
);
|
|
3272
|
-
exit(
|
|
3273
|
-
}
|
|
3274
|
-
function
|
|
3275
|
-
const
|
|
3276
|
-
const
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
}
|
|
3282
|
-
|
|
3283
|
-
if (step) reverseSteps.push(step);
|
|
3284
|
-
}
|
|
3285
|
-
return { reverseSteps, globalAdvisories };
|
|
3286
|
-
}
|
|
3287
|
-
function buildProjectReverseStep(asset, spawn) {
|
|
3288
|
-
switch (asset.method) {
|
|
3289
|
-
case "plugin": {
|
|
3290
|
-
const pluginId = asset.detail.pluginId ?? asset.id;
|
|
3291
|
-
return {
|
|
3292
|
-
label: `claude plugin uninstall --scope project ${pluginId}`,
|
|
3293
|
-
execute: () => {
|
|
3294
|
-
const r = spawn("claude", ["plugin", "uninstall", "--scope", "project", pluginId]);
|
|
3295
|
-
return r.status === 0 ? { ok: true } : { ok: false, message: (r.stderr || "").trim() };
|
|
3296
|
-
}
|
|
3297
|
-
};
|
|
3298
|
-
}
|
|
3299
|
-
case "skill": {
|
|
3300
|
-
const source = asset.detail.source ?? asset.id;
|
|
3301
|
-
return {
|
|
3302
|
-
label: `npx skills remove ${source}`,
|
|
3303
|
-
execute: () => {
|
|
3304
|
-
const r = spawn("npx", [skillsCliSpec(), "remove", source, "--yes"]);
|
|
3305
|
-
return r.status === 0 ? { ok: true } : { ok: false, message: (r.stderr || "").trim() };
|
|
3306
|
-
}
|
|
3307
|
-
};
|
|
3308
|
-
}
|
|
3309
|
-
case "npm": {
|
|
3310
|
-
const pkg = asset.detail.pkg ?? asset.id;
|
|
3311
|
-
return {
|
|
3312
|
-
label: `npm uninstall --save-dev ${pkg}`,
|
|
3313
|
-
execute: () => {
|
|
3314
|
-
const r = spawn("npm", ["uninstall", "--save-dev", pkg]);
|
|
3315
|
-
return r.status === 0 ? { ok: true } : { ok: false, message: (r.stderr || "").trim() };
|
|
3316
|
-
}
|
|
3317
|
-
};
|
|
3318
|
-
}
|
|
3319
|
-
case "npx-run":
|
|
3320
|
-
return null;
|
|
3321
|
-
case "shell-script":
|
|
3322
|
-
return null;
|
|
3323
|
-
case "internal":
|
|
3324
|
-
return null;
|
|
3325
|
-
}
|
|
3326
|
-
}
|
|
3327
|
-
function buildGlobalAdvisoryCmd(asset) {
|
|
3328
|
-
switch (asset.method) {
|
|
3329
|
-
case "plugin": {
|
|
3330
|
-
const pid = asset.detail.pluginId ?? asset.id;
|
|
3331
|
-
return `claude plugin uninstall --scope user ${pid}`;
|
|
3332
|
-
}
|
|
3333
|
-
case "skill": {
|
|
3334
|
-
const s = asset.detail.source ?? asset.id;
|
|
3335
|
-
return `npx skills remove -g ${s}`;
|
|
3336
|
-
}
|
|
3337
|
-
case "npm": {
|
|
3338
|
-
const pkg = asset.detail.pkg ?? asset.id;
|
|
3339
|
-
return `npm uninstall -g ${pkg}`;
|
|
3340
|
-
}
|
|
3341
|
-
case "npx-run":
|
|
3342
|
-
case "shell-script":
|
|
3343
|
-
case "internal":
|
|
3344
|
-
return "(no standard reverse \u2014 manual)";
|
|
3345
|
-
}
|
|
3321
|
+
log(c.dim(" remove one: agent-harness uninstall --only <id>"));
|
|
3322
|
+
log(c.dim(" remove all: agent-harness uninstall"));
|
|
3323
|
+
log("");
|
|
3324
|
+
exit(0);
|
|
3325
|
+
}
|
|
3326
|
+
function formatAssetRows(assets) {
|
|
3327
|
+
const idWidth = Math.max(0, ...assets.map((a) => a.id.length));
|
|
3328
|
+
const methodWidth = Math.max(0, ...assets.map((a) => a.method.length));
|
|
3329
|
+
return assets.map((a) => {
|
|
3330
|
+
const marker = a.scope === "global" ? c.yellow("!") : c.green("\u2713");
|
|
3331
|
+
const version = a.version ? c.dim(` v${a.version}`) : "";
|
|
3332
|
+
const note = a.scope === "global" ? c.yellow(" (manual removal \u2014 D16)") : "";
|
|
3333
|
+
return ` ${marker} ${padDisplay(a.id, idWidth)} ${c.dim(padDisplay(a.method, methodWidth))} ${c.dim(a.scope)}${version}${note}`;
|
|
3334
|
+
});
|
|
3346
3335
|
}
|
|
3347
|
-
function
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3336
|
+
function formatTemplateRows(log, projectDir) {
|
|
3337
|
+
const dirs = [log.templates.claudeDir, log.templates.codexDir, log.templates.opencodeDir].filter(
|
|
3338
|
+
(d2) => Boolean(d2)
|
|
3339
|
+
);
|
|
3340
|
+
const rows = [` ${c.dim(dirs.join(" "))}`];
|
|
3351
3341
|
const rootMd = log.templates.rootClaudeMd;
|
|
3352
3342
|
if (rootMd) {
|
|
3353
|
-
|
|
3354
|
-
|
|
3343
|
+
const path = join12(projectDir, rootMd.path);
|
|
3344
|
+
const modified = existsSync13(path) && hashContent(readFileSync12(path, "utf8")) !== rootMd.sha256;
|
|
3345
|
+
rows.push(
|
|
3346
|
+
modified ? ` ${c.dim(rootMd.path)} ${c.yellow("(\uC218\uC815\uB428 \u2014 uninstall \uC2DC \uBCF4\uC874)")}` : ` ${c.dim(rootMd.path)}`
|
|
3347
|
+
);
|
|
3355
3348
|
}
|
|
3356
|
-
return
|
|
3357
|
-
}
|
|
3358
|
-
function rootClaudeMdModified(log, projectDir) {
|
|
3359
|
-
const rootMd = log.templates.rootClaudeMd;
|
|
3360
|
-
if (!rootMd) return false;
|
|
3361
|
-
const path = join12(projectDir, rootMd.path);
|
|
3362
|
-
if (!existsSync13(path)) return false;
|
|
3363
|
-
return hashContent(readFileSync12(path, "utf8")) !== rootMd.sha256;
|
|
3364
|
-
}
|
|
3365
|
-
function formatTemplateList(log) {
|
|
3366
|
-
const items = [log.templates.claudeDir];
|
|
3367
|
-
if (log.templates.codexDir) items.push(log.templates.codexDir);
|
|
3368
|
-
if (log.templates.opencodeDir) items.push(log.templates.opencodeDir);
|
|
3369
|
-
return items.join(", ");
|
|
3370
|
-
}
|
|
3371
|
-
function defaultSpawn2(cmd, args) {
|
|
3372
|
-
return spawnSync2(cmd, [...args], { encoding: "utf8", stdio: "pipe", timeout: 12e4 });
|
|
3349
|
+
return rows;
|
|
3373
3350
|
}
|
|
3374
|
-
function
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3351
|
+
function formatRootFileRows(rootFiles, projectDir) {
|
|
3352
|
+
const present = rootFiles.filter((f) => existsSync13(join12(projectDir, f.path)));
|
|
3353
|
+
const width = Math.max(0, ...present.map((f) => f.path.length));
|
|
3354
|
+
return present.map(
|
|
3355
|
+
(f) => ` ${c.dim(padDisplay(f.path, width))} ${c.dim(f.change === "created" ? "\uC0DD\uC131" : "\uBCD1\uD569")} ${c.dim(f.notes.join(" / "))}`
|
|
3356
|
+
);
|
|
3378
3357
|
}
|
|
3379
|
-
function
|
|
3380
|
-
cli2.command("
|
|
3358
|
+
function registerListCommand(cli2) {
|
|
3359
|
+
cli2.command("list", "Show what agent-harness installed in this project").option("--project-dir <path>", "[Project] Target project directory", {
|
|
3381
3360
|
default: process.cwd()
|
|
3382
|
-
}).
|
|
3383
|
-
|
|
3384
|
-
"[Mode] Keep `.claude/`, `.codex/`, `.opencode/` templates (remove only external assets)"
|
|
3385
|
-
).action((options) => {
|
|
3386
|
-
uninstallAction(options);
|
|
3361
|
+
}).action((options) => {
|
|
3362
|
+
listAction(options);
|
|
3387
3363
|
});
|
|
3388
3364
|
}
|
|
3389
3365
|
|
|
3390
|
-
// src/
|
|
3366
|
+
// src/commands/uninstall.ts
|
|
3391
3367
|
init_esm_shims();
|
|
3368
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
3369
|
+
import { existsSync as existsSync14, readFileSync as readFileSync13, rmSync } from "fs";
|
|
3370
|
+
import { join as join13, resolve as resolve4 } from "path";
|
|
3392
3371
|
|
|
3393
|
-
// src/
|
|
3372
|
+
// src/uninstall-interactive.ts
|
|
3394
3373
|
init_esm_shims();
|
|
3395
3374
|
|
|
3396
3375
|
// node_modules/@clack/prompts/dist/index.mjs
|
|
@@ -4363,6 +4342,521 @@ ${a}
|
|
|
4363
4342
|
};
|
|
4364
4343
|
var jt = `${e("gray", $)} `;
|
|
4365
4344
|
|
|
4345
|
+
// src/uninstall-interactive.ts
|
|
4346
|
+
function buildRemovableRows(assets) {
|
|
4347
|
+
return assets.map((a) => ({
|
|
4348
|
+
value: a.id,
|
|
4349
|
+
label: `${a.id} [${a.method}]${a.version ? ` v${a.version}` : ""}`,
|
|
4350
|
+
hint: hintFor(a)
|
|
4351
|
+
}));
|
|
4352
|
+
}
|
|
4353
|
+
function hintFor(asset) {
|
|
4354
|
+
if (asset.scope === "global") return "global scope \u2014 \uC790\uB3D9 \uC0AD\uC81C \uC548 \uD568, \uC218\uAE30 \uC81C\uAC70 \uBA85\uB839\uC744 \uCD9C\uB825\uD55C\uB2E4";
|
|
4355
|
+
switch (asset.method) {
|
|
4356
|
+
case "plugin":
|
|
4357
|
+
return "claude plugin uninstall --scope project";
|
|
4358
|
+
case "skill":
|
|
4359
|
+
return "npx skills remove";
|
|
4360
|
+
case "npm":
|
|
4361
|
+
return "npm uninstall --save-dev";
|
|
4362
|
+
case "npx-run":
|
|
4363
|
+
case "shell-script":
|
|
4364
|
+
case "internal":
|
|
4365
|
+
return "\uC790\uB3D9 \uB418\uB3CC\uB9AC\uAE30 \uACBD\uB85C \uC5C6\uC74C \u2014 \uC804\uB7C9 \uC81C\uAC70(`.claude/` \uC0AD\uC81C)\uB85C\uB9CC \uC0AC\uB77C\uC9C4\uB2E4";
|
|
4366
|
+
}
|
|
4367
|
+
}
|
|
4368
|
+
async function runInteractiveUninstall(projectDir, deps = {}) {
|
|
4369
|
+
const prompts = deps.prompts ?? defaultUninstallPrompts();
|
|
4370
|
+
const isTty = deps.isTty ?? (() => Boolean(process.stdin.isTTY));
|
|
4371
|
+
const readLog = deps.readLog ?? readInstallLog;
|
|
4372
|
+
if (!isTty()) return { ok: false, reason: "no-tty" };
|
|
4373
|
+
const log = readLog(projectDir);
|
|
4374
|
+
if (!log) return { ok: false, reason: "no-log" };
|
|
4375
|
+
prompts.intro("uzys-agent-harness \xB7 uninstall");
|
|
4376
|
+
const rows = buildRemovableRows(log.assets);
|
|
4377
|
+
const mode = await prompts.selectMode(rows.length);
|
|
4378
|
+
if (mode === null) {
|
|
4379
|
+
prompts.cancel("Cancelled.");
|
|
4380
|
+
return { ok: false, reason: "cancelled" };
|
|
4381
|
+
}
|
|
4382
|
+
if (mode === "all") {
|
|
4383
|
+
const ok2 = await prompts.confirm(
|
|
4384
|
+
[
|
|
4385
|
+
"\uC804\uB7C9 \uC81C\uAC70 \u2014 \uB418\uB3CC\uB9B4 \uC218 \uC5C6\uB2E4:",
|
|
4386
|
+
` \xB7 \uC790\uC0B0 ${log.assets.length}\uAC1C (\uC790\uB3D9 \uACBD\uB85C\uAC00 \uC788\uB294 \uAC83\uB9CC \uC2E4\uC81C \uC81C\uAC70)`,
|
|
4387
|
+
" \xB7 templates \uC0AD\uC81C: `.claude/` \uB4F1 (\uC124\uCE58 \uAE30\uB85D\uB3C4 \uD568\uAED8 \uC0AC\uB77C\uC9C4\uB2E4)",
|
|
4388
|
+
" \xB7 `.claude/` \uBC16 \uD30C\uC77C(`.mcp.json` \uB4F1)\uC740 \uC0AD\uC81C\uD558\uC9C0 \uC54A\uACE0 \uC548\uB0B4\uB9CC \uD55C\uB2E4"
|
|
4389
|
+
].join("\n")
|
|
4390
|
+
);
|
|
4391
|
+
if (!ok2) {
|
|
4392
|
+
prompts.cancel("Cancelled.");
|
|
4393
|
+
return { ok: false, reason: "cancelled" };
|
|
4394
|
+
}
|
|
4395
|
+
return { ok: true, options: { projectDir } };
|
|
4396
|
+
}
|
|
4397
|
+
const picked = await prompts.selectAssets(rows);
|
|
4398
|
+
if (picked === null) {
|
|
4399
|
+
prompts.cancel("Cancelled.");
|
|
4400
|
+
return { ok: false, reason: "cancelled" };
|
|
4401
|
+
}
|
|
4402
|
+
if (picked.length === 0) {
|
|
4403
|
+
prompts.outro("\uC544\uBB34\uAC83\uB3C4 \uC120\uD0DD\uD558\uC9C0 \uC54A\uC558\uB2E4 \u2014 \uBCC0\uACBD \uC5C6\uC74C.");
|
|
4404
|
+
return { ok: false, reason: "nothing-selected" };
|
|
4405
|
+
}
|
|
4406
|
+
const ok = await prompts.confirm(
|
|
4407
|
+
[
|
|
4408
|
+
`\uC120\uD0DD \uC81C\uAC70 (${picked.length}\uAC1C):`,
|
|
4409
|
+
...picked.map((id) => ` \xB7 ${id}`),
|
|
4410
|
+
"",
|
|
4411
|
+
"templates \uB294 \uADF8\uB300\uB85C \uB454\uB2E4."
|
|
4412
|
+
].join("\n")
|
|
4413
|
+
);
|
|
4414
|
+
if (!ok) {
|
|
4415
|
+
prompts.cancel("Cancelled.");
|
|
4416
|
+
return { ok: false, reason: "cancelled" };
|
|
4417
|
+
}
|
|
4418
|
+
return { ok: true, options: { projectDir, only: picked.join(",") } };
|
|
4419
|
+
}
|
|
4420
|
+
function defaultUninstallPrompts() {
|
|
4421
|
+
return {
|
|
4422
|
+
intro: (m) => ge(m),
|
|
4423
|
+
outro: (m) => ye(m),
|
|
4424
|
+
cancel: (m) => me(m),
|
|
4425
|
+
selectMode: async (rowCount) => {
|
|
4426
|
+
const r = await Ee({
|
|
4427
|
+
message: `\uBB34\uC5C7\uC744 \uC81C\uAC70\uD560\uAE4C? (\uC124\uCE58\uB41C \uC790\uC0B0 ${rowCount}\uAC1C)`,
|
|
4428
|
+
options: [
|
|
4429
|
+
{
|
|
4430
|
+
value: "selected",
|
|
4431
|
+
label: "\uD56D\uBAA9 \uC120\uD0DD\uD574\uC11C \uC81C\uAC70",
|
|
4432
|
+
hint: "templates(`.claude/` \uB4F1)\uB294 \uADF8\uB300\uB85C \uB454\uB2E4"
|
|
4433
|
+
},
|
|
4434
|
+
{
|
|
4435
|
+
value: "all",
|
|
4436
|
+
label: "\uC804\uBD80 \uC81C\uAC70",
|
|
4437
|
+
hint: "\uC790\uC0B0 + templates. \uC124\uCE58 \uAE30\uB85D\uB3C4 \uC0AC\uB77C\uC9C4\uB2E4"
|
|
4438
|
+
}
|
|
4439
|
+
]
|
|
4440
|
+
});
|
|
4441
|
+
return q(r) ? null : r;
|
|
4442
|
+
},
|
|
4443
|
+
selectAssets: async (rows) => {
|
|
4444
|
+
if (rows.length === 0) return [];
|
|
4445
|
+
const r = await ve({
|
|
4446
|
+
message: "\uC81C\uAC70\uD560 \uD56D\uBAA9 (Space \uD1A0\uAE00 \xB7 Enter \uD655\uC815 \xB7 ESC \uCDE8\uC18C)",
|
|
4447
|
+
options: rows.map((x) => ({ value: x.value, label: x.label, hint: x.hint })),
|
|
4448
|
+
required: false
|
|
4449
|
+
});
|
|
4450
|
+
return q(r) ? null : r;
|
|
4451
|
+
},
|
|
4452
|
+
confirm: async (summary) => {
|
|
4453
|
+
const r = await ue({ message: `${summary}
|
|
4454
|
+
|
|
4455
|
+
\uC9C4\uD589\uD560\uAE4C?`, initialValue: false });
|
|
4456
|
+
return q(r) ? null : r;
|
|
4457
|
+
}
|
|
4458
|
+
};
|
|
4459
|
+
}
|
|
4460
|
+
|
|
4461
|
+
// src/commands/uninstall.ts
|
|
4462
|
+
function uninstallAction(options, deps = {}) {
|
|
4463
|
+
const log = deps.log ?? console.log;
|
|
4464
|
+
const err = deps.err ?? console.error;
|
|
4465
|
+
const exit = deps.exit ?? ((code) => process.exit(code));
|
|
4466
|
+
const spawn = deps.spawn ?? defaultSpawn2;
|
|
4467
|
+
const rm = deps.rm ?? defaultRm;
|
|
4468
|
+
const writeLog = deps.writeLog ?? writeInstallLog;
|
|
4469
|
+
const projectDir = resolve4(options.projectDir ?? process.cwd());
|
|
4470
|
+
const installLog = readInstallLog(projectDir);
|
|
4471
|
+
if (!installLog) {
|
|
4472
|
+
err(status.failure(c.red(`ERROR: install log not found at ${installLogPath(projectDir)}`)));
|
|
4473
|
+
err(c.dim(" Was this project installed by agent-harness? Nothing to uninstall."));
|
|
4474
|
+
exit(1);
|
|
4475
|
+
return;
|
|
4476
|
+
}
|
|
4477
|
+
const selectedIds = parseOnly(options.only);
|
|
4478
|
+
if (options.only !== void 0 && selectedIds === null) {
|
|
4479
|
+
err(status.failure(c.red("ERROR: --only \uC5D0 \uC790\uC0B0 id \uAC00 \uC5C6\uB2E4")));
|
|
4480
|
+
err(c.dim(" \uC608: --only code-review (id \uB294 `agent-harness list` \uC5D0\uC11C \uD655\uC778)"));
|
|
4481
|
+
exit(1);
|
|
4482
|
+
return;
|
|
4483
|
+
}
|
|
4484
|
+
const unknown = selectedIds ? unknownIds(installLog, selectedIds) : [];
|
|
4485
|
+
if (unknown.length > 0) {
|
|
4486
|
+
err(status.failure(c.red(`ERROR: not in install log: ${unknown.join(", ")}`)));
|
|
4487
|
+
err(c.dim(` installed: ${installLog.assets.map((a) => a.id).join(", ") || "(none)"}`));
|
|
4488
|
+
exit(1);
|
|
4489
|
+
return;
|
|
4490
|
+
}
|
|
4491
|
+
const targetAssets = selectedIds ? installLog.assets.filter((a) => selectedIds.includes(a.id)) : installLog.assets;
|
|
4492
|
+
const keepTemplates = options.keepTemplates || selectedIds !== null;
|
|
4493
|
+
const rootFiles = selectedIds ? [] : installLog.rootFiles ?? [];
|
|
4494
|
+
const plan = planReverse(targetAssets, spawn);
|
|
4495
|
+
for (const line of headerLines(installLog, selectedIds, targetAssets.length)) log(line);
|
|
4496
|
+
if (options.dryRun) {
|
|
4497
|
+
for (const line of dryRunLines(
|
|
4498
|
+
plan,
|
|
4499
|
+
installLog,
|
|
4500
|
+
projectDir,
|
|
4501
|
+
keepTemplates,
|
|
4502
|
+
targetAssets,
|
|
4503
|
+
rootFiles
|
|
4504
|
+
)) {
|
|
4505
|
+
log(line);
|
|
4506
|
+
}
|
|
4507
|
+
exit(0);
|
|
4508
|
+
return;
|
|
4509
|
+
}
|
|
4510
|
+
const logSurvives = selectedIds !== null;
|
|
4511
|
+
const { succeeded, failed, removedIds } = executeReverse(plan, log, logSurvives);
|
|
4512
|
+
if (!keepTemplates) {
|
|
4513
|
+
const { rootClaudeMdKept } = removeTemplates(installLog, projectDir, rm);
|
|
4514
|
+
log(` ${status.success(`templates removed: ${formatTemplateList(installLog)}`)}`);
|
|
4515
|
+
if (rootClaudeMdKept) {
|
|
4516
|
+
log(
|
|
4517
|
+
` ${c.yellow("\u2298")} CLAUDE.md kept \u2014 modified since install. Remove manually if intended.`
|
|
4518
|
+
);
|
|
4519
|
+
}
|
|
4520
|
+
}
|
|
4521
|
+
const logWriteFailed = settleLog(
|
|
4522
|
+
{ installLog, projectDir, selectedIds, keepTemplates, removedIds },
|
|
4523
|
+
{ log, err, rm, writeLog }
|
|
4524
|
+
);
|
|
4525
|
+
for (const line of advisoryLines(plan, targetAssets, projectDir, keepTemplates, rootFiles)) {
|
|
4526
|
+
log(line);
|
|
4527
|
+
}
|
|
4528
|
+
const outcome = summarize({
|
|
4529
|
+
succeeded,
|
|
4530
|
+
failed,
|
|
4531
|
+
logWriteFailed,
|
|
4532
|
+
// 제거된 것도 없고 templates 도 안 지웠는데, 사용자에게 줄 방법조차 없을 때만 실패다.
|
|
4533
|
+
// ① `--only` = "이걸 빼라"는 특정 요청 — 하나도 못 뺐으면 요청 불이행 (C1).
|
|
4534
|
+
// ② 자동 경로가 없는 자산이 남았으면 — 안내할 명령조차 없다 (R1).
|
|
4535
|
+
// ③ 반면 global 자산만 남은 경우는 **정확한 수기 명령을 출력했으므로** 성공이다.
|
|
4536
|
+
// D16 설계대로의 정상이고, 여기까지 묶으면 global scope 사용자는 exit 0 을 영원히
|
|
4537
|
+
// 못 받는다 (로그가 지워져 재시도하면 더 나빠진다).
|
|
4538
|
+
nothingDone: succeeded === 0 && keepTemplates && (logSurvives || plan.noReversePath.length > 0)
|
|
4539
|
+
});
|
|
4540
|
+
log("");
|
|
4541
|
+
log(outcome.line);
|
|
4542
|
+
exit(outcome.code);
|
|
4543
|
+
}
|
|
4544
|
+
function summarize(r) {
|
|
4545
|
+
if (r.failed > 0)
|
|
4546
|
+
return {
|
|
4547
|
+
line: c.yellow(`uninstall finished with ${r.failed} skip(s) (${r.succeeded} ok)`),
|
|
4548
|
+
code: 1
|
|
4549
|
+
};
|
|
4550
|
+
if (r.nothingDone)
|
|
4551
|
+
return {
|
|
4552
|
+
line: c.yellow("\uC544\uBB34\uAC83\uB3C4 \uC790\uB3D9 \uC81C\uAC70\uB418\uC9C0 \uC54A\uC558\uB2E4 \u2014 \uC704 \uC548\uB0B4\uB97C \uB530\uB77C \uC9C1\uC811 \uCC98\uB9AC\uD574\uC57C \uD55C\uB2E4"),
|
|
4553
|
+
code: 1
|
|
4554
|
+
};
|
|
4555
|
+
if (r.logWriteFailed)
|
|
4556
|
+
return {
|
|
4557
|
+
line: c.yellow("\uC790\uC0B0\uC740 \uC81C\uAC70\uB410\uC73C\uB098 install log \uB97C \uAC31\uC2E0\uD558\uC9C0 \uBABB\uD588\uB2E4 (\uAE30\uB85D\uC774 \uC2E4\uC81C\uC640 \uB2E4\uB974\uB2E4)"),
|
|
4558
|
+
code: 1
|
|
4559
|
+
};
|
|
4560
|
+
return { line: status.success(c.green(`uninstall complete (${r.succeeded} asset(s))`)), code: 0 };
|
|
4561
|
+
}
|
|
4562
|
+
function headerLines(installLog, selectedIds, targetCount) {
|
|
4563
|
+
return [
|
|
4564
|
+
"",
|
|
4565
|
+
c.bold("uzys-agent-harness \xB7 uninstall"),
|
|
4566
|
+
"",
|
|
4567
|
+
c.dim(` installed: ${installLog.installedAt}`),
|
|
4568
|
+
c.dim(` scope: ${installLog.scope}`),
|
|
4569
|
+
c.dim(
|
|
4570
|
+
selectedIds ? ` assets: ${targetCount} selected of ${installLog.assets.length} (--only)` : ` assets: ${installLog.assets.length}`
|
|
4571
|
+
),
|
|
4572
|
+
""
|
|
4573
|
+
];
|
|
4574
|
+
}
|
|
4575
|
+
function executeReverse(plan, log, logSurvives) {
|
|
4576
|
+
let succeeded = 0;
|
|
4577
|
+
let failed = 0;
|
|
4578
|
+
const removedIds = [];
|
|
4579
|
+
for (const step of plan.reverseSteps) {
|
|
4580
|
+
const result = step.execute();
|
|
4581
|
+
if (result.ok) {
|
|
4582
|
+
log(` ${status.success(step.label)}`);
|
|
4583
|
+
removedIds.push(step.assetId);
|
|
4584
|
+
succeeded++;
|
|
4585
|
+
} else {
|
|
4586
|
+
log(` ${c.yellow("\u2298")} ${step.label} (${result.message ?? "failed"})`);
|
|
4587
|
+
failed++;
|
|
4588
|
+
}
|
|
4589
|
+
}
|
|
4590
|
+
const tail = logSurvives ? "\uC790\uB3D9 \uB418\uB3CC\uB9AC\uAE30 \uACBD\uB85C \uC5C6\uC74C, \uAE30\uB85D \uC720\uC9C0" : "\uC790\uB3D9 \uB418\uB3CC\uB9AC\uAE30 \uACBD\uB85C \uC5C6\uC74C";
|
|
4591
|
+
for (const asset of plan.noReversePath) {
|
|
4592
|
+
log(` ${c.yellow("\u2298")} ${asset.id} (${asset.method}) \u2014 ${tail}`);
|
|
4593
|
+
}
|
|
4594
|
+
return { succeeded, failed, removedIds };
|
|
4595
|
+
}
|
|
4596
|
+
function settleLog(ctx, io) {
|
|
4597
|
+
const { installLog, projectDir, selectedIds, keepTemplates, removedIds } = ctx;
|
|
4598
|
+
if (selectedIds) {
|
|
4599
|
+
const remaining = installLog.assets.filter((a) => !removedIds.includes(a.id));
|
|
4600
|
+
try {
|
|
4601
|
+
io.writeLog(projectDir, { ...installLog, assets: remaining });
|
|
4602
|
+
io.log(` ${status.success(`install log updated (${remaining.length} asset(s) remain)`)}`);
|
|
4603
|
+
} catch (e2) {
|
|
4604
|
+
io.err(status.failure(c.red(`ERROR: install log \uAC31\uC2E0 \uC2E4\uD328 \u2014 ${installLogPath(projectDir)}`)));
|
|
4605
|
+
io.err(c.dim(` ${e2 instanceof Error ? e2.message : String(e2)}`));
|
|
4606
|
+
io.err(c.dim(` \uC2E4\uC81C\uB85C \uC81C\uAC70\uB41C \uC790\uC0B0: ${removedIds.join(", ") || "(\uC5C6\uC74C)"}`));
|
|
4607
|
+
return true;
|
|
4608
|
+
}
|
|
4609
|
+
} else if (keepTemplates) {
|
|
4610
|
+
io.rm(installLogPath(projectDir));
|
|
4611
|
+
io.log(` ${status.success("install log removed (templates kept)")}`);
|
|
4612
|
+
}
|
|
4613
|
+
return false;
|
|
4614
|
+
}
|
|
4615
|
+
function dryRunLines(plan, installLog, projectDir, keepTemplates, targetAssets, rootFiles) {
|
|
4616
|
+
const lines = [c.yellow("[DRY RUN] reverse list (\uC2E4\uC81C \uBCC0\uACBD \uC5C6\uC74C):"), ""];
|
|
4617
|
+
if (plan.reverseSteps.length === 0) {
|
|
4618
|
+
lines.push(c.dim(" (no project-scope assets to reverse)"));
|
|
4619
|
+
}
|
|
4620
|
+
lines.push(...plan.reverseSteps.map((s) => ` \u25CB ${s.label}`));
|
|
4621
|
+
lines.push(
|
|
4622
|
+
...plan.noReversePath.map(
|
|
4623
|
+
(a) => c.dim(` \u2298 ${a.id} (${a.method}) \u2014 \uC790\uB3D9 \uB418\uB3CC\uB9AC\uAE30 \uACBD\uB85C \uC5C6\uC74C, \uAE30\uB85D \uC720\uC9C0`)
|
|
4624
|
+
)
|
|
4625
|
+
);
|
|
4626
|
+
if (!keepTemplates) {
|
|
4627
|
+
lines.push(` \u25CB remove templates: ${formatTemplateList(installLog)}`);
|
|
4628
|
+
if (installLog.templates.rootClaudeMd) {
|
|
4629
|
+
lines.push(
|
|
4630
|
+
rootClaudeMdModified(installLog, projectDir) ? " \u25CB keep CLAUDE.md (modified since install \u2014 preserved)" : " \u25CB remove CLAUDE.md"
|
|
4631
|
+
);
|
|
4632
|
+
}
|
|
4633
|
+
}
|
|
4634
|
+
lines.push(...advisoryLines(plan, targetAssets, projectDir, keepTemplates, rootFiles), "");
|
|
4635
|
+
return lines;
|
|
4636
|
+
}
|
|
4637
|
+
function advisoryLines(plan, targetAssets, projectDir, templatesKept, rootFiles) {
|
|
4638
|
+
const lines = [];
|
|
4639
|
+
if (plan.globalAdvisories.length > 0) {
|
|
4640
|
+
lines.push(
|
|
4641
|
+
"",
|
|
4642
|
+
c.yellow(
|
|
4643
|
+
`[GLOBAL] ${plan.globalAdvisories.length} asset(s) at scope=global \u2014 manual removal required (D16):`
|
|
4644
|
+
)
|
|
4645
|
+
);
|
|
4646
|
+
for (const adv of plan.globalAdvisories) {
|
|
4647
|
+
lines.push(c.dim(` \xB7 ${adv.asset.id} (${adv.asset.method})`), c.dim(` ${adv.command}`));
|
|
4648
|
+
}
|
|
4649
|
+
}
|
|
4650
|
+
if (templatesKept) lines.push(...manualAdvisoryLines(targetAssets, projectDir));
|
|
4651
|
+
lines.push(...rootFileAdvisoryLines(rootFiles, projectDir));
|
|
4652
|
+
return lines;
|
|
4653
|
+
}
|
|
4654
|
+
function rootFileAdvisoryLines(rootFiles, projectDir) {
|
|
4655
|
+
const present = rootFiles.filter((f) => existsSync14(join13(projectDir, f.path)));
|
|
4656
|
+
if (present.length === 0) return [];
|
|
4657
|
+
return [
|
|
4658
|
+
"",
|
|
4659
|
+
c.yellow("[ROOT] `.claude/` \uBC16\uC5D0 \uB0A8\uB294 \uAC83 (\uC790\uB3D9\uC73C\uB85C \uC9C0\uC6B0\uC9C0 \uC54A\uB294\uB2E4):"),
|
|
4660
|
+
...present.flatMap((f) => [
|
|
4661
|
+
c.dim(
|
|
4662
|
+
` \xB7 ${f.path} \u2014 ${f.change === "created" ? "\uD558\uB124\uC2A4\uAC00 \uC0DD\uC131 (\uC218\uC815\uD55C \uC801 \uC5C6\uC73C\uBA74 \uC0AD\uC81C\uD574\uB3C4 \uC548\uC804)" : "\uAE30\uC874 \uC0AC\uC6A9\uC790 \uD30C\uC77C\uC5D0 \uBCD1\uD569 (\uC9C1\uC811 \uD655\uC778 \uD544\uC694)"}`
|
|
4663
|
+
),
|
|
4664
|
+
c.dim(` ${f.notes.join(" / ")}`)
|
|
4665
|
+
])
|
|
4666
|
+
];
|
|
4667
|
+
}
|
|
4668
|
+
function planReverse(assets, spawn) {
|
|
4669
|
+
const reverseSteps = [];
|
|
4670
|
+
const globalAdvisories = [];
|
|
4671
|
+
const noReversePath = [];
|
|
4672
|
+
for (const asset of assets) {
|
|
4673
|
+
if (asset.scope === "global") {
|
|
4674
|
+
globalAdvisories.push({ asset, command: buildGlobalAdvisoryCmd(asset) });
|
|
4675
|
+
continue;
|
|
4676
|
+
}
|
|
4677
|
+
const step = buildProjectReverseStep(asset, spawn);
|
|
4678
|
+
if (step) reverseSteps.push(step);
|
|
4679
|
+
else noReversePath.push(asset);
|
|
4680
|
+
}
|
|
4681
|
+
return { reverseSteps, globalAdvisories, noReversePath };
|
|
4682
|
+
}
|
|
4683
|
+
function buildProjectReverseStep(asset, spawn) {
|
|
4684
|
+
switch (asset.method) {
|
|
4685
|
+
case "plugin": {
|
|
4686
|
+
const pluginId = asset.detail.pluginId ?? asset.id;
|
|
4687
|
+
return {
|
|
4688
|
+
assetId: asset.id,
|
|
4689
|
+
label: `claude plugin uninstall --scope project ${pluginId}`,
|
|
4690
|
+
execute: () => {
|
|
4691
|
+
const r = spawn("claude", ["plugin", "uninstall", "--scope", "project", pluginId]);
|
|
4692
|
+
return r.status === 0 ? { ok: true } : { ok: false, message: (r.stderr || "").trim() };
|
|
4693
|
+
}
|
|
4694
|
+
};
|
|
4695
|
+
}
|
|
4696
|
+
case "skill": {
|
|
4697
|
+
const source = asset.detail.source ?? asset.id;
|
|
4698
|
+
return {
|
|
4699
|
+
assetId: asset.id,
|
|
4700
|
+
label: `npx skills remove ${source}`,
|
|
4701
|
+
execute: () => {
|
|
4702
|
+
const r = spawn("npx", [skillsCliSpec(), "remove", source, "--yes"]);
|
|
4703
|
+
return r.status === 0 ? { ok: true } : { ok: false, message: (r.stderr || "").trim() };
|
|
4704
|
+
}
|
|
4705
|
+
};
|
|
4706
|
+
}
|
|
4707
|
+
case "npm": {
|
|
4708
|
+
const pkg = asset.detail.pkg ?? asset.id;
|
|
4709
|
+
return {
|
|
4710
|
+
assetId: asset.id,
|
|
4711
|
+
label: `npm uninstall --save-dev ${pkg}`,
|
|
4712
|
+
execute: () => {
|
|
4713
|
+
const r = spawn("npm", ["uninstall", "--save-dev", pkg]);
|
|
4714
|
+
return r.status === 0 ? { ok: true } : { ok: false, message: (r.stderr || "").trim() };
|
|
4715
|
+
}
|
|
4716
|
+
};
|
|
4717
|
+
}
|
|
4718
|
+
case "npx-run":
|
|
4719
|
+
return null;
|
|
4720
|
+
case "shell-script":
|
|
4721
|
+
return null;
|
|
4722
|
+
case "internal":
|
|
4723
|
+
return null;
|
|
4724
|
+
}
|
|
4725
|
+
}
|
|
4726
|
+
function settingsHasHookCommand(projectDir, command) {
|
|
4727
|
+
const path = join13(projectDir, ".claude", "settings.json");
|
|
4728
|
+
if (!existsSync14(path)) return false;
|
|
4729
|
+
try {
|
|
4730
|
+
const parsed = JSON.parse(readFileSync13(path, "utf8"));
|
|
4731
|
+
return Object.values(parsed.hooks ?? {}).some(
|
|
4732
|
+
(matchers) => matchers.some((m) => m.hooks?.some((h2) => h2.command === command))
|
|
4733
|
+
);
|
|
4734
|
+
} catch {
|
|
4735
|
+
return false;
|
|
4736
|
+
}
|
|
4737
|
+
}
|
|
4738
|
+
function unknownIds(installLog, selectedIds) {
|
|
4739
|
+
const known = new Set(installLog.assets.map((a) => a.id));
|
|
4740
|
+
return selectedIds.filter((id) => !known.has(id));
|
|
4741
|
+
}
|
|
4742
|
+
function parseOnly(only) {
|
|
4743
|
+
if (!only) return null;
|
|
4744
|
+
const ids = only.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
4745
|
+
return ids.length > 0 ? ids : null;
|
|
4746
|
+
}
|
|
4747
|
+
function manualAdvisoryLines(targetAssets, projectDir) {
|
|
4748
|
+
if (!targetAssets.some((a) => a.id === KARPATHY_ASSET_ID)) return [];
|
|
4749
|
+
const items = [];
|
|
4750
|
+
if (settingsHasHookCommand(projectDir, KARPATHY_HOOK_COMMAND))
|
|
4751
|
+
items.push(
|
|
4752
|
+
`.claude/settings.json \u2014 hooks.PreToolUse \uC5D0\uC11C \uB2E4\uC74C command \uD56D\uBAA9 \uC0AD\uC81C:
|
|
4753
|
+
${KARPATHY_HOOK_COMMAND}`
|
|
4754
|
+
);
|
|
4755
|
+
if (existsSync14(join13(projectDir, KARPATHY_HOOK_RELPATH)))
|
|
4756
|
+
items.push(`\`${KARPATHY_HOOK_RELPATH}\` \u2014 \uC0AD\uC81C`);
|
|
4757
|
+
if (items.length === 0) return [];
|
|
4758
|
+
return [
|
|
4759
|
+
"",
|
|
4760
|
+
c.yellow("[MANUAL] \uC790\uB3D9\uC73C\uB85C \uB418\uB3CC\uB9AC\uC9C0 \uC54A\uC740 \uAC83 (\uC0AC\uC6A9\uC790 \uD3B8\uC9D1\uC774 \uC11E\uC774\uB294 \uD45C\uBA74):"),
|
|
4761
|
+
...items.map((i) => c.dim(` \xB7 ${i}`))
|
|
4762
|
+
];
|
|
4763
|
+
}
|
|
4764
|
+
function buildGlobalAdvisoryCmd(asset) {
|
|
4765
|
+
switch (asset.method) {
|
|
4766
|
+
case "plugin": {
|
|
4767
|
+
const pid = asset.detail.pluginId ?? asset.id;
|
|
4768
|
+
return `claude plugin uninstall --scope user ${pid}`;
|
|
4769
|
+
}
|
|
4770
|
+
case "skill": {
|
|
4771
|
+
const s = asset.detail.source ?? asset.id;
|
|
4772
|
+
return `npx skills remove -g ${s}`;
|
|
4773
|
+
}
|
|
4774
|
+
case "npm": {
|
|
4775
|
+
const pkg = asset.detail.pkg ?? asset.id;
|
|
4776
|
+
return `npm uninstall -g ${pkg}`;
|
|
4777
|
+
}
|
|
4778
|
+
case "npx-run":
|
|
4779
|
+
case "shell-script":
|
|
4780
|
+
case "internal":
|
|
4781
|
+
return "(no standard reverse \u2014 manual)";
|
|
4782
|
+
}
|
|
4783
|
+
}
|
|
4784
|
+
function removeTemplates(log, projectDir, rm) {
|
|
4785
|
+
rm(join13(projectDir, log.templates.claudeDir));
|
|
4786
|
+
if (log.templates.codexDir) rm(join13(projectDir, log.templates.codexDir));
|
|
4787
|
+
if (log.templates.opencodeDir) rm(join13(projectDir, log.templates.opencodeDir));
|
|
4788
|
+
const rootMd = log.templates.rootClaudeMd;
|
|
4789
|
+
if (rootMd) {
|
|
4790
|
+
if (rootClaudeMdModified(log, projectDir)) return { rootClaudeMdKept: true };
|
|
4791
|
+
rm(join13(projectDir, rootMd.path));
|
|
4792
|
+
}
|
|
4793
|
+
return { rootClaudeMdKept: false };
|
|
4794
|
+
}
|
|
4795
|
+
function rootClaudeMdModified(log, projectDir) {
|
|
4796
|
+
const rootMd = log.templates.rootClaudeMd;
|
|
4797
|
+
if (!rootMd) return false;
|
|
4798
|
+
const path = join13(projectDir, rootMd.path);
|
|
4799
|
+
if (!existsSync14(path)) return false;
|
|
4800
|
+
return hashContent(readFileSync13(path, "utf8")) !== rootMd.sha256;
|
|
4801
|
+
}
|
|
4802
|
+
function formatTemplateList(log) {
|
|
4803
|
+
const items = [log.templates.claudeDir];
|
|
4804
|
+
if (log.templates.codexDir) items.push(log.templates.codexDir);
|
|
4805
|
+
if (log.templates.opencodeDir) items.push(log.templates.opencodeDir);
|
|
4806
|
+
return items.join(", ");
|
|
4807
|
+
}
|
|
4808
|
+
function defaultSpawn2(cmd, args) {
|
|
4809
|
+
return spawnSync2(cmd, [...args], { encoding: "utf8", stdio: "pipe", timeout: 12e4 });
|
|
4810
|
+
}
|
|
4811
|
+
function defaultRm(path) {
|
|
4812
|
+
if (existsSync14(path)) {
|
|
4813
|
+
rmSync(path, { recursive: true, force: true });
|
|
4814
|
+
}
|
|
4815
|
+
}
|
|
4816
|
+
function registerUninstallCommand(cli2) {
|
|
4817
|
+
cli2.command("uninstall", "Uninstall harness assets (log-based reverse)").option("--project-dir <path>", "[Project] Target project directory", {
|
|
4818
|
+
default: process.cwd()
|
|
4819
|
+
}).option("--dry-run", "[Mode] List reverse steps without executing").option(
|
|
4820
|
+
"--keep-templates",
|
|
4821
|
+
"[Mode] Keep `.claude/`, `.codex/`, `.opencode/` templates (remove only external assets)"
|
|
4822
|
+
).option(
|
|
4823
|
+
"--only <ids>",
|
|
4824
|
+
"[Scope] Remove only these assets (comma-separated ids from `agent-harness list`). Templates untouched"
|
|
4825
|
+
).option("--yes", "[Mode] Skip the interactive picker and remove everything (non-interactive)").action(async (options) => {
|
|
4826
|
+
await dispatchUninstall(options);
|
|
4827
|
+
});
|
|
4828
|
+
}
|
|
4829
|
+
function shouldRunInteractive(options, isTty) {
|
|
4830
|
+
if (!isTty) return false;
|
|
4831
|
+
if (options.yes || options.dryRun) return false;
|
|
4832
|
+
return options.only === void 0;
|
|
4833
|
+
}
|
|
4834
|
+
async function dispatchUninstall(options) {
|
|
4835
|
+
if (!shouldRunInteractive(options, Boolean(process.stdin.isTTY))) {
|
|
4836
|
+
uninstallAction(options);
|
|
4837
|
+
return;
|
|
4838
|
+
}
|
|
4839
|
+
const projectDir = resolve4(options.projectDir ?? process.cwd());
|
|
4840
|
+
const picked = await runInteractiveUninstall(projectDir);
|
|
4841
|
+
if (!picked.ok || !picked.options) {
|
|
4842
|
+
if (picked.reason === "no-log") {
|
|
4843
|
+
console.error(
|
|
4844
|
+
status.failure(c.red(`ERROR: install log not found at ${installLogPath(projectDir)}`))
|
|
4845
|
+
);
|
|
4846
|
+
console.error(c.dim(" Nothing installed here by agent-harness."));
|
|
4847
|
+
process.exit(1);
|
|
4848
|
+
}
|
|
4849
|
+
return;
|
|
4850
|
+
}
|
|
4851
|
+
uninstallAction(picked.options);
|
|
4852
|
+
}
|
|
4853
|
+
|
|
4854
|
+
// src/interactive.ts
|
|
4855
|
+
init_esm_shims();
|
|
4856
|
+
|
|
4857
|
+
// src/prompts.ts
|
|
4858
|
+
init_esm_shims();
|
|
4859
|
+
|
|
4366
4860
|
// src/router.ts
|
|
4367
4861
|
init_esm_shims();
|
|
4368
4862
|
function buildRouterChoices(state) {
|
|
@@ -4466,7 +4960,8 @@ function expandDevMethodBundle(ids) {
|
|
|
4466
4960
|
const rest = ids.filter((v2) => v2 !== DEV_METHOD_BUNDLE_VALUE);
|
|
4467
4961
|
return ids.includes(DEV_METHOD_BUNDLE_VALUE) ? [...rest, ...bundleMemberValues()] : rest;
|
|
4468
4962
|
}
|
|
4469
|
-
function buildPageGroups(cats, initialSet) {
|
|
4963
|
+
function buildPageGroups(cats, initialSet, installedSet = /* @__PURE__ */ new Set()) {
|
|
4964
|
+
const installedMark = (value) => installedSet.has(value) ? " \u25CF installed" : "";
|
|
4470
4965
|
const groups = {};
|
|
4471
4966
|
const flatItems = [];
|
|
4472
4967
|
for (const cat of cats) {
|
|
@@ -4475,14 +4970,14 @@ function buildPageGroups(cats, initialSet) {
|
|
|
4475
4970
|
items.push({
|
|
4476
4971
|
value: `option:${o.key}`,
|
|
4477
4972
|
// v26.62.3 — group header 와 옵션 사이 시각 hierarchy 강화. label prefix 4 space.
|
|
4478
|
-
label: ` ${o.label} [${o.source}]`,
|
|
4973
|
+
label: ` ${o.label} [${o.source}]${installedMark(`option:${o.key}`)}`,
|
|
4479
4974
|
hint: o.hint
|
|
4480
4975
|
});
|
|
4481
4976
|
}
|
|
4482
4977
|
if (cat === DEV_METHOD_BUNDLE_CATEGORY) {
|
|
4483
4978
|
items.push({
|
|
4484
4979
|
value: DEV_METHOD_BUNDLE_VALUE,
|
|
4485
|
-
label: ` uzys \uD558\uB124\uC2A4 \uBC29\uBC95\uB860 ${DEV_METHOD_SKILL_IDS.length}\uC885 [uzys] \u2605 official`,
|
|
4980
|
+
label: ` uzys \uD558\uB124\uC2A4 \uBC29\uBC95\uB860 ${DEV_METHOD_SKILL_IDS.length}\uC885 [uzys] \u2605 official${installedMark(DEV_METHOD_BUNDLE_VALUE)}`,
|
|
4486
4981
|
hint: DEV_METHOD_SKILL_IDS.join(", ")
|
|
4487
4982
|
});
|
|
4488
4983
|
}
|
|
@@ -4494,7 +4989,7 @@ function buildPageGroups(cats, initialSet) {
|
|
|
4494
4989
|
const badge = tier === "official" ? " \u2605 official" : tier === "experimental" ? " \u26A0 experimental (opt-in)" : "";
|
|
4495
4990
|
items.push({
|
|
4496
4991
|
value: `asset:${a.id}`,
|
|
4497
|
-
label: ` ${a.id} [${a.source}]${badge}`,
|
|
4992
|
+
label: ` ${a.id} [${a.source}]${badge}${installedMark(`asset:${a.id}`)}`,
|
|
4498
4993
|
hint: a.description
|
|
4499
4994
|
});
|
|
4500
4995
|
}
|
|
@@ -4609,6 +5104,9 @@ Proceed?`,
|
|
|
4609
5104
|
const initialSet = new Set(displayInitial);
|
|
4610
5105
|
const collected = new Set(displayInitial);
|
|
4611
5106
|
const recapLine = recap ? `Tracks: ${recap.tracks.join(", ")} \xB7 CLIs: ${recap.cli.join(", ")}` : "";
|
|
5107
|
+
const installedSet = new Set(
|
|
5108
|
+
collapseDevMethodBundle(recap?.installed ?? [])
|
|
5109
|
+
);
|
|
4612
5110
|
process.stdout.write("\x1B[?1049h");
|
|
4613
5111
|
let resultIds = null;
|
|
4614
5112
|
let aborted = false;
|
|
@@ -4617,7 +5115,7 @@ Proceed?`,
|
|
|
4617
5115
|
while (pageIdx < pages.length) {
|
|
4618
5116
|
const page = pages[pageIdx];
|
|
4619
5117
|
if (!page) break;
|
|
4620
|
-
const { groups, flatItems } = buildPageGroups(page.cats, initialSet);
|
|
5118
|
+
const { groups, flatItems } = buildPageGroups(page.cats, initialSet, installedSet);
|
|
4621
5119
|
const selectedNow = flatItems.filter((it3) => collected.has(it3.value)).map((it3) => it3.value);
|
|
4622
5120
|
const pageDefault = flatItems.filter((it3) => initialSet.has(it3.value)).length;
|
|
4623
5121
|
const totalSelected = collected.size;
|
|
@@ -4625,6 +5123,9 @@ Proceed?`,
|
|
|
4625
5123
|
`Step ${step.current}/${step.total} \xB7 Page ${pageIdx + 1}/${pages.length} \xB7 ${page.label}`,
|
|
4626
5124
|
recapLine ? ` ${recapLine}` : "",
|
|
4627
5125
|
` Selected so far: ${totalSelected} items \xB7 This page default \u2713 ${pageDefault}/${flatItems.length}`,
|
|
5126
|
+
// 마커의 뜻을 화면에서 바로 알려준다 — 체크 해제를 제거로 오해하는 것이 이 화면의
|
|
5127
|
+
// 원래 문제였으므로, 마커가 보일 때는 제거 경로를 같은 자리에서 말한다.
|
|
5128
|
+
installedSet.size > 0 ? " \u25CF installed = \uC774\uBBF8 \uC124\uCE58\uB428 \xB7 \uCCB4\uD06C \uD574\uC81C\uD574\uB3C4 \uC81C\uAC70\uB418\uC9C0 \uC54A\uB294\uB2E4 (\uC81C\uAC70: agent-harness uninstall)" : "",
|
|
4628
5129
|
" Space toggle \xB7 Enter \u2192 next \xB7 ESC \u2192 prev"
|
|
4629
5130
|
].filter(Boolean).join("\n");
|
|
4630
5131
|
const groupOpts = {
|
|
@@ -4666,8 +5167,8 @@ Proceed?`,
|
|
|
4666
5167
|
|
|
4667
5168
|
// src/state.ts
|
|
4668
5169
|
init_esm_shims();
|
|
4669
|
-
import { existsSync as
|
|
4670
|
-
import { join as
|
|
5170
|
+
import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
|
|
5171
|
+
import { join as join14 } from "path";
|
|
4671
5172
|
var META_FILE = ".claude/.installed-tracks";
|
|
4672
5173
|
var LEGACY_SIGNATURES = [
|
|
4673
5174
|
{ rule: "htmx.md", track: "ssr-htmx" },
|
|
@@ -4677,13 +5178,13 @@ var LEGACY_SIGNATURES = [
|
|
|
4677
5178
|
{ rule: "cli-development.md", track: "tooling" }
|
|
4678
5179
|
];
|
|
4679
5180
|
function detectInstallState(projectDir) {
|
|
4680
|
-
const claudeDir =
|
|
4681
|
-
const hasClaudeDir =
|
|
5181
|
+
const claudeDir = join14(projectDir, ".claude");
|
|
5182
|
+
const hasClaudeDir = existsSync15(claudeDir);
|
|
4682
5183
|
if (!hasClaudeDir) {
|
|
4683
5184
|
return { state: "new", tracks: [], source: "none", hasClaudeDir: false };
|
|
4684
5185
|
}
|
|
4685
|
-
const metaPath =
|
|
4686
|
-
if (
|
|
5186
|
+
const metaPath = join14(projectDir, META_FILE);
|
|
5187
|
+
if (existsSync15(metaPath)) {
|
|
4687
5188
|
const tracks2 = readMetafile(metaPath);
|
|
4688
5189
|
return { state: "existing", tracks: tracks2, source: "metafile", hasClaudeDir: true };
|
|
4689
5190
|
}
|
|
@@ -4691,7 +5192,7 @@ function detectInstallState(projectDir) {
|
|
|
4691
5192
|
return { state: "existing", tracks, source: "legacy", hasClaudeDir: true };
|
|
4692
5193
|
}
|
|
4693
5194
|
function readMetafile(path) {
|
|
4694
|
-
const raw =
|
|
5195
|
+
const raw = readFileSync14(path, "utf8");
|
|
4695
5196
|
const seen = /* @__PURE__ */ new Set();
|
|
4696
5197
|
for (const line of raw.split(/\s+/)) {
|
|
4697
5198
|
const trimmed = line.trim();
|
|
@@ -4702,13 +5203,13 @@ function readMetafile(path) {
|
|
|
4702
5203
|
return [...seen].sort();
|
|
4703
5204
|
}
|
|
4704
5205
|
function inferFromLegacySignatures(projectDir) {
|
|
4705
|
-
const rulesDir =
|
|
4706
|
-
if (!
|
|
5206
|
+
const rulesDir = join14(projectDir, ".claude/rules");
|
|
5207
|
+
if (!existsSync15(rulesDir)) {
|
|
4707
5208
|
return [];
|
|
4708
5209
|
}
|
|
4709
5210
|
const found = /* @__PURE__ */ new Set();
|
|
4710
5211
|
for (const sig of LEGACY_SIGNATURES) {
|
|
4711
|
-
if (
|
|
5212
|
+
if (existsSync15(join14(rulesDir, sig.rule))) {
|
|
4712
5213
|
found.add(sig.track);
|
|
4713
5214
|
}
|
|
4714
5215
|
}
|
|
@@ -4736,6 +5237,19 @@ function toOptionFlags(keys) {
|
|
|
4736
5237
|
withKarpathyHook: picked.has("withKarpathyHook")
|
|
4737
5238
|
};
|
|
4738
5239
|
}
|
|
5240
|
+
function installedTargetState(projectDir) {
|
|
5241
|
+
const log = readInstallLog(projectDir);
|
|
5242
|
+
if (!log) return { installed: [], projectScoped: [] };
|
|
5243
|
+
return {
|
|
5244
|
+
installed: log.assets.map((a) => a.id),
|
|
5245
|
+
projectScoped: log.assets.filter((a) => a.scope !== "global").map((a) => a.id)
|
|
5246
|
+
};
|
|
5247
|
+
}
|
|
5248
|
+
function initialTargetSelection(tracks, installedProjectAssetIds) {
|
|
5249
|
+
const ids = new Set(recommendedExternalAssets(tracks));
|
|
5250
|
+
for (const id of installedProjectAssetIds) ids.add(id);
|
|
5251
|
+
return [...ids].map((id) => `asset:${id}`);
|
|
5252
|
+
}
|
|
4739
5253
|
async function runInteractive(projectDir, deps = {}) {
|
|
4740
5254
|
const prompts = deps.prompts ?? defaultPrompts;
|
|
4741
5255
|
const detect = deps.detect ?? detectInstallState;
|
|
@@ -4749,6 +5263,7 @@ async function runInteractive(projectDir, deps = {}) {
|
|
|
4749
5263
|
}
|
|
4750
5264
|
prompts.intro("uzys-agent-harness installer");
|
|
4751
5265
|
const state = detect(projectDir);
|
|
5266
|
+
const installed = (deps.readInstalled ?? installedTargetState)(projectDir);
|
|
4752
5267
|
let initialTracks;
|
|
4753
5268
|
let mode = "fresh";
|
|
4754
5269
|
if (state.state === "existing") {
|
|
@@ -4824,10 +5339,11 @@ ${summary}`);
|
|
|
4824
5339
|
cli2 = result;
|
|
4825
5340
|
step = "targets";
|
|
4826
5341
|
} else if (step === "targets") {
|
|
4827
|
-
const initial = targetSelections !== null ? [...targetSelections] :
|
|
5342
|
+
const initial = targetSelections !== null ? [...targetSelections] : initialTargetSelection(tracks ?? [], installed.projectScoped);
|
|
4828
5343
|
const result = await prompts.selectInstallTargets(initial, WIZARD.TARGETS, {
|
|
4829
5344
|
tracks: tracks ?? [],
|
|
4830
|
-
cli: cli2 ?? ["claude"]
|
|
5345
|
+
cli: cli2 ?? ["claude"],
|
|
5346
|
+
installed: installed.installed.map((id) => `asset:${id}`)
|
|
4831
5347
|
});
|
|
4832
5348
|
if (result === null) {
|
|
4833
5349
|
step = "cli";
|
|
@@ -4973,6 +5489,7 @@ function buildCli() {
|
|
|
4973
5489
|
cli2.help();
|
|
4974
5490
|
cli2.version(VERSION);
|
|
4975
5491
|
registerInstallCommand(cli2);
|
|
5492
|
+
registerListCommand(cli2);
|
|
4976
5493
|
registerUninstallCommand(cli2);
|
|
4977
5494
|
cli2.command("", "Interactive installer (state detection + prompts)").action(() => defaultAction());
|
|
4978
5495
|
return cli2;
|