@uzysjung/agent-harness 26.153.0 → 26.155.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +2 -1
- package/README.md +2 -1
- package/dist/{chunk-OYNSK6QF.js → chunk-SC6ORMHV.js} +565 -542
- package/dist/chunk-SC6ORMHV.js.map +1 -0
- package/dist/index.js +578 -301
- package/dist/index.js.map +1 -1
- package/dist/trust-tier-drift.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-OYNSK6QF.js.map +0 -1
|
@@ -40,6 +40,8 @@ var init_esm_shims = __esm({
|
|
|
40
40
|
// src/types.ts
|
|
41
41
|
init_esm_shims();
|
|
42
42
|
var TRACKS = [
|
|
43
|
+
// #456 (2026-09-20) — 스택 무관: 원칙·방법론·테스트 스킬만. 스택을 아직 안 정한 프로젝트용.
|
|
44
|
+
"base",
|
|
43
45
|
"tooling",
|
|
44
46
|
"csr-supabase",
|
|
45
47
|
"csr-fastify",
|
|
@@ -70,6 +72,14 @@ var DEFAULT_OPTIONS = {
|
|
|
70
72
|
withPrune: false,
|
|
71
73
|
withCodexTrust: false
|
|
72
74
|
};
|
|
75
|
+
var UPDATE_GROUPS = [
|
|
76
|
+
"skills",
|
|
77
|
+
"new-skills",
|
|
78
|
+
"rules",
|
|
79
|
+
"anchor",
|
|
80
|
+
"hooks",
|
|
81
|
+
"external"
|
|
82
|
+
];
|
|
73
83
|
|
|
74
84
|
// src/external-assets.ts
|
|
75
85
|
init_esm_shims();
|
|
@@ -83,7 +93,7 @@ function anyTrack(tracks, pattern) {
|
|
|
83
93
|
return tracks.some((t) => matchTrack(t, pattern));
|
|
84
94
|
}
|
|
85
95
|
function hasDevTrack(tracks) {
|
|
86
|
-
return anyTrack(tracks, "csr-*|ssr-*|data|full|tooling");
|
|
96
|
+
return anyTrack(tracks, "csr-*|ssr-*|data|full|tooling|base");
|
|
87
97
|
}
|
|
88
98
|
function hasUiTrack(tracks) {
|
|
89
99
|
return anyTrack(tracks, "csr-*|ssr-*|full");
|
|
@@ -94,6 +104,7 @@ function globToRegex(pattern) {
|
|
|
94
104
|
}
|
|
95
105
|
|
|
96
106
|
// src/external-assets.ts
|
|
107
|
+
var DEV_TRACKS_WITH_STACK = TRACKS.filter((t) => t !== "base" && hasDevTrack([t]));
|
|
97
108
|
var CSR_SSR_NEXTJS_FULL = [
|
|
98
109
|
"csr-supabase",
|
|
99
110
|
"csr-fastify",
|
|
@@ -487,7 +498,7 @@ var EXTERNAL_ASSETS = [
|
|
|
487
498
|
description: "frontend-design \u2014 distinctive production-grade UI generation (Anthropic official, avoids generic AI aesthetics)",
|
|
488
499
|
category: "frontend",
|
|
489
500
|
source: "anthropics",
|
|
490
|
-
condition: { kind: "
|
|
501
|
+
condition: { kind: "any-track", tracks: DEV_TRACKS_WITH_STACK },
|
|
491
502
|
method: { kind: "skill", source: "anthropics/skills", skill: "frontend-design" }
|
|
492
503
|
},
|
|
493
504
|
// 2026-08-02 정비 — 프론트엔드 품질 3종 (사용자 지시). 전부 opt-in: frontend-design 이 기본
|
|
@@ -598,7 +609,7 @@ var EXTERNAL_ASSETS = [
|
|
|
598
609
|
description: "find-skills \u2014 search \xB7 rank all installed skills (vercel-labs, all dev tracks)",
|
|
599
610
|
category: "dev-tools",
|
|
600
611
|
source: "vercel-labs",
|
|
601
|
-
condition: { kind: "
|
|
612
|
+
condition: { kind: "any-track", tracks: DEV_TRACKS_WITH_STACK },
|
|
602
613
|
method: { kind: "skill", source: "vercel-labs/skills", skill: "find-skills" }
|
|
603
614
|
},
|
|
604
615
|
// v26.110.0 (ADR-039) — context7 플러그인은 **미등록** (검토 후 철회): templates/mcp.json ·
|
|
@@ -613,7 +624,7 @@ var EXTERNAL_ASSETS = [
|
|
|
613
624
|
// v26.78.0 — Understanding 으로 재분류: 웹 지각(screenshot·DOM). 영상/코드 지각과 같은 축.
|
|
614
625
|
category: "understanding",
|
|
615
626
|
source: "vercel-labs",
|
|
616
|
-
condition: { kind: "
|
|
627
|
+
condition: { kind: "any-track", tracks: DEV_TRACKS_WITH_STACK },
|
|
617
628
|
method: { kind: "npm", pkg: "agent-browser", version: "0.31.0" }
|
|
618
629
|
},
|
|
619
630
|
// v26.78.0 — Understanding 신규 3종 (plugin, opt-in). 에이전트 인지 증강: 영상·코드 지각 + 메모리.
|
|
@@ -1120,135 +1131,513 @@ function filterApplicableAssets(assets, ctx) {
|
|
|
1120
1131
|
return assets.filter((a) => shouldInstallAsset(a, ctx));
|
|
1121
1132
|
}
|
|
1122
1133
|
|
|
1123
|
-
// src/
|
|
1134
|
+
// src/context-cost.ts
|
|
1124
1135
|
init_esm_shims();
|
|
1136
|
+
import { existsSync, readFileSync } from "fs";
|
|
1137
|
+
import { join, resolve } from "path";
|
|
1138
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1125
1139
|
|
|
1126
|
-
// src/
|
|
1140
|
+
// src/project-claude-merge.ts
|
|
1127
1141
|
init_esm_shims();
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1142
|
+
var HARNESS_ANCHOR_FILE = "CLAUDE-uzys-harness.md";
|
|
1143
|
+
var HARNESS_IMPORT_LINE = `@${HARNESS_ANCHOR_FILE}`;
|
|
1144
|
+
var IMPORT_MARKER_START = "<!-- uzys-harness:import:start -->";
|
|
1145
|
+
var IMPORT_MARKER_END = "<!-- uzys-harness:import:end -->";
|
|
1146
|
+
function renderContinuousSkillsNote(selectedInternalSkills) {
|
|
1147
|
+
const lines = CONTINUOUS_SKILLS.filter((s) => selectedInternalSkills.includes(s.id)).map(
|
|
1148
|
+
(s) => `- \`${s.id}\` \u2014 ${s.whenToApply}.`
|
|
1149
|
+
);
|
|
1150
|
+
if (lines.length === 0) return "";
|
|
1151
|
+
return [
|
|
1152
|
+
"## Skills that apply continuously",
|
|
1153
|
+
"",
|
|
1154
|
+
"These installed skills apply to every response or delegation; no prompt ever looks like their job, so open them from this list:",
|
|
1155
|
+
"",
|
|
1156
|
+
...lines
|
|
1157
|
+
].join("\n");
|
|
1158
|
+
}
|
|
1159
|
+
function withContinuousSkillsNote(scaffold, selectedInternalSkills) {
|
|
1160
|
+
const note = renderContinuousSkillsNote(selectedInternalSkills);
|
|
1161
|
+
return note ? `${scaffold}
|
|
1131
1162
|
|
|
1132
|
-
|
|
1133
|
-
init_esm_shims();
|
|
1134
|
-
import {
|
|
1135
|
-
copyFileSync,
|
|
1136
|
-
cpSync,
|
|
1137
|
-
existsSync,
|
|
1138
|
-
constants as fsConstants,
|
|
1139
|
-
mkdirSync,
|
|
1140
|
-
readdirSync,
|
|
1141
|
-
readFileSync,
|
|
1142
|
-
realpathSync,
|
|
1143
|
-
renameSync,
|
|
1144
|
-
rmdirSync
|
|
1145
|
-
} from "fs";
|
|
1146
|
-
import { dirname, join } from "path";
|
|
1147
|
-
function ensureDir(path2) {
|
|
1148
|
-
mkdirSync(path2, { recursive: true });
|
|
1163
|
+
${note}` : scaffold;
|
|
1149
1164
|
}
|
|
1150
|
-
function
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1165
|
+
function importBlock(continuousSkills) {
|
|
1166
|
+
const note = renderContinuousSkillsNote(continuousSkills);
|
|
1167
|
+
const body = note ? `${HARNESS_IMPORT_LINE}
|
|
1168
|
+
|
|
1169
|
+
${note}` : HARNESS_IMPORT_LINE;
|
|
1170
|
+
return `${IMPORT_MARKER_START}
|
|
1171
|
+
${body}
|
|
1172
|
+
${IMPORT_MARKER_END}`;
|
|
1156
1173
|
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1174
|
+
var TRACK_DISPLAY_NAMES = {
|
|
1175
|
+
base: "Base",
|
|
1176
|
+
tooling: "Tooling",
|
|
1177
|
+
"csr-supabase": "CSR Supabase",
|
|
1178
|
+
"csr-fastify": "CSR Fastify",
|
|
1179
|
+
"csr-fastapi": "CSR FastAPI",
|
|
1180
|
+
"ssr-htmx": "SSR HTMX",
|
|
1181
|
+
"ssr-nextjs": "SSR Next.js",
|
|
1182
|
+
data: "Data",
|
|
1183
|
+
executive: "Executive",
|
|
1184
|
+
full: "Full",
|
|
1185
|
+
"project-management": "Project Management",
|
|
1186
|
+
"growth-marketing": "Growth Marketing"
|
|
1187
|
+
};
|
|
1188
|
+
var FULL_EXPANSION = TRACKS.filter((t) => t !== "full");
|
|
1189
|
+
var FILL_SECTIONS = [
|
|
1190
|
+
"identity",
|
|
1191
|
+
"stack",
|
|
1192
|
+
"architecture",
|
|
1193
|
+
"installed-assets",
|
|
1194
|
+
"boundaries",
|
|
1195
|
+
"verify"
|
|
1196
|
+
];
|
|
1197
|
+
var FILL_SPECS = {
|
|
1198
|
+
identity: {
|
|
1199
|
+
title: "Identity & Purpose",
|
|
1200
|
+
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.`,
|
|
1201
|
+
placeholder: "what this project is, who it is for, and why it exists"
|
|
1202
|
+
},
|
|
1203
|
+
stack: {
|
|
1204
|
+
title: "Stack & Commands",
|
|
1205
|
+
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.",
|
|
1206
|
+
placeholder: "languages, runtimes, package manager, and the install/build/test/run commands"
|
|
1207
|
+
},
|
|
1208
|
+
architecture: {
|
|
1209
|
+
title: "Architecture & Layout",
|
|
1210
|
+
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.",
|
|
1211
|
+
placeholder: "where things live, the entry points, and how data flows between layers"
|
|
1212
|
+
},
|
|
1213
|
+
"installed-assets": {
|
|
1214
|
+
title: "Installed Harness Assets",
|
|
1215
|
+
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.",
|
|
1216
|
+
placeholder: "the installed rules/skills/agents/commands and when to use each"
|
|
1217
|
+
},
|
|
1218
|
+
boundaries: {
|
|
1219
|
+
title: "Boundaries \u2014 Always / Ask First / Never",
|
|
1220
|
+
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.`,
|
|
1221
|
+
placeholder: "what to always do, ask before doing, and never do in this repo"
|
|
1222
|
+
},
|
|
1223
|
+
verify: {
|
|
1224
|
+
title: "Verification Gate",
|
|
1225
|
+
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.',
|
|
1226
|
+
placeholder: "the single command that proves a change is safe, and the done bar"
|
|
1160
1227
|
}
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
if (foreign !== null) {
|
|
1166
|
-
skipped.push(foreign);
|
|
1167
|
-
continue;
|
|
1168
|
-
}
|
|
1169
|
-
copyFile(join(source, rel), join(target, rel));
|
|
1228
|
+
};
|
|
1229
|
+
function anchorLine(surface) {
|
|
1230
|
+
if (surface === "claude") {
|
|
1231
|
+
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**.`;
|
|
1170
1232
|
}
|
|
1171
|
-
|
|
1233
|
+
const self = surface === "antigravity-rule" ? "`.agents/rules/uzys-harness.md`" : "`AGENTS.md`";
|
|
1234
|
+
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.`;
|
|
1172
1235
|
}
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
return candidate;
|
|
1180
|
-
} catch (err) {
|
|
1181
|
-
if (err.code !== "EEXIST") throw err;
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
throw new Error(`Cannot allocate a backup path for ${base} (${MAX_BACKUP_CLAIMS} taken)`);
|
|
1236
|
+
function scaffoldBanner(surface) {
|
|
1237
|
+
return [
|
|
1238
|
+
"> \u2699\uFE0F **SCAFFOLD \u2014 not filled in yet.** The sections below are a fill-in template for THIS project, not verified facts.",
|
|
1239
|
+
"> 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.",
|
|
1240
|
+
anchorLine(surface)
|
|
1241
|
+
].join("\n");
|
|
1185
1242
|
}
|
|
1186
|
-
function
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1243
|
+
function renderFillScaffold(surface = "claude") {
|
|
1244
|
+
const blocks = FILL_SECTIONS.map((id) => {
|
|
1245
|
+
const spec = FILL_SPECS[id];
|
|
1246
|
+
return `## ${spec.title}
|
|
1247
|
+
|
|
1248
|
+
<!-- FILL:${id} \u2014 ${spec.prompt} -->
|
|
1249
|
+
|
|
1250
|
+
_(not filled yet \u2014 ${spec.placeholder})_`;
|
|
1251
|
+
});
|
|
1252
|
+
return `${scaffoldBanner(surface)}
|
|
1253
|
+
|
|
1254
|
+
${blocks.join("\n\n")}`;
|
|
1194
1255
|
}
|
|
1195
|
-
function
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
return
|
|
1256
|
+
function mergeProjectClaude(opts) {
|
|
1257
|
+
const expanded = expandTracks(opts.tracks);
|
|
1258
|
+
const trackList = expanded.map((t) => TRACK_DISPLAY_NAMES[t]).join(", ");
|
|
1259
|
+
const header = `# ${opts.projectName}
|
|
1260
|
+
|
|
1261
|
+
> Active track(s): ${trackList}`;
|
|
1262
|
+
return `${header}
|
|
1263
|
+
|
|
1264
|
+
${renderFillScaffold()}
|
|
1265
|
+
`;
|
|
1202
1266
|
}
|
|
1203
|
-
function
|
|
1204
|
-
|
|
1205
|
-
|
|
1267
|
+
function upsertHarnessImport(existing, opts) {
|
|
1268
|
+
const block = importBlock(opts.continuousSkills ?? []);
|
|
1269
|
+
if (existing === null) {
|
|
1270
|
+
return `${mergeProjectClaude(opts)}
|
|
1271
|
+
${block}
|
|
1272
|
+
`;
|
|
1206
1273
|
}
|
|
1207
|
-
|
|
1208
|
-
|
|
1274
|
+
const start = existing.indexOf(IMPORT_MARKER_START);
|
|
1275
|
+
if (start !== -1) {
|
|
1276
|
+
const endAt = existing.indexOf(IMPORT_MARKER_END, start);
|
|
1277
|
+
if (endAt === -1) return existing;
|
|
1278
|
+
const current = existing.slice(start, endAt + IMPORT_MARKER_END.length);
|
|
1279
|
+
if (current === block) return existing;
|
|
1280
|
+
return `${existing.slice(0, start)}${block}${existing.slice(endAt + IMPORT_MARKER_END.length)}`;
|
|
1209
1281
|
}
|
|
1210
|
-
|
|
1282
|
+
if (hasHarnessImport(existing)) {
|
|
1283
|
+
return existing;
|
|
1284
|
+
}
|
|
1285
|
+
const body = existing.endsWith("\n") ? existing : `${existing}
|
|
1286
|
+
`;
|
|
1287
|
+
return `${body}
|
|
1288
|
+
${block}
|
|
1289
|
+
`;
|
|
1211
1290
|
}
|
|
1212
|
-
function
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
);
|
|
1291
|
+
function stripHarnessImport(text) {
|
|
1292
|
+
const start = text.indexOf(IMPORT_MARKER_START);
|
|
1293
|
+
if (start === -1) return null;
|
|
1294
|
+
const endAt = text.indexOf(IMPORT_MARKER_END, start);
|
|
1295
|
+
if (endAt === -1) return null;
|
|
1296
|
+
const before = text.slice(0, start).replace(/\n+$/, "\n");
|
|
1297
|
+
const after = text.slice(endAt + IMPORT_MARKER_END.length).replace(/^\n+/, "");
|
|
1298
|
+
return `${before}${after}`;
|
|
1217
1299
|
}
|
|
1218
|
-
function
|
|
1219
|
-
if (
|
|
1220
|
-
|
|
1221
|
-
for (const
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
} else if (entry.isFile()) {
|
|
1226
|
-
out.push(rel);
|
|
1300
|
+
function hasHarnessImport(text) {
|
|
1301
|
+
if (text.includes(IMPORT_MARKER_START)) return true;
|
|
1302
|
+
let inFence = false;
|
|
1303
|
+
for (const line of text.split("\n")) {
|
|
1304
|
+
if (/^\s*(?:```|~~~)/.test(line)) {
|
|
1305
|
+
inFence = !inFence;
|
|
1306
|
+
continue;
|
|
1227
1307
|
}
|
|
1308
|
+
if (!inFence && line.trim() === HARNESS_IMPORT_LINE) return true;
|
|
1228
1309
|
}
|
|
1229
|
-
return
|
|
1230
|
-
}
|
|
1231
|
-
function formatStamp(now) {
|
|
1232
|
-
return now.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z").slice(0, 15);
|
|
1310
|
+
return false;
|
|
1233
1311
|
}
|
|
1234
|
-
function
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
".claude/rules",
|
|
1238
|
-
".claude/skills",
|
|
1239
|
-
".claude/agents",
|
|
1240
|
-
".claude/hooks",
|
|
1241
|
-
"docs/decisions"
|
|
1242
|
-
];
|
|
1243
|
-
for (const d of dirs) {
|
|
1244
|
-
mkdirSync(join(projectDir, d), { recursive: true });
|
|
1312
|
+
function expandTracks(tracks) {
|
|
1313
|
+
if (tracks.includes("full")) {
|
|
1314
|
+
return FULL_EXPANSION;
|
|
1245
1315
|
}
|
|
1316
|
+
return tracks;
|
|
1246
1317
|
}
|
|
1247
1318
|
|
|
1248
|
-
// src/
|
|
1249
|
-
var
|
|
1250
|
-
|
|
1251
|
-
|
|
1319
|
+
// src/context-cost.ts
|
|
1320
|
+
var CHARS_PER_TOKEN = 4;
|
|
1321
|
+
function estimateTokens(chars) {
|
|
1322
|
+
return Math.ceil(chars / CHARS_PER_TOKEN);
|
|
1323
|
+
}
|
|
1324
|
+
function resolveBundleRoot() {
|
|
1325
|
+
return resolve(fileURLToPath2(new URL(".", import.meta.url)), "..");
|
|
1326
|
+
}
|
|
1327
|
+
function extractFrontmatter(content) {
|
|
1328
|
+
const m = content.match(/^?---\r?\n([\s\S]*?)\r?\n---/);
|
|
1329
|
+
return m?.[1] ?? null;
|
|
1330
|
+
}
|
|
1331
|
+
function assetDescriptorTokens(assetId, root = resolveBundleRoot()) {
|
|
1332
|
+
const asset = EXTERNAL_ASSETS.find((a) => a.id === assetId);
|
|
1333
|
+
if (!asset || asset.method.kind !== "internal") return null;
|
|
1334
|
+
const skillMd = join(root, "templates", "skills", asset.method.key, "SKILL.md");
|
|
1335
|
+
if (!existsSync(skillMd)) return null;
|
|
1336
|
+
const fm = extractFrontmatter(readFileSync(skillMd, "utf8"));
|
|
1337
|
+
return fm === null ? null : estimateTokens(fm.length);
|
|
1338
|
+
}
|
|
1339
|
+
function assetBodyTokens(assetId, root = resolveBundleRoot()) {
|
|
1340
|
+
const asset = EXTERNAL_ASSETS.find((a) => a.id === assetId);
|
|
1341
|
+
if (!asset || asset.method.kind !== "internal") return null;
|
|
1342
|
+
const skillMd = join(root, "templates", "skills", asset.method.key, "SKILL.md");
|
|
1343
|
+
if (!existsSync(skillMd)) return null;
|
|
1344
|
+
const content = readFileSync(skillMd, "utf8");
|
|
1345
|
+
const fm = extractFrontmatter(content);
|
|
1346
|
+
if (fm === null) return estimateTokens(content.length);
|
|
1347
|
+
const close = content.indexOf("\n---", content.indexOf(fm) + fm.length);
|
|
1348
|
+
const body = close === -1 ? "" : content.slice(close + "\n---".length);
|
|
1349
|
+
return estimateTokens(body.trim().length);
|
|
1350
|
+
}
|
|
1351
|
+
function assetCostRows(assetIds, root = resolveBundleRoot()) {
|
|
1352
|
+
return assetIds.map((id) => ({
|
|
1353
|
+
id,
|
|
1354
|
+
descriptorTokens: assetDescriptorTokens(id, root),
|
|
1355
|
+
bodyTokens: assetBodyTokens(id, root)
|
|
1356
|
+
})).sort((a, b) => (b.bodyTokens ?? -1) - (a.bodyTokens ?? -1));
|
|
1357
|
+
}
|
|
1358
|
+
function makeResidentCost(base) {
|
|
1359
|
+
return {
|
|
1360
|
+
...base,
|
|
1361
|
+
perSkillDescriptor: base.perSkillDescriptor ?? {},
|
|
1362
|
+
total: base.rules + base.projectClaudeMd + base.skillDescriptors + base.agentDescriptors,
|
|
1363
|
+
directive: {
|
|
1364
|
+
items: base.items.rules + base.items.claudeMd,
|
|
1365
|
+
tokens: base.rules + base.projectClaudeMd
|
|
1366
|
+
},
|
|
1367
|
+
firing: {
|
|
1368
|
+
items: base.items.skills + base.items.agents,
|
|
1369
|
+
tokens: base.skillDescriptors + base.agentDescriptors
|
|
1370
|
+
}
|
|
1371
|
+
};
|
|
1372
|
+
}
|
|
1373
|
+
function fileTokens(path2) {
|
|
1374
|
+
return existsSync(path2) ? estimateTokens(readFileSync(path2, "utf8").trim().length) : 0;
|
|
1375
|
+
}
|
|
1376
|
+
function descriptorTokens(path2) {
|
|
1377
|
+
if (!existsSync(path2)) return 0;
|
|
1378
|
+
const fm = extractFrontmatter(readFileSync(path2, "utf8"));
|
|
1379
|
+
return fm === null ? 0 : estimateTokens(fm.length);
|
|
1380
|
+
}
|
|
1381
|
+
function landsOnDisk(target, cli) {
|
|
1382
|
+
if (target.startsWith(".claude/agents/")) return cli.includes("claude");
|
|
1383
|
+
return true;
|
|
1384
|
+
}
|
|
1385
|
+
function residentCost(entries, root = resolveBundleRoot()) {
|
|
1386
|
+
const tpl = (source) => join(root, "templates", source);
|
|
1387
|
+
const measured = (e) => e.file ?? tpl(e.source);
|
|
1388
|
+
const perSkill = {};
|
|
1389
|
+
let rules = 0;
|
|
1390
|
+
let skillDescriptors = 0;
|
|
1391
|
+
let agentDescriptors = 0;
|
|
1392
|
+
let ruleItems = 0;
|
|
1393
|
+
let skillItems = 0;
|
|
1394
|
+
let agentItems = 0;
|
|
1395
|
+
for (const e of entries) {
|
|
1396
|
+
if (e.target.startsWith(".claude/rules/")) {
|
|
1397
|
+
rules += fileTokens(measured(e));
|
|
1398
|
+
ruleItems += 1;
|
|
1399
|
+
} else if (e.target.startsWith(".claude/agents/")) {
|
|
1400
|
+
agentDescriptors += descriptorTokens(measured(e));
|
|
1401
|
+
agentItems += 1;
|
|
1402
|
+
} else if (e.target.startsWith(".claude/skills/")) {
|
|
1403
|
+
const t = descriptorTokens(join(measured(e), "SKILL.md"));
|
|
1404
|
+
skillDescriptors += t;
|
|
1405
|
+
skillItems += 1;
|
|
1406
|
+
const id = /\.claude\/skills\/([^/]+)/.exec(e.target)?.[1];
|
|
1407
|
+
if (id !== void 0) perSkill[id] = (perSkill[id] ?? 0) + t;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
const harnessAnchor = fileTokens(join(root, "templates", "CLAUDE.md"));
|
|
1411
|
+
const projectScaffold = estimateTokens(
|
|
1412
|
+
withContinuousSkillsNote(
|
|
1413
|
+
renderFillScaffold(),
|
|
1414
|
+
CONTINUOUS_SKILLS.map((s) => s.id)
|
|
1415
|
+
).trim().length
|
|
1416
|
+
);
|
|
1417
|
+
const projectClaudeMd = harnessAnchor + projectScaffold;
|
|
1418
|
+
const claudeMdItems = (harnessAnchor > 0 ? 1 : 0) + (projectScaffold > 0 ? 1 : 0);
|
|
1419
|
+
return makeResidentCost({
|
|
1420
|
+
rules,
|
|
1421
|
+
projectClaudeMd,
|
|
1422
|
+
skillDescriptors,
|
|
1423
|
+
agentDescriptors,
|
|
1424
|
+
perSkillDescriptor: perSkill,
|
|
1425
|
+
items: {
|
|
1426
|
+
rules: ruleItems,
|
|
1427
|
+
skills: skillItems,
|
|
1428
|
+
agents: agentItems,
|
|
1429
|
+
claudeMd: claudeMdItems,
|
|
1430
|
+
total: ruleItems + skillItems + agentItems + claudeMdItems
|
|
1431
|
+
}
|
|
1432
|
+
});
|
|
1433
|
+
}
|
|
1434
|
+
function summarizeContextCost(assetIds, root = resolveBundleRoot()) {
|
|
1435
|
+
let measuredTokens = 0;
|
|
1436
|
+
let measuredCount = 0;
|
|
1437
|
+
let unmeasuredCount = 0;
|
|
1438
|
+
for (const id of assetIds) {
|
|
1439
|
+
const tokens = assetDescriptorTokens(id, root);
|
|
1440
|
+
if (tokens === null) unmeasuredCount += 1;
|
|
1441
|
+
else {
|
|
1442
|
+
measuredTokens += tokens;
|
|
1443
|
+
measuredCount += 1;
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
return { measuredTokens, measuredCount, unmeasuredCount };
|
|
1447
|
+
}
|
|
1448
|
+
function formatResidentCostLine(r, unmeasuredCount) {
|
|
1449
|
+
if (r.total === 0) return null;
|
|
1450
|
+
const parts = [
|
|
1451
|
+
`directives ${r.directive.items} ~${r.directive.tokens}`,
|
|
1452
|
+
`[rules ${r.items.rules} ~${r.rules} \xB7 CLAUDE.md ${r.items.claudeMd} ~${r.projectClaudeMd}]`,
|
|
1453
|
+
`triggers ${r.firing.items} ~${r.firing.tokens}`,
|
|
1454
|
+
`[skills ${r.items.skills} ~${r.skillDescriptors} \xB7 agents ${r.items.agents} ~${r.agentDescriptors}]`
|
|
1455
|
+
].join(" \xB7 ");
|
|
1456
|
+
const external = unmeasuredCount > 0 ? ` \xB7 ${unmeasuredCount} external asset${unmeasuredCount === 1 ? "" : "s"} unmeasured` : "";
|
|
1457
|
+
return `session-start context cost: ${r.items.total} items resident \xB7 ~${r.total} tokens/session (${parts}${external})`;
|
|
1458
|
+
}
|
|
1459
|
+
var RESIDENT_ROWS = [
|
|
1460
|
+
{
|
|
1461
|
+
// ADR-083 — 축 소계를 **행으로** 낸다. 소계가 없으면 읽는 사람이 네 줄을 머리로 더해야
|
|
1462
|
+
// 하고, 그러면 "무엇을 줄여야 하나"가 표에서 안 보인다.
|
|
1463
|
+
label: "\uC9C0\uC2DC\uBB38",
|
|
1464
|
+
gap: 13,
|
|
1465
|
+
count: (r) => r.directive.items,
|
|
1466
|
+
tokens: (r) => r.directive.tokens
|
|
1467
|
+
},
|
|
1468
|
+
{ label: " rules", gap: 12, count: (r) => r.items.rules, tokens: (r) => r.rules },
|
|
1469
|
+
{
|
|
1470
|
+
// 한 행에 두 파일(앵커 + 스캐폴드). 개수 열이 2 를 뱉어 그 사실을 드러낸다 — 라벨로
|
|
1471
|
+
// 하나만 이름 붙이면 v26.140.0 이전과 같은 거짓 라벨이 된다.
|
|
1472
|
+
label: " CLAUDE.md",
|
|
1473
|
+
gap: 8,
|
|
1474
|
+
count: (r) => r.items.claudeMd,
|
|
1475
|
+
tokens: (r) => r.projectClaudeMd
|
|
1476
|
+
},
|
|
1477
|
+
{
|
|
1478
|
+
label: "\uBC1C\uD654 \uD45C\uBA74",
|
|
1479
|
+
gap: 10,
|
|
1480
|
+
count: (r) => r.firing.items,
|
|
1481
|
+
tokens: (r) => r.firing.tokens
|
|
1482
|
+
},
|
|
1483
|
+
{
|
|
1484
|
+
label: " skill descriptors",
|
|
1485
|
+
gap: 0,
|
|
1486
|
+
count: (r) => r.items.skills,
|
|
1487
|
+
tokens: (r) => r.skillDescriptors
|
|
1488
|
+
},
|
|
1489
|
+
{
|
|
1490
|
+
label: " agent descriptors",
|
|
1491
|
+
gap: 0,
|
|
1492
|
+
count: (r) => r.items.agents,
|
|
1493
|
+
tokens: (r) => r.agentDescriptors
|
|
1494
|
+
}
|
|
1495
|
+
];
|
|
1496
|
+
function formatResidentCostBlock(r) {
|
|
1497
|
+
const n = (v) => String(v).padStart(3);
|
|
1498
|
+
return [
|
|
1499
|
+
" \uAC1C\uC218 \uD1A0\uD070",
|
|
1500
|
+
...RESIDENT_ROWS.map(
|
|
1501
|
+
(row) => ` ${row.label}${" ".repeat(row.gap)}${n(row.count(r))}\uAC1C ~${row.tokens(r)}`
|
|
1502
|
+
),
|
|
1503
|
+
" \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",
|
|
1504
|
+
` \uC0C1\uC8FC \uD569\uACC4 ${n(r.items.total)}\uAC1C \uC0C1\uC8FC \xB7 ~${r.total} tokens/\uC138\uC158`,
|
|
1505
|
+
"",
|
|
1506
|
+
" \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",
|
|
1507
|
+
" '\uBD88\uD544\uC694\uD55C \uC9C0\uC2DC\uBB38\uC774 \uC788\uB098'\uB85C \uD55C\uB2E4. ratchet \uC740 \uC870\uC6A9\uD55C \uC99D\uAC00\uB9CC \uB9C9\uB294\uB2E4.",
|
|
1508
|
+
" \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."
|
|
1509
|
+
];
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
// src/manifest.ts
|
|
1513
|
+
init_esm_shims();
|
|
1514
|
+
|
|
1515
|
+
// src/install-log.ts
|
|
1516
|
+
init_esm_shims();
|
|
1517
|
+
import { createHash } from "crypto";
|
|
1518
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, rmSync, writeFileSync } from "fs";
|
|
1519
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
1520
|
+
|
|
1521
|
+
// src/fs-ops.ts
|
|
1522
|
+
init_esm_shims();
|
|
1523
|
+
import {
|
|
1524
|
+
copyFileSync,
|
|
1525
|
+
cpSync,
|
|
1526
|
+
existsSync as existsSync2,
|
|
1527
|
+
constants as fsConstants,
|
|
1528
|
+
mkdirSync,
|
|
1529
|
+
readdirSync,
|
|
1530
|
+
readFileSync as readFileSync2,
|
|
1531
|
+
realpathSync,
|
|
1532
|
+
renameSync,
|
|
1533
|
+
rmdirSync
|
|
1534
|
+
} from "fs";
|
|
1535
|
+
import { dirname, join as join2 } from "path";
|
|
1536
|
+
function ensureDir(path2) {
|
|
1537
|
+
mkdirSync(path2, { recursive: true });
|
|
1538
|
+
}
|
|
1539
|
+
function copyFile(source, target) {
|
|
1540
|
+
if (!existsSync2(source)) {
|
|
1541
|
+
throw new Error(`Source not found: ${source}`);
|
|
1542
|
+
}
|
|
1543
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
1544
|
+
copyFileSync(source, target);
|
|
1545
|
+
}
|
|
1546
|
+
function copyDir(source, target, foreignOf) {
|
|
1547
|
+
if (!existsSync2(source)) {
|
|
1548
|
+
throw new Error(`Source dir not found: ${source}`);
|
|
1549
|
+
}
|
|
1550
|
+
mkdirSync(target, { recursive: true });
|
|
1551
|
+
const skipped = [];
|
|
1552
|
+
for (const rel of listFilesRecursive(source)) {
|
|
1553
|
+
const foreign = foreignOf(rel);
|
|
1554
|
+
if (foreign !== null) {
|
|
1555
|
+
skipped.push(foreign);
|
|
1556
|
+
continue;
|
|
1557
|
+
}
|
|
1558
|
+
copyFile(join2(source, rel), join2(target, rel));
|
|
1559
|
+
}
|
|
1560
|
+
return skipped;
|
|
1561
|
+
}
|
|
1562
|
+
var MAX_BACKUP_CLAIMS = 100;
|
|
1563
|
+
function claimBackupPath(base, claim) {
|
|
1564
|
+
for (let n = 1; n <= MAX_BACKUP_CLAIMS; n++) {
|
|
1565
|
+
const candidate = n === 1 ? base : `${base}-${n}`;
|
|
1566
|
+
try {
|
|
1567
|
+
claim(candidate);
|
|
1568
|
+
return candidate;
|
|
1569
|
+
} catch (err) {
|
|
1570
|
+
if (err.code !== "EEXIST") throw err;
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
throw new Error(`Cannot allocate a backup path for ${base} (${MAX_BACKUP_CLAIMS} taken)`);
|
|
1574
|
+
}
|
|
1575
|
+
function backupDir(target, now = /* @__PURE__ */ new Date()) {
|
|
1576
|
+
if (!existsSync2(target)) {
|
|
1577
|
+
return null;
|
|
1578
|
+
}
|
|
1579
|
+
const backup = claimBackupPath(`${target}.backup-${formatStamp(now)}`, (c) => mkdirSync(c));
|
|
1580
|
+
rmdirSync(backup);
|
|
1581
|
+
renameSync(target, backup);
|
|
1582
|
+
return backup;
|
|
1583
|
+
}
|
|
1584
|
+
function copyBackupDir(target, now = /* @__PURE__ */ new Date()) {
|
|
1585
|
+
if (!existsSync2(target)) {
|
|
1586
|
+
return null;
|
|
1587
|
+
}
|
|
1588
|
+
const backup = claimBackupPath(`${target}.backup-${formatStamp(now)}`, (c) => mkdirSync(c));
|
|
1589
|
+
cpSync(realpathSync(target), backup, { recursive: true });
|
|
1590
|
+
return backup;
|
|
1591
|
+
}
|
|
1592
|
+
function backupFileIfChanged(target, newContent, now = /* @__PURE__ */ new Date()) {
|
|
1593
|
+
if (!existsSync2(target)) {
|
|
1594
|
+
return null;
|
|
1595
|
+
}
|
|
1596
|
+
if (readFileSync2(target, "utf-8") === newContent) {
|
|
1597
|
+
return null;
|
|
1598
|
+
}
|
|
1599
|
+
return backupFile(target, now);
|
|
1600
|
+
}
|
|
1601
|
+
function backupFile(target, now = /* @__PURE__ */ new Date()) {
|
|
1602
|
+
return claimBackupPath(
|
|
1603
|
+
`${target}.backup-${formatStamp(now)}`,
|
|
1604
|
+
(c) => copyFileSync(target, c, fsConstants.COPYFILE_EXCL)
|
|
1605
|
+
);
|
|
1606
|
+
}
|
|
1607
|
+
function listFilesRecursive(dir, prefix = "") {
|
|
1608
|
+
if (!existsSync2(dir)) return [];
|
|
1609
|
+
const out = [];
|
|
1610
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
1611
|
+
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
1612
|
+
if (entry.isDirectory()) {
|
|
1613
|
+
out.push(...listFilesRecursive(join2(dir, entry.name), rel));
|
|
1614
|
+
} else if (entry.isFile()) {
|
|
1615
|
+
out.push(rel);
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
return out;
|
|
1619
|
+
}
|
|
1620
|
+
function formatStamp(now) {
|
|
1621
|
+
return now.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z").slice(0, 15);
|
|
1622
|
+
}
|
|
1623
|
+
function ensureProjectSkeleton(projectDir) {
|
|
1624
|
+
const dirs = [
|
|
1625
|
+
".claude/commands/uzys",
|
|
1626
|
+
".claude/rules",
|
|
1627
|
+
".claude/skills",
|
|
1628
|
+
".claude/agents",
|
|
1629
|
+
".claude/hooks",
|
|
1630
|
+
"docs/decisions"
|
|
1631
|
+
];
|
|
1632
|
+
for (const d of dirs) {
|
|
1633
|
+
mkdirSync(join2(projectDir, d), { recursive: true });
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
// src/install-log.ts
|
|
1638
|
+
var INSTALL_LOG_FILENAME = ".harness-install.json";
|
|
1639
|
+
var INSTALL_LOG_DIR = ".uzys-agent-harness";
|
|
1640
|
+
var LEGACY_INSTALL_LOG_DIR = ".claude";
|
|
1252
1641
|
var INSTALL_LOG_VERSION = 1;
|
|
1253
1642
|
var POLICY_DIRS = [
|
|
1254
1643
|
{ dir: "rules", ext: ".md" },
|
|
@@ -1371,247 +1760,69 @@ function isHarnessOwned(baseline, key, current) {
|
|
|
1371
1760
|
function mergeExternalFiles(projectDir, previous, current) {
|
|
1372
1761
|
const byPath = /* @__PURE__ */ new Map();
|
|
1373
1762
|
for (const file of [...previous ?? [], ...current]) byPath.set(file.path, file);
|
|
1374
|
-
return [...byPath.values()].filter((f) =>
|
|
1763
|
+
return [...byPath.values()].filter((f) => existsSync3(join3(projectDir, f.path)));
|
|
1375
1764
|
}
|
|
1376
|
-
function collectSkillHashes(projectDir) {
|
|
1377
|
-
const skillsDir =
|
|
1378
|
-
return listFilesRecursive(skillsDir).map((rel) => ({
|
|
1765
|
+
function collectSkillHashes(projectDir, templatesDir) {
|
|
1766
|
+
const skillsDir = join3(projectDir, ".claude/skills");
|
|
1767
|
+
return listFilesRecursive(skillsDir).filter((rel) => existsSync3(join3(templatesDir, "skills", rel))).map((rel) => ({
|
|
1379
1768
|
path: rel,
|
|
1380
|
-
sha256: hashContent(
|
|
1769
|
+
sha256: hashContent(readFileSync3(join3(skillsDir, rel), "utf8"))
|
|
1381
1770
|
}));
|
|
1382
1771
|
}
|
|
1383
1772
|
function collectPolicyHashes(projectDir, templatesDir) {
|
|
1384
1773
|
const out = [];
|
|
1385
1774
|
for (const { dir, ext } of POLICY_DIRS) {
|
|
1386
|
-
const targetDir =
|
|
1387
|
-
const sourceDir =
|
|
1775
|
+
const targetDir = join3(projectDir, ".claude", dir);
|
|
1776
|
+
const sourceDir = join3(templatesDir, dir);
|
|
1388
1777
|
for (const rel of listFilesRecursive(targetDir)) {
|
|
1389
1778
|
if (!rel.endsWith(ext)) continue;
|
|
1390
|
-
if (!
|
|
1779
|
+
if (!existsSync3(join3(sourceDir, rel))) continue;
|
|
1391
1780
|
out.push({
|
|
1392
1781
|
path: `${dir}/${rel}`,
|
|
1393
|
-
sha256: hashContent(
|
|
1782
|
+
sha256: hashContent(readFileSync3(join3(targetDir, rel), "utf8"))
|
|
1394
1783
|
});
|
|
1395
1784
|
}
|
|
1396
|
-
}
|
|
1397
|
-
return out;
|
|
1398
|
-
}
|
|
1399
|
-
function writeInstallLog(projectDir, log) {
|
|
1400
|
-
const path2 = installLogPath(projectDir);
|
|
1401
|
-
mkdirSync2(dirname2(path2), { recursive: true });
|
|
1402
|
-
writeFileSync(path2, `${JSON.stringify(log, null, 2)}
|
|
1403
|
-
`, "utf8");
|
|
1404
|
-
migrateAwayLegacyLog(projectDir);
|
|
1405
|
-
return path2;
|
|
1406
|
-
}
|
|
1407
|
-
function migrateAwayLegacyLog(projectDir) {
|
|
1408
|
-
const legacy = legacyInstallLogPath(projectDir);
|
|
1409
|
-
if (!existsSync2(legacy)) return;
|
|
1410
|
-
try {
|
|
1411
|
-
rmSync(legacy);
|
|
1412
|
-
} catch {
|
|
1413
|
-
}
|
|
1414
|
-
}
|
|
1415
|
-
function readInstallLog(projectDir) {
|
|
1416
|
-
const path2 = [installLogPath(projectDir), legacyInstallLogPath(projectDir)].find(
|
|
1417
|
-
(p) => existsSync2(p)
|
|
1418
|
-
);
|
|
1419
|
-
if (!path2) return null;
|
|
1420
|
-
try {
|
|
1421
|
-
const parsed = JSON.parse(readFileSync2(path2, "utf8"));
|
|
1422
|
-
if (Array.isArray(parsed.assets)) {
|
|
1423
|
-
parsed.assets = parsed.assets.map(
|
|
1424
|
-
(a) => a.method === "npm-global" ? { ...a, method: "npm" } : a
|
|
1425
|
-
);
|
|
1426
|
-
}
|
|
1427
|
-
return parsed;
|
|
1428
|
-
} catch {
|
|
1429
|
-
return null;
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
|
-
function installLogPath(projectDir) {
|
|
1433
|
-
return join2(projectDir, INSTALL_LOG_DIR, INSTALL_LOG_FILENAME);
|
|
1434
|
-
}
|
|
1435
|
-
function legacyInstallLogPath(projectDir) {
|
|
1436
|
-
return join2(projectDir, LEGACY_INSTALL_LOG_DIR, INSTALL_LOG_FILENAME);
|
|
1437
|
-
}
|
|
1438
|
-
|
|
1439
|
-
// src/project-claude-merge.ts
|
|
1440
|
-
init_esm_shims();
|
|
1441
|
-
var HARNESS_ANCHOR_FILE = "CLAUDE-uzys-harness.md";
|
|
1442
|
-
var HARNESS_IMPORT_LINE = `@${HARNESS_ANCHOR_FILE}`;
|
|
1443
|
-
var IMPORT_MARKER_START = "<!-- uzys-harness:import:start -->";
|
|
1444
|
-
var IMPORT_MARKER_END = "<!-- uzys-harness:import:end -->";
|
|
1445
|
-
function renderContinuousSkillsNote(selectedInternalSkills) {
|
|
1446
|
-
const lines = CONTINUOUS_SKILLS.filter((s) => selectedInternalSkills.includes(s.id)).map(
|
|
1447
|
-
(s) => `- \`${s.id}\` \u2014 ${s.whenToApply}.`
|
|
1448
|
-
);
|
|
1449
|
-
if (lines.length === 0) return "";
|
|
1450
|
-
return [
|
|
1451
|
-
"## Skills that apply continuously",
|
|
1452
|
-
"",
|
|
1453
|
-
"These installed skills apply to every response or delegation; no prompt ever looks like their job, so open them from this list:",
|
|
1454
|
-
"",
|
|
1455
|
-
...lines
|
|
1456
|
-
].join("\n");
|
|
1457
|
-
}
|
|
1458
|
-
function withContinuousSkillsNote(scaffold, selectedInternalSkills) {
|
|
1459
|
-
const note = renderContinuousSkillsNote(selectedInternalSkills);
|
|
1460
|
-
return note ? `${scaffold}
|
|
1461
|
-
|
|
1462
|
-
${note}` : scaffold;
|
|
1463
|
-
}
|
|
1464
|
-
function importBlock(continuousSkills) {
|
|
1465
|
-
const note = renderContinuousSkillsNote(continuousSkills);
|
|
1466
|
-
const body = note ? `${HARNESS_IMPORT_LINE}
|
|
1467
|
-
|
|
1468
|
-
${note}` : HARNESS_IMPORT_LINE;
|
|
1469
|
-
return `${IMPORT_MARKER_START}
|
|
1470
|
-
${body}
|
|
1471
|
-
${IMPORT_MARKER_END}`;
|
|
1472
|
-
}
|
|
1473
|
-
var TRACK_DISPLAY_NAMES = {
|
|
1474
|
-
tooling: "Tooling",
|
|
1475
|
-
"csr-supabase": "CSR Supabase",
|
|
1476
|
-
"csr-fastify": "CSR Fastify",
|
|
1477
|
-
"csr-fastapi": "CSR FastAPI",
|
|
1478
|
-
"ssr-htmx": "SSR HTMX",
|
|
1479
|
-
"ssr-nextjs": "SSR Next.js",
|
|
1480
|
-
data: "Data",
|
|
1481
|
-
executive: "Executive",
|
|
1482
|
-
full: "Full",
|
|
1483
|
-
"project-management": "Project Management",
|
|
1484
|
-
"growth-marketing": "Growth Marketing"
|
|
1485
|
-
};
|
|
1486
|
-
var FULL_EXPANSION = TRACKS.filter((t) => t !== "full");
|
|
1487
|
-
var FILL_SECTIONS = [
|
|
1488
|
-
"identity",
|
|
1489
|
-
"stack",
|
|
1490
|
-
"architecture",
|
|
1491
|
-
"installed-assets",
|
|
1492
|
-
"boundaries",
|
|
1493
|
-
"verify"
|
|
1494
|
-
];
|
|
1495
|
-
var FILL_SPECS = {
|
|
1496
|
-
identity: {
|
|
1497
|
-
title: "Identity & Purpose",
|
|
1498
|
-
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.`,
|
|
1499
|
-
placeholder: "what this project is, who it is for, and why it exists"
|
|
1500
|
-
},
|
|
1501
|
-
stack: {
|
|
1502
|
-
title: "Stack & Commands",
|
|
1503
|
-
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.",
|
|
1504
|
-
placeholder: "languages, runtimes, package manager, and the install/build/test/run commands"
|
|
1505
|
-
},
|
|
1506
|
-
architecture: {
|
|
1507
|
-
title: "Architecture & Layout",
|
|
1508
|
-
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.",
|
|
1509
|
-
placeholder: "where things live, the entry points, and how data flows between layers"
|
|
1510
|
-
},
|
|
1511
|
-
"installed-assets": {
|
|
1512
|
-
title: "Installed Harness Assets",
|
|
1513
|
-
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.",
|
|
1514
|
-
placeholder: "the installed rules/skills/agents/commands and when to use each"
|
|
1515
|
-
},
|
|
1516
|
-
boundaries: {
|
|
1517
|
-
title: "Boundaries \u2014 Always / Ask First / Never",
|
|
1518
|
-
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.`,
|
|
1519
|
-
placeholder: "what to always do, ask before doing, and never do in this repo"
|
|
1520
|
-
},
|
|
1521
|
-
verify: {
|
|
1522
|
-
title: "Verification Gate",
|
|
1523
|
-
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.',
|
|
1524
|
-
placeholder: "the single command that proves a change is safe, and the done bar"
|
|
1525
|
-
}
|
|
1526
|
-
};
|
|
1527
|
-
function anchorLine(surface) {
|
|
1528
|
-
if (surface === "claude") {
|
|
1529
|
-
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**.`;
|
|
1530
|
-
}
|
|
1531
|
-
const self = surface === "antigravity-rule" ? "`.agents/rules/uzys-harness.md`" : "`AGENTS.md`";
|
|
1532
|
-
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.`;
|
|
1533
|
-
}
|
|
1534
|
-
function scaffoldBanner(surface) {
|
|
1535
|
-
return [
|
|
1536
|
-
"> \u2699\uFE0F **SCAFFOLD \u2014 not filled in yet.** The sections below are a fill-in template for THIS project, not verified facts.",
|
|
1537
|
-
"> 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.",
|
|
1538
|
-
anchorLine(surface)
|
|
1539
|
-
].join("\n");
|
|
1540
|
-
}
|
|
1541
|
-
function renderFillScaffold(surface = "claude") {
|
|
1542
|
-
const blocks = FILL_SECTIONS.map((id) => {
|
|
1543
|
-
const spec = FILL_SPECS[id];
|
|
1544
|
-
return `## ${spec.title}
|
|
1545
|
-
|
|
1546
|
-
<!-- FILL:${id} \u2014 ${spec.prompt} -->
|
|
1547
|
-
|
|
1548
|
-
_(not filled yet \u2014 ${spec.placeholder})_`;
|
|
1549
|
-
});
|
|
1550
|
-
return `${scaffoldBanner(surface)}
|
|
1551
|
-
|
|
1552
|
-
${blocks.join("\n\n")}`;
|
|
1553
|
-
}
|
|
1554
|
-
function mergeProjectClaude(opts) {
|
|
1555
|
-
const expanded = expandTracks(opts.tracks);
|
|
1556
|
-
const trackList = expanded.map((t) => TRACK_DISPLAY_NAMES[t]).join(", ");
|
|
1557
|
-
const header = `# ${opts.projectName}
|
|
1558
|
-
|
|
1559
|
-
> Active track(s): ${trackList}`;
|
|
1560
|
-
return `${header}
|
|
1561
|
-
|
|
1562
|
-
${renderFillScaffold()}
|
|
1563
|
-
`;
|
|
1564
|
-
}
|
|
1565
|
-
function upsertHarnessImport(existing, opts) {
|
|
1566
|
-
const block = importBlock(opts.continuousSkills ?? []);
|
|
1567
|
-
if (existing === null) {
|
|
1568
|
-
return `${mergeProjectClaude(opts)}
|
|
1569
|
-
${block}
|
|
1570
|
-
`;
|
|
1571
|
-
}
|
|
1572
|
-
const start = existing.indexOf(IMPORT_MARKER_START);
|
|
1573
|
-
if (start !== -1) {
|
|
1574
|
-
const endAt = existing.indexOf(IMPORT_MARKER_END, start);
|
|
1575
|
-
if (endAt === -1) return existing;
|
|
1576
|
-
const current = existing.slice(start, endAt + IMPORT_MARKER_END.length);
|
|
1577
|
-
if (current === block) return existing;
|
|
1578
|
-
return `${existing.slice(0, start)}${block}${existing.slice(endAt + IMPORT_MARKER_END.length)}`;
|
|
1579
|
-
}
|
|
1580
|
-
if (hasHarnessImport(existing)) {
|
|
1581
|
-
return existing;
|
|
1582
|
-
}
|
|
1583
|
-
const body = existing.endsWith("\n") ? existing : `${existing}
|
|
1584
|
-
`;
|
|
1585
|
-
return `${body}
|
|
1586
|
-
${block}
|
|
1587
|
-
`;
|
|
1785
|
+
}
|
|
1786
|
+
return out;
|
|
1588
1787
|
}
|
|
1589
|
-
function
|
|
1590
|
-
const
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
return `${before}${after}`;
|
|
1788
|
+
function writeInstallLog(projectDir, log) {
|
|
1789
|
+
const path2 = installLogPath(projectDir);
|
|
1790
|
+
mkdirSync2(dirname2(path2), { recursive: true });
|
|
1791
|
+
writeFileSync(path2, `${JSON.stringify(log, null, 2)}
|
|
1792
|
+
`, "utf8");
|
|
1793
|
+
migrateAwayLegacyLog(projectDir);
|
|
1794
|
+
return path2;
|
|
1597
1795
|
}
|
|
1598
|
-
function
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
continue;
|
|
1605
|
-
}
|
|
1606
|
-
if (!inFence && line.trim() === HARNESS_IMPORT_LINE) return true;
|
|
1796
|
+
function migrateAwayLegacyLog(projectDir) {
|
|
1797
|
+
const legacy = legacyInstallLogPath(projectDir);
|
|
1798
|
+
if (!existsSync3(legacy)) return;
|
|
1799
|
+
try {
|
|
1800
|
+
rmSync(legacy);
|
|
1801
|
+
} catch {
|
|
1607
1802
|
}
|
|
1608
|
-
return false;
|
|
1609
1803
|
}
|
|
1610
|
-
function
|
|
1611
|
-
|
|
1612
|
-
|
|
1804
|
+
function readInstallLog(projectDir) {
|
|
1805
|
+
const path2 = [installLogPath(projectDir), legacyInstallLogPath(projectDir)].find(
|
|
1806
|
+
(p) => existsSync3(p)
|
|
1807
|
+
);
|
|
1808
|
+
if (!path2) return null;
|
|
1809
|
+
try {
|
|
1810
|
+
const parsed = JSON.parse(readFileSync3(path2, "utf8"));
|
|
1811
|
+
if (Array.isArray(parsed.assets)) {
|
|
1812
|
+
parsed.assets = parsed.assets.map(
|
|
1813
|
+
(a) => a.method === "npm-global" ? { ...a, method: "npm" } : a
|
|
1814
|
+
);
|
|
1815
|
+
}
|
|
1816
|
+
return parsed;
|
|
1817
|
+
} catch {
|
|
1818
|
+
return null;
|
|
1613
1819
|
}
|
|
1614
|
-
|
|
1820
|
+
}
|
|
1821
|
+
function installLogPath(projectDir) {
|
|
1822
|
+
return join3(projectDir, INSTALL_LOG_DIR, INSTALL_LOG_FILENAME);
|
|
1823
|
+
}
|
|
1824
|
+
function legacyInstallLogPath(projectDir) {
|
|
1825
|
+
return join3(projectDir, LEGACY_INSTALL_LOG_DIR, INSTALL_LOG_FILENAME);
|
|
1615
1826
|
}
|
|
1616
1827
|
|
|
1617
1828
|
// src/manifest.ts
|
|
@@ -1634,6 +1845,8 @@ var COMMON_RULES = ["git-policy", "change-management", "doc-governance"];
|
|
|
1634
1845
|
var DEV_RULES = ["test-policy", "ship-checklist"];
|
|
1635
1846
|
var UI_RULES = [];
|
|
1636
1847
|
var TRACK_RULES = {
|
|
1848
|
+
base: [],
|
|
1849
|
+
// #456 — 공통 룰만. cli-development 는 tooling·full 전용
|
|
1637
1850
|
"csr-supabase": [],
|
|
1638
1851
|
"csr-fastify": [],
|
|
1639
1852
|
"csr-fastapi": [],
|
|
@@ -1893,199 +2106,6 @@ var CATEGORY_TITLES = {
|
|
|
1893
2106
|
"ecc-suite": "\u{1F4E6} ECC Suite"
|
|
1894
2107
|
};
|
|
1895
2108
|
|
|
1896
|
-
// src/context-cost.ts
|
|
1897
|
-
init_esm_shims();
|
|
1898
|
-
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
1899
|
-
import { join as join3, resolve } from "path";
|
|
1900
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1901
|
-
var CHARS_PER_TOKEN = 4;
|
|
1902
|
-
function estimateTokens(chars) {
|
|
1903
|
-
return Math.ceil(chars / CHARS_PER_TOKEN);
|
|
1904
|
-
}
|
|
1905
|
-
function resolveBundleRoot() {
|
|
1906
|
-
return resolve(fileURLToPath2(new URL(".", import.meta.url)), "..");
|
|
1907
|
-
}
|
|
1908
|
-
function extractFrontmatter(content) {
|
|
1909
|
-
const m = content.match(/^?---\r?\n([\s\S]*?)\r?\n---/);
|
|
1910
|
-
return m?.[1] ?? null;
|
|
1911
|
-
}
|
|
1912
|
-
function assetDescriptorTokens(assetId, root = resolveBundleRoot()) {
|
|
1913
|
-
const asset = EXTERNAL_ASSETS.find((a) => a.id === assetId);
|
|
1914
|
-
if (!asset || asset.method.kind !== "internal") return null;
|
|
1915
|
-
const skillMd = join3(root, "templates", "skills", asset.method.key, "SKILL.md");
|
|
1916
|
-
if (!existsSync3(skillMd)) return null;
|
|
1917
|
-
const fm = extractFrontmatter(readFileSync3(skillMd, "utf8"));
|
|
1918
|
-
return fm === null ? null : estimateTokens(fm.length);
|
|
1919
|
-
}
|
|
1920
|
-
function assetBodyTokens(assetId, root = resolveBundleRoot()) {
|
|
1921
|
-
const asset = EXTERNAL_ASSETS.find((a) => a.id === assetId);
|
|
1922
|
-
if (!asset || asset.method.kind !== "internal") return null;
|
|
1923
|
-
const skillMd = join3(root, "templates", "skills", asset.method.key, "SKILL.md");
|
|
1924
|
-
if (!existsSync3(skillMd)) return null;
|
|
1925
|
-
const content = readFileSync3(skillMd, "utf8");
|
|
1926
|
-
const fm = extractFrontmatter(content);
|
|
1927
|
-
if (fm === null) return estimateTokens(content.length);
|
|
1928
|
-
const close = content.indexOf("\n---", content.indexOf(fm) + fm.length);
|
|
1929
|
-
const body = close === -1 ? "" : content.slice(close + "\n---".length);
|
|
1930
|
-
return estimateTokens(body.trim().length);
|
|
1931
|
-
}
|
|
1932
|
-
function assetCostRows(assetIds, root = resolveBundleRoot()) {
|
|
1933
|
-
return assetIds.map((id) => ({
|
|
1934
|
-
id,
|
|
1935
|
-
descriptorTokens: assetDescriptorTokens(id, root),
|
|
1936
|
-
bodyTokens: assetBodyTokens(id, root)
|
|
1937
|
-
})).sort((a, b) => (b.bodyTokens ?? -1) - (a.bodyTokens ?? -1));
|
|
1938
|
-
}
|
|
1939
|
-
function makeResidentCost(base) {
|
|
1940
|
-
return {
|
|
1941
|
-
...base,
|
|
1942
|
-
perSkillDescriptor: base.perSkillDescriptor ?? {},
|
|
1943
|
-
total: base.rules + base.projectClaudeMd + base.skillDescriptors + base.agentDescriptors,
|
|
1944
|
-
directive: {
|
|
1945
|
-
items: base.items.rules + base.items.claudeMd,
|
|
1946
|
-
tokens: base.rules + base.projectClaudeMd
|
|
1947
|
-
},
|
|
1948
|
-
firing: {
|
|
1949
|
-
items: base.items.skills + base.items.agents,
|
|
1950
|
-
tokens: base.skillDescriptors + base.agentDescriptors
|
|
1951
|
-
}
|
|
1952
|
-
};
|
|
1953
|
-
}
|
|
1954
|
-
function fileTokens(path2) {
|
|
1955
|
-
return existsSync3(path2) ? estimateTokens(readFileSync3(path2, "utf8").trim().length) : 0;
|
|
1956
|
-
}
|
|
1957
|
-
function descriptorTokens(path2) {
|
|
1958
|
-
if (!existsSync3(path2)) return 0;
|
|
1959
|
-
const fm = extractFrontmatter(readFileSync3(path2, "utf8"));
|
|
1960
|
-
return fm === null ? 0 : estimateTokens(fm.length);
|
|
1961
|
-
}
|
|
1962
|
-
function residentCost(entries, root = resolveBundleRoot()) {
|
|
1963
|
-
const tpl = (source) => join3(root, "templates", source);
|
|
1964
|
-
const measured = (e) => e.file ?? tpl(e.source);
|
|
1965
|
-
const perSkill = {};
|
|
1966
|
-
let rules = 0;
|
|
1967
|
-
let skillDescriptors = 0;
|
|
1968
|
-
let agentDescriptors = 0;
|
|
1969
|
-
let ruleItems = 0;
|
|
1970
|
-
let skillItems = 0;
|
|
1971
|
-
let agentItems = 0;
|
|
1972
|
-
for (const e of entries) {
|
|
1973
|
-
if (e.target.startsWith(".claude/rules/")) {
|
|
1974
|
-
rules += fileTokens(measured(e));
|
|
1975
|
-
ruleItems += 1;
|
|
1976
|
-
} else if (e.target.startsWith(".claude/agents/")) {
|
|
1977
|
-
agentDescriptors += descriptorTokens(measured(e));
|
|
1978
|
-
agentItems += 1;
|
|
1979
|
-
} else if (e.target.startsWith(".claude/skills/")) {
|
|
1980
|
-
const t = descriptorTokens(join3(measured(e), "SKILL.md"));
|
|
1981
|
-
skillDescriptors += t;
|
|
1982
|
-
skillItems += 1;
|
|
1983
|
-
const id = /\.claude\/skills\/([^/]+)/.exec(e.target)?.[1];
|
|
1984
|
-
if (id !== void 0) perSkill[id] = (perSkill[id] ?? 0) + t;
|
|
1985
|
-
}
|
|
1986
|
-
}
|
|
1987
|
-
const harnessAnchor = fileTokens(join3(root, "templates", "CLAUDE.md"));
|
|
1988
|
-
const projectScaffold = estimateTokens(
|
|
1989
|
-
withContinuousSkillsNote(
|
|
1990
|
-
renderFillScaffold(),
|
|
1991
|
-
CONTINUOUS_SKILLS.map((s) => s.id)
|
|
1992
|
-
).trim().length
|
|
1993
|
-
);
|
|
1994
|
-
const projectClaudeMd = harnessAnchor + projectScaffold;
|
|
1995
|
-
const claudeMdItems = (harnessAnchor > 0 ? 1 : 0) + (projectScaffold > 0 ? 1 : 0);
|
|
1996
|
-
return makeResidentCost({
|
|
1997
|
-
rules,
|
|
1998
|
-
projectClaudeMd,
|
|
1999
|
-
skillDescriptors,
|
|
2000
|
-
agentDescriptors,
|
|
2001
|
-
perSkillDescriptor: perSkill,
|
|
2002
|
-
items: {
|
|
2003
|
-
rules: ruleItems,
|
|
2004
|
-
skills: skillItems,
|
|
2005
|
-
agents: agentItems,
|
|
2006
|
-
claudeMd: claudeMdItems,
|
|
2007
|
-
total: ruleItems + skillItems + agentItems + claudeMdItems
|
|
2008
|
-
}
|
|
2009
|
-
});
|
|
2010
|
-
}
|
|
2011
|
-
function summarizeContextCost(assetIds, root = resolveBundleRoot()) {
|
|
2012
|
-
let measuredTokens = 0;
|
|
2013
|
-
let measuredCount = 0;
|
|
2014
|
-
let unmeasuredCount = 0;
|
|
2015
|
-
for (const id of assetIds) {
|
|
2016
|
-
const tokens = assetDescriptorTokens(id, root);
|
|
2017
|
-
if (tokens === null) unmeasuredCount += 1;
|
|
2018
|
-
else {
|
|
2019
|
-
measuredTokens += tokens;
|
|
2020
|
-
measuredCount += 1;
|
|
2021
|
-
}
|
|
2022
|
-
}
|
|
2023
|
-
return { measuredTokens, measuredCount, unmeasuredCount };
|
|
2024
|
-
}
|
|
2025
|
-
function formatResidentCostLine(r, unmeasuredCount) {
|
|
2026
|
-
if (r.total === 0) return null;
|
|
2027
|
-
const parts = [
|
|
2028
|
-
`directives ${r.directive.items} ~${r.directive.tokens}`,
|
|
2029
|
-
`[rules ${r.items.rules} ~${r.rules} \xB7 CLAUDE.md ${r.items.claudeMd} ~${r.projectClaudeMd}]`,
|
|
2030
|
-
`triggers ${r.firing.items} ~${r.firing.tokens}`,
|
|
2031
|
-
`[skills ${r.items.skills} ~${r.skillDescriptors} \xB7 agents ${r.items.agents} ~${r.agentDescriptors}]`
|
|
2032
|
-
].join(" \xB7 ");
|
|
2033
|
-
const external = unmeasuredCount > 0 ? ` \xB7 ${unmeasuredCount} external asset${unmeasuredCount === 1 ? "" : "s"} unmeasured` : "";
|
|
2034
|
-
return `session-start context cost: ${r.items.total} items resident \xB7 ~${r.total} tokens/session (${parts}${external})`;
|
|
2035
|
-
}
|
|
2036
|
-
var RESIDENT_ROWS = [
|
|
2037
|
-
{
|
|
2038
|
-
// ADR-083 — 축 소계를 **행으로** 낸다. 소계가 없으면 읽는 사람이 네 줄을 머리로 더해야
|
|
2039
|
-
// 하고, 그러면 "무엇을 줄여야 하나"가 표에서 안 보인다.
|
|
2040
|
-
label: "\uC9C0\uC2DC\uBB38",
|
|
2041
|
-
gap: 13,
|
|
2042
|
-
count: (r) => r.directive.items,
|
|
2043
|
-
tokens: (r) => r.directive.tokens
|
|
2044
|
-
},
|
|
2045
|
-
{ label: " rules", gap: 12, count: (r) => r.items.rules, tokens: (r) => r.rules },
|
|
2046
|
-
{
|
|
2047
|
-
// 한 행에 두 파일(앵커 + 스캐폴드). 개수 열이 2 를 뱉어 그 사실을 드러낸다 — 라벨로
|
|
2048
|
-
// 하나만 이름 붙이면 v26.140.0 이전과 같은 거짓 라벨이 된다.
|
|
2049
|
-
label: " CLAUDE.md",
|
|
2050
|
-
gap: 8,
|
|
2051
|
-
count: (r) => r.items.claudeMd,
|
|
2052
|
-
tokens: (r) => r.projectClaudeMd
|
|
2053
|
-
},
|
|
2054
|
-
{
|
|
2055
|
-
label: "\uBC1C\uD654 \uD45C\uBA74",
|
|
2056
|
-
gap: 10,
|
|
2057
|
-
count: (r) => r.firing.items,
|
|
2058
|
-
tokens: (r) => r.firing.tokens
|
|
2059
|
-
},
|
|
2060
|
-
{
|
|
2061
|
-
label: " skill descriptors",
|
|
2062
|
-
gap: 0,
|
|
2063
|
-
count: (r) => r.items.skills,
|
|
2064
|
-
tokens: (r) => r.skillDescriptors
|
|
2065
|
-
},
|
|
2066
|
-
{
|
|
2067
|
-
label: " agent descriptors",
|
|
2068
|
-
gap: 0,
|
|
2069
|
-
count: (r) => r.items.agents,
|
|
2070
|
-
tokens: (r) => r.agentDescriptors
|
|
2071
|
-
}
|
|
2072
|
-
];
|
|
2073
|
-
function formatResidentCostBlock(r) {
|
|
2074
|
-
const n = (v) => String(v).padStart(3);
|
|
2075
|
-
return [
|
|
2076
|
-
" \uAC1C\uC218 \uD1A0\uD070",
|
|
2077
|
-
...RESIDENT_ROWS.map(
|
|
2078
|
-
(row) => ` ${row.label}${" ".repeat(row.gap)}${n(row.count(r))}\uAC1C ~${row.tokens(r)}`
|
|
2079
|
-
),
|
|
2080
|
-
" \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",
|
|
2081
|
-
` \uC0C1\uC8FC \uD569\uACC4 ${n(r.items.total)}\uAC1C \uC0C1\uC8FC \xB7 ~${r.total} tokens/\uC138\uC158`,
|
|
2082
|
-
"",
|
|
2083
|
-
" \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",
|
|
2084
|
-
" '\uBD88\uD544\uC694\uD55C \uC9C0\uC2DC\uBB38\uC774 \uC788\uB098'\uB85C \uD55C\uB2E4. ratchet \uC740 \uC870\uC6A9\uD55C \uC99D\uAC00\uB9CC \uB9C9\uB294\uB2E4.",
|
|
2085
|
-
" \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."
|
|
2086
|
-
];
|
|
2087
|
-
}
|
|
2088
|
-
|
|
2089
2109
|
export {
|
|
2090
2110
|
__commonJS,
|
|
2091
2111
|
__toESM,
|
|
@@ -2099,6 +2119,7 @@ export {
|
|
|
2099
2119
|
isInstallScope,
|
|
2100
2120
|
resolveScope,
|
|
2101
2121
|
DEFAULT_OPTIONS,
|
|
2122
|
+
UPDATE_GROUPS,
|
|
2102
2123
|
EXTERNAL_ASSETS,
|
|
2103
2124
|
DEV_METHOD_SKILL_IDS,
|
|
2104
2125
|
INTERNAL_BUNDLED_SKILL_IDS,
|
|
@@ -2112,6 +2133,21 @@ export {
|
|
|
2112
2133
|
assetCliSupport,
|
|
2113
2134
|
assetReachesCli,
|
|
2114
2135
|
filterApplicableAssets,
|
|
2136
|
+
HARNESS_ANCHOR_FILE,
|
|
2137
|
+
HARNESS_IMPORT_LINE,
|
|
2138
|
+
withContinuousSkillsNote,
|
|
2139
|
+
renderFillScaffold,
|
|
2140
|
+
upsertHarnessImport,
|
|
2141
|
+
stripHarnessImport,
|
|
2142
|
+
estimateTokens,
|
|
2143
|
+
resolveBundleRoot,
|
|
2144
|
+
extractFrontmatter,
|
|
2145
|
+
assetCostRows,
|
|
2146
|
+
landsOnDisk,
|
|
2147
|
+
residentCost,
|
|
2148
|
+
summarizeContextCost,
|
|
2149
|
+
formatResidentCostLine,
|
|
2150
|
+
formatResidentCostBlock,
|
|
2115
2151
|
ensureDir,
|
|
2116
2152
|
copyFile,
|
|
2117
2153
|
copyDir,
|
|
@@ -2133,12 +2169,6 @@ export {
|
|
|
2133
2169
|
readInstallLog,
|
|
2134
2170
|
installLogPath,
|
|
2135
2171
|
legacyInstallLogPath,
|
|
2136
|
-
HARNESS_ANCHOR_FILE,
|
|
2137
|
-
HARNESS_IMPORT_LINE,
|
|
2138
|
-
withContinuousSkillsNote,
|
|
2139
|
-
renderFillScaffold,
|
|
2140
|
-
upsertHarnessImport,
|
|
2141
|
-
stripHarnessImport,
|
|
2142
2172
|
buildAssetSpec,
|
|
2143
2173
|
resolveRules,
|
|
2144
2174
|
ALL_RULES,
|
|
@@ -2148,13 +2178,6 @@ export {
|
|
|
2148
2178
|
isCliNeutralTarget,
|
|
2149
2179
|
buildManifest,
|
|
2150
2180
|
CATEGORIES,
|
|
2151
|
-
CATEGORY_TITLES
|
|
2152
|
-
estimateTokens,
|
|
2153
|
-
resolveBundleRoot,
|
|
2154
|
-
assetCostRows,
|
|
2155
|
-
residentCost,
|
|
2156
|
-
summarizeContextCost,
|
|
2157
|
-
formatResidentCostLine,
|
|
2158
|
-
formatResidentCostBlock
|
|
2181
|
+
CATEGORY_TITLES
|
|
2159
2182
|
};
|
|
2160
|
-
//# sourceMappingURL=chunk-
|
|
2183
|
+
//# sourceMappingURL=chunk-SC6ORMHV.js.map
|