@gobing-ai/spur 0.3.14 → 0.3.16
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/package.json +1 -1
- package/spur.js +125 -49
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobing-ai/spur",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.16",
|
|
4
4
|
"description": "Spur CLI — local-first harness for mainstream coding agents: constraint checking, workflow orchestration, agent health, and history analytics. Bun-native; exposes the `spur` command.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"spur",
|
package/spur.js
CHANGED
|
@@ -50887,7 +50887,7 @@ class AgentService {
|
|
|
50887
50887
|
return results.some((result) => !result.usable && result.tier === 1) ? 1 : 0;
|
|
50888
50888
|
}
|
|
50889
50889
|
async run(prompt, flags, deps) {
|
|
50890
|
-
const outcome = await this.executeRun(prompt, flags, deps, false);
|
|
50890
|
+
const outcome = await this.executeRun(prompt, flags, deps, { silent: false });
|
|
50891
50891
|
if (!outcome.ok) {
|
|
50892
50892
|
this.ctx.output.error(outcome.message);
|
|
50893
50893
|
return outcome.exitCode;
|
|
@@ -50905,7 +50905,7 @@ class AgentService {
|
|
|
50905
50905
|
return 3;
|
|
50906
50906
|
}
|
|
50907
50907
|
async runCapture(prompt, flags, deps) {
|
|
50908
|
-
const outcome = await this.executeRun(prompt, flags, deps, true);
|
|
50908
|
+
const outcome = await this.executeRun(prompt, flags, deps, { silent: true });
|
|
50909
50909
|
if (!outcome.ok) {
|
|
50910
50910
|
return { exitCode: outcome.exitCode, answer: "" };
|
|
50911
50911
|
}
|
|
@@ -50919,7 +50919,25 @@ class AgentService {
|
|
|
50919
50919
|
stderr: result.stderr
|
|
50920
50920
|
};
|
|
50921
50921
|
}
|
|
50922
|
-
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;
|
|
50923
50941
|
const mode = stringFlag(flags, "mode", "text");
|
|
50924
50942
|
if (mode !== "text" && mode !== "json") {
|
|
50925
50943
|
return { ok: false, exitCode: 2, message: `Invalid mode: ${mode} (must be text or json)` };
|
|
@@ -50943,7 +50961,9 @@ class AgentService {
|
|
|
50943
50961
|
return { ok: false, exitCode: 2, message: "Prompt is required" };
|
|
50944
50962
|
}
|
|
50945
50963
|
const jsonOutput = silent || booleanFlag(flags, "json");
|
|
50946
|
-
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;
|
|
50947
50967
|
const runner = deps?.runner ?? new AiRunner({
|
|
50948
50968
|
processExecutor: new NodeProcessExecutor3({
|
|
50949
50969
|
output: outputPolicy,
|
|
@@ -50962,7 +50982,8 @@ class AgentService {
|
|
|
50962
50982
|
if (!jsonOutput && TIER2_AGENTS.has(agent)) {
|
|
50963
50983
|
this.ctx.output.error(`Warning: ${agent} is a Tier-2 agent (TUI/gateway only)`);
|
|
50964
50984
|
}
|
|
50965
|
-
const
|
|
50985
|
+
const translated = prompt !== undefined && isClaudeStyleSlashCommand(prompt) ? translateSlashCommand(agent, prompt) : undefined;
|
|
50986
|
+
const input = translated ?? prompt;
|
|
50966
50987
|
const purpose = stringFlag(flags, "purpose", "") || undefined;
|
|
50967
50988
|
const tags = parseTagsFlag(flags);
|
|
50968
50989
|
const systemPrompt = stringFlag(flags, "system-prompt", "") || undefined;
|
|
@@ -50979,9 +51000,9 @@ class AgentService {
|
|
|
50979
51000
|
...systemPrompt !== undefined ? { systemPrompt } : {},
|
|
50980
51001
|
...taskId !== undefined ? { taskId } : {}
|
|
50981
51002
|
};
|
|
51003
|
+
let shimCommand;
|
|
50982
51004
|
try {
|
|
50983
|
-
|
|
50984
|
-
const shimCommand = shim.getPromptCommand(promptOptions);
|
|
51005
|
+
shimCommand = typeof runner.buildPromptCommand === "function" ? runner.buildPromptCommand(agent, promptOptions, { cwd: cwd || undefined }) : getAgentShim(agent).getPromptCommand(promptOptions);
|
|
50985
51006
|
if (!jsonOutput) {
|
|
50986
51007
|
const version3 = (await detector.detectOne(agent)).version;
|
|
50987
51008
|
this.ctx.output.error(`\u2699\uFE0F ${agent}${version3 !== null ? ` v${version3}` : ""}
|
|
@@ -50990,6 +51011,21 @@ class AgentService {
|
|
|
50990
51011
|
} catch (error51) {
|
|
50991
51012
|
return { ok: false, exitCode: 2, message: error51 instanceof Error ? error51.message : String(error51) };
|
|
50992
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
|
+
};
|
|
50993
51029
|
let result;
|
|
50994
51030
|
const controller = new AbortController;
|
|
50995
51031
|
const onTerminate = () => controller.abort();
|
|
@@ -51007,7 +51043,7 @@ class AgentService {
|
|
|
51007
51043
|
process.off("SIGTERM", onTerminate);
|
|
51008
51044
|
process.off("SIGINT", onTerminate);
|
|
51009
51045
|
}
|
|
51010
|
-
return { ok: true, result };
|
|
51046
|
+
return { ok: true, result, invocation };
|
|
51011
51047
|
}
|
|
51012
51048
|
async resolveAgent(prompt, flags, doctorRunner) {
|
|
51013
51049
|
const raw = stringFlag(flags, "agent", "auto");
|
|
@@ -51131,6 +51167,32 @@ class AgentService {
|
|
|
51131
51167
|
function toJson(value) {
|
|
51132
51168
|
return JSON.stringify(value, null, 2);
|
|
51133
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
|
+
}
|
|
51134
51196
|
function extractPhase(prompt) {
|
|
51135
51197
|
if (prompt === undefined)
|
|
51136
51198
|
return;
|
|
@@ -51231,9 +51293,14 @@ function parseTagsFlag(flags) {
|
|
|
51231
51293
|
const tags = raw.split(",").map((tag) => tag.trim()).filter(Boolean);
|
|
51232
51294
|
return tags.length > 0 ? tags : undefined;
|
|
51233
51295
|
}
|
|
51296
|
+
var TRACE_SAFE_SLASH_COMMAND, TRACE_SAFE_SLASH_TOKEN, SENSITIVE_FLAG, SENSITIVE_INLINE_FLAG;
|
|
51234
51297
|
var init_agent_service = __esm(() => {
|
|
51235
51298
|
init_dist7();
|
|
51236
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;
|
|
51237
51304
|
});
|
|
51238
51305
|
|
|
51239
51306
|
// ../../packages/domain/src/analytics/models.ts
|
|
@@ -61556,6 +61623,10 @@ function isPlaceholderBody(body) {
|
|
|
61556
61623
|
const stripped = body.replace(/<!--[\s\S]*?-->/g, "").replace(/^\s*>\s*TBD\s*$/gim, "").trim();
|
|
61557
61624
|
return stripped.length === 0;
|
|
61558
61625
|
}
|
|
61626
|
+
function isPlaceholderCell(cell) {
|
|
61627
|
+
const c3 = cell.trim();
|
|
61628
|
+
return c3 === "" || /^[\u2014\u2013-]+$/.test(c3) || /^n\/?a$/i.test(c3);
|
|
61629
|
+
}
|
|
61559
61630
|
function hasPopulatedPriorityTable(body) {
|
|
61560
61631
|
for (const line of body.split(`
|
|
61561
61632
|
`)) {
|
|
@@ -61565,7 +61636,7 @@ function hasPopulatedPriorityTable(body) {
|
|
|
61565
61636
|
const severityIdx = cells.findIndex((c3) => /^\s*P[1-4]\s*$/.test(c3));
|
|
61566
61637
|
if (severityIdx === -1)
|
|
61567
61638
|
continue;
|
|
61568
|
-
const hasContent = cells.some((c3, i2) => i2 !== severityIdx && c3
|
|
61639
|
+
const hasContent = cells.some((c3, i2) => i2 !== severityIdx && !isPlaceholderCell(c3));
|
|
61569
61640
|
if (hasContent)
|
|
61570
61641
|
return true;
|
|
61571
61642
|
}
|
|
@@ -61587,7 +61658,7 @@ function isReviewScaffold(body) {
|
|
|
61587
61658
|
continue;
|
|
61588
61659
|
const cells = line.split("|").map((c3) => c3.trim());
|
|
61589
61660
|
const isSeparator = cells.every((c3) => c3 === "" || /^:?-+:?$/.test(c3));
|
|
61590
|
-
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));
|
|
61591
61662
|
const isHeader = cells.some((c3) => /severity|file|finding|recommendation/i.test(c3));
|
|
61592
61663
|
if (isEmptyPRow)
|
|
61593
61664
|
sawEmptyPRow = true;
|
|
@@ -63817,55 +63888,46 @@ class AgentRunActionRunner {
|
|
|
63817
63888
|
const expectFile = asOptionalString(options.expectFile);
|
|
63818
63889
|
const capture = asOptionalBoolean(options.capture) || answerFile !== undefined;
|
|
63819
63890
|
const agentLabel = agent ?? "<default>";
|
|
63820
|
-
|
|
63821
|
-
|
|
63822
|
-
const { exitCode: exitCode2, answer } = captured;
|
|
63823
|
-
const ok2 = exitCode2 === 0;
|
|
63824
|
-
if (answerFile !== undefined) {
|
|
63825
|
-
const target = isAbsolute2(answerFile) ? answerFile : join12(cwd, answerFile);
|
|
63826
|
-
await mkdir(dirname10(target), { recursive: true });
|
|
63827
|
-
await writeFile(target, answer, "utf8");
|
|
63828
|
-
}
|
|
63829
|
-
if (ok2 && expectFile !== undefined) {
|
|
63830
|
-
const target = isAbsolute2(expectFile) ? expectFile : join12(cwd, expectFile);
|
|
63831
|
-
if (!existsSync7(target)) {
|
|
63832
|
-
return {
|
|
63833
|
-
ok: false,
|
|
63834
|
-
data: { exitCode: exitCode2, agent: agentLabel, answer },
|
|
63835
|
-
error: `agent.run (${agentLabel}) exited 0 but expected file is absent: ${expectFile}`
|
|
63836
|
-
};
|
|
63837
|
-
}
|
|
63838
|
-
}
|
|
63839
|
-
if (!ok2) {
|
|
63840
|
-
await writePartialWorkArtifact(context4, agentLabel, model, captured, cwd);
|
|
63841
|
-
}
|
|
63842
|
-
return {
|
|
63843
|
-
ok: ok2,
|
|
63844
|
-
data: { exitCode: exitCode2, agent: agentLabel, answer },
|
|
63845
|
-
error: ok2 ? undefined : `agent.run (${agentLabel}) exited with code ${exitCode2}`,
|
|
63846
|
-
setVars: ok2 ? { __agentSession: "open" } : undefined
|
|
63847
|
-
};
|
|
63848
|
-
}
|
|
63849
|
-
const exitCode = await this.agentService.run(input, flags);
|
|
63891
|
+
const traced = await this.agentService.runTraced(input, flags);
|
|
63892
|
+
const { exitCode, stdout: answer } = traced;
|
|
63850
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
|
+
}
|
|
63851
63900
|
if (ok && expectFile !== undefined) {
|
|
63852
63901
|
const target = isAbsolute2(expectFile) ? expectFile : join12(cwd, expectFile);
|
|
63853
63902
|
if (!existsSync7(target)) {
|
|
63854
63903
|
return {
|
|
63855
63904
|
ok: false,
|
|
63856
|
-
data:
|
|
63905
|
+
data: buildResultData(exitCode, agentLabel, capture, answer, invocation),
|
|
63857
63906
|
error: `agent.run (${agentLabel}) exited 0 but expected file is absent: ${expectFile}`
|
|
63858
63907
|
};
|
|
63859
63908
|
}
|
|
63860
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}`;
|
|
63861
63915
|
return {
|
|
63862
63916
|
ok,
|
|
63863
|
-
data:
|
|
63864
|
-
error:
|
|
63917
|
+
data: buildResultData(exitCode, agentLabel, capture, answer, invocation),
|
|
63918
|
+
error: error51,
|
|
63865
63919
|
setVars: ok ? { __agentSession: "open" } : undefined
|
|
63866
63920
|
};
|
|
63867
63921
|
}
|
|
63868
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
|
+
}
|
|
63869
63931
|
function asOptionalString(value) {
|
|
63870
63932
|
if (value === undefined || value === null)
|
|
63871
63933
|
return;
|
|
@@ -63893,13 +63955,16 @@ function asOptionalNumber(value) {
|
|
|
63893
63955
|
}
|
|
63894
63956
|
return;
|
|
63895
63957
|
}
|
|
63896
|
-
async function writePartialWorkArtifact(context4, agentLabel, model,
|
|
63958
|
+
async function writePartialWorkArtifact(context4, agentLabel, model, traced, cwd) {
|
|
63897
63959
|
try {
|
|
63898
|
-
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}`;
|
|
63899
63962
|
const diffStat = await gitDiffStat(cwd);
|
|
63900
|
-
const stdoutTail = tail(
|
|
63901
|
-
const stderrTail = tail(
|
|
63963
|
+
const stdoutTail = tail(traced.stdout, PARTIAL_ARTIFACT_TAIL_CHARS);
|
|
63964
|
+
const stderrTail = tail(traced.stderr ?? "", PARTIAL_ARTIFACT_TAIL_CHARS);
|
|
63902
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)";
|
|
63903
63968
|
const body = [
|
|
63904
63969
|
`# Partial-work handoff \u2014 ${headerLine}`,
|
|
63905
63970
|
"",
|
|
@@ -63908,7 +63973,18 @@ async function writePartialWorkArtifact(context4, agentLabel, model, captured, c
|
|
|
63908
63973
|
`- agent: ${agentLabel}`,
|
|
63909
63974
|
`- model: ${model ?? "(default)"}`,
|
|
63910
63975
|
`- exit reason: ${exitReason}`,
|
|
63911
|
-
`- 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)"}`,
|
|
63912
63988
|
"",
|
|
63913
63989
|
"## git diff --stat",
|
|
63914
63990
|
"```",
|
|
@@ -73234,7 +73310,7 @@ import { join as join15, resolve as resolve5 } from "path";
|
|
|
73234
73310
|
var CLI_CONFIG = {
|
|
73235
73311
|
binaryName: "spur",
|
|
73236
73312
|
binaryLabel: "spur",
|
|
73237
|
-
binaryVersion: "0.3.
|
|
73313
|
+
binaryVersion: "0.3.16",
|
|
73238
73314
|
configDir: ".spur",
|
|
73239
73315
|
configFile: ".spur/config.yaml",
|
|
73240
73316
|
databaseFile: ".spur/spur.db"
|