@uzysjung/agent-harness 26.146.1 → 26.147.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.
Files changed (34) hide show
  1. package/dist/{chunk-OEIGA5IL.js → chunk-GPH5AWLX.js} +450 -53
  2. package/dist/chunk-GPH5AWLX.js.map +1 -0
  3. package/dist/index.js +604 -529
  4. package/dist/index.js.map +1 -1
  5. package/dist/trust-tier-drift.js +1 -1
  6. package/package.json +1 -1
  7. package/templates/CLAUDE.md +14 -5
  8. package/templates/agents/implementer.md +1 -1
  9. package/templates/codex/AGENTS.md.template +11 -0
  10. package/templates/opencode/AGENTS.md.template +8 -1
  11. package/templates/opencode/opencode.json.template +0 -1
  12. package/templates/rules/cli-development.md +2 -2
  13. package/templates/rules/doc-governance.md +3 -1
  14. package/templates/rules/test-policy.md +5 -6
  15. package/templates/scripts/check-absence.sh +205 -0
  16. package/templates/settings.json +0 -9
  17. package/templates/skills/gh-issue-workflow/ISSUE.template.md +111 -35
  18. package/templates/skills/gh-issue-workflow/SKILL.md +268 -138
  19. package/templates/skills/gh-issue-workflow/references/hierarchy.md +261 -0
  20. package/templates/skills/gh-issue-workflow/references/operating-model.md +132 -0
  21. package/templates/skills/gh-issue-workflow/references/readiness.md +121 -0
  22. package/templates/skills/ui-visual-review/SKILL.md +17 -3
  23. package/dist/chunk-OEIGA5IL.js.map +0 -1
  24. package/templates/commands/ecc/checkpoint.md +0 -32
  25. package/templates/commands/ecc/e2e.md +0 -105
  26. package/templates/commands/ecc/eval.md +0 -88
  27. package/templates/commands/ecc/evolve.md +0 -7
  28. package/templates/commands/ecc/harness-audit.md +0 -73
  29. package/templates/commands/ecc/instinct-status.md +0 -8
  30. package/templates/commands/ecc/promote.md +0 -10
  31. package/templates/commands/ecc/security-scan.md +0 -10
  32. package/templates/hooks/mcp-pre-exec.sh +0 -122
  33. package/templates/mcp-allowlist.example +0 -24
  34. package/templates/rules/playwright-launch.md +0 -16
@@ -229,7 +229,7 @@ var EXTERNAL_ASSETS = [
229
229
  id: "gh-issue-workflow",
230
230
  tier: "official",
231
231
  // uzys 자사 스킬 리포
232
- description: "GitHub issue workflow \u2014 investigate, draft, create, implement, verify, and close issue-backed work while keeping read-only, draft, external-write, and status stages distinct",
232
+ description: "GitHub issue workflow \u2014 run work through issues so user and agent share one backlog: group tasks under a parent (epic) issue, order them with milestones and labels, read comments before starting, promote decisions into the body, and keep read-only, draft, external-write, and status stages distinct",
233
233
  category: "workflow",
234
234
  source: "uzys",
235
235
  // 전 트랙 상주 보존 — north-star 와 같은 이유 (이관 전 COMMON_SKILL_DIRS).
@@ -499,6 +499,27 @@ var EXTERNAL_ASSETS = [
499
499
  condition: { kind: "opt-in" },
500
500
  method: { kind: "skill", source: "oso95/scroll-world", skill: "scroll-world" }
501
501
  },
502
+ // 2026-08-16 — Preline 은 Tailwind 컴포넌트 킷이지만 **프레임워크에 매이지 않는다**: htmx·
503
+ // vanilla 에도 붙는다(사용자 확인). 그래서 `frontend` 이면서 React/Vue 트랙 전용이 아니다.
504
+ //
505
+ // 리포 안에 스킬은 `skills/theme-generator/` **하나**다 — 공식 문서는 이것을
506
+ // "preline-theme-generator" 라 부르지만 실제 디렉터리명은 `theme-generator` 이고,
507
+ // `npx skills add` 가 보는 것은 디렉터리명이다(실측 2026-08-16). 문서 표기를 그대로 옮겼다면
508
+ // 설치가 조용히 빗나갔다.
509
+ //
510
+ // 같은 날 조사한 flowbite 는 **넣지 않는다**: 에이전트용 제공물이 MCP 서버뿐인데, 이 저장소의
511
+ // `.mcp.json` 조립은 트랙 조건만 읽어 opt-in 경로가 없다 — 넣으면 해당 트랙 전원에게 항상
512
+ // 켜진다. 안 쓰는 사람에게 MCP 툴 스키마만큼의 상주 비용을 물리는 형태라 기각(사용자 확정).
513
+ {
514
+ id: "preline",
515
+ tier: "vetted",
516
+ // htmlstreamofficial/preline 6,386 (2026-08-16)
517
+ description: "Preline theme generator \u2014 turns a brand description into a Preline UI Tailwind theme (tokens, dark mode, component overrides); framework-agnostic, works with plain HTML and htmx",
518
+ category: "frontend",
519
+ source: "htmlstreamofficial",
520
+ condition: { kind: "opt-in" },
521
+ method: { kind: "skill", source: "htmlstreamofficial/preline", skill: "theme-generator" }
522
+ },
502
523
  // === dev tools (has_dev_track) ===
503
524
  // v26.110.0 (ADR-039) — code-review: /code-review 커맨드 1개 (다중 에이전트 confidence
504
525
  // 스코어링 PR 리뷰). 기본 리뷰 스택(reviewer·code-reviewer·security-reviewer)과 표면 중복
@@ -783,6 +804,51 @@ var EXTERNAL_ASSETS = [
783
804
  skill: "web-design-guidelines"
784
805
  }
785
806
  },
