@gobing-ai/spur 0.3.46 → 0.3.47
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/templates/docs/99_PROJECT_CONSTITUTION.md +16 -4
- package/package.json +1 -1
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/skills/issue-finding/SKILL.md +1 -1
- package/plugins/sp/skills/issue-finding/references/session-formats.md +9 -2
- package/plugins/sp/skills/spur-cli/references/features.md +3 -0
- package/plugins/sp/skills/spur-cli/references/tasks.md +3 -0
- package/spur.js +12 -6
|
@@ -3,7 +3,7 @@ name: Project Constitution
|
|
|
3
3
|
doc: 99_PROJECT_CONSTITUTION
|
|
4
4
|
owns: PROCESS — how the key files are maintained
|
|
5
5
|
authority: authoritative-on-process
|
|
6
|
-
version: 1.
|
|
6
|
+
version: 1.4.0
|
|
7
7
|
created_at: {{init-date}}
|
|
8
8
|
updated_at: {{init-date}}
|
|
9
9
|
edit_rules: 99 §6.8
|
|
@@ -148,6 +148,10 @@ Rules:
|
|
|
148
148
|
same change.
|
|
149
149
|
- `AGENTS.md` may **add** project facts; it may never **contradict** the numbered docs. On
|
|
150
150
|
contradiction, the numbered doc wins — fix `AGENTS.md`.
|
|
151
|
+
- In a monorepo, subdirectory `AGENTS.md` files merge with the root: the agent reads the root
|
|
152
|
+
first, then the file for the package it is working in. Each level carries only its own scope —
|
|
153
|
+
the root holds what spans packages, a package file holds what is true of that package alone.
|
|
154
|
+
Never restate one level's facts at another.
|
|
151
155
|
|
|
152
156
|
### 4.5 Index + satellite docs (`04`/`05` and their folders)
|
|
153
157
|
|
|
@@ -344,11 +348,19 @@ with raw file writes.
|
|
|
344
348
|
1. Factual blocks that mirror code — the command surface, the workspace layout, tool versions —
|
|
345
349
|
are **regenerated from code**, never edited from memory. Verify with the actual registrations
|
|
346
350
|
(e.g. list the CLI's registered nouns/verbs) before writing the block.
|
|
347
|
-
2.
|
|
351
|
+
2. File structure is the most perishable thing you can write down: paths move, and a stale path
|
|
352
|
+
sends an agent confidently to a file that is not there. Prefer capabilities and domain
|
|
353
|
+
vocabulary — which outlive layout — over directory listings; where a concrete path is genuinely
|
|
354
|
+
needed, regenerate it from code per rule 1.
|
|
355
|
+
3. Keep it lean: link to the owning doc instead of restating its facts. `AGENTS.md` repeats only
|
|
348
356
|
what an agent needs in the first 30 seconds of a session.
|
|
349
|
-
|
|
357
|
+
4. Keep the instruction count inside a budget: roughly 150–200 instructions, beyond which an agent
|
|
358
|
+
attends to them unevenly and the marginal rule buys nothing (MEDIUM confidence — secondary
|
|
359
|
+
citation, not measured here). Over budget, cut the rule or move it to its owning doc and link
|
|
360
|
+
per rule 3.
|
|
361
|
+
5. Surfaces that are decided-but-unbuilt are flagged as planned with their ADR pointer, and
|
|
350
362
|
marked "do not invoke as if they exist".
|
|
351
|
-
|
|
363
|
+
6. Re-synced whenever this file changes the map or process (§4.4).
|
|
352
364
|
|
|
353
365
|
### 6.8 This file (`99`)
|
|
354
366
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobing-ai/spur",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.47",
|
|
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/plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.47",
|
|
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"]
|
|
@@ -202,7 +202,7 @@ Before any ad-hoc verification SQL against `history_*` tables, follow the schema
|
|
|
202
202
|
| `test-loop` | Same test command run 3+ times with no source edit between runs | (identical runs − 1) × ~2 min |
|
|
203
203
|
| `guard` | 3+ `spur task check` calls for the same task before pass | (extra checks) × ~3 min |
|
|
204
204
|
| `compaction` | Compactions > 5 per session | count × ~2.5 min |
|
|
205
|
-
| `section-write` | `spur task update --section` calls >
|
|
205
|
+
| `section-write` | `spur task update --section` calls per task > 1.5× the canonical section count for the task's variant/status matrix entry (feature-impl ≈ 9 sections ⇒ flag > ~13 writes/task; one write per canonical section is correct behavior, not waste) | (extra writes) × ~2 min |
|
|
206
206
|
| `git-red-herring` | `git stash` / `git branch` / `git diff` between test failures | ~5–20 min per incident |
|
|
207
207
|
| `verbose-output` | Unfiltered test output flooding (e.g. bare `tail` without failure filter) | per-run × ~1500 tokens |
|
|
208
208
|
|
|
@@ -47,12 +47,18 @@ When parsing a line of JSONL, look for tool/function invocations under common sh
|
|
|
47
47
|
|
|
48
48
|
| Source family | Typical tool block type / path | Bash/command field |
|
|
49
49
|
|---------------|--------------------------------|--------------------|
|
|
50
|
-
| OMP / omp-agent | `message.content[]` entries with `type: "toolCall"` | `
|
|
50
|
+
| OMP / omp-agent | `message.content[]` entries with `type: "toolCall"` | `arguments.command` |
|
|
51
51
|
| Claude Code | `type: "tool_use"` (or nested message content) | `input.command` / `input` |
|
|
52
52
|
| Codex / others | Importer-normalized or vendor-specific; search for `command`, `tool_name`, `name` | best-effort |
|
|
53
53
|
|
|
54
54
|
Always record **what field path you used** in the inventory Notes so evidence is auditable.
|
|
55
55
|
|
|
56
|
+
**Fail-loud rule:** a zero tool-command count across a **non-empty** session set means the field map
|
|
57
|
+
is wrong, not that the sessions were idle. Report a probable field-map error instead of an
|
|
58
|
+
idle-session / no-waste finding — a parser that matches nothing must never produce a clean verdict
|
|
59
|
+
(0534 R3; the OMP shape is `arguments.command`, verified: a toolCall block's keys are
|
|
60
|
+
`['arguments','id','intent','name','partialArgs','streamIndex','type']`).
|
|
61
|
+
|
|
56
62
|
**Loop detection (all sources):** normalize the shell command string and count consecutive or
|
|
57
63
|
near-consecutive identical invocations (≥3) without an intervening source-file edit tool call.
|
|
58
64
|
|
|
@@ -63,7 +69,8 @@ OMP/agent session logs are JSONL under `~/.omp/agent/sessions/-<project>/`:
|
|
|
63
69
|
- Each line is a JSON object with a `type` field
|
|
64
70
|
- Key event types: `session`, `message`, `compaction`, `title`, `title_change`, `custom`
|
|
65
71
|
- Tool calls live in `message.content` as blocks with `type: "toolCall"` (**not** `tool_use`)
|
|
66
|
-
- Bash tool calls expose `
|
|
72
|
+
- Bash tool calls expose `arguments.command` (verified live against OMP JSONL: a toolCall block's
|
|
73
|
+
keys are `['arguments','id','intent','name','partialArgs','streamIndex','type']`)
|
|
67
74
|
- Subagent sessions live in subdirectories (e.g. `Run0376/`, `Refine0378/`)
|
|
68
75
|
- Subagents may have `*.log` beside the JSONL session file
|
|
69
76
|
- Session start: `session.timestamp`
|
|
@@ -99,6 +99,9 @@ spur feature update H2 --section "Acceptance Criteria" --from-file /tmp/ac.md
|
|
|
99
99
|
`--section` **requires** `--from-file` (exit `2` otherwise). Replaces the whole named section body.
|
|
100
100
|
The `## Tasks` block is still rebuilt by `refresh` (files win for that region).
|
|
101
101
|
|
|
102
|
+
Valid section names without a failed write: `spur task sections <wbs> list` (the same pointer
|
|
103
|
+
`spur task update --help` carries).
|
|
104
|
+
|
|
102
105
|
**Advance** (multi-hop forward walk):
|
|
103
106
|
|
|
104
107
|
```bash
|
|
@@ -24,6 +24,7 @@ Start from the WBS, not the corpus layout:
|
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
26
|
spur task show <wbs> --json # metadata + full content + filePath
|
|
27
|
+
spur task get <wbs> --json # alias of `show` — same output, no separate code path
|
|
27
28
|
spur task path <wbs> --json # absolute path only
|
|
28
29
|
```
|
|
29
30
|
|
|
@@ -148,6 +149,8 @@ spur task update 0040 --section Review --from-file /tmp/review.md
|
|
|
148
149
|
read from a file (this is what makes edits crash-safe and reviewable). Exit `2` otherwise.
|
|
149
150
|
- Section names match the DD-08 task headings: `Background`, `Acceptance Criteria`, `Plan`,
|
|
150
151
|
`Solution`, `Testing`, `Review`, `References`, `History`.
|
|
152
|
+
- Valid section names without a failed write: `spur task sections <wbs> list` (the same
|
|
153
|
+
pointer `task update --help` carries).
|
|
151
154
|
- The write replaces the **whole** named section body; assemble the full section in the temp file
|
|
152
155
|
first, then point `--from-file` at it.
|
|
153
156
|
|
package/spur.js
CHANGED
|
@@ -67691,7 +67691,7 @@ class FeatureService {
|
|
|
67691
67691
|
const current = await this.ctx.fs.readFile(ref.filePath);
|
|
67692
67692
|
const doc2 = MarkdownDocument.parse(current, "feature");
|
|
67693
67693
|
if (!doc2.sectionNames.includes(sectionName)) {
|
|
67694
|
-
throw new Error(`Feature ${id} does not contain section "${sectionName}". Available sections: ${doc2.sectionNames.join(", ")}`);
|
|
67694
|
+
throw new Error(`Feature ${id} does not contain section "${sectionName}". Available sections: ${doc2.sectionNames.join(", ")}. ` + "To list valid section names without a failed write: `spur task sections <wbs> list`.");
|
|
67695
67695
|
}
|
|
67696
67696
|
const raw = await this.ctx.fs.readFile(sourceFile);
|
|
67697
67697
|
const body = stripLeadingSectionHeader(raw, sectionName);
|
|
@@ -84106,7 +84106,7 @@ function registerFeatureCommand(program2, context4) {
|
|
|
84106
84106
|
context4.setExitCode(1);
|
|
84107
84107
|
}
|
|
84108
84108
|
});
|
|
84109
|
-
feature.command("show").summary("Show a feature by ID.").argument("<id>", "Feature ID").option("--folder <path>", "Custom features folder").option("--json", "Output machine-readable JSON").action(async (id, options) => {
|
|
84109
|
+
feature.command("show").alias("get").summary("Show a feature by ID.").argument("<id>", "Feature ID").option("--folder <path>", "Custom features folder").option("--json", "Output machine-readable JSON").action(async (id, options) => {
|
|
84110
84110
|
const svc = await makeService(context4, options.folder);
|
|
84111
84111
|
try {
|
|
84112
84112
|
const result = await svc.show(id);
|
|
@@ -84126,7 +84126,12 @@ function registerFeatureCommand(program2, context4) {
|
|
|
84126
84126
|
context4.setExitCode(1);
|
|
84127
84127
|
}
|
|
84128
84128
|
});
|
|
84129
|
-
feature.command("update").summary("Update a feature status, scalar frontmatter field, or section body.").argument("<id>", "Feature ID").argument("[status]", "New lifecycle status").
|
|
84129
|
+
feature.command("update").summary("Update a feature status, scalar frontmatter field, or section body.").argument("<id>", "Feature ID").argument("[status]", "New lifecycle status").addHelpText("after", [
|
|
84130
|
+
"Section names are validated against the closed-world canonical set; a rejected",
|
|
84131
|
+
"`--section` costs a failed write. List valid names first:",
|
|
84132
|
+
"`spur task sections <wbs> list`."
|
|
84133
|
+
].join(`
|
|
84134
|
+
`)).option("--field <key>", "Frontmatter field to set (e.g. priority)").option("--value <value>", "New value for --field").option("--section <name>", "Section name to replace").option("--from-file <path>", "File to read section body from (requires --section)").option("--folder <path>", "Custom features folder").option("--json", "Output machine-readable JSON").action(async (id, status, options) => {
|
|
84130
84135
|
const svc = await makeService(context4, options.folder);
|
|
84131
84136
|
try {
|
|
84132
84137
|
let result;
|
|
@@ -84462,7 +84467,7 @@ import { createRequire } from "module";
|
|
|
84462
84467
|
var CLI_CONFIG = {
|
|
84463
84468
|
binaryName: "spur",
|
|
84464
84469
|
binaryLabel: "spur",
|
|
84465
|
-
binaryVersion: "0.3.
|
|
84470
|
+
binaryVersion: "0.3.47",
|
|
84466
84471
|
configDir: ".spur",
|
|
84467
84472
|
configFile: ".spur/config.yaml",
|
|
84468
84473
|
databaseFile: ".spur/spur.db"
|
|
@@ -96308,7 +96313,7 @@ function registerTaskCommand(program2, context4) {
|
|
|
96308
96313
|
}
|
|
96309
96314
|
}
|
|
96310
96315
|
});
|
|
96311
|
-
task.command("show").summary("Show a task by WBS.").argument("<wbs>", "Task WBS number").option("--folder <path>", "Custom tasks folder").option("--json", "Output machine-readable JSON").action(async (wbs, options) => {
|
|
96316
|
+
task.command("show").alias("get").summary("Show a task by WBS.").argument("<wbs>", "Task WBS number").option("--folder <path>", "Custom tasks folder").option("--json", "Output machine-readable JSON").action(async (wbs, options) => {
|
|
96312
96317
|
const svc = await makeService2(context4, options.folder);
|
|
96313
96318
|
try {
|
|
96314
96319
|
const result = await svc.show(wbs);
|
|
@@ -96332,7 +96337,8 @@ ${result.content}`);
|
|
|
96332
96337
|
"A GuardDeniedError on `testing \u2192 done` means no pipeline run is recorded for the",
|
|
96333
96338
|
"task: run `/sp:dev-verify <wbs> --next` to PASS it, or record the audited bypass with",
|
|
96334
96339
|
'`SPUR_PROVENANCE_OVERRIDE=1 spur task update <wbs> done --force-done --reason "\u2026"`.',
|
|
96335
|
-
"See the gate checklist (spur-dev/references/gate-checklists.md)."
|
|
96340
|
+
"See the gate checklist (spur-dev/references/gate-checklists.md).",
|
|
96341
|
+
"Valid section names (no failed write): `spur task sections <wbs> list`."
|
|
96336
96342
|
].join(`
|
|
96337
96343
|
`)).option("--section <name>", "Section name to replace").option("--from-file <path>", "File to read section body from (requires --section)").option("--feature <id>", "Set the feature_id frontmatter field (traceability edge)").option("--priority <p>", "Set the priority frontmatter field (P0\u2013P3)").option("--ac-numbering <mode>", "Set the ac_numbering frontmatter field (task-local) \u2014 opts the task into the Requirements\u2194AC coverage check").option("--no-lifecycle", "Suppress lifecycle workflow run creation (use during pipeline runs to avoid orphaned lifecycle runs)").option("--force-done", "Allow transitioning to `done` even when the verify verdict is not PASS; records an override (task 0292). Waives the verdict only \u2014 the FSM path still applies, so from an earlier status walk the hops first: `todo` \u2192 `wip` \u2192 `testing` \u2192 `done` (each hop runs the structural `spur task check`)").option("--reason <text>", "Rationale for a forced-done override (paired with --force-done; persisted as done_reason)").option("--verdict-dir <path>", "Directory holding <wbs>-verdict.json artifacts (default: .spur/run)").option("--folder <path>", "Custom tasks folder").option("--json", "Output machine-readable JSON").action(async (wbs, status, options) => {
|
|
96338
96344
|
const svc = await makeService2(context4, options.folder, options.lifecycle === false);
|