@gobing-ai/spur 0.3.73 → 0.3.74
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/config.example.yaml +15 -4
- package/config/pipeline-budgets.json +4 -4
- package/config/rules/strict/runtime-boundaries.yaml +1 -1
- package/config/templates/AGENTS.md +5 -0
- package/config/templates/docs/99_PROJECT_CONSTITUTION.md +2 -0
- package/config/workflows/basic.yaml +5 -2
- package/config/workflows/docs-pipeline.yaml +92 -55
- package/config/workflows/feature-dev.yaml +43 -20
- package/config/workflows/feature-lifecycle.yaml +1 -0
- package/config/workflows/history-anatomy.yaml +8 -4
- package/config/workflows/idea-pipeline.yaml +66 -33
- package/config/workflows/pr-review.yaml +31 -8
- package/config/workflows/task-lifecycle.yaml +1 -0
- package/config/workflows/task-pipeline.yaml +929 -905
- package/config/workflows/wayfinder-resolution.yaml +124 -90
- package/config/workflows/wrapup-pipeline.yaml +208 -83
- package/package.json +1 -1
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/scripts/history-anatomy-cache.mjs +7 -1
- package/plugins/sp/scripts/history-anatomy-cache.ts +11 -2
- package/plugins/sp/scripts/pr-reviewing.mjs +5 -1
- package/plugins/sp/scripts/pr-reviewing.ts +7 -1
- package/plugins/sp/scripts/transition-shim-check.ts +6 -6
- package/plugins/sp/skills/pr-reviewing/SKILL.md +4 -1
- package/plugins/sp/skills/spur-cli/references/self.md +17 -3
- package/plugins/sp/skills/spur-cli/references/tasks.md +1 -1
- package/plugins/sp/skills/spur-cli/references/workflows/workflow-fit-and-tuning.md +5 -5
- package/plugins/sp/skills/spur-cli/references/workflows.md +4 -4
- package/plugins/sp/skills/spur-dev/SKILL.md +17 -0
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +3 -3
- package/plugins/sp/skills/spur-dev/references/gate-checklists.md +1 -2
- package/plugins/sp/skills/wayfinder/SKILL.md +1 -1
- package/spur.js +1125 -784
- package/web/_astro/{BoardApp.BYCNkMOn.js → BoardApp.C60RJZRj.js} +78 -77
- package/web/_astro/BoardApp.ymfj1EHA.js +1 -0
- package/web/_astro/{TaskDetail.CgUreSP2.js → TaskDetail.Guk8VuNu.js} +1 -1
- package/web/_astro/{arc.BySSh34M.js → arc.CAZtlWJM.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.DM46TS_h.js → architectureDiagram-3BPJPVTR.9-XbF_Tz.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.tZhvNUHA.js → blockDiagram-GPEHLZMM.DC4WLx3L.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.PT4Or4Nf.js → c4Diagram-AAUBKEIU.JgjUQMgG.js} +1 -1
- package/web/_astro/channel.Bxxe2Byc.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.J9r0_Bbe.js → chunk-2J33WTMH.D_JFiXa-.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.hzyeIvhR.js → chunk-4BX2VUAB.Bk_RxoeT.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.B0rO7qVh.js → chunk-55IACEB6.a_Rj2VxL.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.wE_Uk5D4.js → chunk-727SXJPM.C2NoS1U6.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.DqEEjQw7.js → chunk-AQP2D5EJ.D1Xn6CNa.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.CDoD9sBX.js → chunk-FMBD7UC4.BhJrSBsX.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.CtX5nF9P.js → chunk-ND2GUHAM.ByVGduYQ.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.CK_EwfaT.js → chunk-QZHKN3VN.B47Paunq.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.DLt5a8Lh.js → classDiagram-4FO5ZUOK.BERMSD5C.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.DLt5a8Lh.js → classDiagram-v2-Q7XG4LA2.BERMSD5C.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.CMCWP49h.js → cose-bilkent-S5V4N54A.O_rHGlhU.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.HyXw_vdS.js → cynefin-OW5HDTMX.qCW2GdNp.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.BTuAzh01.js → dagre-BM42HDAG.C5Y4lH_u.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.D9dr9wfT.js → diagram-2AECGRRQ.DxZmRXxF.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.C4Rot0hj.js → diagram-5GNKFQAL.qSxZeYSK.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.B_TK5uWC.js → diagram-KO2AKTUF.-7vl3bXs.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.JkXKK7CO.js → diagram-LMA3HP47.BHVV-UH3.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.BzMN8Bd6.js → diagram-OG6HWLK6.DHAuW9sK.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.DVZaWGUd.js → erDiagram-TEJ5UH35.DMRLQwPN.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.rjEiWUfR.js → flowDiagram-I6XJVG4X.B3EpPp_8.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.C_EgAarK.js → ganttDiagram-6RSMTGT7.BgUrExXM.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.B-QQSDsK.js → gitGraphDiagram-PVQCEYII.CU-9yVN4.js} +1 -1
- package/web/_astro/{index.B5MTfe7k.css → index.BhvM3djt.css} +1 -1
- package/web/_astro/{infoDiagram-5YYISTIA.DlWesz7T.js → infoDiagram-5YYISTIA.CvVTCRLe.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.BUMZOawi.js → ishikawaDiagram-YF4QCWOH.BUzOfKPf.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.CWfkxfjY.js → journeyDiagram-JHISSGLW.BBTb7ziz.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.B-YpMwXf.js → kanban-definition-UN3LZRKU.CTQQr70i.js} +1 -1
- package/web/_astro/{linear.D7uqzENp.js → linear.CLKlJPiS.js} +1 -1
- package/web/_astro/{mermaid.core.CxrNppBD.js → mermaid.core.BA4wFhLP.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.B4Qe7cM2.js → mindmap-definition-RKZ34NQL.t1vG2l5e.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.Ds-5j2ro.js → pieDiagram-4H26LBE5.C8zJhyTu.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.tBd38uNC.js → quadrantDiagram-W4KKPZXB.DqYYvb7T.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.sFENkWl3.js → requirementDiagram-4Y6WPE33.lzfDZ9nI.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.BeB-Hk7C.js → sankeyDiagram-5OEKKPKP.lm6N5ORM.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.DnTeaSpx.js → sequenceDiagram-3UESZ5HK.E6_IV4Dj.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.B-8Jt5EJ.js → stateDiagram-AJRCARHV.Bnv6Ok2p.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.Br7xoqMW.js → stateDiagram-v2-BHNVJYJU.mJHH_Ng_.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.C-3WdOyi.js → timeline-definition-PNZ67QCA.BhRgqSLa.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.DCIs7Lc6.js → vennDiagram-CIIHVFJN.pjb5DMC9.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.rGAL-bbz.js → wardleyDiagram-YWT4CUSO.CW20KUng.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.hkfQKiRl.js → xychartDiagram-2RQKCTM6.vX9_yuMl.js} +1 -1
- package/web/index.html +2 -2
- package/config/corpus-baseline.json +0 -1805
- package/config/workflow-composition-baseline.json +0 -615
- package/web/_astro/BoardApp.E12MFjOS.js +0 -1
- package/web/_astro/channel.5cYKr5cs.js +0 -1
package/spur.js
CHANGED
|
@@ -50296,6 +50296,7 @@ var init_models = __esm(() => {
|
|
|
50296
50296
|
"gemini-3-flash-preview": { inputPricePer1M: 0.15, outputPricePer1M: 0.6 },
|
|
50297
50297
|
"gemini-3-flash": { inputPricePer1M: 0.15, outputPricePer1M: 0.6 },
|
|
50298
50298
|
"gemini-2.0-flash": { inputPricePer1M: 0.1, outputPricePer1M: 0.4 },
|
|
50299
|
+
"gpt-6-astra": { inputPricePer1M: 2.5, outputPricePer1M: 10 },
|
|
50299
50300
|
"gpt-4o": { inputPricePer1M: 2.5, outputPricePer1M: 10 },
|
|
50300
50301
|
"gpt-4o-mini": { inputPricePer1M: 0.15, outputPricePer1M: 0.6 },
|
|
50301
50302
|
"gpt-4-turbo": { inputPricePer1M: 10, outputPricePer1M: 30 },
|
|
@@ -50754,9 +50755,6 @@ var init_watermark = __esm(() => {
|
|
|
50754
50755
|
});
|
|
50755
50756
|
|
|
50756
50757
|
// ../../packages/domain/src/analytics/forensic-query.ts
|
|
50757
|
-
function withMessageDedup(where) {
|
|
50758
|
-
return where === "" ? `WHERE ${MESSAGE_DEDUP}` : `${where} AND ${MESSAGE_DEDUP}`;
|
|
50759
|
-
}
|
|
50760
50758
|
function escapeLike(value) {
|
|
50761
50759
|
return value.replaceAll("!", "!!").replaceAll("%", "!%").replaceAll("_", "!_");
|
|
50762
50760
|
}
|
|
@@ -50848,7 +50846,7 @@ async function messageRollup(db2, sel, opts) {
|
|
|
50848
50846
|
const { where, params } = buildMessageWhere(sel);
|
|
50849
50847
|
const wm = applyWatermarkToWhere(where, opts?.watermark);
|
|
50850
50848
|
return db2.queryAll(`WITH filtered AS (
|
|
50851
|
-
SELECT m.source, m.model, m.ts, m.input_tokens, m.output_tokens,
|
|
50849
|
+
SELECT m.source, m.session_id, m.model, m.ts, m.input_tokens, m.output_tokens,
|
|
50852
50850
|
m.cache_read_tokens, m.cache_write_tokens, m.cost_usd, m.role, m.duration_ms,
|
|
50853
50851
|
ROW_NUMBER() OVER (PARTITION BY m.request_id ORDER BY m.rowid DESC) AS rn,
|
|
50854
50852
|
m.request_id
|
|
@@ -50856,7 +50854,17 @@ async function messageRollup(db2, sel, opts) {
|
|
|
50856
50854
|
${wm.where}
|
|
50857
50855
|
),
|
|
50858
50856
|
selected AS (
|
|
50859
|
-
SELECT
|
|
50857
|
+
SELECT source,
|
|
50858
|
+
COALESCE(
|
|
50859
|
+
NULLIF(NULLIF(model, ''), 'unknown'),
|
|
50860
|
+
MAX(CASE WHEN model IS NOT NULL AND model != '' AND model != 'unknown' THEN model END)
|
|
50861
|
+
OVER (PARTITION BY source, session_id),
|
|
50862
|
+
NULLIF(model, ''),
|
|
50863
|
+
'unknown'
|
|
50864
|
+
) AS model,
|
|
50865
|
+
ts, input_tokens, output_tokens, cache_read_tokens, cache_write_tokens,
|
|
50866
|
+
cost_usd, role, duration_ms
|
|
50867
|
+
FROM filtered WHERE request_id IS NULL OR rn = 1
|
|
50860
50868
|
)
|
|
50861
50869
|
SELECT source, model, DATE(ts) AS day,
|
|
50862
50870
|
COUNT(*) AS messages,
|
|
@@ -50876,7 +50884,15 @@ async function toolRollup(db2, sel, opts) {
|
|
|
50876
50884
|
const { where, params } = buildMessageWhere(sel);
|
|
50877
50885
|
const wm = applyWatermarkToWhere(where, opts?.watermark);
|
|
50878
50886
|
return db2.queryAll(`WITH filtered_messages AS (
|
|
50879
|
-
SELECT m.record_hash, m.source, m.
|
|
50887
|
+
SELECT m.record_hash, m.source, m.session_id,
|
|
50888
|
+
COALESCE(
|
|
50889
|
+
NULLIF(NULLIF(m.model, ''), 'unknown'),
|
|
50890
|
+
MAX(CASE WHEN m.model IS NOT NULL AND m.model != '' AND m.model != 'unknown' THEN m.model END)
|
|
50891
|
+
OVER (PARTITION BY m.source, m.session_id),
|
|
50892
|
+
NULLIF(m.model, ''),
|
|
50893
|
+
'unknown'
|
|
50894
|
+
) AS model,
|
|
50895
|
+
m.ts
|
|
50880
50896
|
FROM history_message m
|
|
50881
50897
|
${wm.where}
|
|
50882
50898
|
)
|
|
@@ -51243,6 +51259,8 @@ async function loops(db2, sel, opts) {
|
|
|
51243
51259
|
ORDER BY repeats DESC`, ...params, ...wm.params);
|
|
51244
51260
|
}
|
|
51245
51261
|
async function loopRepeatedCallsQuery(db2, params) {
|
|
51262
|
+
const argsDigestSql = params.argsDigest !== null && params.argsDigest !== "" ? "tc.args_digest = ?" : "(tc.args_digest IS NULL OR tc.args_digest = '')";
|
|
51263
|
+
const argsDigestParam = params.argsDigest !== null && params.argsDigest !== "" ? [params.argsDigest] : [];
|
|
51246
51264
|
return db2.queryAll(`SELECT tc.seq AS toolSeq,
|
|
51247
51265
|
COALESCE(tc.started_at, m.ts) AS ts,
|
|
51248
51266
|
tc.tool_name AS toolName,
|
|
@@ -51256,17 +51274,17 @@ async function loopRepeatedCallsQuery(db2, params) {
|
|
|
51256
51274
|
tc.message_hash AS messageHash,
|
|
51257
51275
|
tc.session_id AS sessionId,
|
|
51258
51276
|
tc.source AS source,
|
|
51259
|
-
m.model AS model,
|
|
51260
|
-
|
|
51277
|
+
COALESCE(NULLIF(NULLIF(m.model, ''), 'unknown'), ?) AS model,
|
|
51278
|
+
1 AS links,
|
|
51261
51279
|
m.input_tokens AS inputTokens,
|
|
51262
51280
|
m.cache_read_tokens AS cacheReadTokens,
|
|
51263
51281
|
m.output_tokens AS outputTokens
|
|
51264
51282
|
FROM history_tool_call tc
|
|
51265
51283
|
JOIN history_message m ON m.record_hash = tc.message_hash
|
|
51266
51284
|
WHERE tc.source = ? AND tc.session_id = ? AND tc.tool_name = ?
|
|
51267
|
-
AND
|
|
51285
|
+
AND ${argsDigestSql}
|
|
51268
51286
|
ORDER BY tc.seq ASC
|
|
51269
|
-
LIMIT ?`, params.
|
|
51287
|
+
LIMIT ?`, params.model ?? null, params.source, params.sessionId, params.toolName, ...argsDigestParam, params.limit ?? 50);
|
|
51270
51288
|
}
|
|
51271
51289
|
async function drift(db2, sel, opts) {
|
|
51272
51290
|
const { where, params } = buildMessageWhere(sel);
|
|
@@ -51338,7 +51356,17 @@ async function topStepsByTokens(db2, sel, top, opts) {
|
|
|
51338
51356
|
const tokensOrder = rankOrderExpr(sel, "COALESCE(m.input_tokens, 0) + COALESCE(m.cache_read_tokens, 0)");
|
|
51339
51357
|
const tokensGuard = rankOrderExpr(sel, "(m.input_tokens IS NOT NULL OR m.output_tokens IS NOT NULL)");
|
|
51340
51358
|
const tokensPredicate = withStepPredicates(wm.where, `m.role = 'assistant' AND ${tokensGuard}`);
|
|
51341
|
-
return db2.queryAll(`SELECT m.session_id AS sessionId, m.source AS source, m.ts AS ts,
|
|
51359
|
+
return db2.queryAll(`SELECT m.session_id AS sessionId, m.source AS source, m.ts AS ts,
|
|
51360
|
+
CASE
|
|
51361
|
+
WHEN m.model IS NOT NULL AND m.model != '' AND m.model != 'unknown' THEN m.model
|
|
51362
|
+
WHEN m.session_id IS NOT NULL AND m.session_id NOT IN ('', 'unknown', 'session') THEN (
|
|
51363
|
+
SELECT m2.model FROM history_message m2
|
|
51364
|
+
WHERE m2.source = m.source AND m2.session_id = m.session_id
|
|
51365
|
+
AND m2.model IS NOT NULL AND m2.model != '' AND m2.model != 'unknown'
|
|
51366
|
+
LIMIT 1
|
|
51367
|
+
)
|
|
51368
|
+
ELSE 'unknown'
|
|
51369
|
+
END AS model,
|
|
51342
51370
|
m.input_tokens AS inputTokens, m.cache_read_tokens AS cacheReadTokens,
|
|
51343
51371
|
m.output_tokens AS outputTokens, m.cost_usd AS costUsd, m.duration_ms AS durationMs
|
|
51344
51372
|
FROM history_message m
|
|
@@ -51351,7 +51379,17 @@ async function topStepsByDuration(db2, sel, top, opts) {
|
|
|
51351
51379
|
const wm = applyWatermarkToWhere(where, opts?.watermark);
|
|
51352
51380
|
const durationOrder = rankOrderExpr(sel, "m.duration_ms");
|
|
51353
51381
|
const durationPredicate = withStepPredicates(wm.where, `m.role = 'assistant' AND ${durationOrder} IS NOT NULL`);
|
|
51354
|
-
return db2.queryAll(`SELECT m.session_id AS sessionId, m.source AS source, m.ts AS ts,
|
|
51382
|
+
return db2.queryAll(`SELECT m.session_id AS sessionId, m.source AS source, m.ts AS ts,
|
|
51383
|
+
CASE
|
|
51384
|
+
WHEN m.model IS NOT NULL AND m.model != '' AND m.model != 'unknown' THEN m.model
|
|
51385
|
+
WHEN m.session_id IS NOT NULL AND m.session_id NOT IN ('', 'unknown', 'session') THEN (
|
|
51386
|
+
SELECT m2.model FROM history_message m2
|
|
51387
|
+
WHERE m2.source = m.source AND m2.session_id = m.session_id
|
|
51388
|
+
AND m2.model IS NOT NULL AND m2.model != '' AND m2.model != 'unknown'
|
|
51389
|
+
LIMIT 1
|
|
51390
|
+
)
|
|
51391
|
+
ELSE 'unknown'
|
|
51392
|
+
END AS model,
|
|
51355
51393
|
m.input_tokens AS inputTokens, m.cache_read_tokens AS cacheReadTokens,
|
|
51356
51394
|
m.output_tokens AS outputTokens, m.cost_usd AS costUsd, m.duration_ms AS durationMs
|
|
51357
51395
|
FROM history_message m
|
|
@@ -51374,7 +51412,17 @@ async function topCacheWasteSteps(db2, sel, top, opts) {
|
|
|
51374
51412
|
const wm = applyWatermarkToWhere(where, opts?.watermark);
|
|
51375
51413
|
const inputOrder = rankOrderExpr(sel, "m.input_tokens");
|
|
51376
51414
|
const wastePredicate2 = withStepPredicates(wm.where, `m.role = 'assistant' AND ${inputOrder} > ? AND m.cache_read_tokens < m.input_tokens * ?`);
|
|
51377
|
-
return db2.queryAll(`SELECT m.session_id AS sessionId, m.source AS source, m.ts AS ts,
|
|
51415
|
+
return db2.queryAll(`SELECT m.session_id AS sessionId, m.source AS source, m.ts AS ts,
|
|
51416
|
+
CASE
|
|
51417
|
+
WHEN m.model IS NOT NULL AND m.model != '' AND m.model != 'unknown' THEN m.model
|
|
51418
|
+
WHEN m.session_id IS NOT NULL AND m.session_id NOT IN ('', 'unknown', 'session') THEN (
|
|
51419
|
+
SELECT m2.model FROM history_message m2
|
|
51420
|
+
WHERE m2.source = m.source AND m2.session_id = m.session_id
|
|
51421
|
+
AND m2.model IS NOT NULL AND m2.model != '' AND m2.model != 'unknown'
|
|
51422
|
+
LIMIT 1
|
|
51423
|
+
)
|
|
51424
|
+
ELSE 'unknown'
|
|
51425
|
+
END AS model,
|
|
51378
51426
|
m.input_tokens AS inputTokens, m.cache_read_tokens AS cacheReadTokens,
|
|
51379
51427
|
m.output_tokens AS outputTokens, m.cost_usd AS costUsd, m.duration_ms AS durationMs
|
|
51380
51428
|
FROM history_message m
|
|
@@ -51563,29 +51611,123 @@ function finalizeSessionDurations(events) {
|
|
|
51563
51611
|
}
|
|
51564
51612
|
}
|
|
51565
51613
|
}
|
|
51566
|
-
async function queryTimelineEvents(db2, whereClause, params,
|
|
51614
|
+
async function queryTimelineEvents(db2, whereClause, params, _subWhereClause, _subParams, limit, correlationMap) {
|
|
51567
51615
|
const fetchLimit = limit + 1;
|
|
51568
51616
|
const mainCondition = whereClause.startsWith("WHERE ") ? whereClause.slice(6) : whereClause;
|
|
51569
|
-
const
|
|
51570
|
-
|
|
51571
|
-
|
|
51572
|
-
|
|
51617
|
+
const dedupCondition = `(m.request_id IS NULL OR NOT EXISTS (
|
|
51618
|
+
SELECT 1 FROM history_message dm
|
|
51619
|
+
WHERE dm.request_id = m.request_id AND dm.rowid < m.rowid
|
|
51620
|
+
))`;
|
|
51621
|
+
const fullWhere = mainCondition ? `WHERE ${mainCondition} AND ${dedupCondition}` : `WHERE ${dedupCondition}`;
|
|
51622
|
+
const msgRows = await db2.queryAll(`SELECT m.record_hash AS messageHash, m.seq AS messageSeq,
|
|
51573
51623
|
COALESCE(m.turn_index, m.seq) AS turnIndex,
|
|
51574
51624
|
m.ts AS messageTs, m.role AS messageRole, COALESCE(m.record_type, m.role) AS recordType,
|
|
51575
51625
|
m.source, m.session_id AS sessionId, m.model,
|
|
51576
51626
|
m.duration_ms AS messageDurationMs, m.input_tokens AS inputTokens,
|
|
51577
51627
|
m.cache_read_tokens AS cacheReadTokens, m.output_tokens AS outputTokens,
|
|
51578
|
-
m.content_text AS messagePayload,
|
|
51579
|
-
(SELECT COUNT(*) FROM history_tool_call links WHERE links.message_hash = m.record_hash) AS links,
|
|
51580
|
-
tc.tool_name AS toolName, COALESCE(tc.started_at, m.ts) AS toolTs,
|
|
51581
|
-
tc.duration_ms AS toolDurationMs, tc.status AS toolStatus,
|
|
51582
|
-
tc.args_raw AS toolArgsRaw, tc.args_digest AS toolArgsDigest, tc.error_text AS toolErrorText,
|
|
51583
|
-
COALESCE(tc.seq, 0) AS toolSeq, m.rowid AS messageRowId
|
|
51628
|
+
m.content_text AS messagePayload, m.rowid AS messageRowId
|
|
51584
51629
|
FROM history_message m
|
|
51585
|
-
LEFT JOIN history_tool_call tc ON tc.message_hash = m.record_hash
|
|
51586
51630
|
${fullWhere}
|
|
51587
|
-
ORDER BY
|
|
51588
|
-
LIMIT ?`, ...params,
|
|
51631
|
+
ORDER BY m.ts DESC NULLS LAST, m.rowid DESC
|
|
51632
|
+
LIMIT ?`, ...params, fetchLimit);
|
|
51633
|
+
const msgHashes = msgRows.map((r) => r.messageHash);
|
|
51634
|
+
const tcMap = new Map;
|
|
51635
|
+
if (msgHashes.length > 0) {
|
|
51636
|
+
const inPlaceholders = msgHashes.map(() => "?").join(", ");
|
|
51637
|
+
const tcRows = await db2.queryAll(`SELECT tc.message_hash AS messageHash,
|
|
51638
|
+
tc.tool_name AS toolName, COALESCE(tc.started_at, m.ts) AS toolTs,
|
|
51639
|
+
tc.duration_ms AS toolDurationMs, tc.status AS toolStatus,
|
|
51640
|
+
tc.args_raw AS toolArgsRaw, tc.args_digest AS toolArgsDigest, tc.error_text AS toolErrorText,
|
|
51641
|
+
COALESCE(tc.seq, 0) AS toolSeq
|
|
51642
|
+
FROM history_tool_call tc
|
|
51643
|
+
JOIN history_message m ON m.record_hash = tc.message_hash
|
|
51644
|
+
WHERE tc.message_hash IN (${inPlaceholders})
|
|
51645
|
+
ORDER BY tc.message_hash, COALESCE(tc.seq, 0)
|
|
51646
|
+
LIMIT ?`, ...msgHashes, fetchLimit * 50);
|
|
51647
|
+
for (const tc of tcRows) {
|
|
51648
|
+
let list = tcMap.get(tc.messageHash);
|
|
51649
|
+
if (list === undefined) {
|
|
51650
|
+
list = [];
|
|
51651
|
+
tcMap.set(tc.messageHash, list);
|
|
51652
|
+
}
|
|
51653
|
+
list.push(tc);
|
|
51654
|
+
}
|
|
51655
|
+
}
|
|
51656
|
+
const sessionModelMap = new Map;
|
|
51657
|
+
for (const row of msgRows) {
|
|
51658
|
+
if (row.model && row.model !== "" && row.model !== "unknown") {
|
|
51659
|
+
const key = `${row.source}:::${row.sessionId}`;
|
|
51660
|
+
if (!sessionModelMap.has(key))
|
|
51661
|
+
sessionModelMap.set(key, row.model);
|
|
51662
|
+
}
|
|
51663
|
+
}
|
|
51664
|
+
for (const row of msgRows) {
|
|
51665
|
+
if (!row.model || row.model === "" || row.model === "unknown") {
|
|
51666
|
+
const key = `${row.source}:::${row.sessionId}`;
|
|
51667
|
+
const resolved = sessionModelMap.get(key);
|
|
51668
|
+
if (resolved)
|
|
51669
|
+
row.model = resolved;
|
|
51670
|
+
}
|
|
51671
|
+
}
|
|
51672
|
+
const rows = [];
|
|
51673
|
+
for (const m of msgRows) {
|
|
51674
|
+
const tcs = tcMap.get(m.messageHash) ?? [];
|
|
51675
|
+
const links = tcs.length;
|
|
51676
|
+
rows.push({
|
|
51677
|
+
messageHash: m.messageHash,
|
|
51678
|
+
messageSeq: m.messageSeq,
|
|
51679
|
+
turnIndex: m.turnIndex,
|
|
51680
|
+
messageTs: m.messageTs,
|
|
51681
|
+
messageRole: m.messageRole,
|
|
51682
|
+
recordType: m.recordType,
|
|
51683
|
+
source: m.source,
|
|
51684
|
+
sessionId: m.sessionId,
|
|
51685
|
+
model: m.model,
|
|
51686
|
+
messageDurationMs: m.messageDurationMs,
|
|
51687
|
+
inputTokens: m.inputTokens,
|
|
51688
|
+
cacheReadTokens: m.cacheReadTokens,
|
|
51689
|
+
outputTokens: m.outputTokens,
|
|
51690
|
+
messagePayload: m.messagePayload,
|
|
51691
|
+
links,
|
|
51692
|
+
toolName: null,
|
|
51693
|
+
toolTs: null,
|
|
51694
|
+
toolDurationMs: null,
|
|
51695
|
+
toolStatus: null,
|
|
51696
|
+
toolArgsRaw: null,
|
|
51697
|
+
toolArgsDigest: null,
|
|
51698
|
+
toolErrorText: null,
|
|
51699
|
+
toolSeq: 0,
|
|
51700
|
+
messageRowId: m.messageRowId
|
|
51701
|
+
});
|
|
51702
|
+
for (const tc of tcs) {
|
|
51703
|
+
rows.push({
|
|
51704
|
+
messageHash: m.messageHash,
|
|
51705
|
+
messageSeq: m.messageSeq,
|
|
51706
|
+
turnIndex: m.turnIndex,
|
|
51707
|
+
messageTs: m.messageTs,
|
|
51708
|
+
messageRole: m.messageRole,
|
|
51709
|
+
recordType: m.recordType,
|
|
51710
|
+
source: m.source,
|
|
51711
|
+
sessionId: m.sessionId,
|
|
51712
|
+
model: m.model,
|
|
51713
|
+
messageDurationMs: m.messageDurationMs,
|
|
51714
|
+
inputTokens: m.inputTokens,
|
|
51715
|
+
cacheReadTokens: m.cacheReadTokens,
|
|
51716
|
+
outputTokens: m.outputTokens,
|
|
51717
|
+
messagePayload: m.messagePayload,
|
|
51718
|
+
links,
|
|
51719
|
+
toolName: tc.toolName,
|
|
51720
|
+
toolTs: tc.toolTs,
|
|
51721
|
+
toolDurationMs: tc.toolDurationMs,
|
|
51722
|
+
toolStatus: tc.toolStatus,
|
|
51723
|
+
toolArgsRaw: tc.toolArgsRaw,
|
|
51724
|
+
toolArgsDigest: tc.toolArgsDigest,
|
|
51725
|
+
toolErrorText: tc.toolErrorText,
|
|
51726
|
+
toolSeq: tc.toolSeq,
|
|
51727
|
+
messageRowId: m.messageRowId
|
|
51728
|
+
});
|
|
51729
|
+
}
|
|
51730
|
+
}
|
|
51589
51731
|
const rawEvents = [];
|
|
51590
51732
|
let previousMessage = "";
|
|
51591
51733
|
for (const row of rows) {
|
|
@@ -51904,37 +52046,45 @@ async function historyKpiTrend(db2, sel, opts) {
|
|
|
51904
52046
|
}
|
|
51905
52047
|
async function toolSequenceQuery(db2, scope, filters = {}, limit = 5000) {
|
|
51906
52048
|
const fetchLimit = limit + 1;
|
|
51907
|
-
const
|
|
51908
|
-
const
|
|
52049
|
+
const msgCteParams = [];
|
|
52050
|
+
const tcFilterParams = [];
|
|
52051
|
+
let msgCteWhere;
|
|
51909
52052
|
if (scope.mode === "session") {
|
|
51910
|
-
|
|
51911
|
-
|
|
52053
|
+
msgCteWhere = `WHERE m.source = ? AND m.session_id = ? AND ${MESSAGE_DEDUP}`;
|
|
52054
|
+
msgCteParams.push(scope.source, scope.sessionId);
|
|
51912
52055
|
} else {
|
|
51913
|
-
const { where, params:
|
|
51914
|
-
|
|
51915
|
-
|
|
51916
|
-
|
|
51917
|
-
|
|
52056
|
+
const { where: mw, params: mp } = buildMessageWhere(scope.sel, "m");
|
|
52057
|
+
const msgCondition = mw.startsWith("WHERE ") ? mw.slice(6) : mw;
|
|
52058
|
+
msgCteWhere = msgCondition ? `WHERE ${msgCondition} AND ${MESSAGE_DEDUP}` : `WHERE ${MESSAGE_DEDUP}`;
|
|
52059
|
+
msgCteParams.push(...mp);
|
|
52060
|
+
}
|
|
52061
|
+
const tcFilterClauses = [];
|
|
52062
|
+
if (scope.mode === "session") {
|
|
52063
|
+
tcFilterClauses.push("tc.source = ? AND tc.session_id = ?");
|
|
52064
|
+
tcFilterParams.push(scope.source, scope.sessionId);
|
|
51918
52065
|
}
|
|
51919
52066
|
if (filters.toolNames && filters.toolNames.length > 0) {
|
|
51920
52067
|
const placeholders = filters.toolNames.map(() => "?").join(", ");
|
|
51921
|
-
|
|
51922
|
-
|
|
52068
|
+
tcFilterClauses.push(toolSelectionSql("tc", placeholders));
|
|
52069
|
+
tcFilterParams.push(...filters.toolNames, ...filters.toolNames);
|
|
51923
52070
|
}
|
|
51924
52071
|
if (filters.status && filters.status !== "all") {
|
|
51925
|
-
|
|
51926
|
-
|
|
52072
|
+
tcFilterClauses.push("tc.status = ?");
|
|
52073
|
+
tcFilterParams.push(filters.status);
|
|
51927
52074
|
}
|
|
51928
52075
|
if (filters.search && filters.search.trim().length > 0) {
|
|
51929
52076
|
const searchPattern = `%${escapeLike(filters.search.trim())}%`;
|
|
51930
|
-
|
|
51931
|
-
|
|
52077
|
+
tcFilterClauses.push("(tc.args_raw LIKE ? ESCAPE '!' OR tc.error_text LIKE ? ESCAPE '!' OR tc.effective_tool_name LIKE ? ESCAPE '!' OR tc.tool_name LIKE ? ESCAPE '!')");
|
|
52078
|
+
tcFilterParams.push(searchPattern, searchPattern, searchPattern, searchPattern);
|
|
51932
52079
|
}
|
|
51933
|
-
const
|
|
51934
|
-
const
|
|
51935
|
-
|
|
51936
|
-
|
|
51937
|
-
|
|
52080
|
+
const tcWhere = tcFilterClauses.length > 0 ? `AND ${tcFilterClauses.join(" AND ")}` : "";
|
|
52081
|
+
const rows = await db2.queryAll(`WITH deduped_messages AS (
|
|
52082
|
+
SELECT record_hash, session_id, source, model, input_tokens, cache_read_tokens, output_tokens, ts
|
|
52083
|
+
FROM history_message m
|
|
52084
|
+
${msgCteWhere}
|
|
52085
|
+
)
|
|
52086
|
+
SELECT tc.seq AS toolSeq,
|
|
52087
|
+
COALESCE(tc.started_at, dm.ts) AS ts,
|
|
51938
52088
|
tc.tool_name AS toolName,
|
|
51939
52089
|
tc.effective_tool_name AS effectiveToolName,
|
|
51940
52090
|
tc.status AS status,
|
|
@@ -51947,16 +52097,67 @@ async function toolSequenceQuery(db2, scope, filters = {}, limit = 5000) {
|
|
|
51947
52097
|
tc.message_hash AS messageHash,
|
|
51948
52098
|
tc.session_id AS sessionId,
|
|
51949
52099
|
tc.source AS source,
|
|
51950
|
-
|
|
51951
|
-
|
|
51952
|
-
|
|
51953
|
-
|
|
51954
|
-
|
|
51955
|
-
FROM
|
|
51956
|
-
JOIN
|
|
51957
|
-
${
|
|
51958
|
-
ORDER BY COALESCE(tc.started_at,
|
|
51959
|
-
LIMIT ?`, ...
|
|
52100
|
+
dm.model,
|
|
52101
|
+
0 AS links,
|
|
52102
|
+
dm.input_tokens AS inputTokens,
|
|
52103
|
+
dm.cache_read_tokens AS cacheReadTokens,
|
|
52104
|
+
dm.output_tokens AS outputTokens
|
|
52105
|
+
FROM deduped_messages dm
|
|
52106
|
+
CROSS JOIN history_tool_call tc ON tc.message_hash = dm.record_hash
|
|
52107
|
+
${tcWhere}
|
|
52108
|
+
ORDER BY COALESCE(tc.started_at, dm.ts), tc.source, tc.session_id, tc.seq
|
|
52109
|
+
LIMIT ?`, ...msgCteParams, ...tcFilterParams, fetchLimit);
|
|
52110
|
+
const msgHashCount = new Map;
|
|
52111
|
+
for (const row of rows) {
|
|
52112
|
+
msgHashCount.set(row.messageHash, (msgHashCount.get(row.messageHash) ?? 0) + 1);
|
|
52113
|
+
}
|
|
52114
|
+
for (const row of rows) {
|
|
52115
|
+
row.links = msgHashCount.get(row.messageHash) ?? 1;
|
|
52116
|
+
}
|
|
52117
|
+
const tsqModelMap = new Map;
|
|
52118
|
+
for (const row of rows) {
|
|
52119
|
+
if (row.model && row.model !== "" && row.model !== "unknown") {
|
|
52120
|
+
const key = `${row.source}:::${row.sessionId}`;
|
|
52121
|
+
if (!tsqModelMap.has(key))
|
|
52122
|
+
tsqModelMap.set(key, row.model);
|
|
52123
|
+
}
|
|
52124
|
+
}
|
|
52125
|
+
const missingKeys = new Set;
|
|
52126
|
+
for (const row of rows) {
|
|
52127
|
+
if (!row.model || row.model === "" || row.model === "unknown") {
|
|
52128
|
+
const key = `${row.source}:::${row.sessionId}`;
|
|
52129
|
+
if (!tsqModelMap.has(key))
|
|
52130
|
+
missingKeys.add(key);
|
|
52131
|
+
}
|
|
52132
|
+
}
|
|
52133
|
+
if (missingKeys.size > 0) {
|
|
52134
|
+
const pairs = [...missingKeys].map((k) => {
|
|
52135
|
+
const [src, sid] = k.split(":::");
|
|
52136
|
+
return { src, sid };
|
|
52137
|
+
});
|
|
52138
|
+
const placeholders = pairs.map(() => "(?, ?)").join(", ");
|
|
52139
|
+
const batchParams = [];
|
|
52140
|
+
for (const { src, sid } of pairs) {
|
|
52141
|
+
batchParams.push(src, sid);
|
|
52142
|
+
}
|
|
52143
|
+
const modelRows = await db2.queryAll(`SELECT source, session_id, model
|
|
52144
|
+
FROM history_message
|
|
52145
|
+
WHERE (source, session_id) IN (${placeholders})
|
|
52146
|
+
AND model IS NOT NULL AND model != '' AND model != 'unknown'
|
|
52147
|
+
GROUP BY source, session_id
|
|
52148
|
+
LIMIT ?`, ...batchParams, pairs.length);
|
|
52149
|
+
for (const mr of modelRows) {
|
|
52150
|
+
tsqModelMap.set(`${mr.source}:::${mr.session_id}`, mr.model);
|
|
52151
|
+
}
|
|
52152
|
+
}
|
|
52153
|
+
for (const row of rows) {
|
|
52154
|
+
if (!row.model || row.model === "" || row.model === "unknown") {
|
|
52155
|
+
const key = `${row.source}:::${row.sessionId}`;
|
|
52156
|
+
const resolved = tsqModelMap.get(key);
|
|
52157
|
+
if (resolved)
|
|
52158
|
+
row.model = resolved;
|
|
52159
|
+
}
|
|
52160
|
+
}
|
|
51960
52161
|
const truncated = rows.length > limit;
|
|
51961
52162
|
const finalRows = truncated ? rows.slice(0, limit) : rows;
|
|
51962
52163
|
return {
|
|
@@ -52470,7 +52671,7 @@ async function rollupTableFreshness(db2) {
|
|
|
52470
52671
|
}
|
|
52471
52672
|
return result;
|
|
52472
52673
|
}
|
|
52473
|
-
var ROLLUP_DEFINITION_VERSION = "
|
|
52674
|
+
var ROLLUP_DEFINITION_VERSION = "v5", EMPTY_ROLLUP_WATERMARK, BUCKETED_ROLLUP_TABLES, KEYED_ROLLUP_TABLES, GLOBAL_RANKED_ROLLUP_TABLES, ALL_ROLLUP_TABLES, MSG_BUCKET_5M = `strftime('%Y-%m-%dT%H:%M:00Z', CAST(strftime('%s', m.ts) / 60 * 60 AS INTEGER), 'unixepoch')`, SKILL_BUCKET_5M = `strftime('%Y-%m-%dT%H:%M:00Z', CAST(strftime('%s', sc.started_at) / 60 * 60 AS INTEGER), 'unixepoch')`, MSG_DAY;
|
|
52474
52675
|
var init_rollup_watermark = __esm(() => {
|
|
52475
52676
|
EMPTY_ROLLUP_WATERMARK = {
|
|
52476
52677
|
importedAtWatermark: "",
|
|
@@ -52510,6 +52711,11 @@ async function historyBoardHistoryVersion(db2) {
|
|
|
52510
52711
|
FROM history_message ORDER BY rowid DESC LIMIT 1`);
|
|
52511
52712
|
return `v${HISTORY_BOARD_ROLLUP_VERSION}:message:${row?.importedAt ?? ""}:${row?.maxRowId ?? 0}`;
|
|
52512
52713
|
}
|
|
52714
|
+
async function markHistoryBoardRollupsRefreshed(db2, historyVersion) {
|
|
52715
|
+
await db2.run(`INSERT INTO history_board_rollup_meta (id, history_version, refreshed_at)
|
|
52716
|
+
VALUES (1, ?, ?)
|
|
52717
|
+
ON CONFLICT(id) DO UPDATE SET history_version = excluded.history_version, refreshed_at = excluded.refreshed_at`, historyVersion, new Date().toISOString());
|
|
52718
|
+
}
|
|
52513
52719
|
async function skillCallRollup(db2) {
|
|
52514
52720
|
return db2.queryAll(`SELECT strftime('%Y-%m-%dT%H:%M:00Z', CAST(strftime('%s', started_at) / 60 * 60 AS INTEGER), 'unixepoch') AS bucketStart,
|
|
52515
52721
|
source,
|
|
@@ -52555,9 +52761,10 @@ async function replaceHistoryBoardRollups(db2, seed) {
|
|
|
52555
52761
|
WITH enriched AS (
|
|
52556
52762
|
SELECT m.*,
|
|
52557
52763
|
COALESCE(
|
|
52558
|
-
m.model,
|
|
52764
|
+
NULLIF(NULLIF(m.model, ''), 'unknown'),
|
|
52559
52765
|
MAX(CASE WHEN m.model IS NOT NULL AND m.model != '' AND m.model != 'unknown' THEN m.model END)
|
|
52560
52766
|
OVER (PARTITION BY m.source, m.session_id),
|
|
52767
|
+
NULLIF(m.model, ''),
|
|
52561
52768
|
'unknown'
|
|
52562
52769
|
) AS effective_model
|
|
52563
52770
|
FROM history_message m
|
|
@@ -52588,9 +52795,10 @@ async function replaceHistoryBoardRollups(db2, seed) {
|
|
|
52588
52795
|
WITH enriched AS (
|
|
52589
52796
|
SELECT m.*,
|
|
52590
52797
|
COALESCE(
|
|
52591
|
-
m.model,
|
|
52798
|
+
NULLIF(NULLIF(m.model, ''), 'unknown'),
|
|
52592
52799
|
MAX(CASE WHEN m.model IS NOT NULL AND m.model != '' AND m.model != 'unknown' THEN m.model END)
|
|
52593
52800
|
OVER (PARTITION BY m.source, m.session_id),
|
|
52801
|
+
NULLIF(m.model, ''),
|
|
52594
52802
|
'unknown'
|
|
52595
52803
|
) AS effective_model,
|
|
52596
52804
|
COALESCE(
|
|
@@ -53022,7 +53230,9 @@ function buildSessionWhere(sel, alias3 = "s") {
|
|
|
53022
53230
|
}
|
|
53023
53231
|
async function historyBoardSessionsFromRollup(db2, sel, input) {
|
|
53024
53232
|
const spec = buildSessionWhere(sel);
|
|
53025
|
-
const
|
|
53233
|
+
const sortKey = input.sortBy ?? input.sortField ?? "start";
|
|
53234
|
+
const order = SESSION_ORDER_COLUMNS[sortKey] ?? SESSION_ORDER_COLUMNS.start;
|
|
53235
|
+
const sortDir = (input.sortDir ?? "desc").toUpperCase();
|
|
53026
53236
|
const offset = (input.page - 1) * input.pageSize;
|
|
53027
53237
|
const [items, total] = await Promise.all([
|
|
53028
53238
|
db2.queryAll(`SELECT s.source, s.session_id AS sessionId, s.model,
|
|
@@ -53034,7 +53244,7 @@ async function historyBoardSessionsFromRollup(db2, sel, input) {
|
|
|
53034
53244
|
s.assistant_duration_ms AS assistantDurationMs,
|
|
53035
53245
|
s.top_tool AS topTool, s.state
|
|
53036
53246
|
FROM history_board_session_stats s ${spec.where}
|
|
53037
|
-
ORDER BY ${order} ${
|
|
53247
|
+
ORDER BY ${order} ${sortDir}, s.session_id ASC
|
|
53038
53248
|
LIMIT ? OFFSET ?`, ...spec.params, input.pageSize, offset),
|
|
53039
53249
|
db2.queryFirst(`SELECT COUNT(*) AS total FROM history_board_session_stats s ${spec.where}`, ...spec.params)
|
|
53040
53250
|
]);
|
|
@@ -53278,9 +53488,11 @@ function message5mBucketOps(bucket) {
|
|
|
53278
53488
|
WITH enriched AS (
|
|
53279
53489
|
SELECT m.*,
|
|
53280
53490
|
COALESCE(
|
|
53281
|
-
m.model,
|
|
53282
|
-
|
|
53283
|
-
|
|
53491
|
+
NULLIF(NULLIF(m.model, ''), 'unknown'),
|
|
53492
|
+
(SELECT m2.model FROM history_message m2
|
|
53493
|
+
WHERE m2.source = m.source AND m2.session_id = m.session_id
|
|
53494
|
+
AND m2.model IS NOT NULL AND m2.model != '' AND m2.model != 'unknown'
|
|
53495
|
+
LIMIT 1),
|
|
53284
53496
|
'unknown'
|
|
53285
53497
|
) AS effective_model
|
|
53286
53498
|
FROM history_message m
|
|
@@ -53317,9 +53529,11 @@ function tool5mBucketOps(bucket) {
|
|
|
53317
53529
|
WITH enriched AS (
|
|
53318
53530
|
SELECT m.*,
|
|
53319
53531
|
COALESCE(
|
|
53320
|
-
m.model,
|
|
53321
|
-
|
|
53322
|
-
|
|
53532
|
+
NULLIF(NULLIF(m.model, ''), 'unknown'),
|
|
53533
|
+
(SELECT m2.model FROM history_message m2
|
|
53534
|
+
WHERE m2.source = m.source AND m2.session_id = m.session_id
|
|
53535
|
+
AND m2.model IS NOT NULL AND m2.model != '' AND m2.model != 'unknown'
|
|
53536
|
+
LIMIT 1),
|
|
53323
53537
|
'unknown'
|
|
53324
53538
|
) AS effective_model,
|
|
53325
53539
|
COALESCE(
|
|
@@ -53799,6 +54013,23 @@ async function refreshHistoryBoardRollupsIncremental(db2) {
|
|
|
53799
54013
|
});
|
|
53800
54014
|
}
|
|
53801
54015
|
}
|
|
54016
|
+
async function recordHistoryBoardRollupMeta(db2, historyVersion, refreshedAt = new Date().toISOString()) {
|
|
54017
|
+
await db2.run(`INSERT INTO history_board_rollup_meta (id, history_version, refreshed_at)
|
|
54018
|
+
VALUES (1, ?, ?)
|
|
54019
|
+
ON CONFLICT(id) DO UPDATE SET history_version = excluded.history_version, refreshed_at = excluded.refreshed_at`, historyVersion, refreshedAt);
|
|
54020
|
+
}
|
|
54021
|
+
async function hasHistoryBoardRollupRows(db2, table3) {
|
|
54022
|
+
const validTables = {
|
|
54023
|
+
history_board_message_5m: true,
|
|
54024
|
+
history_board_session_stats: true,
|
|
54025
|
+
history_board_loop_findings: true
|
|
54026
|
+
};
|
|
54027
|
+
if (!validTables[table3]) {
|
|
54028
|
+
throw new Error(`Invalid rollup probe table: ${table3}`);
|
|
54029
|
+
}
|
|
54030
|
+
const row = await db2.queryFirst(`SELECT 1 AS ok FROM ${table3} LIMIT 1`);
|
|
54031
|
+
return row?.ok === 1;
|
|
54032
|
+
}
|
|
53802
54033
|
var MESSAGE_DEDUP2 = `(m.request_id IS NULL OR NOT EXISTS (
|
|
53803
54034
|
SELECT 1 FROM history_message o
|
|
53804
54035
|
WHERE o.request_id = m.request_id AND o.rowid > m.rowid
|
|
@@ -60429,6 +60660,15 @@ var init_run_dao = __esm(() => {
|
|
|
60429
60660
|
stampMetadata(runId, metadata) {
|
|
60430
60661
|
return this.mergeMetadata(runId, metadata);
|
|
60431
60662
|
}
|
|
60663
|
+
stampRunIdentity(runId, definitionDigest, workflowVersion) {
|
|
60664
|
+
return this.adapter.run(`UPDATE runs
|
|
60665
|
+
SET metadata_json = json_set(
|
|
60666
|
+
COALESCE(NULLIF(metadata_json, ''), '{}'),
|
|
60667
|
+
'$.definitionDigest', ?1,
|
|
60668
|
+
'$.workflowVersion', ?2
|
|
60669
|
+
)
|
|
60670
|
+
WHERE id = ?3`, definitionDigest, workflowVersion, runId);
|
|
60671
|
+
}
|
|
60432
60672
|
stampFailureReason(runId, reason) {
|
|
60433
60673
|
return this.adapter.run(`UPDATE runs
|
|
60434
60674
|
SET metadata_json = json_set(
|
|
@@ -68824,14 +69064,190 @@ var init_envelope = __esm(() => {
|
|
|
68824
69064
|
init_serialization();
|
|
68825
69065
|
});
|
|
68826
69066
|
|
|
69067
|
+
// ../../packages/domain/src/retention.ts
|
|
69068
|
+
import { readdirSync as readdirSync5, rmSync as rmSync3, statfsSync, statSync as statSync6 } from "fs";
|
|
69069
|
+
import { join as join6 } from "path";
|
|
69070
|
+
async function estimateReclaimableBytes(db2) {
|
|
69071
|
+
try {
|
|
69072
|
+
const rows = await db2.queryAll("SELECT pageno, pagetype, ncell, payload, unused FROM dbstat ORDER BY pageno");
|
|
69073
|
+
let slack = 0;
|
|
69074
|
+
for (const row of rows)
|
|
69075
|
+
slack += row.unused || 0;
|
|
69076
|
+
return Math.max(0, Math.min(slack, await databaseBytes(db2)));
|
|
69077
|
+
} catch {
|
|
69078
|
+
return 0;
|
|
69079
|
+
}
|
|
69080
|
+
}
|
|
69081
|
+
async function databaseBytes(db2) {
|
|
69082
|
+
const [pageCount, pageSize] = await Promise.all([
|
|
69083
|
+
db2.queryFirst("PRAGMA page_count"),
|
|
69084
|
+
db2.queryFirst("PRAGMA page_size")
|
|
69085
|
+
]);
|
|
69086
|
+
return (pageCount?.page_count ?? 0) * (pageSize?.page_size ?? 0);
|
|
69087
|
+
}
|
|
69088
|
+
function hasSufficientDiskSpace(dbPath, requiredBytes) {
|
|
69089
|
+
try {
|
|
69090
|
+
const fs3 = statfsSync(dbPath);
|
|
69091
|
+
const freeBytes = fs3.bavail * fs3.bsize;
|
|
69092
|
+
return freeBytes >= requiredBytes;
|
|
69093
|
+
} catch {
|
|
69094
|
+
return true;
|
|
69095
|
+
}
|
|
69096
|
+
}
|
|
69097
|
+
async function compactDatabase(db2, opts) {
|
|
69098
|
+
const bytesBefore = await databaseBytes(db2);
|
|
69099
|
+
try {
|
|
69100
|
+
const lastRun = await db2.queryFirst("SELECT COALESCE(MAX(ran_at), 0) AS ran_at FROM spur_retention_meta WHERE kind = 'compaction'");
|
|
69101
|
+
const now = opts.now ?? new Date;
|
|
69102
|
+
const lastRunAt = lastRun?.ran_at ?? 0;
|
|
69103
|
+
if (lastRunAt !== undefined && lastRunAt > 0 && now.getTime() - lastRunAt < COMPACTION_MIN_INTERVAL_DAYS * DAY_MS) {
|
|
69104
|
+
return { ran: false, skippedReason: "recent-run", bytesBefore, bytesAfter: bytesBefore };
|
|
69105
|
+
}
|
|
69106
|
+
const reclaimable = await (opts.estimateReclaim ?? estimateReclaimableBytes)(db2);
|
|
69107
|
+
if (bytesBefore === 0) {
|
|
69108
|
+
return { ran: false, skippedReason: "empty-db", bytesBefore, bytesAfter: bytesBefore };
|
|
69109
|
+
}
|
|
69110
|
+
if (reclaimable / bytesBefore < COMPACTION_MIN_RECLAIM_RATIO) {
|
|
69111
|
+
return { ran: false, skippedReason: "reclaim-below-threshold", bytesBefore, bytesAfter: bytesBefore };
|
|
69112
|
+
}
|
|
69113
|
+
if (!hasSufficientDiskSpace(opts.dbPath, bytesBefore * 2)) {
|
|
69114
|
+
return { ran: false, skippedReason: "insufficient-disk", bytesBefore, bytesAfter: bytesBefore };
|
|
69115
|
+
}
|
|
69116
|
+
await db2.exec("VACUUM");
|
|
69117
|
+
await db2.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
69118
|
+
const bytesAfter = await databaseBytes(db2);
|
|
69119
|
+
await db2.run("INSERT INTO spur_retention_meta (kind, ran_at) VALUES ('compaction', ?)", now.getTime());
|
|
69120
|
+
return { ran: true, bytesBefore, bytesAfter };
|
|
69121
|
+
} catch {
|
|
69122
|
+
return { ran: false, skippedReason: "error", bytesBefore, bytesAfter: bytesBefore };
|
|
69123
|
+
}
|
|
69124
|
+
}
|
|
69125
|
+
async function runRetention(db2, cwd, now = new Date) {
|
|
69126
|
+
const result = {
|
|
69127
|
+
ruleEvalRuns: 0,
|
|
69128
|
+
queueJobs: 0,
|
|
69129
|
+
ledgerRows: 0,
|
|
69130
|
+
backupFiles: 0,
|
|
69131
|
+
compaction: { ran: false, skippedReason: "not-run", bytesBefore: 0, bytesAfter: 0 }
|
|
69132
|
+
};
|
|
69133
|
+
result.ruleEvalRuns = await purgeRuleEvalRuns(db2, now);
|
|
69134
|
+
result.queueJobs = await purgeQueueJobs(db2, now);
|
|
69135
|
+
result.ledgerRows = await purgeLedger(db2, now);
|
|
69136
|
+
result.backupFiles = pruneBackups(cwd, now);
|
|
69137
|
+
result.compaction = await compactDatabase(db2, { dbPath: join6(cwd, ".spur", "spur.db"), now });
|
|
69138
|
+
return result;
|
|
69139
|
+
}
|
|
69140
|
+
async function purgeRuleEvalRuns(db2, now) {
|
|
69141
|
+
return purgeRows(db2, `DELETE FROM rule_eval_runs WHERE created_at < ?`, new Date(now.getTime() - RULE_EVAL_RUN_RETENTION_DAYS * DAY_MS).toISOString());
|
|
69142
|
+
}
|
|
69143
|
+
async function purgeQueueJobs(db2, now) {
|
|
69144
|
+
return purgeRows(db2, `DELETE FROM queue_jobs WHERE status IN ('completed', 'failed') AND updated_at < ?`, now.getTime() - QUEUE_JOB_RETENTION_DAYS * DAY_MS);
|
|
69145
|
+
}
|
|
69146
|
+
async function purgeLedger(db2, now) {
|
|
69147
|
+
return purgeRows(db2, `DELETE FROM history_import_ledger WHERE imported_at < ?`, new Date(now.getTime() - LEDGER_RETENTION_DAYS * DAY_MS).toISOString());
|
|
69148
|
+
}
|
|
69149
|
+
function pruneBackups(cwd, now) {
|
|
69150
|
+
const dir = join6(cwd, ".spur", "backups");
|
|
69151
|
+
let entries;
|
|
69152
|
+
try {
|
|
69153
|
+
entries = readdirSync5(dir);
|
|
69154
|
+
} catch {
|
|
69155
|
+
return 0;
|
|
69156
|
+
}
|
|
69157
|
+
const cutoffMs = now.getTime() - BACKUP_RETENTION_DAYS * DAY_MS;
|
|
69158
|
+
let reclaimed = 0;
|
|
69159
|
+
for (const name of entries) {
|
|
69160
|
+
const path9 = join6(dir, name);
|
|
69161
|
+
try {
|
|
69162
|
+
const stat = statSync6(path9);
|
|
69163
|
+
if (!stat.isFile() || stat.mtimeMs >= cutoffMs)
|
|
69164
|
+
continue;
|
|
69165
|
+
rmSync3(path9, { force: true });
|
|
69166
|
+
reclaimed += 1;
|
|
69167
|
+
} catch {}
|
|
69168
|
+
}
|
|
69169
|
+
return reclaimed;
|
|
69170
|
+
}
|
|
69171
|
+
async function purgeRows(db2, sql4, cutoff) {
|
|
69172
|
+
try {
|
|
69173
|
+
await db2.run(sql4, cutoff);
|
|
69174
|
+
const row = await db2.queryFirst("SELECT changes() AS n");
|
|
69175
|
+
return row?.n ?? 0;
|
|
69176
|
+
} catch {
|
|
69177
|
+
return 0;
|
|
69178
|
+
}
|
|
69179
|
+
}
|
|
69180
|
+
var RULE_EVAL_RUN_RETENTION_DAYS = 90, QUEUE_JOB_RETENTION_DAYS = 30, LEDGER_RETENTION_DAYS = 180, BACKUP_RETENTION_DAYS = 30, DAY_MS, COMPACTION_MIN_INTERVAL_DAYS = 7, COMPACTION_MIN_RECLAIM_RATIO = 0.03;
|
|
69181
|
+
var init_retention = __esm(() => {
|
|
69182
|
+
DAY_MS = 24 * 60 * 60 * 1000;
|
|
69183
|
+
});
|
|
69184
|
+
|
|
69185
|
+
// ../../packages/domain/src/maintenance.ts
|
|
69186
|
+
async function maintainDatabase(db2, options = {}) {
|
|
69187
|
+
const start = performance.now();
|
|
69188
|
+
const bytesBefore = await databaseBytes(db2);
|
|
69189
|
+
let optimized = false;
|
|
69190
|
+
let checkpointed = false;
|
|
69191
|
+
let vacuumed = false;
|
|
69192
|
+
let vacuumSkippedReason;
|
|
69193
|
+
if (options.vacuum) {
|
|
69194
|
+
let canVacuum = true;
|
|
69195
|
+
if (options.dbPath && !hasSufficientDiskSpace(options.dbPath, bytesBefore * 2)) {
|
|
69196
|
+
canVacuum = false;
|
|
69197
|
+
vacuumSkippedReason = "insufficient-disk";
|
|
69198
|
+
}
|
|
69199
|
+
if (canVacuum) {
|
|
69200
|
+
try {
|
|
69201
|
+
await db2.exec("VACUUM");
|
|
69202
|
+
vacuumed = true;
|
|
69203
|
+
const now = options.now ?? new Date;
|
|
69204
|
+
try {
|
|
69205
|
+
await db2.run("INSERT INTO spur_retention_meta (kind, ran_at) VALUES ('compaction', ?)", now.getTime());
|
|
69206
|
+
} catch {}
|
|
69207
|
+
} catch {
|
|
69208
|
+
vacuumed = false;
|
|
69209
|
+
vacuumSkippedReason = "error";
|
|
69210
|
+
}
|
|
69211
|
+
}
|
|
69212
|
+
}
|
|
69213
|
+
try {
|
|
69214
|
+
await db2.exec("PRAGMA optimize;");
|
|
69215
|
+
optimized = true;
|
|
69216
|
+
} catch {
|
|
69217
|
+
optimized = false;
|
|
69218
|
+
}
|
|
69219
|
+
try {
|
|
69220
|
+
await db2.exec("PRAGMA wal_checkpoint(TRUNCATE);");
|
|
69221
|
+
checkpointed = true;
|
|
69222
|
+
} catch {
|
|
69223
|
+
checkpointed = false;
|
|
69224
|
+
}
|
|
69225
|
+
const bytesAfter = await databaseBytes(db2);
|
|
69226
|
+
const bytesReclaimed = Math.max(0, bytesBefore - bytesAfter);
|
|
69227
|
+
const durationMs = Math.round(performance.now() - start);
|
|
69228
|
+
return {
|
|
69229
|
+
optimized,
|
|
69230
|
+
checkpointed,
|
|
69231
|
+
vacuumed,
|
|
69232
|
+
...vacuumSkippedReason ? { vacuumSkippedReason } : {},
|
|
69233
|
+
bytesBefore,
|
|
69234
|
+
bytesAfter,
|
|
69235
|
+
bytesReclaimed,
|
|
69236
|
+
durationMs
|
|
69237
|
+
};
|
|
69238
|
+
}
|
|
69239
|
+
var init_maintenance = __esm(() => {
|
|
69240
|
+
init_retention();
|
|
69241
|
+
});
|
|
69242
|
+
|
|
68827
69243
|
// ../../packages/domain/src/planning/locks.ts
|
|
68828
69244
|
import { closeSync, fsyncSync, mkdirSync as mkdirSync4, openSync, readFileSync as readFileSync7, renameSync as renameSync3, unlinkSync, writeSync } from "fs";
|
|
68829
|
-
import { dirname as dirname7, join as
|
|
69245
|
+
import { dirname as dirname7, join as join7 } from "path";
|
|
68830
69246
|
function lockPathForEntity(folder, entityId) {
|
|
68831
|
-
return
|
|
69247
|
+
return join7(folder, `.${entityId}.lock`);
|
|
68832
69248
|
}
|
|
68833
69249
|
function createLockPath(folder) {
|
|
68834
|
-
return
|
|
69250
|
+
return join7(folder, ".create.lock");
|
|
68835
69251
|
}
|
|
68836
69252
|
function parseLockContent(raw) {
|
|
68837
69253
|
const str = typeof raw === "string" ? raw : raw.toString();
|
|
@@ -68936,7 +69352,7 @@ function fsyncPath(path9, swallowErrors = false) {
|
|
|
68936
69352
|
function atomicWrite(dest, content, entityId, fs3, projectName = "spur") {
|
|
68937
69353
|
const dir = dirname7(dest);
|
|
68938
69354
|
const rand = Math.random().toString(36).slice(2, 10);
|
|
68939
|
-
const tempPath =
|
|
69355
|
+
const tempPath = join7(dir, `.${projectName}.${entityId}.${rand}.tmp`);
|
|
68940
69356
|
fs3.writeFile(tempPath, content);
|
|
68941
69357
|
fsyncPath(tempPath);
|
|
68942
69358
|
renameSync3(tempPath, dest);
|
|
@@ -68945,7 +69361,7 @@ function atomicWrite(dest, content, entityId, fs3, projectName = "spur") {
|
|
|
68945
69361
|
async function atomicWriteAsync(dest, content, entityId, fs3, projectName = "spur") {
|
|
68946
69362
|
const dir = dirname7(dest);
|
|
68947
69363
|
const rand = Math.random().toString(36).slice(2, 10);
|
|
68948
|
-
const tempPath =
|
|
69364
|
+
const tempPath = join7(dir, `.${projectName}.${entityId}.${rand}.tmp`);
|
|
68949
69365
|
await fs3.writeFile(tempPath, content);
|
|
68950
69366
|
fsyncPath(tempPath);
|
|
68951
69367
|
renameSync3(tempPath, dest);
|
|
@@ -68960,7 +69376,7 @@ function cleanupTempFiles(folder, entityId, fs3, projectName = "spur") {
|
|
|
68960
69376
|
let count3 = 0;
|
|
68961
69377
|
for (const entry of entries) {
|
|
68962
69378
|
if (entry.startsWith(prefix) && entry.endsWith(suffix)) {
|
|
68963
|
-
fs3.deleteFile(
|
|
69379
|
+
fs3.deleteFile(join7(folder, entry));
|
|
68964
69380
|
count3++;
|
|
68965
69381
|
}
|
|
68966
69382
|
}
|
|
@@ -69651,118 +70067,6 @@ var init_task_skeleton = __esm(() => {
|
|
|
69651
70067
|
CANONICAL_INDEX = new Map(TASK_CANONICAL_SECTIONS.map((s2, i2) => [s2, i2]));
|
|
69652
70068
|
});
|
|
69653
70069
|
|
|
69654
|
-
// ../../packages/domain/src/retention.ts
|
|
69655
|
-
import { readdirSync as readdirSync5, rmSync as rmSync3, statfsSync, statSync as statSync6 } from "fs";
|
|
69656
|
-
import { join as join7 } from "path";
|
|
69657
|
-
async function estimateReclaimableBytes(db2) {
|
|
69658
|
-
try {
|
|
69659
|
-
const rows = await db2.queryAll("SELECT pageno, pagetype, ncell, payload, unused FROM dbstat ORDER BY pageno");
|
|
69660
|
-
let slack = 0;
|
|
69661
|
-
for (const row of rows)
|
|
69662
|
-
slack += row.unused || 0;
|
|
69663
|
-
return Math.max(0, Math.min(slack, await databaseBytes(db2)));
|
|
69664
|
-
} catch {
|
|
69665
|
-
return 0;
|
|
69666
|
-
}
|
|
69667
|
-
}
|
|
69668
|
-
async function databaseBytes(db2) {
|
|
69669
|
-
const [pageCount, pageSize] = await Promise.all([
|
|
69670
|
-
db2.queryFirst("PRAGMA page_count"),
|
|
69671
|
-
db2.queryFirst("PRAGMA page_size")
|
|
69672
|
-
]);
|
|
69673
|
-
return (pageCount?.page_count ?? 0) * (pageSize?.page_size ?? 0);
|
|
69674
|
-
}
|
|
69675
|
-
async function compactDatabase(db2, opts) {
|
|
69676
|
-
const bytesBefore = await databaseBytes(db2);
|
|
69677
|
-
try {
|
|
69678
|
-
const lastRun = await db2.queryFirst("SELECT COALESCE(MAX(ran_at), 0) AS ran_at FROM spur_retention_meta WHERE kind = 'compaction'");
|
|
69679
|
-
const now = opts.now ?? new Date;
|
|
69680
|
-
const lastRunAt = lastRun?.ran_at ?? 0;
|
|
69681
|
-
if (lastRunAt !== undefined && lastRunAt > 0 && now.getTime() - lastRunAt < COMPACTION_MIN_INTERVAL_DAYS * DAY_MS) {
|
|
69682
|
-
return { ran: false, skippedReason: "recent-run", bytesBefore, bytesAfter: bytesBefore };
|
|
69683
|
-
}
|
|
69684
|
-
const reclaimable = await (opts.estimateReclaim ?? estimateReclaimableBytes)(db2);
|
|
69685
|
-
if (bytesBefore === 0) {
|
|
69686
|
-
return { ran: false, skippedReason: "empty-db", bytesBefore, bytesAfter: bytesBefore };
|
|
69687
|
-
}
|
|
69688
|
-
if (reclaimable / bytesBefore < COMPACTION_MIN_RECLAIM_RATIO) {
|
|
69689
|
-
return { ran: false, skippedReason: "reclaim-below-threshold", bytesBefore, bytesAfter: bytesBefore };
|
|
69690
|
-
}
|
|
69691
|
-
try {
|
|
69692
|
-
const fs3 = statfsSync(opts.dbPath);
|
|
69693
|
-
const freeBytes = fs3.bavail * fs3.bsize;
|
|
69694
|
-
if (freeBytes < bytesBefore * 2) {
|
|
69695
|
-
return { ran: false, skippedReason: "insufficient-disk", bytesBefore, bytesAfter: bytesBefore };
|
|
69696
|
-
}
|
|
69697
|
-
} catch {}
|
|
69698
|
-
await db2.exec("VACUUM");
|
|
69699
|
-
const bytesAfter = await databaseBytes(db2);
|
|
69700
|
-
await db2.run("INSERT INTO spur_retention_meta (kind, ran_at) VALUES ('compaction', ?)", now.getTime());
|
|
69701
|
-
return { ran: true, bytesBefore, bytesAfter };
|
|
69702
|
-
} catch {
|
|
69703
|
-
return { ran: false, skippedReason: "error", bytesBefore, bytesAfter: bytesBefore };
|
|
69704
|
-
}
|
|
69705
|
-
}
|
|
69706
|
-
async function runRetention(db2, cwd, now = new Date) {
|
|
69707
|
-
const result = {
|
|
69708
|
-
ruleEvalRuns: 0,
|
|
69709
|
-
queueJobs: 0,
|
|
69710
|
-
ledgerRows: 0,
|
|
69711
|
-
backupFiles: 0,
|
|
69712
|
-
compaction: { ran: false, skippedReason: "not-run", bytesBefore: 0, bytesAfter: 0 }
|
|
69713
|
-
};
|
|
69714
|
-
result.ruleEvalRuns = await purgeRuleEvalRuns(db2, now);
|
|
69715
|
-
result.queueJobs = await purgeQueueJobs(db2, now);
|
|
69716
|
-
result.ledgerRows = await purgeLedger(db2, now);
|
|
69717
|
-
result.backupFiles = pruneBackups(cwd, now);
|
|
69718
|
-
result.compaction = await compactDatabase(db2, { dbPath: join7(cwd, ".spur", "spur.db"), now });
|
|
69719
|
-
return result;
|
|
69720
|
-
}
|
|
69721
|
-
async function purgeRuleEvalRuns(db2, now) {
|
|
69722
|
-
return purgeRows(db2, `DELETE FROM rule_eval_runs WHERE created_at < ?`, new Date(now.getTime() - RULE_EVAL_RUN_RETENTION_DAYS * DAY_MS).toISOString());
|
|
69723
|
-
}
|
|
69724
|
-
async function purgeQueueJobs(db2, now) {
|
|
69725
|
-
return purgeRows(db2, `DELETE FROM queue_jobs WHERE status IN ('completed', 'failed') AND updated_at < ?`, now.getTime() - QUEUE_JOB_RETENTION_DAYS * DAY_MS);
|
|
69726
|
-
}
|
|
69727
|
-
async function purgeLedger(db2, now) {
|
|
69728
|
-
return purgeRows(db2, `DELETE FROM history_import_ledger WHERE imported_at < ?`, new Date(now.getTime() - LEDGER_RETENTION_DAYS * DAY_MS).toISOString());
|
|
69729
|
-
}
|
|
69730
|
-
function pruneBackups(cwd, now) {
|
|
69731
|
-
const dir = join7(cwd, ".spur", "backups");
|
|
69732
|
-
let entries;
|
|
69733
|
-
try {
|
|
69734
|
-
entries = readdirSync5(dir);
|
|
69735
|
-
} catch {
|
|
69736
|
-
return 0;
|
|
69737
|
-
}
|
|
69738
|
-
const cutoffMs = now.getTime() - BACKUP_RETENTION_DAYS * DAY_MS;
|
|
69739
|
-
let reclaimed = 0;
|
|
69740
|
-
for (const name of entries) {
|
|
69741
|
-
const path9 = join7(dir, name);
|
|
69742
|
-
try {
|
|
69743
|
-
const stat = statSync6(path9);
|
|
69744
|
-
if (!stat.isFile() || stat.mtimeMs >= cutoffMs)
|
|
69745
|
-
continue;
|
|
69746
|
-
rmSync3(path9, { force: true });
|
|
69747
|
-
reclaimed += 1;
|
|
69748
|
-
} catch {}
|
|
69749
|
-
}
|
|
69750
|
-
return reclaimed;
|
|
69751
|
-
}
|
|
69752
|
-
async function purgeRows(db2, sql4, cutoff) {
|
|
69753
|
-
try {
|
|
69754
|
-
await db2.run(sql4, cutoff);
|
|
69755
|
-
const row = await db2.queryFirst("SELECT changes() AS n");
|
|
69756
|
-
return row?.n ?? 0;
|
|
69757
|
-
} catch {
|
|
69758
|
-
return 0;
|
|
69759
|
-
}
|
|
69760
|
-
}
|
|
69761
|
-
var RULE_EVAL_RUN_RETENTION_DAYS = 90, QUEUE_JOB_RETENTION_DAYS = 30, LEDGER_RETENTION_DAYS = 180, BACKUP_RETENTION_DAYS = 30, DAY_MS, COMPACTION_MIN_INTERVAL_DAYS = 7, COMPACTION_MIN_RECLAIM_RATIO = 0.03;
|
|
69762
|
-
var init_retention = __esm(() => {
|
|
69763
|
-
DAY_MS = 24 * 60 * 60 * 1000;
|
|
69764
|
-
});
|
|
69765
|
-
|
|
69766
70070
|
// ../../packages/domain/src/stage-registry/validator.ts
|
|
69767
70071
|
function generateRunId() {
|
|
69768
70072
|
const crypto3 = globalThis.crypto;
|
|
@@ -70070,6 +70374,7 @@ __export(exports_src, {
|
|
|
70070
70374
|
renderMarkdown: () => renderMarkdown,
|
|
70071
70375
|
renderForensics: () => renderForensics,
|
|
70072
70376
|
refreshHistoryBoardRollupsIncremental: () => refreshHistoryBoardRollupsIncremental,
|
|
70377
|
+
recordHistoryBoardRollupMeta: () => recordHistoryBoardRollupMeta,
|
|
70073
70378
|
readRollupWatermarks: () => readRollupWatermarks,
|
|
70074
70379
|
readRecordedImporterSchemaVersion: () => readRecordedImporterSchemaVersion,
|
|
70075
70380
|
readLockMetadata: () => readLockMetadata,
|
|
@@ -70095,6 +70400,8 @@ __export(exports_src, {
|
|
|
70095
70400
|
mergeStubs: () => mergeStubs,
|
|
70096
70401
|
matrixEntrySchema: () => matrixEntrySchema,
|
|
70097
70402
|
materializeWatermarkExclude: () => materializeWatermarkExclude,
|
|
70403
|
+
markHistoryBoardRollupsRefreshed: () => markHistoryBoardRollupsRefreshed,
|
|
70404
|
+
maintainDatabase: () => maintainDatabase,
|
|
70098
70405
|
loops: () => loops,
|
|
70099
70406
|
loopRepeatedCallsQuery: () => loopRepeatedCallsQuery,
|
|
70100
70407
|
looksLikeGherkinAc: () => looksLikeGherkinAc,
|
|
@@ -70129,6 +70436,8 @@ __export(exports_src, {
|
|
|
70129
70436
|
historyBoardDimensionDailyFromMart: () => historyBoardDimensionDailyFromMart,
|
|
70130
70437
|
historyBoardDatabaseBytes: () => historyBoardDatabaseBytes,
|
|
70131
70438
|
historyBoardBucketsFromRollup: () => historyBoardBucketsFromRollup,
|
|
70439
|
+
hasSufficientDiskSpace: () => hasSufficientDiskSpace,
|
|
70440
|
+
hasHistoryBoardRollupRows: () => hasHistoryBoardRollupRows,
|
|
70132
70441
|
getVolatileLayerNames: () => getVolatileLayerNames,
|
|
70133
70442
|
getStageLayerSelection: () => getStageLayerSelection,
|
|
70134
70443
|
getStageLayerNames: () => getStageLayerNames,
|
|
@@ -70165,6 +70474,7 @@ __export(exports_src, {
|
|
|
70165
70474
|
deriveDimensionMartsOps: () => deriveDimensionMartsOps,
|
|
70166
70475
|
deriveDimensionMarts: () => deriveDimensionMarts,
|
|
70167
70476
|
dbHealthCheck: () => dbHealthCheck,
|
|
70477
|
+
databaseBytes: () => databaseBytes,
|
|
70168
70478
|
dataWindow: () => dataWindow,
|
|
70169
70479
|
dailyTokenMatrix: () => dailyTokenMatrix,
|
|
70170
70480
|
createQueueConsumer: () => createQueueConsumer,
|
|
@@ -70330,6 +70640,7 @@ var init_src2 = __esm(() => {
|
|
|
70330
70640
|
init_bdd();
|
|
70331
70641
|
init_dao();
|
|
70332
70642
|
init_envelope();
|
|
70643
|
+
init_maintenance();
|
|
70333
70644
|
init_locks();
|
|
70334
70645
|
init_markdown_document();
|
|
70335
70646
|
init_rebuild_events();
|
|
@@ -71270,7 +71581,8 @@ function buildWorkflowSteps(def) {
|
|
|
71270
71581
|
pause: node.pause === true,
|
|
71271
71582
|
loopBack: incoming.some((e) => (order2.get(e.from) ?? -1) >= (order2.get(node.id) ?? 0)),
|
|
71272
71583
|
conditional: node.id !== def.initialNode && incoming.length > 0 && incoming.every((e) => e.condition !== undefined),
|
|
71273
|
-
nodeType: node.type ?? "action"
|
|
71584
|
+
nodeType: node.type ?? "action",
|
|
71585
|
+
...typeof node.description === "string" && node.description !== "" ? { description: node.description } : {}
|
|
71274
71586
|
};
|
|
71275
71587
|
});
|
|
71276
71588
|
}
|
|
@@ -71286,7 +71598,8 @@ function buildWorkflowSteps(def) {
|
|
|
71286
71598
|
failure: failure.has(state.id),
|
|
71287
71599
|
pause: state.pause === true,
|
|
71288
71600
|
loopBack: incoming.some((t) => (order.get(t.from) ?? -1) >= (order.get(state.id) ?? 0)),
|
|
71289
|
-
conditional: state.id !== def.initialState && incoming.length > 0 && incoming.every((t) => t.guard !== undefined)
|
|
71601
|
+
conditional: state.id !== def.initialState && incoming.length > 0 && incoming.every((t) => t.guard !== undefined),
|
|
71602
|
+
...typeof state.description === "string" && state.description !== "" ? { description: state.description } : {}
|
|
71290
71603
|
};
|
|
71291
71604
|
});
|
|
71292
71605
|
}
|
|
@@ -71297,22 +71610,30 @@ function renderWorkflowTodo(def) {
|
|
|
71297
71610
|
lines.push("Declared step inventory in declaration order, not a predicted execution path.", "");
|
|
71298
71611
|
}
|
|
71299
71612
|
for (const step of buildWorkflowSteps(def)) {
|
|
71300
|
-
|
|
71301
|
-
step.initial ? "initial" : undefined,
|
|
71302
|
-
step.terminal ? "terminal" : undefined,
|
|
71303
|
-
step.failure ? "failure" : undefined,
|
|
71304
|
-
step.pause ? "pause" : undefined,
|
|
71305
|
-
step.loopBack ? "loop-back" : undefined,
|
|
71306
|
-
step.conditional ? "conditional" : undefined,
|
|
71307
|
-
step.nodeType !== undefined && step.nodeType !== "action" ? step.nodeType : undefined
|
|
71308
|
-
].filter((m) => m !== undefined);
|
|
71309
|
-
lines.push(markers.length > 0 ? `- [ ] ${step.id} \u2014 ${markers.join(" \xB7 ")}` : `- [ ] ${step.id}`);
|
|
71613
|
+
lines.push(formatWorkflowStepLine(step));
|
|
71310
71614
|
}
|
|
71311
71615
|
return lines.join(`
|
|
71312
71616
|
`);
|
|
71313
71617
|
}
|
|
71618
|
+
function formatWorkflowStepLine(step) {
|
|
71619
|
+
const markers = [
|
|
71620
|
+
step.initial ? "initial" : undefined,
|
|
71621
|
+
step.terminal ? "terminal" : undefined,
|
|
71622
|
+
step.failure ? "failure" : undefined,
|
|
71623
|
+
step.pause ? "pause" : undefined,
|
|
71624
|
+
step.loopBack ? "loop-back" : undefined,
|
|
71625
|
+
step.conditional ? "conditional" : undefined,
|
|
71626
|
+
step.nodeType !== undefined && step.nodeType !== "action" ? step.nodeType : undefined
|
|
71627
|
+
].filter((m) => m !== undefined);
|
|
71628
|
+
return markers.length > 0 ? `- [ ] ${step.id} \u2014 ${markers.join(" \xB7 ")}` : `- [ ] ${step.id}`;
|
|
71629
|
+
}
|
|
71314
71630
|
function renderRunPlan(def) {
|
|
71315
|
-
|
|
71631
|
+
const kind = def.kind ?? "state-machine";
|
|
71632
|
+
return [
|
|
71633
|
+
`plan (${kind}) \u2014 declared inventory, not a predicted route:`,
|
|
71634
|
+
...buildWorkflowSteps(def).map(formatWorkflowStepLine)
|
|
71635
|
+
].join(`
|
|
71636
|
+
`);
|
|
71316
71637
|
}
|
|
71317
71638
|
function formatDuration(ms) {
|
|
71318
71639
|
const totalSeconds = Math.round(ms / 1000);
|
|
@@ -74183,6 +74504,9 @@ var init_structural_repair = __esm(() => {
|
|
|
74183
74504
|
function key(e) {
|
|
74184
74505
|
return `${e.kind}:${e.id}:${e.code}`;
|
|
74185
74506
|
}
|
|
74507
|
+
function isUnsuppressibleFinding(code) {
|
|
74508
|
+
return REQUIRED_FINDING_CODES.has(code);
|
|
74509
|
+
}
|
|
74186
74510
|
|
|
74187
74511
|
class PlanningCheckService {
|
|
74188
74512
|
fs;
|
|
@@ -74279,18 +74603,31 @@ class PlanningCheckService {
|
|
|
74279
74603
|
}
|
|
74280
74604
|
summarizeWithStatus(status, findings, strict, overrides, accepted, id) {
|
|
74281
74605
|
const effectiveFindings = [];
|
|
74606
|
+
const notes = [];
|
|
74282
74607
|
for (const f of findings) {
|
|
74608
|
+
if (REQUIRED_REFERENCE_CODES.has(f.code) || status === "done" && COMPLETION_FINDING_CODES.has(f.code)) {
|
|
74609
|
+
f.severity = "error";
|
|
74610
|
+
}
|
|
74611
|
+
const unsuppressible = isUnsuppressibleFinding(f.code);
|
|
74283
74612
|
const override = overrides?.[f.code];
|
|
74284
74613
|
if (override === "off") {
|
|
74285
|
-
|
|
74614
|
+
if (unsuppressible) {
|
|
74615
|
+
notes.push(`summarize: override 'off' refused for unsuppressible ${f.code}`);
|
|
74616
|
+
} else {
|
|
74617
|
+
continue;
|
|
74618
|
+
}
|
|
74286
74619
|
}
|
|
74287
74620
|
if (override === "error" || override === "warning") {
|
|
74288
|
-
|
|
74621
|
+
if (unsuppressible) {
|
|
74622
|
+
notes.push(`summarize: severity override for unsuppressible ${f.code} ignored (required severity preserved)`);
|
|
74623
|
+
} else {
|
|
74624
|
+
f.severity = override;
|
|
74625
|
+
}
|
|
74289
74626
|
}
|
|
74290
74627
|
if (strict && f.severity === "warning") {
|
|
74291
74628
|
f.severity = "error";
|
|
74292
74629
|
}
|
|
74293
|
-
if (accepted && id) {
|
|
74630
|
+
if (accepted && id && !unsuppressible) {
|
|
74294
74631
|
const k = key({ kind: this.docKind, id, code: f.code });
|
|
74295
74632
|
const acceptedSev = accepted.get(k);
|
|
74296
74633
|
if (acceptedSev !== undefined && acceptedSev === f.severity) {
|
|
@@ -74314,13 +74651,49 @@ class PlanningCheckService {
|
|
|
74314
74651
|
missingSections.push(f.section);
|
|
74315
74652
|
}
|
|
74316
74653
|
}
|
|
74317
|
-
return { status, findings: effectiveFindings, requiredSections, missingSections, pass: !hasError };
|
|
74654
|
+
return { status, findings: effectiveFindings, requiredSections, missingSections, pass: !hasError, notes };
|
|
74318
74655
|
}
|
|
74319
74656
|
}
|
|
74657
|
+
var REQUIRED_REFERENCE_CODES, COMPLETION_FINDING_CODES, REQUIRED_FINDING_CODES;
|
|
74320
74658
|
var init_planning_check_base = __esm(() => {
|
|
74321
74659
|
init_src2();
|
|
74322
74660
|
init_finding_codes2();
|
|
74323
74661
|
init_structural_repair();
|
|
74662
|
+
REQUIRED_REFERENCE_CODES = new Set([
|
|
74663
|
+
FINDING_CODES.L4_FEATURE_NOT_FOUND,
|
|
74664
|
+
FINDING_CODES.L4_PARENT_NOT_FOUND,
|
|
74665
|
+
FINDING_CODES.L4_DEPENDENCY_NOT_FOUND,
|
|
74666
|
+
FINDING_CODES.L4_LINKED_TASK_PARSE_FAILED
|
|
74667
|
+
]);
|
|
74668
|
+
COMPLETION_FINDING_CODES = new Set([
|
|
74669
|
+
FINDING_CODES.L4_ORPHAN_SCENARIOS,
|
|
74670
|
+
FINDING_CODES.L4_UNCOVERED_FEATURE_SCENARIO,
|
|
74671
|
+
FINDING_CODES.L4_SCENARIO_UNVERIFIED,
|
|
74672
|
+
FINDING_CODES.L4_VERIFYING_INCOMPLETE_TASKS,
|
|
74673
|
+
FINDING_CODES.L4_VERDICT_ROWS_MATCH_NO_SCENARIO,
|
|
74674
|
+
FINDING_CODES.L4_MALFORMED_VERDICT_ARTIFACT,
|
|
74675
|
+
FINDING_CODES.L4_EVIDENCE_NOT_RECOVERABLE,
|
|
74676
|
+
FINDING_CODES.L4_DOGFOOD_MISSING,
|
|
74677
|
+
FINDING_CODES.L4_TESTING_VERDICT_STUB,
|
|
74678
|
+
FINDING_CODES.L4_UNCOVERED_TASK_SCENARIO,
|
|
74679
|
+
FINDING_CODES.L3_REVIEW_TESTING_CONTRADICTION,
|
|
74680
|
+
FINDING_CODES.L4_ROLLUP_SUBTASKS_OPEN,
|
|
74681
|
+
FINDING_CODES.L4_ROLLUP_MISSING_ROSTER,
|
|
74682
|
+
FINDING_CODES.L4_ROLLUP_ROSTER_NOT_DECLARED_DEPENDENCY
|
|
74683
|
+
]);
|
|
74684
|
+
REQUIRED_FINDING_CODES = new Set([
|
|
74685
|
+
FINDING_CODES.L1_MARKDOWN_PARSE,
|
|
74686
|
+
FINDING_CODES.L1_SCHEMA_VALIDATION,
|
|
74687
|
+
FINDING_CODES.L2_MISSING_REQUIRED_SECTION,
|
|
74688
|
+
FINDING_CODES.L3_AC_BDD_ERROR,
|
|
74689
|
+
FINDING_CODES.L3_AC_BDD_INVALID,
|
|
74690
|
+
FINDING_CODES.L3_REQUIREMENTS_EMPTY,
|
|
74691
|
+
FINDING_CODES.L3_AC_EMPTY,
|
|
74692
|
+
FINDING_CODES.L3_REQUIRED_SECTION_PLACEHOLDER,
|
|
74693
|
+
...REQUIRED_REFERENCE_CODES,
|
|
74694
|
+
FINDING_CODES.L4_PREREQUISITE_CYCLE,
|
|
74695
|
+
...COMPLETION_FINDING_CODES
|
|
74696
|
+
]);
|
|
74324
74697
|
});
|
|
74325
74698
|
|
|
74326
74699
|
// ../../packages/app/src/services/task-record.ts
|
|
@@ -74901,7 +75274,8 @@ var init_feature_check = __esm(() => {
|
|
|
74901
75274
|
}
|
|
74902
75275
|
const fm = doc2.frontmatterData ?? {};
|
|
74903
75276
|
const status = fm.status ?? "backlog";
|
|
74904
|
-
const
|
|
75277
|
+
const effectiveStatus = options?.asStatus ?? status;
|
|
75278
|
+
const entry = this.resolveMatrixEntry(isGroupFeature(fm), effectiveStatus);
|
|
74905
75279
|
this.runL2(doc2, entry, findings, raw);
|
|
74906
75280
|
this.runL3(doc2, findings, fm);
|
|
74907
75281
|
if (options?.featuresDir) {
|
|
@@ -74912,10 +75286,10 @@ var init_feature_check = __esm(() => {
|
|
|
74912
75286
|
const primaryTasksDir = options?.tasksDir ?? options?.tasksDirs?.[0];
|
|
74913
75287
|
const runDir = options?.runDir ?? (primaryTasksDir ? defaultVerdictRunDir(primaryTasksDir) : undefined);
|
|
74914
75288
|
const taskScanDirs = options?.tasksDirs && options.tasksDirs.length > 0 ? options.tasksDirs : options?.tasksDir ? [options.tasksDir] : [];
|
|
74915
|
-
await this.runL4(doc2, featureId2,
|
|
75289
|
+
await this.runL4(doc2, featureId2, effectiveStatus, taskScanDirs, dogfoodDir, runDir, findings);
|
|
74916
75290
|
return {
|
|
74917
75291
|
id: featureId2,
|
|
74918
|
-
...this.summarizeWithStatus(
|
|
75292
|
+
...this.summarizeWithStatus(effectiveStatus, findings, strict, options?.severityOverrides),
|
|
74919
75293
|
repairs
|
|
74920
75294
|
};
|
|
74921
75295
|
}
|
|
@@ -75119,6 +75493,15 @@ var init_feature_check = __esm(() => {
|
|
|
75119
75493
|
message: `Feature "${featureId2}" is verifying but ${incompleteTasks.length} linked task(s) are not done/cancelled: ${incompleteTasks.join(", ")}`
|
|
75120
75494
|
});
|
|
75121
75495
|
}
|
|
75496
|
+
if (status === "done" && incompleteTasks.length > 0) {
|
|
75497
|
+
findings.push({
|
|
75498
|
+
layer: "L4",
|
|
75499
|
+
code: FINDING_CODES.L4_VERIFYING_INCOMPLETE_TASKS,
|
|
75500
|
+
severity: "error",
|
|
75501
|
+
section: "",
|
|
75502
|
+
message: `Feature "${featureId2}" is done but ${incompleteTasks.length} linked task(s) are not done/cancelled: ${incompleteTasks.join(", ")}`
|
|
75503
|
+
});
|
|
75504
|
+
}
|
|
75122
75505
|
if (status === "verifying" || status === "done") {
|
|
75123
75506
|
const SELF_REFERENTIAL_PATTERNS = [
|
|
75124
75507
|
/\.spur\/workflows\//,
|
|
@@ -76438,22 +76821,6 @@ var init_task_check = __esm(() => {
|
|
|
76438
76821
|
|
|
76439
76822
|
// ../../packages/app/src/services/corpus-check.ts
|
|
76440
76823
|
import { basename as basename4, dirname as dirname10, join as join13, relative, resolve as resolve3 } from "path";
|
|
76441
|
-
function baselineSeverity(e) {
|
|
76442
|
-
return e.severity ?? "error";
|
|
76443
|
-
}
|
|
76444
|
-
function resolveProjectRoot(cwd) {
|
|
76445
|
-
const fs3 = createNodeFileSystem3(cwd);
|
|
76446
|
-
let current = resolve3(cwd);
|
|
76447
|
-
while (true) {
|
|
76448
|
-
if (fs3.exists(join13(current, ".spur", "config.yaml")) || fs3.exists(join13(current, "config", "corpus-baseline.json"))) {
|
|
76449
|
-
return current;
|
|
76450
|
-
}
|
|
76451
|
-
const parent = dirname10(current);
|
|
76452
|
-
if (parent === current)
|
|
76453
|
-
return resolve3(cwd);
|
|
76454
|
-
current = parent;
|
|
76455
|
-
}
|
|
76456
|
-
}
|
|
76457
76824
|
async function loadTaskMatrix(projectRoot) {
|
|
76458
76825
|
const fs3 = createNodeFileSystem3(projectRoot);
|
|
76459
76826
|
const candidates = [join13(projectRoot, ".spur", "tasks", "section-matrix.yaml")];
|
|
@@ -76502,9 +76869,7 @@ async function structuralSweep(projectRoot) {
|
|
|
76502
76869
|
const wbs = fileName.match(/^(\d{4})_.+\.md$/)?.[1];
|
|
76503
76870
|
if (wbs === undefined)
|
|
76504
76871
|
continue;
|
|
76505
|
-
const result = await taskService.check(join13(tasksDir, fileName), wbs
|
|
76506
|
-
severityOverrides: planning.severityOverrides
|
|
76507
|
-
});
|
|
76872
|
+
const result = await taskService.check(join13(tasksDir, fileName), wbs);
|
|
76508
76873
|
for (const finding of result.findings) {
|
|
76509
76874
|
findings.push({
|
|
76510
76875
|
kind: "task",
|
|
@@ -76519,15 +76884,14 @@ async function structuralSweep(projectRoot) {
|
|
|
76519
76884
|
if (await fs3.exists(featuresDir)) {
|
|
76520
76885
|
const featureService = new FeatureCheckService(fs3);
|
|
76521
76886
|
for (const fileName of await fs3.readDir(featuresDir)) {
|
|
76522
|
-
const id = fileName.match(/^([A-Z][
|
|
76887
|
+
const id = fileName.match(/^([A-Z][0-9]*)_.+\.md$/)?.[1];
|
|
76523
76888
|
if (id === undefined)
|
|
76524
76889
|
continue;
|
|
76525
76890
|
const result = await featureService.check(join13(featuresDir, fileName), id, {
|
|
76526
76891
|
featuresDir,
|
|
76527
76892
|
tasksDir: activeTasksDir,
|
|
76528
76893
|
tasksDirs: taskDirs,
|
|
76529
|
-
runDir: fs3.resolve(".spur/run")
|
|
76530
|
-
severityOverrides: planning.severityOverrides
|
|
76894
|
+
runDir: fs3.resolve(".spur/run")
|
|
76531
76895
|
});
|
|
76532
76896
|
for (const finding of result.findings) {
|
|
76533
76897
|
findings.push({
|
|
@@ -76578,6 +76942,43 @@ async function duplicateIds(cwd, taskDirs, featuresDir) {
|
|
|
76578
76942
|
}
|
|
76579
76943
|
return errors6;
|
|
76580
76944
|
}
|
|
76945
|
+
async function runCorpusCheck(cwd, since, report) {
|
|
76946
|
+
const projectRoot = resolveProjectRoot(cwd);
|
|
76947
|
+
const sweep = await structuralSweep(projectRoot);
|
|
76948
|
+
const fog = await ungraduatedFog(projectRoot, { since, report });
|
|
76949
|
+
const findings = [
|
|
76950
|
+
...sweep.findings,
|
|
76951
|
+
...await duplicateIds(projectRoot, sweep.taskDirs, sweep.featuresDir),
|
|
76952
|
+
...fog.map((finding) => ({ ...finding, severity: "warning" }))
|
|
76953
|
+
];
|
|
76954
|
+
const newErrors = findings.filter((finding) => finding.severity === "error");
|
|
76955
|
+
const newWarnings = findings.filter((finding) => finding.severity === "warning");
|
|
76956
|
+
return {
|
|
76957
|
+
observed: findings.length,
|
|
76958
|
+
baselined: 0,
|
|
76959
|
+
newErrors,
|
|
76960
|
+
newWarnings,
|
|
76961
|
+
bySeverity: {
|
|
76962
|
+
error: { observed: newErrors.length, baselined: 0, newCount: newErrors.length },
|
|
76963
|
+
warning: { observed: newWarnings.length, baselined: 0, newCount: newWarnings.length }
|
|
76964
|
+
},
|
|
76965
|
+
duplicateKeys: [],
|
|
76966
|
+
ok: newErrors.length === 0
|
|
76967
|
+
};
|
|
76968
|
+
}
|
|
76969
|
+
function resolveProjectRoot(cwd) {
|
|
76970
|
+
const fs3 = createNodeFileSystem3(cwd);
|
|
76971
|
+
let current = resolve3(cwd);
|
|
76972
|
+
while (true) {
|
|
76973
|
+
if (fs3.exists(join13(current, ".spur", "config.yaml"))) {
|
|
76974
|
+
return current;
|
|
76975
|
+
}
|
|
76976
|
+
const parent = dirname10(current);
|
|
76977
|
+
if (parent === current)
|
|
76978
|
+
return resolve3(cwd);
|
|
76979
|
+
current = parent;
|
|
76980
|
+
}
|
|
76981
|
+
}
|
|
76581
76982
|
async function git(cwd, args) {
|
|
76582
76983
|
try {
|
|
76583
76984
|
const p = await new NodeProcessExecutor3().run({ command: "git", args, cwd, rejectOnError: false });
|
|
@@ -76742,71 +77143,6 @@ async function ungraduatedFog(cwd = process.cwd(), opts = {}) {
|
|
|
76742
77143
|
report(`corpus-check: fog check evaluated ${spec} \u2014 ${maps} map(s) inspected.`);
|
|
76743
77144
|
return errors6;
|
|
76744
77145
|
}
|
|
76745
|
-
async function collectObservedFindings(projectRoot, since) {
|
|
76746
|
-
const sweep = await structuralSweep(projectRoot);
|
|
76747
|
-
const fogErrors = await ungraduatedFog(projectRoot, { since, report: () => {} });
|
|
76748
|
-
return [...sweep.findings, ...await duplicateIds(projectRoot, sweep.taskDirs, sweep.featuresDir), ...fogErrors];
|
|
76749
|
-
}
|
|
76750
|
-
function duplicateBaselineKeys(baseline) {
|
|
76751
|
-
const counts = new Map;
|
|
76752
|
-
for (const e of baseline.entries)
|
|
76753
|
-
counts.set(key(e), (counts.get(key(e)) ?? 0) + 1);
|
|
76754
|
-
return [...counts].filter(([, n2]) => n2 > 1).map(([k, count3]) => ({ key: k, count: count3 })).sort((a2, b) => b.count - a2.count || a2.key.localeCompare(b.key));
|
|
76755
|
-
}
|
|
76756
|
-
function reconcileBaseline(observed, baseline) {
|
|
76757
|
-
const accepted = new Map(baseline.entries.map((e) => [key(e), baselineSeverity(e)]));
|
|
76758
|
-
const unexpected = observed.filter((e) => accepted.get(key(e)) !== e.severity);
|
|
76759
|
-
const newErrors = unexpected.filter((e) => e.severity === "error");
|
|
76760
|
-
const newWarnings = unexpected.filter((e) => e.severity === "warning");
|
|
76761
|
-
const bySeverity = {
|
|
76762
|
-
error: {
|
|
76763
|
-
observed: observed.filter((o3) => o3.severity === "error").length,
|
|
76764
|
-
baselined: baseline.entries.filter((e) => baselineSeverity(e) === "error").length,
|
|
76765
|
-
newCount: newErrors.length
|
|
76766
|
-
},
|
|
76767
|
-
warning: {
|
|
76768
|
-
observed: observed.filter((o3) => o3.severity === "warning").length,
|
|
76769
|
-
baselined: baseline.entries.filter((e) => baselineSeverity(e) === "warning").length,
|
|
76770
|
-
newCount: newWarnings.length
|
|
76771
|
-
}
|
|
76772
|
-
};
|
|
76773
|
-
const duplicateKeys = duplicateBaselineKeys(baseline);
|
|
76774
|
-
return {
|
|
76775
|
-
observed: observed.length,
|
|
76776
|
-
baselined: baseline.entries.length,
|
|
76777
|
-
newErrors,
|
|
76778
|
-
newWarnings,
|
|
76779
|
-
bySeverity,
|
|
76780
|
-
duplicateKeys,
|
|
76781
|
-
ok: unexpected.length === 0 && duplicateKeys.length === 0
|
|
76782
|
-
};
|
|
76783
|
-
}
|
|
76784
|
-
async function runCorpusCheck(cwd, since) {
|
|
76785
|
-
const projectRoot = resolveProjectRoot(cwd);
|
|
76786
|
-
const baselineFile = join13(projectRoot, "config", "corpus-baseline.json");
|
|
76787
|
-
let baseline = { entries: [] };
|
|
76788
|
-
if (await Bun.file(baselineFile).exists()) {
|
|
76789
|
-
baseline = await Bun.file(baselineFile).json();
|
|
76790
|
-
}
|
|
76791
|
-
const observed = await collectObservedFindings(projectRoot, since);
|
|
76792
|
-
return reconcileBaseline(observed, baseline);
|
|
76793
|
-
}
|
|
76794
|
-
async function loadAcceptedFindings(cwd) {
|
|
76795
|
-
const projectRoot = resolveProjectRoot(cwd);
|
|
76796
|
-
const baselineFile = join13(projectRoot, "config", "corpus-baseline.json");
|
|
76797
|
-
const accepted = new Map;
|
|
76798
|
-
try {
|
|
76799
|
-
if (await Bun.file(baselineFile).exists()) {
|
|
76800
|
-
const baseline = await Bun.file(baselineFile).json();
|
|
76801
|
-
if (Array.isArray(baseline?.entries)) {
|
|
76802
|
-
for (const e of baseline.entries) {
|
|
76803
|
-
accepted.set(key(e), baselineSeverity(e));
|
|
76804
|
-
}
|
|
76805
|
-
}
|
|
76806
|
-
}
|
|
76807
|
-
} catch {}
|
|
76808
|
-
return accepted;
|
|
76809
|
-
}
|
|
76810
77146
|
var FOG_HEADING, OUT_OF_SCOPE_HEADING, FEATURE_ID, DEFAULT_BRANCHES;
|
|
76811
77147
|
var init_corpus_check = __esm(() => {
|
|
76812
77148
|
init_loader();
|
|
@@ -80027,32 +80363,13 @@ async function refreshHistoryRollups(db2) {
|
|
|
80027
80363
|
if (await historyBoardRollupsFresh(db2)) {
|
|
80028
80364
|
return { status: "unchanged", historyVersion: await historyBoardHistoryVersion(db2), cacheWasteSteps: 0 };
|
|
80029
80365
|
}
|
|
80366
|
+
await refreshHistoryBoardRollupsIncremental(db2);
|
|
80030
80367
|
const historyVersion = await historyBoardHistoryVersion(db2);
|
|
80031
|
-
|
|
80032
|
-
|
|
80033
|
-
toolRollup(db2, ALL_HISTORY2),
|
|
80034
|
-
loops(db2, ALL_HISTORY2),
|
|
80035
|
-
sourceSummary(db2, ALL_HISTORY2),
|
|
80036
|
-
topStepsByTokens(db2, ALL_HISTORY2, RANK_DEPTH2),
|
|
80037
|
-
topStepsByDuration(db2, ALL_HISTORY2, RANK_DEPTH2),
|
|
80038
|
-
cacheWasteAggregate(db2, ALL_HISTORY2),
|
|
80039
|
-
topCacheWasteSteps(db2, ALL_HISTORY2, RANK_DEPTH2),
|
|
80040
|
-
skillCallRollup(db2)
|
|
80041
|
-
]);
|
|
80042
|
-
await replaceHistoryBoardRollups(db2, {
|
|
80043
|
-
historyVersion,
|
|
80044
|
-
messageRows,
|
|
80045
|
-
toolRows,
|
|
80046
|
-
loopRows,
|
|
80047
|
-
sourceRows,
|
|
80048
|
-
tokenSteps,
|
|
80049
|
-
durationSteps,
|
|
80050
|
-
cacheWasteSteps,
|
|
80051
|
-
skill5m: skillRows
|
|
80052
|
-
});
|
|
80368
|
+
await recordHistoryBoardRollupMeta(db2, historyVersion);
|
|
80369
|
+
const waste = await cacheWasteAggregate(db2, ALL_HISTORY2);
|
|
80053
80370
|
return { status: "refreshed", historyVersion, cacheWasteSteps: waste?.steps ?? 0 };
|
|
80054
80371
|
}
|
|
80055
|
-
var ALL_HISTORY2,
|
|
80372
|
+
var ALL_HISTORY2, HistorySchemaVersionMismatchError;
|
|
80056
80373
|
var init_history_analysis_service = __esm(() => {
|
|
80057
80374
|
init_src2();
|
|
80058
80375
|
ALL_HISTORY2 = {
|
|
@@ -80258,7 +80575,7 @@ class MockHistoryBoardService {
|
|
|
80258
80575
|
const totalTokensWithCache = totalBilled + totalCacheSaved;
|
|
80259
80576
|
const cacheSavedPercent = totalTokensWithCache > 0 ? Math.round(totalCacheSaved / totalTokensWithCache * 100) : 0;
|
|
80260
80577
|
const errorRate = totalTools > 0 ? Math.round(totalErrors / totalTools * 1000) / 10 : 0;
|
|
80261
|
-
const bucket = filter?.bucket === "auto" || filter?.bucket === undefined ? filter?.range === "1h" ? "1m" : filter?.range === "4h" ? "
|
|
80578
|
+
const bucket = filter?.bucket === "auto" || filter?.bucket === undefined ? filter?.range === "1h" ? "1m" : filter?.range === "4h" ? "1m" : filter?.range === "24h" ? "10m" : filter?.range === "7d" ? "30m" : "1d" : filter.bucket;
|
|
80262
80579
|
const bucketInterval = {
|
|
80263
80580
|
"1m": 1 * 60000,
|
|
80264
80581
|
"3m": 3 * 60000,
|
|
@@ -80270,6 +80587,14 @@ class MockHistoryBoardService {
|
|
|
80270
80587
|
"1d": 86400000
|
|
80271
80588
|
}[bucket];
|
|
80272
80589
|
const dimension = filter?.dimension ?? "model";
|
|
80590
|
+
const ensureBucket = (map2, key2) => {
|
|
80591
|
+
let entry = map2[key2];
|
|
80592
|
+
if (!entry) {
|
|
80593
|
+
entry = { total: 0, cacheRead: 0, output: 0, series: {} };
|
|
80594
|
+
map2[key2] = entry;
|
|
80595
|
+
}
|
|
80596
|
+
return entry;
|
|
80597
|
+
};
|
|
80273
80598
|
const buckets = {};
|
|
80274
80599
|
const modelBuckets = {};
|
|
80275
80600
|
const sourceBuckets = {};
|
|
@@ -80277,35 +80602,35 @@ class MockHistoryBoardService {
|
|
|
80277
80602
|
const skillBuckets = {};
|
|
80278
80603
|
for (const s3 of matching) {
|
|
80279
80604
|
const bKey = new Date(Math.floor(s3.start / bucketInterval) * bucketInterval).toISOString();
|
|
80280
|
-
|
|
80281
|
-
|
|
80282
|
-
|
|
80283
|
-
|
|
80284
|
-
|
|
80285
|
-
|
|
80286
|
-
|
|
80287
|
-
|
|
80288
|
-
|
|
80289
|
-
|
|
80290
|
-
|
|
80291
|
-
|
|
80292
|
-
|
|
80293
|
-
|
|
80294
|
-
|
|
80295
|
-
|
|
80296
|
-
|
|
80297
|
-
|
|
80298
|
-
|
|
80299
|
-
|
|
80300
|
-
|
|
80301
|
-
|
|
80605
|
+
const b = ensureBucket(buckets, bKey);
|
|
80606
|
+
const mb = ensureBucket(modelBuckets, bKey);
|
|
80607
|
+
const sb = ensureBucket(sourceBuckets, bKey);
|
|
80608
|
+
const tb = ensureBucket(toolBuckets, bKey);
|
|
80609
|
+
const kb = ensureBucket(skillBuckets, bKey);
|
|
80610
|
+
b.total += s3.tokens.billedTokens;
|
|
80611
|
+
b.cacheRead += s3.tokens.cacheReadTokens;
|
|
80612
|
+
b.output += s3.tokens.outputTokens;
|
|
80613
|
+
mb.total += s3.tokens.billedTokens;
|
|
80614
|
+
mb.cacheRead += s3.tokens.cacheReadTokens;
|
|
80615
|
+
mb.output += s3.tokens.outputTokens;
|
|
80616
|
+
sb.total += s3.tokens.billedTokens;
|
|
80617
|
+
sb.cacheRead += s3.tokens.cacheReadTokens;
|
|
80618
|
+
sb.output += s3.tokens.outputTokens;
|
|
80619
|
+
tb.total += s3.tokens.billedTokens;
|
|
80620
|
+
tb.cacheRead += s3.tokens.cacheReadTokens;
|
|
80621
|
+
tb.output += s3.tokens.outputTokens;
|
|
80622
|
+
kb.total += s3.tokens.billedTokens;
|
|
80623
|
+
kb.cacheRead += s3.tokens.cacheReadTokens;
|
|
80624
|
+
kb.output += s3.tokens.outputTokens;
|
|
80625
|
+
mb.series[s3.model] = (mb.series[s3.model] ?? 0) + s3.tokens.billedTokens;
|
|
80626
|
+
sb.series[s3.source] = (sb.series[s3.source] ?? 0) + s3.tokens.billedTokens;
|
|
80302
80627
|
const toolWeight = Object.values(s3.toolMix).reduce((sum, count3) => sum + count3, 0);
|
|
80303
80628
|
for (const [tool, count3] of Object.entries(s3.toolMix)) {
|
|
80304
|
-
|
|
80629
|
+
tb.series[tool] = (tb.series[tool] ?? 0) + (toolWeight > 0 ? s3.tokens.billedTokens * count3 / toolWeight : 0);
|
|
80305
80630
|
}
|
|
80306
80631
|
const skillWeight = Object.values(s3.skillMix).reduce((sum, count3) => sum + count3, 0);
|
|
80307
80632
|
for (const [skill, count3] of Object.entries(s3.skillMix)) {
|
|
80308
|
-
|
|
80633
|
+
kb.series[skill] = (kb.series[skill] ?? 0) + (skillWeight > 0 ? s3.tokens.billedTokens * count3 / skillWeight : 0);
|
|
80309
80634
|
}
|
|
80310
80635
|
const dimensions = dimension === "model" ? [[s3.model, s3.tokens.billedTokens]] : dimension === "source" ? [[s3.source, s3.tokens.billedTokens]] : Object.entries(dimension === "tool" ? s3.toolMix : s3.skillMix).map(([key2, count3]) => [
|
|
80311
80636
|
key2,
|
|
@@ -80314,15 +80639,17 @@ class MockHistoryBoardService {
|
|
|
80314
80639
|
const weight = dimensions.reduce((sum, [, count3]) => sum + count3, 0);
|
|
80315
80640
|
for (const [key2, count3] of dimensions) {
|
|
80316
80641
|
const tokens = dimension === "model" || dimension === "source" ? count3 : weight > 0 ? s3.tokens.billedTokens * count3 / weight : 0;
|
|
80317
|
-
|
|
80642
|
+
b.series[key2] = (b.series[key2] ?? 0) + tokens;
|
|
80318
80643
|
}
|
|
80319
80644
|
}
|
|
80320
80645
|
const toTimeSeries = (map2) => Object.entries(map2).sort(([a2], [b]) => a2.localeCompare(b)).map(([bKey, bVal]) => {
|
|
80321
80646
|
const denom = bVal.total + bVal.cacheRead;
|
|
80322
80647
|
const ratio = denom > 0 ? Math.round(bVal.cacheRead / denom * 100) : 0;
|
|
80648
|
+
const gainRatio = denom > 0 ? Math.round(bVal.output / denom * 1000) / 10 : 0;
|
|
80323
80649
|
return {
|
|
80324
80650
|
bucketStart: bKey,
|
|
80325
80651
|
cacheHitRatio: ratio,
|
|
80652
|
+
gainRatio,
|
|
80326
80653
|
series: bVal.series
|
|
80327
80654
|
};
|
|
80328
80655
|
});
|
|
@@ -81239,7 +81566,7 @@ function resolveBucket(bucket, range = "4h") {
|
|
|
81239
81566
|
if (range === "1h")
|
|
81240
81567
|
return "1m";
|
|
81241
81568
|
if (range === "4h")
|
|
81242
|
-
return "
|
|
81569
|
+
return "1m";
|
|
81243
81570
|
if (range === "24h")
|
|
81244
81571
|
return "10m";
|
|
81245
81572
|
if (range === "7d")
|
|
@@ -81364,9 +81691,12 @@ function projectSummary(rows, extras) {
|
|
|
81364
81691
|
const useCalls = totalBucketTokens === 0;
|
|
81365
81692
|
const timeSeries = new Map;
|
|
81366
81693
|
for (const row of rows.buckets) {
|
|
81367
|
-
const point = timeSeries.get(row.bucketStart) ?? { cacheRead: 0, billed: 0, series: {} };
|
|
81368
|
-
const
|
|
81694
|
+
const point = timeSeries.get(row.bucketStart) ?? { cacheRead: 0, billed: 0, output: 0, series: {} };
|
|
81695
|
+
const fresh = row.freshInputTokens ?? 0;
|
|
81696
|
+
const output2 = row.outputTokens ?? 0;
|
|
81697
|
+
const billed = useCalls ? row.calls ?? 0 : fresh + output2;
|
|
81369
81698
|
point.billed += billed;
|
|
81699
|
+
point.output += output2;
|
|
81370
81700
|
point.cacheRead += row.cacheReadTokens ?? 0;
|
|
81371
81701
|
point.series[row.key] = (point.series[row.key] ?? 0) + billed;
|
|
81372
81702
|
timeSeries.set(row.bucketStart, point);
|
|
@@ -81400,11 +81730,15 @@ function projectSummary(rows, extras) {
|
|
|
81400
81730
|
toolCallsCount: rows.toolCalls,
|
|
81401
81731
|
errorRate: rows.toolCalls > 0 ? Math.round(rows.toolErrors / rows.toolCalls * 1000) / 10 : 0
|
|
81402
81732
|
},
|
|
81403
|
-
timeSeries: Array.from(timeSeries.entries()).map(([bucketStart, point]) =>
|
|
81404
|
-
|
|
81405
|
-
|
|
81406
|
-
|
|
81407
|
-
|
|
81733
|
+
timeSeries: Array.from(timeSeries.entries()).map(([bucketStart, point]) => {
|
|
81734
|
+
const totalTokens = point.billed + point.cacheRead;
|
|
81735
|
+
return {
|
|
81736
|
+
bucketStart,
|
|
81737
|
+
cacheHitRatio: totalTokens > 0 ? Math.round(point.cacheRead / totalTokens * 100) : 0,
|
|
81738
|
+
gainRatio: totalTokens > 0 ? Math.round(point.output / totalTokens * 1000) / 10 : 0,
|
|
81739
|
+
series: point.series
|
|
81740
|
+
};
|
|
81741
|
+
}),
|
|
81408
81742
|
topModels: toTopItems(rows.models, false),
|
|
81409
81743
|
topSources: toTopItems(rows.sources, true),
|
|
81410
81744
|
topTools: topToolRows.map((row) => ({
|
|
@@ -81546,18 +81880,25 @@ function projectSkillTimeSeries(buckets) {
|
|
|
81546
81880
|
const useCalls = totalTokens === 0;
|
|
81547
81881
|
const byBucket = new Map;
|
|
81548
81882
|
for (const row of buckets) {
|
|
81549
|
-
const point = byBucket.get(row.bucketStart) ?? { cacheRead: 0, billed: 0, series: {} };
|
|
81550
|
-
const
|
|
81883
|
+
const point = byBucket.get(row.bucketStart) ?? { cacheRead: 0, billed: 0, output: 0, series: {} };
|
|
81884
|
+
const fresh = row.freshInputTokens ?? 0;
|
|
81885
|
+
const output2 = row.outputTokens ?? 0;
|
|
81886
|
+
const val = useCalls ? row.calls ?? 0 : fresh + output2;
|
|
81551
81887
|
point.billed += val;
|
|
81888
|
+
point.output += output2;
|
|
81552
81889
|
point.cacheRead += row.cacheReadTokens ?? 0;
|
|
81553
81890
|
point.series[row.key] = (point.series[row.key] ?? 0) + val;
|
|
81554
81891
|
byBucket.set(row.bucketStart, point);
|
|
81555
81892
|
}
|
|
81556
|
-
return Array.from(byBucket.entries()).sort(([a2], [b]) => a2.localeCompare(b)).map(([bucketStart, point]) =>
|
|
81557
|
-
|
|
81558
|
-
|
|
81559
|
-
|
|
81560
|
-
|
|
81893
|
+
return Array.from(byBucket.entries()).sort(([a2], [b]) => a2.localeCompare(b)).map(([bucketStart, point]) => {
|
|
81894
|
+
const totalTokens2 = point.billed + point.cacheRead;
|
|
81895
|
+
return {
|
|
81896
|
+
bucketStart,
|
|
81897
|
+
cacheHitRatio: totalTokens2 > 0 ? Math.round(point.cacheRead / totalTokens2 * 100) : 0,
|
|
81898
|
+
gainRatio: totalTokens2 > 0 ? Math.round(point.output / totalTokens2 * 1000) / 10 : 0,
|
|
81899
|
+
series: point.series
|
|
81900
|
+
};
|
|
81901
|
+
});
|
|
81561
81902
|
}
|
|
81562
81903
|
function projectPreviousKpis(rows) {
|
|
81563
81904
|
const totalBilledTokens = rows.models.reduce((sum, row) => sum + row.freshInputTokens + row.outputTokens, 0);
|
|
@@ -82430,7 +82771,10 @@ class LiveHistoryBoardService {
|
|
|
82430
82771
|
}
|
|
82431
82772
|
const sel = toArtifactSelector(filter);
|
|
82432
82773
|
const rollupsFresh = await historyBoardRollupsFresh(db2);
|
|
82433
|
-
const
|
|
82774
|
+
const hasRollupRows = await hasHistoryBoardRollupRows(db2, "history_board_loop_findings");
|
|
82775
|
+
const useRollup = rollupsFresh || hasRollupRows;
|
|
82776
|
+
const effectiveSel = rollupsFresh ? sel : boundStaleSelector(sel);
|
|
82777
|
+
const [loopRows, cacheWasteRows, sessionRows, largeSteps, slowStepsRows, modelCompRows] = useRollup ? await Promise.all([
|
|
82434
82778
|
historyBoardLoopsFromRollup(db2, sel, 100),
|
|
82435
82779
|
historyBoardRankedStepsFromRollup(db2, sel, "cache-waste", 10),
|
|
82436
82780
|
historyBoardHeavySessionsFromRollup(db2, sel, 5),
|
|
@@ -82438,57 +82782,69 @@ class LiveHistoryBoardService {
|
|
|
82438
82782
|
historyBoardRankedStepsFromRollup(db2, sel, "duration", 10),
|
|
82439
82783
|
historyBoardModelComparisonFromRollup(db2, sel)
|
|
82440
82784
|
]) : await Promise.all([
|
|
82441
|
-
loops(db2,
|
|
82442
|
-
topCacheWasteSteps(db2,
|
|
82443
|
-
bySession(db2,
|
|
82444
|
-
topStepsByTokens(db2,
|
|
82445
|
-
topStepsByDuration(db2,
|
|
82446
|
-
modelComparison(db2,
|
|
82785
|
+
loops(db2, effectiveSel),
|
|
82786
|
+
topCacheWasteSteps(db2, effectiveSel, 10),
|
|
82787
|
+
bySession(db2, effectiveSel, 5),
|
|
82788
|
+
topStepsByTokens(db2, effectiveSel, 10),
|
|
82789
|
+
topStepsByDuration(db2, effectiveSel, 10),
|
|
82790
|
+
modelComparison(db2, effectiveSel)
|
|
82447
82791
|
]);
|
|
82448
|
-
const
|
|
82449
|
-
|
|
82450
|
-
|
|
82451
|
-
|
|
82452
|
-
|
|
82453
|
-
|
|
82454
|
-
|
|
82455
|
-
|
|
82456
|
-
|
|
82457
|
-
|
|
82458
|
-
|
|
82459
|
-
|
|
82460
|
-
|
|
82461
|
-
|
|
82462
|
-
|
|
82463
|
-
|
|
82464
|
-
|
|
82465
|
-
|
|
82466
|
-
|
|
82467
|
-
category
|
|
82468
|
-
|
|
82469
|
-
|
|
82470
|
-
|
|
82471
|
-
|
|
82472
|
-
|
|
82473
|
-
|
|
82474
|
-
|
|
82475
|
-
|
|
82476
|
-
|
|
82477
|
-
|
|
82478
|
-
|
|
82479
|
-
|
|
82480
|
-
|
|
82481
|
-
|
|
82482
|
-
|
|
82483
|
-
|
|
82484
|
-
|
|
82485
|
-
|
|
82486
|
-
|
|
82487
|
-
|
|
82488
|
-
|
|
82489
|
-
|
|
82490
|
-
|
|
82792
|
+
const EAGER_LOOP_DETAIL_LIMIT = 10;
|
|
82793
|
+
const loopFindings = await Promise.all(loopRows.map(async (l, idx) => {
|
|
82794
|
+
let repeatedCalls;
|
|
82795
|
+
let avgTokens = 250;
|
|
82796
|
+
if (idx < EAGER_LOOP_DETAIL_LIMIT) {
|
|
82797
|
+
try {
|
|
82798
|
+
const callRows = await loopRepeatedCallsQuery(db2, {
|
|
82799
|
+
source: l.source,
|
|
82800
|
+
sessionId: l.sessionId,
|
|
82801
|
+
toolName: l.toolName,
|
|
82802
|
+
argsDigest: l.argsDigest,
|
|
82803
|
+
model: l.model,
|
|
82804
|
+
limit: 50
|
|
82805
|
+
});
|
|
82806
|
+
if (callRows.length > 0) {
|
|
82807
|
+
const totalSampleTokens = callRows.reduce((acc, r) => acc + (r.inputTokens ?? 0) + (r.outputTokens ?? 0), 0);
|
|
82808
|
+
avgTokens = Math.max(1, Math.round(totalSampleTokens / callRows.length));
|
|
82809
|
+
}
|
|
82810
|
+
repeatedCalls = callRows.map((row) => {
|
|
82811
|
+
const category = toolCategory(row.toolName);
|
|
82812
|
+
const freshInputTokens = row.inputTokens ?? 0;
|
|
82813
|
+
const cacheReadTokens = row.cacheReadTokens ?? 0;
|
|
82814
|
+
const outputTokens = row.outputTokens ?? 0;
|
|
82815
|
+
const billedTokens = freshInputTokens + outputTokens;
|
|
82816
|
+
return {
|
|
82817
|
+
seq: row.toolSeq,
|
|
82818
|
+
toolSeq: row.toolSeq,
|
|
82819
|
+
ts: row.ts,
|
|
82820
|
+
toolName: row.toolName,
|
|
82821
|
+
category,
|
|
82822
|
+
status: row.status === "ok" ? "ok" : row.status === "error" ? "error" : "unknown",
|
|
82823
|
+
durationMs: row.durationMs ?? null,
|
|
82824
|
+
durationSource: row.durationMs !== null ? "measured" : "unmeasured",
|
|
82825
|
+
resultBytes: row.resultBytes ?? null,
|
|
82826
|
+
argsRaw: row.argsRaw ?? null,
|
|
82827
|
+
argsDigest: row.argsDigest ?? null,
|
|
82828
|
+
errorText: row.errorText ?? null,
|
|
82829
|
+
callId: row.callId ?? null,
|
|
82830
|
+
messageHash: row.messageHash,
|
|
82831
|
+
sessionId: row.sessionId,
|
|
82832
|
+
source: row.source,
|
|
82833
|
+
model: row.model ?? null,
|
|
82834
|
+
tokens: {
|
|
82835
|
+
billedTokens,
|
|
82836
|
+
freshInputTokens,
|
|
82837
|
+
cacheReadTokens,
|
|
82838
|
+
outputTokens,
|
|
82839
|
+
cacheSavedTokens: 0
|
|
82840
|
+
}
|
|
82841
|
+
};
|
|
82842
|
+
});
|
|
82843
|
+
} catch {
|
|
82844
|
+
repeatedCalls = [];
|
|
82845
|
+
}
|
|
82491
82846
|
}
|
|
82847
|
+
const wastedTokens = Math.max(0, l.repeats - 1) * avgTokens;
|
|
82492
82848
|
return {
|
|
82493
82849
|
tool: l.toolName,
|
|
82494
82850
|
argsHint: l.argsDigest ?? "repeated execution",
|
|
@@ -82496,7 +82852,7 @@ class LiveHistoryBoardService {
|
|
|
82496
82852
|
repeats: l.repeats,
|
|
82497
82853
|
fromSeq: l.firstSeq,
|
|
82498
82854
|
toSeq: l.lastSeq,
|
|
82499
|
-
wastedTokens
|
|
82855
|
+
wastedTokens,
|
|
82500
82856
|
repeatedCalls
|
|
82501
82857
|
};
|
|
82502
82858
|
}));
|
|
@@ -83200,6 +83556,14 @@ class HistoryService {
|
|
|
83200
83556
|
const db2 = await this.ctx.getDb();
|
|
83201
83557
|
return resetHistoryTables(db2);
|
|
83202
83558
|
}
|
|
83559
|
+
async maintain(opts = {}) {
|
|
83560
|
+
const db2 = await this.ctx.getDb();
|
|
83561
|
+
const dbPath = this.ctx.cwd ? join15(this.ctx.cwd, ".spur", "spur.db") : undefined;
|
|
83562
|
+
return maintainDatabase(db2, {
|
|
83563
|
+
...opts,
|
|
83564
|
+
...dbPath && opts.dbPath === undefined ? { dbPath } : {}
|
|
83565
|
+
});
|
|
83566
|
+
}
|
|
83203
83567
|
async analyze(selector, opts = {}) {
|
|
83204
83568
|
const top = opts.top ?? 20;
|
|
83205
83569
|
const db2 = await this.ctx.getDb();
|
|
@@ -83353,6 +83717,12 @@ class HistoryService {
|
|
|
83353
83717
|
attribution2.ambiguousEvidence += sourceAttribution.ambiguousEvidence;
|
|
83354
83718
|
}
|
|
83355
83719
|
}
|
|
83720
|
+
if (opts.dryRun !== true && opts.skipRollupRefresh !== true) {
|
|
83721
|
+
const db2 = await this.ctx.getDb();
|
|
83722
|
+
try {
|
|
83723
|
+
await refreshHistoryRollups(db2);
|
|
83724
|
+
} catch {}
|
|
83725
|
+
}
|
|
83356
83726
|
return { entries, exitCode: computeExitCode(entries), warnings, attribution: attribution2 };
|
|
83357
83727
|
}
|
|
83358
83728
|
async daily(opts = {}) {
|
|
@@ -90317,7 +90687,6 @@ var init_http_request = __esm(() => {
|
|
|
90317
90687
|
|
|
90318
90688
|
// ../../packages/app/src/workflow/composition-baseline.ts
|
|
90319
90689
|
import crypto3 from "crypto";
|
|
90320
|
-
import { resolve as resolve11 } from "path";
|
|
90321
90690
|
function canonicalJsonStringify(obj) {
|
|
90322
90691
|
if (obj === null || typeof obj !== "object") {
|
|
90323
90692
|
return JSON.stringify(obj);
|
|
@@ -90379,131 +90748,7 @@ function extractResolvedWorkflowFacts(workflow) {
|
|
|
90379
90748
|
actions
|
|
90380
90749
|
};
|
|
90381
90750
|
}
|
|
90382
|
-
|
|
90383
|
-
const fs3 = options.fileSystem ?? createNodeFileSystem3();
|
|
90384
|
-
const baselineFile = options.baselinePath ?? resolve11(options.projectRoot, "config/workflow-composition-baseline.json");
|
|
90385
|
-
let baseline = options.baseline;
|
|
90386
|
-
if (!baseline) {
|
|
90387
|
-
if (!await fs3.exists(baselineFile)) {
|
|
90388
|
-
return {
|
|
90389
|
-
pass: false,
|
|
90390
|
-
errors: [`Baseline file not found at ${baselineFile}`],
|
|
90391
|
-
diffs: []
|
|
90392
|
-
};
|
|
90393
|
-
}
|
|
90394
|
-
const content = await fs3.readFile(baselineFile);
|
|
90395
|
-
try {
|
|
90396
|
-
baseline = JSON.parse(content);
|
|
90397
|
-
} catch (e) {
|
|
90398
|
-
return {
|
|
90399
|
-
pass: false,
|
|
90400
|
-
errors: [`Failed to parse baseline JSON: ${e instanceof Error ? e.message : String(e)}`],
|
|
90401
|
-
diffs: []
|
|
90402
|
-
};
|
|
90403
|
-
}
|
|
90404
|
-
}
|
|
90405
|
-
const errors6 = [];
|
|
90406
|
-
const diffs = [];
|
|
90407
|
-
for (const [name, expected] of Object.entries(baseline.workflows)) {
|
|
90408
|
-
const defPath = resolve11(options.projectRoot, expected.definition);
|
|
90409
|
-
if (!await fs3.exists(defPath)) {
|
|
90410
|
-
errors6.push(`Workflow definition for "${name}" missing at ${expected.definition}`);
|
|
90411
|
-
diffs.push({
|
|
90412
|
-
workflow: name,
|
|
90413
|
-
field: "definition",
|
|
90414
|
-
expected: expected.definition,
|
|
90415
|
-
actual: null
|
|
90416
|
-
});
|
|
90417
|
-
continue;
|
|
90418
|
-
}
|
|
90419
|
-
let def;
|
|
90420
|
-
try {
|
|
90421
|
-
def = await loadWorkflowDef(defPath, { validateSchema: false });
|
|
90422
|
-
} catch (e) {
|
|
90423
|
-
errors6.push(`Failed to load workflow definition "${name}": ${e instanceof Error ? e.message : String(e)}`);
|
|
90424
|
-
continue;
|
|
90425
|
-
}
|
|
90426
|
-
const facts = extractResolvedWorkflowFacts(def);
|
|
90427
|
-
const sortedExpectedTerminals = [...expected.terminalStates].sort();
|
|
90428
|
-
const sortedActualTerminals = [...facts.terminalStates].sort();
|
|
90429
|
-
if (JSON.stringify(sortedExpectedTerminals) !== JSON.stringify(sortedActualTerminals)) {
|
|
90430
|
-
errors6.push(`Workflow "${name}" terminalStates mismatch`);
|
|
90431
|
-
diffs.push({
|
|
90432
|
-
workflow: name,
|
|
90433
|
-
field: "terminalStates",
|
|
90434
|
-
expected: expected.terminalStates,
|
|
90435
|
-
actual: facts.terminalStates
|
|
90436
|
-
});
|
|
90437
|
-
}
|
|
90438
|
-
const sortedExpectedModelQueries = [...expected.modelQueries].sort();
|
|
90439
|
-
const sortedActualModelQueries = [...facts.modelQueries].sort();
|
|
90440
|
-
if (JSON.stringify(sortedExpectedModelQueries) !== JSON.stringify(sortedActualModelQueries)) {
|
|
90441
|
-
errors6.push(`Workflow "${name}" modelQueries mismatch`);
|
|
90442
|
-
diffs.push({
|
|
90443
|
-
workflow: name,
|
|
90444
|
-
field: "modelQueries",
|
|
90445
|
-
expected: expected.modelQueries,
|
|
90446
|
-
actual: facts.modelQueries
|
|
90447
|
-
});
|
|
90448
|
-
}
|
|
90449
|
-
const expectedActionKeys = Object.keys(expected.actions).sort();
|
|
90450
|
-
const actualActionKeys = Object.keys(facts.actions).sort();
|
|
90451
|
-
for (const actionKey of expectedActionKeys) {
|
|
90452
|
-
if (!facts.actions[actionKey]) {
|
|
90453
|
-
errors6.push(`Workflow "${name}" action "${actionKey}" is listed in baseline but missing from live definition`);
|
|
90454
|
-
diffs.push({
|
|
90455
|
-
workflow: name,
|
|
90456
|
-
field: `actions.${actionKey}`,
|
|
90457
|
-
expected: expected.actions[actionKey],
|
|
90458
|
-
actual: null
|
|
90459
|
-
});
|
|
90460
|
-
} else {
|
|
90461
|
-
const expAction = expected.actions[actionKey];
|
|
90462
|
-
const actAction = facts.actions[actionKey];
|
|
90463
|
-
if (expAction && actAction) {
|
|
90464
|
-
if (expAction.kind !== actAction.kind) {
|
|
90465
|
-
errors6.push(`Workflow "${name}" action "${actionKey}" kind mismatch: expected ${expAction.kind}, got ${actAction.kind}`);
|
|
90466
|
-
diffs.push({
|
|
90467
|
-
workflow: name,
|
|
90468
|
-
field: `actions.${actionKey}.kind`,
|
|
90469
|
-
expected: expAction.kind,
|
|
90470
|
-
actual: actAction.kind
|
|
90471
|
-
});
|
|
90472
|
-
}
|
|
90473
|
-
if (expAction.invocation !== actAction.invocation) {
|
|
90474
|
-
errors6.push(`Workflow "${name}" action "${actionKey}" invocation mismatch`);
|
|
90475
|
-
diffs.push({
|
|
90476
|
-
workflow: name,
|
|
90477
|
-
field: `actions.${actionKey}.invocation`,
|
|
90478
|
-
expected: expAction.invocation,
|
|
90479
|
-
actual: actAction.invocation
|
|
90480
|
-
});
|
|
90481
|
-
}
|
|
90482
|
-
}
|
|
90483
|
-
}
|
|
90484
|
-
}
|
|
90485
|
-
for (const actionKey of actualActionKeys) {
|
|
90486
|
-
if (!expected.actions[actionKey]) {
|
|
90487
|
-
errors6.push(`Workflow "${name}" action "${actionKey}" present in live definition but unlisted in baseline`);
|
|
90488
|
-
diffs.push({
|
|
90489
|
-
workflow: name,
|
|
90490
|
-
field: `actions.${actionKey}`,
|
|
90491
|
-
expected: null,
|
|
90492
|
-
actual: facts.actions[actionKey]
|
|
90493
|
-
});
|
|
90494
|
-
}
|
|
90495
|
-
}
|
|
90496
|
-
}
|
|
90497
|
-
return {
|
|
90498
|
-
pass: errors6.length === 0,
|
|
90499
|
-
errors: errors6,
|
|
90500
|
-
diffs
|
|
90501
|
-
};
|
|
90502
|
-
}
|
|
90503
|
-
var init_composition_baseline = __esm(() => {
|
|
90504
|
-
init_dist8();
|
|
90505
|
-
init_dist5();
|
|
90506
|
-
});
|
|
90751
|
+
var init_composition_baseline = () => {};
|
|
90507
90752
|
|
|
90508
90753
|
// ../../packages/app/src/workflow/proof-input-fingerprint.ts
|
|
90509
90754
|
import crypto4 from "crypto";
|
|
@@ -90810,7 +91055,7 @@ var init_rule_check = __esm(() => {
|
|
|
90810
91055
|
});
|
|
90811
91056
|
|
|
90812
91057
|
// ../../packages/app/src/workflow/actions/run-artifact.ts
|
|
90813
|
-
import { normalize as normalize3, resolve as
|
|
91058
|
+
import { normalize as normalize3, resolve as resolve11 } from "path";
|
|
90814
91059
|
|
|
90815
91060
|
class RunArtifactActionRunner {
|
|
90816
91061
|
getDb;
|
|
@@ -90838,8 +91083,8 @@ class RunArtifactActionRunner {
|
|
|
90838
91083
|
};
|
|
90839
91084
|
}
|
|
90840
91085
|
const workdir = context4.workdir ?? process.cwd();
|
|
90841
|
-
const allowedDir =
|
|
90842
|
-
const resolvedPath =
|
|
91086
|
+
const allowedDir = resolve11(workdir, ".spur", "run");
|
|
91087
|
+
const resolvedPath = resolve11(workdir, pathRaw);
|
|
90843
91088
|
const normalized = normalize3(resolvedPath);
|
|
90844
91089
|
if (!normalized.startsWith(allowedDir)) {
|
|
90845
91090
|
return {
|
|
@@ -91206,7 +91451,7 @@ var init_checkpoint_contract = __esm(() => {
|
|
|
91206
91451
|
});
|
|
91207
91452
|
|
|
91208
91453
|
// ../../packages/app/src/workflow/workflow-resolver.ts
|
|
91209
|
-
import { basename as basename7, isAbsolute as isAbsolute6, join as join23, resolve as
|
|
91454
|
+
import { basename as basename7, isAbsolute as isAbsolute6, join as join23, resolve as resolve12 } from "path";
|
|
91210
91455
|
function createEmbeddedSchemaOptions(embedded) {
|
|
91211
91456
|
if (embedded === undefined || embedded.size === 0)
|
|
91212
91457
|
return;
|
|
@@ -91237,7 +91482,7 @@ async function readWorkflowNameFast(fs3, filePath) {
|
|
|
91237
91482
|
}
|
|
91238
91483
|
async function scanWorkflowByName(cwd, name) {
|
|
91239
91484
|
const fs3 = createNodeFileSystem3();
|
|
91240
|
-
const projectSpurDir =
|
|
91485
|
+
const projectSpurDir = resolve12(cwd, ".spur", "workflows");
|
|
91241
91486
|
if (fs3.exists(projectSpurDir)) {
|
|
91242
91487
|
try {
|
|
91243
91488
|
const files = (await fs3.readDir(projectSpurDir)).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
|
|
@@ -91270,7 +91515,7 @@ async function scanWorkflowByName(cwd, name) {
|
|
|
91270
91515
|
}
|
|
91271
91516
|
function resolveWorkflowFile(cwd, file2) {
|
|
91272
91517
|
const fs3 = createNodeFileSystem3();
|
|
91273
|
-
const projectPath =
|
|
91518
|
+
const projectPath = resolve12(cwd, file2);
|
|
91274
91519
|
if (fs3.exists(projectPath)) {
|
|
91275
91520
|
return { path: projectPath, source: "project" };
|
|
91276
91521
|
}
|
|
@@ -91278,20 +91523,20 @@ function resolveWorkflowFile(cwd, file2) {
|
|
|
91278
91523
|
const base2 = basename7(file2);
|
|
91279
91524
|
if (!isAbsolute6(file2)) {
|
|
91280
91525
|
const withYaml = isYaml ? file2 : `${file2}.yaml`;
|
|
91281
|
-
const projectSpur =
|
|
91526
|
+
const projectSpur = resolve12(cwd, ".spur", "workflows", withYaml);
|
|
91282
91527
|
if (fs3.exists(projectSpur)) {
|
|
91283
91528
|
return { path: projectSpur, source: "project" };
|
|
91284
91529
|
}
|
|
91285
91530
|
if (!isYaml) {
|
|
91286
|
-
const projectDirect =
|
|
91531
|
+
const projectDirect = resolve12(cwd, `${file2}.yaml`);
|
|
91287
91532
|
if (fs3.exists(projectDirect)) {
|
|
91288
91533
|
return { path: projectDirect, source: "project" };
|
|
91289
91534
|
}
|
|
91290
|
-
const projectSpurPipeline =
|
|
91535
|
+
const projectSpurPipeline = resolve12(cwd, ".spur", "workflows", `${file2}-pipeline.yaml`);
|
|
91291
91536
|
if (fs3.exists(projectSpurPipeline)) {
|
|
91292
91537
|
return { path: projectSpurPipeline, source: "project" };
|
|
91293
91538
|
}
|
|
91294
|
-
const projectDirectPipeline =
|
|
91539
|
+
const projectDirectPipeline = resolve12(cwd, `${file2}-pipeline.yaml`);
|
|
91295
91540
|
if (fs3.exists(projectDirectPipeline)) {
|
|
91296
91541
|
return { path: projectDirectPipeline, source: "project" };
|
|
91297
91542
|
}
|
|
@@ -91355,9 +91600,9 @@ var init_workflow_resolver = __esm(() => {
|
|
|
91355
91600
|
});
|
|
91356
91601
|
|
|
91357
91602
|
// ../../packages/app/src/services/workflow-service.ts
|
|
91358
|
-
import {
|
|
91603
|
+
import { realpathSync as realpathSync5 } from "fs";
|
|
91359
91604
|
import { homedir as homedir8 } from "os";
|
|
91360
|
-
import { basename as basename8, dirname as dirname17, join as join24, resolve as
|
|
91605
|
+
import { basename as basename8, dirname as dirname17, join as join24, resolve as resolve13, sep as sep3 } from "path";
|
|
91361
91606
|
function signalSubprocess(pid) {
|
|
91362
91607
|
if (!Number.isInteger(pid) || pid <= 1)
|
|
91363
91608
|
return false;
|
|
@@ -91400,13 +91645,9 @@ function withSelfPidRecording(inner, db2) {
|
|
|
91400
91645
|
}
|
|
91401
91646
|
});
|
|
91402
91647
|
}
|
|
91403
|
-
function
|
|
91404
|
-
if (!definitionDigest)
|
|
91405
|
-
return inner;
|
|
91648
|
+
function withRunIdentityRecording(inner, db2, identity7) {
|
|
91406
91649
|
const stamp = async (runId) => {
|
|
91407
|
-
|
|
91408
|
-
await new RunDao(db2).mergeMetadata(runId, { definitionDigest });
|
|
91409
|
-
} catch {}
|
|
91650
|
+
await new RunDao(db2).stampRunIdentity(runId, identity7.definitionDigest, identity7.workflowVersion);
|
|
91410
91651
|
};
|
|
91411
91652
|
return new Proxy(inner, {
|
|
91412
91653
|
get(target, prop, receiver) {
|
|
@@ -91510,7 +91751,7 @@ class WorkflowAppService {
|
|
|
91510
91751
|
}
|
|
91511
91752
|
async run(file2, opts = {}) {
|
|
91512
91753
|
const eventsBus = this.ctx.events?.();
|
|
91513
|
-
const resolved = await resolveWorkflowDefinition(this.ctx.cwd, file2, {
|
|
91754
|
+
const resolved = opts.resolvedDefinition ?? await resolveWorkflowDefinition(this.ctx.cwd, file2, {
|
|
91514
91755
|
validateSchema: true,
|
|
91515
91756
|
embeddedSchemas: this.ctx.embeddedSchemas?.()
|
|
91516
91757
|
});
|
|
@@ -91547,6 +91788,8 @@ class WorkflowAppService {
|
|
|
91547
91788
|
await this.stampFailureReason(runId, result);
|
|
91548
91789
|
await this.maybeLinkPipelineRun(file2, runId, opts);
|
|
91549
91790
|
const runResult = result;
|
|
91791
|
+
runResult.definitionDigest = resolved.digest;
|
|
91792
|
+
runResult.version = workflowVersionLiteral(workflow);
|
|
91550
91793
|
if (warnings.length > 0) {
|
|
91551
91794
|
for (const warning of warnings) {
|
|
91552
91795
|
try {
|
|
@@ -91656,7 +91899,7 @@ class WorkflowAppService {
|
|
|
91656
91899
|
try {
|
|
91657
91900
|
sessionsReal = realpathSync5(sessionsDir);
|
|
91658
91901
|
} catch {
|
|
91659
|
-
sessionsReal =
|
|
91902
|
+
sessionsReal = resolve13(sessionsDir);
|
|
91660
91903
|
}
|
|
91661
91904
|
const cutoffMs = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
|
|
91662
91905
|
const reclaimed = [];
|
|
@@ -91675,7 +91918,7 @@ class WorkflowAppService {
|
|
|
91675
91918
|
try {
|
|
91676
91919
|
resolved = realpathSync5(path9);
|
|
91677
91920
|
} catch {
|
|
91678
|
-
resolved =
|
|
91921
|
+
resolved = resolve13(path9);
|
|
91679
91922
|
}
|
|
91680
91923
|
if (!resolved.startsWith(sessionsReal + sep3)) {
|
|
91681
91924
|
skipped.push({ name, reason: "path-confinement: resolved outside .spur/memory/sessions" });
|
|
@@ -91764,6 +92007,11 @@ class WorkflowAppService {
|
|
|
91764
92007
|
rawMeta = {};
|
|
91765
92008
|
}
|
|
91766
92009
|
const persistedDigest = typeof rawMeta.definitionDigest === "string" ? rawMeta.definitionDigest : null;
|
|
92010
|
+
const identityStamped = "workflowVersion" in rawMeta;
|
|
92011
|
+
const persistedVersion = identityStamped && (typeof rawMeta.workflowVersion === "string" || rawMeta.workflowVersion === null) ? rawMeta.workflowVersion : undefined;
|
|
92012
|
+
if (identityStamped && persistedDigest === null) {
|
|
92013
|
+
throw new Error(`Cannot resume run "${runId}": workflow definition drift detected (identity-stamped run has no recorded definition digest, currently ${resolved.digest}). Resume refused.`);
|
|
92014
|
+
}
|
|
91767
92015
|
if (persistedDigest !== null && persistedDigest !== resolved.digest) {
|
|
91768
92016
|
if (opts?.allowDigestMismatch !== true && opts?.force !== true) {
|
|
91769
92017
|
const prompt = `Workflow definition drift detected for run "${runId}": launched with ${persistedDigest}, currently ${resolved.digest}. Continue with modified definition?`;
|
|
@@ -91793,7 +92041,11 @@ class WorkflowAppService {
|
|
|
91793
92041
|
} : {}
|
|
91794
92042
|
});
|
|
91795
92043
|
await this.stampFailureReason(runId, result);
|
|
91796
|
-
|
|
92044
|
+
const runResult = result;
|
|
92045
|
+
runResult.definitionDigest = persistedDigest;
|
|
92046
|
+
if (persistedVersion !== undefined)
|
|
92047
|
+
runResult.version = persistedVersion;
|
|
92048
|
+
return runResult;
|
|
91797
92049
|
}
|
|
91798
92050
|
async stampFailureReason(runId, result) {
|
|
91799
92051
|
if (result.status !== "failed" || typeof result.reason !== "string" || result.reason === "") {
|
|
@@ -91855,21 +92107,21 @@ class WorkflowAppService {
|
|
|
91855
92107
|
const entries = [];
|
|
91856
92108
|
const scannedPaths = new Set;
|
|
91857
92109
|
for (const relPath of workflowPaths) {
|
|
91858
|
-
const absPath =
|
|
92110
|
+
const absPath = resolve13(projectRoot, relPath);
|
|
91859
92111
|
layers.push({ id: "project", path: absPath });
|
|
91860
92112
|
scannedPaths.add(absPath);
|
|
91861
92113
|
try {
|
|
91862
|
-
const found = await scanWorkflowFiles(absPath, "project");
|
|
92114
|
+
const found = await scanWorkflowFiles(absPath, "project", (filePath) => this.loadListEntry(filePath));
|
|
91863
92115
|
entries.push(...found);
|
|
91864
92116
|
} catch {}
|
|
91865
92117
|
}
|
|
91866
92118
|
for (const relPath of workflowPaths) {
|
|
91867
|
-
const absPath =
|
|
92119
|
+
const absPath = resolve13(globalRoot, relPath);
|
|
91868
92120
|
if (scannedPaths.has(absPath))
|
|
91869
92121
|
continue;
|
|
91870
92122
|
layers.push({ id: "global", path: absPath });
|
|
91871
92123
|
try {
|
|
91872
|
-
const found = await scanWorkflowFiles(absPath, "global");
|
|
92124
|
+
const found = await scanWorkflowFiles(absPath, "global", (filePath) => this.loadListEntry(filePath));
|
|
91873
92125
|
entries.push(...found);
|
|
91874
92126
|
} catch {}
|
|
91875
92127
|
}
|
|
@@ -91978,6 +92230,33 @@ class WorkflowAppService {
|
|
|
91978
92230
|
...outputArtifact !== undefined ? { outputArtifact } : {}
|
|
91979
92231
|
};
|
|
91980
92232
|
}
|
|
92233
|
+
async loadListEntry(filePath) {
|
|
92234
|
+
try {
|
|
92235
|
+
const resolved = await resolveWorkflowDefinition(this.ctx.cwd, filePath, {
|
|
92236
|
+
validateSchema: true,
|
|
92237
|
+
embeddedSchemas: this.ctx.embeddedSchemas?.()
|
|
92238
|
+
});
|
|
92239
|
+
return {
|
|
92240
|
+
name: resolved.workflow.name,
|
|
92241
|
+
kind: resolved.workflow.kind ?? "state-machine",
|
|
92242
|
+
valid: true,
|
|
92243
|
+
version: workflowVersionLiteral(resolved.workflow),
|
|
92244
|
+
definitionDigest: resolved.digest
|
|
92245
|
+
};
|
|
92246
|
+
} catch (error51) {
|
|
92247
|
+
const meta3 = await extractWorkflowMeta(filePath);
|
|
92248
|
+
return {
|
|
92249
|
+
name: meta3.name,
|
|
92250
|
+
kind: meta3.kind,
|
|
92251
|
+
valid: false,
|
|
92252
|
+
error: error51 instanceof Error ? error51.message : String(error51)
|
|
92253
|
+
};
|
|
92254
|
+
}
|
|
92255
|
+
}
|
|
92256
|
+
async stampPlanArtifactPath(runId, planArtifactPath) {
|
|
92257
|
+
const db2 = await this.ctx.getDb();
|
|
92258
|
+
await new RunDao(db2).mergeMetadata(runId, { planArtifactPath });
|
|
92259
|
+
}
|
|
91981
92260
|
async createEngineService(opts = {}) {
|
|
91982
92261
|
const processExec = this.ctx.processExecutor?.();
|
|
91983
92262
|
const host = createDefaultWorkflowEngineHost(processExec !== undefined ? { processExecutor: processExec } : {});
|
|
@@ -92021,8 +92300,10 @@ class WorkflowAppService {
|
|
|
92021
92300
|
persistence2 = withSelfPidRecording(persistence2, db2);
|
|
92022
92301
|
}
|
|
92023
92302
|
if (opts.extensions !== undefined) {
|
|
92024
|
-
|
|
92025
|
-
|
|
92303
|
+
persistence2 = withRunIdentityRecording(persistence2, db2, {
|
|
92304
|
+
definitionDigest: computeDefinitionDigest(opts.extensions.workflow),
|
|
92305
|
+
workflowVersion: workflowVersionLiteral(opts.extensions.workflow)
|
|
92306
|
+
});
|
|
92026
92307
|
}
|
|
92027
92308
|
const adapter = bus ? new ObservableWorkflowAdapter(persistence2, opts.extensions !== undefined ? withWorkflowIdentity(bus, opts.extensions.workflow) : bus) : persistence2;
|
|
92028
92309
|
return new WorkflowService(host, adapter);
|
|
@@ -92039,6 +92320,9 @@ class WorkflowAppService {
|
|
|
92039
92320
|
});
|
|
92040
92321
|
}
|
|
92041
92322
|
}
|
|
92323
|
+
function workflowVersionLiteral(workflow) {
|
|
92324
|
+
return typeof workflow.version === "string" && workflow.version !== "" ? workflow.version : null;
|
|
92325
|
+
}
|
|
92042
92326
|
function collectShellCommands(def) {
|
|
92043
92327
|
const entries = [];
|
|
92044
92328
|
const visitAction = (stateId, action, idx) => {
|
|
@@ -92196,7 +92480,7 @@ function collectUndeclaredShellVarViolations(def) {
|
|
|
92196
92480
|
}
|
|
92197
92481
|
function collectCompositionAdvisory(def, workflowFile) {
|
|
92198
92482
|
const findings = [];
|
|
92199
|
-
|
|
92483
|
+
const workflowName = basename8(workflowFile, ".yaml");
|
|
92200
92484
|
const shellLines = (command) => command.split(/\n|;/).map((u2) => u2.trim()).filter((u2) => u2.length > 0 && !u2.startsWith("#")).length;
|
|
92201
92485
|
const agentRunSeverity = (prompt) => {
|
|
92202
92486
|
if (prompt.length < 200)
|
|
@@ -92205,17 +92489,6 @@ function collectCompositionAdvisory(def, workflowFile) {
|
|
|
92205
92489
|
return "medium";
|
|
92206
92490
|
return "high";
|
|
92207
92491
|
};
|
|
92208
|
-
const baseline = loadCompositionBaselineFor(workflowFile);
|
|
92209
|
-
const workflowName = basename8(workflowFile, ".yaml");
|
|
92210
|
-
const adjudicated = (actionKey) => {
|
|
92211
|
-
if (!baseline)
|
|
92212
|
-
return false;
|
|
92213
|
-
const entry = baseline.workflows[workflowName];
|
|
92214
|
-
if (!entry)
|
|
92215
|
-
return false;
|
|
92216
|
-
const action = entry.actions[actionKey];
|
|
92217
|
-
return action?.disposition !== undefined;
|
|
92218
|
-
};
|
|
92219
92492
|
const visitAction = (stateId, action, idx, phase) => {
|
|
92220
92493
|
const actionKey = `${stateId}:${phase}:${idx}`;
|
|
92221
92494
|
if (action.kind === "shell") {
|
|
@@ -92225,16 +92498,12 @@ function collectCompositionAdvisory(def, workflowFile) {
|
|
|
92225
92498
|
const measured = shellLines(cmd);
|
|
92226
92499
|
if (measured < 6)
|
|
92227
92500
|
return;
|
|
92228
|
-
if (adjudicated(actionKey)) {
|
|
92229
|
-
suppressed += 1;
|
|
92230
|
-
return;
|
|
92231
|
-
}
|
|
92232
92501
|
findings.push({
|
|
92233
92502
|
workflow: workflowName,
|
|
92234
92503
|
state: stateId,
|
|
92235
92504
|
actionKey,
|
|
92236
92505
|
measure: { kind: "shell-lines", measured, threshold: 6 },
|
|
92237
|
-
recommendation: `shell action at ${actionKey} measures ${measured} lines (>5 frozen threshold, ADR-069) \u2014 extract to a script
|
|
92506
|
+
recommendation: `shell action at ${actionKey} measures ${measured} lines (>5 frozen threshold, ADR-069) \u2014 extract to a script under scripts/ or plugins/sp/scripts`
|
|
92238
92507
|
});
|
|
92239
92508
|
} else if (action.kind === "agent.run") {
|
|
92240
92509
|
const input = action.options?.input;
|
|
@@ -92242,18 +92511,14 @@ function collectCompositionAdvisory(def, workflowFile) {
|
|
|
92242
92511
|
return;
|
|
92243
92512
|
if (input.trimStart().startsWith("/"))
|
|
92244
92513
|
return;
|
|
92245
|
-
|
|
92246
|
-
|
|
92247
|
-
|
|
92248
|
-
|
|
92249
|
-
|
|
92250
|
-
|
|
92251
|
-
|
|
92252
|
-
|
|
92253
|
-
measure: { kind: "agent-run-chars", measured: input.length, severity },
|
|
92254
|
-
recommendation: `agent.run prompt at ${actionKey} is ${input.length} chars, not slash-pinned (severity ${severity}) \u2014 pin to a slash command or a script with a bounded prompt`
|
|
92255
|
-
});
|
|
92256
|
-
}
|
|
92514
|
+
const severity = agentRunSeverity(input);
|
|
92515
|
+
findings.push({
|
|
92516
|
+
workflow: workflowName,
|
|
92517
|
+
state: stateId,
|
|
92518
|
+
actionKey,
|
|
92519
|
+
measure: { kind: "agent-run-chars", measured: input.length, severity },
|
|
92520
|
+
recommendation: `agent.run prompt at ${actionKey} is ${input.length} chars, not slash-pinned (severity ${severity}) \u2014 pin to a slash command or a script with a bounded prompt`
|
|
92521
|
+
});
|
|
92257
92522
|
}
|
|
92258
92523
|
};
|
|
92259
92524
|
if (def.kind === "transition-flow" || def.kind === undefined) {
|
|
@@ -92271,22 +92536,7 @@ function collectCompositionAdvisory(def, workflowFile) {
|
|
|
92271
92536
|
visitAction(state.id, action, i2, "onExit");
|
|
92272
92537
|
}
|
|
92273
92538
|
}
|
|
92274
|
-
return { findings
|
|
92275
|
-
}
|
|
92276
|
-
function loadCompositionBaselineFor(workflowFile) {
|
|
92277
|
-
let dir = dirname17(resolve14(workflowFile));
|
|
92278
|
-
for (let i2 = 0;i2 < 10; i2++) {
|
|
92279
|
-
const candidate = join24(dir, "config/workflow-composition-baseline.json");
|
|
92280
|
-
if (existsSync10(candidate)) {
|
|
92281
|
-
try {
|
|
92282
|
-
return JSON.parse(readFileSync10(candidate, "utf8"));
|
|
92283
|
-
} catch {
|
|
92284
|
-
return;
|
|
92285
|
-
}
|
|
92286
|
-
}
|
|
92287
|
-
dir = dirname17(dir);
|
|
92288
|
-
}
|
|
92289
|
-
return;
|
|
92539
|
+
return { findings };
|
|
92290
92540
|
}
|
|
92291
92541
|
async function fileExists(path9) {
|
|
92292
92542
|
const fs3 = createNodeFileSystem3();
|
|
@@ -92329,7 +92579,7 @@ async function outputArtifactForRun(cwd, runId) {
|
|
|
92329
92579
|
const relative5 = join24(".spur", "run", `${runId}.log`);
|
|
92330
92580
|
return await fileExists(join24(cwd, relative5)) ? relative5 : undefined;
|
|
92331
92581
|
}
|
|
92332
|
-
async function scanWorkflowFiles(rootPath, source) {
|
|
92582
|
+
async function scanWorkflowFiles(rootPath, source, loadEntry) {
|
|
92333
92583
|
const entries = [];
|
|
92334
92584
|
const fs3 = createNodeFileSystem3();
|
|
92335
92585
|
const rootStat = await fs3.stat(rootPath);
|
|
@@ -92340,7 +92590,7 @@ async function scanWorkflowFiles(rootPath, source) {
|
|
|
92340
92590
|
async function walk3(dirPath) {
|
|
92341
92591
|
const names = await fs3.readDir(dirPath);
|
|
92342
92592
|
for (const name of names) {
|
|
92343
|
-
const fullPath =
|
|
92593
|
+
const fullPath = resolve13(dirPath, name);
|
|
92344
92594
|
const st = await fs3.stat(fullPath);
|
|
92345
92595
|
if (!st)
|
|
92346
92596
|
continue;
|
|
@@ -92348,8 +92598,8 @@ async function scanWorkflowFiles(rootPath, source) {
|
|
|
92348
92598
|
await walk3(fullPath);
|
|
92349
92599
|
} else if (st.isFile() && (name.endsWith(".yaml") || name.endsWith(".yml"))) {
|
|
92350
92600
|
const displayPath = fullPath.startsWith(rootPath) ? fullPath.slice(rootPath.length + 1) : fullPath;
|
|
92351
|
-
const
|
|
92352
|
-
entries.push({ ...
|
|
92601
|
+
const entry = await loadEntry(fullPath);
|
|
92602
|
+
entries.push({ ...entry, path: displayPath, source });
|
|
92353
92603
|
}
|
|
92354
92604
|
}
|
|
92355
92605
|
}
|
|
@@ -92388,12 +92638,20 @@ async function extractWorkflowMeta(filePath) {
|
|
|
92388
92638
|
function rowToTraceEntry(row, cwd) {
|
|
92389
92639
|
let isDryRun = false;
|
|
92390
92640
|
let failureReason;
|
|
92641
|
+
let version3;
|
|
92642
|
+
let definitionDigest;
|
|
92391
92643
|
try {
|
|
92392
92644
|
const meta3 = JSON.parse(row.metadata_json);
|
|
92393
92645
|
isDryRun = meta3.dryRun === true;
|
|
92394
92646
|
if (typeof meta3.failureReason === "string" && meta3.failureReason !== "") {
|
|
92395
92647
|
failureReason = meta3.failureReason;
|
|
92396
92648
|
}
|
|
92649
|
+
if ("workflowVersion" in meta3 && (typeof meta3.workflowVersion === "string" || meta3.workflowVersion === null)) {
|
|
92650
|
+
version3 = meta3.workflowVersion;
|
|
92651
|
+
}
|
|
92652
|
+
if (typeof meta3.definitionDigest === "string" && meta3.definitionDigest !== "") {
|
|
92653
|
+
definitionDigest = meta3.definitionDigest;
|
|
92654
|
+
}
|
|
92397
92655
|
} catch {}
|
|
92398
92656
|
const entry = {
|
|
92399
92657
|
runId: row.id,
|
|
@@ -92406,7 +92664,9 @@ function rowToTraceEntry(row, cwd) {
|
|
|
92406
92664
|
project: systemEventProjectContext(cwd),
|
|
92407
92665
|
durationMs: durationBetween(row.started_at, row.completed_at),
|
|
92408
92666
|
outcome: traceOutcome(row.status),
|
|
92409
|
-
...failureReason !== undefined ? { failureReason } : {}
|
|
92667
|
+
...failureReason !== undefined ? { failureReason } : {},
|
|
92668
|
+
...version3 !== undefined ? { version: version3 } : {},
|
|
92669
|
+
...definitionDigest !== undefined ? { definitionDigest } : {}
|
|
92410
92670
|
};
|
|
92411
92671
|
const nextAction = traceNextAction(entry);
|
|
92412
92672
|
if (nextAction !== undefined)
|
|
@@ -92503,7 +92763,7 @@ async function partialArtifactForAction(cwd, runId, node, ok) {
|
|
|
92503
92763
|
if (ok !== false || !TRACE_IDENTIFIER.test(runId) || !TRACE_IDENTIFIER.test(node))
|
|
92504
92764
|
return;
|
|
92505
92765
|
const relativePath2 = join24(".spur", "run", `${runId}-${node}-partial.md`);
|
|
92506
|
-
return await fileExists(
|
|
92766
|
+
return await fileExists(resolve13(cwd, relativePath2)) ? relativePath2 : undefined;
|
|
92507
92767
|
}
|
|
92508
92768
|
var TASK_PIPELINE_WORKFLOW = "task-pipeline", PIPELINE_LINK_KIND = "pipeline", TRACE_IDENTIFIER, TRACE_RESULT_FIELDS, TRACE_INVOCATION_FIELDS;
|
|
92509
92769
|
var init_workflow_service = __esm(() => {
|
|
@@ -92541,7 +92801,7 @@ var init_workflow_service = __esm(() => {
|
|
|
92541
92801
|
});
|
|
92542
92802
|
|
|
92543
92803
|
// ../../packages/app/src/workflow/idea-handoff.ts
|
|
92544
|
-
import { resolve as
|
|
92804
|
+
import { resolve as resolve14 } from "path";
|
|
92545
92805
|
function processEvidence(r) {
|
|
92546
92806
|
return `exit=${r.exitCode ?? "null"}${r.signal ? ` signal=${r.signal}` : ""}: ${r.stderr.trim() || "no stderr"}`;
|
|
92547
92807
|
}
|
|
@@ -92551,11 +92811,11 @@ async function finalizeIdeaHandoff(options) {
|
|
|
92551
92811
|
const executor = options.processExecutor ?? new NodeProcessExecutor3;
|
|
92552
92812
|
const spurBin = options.spurBin ?? "spur";
|
|
92553
92813
|
const { runId, featureId: featureId2 } = options;
|
|
92554
|
-
const runDir =
|
|
92555
|
-
const batchPath =
|
|
92556
|
-
const resultPath =
|
|
92557
|
-
const orderPath =
|
|
92558
|
-
const reportPath =
|
|
92814
|
+
const runDir = resolve14(root, ".spur", "run");
|
|
92815
|
+
const batchPath = resolve14(runDir, `${runId}-idea-task-batch.json`);
|
|
92816
|
+
const resultPath = resolve14(runDir, `${runId}-idea-batch-create-result.json`);
|
|
92817
|
+
const orderPath = resolve14(runDir, `${runId}-idea-task-order.json`);
|
|
92818
|
+
const reportPath = resolve14(runDir, `${runId}-idea-handoff.md`);
|
|
92559
92819
|
const spurSplit = splitLaunchCommand(spurBin, 'idea-handoff "spurBin"');
|
|
92560
92820
|
if ("error" in spurSplit) {
|
|
92561
92821
|
return {
|
|
@@ -92955,6 +93215,7 @@ async function projectWorkflowProgress(runId, options) {
|
|
|
92955
93215
|
}
|
|
92956
93216
|
} catch {}
|
|
92957
93217
|
const recordedDigest = typeof rawMeta.definitionDigest === "string" ? rawMeta.definitionDigest : null;
|
|
93218
|
+
const recordedVersion = "workflowVersion" in rawMeta && (typeof rawMeta.workflowVersion === "string" || rawMeta.workflowVersion === null) ? rawMeta.workflowVersion : undefined;
|
|
92958
93219
|
let normalizedStatus = "unknown";
|
|
92959
93220
|
const statusLower = (runRow.status ?? "").toLowerCase();
|
|
92960
93221
|
if (statusLower === "done" || statusLower === "completed") {
|
|
@@ -93006,6 +93267,7 @@ async function projectWorkflowProgress(runId, options) {
|
|
|
93006
93267
|
workflow: workflowName,
|
|
93007
93268
|
status: normalizedStatus,
|
|
93008
93269
|
definitionDigest: recordedDigest,
|
|
93270
|
+
...recordedVersion !== undefined ? { version: recordedVersion } : {},
|
|
93009
93271
|
currentState,
|
|
93010
93272
|
states: [],
|
|
93011
93273
|
transitions,
|
|
@@ -93184,6 +93446,7 @@ async function projectWorkflowProgress(runId, options) {
|
|
|
93184
93446
|
workflow: smDef.name || workflowName,
|
|
93185
93447
|
status: normalizedStatus,
|
|
93186
93448
|
definitionDigest: recordedDigest,
|
|
93449
|
+
...recordedVersion !== undefined ? { version: recordedVersion } : {},
|
|
93187
93450
|
currentState,
|
|
93188
93451
|
states: statesProgress,
|
|
93189
93452
|
transitions,
|
|
@@ -93342,7 +93605,6 @@ __export(exports_src2, {
|
|
|
93342
93605
|
registerSpurBuiltins: () => registerSpurBuiltins,
|
|
93343
93606
|
refreshHistoryRollups: () => refreshHistoryRollups,
|
|
93344
93607
|
redactAndBound: () => redactAndBound,
|
|
93345
|
-
reconcileBaseline: () => reconcileBaseline,
|
|
93346
93608
|
readVerdictArtifact: () => readVerdictArtifact,
|
|
93347
93609
|
readVerdict: () => readVerdict,
|
|
93348
93610
|
qualifySectionBody: () => qualifySectionBody,
|
|
@@ -93365,7 +93627,6 @@ __export(exports_src2, {
|
|
|
93365
93627
|
normalizeSystemEventPayload: () => normalizeSystemEventPayload,
|
|
93366
93628
|
normalizeProjectPath: () => normalizeProjectPath,
|
|
93367
93629
|
looksLikeOpaqueId: () => looksLikeOpaqueId,
|
|
93368
|
-
loadAcceptedFindings: () => loadAcceptedFindings,
|
|
93369
93630
|
isSystemEventEnvelopeV2: () => isSystemEventEnvelopeV2,
|
|
93370
93631
|
isRecordAuthoredReview: () => isRecordAuthoredReview,
|
|
93371
93632
|
isPortLive: () => isPortLive,
|
|
@@ -93412,20 +93673,17 @@ __export(exports_src2, {
|
|
|
93412
93673
|
computeExitCode: () => computeExitCode,
|
|
93413
93674
|
computeDefinitionDigest: () => computeDefinitionDigest,
|
|
93414
93675
|
computeAggregate: () => computeAggregate,
|
|
93415
|
-
collectObservedFindings: () => collectObservedFindings,
|
|
93416
93676
|
classifyTaskOutcome: () => classifyTaskOutcome,
|
|
93417
93677
|
classifyPortBindError: () => classifyPortBindError,
|
|
93418
93678
|
classifyDispatch: () => classifyDispatch,
|
|
93419
93679
|
clampToolUseLimit: () => clampToolUseLimit,
|
|
93420
93680
|
clampRunStoreLimit: () => clampRunStoreLimit,
|
|
93421
|
-
checkWorkflowComposition: () => checkWorkflowComposition,
|
|
93422
93681
|
canonicalJsonStringify: () => canonicalJsonStringify,
|
|
93423
93682
|
buildWorkflowSteps: () => buildWorkflowSteps,
|
|
93424
93683
|
buildTrackedBasenameIndex: () => buildTrackedBasenameIndex,
|
|
93425
93684
|
buildSystemEventEnvelope: () => buildSystemEventEnvelope,
|
|
93426
93685
|
buildEscalationPacket: () => buildEscalationPacket,
|
|
93427
93686
|
bridgeEventBus: () => bridgeEventBus,
|
|
93428
|
-
baselineSeverity: () => baselineSeverity,
|
|
93429
93687
|
attributeSessions: () => attributeSessions,
|
|
93430
93688
|
assertPiImporterSafe: () => assertPiImporterSafe,
|
|
93431
93689
|
anchorQualify: () => anchorQualify,
|
|
@@ -101666,15 +101924,15 @@ function parseTimeout(raw) {
|
|
|
101666
101924
|
return n3;
|
|
101667
101925
|
}
|
|
101668
101926
|
function loopSleep(ms, signal) {
|
|
101669
|
-
return new Promise((
|
|
101927
|
+
return new Promise((resolve15) => {
|
|
101670
101928
|
if (signal?.aborted) {
|
|
101671
|
-
|
|
101929
|
+
resolve15();
|
|
101672
101930
|
return;
|
|
101673
101931
|
}
|
|
101674
|
-
const timer = setTimeout(
|
|
101932
|
+
const timer = setTimeout(resolve15, ms);
|
|
101675
101933
|
signal?.addEventListener("abort", () => {
|
|
101676
101934
|
clearTimeout(timer);
|
|
101677
|
-
|
|
101935
|
+
resolve15();
|
|
101678
101936
|
}, { once: true });
|
|
101679
101937
|
});
|
|
101680
101938
|
}
|
|
@@ -101805,7 +102063,7 @@ function waitFail(context4, options, code, message) {
|
|
|
101805
102063
|
|
|
101806
102064
|
// src/release-ops.ts
|
|
101807
102065
|
init_dist5();
|
|
101808
|
-
import { existsSync as
|
|
102066
|
+
import { existsSync as existsSync10, readdirSync as readdirSync7, readFileSync as readFileSync10 } from "fs";
|
|
101809
102067
|
import { join as join26 } from "path";
|
|
101810
102068
|
var SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?(?:\+[0-9A-Za-z-.]+)?$/;
|
|
101811
102069
|
function releaseTag(config4, version3) {
|
|
@@ -101852,7 +102110,7 @@ async function readJson(path9) {
|
|
|
101852
102110
|
function rootWorkspaceGlobs(repoRoot) {
|
|
101853
102111
|
let root;
|
|
101854
102112
|
try {
|
|
101855
|
-
root = JSON.parse(
|
|
102113
|
+
root = JSON.parse(readFileSync10(join26(repoRoot, "package.json"), "utf8"));
|
|
101856
102114
|
} catch {
|
|
101857
102115
|
return [];
|
|
101858
102116
|
}
|
|
@@ -101870,10 +102128,10 @@ function workspaceDirs(repoRoot) {
|
|
|
101870
102128
|
continue;
|
|
101871
102129
|
const parent = glob.slice(0, -2);
|
|
101872
102130
|
const parentAbs = join26(repoRoot, parent);
|
|
101873
|
-
if (!
|
|
102131
|
+
if (!existsSync10(parentAbs))
|
|
101874
102132
|
continue;
|
|
101875
102133
|
for (const entry of readdirSync7(parentAbs, { withFileTypes: true })) {
|
|
101876
|
-
if (entry.isDirectory() &&
|
|
102134
|
+
if (entry.isDirectory() && existsSync10(join26(parentAbs, entry.name, "package.json"))) {
|
|
101877
102135
|
dirs.push(`${parent}/${entry.name}`);
|
|
101878
102136
|
}
|
|
101879
102137
|
}
|
|
@@ -101901,7 +102159,7 @@ async function releaseContext(repoRoot) {
|
|
|
101901
102159
|
const shortName = (name) => name.split("/").pop() ?? name;
|
|
101902
102160
|
const packages = new Map;
|
|
101903
102161
|
for (const ws of workspaces2) {
|
|
101904
|
-
const versionSourceFile =
|
|
102162
|
+
const versionSourceFile = existsSync10(join26(repoRoot, ws.dir, "src", "config.ts")) ? `${ws.dir}/src/config.ts` : undefined;
|
|
101905
102163
|
packages.set(shortName(ws.name), {
|
|
101906
102164
|
packageDir: ws.dir,
|
|
101907
102165
|
packageName: ws.name,
|
|
@@ -102071,7 +102329,7 @@ async function bumpVersion(ctx, config4, version3, options, output2) {
|
|
|
102071
102329
|
staged.push(...await updateWorkspacePins(ctx, config4.packageName, previous, version3, output2));
|
|
102072
102330
|
await syncMarketplaceAndPlugins(ctx, version3, staged, output2);
|
|
102073
102331
|
const lockPath = join26(ctx.repoRoot, "bun.lock");
|
|
102074
|
-
if (
|
|
102332
|
+
if (existsSync10(lockPath) && Bun.file(lockPath).size > 0)
|
|
102075
102333
|
staged.push("bun.lock");
|
|
102076
102334
|
await git2(ctx.repoRoot, ["add", ...staged]);
|
|
102077
102335
|
const message = `${config4.releaseCommitType}(${config4.releaseCommitScope}): ${config4.releaseCommitSubject(version3)}`;
|
|
@@ -102146,7 +102404,7 @@ async function bumpAll(ctx, version3, options, output2) {
|
|
|
102146
102404
|
}
|
|
102147
102405
|
await syncMarketplaceAndPlugins(ctx, version3, staged, output2);
|
|
102148
102406
|
const lockPath = join26(ctx.repoRoot, "bun.lock");
|
|
102149
|
-
if (
|
|
102407
|
+
if (existsSync10(lockPath) && Bun.file(lockPath).size > 0)
|
|
102150
102408
|
staged.push("bun.lock");
|
|
102151
102409
|
await git2(ctx.repoRoot, ["add", ...staged]);
|
|
102152
102410
|
const shortNames = configs.map((c3) => c3.packageName.split("/").pop() ?? c3.packageName).join(" + ");
|
|
@@ -102536,7 +102794,7 @@ function registerFeatureCommand(program2, context4) {
|
|
|
102536
102794
|
if (current === "active") {
|
|
102537
102795
|
await assertFeatureCheckPass(context4, id, options.folder, false, "verifying");
|
|
102538
102796
|
} else if (current === "verifying") {
|
|
102539
|
-
await assertFeatureCheckPass(context4, id, options.folder,
|
|
102797
|
+
await assertFeatureCheckPass(context4, id, options.folder, false, "done");
|
|
102540
102798
|
}
|
|
102541
102799
|
const result = await svc.transition(id, next);
|
|
102542
102800
|
history.push({ from: result.fromStatus ?? current, to: result.toStatus ?? next });
|
|
@@ -102806,7 +103064,7 @@ import { createRequire } from "module";
|
|
|
102806
103064
|
var CLI_CONFIG = {
|
|
102807
103065
|
binaryName: "spur",
|
|
102808
103066
|
binaryLabel: "spur",
|
|
102809
|
-
binaryVersion: "0.3.
|
|
103067
|
+
binaryVersion: "0.3.74",
|
|
102810
103068
|
configDir: ".spur",
|
|
102811
103069
|
configFile: ".spur/config.yaml",
|
|
102812
103070
|
databaseFile: ".spur/spur.db"
|
|
@@ -103191,7 +103449,7 @@ init_loader();
|
|
|
103191
103449
|
init_src2();
|
|
103192
103450
|
init_dist10();
|
|
103193
103451
|
import { homedir as homedir9 } from "os";
|
|
103194
|
-
import { join as join27, resolve as
|
|
103452
|
+
import { join as join27, resolve as resolve15 } from "path";
|
|
103195
103453
|
|
|
103196
103454
|
// src/config/scaffold-manifest.ts
|
|
103197
103455
|
var SCAFFOLD_MANIFEST = [
|
|
@@ -103252,7 +103510,7 @@ If \`.spur/context/\` is absent, proceed normally. Never block work on its absen
|
|
|
103252
103510
|
`;
|
|
103253
103511
|
function globalRulesRoot(context4) {
|
|
103254
103512
|
const override = context4.env.SPUR_GLOBAL_RULES_DIR;
|
|
103255
|
-
return override !== undefined && override.length > 0 ?
|
|
103513
|
+
return override !== undefined && override.length > 0 ? resolve15(context4.cwd, override) : join27(homedir9(), GLOBAL_RULES_DIR2);
|
|
103256
103514
|
}
|
|
103257
103515
|
async function writeIfNew(context4, path9, content, force, result) {
|
|
103258
103516
|
if (!force && await context4.fs.exists(path9)) {
|
|
@@ -103280,7 +103538,7 @@ async function seedGlobalRules(context4) {
|
|
|
103280
103538
|
}
|
|
103281
103539
|
function resolveGlobalConfigDir(context4) {
|
|
103282
103540
|
const globalOverride = context4.env.SPUR_GLOBAL_RULES_DIR;
|
|
103283
|
-
return globalOverride !== undefined && globalOverride.length > 0 ?
|
|
103541
|
+
return globalOverride !== undefined && globalOverride.length > 0 ? resolve15(context4.cwd, globalOverride) : join27(homedir9(), GLOBAL_CONFIG_DIR);
|
|
103284
103542
|
}
|
|
103285
103543
|
async function seedGlobalConfig(context4) {
|
|
103286
103544
|
const source = bundledConfigRoot();
|
|
@@ -103520,6 +103778,42 @@ ${contextEntry}
|
|
|
103520
103778
|
});
|
|
103521
103779
|
}
|
|
103522
103780
|
|
|
103781
|
+
// src/commands/maintain.ts
|
|
103782
|
+
init_src();
|
|
103783
|
+
init_src2();
|
|
103784
|
+
import { join as join28 } from "path";
|
|
103785
|
+
function registerMaintainCommand(program2, context4, options = {}) {
|
|
103786
|
+
program2.command("maintain", { hidden: options.hidden === true }).summary("run database maintenance (optimize, WAL checkpoint truncation, optional VACUUM)").description("Run database maintenance: PRAGMA optimize, WAL checkpoint truncation; optionally VACUUM compaction.").option("--vacuum", "Run VACUUM defragmentation and page compaction", false).option(...SHARED_OPTIONS.jsonSupported).option(...SHARED_OPTIONS.jsonEnvelope).action(async (options2) => {
|
|
103787
|
+
const db2 = await context4.getDb();
|
|
103788
|
+
const dbPath = context4.env.DATABASE_URL ?? join28(context4.cwd, DEFAULT_DATABASE_URL);
|
|
103789
|
+
const result = await maintainDatabase(db2, {
|
|
103790
|
+
vacuum: options2.vacuum === true,
|
|
103791
|
+
dbPath: dbPath.startsWith(":memory:") ? undefined : dbPath
|
|
103792
|
+
});
|
|
103793
|
+
context4.output.write(options2.json ? toEnvelopeJson(result, { enveloped: options2.jsonEnvelope }) : formatMaintenanceResult(result));
|
|
103794
|
+
if (!result.optimized && !result.checkpointed) {
|
|
103795
|
+
context4.setExitCode(1);
|
|
103796
|
+
}
|
|
103797
|
+
});
|
|
103798
|
+
}
|
|
103799
|
+
function formatMaintenanceResult(r) {
|
|
103800
|
+
const lines = [
|
|
103801
|
+
`database maintenance completed in ${r.durationMs}ms:`,
|
|
103802
|
+
` optimize: ${r.optimized ? "ok" : "failed"}`,
|
|
103803
|
+
` wal_checkpoint: ${r.checkpointed ? "ok (truncated)" : "failed"}`,
|
|
103804
|
+
` vacuum: ${r.vacuumed ? "ok" : r.vacuumSkippedReason ? `skipped (${r.vacuumSkippedReason})` : "skipped"}`
|
|
103805
|
+
];
|
|
103806
|
+
if (r.vacuumed && r.bytesReclaimed > 0) {
|
|
103807
|
+
const mbReclaimed = (r.bytesReclaimed / (1024 * 1024)).toFixed(2);
|
|
103808
|
+
lines.push(` reclaimed: ${mbReclaimed} MB`);
|
|
103809
|
+
}
|
|
103810
|
+
const mbBefore = (r.bytesBefore / (1024 * 1024)).toFixed(2);
|
|
103811
|
+
const mbAfter = (r.bytesAfter / (1024 * 1024)).toFixed(2);
|
|
103812
|
+
lines.push(` size: ${mbAfter} MB (was ${mbBefore} MB)`);
|
|
103813
|
+
return lines.join(`
|
|
103814
|
+
`);
|
|
103815
|
+
}
|
|
103816
|
+
|
|
103523
103817
|
// src/commands/message.ts
|
|
103524
103818
|
init_src2();
|
|
103525
103819
|
await init_src3();
|
|
@@ -103790,15 +104084,15 @@ async function runMessageWatch(svc, output2, options, runtime = {}) {
|
|
|
103790
104084
|
}
|
|
103791
104085
|
}
|
|
103792
104086
|
function defaultSleep(ms, signal) {
|
|
103793
|
-
return new Promise((
|
|
104087
|
+
return new Promise((resolve16) => {
|
|
103794
104088
|
if (signal?.aborted) {
|
|
103795
|
-
|
|
104089
|
+
resolve16();
|
|
103796
104090
|
return;
|
|
103797
104091
|
}
|
|
103798
|
-
const timer = setTimeout(
|
|
104092
|
+
const timer = setTimeout(resolve16, ms);
|
|
103799
104093
|
signal?.addEventListener("abort", () => {
|
|
103800
104094
|
clearTimeout(timer);
|
|
103801
|
-
|
|
104095
|
+
resolve16();
|
|
103802
104096
|
}, { once: true });
|
|
103803
104097
|
});
|
|
103804
104098
|
}
|
|
@@ -103825,15 +104119,15 @@ function parseTimeout2(raw) {
|
|
|
103825
104119
|
return n3;
|
|
103826
104120
|
}
|
|
103827
104121
|
function sendSleep(ms, signal) {
|
|
103828
|
-
return new Promise((
|
|
104122
|
+
return new Promise((resolve16) => {
|
|
103829
104123
|
if (signal?.aborted) {
|
|
103830
|
-
|
|
104124
|
+
resolve16();
|
|
103831
104125
|
return;
|
|
103832
104126
|
}
|
|
103833
|
-
const timer = setTimeout(
|
|
104127
|
+
const timer = setTimeout(resolve16, ms);
|
|
103834
104128
|
signal?.addEventListener("abort", () => {
|
|
103835
104129
|
clearTimeout(timer);
|
|
103836
|
-
|
|
104130
|
+
resolve16();
|
|
103837
104131
|
}, { once: true });
|
|
103838
104132
|
});
|
|
103839
104133
|
}
|
|
@@ -103891,10 +104185,10 @@ async function waitForPendingDrain(agentService, teamService, pin, timeoutMs, si
|
|
|
103891
104185
|
|
|
103892
104186
|
// src/commands/migrate.ts
|
|
103893
104187
|
init_src2();
|
|
103894
|
-
import { join as
|
|
104188
|
+
import { join as join29 } from "path";
|
|
103895
104189
|
function registerMigrateCommand(program2, context4, options = {}) {
|
|
103896
104190
|
program2.command("migrate", { hidden: options.hidden === true }).summary("apply CLI-owned schema migrations").option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (options2) => {
|
|
103897
|
-
const migrations = await loadSqlMigrations(
|
|
104191
|
+
const migrations = await loadSqlMigrations(join29(context4.cwd, "drizzle")).catch(() => {
|
|
103898
104192
|
return;
|
|
103899
104193
|
});
|
|
103900
104194
|
const applied = await applyCliMigrations(await context4.getDb(), migrations);
|
|
@@ -103906,12 +104200,12 @@ function registerMigrateCommand(program2, context4, options = {}) {
|
|
|
103906
104200
|
// src/commands/projects.ts
|
|
103907
104201
|
init_dist5();
|
|
103908
104202
|
await init_src3();
|
|
103909
|
-
import { basename as basename10, resolve as
|
|
104203
|
+
import { basename as basename10, resolve as resolve16 } from "path";
|
|
103910
104204
|
function registerProjectsCommand(program2, context4) {
|
|
103911
104205
|
const projectsCmd = program2.command("projects").summary("manage the Spur multi-project registry");
|
|
103912
104206
|
projectsCmd.command("add").argument("<path>", "Project root directory path").option(...SHARED_OPTIONS.nameProjectDisplay).option(...SHARED_OPTIONS.jsonProjectsResponse).option(...SHARED_OPTIONS.jsonEnvelope).action(async (pathArg, options) => {
|
|
103913
104207
|
try {
|
|
103914
|
-
const absolutePath =
|
|
104208
|
+
const absolutePath = resolve16(context4.cwd, pathArg);
|
|
103915
104209
|
if (!await context4.fs.exists(absolutePath)) {
|
|
103916
104210
|
throw new Error(`Directory does not exist: ${absolutePath}`);
|
|
103917
104211
|
}
|
|
@@ -104333,7 +104627,7 @@ function formatTraceDetail(detail) {
|
|
|
104333
104627
|
|
|
104334
104628
|
// src/commands/serve.ts
|
|
104335
104629
|
init_src();
|
|
104336
|
-
import { join as
|
|
104630
|
+
import { join as join33 } from "path";
|
|
104337
104631
|
|
|
104338
104632
|
// ../server/src/index.ts
|
|
104339
104633
|
init_src();
|
|
@@ -104343,11 +104637,11 @@ init_src();
|
|
|
104343
104637
|
init_loader();
|
|
104344
104638
|
init_src2();
|
|
104345
104639
|
await init_src3();
|
|
104346
|
-
import { basename as basename12, dirname as dirname20, isAbsolute as isAbsolute7, join as
|
|
104640
|
+
import { basename as basename12, dirname as dirname20, isAbsolute as isAbsolute7, join as join32 } from "path";
|
|
104347
104641
|
init_dist5();
|
|
104348
104642
|
|
|
104349
104643
|
// ../server/src/bootstrap.ts
|
|
104350
|
-
import { join as
|
|
104644
|
+
import { join as join30 } from "path";
|
|
104351
104645
|
|
|
104352
104646
|
// ../../node_modules/.bun/radash@12.1.1/node_modules/radash/dist/esm/async.mjs
|
|
104353
104647
|
var guard = (func, shouldGuard) => {
|
|
@@ -104620,8 +104914,8 @@ class EventPublisher {
|
|
|
104620
104914
|
if (bufferedEvents.length > 0) {
|
|
104621
104915
|
return { done: false, value: bufferedEvents.shift() };
|
|
104622
104916
|
}
|
|
104623
|
-
return new Promise((
|
|
104624
|
-
pullResolvers.push([
|
|
104917
|
+
return new Promise((resolve17, reject) => {
|
|
104918
|
+
pullResolvers.push([resolve17, reject]);
|
|
104625
104919
|
});
|
|
104626
104920
|
}, async () => {
|
|
104627
104921
|
unsubscribe();
|
|
@@ -110265,6 +110559,7 @@ var historyKpiTrendPointSchema = exports_external.object({
|
|
|
110265
110559
|
var historyTimeSeriesPointSchema = exports_external.object({
|
|
110266
110560
|
bucketStart: exports_external.string(),
|
|
110267
110561
|
cacheHitRatio: exports_external.number(),
|
|
110562
|
+
gainRatio: exports_external.number().optional(),
|
|
110268
110563
|
series: exports_external.record(exports_external.string(), exports_external.number())
|
|
110269
110564
|
});
|
|
110270
110565
|
var historyTopItemSchema = exports_external.object({
|
|
@@ -113767,7 +114062,7 @@ function createApp(appRt, opts) {
|
|
|
113767
114062
|
app.use("*", async (c3, next) => {
|
|
113768
114063
|
const pathname = c3.req.path === "/" ? "/index.html" : c3.req.path;
|
|
113769
114064
|
try {
|
|
113770
|
-
const file2 = Bun.file(
|
|
114065
|
+
const file2 = Bun.file(join30(webDistPath, pathname));
|
|
113771
114066
|
if (await file2.exists()) {
|
|
113772
114067
|
const headers = new Headers({ "content-type": file2.type });
|
|
113773
114068
|
return new Response(file2.stream(), { headers });
|
|
@@ -113780,7 +114075,7 @@ function createApp(appRt, opts) {
|
|
|
113780
114075
|
return c3.json({ error: "Not Found" }, 404);
|
|
113781
114076
|
}
|
|
113782
114077
|
try {
|
|
113783
|
-
const indexFile = Bun.file(
|
|
114078
|
+
const indexFile = Bun.file(join30(webDistPath, "index.html"));
|
|
113784
114079
|
if (await indexFile.exists()) {
|
|
113785
114080
|
return new Response(indexFile.stream(), {
|
|
113786
114081
|
headers: { "content-type": "text/html; charset=utf-8" }
|
|
@@ -113801,7 +114096,7 @@ init_src();
|
|
|
113801
114096
|
init_src2();
|
|
113802
114097
|
init_dist5();
|
|
113803
114098
|
await init_src3();
|
|
113804
|
-
import { dirname as dirname19, join as
|
|
114099
|
+
import { dirname as dirname19, join as join31 } from "path";
|
|
113805
114100
|
var NOOP_OUTPUT = { write: (_s) => {}, error: (_s) => {} };
|
|
113806
114101
|
|
|
113807
114102
|
class LazyPlanningEventEmitter {
|
|
@@ -113837,7 +114132,7 @@ class NotConfiguredError extends Error {
|
|
|
113837
114132
|
function createServerContext(appRt, options) {
|
|
113838
114133
|
const cwd = options.cwd;
|
|
113839
114134
|
const fs3 = options.fs;
|
|
113840
|
-
const dbUrl = options.dbUrl ??
|
|
114135
|
+
const dbUrl = options.dbUrl ?? join31(cwd, DEFAULT_DATABASE_URL);
|
|
113841
114136
|
const eventsBus = options.eventsBus ?? appRt.events;
|
|
113842
114137
|
const jobQueueEnabled = options.jobQueueEnabled ?? false;
|
|
113843
114138
|
const eventProjectContext = systemEventProjectContext(cwd);
|
|
@@ -114123,7 +114418,7 @@ async function loadServerSectionMatrix() {
|
|
|
114123
114418
|
}
|
|
114124
114419
|
const root = bundledConfigRoot();
|
|
114125
114420
|
if (root !== null) {
|
|
114126
|
-
const matrixPath =
|
|
114421
|
+
const matrixPath = join32(root, "tasks", "section-matrix.yaml");
|
|
114127
114422
|
if (await nodeFs.exists(matrixPath)) {
|
|
114128
114423
|
return await loadStructuredSpurConfig(matrixPath, {
|
|
114129
114424
|
validateJsonSchema: false
|
|
@@ -114132,7 +114427,7 @@ async function loadServerSectionMatrix() {
|
|
|
114132
114427
|
}
|
|
114133
114428
|
throw new Error(`no canonical section-matrix found for task creation (F92 R1); tried:
|
|
114134
114429
|
` + ` - ${localPath}
|
|
114135
|
-
` + (root !== null ? ` - ${
|
|
114430
|
+
` + (root !== null ? ` - ${join32(root, "tasks", "section-matrix.yaml")}
|
|
114136
114431
|
` : "") + "copy/generate section-matrix.yaml from the canonical build-time matrix asset (repo `config` `tasks` tree) into one of those paths");
|
|
114137
114432
|
}
|
|
114138
114433
|
var SYSTEM_EVENTS_PRUNE_JOB = "system-events-prune";
|
|
@@ -114270,15 +114565,15 @@ async function handleFeatureActionJob(ctx, env, payload) {
|
|
|
114270
114565
|
await runFeatureActionJob(ctx, env, payload);
|
|
114271
114566
|
}
|
|
114272
114567
|
async function resolveWebDistPath(configuredPath) {
|
|
114273
|
-
const candidates = configuredPath && configuredPath.trim() !== "" ? [isAbsolute7(configuredPath) ? configuredPath :
|
|
114274
|
-
|
|
114275
|
-
|
|
114276
|
-
|
|
114277
|
-
|
|
114278
|
-
|
|
114568
|
+
const candidates = configuredPath && configuredPath.trim() !== "" ? [isAbsolute7(configuredPath) ? configuredPath : join32(process.cwd(), configuredPath)] : [
|
|
114569
|
+
join32(process.cwd(), "dist/web"),
|
|
114570
|
+
join32(import.meta.dir, "web"),
|
|
114571
|
+
join32(dirname20(process.execPath), "web"),
|
|
114572
|
+
join32(dirname20(process.execPath), "../web"),
|
|
114573
|
+
join32(import.meta.dir, "../../../dist/web")
|
|
114279
114574
|
];
|
|
114280
114575
|
for (const candidate of candidates) {
|
|
114281
|
-
if (await Bun.file(
|
|
114576
|
+
if (await Bun.file(join32(candidate, "index.html")).exists()) {
|
|
114282
114577
|
return candidate;
|
|
114283
114578
|
}
|
|
114284
114579
|
}
|
|
@@ -114442,7 +114737,7 @@ if (false) {}
|
|
|
114442
114737
|
|
|
114443
114738
|
// src/commands/serve.ts
|
|
114444
114739
|
function resolveServeDbUrl(cwd, env, configuredUrl) {
|
|
114445
|
-
return env.DATABASE_URL === undefined ?
|
|
114740
|
+
return env.DATABASE_URL === undefined ? join33(cwd, DEFAULT_DATABASE_URL) : configuredUrl;
|
|
114446
114741
|
}
|
|
114447
114742
|
function registerServeCommand(program2, context4, options = {}) {
|
|
114448
114743
|
program2.command("serve", { hidden: options.hidden === true }).summary("start the Spur web server (local fallback)").option(...SHARED_OPTIONS.portServe, parseInt).option("--host <addr>", "Bind address (env: HOST, default: localhost)").option("--no-open", "Skip opening the browser").option(...SHARED_OPTIONS.cwdServe, context4.cwd).option(...SHARED_OPTIONS.jsonServePortUrl).option(...SHARED_OPTIONS.jsonEnvelope).action(async (options2) => {
|
|
@@ -114482,7 +114777,7 @@ function registerServeCommand(program2, context4, options = {}) {
|
|
|
114482
114777
|
}
|
|
114483
114778
|
|
|
114484
114779
|
// src/commands/status.ts
|
|
114485
|
-
import { join as
|
|
114780
|
+
import { join as join34 } from "path";
|
|
114486
114781
|
|
|
114487
114782
|
// src/errors.ts
|
|
114488
114783
|
class CommandError extends Error {
|
|
@@ -114553,8 +114848,8 @@ function registerStatusCommand(program2, context4, options = {}) {
|
|
|
114553
114848
|
}
|
|
114554
114849
|
async function runStatusCore(path9, options, context4) {
|
|
114555
114850
|
const [packageJsonExists, spurConfigExists, git3, agentSpecs] = await Promise.all([
|
|
114556
|
-
context4.fs.exists(
|
|
114557
|
-
context4.fs.exists(
|
|
114851
|
+
context4.fs.exists(join34(context4.cwd, "package.json")),
|
|
114852
|
+
context4.fs.exists(join34(context4.cwd, ".spur", "config.yaml")),
|
|
114558
114853
|
gitContext(context4.cwd),
|
|
114559
114854
|
listAgentSpecIds(context4)
|
|
114560
114855
|
]);
|
|
@@ -114582,14 +114877,14 @@ async function runStatusCore(path9, options, context4) {
|
|
|
114582
114877
|
return status.ok ? 0 : 1;
|
|
114583
114878
|
}
|
|
114584
114879
|
async function listAgentSpecIds(context4) {
|
|
114585
|
-
const dir =
|
|
114880
|
+
const dir = join34(context4.cwd, ".spur", "agents");
|
|
114586
114881
|
if (!await context4.fs.exists(dir))
|
|
114587
114882
|
return [];
|
|
114588
114883
|
const entries = await context4.fs.readDir(dir);
|
|
114589
114884
|
return entries.filter((entry) => entry.endsWith(".yaml") || entry.endsWith(".yml")).map((entry) => entry.replace(/\.ya?ml$/, "")).sort();
|
|
114590
114885
|
}
|
|
114591
114886
|
async function readTargetStatus(context4, targetPath) {
|
|
114592
|
-
const resolved =
|
|
114887
|
+
const resolved = join34(context4.cwd, targetPath);
|
|
114593
114888
|
const stat = await context4.fs.stat(resolved);
|
|
114594
114889
|
if (stat === null)
|
|
114595
114890
|
throw new CommandError(`status failed: path does not exist at ${resolved}`);
|
|
@@ -114601,8 +114896,8 @@ init_loader();
|
|
|
114601
114896
|
init_src2();
|
|
114602
114897
|
init_dist5();
|
|
114603
114898
|
await init_src3();
|
|
114604
|
-
import { existsSync as
|
|
114605
|
-
import { dirname as dirname21, join as
|
|
114899
|
+
import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
|
|
114900
|
+
import { dirname as dirname21, join as join35 } from "path";
|
|
114606
114901
|
// schemas/section-matrix.schema.json
|
|
114607
114902
|
var section_matrix_schema_default = {
|
|
114608
114903
|
$schema: "http://json-schema.org/draft-07/schema#",
|
|
@@ -115704,7 +115999,7 @@ ${result.content}`);
|
|
|
115704
115999
|
let forcedDoneVerdict;
|
|
115705
116000
|
if (status === "done") {
|
|
115706
116001
|
const current = await svc.show(wbs);
|
|
115707
|
-
const verdictDir = options.verdictDir ??
|
|
116002
|
+
const verdictDir = options.verdictDir ?? join35(context4.cwd, ".spur", "run");
|
|
115708
116003
|
const loaded = await readVerdictArtifact(context4.fs, verdictDir, wbs);
|
|
115709
116004
|
const guardOutcome = evaluateDoneTransition({
|
|
115710
116005
|
wbs,
|
|
@@ -116150,73 +116445,42 @@ ${result.content}`);
|
|
|
116150
116445
|
context4.setExitCode(1);
|
|
116151
116446
|
}
|
|
116152
116447
|
});
|
|
116153
|
-
task.command("check").summary("Validate a task file through the four-layer check (design \xA73).").argument("[wbs]", "Task WBS number (validates all tasks in the folder when omitted)").option(...SHARED_OPTIONS.strictTaskAll).option("--strict-core", "Compatibility alias (F92 R2): historically the done-gate label; kept so installed plugins/workflows that call it keep working. No longer meaningful on its own \u2014 target-state selection (`--as`) supplies the real done semantics.").option(...SHARED_OPTIONS.asTaskF92).option("--
|
|
116448
|
+
task.command("check").summary("Validate a task file through the four-layer check (design \xA73).").argument("[wbs]", "Task WBS number (validates all tasks in the folder when omitted)").option("--corpus", "Explicit unsuppressed audit of active tasks and features; warnings are advisory").option("--since <ref>", "Scope the corpus fog comparison to changes since a git ref (requires --corpus)").option(...SHARED_OPTIONS.strictTaskAll).option("--strict-core", "Compatibility alias (F92 R2): historically the done-gate label; kept so installed plugins/workflows that call it keep working. No longer meaningful on its own \u2014 target-state selection (`--as`) supplies the real done semantics.").option(...SHARED_OPTIONS.asTaskF92).option("--fix", "repair structural findings in place (heading presence/level/order; adds missing R-item checkbox markers \u2014 flipping verified boxes is task record's job, not --fix)").option(...SHARED_OPTIONS.folderTasks).option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (wbs, options) => {
|
|
116154
116449
|
const json3 = options.json === true;
|
|
116155
116450
|
const strict = options.strict === true;
|
|
116156
|
-
|
|
116157
|
-
|
|
116158
|
-
writeJsonError(context4.output, options, `invalid --as status "${options.as}" (canonical: ${TASK_STATUSES.join(", ")})`, "VALIDATION_FAILED");
|
|
116159
|
-
context4.setExitCode(2);
|
|
116160
|
-
return;
|
|
116161
|
-
}
|
|
116162
|
-
if (asStatus !== undefined && options.corpus === true) {
|
|
116163
|
-
writeJsonError(context4.output, options, "--as <status> is a single-task target projection and cannot be combined with --corpus", "VALIDATION_FAILED");
|
|
116164
|
-
context4.setExitCode(2);
|
|
116165
|
-
return;
|
|
116166
|
-
}
|
|
116167
|
-
if (options.fix === true && options.corpus === true) {
|
|
116168
|
-
writeJsonError(context4.output, options, "--fix repairs files in place and cannot be combined with --corpus", "VALIDATION_FAILED");
|
|
116451
|
+
if (options.since !== undefined && !options.corpus || options.corpus && (wbs || options.as || options.fix || options.folder || strict || options.strictCore) || options.since?.startsWith("-")) {
|
|
116452
|
+
writeJsonError(context4.output, options, "--corpus accepts only --since and JSON output options; --since requires --corpus and a git ref", "VALIDATION_FAILED");
|
|
116169
116453
|
context4.setExitCode(2);
|
|
116170
116454
|
return;
|
|
116171
116455
|
}
|
|
116172
|
-
|
|
116173
|
-
|
|
116174
|
-
|
|
116175
|
-
writeJsonError(context4.output, options, "--corpus validates the whole corpus and cannot be combined with a WBS", "VALIDATION_FAILED");
|
|
116176
|
-
context4.setExitCode(2);
|
|
116177
|
-
return;
|
|
116178
|
-
}
|
|
116179
|
-
if (String(options.since ?? "").startsWith("-")) {
|
|
116180
|
-
writeJsonError(context4.output, options, "--since requires a git ref value (e.g. --since HEAD~1)", "VALIDATION_FAILED");
|
|
116181
|
-
context4.setExitCode(2);
|
|
116182
|
-
return;
|
|
116183
|
-
}
|
|
116184
|
-
if (options.since !== undefined) {
|
|
116185
|
-
const fogRange = await resolveFogRange(context4.cwd, options.since);
|
|
116186
|
-
if ("skip" in fogRange) {
|
|
116187
|
-
context4.output.error(`corpus-check: fog check SKIPPED (${fogRange.skip}) \u2014 range ${fogRange.spec} was not evaluated.`);
|
|
116188
|
-
}
|
|
116189
|
-
}
|
|
116190
|
-
const result = await runCorpusCheck(context4.cwd, options.since);
|
|
116456
|
+
if (options.corpus) {
|
|
116457
|
+
try {
|
|
116458
|
+
const result = await runCorpusCheck(context4.cwd, options.since, context4.output.error.bind(context4.output));
|
|
116191
116459
|
if (json3) {
|
|
116192
116460
|
context4.output.write(toEnvelopeJson(result, { enveloped: options.jsonEnvelope }));
|
|
116193
116461
|
} else {
|
|
116194
|
-
|
|
116195
|
-
const
|
|
116196
|
-
|
|
116197
|
-
for (const error51 of result.newErrors) {
|
|
116198
|
-
context4.output.error(` NEW [error] ${error51.kind} ${error51.id}: ${error51.code} \u2014 ${error51.message}`);
|
|
116199
|
-
}
|
|
116200
|
-
for (const warning of result.newWarnings) {
|
|
116201
|
-
context4.output.error(` NEW [warning] ${warning.kind} ${warning.id}: ${warning.code} \u2014 ${warning.message}`);
|
|
116462
|
+
context4.output.write(`corpus-check: ${result.newErrors.length} errors, ${result.newWarnings.length} warnings; no suppressions. Use --json for full findings.`);
|
|
116463
|
+
for (const finding of result.newErrors) {
|
|
116464
|
+
writeJsonError(context4.output, options, `${finding.kind}:${finding.id}:${finding.code}: ${finding.message}`);
|
|
116202
116465
|
}
|
|
116203
|
-
for (const dup of result.duplicateKeys) {
|
|
116204
|
-
context4.output.error(` DUP ${dup.key} \u2014 ${dup.count} entries for one key; reconciliation is key-addressed, ` + "so the extras over-cover and hide a partial reduction. Keep one entry per key.");
|
|
116205
|
-
}
|
|
116206
|
-
context4.output.write(result.ok ? "corpus-check OK \u2014 no corpus errors or warnings outside the accepted baseline." : "corpus-check FAILED \u2014 fix the new findings above, or accept them by regenerating the snapshot: bun run scripts/commands/regen-corpus-baseline.ts.");
|
|
116207
116466
|
}
|
|
116208
116467
|
if (!result.ok)
|
|
116209
116468
|
context4.setExitCode(1);
|
|
116210
|
-
|
|
116211
|
-
|
|
116212
|
-
|
|
116213
|
-
writeJsonError(context4.output, options, "--since requires --corpus", "VALIDATION_FAILED");
|
|
116214
|
-
context4.setExitCode(2);
|
|
116215
|
-
return;
|
|
116469
|
+
} catch (err) {
|
|
116470
|
+
writeJsonError(context4.output, options, String(err));
|
|
116471
|
+
context4.setExitCode(1);
|
|
116216
116472
|
}
|
|
116473
|
+
return;
|
|
116474
|
+
}
|
|
116475
|
+
const asStatus = options.as === undefined ? undefined : canonicalStatusOrRaw(options.as);
|
|
116476
|
+
if (options.as !== undefined && !TASK_STATUSES.includes(asStatus ?? "")) {
|
|
116477
|
+
writeJsonError(context4.output, options, `invalid --as status "${options.as}" (canonical: ${TASK_STATUSES.join(", ")})`, "VALIDATION_FAILED");
|
|
116478
|
+
context4.setExitCode(2);
|
|
116479
|
+
return;
|
|
116480
|
+
}
|
|
116481
|
+
try {
|
|
116217
116482
|
const svc = await makeCheckService(context4);
|
|
116218
116483
|
const planningFolders = await resolvePlanningFolders(context4.fs);
|
|
116219
|
-
const accepted = await loadAcceptedFindings(context4.cwd);
|
|
116220
116484
|
const activeFolder = planningFolders.foldersConfig.active_folder;
|
|
116221
116485
|
const tasksDir = context4.fs.resolve(options.folder ?? activeFolder);
|
|
116222
116486
|
const printResult = (result) => {
|
|
@@ -116250,7 +116514,6 @@ ${result.wbs} (${result.status}): ${result.pass ? "PASS" : "FAIL"}`);
|
|
|
116250
116514
|
strict,
|
|
116251
116515
|
asStatus,
|
|
116252
116516
|
severityOverrides: planningFolders.severityOverrides,
|
|
116253
|
-
accepted,
|
|
116254
116517
|
fix: options.fix === true
|
|
116255
116518
|
});
|
|
116256
116519
|
results.push(result);
|
|
@@ -116270,7 +116533,6 @@ ${result.wbs} (${result.status}): ${result.pass ? "PASS" : "FAIL"}`);
|
|
|
116270
116533
|
strict,
|
|
116271
116534
|
asStatus,
|
|
116272
116535
|
severityOverrides: planningFolders.severityOverrides,
|
|
116273
|
-
accepted,
|
|
116274
116536
|
fix: options.fix === true
|
|
116275
116537
|
});
|
|
116276
116538
|
results.push(result);
|
|
@@ -116420,15 +116682,15 @@ function loadTemplateBodies(projectRoot, variant) {
|
|
|
116420
116682
|
if (cached2 !== undefined)
|
|
116421
116683
|
return cached2;
|
|
116422
116684
|
let bodies = {};
|
|
116423
|
-
const localPath =
|
|
116424
|
-
if (
|
|
116425
|
-
bodies = extractTemplateBodies(
|
|
116685
|
+
const localPath = join35(projectRoot, ".spur", "tasks", "templates", `${variant}.md`);
|
|
116686
|
+
if (existsSync11(localPath)) {
|
|
116687
|
+
bodies = extractTemplateBodies(readFileSync11(localPath, "utf8"));
|
|
116426
116688
|
} else {
|
|
116427
116689
|
const root = bundledConfigRoot();
|
|
116428
116690
|
if (root !== null) {
|
|
116429
|
-
const templatePath =
|
|
116430
|
-
if (
|
|
116431
|
-
bodies = extractTemplateBodies(
|
|
116691
|
+
const templatePath = join35(root, "templates", "task", `${variant}.md`);
|
|
116692
|
+
if (existsSync11(templatePath)) {
|
|
116693
|
+
bodies = extractTemplateBodies(readFileSync11(templatePath, "utf8"));
|
|
116432
116694
|
}
|
|
116433
116695
|
}
|
|
116434
116696
|
}
|
|
@@ -116455,12 +116717,10 @@ async function runDoneGateCheck(context4, wbs, folderOverride, targetStatus) {
|
|
|
116455
116717
|
return false;
|
|
116456
116718
|
}
|
|
116457
116719
|
const svc = new TaskCheckService(context4.fs, await loadSectionMatrix(context4.cwd), await makeTaskLocator(context4));
|
|
116458
|
-
const accepted = await loadAcceptedFindings(context4.cwd);
|
|
116459
116720
|
const result = await svc.check(hit.filePath, wbs, {
|
|
116460
116721
|
strict: false,
|
|
116461
116722
|
asStatus: targetStatus,
|
|
116462
|
-
severityOverrides: planningFolders.severityOverrides
|
|
116463
|
-
accepted
|
|
116723
|
+
severityOverrides: planningFolders.severityOverrides
|
|
116464
116724
|
});
|
|
116465
116725
|
return result.pass;
|
|
116466
116726
|
}
|
|
@@ -116486,7 +116746,7 @@ async function loadSectionMatrixUncached(projectRoot) {
|
|
|
116486
116746
|
}
|
|
116487
116747
|
const root = bundledConfigRoot();
|
|
116488
116748
|
if (root !== null) {
|
|
116489
|
-
const matrixPath =
|
|
116749
|
+
const matrixPath = join35(root, "tasks", "section-matrix.yaml");
|
|
116490
116750
|
if (await fs3.exists(matrixPath)) {
|
|
116491
116751
|
const data = await loadStructuredSpurConfig(matrixPath, {
|
|
116492
116752
|
validateJsonSchema: true,
|
|
@@ -116497,7 +116757,7 @@ async function loadSectionMatrixUncached(projectRoot) {
|
|
|
116497
116757
|
}
|
|
116498
116758
|
throw new Error(`no canonical section-matrix found for task section authority (F92 R1); tried:
|
|
116499
116759
|
` + ` - ${localPath}
|
|
116500
|
-
` + (root !== null ? ` - ${
|
|
116760
|
+
` + (root !== null ? ` - ${join35(root, "tasks", "section-matrix.yaml")}
|
|
116501
116761
|
` : "") + "copy/generate section-matrix.yaml from the canonical build-time matrix asset (repo `config` `tasks` tree) into one of those paths");
|
|
116502
116762
|
}
|
|
116503
116763
|
|
|
@@ -116780,13 +117040,13 @@ async function runTeamDown(team, options, context4) {
|
|
|
116780
117040
|
}
|
|
116781
117041
|
|
|
116782
117042
|
// src/commands/workflow.ts
|
|
117043
|
+
init_capability_attestation();
|
|
116783
117044
|
init_loader();
|
|
116784
|
-
init_dist8();
|
|
116785
117045
|
init_dist7();
|
|
116786
117046
|
init_dist5();
|
|
116787
117047
|
await init_src3();
|
|
116788
117048
|
import { closeSync as closeSync5, fstatSync as fstatSync3, openSync as openSync5, readSync as readSync3 } from "fs";
|
|
116789
|
-
import { join as
|
|
117049
|
+
import { join as join36 } from "path";
|
|
116790
117050
|
import { createInterface } from "readline";
|
|
116791
117051
|
import { setTimeout as sleep5 } from "timers/promises";
|
|
116792
117052
|
|
|
@@ -116878,7 +117138,7 @@ function markWorkflowRunActive() {
|
|
|
116878
117138
|
function clearWorkflowRunActive() {
|
|
116879
117139
|
delete process.env[WORKFLOW_RUN_ACTIVE_ENV];
|
|
116880
117140
|
}
|
|
116881
|
-
async function spawnAsyncWorkflowWorker(spurBin, cmd) {
|
|
117141
|
+
async function spawnAsyncWorkflowWorker(spurBin, cmd, extraEnv) {
|
|
116882
117142
|
const line = [spurBin, ...cmd].map(shQuote2).join(" ");
|
|
116883
117143
|
const env = {};
|
|
116884
117144
|
for (const [key2, value2] of Object.entries(process.env)) {
|
|
@@ -116886,6 +117146,9 @@ async function spawnAsyncWorkflowWorker(spurBin, cmd) {
|
|
|
116886
117146
|
env[key2] = value2;
|
|
116887
117147
|
}
|
|
116888
117148
|
env.SPUR_ASYNC_WORKER = "1";
|
|
117149
|
+
for (const [key2, value2] of Object.entries(extraEnv ?? {})) {
|
|
117150
|
+
env[key2] = value2;
|
|
117151
|
+
}
|
|
116889
117152
|
await new NodeProcessExecutor3().run({
|
|
116890
117153
|
command: process.platform === "win32" ? "cmd" : "/bin/sh",
|
|
116891
117154
|
args: process.platform === "win32" ? ["/c", `start /b "" ${[spurBin, ...cmd].map((c3) => `"${c3.replace(/"/g, '""')}"`).join(" ")}`] : ["-c", `nohup ${line} </dev/null >/dev/null 2>&1 &`],
|
|
@@ -116939,6 +117202,35 @@ async function waitForRunRegistration(service, runId, timeoutMs = asyncRegisterT
|
|
|
116939
117202
|
}
|
|
116940
117203
|
}
|
|
116941
117204
|
}
|
|
117205
|
+
function expectedDigestMismatch(resolvedDigest, expectedDigest) {
|
|
117206
|
+
if (expectedDigest === undefined)
|
|
117207
|
+
return null;
|
|
117208
|
+
if (resolvedDigest !== expectedDigest) {
|
|
117209
|
+
return "workflow run: refusing to start \u2014 resolved definition digest " + `${resolvedDigest ?? "<resolution failed>"} differs from the expected digest ` + `${expectedDigest} sent by the async launcher`;
|
|
117210
|
+
}
|
|
117211
|
+
return null;
|
|
117212
|
+
}
|
|
117213
|
+
async function writeWorkflowPlanArtifact(cwd, runId, resolved, secretValues) {
|
|
117214
|
+
const def = resolved.workflow;
|
|
117215
|
+
const payload = toJson2({
|
|
117216
|
+
runId,
|
|
117217
|
+
name: def.name,
|
|
117218
|
+
kind: def.kind ?? "state-machine",
|
|
117219
|
+
format: "todo",
|
|
117220
|
+
version: def.version ?? null,
|
|
117221
|
+
definitionDigest: resolved.digest,
|
|
117222
|
+
steps: buildWorkflowSteps(def).map((step) => ({
|
|
117223
|
+
...step,
|
|
117224
|
+
...step.description !== undefined ? { description: redactAndBound(step.description, secretValues, 512) } : {}
|
|
117225
|
+
}))
|
|
117226
|
+
});
|
|
117227
|
+
const { mkdir, writeFile } = await import("fs/promises");
|
|
117228
|
+
const artifactsDir = join36(cwd, ".spur", "run");
|
|
117229
|
+
await mkdir(artifactsDir, { recursive: true });
|
|
117230
|
+
const artifactPath = join36(artifactsDir, `${runId}-workflow-plan.json`);
|
|
117231
|
+
await writeFile(artifactPath, payload);
|
|
117232
|
+
return artifactPath;
|
|
117233
|
+
}
|
|
116942
117234
|
async function makeEscalationPacketSink(bus, context4) {
|
|
116943
117235
|
try {
|
|
116944
117236
|
let locator;
|
|
@@ -116993,7 +117285,7 @@ function resolveWorkflowPaths(config4) {
|
|
|
116993
117285
|
for (const path9 of paths) {
|
|
116994
117286
|
if (path9.startsWith(BUNDLED_PATH_PREFIX)) {
|
|
116995
117287
|
if (bundledRoot !== null)
|
|
116996
|
-
expanded.push(
|
|
117288
|
+
expanded.push(join36(bundledRoot, path9.slice(BUNDLED_PATH_PREFIX.length)));
|
|
116997
117289
|
} else {
|
|
116998
117290
|
expanded.push(path9);
|
|
116999
117291
|
}
|
|
@@ -117027,14 +117319,11 @@ function registerWorkflowCommand(program2, context4) {
|
|
|
117027
117319
|
} else if (result.valid) {
|
|
117028
117320
|
context4.output.write(`workflow valid: ${result.workflow.name} (${formatWorkflowVersion(result.workflow.version)})`);
|
|
117029
117321
|
const c3 = result.composition;
|
|
117030
|
-
if (c3 &&
|
|
117322
|
+
if (c3 && c3.findings.length > 0) {
|
|
117031
117323
|
for (const f of c3.findings) {
|
|
117032
117324
|
const m = f.measure.kind === "shell-lines" ? `${f.measure.measured} shell lines (threshold ${f.measure.threshold})` : `${f.measure.measured} prompt chars (severity ${f.measure.severity})`;
|
|
117033
117325
|
context4.output.error(`composition advisory: ${f.actionKey} \u2014 ${m} \u2014 ${f.recommendation}`);
|
|
117034
117326
|
}
|
|
117035
|
-
if (c3.suppressed > 0) {
|
|
117036
|
-
context4.output.error(`composition advisory: ${c3.suppressed} shell finding(s) suppressed by baseline dispositions (guards excluded wholesale; docs/design/workflow-shell-ownership.md)`);
|
|
117037
|
-
}
|
|
117038
117327
|
}
|
|
117039
117328
|
} else {
|
|
117040
117329
|
context4.output.error(`workflow invalid: ${result.file}
|
|
@@ -117071,9 +117360,9 @@ ${result.errors.map((m) => ` - ${m}`).join(`
|
|
|
117071
117360
|
const detail = options.verbose === true ? "full" : requestedDetail ?? "invocation";
|
|
117072
117361
|
if (process.env[WORKFLOW_RUN_ACTIVE_ENV] === "1") {
|
|
117073
117362
|
writeJsonError(context4.output, options, `workflow run: refusing to start \u2014 already inside an active workflow run (${WORKFLOW_RUN_ACTIVE_ENV}=1).
|
|
117074
|
-
|
|
117075
|
-
|
|
117076
|
-
|
|
117363
|
+
A pipeline that starts another pipeline forks a worktree and an agent run per level, without bound.
|
|
117364
|
+
If you are an agent running inside a pipeline step: do NOT start a pipeline here. Report what you
|
|
117365
|
+
needed and let the operator run it from a clean shell.`, "VALIDATION_FAILED");
|
|
117077
117366
|
context4.setExitCode(1);
|
|
117078
117367
|
return;
|
|
117079
117368
|
}
|
|
@@ -117106,8 +117395,22 @@ ${result.errors.map((m) => ` - ${m}`).join(`
|
|
|
117106
117395
|
if (options.log === false) {
|
|
117107
117396
|
cmd.push("--no-log");
|
|
117108
117397
|
}
|
|
117398
|
+
let resolvedDefinition2;
|
|
117399
|
+
let planArtifactPath;
|
|
117400
|
+
try {
|
|
117401
|
+
resolvedDefinition2 = await resolveWorkflowDefinition(context4.cwd, file2, {
|
|
117402
|
+
embeddedSchemas: EMBEDDED_SPUR_SCHEMAS
|
|
117403
|
+
});
|
|
117404
|
+
planArtifactPath = await writeWorkflowPlanArtifact(context4.cwd, runId2, resolvedDefinition2, configuredSecretValues(context4.env));
|
|
117405
|
+
} catch (error51) {
|
|
117406
|
+
writeJsonError(context4.output, options, `workflow run --async: ${error51 instanceof Error ? error51.message : String(error51)}`, "VALIDATION_FAILED");
|
|
117407
|
+
context4.setExitCode(1);
|
|
117408
|
+
return;
|
|
117409
|
+
}
|
|
117109
117410
|
try {
|
|
117110
|
-
await spawnAsyncWorkflowWorker(spurBin, cmd
|
|
117411
|
+
await spawnAsyncWorkflowWorker(spurBin, cmd, {
|
|
117412
|
+
SPUR_EXPECTED_DEFINITION_DIGEST: resolvedDefinition2.digest
|
|
117413
|
+
});
|
|
117111
117414
|
} catch {
|
|
117112
117415
|
markWorkflowRunActive();
|
|
117113
117416
|
let result2;
|
|
@@ -117115,7 +117418,8 @@ ${result.errors.map((m) => ` - ${m}`).join(`
|
|
|
117115
117418
|
result2 = await makeSvc(options.json).run(file2, {
|
|
117116
117419
|
runId: runId2,
|
|
117117
117420
|
vars: { spurBin: resolveSpurBin(), ...parseVars(options.vars) },
|
|
117118
|
-
dryRun: options.dryRun || undefined
|
|
117421
|
+
dryRun: options.dryRun || undefined,
|
|
117422
|
+
resolvedDefinition: resolvedDefinition2
|
|
117119
117423
|
});
|
|
117120
117424
|
} finally {
|
|
117121
117425
|
clearWorkflowRunActive();
|
|
@@ -117140,11 +117444,15 @@ ${result.errors.map((m) => ` - ${m}`).join(`
|
|
|
117140
117444
|
context4.setExitCode(1);
|
|
117141
117445
|
return;
|
|
117142
117446
|
}
|
|
117143
|
-
|
|
117447
|
+
try {
|
|
117448
|
+
await makeSvc(options.json).stampPlanArtifactPath(runId2, planArtifactPath);
|
|
117449
|
+
} catch {}
|
|
117450
|
+
const asyncResult = { runId: runId2, status: "started", workflowName: file2, planArtifactPath };
|
|
117144
117451
|
if (json3)
|
|
117145
117452
|
context4.output.write(toEnvelopeJson(asyncResult, { enveloped: options.jsonEnvelope }));
|
|
117146
117453
|
else if (!silent) {
|
|
117147
117454
|
context4.output.write(`Started async run: ${runId2}
|
|
117455
|
+
Plan: ${planArtifactPath}
|
|
117148
117456
|
Monitor with: spur workflow trace ${runId2} --follow`);
|
|
117149
117457
|
}
|
|
117150
117458
|
context4.setExitCode(0);
|
|
@@ -117172,19 +117480,49 @@ Monitor with: spur workflow trace ${runId2} --follow`);
|
|
|
117172
117480
|
traceWriter = new WorkflowTraceWriter(context4.cwd, runId);
|
|
117173
117481
|
traceWriter.attach(bus);
|
|
117174
117482
|
}
|
|
117175
|
-
|
|
117176
|
-
|
|
117483
|
+
const expectedDigest = process.env.SPUR_EXPECTED_DEFINITION_DIGEST;
|
|
117484
|
+
let resolvedDefinition;
|
|
117485
|
+
if (options.plan !== false || expectedDigest !== undefined) {
|
|
117177
117486
|
try {
|
|
117178
|
-
|
|
117179
|
-
|
|
117180
|
-
|
|
117181
|
-
|
|
117487
|
+
resolvedDefinition = await resolveWorkflowDefinition(context4.cwd, file2, {
|
|
117488
|
+
embeddedSchemas: EMBEDDED_SPUR_SCHEMAS
|
|
117489
|
+
});
|
|
117490
|
+
} catch (error51) {
|
|
117491
|
+
if (expectedDigest !== undefined) {
|
|
117492
|
+
writeJsonError(context4.output, options, `workflow run: refusing to start \u2014 ${error51 instanceof Error ? error51.message : String(error51)}`, "VALIDATION_FAILED");
|
|
117493
|
+
context4.setExitCode(1);
|
|
117494
|
+
return;
|
|
117182
117495
|
}
|
|
117183
|
-
}
|
|
117496
|
+
}
|
|
117497
|
+
}
|
|
117498
|
+
const mismatch = expectedDigestMismatch(resolvedDefinition?.digest, expectedDigest);
|
|
117499
|
+
if (mismatch !== null) {
|
|
117500
|
+
writeJsonError(context4.output, options, mismatch, "VALIDATION_FAILED");
|
|
117501
|
+
context4.setExitCode(1);
|
|
117502
|
+
return;
|
|
117503
|
+
}
|
|
117504
|
+
let planPreview;
|
|
117505
|
+
if (options.plan !== false && resolvedDefinition !== undefined) {
|
|
117506
|
+
planPreview = renderRunPlan(resolvedDefinition.workflow);
|
|
117507
|
+
}
|
|
117508
|
+
if (resolvedDefinition !== undefined) {
|
|
117509
|
+
const executors = context4.spurConfig?.agent?.executors;
|
|
117510
|
+
const flow = resolvedDefinition.workflow;
|
|
117511
|
+
const agentRunActions = flow.kind === "transition-flow" ? flow.nodes.flatMap((node) => node.action?.kind === "agent.run" ? [node.action] : []) : flow.states.flatMap((state) => (state.onEnter ?? []).filter((a2) => a2.kind === "agent.run"));
|
|
117512
|
+
for (const action of agentRunActions) {
|
|
117513
|
+
const pin = action.options?.agent;
|
|
117514
|
+
const requires = parseRequiresCapabilities(action.options?.requiresCapabilities);
|
|
117515
|
+
if (typeof pin !== "string" || !requires.ok)
|
|
117516
|
+
continue;
|
|
117517
|
+
const evaluation = evaluateCapabilities(requires.requires, executors?.find((e) => e.name === pin));
|
|
117518
|
+
if (!evaluation.ok) {
|
|
117519
|
+
context4.output.error(`Warning: capability preflight (0777 R4): ${capabilityDiagnostic(pin, evaluation)}`);
|
|
117520
|
+
}
|
|
117521
|
+
}
|
|
117184
117522
|
}
|
|
117185
117523
|
const runLog = options.log === false ? undefined : new WorkflowRunLogSink({
|
|
117186
117524
|
bus,
|
|
117187
|
-
dir:
|
|
117525
|
+
dir: join36(context4.cwd, ".spur", "run"),
|
|
117188
117526
|
runId,
|
|
117189
117527
|
...planPreview !== undefined ? { planPreview } : {},
|
|
117190
117528
|
...resolveOutputLogConfig(context4.spurConfig ?? null)
|
|
@@ -117234,15 +117572,9 @@ Monitor with: spur workflow trace ${runId2} --follow`);
|
|
|
117234
117572
|
}
|
|
117235
117573
|
const steeringController = options.steer === true ? await (async () => {
|
|
117236
117574
|
let identity7;
|
|
117237
|
-
|
|
117238
|
-
|
|
117239
|
-
|
|
117240
|
-
const def = await loadWorkflowDef(resolved.path, {
|
|
117241
|
-
validateSchema: false
|
|
117242
|
-
});
|
|
117243
|
-
identity7 = createWorkflowEventIdentity(def);
|
|
117244
|
-
}
|
|
117245
|
-
} catch {}
|
|
117575
|
+
if (resolvedDefinition !== undefined) {
|
|
117576
|
+
identity7 = createWorkflowEventIdentity(resolvedDefinition.workflow);
|
|
117577
|
+
}
|
|
117246
117578
|
return new WorkflowSteeringController((ack) => {
|
|
117247
117579
|
bus.emit("workflow.steering", identity7 === undefined ? ack : decorateWorkflowEvent(identity7, "workflow.steering", ack));
|
|
117248
117580
|
context4.output.write(`[steer] ${ack.accepted ? "ack" : "nack"} ${ack.operation} \xB7 ${ack.reason ?? `version=${ack.version}`}`);
|
|
@@ -117264,7 +117596,8 @@ Monitor with: spur workflow trace ${runId2} --follow`);
|
|
|
117264
117596
|
vars,
|
|
117265
117597
|
dryRun: options.dryRun || undefined,
|
|
117266
117598
|
recordSelfPid: process.env.SPUR_ASYNC_WORKER === "1",
|
|
117267
|
-
...steeringController !== undefined ? { steeringController } : {}
|
|
117599
|
+
...steeringController !== undefined ? { steeringController } : {},
|
|
117600
|
+
...resolvedDefinition !== undefined ? { resolvedDefinition } : {}
|
|
117268
117601
|
});
|
|
117269
117602
|
} finally {
|
|
117270
117603
|
clearWorkflowRunActive();
|
|
@@ -117342,7 +117675,7 @@ Monitor with: spur workflow trace ${runId2} --follow`);
|
|
|
117342
117675
|
ledger.unsubscribe();
|
|
117343
117676
|
}
|
|
117344
117677
|
});
|
|
117345
|
-
workflow.command("clean").description("Housekeeping: finalize orphaned runs stuck in running/pending past a staleness threshold
|
|
117678
|
+
workflow.command("clean").description("Housekeeping: finalize orphaned runs stuck in running/pending past a staleness threshold (mark as failed) and reclaim retained run logs older than workflow.logRetentionDays. `--logs` scopes to log reclamation only. To cancel a single live run by id, use `spur workflow cancel <run-id>` instead.").option("--older-than <minutes>", "Staleness threshold in minutes (stale-run scope only)", "30").option(...SHARED_OPTIONS.forceWorkflowClean).option("--logs", "Scope to retained run-log reclamation only (skip stale-run finalization)").option(...SHARED_OPTIONS.dryRunWorkflowClean).option(...SHARED_OPTIONS.jsonSupported).option(...SHARED_OPTIONS.jsonEnvelope).action(async (options) => {
|
|
117346
117679
|
const dryRun = options.dryRun === true;
|
|
117347
117680
|
const logsOnly = options.logs === true;
|
|
117348
117681
|
const force = options.force === true;
|
|
@@ -117441,21 +117774,25 @@ Monitor with: spur workflow trace ${runId2} --follow`);
|
|
|
117441
117774
|
context4.setExitCode(1);
|
|
117442
117775
|
return;
|
|
117443
117776
|
}
|
|
117444
|
-
|
|
117445
|
-
let def;
|
|
117777
|
+
let resolvedDefinition;
|
|
117446
117778
|
try {
|
|
117447
|
-
|
|
117779
|
+
resolvedDefinition = await resolveWorkflowDefinition(context4.cwd, file2, {
|
|
117780
|
+
embeddedSchemas: EMBEDDED_SPUR_SCHEMAS
|
|
117781
|
+
});
|
|
117448
117782
|
} catch (err) {
|
|
117449
117783
|
writeJsonError(context4.output, options, `workflow show: cannot read or parse ${file2} \u2014 ${err instanceof Error ? err.message : String(err)}`, "VALIDATION_FAILED");
|
|
117450
117784
|
context4.setExitCode(1);
|
|
117451
117785
|
return;
|
|
117452
117786
|
}
|
|
117787
|
+
const def = resolvedDefinition.workflow;
|
|
117453
117788
|
if (options.json) {
|
|
117454
117789
|
if (options.format === "todo") {
|
|
117455
117790
|
context4.output.write(toJson2({
|
|
117456
117791
|
name: def.name,
|
|
117457
117792
|
kind: def.kind ?? "state-machine",
|
|
117458
117793
|
format: "todo",
|
|
117794
|
+
version: resolvedDefinition.workflow.version ?? null,
|
|
117795
|
+
definitionDigest: resolvedDefinition.digest,
|
|
117459
117796
|
steps: buildWorkflowSteps(def)
|
|
117460
117797
|
}));
|
|
117461
117798
|
} else {
|
|
@@ -117463,6 +117800,8 @@ Monitor with: spur workflow trace ${runId2} --follow`);
|
|
|
117463
117800
|
name: def.name,
|
|
117464
117801
|
kind: def.kind ?? "state-machine",
|
|
117465
117802
|
format: "mermaid",
|
|
117803
|
+
version: resolvedDefinition.workflow.version ?? null,
|
|
117804
|
+
definitionDigest: resolvedDefinition.digest,
|
|
117466
117805
|
diagram: renderWorkflowMermaid(def)
|
|
117467
117806
|
}));
|
|
117468
117807
|
}
|
|
@@ -117710,7 +118049,7 @@ function readRunLogChunk(logPath, offset) {
|
|
|
117710
118049
|
}
|
|
117711
118050
|
}
|
|
117712
118051
|
async function followRunLog(service, runId, dir, pollMs, write, wait = (ms) => sleep5(ms)) {
|
|
117713
|
-
const logPath =
|
|
118052
|
+
const logPath = join36(dir, ".spur", "run", `${runId}.log`);
|
|
117714
118053
|
let offset = 0;
|
|
117715
118054
|
let everRead = false;
|
|
117716
118055
|
while (true) {
|
|
@@ -117773,7 +118112,7 @@ init_src();
|
|
|
117773
118112
|
init_src2();
|
|
117774
118113
|
init_dist5();
|
|
117775
118114
|
await init_src3();
|
|
117776
|
-
import { dirname as dirname22, join as
|
|
118115
|
+
import { dirname as dirname22, join as join37, resolve as resolve17 } from "path";
|
|
117777
118116
|
import { isatty as isatty4 } from "tty";
|
|
117778
118117
|
|
|
117779
118118
|
// ../../node_modules/.bun/@clack+core@1.4.1/node_modules/@clack/core/dist/index.mjs
|
|
@@ -119188,7 +119527,7 @@ function resolveAgentRoles(agentConfig) {
|
|
|
119188
119527
|
return resolved;
|
|
119189
119528
|
}
|
|
119190
119529
|
function createCliContext(options) {
|
|
119191
|
-
const cwd =
|
|
119530
|
+
const cwd = resolve17(options.cwd ?? process.cwd());
|
|
119192
119531
|
const env = options.env ?? process.env;
|
|
119193
119532
|
const fs3 = createNodeFileSystem3(cwd);
|
|
119194
119533
|
const agentConfig = options.agentConfig ?? options.spurConfig?.agent;
|
|
@@ -119236,7 +119575,7 @@ function createCliContext(options) {
|
|
|
119236
119575
|
function noopSetExitCode(_code) {}
|
|
119237
119576
|
async function createMigratedDbAdapter(cwd = process.cwd(), env = process.env, dbUrl) {
|
|
119238
119577
|
const config4 = buildConfigFromEnv(env);
|
|
119239
|
-
const configuredUrl = env.DATABASE_URL === undefined ?
|
|
119578
|
+
const configuredUrl = env.DATABASE_URL === undefined ? join37(cwd, DEFAULT_DATABASE_URL) : config4.database.url;
|
|
119240
119579
|
const url2 = dbUrl ?? configuredUrl;
|
|
119241
119580
|
if (url2 !== IN_MEMORY_DATABASE_URL) {
|
|
119242
119581
|
await createNodeFileSystem3().ensureDir(dirname22(url2));
|
|
@@ -119317,10 +119656,12 @@ async function runCommandDispatch(argv, context4, output2) {
|
|
|
119317
119656
|
registerRuleCommand(program2, context4);
|
|
119318
119657
|
const selfCommand = program2.command("self").summary("inspect and manage the Spur installation itself");
|
|
119319
119658
|
registerInitCommand(selfCommand, context4);
|
|
119659
|
+
registerMaintainCommand(selfCommand, context4);
|
|
119320
119660
|
registerMigrateCommand(selfCommand, context4);
|
|
119321
119661
|
registerServeCommand(selfCommand, context4);
|
|
119322
119662
|
registerStatusCommand(selfCommand, context4);
|
|
119323
119663
|
registerInitCommand(program2, context4, { hidden: true });
|
|
119664
|
+
registerMaintainCommand(program2, context4, { hidden: true });
|
|
119324
119665
|
registerMigrateCommand(program2, context4, { hidden: true });
|
|
119325
119666
|
registerServeCommand(program2, context4, { hidden: true });
|
|
119326
119667
|
registerStatusCommand(program2, context4, { hidden: true });
|