807
+ {
808
+ // 2026-08-17 (사용자 지정) — 웹 게임 개발. **웹 전용이다**: 스킬 본문 frontmatter 가
809
+ // "HTML5, Canvas, WebGL, and JavaScript" 로 스스로 범위를 적고 있고 프레임워크도
810
+ // Phaser · Three.js · Babylon.js · A-Frame 이다. Unity/Unreal/Godot 은 안 덮으므로
811
+ // description 에 "web" 을 남긴다 — 엔진 이름을 지우면 네이티브 엔진까지 되는 것처럼 읽힌다.
812
+ // opt-in 인 이유: frontend 트랙 사용자 대부분은 게임을 만들지 않는다. 게임 트랙은 아직 없다.
813
+ id: "game-engine",
814
+ tier: "vetted",
815
+ // github/awesome-copilot 37,909★ (MIT, 2026-08-17 `gh api` 실측 · GitHub 공식 조직)
816
+ description: "Game engine \u2014 web game development with HTML5 Canvas \xB7 WebGL (Phaser \xB7 Three.js \xB7 Babylon.js): game loop, physics, collision, sprites, tilemaps, gamepad input, WebRTC multiplayer",
817
+ category: "frontend",
818
+ source: "github",
819
+ condition: { kind: "opt-in" },
820
+ method: {
821
+ kind: "skill",
822
+ source: "https://github.com/github/awesome-copilot",
823
+ skill: "game-engine"
824
+ }
825
+ },
826
+ {
827
+ // 2026-08-17 (사용자 지정) — 게임 스튜디오 **프로세스** 스킬 73종.
828
+ //
829
+ // description 이 두 가지를 반드시 말해야 하는 이유 (둘 다 Docker 실측):
830
+ // ① **에이전트는 안 깔린다.** 리포의 엔진 전문성(`godot-specialist`·`unity-specialist`·
831
+ // `unreal-specialist`)은 49개 *에이전트*에 있는데 `npx skills add` 는 `.claude/skills/`
832
+ // 만 가져온다(실측: 73 SKILL.md, settings.json·rules·hooks 미설치). "Unity 되나?" 로
833
+ // 고른 사용자가 스프린트 계획 스킬을 받게 되므로 안 적으면 그게 거짓 광고다.
834
+ // ② **상주 ~4,440 tok/세션.** 73 descriptor 실측(17,760자/4). tooling 트랙 baseline 전체가
835
+ // ~5,037 tok 이라 이 자산 하나가 상주를 거의 두 배로 만든다 — 이 저장소의 1차 지표를
836
+ // 가장 크게 움직이는 자산이라 선택 시점에 숫자가 보여야 한다.
837
+ //
838
+ // 저자의 공식 설치법은 `git clone` (프로젝트 템플릿)이다. 그 경로는 `.claude/settings.json`·
839
+ // rules·hooks 를 함께 들여와 우리 하네스와 같은 자리를 다투므로 카탈로그에 넣지 않는다.
840
+ id: "game-studios",
841
+ tier: "vetted",
842
+ // Donchitos/Claude-Code-Game-Studios 23,924★ (MIT · 최종 push 2026-05-21, 2026-08-17 `gh api` 실측)
843
+ description: "Game studio workflow \u2014 73 process skills (sprint-plan \xB7 qa-plan \xB7 playtest-report \xB7 release-checklist \xB7 art-bible \xB7 balance-check). Skills only: the repo's 49 engine agents (godot/unity/unreal-specialist) are NOT installed. ~4,440 tok/session resident",
844
+ category: "workflow",
845
+ source: "Donchitos",
846
+ condition: { kind: "opt-in" },
847
+ method: {
848
+ kind: "skill",
849
+ source: "https://github.com/Donchitos/Claude-Code-Game-Studios"
850
+ }
851
+ },
786
852
  // === Executive tracks ===
