@uzysjung/agent-harness 26.102.0 → 26.103.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.md CHANGED
@@ -71,6 +71,26 @@ Curation, not a list to browse — pick `csr-supabase` and step 3 pre-checks exa
71
71
 
72
72
  ---
73
73
 
74
+ ## Curation philosophy — lean by default
75
+
76
+ > Maintainer's direction statement (2026-07). The catalog itself is unchanged by this section — it describes how curation decisions are made going forward.
77
+
78
+ Frontier coding agents (Opus 4.8 / GPT-5.6 class) keep absorbing what skill packs used to teach. Our working position: **a skill has to earn its context cost** — every installed skill occupies the agent's attention even when unused, so the default install stays minimal and "more skills" is treated as a cost, not a feature.
79
+
80
+ What we believe still earns its place is the **insight layer**, not the knowledge layer:
81
+
82
+ - **Orchestration know-how** — model/effort role splits (`model-orchestration`), multi-perspective verification (`multi-persona-review`), auditing your own harness (`harness-health-audit`).
83
+ - **Cross-CLI leverage** — use each CLI for what it's best at: natural prose polish via Antigravity (`gemini-consult`), concise structuring + image generation via Codex (`codex-consult`).
84
+ - **Operational facts** — CLI flags, auth flows, deploy steps (`supabase-cli`, `railway-skills`, …) drift with upstream releases no matter how smart models get. Generic pattern guides are the first to become redundant.
85
+
86
+ Structured workflow bundles (superpowers, BMAD, OpenSpec, …) stay **opt-in and never pre-checked**. Our view: with strong models, an agile direction plus a firm git/PR policy beats enforced ceremony for solo/greenfield work — but multi-dev alignment, junior onboarding, and audit trails are human-side problems that no model upgrade solves, so the choice stays yours ([Workflow curation guide](docs/WORKFLOWS.md)).
87
+
88
+ What stays fixed is small and deterministic: git/PR policy, verification gates, honest reporting. Within those rails the agent develops flexibly — including running several agents in parallel in one session (safety conventions for that, like worktree isolation, are roadmap items, not shipped features). Since v26.103.0 the installer shows the **session-start context cost** of your selection (bundled skills measured from frontmatter, external assets honestly marked unmeasured), and our test suite holds the bundled-skill descriptors of the default install under a token budget (external assets stay unmeasured).
89
+
90
+ One honest note: this repository's *own* development harness is deliberately heavier than what it installs — it self-verifies an installer that writes into your project. Shipped defaults ≠ our dev rig.
91
+
92
+ ---
93
+
74
94
  ## Installing into an existing project
75
95
 
76
96
  `agent-harness` never silently overwrites your config. Before replacing an **editable** file whose contents differ, it writes a timestamped backup next to it — and every backup path is printed in the install summary (`backup` rows). Nothing is deleted.
@@ -200,12 +220,15 @@ External assets are recommended automatically based on your track selection. Ste
200
220
  | `asis-tobe-decision` ★ | Present an A-or-B / approval moment as context → recommendation → option table → AS-IS/TO-BE contrast | this project (core) |
201
221
  | `compaction-handoff` ★ | Persist durable state + git snapshot + resume anchor before a context `/compact` | this project (core) |
202
222
  | `northstar-roadmap` ★ | Measure current state vs the vision doc → ranked feature backlog persisted to docs/plans + memory | this project (core) |
223
+ | `model-orchestration` ★ | Model orchestration policy — role split (orchestrator directs/reviews · strong model authors core/V&V · mid model does repetitive impl/E2E) + effort floors + quota handoff | this project (core) |
224
+ | `harness-health-audit` ★ | Audit your CLAUDE.md/rules/skills/hooks on 4 questions a linter can't answer: TRUE · USED · AFFORDABLE · SAFE | this project (core) |
203
225
 
