@uzysjung/agent-harness 26.152.0 → 26.154.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-EQDC2AAU.js → chunk-242TVGMZ.js} +582 -556
- package/dist/chunk-242TVGMZ.js.map +1 -0
- package/dist/index.js +623 -303
- package/dist/index.js.map +1 -1
- package/dist/trust-tier-drift.js +1 -1
- package/package.json +1 -1
- package/templates/CLAUDE.md +93 -145
- package/templates/codex/README.md +6 -7
- package/templates/codex/config.toml.template +1 -9
- package/templates/skills/audit-harness-fit/README.md +76 -43
- package/templates/skills/audit-harness-fit/SKILL.md +62 -47
- package/templates/skills/audit-harness-fit/evals/scenarios.yaml +179 -22
- package/templates/skills/audit-harness-fit/references/apply.md +74 -45
- package/templates/skills/audit-harness-fit/references/audit.md +142 -118
- package/templates/skills/audit-harness-fit/references/populate.md +53 -49
- package/templates/skills/audit-harness-fit/references/verification.md +131 -100
- package/templates/skills/clear-korean-communication/SKILL.md +76 -209
- package/templates/skills/external-model-consult/scripts/codex-ask.sh +9 -4
- package/templates/skills/external-model-consult/scripts/gemini-ask.sh +9 -4
- package/templates/skills/model-orchestration/SKILL.md +123 -267
- package/templates/skills/objective-brief/SKILL.md +3 -3
- package/dist/chunk-EQDC2AAU.js.map +0 -1
- package/templates/codex/hooks/README.md +0 -37
- package/templates/codex/hooks/session-start.sh +0 -7
- package/templates/codex/hooks/uncommitted-check.sh +0 -7
- package/templates/skills/clear-korean-communication/references/pre-send-checklist.md +0 -37
- package/templates/skills/clear-korean-communication/references/why-it-works.md +0 -24
- package/templates/skills/clear-korean-communication/references/worked-examples.md +0 -89
|
@@ -70,6 +70,14 @@ var DEFAULT_OPTIONS = {
|
|
|
70
70
|
withPrune: false,
|
|
71
71
|
withCodexTrust: false
|
|
72
72
|
};
|
|
73
|
+
var UPDATE_GROUPS = [
|
|
74
|
+
"skills",
|
|
75
|
+
"new-skills",
|
|
76
|
+
"rules",
|
|
77
|
+
"anchor",
|
|
78
|
+
"hooks",
|
|
79
|
+
"external"
|
|
80
|
+
];
|
|
73
81
|
|
|
74
82
|
// src/external-assets.ts
|
|
75
83
|
init_esm_shims();
|
|
@@ -275,7 +283,7 @@ var EXTERNAL_ASSETS = [
|
|
|
275
283
|
id: "audit-harness-fit",
|
|
276
284
|
tier: "official",
|
|
277
285
|
// uzys 자사 스킬
|
|
278
|
-
description: "Audit harness fit \u2014
|
|
286
|
+
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",
|
|
279
287
|
category: "workflow",
|
|
280
288
|
source: "uzys",
|
|
281
289
|
condition: { kind: "any-track", tracks: [...TRACKS] },
|
|
@@ -516,7 +524,10 @@ var EXTERNAL_ASSETS = [
|
|
|
516
524
|
condition: { kind: "opt-in" },
|
|
517
525
|
// 리포에 스킬 14종(brandkit·brutalist·minimalist 등)이 있고 본체가 `taste-skill` —
|
|
518
526
|
// 스타일 변형은 그 안에서 고른다. 다른 단일 대표 자산(remotion)과 같은 형태.
|
|
519
|
-
|
|
527
|
+
// 2026-09-20 (#420) — 업스트림이 SKILL.md 의 `name:` 을 `design-taste-frontend` 로 바꿨다. `npx skills
|
|
528
|
+
// add --skill` 은 디렉터리명이 아니라 **frontmatter name** 으로 맞춘다(실측: 옛 이름은 "No matching
|
|
529
|
+
// skills found", 새 이름은 설치 완료). 디렉터리는 여전히 `skills/taste-skill/` 이다.
|
|
530
|
+
method: { kind: "skill", source: "Leonxlnx/taste-skill", skill: "design-taste-frontend" }
|
|
520
531
|
},
|
|
521
532
|
{
|
|
522
533
|
id: "scroll-world",
|
|
@@ -531,10 +542,10 @@ var EXTERNAL_ASSETS = [
|
|
|
531
542
|
// 2026-08-16 — Preline 은 Tailwind 컴포넌트 킷이지만 **프레임워크에 매이지 않는다**: htmx·
|
|
532
543
|
// vanilla 에도 붙는다(사용자 확인). 그래서 `frontend` 이면서 React/Vue 트랙 전용이 아니다.
|
|
533
544
|
//
|
|
534
|
-
// 리포 안에 스킬은 `skills/theme-generator/`
|
|
535
|
-
//
|
|
536
|
-
//
|
|
537
|
-
//
|
|
545
|
+
// 리포 안에 테마 스킬은 `skills/theme-generator/` 하나다. 2026-08-16 실측에서는 `npx skills add`
|
|
546
|
+
// 가 디렉터리명(`theme-generator`)으로 맞췄는데, 2026-09-01 부터 그 이름이 "No matching skills
|
|
547
|
+
// found" 가 됐다(#420) — 지금은 SKILL.md 의 `name:`(`preline-theme-generator`)으로 맞춘다(실측
|
|
548
|
+
// 2026-09-20: 옛 이름 exit 1 · 새 이름 설치 완료). 이름이 바뀌면 월 1회 catalog-verify 가 잡는다.
|
|
538
549
|
//
|
|
539
550
|
// 같은 날 조사한 flowbite 는 **넣지 않는다**: 에이전트용 제공물이 MCP 서버뿐인데, 이 저장소의
|
|
540
551
|
// `.mcp.json` 조립은 트랙 조건만 읽어 opt-in 경로가 없다 — 넣으면 해당 트랙 전원에게 항상
|
|
@@ -547,7 +558,11 @@ var EXTERNAL_ASSETS = [
|
|
|
547
558
|
category: "frontend",
|
|
548
559
|
source: "htmlstreamofficial",
|
|
549
560
|
condition: { kind: "opt-in" },
|
|
550
|
-
method: {
|
|
561
|
+
method: {
|
|
562
|
+
kind: "skill",
|
|
563
|
+
source: "htmlstreamofficial/preline",
|
|
564
|
+
skill: "preline-theme-generator"
|
|
565
|
+
}
|
|
551
566
|
},
|
|
552
567
|
// === dev tools (has_dev_track) ===
|
|
553
568
|
// v26.110.0 (ADR-039) — code-review: /code-review 커맨드 1개 (다중 에이전트 confidence
|
|
@@ -1113,135 +1128,512 @@ function filterApplicableAssets(assets, ctx) {
|
|
|
1113
1128
|
return assets.filter((a) => shouldInstallAsset(a, ctx));
|
|
1114
1129
|
}
|
|
1115
1130
|
|
|
1116
|
-
// src/
|
|
1131
|
+
// src/context-cost.ts
|
|
1117
1132
|
init_esm_shims();
|
|
1133
|
+
import { existsSync, readFileSync } from "fs";
|
|
1134
|
+
import { join, resolve } from "path";
|
|
1135
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1118
1136
|
|
|
1119
|
-
// src/
|
|
1137
|
+
// src/project-claude-merge.ts
|
|
1120
1138
|
init_esm_shims();
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1139
|
+
var HARNESS_ANCHOR_FILE = "CLAUDE-uzys-harness.md";
|
|
1140
|
+
var HARNESS_IMPORT_LINE = `@${HARNESS_ANCHOR_FILE}`;
|
|
1141
|
+
var IMPORT_MARKER_START = "<!-- uzys-harness:import:start -->";
|
|
1142
|
+
var IMPORT_MARKER_END = "<!-- uzys-harness:import:end -->";
|
|
1143
|
+
function renderContinuousSkillsNote(selectedInternalSkills) {
|
|
1144
|
+
const lines = CONTINUOUS_SKILLS.filter((s) => selectedInternalSkills.includes(s.id)).map(
|
|
1145
|
+
(s) => `- \`${s.id}\` \u2014 ${s.whenToApply}.`
|
|
1146
|
+
);
|
|
1147
|
+
if (lines.length === 0) return "";
|
|
1148
|
+
return [
|
|
1149
|
+
"## Skills that apply continuously",
|
|
1150
|
+
"",
|
|
1151
|
+
"These installed skills apply to every response or delegation; no prompt ever looks like their job, so open them from this list:",
|
|
1152
|
+
"",
|
|
1153
|
+
...lines
|
|
1154
|
+
].join("\n");
|
|
1155
|
+
}
|
|
1156
|
+
function withContinuousSkillsNote(scaffold, selectedInternalSkills) {
|
|
1157
|
+
const note = renderContinuousSkillsNote(selectedInternalSkills);
|
|
1158
|
+
return note ? `${scaffold}
|
|
1124
1159
|
|
|
1125
|
-
|
|
1126
|
-
init_esm_shims();
|
|
1127
|
-
import {
|
|
1128
|
-
copyFileSync,
|
|
1129
|
-
cpSync,
|
|
1130
|
-
existsSync,
|
|
1131
|
-
constants as fsConstants,
|
|
1132
|
-
mkdirSync,
|
|
1133
|
-
readdirSync,
|
|
1134
|
-
readFileSync,
|
|
1135
|
-
realpathSync,
|
|
1136
|
-
renameSync,
|
|
1137
|
-
rmdirSync
|
|
1138
|
-
} from "fs";
|
|
1139
|
-
import { dirname, join } from "path";
|
|
1140
|
-
function ensureDir(path2) {
|
|
1141
|
-
mkdirSync(path2, { recursive: true });
|
|
1160
|
+
${note}` : scaffold;
|
|
1142
1161
|
}
|
|
1143
|
-
function
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1162
|
+
function importBlock(continuousSkills) {
|
|
1163
|
+
const note = renderContinuousSkillsNote(continuousSkills);
|
|
1164
|
+
const body = note ? `${HARNESS_IMPORT_LINE}
|
|
1165
|
+
|
|
1166
|
+
${note}` : HARNESS_IMPORT_LINE;
|
|
1167
|
+
return `${IMPORT_MARKER_START}
|
|
1168
|
+
${body}
|
|
1169
|
+
${IMPORT_MARKER_END}`;
|
|
1149
1170
|
}
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1171
|
+
var TRACK_DISPLAY_NAMES = {
|
|
1172
|
+
tooling: "Tooling",
|
|
1173
|
+
"csr-supabase": "CSR Supabase",
|
|
1174
|
+
"csr-fastify": "CSR Fastify",
|
|
1175
|
+
"csr-fastapi": "CSR FastAPI",
|
|
1176
|
+
"ssr-htmx": "SSR HTMX",
|
|
1177
|
+
"ssr-nextjs": "SSR Next.js",
|
|
1178
|
+
data: "Data",
|
|
1179
|
+
executive: "Executive",
|
|
1180
|
+
full: "Full",
|
|
1181
|
+
"project-management": "Project Management",
|
|
1182
|
+
"growth-marketing": "Growth Marketing"
|
|
1183
|
+
};
|
|
1184
|
+
var FULL_EXPANSION = TRACKS.filter((t) => t !== "full");
|
|
1185
|
+
var FILL_SECTIONS = [
|
|
1186
|
+
"identity",
|
|
1187
|
+
"stack",
|
|
1188
|
+
"architecture",
|
|
1189
|
+
"installed-assets",
|
|
1190
|
+
"boundaries",
|
|
1191
|
+
"verify"
|
|
1192
|
+
];
|
|
1193
|
+
var FILL_SPECS = {
|
|
1194
|
+
identity: {
|
|
1195
|
+
title: "Identity & Purpose",
|
|
1196
|
+
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.`,
|
|
1197
|
+
placeholder: "what this project is, who it is for, and why it exists"
|
|
1198
|
+
},
|
|
1199
|
+
stack: {
|
|
1200
|
+
title: "Stack & Commands",
|
|
1201
|
+
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.",
|
|
1202
|
+
placeholder: "languages, runtimes, package manager, and the install/build/test/run commands"
|
|
1203
|
+
},
|
|
1204
|
+
architecture: {
|
|
1205
|
+
title: "Architecture & Layout",
|
|
1206
|
+
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.",
|
|
1207
|
+
placeholder: "where things live, the entry points, and how data flows between layers"
|
|
1208
|
+
},
|
|
1209
|
+
"installed-assets": {
|
|
1210
|
+
title: "Installed Harness Assets",
|
|
1211
|
+
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 .claude/rules/*.md, .claude/skills/, .claude/agents/, .claude/commands/ (or this CLI's equivalent) 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.",
|
|
1212
|
+
placeholder: "the installed rules/skills/agents/commands and when to use each"
|
|
1213
|
+
},
|
|
1214
|
+
boundaries: {
|
|
1215
|
+
title: "Boundaries \u2014 Always / Ask First / Never",
|
|
1216
|
+
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.`,
|
|
1217
|
+
placeholder: "what to always do, ask before doing, and never do in this repo"
|
|
1218
|
+
},
|
|
1219
|
+
verify: {
|
|
1220
|
+
title: "Verification Gate",
|
|
1221
|
+
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.',
|
|
1222
|
+
placeholder: "the single command that proves a change is safe, and the done bar"
|
|
1153
1223
|
}
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
if (foreign !== null) {
|
|
1159
|
-
skipped.push(foreign);
|
|
1160
|
-
continue;
|
|
1161
|
-
}
|
|
1162
|
-
copyFile(join(source, rel), join(target, rel));
|
|
1224
|
+
};
|
|
1225
|
+
function anchorLine(surface) {
|
|
1226
|
+
if (surface === "claude") {
|
|
1227
|
+
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**.`;
|
|
1163
1228
|
}
|
|
1164
|
-
|
|
1229
|
+
const self = surface === "antigravity-rule" ? "`.agents/rules/uzys-harness.md`" : "`AGENTS.md`";
|
|
1230
|
+
return `> **This file (${self}) 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.`;
|
|
1165
1231
|
}
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
return candidate;
|
|
1173
|
-
} catch (err) {
|
|
1174
|
-
if (err.code !== "EEXIST") throw err;
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
throw new Error(`Cannot allocate a backup path for ${base} (${MAX_BACKUP_CLAIMS} taken)`);
|
|
1232
|
+
function scaffoldBanner(surface) {
|
|
1233
|
+
return [
|
|
1234
|
+
"> \u2699\uFE0F **SCAFFOLD \u2014 not filled in yet.** The sections below are a fill-in template for THIS project, not verified facts.",
|
|
1235
|
+
"> 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.",
|
|
1236
|
+
anchorLine(surface)
|
|
1237
|
+
].join("\n");
|
|
1178
1238
|
}
|
|
1179
|
-
function
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1239
|
+
function renderFillScaffold(surface = "claude") {
|
|
1240
|
+
const blocks = FILL_SECTIONS.map((id) => {
|
|
1241
|
+
const spec = FILL_SPECS[id];
|
|
1242
|
+
return `## ${spec.title}
|
|
1243
|
+
|
|
1244
|
+
<!-- FILL:${id} \u2014 ${spec.prompt} -->
|
|
1245
|
+
|
|
1246
|
+
_(not filled yet \u2014 ${spec.placeholder})_`;
|
|
1247
|
+
});
|
|
1248
|
+
return `${scaffoldBanner(surface)}
|
|
1249
|
+
|
|
1250
|
+
${blocks.join("\n\n")}`;
|
|
1187
1251
|
}
|
|
1188
|
-
function
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
return
|
|
1252
|
+
function mergeProjectClaude(opts) {
|
|
1253
|
+
const expanded = expandTracks(opts.tracks);
|
|
1254
|
+
const trackList = expanded.map((t) => TRACK_DISPLAY_NAMES[t]).join(", ");
|
|
1255
|
+
const header = `# ${opts.projectName}
|
|
1256
|
+
|
|
1257
|
+
> Active track(s): ${trackList}`;
|
|
1258
|
+
return `${header}
|
|
1259
|
+
|
|
1260
|
+
${renderFillScaffold()}
|
|
1261
|
+
`;
|
|
1195
1262
|
}
|
|
1196
|
-
function
|
|
1197
|
-
|
|
1198
|
-
|
|
1263
|
+
function upsertHarnessImport(existing, opts) {
|
|
1264
|
+
const block = importBlock(opts.continuousSkills ?? []);
|
|
1265
|
+
if (existing === null) {
|
|
1266
|
+
return `${mergeProjectClaude(opts)}
|
|
1267
|
+
${block}
|
|
1268
|
+
`;
|
|
1199
1269
|
}
|
|
1200
|
-
|
|
1201
|
-
|
|
1270
|
+
const start = existing.indexOf(IMPORT_MARKER_START);
|
|
1271
|
+
if (start !== -1) {
|
|
1272
|
+
const endAt = existing.indexOf(IMPORT_MARKER_END, start);
|
|
1273
|
+
if (endAt === -1) return existing;
|
|
1274
|
+
const current = existing.slice(start, endAt + IMPORT_MARKER_END.length);
|
|
1275
|
+
if (current === block) return existing;
|
|
1276
|
+
return `${existing.slice(0, start)}${block}${existing.slice(endAt + IMPORT_MARKER_END.length)}`;
|
|
1202
1277
|
}
|
|
1203
|
-
|
|
1278
|
+
if (hasHarnessImport(existing)) {
|
|
1279
|
+
return existing;
|
|
1280
|
+
}
|
|
1281
|
+
const body = existing.endsWith("\n") ? existing : `${existing}
|
|
1282
|
+
`;
|
|
1283
|
+
return `${body}
|
|
1284
|
+
${block}
|
|
1285
|
+
`;
|
|
1204
1286
|
}
|
|
1205
|
-
function
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
);
|
|
1287
|
+
function stripHarnessImport(text) {
|
|
1288
|
+
const start = text.indexOf(IMPORT_MARKER_START);
|
|
1289
|
+
if (start === -1) return null;
|
|
1290
|
+
const endAt = text.indexOf(IMPORT_MARKER_END, start);
|
|
1291
|
+
if (endAt === -1) return null;
|
|
1292
|
+
const before = text.slice(0, start).replace(/\n+$/, "\n");
|
|
1293
|
+
const after = text.slice(endAt + IMPORT_MARKER_END.length).replace(/^\n+/, "");
|
|
1294
|
+
return `${before}${after}`;
|
|
1210
1295
|
}
|
|
1211
|
-
function
|
|
1212
|
-
if (
|
|
1213
|
-
|
|
1214
|
-
for (const
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
} else if (entry.isFile()) {
|
|
1219
|
-
out.push(rel);
|
|
1296
|
+
function hasHarnessImport(text) {
|
|
1297
|
+
if (text.includes(IMPORT_MARKER_START)) return true;
|
|
1298
|
+
let inFence = false;
|
|
1299
|
+
for (const line of text.split("\n")) {
|
|
1300
|
+
if (/^\s*(?:```|~~~)/.test(line)) {
|
|
1301
|
+
inFence = !inFence;
|
|
1302
|
+
continue;
|
|
1220
1303
|
}
|
|
1304
|
+
if (!inFence && line.trim() === HARNESS_IMPORT_LINE) return true;
|
|
1221
1305
|
}
|
|
1222
|
-
return
|
|
1223
|
-
}
|
|
1224
|
-
function formatStamp(now) {
|
|
1225
|
-
return now.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z").slice(0, 15);
|
|
1306
|
+
return false;
|
|
1226
1307
|
}
|
|
1227
|
-
function
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
".claude/rules",
|
|
1231
|
-
".claude/skills",
|
|
1232
|
-
".claude/agents",
|
|
1233
|
-
".claude/hooks",
|
|
1234
|
-
"docs/decisions"
|
|
1235
|
-
];
|
|
1236
|
-
for (const d of dirs) {
|
|
1237
|
-
mkdirSync(join(projectDir, d), { recursive: true });
|
|
1308
|
+
function expandTracks(tracks) {
|
|
1309
|
+
if (tracks.includes("full")) {
|
|
1310
|
+
return FULL_EXPANSION;
|
|
1238
1311
|
}
|
|
1312
|
+
return tracks;
|
|
1239
1313
|
}
|
|
1240
1314
|
|
|
1241
|
-
// src/
|
|
1242
|
-
var
|
|
1243
|
-
|
|
1244
|
-
|
|
1315
|
+
// src/context-cost.ts
|
|
1316
|
+
var CHARS_PER_TOKEN = 4;
|
|
1317
|
+
function estimateTokens(chars) {
|
|
1318
|
+
return Math.ceil(chars / CHARS_PER_TOKEN);
|
|
1319
|
+
}
|
|
1320
|
+
function resolveBundleRoot() {
|
|
1321
|
+
return resolve(fileURLToPath2(new URL(".", import.meta.url)), "..");
|
|
1322
|
+
}
|
|
1323
|
+
function extractFrontmatter(content) {
|
|
1324
|
+
const m = content.match(/^?---\r?\n([\s\S]*?)\r?\n---/);
|
|
1325
|
+
return m?.[1] ?? null;
|
|
1326
|
+
}
|
|
1327
|
+
function assetDescriptorTokens(assetId, root = resolveBundleRoot()) {
|
|
1328
|
+
const asset = EXTERNAL_ASSETS.find((a) => a.id === assetId);
|
|
1329
|
+
if (!asset || asset.method.kind !== "internal") return null;
|
|
1330
|
+
const skillMd = join(root, "templates", "skills", asset.method.key, "SKILL.md");
|
|
1331
|
+
if (!existsSync(skillMd)) return null;
|
|
1332
|
+
const fm = extractFrontmatter(readFileSync(skillMd, "utf8"));
|
|
1333
|
+
return fm === null ? null : estimateTokens(fm.length);
|
|
1334
|
+
}
|
|
1335
|
+
function assetBodyTokens(assetId, root = resolveBundleRoot()) {
|
|
1336
|
+
const asset = EXTERNAL_ASSETS.find((a) => a.id === assetId);
|
|
1337
|
+
if (!asset || asset.method.kind !== "internal") return null;
|
|
1338
|
+
const skillMd = join(root, "templates", "skills", asset.method.key, "SKILL.md");
|
|
1339
|
+
if (!existsSync(skillMd)) return null;
|
|
1340
|
+
const content = readFileSync(skillMd, "utf8");
|
|
1341
|
+
const fm = extractFrontmatter(content);
|
|
1342
|
+
if (fm === null) return estimateTokens(content.length);
|
|
1343
|
+
const close = content.indexOf("\n---", content.indexOf(fm) + fm.length);
|
|
1344
|
+
const body = close === -1 ? "" : content.slice(close + "\n---".length);
|
|
1345
|
+
return estimateTokens(body.trim().length);
|
|
1346
|
+
}
|
|
1347
|
+
function assetCostRows(assetIds, root = resolveBundleRoot()) {
|
|
1348
|
+
return assetIds.map((id) => ({
|
|
1349
|
+
id,
|
|
1350
|
+
descriptorTokens: assetDescriptorTokens(id, root),
|
|
1351
|
+
bodyTokens: assetBodyTokens(id, root)
|
|
1352
|
+
})).sort((a, b) => (b.bodyTokens ?? -1) - (a.bodyTokens ?? -1));
|
|
1353
|
+
}
|
|
1354
|
+
function makeResidentCost(base) {
|
|
1355
|
+
return {
|
|
1356
|
+
...base,
|
|
1357
|
+
perSkillDescriptor: base.perSkillDescriptor ?? {},
|
|
1358
|
+
total: base.rules + base.projectClaudeMd + base.skillDescriptors + base.agentDescriptors,
|
|
1359
|
+
directive: {
|
|
1360
|
+
items: base.items.rules + base.items.claudeMd,
|
|
1361
|
+
tokens: base.rules + base.projectClaudeMd
|
|
1362
|
+
},
|
|
1363
|
+
firing: {
|
|
1364
|
+
items: base.items.skills + base.items.agents,
|
|
1365
|
+
tokens: base.skillDescriptors + base.agentDescriptors
|
|
1366
|
+
}
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
function fileTokens(path2) {
|
|
1370
|
+
return existsSync(path2) ? estimateTokens(readFileSync(path2, "utf8").trim().length) : 0;
|
|
1371
|
+
}
|
|
1372
|
+
function descriptorTokens(path2) {
|
|
1373
|
+
if (!existsSync(path2)) return 0;
|
|
1374
|
+
const fm = extractFrontmatter(readFileSync(path2, "utf8"));
|
|
1375
|
+
return fm === null ? 0 : estimateTokens(fm.length);
|
|
1376
|
+
}
|
|
1377
|
+
function landsOnDisk(target, cli) {
|
|
1378
|
+
if (target.startsWith(".claude/agents/")) return cli.includes("claude");
|
|
1379
|
+
return true;
|
|
1380
|
+
}
|
|
1381
|
+
function residentCost(entries, root = resolveBundleRoot()) {
|
|
1382
|
+
const tpl = (source) => join(root, "templates", source);
|
|
1383
|
+
const measured = (e) => e.file ?? tpl(e.source);
|
|
1384
|
+
const perSkill = {};
|
|
1385
|
+
let rules = 0;
|
|
1386
|
+
let skillDescriptors = 0;
|
|
1387
|
+
let agentDescriptors = 0;
|
|
1388
|
+
let ruleItems = 0;
|
|
1389
|
+
let skillItems = 0;
|
|
1390
|
+
let agentItems = 0;
|
|
1391
|
+
for (const e of entries) {
|
|
1392
|
+
if (e.target.startsWith(".claude/rules/")) {
|
|
1393
|
+
rules += fileTokens(measured(e));
|
|
1394
|
+
ruleItems += 1;
|
|
1395
|
+
} else if (e.target.startsWith(".claude/agents/")) {
|
|
1396
|
+
agentDescriptors += descriptorTokens(measured(e));
|
|
1397
|
+
agentItems += 1;
|
|
1398
|
+
} else if (e.target.startsWith(".claude/skills/")) {
|
|
1399
|
+
const t = descriptorTokens(join(measured(e), "SKILL.md"));
|
|
1400
|
+
skillDescriptors += t;
|
|
1401
|
+
skillItems += 1;
|
|
1402
|
+
const id = /\.claude\/skills\/([^/]+)/.exec(e.target)?.[1];
|
|
1403
|
+
if (id !== void 0) perSkill[id] = (perSkill[id] ?? 0) + t;
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
const harnessAnchor = fileTokens(join(root, "templates", "CLAUDE.md"));
|
|
1407
|
+
const projectScaffold = estimateTokens(
|
|
1408
|
+
withContinuousSkillsNote(
|
|
1409
|
+
renderFillScaffold(),
|
|
1410
|
+
CONTINUOUS_SKILLS.map((s) => s.id)
|
|
1411
|
+
).trim().length
|
|
1412
|
+
);
|
|
1413
|
+
const projectClaudeMd = harnessAnchor + projectScaffold;
|
|
1414
|
+
const claudeMdItems = (harnessAnchor > 0 ? 1 : 0) + (projectScaffold > 0 ? 1 : 0);
|
|
1415
|
+
return makeResidentCost({
|
|
1416
|
+
rules,
|
|
1417
|
+
projectClaudeMd,
|
|
1418
|
+
skillDescriptors,
|
|
1419
|
+
agentDescriptors,
|
|
1420
|
+
perSkillDescriptor: perSkill,
|
|
1421
|
+
items: {
|
|
1422
|
+
rules: ruleItems,
|
|
1423
|
+
skills: skillItems,
|
|
1424
|
+
agents: agentItems,
|
|
1425
|
+
claudeMd: claudeMdItems,
|
|
1426
|
+
total: ruleItems + skillItems + agentItems + claudeMdItems
|
|
1427
|
+
}
|
|
1428
|
+
});
|
|
1429
|
+
}
|
|
1430
|
+
function summarizeContextCost(assetIds, root = resolveBundleRoot()) {
|
|
1431
|
+
let measuredTokens = 0;
|
|
1432
|
+
let measuredCount = 0;
|
|
1433
|
+
let unmeasuredCount = 0;
|
|
1434
|
+
for (const id of assetIds) {
|
|
1435
|
+
const tokens = assetDescriptorTokens(id, root);
|
|
1436
|
+
if (tokens === null) unmeasuredCount += 1;
|
|
1437
|
+
else {
|
|
1438
|
+
measuredTokens += tokens;
|
|
1439
|
+
measuredCount += 1;
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
return { measuredTokens, measuredCount, unmeasuredCount };
|
|
1443
|
+
}
|
|
1444
|
+
function formatResidentCostLine(r, unmeasuredCount) {
|
|
1445
|
+
if (r.total === 0) return null;
|
|
1446
|
+
const parts = [
|
|
1447
|
+
`directives ${r.directive.items} ~${r.directive.tokens}`,
|
|
1448
|
+
`[rules ${r.items.rules} ~${r.rules} \xB7 CLAUDE.md ${r.items.claudeMd} ~${r.projectClaudeMd}]`,
|
|
1449
|
+
`triggers ${r.firing.items} ~${r.firing.tokens}`,
|
|
1450
|
+
`[skills ${r.items.skills} ~${r.skillDescriptors} \xB7 agents ${r.items.agents} ~${r.agentDescriptors}]`
|
|
1451
|
+
].join(" \xB7 ");
|
|
1452
|
+
const external = unmeasuredCount > 0 ? ` \xB7 ${unmeasuredCount} external asset${unmeasuredCount === 1 ? "" : "s"} unmeasured` : "";
|
|
1453
|
+
return `session-start context cost: ${r.items.total} items resident \xB7 ~${r.total} tokens/session (${parts}${external})`;
|
|
1454
|
+
}
|
|
1455
|
+
var RESIDENT_ROWS = [
|
|
1456
|
+
{
|
|
1457
|
+
// ADR-083 — 축 소계를 **행으로** 낸다. 소계가 없으면 읽는 사람이 네 줄을 머리로 더해야
|
|
1458
|
+
// 하고, 그러면 "무엇을 줄여야 하나"가 표에서 안 보인다.
|
|
1459
|
+
label: "\uC9C0\uC2DC\uBB38",
|
|
1460
|
+
gap: 13,
|
|
1461
|
+
count: (r) => r.directive.items,
|
|
1462
|
+
tokens: (r) => r.directive.tokens
|
|
1463
|
+
},
|
|
1464
|
+
{ label: " rules", gap: 12, count: (r) => r.items.rules, tokens: (r) => r.rules },
|
|
1465
|
+
{
|
|
1466
|
+
// 한 행에 두 파일(앵커 + 스캐폴드). 개수 열이 2 를 뱉어 그 사실을 드러낸다 — 라벨로
|
|
1467
|
+
// 하나만 이름 붙이면 v26.140.0 이전과 같은 거짓 라벨이 된다.
|
|
1468
|
+
label: " CLAUDE.md",
|
|
1469
|
+
gap: 8,
|
|
1470
|
+
count: (r) => r.items.claudeMd,
|
|
1471
|
+
tokens: (r) => r.projectClaudeMd
|
|
1472
|
+
},
|
|
1473
|
+
{
|
|
1474
|
+
label: "\uBC1C\uD654 \uD45C\uBA74",
|
|
1475
|
+
gap: 10,
|
|
1476
|
+
count: (r) => r.firing.items,
|
|
1477
|
+
tokens: (r) => r.firing.tokens
|
|
1478
|
+
},
|
|
1479
|
+
{
|
|
1480
|
+
label: " skill descriptors",
|
|
1481
|
+
gap: 0,
|
|
1482
|
+
count: (r) => r.items.skills,
|
|
1483
|
+
tokens: (r) => r.skillDescriptors
|
|
1484
|
+
},
|
|
1485
|
+
{
|
|
1486
|
+
label: " agent descriptors",
|
|
1487
|
+
gap: 0,
|
|
1488
|
+
count: (r) => r.items.agents,
|
|
1489
|
+
tokens: (r) => r.agentDescriptors
|
|
1490
|
+
}
|
|
1491
|
+
];
|
|
1492
|
+
function formatResidentCostBlock(r) {
|
|
1493
|
+
const n = (v) => String(v).padStart(3);
|
|
1494
|
+
return [
|
|
1495
|
+
" \uAC1C\uC218 \uD1A0\uD070",
|
|
1496
|
+
...RESIDENT_ROWS.map(
|
|
1497
|
+
(row) => ` ${row.label}${" ".repeat(row.gap)}${n(row.count(r))}\uAC1C ~${row.tokens(r)}`
|
|
1498
|
+
),
|
|
1499
|
+
" \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",
|
|
1500
|
+
` \uC0C1\uC8FC \uD569\uACC4 ${n(r.items.total)}\uAC1C \uC0C1\uC8FC \xB7 ~${r.total} tokens/\uC138\uC158`,
|
|
1501
|
+
"",
|
|
1502
|
+
" \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",
|
|
1503
|
+
" '\uBD88\uD544\uC694\uD55C \uC9C0\uC2DC\uBB38\uC774 \uC788\uB098'\uB85C \uD55C\uB2E4. ratchet \uC740 \uC870\uC6A9\uD55C \uC99D\uAC00\uB9CC \uB9C9\uB294\uB2E4.",
|
|
1504
|
+
" \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."
|
|
1505
|
+
];
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
// src/manifest.ts
|
|
1509
|
+
init_esm_shims();
|
|
1510
|
+
|
|
1511
|
+
// src/install-log.ts
|
|
1512
|
+
init_esm_shims();
|
|
1513
|
+
import { createHash } from "crypto";
|
|
1514
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, rmSync, writeFileSync } from "fs";
|
|
1515
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
1516
|
+
|
|
1517
|
+
// src/fs-ops.ts
|
|
1518
|
+
init_esm_shims();
|
|
1519
|
+
import {
|
|
1520
|
+
copyFileSync,
|
|
1521
|
+
cpSync,
|
|
1522
|
+
existsSync as existsSync2,
|
|
1523
|
+
constants as fsConstants,
|
|
1524
|
+
mkdirSync,
|
|
1525
|
+
readdirSync,
|
|
1526
|
+
readFileSync as readFileSync2,
|
|
1527
|
+
realpathSync,
|
|
1528
|
+
renameSync,
|
|
1529
|
+
rmdirSync
|
|
1530
|
+
} from "fs";
|
|
1531
|
+
import { dirname, join as join2 } from "path";
|
|
1532
|
+
function ensureDir(path2) {
|
|
1533
|
+
mkdirSync(path2, { recursive: true });
|
|
1534
|
+
}
|
|
1535
|
+
function copyFile(source, target) {
|
|
1536
|
+
if (!existsSync2(source)) {
|
|
1537
|
+
throw new Error(`Source not found: ${source}`);
|
|
1538
|
+
}
|
|
1539
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
1540
|
+
copyFileSync(source, target);
|
|
1541
|
+
}
|
|
1542
|
+
function copyDir(source, target, foreignOf) {
|
|
1543
|
+
if (!existsSync2(source)) {
|
|
1544
|
+
throw new Error(`Source dir not found: ${source}`);
|
|
1545
|
+
}
|
|
1546
|
+
mkdirSync(target, { recursive: true });
|
|
1547
|
+
const skipped = [];
|
|
1548
|
+
for (const rel of listFilesRecursive(source)) {
|
|
1549
|
+
const foreign = foreignOf(rel);
|
|
1550
|
+
if (foreign !== null) {
|
|
1551
|
+
skipped.push(foreign);
|
|
1552
|
+
continue;
|
|
1553
|
+
}
|
|
1554
|
+
copyFile(join2(source, rel), join2(target, rel));
|
|
1555
|
+
}
|
|
1556
|
+
return skipped;
|
|
1557
|
+
}
|
|
1558
|
+
var MAX_BACKUP_CLAIMS = 100;
|
|
1559
|
+
function claimBackupPath(base, claim) {
|
|
1560
|
+
for (let n = 1; n <= MAX_BACKUP_CLAIMS; n++) {
|
|
1561
|
+
const candidate = n === 1 ? base : `${base}-${n}`;
|
|
1562
|
+
try {
|
|
1563
|
+
claim(candidate);
|
|
1564
|
+
return candidate;
|
|
1565
|
+
} catch (err) {
|
|
1566
|
+
if (err.code !== "EEXIST") throw err;
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
throw new Error(`Cannot allocate a backup path for ${base} (${MAX_BACKUP_CLAIMS} taken)`);
|
|
1570
|
+
}
|
|
1571
|
+
function backupDir(target, now = /* @__PURE__ */ new Date()) {
|
|
1572
|
+
if (!existsSync2(target)) {
|
|
1573
|
+
return null;
|
|
1574
|
+
}
|
|
1575
|
+
const backup = claimBackupPath(`${target}.backup-${formatStamp(now)}`, (c) => mkdirSync(c));
|
|
1576
|
+
rmdirSync(backup);
|
|
1577
|
+
renameSync(target, backup);
|
|
1578
|
+
return backup;
|
|
1579
|
+
}
|
|
1580
|
+
function copyBackupDir(target, now = /* @__PURE__ */ new Date()) {
|
|
1581
|
+
if (!existsSync2(target)) {
|
|
1582
|
+
return null;
|
|
1583
|
+
}
|
|
1584
|
+
const backup = claimBackupPath(`${target}.backup-${formatStamp(now)}`, (c) => mkdirSync(c));
|
|
1585
|
+
cpSync(realpathSync(target), backup, { recursive: true });
|
|
1586
|
+
return backup;
|
|
1587
|
+
}
|
|
1588
|
+
function backupFileIfChanged(target, newContent, now = /* @__PURE__ */ new Date()) {
|
|
1589
|
+
if (!existsSync2(target)) {
|
|
1590
|
+
return null;
|
|
1591
|
+
}
|
|
1592
|
+
if (readFileSync2(target, "utf-8") === newContent) {
|
|
1593
|
+
return null;
|
|
1594
|
+
}
|
|
1595
|
+
return backupFile(target, now);
|
|
1596
|
+
}
|
|
1597
|
+
function backupFile(target, now = /* @__PURE__ */ new Date()) {
|
|
1598
|
+
return claimBackupPath(
|
|
1599
|
+
`${target}.backup-${formatStamp(now)}`,
|
|
1600
|
+
(c) => copyFileSync(target, c, fsConstants.COPYFILE_EXCL)
|
|
1601
|
+
);
|
|
1602
|
+
}
|
|
1603
|
+
function listFilesRecursive(dir, prefix = "") {
|
|
1604
|
+
if (!existsSync2(dir)) return [];
|
|
1605
|
+
const out = [];
|
|
1606
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
1607
|
+
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
1608
|
+
if (entry.isDirectory()) {
|
|
1609
|
+
out.push(...listFilesRecursive(join2(dir, entry.name), rel));
|
|
1610
|
+
} else if (entry.isFile()) {
|
|
1611
|
+
out.push(rel);
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
return out;
|
|
1615
|
+
}
|
|
1616
|
+
function formatStamp(now) {
|
|
1617
|
+
return now.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z").slice(0, 15);
|
|
1618
|
+
}
|
|
1619
|
+
function ensureProjectSkeleton(projectDir) {
|
|
1620
|
+
const dirs = [
|
|
1621
|
+
".claude/commands/uzys",
|
|
1622
|
+
".claude/rules",
|
|
1623
|
+
".claude/skills",
|
|
1624
|
+
".claude/agents",
|
|
1625
|
+
".claude/hooks",
|
|
1626
|
+
"docs/decisions"
|
|
1627
|
+
];
|
|
1628
|
+
for (const d of dirs) {
|
|
1629
|
+
mkdirSync(join2(projectDir, d), { recursive: true });
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
// src/install-log.ts
|
|
1634
|
+
var INSTALL_LOG_FILENAME = ".harness-install.json";
|
|
1635
|
+
var INSTALL_LOG_DIR = ".uzys-agent-harness";
|
|
1636
|
+
var LEGACY_INSTALL_LOG_DIR = ".claude";
|
|
1245
1637
|
var INSTALL_LOG_VERSION = 1;
|
|
1246
1638
|
var POLICY_DIRS = [
|
|
1247
1639
|
{ dir: "rules", ext: ".md" },
|
|
@@ -1364,247 +1756,69 @@ function isHarnessOwned(baseline, key, current) {
|
|
|
1364
1756
|
function mergeExternalFiles(projectDir, previous, current) {
|
|
1365
1757
|
const byPath = /* @__PURE__ */ new Map();
|
|
1366
1758
|
for (const file of [...previous ?? [], ...current]) byPath.set(file.path, file);
|
|
1367
|
-
return [...byPath.values()].filter((f) =>
|
|
1368
|
-
}
|
|
1369
|
-
function collectSkillHashes(projectDir) {
|
|
1370
|
-
const skillsDir = join2(projectDir, ".claude/skills");
|
|
1371
|
-
return listFilesRecursive(skillsDir).map((rel) => ({
|
|
1372
|
-
path: rel,
|
|
1373
|
-
sha256: hashContent(readFileSync2(join2(skillsDir, rel), "utf8"))
|
|
1374
|
-
}));
|
|
1375
|
-
}
|
|
1376
|
-
function collectPolicyHashes(projectDir, templatesDir) {
|
|
1377
|
-
const out = [];
|
|
1378
|
-
for (const { dir, ext } of POLICY_DIRS) {
|
|
1379
|
-
const targetDir = join2(projectDir, ".claude", dir);
|
|
1380
|
-
const sourceDir = join2(templatesDir, dir);
|
|
1381
|
-
for (const rel of listFilesRecursive(targetDir)) {
|
|
1382
|
-
if (!rel.endsWith(ext)) continue;
|
|
1383
|
-
if (!existsSync2(join2(sourceDir, rel))) continue;
|
|
1384
|
-
out.push({
|
|
1385
|
-
path: `${dir}/${rel}`,
|
|
1386
|
-
sha256: hashContent(readFileSync2(join2(targetDir, rel), "utf8"))
|
|
1387
|
-
});
|
|
1388
|
-
}
|
|
1389
|
-
}
|
|
1390
|
-
return out;
|
|
1391
|
-
}
|
|
1392
|
-
function writeInstallLog(projectDir, log) {
|
|
1393
|
-
const path2 = installLogPath(projectDir);
|
|
1394
|
-
mkdirSync2(dirname2(path2), { recursive: true });
|
|
1395
|
-
writeFileSync(path2, `${JSON.stringify(log, null, 2)}
|
|
1396
|
-
`, "utf8");
|
|
1397
|
-
migrateAwayLegacyLog(projectDir);
|
|
1398
|
-
return path2;
|
|
1399
|
-
}
|
|
1400
|
-
function migrateAwayLegacyLog(projectDir) {
|
|
1401
|
-
const legacy = legacyInstallLogPath(projectDir);
|
|
1402
|
-
if (!existsSync2(legacy)) return;
|
|
1403
|
-
try {
|
|
1404
|
-
rmSync(legacy);
|
|
1405
|
-
} catch {
|
|
1406
|
-
}
|
|
1407
|
-
}
|
|
1408
|
-
function readInstallLog(projectDir) {
|
|
1409
|
-
const path2 = [installLogPath(projectDir), legacyInstallLogPath(projectDir)].find(
|
|
1410
|
-
(p) => existsSync2(p)
|
|
1411
|
-
);
|
|
1412
|
-
if (!path2) return null;
|
|
1413
|
-
try {
|
|
1414
|
-
const parsed = JSON.parse(readFileSync2(path2, "utf8"));
|
|
1415
|
-
if (Array.isArray(parsed.assets)) {
|
|
1416
|
-
parsed.assets = parsed.assets.map(
|
|
1417
|
-
(a) => a.method === "npm-global" ? { ...a, method: "npm" } : a
|
|
1418
|
-
);
|
|
1419
|
-
}
|
|
1420
|
-
return parsed;
|
|
1421
|
-
} catch {
|
|
1422
|
-
return null;
|
|
1423
|
-
}
|
|
1424
|
-
}
|
|
1425
|
-
function installLogPath(projectDir) {
|
|
1426
|
-
return join2(projectDir, INSTALL_LOG_DIR, INSTALL_LOG_FILENAME);
|
|
1427
|
-
}
|
|
1428
|
-
function legacyInstallLogPath(projectDir) {
|
|
1429
|
-
return join2(projectDir, LEGACY_INSTALL_LOG_DIR, INSTALL_LOG_FILENAME);
|
|
1430
|
-
}
|
|
1431
|
-
|
|
1432
|
-
// src/project-claude-merge.ts
|
|
1433
|
-
init_esm_shims();
|
|
1434
|
-
var HARNESS_ANCHOR_FILE = "CLAUDE-uzys-harness.md";
|
|
1435
|
-
var HARNESS_IMPORT_LINE = `@${HARNESS_ANCHOR_FILE}`;
|
|
1436
|
-
var IMPORT_MARKER_START = "<!-- uzys-harness:import:start -->";
|
|
1437
|
-
var IMPORT_MARKER_END = "<!-- uzys-harness:import:end -->";
|
|
1438
|
-
function renderContinuousSkillsNote(selectedInternalSkills) {
|
|
1439
|
-
const lines = CONTINUOUS_SKILLS.filter((s) => selectedInternalSkills.includes(s.id)).map(
|
|
1440
|
-
(s) => `- \`${s.id}\` \u2014 ${s.whenToApply}.`
|
|
1441
|
-
);
|
|
1442
|
-
if (lines.length === 0) return "";
|
|
1443
|
-
return [
|
|
1444
|
-
"## Skills that apply continuously",
|
|
1445
|
-
"",
|
|
1446
|
-
"These installed skills apply to every response or delegation; no prompt ever looks like their job, so open them from this list:",
|
|
1447
|
-
"",
|
|
1448
|
-
...lines
|
|
1449
|
-
].join("\n");
|
|
1450
|
-
}
|
|
1451
|
-
function withContinuousSkillsNote(scaffold, selectedInternalSkills) {
|
|
1452
|
-
const note = renderContinuousSkillsNote(selectedInternalSkills);
|
|
1453
|
-
return note ? `${scaffold}
|
|
1454
|
-
|
|
1455
|
-
${note}` : scaffold;
|
|
1456
|
-
}
|
|
1457
|
-
function importBlock(continuousSkills) {
|
|
1458
|
-
const note = renderContinuousSkillsNote(continuousSkills);
|
|
1459
|
-
const body = note ? `${HARNESS_IMPORT_LINE}
|
|
1460
|
-
|
|
1461
|
-
${note}` : HARNESS_IMPORT_LINE;
|
|
1462
|
-
return `${IMPORT_MARKER_START}
|
|
1463
|
-
${body}
|
|
1464
|
-
${IMPORT_MARKER_END}`;
|
|
1759
|
+
return [...byPath.values()].filter((f) => existsSync3(join3(projectDir, f.path)));
|
|
1465
1760
|
}
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
"
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
"ssr-nextjs": "SSR Next.js",
|
|
1473
|
-
data: "Data",
|
|
1474
|
-
executive: "Executive",
|
|
1475
|
-
full: "Full",
|
|
1476
|
-
"project-management": "Project Management",
|
|
1477
|
-
"growth-marketing": "Growth Marketing"
|
|
1478
|
-
};
|
|
1479
|
-
var FULL_EXPANSION = TRACKS.filter((t) => t !== "full");
|
|
1480
|
-
var FILL_SECTIONS = [
|
|
1481
|
-
"identity",
|
|
1482
|
-
"stack",
|
|
1483
|
-
"architecture",
|
|
1484
|
-
"installed-assets",
|
|
1485
|
-
"boundaries",
|
|
1486
|
-
"verify"
|
|
1487
|
-
];
|
|
1488
|
-
var FILL_SPECS = {
|
|
1489
|
-
identity: {
|
|
1490
|
-
title: "Identity & Purpose",
|
|
1491
|
-
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.`,
|
|
1492
|
-
placeholder: "what this project is, who it is for, and why it exists"
|
|
1493
|
-
},
|
|
1494
|
-
stack: {
|
|
1495
|
-
title: "Stack & Commands",
|
|
1496
|
-
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.",
|
|
1497
|
-
placeholder: "languages, runtimes, package manager, and the install/build/test/run commands"
|
|
1498
|
-
},
|
|
1499
|
-
architecture: {
|
|
1500
|
-
title: "Architecture & Layout",
|
|
1501
|
-
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.",
|
|
1502
|
-
placeholder: "where things live, the entry points, and how data flows between layers"
|
|
1503
|
-
},
|
|
1504
|
-
"installed-assets": {
|
|
1505
|
-
title: "Installed Harness Assets",
|
|
1506
|
-
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 .claude/rules/*.md, .claude/skills/, .claude/agents/, .claude/commands/ (or this CLI's equivalent) 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.",
|
|
1507
|
-
placeholder: "the installed rules/skills/agents/commands and when to use each"
|
|
1508
|
-
},
|
|
1509
|
-
boundaries: {
|
|
1510
|
-
title: "Boundaries \u2014 Always / Ask First / Never",
|
|
1511
|
-
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.`,
|
|
1512
|
-
placeholder: "what to always do, ask before doing, and never do in this repo"
|
|
1513
|
-
},
|
|
1514
|
-
verify: {
|
|
1515
|
-
title: "Verification Gate",
|
|
1516
|
-
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.',
|
|
1517
|
-
placeholder: "the single command that proves a change is safe, and the done bar"
|
|
1518
|
-
}
|
|
1519
|
-
};
|
|
1520
|
-
function anchorLine(surface) {
|
|
1521
|
-
if (surface === "claude") {
|
|
1522
|
-
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**.`;
|
|
1523
|
-
}
|
|
1524
|
-
const self = surface === "antigravity-rule" ? "`.agents/rules/uzys-harness.md`" : "`AGENTS.md`";
|
|
1525
|
-
return `> **This file (${self}) 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.`;
|
|
1526
|
-
}
|
|
1527
|
-
function scaffoldBanner(surface) {
|
|
1528
|
-
return [
|
|
1529
|
-
"> \u2699\uFE0F **SCAFFOLD \u2014 not filled in yet.** The sections below are a fill-in template for THIS project, not verified facts.",
|
|
1530
|
-
"> 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.",
|
|
1531
|
-
anchorLine(surface)
|
|
1532
|
-
].join("\n");
|
|
1533
|
-
}
|
|
1534
|
-
function renderFillScaffold(surface = "claude") {
|
|
1535
|
-
const blocks = FILL_SECTIONS.map((id) => {
|
|
1536
|
-
const spec = FILL_SPECS[id];
|
|
1537
|
-
return `## ${spec.title}
|
|
1538
|
-
|
|
1539
|
-
<!-- FILL:${id} \u2014 ${spec.prompt} -->
|
|
1540
|
-
|
|
1541
|
-
_(not filled yet \u2014 ${spec.placeholder})_`;
|
|
1542
|
-
});
|
|
1543
|
-
return `${scaffoldBanner(surface)}
|
|
1544
|
-
|
|
1545
|
-
${blocks.join("\n\n")}`;
|
|
1546
|
-
}
|
|
1547
|
-
function mergeProjectClaude(opts) {
|
|
1548
|
-
const expanded = expandTracks(opts.tracks);
|
|
1549
|
-
const trackList = expanded.map((t) => TRACK_DISPLAY_NAMES[t]).join(", ");
|
|
1550
|
-
const header = `# ${opts.projectName}
|
|
1551
|
-
|
|
1552
|
-
> Active track(s): ${trackList}`;
|
|
1553
|
-
return `${header}
|
|
1554
|
-
|
|
1555
|
-
${renderFillScaffold()}
|
|
1556
|
-
`;
|
|
1557
|
-
}
|
|
1558
|
-
function upsertHarnessImport(existing, opts) {
|
|
1559
|
-
const block = importBlock(opts.continuousSkills ?? []);
|
|
1560
|
-
if (existing === null) {
|
|
1561
|
-
return `${mergeProjectClaude(opts)}
|
|
1562
|
-
${block}
|
|
1563
|
-
`;
|
|
1564
|
-
}
|
|
1565
|
-
const start = existing.indexOf(IMPORT_MARKER_START);
|
|
1566
|
-
if (start !== -1) {
|
|
1567
|
-
const endAt = existing.indexOf(IMPORT_MARKER_END, start);
|
|
1568
|
-
if (endAt === -1) return existing;
|
|
1569
|
-
const current = existing.slice(start, endAt + IMPORT_MARKER_END.length);
|
|
1570
|
-
if (current === block) return existing;
|
|
1571
|
-
return `${existing.slice(0, start)}${block}${existing.slice(endAt + IMPORT_MARKER_END.length)}`;
|
|
1572
|
-
}
|
|
1573
|
-
if (hasHarnessImport(existing)) {
|
|
1574
|
-
return existing;
|
|
1575
|
-
}
|
|
1576
|
-
const body = existing.endsWith("\n") ? existing : `${existing}
|
|
1577
|
-
`;
|
|
1578
|
-
return `${body}
|
|
1579
|
-
${block}
|
|
1580
|
-
`;
|
|
1581
|
-
}
|
|
1582
|
-
function stripHarnessImport(text) {
|
|
1583
|
-
const start = text.indexOf(IMPORT_MARKER_START);
|
|
1584
|
-
if (start === -1) return null;
|
|
1585
|
-
const endAt = text.indexOf(IMPORT_MARKER_END, start);
|
|
1586
|
-
if (endAt === -1) return null;
|
|
1587
|
-
const before = text.slice(0, start).replace(/\n+$/, "\n");
|
|
1588
|
-
const after = text.slice(endAt + IMPORT_MARKER_END.length).replace(/^\n+/, "");
|
|
1589
|
-
return `${before}${after}`;
|
|
1761
|
+
function collectSkillHashes(projectDir, templatesDir) {
|
|
1762
|
+
const skillsDir = join3(projectDir, ".claude/skills");
|
|
1763
|
+
return listFilesRecursive(skillsDir).filter((rel) => existsSync3(join3(templatesDir, "skills", rel))).map((rel) => ({
|
|
1764
|
+
path: rel,
|
|
1765
|
+
sha256: hashContent(readFileSync3(join3(skillsDir, rel), "utf8"))
|
|
1766
|
+
}));
|
|
1590
1767
|
}
|
|
1591
|
-
function
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
continue;
|
|
1768
|
+
function collectPolicyHashes(projectDir, templatesDir) {
|
|
1769
|
+
const out = [];
|
|
1770
|
+
for (const { dir, ext } of POLICY_DIRS) {
|
|
1771
|
+
const targetDir = join3(projectDir, ".claude", dir);
|
|
1772
|
+
const sourceDir = join3(templatesDir, dir);
|
|
1773
|
+
for (const rel of listFilesRecursive(targetDir)) {
|
|
1774
|
+
if (!rel.endsWith(ext)) continue;
|
|
1775
|
+
if (!existsSync3(join3(sourceDir, rel))) continue;
|
|
1776
|
+
out.push({
|
|
1777
|
+
path: `${dir}/${rel}`,
|
|
1778
|
+
sha256: hashContent(readFileSync3(join3(targetDir, rel), "utf8"))
|
|
1779
|
+
});
|
|
1598
1780
|
}
|
|
1599
|
-
if (!inFence && line.trim() === HARNESS_IMPORT_LINE) return true;
|
|
1600
1781
|
}
|
|
1601
|
-
return
|
|
1782
|
+
return out;
|
|
1602
1783
|
}
|
|
1603
|
-
function
|
|
1604
|
-
|
|
1605
|
-
|
|
1784
|
+
function writeInstallLog(projectDir, log) {
|
|
1785
|
+
const path2 = installLogPath(projectDir);
|
|
1786
|
+
mkdirSync2(dirname2(path2), { recursive: true });
|
|
1787
|
+
writeFileSync(path2, `${JSON.stringify(log, null, 2)}
|
|
1788
|
+
`, "utf8");
|
|
1789
|
+
migrateAwayLegacyLog(projectDir);
|
|
1790
|
+
return path2;
|
|
1791
|
+
}
|
|
1792
|
+
function migrateAwayLegacyLog(projectDir) {
|
|
1793
|
+
const legacy = legacyInstallLogPath(projectDir);
|
|
1794
|
+
if (!existsSync3(legacy)) return;
|
|
1795
|
+
try {
|
|
1796
|
+
rmSync(legacy);
|
|
1797
|
+
} catch {
|
|
1606
1798
|
}
|
|
1607
|
-
|
|
1799
|
+
}
|
|
1800
|
+
function readInstallLog(projectDir) {
|
|
1801
|
+
const path2 = [installLogPath(projectDir), legacyInstallLogPath(projectDir)].find(
|
|
1802
|
+
(p) => existsSync3(p)
|
|
1803
|
+
);
|
|
1804
|
+
if (!path2) return null;
|
|
1805
|
+
try {
|
|
1806
|
+
const parsed = JSON.parse(readFileSync3(path2, "utf8"));
|
|
1807
|
+
if (Array.isArray(parsed.assets)) {
|
|
1808
|
+
parsed.assets = parsed.assets.map(
|
|
1809
|
+
(a) => a.method === "npm-global" ? { ...a, method: "npm" } : a
|
|
1810
|
+
);
|
|
1811
|
+
}
|
|
1812
|
+
return parsed;
|
|
1813
|
+
} catch {
|
|
1814
|
+
return null;
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
function installLogPath(projectDir) {
|
|
1818
|
+
return join3(projectDir, INSTALL_LOG_DIR, INSTALL_LOG_FILENAME);
|
|
1819
|
+
}
|
|
1820
|
+
function legacyInstallLogPath(projectDir) {
|
|
1821
|
+
return join3(projectDir, LEGACY_INSTALL_LOG_DIR, INSTALL_LOG_FILENAME);
|
|
1608
1822
|
}
|
|
1609
1823
|
|
|
1610
1824
|
// src/manifest.ts
|
|
@@ -1886,198 +2100,6 @@ var CATEGORY_TITLES = {
|
|
|
1886
2100
|
"ecc-suite": "\u{1F4E6} ECC Suite"
|
|
1887
2101
|
};
|
|
1888
2102
|
|
|
1889
|
-
// src/context-cost.ts
|
|
1890
|
-
init_esm_shims();
|
|
1891
|
-
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
1892
|
-
import { join as join3, resolve } from "path";
|
|
1893
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1894
|
-
var CHARS_PER_TOKEN = 4;
|
|
1895
|
-
function estimateTokens(chars) {
|
|
1896
|
-
return Math.ceil(chars / CHARS_PER_TOKEN);
|
|
1897
|
-
}
|
|
1898
|
-
function resolveBundleRoot() {
|
|
1899
|
-
return resolve(fileURLToPath2(new URL(".", import.meta.url)), "..");
|
|
1900
|
-
}
|
|
1901
|
-
function extractFrontmatter(content) {
|
|
1902
|
-
const m = content.match(/^?---\r?\n([\s\S]*?)\r?\n---/);
|
|
1903
|
-
return m?.[1] ?? null;
|
|
1904
|
-
}
|
|
1905
|
-
function assetDescriptorTokens(assetId, root = resolveBundleRoot()) {
|
|
1906
|
-
const asset = EXTERNAL_ASSETS.find((a) => a.id === assetId);
|
|
1907
|
-
if (!asset || asset.method.kind !== "internal") return null;
|
|
1908
|
-
const skillMd = join3(root, "templates", "skills", asset.method.key, "SKILL.md");
|
|
1909
|
-
if (!existsSync3(skillMd)) return null;
|
|
1910
|
-
const fm = extractFrontmatter(readFileSync3(skillMd, "utf8"));
|
|
1911
|
-
return fm === null ? null : estimateTokens(fm.length);
|
|
1912
|
-
}
|
|
1913
|
-
function assetBodyTokens(assetId, root = resolveBundleRoot()) {
|
|
1914
|
-
const asset = EXTERNAL_ASSETS.find((a) => a.id === assetId);
|
|
1915
|
-
if (!asset || asset.method.kind !== "internal") return null;
|
|
1916
|
-
const skillMd = join3(root, "templates", "skills", asset.method.key, "SKILL.md");
|
|
1917
|
-
if (!existsSync3(skillMd)) return null;
|
|
1918
|
-
const content = readFileSync3(skillMd, "utf8");
|
|
1919
|
-
const fm = extractFrontmatter(content);
|
|
1920
|
-
if (fm === null) return estimateTokens(content.length);
|
|
1921
|
-
const close = content.indexOf("\n---", content.indexOf(fm) + fm.length);
|
|
1922
|
-
const body = close === -1 ? "" : content.slice(close + "\n---".length);
|
|
1923
|
-
return estimateTokens(body.trim().length);
|
|
1924
|
-
}
|
|
1925
|
-
function assetCostRows(assetIds, root = resolveBundleRoot()) {
|
|
1926
|
-
return assetIds.map((id) => ({
|
|
1927
|
-
id,
|
|
1928
|
-
descriptorTokens: assetDescriptorTokens(id, root),
|
|
1929
|
-
bodyTokens: assetBodyTokens(id, root)
|
|
1930
|
-
})).sort((a, b) => (b.bodyTokens ?? -1) - (a.bodyTokens ?? -1));
|
|
1931
|
-
}
|
|
1932
|
-
function makeResidentCost(base) {
|
|
1933
|
-
return {
|
|
1934
|
-
...base,
|
|
1935
|
-
perSkillDescriptor: base.perSkillDescriptor ?? {},
|
|
1936
|
-
total: base.rules + base.projectClaudeMd + base.skillDescriptors + base.agentDescriptors,
|
|
1937
|
-
directive: {
|
|
1938
|
-
items: base.items.rules + base.items.claudeMd,
|
|
1939
|
-
tokens: base.rules + base.projectClaudeMd
|
|
1940
|
-
},
|
|
1941
|
-
firing: {
|
|
1942
|
-
items: base.items.skills + base.items.agents,
|
|
1943
|
-
tokens: base.skillDescriptors + base.agentDescriptors
|
|
1944
|
-
}
|
|
1945
|
-
};
|
|
1946
|
-
}
|
|
1947
|
-
function fileTokens(path2) {
|
|
1948
|
-
return existsSync3(path2) ? estimateTokens(readFileSync3(path2, "utf8").trim().length) : 0;
|
|
1949
|
-
}
|
|
1950
|
-
function descriptorTokens(path2) {
|
|
1951
|
-
if (!existsSync3(path2)) return 0;
|
|
1952
|
-
const fm = extractFrontmatter(readFileSync3(path2, "utf8"));
|
|
1953
|
-
return fm === null ? 0 : estimateTokens(fm.length);
|
|
1954
|
-
}
|
|
1955
|
-
function residentCost(entries, root = resolveBundleRoot()) {
|
|
1956
|
-
const tpl = (source) => join3(root, "templates", source);
|
|
1957
|
-
const perSkill = {};
|
|
1958
|
-
let rules = 0;
|
|
1959
|
-
let skillDescriptors = 0;
|
|
1960
|
-
let agentDescriptors = 0;
|
|
1961
|
-
let ruleItems = 0;
|
|
1962
|
-
let skillItems = 0;
|
|
1963
|
-
let agentItems = 0;
|
|
1964
|
-
for (const e of entries) {
|
|
1965
|
-
if (e.target.startsWith(".claude/rules/")) {
|
|
1966
|
-
rules += fileTokens(tpl(e.source));
|
|
1967
|
-
ruleItems += 1;
|
|
1968
|
-
} else if (e.target.startsWith(".claude/agents/")) {
|
|
1969
|
-
agentDescriptors += descriptorTokens(tpl(e.source));
|
|
1970
|
-
agentItems += 1;
|
|
1971
|
-
} else if (e.target.startsWith(".claude/skills/")) {
|
|
1972
|
-
const t = descriptorTokens(join3(tpl(e.source), "SKILL.md"));
|
|
1973
|
-
skillDescriptors += t;
|
|
1974
|
-
skillItems += 1;
|
|
1975
|
-
const id = /\.claude\/skills\/([^/]+)/.exec(e.target)?.[1];
|
|
1976
|
-
if (id !== void 0) perSkill[id] = (perSkill[id] ?? 0) + t;
|
|
1977
|
-
}
|
|
1978
|
-
}
|
|
1979
|
-
const harnessAnchor = fileTokens(join3(root, "templates", "CLAUDE.md"));
|
|
1980
|
-
const projectScaffold = estimateTokens(
|
|
1981
|
-
withContinuousSkillsNote(
|
|
1982
|
-
renderFillScaffold(),
|
|
1983
|
-
CONTINUOUS_SKILLS.map((s) => s.id)
|
|
1984
|
-
).trim().length
|
|
1985
|
-
);
|
|
1986
|
-
const projectClaudeMd = harnessAnchor + projectScaffold;
|
|
1987
|
-
const claudeMdItems = (harnessAnchor > 0 ? 1 : 0) + (projectScaffold > 0 ? 1 : 0);
|
|
1988
|
-
return makeResidentCost({
|
|
1989
|
-
rules,
|
|
1990
|
-
projectClaudeMd,
|
|
1991
|
-
skillDescriptors,
|
|
1992
|
-
agentDescriptors,
|
|
1993
|
-
perSkillDescriptor: perSkill,
|
|
1994
|
-
items: {
|
|
1995
|
-
rules: ruleItems,
|
|
1996
|
-
skills: skillItems,
|
|
1997
|
-
agents: agentItems,
|
|
1998
|
-
claudeMd: claudeMdItems,
|
|
1999
|
-
total: ruleItems + skillItems + agentItems + claudeMdItems
|
|
2000
|
-
}
|
|
2001
|
-
});
|
|
2002
|
-
}
|
|
2003
|
-
function summarizeContextCost(assetIds, root = resolveBundleRoot()) {
|
|
2004
|
-
let measuredTokens = 0;
|
|
2005
|
-
let measuredCount = 0;
|
|
2006
|
-
let unmeasuredCount = 0;
|
|
2007
|
-
for (const id of assetIds) {
|
|
2008
|
-
const tokens = assetDescriptorTokens(id, root);
|
|
2009
|
-
if (tokens === null) unmeasuredCount += 1;
|
|
2010
|
-
else {
|
|
2011
|
-
measuredTokens += tokens;
|
|
2012
|
-
measuredCount += 1;
|
|
2013
|
-
}
|
|
2014
|
-
}
|
|
2015
|
-
return { measuredTokens, measuredCount, unmeasuredCount };
|
|
2016
|
-
}
|
|
2017
|
-
function formatResidentCostLine(r, unmeasuredCount) {
|
|
2018
|
-
if (r.total === 0) return null;
|
|
2019
|
-
const parts = [
|
|
2020
|
-
`directives ${r.directive.items} ~${r.directive.tokens}`,
|
|
2021
|
-
`[rules ${r.items.rules} ~${r.rules} \xB7 CLAUDE.md ${r.items.claudeMd} ~${r.projectClaudeMd}]`,
|
|
2022
|
-
`triggers ${r.firing.items} ~${r.firing.tokens}`,
|
|
2023
|
-
`[skills ${r.items.skills} ~${r.skillDescriptors} \xB7 agents ${r.items.agents} ~${r.agentDescriptors}]`
|
|
2024
|
-
].join(" \xB7 ");
|
|
2025
|
-
const external = unmeasuredCount > 0 ? ` \xB7 ${unmeasuredCount} external asset${unmeasuredCount === 1 ? "" : "s"} unmeasured` : "";
|
|
2026
|
-
return `session-start context cost: ${r.items.total} items resident \xB7 ~${r.total} tokens/session (${parts}${external})`;
|
|
2027
|
-
}
|
|
2028
|
-
var RESIDENT_ROWS = [
|
|
2029
|
-
{
|
|
2030
|
-
// ADR-083 — 축 소계를 **행으로** 낸다. 소계가 없으면 읽는 사람이 네 줄을 머리로 더해야
|
|
2031
|
-
// 하고, 그러면 "무엇을 줄여야 하나"가 표에서 안 보인다.
|
|
2032
|
-
label: "\uC9C0\uC2DC\uBB38",
|
|
2033
|
-
gap: 13,
|
|
2034
|
-
count: (r) => r.directive.items,
|
|
2035
|
-
tokens: (r) => r.directive.tokens
|
|
2036
|
-
},
|
|
2037
|
-
{ label: " rules", gap: 12, count: (r) => r.items.rules, tokens: (r) => r.rules },
|
|
2038
|
-
{
|
|
2039
|
-
// 한 행에 두 파일(앵커 + 스캐폴드). 개수 열이 2 를 뱉어 그 사실을 드러낸다 — 라벨로
|
|
2040
|
-
// 하나만 이름 붙이면 v26.140.0 이전과 같은 거짓 라벨이 된다.
|
|
2041
|
-
label: " CLAUDE.md",
|
|
2042
|
-
gap: 8,
|
|
2043
|
-
count: (r) => r.items.claudeMd,
|
|
2044
|
-
tokens: (r) => r.projectClaudeMd
|
|
2045
|
-
},
|
|
2046
|
-
{
|
|
2047
|
-
label: "\uBC1C\uD654 \uD45C\uBA74",
|
|
2048
|
-
gap: 10,
|
|
2049
|
-
count: (r) => r.firing.items,
|
|
2050
|
-
tokens: (r) => r.firing.tokens
|
|
2051
|
-
},
|
|
2052
|
-
{
|
|
2053
|
-
label: " skill descriptors",
|
|
2054
|
-
gap: 0,
|
|
2055
|
-
count: (r) => r.items.skills,
|
|
2056
|
-
tokens: (r) => r.skillDescriptors
|
|
2057
|
-
},
|
|
2058
|
-
{
|
|
2059
|
-
label: " agent descriptors",
|
|
2060
|
-
gap: 0,
|
|
2061
|
-
count: (r) => r.items.agents,
|
|
2062
|
-
tokens: (r) => r.agentDescriptors
|
|
2063
|
-
}
|
|
2064
|
-
];
|
|
2065
|
-
function formatResidentCostBlock(r) {
|
|
2066
|
-
const n = (v) => String(v).padStart(3);
|
|
2067
|
-
return [
|
|
2068
|
-
" \uAC1C\uC218 \uD1A0\uD070",
|
|
2069
|
-
...RESIDENT_ROWS.map(
|
|
2070
|
-
(row) => ` ${row.label}${" ".repeat(row.gap)}${n(row.count(r))}\uAC1C ~${row.tokens(r)}`
|
|
2071
|
-
),
|
|
2072
|
-
" \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",
|
|
2073
|
-
` \uC0C1\uC8FC \uD569\uACC4 ${n(r.items.total)}\uAC1C \uC0C1\uC8FC \xB7 ~${r.total} tokens/\uC138\uC158`,
|
|
2074
|
-
"",
|
|
2075
|
-
" \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",
|
|
2076
|
-
" '\uBD88\uD544\uC694\uD55C \uC9C0\uC2DC\uBB38\uC774 \uC788\uB098'\uB85C \uD55C\uB2E4. ratchet \uC740 \uC870\uC6A9\uD55C \uC99D\uAC00\uB9CC \uB9C9\uB294\uB2E4.",
|
|
2077
|
-
" \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."
|
|
2078
|
-
];
|
|
2079
|
-
}
|
|
2080
|
-
|
|
2081
2103
|
export {
|
|
2082
2104
|
__commonJS,
|
|
2083
2105
|
__toESM,
|
|
@@ -2091,6 +2113,7 @@ export {
|
|
|
2091
2113
|
isInstallScope,
|
|
2092
2114
|
resolveScope,
|
|
2093
2115
|
DEFAULT_OPTIONS,
|
|
2116
|
+
UPDATE_GROUPS,
|
|
2094
2117
|
EXTERNAL_ASSETS,
|
|
2095
2118
|
DEV_METHOD_SKILL_IDS,
|
|
2096
2119
|
INTERNAL_BUNDLED_SKILL_IDS,
|
|
@@ -2104,6 +2127,21 @@ export {
|
|
|
2104
2127
|
assetCliSupport,
|
|
2105
2128
|
assetReachesCli,
|
|
2106
2129
|
filterApplicableAssets,
|
|
2130
|
+
HARNESS_ANCHOR_FILE,
|
|
2131
|
+
HARNESS_IMPORT_LINE,
|
|
2132
|
+
withContinuousSkillsNote,
|
|
2133
|
+
renderFillScaffold,
|
|
2134
|
+
upsertHarnessImport,
|
|
2135
|
+
stripHarnessImport,
|
|
2136
|
+
estimateTokens,
|
|
2137
|
+
resolveBundleRoot,
|
|
2138
|
+
extractFrontmatter,
|
|
2139
|
+
assetCostRows,
|
|
2140
|
+
landsOnDisk,
|
|
2141
|
+
residentCost,
|
|
2142
|
+
summarizeContextCost,
|
|
2143
|
+
formatResidentCostLine,
|
|
2144
|
+
formatResidentCostBlock,
|
|
2107
2145
|
ensureDir,
|
|
2108
2146
|
copyFile,
|
|
2109
2147
|
copyDir,
|
|
@@ -2125,27 +2163,15 @@ export {
|
|
|
2125
2163
|
readInstallLog,
|
|
2126
2164
|
installLogPath,
|
|
2127
2165
|
legacyInstallLogPath,
|
|
2128
|
-
HARNESS_ANCHOR_FILE,
|
|
2129
|
-
HARNESS_IMPORT_LINE,
|
|
2130
|
-
withContinuousSkillsNote,
|
|
2131
|
-
renderFillScaffold,
|
|
2132
|
-
upsertHarnessImport,
|
|
2133
|
-
stripHarnessImport,
|
|
2134
2166
|
buildAssetSpec,
|
|
2135
2167
|
resolveRules,
|
|
2136
2168
|
ALL_RULES,
|
|
2169
|
+
TRACK_AGENTS,
|
|
2137
2170
|
RETIRED_AGENTS,
|
|
2138
2171
|
RETIRED_AGENT_IDS,
|
|
2139
2172
|
isCliNeutralTarget,
|
|
2140
2173
|
buildManifest,
|
|
2141
2174
|
CATEGORIES,
|
|
2142
|
-
CATEGORY_TITLES
|
|
2143
|
-
estimateTokens,
|
|
2144
|
-
resolveBundleRoot,
|
|
2145
|
-
assetCostRows,
|
|
2146
|
-
residentCost,
|
|
2147
|
-
summarizeContextCost,
|
|
2148
|
-
formatResidentCostLine,
|
|
2149
|
-
formatResidentCostBlock
|
|
2175
|
+
CATEGORY_TITLES
|
|
2150
2176
|
};
|
|
2151
|
-
//# sourceMappingURL=chunk-
|
|
2177
|
+
//# sourceMappingURL=chunk-242TVGMZ.js.map
|