@uzysjung/agent-harness 26.107.0 → 26.108.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/dist/index.js CHANGED
@@ -24,7 +24,7 @@ import {
24
24
  isInstallScope,
25
25
  isTrack,
26
26
  resolveScope
27
- } from "./chunk-HINGFZ65.js";
27
+ } from "./chunk-ZEV3DL6G.js";
28
28
 
29
29
  // node_modules/sisteransi/src/index.js
30
30
  var require_src = __commonJS({
@@ -709,7 +709,7 @@ var cac = (name = "") => new CAC(name);
709
709
  // package.json
710
710
  var package_default = {
711
711
  name: "@uzysjung/agent-harness",
712
- version: "26.107.0",
712
+ version: "26.108.0",
713
713
  description: "Curate vetted AI-coding skills & plugins by your tech stack \u2014 install only what you need, across Claude Code, Codex, OpenCode & Antigravity",
714
714
  type: "module",
715
715
  publishConfig: {
@@ -902,13 +902,13 @@ function padDisplay(s, width) {
902
902
  init_esm_shims();
903
903
  import {
904
904
  chmodSync as chmodSync2,
905
- existsSync as existsSync11,
905
+ existsSync as existsSync12,
906
906
  mkdirSync as mkdirSync4,
907
907
  readdirSync as readdirSync3,
908
908
  readFileSync as readFileSync11,
909
909
  writeFileSync as writeFileSync9
910
910
  } from "fs";
911
- import { basename as basename4, dirname as dirname4, join as join10, resolve } from "path";
911
+ import { basename as basename4, dirname as dirname4, join as join11, resolve } from "path";
912
912
 
913
913
  // src/antigravity/transform.ts
914
914
  init_esm_shims();
@@ -1154,21 +1154,59 @@ function writeRules(harnessRoot, projectDir) {
1154
1154
  return target;
1155
1155
  }
1156
1156
 
1157
+ // src/ci-scaffold.ts
1158
+ init_esm_shims();
1159
+ import { existsSync as existsSync3 } from "fs";
1160
+ import { join as join3 } from "path";
1161
+ var PYTHON_TRACKS = "data|csr-fastapi|full";
1162
+ var VARIANTS = [
1163
+ {
1164
+ source: "ci-node.yml",
1165
+ target: "ci.yml",
1166
+ applies: (tracks) => anyTrack(tracks, "csr-*|ssr-*|tooling|full") || !anyTrack(tracks, PYTHON_TRACKS)
1167
+ },
1168
+ {
1169
+ source: "ci-python.yml",
1170
+ target: "ci-python.yml",
1171
+ applies: (tracks) => anyTrack(tracks, PYTHON_TRACKS)
1172
+ },
1173
+ {
1174
+ source: "e2e.yml",
1175
+ target: "e2e.yml",
1176
+ applies: (tracks) => hasUiTrack(tracks)
1177
+ }
1178
+ ];
1179
+ function installCiScaffold(ctx) {
1180
+ const report = { written: [], skippedExisting: [] };
1181
+ for (const v2 of VARIANTS) {
1182
+ if (!v2.applies(ctx.tracks)) continue;
1183
+ const relTarget = `.github/workflows/${v2.target}`;
1184
+ const target = join3(ctx.projectDir, relTarget);
1185
+ if (existsSync3(target)) {
1186
+ report.skippedExisting.push(relTarget);
1187
+ continue;
1188
+ }
1189
+ copyFile(join3(ctx.harnessRoot, "templates/github-workflows", v2.source), target);
1190
+ report.written.push(relTarget);
1191
+ }
1192
+ return report;
1193
+ }
1194
+
1157
1195
  // src/codex/opt-in.ts
1158
1196
  init_esm_shims();
1159
1197
  import { homedir } from "os";
1160
- import { join as join3 } from "path";
1198
+ import { join as join4 } from "path";
1161
1199
 
1162
1200
  // src/codex/trust-entry.ts
1163
1201
  init_esm_shims();
1164
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1202
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1165
1203
  import { dirname as dirname2 } from "path";
1166
1204
  var TRUST_BLOCK_REGEX = /\[projects\."([^"]+)"\]/g;
1167
1205
  function registerTrustEntry(opts) {
1168
1206
  const { configPath, projectDir } = opts;
1169
1207
  try {
1170
1208
  mkdirSync2(dirname2(configPath), { recursive: true });
1171
- const existing = existsSync3(configPath) ? readFileSync3(configPath, "utf8") : "";
1209
+ const existing = existsSync4(configPath) ? readFileSync3(configPath, "utf8") : "";
1172
1210
  if (hasTrustEntry(existing, projectDir)) {
1173
1211
  return { status: "already-present" };
1174
1212
  }
@@ -1192,8 +1230,8 @@ function hasTrustEntry(configContent, projectDir) {
1192
1230
 
1193
1231
  // src/codex/opt-in.ts
1194
1232
  function runCodexOptIn(ctx) {
1195
- const codexHome = ctx.codexHome ?? join3(homedir(), ".codex");
1196
- const configPath = join3(codexHome, "config.toml");
1233
+ const codexHome = ctx.codexHome ?? join4(homedir(), ".codex");
1234
+ const configPath = join4(codexHome, "config.toml");
1197
1235
  const result = registerTrustEntry({ configPath, projectDir: ctx.projectDir });
1198
1236
  return {
1199
1237
  trustEntry: {
@@ -1206,8 +1244,8 @@ function runCodexOptIn(ctx) {
1206
1244
 
1207
1245
  // src/codex/transform.ts
1208
1246
  init_esm_shims();
1209
- import { chmodSync, existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
1210
- import { basename as basename2, join as join4 } from "path";
1247
+ import { chmodSync, existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
1248
+ import { basename as basename2, join as join5 } from "path";
1211
1249
 
1212
1250
  // src/codex/config-toml.ts
1213
1251
  init_esm_shims();
@@ -1276,12 +1314,12 @@ var HOOK_NAMES = ["session-start", "hito-counter"];
1276
1314
  var ENV_VAR_RENAME = /CLAUDE_PROJECT_DIR/g;
1277
1315
  function runCodexTransform(params) {
1278
1316
  const { harnessRoot, projectDir, selectedInternalSkills = [] } = params;
1279
- const claudeMd = readRequired(join4(harnessRoot, "templates/CLAUDE.md"));
1280
- const agentsTemplate = readRequired(join4(harnessRoot, "templates/codex/AGENTS.md.template"));
1281
- const configTemplate = readRequired(join4(harnessRoot, "templates/codex/config.toml.template"));
1317
+ const claudeMd = readRequired(join5(harnessRoot, "templates/CLAUDE.md"));
1318
+ const agentsTemplate = readRequired(join5(harnessRoot, "templates/codex/AGENTS.md.template"));
1319
+ const configTemplate = readRequired(join5(harnessRoot, "templates/codex/config.toml.template"));
1282
1320
  const projectName = basename2(projectDir);
1283
- const mcp = readOptionalJson(join4(harnessRoot, ".mcp.json"));
1284
- const agentsMdPath = join4(projectDir, "AGENTS.md");
1321
+ const mcp = readOptionalJson(join5(harnessRoot, ".mcp.json"));
1322
+ const agentsMdPath = join5(projectDir, "AGENTS.md");
1285
1323
  ensureDir(projectDir);
1286
1324
  const agentsMdOut = renderAgentsMd({
1287
1325
  template: agentsTemplate,
@@ -1291,8 +1329,8 @@ function runCodexTransform(params) {
1291
1329
  });
1292
1330
  backupFileIfChanged(agentsMdPath, agentsMdOut);
1293
1331
  writeFileSync3(agentsMdPath, agentsMdOut);
1294
- const configTomlPath = join4(projectDir, ".codex/config.toml");
1295
- ensureDir(join4(projectDir, ".codex"));
1332
+ const configTomlPath = join5(projectDir, ".codex/config.toml");
1333
+ ensureDir(join5(projectDir, ".codex"));
1296
1334
  writeFileSync3(
1297
1335
  configTomlPath,
1298
1336
  renderConfigToml({
@@ -1302,42 +1340,42 @@ function runCodexTransform(params) {
1302
1340
  mcp
1303
1341
  })
1304
1342
  );
1305
- const hookDir = join4(projectDir, ".codex/hooks");
1343
+ const hookDir = join5(projectDir, ".codex/hooks");
1306
1344
  ensureDir(hookDir);
1307
1345
  const hookFiles = [];
1308
1346
  for (const hook of HOOK_NAMES) {
1309
- const src = join4(harnessRoot, "templates/hooks", `${hook}.sh`);
1310
- if (!existsSync4(src)) {
1347
+ const src = join5(harnessRoot, "templates/hooks", `${hook}.sh`);
1348
+ if (!existsSync5(src)) {
1311
1349
  continue;
1312
1350
  }
1313
1351
  const ported = readFileSync4(src, "utf8").replace(ENV_VAR_RENAME, "CODEX_PROJECT_DIR");
1314
- const target = join4(hookDir, `${hook}.sh`);
1352
+ const target = join5(hookDir, `${hook}.sh`);
1315
1353
  writeFileSync3(target, ported);
1316
1354
  chmodSync(target, 493);
1317
1355
  hookFiles.push(target);
1318
1356
  }
1319
1357
  const skillFiles = [];
1320
1358
  for (const id of selectedInternalSkills) {
1321
- const src = join4(harnessRoot, "templates/skills", id, "SKILL.md");
1322
- if (!existsSync4(src)) {
1359
+ const src = join5(harnessRoot, "templates/skills", id, "SKILL.md");
1360
+ if (!existsSync5(src)) {
1323
1361
  continue;
1324
1362
  }
1325
- const skillDir = join4(projectDir, ".agents", "skills", id);
1363
+ const skillDir = join5(projectDir, ".agents", "skills", id);
1326
1364
  ensureDir(skillDir);
1327
- const target = join4(skillDir, "SKILL.md");
1365
+ const target = join5(skillDir, "SKILL.md");
1328
1366
  writeFileSync3(target, renderBundledSkill(readFileSync4(src, "utf8")));
1329
1367
  skillFiles.push(target);
1330
1368
  }
1331
1369
  return { agentsMdPath, configTomlPath, hookFiles, skillFiles };
1332
1370
  }
1333
1371
  function readRequired(path) {
1334
- if (!existsSync4(path)) {
1372
+ if (!existsSync5(path)) {
1335
1373
  throw new Error(`Codex transform: required source missing: ${path}`);
1336
1374
  }
1337
1375
  return readFileSync4(path, "utf8");
1338
1376
  }
1339
1377
  function readOptionalJson(path) {
1340
- if (!existsSync4(path)) {
1378
+ if (!existsSync5(path)) {
1341
1379
  return null;
1342
1380
  }
1343
1381
  try {
@@ -1349,8 +1387,8 @@ function readOptionalJson(path) {
1349
1387
 
1350
1388
  // src/env-files.ts
1351
1389
  init_esm_shims();
1352
- import { appendFileSync, existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
1353
- import { join as join5 } from "path";
1390
+ import { appendFileSync, existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
1391
+ import { join as join6 } from "path";
1354
1392
  var ENV_EXAMPLE_BODY = `# .env.example \u2014 csr-supabase Track
1355
1393
  # Copy to .env (gitignored) and fill in values: cp .env.example .env
1356
1394
 
@@ -1387,16 +1425,16 @@ function writeEnvExample(projectDir, tracks) {
1387
1425
  if (!tracks.some((t) => ENV_EXAMPLE_TRACKS.includes(t))) {
1388
1426
  return false;
1389
1427
  }
1390
- const path = join5(projectDir, ".env.example");
1391
- if (existsSync5(path)) {
1428
+ const path = join6(projectDir, ".env.example");
1429
+ if (existsSync6(path)) {
1392
1430
  return false;
1393
1431
  }
1394
1432
  writeFileSync4(path, ENV_EXAMPLE_BODY);
1395
1433
  return true;
1396
1434
  }
1397
1435
  function addGitignoreEnv(projectDir) {
1398
- const path = join5(projectDir, ".gitignore");
1399
- if (!existsSync5(path)) {
1436
+ const path = join6(projectDir, ".gitignore");
1437
+ if (!existsSync6(path)) {
1400
1438
  return false;
1401
1439
  }
1402
1440
  const content = readFileSync5(path, "utf8");
@@ -1413,8 +1451,8 @@ function addGitignoreEnv(projectDir) {
1413
1451
  var NPX_SKILLS_AGENT_DIRS = [".factory/", ".goose/"];
1414
1452
  var GITIGNORE_NPX_SKILLS_HEADER = "# npx skills add multi-CLI cache (auto-added by agent-harness)";
1415
1453
  function addGitignoreNpxSkillsAgents(projectDir) {
1416
- const path = join5(projectDir, ".gitignore");
1417
- if (!existsSync5(path)) {
1454
+ const path = join6(projectDir, ".gitignore");
1455
+ if (!existsSync6(path)) {
1418
1456
  return [];
1419
1457
  }
1420
1458
  const content = readFileSync5(path, "utf8");
@@ -1433,12 +1471,12 @@ ${block}
1433
1471
  return [...missing];
1434
1472
  }
1435
1473
  function writeMcpAllowlist(projectDir) {
1436
- const allowlistPath = join5(projectDir, ".mcp-allowlist");
1437
- if (existsSync5(allowlistPath)) {
1474
+ const allowlistPath = join6(projectDir, ".mcp-allowlist");
1475
+ if (existsSync6(allowlistPath)) {
1438
1476
  return null;
1439
1477
  }
1440
- const mcpPath = join5(projectDir, ".mcp.json");
1441
- if (!existsSync5(mcpPath)) {
1478
+ const mcpPath = join6(projectDir, ".mcp.json");
1479
+ if (!existsSync6(mcpPath)) {
1442
1480
  return null;
1443
1481
  }
1444
1482
  let names = [];
@@ -1466,9 +1504,9 @@ function writeMcpAllowlist(projectDir) {
1466
1504
  // src/external-installer.ts
1467
1505
  init_esm_shims();
1468
1506
  import { spawnSync } from "child_process";
1469
- import { existsSync as existsSync6, readdirSync, readFileSync as readFileSync6 } from "fs";
1507
+ import { existsSync as existsSync7, readdirSync, readFileSync as readFileSync6 } from "fs";
1470
1508
  import { homedir as homedir2 } from "os";
1471
- import { join as join6 } from "path";
1509
+ import { join as join7 } from "path";
1472
1510
  var DEFAULT_SPAWN_TIMEOUT_MS = 12e4;
1473
1511
  function selectExternalTargets(assets, ctx) {
1474
1512
  const conditionPassed = filterApplicableAssets(assets, ctx).filter(
@@ -1544,8 +1582,8 @@ function installOne(asset, ctx) {
1544
1582
  cwd
1545
1583
  );
1546
1584
  case "shell-script": {
1547
- const scriptPath = join6(ctx.harnessRoot, method.script);
1548
- if (!existsSync6(scriptPath)) {
1585
+ const scriptPath = join7(ctx.harnessRoot, method.script);
1586
+ if (!existsSync7(scriptPath)) {
1549
1587
  return {
1550
1588
  asset,
1551
1589
  ok: false,
@@ -1635,16 +1673,16 @@ function detectVersion(method, spawn) {
1635
1673
  if (at2 <= 0) return void 0;
1636
1674
  const plugin = method.pluginId.slice(0, at2);
1637
1675
  const marketplaceShort = method.pluginId.slice(at2 + 1);
1638
- const cacheBase = join6(homedir2(), ".claude/plugins/cache", marketplaceShort, plugin);
1639
- if (!existsSync6(cacheBase)) return void 0;
1676
+ const cacheBase = join7(homedir2(), ".claude/plugins/cache", marketplaceShort, plugin);
1677
+ if (!existsSync7(cacheBase)) return void 0;
1640
1678
  const versions = readdirSync(cacheBase).filter((v2) => /^\d/.test(v2)).sort();
1641
1679
  return versions.at(-1);
1642
1680
  }
1643
1681
  case "npm": {
1644
1682
  const npmRoot = getNpmGlobalRoot(spawn);
1645
1683
  if (!npmRoot) return void 0;
1646
- const pkgJson = join6(npmRoot, method.pkg, "package.json");
1647
- if (!existsSync6(pkgJson)) return void 0;
1684
+ const pkgJson = join7(npmRoot, method.pkg, "package.json");
1685
+ if (!existsSync7(pkgJson)) return void 0;
1648
1686
  const parsed = JSON.parse(readFileSync6(pkgJson, "utf8"));
1649
1687
  return parsed.version;
1650
1688
  }
@@ -1673,8 +1711,8 @@ function getNpmGlobalRoot(spawn) {
1673
1711
  // src/install-log.ts
1674
1712
  init_esm_shims();
1675
1713
  import { createHash } from "crypto";
1676
- import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
1677
- import { dirname as dirname3, join as join7 } from "path";
1714
+ import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
1715
+ import { dirname as dirname3, join as join8 } from "path";
1678
1716
  var INSTALL_LOG_FILENAME = ".harness-install.json";
1679
1717
  var INSTALL_LOG_VERSION = 1;
1680
1718
  function buildAssetEntries(report, scope) {
@@ -1732,15 +1770,15 @@ function hashContent(content) {
1732
1770
  return createHash("sha256").update(content, "utf8").digest("hex");
1733
1771
  }
1734
1772
  function writeInstallLog(projectDir, log) {
1735
- const path = join7(projectDir, ".claude", INSTALL_LOG_FILENAME);
1773
+ const path = join8(projectDir, ".claude", INSTALL_LOG_FILENAME);
1736
1774
  mkdirSync3(dirname3(path), { recursive: true });
1737
1775
  writeFileSync5(path, `${JSON.stringify(log, null, 2)}
1738
1776
  `, "utf8");
1739
1777
  return path;
1740
1778
  }
1741
1779
  function readInstallLog(projectDir) {
1742
- const path = join7(projectDir, ".claude", INSTALL_LOG_FILENAME);
1743
- if (!existsSync7(path)) return null;
1780
+ const path = join8(projectDir, ".claude", INSTALL_LOG_FILENAME);
1781
+ if (!existsSync8(path)) return null;
1744
1782
  try {
1745
1783
  const parsed = JSON.parse(readFileSync7(path, "utf8"));
1746
1784
  if (Array.isArray(parsed.assets)) {
@@ -1754,7 +1792,7 @@ function readInstallLog(projectDir) {
1754
1792
  }
1755
1793
  }
1756
1794
  function installLogPath(projectDir) {
1757
- return join7(projectDir, ".claude", INSTALL_LOG_FILENAME);
1795
+ return join8(projectDir, ".claude", INSTALL_LOG_FILENAME);
1758
1796
  }
1759
1797
 
1760
1798
  // src/manifest.ts
@@ -2002,7 +2040,7 @@ function buildManifest(spec) {
2002
2040
 
2003
2041
  // src/mcp-merge.ts
2004
2042
  init_esm_shims();
2005
- import { existsSync as existsSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
2043
+ import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
2006
2044
  function parseTrackMcpMap(raw) {
2007
2045
  const rows = [];
2008
2046
  for (const line of raw.split(/\r?\n/)) {
@@ -2054,8 +2092,8 @@ function mergeMcpServers(base, rows, tracks) {
2054
2092
  }
2055
2093
  function composeMcpJson(opts) {
2056
2094
  const base = JSON.parse(readFileSync8(opts.templateMcpPath, "utf8"));
2057
- const merged = opts.existingPath && existsSync8(opts.existingPath) ? mergeUserBase(base, opts.existingPath) : base;
2058
- const mapRaw = existsSync8(opts.trackMapPath) ? readFileSync8(opts.trackMapPath, "utf8") : "";
2095
+ const merged = opts.existingPath && existsSync9(opts.existingPath) ? mergeUserBase(base, opts.existingPath) : base;
2096
+ const mapRaw = existsSync9(opts.trackMapPath) ? readFileSync8(opts.trackMapPath, "utf8") : "";
2059
2097
  const rows = parseTrackMcpMap(mapRaw);
2060
2098
  return mergeMcpServers(merged, rows, opts.tracks);
2061
2099
  }
@@ -2077,8 +2115,8 @@ function writeMcpJson(path, mcp) {
2077
2115
 
2078
2116
  // src/opencode/transform.ts
2079
2117
  init_esm_shims();
2080
- import { existsSync as existsSync9, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
2081
- import { basename as basename3, join as join8 } from "path";
2118
+ import { existsSync as existsSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
2119
+ import { basename as basename3, join as join9 } from "path";
2082
2120
 
2083
2121
  // src/opencode/agents-md.ts
2084
2122
  init_esm_shims();
@@ -2179,15 +2217,15 @@ function parseTemplate(template) {
2179
2217
  // src/opencode/transform.ts
2180
2218
  function runOpencodeTransform(params) {
2181
2219
  const { harnessRoot, projectDir, selectedInternalSkills = [] } = params;
2182
- const claudeMd = readRequired2(join8(harnessRoot, "templates/CLAUDE.md"));
2183
- const agentsTemplate = readRequired2(join8(harnessRoot, "templates/opencode/AGENTS.md.template"));
2220
+ const claudeMd = readRequired2(join9(harnessRoot, "templates/CLAUDE.md"));
2221
+ const agentsTemplate = readRequired2(join9(harnessRoot, "templates/opencode/AGENTS.md.template"));
2184
2222
  const opencodeTemplate = readRequired2(
2185
- join8(harnessRoot, "templates/opencode/opencode.json.template")
2223
+ join9(harnessRoot, "templates/opencode/opencode.json.template")
2186
2224
  );
2187
2225
  const projectName = basename3(projectDir);
2188
- const mcp = readOptionalJson2(join8(harnessRoot, ".mcp.json"));
2226
+ const mcp = readOptionalJson2(join9(harnessRoot, ".mcp.json"));
2189
2227
  ensureDir(projectDir);
2190
- const agentsMdPath = join8(projectDir, "AGENTS.md");
2228
+ const agentsMdPath = join9(projectDir, "AGENTS.md");
2191
2229
  const agentsMdOut = renderAgentsMd2({
2192
2230
  template: agentsTemplate,
2193
2231
  claudeMd,
@@ -2196,18 +2234,18 @@ function runOpencodeTransform(params) {
2196
2234
  });
2197
2235
  backupFileIfChanged(agentsMdPath, agentsMdOut);
2198
2236
  writeFileSync7(agentsMdPath, agentsMdOut);
2199
- const opencodeJsonPath = join8(projectDir, "opencode.json");
2237
+ const opencodeJsonPath = join9(projectDir, "opencode.json");
2200
2238
  writeFileSync7(opencodeJsonPath, renderOpencodeJson({ template: opencodeTemplate, mcp }));
2201
- const cmdDir = join8(projectDir, ".opencode/commands");
2239
+ const cmdDir = join9(projectDir, ".opencode/commands");
2202
2240
  ensureDir(cmdDir);
2203
2241
  const commandFiles = [];
2204
2242
  for (const id of selectedInternalSkills) {
2205
- const src = join8(harnessRoot, "templates/skills", id, "SKILL.md");
2206
- if (!existsSync9(src)) {
2243
+ const src = join9(harnessRoot, "templates/skills", id, "SKILL.md");
2244
+ if (!existsSync10(src)) {
2207
2245
  continue;
2208
2246
  }
2209
- const target = join8(cmdDir, `${id}.md`);
2210
- const shellDependent = existsSync9(join8(harnessRoot, "templates/skills", id, "scripts"));
2247
+ const target = join9(cmdDir, `${id}.md`);
2248
+ const shellDependent = existsSync10(join9(harnessRoot, "templates/skills", id, "scripts"));
2211
2249
  writeFileSync7(
2212
2250
  target,
2213
2251
  renderCommandFromSkill(readFileSync9(src, "utf8"), id, { shellDependent })
@@ -2217,13 +2255,13 @@ function runOpencodeTransform(params) {
2217
2255
  return { agentsMdPath, opencodeJsonPath, commandFiles };
2218
2256
  }
2219
2257
  function readRequired2(path) {
2220
- if (!existsSync9(path)) {
2258
+ if (!existsSync10(path)) {
2221
2259
  throw new Error(`OpenCode transform: required source missing: ${path}`);
2222
2260
  }
2223
2261
  return readFileSync9(path, "utf8");
2224
2262
  }
2225
2263
  function readOptionalJson2(path) {
2226
- if (!existsSync9(path)) {
2264
+ if (!existsSync10(path)) {
2227
2265
  return null;
2228
2266
  }
2229
2267
  try {
@@ -2261,15 +2299,15 @@ function addPreToolUseHook(settings, matcher, command) {
2261
2299
  init_esm_shims();
2262
2300
  import {
2263
2301
  copyFileSync as copyFileSync2,
2264
- existsSync as existsSync10,
2302
+ existsSync as existsSync11,
2265
2303
  readdirSync as readdirSync2,
2266
2304
  readFileSync as readFileSync10,
2267
2305
  unlinkSync,
2268
2306
  writeFileSync as writeFileSync8
2269
2307
  } from "fs";
2270
- import { join as join9 } from "path";
2308
+ import { join as join10 } from "path";
2271
2309
  function runUpdateMode(projectDir, templatesDir) {
2272
- const claudeDir = join9(projectDir, ".claude");
2310
+ const claudeDir = join10(projectDir, ".claude");
2273
2311
  const report = {
2274
2312
  updated: {},
2275
2313
  pruned: {},
@@ -2278,26 +2316,26 @@ function runUpdateMode(projectDir, templatesDir) {
2278
2316
  };
2279
2317
  const targets = [
2280
2318
  {
2281
- target: join9(claudeDir, "rules"),
2282
- source: join9(templatesDir, "rules"),
2319
+ target: join10(claudeDir, "rules"),
2320
+ source: join10(templatesDir, "rules"),
2283
2321
  pattern: ".md",
2284
2322
  label: ".claude/rules"
2285
2323
  },
2286
2324
  {
2287
- target: join9(claudeDir, "agents"),
2288
- source: join9(templatesDir, "agents"),
2325
+ target: join10(claudeDir, "agents"),
2326
+ source: join10(templatesDir, "agents"),
2289
2327
  pattern: ".md",
2290
2328
  label: ".claude/agents"
2291
2329
  },
2292
2330
  {
2293
- target: join9(claudeDir, "commands/uzys"),
2294
- source: join9(templatesDir, "commands/uzys"),
2331
+ target: join10(claudeDir, "commands/uzys"),
2332
+ source: join10(templatesDir, "commands/uzys"),
2295
2333
  pattern: ".md",
2296
2334
  label: ".claude/commands/uzys"
2297
2335
  },
2298
2336
  {
2299
- target: join9(claudeDir, "hooks"),
2300
- source: join9(templatesDir, "hooks"),
2337
+ target: join10(claudeDir, "hooks"),
2338
+ source: join10(templatesDir, "hooks"),
2301
2339
  pattern: ".sh",
2302
2340
  label: ".claude/hooks"
2303
2341
  }
@@ -2306,26 +2344,26 @@ function runUpdateMode(projectDir, templatesDir) {
2306
2344
  report.updated[t.label] = updateDir(t.target, t.source, t.pattern);
2307
2345
  report.pruned[t.label] = pruneOrphans(t.target, t.source, t.pattern);
2308
2346
  }
2309
- const claudeMd = join9(claudeDir, "CLAUDE.md");
2310
- const templateMd = join9(templatesDir, "CLAUDE.md");
2311
- if (existsSync10(claudeMd) && existsSync10(templateMd)) {
2347
+ const claudeMd = join10(claudeDir, "CLAUDE.md");
2348
+ const templateMd = join10(templatesDir, "CLAUDE.md");
2349
+ if (existsSync11(claudeMd) && existsSync11(templateMd)) {
2312
2350
  copyFileSync2(templateMd, claudeMd);
2313
2351
  report.claudeMdUpdated = true;
2314
2352
  }
2315
- const settingsPath = join9(claudeDir, "settings.json");
2316
- if (existsSync10(settingsPath)) {
2317
- report.staleHookRefs = cleanStaleHookRefs(settingsPath, join9(claudeDir, "hooks"));
2353
+ const settingsPath = join10(claudeDir, "settings.json");
2354
+ if (existsSync11(settingsPath)) {
2355
+ report.staleHookRefs = cleanStaleHookRefs(settingsPath, join10(claudeDir, "hooks"));
2318
2356
  }
2319
2357
  return report;
2320
2358
  }
2321
2359
  function updateDir(target, source, ext) {
2322
- if (!existsSync10(target) || !existsSync10(source)) return 0;
2360
+ if (!existsSync11(target) || !existsSync11(source)) return 0;
2323
2361
  let count = 0;
2324
2362
  for (const file of readdirSync2(target)) {
2325
2363
  if (!file.endsWith(ext)) continue;
2326
- const targetFile = join9(target, file);
2327
- const sourceFile = join9(source, file);
2328
- if (existsSync10(sourceFile)) {
2364
+ const targetFile = join10(target, file);
2365
+ const sourceFile = join10(source, file);
2366
+ if (existsSync11(sourceFile)) {
2329
2367
  copyFileSync2(sourceFile, targetFile);
2330
2368
  count++;
2331
2369
  }
@@ -2333,13 +2371,13 @@ function updateDir(target, source, ext) {
2333
2371
  return count;
2334
2372
  }
2335
2373
  function pruneOrphans(target, source, ext) {
2336
- if (!existsSync10(target) || !existsSync10(source)) return [];
2374
+ if (!existsSync11(target) || !existsSync11(source)) return [];
2337
2375
  const removed = [];
2338
2376
  for (const file of readdirSync2(target)) {
2339
2377
  if (!file.endsWith(ext)) continue;
2340
- const sourceFile = join9(source, file);
2341
- if (!existsSync10(sourceFile)) {
2342
- const targetFile = join9(target, file);
2378
+ const sourceFile = join10(source, file);
2379
+ if (!existsSync11(sourceFile)) {
2380
+ const targetFile = join10(target, file);
2343
2381
  try {
2344
2382
  unlinkSync(targetFile);
2345
2383
  removed.push(file);
@@ -2380,7 +2418,7 @@ function keepHookRef(hook, hooksDir, removed) {
2380
2418
  const refMatch = (hook?.command ?? "").match(/\/\.claude\/hooks\/([^"\s/]+\.sh)/);
2381
2419
  if (!refMatch?.[1]) return true;
2382
2420
  const fname = refMatch[1];
2383
- const exists = existsSync10(join9(hooksDir, fname));
2421
+ const exists = existsSync11(join10(hooksDir, fname));
2384
2422
  if (!exists && !removed.includes(fname)) removed.push(fname);
2385
2423
  return exists;
2386
2424
  }
@@ -2391,12 +2429,12 @@ var KARPATHY_ASSET_ID = "karpathy-coder";
2391
2429
  function runInstall(ctx) {
2392
2430
  const { harnessRoot, projectDir, spec } = ctx;
2393
2431
  const mode = ctx.mode ?? "fresh";
2394
- const templatesDir = join10(harnessRoot, "templates");
2395
- if (!existsSync11(templatesDir)) {
2432
+ const templatesDir = join11(harnessRoot, "templates");
2433
+ if (!existsSync12(templatesDir)) {
2396
2434
  throw new Error(`Templates dir not found: ${templatesDir}`);
2397
2435
  }
2398
- const claudeDir = join10(projectDir, ".claude");
2399
- if (mode === "update" && !existsSync11(claudeDir)) {
2436
+ const claudeDir = join11(projectDir, ".claude");
2437
+ if (mode === "update" && !existsSync12(claudeDir)) {
2400
2438
  throw new Error(`Update mode requires existing .claude/ at ${claudeDir}`);
2401
2439
  }
2402
2440
  const backupPath = resolveBackupPath(ctx, mode, claudeDir);
@@ -2406,6 +2444,11 @@ function runInstall(ctx) {
2406
2444
  const manifestSpec = buildManifestSpec(spec);
2407
2445
  const base = spec.cli.includes("claude") ? installClaudeBaseline(manifestSpec, projectDir, templatesDir) : emptyClaudeBaseline();
2408
2446
  const mcpResult = composeAndWriteMcp(harnessRoot, projectDir, spec);
2447
+ const ciScaffold = isAssetSelected("ci-scaffold", {
2448
+ tracks: spec.tracks,
2449
+ options: spec.options,
2450
+ ...spec.userOverride ? { userOverride: spec.userOverride } : {}
2451
+ }) ? installCiScaffold({ harnessRoot, projectDir, tracks: spec.tracks }) : null;
2409
2452
  const baseline = {
2410
2453
  filesCopied: base.filesCopied,
2411
2454
  dirsCopied: base.dirsCopied,
@@ -2414,6 +2457,7 @@ function runInstall(ctx) {
2414
2457
  installedTracks: [...spec.tracks].sort(),
2415
2458
  mcpServers: Object.keys(mcpResult.mcpServers).sort(),
2416
2459
  ...runCliTransforms(spec, harnessRoot, projectDir, manifestSpec.selectedInternalSkills),
2460
+ ciScaffold,
2417
2461
  updateMode: null,
2418
2462
  mode,
2419
2463
  envFiles: writeEnvironmentFiles(projectDir, spec.tracks),
@@ -2445,6 +2489,7 @@ function runUpdateInstall(ctx, templatesDir, backupPath) {
2445
2489
  codexOptIn: null,
2446
2490
  opencode: null,
2447
2491
  antigravity: null,
2492
+ ciScaffold: null,
2448
2493
  updateMode: updateReport,
2449
2494
  mode: "update",
2450
2495
  envFiles: {
@@ -2498,9 +2543,9 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir) {
2498
2543
  if (!entry.applies(manifestSpec)) {
2499
2544
  continue;
2500
2545
  }
2501
- const source = join10(templatesDir, entry.source);
2502
- const target = join10(projectDir, entry.target);
2503
- if (!existsSync11(source)) {
2546
+ const source = join11(templatesDir, entry.source);
2547
+ const target = join11(projectDir, entry.target);
2548
+ if (!existsSync12(source)) {
2504
2549
  result.skipped += 1;
2505
2550
  continue;
2506
2551
  }
@@ -2519,8 +2564,8 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir) {
2519
2564
  }
2520
2565
  accumulateCategory(result.categories, entry);
2521
2566
  }
2522
- const hookDir = join10(projectDir, ".claude/hooks");
2523
- if (existsSync11(hookDir)) {
2567
+ const hookDir = join11(projectDir, ".claude/hooks");
2568
+ if (existsSync12(hookDir)) {
2524
2569
  chmodHooksSync(hookDir);
2525
2570
  }
2526
2571
  writeInstalledTracks(projectDir, manifestSpec.tracks);
@@ -2630,10 +2675,10 @@ function wireKarpathyHook(spec, external, harnessRoot, projectDir) {
2630
2675
  if (!karpathyResult?.ok) {
2631
2676
  return { wired: false, reason: "plugin-install-failed" };
2632
2677
  }
2633
- const sourceHook = join10(harnessRoot, "templates/hooks/karpathy-gate.sh");
2634
- const targetHook = join10(projectDir, ".claude/hooks/karpathy-gate.sh");
2678
+ const sourceHook = join11(harnessRoot, "templates/hooks/karpathy-gate.sh");
2679
+ const targetHook = join11(projectDir, ".claude/hooks/karpathy-gate.sh");
2635
2680
  let hookScriptCopied = false;
2636
- if (existsSync11(sourceHook)) {
2681
+ if (existsSync12(sourceHook)) {
2637
2682
  copyFile(sourceHook, targetHook);
2638
2683
  try {
2639
2684
  chmodSync2(targetHook, 493);
@@ -2641,9 +2686,9 @@ function wireKarpathyHook(spec, external, harnessRoot, projectDir) {
2641
2686
  }
2642
2687
  hookScriptCopied = true;
2643
2688
  }
2644
- const settingsPath = join10(projectDir, ".claude/settings.json");
2689
+ const settingsPath = join11(projectDir, ".claude/settings.json");
2645
2690
  let settingsUpdated = false;
2646
- if (existsSync11(settingsPath)) {
2691
+ if (existsSync12(settingsPath)) {
2647
2692
  const raw = readFileSync11(settingsPath, "utf8");
2648
2693
  let before;
2649
2694
  try {
@@ -2663,10 +2708,10 @@ function wireKarpathyHook(spec, external, harnessRoot, projectDir) {
2663
2708
  return { wired: true, settingsUpdated, hookScriptCopied };
2664
2709
  }
2665
2710
  function composeAndWriteMcp(harnessRoot, projectDir, spec) {
2666
- const mcpPath = join10(projectDir, ".mcp.json");
2711
+ const mcpPath = join11(projectDir, ".mcp.json");
2667
2712
  const composed = composeMcpJson({
2668
- templateMcpPath: join10(harnessRoot, "templates/mcp.json"),
2669
- trackMapPath: join10(harnessRoot, "templates/track-mcp-map.tsv"),
2713
+ templateMcpPath: join11(harnessRoot, "templates/mcp.json"),
2714
+ trackMapPath: join11(harnessRoot, "templates/track-mcp-map.tsv"),
2670
2715
  existingPath: mcpPath,
2671
2716
  tracks: spec.tracks
2672
2717
  });
@@ -2692,7 +2737,7 @@ function accumulateCategory(cats, entry) {
2692
2737
  }
2693
2738
  }
2694
2739
  function writeInstalledTracks(projectDir, tracks) {
2695
- const path = join10(projectDir, ".claude/.installed-tracks");
2740
+ const path = join11(projectDir, ".claude/.installed-tracks");
2696
2741
  mkdirSync4(dirname4(path), { recursive: true });
2697
2742
  const sorted = [...new Set(tracks)].sort().join("\n");
2698
2743
  writeFileSync9(path, `${sorted}
@@ -2700,7 +2745,7 @@ function writeInstalledTracks(projectDir, tracks) {
2700
2745
  }
2701
2746
  function writeRootClaudeMd(projectDir, tracks) {
2702
2747
  const content = mergeProjectClaude(tracks, { projectName: basename4(projectDir) });
2703
- const target = join10(projectDir, "CLAUDE.md");
2748
+ const target = join11(projectDir, "CLAUDE.md");
2704
2749
  const backup = backupFileIfChanged(target, content);
2705
2750
  writeFileSync9(target, content);
2706
2751
  return { content, backup };
@@ -2722,8 +2767,8 @@ init_esm_shims();
2722
2767
 
2723
2768
  // src/context-cost.ts
2724
2769
  init_esm_shims();
2725
- import { existsSync as existsSync12, readFileSync as readFileSync12 } from "fs";
2726
- import { join as join11, resolve as resolve2 } from "path";
2770
+ import { existsSync as existsSync13, readFileSync as readFileSync12 } from "fs";
2771
+ import { join as join12, resolve as resolve2 } from "path";
2727
2772
  import { fileURLToPath } from "url";
2728
2773
  var CHARS_PER_TOKEN = 4;
2729
2774
  function estimateTokens(chars) {
@@ -2739,8 +2784,8 @@ function extractFrontmatter(content) {
2739
2784
  function assetDescriptorTokens(assetId, root = resolveBundleRoot()) {
2740
2785
  const asset = EXTERNAL_ASSETS.find((a) => a.id === assetId);
2741
2786
  if (!asset || asset.method.kind !== "internal") return null;
2742
- const skillMd = join11(root, "templates", "skills", asset.method.key, "SKILL.md");
2743
- if (!existsSync12(skillMd)) return null;
2787
+ const skillMd = join12(root, "templates", "skills", asset.method.key, "SKILL.md");
2788
+ if (!existsSync13(skillMd)) return null;
2744
2789
  const fm = extractFrontmatter(readFileSync12(skillMd, "utf8"));
2745
2790
  return fm === null ? null : estimateTokens(fm.length);
2746
2791
  }
@@ -3139,6 +3184,14 @@ function renderPhase1Rows(log, baseline, verbose = false, withEcc = false, claud
3139
3184
  )
3140
3185
  );
3141
3186
  }
3187
+ if (baseline.ciScaffold) {
3188
+ for (const f of baseline.ciScaffold.written) {
3189
+ log(assetRow("success", f, "CI scaffold (fill-in template)", TEMPLATES_COL));
3190
+ }
3191
+ for (const f of baseline.ciScaffold.skippedExisting) {
3192
+ log(assetRow("skip", f, "exists \u2014 preserved (no overwrite)", TEMPLATES_COL));
3193
+ }
3194
+ }
3142
3195
  if (baseline.skipped > 0) {
3143
3196
  log(
3144
3197
  assetRow(
@@ -3403,8 +3456,8 @@ function registerInstallCommand(cli2) {
3403
3456
  // src/commands/uninstall.ts
3404
3457
  init_esm_shims();
3405
3458
  import { spawnSync as spawnSync2 } from "child_process";
3406
- import { existsSync as existsSync13, readFileSync as readFileSync13, rmSync } from "fs";
3407
- import { join as join12, resolve as resolve4 } from "path";
3459
+ import { existsSync as existsSync14, readFileSync as readFileSync13, rmSync } from "fs";
3460
+ import { join as join13, resolve as resolve4 } from "path";
3408
3461
  function uninstallAction(options, deps = {}) {
3409
3462
  const log = deps.log ?? console.log;
3410
3463
  const err = deps.err ?? console.error;
@@ -3576,21 +3629,21 @@ function buildGlobalAdvisoryCmd(asset) {
3576
3629
  }
3577
3630
  }
3578
3631
  function removeTemplates(log, projectDir, rm) {
3579
- rm(join12(projectDir, log.templates.claudeDir));
3580
- if (log.templates.codexDir) rm(join12(projectDir, log.templates.codexDir));
3581
- if (log.templates.opencodeDir) rm(join12(projectDir, log.templates.opencodeDir));
3632
+ rm(join13(projectDir, log.templates.claudeDir));
3633
+ if (log.templates.codexDir) rm(join13(projectDir, log.templates.codexDir));
3634
+ if (log.templates.opencodeDir) rm(join13(projectDir, log.templates.opencodeDir));
3582
3635
  const rootMd = log.templates.rootClaudeMd;
3583
3636
  if (rootMd) {
3584
3637
  if (rootClaudeMdModified(log, projectDir)) return { rootClaudeMdKept: true };
3585
- rm(join12(projectDir, rootMd.path));
3638
+ rm(join13(projectDir, rootMd.path));
3586
3639
  }
3587
3640
  return { rootClaudeMdKept: false };
3588
3641
  }
3589
3642
  function rootClaudeMdModified(log, projectDir) {
3590
3643
  const rootMd = log.templates.rootClaudeMd;
3591
3644
  if (!rootMd) return false;
3592
- const path = join12(projectDir, rootMd.path);
3593
- if (!existsSync13(path)) return false;
3645
+ const path = join13(projectDir, rootMd.path);
3646
+ if (!existsSync14(path)) return false;
3594
3647
  return hashContent(readFileSync13(path, "utf8")) !== rootMd.sha256;
3595
3648
  }
3596
3649
  function formatTemplateList(log) {
@@ -3603,7 +3656,7 @@ function defaultSpawn2(cmd, args) {
3603
3656
  return spawnSync2(cmd, [...args], { encoding: "utf8", stdio: "pipe", timeout: 12e4 });
3604
3657
  }
3605
3658
  function defaultRm(path) {
3606
- if (existsSync13(path)) {
3659
+ if (existsSync14(path)) {
3607
3660
  rmSync(path, { recursive: true, force: true });
3608
3661
  }
3609
3662
  }
@@ -4897,8 +4950,8 @@ Proceed?`,
4897
4950
 
4898
4951
  // src/state.ts
4899
4952
  init_esm_shims();
4900
- import { existsSync as existsSync14, readFileSync as readFileSync14 } from "fs";
4901
- import { join as join13 } from "path";
4953
+ import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
4954
+ import { join as join14 } from "path";
4902
4955
  var META_FILE = ".claude/.installed-tracks";
4903
4956
  var LEGACY_SIGNATURES = [
4904
4957
  { rule: "htmx.md", track: "ssr-htmx" },
@@ -4908,13 +4961,13 @@ var LEGACY_SIGNATURES = [
4908
4961
  { rule: "cli-development.md", track: "tooling" }
4909
4962
  ];
4910
4963
  function detectInstallState(projectDir) {
4911
- const claudeDir = join13(projectDir, ".claude");
4912
- const hasClaudeDir = existsSync14(claudeDir);
4964
+ const claudeDir = join14(projectDir, ".claude");
4965
+ const hasClaudeDir = existsSync15(claudeDir);
4913
4966
  if (!hasClaudeDir) {
4914
4967
  return { state: "new", tracks: [], source: "none", hasClaudeDir: false };
4915
4968
  }
4916
- const metaPath = join13(projectDir, META_FILE);
4917
- if (existsSync14(metaPath)) {
4969
+ const metaPath = join14(projectDir, META_FILE);
4970
+ if (existsSync15(metaPath)) {
4918
4971
  const tracks2 = readMetafile(metaPath);
4919
4972
  return { state: "existing", tracks: tracks2, source: "metafile", hasClaudeDir: true };
4920
4973
  }
@@ -4933,13 +4986,13 @@ function readMetafile(path) {
4933
4986
  return [...seen].sort();
4934
4987
  }
4935
4988
  function inferFromLegacySignatures(projectDir) {
4936
- const rulesDir = join13(projectDir, ".claude/rules");
4937
- if (!existsSync14(rulesDir)) {
4989
+ const rulesDir = join14(projectDir, ".claude/rules");
4990
+ if (!existsSync15(rulesDir)) {
4938
4991
  return [];
4939
4992
  }
4940
4993
  const found = /* @__PURE__ */ new Set();
4941
4994
  for (const sig of LEGACY_SIGNATURES) {
4942
- if (existsSync14(join13(rulesDir, sig.rule))) {
4995
+ if (existsSync15(join14(rulesDir, sig.rule))) {
4943
4996
  found.add(sig.track);
4944
4997
  }
4945
4998
  }