@uzysjung/agent-harness 26.174.0 → 26.176.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/chunk-HB6UJWC6.js +5822 -0
- package/dist/chunk-HB6UJWC6.js.map +1 -0
- package/dist/index.js +1674 -3921
- package/dist/index.js.map +1 -1
- package/dist/trust-tier-drift.js +1 -1
- package/package.json +2 -2
- package/templates/hooks/protect-files.sh +7 -4
- package/dist/chunk-LAWKDFKZ.js +0 -2683
- package/dist/chunk-LAWKDFKZ.js.map +0 -1
package/dist/chunk-LAWKDFKZ.js
DELETED
|
@@ -1,2683 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __esm = (fn, res) => function __init() {
|
|
9
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
-
};
|
|
11
|
-
var __commonJS = (cb, mod) => function __require() {
|
|
12
|
-
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
13
|
-
};
|
|
14
|
-
var __copyProps = (to, from, except, desc) => {
|
|
15
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
16
|
-
for (let key of __getOwnPropNames(from))
|
|
17
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
18
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
19
|
-
}
|
|
20
|
-
return to;
|
|
21
|
-
};
|
|
22
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
23
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
24
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
25
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
26
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
27
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
28
|
-
mod
|
|
29
|
-
));
|
|
30
|
-
|
|
31
|
-
// node_modules/tsup/assets/esm_shims.js
|
|
32
|
-
import path from "path";
|
|
33
|
-
import { fileURLToPath } from "url";
|
|
34
|
-
var init_esm_shims = __esm({
|
|
35
|
-
"node_modules/tsup/assets/esm_shims.js"() {
|
|
36
|
-
"use strict";
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
// src/types.ts
|
|
41
|
-
init_esm_shims();
|
|
42
|
-
var TRACKS = [
|
|
43
|
-
// #456 (2026-09-20) — 스택 무관: 원칙·방법론·테스트 스킬만. 스택을 아직 안 정한 프로젝트용.
|
|
44
|
-
"base",
|
|
45
|
-
"tooling",
|
|
46
|
-
"csr-supabase",
|
|
47
|
-
"csr-fastify",
|
|
48
|
-
"csr-fastapi",
|
|
49
|
-
"ssr-htmx",
|
|
50
|
-
// #820 — htmx 의 Node 백엔드. `ssr-htmx`(FastAPI) 의 id 는 설치 기록이 갖고 있어 그대로 두고 옆에 더했다.
|
|
51
|
-
"ssr-htmx-fastify",
|
|
52
|
-
"ssr-nextjs",
|
|
53
|
-
"data",
|
|
54
|
-
"executive",
|
|
55
|
-
"full",
|
|
56
|
-
"project-management",
|
|
57
|
-
"growth-marketing"
|
|
58
|
-
];
|
|
59
|
-
function isTrack(value) {
|
|
60
|
-
return typeof value === "string" && TRACKS.includes(value);
|
|
61
|
-
}
|
|
62
|
-
var CLI_BASES = ["claude", "codex", "opencode", "antigravity"];
|
|
63
|
-
function isCliBase(value) {
|
|
64
|
-
return typeof value === "string" && CLI_BASES.includes(value);
|
|
65
|
-
}
|
|
66
|
-
var INSTALL_SCOPES = ["project", "global"];
|
|
67
|
-
function isInstallScope(value) {
|
|
68
|
-
return typeof value === "string" && INSTALL_SCOPES.includes(value);
|
|
69
|
-
}
|
|
70
|
-
function resolveScope(scope) {
|
|
71
|
-
return scope ?? "project";
|
|
72
|
-
}
|
|
73
|
-
var DEFAULT_OPTIONS = {
|
|
74
|
-
withCodexTrust: false
|
|
75
|
-
};
|
|
76
|
-
var UPDATE_GROUPS = [
|
|
77
|
-
"skills",
|
|
78
|
-
"new-skills",
|
|
79
|
-
"rules",
|
|
80
|
-
"anchor",
|
|
81
|
-
"hooks",
|
|
82
|
-
"external"
|
|
83
|
-
];
|
|
84
|
-
|
|
85
|
-
// src/external-assets.ts
|
|
86
|
-
init_esm_shims();
|
|
87
|
-
|
|
88
|
-
// src/track-match.ts
|
|
89
|
-
init_esm_shims();
|
|
90
|
-
function matchTrack(track, pattern) {
|
|
91
|
-
return pattern.split("|").some((p) => globToRegex(p.trim()).test(track));
|
|
92
|
-
}
|
|
93
|
-
function anyTrack(tracks, pattern) {
|
|
94
|
-
return tracks.some((t) => matchTrack(t, pattern));
|
|
95
|
-
}
|
|
96
|
-
function hasDevTrack(tracks) {
|
|
97
|
-
return anyTrack(tracks, "csr-*|ssr-*|data|full|tooling|base");
|
|
98
|
-
}
|
|
99
|
-
function hasUiTrack(tracks) {
|
|
100
|
-
return anyTrack(tracks, "csr-*|ssr-*|full");
|
|
101
|
-
}
|
|
102
|
-
function globToRegex(pattern) {
|
|
103
|
-
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
104
|
-
return new RegExp(`^${escaped}$`);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// src/external-assets.ts
|
|
108
|
-
var UI_TRACKS = TRACKS.filter((t) => hasUiTrack([t]));
|
|
109
|
-
var CSR_SSR_NEXTJS_FULL = [
|
|
110
|
-
"csr-supabase",
|
|
111
|
-
"csr-fastify",
|
|
112
|
-
"csr-fastapi",
|
|
113
|
-
"ssr-nextjs",
|
|
114
|
-
"full"
|
|
115
|
-
];
|
|
116
|
-
var EXTERNAL_ASSETS = [
|
|
117
|
-
// === data Track ===
|
|
118
|
-
{
|
|
119
|
-
id: "anthropic-data-plugin",
|
|
120
|
-
tier: "official",
|
|
121
|
-
// anthropics/knowledge-work-plugins 18k
|
|
122
|
-
description: "Anthropic data plugin \u2014 SQL exploration, analysis, and visualization workflows. Installing it does not connect your warehouse; that is a separate MCP setup",
|
|
123
|
-
category: "data",
|
|
124
|
-
source: "anthropics",
|
|
125
|
-
condition: { kind: "any-track", tracks: ["data", "full"] },
|
|
126
|
-
method: {
|
|
127
|
-
kind: "plugin",
|
|
128
|
-
marketplace: "anthropics/knowledge-work-plugins",
|
|
129
|
-
pluginId: "data@knowledge-work-plugins"
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
|
-
// === Internal template assets (v26.81.0, ADR-022 — 이전 OptionFlags.withTauri/withUzysHarness) ===
|
|
133
|
-
{
|
|
134
|
-
id: "tauri-desktop",
|
|
135
|
-
tier: "official",
|
|
136
|
-
// uzys 본 하네스 자체 템플릿
|
|
137
|
-
description: "Tauri desktop rule template (CSR/full tracks \u2014 manifest rule mapping)",
|
|
138
|
-
category: "frontend",
|
|
139
|
-
source: "uzys",
|
|
140
|
-
condition: { kind: "opt-in" },
|
|
141
|
-
method: { kind: "internal", key: "tauri-desktop" }
|
|
142
|
-
},
|
|
143
|
-
// v26.108.0 (ADR-037, 라이프사이클 자산화 ②) — CI 스캐폴드: `.github/workflows/` fill-in
|
|
144
|
-
// 워크플로(tag-트리거 CI + 실DB 서비스 컨테이너 + coverage 게이트 + Playwright E2E).
|
|
145
|
-
// 실무 CI 패턴의 도메인 중립 일반화 (CI 부재 프로젝트의 갭이 계기). `.claude/` 밖에
|
|
146
|
-
// 쓰는 첫 자산 — 기존 워크플로 파일은 절대 덮어쓰지 않는다 (src/ci-scaffold.ts,
|
|
147
|
-
// manifest 경유 아님 — CLI 선택과 무관하게 설치되는 CLI-agnostic 산출물).
|
|
148
|
-
// ADR-105 (#726, 사용자 결정 2026-10-11) — opt-in 전용 → 개발 트랙 기본. 없을 때만 쓰는 계약은 그대로.
|
|
149
|
-
{
|
|
150
|
-
id: "ci-scaffold",
|
|
151
|
-
tier: "official",
|
|
152
|
-
// uzys 본 하네스 자체 템플릿
|
|
153
|
-
description: "CI scaffold \u2014 .github/workflows fill-in templates (tag-triggered CI + real-DB service container + coverage gate + Playwright E2E); writes only when the file is absent, never overwrites existing workflow files",
|
|
154
|
-
category: "workflow",
|
|
155
|
-
source: "uzys",
|
|
156
|
-
condition: { kind: "has-dev-track" },
|
|
157
|
-
method: { kind: "internal", key: "ci-scaffold" }
|
|
158
|
-
},
|
|
159
|
-
// === Repo-bundled internal skills (uzys 1st-party, v26.87.0) ===
|
|
160
|
-
// 2026-08-02 복원 (ADR-062) — 이관(ADR-060) 후 감사에서 본문 열화 104건(dropped 76 ·
|
|
161
|
-
// damaged 28)이 확인돼 9종을 이 리포 번들로 되돌렸다. 아래 10종 전부 `kind: "internal"`.
|
|
162
|
-
// #602 — ui-visual-review: TRACKS.md 가 말하는 트랙 추종 번들 스킬("follows the track, not
|
|
163
|
-
// the catalog"). internal 카탈로그 자산으로 등록해 selectedInternalSkills 흐름에 태우면
|
|
164
|
-
// .agents/skills(비-Claude 자리)에도 깔린다 — 종전의 .claude/ 전용 배선과 어긋나던 것.
|
|
165
|
-
{
|
|
166
|
-
id: "ui-visual-review",
|
|
167
|
-
tier: "official",
|
|
168
|
-
// uzys 자사 스킬
|
|
169
|
-
description: "UI visual review \u2014 screenshot capture, baseline diff, and a review gate for UI changes (ships with UI tracks; ssr-htmx excluded)",
|
|
170
|
-
category: "frontend",
|
|
171
|
-
source: "uzys",
|
|
172
|
-
condition: {
|
|
173
|
-
kind: "any-track",
|
|
174
|
-
tracks: ["csr-supabase", "csr-fastify", "csr-fastapi", "ssr-nextjs", "full"]
|
|
175
|
-
},
|
|
176
|
-
method: { kind: "internal", key: "ui-visual-review" }
|
|
177
|
-
},
|
|
178
|
-
{
|
|
179
|
-
id: "compaction-handoff",
|
|
180
|
-
tier: "official",
|
|
181
|
-
// uzys 본 하네스 자체 템플릿
|
|
182
|
-
description: "Compaction handoff \u2014 persist durable state + git snapshot + resume anchor before a context /compact so nothing is lost",
|
|
183
|
-
category: "workflow",
|
|
184
|
-
source: "uzys",
|
|
185
|
-
condition: { kind: "has-dev-track" },
|
|
186
|
-
method: { kind: "internal", key: "compaction-handoff" }
|
|
187
|
-
},
|
|
188
|
-
// === uzys 방법론 스킬 — 이 리포 번들 (2026-08-02 복원, ADR-062) ===
|
|
189
|
-
// ASIS(ADR-060): `kind: "skill"` — uzysjung/uzys-agent-skills 에서 `npx skills add`.
|
|
190
|
-
// TOBE: templates/skills/<id>/ 번들 → 설치 시 dir copy (`kind: "internal"`).
|
|
191
|
-
// 이관본이 판정 기준·수치·워크드 예시를 잃어(감사 실측 104건) 스킬이 무엇을 하라는지가
|
|
192
|
-
// 남고 무엇으로 판정하는지가 사라졌다. 배포 경로를 하나로 모으는 이득보다 본문 보존이
|
|
193
|
-
// 앞선다 — 본문 게이트(테스트)가 이 리포에만 있기 때문이다. tier official = 자사
|
|
194
|
-
// (star 무관 — trust-tier-drift 가 official 을 건너뛴다). condition 은 이관 전 도달 범위 유지.
|
|
195
|
-
{
|
|
196
|
-
// #485 — 전신 `clear-korean-communication` 개명. 이름이 "한국어 전용"으로 읽혀 영어 사용자에게
|
|
197
|
-
// 닿지 않았다(사용자 결정 2026-09-20). 본문은 사용자가 검토한 개정안, 답변 언어는 사용자의 언어.
|
|
198
|
-
id: "user-centered-explanation",
|
|
199
|
-
tier: "official",
|
|
200
|
-
// uzys 자사 스킬 리포
|
|
201
|
-
description: "User-centered explanation \u2014 explain problems, changes, and choices as what the user does and sees (impact and cause first, then evidence), in the user's language; covers decisions, approval requests, and AS-IS/TO-BE contrasts of screens and flows",
|
|
202
|
-
category: "workflow",
|
|
203
|
-
source: "uzys",
|
|
204
|
-
condition: { kind: "has-dev-track" },
|
|
205
|
-
method: { kind: "internal", key: "user-centered-explanation" }
|
|
206
|
-
},
|
|
207
|
-
{
|
|
208
|
-
id: "north-star",
|
|
209
|
-
tier: "official",
|
|
210
|
-
// uzys 자사 스킬 리포
|
|
211
|
-
description: "North Star \u2014 create or safely update the project direction baseline and its revision-linked roadmap, then analyze the impact on audits, verification criteria, issues, and work in progress",
|
|
212
|
-
category: "workflow",
|
|
213
|
-
source: "uzys",
|
|
214
|
-
// 전 트랙 상주 보존 — 이관 전에는 manifest COMMON_SKILL_DIRS(조건 없는 전 트랙 설치)였다.
|
|
215
|
-
// 이 카탈로그에 "always" kind 가 없어 전 트랙 나열로 같은 도달 범위를 표현한다.
|
|
216
|
-
condition: { kind: "any-track", tracks: [...TRACKS] },
|
|
217
|
-
method: { kind: "internal", key: "north-star" }
|
|
218
|
-
},
|
|
219
|
-
{
|
|
220
|
-
id: "audit-service-gaps",
|
|
221
|
-
tier: "official",
|
|
222
|
-
// uzys 자사 스킬 리포
|
|
223
|
-
description: "Audit service gaps \u2014 audit observable gaps between the service's current state and an explicit North Star baseline (reverse \xB7 defect \xB7 experience \xB7 benchmark \xB7 verify \xB7 change-impact \xB7 drift modes)",
|
|
224
|
-
category: "dev-tools",
|
|
225
|
-
source: "uzys",
|
|
226
|
-
condition: { kind: "has-dev-track" },
|
|
227
|
-
method: { kind: "internal", key: "audit-service-gaps" }
|
|
228
|
-
},
|
|
229
|
-
{
|
|
230
|
-
id: "multi-persona-review",
|
|
231
|
-
tier: "official",
|
|
232
|
-
// uzys 자사 스킬 리포
|
|
233
|
-
description: "Multi-persona review \u2014 review one artifact through independent stakeholder and failure lenses, then synthesize evidence-backed, severity-ranked findings without discarding minority views",
|
|
234
|
-
category: "dev-tools",
|
|
235
|
-
source: "uzys",
|
|
236
|
-
condition: { kind: "has-dev-track" },
|
|
237
|
-
method: { kind: "internal", key: "multi-persona-review" }
|
|
238
|
-
},
|
|
239
|
-
{
|
|
240
|
-
id: "recurrence-prevention",
|
|
241
|
-
tier: "official",
|
|
242
|
-
// uzys 자사 스킬 리포
|
|
243
|
-
description: "Recurrence prevention \u2014 reconstruct a repeated defect's failure signature and timeline, then pick the least costly countermeasure (code \xB7 regression test \xB7 rule \xB7 hook \xB7 derivation \xB7 gate)",
|
|
244
|
-
category: "workflow",
|
|
245
|
-
source: "uzys",
|
|
246
|
-
condition: { kind: "has-dev-track" },
|
|
247
|
-
method: { kind: "internal", key: "recurrence-prevention" }
|
|
248
|
-
},
|
|
249
|
-
{
|
|
250
|
-
id: "gh-issue-workflow",
|
|
251
|
-
tier: "official",
|
|
252
|
-
// uzys 자사 스킬 리포
|
|
253
|
-
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",
|
|
254
|
-
category: "workflow",
|
|
255
|
-
source: "uzys",
|
|
256
|
-
// 전 트랙 상주 보존 — north-star 와 같은 이유 (이관 전 COMMON_SKILL_DIRS).
|
|
257
|
-
condition: { kind: "any-track", tracks: [...TRACKS] },
|
|
258
|
-
method: { kind: "internal", key: "gh-issue-workflow" }
|
|
259
|
-
},
|
|
260
|
-
{
|
|
261
|
-
id: "model-orchestration",
|
|
262
|
-
tier: "official",
|
|
263
|
-
// uzys 자사 스킬 리포
|
|
264
|
-
description: "Model orchestration \u2014 decompose work by capability and route independent, bounded tasks to available agents while preserving ownership, permissions, and verification. Model names are Claude Code vocabulary; on other CLIs the same split maps onto that vendor's tiers",
|
|
265
|
-
category: "workflow",
|
|
266
|
-
source: "uzys",
|
|
267
|
-
// ADR-105 (#726) — opt-in → 개발 트랙 기본.
|
|
268
|
-
condition: { kind: "has-dev-track" },
|
|
269
|
-
method: { kind: "internal", key: "model-orchestration" }
|
|
270
|
-
},
|
|
271
|
-
{
|
|
272
|
-
// 전신 = gemini-consult + codex-consult. provider 중립 한 스킬로 통합됐다(통합은 유지 —
|
|
273
|
-
// 되돌린 것은 배포 경로이지 통합이 아니다).
|
|
274
|
-
id: "external-model-consult",
|
|
275
|
-
tier: "official",
|
|
276
|
-
// uzys 자사 스킬 리포
|
|
277
|
-
description: "External model consult \u2014 ask an available external model/provider for an independent draft, critique, or provider-only output such as image generation (requires that provider's CLI)",
|
|
278
|
-
category: "dev-tools",
|
|
279
|
-
source: "uzys",
|
|
280
|
-
// ADR-105 (#726) — opt-in → 개발 트랙 기본. 설치는 디렉터리 복사뿐이라 provider CLI 가 없어도
|
|
281
|
-
// 설치는 성공한다 — 없으면 실행 시점에 래퍼가 멈추고 스킬이 "설치·로그인은 사용자 몫"을 알린다.
|
|
282
|
-
condition: { kind: "has-dev-track" },
|
|
283
|
-
method: { kind: "internal", key: "external-model-consult" }
|
|
284
|
-
},
|
|
285
|
-
{
|
|
286
|
-
// #729 (ADR-109) — 토론 정책(참여자 · 독립성 · 라운드 · 예산 · 재시도 · 대체 · 복귀 · 통합)은 이 스킬만
|
|
287
|
-
// 소유한다. 하위 경로(external-model-consult · 브라우저 위임 3종)는 실행 매개변수와 결과·상태만 맡는다.
|
|
288
|
-
// has-dev-track 인 이유: 브라우저 전제가 없다 — external-model-consult 의 CLI 둘만으로 "서로 다른 실제 모델 2개"가
|
|
289
|
-
// 성립하고, 없는 경로는 실행 시점에 unavailable 로 기록한다. DEV_METHOD 에 넣지 않는다(수단 — ADR-105 D2).
|
|
290
|
-
id: "multi-model-debate",
|
|
291
|
-
tier: "official",
|
|
292
|
-
// uzys 자사 스킬
|
|
293
|
-
description: "Multi-model debate \u2014 two or three different real models answer independently, then only decision-relevant disagreements are cross-examined and checked; uses whichever installed routes are available (external-model-consult CLIs or the browser delegates)",
|
|
294
|
-
category: "dev-tools",
|
|
295
|
-
source: "uzys",
|
|
296
|
-
condition: { kind: "has-dev-track" },
|
|
297
|
-
method: { kind: "internal", key: "multi-model-debate" }
|
|
298
|
-
},
|
|
299
|
-
// #729 (ADR-109, 사용자 결정 2026-10-12) — 브라우저 위임 3종은 opt-in. 실행 전제(브라우저 도구 · 로그인된 브라우저 ·
|
|
300
|
-
// 그 서비스 계정)가 하네스 밖에 있고 Codex CLI 에는 브라우저가 없다(공식 문서). #489 가 agent-browser 를 opt-in 으로
|
|
301
|
-
// 내린 축과 같다. 설치는 디렉터리 복사뿐이다 — `--with <id>` · 위저드 체크로 켠다.
|
|
302
|
-
{
|
|
303
|
-
id: "gemini-delegate",
|
|
304
|
-
tier: "official",
|
|
305
|
-
// uzys 자사 스킬
|
|
306
|
-
description: "Gemini delegate \u2014 hand design, review, or Korean polishing to Gemini on the web and collect the answer (needs a browser tool + your logged-in browser)",
|
|
307
|
-
category: "dev-tools",
|
|
308
|
-
source: "uzys",
|
|
309
|
-
condition: { kind: "opt-in" },
|
|
310
|
-
method: { kind: "internal", key: "gemini-delegate" }
|
|
311
|
-
},
|
|
312
|
-
{
|
|
313
|
-
id: "chatgpt-delegate",
|
|
314
|
-
tier: "official",
|
|
315
|
-
// uzys 자사 스킬
|
|
316
|
-
description: "ChatGPT delegate \u2014 hand design, planning, or review to ChatGPT on the web and collect the answer (needs a browser tool + your logged-in browser)",
|
|
317
|
-
category: "dev-tools",
|
|
318
|
-
source: "uzys",
|
|
319
|
-
condition: { kind: "opt-in" },
|
|
320
|
-
method: { kind: "internal", key: "chatgpt-delegate" }
|
|
321
|
-
},
|
|
322
|
-
{
|
|
323
|
-
id: "genspark-delegate",
|
|
324
|
-
tier: "official",
|
|
325
|
-
// uzys 자사 스킬
|
|
326
|
-
description: "Genspark delegate \u2014 hand design or planning to Genspark AI Chat, or generate and download images with AI Image (needs a browser tool + your logged-in browser)",
|
|
327
|
-
category: "dev-tools",
|
|
328
|
-
source: "uzys",
|
|
329
|
-
condition: { kind: "opt-in" },
|
|
330
|
-
method: { kind: "internal", key: "genspark-delegate" }
|
|
331
|
-
},
|
|
332
|
-
{
|
|
333
|
-
// 복원 9종이 아니라 신설이다 — 이관 이력이 없다.
|
|
334
|
-
// 전 트랙인 이유: 위임과 요청 정규화는 개발 트랙의 행위가 아니라 **전 트랙의 행위**다.
|
|
335
|
-
// executive 트랙도 서브에이전트에 일을 넘기고, 그때 브리프가 없으면 완료 판정 기준 없이 넘긴다.
|
|
336
|
-
// ADR-088 (#426 F-04) — 전신 `task-brief` 개명. 문턱이 **위임 · 설계 · 다단계(피처/프로젝트
|
|
337
|
-
// 규모 이상)** 로 좁혀졌고, 한 줄 요청마다 브리프를 권하던 매 프롬프트 넛지 훅과 상시 스킬
|
|
338
|
-
// 안내(`CONTINUOUS_SKILLS`)가 함께 빠졌다. 이름이 그 문턱을 들고 있다.
|
|
339
|
-
id: "objective-brief",
|
|
340
|
-
tier: "official",
|
|
341
|
-
// uzys 자사 스킬
|
|
342
|
-
description: "Objective brief \u2014 normalize a delegation, a design task, or multi-step work at feature scale into the canonical brief (objective \xB7 inputs \xB7 invariants \xB7 success criteria \xB7 boundaries \xB7 autonomy \xB7 verification) and write every delegation prompt in that same shape",
|
|
343
|
-
category: "workflow",
|
|
344
|
-
source: "uzys",
|
|
345
|
-
condition: { kind: "any-track", tracks: [...TRACKS] },
|
|
346
|
-
method: { kind: "internal", key: "objective-brief" }
|
|
347
|
-
},
|
|
348
|
-
{
|
|
349
|
-
// objective-brief 와 같은 신설이다 — 이관 이력이 없다.
|
|
350
|
-
// 전 트랙인 이유: 이 하네스는 **모든 트랙에** 앵커·룰·훅을 깐다. 그 상주층이 밥값을 하는지
|
|
351
|
-
// 되묻는 루프만 개발 트랙에 두면, 상주 비용은 전원이 무는데 감사는 일부만 갖는 비대칭이
|
|
352
|
-
// 된다. 감사 대상이 개발 산출물이 아니라 **설치본 자신**이라 트랙 술어와 무관하다.
|
|
353
|
-
// ADR-084 (#425) — 2판. 판정 근거가 공식 체크리스트 인용에서 **확정된 의도 + 리포 실증**으로
|
|
354
|
-
// 바뀌었고, 모드 4개(audit · verification · apply · populate)를 SKILL.md 가 references 로
|
|
355
|
-
// 라우팅한다. 비-Claude CLI 도 디렉터리째 받는 것이 전제다(#431).
|
|
356
|
-
id: "audit-harness-fit",
|
|
357
|
-
tier: "official",
|
|
358
|
-
// uzys 자사 스킬
|
|
359
|
-
description: "Audit harness fit \u2014 improve agent instructions and skills for autonomous, productive delivery with verifiable quality: resolve needless questions and repeated checks, conflicting decisions, missing actionable context, and low-value procedures; adapt guidance to demonstrated model and tool capabilities; also fill or refresh AGENTS.md / CLAUDE.md project context from repository evidence",
|
|
360
|
-
category: "workflow",
|
|
361
|
-
source: "uzys",
|
|
362
|
-
condition: { kind: "any-track", tracks: [...TRACKS] },
|
|
363
|
-
method: { kind: "internal", key: "audit-harness-fit" }
|
|
364
|
-
},
|
|
365
|
-
{
|
|
366
|
-
// CI 가 멈췄을 때(결제 실패·쿼터 소진·Actions 장애) 검증을 포기하지도, 위조하지도 않는 절차.
|
|
367
|
-
// has-dev-track 인 이유: 이건 **누가 요청해서 켜는 것이 아니라 사고가 났을 때 켜지는** 것이다.
|
|
368
|
-
// CI 가 죽고 나서야 필요해지는 자산을 opt-in 으로 두면, 정작 그 순간에 없다. 대신 executive
|
|
369
|
-
// 트랙에는 깔지 않는다 — 거기엔 워크플로가 없다.
|
|
370
|
-
// DEV_METHOD_SKILL_IDS 에 넣지 않는 이유: 저건 wizard 의 "하네스 방법론 N종" 묶음이고,
|
|
371
|
-
// 이건 방법론이 아니라 사고 대응 런북이다. 묶음에 끼면 묶음의 뜻이 흐려진다.
|
|
372
|
-
id: "self-hosted-github-runner",
|
|
373
|
-
tier: "official",
|
|
374
|
-
// uzys 자사 스킬
|
|
375
|
-
description: "Self-hosted GitHub runner \u2014 when hosted runners stop (billing failure, spending limit, minutes exhausted, org quota, Actions outage), run the repo's existing workflow files unchanged on a Docker self-hosted runner instead of copying CI steps into a local script; covers runner-label switching, architecture and cache-key traps, container isolation, the risk that pull_request jobs then execute on your own machine, and the rollback path",
|
|
376
|
-
category: "dev-tools",
|
|
377
|
-
source: "uzys",
|
|
378
|
-
condition: { kind: "has-dev-track" },
|
|
379
|
-
method: { kind: "internal", key: "self-hosted-github-runner" }
|
|
380
|
-
},
|
|
381
|
-
{
|
|
382
|
-
// 트랙과 무관한 글쓰기 규율이다. ADR-105 (#726, 사용자 결정 2026-10-11) — opt-in → 개발 트랙
|
|
383
|
-
// 기본. 한국어로 쓰지 않는 설치자에게는 descriptor 상주가 낭비라는 대가를 받아들였다
|
|
384
|
-
// (빼려면 `--without natural-korean`).
|
|
385
|
-
// user-centered-explanation 과의 경계: 저쪽은 **무엇을 어떤 형식으로 말할지**(설명·승인
|
|
386
|
-
// 요청의 구조)이고, 이쪽은 **한국어로 쓰는 행위 자체**(답변·글쓰기·번역·퇴고)의 표현이다.
|
|
387
|
-
// #428 — 전신 `humanize-korean`(#355, 진단-우선 퇴고)을 개명·교체했다. 범위가 퇴고에서
|
|
388
|
-
// 쓰기·번역까지로 넓어져 이름이 뜻과 어긋났다. 옛 id 로 깐 설치본은 `update` 가 손대지
|
|
389
|
-
// 않고(설치된 디렉터리만 갱신) 그대로 남는다 — 새 판은 `--with natural-korean` 으로 받는다.
|
|
390
|
-
id: "natural-korean",
|
|
391
|
-
tier: "official",
|
|
392
|
-
// uzys 자사 스킬
|
|
393
|
-
description: "Natural Korean \u2014 write, answer, translate, and revise in Korean that reads as Korean: keep meaning and register, cut translationese, needless English, stock phrases, and mechanical repetition; fix only what is actually awkward",
|
|
394
|
-
category: "workflow",
|
|
395
|
-
source: "uzys",
|
|
396
|
-
condition: { kind: "has-dev-track" },
|
|
397
|
-
method: { kind: "internal", key: "natural-korean" }
|
|
398
|
-
},
|
|
399
|
-
// === Option-gated (v26.42.0 — opt-in, BREAKING vs prior has-dev-track auto-install) ===
|
|
400
|
-
{
|
|
401
|
-
// v26.75.0 (ADR-021) — `npm i --save-dev @fission-ai/openspec` 후 `openspec init` 로 슬래시 주입.
|
|
402
|
-
id: "openspec",
|
|
403
|
-
tier: "vetted",
|
|
404
|
-
// Fission-AI/OpenSpec 53k
|
|
405
|
-
description: "OpenSpec \u2014 spec-driven change workflow for an existing codebase (propose \u2192 apply \u2192 archive). Use when your team has adopted a shared spec-and-delta process; it is a collaboration contract, not a coding aid",
|
|
406
|
-
category: "workflow",
|
|
407
|
-
source: "fission-ai",
|
|
408
|
-
condition: { kind: "opt-in" },
|
|
409
|
-
method: { kind: "npm", pkg: "@fission-ai/openspec", version: "1.4.1" }
|
|
410
|
-
},
|
|
411
|
-
{
|
|
412
|
-
// v26.75.0 (ADR-021) — 비대화형 install. v26.75.1: `--directory .` 누락 시 "Installation
|
|
413
|
-
// directory" 프롬프트에서 hang (Docker realcli 검출). cwd(=project) 기준 `.` 지정으로 봉합.
|
|
414
|
-
id: "bmad-method",
|
|
415
|
-
tier: "vetted",
|
|
416
|
-
// bmad-code-org/BMAD-METHOD 48k
|
|
417
|
-
description: "BMAD-METHOD \u2014 multi-agent agile workflow (PM / Architect / Dev roles). Use for team-scale delivery with fixed roles; the pinned 6.9.0 still runs the full ceremony, so skip it for solo or small changes",
|
|
418
|
-
category: "workflow",
|
|
419
|
-
source: "bmad-code-org",
|
|
420
|
-
condition: { kind: "opt-in" },
|
|
421
|
-
// v26.102.0 (ADR-031) — args 의 `--tools claude-code` 하드코딩이 claude 전용 산출물
|
|
422
|
-
// (.claude/ agent commands)을 생성 (Docker 실검증 realcli-workflows-2026-06-06).
|
|
423
|
-
// kind 기본값(npx-run=전 CLI)이 이 자산에선 거짓 → override. 대칭 실현(--tools derive)은 M4+.
|
|
424
|
-
cliSupportOverride: ["claude"],
|
|
425
|
-
method: {
|
|
426
|
-
kind: "npx-run",
|
|
427
|
-
cmd: "bmad-method",
|
|
428
|
-
version: "6.9.0",
|
|
429
|
-
args: ["install", "--directory", ".", "--tools", "claude-code", "--yes"]
|
|
430
|
-
}
|
|
431
|
-
},
|
|
432
|
-
// === Railway (csr-fastify|csr-fastapi|ssr-*|full) ===
|
|
433
|
-
// v0.6.3 — railway-plugin entry 제거. railwayapp/railway-plugin repo 자체 존재 안 함
|
|
434
|
-
// (404 Not Found). 공식 docs (https://docs.railway.com/ai/claude-code-plugin) 형식은
|
|
435
|
-
// marketplace add `railwayapp/railway-skills` + plugin install `railway@railway-skills`만.
|
|
436
|
-
// → 아래 railway-skills entry로 단일화.
|
|
437
|
-
{
|
|
438
|
-
id: "railway-skills",
|
|
439
|
-
tier: "experimental",
|
|
440
|
-
// railwayapp/railway-skills 268
|
|
441
|
-
description: "Railway skills \u2014 deploy and manage projects, services, and environments on Railway. Only for projects hosted on Railway; it drives a real account",
|
|
442
|
-
category: "backend",
|
|
443
|
-
source: "railwayapp",
|
|
444
|
-
// 2026-08-02 사용자 결정: 트랙 기본 → opt-in (ADR-063)
|
|
445
|
-
condition: { kind: "opt-in" },
|
|
446
|
-
method: {
|
|
447
|
-
kind: "plugin",
|
|
448
|
-
marketplace: "railwayapp/railway-skills",
|
|
449
|
-
pluginId: "railway@railway-skills"
|
|
450
|
-
}
|
|
451
|
-
},
|
|
452
|
-
// #709 (ADR-101) — Railway MCP 서버의 **선택** 경로. 서버 정의는 트랙 표(`templates/track-mcp-map.tsv`)의 같은 이름
|
|
453
|
-
// 행 하나다(key === 행 이름). 그 행의 패턴이 기본 트랙(csr-* · ssr-htmx · ssr-htmx-fastify · full)을 정하고, 이 항목은 그 밖의 트랙
|
|
454
|
-
// (ssr-nextjs 등)에서 위저드 체크 · `--with` 로 고르게 한다. experimental 이라 조건으로는 미리 체크되지 않는다.
|
|
455
|
-
// #712 (ADR-106) — 행이 Railway 공식 원격 서버(`https://mcp.railway.com`, OAuth)로 바뀌었다. 이름 · 키 id 는 그대로라 기록이
|
|
456
|
-
// 이어져 update 가 같은 키를 갈아 끼운다.
|
|
457
|
-
{
|
|
458
|
-
id: "railway-mcp-server",
|
|
459
|
-
tier: "experimental",
|
|
460
|
-
// railwayapp/cli 621★ (원격 서버에 공개 저장소가 따로 없다 — 같은 벤더의 CLI 를 감시, REPO_OVERRIDE)
|
|
461
|
-
description: "Railway's official remote MCP (https://mcp.railway.com, signs in with OAuth on first use) in .mcp.json, Codex and OpenCode \u2014 default on csr-* \xB7 ssr-htmx \xB7 ssr-htmx-fastify \xB7 full; check it to add it on any other track",
|
|
462
|
-
category: "backend",
|
|
463
|
-
source: "railwayapp",
|
|
464
|
-
condition: { kind: "opt-in" },
|
|
465
|
-
// #709 리뷰 NOTE 3 — `.mcp.json` · Codex `[mcp_servers]` · `opencode.json` `mcp` 만 받는다. Antigravity 변환에는 MCP 가 없다
|
|
466
|
-
// (internal 기본값 = 4 CLI 는 과대 표기).
|
|
467
|
-
cliSupportOverride: ["claude", "codex", "opencode"],
|
|
468
|
-
method: { kind: "internal", key: "railway-mcp-server" }
|
|
469
|
-
},
|
|
470
|
-
// #711 (ADR-106) — Vercel 공식 원격 MCP. 트랙 표 행의 패턴은 `-`(기본 트랙 없음) — 어느 트랙에서도 미리 체크하지 않는다
|
|
471
|
-
// (사용자 확정 2026-10-11 "opt-in", ssr-nextjs 포함). Antigravity 는 MCP 를 받지 않는다(Vercel 승인 클라이언트에도 없다).
|
|
472
|
-
{
|
|
473
|
-
id: "vercel",
|
|
474
|
-
tier: "vetted",
|
|
475
|
-
// Vercel MCP 에 공개 저장소가 따로 없다 — 같은 벤더의 vercel/vercel 16k 를 감시(REPO_OVERRIDE)
|
|
476
|
-
description: "Vercel's official remote MCP (https://mcp.vercel.com, signs in with OAuth on first use) in .mcp.json, Codex and OpenCode. Only if this project deploys to Vercel",
|
|
477
|
-
category: "backend",
|
|
478
|
-
source: "vercel",
|
|
479
|
-
condition: { kind: "opt-in" },
|
|
480
|
-
cliSupportOverride: ["claude", "codex", "opencode"],
|
|
481
|
-
method: { kind: "internal", key: "vercel" }
|
|
482
|
-
},
|
|
483
|
-
// #722 (ADR-106) — GitHub 공식 원격 MCP 의 opt-in 경로. 기본에서 뺀 옛 `github`(@modelcontextprotocol/server-github, 상류
|
|
484
|
-
// deprecated)와 **다른 이름**이다 — 같은 이름이면 옛 설치본의 기록된 몫이 선택으로 읽혀 PAT 없는 설치자에게 매 세션 실패하는
|
|
485
|
-
// 서버가 조용히 깔린다. 인증 = 환경변수 GITHUB_PAT(파일에는 이름만).
|
|
486
|
-
{
|
|
487
|
-
id: "github-mcp",
|
|
488
|
-
tier: "vetted",
|
|
489
|
-
// github/github-mcp-server 33.5k (REPO_OVERRIDE)
|
|
490
|
-
description: "GitHub's official remote MCP server (api.githubcopilot.com) in .mcp.json, Codex and OpenCode \u2014 needs a GitHub personal access token in GITHUB_PAT in the shell you start the CLI from. Not needed when the agent uses the gh CLI",
|
|
491
|
-
category: "dev-tools",
|
|
492
|
-
source: "github",
|
|
493
|
-
condition: { kind: "opt-in" },
|
|
494
|
-
cliSupportOverride: ["claude", "codex", "opencode"],
|
|
495
|
-
method: { kind: "internal", key: "github-mcp" }
|
|
496
|
-
},
|
|
497
|
-
// #722 (ADR-106) — Chrome DevTools MCP. 트랙 표 행이 웹 트랙(csr-* · ssr-* · full)의 기본이고, 이 항목은 그 밖의 트랙에서
|
|
498
|
-
// 고르게 한다. Google 사용 통계는 행의 `--no-usage-statistics` 로 끈다.
|
|
499
|
-
{
|
|
500
|
-
id: "chrome-devtools",
|
|
501
|
-
tier: "vetted",
|
|
502
|
-
// ChromeDevTools/chrome-devtools-mcp 53k (REPO_OVERRIDE)
|
|
503
|
-
description: "Chrome DevTools MCP \u2014 browser automation, console, network and performance tools for the agent. Default on csr-* \xB7 ssr-* \xB7 full; check it to add it on any other track. Google's usage statistics are turned off (--no-usage-statistics)",
|
|
504
|
-
category: "understanding",
|
|
505
|
-
source: "ChromeDevTools",
|
|
506
|
-
condition: { kind: "opt-in" },
|
|
507
|
-
cliSupportOverride: ["claude", "codex", "opencode"],
|
|
508
|
-
method: { kind: "internal", key: "chrome-devtools" }
|
|
509
|
-
},
|
|
510
|
-
// #751 — Context7 MCP. 트랙 표 행의 패턴 `*` 가 모든 트랙의 기본이라 위저드 3단계에 늘 체크된 채 보이고, 풀거나
|
|
511
|
-
// `--without context7` 이면 `.mcp.json` · Codex · OpenCode 세 자리에서 빠진다(#709 · #722 와 같은 경로). 기본값은 그대로다.
|
|
512
|
-
{
|
|
513
|
-
id: "context7",
|
|
514
|
-
tier: "vetted",
|
|
515
|
-
// upstash/context7 62.9k (REPO_OVERRIDE)
|
|
516
|
-
description: "Context7 MCP \u2014 current, version-specific library docs for the agent, in .mcp.json, Codex and OpenCode. Default on every track; uncheck it to leave it out",
|
|
517
|
-
category: "understanding",
|
|
518
|
-
source: "upstash",
|
|
519
|
-
condition: { kind: "opt-in" },
|
|
520
|
-
cliSupportOverride: ["claude", "codex", "opencode"],
|
|
521
|
-
method: { kind: "internal", key: "context7" }
|
|
522
|
-
},
|
|
523
|
-
// #751 — Supabase MCP. 트랙 표 행이 csr-supabase · full 의 기본이고, 이 항목은 그 밖의 트랙에서 고르게 · 기본 트랙에서
|
|
524
|
-
// 빼게 한다. 서버 정의(명령 · 인자)는 트랙 표 한 곳이다.
|
|
525
|
-
{
|
|
526
|
-
id: "supabase",
|
|
527
|
-
tier: "vetted",
|
|
528
|
-
// supabase/mcp 2.9k (REPO_OVERRIDE)
|
|
529
|
-
description: "Supabase's official remote MCP (https://mcp.supabase.com/mcp, signs in with OAuth on first use) in .mcp.json, Codex and OpenCode. Default on csr-supabase \xB7 full; check it to add it on any other track. Only if this project uses Supabase",
|
|
530
|
-
category: "backend",
|
|
531
|
-
source: "supabase",
|
|
532
|
-
condition: { kind: "opt-in" },
|
|
533
|
-
cliSupportOverride: ["claude", "codex", "opencode"],
|
|
534
|
-
method: { kind: "internal", key: "supabase" }
|
|
535
|
-
},
|
|
536
|
-
// === csr-supabase|full CLI ===
|
|
537
|
-
{
|
|
538
|
-
id: "vercel-cli",
|
|
539
|
-
tier: "vetted",
|
|
540
|
-
// vercel/vercel 16k
|
|
541
|
-
description: "Vercel CLI \u2014 the real deploy tool as a devDependency (global under --scope global). Only if this project deploys to Vercel. Non-interactive `vercel link`/deploy needs `--scope` when your account has several teams (55.0.0+)",
|
|
542
|
-
category: "backend",
|
|
543
|
-
source: "vercel",
|
|
544
|
-
// 2026-08-02 사용자 결정: 트랙 기본 → opt-in (ADR-063). #709 가 ssr-nextjs 기본으로 바꿨다가 #715 에서 되돌렸다 —
|
|
545
|
-
// 설치자 package.json 에 의존성을 더하고(에이전트는 npx 로 쓸 수 있다) login 없이는 쓸 것이 없으며 핀이 낡는다.
|
|
546
|
-
// Vercel 공식 MCP 도 opt-in 이다(`vercel` 항목, #711 · ADR-106 — 사용자 확정 2026-10-11).
|
|
547
|
-
condition: { kind: "opt-in" },
|
|
548
|
-
method: { kind: "npm", pkg: "vercel", version: "63.1.2" }
|
|
549
|
-
},
|
|
550
|
-
{
|
|
551
|
-
// v26.106.0 (ADR-035, 사용자 승인 B): 배포 CLI 2종 동시 기본의 중복 해소 — npm dl 실측 10.11:1
|
|
552
|
-
// (vercel 2.79M vs netlify 276k /주, 2026-07-18) → vercel 만 기본, netlify 는 opt-in.
|
|
553
|
-
id: "netlify-cli",
|
|
554
|
-
tier: "vetted",
|
|
555
|
-
// netlify/cli 1.9k
|
|
556
|
-
description: "Netlify CLI \u2014 the real deploy tool as a devDependency (global under --scope global). Only if this project deploys to Netlify",
|
|
557
|
-
category: "backend",
|
|
558
|
-
source: "netlify",
|
|
559
|
-
condition: { kind: "opt-in" },
|
|
560
|
-
method: { kind: "npm", pkg: "netlify-cli", version: "26.1.0" }
|
|
561
|
-
},
|
|
562
|
-
{
|
|
563
|
-
id: "supabase-cli",
|
|
564
|
-
tier: "vetted",
|
|
565
|
-
// supabase 103k
|
|
566
|
-
description: "Supabase CLI \u2014 the real Supabase tool as a devDependency (global under --scope global); first `supabase login` opens an OAuth browser flow. Only if this project uses Supabase",
|
|
567
|
-
category: "backend",
|
|
568
|
-
source: "supabase",
|
|
569
|
-
// 2026-08-02 사용자 결정: 트랙 기본 → opt-in (ADR-063)
|
|
570
|
-
condition: { kind: "opt-in" },
|
|
571
|
-
method: { kind: "npm", pkg: "supabase", version: "2.108.0" }
|
|
572
|
-
},
|
|
573
|
-
// === UI tracks (csr-*|ssr-*|full) ===
|
|
574
|
-
// v26.92.0 — frontend-design (Anthropic official).
|
|
575
|
-
// 사용자 결정: has-dev-track 기본추천 (모든 개발 트랙, executive 제외).
|
|
576
|
-
// #709 (ADR-101) — UI 가 있는 트랙만(`UI_TRACKS`). data · tooling 은 미리 체크하지 않는다.
|
|
577
|
-
// category=frontend (UI 자산, wizard 그룹).
|
|
578
|
-
//
|
|
579
|
-
// 2026-08-26 (#344) — **plugin → skill.** 배달 방식이 도달 CLI 를 정한다(assetCliSupport):
|
|
580
|
-
// `plugin` 은 `claude plugin install` 을 spawn 하므로 **구조적으로 Claude Code 전용**이라,
|
|
581
|
-
// Codex·OpenCode·Antigravity 를 고른 사용자는 기본 추천 자산인데도 한 번도 받지 못했다
|
|
582
|
-
// (사용자 적발). 같은 스킬이 `anthropics/skills` 에 원본으로 있고 `npx skills add` 는
|
|
583
|
-
// `--agent` 로 네 CLI 전부에 깐다 — 실재 확인 2026-08-26: `skills/frontend-design/SKILL.md`.
|
|
584
|
-
// tier 는 official 그대로(저자 동일). marketplace 경유가 아니게 되어 upstream 판본을 직접 쓴다.
|
|
585
|
-
{
|
|
586
|
-
id: "frontend-design",
|
|
587
|
-
tier: "official",
|
|
588
|
-
// anthropics/skills (Anthropic 저자, ★171.5K — 2026-08-26 gh api 실측)
|
|
589
|
-
description: "frontend-design \u2014 visual direction, typography, and anti-generic UI guidance (Anthropic official). For new UI or an explicit redesign; do not let it override an existing design system",
|
|
590
|
-
category: "frontend",
|
|
591
|
-
source: "anthropics",
|
|
592
|
-
condition: { kind: "any-track", tracks: UI_TRACKS },
|
|
593
|
-
method: { kind: "skill", source: "anthropics/skills", skill: "frontend-design" }
|
|
594
|
-
},
|
|
595
|
-
// 2026-08-02 정비 — 프론트엔드 품질 3종 (사용자 지시). 전부 opt-in: frontend-design 이 기본
|
|
596
|
-
// 생성기이고 이 셋은 그 위의 취향·디테일 층이라 상시 비용을 물릴 근거가 없다 (ADR-032/035
|
|
597
|
-
// 와 같은 축). star 는 각 tier 라인 주석 = 2026-08-02 `gh api` 실측.
|
|
598
|
-
{
|
|
599
|
-
id: "jakubkrehel-skills",
|
|
600
|
-
tier: "vetted",
|
|
601
|
-
// jakubkrehel/skills 2,602 (2026-08-02)
|
|
602
|
-
description: "Better-* interface suite \u2014 one skill per concern: typography, OKLCH color, accessibility, layout, UX writing, component states. Add when you need a specific UI review, not as a default",
|
|
603
|
-
category: "frontend",
|
|
604
|
-
source: "jakubkrehel",
|
|
605
|
-
condition: { kind: "opt-in" },
|
|
606
|
-
// `--skill` 미지정 = 리포의 7 스킬 전부 설치. 다른 multi-skill 출처(K-Dense·softaworks)는
|
|
607
|
-
// 한 자산 = 한 스킬이라 `--skill` 을 썼지만, 여기 광고 문구가 "7 skills" 세트다 —
|
|
608
|
-
// 한 개만 깔면 description 이 곧 거짓이 된다. 실호출 형태는 Docker 실설치(AC8)로 확인한다.
|
|
609
|
-
method: { kind: "skill", source: "jakubkrehel/skills" }
|
|
610
|
-
},
|
|
611
|
-
{
|
|
612
|
-
id: "taste-skill",
|
|
613
|
-
tier: "vetted",
|
|
614
|
-
// Leonxlnx/taste-skill 70,078 (2026-08-02)
|
|
615
|
-
description: "taste-skill \u2014 tunes the look of landing pages, portfolios, and redesigns (variance \xB7 motion \xB7 density). Not for dashboards or multi-step product UI",
|
|
616
|
-
category: "frontend",
|
|
617
|
-
source: "Leonxlnx",
|
|
618
|
-
condition: { kind: "opt-in" },
|
|
619
|
-
// 리포에 스킬 14종(brandkit·brutalist·minimalist 등)이 있고 본체가 `taste-skill` —
|
|
620
|
-
// 스타일 변형은 그 안에서 고른다. 다른 단일 대표 자산(remotion)과 같은 형태.
|
|
621
|
-
// 2026-09-20 (#420) — 업스트림이 SKILL.md 의 `name:` 을 `design-taste-frontend` 로 바꿨다. `npx skills
|
|
622
|
-
// add --skill` 은 디렉터리명이 아니라 **frontmatter name** 으로 맞춘다(실측: 옛 이름은 "No matching
|
|
623
|
-
// skills found", 새 이름은 설치 완료). 디렉터리는 여전히 `skills/taste-skill/` 이다.
|
|
624
|
-
method: { kind: "skill", source: "Leonxlnx/taste-skill", skill: "design-taste-frontend" }
|
|
625
|
-
},
|
|
626
|
-
{
|
|
627
|
-
id: "scroll-world",
|
|
628
|
-
tier: "vetted",
|
|
629
|
-
// oso95/scroll-world 6,863 (2026-08-02)
|
|
630
|
-
description: "scroll-world \u2014 scroll-driven 3D landing pages with generated media assets. A special production pipeline that needs external image/video services; only when that effect is requested",
|
|
631
|
-
category: "frontend",
|
|
632
|
-
source: "oso95",
|
|
633
|
-
condition: { kind: "opt-in" },
|
|
634
|
-
method: { kind: "skill", source: "oso95/scroll-world", skill: "scroll-world" }
|
|
635
|
-
},
|
|
636
|
-
// 2026-08-16 — Preline 은 Tailwind 컴포넌트 킷이지만 **프레임워크에 매이지 않는다**: htmx·
|
|
637
|
-
// vanilla 에도 붙는다(사용자 확인). 그래서 `frontend` 이면서 React/Vue 트랙 전용이 아니다.
|
|
638
|
-
//
|
|
639
|
-
// 리포 안에 테마 스킬은 `skills/theme-generator/` 하나다. 2026-08-16 실측에서는 `npx skills add`
|
|
640
|
-
// 가 디렉터리명(`theme-generator`)으로 맞췄는데, 2026-09-01 부터 그 이름이 "No matching skills
|
|
641
|
-
// found" 가 됐다(#420) — 지금은 SKILL.md 의 `name:`(`preline-theme-generator`)으로 맞춘다(실측
|
|
642
|
-
// 2026-09-20: 옛 이름 exit 1 · 새 이름 설치 완료). 이름이 바뀌면 월 1회 catalog-verify 가 잡는다.
|
|
643
|
-
//
|
|
644
|
-
// 같은 날 조사한 flowbite 는 **넣지 않는다**: 에이전트용 제공물이 MCP 서버뿐인데, 그때 이 저장소의
|
|
645
|
-
// `.mcp.json` 조립은 트랙 조건만 읽어 opt-in 경로가 없었다 — 넣으면 해당 트랙 전원에게 항상
|
|
646
|
-
// 켜진다. 안 쓰는 사람에게 MCP 툴 스키마만큼의 상주 비용을 물리는 형태라 기각(사용자 확정).
|
|
647
|
-
// #709 (ADR-101) 부터는 카탈로그 `internal` 항목으로 opt-in 경로가 있다(`railway-mcp-server`). flowbite 는
|
|
648
|
-
// 그 경로로 다시 심사할 수 있다 — 이 변경은 넣지 않는다.
|
|
649
|
-
{
|
|
650
|
-
id: "preline",
|
|
651
|
-
tier: "vetted",
|
|
652
|
-
// htmlstreamofficial/preline 6,386 (2026-08-16)
|
|
653
|
-
description: "Preline theme generator \u2014 turns a brand description into a Preline UI Tailwind theme (tokens, dark mode, overrides) with a local generator script. Only for projects that use Preline",
|
|
654
|
-
category: "frontend",
|
|
655
|
-
source: "htmlstreamofficial",
|
|
656
|
-
condition: { kind: "opt-in" },
|
|
657
|
-
method: {
|
|
658
|
-
kind: "skill",
|
|
659
|
-
source: "htmlstreamofficial/preline",
|
|
660
|
-
skill: "preline-theme-generator"
|
|
661
|
-
}
|
|
662
|
-
},
|
|
663
|
-
// #822 (사용자 결정 2026-10-14) — ast-grep 공식 스킬. 언어에 매이지 않아 트랙에 묶지 않고 opt-in.
|
|
664
|
-
// 벤더 README 가 "설치하면 발견될 뿐 선호되지 않는다"고 적고, 기본으로 쓰게 하려면 상주 지시를 권한다 — 관측 없는
|
|
665
|
-
// 상주는 넣지 않으므로 원하는 사람이 고르는 자리에만 둔다. 스킬 파일에 CLI 설치 안내가 없어 description 에 적는다.
|
|
666
|
-
// 리포에 스킬이 2종(ast-grep · ast-grep-outline)이라 하나만 받는다. #820 의 htmx-fastapi-ui 룰은 이 스킬 없이 동작한다.
|
|
667
|
-
{
|
|
668
|
-
id: "ast-grep",
|
|
669
|
-
tier: "experimental",
|
|
670
|
-
// ast-grep/agent-skill 893★ (license none — README "follows ast-grep's MIT"; 본체 ast-grep/ast-grep 16k MIT, 2026-10-14)
|
|
671
|
-
description: "ast-grep \u2014 write and test structural (AST) search and lint rules across languages (ast-grep official). Needs the ast-grep CLI installed separately; fires reliably only when you ask for ast-grep",
|
|
672
|
-
category: "dev-tools",
|
|
673
|
-
source: "ast-grep",
|
|
674
|
-
condition: { kind: "opt-in" },
|
|
675
|
-
method: { kind: "skill", source: "ast-grep/agent-skill", skill: "ast-grep" }
|
|
676
|
-
},
|
|
677
|
-
// === dev tools (has_dev_track) ===
|
|
678
|
-
// v26.110.0 (ADR-039) — security-guidance: 매 편집 패턴 경고 + LLM diff 리뷰 (훅 12파일,
|
|
679
|
-
// Python + Agent SDK 의존). 상시 훅 = 매 편집 비용 + 폭발 반경 — 실측 전 기본설치 금지
|
|
680
|
-
// (Context Cost NSM) → opt-in. Claude Code `/security-review`·agentshield ship 게이트와 보완.
|
|
681
|
-
{
|
|
682
|
-
id: "security-guidance",
|
|
683
|
-
tier: "official",
|
|
684
|
-
// anthropics/claude-plugins-official (220.8K installs, 사용자 관측 2026-07-18)
|
|
685
|
-
description: "security-guidance \u2014 pattern warnings on every edit plus an LLM review of diffs and commits (Anthropic official). Weigh it against security tools you already run: overlap, false positives, and cost per edit",
|
|
686
|
-
category: "dev-tools",
|
|
687
|
-
source: "anthropics",
|
|
688
|
-
condition: { kind: "opt-in" },
|
|
689
|
-
method: {
|
|
690
|
-
kind: "plugin",
|
|
691
|
-
marketplace: "anthropics/claude-plugins-official",
|
|
692
|
-
pluginId: "security-guidance@claude-plugins-official"
|
|
693
|
-
}
|
|
694
|
-
},
|
|
695
|
-
// v26.110.0 (ADR-039) — context7 플러그인은 **미등록** (검토 후 철회): 트랙 표(#751 전 templates/mcp.json) ·
|
|
696
|
-
// codex config.toml.template · opencode 설정이 이미 @upstash/context7-mcp 를 기본 wiring —
|
|
697
|
-
// 플러그인 추가 = 동일 서버 중복 등록이고 도달 범위도 더 좁다(plugin=claude-only vs
|
|
698
|
-
// 템플릿=claude+codex+opencode). "문서 조회 기본 제공" 요구는 기충족.
|
|
699
|
-
{
|
|
700
|
-
id: "agent-browser",
|
|
701
|
-
tier: "vetted",
|
|
702
|
-
// vercel-labs/agent-browser 34k
|
|
703
|
-
description: "agent-browser \u2014 drives a real browser from the terminal (Playwright wrapper: screenshots, DOM search). Only if your CLI has no equivalent browser tool",
|
|
704
|
-
// v26.78.0 — Understanding 으로 재분류: 웹 지각(screenshot·DOM). 영상/코드 지각과 같은 축.
|
|
705
|
-
category: "understanding",
|
|
706
|
-
source: "vercel-labs",
|
|
707
|
-
// #489 (사용자 결정 2026-09-20 a) — 기본 체크에서 opt-in 으로. computer-use 가 같은 일을 하고,
|
|
708
|
-
// 셋 중 설치 비용이 가장 크다(npm devDep → 안 쓰는 프로젝트에 node_modules 만 남는다).
|
|
709
|
-
// 3단계에는 그대로 뜨되 체크 해제, `--with agent-browser` 로 켠다. 기존 설치본은 영향 없음.
|
|
710
|
-
condition: { kind: "opt-in" },
|
|
711
|
-
method: { kind: "npm", pkg: "agent-browser", version: "0.31.0" }
|
|
712
|
-
},
|
|
713
|
-
// v26.78.0 — Understanding 신규 3종 (plugin, opt-in). 에이전트 인지 증강: 영상·코드 지각 + 메모리.
|
|
714
|
-
{
|
|
715
|
-
id: "claude-video",
|
|
716
|
-
tier: "vetted",
|
|
717
|
-
// bradautomates/claude-video 1.8k
|
|
718
|
-
description: "Claude Video \u2014 fetches a video from a URL, extracts frames and a transcript so the agent can see and hear it. For video evidence, not general image understanding",
|
|
719
|
-
category: "understanding",
|
|
720
|
-
source: "bradautomates",
|
|
721
|
-
condition: { kind: "opt-in" },
|
|
722
|
-
method: {
|
|
723
|
-
kind: "plugin",
|
|
724
|
-
marketplace: "bradautomates/claude-video",
|
|
725
|
-
pluginId: "watch@claude-video"
|
|
726
|
-
}
|
|
727
|
-
},
|
|
728
|
-
{
|
|
729
|
-
id: "understand-anything",
|
|
730
|
-
tier: "vetted",
|
|
731
|
-
// Lum1104/Understand-Anything 53k
|
|
732
|
-
description: "Understand Anything \u2014 builds an interactive knowledge graph and dashboard of your codebase. For visual exploration of a large codebase; the graph costs time to build and keep current",
|
|
733
|
-
category: "understanding",
|
|
734
|
-
source: "Lum1104",
|
|
735
|
-
condition: { kind: "opt-in" },
|
|
736
|
-
method: {
|
|
737
|
-
kind: "plugin",
|
|
738
|
-
marketplace: "Lum1104/Understand-Anything",
|
|
739
|
-
pluginId: "understand-anything@understand-anything"
|
|
740
|
-
}
|
|
741
|
-
},
|
|
742
|
-
// === Visual & Media (v26.85.0) — 코드-퍼스트 제작 자산. 전부 opt-in. ===
|
|
743
|
-
// Docker 실설치 검증 PASS (실 claude 2.1.177): plugin install / npx skills add resolve 확인.
|
|
744
|
-
{
|
|
745
|
-
id: "frontend-slides",
|
|
746
|
-
tier: "vetted",
|
|
747
|
-
// zarazhangrui/frontend-slides 21k
|
|
748
|
-
description: "frontend-slides \u2014 dependency-free HTML slide decks with PPTX-to-HTML and PDF export scripts. When an HTML deck is the actual deliverable",
|
|
749
|
-
category: "visual-media",
|
|
750
|
-
source: "zarazhangrui",
|
|
751
|
-
condition: { kind: "opt-in" },
|
|
752
|
-
method: {
|
|
753
|
-
kind: "plugin",
|
|
754
|
-
marketplace: "zarazhangrui/frontend-slides",
|
|
755
|
-
pluginId: "frontend-slides@frontend-slides"
|
|
756
|
-
}
|
|
757
|
-
},
|
|
758
|
-
{
|
|
759
|
-
id: "marp-slide",
|
|
760
|
-
tier: "vetted",
|
|
761
|
-
// softaworks/agent-toolkit 2k
|
|
762
|
-
description: "marp-slide \u2014 Marp Markdown slides with 7 themes and PPTX/PDF export. Only for a Marp-based workflow",
|
|
763
|
-
category: "visual-media",
|
|
764
|
-
source: "softaworks",
|
|
765
|
-
// softaworks plugin dir 는 plugin.json 부재 → skill 경로가 안전 (Docker 검증).
|
|
766
|
-
condition: { kind: "opt-in" },
|
|
767
|
-
method: { kind: "skill", source: "softaworks/agent-toolkit", skill: "marp-slide" }
|
|
768
|
-
},
|
|
769
|
-
{
|
|
770
|
-
id: "gsap-skills",
|
|
771
|
-
tier: "vetted",
|
|
772
|
-
// greensock/gsap-skills 9k (GSAP 본가 공식)
|
|
773
|
-
description: "GSAP skills \u2014 GreenSock's own guide to timelines, ScrollTrigger, and framework lifecycle cleanup. Only for projects that already use GSAP; not a reason to pick it",
|
|
774
|
-
category: "visual-media",
|
|
775
|
-
source: "greensock",
|
|
776
|
-
condition: { kind: "opt-in" },
|
|
777
|
-
method: {
|
|
778
|
-
kind: "plugin",
|
|
779
|
-
marketplace: "greensock/gsap-skills",
|
|
780
|
-
pluginId: "gsap-skills@gsap-skills"
|
|
781
|
-
}
|
|
782
|
-
},
|
|
783
|
-
{
|
|
784
|
-
// remotion: star 3.6k → vetted (star 기반 tier SSOT + trust-tier-drift CI 정합, 사용자 결정 2026-06-13).
|
|
785
|
-
// 코어 BUSL(Business Source License) — opt-in + description 고지로 신중 취급 (경고 배지 대신).
|
|
786
|
-
// --skill 값 = remotion-best-practices (dir `remotion` ≠ frontmatter name, Docker 실측 확정).
|
|
787
|
-
id: "remotion",
|
|
788
|
-
tier: "vetted",
|
|
789
|
-
// remotion-dev/skills 3.6k (license none — 출처 신뢰; 코어 BUSL 고지)
|
|
790
|
-
description: "Remotion \u2014 programmatic MP4 video from React components (core license BUSL). The skill is a reference; the renderer and its environment are a separate install",
|
|
791
|
-
category: "visual-media",
|
|
792
|
-
source: "remotion-dev",
|
|
793
|
-
condition: { kind: "opt-in" },
|
|
794
|
-
method: { kind: "skill", source: "remotion-dev/skills", skill: "remotion-best-practices" }
|
|
795
|
-
},
|
|
796
|
-
// Issue #176 — 프레젠테이션 생성 스택 4종 (Docker 실설치 4/4 PASS: skills@1.5.11 add <src> --agent claude-code --skill, 2026-06-20).
|
|
797
|
-
{
|
|
798
|
-
id: "ppt-master",
|
|
799
|
-
tier: "vetted",
|
|
800
|
-
// hugohe3/ppt-master 29k
|
|
801
|
-
description: "ppt-master \u2014 editable PowerPoint (.pptx) from a document, with native shapes, speaker notes, and your own template. When the deliverable must stay editable in PowerPoint",
|
|
802
|
-
category: "visual-media",
|
|
803
|
-
source: "hugohe3",
|
|
804
|
-
condition: { kind: "opt-in" },
|
|
805
|
-
method: { kind: "skill", source: "hugohe3/ppt-master", skill: "ppt-master" }
|
|
806
|
-
},
|
|
807
|
-
{
|
|
808
|
-
// ADR-104 (#726) — web-video-presentation 의 교체. plugin 인 이유: skill 방식이면 상류 CLI 가
|
|
809
|
-
// 작업 중 `npx hyperframes skills update`/`init` 으로 같은 스킬 디렉터리를 다시 쓰고 prune 해
|
|
810
|
-
// 설치 기록(#573 sha)과 어긋난다. plugin 모드는 그 런타임 설치를 상류가 스스로 금한다.
|
|
811
|
-
// Anthropic 공식 마켓플레이스 항목(sha 고정 URL 소스, 내용 저자 HeyGen) — 그래서 tier official.
|
|
812
|
-
id: "hyperframes",
|
|
813
|
-
tier: "official",
|
|
814
|
-
// anthropics/claude-plugins-official 등재 (heygen-com/hyperframes)
|
|
815
|
-
description: "HyperFrames \u2014 write video as HTML and render it to MP4 (headless Chrome + FFmpeg): narrated explainers, slideshow-style decks, captions and voice-over. Rendering needs Node 22+ and FFmpeg",
|
|
816
|
-
category: "visual-media",
|
|
817
|
-
source: "heygen-com",
|
|
818
|
-
condition: { kind: "opt-in" },
|
|
819
|
-
method: {
|
|
820
|
-
kind: "plugin",
|
|
821
|
-
marketplace: "anthropics/claude-plugins-official",
|
|
822
|
-
pluginId: "hyperframes@claude-plugins-official"
|
|
823
|
-
}
|
|
824
|
-
},
|
|
825
|
-
// === Supabase agent-skills (csr-supabase|full) ===
|
|
826
|
-
{
|
|
827
|
-
id: "supabase-agent-skills",
|
|
828
|
-
tier: "vetted",
|
|
829
|
-
// supabase/agent-skills 2.2k
|
|
830
|
-
description: "Supabase agent skills \u2014 auth, RLS, edge functions, realtime, and current docs (Supabase official). Only for projects on Supabase; pre-checked on csr-supabase and full",
|
|
831
|
-
category: "backend",
|
|
832
|
-
source: "supabase",
|
|
833
|
-
condition: { kind: "any-track", tracks: ["csr-supabase", "full"] },
|
|
834
|
-
method: {
|
|
835
|
-
kind: "plugin",
|
|
836
|
-
marketplace: "supabase/agent-skills",
|
|
837
|
-
pluginId: "supabase@supabase-agent-skills"
|
|
838
|
-
}
|
|
839
|
-
},
|
|
840
|
-
{
|
|
841
|
-
id: "postgres-best-practices",
|
|
842
|
-
tier: "vetted",
|
|
843
|
-
// supabase/agent-skills 2.2k
|
|
844
|
-
description: "Postgres best practices \u2014 schema, index, and query optimization reference (Supabase official). Reach for it when designing tables or fixing slow queries, not on every edit",
|
|
845
|
-
category: "backend",
|
|
846
|
-
source: "supabase",
|
|
847
|
-
condition: { kind: "any-track", tracks: ["csr-supabase", "full"] },
|
|
848
|
-
method: {
|
|
849
|
-
kind: "plugin",
|
|
850
|
-
marketplace: "supabase/agent-skills",
|
|
851
|
-
pluginId: "postgres-best-practices@supabase-agent-skills"
|
|
852
|
-
}
|
|
853
|
-
},
|
|
854
|
-
// === React + Next UI tracks ===
|
|
855
|
-
// v0.6.3 — vercel-labs/agent-skills source는 short form 안 됨. full HTTPS URL 필요.
|
|
856
|
-
// 사용자 확인 형식: `npx skills add https://github.com/vercel-labs/agent-skills --skill <name>`.
|
|
857
|
-
{
|
|
858
|
-
id: "react-best-practices",
|
|
859
|
-
tier: "vetted",
|
|
860
|
-
// vercel-labs/agent-skills 27k (license none — 출처 신뢰)
|
|
861
|
-
description: "React best practices \u2014 Vercel's data-fetching order, server rendering, serialization, and cache guidance. For performance work in React, not every component edit",
|
|
862
|
-
category: "frontend",
|
|
863
|
-
source: "vercel-labs",
|
|
864
|
-
condition: { kind: "any-track", tracks: CSR_SSR_NEXTJS_FULL },
|
|
865
|
-
method: {
|
|
866
|
-
kind: "skill",
|
|
867
|
-
source: "https://github.com/vercel-labs/agent-skills",
|
|
868
|
-
// v0.6.5 — skills.sh registry name. GitHub dir 이름(react-best-practices)과 다름.
|
|
869
|
-
// skills.sh: 대부분 vercel- prefix (web-design-guidelines, deploy-to-vercel만 예외).
|
|
870
|
-
skill: "vercel-react-best-practices"
|
|
871
|
-
}
|
|
872
|
-
},
|
|
873
|
-
{
|
|
874
|
-
id: "shadcn-ui",
|
|
875
|
-
tier: "vetted",
|
|
876
|
-
// shadcn-ui/ui 115k
|
|
877
|
-
description: "shadcn/ui \u2014 reads this project's component config and registry so the agent adds components the way your setup expects (shadcn official). Only for projects that use shadcn",
|
|
878
|
-
category: "frontend",
|
|
879
|
-
source: "shadcn-ui",
|
|
880
|
-
condition: { kind: "any-track", tracks: CSR_SSR_NEXTJS_FULL },
|
|
881
|
-
// v26.54.1 — shadcn/ui repo 의 실제 skill 이름은 `shadcn` (자산 id 와 다름).
|
|
882
|
-
method: { kind: "skill", source: "shadcn/ui", skill: "shadcn" }
|
|
883
|
-
},
|
|
884
|
-
{
|
|
885
|
-
// v26.106.0 (ADR-035, 사용자 승인 D): 순수 pattern-guide(일반 디자인 가이드라인) → opt-in 강등
|
|
886
|
-
// (T2 가설 전제 — taste 3종 중복 주장은 검증자 정정으로 기각, 근거는 P축 단독).
|
|
887
|
-
id: "web-design-guidelines",
|
|
888
|
-
tier: "vetted",
|
|
889
|
-
// vercel-labs/agent-skills 27k (license none — 출처 신뢰)
|
|
890
|
-
description: "Web design guidelines \u2014 Vercel's review checklist for hierarchy, color, spacing, and accessibility. A review aid, not a generator; frontend-design covers the default",
|
|
891
|
-
category: "frontend",
|
|
892
|
-
source: "vercel-labs",
|
|
893
|
-
condition: { kind: "opt-in" },
|
|
894
|
-
method: {
|
|
895
|
-
kind: "skill",
|
|
896
|
-
source: "https://github.com/vercel-labs/agent-skills",
|
|
897
|
-
skill: "web-design-guidelines"
|
|
898
|
-
}
|
|
899
|
-
},
|
|
900
|
-
{
|
|
901
|
-
// 2026-08-17 (사용자 지정) — 웹 게임 개발. **웹 전용이다**: 스킬 본문 frontmatter 가
|
|
902
|
-
// "HTML5, Canvas, WebGL, and JavaScript" 로 스스로 범위를 적고 있고 프레임워크도
|
|
903
|
-
// Phaser · Three.js · Babylon.js · A-Frame 이다. Unity/Unreal/Godot 은 안 덮으므로
|
|
904
|
-
// description 에 "web" 을 남긴다 — 엔진 이름을 지우면 네이티브 엔진까지 되는 것처럼 읽힌다.
|
|
905
|
-
// opt-in 인 이유: frontend 트랙 사용자 대부분은 게임을 만들지 않는다. 게임 트랙은 아직 없다.
|
|
906
|
-
id: "game-engine",
|
|
907
|
-
tier: "vetted",
|
|
908
|
-
// github/awesome-copilot 37,909★ (MIT, 2026-08-17 `gh api` 실측 · GitHub 공식 조직)
|
|
909
|
-
description: "Game engine \u2014 web game development with HTML5 Canvas / WebGL (Phaser, Three.js, Babylon.js): game loop, physics, sprites, tilemaps, input. Web games only, not Unity or Unreal",
|
|
910
|
-
category: "frontend",
|
|
911
|
-
source: "github",
|
|
912
|
-
condition: { kind: "opt-in" },
|
|
913
|
-
method: {
|
|
914
|
-
kind: "skill",
|
|
915
|
-
source: "https://github.com/github/awesome-copilot",
|
|
916
|
-
skill: "game-engine"
|
|
917
|
-
}
|
|
918
|
-
},
|
|
919
|
-
// === Executive tracks ===
|
|
920
|
-
{
|
|
921
|
-
id: "anthropic-document-skills",
|
|
922
|
-
tier: "official",
|
|
923
|
-
// anthropics/skills 144k
|
|
924
|
-
description: "Anthropic document skills \u2014 pptx / docx / xlsx / pdf authoring with real scripts (slide duplication, OOXML validation, thumbnails). For document deliverables",
|
|
925
|
-
category: "business",
|
|
926
|
-
source: "anthropics",
|
|
927
|
-
condition: { kind: "any-track", tracks: ["executive", "full"] },
|
|
928
|
-
method: {
|
|
929
|
-
kind: "plugin",
|
|
930
|
-
marketplace: "anthropics/skills",
|
|
931
|
-
pluginId: "document-skills@anthropic-agent-skills"
|
|
932
|
-
}
|
|
933
|
-
},
|
|
934
|
-
// alirezarezvani/claude-skills marketplace (v2.3.0) — 2026-04-25 통합 갱신.
|
|
935
|
-
// 기존 alirezarezvani/c-level-skills + alirezarezvani/finance-skills 별도 marketplace
|
|
936
|
-
// → 통합된 alirezarezvani/claude-skills marketplace (claude-code-skills 이름)로 이동.
|
|
937
|
-
{
|
|
938
|
-
id: "finance-skills",
|
|
939
|
-
tier: "vetted",
|
|
940
|
-
// alirezarezvani 16k
|
|
941
|
-
description: "finance-skills \u2014 financial analyst, SaaS metrics, and investment advisor skills with calculation scripts. For financial analysis work; the tools matter more than the role names",
|
|
942
|
-
category: "business",
|
|
943
|
-
source: "alirezarezvani",
|
|
944
|
-
// 2026-08-02 사용자 결정: 트랙 기본 → opt-in (ADR-063)
|
|
945
|
-
condition: { kind: "opt-in" },
|
|
946
|
-
method: {
|
|
947
|
-
kind: "plugin",
|
|
948
|
-
marketplace: "alirezarezvani/claude-skills",
|
|
949
|
-
pluginId: "finance-skills@claude-code-skills"
|
|
950
|
-
}
|
|
951
|
-
},
|
|
952
|
-
// === Growth Marketing Track (v0.5.0) ===
|
|
953
|
-
// 2026-08-02 정비 — alirezarezvani 번들 4종(c-level·business-growth·pm·marketing) +
|
|
954
|
-
// research-summarizer 제거. 남는 마케팅 번들은 아래 coreyhaines31/marketingskills 하나로,
|
|
955
|
-
// 동명이물(marketing-skills ≠ marketingskills) 병존도 이로써 해소된다.
|
|
956
|
-
// v26.91.0 — coreyhaines31/marketingskills (35k★, MIT, plugin v2.5.1). opt-in =
|
|
957
|
-
// growth-marketing 외 전 트랙에서도 wizard 토글 + `--with marketingskills` 로 설치 가능
|
|
958
|
-
// ("SEO 는 일반 개발에도 활용" 충족: dev 트랙에서 본 번들 토글). 45 스킬 중 SEO 7종
|
|
959
|
-
// (seo-audit/schema/ai-seo/site-architecture/programmatic-seo/content/aso)은 상호참조
|
|
960
|
-
// (product-marketing 우선 + seo-audit↔schema↔ai-seo)라 부분 추출 시 포인터 깨짐 → 번들
|
|
961
|
-
// 통째 설치만 정합. repoForAsset = marketplace(coreyhaines31/marketingskills) → drift 라이브.
|
|
962
|
-
{
|
|
963
|
-
id: "marketingskills",
|
|
964
|
-
tier: "vetted",
|
|
965
|
-
// coreyhaines31 35k
|
|
966
|
-
description: "marketingskills \u2014 SEO, conversion, ads, content, and measurement (45 skills, cross-referenced). For marketing work; too broad as a default on a development track",
|
|
967
|
-
category: "business",
|
|
968
|
-
source: "coreyhaines31",
|
|
969
|
-
condition: { kind: "opt-in" },
|
|
970
|
-
method: {
|
|
971
|
-
kind: "plugin",
|
|
972
|
-
marketplace: "coreyhaines31/marketingskills",
|
|
973
|
-
pluginId: "marketing-skills@marketingskills"
|
|
974
|
-
}
|
|
975
|
-
},
|
|
976
|
-
// v26.76.0 — content-creator / demand-gen 제거: alirezarezvani/claude-skills marketplace.json 에
|
|
977
|
-
// 해당 plugin 부재(Docker 실설치 검출, exit 1). 거짓 광고 0건 원칙(Promise=Implementation).
|
|
978
|
-
// 2026-08-02 정비 — karpathy-coder 제거. Write|Edit 마다 Python complexity 검사를 돌리는
|
|
979
|
-
// 상시 훅이었고, 그 검사는 현행 모델이 이미 하는 일이다 (Opus 5 가이드의 "legacy harness
|
|
980
|
-
// scaffolding"에 정확히 해당). 딸린 배선(`--with-karpathy-hook` 플래그 · templates/hooks/
|
|
981
|
-
// karpathy-gate.sh · settings.json PreToolUse auto-wire)도 함께 삭제 — BREAKING, ADR-060.
|
|
982
|
-
// === Option-gated ===
|
|
983
|
-
{
|
|
984
|
-
// ADR-104 (#726) — trailofbits-skills(differential-review)의 교체. Anthropic 공식 플러그인.
|
|
985
|
-
// `git push` 에 훅을 걸고 스캔이 시간·토큰을 쓰므로 opt-in 이다.
|
|
986
|
-
id: "claude-security",
|
|
987
|
-
tier: "official",
|
|
988
|
-
// anthropics/claude-plugins-official 등재 (./plugins/claude-security)
|
|
989
|
-
description: "Claude Security \u2014 on-request vulnerability scan of a branch, PR, or commit diff; a verifier agent challenges each finding and results are tallied in code; asks you to confirm the cost before it starts. For a light diff check, the built-in /security-review is enough",
|
|
990
|
-
category: "dev-tools",
|
|
991
|
-
source: "anthropics",
|
|
992
|
-
condition: { kind: "opt-in" },
|
|
993
|
-
method: {
|
|
994
|
-
kind: "plugin",
|
|
995
|
-
marketplace: "anthropics/claude-plugins-official",
|
|
996
|
-
pluginId: "claude-security@claude-plugins-official"
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
];
|
|
1000
|
-
var REMOVED_CATALOG_ASSETS = {
|
|
1001
|
-
"product-skills": {},
|
|
1002
|
-
agentmemory: {},
|
|
1003
|
-
revealjs: {},
|
|
1004
|
-
"trailofbits-skills": { replacedBy: "claude-security" },
|
|
1005
|
-
"web-video-presentation": { replacedBy: "hyperframes" }
|
|
1006
|
-
};
|
|
1007
|
-
var DEV_METHOD_SKILL_IDS = [
|
|
1008
|
-
"compaction-handoff",
|
|
1009
|
-
"user-centered-explanation",
|
|
1010
|
-
"audit-service-gaps",
|
|
1011
|
-
"multi-persona-review",
|
|
1012
|
-
"recurrence-prevention"
|
|
1013
|
-
];
|
|
1014
|
-
var INTERNAL_BUNDLED_SKILL_IDS = [
|
|
1015
|
-
...DEV_METHOD_SKILL_IDS,
|
|
1016
|
-
// #602 — 트랙을 따라가는 번들 스킬(csr-*·ssr-*·full). selectedInternalSkills 로 흘러야
|
|
1017
|
-
// .agents/skills(비-Claude 자리)에도 깔린다 — manifest 의 UI_SKILL_DIRS 배선은 .claude/ 만
|
|
1018
|
-
// 알아 TRACKS.md 와 어긋났다. 조건은 buildAssetSpec 이 UI 트랙으로 건다.
|
|
1019
|
-
"ui-visual-review",
|
|
1020
|
-
"north-star",
|
|
1021
|
-
"gh-issue-workflow",
|
|
1022
|
-
"objective-brief",
|
|
1023
|
-
"audit-harness-fit",
|
|
1024
|
-
"model-orchestration",
|
|
1025
|
-
"external-model-consult",
|
|
1026
|
-
"self-hosted-github-runner",
|
|
1027
|
-
"natural-korean",
|
|
1028
|
-
// #729 (ADR-109) — 토론(개발 트랙 기본) + 브라우저 위임 3종(opt-in). condition 이 설치를 가른다.
|
|
1029
|
-
"multi-model-debate",
|
|
1030
|
-
"gemini-delegate",
|
|
1031
|
-
"chatgpt-delegate",
|
|
1032
|
-
"genspark-delegate"
|
|
1033
|
-
];
|
|
1034
|
-
var CONTINUOUS_SKILLS = [
|
|
1035
|
-
{
|
|
1036
|
-
// #493 (#485 분리, 사용자 결정 2026-09-20) — 상주 한 줄의 문안은 사용자가 이슈에 적은 원칙이다:
|
|
1037
|
-
// 평소 설명부터 사용자 행동·결과를 먼저, 재설명·UX 전후·중요한 선택 정리는 스킬로.
|
|
1038
|
-
id: "user-centered-explanation",
|
|
1039
|
-
whenToApply: "when explaining a development problem, change, or choice to the user, explain what the user does and sees before internal implementation \u2014 in every answer and report, not only when approval is asked for; for a detailed re-explanation, a UX before/after comparison, or laying out an important decision, apply this skill"
|
|
1040
|
-
},
|
|
1041
|
-
{
|
|
1042
|
-
id: "model-orchestration",
|
|
1043
|
-
whenToApply: "when work is delegated, it decides which lane takes the work and how that lane is run"
|
|
1044
|
-
}
|
|
1045
|
-
];
|
|
1046
|
-
var RENAMED_SKILL_IDS = {
|
|
1047
|
-
"task-brief": "objective-brief",
|
|
1048
|
-
"humanize-korean": "natural-korean",
|
|
1049
|
-
// #485 (2026-09-20) — 새 이름은 `new-skills` 묶음(#480 ①)이 같은 update 에서 깐다.
|
|
1050
|
-
"clear-korean-communication": "user-centered-explanation"
|
|
1051
|
-
};
|
|
1052
|
-
var RETIRED_SKILL_IDS = [
|
|
1053
|
-
"strategic-compact",
|
|
1054
|
-
"continuous-learning-v2",
|
|
1055
|
-
"spec-scaling",
|
|
1056
|
-
// ADR-090 (#452) — 관측 없는 자산 은퇴. 앞 세 종과 같은 경로로 안내한다.
|
|
1057
|
-
"verification-loop",
|
|
1058
|
-
"deep-research",
|
|
1059
|
-
"eval-harness",
|
|
1060
|
-
"agent-introspection-debugging",
|
|
1061
|
-
// #492 — ECC cherry-pick 스킬 7종 은퇴. 번들에서 빠졌으니 설치본에 남은 디렉터리는 죽은
|
|
1062
|
-
// 사본이다 — `update` 화면이 "지워도 된다"를 말하고 삭제는 사용자가 한다(위와 같은 규율).
|
|
1063
|
-
"python-patterns",
|
|
1064
|
-
"python-testing",
|
|
1065
|
-
"e2e-testing",
|
|
1066
|
-
"nextjs-turbopack",
|
|
1067
|
-
"market-research",
|
|
1068
|
-
"investor-materials",
|
|
1069
|
-
"investor-outreach"
|
|
1070
|
-
];
|
|
1071
|
-
var TRUST_TIER = Object.fromEntries(
|
|
1072
|
-
EXTERNAL_ASSETS.map((a) => [a.id, a.tier])
|
|
1073
|
-
);
|
|
1074
|
-
function assetTrustTier(assetId) {
|
|
1075
|
-
return TRUST_TIER[assetId] ?? "experimental";
|
|
1076
|
-
}
|
|
1077
|
-
function shouldInstallAsset(asset, ctx) {
|
|
1078
|
-
if (ctx.userOverride?.forceExclude.includes(asset.id)) return false;
|
|
1079
|
-
if (ctx.userOverride?.forceInclude.includes(asset.id)) return true;
|
|
1080
|
-
if (TRUST_TIER[asset.id] === "experimental") return false;
|
|
1081
|
-
return matchesCondition(asset, ctx);
|
|
1082
|
-
}
|
|
1083
|
-
function matchesCondition(asset, ctx) {
|
|
1084
|
-
const cond = asset.condition;
|
|
1085
|
-
switch (cond.kind) {
|
|
1086
|
-
case "any-track":
|
|
1087
|
-
return ctx.tracks.some((t) => cond.tracks.includes(t));
|
|
1088
|
-
case "has-dev-track":
|
|
1089
|
-
return hasDevTrack(ctx.tracks);
|
|
1090
|
-
case "option":
|
|
1091
|
-
return ctx.options[cond.flag] === true;
|
|
1092
|
-
case "opt-in":
|
|
1093
|
-
return false;
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
function isAssetSelected(assetId, ctx) {
|
|
1097
|
-
const asset = EXTERNAL_ASSETS.find((a) => a.id === assetId);
|
|
1098
|
-
return asset ? shouldInstallAsset(asset, ctx) : false;
|
|
1099
|
-
}
|
|
1100
|
-
function experimentalOptInCandidates(ctx) {
|
|
1101
|
-
return EXTERNAL_ASSETS.filter(
|
|
1102
|
-
(a) => TRUST_TIER[a.id] === "experimental" && !ctx.userOverride?.forceInclude.includes(a.id) && matchesCondition(a, ctx)
|
|
1103
|
-
);
|
|
1104
|
-
}
|
|
1105
|
-
function assetCliSupport(asset) {
|
|
1106
|
-
if (asset.cliSupportOverride) {
|
|
1107
|
-
return [...asset.cliSupportOverride];
|
|
1108
|
-
}
|
|
1109
|
-
switch (asset.method.kind) {
|
|
1110
|
-
case "plugin":
|
|
1111
|
-
return ["claude"];
|
|
1112
|
-
case "skill":
|
|
1113
|
-
case "npm":
|
|
1114
|
-
case "npx-run":
|
|
1115
|
-
case "internal":
|
|
1116
|
-
return [...CLI_BASES];
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
function assetReachesCli(asset, cli) {
|
|
1120
|
-
if (cli.length === 0) {
|
|
1121
|
-
return true;
|
|
1122
|
-
}
|
|
1123
|
-
const support = assetCliSupport(asset);
|
|
1124
|
-
return cli.some((c) => support.includes(c));
|
|
1125
|
-
}
|
|
1126
|
-
function filterApplicableAssets(assets, ctx) {
|
|
1127
|
-
return assets.filter((a) => shouldInstallAsset(a, ctx));
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
// src/manifest.ts
|
|
1131
|
-
init_esm_shims();
|
|
1132
|
-
|
|
1133
|
-
// src/install-log.ts
|
|
1134
|
-
init_esm_shims();
|
|
1135
|
-
import { createHash } from "crypto";
|
|
1136
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, rmSync, statSync, writeFileSync } from "fs";
|
|
1137
|
-
import { dirname as dirname3, join as join4 } from "path";
|
|
1138
|
-
|
|
1139
|
-
// package.json
|
|
1140
|
-
var package_default = {
|
|
1141
|
-
name: "@uzysjung/agent-harness",
|
|
1142
|
-
version: "26.174.0",
|
|
1143
|
-
description: "Curate vetted AI-coding skills & plugins by your tech stack \u2014 install only what you need, across Claude Code, Codex, OpenCode & Antigravity",
|
|
1144
|
-
type: "module",
|
|
1145
|
-
publishConfig: {
|
|
1146
|
-
access: "public"
|
|
1147
|
-
},
|
|
1148
|
-
engines: {
|
|
1149
|
-
node: ">=20.12.0"
|
|
1150
|
-
},
|
|
1151
|
-
bin: {
|
|
1152
|
-
"agent-harness": "./dist/index.js"
|
|
1153
|
-
},
|
|
1154
|
-
files: [
|
|
1155
|
-
"dist",
|
|
1156
|
-
"templates",
|
|
1157
|
-
"README.md",
|
|
1158
|
-
"LICENSE"
|
|
1159
|
-
],
|
|
1160
|
-
scripts: {
|
|
1161
|
-
build: "tsup",
|
|
1162
|
-
dev: "tsup --watch",
|
|
1163
|
-
"gen:compat": "npm run build && node scripts/gen-compatibility.mjs",
|
|
1164
|
-
"gen:legacy-fingerprints": "node scripts/gen-legacy-fingerprints.mjs",
|
|
1165
|
-
test: "vitest run",
|
|
1166
|
-
"test:watch": "vitest",
|
|
1167
|
-
"test:coverage": "vitest run --coverage",
|
|
1168
|
-
typecheck: "tsc --noEmit",
|
|
1169
|
-
lint: "biome check src tests",
|
|
1170
|
-
"lint:fix": "biome check --write src tests",
|
|
1171
|
-
format: "biome format --write src tests",
|
|
1172
|
-
ci: "npm run typecheck && npm run lint && npm run test:coverage && npm run build",
|
|
1173
|
-
prepare: "[ -d dist ] || npm run build",
|
|
1174
|
-
"cost:report": "npm run build && node scripts/context-cost-report.mjs",
|
|
1175
|
-
"cost:baseline": "npm run build && node scripts/context-cost-baseline.mjs",
|
|
1176
|
-
"assets:history": "bash scripts/asset-history.sh",
|
|
1177
|
-
"release:audit": "node scripts/release-audit.mjs",
|
|
1178
|
-
security: "npm audit --omit=dev --audit-level=high && npx --yes ecc-agentshield@1.4.0 scan --baseline security-baseline.json --gate",
|
|
1179
|
-
"security:baseline": "npx --yes ecc-agentshield@1.4.0 scan --save-baseline security-baseline.json"
|
|
1180
|
-
},
|
|
1181
|
-
dependencies: {
|
|
1182
|
-
"@clack/prompts": "^1.3.0",
|
|
1183
|
-
cac: "^7.0.0",
|
|
1184
|
-
"smol-toml": "^1.9.0"
|
|
1185
|
-
},
|
|
1186
|
-
devDependencies: {
|
|
1187
|
-
"@biomejs/biome": "^2.4.13",
|
|
1188
|
-
"@types/node": "^25.6.0",
|
|
1189
|
-
"@vitest/coverage-v8": "^4.1.11",
|
|
1190
|
-
tsup: "^8.3.0",
|
|
1191
|
-
typescript: "^5.6.0",
|
|
1192
|
-
vitest: "^4.1.11"
|
|
1193
|
-
},
|
|
1194
|
-
license: "MIT",
|
|
1195
|
-
repository: {
|
|
1196
|
-
type: "git",
|
|
1197
|
-
url: "git+https://github.com/uzysjung/uzys-agent-harness.git"
|
|
1198
|
-
}
|
|
1199
|
-
};
|
|
1200
|
-
|
|
1201
|
-
// src/adapters/shared-files.ts
|
|
1202
|
-
init_esm_shims();
|
|
1203
|
-
var SHARED_FILES = {
|
|
1204
|
-
"CLAUDE.md": { adapter: "marker-md", prefix: "claude-md:" },
|
|
1205
|
-
"AGENTS.md": { adapter: "marker-md", prefix: "agents-md:" },
|
|
1206
|
-
".claude/settings.json": { adapter: "json-keys", prefix: "settings:" },
|
|
1207
|
-
".mcp.json": { adapter: "json-keys", prefix: "mcp:", strip: "mcpServers." },
|
|
1208
|
-
"opencode.json": { adapter: "json-keys", prefix: "opencode:" },
|
|
1209
|
-
".codex/config.toml": { adapter: "toml-region", prefix: "codex:" },
|
|
1210
|
-
".gitignore": { adapter: "lines", prefix: "gitignore:" }
|
|
1211
|
-
};
|
|
1212
|
-
var KEY_ID_PREFIXES = Object.values(SHARED_FILES).map(
|
|
1213
|
-
(f) => f.prefix
|
|
1214
|
-
);
|
|
1215
|
-
function isKeyId(id) {
|
|
1216
|
-
return KEY_ID_PREFIXES.some((p) => id.startsWith(p));
|
|
1217
|
-
}
|
|
1218
|
-
function sharedPathOfKeyId(id) {
|
|
1219
|
-
for (const [path2, file] of Object.entries(SHARED_FILES))
|
|
1220
|
-
if (id.startsWith(file.prefix)) return path2;
|
|
1221
|
-
return null;
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
// src/cli-targets.ts
|
|
1225
|
-
init_esm_shims();
|
|
1226
|
-
var CLI_BASE_SORT_ORDER = {
|
|
1227
|
-
claude: 0,
|
|
1228
|
-
codex: 1,
|
|
1229
|
-
opencode: 2,
|
|
1230
|
-
antigravity: 3
|
|
1231
|
-
};
|
|
1232
|
-
var CLI_DISPLAY_NAMES = {
|
|
1233
|
-
claude: "Claude",
|
|
1234
|
-
codex: "Codex",
|
|
1235
|
-
opencode: "OpenCode",
|
|
1236
|
-
antigravity: "Antigravity"
|
|
1237
|
-
};
|
|
1238
|
-
function parseCliTargets(input) {
|
|
1239
|
-
const items = normalizeInput(input);
|
|
1240
|
-
if (items.length === 0) {
|
|
1241
|
-
return { ok: true, targets: ["claude"], warnings: [] };
|
|
1242
|
-
}
|
|
1243
|
-
const collected = /* @__PURE__ */ new Set();
|
|
1244
|
-
const warnings = [];
|
|
1245
|
-
for (const item of items) {
|
|
1246
|
-
if (!isCliBase(item)) {
|
|
1247
|
-
let hint = "";
|
|
1248
|
-
if (item === "both") {
|
|
1249
|
-
hint = "\n v0.8.0 removed 'both' alias. Use --cli claude --cli codex.";
|
|
1250
|
-
} else if (item === "all") {
|
|
1251
|
-
hint = "\n v0.8.0 removed 'all' alias. Use --cli claude --cli codex --cli opencode.";
|
|
1252
|
-
} else if (item.includes(",")) {
|
|
1253
|
-
hint = "\n Tip: comma-separated not supported. Use --cli A --cli B for multiple.";
|
|
1254
|
-
}
|
|
1255
|
-
return {
|
|
1256
|
-
ok: false,
|
|
1257
|
-
targets: ["claude"],
|
|
1258
|
-
warnings,
|
|
1259
|
-
error: `Invalid --cli value: ${item}. Must be one of: ${CLI_BASES.join(" | ")}${hint}`
|
|
1260
|
-
};
|
|
1261
|
-
}
|
|
1262
|
-
collected.add(item);
|
|
1263
|
-
}
|
|
1264
|
-
const targets = [...collected].sort((a, b) => CLI_BASE_SORT_ORDER[a] - CLI_BASE_SORT_ORDER[b]);
|
|
1265
|
-
return { ok: true, targets, warnings };
|
|
1266
|
-
}
|
|
1267
|
-
function normalizeInput(input) {
|
|
1268
|
-
if (input === void 0 || input === null) return [];
|
|
1269
|
-
if (typeof input === "string") {
|
|
1270
|
-
const trimmed = input.trim();
|
|
1271
|
-
return trimmed === "" ? [] : [trimmed];
|
|
1272
|
-
}
|
|
1273
|
-
return input.filter((s) => typeof s === "string" && s.trim() !== "").map((s) => s.trim());
|
|
1274
|
-
}
|
|
1275
|
-
function targetsInclude(targets, base) {
|
|
1276
|
-
return targets.includes(base);
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
// src/fs-ops.ts
|
|
1280
|
-
init_esm_shims();
|
|
1281
|
-
import {
|
|
1282
|
-
copyFileSync,
|
|
1283
|
-
existsSync,
|
|
1284
|
-
constants as fsConstants,
|
|
1285
|
-
mkdirSync,
|
|
1286
|
-
readdirSync,
|
|
1287
|
-
readFileSync
|
|
1288
|
-
} from "fs";
|
|
1289
|
-
import { dirname as dirname2, join as join2 } from "path";
|
|
1290
|
-
|
|
1291
|
-
// src/outside-project.ts
|
|
1292
|
-
init_esm_shims();
|
|
1293
|
-
import { lstatSync, readlinkSync, realpathSync } from "fs";
|
|
1294
|
-
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "path";
|
|
1295
|
-
function isOutsideProject(projectDir, target) {
|
|
1296
|
-
const root = resolveWriteTarget(projectDir) ?? projectDir;
|
|
1297
|
-
const rel = relative(root, target);
|
|
1298
|
-
return rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel);
|
|
1299
|
-
}
|
|
1300
|
-
function resolveWriteTarget(abs) {
|
|
1301
|
-
const tail = [];
|
|
1302
|
-
const hops = /* @__PURE__ */ new Set();
|
|
1303
|
-
let path2 = abs;
|
|
1304
|
-
for (; ; ) {
|
|
1305
|
-
try {
|
|
1306
|
-
return join(realpathSync(path2), ...tail);
|
|
1307
|
-
} catch {
|
|
1308
|
-
}
|
|
1309
|
-
let isLink = false;
|
|
1310
|
-
try {
|
|
1311
|
-
const st = lstatSync(path2, { throwIfNoEntry: false });
|
|
1312
|
-
if (st !== void 0 && !st.isSymbolicLink()) return null;
|
|
1313
|
-
isLink = st !== void 0;
|
|
1314
|
-
} catch {
|
|
1315
|
-
return null;
|
|
1316
|
-
}
|
|
1317
|
-
if (isLink) {
|
|
1318
|
-
if (hops.has(path2)) return null;
|
|
1319
|
-
hops.add(path2);
|
|
1320
|
-
path2 = resolve(dirname(path2), readlinkSync(path2));
|
|
1321
|
-
continue;
|
|
1322
|
-
}
|
|
1323
|
-
const parent = dirname(path2);
|
|
1324
|
-
if (parent === path2) return null;
|
|
1325
|
-
tail.unshift(basename(path2));
|
|
1326
|
-
path2 = parent;
|
|
1327
|
-
}
|
|
1328
|
-
}
|
|
1329
|
-
function outsideProjectTarget(projectDir, abs) {
|
|
1330
|
-
const target = resolveWriteTarget(abs);
|
|
1331
|
-
if (target === null || !isOutsideProject(projectDir, target)) return null;
|
|
1332
|
-
const rel = relative(projectDir, abs);
|
|
1333
|
-
const parts = rel.split(sep);
|
|
1334
|
-
let link = rel;
|
|
1335
|
-
let linkTarget = target;
|
|
1336
|
-
for (let i = 1; i <= parts.length; i++) {
|
|
1337
|
-
const prefix = join(projectDir, ...parts.slice(0, i));
|
|
1338
|
-
const st = lstatSync(prefix, { throwIfNoEntry: false });
|
|
1339
|
-
if (st?.isSymbolicLink() !== true) continue;
|
|
1340
|
-
const resolved = resolveWriteTarget(prefix);
|
|
1341
|
-
if (resolved === null || !isOutsideProject(projectDir, resolved)) continue;
|
|
1342
|
-
link = parts.slice(0, i).join(sep);
|
|
1343
|
-
linkTarget = resolved;
|
|
1344
|
-
break;
|
|
1345
|
-
}
|
|
1346
|
-
const slash = (p) => p.split(sep).join("/");
|
|
1347
|
-
return { path: slash(rel), target, link: slash(link), linkTarget };
|
|
1348
|
-
}
|
|
1349
|
-
function outsideLinkAt(projectDir, rel) {
|
|
1350
|
-
const abs = join(projectDir, rel);
|
|
1351
|
-
try {
|
|
1352
|
-
if (!lstatSync(abs, { throwIfNoEntry: false })?.isSymbolicLink()) return null;
|
|
1353
|
-
} catch {
|
|
1354
|
-
return null;
|
|
1355
|
-
}
|
|
1356
|
-
return outsideProjectTarget(projectDir, abs);
|
|
1357
|
-
}
|
|
1358
|
-
function createOutsideGuard(projectDir) {
|
|
1359
|
-
const seen = /* @__PURE__ */ new Map();
|
|
1360
|
-
return {
|
|
1361
|
-
skip(abs) {
|
|
1362
|
-
const hit = outsideProjectTarget(projectDir, abs);
|
|
1363
|
-
if (hit === null) return false;
|
|
1364
|
-
if (!seen.has(hit.path)) seen.set(hit.path, hit);
|
|
1365
|
-
return true;
|
|
1366
|
-
},
|
|
1367
|
-
list: () => [...seen.values()]
|
|
1368
|
-
};
|
|
1369
|
-
}
|
|
1370
|
-
function mergeOutside(...lists) {
|
|
1371
|
-
const out = /* @__PURE__ */ new Map();
|
|
1372
|
-
for (const list of lists) for (const o of list) if (!out.has(o.path)) out.set(o.path, o);
|
|
1373
|
-
return [...out.values()];
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
// src/fs-ops.ts
|
|
1377
|
-
function ensureDir(path2) {
|
|
1378
|
-
mkdirSync(path2, { recursive: true });
|
|
1379
|
-
}
|
|
1380
|
-
function copyFile(source, target) {
|
|
1381
|
-
if (!existsSync(source)) {
|
|
1382
|
-
throw new Error(`Source not found: ${source}`);
|
|
1383
|
-
}
|
|
1384
|
-
mkdirSync(dirname2(target), { recursive: true });
|
|
1385
|
-
copyFileSync(source, target);
|
|
1386
|
-
}
|
|
1387
|
-
var MAX_BACKUP_CLAIMS = 100;
|
|
1388
|
-
function claimBackupPath(base, claim) {
|
|
1389
|
-
for (let n = 1; n <= MAX_BACKUP_CLAIMS; n++) {
|
|
1390
|
-
const candidate = n === 1 ? base : `${base}-${n}`;
|
|
1391
|
-
try {
|
|
1392
|
-
claim(candidate);
|
|
1393
|
-
return candidate;
|
|
1394
|
-
} catch (err) {
|
|
1395
|
-
if (err.code !== "EEXIST") throw err;
|
|
1396
|
-
}
|
|
1397
|
-
}
|
|
1398
|
-
throw new Error(`Cannot allocate a backup path for ${base} (${MAX_BACKUP_CLAIMS} taken)`);
|
|
1399
|
-
}
|
|
1400
|
-
function backupFile(target, now = /* @__PURE__ */ new Date()) {
|
|
1401
|
-
return claimBackupPath(
|
|
1402
|
-
`${target}.backup-${formatStamp(now)}`,
|
|
1403
|
-
(c) => copyFileSync(target, c, fsConstants.COPYFILE_EXCL)
|
|
1404
|
-
);
|
|
1405
|
-
}
|
|
1406
|
-
function backupFromCopy(target, source, now = /* @__PURE__ */ new Date()) {
|
|
1407
|
-
mkdirSync(dirname2(target), { recursive: true });
|
|
1408
|
-
return claimBackupPath(
|
|
1409
|
-
`${target}.backup-${formatStamp(now)}`,
|
|
1410
|
-
(c) => copyFileSync(source, c, fsConstants.COPYFILE_EXCL)
|
|
1411
|
-
);
|
|
1412
|
-
}
|
|
1413
|
-
function claimBackupDir(target, now = /* @__PURE__ */ new Date()) {
|
|
1414
|
-
return claimBackupPath(`${target}.backup-${formatStamp(now)}`, (c) => mkdirSync(c));
|
|
1415
|
-
}
|
|
1416
|
-
function backupOriginal(backupPath) {
|
|
1417
|
-
const m = /^(.+)\.backup-\d{8}T\d{6}(?:-\d+)?$/.exec(backupPath);
|
|
1418
|
-
return m?.[1] ?? null;
|
|
1419
|
-
}
|
|
1420
|
-
function listFilesRecursive(dir, prefix = "") {
|
|
1421
|
-
if (!existsSync(dir)) return [];
|
|
1422
|
-
const out = [];
|
|
1423
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
1424
|
-
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
1425
|
-
if (entry.isDirectory()) {
|
|
1426
|
-
out.push(...listFilesRecursive(join2(dir, entry.name), rel));
|
|
1427
|
-
} else if (entry.isFile()) {
|
|
1428
|
-
out.push(rel);
|
|
1429
|
-
}
|
|
1430
|
-
}
|
|
1431
|
-
return out;
|
|
1432
|
-
}
|
|
1433
|
-
function formatStamp(now) {
|
|
1434
|
-
return now.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z").slice(0, 15);
|
|
1435
|
-
}
|
|
1436
|
-
var PROJECT_SKELETON_DIRS = [
|
|
1437
|
-
".claude/commands/uzys",
|
|
1438
|
-
".claude/rules",
|
|
1439
|
-
".claude/skills",
|
|
1440
|
-
".claude/agents",
|
|
1441
|
-
".claude/hooks",
|
|
1442
|
-
"docs/decisions"
|
|
1443
|
-
];
|
|
1444
|
-
function ensureProjectSkeleton(projectDir) {
|
|
1445
|
-
for (const d of PROJECT_SKELETON_DIRS) {
|
|
1446
|
-
if (outsideProjectTarget(projectDir, join2(projectDir, d)) !== null) continue;
|
|
1447
|
-
mkdirSync(join2(projectDir, d), { recursive: true });
|
|
1448
|
-
}
|
|
1449
|
-
}
|
|
1450
|
-
function backupIfLossyUtf8(target, now = /* @__PURE__ */ new Date()) {
|
|
1451
|
-
if (!existsSync(target)) return null;
|
|
1452
|
-
let raw;
|
|
1453
|
-
try {
|
|
1454
|
-
raw = readFileSync(target);
|
|
1455
|
-
} catch {
|
|
1456
|
-
return null;
|
|
1457
|
-
}
|
|
1458
|
-
if (Buffer.from(raw.toString("utf8")).equals(raw)) return null;
|
|
1459
|
-
const backup = backupFile(target, now);
|
|
1460
|
-
console.error(
|
|
1461
|
-
`\u26A0 ${target} contains bytes that are not valid UTF-8 \u2014 rewriting would alter them permanently. Original bytes saved as ${backup}`
|
|
1462
|
-
);
|
|
1463
|
-
return backup;
|
|
1464
|
-
}
|
|
1465
|
-
|
|
1466
|
-
// src/track-heal.ts
|
|
1467
|
-
init_esm_shims();
|
|
1468
|
-
import { lstatSync as lstatSync2, readFileSync as readFileSync2 } from "fs";
|
|
1469
|
-
import { join as join3 } from "path";
|
|
1470
|
-
var META_FILE = ".claude/.installed-tracks";
|
|
1471
|
-
var META_KEY = ".installed-tracks";
|
|
1472
|
-
function memoTargets(render) {
|
|
1473
|
-
const cache = /* @__PURE__ */ new Map();
|
|
1474
|
-
return (tracks) => {
|
|
1475
|
-
const key = [...tracks].sort().join(",");
|
|
1476
|
-
const hit = cache.get(key) ?? new Set(render(tracks));
|
|
1477
|
-
cache.set(key, hit);
|
|
1478
|
-
return hit;
|
|
1479
|
-
};
|
|
1480
|
-
}
|
|
1481
|
-
var claudeTargets = memoTargets((tracks) => {
|
|
1482
|
-
const spec = buildAssetSpec({ tracks, options: DEFAULT_OPTIONS });
|
|
1483
|
-
return buildManifest(spec).filter((e) => e.applies(spec) && (e.type === "file" || e.target.startsWith(".claude/skills/"))).map((e) => e.target);
|
|
1484
|
-
});
|
|
1485
|
-
function claudeRecorded(log) {
|
|
1486
|
-
return /* @__PURE__ */ new Set([
|
|
1487
|
-
...(log.policyFiles ?? []).map((f) => `.claude/${f.path}`),
|
|
1488
|
-
...(log.skillFiles ?? []).map((f) => `.claude/skills/${f.path.split("/")[0] ?? ""}`)
|
|
1489
|
-
]);
|
|
1490
|
-
}
|
|
1491
|
-
function healRecordedTracks(log, projectDir, clis) {
|
|
1492
|
-
if (!clis.includes("claude")) return log;
|
|
1493
|
-
const recorded = log.spec.tracks.filter(isTrack);
|
|
1494
|
-
const added = metafileTracks(log, projectDir).filter((t) => !recorded.includes(t));
|
|
1495
|
-
if (added.length === 0) return log;
|
|
1496
|
-
return {
|
|
1497
|
-
...log,
|
|
1498
|
-
spec: { ...log.spec, tracks: [.../* @__PURE__ */ new Set([...log.spec.tracks, ...added])].sort() }
|
|
1499
|
-
};
|
|
1500
|
-
}
|
|
1501
|
-
function metafileTracks(log, projectDir) {
|
|
1502
|
-
const abs = join3(projectDir, META_FILE);
|
|
1503
|
-
if (lstatSync2(abs, { throwIfNoEntry: false })?.isFile() !== true) return [];
|
|
1504
|
-
const content = readFileSync2(abs, "utf8");
|
|
1505
|
-
const sha = log.policyFiles?.find((f) => f.path === META_KEY)?.sha256;
|
|
1506
|
-
if (sha === void 0 || hashContent(content) !== sha) return [];
|
|
1507
|
-
return content.split("\n").map((l) => l.trim()).filter(isTrack);
|
|
1508
|
-
}
|
|
1509
|
-
|
|
1510
|
-
// src/install-log.ts
|
|
1511
|
-
var INSTALL_LOG_FILENAME = ".harness-install.json";
|
|
1512
|
-
var INSTALL_LOG_DIR = ".uzys-agent-harness";
|
|
1513
|
-
var LEGACY_INSTALL_LOG_DIR = ".claude";
|
|
1514
|
-
var INSTALL_LOG_VERSION = 1;
|
|
1515
|
-
var POLICY_DIRS = [
|
|
1516
|
-
{ dir: "rules", ext: ".md" },
|
|
1517
|
-
{ dir: "agents", ext: ".md" },
|
|
1518
|
-
{ dir: "commands/uzys", ext: ".md" },
|
|
1519
|
-
{ dir: "hooks", ext: ".sh" }
|
|
1520
|
-
];
|
|
1521
|
-
var ANTIGRAVITY_RULE_FILE = ".agents/rules/uzys-harness.md";
|
|
1522
|
-
function installedClis(log) {
|
|
1523
|
-
if (log === null) return [];
|
|
1524
|
-
if (log.spec.clis) return sortClis(log.spec.clis);
|
|
1525
|
-
const found = new Set(log.spec.cli.filter(isCliBase));
|
|
1526
|
-
if (log.templates.rootClaudeMd !== void 0) found.add("claude");
|
|
1527
|
-
if (log.templates.codexDir !== void 0) found.add("codex");
|
|
1528
|
-
if (log.templates.opencodeDir !== void 0) found.add("opencode");
|
|
1529
|
-
if ((log.externalFiles ?? []).some((f) => f.path === ANTIGRAVITY_RULE_FILE)) {
|
|
1530
|
-
found.add("antigravity");
|
|
1531
|
-
}
|
|
1532
|
-
return sortClis([...found]);
|
|
1533
|
-
}
|
|
1534
|
-
function sortClis(clis) {
|
|
1535
|
-
return [...new Set(clis)].filter(isCliBase).sort((a, b) => CLI_BASE_SORT_ORDER[a] - CLI_BASE_SORT_ORDER[b]);
|
|
1536
|
-
}
|
|
1537
|
-
function buildAssetEntries(report, scope, previous) {
|
|
1538
|
-
if (!report) return [];
|
|
1539
|
-
return report.attempted.filter((r) => r.ok).map((r) => {
|
|
1540
|
-
const entry = assetToLogEntry(r.asset, scope, r.version);
|
|
1541
|
-
if (r.files === void 0) return entry;
|
|
1542
|
-
const recordedBefore = previous?.find((a) => a.id === r.asset.id);
|
|
1543
|
-
if (recordedBefore !== void 0 && recordedBefore.files === void 0) return entry;
|
|
1544
|
-
return { ...entry, files: mergeToolFiles(recordedBefore?.files, r.files) };
|
|
1545
|
-
});
|
|
1546
|
-
}
|
|
1547
|
-
function mergeToolFiles(prior, run) {
|
|
1548
|
-
const changed = new Map(run.changed.map((f) => [f.path, f.sha256]));
|
|
1549
|
-
const byPath = /* @__PURE__ */ new Map();
|
|
1550
|
-
for (const f of prior ?? []) byPath.set(f.path, changed.get(f.path) ?? f.sha256);
|
|
1551
|
-
for (const f of run.created) byPath.set(f.path, f.sha256);
|
|
1552
|
-
return [...byPath].map(([path2, sha256]) => ({ path: path2, sha256 }));
|
|
1553
|
-
}
|
|
1554
|
-
function assetToLogEntry(asset, scope, version) {
|
|
1555
|
-
const detail = methodDetail(asset.method);
|
|
1556
|
-
const entry = {
|
|
1557
|
-
id: asset.id,
|
|
1558
|
-
category: asset.category,
|
|
1559
|
-
method: asset.method.kind,
|
|
1560
|
-
scope,
|
|
1561
|
-
detail
|
|
1562
|
-
};
|
|
1563
|
-
if (version) entry.version = version;
|
|
1564
|
-
return entry;
|
|
1565
|
-
}
|
|
1566
|
-
function methodDetail(method) {
|
|
1567
|
-
switch (method.kind) {
|
|
1568
|
-
case "plugin":
|
|
1569
|
-
return { marketplace: method.marketplace, pluginId: method.pluginId };
|
|
1570
|
-
case "skill":
|
|
1571
|
-
return { source: method.source, ...method.skill ? { skill: method.skill } : {} };
|
|
1572
|
-
case "npm":
|
|
1573
|
-
return { pkg: method.pkg };
|
|
1574
|
-
case "npx-run":
|
|
1575
|
-
return { cmd: method.cmd, args: (method.args ?? []).join(" ") };
|
|
1576
|
-
case "internal":
|
|
1577
|
-
return { key: method.key };
|
|
1578
|
-
}
|
|
1579
|
-
}
|
|
1580
|
-
var BUNDLED_SKILL_IDS = new Set(INTERNAL_BUNDLED_SKILL_IDS);
|
|
1581
|
-
function buildInstallLog(spec, external, scope, rootClaudeMd, previous, claudeDirMovedAside = false, rootFiles = [], codexTrust) {
|
|
1582
|
-
const skillExclude = (spec.userOverride?.forceExclude ?? []).filter(
|
|
1583
|
-
(id) => BUNDLED_SKILL_IDS.has(id)
|
|
1584
|
-
);
|
|
1585
|
-
const templates = {
|
|
1586
|
-
...spec.cli.includes("claude") ? { claudeDir: ".claude/" } : {},
|
|
1587
|
-
...spec.cli.includes("codex") ? { codexDir: ".codex/" } : {},
|
|
1588
|
-
...spec.cli.includes("opencode") ? { opencodeDir: ".opencode/" } : {},
|
|
1589
|
-
...rootClaudeMd ? { rootClaudeMd } : {}
|
|
1590
|
-
};
|
|
1591
|
-
const clis = sortClis([...installedClis(previous ?? null), ...spec.cli.filter(isCliBase)]);
|
|
1592
|
-
const tracks = [
|
|
1593
|
-
.../* @__PURE__ */ new Set([...(previous?.spec.tracks ?? []).filter(isTrack), ...spec.tracks])
|
|
1594
|
-
].sort();
|
|
1595
|
-
const log = {
|
|
1596
|
-
schemaVersion: INSTALL_LOG_VERSION,
|
|
1597
|
-
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1598
|
-
scope,
|
|
1599
|
-
spec: {
|
|
1600
|
-
tracks,
|
|
1601
|
-
cli: spec.cli,
|
|
1602
|
-
clis,
|
|
1603
|
-
...spec.baselineExclude && spec.baselineExclude.length > 0 ? { baselineExclude: spec.baselineExclude } : {},
|
|
1604
|
-
...skillExclude.length > 0 ? { skillExclude } : {}
|
|
1605
|
-
},
|
|
1606
|
-
// 이번 설치가 만든 항목이 이기고, 이번에 안 만든 항목은 이전 값을 그대로 둔다.
|
|
1607
|
-
// (예: claude 로 깔고 나중에 codex 만 추가 설치해도 root CLAUDE.md 기록이 살아남는다)
|
|
1608
|
-
templates: { ...previous?.templates, ...templates },
|
|
1609
|
-
assets: mergeAssets(
|
|
1610
|
-
claudeDirMovedAside ? previous?.assets?.filter(survivesClaudeDirRename) : previous?.assets,
|
|
1611
|
-
buildAssetEntries(external, scope, previous?.assets)
|
|
1612
|
-
)
|
|
1613
|
-
};
|
|
1614
|
-
const mergedRootFiles = mergeRootFiles(previous?.rootFiles, rootFiles);
|
|
1615
|
-
if (mergedRootFiles.length > 0) log.rootFiles = mergedRootFiles;
|
|
1616
|
-
const trust = codexTrust ?? previous?.codexTrust;
|
|
1617
|
-
if (trust) log.codexTrust = trust;
|
|
1618
|
-
return log;
|
|
1619
|
-
}
|
|
1620
|
-
function mergeRootFiles(previous, current) {
|
|
1621
|
-
const byPath = /* @__PURE__ */ new Map();
|
|
1622
|
-
for (const file of [...previous ?? [], ...current]) {
|
|
1623
|
-
const prior = byPath.get(file.path);
|
|
1624
|
-
byPath.set(
|
|
1625
|
-
file.path,
|
|
1626
|
-
prior && file.change !== "displaced" ? {
|
|
1627
|
-
path: file.path,
|
|
1628
|
-
change: prior.change === "created" ? "created" : file.change,
|
|
1629
|
-
notes: [.../* @__PURE__ */ new Set([...prior.notes, ...file.notes])]
|
|
1630
|
-
} : file
|
|
1631
|
-
);
|
|
1632
|
-
}
|
|
1633
|
-
return [...byPath.values()];
|
|
1634
|
-
}
|
|
1635
|
-
function survivesClaudeDirRename(asset) {
|
|
1636
|
-
if (asset.scope === "global") return true;
|
|
1637
|
-
switch (asset.method) {
|
|
1638
|
-
case "plugin":
|
|
1639
|
-
return true;
|
|
1640
|
-
// `~/.claude/plugins/cache` — 프로젝트 밖
|
|
1641
|
-
case "npm":
|
|
1642
|
-
return true;
|
|
1643
|
-
// `node_modules/`
|
|
1644
|
-
case "skill":
|
|
1645
|
-
return false;
|
|
1646
|
-
// `npx skills add` project scope → `.claude/skills/`
|
|
1647
|
-
case "shell-script":
|
|
1648
|
-
return false;
|
|
1649
|
-
// legacy(#492 은퇴): ecc-prune → `.claude/local-plugins/`
|
|
1650
|
-
case "npx-run":
|
|
1651
|
-
return false;
|
|
1652
|
-
case "internal":
|
|
1653
|
-
return false;
|
|
1654
|
-
}
|
|
1655
|
-
}
|
|
1656
|
-
function mergeAssets(previous, current) {
|
|
1657
|
-
if (!previous || previous.length === 0) return [...current];
|
|
1658
|
-
const currentById = new Map(current.map((a) => [a.id, a]));
|
|
1659
|
-
const previousIds = new Set(previous.map((a) => a.id));
|
|
1660
|
-
return [
|
|
1661
|
-
...previous.map((a) => currentById.get(a.id) ?? a),
|
|
1662
|
-
...current.filter((a) => !previousIds.has(a.id))
|
|
1663
|
-
];
|
|
1664
|
-
}
|
|
1665
|
-
function hashContent(content) {
|
|
1666
|
-
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
1667
|
-
}
|
|
1668
|
-
function mergeExternalFiles(previous, current) {
|
|
1669
|
-
const byPath = /* @__PURE__ */ new Map();
|
|
1670
|
-
for (const file of [...previous ?? [], ...current]) byPath.set(file.path, file);
|
|
1671
|
-
return [...byPath.values()];
|
|
1672
|
-
}
|
|
1673
|
-
function writeInstallLog(projectDir, log) {
|
|
1674
|
-
const path2 = installLogPath(projectDir);
|
|
1675
|
-
mkdirSync2(dirname3(path2), { recursive: true });
|
|
1676
|
-
const marked = { ...normalizeLogOrder(log), excludedKeysMigrated: true };
|
|
1677
|
-
writeFileSync(path2, `${JSON.stringify(marked, null, 2)}
|
|
1678
|
-
`, "utf8");
|
|
1679
|
-
migrateAwayLegacyLog(projectDir);
|
|
1680
|
-
return path2;
|
|
1681
|
-
}
|
|
1682
|
-
function normalizeLogOrder(log) {
|
|
1683
|
-
const cmp = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
1684
|
-
const byPath = (xs) => [...xs].sort((a, b) => cmp(a.path, b.path));
|
|
1685
|
-
const out = { ...log };
|
|
1686
|
-
if (log.policyFiles !== void 0) out.policyFiles = byPath(log.policyFiles);
|
|
1687
|
-
if (log.skillFiles !== void 0) out.skillFiles = byPath(log.skillFiles);
|
|
1688
|
-
if (log.externalFiles !== void 0) out.externalFiles = byPath(log.externalFiles);
|
|
1689
|
-
if (log.rootFiles !== void 0) out.rootFiles = byPath(log.rootFiles);
|
|
1690
|
-
if (log.portions !== void 0) {
|
|
1691
|
-
out.portions = [...log.portions].sort((a, b) => cmp(a.path, b.path) || cmp(a.key, b.key));
|
|
1692
|
-
}
|
|
1693
|
-
return out;
|
|
1694
|
-
}
|
|
1695
|
-
function migrateAwayLegacyLog(projectDir) {
|
|
1696
|
-
const legacy = legacyInstallLogPath(projectDir);
|
|
1697
|
-
if (!existsSync2(legacy)) return;
|
|
1698
|
-
try {
|
|
1699
|
-
rmSync(legacy);
|
|
1700
|
-
} catch {
|
|
1701
|
-
}
|
|
1702
|
-
}
|
|
1703
|
-
function readInstallLog(projectDir) {
|
|
1704
|
-
return readInstallLogStatus(projectDir).log;
|
|
1705
|
-
}
|
|
1706
|
-
function readInstallLogStatus(projectDir) {
|
|
1707
|
-
const path2 = [installLogPath(projectDir), legacyInstallLogPath(projectDir)].find(
|
|
1708
|
-
(p) => existsSync2(p)
|
|
1709
|
-
);
|
|
1710
|
-
if (!path2) return { status: "missing", log: null };
|
|
1711
|
-
let parsed;
|
|
1712
|
-
try {
|
|
1713
|
-
parsed = JSON.parse(readFileSync3(path2, "utf8"));
|
|
1714
|
-
} catch {
|
|
1715
|
-
return { status: "corrupted", log: null };
|
|
1716
|
-
}
|
|
1717
|
-
if (!isValidInstallLogShape(parsed)) return { status: "corrupted", log: null };
|
|
1718
|
-
const log = parsed;
|
|
1719
|
-
if (Array.isArray(log.assets)) {
|
|
1720
|
-
log.assets = log.assets.map(
|
|
1721
|
-
(a) => a.method === "npm-global" ? { ...a, method: "npm" } : a
|
|
1722
|
-
);
|
|
1723
|
-
}
|
|
1724
|
-
const migrated = migrateExcluded(log, projectDir);
|
|
1725
|
-
if (migrated.droppedKeys.length > 0) {
|
|
1726
|
-
LEGACY_RELEASED.set(migrated.log, {
|
|
1727
|
-
released: migrated.droppedKeys,
|
|
1728
|
-
catalog: migrated.releasedCatalog
|
|
1729
|
-
});
|
|
1730
|
-
}
|
|
1731
|
-
return {
|
|
1732
|
-
status: "ok",
|
|
1733
|
-
log: healRecordedTracks(migrated.log, projectDir, installedClis(migrated.log))
|
|
1734
|
-
};
|
|
1735
|
-
}
|
|
1736
|
-
var LEGACY_RELEASED = /* @__PURE__ */ new WeakMap();
|
|
1737
|
-
function legacyDroppedKeys(log) {
|
|
1738
|
-
return log === null ? [] : LEGACY_RELEASED.get(log)?.released ?? [];
|
|
1739
|
-
}
|
|
1740
|
-
function legacyReleasedCatalog(log) {
|
|
1741
|
-
return log === null ? [] : LEGACY_RELEASED.get(log)?.catalog ?? [];
|
|
1742
|
-
}
|
|
1743
|
-
var SELECTIONS_MAX = 100;
|
|
1744
|
-
var HARNESS_VERSION = package_default.version;
|
|
1745
|
-
function appendSelection(log, ev) {
|
|
1746
|
-
const empty = (a) => a === void 0 || a.length === 0;
|
|
1747
|
-
if (empty(ev.with) && empty(ev.without) && empty(ev.released) && empty(ev.kept)) return log;
|
|
1748
|
-
const clean = {
|
|
1749
|
-
at: ev.at,
|
|
1750
|
-
harness: ev.harness,
|
|
1751
|
-
by: ev.by,
|
|
1752
|
-
...ev.via !== void 0 ? { via: ev.via } : {},
|
|
1753
|
-
...empty(ev.with) ? {} : { with: [...ev.with ?? []] },
|
|
1754
|
-
...empty(ev.without) ? {} : { without: [...ev.without ?? []] },
|
|
1755
|
-
...empty(ev.released) ? {} : { released: [...ev.released ?? []] },
|
|
1756
|
-
...empty(ev.kept) ? {} : { kept: [...ev.kept ?? []] },
|
|
1757
|
-
...ev.interrupted ? { interrupted: true } : {}
|
|
1758
|
-
};
|
|
1759
|
-
return { ...log, selections: [...log.selections ?? [], clean].slice(-SELECTIONS_MAX) };
|
|
1760
|
-
}
|
|
1761
|
-
var LEGACY_REINSTALL_WINDOW_MS = 6e5;
|
|
1762
|
-
function assetFiles(projectDir, asset) {
|
|
1763
|
-
if (asset.files !== void 0) {
|
|
1764
|
-
return asset.files.map((f) => join4(projectDir, f.path)).filter((p) => existsSync2(p));
|
|
1765
|
-
}
|
|
1766
|
-
const dir = asset.detail.skill ?? asset.id;
|
|
1767
|
-
return [".claude/skills", ".agents/skills"].flatMap(
|
|
1768
|
-
(d) => listFilesRecursive(join4(projectDir, d, dir)).map((rel) => join4(projectDir, d, dir, rel))
|
|
1769
|
-
);
|
|
1770
|
-
}
|
|
1771
|
-
function legacyCatalogReleased(projectDir, log, asset) {
|
|
1772
|
-
if (asset.method !== "skill" || asset.scope === "global") return false;
|
|
1773
|
-
const files = assetFiles(projectDir, asset);
|
|
1774
|
-
if (files.length === 0) return false;
|
|
1775
|
-
const t = Date.parse(log.installedAt);
|
|
1776
|
-
if (Number.isNaN(t)) return false;
|
|
1777
|
-
const m = Math.max(...files.map((f) => statSync(f).mtimeMs));
|
|
1778
|
-
return m <= t && m >= t - LEGACY_REINSTALL_WINDOW_MS;
|
|
1779
|
-
}
|
|
1780
|
-
function migrateExcluded(log, projectDir) {
|
|
1781
|
-
if (log.excludedKeysMigrated === true) return { log, droppedKeys: [], releasedCatalog: [] };
|
|
1782
|
-
const baseline = new Set(log.spec.baselineExclude ?? []);
|
|
1783
|
-
const skills = new Set(log.spec.skillExclude ?? []);
|
|
1784
|
-
const released = [];
|
|
1785
|
-
const releasedCatalog = [];
|
|
1786
|
-
const keptCatalog = [];
|
|
1787
|
-
const kept = [];
|
|
1788
|
-
for (const id of log.excluded ?? []) {
|
|
1789
|
-
if (isKeyId(id)) {
|
|
1790
|
-
released.push(id);
|
|
1791
|
-
continue;
|
|
1792
|
-
}
|
|
1793
|
-
if (id.startsWith("baseline:") && !baseline.has(id)) {
|
|
1794
|
-
released.push(id);
|
|
1795
|
-
continue;
|
|
1796
|
-
}
|
|
1797
|
-
if (BUNDLED_SKILL_IDS.has(id) && !skills.has(id)) {
|
|
1798
|
-
released.push(id);
|
|
1799
|
-
continue;
|
|
1800
|
-
}
|
|
1801
|
-
const asset = log.assets.find((a) => a.id === id);
|
|
1802
|
-
if (asset !== void 0 && !BUNDLED_SKILL_IDS.has(id) && !id.startsWith("baseline:")) {
|
|
1803
|
-
if (projectDir !== void 0 && legacyCatalogReleased(projectDir, log, asset)) {
|
|
1804
|
-
released.push(id);
|
|
1805
|
-
releasedCatalog.push(id);
|
|
1806
|
-
continue;
|
|
1807
|
-
}
|
|
1808
|
-
keptCatalog.push(id);
|
|
1809
|
-
}
|
|
1810
|
-
kept.push(id);
|
|
1811
|
-
}
|
|
1812
|
-
let next = { ...log, excludedKeysMigrated: true };
|
|
1813
|
-
if (kept.length > 0) next.excluded = kept;
|
|
1814
|
-
else delete next.excluded;
|
|
1815
|
-
next = appendSelection(next, {
|
|
1816
|
-
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1817
|
-
harness: HARNESS_VERSION,
|
|
1818
|
-
by: "migration",
|
|
1819
|
-
released,
|
|
1820
|
-
kept: keptCatalog
|
|
1821
|
-
});
|
|
1822
|
-
return { log: next, droppedKeys: released, releasedCatalog };
|
|
1823
|
-
}
|
|
1824
|
-
function isValidInstallLogShape(value) {
|
|
1825
|
-
if (typeof value !== "object" || value === null) return false;
|
|
1826
|
-
const v = value;
|
|
1827
|
-
const spec = v.spec;
|
|
1828
|
-
if (typeof spec !== "object" || spec === null) return false;
|
|
1829
|
-
if (!Array.isArray(spec.tracks)) return false;
|
|
1830
|
-
if (!Array.isArray(v.assets)) return false;
|
|
1831
|
-
if (typeof v.templates !== "object" || v.templates === null) return false;
|
|
1832
|
-
return true;
|
|
1833
|
-
}
|
|
1834
|
-
function corruptedInstallLogMessage(projectDir) {
|
|
1835
|
-
return `install log is corrupted at ${installLogPath(projectDir)} \u2014 if it holds git conflict markers, take one side whole; otherwise run install --reinstall (it forgets recorded exclusions and external assets)`;
|
|
1836
|
-
}
|
|
1837
|
-
function installLogPath(projectDir) {
|
|
1838
|
-
return join4(projectDir, INSTALL_LOG_DIR, INSTALL_LOG_FILENAME);
|
|
1839
|
-
}
|
|
1840
|
-
function legacyInstallLogPath(projectDir) {
|
|
1841
|
-
return join4(projectDir, LEGACY_INSTALL_LOG_DIR, INSTALL_LOG_FILENAME);
|
|
1842
|
-
}
|
|
1843
|
-
|
|
1844
|
-
// src/project-claude-merge.ts
|
|
1845
|
-
init_esm_shims();
|
|
1846
|
-
var HARNESS_ANCHOR_FILE = "CLAUDE-uzys-harness.md";
|
|
1847
|
-
var HARNESS_IMPORT_LINE = `@${HARNESS_ANCHOR_FILE}`;
|
|
1848
|
-
var IMPORT_MARKER_START = "<!-- uzys-harness:import:start -->";
|
|
1849
|
-
var IMPORT_MARKER_END = "<!-- uzys-harness:import:end -->";
|
|
1850
|
-
var CONTINUOUS_SKILLS_HEADING = "## Skills that apply continuously";
|
|
1851
|
-
function renderContinuousSkillsNote(selectedInternalSkills) {
|
|
1852
|
-
const lines = CONTINUOUS_SKILLS.filter((s) => selectedInternalSkills.includes(s.id)).map(
|
|
1853
|
-
(s) => `- \`${s.id}\` \u2014 ${s.whenToApply}.`
|
|
1854
|
-
);
|
|
1855
|
-
if (lines.length === 0) return "";
|
|
1856
|
-
return [
|
|
1857
|
-
CONTINUOUS_SKILLS_HEADING,
|
|
1858
|
-
"",
|
|
1859
|
-
"These installed skills apply to every response or delegation; no prompt ever looks like their job, so open them from this list:",
|
|
1860
|
-
"",
|
|
1861
|
-
...lines
|
|
1862
|
-
].join("\n");
|
|
1863
|
-
}
|
|
1864
|
-
function withContinuousSkillsNote(scaffold, selectedInternalSkills) {
|
|
1865
|
-
const note = renderContinuousSkillsNote(selectedInternalSkills);
|
|
1866
|
-
return note ? `${scaffold}
|
|
1867
|
-
|
|
1868
|
-
${note}` : scaffold;
|
|
1869
|
-
}
|
|
1870
|
-
function importBlock(continuousSkills) {
|
|
1871
|
-
const note = renderContinuousSkillsNote(continuousSkills);
|
|
1872
|
-
const body = note ? `${HARNESS_IMPORT_LINE}
|
|
1873
|
-
|
|
1874
|
-
${note}` : HARNESS_IMPORT_LINE;
|
|
1875
|
-
return `${IMPORT_MARKER_START}
|
|
1876
|
-
${body}
|
|
1877
|
-
${IMPORT_MARKER_END}`;
|
|
1878
|
-
}
|
|
1879
|
-
var TRACK_DISPLAY_NAMES = {
|
|
1880
|
-
base: "Base",
|
|
1881
|
-
tooling: "Tooling",
|
|
1882
|
-
"csr-supabase": "CSR Supabase",
|
|
1883
|
-
"csr-fastify": "CSR Fastify",
|
|
1884
|
-
"csr-fastapi": "CSR FastAPI",
|
|
1885
|
-
"ssr-htmx": "SSR htmx + FastAPI",
|
|
1886
|
-
"ssr-htmx-fastify": "SSR htmx + Fastify",
|
|
1887
|
-
"ssr-nextjs": "SSR Next.js",
|
|
1888
|
-
data: "Data",
|
|
1889
|
-
executive: "Executive",
|
|
1890
|
-
full: "Full",
|
|
1891
|
-
"project-management": "Project Management",
|
|
1892
|
-
"growth-marketing": "Growth Marketing"
|
|
1893
|
-
};
|
|
1894
|
-
var FULL_EXPANSION = TRACKS.filter((t) => t !== "full");
|
|
1895
|
-
var FILL_SECTIONS = [
|
|
1896
|
-
"identity",
|
|
1897
|
-
"stack",
|
|
1898
|
-
"architecture",
|
|
1899
|
-
"installed-assets",
|
|
1900
|
-
"boundaries",
|
|
1901
|
-
"verify"
|
|
1902
|
-
];
|
|
1903
|
-
var FILL_SPECS = {
|
|
1904
|
-
identity: {
|
|
1905
|
-
title: "Identity & Purpose",
|
|
1906
|
-
prompt: `Replace the H1 title above with this project's real name, then state in 1-2 plain sentences what it does, who uses it, and why it exists. Sources: README.md, the package.json / pyproject.toml "description", docs/. Do NOT describe the harness itself. Delete this comment when done.`,
|
|
1907
|
-
placeholder: "what this project is, who it is for, and why it exists"
|
|
1908
|
-
},
|
|
1909
|
-
stack: {
|
|
1910
|
-
title: "Stack & Commands",
|
|
1911
|
-
prompt: "Replace the list below with this project's REAL stack. Inspect package.json / pyproject.toml / go.mod / Cargo.toml / Gemfile + lockfiles and any Makefile/justfile/package scripts. List the language(s)+versions, framework(s), package manager, and the exact install/build/test/run commands. Verify each command exists before writing it \u2014 never guess. Delete lines that do not apply, then delete this comment.",
|
|
1912
|
-
placeholder: "languages, runtimes, package manager, and the install/build/test/run commands"
|
|
1913
|
-
},
|
|
1914
|
-
architecture: {
|
|
1915
|
-
title: "Architecture & Layout",
|
|
1916
|
-
prompt: "Map this repository. List each top-level source directory and what it holds, the entry point(s), the 3-5 files a newcomer reads first, and how data/requests flow between the main layers. View the real tree (e.g. `git ls-files | sed 's#/.*##' | sort -u`) \u2014 do not assume a framework's conventional layout. Flag any generated/vendored directories that must never be hand-edited. If this is a single small module, say so in one line. Delete this comment when done.",
|
|
1917
|
-
placeholder: "where things live, the entry points, and how data flows between layers"
|
|
1918
|
-
},
|
|
1919
|
-
"installed-assets": {
|
|
1920
|
-
title: "Installed Harness Assets",
|
|
1921
|
-
prompt: "List the harness assets installed in this project (rules / skills / agents / commands) and add one line each on when to reach for it here. Verify against the harness folders this CLI reads (.claude/rules/ \xB7 .claude/skills/ \xB7 .claude/agents/ for Claude Code; AGENTS.md \xA7 Harness Rules \xB7 .agents/skills/ for Codex and OpenCode; .agents/rules/ \xB7 .agents/skills/ for Antigravity) and list ONLY assets whose file exists on disk \u2014 do not invent any. Do NOT restate the harness's working principles (they live in this project's harness anchor and rules layer) \u2014 cross-reference them instead. Delete this comment when done.",
|
|
1922
|
-
placeholder: "the installed rules/skills/agents/commands and when to use each"
|
|
1923
|
-
},
|
|
1924
|
-
boundaries: {
|
|
1925
|
-
title: "Boundaries \u2014 Always / Ask First / Never",
|
|
1926
|
-
prompt: `Fill the Always / Ask First / Never lists with this repo's real red lines. Read the CI config, CODEOWNERS, .gitignore, and release/deploy scripts. Never must include secrets, generated files, force-push, and direct commits to the default branch, plus any repo-specific "do not touch" directories. Every entry must be specific and enforceable here. Delete this comment when done.`,
|
|
1927
|
-
placeholder: "what to always do, ask before doing, and never do in this repo"
|
|
1928
|
-
},
|
|
1929
|
-
verify: {
|
|
1930
|
-
title: "Verification Gate",
|
|
1931
|
-
prompt: 'State the single command (or short sequence) that PROVES a change is safe here \u2014 the test/lint/typecheck/build gate you run before committing \u2014 plus the coverage/CI threshold and what "done" means. Sources: package scripts, CI workflow files, CONTRIBUTING. An agent must be able to self-verify without guessing. Delete this comment when done.',
|
|
1932
|
-
placeholder: "the single command that proves a change is safe, and the done bar"
|
|
1933
|
-
}
|
|
1934
|
-
};
|
|
1935
|
-
function anchorLine(surface) {
|
|
1936
|
-
if (surface === "claude") {
|
|
1937
|
-
return `> The working principles live in this project's harness anchor \`${HARNESS_ANCHOR_FILE}\`, imported at the bottom of this file. Everything above that import is **project-specific context only**.`;
|
|
1938
|
-
}
|
|
1939
|
-
return `> **This file (\`AGENTS.md\`) is the harness anchor** \u2014 it carries the working principles in full, further down. Only the \`## \u2026\` sections immediately below are project-specific context for you to fill in.`;
|
|
1940
|
-
}
|
|
1941
|
-
function scaffoldBanner(surface) {
|
|
1942
|
-
return [
|
|
1943
|
-
"> \u2699\uFE0F **SCAFFOLD \u2014 not filled in yet.** The sections below are a fill-in template for THIS project, not verified facts.",
|
|
1944
|
-
"> To fill: open this file and paste each `<!-- FILL: \u2026 -->` comment's instruction into your coding agent (e.g. Claude Code) \u2014 it will inspect the real repo and write the section. You can also fill them by hand; the comments are the instructions.",
|
|
1945
|
-
anchorLine(surface)
|
|
1946
|
-
].join("\n");
|
|
1947
|
-
}
|
|
1948
|
-
function renderFillScaffold(surface = "claude") {
|
|
1949
|
-
const blocks = FILL_SECTIONS.map((id) => {
|
|
1950
|
-
const spec = FILL_SPECS[id];
|
|
1951
|
-
return `## ${spec.title}
|
|
1952
|
-
|
|
1953
|
-
<!-- FILL:${id} \u2014 ${spec.prompt} -->
|
|
1954
|
-
|
|
1955
|
-
_(not filled yet \u2014 ${spec.placeholder})_`;
|
|
1956
|
-
});
|
|
1957
|
-
return `${scaffoldBanner(surface)}
|
|
1958
|
-
|
|
1959
|
-
${blocks.join("\n\n")}`;
|
|
1960
|
-
}
|
|
1961
|
-
var SCAFFOLD_MARK = "SCAFFOLD \u2014 not filled in yet";
|
|
1962
|
-
var TRACK_NOTE_MARK = "Active track(s):";
|
|
1963
|
-
function dropHarnessQuoteBlocks(lines) {
|
|
1964
|
-
const out = [];
|
|
1965
|
-
let i = 0;
|
|
1966
|
-
while (i < lines.length) {
|
|
1967
|
-
if (!(lines[i] ?? "").trim().startsWith(">")) {
|
|
1968
|
-
out.push(lines[i] ?? "");
|
|
1969
|
-
i += 1;
|
|
1970
|
-
continue;
|
|
1971
|
-
}
|
|
1972
|
-
let end = i;
|
|
1973
|
-
while (end < lines.length && (lines[end] ?? "").trim().startsWith(">")) end++;
|
|
1974
|
-
const run = lines.slice(i, end);
|
|
1975
|
-
const text = run.join("\n");
|
|
1976
|
-
if (!text.includes(SCAFFOLD_MARK) && !text.includes(TRACK_NOTE_MARK)) out.push(...run);
|
|
1977
|
-
i = end;
|
|
1978
|
-
}
|
|
1979
|
-
return out;
|
|
1980
|
-
}
|
|
1981
|
-
function continuousNoteEnd(lines, headingAt) {
|
|
1982
|
-
let i = headingAt + 1;
|
|
1983
|
-
while (i < lines.length) {
|
|
1984
|
-
const l = (lines[i] ?? "").replace(/\r$/, "").trim();
|
|
1985
|
-
const isNote = l === "" || l.startsWith("- `") || l.startsWith("These installed skills apply");
|
|
1986
|
-
if (!isNote) break;
|
|
1987
|
-
i++;
|
|
1988
|
-
}
|
|
1989
|
-
return i;
|
|
1990
|
-
}
|
|
1991
|
-
function dropContinuousSkillsNote(lines) {
|
|
1992
|
-
const at = lines.findIndex((l) => l.replace(/\r$/, "").trim() === CONTINUOUS_SKILLS_HEADING);
|
|
1993
|
-
if (at === -1) return [...lines];
|
|
1994
|
-
return [...lines.slice(0, at), ...lines.slice(continuousNoteEnd(lines, at))];
|
|
1995
|
-
}
|
|
1996
|
-
function dropFillComments(lines) {
|
|
1997
|
-
const out = [];
|
|
1998
|
-
let inFill = false;
|
|
1999
|
-
for (const line of lines) {
|
|
2000
|
-
if (!inFill && line.trim().startsWith("<!-- FILL:")) inFill = true;
|
|
2001
|
-
if (inFill) {
|
|
2002
|
-
if (line.includes("-->")) inFill = false;
|
|
2003
|
-
continue;
|
|
2004
|
-
}
|
|
2005
|
-
out.push(line);
|
|
2006
|
-
}
|
|
2007
|
-
return out;
|
|
2008
|
-
}
|
|
2009
|
-
function scaffoldComparable(lines) {
|
|
2010
|
-
return dropFillComments(dropContinuousSkillsNote(dropHarnessQuoteBlocks(lines))).map((l) => l.replace(/\r$/, "").trimEnd()).filter((l) => l.trim() !== "");
|
|
2011
|
-
}
|
|
2012
|
-
function isUnfilledScaffold(body) {
|
|
2013
|
-
const pristine = scaffoldComparable(renderFillScaffold("agents-md").split("\n"));
|
|
2014
|
-
return scaffoldComparable(body).join("\n") === pristine.join("\n");
|
|
2015
|
-
}
|
|
2016
|
-
function mergeProjectClaude(opts) {
|
|
2017
|
-
const expanded = expandTracks(opts.tracks);
|
|
2018
|
-
const trackList = expanded.map((t) => TRACK_DISPLAY_NAMES[t]).join(", ");
|
|
2019
|
-
const header = `# ${opts.projectName}
|
|
2020
|
-
|
|
2021
|
-
> Active track(s): ${trackList}`;
|
|
2022
|
-
return `${header}
|
|
2023
|
-
|
|
2024
|
-
${opts.projectContext ?? renderFillScaffold()}
|
|
2025
|
-
`;
|
|
2026
|
-
}
|
|
2027
|
-
function upsertHarnessImport(existing, opts) {
|
|
2028
|
-
const block = importBlock(opts.continuousSkills ?? []);
|
|
2029
|
-
if (existing === null) {
|
|
2030
|
-
return `${mergeProjectClaude(opts)}
|
|
2031
|
-
${block}
|
|
2032
|
-
`;
|
|
2033
|
-
}
|
|
2034
|
-
const start = existing.indexOf(IMPORT_MARKER_START);
|
|
2035
|
-
if (start !== -1) {
|
|
2036
|
-
const endAt = existing.indexOf(IMPORT_MARKER_END, start);
|
|
2037
|
-
if (endAt === -1) return existing;
|
|
2038
|
-
const current = existing.slice(start, endAt + IMPORT_MARKER_END.length);
|
|
2039
|
-
if (current === block) return existing;
|
|
2040
|
-
return `${existing.slice(0, start)}${block}${existing.slice(endAt + IMPORT_MARKER_END.length)}`;
|
|
2041
|
-
}
|
|
2042
|
-
if (hasHarnessImport(existing)) {
|
|
2043
|
-
return existing;
|
|
2044
|
-
}
|
|
2045
|
-
const body = existing.endsWith("\n") ? existing : `${existing}
|
|
2046
|
-
`;
|
|
2047
|
-
return `${body}
|
|
2048
|
-
${block}
|
|
2049
|
-
`;
|
|
2050
|
-
}
|
|
2051
|
-
function stripHarnessImport(text) {
|
|
2052
|
-
if (!text.includes(IMPORT_MARKER_START) && !text.includes(IMPORT_MARKER_END)) return null;
|
|
2053
|
-
const endsWithNewline = text.endsWith("\n");
|
|
2054
|
-
const out = [];
|
|
2055
|
-
const lines = text.split("\n");
|
|
2056
|
-
let i = 0;
|
|
2057
|
-
while (i < lines.length) {
|
|
2058
|
-
const line = lines[i] ?? "";
|
|
2059
|
-
if (line.includes(IMPORT_MARKER_START)) {
|
|
2060
|
-
let j = i;
|
|
2061
|
-
while (j < lines.length && !(lines[j] ?? "").includes(IMPORT_MARKER_END)) j++;
|
|
2062
|
-
i = j < lines.length ? j + 1 : i + 1;
|
|
2063
|
-
if (out.length > 0 && out[out.length - 1] === "") out.pop();
|
|
2064
|
-
continue;
|
|
2065
|
-
}
|
|
2066
|
-
if (line.includes(IMPORT_MARKER_END) || line.trim() === HARNESS_IMPORT_LINE) {
|
|
2067
|
-
i++;
|
|
2068
|
-
continue;
|
|
2069
|
-
}
|
|
2070
|
-
out.push(line);
|
|
2071
|
-
i++;
|
|
2072
|
-
}
|
|
2073
|
-
let result = out.join("\n");
|
|
2074
|
-
if (endsWithNewline && result !== "" && !result.endsWith("\n")) result += "\n";
|
|
2075
|
-
if (!endsWithNewline && result.endsWith("\n")) result = result.slice(0, -1);
|
|
2076
|
-
return result === text ? null : result;
|
|
2077
|
-
}
|
|
2078
|
-
function hasHarnessImport(text) {
|
|
2079
|
-
if (text.includes(IMPORT_MARKER_START)) return true;
|
|
2080
|
-
let inFence = false;
|
|
2081
|
-
for (const line of text.split("\n")) {
|
|
2082
|
-
if (/^\s*(?:```|~~~)/.test(line)) {
|
|
2083
|
-
inFence = !inFence;
|
|
2084
|
-
continue;
|
|
2085
|
-
}
|
|
2086
|
-
if (!inFence && line.trim() === HARNESS_IMPORT_LINE) return true;
|
|
2087
|
-
}
|
|
2088
|
-
return false;
|
|
2089
|
-
}
|
|
2090
|
-
function expandTracks(tracks) {
|
|
2091
|
-
if (tracks.includes("full")) {
|
|
2092
|
-
return FULL_EXPANSION;
|
|
2093
|
-
}
|
|
2094
|
-
return tracks;
|
|
2095
|
-
}
|
|
2096
|
-
|
|
2097
|
-
// src/manifest.ts
|
|
2098
|
-
function buildAssetSpec(ctx) {
|
|
2099
|
-
return {
|
|
2100
|
-
tracks: ctx.tracks,
|
|
2101
|
-
withTauri: isAssetSelected("tauri-desktop", ctx),
|
|
2102
|
-
// v26.87.0 — internal bundled skills (dev-method + opt-in advisors, v26.95.0). id 별 condition
|
|
2103
|
-
// (has-dev-track vs opt-in)은 isAssetSelected 가 적용한다.
|
|
2104
|
-
selectedInternalSkills: INTERNAL_BUNDLED_SKILL_IDS.filter((id) => isAssetSelected(id, ctx))
|
|
2105
|
-
};
|
|
2106
|
-
}
|
|
2107
|
-
var all = () => true;
|
|
2108
|
-
var dev = (s) => hasDevTrack(s.tracks);
|
|
2109
|
-
var onTracks = (pattern) => (s) => anyTrack(s.tracks, pattern);
|
|
2110
|
-
var COMMON_RULES = ["git-policy", "change-management", "doc-governance"];
|
|
2111
|
-
var DEV_RULES = ["test-policy", "ship-checklist"];
|
|
2112
|
-
var UI_RULES = ["ui-components"];
|
|
2113
|
-
var REACT_UI_TRACKS = "csr-*|ssr-nextjs|full";
|
|
2114
|
-
var TRACK_RULES = {
|
|
2115
|
-
base: [],
|
|
2116
|
-
// #456 — 공통 룰만. cli-development 는 tooling·full 전용
|
|
2117
|
-
"csr-supabase": [],
|
|
2118
|
-
"csr-fastify": [],
|
|
2119
|
-
"csr-fastapi": [],
|
|
2120
|
-
// #820 — htmx 화면 기술(Tailwind v4 + daisyUI)과 그 스택에서 실제로 도는 린트. 백엔드마다 템플릿 형식이
|
|
2121
|
-
// 달라 린트가 갈린다(Jinja = ast-grep · 서버 JSX = @shadcn/lint) — 실측 = `docs/plans/820-spike/`. `full` 은 아래.
|
|
2122
|
-
"ssr-htmx": ["htmx-fastapi-ui"],
|
|
2123
|
-
"ssr-htmx-fastify": ["htmx-fastify-ui"],
|
|
2124
|
-
"ssr-nextjs": [],
|
|
2125
|
-
data: [],
|
|
2126
|
-
executive: [],
|
|
2127
|
-
tooling: ["cli-development"],
|
|
2128
|
-
// #820 — htmx 룰 둘은 `full` 에 넣지 않는다. OpenCode 가 섞인 설치는 룰을 AGENTS.md 본문에 인라인하는데, full 의 렌더가
|
|
2129
|
-
// Antigravity 상한(24,000 B)을 넘는다(22,322 → 24,455 B, `agents-md-size-budget`). htmx 를 쓰면 그 트랙을 함께 고른다.
|
|
2130
|
-
full: ["cli-development"],
|
|
2131
|
-
// v0.5.0 — executive-style baselines (no dev rules; common rules only).
|
|
2132
|
-
"project-management": [],
|
|
2133
|
-
"growth-marketing": []
|
|
2134
|
-
};
|
|
2135
|
-
function resolveRules(spec) {
|
|
2136
|
-
const set = new Set(COMMON_RULES);
|
|
2137
|
-
if (hasDevTrack(spec.tracks)) {
|
|
2138
|
-
for (const r of DEV_RULES) {
|
|
2139
|
-
set.add(r);
|
|
2140
|
-
}
|
|
2141
|
-
}
|
|
2142
|
-
if (anyTrack(spec.tracks, REACT_UI_TRACKS)) {
|
|
2143
|
-
for (const r of UI_RULES) {
|
|
2144
|
-
set.add(r);
|
|
2145
|
-
}
|
|
2146
|
-
}
|
|
2147
|
-
for (const t of spec.tracks) {
|
|
2148
|
-
for (const r of TRACK_RULES[t]) {
|
|
2149
|
-
set.add(r);
|
|
2150
|
-
}
|
|
2151
|
-
}
|
|
2152
|
-
return [...set].sort();
|
|
2153
|
-
}
|
|
2154
|
-
var ALL_RULES = resolveRules({ tracks: [...TRACKS] });
|
|
2155
|
-
var CORE_AGENTS = ["reviewer"];
|
|
2156
|
-
var DEV_AGENTS = ["implementer"];
|
|
2157
|
-
var TRACK_AGENTS = [
|
|
2158
|
-
["data-analyst", "data|full"],
|
|
2159
|
-
["strategist", "executive|full"]
|
|
2160
|
-
];
|
|
2161
|
-
var RETIRED_AGENTS = [
|
|
2162
|
-
{ id: "code-reviewer", instead: "Claude Code \uC758 `/code-review` \uAC00 \uAC19\uC740 \uC77C\uC744 \uD55C\uB2E4" },
|
|
2163
|
-
{ id: "security-reviewer", instead: "Claude Code \uC758 `/security-review` \uAC00 \uAC19\uC740 \uC77C\uC744 \uD55C\uB2E4" },
|
|
2164
|
-
// ADR-090 (#452) — spawn 0 · 결함만 관측된 세 종. **대안을 종마다 적는다**: 한 문구로 뭉치면
|
|
2165
|
-
// 자기 에이전트와 상관없는 대안을 읽게 되고, 그건 "대신 쓸 것을 말한다"의 뜻을 잃는다.
|
|
2166
|
-
{ id: "plan-checker", instead: "`reviewer` \uAC00 \uACC4\uD68D \uBB38\uC11C\uB3C4 \uAC19\uC740 \uACC4\uC57D\uC73C\uB85C \uAC80\uD1A0\uD55C\uB2E4" },
|
|
2167
|
-
{ id: "silent-failure-hunter", instead: "Claude Code \uC758 `/code-review` \uAC00 \uAC19\uC740 \uC77C\uC744 \uD55C\uB2E4" },
|
|
2168
|
-
{ id: "build-error-resolver", instead: "\uBAA8\uB378\uC774 \uAE30\uBCF8\uC73C\uB85C \uD558\uB294 \uC77C\uC774\uB77C \uB300\uCCB4 \uC790\uC0B0\uC774 \uC5C6\uB2E4" }
|
|
2169
|
-
];
|
|
2170
|
-
var RETIRED_PATHS = [
|
|
2171
|
-
// rules (23)
|
|
2172
|
-
"rules/api-contract.md",
|
|
2173
|
-
"rules/benchmark-parity.md",
|
|
2174
|
-
"rules/code-style.md",
|
|
2175
|
-
"rules/commit-policy.md",
|
|
2176
|
-
"rules/data-analysis.md",
|
|
2177
|
-
"rules/database.md",
|
|
2178
|
-
"rules/design-workflow.md",
|
|
2179
|
-
"rules/ecc-git-workflow.md",
|
|
2180
|
-
"rules/ecc-performance-common.md",
|
|
2181
|
-
"rules/ecc-security-common.md",
|
|
2182
|
-
"rules/ecc-testing.md",
|
|
2183
|
-
"rules/error-handling.md",
|
|
2184
|
-
"rules/gates-taxonomy.md",
|
|
2185
|
-
"rules/gsd-planner-antipatterns.md",
|
|
2186
|
-
"rules/htmx.md",
|
|
2187
|
-
"rules/model-routing.md",
|
|
2188
|
-
"rules/nextjs.md",
|
|
2189
|
-
"rules/no-false-ship.md",
|
|
2190
|
-
"rules/playwright-launch.md",
|
|
2191
|
-
"rules/pyside6.md",
|
|
2192
|
-
"rules/seo.md",
|
|
2193
|
-
"rules/shadcn.md",
|
|
2194
|
-
"rules/tauri.md",
|
|
2195
|
-
// agents (5)
|
|
2196
|
-
"agents/build-error-resolver.md",
|
|
2197
|
-
"agents/code-reviewer.md",
|
|
2198
|
-
"agents/plan-checker.md",
|
|
2199
|
-
"agents/security-reviewer.md",
|
|
2200
|
-
"agents/silent-failure-hunter.md",
|
|
2201
|
-
// commands/uzys (7)
|
|
2202
|
-
"commands/uzys/auto.md",
|
|
2203
|
-
"commands/uzys/build.md",
|
|
2204
|
-
"commands/uzys/plan.md",
|
|
2205
|
-
"commands/uzys/review.md",
|
|
2206
|
-
"commands/uzys/ship.md",
|
|
2207
|
-
"commands/uzys/spec.md",
|
|
2208
|
-
"commands/uzys/test.md",
|
|
2209
|
-
// hooks (10)
|
|
2210
|
-
"hooks/agentshield-gate.sh",
|
|
2211
|
-
"hooks/checkpoint-snapshot.sh",
|
|
2212
|
-
"hooks/codebase-map.sh",
|
|
2213
|
-
"hooks/gate-check.sh",
|
|
2214
|
-
"hooks/hito-counter.sh",
|
|
2215
|
-
"hooks/karpathy-gate.sh",
|
|
2216
|
-
"hooks/mcp-pre-exec.sh",
|
|
2217
|
-
"hooks/spec-drift-check.sh",
|
|
2218
|
-
"hooks/task-brief-nudge.sh",
|
|
2219
|
-
"hooks/uncommitted-check.sh"
|
|
2220
|
-
];
|
|
2221
|
-
var RETIRED_HOOK_WIRING_REFS = [
|
|
2222
|
-
// 6617766 (2026-04-14, phase4b) 배선 → c8fdef1 (2026-05-03, CL-v2 observer 중복 fire 제거) 제거.
|
|
2223
|
-
// PreToolUse · PostToolUse 각각 matcher "*" 묶음 하나 — `observe.sh pre|post`, async.
|
|
2224
|
-
"skills/continuous-learning-v2/hooks/observe.sh",
|
|
2225
|
-
// 5defc58 (2026-04-14, wire strategic-compact) 배선 → a125453 (2026-09-13, #444 스킬 3종 은퇴) 제거.
|
|
2226
|
-
// PreToolUse 의 protect-files 묶음 안 두 번째 핸들러, async.
|
|
2227
|
-
"skills/strategic-compact/suggest-compact.sh"
|
|
2228
|
-
];
|
|
2229
|
-
var RETIRED_AGENT_IDS = RETIRED_PATHS.filter(
|
|
2230
|
-
(p) => p.startsWith("agents/")
|
|
2231
|
-
).map((p) => p.slice("agents/".length).replace(/\.md$/, ""));
|
|
2232
|
-
var ALWAYS_HOOKS = ["session-start.sh", "protect-files.sh"];
|
|
2233
|
-
var COMMON_SKILL_DIRS = [];
|
|
2234
|
-
var DEV_SKILL_DIRS = [];
|
|
2235
|
-
function isCliNeutralTarget(target) {
|
|
2236
|
-
return target.startsWith(`${INSTALL_LOG_DIR}/`);
|
|
2237
|
-
}
|
|
2238
|
-
function buildManifest(spec) {
|
|
2239
|
-
const m = [];
|
|
2240
|
-
for (const r of resolveRules(spec)) {
|
|
2241
|
-
m.push({
|
|
2242
|
-
source: `rules/${r}.md`,
|
|
2243
|
-
target: `.claude/rules/${r}.md`,
|
|
2244
|
-
type: "file",
|
|
2245
|
-
applies: all
|
|
2246
|
-
});
|
|
2247
|
-
}
|
|
2248
|
-
m.push({
|
|
2249
|
-
source: "CLAUDE.md",
|
|
2250
|
-
target: HARNESS_ANCHOR_FILE,
|
|
2251
|
-
type: "file",
|
|
2252
|
-
applies: all
|
|
2253
|
-
});
|
|
2254
|
-
m.push({
|
|
2255
|
-
source: "scripts/protect-branch.sh",
|
|
2256
|
-
target: ".uzys-agent-harness/protect-branch.sh",
|
|
2257
|
-
type: "file",
|
|
2258
|
-
applies: all
|
|
2259
|
-
});
|
|
2260
|
-
m.push({
|
|
2261
|
-
source: "scripts/spec-drift-check.sh",
|
|
2262
|
-
target: ".uzys-agent-harness/spec-drift-check.sh",
|
|
2263
|
-
type: "file",
|
|
2264
|
-
applies: all
|
|
2265
|
-
});
|
|
2266
|
-
m.push({
|
|
2267
|
-
source: "scripts/check-absence.sh",
|
|
2268
|
-
target: ".uzys-agent-harness/check-absence.sh",
|
|
2269
|
-
type: "file",
|
|
2270
|
-
applies: all
|
|
2271
|
-
});
|
|
2272
|
-
for (const a of CORE_AGENTS) {
|
|
2273
|
-
m.push({
|
|
2274
|
-
source: `agents/${a}.md`,
|
|
2275
|
-
target: `.claude/agents/${a}.md`,
|
|
2276
|
-
type: "file",
|
|
2277
|
-
applies: all
|
|
2278
|
-
});
|
|
2279
|
-
}
|
|
2280
|
-
for (const a of DEV_AGENTS) {
|
|
2281
|
-
m.push({
|
|
2282
|
-
source: `agents/${a}.md`,
|
|
2283
|
-
target: `.claude/agents/${a}.md`,
|
|
2284
|
-
type: "file",
|
|
2285
|
-
applies: dev
|
|
2286
|
-
});
|
|
2287
|
-
}
|
|
2288
|
-
for (const [a, pattern] of TRACK_AGENTS) {
|
|
2289
|
-
m.push({
|
|
2290
|
-
source: `agents/${a}.md`,
|
|
2291
|
-
target: `.claude/agents/${a}.md`,
|
|
2292
|
-
type: "file",
|
|
2293
|
-
applies: onTracks(pattern)
|
|
2294
|
-
});
|
|
2295
|
-
}
|
|
2296
|
-
for (const sd of COMMON_SKILL_DIRS) {
|
|
2297
|
-
m.push({
|
|
2298
|
-
source: `skills/${sd}`,
|
|
2299
|
-
target: `.claude/skills/${sd}`,
|
|
2300
|
-
type: "dir",
|
|
2301
|
-
applies: all
|
|
2302
|
-
});
|
|
2303
|
-
}
|
|
2304
|
-
for (const sd of INTERNAL_BUNDLED_SKILL_IDS) {
|
|
2305
|
-
m.push({
|
|
2306
|
-
source: `skills/${sd}`,
|
|
2307
|
-
target: `.claude/skills/${sd}`,
|
|
2308
|
-
type: "dir",
|
|
2309
|
-
// #602 — selectedInternalSkills 가 SSOT(위저드 해제·--without 반영). `tracks` 만 담긴
|
|
2310
|
-
// 스펙(listBaselineTargets·위저드 1단계 등)에서는 카탈로그 조건으로 판정한다 —
|
|
2311
|
-
// buildAssetSpec 와 같은 isAssetSelected 라 두 번 적는 사본이 아니라 같은 원천이다.
|
|
2312
|
-
applies: (s) => s.selectedInternalSkills !== void 0 ? s.selectedInternalSkills.includes(sd) : (
|
|
2313
|
-
// #602 폴백은 트랙 추종 번들 스킬 한정 — 카탈로그 옵트인(compaction-handoff 등)은
|
|
2314
|
-
// 선택 목록 없이는 깔리지 않는다(위저드 해제·--without 의 SSOT 를 우회하는 금이 된다).
|
|
2315
|
-
sd === "ui-visual-review" && isAssetSelected(sd, { tracks: s.tracks, options: { withCodexTrust: false } })
|
|
2316
|
-
)
|
|
2317
|
-
});
|
|
2318
|
-
}
|
|
2319
|
-
for (const sd of DEV_SKILL_DIRS) {
|
|
2320
|
-
m.push({
|
|
2321
|
-
source: `skills/${sd}`,
|
|
2322
|
-
target: `.claude/skills/${sd}`,
|
|
2323
|
-
type: "dir",
|
|
2324
|
-
applies: dev
|
|
2325
|
-
});
|
|
2326
|
-
}
|
|
2327
|
-
for (const h of ALWAYS_HOOKS) {
|
|
2328
|
-
m.push({
|
|
2329
|
-
source: `hooks/${h}`,
|
|
2330
|
-
target: `.claude/hooks/${h}`,
|
|
2331
|
-
type: "file",
|
|
2332
|
-
applies: all
|
|
2333
|
-
});
|
|
2334
|
-
}
|
|
2335
|
-
m.push({
|
|
2336
|
-
source: "settings.json",
|
|
2337
|
-
target: ".claude/settings.json",
|
|
2338
|
-
type: "file",
|
|
2339
|
-
applies: all
|
|
2340
|
-
});
|
|
2341
|
-
return m;
|
|
2342
|
-
}
|
|
2343
|
-
|
|
2344
|
-
// src/context-cost.ts
|
|
2345
|
-
init_esm_shims();
|
|
2346
|
-
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
|
|
2347
|
-
import { join as join5, resolve as resolve2 } from "path";
|
|
2348
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2349
|
-
var CHARS_PER_TOKEN = 4;
|
|
2350
|
-
function estimateTokens(chars) {
|
|
2351
|
-
return Math.ceil(chars / CHARS_PER_TOKEN);
|
|
2352
|
-
}
|
|
2353
|
-
function resolveBundleRoot() {
|
|
2354
|
-
return resolve2(fileURLToPath2(new URL(".", import.meta.url)), "..");
|
|
2355
|
-
}
|
|
2356
|
-
function extractFrontmatter(content) {
|
|
2357
|
-
const m = content.match(/^?---\r?\n([\s\S]*?)\r?\n---/);
|
|
2358
|
-
return m?.[1] ?? null;
|
|
2359
|
-
}
|
|
2360
|
-
function assetDescriptorTokens(assetId, root = resolveBundleRoot()) {
|
|
2361
|
-
const asset = EXTERNAL_ASSETS.find((a) => a.id === assetId);
|
|
2362
|
-
if (!asset || asset.method.kind !== "internal") return null;
|
|
2363
|
-
const skillMd = join5(root, "templates", "skills", asset.method.key, "SKILL.md");
|
|
2364
|
-
if (!existsSync3(skillMd)) return null;
|
|
2365
|
-
const fm = extractFrontmatter(readFileSync4(skillMd, "utf8"));
|
|
2366
|
-
return fm === null ? null : estimateTokens(fm.length);
|
|
2367
|
-
}
|
|
2368
|
-
function assetBodyTokens(assetId, root = resolveBundleRoot()) {
|
|
2369
|
-
const asset = EXTERNAL_ASSETS.find((a) => a.id === assetId);
|
|
2370
|
-
if (!asset || asset.method.kind !== "internal") return null;
|
|
2371
|
-
const skillMd = join5(root, "templates", "skills", asset.method.key, "SKILL.md");
|
|
2372
|
-
if (!existsSync3(skillMd)) return null;
|
|
2373
|
-
const content = readFileSync4(skillMd, "utf8");
|
|
2374
|
-
const fm = extractFrontmatter(content);
|
|
2375
|
-
if (fm === null) return estimateTokens(content.length);
|
|
2376
|
-
const close = content.indexOf("\n---", content.indexOf(fm) + fm.length);
|
|
2377
|
-
const body = close === -1 ? "" : content.slice(close + "\n---".length);
|
|
2378
|
-
return estimateTokens(body.trim().length);
|
|
2379
|
-
}
|
|
2380
|
-
function assetCostRows(assetIds, root = resolveBundleRoot()) {
|
|
2381
|
-
return assetIds.map((id) => ({
|
|
2382
|
-
id,
|
|
2383
|
-
descriptorTokens: assetDescriptorTokens(id, root),
|
|
2384
|
-
bodyTokens: assetBodyTokens(id, root)
|
|
2385
|
-
})).sort((a, b) => (b.bodyTokens ?? -1) - (a.bodyTokens ?? -1));
|
|
2386
|
-
}
|
|
2387
|
-
function makeResidentCost(base) {
|
|
2388
|
-
return {
|
|
2389
|
-
...base,
|
|
2390
|
-
contextFile: base.contextFile ?? "CLAUDE.md",
|
|
2391
|
-
perSkillDescriptor: base.perSkillDescriptor ?? {},
|
|
2392
|
-
total: base.rules + base.projectClaudeMd + base.skillDescriptors + base.agentDescriptors,
|
|
2393
|
-
directive: {
|
|
2394
|
-
items: base.items.rules + base.items.claudeMd,
|
|
2395
|
-
tokens: base.rules + base.projectClaudeMd
|
|
2396
|
-
},
|
|
2397
|
-
firing: {
|
|
2398
|
-
items: base.items.skills + base.items.agents,
|
|
2399
|
-
tokens: base.skillDescriptors + base.agentDescriptors
|
|
2400
|
-
}
|
|
2401
|
-
};
|
|
2402
|
-
}
|
|
2403
|
-
function fileTokens(path2) {
|
|
2404
|
-
return existsSync3(path2) ? estimateTokens(readFileSync4(path2, "utf8").trim().length) : 0;
|
|
2405
|
-
}
|
|
2406
|
-
function descriptorTokens(path2) {
|
|
2407
|
-
if (!existsSync3(path2)) return 0;
|
|
2408
|
-
const fm = extractFrontmatter(readFileSync4(path2, "utf8"));
|
|
2409
|
-
return fm === null ? 0 : estimateTokens(fm.length);
|
|
2410
|
-
}
|
|
2411
|
-
function anchorSurface(cli) {
|
|
2412
|
-
if (cli.includes("claude") || cli.length === 0) {
|
|
2413
|
-
return { label: "CLAUDE.md", files: 2, scaffold: "claude" };
|
|
2414
|
-
}
|
|
2415
|
-
return { label: "AGENTS.md", files: 1, scaffold: "agents-md" };
|
|
2416
|
-
}
|
|
2417
|
-
function residentCost(entries, root = resolveBundleRoot(), cli = ["claude"], contextFiles) {
|
|
2418
|
-
const tpl = (source) => join5(root, "templates", source);
|
|
2419
|
-
const measured = (e) => e.file ?? tpl(e.source);
|
|
2420
|
-
const perSkill = {};
|
|
2421
|
-
let rules = 0;
|
|
2422
|
-
let skillDescriptors = 0;
|
|
2423
|
-
let agentDescriptors = 0;
|
|
2424
|
-
let ruleItems = 0;
|
|
2425
|
-
let skillItems = 0;
|
|
2426
|
-
let agentItems = 0;
|
|
2427
|
-
for (const e of entries) {
|
|
2428
|
-
if (e.target.startsWith(".claude/rules/")) {
|
|
2429
|
-
rules += fileTokens(measured(e));
|
|
2430
|
-
ruleItems += 1;
|
|
2431
|
-
} else if (e.target.startsWith(".claude/agents/")) {
|
|
2432
|
-
agentDescriptors += descriptorTokens(measured(e));
|
|
2433
|
-
agentItems += 1;
|
|
2434
|
-
} else if (e.target.startsWith(".claude/skills/")) {
|
|
2435
|
-
const t = descriptorTokens(join5(measured(e), "SKILL.md"));
|
|
2436
|
-
skillDescriptors += t;
|
|
2437
|
-
skillItems += 1;
|
|
2438
|
-
const id = /\.claude\/skills\/([^/]+)/.exec(e.target)?.[1];
|
|
2439
|
-
if (id !== void 0) perSkill[id] = (perSkill[id] ?? 0) + t;
|
|
2440
|
-
}
|
|
2441
|
-
}
|
|
2442
|
-
const harnessAnchor = fileTokens(join5(root, "templates", "CLAUDE.md"));
|
|
2443
|
-
const surface = anchorSurface(cli);
|
|
2444
|
-
const projectScaffold = estimateTokens(
|
|
2445
|
-
withContinuousSkillsNote(
|
|
2446
|
-
renderFillScaffold(surface.scaffold),
|
|
2447
|
-
CONTINUOUS_SKILLS.map((s) => s.id)
|
|
2448
|
-
).trim().length
|
|
2449
|
-
);
|
|
2450
|
-
const onDisk = contextFiles?.map((f) => typeof f === "string" ? fileTokens(f) : estimateTokens(f.text.trim().length)).filter((t) => t > 0);
|
|
2451
|
-
const projectClaudeMd = onDisk === void 0 ? harnessAnchor + projectScaffold : onDisk.reduce((a, b) => a + b, 0);
|
|
2452
|
-
const claudeMdItems = onDisk !== void 0 ? onDisk.length : surface.files === 1 ? 1 : (harnessAnchor > 0 ? 1 : 0) + (projectScaffold > 0 ? 1 : 0);
|
|
2453
|
-
return makeResidentCost({
|
|
2454
|
-
rules,
|
|
2455
|
-
projectClaudeMd,
|
|
2456
|
-
skillDescriptors,
|
|
2457
|
-
agentDescriptors,
|
|
2458
|
-
perSkillDescriptor: perSkill,
|
|
2459
|
-
contextFile: surface.label,
|
|
2460
|
-
items: {
|
|
2461
|
-
rules: ruleItems,
|
|
2462
|
-
skills: skillItems,
|
|
2463
|
-
agents: agentItems,
|
|
2464
|
-
claudeMd: claudeMdItems,
|
|
2465
|
-
total: ruleItems + skillItems + agentItems + claudeMdItems
|
|
2466
|
-
}
|
|
2467
|
-
});
|
|
2468
|
-
}
|
|
2469
|
-
function summarizeContextCost(assetIds, root = resolveBundleRoot()) {
|
|
2470
|
-
let measuredTokens = 0;
|
|
2471
|
-
let measuredCount = 0;
|
|
2472
|
-
let unmeasuredCount = 0;
|
|
2473
|
-
for (const id of assetIds) {
|
|
2474
|
-
const tokens = assetDescriptorTokens(id, root);
|
|
2475
|
-
if (tokens === null) unmeasuredCount += 1;
|
|
2476
|
-
else {
|
|
2477
|
-
measuredTokens += tokens;
|
|
2478
|
-
measuredCount += 1;
|
|
2479
|
-
}
|
|
2480
|
-
}
|
|
2481
|
-
return { measuredTokens, measuredCount, unmeasuredCount };
|
|
2482
|
-
}
|
|
2483
|
-
function formatResidentCostLine(r, unmeasuredCount) {
|
|
2484
|
-
if (r.total === 0) return null;
|
|
2485
|
-
const rules = r.rulesInline ? `rules ${r.items.rules} inside ${r.contextFile}${r.rulesInline === "planned" ? " (planned count)" : ""}` : `rules ${r.items.rules} ~${r.rules}`;
|
|
2486
|
-
const note = r.contextNote ? ` ${r.contextNote}` : "";
|
|
2487
|
-
const parts = [
|
|
2488
|
-
`directives ${r.directive.items} ~${r.directive.tokens}`,
|
|
2489
|
-
`[${rules} \xB7 ${r.contextFile} ${r.items.claudeMd} ~${r.projectClaudeMd}${note}]`,
|
|
2490
|
-
`triggers ${r.firing.items} ~${r.firing.tokens}`,
|
|
2491
|
-
`[skills ${r.items.skills} ~${r.skillDescriptors} \xB7 agents ${r.items.agents} ~${r.agentDescriptors}]`
|
|
2492
|
-
].join(" \xB7 ");
|
|
2493
|
-
const external = unmeasuredCount > 0 ? ` \xB7 ${unmeasuredCount} external asset${unmeasuredCount === 1 ? "" : "s"} unmeasured` : "";
|
|
2494
|
-
return `session-start context cost: ${r.items.total} items resident \xB7 ~${r.total} tokens/session (${parts}${external})`;
|
|
2495
|
-
}
|
|
2496
|
-
var RESIDENT_ROWS = [
|
|
2497
|
-
{
|
|
2498
|
-
// ADR-083 — 축 소계를 **행으로** 낸다. 소계가 없으면 읽는 사람이 네 줄을 머리로 더해야
|
|
2499
|
-
// 하고, 그러면 "무엇을 줄여야 하나"가 표에서 안 보인다.
|
|
2500
|
-
label: "\uC9C0\uC2DC\uBB38",
|
|
2501
|
-
gap: 13,
|
|
2502
|
-
count: (r) => r.directive.items,
|
|
2503
|
-
tokens: (r) => r.directive.tokens
|
|
2504
|
-
},
|
|
2505
|
-
{ label: " rules", gap: 12, count: (r) => r.items.rules, tokens: (r) => r.rules },
|
|
2506
|
-
{
|
|
2507
|
-
// 한 행에 두 파일(앵커 + 스캐폴드). 개수 열이 2 를 뱉어 그 사실을 드러낸다 — 라벨로
|
|
2508
|
-
// 하나만 이름 붙이면 v26.140.0 이전과 같은 거짓 라벨이 된다.
|
|
2509
|
-
label: " CLAUDE.md",
|
|
2510
|
-
gap: 8,
|
|
2511
|
-
count: (r) => r.items.claudeMd,
|
|
2512
|
-
tokens: (r) => r.projectClaudeMd
|
|
2513
|
-
},
|
|
2514
|
-
{
|
|
2515
|
-
label: "\uBC1C\uD654 \uD45C\uBA74",
|
|
2516
|
-
gap: 10,
|
|
2517
|
-
count: (r) => r.firing.items,
|
|
2518
|
-
tokens: (r) => r.firing.tokens
|
|
2519
|
-
},
|
|
2520
|
-
{
|
|
2521
|
-
label: " skill descriptors",
|
|
2522
|
-
gap: 0,
|
|
2523
|
-
count: (r) => r.items.skills,
|
|
2524
|
-
tokens: (r) => r.skillDescriptors
|
|
2525
|
-
},
|
|
2526
|
-
{
|
|
2527
|
-
label: " agent descriptors",
|
|
2528
|
-
gap: 0,
|
|
2529
|
-
count: (r) => r.items.agents,
|
|
2530
|
-
tokens: (r) => r.agentDescriptors
|
|
2531
|
-
}
|
|
2532
|
-
];
|
|
2533
|
-
function formatResidentCostBlock(r) {
|
|
2534
|
-
const n = (v) => String(v).padStart(3);
|
|
2535
|
-
return [
|
|
2536
|
-
" \uAC1C\uC218 \uD1A0\uD070",
|
|
2537
|
-
...RESIDENT_ROWS.map(
|
|
2538
|
-
(row) => ` ${row.label}${" ".repeat(row.gap)}${n(row.count(r))}\uAC1C ~${row.tokens(r)}`
|
|
2539
|
-
),
|
|
2540
|
-
" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
|
|
2541
|
-
` \uC0C1\uC8FC \uD569\uACC4 ${n(r.items.total)}\uAC1C \uC0C1\uC8FC \xB7 ~${r.total} tokens/\uC138\uC158`,
|
|
2542
|
-
"",
|
|
2543
|
-
" \uC9C0\uC2DC\uBB38 = \uC6B0\uB9AC\uAC00 \uD56D\uC0C1 \uC77D\uD788\uAC8C \uB123\uC740 \uAC83. **\uC791\uC744\uC218\uB85D \uC88B\uC740 \uAC12\uC774 \uC544\uB2C8\uB2E4** \u2014 \uD310\uB2E8\uC740",
|
|
2544
|
-
" '\uBD88\uD544\uC694\uD55C \uC9C0\uC2DC\uBB38\uC774 \uC788\uB098'\uB85C \uD55C\uB2E4. ratchet \uC740 \uC870\uC6A9\uD55C \uC99D\uAC00\uB9CC \uB9C9\uB294\uB2E4.",
|
|
2545
|
-
" \uBC1C\uD654 \uD45C\uBA74 = \uC2A4\uD0AC\xB7\uC5D0\uC774\uC804\uD2B8\uAC00 \uBD88\uB9AC\uB824\uACE0 \uC0C1\uC8FC\uC2DC\uD0A4\uB294 descriptor. \uAE4E\uC73C\uBA74 \uC548 \uBD88\uB9B0\uB2E4."
|
|
2546
|
-
];
|
|
2547
|
-
}
|
|
2548
|
-
|
|
2549
|
-
// src/categories.ts
|
|
2550
|
-
init_esm_shims();
|
|
2551
|
-
var CATEGORIES = [
|
|
2552
|
-
"frontend",
|
|
2553
|
-
"backend",
|
|
2554
|
-
"data",
|
|
2555
|
-
"business",
|
|
2556
|
-
"dev-tools",
|
|
2557
|
-
"understanding",
|
|
2558
|
-
"visual-media",
|
|
2559
|
-
"workflow"
|
|
2560
|
-
];
|
|
2561
|
-
var CATEGORY_TITLES = {
|
|
2562
|
-
frontend: "\u{1F3A8} Frontend (UI \xB7 Design)",
|
|
2563
|
-
backend: "\u{1F5C4}\uFE0F Backend (API \xB7 DB \xB7 Deploy)",
|
|
2564
|
-
data: "\u{1F4CA} Data",
|
|
2565
|
-
business: "\u{1F4BC} Business (Documents)",
|
|
2566
|
-
"dev-tools": "\u{1F6E1}\uFE0F Dev Tools (Security \xB7 Quality)",
|
|
2567
|
-
// v26.78.0 — 에이전트 인지 증강: 환경에 대한 지각(영상·웹·코드) + 기억(memory).
|
|
2568
|
-
understanding: "\u{1F9E0} Understanding (Perception \xB7 Memory)",
|
|
2569
|
-
// v26.85.0 — 코드-퍼스트 비주얼/미디어 제작: 슬라이드·다이어그램·모션·동영상.
|
|
2570
|
-
"visual-media": "\u{1F3AC} Visual & Media (Slides \xB7 Diagrams \xB7 Motion \xB7 Video)",
|
|
2571
|
-
workflow: "\u{1F504} Workflow (Development Cycle)"
|
|
2572
|
-
};
|
|
2573
|
-
|
|
2574
|
-
export {
|
|
2575
|
-
__commonJS,
|
|
2576
|
-
__toESM,
|
|
2577
|
-
init_esm_shims,
|
|
2578
|
-
package_default,
|
|
2579
|
-
SHARED_FILES,
|
|
2580
|
-
isKeyId,
|
|
2581
|
-
sharedPathOfKeyId,
|
|
2582
|
-
TRACKS,
|
|
2583
|
-
isTrack,
|
|
2584
|
-
CLI_BASES,
|
|
2585
|
-
isCliBase,
|
|
2586
|
-
isInstallScope,
|
|
2587
|
-
resolveScope,
|
|
2588
|
-
DEFAULT_OPTIONS,
|
|
2589
|
-
UPDATE_GROUPS,
|
|
2590
|
-
CLI_BASE_SORT_ORDER,
|
|
2591
|
-
CLI_DISPLAY_NAMES,
|
|
2592
|
-
parseCliTargets,
|
|
2593
|
-
targetsInclude,
|
|
2594
|
-
anyTrack,
|
|
2595
|
-
hasUiTrack,
|
|
2596
|
-
EXTERNAL_ASSETS,
|
|
2597
|
-
REMOVED_CATALOG_ASSETS,
|
|
2598
|
-
DEV_METHOD_SKILL_IDS,
|
|
2599
|
-
INTERNAL_BUNDLED_SKILL_IDS,
|
|
2600
|
-
CONTINUOUS_SKILLS,
|
|
2601
|
-
RENAMED_SKILL_IDS,
|
|
2602
|
-
RETIRED_SKILL_IDS,
|
|
2603
|
-
TRUST_TIER,
|
|
2604
|
-
assetTrustTier,
|
|
2605
|
-
shouldInstallAsset,
|
|
2606
|
-
isAssetSelected,
|
|
2607
|
-
experimentalOptInCandidates,
|
|
2608
|
-
assetCliSupport,
|
|
2609
|
-
assetReachesCli,
|
|
2610
|
-
filterApplicableAssets,
|
|
2611
|
-
isOutsideProject,
|
|
2612
|
-
outsideProjectTarget,
|
|
2613
|
-
outsideLinkAt,
|
|
2614
|
-
createOutsideGuard,
|
|
2615
|
-
mergeOutside,
|
|
2616
|
-
ensureDir,
|
|
2617
|
-
copyFile,
|
|
2618
|
-
backupFile,
|
|
2619
|
-
backupFromCopy,
|
|
2620
|
-
claimBackupDir,
|
|
2621
|
-
backupOriginal,
|
|
2622
|
-
listFilesRecursive,
|
|
2623
|
-
PROJECT_SKELETON_DIRS,
|
|
2624
|
-
ensureProjectSkeleton,
|
|
2625
|
-
backupIfLossyUtf8,
|
|
2626
|
-
HARNESS_ANCHOR_FILE,
|
|
2627
|
-
HARNESS_IMPORT_LINE,
|
|
2628
|
-
CONTINUOUS_SKILLS_HEADING,
|
|
2629
|
-
renderContinuousSkillsNote,
|
|
2630
|
-
scaffoldBanner,
|
|
2631
|
-
renderFillScaffold,
|
|
2632
|
-
dropHarnessQuoteBlocks,
|
|
2633
|
-
continuousNoteEnd,
|
|
2634
|
-
dropContinuousSkillsNote,
|
|
2635
|
-
isUnfilledScaffold,
|
|
2636
|
-
upsertHarnessImport,
|
|
2637
|
-
stripHarnessImport,
|
|
2638
|
-
buildAssetSpec,
|
|
2639
|
-
resolveRules,
|
|
2640
|
-
TRACK_AGENTS,
|
|
2641
|
-
RETIRED_AGENTS,
|
|
2642
|
-
RETIRED_PATHS,
|
|
2643
|
-
RETIRED_HOOK_WIRING_REFS,
|
|
2644
|
-
RETIRED_AGENT_IDS,
|
|
2645
|
-
isCliNeutralTarget,
|
|
2646
|
-
buildManifest,
|
|
2647
|
-
memoTargets,
|
|
2648
|
-
claudeTargets,
|
|
2649
|
-
claudeRecorded,
|
|
2650
|
-
INSTALL_LOG_FILENAME,
|
|
2651
|
-
INSTALL_LOG_DIR,
|
|
2652
|
-
LEGACY_INSTALL_LOG_DIR,
|
|
2653
|
-
POLICY_DIRS,
|
|
2654
|
-
installedClis,
|
|
2655
|
-
buildAssetEntries,
|
|
2656
|
-
buildInstallLog,
|
|
2657
|
-
mergeRootFiles,
|
|
2658
|
-
hashContent,
|
|
2659
|
-
mergeExternalFiles,
|
|
2660
|
-
writeInstallLog,
|
|
2661
|
-
readInstallLog,
|
|
2662
|
-
readInstallLogStatus,
|
|
2663
|
-
legacyDroppedKeys,
|
|
2664
|
-
legacyReleasedCatalog,
|
|
2665
|
-
HARNESS_VERSION,
|
|
2666
|
-
appendSelection,
|
|
2667
|
-
corruptedInstallLogMessage,
|
|
2668
|
-
installLogPath,
|
|
2669
|
-
legacyInstallLogPath,
|
|
2670
|
-
estimateTokens,
|
|
2671
|
-
resolveBundleRoot,
|
|
2672
|
-
extractFrontmatter,
|
|
2673
|
-
assetCostRows,
|
|
2674
|
-
makeResidentCost,
|
|
2675
|
-
anchorSurface,
|
|
2676
|
-
residentCost,
|
|
2677
|
-
summarizeContextCost,
|
|
2678
|
-
formatResidentCostLine,
|
|
2679
|
-
formatResidentCostBlock,
|
|
2680
|
-
CATEGORIES,
|
|
2681
|
-
CATEGORY_TITLES
|
|
2682
|
-
};
|
|
2683
|
-
//# sourceMappingURL=chunk-LAWKDFKZ.js.map
|