@gobing-ai/spur 0.4.30 → 0.4.31

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/spur.js CHANGED
@@ -43367,6 +43367,56 @@ var require_extra_typings = __commonJS(function(exports, module) {
43367
43367
  exports.createArgument = (name, description) => new commander.Argument(name, description);
43368
43368
  });
43369
43369
 
43370
+ // ../../packages/domain/src/analytics/models.ts
43371
+ var init_models = () => {};
43372
+
43373
+ // ../../packages/domain/src/analytics/costs.ts
43374
+ function cacheHitRatio(totals) {
43375
+ if (totals.recordsWithUsage === 0)
43376
+ return null;
43377
+ if (totals.inputTokens === 0)
43378
+ return null;
43379
+ return totals.cacheReadTokens / totals.inputTokens;
43380
+ }
43381
+ function formatSummary(summary) {
43382
+ const lines = [];
43383
+ const totalCost = summary.totals.costUsd.toFixed(2);
43384
+ const totalInput = (summary.totals.inputTokens / 1e6).toFixed(1);
43385
+ const totalOutput = (summary.totals.outputTokens / 1e6).toFixed(1);
43386
+ lines.push(`Total: ${totalInput}M input / ${totalOutput}M output tokens \xB7 $${totalCost} \xB7 ${summary.totals.records} records`);
43387
+ lines.push(`Cache hit: ${formatRatio(cacheHitRatio(summary.totals))} of input tokens served from cache`);
43388
+ lines.push("");
43389
+ lines.push("By source:");
43390
+ const sourceEntries = Object.entries(summary.bySource).sort(byCostDesc);
43391
+ for (const entry of sourceEntries) {
43392
+ const [source, stats] = entry;
43393
+ lines.push(` ${source.padEnd(12)} $${stats.costUsd.toFixed(2).padStart(8)} ${(stats.inputTokens / 1e6).toFixed(1).padStart(6)}M in ${(stats.outputTokens / 1e6).toFixed(1).padStart(6)}M out ${String(stats.records).padStart(6)} rec`);
43394
+ }
43395
+ lines.push("");
43396
+ lines.push("By model:");
43397
+ const modelEntries = Object.entries(summary.byModel).sort(byCostDesc);
43398
+ for (const entry of modelEntries) {
43399
+ const [model, stats] = entry;
43400
+ lines.push(` ${model.slice(0, 28).padEnd(28)} $${stats.costUsd.toFixed(2).padStart(8)} ${(stats.inputTokens / 1e6).toFixed(1).padStart(6)}M in ${(stats.outputTokens / 1e6).toFixed(1).padStart(6)}M out ${String(stats.records).padStart(6)} rec`);
43401
+ }
43402
+ return lines.join(`
43403
+ `);
43404
+ }
43405
+ function formatRatio(ratio) {
43406
+ return ratio === null ? "n/a" : `${(ratio * 100).toFixed(1)}%`;
43407
+ }
43408
+ function byCostDesc([, a], [, b]) {
43409
+ return b.costUsd - a.costUsd;
43410
+ }
43411
+ var init_costs = __esm(() => {
43412
+ init_models();
43413
+ });
43414
+
43415
+ // ../../packages/domain/src/analytics/antigravity-etl.ts
43416
+ var init_antigravity_etl = __esm(() => {
43417
+ init_costs();
43418
+ });
43419
+
43370
43420
  // ../../packages/domain/src/analytics/artifact.ts
43371
43421
  import { createHash } from "crypto";
