@gobing-ai/spur 0.3.76 → 0.3.78
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/plugin-scripts.json +4 -0
- package/config/rules/strict/runtime-boundaries.yaml +1 -0
- package/config/workflows/task-pipeline.yaml +19 -6
- package/package.json +9 -9
- package/plugins/sp/commands/dev-refineall.md +1 -0
- package/plugins/sp/commands/dev-run.md +1 -0
- package/plugins/sp/commands/dev-runall.md +1 -0
- package/plugins/sp/commands/dev-verifyall.md +1 -0
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/scripts/batch-preflight.mjs +173 -2
- package/plugins/sp/scripts/batch-preflight.ts +257 -2
- package/plugins/sp/scripts/inline-run-setup.ts +198 -0
- package/plugins/sp/scripts/verify-answer-lint.ts +139 -26
- package/plugins/sp/skills/code-verification/SKILL.md +4 -2
- package/plugins/sp/skills/code-verification/references/verdict-schema.md +11 -2
- package/plugins/sp/skills/dogfood-testing/SKILL.md +41 -36
- package/plugins/sp/skills/dogfood-testing/references/monitor-ledger.md +34 -10
- package/plugins/sp/skills/dogfood-testing/references/report-template.md +10 -6
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +33 -0
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +17 -0
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +133 -16
- package/spur.js +10328 -9186
|
@@ -71,6 +71,7 @@ rules:
|
|
|
71
71
|
- "packages/config/src/loader.ts" # sync resolveConfigFile() / schema specifier lookup
|
|
72
72
|
- "packages/config/src/executor-update.ts" # atomic single-entry config update (task 0797): O_EXCL lock, fsync'd tmp+rename commit, lstat symlink refusal, mode preservation — ts-runtime FileSystem seam has no lstat/fsync/chmod/O_EXCL
|
|
73
73
|
- "packages/app/src/services/project-start.ts" # sync CLI binary path checks before daemon spawn
|
|
74
|
+
- "apps/cli/src/commands/serve.ts" # sync resolveServeCwd() directory validation before server startup (task 0805 R2); mirrors task.ts sync-template exemption
|
|
74
75
|
|
|
75
76
|
# Synchronous template & workflow resolvers:
|
|
76
77
|
- "apps/cli/src/commands/task.ts" # sync loadTemplateContent/Bodies callbacks
|
|
@@ -270,12 +270,20 @@ states:
|
|
|
270
270
|
# WorkflowAppService.run(); the wbs fallback keeps a driver-less invocation from writing to
|
|
271
271
|
# a bare "-route-reason.txt". The log line carries the run id for the same reason — an
|
|
272
272
|
# unattributed append is log scraping, which R5 explicitly rejects as evidence.
|
|
273
|
+
# 0804 R8: the run id must be a single safe filename component before it
|
|
274
|
+
# becomes REASON_FILE — unresolved interpolation ($ { } vars.), path
|
|
275
|
+
# separators and dot traversal are a failed action (nonzero, no artifact).
|
|
276
|
+
# NOTE: no `#` comments inside the folded scalar — `>-` joins all same-indent
|
|
277
|
+
# lines into ONE shell line, so an in-scalar `#` comments out the rest of the
|
|
278
|
+
# command (the original R8 draft broke exactly this way; the case statement
|
|
279
|
+
# must stay one line).
|
|
273
280
|
- kind: shell
|
|
274
281
|
options:
|
|
275
282
|
command: >-
|
|
276
283
|
mkdir -p .spur/run .spur/memory &&
|
|
277
284
|
RUN_ID="$__runId" &&
|
|
278
285
|
if [ -z "$RUN_ID" ]; then RUN_ID="pipeline-$wbs"; fi &&
|
|
286
|
+
case "$RUN_ID" in *'$'*|*'{'*|*'}'*|*vars.*|*/*|*'\'*|*..*) echo "route-reason: refusing unsafe run id: $RUN_ID" >&2; exit 1 ;; *) : ;; esac &&
|
|
279
287
|
REASON_FILE=".spur/run/$RUN_ID-route-reason.txt" &&
|
|
280
288
|
if [ "$mode" = "fast" ]; then
|
|
281
289
|
echo "fast:evidence complete+consistent" > "$REASON_FILE";
|
|
@@ -335,7 +343,7 @@ states:
|
|
|
335
343
|
RETRY_OUTPUT=$("$@" 2>&1); RETRY_RC=$?;
|
|
336
344
|
printf '%s\n' "$RETRY_OUTPUT";
|
|
337
345
|
if [ "$RETRY_RC" -eq 0 ]; then return 0; fi;
|
|
338
|
-
if ! printf '%s' "$RETRY_OUTPUT" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY|database is locked'; then return "$RETRY_RC"; fi;
|
|
346
|
+
if ! printf '%s' "$RETRY_OUTPUT" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY|database is locked|SQLite database .*is busy|SQLITE_BUSY'; then return "$RETRY_RC"; fi;
|
|
339
347
|
sleep 2;
|
|
340
348
|
RETRY_SECOND_OUTPUT=$("$@" 2>&1); RETRY_RC=$?;
|
|
341
349
|
printf '%s\n' "$RETRY_SECOND_OUTPUT";
|
|
@@ -431,7 +439,7 @@ states:
|
|
|
431
439
|
ATTEMPT_LOG="$LOG_FILE.attempt-$gate_attempt";
|
|
432
440
|
sh -c "$qualityGateCmd" > "$ATTEMPT_LOG" 2>&1; gate_rc=$?;
|
|
433
441
|
gate_locked=0;
|
|
434
|
-
grep -
|
|
442
|
+
grep -Eq 'SQLiteError: database is locked|SQLite database .*is busy|SQLITE_BUSY' "$ATTEMPT_LOG" && gate_locked=1;
|
|
435
443
|
cat "$ATTEMPT_LOG" >> "$LOG_FILE";
|
|
436
444
|
rm -f "$ATTEMPT_LOG";
|
|
437
445
|
if [ "$gate_rc" -eq 0 ] || [ "$gate_locked" -ne 1 ] || [ "$gate_attempt" -ge 5 ]; then break; fi;
|
|
@@ -547,7 +555,7 @@ states:
|
|
|
547
555
|
ATTEMPT_LOG="$LOG_FILE.attempt-$gate_attempt";
|
|
548
556
|
sh -c "$qualityGateCmd" > "$ATTEMPT_LOG" 2>&1; gate_rc=$?;
|
|
549
557
|
gate_locked=0;
|
|
550
|
-
grep -
|
|
558
|
+
grep -Eq 'SQLiteError: database is locked|SQLite database .*is busy|SQLITE_BUSY' "$ATTEMPT_LOG" && gate_locked=1;
|
|
551
559
|
cat "$ATTEMPT_LOG" >> "$LOG_FILE";
|
|
552
560
|
rm -f "$ATTEMPT_LOG";
|
|
553
561
|
if [ "$gate_rc" -eq 0 ] || [ "$gate_locked" -ne 1 ] || [ "$gate_attempt" -ge 5 ]; then break; fi;
|
|
@@ -747,7 +755,7 @@ states:
|
|
|
747
755
|
RETRY_OUTPUT=$("$@" 2>&1); RETRY_RC=$?;
|
|
748
756
|
printf '%s\n' "$RETRY_OUTPUT";
|
|
749
757
|
if [ "$RETRY_RC" -eq 0 ]; then return 0; fi;
|
|
750
|
-
if ! printf '%s' "$RETRY_OUTPUT" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY|database is locked'; then return "$RETRY_RC"; fi;
|
|
758
|
+
if ! printf '%s' "$RETRY_OUTPUT" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY|database is locked|SQLite database .*is busy|SQLITE_BUSY'; then return "$RETRY_RC"; fi;
|
|
751
759
|
sleep 2;
|
|
752
760
|
RETRY_SECOND_OUTPUT=$("$@" 2>&1); RETRY_RC=$?;
|
|
753
761
|
printf '%s\n' "$RETRY_SECOND_OUTPUT";
|
|
@@ -794,7 +802,7 @@ states:
|
|
|
794
802
|
RETRY_OUTPUT=$("$@" 2>&1); RETRY_RC=$?;
|
|
795
803
|
printf '%s\n' "$RETRY_OUTPUT";
|
|
796
804
|
if [ "$RETRY_RC" -eq 0 ]; then return 0; fi;
|
|
797
|
-
if ! printf '%s' "$RETRY_OUTPUT" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY|database is locked'; then return "$RETRY_RC"; fi;
|
|
805
|
+
if ! printf '%s' "$RETRY_OUTPUT" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY|database is locked|SQLite database .*is busy|SQLITE_BUSY'; then return "$RETRY_RC"; fi;
|
|
798
806
|
sleep 2;
|
|
799
807
|
RETRY_SECOND_OUTPUT=$("$@" 2>&1); RETRY_RC=$?;
|
|
800
808
|
printf '%s\n' "$RETRY_SECOND_OUTPUT";
|
|
@@ -1016,8 +1024,13 @@ transitions:
|
|
|
1016
1024
|
guard:
|
|
1017
1025
|
kind: shell
|
|
1018
1026
|
options:
|
|
1027
|
+
# 0804 R6: `--as done` projects the check onto the done target so open
|
|
1028
|
+
# Plan/AC checkboxes fail HERE — before the done action runs — mirroring
|
|
1029
|
+
# the CLI's own target-aware `--no-lifecycle` backstop (F92 R3). No `#`
|
|
1030
|
+
# comments inside the folded scalar: `>-` joins same-indent lines into
|
|
1031
|
+
# one shell line.
|
|
1019
1032
|
command: >-
|
|
1020
|
-
$spurBin task check $wbs &&
|
|
1033
|
+
$spurBin task check $wbs --as done &&
|
|
1021
1034
|
test "$(jq -r .verdict .spur/run/$wbs-verdict.json 2>/dev/null)" = PASS &&
|
|
1022
1035
|
test "$(jq -r '.proof.digest // ""' .spur/run/$wbs-verdict.json 2>/dev/null)" = "$proofDigest"
|
|
1023
1036
|
- from: record
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobing-ai/spur",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.78",
|
|
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",
|
|
@@ -53,14 +53,14 @@
|
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@commander-js/extra-typings": "^14.0.0",
|
|
56
|
-
"@gobing-ai/ts-db": "^0.4.
|
|
57
|
-
"@gobing-ai/ts-ai-runner": "^0.4.
|
|
58
|
-
"@gobing-ai/ts-dual-workflow-engine": "^0.4.
|
|
59
|
-
"@gobing-ai/ts-infra": "^0.4.
|
|
60
|
-
"@gobing-ai/ts-llm-jsonl-importer": "^0.4.
|
|
61
|
-
"@gobing-ai/ts-rule-engine": "^0.4.
|
|
62
|
-
"@gobing-ai/ts-runtime": "^0.4.
|
|
63
|
-
"@gobing-ai/ts-utils": "^0.4.
|
|
56
|
+
"@gobing-ai/ts-db": "^0.4.60",
|
|
57
|
+
"@gobing-ai/ts-ai-runner": "^0.4.60",
|
|
58
|
+
"@gobing-ai/ts-dual-workflow-engine": "^0.4.60",
|
|
59
|
+
"@gobing-ai/ts-infra": "^0.4.60",
|
|
60
|
+
"@gobing-ai/ts-llm-jsonl-importer": "^0.4.60",
|
|
61
|
+
"@gobing-ai/ts-rule-engine": "^0.4.60",
|
|
62
|
+
"@gobing-ai/ts-runtime": "^0.4.60",
|
|
63
|
+
"@gobing-ai/ts-utils": "^0.4.60",
|
|
64
64
|
"@types/bun": "1.3.14",
|
|
65
65
|
"@types/figlet": "^1.7.0",
|
|
66
66
|
"@types/node-notifier": "8.0.5",
|
|
@@ -62,5 +62,6 @@ thread `--depth` into each per-task refine.
|
|
|
62
62
|
## Implementation
|
|
63
63
|
|
|
64
64
|
- Apply the [inline-default execution-surface contract](../skills/spur-dev/references/cross-cutting.md#inline-default-execution-surface).
|
|
65
|
+
- Apply the [shared startup contract](../skills/spur-dev/references/cross-cutting.md#shared-startup-contract-task-0814-r1r3r4r6r7r8) — bootstrap checklist, quick readiness before isolation, workflow inventory before YAML, comprehensive checks at owning boundaries (task 0814 R1/R3/R4/R7/R8).
|
|
65
66
|
- Batch orchestration: `Skill(skill="sp:spur-dev", args="refineall $ARGUMENTS")`
|
|
66
67
|
- Per-task refine (inner): `Skill(skill="sp:spur-dev", args="refine <wbs> $SHARED_FLAGS")`
|
|
@@ -31,6 +31,7 @@ For shared semantics, see the [flag glossary](../skills/spur-dev/references/flag
|
|
|
31
31
|
## Implementation
|
|
32
32
|
|
|
33
33
|
- Apply the [inline-default execution-surface contract](../skills/spur-dev/references/cross-cutting.md#inline-default-execution-surface).
|
|
34
|
+
- Apply the [shared startup contract](../skills/spur-dev/references/cross-cutting.md#shared-startup-contract-task-0814-r1r3r4r6r7r8) — bootstrap checklist, quick readiness before isolation, workflow inventory before YAML, comprehensive checks at their owning boundaries (task 0814 R1/R3/R4/R7/R8).
|
|
34
35
|
- Full pipeline (default `--mode full`): interactive omit/`--agent inline` uses the
|
|
35
36
|
[inline pipeline driver](../skills/spur-dev/references/inline-pipeline-driver.md) via
|
|
36
37
|
`Skill(skill="sp:spur-dev", args="run-inline $ARGUMENTS")`; `--agent auto` or a named executor
|
|
@@ -82,5 +82,6 @@ full distinction.
|
|
|
82
82
|
## Implementation
|
|
83
83
|
|
|
84
84
|
- Apply the [inline-default execution-surface contract](../skills/spur-dev/references/cross-cutting.md#inline-default-execution-surface). Interactive sequential omit/`inline` keeps the orchestrator in the host session through the [inline pipeline driver](../skills/spur-dev/references/inline-pipeline-driver.md); the resolved selector applies uniformly — eligible `agent.run` stages dispatch once to a native subagent with host fallback (0508). `--agent auto`, a name, or parallel mode retains the isolated per-task workflow boundary.
|
|
85
|
+
- Apply the [shared startup contract](../skills/spur-dev/references/cross-cutting.md#shared-startup-contract-task-0814-r1r3r4r6r7r8) — bootstrap checklist, quick readiness before isolation, workflow inventory before YAML, comprehensive checks at owning boundaries (task 0814 R1/R3/R4/R7/R8).
|
|
85
86
|
- Interactive sequential omit/inline: `Skill(skill="sp:spur-dev", args="runall-inline $ARGUMENTS")`.
|
|
86
87
|
- Explicit executor or parallel mode: `Skill(skill="sp:spur-dev", args="runall $ARGUMENTS")` → `sp:super-planner` agent.
|
|
@@ -68,6 +68,7 @@ Without `--fix all`, shippable is not evaluated (optional note: use `--fix all`
|
|
|
68
68
|
## Implementation
|
|
69
69
|
|
|
70
70
|
- Apply the [inline-default execution-surface contract](../skills/spur-dev/references/cross-cutting.md#inline-default-execution-surface).
|
|
71
|
+
- Apply the [shared startup contract](../skills/spur-dev/references/cross-cutting.md#shared-startup-contract-task-0814-r1r3r4r6r7r8) — bootstrap checklist, quick readiness before isolation, workflow inventory before YAML, comprehensive checks at owning boundaries (task 0814 R1/R3/R4/R7/R8).
|
|
71
72
|
- Batch orchestration: `Skill(skill="sp:spur-dev", args="verifyall $ARGUMENTS")`
|
|
72
73
|
- Per-task verification (inner): `Skill(skill="sp:code-verification", args="verify <wbs> $SHARED_FLAGS")`
|
|
73
74
|
- Shippable gate: once after the batch (same skill § Shippable readiness gate)
|
package/plugins/sp/plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.78",
|
|
4
4
|
"description": "Spur — a local-first harness engineering toolkit that wraps mainstream coding agents with constraint checking, workflow orchestration, and history analytics.",
|
|
5
5
|
"extensions": {
|
|
6
6
|
"pi": ["./hooks/pi/guard-extension.ts"]
|
|
@@ -3,6 +3,119 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
4
4
|
|
|
5
5
|
// plugins/sp/scripts/batch-preflight.ts
|
|
6
|
+
function quickReadiness(input) {
|
|
7
|
+
const status = (input.status ?? "").toLowerCase();
|
|
8
|
+
const operation = input.operation;
|
|
9
|
+
if (operation !== "run" && operation !== "refine" && operation !== "verify") {
|
|
10
|
+
return {
|
|
11
|
+
action: "invalid",
|
|
12
|
+
code: "IV",
|
|
13
|
+
reason: `quick-readiness: unknown operation '${operation}' (${input.wbs})`
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
if (input.filteredCount !== undefined) {
|
|
17
|
+
if (input.filteredCount < 0) {
|
|
18
|
+
return {
|
|
19
|
+
action: "invalid",
|
|
20
|
+
code: "IV",
|
|
21
|
+
reason: `quick-readiness: invalid negative filtered count '${input.filteredCount}' (${input.wbs})`
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
if (input.filteredCount === 0) {
|
|
25
|
+
return {
|
|
26
|
+
action: "skipped",
|
|
27
|
+
code: "EMPTY",
|
|
28
|
+
reason: `quick-readiness: empty status-filtered set — nothing to ${operation} (${input.wbs})`
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (status === "cancelled" || status === "done") {
|
|
33
|
+
if (operation === "verify" && input.force === true) {
|
|
34
|
+
return {
|
|
35
|
+
action: "runnable",
|
|
36
|
+
code: "FORCE",
|
|
37
|
+
reason: `quick-readiness: verify --force re-verification of ${status} task ${input.wbs}`
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
action: "skipped",
|
|
42
|
+
code: status === "done" ? "DONE" : "CANCELLED",
|
|
43
|
+
reason: `quick-readiness: already ${status} — no ${operation} hop (${input.wbs})`
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
if (status === "blocked") {
|
|
47
|
+
return {
|
|
48
|
+
action: "blocked",
|
|
49
|
+
code: "BLK",
|
|
50
|
+
reason: `quick-readiness: blocked — human/handover first (${input.wbs})`
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
if (operation === "run" && input.dependencies && input.dependencies.length > 0) {
|
|
54
|
+
const unmet = input.dependencies.filter((d) => (input.depStatuses?.[d] ?? "missing").toLowerCase() !== "done");
|
|
55
|
+
if (unmet.length > 0) {
|
|
56
|
+
return {
|
|
57
|
+
action: "blocked",
|
|
58
|
+
code: "DEP",
|
|
59
|
+
reason: `quick-readiness: unmet deps — ${unmet.join(", ")} (${input.wbs})`,
|
|
60
|
+
unmetDeps: unmet
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const required = input.requiredSections ?? [];
|
|
65
|
+
const present = input.presentSections ?? [];
|
|
66
|
+
const gaps = required.filter((s) => {
|
|
67
|
+
const finding = input.sectionFindings?.[s];
|
|
68
|
+
return !present.includes(s) || finding !== undefined && finding !== "";
|
|
69
|
+
});
|
|
70
|
+
if (operation === "refine") {
|
|
71
|
+
if (status !== "backlog" && status !== "todo") {
|
|
72
|
+
return {
|
|
73
|
+
action: "skipped",
|
|
74
|
+
code: "NONPLAN",
|
|
75
|
+
reason: `quick-readiness: refine targets backlog/todo only, not '${status}' (${input.wbs})`
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
action: "runnable",
|
|
80
|
+
code: "OK",
|
|
81
|
+
reason: `quick-readiness: refine ready for ${input.wbs} (${gaps.length} planning gap(s) to fill)`
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (operation === "verify") {
|
|
85
|
+
if (status !== "testing" && status !== "wip") {
|
|
86
|
+
return {
|
|
87
|
+
action: "invalid",
|
|
88
|
+
code: "NOVERIFY",
|
|
89
|
+
reason: `quick-readiness: verify needs testing/wip, not '${status}' (${input.wbs})`
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
action: "runnable",
|
|
94
|
+
code: "OK",
|
|
95
|
+
reason: `quick-readiness: verify ready for ${input.wbs}`
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
if (status !== "todo" && status !== "wip" && status !== "testing") {
|
|
99
|
+
return {
|
|
100
|
+
action: "invalid",
|
|
101
|
+
code: "NORUN",
|
|
102
|
+
reason: `quick-readiness: run needs todo/wip/testing, not '${status}' (${input.wbs})`
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (gaps.length > 0) {
|
|
106
|
+
return {
|
|
107
|
+
action: "needs-refinement",
|
|
108
|
+
code: "REFINE",
|
|
109
|
+
reason: `quick-readiness: implementation sections incomplete (${input.wbs})`,
|
|
110
|
+
gaps
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
action: "runnable",
|
|
115
|
+
code: "OK",
|
|
116
|
+
reason: `quick-readiness: run ready for ${input.wbs}`
|
|
117
|
+
};
|
|
118
|
+
}
|
|
6
119
|
function preflightTask(input) {
|
|
7
120
|
const status = (input.status ?? "").toLowerCase();
|
|
8
121
|
const deps = input.dependencies ?? [];
|
|
@@ -72,6 +185,11 @@ function parsePreflightCliArgs(argv) {
|
|
|
72
185
|
let recovery = false;
|
|
73
186
|
let help = false;
|
|
74
187
|
let json = false;
|
|
188
|
+
let operation = null;
|
|
189
|
+
let force = false;
|
|
190
|
+
let filteredCount = null;
|
|
191
|
+
let requiredSections = [];
|
|
192
|
+
let presentSections = [];
|
|
75
193
|
for (let i = 0;i < argv.length; i++) {
|
|
76
194
|
const a = argv[i];
|
|
77
195
|
if (a === "--help" || a === "-h")
|
|
@@ -80,7 +198,21 @@ function parsePreflightCliArgs(argv) {
|
|
|
80
198
|
json = true;
|
|
81
199
|
else if (a === "--recovery")
|
|
82
200
|
recovery = true;
|
|
83
|
-
else if (a === "--
|
|
201
|
+
else if (a === "--force")
|
|
202
|
+
force = true;
|
|
203
|
+
else if (a === "--operation") {
|
|
204
|
+
const v = argv[++i] ?? "";
|
|
205
|
+
operation = v === "run" || v === "refine" || v === "verify" ? v : null;
|
|
206
|
+
} else if (a === "--filtered-count") {
|
|
207
|
+
const v = Number(argv[++i]);
|
|
208
|
+
filteredCount = Number.isFinite(v) ? v : null;
|
|
209
|
+
} else if (a === "--required-sections") {
|
|
210
|
+
const raw = argv[++i] ?? "";
|
|
211
|
+
requiredSections = raw.length === 0 ? [] : raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
212
|
+
} else if (a === "--present-sections") {
|
|
213
|
+
const raw = argv[++i] ?? "";
|
|
214
|
+
presentSections = raw.length === 0 ? [] : raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
215
|
+
} else if (a === "--wbs")
|
|
84
216
|
wbs = argv[++i] ?? wbs;
|
|
85
217
|
else if (a === "--status")
|
|
86
218
|
status = argv[++i] ?? null;
|
|
@@ -96,11 +228,25 @@ function parsePreflightCliArgs(argv) {
|
|
|
96
228
|
}
|
|
97
229
|
}
|
|
98
230
|
}
|
|
99
|
-
return {
|
|
231
|
+
return {
|
|
232
|
+
status,
|
|
233
|
+
deps,
|
|
234
|
+
depStatuses,
|
|
235
|
+
wbs,
|
|
236
|
+
recovery,
|
|
237
|
+
help,
|
|
238
|
+
json,
|
|
239
|
+
operation,
|
|
240
|
+
force,
|
|
241
|
+
filteredCount,
|
|
242
|
+
requiredSections,
|
|
243
|
+
presentSections
|
|
244
|
+
};
|
|
100
245
|
}
|
|
101
246
|
var PREFLIGHT_CLI_USAGE = `Usage:
|
|
102
247
|
bun plugins/sp/scripts/batch-preflight.ts --wbs <wbs> --status <status> \\
|
|
103
248
|
[--deps 0275,0276] [--dep-status 0275:done,0276:todo] [--recovery] [--json]
|
|
249
|
+
bun plugins/sp/scripts/batch-preflight.ts --operation <run|refine|verify> --wbs <wbs> --status <status> [--filtered-count <n>] [--required-sections A,B] [--present-sections A,B] [--force] [--json]
|
|
104
250
|
|
|
105
251
|
Exit: 0 = run (or recovery hint printed); 2 = skip; 1 = usage.`;
|
|
106
252
|
function runPreflightCli(argv) {
|
|
@@ -117,6 +263,30 @@ function runPreflightCli(argv) {
|
|
|
117
263
|
`;
|
|
118
264
|
return { exitCode: 0, stdout: body, stderr: "" };
|
|
119
265
|
}
|
|
266
|
+
if (args.operation !== null) {
|
|
267
|
+
const result2 = quickReadiness({
|
|
268
|
+
wbs: args.wbs,
|
|
269
|
+
status: args.status,
|
|
270
|
+
operation: args.operation,
|
|
271
|
+
dependencies: args.deps,
|
|
272
|
+
depStatuses: args.depStatuses,
|
|
273
|
+
...args.filteredCount !== null ? { filteredCount: args.filteredCount } : {},
|
|
274
|
+
...args.requiredSections.length > 0 ? { requiredSections: args.requiredSections } : {},
|
|
275
|
+
...args.presentSections.length > 0 ? { presentSections: args.presentSections } : {},
|
|
276
|
+
...args.force ? { force: true } : {}
|
|
277
|
+
});
|
|
278
|
+
const runnable = result2.action === "runnable" || result2.action === "needs-refinement";
|
|
279
|
+
if (args.json) {
|
|
280
|
+
return { exitCode: runnable ? 0 : 2, stdout: `${JSON.stringify(result2, null, 2)}
|
|
281
|
+
`, stderr: "" };
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
exitCode: runnable ? 0 : 2,
|
|
285
|
+
stdout: `${result2.action}${result2.code ? ` ${result2.code}` : ""}: ${result2.reason}
|
|
286
|
+
`,
|
|
287
|
+
stderr: ""
|
|
288
|
+
};
|
|
289
|
+
}
|
|
120
290
|
const result = preflightTask({
|
|
121
291
|
wbs: args.wbs,
|
|
122
292
|
status: args.status,
|
|
@@ -154,6 +324,7 @@ function runPreflightCli(argv) {
|
|
|
154
324
|
export {
|
|
155
325
|
runPreflightCli,
|
|
156
326
|
recoveryHint,
|
|
327
|
+
quickReadiness,
|
|
157
328
|
preflightTask,
|
|
158
329
|
parsePreflightCliArgs,
|
|
159
330
|
PREFLIGHT_CLI_USAGE
|