@uzysjung/agent-harness 26.101.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/dist/index.js CHANGED
@@ -2,20 +2,29 @@
2
2
  import {
3
3
  CATEGORIES,
4
4
  CATEGORY_TITLES,
5
+ CLI_BASES,
6
+ DEFAULT_OPTIONS,
5
7
  DEV_METHOD_SKILL_IDS,
6
8
  EXTERNAL_ASSETS,
7
9
  INTERNAL_BUNDLED_SKILL_IDS,
10
+ TRACKS,
8
11
  __commonJS,
9
12
  __toESM,
10
13
  anyTrack,
14
+ assetCliSupport,
15
+ assetReachesCli,
11
16
  assetTrustTier,
12
17
  experimentalOptInCandidates,
13
18
  filterApplicableAssets,
14
19
  hasDevTrack,
15
20
  hasUiTrack,
16
21
  init_esm_shims,
17
- isAssetSelected
18
- } from "./chunk-S3SQJRYD.js";
22
+ isAssetSelected,
23
+ isCliBase,
24
+ isInstallScope,
25
+ isTrack,
26
+ resolveScope
27
+ } from "./chunk-4QB5UQDP.js";
19
28
 
20
29
  // node_modules/sisteransi/src/index.js
21
30
  var require_src = __commonJS({
@@ -700,7 +709,7 @@ var cac = (name = "") => new CAC(name);
700
709
  // package.json
701
710
  var package_default = {
702
711
  name: "@uzysjung/agent-harness",
703
- version: "26.101.0",
712
+ version: "26.103.0",
704
713
  description: "Curate vetted AI-coding skills & plugins by your tech stack \u2014 install only what you need, across Claude Code, Codex, OpenCode & Antigravity",
705
714
  type: "module",
706
715
  publishConfig: {
@@ -754,47 +763,11 @@ var package_default = {
754
763
 
755
764
  // src/commands/install.ts
756
765
  init_esm_shims();
757
- import { resolve as resolve2 } from "path";
766
+ import { resolve as resolve3 } from "path";
767
+ import { fileURLToPath as fileURLToPath2 } from "url";
758
768
 
759
769
  // src/cli-targets.ts
760
770
  init_esm_shims();
761
-
762
- // src/types.ts
763
- init_esm_shims();
764
- var TRACKS = [
765
- "tooling",
766
- "csr-supabase",
767
- "csr-fastify",
768
- "csr-fastapi",
769
- "ssr-htmx",
770
- "ssr-nextjs",
771
- "data",
772
- "executive",
773
- "full",
774
- "project-management",
775
- "growth-marketing"
776
- ];
777
- function isTrack(value) {
778
- return typeof value === "string" && TRACKS.includes(value);
779
- }
780
- var CLI_BASES = ["claude", "codex", "opencode", "antigravity"];
781
- function isCliBase(value) {
782
- return typeof value === "string" && CLI_BASES.includes(value);
783
- }
784
- var INSTALL_SCOPES = ["project", "global"];
785
- function isInstallScope(value) {
786
- return typeof value === "string" && INSTALL_SCOPES.includes(value);
787
- }
788
- function resolveScope(scope) {
789
- return scope ?? "project";
790
- }
791
- var DEFAULT_OPTIONS = {
792
- withPrune: false,
793
- withCodexTrust: false,
794
- withKarpathyHook: false
795
- };
796
-
797
- // src/cli-targets.ts
798
771
  var CLI_BASE_SORT_ORDER = {
799
772
  claude: 0,
800
773
  codex: 1,
@@ -1497,6 +1470,15 @@ import { existsSync as existsSync6, readdirSync, readFileSync as readFileSync6 }
1497
1470
  import { homedir as homedir2 } from "os";
1498
1471
  import { join as join6 } from "path";
1499
1472
  var DEFAULT_SPAWN_TIMEOUT_MS = 12e4;
1473
+ function selectExternalTargets(assets, ctx) {
1474
+ const conditionPassed = filterApplicableAssets(assets, ctx).filter(
1475
+ (a) => a.method.kind !== "internal"
1476
+ );
1477
+ return {
1478
+ targets: conditionPassed.filter((a) => assetReachesCli(a, ctx.cli)),
1479
+ excludedByCli: conditionPassed.filter((a) => !assetReachesCli(a, ctx.cli))
1480
+ };
1481
+ }
1500
1482
  function runExternalInstall(ctx, deps = {}) {
1501
1483
  const log = deps.log ?? console.log;
1502
1484
  const warn = deps.warn ?? console.error;
@@ -1504,9 +1486,7 @@ function runExternalInstall(ctx, deps = {}) {
1504
1486
  const assets = deps.assets ?? EXTERNAL_ASSETS;
1505
1487
  const harnessRoot = deps.harnessRoot ?? process.cwd();
1506
1488
  const projectDir = ctx.projectDir ?? process.cwd();
1507
- const applicable = filterApplicableAssets(assets, ctx).filter(
1508
- (a) => a.method.kind !== "internal"
1509
- );
1489
+ const { targets: applicable, excludedByCli } = selectExternalTargets(assets, ctx);
1510
1490
  const sorted = [...applicable].sort((a, b2) => {
1511
1491
  const ai = CATEGORIES.indexOf(a.category);
1512
1492
  const bi = CATEGORIES.indexOf(b2.category);
@@ -1533,7 +1513,8 @@ function runExternalInstall(ctx, deps = {}) {
1533
1513
  return {
1534
1514
  attempted,
1535
1515
  succeeded: attempted.filter((r) => r.ok).length,
1536
- skipped: attempted.filter((r) => !r.ok).length
1516
+ skipped: attempted.filter((r) => !r.ok).length,
1517
+ excludedByCli
1537
1518
  };
1538
1519
  }
1539
1520
  function installOne(asset, ctx) {
@@ -2612,7 +2593,10 @@ function runExternalPhase(ctx) {
2612
2593
  options: spec.options,
2613
2594
  ...spec.userOverride ? { userOverride: spec.userOverride } : {}
2614
2595
  };
2615
- const applicableCount = filterApplicableAssets(EXTERNAL_ASSETS, filterCtx).length;
2596
+ const applicableCount = selectExternalTargets(EXTERNAL_ASSETS, {
2597
+ ...filterCtx,
2598
+ cli: spec.cli
2599
+ }).targets.length;
2616
2600
  ctx.onProgress?.({ type: "external-start", assetCount: applicableCount });
2617
2601
  const external = runExt(
2618
2602
  { ...filterCtx, cli: spec.cli, projectDir, ...spec.scope ? { scope: spec.scope } : {} },
@@ -2736,6 +2720,54 @@ function listHookFiles(hookDir) {
2736
2720
  // src/commands/install-render.ts
2737
2721
  init_esm_shims();
2738
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
+
2739
2771
  // src/preset-recommend.ts
2740
2772
  init_esm_shims();
2741
2773
  function recommendedExternalAssets(presets) {
@@ -2791,10 +2823,17 @@ function renderInstallHeader(log, spec, mode) {
2791
2823
  log(infoRow("OPTIONS", formatOptions(spec)));
2792
2824
  const finalAssets = finalSelectedAssets(spec.tracks, spec.userOverride);
2793
2825
  if (finalAssets.length > 0) {
2794
- log(infoRow("ASSETS", `${finalAssets.length} selected`));
2826
+ const unreachable = finalAssets.filter((id) => {
2827
+ const asset = EXTERNAL_ASSETS.find((a) => a.id === id);
2828
+ return asset ? !assetReachesCli(asset, spec.cli) : false;
2829
+ });
2830
+ const label = unreachable.length > 0 ? `${finalAssets.length} selected (${unreachable.length} outside [${spec.cli.join(", ")}] reach \u2014 not installed)` : `${finalAssets.length} selected`;
2831
+ log(infoRow("ASSETS", label));
2795
2832
  for (const [cat, ids] of groupAssetsByCategory(finalAssets)) {
2796
2833
  log(` ${c.dim(`\xB7 ${cat}:`)} ${ids.join(", ")}`);
2797
2834
  }
2835
+ const cost = formatContextCostLine(summarizeContextCost(finalAssets));
2836
+ if (cost) log(` ${c.dim(`\xB7 ${cost}`)}`);
2798
2837
  }
2799
2838
  log("");
2800
2839
  }
@@ -2804,12 +2843,32 @@ function createInstallRenderer(log, spec, verbose) {
2804
2843
  const callbacks = {
2805
2844
  onProgress: (event) => {
2806
2845
  if (event.type === "baseline-complete") {
2807
- const eccSelected = isAssetSelected("ecc-plugin", spec) || spec.options.withPrune === true;
2808
- renderPhase1Rows(log, event.baseline, verbose, eccSelected);
2846
+ const claudeSelected = targetsInclude(spec.cli, "claude");
2847
+ const eccWillInstall = claudeSelected && (isAssetSelected("ecc-plugin", spec) || spec.options.withPrune === true);
2848
+ renderPhase1Rows(log, event.baseline, verbose, eccWillInstall, claudeSelected);
2809
2849
  } else if (event.type === "external-start" && event.assetCount > 0) {
2810
2850
  log(unifiedSection(`External assets (${event.assetCount})`));
2811
2851
  log("");
2812
2852
  phase2HeaderPrinted = true;
2853
+ } else if (event.type === "external-complete") {
2854
+ const excluded = event.report.excludedByCli;
2855
+ if (excluded.length > 0) {
2856
+ if (!phase2HeaderPrinted) {
2857
+ log(unifiedSection("External assets (0)"));
2858
+ phase2HeaderPrinted = true;
2859
+ }
2860
+ const bySupport = /* @__PURE__ */ new Map();
2861
+ for (const a of excluded) {
2862
+ const key = assetCliSupport(a).join("/");
2863
+ bySupport.set(key, [...bySupport.get(key) ?? [], a.id]);
2864
+ }
2865
+ log("");
2866
+ for (const [support, ids] of bySupport) {
2867
+ log(
2868
+ ` ${c.dim(`\xB7 ${ids.length} asset(s) not installed \u2014 requires ${support}, selected [${spec.cli.join(", ")}]: ${ids.join(", ")}`)}`
2869
+ );
2870
+ }
2871
+ }
2813
2872
  }
2814
2873
  },
2815
2874
  externalDeps: {
@@ -2917,22 +2976,17 @@ function renderFinalSummary(log, spec, report, fromWizard) {
2917
2976
  )
2918
2977
  );
2919
2978
  }
2920
- const nonClaudeCli = spec.cli.filter((b2) => b2 !== "claude");
2921
- if (nonClaudeCli.length > 0) {
2922
- const claudeOnlyPlugins = EXTERNAL_ASSETS.filter(
2923
- (a) => a.method.kind === "plugin" && isAssetSelected(a.id, spec)
2924
- );
2925
- if (claudeOnlyPlugins.length > 0) {
2926
- log("");
2927
- log(
2928
- infoRow(
2929
- "NOTE",
2930
- c.dim(
2931
- `${claudeOnlyPlugins.length} plugin asset${claudeOnlyPlugins.length > 1 ? "s" : ""} are Claude Code-only \u2014 not installed for ${nonClaudeCli.map((b2) => CLI_SUMMARY_LABELS[b2]).join("/")}: ${claudeOnlyPlugins.map((a) => a.id).join(", ")}`
2932
- )
2979
+ if (report.external && report.external.excludedByCli.length > 0) {
2980
+ const excluded = report.external.excludedByCli;
2981
+ log("");
2982
+ log(
2983
+ infoRow(
2984
+ "EXCLUDED",
2985
+ c.dim(
2986
+ `${excluded.length} asset${excluded.length > 1 ? "s" : ""} not installed \u2014 outside [${spec.cli.join(", ")}] reach: ${excluded.map((a) => a.id).join(", ")}`
2933
2987
  )
2934
- );
2935
- }
2988
+ )
2989
+ );
2936
2990
  }
2937
2991
  if (!fromWizard) {
2938
2992
  const optIn = experimentalOptInCandidates(spec);
@@ -2992,7 +3046,7 @@ function formatAssetMeta(asset, version) {
2992
3046
  return `internal \xB7 templates (${m.key})`;
2993
3047
  }
2994
3048
  }
2995
- function renderPhase1Rows(log, baseline, verbose = false, withEcc = false) {
3049
+ function renderPhase1Rows(log, baseline, verbose = false, withEcc = false, claudeSelected = true) {
2996
3050
  if (baseline.updateMode) {
2997
3051
  if (baseline.backup) {
2998
3052
  log(assetRow("success", "backup", shortenPath(baseline.backup)));
@@ -3115,7 +3169,9 @@ function renderPhase1Rows(log, baseline, verbose = false, withEcc = false) {
3115
3169
  log(
3116
3170
  ` ${c.dim("\xB7")} ${c.dim("ECC plugin not selected \u2014 cherry-pick fallback active (up to 4 agents + 8 skills + 3 commands)")}`
3117
3171
  );
3118
- log(` ${c.dim("\xB7")} ${c.dim("Use --with ecc-plugin to install ECC plugin instead")}`);
3172
+ if (claudeSelected) {
3173
+ log(` ${c.dim("\xB7")} ${c.dim("Use --with ecc-plugin to install ECC plugin instead")}`);
3174
+ }
3119
3175
  }
3120
3176
  if (baseline.envFiles.envExampleCreated) {
3121
3177
  log(assetRow("success", ".env.example", "Supabase token guide"));
@@ -3243,7 +3299,7 @@ function installAction(options, deps = {}) {
3243
3299
  withKarpathyHook: options.withKarpathyHook === true
3244
3300
  },
3245
3301
  cli: validated.cli,
3246
- projectDir: resolve2(options.projectDir ?? process.cwd()),
3302
+ projectDir: resolve3(options.projectDir ?? process.cwd()),
3247
3303
  scope: resolveScopeOption(options.scope, err)
3248
3304
  };
3249
3305
  executeSpec(spec, {
@@ -3312,7 +3368,7 @@ function defaultRunPipeline(spec, harnessRoot, mode, callbacks) {
3312
3368
  return runInstall(ctx);
3313
3369
  }
3314
3370
  function defaultHarnessRoot() {
3315
- return resolve2(new URL(".", import.meta.url).pathname, "..");
3371
+ return resolve3(fileURLToPath2(new URL(".", import.meta.url)), "..");
3316
3372
  }
3317
3373
  function registerInstallCommand(cli2) {
3318
3374
  cli2.command("install", "Install harness assets into a project").option("--track <name>", "[Track] Track to install (repeatable)", { type: [String] }).option(
@@ -3347,15 +3403,15 @@ function registerInstallCommand(cli2) {
3347
3403
  // src/commands/uninstall.ts
3348
3404
  init_esm_shims();
3349
3405
  import { spawnSync as spawnSync2 } from "child_process";
3350
- import { existsSync as existsSync12, readFileSync as readFileSync12, rmSync } from "fs";
3351
- 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";
3352
3408
  function uninstallAction(options, deps = {}) {
3353
3409
  const log = deps.log ?? console.log;
3354
3410
  const err = deps.err ?? console.error;
3355
3411
  const exit = deps.exit ?? ((code) => process.exit(code));
3356
3412
  const spawn = deps.spawn ?? defaultSpawn2;
3357
3413
  const rm = deps.rm ?? defaultRm;
3358
- const projectDir = resolve3(options.projectDir ?? process.cwd());
3414
+ const projectDir = resolve4(options.projectDir ?? process.cwd());
3359
3415
  const installLog = readInstallLog(projectDir);
3360
3416
  if (!installLog) {
3361
3417
  err(status.failure(c.red(`ERROR: install log not found at ${installLogPath(projectDir)}`)));
@@ -3520,22 +3576,22 @@ function buildGlobalAdvisoryCmd(asset) {
3520
3576
  }
3521
3577
  }
3522
3578
  function removeTemplates(log, projectDir, rm) {
3523
- rm(join11(projectDir, log.templates.claudeDir));
3524
- if (log.templates.codexDir) rm(join11(projectDir, log.templates.codexDir));
3525
- 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));
3526
3582
  const rootMd = log.templates.rootClaudeMd;
3527
3583
  if (rootMd) {
3528
3584
  if (rootClaudeMdModified(log, projectDir)) return { rootClaudeMdKept: true };
3529
- rm(join11(projectDir, rootMd.path));
3585
+ rm(join12(projectDir, rootMd.path));
3530
3586
  }
3531
3587
  return { rootClaudeMdKept: false };
3532
3588
  }
3533
3589
  function rootClaudeMdModified(log, projectDir) {
3534
3590
  const rootMd = log.templates.rootClaudeMd;
3535
3591
  if (!rootMd) return false;
3536
- const path = join11(projectDir, rootMd.path);
3537
- if (!existsSync12(path)) return false;
3538
- 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;
3539
3595
  }
3540
3596
  function formatTemplateList(log) {
3541
3597
  const items = [log.templates.claudeDir];
@@ -3547,7 +3603,7 @@ function defaultSpawn2(cmd, args) {
3547
3603
  return spawnSync2(cmd, [...args], { encoding: "utf8", stdio: "pipe", timeout: 12e4 });
3548
3604
  }
3549
3605
  function defaultRm(path) {
3550
- if (existsSync12(path)) {
3606
+ if (existsSync13(path)) {
3551
3607
  rmSync(path, { recursive: true, force: true });
3552
3608
  }
3553
3609
  }
@@ -4841,8 +4897,8 @@ Proceed?`,
4841
4897
 
4842
4898
  // src/state.ts
4843
4899
  init_esm_shims();
4844
- import { existsSync as existsSync13, readFileSync as readFileSync13 } from "fs";
4845
- import { join as join12 } from "path";
4900
+ import { existsSync as existsSync14, readFileSync as readFileSync14 } from "fs";
4901
+ import { join as join13 } from "path";
4846
4902
  var META_FILE = ".claude/.installed-tracks";
4847
4903
  var LEGACY_SIGNATURES = [
4848
4904
  { rule: "htmx.md", track: "ssr-htmx" },
@@ -4852,13 +4908,13 @@ var LEGACY_SIGNATURES = [
4852
4908
  { rule: "cli-development.md", track: "tooling" }
4853
4909
  ];
4854
4910
  function detectInstallState(projectDir) {
4855
- const claudeDir = join12(projectDir, ".claude");
4856
- const hasClaudeDir = existsSync13(claudeDir);
4911
+ const claudeDir = join13(projectDir, ".claude");
4912
+ const hasClaudeDir = existsSync14(claudeDir);
4857
4913
  if (!hasClaudeDir) {
4858
4914
  return { state: "new", tracks: [], source: "none", hasClaudeDir: false };
4859
4915
  }
4860
- const metaPath = join12(projectDir, META_FILE);
4861
- if (existsSync13(metaPath)) {
4916
+ const metaPath = join13(projectDir, META_FILE);
4917
+ if (existsSync14(metaPath)) {
4862
4918
  const tracks2 = readMetafile(metaPath);
4863
4919
  return { state: "existing", tracks: tracks2, source: "metafile", hasClaudeDir: true };
4864
4920
  }
@@ -4866,7 +4922,7 @@ function detectInstallState(projectDir) {
4866
4922
  return { state: "existing", tracks, source: "legacy", hasClaudeDir: true };
4867
4923
  }
4868
4924
  function readMetafile(path) {
4869
- const raw = readFileSync13(path, "utf8");
4925
+ const raw = readFileSync14(path, "utf8");
4870
4926
  const seen = /* @__PURE__ */ new Set();
4871
4927
  for (const line of raw.split(/\s+/)) {
4872
4928
  const trimmed = line.trim();
@@ -4877,13 +4933,13 @@ function readMetafile(path) {
4877
4933
  return [...seen].sort();
4878
4934
  }
4879
4935
  function inferFromLegacySignatures(projectDir) {
4880
- const rulesDir = join12(projectDir, ".claude/rules");
4881
- if (!existsSync13(rulesDir)) {
4936
+ const rulesDir = join13(projectDir, ".claude/rules");
4937
+ if (!existsSync14(rulesDir)) {
4882
4938
  return [];
4883
4939
  }
4884
4940
  const found = /* @__PURE__ */ new Set();
4885
4941
  for (const sig of LEGACY_SIGNATURES) {
4886
- if (existsSync13(join12(rulesDir, sig.rule))) {
4942
+ if (existsSync14(join13(rulesDir, sig.rule))) {
4887
4943
  found.add(sig.track);
4888
4944
  }
4889
4945
  }
@@ -5086,10 +5142,18 @@ function formatSummary(spec) {
5086
5142
  ];
5087
5143
  const finalAssets = finalSelectedAssets(spec.tracks, spec.userOverride);
5088
5144
  if (finalAssets.length > 0) {
5089
- lines.push(`Assets: ${finalAssets.length} selected`);
5145
+ const unreachable = finalAssets.filter((id) => {
5146
+ const asset = EXTERNAL_ASSETS.find((a) => a.id === id);
5147
+ return asset ? !assetReachesCli(asset, spec.cli) : false;
5148
+ });
5149
+ lines.push(
5150
+ unreachable.length > 0 ? `Assets: ${finalAssets.length} selected (${unreachable.length} outside [${spec.cli.join(", ")}] reach \u2014 not installed)` : `Assets: ${finalAssets.length} selected`
5151
+ );
5090
5152
  for (const [cat, ids] of groupAssetsByCategory(finalAssets)) {
5091
5153
  lines.push(` \xB7 ${cat}: ${ids.join(", ")}`);
5092
5154
  }
5155
+ const cost = formatContextCostLine(summarizeContextCost(finalAssets));
5156
+ if (cost) lines.push(` \xB7 ${cost}`);
5093
5157
  }
5094
5158
  if (spec.userOverride) {
5095
5159
  if (spec.userOverride.forceInclude.length > 0) {