@gobing-ai/spur 0.3.13 → 0.3.15
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/config/workflows/task-lifecycle.yaml +10 -0
- package/config/workflows/wayfinder-resolution.yaml +199 -0
- package/package.json +1 -1
- package/spur.js +353 -98
- package/web/_astro/BoardApp.BdyUxAQU.js +1 -0
- package/web/_astro/{BoardApp.CqBOrPzf.js → BoardApp.oOZMY52P.js} +65 -65
- package/web/_astro/{TaskDetail.BmxycoeS.js → TaskDetail.DZVfe6fy.js} +1 -1
- package/web/_astro/{arc.-ZkWbWcN.js → arc.CmzWyu5T.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.fJ8ETc6n.js → architectureDiagram-3BPJPVTR.DEvXeSWP.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.CdhoveXr.js → blockDiagram-GPEHLZMM.DULgqAsN.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.CWv7Jy0p.js → c4Diagram-AAUBKEIU.CtwqGUxK.js} +1 -1
- package/web/_astro/channel.C6f9ncOV.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.CvYWDwal.js → chunk-2J33WTMH.lyKyWJjV.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.BVp_y1oc.js → chunk-4BX2VUAB.COAavIDd.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.CQCviGQk.js → chunk-55IACEB6.EoRbsXM1.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.B3E6Bisv.js → chunk-727SXJPM.DhjfPs_B.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.BvWfYj9E.js → chunk-AQP2D5EJ.BgSRWTUm.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.DcLP2Ypw.js → chunk-FMBD7UC4.Dj0MuLrK.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.DvXZJEqI.js → chunk-ND2GUHAM.B2_roVTb.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.D4rK2HTI.js → chunk-QZHKN3VN.Cr3PSROo.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.DSCYtdVf.js → classDiagram-4FO5ZUOK.CXEuLRp6.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.DSCYtdVf.js → classDiagram-v2-Q7XG4LA2.CXEuLRp6.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.Bf7HMV4Q.js → cose-bilkent-S5V4N54A._ulIkUyI.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.DsBoJTBN.js → dagre-BM42HDAG.D5SRsv4S.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.BMVxdKV3.js → diagram-2AECGRRQ.rMwI7OR9.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.BllW5zst.js → diagram-5GNKFQAL.DWZ2ui2V.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.CNHoSD6q.js → diagram-KO2AKTUF.DuqJ_D0B.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.DZT6otFZ.js → diagram-LMA3HP47.BDxKBl9B.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.EwFWHBRO.js → diagram-OG6HWLK6.D6IMfAPf.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.CaXPcR5W.js → erDiagram-TEJ5UH35.DSsh9EFG.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.FEcfML44.js → flowDiagram-I6XJVG4X.DwjNzc4e.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.Bs7t3AQM.js → ganttDiagram-6RSMTGT7.DbYs_nXA.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.DmxLV9JC.js → gitGraphDiagram-PVQCEYII.DX5-xqVb.js} +1 -1
- package/web/_astro/{infoDiagram-5YYISTIA.E7_NrFUE.js → infoDiagram-5YYISTIA.jP6EGbwr.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.Bs5Q4r-U.js → ishikawaDiagram-YF4QCWOH.Cn89YzBJ.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.D3U7T1y1.js → journeyDiagram-JHISSGLW.CNT2WvMT.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.DfdWsO2E.js → kanban-definition-UN3LZRKU.CLEZjEjD.js} +1 -1
- package/web/_astro/{linear.CAqUTUf3.js → linear.Cn4ewR2z.js} +1 -1
- package/web/_astro/{mermaid.core.ZlPUJ21K.js → mermaid.core.BEACwKCZ.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.ClTJkcTG.js → mindmap-definition-RKZ34NQL.BE9cnWaH.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.B9kJfBw_.js → pieDiagram-4H26LBE5.CLcCK_JM.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.CtTJH5Fo.js → quadrantDiagram-W4KKPZXB.qw8LmkgG.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.WVLq9rr2.js → requirementDiagram-4Y6WPE33.D02534qk.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.L_XUdyqe.js → sankeyDiagram-5OEKKPKP.I6nOztAU.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.DCdtsEdj.js → sequenceDiagram-3UESZ5HK.CXTuiAV8.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.-Koi9kLh.js → stateDiagram-AJRCARHV.BV-mNJRM.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.DHXu_l8w.js → stateDiagram-v2-BHNVJYJU.kF2_t5fy.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.Czotaq0j.js → timeline-definition-PNZ67QCA.BiIzvhe5.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.BfEI3zhc.js → vennDiagram-CIIHVFJN.9cR-p_zq.js} +1 -1
- package/web/_astro/{wardley-L42UT6IY.C33mahwT.js → wardley-L42UT6IY.CwwpzGfz.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.D8IzxB1w.js → wardleyDiagram-YWT4CUSO.Q3hswh_f.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.D-cfi9uT.js → xychartDiagram-2RQKCTM6.Co-1OW2E.js} +1 -1
- package/web/index.html +1 -1
- package/web/_astro/BoardApp.XG2FoRlA.js +0 -1
- package/web/_astro/channel.BrBVc6tg.js +0 -1
package/spur.js
CHANGED
|
@@ -48,10 +48,7 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
|
48
48
|
var __require = import.meta.require;
|
|
49
49
|
|
|
50
50
|
// ../../node_modules/.bun/@gobing-ai+ts-utils@0.4.10/node_modules/@gobing-ai/ts-utils/dist/errors.js
|
|
51
|
-
|
|
52
|
-
return error instanceof AppError;
|
|
53
|
-
}
|
|
54
|
-
var ErrorCode, AppError, NotFoundError;
|
|
51
|
+
var ErrorCode;
|
|
55
52
|
var init_errors = __esm(() => {
|
|
56
53
|
ErrorCode = {
|
|
57
54
|
NotFound: "NOT_FOUND",
|
|
@@ -59,20 +56,6 @@ var init_errors = __esm(() => {
|
|
|
59
56
|
Conflict: "CONFLICT",
|
|
60
57
|
Internal: "INTERNAL"
|
|
61
58
|
};
|
|
62
|
-
AppError = class AppError extends Error {
|
|
63
|
-
code;
|
|
64
|
-
constructor(code, message) {
|
|
65
|
-
super(message);
|
|
66
|
-
this.name = "AppError";
|
|
67
|
-
this.code = code;
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
NotFoundError = class NotFoundError extends AppError {
|
|
71
|
-
constructor(message) {
|
|
72
|
-
super(ErrorCode.NotFound, message);
|
|
73
|
-
this.name = "NotFoundError";
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
59
|
});
|
|
77
60
|
|
|
78
61
|
// ../../node_modules/.bun/@gobing-ai+ts-utils@0.4.10/node_modules/@gobing-ai/ts-utils/dist/api-response.js
|
|
@@ -50904,7 +50887,7 @@ class AgentService {
|
|
|
50904
50887
|
return results.some((result) => !result.usable && result.tier === 1) ? 1 : 0;
|
|
50905
50888
|
}
|
|
50906
50889
|
async run(prompt, flags, deps) {
|
|
50907
|
-
const outcome = await this.executeRun(prompt, flags, deps, false);
|
|
50890
|
+
const outcome = await this.executeRun(prompt, flags, deps, { silent: false });
|
|
50908
50891
|
if (!outcome.ok) {
|
|
50909
50892
|
this.ctx.output.error(outcome.message);
|
|
50910
50893
|
return outcome.exitCode;
|
|
@@ -50922,7 +50905,7 @@ class AgentService {
|
|
|
50922
50905
|
return 3;
|
|
50923
50906
|
}
|
|
50924
50907
|
async runCapture(prompt, flags, deps) {
|
|
50925
|
-
const outcome = await this.executeRun(prompt, flags, deps, true);
|
|
50908
|
+
const outcome = await this.executeRun(prompt, flags, deps, { silent: true });
|
|
50926
50909
|
if (!outcome.ok) {
|
|
50927
50910
|
return { exitCode: outcome.exitCode, answer: "" };
|
|
50928
50911
|
}
|
|
@@ -50936,7 +50919,25 @@ class AgentService {
|
|
|
50936
50919
|
stderr: result.stderr
|
|
50937
50920
|
};
|
|
50938
50921
|
}
|
|
50939
|
-
async
|
|
50922
|
+
async runTraced(prompt, flags, deps) {
|
|
50923
|
+
const outcome = await this.executeRun(prompt, flags, deps, { silent: true, nonInteractive: true });
|
|
50924
|
+
if (!outcome.ok) {
|
|
50925
|
+
return { exitCode: outcome.exitCode, stdout: "", message: outcome.message };
|
|
50926
|
+
}
|
|
50927
|
+
const result = outcome.result;
|
|
50928
|
+
const exitCode = result.exitCode === 0 ? 0 : 3;
|
|
50929
|
+
return {
|
|
50930
|
+
exitCode,
|
|
50931
|
+
invocation: outcome.invocation,
|
|
50932
|
+
stdout: result.stdout,
|
|
50933
|
+
stderr: result.stderr,
|
|
50934
|
+
durationMs: result.durationMs,
|
|
50935
|
+
...result.signal !== undefined ? { signal: result.signal } : {}
|
|
50936
|
+
};
|
|
50937
|
+
}
|
|
50938
|
+
async executeRun(prompt, flags, deps, options) {
|
|
50939
|
+
const silent = options.silent;
|
|
50940
|
+
const nonInteractive = options.nonInteractive === true;
|
|
50940
50941
|
const mode = stringFlag(flags, "mode", "text");
|
|
50941
50942
|
if (mode !== "text" && mode !== "json") {
|
|
50942
50943
|
return { ok: false, exitCode: 2, message: `Invalid mode: ${mode} (must be text or json)` };
|
|
@@ -50960,7 +50961,9 @@ class AgentService {
|
|
|
50960
50961
|
return { ok: false, exitCode: 2, message: "Prompt is required" };
|
|
50961
50962
|
}
|
|
50962
50963
|
const jsonOutput = silent || booleanFlag(flags, "json");
|
|
50963
|
-
const
|
|
50964
|
+
const forceBuffered = silent || nonInteractive;
|
|
50965
|
+
const outputPolicy = forceBuffered ? { mode: "buffered" } : jsonOutput ? { mode: "buffered" } : { mode: "stream", isTTY: isatty3(1) };
|
|
50966
|
+
const outputMode = outputPolicy.mode;
|
|
50964
50967
|
const runner = deps?.runner ?? new AiRunner({
|
|
50965
50968
|
processExecutor: new NodeProcessExecutor3({
|
|
50966
50969
|
output: outputPolicy,
|
|
@@ -50979,7 +50982,8 @@ class AgentService {
|
|
|
50979
50982
|
if (!jsonOutput && TIER2_AGENTS.has(agent)) {
|
|
50980
50983
|
this.ctx.output.error(`Warning: ${agent} is a Tier-2 agent (TUI/gateway only)`);
|
|
50981
50984
|
}
|
|
50982
|
-
const
|
|
50985
|
+
const translated = prompt !== undefined && isClaudeStyleSlashCommand(prompt) ? translateSlashCommand(agent, prompt) : undefined;
|
|
50986
|
+
const input = translated ?? prompt;
|
|
50983
50987
|
const purpose = stringFlag(flags, "purpose", "") || undefined;
|
|
50984
50988
|
const tags = parseTagsFlag(flags);
|
|
50985
50989
|
const systemPrompt = stringFlag(flags, "system-prompt", "") || undefined;
|
|
@@ -50996,9 +51000,9 @@ class AgentService {
|
|
|
50996
51000
|
...systemPrompt !== undefined ? { systemPrompt } : {},
|
|
50997
51001
|
...taskId !== undefined ? { taskId } : {}
|
|
50998
51002
|
};
|
|
51003
|
+
let shimCommand;
|
|
50999
51004
|
try {
|
|
51000
|
-
|
|
51001
|
-
const shimCommand = shim.getPromptCommand(promptOptions);
|
|
51005
|
+
shimCommand = typeof runner.buildPromptCommand === "function" ? runner.buildPromptCommand(agent, promptOptions, { cwd: cwd || undefined }) : getAgentShim(agent).getPromptCommand(promptOptions);
|
|
51002
51006
|
if (!jsonOutput) {
|
|
51003
51007
|
const version3 = (await detector.detectOne(agent)).version;
|
|
51004
51008
|
this.ctx.output.error(`\u2699\uFE0F ${agent}${version3 !== null ? ` v${version3}` : ""}
|
|
@@ -51007,6 +51011,21 @@ class AgentService {
|
|
|
51007
51011
|
} catch (error51) {
|
|
51008
51012
|
return { ok: false, exitCode: 2, message: error51 instanceof Error ? error51.message : String(error51) };
|
|
51009
51013
|
}
|
|
51014
|
+
const traceInput = input === undefined ? undefined : traceSafePrompt(input);
|
|
51015
|
+
const invocation = {
|
|
51016
|
+
agent,
|
|
51017
|
+
source: resolved.source,
|
|
51018
|
+
command: shimCommand.command,
|
|
51019
|
+
argv: sanitizeInvocationArgv(shimCommand.args, input, traceInput),
|
|
51020
|
+
...cwd !== "" ? { cwd } : {},
|
|
51021
|
+
mode,
|
|
51022
|
+
outputMode,
|
|
51023
|
+
...timeoutMs !== undefined ? { timeoutMs } : {},
|
|
51024
|
+
continue: continueFlag,
|
|
51025
|
+
stdinInteractive: false,
|
|
51026
|
+
...model !== undefined ? { model } : {},
|
|
51027
|
+
...translated !== undefined && prompt !== undefined ? { translatedFrom: traceSafePrompt(prompt) } : {}
|
|
51028
|
+
};
|
|
51010
51029
|
let result;
|
|
51011
51030
|
const controller = new AbortController;
|
|
51012
51031
|
const onTerminate = () => controller.abort();
|
|
@@ -51024,7 +51043,7 @@ class AgentService {
|
|
|
51024
51043
|
process.off("SIGTERM", onTerminate);
|
|
51025
51044
|
process.off("SIGINT", onTerminate);
|
|
51026
51045
|
}
|
|
51027
|
-
return { ok: true, result };
|
|
51046
|
+
return { ok: true, result, invocation };
|
|
51028
51047
|
}
|
|
51029
51048
|
async resolveAgent(prompt, flags, doctorRunner) {
|
|
51030
51049
|
const raw = stringFlag(flags, "agent", "auto");
|
|
@@ -51148,6 +51167,32 @@ class AgentService {
|
|
|
51148
51167
|
function toJson(value) {
|
|
51149
51168
|
return JSON.stringify(value, null, 2);
|
|
51150
51169
|
}
|
|
51170
|
+
function traceSafePrompt(prompt) {
|
|
51171
|
+
const trimmed = prompt.trim();
|
|
51172
|
+
if (!TRACE_SAFE_SLASH_COMMAND.test(trimmed)) {
|
|
51173
|
+
return `[redacted prompt: ${prompt.length} chars]`;
|
|
51174
|
+
}
|
|
51175
|
+
const [command = "", ...tokens] = trimmed.split(/\s+/);
|
|
51176
|
+
const safeTokens = tokens.map((token) => TRACE_SAFE_SLASH_TOKEN.test(token) ? token : "[redacted]");
|
|
51177
|
+
return [command, ...safeTokens].join(" ");
|
|
51178
|
+
}
|
|
51179
|
+
function sanitizeInvocationArgv(argv, rawInput, traceInput) {
|
|
51180
|
+
let redactNext = false;
|
|
51181
|
+
return argv.map((arg) => {
|
|
51182
|
+
if (redactNext) {
|
|
51183
|
+
redactNext = false;
|
|
51184
|
+
return "[redacted]";
|
|
51185
|
+
}
|
|
51186
|
+
if (SENSITIVE_FLAG.test(arg)) {
|
|
51187
|
+
redactNext = true;
|
|
51188
|
+
return arg;
|
|
51189
|
+
}
|
|
51190
|
+
if (rawInput !== undefined && traceInput !== undefined && arg.includes(rawInput)) {
|
|
51191
|
+
return traceInput;
|
|
51192
|
+
}
|
|
51193
|
+
return arg.replace(SENSITIVE_INLINE_FLAG, "$1[redacted]").replace(/\bBearer\s+\S+/gi, "Bearer [redacted]").replace(/\b(?:sk|ghp|github_pat)-?[A-Za-z0-9_]{12,}\b/g, "[redacted]");
|
|
51194
|
+
});
|
|
51195
|
+
}
|
|
51151
51196
|
function extractPhase(prompt) {
|
|
51152
51197
|
if (prompt === undefined)
|
|
51153
51198
|
return;
|
|
@@ -51248,9 +51293,14 @@ function parseTagsFlag(flags) {
|
|
|
51248
51293
|
const tags = raw.split(",").map((tag) => tag.trim()).filter(Boolean);
|
|
51249
51294
|
return tags.length > 0 ? tags : undefined;
|
|
51250
51295
|
}
|
|
51296
|
+
var TRACE_SAFE_SLASH_COMMAND, TRACE_SAFE_SLASH_TOKEN, SENSITIVE_FLAG, SENSITIVE_INLINE_FLAG;
|
|
51251
51297
|
var init_agent_service = __esm(() => {
|
|
51252
51298
|
init_dist7();
|
|
51253
51299
|
init_dist5();
|
|
51300
|
+
TRACE_SAFE_SLASH_COMMAND = /^(?:\/skill:(?:sp|rd3)-|\/(?:sp|rd3)[:-]|\$(?:sp|rd3)-)[A-Za-z0-9._-]+(?:\s|$)/;
|
|
51301
|
+
TRACE_SAFE_SLASH_TOKEN = /^(?:\d{4}|--(?:auto|next|force|bdd)|--(?:mode|fix|focus)|implement|test|review|verify|all|none|blockers-first|quick|requirements|background|constraints|acceptance)$/;
|
|
51302
|
+
SENSITIVE_FLAG = /^--?(?:api[-_]?key|authorization|credential|password|secret|token)$/i;
|
|
51303
|
+
SENSITIVE_INLINE_FLAG = /^(--?(?:api[-_]?key|authorization|credential|password|secret|token)=).+$/i;
|
|
51254
51304
|
});
|
|
51255
51305
|
|
|
51256
51306
|
// ../../packages/domain/src/analytics/models.ts
|
|
@@ -58399,6 +58449,8 @@ var init_schema4 = __esm(() => {
|
|
|
58399
58449
|
priority: exports_external.enum(PRIORITIES).optional(),
|
|
58400
58450
|
tags: exports_external.array(exports_external.string()).optional(),
|
|
58401
58451
|
dependencies: exports_external.array(exports_external.string()).optional(),
|
|
58452
|
+
done_forced: exports_external.preprocess((v) => typeof v === "string" ? v === "true" : v, exports_external.boolean().optional()),
|
|
58453
|
+
done_reason: exports_external.string().optional(),
|
|
58402
58454
|
created_at: isoDateString,
|
|
58403
58455
|
updated_at: isoDateString
|
|
58404
58456
|
});
|
|
@@ -59046,6 +59098,110 @@ var init_corpus_migrator = __esm(() => {
|
|
|
59046
59098
|
NULL_PRESERVING_KEYS = new Set(["parent_wbs"]);
|
|
59047
59099
|
});
|
|
59048
59100
|
|
|
59101
|
+
// ../../packages/app/src/services/done-transition-guard.ts
|
|
59102
|
+
async function readVerdictArtifact(fs3, runDir, wbs) {
|
|
59103
|
+
const path9 = `${runDir}/${wbs}-verdict.json`;
|
|
59104
|
+
let exists2;
|
|
59105
|
+
try {
|
|
59106
|
+
exists2 = await fs3.exists(path9);
|
|
59107
|
+
} catch {
|
|
59108
|
+
exists2 = true;
|
|
59109
|
+
}
|
|
59110
|
+
if (!exists2) {
|
|
59111
|
+
return { artifact: undefined, path: path9 };
|
|
59112
|
+
}
|
|
59113
|
+
let raw;
|
|
59114
|
+
try {
|
|
59115
|
+
raw = await fs3.readFile(path9);
|
|
59116
|
+
} catch (err) {
|
|
59117
|
+
return { artifact: undefined, path: path9, readError: `unreadable artifact: ${err.message}` };
|
|
59118
|
+
}
|
|
59119
|
+
let parsed;
|
|
59120
|
+
try {
|
|
59121
|
+
parsed = JSON.parse(raw);
|
|
59122
|
+
} catch (err) {
|
|
59123
|
+
return { artifact: undefined, path: path9, readError: `malformed JSON: ${err.message}` };
|
|
59124
|
+
}
|
|
59125
|
+
if (typeof parsed !== "object" || parsed === null || !("verdict" in parsed)) {
|
|
59126
|
+
return { artifact: undefined, path: path9, readError: "missing required `verdict` field" };
|
|
59127
|
+
}
|
|
59128
|
+
return { artifact: parsed, path: path9 };
|
|
59129
|
+
}
|
|
59130
|
+
function computeAggregate(artifact) {
|
|
59131
|
+
const reqs = artifact.requirements ?? [];
|
|
59132
|
+
const acs = artifact.acceptanceCriteria ?? [];
|
|
59133
|
+
if (reqs.length === 0 && acs.length === 0) {
|
|
59134
|
+
return artifact.verdict;
|
|
59135
|
+
}
|
|
59136
|
+
if (reqs.some((r) => r.status === "UNMET") || acs.some((a2) => a2.status === "UNMET")) {
|
|
59137
|
+
return "FAIL";
|
|
59138
|
+
}
|
|
59139
|
+
if (reqs.some((r) => r.status === "PARTIAL") || acs.some((a2) => a2.status === "PARTIAL")) {
|
|
59140
|
+
return "PARTIAL";
|
|
59141
|
+
}
|
|
59142
|
+
return "PASS";
|
|
59143
|
+
}
|
|
59144
|
+
function formatDenialMessage(args) {
|
|
59145
|
+
const { wbs, taskFilePath, verdictPath, verdict, inconsistency, artifact } = args;
|
|
59146
|
+
const lines = [
|
|
59147
|
+
`Cannot transition task ${wbs} to done: verify verdict is ${verdict}.`,
|
|
59148
|
+
` task: ${taskFilePath}`,
|
|
59149
|
+
` verdict: ${verdictPath}`
|
|
59150
|
+
];
|
|
59151
|
+
if (inconsistency) {
|
|
59152
|
+
lines.push(` warning: artifact is self-inconsistent \u2014 stored aggregate ${inconsistency.stored} contradicts rows (computed ${inconsistency.computed}). Treated as non-PASS.`);
|
|
59153
|
+
}
|
|
59154
|
+
const reqCount = artifact?.requirements?.length ?? 0;
|
|
59155
|
+
const acCount = artifact?.acceptanceCriteria?.length ?? 0;
|
|
59156
|
+
const rowCount = reqCount + acCount;
|
|
59157
|
+
if (verdict === "UNKNOWN" && artifact !== undefined && (artifact.source === "spur-task-verdict" || rowCount === 0)) {
|
|
59158
|
+
const source = artifact.source ?? "unknown";
|
|
59159
|
+
lines.push(` source: ${source} artifact contains ${rowCount} structured row${rowCount === 1 ? "" : "s"} ` + `(${reqCount} requirement${reqCount === 1 ? "" : "s"}, ${acCount} AC${acCount === 1 ? "" : "s"}). ` + `UNKNOWN means the verify answer file carried no parseable markdown tables ` + `(\`| Req | Status | Evidence |\` and \`| AC | Status | Evidence Type | Evidence |\`).`);
|
|
59160
|
+
lines.push(` see sp:spur-cli \`tasks/verbs.md\` \xA7Answer-file shape for the expected format. ` + `Re-run \`/sp:dev-verify ${wbs}\` so the skill authors the tables; do not loosen the parser.`);
|
|
59161
|
+
}
|
|
59162
|
+
lines.push(" remediation: re-run `/sp:dev-verify " + wbs + "` until PASS, or override with `spur task update " + wbs + ' done --force-done --reason "<why>"`.');
|
|
59163
|
+
return lines.join(`
|
|
59164
|
+
`);
|
|
59165
|
+
}
|
|
59166
|
+
function formatNoopMessage(wbs, status) {
|
|
59167
|
+
return `${wbs}: already ${status} \u2014 no transition`;
|
|
59168
|
+
}
|
|
59169
|
+
function evaluateDoneTransition(input) {
|
|
59170
|
+
const { wbs, taskFilePath, currentStatus, targetStatus, forced, reason, artifact } = input;
|
|
59171
|
+
if (targetStatus === currentStatus) {
|
|
59172
|
+
return { kind: "noop", fromStatus: currentStatus, message: formatNoopMessage(wbs, currentStatus) };
|
|
59173
|
+
}
|
|
59174
|
+
if (artifact === undefined) {
|
|
59175
|
+
return { kind: "allow", reason: "no-artifact" };
|
|
59176
|
+
}
|
|
59177
|
+
if (forced) {
|
|
59178
|
+
return { kind: "allow", reason: "forced" };
|
|
59179
|
+
}
|
|
59180
|
+
const computed = computeAggregate(artifact);
|
|
59181
|
+
const effective = harshnessMax(artifact.verdict, computed);
|
|
59182
|
+
if (effective === "PASS") {
|
|
59183
|
+
return { kind: "allow", reason: "pass" };
|
|
59184
|
+
}
|
|
59185
|
+
const verdictPath = `.spur/run/${wbs}-verdict.json`;
|
|
59186
|
+
const inconsistency = artifact.verdict !== computed ? { stored: artifact.verdict, computed } : undefined;
|
|
59187
|
+
return {
|
|
59188
|
+
kind: "deny",
|
|
59189
|
+
verdict: effective,
|
|
59190
|
+
message: formatDenialMessage({
|
|
59191
|
+
wbs,
|
|
59192
|
+
taskFilePath,
|
|
59193
|
+
verdictPath,
|
|
59194
|
+
verdict: effective,
|
|
59195
|
+
inconsistency,
|
|
59196
|
+
artifact
|
|
59197
|
+
})
|
|
59198
|
+
};
|
|
59199
|
+
}
|
|
59200
|
+
function harshnessMax(a2, b) {
|
|
59201
|
+
const rank = { PASS: 0, UNKNOWN: 1, PARTIAL: 2, FAIL: 3 };
|
|
59202
|
+
return rank[a2] >= rank[b] ? a2 : b;
|
|
59203
|
+
}
|
|
59204
|
+
|
|
59049
59205
|
// ../../packages/app/src/services/event-names.ts
|
|
59050
59206
|
function event(name, source, renderer, payloadPolicy = "metadata-only", tier = "default") {
|
|
59051
59207
|
return {
|
|
@@ -61467,6 +61623,10 @@ function isPlaceholderBody(body) {
|
|
|
61467
61623
|
const stripped = body.replace(/<!--[\s\S]*?-->/g, "").replace(/^\s*>\s*TBD\s*$/gim, "").trim();
|
|
61468
61624
|
return stripped.length === 0;
|
|
61469
61625
|
}
|
|
61626
|
+
function isPlaceholderCell(cell) {
|
|
61627
|
+
const c3 = cell.trim();
|
|
61628
|
+
return c3 === "" || /^[\u2014\u2013-]+$/.test(c3) || /^n\/?a$/i.test(c3);
|
|
61629
|
+
}
|
|
61470
61630
|
function hasPopulatedPriorityTable(body) {
|
|
61471
61631
|
for (const line of body.split(`
|
|
61472
61632
|
`)) {
|
|
@@ -61476,7 +61636,7 @@ function hasPopulatedPriorityTable(body) {
|
|
|
61476
61636
|
const severityIdx = cells.findIndex((c3) => /^\s*P[1-4]\s*$/.test(c3));
|
|
61477
61637
|
if (severityIdx === -1)
|
|
61478
61638
|
continue;
|
|
61479
|
-
const hasContent = cells.some((c3, i2) => i2 !== severityIdx && c3
|
|
61639
|
+
const hasContent = cells.some((c3, i2) => i2 !== severityIdx && !isPlaceholderCell(c3));
|
|
61480
61640
|
if (hasContent)
|
|
61481
61641
|
return true;
|
|
61482
61642
|
}
|
|
@@ -61498,7 +61658,7 @@ function isReviewScaffold(body) {
|
|
|
61498
61658
|
continue;
|
|
61499
61659
|
const cells = line.split("|").map((c3) => c3.trim());
|
|
61500
61660
|
const isSeparator = cells.every((c3) => c3 === "" || /^:?-+:?$/.test(c3));
|
|
61501
|
-
const isEmptyPRow = cells.every((c3) => c3
|
|
61661
|
+
const isEmptyPRow = cells.every((c3) => isPlaceholderCell(c3) || /^P[1-4]$/.test(c3)) && cells.some((c3) => /^P[1-4]$/.test(c3));
|
|
61502
61662
|
const isHeader = cells.some((c3) => /severity|file|finding|recommendation/i.test(c3));
|
|
61503
61663
|
if (isEmptyPRow)
|
|
61504
61664
|
sawEmptyPRow = true;
|
|
@@ -61663,6 +61823,18 @@ var init_task_check = __esm(() => {
|
|
|
61663
61823
|
}
|
|
61664
61824
|
}
|
|
61665
61825
|
async runL4(doc2, fm, findings, featuresDir, tasksDir) {
|
|
61826
|
+
const template = typeof fm.template === "string" ? fm.template : DEFAULT_TASK_VARIANT;
|
|
61827
|
+
if (template === DEFAULT_TASK_VARIANT) {
|
|
61828
|
+
const designBody = doc2.getSection("Design");
|
|
61829
|
+
if (designBody !== null && isPlaceholderBody(designBody)) {
|
|
61830
|
+
findings.push({
|
|
61831
|
+
layer: "L4",
|
|
61832
|
+
severity: "warning",
|
|
61833
|
+
section: "Design",
|
|
61834
|
+
message: "Design section is present but empty (placeholder-only) \u2014 populate it so the task retains an explicit implementation approach"
|
|
61835
|
+
});
|
|
61836
|
+
}
|
|
61837
|
+
}
|
|
61666
61838
|
const featureId2 = fm.feature_id ?? fm["feature-id"];
|
|
61667
61839
|
const parentWbs = fm.parent_wbs ?? fm["parent-wbs"];
|
|
61668
61840
|
const deps = fm.dependencies;
|
|
@@ -62360,9 +62532,15 @@ class TaskService {
|
|
|
62360
62532
|
return this.writeService.transition(ref, toStatus, actor ?? this.ctx.actor ?? "system");
|
|
62361
62533
|
}
|
|
62362
62534
|
async updateField(wbs, key, value) {
|
|
62363
|
-
const allowed =
|
|
62364
|
-
|
|
62365
|
-
|
|
62535
|
+
const allowed = {
|
|
62536
|
+
feature_id: true,
|
|
62537
|
+
parent_wbs: true,
|
|
62538
|
+
priority: true,
|
|
62539
|
+
done_forced: true,
|
|
62540
|
+
done_reason: true
|
|
62541
|
+
};
|
|
62542
|
+
if (!(key in allowed)) {
|
|
62543
|
+
throw new Error(`Field "${key}" is not settable via update; allowed: ${Object.keys(allowed).join(", ")}.`);
|
|
62366
62544
|
}
|
|
62367
62545
|
const filePath = await this.resolveTaskFile(wbs);
|
|
62368
62546
|
const ref = { kind: "task", id: wbs, filePath, folder: this.ctx.tasksDir };
|
|
@@ -63710,55 +63888,46 @@ class AgentRunActionRunner {
|
|
|
63710
63888
|
const expectFile = asOptionalString(options.expectFile);
|
|
63711
63889
|
const capture = asOptionalBoolean(options.capture) || answerFile !== undefined;
|
|
63712
63890
|
const agentLabel = agent ?? "<default>";
|
|
63713
|
-
|
|
63714
|
-
|
|
63715
|
-
const { exitCode: exitCode2, answer } = captured;
|
|
63716
|
-
const ok2 = exitCode2 === 0;
|
|
63717
|
-
if (answerFile !== undefined) {
|
|
63718
|
-
const target = isAbsolute2(answerFile) ? answerFile : join12(cwd, answerFile);
|
|
63719
|
-
await mkdir(dirname10(target), { recursive: true });
|
|
63720
|
-
await writeFile(target, answer, "utf8");
|
|
63721
|
-
}
|
|
63722
|
-
if (ok2 && expectFile !== undefined) {
|
|
63723
|
-
const target = isAbsolute2(expectFile) ? expectFile : join12(cwd, expectFile);
|
|
63724
|
-
if (!existsSync7(target)) {
|
|
63725
|
-
return {
|
|
63726
|
-
ok: false,
|
|
63727
|
-
data: { exitCode: exitCode2, agent: agentLabel, answer },
|
|
63728
|
-
error: `agent.run (${agentLabel}) exited 0 but expected file is absent: ${expectFile}`
|
|
63729
|
-
};
|
|
63730
|
-
}
|
|
63731
|
-
}
|
|
63732
|
-
if (!ok2) {
|
|
63733
|
-
await writePartialWorkArtifact(context4, agentLabel, model, captured, cwd);
|
|
63734
|
-
}
|
|
63735
|
-
return {
|
|
63736
|
-
ok: ok2,
|
|
63737
|
-
data: { exitCode: exitCode2, agent: agentLabel, answer },
|
|
63738
|
-
error: ok2 ? undefined : `agent.run (${agentLabel}) exited with code ${exitCode2}`,
|
|
63739
|
-
setVars: ok2 ? { __agentSession: "open" } : undefined
|
|
63740
|
-
};
|
|
63741
|
-
}
|
|
63742
|
-
const exitCode = await this.agentService.run(input, flags);
|
|
63891
|
+
const traced = await this.agentService.runTraced(input, flags);
|
|
63892
|
+
const { exitCode, stdout: answer } = traced;
|
|
63743
63893
|
const ok = exitCode === 0;
|
|
63894
|
+
const invocation = traced.invocation;
|
|
63895
|
+
if (capture && answerFile !== undefined) {
|
|
63896
|
+
const target = isAbsolute2(answerFile) ? answerFile : join12(cwd, answerFile);
|
|
63897
|
+
await mkdir(dirname10(target), { recursive: true });
|
|
63898
|
+
await writeFile(target, answer, "utf8");
|
|
63899
|
+
}
|
|
63744
63900
|
if (ok && expectFile !== undefined) {
|
|
63745
63901
|
const target = isAbsolute2(expectFile) ? expectFile : join12(cwd, expectFile);
|
|
63746
63902
|
if (!existsSync7(target)) {
|
|
63747
63903
|
return {
|
|
63748
63904
|
ok: false,
|
|
63749
|
-
data:
|
|
63905
|
+
data: buildResultData(exitCode, agentLabel, capture, answer, invocation),
|
|
63750
63906
|
error: `agent.run (${agentLabel}) exited 0 but expected file is absent: ${expectFile}`
|
|
63751
63907
|
};
|
|
63752
63908
|
}
|
|
63753
63909
|
}
|
|
63910
|
+
if (!ok) {
|
|
63911
|
+
await writePartialWorkArtifact(context4, agentLabel, model, traced, cwd);
|
|
63912
|
+
}
|
|
63913
|
+
const stepLabel = context4.stateOrNodeId;
|
|
63914
|
+
const error51 = ok ? undefined : traced.signal !== undefined ? timeoutMs !== undefined ? `agent.run '${stepLabel}' (${agentLabel}) terminated by signal ${traced.signal} (configured timeout: ${timeoutMs}ms; timeout or cancellation); see partial-work artifact` : `agent.run '${stepLabel}' (${agentLabel}) was cancelled by signal ${traced.signal}; see partial-work artifact` : traced.message !== undefined ? `agent.run '${stepLabel}' (${agentLabel}) dispatch failed: ${traced.message}` : `agent.run '${stepLabel}' (${agentLabel}) exited with code ${exitCode}`;
|
|
63754
63915
|
return {
|
|
63755
63916
|
ok,
|
|
63756
|
-
data:
|
|
63757
|
-
error:
|
|
63917
|
+
data: buildResultData(exitCode, agentLabel, capture, answer, invocation),
|
|
63918
|
+
error: error51,
|
|
63758
63919
|
setVars: ok ? { __agentSession: "open" } : undefined
|
|
63759
63920
|
};
|
|
63760
63921
|
}
|
|
63761
63922
|
}
|
|
63923
|
+
function buildResultData(exitCode, agentLabel, capture, answer, invocation) {
|
|
63924
|
+
const data = { exitCode, agent: agentLabel };
|
|
63925
|
+
if (capture)
|
|
63926
|
+
data.answer = answer;
|
|
63927
|
+
if (invocation !== undefined)
|
|
63928
|
+
data.invocation = invocation;
|
|
63929
|
+
return data;
|
|
63930
|
+
}
|
|
63762
63931
|
function asOptionalString(value) {
|
|
63763
63932
|
if (value === undefined || value === null)
|
|
63764
63933
|
return;
|
|
@@ -63786,13 +63955,16 @@ function asOptionalNumber(value) {
|
|
|
63786
63955
|
}
|
|
63787
63956
|
return;
|
|
63788
63957
|
}
|
|
63789
|
-
async function writePartialWorkArtifact(context4, agentLabel, model,
|
|
63958
|
+
async function writePartialWorkArtifact(context4, agentLabel, model, traced, cwd) {
|
|
63790
63959
|
try {
|
|
63791
|
-
const
|
|
63960
|
+
const signal = traced.signal;
|
|
63961
|
+
const exitReason = signal !== undefined ? `killed by signal ${signal} (likely timeout or cancellation)` : traced.message !== undefined ? `dispatch error: ${traced.message}` : `exited with code ${traced.exitCode}`;
|
|
63792
63962
|
const diffStat = await gitDiffStat(cwd);
|
|
63793
|
-
const stdoutTail = tail(
|
|
63794
|
-
const stderrTail = tail(
|
|
63963
|
+
const stdoutTail = tail(traced.stdout, PARTIAL_ARTIFACT_TAIL_CHARS);
|
|
63964
|
+
const stderrTail = tail(traced.stderr ?? "", PARTIAL_ARTIFACT_TAIL_CHARS);
|
|
63795
63965
|
const headerLine = model !== undefined ? `${agentLabel} (model: ${model})` : agentLabel;
|
|
63966
|
+
const inv = traced.invocation;
|
|
63967
|
+
const argvLine = inv ? `${inv.command} ${inv.argv.join(" ")}` : "(invocation not captured)";
|
|
63796
63968
|
const body = [
|
|
63797
63969
|
`# Partial-work handoff \u2014 ${headerLine}`,
|
|
63798
63970
|
"",
|
|
@@ -63801,7 +63973,18 @@ async function writePartialWorkArtifact(context4, agentLabel, model, captured, c
|
|
|
63801
63973
|
`- agent: ${agentLabel}`,
|
|
63802
63974
|
`- model: ${model ?? "(default)"}`,
|
|
63803
63975
|
`- exit reason: ${exitReason}`,
|
|
63804
|
-
`- elapsed: ${
|
|
63976
|
+
`- elapsed: ${traced.durationMs ?? "unknown"}ms`,
|
|
63977
|
+
"",
|
|
63978
|
+
"## resolved invocation",
|
|
63979
|
+
"",
|
|
63980
|
+
`- command: ${argvLine}`,
|
|
63981
|
+
`- cwd: ${inv?.cwd ?? "(inherit)"}`,
|
|
63982
|
+
`- mode: ${inv?.mode ?? "unknown"}`,
|
|
63983
|
+
`- timeoutMs: ${inv?.timeoutMs ?? "(none)"}`,
|
|
63984
|
+
`- continue: ${inv?.continue ?? false}`,
|
|
63985
|
+
`- output: ${inv?.outputMode ?? "unknown"}`,
|
|
63986
|
+
`- stdinInteractive: ${inv?.stdinInteractive ?? false}`,
|
|
63987
|
+
`- translatedFrom: ${inv?.translatedFrom ?? "(none)"}`,
|
|
63805
63988
|
"",
|
|
63806
63989
|
"## git diff --stat",
|
|
63807
63990
|
"```",
|
|
@@ -65068,6 +65251,7 @@ __export(exports_src2, {
|
|
|
65068
65251
|
renderReview: () => renderReview,
|
|
65069
65252
|
registerSystemEventTap: () => registerSystemEventTap,
|
|
65070
65253
|
registerSpurBuiltins: () => registerSpurBuiltins,
|
|
65254
|
+
readVerdictArtifact: () => readVerdictArtifact,
|
|
65071
65255
|
readVerdict: () => readVerdict,
|
|
65072
65256
|
parseVerdict: () => parseVerdict,
|
|
65073
65257
|
parsePsOutput: () => parsePsOutput,
|
|
@@ -65077,10 +65261,14 @@ __export(exports_src2, {
|
|
|
65077
65261
|
hitlConfirmDefault: () => hitlConfirmDefault,
|
|
65078
65262
|
hitlAutoApproveEnabled: () => hitlAutoApproveEnabled,
|
|
65079
65263
|
hasPopulatedPriorityTable: () => hasPopulatedPriorityTable,
|
|
65264
|
+
formatNoopMessage: () => formatNoopMessage,
|
|
65265
|
+
formatDenialMessage: () => formatDenialMessage,
|
|
65080
65266
|
extractSystemEventActor: () => extractSystemEventActor,
|
|
65081
65267
|
extractReviewSectionBody: () => extractReviewSectionBody,
|
|
65268
|
+
evaluateDoneTransition: () => evaluateDoneTransition,
|
|
65082
65269
|
deriveVerdict: () => deriveVerdict,
|
|
65083
65270
|
createPsProcessInspector: () => createPsProcessInspector,
|
|
65271
|
+
computeAggregate: () => computeAggregate,
|
|
65084
65272
|
clampToolUseLimit: () => clampToolUseLimit,
|
|
65085
65273
|
bridgeEventBus: () => bridgeEventBus,
|
|
65086
65274
|
WorkflowAppService: () => WorkflowAppService,
|
|
@@ -73122,7 +73310,7 @@ import { join as join15, resolve as resolve5 } from "path";
|
|
|
73122
73310
|
var CLI_CONFIG = {
|
|
73123
73311
|
binaryName: "spur",
|
|
73124
73312
|
binaryLabel: "spur",
|
|
73125
|
-
binaryVersion: "0.3.
|
|
73313
|
+
binaryVersion: "0.3.15",
|
|
73126
73314
|
configDir: ".spur",
|
|
73127
73315
|
configFile: ".spur/config.yaml",
|
|
73128
73316
|
databaseFile: ".spur/spur.db"
|
|
@@ -78973,18 +79161,15 @@ var contextInjector = (appRt) => async function contextInjectorMiddleware(c3, ne
|
|
|
78973
79161
|
await next();
|
|
78974
79162
|
};
|
|
78975
79163
|
|
|
78976
|
-
// ../server/src/middleware/error-handler.ts
|
|
78977
|
-
init_dist();
|
|
78978
|
-
|
|
78979
79164
|
// ../server/src/errors.ts
|
|
78980
|
-
|
|
79165
|
+
init_errors5();
|
|
78981
79166
|
|
|
78982
79167
|
// ../server/src/middleware/error-handler.ts
|
|
78983
79168
|
function hasHttpStatus(err) {
|
|
78984
79169
|
return typeof err === "object" && err !== null && "status" in err && typeof err.status === "number";
|
|
78985
79170
|
}
|
|
78986
79171
|
function isAppErrorLike(err) {
|
|
78987
|
-
return
|
|
79172
|
+
return typeof err === "object" && err !== null && "code" in err && typeof err.code === "string" && "message" in err && typeof err.message === "string";
|
|
78988
79173
|
}
|
|
78989
79174
|
function errorEnvelope(code, message, details) {
|
|
78990
79175
|
return { ok: false, error: { code, message, ...details !== undefined ? { details } : {} } };
|
|
@@ -79661,7 +79846,6 @@ var contract = {
|
|
|
79661
79846
|
|
|
79662
79847
|
// ../server/src/modules/feature/handlers.ts
|
|
79663
79848
|
init_schema4();
|
|
79664
|
-
init_dist();
|
|
79665
79849
|
var os2 = implement(contract);
|
|
79666
79850
|
function createResponseShape(r2) {
|
|
79667
79851
|
return { id: r2.ref.id, filePath: r2.ref.filePath };
|
|
@@ -79684,7 +79868,7 @@ function createFeatureHandlers(ctx) {
|
|
|
79684
79868
|
show: os2.feature.show.handler(async ({ input }) => {
|
|
79685
79869
|
const result = await ctx.featureService().show(input.id);
|
|
79686
79870
|
if (!result)
|
|
79687
|
-
throw new
|
|
79871
|
+
throw new HTTPException(404, { message: `Feature ${input.id} not found` });
|
|
79688
79872
|
return {
|
|
79689
79873
|
ok: true,
|
|
79690
79874
|
data: {
|
|
@@ -79713,7 +79897,7 @@ function createFeatureHandlers(ctx) {
|
|
|
79713
79897
|
const folders = ctx.planningFolders();
|
|
79714
79898
|
const feature = await ctx.featureService().show(input.id);
|
|
79715
79899
|
if (!feature)
|
|
79716
|
-
throw new
|
|
79900
|
+
throw new HTTPException(404, { message: `Feature ${input.id} not found` });
|
|
79717
79901
|
const { FeatureCheckService: FeatureCheckService2 } = await Promise.resolve().then(() => (init_feature_check(), exports_feature_check));
|
|
79718
79902
|
const svc = new FeatureCheckService2(ctx.fs);
|
|
79719
79903
|
return {
|
|
@@ -79727,7 +79911,7 @@ function createFeatureHandlers(ctx) {
|
|
|
79727
79911
|
body: os2.feature.body.handler(async ({ input }) => {
|
|
79728
79912
|
const feature = await ctx.featureService().show(input.id);
|
|
79729
79913
|
if (!feature)
|
|
79730
|
-
throw new
|
|
79914
|
+
throw new HTTPException(404, { message: `Feature ${input.id} not found` });
|
|
79731
79915
|
await ctx.featureService().updateBody(input.id, input.body);
|
|
79732
79916
|
return { ok: true, data: {} };
|
|
79733
79917
|
}),
|
|
@@ -79899,17 +80083,33 @@ function parseLimit(raw2, fallback) {
|
|
|
79899
80083
|
}
|
|
79900
80084
|
|
|
79901
80085
|
// ../server/src/modules/observability/index.ts
|
|
79902
|
-
init_src3();
|
|
79903
80086
|
var ledgerWatcher;
|
|
80087
|
+
var ledgerWatcherLoad;
|
|
79904
80088
|
var watcherPath;
|
|
79905
|
-
function getLedgerWatcher(path9) {
|
|
79906
|
-
if (
|
|
79907
|
-
ledgerWatcher
|
|
79908
|
-
|
|
79909
|
-
|
|
79910
|
-
|
|
80089
|
+
async function getLedgerWatcher(path9) {
|
|
80090
|
+
if (watcherPath === path9) {
|
|
80091
|
+
if (ledgerWatcher)
|
|
80092
|
+
return ledgerWatcher;
|
|
80093
|
+
if (ledgerWatcherLoad)
|
|
80094
|
+
return ledgerWatcherLoad;
|
|
80095
|
+
}
|
|
80096
|
+
ledgerWatcher?.stop();
|
|
80097
|
+
ledgerWatcher = undefined;
|
|
80098
|
+
watcherPath = path9;
|
|
80099
|
+
const load2 = Promise.resolve().then(() => (init_src3(), exports_src2)).then(({ TokenLedgerWatcher: TokenLedgerWatcher2 }) => {
|
|
80100
|
+
const watcher2 = new TokenLedgerWatcher2({ ledgerPath: path9 });
|
|
80101
|
+
watcher2.start();
|
|
80102
|
+
return watcher2;
|
|
80103
|
+
});
|
|
80104
|
+
ledgerWatcherLoad = load2;
|
|
80105
|
+
const watcher = await load2;
|
|
80106
|
+
if (watcherPath === path9 && ledgerWatcherLoad === load2) {
|
|
80107
|
+
ledgerWatcher = watcher;
|
|
80108
|
+
ledgerWatcherLoad = undefined;
|
|
80109
|
+
} else {
|
|
80110
|
+
watcher.stop();
|
|
79911
80111
|
}
|
|
79912
|
-
return
|
|
80112
|
+
return watcher;
|
|
79913
80113
|
}
|
|
79914
80114
|
function toolUseSsePayload(event2) {
|
|
79915
80115
|
return {
|
|
@@ -79924,7 +80124,7 @@ function handleProcesses(ctx) {
|
|
|
79924
80124
|
const snapshot = await ctx.processInventory().snapshot();
|
|
79925
80125
|
return c3.json(snapshot);
|
|
79926
80126
|
} catch (err) {
|
|
79927
|
-
if (err instanceof
|
|
80127
|
+
if (err instanceof Error && "code" in err && err.code === "UNSUPPORTED_PLATFORM") {
|
|
79928
80128
|
return c3.json({ error: err.message, code: err.code }, 501);
|
|
79929
80129
|
}
|
|
79930
80130
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -79943,7 +80143,7 @@ function handleToolUseGet(ctx) {
|
|
|
79943
80143
|
limitRaw = parsed;
|
|
79944
80144
|
}
|
|
79945
80145
|
const before = c3.req.query("before") ?? undefined;
|
|
79946
|
-
const limit =
|
|
80146
|
+
const limit = limitRaw === undefined || !Number.isFinite(limitRaw) || limitRaw <= 0 ? 200 : Math.min(limitRaw, 1000);
|
|
79947
80147
|
const snapshot = ctx.tokenLedger().snapshot({ limit, before });
|
|
79948
80148
|
return c3.json(snapshot);
|
|
79949
80149
|
} catch (err) {
|
|
@@ -79972,7 +80172,7 @@ function handleToolUseStream(ctx) {
|
|
|
79972
80172
|
closeController();
|
|
79973
80173
|
};
|
|
79974
80174
|
const stream = new ReadableStream({
|
|
79975
|
-
start(controller) {
|
|
80175
|
+
async start(controller) {
|
|
79976
80176
|
const encoder = new TextEncoder;
|
|
79977
80177
|
closeController = () => {
|
|
79978
80178
|
try {
|
|
@@ -79985,17 +80185,17 @@ function handleToolUseStream(ctx) {
|
|
|
79985
80185
|
}
|
|
79986
80186
|
signal.addEventListener("abort", teardown);
|
|
79987
80187
|
heartbeatInterval = setInterval(sendSseKeepalive, 15000, closed, controller, encoder);
|
|
79988
|
-
enqueueSseFrame(closed, controller, encoder, {
|
|
79989
|
-
type: "connected",
|
|
79990
|
-
occurredAt: new Date().toISOString()
|
|
79991
|
-
});
|
|
79992
80188
|
try {
|
|
79993
80189
|
const path9 = ctx.tokenLedger().path;
|
|
79994
|
-
const watcher = getLedgerWatcher(path9);
|
|
80190
|
+
const watcher = await getLedgerWatcher(path9);
|
|
79995
80191
|
unsubscribe = watcher.subscribe((event2) => {
|
|
79996
80192
|
if (!enqueueSseFrame(closed, controller, encoder, toolUseSsePayload(event2)))
|
|
79997
80193
|
teardown();
|
|
79998
80194
|
});
|
|
80195
|
+
enqueueSseFrame(closed, controller, encoder, {
|
|
80196
|
+
type: "connected",
|
|
80197
|
+
occurredAt: new Date().toISOString()
|
|
80198
|
+
});
|
|
79999
80199
|
} catch {
|
|
80000
80200
|
enqueueSseFrame(closed, controller, encoder, {
|
|
80001
80201
|
type: "error",
|
|
@@ -80027,7 +80227,6 @@ var observabilityModule = {
|
|
|
80027
80227
|
|
|
80028
80228
|
// ../server/src/modules/task/handlers.ts
|
|
80029
80229
|
init_schema4();
|
|
80030
|
-
init_dist();
|
|
80031
80230
|
var os3 = implement(contract);
|
|
80032
80231
|
function toFilters(query) {
|
|
80033
80232
|
if (!query)
|
|
@@ -80068,7 +80267,7 @@ function createTaskHandlers(ctx) {
|
|
|
80068
80267
|
show: os3.task.show.handler(async ({ input }) => {
|
|
80069
80268
|
const result = await ctx.taskService().show(input.wbs);
|
|
80070
80269
|
if (!result)
|
|
80071
|
-
throw new
|
|
80270
|
+
throw new HTTPException(404, { message: `Task ${input.wbs} not found` });
|
|
80072
80271
|
return {
|
|
80073
80272
|
ok: true,
|
|
80074
80273
|
data: {
|
|
@@ -83546,6 +83745,13 @@ var STATUS_TITLE = {
|
|
|
83546
83745
|
done: "Done",
|
|
83547
83746
|
cancelled: "Canceled"
|
|
83548
83747
|
};
|
|
83748
|
+
function canonicalStatusOrRaw(raw2) {
|
|
83749
|
+
try {
|
|
83750
|
+
return normalizeTaskStatus(raw2);
|
|
83751
|
+
} catch {
|
|
83752
|
+
return raw2;
|
|
83753
|
+
}
|
|
83754
|
+
}
|
|
83549
83755
|
function renderTaskBoard(tasks, boardTitle, color, columns2) {
|
|
83550
83756
|
const lines = ["", ` ${color.blue(`Kanban Board \u2014 ${boardTitle}`)}`, ""];
|
|
83551
83757
|
for (const status of columns2) {
|
|
@@ -83631,7 +83837,7 @@ ${result.content}`);
|
|
|
83631
83837
|
context4.setExitCode(1);
|
|
83632
83838
|
}
|
|
83633
83839
|
});
|
|
83634
|
-
task.command("update").summary("Update a task status or replace a section.").argument("<wbs>", "Task WBS number").argument("[status]", "New status (for lifecycle transition)").option("--section <name>", "Section name to replace").option("--from-file <path>", "File to read section body from (requires --section)").option("--feature <id>", "Set the feature_id frontmatter field (traceability edge)").option("--priority <p>", "Set the priority frontmatter field (P0\u2013P3)").option("--no-lifecycle", "Suppress lifecycle workflow run creation (use during pipeline runs to avoid orphaned lifecycle runs)").option("--folder <path>", "Custom tasks folder").option("--json", "Output machine-readable JSON").action(async (wbs, status, options) => {
|
|
83840
|
+
task.command("update").summary("Update a task status or replace a section.").argument("<wbs>", "Task WBS number").argument("[status]", "New status (for lifecycle transition)").option("--section <name>", "Section name to replace").option("--from-file <path>", "File to read section body from (requires --section)").option("--feature <id>", "Set the feature_id frontmatter field (traceability edge)").option("--priority <p>", "Set the priority frontmatter field (P0\u2013P3)").option("--no-lifecycle", "Suppress lifecycle workflow run creation (use during pipeline runs to avoid orphaned lifecycle runs)").option("--force-done", "Allow transitioning to `done` even when the verify verdict is not PASS; records an override (task 0292)").option("--reason <text>", "Rationale for a forced-done override (paired with --force-done; persisted as done_reason)").option("--verdict-dir <path>", "Directory holding <wbs>-verdict.json artifacts (default: .spur/run)").option("--folder <path>", "Custom tasks folder").option("--json", "Output machine-readable JSON").action(async (wbs, status, options) => {
|
|
83635
83841
|
const svc = await makeService2(context4, options.folder, options.lifecycle === false);
|
|
83636
83842
|
try {
|
|
83637
83843
|
if (options.section !== undefined) {
|
|
@@ -83659,6 +83865,7 @@ ${result.content}`);
|
|
|
83659
83865
|
context4.output.write(`Set ${key}=${value2} on task ${result.ref.id}`);
|
|
83660
83866
|
}
|
|
83661
83867
|
} else if (status !== undefined) {
|
|
83868
|
+
status = canonicalStatusOrRaw(status);
|
|
83662
83869
|
if (options.lifecycle !== false && (status === "done" || status === "testing")) {
|
|
83663
83870
|
const adapter = makeLifecycleAdapter(context4, TASK_LIFECYCLE_PROFILE);
|
|
83664
83871
|
if (adapter === undefined) {
|
|
@@ -83671,11 +83878,59 @@ ${result.content}`);
|
|
|
83671
83878
|
}
|
|
83672
83879
|
}
|
|
83673
83880
|
}
|
|
83881
|
+
let forcedDone = false;
|
|
83882
|
+
let forcedDoneReason;
|
|
83883
|
+
let forcedDoneVerdict;
|
|
83884
|
+
if (status === "done") {
|
|
83885
|
+
const current = await svc.show(wbs);
|
|
83886
|
+
const verdictDir = options.verdictDir ?? join22(context4.cwd, ".spur", "run");
|
|
83887
|
+
const loaded = await readVerdictArtifact(context4.fs, verdictDir, wbs);
|
|
83888
|
+
const guardOutcome = evaluateDoneTransition({
|
|
83889
|
+
wbs,
|
|
83890
|
+
taskFilePath: current.filePath,
|
|
83891
|
+
currentStatus: canonicalStatusOrRaw(String(current.frontmatter.status)),
|
|
83892
|
+
targetStatus: "done",
|
|
83893
|
+
forced: options.forceDone === true,
|
|
83894
|
+
reason: options.reason,
|
|
83895
|
+
artifact: loaded.artifact
|
|
83896
|
+
});
|
|
83897
|
+
if (guardOutcome.kind === "noop") {
|
|
83898
|
+
if (options.json) {
|
|
83899
|
+
context4.output.write(toJson2({ ok: true, noop: true, wbs, status: "done" }));
|
|
83900
|
+
} else {
|
|
83901
|
+
context4.output.write(guardOutcome.message);
|
|
83902
|
+
}
|
|
83903
|
+
return;
|
|
83904
|
+
}
|
|
83905
|
+
if (guardOutcome.kind === "deny") {
|
|
83906
|
+
context4.output.error(guardOutcome.message);
|
|
83907
|
+
context4.setExitCode(1);
|
|
83908
|
+
return;
|
|
83909
|
+
}
|
|
83910
|
+
if (guardOutcome.reason === "forced" && loaded.artifact !== undefined) {
|
|
83911
|
+
forcedDone = true;
|
|
83912
|
+
forcedDoneReason = options.reason;
|
|
83913
|
+
forcedDoneVerdict = loaded.artifact.verdict;
|
|
83914
|
+
}
|
|
83915
|
+
}
|
|
83674
83916
|
const result = await svc.updateStatus(wbs, status);
|
|
83917
|
+
if (forcedDone) {
|
|
83918
|
+
try {
|
|
83919
|
+
await svc.updateField(wbs, "done_forced", "true");
|
|
83920
|
+
if (forcedDoneReason !== undefined && forcedDoneReason.length > 0) {
|
|
83921
|
+
await svc.updateField(wbs, "done_reason", forcedDoneReason);
|
|
83922
|
+
}
|
|
83923
|
+
} catch (auditErr) {
|
|
83924
|
+
context4.output.error(`warning: failed to record done-forced audit fields: ${String(auditErr)}`);
|
|
83925
|
+
}
|
|
83926
|
+
}
|
|
83675
83927
|
if (options.json) {
|
|
83676
83928
|
context4.output.write(toJson2(result));
|
|
83677
83929
|
} else {
|
|
83678
83930
|
context4.output.write(`${result.ref.id}: ${result.fromStatus} \u2192 ${result.toStatus}`);
|
|
83931
|
+
if (forcedDone && forcedDoneVerdict !== undefined) {
|
|
83932
|
+
context4.output.write(`\u24D8 Override recorded: task advanced to done despite ${forcedDoneVerdict} verdict (done_forced=true).`);
|
|
83933
|
+
}
|
|
83679
83934
|
}
|
|
83680
83935
|
if (status === "done" && !options.json) {
|
|
83681
83936
|
const task2 = await svc.show(wbs);
|