787
853
  {
788
854
  id: "anthropic-document-skills",
@@ -993,35 +1059,312 @@ function filterApplicableAssets(assets, ctx) {
993
1059
  return assets.filter((a) => shouldInstallAsset(a, ctx));
994
1060
  }
995
1061
 
996
- // src/categories.ts
1062
+ // src/manifest.ts
997
1063
  init_esm_shims();
998
- var CATEGORIES = [
999
- "frontend",
1000
- "backend",
1001
- "data",
1002
- "business",
1003
- "dev-tools",
1004
- "understanding",
1005
- "visual-media",
1006
- "workflow",
1007
- "ecc-suite"
1008
- ];
1009
- var CATEGORY_TITLES = {
1010
- frontend: "\u{1F3A8} Frontend (UI \xB7 Design)",
1011
- backend: "\u{1F5C4}\uFE0F Backend (API \xB7 DB \xB7 Deploy)",
1012
- data: "\u{1F4CA} Data",
1013
- business: "\u{1F4BC} Business (Documents)",
1014
- "dev-tools": "\u{1F6E1}\uFE0F Dev Tools (Security \xB7 Quality)",
1015
- // v26.78.0 — 에이전트 인지 증강: 환경에 대한 지각(영상·웹·코드) + 기억(memory).
1016
- understanding: "\u{1F9E0} Understanding (Perception \xB7 Memory)",
1017
- // v26.85.0 — 코드-퍼스트 비주얼/미디어 제작: 슬라이드·다이어그램·모션·동영상.
1018
- "visual-media": "\u{1F3AC} Visual & Media (Slides \xB7 Diagrams \xB7 Motion \xB7 Video)",
1019
- workflow: "\u{1F504} Workflow (Development Cycle)",
1020
- "ecc-suite": "\u{1F4E6} ECC Suite"
1021
- };
1022
1064
 
1023
- // src/manifest.ts
1065
+ // src/install-log.ts
1066
+ init_esm_shims();
1067
+ import { createHash } from "crypto";
1068
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync, writeFileSync } from "fs";
1069
+ import { dirname as dirname2, join as join2 } from "path";
1070
+
1071
+ // src/fs-ops.ts
1024
1072
  init_esm_shims();