43372
43422
  function selectorDigest(selector) {
@@ -43434,51 +43484,6 @@ var init_artifact_digest = __esm(() => {
43434
43484
  RANKED_ARTIFACT_KEYS = new Set(Object.entries(ARTIFACT_ARRAY_CLASSIFICATION).filter(([, kind]) => kind === "ranked").map(([key]) => key));
43435
43485
  });
43436
43486
 
43437
- // ../../packages/domain/src/analytics/models.ts
43438
- var init_models = () => {};
43439
-
43440
- // ../../packages/domain/src/analytics/costs.ts
43441
- function cacheHitRatio(totals) {
43442
- if (totals.recordsWithUsage === 0)
43443
- return null;
43444
- if (totals.inputTokens === 0)
43445
- return null;
43446
- return totals.cacheReadTokens / totals.inputTokens;
43447
- }
43448
- function formatSummary(summary) {
43449
- const lines = [];
43450
- const totalCost = summary.totals.costUsd.toFixed(2);
43451
- const totalInput = (summary.totals.inputTokens / 1e6).toFixed(1);
43452
- const totalOutput = (summary.totals.outputTokens / 1e6).toFixed(1);
43453
- lines.push(`Total: ${totalInput}M input / ${totalOutput}M output tokens \xB7 $${totalCost} \xB7 ${summary.totals.records} records`);
43454
- lines.push(`Cache hit: ${formatRatio(cacheHitRatio(summary.totals))} of input tokens served from cache`);
43455
- lines.push("");
43456
- lines.push("By source:");
43457
- const sourceEntries = Object.entries(summary.bySource).sort(byCostDesc);
43458
- for (const entry of sourceEntries) {
43459
- const [source, stats] = entry;
43460
- lines.push(` ${source.padEnd(12)} $${stats.costUsd.toFixed(2).padStart(8)} ${(stats.inputTokens / 1e6).toFixed(1).padStart(6)}M in ${(stats.outputTokens / 1e6).toFixed(1).padStart(6)}M out ${String(stats.records).padStart(6)} rec`);
43461
- }
43462
- lines.push("");
43463
- lines.push("By model:");
43464
- const modelEntries = Object.entries(summary.byModel).sort(byCostDesc);
43465
- for (const entry of modelEntries) {
43466
- const [model, stats] = entry;
43467
- lines.push(` ${model.slice(0, 28).padEnd(28)} $${stats.costUsd.toFixed(2).padStart(8)} ${(stats.inputTokens / 1e6).toFixed(1).padStart(6)}M in ${(stats.outputTokens / 1e6).toFixed(1).padStart(6)}M out ${String(stats.records).padStart(6)} rec`);
43468
- }
43469
- return lines.join(`
43470
- `);
43471
- }
43472
- function formatRatio(ratio) {
43473
- return ratio === null ? "n/a" : `${(ratio * 100).toFixed(1)}%`;
43474
- }
43475
- function byCostDesc([, a], [, b]) {
43476
- return b.costUsd - a.costUsd;
43477
- }
43478
- var init_costs = __esm(() => {
43479
- init_models();
43480
- });
43481
-
43482
43487
  // ../../packages/domain/src/analytics/derived.ts
43483
43488
  class MetricRegistry {
43484
43489
  fns = [];
@@ -43719,6 +43724,81 @@ async function deriveMissingAssistantDurations(db, ceilingMs) {
43719
43724
  `);
43720
43725
  }
43721
43726
 
43727
+ // ../../packages/domain/src/analytics/executor-latency.ts
43728
+ async function readExecutorLatency(db, keys) {
43729
+ const wanted = [...new Set(keys)].filter((key) => key.length > 0);
43730
+ if (wanted.length === 0)
43731
+ return new Map;
43732
+ try {
43733
+ const placeholders = wanted.map(() => "?").join(", ");
43734
+ const rows = await db.queryAll(`SELECT model, assistant_duration_ms, assistant_duration_samples, errors, tool_calls
43735
+ FROM history_board_model_stats WHERE model IN (${placeholders})`, ...wanted);
43736
+ const stats = new Map;
43737
+ for (const row of rows) {
43738
+ const samples = row.assistant_duration_samples;
43739
+ if (samples <= 0)
43740
+ continue;
43741
+ const successRate = row.tool_calls > 0 ? Math.min(1, Math.max(0, 1 - row.errors / row.tool_calls)) : 1;
43742
+ stats.set(row.model, {
43743
+ meanMs: row.assistant_duration_ms / samples,
43744
+ samples,
43745
+ successRate
43746
+ });
43747
+ }
43748
+ return stats;
43749
+ } catch {
43750
+ return new Map;
43751
+ }
43752
+ }
43753
+
43754
+ // ../../packages/domain/src/analytics/selector-time-range.ts
43755
+ function instantPrefilterBound(bound, slackMs) {
43756
+ const at = Date.parse(bound);
43757
+ return Number.isFinite(at) ? new Date(at + slackMs).toISOString() : undefined;
43758
+ }
43759
+ function buildTimeRangeClauses(range, target) {
43760
+ if (!COLUMN.test(target.column))
43761
+ throw new Error(`invalid time-range column: ${target.column}`);
43762
+ const bound = BOUND_SQL[target.grain];
43763
+ const clauses = [];
43764
+ const params = [];
43765
+ const column = COLUMN_SQL[target.grain](target.column);
43766
+ const prefilter = target.grain === "instant";
43767
+ if (range.since !== null) {
43768
+ const low = prefilter ? instantPrefilterBound(range.since, -INSTANT_PREFILTER_SLACK_MS) : undefined;
43769
+ if (low !== undefined) {
43770
+ clauses.push(`${target.column} >= ?`);
43771
+ params.push(low);
43772
+ }
43773
+ clauses.push(`${column} >= ${bound}`);
43774
+ params.push(range.since);
43775
+ }
43776
+ if (range.until !== null) {
43777
+ const high = prefilter ? instantPrefilterBound(range.until, INSTANT_PREFILTER_SLACK_MS) : undefined;
43778
+ if (high !== undefined) {
43779
+ clauses.push(`${target.column} <= ?`);
43780
+ params.push(high);
43781
+ }
43782
+ clauses.push(`${column} <= ${bound}`);
43783
+ params.push(range.until);
43784
+ }
43785
+ return { clauses, params };
43786
+ }
43787
+ var BOUND_SQL, COLUMN_SQL, COLUMN, INSTANT_PREFILTER_SLACK_MS = 1000;
43788
+ var init_selector_time_range = __esm(() => {
43789
+ BOUND_SQL = {
43790
+ day: "DATE(?)",
43791
+ timestamp: "strftime('%Y-%m-%dT%H:%M:%SZ', ?)",
43792
+ instant: "julianday(?)"
43793
+ };
43794
+ COLUMN_SQL = {
43795
+ day: (column) => column,
43796
+ timestamp: (column) => column,
43797
+ instant: (column) => `julianday(${column})`
43798
+ };
43799
+ COLUMN = /^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)?$/;
43800
+ });
43801
+
43722
43802
  // ../../packages/domain/src/analytics/tool-name-sql.ts
43723
43803
  var EFFECTIVE_TOOL_NAME_SQL = `CASE
43724
43804
  WHEN tc.tool_name IS NOT NULL AND TRIM(tc.tool_name) != '' AND tc.tool_name != 'unknown'
@@ -43809,13 +43889,10 @@ function escapeLike(value) {
43809
43889
  function buildMessageWhereClauses(sel, alias) {
43810
43890
  const clauses = [];
43811
43891
  const params = [];
43812
- if (sel.since != null) {
43813
- clauses.push(`${alias}.ts >= ?`);
43814
- params.push(sel.since);
43815
- }
43816
- if (sel.until != null) {
43817
- clauses.push(`${alias}.ts <= ?`);
43818
- params.push(sel.until);
43892
+ if (sel.since != null || sel.until != null) {
43893
+ const range = buildTimeRangeClauses({ since: sel.since ?? null, until: sel.until ?? null }, { column: `${alias}.ts`, grain: "instant" });
43894
+ clauses.push(...range.clauses);
43895
+ params.push(...range.params);
43819
43896
  }
43820
43897
  if (sel.sources != null && sel.sources.length > 0) {
43821
43898
  clauses.push(`${alias}.source IN (${sel.sources.map(() => "?").join(", ")})`);
@@ -43887,6 +43964,7 @@ function buildMessageWhereClauses(sel, alias) {
43887
43964
  return { clauses, params };
43888
43965
  }
43889
43966
  var init_message_where = __esm(() => {
43967
+ init_selector_time_range();
43890
43968
  init_tool_alias();
43891
43969
  });
43892
43970
 
@@ -44132,8 +44210,8 @@ async function bySession(db, sel, top, opts) {
44132
44210
  SUM(COALESCE(cache_read_tokens, 0)) AS cache_read_tokens,
44133
44211
  SUM(COALESCE(output_tokens, 0)) AS output_tokens,
44134
44212
  SUM(cost_usd) AS cost_usd,
44135
- SUM(CASE WHEN role = 'assistant' THEN COALESCE(duration_ms, 0) ELSE 0 END) AS assistant_duration_ms,
44136
- SUM(CASE WHEN role = 'assistant' AND duration_ms IS NULL THEN 1 ELSE 0 END) AS assistant_duration_unmeasured
44213
+ SUM(CASE WHEN role = 'assistant' AND disposition <> 'poll-duplicate' THEN COALESCE(duration_ms, 0) ELSE 0 END) AS assistant_duration_ms,
44214
+ SUM(CASE WHEN role = 'assistant' AND duration_ms IS NULL AND disposition <> 'poll-duplicate' THEN 1 ELSE 0 END) AS assistant_duration_unmeasured
44137
44215
  FROM selected m
44138
44216
  GROUP BY source, session_id
44139
44217
  ), last_messages AS (
@@ -44141,7 +44219,7 @@ async function bySession(db, sel, top, opts) {
44141
44219
  ROW_NUMBER() OVER (
44142
44220
  PARTITION BY source, session_id ORDER BY seq DESC, source_rowid DESC
44143
44221
  ) AS rank
44144
- FROM selected WHERE disposition != 'meta'
44222
+ FROM selected WHERE disposition NOT IN ('meta', 'poll-duplicate') -- 1324 R3: latency denominator excludes collapsed polls
44145
44223
  )
44146
44224
  SELECT ms.session_id AS sessionId, ms.source AS source, ms.model AS model,
44147
44225
  ms.started_at AS startedAt, ms.ended_at AS endedAt, ms.messages AS messages,
@@ -44311,7 +44389,7 @@ async function bySessionPage(db, sel, input, opts) {
44311
44389
  last_messages AS (
44312
44390
  SELECT source, session_id, record_hash, role,
44313
44391
  ROW_NUMBER() OVER (PARTITION BY source, session_id ORDER BY seq DESC, source_rowid DESC) AS rank
44314
- FROM selected WHERE disposition != 'meta'
44392
+ FROM selected WHERE disposition NOT IN ('meta', 'poll-duplicate') -- 1324 R3: latency denominator excludes collapsed polls
44315
44393
  )
44316
44394
  SELECT ms.session_id AS sessionId, ms.source AS source, ms.model AS model,
44317
44395
  ms.started_at AS startedAt, ms.ended_at AS endedAt, ms.messages AS messages,
@@ -44387,8 +44465,10 @@ async function loops(db, sel, opts) {
44387
44465
  FROM history_tool_call tc
44388
44466
  JOIN history_message m ON m.record_hash = tc.message_hash
44389
44467
  WHERE tc.session_id IN (${placeholders})
44390
- AND tc.args_digest IS NOT NULL
44468
+ AND tc.args_digest IS NOT NULL AND tc.args_digest <> ''
44391
44469
  AND tc.session_id NOT IN ('', 'unknown', 'session')
44470
+ -- 1324 R3: tool calls hanging off a collapsed poll duplicate are loop noise.
44471
+ AND COALESCE(m.disposition, '') <> 'poll-duplicate'
44392
44472
  GROUP BY tc.source, tc.session_id, tc.tool_name, tc.args_digest
44393
44473
  HAVING COUNT(*) >= 3
44394
44474
  ORDER BY repeats DESC`, ...sessionScope);
@@ -44396,7 +44476,7 @@ async function loops(db, sel, opts) {
44396
44476
  const { where, params } = buildMessageWhere(sel);
44397
44477
  const wm = applyWatermarkToWhere(where, opts?.watermark);
44398
44478
  return db.queryAll(`WITH filtered_messages AS (
44399
- SELECT m.record_hash, m.source, m.model, m.ts
44479
+ SELECT m.record_hash, m.source, m.model, m.ts, m.disposition
44400
44480
  FROM history_message m
44401
44481
  ${wm.where}
44402
44482
  )
@@ -44409,7 +44489,8 @@ async function loops(db, sel, opts) {
44409
44489
  MAX(tc.seq) AS lastSeq
44410
44490
  FROM filtered_messages fm
44411
44491
  CROSS JOIN history_tool_call tc ON tc.message_hash = fm.record_hash
44412
- WHERE tc.args_digest IS NOT NULL AND tc.session_id NOT IN ('', 'unknown', 'session')
44492
+ WHERE tc.args_digest IS NOT NULL AND tc.args_digest <> '' AND tc.session_id NOT IN ('', 'unknown', 'session')
44493
+ AND COALESCE(fm.disposition, '') <> 'poll-duplicate' -- 1324 R3: collapsed poll noise
44413
44494
  GROUP BY fm.source, tc.session_id, tc.tool_name, tc.args_digest
44414
44495
  HAVING COUNT(*) >= 3
44415
44496
  ORDER BY repeats DESC`, ...params, ...wm.params);
@@ -45392,32 +45473,6 @@ var init_forensic_query = __esm(() => {
45392
45473
  END`;
45393
45474
  });
45394
45475
 
45395
- // ../../packages/domain/src/analytics/selector-time-range.ts
45396
- function buildTimeRangeClauses(range, target) {
45397
- if (!COLUMN.test(target.column))
45398
- throw new Error(`invalid time-range column: ${target.column}`);
45399
- const bound = BOUND_SQL[target.grain];
45400
- const clauses = [];
45401
- const params = [];
45402
- if (range.since !== null) {
45403
- clauses.push(`${target.column} >= ${bound}`);
45404
- params.push(range.since);
45405
- }
45406
- if (range.until !== null) {
45407
- clauses.push(`${target.column} <= ${bound}`);
45408
- params.push(range.until);
45409
- }
45410
- return { clauses, params };
45411
- }
45412
- var BOUND_SQL, COLUMN;
45413
- var init_selector_time_range = __esm(() => {
45414
- BOUND_SQL = {
45415
- day: "DATE(?)",
45416
- timestamp: "strftime('%Y-%m-%dT%H:%M:%SZ', ?)"
45417
- };
45418
- COLUMN = /^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)?$/;
45419
- });
45420
-
45421
45476
  // ../../packages/domain/src/analytics/history-board-marts.ts
45422
45477
  function resolveSummaryReadPath(input) {
45423
45478
  if (!input.fresh)
@@ -53813,18 +53868,12 @@ async function loadFolds(db, spec) {
53813
53868
  }
53814
53869
  }
53815
53870
  function eventWindow(spec) {
53816
- const clauses = [];
53817
- const params = [];
53818
- if (spec.since !== undefined) {
53819
- clauses.push("occurred_at >= ?");
53820
- params.push(spec.since);
53821
- }
53822
- if (spec.until !== undefined) {
53823
- clauses.push("occurred_at <= ?");
53824
- params.push(spec.until);
53825
- }
53871
+ const { clauses, params } = buildTimeRangeClauses({ since: spec.since ?? null, until: spec.until ?? null }, { column: "occurred_at", grain: "instant" });
53826
53872
  return { where: clauses.length > 0 ? `AND ${clauses.join(" AND ")}` : "", params };
53827
53873
  }
53874
+ var init_pairings = __esm(() => {
53875
+ init_selector_time_range();
53876
+ });
53828
53877
  // ../../packages/domain/src/analytics/render-report.ts
53829
53878
  function assertArtifactVersion(actualVersion, artifactPath, expectedVersion = HISTORY_ARTIFACT_SCHEMA_VERSION) {
53830
53879
  if (actualVersion !== expectedVersion) {
@@ -55645,6 +55694,7 @@ var init_workflow_measurements = __esm(() => {
55645
55694
 
55646
55695
  // ../../packages/domain/src/analytics/index.ts
55647
55696
  var init_analytics = __esm(() => {
55697
+ init_antigravity_etl();
55648
55698
  init_artifact();
55649
55699
  init_artifact_digest();
55650
55700
  init_costs();
@@ -55655,6 +55705,7 @@ var init_analytics = __esm(() => {
55655
55705
  init_importer_schema_version();
55656
55706
  init_models();
55657
55707
  init_narrow_artifact();
55708
+ init_pairings();
55658
55709
  init_render_forensics();
55659
55710
  init_render_pairings();
55660
55711
  init_render_report();
@@ -58153,7 +58204,7 @@ var init_run_dao = __esm(() => {
58153
58204
  FROM runs
58154
58205
  WHERE (?1 IS NULL OR workflow_name = ?1)
58155
58206
  AND (?2 IS NULL OR status = ?2)
58156
- AND (?3 IS NULL OR started_at >= ?3)
58207
+ AND (?3 IS NULL OR julianday(started_at) >= julianday(?3))
58157
58208
  AND (
58158
58209
  ?4 IS NULL
58159
58210
  OR started_at < ?4
@@ -58165,6 +58216,9 @@ var init_run_dao = __esm(() => {
58165
58216
  traceRowById(runId) {
58166
58217
  return this.adapter.queryFirst("SELECT id, workflow_name, mode, status, agent, started_at, completed_at, updated_at, terminal_reason, metadata_json, pid FROM runs WHERE id = ?", runId);
58167
58218
  }
58219
+ traceRowsByIdPrefix(prefix) {
58220
+ return this.adapter.queryAll("SELECT id FROM runs WHERE substr(id, 1, length(?1)) = ?1 ORDER BY id LIMIT 21", prefix);
58221
+ }
58168
58222
  mergeMetadata(runId, patch) {
58169
58223
  return this.adapter.run(`UPDATE runs
58170
58224
  SET metadata_json = json_patch(
@@ -58321,6 +58375,19 @@ var init_run_storage_reference_dao = __esm(() => {
58321
58375
  });
58322
58376
 
58323
58377
  // ../../packages/domain/src/dao/run-transfer.ts
58378
+ function stampWorktreeOrigin(row) {
58379
+ let meta = {};
58380
+ try {
58381
+ const parsed = JSON.parse(String(row.metadata_json ?? "{}"));
58382
+ if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
58383
+ meta = parsed;
58384
+ }
58385
+ } catch {
58386
+ meta = {};
58387
+ }
58388
+ meta.worktree_origin = true;
58389
+ return { ...row, metadata_json: JSON.stringify(meta) };
58390
+ }
58324
58391
  function insertIfAbsentOps(table, rows, targetColumns) {
58325
58392
  return rows.map((row) => {
58326
58393
  const columns = Object.keys(row).filter((column) => targetColumns.has(column));
@@ -58349,6 +58416,29 @@ async function ledgerOps(from, to, runId) {
58349
58416
  }
58350
58417
  return ops;
58351
58418
  }
58419
+ async function worktreeRunSessionOps(from, to, runId) {
58420
+ const present = await from.queryFirst("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?", "history_run_session");
58421
+ if (present === undefined)
58422
+ return [];
58423
+ const rows = await from.queryAll("SELECT * FROM history_run_session WHERE run_id = ?", runId);
58424
+ if (rows.length === 0)
58425
+ return [];
58426
+ const targetColumns = await tableColumns(to, "history_run_session");
58427
+ const ops = [];
58428
+ for (const row of rows) {
58429
+ const exists = await to.queryFirst(`SELECT run_id FROM history_run_session
58430
+ WHERE run_id = ? AND source IS ? AND session_id IS ? AND exactness IS ? AND resolved_at IS ?
58431
+ LIMIT 1`, row.run_id, row.source ?? null, row.session_id ?? null, row.exactness ?? null, row.resolved_at ?? null);
58432
+ if (exists !== undefined)
58433
+ continue;
58434
+ const columns = Object.keys(row).filter((column) => targetColumns.has(column));
58435
+ ops.push({
58436
+ sql: `INSERT INTO history_run_session (${columns.join(", ")}) ` + `VALUES (${columns.map(() => "?").join(", ")})`,
58437
+ params: columns.map((column) => column === "mechanism" ? "worktree-merge" : row[column])
58438
+ });
58439
+ }
58440
+ return ops;
58441
+ }
58352
58442
  async function tableColumns(db, table) {
58353
58443
  const rows = await db.queryAll(`PRAGMA table_info(${table})`);
58354
58444
  return new Set(rows.map((row) => row.name));
@@ -58387,7 +58477,8 @@ async function transferRunTables(from, to) {
58387
58477
  continue;
58388
58478
  }
58389
58479
  }
58390
- const ops = insertIfAbsentOps("runs", [row], targetRunsColumns);
58480
+ const ops = insertIfAbsentOps("runs", [stampWorktreeOrigin(row)], targetRunsColumns);
58481
+ ops.push(...await worktreeRunSessionOps(from, to, id));
58391
58482
  for (const table of CHILD_TABLES) {
58392
58483
  ops.push(...insertIfAbsentOps(table, await from.queryAll(`SELECT * FROM ${table} WHERE run_id = ?`, id), await tableColumns(to, table)));
58393
58484
  }
@@ -75695,6 +75786,13 @@ class MarkdownDocument {
75695
75786
  get sectionNames() {
75696
75787
  return this._sections.map((s) => s.name);
75697
75788
  }
75789
+ sectionContent(name) {
75790
+ const section = this.findSection(name);
75791
+ if (section === undefined)
75792
+ return null;
75793
+ const text = section.modifiedText ?? section.originalText;
75794
+ return { headingLine: section.headingLine, text };
75795
+ }
75698
75796
  get demotedHeadings() {
75699
75797
  return this._demotedHeadings;
75700
75798
  }
@@ -99550,6 +99648,12 @@ function bounded3(value, maxLength = MAX_FIELD_LENGTH) {
99550
99648
  const redacted = value.replace(SECRET_PATTERN2, "[REDACTED]");
99551
99649
  return redacted.length <= maxLength ? redacted : `${redacted.slice(0, maxLength)}\u2026`;
99552
99650
  }
99651
+ function positiveMs(value) {
99652
+ const n = typeof value === "string" && value.trim() !== "" ? Number(value) : value;
99653
+ if (typeof n !== "number" || !Number.isFinite(n) || n <= 0)
99654
+ return;
99655
+ return Math.trunc(n);
99656
+ }
99553
99657
  function projectActionMetadata(kind, options) {
99554
99658
  if (options === undefined)
99555
99659
  return;
@@ -99561,9 +99665,9 @@ function projectActionMetadata(kind, options) {
99561
99665
  if (kind === "agent.run" && typeof options.role === "string" && options.role !== "") {
99562
99666
  metadata.role = bounded3(options.role);
99563
99667
  }
99564
- if (typeof options.timeoutMs === "number" && Number.isFinite(options.timeoutMs)) {
99565
- metadata.timeoutMs = Math.max(0, options.timeoutMs);
99566
- }
99668
+ const timeoutMs = positiveMs(options.timeoutMs);
99669
+ if (timeoutMs !== undefined)
99670
+ metadata.timeoutMs = timeoutMs;
99567
99671
  if (typeof options.maxTokens === "number" && Number.isFinite(options.maxTokens)) {
99568
99672
  metadata.maxTokens = Math.max(0, options.maxTokens);
99569
99673
  }
@@ -102629,6 +102733,13 @@ function classifyDispatch(result) {
102629
102733
  }
102630
102734
  return;
102631
102735
  }
102736
+ function executorUnavailabilityEvidence(result) {
102737
+ if (result.exitCode === 2)
102738
+ return `dispatch-level exit 2 (executor unusable for this run)`;
102739
+ if (result.signal === "unavailable")
102740
+ return `terminal signal ${result.signal}`;
102741
+ return;
102742
+ }
102632
102743
  var FAILURE_RULES, PERMISSION_PATTERNS, HTTP_STATUS_PATTERN;
102633
102744
  var init_failure_classification = __esm(() => {
102634
102745
  init_agent_execution();
@@ -105481,6 +105592,8 @@ ${traced.stdout ?? ""}`);
105481
105592
  const resumedDispatch = ok && affinityOn && !freshSession && resumeSupported && storedSessionId !== undefined;
105482
105593
  if (!ok && traced.signal !== undefined)
105483
105594
  resultData.signal = traced.signal;
105595
+ if (traced.ladder !== undefined && traced.ladder.length > 0)
105596
+ resultData.ladder = traced.ladder;
105484
105597
  resultData.session = resumedDispatch ? "reused" : "fresh";
105485
105598
  resultData.sessionSource = sessionDeclared ? "declared" : "default";
105486
105599
  if (escalated) {
@@ -107544,6 +107657,72 @@ var init_agent_invocation_sanitize = __esm(() => {
107544
107657
  SENSITIVE_INLINE_FLAG = /^(--?(?:api[-_]?key|authorization|credential|password|secret|token)=).+$/i;
107545
107658
  });
107546
107659
 
107660
+ // ../../packages/app/src/services/adaptive-timeout.ts
107661
+ function derivedBudgetMs(latency) {
107662
+ const base = latency.p95Ms ?? latency.meanMs;
107663
+ const headroom = latency.p95Ms !== undefined ? P95_HEADROOM : MEAN_HEADROOM;
107664
+ return Math.max(DERIVED_FLOOR_MS, Math.ceil(base * headroom));
107665
+ }
107666
+ function resolveAdaptiveTimeoutMs(staticMs, latency) {
107667
+ if (latency === undefined || latency.samples <= 0) {
107668
+ return {
107669
+ source: "static",
107670
+ ...staticMs !== undefined ? { staticMs, appliedMs: staticMs } : {},
107671
+ fits: true
107672
+ };
107673
+ }
107674
+ const derivedMs = derivedBudgetMs(latency);
107675
+ if (staticMs === undefined) {
107676
+ return {
107677
+ source: "derived-latency",
107678
+ appliedMs: derivedMs,
107679
+ derivedMs,
107680
+ meanMs: latency.meanMs,
107681
+ ...latency.p95Ms !== undefined ? { p95Ms: latency.p95Ms } : {},
107682
+ samples: latency.samples,
107683
+ successRate: latency.successRate,
107684
+ fits: true
107685
+ };
107686
+ }
107687
+ const fits = derivedMs <= staticMs;
107688
+ return {
107689
+ source: "static",
107690
+ staticMs,
107691
+ appliedMs: staticMs,
107692
+ derivedMs,
107693
+ meanMs: latency.meanMs,
107694
+ ...latency.p95Ms !== undefined ? { p95Ms: latency.p95Ms } : {},
107695
+ samples: latency.samples,
107696
+ successRate: latency.successRate,
107697
+ fits
107698
+ };
107699
+ }
107700
+ function ladderScore(latency) {
107701
+ const base = latency.p95Ms ?? latency.meanMs;
107702
+ const successRate = Math.min(1, Math.max(0.1, latency.successRate));
107703
+ return base / successRate;
107704
+ }
107705
+ async function loadExecutorLatency(source, keys) {
107706
+ if (source.historyLatency !== undefined) {
107707
+ try {
107708
+ return new Map(Object.entries(await source.historyLatency([...keys])));
107709
+ } catch {
107710
+ return new Map;
107711
+ }
107712
+ }
107713
+ if (source.getDb === undefined)
107714
+ return new Map;
107715
+ try {
107716
+ return await readExecutorLatency(await source.getDb(), keys);
107717
+ } catch {
107718
+ return new Map;
107719
+ }
107720
+ }
107721
+ var P95_HEADROOM = 1.5, MEAN_HEADROOM = 3, DERIVED_FLOOR_MS = 30000;
107722
+ var init_adaptive_timeout = __esm(() => {
107723
+ init_src2();
107724
+ });
107725
+
107547
107726
  // ../../packages/app/src/services/agent-run-flags.ts
107548
107727
  function stringFlag(flags, name, fallback) {
107549
107728
  const value = flags[name];
@@ -107743,10 +107922,12 @@ class AgentResolver {
107743
107922
  const failedTier = getExecutorTier(failed);
107744
107923
  const failedCanonical = resolveAgentName2(failed.agent);
107745
107924
  const exhaustedAgents = new Set(executors.filter((executor) => executor.name === fromExecutor || (exclude?.has(executor.name) ?? false)).map((executor) => resolveAgentName2(executor.agent)).filter((agent) => agent !== undefined));
107746
- const sideways = executors.filter((e) => {
107925
+ let sideways = executors.filter((e) => {
107747
107926
  const canonical = resolveAgentName2(e.agent);
107748
107927
  return !executorDisabled(e) && e.name !== fromExecutor && getExecutorTier(e) === failedTier && canonical !== failedCanonical && (canonical === undefined || !exhaustedAgents.has(canonical)) && !(exclude?.has(e.name) ?? false);
107749
107928
  });
107929
+ const sidewaysOrder = await this.orderEligibleByMeasuredLatency(sideways, signal !== undefined);
107930
+ sideways = sidewaysOrder.ordered;
107750
107931
  for (const executor of sideways) {
107751
107932
  const canonical = resolveAgentName2(executor.agent);
107752
107933
  if (canonical === undefined) {
@@ -107772,6 +107953,7 @@ class AgentResolver {
107772
107953
  model: executor.model,
107773
107954
  source: "stage",
107774
107955
  executor: executor.name,
107956
+ ...sidewaysOrder.ordering !== undefined ? { ordering: sidewaysOrder.ordering } : {},
107775
107957
  stage: {
107776
107958
  stageId: stageRecord.id,
107777
107959
  policy,
@@ -107810,7 +107992,9 @@ class AgentResolver {
107810
107992
  if (eligible.length === 0) {
107811
107993
  return;
107812
107994
  }
107813
- for (const executor of eligible) {
107995
+ const measuredOrder = await this.orderEligibleByMeasuredLatency(eligible, signal !== undefined);
107996
+ const orderedEligible = measuredOrder.ordered;
107997
+ for (const executor of orderedEligible) {
107814
107998
  const canonical = resolveAgentName2(executor.agent);
107815
107999
  if (canonical === undefined) {
107816
108000
  return {
@@ -107834,6 +108018,7 @@ class AgentResolver {
107834
108018
  model: executor.model,
107835
108019
  source: "stage",
107836
108020
  executor: executor.name,
108021
+ ...measuredOrder.ordering !== undefined ? { ordering: measuredOrder.ordering } : {},
107837
108022
  stage: {
107838
108023
  stageId: stageRecord.id,
107839
108024
  policy,
@@ -107845,6 +108030,36 @@ class AgentResolver {
107845
108030
  }
107846
108031
  return;
107847
108032
  }
108033
+ async orderEligibleByMeasuredLatency(eligible, ordered) {
108034
+ if (!ordered || eligible.length <= 1)
108035
+ return { ordered: [...eligible] };
108036
+ const stats = await loadExecutorLatency(this.ctx, eligible.map((e) => e.model ?? e.name));
108037
+ const scored = eligible.map((executor, index) => {
108038
+ const hit = stats.get(executor.model ?? executor.name);
108039
+ return {
108040
+ executor,
108041
+ index,
108042
+ ...hit !== undefined && hit.samples > 0 ? { score: ladderScore(hit), samples: hit.samples } : {}
108043
+ };
108044
+ });
108045
+ const known = scored.filter((entry) => entry.score !== undefined);
108046
+ if (known.length === 0)
108047
+ return { ordered: [...eligible] };
108048
+ const sorted = [...scored].sort((a, b) => {
108049
+ const aKnown = a.score !== undefined ? 1 : 0;
108050
+ const bKnown = b.score !== undefined ? 1 : 0;
108051
+ if (aKnown !== bKnown)
108052
+ return bKnown - aKnown;
108053
+ if (aKnown === 1)
108054
+ return (a.score ?? 0) - (b.score ?? 0) || a.index - b.index;
108055
+ return a.index - b.index;
108056
+ });
108057
+ const ordering = sorted.map((entry) => ({
108058
+ executor: entry.executor.name,
108059
+ ...entry.score !== undefined ? { scoreMs: entry.score, samples: entry.samples } : {}
108060
+ }));
108061
+ return { ordered: sorted.map((entry) => entry.executor), ordering };
108062
+ }
107848
108063
  async resolveAgentPriority(doctorRunner) {
107849
108064
  const results = await doctorRunner.runAll();
107850
108065
  for (const name of TIER1_PRIORITY2) {
@@ -108076,6 +108291,7 @@ var warnedBareBinary;
108076
108291
  var init_agent_resolver = __esm(() => {
108077
108292
  init_src2();
108078
108293
  init_dist29();
108294
+ init_adaptive_timeout();
108079
108295
  init_capability_attestation();
108080
108296
  init_executor_tier();
108081
108297
  warnedBareBinary = new Set;
@@ -109066,6 +109282,36 @@ class AgentRunExecutor {
109066
109282
  this.resolver = resolver;
109067
109283
  }
109068
109284
  sessionRootRegistry = { active: new Map, overlapped: new Set };
109285
+ runBreakers = new Map;
109286
+ static BREAKER_CAP = 256;
109287
+ breakerFor(runId) {
109288
+ let breaker = this.runBreakers.get(runId);
109289
+ if (breaker === undefined) {
109290
+ breaker = { executors: new Set, touchedAt: Date.now() };
109291
+ this.runBreakers.set(runId, breaker);
109292
+ if (this.runBreakers.size > AgentRunExecutor.BREAKER_CAP) {
109293
+ const oldest = [...this.runBreakers.entries()].sort((a, b) => a[1].touchedAt - b[1].touchedAt);
109294
+ for (const [key] of oldest.slice(0, this.runBreakers.size - AgentRunExecutor.BREAKER_CAP)) {
109295
+ this.runBreakers.delete(key);
109296
+ }
109297
+ }
109298
+ }
109299
+ breaker.touchedAt = Date.now();
109300
+ return breaker;
109301
+ }
109302
+ recordLadderDecision(decisions, identity, events, decision) {
109303
+ const row = { ...decision, at: new Date().toISOString() };
109304
+ decisions.push(row);
109305
+ if (events !== undefined) {
109306
+ events.emit("agent.invoke.ladder", {
109307
+ runId: identity.runId,
109308
+ executionId: identity.executionId,
109309
+ ...identity.actionId !== undefined ? { actionId: identity.actionId } : {},
109310
+ ...row,
109311
+ severity: "warning"
109312
+ });
109313
+ }
109314
+ }
109069
109315
  async executeRun(prompt, flags, deps, options) {
109070
109316
  const silent = options.silent;
109071
109317
  const nonInteractive = options.nonInteractive === true;
@@ -109137,6 +109383,9 @@ class AgentRunExecutor {
109137
109383
  if (quotaEventBridge !== undefined)
109138
109384
  quotaEventBridge.on("agent.quota.exhausted", onQuotaExhausted);
109139
109385
  const tiersAttempted = new Set(currentStage ? [currentStage.executorTier] : []);
109386
+ const breaker = this.breakerFor(stringFlag(flags, "run-id", runCorrelation.runId));
109387
+ const ladderDecisions = [];
109388
+ const latencyCache = new Map;
109140
109389
  if (!jsonOutput && TIER2_AGENTS2.has(currentAgent)) {
109141
109390
  this.ctx.output.error(`Warning: ${currentAgent} is a Tier-2 agent (TUI/gateway only)`);
109142
109391
  }
@@ -109227,10 +109476,85 @@ class AgentRunExecutor {
109227
109476
  await sessionObserver.watermark(currentAgent, sessionDir);
109228
109477
  }
109229
109478
  }
109479
+ if (currentStage !== undefined && breaker.executors.has(currentStage.executorName)) {
109480
+ const skippedExecutor = currentStage.executorName;
109481
+ runFlags.signal = "resource-exhaustion";
109482
+ runFlags["from-executor"] = skippedExecutor;
109483
+ const rerun = await this.resolver.resolveAgent(runFlags, doctorRunner, new Set([...attemptedExecutors, ...breaker.executors]));
109484
+ if (!rerun.ok || rerun.stage === undefined || breaker.executors.has(rerun.stage.executorName)) {
109485
+ const message = `executor circuit breaker: every remaining executor for stage '${skippedExecutor}' is ` + `unavailable for this run (tripped: ${[...breaker.executors].join(", ")})`;
109486
+ this.recordLadderDecision(ladderDecisions, lifecycle.identity, this.ctx.events, {
109487
+ kind: "chain-exhausted",
109488
+ executor: skippedExecutor,
109489
+ reason: message
109490
+ });
109491
+ await sessionObserver?.resolve();
109492
+ return { ok: false, exitCode: 2, message };
109493
+ }
109494
+ currentAgent = rerun.agent;
109495
+ currentModel = rerun.model;
109496
+ currentSource = rerun.source;
109497
+ currentStage = rerun.stage;
109498
+ currentRole = rerun.role;
109499
+ currentRoleOrigin = rerun.roleOrigin;
109500
+ dispatchExecutor.setRoleEnv(currentRole);
109501
+ currentTier = rerun.tier;
109502
+ currentExecutor = rerun.executor;
109503
+ routing = buildRoutingAttribution(rerun);
109504
+ attemptedExecutors.add(rerun.stage.executorName);
109505
+ tiersAttempted.add(rerun.stage.executorTier);
109506
+ this.recordLadderDecision(ladderDecisions, lifecycle.identity, this.ctx.events, {
109507
+ kind: "breaker-skip",
109508
+ executor: skippedExecutor,
109509
+ toExecutor: rerun.stage.executorName,
109510
+ reason: "run-scoped circuit breaker: skipped an executor proven unavailable earlier in this run"
109511
+ });
109512
+ }
109230
109513
  try {
109231
109514
  for (let attempt = 0;; attempt++) {
109232
109515
  const agent = currentAgent;
109233
109516
  const model = (attempt === 0 ? explicitModel : undefined) ?? currentModel;
109517
+ const attemptExecutorEntry = this.ctx.agentConfig?.executors?.find((entry) => entry.name === (currentExecutor ?? ""));
109518
+ const latencyKey = attemptExecutorEntry?.model ?? currentExecutor ?? agent;
109519
+ if (!latencyCache.has(latencyKey)) {
109520
+ const stats = await loadExecutorLatency(this.ctx, [latencyKey]);
109521
+ latencyCache.set(latencyKey, stats.get(latencyKey));
109522
+ }
109523
+ const timeoutDecision = resolveAdaptiveTimeoutMs(timeoutMs, latencyCache.get(latencyKey));
109524
+ const effectiveTimeoutMs = timeoutDecision.appliedMs ?? timeoutMs;
109525
+ if (timeoutMs !== undefined && !timeoutDecision.fits && currentStage !== undefined && breaker.executors.size + attemptedExecutors.size < 64) {
109526
+ const exhaustedExecutor = currentStage.executorName;
109527
+ runFlags.signal = "timeout";
109528
+ runFlags["from-executor"] = exhaustedExecutor;
109529
+ const rerun = await this.resolver.resolveAgent(runFlags, doctorRunner, new Set([...attemptedExecutors, ...breaker.executors]));
109530
+ if (rerun.ok && rerun.stage !== undefined && rerun.stage.executorName !== exhaustedExecutor && !attemptedExecutors.has(rerun.stage.executorName) && !breaker.executors.has(rerun.stage.executorName)) {
109531
+ currentAgent = rerun.agent;
109532
+ currentModel = rerun.model;
109533
+ currentSource = rerun.source;
109534
+ currentStage = rerun.stage;
109535
+ currentRole = rerun.role;
109536
+ currentRoleOrigin = rerun.roleOrigin;
109537
+ dispatchExecutor.setRoleEnv(currentRole);
109538
+ currentTier = rerun.tier;
109539
+ currentExecutor = rerun.executor;
109540
+ routing = buildRoutingAttribution(rerun);
109541
+ attemptedExecutors.add(rerun.stage.executorName);
109542
+ tiersAttempted.add(rerun.stage.executorTier);
109543
+ this.recordLadderDecision(ladderDecisions, lifecycle.identity, this.ctx.events, {
109544
+ kind: "timeout-exhausted-skip",
109545
+ executor: exhaustedExecutor,
109546
+ toExecutor: rerun.stage.executorName,
109547
+ reason: `measured latency exceeds the static budget; failed over without burning ` + `the ${timeoutMs}ms timeout`,
109548
+ detail: JSON.stringify({
109549
+ derivedMs: timeoutDecision.derivedMs,
109550
+ staticMs: timeoutMs,
109551
+ meanMs: timeoutDecision.meanMs,
109552
+ samples: timeoutDecision.samples
109553
+ })
109554
+ });
109555
+ continue;
109556
+ }
109557
+ }
109234
109558
  if (requiresCapabilities !== undefined && Object.keys(requiresCapabilities).length > 0) {
109235
109559
  const executorEntry = this.ctx.agentConfig?.executors?.find((entry) => entry.name === (currentExecutor ?? ""));
109236
109560
  const evaluation = evaluateCapabilities(requiresCapabilities, executorEntry);
@@ -109302,7 +109626,8 @@ class AgentRunExecutor {
109302
109626
  ...cwd !== "" ? { cwd } : {},
109303
109627
  mode,
109304
109628
  outputMode,
109305
- ...timeoutMs !== undefined ? { timeoutMs } : {},
109629
+ ...effectiveTimeoutMs !== undefined ? { timeoutMs: effectiveTimeoutMs } : {},
109630
+ ...timeoutDecision.staticMs !== undefined || timeoutDecision.derivedMs !== undefined ? { timeoutBudget: timeoutDecision } : {},
109306
109631
  continue: !freshRole && continueFlag,
109307
109632
  stdinInteractive: false,
109308
109633
  ...model !== undefined ? { model } : {},
@@ -109314,7 +109639,7 @@ class AgentRunExecutor {
109314
109639
  agent,
109315
109640
  ...model !== undefined ? { model } : {},
109316
109641
  invocation: `${attemptInvocation.command} ${attemptInvocation.argv.join(" ")}`,
109317
- ...timeoutMs !== undefined ? { timeoutMs } : {},
109642
+ ...effectiveTimeoutMs !== undefined ? { timeoutMs: effectiveTimeoutMs } : {},
109318
109643
  ...routing !== undefined ? { routing } : {}
109319
109644
  });
109320
109645
  dispatchStartedAt = Date.now();
@@ -109359,7 +109684,7 @@ class AgentRunExecutor {
109359
109684
  try {
109360
109685
  result = warmOutcome !== undefined ? warmResult(warmOutcome, dispatchStartedAt) : await runner.runPromptCommand(agent, promptOptions, {
109361
109686
  cwd: cwd || undefined,
109362
- ...timeoutMs !== undefined ? { timeout: timeoutMs } : {},
109687
+ ...effectiveTimeoutMs !== undefined ? { timeout: effectiveTimeoutMs } : {},
109363
109688
  signal: controller.signal,
109364
109689
  correlation: lifecycle.identity,
109365
109690
  ...currentExecutor !== undefined ? {
@@ -109399,8 +109724,19 @@ class AgentRunExecutor {
109399
109724
  if (result.exitCode === 0)
109400
109725
  break;
109401
109726
  const quotaConfirmed = currentExecutor !== undefined && runQuotaExhaustedExecutors.has(currentExecutor);
109727
+ const unavailability = executorUnavailabilityEvidence(result);
109728
+ let breakerTripped = false;
109729
+ if (unavailability !== undefined && currentStage !== undefined) {
109730
+ breaker.executors.add(currentStage.executorName);
109731
+ breakerTripped = true;
109732
+ this.recordLadderDecision(ladderDecisions, lifecycle.identity, this.ctx.events, {
109733
+ kind: "breaker-trip",
109734
+ executor: currentStage.executorName,
109735
+ reason: `circuit breaker: ${unavailability}`
109736
+ });
109737
+ }
109402
109738
  const classifiedSignal = classifyObjectiveFailure(result);
109403
- let escalationSignal = quotaConfirmed ? "resource-exhaustion" : classifiedSignal;
109739
+ let escalationSignal = quotaConfirmed ? "resource-exhaustion" : classifiedSignal ?? (breakerTripped ? "resource-exhaustion" : undefined);
109404
109740
  if (escalationSignal === undefined && result.signal === undefined && this.ctx.classifyUnmatchedFailure !== undefined) {
109405
109741
  const text = `${result.stderr ?? ""}
109406
109742
  ${result.stdout ?? ""}`;
@@ -109415,15 +109751,21 @@ ${result.stdout ?? ""}`;
109415
109751
  }
109416
109752
  const matchingFallbacks = currentStage.policy.fallback.filter((fallback) => fallback.trigger === escalationSignal);
109417
109753
  if (matchingFallbacks.length === 0 || escalationSignal !== "resource-exhaustion" && attempt >= matchingFallbacks.length) {
109418
- break;
109754
+ if (!breakerTripped)
109755
+ break;
109419
109756
  }
109420
109757
  runFlags.signal = escalationSignal;
109421
109758
  runFlags["from-executor"] = currentStage.executorName;
109422
- const nextResolved = await this.resolver.resolveAgent(runFlags, doctorRunner, attemptedExecutors);
109423
- if (!nextResolved.ok || nextResolved.stage === undefined || attemptedExecutors.has(nextResolved.stage.executorName)) {
109759
+ const nextResolved = await this.resolver.resolveAgent(runFlags, doctorRunner, new Set([...attemptedExecutors, ...breaker.executors]));
109760
+ if (!nextResolved.ok || nextResolved.stage === undefined || attemptedExecutors.has(nextResolved.stage.executorName) || breaker.executors.has(nextResolved.stage.executorName)) {
109424
109761
  if (!jsonOutput) {
109425
109762
  this.ctx.output.error(`Escalation chain exhausted after ${attempt + 1} attempt(s); stage=${currentStage.stageId}; tiers attempted: ${[...tiersAttempted].join(", ")}; executors tried: ${[...attemptedExecutors].join(", ")}`);
109426
109763
  }
109764
+ this.recordLadderDecision(ladderDecisions, lifecycle.identity, this.ctx.events, {
109765
+ kind: "chain-exhausted",
109766
+ executor: currentStage.executorName,
109767
+ reason: `escalation chain exhausted; executors tried: ${[...attemptedExecutors].join(", ")}`
109768
+ });
109427
109769
  if (this.ctx.events !== undefined) {
109428
109770
  this.ctx.events.emit("agent.invoke.exhausted", {
109429
109771
  runId: lifecycle.identity.runId,
@@ -109438,9 +109780,12 @@ ${result.stdout ?? ""}`;
109438
109780
  }
109439
109781
  break;
109440
109782
  }
109783
+ const escalationLine = `Escalating: ${currentStage.executorName} (tier ${currentStage.executorTier}) failed with ${escalationSignal}; retrying on ${nextResolved.stage.executorName} (tier ${nextResolved.stage.executorTier})`;
109784
+ const failedExecutor = currentStage.executorName;
109441
109785
  if (!jsonOutput) {
109442
- this.ctx.output.error(`Escalating: ${currentStage.executorName} (tier ${currentStage.executorTier}) failed with ${escalationSignal}; retrying on ${nextResolved.stage.executorName} (tier ${nextResolved.stage.executorTier})`);
109786
+ this.ctx.output.error(escalationLine);
109443
109787
  }
109788
+ agentLog.append("stderr", escalationLine);
109444
109789
  if (this.ctx.events !== undefined) {
109445
109790
  this.ctx.events.emit("agent.invoke.escalated", {
109446
109791
  runId: lifecycle.identity.runId,
@@ -109466,6 +109811,12 @@ ${result.stdout ?? ""}`;
109466
109811
  routing = buildRoutingAttribution(nextResolved);
109467
109812
  attemptedExecutors.add(nextResolved.stage.executorName);
109468
109813
  tiersAttempted.add(nextResolved.stage.executorTier);
109814
+ this.recordLadderDecision(ladderDecisions, lifecycle.identity, this.ctx.events, {
109815
+ kind: "escalation",
109816
+ executor: failedExecutor,
109817
+ toExecutor: nextResolved.stage.executorName,
109818
+ reason: `escalated on ${escalationSignal}`
109819
+ });
109469
109820
  }
109470
109821
  } finally {
109471
109822
  if (quotaEventBridge !== undefined)
@@ -109515,6 +109866,7 @@ ${result.stdout ?? ""}`;
109515
109866
  ok: true,
109516
109867
  result,
109517
109868
  invocation,
109869
+ ...ladderDecisions.length > 0 ? { ladder: ladderDecisions } : {},
109518
109870
  ...occupantRef !== undefined ? { coordination: { occupant: occupantRef } } : {}
109519
109871
  };
109520
109872
  }
@@ -109540,6 +109892,7 @@ var init_agent_run_executor = __esm(() => {
109540
109892
  init_agent_run_log();
109541
109893
  init_fleet_dispatch();
109542
109894
  init_run_record();
109895
+ init_adaptive_timeout();
109543
109896
  init_agent_invocation_sanitize();
109544
109897
  init_agent_routing_attribution();
109545
109898
  init_agent_run_preflight();
@@ -109712,6 +110065,7 @@ class AgentService {
109712
110065
  return {
109713
110066
  exitCode,
109714
110067
  invocation: outcome.invocation,
110068
+ ...outcome.ladder !== undefined ? { ladder: outcome.ladder } : {},
109715
110069
  stdout: result.stdout,
109716
110070
  stderr: result.stderr,
109717
110071
  durationMs: result.durationMs,
@@ -119085,7 +119439,10 @@ async function deriveVerifiedOutcome(deps, window2 = {}) {
119085
119439
  const linkDao = new TaskRunLinkDao(deps.db);
119086
119440
  linkRows = [];
119087
119441
  for (const run of runs) {
119088
- if (window2.until !== null && window2.until !== undefined && (run.started_at ?? "") > window2.until)
119442
+ const startedAt = Date.parse(run.started_at ?? "");
119443
+ if (!Number.isFinite(startedAt))
119444
+ continue;
119445
+ if (window2.until !== null && window2.until !== undefined && startedAt > Date.parse(window2.until))
119089
119446
  continue;
119090
119447
  for (const link of await linkDao.listByRun(run.id, 100)) {
119091
119448
  linkRows.push({
@@ -128183,10 +128540,19 @@ class TaskFiles {
128183
128540
  slugify(title) {
128184
128541
  return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60);
128185
128542
  }
128543
+ async sizeBudgetWarnings(filePath) {
128544
+ try {
128545
+ const doc = MarkdownDocument.parse(await this.ctx.fs.readFile(filePath), "task");
128546
+ return evaluateTaskSize(doc.bodyWithoutFrontmatter).reasons;
128547
+ } catch {
128548
+ return [];
128549
+ }
128550
+ }
128186
128551
  }
128187
128552
  var init_task_files = __esm(() => {
128188
128553
  init_src2();
128189
128554
  init_task_service_errors();
128555
+ init_task_size_precheck();
128190
128556
  });
128191
128557
 
128192
128558
  // ../../packages/app/src/services/task-section-bodies.ts
@@ -128524,12 +128890,15 @@ ${templateBackground}`;
128524
128890
  const ref = { kind: "task", id: wbs, filePath, folder };
128525
128891
  return { ref, content };
128526
128892
  });
128527
- if (featureReopened === undefined && shape.normalized === undefined)
128893
+ const sizeWarnings = await this.files.sizeBudgetWarnings(created.ref.filePath);
128894
+ if (featureReopened === undefined && shape.normalized === undefined && sizeWarnings.length === 0) {
128528
128895
  return created;
128896
+ }
128529
128897
  return {
128530
128898
  ...created,
128531
128899
  ...featureReopened !== undefined ? { featureReopened } : {},
128532
- ...shape.normalized !== undefined ? { normalized: shape.normalized } : {}
128900
+ ...shape.normalized !== undefined ? { normalized: shape.normalized } : {},
128901
+ ...sizeWarnings.length > 0 ? { warnings: [...created.warnings ?? [], ...sizeWarnings] } : {}
128533
128902
  };
128534
128903
  }
128535
128904
  async batchCreate(jsonPath) {
@@ -128658,7 +129027,14 @@ ${issues}`);
128658
129027
  const ref = { kind: "task", id: wbs, filePath, folder };
128659
129028
  return { ref, content };
128660
129029
  });
128661
- return shape.normalized !== undefined ? { ...created, normalized: shape.normalized } : created;
129030
+ const sizeWarnings = await this.files.sizeBudgetWarnings(created.ref.filePath);
129031
+ if (shape.normalized === undefined && sizeWarnings.length === 0)
129032
+ return created;
129033
+ return {
129034
+ ...created,
129035
+ ...shape.normalized !== undefined ? { normalized: shape.normalized } : {},
129036
+ ...sizeWarnings.length > 0 ? { warnings: [...created.warnings ?? [], ...sizeWarnings] } : {}
129037
+ };
128662
129038
  }
128663
129039
  async deriveBackground(featureId) {
128664
129040
  const featuresDir = this.featureLink.deriveFeaturesDir();
@@ -128725,23 +129101,62 @@ class TaskQueryOps {
128725
129101
  this.locator = locator;
128726
129102
  this.files = files;
128727
129103
  }
128728
- async show(wbs) {
129104
+ async readTaskSummary(wbs) {
128729
129105
  const filePath = await this.files.resolveTaskFile(wbs);
128730
129106
  const raw = await this.ctx.fs.readFile(filePath);
128731
129107
  const doc = MarkdownDocument.parse(raw, "task");
128732
129108
  const fm = doc.frontmatterData ?? {};
128733
129109
  const { status, featureId } = taskMetadata(fm);
128734
129110
  return {
128735
- wbs,
128736
- name: fm.name ?? "",
128737
- status,
128738
- filePath,
128739
- dependencies: Array.isArray(fm.dependencies) ? fm.dependencies.filter((dep) => typeof dep === "string") : [],
128740
- feature_id: featureId,
128741
- frontmatter: fm,
128742
- content: doc.bodyWithoutFrontmatter
129111
+ summary: {
129112
+ wbs,
129113
+ name: fm.name ?? "",
129114
+ status,
129115
+ filePath,
129116
+ dependencies: Array.isArray(fm.dependencies) ? fm.dependencies.filter((dep) => typeof dep === "string") : [],
129117
+ feature_id: featureId,
129118
+ frontmatter: fm
129119
+ },
129120
+ doc,
129121
+ raw
128743
129122
  };
128744
129123
  }
129124
+ async show(wbs) {
129125
+ const { summary, doc } = await this.readTaskSummary(wbs);
129126
+ return { ...summary, content: doc.bodyWithoutFrontmatter };
129127
+ }
129128
+ async showSections(wbs, requested) {
129129
+ const { summary, doc, raw } = await this.readTaskSummary(wbs);
129130
+ const present = doc.sectionNames;
129131
+ const unknown = requested.filter((name) => !present.includes(name));
129132
+ if (unknown.length > 0) {
129133
+ const err = new Error(`Unknown section(s): ${unknown.map((n) => `"${n}"`).join(", ")}. ` + `Sections in task ${wbs}: ${present.join(", ") || "(none)"}`);
129134
+ err.cliCode = "VALIDATION_FAILED";
129135
+ throw err;
129136
+ }
129137
+ const sections = present.filter((name) => requested.includes(name)).map((name) => {
129138
+ const content = doc.sectionContent(name);
129139
+ return {
129140
+ name,
129141
+ headingLine: content?.headingLine ?? "",
129142
+ byteCount: Buffer.byteLength(content?.text ?? ""),
129143
+ content: content?.text ?? ""
129144
+ };
129145
+ });
129146
+ return { ...summary, sections, totalBytes: Buffer.byteLength(raw) };
129147
+ }
129148
+ async showSummary(wbs) {
129149
+ const { summary, doc, raw } = await this.readTaskSummary(wbs);
129150
+ const sections = doc.sectionNames.map((name) => {
129151
+ const content = doc.sectionContent(name);
129152
+ return {
129153
+ name,
129154
+ headingLine: content?.headingLine ?? "",
129155
+ byteCount: Buffer.byteLength(content?.text ?? "")
129156
+ };
129157
+ });
129158
+ return { ...summary, sections, totalBytes: Buffer.byteLength(raw) };
129159
+ }
128745
129160
  async refresh() {
128746
129161
  const folders = this.files.allFolderDirs();
128747
129162
  let totalTasks = 0;
@@ -130327,7 +130742,9 @@ class TaskUpdateOps {
130327
130742
  async updateBody(wbs, body, _actor) {
130328
130743
  const filePath = await this.files.resolveTaskFile(wbs);
130329
130744
  const ref = { kind: "task", id: wbs, filePath, folder: this.ctx.tasksDir };
130330
- return this.writeService.updateBody(ref, body);
130745
+ const result = await this.writeService.updateBody(ref, body);
130746
+ const sizeWarnings = await this.files.sizeBudgetWarnings(filePath);
130747
+ return sizeWarnings.length > 0 ? { ...result, warnings: [...result.warnings ?? [], ...sizeWarnings] } : result;
130331
130748
  }
130332
130749
  async updateSection(wbs, sectionName, sourceFile, append = false) {
130333
130750
  const filePath = await this.files.resolveTaskFile(wbs);
@@ -130467,6 +130884,12 @@ class TaskService {
130467
130884
  async show(wbs) {
130468
130885
  return this.queryOps.show(wbs);
130469
130886
  }
130887
+ async showSections(wbs, requested) {
130888
+ return this.queryOps.showSections(wbs, requested);
130889
+ }
130890
+ async showSummary(wbs) {
130891
+ return this.queryOps.showSummary(wbs);
130892
+ }
130470
130893
  async updateStatus(wbs, toStatus, actor) {
130471
130894
  return this.updateOps.updateStatus(wbs, toStatus, actor);
130472
130895
  }
@@ -131829,6 +132252,20 @@ class CommandGateActionRunner {
131829
132252
  error: `resultFile ${error.message}`
131830
132253
  };
131831
132254
  }
132255
+ let logNormalized;
132256
+ if (options.logFile !== undefined && options.logFile !== null && options.logFile !== "") {
132257
+ if (typeof options.logFile !== "string") {
132258
+ return { ok: false, error: 'Action option "logFile" must be a string resolving under .spur/run/' };
132259
+ }
132260
+ try {
132261
+ logNormalized = await resolveRunArtifactPath(this.fileSystem, workdir, options.logFile);
132262
+ } catch (error) {
132263
+ return {
132264
+ ok: false,
132265
+ error: `logFile ${error.message}`
132266
+ };
132267
+ }
132268
+ }
131832
132269
  const split = splitLaunchCommand(executable, 'command.gate "executable"');
131833
132270
  if ("error" in split) {
131834
132271
  return { ok: false, error: split.error };
@@ -131853,6 +132290,7 @@ class CommandGateActionRunner {
131853
132290
  let lastExitCode = -1;
131854
132291
  let attemptsRun = 0;
131855
132292
  let timedOut = false;
132293
+ let capturedLog = "";
131856
132294
  for (let attempt = 1;attempt <= maxAttempts; attempt++) {
131857
132295
  attemptsRun = attempt;
131858
132296
  const res = await this.processExecutor.run({
@@ -131868,7 +132306,14 @@ class CommandGateActionRunner {
131868
132306
  lastStderr = res.stderr;
131869
132307
  lastExitCode = res.exitCode ?? -1;
131870
132308
  timedOut = res.outcome === "timeout";
132309
+ capturedLog += `--- attempt ${attempt} exit ${lastExitCode}${timedOut ? " (timeout)" : ""}
132310
+ ${lastStdout}${lastStderr}`;
131871
132311
  if (res.exitCode === 0) {
132312
+ if (logNormalized !== undefined) {
132313
+ await this.fileSystem.ensureDir(normalize3(join59(logNormalized, "..")));
132314
+ await this.fileSystem.writeFile(logNormalized, `${capturedLog}gate outcome: PASS (exit 0)
132315
+ `);
132316
+ }
131872
132317
  await this.fileSystem.writeFile(normalized, `PASS
131873
132318
  `);
131874
132319
  return {
@@ -131879,7 +132324,8 @@ class CommandGateActionRunner {
131879
132324
  attempts: attemptsRun,
131880
132325
  stdout: lastStdout,
131881
132326
  stderr: lastStderr,
131882
- resultFile: normalized
132327
+ resultFile: normalized,
132328
+ ...logNormalized !== undefined ? { logFile: logNormalized } : {}
131883
132329
  }
131884
132330
  };
131885
132331
  }
@@ -131894,6 +132340,11 @@ ${lastStderr}`;
131894
132340
  }
131895
132341
  break;
131896
132342
  }
132343
+ if (logNormalized !== undefined) {
132344
+ await this.fileSystem.ensureDir(normalize3(join59(logNormalized, "..")));
132345
+ await this.fileSystem.writeFile(logNormalized, `${capturedLog}gate outcome: ${commandGateOutcome(lastExitCode)} (exit ${lastExitCode})
132346
+ `);
132347
+ }
131897
132348
  await this.fileSystem.writeFile(normalized, `${commandGateOutcome(lastExitCode)}
131898
132349
  `);
131899
132350
  const failureData = {
@@ -131903,6 +132354,7 @@ ${lastStderr}`;
131903
132354
  stdout: lastStdout,
131904
132355
  stderr: lastStderr,
131905
132356
  resultFile: normalized,
132357
+ ...logNormalized !== undefined ? { logFile: logNormalized } : {},
131906
132358
  ...timedOut ? { timedOut: true, timeoutMs } : {}
131907
132359
  };
131908
132360
  if (softFail) {
@@ -135927,6 +136379,18 @@ class WorkflowInspectOps {
135927
136379
  const entries = await Promise.all(rows.map(async (row) => rowToTraceEntry(row, this.ctx.cwd, Date.now(), row.status === "paused" || row.status === "interrupted" ? pausedNodeBudgetMs(await actionDao.actionRowsByRunId(row.id)) : undefined)));
135928
136380
  return { entries, total: rows.length };
135929
136381
  }
136382
+ async resolveRunId(db, input) {
136383
+ const runDao = new RunDao(db);
136384
+ if (await runDao.traceRowById(input))
136385
+ return input;
136386
+ const rows = await runDao.traceRowsByIdPrefix(input);
136387
+ if (rows.length === 0)
136388
+ throw new Error(`Run not found: ${input}`);
136389
+ if (rows.length === 1)
136390
+ return rows[0]?.id ?? input;
136391
+ const candidates = rows.slice(0, 20).map((r) => r.id);
136392
+ throw new Error(`Ambiguous run id prefix: ${input}; candidates: ${candidates.join(", ")}`);
136393
+ }
135930
136394
  async traceRun(db, runId, preloadedRow) {
135931
136395
  const runDao = new RunDao(db);
135932
136396
  const row = preloadedRow ?? await runDao.traceRowById(runId);
@@ -137207,10 +137671,18 @@ class WorkflowAppService {
137207
137671
  const db = await this.ctx.getDb();
137208
137672
  if (typeof runIdOrFilter === "string") {
137209
137673
  const probe = await reapIfWorkerLost(db, this.ctx.cwd, runIdOrFilter);
137210
- return this.inspectOps.traceRun(db, runIdOrFilter, probe.reaped ? undefined : probe.row);
137674
+ if (probe.row !== undefined) {
137675
+ return this.inspectOps.traceRun(db, runIdOrFilter, probe.reaped ? undefined : probe.row);
137676
+ }
137677
+ const resolved = await this.inspectOps.resolveRunId(db, runIdOrFilter);
137678
+ const resolvedProbe = await reapIfWorkerLost(db, this.ctx.cwd, resolved);
137679
+ return this.inspectOps.traceRun(db, resolved, resolvedProbe.reaped ? undefined : resolvedProbe.row);
137211
137680
  }
137212
137681
  return this.inspectOps.traceList(db, runIdOrFilter ?? {});
137213
137682
  }
137683
+ async resolveRunId(runId) {
137684
+ return this.inspectOps.resolveRunId(await this.ctx.getDb(), runId);
137685
+ }
137214
137686
  inspectRunRecord(runId) {
137215
137687
  return this.inspectOps.inspectRunRecord(runId);
137216
137688
  }
@@ -146170,6 +146642,83 @@ function resolveReceiptReuse(receiptPath, wbs, capture) {
146170
146642
  const receiptStatus = readReceiptStatus(receiptPath, capture.tree, wbs);
146171
146643
  return { reuse: receiptStatus.reuse, receiptStatus };
146172
146644
  }
146645
+ function workspaceGraphFrom(workspaces) {
146646
+ const byName = new Map;
146647
+ for (const workspace of workspaces) {
146648
+ if (workspace.name.length > 0)
146649
+ byName.set(workspace.name, workspace.dir);
146650
+ }
146651
+ const dependents = new Map;
146652
+ for (const workspace of workspaces) {
146653
+ for (const dependency of new Set(workspace.dependencies)) {
146654
+ if (dependency === workspace.name || !byName.has(dependency))
146655
+ continue;
146656
+ const dirs = dependents.get(dependency) ?? [];
146657
+ if (!dirs.includes(workspace.dir))
146658
+ dirs.push(workspace.dir);
146659
+ dependents.set(dependency, dirs);
146660
+ }
146661
+ }
146662
+ return { byName, dependents };
146663
+ }
146664
+ function dependentsOf(graph, dir) {
146665
+ for (const [name, owner] of graph.byName) {
146666
+ if (owner !== dir)
146667
+ continue;
146668
+ return graph.dependents.get(name) ?? [];
146669
+ }
146670
+ return [];
146671
+ }
146672
+ function workspaceDependenciesOf(abs, dir) {
146673
+ let name = "";
146674
+ const dependencies = [];
146675
+ try {
146676
+ const pkg = JSON.parse(readFileSync21(abs(`${dir}/package.json`), "utf8"));
146677
+ if (typeof pkg.name === "string")
146678
+ name = pkg.name;
146679
+ for (const section of [pkg.dependencies, pkg.devDependencies, pkg.peerDependencies]) {
146680
+ if (section !== null && typeof section === "object")
146681
+ dependencies.push(...Object.keys(section));
146682
+ }
146683
+ } catch {}
146684
+ return { name, dependencies };
146685
+ }
146686
+ function readWorkspaceGraph(cwd) {
146687
+ const abs = (p) => cwd ? join78(cwd, p) : p;
146688
+ const info = [];
146689
+ let globs = [];
146690
+ try {
146691
+ const root = JSON.parse(readFileSync21(abs("package.json"), "utf8"));
146692
+ const declared = Array.isArray(root.workspaces) ? root.workspaces : root.workspaces?.packages;
146693
+ if (Array.isArray(declared))
146694
+ globs = declared.filter((g) => typeof g === "string");
146695
+ } catch {
146696
+ return { byName: new Map, dependents: new Map };
146697
+ }
146698
+ const dirs = new Set;
146699
+ for (const glob of globs) {
146700
+ if (glob.endsWith("/*")) {
146701
+ const parent = glob.slice(0, -2);
146702
+ let entries = [];
146703
+ try {
146704
+ entries = readdirSync11(abs(parent));
146705
+ } catch {
146706
+ continue;
146707
+ }
146708
+ for (const entry of entries) {
146709
+ if (existsSync23(abs(`${parent}/${entry}/package.json`)))
146710
+ dirs.add(`${parent}/${entry}`);
146711
+ }
146712
+ } else if (existsSync23(abs(`${glob}/package.json`))) {
146713
+ dirs.add(glob);
146714
+ }
146715
+ }
146716
+ for (const dir of [...dirs].sort()) {
146717
+ const { name, dependencies } = workspaceDependenciesOf(abs, dir);
146718
+ info.push({ dir, name, dependencies });
146719
+ }
146720
+ return workspaceGraphFrom(info);
146721
+ }
146173
146722
  function workspaceOf(file, exists) {
146174
146723
  const segments = file.split("/");
146175
146724
  for (let depth = 1;depth < segments.length; depth++) {
@@ -146179,7 +146728,7 @@ function workspaceOf(file, exists) {
146179
146728
  }
146180
146729
  return null;
146181
146730
  }
146182
- function lightScope(changedFiles, exists = existsSync23) {
146731
+ function lightScope(changedFiles, exists = existsSync23, graph) {
146183
146732
  const files = [];
146184
146733
  const workspaces = new Set;
146185
146734
  const tests = new Set;
@@ -146229,7 +146778,29 @@ function lightScope(changedFiles, exists = existsSync23) {
146229
146778
  tests.add(file);
146230
146779
  }
146231
146780
  }
146232
- return { files, workspaces: [...workspaces].sort(), tests: [...tests].sort() };
146781
+ let fallback;
146782
+ if (graph !== undefined) {
146783
+ for (const file of changedFiles) {
146784
+ if (file.startsWith("plugins/sp/"))
146785
+ continue;
146786
+ const owner = workspaceOf(file, exists);
146787
+ if (owner === null) {
146788
+ fallback = `unmapped path ${file} (no owning workspace package.json) \u2014 full suite required`;
146789
+ break;
146790
+ }
146791
+ const dependents = dependentsOf(graph, owner);
146792
+ if (dependents.length > 0) {
146793
+ fallback = `shared package ${owner} (depended on by ${dependents.join(", ")}) \u2014 full suite required`;
146794
+ break;
146795
+ }
146796
+ }
146797
+ }
146798
+ return {
146799
+ files,
146800
+ workspaces: [...workspaces].sort(),
146801
+ tests: [...tests].sort(),
146802
+ ...fallback !== undefined ? { fallback } : {}
146803
+ };
146233
146804
  }
146234
146805
  function workspaceHasTypecheck(workspace) {
146235
146806
  try {
@@ -146272,10 +146843,21 @@ function planLightChecks(scope, hasTypecheck = workspaceHasTypecheck, builder) {
146272
146843
  }
146273
146844
  return plans;
146274
146845
  }
146275
- function gitChangedFiles(cwd, timeoutMs) {
146846
+ function readBaseSha(path) {
146847
+ if (path === undefined)
146848
+ return null;
146849
+ try {
146850
+ const line = readFileSync21(path, "utf8").trim();
146851
+ return /^[0-9a-f]{7,64}$/i.test(line) ? line : null;
146852
+ } catch {
146853
+ return null;
146854
+ }
146855
+ }
146856
+ function gitChangedFiles(cwd, timeoutMs, baseShaPath) {
146276
146857
  const abs = (p) => cwd ? join78(cwd, p) : p;
146277
146858
  const changed = new Set;
146278
- for (const cmd of ["git diff --name-only HEAD", "git ls-files --others --exclude-standard"]) {
146859
+ const base = readBaseSha(baseShaPath) ?? "HEAD";
146860
+ for (const cmd of [`git diff --name-only ${base}`, "git ls-files --others --exclude-standard"]) {
146279
146861
  const result = runShellCommand(cmd, cwd, undefined, timeoutMs);
146280
146862
  if (result.timedOut)
146281
146863
  return null;
@@ -146302,6 +146884,7 @@ function runDeferredGate(env, options = {}) {
146302
146884
  writeFileSync14(abs(statusFile), `${status}
146303
146885
  `);
146304
146886
  const note = status === "DEFERRED" ? `deferred gate: light tier PASS \u2014 the integrated full gate is owed on the batch base ref (1111 R3)
146887
+ ` : light.blocked !== undefined ? `deferred gate: light tier BLOCKED (${light.blocked}) \u2014 no scoped tier could decide; the integrated full gate stays owed (1111 R3; 1322 R3)
146305
146888
  ` : `deferred gate: light tier FAIL \u2014 the task's bounded fix lane runs; the integrated full gate stays owed (1111 R3)
146306
146889
  `;
146307
146890
  appendFileSync7(abs(light.logFile), note);
@@ -146318,7 +146901,7 @@ function runLightGate(env, options = {}) {
146318
146901
  const receiptFile = join78(runDir, `${env.wbs}-check-receipt.json`);
146319
146902
  writeFileSync14(abs(logFile), "");
146320
146903
  const timeoutMs = gateTimeoutMsFor(env, options);
146321
- const changed = gitChangedFiles(cwd, timeoutMs);
146904
+ const changed = gitChangedFiles(cwd, timeoutMs, abs(join78(runDir, `${env.wbs}-base.sha`)));
146322
146905
  if (changed === null) {
146323
146906
  const line = `--- light gate BLOCKED: changed-path git read timed out after ${timeoutMs}ms
146324
146907
  `;
@@ -146339,7 +146922,29 @@ function runLightGate(env, options = {}) {
146339
146922
  appendFileSync7(abs(logFile), `--- light receipt: no git tree identity \u2014 ${capture.error}
146340
146923
  `);
146341
146924
  }
146342
- const scope = lightScope(changed, (p) => existsSync23(abs(p)));
146925
+ const scope = lightScope(changed, (p) => existsSync23(abs(p)), readWorkspaceGraph(cwd));
146926
+ const scopeHeader = scope.fallback !== undefined ? `--- light scope: full-suite-fallback \u2014 ${scope.fallback}
146927
+ --- light scope files (${changed.length}): ${changed.join(" ")}
146928
+ ` : `--- light scope: selected \u2014 workspaces: ${scope.workspaces.join(" ") || "(none)"}; related tests: ${scope.tests.length}; files: ${changed.length}
146929
+ --- light scope files (${changed.length}): ${changed.join(" ")}
146930
+ `;
146931
+ appendFileSync7(abs(logFile), scopeHeader);
146932
+ if (scope.fallback !== undefined) {
146933
+ const line = `--- light gate BLOCKED: affected-scope fallback \u2014 full gate owns this tree
146934
+ `;
146935
+ appendFileSync7(abs(logFile), line);
146936
+ process.stdout.write(`light gate BLOCKED \u2014 ${scope.fallback}
146937
+ `);
146938
+ return {
146939
+ status: "FAIL",
146940
+ scope,
146941
+ checks: [],
146942
+ logFile,
146943
+ receiptFile,
146944
+ blocked: scope.fallback,
146945
+ fallback: scope.fallback
146946
+ };
146947
+ }
146343
146948
  const plans = planLightChecks(scope, (workspace) => workspaceHasTypecheck(abs(workspace)), options.builder);
146344
146949
  const reusable = new Map;
146345
146950
  let preserveFullReceipt = false;
@@ -146401,6 +147006,11 @@ function runLightGate(env, options = {}) {
146401
147006
  appendFileSync7(abs(logFile), `--- light receipt: not written \u2014 the full-tier receipt at the same git tree is preserved
146402
147007
  `);
146403
147008
  process.stdout.write(`light gate ${receipt.status} (${scope.files.length} changed files; checks: ${checks.length},` + ` skipped: ${skipped}, unbound: ${unbound}; receipt: ${receiptFile} preserved (full tier); log: ${logFile})
147009
+ `);
147010
+ } else if (!capture.ok) {
147011
+ appendFileSync7(abs(logFile), `--- light receipt: not written \u2014 git tree capture failed (${capture.error})
147012
+ `);
147013
+ process.stdout.write(`light gate ${receipt.status} (${scope.files.length} changed files; checks: ${checks.length},` + ` skipped: ${skipped}, unbound: ${unbound}; receipt: none (git tree capture failed); log: ${logFile})
146404
147014
  `);
146405
147015
  } else {
146406
147016
  writeFileSync14(abs(receiptFile), `${JSON.stringify(receipt, null, 2)}
@@ -146709,6 +147319,37 @@ function runQualityGate(mode, env, options = {}) {
146709
147319
  appendFileSync7(abs(logFile), line);
146710
147320
  gateRc = 1;
146711
147321
  }
147322
+ if (mode === "recheck" && commandPresent && gateRc === 0) {
147323
+ const light = runLightGate(env, options);
147324
+ appendFileSync7(abs(logFile), `--- gate visit ${mode}: affected-scope light tier (1322) \u2014 ${light.status}
147325
+ ${readFileSync21(abs(light.logFile), "utf8")}`);
147326
+ if (light.fallback === undefined) {
147327
+ if (light.status === "FAIL") {
147328
+ writeFileSync14(abs(findingsFile), extractFindings(readFileSync21(abs(logFile), "utf8")));
147329
+ writeFileSync14(abs(statusFile), `FAIL
147330
+ `);
147331
+ appendFileSync7(abs(logFile), `git-tree: ${tree.ok ? tree.tree : "unbound"}
147332
+ `);
147333
+ process.stdout.write(`quality gate FAIL (light tier) \u2014 last 40 lines follow (full log: ${logFile})
147334
+ `);
147335
+ process.stdout.write(tailLines(readFileSync21(abs(logFile), "utf8"), 40));
147336
+ return {
147337
+ status: "FAIL",
147338
+ attempts: 0,
147339
+ logFile,
147340
+ findingsFile,
147341
+ statusFile,
147342
+ attemptFile,
147343
+ receiptFile: light.receiptFile
147344
+ };
147345
+ }
147346
+ } else {
147347
+ const line = `check.scope-fallback \u2014 ${light.fallback}; probe + full gate follow
147348
+ `;
147349
+ process.stdout.write(line);
147350
+ appendFileSync7(abs(logFile), line);
147351
+ }
147352
+ }
146712
147353
  if (mode === "recheck" && gateRc === 0 && (env.gateProbeCmd ?? "").length > 0) {
146713
147354
  const probe = runShellCommand(env.gateProbeCmd ?? "", cwd, undefined, timeoutMs);
146714
147355
  writeFileSync14(abs(`${logFile}.probe`), probe.output);
@@ -147279,6 +147920,15 @@ function parseProvenance(reportMarkdown) {
147279
147920
  schemaVersion: obj.schemaVersion == null ? undefined : Number(obj.schemaVersion),
147280
147921
  executor: optionalString(obj.executor),
147281
147922
  model: optionalString(obj.model),
147923
+ executorProfile: optionalString(obj.executorProfile),
147924
+ dispatches: Array.isArray(obj.dispatches) ? obj.dispatches.map((d) => ({
147925
+ node: String(d.node ?? ""),
147926
+ actionId: String(d.actionId ?? ""),
147927
+ agent: String(d.agent ?? ""),
147928
+ executor: String(d.executor ?? ""),
147929
+ model: String(d.model ?? ""),
147930
+ timeoutMs: d.timeoutMs === "unbounded" ? "unbounded" : Number(d.timeoutMs ?? 0)
147931
+ })) : undefined,
147282
147932
  cacheDisposition: DISPOSITIONS.includes(obj.cacheDisposition) ? obj.cacheDisposition : undefined
147283
147933
  };
147284
147934
  } catch {
@@ -147322,6 +147972,88 @@ function decideCache(cached, current, opts) {
147322
147972
  reasons.push("window-closed");
147323
147973
  return { disposition: reasons.length === 0 ? "hit" : "miss", reasons };
147324
147974
  }
147975
+ function evaluateCalcAssertion(expr) {
147976
+ const text = expr.trim().replace(/^`?calc`?\s*:\s*/i, "").trim();
147977
+ const cmp = text.match(new RegExp(`^(${NUM})\\s*(<=|>=|<|>)\\s*(${NUM})$`));
147978
+ if (cmp !== null) {
147979
+ const left = Number(cmp[1]);
147980
+ const right = Number(cmp[3]);
147981
+ switch (cmp[2]) {
147982
+ case "<":
147983
+ return left < right;
147984
+ case "<=":
147985
+ return left <= right;
147986
+ case ">":
147987
+ return left > right;
147988
+ default:
147989
+ return left >= right;
147990
+ }
147991
+ }
147992
+ const eq = text.match(new RegExp(`^(${NUM}(?:\\s*[-+*/]\\s*${NUM})+)\\s*=\\s*(${NUM})$`));
147993
+ if (eq === null)
147994
+ return;
147995
+ const tokens = (eq[1] ?? "").split(/\s*([-+*/])\s*/);
147996
+ let acc = Number(tokens[0]);
147997
+ let exact = Number.isInteger(acc);
147998
+ let divided = false;
147999
+ for (let i = 1;i < tokens.length; i += 2) {
148000
+ const op = tokens[i];
148001
+ const operand = Number(tokens[i + 1]);
148002
+ if (op === "+")
148003
+ acc += operand;
148004
+ else if (op === "-")
148005
+ acc -= operand;
148006
+ else if (op === "*")
148007
+ acc *= operand;
148008
+ else {
148009
+ if (operand === 0)
148010
+ return false;
148011
+ acc /= operand;
148012
+ divided = true;
148013
+ }
148014
+ exact = exact && Number.isInteger(operand);
148015
+ }
148016
+ const expected = Number(eq[2]);
148017
+ if (!divided && exact && Number.isInteger(expected))
148018
+ return acc === expected;
148019
+ const scale = Math.max(Math.abs(acc), Math.abs(expected), 1);
148020
+ return Math.abs(acc - expected) <= 0.000000001 * scale;
148021
+ }
148022
+ function auditArithmetic(findingsBody, problems) {
148023
+ const blockList = findingsBody.split(/^###\s+/m).slice(1);
148024
+ for (const block of blockList) {
148025
+ const lines = block.split(`
148026
+ `);
148027
+ const calcLines = lines.filter((l) => CALC_LINE.test(l));
148028
+ for (const line of calcLines) {
148029
+ const expr = line.slice(line.search(/calc/i));
148030
+ const verdict = evaluateCalcAssertion(expr);
148031
+ const shown = expr.replace(/^`?calc`?\s*:\s*/i, "").trim();
148032
+ if (verdict === undefined)
148033
+ problems.push(`arithmetic-invalid-assertion:${shown}`);
148034
+ else if (!verdict)
148035
+ problems.push(`arithmetic-contradiction:${shown}`);
148036
+ }
148037
+ const fieldNames = ["observation", "inference"];
148038
+ for (const field of fieldNames) {
148039
+ const start = lines.findIndex((l) => new RegExp(`^[-*\\s]*\\*{0,2}\`?${field}\`?\\*{0,2}\\s*:`).test(l));
148040
+ if (start === -1)
148041
+ continue;
148042
+ let end = lines.length;
148043
+ for (let i = start + 1;i < lines.length; i++) {
148044
+ if (/^[-*]\s*`?[a-zA-Z]+`?\s*:/.test(lines[i] ?? "")) {
148045
+ end = i;
148046
+ break;
148047
+ }
148048
+ }
148049
+ const span = lines.slice(start, end).join(`
148050
+ `);
148051
+ if (DERIVED_SIGNAL.test(span) && !CALC_LINE.test(span)) {
148052
+ problems.push(`arithmetic-evidence-missing:${field}`);
148053
+ }
148054
+ }
148055
+ }
148056
+ }
147325
148057
  function escapeRe(s) {
147326
148058
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
147327
148059
  }
@@ -147379,6 +148111,7 @@ function checkReportStructure(reportMarkdown) {
147379
148111
  problems.push(`finding-invalid-key-category:${firstSegment}`);
147380
148112
  }
147381
148113
  }
148114
+ auditArithmetic(findingsBody, problems);
147382
148115
  }
147383
148116
  const ledgerIdx = reportMarkdown.search(/^#{2,3}\s+Evidence\s+ledger/im);
147384
148117
  if (ledgerIdx !== -1) {
@@ -147561,6 +148294,66 @@ function validateSelector(opts) {
147561
148294
  until: val(opts.until)
147562
148295
  };
147563
148296
  }
148297
+ function collectAgentRuns(node, out) {
148298
+ if (Array.isArray(node)) {
148299
+ for (const item of node)
148300
+ collectAgentRuns(item, out);
148301
+ return;
148302
+ }
148303
+ if (node === null || typeof node !== "object")
148304
+ return;
148305
+ const obj = node;
148306
+ if (obj.actionKind === "agent.run" && obj.ok === true)
148307
+ out.push(obj);
148308
+ for (const value of Object.values(obj))
148309
+ collectAgentRuns(value, out);
148310
+ }
148311
+ function applyFinalProvenance(p, input) {
148312
+ const lines = readFileSync25(input.validationPath, "utf8").trimEnd().split(`
148313
+ `);
148314
+ if ((lines[lines.length - 1] ?? "").trim() !== "Verdict: PASS") {
148315
+ throw new Error(`final validation artifact does not end with "Verdict: PASS": ${input.validationPath}`);
148316
+ }
148317
+ const validatedAt = statSync17(input.validationPath).mtime.toISOString();
148318
+ let trace;
148319
+ try {
148320
+ trace = JSON.parse(readFileSync25(input.tracePath, "utf8"));
148321
+ } catch (err) {
148322
+ throw new Error(`could not parse run trace at ${input.tracePath}: ${err.message}`);
148323
+ }
148324
+ const collected = [];
148325
+ collectAgentRuns(trace, collected);
148326
+ const seen = new Set;
148327
+ const dispatches = [];
148328
+ for (const action of collected) {
148329
+ const actionId = action.actionId ?? "";
148330
+ if (actionId !== "" && seen.has(actionId))
148331
+ continue;
148332
+ if (actionId !== "")
148333
+ seen.add(actionId);
148334
+ const inv = action.invocation ?? {};
148335
+ dispatches.push({
148336
+ node: action.node ?? "",
148337
+ actionId,
148338
+ agent: inv.agent != null && inv.agent !== "" ? inv.agent : "not available: invocation agent absent",
148339
+ executor: inv.executor != null && inv.executor !== "" ? inv.executor : "not available: invocation executor absent",
148340
+ model: inv.model != null && inv.model !== "" ? inv.model : "not available: invocation model absent",
148341
+ timeoutMs: typeof inv.timeoutMs === "number" && inv.timeoutMs > 0 ? inv.timeoutMs : "unbounded"
148342
+ });
148343
+ }
148344
+ if (dispatches.length === 0) {
148345
+ throw new Error(`run trace records no successful agent.run action: ${input.tracePath}`);
148346
+ }
148347
+ const finalValidation = [...dispatches].reverse().find((d) => d.node === "validate") ?? dispatches[dispatches.length - 1];
148348
+ return {
148349
+ ...p,
148350
+ validatedAt,
148351
+ dispatches,
148352
+ executor: finalValidation?.agent ?? NOT_AVAILABLE,
148353
+ model: finalValidation?.model ?? NOT_AVAILABLE,
148354
+ executorProfile: finalValidation?.executor ?? NOT_AVAILABLE
148355
+ };
148356
+ }
147564
148357
  function buildProvenance(opts) {
147565
148358
  let raw;
147566
148359
  try {
@@ -147608,10 +148401,10 @@ function buildProvenance(opts) {
147608
148401
  runId: opts.runId,
147609
148402
  currentArtifactPath: opts.artifact,
147610
148403
  baselineArtifactPath: opts.baseline ?? null,
147611
- spurVersion: opts.spurVersion ?? NOT_AVAILABLE,
148404
+ spurVersion: opts.spurVersion !== undefined && opts.spurVersion !== "" ? opts.spurVersion : typeof raw.spurVersion === "string" && raw.spurVersion !== "" ? raw.spurVersion : NOT_AVAILABLE,
147612
148405
  schemaVersion: typeof raw.schemaVersion === "number" ? raw.schemaVersion : undefined,
147613
- executor: opts.executor ?? NOT_AVAILABLE,
147614
- model: opts.model ?? NOT_AVAILABLE
148406
+ executor: opts.executor ?? "not available: probe did not receive an executor",
148407
+ model: opts.model ?? "not available: probe did not receive a model"
147615
148408
  };
147616
148409
  }
147617
148410
  function probe(opts) {
@@ -147656,6 +148449,12 @@ function renderProvenanceFrontmatter(p) {
147656
148449
  for (const c of p.coverage) {
147657
148450
  lines.push(` - source: ${c.source}, status: ${c.status}, lastImportedAt: ${c.lastImportedAt ?? "null"}`);
147658
148451
  }
148452
+ if (p.dispatches !== undefined && p.dispatches.length > 0) {
148453
+ lines.push("dispatches:");
148454
+ for (const d of p.dispatches) {
148455
+ lines.push(` - node: ${d.node}, actionId: ${d.actionId}, agent: ${d.agent}, executor: ${d.executor}, model: ${d.model}, timeoutMs: ${d.timeoutMs}`);
148456
+ }
148457
+ }
147659
148458
  lines.push("---");
147660
148459
  return lines.join(`
147661
148460
  `);
@@ -147707,7 +148506,7 @@ function diffPorcelain(before, now, expects) {
147707
148506
  const beforePaths = porcelainPaths(before);
147708
148507
  return [...porcelainPaths(now)].filter((p) => !beforePaths.has(p) && !expects.has(p)).sort();
147709
148508
  }
147710
- var REPORT_SECTIONS, FINDING_FIELDS, DISPOSITIONS, FINDING_CATEGORIES, NOT_AVAILABLE = "not available", YAML_KEYS;
148509
+ var REPORT_SECTIONS, FINDING_FIELDS, DISPOSITIONS, DERIVED_SIGNAL, NUM = "-?\\d+(?:\\.\\d+)?", CALC_LINE, FINDING_CATEGORIES, NOT_AVAILABLE = "not available", YAML_KEYS;
147711
148510
  var init_history_anatomy = __esm(() => {
147712
148511
  init_artifact_digest();
147713
148512
  REPORT_SECTIONS = [
@@ -147738,6 +148537,8 @@ var init_history_anatomy = __esm(() => {
147738
148537
  "ownerSurface"
147739
148538
  ];
147740
148539
  DISPOSITIONS = ["hit", "miss", "forced-recompute"];
148540
+ DERIVED_SIGNAL = /%|\b(larger|smaller|higher|lower|more than|less than|above|below|exceeds|dominates|gap|delta|difference|sum|sums|sum of|share|ratio|mean|average)\b/i;
148541
+ CALC_LINE = /`?calc`?\s*:\s*\S/i;
147741
148542
  FINDING_CATEGORIES = [
147742
148543
  "reliability",
147743
148544
  "repetition",
@@ -147763,6 +148564,7 @@ var init_history_anatomy = __esm(() => {
147763
148564
  "spurVersion",
147764
148565
  "schemaVersion",
147765
148566
  "executor",
148567
+ "executorProfile",
147766
148568
  "model",
147767
148569
  "cacheDisposition"
147768
148570
  ];
@@ -147976,16 +148778,19 @@ ${reasons}`, stderr: "" };
147976
148778
  return {
147977
148779
  exitCode: 1,
147978
148780
  stdout: "",
147979
- stderr: `usage: spur history anatomy-stamp --candidate <c.md> --provenance <p.json> --out <o.md>
148781
+ stderr: `usage: spur history anatomy-stamp --candidate <c.md> --provenance <p.json> --out <o.md> [--validation <v.txt> --trace <t.json>]
147980
148782
  `
147981
148783
  };
147982
148784
  }
147983
148785
  try {
147984
- const p = JSON.parse(readFileSync26(f.provenance, "utf8"));
148786
+ let p = JSON.parse(readFileSync26(f.provenance, "utf8"));
148787
+ if (f.validation !== undefined && f.trace !== undefined) {
148788
+ p = applyFinalProvenance(p, { validationPath: f.validation, tracePath: f.trace });
148789
+ }
147985
148790
  writeFileSync18(f.out, `${stampReport(readFileSync26(f.candidate, "utf8"), p)}
147986
148791
  `);
147987
- } catch {
147988
- return { exitCode: 1, stdout: "", stderr: `stamp: could not read candidate or provenance
148792
+ } catch (err) {
148793
+ return { exitCode: 1, stdout: "", stderr: `stamp: ${err.message}
147989
148794
  ` };
147990
148795
  }
147991
148796
  return { exitCode: 0, stdout: "", stderr: "" };
@@ -156992,6 +157797,7 @@ var SHARED_OPTIONS = {
156992
157797
  ],
156993
157798
  jsonSupported: ["--json", "Output machine-readable JSON where supported"],
156994
157799
  section: ["--section <name>", "Section name to write"],
157800
+ sectionShowFilter: ["--section <name>", "Show only this section, byte-exact (repeatable)"],
156995
157801
  fromFile: ["--from-file <path>", "File to read section body from (requires --section)"],
156996
157802
  statusFilter: ["--status <s>", "Filter by status"],
156997
157803
  last: ["--last <n>", "Limit results (default 20)"],
@@ -158817,11 +159623,13 @@ function registerTaskBatchCreateCommand(task, context) {
158817
159623
  if (options.json) {
158818
159624
  const ids = children.map((r) => r.ref.id);
158819
159625
  const normalized = children.map((r, i) => ({ wbs: ids[i] ?? r.ref.id, normalized: r.normalized ?? [] })).filter((row) => row.normalized.length > 0);
159626
+ const warnings = children.map((r, i) => ({ wbs: ids[i] ?? r.ref.id, warnings: r.warnings ?? [] })).filter((row) => row.warnings.length > 0);
158820
159627
  context.output.write(toEnvelopeJson({
158821
159628
  created: children.length,
158822
159629
  wbs: ids,
158823
159630
  parentsWired,
158824
159631
  ...normalized.length > 0 ? { normalized } : {},
159632
+ ...warnings.length > 0 ? { warnings } : {},
158825
159633
  ...featureReopened.some((r) => r !== undefined) ? { featureReopened } : {},
158826
159634
  ...readiness
158827
159635
  }, { enveloped: options.jsonEnvelope }));
@@ -158830,6 +159638,11 @@ function registerTaskBatchCreateCommand(task, context) {
158830
159638
  for (const r of children) {
158831
159639
  context.output.write(` ${r.ref.id} ${r.ref.filePath}`);
158832
159640
  }
159641
+ for (const r of children) {
159642
+ for (const warning of r.warnings ?? []) {
159643
+ context.output.error(`${r.ref.id}: ${warning}`);
159644
+ }
159645
+ }
158833
159646
  if (parentsWired.length > 0) {
158834
159647
  context.output.write(`Wired ${parentsWired.length} parent(s):`);
158835
159648
  for (const p of parentsWired) {
@@ -159135,10 +159948,20 @@ function registerTaskCreateCommand(task, context) {
159135
159948
  cwd: context.cwd
159136
159949
  });
159137
159950
  }
159951
+ const warnings = result.warnings ?? [];
159138
159952
  if (options.json) {
159139
- context.output.write(toEnvelopeJson({ ...result, wbs: result.ref.id, filePath: result.ref.filePath, ...readiness }, { enveloped: options.jsonEnvelope }));
159953
+ context.output.write(toEnvelopeJson({
159954
+ ...result,
159955
+ wbs: result.ref.id,
159956
+ filePath: result.ref.filePath,
159957
+ ...readiness,
159958
+ ...warnings.length > 0 ? { warnings } : {}
159959
+ }, { enveloped: options.jsonEnvelope }));
159140
159960
  } else {
159141
159961
  context.output.write(`Created task ${result.ref.id}: ${result.ref.filePath}`);
159962
+ for (const warning of warnings) {
159963
+ context.output.error(warning);
159964
+ }
159142
159965
  if (readiness.readiness.status === "ready") {
159143
159966
  context.output.write(`Ready: checklist + post-check passed (${result.ref.id} promoted to todo)`);
159144
159967
  }
@@ -159932,14 +160755,37 @@ function registerTaskSectionsCommand(task, context) {
159932
160755
 
159933
160756
  // src/commands/task/show.ts
159934
160757
  init_src2();
160758
+ function collectSection(value, previous = []) {
160759
+ return [...previous, value];
160760
+ }
160761
+ function parseCsvSections(csv) {
160762
+ if (csv === undefined)
160763
+ return [];
160764
+ return csv.split(",").map((name) => name.trim()).filter((name) => name !== "");
160765
+ }
159935
160766
  function registerTaskShowCommand(task, context) {
159936
- task.command("show").alias("get").summary("Show a task by WBS.").argument("<wbs>", "Task WBS number").option(...SHARED_OPTIONS.folderTasks).option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (wbs, options) => {
160767
+ task.command("show").alias("get").summary("Show a task by WBS.").argument("<wbs>", "Task WBS number").option(...SHARED_OPTIONS.folderTasks).option(...SHARED_OPTIONS.sectionShowFilter, collectSection, []).option("--sections <csv>", 'Comma-separated section names to show (e.g. "Background,Plan"); combined with --section').option("--summary", "Print frontmatter metadata plus a section index (name, heading, byte count) and total file size \u2014 no section bodies", false).option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (wbs, options) => {
160768
+ const requested = [...options.section ?? [], ...parseCsvSections(options.sections)];
160769
+ if (options.summary === true && requested.length > 0) {
160770
+ writeJsonError(context.output, options, "--summary cannot be combined with --section/--sections", "VALIDATION_FAILED");
160771
+ context.setExitCode(2);
160772
+ return;
160773
+ }
159937
160774
  const svc = await makeService(context, options.folder);
159938
160775
  try {
159939
- const result = await svc.show(wbs);
160776
+ const result = options.summary === true ? await svc.showSummary(wbs) : requested.length > 0 ? await svc.showSections(wbs, requested) : await svc.show(wbs);
159940
160777
  if (options.json) {
159941
160778
  const { frontmatter, ...rest } = result;
159942
160779
  context.output.write(toEnvelopeJson({ ...rest, frontmatter }, { enveloped: options.jsonEnvelope }));
160780
+ } else if ("sections" in result) {
160781
+ const header = `${taskStatusIcon(result.status)} ${result.status.toUpperCase()} \u2014 ${result.wbs}`;
160782
+ if (result.sections.every((entry) => entry.content === undefined)) {
160783
+ const lines = result.sections.map((entry) => ` ${entry.headingLine} \u2014 ${entry.byteCount} bytes`);
160784
+ context.output.write([`${header} \u2014 summary`, ...lines, ` total: ${result.totalBytes} bytes`].join(`
160785
+ `));
160786
+ } else {
160787
+ context.output.write([header, ...result.sections.map((entry) => entry.content ?? "")].join(""));
160788
+ }
159943
160789
  } else {
159944
160790
  context.output.write(`${taskStatusIcon(result.status)} ${result.status.toUpperCase()} \u2014 ${result.wbs}
159945
160791
 
@@ -159948,7 +160794,7 @@ ${result.content}`);
159948
160794
  } catch (err) {
159949
160795
  const cliCode = err?.cliCode;
159950
160796
  writeJsonError(context.output, options, String(err), "INTERNAL_ERROR", cliCode !== undefined ? { cliCode } : undefined);
159951
- context.setExitCode(1);
160797
+ context.setExitCode(cliCode === "VALIDATION_FAILED" ? 3 : 1);
159952
160798
  }
159953
160799
  });
159954
160800
  }
@@ -165193,7 +166039,7 @@ init_src2();
165193
166039
  var CLI_CONFIG = {
165194
166040
  binaryName: "spur",
165195
166041
  binaryLabel: "spur",
165196
- binaryVersion: "0.4.30",
166042
+ binaryVersion: "0.4.31",
165197
166043
  configDir: ".spur",
165198
166044
  configFile: ".spur/config.yaml",
165199
166045
  databaseFile: ".spur/spur.db"
@@ -172932,7 +173778,7 @@ init_dist5();
172932
173778
  import { homedir as homedir17 } from "os";
172933
173779
  import { basename as basename23, join as join95, relative as relative19, resolve as resolve37 } from "path";
172934
173780
  // ../server/package.json
172935
- var version4 = "0.4.30";
173781
+ var version4 = "0.4.31";
172936
173782
 
172937
173783
  // ../server/src/modules/health/index.ts
172938
173784
  var startedAt = Date.now();
@@ -180049,13 +180895,23 @@ function registerWorkflowTraceCommand(workflow, context) {
180049
180895
  context.setExitCode(1);
180050
180896
  return;
180051
180897
  }
180052
- if (options.follow === true && runId !== undefined) {
180053
- const timedOut = options.output === true ? await followRunLog(svc, runId, context.cwd, pollMs, (line) => context.output.write(line), undefined, undefined, timeoutMs) : await followTrace(svc, runId, pollMs, (line) => context.output.write(line), undefined, undefined, timeoutMs);
180898
+ let resolvedRunId = runId;
180899
+ if (runId !== undefined) {
180900
+ try {
180901
+ resolvedRunId = await svc.resolveRunId(runId);
180902
+ } catch (err) {
180903
+ writeJsonError(context.output, options, err.message, "NOT_FOUND");
180904
+ context.setExitCode(1);
180905
+ return;
180906
+ }
180907
+ }
180908
+ if (options.follow === true && resolvedRunId !== undefined) {
180909
+ const timedOut = options.output === true ? await followRunLog(svc, resolvedRunId, context.cwd, pollMs, (line) => context.output.write(line), undefined, undefined, timeoutMs) : await followTrace(svc, resolvedRunId, pollMs, (line) => context.output.write(line), undefined, undefined, timeoutMs);
180054
180910
  if (timedOut)
180055
180911
  context.setExitCode(1);
180056
180912
  return;
180057
180913
  }
180058
- const result = runId ? await svc.trace(runId) : await svc.trace({
180914
+ const result = resolvedRunId ? await svc.trace(resolvedRunId) : await svc.trace({
180059
180915
  workflow: options.workflow,
180060
180916
  status: options.status,
180061
180917
  since: options.since,