@uzysjung/agent-harness 26.167.0 → 26.169.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 +2 -2
- package/README.md +2 -2
- package/dist/{chunk-KVXCTS6D.js → chunk-SU2RUN2W.js} +116 -88
- package/dist/chunk-SU2RUN2W.js.map +1 -0
- package/dist/index.js +1643 -966
- package/dist/index.js.map +1 -1
- package/dist/trust-tier-drift.js +5 -2
- package/dist/trust-tier-drift.js.map +1 -1
- package/package.json +1 -1
- package/templates/CLAUDE.md +7 -0
- package/templates/mcp.json +2 -8
- package/templates/rules/ship-checklist.md +2 -2
- package/templates/skills/audit-harness-fit/SKILL.md +15 -4
- package/templates/skills/audit-harness-fit/references/apply.md +18 -0
- package/templates/skills/audit-harness-fit/references/audit.md +16 -3
- package/templates/skills/audit-harness-fit/references/verification.md +7 -0
- package/templates/skills/chatgpt-delegate/SKILL.md +221 -0
- package/templates/skills/external-model-consult/SKILL.md +82 -40
- package/templates/skills/external-model-consult/references/direct-calls.md +23 -14
- package/templates/skills/external-model-consult/references/model-evidence.md +156 -0
- package/templates/skills/external-model-consult/scripts/codex-ask.sh +4 -3
- package/templates/skills/external-model-consult/scripts/gemini-ask.sh +3 -3
- package/templates/skills/gemini-delegate/SKILL.md +223 -0
- package/templates/skills/genspark-delegate/SKILL.md +283 -0
- package/templates/skills/model-orchestration/SKILL.md +5 -0
- package/templates/skills/multi-model-debate/SKILL.md +187 -0
- package/templates/skills/multi-model-debate/agents/openai.yaml +4 -0
- package/templates/skills/multi-model-debate/references/adapters.md +143 -0
- package/templates/skills/multi-model-debate/references/evaluation.md +80 -0
- package/templates/skills/multi-model-debate/references/prompts.md +87 -0
- package/templates/skills/multi-model-debate/references/research.md +171 -0
- package/templates/track-mcp-map.tsv +3 -1
- package/dist/chunk-KVXCTS6D.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -17,10 +17,12 @@ import {
|
|
|
17
17
|
INTERNAL_BUNDLED_SKILL_IDS,
|
|
18
18
|
LEGACY_INSTALL_LOG_DIR,
|
|
19
19
|
POLICY_DIRS,
|
|
20
|
+
PROJECT_SKELETON_DIRS,
|
|
20
21
|
REMOVED_CATALOG_ASSETS,
|
|
21
22
|
RENAMED_SKILL_IDS,
|
|
22
23
|
RETIRED_AGENTS,
|
|
23
24
|
RETIRED_AGENT_IDS,
|
|
25
|
+
RETIRED_HOOK_WIRING_REFS,
|
|
24
26
|
RETIRED_PATHS,
|
|
25
27
|
RETIRED_SKILL_IDS,
|
|
26
28
|
SHARED_FILES,
|
|
@@ -35,10 +37,11 @@ import {
|
|
|
35
37
|
assetCliSupport,
|
|
36
38
|
assetReachesCli,
|
|
37
39
|
assetTrustTier,
|
|
38
|
-
|
|
40
|
+
backupContent,
|
|
39
41
|
backupFile,
|
|
40
42
|
backupIfLossyUtf8,
|
|
41
43
|
backupOriginal,
|
|
44
|
+
buildAssetEntries,
|
|
42
45
|
buildAssetSpec,
|
|
43
46
|
buildInstallLog,
|
|
44
47
|
buildManifest,
|
|
@@ -47,12 +50,10 @@ import {
|
|
|
47
50
|
collectPolicyHashes,
|
|
48
51
|
collectSkillHashes,
|
|
49
52
|
continuousNoteEnd,
|
|
50
|
-
copyBackupDir,
|
|
51
53
|
copyDir,
|
|
52
54
|
copyFile,
|
|
53
55
|
corruptedInstallLogMessage,
|
|
54
56
|
createOutsideGuard,
|
|
55
|
-
dirTreesIdentical,
|
|
56
57
|
dropContinuousSkillsNote,
|
|
57
58
|
dropHarnessQuoteBlocks,
|
|
58
59
|
ensureDir,
|
|
@@ -103,7 +104,7 @@ import {
|
|
|
103
104
|
targetsInclude,
|
|
104
105
|
upsertHarnessImport,
|
|
105
106
|
writeInstallLog
|
|
106
|
-
} from "./chunk-
|
|
107
|
+
} from "./chunk-SU2RUN2W.js";
|
|
107
108
|
|
|
108
109
|
// node_modules/sisteransi/src/index.js
|
|
109
110
|
var require_src = __commonJS({
|
|
@@ -787,8 +788,7 @@ var cac = (name = "") => new CAC(name);
|
|
|
787
788
|
|
|
788
789
|
// src/commands/install.ts
|
|
789
790
|
init_esm_shims();
|
|
790
|
-
import {
|
|
791
|
-
import { join as join32, resolve as resolve2 } from "path";
|
|
791
|
+
import { resolve as resolve2 } from "path";
|
|
792
792
|
import { fileURLToPath } from "url";
|
|
793
793
|
|
|
794
794
|
// src/adapters/index.ts
|
|
@@ -827,45 +827,59 @@ function keepHookRef(hook, claudeDir, removed) {
|
|
|
827
827
|
if (!exists && !removed.includes(relPath)) removed.push(relPath);
|
|
828
828
|
return exists;
|
|
829
829
|
}
|
|
830
|
-
function
|
|
830
|
+
function findStaleHookRefs(settingsPath, claudeDir, spare) {
|
|
831
831
|
let settings;
|
|
832
832
|
try {
|
|
833
833
|
settings = JSON.parse(readFileSync(settingsPath, "utf8"));
|
|
834
834
|
} catch {
|
|
835
|
-
return
|
|
835
|
+
return [];
|
|
836
|
+
}
|
|
837
|
+
const stale = [];
|
|
838
|
+
for (const eventEntries of Object.values(settings.hooks ?? {})) {
|
|
839
|
+
if (!Array.isArray(eventEntries)) continue;
|
|
840
|
+
for (const entry of eventEntries) {
|
|
841
|
+
if (!Array.isArray(entry?.hooks)) continue;
|
|
842
|
+
for (const hook of entry.hooks) {
|
|
843
|
+
if (spare?.(projectAnchoredRef(hook?.command ?? "", claudeDir) ?? "") === true) continue;
|
|
844
|
+
keepHookRef(hook, claudeDir, stale);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
return stale;
|
|
849
|
+
}
|
|
850
|
+
function removeRetiredHookRefs(settingsPath, claudeDir, refs, opts = {}) {
|
|
851
|
+
let settings;
|
|
852
|
+
try {
|
|
853
|
+
settings = JSON.parse(readFileSync(settingsPath, "utf8"));
|
|
854
|
+
} catch {
|
|
855
|
+
return [];
|
|
836
856
|
}
|
|
837
|
-
const hookEvents = settings.hooks ?? {};
|
|
838
857
|
const removed = [];
|
|
839
|
-
const
|
|
840
|
-
|
|
841
|
-
if (!
|
|
842
|
-
|
|
858
|
+
const isRetiredDead = (hook) => {
|
|
859
|
+
const rel = projectAnchoredRef(hook?.command ?? "", claudeDir);
|
|
860
|
+
if (rel === void 0 || !refs.includes(rel)) return false;
|
|
861
|
+
if (opts.evenIfScriptExists !== true && existsSync(join(claudeDir, rel))) return false;
|
|
862
|
+
if (!removed.includes(rel)) removed.push(rel);
|
|
863
|
+
return true;
|
|
864
|
+
};
|
|
865
|
+
const hooks = {};
|
|
866
|
+
for (const [event, entries] of Object.entries(settings.hooks ?? {})) {
|
|
867
|
+
if (!Array.isArray(entries)) {
|
|
868
|
+
hooks[event] = entries;
|
|
843
869
|
continue;
|
|
844
870
|
}
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
)
|
|
852
|
-
)
|
|
853
|
-
})).filter((entry) => entry.hooks.length > 0);
|
|
871
|
+
hooks[event] = entries.flatMap((entry) => {
|
|
872
|
+
if (!Array.isArray(entry?.hooks)) return [entry];
|
|
873
|
+
const kept = entry.hooks.filter((hook) => !isRetiredDead(hook));
|
|
874
|
+
if (kept.length === entry.hooks.length) return [entry];
|
|
875
|
+
return kept.length > 0 ? [{ ...entry, hooks: kept }] : [];
|
|
876
|
+
});
|
|
854
877
|
}
|
|
855
|
-
|
|
856
|
-
}
|
|
857
|
-
function findStaleHookRefs(settingsPath, claudeDir) {
|
|
858
|
-
return scanStaleHookRefs(settingsPath, claudeDir)?.removed ?? [];
|
|
859
|
-
}
|
|
860
|
-
function cleanStaleHookRefs(settingsPath, claudeDir, spare) {
|
|
861
|
-
const scan = scanStaleHookRefs(settingsPath, claudeDir, spare);
|
|
862
|
-
if (scan === null) return [];
|
|
863
|
-
if (scan.removed.length > 0) {
|
|
864
|
-
const next = { ...scan.settings, hooks: scan.cleanedHooks };
|
|
865
|
-
writeFileSync(settingsPath, `${JSON.stringify(next, null, 2)}
|
|
878
|
+
if (removed.length > 0 && opts.write !== false) {
|
|
879
|
+
writeFileSync(settingsPath, `${JSON.stringify({ ...settings, hooks }, null, 2)}
|
|
866
880
|
`);
|
|
867
881
|
}
|
|
868
|
-
return
|
|
882
|
+
return removed;
|
|
869
883
|
}
|
|
870
884
|
|
|
871
885
|
// src/adapters/contract.ts
|
|
@@ -1424,6 +1438,125 @@ init_esm_shims();
|
|
|
1424
1438
|
|
|
1425
1439
|
// src/agents-md-merge.ts
|
|
1426
1440
|
init_esm_shims();
|
|
1441
|
+
|
|
1442
|
+
// src/retired-anchor-sections.ts
|
|
1443
|
+
init_esm_shims();
|
|
1444
|
+
import { createHash } from "crypto";
|
|
1445
|
+
function retiredAnchorSectionKey(lines2) {
|
|
1446
|
+
const out = lines2.map((l) => l.replace(/\r$/, "").trimEnd());
|
|
1447
|
+
while (out.length > 1 && out.at(-1) === "") out.pop();
|
|
1448
|
+
return createHash("sha256").update(out.join("\n")).digest("hex");
|
|
1449
|
+
}
|
|
1450
|
+
var RETIRED_ANCHOR_SECTION_KEYS = /* @__PURE__ */ new Set([
|
|
1451
|
+
"522bac15bc17949697e291b837c0d9624f5e0c7666400a3baf94cdef7cfab8b5",
|
|
1452
|
+
// ## Rule 1 — Think Before Coding
|
|
1453
|
+
"ea10b7cd9b84a71949a071fae15256f490b3fe407159d7bddcf3f882310fa835",
|
|
1454
|
+
// ## Rule 2 — Simplicity First
|
|
1455
|
+
"c486465d9554f2de1711cd056437f6a95a5d3243264b82d29a6957f24b5a6de3",
|
|
1456
|
+
// ## Rule 3 — Surgical Changes
|
|
1457
|
+
"3d080574cc7ae81eda75c4aa019445b38b6f69c5f9eeeda9097e10b6041e7e94",
|
|
1458
|
+
// ## Rule 4 — Goal-Driven Execution
|
|
1459
|
+
"11e43547f6b1613a5dabb3edaf5131d696a3fd375b3c22558f03555a0f5cf2ef",
|
|
1460
|
+
// ## Rule 5 — Use the model only for judgment calls
|
|
1461
|
+
"60d91718de6ae16ec86b50e7c9a939a6ca5fc0721422b592700cc9972f55aca2",
|
|
1462
|
+
// ## Rule 6 — Token budgets are not advisory
|
|
1463
|
+
"9d482c7307cbcb02acc6fb35cd57b24baa9fce04528e2bd21e1e877603f8b85b",
|
|
1464
|
+
// ## Rule 7 — Surface conflicts, don't average them
|
|
1465
|
+
"abca09f13833ce18bd0ffc0793d59780c35447ca79c2a512afcbc9d7a7116ed8",
|
|
1466
|
+
// ## Rule 8 — Read before you write
|
|
1467
|
+
"5b4eae6c3dc2d92cfb1772889650882a4a2115f08a36602da9a1d96697059b91",
|
|
1468
|
+
// ## Rule 9 — Tests verify intent, not just behavior
|
|
1469
|
+
"632670b8897dc373d4a12f611c2436d9a8e86d8cdab917eecabc4bf2a56f8030",
|
|
1470
|
+
// ## Rule 10 — Checkpoint after every significant step
|
|
1471
|
+
"252e99f25b05a47e5ce884ac9f1e1d34156116b9a76331e2510b1354a6c98cd4",
|
|
1472
|
+
// ## Rule 11 — Match the codebase's conventions, even if you disagree
|
|
1473
|
+
"c28a12b589b6576379793c51fd01dbfaf91d391ac773f84ae0477ddb60a1e3e8",
|
|
1474
|
+
// ## Rule 12 — Fail loud
|
|
1475
|
+
"72feaf96f998989351f8083edb891c7b0c3bccca65dbea2732fb2864b8f98bd4",
|
|
1476
|
+
// ## 의사결정 및 컨펌 요청 시
|
|
1477
|
+
"95c932ecb4f4fdcdd8d483f9d02166b906f6eb045ef33ee474a16913d0c3e6cd",
|
|
1478
|
+
// ## Phase/작업 완료 시 Self-Audit 실행:
|
|
1479
|
+
"c7f9fa755bd66d80c2df496c6c759e8accd736cd8fef64460feb7ae9cd4c8006",
|
|
1480
|
+
// ## Context Management
|
|
1481
|
+
"6ec0b415a8f86c9940bccd3e6db895ea4a110185e76fef223a348f9553fd897c",
|
|
1482
|
+
// ## Rule 12 — Fail loud
|
|
1483
|
+
"a78f0ffa6b51c9b45c964ecb92db951534336f7e558a3d39d11f015ee6c3466c",
|
|
1484
|
+
// ## Anti-Patterns (Forbidden)
|
|
1485
|
+
"50d00a511e87571d7ff85f6584b87b9f5c2eef06122d8ac5fdccf3f047643f12",
|
|
1486
|
+
// ## When Requesting Decisions or Confirmation
|
|
1487
|
+
"574519e07f48bcc04f0e3d505c0f9c157ddd426b5eda2baa99656cd4712dad2b",
|
|
1488
|
+
// ## Run Self-Audit on Phase/Task Completion
|
|
1489
|
+
"df73823c02e6eee000388444964acfec233ffeec0e20fdc5d31eb018d2ca93c1",
|
|
1490
|
+
// ## Context Management
|
|
1491
|
+
"8f49f0a592575a65eadd3e33950c96616223aa8bb116707103affeef71db28c6",
|
|
1492
|
+
// ## Delegate the Building, Keep the Deciding
|
|
1493
|
+
"6115eccc3b2702f436a9db7d6af9803fa7b1fbf7b9ff1110ea658fadd67ec5ea",
|
|
1494
|
+
// ## 1. Think before coding
|
|
1495
|
+
"15b120395ec8e757ecbe3e65775371a9120953617fb37eb062e0c176b52f045a",
|
|
1496
|
+
// ## 2. Prefer the simplest sufficient solution
|
|
1497
|
+
"cc466b4781444ca54aedebd20ba3aa531c3aa84cc438953df84b133bd32a0e1e",
|
|
1498
|
+
// ## 3. Make surgical changes and preserve existing work
|
|
1499
|
+
"f6d1f6f02cb3c19e61becf329f20335c54e684bccb06b33e4b7f66eb49f9c6b6",
|
|
1500
|
+
// ## 4. Define success before editing
|
|
1501
|
+
"372bf30b282b861c6a89e355c94ffd239bb0202e2ef928f8af99d640579710f0",
|
|
1502
|
+
// ## 5. Report evidence, not confidence
|
|
1503
|
+
"66ca0e3aae98d24c65afbc418645df29d8c570be81918c42192eb1beecd21f23",
|
|
1504
|
+
// ## 6. Do not cross high-impact boundaries alone
|
|
1505
|
+
"9c6c27eb57a9790273fa3fdf0df3628d927b025f870f09933692d1b522d73a25",
|
|
1506
|
+
// ## Decisions and explanations
|
|
1507
|
+
"922d100c2dc6ef8a53989a920c3554afa7efd148255bc144412403c0289439e6",
|
|
1508
|
+
// ## Decisions and explanations
|
|
1509
|
+
"330a67775a28557431aebf36a44018fc5194a4b929bf2cf95184b1f0828a6360",
|
|
1510
|
+
// ## 1. Understand First
|
|
1511
|
+
"3b2f0147dc7bd0f01fd12485790199ef4ee96a6b723e244ffbf3dc96933a1ab8",
|
|
1512
|
+
// ## 2. Define Success and Keep It Simple
|
|
1513
|
+
"8802fb25c05879c98dbf8407001d239c410d25b4fa2521b11d9d7cc108f73b29",
|
|
1514
|
+
// ## 3. Preserve Sound Boundaries
|
|
1515
|
+
"1d0d9bf05d8870dd0dc62e388ceffc98923f1dd2074ef8038f80a50c9b5b9a81",
|
|
1516
|
+
// ## 4. Make Surgical Changes
|
|
1517
|
+
"05e08e2318061141464ae714d6770d35527576affa787740d9c6d9345d3462dd",
|
|
1518
|
+
// ## 5. Verify and Review
|
|
1519
|
+
"7b39f1f92a72517d6ae52599becfef29f675e48185a3483dc993d7accd4aee7c",
|
|
1520
|
+
// ## 6. Protect High-Impact Boundaries
|
|
1521
|
+
"d5d93c55de33e4cdcb38f76c6d6d70a6e0beac7a176c8aa1daa09905ccc5fe39",
|
|
1522
|
+
// ## 7. Report Evidence
|
|
1523
|
+
"cefc8bb6194d4f1b6302345e96ffb3a28b8b992032fd40f9f5b4f18997657e7a",
|
|
1524
|
+
// ## Presenting a decision
|
|
1525
|
+
"7385ff5df34148278a8f4b164c11ba5f9355626abcc13a822dd1ebb206321c2f",
|
|
1526
|
+
// ## Skills that apply continuously
|
|
1527
|
+
"ad9fd1ff225c57c9801a7d7472bf4ddf9c952c602f4a93eae86fbcb3bcf468bc",
|
|
1528
|
+
// ## 7. Report Evidence
|
|
1529
|
+
"868fe823fc40011d08816c9f1105ef6df7ca7f2479779303ce9583bba1ce6e0d",
|
|
1530
|
+
// ## Skills that apply continuously
|
|
1531
|
+
"3f8259ca7c92d79cca1a36bba38f36d4ef5a96fd6cafbd0edb5a79492082f46f",
|
|
1532
|
+
// ## Presenting a decision
|
|
1533
|
+
"ecfdcd50e97723af77cfe6889bbad784cdeb86dd0c52af3ff98ddf6adf80f445",
|
|
1534
|
+
// ## 1. Resolve what matters, then act
|
|
1535
|
+
"9321cb1312a080bcf560940b8f86d3d59b39c667add4b8f3ad785ddae9bc8b20",
|
|
1536
|
+
// ## 2. Choose the simplest sufficient solution
|
|
1537
|
+
"a4c345996a8578cc24b62fde27084e5848a61e4a40d91b0c72a95da46203b43c",
|
|
1538
|
+
// ## 3. Keep changes focused and preserve existing work
|
|
1539
|
+
"1adbf1bf88bb8288744e1c1d73518ffdec12a5b668d5a225e2bedd006844466c",
|
|
1540
|
+
// ## 4. Define success and verify proportionally
|
|
1541
|
+
"7636e57a877d54039f0c2829fde84202c65378bd36b8c8abb7274a29eaa5f8ac",
|
|
1542
|
+
// ## 5. Report evidence and stop unproductive loops
|
|
1543
|
+
"ddf2be9e04c04bf53ad9b5476e45639b33a60bf49bd3a83835796632052b17b4",
|
|
1544
|
+
// ## 6. Keep authority explicit
|
|
1545
|
+
"16b0edc0f4886a9f9ec94e6fb97206c385286b2c9d5563b2324b7a2f4a434435",
|
|
1546
|
+
// ## 1. Understand what success means
|
|
1547
|
+
"b10ab1eafe25bb7ba3b20b907b1d7eedde03e52fbd49905f24e192e30b4e1b9a",
|
|
1548
|
+
// ## 2. Choose and adapt the approach
|
|
1549
|
+
"69bcfb8b37ac184b1a30945a0dea59108ef1242c2f40fb778be118cbd524aea7",
|
|
1550
|
+
// ## 3. Keep changes focused and coherent
|
|
1551
|
+
"f22a5a13c5ed8460c9370212d91354d0fea2fefc7f420a1eb3088504ae8da6c0",
|
|
1552
|
+
// ## 4. Verify outcomes in proportion to consequences
|
|
1553
|
+
"7a335cde69e8055ee5e137a9a5c09fd7f7c2e551ab099a2cda87f0c3dcd95a19",
|
|
1554
|
+
// ## 5. Let evidence determine readiness
|
|
1555
|
+
"79797568bb71bd0f7b14393f72e61721a4bfe99ecfb6126ffa38a6c03c53d765"
|
|
1556
|
+
// ## 6. Act within clear authority
|
|
1557
|
+
]);
|
|
1558
|
+
|
|
1559
|
+
// src/agents-md-merge.ts
|
|
1427
1560
|
var marker = (name) => ({
|
|
1428
1561
|
start: `<!-- uzys-harness:${name}:start -->`,
|
|
1429
1562
|
end: `<!-- uzys-harness:${name}:end -->`
|
|
@@ -1562,8 +1695,11 @@ function mergeAgentsMd(params) {
|
|
|
1562
1695
|
}
|
|
1563
1696
|
const rulesNow = sectionBody(existing, names, RULES_SECTION);
|
|
1564
1697
|
const rulesNew = sectionBody(rendered, names, RULES_SECTION);
|
|
1698
|
+
let replacedRulesHeadings = null;
|
|
1565
1699
|
if (rulesNow !== null && rulesNew !== null && blockRange(rulesNow, ANCHOR_BLOCK) !== null) {
|
|
1566
1700
|
bodies.set(RULES_SECTION, refreshBlock(rulesNow, rulesNew, ANCHOR_BLOCK));
|
|
1701
|
+
} else if (rulesNow !== null && rulesNew !== null) {
|
|
1702
|
+
replacedRulesHeadings = headingLines(rulesNew);
|
|
1567
1703
|
}
|
|
1568
1704
|
const merged = bodies.size === 0 ? rendered : assemble(rendered, names, bodies);
|
|
1569
1705
|
const mergedText = merged.replace(/\r/g, "");
|
|
@@ -1571,8 +1707,8 @@ function mergeAgentsMd(params) {
|
|
|
1571
1707
|
existing,
|
|
1572
1708
|
names,
|
|
1573
1709
|
rendered,
|
|
1574
|
-
|
|
1575
|
-
|
|
1710
|
+
params.harnessRuleHeadings ?? /* @__PURE__ */ new Set(),
|
|
1711
|
+
replacedRulesHeadings
|
|
1576
1712
|
).filter((e2) => !mergedText.includes(e2.join("\n").replace(/\r/g, "")));
|
|
1577
1713
|
if (extras.length === 0) return merged;
|
|
1578
1714
|
return `${merged.replace(/\n+$/, "\n")}${extras.map((e2) => e2.join("\n")).join("\n")}`.replace(
|
|
@@ -1580,24 +1716,48 @@ function mergeAgentsMd(params) {
|
|
|
1580
1716
|
"\n"
|
|
1581
1717
|
);
|
|
1582
1718
|
}
|
|
1583
|
-
function
|
|
1719
|
+
function headingOwner(bare, ctx) {
|
|
1720
|
+
const name = /^## (.+?)\s*$/.exec(bare)?.[1];
|
|
1721
|
+
if (name !== void 0 && ctx.names.has(name)) return "harness";
|
|
1722
|
+
if (ctx.region === CONTEXT_SECTION || ctx.region === RULES_SECTION) return "body";
|
|
1723
|
+
if (ctx.region === HARNESS_RULES_SECTION && ctx.harnessHeadings.has(bare.trim())) {
|
|
1724
|
+
return "harness";
|
|
1725
|
+
}
|
|
1726
|
+
return "installer";
|
|
1727
|
+
}
|
|
1728
|
+
function headingLines(body) {
|
|
1729
|
+
return new Set(body.filter((l) => /^## /.test(l)).map((l) => l.replace(/\r$/, "").trim()));
|
|
1730
|
+
}
|
|
1731
|
+
function renderedRuleHeadings(rendered, names) {
|
|
1732
|
+
return headingLines(sectionBody(rendered, names, HARNESS_RULES_SECTION) ?? []);
|
|
1733
|
+
}
|
|
1734
|
+
function extraTopLevelSections(existing, names, rendered, harnessRuleHeadings, replacedRulesHeadings) {
|
|
1735
|
+
const harnessHeadings = /* @__PURE__ */ new Set([
|
|
1736
|
+
...harnessRuleHeadings,
|
|
1737
|
+
...renderedRuleHeadings(rendered, names)
|
|
1738
|
+
]);
|
|
1584
1739
|
const out = [];
|
|
1740
|
+
const replacedSections = /* @__PURE__ */ new Set();
|
|
1585
1741
|
let current = null;
|
|
1586
|
-
let
|
|
1742
|
+
let region = null;
|
|
1587
1743
|
for (const line of existing.split("\n")) {
|
|
1588
1744
|
const bare = line.replace(/\r$/, "");
|
|
1589
1745
|
const name = /^## (.+?)\s*$/.exec(bare)?.[1];
|
|
1590
1746
|
if (name !== void 0) {
|
|
1591
1747
|
if (current !== null) out.push(current);
|
|
1592
|
-
if (names.has(name))
|
|
1593
|
-
const
|
|
1594
|
-
|
|
1748
|
+
if (names.has(name)) region = name;
|
|
1749
|
+
const owner = headingOwner(bare, { region, names, harnessHeadings });
|
|
1750
|
+
const replacedBody = owner === "body" && region === RULES_SECTION && replacedRulesHeadings !== null && !replacedRulesHeadings.has(bare.trim());
|
|
1751
|
+
current = owner === "installer" || replacedBody ? [line] : null;
|
|
1752
|
+
if (replacedBody && current !== null) replacedSections.add(current);
|
|
1595
1753
|
continue;
|
|
1596
1754
|
}
|
|
1597
1755
|
if (current !== null) current.push(line);
|
|
1598
1756
|
}
|
|
1599
1757
|
if (current !== null) out.push(current);
|
|
1600
|
-
return out.
|
|
1758
|
+
return out.filter(
|
|
1759
|
+
(lines2) => !replacedSections.has(lines2) || !RETIRED_ANCHOR_SECTION_KEYS.has(retiredAnchorSectionKey(lines2))
|
|
1760
|
+
).map((lines2) => {
|
|
1601
1761
|
const trimmed = [...lines2];
|
|
1602
1762
|
while (trimmed.length > 1 && (trimmed.at(-1) ?? "").trim() === "") trimmed.pop();
|
|
1603
1763
|
return trimmed;
|
|
@@ -1630,14 +1790,9 @@ function installerSectionsInHarnessArea(existing, names, harnessRuleHeadings) {
|
|
|
1630
1790
|
const name = /^## (.+?)\s*$/.exec(bare)?.[1];
|
|
1631
1791
|
if (name !== void 0) {
|
|
1632
1792
|
if (current !== null) out.push(current);
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
continue;
|
|
1637
|
-
}
|
|
1638
|
-
if (region === CONTEXT_SECTION || region === RULES_SECTION) continue;
|
|
1639
|
-
const harnessRule = region === HARNESS_RULES_SECTION && harnessRuleHeadings.has(bare.trim());
|
|
1640
|
-
if (!harnessRule) current = [bare];
|
|
1793
|
+
if (names.has(name)) region = name;
|
|
1794
|
+
const owner = headingOwner(bare, { region, names, harnessHeadings: harnessRuleHeadings });
|
|
1795
|
+
current = owner === "installer" ? [bare] : null;
|
|
1641
1796
|
continue;
|
|
1642
1797
|
}
|
|
1643
1798
|
if (current !== null) current.push(bare);
|
|
@@ -2993,8 +3148,8 @@ function padDisplay(s, width) {
|
|
|
2993
3148
|
|
|
2994
3149
|
// src/install-writes.ts
|
|
2995
3150
|
init_esm_shims();
|
|
2996
|
-
import { existsSync as
|
|
2997
|
-
import { dirname, join as
|
|
3151
|
+
import { existsSync as existsSync6, mkdirSync, readFileSync as readFileSync4, statSync, writeFileSync as writeFileSync3 } from "fs";
|
|
3152
|
+
import { dirname, join as join7, relative, sep } from "path";
|
|
2998
3153
|
|
|
2999
3154
|
// src/env-files.ts
|
|
3000
3155
|
init_esm_shims();
|
|
@@ -3003,8 +3158,9 @@ import { join as join4 } from "path";
|
|
|
3003
3158
|
var ENV_EXAMPLE_BODY = `# .env.example \u2014 csr-supabase Track
|
|
3004
3159
|
# Copy to .env (gitignored) and fill in values: cp .env.example .env
|
|
3005
3160
|
|
|
3006
|
-
# ===== Supabase Management API
|
|
3007
|
-
#
|
|
3161
|
+
# ===== Supabase Management API =====
|
|
3162
|
+
# MCP \uC11C\uBC84(https://mcp.supabase.com/mcp)\uB294 \uCCAB \uC0AC\uC6A9 \uB54C \uBE0C\uB77C\uC6B0\uC800 OAuth \uB85C\uADF8\uC778 \u2014 \uC774 \uD1A0\uD070\uC774 \uD544\uC694 \uC5C6\uB2E4.
|
|
3163
|
+
# Personal Access Token \u2014 \uB85C\uCEEC stdio \uC11C\uBC84(@supabase/mcp-server-supabase)\uB098 CI \uCC98\uB7FC \uBE0C\uB77C\uC6B0\uC800 \uB85C\uADF8\uC778\uC774 \uC548 \uB418\uB294 \uACF3\uC5D0\uC11C\uB9CC
|
|
3008
3164
|
# \uBC1C\uAE09: https://supabase.com/dashboard/account/tokens
|
|
3009
3165
|
SUPABASE_ACCESS_TOKEN=
|
|
3010
3166
|
|
|
@@ -3169,6 +3325,28 @@ function advisory(input) {
|
|
|
3169
3325
|
if (input.run === "update") return j("leave", "", "keep");
|
|
3170
3326
|
return j("create", LINES.scaffoldWrote, "advisory");
|
|
3171
3327
|
}
|
|
3328
|
+
function judgeDisplaced(input) {
|
|
3329
|
+
if (input.backup === null) return { verdict: "leave", line: "", record: "forget" };
|
|
3330
|
+
if (!input.backupExists) {
|
|
3331
|
+
return {
|
|
3332
|
+
verdict: "advise",
|
|
3333
|
+
line: "could not put back your earlier file \u2014 its backup is no longer there",
|
|
3334
|
+
record: "forget"
|
|
3335
|
+
};
|
|
3336
|
+
}
|
|
3337
|
+
if (!input.slotEmpty) {
|
|
3338
|
+
return {
|
|
3339
|
+
verdict: "advise",
|
|
3340
|
+
line: `not put back \u2014 the spot is taken; your earlier file is at ${input.backup}`,
|
|
3341
|
+
record: "keep"
|
|
3342
|
+
};
|
|
3343
|
+
}
|
|
3344
|
+
return {
|
|
3345
|
+
verdict: "restore",
|
|
3346
|
+
line: `put back your earlier file (from ${input.backup})`,
|
|
3347
|
+
record: "forget"
|
|
3348
|
+
};
|
|
3349
|
+
}
|
|
3172
3350
|
|
|
3173
3351
|
// src/mcp-merge.ts
|
|
3174
3352
|
init_esm_shims();
|
|
@@ -3188,6 +3366,12 @@ var PRIOR_MCP_SERVERS = [
|
|
|
3188
3366
|
value: { type: "stdio", command: "npx", args: ["-y", "@railway/mcp-server"] },
|
|
3189
3367
|
updated: "now Railway's remote server \u2014 sign in on first use, see NEXT"
|
|
3190
3368
|
},
|
|
3369
|
+
{
|
|
3370
|
+
// #766 — `@supabase/mcp-server` 는 npm 에 없는 이름이라(404) 서버가 뜬 적이 없다
|
|
3371
|
+
name: "supabase",
|
|
3372
|
+
value: { type: "stdio", command: "npx", args: ["-y", "@supabase/mcp-server"] },
|
|
3373
|
+
updated: "now Supabase's remote server \u2014 sign in on first use, see NEXT"
|
|
3374
|
+
},
|
|
3191
3375
|
{
|
|
3192
3376
|
name: "chrome-devtools",
|
|
3193
3377
|
value: { type: "stdio", command: "npx", args: ["-y", "chrome-devtools-mcp@latest"] },
|
|
@@ -3366,6 +3550,8 @@ function recordedMcpAssetIds(log) {
|
|
|
3366
3550
|
|
|
3367
3551
|
// src/recorded.ts
|
|
3368
3552
|
init_esm_shims();
|
|
3553
|
+
import { existsSync as existsSync5 } from "fs";
|
|
3554
|
+
import { join as join6 } from "path";
|
|
3369
3555
|
|
|
3370
3556
|
// src/cli-ownership.ts
|
|
3371
3557
|
init_esm_shims();
|
|
@@ -3379,19 +3565,19 @@ var SHARED_BY_ALL = (self) => [
|
|
|
3379
3565
|
];
|
|
3380
3566
|
var CLI_OWNERSHIP = {
|
|
3381
3567
|
claude: [
|
|
3382
|
-
{ path: ".claude/", kind: "
|
|
3568
|
+
{ path: ".claude/", kind: "recorded", sharedWith: [] },
|
|
3383
3569
|
{ path: HARNESS_ANCHOR_FILE, kind: "anchor", sharedWith: [] },
|
|
3384
3570
|
{ path: "CLAUDE.md", kind: "import-block", sharedWith: [] },
|
|
3385
3571
|
...SHARED_BY_ALL("claude")
|
|
3386
3572
|
],
|
|
3387
3573
|
codex: [
|
|
3388
|
-
{ path: ".codex/", kind: "
|
|
3574
|
+
{ path: ".codex/", kind: "recorded", sharedWith: [] },
|
|
3389
3575
|
{ path: "AGENTS.md", kind: "recorded", sharedWith: ["opencode", "antigravity"] },
|
|
3390
3576
|
{ path: ".agents/skills/", kind: "recorded", sharedWith: ["opencode", "antigravity"] },
|
|
3391
3577
|
...SHARED_BY_ALL("codex")
|
|
3392
3578
|
],
|
|
3393
3579
|
opencode: [
|
|
3394
|
-
{ path: ".opencode/", kind: "
|
|
3580
|
+
{ path: ".opencode/", kind: "recorded", sharedWith: [] },
|
|
3395
3581
|
{ path: "opencode.json", kind: "recorded", sharedWith: [] },
|
|
3396
3582
|
{ path: "AGENTS.md", kind: "recorded", sharedWith: ["codex", "antigravity"] },
|
|
3397
3583
|
{ path: ".agents/skills/", kind: "recorded", sharedWith: ["codex", "antigravity"] },
|
|
@@ -3440,14 +3626,36 @@ function ownersOf(path) {
|
|
|
3440
3626
|
);
|
|
3441
3627
|
}
|
|
3442
3628
|
function isRecordedTrackTarget(log, path) {
|
|
3629
|
+
return recordedTrackTargets(log)(path);
|
|
3630
|
+
}
|
|
3631
|
+
function recordedTrackTargets(log) {
|
|
3443
3632
|
const spec = {
|
|
3444
3633
|
tracks: log.spec.tracks.filter(isTrack),
|
|
3445
3634
|
selectedInternalSkills: INTERNAL_BUNDLED_SKILL_IDS
|
|
3446
3635
|
};
|
|
3447
|
-
|
|
3448
|
-
|
|
3636
|
+
const entries = buildManifest(spec).filter((e2) => e2.applies(spec));
|
|
3637
|
+
const templates = join6(resolveBundleRoot(), "templates");
|
|
3638
|
+
return (path) => entries.some(
|
|
3639
|
+
(e2) => e2.type === "dir" ? path.startsWith(`${e2.target}/`) && existsSync5(join6(templates, e2.source, path.slice(e2.target.length + 1))) : e2.target === path
|
|
3449
3640
|
);
|
|
3450
3641
|
}
|
|
3642
|
+
function recordedTrackSpots(log) {
|
|
3643
|
+
const spec = {
|
|
3644
|
+
tracks: log.spec.tracks.filter(isTrack),
|
|
3645
|
+
selectedInternalSkills: INTERNAL_BUNDLED_SKILL_IDS
|
|
3646
|
+
};
|
|
3647
|
+
const templates = join6(resolveBundleRoot(), "templates");
|
|
3648
|
+
const out = [];
|
|
3649
|
+
for (const e2 of buildManifest(spec)) {
|
|
3650
|
+
if (!e2.applies(spec) || !e2.target.startsWith(CLAUDE_DIR)) continue;
|
|
3651
|
+
if (e2.type === "file") out.push(e2.target);
|
|
3652
|
+
else
|
|
3653
|
+
for (const rel of listFilesRecursive(join6(templates, e2.source)))
|
|
3654
|
+
out.push(`${e2.target}/${rel}`);
|
|
3655
|
+
}
|
|
3656
|
+
for (const p2 of RETIRED_PATHS) out.push(`${CLAUDE_DIR}${p2}`);
|
|
3657
|
+
return out;
|
|
3658
|
+
}
|
|
3451
3659
|
function isRetiredPath(path) {
|
|
3452
3660
|
return path.startsWith(CLAUDE_DIR) && RETIRED_PATHS.includes(path.slice(CLAUDE_DIR.length));
|
|
3453
3661
|
}
|
|
@@ -3480,9 +3688,30 @@ function recorded(log, path, opts = {}) {
|
|
|
3480
3688
|
const owners = ownersOf(path);
|
|
3481
3689
|
const ownedByLog = owners.some((c3) => clis.includes(c3));
|
|
3482
3690
|
const claudePath = path.startsWith(CLAUDE_DIR);
|
|
3483
|
-
if (ownedByLog &&
|
|
3691
|
+
if (ownedByLog && (claudePath ? legacyOwned() : isTarget(path))) {
|
|
3692
|
+
if (scanFieldOf(log, path) === "present") return { state: "none" };
|
|
3693
|
+
return { state: "no-sha" };
|
|
3694
|
+
}
|
|
3484
3695
|
return { state: "none" };
|
|
3485
3696
|
}
|
|
3697
|
+
function scanFieldOf(log, path) {
|
|
3698
|
+
const present2 = (yes) => yes ? "present" : "absent";
|
|
3699
|
+
if (path.startsWith(SKILLS_DIR)) return present2(log.skillFiles !== void 0);
|
|
3700
|
+
if (isExternalOnly(path))
|
|
3701
|
+
return present2((log.externalFiles ?? []).some((f) => isExternalOnly(f.path)));
|
|
3702
|
+
const policy = POLICY_DIRS.find(
|
|
3703
|
+
({ dir, ext }) => path.startsWith(`${CLAUDE_DIR}${dir}/`) && path.endsWith(ext)
|
|
3704
|
+
);
|
|
3705
|
+
if (policy === void 0) return "outside";
|
|
3706
|
+
return present2((log.policyFiles ?? []).some((f) => f.path.startsWith(`${policy.dir}/`)));
|
|
3707
|
+
}
|
|
3708
|
+
function isExternalOnly(path) {
|
|
3709
|
+
const owners = ownersOf(path);
|
|
3710
|
+
return owners.length > 0 && !owners.includes("claude");
|
|
3711
|
+
}
|
|
3712
|
+
function recordedOwner(log) {
|
|
3713
|
+
return (path) => recorded(log, path, { isTarget: () => true }).state !== "none";
|
|
3714
|
+
}
|
|
3486
3715
|
function kindOf(log, path) {
|
|
3487
3716
|
if (adapterFor(path) !== null) return "shared";
|
|
3488
3717
|
if (isScaffoldPath(path)) return "advisory";
|
|
@@ -3520,10 +3749,10 @@ function createInstallWriter(args) {
|
|
|
3520
3749
|
else external.set(path, sha256);
|
|
3521
3750
|
}
|
|
3522
3751
|
function harness(path, from, opts = {}) {
|
|
3523
|
-
const abs =
|
|
3752
|
+
const abs = join7(projectDir, path);
|
|
3524
3753
|
if (outside.skip(abs)) return null;
|
|
3525
3754
|
const next = "source" in from ? readFileSync4(from.source, "utf8") : from.content;
|
|
3526
|
-
const disk =
|
|
3755
|
+
const disk = existsSync6(abs) ? readFileSync4(abs, "utf8") : null;
|
|
3527
3756
|
const j4 = judge({
|
|
3528
3757
|
op: "write",
|
|
3529
3758
|
kind: "harness",
|
|
@@ -3585,8 +3814,8 @@ function createInstallWriter(args) {
|
|
|
3585
3814
|
const file = SHARED_FILES[path];
|
|
3586
3815
|
if (file === void 0) throw new Error(`install: ${path} is not a shared file`);
|
|
3587
3816
|
const adapter = ADAPTERS[file.adapter];
|
|
3588
|
-
const abs =
|
|
3589
|
-
const disk =
|
|
3817
|
+
const abs = join7(projectDir, path);
|
|
3818
|
+
const disk = existsSync6(abs) ? readFileSync4(abs, "utf8") : null;
|
|
3590
3819
|
const own = new Map(
|
|
3591
3820
|
(previousLog?.portions ?? []).filter((p2) => p2.path === path).map((p2) => [p2.key, p2.sha256])
|
|
3592
3821
|
);
|
|
@@ -3742,21 +3971,36 @@ function thisRunExclusions(spec, previous, accepts) {
|
|
|
3742
3971
|
]);
|
|
3743
3972
|
return { spec: specWithExclusions(spec, excluded), excluded };
|
|
3744
3973
|
}
|
|
3745
|
-
function inheritScanned(previous) {
|
|
3974
|
+
function inheritScanned(previous, projectDir) {
|
|
3746
3975
|
if (previous === null) return { policyFiles: [], skillFiles: [] };
|
|
3747
3976
|
const policyFiles = previous.policyFiles ?? [];
|
|
3748
3977
|
const skillFiles = previous.skillFiles ?? [];
|
|
3749
3978
|
if (previous.records === "writer")
|
|
3750
3979
|
return { policyFiles: [...policyFiles], skillFiles: [...skillFiles] };
|
|
3751
3980
|
const owned = (full) => recorded(previous, full).state !== "none";
|
|
3981
|
+
const kept = policyFiles.filter((f) => owned(`${CLAUDE_DIR2}${f.path}`));
|
|
3752
3982
|
return {
|
|
3753
|
-
policyFiles:
|
|
3983
|
+
policyFiles: projectDir === void 0 ? kept : [...kept, ...retiredClaims(previous, projectDir, kept)],
|
|
3754
3984
|
skillFiles: skillFiles.filter((f) => owned(`${SKILLS_DIR2}${f.path}`))
|
|
3755
3985
|
};
|
|
3756
3986
|
}
|
|
3987
|
+
function retiredClaims(previous, projectDir, kept) {
|
|
3988
|
+
const have = new Set(kept.map((f) => f.path));
|
|
3989
|
+
const out = [];
|
|
3990
|
+
for (const p2 of RETIRED_PATHS) {
|
|
3991
|
+
if (have.has(p2) || recorded(previous, `${CLAUDE_DIR2}${p2}`).state === "none") continue;
|
|
3992
|
+
const abs = join7(projectDir, CLAUDE_DIR2, p2);
|
|
3993
|
+
try {
|
|
3994
|
+
if (!statSync(abs).isFile()) continue;
|
|
3995
|
+
out.push({ path: p2, sha256: hashContent(readFileSync4(abs, "utf8")) });
|
|
3996
|
+
} catch {
|
|
3997
|
+
}
|
|
3998
|
+
}
|
|
3999
|
+
return out;
|
|
4000
|
+
}
|
|
3757
4001
|
function composeWriterLog(args) {
|
|
3758
4002
|
const { base, previous, ledger } = args;
|
|
3759
|
-
const inherited = inheritScanned(previous);
|
|
4003
|
+
const inherited = inheritScanned(previous, args.projectDir);
|
|
3760
4004
|
const policyFiles = mergeExternalFiles(inherited.policyFiles, ledger.policyFiles);
|
|
3761
4005
|
const skillFiles = mergeExternalFiles(inherited.skillFiles, ledger.skillFiles);
|
|
3762
4006
|
const externalFiles = mergeExternalFiles(previous?.externalFiles, [
|
|
@@ -3852,7 +4096,7 @@ function legacySettingsSeed(text, render, projectDir) {
|
|
|
3852
4096
|
} catch {
|
|
3853
4097
|
return seed;
|
|
3854
4098
|
}
|
|
3855
|
-
const claudeDir =
|
|
4099
|
+
const claudeDir = join7(projectDir, ".claude");
|
|
3856
4100
|
const candidates = new Set([...render.keys()].filter((k2) => k2.startsWith("hooks.")));
|
|
3857
4101
|
const hooks = isObject2(root) && isObject2(root.hooks) ? root.hooks : {};
|
|
3858
4102
|
for (const [event, groups] of Object.entries(hooks)) {
|
|
@@ -3965,13 +4209,13 @@ function legacyGitignoreSeed(text, render, previous) {
|
|
|
3965
4209
|
|
|
3966
4210
|
// src/installer.ts
|
|
3967
4211
|
init_esm_shims();
|
|
3968
|
-
import { chmodSync as chmodSync3, existsSync as
|
|
3969
|
-
import { basename as basename4, join as
|
|
4212
|
+
import { chmodSync as chmodSync3, existsSync as existsSync22, readdirSync as readdirSync4, readFileSync as readFileSync22, writeFileSync as writeFileSync9 } from "fs";
|
|
4213
|
+
import { basename as basename4, join as join30, relative as relative3, resolve, sep as sep4 } from "path";
|
|
3970
4214
|
|
|
3971
4215
|
// src/anchor-seed.ts
|
|
3972
4216
|
init_esm_shims();
|
|
3973
|
-
import { existsSync as
|
|
3974
|
-
import { join as
|
|
4217
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
|
|
4218
|
+
import { join as join8 } from "path";
|
|
3975
4219
|
|
|
3976
4220
|
// src/agents-md-render.ts
|
|
3977
4221
|
init_esm_shims();
|
|
@@ -4055,7 +4299,7 @@ ${CODEX_SIZE_NOTE}` : RULES_NOTE : "",
|
|
|
4055
4299
|
|
|
4056
4300
|
// src/anchor-seed.ts
|
|
4057
4301
|
function readIfExists(path) {
|
|
4058
|
-
if (!
|
|
4302
|
+
if (!existsSync7(path)) return null;
|
|
4059
4303
|
try {
|
|
4060
4304
|
return readFileSync5(path, "utf8");
|
|
4061
4305
|
} catch {
|
|
@@ -4078,7 +4322,7 @@ function compose(body, into) {
|
|
|
4078
4322
|
${trimmed.join("\n")}`;
|
|
4079
4323
|
}
|
|
4080
4324
|
function seedAgentsMdProjectContext(projectDir) {
|
|
4081
|
-
const root = readIfExists(
|
|
4325
|
+
const root = readIfExists(join8(projectDir, "CLAUDE.md"));
|
|
4082
4326
|
if (root === null) return null;
|
|
4083
4327
|
const withoutImport = stripHarnessImport(root) ?? root;
|
|
4084
4328
|
const body = dropHarnessQuoteBlocks(dropTitle(withoutImport.split("\n")));
|
|
@@ -4086,9 +4330,9 @@ function seedAgentsMdProjectContext(projectDir) {
|
|
|
4086
4330
|
return compose(body, "agents-md");
|
|
4087
4331
|
}
|
|
4088
4332
|
function seedRootClaudeProjectContext(projectDir, harnessRoot) {
|
|
4089
|
-
const agents = readIfExists(
|
|
4333
|
+
const agents = readIfExists(join8(projectDir, "AGENTS.md"));
|
|
4090
4334
|
if (agents === null) return null;
|
|
4091
|
-
const template = readIfExists(
|
|
4335
|
+
const template = readIfExists(join8(harnessRoot, AGENTS_MD_TEMPLATE));
|
|
4092
4336
|
if (template === null) return null;
|
|
4093
4337
|
const installer = projectContextInstallerLines({ existing: agents, template });
|
|
4094
4338
|
if (installer === null) return null;
|
|
@@ -4099,8 +4343,8 @@ function seedRootClaudeProjectContext(projectDir, harnessRoot) {
|
|
|
4099
4343
|
|
|
4100
4344
|
// src/ci-scaffold.ts
|
|
4101
4345
|
init_esm_shims();
|
|
4102
|
-
import { existsSync as
|
|
4103
|
-
import { join as
|
|
4346
|
+
import { existsSync as existsSync8 } from "fs";
|
|
4347
|
+
import { join as join9 } from "path";
|
|
4104
4348
|
var PYTHON_TRACKS = "data|csr-fastapi|full";
|
|
4105
4349
|
var VARIANTS = [
|
|
4106
4350
|
{
|
|
@@ -4124,12 +4368,12 @@ function installCiScaffold(ctx) {
|
|
|
4124
4368
|
for (const v2 of VARIANTS) {
|
|
4125
4369
|
if (!v2.applies(ctx.tracks)) continue;
|
|
4126
4370
|
const relTarget = `.github/workflows/${v2.target}`;
|
|
4127
|
-
const target =
|
|
4128
|
-
if (
|
|
4371
|
+
const target = join9(ctx.projectDir, relTarget);
|
|
4372
|
+
if (existsSync8(target)) {
|
|
4129
4373
|
report.skippedExisting.push(relTarget);
|
|
4130
4374
|
continue;
|
|
4131
4375
|
}
|
|
4132
|
-
copyFile(
|
|
4376
|
+
copyFile(join9(ctx.harnessRoot, "templates/github-workflows", v2.source), target);
|
|
4133
4377
|
report.written.push(relTarget);
|
|
4134
4378
|
}
|
|
4135
4379
|
return report;
|
|
@@ -4137,19 +4381,18 @@ function installCiScaffold(ctx) {
|
|
|
4137
4381
|
|
|
4138
4382
|
// src/cli-transforms.ts
|
|
4139
4383
|
init_esm_shims();
|
|
4140
|
-
import { existsSync as
|
|
4141
|
-
import { join as
|
|
4384
|
+
import { existsSync as existsSync17, readFileSync as readFileSync16 } from "fs";
|
|
4385
|
+
import { join as join22 } from "path";
|
|
4142
4386
|
|
|
4143
4387
|
// src/agents-md-transform.ts
|
|
4144
4388
|
init_esm_shims();
|
|
4145
|
-
import { existsSync as
|
|
4146
|
-
import { basename, join as
|
|
4389
|
+
import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
|
|
4390
|
+
import { basename, join as join14 } from "path";
|
|
4147
4391
|
|
|
4148
4392
|
// src/agents-skill-targets.ts
|
|
4149
4393
|
init_esm_shims();
|
|
4150
|
-
import { join as
|
|
4151
|
-
function bundledSkillTargets(projectDir) {
|
|
4152
|
-
const log = readInstallLog(projectDir);
|
|
4394
|
+
import { join as join10, sep as sep2 } from "path";
|
|
4395
|
+
function bundledSkillTargets(projectDir, log = readInstallLog(projectDir)) {
|
|
4153
4396
|
if (log === null) return [];
|
|
4154
4397
|
const tracks = log.spec.tracks.filter(
|
|
4155
4398
|
(t) => TRACKS.includes(t)
|
|
@@ -4162,7 +4405,7 @@ function bundledSkillTargets(projectDir) {
|
|
|
4162
4405
|
}
|
|
4163
4406
|
function allowSkillCreation(writer, projectDir, ids) {
|
|
4164
4407
|
if (ids.length === 0) return writer;
|
|
4165
|
-
const roots = ids.map((id) => `${
|
|
4408
|
+
const roots = ids.map((id) => `${join10(projectDir, ".agents", "skills", id)}${sep2}`);
|
|
4166
4409
|
return {
|
|
4167
4410
|
write(absPath, content, opts) {
|
|
4168
4411
|
const inTarget = roots.some((root) => absPath.startsWith(root));
|
|
@@ -4183,24 +4426,24 @@ function agentsSkillSlot(params) {
|
|
|
4183
4426
|
|
|
4184
4427
|
// src/owned-write.ts
|
|
4185
4428
|
init_esm_shims();
|
|
4186
|
-
import { existsSync as
|
|
4429
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
4187
4430
|
import { dirname as dirname2, relative as relative2, sep as sep3 } from "path";
|
|
4188
4431
|
|
|
4189
4432
|
// src/foreign-slot.ts
|
|
4190
4433
|
init_esm_shims();
|
|
4191
|
-
import { lstatSync, realpathSync, statSync } from "fs";
|
|
4192
|
-
import { join as
|
|
4434
|
+
import { lstatSync, realpathSync, statSync as statSync2 } from "fs";
|
|
4435
|
+
import { join as join11 } from "path";
|
|
4193
4436
|
var SKILL_SLOT = /^((?:\.claude|\.agents)\/skills\/[^/]+)(?:\/|$)/;
|
|
4194
4437
|
function occupiedByNonDirectory(path) {
|
|
4195
4438
|
const stat = lstatSync(path, { throwIfNoEntry: false });
|
|
4196
4439
|
return stat !== void 0 && !stat.isDirectory();
|
|
4197
4440
|
}
|
|
4198
4441
|
function linksToProjectSharedSkill(projectDir, id) {
|
|
4199
|
-
const slot =
|
|
4442
|
+
const slot = join11(projectDir, ".claude/skills", id);
|
|
4200
4443
|
if (lstatSync(slot, { throwIfNoEntry: false })?.isSymbolicLink() !== true) return false;
|
|
4201
4444
|
try {
|
|
4202
4445
|
const real = realpathSync(slot);
|
|
4203
|
-
return real ===
|
|
4446
|
+
return real === join11(realpathSync(projectDir), ".agents/skills", id) && statSync2(real).isDirectory();
|
|
4204
4447
|
} catch {
|
|
4205
4448
|
return false;
|
|
4206
4449
|
}
|
|
@@ -4208,15 +4451,15 @@ function linksToProjectSharedSkill(projectDir, id) {
|
|
|
4208
4451
|
function foreignOwnedTarget(projectDir, rel) {
|
|
4209
4452
|
const slot = SKILL_SLOT.exec(rel)?.[1];
|
|
4210
4453
|
if (slot === void 0) return null;
|
|
4211
|
-
if (occupiedByNonDirectory(
|
|
4454
|
+
if (occupiedByNonDirectory(join11(projectDir, slot))) return slot;
|
|
4212
4455
|
if (rel !== slot) {
|
|
4213
4456
|
const parts = rel.slice(slot.length + 1).split("/");
|
|
4214
4457
|
let cursor = slot;
|
|
4215
4458
|
for (const part of parts.slice(0, -1)) {
|
|
4216
4459
|
cursor = `${cursor}/${part}`;
|
|
4217
|
-
if (occupiedByNonDirectory(
|
|
4460
|
+
if (occupiedByNonDirectory(join11(projectDir, cursor))) return cursor;
|
|
4218
4461
|
}
|
|
4219
|
-
const leaf = lstatSync(
|
|
4462
|
+
const leaf = lstatSync(join11(projectDir, rel), { throwIfNoEntry: false });
|
|
4220
4463
|
if (leaf !== void 0 && !leaf.isFile()) return rel;
|
|
4221
4464
|
}
|
|
4222
4465
|
return null;
|
|
@@ -4224,13 +4467,18 @@ function foreignOwnedTarget(projectDir, rel) {
|
|
|
4224
4467
|
|
|
4225
4468
|
// src/owned-write.ts
|
|
4226
4469
|
function createOwnedWriter(projectDir, baseline, options = {}) {
|
|
4227
|
-
const { now = /* @__PURE__ */ new Date(), refreshOnly = false, journal } = options;
|
|
4470
|
+
const { now = /* @__PURE__ */ new Date(), refreshOnly = false, journal, owned = () => false } = options;
|
|
4228
4471
|
const written = /* @__PURE__ */ new Map();
|
|
4229
4472
|
const backedUp = [];
|
|
4230
4473
|
const backupPaths = [];
|
|
4231
4474
|
const foreignOwned = [];
|
|
4232
4475
|
const restored = [];
|
|
4476
|
+
const displaced = /* @__PURE__ */ new Map();
|
|
4233
4477
|
const outside = createOutsideGuard(projectDir);
|
|
4478
|
+
const displace = (f) => {
|
|
4479
|
+
displaced.set(f.path, f);
|
|
4480
|
+
journal?.displaced?.(f);
|
|
4481
|
+
};
|
|
4234
4482
|
let updated = 0;
|
|
4235
4483
|
return {
|
|
4236
4484
|
write(absPath, content, opts) {
|
|
@@ -4242,7 +4490,7 @@ function createOwnedWriter(projectDir, baseline, options = {}) {
|
|
|
4242
4490
|
}
|
|
4243
4491
|
if (outside.skip(absPath)) return false;
|
|
4244
4492
|
const digest = hashContent(content);
|
|
4245
|
-
if (!
|
|
4493
|
+
if (!existsSync9(absPath)) {
|
|
4246
4494
|
const recordedPath = baseline.has(rel);
|
|
4247
4495
|
if (refreshOnly && !opts?.createInRefresh && !recordedPath) return false;
|
|
4248
4496
|
mkdirSync2(dirname2(absPath), { recursive: true });
|
|
@@ -4251,12 +4499,20 @@ function createOwnedWriter(projectDir, baseline, options = {}) {
|
|
|
4251
4499
|
if (refreshOnly && recordedPath) restored.push(rel);
|
|
4252
4500
|
} else {
|
|
4253
4501
|
const current = readFileSync6(absPath, "utf8");
|
|
4502
|
+
const firstContact = !baseline.has(rel) && !owned(rel);
|
|
4503
|
+
if (current === content && firstContact) {
|
|
4504
|
+
displace({ path: rel, change: "displaced", notes: [] });
|
|
4505
|
+
return true;
|
|
4506
|
+
}
|
|
4254
4507
|
if (current !== content) {
|
|
4255
4508
|
if (!isHarnessOwned(baseline, rel, current)) {
|
|
4256
4509
|
const backup = backupFile(absPath, now);
|
|
4257
4510
|
backupPaths.push(backup);
|
|
4258
|
-
backedUp.push(rel);
|
|
4259
4511
|
journal?.backup(backup);
|
|
4512
|
+
if (firstContact) {
|
|
4513
|
+
const backupRel = relative2(projectDir, backup).split(sep3).join("/");
|
|
4514
|
+
displace({ path: rel, change: "displaced", notes: [backupRel] });
|
|
4515
|
+
} else backedUp.push(rel);
|
|
4260
4516
|
}
|
|
4261
4517
|
writeFileSync4(absPath, content);
|
|
4262
4518
|
updated++;
|
|
@@ -4276,7 +4532,8 @@ function createOwnedWriter(projectDir, baseline, options = {}) {
|
|
|
4276
4532
|
backedUp: [...backedUp],
|
|
4277
4533
|
backupPaths: [...backupPaths],
|
|
4278
4534
|
updated,
|
|
4279
|
-
restored: [...restored]
|
|
4535
|
+
restored: [...restored],
|
|
4536
|
+
displaced: [...displaced.values()]
|
|
4280
4537
|
};
|
|
4281
4538
|
}
|
|
4282
4539
|
};
|
|
@@ -4284,8 +4541,8 @@ function createOwnedWriter(projectDir, baseline, options = {}) {
|
|
|
4284
4541
|
|
|
4285
4542
|
// src/rules-port.ts
|
|
4286
4543
|
init_esm_shims();
|
|
4287
|
-
import { existsSync as
|
|
4288
|
-
import { join as
|
|
4544
|
+
import { existsSync as existsSync10, readdirSync, readFileSync as readFileSync7 } from "fs";
|
|
4545
|
+
import { join as join12 } from "path";
|
|
4289
4546
|
function claudePaths(source) {
|
|
4290
4547
|
if (!source.startsWith("---\n")) return [];
|
|
4291
4548
|
const end = source.indexOf("\n---", 4);
|
|
@@ -4313,16 +4570,16 @@ function stripClaudeFrontmatter(source) {
|
|
|
4313
4570
|
function portRules(harnessRoot, ruleNames) {
|
|
4314
4571
|
const ported = [];
|
|
4315
4572
|
for (const name of ruleNames) {
|
|
4316
|
-
const src =
|
|
4317
|
-
if (!
|
|
4573
|
+
const src = join12(harnessRoot, "templates/rules", `${name}.md`);
|
|
4574
|
+
if (!existsSync10(src)) continue;
|
|
4318
4575
|
const source = readFileSync7(src, "utf8");
|
|
4319
4576
|
ported.push({ name, body: stripClaudeFrontmatter(source), paths: claudePaths(source) });
|
|
4320
4577
|
}
|
|
4321
4578
|
return ported;
|
|
4322
4579
|
}
|
|
4323
4580
|
function ruleHeadings(harnessRoot) {
|
|
4324
|
-
const dir =
|
|
4325
|
-
if (!
|
|
4581
|
+
const dir = join12(harnessRoot, "templates/rules");
|
|
4582
|
+
if (!existsSync10(dir)) return /* @__PURE__ */ new Set();
|
|
4326
4583
|
const names = readdirSync(dir).filter((f) => f.endsWith(".md")).map((f) => f.slice(0, -3));
|
|
4327
4584
|
const block = renderRulesBlock(portRules(harnessRoot, names));
|
|
4328
4585
|
return new Set(
|
|
@@ -4336,8 +4593,8 @@ function renderRulesBlock(rules) {
|
|
|
4336
4593
|
|
|
4337
4594
|
// src/shared-write.ts
|
|
4338
4595
|
init_esm_shims();
|
|
4339
|
-
import { existsSync as
|
|
4340
|
-
import { dirname as dirname3, join as
|
|
4596
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync3, readFileSync as readFileSync8, rmSync, statSync as statSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
4597
|
+
import { dirname as dirname3, join as join13 } from "path";
|
|
4341
4598
|
function toPortions(path, portions) {
|
|
4342
4599
|
const adapter = adapterFor(path);
|
|
4343
4600
|
if (adapter === null) throw new Error(`shared-write: ${path} is not a shared file`);
|
|
@@ -4352,8 +4609,8 @@ function writeShared(params) {
|
|
|
4352
4609
|
const { projectDir, path, render, record, baseline, writer, refreshOnly } = params;
|
|
4353
4610
|
const adapter = adapterFor(path);
|
|
4354
4611
|
if (adapter === null) throw new Error(`shared-write: ${path} is not a shared file`);
|
|
4355
|
-
const abs =
|
|
4356
|
-
const onDisk =
|
|
4612
|
+
const abs = join13(projectDir, path);
|
|
4613
|
+
const onDisk = existsSync11(abs) ? readFileSync8(abs, "utf8") : null;
|
|
4357
4614
|
const recorded2 = recordedFor(path, record);
|
|
4358
4615
|
const excluded = excludedKeys(path, record.excluded ?? []);
|
|
4359
4616
|
const result = (action2, rest = {}) => ({
|
|
@@ -4472,8 +4729,8 @@ function agentsMdModel(disk, recordedByHarness, recordedBlock = false) {
|
|
|
4472
4729
|
return "block";
|
|
4473
4730
|
}
|
|
4474
4731
|
function writeAgentsMd(params) {
|
|
4475
|
-
const abs =
|
|
4476
|
-
const disk =
|
|
4732
|
+
const abs = join13(params.projectDir, AGENTS_MD);
|
|
4733
|
+
const disk = existsSync11(abs) ? readFileSync8(abs, "utf8") : null;
|
|
4477
4734
|
const recordedBlock = (params.record.portions ?? []).some((p2) => p2.path === AGENTS_MD);
|
|
4478
4735
|
if (agentsMdModel(disk, params.baseline.has(AGENTS_MD), recordedBlock) === "sections") {
|
|
4479
4736
|
params.writer.write(
|
|
@@ -4526,8 +4783,8 @@ function stripShared(params) {
|
|
|
4526
4783
|
portions: [],
|
|
4527
4784
|
...rest
|
|
4528
4785
|
});
|
|
4529
|
-
const abs =
|
|
4530
|
-
if (!
|
|
4786
|
+
const abs = join13(projectDir, path);
|
|
4787
|
+
if (!existsSync11(abs) || !statSync3(abs).isFile()) return out("none");
|
|
4531
4788
|
const disk = readFileSync8(abs, "utf8");
|
|
4532
4789
|
const recorded2 = new Map(
|
|
4533
4790
|
params.portions.filter((p2) => p2.path === path).map((p2) => [p2.key, p2.sha256])
|
|
@@ -4579,18 +4836,19 @@ function runAgentsMdTransform(params) {
|
|
|
4579
4836
|
const clis = agentsMdClis(params.clis);
|
|
4580
4837
|
const writer = createOwnedWriter(projectDir, baseline, {
|
|
4581
4838
|
refreshOnly,
|
|
4582
|
-
...params.journal === void 0 ? {} : { journal: params.journal }
|
|
4839
|
+
...params.journal === void 0 ? {} : { journal: params.journal },
|
|
4840
|
+
...params.owned === void 0 ? {} : { owned: params.owned }
|
|
4583
4841
|
});
|
|
4584
|
-
const claudeMd = readRequired(
|
|
4585
|
-
const template = readRequired(
|
|
4842
|
+
const claudeMd = readRequired(join14(harnessRoot, "templates/CLAUDE.md"));
|
|
4843
|
+
const template = readRequired(join14(harnessRoot, AGENTS_MD_TEMPLATE));
|
|
4586
4844
|
const { skillIds } = agentsSkillSlot({
|
|
4587
4845
|
projectDir,
|
|
4588
4846
|
selectedInternalSkills: params.selectedInternalSkills,
|
|
4589
4847
|
refreshOnly,
|
|
4590
4848
|
writer
|
|
4591
4849
|
});
|
|
4592
|
-
const agentsMdPath =
|
|
4593
|
-
const exists =
|
|
4850
|
+
const agentsMdPath = join14(projectDir, "AGENTS.md");
|
|
4851
|
+
const exists = existsSync12(agentsMdPath);
|
|
4594
4852
|
const creates = !exists && (!refreshOnly || params.installedByRecord === true);
|
|
4595
4853
|
const legacy = params.legacyContext ?? null;
|
|
4596
4854
|
const fromRoot = creates ? seedAgentsMdProjectContext(projectDir) : null;
|
|
@@ -4632,13 +4890,13 @@ function runAgentsMdTransform(params) {
|
|
|
4632
4890
|
};
|
|
4633
4891
|
}
|
|
4634
4892
|
function seededInto(path, before, seed, from) {
|
|
4635
|
-
if (seed === null || !
|
|
4893
|
+
if (seed === null || !existsSync12(path)) return null;
|
|
4636
4894
|
const after = readFileSync9(path, "utf8");
|
|
4637
4895
|
const body = renameSlashes(seed.slice(seed.indexOf("\n\n") + 2));
|
|
4638
4896
|
return after !== before && after.includes(body) ? from : null;
|
|
4639
4897
|
}
|
|
4640
4898
|
function readRequired(path) {
|
|
4641
|
-
if (!
|
|
4899
|
+
if (!existsSync12(path)) {
|
|
4642
4900
|
throw new Error(`AGENTS.md transform: required source missing: ${path}`);
|
|
4643
4901
|
}
|
|
4644
4902
|
return readFileSync9(path, "utf8");
|
|
@@ -4647,7 +4905,7 @@ function readRequired(path) {
|
|
|
4647
4905
|
// src/antigravity/retire-anchor.ts
|
|
4648
4906
|
init_esm_shims();
|
|
4649
4907
|
import { lstatSync as lstatSync2, readFileSync as readFileSync10, rmSync as rmSync2 } from "fs";
|
|
4650
|
-
import { join as
|
|
4908
|
+
import { join as join15 } from "path";
|
|
4651
4909
|
var LEGACY_ANTIGRAVITY_ANCHOR_SECTIONS = ["Project Context", "Project Rules", "Protected Files"];
|
|
4652
4910
|
function anchorRecord(previous) {
|
|
4653
4911
|
return previous.find((f) => f.path === LEGACY_ANTIGRAVITY_ANCHOR) ?? null;
|
|
@@ -4675,7 +4933,7 @@ function legacyContextLines(text) {
|
|
|
4675
4933
|
}
|
|
4676
4934
|
function readLegacyAntigravityContext(projectDir, previous) {
|
|
4677
4935
|
if (anchorRecord(previous) === null) return null;
|
|
4678
|
-
const text = readRegular(
|
|
4936
|
+
const text = readRegular(join15(projectDir, LEGACY_ANTIGRAVITY_ANCHOR));
|
|
4679
4937
|
if (text === null) return null;
|
|
4680
4938
|
const lines2 = legacyContextLines(text);
|
|
4681
4939
|
if (lines2 === null) return null;
|
|
@@ -4699,7 +4957,7 @@ function retireAntigravityAnchor(args) {
|
|
|
4699
4957
|
const rec = anchorRecord(args.previous);
|
|
4700
4958
|
if (rec === null) return null;
|
|
4701
4959
|
const path = LEGACY_ANTIGRAVITY_ANCHOR;
|
|
4702
|
-
const abs =
|
|
4960
|
+
const abs = join15(args.projectDir, path);
|
|
4703
4961
|
const left = (why) => ({
|
|
4704
4962
|
path,
|
|
4705
4963
|
action: "left",
|
|
@@ -4737,12 +4995,12 @@ function relativeBackup(projectDir, backup) {
|
|
|
4737
4995
|
|
|
4738
4996
|
// src/antigravity/transform.ts
|
|
4739
4997
|
init_esm_shims();
|
|
4740
|
-
import { join as
|
|
4998
|
+
import { join as join17 } from "path";
|
|
4741
4999
|
|
|
4742
5000
|
// src/codex/skills.ts
|
|
4743
5001
|
init_esm_shims();
|
|
4744
|
-
import { chmodSync, existsSync as
|
|
4745
|
-
import { join as
|
|
5002
|
+
import { chmodSync, existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
|
|
5003
|
+
import { join as join16 } from "path";
|
|
4746
5004
|
function renderBundledSkill(source) {
|
|
4747
5005
|
const trimmed = source.trimEnd();
|
|
4748
5006
|
const lines2 = trimmed.split(/\r?\n/);
|
|
@@ -4774,13 +5032,13 @@ function writeBundledSkillDirs(params) {
|
|
|
4774
5032
|
const { harnessRoot, projectDir, skillIds, writer } = params;
|
|
4775
5033
|
const written = [];
|
|
4776
5034
|
for (const id of skillIds) {
|
|
4777
|
-
const srcDir =
|
|
4778
|
-
const srcSkill =
|
|
4779
|
-
if (!
|
|
5035
|
+
const srcDir = join16(harnessRoot, "templates/skills", id);
|
|
5036
|
+
const srcSkill = join16(srcDir, "SKILL.md");
|
|
5037
|
+
if (!existsSync13(srcSkill)) {
|
|
4780
5038
|
continue;
|
|
4781
5039
|
}
|
|
4782
|
-
const targetDir =
|
|
4783
|
-
const targetSkill =
|
|
5040
|
+
const targetDir = join16(projectDir, ".agents", "skills", id);
|
|
5041
|
+
const targetSkill = join16(targetDir, "SKILL.md");
|
|
4784
5042
|
if (!writer.write(targetSkill, renderBundledSkill(readFileSync11(srcSkill, "utf8")))) {
|
|
4785
5043
|
continue;
|
|
4786
5044
|
}
|
|
@@ -4789,10 +5047,10 @@ function writeBundledSkillDirs(params) {
|
|
|
4789
5047
|
if (rel === "SKILL.md") continue;
|
|
4790
5048
|
if (rel.split("/").some((segment2) => segment2.startsWith("."))) continue;
|
|
4791
5049
|
const segments = rel.split("/");
|
|
4792
|
-
const raw = readFileSync11(
|
|
5050
|
+
const raw = readFileSync11(join16(srcDir, ...segments), "utf8");
|
|
4793
5051
|
const ported = rel.endsWith(".md") || rel.endsWith(".sh") ? `${portBody(raw)}
|
|
4794
5052
|
` : raw;
|
|
4795
|
-
const target =
|
|
5053
|
+
const target = join16(targetDir, ...segments);
|
|
4796
5054
|
if (!writer.write(target, ported, { createInRefresh: true })) continue;
|
|
4797
5055
|
if (rel.endsWith(".sh")) chmodSync(target, 493);
|
|
4798
5056
|
written.push(target);
|
|
@@ -4822,7 +5080,8 @@ function runAntigravityTransform(params) {
|
|
|
4822
5080
|
} = params;
|
|
4823
5081
|
const writer = createOwnedWriter(projectDir, baseline, {
|
|
4824
5082
|
refreshOnly: refreshOnly ?? false,
|
|
4825
|
-
...params.journal === void 0 ? {} : { journal: params.journal }
|
|
5083
|
+
...params.journal === void 0 ? {} : { journal: params.journal },
|
|
5084
|
+
...params.owned === void 0 ? {} : { owned: params.owned }
|
|
4826
5085
|
});
|
|
4827
5086
|
const { skillIds, skillWriter } = agentsSkillSlot({
|
|
4828
5087
|
projectDir,
|
|
@@ -4833,7 +5092,7 @@ function runAntigravityTransform(params) {
|
|
|
4833
5092
|
const antigravityInstalled = params.installedByRecord === true;
|
|
4834
5093
|
const harnessRuleFiles = [];
|
|
4835
5094
|
for (const rule of portRules(harnessRoot, rules)) {
|
|
4836
|
-
const target =
|
|
5095
|
+
const target = join17(projectDir, ".agents", "rules", `${rule.name}.md`);
|
|
4837
5096
|
const content = `${antigravityFrontmatter(rule)}${rule.body}
|
|
4838
5097
|
`;
|
|
4839
5098
|
if (!writer.write(target, content, { createInRefresh: antigravityInstalled })) {
|
|
@@ -4857,11 +5116,11 @@ function runAntigravityTransform(params) {
|
|
|
4857
5116
|
// src/codex/opt-in.ts
|
|
4858
5117
|
init_esm_shims();
|
|
4859
5118
|
import { homedir } from "os";
|
|
4860
|
-
import { join as
|
|
5119
|
+
import { join as join18 } from "path";
|
|
4861
5120
|
|
|
4862
5121
|
// src/codex/trust-entry.ts
|
|
4863
5122
|
init_esm_shims();
|
|
4864
|
-
import { existsSync as
|
|
5123
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync4, readFileSync as readFileSync12, realpathSync as realpathSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
4865
5124
|
import { dirname as dirname4 } from "path";
|
|
4866
5125
|
var UNSAFE_APPEND = "adding the entry would make this file unreadable TOML \u2014 left untouched";
|
|
4867
5126
|
function trustKeysFor(projectDir) {
|
|
@@ -4904,7 +5163,7 @@ function registerTrustEntry(opts) {
|
|
|
4904
5163
|
const keys = trustKeysFor(projectDir);
|
|
4905
5164
|
const key = keys[0] ?? projectDir;
|
|
4906
5165
|
try {
|
|
4907
|
-
const existing =
|
|
5166
|
+
const existing = existsSync14(configPath) ? readFileSync12(configPath, "utf8") : "";
|
|
4908
5167
|
let parsed;
|
|
4909
5168
|
try {
|
|
4910
5169
|
parsed = parse(existing);
|
|
@@ -4955,8 +5214,8 @@ function firstLine(e2) {
|
|
|
4955
5214
|
|
|
4956
5215
|
// src/codex/opt-in.ts
|
|
4957
5216
|
function runCodexOptIn(ctx) {
|
|
4958
|
-
const codexHome = ctx.codexHome ??
|
|
4959
|
-
const configPath =
|
|
5217
|
+
const codexHome = ctx.codexHome ?? join18(homedir(), ".codex");
|
|
5218
|
+
const configPath = join18(codexHome, "config.toml");
|
|
4960
5219
|
const result = registerTrustEntry({ configPath, projectDir: ctx.projectDir });
|
|
4961
5220
|
return {
|
|
4962
5221
|
trustEntry: {
|
|
@@ -4971,13 +5230,13 @@ function runCodexOptIn(ctx) {
|
|
|
4971
5230
|
|
|
4972
5231
|
// src/codex/transform.ts
|
|
4973
5232
|
init_esm_shims();
|
|
4974
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
4975
|
-
import { basename as basename2, join as
|
|
5233
|
+
import { chmodSync as chmodSync2, existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
|
|
5234
|
+
import { basename as basename2, join as join20 } from "path";
|
|
4976
5235
|
|
|
4977
5236
|
// src/codex/config-toml.ts
|
|
4978
5237
|
init_esm_shims();
|
|
4979
5238
|
import { readFileSync as readFileSync13 } from "fs";
|
|
4980
|
-
import { join as
|
|
5239
|
+
import { join as join19 } from "path";
|
|
4981
5240
|
var TABLES_REGION = "tables";
|
|
4982
5241
|
function configRegions(rendered) {
|
|
4983
5242
|
const lines2 = rendered.split(/\r?\n/);
|
|
@@ -5022,7 +5281,7 @@ function retiredFeatureKeys(disk) {
|
|
|
5022
5281
|
function retiredFeaturesLeftBehind(projectDir, r) {
|
|
5023
5282
|
if (r.action !== "left" && !r.leftAsIs.includes(TABLES_REGION)) return [];
|
|
5024
5283
|
try {
|
|
5025
|
-
return retiredFeatureKeys(readFileSync13(
|
|
5284
|
+
return retiredFeatureKeys(readFileSync13(join19(projectDir, ".codex/config.toml"), "utf8"));
|
|
5026
5285
|
} catch {
|
|
5027
5286
|
return [];
|
|
5028
5287
|
}
|
|
@@ -5090,9 +5349,10 @@ function runCodexTransform(params) {
|
|
|
5090
5349
|
} = params;
|
|
5091
5350
|
const writer = createOwnedWriter(projectDir, baseline, {
|
|
5092
5351
|
refreshOnly: refreshOnly ?? false,
|
|
5093
|
-
...params.journal === void 0 ? {} : { journal: params.journal }
|
|
5352
|
+
...params.journal === void 0 ? {} : { journal: params.journal },
|
|
5353
|
+
...params.owned === void 0 ? {} : { owned: params.owned }
|
|
5094
5354
|
});
|
|
5095
|
-
const configTemplate = readRequired2(
|
|
5355
|
+
const configTemplate = readRequired2(join20(harnessRoot, "templates/codex/config.toml.template"));
|
|
5096
5356
|
const projectName = basename2(projectDir);
|
|
5097
5357
|
const { skillIds, skillWriter } = agentsSkillSlot({
|
|
5098
5358
|
projectDir,
|
|
@@ -5100,7 +5360,7 @@ function runCodexTransform(params) {
|
|
|
5100
5360
|
refreshOnly: refreshOnly ?? false,
|
|
5101
5361
|
writer
|
|
5102
5362
|
});
|
|
5103
|
-
const configTomlPath =
|
|
5363
|
+
const configTomlPath = join20(projectDir, CONFIG_TOML);
|
|
5104
5364
|
const configToml = writeConfigToml({
|
|
5105
5365
|
projectDir,
|
|
5106
5366
|
regions: configRegions(
|
|
@@ -5111,15 +5371,15 @@ function runCodexTransform(params) {
|
|
|
5111
5371
|
record: shared2,
|
|
5112
5372
|
refreshOnly: refreshOnly ?? false
|
|
5113
5373
|
});
|
|
5114
|
-
const hookDir =
|
|
5374
|
+
const hookDir = join20(projectDir, ".codex/hooks");
|
|
5115
5375
|
const hookFiles = [];
|
|
5116
5376
|
for (const hook of HOOK_NAMES) {
|
|
5117
|
-
const src =
|
|
5118
|
-
if (!
|
|
5377
|
+
const src = join20(harnessRoot, "templates/hooks", `${hook}.sh`);
|
|
5378
|
+
if (!existsSync15(src)) {
|
|
5119
5379
|
continue;
|
|
5120
5380
|
}
|
|
5121
5381
|
const ported = readFileSync14(src, "utf8").replace(ENV_VAR_RENAME, "CODEX_PROJECT_DIR");
|
|
5122
|
-
const target =
|
|
5382
|
+
const target = join20(hookDir, `${hook}.sh`);
|
|
5123
5383
|
if (!writer.write(target, ported)) continue;
|
|
5124
5384
|
chmodSync2(target, 493);
|
|
5125
5385
|
hookFiles.push(target);
|
|
@@ -5141,8 +5401,8 @@ function runCodexTransform(params) {
|
|
|
5141
5401
|
var CONFIG_TOML = ".codex/config.toml";
|
|
5142
5402
|
var LEGACY_CHANGED = "written by an earlier harness version and changed since \u2014 left as is; harness part not added";
|
|
5143
5403
|
function writeConfigToml(args) {
|
|
5144
|
-
const abs =
|
|
5145
|
-
const disk =
|
|
5404
|
+
const abs = join20(args.projectDir, CONFIG_TOML);
|
|
5405
|
+
const disk = existsSync15(abs) ? readFileSync14(abs, "utf8") : null;
|
|
5146
5406
|
const regions = disk === null ? null : ADAPTERS["toml-region"].read(disk);
|
|
5147
5407
|
if (disk !== null && regions?.size === 0 && isChangedLegacyConfig(disk, args.baseline.get(CONFIG_TOML))) {
|
|
5148
5408
|
return {
|
|
@@ -5172,7 +5432,7 @@ function writeConfigToml(args) {
|
|
|
5172
5432
|
});
|
|
5173
5433
|
}
|
|
5174
5434
|
function readRequired2(path) {
|
|
5175
|
-
if (!
|
|
5435
|
+
if (!existsSync15(path)) {
|
|
5176
5436
|
throw new Error(`Codex transform: required source missing: ${path}`);
|
|
5177
5437
|
}
|
|
5178
5438
|
return readFileSync14(path, "utf8");
|
|
@@ -5180,8 +5440,8 @@ function readRequired2(path) {
|
|
|
5180
5440
|
|
|
5181
5441
|
// src/opencode/transform.ts
|
|
5182
5442
|
init_esm_shims();
|
|
5183
|
-
import { existsSync as
|
|
5184
|
-
import { join as
|
|
5443
|
+
import { existsSync as existsSync16, readdirSync as readdirSync2, readFileSync as readFileSync15, unlinkSync } from "fs";
|
|
5444
|
+
import { join as join21 } from "path";
|
|
5185
5445
|
|
|
5186
5446
|
// src/opencode/opencode-json.ts
|
|
5187
5447
|
init_esm_shims();
|
|
@@ -5235,10 +5495,11 @@ function runOpencodeTransform(params) {
|
|
|
5235
5495
|
} = params;
|
|
5236
5496
|
const writer = createOwnedWriter(projectDir, baseline, {
|
|
5237
5497
|
refreshOnly: refreshOnly ?? false,
|
|
5238
|
-
...params.journal === void 0 ? {} : { journal: params.journal }
|
|
5498
|
+
...params.journal === void 0 ? {} : { journal: params.journal },
|
|
5499
|
+
...params.owned === void 0 ? {} : { owned: params.owned }
|
|
5239
5500
|
});
|
|
5240
5501
|
const opencodeTemplate = readRequired3(
|
|
5241
|
-
|
|
5502
|
+
join21(harnessRoot, "templates/opencode/opencode.json.template")
|
|
5242
5503
|
);
|
|
5243
5504
|
const { skillIds, skillWriter } = agentsSkillSlot({
|
|
5244
5505
|
projectDir,
|
|
@@ -5247,7 +5508,7 @@ function runOpencodeTransform(params) {
|
|
|
5247
5508
|
writer
|
|
5248
5509
|
});
|
|
5249
5510
|
ensureDir(projectDir);
|
|
5250
|
-
const opencodeJsonPath =
|
|
5511
|
+
const opencodeJsonPath = join21(projectDir, OPENCODE_JSON);
|
|
5251
5512
|
const render = renderOpencodeMcp(mcp);
|
|
5252
5513
|
const opencodeJson = writeShared({
|
|
5253
5514
|
projectDir,
|
|
@@ -5266,13 +5527,13 @@ function runOpencodeTransform(params) {
|
|
|
5266
5527
|
writer: skillWriter
|
|
5267
5528
|
});
|
|
5268
5529
|
const retiredCommands = [];
|
|
5269
|
-
const cmdDir =
|
|
5270
|
-
if (
|
|
5530
|
+
const cmdDir = join21(projectDir, ".opencode/commands");
|
|
5531
|
+
if (existsSync16(cmdDir)) {
|
|
5271
5532
|
for (const entry of readdirSync2(cmdDir)) {
|
|
5272
5533
|
if (!entry.endsWith(".md")) continue;
|
|
5273
5534
|
const id = entry.slice(0, -3);
|
|
5274
|
-
if (!
|
|
5275
|
-
const victim =
|
|
5535
|
+
if (!existsSync16(join21(harnessRoot, "templates/skills", id, "SKILL.md"))) continue;
|
|
5536
|
+
const victim = join21(cmdDir, entry);
|
|
5276
5537
|
if (writer.skipOutside(victim)) continue;
|
|
5277
5538
|
try {
|
|
5278
5539
|
backupFile(victim);
|
|
@@ -5292,7 +5553,7 @@ function runOpencodeTransform(params) {
|
|
|
5292
5553
|
}
|
|
5293
5554
|
var OPENCODE_JSON = "opencode.json";
|
|
5294
5555
|
function readRequired3(path) {
|
|
5295
|
-
if (!
|
|
5556
|
+
if (!existsSync16(path)) {
|
|
5296
5557
|
throw new Error(`OpenCode transform: required source missing: ${path}`);
|
|
5297
5558
|
}
|
|
5298
5559
|
return readFileSync15(path, "utf8");
|
|
@@ -5317,7 +5578,7 @@ function trackDefaultMcpAssetIds(harnessRoot, tracks) {
|
|
|
5317
5578
|
}
|
|
5318
5579
|
function trackDefaultMcpNames(harnessRoot, tracks) {
|
|
5319
5580
|
const { templateMcpPath } = mcpSources(harnessRoot);
|
|
5320
|
-
const base =
|
|
5581
|
+
const base = existsSync17(templateMcpPath) ? Object.keys(JSON.parse(readFileSync16(templateMcpPath, "utf8")).mcpServers) : [];
|
|
5321
5582
|
return /* @__PURE__ */ new Set([
|
|
5322
5583
|
...base,
|
|
5323
5584
|
...trackRows(harnessRoot).filter((r) => anyTrack(tracks, r.pattern)).map((r) => r.name)
|
|
@@ -5325,7 +5586,7 @@ function trackDefaultMcpNames(harnessRoot, tracks) {
|
|
|
5325
5586
|
}
|
|
5326
5587
|
function trackRows(harnessRoot) {
|
|
5327
5588
|
const { trackMapPath } = mcpSources(harnessRoot);
|
|
5328
|
-
return
|
|
5589
|
+
return existsSync17(trackMapPath) ? parseTrackMcpMap(readFileSync16(trackMapPath, "utf8")) : [];
|
|
5329
5590
|
}
|
|
5330
5591
|
function renderEverySelectableMcp(harnessRoot, tracks) {
|
|
5331
5592
|
return composeMcpJson({
|
|
@@ -5345,8 +5606,8 @@ function adapterExcluded(harnessRoot, excluded) {
|
|
|
5345
5606
|
}
|
|
5346
5607
|
function mcpSources(harnessRoot) {
|
|
5347
5608
|
return {
|
|
5348
|
-
templateMcpPath:
|
|
5349
|
-
trackMapPath:
|
|
5609
|
+
templateMcpPath: join22(harnessRoot, "templates/mcp.json"),
|
|
5610
|
+
trackMapPath: join22(harnessRoot, "templates/track-mcp-map.tsv")
|
|
5350
5611
|
};
|
|
5351
5612
|
}
|
|
5352
5613
|
var ALL_CLI_TARGETS = CLI_BASES;
|
|
@@ -5363,9 +5624,13 @@ function runCliTransforms(params) {
|
|
|
5363
5624
|
codexTrust = false,
|
|
5364
5625
|
shared: shared2 = {},
|
|
5365
5626
|
recordedClis: recordedClis2 = [],
|
|
5366
|
-
journal
|
|
5627
|
+
journal,
|
|
5628
|
+
owned
|
|
5367
5629
|
} = params;
|
|
5368
|
-
const
|
|
5630
|
+
const writerParams = {
|
|
5631
|
+
...journal === void 0 ? {} : { journal },
|
|
5632
|
+
...owned === void 0 ? {} : { owned }
|
|
5633
|
+
};
|
|
5369
5634
|
const legacyContext = readLegacyAntigravityContext(projectDir, previousExternal);
|
|
5370
5635
|
const outside = createOutsideGuard(projectDir);
|
|
5371
5636
|
const baseline = new Map(previousExternal.map((f) => [f.path, f.sha256]));
|
|
@@ -5373,6 +5638,7 @@ function runCliTransforms(params) {
|
|
|
5373
5638
|
const externalBackups = [];
|
|
5374
5639
|
const externalBackedUp = [];
|
|
5375
5640
|
const externalForeignOwned = [];
|
|
5641
|
+
const displaced = /* @__PURE__ */ new Map();
|
|
5376
5642
|
let externalOutside = [];
|
|
5377
5643
|
let externalUpdated = 0;
|
|
5378
5644
|
const restoredFiles = [];
|
|
@@ -5392,6 +5658,8 @@ function runCliTransforms(params) {
|
|
|
5392
5658
|
}
|
|
5393
5659
|
externalBackups.push(...report.ownership.backupPaths);
|
|
5394
5660
|
externalBackedUp.push(...report.ownership.backedUp);
|
|
5661
|
+
for (const f of report.ownership.displaced ?? [])
|
|
5662
|
+
if (!displaced.has(f.path)) displaced.set(f.path, f);
|
|
5395
5663
|
for (const f of report.ownership.foreignOwned) {
|
|
5396
5664
|
if (!externalForeignOwned.includes(f)) externalForeignOwned.push(f);
|
|
5397
5665
|
}
|
|
@@ -5411,7 +5679,7 @@ function runCliTransforms(params) {
|
|
|
5411
5679
|
baseline,
|
|
5412
5680
|
refreshOnly,
|
|
5413
5681
|
shared: shared2,
|
|
5414
|
-
...
|
|
5682
|
+
...writerParams
|
|
5415
5683
|
});
|
|
5416
5684
|
absorb(codex);
|
|
5417
5685
|
absorbShared(codex, [codex.configToml]);
|
|
@@ -5431,7 +5699,7 @@ function runCliTransforms(params) {
|
|
|
5431
5699
|
baseline,
|
|
5432
5700
|
refreshOnly,
|
|
5433
5701
|
shared: shared2,
|
|
5434
|
-
...
|
|
5702
|
+
...writerParams
|
|
5435
5703
|
});
|
|
5436
5704
|
absorb(opencode);
|
|
5437
5705
|
absorbShared(opencode, [opencode.opencodeJson]);
|
|
@@ -5447,7 +5715,7 @@ function runCliTransforms(params) {
|
|
|
5447
5715
|
baseline,
|
|
5448
5716
|
refreshOnly,
|
|
5449
5717
|
installedByRecord: recordedClis2.includes("antigravity"),
|
|
5450
|
-
...
|
|
5718
|
+
...writerParams
|
|
5451
5719
|
});
|
|
5452
5720
|
absorb(antigravity);
|
|
5453
5721
|
journal?.done("antigravity");
|
|
@@ -5469,7 +5737,7 @@ function runCliTransforms(params) {
|
|
|
5469
5737
|
installedByRecord: agentsSet.some((c3) => recordedClis2.includes(c3)),
|
|
5470
5738
|
shared: shared2,
|
|
5471
5739
|
legacyContext,
|
|
5472
|
-
...
|
|
5740
|
+
...writerParams
|
|
5473
5741
|
});
|
|
5474
5742
|
absorb(agentsMd);
|
|
5475
5743
|
absorbShared(agentsMd, [agentsMd.agentsMd.shared]);
|
|
@@ -5497,6 +5765,7 @@ function runCliTransforms(params) {
|
|
|
5497
5765
|
externalBackups,
|
|
5498
5766
|
externalUpdated,
|
|
5499
5767
|
externalBackedUp,
|
|
5768
|
+
displaced: [...displaced.values()],
|
|
5500
5769
|
externalForeignOwned,
|
|
5501
5770
|
externalOutside,
|
|
5502
5771
|
sharedFiles,
|
|
@@ -5508,8 +5777,8 @@ function runCliTransforms(params) {
|
|
|
5508
5777
|
|
|
5509
5778
|
// src/excluded-still-there.ts
|
|
5510
5779
|
init_esm_shims();
|
|
5511
|
-
import { existsSync as
|
|
5512
|
-
import { join as
|
|
5780
|
+
import { existsSync as existsSync18 } from "fs";
|
|
5781
|
+
import { join as join23 } from "path";
|
|
5513
5782
|
function excludedStillThere(projectDir, excluded, baselineOnDisk, previousLog) {
|
|
5514
5783
|
const out = [];
|
|
5515
5784
|
const add = (id, catalog) => {
|
|
@@ -5522,7 +5791,7 @@ function excludedStillThere(projectDir, excluded, baselineOnDisk, previousLog) {
|
|
|
5522
5791
|
for (const id of excluded) {
|
|
5523
5792
|
if (INTERNAL_BUNDLED_SKILL_IDS.includes(id)) {
|
|
5524
5793
|
const there = [".claude/skills", ".agents/skills"].some(
|
|
5525
|
-
(d2) =>
|
|
5794
|
+
(d2) => existsSync18(join23(projectDir, d2, id))
|
|
5526
5795
|
);
|
|
5527
5796
|
if (there) add(id, false);
|
|
5528
5797
|
} else {
|
|
@@ -5536,17 +5805,17 @@ function excludedStillThere(projectDir, excluded, baselineOnDisk, previousLog) {
|
|
|
5536
5805
|
function assetStillThere(projectDir, asset) {
|
|
5537
5806
|
if (asset.method !== "skill" || asset.scope === "global") return true;
|
|
5538
5807
|
if (asset.files !== void 0)
|
|
5539
|
-
return asset.files.some((f) =>
|
|
5808
|
+
return asset.files.some((f) => existsSync18(join23(projectDir, f.path)));
|
|
5540
5809
|
const dir = asset.detail.skill ?? asset.id;
|
|
5541
|
-
return [".claude/skills", ".agents/skills"].some((d2) =>
|
|
5810
|
+
return [".claude/skills", ".agents/skills"].some((d2) => existsSync18(join23(projectDir, d2, dir)));
|
|
5542
5811
|
}
|
|
5543
5812
|
|
|
5544
5813
|
// src/external-installer.ts
|
|
5545
5814
|
init_esm_shims();
|
|
5546
5815
|
import { spawnSync } from "child_process";
|
|
5547
|
-
import { existsSync as
|
|
5816
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync5, readFileSync as readFileSync17, writeFileSync as writeFileSync7 } from "fs";
|
|
5548
5817
|
import { homedir as homedir2 } from "os";
|
|
5549
|
-
import { join as
|
|
5818
|
+
import { dirname as dirname5, join as join24 } from "path";
|
|
5550
5819
|
var SKILL_ROOTS = [".claude/skills", ".agents/skills"];
|
|
5551
5820
|
function skillRootOf(cli) {
|
|
5552
5821
|
return cli === "claude" ? ".claude/skills" : ".agents/skills";
|
|
@@ -5554,11 +5823,11 @@ function skillRootOf(cli) {
|
|
|
5554
5823
|
function snapshotSkillRoots(projectDir) {
|
|
5555
5824
|
const out = /* @__PURE__ */ new Map();
|
|
5556
5825
|
for (const root of SKILL_ROOTS) {
|
|
5557
|
-
if (outsideProjectTarget(projectDir,
|
|
5558
|
-
for (const rel of listFilesRecursive(
|
|
5826
|
+
if (outsideProjectTarget(projectDir, join24(projectDir, root)) !== null) continue;
|
|
5827
|
+
for (const rel of listFilesRecursive(join24(projectDir, root))) {
|
|
5559
5828
|
const path = `${root}/${rel}`;
|
|
5560
5829
|
try {
|
|
5561
|
-
out.set(path, hashContent(readFileSync17(
|
|
5830
|
+
out.set(path, hashContent(readFileSync17(join24(projectDir, path), "utf8")));
|
|
5562
5831
|
} catch {
|
|
5563
5832
|
}
|
|
5564
5833
|
}
|
|
@@ -5635,7 +5904,7 @@ function installOne(asset, ctx) {
|
|
|
5635
5904
|
return runSpawn(asset, ctx.spawn, "npx", buildSkillArgs(method, ctx.cli, ctx.scope), cwd);
|
|
5636
5905
|
}
|
|
5637
5906
|
const outside = SKILL_ROOTS.flatMap((root) => {
|
|
5638
|
-
const hit = outsideProjectTarget(cwd,
|
|
5907
|
+
const hit = outsideProjectTarget(cwd, join24(cwd, root));
|
|
5639
5908
|
return hit === null ? [] : [{ root, target: hit.target }];
|
|
5640
5909
|
});
|
|
5641
5910
|
const outRoots = new Set(outside.map((o) => o.root));
|
|
@@ -5712,6 +5981,11 @@ function refreshExternalSkills(projectDir, deps = {}) {
|
|
|
5712
5981
|
if (targets.length === 0) {
|
|
5713
5982
|
return { ...none, notInCatalog, unknown: false };
|
|
5714
5983
|
}
|
|
5984
|
+
const targetIds = new Set(targets.map((a) => a.id));
|
|
5985
|
+
const settle = holdSkillSlots(
|
|
5986
|
+
projectDir,
|
|
5987
|
+
log.assets.filter((a) => targetIds.has(a.id))
|
|
5988
|
+
);
|
|
5715
5989
|
const report = (deps.run ?? runExternalInstall)(
|
|
5716
5990
|
{
|
|
5717
5991
|
tracks: log.spec.tracks,
|
|
@@ -5732,6 +6006,8 @@ function refreshExternalSkills(projectDir, deps = {}) {
|
|
|
5732
6006
|
...deps.warn ? { warn: deps.warn } : {}
|
|
5733
6007
|
}
|
|
5734
6008
|
);
|
|
6009
|
+
settle();
|
|
6010
|
+
recordRefreshedFiles(projectDir, log, report, deps.writeLog ?? writeInstallLog);
|
|
5735
6011
|
return {
|
|
5736
6012
|
attempted: report.attempted.length,
|
|
5737
6013
|
refreshed: report.succeeded,
|
|
@@ -5740,6 +6016,65 @@ function refreshExternalSkills(projectDir, deps = {}) {
|
|
|
5740
6016
|
unknown: false
|
|
5741
6017
|
};
|
|
5742
6018
|
}
|
|
6019
|
+
function holdSkillSlots(projectDir, assets) {
|
|
6020
|
+
const held = assets.flatMap((a) => holdSlotFiles(projectDir, a.files));
|
|
6021
|
+
return () => held.flatMap((h2) => settleHeld(h2) ?? []);
|
|
6022
|
+
}
|
|
6023
|
+
function holdSlotFiles(projectDir, files) {
|
|
6024
|
+
if (files === void 0) return [];
|
|
6025
|
+
const sha = new Map(files.map((f) => [f.path, f.sha256]));
|
|
6026
|
+
const slots = new Set(files.map((f) => f.path.split("/").slice(0, 3).join("/")));
|
|
6027
|
+
const out = [];
|
|
6028
|
+
for (const slot of slots) {
|
|
6029
|
+
if (outsideProjectTarget(projectDir, join24(projectDir, slot)) !== null) continue;
|
|
6030
|
+
for (const rel of listFilesRecursive(join24(projectDir, slot))) {
|
|
6031
|
+
const path = `${slot}/${rel}`;
|
|
6032
|
+
const abs = join24(projectDir, path);
|
|
6033
|
+
let content;
|
|
6034
|
+
try {
|
|
6035
|
+
content = readFileSync17(abs);
|
|
6036
|
+
} catch {
|
|
6037
|
+
continue;
|
|
6038
|
+
}
|
|
6039
|
+
const recordedSha = sha.get(path);
|
|
6040
|
+
if (recordedSha === void 0) out.push({ abs, content, recorded: false });
|
|
6041
|
+
else if (hashContent(content.toString("utf8")) !== recordedSha)
|
|
6042
|
+
out.push({ abs, content, recorded: true });
|
|
6043
|
+
}
|
|
6044
|
+
}
|
|
6045
|
+
return out;
|
|
6046
|
+
}
|
|
6047
|
+
function settleHeld(h2) {
|
|
6048
|
+
let now = null;
|
|
6049
|
+
try {
|
|
6050
|
+
now = readFileSync17(h2.abs);
|
|
6051
|
+
} catch {
|
|
6052
|
+
}
|
|
6053
|
+
if (now?.equals(h2.content)) return null;
|
|
6054
|
+
if (!h2.recorded && now === null) {
|
|
6055
|
+
mkdirSync5(dirname5(h2.abs), { recursive: true });
|
|
6056
|
+
writeFileSync7(h2.abs, h2.content);
|
|
6057
|
+
return null;
|
|
6058
|
+
}
|
|
6059
|
+
return backupContent(h2.abs, h2.content);
|
|
6060
|
+
}
|
|
6061
|
+
function recordRefreshedFiles(projectDir, log, report, writeLog) {
|
|
6062
|
+
const next = new Map(
|
|
6063
|
+
buildAssetEntries(report, log.scope, log.assets).filter((e2) => e2.files !== void 0).map((e2) => [e2.id, e2.files])
|
|
6064
|
+
);
|
|
6065
|
+
let changed = false;
|
|
6066
|
+
const assets = log.assets.map((a) => {
|
|
6067
|
+
const files = next.get(a.id);
|
|
6068
|
+
if (files === void 0 || JSON.stringify(files) === JSON.stringify(a.files)) return a;
|
|
6069
|
+
changed = true;
|
|
6070
|
+
return { ...a, files };
|
|
6071
|
+
});
|
|
6072
|
+
if (!changed) return;
|
|
6073
|
+
try {
|
|
6074
|
+
writeLog(projectDir, { ...log, assets });
|
|
6075
|
+
} catch {
|
|
6076
|
+
}
|
|
6077
|
+
}
|
|
5743
6078
|
function buildSkillArgs(method, cli, scope) {
|
|
5744
6079
|
const args = [skillsCliSpec(), "add", method.source];
|
|
5745
6080
|
if (method.skill) {
|
|
@@ -5816,8 +6151,8 @@ function detectVersion(method, spawn, scope, projectDir) {
|
|
|
5816
6151
|
case "plugin":
|
|
5817
6152
|
return detectPluginVersion(method.pluginId, scope, projectDir);
|
|
5818
6153
|
case "npm": {
|
|
5819
|
-
const pkgJson = scope === "global" ? detectGlobalNpmPackageJsonPath(method.pkg, spawn) :
|
|
5820
|
-
if (!pkgJson || !
|
|
6154
|
+
const pkgJson = scope === "global" ? detectGlobalNpmPackageJsonPath(method.pkg, spawn) : join24(projectDir, "node_modules", method.pkg, "package.json");
|
|
6155
|
+
if (!pkgJson || !existsSync19(pkgJson)) return void 0;
|
|
5821
6156
|
const parsed = JSON.parse(readFileSync17(pkgJson, "utf8"));
|
|
5822
6157
|
return parsed.version;
|
|
5823
6158
|
}
|
|
@@ -5830,8 +6165,8 @@ function detectVersion(method, spawn, scope, projectDir) {
|
|
|
5830
6165
|
}
|
|
5831
6166
|
function detectPluginVersion(pluginId, scope, projectDir) {
|
|
5832
6167
|
const claudeScope = scope === "global" ? "user" : "project";
|
|
5833
|
-
const path =
|
|
5834
|
-
if (!
|
|
6168
|
+
const path = join24(homedir2(), ".claude/plugins/installed_plugins.json");
|
|
6169
|
+
if (!existsSync19(path)) return void 0;
|
|
5835
6170
|
const data = JSON.parse(readFileSync17(path, "utf8"));
|
|
5836
6171
|
const entries = data.plugins?.[pluginId] ?? [];
|
|
5837
6172
|
const matches = entries.filter(
|
|
@@ -5845,7 +6180,7 @@ function detectPluginVersion(pluginId, scope, projectDir) {
|
|
|
5845
6180
|
}
|
|
5846
6181
|
function detectGlobalNpmPackageJsonPath(pkg, spawn) {
|
|
5847
6182
|
const npmRoot = getNpmGlobalRoot(spawn);
|
|
5848
|
-
return npmRoot ?
|
|
6183
|
+
return npmRoot ? join24(npmRoot, pkg, "package.json") : void 0;
|
|
5849
6184
|
}
|
|
5850
6185
|
var npmGlobalRootCache;
|
|
5851
6186
|
function getNpmGlobalRoot(spawn) {
|
|
@@ -5865,7 +6200,7 @@ function getNpmGlobalRoot(spawn) {
|
|
|
5865
6200
|
// src/key-ids.ts
|
|
5866
6201
|
init_esm_shims();
|
|
5867
6202
|
import { readFileSync as readFileSync18 } from "fs";
|
|
5868
|
-
import { join as
|
|
6203
|
+
import { join as join25 } from "path";
|
|
5869
6204
|
function renderedKeyIds(harnessRoot, input, clis) {
|
|
5870
6205
|
const out = /* @__PURE__ */ new Set();
|
|
5871
6206
|
const add = (path, key) => {
|
|
@@ -5888,7 +6223,7 @@ function renderedKeyIds(harnessRoot, input, clis) {
|
|
|
5888
6223
|
for (const name of Object.keys(mcp.mcpServers)) add(".mcp.json", `mcpServers.${name}`);
|
|
5889
6224
|
for (const key of gitignoreRender().keys()) add(".gitignore", key);
|
|
5890
6225
|
if (clis.includes("claude")) {
|
|
5891
|
-
const template = readFileSync18(
|
|
6226
|
+
const template = readFileSync18(join25(harnessRoot, "templates/settings.json"), "utf8");
|
|
5892
6227
|
for (const key of renderSettingsPortion(template, () => true).keys()) {
|
|
5893
6228
|
add(".claude/settings.json", key);
|
|
5894
6229
|
}
|
|
@@ -5929,7 +6264,7 @@ function withoutAccepts(harnessRoot, spec, previous) {
|
|
|
5929
6264
|
|
|
5930
6265
|
// src/linked-skill-bodies.ts
|
|
5931
6266
|
init_esm_shims();
|
|
5932
|
-
import { join as
|
|
6267
|
+
import { join as join26 } from "path";
|
|
5933
6268
|
function refreshLinkedSkillBodies(params) {
|
|
5934
6269
|
const { harnessRoot, projectDir, ids, baseline, journal } = params;
|
|
5935
6270
|
const bodyKey = (id) => `.agents/skills/${id}/SKILL.md`;
|
|
@@ -5940,10 +6275,11 @@ function refreshLinkedSkillBodies(params) {
|
|
|
5940
6275
|
);
|
|
5941
6276
|
const result = writer.result();
|
|
5942
6277
|
return {
|
|
5943
|
-
updated: ours.filter((id) => written.has(
|
|
6278
|
+
updated: ours.filter((id) => written.has(join26(projectDir, bodyKey(id)))),
|
|
5944
6279
|
notOurs: ids.filter((id) => !baseline.has(bodyKey(id))),
|
|
5945
6280
|
files: result.files,
|
|
5946
6281
|
backupPaths: result.backupPaths,
|
|
6282
|
+
displaced: result.displaced ?? [],
|
|
5947
6283
|
foreignOwned: result.foreignOwned,
|
|
5948
6284
|
outside: result.outside ?? []
|
|
5949
6285
|
};
|
|
@@ -5952,7 +6288,7 @@ function refreshLinkedSkillBodies(params) {
|
|
|
5952
6288
|
// src/out-of-track.ts
|
|
5953
6289
|
init_esm_shims();
|
|
5954
6290
|
import { lstatSync as lstatSync3, readFileSync as readFileSync19, rmSync as rmSync3 } from "fs";
|
|
5955
|
-
import { join as
|
|
6291
|
+
import { join as join27 } from "path";
|
|
5956
6292
|
var AGENTS_RULE = /^\.agents\/rules\/([^/]+)\.md$/;
|
|
5957
6293
|
var ANCHOR_RULE = "uzys-harness";
|
|
5958
6294
|
function reclaimOutOfTrack(args) {
|
|
@@ -5972,7 +6308,7 @@ function reclaimOutOfTrack(args) {
|
|
|
5972
6308
|
const name = AGENTS_RULE.exec(f.path)?.[1];
|
|
5973
6309
|
if (name === void 0 || name === ANCHOR_RULE || rendered.has(name)) continue;
|
|
5974
6310
|
if (isBaselineExcluded(`.claude/rules/${name}.md`, args.excluded)) continue;
|
|
5975
|
-
const abs =
|
|
6311
|
+
const abs = join27(projectDir, f.path);
|
|
5976
6312
|
if (args.outside.skip(abs)) continue;
|
|
5977
6313
|
const stat = lstatSync3(abs, { throwIfNoEntry: false });
|
|
5978
6314
|
if (stat !== void 0 && !stat.isFile()) continue;
|
|
@@ -6013,15 +6349,15 @@ function tracksBringing(rule) {
|
|
|
6013
6349
|
|
|
6014
6350
|
// src/state.ts
|
|
6015
6351
|
init_esm_shims();
|
|
6016
|
-
import { existsSync as
|
|
6017
|
-
import { join as
|
|
6352
|
+
import { existsSync as existsSync20, readFileSync as readFileSync20 } from "fs";
|
|
6353
|
+
import { join as join28 } from "path";
|
|
6018
6354
|
var META_FILE = ".claude/.installed-tracks";
|
|
6019
6355
|
var LEGACY_ANCHOR_FILE = ".claude/CLAUDE.md";
|
|
6020
6356
|
var TRACE_RULES = ["htmx", "nextjs", "data-analysis", "pyside6", "cli-development"].map(
|
|
6021
6357
|
(r) => `.claude/rules/${r}.md`
|
|
6022
6358
|
);
|
|
6023
6359
|
function detectInstallState(projectDir) {
|
|
6024
|
-
const hasClaudeDir =
|
|
6360
|
+
const hasClaudeDir = existsSync20(join28(projectDir, ".claude"));
|
|
6025
6361
|
const read = readInstallLogStatus(projectDir);
|
|
6026
6362
|
if (read.status === "ok") {
|
|
6027
6363
|
return {
|
|
@@ -6041,17 +6377,17 @@ function tracksFromLog(tracks) {
|
|
|
6041
6377
|
return [...new Set(tracks.filter(isTrack))].sort();
|
|
6042
6378
|
}
|
|
6043
6379
|
function collectTraces(projectDir) {
|
|
6044
|
-
const at5 = (rel) =>
|
|
6045
|
-
const contains = (rel, needle) =>
|
|
6380
|
+
const at5 = (rel) => join28(projectDir, rel);
|
|
6381
|
+
const contains = (rel, needle) => existsSync20(at5(rel)) && readFileSync20(at5(rel), "utf8").includes(needle);
|
|
6046
6382
|
const traces = [];
|
|
6047
|
-
if (
|
|
6383
|
+
if (existsSync20(at5(META_FILE)))
|
|
6048
6384
|
traces.push({ path: META_FILE, tracks: readMetafile(at5(META_FILE)) });
|
|
6049
|
-
if (
|
|
6385
|
+
if (existsSync20(at5(HARNESS_ANCHOR_FILE))) traces.push({ path: HARNESS_ANCHOR_FILE });
|
|
6050
6386
|
if (contains("CLAUDE.md", "<!-- uzys-harness:import:start -->"))
|
|
6051
6387
|
traces.push({ path: "CLAUDE.md" });
|
|
6052
6388
|
if (contains("AGENTS.md", "<!-- uzys-harness:")) traces.push({ path: "AGENTS.md" });
|
|
6053
6389
|
for (const rel of [".agents/rules/uzys-harness.md", LEGACY_ANCHOR_FILE, ...TRACE_RULES]) {
|
|
6054
|
-
if (
|
|
6390
|
+
if (existsSync20(at5(rel))) traces.push({ path: rel });
|
|
6055
6391
|
}
|
|
6056
6392
|
return traces;
|
|
6057
6393
|
}
|
|
@@ -6100,17 +6436,17 @@ function reportNotInstalled(detected, projectDir, err) {
|
|
|
6100
6436
|
init_esm_shims();
|
|
6101
6437
|
import {
|
|
6102
6438
|
copyFileSync,
|
|
6103
|
-
existsSync as
|
|
6439
|
+
existsSync as existsSync21,
|
|
6104
6440
|
lstatSync as lstatSync4,
|
|
6105
|
-
mkdirSync as
|
|
6441
|
+
mkdirSync as mkdirSync6,
|
|
6106
6442
|
readdirSync as readdirSync3,
|
|
6107
6443
|
readFileSync as readFileSync21,
|
|
6108
6444
|
rmdirSync,
|
|
6109
|
-
statSync as
|
|
6445
|
+
statSync as statSync4,
|
|
6110
6446
|
unlinkSync as unlinkSync2,
|
|
6111
|
-
writeFileSync as
|
|
6447
|
+
writeFileSync as writeFileSync8
|
|
6112
6448
|
} from "fs";
|
|
6113
|
-
import { basename as basename3, dirname as
|
|
6449
|
+
import { basename as basename3, dirname as dirname6, join as join29 } from "path";
|
|
6114
6450
|
var LEGACY_ANCHOR_FILE2 = ".claude/CLAUDE.md";
|
|
6115
6451
|
function buildUpdateSpec(projectDir, tracks, only) {
|
|
6116
6452
|
const log = readInstallLog(projectDir);
|
|
@@ -6132,7 +6468,7 @@ function parseUpdateOnly(raw) {
|
|
|
6132
6468
|
return { ok: true, groups: [...new Set(raw)] };
|
|
6133
6469
|
}
|
|
6134
6470
|
function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
6135
|
-
const claudeDir =
|
|
6471
|
+
const claudeDir = join29(projectDir, ".claude");
|
|
6136
6472
|
const startedAt = Date.now();
|
|
6137
6473
|
const backupsAtStart = new Set(
|
|
6138
6474
|
collectRunBackups(projectDir, Number.NEGATIVE_INFINITY).map((b2) => b2.backup)
|
|
@@ -6174,33 +6510,63 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
|
6174
6510
|
report.restored = fresh.restored;
|
|
6175
6511
|
const logAtStart = readInstallLog(projectDir);
|
|
6176
6512
|
const claudeManaged = logAtStart === null || installedClis(logAtStart).includes("claude");
|
|
6177
|
-
|
|
6513
|
+
const recordOf = recordedStateOf(logAtStart);
|
|
6514
|
+
const claudeDisplaced = [];
|
|
6515
|
+
report.claudeUnrecorded = !claudeManaged && logAtStart !== null && existsSync21(claudeDir) ? recordClaudeCommand(logAtStart, installedTracks(projectDir)) : null;
|
|
6178
6516
|
const policyBase = policyBaseline(projectDir);
|
|
6179
6517
|
for (const { dir, ext } of POLICY_DIRS) {
|
|
6180
6518
|
if (!claudeManaged || !wants(dir === "hooks" ? "hooks" : "rules")) continue;
|
|
6181
|
-
const target =
|
|
6182
|
-
const source =
|
|
6519
|
+
const target = join29(claudeDir, dir);
|
|
6520
|
+
const source = join29(templatesDir, dir);
|
|
6183
6521
|
const label = `.claude/${dir}`;
|
|
6184
|
-
const ctx = {
|
|
6522
|
+
const ctx = {
|
|
6523
|
+
prefix: dir,
|
|
6524
|
+
baseline: policyBase,
|
|
6525
|
+
outside,
|
|
6526
|
+
excluded: excludedIds(logAtStart),
|
|
6527
|
+
// 0단계가 방금 깐 파일은 기록 전이지만 하네스 것이다 — 첫 접촉으로 읽지 않는다
|
|
6528
|
+
justWritten: new Set(fresh.installed),
|
|
6529
|
+
...recordOf === void 0 ? {} : { recordOf }
|
|
6530
|
+
};
|
|
6185
6531
|
const synced = updateDir(target, source, ext, ctx);
|
|
6186
6532
|
report.updated[label] = synced.updated;
|
|
6187
6533
|
report.policyBackedUp.push(...synced.backedUp);
|
|
6534
|
+
claudeDisplaced.push(...synced.displaced);
|
|
6188
6535
|
report.pruned[label] = pruneOrphans(target, source, ext, ctx);
|
|
6189
6536
|
}
|
|
6190
6537
|
const syncedDirs = POLICY_DIRS.filter((d2) => wants(d2.dir === "hooks" ? "hooks" : "rules")).map(
|
|
6191
6538
|
(d2) => d2.dir
|
|
6192
6539
|
);
|
|
6193
6540
|
if (claudeManaged && syncedDirs.length > 0)
|
|
6194
|
-
refreshPolicyBaseline(
|
|
6541
|
+
refreshPolicyBaseline(
|
|
6542
|
+
projectDir,
|
|
6543
|
+
templatesDir,
|
|
6544
|
+
syncedDirs,
|
|
6545
|
+
outsidePaths(outside, ".claude/"),
|
|
6546
|
+
notOursUnder(claudeDisplaced, ".claude/")
|
|
6547
|
+
);
|
|
6548
|
+
const skillTargets = logAtStart === null ? void 0 : recordedSkillIds(projectDir, logAtStart);
|
|
6195
6549
|
const skillSync = claudeManaged && wants("skills") ? syncSkills(
|
|
6196
|
-
|
|
6197
|
-
|
|
6550
|
+
join29(claudeDir, "skills"),
|
|
6551
|
+
join29(templatesDir, "skills"),
|
|
6198
6552
|
skillBaseline(projectDir),
|
|
6199
6553
|
/* @__PURE__ */ new Date(),
|
|
6200
6554
|
(relInSkills) => foreignOwnedTarget(projectDir, `.claude/skills/${relInSkills}`),
|
|
6201
6555
|
outside,
|
|
6202
|
-
excludedSkillOf(logAtStart)
|
|
6203
|
-
|
|
6556
|
+
excludedSkillOf(logAtStart),
|
|
6557
|
+
{
|
|
6558
|
+
...recordOf === void 0 ? {} : { recordOf },
|
|
6559
|
+
...skillTargets === void 0 ? {} : { targets: skillTargets }
|
|
6560
|
+
}
|
|
6561
|
+
) : {
|
|
6562
|
+
updated: 0,
|
|
6563
|
+
backedUp: [],
|
|
6564
|
+
skippedLinks: [],
|
|
6565
|
+
foreignOwned: [],
|
|
6566
|
+
pruned: [],
|
|
6567
|
+
displaced: []
|
|
6568
|
+
};
|
|
6569
|
+
claudeDisplaced.push(...skillSync.displaced);
|
|
6204
6570
|
if (wants("skills")) report.updated[".claude/skills"] = skillSync.updated;
|
|
6205
6571
|
report.skillsBackedUp = skillSync.backedUp;
|
|
6206
6572
|
report.skillsSkippedLinks = skillSync.skippedLinks;
|
|
@@ -6210,10 +6576,17 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
|
6210
6576
|
report.restored.push(...newSkills.restored);
|
|
6211
6577
|
const copiedSkills = [...newSkills.installed, ...newSkills.restored];
|
|
6212
6578
|
if (claudeManaged && wants("skills"))
|
|
6213
|
-
refreshSkillBaseline(
|
|
6579
|
+
refreshSkillBaseline(
|
|
6580
|
+
projectDir,
|
|
6581
|
+
templatesDir,
|
|
6582
|
+
outsidePaths(outside, ".claude/skills/"),
|
|
6583
|
+
skillTargets,
|
|
6584
|
+
notOursUnder(claudeDisplaced, ".claude/skills/")
|
|
6585
|
+
);
|
|
6214
6586
|
else if (claudeManaged && copiedSkills.length > 0)
|
|
6215
6587
|
recordNewSkillBaseline(projectDir, templatesDir, copiedSkills);
|
|
6216
|
-
if (
|
|
6588
|
+
if (claudeDisplaced.length > 0) recordDisplaced(projectDir, claudeDisplaced);
|
|
6589
|
+
if (existsSync21(claudeDir) && claudeManaged) {
|
|
6217
6590
|
if (wants("anchor")) syncHarnessAnchor(projectDir, templatesDir, report, outside);
|
|
6218
6591
|
}
|
|
6219
6592
|
const harnessMcp = renderHarnessMcp(harnessRoot, {
|
|
@@ -6232,14 +6605,18 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
|
6232
6605
|
droppedKeys: legacyDroppedKeys(logAtStart),
|
|
6233
6606
|
harnessMcp
|
|
6234
6607
|
});
|
|
6235
|
-
const settingsPath =
|
|
6236
|
-
if (claudeManaged && wants("hooks") &&
|
|
6237
|
-
report.
|
|
6608
|
+
const settingsPath = join29(claudeDir, "settings.json");
|
|
6609
|
+
if (claudeManaged && wants("hooks") && existsSync21(settingsPath) && !outside.skip(settingsPath)) {
|
|
6610
|
+
report.retiredHookRefsRemoved = removeRetiredHookRefs(
|
|
6611
|
+
settingsPath,
|
|
6612
|
+
claudeDir,
|
|
6613
|
+
RETIRED_HOOK_WIRING_REFS
|
|
6614
|
+
);
|
|
6615
|
+
report.staleHookRefs = findStaleHookRefs(
|
|
6238
6616
|
settingsPath,
|
|
6239
6617
|
claudeDir,
|
|
6240
6618
|
recordedHarnessHook(projectDir, logAtStart)
|
|
6241
6619
|
);
|
|
6242
|
-
if (report.staleHookRefs.length > 0) dropHealedHookPortions(projectDir, report.staleHookRefs);
|
|
6243
6620
|
}
|
|
6244
6621
|
if (wants("hooks")) report.mcpAllowlistRetired = retireMcpAllowlist(projectDir);
|
|
6245
6622
|
report.retiredAgents = staleAgentFiles(claudeDir);
|
|
@@ -6249,6 +6626,7 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
|
6249
6626
|
const external = wants("external") ? refreshExternalCli(projectDir, harnessRoot, legacyDroppedKeys(logAtStart), harnessMcp) : {
|
|
6250
6627
|
externalUpdated: 0,
|
|
6251
6628
|
externalBackedUp: [],
|
|
6629
|
+
displaced: [],
|
|
6252
6630
|
externalForeignOwned: [],
|
|
6253
6631
|
externalOutside: [],
|
|
6254
6632
|
written: [],
|
|
@@ -6340,9 +6718,15 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
|
6340
6718
|
report.externalSkillsUnknown = skillRefresh.unknown;
|
|
6341
6719
|
const outsideLinks = mergeOutside(outside.list(), sharedWrites.outside, external.externalOutside);
|
|
6342
6720
|
if (outsideLinks.length > 0) report.outsideLinks = outsideLinks;
|
|
6721
|
+
const displacedAll = [...claudeDisplaced, ...external.displaced];
|
|
6722
|
+
const displacedMoved = displacedAll.filter((f) => f.notes.length > 0).map((f) => f.path);
|
|
6723
|
+
const displacedSame = displacedAll.filter((f) => f.notes.length === 0).map((f) => f.path);
|
|
6724
|
+
if (displacedMoved.length > 0) report.displaced = displacedMoved;
|
|
6725
|
+
if (displacedSame.length > 0) report.displacedSame = displacedSame;
|
|
6343
6726
|
report.backups = collectRunBackups(projectDir, startedAt, backupsAtStart);
|
|
6344
6727
|
const onRecord = checksumOnRecord(logAtStart);
|
|
6345
|
-
const
|
|
6728
|
+
const firstContact = new Set(displacedMoved);
|
|
6729
|
+
const noChecksum = report.backups.map((b2) => b2.path).filter((p2) => !onRecord(p2) && !firstContact.has(p2));
|
|
6346
6730
|
if (noChecksum.length > 0) report.noChecksum = noChecksum;
|
|
6347
6731
|
writeBackupList(projectDir, report.backups, new Set(noChecksum));
|
|
6348
6732
|
return report;
|
|
@@ -6422,7 +6806,8 @@ function checksumOnRecord(log) {
|
|
|
6422
6806
|
const paths = /* @__PURE__ */ new Set([
|
|
6423
6807
|
...(log.policyFiles ?? []).map((f) => `.claude/${f.path}`),
|
|
6424
6808
|
...(log.skillFiles ?? []).map((f) => `.claude/skills/${f.path}`),
|
|
6425
|
-
...(log.externalFiles ?? []).map((f) => f.path)
|
|
6809
|
+
...(log.externalFiles ?? []).map((f) => f.path),
|
|
6810
|
+
...log.assets.flatMap((a) => a.files ?? []).map((f) => f.path)
|
|
6426
6811
|
]);
|
|
6427
6812
|
const anchor = log.templates.rootClaudeMd;
|
|
6428
6813
|
if (anchor !== void 0) paths.add(anchor.path);
|
|
@@ -6435,7 +6820,7 @@ function retiredFeaturesPart(projectDir, f) {
|
|
|
6435
6820
|
}
|
|
6436
6821
|
function legacyHookOnDisk(projectDir, rel) {
|
|
6437
6822
|
try {
|
|
6438
|
-
return hasLegacyHarnessHook(readFileSync21(
|
|
6823
|
+
return hasLegacyHarnessHook(readFileSync21(join29(projectDir, rel), "utf8"));
|
|
6439
6824
|
} catch {
|
|
6440
6825
|
return false;
|
|
6441
6826
|
}
|
|
@@ -6461,8 +6846,8 @@ function writeUpdateSharedFiles(args) {
|
|
|
6461
6846
|
excluded: adapterExcluded(harnessRoot, excluded)
|
|
6462
6847
|
});
|
|
6463
6848
|
const tracks = installedTracks(projectDir);
|
|
6464
|
-
const settingsSource =
|
|
6465
|
-
if (args.claudeManaged && wants("hooks") &&
|
|
6849
|
+
const settingsSource = join29(templatesDir, "settings.json");
|
|
6850
|
+
if (args.claudeManaged && wants("hooks") && existsSync21(settingsSource)) {
|
|
6466
6851
|
const spec = buildAssetSpec({ tracks, options: DEFAULT_OPTIONS });
|
|
6467
6852
|
const manifest = buildManifest(spec);
|
|
6468
6853
|
writeSettingsShared(
|
|
@@ -6472,7 +6857,7 @@ function writeUpdateSharedFiles(args) {
|
|
|
6472
6857
|
log,
|
|
6473
6858
|
(script) => {
|
|
6474
6859
|
const target = `.claude/hooks/${script}`;
|
|
6475
|
-
return manifest.some((e2) => e2.target === target && e2.applies(spec)) && !isBaselineExcluded(target, excluded) &&
|
|
6860
|
+
return manifest.some((e2) => e2.target === target && e2.applies(spec)) && !isBaselineExcluded(target, excluded) && existsSync21(join29(projectDir, target));
|
|
6476
6861
|
}
|
|
6477
6862
|
// 기록이 claude 를 깔린 CLI 로 말한다 = install 이 이 파일을 썼다 — 없으면 만든다(훅 배선이 여기 산다)
|
|
6478
6863
|
);
|
|
@@ -6536,11 +6921,11 @@ function catalogRemovedOnRecord(log) {
|
|
|
6536
6921
|
}
|
|
6537
6922
|
function staleSkillDirs(projectDir, claudeDir, clis) {
|
|
6538
6923
|
const stale = (id) => RENAMED_SKILL_IDS[id] !== void 0 || RETIRED_SKILL_IDS.includes(id);
|
|
6539
|
-
const dirs = [
|
|
6540
|
-
if (clis.some((cli) => cli !== "claude")) dirs.push(
|
|
6924
|
+
const dirs = [join29(claudeDir, "skills")];
|
|
6925
|
+
if (clis.some((cli) => cli !== "claude")) dirs.push(join29(projectDir, ".agents", "skills"));
|
|
6541
6926
|
const ids = /* @__PURE__ */ new Set();
|
|
6542
6927
|
for (const dir of dirs) {
|
|
6543
|
-
if (!
|
|
6928
|
+
if (!existsSync21(dir)) continue;
|
|
6544
6929
|
for (const e2 of readdirSync3(dir, { withFileTypes: true })) {
|
|
6545
6930
|
if (e2.isDirectory() && stale(e2.name)) ids.add(e2.name);
|
|
6546
6931
|
}
|
|
@@ -6548,14 +6933,14 @@ function staleSkillDirs(projectDir, claudeDir, clis) {
|
|
|
6548
6933
|
return [...ids];
|
|
6549
6934
|
}
|
|
6550
6935
|
function staleAgentFiles(claudeDir) {
|
|
6551
|
-
const dir =
|
|
6552
|
-
if (!
|
|
6936
|
+
const dir = join29(claudeDir, "agents");
|
|
6937
|
+
if (!existsSync21(dir)) return [];
|
|
6553
6938
|
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));
|
|
6554
6939
|
}
|
|
6555
6940
|
function demotedAgentFiles(claudeDir, tracks) {
|
|
6556
6941
|
if (tracks.length === 0) return [];
|
|
6557
|
-
const dir =
|
|
6558
|
-
if (!
|
|
6942
|
+
const dir = join29(claudeDir, "agents");
|
|
6943
|
+
if (!existsSync21(dir)) return [];
|
|
6559
6944
|
const demoted = TRACK_AGENTS.filter(([, pattern]) => !anyTrack(tracks, pattern)).map(
|
|
6560
6945
|
([id]) => id
|
|
6561
6946
|
);
|
|
@@ -6577,21 +6962,21 @@ function installNewAssets(projectDir, templatesDir, wants, outside) {
|
|
|
6577
6962
|
if (!wants(group)) continue;
|
|
6578
6963
|
if (entry.target === HARNESS_ANCHOR_FILE) continue;
|
|
6579
6964
|
if (isBaselineExcluded(entry.target, baselineExcluded)) continue;
|
|
6580
|
-
const target =
|
|
6965
|
+
const target = join29(projectDir, entry.target);
|
|
6581
6966
|
const foreign = foreignOwnedTarget(projectDir, entry.target);
|
|
6582
6967
|
if (foreign !== null) {
|
|
6583
6968
|
if (!foreignOwned.includes(foreign)) foreignOwned.push(foreign);
|
|
6584
6969
|
continue;
|
|
6585
6970
|
}
|
|
6586
|
-
if (
|
|
6587
|
-
const source =
|
|
6588
|
-
if (!
|
|
6971
|
+
if (existsSync21(target)) continue;
|
|
6972
|
+
const source = join29(templatesDir, entry.source);
|
|
6973
|
+
if (!existsSync21(source)) continue;
|
|
6589
6974
|
if (outside.skip(target)) continue;
|
|
6590
6975
|
if (entry.target.startsWith(".claude/")) {
|
|
6591
6976
|
if (!claudeSelected) continue;
|
|
6592
6977
|
if (entry.target === SETTINGS_TARGET) continue;
|
|
6593
6978
|
}
|
|
6594
|
-
|
|
6979
|
+
mkdirSync6(dirname6(target), { recursive: true });
|
|
6595
6980
|
copyFileSync(source, target);
|
|
6596
6981
|
const recordedAs = entry.target.startsWith(".claude/") ? entry.target.slice(8) : entry.target;
|
|
6597
6982
|
const wired = entry.target.startsWith(".claude/hooks/") && wiredBefore.has(entry.target.slice(".claude/hooks/".length));
|
|
@@ -6611,27 +6996,27 @@ function collectRunBackups(projectDir, startedAt, exclude) {
|
|
|
6611
6996
|
const consider = (rel) => {
|
|
6612
6997
|
if (!BACKUP_SUFFIX.test(rel)) return;
|
|
6613
6998
|
if (exclude?.has(rel)) return;
|
|
6614
|
-
const abs =
|
|
6615
|
-
if (
|
|
6999
|
+
const abs = join29(projectDir, rel);
|
|
7000
|
+
if (statSync4(abs).mtimeMs < startedAt - 1e3) return;
|
|
6616
7001
|
out.push({ path: rel.replace(BACKUP_SUFFIX, ""), backup: rel });
|
|
6617
7002
|
};
|
|
6618
7003
|
for (const e2 of readdirSync3(projectDir, { withFileTypes: true }))
|
|
6619
7004
|
if (e2.isFile()) consider(e2.name);
|
|
6620
7005
|
for (const dir of BACKUP_SCAN_DIRS) {
|
|
6621
|
-
for (const rel of listFilesRecursive(
|
|
7006
|
+
for (const rel of listFilesRecursive(join29(projectDir, dir))) consider(`${dir}/${rel}`);
|
|
6622
7007
|
}
|
|
6623
7008
|
return out.sort((a, b2) => a.path.localeCompare(b2.path));
|
|
6624
7009
|
}
|
|
6625
7010
|
function writeBackupList(projectDir, backups, noChecksum = /* @__PURE__ */ new Set()) {
|
|
6626
|
-
const dir =
|
|
6627
|
-
const file =
|
|
7011
|
+
const dir = join29(projectDir, ".uzys-agent-harness");
|
|
7012
|
+
const file = join29(dir, "update-backups.json");
|
|
6628
7013
|
try {
|
|
6629
7014
|
if (backups.length === 0) {
|
|
6630
|
-
if (
|
|
7015
|
+
if (existsSync21(file)) unlinkSync2(file);
|
|
6631
7016
|
return;
|
|
6632
7017
|
}
|
|
6633
|
-
if (!
|
|
6634
|
-
|
|
7018
|
+
if (!existsSync21(dir)) return;
|
|
7019
|
+
writeFileSync8(
|
|
6635
7020
|
file,
|
|
6636
7021
|
`${JSON.stringify(
|
|
6637
7022
|
{
|
|
@@ -6662,15 +7047,15 @@ function installNewSkillDirs(projectDir, templatesDir, tracks, outside) {
|
|
|
6662
7047
|
if (entry.type !== "dir" || !entry.target.startsWith(".claude/skills/")) continue;
|
|
6663
7048
|
if (!entry.applies(spec) || isBaselineExcluded(entry.target, excluded)) continue;
|
|
6664
7049
|
if (skillExcluded.has(entry.target.slice(".claude/skills/".length))) continue;
|
|
6665
|
-
const target =
|
|
7050
|
+
const target = join29(projectDir, entry.target);
|
|
6666
7051
|
const foreign = foreignOwnedTarget(projectDir, entry.target);
|
|
6667
7052
|
if (foreign !== null) {
|
|
6668
7053
|
if (!foreignOwned.includes(foreign)) foreignOwned.push(foreign);
|
|
6669
7054
|
continue;
|
|
6670
7055
|
}
|
|
6671
|
-
if (
|
|
6672
|
-
const source =
|
|
6673
|
-
if (!
|
|
7056
|
+
if (existsSync21(target)) continue;
|
|
7057
|
+
const source = join29(templatesDir, entry.source);
|
|
7058
|
+
if (!existsSync21(source)) continue;
|
|
6674
7059
|
if (outside.skip(target)) continue;
|
|
6675
7060
|
copyDir(source, target, (rel) => foreignOwnedTarget(projectDir, `${entry.target}/${rel}`));
|
|
6676
7061
|
if (priorIds.has(entry.target.slice(".claude/skills/".length))) restored.push(entry.target);
|
|
@@ -6681,6 +7066,7 @@ function installNewSkillDirs(projectDir, templatesDir, tracks, outside) {
|
|
|
6681
7066
|
function recordNewSkillBaseline(projectDir, templatesDir, targets) {
|
|
6682
7067
|
const log = readInstallLog(projectDir);
|
|
6683
7068
|
if (!log) return;
|
|
7069
|
+
if (log.skillFiles === void 0) return;
|
|
6684
7070
|
const ids = new Set(targets.map((t) => t.slice(".claude/skills/".length)));
|
|
6685
7071
|
const added = collectSkillHashes(projectDir, templatesDir).filter(
|
|
6686
7072
|
(f) => ids.has(f.path.split("/")[0] ?? "")
|
|
@@ -6700,14 +7086,14 @@ function trackOnlyFileAssets(tracks) {
|
|
|
6700
7086
|
);
|
|
6701
7087
|
}
|
|
6702
7088
|
function syncHarnessAnchor(projectDir, templatesDir, report, outside, now = /* @__PURE__ */ new Date()) {
|
|
6703
|
-
if (
|
|
7089
|
+
if (existsSync21(join29(projectDir, LEGACY_ANCHOR_FILE2))) {
|
|
6704
7090
|
report.legacyAnchor = LEGACY_ANCHOR_FILE2;
|
|
6705
7091
|
}
|
|
6706
|
-
const templateMd =
|
|
6707
|
-
if (!
|
|
6708
|
-
const anchor =
|
|
7092
|
+
const templateMd = join29(templatesDir, "CLAUDE.md");
|
|
7093
|
+
if (!existsSync21(templateMd)) return;
|
|
7094
|
+
const anchor = join29(projectDir, HARNESS_ANCHOR_FILE);
|
|
6709
7095
|
if (outside.skip(anchor)) return;
|
|
6710
|
-
const existed =
|
|
7096
|
+
const existed = existsSync21(anchor);
|
|
6711
7097
|
if (existed) {
|
|
6712
7098
|
const current = readFileSync21(anchor, "utf8");
|
|
6713
7099
|
if (current !== readFileSync21(templateMd, "utf8")) {
|
|
@@ -6731,21 +7117,21 @@ function syncHarnessAnchor(projectDir, templatesDir, report, outside, now = /* @
|
|
|
6731
7117
|
recordAnchorBaseline(projectDir, anchor);
|
|
6732
7118
|
}
|
|
6733
7119
|
function upsertRootImport(projectDir, outside) {
|
|
6734
|
-
const target =
|
|
7120
|
+
const target = join29(projectDir, "CLAUDE.md");
|
|
6735
7121
|
if (outside.skip(target)) return false;
|
|
6736
|
-
const existing =
|
|
7122
|
+
const existing = existsSync21(target) ? readFileSync21(target, "utf8") : null;
|
|
6737
7123
|
const next = upsertHarnessImport(existing, {
|
|
6738
7124
|
projectName: basename3(projectDir),
|
|
6739
7125
|
tracks: installedTracks(projectDir),
|
|
6740
7126
|
// ADR-085 — update 는 선택 목록을 인자로 받지 않는다. `syncSkills` 와 같은 판정을 쓴다:
|
|
6741
7127
|
// `.claude/skills/<id>` 가 있으면 그 스킬은 이 설치가 고른 것이다.
|
|
6742
7128
|
continuousSkills: CONTINUOUS_SKILLS.map((s) => s.id).filter(
|
|
6743
|
-
(id) =>
|
|
7129
|
+
(id) => existsSync21(join29(projectDir, ".claude/skills", id))
|
|
6744
7130
|
)
|
|
6745
7131
|
});
|
|
6746
7132
|
if (next === existing) return false;
|
|
6747
7133
|
if (existing !== null) backupIfLossyUtf8(target);
|
|
6748
|
-
|
|
7134
|
+
writeFileSync8(target, next);
|
|
6749
7135
|
return true;
|
|
6750
7136
|
}
|
|
6751
7137
|
function installedTracks(projectDir) {
|
|
@@ -6775,7 +7161,7 @@ function excludedBaselineOnDisk(projectDir, log) {
|
|
|
6775
7161
|
if (excluded.size === 0) return [];
|
|
6776
7162
|
const spec = buildAssetSpec({ tracks: installedTracks(projectDir), options: DEFAULT_OPTIONS });
|
|
6777
7163
|
return buildManifest(spec).filter(
|
|
6778
|
-
(e2) => e2.applies(spec) && isBaselineExcluded(e2.target, excluded) &&
|
|
7164
|
+
(e2) => e2.applies(spec) && isBaselineExcluded(e2.target, excluded) && existsSync21(join29(projectDir, e2.target))
|
|
6779
7165
|
).map((e2) => e2.target);
|
|
6780
7166
|
}
|
|
6781
7167
|
function excludedSkillOf(log) {
|
|
@@ -6785,12 +7171,12 @@ function excludedSkillOf(log) {
|
|
|
6785
7171
|
function recordedRenderInputs(projectDir, log) {
|
|
6786
7172
|
const excluded = excludedIds(log);
|
|
6787
7173
|
return {
|
|
6788
|
-
// 스킬은
|
|
6789
|
-
// 없는 것을 건너뛰지만, `AGENTS.md` 의 상시 스킬 안내(ADR-085)는 이 목록 그대로 렌더돼
|
|
6790
|
-
// `--without` 으로 뺀 스킬(#505)·opt-in 스킬을 "열어라"고 적었다(실측 2026-09-21).
|
|
6791
|
-
//
|
|
7174
|
+
// 스킬은 **기록 + 이번 릴리즈 기본**만 넘긴다(#761 — `recordedBundledSkills`). 스킬 *파일*은 전체 목록을 넘겨도
|
|
7175
|
+
// refreshOnly 가 없는 것을 건너뛰지만, `AGENTS.md` 의 상시 스킬 안내(ADR-085)는 이 목록 그대로 렌더돼
|
|
7176
|
+
// `--without` 으로 뺀 스킬(#505)·opt-in 스킬을 "열어라"고 적었다(실측 2026-09-21). 디스크에 같은 이름의 폴더가
|
|
7177
|
+
// 있다는 것은 그 스킬을 깔았다는 증거가 아니다(ADR-096 D6) — 손으로 만든 opt-in 이름 폴더를 덮지 않는다.
|
|
6792
7178
|
// ADR-099 R3 — 뺀 스킬은 디스크에 남아 있어도 갱신하지도 안내하지도 않는다(install 이 내는 것과 같은 목록)
|
|
6793
|
-
selectedInternalSkills:
|
|
7179
|
+
selectedInternalSkills: recordedBundledSkills(projectDir, log).filter(
|
|
6794
7180
|
(id) => !excludedSkillOf(log)(id)
|
|
6795
7181
|
),
|
|
6796
7182
|
// #601 — install 과 같은 SSOT(resolveRules)를 설치된 트랙으로. 렌더 경로(AGENTS.md 절 · antigravity 룰 생성)는
|
|
@@ -6800,11 +7186,29 @@ function recordedRenderInputs(projectDir, log) {
|
|
|
6800
7186
|
)
|
|
6801
7187
|
};
|
|
6802
7188
|
}
|
|
6803
|
-
function
|
|
6804
|
-
|
|
6805
|
-
|
|
6806
|
-
|
|
7189
|
+
function recordedBundledSkills(projectDir, log) {
|
|
7190
|
+
if (log === null) {
|
|
7191
|
+
return INTERNAL_BUNDLED_SKILL_IDS.filter(
|
|
7192
|
+
(id) => existsSync21(join29(projectDir, ".claude/skills", id)) || existsSync21(join29(projectDir, ".agents/skills", id))
|
|
7193
|
+
);
|
|
7194
|
+
}
|
|
7195
|
+
const ids = recordedSkillIds(projectDir, log);
|
|
7196
|
+
return INTERNAL_BUNDLED_SKILL_IDS.filter((id) => ids.has(id));
|
|
6807
7197
|
}
|
|
7198
|
+
function recordedSkillIds(projectDir, log) {
|
|
7199
|
+
const isTarget = recordedTrackTargets(log);
|
|
7200
|
+
const ids = new Set(bundledSkillTargets(projectDir, log));
|
|
7201
|
+
for (const f of log.skillFiles ?? []) {
|
|
7202
|
+
const id = f.path.split("/")[0] ?? "";
|
|
7203
|
+
if (recorded(log, `.claude/skills/${f.path}`, { isTarget }).state !== "none") ids.add(id);
|
|
7204
|
+
}
|
|
7205
|
+
for (const f of log.externalFiles ?? []) {
|
|
7206
|
+
const id = SHARED_SKILL_ID.exec(f.path)?.[1];
|
|
7207
|
+
if (id !== void 0) ids.add(id);
|
|
7208
|
+
}
|
|
7209
|
+
return ids;
|
|
7210
|
+
}
|
|
7211
|
+
var SHARED_SKILL_ID = /^\.agents\/skills\/([^/]+)\//;
|
|
6808
7212
|
function installedCliTargets(log) {
|
|
6809
7213
|
if (log === null) return ALL_CLI_TARGETS;
|
|
6810
7214
|
return installedClis(log);
|
|
@@ -6832,9 +7236,9 @@ var SHARED_SKILLS_DIR = ".agents/skills";
|
|
|
6832
7236
|
var SHARED_SKILL_FILE = /^\.agents\/skills\/([^/]+)\//;
|
|
6833
7237
|
var SHARED_SKILL_MD = /^\.agents\/skills\/([^/]+)\/SKILL\.md$/;
|
|
6834
7238
|
function sharedSkillIdsOnDisk(projectDir) {
|
|
6835
|
-
const root =
|
|
6836
|
-
if (!
|
|
6837
|
-
return new Set(readdirSync3(root).filter((id) =>
|
|
7239
|
+
const root = join29(projectDir, SHARED_SKILLS_DIR);
|
|
7240
|
+
if (!existsSync21(root)) return /* @__PURE__ */ new Set();
|
|
7241
|
+
return new Set(readdirSync3(root).filter((id) => existsSync21(join29(root, id, "SKILL.md"))));
|
|
6838
7242
|
}
|
|
6839
7243
|
var CREATED_SHARED_HOW = {
|
|
6840
7244
|
"opencode.json": "created from template (harness part + template defaults)",
|
|
@@ -6861,14 +7265,14 @@ function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp) {
|
|
|
6861
7265
|
const log = readInstallLog(projectDir);
|
|
6862
7266
|
const presentBefore = sharedSkillIdsOnDisk(projectDir);
|
|
6863
7267
|
const rulesBefore = /* @__PURE__ */ new Set();
|
|
6864
|
-
const rulesDir =
|
|
6865
|
-
if (
|
|
7268
|
+
const rulesDir = join29(projectDir, ".agents", "rules");
|
|
7269
|
+
if (existsSync21(rulesDir)) {
|
|
6866
7270
|
try {
|
|
6867
7271
|
for (const f of readdirSync3(rulesDir)) rulesBefore.add(f);
|
|
6868
7272
|
} catch {
|
|
6869
7273
|
}
|
|
6870
7274
|
}
|
|
6871
|
-
const agentsMdBefore =
|
|
7275
|
+
const agentsMdBefore = existsSync21(join29(projectDir, "AGENTS.md"));
|
|
6872
7276
|
const result = runCliTransforms({
|
|
6873
7277
|
harnessRoot,
|
|
6874
7278
|
projectDir,
|
|
@@ -6884,7 +7288,10 @@ function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp) {
|
|
|
6884
7288
|
shared: { portions: log?.portions ?? [], excluded: [...excludedIds(log)] },
|
|
6885
7289
|
// ADR-099 R2 (#584) — 기록이 깔린 CLI 로 말하면 그 CLI 의 앵커·룰을 없어도 만든다(디스크의 앵커 존재가 아니라 기록)
|
|
6886
7290
|
// 기록 없음: CLI 경로로는 도달 불가 — 진입 판정(#699)이 먼저 거절. 엔진 단위 방어(빈 집합).
|
|
6887
|
-
recordedClis: log === null ? [] : installedClis(log)
|
|
7291
|
+
recordedClis: log === null ? [] : installedClis(log),
|
|
7292
|
+
// #761 B-1 — 외부 기록이 아예 없던 옛 판의 산출물은 첫 접촉이 아니라 체크섬 없는 하네스 파일이다(install 과 같은 판정).
|
|
7293
|
+
// 앞 단계(`refreshNeutralHelpers`)가 헬퍼만 든 `externalFiles` 를 적어도 같은 답이다 — 헬퍼는 외부 자리로 세지 않는다(B-2)
|
|
7294
|
+
owned: recordedOwner(log)
|
|
6888
7295
|
});
|
|
6889
7296
|
if (log) {
|
|
6890
7297
|
const retiredPath = result.anchorRetired?.forget === true ? result.anchorRetired.path : null;
|
|
@@ -6902,7 +7309,8 @@ function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp) {
|
|
|
6902
7309
|
if (portions.length > 0) next.portions = portions;
|
|
6903
7310
|
else delete next.portions;
|
|
6904
7311
|
const created = result.sharedFiles.filter((f) => f.createdAsRoot === true).map((f) => ({ path: f.path, change: "created", notes: ["\uD558\uB124\uC2A4 \uBAAB\uB9CC(\uBE14\uB85D)"] }));
|
|
6905
|
-
|
|
7312
|
+
const roots = [...created, ...withoutLostBackups(log.rootFiles, result.displaced ?? [])];
|
|
7313
|
+
if (roots.length > 0) next.rootFiles = mergeRootFiles(log.rootFiles, roots);
|
|
6906
7314
|
try {
|
|
6907
7315
|
writeInstallLog(projectDir, next);
|
|
6908
7316
|
} catch {
|
|
@@ -6959,6 +7367,7 @@ function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp) {
|
|
|
6959
7367
|
return {
|
|
6960
7368
|
externalUpdated: result.externalUpdated,
|
|
6961
7369
|
externalBackedUp: result.externalBackedUp,
|
|
7370
|
+
displaced: result.displaced ?? [],
|
|
6962
7371
|
externalForeignOwned: result.externalForeignOwned,
|
|
6963
7372
|
externalOutside: result.externalOutside,
|
|
6964
7373
|
written: result.externalFiles.map((f) => f.path),
|
|
@@ -7001,92 +7410,154 @@ function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp) {
|
|
|
7001
7410
|
})
|
|
7002
7411
|
};
|
|
7003
7412
|
}
|
|
7004
|
-
function isHarnessOwned2(ctx, rel, current) {
|
|
7005
|
-
return isHarnessOwned(ctx.baseline, `${ctx.prefix}/${rel}`, current);
|
|
7006
|
-
}
|
|
7007
7413
|
function updateDir(target, source, ext, ctx, now = /* @__PURE__ */ new Date()) {
|
|
7008
|
-
if (!
|
|
7414
|
+
if (!existsSync21(target) || !existsSync21(source))
|
|
7415
|
+
return { updated: 0, backedUp: [], displaced: [] };
|
|
7009
7416
|
let updated = 0;
|
|
7010
7417
|
const backedUp = [];
|
|
7418
|
+
const displaced = [];
|
|
7011
7419
|
for (const file of readdirSync3(target)) {
|
|
7012
7420
|
if (!file.endsWith(ext)) continue;
|
|
7013
|
-
const targetFile =
|
|
7014
|
-
const sourceFile =
|
|
7015
|
-
if (!
|
|
7421
|
+
const targetFile = join29(target, file);
|
|
7422
|
+
const sourceFile = join29(source, file);
|
|
7423
|
+
if (!existsSync21(sourceFile)) continue;
|
|
7016
7424
|
if (ctx.outside?.skip(targetFile)) continue;
|
|
7017
|
-
|
|
7425
|
+
const projectRel = `.claude/${ctx.prefix}/${file}`;
|
|
7426
|
+
if (ctx.excluded && isBaselineExcluded(projectRel, ctx.excluded)) continue;
|
|
7427
|
+
if (ctx.justWritten?.has(projectRel)) continue;
|
|
7018
7428
|
const next = readFileSync21(sourceFile, "utf8");
|
|
7019
|
-
const
|
|
7020
|
-
|
|
7021
|
-
if (
|
|
7022
|
-
|
|
7023
|
-
|
|
7024
|
-
|
|
7025
|
-
writeFileSync7(targetFile, next);
|
|
7026
|
-
updated++;
|
|
7429
|
+
const rec = ctx.recordOf?.(projectRel) ?? baselineRecord(ctx.baseline, `${ctx.prefix}/${file}`);
|
|
7430
|
+
const w3 = judgedUpdateWrite(targetFile, rec, next, now);
|
|
7431
|
+
if (w3.changed) updated++;
|
|
7432
|
+
if (w3.kind === "edited") backedUp.push(`${ctx.prefix}/${file}`);
|
|
7433
|
+
const d2 = displacedEntry(w3, projectRel, targetFile);
|
|
7434
|
+
if (d2 !== null) displaced.push(d2);
|
|
7027
7435
|
}
|
|
7028
|
-
return { updated, backedUp };
|
|
7436
|
+
return { updated, backedUp, displaced };
|
|
7029
7437
|
}
|
|
7030
|
-
function syncSkills(targetDir, sourceDir, baseline, now = /* @__PURE__ */ new Date(), foreignOf, outside, excludedSkill) {
|
|
7031
|
-
if (!
|
|
7032
|
-
return {
|
|
7438
|
+
function syncSkills(targetDir, sourceDir, baseline, now = /* @__PURE__ */ new Date(), foreignOf, outside, excludedSkill, opts = {}) {
|
|
7439
|
+
if (!existsSync21(targetDir) || !existsSync21(sourceDir))
|
|
7440
|
+
return {
|
|
7441
|
+
updated: 0,
|
|
7442
|
+
backedUp: [],
|
|
7443
|
+
skippedLinks: [],
|
|
7444
|
+
foreignOwned: [],
|
|
7445
|
+
pruned: [],
|
|
7446
|
+
displaced: []
|
|
7447
|
+
};
|
|
7033
7448
|
let updated = 0;
|
|
7034
7449
|
const backedUp = [];
|
|
7035
7450
|
const skippedLinks = [];
|
|
7036
7451
|
const foreignOwned = [];
|
|
7037
7452
|
const pruned = [];
|
|
7453
|
+
const displaced = [];
|
|
7038
7454
|
for (const skill of readdirSync3(sourceDir, { withFileTypes: true })) {
|
|
7039
7455
|
if (!skill.isDirectory()) continue;
|
|
7040
|
-
const targetSkill =
|
|
7456
|
+
const targetSkill = join29(targetDir, skill.name);
|
|
7041
7457
|
if (occupiedByNonDirectory(targetSkill)) {
|
|
7042
7458
|
skippedLinks.push(skill.name);
|
|
7043
7459
|
continue;
|
|
7044
7460
|
}
|
|
7045
|
-
if (!
|
|
7461
|
+
if (opts.targets !== void 0 && !opts.targets.has(skill.name)) continue;
|
|
7462
|
+
if (!existsSync21(targetSkill)) continue;
|
|
7046
7463
|
if (excludedSkill?.(skill.name)) continue;
|
|
7047
|
-
for (const rel of listFilesRecursive(
|
|
7048
|
-
const targetFile =
|
|
7464
|
+
for (const rel of listFilesRecursive(join29(sourceDir, skill.name))) {
|
|
7465
|
+
const targetFile = join29(targetSkill, rel);
|
|
7049
7466
|
const foreign = foreignOf(`${skill.name}/${rel}`);
|
|
7050
7467
|
if (foreign !== null) {
|
|
7051
7468
|
if (!foreignOwned.includes(foreign)) foreignOwned.push(foreign);
|
|
7052
7469
|
continue;
|
|
7053
7470
|
}
|
|
7054
7471
|
if (outside?.skip(targetFile)) continue;
|
|
7055
|
-
const next = readFileSync21(
|
|
7056
|
-
|
|
7057
|
-
|
|
7058
|
-
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
|
|
7062
|
-
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
if (recorded2 === void 0 || hashContent(current) !== recorded2) {
|
|
7066
|
-
backupFile(targetFile, now);
|
|
7067
|
-
backedUp.push(`${skill.name}/${rel}`);
|
|
7068
|
-
}
|
|
7069
|
-
writeFileSync7(targetFile, next);
|
|
7070
|
-
updated++;
|
|
7071
|
-
}
|
|
7072
|
-
const bundled = new Set(listFilesRecursive(join28(sourceDir, skill.name)));
|
|
7472
|
+
const next = readFileSync21(join29(sourceDir, skill.name, rel), "utf8");
|
|
7473
|
+
const projectRel = `.claude/skills/${skill.name}/${rel}`;
|
|
7474
|
+
const rec = opts.recordOf?.(projectRel) ?? baselineRecord(baseline, `${skill.name}/${rel}`);
|
|
7475
|
+
const w3 = judgedUpdateWrite(targetFile, rec, next, now);
|
|
7476
|
+
if (w3.changed) updated++;
|
|
7477
|
+
if (w3.kind === "edited") backedUp.push(`${skill.name}/${rel}`);
|
|
7478
|
+
const d2 = displacedEntry(w3, projectRel, targetFile);
|
|
7479
|
+
if (d2 !== null) displaced.push(d2);
|
|
7480
|
+
}
|
|
7481
|
+
const bundled = new Set(listFilesRecursive(join29(sourceDir, skill.name)));
|
|
7073
7482
|
for (const rel of listFilesRecursive(targetSkill)) {
|
|
7074
7483
|
if (bundled.has(rel) || basename3(rel).includes(".backup-")) continue;
|
|
7075
7484
|
if (foreignOf(`${skill.name}/${rel}`) !== null) continue;
|
|
7076
|
-
const targetFile =
|
|
7485
|
+
const targetFile = join29(targetSkill, rel);
|
|
7077
7486
|
if (outside?.skip(targetFile)) continue;
|
|
7078
|
-
const
|
|
7079
|
-
if (
|
|
7487
|
+
const rec = opts.recordOf?.(`.claude/skills/${skill.name}/${rel}`) ?? baselineRecord(baseline, `${skill.name}/${rel}`);
|
|
7488
|
+
if (rec.state !== "sha" || hashContent(readFileSync21(targetFile, "utf8")) !== rec.sha256) {
|
|
7080
7489
|
backupFile(targetFile, now);
|
|
7081
7490
|
backedUp.push(`${skill.name}/${rel}`);
|
|
7082
7491
|
}
|
|
7083
7492
|
unlinkSync2(targetFile);
|
|
7084
7493
|
pruned.push(`${skill.name}/${rel}`);
|
|
7085
|
-
for (let d2 =
|
|
7494
|
+
for (let d2 = dirname6(targetFile); d2 !== targetSkill && readdirSync3(d2).length === 0; d2 = dirname6(d2))
|
|
7086
7495
|
rmdirSync(d2);
|
|
7087
7496
|
}
|
|
7088
7497
|
}
|
|
7089
|
-
return { updated, backedUp, skippedLinks, foreignOwned, pruned };
|
|
7498
|
+
return { updated, backedUp, skippedLinks, foreignOwned, pruned, displaced };
|
|
7499
|
+
}
|
|
7500
|
+
function judgedUpdateWrite(abs, rec, next, now) {
|
|
7501
|
+
const disk = existsSync21(abs) ? readFileSync21(abs, "utf8") : null;
|
|
7502
|
+
const j4 = judge({ op: "write", kind: "harness", rec, disk, next, run: "update" });
|
|
7503
|
+
let backup;
|
|
7504
|
+
switch (j4.verdict) {
|
|
7505
|
+
case "leave":
|
|
7506
|
+
break;
|
|
7507
|
+
case "create":
|
|
7508
|
+
mkdirSync6(dirname6(abs), { recursive: true });
|
|
7509
|
+
writeFileSync8(abs, next);
|
|
7510
|
+
break;
|
|
7511
|
+
case "overwrite":
|
|
7512
|
+
writeFileSync8(abs, next);
|
|
7513
|
+
break;
|
|
7514
|
+
case "backup+overwrite":
|
|
7515
|
+
backup = backupFile(abs, now);
|
|
7516
|
+
writeFileSync8(abs, next);
|
|
7517
|
+
break;
|
|
7518
|
+
default:
|
|
7519
|
+
throw new Error(`update: '${j4.verdict}' is not a write verdict (${abs})`);
|
|
7520
|
+
}
|
|
7521
|
+
const changed = j4.verdict !== "leave";
|
|
7522
|
+
if (j4.record === "displaced") return { kind: "displacedSame", changed };
|
|
7523
|
+
if (backup === void 0) return { kind: changed ? "written" : "same", changed };
|
|
7524
|
+
return { kind: j4.record === "displaced+sha" ? "displaced" : "edited", changed, backup };
|
|
7525
|
+
}
|
|
7526
|
+
function displacedEntry(w3, projectRel, abs) {
|
|
7527
|
+
if (w3.kind === "displacedSame") return { path: projectRel, change: "displaced", notes: [] };
|
|
7528
|
+
if (w3.kind !== "displaced" || w3.backup === void 0) return null;
|
|
7529
|
+
const backupRel = `${projectRel}${w3.backup.slice(abs.length)}`;
|
|
7530
|
+
return { path: projectRel, change: "displaced", notes: [backupRel] };
|
|
7531
|
+
}
|
|
7532
|
+
function baselineRecord(baseline, key) {
|
|
7533
|
+
const sha256 = baseline.get(key);
|
|
7534
|
+
return sha256 === void 0 ? { state: "no-sha" } : { state: "sha", sha256 };
|
|
7535
|
+
}
|
|
7536
|
+
function recordedStateOf(log) {
|
|
7537
|
+
if (log === null) return void 0;
|
|
7538
|
+
const isTarget = recordedTrackTargets(log);
|
|
7539
|
+
return (projectRel) => recorded(log, projectRel, { isTarget });
|
|
7540
|
+
}
|
|
7541
|
+
function notOursUnder(displaced, prefix) {
|
|
7542
|
+
return new Set(
|
|
7543
|
+
displaced.filter((f) => f.notes.length === 0 && f.path.startsWith(prefix)).map((f) => f.path.slice(prefix.length))
|
|
7544
|
+
);
|
|
7545
|
+
}
|
|
7546
|
+
function withoutLostBackups(prior, add) {
|
|
7547
|
+
const backedUp = new Set(
|
|
7548
|
+
(prior ?? []).filter((f) => f.change === "displaced" && f.notes.length > 0).map((f) => f.path)
|
|
7549
|
+
);
|
|
7550
|
+
return add.filter((f) => f.notes.length > 0 || !backedUp.has(f.path));
|
|
7551
|
+
}
|
|
7552
|
+
function recordDisplaced(projectDir, displaced) {
|
|
7553
|
+
const log = readInstallLog(projectDir);
|
|
7554
|
+
if (!log) return;
|
|
7555
|
+
const add = withoutLostBackups(log.rootFiles, displaced);
|
|
7556
|
+
if (add.length === 0) return;
|
|
7557
|
+
try {
|
|
7558
|
+
writeInstallLog(projectDir, { ...log, rootFiles: mergeRootFiles(log.rootFiles, add) });
|
|
7559
|
+
} catch {
|
|
7560
|
+
}
|
|
7090
7561
|
}
|
|
7091
7562
|
function skillBaseline(projectDir) {
|
|
7092
7563
|
const log = readInstallLog(projectDir);
|
|
@@ -7096,15 +7567,18 @@ function policyBaseline(projectDir) {
|
|
|
7096
7567
|
const log = readInstallLog(projectDir);
|
|
7097
7568
|
return new Map((log?.policyFiles ?? []).map((f) => [f.path, f.sha256]));
|
|
7098
7569
|
}
|
|
7099
|
-
function refreshPolicyBaseline(projectDir, templatesDir, dirs, outside) {
|
|
7570
|
+
function refreshPolicyBaseline(projectDir, templatesDir, dirs, outside, notOurs = /* @__PURE__ */ new Set()) {
|
|
7100
7571
|
const log = readInstallLog(projectDir);
|
|
7101
7572
|
if (!log) return;
|
|
7102
7573
|
const excluded = excludedIds(log);
|
|
7103
7574
|
const inDirs = (path) => (dirs === void 0 || dirs.some((d2) => path.startsWith(`${d2}/`))) && !outside.has(path) && !isBaselineExcluded(`.claude/${path}`, excluded);
|
|
7104
|
-
const
|
|
7575
|
+
const rewritten = [
|
|
7105
7576
|
...(log.policyFiles ?? []).filter((f) => !inDirs(f.path)),
|
|
7106
|
-
...collectPolicyHashes(projectDir, templatesDir).filter(
|
|
7577
|
+
...collectPolicyHashes(projectDir, templatesDir).filter(
|
|
7578
|
+
(f) => inDirs(f.path) && !notOurs.has(f.path)
|
|
7579
|
+
)
|
|
7107
7580
|
];
|
|
7581
|
+
const policyFiles = [...rewritten, ...retiredClaims(log, projectDir, rewritten)];
|
|
7108
7582
|
const next = { ...log };
|
|
7109
7583
|
if (policyFiles.length > 0) next.policyFiles = policyFiles;
|
|
7110
7584
|
else delete next.policyFiles;
|
|
@@ -7118,13 +7592,16 @@ function outsidePaths(outside, prefix) {
|
|
|
7118
7592
|
outside.list().filter((o) => o.path.startsWith(prefix)).map((o) => o.path.slice(prefix.length))
|
|
7119
7593
|
);
|
|
7120
7594
|
}
|
|
7121
|
-
function refreshSkillBaseline(projectDir, templatesDir, outside) {
|
|
7595
|
+
function refreshSkillBaseline(projectDir, templatesDir, outside, targets, notOurs = /* @__PURE__ */ new Set()) {
|
|
7122
7596
|
const log = readInstallLog(projectDir);
|
|
7123
7597
|
if (!log) return;
|
|
7124
7598
|
const excludedSkill = excludedSkillOf(log);
|
|
7125
7599
|
const keepPrior = (path) => outside.has(path) || excludedSkill(path.split("/")[0] ?? "");
|
|
7600
|
+
const ours = (path) => (targets === void 0 || targets.has(path.split("/")[0] ?? "")) && !notOurs.has(path);
|
|
7126
7601
|
const skillFiles = [
|
|
7127
|
-
...collectSkillHashes(projectDir, templatesDir).filter(
|
|
7602
|
+
...collectSkillHashes(projectDir, templatesDir).filter(
|
|
7603
|
+
(f) => !keepPrior(f.path) && ours(f.path)
|
|
7604
|
+
),
|
|
7128
7605
|
...(log.skillFiles ?? []).filter((f) => keepPrior(f.path))
|
|
7129
7606
|
];
|
|
7130
7607
|
const next = { ...log };
|
|
@@ -7136,18 +7613,20 @@ function refreshSkillBaseline(projectDir, templatesDir, outside) {
|
|
|
7136
7613
|
}
|
|
7137
7614
|
}
|
|
7138
7615
|
function pruneOrphans(target, source, ext, ctx, now = /* @__PURE__ */ new Date()) {
|
|
7139
|
-
if (!
|
|
7616
|
+
if (!existsSync21(target) || !existsSync21(source)) return [];
|
|
7140
7617
|
const removed = [];
|
|
7141
7618
|
for (const file of readdirSync3(target)) {
|
|
7142
7619
|
if (!file.endsWith(ext)) continue;
|
|
7143
|
-
const sourceFile =
|
|
7144
|
-
if (!
|
|
7145
|
-
const targetFile =
|
|
7146
|
-
|
|
7620
|
+
const sourceFile = join29(source, file);
|
|
7621
|
+
if (!existsSync21(sourceFile)) {
|
|
7622
|
+
const targetFile = join29(target, file);
|
|
7623
|
+
const key = `${ctx.prefix}/${file}`;
|
|
7624
|
+
if (!ctx.baseline.has(key)) continue;
|
|
7147
7625
|
if (ctx.outside?.skip(targetFile)) continue;
|
|
7148
7626
|
try {
|
|
7149
7627
|
const current = readFileSync21(targetFile, "utf8");
|
|
7150
|
-
|
|
7628
|
+
const owned = ctx.baseline.get(key) === hashContent(current) && ctx.recordOf?.(`.claude/${key}`).state !== "no-sha";
|
|
7629
|
+
if (!owned) backupFile(targetFile, now);
|
|
7151
7630
|
unlinkSync2(targetFile);
|
|
7152
7631
|
removed.push(file);
|
|
7153
7632
|
} catch {
|
|
@@ -7157,8 +7636,8 @@ function pruneOrphans(target, source, ext, ctx, now = /* @__PURE__ */ new Date()
|
|
|
7157
7636
|
return removed;
|
|
7158
7637
|
}
|
|
7159
7638
|
function retireMcpAllowlist(projectDir, now = /* @__PURE__ */ new Date()) {
|
|
7160
|
-
const target =
|
|
7161
|
-
if (!
|
|
7639
|
+
const target = join29(projectDir, ".mcp-allowlist");
|
|
7640
|
+
if (!existsSync21(target)) return null;
|
|
7162
7641
|
try {
|
|
7163
7642
|
const backup = backupFile(target, now);
|
|
7164
7643
|
unlinkSync2(target);
|
|
@@ -7173,9 +7652,9 @@ function refreshNeutralHelpers(projectDir, templatesDir, report) {
|
|
|
7173
7652
|
for (const entry of buildManifest(spec)) {
|
|
7174
7653
|
if (entry.type !== "file" || !isCliNeutralTarget(entry.target)) continue;
|
|
7175
7654
|
if (!entry.applies(spec)) continue;
|
|
7176
|
-
const abs =
|
|
7177
|
-
const source =
|
|
7178
|
-
if (!
|
|
7655
|
+
const abs = join29(projectDir, entry.target);
|
|
7656
|
+
const source = join29(templatesDir, entry.source);
|
|
7657
|
+
if (!existsSync21(source)) continue;
|
|
7179
7658
|
let st2;
|
|
7180
7659
|
try {
|
|
7181
7660
|
st2 = lstatSync4(abs);
|
|
@@ -7197,7 +7676,7 @@ function refreshNeutralHelpers(projectDir, templatesDir, report) {
|
|
|
7197
7676
|
continue;
|
|
7198
7677
|
}
|
|
7199
7678
|
if (recorded2 === void 0 || recorded2.sha256 !== diskSha) backupFile(abs);
|
|
7200
|
-
|
|
7679
|
+
writeFileSync8(abs, freshContent, "utf8");
|
|
7201
7680
|
refreshed.set(entry.target, freshSha);
|
|
7202
7681
|
report.updated[entry.target] = (report.updated[entry.target] ?? 0) + 1;
|
|
7203
7682
|
}
|
|
@@ -7221,34 +7700,16 @@ function recordClaudeCommand(log, tracks) {
|
|
|
7221
7700
|
" "
|
|
7222
7701
|
);
|
|
7223
7702
|
}
|
|
7224
|
-
function dropHealedHookPortions(projectDir, healedFiles) {
|
|
7225
|
-
const healed = new Set(healedFiles.map((f) => f.split("/").pop() ?? f));
|
|
7226
|
-
const isHealedKey = (key) => {
|
|
7227
|
-
const at5 = key.lastIndexOf("#");
|
|
7228
|
-
return at5 !== -1 && healed.has(key.slice(at5 + 1));
|
|
7229
|
-
};
|
|
7230
|
-
const log = readInstallLog(projectDir);
|
|
7231
|
-
if (log === null) return;
|
|
7232
|
-
const portions = (log.portions ?? []).filter(
|
|
7233
|
-
(p2) => !(p2.path === join28(".claude", "settings.json") && isHealedKey(p2.key))
|
|
7234
|
-
);
|
|
7235
|
-
if (portions.length > 0) log.portions = portions;
|
|
7236
|
-
else delete log.portions;
|
|
7237
|
-
try {
|
|
7238
|
-
writeInstallLog(projectDir, log);
|
|
7239
|
-
} catch {
|
|
7240
|
-
}
|
|
7241
|
-
}
|
|
7242
7703
|
|
|
7243
7704
|
// src/installer.ts
|
|
7244
7705
|
function runInstall(ctx) {
|
|
7245
7706
|
const { harnessRoot, projectDir } = ctx;
|
|
7246
7707
|
const mode = ctx.mode ?? "fresh";
|
|
7247
|
-
const templatesDir =
|
|
7248
|
-
if (!
|
|
7708
|
+
const templatesDir = join30(harnessRoot, "templates");
|
|
7709
|
+
if (!existsSync22(templatesDir)) {
|
|
7249
7710
|
throw new Error(`Templates dir not found: ${templatesDir}`);
|
|
7250
7711
|
}
|
|
7251
|
-
const claudeDir =
|
|
7712
|
+
const claudeDir = join30(projectDir, ".claude");
|
|
7252
7713
|
const previousLog = readInstallLog(projectDir);
|
|
7253
7714
|
const legacyAnchor = mode === "update" ? null : legacyAnchorTrace(projectDir, ctx.spec);
|
|
7254
7715
|
if (mode === "update" && previousLog === null) {
|
|
@@ -7256,17 +7717,13 @@ function runInstall(ctx) {
|
|
|
7256
7717
|
`Update mode requires an install record at ${installLogPath(projectDir)} \u2014 run: agent-harness install --track <name>`
|
|
7257
7718
|
);
|
|
7258
7719
|
}
|
|
7259
|
-
if (mode === "update" && !
|
|
7720
|
+
if (mode === "update" && !existsSync22(claudeDir) && installedClis(previousLog).includes("claude")) {
|
|
7260
7721
|
throw new Error(
|
|
7261
7722
|
`Update mode found a broken install at ${projectDir} \u2014 this project installed Claude Code assets but \`.claude/\` is gone. Reinstall instead: agent-harness install --track <name>`
|
|
7262
7723
|
);
|
|
7263
7724
|
}
|
|
7264
7725
|
if (mode === "update" && previousLog !== null) {
|
|
7265
|
-
return runUpdateInstall(
|
|
7266
|
-
ctx,
|
|
7267
|
-
templatesDir,
|
|
7268
|
-
resolveUpdateBackupPath(ctx, claudeDir, previousLog)
|
|
7269
|
-
);
|
|
7726
|
+
return runUpdateInstall(ctx, templatesDir);
|
|
7270
7727
|
}
|
|
7271
7728
|
const { spec, excluded } = thisRunExclusions(
|
|
7272
7729
|
ctx.spec,
|
|
@@ -7277,8 +7734,8 @@ function runInstall(ctx) {
|
|
|
7277
7734
|
const manifestSpec = buildManifestSpec(spec);
|
|
7278
7735
|
const baselineExcluded = new Set(spec.baselineExclude ?? []);
|
|
7279
7736
|
if (spec.cli.includes("claude")) {
|
|
7280
|
-
const settingsPath =
|
|
7281
|
-
if (
|
|
7737
|
+
const settingsPath = join30(projectDir, ".claude", "settings.json");
|
|
7738
|
+
if (existsSync22(settingsPath)) {
|
|
7282
7739
|
try {
|
|
7283
7740
|
JSON.parse(readFileSync22(settingsPath, "utf8"));
|
|
7284
7741
|
} catch {
|
|
@@ -7352,6 +7809,7 @@ function runInstallStages(ctx, args) {
|
|
|
7352
7809
|
externalBackups,
|
|
7353
7810
|
externalUpdated: _externalUpdated,
|
|
7354
7811
|
externalBackedUp: _externalBackedUp,
|
|
7812
|
+
displaced: cliDisplaced = [],
|
|
7355
7813
|
externalForeignOwned,
|
|
7356
7814
|
externalOutside,
|
|
7357
7815
|
sharedFiles: cliSharedFiles,
|
|
@@ -7383,6 +7841,8 @@ function runInstallStages(ctx, args) {
|
|
|
7383
7841
|
previousExternal: previousLog?.externalFiles ?? [],
|
|
7384
7842
|
// #642 (ADR-096 D2) — CLI 집합은 누적이다: 기록된 CLI 도 `AGENTS.md` 집합에 들어간다(`--reinstall` 도 누적)
|
|
7385
7843
|
recordedClis: previousLog === null ? [] : installedClis(previousLog),
|
|
7844
|
+
// #761 B-1 — 외부 기록이 아예 없던 옛 판의 산출물은 첫 접촉이 아니라 체크섬 없는 하네스 파일이다(`.claude/` B3 와 같은 판정)
|
|
7845
|
+
owned: recordedOwner(previousLog),
|
|
7386
7846
|
// ADR-097 결정 2 — 범위 조건 없이 `--with-codex-trust` 하나로 정한다.
|
|
7387
7847
|
codexTrust: spec.options.withCodexTrust,
|
|
7388
7848
|
// #551 R2 — `.codex/config.toml` · `opencode.json` · 첫 접촉 `AGENTS.md` 의 몫도 `.mcp.json` 과 같은 왕복을
|
|
@@ -7433,8 +7893,6 @@ function runInstallStages(ctx, args) {
|
|
|
7433
7893
|
filesCopied: base.filesCopied,
|
|
7434
7894
|
dirsCopied: base.dirsCopied,
|
|
7435
7895
|
skipped: base.skipped,
|
|
7436
|
-
// #551 PR-3 — install · `--reinstall` 은 폴더를 옮기지 않는다. 백업은 파일마다 `backups`/`judged` 에 있다.
|
|
7437
|
-
backup: null,
|
|
7438
7896
|
installedTracks: [...spec.tracks].sort(),
|
|
7439
7897
|
mcpServers: mcp,
|
|
7440
7898
|
...cliTransforms,
|
|
@@ -7482,7 +7940,7 @@ function runInstallStages(ctx, args) {
|
|
|
7482
7940
|
...outOfTrack.removed.length + outOfTrack.backedUp.length + outOfTrack.kept.length > 0 ? { outOfTrack } : {}
|
|
7483
7941
|
};
|
|
7484
7942
|
ctx.onProgress?.({ type: "baseline-complete", baseline });
|
|
7485
|
-
const external = runExternalPhase(ctx);
|
|
7943
|
+
const { report: external, backups: externalSkillBackups } = runExternalPhase(ctx, previousLog);
|
|
7486
7944
|
writeInstallLogSafe(
|
|
7487
7945
|
ctx,
|
|
7488
7946
|
// 링크 본문의 기준선도 같은 필드다 — 같은 경로면 뒤(이번에 쓴 값)가 이긴다.
|
|
@@ -7495,11 +7953,20 @@ function runInstallStages(ctx, args) {
|
|
|
7495
7953
|
...collectRootFiles(envFiles, ciScaffold, ledger.shared),
|
|
7496
7954
|
// ADR-099 R2 — 파일째 사라진 첫 접촉 블록 모델 `AGENTS.md` 를 블록만 담아 다시 만들었다(기준선 밖) — uninstall 이
|
|
7497
7955
|
// 블록을 걷고 남는 것이 없으면 파일째 지운다
|
|
7498
|
-
...cliSharedFiles.filter((f) => f.createdAsRoot === true).map((f) => ({ path: f.path, change: "created", notes: ["\uD558\uB124\uC2A4 \uBAAB\uB9CC(\uBE14\uB85D)"] }))
|
|
7956
|
+
...cliSharedFiles.filter((f) => f.createdAsRoot === true).map((f) => ({ path: f.path, change: "created", notes: ["\uD558\uB124\uC2A4 \uBAAB\uB9CC(\uBE14\uB85D)"] })),
|
|
7957
|
+
// #761 — 외부 변환 · 링크 본문이 첫 접촉으로 비켜 둔 설치자 파일. 하네스가 떠날 때 되돌리는 근거다
|
|
7958
|
+
...cliDisplaced,
|
|
7959
|
+
...linked.displaced
|
|
7499
7960
|
],
|
|
7500
7961
|
cliTransforms.codexOptIn
|
|
7501
7962
|
);
|
|
7502
|
-
return {
|
|
7963
|
+
return {
|
|
7964
|
+
...baseline,
|
|
7965
|
+
// #770 HIGH-1 — 외부 스킬을 다시 받으며 남긴 백업은 Phase 1 행 뒤에 생긴다 — 보고에도 싣는다
|
|
7966
|
+
...externalSkillBackups.length > 0 ? { backups: [...baseline.backups ?? [], ...externalSkillBackups] } : {},
|
|
7967
|
+
external,
|
|
7968
|
+
staleHookRefs: []
|
|
7969
|
+
};
|
|
7503
7970
|
}
|
|
7504
7971
|
function legacyAnchorTrace(projectDir, spec) {
|
|
7505
7972
|
if (!spec.cli.includes("claude")) return null;
|
|
@@ -7507,12 +7974,7 @@ function legacyAnchorTrace(projectDir, spec) {
|
|
|
7507
7974
|
if (state !== "none" || traces.length < 2) return null;
|
|
7508
7975
|
return traces.some((t) => t.path === LEGACY_ANCHOR_FILE) ? LEGACY_ANCHOR_FILE : null;
|
|
7509
7976
|
}
|
|
7510
|
-
function
|
|
7511
|
-
const claudeUntouched = !installedClis(previousLog).includes("claude");
|
|
7512
|
-
if (!(ctx.backup ?? !claudeUntouched)) return null;
|
|
7513
|
-
return copyBackupDir(claudeDir);
|
|
7514
|
-
}
|
|
7515
|
-
function runUpdateInstall(ctx, templatesDir, backupPath) {
|
|
7977
|
+
function runUpdateInstall(ctx, templatesDir) {
|
|
7516
7978
|
const updateReport = runUpdateMode(
|
|
7517
7979
|
ctx.projectDir,
|
|
7518
7980
|
templatesDir,
|
|
@@ -7527,7 +7989,6 @@ function runUpdateInstall(ctx, templatesDir, backupPath) {
|
|
|
7527
7989
|
baselineExcluded: [],
|
|
7528
7990
|
baselineExcludedOnDisk: [],
|
|
7529
7991
|
baselineForeignOwned: [],
|
|
7530
|
-
backup: backupPath,
|
|
7531
7992
|
installedTracks: [...ctx.spec.tracks].sort(),
|
|
7532
7993
|
mcpServers: [],
|
|
7533
7994
|
codex: null,
|
|
@@ -7557,7 +8018,7 @@ function releasedThisRun(args) {
|
|
|
7557
8018
|
}
|
|
7558
8019
|
if (id.startsWith(BASELINE_PREFIX)) {
|
|
7559
8020
|
const again2 = manifest.some(
|
|
7560
|
-
(e2) => e2.applies(manifestSpec) && classifyBaselineTarget(e2.target)?.id === id &&
|
|
8021
|
+
(e2) => e2.applies(manifestSpec) && classifyBaselineTarget(e2.target)?.id === id && existsSync22(join30(projectDir, e2.target))
|
|
7561
8022
|
);
|
|
7562
8023
|
return { id, again: again2, tail: "the harness manages it again" };
|
|
7563
8024
|
}
|
|
@@ -7566,7 +8027,7 @@ function releasedThisRun(args) {
|
|
|
7566
8027
|
options: spec.options,
|
|
7567
8028
|
...spec.userOverride ? { userOverride: spec.userOverride } : {}
|
|
7568
8029
|
});
|
|
7569
|
-
const recorded2 = (previousLog?.assets.some((a) => a.id === id) ?? false) || [".claude/skills", ".agents/skills"].some((d2) =>
|
|
8030
|
+
const recorded2 = (previousLog?.assets.some((a) => a.id === id) ?? false) || [".claude/skills", ".agents/skills"].some((d2) => existsSync22(join30(projectDir, d2, id)));
|
|
7570
8031
|
return {
|
|
7571
8032
|
id,
|
|
7572
8033
|
again,
|
|
@@ -7578,10 +8039,10 @@ function pendingKeyExcludes(projectDir, keyExclude, touched) {
|
|
|
7578
8039
|
const done = new Set(touched);
|
|
7579
8040
|
return keyExclude.flatMap((id) => {
|
|
7580
8041
|
const path = sharedPathOfKeyId(id);
|
|
7581
|
-
if (path === null || done.has(path) || !
|
|
8042
|
+
if (path === null || done.has(path) || !existsSync22(join30(projectDir, path))) return [];
|
|
7582
8043
|
const name = adapterFor(path);
|
|
7583
8044
|
const keys = [...excludedKeys(path, [id])];
|
|
7584
|
-
const present2 = name === null ? null : ADAPTERS[name].read(readFileSync22(
|
|
8045
|
+
const present2 = name === null ? null : ADAPTERS[name].read(readFileSync22(join30(projectDir, path), "utf8"), keys, projectDir);
|
|
7585
8046
|
return present2 !== null && present2.size === 0 ? [] : [{ id, path }];
|
|
7586
8047
|
});
|
|
7587
8048
|
}
|
|
@@ -7625,8 +8086,8 @@ function installCliNeutralAssets(manifestSpec, templatesDir, baselineExcluded, w
|
|
|
7625
8086
|
continue;
|
|
7626
8087
|
}
|
|
7627
8088
|
if (!isCliNeutralTarget(entry.target)) continue;
|
|
7628
|
-
const source =
|
|
7629
|
-
if (!
|
|
8089
|
+
const source = join30(templatesDir, entry.source);
|
|
8090
|
+
if (!existsSync22(source)) {
|
|
7630
8091
|
result.skipped += 1;
|
|
7631
8092
|
continue;
|
|
7632
8093
|
}
|
|
@@ -7646,14 +8107,14 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
|
|
|
7646
8107
|
if (!entry.applies(manifestSpec)) {
|
|
7647
8108
|
continue;
|
|
7648
8109
|
}
|
|
7649
|
-
const target =
|
|
8110
|
+
const target = join30(projectDir, entry.target);
|
|
7650
8111
|
if (isBaselineExcluded(entry.target, baselineExcluded)) {
|
|
7651
8112
|
result.excluded.push(entry.target);
|
|
7652
|
-
if (
|
|
8113
|
+
if (existsSync22(target)) result.excludedOnDisk.push(entry.target);
|
|
7653
8114
|
continue;
|
|
7654
8115
|
}
|
|
7655
|
-
const source =
|
|
7656
|
-
if (!
|
|
8116
|
+
const source = join30(templatesDir, entry.source);
|
|
8117
|
+
if (!existsSync22(source)) {
|
|
7657
8118
|
result.skipped += 1;
|
|
7658
8119
|
continue;
|
|
7659
8120
|
}
|
|
@@ -7683,19 +8144,19 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
|
|
|
7683
8144
|
if (!result.foreignOwned.includes(slot)) result.foreignOwned.push(slot);
|
|
7684
8145
|
continue;
|
|
7685
8146
|
}
|
|
7686
|
-
if (writer.skipOutside(
|
|
8147
|
+
if (writer.skipOutside(join30(projectDir, entry.target, rel))) {
|
|
7687
8148
|
leftOutside += 1;
|
|
7688
8149
|
continue;
|
|
7689
8150
|
}
|
|
7690
|
-
writer.harness(`${entry.target}/${rel}`, { source:
|
|
8151
|
+
writer.harness(`${entry.target}/${rel}`, { source: join30(source, rel) });
|
|
7691
8152
|
}
|
|
7692
8153
|
if (leftOutside > 0) continue;
|
|
7693
8154
|
result.dirsCopied += 1;
|
|
7694
8155
|
}
|
|
7695
8156
|
accumulateCategory(result.categories, entry);
|
|
7696
8157
|
}
|
|
7697
|
-
const hookDir =
|
|
7698
|
-
if (
|
|
8158
|
+
const hookDir = join30(projectDir, ".claude/hooks");
|
|
8159
|
+
if (existsSync22(hookDir)) {
|
|
7699
8160
|
chmodHooksSync(hookDir, projectDir);
|
|
7700
8161
|
}
|
|
7701
8162
|
writer.harness(
|
|
@@ -7711,12 +8172,12 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
|
|
|
7711
8172
|
if (settingsSource !== null) {
|
|
7712
8173
|
writeSettingsPortion(writer, settingsSource, projectDir, previousLog, (script) => {
|
|
7713
8174
|
const target = `${HOOKS_PREFIX}${script}`;
|
|
7714
|
-
return manifest.some((e2) => e2.target === target && e2.applies(manifestSpec)) && !isBaselineExcluded(target, baselineExcluded) &&
|
|
8175
|
+
return manifest.some((e2) => e2.target === target && e2.applies(manifestSpec)) && !isBaselineExcluded(target, baselineExcluded) && existsSync22(join30(projectDir, target));
|
|
7715
8176
|
});
|
|
7716
8177
|
result.keptHookRefs.push(
|
|
7717
8178
|
...findStaleHookRefs(
|
|
7718
|
-
|
|
7719
|
-
|
|
8179
|
+
join30(projectDir, ".claude", "settings.json"),
|
|
8180
|
+
join30(projectDir, ".claude")
|
|
7720
8181
|
)
|
|
7721
8182
|
);
|
|
7722
8183
|
}
|
|
@@ -7726,7 +8187,7 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
|
|
|
7726
8187
|
// ADR-085 · update(`upsertRootImport`)와 같은 판정 — 실제로 깔린 스킬만 안내한다. 밖 링크라 쓰지 않은
|
|
7727
8188
|
// 스킬(ADR-098)을 "installed" 라 적으면 다음 update 가 그 절을 걷어 내 두 동작이 서로 반대로 쓴다.
|
|
7728
8189
|
(manifestSpec.selectedInternalSkills ?? []).filter(
|
|
7729
|
-
(id) =>
|
|
8190
|
+
(id) => existsSync22(join30(projectDir, ".claude", "skills", id))
|
|
7730
8191
|
),
|
|
7731
8192
|
harnessRoot,
|
|
7732
8193
|
writer
|
|
@@ -7765,9 +8226,9 @@ function writeEnvironmentFiles(writer, envExampleCreated, previousLog) {
|
|
|
7765
8226
|
gitignoreNpxSkillsAdded: res.added.filter((line) => line !== ".env")
|
|
7766
8227
|
};
|
|
7767
8228
|
}
|
|
7768
|
-
function runExternalPhase(ctx) {
|
|
8229
|
+
function runExternalPhase(ctx, previousLog) {
|
|
7769
8230
|
if (ctx.runExternal === null) {
|
|
7770
|
-
return null;
|
|
8231
|
+
return { report: null, backups: [] };
|
|
7771
8232
|
}
|
|
7772
8233
|
const { projectDir, spec } = ctx;
|
|
7773
8234
|
const runExt = ctx.runExternal ?? runExternalInstall;
|
|
@@ -7788,17 +8249,24 @@ function runExternalPhase(ctx) {
|
|
|
7788
8249
|
options: spec.options,
|
|
7789
8250
|
...spec.userOverride ? { userOverride: spec.userOverride } : {}
|
|
7790
8251
|
};
|
|
7791
|
-
const
|
|
7792
|
-
|
|
7793
|
-
|
|
7794
|
-
|
|
7795
|
-
|
|
8252
|
+
const { targets } = selectExternalTargets(EXTERNAL_ASSETS, { ...filterCtx, cli: spec.cli });
|
|
8253
|
+
ctx.onProgress?.({ type: "external-start", assetCount: targets.length });
|
|
8254
|
+
const skillIds = new Set(targets.filter((a) => a.method.kind === "skill").map((a) => a.id));
|
|
8255
|
+
const settle = holdSkillSlots(
|
|
8256
|
+
projectDir,
|
|
8257
|
+
(previousLog?.assets ?? []).filter((a) => a.method === "skill" && skillIds.has(a.id))
|
|
8258
|
+
);
|
|
7796
8259
|
const external = runExt(
|
|
7797
8260
|
{ ...filterCtx, cli: spec.cli, projectDir, ...spec.scope ? { scope: spec.scope } : {} },
|
|
7798
8261
|
externalDeps
|
|
7799
8262
|
);
|
|
7800
|
-
|
|
7801
|
-
|
|
8263
|
+
const backups = settle();
|
|
8264
|
+
ctx.onProgress?.({
|
|
8265
|
+
type: "external-complete",
|
|
8266
|
+
report: external,
|
|
8267
|
+
...backups.length > 0 ? { backups } : {}
|
|
8268
|
+
});
|
|
8269
|
+
return { report: external, backups };
|
|
7802
8270
|
}
|
|
7803
8271
|
var InstallInterruptedError = class extends Error {
|
|
7804
8272
|
constructor(message, written, record, backups = [], hadInstall = false) {
|
|
@@ -7819,11 +8287,16 @@ function createInterruptJournal() {
|
|
|
7819
8287
|
const shared2 = /* @__PURE__ */ new Map();
|
|
7820
8288
|
const completed = /* @__PURE__ */ new Set();
|
|
7821
8289
|
const backups = [];
|
|
8290
|
+
const displacedFiles = /* @__PURE__ */ new Map();
|
|
7822
8291
|
const journal = {
|
|
7823
8292
|
files,
|
|
7824
8293
|
shared: shared2,
|
|
7825
8294
|
completed,
|
|
7826
8295
|
backups,
|
|
8296
|
+
displacedFiles,
|
|
8297
|
+
displaced: (f) => {
|
|
8298
|
+
if (!displacedFiles.has(f.path)) displacedFiles.set(f.path, f);
|
|
8299
|
+
},
|
|
7827
8300
|
codexOptIn: null,
|
|
7828
8301
|
trust: (report) => {
|
|
7829
8302
|
journal.codexOptIn = report;
|
|
@@ -7875,7 +8348,7 @@ function recordInterruptedInstall(ctx, error, run) {
|
|
|
7875
8348
|
ledger.anchor,
|
|
7876
8349
|
previousLog,
|
|
7877
8350
|
false,
|
|
7878
|
-
[...rootFiles, ...ledger.rootFiles],
|
|
8351
|
+
[...rootFiles, ...ledger.rootFiles, ...journal.displacedFiles.values()],
|
|
7879
8352
|
codexTrust
|
|
7880
8353
|
);
|
|
7881
8354
|
const path = writeInstallLog(
|
|
@@ -7998,9 +8471,9 @@ function accumulateCategory(cats, entry) {
|
|
|
7998
8471
|
}
|
|
7999
8472
|
}
|
|
8000
8473
|
function writeRootClaudeMd(projectDir, tracks, continuousSkills, harnessRoot, writer) {
|
|
8001
|
-
const target =
|
|
8474
|
+
const target = join30(projectDir, "CLAUDE.md");
|
|
8002
8475
|
if (writer.skipOutside(target)) return null;
|
|
8003
|
-
const existing =
|
|
8476
|
+
const existing = existsSync22(target) ? readFileSync22(target, "utf-8") : null;
|
|
8004
8477
|
const seeded = existing === null ? seedRootClaudeProjectContext(projectDir, harnessRoot) : null;
|
|
8005
8478
|
const content = upsertHarnessImport(existing, {
|
|
8006
8479
|
projectName: basename4(projectDir),
|
|
@@ -8010,7 +8483,7 @@ function writeRootClaudeMd(projectDir, tracks, continuousSkills, harnessRoot, wr
|
|
|
8010
8483
|
});
|
|
8011
8484
|
if (content !== existing) {
|
|
8012
8485
|
if (existing !== null) backupIfLossyUtf8(target);
|
|
8013
|
-
|
|
8486
|
+
writeFileSync9(target, content);
|
|
8014
8487
|
}
|
|
8015
8488
|
return {
|
|
8016
8489
|
created: existing === null,
|
|
@@ -8034,8 +8507,8 @@ function listHookFiles(hookDir) {
|
|
|
8034
8507
|
// src/commands/install-render.ts
|
|
8035
8508
|
init_esm_shims();
|
|
8036
8509
|
import { homedir as homedir3 } from "os";
|
|
8037
|
-
import { existsSync as
|
|
8038
|
-
import { join as
|
|
8510
|
+
import { existsSync as existsSync24, readdirSync as readdirSync5, readFileSync as readFileSync24 } from "fs";
|
|
8511
|
+
import { join as join32, relative as relative4 } from "path";
|
|
8039
8512
|
|
|
8040
8513
|
// src/preset-recommend.ts
|
|
8041
8514
|
init_esm_shims();
|
|
@@ -8071,10 +8544,10 @@ function groupAssetsByCategory(assetIds) {
|
|
|
8071
8544
|
|
|
8072
8545
|
// src/resident-entries.ts
|
|
8073
8546
|
init_esm_shims();
|
|
8074
|
-
import { existsSync as
|
|
8075
|
-
import { basename as basename5, join as
|
|
8547
|
+
import { existsSync as existsSync23, readFileSync as readFileSync23 } from "fs";
|
|
8548
|
+
import { basename as basename5, join as join31 } from "path";
|
|
8076
8549
|
function residentEntries(spec) {
|
|
8077
|
-
const skillThere = (id) => INTERNAL_BUNDLED_SKILL_IDS.includes(id) && [".claude/skills", ".agents/skills"].some((d2) =>
|
|
8550
|
+
const skillThere = (id) => INTERNAL_BUNDLED_SKILL_IDS.includes(id) && [".claude/skills", ".agents/skills"].some((d2) => existsSync23(join31(spec.projectDir, d2, id)));
|
|
8078
8551
|
const forceExclude = (spec.userOverride?.forceExclude ?? []).filter((id) => !skillThere(id));
|
|
8079
8552
|
const assetSpec = buildManifestSpec(
|
|
8080
8553
|
spec.userOverride ? { ...spec, userOverride: { ...spec.userOverride, forceExclude } } : spec
|
|
@@ -8100,7 +8573,7 @@ function residentEntries(spec) {
|
|
|
8100
8573
|
function residentCostFor(spec, entries = residentEntries(spec), contextFiles) {
|
|
8101
8574
|
if (contextFiles !== void 0) return residentCost(entries, void 0, spec.cli, contextFiles);
|
|
8102
8575
|
const cost = residentCost(entries, void 0, spec.cli, plannedClaudeContext(spec));
|
|
8103
|
-
if (anchorSurface(spec.cli).scaffold === "agents-md" &&
|
|
8576
|
+
if (anchorSurface(spec.cli).scaffold === "agents-md" && existsSync23(join31(spec.projectDir, "AGENTS.md"))) {
|
|
8104
8577
|
return makeResidentCost({
|
|
8105
8578
|
...cost,
|
|
8106
8579
|
contextNote: "planned, not counting text already in AGENTS.md"
|
|
@@ -8111,8 +8584,8 @@ function residentCostFor(spec, entries = residentEntries(spec), contextFiles) {
|
|
|
8111
8584
|
function plannedClaudeContext(spec) {
|
|
8112
8585
|
if (!spec.cli.includes("claude")) return void 0;
|
|
8113
8586
|
const root = resolveBundleRoot();
|
|
8114
|
-
const rootClaude =
|
|
8115
|
-
const existing =
|
|
8587
|
+
const rootClaude = join31(spec.projectDir, "CLAUDE.md");
|
|
8588
|
+
const existing = existsSync23(rootClaude) ? readFileSync23(rootClaude, "utf-8") : null;
|
|
8116
8589
|
const seeded = existing === null ? seedRootClaudeProjectContext(spec.projectDir, root) : null;
|
|
8117
8590
|
if (existing === null && seeded === null) return void 0;
|
|
8118
8591
|
const manifestSpec = buildManifestSpec(spec);
|
|
@@ -8122,14 +8595,14 @@ function plannedClaudeContext(spec) {
|
|
|
8122
8595
|
continuousSkills: manifestSpec.selectedInternalSkills ?? [],
|
|
8123
8596
|
...seeded === null ? {} : { projectContext: seeded }
|
|
8124
8597
|
});
|
|
8125
|
-
return [
|
|
8598
|
+
return [join31(root, "templates", "CLAUDE.md"), { text }];
|
|
8126
8599
|
}
|
|
8127
8600
|
function onDiskTargets(spec, assetSpec) {
|
|
8128
8601
|
const out = /* @__PURE__ */ new Set();
|
|
8129
8602
|
for (const e2 of buildManifest(assetSpec)) {
|
|
8130
8603
|
if (!e2.applies(assetSpec)) continue;
|
|
8131
8604
|
const t = classifyBaselineTarget(e2.target);
|
|
8132
|
-
if (t !== null &&
|
|
8605
|
+
if (t !== null && existsSync23(join31(spec.projectDir, e2.target))) out.add(t.id);
|
|
8133
8606
|
}
|
|
8134
8607
|
return out;
|
|
8135
8608
|
}
|
|
@@ -8191,6 +8664,7 @@ function createInstallRenderer(log, spec, verbose) {
|
|
|
8191
8664
|
log("");
|
|
8192
8665
|
phase2HeaderPrinted = true;
|
|
8193
8666
|
} else if (event.type === "external-complete") {
|
|
8667
|
+
for (const b2 of event.backups ?? []) log(assetRow("success", "backup", shortenPath(b2)));
|
|
8194
8668
|
const excluded = event.report.excludedByCli;
|
|
8195
8669
|
if (excluded.length > 0) {
|
|
8196
8670
|
if (!phase2HeaderPrinted) {
|
|
@@ -8239,10 +8713,6 @@ function renderUpdateSummary(log, spec, report) {
|
|
|
8239
8713
|
log("");
|
|
8240
8714
|
log(infoRow("STATUS", c.green("Update complete")));
|
|
8241
8715
|
log(infoRow("MODE", "update"));
|
|
8242
|
-
if (report.backup) {
|
|
8243
|
-
log(infoRow("BACKUP", shortenPath(report.backup)));
|
|
8244
|
-
log(infoRow("ROLLBACK", `rm -rf .claude && mv ${shellQuotePath(report.backup)} .claude`));
|
|
8245
|
-
}
|
|
8246
8716
|
const cost = formatResidentCostLine(
|
|
8247
8717
|
rulesInsideAgentsMd(
|
|
8248
8718
|
spec,
|
|
@@ -8268,7 +8738,8 @@ function renderUpdateSummary(log, spec, report) {
|
|
|
8268
8738
|
`${backups.length} file(s) ${how}, as *.backup-<time>${unmeasuredPart} \xB7 list: .uzys-agent-harness/update-backups.json`
|
|
8269
8739
|
)
|
|
8270
8740
|
);
|
|
8271
|
-
|
|
8741
|
+
const displacedN = report.updateMode?.displaced?.length ?? 0;
|
|
8742
|
+
if (removedBefore + displacedN < backups.length)
|
|
8272
8743
|
log(
|
|
8273
8744
|
infoRow(
|
|
8274
8745
|
"NEXT",
|
|
@@ -8283,23 +8754,23 @@ function residentEntriesOnDisk(spec) {
|
|
|
8283
8754
|
const hasClaude = spec.cli.includes("claude");
|
|
8284
8755
|
const surface = anchorSurface(spec.cli).scaffold;
|
|
8285
8756
|
const mdFiles = (dir, kind, skip) => {
|
|
8286
|
-
const abs =
|
|
8287
|
-
return
|
|
8757
|
+
const abs = join32(spec.projectDir, dir);
|
|
8758
|
+
return existsSync24(abs) ? readdirSync5(abs, { withFileTypes: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".md") && e2.name !== skip).map((e2) => ({
|
|
8288
8759
|
source: `${kind}/${e2.name}`,
|
|
8289
8760
|
// `target` 은 `residentCost` 의 분류 키다 — 실제 자리는 `file` 이 말한다.
|
|
8290
8761
|
target: `.claude/${kind}/${e2.name}`,
|
|
8291
8762
|
// 배포판이 아니라 **이 프로젝트의 파일**을 잰다 — 사용자가 고친 본문도,
|
|
8292
8763
|
// 배포판에 더는 없는 파일도 실제로 상주하는 것은 이쪽이다.
|
|
8293
|
-
file:
|
|
8764
|
+
file: join32(abs, e2.name)
|
|
8294
8765
|
})) : [];
|
|
8295
8766
|
};
|
|
8296
|
-
const rules = surface === "claude" ? mdFiles(
|
|
8297
|
-
const slot = hasClaude ?
|
|
8767
|
+
const rules = surface === "claude" ? mdFiles(join32(".claude", "rules"), "rules") : rulesInline(spec.cli) ? planned.filter((e2) => e2.target.startsWith(".claude/rules/")) : mdFiles(join32(".agents", "rules"), "rules", "uzys-harness.md");
|
|
8768
|
+
const slot = hasClaude ? join32(".claude", "skills") : join32(".agents", "skills");
|
|
8298
8769
|
const skills = planned.filter((e2) => e2.target.startsWith(".claude/skills/")).map((e2) => ({
|
|
8299
8770
|
...e2,
|
|
8300
|
-
file:
|
|
8301
|
-
})).filter((e2) =>
|
|
8302
|
-
const agents = hasClaude ? mdFiles(
|
|
8771
|
+
file: join32(spec.projectDir, slot, e2.target.slice(".claude/skills/".length))
|
|
8772
|
+
})).filter((e2) => existsSync24(e2.file));
|
|
8773
|
+
const agents = hasClaude ? mdFiles(join32(".claude", "agents"), "agents") : [];
|
|
8303
8774
|
return [
|
|
8304
8775
|
...planned.filter(
|
|
8305
8776
|
(e2) => !e2.target.startsWith(".claude/rules/") && !e2.target.startsWith(".claude/agents/") && !e2.target.startsWith(".claude/skills/")
|
|
@@ -8311,8 +8782,8 @@ function residentEntriesOnDisk(spec) {
|
|
|
8311
8782
|
}
|
|
8312
8783
|
function contextFilesOnDisk(spec) {
|
|
8313
8784
|
const surface = anchorSurface(spec.cli).scaffold;
|
|
8314
|
-
const files = surface === "claude" ? [HARNESS_ANCHOR_FILE, "CLAUDE.md",
|
|
8315
|
-
return files.map((f) =>
|
|
8785
|
+
const files = surface === "claude" ? [HARNESS_ANCHOR_FILE, "CLAUDE.md", join32(".claude", "CLAUDE.md")] : ["AGENTS.md"];
|
|
8786
|
+
return files.map((f) => join32(spec.projectDir, f));
|
|
8316
8787
|
}
|
|
8317
8788
|
function rulesInsideAgentsMd(spec, cost) {
|
|
8318
8789
|
if (anchorSurface(spec.cli).scaffold !== "agents-md" || !rulesInline(spec.cli)) return cost;
|
|
@@ -8566,9 +9037,9 @@ function renderFinalSummary(log, spec, report, fromWizard) {
|
|
|
8566
9037
|
const codexTrusted = report.codexOptIn?.trustEntry.status === "registered" || report.codexOptIn?.trustEntry.status === "already-present";
|
|
8567
9038
|
const alreadyTrustedGlobally = (() => {
|
|
8568
9039
|
try {
|
|
8569
|
-
const home = process.env.CODEX_HOME ??
|
|
8570
|
-
const configPath =
|
|
8571
|
-
if (!
|
|
9040
|
+
const home = process.env.CODEX_HOME ?? join32(homedir3(), ".codex");
|
|
9041
|
+
const configPath = join32(home, "config.toml");
|
|
9042
|
+
if (!existsSync24(configPath)) return false;
|
|
8572
9043
|
return hasTrustEntry(readFileSync24(configPath, "utf8"), spec.projectDir);
|
|
8573
9044
|
} catch {
|
|
8574
9045
|
return false;
|
|
@@ -8594,7 +9065,7 @@ function renderFinalSummary(log, spec, report, fromWizard) {
|
|
|
8594
9065
|
const remote = (report.shared ?? []).find((f) => f.path === ".mcp.json")?.remote ?? [];
|
|
8595
9066
|
for (const line of remoteMcpNextLines(remote, spec.cli)) log(`${" ".repeat(16)} ${line}`);
|
|
8596
9067
|
const scaffoldFiles = scaffoldFilesForCli(spec.cli).filter(
|
|
8597
|
-
(f) => f === "CLAUDE.md" && report.rootClaudeMd?.created === true || hasFillPrompt(
|
|
9068
|
+
(f) => f === "CLAUDE.md" && report.rootClaudeMd?.created === true || hasFillPrompt(join32(spec.projectDir, f))
|
|
8598
9069
|
);
|
|
8599
9070
|
if (scaffoldFiles.length > 0) {
|
|
8600
9071
|
const auditInstalled = buildManifestSpec(spec).selectedInternalSkills?.includes("audit-harness-fit");
|
|
@@ -8664,12 +9135,25 @@ function linkedSkillRows(log, updated, notOurs) {
|
|
|
8664
9135
|
);
|
|
8665
9136
|
}
|
|
8666
9137
|
}
|
|
9138
|
+
function hadFileRow(log, area, paths) {
|
|
9139
|
+
if (paths.length === 0) return;
|
|
9140
|
+
log(
|
|
9141
|
+
assetRow(
|
|
9142
|
+
"skip",
|
|
9143
|
+
`${area} had files with these names`,
|
|
9144
|
+
`${paths.length} backed up as *.backup-<time> \u2014 yours, put back on uninstall \xB7 ${paths.join(", ")}`
|
|
9145
|
+
)
|
|
9146
|
+
);
|
|
9147
|
+
}
|
|
8667
9148
|
function renderPhase1Rows(log, baseline, verbose = false) {
|
|
8668
9149
|
if (baseline.updateMode) {
|
|
8669
9150
|
const noChecksum = new Set(baseline.updateMode.noChecksum ?? []);
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
|
|
9151
|
+
const displaced = baseline.updateMode.displaced ?? [];
|
|
9152
|
+
const displacedSkills = displaced.filter((p2) => p2.startsWith(".claude/skills/"));
|
|
9153
|
+
const displacedPolicy = displaced.filter(
|
|
9154
|
+
(p2) => p2.startsWith(".claude/") && !p2.startsWith(".claude/skills/")
|
|
9155
|
+
);
|
|
9156
|
+
const displacedExternal = displaced.filter((p2) => !p2.startsWith(".claude/"));
|
|
8673
9157
|
for (const [dir, count] of Object.entries(baseline.updateMode.updated)) {
|
|
8674
9158
|
if (count > 0) log(assetRow("success", dir, `${count} files updated`));
|
|
8675
9159
|
}
|
|
@@ -8803,6 +9287,7 @@ function renderPhase1Rows(log, baseline, verbose = false) {
|
|
|
8803
9287
|
)
|
|
8804
9288
|
);
|
|
8805
9289
|
}
|
|
9290
|
+
hadFileRow(log, ".claude/skills", displacedSkills);
|
|
8806
9291
|
if (baseline.updateMode.skillsPruned.length > 0) {
|
|
8807
9292
|
log(
|
|
8808
9293
|
assetRow(
|
|
@@ -8886,6 +9371,7 @@ function renderPhase1Rows(log, baseline, verbose = false) {
|
|
|
8886
9371
|
)
|
|
8887
9372
|
);
|
|
8888
9373
|
}
|
|
9374
|
+
hadFileRow(log, "policy files", displacedPolicy);
|
|
8889
9375
|
const unmeasured = [...noChecksum].filter((p2) => p2 !== ".mcp-allowlist");
|
|
8890
9376
|
if (unmeasured.length > 0) {
|
|
8891
9377
|
log(
|
|
@@ -8896,13 +9382,23 @@ function renderPhase1Rows(log, baseline, verbose = false) {
|
|
|
8896
9382
|
)
|
|
8897
9383
|
);
|
|
8898
9384
|
}
|
|
9385
|
+
const retiredRefs = baseline.updateMode.retiredHookRefsRemoved ?? [];
|
|
9386
|
+
if (retiredRefs.length > 0) {
|
|
9387
|
+
log(
|
|
9388
|
+
assetRow(
|
|
9389
|
+
"success",
|
|
9390
|
+
"settings.json retired hook refs",
|
|
9391
|
+
`${retiredRefs.length} removed (harness wiring from an earlier release; script gone: ${retiredRefs.join(", ")})`
|
|
9392
|
+
)
|
|
9393
|
+
);
|
|
9394
|
+
}
|
|
8899
9395
|
const staleRefs = baseline.updateMode.staleHookRefs;
|
|
8900
9396
|
if (staleRefs.length > 0) {
|
|
8901
9397
|
log(
|
|
8902
9398
|
assetRow(
|
|
8903
9399
|
"skip",
|
|
8904
9400
|
"settings.json stale hook refs",
|
|
8905
|
-
`${staleRefs.length}
|
|
9401
|
+
`${staleRefs.length} left (yours \u2014 not in the install record; script missing: ${staleRefs.join(", ")})`
|
|
8906
9402
|
)
|
|
8907
9403
|
);
|
|
8908
9404
|
}
|
|
@@ -8925,6 +9421,17 @@ function renderPhase1Rows(log, baseline, verbose = false) {
|
|
|
8925
9421
|
)
|
|
8926
9422
|
);
|
|
8927
9423
|
}
|
|
9424
|
+
hadFileRow(log, "external CLI files", displacedExternal);
|
|
9425
|
+
const displacedSame = baseline.updateMode.displacedSame ?? [];
|
|
9426
|
+
if (displacedSame.length > 0) {
|
|
9427
|
+
log(
|
|
9428
|
+
assetRow(
|
|
9429
|
+
"skip",
|
|
9430
|
+
"same as the harness version",
|
|
9431
|
+
`${displacedSame.length} ${LINES.sameAsHarness} \xB7 ${displacedSame.join(", ")}`
|
|
9432
|
+
)
|
|
9433
|
+
);
|
|
9434
|
+
}
|
|
8928
9435
|
if (baseline.updateMode.externalSkillsRefreshed > 0) {
|
|
8929
9436
|
log(
|
|
8930
9437
|
assetRow(
|
|
@@ -9092,9 +9599,6 @@ function renderPhase1Rows(log, baseline, verbose = false) {
|
|
|
9092
9599
|
)
|
|
9093
9600
|
);
|
|
9094
9601
|
}
|
|
9095
|
-
if (baseline.backup) {
|
|
9096
|
-
log(assetRow("success", "backup", shortenPath(baseline.backup), TEMPLATES_COL));
|
|
9097
|
-
}
|
|
9098
9602
|
const shared2 = baseline.shared;
|
|
9099
9603
|
if (shared2 !== void 0) {
|
|
9100
9604
|
for (const f of shared2) {
|
|
@@ -9330,9 +9834,6 @@ function shortenPath(p2) {
|
|
|
9330
9834
|
}
|
|
9331
9835
|
return p2;
|
|
9332
9836
|
}
|
|
9333
|
-
function shellQuotePath(p2) {
|
|
9334
|
-
return `'${p2.replace(/'/g, `'\\''`)}'`;
|
|
9335
|
-
}
|
|
9336
9837
|
function rootClaudeMdMeta(root, trackCount) {
|
|
9337
9838
|
const tracks = `${trackCount} track${trackCount > 1 ? "s" : ""} noted`;
|
|
9338
9839
|
if (!root.created) return `@import ${HARNESS_ANCHOR_FILE} (body preserved)`;
|
|
@@ -9616,16 +10117,6 @@ function executeSpec(requested, deps = {}) {
|
|
|
9616
10117
|
exit(1);
|
|
9617
10118
|
return;
|
|
9618
10119
|
}
|
|
9619
|
-
if (deps.mode === "update" && report.backup !== null) {
|
|
9620
|
-
const liveClaude = join32(spec.projectDir, ".claude");
|
|
9621
|
-
if (dirTreesIdentical(liveClaude, report.backup)) {
|
|
9622
|
-
try {
|
|
9623
|
-
rmSync4(report.backup, { recursive: true, force: true });
|
|
9624
|
-
report.backup = null;
|
|
9625
|
-
} catch {
|
|
9626
|
-
}
|
|
9627
|
-
}
|
|
9628
|
-
}
|
|
9629
10120
|
if (report.updateMode) {
|
|
9630
10121
|
renderUpdateSummary(log, spec, report);
|
|
9631
10122
|
return;
|
|
@@ -9704,7 +10195,7 @@ function registerInstallCommand(cli) {
|
|
|
9704
10195
|
|
|
9705
10196
|
// src/commands/list.ts
|
|
9706
10197
|
init_esm_shims();
|
|
9707
|
-
import { existsSync as
|
|
10198
|
+
import { existsSync as existsSync25, readFileSync as readFileSync25 } from "fs";
|
|
9708
10199
|
import { join as join33, resolve as resolve3 } from "path";
|
|
9709
10200
|
function listAction(options = {}, deps = {}) {
|
|
9710
10201
|
const log = deps.log ?? console.log;
|
|
@@ -9809,7 +10300,7 @@ function formatHarnessFileRows(log, projectDir) {
|
|
|
9809
10300
|
const rootMd = log.templates.rootClaudeMd;
|
|
9810
10301
|
if (rootMd) {
|
|
9811
10302
|
const path = join33(projectDir, rootMd.path);
|
|
9812
|
-
const modified =
|
|
10303
|
+
const modified = existsSync25(path) && hashContent(readFileSync25(path, "utf8")) !== rootMd.sha256;
|
|
9813
10304
|
rows.push(
|
|
9814
10305
|
modified ? ` ${c.dim(rootMd.path)} ${c.yellow("(\uC218\uC815\uB428 \u2014 uninstall \uC2DC \uBCF4\uC874)")}` : ` ${c.dim(rootMd.path)}`
|
|
9815
10306
|
);
|
|
@@ -9823,7 +10314,7 @@ var ROOT_FILE_CHANGE = {
|
|
|
9823
10314
|
displaced: "\uBE44\uCF1C \uB460"
|
|
9824
10315
|
};
|
|
9825
10316
|
function formatRootFileRows(rootFiles, projectDir) {
|
|
9826
|
-
const present2 = rootFiles.filter((f) =>
|
|
10317
|
+
const present2 = rootFiles.filter((f) => existsSync25(join33(projectDir, f.path)));
|
|
9827
10318
|
const width = Math.max(0, ...present2.map((f) => f.path.length));
|
|
9828
10319
|
return present2.map(
|
|
9829
10320
|
(f) => ` ${c.dim(padDisplay(f.path, width))} ${c.dim(ROOT_FILE_CHANGE[f.change])} ${c.dim(f.notes.join(" / "))}`
|
|
@@ -9842,18 +10333,21 @@ init_esm_shims();
|
|
|
9842
10333
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
9843
10334
|
import {
|
|
9844
10335
|
accessSync,
|
|
9845
|
-
existsSync as
|
|
10336
|
+
existsSync as existsSync26,
|
|
9846
10337
|
constants as fsConstants,
|
|
9847
10338
|
lstatSync as lstatSync5,
|
|
10339
|
+
mkdirSync as mkdirSync7,
|
|
9848
10340
|
readdirSync as readdirSync6,
|
|
9849
10341
|
readFileSync as readFileSync26,
|
|
10342
|
+
readlinkSync,
|
|
9850
10343
|
realpathSync as realpathSync3,
|
|
10344
|
+
renameSync,
|
|
9851
10345
|
rmdirSync as rmdirSync2,
|
|
9852
|
-
rmSync as
|
|
9853
|
-
statSync as
|
|
9854
|
-
writeFileSync as
|
|
10346
|
+
rmSync as rmSync4,
|
|
10347
|
+
statSync as statSync5,
|
|
10348
|
+
writeFileSync as writeFileSync10
|
|
9855
10349
|
} from "fs";
|
|
9856
|
-
import { dirname as
|
|
10350
|
+
import { dirname as dirname7, join as join34, relative as relative5, resolve as resolve4, sep as sep5 } from "path";
|
|
9857
10351
|
|
|
9858
10352
|
// src/uninstall-interactive.ts
|
|
9859
10353
|
init_esm_shims();
|
|
@@ -11130,7 +11624,7 @@ var defaultPrompts = {
|
|
|
11130
11624
|
{
|
|
11131
11625
|
value: "hooks",
|
|
11132
11626
|
label: "Hooks \xB7 settings.json",
|
|
11133
|
-
hint: "
|
|
11627
|
+
hint: "harness hook wiring \xB7 dead refs reported, not removed"
|
|
11134
11628
|
},
|
|
11135
11629
|
{
|
|
11136
11630
|
value: "external",
|
|
@@ -11272,20 +11766,26 @@ function buildUninstallModeChoices(clis, assetCount) {
|
|
|
11272
11766
|
{
|
|
11273
11767
|
value: "selected",
|
|
11274
11768
|
label: "Remove selected assets",
|
|
11275
|
-
hint: assetCount > 0 ? `pick from the ${assetCount} external assets;
|
|
11769
|
+
hint: assetCount > 0 ? `pick from the ${assetCount} external assets; harness files stay` : "no external assets recorded",
|
|
11276
11770
|
enabled: assetCount > 0
|
|
11277
11771
|
},
|
|
11278
11772
|
{
|
|
11279
11773
|
value: "all",
|
|
11280
11774
|
label: "Remove everything",
|
|
11281
|
-
hint: "assets +
|
|
11775
|
+
hint: "assets + harness files + the install record \u2014 harness files are removed one by one; your own files in .claude/ \xB7 .codex/ \xB7 .opencode/ stay",
|
|
11282
11776
|
enabled: true
|
|
11283
11777
|
}
|
|
11284
11778
|
];
|
|
11285
11779
|
}
|
|
11780
|
+
var CLI_FOLDERS = [".claude/", ".codex/", ".opencode/"];
|
|
11781
|
+
var FOLDER_CLI = {
|
|
11782
|
+
".claude/": "claude",
|
|
11783
|
+
".codex/": "codex",
|
|
11784
|
+
".opencode/": "opencode"
|
|
11785
|
+
};
|
|
11286
11786
|
function describeOwned(p2) {
|
|
11287
|
-
if (p2.
|
|
11288
|
-
return `${p2.path}
|
|
11787
|
+
if (CLI_FOLDERS.includes(p2.path)) {
|
|
11788
|
+
return `${p2.path} harness files, one by one (your own files there stay)`;
|
|
11289
11789
|
}
|
|
11290
11790
|
if (p2.kind === "import-block") return `the import block in ${p2.path} (your text stays)`;
|
|
11291
11791
|
if (p2.path === "AGENTS.md") return "AGENTS.md harness sections (your ## Project Context stays)";
|
|
@@ -11296,7 +11796,7 @@ function keptLinkLines(projectDir, dirs) {
|
|
|
11296
11796
|
return dirs.flatMap((d2) => {
|
|
11297
11797
|
const o = outsideLinkAt(projectDir, d2.replace(/\/+$/, ""));
|
|
11298
11798
|
return o ? [
|
|
11299
|
-
` \xB7 ${o.link} stays \u2014 the link points outside the project (${o.link} \u2192 ${o.linkTarget});
|
|
11799
|
+
` \xB7 ${o.link} stays \u2014 the link points outside the project (${o.link} \u2192 ${o.linkTarget}); nothing behind it is removed`
|
|
11300
11800
|
] : [];
|
|
11301
11801
|
});
|
|
11302
11802
|
}
|
|
@@ -11331,7 +11831,7 @@ function cliDirs(cli, installed) {
|
|
|
11331
11831
|
cli,
|
|
11332
11832
|
installed.filter((c3) => c3 !== cli)
|
|
11333
11833
|
);
|
|
11334
|
-
return [...exclusive, ...sharedNowUnowned].filter((p2) => p2.
|
|
11834
|
+
return [...exclusive, ...sharedNowUnowned].filter((p2) => CLI_FOLDERS.includes(p2.path)).map((p2) => p2.path);
|
|
11335
11835
|
}
|
|
11336
11836
|
function buildCliRows(installed) {
|
|
11337
11837
|
return installed.map((cli) => {
|
|
@@ -11370,12 +11870,10 @@ async function runInteractiveUninstall(projectDir, deps = {}) {
|
|
|
11370
11870
|
[
|
|
11371
11871
|
"Remove everything? This is the same as: agent-harness uninstall --yes",
|
|
11372
11872
|
` \xB7 ${log.assets.length} recorded asset(s) \u2014 only those with an automatic reverse path are removed`,
|
|
11373
|
-
" \xB7
|
|
11873
|
+
" \xB7 harness files are removed one by one; your own files in .claude/ \xB7 .codex/ \xB7 .opencode/ stay (a file of yours the harness had replaced is put back)",
|
|
11374
11874
|
...keptLinkLines(
|
|
11375
11875
|
projectDir,
|
|
11376
|
-
|
|
11377
|
-
(d2) => typeof d2 === "string"
|
|
11378
|
-
)
|
|
11876
|
+
CLI_FOLDERS.filter((d2) => FOLDER_CLI[d2] !== void 0 && clis.includes(FOLDER_CLI[d2]))
|
|
11379
11877
|
),
|
|
11380
11878
|
" \xB7 the install record goes too",
|
|
11381
11879
|
" \xB7 files outside (.mcp.json etc.) are not deleted \u2014 you get instructions instead"
|
|
@@ -11472,14 +11970,9 @@ Proceed?`, initialValue: false });
|
|
|
11472
11970
|
}
|
|
11473
11971
|
|
|
11474
11972
|
// src/commands/uninstall.ts
|
|
11475
|
-
|
|
11476
|
-
return join34(projectDir, rel.replace(/\/+$/, ""));
|
|
11477
|
-
}
|
|
11478
|
-
function outsideTemplateLink(projectDir, rel) {
|
|
11479
|
-
return outsideLinkAt(projectDir, rel.replace(/\/+$/, ""));
|
|
11480
|
-
}
|
|
11973
|
+
var CODEX_CONFIG = ".codex/config.toml";
|
|
11481
11974
|
function keptOutsideLinkLine(o, log) {
|
|
11482
|
-
const head = ` ${c.yellow("\u2298")} ${o.link} \u2014 left as is: the link points outside the project (${o.link} \u2192 ${o.linkTarget}).
|
|
11975
|
+
const head = ` ${c.yellow("\u2298")} ${o.link} \u2014 left as is: the link points outside the project (${o.link} \u2192 ${o.linkTarget}). Nothing behind it is removed`;
|
|
11483
11976
|
const files = recordedUnder(log, o.link);
|
|
11484
11977
|
if (files.length === 0) return `${head}; clean up harness files there by hand if intended.`;
|
|
11485
11978
|
const shown = files.slice(0, KEPT_LINK_FILES_SHOWN).join(" \xB7 ");
|
|
@@ -11496,17 +11989,18 @@ function recordedUnder(log, link) {
|
|
|
11496
11989
|
];
|
|
11497
11990
|
return [...new Set(paths.filter((p2) => p2.startsWith(`${link}/`)))].sort();
|
|
11498
11991
|
}
|
|
11499
|
-
function
|
|
11500
|
-
|
|
11501
|
-
|
|
11502
|
-
|
|
11503
|
-
|
|
11504
|
-
|
|
11505
|
-
|
|
11506
|
-
|
|
11507
|
-
|
|
11508
|
-
|
|
11509
|
-
|
|
11992
|
+
function keepTemplatesRemovedLines(log) {
|
|
11993
|
+
const ids = log.assets.filter((a) => a.scope === "project").map((a) => a.id);
|
|
11994
|
+
return [
|
|
11995
|
+
status.failure(
|
|
11996
|
+
c.red(
|
|
11997
|
+
"ERROR: --keep-templates was removed in v26.169.0 \u2014 harness files are now removed one by one and your own files in .claude/ \xB7 .codex/ \xB7 .opencode/ stay in place."
|
|
11998
|
+
)
|
|
11999
|
+
),
|
|
12000
|
+
c.dim(
|
|
12001
|
+
ids.length > 0 ? ` To remove only the external assets: agent-harness uninstall --only ${ids.join(",")}` : " nothing on record for --only \u2014 run uninstall (everything) or uninstall --cli <name>"
|
|
12002
|
+
)
|
|
12003
|
+
];
|
|
11510
12004
|
}
|
|
11511
12005
|
function uninstallAction(options, deps = {}) {
|
|
11512
12006
|
const log = deps.log ?? console.log;
|
|
@@ -11515,7 +12009,6 @@ function uninstallAction(options, deps = {}) {
|
|
|
11515
12009
|
const spawn = deps.spawn ?? defaultSpawn2;
|
|
11516
12010
|
const rm = deps.rm ?? defaultRm;
|
|
11517
12011
|
const writeLog = deps.writeLog ?? writeInstallLog;
|
|
11518
|
-
const moveAside = deps.moveAside ?? backupDir;
|
|
11519
12012
|
const harnessRoot = (deps.resolveHarnessRoot ?? defaultHarnessRoot)();
|
|
11520
12013
|
const projectDir = resolve4(options.projectDir ?? process.cwd());
|
|
11521
12014
|
const detected = detectInstallState(projectDir);
|
|
@@ -11525,10 +12018,15 @@ function uninstallAction(options, deps = {}) {
|
|
|
11525
12018
|
exit(1);
|
|
11526
12019
|
return;
|
|
11527
12020
|
}
|
|
12021
|
+
if (options.keepTemplates) {
|
|
12022
|
+
for (const line of keepTemplatesRemovedLines(installLog)) err(line);
|
|
12023
|
+
exit(1);
|
|
12024
|
+
return;
|
|
12025
|
+
}
|
|
11528
12026
|
if (options.cli !== void 0) {
|
|
11529
12027
|
removeCliAction(
|
|
11530
12028
|
{ options, installLog, projectDir, harnessRoot },
|
|
11531
|
-
{ log, err, exit, rm, writeLog
|
|
12029
|
+
{ log, err, exit, rm, writeLog }
|
|
11532
12030
|
);
|
|
11533
12031
|
return;
|
|
11534
12032
|
}
|
|
@@ -11547,46 +12045,31 @@ function uninstallAction(options, deps = {}) {
|
|
|
11547
12045
|
return;
|
|
11548
12046
|
}
|
|
11549
12047
|
const targetAssets = selectedIds ? installLog.assets.filter((a) => selectedIds.includes(a.id)) : installLog.assets;
|
|
11550
|
-
const
|
|
11551
|
-
const movedDirs = keepTemplates ? [] : recordedTemplateDirs(installLog);
|
|
11552
|
-
const sharedPaths = keepTemplates ? [] : [...CLI_SHARED, ...ROOT_SHARED];
|
|
12048
|
+
const assetsOnly = selectedIds !== null;
|
|
11553
12049
|
const rootFiles = withSkillsLock(
|
|
11554
12050
|
selectedIds ? [] : (installLog.rootFiles ?? []).filter(
|
|
11555
|
-
(f) => !
|
|
12051
|
+
(f) => !UNINSTALL_SHARED.includes(f.path) && f.change !== "displaced"
|
|
11556
12052
|
),
|
|
11557
12053
|
targetAssets,
|
|
11558
12054
|
projectDir
|
|
11559
12055
|
);
|
|
11560
12056
|
const globalTrust = selectedIds ? void 0 : installLog.codexTrust;
|
|
11561
|
-
const templatesLog = {
|
|
11562
|
-
...installLog,
|
|
11563
|
-
externalFiles: (installLog.externalFiles ?? []).filter(
|
|
11564
|
-
(f) => !f.path.startsWith(`${INSTALL_LOG_DIR}/`)
|
|
11565
|
-
)
|
|
11566
|
-
};
|
|
11567
12057
|
const journal = [];
|
|
11568
12058
|
let filesRemoved = 0;
|
|
11569
12059
|
const rmJ = (path) => {
|
|
11570
12060
|
rm(path);
|
|
11571
12061
|
filesRemoved += 1;
|
|
11572
12062
|
};
|
|
11573
|
-
const
|
|
11574
|
-
const backup = moveAside(path);
|
|
11575
|
-
if (backup)
|
|
11576
|
-
journal.push(`moved ${relative5(projectDir, path)} aside \u2192 ${relative5(projectDir, backup)}`);
|
|
11577
|
-
return backup;
|
|
11578
|
-
};
|
|
11579
|
-
const plan = planReverse(targetAssets, { spawn, projectDir, rm: rmJ, movedDirs });
|
|
12063
|
+
const plan = planReverse(targetAssets, { spawn, projectDir, rm: rmJ });
|
|
11580
12064
|
for (const line of headerLines(installLog, selectedIds, targetAssets.length)) log(line);
|
|
11581
12065
|
if (options.dryRun) {
|
|
11582
12066
|
for (const line of dryRunLines(
|
|
11583
12067
|
plan,
|
|
11584
|
-
|
|
12068
|
+
installLog,
|
|
11585
12069
|
projectDir,
|
|
11586
|
-
|
|
12070
|
+
assetsOnly,
|
|
11587
12071
|
rootFiles,
|
|
11588
12072
|
harnessRoot,
|
|
11589
|
-
sharedPaths,
|
|
11590
12073
|
globalTrust
|
|
11591
12074
|
)) {
|
|
11592
12075
|
log(line);
|
|
@@ -11594,15 +12077,7 @@ function uninstallAction(options, deps = {}) {
|
|
|
11594
12077
|
exit(0);
|
|
11595
12078
|
return;
|
|
11596
12079
|
}
|
|
11597
|
-
const blocked = unwritablePaths({
|
|
11598
|
-
projectDir,
|
|
11599
|
-
installLog: templatesLog,
|
|
11600
|
-
targetAssets,
|
|
11601
|
-
selectedIds,
|
|
11602
|
-
keepTemplates,
|
|
11603
|
-
movedDirs,
|
|
11604
|
-
sharedPaths
|
|
11605
|
-
});
|
|
12080
|
+
const blocked = unwritablePaths({ projectDir, installLog, targetAssets, selectedIds });
|
|
11606
12081
|
if (blocked.length > 0) {
|
|
11607
12082
|
err(status.failure(c.red("ERROR: uninstall cannot write here \u2014 nothing was removed:")));
|
|
11608
12083
|
for (const p2 of blocked) err(c.dim(` \xB7 ${p2 === "." ? "the project folder" : p2}`));
|
|
@@ -11614,32 +12089,21 @@ function uninstallAction(options, deps = {}) {
|
|
|
11614
12089
|
let reversed;
|
|
11615
12090
|
try {
|
|
11616
12091
|
reversed = executeReverse(plan, log, logSurvives, journal);
|
|
11617
|
-
if (!
|
|
11618
|
-
const
|
|
11619
|
-
|
|
11620
|
-
|
|
11621
|
-
}
|
|
11622
|
-
const { rootClaudeMdKept, importStripped, external, moved, keptLinks } = removeTemplates(
|
|
11623
|
-
templatesLog,
|
|
11624
|
-
projectDir,
|
|
11625
|
-
{ rm: rmJ, moveAside: moveAsideJ },
|
|
12092
|
+
if (!assetsOnly) {
|
|
12093
|
+
const done = removeHarnessFiles(installLog, projectDir, FULL_SCOPE, {
|
|
12094
|
+
rm: rmJ,
|
|
12095
|
+
write: true,
|
|
11626
12096
|
harnessRoot
|
|
11627
|
-
);
|
|
11628
|
-
for (const
|
|
11629
|
-
|
|
11630
|
-
|
|
11631
|
-
log(` ${status.success("templates removed: (none)")}`);
|
|
12097
|
+
});
|
|
12098
|
+
for (const r of done.shared) {
|
|
12099
|
+
if (r.removed.length > 0 || r.fileRemoved)
|
|
12100
|
+
journal.push(`removed the harness part from ${r.path}`);
|
|
11632
12101
|
}
|
|
11633
|
-
for (const
|
|
11634
|
-
|
|
11635
|
-
if (importStripped) {
|
|
11636
|
-
log(` ${status.success("CLAUDE.md \u2014 harness @import removed (\uBCF8\uBB38 \uBCF4\uC874)")}`);
|
|
12102
|
+
for (const d2 of done.displaced) {
|
|
12103
|
+
if (d2.verdict === "restore") journal.push(`put back ${d2.path}`);
|
|
11637
12104
|
}
|
|
11638
|
-
|
|
11639
|
-
|
|
11640
|
-
log(
|
|
11641
|
-
` ${c.yellow("\u2298")} ${kept} kept \u2014 modified since install. Remove manually if intended.`
|
|
11642
|
-
);
|
|
12105
|
+
for (const line of harnessRemovalLines(done, installLog, projectDir, FULL_SCOPE, false)) {
|
|
12106
|
+
log(line);
|
|
11643
12107
|
}
|
|
11644
12108
|
}
|
|
11645
12109
|
} catch (e2) {
|
|
@@ -11664,26 +12128,20 @@ function uninstallAction(options, deps = {}) {
|
|
|
11664
12128
|
{ installLog, projectDir, selectedIds, removedIds },
|
|
11665
12129
|
{ log, err, rm, writeLog }
|
|
11666
12130
|
);
|
|
11667
|
-
for (const line of advisoryLines(
|
|
11668
|
-
plan,
|
|
11669
|
-
projectDir,
|
|
11670
|
-
rootFiles,
|
|
11671
|
-
options.keepTemplates === true,
|
|
11672
|
-
globalTrust
|
|
11673
|
-
)) {
|
|
12131
|
+
for (const line of advisoryLines(plan, projectDir, rootFiles, globalTrust)) {
|
|
11674
12132
|
log(line);
|
|
11675
12133
|
}
|
|
11676
12134
|
const outcome = summarize({
|
|
11677
12135
|
succeeded,
|
|
11678
12136
|
failed,
|
|
11679
12137
|
logWriteFailed,
|
|
11680
|
-
// 제거된 것도 없고
|
|
12138
|
+
// 제거된 것도 없고 하네스 파일도 안 건드렸는데, 사용자에게 줄 방법조차 없을 때만 실패다.
|
|
11681
12139
|
// ① `--only` = "이걸 빼라"는 특정 요청 — 하나도 못 뺐으면 요청 불이행 (C1).
|
|
11682
12140
|
// ② 자동 경로가 없는 자산이 남았으면 — 안내할 명령조차 없다 (R1).
|
|
11683
12141
|
// ③ 반면 global 자산만 남은 경우는 **정확한 수기 명령을 출력했으므로** 성공이다.
|
|
11684
12142
|
// D16 설계대로의 정상이고, 여기까지 묶으면 global scope 사용자는 exit 0 을 영원히
|
|
11685
12143
|
// 못 받는다 (로그가 지워져 재시도하면 더 나빠진다).
|
|
11686
|
-
nothingDone: succeeded === 0 &&
|
|
12144
|
+
nothingDone: succeeded === 0 && assetsOnly && (logSurvives || plan.noReversePath.length > 0)
|
|
11687
12145
|
});
|
|
11688
12146
|
log("");
|
|
11689
12147
|
log(outcome.line);
|
|
@@ -11700,18 +12158,19 @@ function unwritablePaths(ctx) {
|
|
|
11700
12158
|
if (!safeExists(abs)) return;
|
|
11701
12159
|
const real = safeRealpath(abs);
|
|
11702
12160
|
if (real !== null && isOutsideProject(projectDir, real)) return;
|
|
11703
|
-
need.add(
|
|
12161
|
+
need.add(dirname7(path));
|
|
11704
12162
|
};
|
|
11705
12163
|
for (const a of project)
|
|
11706
12164
|
if (a.method === "skill") for (const f of a.files ?? []) parentOf(f.path);
|
|
11707
|
-
if (
|
|
12165
|
+
if (ctx.selectedIds === null) {
|
|
11708
12166
|
const recordedShared = new Set((installLog.portions ?? []).map((p2) => p2.path));
|
|
11709
|
-
|
|
11710
|
-
|
|
11711
|
-
|
|
11712
|
-
|
|
11713
|
-
if (!underAny(f.path, ctx.movedDirs)) parentOf(f.path);
|
|
12167
|
+
const made = new Set((installLog.externalFiles ?? []).map((f) => f.path));
|
|
12168
|
+
for (const p2 of UNINSTALL_SHARED) {
|
|
12169
|
+
if (recordedShared.has(p2) && existsSync26(join34(projectDir, p2))) need.add(p2);
|
|
12170
|
+
if (made.has(p2)) parentOf(p2);
|
|
11714
12171
|
}
|
|
12172
|
+
for (const p2 of harnessCandidates(installLog)) parentOf(p2);
|
|
12173
|
+
for (const f of installLog.rootFiles ?? []) if (f.change === "displaced") parentOf(f.path);
|
|
11715
12174
|
if (hasRootImport(projectDir)) need.add("CLAUDE.md");
|
|
11716
12175
|
}
|
|
11717
12176
|
return [...need].filter((p2) => {
|
|
@@ -11801,14 +12260,15 @@ function settleLog(ctx, io) {
|
|
|
11801
12260
|
}
|
|
11802
12261
|
} else {
|
|
11803
12262
|
io.rm(join34(projectDir, INSTALL_LOG_DIR));
|
|
11804
|
-
if (
|
|
12263
|
+
if (outsideLinkAt(projectDir, LEGACY_INSTALL_LOG_DIR) === null) {
|
|
11805
12264
|
io.rm(legacyInstallLogPath(projectDir));
|
|
12265
|
+
pruneEmptyDirsUpward(projectDir, join34(projectDir, LEGACY_INSTALL_LOG_DIR));
|
|
11806
12266
|
}
|
|
11807
12267
|
io.log(` ${status.success("install log removed")}`);
|
|
11808
12268
|
}
|
|
11809
12269
|
return false;
|
|
11810
12270
|
}
|
|
11811
|
-
function dryRunLines(plan, installLog, projectDir,
|
|
12271
|
+
function dryRunLines(plan, installLog, projectDir, assetsOnly, rootFiles, harnessRoot, globalTrust) {
|
|
11812
12272
|
const lines2 = [c.yellow("[DRY RUN] reverse list (\uC2E4\uC81C \uBCC0\uACBD \uC5C6\uC74C):"), ""];
|
|
11813
12273
|
if (plan.reverseSteps.length === 0) {
|
|
11814
12274
|
lines2.push(c.dim(" (no project-scope assets to reverse)"));
|
|
@@ -11821,35 +12281,19 @@ function dryRunLines(plan, installLog, projectDir, keepTemplates, rootFiles, har
|
|
|
11821
12281
|
...(plan.hints.get(a.id) ?? []).map((h2) => c.dim(` \u21B3 ${h2}`))
|
|
11822
12282
|
])
|
|
11823
12283
|
);
|
|
11824
|
-
if (!
|
|
11825
|
-
const
|
|
11826
|
-
|
|
11827
|
-
|
|
11828
|
-
|
|
11829
|
-
|
|
11830
|
-
}
|
|
11831
|
-
|
|
11832
|
-
const rootMd = installLog.templates.rootClaudeMd;
|
|
11833
|
-
if (rootMd) {
|
|
11834
|
-
lines2.push(
|
|
11835
|
-
rootClaudeMdModified(installLog, projectDir) ? ` \u25CB keep ${rootMd.path} (modified since install \u2014 preserved)` : ` \u25CB remove ${rootMd.path}`
|
|
11836
|
-
);
|
|
11837
|
-
}
|
|
11838
|
-
if (hasRootImport(projectDir)) {
|
|
11839
|
-
lines2.push(" \u25CB strip harness @import from CLAUDE.md (\uBCF8\uBB38 \uBCF4\uC874)");
|
|
11840
|
-
}
|
|
11841
|
-
lines2.push(...previewExternalLines(installLog, projectDir, harnessRoot, dirs));
|
|
11842
|
-
lines2.push(
|
|
11843
|
-
...sharedStripLines(
|
|
11844
|
-
stripCliShared(installLog, projectDir, harnessRoot, sharedPaths, false),
|
|
11845
|
-
true
|
|
11846
|
-
)
|
|
11847
|
-
);
|
|
12284
|
+
if (!assetsOnly) {
|
|
12285
|
+
const preview = removeHarnessFiles(installLog, projectDir, FULL_SCOPE, {
|
|
12286
|
+
rm: () => {
|
|
12287
|
+
},
|
|
12288
|
+
write: false,
|
|
12289
|
+
harnessRoot
|
|
12290
|
+
});
|
|
12291
|
+
lines2.push(...harnessRemovalLines(preview, installLog, projectDir, FULL_SCOPE, true));
|
|
11848
12292
|
}
|
|
11849
|
-
lines2.push(...advisoryLines(plan, projectDir, rootFiles,
|
|
12293
|
+
lines2.push(...advisoryLines(plan, projectDir, rootFiles, globalTrust), "");
|
|
11850
12294
|
return lines2;
|
|
11851
12295
|
}
|
|
11852
|
-
function advisoryLines(plan, projectDir, rootFiles,
|
|
12296
|
+
function advisoryLines(plan, projectDir, rootFiles, globalTrust) {
|
|
11853
12297
|
const lines2 = [];
|
|
11854
12298
|
if (globalTrust) {
|
|
11855
12299
|
lines2.push(
|
|
@@ -11870,24 +12314,25 @@ function advisoryLines(plan, projectDir, rootFiles, keepTemplates = false, globa
|
|
|
11870
12314
|
lines2.push(c.dim(` \xB7 ${adv.asset.id} (${adv.asset.method})`), c.dim(` ${adv.command}`));
|
|
11871
12315
|
}
|
|
11872
12316
|
}
|
|
11873
|
-
lines2.push(...rootFileAdvisoryLines(rootFiles, projectDir
|
|
12317
|
+
lines2.push(...rootFileAdvisoryLines(rootFiles, projectDir));
|
|
11874
12318
|
lines2.push(...backupArtifactAdvisoryLines(projectDir));
|
|
11875
12319
|
return lines2;
|
|
11876
12320
|
}
|
|
11877
12321
|
function backupArtifactAdvisoryLines(projectDir) {
|
|
11878
|
-
const stampRe = /\.backup-\d{8}T\d{6}$/;
|
|
11879
12322
|
const found = [];
|
|
11880
12323
|
for (const e2 of readdirSync6(projectDir, { withFileTypes: true })) {
|
|
11881
|
-
if (
|
|
12324
|
+
if (CLI_FOLDERS2.some((d2) => e2.name.startsWith(`${d2}.backup-`))) {
|
|
11882
12325
|
found.push(e2.name);
|
|
11883
|
-
} else if (e2.isFile() &&
|
|
12326
|
+
} else if (e2.isFile() && BACKUP_SUFFIX.test(e2.name)) {
|
|
11884
12327
|
found.push(e2.name);
|
|
11885
12328
|
}
|
|
11886
12329
|
}
|
|
11887
|
-
const
|
|
11888
|
-
|
|
11889
|
-
|
|
11890
|
-
|
|
12330
|
+
for (const dir of BACKUP_SCAN_DIRS) {
|
|
12331
|
+
if (dir === INSTALL_LOG_DIR) continue;
|
|
12332
|
+
const abs = join34(projectDir, dir);
|
|
12333
|
+
if (!existsSync26(abs) || outsideLinkAt(projectDir, dir) !== null) continue;
|
|
12334
|
+
for (const rel of listFilesRecursive(abs)) {
|
|
12335
|
+
if (BACKUP_SUFFIX.test(rel)) found.push(`${dir}/${rel}`);
|
|
11891
12336
|
}
|
|
11892
12337
|
}
|
|
11893
12338
|
if (found.length === 0) return [];
|
|
@@ -11898,9 +12343,9 @@ function backupArtifactAdvisoryLines(projectDir) {
|
|
|
11898
12343
|
...found.sort().map((f) => c.dim(` ${f}`))
|
|
11899
12344
|
];
|
|
11900
12345
|
}
|
|
11901
|
-
function rootFileAdvisoryLines(rootFiles, projectDir
|
|
11902
|
-
const header =
|
|
11903
|
-
const present2 = rootFiles.filter((f) =>
|
|
12346
|
+
function rootFileAdvisoryLines(rootFiles, projectDir) {
|
|
12347
|
+
const header = "[ROOT] `.claude/` \uBC16\uC5D0 \uB0A8\uB294 \uAC83 (\uC790\uB3D9\uC73C\uB85C \uC9C0\uC6B0\uC9C0 \uC54A\uB294\uB2E4):";
|
|
12348
|
+
const present2 = rootFiles.filter((f) => existsSync26(join34(projectDir, f.path)));
|
|
11904
12349
|
if (present2.length === 0) return [];
|
|
11905
12350
|
const outside = /* @__PURE__ */ new Map();
|
|
11906
12351
|
const inside = present2.filter((f) => {
|
|
@@ -11943,7 +12388,7 @@ function rootFileMeaning(f, projectDir) {
|
|
|
11943
12388
|
case "displaced": {
|
|
11944
12389
|
const backup = f.notes[0];
|
|
11945
12390
|
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)";
|
|
11946
|
-
return
|
|
12391
|
+
return existsSync26(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`;
|
|
11947
12392
|
}
|
|
11948
12393
|
}
|
|
11949
12394
|
}
|
|
@@ -11954,8 +12399,8 @@ var TEMPLATE_DIR_FIELD = {
|
|
|
11954
12399
|
};
|
|
11955
12400
|
function resolveCliTarget(ctx, io) {
|
|
11956
12401
|
const { options, installLog } = ctx;
|
|
11957
|
-
if (options.only !== void 0
|
|
11958
|
-
io.err(status.failure(c.red("ERROR: --cli \uB294 --only \
|
|
12402
|
+
if (options.only !== void 0) {
|
|
12403
|
+
io.err(status.failure(c.red("ERROR: --cli \uB294 --only \uC640 \uD568\uAED8 \uC4F8 \uC218 \uC5C6\uB2E4")));
|
|
11959
12404
|
io.err(
|
|
11960
12405
|
c.dim(
|
|
11961
12406
|
" CLI \uD558\uB098 \uC81C\uAC70: uninstall --cli <name> / \uC790\uC0B0 \uD558\uB098 \uC81C\uAC70: uninstall --only <id>"
|
|
@@ -11989,20 +12434,24 @@ function resolveCliTarget(ctx, io) {
|
|
|
11989
12434
|
function underAny(path, owned) {
|
|
11990
12435
|
return owned.some((o) => o.endsWith("/") ? path.startsWith(o) : path === o);
|
|
11991
12436
|
}
|
|
11992
|
-
function planCliRemoval(target, remaining
|
|
12437
|
+
function planCliRemoval(target, remaining) {
|
|
11993
12438
|
const { exclusive, sharedNowUnowned } = removableFor(target, remaining);
|
|
11994
12439
|
const owned = [...exclusive, ...sharedNowUnowned];
|
|
11995
|
-
const
|
|
11996
|
-
const
|
|
11997
|
-
(f) => underAny(f.path, recorded2)
|
|
11998
|
-
);
|
|
12440
|
+
const prefixes = owned.filter((o) => o.kind === "recorded" || o.kind === "anchor").map((o) => o.path);
|
|
12441
|
+
const has = (path) => underAny(path, prefixes);
|
|
11999
12442
|
return {
|
|
12000
12443
|
target,
|
|
12001
|
-
|
|
12002
|
-
|
|
12003
|
-
|
|
12004
|
-
|
|
12005
|
-
|
|
12444
|
+
scope: {
|
|
12445
|
+
has,
|
|
12446
|
+
// `.claude/settings.json` 은 claude 의 `.claude/` 아래 · `.codex/config.toml` 은 codex 의 `.codex/` 아래 · `AGENTS.md` ·
|
|
12447
|
+
// `opencode.json` 은 마지막 사용자일 때만 표에 든다. `.mcp.json`(`keep`) · `.gitignore` 는 CLI 하나로 빠지지 않는다
|
|
12448
|
+
shared: UNINSTALL_SHARED.filter(has),
|
|
12449
|
+
// claude 를 뺄 때 `.claude/` 아래 다섯 — `docs/decisions` 는 CLI 소속이 아니다
|
|
12450
|
+
skeleton: PROJECT_SKELETON_DIRS.filter(has),
|
|
12451
|
+
folders: CLI_FOLDERS2.filter((d2) => has(`${d2}/`)),
|
|
12452
|
+
importBlock: owned.some((o) => o.kind === "import-block"),
|
|
12453
|
+
anchor: owned.some((o) => o.kind === "anchor")
|
|
12454
|
+
}
|
|
12006
12455
|
};
|
|
12007
12456
|
}
|
|
12008
12457
|
function removeCliHeader(target, installed, remaining) {
|
|
@@ -12019,27 +12468,14 @@ function removeCliHeader(target, installed, remaining) {
|
|
|
12019
12468
|
function removeCliDryRunLines(plan, ctx, remaining) {
|
|
12020
12469
|
const { installLog, projectDir, harnessRoot } = ctx;
|
|
12021
12470
|
const lines2 = [c.yellow("[DRY RUN] CLI \uC81C\uAC70 \uBBF8\uB9AC\uBCF4\uAE30 (\uC2E4\uC81C \uBCC0\uACBD \uC5C6\uC74C):"), ""];
|
|
12022
|
-
|
|
12023
|
-
|
|
12024
|
-
|
|
12025
|
-
|
|
12026
|
-
|
|
12027
|
-
|
|
12028
|
-
lines2.push(
|
|
12029
|
-
|
|
12030
|
-
stripCliShared(installLog, projectDir, harnessRoot, plan.shared, false),
|
|
12031
|
-
true
|
|
12032
|
-
)
|
|
12033
|
-
);
|
|
12034
|
-
const rootMd = plan.anchor ? installLog.templates.rootClaudeMd : void 0;
|
|
12035
|
-
if (rootMd && existsSync25(join34(projectDir, rootMd.path))) {
|
|
12036
|
-
lines2.push(
|
|
12037
|
-
rootClaudeMdModified(installLog, projectDir) ? ` \u25CB keep ${rootMd.path} (modified since install \u2014 preserved)` : ` \u25CB remove ${rootMd.path}`
|
|
12038
|
-
);
|
|
12039
|
-
}
|
|
12040
|
-
if (plan.importBlock && hasRootImport(projectDir)) {
|
|
12041
|
-
lines2.push(" \u25CB strip harness @import from CLAUDE.md (\uBCF8\uBB38 \uBCF4\uC874)");
|
|
12042
|
-
}
|
|
12471
|
+
const preview = removeHarnessFiles(installLog, projectDir, plan.scope, {
|
|
12472
|
+
rm: () => {
|
|
12473
|
+
},
|
|
12474
|
+
write: false,
|
|
12475
|
+
harnessRoot
|
|
12476
|
+
});
|
|
12477
|
+
lines2.push(...harnessRemovalLines(preview, installLog, projectDir, plan.scope, true));
|
|
12478
|
+
lines2.push(...assetsLeftLines(installLog, projectDir, plan.scope));
|
|
12043
12479
|
const rerender = agentsMdRerenderSet(plan.target, remaining, installLog, projectDir);
|
|
12044
12480
|
if (rerender !== null) {
|
|
12045
12481
|
lines2.push(` \u25CB would re-render AGENTS.md for ${displayNames(rerender)} (your sections kept)`);
|
|
@@ -12055,7 +12491,7 @@ function agentsMdRerenderSet(target, remaining, log, projectDir) {
|
|
|
12055
12491
|
const set = agentsMdClis(remaining);
|
|
12056
12492
|
if (set.length === 0) return null;
|
|
12057
12493
|
const recorded2 = (log.externalFiles ?? []).some((f) => f.path === AGENTS_MD2) || (log.portions ?? []).some((p2) => p2.path === AGENTS_MD2);
|
|
12058
|
-
if (!recorded2 || !
|
|
12494
|
+
if (!recorded2 || !existsSync26(join34(projectDir, AGENTS_MD2))) return null;
|
|
12059
12495
|
return set;
|
|
12060
12496
|
}
|
|
12061
12497
|
function rerenderAgentsMd(ctx, set) {
|
|
@@ -12096,21 +12532,7 @@ function rerenderLines(r) {
|
|
|
12096
12532
|
}
|
|
12097
12533
|
return lines2;
|
|
12098
12534
|
}
|
|
12099
|
-
function
|
|
12100
|
-
const { installLog, projectDir } = ctx;
|
|
12101
|
-
const rootMd = installLog.templates.rootClaudeMd;
|
|
12102
|
-
if (!rootMd || !existsSync25(join34(projectDir, rootMd.path))) return false;
|
|
12103
|
-
if (rootClaudeMdModified(installLog, projectDir)) {
|
|
12104
|
-
io.log(
|
|
12105
|
-
` ${c.yellow("\u2298")} ${rootMd.path} kept \u2014 modified since install. Remove manually if intended.`
|
|
12106
|
-
);
|
|
12107
|
-
return false;
|
|
12108
|
-
}
|
|
12109
|
-
io.rm(join34(projectDir, rootMd.path));
|
|
12110
|
-
io.log(` ${status.success(`${rootMd.path} removed`)}`);
|
|
12111
|
-
return true;
|
|
12112
|
-
}
|
|
12113
|
-
function settleCliLog(ctx, target, remaining, recovered, anchorRemoved, removedDirs, shared2) {
|
|
12535
|
+
function settleCliLog(ctx, target, remaining, done, anchorRemoved) {
|
|
12114
12536
|
const { installLog } = ctx;
|
|
12115
12537
|
const next = {
|
|
12116
12538
|
...installLog,
|
|
@@ -12123,23 +12545,26 @@ function settleCliLog(ctx, target, remaining, recovered, anchorRemoved, removedD
|
|
|
12123
12545
|
},
|
|
12124
12546
|
templates: { ...installLog.templates }
|
|
12125
12547
|
};
|
|
12126
|
-
next.assets = installLog.assets.map(
|
|
12127
|
-
(a) => a.files === void 0 ? a : { ...a, files: a.files.filter((f) => !underAny(f.path, removedDirs)) }
|
|
12128
|
-
);
|
|
12129
12548
|
const field = TEMPLATE_DIR_FIELD[target];
|
|
12130
12549
|
if (field) delete next.templates[field];
|
|
12131
12550
|
if (anchorRemoved) delete next.templates.rootClaudeMd;
|
|
12132
|
-
|
|
12133
|
-
|
|
12134
|
-
|
|
12551
|
+
if (target === "claude") {
|
|
12552
|
+
delete next.policyFiles;
|
|
12553
|
+
delete next.skillFiles;
|
|
12554
|
+
}
|
|
12555
|
+
const survivors = (installLog.externalFiles ?? []).filter((f) => !done.forgotten.has(f.path));
|
|
12135
12556
|
if (survivors.length > 0) next.externalFiles = survivors;
|
|
12136
12557
|
else delete next.externalFiles;
|
|
12137
|
-
const
|
|
12558
|
+
const settled = new Set(done.displaced.filter((d2) => d2.forget).map((d2) => d2.path));
|
|
12559
|
+
if (installLog.rootFiles !== void 0) {
|
|
12560
|
+
next.rootFiles = installLog.rootFiles.filter(
|
|
12561
|
+
(f) => !(f.change === "displaced" && settled.has(f.path))
|
|
12562
|
+
);
|
|
12563
|
+
}
|
|
12564
|
+
const touched = new Set(done.shared.map((r) => r.path));
|
|
12138
12565
|
const portions = [
|
|
12139
|
-
...(installLog.portions ?? []).filter(
|
|
12140
|
-
|
|
12141
|
-
),
|
|
12142
|
-
...shared2.flatMap((r) => r.portions)
|
|
12566
|
+
...(installLog.portions ?? []).filter((p2) => !touched.has(p2.path)),
|
|
12567
|
+
...done.shared.flatMap((r) => r.portions)
|
|
12143
12568
|
];
|
|
12144
12569
|
if (portions.length > 0) next.portions = portions;
|
|
12145
12570
|
else delete next.portions;
|
|
@@ -12151,36 +12576,26 @@ function removeCliAction(ctx, io) {
|
|
|
12151
12576
|
const { options, installLog, projectDir, harnessRoot } = ctx;
|
|
12152
12577
|
const installed = installedClis(installLog);
|
|
12153
12578
|
const remaining = installed.filter((cli) => cli !== target);
|
|
12154
|
-
const plan = planCliRemoval(target, remaining
|
|
12579
|
+
const plan = planCliRemoval(target, remaining);
|
|
12155
12580
|
for (const line of removeCliHeader(target, installed, remaining)) io.log(line);
|
|
12156
12581
|
if (options.dryRun) {
|
|
12157
12582
|
for (const line of removeCliDryRunLines(plan, ctx, remaining)) io.log(line);
|
|
12158
12583
|
io.exit(0);
|
|
12159
12584
|
return;
|
|
12160
12585
|
}
|
|
12161
|
-
|
|
12162
|
-
|
|
12163
|
-
|
|
12164
|
-
|
|
12165
|
-
|
|
12166
|
-
|
|
12167
|
-
|
|
12168
|
-
const backup = io.moveAside(templateDirPath(projectDir, dir));
|
|
12169
|
-
if (backup) io.log(movedAsideLine(projectDir, dir, backup));
|
|
12170
|
-
}
|
|
12171
|
-
const external = removeExternalFiles(plan.scoped, projectDir, io.rm, harnessRoot);
|
|
12172
|
-
for (const line of externalRemovalLines(external)) io.log(line);
|
|
12173
|
-
const shared2 = stripCliShared(installLog, projectDir, harnessRoot, plan.shared, true);
|
|
12174
|
-
for (const line of sharedStripLines(shared2, false)) io.log(line);
|
|
12175
|
-
if (plan.importBlock && stripRootImport(projectDir)) {
|
|
12176
|
-
io.log(` ${status.success("CLAUDE.md \u2014 harness @import removed (\uBCF8\uBB38 \uBCF4\uC874)")}`);
|
|
12586
|
+
const done = removeHarnessFiles(installLog, projectDir, plan.scope, {
|
|
12587
|
+
rm: io.rm,
|
|
12588
|
+
write: true,
|
|
12589
|
+
harnessRoot
|
|
12590
|
+
});
|
|
12591
|
+
for (const line of harnessRemovalLines(done, installLog, projectDir, plan.scope, false)) {
|
|
12592
|
+
io.log(line);
|
|
12177
12593
|
}
|
|
12178
|
-
const
|
|
12594
|
+
for (const line of assetsLeftLines(installLog, projectDir, plan.scope)) io.log(line);
|
|
12179
12595
|
const rerenderSet = agentsMdRerenderSet(target, remaining, installLog, projectDir);
|
|
12180
12596
|
const rerender = rerenderSet === null ? null : rerenderAgentsMd(ctx, rerenderSet);
|
|
12181
12597
|
if (rerender !== null) for (const line of rerenderLines(rerender)) io.log(line);
|
|
12182
|
-
const
|
|
12183
|
-
const settled = settleCliLog(ctx, target, remaining, recovered, anchorRemoved, plan.dirs, shared2);
|
|
12598
|
+
const settled = settleCliLog(ctx, target, remaining, done, done.anchor.removed);
|
|
12184
12599
|
const next = rerender === null ? settled : withRerender(settled, rerender);
|
|
12185
12600
|
try {
|
|
12186
12601
|
io.writeLog(projectDir, next);
|
|
@@ -12199,19 +12614,36 @@ function removeCliAction(ctx, io) {
|
|
|
12199
12614
|
io.log(status.success(c.green(`${target} removed (remaining: ${remaining.join(", ")})`)));
|
|
12200
12615
|
io.exit(0);
|
|
12201
12616
|
}
|
|
12202
|
-
var
|
|
12203
|
-
|
|
12617
|
+
var UNINSTALL_SHARED = Object.keys(SHARED_FILES).filter(
|
|
12618
|
+
(p2) => p2 !== "CLAUDE.md"
|
|
12619
|
+
);
|
|
12204
12620
|
var SKILLS_LOCK = "skills-lock.json";
|
|
12205
12621
|
function withSkillsLock(rootFiles, assets, projectDir) {
|
|
12206
12622
|
const out = [...rootFiles];
|
|
12207
12623
|
const skills = assets.some((a) => a.method === "skill" && a.scope === "project");
|
|
12208
|
-
if (skills && !out.some((f) => f.path === SKILLS_LOCK) &&
|
|
12624
|
+
if (skills && !out.some((f) => f.path === SKILLS_LOCK) && existsSync26(join34(projectDir, SKILLS_LOCK))) {
|
|
12209
12625
|
out.push({ path: SKILLS_LOCK, change: "advisory", notes: [] });
|
|
12210
12626
|
}
|
|
12211
12627
|
return out;
|
|
12212
12628
|
}
|
|
12213
|
-
function remnantFor(path, log, harnessRoot) {
|
|
12629
|
+
function remnantFor(path, log, harnessRoot, projectDir) {
|
|
12214
12630
|
const clis = installedClis(log);
|
|
12631
|
+
if (path === SETTINGS_TARGET) {
|
|
12632
|
+
return (disk) => {
|
|
12633
|
+
try {
|
|
12634
|
+
const template = readFileSync26(join34(harnessRoot, "templates", "settings.json"), "utf8");
|
|
12635
|
+
const render = renderSettingsPortion(template, () => true);
|
|
12636
|
+
return [...legacySettingsSeed(disk, render, projectDir).keys()].filter(
|
|
12637
|
+
(k2) => !k2.endsWith("{}")
|
|
12638
|
+
);
|
|
12639
|
+
} catch {
|
|
12640
|
+
return [];
|
|
12641
|
+
}
|
|
12642
|
+
};
|
|
12643
|
+
}
|
|
12644
|
+
if (path === CODEX_CONFIG) {
|
|
12645
|
+
return (disk) => [...ADAPTERS["toml-region"].read(disk)?.keys() ?? []];
|
|
12646
|
+
}
|
|
12215
12647
|
if (path === ".gitignore") {
|
|
12216
12648
|
return (disk) => [...legacyGitignoreSeed(disk, gitignoreRender(), log).keys()];
|
|
12217
12649
|
}
|
|
@@ -12261,6 +12693,12 @@ function knownServerValues(servers, form, prefix = "mcpServers.") {
|
|
|
12261
12693
|
return out;
|
|
12262
12694
|
}
|
|
12263
12695
|
function remnantLine(path) {
|
|
12696
|
+
if (path === SETTINGS_TARGET) {
|
|
12697
|
+
return (names) => `may still hold the harness's hooks (not on record): ${names.join(" \xB7 ")} \u2014 delete them by hand if they are not yours`;
|
|
12698
|
+
}
|
|
12699
|
+
if (path === CODEX_CONFIG) {
|
|
12700
|
+
return (names) => `harness regions not on record: ${names.join(" \xB7 ")} \u2014 delete them by hand if you want them gone`;
|
|
12701
|
+
}
|
|
12264
12702
|
if (path === ".mcp.json") {
|
|
12265
12703
|
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`;
|
|
12266
12704
|
}
|
|
@@ -12269,31 +12707,11 @@ function remnantLine(path) {
|
|
|
12269
12707
|
}
|
|
12270
12708
|
return path === AGENTS_MD2 ? () => "the harness block is not on record (written before the harness recorded its part) \u2014 delete the <!-- uzys-harness:agents --> block by hand if you want it gone" : (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`;
|
|
12271
12709
|
}
|
|
12272
|
-
function stripCliShared(log, projectDir, harnessRoot, paths, write) {
|
|
12273
|
-
const harnessMade = new Set((log.externalFiles ?? []).map((f) => f.path));
|
|
12274
|
-
const created = new Set(
|
|
12275
|
-
(log.rootFiles ?? []).filter((f) => f.change === "created").map((f) => f.path)
|
|
12276
|
-
);
|
|
12277
|
-
return paths.filter((path) => !harnessMade.has(path)).map(
|
|
12278
|
-
(path) => stripShared({
|
|
12279
|
-
projectDir,
|
|
12280
|
-
path,
|
|
12281
|
-
portions: log.portions ?? [],
|
|
12282
|
-
excluded: [...excludedIds(log)],
|
|
12283
|
-
remnant: remnantFor(path, log, harnessRoot),
|
|
12284
|
-
remnantLine: remnantLine(path),
|
|
12285
|
-
write,
|
|
12286
|
-
// ADR-099 R2 — 블록만 담아 다시 만든 `AGENTS.md`(첫 접촉 블록 모델)도 하네스가 만든 파일이다
|
|
12287
|
-
removeIfEmpty: created.has(path)
|
|
12288
|
-
})
|
|
12289
|
-
);
|
|
12290
|
-
}
|
|
12291
12710
|
function sharedStripLines(results, preview) {
|
|
12292
12711
|
const lines2 = [];
|
|
12293
12712
|
for (const r of results) {
|
|
12294
12713
|
const display = (k2) => {
|
|
12295
12714
|
if (k2 === AGENTS_BLOCK_NAME) return ["harness block"];
|
|
12296
|
-
if (!ROOT_SHARED.includes(r.path)) return [k2];
|
|
12297
12715
|
const id = keyId(r.path, k2);
|
|
12298
12716
|
return id === null ? [] : [id.slice(id.indexOf(":") + 1)];
|
|
12299
12717
|
};
|
|
@@ -12343,7 +12761,7 @@ function noReverseHints(asset, ctx) {
|
|
|
12343
12761
|
}
|
|
12344
12762
|
if (asset.method !== "skill") return [];
|
|
12345
12763
|
const name = asset.detail.skill;
|
|
12346
|
-
const left = name === void 0 ? [] : [".claude/skills", ".agents/skills"].map((root) => `${root}/${name}/`).filter((p2) =>
|
|
12764
|
+
const left = name === void 0 ? [] : [".claude/skills", ".agents/skills"].map((root) => `${root}/${name}/`).filter((p2) => existsSync26(join34(ctx.projectDir, p2)));
|
|
12347
12765
|
return [
|
|
12348
12766
|
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"
|
|
12349
12767
|
];
|
|
@@ -12423,118 +12841,513 @@ function buildGlobalAdvisoryCmd(asset) {
|
|
|
12423
12841
|
return "(no standard reverse \u2014 manual)";
|
|
12424
12842
|
}
|
|
12425
12843
|
}
|
|
12426
|
-
|
|
12427
|
-
|
|
12428
|
-
|
|
12429
|
-
|
|
12430
|
-
|
|
12431
|
-
|
|
12432
|
-
|
|
12433
|
-
|
|
12434
|
-
|
|
12435
|
-
|
|
12436
|
-
|
|
12844
|
+
var CLAUDE_DIR3 = ".claude/";
|
|
12845
|
+
var LEGACY_LOG = `${LEGACY_INSTALL_LOG_DIR}/${INSTALL_LOG_FILENAME}`;
|
|
12846
|
+
var SKILLS_DIR3 = ".claude/skills/";
|
|
12847
|
+
var CLI_FOLDERS2 = [".claude", ".codex", ".opencode"];
|
|
12848
|
+
var LEFT_FILES_SHOWN = 3;
|
|
12849
|
+
var FULL_SCOPE = {
|
|
12850
|
+
has: () => true,
|
|
12851
|
+
shared: UNINSTALL_SHARED,
|
|
12852
|
+
skeleton: PROJECT_SKELETON_DIRS,
|
|
12853
|
+
folders: CLI_FOLDERS2,
|
|
12854
|
+
importBlock: true,
|
|
12855
|
+
anchor: true
|
|
12856
|
+
};
|
|
12857
|
+
function removeHarnessFiles(log, projectDir, scope, io) {
|
|
12858
|
+
const out = {
|
|
12859
|
+
removed: [],
|
|
12860
|
+
backedUp: [],
|
|
12861
|
+
forgotten: /* @__PURE__ */ new Set(),
|
|
12862
|
+
stripped: [],
|
|
12863
|
+
kept: [],
|
|
12864
|
+
unjudged: [],
|
|
12865
|
+
outside: [],
|
|
12866
|
+
displaced: [],
|
|
12867
|
+
shared: [],
|
|
12868
|
+
retiredWiring: [],
|
|
12869
|
+
importStripped: false,
|
|
12870
|
+
anchor: { removed: false, lines: [] }
|
|
12871
|
+
};
|
|
12872
|
+
const pruneFrom = [];
|
|
12873
|
+
stripSharedFiles(log, projectDir, scope.shared, io, out, pruneFrom);
|
|
12874
|
+
const isTarget = harnessTargets(log);
|
|
12875
|
+
for (const path of harnessCandidates(log)) {
|
|
12876
|
+
if (scope.has(path))
|
|
12877
|
+
removeRecordedFile(path, { log, projectDir, io, out, isTarget, pruneFrom });
|
|
12878
|
+
}
|
|
12879
|
+
if (scope.importBlock) {
|
|
12880
|
+
out.importStripped = io.write ? stripRootImport(projectDir) : hasRootImport(projectDir);
|
|
12881
|
+
}
|
|
12882
|
+
if (scope.anchor) {
|
|
12883
|
+
out.anchor = removeAnchor(log, projectDir, io);
|
|
12884
|
+
const anchorPath = log.templates?.rootClaudeMd?.path;
|
|
12885
|
+
if (out.anchor.removed && anchorPath !== void 0) out.removedAnchor = anchorPath;
|
|
12886
|
+
}
|
|
12887
|
+
restoreDisplaced(log, projectDir, scope, io, out);
|
|
12888
|
+
if (io.write) {
|
|
12889
|
+
for (const p2 of pruneFrom) pruneEmptyDirsUpward(projectDir, dirname7(join34(projectDir, p2)));
|
|
12890
|
+
for (const d2 of scope.skeleton) {
|
|
12891
|
+
const abs = join34(projectDir, d2);
|
|
12892
|
+
if (outsideProjectTarget(projectDir, abs) !== null) continue;
|
|
12893
|
+
pruneEmptyDirsUpward(projectDir, abs);
|
|
12437
12894
|
}
|
|
12438
|
-
const backup = io.moveAside(templateDirPath(projectDir, rel));
|
|
12439
|
-
return backup ? [{ rel, backup }] : [];
|
|
12440
|
-
});
|
|
12441
|
-
const external = removeExternalFiles(log, projectDir, rm, harnessRoot);
|
|
12442
|
-
external.removed.push(...recovered.removed);
|
|
12443
|
-
external.kept.push(...recovered.kept);
|
|
12444
|
-
for (const o of [...recovered.outside, ...outsidePortions]) {
|
|
12445
|
-
if (!external.outside.some((e2) => e2.path === o.path)) external.outside.push(o);
|
|
12446
|
-
}
|
|
12447
|
-
external.outside = external.outside.filter((o) => !underKeptLink(o.path, keptLinks));
|
|
12448
|
-
const importStripped = stripRootImport(projectDir);
|
|
12449
|
-
const rootMd = log.templates?.rootClaudeMd;
|
|
12450
|
-
if (rootMd) {
|
|
12451
|
-
if (rootClaudeMdModified(log, projectDir))
|
|
12452
|
-
return { rootClaudeMdKept: true, importStripped, external, moved, keptLinks };
|
|
12453
|
-
rm(join34(projectDir, rootMd.path));
|
|
12454
12895
|
}
|
|
12455
|
-
return
|
|
12896
|
+
return out;
|
|
12456
12897
|
}
|
|
12457
|
-
|
|
12458
|
-
|
|
12898
|
+
function harnessCandidates(log) {
|
|
12899
|
+
const paths = [
|
|
12900
|
+
...(log.policyFiles ?? []).map((f) => `${CLAUDE_DIR3}${f.path}`),
|
|
12901
|
+
...(log.skillFiles ?? []).map((f) => `${SKILLS_DIR3}${f.path}`),
|
|
12902
|
+
...(log.externalFiles ?? []).map((f) => f.path).filter((p2) => !p2.startsWith(`${INSTALL_LOG_DIR}/`)),
|
|
12903
|
+
// 어느 필드에도 적힌 적 없는 옛 판의 하네스 파일 — `isTarget` 이 알려 준다(A8 · install 과 같은 방식)
|
|
12904
|
+
INSTALLED_TRACKS,
|
|
12905
|
+
// 스캔 필드가 아예 없는 옛 로그(v26.125.0 이하)는 필드가 하네스 파일을 열거하지 못한다 — manifest 의 하네스 자리 ·
|
|
12906
|
+
// 은퇴 경로를 후보로 넣고 `recorded()` 가 가른다(필드 없음 → `no-sha` · 필드 있음 → 기록에 없으면 `none`, PR-D 리뷰 N1).
|
|
12907
|
+
// 그 은퇴 자리는 새 install · update 가 필드를 쓸 때 `policyFiles` 로 이어받는다(`retiredClaims` — 재리뷰 BLOCKER-1 ②)
|
|
12908
|
+
...log.records === "writer" ? [] : recordedTrackSpots(log)
|
|
12909
|
+
];
|
|
12910
|
+
return [...new Set(paths)].filter((p2) => kindOf(log, p2) === "harness");
|
|
12911
|
+
}
|
|
12912
|
+
function harnessTargets(log) {
|
|
12913
|
+
const tracked = recordedTrackTargets(log);
|
|
12914
|
+
return (p2) => p2 === INSTALLED_TRACKS || tracked(p2);
|
|
12915
|
+
}
|
|
12916
|
+
function templateNext(harnessRoot, log, path) {
|
|
12917
|
+
if (path === INSTALLED_TRACKS) return installedTracksText(log.spec.tracks.filter(isTrack));
|
|
12918
|
+
if (!path.startsWith(CLAUDE_DIR3)) return null;
|
|
12459
12919
|
try {
|
|
12460
|
-
return readFileSync26(join34(harnessRoot,
|
|
12920
|
+
return readFileSync26(join34(harnessRoot, "templates", path.slice(CLAUDE_DIR3.length)), "utf8");
|
|
12461
12921
|
} catch {
|
|
12462
12922
|
return null;
|
|
12463
12923
|
}
|
|
12464
12924
|
}
|
|
12465
|
-
function
|
|
12466
|
-
const
|
|
12467
|
-
const
|
|
12925
|
+
function removeRecordedFile(path, ctx) {
|
|
12926
|
+
const { log, projectDir, io, out } = ctx;
|
|
12927
|
+
const abs = join34(projectDir, path);
|
|
12928
|
+
let foreign;
|
|
12929
|
+
try {
|
|
12930
|
+
foreign = foreignOwnedTarget(projectDir, path);
|
|
12931
|
+
} catch {
|
|
12932
|
+
out.unjudged.push(path);
|
|
12933
|
+
return;
|
|
12934
|
+
}
|
|
12935
|
+
if (foreign !== null) {
|
|
12936
|
+
out.forgotten.add(path);
|
|
12937
|
+
return;
|
|
12938
|
+
}
|
|
12939
|
+
const outside = outsideProjectTarget(projectDir, abs);
|
|
12940
|
+
if (outside !== null) {
|
|
12941
|
+
out.outside.push(outside);
|
|
12942
|
+
return;
|
|
12943
|
+
}
|
|
12944
|
+
let st2;
|
|
12945
|
+
try {
|
|
12946
|
+
st2 = lstatSync5(abs, { throwIfNoEntry: false });
|
|
12947
|
+
} catch {
|
|
12948
|
+
out.unjudged.push(path);
|
|
12949
|
+
return;
|
|
12950
|
+
}
|
|
12951
|
+
if (st2 === void 0 || st2.isSymbolicLink() && !existsSync26(abs)) {
|
|
12952
|
+
out.forgotten.add(path);
|
|
12953
|
+
return;
|
|
12954
|
+
}
|
|
12955
|
+
let effective = abs;
|
|
12956
|
+
let disk;
|
|
12957
|
+
try {
|
|
12958
|
+
const real = realpathSync3(abs);
|
|
12959
|
+
if (!statSync5(real).isFile()) {
|
|
12960
|
+
out.unjudged.push(path);
|
|
12961
|
+
return;
|
|
12962
|
+
}
|
|
12963
|
+
if (st2.isSymbolicLink()) effective = real;
|
|
12964
|
+
disk = readFileSync26(effective, "utf8");
|
|
12965
|
+
} catch {
|
|
12966
|
+
out.unjudged.push(path);
|
|
12967
|
+
return;
|
|
12968
|
+
}
|
|
12969
|
+
const verdict = judge({
|
|
12970
|
+
op: "remove",
|
|
12971
|
+
kind: "harness",
|
|
12972
|
+
rec: recorded(log, path, { isTarget: ctx.isTarget }),
|
|
12973
|
+
disk,
|
|
12974
|
+
next: templateNext(io.harnessRoot, log, path)
|
|
12975
|
+
});
|
|
12976
|
+
if (verdict.verdict !== "remove" && verdict.verdict !== "backup+remove") return;
|
|
12977
|
+
let backup = null;
|
|
12978
|
+
if (verdict.verdict === "backup+remove") {
|
|
12979
|
+
backup = io.write ? relative5(projectDir, backupFile(effective)).split(sep5).join("/") : `${relative5(projectDir, effective).split(sep5).join("/")}.backup-<time>`;
|
|
12980
|
+
}
|
|
12981
|
+
if (io.write) {
|
|
12982
|
+
if (effective !== abs) io.rm(abs);
|
|
12983
|
+
io.rm(effective);
|
|
12984
|
+
}
|
|
12985
|
+
out.removed.push(path);
|
|
12986
|
+
out.forgotten.add(path);
|
|
12987
|
+
ctx.pruneFrom.push(path);
|
|
12988
|
+
if (backup !== null) out.backedUp.push({ path, backup, line: verdict.line });
|
|
12989
|
+
}
|
|
12990
|
+
function stripSharedFiles(log, projectDir, paths, io, out, pruneFrom) {
|
|
12991
|
+
const made = new Map((log.externalFiles ?? []).map((f) => [f.path, f.sha256]));
|
|
12992
|
+
const created = new Set(
|
|
12993
|
+
(log.rootFiles ?? []).filter((f) => f.change === "created").map((f) => f.path)
|
|
12994
|
+
);
|
|
12995
|
+
const onRecord = /* @__PURE__ */ new Set([
|
|
12996
|
+
...(log.portions ?? []).map((p2) => p2.path),
|
|
12997
|
+
...made.keys(),
|
|
12998
|
+
...(log.rootFiles ?? []).map((f) => f.path)
|
|
12999
|
+
]);
|
|
13000
|
+
const clis = installedClis(log);
|
|
13001
|
+
const relevant = (path) => {
|
|
13002
|
+
const owners = ownersOf(path);
|
|
13003
|
+
return onRecord.has(path) || owners.length === 0 || owners.some((c3) => clis.includes(c3));
|
|
13004
|
+
};
|
|
12468
13005
|
for (const path of paths) {
|
|
12469
|
-
if (path
|
|
12470
|
-
if (!dirs.some((d2) => path.startsWith(d2))) continue;
|
|
13006
|
+
if (!relevant(path)) continue;
|
|
12471
13007
|
const abs = join34(projectDir, path);
|
|
12472
|
-
if (
|
|
12473
|
-
|
|
12474
|
-
|
|
12475
|
-
|
|
13008
|
+
if (safeExists(abs)) {
|
|
13009
|
+
const o = outsideProjectTarget(projectDir, abs);
|
|
13010
|
+
if (o !== null) {
|
|
13011
|
+
out.outside.push(o);
|
|
13012
|
+
continue;
|
|
13013
|
+
}
|
|
13014
|
+
}
|
|
13015
|
+
if (path === SETTINGS_TARGET && safeExists(abs)) {
|
|
13016
|
+
out.retiredWiring = removeRetiredHookRefs(
|
|
13017
|
+
abs,
|
|
13018
|
+
join34(projectDir, ".claude"),
|
|
13019
|
+
RETIRED_HOOK_WIRING_REFS,
|
|
13020
|
+
{ evenIfScriptExists: true, write: io.write }
|
|
13021
|
+
);
|
|
13022
|
+
}
|
|
13023
|
+
const sha = made.get(path);
|
|
13024
|
+
if (sha !== void 0 && removeHarnessMadeShared(path, sha, projectDir, io, out, pruneFrom)) {
|
|
12476
13025
|
continue;
|
|
12477
13026
|
}
|
|
12478
|
-
stripShared({
|
|
13027
|
+
const r = stripShared({
|
|
12479
13028
|
projectDir,
|
|
12480
13029
|
path,
|
|
12481
13030
|
portions: log.portions ?? [],
|
|
12482
13031
|
excluded: [...excludedIds(log)],
|
|
12483
|
-
remnant: (
|
|
12484
|
-
remnantLine: ()
|
|
12485
|
-
write:
|
|
13032
|
+
remnant: remnantFor(path, log, io.harnessRoot, projectDir),
|
|
13033
|
+
remnantLine: remnantLine(path),
|
|
13034
|
+
write: io.write,
|
|
13035
|
+
// 하네스가 만든 파일(#569 `created` · #13 `externalFiles`)은 몫을 걷고 남는 것이 없으면 파일째 지운다
|
|
13036
|
+
removeIfEmpty: created.has(path) || sha !== void 0
|
|
12486
13037
|
});
|
|
13038
|
+
if (r.fileRemoved) pruneFrom.push(path);
|
|
13039
|
+
out.shared.push(r);
|
|
12487
13040
|
}
|
|
12488
|
-
return outside;
|
|
12489
13041
|
}
|
|
12490
|
-
function
|
|
12491
|
-
|
|
13042
|
+
function removeHarnessMadeShared(path, sha, projectDir, io, out, pruneFrom) {
|
|
13043
|
+
const abs = join34(projectDir, path);
|
|
13044
|
+
if (!existsSync26(abs)) {
|
|
13045
|
+
out.forgotten.add(path);
|
|
13046
|
+
return true;
|
|
13047
|
+
}
|
|
13048
|
+
let effective = abs;
|
|
13049
|
+
let current;
|
|
13050
|
+
try {
|
|
13051
|
+
const real = realpathSync3(abs);
|
|
13052
|
+
if (!statSync5(real).isFile()) {
|
|
13053
|
+
out.unjudged.push(path);
|
|
13054
|
+
return true;
|
|
13055
|
+
}
|
|
13056
|
+
if (lstatSync5(abs).isSymbolicLink()) effective = real;
|
|
13057
|
+
current = readFileSync26(effective, "utf8");
|
|
13058
|
+
} catch {
|
|
13059
|
+
out.unjudged.push(path);
|
|
13060
|
+
return true;
|
|
13061
|
+
}
|
|
13062
|
+
if (hashContent(current) !== sha) {
|
|
13063
|
+
if (path === AGENTS_MD2) {
|
|
13064
|
+
out.kept.push(path);
|
|
13065
|
+
return true;
|
|
13066
|
+
}
|
|
13067
|
+
out.forgotten.add(path);
|
|
13068
|
+
return false;
|
|
13069
|
+
}
|
|
13070
|
+
if (path === AGENTS_MD2) {
|
|
13071
|
+
const verdict = agentsMdRemainder(current, io.harnessRoot);
|
|
13072
|
+
if (!verdict.ok) {
|
|
13073
|
+
out.unjudged.push(path);
|
|
13074
|
+
return true;
|
|
13075
|
+
}
|
|
13076
|
+
if (verdict.remainder !== null) {
|
|
13077
|
+
try {
|
|
13078
|
+
if (io.write) writeFileSync10(effective, neutralizeScaffoldBanner(verdict.remainder), "utf8");
|
|
13079
|
+
out.stripped.push(path);
|
|
13080
|
+
out.forgotten.add(path);
|
|
13081
|
+
} catch {
|
|
13082
|
+
out.unjudged.push(path);
|
|
13083
|
+
}
|
|
13084
|
+
return true;
|
|
13085
|
+
}
|
|
13086
|
+
}
|
|
13087
|
+
if (io.write) {
|
|
13088
|
+
if (effective !== abs) io.rm(abs);
|
|
13089
|
+
io.rm(effective);
|
|
13090
|
+
}
|
|
13091
|
+
out.shared.push({
|
|
13092
|
+
path,
|
|
13093
|
+
action: "removed",
|
|
13094
|
+
line: "",
|
|
13095
|
+
removed: [],
|
|
13096
|
+
kept: [],
|
|
13097
|
+
portions: [],
|
|
13098
|
+
fileRemoved: true
|
|
13099
|
+
});
|
|
13100
|
+
out.forgotten.add(path);
|
|
13101
|
+
pruneFrom.push(path);
|
|
13102
|
+
return true;
|
|
12492
13103
|
}
|
|
12493
|
-
function
|
|
12494
|
-
const
|
|
12495
|
-
|
|
12496
|
-
|
|
12497
|
-
|
|
12498
|
-
|
|
12499
|
-
if (
|
|
12500
|
-
const
|
|
12501
|
-
if (
|
|
12502
|
-
|
|
12503
|
-
|
|
12504
|
-
|
|
12505
|
-
|
|
12506
|
-
|
|
12507
|
-
|
|
13104
|
+
function restoreDisplaced(log, projectDir, scope, io, out) {
|
|
13105
|
+
const willBeGone = /* @__PURE__ */ new Set([
|
|
13106
|
+
...out.removed,
|
|
13107
|
+
...out.removedAnchor === void 0 ? [] : [out.removedAnchor]
|
|
13108
|
+
]);
|
|
13109
|
+
for (const f of log.rootFiles ?? []) {
|
|
13110
|
+
if (f.change !== "displaced" || !scope.has(f.path)) continue;
|
|
13111
|
+
const slot = join34(projectDir, f.path);
|
|
13112
|
+
if (outsideProjectTarget(projectDir, slot) !== null) continue;
|
|
13113
|
+
const backup = f.notes[0] ?? null;
|
|
13114
|
+
const d2 = judgeDisplaced({
|
|
13115
|
+
backup,
|
|
13116
|
+
backupExists: backup !== null && safeExists(join34(projectDir, backup)),
|
|
13117
|
+
slotEmpty: !safeExists(slot) || !io.write && willBeGone.has(f.path)
|
|
13118
|
+
});
|
|
13119
|
+
if (io.write && d2.verdict === "restore" && backup !== null) {
|
|
13120
|
+
mkdirSync7(dirname7(slot), { recursive: true });
|
|
13121
|
+
renameSync(join34(projectDir, backup), slot);
|
|
12508
13122
|
}
|
|
12509
|
-
|
|
12510
|
-
|
|
12511
|
-
|
|
13123
|
+
out.displaced.push({
|
|
13124
|
+
path: f.path,
|
|
13125
|
+
backup,
|
|
13126
|
+
verdict: d2.verdict,
|
|
13127
|
+
line: d2.line,
|
|
13128
|
+
forget: d2.record === "forget"
|
|
13129
|
+
});
|
|
13130
|
+
}
|
|
13131
|
+
}
|
|
13132
|
+
function topOf(path) {
|
|
13133
|
+
const i = path.indexOf("/");
|
|
13134
|
+
return i < 0 ? path : `${path.slice(0, i)}/`;
|
|
13135
|
+
}
|
|
13136
|
+
function harnessRemovalLines(r, log, projectDir, scope, preview) {
|
|
13137
|
+
const lines2 = [];
|
|
13138
|
+
const groups = /* @__PURE__ */ new Map();
|
|
13139
|
+
for (const p2 of r.removed) {
|
|
13140
|
+
const g = groups.get(topOf(p2)) ?? { removed: 0, backedUp: 0 };
|
|
13141
|
+
g.removed += 1;
|
|
13142
|
+
groups.set(topOf(p2), g);
|
|
13143
|
+
}
|
|
13144
|
+
for (const b2 of r.backedUp) {
|
|
13145
|
+
const g = groups.get(topOf(b2.path));
|
|
13146
|
+
if (g) g.backedUp += 1;
|
|
13147
|
+
}
|
|
13148
|
+
if (preview) {
|
|
13149
|
+
for (const [top, g] of groups) {
|
|
13150
|
+
const edited = g.backedUp > 0 ? ` (${g.backedUp} you edited \u2192 saved beside as *.backup-<time>)` : "";
|
|
13151
|
+
lines2.push(` \u25CB remove ${g.removed} harness file(s) under ${top}${edited}`);
|
|
13152
|
+
}
|
|
13153
|
+
} else if (groups.size > 0) {
|
|
13154
|
+
const parts = [...groups].map(
|
|
13155
|
+
([top, g], i) => i === 0 ? `${top} ${g.removed} harness file(s)` : `${top} ${g.removed}`
|
|
13156
|
+
);
|
|
13157
|
+
lines2.push(` ${status.success(`removed ${parts.join(" \xB7 ")}`)}`);
|
|
13158
|
+
for (const b2 of r.backedUp) {
|
|
13159
|
+
const what = b2.line.includes("<file>.backup-<time>") ? b2.line.replace("<file>.backup-<time>", b2.backup) : `${b2.line} (${b2.backup})`;
|
|
13160
|
+
lines2.push(` ${status.success(`backed up ${b2.path} \u2014 ${what}`)}`);
|
|
12512
13161
|
}
|
|
13162
|
+
}
|
|
13163
|
+
if (r.importStripped) {
|
|
13164
|
+
lines2.push(
|
|
13165
|
+
preview ? " \u25CB strip harness @import from CLAUDE.md (\uBCF8\uBB38 \uBCF4\uC874)" : ` ${status.success("CLAUDE.md \u2014 harness @import removed (\uBCF8\uBB38 \uBCF4\uC874)")}`
|
|
13166
|
+
);
|
|
13167
|
+
}
|
|
13168
|
+
lines2.push(...r.anchor.lines);
|
|
13169
|
+
const restore = r.displaced.filter((d2) => d2.verdict === "restore");
|
|
13170
|
+
if (preview && restore.length > 0) {
|
|
13171
|
+
lines2.push(
|
|
13172
|
+
` \u25CB put back ${restore.length} file(s) of yours (displaced): ${restore.map((d2) => d2.path).join(" \xB7 ")}`
|
|
13173
|
+
);
|
|
13174
|
+
}
|
|
13175
|
+
for (const d2 of r.displaced) {
|
|
13176
|
+
if (d2.verdict === "restore" && !preview) {
|
|
13177
|
+
lines2.push(` ${status.success(`put back ${d2.path} (from ${d2.backup})`)}`);
|
|
13178
|
+
} else if (d2.verdict === "advise") {
|
|
13179
|
+
lines2.push(` ${c.yellow("\u2298")} ${d2.path} \u2014 ${d2.line}`);
|
|
13180
|
+
}
|
|
13181
|
+
}
|
|
13182
|
+
for (const path of r.stripped) {
|
|
13183
|
+
lines2.push(
|
|
13184
|
+
preview ? ` \u25CB strip harness sections from ${path} (\uBCF8\uBB38 \uBCF4\uC874)` : ` ${status.success(`${path} \u2014 harness sections removed (\uBCF8\uBB38 \uBCF4\uC874)`)}`
|
|
13185
|
+
);
|
|
13186
|
+
}
|
|
13187
|
+
for (const path of r.unjudged) {
|
|
13188
|
+
lines2.push(
|
|
13189
|
+
path === AGENTS_MD2 ? ` ${c.yellow("\u2298")} ${path} kept \u2014 harness sections could not be separated (a file or template could not be read, or the write failed). Remove manually if intended.` : ` ${c.yellow("\u2298")} ${path} left \u2014 could not read it to judge. Remove manually if intended.`
|
|
13190
|
+
);
|
|
13191
|
+
}
|
|
13192
|
+
lines2.push(...outsideLines(r.outside, log));
|
|
13193
|
+
for (const path of r.kept) {
|
|
13194
|
+
lines2.push(
|
|
13195
|
+
` ${c.yellow("\u2298")} ${path} kept \u2014 modified since install. Remove manually if intended.`
|
|
13196
|
+
);
|
|
13197
|
+
}
|
|
13198
|
+
if (r.retiredWiring.length > 0) {
|
|
13199
|
+
const refs = r.retiredWiring.join(" \xB7 ");
|
|
13200
|
+
lines2.push(
|
|
13201
|
+
preview ? ` \u25CB remove retired harness hook wiring from ${SETTINGS_TARGET}: ${refs}` : ` ${status.success(`${SETTINGS_TARGET} \u2014 removed retired harness hook wiring: ${refs}`)}`
|
|
13202
|
+
);
|
|
13203
|
+
}
|
|
13204
|
+
lines2.push(...sharedStripLines(r.shared, preview));
|
|
13205
|
+
lines2.push(...leftFolderLines(r, log, projectDir, scope, preview));
|
|
13206
|
+
return lines2;
|
|
13207
|
+
}
|
|
13208
|
+
function outsideLines(outside, log) {
|
|
13209
|
+
const byLink = /* @__PURE__ */ new Map();
|
|
13210
|
+
for (const o of outside) if (!byLink.has(o.link)) byLink.set(o.link, o);
|
|
13211
|
+
return [...byLink.values()].map(
|
|
13212
|
+
(o) => o.link === o.path ? ` ${c.yellow("\u2298")} ${o.path} kept \u2014 link target is outside the project (${o.target}). Not touched; remove manually if intended.` : keptOutsideLinkLine(o, log)
|
|
13213
|
+
);
|
|
13214
|
+
}
|
|
13215
|
+
function leftFolderLines(r, log, projectDir, scope, preview) {
|
|
13216
|
+
const assetFiles = new Set(log.assets.flatMap((a) => (a.files ?? []).map((f) => f.path)));
|
|
13217
|
+
const gone = new Set(
|
|
13218
|
+
preview ? [...r.removed, ...r.shared.filter((s) => s.fileRemoved).map((s) => s.path)] : []
|
|
13219
|
+
);
|
|
13220
|
+
const back = preview ? r.displaced.filter((d2) => d2.verdict === "restore").map((d2) => d2.path) : [];
|
|
13221
|
+
const said = (full) => r.unjudged.includes(full) || r.kept.includes(full) || r.outside.some((o) => o.path === full || full.startsWith(`${o.link}/`));
|
|
13222
|
+
const whose = "not on the install record \u2014 yours, or left by an older harness version; delete by hand any that are not yours";
|
|
13223
|
+
const lines2 = [];
|
|
13224
|
+
for (const dir of scope.folders) {
|
|
13225
|
+
if (outsideLinkAt(projectDir, dir) !== null) continue;
|
|
13226
|
+
const entries = /* @__PURE__ */ new Map();
|
|
13227
|
+
for (const n of yoursUnder(projectDir, dir)) {
|
|
13228
|
+
const full = `${dir}/${n.name}`;
|
|
13229
|
+
if (full === LEGACY_LOG) continue;
|
|
13230
|
+
if (!gone.has(full) && !assetFiles.has(full) && !said(full)) entries.set(n.name, n.shown);
|
|
13231
|
+
}
|
|
13232
|
+
for (const p2 of back)
|
|
13233
|
+
if (p2.startsWith(`${dir}/`)) entries.set(p2.slice(dir.length + 1), p2.slice(dir.length + 1));
|
|
13234
|
+
const shown = [...entries.values()].sort();
|
|
13235
|
+
if (shown.length === 0) continue;
|
|
13236
|
+
const head = shown.slice(0, LEFT_FILES_SHOWN).join(" \xB7 ");
|
|
13237
|
+
const more = shown.length > LEFT_FILES_SHOWN ? ` +${shown.length - LEFT_FILES_SHOWN} more` : "";
|
|
13238
|
+
const what = `${dir}/ \u2014 ${shown.length} file(s) ${whose}: ${head}${more}`;
|
|
13239
|
+
lines2.push(preview ? ` \u25CB leave ${what}` : ` ${c.yellow("\u2298")} left ${what}`);
|
|
13240
|
+
}
|
|
13241
|
+
return lines2;
|
|
13242
|
+
}
|
|
13243
|
+
function yoursUnder(projectDir, dir) {
|
|
13244
|
+
const out = [];
|
|
13245
|
+
const walk2 = (abs2, prefix) => {
|
|
13246
|
+
let entries;
|
|
12513
13247
|
try {
|
|
12514
|
-
|
|
12515
|
-
kept.push(path);
|
|
12516
|
-
continue;
|
|
12517
|
-
}
|
|
12518
|
-
rm(target);
|
|
12519
|
-
removed.push(path);
|
|
13248
|
+
entries = readdirSync6(abs2, { withFileTypes: true });
|
|
12520
13249
|
} catch {
|
|
12521
|
-
|
|
13250
|
+
return;
|
|
12522
13251
|
}
|
|
13252
|
+
for (const e2 of entries) {
|
|
13253
|
+
const name = `${prefix}${e2.name}`;
|
|
13254
|
+
if (e2.isDirectory()) walk2(join34(abs2, e2.name), `${name}/`);
|
|
13255
|
+
else if (BACKUP_SUFFIX.test(e2.name)) continue;
|
|
13256
|
+
else if (e2.isSymbolicLink()) {
|
|
13257
|
+
let target = "?";
|
|
13258
|
+
try {
|
|
13259
|
+
target = readlinkSync(join34(abs2, e2.name));
|
|
13260
|
+
} catch {
|
|
13261
|
+
}
|
|
13262
|
+
out.push({ name, shown: `${name} (your link \u2192 ${target})` });
|
|
13263
|
+
} else out.push({ name, shown: name });
|
|
13264
|
+
}
|
|
13265
|
+
};
|
|
13266
|
+
const abs = join34(projectDir, dir);
|
|
13267
|
+
try {
|
|
13268
|
+
if (!statSync5(abs).isDirectory()) return [];
|
|
13269
|
+
} catch {
|
|
13270
|
+
return [];
|
|
12523
13271
|
}
|
|
12524
|
-
|
|
13272
|
+
walk2(abs, "");
|
|
13273
|
+
return out;
|
|
12525
13274
|
}
|
|
12526
|
-
function
|
|
13275
|
+
function assetsLeftLines(log, projectDir, scope) {
|
|
13276
|
+
const lines2 = [];
|
|
13277
|
+
for (const a of log.assets) {
|
|
13278
|
+
const here = (a.files ?? []).map((f) => f.path).filter((p2) => scope.has(p2) && safeExists(join34(projectDir, p2)));
|
|
13279
|
+
if (here.length === 0) continue;
|
|
13280
|
+
const slots = [...new Set(here.map((p2) => p2.split("/").slice(0, 3).join("/")))].sort();
|
|
13281
|
+
lines2.push(
|
|
13282
|
+
` ${c.yellow("\u2298")} left ${slots.join(" \xB7 ")} (asset ${a.id}) \u2014 remove with: agent-harness uninstall --only ${a.id}`
|
|
13283
|
+
);
|
|
13284
|
+
}
|
|
13285
|
+
return lines2;
|
|
13286
|
+
}
|
|
13287
|
+
function removeAnchor(log, projectDir, io) {
|
|
13288
|
+
const rootMd = log.templates?.rootClaudeMd;
|
|
13289
|
+
if (!rootMd) return { removed: false, lines: [] };
|
|
13290
|
+
const abs = join34(projectDir, rootMd.path);
|
|
13291
|
+
if (!existsSync26(abs)) return { removed: false, lines: [] };
|
|
13292
|
+
const outside = outsideProjectTarget(projectDir, abs);
|
|
13293
|
+
if (outside !== null) return { removed: false, lines: outsideLines([outside], log) };
|
|
13294
|
+
let disk;
|
|
12527
13295
|
try {
|
|
12528
|
-
|
|
13296
|
+
disk = readFileSync26(abs, "utf8");
|
|
13297
|
+
} catch {
|
|
13298
|
+
return {
|
|
13299
|
+
removed: false,
|
|
13300
|
+
lines: [
|
|
13301
|
+
` ${c.yellow("\u2298")} ${rootMd.path} left \u2014 could not read it to judge. Remove manually if intended.`
|
|
13302
|
+
]
|
|
13303
|
+
};
|
|
13304
|
+
}
|
|
13305
|
+
const verdict = judge({
|
|
13306
|
+
op: "remove",
|
|
13307
|
+
kind: "harness",
|
|
13308
|
+
rec: { state: "sha", sha256: rootMd.sha256 },
|
|
13309
|
+
disk,
|
|
13310
|
+
next: null
|
|
13311
|
+
});
|
|
13312
|
+
const edited = verdict.verdict === "backup+remove";
|
|
13313
|
+
if (edited && rootMd.path === "CLAUDE.md") {
|
|
13314
|
+
return {
|
|
13315
|
+
removed: false,
|
|
13316
|
+
lines: [
|
|
13317
|
+
io.write ? ` ${c.yellow("\u2298")} ${rootMd.path} kept \u2014 modified since install. Remove manually if intended.` : ` \u25CB keep ${rootMd.path} (modified since install \u2014 preserved)`
|
|
13318
|
+
]
|
|
13319
|
+
};
|
|
13320
|
+
}
|
|
13321
|
+
if (!io.write) {
|
|
13322
|
+
return {
|
|
13323
|
+
removed: true,
|
|
13324
|
+
lines: [
|
|
13325
|
+
edited ? ` \u25CB remove ${rootMd.path} (you edited it \u2192 saved beside as ${rootMd.path}.backup-<time>)` : ` \u25CB remove ${rootMd.path}`
|
|
13326
|
+
]
|
|
13327
|
+
};
|
|
13328
|
+
}
|
|
13329
|
+
const backup = edited ? relative5(projectDir, backupFile(abs)).split(sep5).join("/") : null;
|
|
13330
|
+
io.rm(abs);
|
|
13331
|
+
return {
|
|
13332
|
+
removed: true,
|
|
13333
|
+
lines: [
|
|
13334
|
+
backup === null ? ` ${status.success(`${rootMd.path} removed`)}` : ` ${status.success(`backed up ${rootMd.path} \u2014 ${verdict.line.replace("<file>.backup-<time>", backup)}`)}`
|
|
13335
|
+
]
|
|
13336
|
+
};
|
|
13337
|
+
}
|
|
13338
|
+
var AGENTS_MD2 = "AGENTS.md";
|
|
13339
|
+
function readAgentsMdTemplate(harnessRoot) {
|
|
13340
|
+
try {
|
|
13341
|
+
return readFileSync26(join34(harnessRoot, AGENTS_MD_TEMPLATE), "utf8");
|
|
12529
13342
|
} catch {
|
|
12530
13343
|
return null;
|
|
12531
13344
|
}
|
|
12532
13345
|
}
|
|
12533
|
-
function
|
|
13346
|
+
function safeRealpath(abs) {
|
|
12534
13347
|
try {
|
|
12535
|
-
return
|
|
13348
|
+
return realpathSync3(abs);
|
|
12536
13349
|
} catch {
|
|
12537
|
-
return
|
|
13350
|
+
return null;
|
|
12538
13351
|
}
|
|
12539
13352
|
}
|
|
12540
13353
|
function neutralizeScaffoldBanner(text) {
|
|
@@ -12552,71 +13365,6 @@ function agentsMdRemainder(current, harnessRoot) {
|
|
|
12552
13365
|
})
|
|
12553
13366
|
};
|
|
12554
13367
|
}
|
|
12555
|
-
function removeExternalFiles(log, projectDir, rm, harnessRoot) {
|
|
12556
|
-
const removed = [];
|
|
12557
|
-
const kept = [];
|
|
12558
|
-
const stripped = [];
|
|
12559
|
-
const unjudged = [];
|
|
12560
|
-
const outside = [];
|
|
12561
|
-
for (const { path, sha256 } of log.externalFiles ?? []) {
|
|
12562
|
-
const abs = join34(projectDir, path);
|
|
12563
|
-
if (!existsSync25(abs)) continue;
|
|
12564
|
-
let current;
|
|
12565
|
-
let effective = abs;
|
|
12566
|
-
const foreignSkillSlot = path.startsWith(".agents/skills/");
|
|
12567
|
-
try {
|
|
12568
|
-
const isLink = lstatSync5(abs).isSymbolicLink();
|
|
12569
|
-
if (isLink && foreignSkillSlot) continue;
|
|
12570
|
-
const resolved = realpathSync3(abs);
|
|
12571
|
-
if (!statSync4(resolved).isFile()) continue;
|
|
12572
|
-
if (isOutsideProject(projectDir, resolved)) {
|
|
12573
|
-
outside.push({ path, target: resolved });
|
|
12574
|
-
continue;
|
|
12575
|
-
}
|
|
12576
|
-
if (isLink) effective = resolved;
|
|
12577
|
-
current = readFileSync26(effective, "utf8");
|
|
12578
|
-
} catch {
|
|
12579
|
-
unjudged.push(path);
|
|
12580
|
-
continue;
|
|
12581
|
-
}
|
|
12582
|
-
if (hashContent(current) !== sha256) {
|
|
12583
|
-
kept.push(path);
|
|
12584
|
-
continue;
|
|
12585
|
-
}
|
|
12586
|
-
if (path === AGENTS_MD2) {
|
|
12587
|
-
const verdict = agentsMdRemainder(current, harnessRoot);
|
|
12588
|
-
if (!verdict.ok) {
|
|
12589
|
-
unjudged.push(path);
|
|
12590
|
-
continue;
|
|
12591
|
-
}
|
|
12592
|
-
if (verdict.remainder !== null) {
|
|
12593
|
-
try {
|
|
12594
|
-
writeFileSync9(effective, neutralizeScaffoldBanner(verdict.remainder), "utf8");
|
|
12595
|
-
stripped.push(path);
|
|
12596
|
-
} catch {
|
|
12597
|
-
unjudged.push(path);
|
|
12598
|
-
}
|
|
12599
|
-
continue;
|
|
12600
|
-
}
|
|
12601
|
-
}
|
|
12602
|
-
rm(effective);
|
|
12603
|
-
if (effective !== abs) {
|
|
12604
|
-
try {
|
|
12605
|
-
rm(abs);
|
|
12606
|
-
} catch {
|
|
12607
|
-
}
|
|
12608
|
-
}
|
|
12609
|
-
removed.push(path);
|
|
12610
|
-
}
|
|
12611
|
-
for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname6(join34(projectDir, path)));
|
|
12612
|
-
const decisions = join34(projectDir, "docs", "decisions");
|
|
12613
|
-
try {
|
|
12614
|
-
if (existsSync25(decisions) && readdirSync6(decisions).length === 0)
|
|
12615
|
-
rmSync5(decisions, { recursive: true });
|
|
12616
|
-
} catch {
|
|
12617
|
-
}
|
|
12618
|
-
return { removed, kept, stripped, unjudged, outside };
|
|
12619
|
-
}
|
|
12620
13368
|
function removeToolFiles(files, projectDir, rm) {
|
|
12621
13369
|
const notes = [];
|
|
12622
13370
|
const removed = [];
|
|
@@ -12650,7 +13398,7 @@ function removeToolFiles(files, projectDir, rm) {
|
|
|
12650
13398
|
notes.push(`left ${path} \u2014 ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
12651
13399
|
}
|
|
12652
13400
|
}
|
|
12653
|
-
for (const path of removed) pruneEmptyDirsUpward(projectDir,
|
|
13401
|
+
for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname7(join34(projectDir, path)));
|
|
12654
13402
|
return failed > 0 ? { ok: false, message: `${failed} file(s) could not be removed`, notes } : { ok: true, notes };
|
|
12655
13403
|
}
|
|
12656
13404
|
function pruneEmptyDirsUpward(projectDir, startDir) {
|
|
@@ -12664,7 +13412,7 @@ function pruneEmptyDirsUpward(projectDir, startDir) {
|
|
|
12664
13412
|
} catch {
|
|
12665
13413
|
return;
|
|
12666
13414
|
}
|
|
12667
|
-
cursor =
|
|
13415
|
+
cursor = dirname7(cursor);
|
|
12668
13416
|
}
|
|
12669
13417
|
}
|
|
12670
13418
|
function stripRootImport(projectDir) {
|
|
@@ -12673,121 +13421,31 @@ function stripRootImport(projectDir) {
|
|
|
12673
13421
|
if (stripped === null) return false;
|
|
12674
13422
|
try {
|
|
12675
13423
|
backupIfLossyUtf8(join34(projectDir, "CLAUDE.md"));
|
|
12676
|
-
|
|
13424
|
+
writeFileSync10(join34(projectDir, "CLAUDE.md"), stripped, "utf8");
|
|
12677
13425
|
return true;
|
|
12678
13426
|
} catch {
|
|
12679
13427
|
return false;
|
|
12680
13428
|
}
|
|
12681
13429
|
}
|
|
12682
|
-
function previewExternalLines(installLog, projectDir, harnessRoot, movedDirs = []) {
|
|
12683
|
-
const lines2 = [];
|
|
12684
|
-
let removable = 0;
|
|
12685
|
-
for (const { path, sha256 } of installLog.externalFiles ?? []) {
|
|
12686
|
-
const abs = join34(projectDir, path);
|
|
12687
|
-
const underMoved = underAny(path, movedDirs);
|
|
12688
|
-
const isLink = !underMoved || existsSync25(abs) ? safeIsSymlink(abs) : false;
|
|
12689
|
-
if (underMoved && !isLink) continue;
|
|
12690
|
-
if (!existsSync25(abs)) continue;
|
|
12691
|
-
let effective = abs;
|
|
12692
|
-
const foreignSkillSlot = path.startsWith(".agents/skills/");
|
|
12693
|
-
try {
|
|
12694
|
-
const isLinkPath = lstatSync5(abs).isSymbolicLink();
|
|
12695
|
-
if (isLinkPath && foreignSkillSlot) continue;
|
|
12696
|
-
const resolved = realpathSync3(abs);
|
|
12697
|
-
if (!statSync4(resolved).isFile()) continue;
|
|
12698
|
-
if (isOutsideProject(projectDir, resolved)) {
|
|
12699
|
-
lines2.push(` \u25CB keep ${path} (link target outside project: ${resolved} \u2014 preserved)`);
|
|
12700
|
-
continue;
|
|
12701
|
-
}
|
|
12702
|
-
if (isLinkPath) effective = resolved;
|
|
12703
|
-
} catch {
|
|
12704
|
-
continue;
|
|
12705
|
-
}
|
|
12706
|
-
const current = readFileSync26(effective, "utf8");
|
|
12707
|
-
if (hashContent(current) !== sha256) {
|
|
12708
|
-
lines2.push(` \u25CB keep ${path} (modified since install \u2014 preserved)`);
|
|
12709
|
-
continue;
|
|
12710
|
-
}
|
|
12711
|
-
if (path === AGENTS_MD2) {
|
|
12712
|
-
const verdict = agentsMdRemainder(current, harnessRoot);
|
|
12713
|
-
if (!verdict.ok) {
|
|
12714
|
-
lines2.push(` \u25CB keep ${path} (harness sections could not be separated \u2014 preserved)`);
|
|
12715
|
-
continue;
|
|
12716
|
-
}
|
|
12717
|
-
if (verdict.remainder !== null) {
|
|
12718
|
-
lines2.push(` \u25CB strip harness sections from ${path} (\uBCF8\uBB38 \uBCF4\uC874)`);
|
|
12719
|
-
continue;
|
|
12720
|
-
}
|
|
12721
|
-
}
|
|
12722
|
-
removable += 1;
|
|
12723
|
-
}
|
|
12724
|
-
if (removable > 0) lines2.unshift(` \u25CB remove ${removable} CLI output file(s)`);
|
|
12725
|
-
return lines2;
|
|
12726
|
-
}
|
|
12727
|
-
function externalRemovalLines(external) {
|
|
12728
|
-
const lines2 = [];
|
|
12729
|
-
if (external.removed.length > 0) {
|
|
12730
|
-
lines2.push(` ${status.success(`CLI outputs removed: ${external.removed.length} file(s)`)}`);
|
|
12731
|
-
}
|
|
12732
|
-
for (const path of external.stripped) {
|
|
12733
|
-
lines2.push(` ${status.success(`${path} \u2014 harness sections removed (\uBCF8\uBB38 \uBCF4\uC874)`)}`);
|
|
12734
|
-
}
|
|
12735
|
-
for (const path of external.unjudged) {
|
|
12736
|
-
lines2.push(
|
|
12737
|
-
` ${c.yellow("\u2298")} ${path} kept \u2014 harness sections could not be separated (a file or template could not be read, or the write failed). Remove manually if intended.`
|
|
12738
|
-
);
|
|
12739
|
-
}
|
|
12740
|
-
const outsideGroups = /* @__PURE__ */ new Map();
|
|
12741
|
-
for (const o of external.outside) {
|
|
12742
|
-
const key = `${dirname6(o.path)}\0${dirname6(o.target)}`;
|
|
12743
|
-
outsideGroups.set(key, [...outsideGroups.get(key) ?? [], o]);
|
|
12744
|
-
}
|
|
12745
|
-
for (const group of outsideGroups.values()) {
|
|
12746
|
-
const first = group[0];
|
|
12747
|
-
if (!first) continue;
|
|
12748
|
-
if (group.length === 1) {
|
|
12749
|
-
lines2.push(
|
|
12750
|
-
` ${c.yellow("\u2298")} ${first.path} kept \u2014 link target is outside the project (${first.target}). Not touched; remove manually if intended.`
|
|
12751
|
-
);
|
|
12752
|
-
} else {
|
|
12753
|
-
lines2.push(
|
|
12754
|
-
` ${c.yellow("\u2298")} ${dirname6(first.path)}/ kept ${group.length} file(s) \u2014 link target is outside the project (${dirname6(first.target)}). Not touched; remove manually if intended.`
|
|
12755
|
-
);
|
|
12756
|
-
}
|
|
12757
|
-
}
|
|
12758
|
-
for (const path of external.kept) {
|
|
12759
|
-
lines2.push(
|
|
12760
|
-
` ${c.yellow("\u2298")} ${path} kept \u2014 modified since install. Remove manually if intended.`
|
|
12761
|
-
);
|
|
12762
|
-
}
|
|
12763
|
-
return lines2;
|
|
12764
|
-
}
|
|
12765
13430
|
function hasRootImport(projectDir) {
|
|
12766
13431
|
const current = readRootClaudeMd(projectDir);
|
|
12767
13432
|
return current !== null && stripHarnessImport(current) !== null;
|
|
12768
13433
|
}
|
|
12769
13434
|
function readRootClaudeMd(projectDir) {
|
|
12770
13435
|
const path = join34(projectDir, "CLAUDE.md");
|
|
12771
|
-
if (!
|
|
13436
|
+
if (!existsSync26(path)) return null;
|
|
12772
13437
|
try {
|
|
12773
13438
|
return readFileSync26(path, "utf8");
|
|
12774
13439
|
} catch {
|
|
12775
13440
|
return null;
|
|
12776
13441
|
}
|
|
12777
13442
|
}
|
|
12778
|
-
function rootClaudeMdModified(log, projectDir) {
|
|
12779
|
-
const rootMd = log.templates.rootClaudeMd;
|
|
12780
|
-
if (!rootMd) return false;
|
|
12781
|
-
const path = join34(projectDir, rootMd.path);
|
|
12782
|
-
if (!existsSync25(path)) return false;
|
|
12783
|
-
return hashContent(readFileSync26(path, "utf8")) !== rootMd.sha256;
|
|
12784
|
-
}
|
|
12785
13443
|
function defaultSpawn2(cmd, args) {
|
|
12786
13444
|
return spawnSync2(cmd, [...args], { encoding: "utf8", stdio: "pipe", timeout: 12e4 });
|
|
12787
13445
|
}
|
|
12788
13446
|
function defaultRm(path) {
|
|
12789
|
-
if (
|
|
12790
|
-
|
|
13447
|
+
if (existsSync26(path)) {
|
|
13448
|
+
rmSync4(path, { recursive: true, force: true });
|
|
12791
13449
|
}
|
|
12792
13450
|
}
|
|
12793
13451
|
function registerUninstallCommand(cli) {
|
|
@@ -12795,10 +13453,10 @@ function registerUninstallCommand(cli) {
|
|
|
12795
13453
|
default: process.cwd()
|
|
12796
13454
|
}).option("--dry-run", "[Mode] List reverse steps without executing").option(
|
|
12797
13455
|
"--keep-templates",
|
|
12798
|
-
"[
|
|
13456
|
+
"[Removed in v26.169.0] Rejected \u2014 prints the `--only <ids>` that removes only the external assets"
|
|
12799
13457
|
).option(
|
|
12800
13458
|
"--only <ids>",
|
|
12801
|
-
"[Scope] Remove only these assets (comma-separated ids from `agent-harness list`).
|
|
13459
|
+
"[Scope] Remove only these assets (comma-separated ids from `agent-harness list`). Harness files untouched"
|
|
12802
13460
|
).option(
|
|
12803
13461
|
"--cli <name>",
|
|
12804
13462
|
`[Scope] Remove one CLI only (${CLI_BASES.join(" | ")}). Shared files stay until the last user leaves`
|
|
@@ -12809,6 +13467,7 @@ function registerUninstallCommand(cli) {
|
|
|
12809
13467
|
function shouldRunInteractive(options, isTty) {
|
|
12810
13468
|
if (!isTty) return false;
|
|
12811
13469
|
if (options.yes || options.dryRun) return false;
|
|
13470
|
+
if (options.keepTemplates) return false;
|
|
12812
13471
|
return options.only === void 0 && options.cli === void 0;
|
|
12813
13472
|
}
|
|
12814
13473
|
async function dispatchUninstall(options) {
|
|
@@ -12924,11 +13583,24 @@ function installedTargetState(projectDir) {
|
|
|
12924
13583
|
const log = readInstallLog(projectDir);
|
|
12925
13584
|
if (!log) return { installed: [], projectScoped: [] };
|
|
12926
13585
|
const mcp = recordedMcpAssetIds(log);
|
|
13586
|
+
const skills = recordedBundledSkillIds(log);
|
|
12927
13587
|
return {
|
|
12928
|
-
installed: [...log.assets.map((a) => a.id), ...mcp],
|
|
12929
|
-
projectScoped: [
|
|
13588
|
+
installed: [...log.assets.map((a) => a.id), ...mcp, ...skills],
|
|
13589
|
+
projectScoped: [
|
|
13590
|
+
...log.assets.filter((a) => a.scope !== "global").map((a) => a.id),
|
|
13591
|
+
...mcp,
|
|
13592
|
+
...skills
|
|
13593
|
+
]
|
|
12930
13594
|
};
|
|
12931
13595
|
}
|
|
13596
|
+
function recordedBundledSkillIds(log) {
|
|
13597
|
+
if (log === null) return [];
|
|
13598
|
+
return INTERNAL_BUNDLED_SKILL_IDS.filter(
|
|
13599
|
+
(id) => [`.claude/skills/${id}/SKILL.md`, `.agents/skills/${id}/SKILL.md`].some(
|
|
13600
|
+
(path) => recorded(log, path).state === "sha"
|
|
13601
|
+
)
|
|
13602
|
+
);
|
|
13603
|
+
}
|
|
12932
13604
|
function wizardRecommended(tracks) {
|
|
12933
13605
|
return [
|
|
12934
13606
|
.../* @__PURE__ */ new Set([
|
|
@@ -12966,6 +13638,7 @@ function classifyUpdateIntent(log, confirmed, legacyTracks = []) {
|
|
|
12966
13638
|
if (recorded2.some((a) => a.scope !== "global" && !picked.has(a.id) && !out.has(a.id)))
|
|
12967
13639
|
return "add";
|
|
12968
13640
|
if (recordedMcpAssetIds(log).some((id) => !picked.has(id) && !out.has(id))) return "add";
|
|
13641
|
+
if (recordedBundledSkillIds(log).some((id) => !picked.has(id) && !out.has(id))) return "add";
|
|
12969
13642
|
const offered = new Set(listBaselineTargets({ tracks: confirmed.tracks }).map((t) => t.id));
|
|
12970
13643
|
const recordedBaseline = [...out].filter((id) => offered.has(id));
|
|
12971
13644
|
if (!sameSet(confirmed.baselineExclude, recordedBaseline)) return "add";
|
|
@@ -12983,7 +13656,9 @@ function coveredByRecord(log, legacyTracks) {
|
|
|
12983
13656
|
...(log?.assets ?? []).map((a) => a.id),
|
|
12984
13657
|
// #709 — 기록된 선택 MCP 서버 · 기록 트랙의 기본 MCP 행은 refresh 가 그대로 둔다(`.mcp.json` 몫 기록 · 트랙 표)
|
|
12985
13658
|
...recordedMcpAssetIds(log),
|
|
12986
|
-
...trackDefaultMcpAssetIds(resolveBundleRoot(), recordTracks)
|
|
13659
|
+
...trackDefaultMcpAssetIds(resolveBundleRoot(), recordTracks),
|
|
13660
|
+
// #760 — 기록된 번들 스킬은 refresh 가 그대로 갱신한다(`skillFiles` · `externalFiles` 기준선)
|
|
13661
|
+
...recordedBundledSkillIds(log)
|
|
12987
13662
|
]);
|
|
12988
13663
|
for (const id of recommendedExternalAssets(recordTracks)) {
|
|
12989
13664
|
const asset = EXTERNAL_ASSETS.find((a) => a.id === id);
|
|
@@ -13253,7 +13928,9 @@ async function confirmUpdate(input) {
|
|
|
13253
13928
|
...baselineExclude,
|
|
13254
13929
|
...unchecked(log, tracks, assetIds, baselineIds),
|
|
13255
13930
|
// #709 — 기록된 선택 MCP 서버의 체크를 풀었으면 뺀다(opt-in 이라 추천 대비 빼기에 안 든다)
|
|
13256
|
-
...recordedMcpAssetIds(log).filter((id) => !assetIds.includes(id))
|
|
13931
|
+
...recordedMcpAssetIds(log).filter((id) => !assetIds.includes(id)),
|
|
13932
|
+
// #760 — 기록된 번들 스킬(opt-in 포함)의 체크를 풀었으면 뺀다 — 같은 이유
|
|
13933
|
+
...recordedBundledSkillIds(log).filter((id) => !assetIds.includes(id))
|
|
13257
13934
|
])
|
|
13258
13935
|
];
|
|
13259
13936
|
const options = {
|