@uzysjung/agent-harness 26.83.0 → 26.84.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 +1 -1
- package/README.md +34 -3
- package/dist/index.js +102 -63
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.ko.md
CHANGED
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# uzys-agent-harness
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**Install only the AI-coding workflows your tech stack actually needs — vetted, curated, and set up with one command across Claude Code, Codex, OpenCode & Antigravity.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Coding agents keep getting stronger out of the box — piling on skills and MCPs you'll never use just bloats their context. And the awesome-lists have too many options to wade through. `agent-harness` curates by **tech stack**: of the vetted options, you install only what this project actually calls for. **Claude Code is first-class; Codex / OpenCode / Antigravity get the skills + rules layer.** Project scope by default — no global pollution unless you ask.
|
|
6
6
|
|
|
7
7
|
[](LICENSE)
|
|
8
8
|
[](https://github.com/uzysjung/uzys-agent-harness/releases)
|
|
@@ -10,6 +10,8 @@ Pick a stack track. Get a curated set of **vetted** skills, plugins, and rules
|
|
|
10
10
|
|
|
11
11
|

|
|
12
12
|
|
|
13
|
+
> **What "vetted" means** — ≥ 1000 GitHub stars + active maintenance + a Docker install-verification run, re-checked by CI ([catalog-verify](docs/COMPATIBILITY.md), [trust-tier-drift](.github/workflows/)). It is **not** a line-by-line security audit or a prompt-injection scan of asset contents. Treat installed assets like any third-party dependency — see [SECURITY.md](SECURITY.md).
|
|
14
|
+
|
|
13
15
|
🇰🇷 [한국어](./README.ko.md)
|
|
14
16
|
|
|
15
17
|
---
|
|
@@ -59,6 +61,21 @@ npx -y @uzysjung/agent-harness install \
|
|
|
59
61
|
|
|
60
62
|
---
|
|
61
63
|
|
|
64
|
+
## Installing into an existing project
|
|
65
|
+
|
|
66
|
+
`agent-harness` never silently overwrites your config. Before replacing an **editable** file whose contents differ, it writes a timestamped backup next to it — and every backup path is printed in the install summary (`backup` rows). Nothing is deleted.
|
|
67
|
+
|
|
68
|
+
| You already have… | What happens |
|
|
69
|
+
|---|---|
|
|
70
|
+
| `.claude/settings.json` with your own hooks / statusLine | Backed up to `settings.json.backup-<ts>` before update |
|
|
71
|
+
| Root `CLAUDE.md` (yours differs from the generated one) | Backed up to `CLAUDE.md.backup-<ts>` before the merge write |
|
|
72
|
+
| `.claude/` on `--reinstall` / `update` mode | The whole directory is renamed to `.claude.backup-<ts>` first |
|
|
73
|
+
| `.mcp.json` | Your existing MCP servers are preserved and merged, not replaced |
|
|
74
|
+
|
|
75
|
+
> Fresh project? None of this triggers — backups only protect pre-existing files.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
62
79
|
## Tracks
|
|
63
80
|
|
|
64
81
|
Pick one or more at step 1. Each track determines which skills/plugins/rules are pre-checked in step 3.
|
|
@@ -253,7 +270,7 @@ Flags:
|
|
|
253
270
|
|
|
254
271
|
```
|
|
255
272
|
┌──────────────────────────────────────────────────────────┐
|
|
256
|
-
│ npx agent-harness
|
|
273
|
+
│ npx -y @uzysjung/agent-harness │
|
|
257
274
|
│ │ │
|
|
258
275
|
│ ▼ │
|
|
259
276
|
│ ┌─ 6-step wizard ──────────────────────────────────┐ │
|
|
@@ -277,6 +294,20 @@ Flags:
|
|
|
277
294
|
└──────────────────────────────────────────────────────────┘
|
|
278
295
|
```
|
|
279
296
|
|
|
297
|
+
After install, a `tooling` + Claude project looks like:
|
|
298
|
+
|
|
299
|
+
```
|
|
300
|
+
your-project/
|
|
301
|
+
├── .claude/
|
|
302
|
+
│ ├── rules/ # coding conventions for your stack
|
|
303
|
+
│ ├── agents/ # subagent definitions
|
|
304
|
+
│ ├── commands/uzys/ # /uzys:* commands (only if you opted into uzys-harness)
|
|
305
|
+
│ ├── hooks/ # gate / pre-commit hooks
|
|
306
|
+
│ └── settings.json # your existing one is backed up first
|
|
307
|
+
├── CLAUDE.md # merged instructions (yours backed up if it differed)
|
|
308
|
+
└── .mcp.json # MCP servers, merged with yours
|
|
309
|
+
```
|
|
310
|
+
|
|
280
311
|
---
|
|
281
312
|
|
|
282
313
|
## CLI support
|
package/dist/index.js
CHANGED
|
@@ -698,7 +698,7 @@ var cac = (name = "") => new CAC(name);
|
|
|
698
698
|
// package.json
|
|
699
699
|
var package_default = {
|
|
700
700
|
name: "@uzysjung/agent-harness",
|
|
701
|
-
version: "26.
|
|
701
|
+
version: "26.84.0",
|
|
702
702
|
description: "One-command installer & curator of vetted, Docker-verified AI-coding workflows across Claude Code, Codex, OpenCode & Antigravity",
|
|
703
703
|
type: "module",
|
|
704
704
|
publishConfig: {
|
|
@@ -933,7 +933,7 @@ import {
|
|
|
933
933
|
existsSync as existsSync14,
|
|
934
934
|
mkdirSync as mkdirSync6,
|
|
935
935
|
readdirSync as readdirSync4,
|
|
936
|
-
readFileSync as
|
|
936
|
+
readFileSync as readFileSync14,
|
|
937
937
|
writeFileSync as writeFileSync11
|
|
938
938
|
} from "fs";
|
|
939
939
|
import { dirname as dirname4, join as join12, resolve } from "path";
|
|
@@ -996,7 +996,7 @@ function runAntigravityOptIn(ctx) {
|
|
|
996
996
|
|
|
997
997
|
// src/antigravity/transform.ts
|
|
998
998
|
init_esm_shims();
|
|
999
|
-
import { existsSync as existsSync3, readFileSync as
|
|
999
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
1000
1000
|
import { basename, join as join3 } from "path";
|
|
1001
1001
|
|
|
1002
1002
|
// src/codex/skills.ts
|
|
@@ -1049,7 +1049,7 @@ function stripQuotes(raw) {
|
|
|
1049
1049
|
|
|
1050
1050
|
// src/fs-ops.ts
|
|
1051
1051
|
init_esm_shims();
|
|
1052
|
-
import { copyFileSync, cpSync as cpSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, renameSync } from "fs";
|
|
1052
|
+
import { copyFileSync, cpSync as cpSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync } from "fs";
|
|
1053
1053
|
import { dirname, join as join2 } from "path";
|
|
1054
1054
|
function ensureDir(path) {
|
|
1055
1055
|
mkdirSync2(path, { recursive: true });
|
|
@@ -1084,6 +1084,17 @@ function copyBackupDir(target, now = /* @__PURE__ */ new Date()) {
|
|
|
1084
1084
|
cpSync2(target, backup, { recursive: true });
|
|
1085
1085
|
return backup;
|
|
1086
1086
|
}
|
|
1087
|
+
function backupFileIfChanged(target, newContent, now = /* @__PURE__ */ new Date()) {
|
|
1088
|
+
if (!existsSync2(target)) {
|
|
1089
|
+
return null;
|
|
1090
|
+
}
|
|
1091
|
+
if (readFileSync2(target, "utf-8") === newContent) {
|
|
1092
|
+
return null;
|
|
1093
|
+
}
|
|
1094
|
+
const backup = `${target}.backup-${formatStamp(now)}`;
|
|
1095
|
+
copyFileSync(target, backup);
|
|
1096
|
+
return backup;
|
|
1097
|
+
}
|
|
1087
1098
|
function formatStamp(now) {
|
|
1088
1099
|
return now.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z").slice(0, 15);
|
|
1089
1100
|
}
|
|
@@ -1116,11 +1127,11 @@ function runAntigravityTransform(params) {
|
|
|
1116
1127
|
const cmdSrc = join3(harnessRoot, "templates/commands/uzys", `${phase}.md`);
|
|
1117
1128
|
let source = "";
|
|
1118
1129
|
if (existsSync3(cmdSrc)) {
|
|
1119
|
-
source =
|
|
1130
|
+
source = readFileSync3(cmdSrc, "utf8");
|
|
1120
1131
|
} else {
|
|
1121
1132
|
const fallback = join3(harnessRoot, "templates/codex/skills", `uzys-${phase}/SKILL.md`);
|
|
1122
1133
|
if (existsSync3(fallback)) {
|
|
1123
|
-
source =
|
|
1134
|
+
source = readFileSync3(fallback, "utf8");
|
|
1124
1135
|
}
|
|
1125
1136
|
}
|
|
1126
1137
|
const skillTarget = join3(skillDir, "SKILL.md");
|
|
@@ -1143,8 +1154,8 @@ function writeRules(harnessRoot, projectDir) {
|
|
|
1143
1154
|
if (!existsSync3(claudeMdPath) || !existsSync3(templatePath)) {
|
|
1144
1155
|
return null;
|
|
1145
1156
|
}
|
|
1146
|
-
const claudeMd =
|
|
1147
|
-
const template =
|
|
1157
|
+
const claudeMd = readFileSync3(claudeMdPath, "utf8");
|
|
1158
|
+
const template = readFileSync3(templatePath, "utf8");
|
|
1148
1159
|
const rulesDir = join3(projectDir, ".agents", "rules");
|
|
1149
1160
|
ensureDir(rulesDir);
|
|
1150
1161
|
const target = join3(rulesDir, "uzys-harness.md");
|
|
@@ -1154,7 +1165,7 @@ function writeRules(harnessRoot, projectDir) {
|
|
|
1154
1165
|
|
|
1155
1166
|
// src/codex/opt-in.ts
|
|
1156
1167
|
init_esm_shims();
|
|
1157
|
-
import { cpSync as cpSync3, existsSync as existsSync5, mkdirSync as mkdirSync4, readdirSync, readFileSync as
|
|
1168
|
+
import { cpSync as cpSync3, existsSync as existsSync5, mkdirSync as mkdirSync4, readdirSync, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
1158
1169
|
import { homedir as homedir2 } from "os";
|
|
1159
1170
|
import { join as join4 } from "path";
|
|
1160
1171
|
|
|
@@ -1201,14 +1212,14 @@ var CODEX_PROMPT_PHASES = ["spec", "plan", "build", "test", "review", "ship"];
|
|
|
1201
1212
|
|
|
1202
1213
|
// src/codex/trust-entry.ts
|
|
1203
1214
|
init_esm_shims();
|
|
1204
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as
|
|
1215
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
1205
1216
|
import { dirname as dirname2 } from "path";
|
|
1206
1217
|
var TRUST_BLOCK_REGEX = /\[projects\."([^"]+)"\]/g;
|
|
1207
1218
|
function registerTrustEntry(opts) {
|
|
1208
1219
|
const { configPath, projectDir } = opts;
|
|
1209
1220
|
try {
|
|
1210
1221
|
mkdirSync3(dirname2(configPath), { recursive: true });
|
|
1211
|
-
const existing = existsSync4(configPath) ?
|
|
1222
|
+
const existing = existsSync4(configPath) ? readFileSync4(configPath, "utf8") : "";
|
|
1212
1223
|
if (hasTrustEntry(existing, projectDir)) {
|
|
1213
1224
|
return { status: "already-present" };
|
|
1214
1225
|
}
|
|
@@ -1287,7 +1298,7 @@ function copyCodexPrompts(harnessRoot, projectDir, promptsTarget) {
|
|
|
1287
1298
|
for (const phase of CODEX_PROMPT_PHASES) {
|
|
1288
1299
|
const src = join4(sourceDir, `${phase}.md`);
|
|
1289
1300
|
if (!existsSync5(src)) continue;
|
|
1290
|
-
const source =
|
|
1301
|
+
const source = readFileSync5(src, "utf8");
|
|
1291
1302
|
const dst = join4(promptsTarget, `uzys-${phase}.md`);
|
|
1292
1303
|
writeFileSync4(dst, renderCodexPrompt({ source, phase }));
|
|
1293
1304
|
count++;
|
|
@@ -1323,7 +1334,7 @@ function copyCodexSkills(projectDir, skillsTarget) {
|
|
|
1323
1334
|
|
|
1324
1335
|
// src/codex/transform.ts
|
|
1325
1336
|
init_esm_shims();
|
|
1326
|
-
import { chmodSync, existsSync as existsSync6, readFileSync as
|
|
1337
|
+
import { chmodSync, existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
1327
1338
|
import { basename as basename2, join as join5 } from "path";
|
|
1328
1339
|
|
|
1329
1340
|
// src/codex/config-toml.ts
|
|
@@ -1421,7 +1432,7 @@ function runCodexTransform(params) {
|
|
|
1421
1432
|
if (!existsSync6(src)) {
|
|
1422
1433
|
continue;
|
|
1423
1434
|
}
|
|
1424
|
-
const ported =
|
|
1435
|
+
const ported = readFileSync6(src, "utf8").replace(ENV_VAR_RENAME, "CODEX_PROJECT_DIR");
|
|
1425
1436
|
const target = join5(hookDir, `${hook}.sh`);
|
|
1426
1437
|
writeFileSync5(target, ported);
|
|
1427
1438
|
chmodSync(target, 493);
|
|
@@ -1435,11 +1446,11 @@ function runCodexTransform(params) {
|
|
|
1435
1446
|
const cmdSrc = join5(harnessRoot, "templates/commands/uzys", `${phase}.md`);
|
|
1436
1447
|
let source = "";
|
|
1437
1448
|
if (existsSync6(cmdSrc)) {
|
|
1438
|
-
source =
|
|
1449
|
+
source = readFileSync6(cmdSrc, "utf8");
|
|
1439
1450
|
} else {
|
|
1440
1451
|
const fallback = join5(harnessRoot, "templates/codex/skills", `uzys-${phase}/SKILL.md`);
|
|
1441
1452
|
if (existsSync6(fallback)) {
|
|
1442
|
-
source =
|
|
1453
|
+
source = readFileSync6(fallback, "utf8");
|
|
1443
1454
|
}
|
|
1444
1455
|
}
|
|
1445
1456
|
const target = join5(skillDir, "SKILL.md");
|
|
@@ -1456,7 +1467,7 @@ function runCodexTransform(params) {
|
|
|
1456
1467
|
if (!existsSync6(cmdSrc)) {
|
|
1457
1468
|
continue;
|
|
1458
1469
|
}
|
|
1459
|
-
const source =
|
|
1470
|
+
const source = readFileSync6(cmdSrc, "utf8");
|
|
1460
1471
|
const target = join5(promptDir, `uzys-${phase}.md`);
|
|
1461
1472
|
writeFileSync5(target, renderCodexPrompt({ source, phase }));
|
|
1462
1473
|
promptFiles.push(target);
|
|
@@ -1468,14 +1479,14 @@ function readRequired(path) {
|
|
|
1468
1479
|
if (!existsSync6(path)) {
|
|
1469
1480
|
throw new Error(`Codex transform: required source missing: ${path}`);
|
|
1470
1481
|
}
|
|
1471
|
-
return
|
|
1482
|
+
return readFileSync6(path, "utf8");
|
|
1472
1483
|
}
|
|
1473
1484
|
function readOptionalJson(path) {
|
|
1474
1485
|
if (!existsSync6(path)) {
|
|
1475
1486
|
return null;
|
|
1476
1487
|
}
|
|
1477
1488
|
try {
|
|
1478
|
-
return JSON.parse(
|
|
1489
|
+
return JSON.parse(readFileSync6(path, "utf8"));
|
|
1479
1490
|
} catch {
|
|
1480
1491
|
return null;
|
|
1481
1492
|
}
|
|
@@ -1483,7 +1494,7 @@ function readOptionalJson(path) {
|
|
|
1483
1494
|
|
|
1484
1495
|
// src/env-files.ts
|
|
1485
1496
|
init_esm_shims();
|
|
1486
|
-
import { appendFileSync, existsSync as existsSync7, readFileSync as
|
|
1497
|
+
import { appendFileSync, existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
|
|
1487
1498
|
import { join as join6 } from "path";
|
|
1488
1499
|
var ENV_EXAMPLE_BODY = `# .env.example \u2014 csr-supabase Track
|
|
1489
1500
|
# Copy to .env (gitignored) and fill in values: cp .env.example .env
|
|
@@ -1533,7 +1544,7 @@ function addGitignoreEnv(projectDir) {
|
|
|
1533
1544
|
if (!existsSync7(path)) {
|
|
1534
1545
|
return false;
|
|
1535
1546
|
}
|
|
1536
|
-
const content =
|
|
1547
|
+
const content = readFileSync7(path, "utf8");
|
|
1537
1548
|
if (GITIGNORE_ENV_PATTERN.test(content)) {
|
|
1538
1549
|
return false;
|
|
1539
1550
|
}
|
|
@@ -1551,7 +1562,7 @@ function addGitignoreNpxSkillsAgents(projectDir) {
|
|
|
1551
1562
|
if (!existsSync7(path)) {
|
|
1552
1563
|
return [];
|
|
1553
1564
|
}
|
|
1554
|
-
const content =
|
|
1565
|
+
const content = readFileSync7(path, "utf8");
|
|
1555
1566
|
const missing = NPX_SKILLS_AGENT_DIRS.filter((pattern) => {
|
|
1556
1567
|
const lineRegex = new RegExp(`^${pattern.replace(/\./g, "\\.").replace(/\//g, "/")}\\s*$`, "m");
|
|
1557
1568
|
return !lineRegex.test(content);
|
|
@@ -1577,7 +1588,7 @@ function writeMcpAllowlist(projectDir) {
|
|
|
1577
1588
|
}
|
|
1578
1589
|
let names = [];
|
|
1579
1590
|
try {
|
|
1580
|
-
const parsed = JSON.parse(
|
|
1591
|
+
const parsed = JSON.parse(readFileSync7(mcpPath, "utf8"));
|
|
1581
1592
|
names = Object.keys(parsed.mcpServers ?? {}).sort();
|
|
1582
1593
|
} catch {
|
|
1583
1594
|
return null;
|
|
@@ -1600,7 +1611,7 @@ function writeMcpAllowlist(projectDir) {
|
|
|
1600
1611
|
// src/external-installer.ts
|
|
1601
1612
|
init_esm_shims();
|
|
1602
1613
|
import { spawnSync } from "child_process";
|
|
1603
|
-
import { existsSync as existsSync8, readdirSync as readdirSync2, readFileSync as
|
|
1614
|
+
import { existsSync as existsSync8, readdirSync as readdirSync2, readFileSync as readFileSync8 } from "fs";
|
|
1604
1615
|
import { homedir as homedir3 } from "os";
|
|
1605
1616
|
import { join as join7 } from "path";
|
|
1606
1617
|
var DEFAULT_SPAWN_TIMEOUT_MS = 12e4;
|
|
@@ -1691,8 +1702,12 @@ var SKILLS_CLI_AGENT_MAP = {
|
|
|
1691
1702
|
// v26.66.0 — Antigravity (Google) skills agent. `.agents/skills/` 표준 공유 (codex transform 산출과 동일).
|
|
1692
1703
|
antigravity: "antigravity"
|
|
1693
1704
|
};
|
|
1705
|
+
var SKILLS_CLI_VERSION = "1.5.11";
|
|
1706
|
+
function skillsCliSpec() {
|
|
1707
|
+
return `skills@${SKILLS_CLI_VERSION}`;
|
|
1708
|
+
}
|
|
1694
1709
|
function buildSkillArgs(method, cli2, scope) {
|
|
1695
|
-
const args = [
|
|
1710
|
+
const args = [skillsCliSpec(), "add", method.source];
|
|
1696
1711
|
if (method.skill) {
|
|
1697
1712
|
args.push("--skill", method.skill);
|
|
1698
1713
|
}
|
|
@@ -1767,7 +1782,7 @@ function detectVersion(method, spawn) {
|
|
|
1767
1782
|
if (!npmRoot) return void 0;
|
|
1768
1783
|
const pkgJson = join7(npmRoot, method.pkg, "package.json");
|
|
1769
1784
|
if (!existsSync8(pkgJson)) return void 0;
|
|
1770
|
-
const parsed = JSON.parse(
|
|
1785
|
+
const parsed = JSON.parse(readFileSync8(pkgJson, "utf8"));
|
|
1771
1786
|
return parsed.version;
|
|
1772
1787
|
}
|
|
1773
1788
|
default:
|
|
@@ -1795,7 +1810,7 @@ function getNpmGlobalRoot(spawn) {
|
|
|
1795
1810
|
// src/install-log.ts
|
|
1796
1811
|
init_esm_shims();
|
|
1797
1812
|
import { createHash } from "crypto";
|
|
1798
|
-
import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as
|
|
1813
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
|
|
1799
1814
|
import { dirname as dirname3, join as join8 } from "path";
|
|
1800
1815
|
var INSTALL_LOG_FILENAME = ".harness-install.json";
|
|
1801
1816
|
var INSTALL_LOG_VERSION = 1;
|
|
@@ -1864,7 +1879,7 @@ function readInstallLog(projectDir) {
|
|
|
1864
1879
|
const path = join8(projectDir, ".claude", INSTALL_LOG_FILENAME);
|
|
1865
1880
|
if (!existsSync9(path)) return null;
|
|
1866
1881
|
try {
|
|
1867
|
-
const parsed = JSON.parse(
|
|
1882
|
+
const parsed = JSON.parse(readFileSync9(path, "utf8"));
|
|
1868
1883
|
if (Array.isArray(parsed.assets)) {
|
|
1869
1884
|
parsed.assets = parsed.assets.map(
|
|
1870
1885
|
(a) => a.method === "npm-global" ? { ...a, method: "npm" } : a
|
|
@@ -2127,7 +2142,7 @@ function buildManifest(spec) {
|
|
|
2127
2142
|
|
|
2128
2143
|
// src/mcp-merge.ts
|
|
2129
2144
|
init_esm_shims();
|
|
2130
|
-
import { existsSync as existsSync10, readFileSync as
|
|
2145
|
+
import { existsSync as existsSync10, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "fs";
|
|
2131
2146
|
function parseTrackMcpMap(raw) {
|
|
2132
2147
|
const rows = [];
|
|
2133
2148
|
for (const line of raw.split(/\r?\n/)) {
|
|
@@ -2178,15 +2193,15 @@ function mergeMcpServers(base, rows, tracks) {
|
|
|
2178
2193
|
return out;
|
|
2179
2194
|
}
|
|
2180
2195
|
function composeMcpJson(opts) {
|
|
2181
|
-
const base = JSON.parse(
|
|
2196
|
+
const base = JSON.parse(readFileSync10(opts.templateMcpPath, "utf8"));
|
|
2182
2197
|
const merged = opts.existingPath && existsSync10(opts.existingPath) ? mergeUserBase(base, opts.existingPath) : base;
|
|
2183
|
-
const mapRaw = existsSync10(opts.trackMapPath) ?
|
|
2198
|
+
const mapRaw = existsSync10(opts.trackMapPath) ? readFileSync10(opts.trackMapPath, "utf8") : "";
|
|
2184
2199
|
const rows = parseTrackMcpMap(mapRaw);
|
|
2185
2200
|
return mergeMcpServers(merged, rows, opts.tracks);
|
|
2186
2201
|
}
|
|
2187
2202
|
function mergeUserBase(base, existingPath) {
|
|
2188
2203
|
try {
|
|
2189
|
-
const existing = JSON.parse(
|
|
2204
|
+
const existing = JSON.parse(readFileSync10(existingPath, "utf8"));
|
|
2190
2205
|
return {
|
|
2191
2206
|
...base,
|
|
2192
2207
|
mcpServers: { ...base.mcpServers, ...existing.mcpServers }
|
|
@@ -2202,7 +2217,7 @@ function writeMcpJson(path, mcp) {
|
|
|
2202
2217
|
|
|
2203
2218
|
// src/opencode/transform.ts
|
|
2204
2219
|
init_esm_shims();
|
|
2205
|
-
import { copyFileSync as copyFileSync2, existsSync as existsSync11, readFileSync as
|
|
2220
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync11, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "fs";
|
|
2206
2221
|
import { basename as basename3, join as join9 } from "path";
|
|
2207
2222
|
|
|
2208
2223
|
// src/opencode/agents-md.ts
|
|
@@ -2315,7 +2330,7 @@ function runOpencodeTransform(params) {
|
|
|
2315
2330
|
const cmdSrc = join9(harnessRoot, "templates/commands/uzys", `${phase}.md`);
|
|
2316
2331
|
let source = "";
|
|
2317
2332
|
if (existsSync11(cmdSrc)) {
|
|
2318
|
-
source =
|
|
2333
|
+
source = readFileSync11(cmdSrc, "utf8");
|
|
2319
2334
|
} else {
|
|
2320
2335
|
const fallback = join9(
|
|
2321
2336
|
harnessRoot,
|
|
@@ -2323,7 +2338,7 @@ function runOpencodeTransform(params) {
|
|
|
2323
2338
|
`uzys-${phase}.md`
|
|
2324
2339
|
);
|
|
2325
2340
|
if (existsSync11(fallback)) {
|
|
2326
|
-
source =
|
|
2341
|
+
source = readFileSync11(fallback, "utf8");
|
|
2327
2342
|
}
|
|
2328
2343
|
}
|
|
2329
2344
|
const target = join9(cmdDir, `uzys-${phase}.md`);
|
|
@@ -2345,14 +2360,14 @@ function readRequired2(path) {
|
|
|
2345
2360
|
if (!existsSync11(path)) {
|
|
2346
2361
|
throw new Error(`OpenCode transform: required source missing: ${path}`);
|
|
2347
2362
|
}
|
|
2348
|
-
return
|
|
2363
|
+
return readFileSync11(path, "utf8");
|
|
2349
2364
|
}
|
|
2350
2365
|
function readOptionalJson2(path) {
|
|
2351
2366
|
if (!existsSync11(path)) {
|
|
2352
2367
|
return null;
|
|
2353
2368
|
}
|
|
2354
2369
|
try {
|
|
2355
|
-
return JSON.parse(
|
|
2370
|
+
return JSON.parse(readFileSync11(path, "utf8"));
|
|
2356
2371
|
} catch {
|
|
2357
2372
|
return null;
|
|
2358
2373
|
}
|
|
@@ -2360,7 +2375,7 @@ function readOptionalJson2(path) {
|
|
|
2360
2375
|
|
|
2361
2376
|
// src/project-claude-merge.ts
|
|
2362
2377
|
init_esm_shims();
|
|
2363
|
-
import { existsSync as existsSync12, readFileSync as
|
|
2378
|
+
import { existsSync as existsSync12, readFileSync as readFileSync12 } from "fs";
|
|
2364
2379
|
import { join as join10 } from "path";
|
|
2365
2380
|
var SECTIONS = [
|
|
2366
2381
|
"stack",
|
|
@@ -2411,7 +2426,7 @@ var FULL_EXPANSION = [
|
|
|
2411
2426
|
];
|
|
2412
2427
|
function mergeProjectClaude(tracks, opts) {
|
|
2413
2428
|
const expanded = expandTracks(tracks);
|
|
2414
|
-
const baseRaw =
|
|
2429
|
+
const baseRaw = readFileSync12(join10(opts.baseDir, "_base.md"), "utf8");
|
|
2415
2430
|
let output = baseRaw;
|
|
2416
2431
|
output = output.replace("<!-- INSERT: track-list -->", trackList(expanded));
|
|
2417
2432
|
output = output.replace("<!-- INSERT: tagline -->", taglineList(expanded, opts.baseDir));
|
|
@@ -2443,7 +2458,7 @@ function taglineList(tracks, baseDir) {
|
|
|
2443
2458
|
if (!existsSync12(path)) {
|
|
2444
2459
|
continue;
|
|
2445
2460
|
}
|
|
2446
|
-
const body =
|
|
2461
|
+
const body = readFileSync12(path, "utf8").trim();
|
|
2447
2462
|
if (body) {
|
|
2448
2463
|
taglines.push(body);
|
|
2449
2464
|
}
|
|
@@ -2457,7 +2472,7 @@ function renderSection(section, tracks, baseDir) {
|
|
|
2457
2472
|
if (!existsSync12(path)) {
|
|
2458
2473
|
continue;
|
|
2459
2474
|
}
|
|
2460
|
-
const body =
|
|
2475
|
+
const body = readFileSync12(path, "utf8").trim();
|
|
2461
2476
|
if (body) {
|
|
2462
2477
|
present.push({ track: t, body });
|
|
2463
2478
|
}
|
|
@@ -2516,7 +2531,7 @@ import {
|
|
|
2516
2531
|
copyFileSync as copyFileSync3,
|
|
2517
2532
|
existsSync as existsSync13,
|
|
2518
2533
|
readdirSync as readdirSync3,
|
|
2519
|
-
readFileSync as
|
|
2534
|
+
readFileSync as readFileSync13,
|
|
2520
2535
|
unlinkSync,
|
|
2521
2536
|
writeFileSync as writeFileSync10
|
|
2522
2537
|
} from "fs";
|
|
@@ -2605,7 +2620,7 @@ function pruneOrphans(target, source, ext) {
|
|
|
2605
2620
|
function cleanStaleHookRefs(settingsPath, hooksDir) {
|
|
2606
2621
|
let settings;
|
|
2607
2622
|
try {
|
|
2608
|
-
settings = JSON.parse(
|
|
2623
|
+
settings = JSON.parse(readFileSync13(settingsPath, "utf8"));
|
|
2609
2624
|
} catch {
|
|
2610
2625
|
return [];
|
|
2611
2626
|
}
|
|
@@ -2671,7 +2686,8 @@ function runInstall(ctx) {
|
|
|
2671
2686
|
mode,
|
|
2672
2687
|
envFiles: writeEnvironmentFiles(projectDir, spec.tracks),
|
|
2673
2688
|
categories: base.categories,
|
|
2674
|
-
rootClaudeMd: base.rootClaudeMd
|
|
2689
|
+
rootClaudeMd: base.rootClaudeMd,
|
|
2690
|
+
backups: base.backups
|
|
2675
2691
|
};
|
|
2676
2692
|
ctx.onProgress?.({ type: "baseline-complete", baseline });
|
|
2677
2693
|
const external = runExternalPhase(ctx);
|
|
@@ -2733,7 +2749,8 @@ function emptyClaudeBaseline() {
|
|
|
2733
2749
|
skipped: 0,
|
|
2734
2750
|
categories: { rules: [], agents: [], hooks: [], commands: 0, skills: [] },
|
|
2735
2751
|
rootClaudeMd: null,
|
|
2736
|
-
rootClaudeMdLog: null
|
|
2752
|
+
rootClaudeMdLog: null,
|
|
2753
|
+
backups: []
|
|
2737
2754
|
};
|
|
2738
2755
|
}
|
|
2739
2756
|
function installClaudeBaseline(manifestSpec, harnessRoot, projectDir, templatesDir) {
|
|
@@ -2751,6 +2768,12 @@ function installClaudeBaseline(manifestSpec, harnessRoot, projectDir, templatesD
|
|
|
2751
2768
|
continue;
|
|
2752
2769
|
}
|
|
2753
2770
|
if (entry.type === "file") {
|
|
2771
|
+
if (entry.target === ".claude/settings.json") {
|
|
2772
|
+
const backup = backupFileIfChanged(target, readFileSync14(source, "utf-8"));
|
|
2773
|
+
if (backup) {
|
|
2774
|
+
result.backups.push(backup);
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2754
2777
|
copyFile(source, target);
|
|
2755
2778
|
result.filesCopied += 1;
|
|
2756
2779
|
} else {
|
|
@@ -2764,9 +2787,12 @@ function installClaudeBaseline(manifestSpec, harnessRoot, projectDir, templatesD
|
|
|
2764
2787
|
chmodHooksSync(hookDir);
|
|
2765
2788
|
}
|
|
2766
2789
|
writeInstalledTracks(projectDir, manifestSpec.tracks);
|
|
2767
|
-
const
|
|
2790
|
+
const rootClaudeMd = writeRootClaudeMd(harnessRoot, projectDir, manifestSpec.tracks);
|
|
2768
2791
|
result.rootClaudeMd = { tracks: manifestSpec.tracks };
|
|
2769
|
-
result.rootClaudeMdLog = { path: "CLAUDE.md", sha256: hashContent(
|
|
2792
|
+
result.rootClaudeMdLog = { path: "CLAUDE.md", sha256: hashContent(rootClaudeMd.content) };
|
|
2793
|
+
if (rootClaudeMd.backup) {
|
|
2794
|
+
result.backups.push(rootClaudeMd.backup);
|
|
2795
|
+
}
|
|
2770
2796
|
return result;
|
|
2771
2797
|
}
|
|
2772
2798
|
function writeEnvironmentFiles(projectDir, tracks) {
|
|
@@ -2893,7 +2919,7 @@ function wireKarpathyHook(spec, external, harnessRoot, projectDir) {
|
|
|
2893
2919
|
const settingsPath = join12(projectDir, ".claude/settings.json");
|
|
2894
2920
|
let settingsUpdated = false;
|
|
2895
2921
|
if (existsSync14(settingsPath)) {
|
|
2896
|
-
const raw =
|
|
2922
|
+
const raw = readFileSync14(settingsPath, "utf8");
|
|
2897
2923
|
let before;
|
|
2898
2924
|
try {
|
|
2899
2925
|
before = JSON.parse(raw);
|
|
@@ -2950,8 +2976,10 @@ function writeInstalledTracks(projectDir, tracks) {
|
|
|
2950
2976
|
function writeRootClaudeMd(harnessRoot, projectDir, tracks) {
|
|
2951
2977
|
const baseDir = join12(harnessRoot, "templates/project-claude");
|
|
2952
2978
|
const content = mergeProjectClaude(tracks, { baseDir });
|
|
2953
|
-
|
|
2954
|
-
|
|
2979
|
+
const target = join12(projectDir, "CLAUDE.md");
|
|
2980
|
+
const backup = backupFileIfChanged(target, content);
|
|
2981
|
+
writeFileSync11(target, content);
|
|
2982
|
+
return { content, backup };
|
|
2955
2983
|
}
|
|
2956
2984
|
function chmodHooksSync(hookDir) {
|
|
2957
2985
|
for (const file of listHookFiles(hookDir)) {
|
|
@@ -3223,7 +3251,15 @@ function renderFinalSummary(log, spec, report, fromWizard) {
|
|
|
3223
3251
|
}
|
|
3224
3252
|
}
|
|
3225
3253
|
log("");
|
|
3226
|
-
|
|
3254
|
+
const hasUzysHarness = isAssetSelected("uzys-harness", spec);
|
|
3255
|
+
const hasClaude = spec.cli.includes("claude");
|
|
3256
|
+
if (hasUzysHarness && hasClaude) {
|
|
3257
|
+
log(infoRow("NEXT", `${c.bold("claude")} \u2192 ${c.cyan("/uzys:spec")}`));
|
|
3258
|
+
} else {
|
|
3259
|
+
const primary = (hasClaude ? "claude" : spec.cli[0]) ?? "claude";
|
|
3260
|
+
const label = CLI_SUMMARY_LABELS[primary];
|
|
3261
|
+
log(infoRow("NEXT", `Open ${c.bold(label)} \u2014 installed rules & skills are now active`));
|
|
3262
|
+
}
|
|
3227
3263
|
log("");
|
|
3228
3264
|
}
|
|
3229
3265
|
function formatAssetMeta(asset, version) {
|
|
@@ -3272,6 +3308,11 @@ function renderPhase1Rows(log, baseline, verbose = false, withEcc = false) {
|
|
|
3272
3308
|
}
|
|
3273
3309
|
return;
|
|
3274
3310
|
}
|
|
3311
|
+
if (baseline.backups) {
|
|
3312
|
+
for (const b2 of baseline.backups) {
|
|
3313
|
+
log(assetRow("success", "backup", shortenPath(b2)));
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3275
3316
|
const cats = baseline.categories;
|
|
3276
3317
|
if (cats) {
|
|
3277
3318
|
const phase1Row = (label, count, useText, files) => {
|
|
@@ -3363,7 +3404,7 @@ function renderPhase1Rows(log, baseline, verbose = false, withEcc = false) {
|
|
|
3363
3404
|
log(
|
|
3364
3405
|
` ${c.dim("\xB7")} ${c.dim("ECC plugin not selected \u2014 cherry-pick fallback active (up to 4 agents + 8 skills + 3 commands)")}`
|
|
3365
3406
|
);
|
|
3366
|
-
log(` ${c.dim("\xB7")} ${c.dim("Use --with-
|
|
3407
|
+
log(` ${c.dim("\xB7")} ${c.dim("Use --with ecc-plugin to install ECC plugin instead")}`);
|
|
3367
3408
|
}
|
|
3368
3409
|
if (baseline.envFiles.envExampleCreated) {
|
|
3369
3410
|
log(assetRow("success", ".env.example", "Supabase token guide"));
|
|
@@ -3591,11 +3632,9 @@ function registerInstallCommand(cli2) {
|
|
|
3591
3632
|
}
|
|
3592
3633
|
).option("--project-dir <path>", "[Project] Target project directory", {
|
|
3593
3634
|
default: process.cwd()
|
|
3635
|
+
}).option("--scope <scope>", "[Scope] Installation scope: project (default) | global", {
|
|
3636
|
+
default: "project"
|
|
3594
3637
|
}).option(
|
|
3595
|
-
"--scope <scope>",
|
|
3596
|
-
"[Scope] Installation scope: project (default) | global. ADR-020 / NORTH_STAR D16",
|
|
3597
|
-
{ default: "project" }
|
|
3598
|
-
).option(
|
|
3599
3638
|
"--with <asset-id>",
|
|
3600
3639
|
"[Asset] Force-include External Asset id (regardless of preset). Repeatable. v26.47.0+"
|
|
3601
3640
|
).option(
|
|
@@ -3603,10 +3642,10 @@ function registerInstallCommand(cli2) {
|
|
|
3603
3642
|
"[Asset] Force-exclude External Asset id (drop from preset recommendation). Repeatable. v26.47.0+"
|
|
3604
3643
|
).option(
|
|
3605
3644
|
"--with-codex-prompts",
|
|
3606
|
-
"[Codex] Unify Codex slash (~/.codex/prompts/uzys-*.md). v26.46.0+
|
|
3645
|
+
"[Codex] Unify Codex slash (~/.codex/prompts/uzys-*.md). Requires --cli codex. (v26.46.0+)"
|
|
3607
3646
|
).option(
|
|
3608
3647
|
"--no-codex-prompts",
|
|
3609
|
-
"[Codex]
|
|
3648
|
+
"[Codex] Backward-compat noop \u2014 Codex slash is opt-in via --with-codex-prompts (v26.64.0 ADR-020)"
|
|
3610
3649
|
).option(
|
|
3611
3650
|
"--with-codex-skills",
|
|
3612
3651
|
"[Codex] Codex global opt-in: copy uzys-* skills to ~/.codex/skills/"
|
|
@@ -3628,7 +3667,7 @@ function registerInstallCommand(cli2) {
|
|
|
3628
3667
|
// src/commands/uninstall.ts
|
|
3629
3668
|
init_esm_shims();
|
|
3630
3669
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
3631
|
-
import { existsSync as existsSync15, readFileSync as
|
|
3670
|
+
import { existsSync as existsSync15, readFileSync as readFileSync15, rmSync } from "fs";
|
|
3632
3671
|
import { join as join13, resolve as resolve3 } from "path";
|
|
3633
3672
|
function uninstallAction(options, deps = {}) {
|
|
3634
3673
|
const log = deps.log ?? console.log;
|
|
@@ -3757,7 +3796,7 @@ function buildProjectReverseStep(asset, spawn) {
|
|
|
3757
3796
|
return {
|
|
3758
3797
|
label: `npx skills remove ${source}`,
|
|
3759
3798
|
execute: () => {
|
|
3760
|
-
const r = spawn("npx", [
|
|
3799
|
+
const r = spawn("npx", [skillsCliSpec(), "remove", source, "--yes"]);
|
|
3761
3800
|
return r.status === 0 ? { ok: true } : { ok: false, message: (r.stderr || "").trim() };
|
|
3762
3801
|
}
|
|
3763
3802
|
};
|
|
@@ -3816,7 +3855,7 @@ function rootClaudeMdModified(log, projectDir) {
|
|
|
3816
3855
|
if (!rootMd) return false;
|
|
3817
3856
|
const path = join13(projectDir, rootMd.path);
|
|
3818
3857
|
if (!existsSync15(path)) return false;
|
|
3819
|
-
return hashContent(
|
|
3858
|
+
return hashContent(readFileSync15(path, "utf8")) !== rootMd.sha256;
|
|
3820
3859
|
}
|
|
3821
3860
|
function formatTemplateList(log) {
|
|
3822
3861
|
const items = [log.templates.claudeDir];
|
|
@@ -5102,7 +5141,7 @@ Proceed?`,
|
|
|
5102
5141
|
|
|
5103
5142
|
// src/state.ts
|
|
5104
5143
|
init_esm_shims();
|
|
5105
|
-
import { existsSync as existsSync16, readFileSync as
|
|
5144
|
+
import { existsSync as existsSync16, readFileSync as readFileSync16 } from "fs";
|
|
5106
5145
|
import { join as join14 } from "path";
|
|
5107
5146
|
var META_FILE = ".claude/.installed-tracks";
|
|
5108
5147
|
var LEGACY_SIGNATURES = [
|
|
@@ -5127,7 +5166,7 @@ function detectInstallState(projectDir) {
|
|
|
5127
5166
|
return { state: "existing", tracks, source: "legacy", hasClaudeDir: true };
|
|
5128
5167
|
}
|
|
5129
5168
|
function readMetafile(path) {
|
|
5130
|
-
const raw =
|
|
5169
|
+
const raw = readFileSync16(path, "utf8");
|
|
5131
5170
|
const seen = /* @__PURE__ */ new Set();
|
|
5132
5171
|
for (const line of raw.split(/\s+/)) {
|
|
5133
5172
|
const trimmed = line.trim();
|