1073
+ import {
1074
+ copyFileSync,
1075
+ cpSync,
1076
+ existsSync,
1077
+ constants as fsConstants,
1078
+ mkdirSync,
1079
+ readdirSync,
1080
+ readFileSync,
1081
+ realpathSync,
1082
+ renameSync,
1083
+ rmdirSync
1084
+ } from "fs";
1085
+ import { dirname, join } from "path";
1086
+ function ensureDir(path2) {
1087
+ mkdirSync(path2, { recursive: true });
1088
+ }
1089
+ function copyFile(source, target) {
1090
+ if (!existsSync(source)) {
1091
+ throw new Error(`Source not found: ${source}`);
1092
+ }
1093
+ mkdirSync(dirname(target), { recursive: true });
1094
+ copyFileSync(source, target);
1095
+ }
1096
+ function copyDir(source, target) {
1097
+ if (!existsSync(source)) {
1098
+ throw new Error(`Source dir not found: ${source}`);
1099
+ }
1100
+ mkdirSync(target, { recursive: true });
1101
+ cpSync(source, target, { recursive: true, force: true });
1102
+ }
1103
+ var MAX_BACKUP_CLAIMS = 100;
1104
+ function claimBackupPath(base, claim) {
1105
+ for (let n = 1; n <= MAX_BACKUP_CLAIMS; n++) {
1106
+ const candidate = n === 1 ? base : `${base}-${n}`;
1107
+ try {
1108
+ claim(candidate);
1109
+ return candidate;
1110
+ } catch (err) {
1111
+ if (err.code !== "EEXIST") throw err;
1112
+ }
1113
+ }
1114
+ throw new Error(`Cannot allocate a backup path for ${base} (${MAX_BACKUP_CLAIMS} taken)`);
1115
+ }
1116
+ function backupDir(target, now = /* @__PURE__ */ new Date()) {
1117
+ if (!existsSync(target)) {
1118
+ return null;
1119
+ }
1120
+ const backup = claimBackupPath(`${target}.backup-${formatStamp(now)}`, (c) => mkdirSync(c));
1121
+ rmdirSync(backup);
1122
+ renameSync(target, backup);
1123
+ return backup;
1124
+ }
1125
+ function copyBackupDir(target, now = /* @__PURE__ */ new Date()) {
1126
+ if (!existsSync(target)) {
1127
+ return null;
1128
+ }
1129
+ const backup = claimBackupPath(`${target}.backup-${formatStamp(now)}`, (c) => mkdirSync(c));
1130
+ cpSync(realpathSync(target), backup, { recursive: true });
1131
+ return backup;
1132
+ }
1133
+ function backupFileIfChanged(target, newContent, now = /* @__PURE__ */ new Date()) {
1134
+ if (!existsSync(target)) {
1135
+ return null;
1136
+ }
1137
+ if (readFileSync(target, "utf-8") === newContent) {
1138
+ return null;
1139
+ }
1140
+ return backupFile(target, now);
1141
+ }
1142
+ function backupFile(target, now = /* @__PURE__ */ new Date()) {
1143
+ return claimBackupPath(
1144
+ `${target}.backup-${formatStamp(now)}`,
1145
+ (c) => copyFileSync(target, c, fsConstants.COPYFILE_EXCL)
1146
+ );
1147
+ }
1148
+ function listFilesRecursive(dir, prefix = "") {
1149
+ if (!existsSync(dir)) return [];
1150
+ const out = [];
1151
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
1152
+ const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
1153
+ if (entry.isDirectory()) {
1154
+ out.push(...listFilesRecursive(join(dir, entry.name), rel));
1155
+ } else if (entry.isFile()) {
1156
+ out.push(rel);
1157
+ }
1158
+ }
1159
+ return out;
1160
+ }
1161
+ function formatStamp(now) {
1162
+ return now.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z").slice(0, 15);
1163
+ }
1164
+ function ensureProjectSkeleton(projectDir) {
1165
+ const dirs = [
1166
+ ".claude/commands/uzys",
1167
+ ".claude/rules",
1168
+ ".claude/skills",
1169
+ ".claude/agents",
1170
+ ".claude/hooks",
1171
+ "docs/decisions"
1172
+ ];
1173
+ for (const d of dirs) {
1174
+ mkdirSync(join(projectDir, d), { recursive: true });
1175
+ }
1176
+ }
1177
+
1178
+ // src/install-log.ts
1179
+ var INSTALL_LOG_FILENAME = ".harness-install.json";
1180
+ var INSTALL_LOG_DIR = ".uzys-agent-harness";
1181
+ var LEGACY_INSTALL_LOG_DIR = ".claude";
1182
+ var INSTALL_LOG_VERSION = 1;
1183
+ var POLICY_DIRS = [
1184
+ { dir: "rules", ext: ".md" },
1185
+ { dir: "agents", ext: ".md" },
1186
+ { dir: "commands/uzys", ext: ".md" },
1187
+ { dir: "hooks", ext: ".sh" }
1188
+ ];
1189
+ function buildAssetEntries(report, scope) {
1190
+ if (!report) return [];
1191
+ return report.attempted.filter((r) => r.ok).map((r) => assetToLogEntry(r.asset, scope, r.version));
1192
+ }
1193
+ function assetToLogEntry(asset, scope, version) {
1194
+ const detail = methodDetail(asset.method);
1195
+ const entry = {
1196
+ id: asset.id,
1197
+ category: asset.category,
1198
+ method: asset.method.kind,
1199
+ scope,
1200
+ detail
1201
+ };
1202
+ if (version) entry.version = version;
1203
+ return entry;
1204
+ }
1205
+ function methodDetail(method) {
1206
+ switch (method.kind) {
1207
+ case "plugin":
1208
+ return { marketplace: method.marketplace, pluginId: method.pluginId };
1209
+ case "skill":
1210
+ return { source: method.source, ...method.skill ? { skill: method.skill } : {} };
1211
+ case "npm":
1212
+ return { pkg: method.pkg };
1213
+ case "npx-run":
1214
+ return { cmd: method.cmd, args: (method.args ?? []).join(" ") };
1215
+ case "shell-script":
1216
+ return { script: method.script, args: method.args.join(" ") };
1217
+ case "internal":
1218
+ return { key: method.key };
1219
+ }
1220
+ }
1221
+ function buildInstallLog(spec, external, scope, rootClaudeMd, previous, claudeDirMovedAside = false, rootFiles = []) {
1222
+ const templates = {
1223
+ claudeDir: ".claude/",
1224
+ ...spec.cli.includes("codex") ? { codexDir: ".codex/" } : {},
1225
+ ...spec.cli.includes("opencode") ? { opencodeDir: ".opencode/" } : {},
1226
+ ...rootClaudeMd ? { rootClaudeMd } : {}
1227
+ };
1228
+ const log = {
1229
+ schemaVersion: INSTALL_LOG_VERSION,
1230
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
1231
+ scope,
1232
+ spec: {
1233
+ tracks: spec.tracks,
1234
+ cli: spec.cli,
1235
+ ...spec.baselineExclude && spec.baselineExclude.length > 0 ? { baselineExclude: spec.baselineExclude } : {}
1236
+ },
1237
+ // 이번 설치가 만든 항목이 이기고, 이번에 안 만든 항목은 이전 값을 그대로 둔다.
1238
+ // (예: claude 로 깔고 나중에 codex 만 추가 설치해도 root CLAUDE.md 기록이 살아남는다)
1239
+ templates: { ...previous?.templates, ...templates },
1240
+ assets: mergeAssets(
1241
+ claudeDirMovedAside ? previous?.assets?.filter(survivesClaudeDirRename) : previous?.assets,
1242
+ buildAssetEntries(external, scope)
1243
+ )
1244
+ };
1245
+ const mergedRootFiles = mergeRootFiles(previous?.rootFiles, rootFiles);
1246
+ if (mergedRootFiles.length > 0) log.rootFiles = mergedRootFiles;
1247
+ return log;
1248
+ }
1249
+ function mergeRootFiles(previous, current) {
1250
+ const byPath = /* @__PURE__ */ new Map();
1251
+ for (const file of [...previous ?? [], ...current]) {
1252
+ const prior = byPath.get(file.path);
1253
+ byPath.set(
1254
+ file.path,
1255
+ prior ? {
1256
+ path: file.path,
1257
+ change: prior.change === "created" ? "created" : file.change,
1258
+ notes: [.../* @__PURE__ */ new Set([...prior.notes, ...file.notes])]
1259
+ } : file
1260
+ );
1261
+ }
1262
+ return [...byPath.values()];
1263
+ }
1264
+ function survivesClaudeDirRename(asset) {
1265
+ if (asset.scope === "global") return true;
1266
+ switch (asset.method) {
1267
+ case "plugin":
1268
+ return true;
1269
+ // `~/.claude/plugins/cache` — 프로젝트 밖
1270
+ case "npm":
1271
+ return true;
1272
+ // `node_modules/`
1273
+ case "skill":
1274
+ return false;
1275
+ // `npx skills add` project scope → `.claude/skills/`
1276
+ case "shell-script":
1277
+ return false;
1278
+ // ecc-prune → `.claude/local-plugins/`
1279
+ case "npx-run":
1280
+ return false;
1281
+ case "internal":
1282
+ return false;
1283
+ }
1284
+ }
1285
+ function mergeAssets(previous, current) {
1286
+ if (!previous || previous.length === 0) return [...current];
1287
+ const currentById = new Map(current.map((a) => [a.id, a]));
1288
+ const previousIds = new Set(previous.map((a) => a.id));
1289
+ return [
1290
+ ...previous.map((a) => currentById.get(a.id) ?? a),
1291
+ ...current.filter((a) => !previousIds.has(a.id))
1292
+ ];
1293
+ }
1294
+ function hashContent(content) {
1295
+ return createHash("sha256").update(content, "utf8").digest("hex");
1296
+ }
1297
+ function isHarnessOwned(baseline, key, current) {
1298
+ const recorded = baseline.get(key);
1299
+ return recorded !== void 0 && recorded === hashContent(current);
1300
+ }
1301
+ function mergeExternalFiles(projectDir, previous, current) {
1302
+ const byPath = /* @__PURE__ */ new Map();
1303
+ for (const file of [...previous ?? [], ...current]) byPath.set(file.path, file);
1304
+ return [...byPath.values()].filter((f) => existsSync2(join2(projectDir, f.path)));
1305
+ }
1306
+ function collectSkillHashes(projectDir) {
1307
+ const skillsDir = join2(projectDir, ".claude/skills");
1308
+ return listFilesRecursive(skillsDir).map((rel) => ({
1309
+ path: rel,
1310
+ sha256: hashContent(readFileSync2(join2(skillsDir, rel), "utf8"))
1311
+ }));
1312
+ }
1313
+ function collectPolicyHashes(projectDir, templatesDir) {
1314
+ const out = [];
1315
+ for (const { dir, ext } of POLICY_DIRS) {
1316
+ const targetDir = join2(projectDir, ".claude", dir);
1317
+ const sourceDir = join2(templatesDir, dir);
1318
+ for (const rel of listFilesRecursive(targetDir)) {
1319
+ if (!rel.endsWith(ext)) continue;
1320
+ if (!existsSync2(join2(sourceDir, rel))) continue;
1321
+ out.push({
1322
+ path: `${dir}/${rel}`,
1323
+ sha256: hashContent(readFileSync2(join2(targetDir, rel), "utf8"))
1324
+ });
1325
+ }
1326
+ }
1327
+ return out;
1328
+ }
1329
+ function writeInstallLog(projectDir, log) {
1330
+ const path2 = installLogPath(projectDir);
1331
+ mkdirSync2(dirname2(path2), { recursive: true });
1332
+ writeFileSync(path2, `${JSON.stringify(log, null, 2)}
1333
+ `, "utf8");
1334
+ migrateAwayLegacyLog(projectDir);
1335
+ return path2;
1336
+ }
1337
+ function migrateAwayLegacyLog(projectDir) {
1338
+ const legacy = legacyInstallLogPath(projectDir);
1339
+ if (!existsSync2(legacy)) return;
1340
+ try {
1341
+ rmSync(legacy);
1342
+ } catch {
1343
+ }
1344
+ }
1345
+ function readInstallLog(projectDir) {
1346
+ const path2 = [installLogPath(projectDir), legacyInstallLogPath(projectDir)].find(
1347
+ (p) => existsSync2(p)
1348
+ );
1349
+ if (!path2) return null;
1350
+ try {
1351
+ const parsed = JSON.parse(readFileSync2(path2, "utf8"));
1352
+ if (Array.isArray(parsed.assets)) {
1353
+ parsed.assets = parsed.assets.map(
1354
+ (a) => a.method === "npm-global" ? { ...a, method: "npm" } : a
1355
+ );
1356
+ }
1357
+ return parsed;
1358
+ } catch {
1359
+ return null;
1360
+ }
1361
+ }
1362
+ function installLogPath(projectDir) {
1363
+ return join2(projectDir, INSTALL_LOG_DIR, INSTALL_LOG_FILENAME);
1364
+ }
1365
+ function legacyInstallLogPath(projectDir) {
1366
+ return join2(projectDir, LEGACY_INSTALL_LOG_DIR, INSTALL_LOG_FILENAME);
1367
+ }
1025
1368
 
