@gobing-ai/spur 0.4.34 → 0.4.35
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/pipeline-budgets.json +2 -2
- package/config/workflows/task-pipeline.yaml +1 -1
- package/package.json +1 -1
- package/plugins/sp/README.md +4 -2
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/skills/code-verification/SKILL.md +3 -10
- package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +3 -0
- package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +1 -1
- package/plugins/sp/skills/spur-dev/references/migration-guide.md +214 -0
- package/spur.js +82 -213
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
"note": "Per-pipeline model-query + wall-clock cost budgets (task 0607 R3). modelQueries anchors the live workflow definitions' query lists — the SSOT, extracted by extractResolvedWorkflowFacts and guarded by composition-baseline.test.ts (0775 retired the snapshot this file previously anchored to). wallClockMs is a gross-regression ceiling anchored to measured real-run data (R5): the gate compares the median-of-sane terminal-run wall-clock against it. A null budget is unenforced until measured — never treated as 0. Raising a numeric budget requires a fresh recorded decision in the same commit (config/pipeline-budgets.json vs git HEAD); the `spur workflow cost --budget` gate fails a silent raise by name.",
|
|
5
5
|
"budgets": {
|
|
6
6
|
"task-pipeline": {
|
|
7
|
-
"modelQueries":
|
|
7
|
+
"modelQueries": 3,
|
|
8
8
|
"wallClockMs": 2300000,
|
|
9
9
|
"tokenCostUsd": null,
|
|
10
|
-
"source": "Full-depth runs 2026-08-08..20: 1459s-2719s; authoritative 2053s/2023s/1985s (n=3, 2026-08-19/20, per ADR-076 retirement — 538s unusable). Budget = 2300s ≈ max+~12% headroom. Gate measures median-of-sane terminal runs; short precheck-failure/dry runs pull the median down, so this is a gross-regression ceiling. Live fixture re-measurement: bun scripts/spur-dev.ts eval-pipeline --runs 3 (operator command in 0607 Solution).",
|
|
10
|
+
"source": "Full-depth runs 2026-08-08..20: 1459s-2719s; authoritative 2053s/2023s/1985s (n=3, 2026-08-19/20, per ADR-076 retirement — 538s unusable). Budget = 2300s ≈ max+~12% headroom. Gate measures median-of-sane terminal runs; short precheck-failure/dry runs pull the median down, so this is a gross-regression ceiling. Live fixture re-measurement: bun scripts/spur-dev.ts eval-pipeline --runs 3 (operator command in 0607 Solution). 2026-11-14 recount: 4 -> 3 model queries (implement, remediate, review) after the slim-harness refactor; the wall-clock ceiling predates it and is reference only — no pipeline, gate or hook reads it; only a manual `spur workflow cost --budget` compares against it.",
|
|
11
11
|
"decision": null
|
|
12
12
|
},
|
|
13
13
|
"idea-pipeline": {
|
|
@@ -694,7 +694,7 @@ states:
|
|
|
694
694
|
args: ["task", "verdict", "${vars.wbs}", "--from-answer", ".spur/run/${vars.__runId}-review-cert.md"]
|
|
695
695
|
resultFile: .spur/run/${vars.__runId}-review-verdict.status
|
|
696
696
|
softFail: true
|
|
697
|
-
# Derivation only
|
|
697
|
+
# Derivation only: the gate already ran the tests for this tree.
|
|
698
698
|
timeoutMs: "120000"
|
|
699
699
|
# (e) one fold, one token: PASS requires the typed verdict PASS and the artifact's own PASS.
|
|
700
700
|
# Fail-closed: any missing/corrupt input leaves FAIL, so the transitions can route a
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobing-ai/spur",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.35",
|
|
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/plugins/sp/README.md
CHANGED
|
@@ -55,6 +55,8 @@ Entry points that feed the main flow above, or run independently of it — see t
|
|
|
55
55
|
from git history.
|
|
56
56
|
- The **project bootstrapper** scaffolds a brand-new Spur project (config + docs), then tailors it
|
|
57
57
|
to the target stack.
|
|
58
|
+
- The **[downstream migration guide](skills/spur-dev/references/migration-guide.md)** upgrades an
|
|
59
|
+
existing project without replacing its `.spur/config.yaml`, feature tree, or task history.
|
|
58
60
|
|
|
59
61
|
### Batch and parallel paths
|
|
60
62
|
|
|
@@ -242,8 +244,8 @@ plugins/sp/
|
|
|
242
244
|
│ │ └── references/ # ac-style-guide, cross-cutting, decision-brief, dev-operations,
|
|
243
245
|
│ │ done-housekeeping, execution-batch, execution-workflow,
|
|
244
246
|
│ │ feature-link-helper, flag-glossary, gate-checklists, glossary,
|
|
245
|
-
│ │ idea-evaluation, execution-workflow, planning-workflow,
|
|
246
|
-
│ │ product-planning, section-batching (
|
|
247
|
+
│ │ idea-evaluation, migration-guide, execution-workflow, planning-workflow,
|
|
248
|
+
│ │ product-planning, section-batching (17 files)
|
|
247
249
|
│ ├── test-driven-development/ # TDD workflow companion (SKILL.md only)
|
|
248
250
|
│ ├── reverse-engineering/ # Codebase reverse engineering / HLD / audit
|
|
249
251
|
│ │ ├── agents/openai.yaml
|
package/plugins/sp/plugin.json
CHANGED
|
@@ -144,13 +144,8 @@ assign a per-requirement status:
|
|
|
144
144
|
Record the evidence string (repo-relative `file:line`, command, or test name) per requirement —
|
|
145
145
|
this lands in `## Testing`.
|
|
146
146
|
|
|
147
|
-
**
|
|
148
|
-
|
|
149
|
-
MUST name the requirement's subject (the R-item's noun - not merely exist on disk) before a MET row
|
|
150
|
-
is written. A citation whose anchor resolves to another ticket's content, a stale line range, or a
|
|
151
|
-
heading/comment unrelated to the requirement fails the row to UNMET and surfaces the stale anchor as
|
|
152
|
-
a finding (severity >= P2). This closes the gap where a verify run certified a task `done` citing
|
|
153
|
-
`evidence:134` that was actually a sibling ticket's telemetry text (0299 R1, from the 0282 re-audit).
|
|
147
|
+
**Cite what you actually read.** Evidence must be something you inspected this run and that
|
|
148
|
+
supports the requirement; nothing parses or re-checks the cell, so its accuracy is yours.
|
|
154
149
|
|
|
155
150
|
**External evidence (task 0584 / ADR-062) — legal citation form.** Evidence that lives OUTSIDE this
|
|
156
151
|
repository has a frozen non-anchor form: a named origin plus a backticked path with the line number
|
|
@@ -199,9 +194,7 @@ The answer file must include a stable AC table:
|
|
|
199
194
|
| Scenario: CLI emits JSON | MET | test | `apps/cli/tests/foo.test.ts:42` |
|
|
200
195
|
```
|
|
201
196
|
|
|
202
|
-
**
|
|
203
|
-
evidence column is subject to the Step 4 line-anchor rule - re-read at the cited lines this run and
|
|
204
|
-
confirm the content names the AC's subject before marking the row MET.
|
|
197
|
+
**AC evidence follows the same rule as Step 4:** cite what you inspected this run.
|
|
205
198
|
|
|
206
199
|
### Step 6 — Design conformance (Phase 7a; Wave C / 0179 R1–R3)
|
|
207
200
|
|
|
@@ -256,6 +256,9 @@ the active tasks folder (or `--folder`).
|
|
|
256
256
|
| `--folder <path>` | Custom tasks folder. |
|
|
257
257
|
| `--json` | Machine-readable report envelope. |
|
|
258
258
|
|
|
259
|
+
For a complete downstream upgrade, use the [migration guide](../../../spur-dev/references/migration-guide.md):
|
|
260
|
+
`task migrate` is only the A17 normalization step, not the whole migration.
|
|
261
|
+
|
|
259
262
|
## `resolve <file-path>`
|
|
260
263
|
|
|
261
264
|
Map a file path to its **owning task** — returns the WBS + task file. Strategies, in order: direct
|
|
@@ -333,5 +333,5 @@ truly does not apply (do not silently default new tasks to it).
|
|
|
333
333
|
| Legacy raw bullets | `- AC1 …` (no checkbox, no `Scenario:`) | n/a | unparsed: invisible to subset, binding, and box checks — preserve in place, never author new |
|
|
334
334
|
|
|
335
335
|
`task-local` altitude skips only the feature-subset check. It does not waive the unchecked-box check,
|
|
336
|
-
required sections, the proof-bound verdict,
|
|
336
|
+
required sections, the proof-bound verdict, or any other done check — AC syntax alone,
|
|
337
337
|
at any altitude, never makes a task done.
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: downstream-migration
|
|
3
|
+
description: "Migrate an existing Spur project to the current CLI and plugin contract while preserving local config and planning history."
|
|
4
|
+
see_also:
|
|
5
|
+
- spur-dev
|
|
6
|
+
- spur-cli
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Downstream migration guide
|
|
10
|
+
|
|
11
|
+
Use this guide to upgrade an existing project to **Spur 0.4.34** and its matching `sp` plugin.
|
|
12
|
+
For a later release, use the same procedure and read that release's `CHANGELOG.md` entries for
|
|
13
|
+
additional required changes. Do not treat every release as requiring a corpus rewrite.
|
|
14
|
+
|
|
15
|
+
## 1. Preservation contract
|
|
16
|
+
|
|
17
|
+
- `.spur/config.yaml`, `docs/features/`, and `docs/tasks*/` are project-owned inputs. Never replace
|
|
18
|
+
them with package examples or a fresh checkout.
|
|
19
|
+
- Task and feature Markdown is the planning SSOT. The SQLite database is derived; database
|
|
20
|
+
migrations do not rewrite that Markdown. `docs/features/INDEX.md` and generated task rosters are
|
|
21
|
+
outputs and must only be regenerated through `spur feature`.
|
|
22
|
+
- Do not run `spur self init --force`, bulk-copy the packaged default configuration or its
|
|
23
|
+
workflow definitions, or edit generated feature regions directly. Existing project overrides need
|
|
24
|
+
reconciliation, not replacement.
|
|
25
|
+
- Keep history. Retired commands and old evidence prose can remain in completed task records;
|
|
26
|
+
they are not instructions to re-run. `spur task migrate-anchors` and `spur task residual` are
|
|
27
|
+
removed and must not be called.
|
|
28
|
+
- Use CLI-gated writes. Feature acceptance criteria go through `spur feature update --section`;
|
|
29
|
+
task sections go through `spur task update --section`. No direct corpus edits.
|
|
30
|
+
|
|
31
|
+
## 2. Preflight and backup
|
|
32
|
+
|
|
33
|
+
Work from the downstream project root, with a clean Git tree and a named backup ref:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
cd /path/to/project
|
|
37
|
+
git status --short
|
|
38
|
+
git switch -c chore/spur-0.4.34-migration
|
|
39
|
+
spur --version
|
|
40
|
+
spur self status --json
|
|
41
|
+
spur self migrate --check --json
|
|
42
|
+
spur self exec --list --json
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`self migrate --check` is read-only: exit 1 means importer-schema drift and requires the apply
|
|
46
|
+
step below; an absent database is allowed. Keep the current checkout or another backup ref so the
|
|
47
|
+
migration can be reverted. If `.spur/spur.db` exists, stop the project server and copy it to a
|
|
48
|
+
backup location before continuing. Do not back up or restore Markdown by deleting it first.
|
|
49
|
+
|
|
50
|
+
## 3. Install matching CLI and plugin
|
|
51
|
+
|
|
52
|
+
Install the CLI, then install the plugin from that same package rather than mixing versions:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
bun add -g @gobing-ai/superskill @gobing-ai/spur@0.4.34
|
|
56
|
+
superskill install sp --marketplace $(dirname $(bun pm bin -g))/install/global/node_modules/@gobing-ai/spur
|
|
57
|
+
spur --version
|
|
58
|
+
spur agent doctor --json
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Confirm the reported CLI version is the intended release. `agent doctor` checks executor usability;
|
|
62
|
+
it does not prove authentication or provider quota. The Electron shell is retired: use
|
|
63
|
+
`spur self serve` for the Board.
|
|
64
|
+
|
|
65
|
+
## 4. Migrate `.spur/config.yaml`
|
|
66
|
+
|
|
67
|
+
Preserve project-specific values and make only the applicable edits:
|
|
68
|
+
|
|
69
|
+
| Existing state | Required action |
|
|
70
|
+
| --- | --- |
|
|
71
|
+
| `.spur/config.json` still supplies project config | Move the project-owned values into `.spur/config.yaml`; do not keep a second config authority. |
|
|
72
|
+
| `version` is absent or an integer | Use the YAML string `version: "1.1"`. |
|
|
73
|
+
| `agent.team` or `.spur/fleet.json` declares a roster | Move it to **project-layer** `agent.fleet`. Keep executor profiles in the global layer, not in the fleet. A leftover old carrier fails loading by design. |
|
|
74
|
+
| `workflow.hitlDecisionMaker` | Rename the section to `workflows.hitlDecisionMaker`. |
|
|
75
|
+
| Bun project with no `builder` block | No change: `builder.preset: bun` supplies the existing commands. |
|
|
76
|
+
| Non-Bun project, or custom commands required | Set `builder.preset: none` and bind `builder.actions` (`lint`, `test`, `check`, and any required `*-changed` actions) to the project's real commands. |
|
|
77
|
+
| `tasks.folders` and `tasks.active` | Preserve folder names, labels, `baseCounter`, and the active folder. Archived folders remain readable history, not a bulk-migration target. |
|
|
78
|
+
| `features.dir` | Preserve the existing feature tree and hierarchical IDs; do not renumber features or scenario titles. |
|
|
79
|
+
|
|
80
|
+
Example non-Bun binding only (replace placeholders with real commands):
|
|
81
|
+
|
|
82
|
+
```yaml
|
|
83
|
+
builder:
|
|
84
|
+
preset: none
|
|
85
|
+
actions:
|
|
86
|
+
lint: <project-lint-command>
|
|
87
|
+
test: <project-test-command>
|
|
88
|
+
check: [lint, test]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Validate resolution without running the actions:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
spur self exec --list --json
|
|
95
|
+
spur self exec check --dry-run --json
|
|
96
|
+
spur self status --json
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Do not copy `config/config.example.yaml` over the project file. That example documents available
|
|
100
|
+
keys; it is not a migration target.
|
|
101
|
+
|
|
102
|
+
## 5. Reconcile project workflow overrides
|
|
103
|
+
|
|
104
|
+
The bundled definitions are not drop-in replacements for project overrides. Compare with
|
|
105
|
+
`spur workflow list --json` and validate every project-owned definition with
|
|
106
|
+
`spur workflow validate <path>`.
|
|
107
|
+
|
|
108
|
+
For 0.4.34 specifically:
|
|
109
|
+
|
|
110
|
+
- Task execution is D65: `precheck → implement[⇄escalate] → gate[⇄remediate] → review → done`.
|
|
111
|
+
There are no separate `test`/`test-recheck`, `approve`, `verify`, or `record` states. Completion
|
|
112
|
+
evidence is a tree-hash gate receipt plus the reviewer verdict; the old proof-digest chain,
|
|
113
|
+
answer lint, answer-confidence fields, residual sweep, and verdict replay are gone.
|
|
114
|
+
- `deferQualityGate: "true"` makes the incremental gate the per-task default. The full project
|
|
115
|
+
gate runs once per feature or batch; do not remove that integrated-tree check.
|
|
116
|
+
- Feature verification is owned by `feature-lifecycle`: entering `verifying` runs
|
|
117
|
+
`spur feature verify`. The separate `feature-verification.yaml` workflow is removed, and
|
|
118
|
+
`wrapup-pipeline` no longer changes feature status.
|
|
119
|
+
- Planning uses `idea-pipeline`. The legacy planning, docs, feature-development, duplicate task,
|
|
120
|
+
PR-review, and wayfinder workflow definitions are removed. Route former callers to `idea-pipeline`,
|
|
121
|
+
`task-pipeline`, `feature-lifecycle`,
|
|
122
|
+
`spur builder pr-check|pr-open|pr-review|pr-status`, or the wayfinder skill as appropriate.
|
|
123
|
+
- Project-layer `task-lifecycle.yaml` and `feature-lifecycle.yaml` overrides must be re-synced for
|
|
124
|
+
current guards and the non-terminal `deprecated` state. The status addition itself needs no
|
|
125
|
+
corpus migration.
|
|
126
|
+
|
|
127
|
+
## 6. Migrate and validate the planning corpus
|
|
128
|
+
|
|
129
|
+
Run only the operations that apply to the project's actual state:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# A17 normalization only, not a general schema migrator — preview first.
|
|
133
|
+
spur task migrate --dry-run --json
|
|
134
|
+
# If the preview shows wanted A17 changes and no conflicts:
|
|
135
|
+
spur task migrate --json
|
|
136
|
+
|
|
137
|
+
# Structural checks and optional mechanical repairs; review their diffs afterward.
|
|
138
|
+
spur task check --fix --json
|
|
139
|
+
spur feature check --fix --json
|
|
140
|
+
|
|
141
|
+
# Required final validation (warnings are reported separately from errors).
|
|
142
|
+
spur task check --corpus --json
|
|
143
|
+
spur feature check --json
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`task migrate` normalizes the active task folder (or an explicit `--folder`). It is not a
|
|
147
|
+
feature-migration or a reason to rewrite archived folders. `task check --fix` and
|
|
148
|
+
`feature check --fix` repair structure, not semantics or Gherkin conversion.
|
|
149
|
+
|
|
150
|
+
Feature acceptance criteria must use valid BDD with a `Feature:` declaration and `Scenario:`
|
|
151
|
+
blocks. Raw legacy bullets need a deliberate conversion through:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
spur feature update <id> --section 'Acceptance Criteria' --from-file /tmp/<id>-ac.md
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Preserve each scenario's R-number and meaning. Use `@deprecated` and a Notes entry for a scenario
|
|
158
|
+
that is historical rather than a current requirement. If validation exposes an old hook or
|
|
159
|
+
lifecycle claim, check the current ADR/design owner instead of editing it blindly.
|
|
160
|
+
|
|
161
|
+
Preview lifecycle derivation before applying it, then regenerate derived projections only after
|
|
162
|
+
the corpus checks are understood:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
spur feature sync --all --dry-run --json
|
|
166
|
+
spur feature sync --all --json
|
|
167
|
+
spur feature refresh --all --json
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
A missing, stale, running, or failed feature-verification receipt is unfinished work, not something
|
|
171
|
+
to hide by forcing a transition. Run `spur feature verify <id>` after the applicable full project
|
|
172
|
+
gate passes. Ordinary warnings can remain visible; do not manufacture a clean report by editing
|
|
173
|
+
history or suppressing findings.
|
|
174
|
+
|
|
175
|
+
## 7. Final acceptance and rollback
|
|
176
|
+
|
|
177
|
+
Run the project gate and inspect the migration diff:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
spur self exec check
|
|
181
|
+
spur self exec check-feature # when closing a feature; full project gate
|
|
182
|
+
spur workflow list --json
|
|
183
|
+
spur task check --corpus --json
|
|
184
|
+
spur feature check --json
|
|
185
|
+
git diff --check
|
|
186
|
+
git status --short
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
`check-feature` is the full project gate for feature closure, not a replacement for the default
|
|
190
|
+
incremental task gate. If an action is unbound, fix `builder.actions` instead of substituting a
|
|
191
|
+
command from another repository.
|
|
192
|
+
|
|
193
|
+
Stop on the first material error. Restore from the backup ref rather than running init or
|
|
194
|
+
bulk-deleting files:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
git diff <backup-ref> -- .spur/config.yaml docs/features docs/tasks*
|
|
198
|
+
git restore --source=<backup-ref> -- .spur/config.yaml docs/features docs/tasks*
|
|
199
|
+
git status --short
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Review new untracked files separately; `git restore` does not remove them. Reinstall the previous
|
|
203
|
+
CLI/plugin version and restore the database backup only after stopping the server. Keep failure
|
|
204
|
+
logs and durable evidence under `.spur/memory/`; `.spur/run/` is disposable scratch, not a rollback
|
|
205
|
+
authority.
|
|
206
|
+
|
|
207
|
+
## Agent handoff
|
|
208
|
+
|
|
209
|
+
Give the downstream agent this boundary verbatim:
|
|
210
|
+
|
|
211
|
+
> Follow `plugins/sp/skills/spur-dev/references/migration-guide.md`. Preserve `.spur/config.yaml`,
|
|
212
|
+
> `docs/features/`, and `docs/tasks*/`. Use dry runs and CLI-gated writes. Do not run `init --force`,
|
|
213
|
+
> replace project workflows wholesale, rewrite historical corpus, or hide verification findings.
|
|
214
|
+
> Stop on the first material error and report it with its evidence.
|
package/spur.js
CHANGED
|
@@ -111389,29 +111389,6 @@ function isPlaceholderBody(body) {
|
|
|
111389
111389
|
const stripped = body.replace(/<!--[\s\S]*?-->/g, "").replace(/^\s*>\s*TBD\s*$/gim, "").trim();
|
|
111390
111390
|
return stripped.length === 0;
|
|
111391
111391
|
}
|
|
111392
|
-
function extractBacktickLineAnchors(body) {
|
|
111393
|
-
const out = [];
|
|
111394
|
-
const re = /`([^`\n]+?:(\d+)(?:-(\d+))?)`/g;
|
|
111395
|
-
let m = re.exec(body);
|
|
111396
|
-
while (m !== null) {
|
|
111397
|
-
const raw = m[1] ?? "";
|
|
111398
|
-
const startStr = m[2];
|
|
111399
|
-
const endStr = m[3];
|
|
111400
|
-
if (startStr) {
|
|
111401
|
-
const pathPart = raw.replace(/:(\d+)(?:-(\d+))?$/, "");
|
|
111402
|
-
const hasExt = /\.\w{1,8}$/.test(pathPart.split("/").pop() ?? "");
|
|
111403
|
-
if (pathPart && !pathPart.includes("://") && hasExt) {
|
|
111404
|
-
const startLine = Number(startStr);
|
|
111405
|
-
const endLine = endStr !== undefined ? Number(endStr) : undefined;
|
|
111406
|
-
if (Number.isFinite(startLine) && startLine >= 1) {
|
|
111407
|
-
out.push({ raw, path: pathPart, startLine, endLine });
|
|
111408
|
-
}
|
|
111409
|
-
}
|
|
111410
|
-
}
|
|
111411
|
-
m = re.exec(body);
|
|
111412
|
-
}
|
|
111413
|
-
return out;
|
|
111414
|
-
}
|
|
111415
111392
|
var TaskCheckService;
|
|
111416
111393
|
var init_task_check = __esm(() => {
|
|
111417
111394
|
init_src2();
|
|
@@ -128900,174 +128877,6 @@ var init_token_ledger_watcher = __esm(() => {
|
|
|
128900
128877
|
init_token_ledger_service();
|
|
128901
128878
|
});
|
|
128902
128879
|
|
|
128903
|
-
// ../../packages/app/src/services/sdlc-actions.ts
|
|
128904
|
-
function isReservedAction(name) {
|
|
128905
|
-
return RESERVED_ACTIONS.includes(name);
|
|
128906
|
-
}
|
|
128907
|
-
function resolveAction(builder, name) {
|
|
128908
|
-
const reserved = isReservedAction(name);
|
|
128909
|
-
const declared = builder?.actions?.[name];
|
|
128910
|
-
if (declared !== undefined) {
|
|
128911
|
-
return Array.isArray(declared) ? { name, reserved, source: "config", kind: "composite", steps: [...declared] } : { name, reserved, source: "config", kind: "command", command: declared };
|
|
128912
|
-
}
|
|
128913
|
-
if ((builder?.preset ?? "bun") === "bun") {
|
|
128914
|
-
if (reserved)
|
|
128915
|
-
return { name, reserved, source: "preset", kind: "command", command: BUN_PRESET[name] };
|
|
128916
|
-
} else {
|
|
128917
|
-
const command = reserved ? STACK_NEUTRAL_DEFAULTS[name] : undefined;
|
|
128918
|
-
if (command !== undefined)
|
|
128919
|
-
return { name, reserved, source: "default", kind: "command", command };
|
|
128920
|
-
}
|
|
128921
|
-
return { name, reserved, source: "unbound", kind: "unbound" };
|
|
128922
|
-
}
|
|
128923
|
-
function listActions(builder) {
|
|
128924
|
-
const custom = Object.keys(builder?.actions ?? {}).filter((name) => !isReservedAction(name)).sort();
|
|
128925
|
-
return [...RESERVED_ACTIONS, ...custom].map((name) => resolveAction(builder, name));
|
|
128926
|
-
}
|
|
128927
|
-
function renderScoped(command, values) {
|
|
128928
|
-
let rendered = command;
|
|
128929
|
-
for (const [placeholder, items] of [
|
|
128930
|
-
["{files}", values.files],
|
|
128931
|
-
["{paths}", values.paths]
|
|
128932
|
-
]) {
|
|
128933
|
-
if (!rendered.includes(placeholder))
|
|
128934
|
-
continue;
|
|
128935
|
-
if (items === undefined)
|
|
128936
|
-
throw new Error(`renderScoped: ${placeholder} has no value`);
|
|
128937
|
-
if (items.length === 0)
|
|
128938
|
-
throw new Error(`renderScoped: ${placeholder} has an empty list`);
|
|
128939
|
-
rendered = rendered.replaceAll(placeholder, () => items.map(shQuote2).join(" "));
|
|
128940
|
-
}
|
|
128941
|
-
if (rendered.includes("{workspace}")) {
|
|
128942
|
-
const workspace = values.workspace;
|
|
128943
|
-
if (workspace === undefined)
|
|
128944
|
-
throw new Error("renderScoped: {workspace} has no value");
|
|
128945
|
-
if (workspace.length === 0)
|
|
128946
|
-
throw new Error("renderScoped: {workspace} is empty");
|
|
128947
|
-
rendered = rendered.replaceAll("{workspace}", () => shQuote2(workspace));
|
|
128948
|
-
}
|
|
128949
|
-
return rendered;
|
|
128950
|
-
}
|
|
128951
|
-
var RESERVED_ACTIONS, BUN_PRESET, STACK_NEUTRAL_DEFAULTS;
|
|
128952
|
-
var init_sdlc_actions = __esm(() => {
|
|
128953
|
-
init_src();
|
|
128954
|
-
RESERVED_ACTIONS = SDLC_ACTIONS;
|
|
128955
|
-
BUN_PRESET = {
|
|
128956
|
-
build: "bun run build",
|
|
128957
|
-
dev: "bun run dev",
|
|
128958
|
-
clean: "bun run clean",
|
|
128959
|
-
format: "bun run format",
|
|
128960
|
-
autofix: "bun run autofix",
|
|
128961
|
-
lint: "bun run lint",
|
|
128962
|
-
typecheck: "bun run typecheck",
|
|
128963
|
-
test: "bun run test",
|
|
128964
|
-
"test-full": "bun run test:full",
|
|
128965
|
-
"test-heavy": "bun run test:heavy",
|
|
128966
|
-
"rules-pre": "bun run test-pre-check",
|
|
128967
|
-
"rules-post": "bun run test-post-check",
|
|
128968
|
-
check: "bun run spur-check",
|
|
128969
|
-
"check-full": "bun run spur-check:full",
|
|
128970
|
-
"check-feature": "bun run spur-check-feature",
|
|
128971
|
-
"lint-changed": "bunx biome check {files}",
|
|
128972
|
-
"typecheck-changed": "cd {workspace} && bun run typecheck",
|
|
128973
|
-
"test-changed": "cd {workspace} && bun test --coverage --coverage-reporter=text {paths}"
|
|
128974
|
-
};
|
|
128975
|
-
STACK_NEUTRAL_DEFAULTS = SDLC_STACK_NEUTRAL_ACTIONS;
|
|
128976
|
-
});
|
|
128977
|
-
|
|
128978
|
-
// ../../packages/app/src/services/verdict-replay.ts
|
|
128979
|
-
function citedTestAnchors(rows, max) {
|
|
128980
|
-
const seen = new Map;
|
|
128981
|
-
for (const row of rows) {
|
|
128982
|
-
for (const anchor of extractBacktickLineAnchors(row.evidence ?? "")) {
|
|
128983
|
-
if (!TEST_FILE_PATTERN.test(anchor.path) || seen.has(anchor.path))
|
|
128984
|
-
continue;
|
|
128985
|
-
seen.set(anchor.path, anchor.raw);
|
|
128986
|
-
}
|
|
128987
|
-
}
|
|
128988
|
-
return new Map([...seen].slice(0, max));
|
|
128989
|
-
}
|
|
128990
|
-
function replayWorkspace(file) {
|
|
128991
|
-
const parts = file.split("/");
|
|
128992
|
-
if (parts.length >= 3 && (parts[0] === "apps" || parts[0] === "packages"))
|
|
128993
|
-
return `${parts[0]}/${parts[1]}`;
|
|
128994
|
-
return ".";
|
|
128995
|
-
}
|
|
128996
|
-
async function replayCitedTests(rows, options = {}) {
|
|
128997
|
-
const cwd = options.cwd ?? process.cwd();
|
|
128998
|
-
const timeoutMs = options.timeoutMs ?? REPLAY_TIMEOUT_MS;
|
|
128999
|
-
const anchors = citedTestAnchors(rows, options.maxFiles ?? REPLAY_MAX_FILES);
|
|
129000
|
-
const files = [...anchors.keys()];
|
|
129001
|
-
const executor = options.executor ?? new NodeProcessExecutor2;
|
|
129002
|
-
const fileSystem = options.fs ?? createNodeFileSystem2();
|
|
129003
|
-
const resolved = resolveAction(options.builder, "test-changed");
|
|
129004
|
-
const template = resolved.kind === "command" ? resolved.command : resolved.kind === "composite" ? resolved.steps.join(" && ") : "";
|
|
129005
|
-
const ran = [];
|
|
129006
|
-
const skipped = [];
|
|
129007
|
-
const findings = [];
|
|
129008
|
-
const thresholdByWorkspace = new Map;
|
|
129009
|
-
for (const file of files) {
|
|
129010
|
-
const anchor = anchors.get(file) ?? file;
|
|
129011
|
-
const exists = await fileSystem.exists(`${cwd}/${file}`);
|
|
129012
|
-
if (!exists) {
|
|
129013
|
-
findings.push({ anchor, file, missing: true });
|
|
129014
|
-
continue;
|
|
129015
|
-
}
|
|
129016
|
-
if (template.length === 0)
|
|
129017
|
-
continue;
|
|
129018
|
-
const workspace = replayWorkspace(file);
|
|
129019
|
-
let threshold = thresholdByWorkspace.get(workspace);
|
|
129020
|
-
if (threshold === undefined) {
|
|
129021
|
-
threshold = await declaresCoverageThreshold(fileSystem, `${cwd}/${workspace}/bunfig.toml`);
|
|
129022
|
-
thresholdByWorkspace.set(workspace, threshold);
|
|
129023
|
-
}
|
|
129024
|
-
if (threshold) {
|
|
129025
|
-
skipped.push({ file, reason: `coverageThreshold in ${workspace}/bunfig.toml` });
|
|
129026
|
-
continue;
|
|
129027
|
-
}
|
|
129028
|
-
const path = workspace === "." ? file : file.slice(workspace.length + 1);
|
|
129029
|
-
const command = renderScoped(template, { workspace, paths: [path] });
|
|
129030
|
-
const result = await executor.run({
|
|
129031
|
-
command: "sh",
|
|
129032
|
-
args: ["-c", command],
|
|
129033
|
-
cwd,
|
|
129034
|
-
forceBuffered: true,
|
|
129035
|
-
rejectOnError: false,
|
|
129036
|
-
timeout: timeoutMs
|
|
129037
|
-
});
|
|
129038
|
-
const exitCode = result.exitCode ?? null;
|
|
129039
|
-
ran.push({ file, exitCode });
|
|
129040
|
-
if (result.outcome === "timeout")
|
|
129041
|
-
findings.push({ anchor, file, timedOut: true, exitCode });
|
|
129042
|
-
else if (exitCode !== 0)
|
|
129043
|
-
findings.push({ anchor, file, exitCode });
|
|
129044
|
-
}
|
|
129045
|
-
return { files, ran, skipped, findings };
|
|
129046
|
-
}
|
|
129047
|
-
async function declaresCoverageThreshold(fileSystem, bunfigPath) {
|
|
129048
|
-
try {
|
|
129049
|
-
if (!await fileSystem.exists(bunfigPath))
|
|
129050
|
-
return false;
|
|
129051
|
-
return COVERAGE_THRESHOLD_PATTERN.test(await fileSystem.readFile(bunfigPath));
|
|
129052
|
-
} catch {
|
|
129053
|
-
return false;
|
|
129054
|
-
}
|
|
129055
|
-
}
|
|
129056
|
-
function formatReplayReport(replay) {
|
|
129057
|
-
return {
|
|
129058
|
-
summary: `Replay: ${replay.files.length} cited test file(s), ${replay.findings.length} finding(s)${replay.skipped.length > 0 ? `, ${replay.skipped.length} skipped` : ""}`,
|
|
129059
|
-
findings: replay.findings.map((finding) => ` ${finding.anchor}: ${finding.missing ? "missing" : finding.timedOut ? "timed out" : `exit ${finding.exitCode ?? "unknown"}`}`)
|
|
129060
|
-
};
|
|
129061
|
-
}
|
|
129062
|
-
var REPLAY_MAX_FILES = 5, REPLAY_TIMEOUT_MS = 120000, TEST_FILE_PATTERN, COVERAGE_THRESHOLD_PATTERN;
|
|
129063
|
-
var init_verdict_replay = __esm(() => {
|
|
129064
|
-
init_dist5();
|
|
129065
|
-
init_sdlc_actions();
|
|
129066
|
-
init_task_check();
|
|
129067
|
-
TEST_FILE_PATTERN = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
|
|
129068
|
-
COVERAGE_THRESHOLD_PATTERN = /^\s*coverageThreshold\s*=/m;
|
|
129069
|
-
});
|
|
129070
|
-
|
|
129071
128880
|
// ../../packages/app/src/workflow/checkpoint-contract.ts
|
|
129072
128881
|
function parseCheckpointMetadata(raw) {
|
|
129073
128882
|
if (!raw.startsWith("---"))
|
|
@@ -136582,7 +136391,6 @@ var init_src4 = __esm(() => {
|
|
|
136582
136391
|
init_task_verdict();
|
|
136583
136392
|
init_token_ledger_service();
|
|
136584
136393
|
init_token_ledger_watcher();
|
|
136585
|
-
init_verdict_replay();
|
|
136586
136394
|
init_warm_turn_dispatch();
|
|
136587
136395
|
init_workflow_clean_ops();
|
|
136588
136396
|
init_workflow_service();
|
|
@@ -143810,6 +143618,81 @@ var init_task_diffstat = __esm(() => {
|
|
|
143810
143618
|
init_task_triage();
|
|
143811
143619
|
});
|
|
143812
143620
|
|
|
143621
|
+
// ../../packages/app/src/services/sdlc-actions.ts
|
|
143622
|
+
function isReservedAction(name) {
|
|
143623
|
+
return RESERVED_ACTIONS.includes(name);
|
|
143624
|
+
}
|
|
143625
|
+
function resolveAction(builder, name) {
|
|
143626
|
+
const reserved = isReservedAction(name);
|
|
143627
|
+
const declared = builder?.actions?.[name];
|
|
143628
|
+
if (declared !== undefined) {
|
|
143629
|
+
return Array.isArray(declared) ? { name, reserved, source: "config", kind: "composite", steps: [...declared] } : { name, reserved, source: "config", kind: "command", command: declared };
|
|
143630
|
+
}
|
|
143631
|
+
if ((builder?.preset ?? "bun") === "bun") {
|
|
143632
|
+
if (reserved)
|
|
143633
|
+
return { name, reserved, source: "preset", kind: "command", command: BUN_PRESET[name] };
|
|
143634
|
+
} else {
|
|
143635
|
+
const command = reserved ? STACK_NEUTRAL_DEFAULTS[name] : undefined;
|
|
143636
|
+
if (command !== undefined)
|
|
143637
|
+
return { name, reserved, source: "default", kind: "command", command };
|
|
143638
|
+
}
|
|
143639
|
+
return { name, reserved, source: "unbound", kind: "unbound" };
|
|
143640
|
+
}
|
|
143641
|
+
function listActions(builder) {
|
|
143642
|
+
const custom = Object.keys(builder?.actions ?? {}).filter((name) => !isReservedAction(name)).sort();
|
|
143643
|
+
return [...RESERVED_ACTIONS, ...custom].map((name) => resolveAction(builder, name));
|
|
143644
|
+
}
|
|
143645
|
+
function renderScoped(command, values) {
|
|
143646
|
+
let rendered = command;
|
|
143647
|
+
for (const [placeholder, items] of [
|
|
143648
|
+
["{files}", values.files],
|
|
143649
|
+
["{paths}", values.paths]
|
|
143650
|
+
]) {
|
|
143651
|
+
if (!rendered.includes(placeholder))
|
|
143652
|
+
continue;
|
|
143653
|
+
if (items === undefined)
|
|
143654
|
+
throw new Error(`renderScoped: ${placeholder} has no value`);
|
|
143655
|
+
if (items.length === 0)
|
|
143656
|
+
throw new Error(`renderScoped: ${placeholder} has an empty list`);
|
|
143657
|
+
rendered = rendered.replaceAll(placeholder, () => items.map(shQuote2).join(" "));
|
|
143658
|
+
}
|
|
143659
|
+
if (rendered.includes("{workspace}")) {
|
|
143660
|
+
const workspace = values.workspace;
|
|
143661
|
+
if (workspace === undefined)
|
|
143662
|
+
throw new Error("renderScoped: {workspace} has no value");
|
|
143663
|
+
if (workspace.length === 0)
|
|
143664
|
+
throw new Error("renderScoped: {workspace} is empty");
|
|
143665
|
+
rendered = rendered.replaceAll("{workspace}", () => shQuote2(workspace));
|
|
143666
|
+
}
|
|
143667
|
+
return rendered;
|
|
143668
|
+
}
|
|
143669
|
+
var RESERVED_ACTIONS, BUN_PRESET, STACK_NEUTRAL_DEFAULTS;
|
|
143670
|
+
var init_sdlc_actions = __esm(() => {
|
|
143671
|
+
init_src();
|
|
143672
|
+
RESERVED_ACTIONS = SDLC_ACTIONS;
|
|
143673
|
+
BUN_PRESET = {
|
|
143674
|
+
build: "bun run build",
|
|
143675
|
+
dev: "bun run dev",
|
|
143676
|
+
clean: "bun run clean",
|
|
143677
|
+
format: "bun run format",
|
|
143678
|
+
autofix: "bun run autofix",
|
|
143679
|
+
lint: "bun run lint",
|
|
143680
|
+
typecheck: "bun run typecheck",
|
|
143681
|
+
test: "bun run test",
|
|
143682
|
+
"test-full": "bun run test:full",
|
|
143683
|
+
"test-heavy": "bun run test:heavy",
|
|
143684
|
+
"rules-pre": "bun run test-pre-check",
|
|
143685
|
+
"rules-post": "bun run test-post-check",
|
|
143686
|
+
check: "bun run spur-check",
|
|
143687
|
+
"check-full": "bun run spur-check:full",
|
|
143688
|
+
"check-feature": "bun run spur-check-feature",
|
|
143689
|
+
"lint-changed": "bunx biome check {files}",
|
|
143690
|
+
"typecheck-changed": "cd {workspace} && bun run typecheck",
|
|
143691
|
+
"test-changed": "cd {workspace} && bun test --coverage --coverage-reporter=text {paths}"
|
|
143692
|
+
};
|
|
143693
|
+
STACK_NEUTRAL_DEFAULTS = SDLC_STACK_NEUTRAL_ACTIONS;
|
|
143694
|
+
});
|
|
143695
|
+
|
|
143813
143696
|
// ../../packages/app/src/services/quality-gate.ts
|
|
143814
143697
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
143815
143698
|
import { randomUUID as randomUUID12 } from "crypto";
|
|
@@ -144131,7 +144014,7 @@ function lightScope(changedFiles, exists = existsSync23, graph) {
|
|
|
144131
144014
|
tests.add(candidate);
|
|
144132
144015
|
matched = true;
|
|
144133
144016
|
}
|
|
144134
|
-
} else if (rest.startsWith("tests/") &&
|
|
144017
|
+
} else if (rest.startsWith("tests/") && TEST_FILE_PATTERN.test(rest)) {
|
|
144135
144018
|
tests.add(file);
|
|
144136
144019
|
matched = true;
|
|
144137
144020
|
}
|
|
@@ -144148,7 +144031,7 @@ function lightScope(changedFiles, exists = existsSync23, graph) {
|
|
|
144148
144031
|
const candidate = `${workspace}/tests/${rest.slice("src/".length).replace(/\.tsx?$/, (ext) => `.test${ext}`)}`;
|
|
144149
144032
|
if (exists(candidate))
|
|
144150
144033
|
tests.add(candidate);
|
|
144151
|
-
} else if (rest.startsWith("tests/") &&
|
|
144034
|
+
} else if (rest.startsWith("tests/") && TEST_FILE_PATTERN.test(rest)) {
|
|
144152
144035
|
tests.add(file);
|
|
144153
144036
|
}
|
|
144154
144037
|
}
|
|
@@ -144959,7 +144842,7 @@ function qualityGateMain(argv, rawEnv = getEnvVars(), options = {}) {
|
|
|
144959
144842
|
return 2;
|
|
144960
144843
|
}
|
|
144961
144844
|
}
|
|
144962
|
-
var MAX_GATE_ATTEMPTS = 5, MAX_FINDINGS = 20, RETRY_DELAY_MS_DEFAULT = 1e4, RETRY_DELAY_MS_ENV = "SPUR_QUALITY_GATE_RETRY_DELAY_MS", GATE_TIMEOUT_MS_ENV = "gateTimeoutMs", GATE_TIMEOUT_MS_DEFAULT = 900000, LOCKED_PATTERN, FINDINGS_PATTERN, COVERAGE_ROW_PATTERN, RECEIPT_SCHEMA_VERSION = "check-receipt/v1",
|
|
144845
|
+
var MAX_GATE_ATTEMPTS = 5, MAX_FINDINGS = 20, RETRY_DELAY_MS_DEFAULT = 1e4, RETRY_DELAY_MS_ENV = "SPUR_QUALITY_GATE_RETRY_DELAY_MS", GATE_TIMEOUT_MS_ENV = "gateTimeoutMs", GATE_TIMEOUT_MS_DEFAULT = 900000, LOCKED_PATTERN, FINDINGS_PATTERN, COVERAGE_ROW_PATTERN, RECEIPT_SCHEMA_VERSION = "check-receipt/v1", TEST_FILE_PATTERN, GATE_LOCK_DIR_ENV = "SPUR_GATE_LOCK_DIR", GATE_LOCK_POLL_MS_ENV = "SPUR_GATE_LOCK_POLL_MS", GATE_LOCK_OFF_ENV = "SPUR_GATE_LOCK", GATE_LOCK_TOKEN_ENV = "SPUR_GATE_LOCK_TOKEN", GATE_LOCK_POLL_MS_DEFAULT = 5000, GATE_LOCK_TIMEOUT_MS_ENV = "SPUR_GATE_LOCK_TIMEOUT_MS", GATE_LOCK_TIMEOUT_MS_DEFAULT, GATE_LOCK_MKDIR_HOLD_MS_ENV = "SPUR_GATE_LOCK_TEST_MKDIR_HOLD_MS", GATE_LOCK_LOST_RACE_SLEEP_MS = 20, GATE_LOCK_LOST_RACE_LOG_AT = 250, GateLockTimeoutError, QUALITY_GATE_MODES, QUALITY_GATE_USAGE = "usage: spur task gate <wbs> <run|recheck|light|deferred|status> (env: qualityGateCmd, gateProbeCmd, runId)";
|
|
144963
144846
|
var init_quality_gate = __esm(() => {
|
|
144964
144847
|
init_dist();
|
|
144965
144848
|
init_proof_input_fingerprint();
|
|
@@ -144967,7 +144850,7 @@ var init_quality_gate = __esm(() => {
|
|
|
144967
144850
|
LOCKED_PATTERN = /SQLiteError: database is locked|SQLite database .*is busy|SQLITE_BUSY/;
|
|
144968
144851
|
FINDINGS_PATTERN = /[A-Za-z0-9_./-]+\.[A-Za-z]+:[0-9]+/g;
|
|
144969
144852
|
COVERAGE_ROW_PATTERN = /^\s*(\S+\.[A-Za-z]+)\s*\|\s*([\d.]+)\s*\|\s*([\d.]+)\s*\|\s*(\S*)/;
|
|
144970
|
-
|
|
144853
|
+
TEST_FILE_PATTERN = /\.test\.tsx?$/;
|
|
144971
144854
|
GATE_LOCK_TIMEOUT_MS_DEFAULT = 2 * 60 * 60 * 1000;
|
|
144972
144855
|
GateLockTimeoutError = class GateLockTimeoutError extends Error {
|
|
144973
144856
|
constructor(message) {
|
|
@@ -158122,7 +158005,7 @@ function registerTaskUpdateCommand(task, context) {
|
|
|
158122
158005
|
// src/commands/task/verdict.ts
|
|
158123
158006
|
init_src4();
|
|
158124
158007
|
function registerTaskVerdictCommand(task, context) {
|
|
158125
|
-
task.command("verdict").summary("Derive PASS/PARTIAL/FAIL/UNKNOWN verdict from verify answer text (replaces pipeline grep/shell).").argument("<wbs>", "Task WBS number").option("--from-answer <path>", "Path to verify answer text file").option(
|
|
158008
|
+
task.command("verdict").summary("Derive PASS/PARTIAL/FAIL/UNKNOWN verdict from verify answer text (replaces pipeline grep/shell).").argument("<wbs>", "Task WBS number").option("--from-answer <path>", "Path to verify answer text file").option(...SHARED_OPTIONS.folderTasks).option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (wbs, options) => {
|
|
158126
158009
|
await Promise.resolve().then(() => init_src4());
|
|
158127
158010
|
const answerPath = options.fromAnswer ?? `.spur/run/${wbs}-verify-answer.txt`;
|
|
158128
158011
|
let answerText;
|
|
@@ -158143,17 +158026,9 @@ function registerTaskVerdictCommand(task, context) {
|
|
|
158143
158026
|
const taskCheckPassed = true;
|
|
158144
158027
|
const taskAc = await readTaskAcceptanceCriteria(wbs, corpusDeps);
|
|
158145
158028
|
const result = deriveVerdict(answerText, taskCheckPassed, taskAc);
|
|
158146
|
-
const replay = options.replay === true ? await replayCitedTests([...result.requirements, ...result.acceptanceCriteria ?? []], {
|
|
158147
|
-
cwd: context.cwd,
|
|
158148
|
-
builder: context.spurConfig?.builder
|
|
158149
|
-
}) : undefined;
|
|
158150
|
-
const downgraded = replay !== undefined && replay.findings.length > 0 && result.verdict === "PASS";
|
|
158151
|
-
if (downgraded)
|
|
158152
|
-
result.verdict = "PARTIAL";
|
|
158153
158029
|
const jsonOut = JSON.stringify({
|
|
158154
158030
|
wbs,
|
|
158155
158031
|
...result,
|
|
158156
|
-
...replay !== undefined ? { replay } : {},
|
|
158157
158032
|
source: "spur-task-verdict"
|
|
158158
158033
|
}, null, 2);
|
|
158159
158034
|
await context.fs.ensureDir(".spur/run");
|
|
@@ -158163,12 +158038,6 @@ function registerTaskVerdictCommand(task, context) {
|
|
|
158163
158038
|
context.output.write(jsonOut);
|
|
158164
158039
|
} else {
|
|
158165
158040
|
context.output.write(`Verdict: ${result.verdict} (${result.requirements.length} requirements, ${result.checks.length} checks)`);
|
|
158166
|
-
if (replay !== undefined) {
|
|
158167
|
-
const report = formatReplayReport(replay);
|
|
158168
|
-
context.output.write(report.summary);
|
|
158169
|
-
for (const line of report.findings)
|
|
158170
|
-
context.output.error(line);
|
|
158171
|
-
}
|
|
158172
158041
|
}
|
|
158173
158042
|
if (result.verdict !== "PASS") {
|
|
158174
158043
|
context.setExitCode(1);
|
|
@@ -163067,7 +162936,7 @@ init_src2();
|
|
|
163067
162936
|
var CLI_CONFIG = {
|
|
163068
162937
|
binaryName: "spur",
|
|
163069
162938
|
binaryLabel: "spur",
|
|
163070
|
-
binaryVersion: "0.4.
|
|
162939
|
+
binaryVersion: "0.4.35",
|
|
163071
162940
|
configDir: ".spur",
|
|
163072
162941
|
configFile: ".spur/config.yaml",
|
|
163073
162942
|
databaseFile: ".spur/spur.db"
|
|
@@ -170806,7 +170675,7 @@ init_dist5();
|
|
|
170806
170675
|
import { homedir as homedir17 } from "os";
|
|
170807
170676
|
import { basename as basename21, join as join91, relative as relative18, resolve as resolve37 } from "path";
|
|
170808
170677
|
// ../server/package.json
|
|
170809
|
-
var version4 = "0.4.
|
|
170678
|
+
var version4 = "0.4.35";
|
|
170810
170679
|
|
|
170811
170680
|
// ../server/src/modules/health/index.ts
|
|
170812
170681
|
var startedAt = Date.now();
|