scrumrun 3.1.2 → 4.1.0
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/CHANGELOG.md +29 -0
- package/CORE.md +13 -5
- package/README.md +73 -16
- package/SPEC.md +38 -3
- package/bin/scrumrun.js +102 -13
- package/docs/COMMANDS.md +17 -7
- package/docs/ENTITY-MODEL.md +17 -14
- package/docs/ERROR-CODES.md +4 -0
- package/docs/QUICKSTART.md +21 -19
- package/docs/SCHEMA.md +14 -10
- package/docs/SEMANTIC-MEMORY.md +28 -0
- package/lib/actions/index.js +81 -0
- package/lib/commands/manifest.js +4 -3
- package/lib/commands/render.js +3 -2
- package/lib/errors.js +4 -0
- package/lib/git/context.js +30 -0
- package/lib/guardrails/changeset.js +45 -0
- package/lib/guardrails/evaluate.js +175 -0
- package/lib/memory/compaction.js +289 -0
- package/lib/memory/index.js +62 -2
- package/lib/migrate/ops.js +92 -0
- package/lib/migrate/run.js +108 -0
- package/lib/runtime/briefing.js +29 -0
- package/lib/runtime/context.js +3 -1
- package/lib/runtime/orchestrator.js +4 -4
- package/lib/runtime/policy-engine.js +11 -0
- package/lib/runtime/policy-integrity.js +83 -0
- package/lib/runtime/watcher.js +185 -0
- package/lib/v2/artifacts.js +9 -0
- package/lib/v2/conformance.js +45 -18
- package/lib/v2/paths.js +2 -1
- package/lib/v2/runs-jsonl.js +134 -0
- package/lib/v2/schema.js +7 -7
- package/lib/v2/task-schema.js +133 -0
- package/package.json +2 -2
- package/scripts/generate-contract-docs.js +6 -2
- package/templates/project/.scrumrun/config.md +9 -0
- package/templates/project/.scrumrun/method.json +3 -0
- package/templates/project/AGENTS.md +10 -4
- package/templates/project-lean/AGENTS.md +6 -2
- package/templates/shared/hooks/pre-commit +16 -0
- package/templates/shared/skills/scrumrun/SKILL.md +11 -7
- package/templates/shared/view.html +281 -0
- package/types/index.d.ts +1 -1
package/docs/ENTITY-MODEL.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# ScrumRun
|
|
1
|
+
# ScrumRun 4.0 Markdown Model
|
|
2
2
|
|
|
3
3
|
This document explains the model. Exact ids, directories, initial states, transitions, structural cardinalities, truth ownership, and authority boundaries are generated from `lib/v2/schema.js` into [`SCHEMA.md`](SCHEMA.md). Do not maintain another hand-written schema table here.
|
|
4
4
|
|
|
@@ -6,32 +6,35 @@ This document explains the model. Exact ids, directories, initial states, transi
|
|
|
6
6
|
|
|
7
7
|
| Entity | Question | Lifetime |
|
|
8
8
|
|---|---|---|
|
|
9
|
+
| Core | How must the agent work? | Stable, sealed policy |
|
|
10
|
+
| Guardrails | What may never be broken here? | Stable, sealed project policy |
|
|
11
|
+
| Knowledge | What do we know, and why? | Reviewed until stale/deprecated/invalidated |
|
|
12
|
+
| Backlog | What is provisioned but not started? | Tasks with `status: backlog` |
|
|
9
13
|
| Feature | Why does this initiative matter? | Long-lived |
|
|
10
|
-
|
|
|
11
|
-
|
|
|
12
|
-
| Run |
|
|
13
|
-
| Memory | What do we know, and why? | Reviewed until stale/deprecated/invalidated |
|
|
14
|
+
| Sprint | Which Tasks ship together? | Feature, fix, or maintenance batch |
|
|
15
|
+
| Task | What concrete outcome must be delivered? | Until outcome/closure |
|
|
16
|
+
| Run | What happened while executing a Task or Sprint? | Optional audit/handoff record |
|
|
14
17
|
|
|
15
18
|
```text
|
|
16
|
-
|
|
19
|
+
SPRINT-012 (type: fix)
|
|
17
20
|
└── TASK-018
|
|
18
|
-
├──
|
|
19
|
-
├──
|
|
20
|
-
├──
|
|
21
|
-
└──
|
|
21
|
+
├── feature → FEAT-003
|
|
22
|
+
├── depends_on → TASK-014, DEC-018
|
|
23
|
+
├── guardrails → GR-004
|
|
24
|
+
└── run → RUN-044 (optional)
|
|
22
25
|
```
|
|
23
26
|
|
|
24
|
-
Task is
|
|
27
|
+
Task is the executable unit. A Task may be independent of a Sprint. A Sprint groups Tasks and may be `feature`, `fix`, or `maintenance`. Runs are optional and never substitute for a Task's direct Markdown handoff.
|
|
25
28
|
|
|
26
29
|
## Canonical artifacts
|
|
27
30
|
|
|
28
|
-
All artifacts carry `id`, `kind`, `status`, `created`, `updated`, and `method`. Relations
|
|
31
|
+
All artifacts carry `id`, `kind`, `status`, `created`, `updated`, and `method`. Relations use simple frontmatter and relative links: `sprint: SPRINT-012`, `feature: FEAT-003`, `depends_on: [TASK-014, DEC-018]`, and `## Related`. Unknown fields and sections are preserved. The generated [`SCHEMA.md`](SCHEMA.md) is the authoritative inventory.
|
|
29
32
|
|
|
30
33
|
## Operational flow
|
|
31
34
|
|
|
32
|
-
Approval creates
|
|
35
|
+
Approval creates or refines a Task. An optional Run can record strict audit or handoff history, but no Run, ledger, permit, or status synchronization blocks normal Markdown work. An approved Task continues through discover → implement → verify → fix → verify until its `## Done when` contract is delivered.
|
|
33
36
|
|
|
34
|
-
Feature and Sprint provide context/grouping and do not own execution history. Review attaches evidence.
|
|
37
|
+
Feature and Sprint provide context/grouping and do not own execution history. Review attaches evidence. Knowledge, Decisions, and Insights explain constraints across all of them. Core and Guardrails are sealed Markdown policy: only explicit owner-reviewed maintenance may change them.
|
|
35
38
|
|
|
36
39
|
## Generated projections
|
|
37
40
|
|
package/docs/ERROR-CODES.md
CHANGED
|
@@ -77,6 +77,7 @@ SR-E-102 Run transition rejected: missing validation or learning evidence.
|
|
|
77
77
|
| `SR-E-150` | Guardrail check blocked the operation. | Read the reported GR-NNN, satisfy or retire it explicitly; guardrails never bypass silently. |
|
|
78
78
|
| `SR-E-151` | Guardrail obligation is still pending. | Resolve each pending guardrail via `scrumrun plan run --satisfy-guardrail` before completing the Run. |
|
|
79
79
|
| `SR-E-152` | Guardrail declaration is malformed. | Every active guardrail requires Status, Enforcement, Scope, and Rule fields; check `.scrumrun/guardrails.md`. |
|
|
80
|
+
| `SR-E-153` | Declarative Guardrail YAML is malformed. | Use the documented yaml enforcement block with match, on_violation, severity, and optional evidence. |
|
|
80
81
|
|
|
81
82
|
### Edit permits (Mutation Gateway)
|
|
82
83
|
|
|
@@ -121,6 +122,9 @@ SR-E-102 Run transition rejected: missing validation or learning evidence.
|
|
|
121
122
|
|---|---|---|
|
|
122
123
|
| `SR-E-450` | Conformance check failed. | The reported invariant identifies the exact violation; the message includes the file and expected shape. |
|
|
123
124
|
| `SR-E-451` | Installed client asset is stale. | Re-run `scrumrun update` for the specific client. `doctor --strict` shows which files diverge. |
|
|
125
|
+
| `SR-E-452` | Task Markdown is missing a required section. | Add the reported section (e.g. `## Request`, `## Done when`) to the Task file. The validator lives in `lib/v2/task-schema.js`. |
|
|
126
|
+
| `SR-E-453` | Completed Task has no `## Completion` or associated Run `## Technical Summary`. | Append a short `## Completion` bullet list to the Task, or complete the Run with `plan run --complete --summary "..."`. |
|
|
127
|
+
| `SR-E-454` | Task is running inside a git repository but no `git.branch` was captured. | Warning-only. Reconcile with `scrumrun repair --apply`, or add `git: { branch: <name>, base_sha: <sha> }` to the Task frontmatter. |
|
|
124
128
|
|
|
125
129
|
### Configuration and installation
|
|
126
130
|
|
package/docs/QUICKSTART.md
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
# Quickstart
|
|
2
2
|
|
|
3
|
-
Get from "never heard of it" to your first
|
|
3
|
+
Get from "never heard of it" to your first delivered Task in under 10 minutes.
|
|
4
4
|
No `SPEC.md` reading required.
|
|
5
5
|
|
|
6
6
|
## The three-minute mental model
|
|
7
7
|
|
|
8
8
|
ScrumRun gives an AI coding agent a small vocabulary and a project memory.
|
|
9
9
|
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
10
|
+
- **Core** — how the agent works; read first and never edited during product work.
|
|
11
|
+
- **Guardrails** — rules that cannot be broken.
|
|
12
|
+
- **Knowledge** — facts, decisions, insights, and dossiers about the project.
|
|
13
|
+
- **Task** — one concrete delivery, independent or inside a Sprint.
|
|
14
|
+
- **Sprint** — a feature, fix, or maintenance batch of Tasks. Optional.
|
|
15
|
+
- **Feature** — a bigger initiative that groups delivery context.
|
|
16
|
+
- **Run** — optional audit/handoff Markdown for a Task or Sprint.
|
|
16
17
|
|
|
17
18
|
Everything lives as Markdown under `.scrumrun/`. Any Markdown-capable agent
|
|
18
19
|
can follow it.
|
|
@@ -64,24 +65,25 @@ command (`| less`, `> intake.txt`) or set `NO_COLOR=1` to get the plain
|
|
|
64
65
|
Markdown summary instead; add `--json` for a fully structured payload
|
|
65
66
|
you can feed to CI or a downstream tool.
|
|
66
67
|
|
|
67
|
-
Only then does
|
|
68
|
+
Only then does the Task become approved. The agent executes it directly in code and Markdown; a Run is optional.
|
|
68
69
|
|
|
69
|
-
##
|
|
70
|
+
## Delivering the Task
|
|
70
71
|
|
|
71
|
-
The agent executes inside the approved scope
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
The agent executes inside the approved scope until `## Done when` is true.
|
|
73
|
+
It does not stop after an inventory, partial fix, or progress report. Any
|
|
74
|
+
missing implementation discovered in scope is work to do now, not a follow-up.
|
|
74
75
|
|
|
75
76
|
```
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
TASK-001
|
|
78
|
+
→ inspect the duplicate-submit path
|
|
79
|
+
→ implement the fix
|
|
80
|
+
→ run the relevant checks
|
|
81
|
+
→ correct failures
|
|
82
|
+
→ write ## Completion and status: completed
|
|
81
83
|
```
|
|
82
84
|
|
|
83
|
-
A
|
|
84
|
-
|
|
85
|
+
A Run may be added when a strict audit or detailed handoff is useful. It never
|
|
86
|
+
needs to exist for normal work to start or finish.
|
|
85
87
|
|
|
86
88
|
You can render a Run's ledger as a human timeline instead of reading the
|
|
87
89
|
raw JSON:
|
package/docs/SCHEMA.md
CHANGED
|
@@ -20,9 +20,9 @@ The boundaries above are deliberately different: SPEC owns meanings, the executa
|
|
|
20
20
|
| Kind | Stable ID | Canonical directory | Allowed initial status | Truth owned by this artifact |
|
|
21
21
|
|---|---|---|---|---|
|
|
22
22
|
| feature | `FEAT-NNN` | `features/` | `backlog`, `proposed` | initiative purpose, scope, dependencies, and lifecycle |
|
|
23
|
-
| task | `TASK-NNN` | `tasks/` | `backlog`, `proposed`, `running` | scope,
|
|
24
|
-
| sprint | `SPRINT-NNN` | `sprints/` | `proposed` | timebox or delivery-batch membership;
|
|
25
|
-
| run | `RUN-NNN` | `runs/` | `executing` |
|
|
23
|
+
| task | `TASK-NNN` | `tasks/` | `backlog`, `proposed`, `in_progress`, `running` | scope, owner-defined sections, links, and status |
|
|
24
|
+
| sprint | `SPRINT-NNN` | `sprints/` | `proposed` | timebox or delivery-batch membership; may be feature, fix, or maintenance |
|
|
25
|
+
| run | `RUN-NNN` | `runs/` | `executing` | optional human-readable execution record and outcome |
|
|
26
26
|
| review | `REV-NNN` | `reviews/` | `proposed` | scoped findings, checks, evidence, and verdict |
|
|
27
27
|
| knowledge | `K-NNN` | `memory/knowledge/` | `candidate` | approved evidence-backed fact and validity |
|
|
28
28
|
| decision | `DEC-NNN` | `memory/decisions/` | `open` | decision, rationale, validity, and lifecycle |
|
|
@@ -37,9 +37,9 @@ Every artifact also requires `id`, `kind`, `status`, `created`, `updated`, and `
|
|
|
37
37
|
|---|---|---|---|
|
|
38
38
|
| `feature` | feature (`FEAT-NNN`) | 0..1 | long-lived initiative containing the artifact |
|
|
39
39
|
| `sprint` | sprint (`SPRINT-NNN`) | 0..1 | optional delivery batch containing a Task or Run |
|
|
40
|
-
| `task` | task (`TASK-NNN`) |
|
|
40
|
+
| `task` | task (`TASK-NNN`) | 0..1 | atomic work executed or reviewed by the artifact |
|
|
41
41
|
|
|
42
|
-
Task is the atomic unit. A Task may have zero or one Sprint. A
|
|
42
|
+
Task is the atomic unit. A Task may have zero or one Sprint. A Run is optional Markdown history and may reference either a Task or a Sprint; it never controls Task status in Markdown-first work. Sprint membership is authoritative on `Task.sprint`; a Sprint's `## Tasks` list is a human-readable projection that must agree with it. Additional owner-defined relations such as `depends_on: [TASK-014, DEC-018]` are preserved as local Markdown graph data.
|
|
43
43
|
|
|
44
44
|
## Scalar constraints
|
|
45
45
|
|
|
@@ -58,14 +58,14 @@ Newly authored Runs use `ledger: 1`. Their `## Events` section contains append-o
|
|
|
58
58
|
|
|
59
59
|
Every event requires `schema`, `id`, contiguous `sequence`, RFC3339 `occurred_at`, `timestamp_precision`, `actor`, `from`, `to`, `reason`, and structured `evidence`. Event types are `transition`, `snapshot`, `guardrail`, `mutation`. Evidence kinds are `approval`, `command`, `test`, `file`, `review`, `decision`, `insight`, `risk`, `note`, `migration`, `legacy`, `guardrail`, `mutation`.
|
|
60
60
|
|
|
61
|
-
A native ledger begins with `created → executing`; an evidenced migration `snapshot` may establish one historical baseline without inventing missing transitions. Event order, transition legality, final status, updated date, and completion evidence are machine-validated. Run owns
|
|
61
|
+
A native ledger begins with `created → executing`; an evidenced migration `snapshot` may establish one historical baseline without inventing missing transitions. Event order, transition legality, final status, updated date, and completion evidence are machine-validated when a strict audit Run is used. Run owns optional event history; Task Markdown owns the delivered scope and status in normal work.
|
|
62
62
|
|
|
63
63
|
## Truth questions
|
|
64
64
|
|
|
65
65
|
- **feature:** Why does this initiative exist?
|
|
66
|
-
- **task:** What
|
|
67
|
-
- **sprint:**
|
|
68
|
-
- **run:**
|
|
66
|
+
- **task:** What concrete outcome is intended?
|
|
67
|
+
- **sprint:** Which Tasks are grouped for this delivery?
|
|
68
|
+
- **run:** What happened while executing a Task or Sprint?
|
|
69
69
|
- **review:** What independent validation was performed?
|
|
70
70
|
- **knowledge:** What verified project fact is reusable?
|
|
71
71
|
- **decision:** What normative choice constrains future work?
|
|
@@ -77,7 +77,7 @@ A native ledger begins with `created → executing`; an evidenced migration `sna
|
|
|
77
77
|
| Kind | Declared transitions |
|
|
78
78
|
|---|---|
|
|
79
79
|
| feature | `backlog` → `proposed`, `active`, `cancelled`<br>`proposed` → `active`, `cancelled`<br>`active` → `paused`, `completed`, `cancelled`<br>`completed` → terminal<br>`paused` → `active`, `cancelled`<br>`cancelled` → terminal |
|
|
80
|
-
| task | `backlog` → `proposed`, `running`, `cancelled`<br>`proposed` → `running`, `cancelled`<br>`running` → `validating`, `failed`, `blocked`, `cancelled`<br>`validating` → `learning`, `failed`, `blocked`<br>`learning` → `completed`, `failed`, `blocked`<br>`partial` → `running`, `cancelled`<br>`completed` → terminal<br>`failed` → `running`, `cancelled`<br>`blocked` → `running`, `cancelled`<br>`cancelled` → terminal |
|
|
80
|
+
| task | `backlog` → `proposed`, `in_progress`, `running`, `cancelled`<br>`proposed` → `in_progress`, `running`, `cancelled`<br>`in_progress` → `completed`, `failed`, `blocked`, `cancelled`<br>`running` → `validating`, `completed`, `failed`, `blocked`, `cancelled`<br>`validating` → `learning`, `completed`, `failed`, `blocked`<br>`learning` → `completed`, `failed`, `blocked`<br>`partial` → `in_progress`, `running`, `cancelled`<br>`completed` → terminal<br>`failed` → `in_progress`, `running`, `cancelled`<br>`blocked` → `in_progress`, `running`, `cancelled`<br>`cancelled` → terminal |
|
|
81
81
|
| sprint | `proposed` → `running`, `cancelled`<br>`running` → `partial`, `completed`, `blocked`, `cancelled`<br>`partial` → `running`, `completed`, `cancelled`<br>`completed` → terminal<br>`blocked` → `running`, `cancelled`<br>`cancelled` → terminal |
|
|
82
82
|
| run | `executing` → `validating`, `failed`, `blocked`<br>`validating` → `learning`, `failed`, `blocked`<br>`learning` → `completed`, `failed`, `blocked`<br>`partial` → `executing`, `failed`, `blocked`<br>`completed` → terminal<br>`failed` → terminal<br>`blocked` → `executing`, `failed` |
|
|
83
83
|
| review | `proposed` → `running`<br>`running` → `passed`, `failed`<br>`passed` → `archived`<br>`failed` → `running`, `archived`<br>`archived` → terminal |
|
|
@@ -86,6 +86,10 @@ A native ledger begins with `created → executing`; an evidenced migration `sna
|
|
|
86
86
|
| insight | `candidate` → `confirmed`, `invalidated`<br>`confirmed` → `stale`, `deprecated`, `invalidated`<br>`stale` → `confirmed`, `deprecated`, `invalidated`<br>`deprecated` → `confirmed`<br>`invalidated` → terminal |
|
|
87
87
|
| dossier | `active` → `stale`, `deprecated`, `archived`<br>`stale` → `active`, `deprecated`, `archived`<br>`deprecated` → `archived`<br>`archived` → terminal |
|
|
88
88
|
|
|
89
|
+
## Declarative Guardrail enforcement
|
|
90
|
+
|
|
91
|
+
Project Guardrails may include an optional fenced `yaml enforcement` block. Its restricted, dependency-free YAML schema is defined normatively in `SPEC.md §6.1`: `match.paths[]`, `match.diff[]`, `match.symbols[]`, `on_violation` (`block` or `warn`), `severity`, and optional `evidence`. The pure evaluator consumes only that normalized rule data and a supplied ChangeSet; it has no network or LLM dependency. Prose-only Guardrails remain valid.
|
|
92
|
+
|
|
89
93
|
## Projections
|
|
90
94
|
|
|
91
95
|
`state.md`, `map.md`, context packages, and `.cache/` are disposable. They may summarize or index canonical artifacts, but they cannot introduce status, policy, relations, decisions, or knowledge.
|
package/docs/SEMANTIC-MEMORY.md
CHANGED
|
@@ -66,3 +66,31 @@ scrumrun knowledge context --clear
|
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
SQLite is ignored and disposable. The derived index records its search backend: FTS5/BM25 is selected when the current Node.js SQLite build supports it; otherwise ScrumRun uses deterministic parameterized token matching over the same artifact, code, and relation tables. Queries default to 10 records/40 relations and hard-cap at 100/100. Match type, truth state, warnings, relation counts, and evidence are returned so recommendations remain explainable.
|
|
69
|
+
|
|
70
|
+
## Deterministic Dossier compaction
|
|
71
|
+
|
|
72
|
+
Compaction is an opt-in maintenance operation for a project with many related,
|
|
73
|
+
already reviewed memory records. It never calls an LLM and never runs in the
|
|
74
|
+
background:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
scrumrun knowledge dossier --compact --dry-run
|
|
78
|
+
scrumrun knowledge dossier --compact --apply --approve
|
|
79
|
+
scrumrun knowledge dossier --compact --rollback DOS-001
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Only approved Knowledge and confirmed Insights without `superseded_by` are
|
|
83
|
+
eligible. Each record is represented by its subject, declared relations
|
|
84
|
+
(including `used_by`), and explicit `#tags` or `## Tags`/`## Topics` bullets.
|
|
85
|
+
Two records are connected when the Jaccard overlap of those features is at
|
|
86
|
+
least `memory.compaction.threshold` (default `0.6`). Connected components with
|
|
87
|
+
at least `memory.compaction.min_members` records (default `3`) become proposed
|
|
88
|
+
clusters. IDs, feature sorting, links, and labels are all deterministic.
|
|
89
|
+
|
|
90
|
+
Dry-run is read-only and reports member IDs, pair evidence, score and effective
|
|
91
|
+
configuration. Apply additionally requires the explicit `--approve` flag: it
|
|
92
|
+
creates a Dossier with original artifact IDs as evidence and adds only
|
|
93
|
+
`superseded_by: DOS-NNN` to the source records. Sources are never deleted. The
|
|
94
|
+
Dossier holds a reversible snapshot; rollback restores byte-exact sources only
|
|
95
|
+
when they have not changed since compaction, then archives the Dossier. This
|
|
96
|
+
refusal protects later owner edits rather than overwriting them.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { addPlanArtifact, amendPlanArtifact } = require("../runtime/orchestrator");
|
|
4
|
+
const { createMemory } = require("../memory/service");
|
|
5
|
+
|
|
6
|
+
const REGISTRY = Object.freeze({
|
|
7
|
+
"plan.task.add": {
|
|
8
|
+
describe: "Create a Task (optionally as backlog or fix).",
|
|
9
|
+
handler: (projectRoot, payload) => {
|
|
10
|
+
requireString(payload, "title");
|
|
11
|
+
return addPlanArtifact(projectRoot, "task", payload.title, {
|
|
12
|
+
type: payload.type || null,
|
|
13
|
+
status: payload.status || null
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"plan.feature.add": {
|
|
18
|
+
describe: "Create a Feature.",
|
|
19
|
+
handler: (projectRoot, payload) => {
|
|
20
|
+
requireString(payload, "title");
|
|
21
|
+
return addPlanArtifact(projectRoot, "feature", payload.title, {});
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"plan.sprint.add": {
|
|
25
|
+
describe: "Create a Sprint.",
|
|
26
|
+
handler: (projectRoot, payload) => {
|
|
27
|
+
requireString(payload, "title");
|
|
28
|
+
return addPlanArtifact(projectRoot, "sprint", payload.title, {});
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"plan.task.amend": {
|
|
32
|
+
describe: "Amend an existing Task's title/sections/type/feature/sprint.",
|
|
33
|
+
handler: (projectRoot, payload) => {
|
|
34
|
+
requireString(payload, "id");
|
|
35
|
+
return amendPlanArtifact(projectRoot, "task", payload.id, payload);
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"knowledge.fact.add": {
|
|
39
|
+
describe: "Create a Knowledge (K-NNN) candidate.",
|
|
40
|
+
handler: (projectRoot, payload) => {
|
|
41
|
+
requireString(payload, "title");
|
|
42
|
+
return createMemory(projectRoot, "knowledge", payload);
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"knowledge.decision.add": {
|
|
46
|
+
describe: "Create a Decision (DEC-NNN) candidate.",
|
|
47
|
+
handler: (projectRoot, payload) => {
|
|
48
|
+
requireString(payload, "title");
|
|
49
|
+
return createMemory(projectRoot, "decision", payload);
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"knowledge.insight.propose": {
|
|
53
|
+
describe: "Propose an Insight (INS-NNN) candidate.",
|
|
54
|
+
handler: (projectRoot, payload) => {
|
|
55
|
+
requireString(payload, "title");
|
|
56
|
+
return createMemory(projectRoot, "insight", payload);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
function requireString(payload, field) {
|
|
62
|
+
if (!payload || typeof payload[field] !== "string" || !payload[field].trim()) {
|
|
63
|
+
throw new Error(`payload.${field} must be a non-empty string`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function listActions() {
|
|
68
|
+
return Object.entries(REGISTRY).map(([name, { describe }]) => ({ name, describe }));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function executeAction(name, projectRoot, payload) {
|
|
72
|
+
const entry = REGISTRY[name];
|
|
73
|
+
if (!entry) {
|
|
74
|
+
const known = Object.keys(REGISTRY).sort().join(", ");
|
|
75
|
+
throw new Error(`Unknown action "${name}". Known actions: ${known}`);
|
|
76
|
+
}
|
|
77
|
+
const normalizedPayload = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
78
|
+
return entry.handler(projectRoot, normalizedPayload);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = { REGISTRY, executeAction, listActions };
|
package/lib/commands/manifest.js
CHANGED
|
@@ -36,7 +36,7 @@ const nouns = Object.freeze({
|
|
|
36
36
|
fact: ["--add [--title] [--content] [--evidence] [--relation] [--subject] [--source] [--source-id] [--valid-from] [--valid-until] [--review-trigger]", "--list [--include-inactive]", "--show", "--approve [--evidence] [--note]", "--reject [--note]", "--deprecate [--note]", "--invalidate [--note]"],
|
|
37
37
|
decision: ["--add [--title] [--content] [--evidence] [--relation] [--subject] [--source] [--source-id] [--valid-from] [--valid-until] [--review-trigger]", "--list [--include-inactive]", "--show", "--resolve [--evidence] [--note]", "--deprecate [--note]", "--invalidate [--note]"],
|
|
38
38
|
insight: ["--propose [--title] [--content] [--evidence] [--relation] [--subject] [--source] [--source-id] [--confidence] [--valid-from] [--valid-until] [--review-trigger]", "--list [--include-inactive]", "--show", "--confirm [--evidence] [--note]", "--stale [--note]", "--reject [--note]", "--deprecate [--note]", "--invalidate [--note]"],
|
|
39
|
-
dossier: ["--add [--title] [--content] [--evidence] [--relation] [--subject] [--source] [--source-id] [--valid-from] [--valid-until] [--review-trigger]", "--list [--include-inactive]", "--show", "--refresh [--evidence] [--note]", "--stale [--note]", "--deprecate [--note]", "--archive [--note]"],
|
|
39
|
+
dossier: ["--add [--title] [--content] [--evidence] [--relation] [--subject] [--source] [--source-id] [--valid-from] [--valid-until] [--review-trigger]", "--list [--include-inactive]", "--show", "--refresh [--evidence] [--note]", "--stale [--note]", "--deprecate [--note]", "--archive [--note]", "--compact --dry-run", "--compact --apply --approve", "--compact --rollback <DOS-NNN>"],
|
|
40
40
|
context: ["--build", "--update", "--show", "--clear"],
|
|
41
41
|
map: ["--build", "--show"],
|
|
42
42
|
errors: ["--show"],
|
|
@@ -55,7 +55,7 @@ const nouns = Object.freeze({
|
|
|
55
55
|
description: "run scoped evidence-based quality gates",
|
|
56
56
|
subjects: {
|
|
57
57
|
code: ["--run"],
|
|
58
|
-
artifact: ["--run", "--record --task <TASK-NNN> [--run <RUN-NNN>] [--title] [--evidence]"],
|
|
58
|
+
artifact: ["--run [--staged]", "--record --task <TASK-NNN> [--run <RUN-NNN>] [--title] [--evidence]"],
|
|
59
59
|
migration: ["--run"],
|
|
60
60
|
release: ["--run"]
|
|
61
61
|
}
|
|
@@ -65,9 +65,10 @@ const nouns = Object.freeze({
|
|
|
65
65
|
subjects: {
|
|
66
66
|
project: ["--show", "--language", "--interaction", "--approval", "--quick-tasks"],
|
|
67
67
|
init: ["--local", "--shared", "--lean", "--no-agent-hint", "--force"],
|
|
68
|
-
update: ["all [--migrate]", "codex [--migrate]", "opencode [--migrate]", "claude [--migrate]"],
|
|
68
|
+
update: ["all [--project] [--seal-policy] [--migrate]", "codex [--project] [--seal-policy] [--migrate]", "opencode [--project] [--seal-policy] [--migrate]", "claude [--project] [--seal-policy] [--migrate]"],
|
|
69
69
|
migrate: ["--to 2 --dry-run", "--to 2 --apply", "--to 2 --rollback"],
|
|
70
70
|
doctor: ["all [--strict] [--recover] [--dry-run]", "codex [--strict] [--recover] [--dry-run]", "opencode [--strict] [--recover] [--dry-run]", "claude [--strict] [--recover] [--dry-run]"],
|
|
71
|
+
watch: ["--start", "--stop", "--status"],
|
|
71
72
|
uninstall: ["--force"],
|
|
72
73
|
help: ["<topic>"]
|
|
73
74
|
}
|
package/lib/commands/render.js
CHANGED
|
@@ -29,15 +29,16 @@ ${grammarLines().join("\n")}
|
|
|
29
29
|
|
|
30
30
|
- Natural-language product work begins with a read-only understanding pass; do not invoke \`plan intake\` merely to satisfy procedure.
|
|
31
31
|
- Intake, contextualization, policy, risk, classification, and planning are read-only until explicit approval.
|
|
32
|
-
- After approval, work directly in source files and relevant \`.scrumrun/\` Markdown.
|
|
32
|
+
- After approval, work directly in source files and relevant \`.scrumrun/\` Markdown until the approved Task is delivered. Keep executing discover → implement → verify → fix → verify; a progress report is allowed only when the owner asks for it and never ends the workflow. Do not answer with an inventory, partial progress report, decomposition, or remaining-work list; those are internal steps. A gap discovered in scope is work to implement now, not a “next step”. Feature/Sprint/Run are useful only when they add context.
|
|
33
33
|
- A Run is optional audit/handoff context, never a prerequisite for starting, amending, or completing a Task. Do not block on a missing Run, a legacy status, or stale generated state.
|
|
34
34
|
- The CLI can inspect/repair/report structured artifacts, but it does not own the daily workflow.
|
|
35
35
|
- \`guardrails.md\` is canonical project policy; \`golden-rules.md\` is v1 compatibility only.
|
|
36
36
|
- Evaluate active Guardrails as \`passed\`, \`blocked\`, or \`deferred\`; cite exact \`GR-NNN\` ids and keep deferred execution gates visible.
|
|
37
|
-
- Block only for an explicit Guardrail, secret/security risk, destructive action without approval, or an unmet required
|
|
37
|
+
- Block only for an explicit Guardrail, secret/security risk, destructive action without approval, or an unmet required delivery criterion. \`Follow-ups\` may only contain work outside the approved \`Done when\` contract; optional unrun E2E/review coverage is a follow-up/risk, not a failed Run.
|
|
38
38
|
- Use the CLI only for \`init\`, \`update --project\`, \`migrate\`, \`repair\`, \`doctor\`, reports, or release checks. Do not invoke \`npx scrumrun@latest\` during execution.
|
|
39
39
|
- Never invoke \`plan run --fail|--block|--retry|--finalize|--complete|--validate\` or \`plan task --start\` during normal work. These optional strict-audit commands must not decide a Task outcome.
|
|
40
40
|
- Strict per-path Mutation Gateway permits and ledger finalization remain available only when the owner explicitly requests strict execution.
|
|
41
|
+
- Never edit \`core.md\` or \`guardrails.md\` during product work. They are sealed policy; owner-requested policy changes are reviewed and sealed at the maintenance edge.
|
|
41
42
|
- Knowledge/Decision/Insight records require evidence; AI-proposed Insights remain \`candidate\` until confirmed.
|
|
42
43
|
- Never print vault values or write before approval.
|
|
43
44
|
- Unknown nouns, subjects, actions, ids, or ambiguous approval must produce a deterministic explanation, never a guessed mutation.
|
package/lib/errors.js
CHANGED
|
@@ -40,6 +40,7 @@ const CATALOG = Object.freeze({
|
|
|
40
40
|
"SR-E-150": { summary: "Guardrail check blocked the operation.", remediation: "Read the reported GR-NNN, satisfy or retire it explicitly; guardrails never bypass silently." },
|
|
41
41
|
"SR-E-151": { summary: "Guardrail obligation is still pending.", remediation: "Resolve each `pending guardrail` via `scrumrun plan run --satisfy-guardrail` before completing the Run." },
|
|
42
42
|
"SR-E-152": { summary: "Guardrail declaration is malformed.", remediation: "Every active guardrail requires Status, Enforcement, Scope, and Rule fields; check .scrumrun/guardrails.md." },
|
|
43
|
+
"SR-E-153": { summary: "Declarative Guardrail YAML is malformed.", remediation: "Use the documented yaml enforcement block with match, on_violation, severity, and optional evidence." },
|
|
43
44
|
|
|
44
45
|
// Edit permits (Mutation Gateway)
|
|
45
46
|
"SR-E-200": { summary: "No edit permit for this path.", remediation: "Request one with `scrumrun plan run --authorize-mutation RUN-NNN --path <path>` before editing canonical or source files." },
|
|
@@ -66,6 +67,9 @@ const CATALOG = Object.freeze({
|
|
|
66
67
|
// Conformance / doctor
|
|
67
68
|
"SR-E-450": { summary: "Conformance check failed.", remediation: "The reported invariant identifies the exact violation; the message includes the file and expected shape." },
|
|
68
69
|
"SR-E-451": { summary: "Installed client asset is stale.", remediation: "Re-run `scrumrun update` for the specific client. `doctor --strict` shows which files diverge." },
|
|
70
|
+
"SR-E-452": { summary: "Task Markdown is missing a required section.", remediation: "Add the reported section (e.g. `## Request`, `## Done when`) to the Task file. The validator lives in `lib/v2/task-schema.js`." },
|
|
71
|
+
"SR-E-453": { summary: "Completed Task has no `## Completion` or associated Run `## Technical Summary`.", remediation: "Append a short `## Completion` bullet list to the Task, or complete the Run with `plan run --complete --summary \"...\"`." },
|
|
72
|
+
"SR-E-454": { summary: "Task is running inside a git repository but no `git.branch` was captured.", remediation: "Warning-only. Reconcile with `scrumrun repair --apply`, or add `git: { branch: <name>, base_sha: <sha> }` to the Task frontmatter." },
|
|
69
73
|
|
|
70
74
|
// Configuration / install
|
|
71
75
|
"SR-E-500": { summary: "ScrumRun project not initialized.", remediation: "Run `scrumrun init` in the repository root." },
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { execFileSync } = require("node:child_process");
|
|
4
|
+
|
|
5
|
+
function git(projectRoot, args) {
|
|
6
|
+
try {
|
|
7
|
+
const output = execFileSync("git", args, {
|
|
8
|
+
cwd: projectRoot,
|
|
9
|
+
encoding: "utf8",
|
|
10
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
11
|
+
});
|
|
12
|
+
return output.replace(/\r?\n$/, "");
|
|
13
|
+
} catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function readGitContext(projectRoot) {
|
|
19
|
+
const inside = git(projectRoot, ["rev-parse", "--is-inside-work-tree"]);
|
|
20
|
+
if (inside !== "true") return { isRepo: false, branch: null, headSha: null };
|
|
21
|
+
const branch = git(projectRoot, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
22
|
+
const headSha = git(projectRoot, ["rev-parse", "HEAD"]);
|
|
23
|
+
return {
|
|
24
|
+
isRepo: true,
|
|
25
|
+
branch: branch === "HEAD" ? null : branch,
|
|
26
|
+
headSha: headSha || null
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = { readGitContext };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { execFileSync } = require("node:child_process");
|
|
4
|
+
const fs = require("node:fs");
|
|
5
|
+
const path = require("node:path");
|
|
6
|
+
|
|
7
|
+
function git(projectRoot, args) {
|
|
8
|
+
try { return execFileSync("git", args, { cwd: projectRoot, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }); }
|
|
9
|
+
catch { return null; }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function existsAtHead(projectRoot, relative) {
|
|
13
|
+
try {
|
|
14
|
+
execFileSync("git", ["cat-file", "-e", `HEAD:${relative}`], { cwd: projectRoot, stdio: "ignore" });
|
|
15
|
+
return true;
|
|
16
|
+
} catch { return false; }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function changedPaths(projectRoot, staged) {
|
|
20
|
+
const names = git(projectRoot, staged ? ["diff", "--cached", "--name-only", "--diff-filter=ACMR"] : ["diff", "HEAD", "--name-only", "--diff-filter=ACMR"]);
|
|
21
|
+
const paths = names === null ? [] : names.split(/\r?\n/).filter(Boolean);
|
|
22
|
+
if (staged) return paths;
|
|
23
|
+
const status = git(projectRoot, ["status", "--porcelain"]);
|
|
24
|
+
if (status !== null) {
|
|
25
|
+
for (const line of status.split(/\r?\n/)) {
|
|
26
|
+
const candidate = line.slice(3).trim();
|
|
27
|
+
if (candidate && !candidate.includes(" -> ")) paths.push(candidate);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return [...new Set(paths)];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function collectChangeSet(projectRoot, { staged = false } = {}) {
|
|
34
|
+
const diff = git(projectRoot, staged ? ["diff", "--cached", "--no-ext-diff", "--unified=0"] : ["diff", "HEAD", "--no-ext-diff", "--unified=0"]);
|
|
35
|
+
const paths = changedPaths(projectRoot, staged);
|
|
36
|
+
const untrackedContent = staged ? "" : paths
|
|
37
|
+
.filter((relative) => !existsAtHead(projectRoot, relative))
|
|
38
|
+
.map((relative) => {
|
|
39
|
+
const file = path.join(projectRoot, relative);
|
|
40
|
+
try { return fs.statSync(file).isFile() ? `\n--- ${relative}\n${fs.readFileSync(file, "utf8")}` : ""; } catch { return ""; }
|
|
41
|
+
}).join("");
|
|
42
|
+
return { paths, diff: `${diff || ""}${untrackedContent}`, symbols: [], source: staged ? "git-index" : "git-worktree" };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = { collectChangeSet };
|