204
- **First-party advisor** (`official`, **opt-in** — `--with gemini-consult`; repo-bundled, installs across all 4 CLIs; needs the external Antigravity [`agy`](https://antigravity.google/cli) CLI at runtime):
226
+ **First-party advisors** (`official`, **opt-in** — `--with gemini-consult` / `--with codex-consult`; repo-bundled, install across all 4 CLIs; each needs its external CLI at runtime — Antigravity [`agy`](https://antigravity.google/cli) or OpenAI `codex`):
205
227
 
206
228
  | Asset | What | Source |
207
229
  |---|---|---|
208
- | `gemini-consult` | Consult Gemini (via `agy`) for natural **Korean** phrasing + **multi-persona** second-opinion review — an idiomatic, independent second model | this project |
230
+ | `gemini-consult` | Consult Gemini (via `agy`) for natural **Korean** phrasing + **multi-persona** second-opinion review + image generation — an idiomatic, independent second model | this project |
231
+ | `codex-consult` | Consult OpenAI Codex (`codex exec`) for **concise / structured** rewriting + **image generation** (real PNG on disk) — division of labor: nuance/persona → gemini, concision/structure/images → codex | this project |
209
232
 
210
233
  ### Security & ECC (opt-in)
211
234
 
package/dist/index.js CHANGED
@@ -709,7 +709,7 @@ var cac = (name = "") => new CAC(name);
709
709
  // package.json
710
710
  var package_default = {
711
711
  name: "@uzysjung/agent-harness",
712
- version: "26.102.0",
712
+ version: "26.103.0",
713
713
  description: "Curate vetted AI-coding skills & plugins by your tech stack \u2014 install only what you need, across Claude Code, Codex, OpenCode & Antigravity",
714
714
  type: "module",
715
715
  publishConfig: {
@@ -763,7 +763,8 @@ var package_default = {
763
763
 
764
764
  // src/commands/install.ts
765
765
  init_esm_shims();
766
- import { resolve as resolve2 } from "path";
766
+ import { resolve as resolve3 } from "path";
767
+ import { fileURLToPath as fileURLToPath2 } from "url";
767
768
 
768
769
  // src/cli-targets.ts
769
770
  init_esm_shims();
@@ -2719,6 +2720,54 @@ function listHookFiles(hookDir) {
2719
2720
  // src/commands/install-render.ts
2720
2721
  init_esm_shims();
2721
2722
 
2723
+ // src/context-cost.ts
2724
+ init_esm_shims();
2725
+ import { existsSync as existsSync12, readFileSync as readFileSync12 } from "fs";
2726
+ import { join as join11, resolve as resolve2 } from "path";
2727
+ import { fileURLToPath } from "url";
2728
+ var CHARS_PER_TOKEN = 4;
2729
+ function estimateTokens(chars) {
2730
+ return Math.ceil(chars / CHARS_PER_TOKEN);
2731
+ }
2732
+ function resolveBundleRoot() {
2733
+ return resolve2(fileURLToPath(new URL(".", import.meta.url)), "..");
2734
+ }
2735
+ function extractFrontmatter(content) {
2736
+ const m = content.match(/^?---\r?\n([\s\S]*?)\r?\n---/);
2737
+ return m?.[1] ?? null;
2738
+ }
2739
+ function assetDescriptorTokens(assetId, root = resolveBundleRoot()) {
2740
+ const asset = EXTERNAL_ASSETS.find((a) => a.id === assetId);
2741
+ if (!asset || asset.method.kind !== "internal") return null;
2742
+ const skillMd = join11(root, "templates", "skills", asset.method.key, "SKILL.md");
2743
+ if (!existsSync12(skillMd)) return null;
2744
+ const fm = extractFrontmatter(readFileSync12(skillMd, "utf8"));
2745
+ return fm === null ? null : estimateTokens(fm.length);
2746
+ }
2747
+ function summarizeContextCost(assetIds, root = resolveBundleRoot()) {
2748
+ let measuredTokens = 0;
2749
+ let measuredCount = 0;
2750
+ let unmeasuredCount = 0;
2751
+ for (const id of assetIds) {
2752
+ const tokens = assetDescriptorTokens(id, root);
2753
+ if (tokens === null) unmeasuredCount += 1;
2754
+ else {
2755
+ measuredTokens += tokens;
2756
+ measuredCount += 1;
2757
+ }
2758
+ }
2759
+ return { measuredTokens, measuredCount, unmeasuredCount };
2760
+ }
2761
+ function formatContextCostLine(s) {
2762
+ if (s.measuredCount === 0 && s.unmeasuredCount === 0) return null;
2763
+ if (s.measuredCount === 0) {
2764
+ return `session-start context cost: unmeasured (${s.unmeasuredCount} external asset${s.unmeasuredCount === 1 ? "" : "s"})`;
2765
+ }
2766
+ const skills = `${s.measuredCount} bundled skill${s.measuredCount === 1 ? "" : "s"} measured`;
2767
+ const external = s.unmeasuredCount > 0 ? ` \xB7 ${s.unmeasuredCount} external unmeasured` : "";
2768
+ return `session-start context cost: ~${s.measuredTokens} tokens (${skills}${external})`;
2769
+ }
2770
+
2722
2771
  // src/preset-recommend.ts
2723
2772
  init_esm_shims();
2724
2773
  function recommendedExternalAssets(presets) {
@@ -2783,6 +2832,8 @@ function renderInstallHeader(log, spec, mode) {
2783
2832
  for (const [cat, ids] of groupAssetsByCategory(finalAssets)) {
2784
2833
  log(` ${c.dim(`\xB7 ${cat}:`)} ${ids.join(", ")}`);
2785
2834
  }
2835
+ const cost = formatContextCostLine(summarizeContextCost(finalAssets));
2836
+ if (cost) log(` ${c.dim(`\xB7 ${cost}`)}`);
2786
2837
  }
2787
2838
  log("");
2788
2839
  }
@@ -3248,7 +3299,7 @@ function installAction(options, deps = {}) {
3248
3299
  withKarpathyHook: options.withKarpathyHook === true
3249
3300
  },
3250
3301
  cli: validated.cli,
3251
- projectDir: resolve2(options.projectDir ?? process.cwd()),
3302
+ projectDir: resolve3(options.projectDir ?? process.cwd()),
3252
3303
  scope: resolveScopeOption(options.scope, err)
3253
3304
  };
3254
3305
  executeSpec(spec, {
@@ -3317,7 +3368,7 @@ function defaultRunPipeline(spec, harnessRoot, mode, callbacks) {
3317
3368
  return runInstall(ctx);
3318
3369
  }
3319
3370
  function defaultHarnessRoot() {
3320
- return resolve2(new URL(".", import.meta.url).pathname, "..");
3371
+ return resolve3(fileURLToPath2(new URL(".", import.meta.url)), "..");
3321
3372
  }
3322
3373
  function registerInstallCommand(cli2) {
3323
3374
  cli2.command("install", "Install harness assets into a project").option("--track <name>", "[Track] Track to install (repeatable)", { type: [String] }).option(
@@ -3352,15 +3403,15 @@ function registerInstallCommand(cli2) {
3352
3403
  // src/commands/uninstall.ts
3353
3404
  init_esm_shims();
3354
3405
  import { spawnSync as spawnSync2 } from "child_process";
3355
- import { existsSync as existsSync12, readFileSync as readFileSync12, rmSync } from "fs";
3356
- import { join as join11, resolve as resolve3 } from "path";
3406
+ import { existsSync as existsSync13, readFileSync as readFileSync13, rmSync } from "fs";
3407
+ import { join as join12, resolve as resolve4 } from "path";
3357
3408
  function uninstallAction(options, deps = {}) {
3358
3409
  const log = deps.log ?? console.log;
3359
3410
  const err = deps.err ?? console.error;
3360
3411
  const exit = deps.exit ?? ((code) => process.exit(code));
3361
3412
  const spawn = deps.spawn ?? defaultSpawn2;
3362
3413
  const rm = deps.rm ?? defaultRm;
3363
- const projectDir = resolve3(options.projectDir ?? process.cwd());
3414
+ const projectDir = resolve4(options.projectDir ?? process.cwd());
3364
3415
  const installLog = readInstallLog(projectDir);
3365
3416
  if (!installLog) {
3366
3417
  err(status.failure(c.red(`ERROR: install log not found at ${installLogPath(projectDir)}`)));
@@ -3525,22 +3576,22 @@ function buildGlobalAdvisoryCmd(asset) {
3525
3576
  }
3526
3577
  }
3527
3578
  function removeTemplates(log, projectDir, rm) {
3528
- rm(join11(projectDir, log.templates.claudeDir));
3529
- if (log.templates.codexDir) rm(join11(projectDir, log.templates.codexDir));
3530
- if (log.templates.opencodeDir) rm(join11(projectDir, log.templates.opencodeDir));
3579
+ rm(join12(projectDir, log.templates.claudeDir));
3580
+ if (log.templates.codexDir) rm(join12(projectDir, log.templates.codexDir));
3581
+ if (log.templates.opencodeDir) rm(join12(projectDir, log.templates.opencodeDir));
3531
3582
  const rootMd = log.templates.rootClaudeMd;
3532
3583
  if (rootMd) {
3533
3584
  if (rootClaudeMdModified(log, projectDir)) return { rootClaudeMdKept: true };
3534
- rm(join11(projectDir, rootMd.path));
3585
+ rm(join12(projectDir, rootMd.path));
3535
3586
  }
3536
3587
  return { rootClaudeMdKept: false };
3537
3588
  }
3538
3589
  function rootClaudeMdModified(log, projectDir) {
3539
3590
  const rootMd = log.templates.rootClaudeMd;
3540
3591
  if (!rootMd) return false;
3541
- const path = join11(projectDir, rootMd.path);
3542
- if (!existsSync12(path)) return false;
3543
- return hashContent(readFileSync12(path, "utf8")) !== rootMd.sha256;
3592
+ const path = join12(projectDir, rootMd.path);
3593
+ if (!existsSync13(path)) return false;
3594
+ return hashContent(readFileSync13(path, "utf8")) !== rootMd.sha256;
3544
3595
  }
3545
3596
  function formatTemplateList(log) {
3546
3597
  const items = [log.templates.claudeDir];
@@ -3552,7 +3603,7 @@ function defaultSpawn2(cmd, args) {
3552
3603
  return spawnSync2(cmd, [...args], { encoding: "utf8", stdio: "pipe", timeout: 12e4 });
3553
3604
  }
3554
3605
  function defaultRm(path) {
3555
- if (existsSync12(path)) {
3606
+ if (existsSync13(path)) {
3556
3607
  rmSync(path, { recursive: true, force: true });
3557
3608
  }
3558
3609
  }
@@ -4846,8 +4897,8 @@ Proceed?`,
4846
4897
 
4847
4898
  // src/state.ts
4848
4899
  init_esm_shims();
4849
- import { existsSync as existsSync13, readFileSync as readFileSync13 } from "fs";
4850
- import { join as join12 } from "path";
4900
+ import { existsSync as existsSync14, readFileSync as readFileSync14 } from "fs";
4901
+ import { join as join13 } from "path";
4851
4902
  var META_FILE = ".claude/.installed-tracks";
4852
4903
  var LEGACY_SIGNATURES = [
4853
4904
  { rule: "htmx.md", track: "ssr-htmx" },
@@ -4857,13 +4908,13 @@ var LEGACY_SIGNATURES = [
4857
4908
  { rule: "cli-development.md", track: "tooling" }
4858
4909
  ];
4859
4910
  function detectInstallState(projectDir) {
4860
- const claudeDir = join12(projectDir, ".claude");
4861
- const hasClaudeDir = existsSync13(claudeDir);
4911
+ const claudeDir = join13(projectDir, ".claude");
4912
+ const hasClaudeDir = existsSync14(claudeDir);
4862
4913
  if (!hasClaudeDir) {
4863
4914
  return { state: "new", tracks: [], source: "none", hasClaudeDir: false };
4864
4915
  }
4865
- const metaPath = join12(projectDir, META_FILE);
4866
- if (existsSync13(metaPath)) {
4916
+ const metaPath = join13(projectDir, META_FILE);
4917
+ if (existsSync14(metaPath)) {
4867
4918
  const tracks2 = readMetafile(metaPath);
4868
4919
  return { state: "existing", tracks: tracks2, source: "metafile", hasClaudeDir: true };
4869
4920
  }
@@ -4871,7 +4922,7 @@ function detectInstallState(projectDir) {
4871
4922
  return { state: "existing", tracks, source: "legacy", hasClaudeDir: true };
4872
4923
  }
4873
4924
  function readMetafile(path) {
4874
- const raw = readFileSync13(path, "utf8");
4925
+ const raw = readFileSync14(path, "utf8");
4875
4926
  const seen = /* @__PURE__ */ new Set();
4876
4927
  for (const line of raw.split(/\s+/)) {
4877
4928
  const trimmed = line.trim();
@@ -4882,13 +4933,13 @@ function readMetafile(path) {
4882
4933
  return [...seen].sort();
4883
4934
  }
4884
4935
  function inferFromLegacySignatures(projectDir) {
4885
- const rulesDir = join12(projectDir, ".claude/rules");
4886
- if (!existsSync13(rulesDir)) {
4936
+ const rulesDir = join13(projectDir, ".claude/rules");
4937
+ if (!existsSync14(rulesDir)) {
4887
4938
  return [];
4888
4939
  }
4889
4940
  const found = /* @__PURE__ */ new Set();
4890
4941
  for (const sig of LEGACY_SIGNATURES) {
4891
- if (existsSync13(join12(rulesDir, sig.rule))) {
4942
+ if (existsSync14(join13(rulesDir, sig.rule))) {
4892
4943
  found.add(sig.track);
4893
4944
  }
4894
4945
  }
@@ -5101,6 +5152,8 @@ function formatSummary(spec) {
5101
5152
  for (const [cat, ids] of groupAssetsByCategory(finalAssets)) {
5102
5153
  lines.push(` \xB7 ${cat}: ${ids.join(", ")}`);
5103
5154
  }
5155
+ const cost = formatContextCostLine(summarizeContextCost(finalAssets));
5156
+ if (cost) lines.push(` \xB7 ${cost}`);
5104
5157
  }
5105
5158
  if (spec.userOverride) {
5106
5159
  if (spec.userOverride.forceInclude.length > 0) {