@gobing-ai/spur 0.3.31 → 0.3.32
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-pipeline.yaml +7 -2
- package/package.json +1 -1
- package/spur.js +93 -31
|
@@ -76,6 +76,8 @@ vars:
|
|
|
76
76
|
# with the same package-manager surface (this monorepo is Bun-only):
|
|
77
77
|
# `--vars '{"qualityGateCmd":"bun run lint && bun run test"}'`. Soft probe, hard recheck,
|
|
78
78
|
# and the fixall slash input all use this same var so the command stays single-sourced.
|
|
79
|
+
# TRUSTED CONFIG ONLY — this string is executed via `sh -c` (see test/test-recheck). Never
|
|
80
|
+
# interpolate untrusted operator/LLM input into qualityGateCmd (task 0436 SECUA residual).
|
|
79
81
|
qualityGateCmd: "bun run autofix && bun run spur-check"
|
|
80
82
|
# Max /sp:dev-fixall attempts after a red quality-gate probe/recheck (bounded; no thrash).
|
|
81
83
|
# Attempt counter: .spur/run/<wbs>-test-fix-attempt. Default 2 = two fixall hops before failed.
|
|
@@ -179,7 +181,9 @@ states:
|
|
|
179
181
|
ATTEMPT_FILE=".spur/run/$wbs-test-fix-attempt" &&
|
|
180
182
|
echo 0 > "$ATTEMPT_FILE" &&
|
|
181
183
|
set +e &&
|
|
182
|
-
(
|
|
184
|
+
# sh -c re-parses multi-command gates (&&, ;); do NOT use bare `$qualityGateCmd`
|
|
185
|
+
# word-split (breaks on &&). qualityGateCmd is pipeline-owned trusted config only.
|
|
186
|
+
( sh -c "$qualityGateCmd" ); gate_rc=$?; set -e &&
|
|
183
187
|
if [ "$gate_rc" -eq 0 ]; then
|
|
184
188
|
printf 'PASS\n' > "$STATUS_FILE";
|
|
185
189
|
else
|
|
@@ -219,7 +223,8 @@ states:
|
|
|
219
223
|
mkdir -p .spur/run &&
|
|
220
224
|
STATUS_FILE=".spur/run/$wbs-test-gate.status" &&
|
|
221
225
|
set +e &&
|
|
222
|
-
|
|
226
|
+
# See test probe: sh -c for multi-command; qualityGateCmd is trusted config only.
|
|
227
|
+
( sh -c "$qualityGateCmd" ); gate_rc=$?; set -e &&
|
|
223
228
|
if [ "$gate_rc" -eq 0 ]; then
|
|
224
229
|
printf 'PASS\n' > "$STATUS_FILE";
|
|
225
230
|
else
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobing-ai/spur",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.32",
|
|
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
|
@@ -63914,6 +63914,37 @@ class JobWorkerService {
|
|
|
63914
63914
|
}
|
|
63915
63915
|
}
|
|
63916
63916
|
|
|
63917
|
+
// ../../packages/app/src/services/pipeline-run-link.ts
|
|
63918
|
+
async function ensurePipelineRunLink(db2, wbs, options = {}) {
|
|
63919
|
+
const dao2 = new TaskRunLinkDao(db2);
|
|
63920
|
+
const links = await dao2.listByWbs(wbs, 20);
|
|
63921
|
+
const existing = links.find((l) => l.kind === "pipeline");
|
|
63922
|
+
if (existing) {
|
|
63923
|
+
return {
|
|
63924
|
+
created: false,
|
|
63925
|
+
id: existing.id,
|
|
63926
|
+
wbs: existing.wbs,
|
|
63927
|
+
runId: existing.run_id,
|
|
63928
|
+
kind: "pipeline"
|
|
63929
|
+
};
|
|
63930
|
+
}
|
|
63931
|
+
const id = createId("trl");
|
|
63932
|
+
const runId = options.runId ?? `record:${wbs}:${Date.now()}`;
|
|
63933
|
+
await dao2.insert({
|
|
63934
|
+
id,
|
|
63935
|
+
wbs,
|
|
63936
|
+
run_id: runId,
|
|
63937
|
+
kind: "pipeline",
|
|
63938
|
+
created_at: new Date().toISOString()
|
|
63939
|
+
});
|
|
63940
|
+
return { created: true, id, wbs, runId, kind: "pipeline" };
|
|
63941
|
+
}
|
|
63942
|
+
var TASK_FORWARD_CHAIN;
|
|
63943
|
+
var init_pipeline_run_link = __esm(() => {
|
|
63944
|
+
init_src2();
|
|
63945
|
+
TASK_FORWARD_CHAIN = ["backlog", "todo", "wip", "testing", "done"];
|
|
63946
|
+
});
|
|
63947
|
+
|
|
63917
63948
|
// ../../packages/app/src/services/planning-events.ts
|
|
63918
63949
|
class BusPlanningEventEmitter {
|
|
63919
63950
|
bus;
|
|
@@ -67372,11 +67403,51 @@ class TaskService {
|
|
|
67372
67403
|
result.solutionBackfilled = true;
|
|
67373
67404
|
}
|
|
67374
67405
|
if (opts.transition !== undefined) {
|
|
67375
|
-
const
|
|
67376
|
-
|
|
67406
|
+
const target = opts.transition;
|
|
67407
|
+
const current = (doc2.frontmatterData?.status ?? "").toLowerCase();
|
|
67408
|
+
if (current === target) {
|
|
67409
|
+
result.transitionedTo = current;
|
|
67410
|
+
} else if (target === "done") {
|
|
67411
|
+
if (verdict.verdict !== "PASS") {
|
|
67412
|
+
throw new GuardDeniedError(`Cannot transition task ${wbs} to done: verify verdict is ${verdict.verdict}, not PASS. ` + `Re-run the pipeline to a PASS verdict, or use ` + `\`spur task update ${wbs} done --force-done --reason "<why>"\` to override.`);
|
|
67413
|
+
}
|
|
67414
|
+
const startIdx = TASK_FORWARD_CHAIN.indexOf(current);
|
|
67415
|
+
const targetIdx = TASK_FORWARD_CHAIN.indexOf(target);
|
|
67416
|
+
if (startIdx !== -1 && targetIdx !== -1 && targetIdx > startIdx) {
|
|
67417
|
+
let reached = current;
|
|
67418
|
+
for (let i2 = startIdx + 1;i2 <= targetIdx; i2 += 1) {
|
|
67419
|
+
const hop = TASK_FORWARD_CHAIN[i2];
|
|
67420
|
+
if (hop === "done") {
|
|
67421
|
+
await this.ensurePipelineRunLink(wbs);
|
|
67422
|
+
}
|
|
67423
|
+
try {
|
|
67424
|
+
const hopResult = await this.writeService.transition(ref, hop);
|
|
67425
|
+
reached = hopResult.toStatus ?? hop;
|
|
67426
|
+
result.transitionedTo = reached;
|
|
67427
|
+
} catch (err) {
|
|
67428
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
67429
|
+
throw new GuardDeniedError(`Record auto-walk for task ${wbs} stopped at '${reached}' ` + `(failed hop \u2192 '${hop}'): ${msg}. ` + `Re-run \`spur task record ${wbs} --transition done\` once the guard is green.`);
|
|
67430
|
+
}
|
|
67431
|
+
}
|
|
67432
|
+
} else {
|
|
67433
|
+
await this.ensurePipelineRunLink(wbs);
|
|
67434
|
+
const transitionResult = await this.writeService.transition(ref, target);
|
|
67435
|
+
result.transitionedTo = transitionResult.toStatus;
|
|
67436
|
+
}
|
|
67437
|
+
} else {
|
|
67438
|
+
const transitionResult = await this.writeService.transition(ref, target);
|
|
67439
|
+
result.transitionedTo = transitionResult.toStatus;
|
|
67440
|
+
}
|
|
67377
67441
|
}
|
|
67378
67442
|
return result;
|
|
67379
67443
|
}
|
|
67444
|
+
async ensurePipelineRunLink(wbs) {
|
|
67445
|
+
const getDb = this.ctx.getDb;
|
|
67446
|
+
if (getDb === undefined)
|
|
67447
|
+
return;
|
|
67448
|
+
const db2 = await getDb();
|
|
67449
|
+
await ensurePipelineRunLink(db2, wbs);
|
|
67450
|
+
}
|
|
67380
67451
|
async batchCreate(jsonPath) {
|
|
67381
67452
|
const raw = await this.ctx.fs.readFile(jsonPath);
|
|
67382
67453
|
let parsed;
|
|
@@ -67714,6 +67785,8 @@ ${block}` : block);
|
|
|
67714
67785
|
var DependencyMutationError, SectionMutationError, DuplicateFollowUpError, WbsCollisionError, ROSTER_START = "<!-- AUTO-GENERATED by spur task refresh-roster -->", ROSTER_END = "<!-- END AUTO-GENERATED -->", ROSTER_REGION_RE, TASK_ACTION_COMMANDS, DEFAULT_CREATION_SECTIONS;
|
|
67715
67786
|
var init_task_service = __esm(() => {
|
|
67716
67787
|
init_src2();
|
|
67788
|
+
init_errors5();
|
|
67789
|
+
init_pipeline_run_link();
|
|
67717
67790
|
init_task_locator();
|
|
67718
67791
|
init_task_record();
|
|
67719
67792
|
DependencyMutationError = class DependencyMutationError extends Error {
|
|
@@ -70746,6 +70819,7 @@ __export(exports_src2, {
|
|
|
70746
70819
|
extractSystemEventActor: () => extractSystemEventActor,
|
|
70747
70820
|
extractReviewSectionBody: () => extractReviewSectionBody,
|
|
70748
70821
|
evaluateDoneTransition: () => evaluateDoneTransition,
|
|
70822
|
+
ensurePipelineRunLink: () => ensurePipelineRunLink,
|
|
70749
70823
|
encodeRunListCursor: () => encodeRunListCursor,
|
|
70750
70824
|
deriveVerdict: () => deriveVerdict,
|
|
70751
70825
|
decodeRunListCursor: () => decodeRunListCursor,
|
|
@@ -70774,6 +70848,7 @@ __export(exports_src2, {
|
|
|
70774
70848
|
TOKEN_LEDGER_MAX_LIMIT: () => TOKEN_LEDGER_MAX_LIMIT,
|
|
70775
70849
|
TOKEN_LEDGER_DEFAULT_LIMIT: () => TOKEN_LEDGER_DEFAULT_LIMIT,
|
|
70776
70850
|
TASK_LIFECYCLE_PROFILE: () => TASK_LIFECYCLE_PROFILE,
|
|
70851
|
+
TASK_FORWARD_CHAIN: () => TASK_FORWARD_CHAIN,
|
|
70777
70852
|
TASK_ACTION_COMMANDS: () => TASK_ACTION_COMMANDS,
|
|
70778
70853
|
SystemEventEmitter: () => SystemEventEmitter,
|
|
70779
70854
|
SupervisorService: () => SupervisorService,
|
|
@@ -70842,6 +70917,7 @@ var init_src3 = __esm(() => {
|
|
|
70842
70917
|
init_feature_service();
|
|
70843
70918
|
init_finding_codes2();
|
|
70844
70919
|
init_history_service();
|
|
70920
|
+
init_pipeline_run_link();
|
|
70845
70921
|
init_planning_events();
|
|
70846
70922
|
init_planning_write_service();
|
|
70847
70923
|
init_process_inspector();
|
|
@@ -78955,7 +79031,7 @@ import { join as join19, resolve as resolve6 } from "path";
|
|
|
78955
79031
|
var CLI_CONFIG = {
|
|
78956
79032
|
binaryName: "spur",
|
|
78957
79033
|
binaryLabel: "spur",
|
|
78958
|
-
binaryVersion: "0.3.
|
|
79034
|
+
binaryVersion: "0.3.32",
|
|
78959
79035
|
configDir: ".spur",
|
|
78960
79036
|
configFile: ".spur/config.yaml",
|
|
78961
79037
|
databaseFile: ".spur/spur.db"
|
|
@@ -90654,37 +90730,22 @@ ${result.wbs} (${result.status}): ${result.pass ? "PASS" : "FAIL"}`);
|
|
|
90654
90730
|
task.command("run-link").summary("Record a pipeline provenance link for a task (used by --next auto chains to satisfy testing\u2192done guard).").argument("<wbs>", "Task WBS number").option("--source <source>", "Link source identifier (e.g. next-auto)", "chain").option("--run-id <id>", "Explicit run_id (auto-generated when omitted)").option("--json", "Output machine-readable JSON").action(async (wbs, options) => {
|
|
90655
90731
|
try {
|
|
90656
90732
|
const db2 = await context4.getDb();
|
|
90657
|
-
const
|
|
90658
|
-
|
|
90659
|
-
|
|
90660
|
-
|
|
90661
|
-
|
|
90662
|
-
|
|
90663
|
-
|
|
90664
|
-
|
|
90665
|
-
|
|
90666
|
-
|
|
90667
|
-
id: entry.id,
|
|
90668
|
-
wbs: entry.wbs,
|
|
90669
|
-
runId: entry.run_id,
|
|
90670
|
-
kind: entry.kind,
|
|
90671
|
-
existed: true
|
|
90672
|
-
};
|
|
90673
|
-
if (options.json) {
|
|
90674
|
-
context4.output.write(toJson2(result2));
|
|
90675
|
-
} else {
|
|
90676
|
-
context4.output.write(`Pipeline run-link already exists for ${wbs} (${entry.id}). Skipped.`);
|
|
90677
|
-
}
|
|
90678
|
-
return;
|
|
90679
|
-
}
|
|
90680
|
-
const id = createId("trl");
|
|
90681
|
-
const runId = options.runId ?? `chain:${options.source}:${wbs}:${Date.now()}`;
|
|
90682
|
-
await dao2.insert({ id, wbs, run_id: runId, kind: "pipeline", created_at: new Date().toISOString() });
|
|
90683
|
-
const result = { id, wbs, runId, kind: "pipeline" };
|
|
90733
|
+
const ensured = await ensurePipelineRunLink(db2, wbs, {
|
|
90734
|
+
runId: options.runId ?? `chain:${options.source}:${wbs}:${Date.now()}`
|
|
90735
|
+
});
|
|
90736
|
+
const result = {
|
|
90737
|
+
id: ensured.id,
|
|
90738
|
+
wbs: ensured.wbs,
|
|
90739
|
+
runId: ensured.runId,
|
|
90740
|
+
kind: ensured.kind,
|
|
90741
|
+
existed: !ensured.created
|
|
90742
|
+
};
|
|
90684
90743
|
if (options.json) {
|
|
90685
90744
|
context4.output.write(toJson2(result));
|
|
90745
|
+
} else if (ensured.created) {
|
|
90746
|
+
context4.output.write(`Recorded pipeline run-link ${ensured.id} for task ${wbs} (source: ${options.source})`);
|
|
90686
90747
|
} else {
|
|
90687
|
-
context4.output.write(`
|
|
90748
|
+
context4.output.write(`Pipeline run-link already exists for ${wbs} (${ensured.id}). Skipped.`);
|
|
90688
90749
|
}
|
|
90689
90750
|
} catch (err) {
|
|
90690
90751
|
context4.output.error(String(err));
|
|
@@ -90731,6 +90792,7 @@ async function makeService2(context4, folderOverride, noLifecycle = false) {
|
|
|
90731
90792
|
fs: context4.fs,
|
|
90732
90793
|
tasksDir,
|
|
90733
90794
|
writeService,
|
|
90795
|
+
getDb: () => context4.getDb(),
|
|
90734
90796
|
sectionMatrix: await loadSectionMatrix(context4.cwd),
|
|
90735
90797
|
resolveTemplate: (variant) => loadTemplateContent(context4.cwd, variant),
|
|
90736
90798
|
resolveTemplateBodies: (variant) => loadTemplateBodies(context4.cwd, variant),
|