1026
1369
  // src/project-claude-merge.ts
1027
1370
  init_esm_shims();
@@ -1165,7 +1508,7 @@ var ui = (s) => hasUiTrack(s.tracks);
1165
1508
  var onTracks = (pattern) => (s) => anyTrack(s.tracks, pattern);
1166
1509
  var COMMON_RULES = ["git-policy", "change-management", "doc-governance"];
1167
1510
  var DEV_RULES = ["test-policy", "ship-checklist"];
1168
- var UI_RULES = ["playwright-launch"];
1511
+ var UI_RULES = [];
1169
1512
  var TRACK_RULES = {
1170
1513
  "csr-supabase": [],
1171
1514
  "csr-fastify": [],
@@ -1199,6 +1542,7 @@ function resolveRules(spec) {
1199
1542
  }
1200
1543
  return [...set].sort();
1201
1544
  }
1545
+ var ALL_RULES = resolveRules({ tracks: [...TRACKS] });
1202
1546
  var CORE_AGENTS = ["reviewer", "data-analyst", "strategist"];
1203
1547
  var CORE_AGENTS_ECC = ["code-reviewer", "security-reviewer"];
1204
1548
  var DEV_AGENTS = ["plan-checker", "implementer"];
@@ -1206,7 +1550,6 @@ var DEV_AGENTS_ECC = ["silent-failure-hunter", "build-error-resolver"];
1206
1550
  var ALWAYS_HOOKS = [
1207
1551
  "session-start.sh",
1208
1552
  "protect-files.sh",
1209
- "mcp-pre-exec.sh",
1210
1553
  // UserPromptSubmit 넛지. 차단하지 않는 유일한 훅 — 판정은 결정적 두 조건뿐이고(길이 ·
1211
1554
  // `<objective>` 표식 부재) 변환 자체는 `task-brief` 스킬 몫이다. `templates/settings.json`
1212
1555
  // 배선과 한 벌 (hook-wiring-parity 가 한쪽만 있는 상태를 문다).
@@ -1222,6 +1565,9 @@ var MODIFIED_ECC_SKILL_DIRS = [...MODIFIED_COMMON_SKILL_DIRS, ...MODIFIED_DEV_SK
1222
1565
  var UI_SKILL_DIRS = ["ui-visual-review"];
1223
1566
  var UI_SKILL_DIRS_ECC = ["e2e-testing"];
1224
1567
  var PYTHON_SKILL_DIRS_ECC = ["python-patterns", "python-testing"];
1568
+ function isCliNeutralTarget(target) {
1569
+ return target.startsWith(`${INSTALL_LOG_DIR}/`);
1570
+ }
1225
1571
  function buildManifest(spec) {
1226
1572
  const m = [];
1227
1573
  for (const r of resolveRules(spec)) {
@@ -1232,12 +1578,6 @@ function buildManifest(spec) {
1232
1578
  applies: all
1233
1579
  });
1234
1580
  }
1235
- m.push({
1236
- source: "commands/ecc",
1237
- target: ".claude/commands/ecc",
1238
- type: "dir",
1239
- applies: (s) => !s.withEcc
1240
- });
1241
1581
  m.push({
1242
1582
  source: "CLAUDE.md",
1243
1583
  target: HARNESS_ANCHOR_FILE,
@@ -1256,6 +1596,12 @@ function buildManifest(spec) {
1256
1596
  type: "file",
1257
1597
  applies: all
1258
1598
  });
1599
+ m.push({
1600
+ source: "scripts/check-absence.sh",
1601
+ target: ".uzys-agent-harness/check-absence.sh",
1602
+ type: "file",
1603
+ applies: all
1604
+ });
1259
1605
  for (const a of CORE_AGENTS) {
1260
1606
  m.push({
1261
1607
  source: `agents/${a}.md`,
@@ -1411,10 +1757,37 @@ function buildManifest(spec) {
1411
1757
  return m;
1412
1758
  }
1413
1759
 
1760
+ // src/categories.ts
1761
+ init_esm_shims();
1762
+ var CATEGORIES = [
1763
+ "frontend",
1764
+ "backend",
1765
+ "data",
1766
+ "business",
1767
+ "dev-tools",
1768
+ "understanding",
1769
+ "visual-media",
1770
+ "workflow",
1771
+ "ecc-suite"
1772
+ ];
1773
+ var CATEGORY_TITLES = {
1774
+ frontend: "\u{1F3A8} Frontend (UI \xB7 Design)",
1775
+ backend: "\u{1F5C4}\uFE0F Backend (API \xB7 DB \xB7 Deploy)",
1776
+ data: "\u{1F4CA} Data",
1777
+ business: "\u{1F4BC} Business (Documents)",
1778
+ "dev-tools": "\u{1F6E1}\uFE0F Dev Tools (Security \xB7 Quality)",
1779
+ // v26.78.0 — 에이전트 인지 증강: 환경에 대한 지각(영상·웹·코드) + 기억(memory).
1780
+ understanding: "\u{1F9E0} Understanding (Perception \xB7 Memory)",
1781
+ // v26.85.0 — 코드-퍼스트 비주얼/미디어 제작: 슬라이드·다이어그램·모션·동영상.
1782
+ "visual-media": "\u{1F3AC} Visual & Media (Slides \xB7 Diagrams \xB7 Motion \xB7 Video)",
1783
+ workflow: "\u{1F504} Workflow (Development Cycle)",
1784
+ "ecc-suite": "\u{1F4E6} ECC Suite"
1785
+ };
1786
+
1414
1787
  // src/context-cost.ts
1415
1788
  init_esm_shims();
1416
- import { existsSync, readFileSync } from "fs";
1417
- import { join, resolve } from "path";
1789
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
1790
+ import { join as join3, resolve } from "path";
1418
1791
  import { fileURLToPath as fileURLToPath2 } from "url";
1419
1792
  var CHARS_PER_TOKEN = 4;
1420
1793
  function estimateTokens(chars) {
@@ -1430,17 +1803,17 @@ function extractFrontmatter(content) {
1430
1803
  function assetDescriptorTokens(assetId, root = resolveBundleRoot()) {
1431
1804
  const asset = EXTERNAL_ASSETS.find((a) => a.id === assetId);
1432
1805
  if (!asset || asset.method.kind !== "internal") return null;
1433
- const skillMd = join(root, "templates", "skills", asset.method.key, "SKILL.md");
1434
- if (!existsSync(skillMd)) return null;
1435
- const fm = extractFrontmatter(readFileSync(skillMd, "utf8"));
1806
+ const skillMd = join3(root, "templates", "skills", asset.method.key, "SKILL.md");
1807
+ if (!existsSync3(skillMd)) return null;
1808
+ const fm = extractFrontmatter(readFileSync3(skillMd, "utf8"));
1436
1809
  return fm === null ? null : estimateTokens(fm.length);
1437
1810
  }
1438
1811
  function assetBodyTokens(assetId, root = resolveBundleRoot()) {
1439
1812
  const asset = EXTERNAL_ASSETS.find((a) => a.id === assetId);
1440
1813
  if (!asset || asset.method.kind !== "internal") return null;
1441
- const skillMd = join(root, "templates", "skills", asset.method.key, "SKILL.md");
1442
- if (!existsSync(skillMd)) return null;
1443
- const content = readFileSync(skillMd, "utf8");
1814
+ const skillMd = join3(root, "templates", "skills", asset.method.key, "SKILL.md");
1815
+ if (!existsSync3(skillMd)) return null;
1816
+ const content = readFileSync3(skillMd, "utf8");
1444
1817
  const fm = extractFrontmatter(content);
1445
1818
  if (fm === null) return estimateTokens(content.length);
1446
1819
  const close = content.indexOf("\n---", content.indexOf(fm) + fm.length);
@@ -1455,15 +1828,15 @@ function assetCostRows(assetIds, root = resolveBundleRoot()) {
1455
1828
  })).sort((a, b) => (b.bodyTokens ?? -1) - (a.bodyTokens ?? -1));
1456
1829
  }
1457
1830
  function fileTokens(path2) {
1458
- return existsSync(path2) ? estimateTokens(readFileSync(path2, "utf8").trim().length) : 0;
1831
+ return existsSync3(path2) ? estimateTokens(readFileSync3(path2, "utf8").trim().length) : 0;
1459
1832
  }
1460
1833
  function descriptorTokens(path2) {
1461
- if (!existsSync(path2)) return 0;
1462
- const fm = extractFrontmatter(readFileSync(path2, "utf8"));
1834
+ if (!existsSync3(path2)) return 0;
1835
+ const fm = extractFrontmatter(readFileSync3(path2, "utf8"));
1463
1836
  return fm === null ? 0 : estimateTokens(fm.length);
1464
1837
  }
1465
1838
  function residentCost(entries, root = resolveBundleRoot()) {
1466
- const tpl = (source) => join(root, "templates", source);
1839
+ const tpl = (source) => join3(root, "templates", source);
1467
1840
  let rules = 0;
1468
1841
  let skillDescriptors = 0;
1469
1842
  let agentDescriptors = 0;
@@ -1478,11 +1851,11 @@ function residentCost(entries, root = resolveBundleRoot()) {
1478
1851
  agentDescriptors += descriptorTokens(tpl(e.source));
1479
1852
  agentItems += 1;
1480
1853
  } else if (e.target.startsWith(".claude/skills/")) {
1481
- skillDescriptors += descriptorTokens(join(tpl(e.source), "SKILL.md"));
1854
+ skillDescriptors += descriptorTokens(join3(tpl(e.source), "SKILL.md"));
1482
1855
  skillItems += 1;
1483
1856
  }
1484
1857
  }
1485
- const harnessAnchor = fileTokens(join(root, "templates", "CLAUDE.md"));
1858
+ const harnessAnchor = fileTokens(join3(root, "templates", "CLAUDE.md"));
1486
1859
  const projectScaffold = estimateTokens(renderFillScaffold().trim().length);
1487
1860
  const projectClaudeMd = harnessAnchor + projectScaffold;
1488
1861
  const claudeMdItems = (harnessAnchor > 0 ? 1 : 0) + (projectScaffold > 0 ? 1 : 0);
@@ -1565,6 +1938,8 @@ export {
1565
1938
  __commonJS,
1566
1939
  __toESM,
1567
1940
  init_esm_shims,
1941
+ anyTrack,
1942
+ hasUiTrack,
1568
1943
  TRACKS,
1569
1944
  isTrack,
1570
1945
  CLI_BASES,
@@ -1572,8 +1947,6 @@ export {
1572
1947
  isInstallScope,
1573
1948
  resolveScope,
1574
1949
  DEFAULT_OPTIONS,
1575
- anyTrack,
1576
- hasUiTrack,
1577
1950
  EXTERNAL_ASSETS,
1578
1951
  DEV_METHOD_SKILL_IDS,
1579
1952
  INTERNAL_BUNDLED_SKILL_IDS,
@@ -1584,14 +1957,38 @@ export {
1584
1957
  assetCliSupport,
1585
1958
  assetReachesCli,
1586
1959
  filterApplicableAssets,
1960
+ ensureDir,
1961
+ copyFile,
1962
+ copyDir,
1963
+ backupDir,
1964
+ copyBackupDir,
1965
+ backupFileIfChanged,
1966
+ backupFile,
1967
+ listFilesRecursive,
1968
+ ensureProjectSkeleton,
1969
+ INSTALL_LOG_DIR,
1970
+ POLICY_DIRS,
1971
+ buildInstallLog,
1972
+ hashContent,
1973
+ isHarnessOwned,
1974
+ mergeExternalFiles,
1975
+ collectSkillHashes,
1976
+ collectPolicyHashes,
1977
+ writeInstallLog,
1978
+ readInstallLog,
1979
+ installLogPath,
1980
+ legacyInstallLogPath,
1587
1981
  HARNESS_ANCHOR_FILE,
1588
1982
  HARNESS_IMPORT_LINE,
1589
1983
  renderFillScaffold,
1590
1984
  upsertHarnessImport,
1591
1985
  stripHarnessImport,
1986
+ resolveRules,
1987
+ ALL_RULES,
1988
+ isCliNeutralTarget,
1989
+ buildManifest,
1592
1990
  CATEGORIES,
1593
1991
  CATEGORY_TITLES,
1594
- buildManifest,
1595
1992
  estimateTokens,
1596
1993
  resolveBundleRoot,
1597
1994
  assetCostRows,
@@ -1600,4 +1997,4 @@ export {
1600
1997
  formatResidentCostLine,
1601
1998
  formatResidentCostBlock
1602
1999
  };
1603
- //# sourceMappingURL=chunk-OEIGA5IL.js.map
2000
+ //# sourceMappingURL=chunk-GPH5AWLX.js.map