@gobing-ai/spur 0.4.30 → 0.4.33
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/.claude-plugin/marketplace.json +1 -1
- package/config/workflows/history-anatomy.yaml +13 -2
- package/config/workflows/task-pipeline.yaml +5 -0
- package/package.json +1 -1
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/skills/history-anatomy/references/operations.md +11 -0
- package/plugins/sp/skills/history-anatomy/references/report-contract.md +28 -0
- package/plugins/sp/skills/spur-cli/references/tasks.md +45 -2
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +26 -0
- package/plugins/sp/skills/spur-dev/references/execution-workflow.md +5 -0
- package/spur.js +1038 -156
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
|
-
|
|
43814
|
-
|
|
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
|
|
|
@@ -44004,6 +44082,7 @@ async function messageRollup(db, sel, opts) {
|
|
|
44004
44082
|
return db.queryAll(`WITH filtered AS (
|
|
44005
44083
|
SELECT m.source, m.session_id, m.model, m.ts, m.input_tokens, m.output_tokens,
|
|
44006
44084
|
m.cache_read_tokens, m.cache_write_tokens, m.cost_usd, m.role, m.duration_ms,
|
|
44085
|
+
m.disposition,
|
|
44007
44086
|
ROW_NUMBER() OVER (PARTITION BY m.request_id ORDER BY m.rowid DESC) AS rn,
|
|
44008
44087
|
m.request_id
|
|
44009
44088
|
FROM history_message m
|
|
@@ -44019,7 +44098,7 @@ async function messageRollup(db, sel, opts) {
|
|
|
44019
44098
|
'unknown'
|
|
44020
44099
|
) AS model,
|
|
44021
44100
|
ts, input_tokens, output_tokens, cache_read_tokens, cache_write_tokens,
|
|
44022
|
-
cost_usd, role, duration_ms
|
|
44101
|
+
cost_usd, role, duration_ms, disposition
|
|
44023
44102
|
FROM filtered WHERE request_id IS NULL OR rn = 1
|
|
44024
44103
|
)
|
|
44025
44104
|
SELECT source, model, DATE(ts) AS day,
|
|
@@ -44030,9 +44109,12 @@ async function messageRollup(db, sel, opts) {
|
|
|
44030
44109
|
SUM(cache_write_tokens) AS cacheWriteTokens,
|
|
44031
44110
|
SUM(cost_usd) AS costUsd,
|
|
44032
44111
|
SUM(CASE WHEN input_tokens IS NOT NULL OR output_tokens IS NOT NULL THEN 1 ELSE 0 END) AS recordsWithUsage,
|
|
44033
|
-
|
|
44034
|
-
|
|
44035
|
-
|
|
44112
|
+
-- 1328: latency aggregates exclude collapsed poll rows \u2014 the duration
|
|
44113
|
+
-- backfill hands poll duplicates derived durations, so executor-latency
|
|
44114
|
+
-- means/samples stay unpolluted (1324 R3's rollup-chain residue).
|
|
44115
|
+
SUM(CASE WHEN role = 'assistant' AND disposition <> 'poll-duplicate' THEN duration_ms END) AS assistantDurationMs,
|
|
44116
|
+
SUM(CASE WHEN role = 'assistant' AND duration_ms IS NULL AND disposition <> 'poll-duplicate' THEN 1 ELSE 0 END) AS assistantDurationUnmeasured,
|
|
44117
|
+
SUM(CASE WHEN role = 'assistant' AND duration_ms > 0 AND disposition <> 'poll-duplicate' THEN 1 ELSE 0 END) AS assistantDurationSamples
|
|
44036
44118
|
FROM selected
|
|
44037
44119
|
GROUP BY source, model, DATE(ts)`, ...params, ...wm.params);
|
|
44038
44120
|
}
|
|
@@ -44132,8 +44214,8 @@ async function bySession(db, sel, top, opts) {
|
|
|
44132
44214
|
SUM(COALESCE(cache_read_tokens, 0)) AS cache_read_tokens,
|
|
44133
44215
|
SUM(COALESCE(output_tokens, 0)) AS output_tokens,
|
|
44134
44216
|
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
|
|
44217
|
+
SUM(CASE WHEN role = 'assistant' AND disposition <> 'poll-duplicate' THEN COALESCE(duration_ms, 0) ELSE 0 END) AS assistant_duration_ms,
|
|
44218
|
+
SUM(CASE WHEN role = 'assistant' AND duration_ms IS NULL AND disposition <> 'poll-duplicate' THEN 1 ELSE 0 END) AS assistant_duration_unmeasured
|
|
44137
44219
|
FROM selected m
|
|
44138
44220
|
GROUP BY source, session_id
|
|
44139
44221
|
), last_messages AS (
|
|
@@ -44141,7 +44223,7 @@ async function bySession(db, sel, top, opts) {
|
|
|
44141
44223
|
ROW_NUMBER() OVER (
|
|
44142
44224
|
PARTITION BY source, session_id ORDER BY seq DESC, source_rowid DESC
|
|
44143
44225
|
) AS rank
|
|
44144
|
-
FROM selected WHERE disposition
|
|
44226
|
+
FROM selected WHERE disposition NOT IN ('meta', 'poll-duplicate') -- 1324 R3: latency denominator excludes collapsed polls
|
|
44145
44227
|
)
|
|
44146
44228
|
SELECT ms.session_id AS sessionId, ms.source AS source, ms.model AS model,
|
|
44147
44229
|
ms.started_at AS startedAt, ms.ended_at AS endedAt, ms.messages AS messages,
|
|
@@ -44311,7 +44393,7 @@ async function bySessionPage(db, sel, input, opts) {
|
|
|
44311
44393
|
last_messages AS (
|
|
44312
44394
|
SELECT source, session_id, record_hash, role,
|
|
44313
44395
|
ROW_NUMBER() OVER (PARTITION BY source, session_id ORDER BY seq DESC, source_rowid DESC) AS rank
|
|
44314
|
-
FROM selected WHERE disposition
|
|
44396
|
+
FROM selected WHERE disposition NOT IN ('meta', 'poll-duplicate') -- 1324 R3: latency denominator excludes collapsed polls
|
|
44315
44397
|
)
|
|
44316
44398
|
SELECT ms.session_id AS sessionId, ms.source AS source, ms.model AS model,
|
|
44317
44399
|
ms.started_at AS startedAt, ms.ended_at AS endedAt, ms.messages AS messages,
|
|
@@ -44387,8 +44469,10 @@ async function loops(db, sel, opts) {
|
|
|
44387
44469
|
FROM history_tool_call tc
|
|
44388
44470
|
JOIN history_message m ON m.record_hash = tc.message_hash
|
|
44389
44471
|
WHERE tc.session_id IN (${placeholders})
|
|
44390
|
-
AND tc.args_digest IS NOT NULL
|
|
44472
|
+
AND tc.args_digest IS NOT NULL AND tc.args_digest <> ''
|
|
44391
44473
|
AND tc.session_id NOT IN ('', 'unknown', 'session')
|
|
44474
|
+
-- 1324 R3: tool calls hanging off a collapsed poll duplicate are loop noise.
|
|
44475
|
+
AND COALESCE(m.disposition, '') <> 'poll-duplicate'
|
|
44392
44476
|
GROUP BY tc.source, tc.session_id, tc.tool_name, tc.args_digest
|
|
44393
44477
|
HAVING COUNT(*) >= 3
|
|
44394
44478
|
ORDER BY repeats DESC`, ...sessionScope);
|
|
@@ -44396,7 +44480,7 @@ async function loops(db, sel, opts) {
|
|
|
44396
44480
|
const { where, params } = buildMessageWhere(sel);
|
|
44397
44481
|
const wm = applyWatermarkToWhere(where, opts?.watermark);
|
|
44398
44482
|
return db.queryAll(`WITH filtered_messages AS (
|
|
44399
|
-
SELECT m.record_hash, m.source, m.model, m.ts
|
|
44483
|
+
SELECT m.record_hash, m.source, m.model, m.ts, m.disposition
|
|
44400
44484
|
FROM history_message m
|
|
44401
44485
|
${wm.where}
|
|
44402
44486
|
)
|
|
@@ -44409,7 +44493,8 @@ async function loops(db, sel, opts) {
|
|
|
44409
44493
|
MAX(tc.seq) AS lastSeq
|
|
44410
44494
|
FROM filtered_messages fm
|
|
44411
44495
|
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')
|
|
44496
|
+
WHERE tc.args_digest IS NOT NULL AND tc.args_digest <> '' AND tc.session_id NOT IN ('', 'unknown', 'session')
|
|
44497
|
+
AND COALESCE(fm.disposition, '') <> 'poll-duplicate' -- 1324 R3: collapsed poll noise
|
|
44413
44498
|
GROUP BY fm.source, tc.session_id, tc.tool_name, tc.args_digest
|
|
44414
44499
|
HAVING COUNT(*) >= 3
|
|
44415
44500
|
ORDER BY repeats DESC`, ...params, ...wm.params);
|
|
@@ -45392,32 +45477,6 @@ var init_forensic_query = __esm(() => {
|
|
|
45392
45477
|
END`;
|
|
45393
45478
|
});
|
|
45394
45479
|
|
|
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
45480
|
// ../../packages/domain/src/analytics/history-board-marts.ts
|
|
45422
45481
|
function resolveSummaryReadPath(input) {
|
|
45423
45482
|
if (!input.fresh)
|
|
@@ -53813,18 +53872,12 @@ async function loadFolds(db, spec) {
|
|
|
53813
53872
|
}
|
|
53814
53873
|
}
|
|
53815
53874
|
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
|
-
}
|
|
53875
|
+
const { clauses, params } = buildTimeRangeClauses({ since: spec.since ?? null, until: spec.until ?? null }, { column: "occurred_at", grain: "instant" });
|
|
53826
53876
|
return { where: clauses.length > 0 ? `AND ${clauses.join(" AND ")}` : "", params };
|
|
53827
53877
|
}
|
|
53878
|
+
var init_pairings = __esm(() => {
|
|
53879
|
+
init_selector_time_range();
|
|
53880
|
+
});
|
|
53828
53881
|
// ../../packages/domain/src/analytics/render-report.ts
|
|
53829
53882
|
function assertArtifactVersion(actualVersion, artifactPath, expectedVersion = HISTORY_ARTIFACT_SCHEMA_VERSION) {
|
|
53830
53883
|
if (actualVersion !== expectedVersion) {
|
|
@@ -55645,6 +55698,7 @@ var init_workflow_measurements = __esm(() => {
|
|
|
55645
55698
|
|
|
55646
55699
|
// ../../packages/domain/src/analytics/index.ts
|
|
55647
55700
|
var init_analytics = __esm(() => {
|
|
55701
|
+
init_antigravity_etl();
|
|
55648
55702
|
init_artifact();
|
|
55649
55703
|
init_artifact_digest();
|
|
55650
55704
|
init_costs();
|
|
@@ -55655,6 +55709,7 @@ var init_analytics = __esm(() => {
|
|
|
55655
55709
|
init_importer_schema_version();
|
|
55656
55710
|
init_models();
|
|
55657
55711
|
init_narrow_artifact();
|
|
55712
|
+
init_pairings();
|
|
55658
55713
|
init_render_forensics();
|
|
55659
55714
|
init_render_pairings();
|
|
55660
55715
|
init_render_report();
|
|
@@ -58153,7 +58208,7 @@ var init_run_dao = __esm(() => {
|
|
|
58153
58208
|
FROM runs
|
|
58154
58209
|
WHERE (?1 IS NULL OR workflow_name = ?1)
|
|
58155
58210
|
AND (?2 IS NULL OR status = ?2)
|
|
58156
|
-
AND (?3 IS NULL OR started_at >= ?3)
|
|
58211
|
+
AND (?3 IS NULL OR julianday(started_at) >= julianday(?3))
|
|
58157
58212
|
AND (
|
|
58158
58213
|
?4 IS NULL
|
|
58159
58214
|
OR started_at < ?4
|
|
@@ -58165,6 +58220,9 @@ var init_run_dao = __esm(() => {
|
|
|
58165
58220
|
traceRowById(runId) {
|
|
58166
58221
|
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
58222
|
}
|
|
58223
|
+
traceRowsByIdPrefix(prefix) {
|
|
58224
|
+
return this.adapter.queryAll("SELECT id FROM runs WHERE substr(id, 1, length(?1)) = ?1 ORDER BY id LIMIT 21", prefix);
|
|
58225
|
+
}
|
|
58168
58226
|
mergeMetadata(runId, patch) {
|
|
58169
58227
|
return this.adapter.run(`UPDATE runs
|
|
58170
58228
|
SET metadata_json = json_patch(
|
|
@@ -58321,6 +58379,19 @@ var init_run_storage_reference_dao = __esm(() => {
|
|
|
58321
58379
|
});
|
|
58322
58380
|
|
|
58323
58381
|
// ../../packages/domain/src/dao/run-transfer.ts
|
|
58382
|
+
function stampWorktreeOrigin(row) {
|
|
58383
|
+
let meta = {};
|
|
58384
|
+
try {
|
|
58385
|
+
const parsed = JSON.parse(String(row.metadata_json ?? "{}"));
|
|
58386
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
58387
|
+
meta = parsed;
|
|
58388
|
+
}
|
|
58389
|
+
} catch {
|
|
58390
|
+
meta = {};
|
|
58391
|
+
}
|
|
58392
|
+
meta.worktree_origin = true;
|
|
58393
|
+
return { ...row, metadata_json: JSON.stringify(meta) };
|
|
58394
|
+
}
|
|
58324
58395
|
function insertIfAbsentOps(table, rows, targetColumns) {
|
|
58325
58396
|
return rows.map((row) => {
|
|
58326
58397
|
const columns = Object.keys(row).filter((column) => targetColumns.has(column));
|
|
@@ -58349,6 +58420,29 @@ async function ledgerOps(from, to, runId) {
|
|
|
58349
58420
|
}
|
|
58350
58421
|
return ops;
|
|
58351
58422
|
}
|
|
58423
|
+
async function worktreeRunSessionOps(from, to, runId) {
|
|
58424
|
+
const present = await from.queryFirst("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?", "history_run_session");
|
|
58425
|
+
if (present === undefined)
|
|
58426
|
+
return [];
|
|
58427
|
+
const rows = await from.queryAll("SELECT * FROM history_run_session WHERE run_id = ?", runId);
|
|
58428
|
+
if (rows.length === 0)
|
|
58429
|
+
return [];
|
|
58430
|
+
const targetColumns = await tableColumns(to, "history_run_session");
|
|
58431
|
+
const ops = [];
|
|
58432
|
+
for (const row of rows) {
|
|
58433
|
+
const exists = await to.queryFirst(`SELECT run_id FROM history_run_session
|
|
58434
|
+
WHERE run_id = ? AND source IS ? AND session_id IS ? AND exactness IS ? AND resolved_at IS ?
|
|
58435
|
+
LIMIT 1`, row.run_id, row.source ?? null, row.session_id ?? null, row.exactness ?? null, row.resolved_at ?? null);
|
|
58436
|
+
if (exists !== undefined)
|
|
58437
|
+
continue;
|
|
58438
|
+
const columns = Object.keys(row).filter((column) => targetColumns.has(column));
|
|
58439
|
+
ops.push({
|
|
58440
|
+
sql: `INSERT INTO history_run_session (${columns.join(", ")}) ` + `VALUES (${columns.map(() => "?").join(", ")})`,
|
|
58441
|
+
params: columns.map((column) => column === "mechanism" ? "worktree-merge" : row[column])
|
|
58442
|
+
});
|
|
58443
|
+
}
|
|
58444
|
+
return ops;
|
|
58445
|
+
}
|
|
58352
58446
|
async function tableColumns(db, table) {
|
|
58353
58447
|
const rows = await db.queryAll(`PRAGMA table_info(${table})`);
|
|
58354
58448
|
return new Set(rows.map((row) => row.name));
|
|
@@ -58387,7 +58481,8 @@ async function transferRunTables(from, to) {
|
|
|
58387
58481
|
continue;
|
|
58388
58482
|
}
|
|
58389
58483
|
}
|
|
58390
|
-
const ops = insertIfAbsentOps("runs", [row], targetRunsColumns);
|
|
58484
|
+
const ops = insertIfAbsentOps("runs", [stampWorktreeOrigin(row)], targetRunsColumns);
|
|
58485
|
+
ops.push(...await worktreeRunSessionOps(from, to, id));
|
|
58391
58486
|
for (const table of CHILD_TABLES) {
|
|
58392
58487
|
ops.push(...insertIfAbsentOps(table, await from.queryAll(`SELECT * FROM ${table} WHERE run_id = ?`, id), await tableColumns(to, table)));
|
|
58393
58488
|
}
|
|
@@ -75695,6 +75790,13 @@ class MarkdownDocument {
|
|
|
75695
75790
|
get sectionNames() {
|
|
75696
75791
|
return this._sections.map((s) => s.name);
|
|
75697
75792
|
}
|
|
75793
|
+
sectionContent(name) {
|
|
75794
|
+
const section = this.findSection(name);
|
|
75795
|
+
if (section === undefined)
|
|
75796
|
+
return null;
|
|
75797
|
+
const text = section.modifiedText ?? section.originalText;
|
|
75798
|
+
return { headingLine: section.headingLine, text };
|
|
75799
|
+
}
|
|
75698
75800
|
get demotedHeadings() {
|
|
75699
75801
|
return this._demotedHeadings;
|
|
75700
75802
|
}
|
|
@@ -99550,6 +99652,12 @@ function bounded3(value, maxLength = MAX_FIELD_LENGTH) {
|
|
|
99550
99652
|
const redacted = value.replace(SECRET_PATTERN2, "[REDACTED]");
|
|
99551
99653
|
return redacted.length <= maxLength ? redacted : `${redacted.slice(0, maxLength)}\u2026`;
|
|
99552
99654
|
}
|
|
99655
|
+
function positiveMs(value) {
|
|
99656
|
+
const n = typeof value === "string" && value.trim() !== "" ? Number(value) : value;
|
|
99657
|
+
if (typeof n !== "number" || !Number.isFinite(n) || n <= 0)
|
|
99658
|
+
return;
|
|
99659
|
+
return Math.trunc(n);
|
|
99660
|
+
}
|
|
99553
99661
|
function projectActionMetadata(kind, options) {
|
|
99554
99662
|
if (options === undefined)
|
|
99555
99663
|
return;
|
|
@@ -99561,9 +99669,9 @@ function projectActionMetadata(kind, options) {
|
|
|
99561
99669
|
if (kind === "agent.run" && typeof options.role === "string" && options.role !== "") {
|
|
99562
99670
|
metadata.role = bounded3(options.role);
|
|
99563
99671
|
}
|
|
99564
|
-
|
|
99565
|
-
|
|
99566
|
-
|
|
99672
|
+
const timeoutMs = positiveMs(options.timeoutMs);
|
|
99673
|
+
if (timeoutMs !== undefined)
|
|
99674
|
+
metadata.timeoutMs = timeoutMs;
|
|
99567
99675
|
if (typeof options.maxTokens === "number" && Number.isFinite(options.maxTokens)) {
|
|
99568
99676
|
metadata.maxTokens = Math.max(0, options.maxTokens);
|
|
99569
99677
|
}
|
|
@@ -102629,6 +102737,13 @@ function classifyDispatch(result) {
|
|
|
102629
102737
|
}
|
|
102630
102738
|
return;
|
|
102631
102739
|
}
|
|
102740
|
+
function executorUnavailabilityEvidence(result) {
|
|
102741
|
+
if (result.exitCode === 2)
|
|
102742
|
+
return `dispatch-level exit 2 (executor unusable for this run)`;
|
|
102743
|
+
if (result.signal === "unavailable")
|
|
102744
|
+
return `terminal signal ${result.signal}`;
|
|
102745
|
+
return;
|
|
102746
|
+
}
|
|
102632
102747
|
var FAILURE_RULES, PERMISSION_PATTERNS, HTTP_STATUS_PATTERN;
|
|
102633
102748
|
var init_failure_classification = __esm(() => {
|
|
102634
102749
|
init_agent_execution();
|
|
@@ -105481,6 +105596,8 @@ ${traced.stdout ?? ""}`);
|
|
|
105481
105596
|
const resumedDispatch = ok && affinityOn && !freshSession && resumeSupported && storedSessionId !== undefined;
|
|
105482
105597
|
if (!ok && traced.signal !== undefined)
|
|
105483
105598
|
resultData.signal = traced.signal;
|
|
105599
|
+
if (traced.ladder !== undefined && traced.ladder.length > 0)
|
|
105600
|
+
resultData.ladder = traced.ladder;
|
|
105484
105601
|
resultData.session = resumedDispatch ? "reused" : "fresh";
|
|
105485
105602
|
resultData.sessionSource = sessionDeclared ? "declared" : "default";
|
|
105486
105603
|
if (escalated) {
|
|
@@ -107544,6 +107661,72 @@ var init_agent_invocation_sanitize = __esm(() => {
|
|
|
107544
107661
|
SENSITIVE_INLINE_FLAG = /^(--?(?:api[-_]?key|authorization|credential|password|secret|token)=).+$/i;
|
|
107545
107662
|
});
|
|
107546
107663
|
|
|
107664
|
+
// ../../packages/app/src/services/adaptive-timeout.ts
|
|
107665
|
+
function derivedBudgetMs(latency) {
|
|
107666
|
+
const base = latency.p95Ms ?? latency.meanMs;
|
|
107667
|
+
const headroom = latency.p95Ms !== undefined ? P95_HEADROOM : MEAN_HEADROOM;
|
|
107668
|
+
return Math.max(DERIVED_FLOOR_MS, Math.ceil(base * headroom));
|
|
107669
|
+
}
|
|
107670
|
+
function resolveAdaptiveTimeoutMs(staticMs, latency) {
|
|
107671
|
+
if (latency === undefined || latency.samples <= 0) {
|
|
107672
|
+
return {
|
|
107673
|
+
source: "static",
|
|
107674
|
+
...staticMs !== undefined ? { staticMs, appliedMs: staticMs } : {},
|
|
107675
|
+
fits: true
|
|
107676
|
+
};
|
|
107677
|
+
}
|
|
107678
|
+
const derivedMs = derivedBudgetMs(latency);
|
|
107679
|
+
if (staticMs === undefined) {
|
|
107680
|
+
return {
|
|
107681
|
+
source: "derived-latency",
|
|
107682
|
+
appliedMs: derivedMs,
|
|
107683
|
+
derivedMs,
|
|
107684
|
+
meanMs: latency.meanMs,
|
|
107685
|
+
...latency.p95Ms !== undefined ? { p95Ms: latency.p95Ms } : {},
|
|
107686
|
+
samples: latency.samples,
|
|
107687
|
+
successRate: latency.successRate,
|
|
107688
|
+
fits: true
|
|
107689
|
+
};
|
|
107690
|
+
}
|
|
107691
|
+
const fits = derivedMs <= staticMs;
|
|
107692
|
+
return {
|
|
107693
|
+
source: "static",
|
|
107694
|
+
staticMs,
|
|
107695
|
+
appliedMs: staticMs,
|
|
107696
|
+
derivedMs,
|
|
107697
|
+
meanMs: latency.meanMs,
|
|
107698
|
+
...latency.p95Ms !== undefined ? { p95Ms: latency.p95Ms } : {},
|
|
107699
|
+
samples: latency.samples,
|
|
107700
|
+
successRate: latency.successRate,
|
|
107701
|
+
fits
|
|
107702
|
+
};
|
|
107703
|
+
}
|
|
107704
|
+
function ladderScore(latency) {
|
|
107705
|
+
const base = latency.p95Ms ?? latency.meanMs;
|
|
107706
|
+
const successRate = Math.min(1, Math.max(0.1, latency.successRate));
|
|
107707
|
+
return base / successRate;
|
|
107708
|
+
}
|
|
107709
|
+
async function loadExecutorLatency(source, keys) {
|
|
107710
|
+
if (source.historyLatency !== undefined) {
|
|
107711
|
+
try {
|
|
107712
|
+
return new Map(Object.entries(await source.historyLatency([...keys])));
|
|
107713
|
+
} catch {
|
|
107714
|
+
return new Map;
|
|
107715
|
+
}
|
|
107716
|
+
}
|
|
107717
|
+
if (source.getDb === undefined)
|
|
107718
|
+
return new Map;
|
|
107719
|
+
try {
|
|
107720
|
+
return await readExecutorLatency(await source.getDb(), keys);
|
|
107721
|
+
} catch {
|
|
107722
|
+
return new Map;
|
|
107723
|
+
}
|
|
107724
|
+
}
|
|
107725
|
+
var P95_HEADROOM = 1.5, MEAN_HEADROOM = 3, DERIVED_FLOOR_MS = 30000;
|
|
107726
|
+
var init_adaptive_timeout = __esm(() => {
|
|
107727
|
+
init_src2();
|
|
107728
|
+
});
|
|
107729
|
+
|
|
107547
107730
|
// ../../packages/app/src/services/agent-run-flags.ts
|
|
107548
107731
|
function stringFlag(flags, name, fallback) {
|
|
107549
107732
|
const value = flags[name];
|
|
@@ -107743,10 +107926,12 @@ class AgentResolver {
|
|
|
107743
107926
|
const failedTier = getExecutorTier(failed);
|
|
107744
107927
|
const failedCanonical = resolveAgentName2(failed.agent);
|
|
107745
107928
|
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
|
-
|
|
107929
|
+
let sideways = executors.filter((e) => {
|
|
107747
107930
|
const canonical = resolveAgentName2(e.agent);
|
|
107748
107931
|
return !executorDisabled(e) && e.name !== fromExecutor && getExecutorTier(e) === failedTier && canonical !== failedCanonical && (canonical === undefined || !exhaustedAgents.has(canonical)) && !(exclude?.has(e.name) ?? false);
|
|
107749
107932
|
});
|
|
107933
|
+
const sidewaysOrder = await this.orderEligibleByMeasuredLatency(sideways, signal !== undefined);
|
|
107934
|
+
sideways = sidewaysOrder.ordered;
|
|
107750
107935
|
for (const executor of sideways) {
|
|
107751
107936
|
const canonical = resolveAgentName2(executor.agent);
|
|
107752
107937
|
if (canonical === undefined) {
|
|
@@ -107772,6 +107957,7 @@ class AgentResolver {
|
|
|
107772
107957
|
model: executor.model,
|
|
107773
107958
|
source: "stage",
|
|
107774
107959
|
executor: executor.name,
|
|
107960
|
+
...sidewaysOrder.ordering !== undefined ? { ordering: sidewaysOrder.ordering } : {},
|
|
107775
107961
|
stage: {
|
|
107776
107962
|
stageId: stageRecord.id,
|
|
107777
107963
|
policy,
|
|
@@ -107810,7 +107996,9 @@ class AgentResolver {
|
|
|
107810
107996
|
if (eligible.length === 0) {
|
|
107811
107997
|
return;
|
|
107812
107998
|
}
|
|
107813
|
-
|
|
107999
|
+
const measuredOrder = await this.orderEligibleByMeasuredLatency(eligible, signal !== undefined);
|
|
108000
|
+
const orderedEligible = measuredOrder.ordered;
|
|
108001
|
+
for (const executor of orderedEligible) {
|
|
107814
108002
|
const canonical = resolveAgentName2(executor.agent);
|
|
107815
108003
|
if (canonical === undefined) {
|
|
107816
108004
|
return {
|
|
@@ -107834,6 +108022,7 @@ class AgentResolver {
|
|
|
107834
108022
|
model: executor.model,
|
|
107835
108023
|
source: "stage",
|
|
107836
108024
|
executor: executor.name,
|
|
108025
|
+
...measuredOrder.ordering !== undefined ? { ordering: measuredOrder.ordering } : {},
|
|
107837
108026
|
stage: {
|
|
107838
108027
|
stageId: stageRecord.id,
|
|
107839
108028
|
policy,
|
|
@@ -107845,6 +108034,36 @@ class AgentResolver {
|
|
|
107845
108034
|
}
|
|
107846
108035
|
return;
|
|
107847
108036
|
}
|
|
108037
|
+
async orderEligibleByMeasuredLatency(eligible, ordered) {
|
|
108038
|
+
if (!ordered || eligible.length <= 1)
|
|
108039
|
+
return { ordered: [...eligible] };
|
|
108040
|
+
const stats = await loadExecutorLatency(this.ctx, eligible.map((e) => e.model ?? e.name));
|
|
108041
|
+
const scored = eligible.map((executor, index) => {
|
|
108042
|
+
const hit = stats.get(executor.model ?? executor.name);
|
|
108043
|
+
return {
|
|
108044
|
+
executor,
|
|
108045
|
+
index,
|
|
108046
|
+
...hit !== undefined && hit.samples > 0 ? { score: ladderScore(hit), samples: hit.samples } : {}
|
|
108047
|
+
};
|
|
108048
|
+
});
|
|
108049
|
+
const known = scored.filter((entry) => entry.score !== undefined);
|
|
108050
|
+
if (known.length === 0)
|
|
108051
|
+
return { ordered: [...eligible] };
|
|
108052
|
+
const sorted = [...scored].sort((a, b) => {
|
|
108053
|
+
const aKnown = a.score !== undefined ? 1 : 0;
|
|
108054
|
+
const bKnown = b.score !== undefined ? 1 : 0;
|
|
108055
|
+
if (aKnown !== bKnown)
|
|
108056
|
+
return bKnown - aKnown;
|
|
108057
|
+
if (aKnown === 1)
|
|
108058
|
+
return (a.score ?? 0) - (b.score ?? 0) || a.index - b.index;
|
|
108059
|
+
return a.index - b.index;
|
|
108060
|
+
});
|
|
108061
|
+
const ordering = sorted.map((entry) => ({
|
|
108062
|
+
executor: entry.executor.name,
|
|
108063
|
+
...entry.score !== undefined ? { scoreMs: entry.score, samples: entry.samples } : {}
|
|
108064
|
+
}));
|
|
108065
|
+
return { ordered: sorted.map((entry) => entry.executor), ordering };
|
|
108066
|
+
}
|
|
107848
108067
|
async resolveAgentPriority(doctorRunner) {
|
|
107849
108068
|
const results = await doctorRunner.runAll();
|
|
107850
108069
|
for (const name of TIER1_PRIORITY2) {
|
|
@@ -108076,6 +108295,7 @@ var warnedBareBinary;
|
|
|
108076
108295
|
var init_agent_resolver = __esm(() => {
|
|
108077
108296
|
init_src2();
|
|
108078
108297
|
init_dist29();
|
|
108298
|
+
init_adaptive_timeout();
|
|
108079
108299
|
init_capability_attestation();
|
|
108080
108300
|
init_executor_tier();
|
|
108081
108301
|
warnedBareBinary = new Set;
|
|
@@ -109066,6 +109286,36 @@ class AgentRunExecutor {
|
|
|
109066
109286
|
this.resolver = resolver;
|
|
109067
109287
|
}
|
|
109068
109288
|
sessionRootRegistry = { active: new Map, overlapped: new Set };
|
|
109289
|
+
runBreakers = new Map;
|
|
109290
|
+
static BREAKER_CAP = 256;
|
|
109291
|
+
breakerFor(runId) {
|
|
109292
|
+
let breaker = this.runBreakers.get(runId);
|
|
109293
|
+
if (breaker === undefined) {
|
|
109294
|
+
breaker = { executors: new Set, touchedAt: Date.now() };
|
|
109295
|
+
this.runBreakers.set(runId, breaker);
|
|
109296
|
+
if (this.runBreakers.size > AgentRunExecutor.BREAKER_CAP) {
|
|
109297
|
+
const oldest = [...this.runBreakers.entries()].sort((a, b) => a[1].touchedAt - b[1].touchedAt);
|
|
109298
|
+
for (const [key] of oldest.slice(0, this.runBreakers.size - AgentRunExecutor.BREAKER_CAP)) {
|
|
109299
|
+
this.runBreakers.delete(key);
|
|
109300
|
+
}
|
|
109301
|
+
}
|
|
109302
|
+
}
|
|
109303
|
+
breaker.touchedAt = Date.now();
|
|
109304
|
+
return breaker;
|
|
109305
|
+
}
|
|
109306
|
+
recordLadderDecision(decisions, identity, events, decision) {
|
|
109307
|
+
const row = { ...decision, at: new Date().toISOString() };
|
|
109308
|
+
decisions.push(row);
|
|
109309
|
+
if (events !== undefined) {
|
|
109310
|
+
events.emit("agent.invoke.ladder", {
|
|
109311
|
+
runId: identity.runId,
|
|
109312
|
+
executionId: identity.executionId,
|
|
109313
|
+
...identity.actionId !== undefined ? { actionId: identity.actionId } : {},
|
|
109314
|
+
...row,
|
|
109315
|
+
severity: "warning"
|
|
109316
|
+
});
|
|
109317
|
+
}
|
|
109318
|
+
}
|
|
109069
109319
|
async executeRun(prompt, flags, deps, options) {
|
|
109070
109320
|
const silent = options.silent;
|
|
109071
109321
|
const nonInteractive = options.nonInteractive === true;
|
|
@@ -109137,6 +109387,9 @@ class AgentRunExecutor {
|
|
|
109137
109387
|
if (quotaEventBridge !== undefined)
|
|
109138
109388
|
quotaEventBridge.on("agent.quota.exhausted", onQuotaExhausted);
|
|
109139
109389
|
const tiersAttempted = new Set(currentStage ? [currentStage.executorTier] : []);
|
|
109390
|
+
const breaker = this.breakerFor(stringFlag(flags, "run-id", runCorrelation.runId));
|
|
109391
|
+
const ladderDecisions = [];
|
|
109392
|
+
const latencyCache = new Map;
|
|
109140
109393
|
if (!jsonOutput && TIER2_AGENTS2.has(currentAgent)) {
|
|
109141
109394
|
this.ctx.output.error(`Warning: ${currentAgent} is a Tier-2 agent (TUI/gateway only)`);
|
|
109142
109395
|
}
|
|
@@ -109208,6 +109461,7 @@ class AgentRunExecutor {
|
|
|
109208
109461
|
let result;
|
|
109209
109462
|
let invocation;
|
|
109210
109463
|
let dispatchStartedAt = Date.now();
|
|
109464
|
+
let lifecycleStarted = false;
|
|
109211
109465
|
const getDb = this.ctx.getDb;
|
|
109212
109466
|
let sessionObserver;
|
|
109213
109467
|
if (getDb !== undefined) {
|
|
@@ -109227,10 +109481,85 @@ class AgentRunExecutor {
|
|
|
109227
109481
|
await sessionObserver.watermark(currentAgent, sessionDir);
|
|
109228
109482
|
}
|
|
109229
109483
|
}
|
|
109484
|
+
if (currentStage !== undefined && breaker.executors.has(currentStage.executorName)) {
|
|
109485
|
+
const skippedExecutor = currentStage.executorName;
|
|
109486
|
+
runFlags.signal = "resource-exhaustion";
|
|
109487
|
+
runFlags["from-executor"] = skippedExecutor;
|
|
109488
|
+
const rerun = await this.resolver.resolveAgent(runFlags, doctorRunner, new Set([...attemptedExecutors, ...breaker.executors]));
|
|
109489
|
+
if (!rerun.ok || rerun.stage === undefined || breaker.executors.has(rerun.stage.executorName)) {
|
|
109490
|
+
const message = `executor circuit breaker: every remaining executor for stage '${skippedExecutor}' is ` + `unavailable for this run (tripped: ${[...breaker.executors].join(", ")})`;
|
|
109491
|
+
this.recordLadderDecision(ladderDecisions, lifecycle.identity, this.ctx.events, {
|
|
109492
|
+
kind: "chain-exhausted",
|
|
109493
|
+
executor: skippedExecutor,
|
|
109494
|
+
reason: message
|
|
109495
|
+
});
|
|
109496
|
+
await sessionObserver?.resolve();
|
|
109497
|
+
return { ok: false, exitCode: 2, message };
|
|
109498
|
+
}
|
|
109499
|
+
currentAgent = rerun.agent;
|
|
109500
|
+
currentModel = rerun.model;
|
|
109501
|
+
currentSource = rerun.source;
|
|
109502
|
+
currentStage = rerun.stage;
|
|
109503
|
+
currentRole = rerun.role;
|
|
109504
|
+
currentRoleOrigin = rerun.roleOrigin;
|
|
109505
|
+
dispatchExecutor.setRoleEnv(currentRole);
|
|
109506
|
+
currentTier = rerun.tier;
|
|
109507
|
+
currentExecutor = rerun.executor;
|
|
109508
|
+
routing = buildRoutingAttribution(rerun);
|
|
109509
|
+
attemptedExecutors.add(rerun.stage.executorName);
|
|
109510
|
+
tiersAttempted.add(rerun.stage.executorTier);
|
|
109511
|
+
this.recordLadderDecision(ladderDecisions, lifecycle.identity, this.ctx.events, {
|
|
109512
|
+
kind: "breaker-skip",
|
|
109513
|
+
executor: skippedExecutor,
|
|
109514
|
+
toExecutor: rerun.stage.executorName,
|
|
109515
|
+
reason: "run-scoped circuit breaker: skipped an executor proven unavailable earlier in this run"
|
|
109516
|
+
});
|
|
109517
|
+
}
|
|
109230
109518
|
try {
|
|
109231
109519
|
for (let attempt = 0;; attempt++) {
|
|
109232
109520
|
const agent = currentAgent;
|
|
109233
109521
|
const model = (attempt === 0 ? explicitModel : undefined) ?? currentModel;
|
|
109522
|
+
const attemptExecutorEntry = this.ctx.agentConfig?.executors?.find((entry) => entry.name === (currentExecutor ?? ""));
|
|
109523
|
+
const latencyKey = attemptExecutorEntry?.model ?? currentExecutor ?? agent;
|
|
109524
|
+
if (!latencyCache.has(latencyKey)) {
|
|
109525
|
+
const stats = await loadExecutorLatency(this.ctx, [latencyKey]);
|
|
109526
|
+
latencyCache.set(latencyKey, stats.get(latencyKey));
|
|
109527
|
+
}
|
|
109528
|
+
const timeoutDecision = resolveAdaptiveTimeoutMs(timeoutMs, latencyCache.get(latencyKey));
|
|
109529
|
+
const effectiveTimeoutMs = timeoutDecision.appliedMs ?? timeoutMs;
|
|
109530
|
+
if (timeoutMs !== undefined && !timeoutDecision.fits && currentStage !== undefined && breaker.executors.size + attemptedExecutors.size < 64) {
|
|
109531
|
+
const exhaustedExecutor = currentStage.executorName;
|
|
109532
|
+
runFlags.signal = "timeout";
|
|
109533
|
+
runFlags["from-executor"] = exhaustedExecutor;
|
|
109534
|
+
const rerun = await this.resolver.resolveAgent(runFlags, doctorRunner, new Set([...attemptedExecutors, ...breaker.executors]));
|
|
109535
|
+
if (rerun.ok && rerun.stage !== undefined && rerun.stage.executorName !== exhaustedExecutor && !attemptedExecutors.has(rerun.stage.executorName) && !breaker.executors.has(rerun.stage.executorName)) {
|
|
109536
|
+
currentAgent = rerun.agent;
|
|
109537
|
+
currentModel = rerun.model;
|
|
109538
|
+
currentSource = rerun.source;
|
|
109539
|
+
currentStage = rerun.stage;
|
|
109540
|
+
currentRole = rerun.role;
|
|
109541
|
+
currentRoleOrigin = rerun.roleOrigin;
|
|
109542
|
+
dispatchExecutor.setRoleEnv(currentRole);
|
|
109543
|
+
currentTier = rerun.tier;
|
|
109544
|
+
currentExecutor = rerun.executor;
|
|
109545
|
+
routing = buildRoutingAttribution(rerun);
|
|
109546
|
+
attemptedExecutors.add(rerun.stage.executorName);
|
|
109547
|
+
tiersAttempted.add(rerun.stage.executorTier);
|
|
109548
|
+
this.recordLadderDecision(ladderDecisions, lifecycle.identity, this.ctx.events, {
|
|
109549
|
+
kind: "timeout-exhausted-skip",
|
|
109550
|
+
executor: exhaustedExecutor,
|
|
109551
|
+
toExecutor: rerun.stage.executorName,
|
|
109552
|
+
reason: `measured latency exceeds the static budget; failed over without burning ` + `the ${timeoutMs}ms timeout`,
|
|
109553
|
+
detail: JSON.stringify({
|
|
109554
|
+
derivedMs: timeoutDecision.derivedMs,
|
|
109555
|
+
staticMs: timeoutMs,
|
|
109556
|
+
meanMs: timeoutDecision.meanMs,
|
|
109557
|
+
samples: timeoutDecision.samples
|
|
109558
|
+
})
|
|
109559
|
+
});
|
|
109560
|
+
continue;
|
|
109561
|
+
}
|
|
109562
|
+
}
|
|
109234
109563
|
if (requiresCapabilities !== undefined && Object.keys(requiresCapabilities).length > 0) {
|
|
109235
109564
|
const executorEntry = this.ctx.agentConfig?.executors?.find((entry) => entry.name === (currentExecutor ?? ""));
|
|
109236
109565
|
const evaluation = evaluateCapabilities(requiresCapabilities, executorEntry);
|
|
@@ -109302,19 +109631,21 @@ class AgentRunExecutor {
|
|
|
109302
109631
|
...cwd !== "" ? { cwd } : {},
|
|
109303
109632
|
mode,
|
|
109304
109633
|
outputMode,
|
|
109305
|
-
...
|
|
109634
|
+
...effectiveTimeoutMs !== undefined ? { timeoutMs: effectiveTimeoutMs } : {},
|
|
109635
|
+
...timeoutDecision.staticMs !== undefined || timeoutDecision.derivedMs !== undefined ? { timeoutBudget: timeoutDecision } : {},
|
|
109306
109636
|
continue: !freshRole && continueFlag,
|
|
109307
109637
|
stdinInteractive: false,
|
|
109308
109638
|
...model !== undefined ? { model } : {},
|
|
109309
109639
|
...sessionId !== undefined ? { sessionId } : {},
|
|
109310
109640
|
...translated !== undefined && prompt !== undefined ? { translatedFrom: traceSafePrompt(prompt) } : {}
|
|
109311
109641
|
};
|
|
109312
|
-
if (
|
|
109642
|
+
if (!lifecycleStarted) {
|
|
109643
|
+
lifecycleStarted = true;
|
|
109313
109644
|
lifecycle.start({
|
|
109314
109645
|
agent,
|
|
109315
109646
|
...model !== undefined ? { model } : {},
|
|
109316
109647
|
invocation: `${attemptInvocation.command} ${attemptInvocation.argv.join(" ")}`,
|
|
109317
|
-
...
|
|
109648
|
+
...effectiveTimeoutMs !== undefined ? { timeoutMs: effectiveTimeoutMs } : {},
|
|
109318
109649
|
...routing !== undefined ? { routing } : {}
|
|
109319
109650
|
});
|
|
109320
109651
|
dispatchStartedAt = Date.now();
|
|
@@ -109359,7 +109690,7 @@ class AgentRunExecutor {
|
|
|
109359
109690
|
try {
|
|
109360
109691
|
result = warmOutcome !== undefined ? warmResult(warmOutcome, dispatchStartedAt) : await runner.runPromptCommand(agent, promptOptions, {
|
|
109361
109692
|
cwd: cwd || undefined,
|
|
109362
|
-
...
|
|
109693
|
+
...effectiveTimeoutMs !== undefined ? { timeout: effectiveTimeoutMs } : {},
|
|
109363
109694
|
signal: controller.signal,
|
|
109364
109695
|
correlation: lifecycle.identity,
|
|
109365
109696
|
...currentExecutor !== undefined ? {
|
|
@@ -109399,8 +109730,19 @@ class AgentRunExecutor {
|
|
|
109399
109730
|
if (result.exitCode === 0)
|
|
109400
109731
|
break;
|
|
109401
109732
|
const quotaConfirmed = currentExecutor !== undefined && runQuotaExhaustedExecutors.has(currentExecutor);
|
|
109733
|
+
const unavailability = executorUnavailabilityEvidence(result);
|
|
109734
|
+
let breakerTripped = false;
|
|
109735
|
+
if (unavailability !== undefined && currentStage !== undefined) {
|
|
109736
|
+
breaker.executors.add(currentStage.executorName);
|
|
109737
|
+
breakerTripped = true;
|
|
109738
|
+
this.recordLadderDecision(ladderDecisions, lifecycle.identity, this.ctx.events, {
|
|
109739
|
+
kind: "breaker-trip",
|
|
109740
|
+
executor: currentStage.executorName,
|
|
109741
|
+
reason: `circuit breaker: ${unavailability}`
|
|
109742
|
+
});
|
|
109743
|
+
}
|
|
109402
109744
|
const classifiedSignal = classifyObjectiveFailure(result);
|
|
109403
|
-
let escalationSignal = quotaConfirmed ? "resource-exhaustion" : classifiedSignal;
|
|
109745
|
+
let escalationSignal = quotaConfirmed ? "resource-exhaustion" : classifiedSignal ?? (breakerTripped ? "resource-exhaustion" : undefined);
|
|
109404
109746
|
if (escalationSignal === undefined && result.signal === undefined && this.ctx.classifyUnmatchedFailure !== undefined) {
|
|
109405
109747
|
const text = `${result.stderr ?? ""}
|
|
109406
109748
|
${result.stdout ?? ""}`;
|
|
@@ -109415,15 +109757,21 @@ ${result.stdout ?? ""}`;
|
|
|
109415
109757
|
}
|
|
109416
109758
|
const matchingFallbacks = currentStage.policy.fallback.filter((fallback) => fallback.trigger === escalationSignal);
|
|
109417
109759
|
if (matchingFallbacks.length === 0 || escalationSignal !== "resource-exhaustion" && attempt >= matchingFallbacks.length) {
|
|
109418
|
-
|
|
109760
|
+
if (!breakerTripped)
|
|
109761
|
+
break;
|
|
109419
109762
|
}
|
|
109420
109763
|
runFlags.signal = escalationSignal;
|
|
109421
109764
|
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)) {
|
|
109765
|
+
const nextResolved = await this.resolver.resolveAgent(runFlags, doctorRunner, new Set([...attemptedExecutors, ...breaker.executors]));
|
|
109766
|
+
if (!nextResolved.ok || nextResolved.stage === undefined || attemptedExecutors.has(nextResolved.stage.executorName) || breaker.executors.has(nextResolved.stage.executorName)) {
|
|
109424
109767
|
if (!jsonOutput) {
|
|
109425
109768
|
this.ctx.output.error(`Escalation chain exhausted after ${attempt + 1} attempt(s); stage=${currentStage.stageId}; tiers attempted: ${[...tiersAttempted].join(", ")}; executors tried: ${[...attemptedExecutors].join(", ")}`);
|
|
109426
109769
|
}
|
|
109770
|
+
this.recordLadderDecision(ladderDecisions, lifecycle.identity, this.ctx.events, {
|
|
109771
|
+
kind: "chain-exhausted",
|
|
109772
|
+
executor: currentStage.executorName,
|
|
109773
|
+
reason: `escalation chain exhausted; executors tried: ${[...attemptedExecutors].join(", ")}`
|
|
109774
|
+
});
|
|
109427
109775
|
if (this.ctx.events !== undefined) {
|
|
109428
109776
|
this.ctx.events.emit("agent.invoke.exhausted", {
|
|
109429
109777
|
runId: lifecycle.identity.runId,
|
|
@@ -109438,9 +109786,12 @@ ${result.stdout ?? ""}`;
|
|
|
109438
109786
|
}
|
|
109439
109787
|
break;
|
|
109440
109788
|
}
|
|
109789
|
+
const escalationLine = `Escalating: ${currentStage.executorName} (tier ${currentStage.executorTier}) failed with ${escalationSignal}; retrying on ${nextResolved.stage.executorName} (tier ${nextResolved.stage.executorTier})`;
|
|
109790
|
+
const failedExecutor = currentStage.executorName;
|
|
109441
109791
|
if (!jsonOutput) {
|
|
109442
|
-
this.ctx.output.error(
|
|
109792
|
+
this.ctx.output.error(escalationLine);
|
|
109443
109793
|
}
|
|
109794
|
+
agentLog.append("stderr", escalationLine);
|
|
109444
109795
|
if (this.ctx.events !== undefined) {
|
|
109445
109796
|
this.ctx.events.emit("agent.invoke.escalated", {
|
|
109446
109797
|
runId: lifecycle.identity.runId,
|
|
@@ -109466,6 +109817,12 @@ ${result.stdout ?? ""}`;
|
|
|
109466
109817
|
routing = buildRoutingAttribution(nextResolved);
|
|
109467
109818
|
attemptedExecutors.add(nextResolved.stage.executorName);
|
|
109468
109819
|
tiersAttempted.add(nextResolved.stage.executorTier);
|
|
109820
|
+
this.recordLadderDecision(ladderDecisions, lifecycle.identity, this.ctx.events, {
|
|
109821
|
+
kind: "escalation",
|
|
109822
|
+
executor: failedExecutor,
|
|
109823
|
+
toExecutor: nextResolved.stage.executorName,
|
|
109824
|
+
reason: `escalated on ${escalationSignal}`
|
|
109825
|
+
});
|
|
109469
109826
|
}
|
|
109470
109827
|
} finally {
|
|
109471
109828
|
if (quotaEventBridge !== undefined)
|
|
@@ -109515,6 +109872,7 @@ ${result.stdout ?? ""}`;
|
|
|
109515
109872
|
ok: true,
|
|
109516
109873
|
result,
|
|
109517
109874
|
invocation,
|
|
109875
|
+
...ladderDecisions.length > 0 ? { ladder: ladderDecisions } : {},
|
|
109518
109876
|
...occupantRef !== undefined ? { coordination: { occupant: occupantRef } } : {}
|
|
109519
109877
|
};
|
|
109520
109878
|
}
|
|
@@ -109540,6 +109898,7 @@ var init_agent_run_executor = __esm(() => {
|
|
|
109540
109898
|
init_agent_run_log();
|
|
109541
109899
|
init_fleet_dispatch();
|
|
109542
109900
|
init_run_record();
|
|
109901
|
+
init_adaptive_timeout();
|
|
109543
109902
|
init_agent_invocation_sanitize();
|
|
109544
109903
|
init_agent_routing_attribution();
|
|
109545
109904
|
init_agent_run_preflight();
|
|
@@ -109712,6 +110071,7 @@ class AgentService {
|
|
|
109712
110071
|
return {
|
|
109713
110072
|
exitCode,
|
|
109714
110073
|
invocation: outcome.invocation,
|
|
110074
|
+
...outcome.ladder !== undefined ? { ladder: outcome.ladder } : {},
|
|
109715
110075
|
stdout: result.stdout,
|
|
109716
110076
|
stderr: result.stderr,
|
|
109717
110077
|
durationMs: result.durationMs,
|
|
@@ -119085,7 +119445,10 @@ async function deriveVerifiedOutcome(deps, window2 = {}) {
|
|
|
119085
119445
|
const linkDao = new TaskRunLinkDao(deps.db);
|
|
119086
119446
|
linkRows = [];
|
|
119087
119447
|
for (const run of runs) {
|
|
119088
|
-
|
|
119448
|
+
const startedAt = Date.parse(run.started_at ?? "");
|
|
119449
|
+
if (!Number.isFinite(startedAt))
|
|
119450
|
+
continue;
|
|
119451
|
+
if (window2.until !== null && window2.until !== undefined && startedAt > Date.parse(window2.until))
|
|
119089
119452
|
continue;
|
|
119090
119453
|
for (const link of await linkDao.listByRun(run.id, 100)) {
|
|
119091
119454
|
linkRows.push({
|
|
@@ -128183,10 +128546,19 @@ class TaskFiles {
|
|
|
128183
128546
|
slugify(title) {
|
|
128184
128547
|
return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60);
|
|
128185
128548
|
}
|
|
128549
|
+
async sizeBudgetWarnings(filePath) {
|
|
128550
|
+
try {
|
|
128551
|
+
const doc = MarkdownDocument.parse(await this.ctx.fs.readFile(filePath), "task");
|
|
128552
|
+
return evaluateTaskSize(doc.bodyWithoutFrontmatter).reasons;
|
|
128553
|
+
} catch {
|
|
128554
|
+
return [];
|
|
128555
|
+
}
|
|
128556
|
+
}
|
|
128186
128557
|
}
|
|
128187
128558
|
var init_task_files = __esm(() => {
|
|
128188
128559
|
init_src2();
|
|
128189
128560
|
init_task_service_errors();
|
|
128561
|
+
init_task_size_precheck();
|
|
128190
128562
|
});
|
|
128191
128563
|
|
|
128192
128564
|
// ../../packages/app/src/services/task-section-bodies.ts
|
|
@@ -128503,7 +128875,9 @@ ${templateBackground}`;
|
|
|
128503
128875
|
featureId: params.featureId,
|
|
128504
128876
|
parentWbs: params.parentWbs,
|
|
128505
128877
|
template: params.template,
|
|
128506
|
-
explicitStatus: params.status
|
|
128878
|
+
explicitStatus: params.status,
|
|
128879
|
+
requirements: params.requirements,
|
|
128880
|
+
plan: params.plan
|
|
128507
128881
|
});
|
|
128508
128882
|
this.validateCandidateOrThrow(params.title, shape);
|
|
128509
128883
|
let featureReopened;
|
|
@@ -128524,12 +128898,15 @@ ${templateBackground}`;
|
|
|
128524
128898
|
const ref = { kind: "task", id: wbs, filePath, folder };
|
|
128525
128899
|
return { ref, content };
|
|
128526
128900
|
});
|
|
128527
|
-
|
|
128901
|
+
const sizeWarnings = await this.files.sizeBudgetWarnings(created.ref.filePath);
|
|
128902
|
+
if (featureReopened === undefined && shape.normalized === undefined && sizeWarnings.length === 0) {
|
|
128528
128903
|
return created;
|
|
128904
|
+
}
|
|
128529
128905
|
return {
|
|
128530
128906
|
...created,
|
|
128531
128907
|
...featureReopened !== undefined ? { featureReopened } : {},
|
|
128532
|
-
...shape.normalized !== undefined ? { normalized: shape.normalized } : {}
|
|
128908
|
+
...shape.normalized !== undefined ? { normalized: shape.normalized } : {},
|
|
128909
|
+
...sizeWarnings.length > 0 ? { warnings: [...created.warnings ?? [], ...sizeWarnings] } : {}
|
|
128533
128910
|
};
|
|
128534
128911
|
}
|
|
128535
128912
|
async batchCreate(jsonPath) {
|
|
@@ -128658,7 +129035,14 @@ ${issues}`);
|
|
|
128658
129035
|
const ref = { kind: "task", id: wbs, filePath, folder };
|
|
128659
129036
|
return { ref, content };
|
|
128660
129037
|
});
|
|
128661
|
-
|
|
129038
|
+
const sizeWarnings = await this.files.sizeBudgetWarnings(created.ref.filePath);
|
|
129039
|
+
if (shape.normalized === undefined && sizeWarnings.length === 0)
|
|
129040
|
+
return created;
|
|
129041
|
+
return {
|
|
129042
|
+
...created,
|
|
129043
|
+
...shape.normalized !== undefined ? { normalized: shape.normalized } : {},
|
|
129044
|
+
...sizeWarnings.length > 0 ? { warnings: [...created.warnings ?? [], ...sizeWarnings] } : {}
|
|
129045
|
+
};
|
|
128662
129046
|
}
|
|
128663
129047
|
async deriveBackground(featureId) {
|
|
128664
129048
|
const featuresDir = this.featureLink.deriveFeaturesDir();
|
|
@@ -128725,23 +129109,62 @@ class TaskQueryOps {
|
|
|
128725
129109
|
this.locator = locator;
|
|
128726
129110
|
this.files = files;
|
|
128727
129111
|
}
|
|
128728
|
-
async
|
|
129112
|
+
async readTaskSummary(wbs) {
|
|
128729
129113
|
const filePath = await this.files.resolveTaskFile(wbs);
|
|
128730
129114
|
const raw = await this.ctx.fs.readFile(filePath);
|
|
128731
129115
|
const doc = MarkdownDocument.parse(raw, "task");
|
|
128732
129116
|
const fm = doc.frontmatterData ?? {};
|
|
128733
129117
|
const { status, featureId } = taskMetadata(fm);
|
|
128734
129118
|
return {
|
|
128735
|
-
|
|
128736
|
-
|
|
128737
|
-
|
|
128738
|
-
|
|
128739
|
-
|
|
128740
|
-
|
|
128741
|
-
|
|
128742
|
-
|
|
129119
|
+
summary: {
|
|
129120
|
+
wbs,
|
|
129121
|
+
name: fm.name ?? "",
|
|
129122
|
+
status,
|
|
129123
|
+
filePath,
|
|
129124
|
+
dependencies: Array.isArray(fm.dependencies) ? fm.dependencies.filter((dep) => typeof dep === "string") : [],
|
|
129125
|
+
feature_id: featureId,
|
|
129126
|
+
frontmatter: fm
|
|
129127
|
+
},
|
|
129128
|
+
doc,
|
|
129129
|
+
raw
|
|
128743
129130
|
};
|
|
128744
129131
|
}
|
|
129132
|
+
async show(wbs) {
|
|
129133
|
+
const { summary, doc } = await this.readTaskSummary(wbs);
|
|
129134
|
+
return { ...summary, content: doc.bodyWithoutFrontmatter };
|
|
129135
|
+
}
|
|
129136
|
+
async showSections(wbs, requested) {
|
|
129137
|
+
const { summary, doc, raw } = await this.readTaskSummary(wbs);
|
|
129138
|
+
const present = doc.sectionNames;
|
|
129139
|
+
const unknown = requested.filter((name) => !present.includes(name));
|
|
129140
|
+
if (unknown.length > 0) {
|
|
129141
|
+
const err = new Error(`Unknown section(s): ${unknown.map((n) => `"${n}"`).join(", ")}. ` + `Sections in task ${wbs}: ${present.join(", ") || "(none)"}`);
|
|
129142
|
+
err.cliCode = "VALIDATION_FAILED";
|
|
129143
|
+
throw err;
|
|
129144
|
+
}
|
|
129145
|
+
const sections = present.filter((name) => requested.includes(name)).map((name) => {
|
|
129146
|
+
const content = doc.sectionContent(name);
|
|
129147
|
+
return {
|
|
129148
|
+
name,
|
|
129149
|
+
headingLine: content?.headingLine ?? "",
|
|
129150
|
+
byteCount: Buffer.byteLength(content?.text ?? ""),
|
|
129151
|
+
content: content?.text ?? ""
|
|
129152
|
+
};
|
|
129153
|
+
});
|
|
129154
|
+
return { ...summary, sections, totalBytes: Buffer.byteLength(raw) };
|
|
129155
|
+
}
|
|
129156
|
+
async showSummary(wbs) {
|
|
129157
|
+
const { summary, doc, raw } = await this.readTaskSummary(wbs);
|
|
129158
|
+
const sections = doc.sectionNames.map((name) => {
|
|
129159
|
+
const content = doc.sectionContent(name);
|
|
129160
|
+
return {
|
|
129161
|
+
name,
|
|
129162
|
+
headingLine: content?.headingLine ?? "",
|
|
129163
|
+
byteCount: Buffer.byteLength(content?.text ?? "")
|
|
129164
|
+
};
|
|
129165
|
+
});
|
|
129166
|
+
return { ...summary, sections, totalBytes: Buffer.byteLength(raw) };
|
|
129167
|
+
}
|
|
128745
129168
|
async refresh() {
|
|
128746
129169
|
const folders = this.files.allFolderDirs();
|
|
128747
129170
|
let totalTasks = 0;
|
|
@@ -130327,7 +130750,9 @@ class TaskUpdateOps {
|
|
|
130327
130750
|
async updateBody(wbs, body, _actor) {
|
|
130328
130751
|
const filePath = await this.files.resolveTaskFile(wbs);
|
|
130329
130752
|
const ref = { kind: "task", id: wbs, filePath, folder: this.ctx.tasksDir };
|
|
130330
|
-
|
|
130753
|
+
const result = await this.writeService.updateBody(ref, body);
|
|
130754
|
+
const sizeWarnings = await this.files.sizeBudgetWarnings(filePath);
|
|
130755
|
+
return sizeWarnings.length > 0 ? { ...result, warnings: [...result.warnings ?? [], ...sizeWarnings] } : result;
|
|
130331
130756
|
}
|
|
130332
130757
|
async updateSection(wbs, sectionName, sourceFile, append = false) {
|
|
130333
130758
|
const filePath = await this.files.resolveTaskFile(wbs);
|
|
@@ -130467,6 +130892,12 @@ class TaskService {
|
|
|
130467
130892
|
async show(wbs) {
|
|
130468
130893
|
return this.queryOps.show(wbs);
|
|
130469
130894
|
}
|
|
130895
|
+
async showSections(wbs, requested) {
|
|
130896
|
+
return this.queryOps.showSections(wbs, requested);
|
|
130897
|
+
}
|
|
130898
|
+
async showSummary(wbs) {
|
|
130899
|
+
return this.queryOps.showSummary(wbs);
|
|
130900
|
+
}
|
|
130470
130901
|
async updateStatus(wbs, toStatus, actor) {
|
|
130471
130902
|
return this.updateOps.updateStatus(wbs, toStatus, actor);
|
|
130472
130903
|
}
|
|
@@ -131829,6 +132260,20 @@ class CommandGateActionRunner {
|
|
|
131829
132260
|
error: `resultFile ${error.message}`
|
|
131830
132261
|
};
|
|
131831
132262
|
}
|
|
132263
|
+
let logNormalized;
|
|
132264
|
+
if (options.logFile !== undefined && options.logFile !== null && options.logFile !== "") {
|
|
132265
|
+
if (typeof options.logFile !== "string") {
|
|
132266
|
+
return { ok: false, error: 'Action option "logFile" must be a string resolving under .spur/run/' };
|
|
132267
|
+
}
|
|
132268
|
+
try {
|
|
132269
|
+
logNormalized = await resolveRunArtifactPath(this.fileSystem, workdir, options.logFile);
|
|
132270
|
+
} catch (error) {
|
|
132271
|
+
return {
|
|
132272
|
+
ok: false,
|
|
132273
|
+
error: `logFile ${error.message}`
|
|
132274
|
+
};
|
|
132275
|
+
}
|
|
132276
|
+
}
|
|
131832
132277
|
const split = splitLaunchCommand(executable, 'command.gate "executable"');
|
|
131833
132278
|
if ("error" in split) {
|
|
131834
132279
|
return { ok: false, error: split.error };
|
|
@@ -131853,6 +132298,7 @@ class CommandGateActionRunner {
|
|
|
131853
132298
|
let lastExitCode = -1;
|
|
131854
132299
|
let attemptsRun = 0;
|
|
131855
132300
|
let timedOut = false;
|
|
132301
|
+
let capturedLog = "";
|
|
131856
132302
|
for (let attempt = 1;attempt <= maxAttempts; attempt++) {
|
|
131857
132303
|
attemptsRun = attempt;
|
|
131858
132304
|
const res = await this.processExecutor.run({
|
|
@@ -131868,7 +132314,14 @@ class CommandGateActionRunner {
|
|
|
131868
132314
|
lastStderr = res.stderr;
|
|
131869
132315
|
lastExitCode = res.exitCode ?? -1;
|
|
131870
132316
|
timedOut = res.outcome === "timeout";
|
|
132317
|
+
capturedLog += `--- attempt ${attempt} exit ${lastExitCode}${timedOut ? " (timeout)" : ""}
|
|
132318
|
+
${lastStdout}${lastStderr}`;
|
|
131871
132319
|
if (res.exitCode === 0) {
|
|
132320
|
+
if (logNormalized !== undefined) {
|
|
132321
|
+
await this.fileSystem.ensureDir(normalize3(join59(logNormalized, "..")));
|
|
132322
|
+
await this.fileSystem.writeFile(logNormalized, `${capturedLog}gate outcome: PASS (exit 0)
|
|
132323
|
+
`);
|
|
132324
|
+
}
|
|
131872
132325
|
await this.fileSystem.writeFile(normalized, `PASS
|
|
131873
132326
|
`);
|
|
131874
132327
|
return {
|
|
@@ -131879,7 +132332,8 @@ class CommandGateActionRunner {
|
|
|
131879
132332
|
attempts: attemptsRun,
|
|
131880
132333
|
stdout: lastStdout,
|
|
131881
132334
|
stderr: lastStderr,
|
|
131882
|
-
resultFile: normalized
|
|
132335
|
+
resultFile: normalized,
|
|
132336
|
+
...logNormalized !== undefined ? { logFile: logNormalized } : {}
|
|
131883
132337
|
}
|
|
131884
132338
|
};
|
|
131885
132339
|
}
|
|
@@ -131894,6 +132348,11 @@ ${lastStderr}`;
|
|
|
131894
132348
|
}
|
|
131895
132349
|
break;
|
|
131896
132350
|
}
|
|
132351
|
+
if (logNormalized !== undefined) {
|
|
132352
|
+
await this.fileSystem.ensureDir(normalize3(join59(logNormalized, "..")));
|
|
132353
|
+
await this.fileSystem.writeFile(logNormalized, `${capturedLog}gate outcome: ${commandGateOutcome(lastExitCode)} (exit ${lastExitCode})
|
|
132354
|
+
`);
|
|
132355
|
+
}
|
|
131897
132356
|
await this.fileSystem.writeFile(normalized, `${commandGateOutcome(lastExitCode)}
|
|
131898
132357
|
`);
|
|
131899
132358
|
const failureData = {
|
|
@@ -131903,6 +132362,7 @@ ${lastStderr}`;
|
|
|
131903
132362
|
stdout: lastStdout,
|
|
131904
132363
|
stderr: lastStderr,
|
|
131905
132364
|
resultFile: normalized,
|
|
132365
|
+
...logNormalized !== undefined ? { logFile: logNormalized } : {},
|
|
131906
132366
|
...timedOut ? { timedOut: true, timeoutMs } : {}
|
|
131907
132367
|
};
|
|
131908
132368
|
if (softFail) {
|
|
@@ -135927,6 +136387,18 @@ class WorkflowInspectOps {
|
|
|
135927
136387
|
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
136388
|
return { entries, total: rows.length };
|
|
135929
136389
|
}
|
|
136390
|
+
async resolveRunId(db, input) {
|
|
136391
|
+
const runDao = new RunDao(db);
|
|
136392
|
+
if (await runDao.traceRowById(input))
|
|
136393
|
+
return input;
|
|
136394
|
+
const rows = await runDao.traceRowsByIdPrefix(input);
|
|
136395
|
+
if (rows.length === 0)
|
|
136396
|
+
throw new Error(`Run not found: ${input}`);
|
|
136397
|
+
if (rows.length === 1)
|
|
136398
|
+
return rows[0]?.id ?? input;
|
|
136399
|
+
const candidates = rows.slice(0, 20).map((r) => r.id);
|
|
136400
|
+
throw new Error(`Ambiguous run id prefix: ${input}; candidates: ${candidates.join(", ")}`);
|
|
136401
|
+
}
|
|
135930
136402
|
async traceRun(db, runId, preloadedRow) {
|
|
135931
136403
|
const runDao = new RunDao(db);
|
|
135932
136404
|
const row = preloadedRow ?? await runDao.traceRowById(runId);
|
|
@@ -137207,10 +137679,18 @@ class WorkflowAppService {
|
|
|
137207
137679
|
const db = await this.ctx.getDb();
|
|
137208
137680
|
if (typeof runIdOrFilter === "string") {
|
|
137209
137681
|
const probe = await reapIfWorkerLost(db, this.ctx.cwd, runIdOrFilter);
|
|
137210
|
-
|
|
137682
|
+
if (probe.row !== undefined) {
|
|
137683
|
+
return this.inspectOps.traceRun(db, runIdOrFilter, probe.reaped ? undefined : probe.row);
|
|
137684
|
+
}
|
|
137685
|
+
const resolved = await this.inspectOps.resolveRunId(db, runIdOrFilter);
|
|
137686
|
+
const resolvedProbe = await reapIfWorkerLost(db, this.ctx.cwd, resolved);
|
|
137687
|
+
return this.inspectOps.traceRun(db, resolved, resolvedProbe.reaped ? undefined : resolvedProbe.row);
|
|
137211
137688
|
}
|
|
137212
137689
|
return this.inspectOps.traceList(db, runIdOrFilter ?? {});
|
|
137213
137690
|
}
|
|
137691
|
+
async resolveRunId(runId) {
|
|
137692
|
+
return this.inspectOps.resolveRunId(await this.ctx.getDb(), runId);
|
|
137693
|
+
}
|
|
137214
137694
|
inspectRunRecord(runId) {
|
|
137215
137695
|
return this.inspectOps.inspectRunRecord(runId);
|
|
137216
137696
|
}
|
|
@@ -146170,6 +146650,91 @@ function resolveReceiptReuse(receiptPath, wbs, capture) {
|
|
|
146170
146650
|
const receiptStatus = readReceiptStatus(receiptPath, capture.tree, wbs);
|
|
146171
146651
|
return { reuse: receiptStatus.reuse, receiptStatus };
|
|
146172
146652
|
}
|
|
146653
|
+
function workspaceGraphFrom(workspaces) {
|
|
146654
|
+
const byName = new Map;
|
|
146655
|
+
for (const workspace of workspaces) {
|
|
146656
|
+
if (workspace.name.length > 0)
|
|
146657
|
+
byName.set(workspace.name, workspace.dir);
|
|
146658
|
+
}
|
|
146659
|
+
const dependents = new Map;
|
|
146660
|
+
for (const workspace of workspaces) {
|
|
146661
|
+
for (const dependency of new Set(workspace.dependencies)) {
|
|
146662
|
+
if (dependency === workspace.name || !byName.has(dependency))
|
|
146663
|
+
continue;
|
|
146664
|
+
const dirs = dependents.get(dependency) ?? [];
|
|
146665
|
+
if (!dirs.includes(workspace.dir))
|
|
146666
|
+
dirs.push(workspace.dir);
|
|
146667
|
+
dependents.set(dependency, dirs);
|
|
146668
|
+
}
|
|
146669
|
+
}
|
|
146670
|
+
return { byName, dependents };
|
|
146671
|
+
}
|
|
146672
|
+
function dependentsOf(graph, dir) {
|
|
146673
|
+
for (const [name, owner] of graph.byName) {
|
|
146674
|
+
if (owner !== dir)
|
|
146675
|
+
continue;
|
|
146676
|
+
return graph.dependents.get(name) ?? [];
|
|
146677
|
+
}
|
|
146678
|
+
return [];
|
|
146679
|
+
}
|
|
146680
|
+
function workspaceDependenciesOf(abs, dir) {
|
|
146681
|
+
let name = "";
|
|
146682
|
+
const dependencies = [];
|
|
146683
|
+
try {
|
|
146684
|
+
const pkg = JSON.parse(readFileSync21(abs(`${dir}/package.json`), "utf8"));
|
|
146685
|
+
if (typeof pkg.name === "string")
|
|
146686
|
+
name = pkg.name;
|
|
146687
|
+
for (const section of [pkg.dependencies, pkg.devDependencies, pkg.peerDependencies]) {
|
|
146688
|
+
if (section !== null && typeof section === "object")
|
|
146689
|
+
dependencies.push(...Object.keys(section));
|
|
146690
|
+
}
|
|
146691
|
+
} catch {}
|
|
146692
|
+
return { name, dependencies };
|
|
146693
|
+
}
|
|
146694
|
+
function readWorkspaceGraph(cwd) {
|
|
146695
|
+
const abs = (p) => cwd ? join78(cwd, p) : p;
|
|
146696
|
+
const info = [];
|
|
146697
|
+
let globs = [];
|
|
146698
|
+
try {
|
|
146699
|
+
const root = JSON.parse(readFileSync21(abs("package.json"), "utf8"));
|
|
146700
|
+
const declared = Array.isArray(root.workspaces) ? root.workspaces : root.workspaces?.packages;
|
|
146701
|
+
if (Array.isArray(declared))
|
|
146702
|
+
globs = declared.filter((g) => typeof g === "string");
|
|
146703
|
+
} catch {
|
|
146704
|
+
return { byName: new Map, dependents: new Map };
|
|
146705
|
+
}
|
|
146706
|
+
const unsupported = globs.find((glob) => glob.includes("*") && (!glob.endsWith("/*") || glob === "/*"));
|
|
146707
|
+
if (unsupported !== undefined) {
|
|
146708
|
+
return {
|
|
146709
|
+
byName: new Map,
|
|
146710
|
+
dependents: new Map,
|
|
146711
|
+
unsupported: `workspaces glob ${JSON.stringify(unsupported)} is not a supported shape (only \`dir/*\` globs and literal dirs expand)`
|
|
146712
|
+
};
|
|
146713
|
+
}
|
|
146714
|
+
const dirs = new Set;
|
|
146715
|
+
for (const glob of globs) {
|
|
146716
|
+
if (glob.endsWith("/*")) {
|
|
146717
|
+
const parent = glob.slice(0, -2);
|
|
146718
|
+
let entries = [];
|
|
146719
|
+
try {
|
|
146720
|
+
entries = readdirSync11(abs(parent));
|
|
146721
|
+
} catch {
|
|
146722
|
+
continue;
|
|
146723
|
+
}
|
|
146724
|
+
for (const entry of entries) {
|
|
146725
|
+
if (existsSync23(abs(`${parent}/${entry}/package.json`)))
|
|
146726
|
+
dirs.add(`${parent}/${entry}`);
|
|
146727
|
+
}
|
|
146728
|
+
} else if (existsSync23(abs(`${glob}/package.json`))) {
|
|
146729
|
+
dirs.add(glob);
|
|
146730
|
+
}
|
|
146731
|
+
}
|
|
146732
|
+
for (const dir of [...dirs].sort()) {
|
|
146733
|
+
const { name, dependencies } = workspaceDependenciesOf(abs, dir);
|
|
146734
|
+
info.push({ dir, name, dependencies });
|
|
146735
|
+
}
|
|
146736
|
+
return workspaceGraphFrom(info);
|
|
146737
|
+
}
|
|
146173
146738
|
function workspaceOf(file, exists) {
|
|
146174
146739
|
const segments = file.split("/");
|
|
146175
146740
|
for (let depth = 1;depth < segments.length; depth++) {
|
|
@@ -146179,7 +146744,7 @@ function workspaceOf(file, exists) {
|
|
|
146179
146744
|
}
|
|
146180
146745
|
return null;
|
|
146181
146746
|
}
|
|
146182
|
-
function lightScope(changedFiles, exists = existsSync23) {
|
|
146747
|
+
function lightScope(changedFiles, exists = existsSync23, graph) {
|
|
146183
146748
|
const files = [];
|
|
146184
146749
|
const workspaces = new Set;
|
|
146185
146750
|
const tests = new Set;
|
|
@@ -146229,7 +146794,32 @@ function lightScope(changedFiles, exists = existsSync23) {
|
|
|
146229
146794
|
tests.add(file);
|
|
146230
146795
|
}
|
|
146231
146796
|
}
|
|
146232
|
-
|
|
146797
|
+
let fallback;
|
|
146798
|
+
if (graph?.unsupported !== undefined) {
|
|
146799
|
+
fallback = `${graph.unsupported} \u2014 full suite required`;
|
|
146800
|
+
}
|
|
146801
|
+
if (fallback === undefined && graph !== undefined) {
|
|
146802
|
+
for (const file of changedFiles) {
|
|
146803
|
+
if (file.startsWith("plugins/sp/"))
|
|
146804
|
+
continue;
|
|
146805
|
+
const owner = workspaceOf(file, exists);
|
|
146806
|
+
if (owner === null) {
|
|
146807
|
+
fallback = `unmapped path ${file} (no owning workspace package.json) \u2014 full suite required`;
|
|
146808
|
+
break;
|
|
146809
|
+
}
|
|
146810
|
+
const dependents = dependentsOf(graph, owner);
|
|
146811
|
+
if (dependents.length > 0) {
|
|
146812
|
+
fallback = `shared package ${owner} (depended on by ${dependents.join(", ")}) \u2014 full suite required`;
|
|
146813
|
+
break;
|
|
146814
|
+
}
|
|
146815
|
+
}
|
|
146816
|
+
}
|
|
146817
|
+
return {
|
|
146818
|
+
files,
|
|
146819
|
+
workspaces: [...workspaces].sort(),
|
|
146820
|
+
tests: [...tests].sort(),
|
|
146821
|
+
...fallback !== undefined ? { fallback } : {}
|
|
146822
|
+
};
|
|
146233
146823
|
}
|
|
146234
146824
|
function workspaceHasTypecheck(workspace) {
|
|
146235
146825
|
try {
|
|
@@ -146272,10 +146862,21 @@ function planLightChecks(scope, hasTypecheck = workspaceHasTypecheck, builder) {
|
|
|
146272
146862
|
}
|
|
146273
146863
|
return plans;
|
|
146274
146864
|
}
|
|
146275
|
-
function
|
|
146865
|
+
function readBaseSha(path) {
|
|
146866
|
+
if (path === undefined)
|
|
146867
|
+
return null;
|
|
146868
|
+
try {
|
|
146869
|
+
const line = readFileSync21(path, "utf8").trim();
|
|
146870
|
+
return /^[0-9a-f]{7,64}$/i.test(line) ? line : null;
|
|
146871
|
+
} catch {
|
|
146872
|
+
return null;
|
|
146873
|
+
}
|
|
146874
|
+
}
|
|
146875
|
+
function gitChangedFiles(cwd, timeoutMs, baseShaPath) {
|
|
146276
146876
|
const abs = (p) => cwd ? join78(cwd, p) : p;
|
|
146277
146877
|
const changed = new Set;
|
|
146278
|
-
|
|
146878
|
+
const base = readBaseSha(baseShaPath) ?? "HEAD";
|
|
146879
|
+
for (const cmd of [`git diff --name-only ${base}`, "git ls-files --others --exclude-standard"]) {
|
|
146279
146880
|
const result = runShellCommand(cmd, cwd, undefined, timeoutMs);
|
|
146280
146881
|
if (result.timedOut)
|
|
146281
146882
|
return null;
|
|
@@ -146302,6 +146903,7 @@ function runDeferredGate(env, options = {}) {
|
|
|
146302
146903
|
writeFileSync14(abs(statusFile), `${status}
|
|
146303
146904
|
`);
|
|
146304
146905
|
const note = status === "DEFERRED" ? `deferred gate: light tier PASS \u2014 the integrated full gate is owed on the batch base ref (1111 R3)
|
|
146906
|
+
` : 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
146907
|
` : `deferred gate: light tier FAIL \u2014 the task's bounded fix lane runs; the integrated full gate stays owed (1111 R3)
|
|
146306
146908
|
`;
|
|
146307
146909
|
appendFileSync7(abs(light.logFile), note);
|
|
@@ -146318,7 +146920,7 @@ function runLightGate(env, options = {}) {
|
|
|
146318
146920
|
const receiptFile = join78(runDir, `${env.wbs}-check-receipt.json`);
|
|
146319
146921
|
writeFileSync14(abs(logFile), "");
|
|
146320
146922
|
const timeoutMs = gateTimeoutMsFor(env, options);
|
|
146321
|
-
const changed = gitChangedFiles(cwd, timeoutMs);
|
|
146923
|
+
const changed = gitChangedFiles(cwd, timeoutMs, abs(join78(runDir, `${env.wbs}-base.sha`)));
|
|
146322
146924
|
if (changed === null) {
|
|
146323
146925
|
const line = `--- light gate BLOCKED: changed-path git read timed out after ${timeoutMs}ms
|
|
146324
146926
|
`;
|
|
@@ -146339,7 +146941,29 @@ function runLightGate(env, options = {}) {
|
|
|
146339
146941
|
appendFileSync7(abs(logFile), `--- light receipt: no git tree identity \u2014 ${capture.error}
|
|
146340
146942
|
`);
|
|
146341
146943
|
}
|
|
146342
|
-
const scope = lightScope(changed, (p) => existsSync23(abs(p)));
|
|
146944
|
+
const scope = lightScope(changed, (p) => existsSync23(abs(p)), readWorkspaceGraph(cwd));
|
|
146945
|
+
const scopeHeader = scope.fallback !== undefined ? `--- light scope: full-suite-fallback \u2014 ${scope.fallback}
|
|
146946
|
+
--- light scope files (${changed.length}): ${changed.join(" ")}
|
|
146947
|
+
` : `--- light scope: selected \u2014 workspaces: ${scope.workspaces.join(" ") || "(none)"}; related tests: ${scope.tests.length}; files: ${changed.length}
|
|
146948
|
+
--- light scope files (${changed.length}): ${changed.join(" ")}
|
|
146949
|
+
`;
|
|
146950
|
+
appendFileSync7(abs(logFile), scopeHeader);
|
|
146951
|
+
if (scope.fallback !== undefined) {
|
|
146952
|
+
const line = `--- light gate BLOCKED: affected-scope fallback \u2014 full gate owns this tree
|
|
146953
|
+
`;
|
|
146954
|
+
appendFileSync7(abs(logFile), line);
|
|
146955
|
+
process.stdout.write(`light gate BLOCKED \u2014 ${scope.fallback}
|
|
146956
|
+
`);
|
|
146957
|
+
return {
|
|
146958
|
+
status: "FAIL",
|
|
146959
|
+
scope,
|
|
146960
|
+
checks: [],
|
|
146961
|
+
logFile,
|
|
146962
|
+
receiptFile,
|
|
146963
|
+
blocked: scope.fallback,
|
|
146964
|
+
fallback: scope.fallback
|
|
146965
|
+
};
|
|
146966
|
+
}
|
|
146343
146967
|
const plans = planLightChecks(scope, (workspace) => workspaceHasTypecheck(abs(workspace)), options.builder);
|
|
146344
146968
|
const reusable = new Map;
|
|
146345
146969
|
let preserveFullReceipt = false;
|
|
@@ -146401,6 +147025,11 @@ function runLightGate(env, options = {}) {
|
|
|
146401
147025
|
appendFileSync7(abs(logFile), `--- light receipt: not written \u2014 the full-tier receipt at the same git tree is preserved
|
|
146402
147026
|
`);
|
|
146403
147027
|
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})
|
|
147028
|
+
`);
|
|
147029
|
+
} else if (!capture.ok) {
|
|
147030
|
+
appendFileSync7(abs(logFile), `--- light receipt: not written \u2014 git tree capture failed (${capture.error})
|
|
147031
|
+
`);
|
|
147032
|
+
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
147033
|
`);
|
|
146405
147034
|
} else {
|
|
146406
147035
|
writeFileSync14(abs(receiptFile), `${JSON.stringify(receipt, null, 2)}
|
|
@@ -146709,6 +147338,37 @@ function runQualityGate(mode, env, options = {}) {
|
|
|
146709
147338
|
appendFileSync7(abs(logFile), line);
|
|
146710
147339
|
gateRc = 1;
|
|
146711
147340
|
}
|
|
147341
|
+
if (mode === "recheck" && commandPresent && gateRc === 0) {
|
|
147342
|
+
const light = runLightGate(env, options);
|
|
147343
|
+
appendFileSync7(abs(logFile), `--- gate visit ${mode}: affected-scope light tier (1322) \u2014 ${light.status}
|
|
147344
|
+
${readFileSync21(abs(light.logFile), "utf8")}`);
|
|
147345
|
+
if (light.fallback === undefined) {
|
|
147346
|
+
if (light.status === "FAIL") {
|
|
147347
|
+
writeFileSync14(abs(findingsFile), extractFindings(readFileSync21(abs(logFile), "utf8")));
|
|
147348
|
+
writeFileSync14(abs(statusFile), `FAIL
|
|
147349
|
+
`);
|
|
147350
|
+
appendFileSync7(abs(logFile), `git-tree: ${tree.ok ? tree.tree : "unbound"}
|
|
147351
|
+
`);
|
|
147352
|
+
process.stdout.write(`quality gate FAIL (light tier) \u2014 last 40 lines follow (full log: ${logFile})
|
|
147353
|
+
`);
|
|
147354
|
+
process.stdout.write(tailLines(readFileSync21(abs(logFile), "utf8"), 40));
|
|
147355
|
+
return {
|
|
147356
|
+
status: "FAIL",
|
|
147357
|
+
attempts: 0,
|
|
147358
|
+
logFile,
|
|
147359
|
+
findingsFile,
|
|
147360
|
+
statusFile,
|
|
147361
|
+
attemptFile,
|
|
147362
|
+
receiptFile: light.receiptFile
|
|
147363
|
+
};
|
|
147364
|
+
}
|
|
147365
|
+
} else {
|
|
147366
|
+
const line = `check.scope-fallback \u2014 ${light.fallback}; probe + full gate follow
|
|
147367
|
+
`;
|
|
147368
|
+
process.stdout.write(line);
|
|
147369
|
+
appendFileSync7(abs(logFile), line);
|
|
147370
|
+
}
|
|
147371
|
+
}
|
|
146712
147372
|
if (mode === "recheck" && gateRc === 0 && (env.gateProbeCmd ?? "").length > 0) {
|
|
146713
147373
|
const probe = runShellCommand(env.gateProbeCmd ?? "", cwd, undefined, timeoutMs);
|
|
146714
147374
|
writeFileSync14(abs(`${logFile}.probe`), probe.output);
|
|
@@ -147279,6 +147939,15 @@ function parseProvenance(reportMarkdown) {
|
|
|
147279
147939
|
schemaVersion: obj.schemaVersion == null ? undefined : Number(obj.schemaVersion),
|
|
147280
147940
|
executor: optionalString(obj.executor),
|
|
147281
147941
|
model: optionalString(obj.model),
|
|
147942
|
+
executorProfile: optionalString(obj.executorProfile),
|
|
147943
|
+
dispatches: Array.isArray(obj.dispatches) ? obj.dispatches.map((d) => ({
|
|
147944
|
+
node: String(d.node ?? ""),
|
|
147945
|
+
actionId: String(d.actionId ?? ""),
|
|
147946
|
+
agent: String(d.agent ?? ""),
|
|
147947
|
+
executor: String(d.executor ?? ""),
|
|
147948
|
+
model: String(d.model ?? ""),
|
|
147949
|
+
timeoutMs: d.timeoutMs === "unbounded" ? "unbounded" : Number(d.timeoutMs ?? 0)
|
|
147950
|
+
})) : undefined,
|
|
147282
147951
|
cacheDisposition: DISPOSITIONS.includes(obj.cacheDisposition) ? obj.cacheDisposition : undefined
|
|
147283
147952
|
};
|
|
147284
147953
|
} catch {
|
|
@@ -147322,6 +147991,88 @@ function decideCache(cached, current, opts) {
|
|
|
147322
147991
|
reasons.push("window-closed");
|
|
147323
147992
|
return { disposition: reasons.length === 0 ? "hit" : "miss", reasons };
|
|
147324
147993
|
}
|
|
147994
|
+
function evaluateCalcAssertion(expr) {
|
|
147995
|
+
const text = expr.trim().replace(/^`?calc`?\s*:\s*/i, "").trim();
|
|
147996
|
+
const cmp = text.match(new RegExp(`^(${NUM})\\s*(<=|>=|<|>)\\s*(${NUM})$`));
|
|
147997
|
+
if (cmp !== null) {
|
|
147998
|
+
const left = Number(cmp[1]);
|
|
147999
|
+
const right = Number(cmp[3]);
|
|
148000
|
+
switch (cmp[2]) {
|
|
148001
|
+
case "<":
|
|
148002
|
+
return left < right;
|
|
148003
|
+
case "<=":
|
|
148004
|
+
return left <= right;
|
|
148005
|
+
case ">":
|
|
148006
|
+
return left > right;
|
|
148007
|
+
default:
|
|
148008
|
+
return left >= right;
|
|
148009
|
+
}
|
|
148010
|
+
}
|
|
148011
|
+
const eq = text.match(new RegExp(`^(${NUM}(?:\\s*[-+*/]\\s*${NUM})+)\\s*=\\s*(${NUM})$`));
|
|
148012
|
+
if (eq === null)
|
|
148013
|
+
return;
|
|
148014
|
+
const tokens = (eq[1] ?? "").split(/\s*([-+*/])\s*/);
|
|
148015
|
+
let acc = Number(tokens[0]);
|
|
148016
|
+
let exact = Number.isInteger(acc);
|
|
148017
|
+
let divided = false;
|
|
148018
|
+
for (let i = 1;i < tokens.length; i += 2) {
|
|
148019
|
+
const op = tokens[i];
|
|
148020
|
+
const operand = Number(tokens[i + 1]);
|
|
148021
|
+
if (op === "+")
|
|
148022
|
+
acc += operand;
|
|
148023
|
+
else if (op === "-")
|
|
148024
|
+
acc -= operand;
|
|
148025
|
+
else if (op === "*")
|
|
148026
|
+
acc *= operand;
|
|
148027
|
+
else {
|
|
148028
|
+
if (operand === 0)
|
|
148029
|
+
return false;
|
|
148030
|
+
acc /= operand;
|
|
148031
|
+
divided = true;
|
|
148032
|
+
}
|
|
148033
|
+
exact = exact && Number.isInteger(operand);
|
|
148034
|
+
}
|
|
148035
|
+
const expected = Number(eq[2]);
|
|
148036
|
+
if (!divided && exact && Number.isInteger(expected))
|
|
148037
|
+
return acc === expected;
|
|
148038
|
+
const scale = Math.max(Math.abs(acc), Math.abs(expected), 1);
|
|
148039
|
+
return Math.abs(acc - expected) <= 0.000000001 * scale;
|
|
148040
|
+
}
|
|
148041
|
+
function auditArithmetic(findingsBody, problems) {
|
|
148042
|
+
const blockList = findingsBody.split(/^###\s+/m).slice(1);
|
|
148043
|
+
for (const block of blockList) {
|
|
148044
|
+
const lines = block.split(`
|
|
148045
|
+
`);
|
|
148046
|
+
const calcLines = lines.filter((l) => CALC_LINE.test(l));
|
|
148047
|
+
for (const line of calcLines) {
|
|
148048
|
+
const expr = line.slice(line.search(/calc/i));
|
|
148049
|
+
const verdict = evaluateCalcAssertion(expr);
|
|
148050
|
+
const shown = expr.replace(/^`?calc`?\s*:\s*/i, "").trim();
|
|
148051
|
+
if (verdict === undefined)
|
|
148052
|
+
problems.push(`arithmetic-invalid-assertion:${shown}`);
|
|
148053
|
+
else if (!verdict)
|
|
148054
|
+
problems.push(`arithmetic-contradiction:${shown}`);
|
|
148055
|
+
}
|
|
148056
|
+
const fieldNames = ["observation", "inference"];
|
|
148057
|
+
for (const field of fieldNames) {
|
|
148058
|
+
const start = lines.findIndex((l) => new RegExp(`^[-*\\s]*\\*{0,2}\`?${field}\`?\\*{0,2}\\s*:`).test(l));
|
|
148059
|
+
if (start === -1)
|
|
148060
|
+
continue;
|
|
148061
|
+
let end = lines.length;
|
|
148062
|
+
for (let i = start + 1;i < lines.length; i++) {
|
|
148063
|
+
if (/^[-*]\s*`?[a-zA-Z]+`?\s*:/.test(lines[i] ?? "")) {
|
|
148064
|
+
end = i;
|
|
148065
|
+
break;
|
|
148066
|
+
}
|
|
148067
|
+
}
|
|
148068
|
+
const span = lines.slice(start, end).join(`
|
|
148069
|
+
`);
|
|
148070
|
+
if (DERIVED_SIGNAL.test(span) && !CALC_LINE.test(span)) {
|
|
148071
|
+
problems.push(`arithmetic-evidence-missing:${field}`);
|
|
148072
|
+
}
|
|
148073
|
+
}
|
|
148074
|
+
}
|
|
148075
|
+
}
|
|
147325
148076
|
function escapeRe(s) {
|
|
147326
148077
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
147327
148078
|
}
|
|
@@ -147379,6 +148130,7 @@ function checkReportStructure(reportMarkdown) {
|
|
|
147379
148130
|
problems.push(`finding-invalid-key-category:${firstSegment}`);
|
|
147380
148131
|
}
|
|
147381
148132
|
}
|
|
148133
|
+
auditArithmetic(findingsBody, problems);
|
|
147382
148134
|
}
|
|
147383
148135
|
const ledgerIdx = reportMarkdown.search(/^#{2,3}\s+Evidence\s+ledger/im);
|
|
147384
148136
|
if (ledgerIdx !== -1) {
|
|
@@ -147561,6 +148313,66 @@ function validateSelector(opts) {
|
|
|
147561
148313
|
until: val(opts.until)
|
|
147562
148314
|
};
|
|
147563
148315
|
}
|
|
148316
|
+
function collectAgentRuns(node, out) {
|
|
148317
|
+
if (Array.isArray(node)) {
|
|
148318
|
+
for (const item of node)
|
|
148319
|
+
collectAgentRuns(item, out);
|
|
148320
|
+
return;
|
|
148321
|
+
}
|
|
148322
|
+
if (node === null || typeof node !== "object")
|
|
148323
|
+
return;
|
|
148324
|
+
const obj = node;
|
|
148325
|
+
if (obj.actionKind === "agent.run" && obj.ok === true)
|
|
148326
|
+
out.push(obj);
|
|
148327
|
+
for (const value of Object.values(obj))
|
|
148328
|
+
collectAgentRuns(value, out);
|
|
148329
|
+
}
|
|
148330
|
+
function applyFinalProvenance(p, input) {
|
|
148331
|
+
const lines = readFileSync25(input.validationPath, "utf8").trimEnd().split(`
|
|
148332
|
+
`);
|
|
148333
|
+
if ((lines[lines.length - 1] ?? "").trim() !== "Verdict: PASS") {
|
|
148334
|
+
throw new Error(`final validation artifact does not end with "Verdict: PASS": ${input.validationPath}`);
|
|
148335
|
+
}
|
|
148336
|
+
const validatedAt = statSync17(input.validationPath).mtime.toISOString();
|
|
148337
|
+
let trace;
|
|
148338
|
+
try {
|
|
148339
|
+
trace = JSON.parse(readFileSync25(input.tracePath, "utf8"));
|
|
148340
|
+
} catch (err) {
|
|
148341
|
+
throw new Error(`could not parse run trace at ${input.tracePath}: ${err.message}`);
|
|
148342
|
+
}
|
|
148343
|
+
const collected = [];
|
|
148344
|
+
collectAgentRuns(trace, collected);
|
|
148345
|
+
const seen = new Set;
|
|
148346
|
+
const dispatches = [];
|
|
148347
|
+
for (const action of collected) {
|
|
148348
|
+
const actionId = action.actionId ?? "";
|
|
148349
|
+
if (actionId !== "" && seen.has(actionId))
|
|
148350
|
+
continue;
|
|
148351
|
+
if (actionId !== "")
|
|
148352
|
+
seen.add(actionId);
|
|
148353
|
+
const inv = action.invocation ?? {};
|
|
148354
|
+
dispatches.push({
|
|
148355
|
+
node: action.node ?? "",
|
|
148356
|
+
actionId,
|
|
148357
|
+
agent: inv.agent != null && inv.agent !== "" ? inv.agent : "not available: invocation agent absent",
|
|
148358
|
+
executor: inv.executor != null && inv.executor !== "" ? inv.executor : "not available: invocation executor absent",
|
|
148359
|
+
model: inv.model != null && inv.model !== "" ? inv.model : "not available: invocation model absent",
|
|
148360
|
+
timeoutMs: typeof inv.timeoutMs === "number" && inv.timeoutMs > 0 ? inv.timeoutMs : "unbounded"
|
|
148361
|
+
});
|
|
148362
|
+
}
|
|
148363
|
+
if (dispatches.length === 0) {
|
|
148364
|
+
throw new Error(`run trace records no successful agent.run action: ${input.tracePath}`);
|
|
148365
|
+
}
|
|
148366
|
+
const finalValidation = [...dispatches].reverse().find((d) => d.node === "validate") ?? dispatches[dispatches.length - 1];
|
|
148367
|
+
return {
|
|
148368
|
+
...p,
|
|
148369
|
+
validatedAt,
|
|
148370
|
+
dispatches,
|
|
148371
|
+
executor: finalValidation?.agent ?? NOT_AVAILABLE,
|
|
148372
|
+
model: finalValidation?.model ?? NOT_AVAILABLE,
|
|
148373
|
+
executorProfile: finalValidation?.executor ?? NOT_AVAILABLE
|
|
148374
|
+
};
|
|
148375
|
+
}
|
|
147564
148376
|
function buildProvenance(opts) {
|
|
147565
148377
|
let raw;
|
|
147566
148378
|
try {
|
|
@@ -147608,10 +148420,10 @@ function buildProvenance(opts) {
|
|
|
147608
148420
|
runId: opts.runId,
|
|
147609
148421
|
currentArtifactPath: opts.artifact,
|
|
147610
148422
|
baselineArtifactPath: opts.baseline ?? null,
|
|
147611
|
-
spurVersion: opts.spurVersion
|
|
148423
|
+
spurVersion: opts.spurVersion !== undefined && opts.spurVersion !== "" ? opts.spurVersion : typeof raw.spurVersion === "string" && raw.spurVersion !== "" ? raw.spurVersion : NOT_AVAILABLE,
|
|
147612
148424
|
schemaVersion: typeof raw.schemaVersion === "number" ? raw.schemaVersion : undefined,
|
|
147613
|
-
executor: opts.executor ??
|
|
147614
|
-
model: opts.model ??
|
|
148425
|
+
executor: opts.executor ?? "not available: probe did not receive an executor",
|
|
148426
|
+
model: opts.model ?? "not available: probe did not receive a model"
|
|
147615
148427
|
};
|
|
147616
148428
|
}
|
|
147617
148429
|
function probe(opts) {
|
|
@@ -147656,6 +148468,12 @@ function renderProvenanceFrontmatter(p) {
|
|
|
147656
148468
|
for (const c of p.coverage) {
|
|
147657
148469
|
lines.push(` - source: ${c.source}, status: ${c.status}, lastImportedAt: ${c.lastImportedAt ?? "null"}`);
|
|
147658
148470
|
}
|
|
148471
|
+
if (p.dispatches !== undefined && p.dispatches.length > 0) {
|
|
148472
|
+
lines.push("dispatches:");
|
|
148473
|
+
for (const d of p.dispatches) {
|
|
148474
|
+
lines.push(` - node: ${d.node}, actionId: ${d.actionId}, agent: ${d.agent}, executor: ${d.executor}, model: ${d.model}, timeoutMs: ${d.timeoutMs}`);
|
|
148475
|
+
}
|
|
148476
|
+
}
|
|
147659
148477
|
lines.push("---");
|
|
147660
148478
|
return lines.join(`
|
|
147661
148479
|
`);
|
|
@@ -147707,7 +148525,7 @@ function diffPorcelain(before, now, expects) {
|
|
|
147707
148525
|
const beforePaths = porcelainPaths(before);
|
|
147708
148526
|
return [...porcelainPaths(now)].filter((p) => !beforePaths.has(p) && !expects.has(p)).sort();
|
|
147709
148527
|
}
|
|
147710
|
-
var REPORT_SECTIONS, FINDING_FIELDS, DISPOSITIONS, FINDING_CATEGORIES, NOT_AVAILABLE = "not available", YAML_KEYS;
|
|
148528
|
+
var REPORT_SECTIONS, FINDING_FIELDS, DISPOSITIONS, DERIVED_SIGNAL, NUM = "-?\\d+(?:\\.\\d+)?", CALC_LINE, FINDING_CATEGORIES, NOT_AVAILABLE = "not available", YAML_KEYS;
|
|
147711
148529
|
var init_history_anatomy = __esm(() => {
|
|
147712
148530
|
init_artifact_digest();
|
|
147713
148531
|
REPORT_SECTIONS = [
|
|
@@ -147738,6 +148556,8 @@ var init_history_anatomy = __esm(() => {
|
|
|
147738
148556
|
"ownerSurface"
|
|
147739
148557
|
];
|
|
147740
148558
|
DISPOSITIONS = ["hit", "miss", "forced-recompute"];
|
|
148559
|
+
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;
|
|
148560
|
+
CALC_LINE = /`?calc`?\s*:\s*\S/i;
|
|
147741
148561
|
FINDING_CATEGORIES = [
|
|
147742
148562
|
"reliability",
|
|
147743
148563
|
"repetition",
|
|
@@ -147763,6 +148583,7 @@ var init_history_anatomy = __esm(() => {
|
|
|
147763
148583
|
"spurVersion",
|
|
147764
148584
|
"schemaVersion",
|
|
147765
148585
|
"executor",
|
|
148586
|
+
"executorProfile",
|
|
147766
148587
|
"model",
|
|
147767
148588
|
"cacheDisposition"
|
|
147768
148589
|
];
|
|
@@ -147976,16 +148797,19 @@ ${reasons}`, stderr: "" };
|
|
|
147976
148797
|
return {
|
|
147977
148798
|
exitCode: 1,
|
|
147978
148799
|
stdout: "",
|
|
147979
|
-
stderr: `usage: spur history anatomy-stamp --candidate <c.md> --provenance <p.json> --out <o.md>
|
|
148800
|
+
stderr: `usage: spur history anatomy-stamp --candidate <c.md> --provenance <p.json> --out <o.md> [--validation <v.txt> --trace <t.json>]
|
|
147980
148801
|
`
|
|
147981
148802
|
};
|
|
147982
148803
|
}
|
|
147983
148804
|
try {
|
|
147984
|
-
|
|
148805
|
+
let p = JSON.parse(readFileSync26(f.provenance, "utf8"));
|
|
148806
|
+
if (f.validation !== undefined && f.trace !== undefined) {
|
|
148807
|
+
p = applyFinalProvenance(p, { validationPath: f.validation, tracePath: f.trace });
|
|
148808
|
+
}
|
|
147985
148809
|
writeFileSync18(f.out, `${stampReport(readFileSync26(f.candidate, "utf8"), p)}
|
|
147986
148810
|
`);
|
|
147987
|
-
} catch {
|
|
147988
|
-
return { exitCode: 1, stdout: "", stderr: `stamp:
|
|
148811
|
+
} catch (err) {
|
|
148812
|
+
return { exitCode: 1, stdout: "", stderr: `stamp: ${err.message}
|
|
147989
148813
|
` };
|
|
147990
148814
|
}
|
|
147991
148815
|
return { exitCode: 0, stdout: "", stderr: "" };
|
|
@@ -156992,6 +157816,7 @@ var SHARED_OPTIONS = {
|
|
|
156992
157816
|
],
|
|
156993
157817
|
jsonSupported: ["--json", "Output machine-readable JSON where supported"],
|
|
156994
157818
|
section: ["--section <name>", "Section name to write"],
|
|
157819
|
+
sectionShowFilter: ["--section <name>", "Show only this section, byte-exact (repeatable)"],
|
|
156995
157820
|
fromFile: ["--from-file <path>", "File to read section body from (requires --section)"],
|
|
156996
157821
|
statusFilter: ["--status <s>", "Filter by status"],
|
|
156997
157822
|
last: ["--last <n>", "Limit results (default 20)"],
|
|
@@ -158817,11 +159642,13 @@ function registerTaskBatchCreateCommand(task, context) {
|
|
|
158817
159642
|
if (options.json) {
|
|
158818
159643
|
const ids = children.map((r) => r.ref.id);
|
|
158819
159644
|
const normalized = children.map((r, i) => ({ wbs: ids[i] ?? r.ref.id, normalized: r.normalized ?? [] })).filter((row) => row.normalized.length > 0);
|
|
159645
|
+
const warnings = children.map((r, i) => ({ wbs: ids[i] ?? r.ref.id, warnings: r.warnings ?? [] })).filter((row) => row.warnings.length > 0);
|
|
158820
159646
|
context.output.write(toEnvelopeJson({
|
|
158821
159647
|
created: children.length,
|
|
158822
159648
|
wbs: ids,
|
|
158823
159649
|
parentsWired,
|
|
158824
159650
|
...normalized.length > 0 ? { normalized } : {},
|
|
159651
|
+
...warnings.length > 0 ? { warnings } : {},
|
|
158825
159652
|
...featureReopened.some((r) => r !== undefined) ? { featureReopened } : {},
|
|
158826
159653
|
...readiness
|
|
158827
159654
|
}, { enveloped: options.jsonEnvelope }));
|
|
@@ -158830,6 +159657,11 @@ function registerTaskBatchCreateCommand(task, context) {
|
|
|
158830
159657
|
for (const r of children) {
|
|
158831
159658
|
context.output.write(` ${r.ref.id} ${r.ref.filePath}`);
|
|
158832
159659
|
}
|
|
159660
|
+
for (const r of children) {
|
|
159661
|
+
for (const warning of r.warnings ?? []) {
|
|
159662
|
+
context.output.error(`${r.ref.id}: ${warning}`);
|
|
159663
|
+
}
|
|
159664
|
+
}
|
|
158833
159665
|
if (parentsWired.length > 0) {
|
|
158834
159666
|
context.output.write(`Wired ${parentsWired.length} parent(s):`);
|
|
158835
159667
|
for (const p of parentsWired) {
|
|
@@ -159097,7 +159929,7 @@ ${result.wbs} (${result.status}): ${result.pass ? "PASS" : "FAIL"}`);
|
|
|
159097
159929
|
init_src4();
|
|
159098
159930
|
init_src2();
|
|
159099
159931
|
function registerTaskCreateCommand(task, context) {
|
|
159100
|
-
task.command("create").summary("Create a new task with race-safe WBS allocation.").argument("<title>", "Task title").option(...SHARED_OPTIONS.featureTrace).option("--parent <wbs>", "Parent WBS for sub-task grouping").option("--template <variant>", `Template variant (${TASK_VARIANTS.join("|")})`).option("--no-reopen", "Do not reopen a 'verifying' parent feature to 'active'").option(...SHARED_OPTIONS.folderTasks).option("--dedupe-within <seconds>", "Override the default dedup window (seconds). Guard is on (300s) by default when --feature is set.", Number).option("--allow-duplicate-name", "Disable the dedup guard entirely (creates anyway)").option("--skip-ready", "Skip ready preparation (backlog capture only, no model dispatch)").option("--agent <selector>", "Agent used for ready preparation (defaults to the configured agent)").option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (title, options) => {
|
|
159932
|
+
task.command("create").summary("Create a new task with race-safe WBS allocation.").argument("<title>", "Task title").option(...SHARED_OPTIONS.featureTrace).option("--parent <wbs>", "Parent WBS for sub-task grouping").option("--template <variant>", `Template variant (${TASK_VARIANTS.join("|")})`).option("--requirements <text>", "Pre-filled Requirements body (normalized like batch items)").option("--plan <text>", "Pre-filled Plan body (ordered checklist)").option("--no-reopen", "Do not reopen a 'verifying' parent feature to 'active'").option(...SHARED_OPTIONS.folderTasks).option("--dedupe-within <seconds>", "Override the default dedup window (seconds). Guard is on (300s) by default when --feature is set.", Number).option("--allow-duplicate-name", "Disable the dedup guard entirely (creates anyway)").option("--skip-ready", "Skip ready preparation (backlog capture only, no model dispatch)").option("--agent <selector>", "Agent used for ready preparation (defaults to the configured agent)").option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (title, options) => {
|
|
159101
159933
|
if (options.template !== undefined && !TASK_VARIANTS.includes(options.template)) {
|
|
159102
159934
|
writeCreateJsonError(context, options, "invalid-usage", `Unknown template variant "${options.template}". Valid: ${TASK_VARIANTS.join(", ")}`);
|
|
159103
159935
|
context.setExitCode(2);
|
|
@@ -159120,6 +159952,8 @@ function registerTaskCreateCommand(task, context) {
|
|
|
159120
159952
|
featureId: options.feature,
|
|
159121
159953
|
parentWbs: options.parent,
|
|
159122
159954
|
template: options.template,
|
|
159955
|
+
requirements: options.requirements,
|
|
159956
|
+
plan: options.plan,
|
|
159123
159957
|
noReopen: options.reopen === false,
|
|
159124
159958
|
dedupeWithinSec: options.allowDuplicateName ? null : options.dedupeWithin
|
|
159125
159959
|
});
|
|
@@ -159135,10 +159969,20 @@ function registerTaskCreateCommand(task, context) {
|
|
|
159135
159969
|
cwd: context.cwd
|
|
159136
159970
|
});
|
|
159137
159971
|
}
|
|
159972
|
+
const warnings = result.warnings ?? [];
|
|
159138
159973
|
if (options.json) {
|
|
159139
|
-
context.output.write(toEnvelopeJson({
|
|
159974
|
+
context.output.write(toEnvelopeJson({
|
|
159975
|
+
...result,
|
|
159976
|
+
wbs: result.ref.id,
|
|
159977
|
+
filePath: result.ref.filePath,
|
|
159978
|
+
...readiness,
|
|
159979
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
159980
|
+
}, { enveloped: options.jsonEnvelope }));
|
|
159140
159981
|
} else {
|
|
159141
159982
|
context.output.write(`Created task ${result.ref.id}: ${result.ref.filePath}`);
|
|
159983
|
+
for (const warning of warnings) {
|
|
159984
|
+
context.output.error(warning);
|
|
159985
|
+
}
|
|
159142
159986
|
if (readiness.readiness.status === "ready") {
|
|
159143
159987
|
context.output.write(`Ready: checklist + post-check passed (${result.ref.id} promoted to todo)`);
|
|
159144
159988
|
}
|
|
@@ -159932,14 +160776,42 @@ function registerTaskSectionsCommand(task, context) {
|
|
|
159932
160776
|
|
|
159933
160777
|
// src/commands/task/show.ts
|
|
159934
160778
|
init_src2();
|
|
160779
|
+
function collectSection(value, previous = []) {
|
|
160780
|
+
return [...previous, value];
|
|
160781
|
+
}
|
|
160782
|
+
function parseCsvSections(csv) {
|
|
160783
|
+
if (csv === undefined)
|
|
160784
|
+
return [];
|
|
160785
|
+
return csv.split(",").map((name) => name.trim()).filter((name) => name !== "");
|
|
160786
|
+
}
|
|
159935
160787
|
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) => {
|
|
160788
|
+
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) => {
|
|
160789
|
+
const requested = [...options.section ?? [], ...parseCsvSections(options.sections)];
|
|
160790
|
+
if (options.sections !== undefined && parseCsvSections(options.sections).length === 0) {
|
|
160791
|
+
writeJsonError(context.output, options, `--sections "${options.sections}" carries no usable section names`, "VALIDATION_FAILED");
|
|
160792
|
+
context.setExitCode(2);
|
|
160793
|
+
return;
|
|
160794
|
+
}
|
|
160795
|
+
if (options.summary === true && requested.length > 0) {
|
|
160796
|
+
writeJsonError(context.output, options, "--summary cannot be combined with --section/--sections", "VALIDATION_FAILED");
|
|
160797
|
+
context.setExitCode(2);
|
|
160798
|
+
return;
|
|
160799
|
+
}
|
|
159937
160800
|
const svc = await makeService(context, options.folder);
|
|
159938
160801
|
try {
|
|
159939
|
-
const result = await svc.show(wbs);
|
|
160802
|
+
const result = options.summary === true ? await svc.showSummary(wbs) : requested.length > 0 ? await svc.showSections(wbs, requested) : await svc.show(wbs);
|
|
159940
160803
|
if (options.json) {
|
|
159941
160804
|
const { frontmatter, ...rest } = result;
|
|
159942
160805
|
context.output.write(toEnvelopeJson({ ...rest, frontmatter }, { enveloped: options.jsonEnvelope }));
|
|
160806
|
+
} else if ("sections" in result) {
|
|
160807
|
+
const header = `${taskStatusIcon(result.status)} ${result.status.toUpperCase()} \u2014 ${result.wbs}`;
|
|
160808
|
+
if (result.sections.every((entry) => entry.content === undefined)) {
|
|
160809
|
+
const lines = result.sections.map((entry) => ` ${entry.headingLine} \u2014 ${entry.byteCount} bytes`);
|
|
160810
|
+
context.output.write([`${header} \u2014 summary`, ...lines, ` total: ${result.totalBytes} bytes`].join(`
|
|
160811
|
+
`));
|
|
160812
|
+
} else {
|
|
160813
|
+
context.output.write([header, ...result.sections.map((entry) => entry.content ?? "")].join(""));
|
|
160814
|
+
}
|
|
159943
160815
|
} else {
|
|
159944
160816
|
context.output.write(`${taskStatusIcon(result.status)} ${result.status.toUpperCase()} \u2014 ${result.wbs}
|
|
159945
160817
|
|
|
@@ -159948,7 +160820,7 @@ ${result.content}`);
|
|
|
159948
160820
|
} catch (err) {
|
|
159949
160821
|
const cliCode = err?.cliCode;
|
|
159950
160822
|
writeJsonError(context.output, options, String(err), "INTERNAL_ERROR", cliCode !== undefined ? { cliCode } : undefined);
|
|
159951
|
-
context.setExitCode(1);
|
|
160823
|
+
context.setExitCode(cliCode === "VALIDATION_FAILED" ? 3 : 1);
|
|
159952
160824
|
}
|
|
159953
160825
|
});
|
|
159954
160826
|
}
|
|
@@ -165193,7 +166065,7 @@ init_src2();
|
|
|
165193
166065
|
var CLI_CONFIG = {
|
|
165194
166066
|
binaryName: "spur",
|
|
165195
166067
|
binaryLabel: "spur",
|
|
165196
|
-
binaryVersion: "0.4.
|
|
166068
|
+
binaryVersion: "0.4.33",
|
|
165197
166069
|
configDir: ".spur",
|
|
165198
166070
|
configFile: ".spur/config.yaml",
|
|
165199
166071
|
databaseFile: ".spur/spur.db"
|
|
@@ -172932,7 +173804,7 @@ init_dist5();
|
|
|
172932
173804
|
import { homedir as homedir17 } from "os";
|
|
172933
173805
|
import { basename as basename23, join as join95, relative as relative19, resolve as resolve37 } from "path";
|
|
172934
173806
|
// ../server/package.json
|
|
172935
|
-
var version4 = "0.4.
|
|
173807
|
+
var version4 = "0.4.33";
|
|
172936
173808
|
|
|
172937
173809
|
// ../server/src/modules/health/index.ts
|
|
172938
173810
|
var startedAt = Date.now();
|
|
@@ -180049,13 +180921,23 @@ function registerWorkflowTraceCommand(workflow, context) {
|
|
|
180049
180921
|
context.setExitCode(1);
|
|
180050
180922
|
return;
|
|
180051
180923
|
}
|
|
180052
|
-
|
|
180053
|
-
|
|
180924
|
+
let resolvedRunId = runId;
|
|
180925
|
+
if (runId !== undefined) {
|
|
180926
|
+
try {
|
|
180927
|
+
resolvedRunId = await svc.resolveRunId(runId);
|
|
180928
|
+
} catch (err) {
|
|
180929
|
+
writeJsonError(context.output, options, err.message, "NOT_FOUND");
|
|
180930
|
+
context.setExitCode(1);
|
|
180931
|
+
return;
|
|
180932
|
+
}
|
|
180933
|
+
}
|
|
180934
|
+
if (options.follow === true && resolvedRunId !== undefined) {
|
|
180935
|
+
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
180936
|
if (timedOut)
|
|
180055
180937
|
context.setExitCode(1);
|
|
180056
180938
|
return;
|
|
180057
180939
|
}
|
|
180058
|
-
const result =
|
|
180940
|
+
const result = resolvedRunId ? await svc.trace(resolvedRunId) : await svc.trace({
|
|
180059
180941
|
workflow: options.workflow,
|
|
180060
180942
|
status: options.status,
|
|
180061
180943
|
since: options.since,
|