scrumrun 2.7.10 → 3.0.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 +10 -0
- package/CORE.md +1 -1
- package/README.md +28 -23
- package/SPEC.md +2 -2
- package/bin/scrumrun.js +10 -1
- package/docs/COMMANDS.md +5 -4
- package/lib/commands/manifest.js +1 -0
- package/lib/commands/render.js +2 -1
- package/lib/commands/repair.js +45 -0
- package/lib/runtime/mutation-gateway.js +74 -0
- package/lib/runtime/orchestrator.js +93 -2
- package/package.json +1 -1
- package/templates/project/AGENTS.md +4 -2
- package/templates/shared/skills/scrumrun/SKILL.md +6 -8
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,16 @@ All notable changes follow Semantic Versioning.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 3.0.0 - 2026-08-31
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- **Lightweight execution is now the default.** After approval, agents work directly in application files and the linked Task Markdown, then close the Run with one `scrumrun sc plan run --finalize RUN-NNN` checkpoint.
|
|
12
|
+
- **Finalization is fail-closed.** It audits the complete workspace delta, policy fingerprint, protected paths, symlink safety, scannability, secret boundary, Run ledger, and every Guardrail before completing the Task.
|
|
13
|
+
- **Guardrail proof is co-located with work.** Non-automatic rules use a compact `## Guardrail Evidence` section in the Task and are validated together at finalization; missing evidence blocks completion.
|
|
14
|
+
- **No network command loop.** Generated agent instructions prohibit repeated `npx scrumrun@latest` calls during execution. `npx` remains for one-off installation, migration, and recovery.
|
|
15
|
+
- **Strict mode remains available.** Path-scoped Mutation Gateway permits and individual Run transitions are retained for owners who explicitly request per-edit control.
|
|
16
|
+
|
|
7
17
|
## 2.7.7 - 2026-08-24
|
|
8
18
|
|
|
9
19
|
### Added
|
package/CORE.md
CHANGED
|
@@ -84,7 +84,7 @@ AGENTS.md
|
|
|
84
84
|
|
|
85
85
|
**Before querying project state, read `.scrumrun/method.json`.** Its `paths` block is the authoritative index of every canonical location in this project. Navigate by that index; if a path is not declared there, it is not canonical truth. Directory listing and grep are fallbacks — never the first step. A ScrumRun-aware agent must never search for `goals/`, `backlog.md`, `sprint.md`, or any legacy layout: those are absent by design once migration completes and are surfaced only through `.scrumrun/.migration-backup/`.
|
|
86
86
|
|
|
87
|
-
**Never write Run events by hand.**
|
|
87
|
+
**Never write Run events by hand.** After approval, work directly in code and in the linked Task Markdown. Finish the normal session once with `scrumrun sc plan run --finalize RUN-NNN`; the CLI then audits the complete workspace delta, verifies Guardrails and evidence, and writes the validated Run event chain. Do not use `npx scrumrun@latest` during execution. The older `--validate | --learn | --complete | --satisfy-guardrail | --authorize-mutation | --record-mutation` operations remain available only for an owner-requested strict path. Existing hand-written Runs can be recovered with `sc plan run --normalize-legacy` (byte-exact original preserved in `.scrumrun/.migration-backup/runs/`).
|
|
88
88
|
|
|
89
89
|
Canonical truth is Markdown. SQLite/cache data stores only rebuildable indexes, symbol projections, relations, and bounded context packages. Deleting `.cache/` must never delete authored truth.
|
|
90
90
|
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
ScrumRun gives an agent a small command surface and a precise project memory: what should be done, how each attempt happened, which decisions constrain the code, and why the architecture exists in its current form.
|
|
6
6
|
|
|
7
|
-
**Package:** `
|
|
7
|
+
**Package:** `3.0.0` · **Method target:** `2.0.0` · **Runtime:** Node.js `>=22.13.0` · **License:** MIT
|
|
8
8
|
|
|
9
9
|
**New here?** Read the [Quickstart](docs/QUICKSTART.md) — first Run in under 10 minutes, no `SPEC.md` reading required. Full docs map in [`docs/INDEX.md`](docs/INDEX.md).
|
|
10
10
|
|
|
@@ -48,7 +48,7 @@ npm i -g scrumrun@latest
|
|
|
48
48
|
scrumrun update
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
`
|
|
51
|
+
Use the installed `scrumrun` command for project work. `npx` is appropriate for one-off installation, migration, or recovery only; agents must never invoke `npx scrumrun@latest` repeatedly while executing a Task, because that adds network dependency and latency to the work loop.
|
|
52
52
|
|
|
53
53
|
`install` adds the client integration; `init` creates the project tree. Initialization is local by default: `.scrumrun/` and the generated agent hint are added to `.git/info/exclude`. Use `--shared` when the team wants to commit the project memory.
|
|
54
54
|
|
|
@@ -85,7 +85,13 @@ Explicit approval atomically materializes the required plan artifacts and always
|
|
|
85
85
|
EXECUTING → VALIDATING → LEARNING → COMPLETED | FAILED | BLOCKED
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
Every approved Task carries an `## Acceptance Criteria` section so "done" is defined before work begins
|
|
88
|
+
Every approved Task carries an `## Acceptance Criteria` section so "done" is defined before work begins. After approval, the agent works directly in code and the Task Markdown. It adds `## Technical Summary` and, when a non-automatic rule needs proof, a compact `## Guardrail Evidence` section. One final checkpoint closes the work:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
scrumrun sc plan run --finalize RUN-001
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The checkpoint validates the complete delta, policy, protected paths, secret boundary, acceptance evidence, and every Guardrail before writing the Run ledger and synchronizing the Task. Failed retries remain available as separate Runs.
|
|
89
95
|
|
|
90
96
|
Each Run contains a machine-validated event ledger. Events have stable ids such as `RUN-044-EVT-003`, RFC3339 timestamps, actors, reasons, and typed evidence for commands, tests, files, reviews, decisions, insights, and risks. Run is the only operational history; Task keeps its approved scope and synchronized current status without duplicating those events. Completion is rejected when validation or learning evidence is missing.
|
|
91
97
|
|
|
@@ -211,7 +217,7 @@ npx scrumrun@latest doctor codex --strict
|
|
|
211
217
|
|
|
212
218
|
`guardrails.md` is canonical project policy. Active `GR-NNN` rules are evaluated into explicit `passed`, `blocked`, or `deferred` results; blocks identify the exact Guardrail and deferred checks stay visible for their execution-time gate. `config.md` contains preferences and cannot weaken policy. Duplicate/unknown Guardrails, disabled approval, and unsafe read-only paths fail conformance. `state.md`, `map.md`, context packages, and SQLite are generated navigation aids, never authority. `state.md` carries the same source fingerprint used by intake, an RFC3339 generation time, and a watch fingerprint for fast verified staleness checks.
|
|
213
219
|
|
|
214
|
-
Approved Runs persist every deferred result as an append-only obligation and bind the exact policy plus workspace baseline.
|
|
220
|
+
Approved Runs persist every deferred result as an append-only obligation and bind the exact policy plus workspace baseline. The normal final checkpoint verifies all workspace changes at once: owner/read-only scope, symlinks, new secret-like content, policy drift, and required Guardrail evidence. Completion fails closed when any verification is missing or fails. The 15-minute path-scoped Mutation Gateway remains available for teams that explicitly choose strict per-edit control.
|
|
215
221
|
|
|
216
222
|
### Owner-controlled allowlist for descriptive files
|
|
217
223
|
|
|
@@ -243,43 +249,42 @@ Rules:
|
|
|
243
249
|
|
|
244
250
|
```bash
|
|
245
251
|
# inspect the grammar
|
|
246
|
-
|
|
252
|
+
scrumrun commands
|
|
247
253
|
|
|
248
254
|
# plan without writes, then approve the emitted token
|
|
249
|
-
|
|
250
|
-
|
|
255
|
+
scrumrun sc plan intake "Fix pricing rounding" --type fix --preview "Rounding moved after tax calc"
|
|
256
|
+
scrumrun sc plan intake --approve <token>
|
|
251
257
|
|
|
252
258
|
# record what was done at completion, so the next agent inherits it
|
|
253
|
-
|
|
259
|
+
scrumrun sc plan run --finalize RUN-001 --summary "Moved rounding after tax calc in checkout/pricing.ts"
|
|
254
260
|
|
|
255
261
|
# auto-sequencing: surface and start the next backlog Task
|
|
256
|
-
|
|
257
|
-
|
|
262
|
+
scrumrun sc plan task --next
|
|
263
|
+
scrumrun sc plan task --start TASK-009
|
|
258
264
|
|
|
259
265
|
# authorize and record a material source mutation
|
|
260
|
-
|
|
261
|
-
|
|
266
|
+
scrumrun sc plan run --authorize-mutation RUN-001 --path src/pricing.ts # strict mode only
|
|
267
|
+
scrumrun sc plan run --record-mutation RUN-001 --permit MUT-... --note "Pricing change recorded"
|
|
262
268
|
|
|
263
269
|
# record an audit-derived Review, then resolve a persisted completion gate
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
npx scrumrun@latest sc plan run --satisfy-guardrail RUN-001 --guardrail GR-003 --review REV-001
|
|
270
|
+
scrumrun sc review artifact --run
|
|
271
|
+
scrumrun sc review artifact --record --task TASK-001 --run RUN-001 --evidence "npm test: passed"
|
|
267
272
|
|
|
268
273
|
# memory lifecycle
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
274
|
+
scrumrun sc knowledge insight --propose "Pricing stays in backend" --evidence src/pricing.ts
|
|
275
|
+
scrumrun sc knowledge insight --confirm INS-001
|
|
276
|
+
scrumrun sc knowledge study calculateFinalPrice
|
|
272
277
|
|
|
273
278
|
# rebuild or inspect the derived graph
|
|
274
|
-
|
|
275
|
-
|
|
279
|
+
scrumrun sc knowledge map --build
|
|
280
|
+
scrumrun sc knowledge map --show
|
|
276
281
|
|
|
277
282
|
# read a Run as a human timeline instead of raw ledger JSON
|
|
278
|
-
|
|
283
|
+
scrumrun sc plan run --render RUN-001
|
|
279
284
|
|
|
280
285
|
# aggregate every Run in the project (p50/p95 durations, retries, guardrail counts)
|
|
281
|
-
|
|
282
|
-
|
|
286
|
+
scrumrun sc plan run --stats
|
|
287
|
+
scrumrun sc plan run --stats --task TASK-001 --json
|
|
283
288
|
|
|
284
289
|
# preview a canonical-transaction recovery before touching disk
|
|
285
290
|
npx scrumrun@latest sc config doctor --recover --dry-run
|
package/SPEC.md
CHANGED
|
@@ -202,11 +202,11 @@ Everything through `AWAITING_APPROVAL` is read-only. It may exist in process mem
|
|
|
202
202
|
|
|
203
203
|
Approval atomically creates one Task and its first Run. If either write fails, neither may remain. Project changes after planning invalidate the token. Reusing a successfully consumed token is idempotent.
|
|
204
204
|
|
|
205
|
-
The approved Run binds the exact Guardrail-policy fingerprint and a canonical workspace baseline. Every post-approval `deferred` check becomes an append-only Guardrail obligation.
|
|
205
|
+
The approved Run binds the exact Guardrail-policy fingerprint and a canonical workspace baseline. Every post-approval `deferred` check becomes an append-only Guardrail obligation. In the normal session path, agents work directly and one final checkpoint verifies the complete baseline-to-final delta: policy freshness, read-only boundaries, symlink safety, scannability, newly introduced secret-like content, and evidence for every Guardrail. Missing or unverifiable evidence fails closed. Strict teams may opt into short-lived, path-scoped permits and per-edit recording; those commands add stronger intermediate control but are not required for normal execution.
|
|
206
206
|
|
|
207
207
|
The agent may assert the classification explicitly (`--type fix|task|feature|docs|discovery`), overriding keyword inference with validation and a stable reason. It may attach a short technical preview (`--preview`), rendered in the terminal, bound into the approval token, and stored as `## Preview` on the approved Task. A Task declares its `## Acceptance Criteria` before execution; completion is measured against them, never against elapsed time or token budget.
|
|
208
208
|
|
|
209
|
-
Run transitions synchronously update the linked Task and append exactly one structured event to the Run ledger.
|
|
209
|
+
Run transitions synchronously update the linked Task and append exactly one structured event to the Run ledger. The normal `--finalize` checkpoint writes the required validating, learning, and completed transitions together after its audit; strict mode may write them individually. Task status changes without receiving a duplicate narrative history. Completion requires a `## Technical Summary` and evidenced validation/learning. Multi-file mutations use a durable local transaction journal: `prepared` operations roll back byte-exactly after failure/interruption, while `committed` journals are verified and finalized. Ordinary audit is read-only and reports pending recovery; `doctor --recover` or retrying the approved mutation performs recovery explicitly. Entering `learning` may extract structured candidates from the Run, but extraction failure never blocks Run progress.
|
|
210
210
|
|
|
211
211
|
Backlog is a queue view of Tasks with `status: backlog`, ordered oldest-first by id. Starting a backlog Task (`--next` to surface, `--start` to promote) transitions `backlog → running`, creates the Task's first Run, re-evaluates policy, and records the agent identity. Starting is itself the explicit approval required by I-01.
|
|
212
212
|
|
package/bin/scrumrun.js
CHANGED
|
@@ -31,7 +31,7 @@ const { ARTIFACT_TYPES, ArtifactRepository } = require(path.join(root, "lib", "v
|
|
|
31
31
|
const { aliases: COMMAND_ALIASES, resolveAlias, resolveRoute } = require(path.join(root, "lib", "commands", "manifest"));
|
|
32
32
|
const { renderCommandHelp, renderCompatibilityPrompt, renderRootPrompt } = require(path.join(root, "lib", "commands", "render"));
|
|
33
33
|
const { planRequest } = require(path.join(root, "lib", "runtime", "request-engine"));
|
|
34
|
-
const { addPlanArtifact, approveRequest, nextBacklogTask, refreshErrors, refreshState, retryTask, startBacklogTask, transitionRun } = require(path.join(root, "lib", "runtime", "orchestrator"));
|
|
34
|
+
const { addPlanArtifact, approveRequest, finalizeRun, nextBacklogTask, refreshErrors, refreshState, retryTask, startBacklogTask, transitionRun } = require(path.join(root, "lib", "runtime", "orchestrator"));
|
|
35
35
|
const { authorizeMutation, recordMutation, satisfyGuardrail } = require(path.join(root, "lib", "runtime", "mutation-gateway"));
|
|
36
36
|
const { recordArtifactReview } = require(path.join(root, "lib", "runtime", "review-service"));
|
|
37
37
|
const { createMemory, listMemory, showMemory, transitionMemory } = require(path.join(root, "lib", "memory", "service"));
|
|
@@ -1562,6 +1562,15 @@ function executeRootRoute(route) {
|
|
|
1562
1562
|
return;
|
|
1563
1563
|
}
|
|
1564
1564
|
if (noun === "plan" && subject === "run") {
|
|
1565
|
+
if (routeArgs[0] === "--finalize") {
|
|
1566
|
+
const runId = routeArgs[1];
|
|
1567
|
+
if (!runId) throw new Error("--finalize requires RUN-NNN.");
|
|
1568
|
+
const result = finalizeRun(process.cwd(), runId, runTransitionOptions(routeArgs.slice(2)));
|
|
1569
|
+
console.log(`${result.run.id}: completed; ${result.task.id}: completed. Final session audit verified ${result.changes} change(s).`);
|
|
1570
|
+
if (result.resolved.length) console.log(`Guardrails verified: ${result.resolved.join(", ")}.`);
|
|
1571
|
+
if (result.learning && result.learning.created.length) console.log(`Learning candidates: ${result.learning.created.join(", ")}.`);
|
|
1572
|
+
return;
|
|
1573
|
+
}
|
|
1565
1574
|
if (routeArgs[0] === "--authorize-mutation") {
|
|
1566
1575
|
const paths = optionValues(routeArgs.slice(2), "--path");
|
|
1567
1576
|
const result = authorizeMutation(process.cwd(), routeArgs[1], paths);
|
package/docs/COMMANDS.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# ScrumRun
|
|
1
|
+
# ScrumRun 3.0 Command Reference
|
|
2
2
|
|
|
3
3
|
The canonical grammar is:
|
|
4
4
|
|
|
@@ -6,7 +6,7 @@ The canonical grammar is:
|
|
|
6
6
|
/sc <noun> <subject> <action> [args]
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
Use `/sc` inside a supported AI client. The equivalent CLI form is `
|
|
9
|
+
Use `/sc` inside a supported AI client. The equivalent CLI form is `scrumrun sc ...`. Use `npx scrumrun@latest` only for one-off installation or recovery, never inside an agent's normal execution loop.
|
|
10
10
|
|
|
11
11
|
## Plan
|
|
12
12
|
|
|
@@ -17,13 +17,14 @@ Use `/sc` inside a supported AI client. The equivalent CLI form is `npx scrumrun
|
|
|
17
17
|
/sc plan sprint --add|--list|--show|--start|--complete|--block
|
|
18
18
|
/sc plan feature --add|--list|--show|--activate|--complete
|
|
19
19
|
/sc plan run --list|--show|--validate|--learn|--complete|--resume|--fail|--block [--note] [typed evidence flags]
|
|
20
|
+
/sc plan run --finalize RUN-NNN [--summary "technical recap"] [--note]
|
|
20
21
|
/sc plan run --authorize-mutation RUN-NNN --path <relative-path> [--path ...]
|
|
21
22
|
/sc plan run --record-mutation RUN-NNN --permit MUT-id [--note] [--actor]
|
|
22
23
|
/sc plan run --satisfy-guardrail RUN-NNN --guardrail GR-NNN [typed evidence flags]
|
|
23
24
|
/sc plan challenge <question>
|
|
24
25
|
```
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
Normal execution is Markdown-first: after approval, work in code and the linked Task, then use one `--finalize` checkpoint. It verifies every workspace change and all Guardrail evidence before writing the Run transitions. A retry requires a failed, blocked, or partial Task and creates a new Run. Mutation permits are available only for explicitly requested strict mode.
|
|
27
28
|
|
|
28
29
|
## Knowledge
|
|
29
30
|
|
|
@@ -72,4 +73,4 @@ Top-level CLI aliases (`init`, `update`, `migrate`, `doctor`, `uninstall`, `stat
|
|
|
72
73
|
|
|
73
74
|
Run transitions accept typed evidence through `--command`, `--test`, `--file`, `--review`, `--decision`, `--insight`, `--risk`, or generic `--evidence kind:value`. `doctor --recover` is an explicit write that resolves only safe pending kernel transactions; doctor without it remains read-only.
|
|
74
75
|
|
|
75
|
-
Run `
|
|
76
|
+
Run `scrumrun commands` for grammar rendered directly from the current manifest.
|
package/lib/commands/manifest.js
CHANGED
|
@@ -15,6 +15,7 @@ const nouns = Object.freeze({
|
|
|
15
15
|
"--render <RUN-NNN>",
|
|
16
16
|
"--stats [--task <TASK-NNN>] [--feature <FEAT-NNN>] [--sprint <SPRINT-NNN>] [--json]",
|
|
17
17
|
"--normalize-legacy [--dry-run]",
|
|
18
|
+
"--finalize <RUN-NNN> [--summary \"technical recap\"] [--note]",
|
|
18
19
|
"--authorize-mutation <RUN-NNN> --path <relative-path>",
|
|
19
20
|
"--record-mutation <RUN-NNN> --permit <MUT-id> [--note] [--actor]",
|
|
20
21
|
"--satisfy-guardrail <RUN-NNN> --guardrail <GR-NNN> [--note] [--evidence] [--review] [--migration] [--actor]",
|
package/lib/commands/render.js
CHANGED
|
@@ -35,7 +35,8 @@ ${grammarLines().join("\n")}
|
|
|
35
35
|
- Linked Task/Run writes use a durable recovery journal; audit reports pending recovery and never repairs without explicit authorization.
|
|
36
36
|
- \`guardrails.md\` is canonical project policy; \`golden-rules.md\` is v1 compatibility only.
|
|
37
37
|
- Evaluate active Guardrails as \`passed\`, \`blocked\`, or \`deferred\`; cite exact \`GR-NNN\` ids and keep deferred execution gates visible.
|
|
38
|
-
-
|
|
38
|
+
- The normal path is one session checkpoint: after approval, work directly in code and the linked Task Markdown; finish once with \`sc plan run --finalize RUN-NNN\`. It verifies the complete workspace delta, policy, secrets, protected paths, and all Guardrail evidence together. Do not invoke \`npx scrumrun@latest\` during execution.
|
|
39
|
+
- Strict per-path Mutation Gateway permits remain available only when the owner explicitly requests strict execution.
|
|
39
40
|
- Knowledge/Decision/Insight records require evidence; AI-proposed Insights remain \`candidate\` until confirmed.
|
|
40
41
|
- Never print vault values or write before approval.
|
|
41
42
|
- Unknown nouns, subjects, actions, ids, or ambiguous approval must produce a deterministic explanation, never a guessed mutation.
|
package/lib/commands/repair.js
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
// complete→completed, in_progress→executing.
|
|
13
13
|
// 3. frontmatter method field: any value that isn't "2.0.0" → "2.0.0".
|
|
14
14
|
// 4. run.attempt missing → 1 (documented default).
|
|
15
|
+
// 5. Sprint task projections: add Task.sprint references absent from the
|
|
16
|
+
// Sprint's `## Tasks` list. Extra entries remain for human review.
|
|
15
17
|
//
|
|
16
18
|
// Categories intentionally NOT touched (need human judgment):
|
|
17
19
|
// - Secret-like content (SECRET_CANONICAL)
|
|
@@ -132,6 +134,48 @@ function scanTaskIds(scrumDir) {
|
|
|
132
134
|
return ids;
|
|
133
135
|
}
|
|
134
136
|
|
|
137
|
+
function planSprintMembership(scrumDir) {
|
|
138
|
+
const tasksBySprint = new Map();
|
|
139
|
+
for (const file of listDir(path.join(scrumDir, "tasks"), TASK_FILE)) {
|
|
140
|
+
const parsed = splitFrontmatter(readIf(file));
|
|
141
|
+
if (!parsed) continue;
|
|
142
|
+
const sprint = extractField(parsed.header, "sprint");
|
|
143
|
+
const id = extractField(parsed.header, "id") || path.basename(file, ".md");
|
|
144
|
+
if (!/^SPRINT-\d{3,}$/.test(sprint || "") || !TASK_REF.test(id)) continue;
|
|
145
|
+
if (!tasksBySprint.has(sprint)) tasksBySprint.set(sprint, []);
|
|
146
|
+
tasksBySprint.get(sprint).push(id);
|
|
147
|
+
}
|
|
148
|
+
const entries = [];
|
|
149
|
+
for (const file of listDir(path.join(scrumDir, "sprints"), SPRINT_FILE)) {
|
|
150
|
+
const original = readIf(file);
|
|
151
|
+
const parsed = splitFrontmatter(original);
|
|
152
|
+
if (!parsed) continue;
|
|
153
|
+
const sprintId = extractField(parsed.header, "id") || path.basename(file, ".md");
|
|
154
|
+
const expected = tasksBySprint.get(sprintId) || [];
|
|
155
|
+
if (!expected.length) continue;
|
|
156
|
+
const heading = /^## Tasks[ \t]*$/m.exec(parsed.body);
|
|
157
|
+
if (!heading) continue;
|
|
158
|
+
const tail = parsed.body.slice(heading.index + heading[0].length);
|
|
159
|
+
const nextHeading = /^## [^\r\n]+$/m.exec(tail);
|
|
160
|
+
const sectionEnd = nextHeading ? heading.index + heading[0].length + nextHeading.index : parsed.body.length;
|
|
161
|
+
const section = parsed.body.slice(heading.index + heading[0].length, sectionEnd);
|
|
162
|
+
const listed = new Set([...section.matchAll(/^-\s+(TASK-\d{3,})\s*$/gm)].map((match) => match[1]));
|
|
163
|
+
const missing = expected.filter((id) => !listed.has(id));
|
|
164
|
+
if (!missing.length) continue;
|
|
165
|
+
const before = parsed.body.slice(0, sectionEnd).replace(/\s*$/, "\n");
|
|
166
|
+
const after = parsed.body.slice(sectionEnd).replace(/^\n/, "");
|
|
167
|
+
const body = `${before}${missing.map((id) => `- ${id}`).join("\n")}\n${after}`;
|
|
168
|
+
entries.push({
|
|
169
|
+
file,
|
|
170
|
+
kind: "sprint-membership",
|
|
171
|
+
changes: [{ field: "sprint.tasks", from: "missing projection", to: missing.join(", ") }],
|
|
172
|
+
nextText: `---\n${parsed.header}\n---${parsed.sep}${body}`,
|
|
173
|
+
originalText: original
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return entries;
|
|
177
|
+
}
|
|
178
|
+
|
|
135
179
|
function legacyMetadata(body) {
|
|
136
180
|
const metadata = {};
|
|
137
181
|
const section = String(body || "").match(/^## Metadata\s*$([\s\S]*?)(?=^##\s+|(?![\s\S]))/im);
|
|
@@ -629,6 +673,7 @@ function analyze(scrumDir) {
|
|
|
629
673
|
const missingFrontmatter = new Set(entries.filter((entry) => entry.kind === "legacy-frontmatter").map((entry) => entry.file));
|
|
630
674
|
entries.push(...planAcceptanceCriteria(scrumDir).filter((entry) => !missingFrontmatter.has(entry.file)));
|
|
631
675
|
entries.push(...planSecretRedaction(scrumDir));
|
|
676
|
+
entries.push(...planSprintMembership(scrumDir));
|
|
632
677
|
|
|
633
678
|
for (const file of listDir(path.join(scrumDir, "tasks"), TASK_FILE)) {
|
|
634
679
|
const plan = planFile(file, "task", ctx);
|
|
@@ -419,9 +419,82 @@ function prepareCompletion(projectRoot, runArtifact, options = {}) {
|
|
|
419
419
|
return { record, body, resolved, status: "passed" };
|
|
420
420
|
}
|
|
421
421
|
|
|
422
|
+
// The normal v3 path is session based: an agent works freely after approval
|
|
423
|
+
// and the kernel verifies the complete workspace delta once, at completion.
|
|
424
|
+
// The older permit chain remains available for strict teams, but is no longer
|
|
425
|
+
// required to produce trustworthy evidence for ordinary work.
|
|
426
|
+
function prepareSessionCompletion(projectRoot, runArtifact, options = {}) {
|
|
427
|
+
if (runArtifact.record.guardrails !== 1 || runArtifact.record.workspace !== 1) {
|
|
428
|
+
return { record: runArtifact.record, body: runArtifact.body, changes: [], resolved: [], status: "legacy" };
|
|
429
|
+
}
|
|
430
|
+
const policy = policyState(projectRoot);
|
|
431
|
+
assertPolicyBound(runArtifact, policy);
|
|
432
|
+
const baseline = expectedWorkspace(runArtifact);
|
|
433
|
+
if (!baseline) throw new Error(`${runArtifact.record.id} has no workspace baseline; retry the Task before finalizing.`);
|
|
434
|
+
const actual = workspaceState(projectRoot);
|
|
435
|
+
const changes = changesBetween(baseline, actual);
|
|
436
|
+
const readOnly = readOnlyPaths(policy.config).map((value) => normalizedRelative(projectRoot, value));
|
|
437
|
+
const protectedChanges = changes.filter((change) => readOnly.some((item) => change.path === item || change.path.startsWith(`${item}/`)));
|
|
438
|
+
if (protectedChanges.length) throw new Error(`READ_ONLY_PATH: final workspace check found changes in ${protectedChanges.map((item) => item.path).join(", ")}.`);
|
|
439
|
+
const unsafeKinds = changes.filter((change) => !["file", "missing", "clean"].includes(change.after_kind));
|
|
440
|
+
if (unsafeKinds.length) throw new Error(`UNSAFE_MUTATION_TYPE: final workspace check found ${unsafeKinds.map((item) => item.path).join(", ")}.`);
|
|
441
|
+
const unscannable = changes.filter((change) => change.after_kind === "file" && change.after_scan !== "text");
|
|
442
|
+
if (unscannable.length) throw new Error(`UNSCANNABLE_MUTATION: final workspace check cannot inspect ${unscannable.map((item) => item.path).join(", ")}.`);
|
|
443
|
+
const leaked = changes.filter((change) => change.new_secret_fingerprints.length);
|
|
444
|
+
if (leaked.length) throw new Error(`SECRET_BOUNDARY: final workspace check found secret-like content in ${leaked.map((item) => item.path).join(", ")}.`);
|
|
445
|
+
|
|
446
|
+
let record = runArtifact.record;
|
|
447
|
+
let body = runArtifact.body;
|
|
448
|
+
if (changes.length) {
|
|
449
|
+
const appended = appendMutationEvent(record, body, {
|
|
450
|
+
mutation_id: `MUT-${sha256(`${runArtifact.record.id}:${baseline.fingerprint}:${actual.fingerprint}`).slice(0, 16)}`,
|
|
451
|
+
paths: changes.map((change) => change.path).sort(),
|
|
452
|
+
changes: changes.map(({ new_secret_fingerprints, ...change }) => change),
|
|
453
|
+
workspace_before: baseline.fingerprint,
|
|
454
|
+
workspace_after: publicWorkspace(actual),
|
|
455
|
+
policy_fingerprint: policy.fingerprint
|
|
456
|
+
}, {
|
|
457
|
+
note: options.note || `Final session audit verified ${changes.length} workspace change(s).`,
|
|
458
|
+
actor: options.actor || "agent",
|
|
459
|
+
evidence: [{ kind: "mutation", summary: `${changes.length} changed path(s) passed the final workspace audit.` }]
|
|
460
|
+
});
|
|
461
|
+
record = appended.record;
|
|
462
|
+
body = appended.body;
|
|
463
|
+
}
|
|
464
|
+
const resolved = [];
|
|
465
|
+
const repository = new ArtifactRepository(path.join(projectRoot, ".scrumrun"));
|
|
466
|
+
const supplied = options.guardrailEvidence || {};
|
|
467
|
+
const automatic = new Set(["builtin:owner-work", "builtin:read-only-path", "builtin:secret-boundary"]);
|
|
468
|
+
for (const obligation of guardrailState(record, body).filter((item) => item.status !== "passed")) {
|
|
469
|
+
const evidence = automatic.has(obligation.enforcement)
|
|
470
|
+
? [{ kind: "mutation", summary: "Verified by the final workspace audit." }]
|
|
471
|
+
: Array.isArray(supplied[obligation.guardrail]) ? supplied[obligation.guardrail] : [];
|
|
472
|
+
if (!evidence.length) {
|
|
473
|
+
throw new Error(`GUARDRAILS_PENDING: ${obligation.guardrail} needs final evidence in the Task's ## Guardrail Evidence section.`);
|
|
474
|
+
}
|
|
475
|
+
assertGateEvidence(projectRoot, repository, requiredEvidenceKind(obligation.enforcement), evidence, obligation.guardrail);
|
|
476
|
+
const appended = appendGuardrailEvent(record, body, obligation, "passed", {
|
|
477
|
+
note: `${obligation.guardrail} passed at the final session checkpoint.`,
|
|
478
|
+
actor: "finalize",
|
|
479
|
+
occurredAt: options.occurredAt,
|
|
480
|
+
evidence
|
|
481
|
+
});
|
|
482
|
+
record = appended.record;
|
|
483
|
+
body = appended.body;
|
|
484
|
+
resolved.push(obligation.guardrail);
|
|
485
|
+
}
|
|
486
|
+
const transactions = pendingTransactionStatus(path.join(projectRoot, ".scrumrun"));
|
|
487
|
+
if (transactions.error || transactions.pending.length) throw new Error(`TRANSACTION_GATE: ${transactions.error || "pending canonical transaction"}.`);
|
|
488
|
+
return { record, body, changes, resolved, status: "passed", policy: policy.fingerprint };
|
|
489
|
+
}
|
|
490
|
+
|
|
422
491
|
function auditActiveWorkspace(projectRoot, runArtifact) {
|
|
423
492
|
try {
|
|
424
493
|
if (!runArtifact.record || runArtifact.record.workspace !== 1 || ["completed", "failed", "blocked"].includes(runArtifact.record.status)) return null;
|
|
494
|
+
// Session Runs intentionally accumulate unrecorded workspace changes until
|
|
495
|
+
// their single final checkpoint. Those changes are audited by --finalize,
|
|
496
|
+
// not reported as a strict-mode bypass while work is still in progress.
|
|
497
|
+
if (runArtifact.record.execution === "session") return null;
|
|
425
498
|
verifyWorkspaceIntegrity(projectRoot, runArtifact);
|
|
426
499
|
return null;
|
|
427
500
|
} catch (error) {
|
|
@@ -440,6 +513,7 @@ module.exports = {
|
|
|
440
513
|
permitsOverlap,
|
|
441
514
|
policyState,
|
|
442
515
|
prepareCompletion,
|
|
516
|
+
prepareSessionCompletion,
|
|
443
517
|
publicWorkspace,
|
|
444
518
|
recordMutation,
|
|
445
519
|
satisfyGuardrail,
|
|
@@ -23,7 +23,7 @@ const { currentBranch } = require("./workspace-state");
|
|
|
23
23
|
const { extractLearningCandidates } = require("../code-intel/learning");
|
|
24
24
|
const { appendRunEvent, appendTechnicalSummary, createRunBody, instant } = require("./run-ledger");
|
|
25
25
|
const { generateBriefing, generateErrorsReport } = require("./briefing");
|
|
26
|
-
const { assertCanonicalWrite, policyState, prepareCompletion, publicWorkspace, verifyWorkspaceIntegrity, workspaceState } = require("./mutation-gateway");
|
|
26
|
+
const { assertCanonicalWrite, policyState, prepareCompletion, prepareSessionCompletion, publicWorkspace, verifyWorkspaceIntegrity, workspaceState } = require("./mutation-gateway");
|
|
27
27
|
const { recoverPendingTransactions, runKernelTransaction } = require("../v2/transaction");
|
|
28
28
|
|
|
29
29
|
const TERMINAL = new Set(["completed", "failed", "cancelled", "resolved", "rejected", "deprecated", "invalidated", "archived", "passed"]);
|
|
@@ -164,6 +164,7 @@ function approveRequestUnlocked(projectRoot, token, { failurePoint = null, inter
|
|
|
164
164
|
ledger: 1,
|
|
165
165
|
guardrails: 1,
|
|
166
166
|
workspace: 1,
|
|
167
|
+
execution: "session",
|
|
167
168
|
approval_id: approvalId
|
|
168
169
|
};
|
|
169
170
|
const taskLinks = { feature: featureId, sprint: sprintId };
|
|
@@ -267,6 +268,94 @@ function transitionedRunContent(content, nextStatus, options = {}) {
|
|
|
267
268
|
};
|
|
268
269
|
}
|
|
269
270
|
|
|
271
|
+
function sectionBody(markdown, heading) {
|
|
272
|
+
const match = new RegExp(`^## ${heading}[ \\t]*\\r?\\n\\r?\\n([\\s\\S]*?)(?=^## |(?![\\s\\S]))`, "m").exec(markdown || "");
|
|
273
|
+
return match ? match[1].trim() : null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Human-authored evidence stays beside the work in the Task, rather than
|
|
277
|
+
// forcing a separate CLI invocation for every guardrail. The single final
|
|
278
|
+
// checkpoint parses it deterministically and rejects malformed entries.
|
|
279
|
+
//
|
|
280
|
+
// - GR-005 | review | REV-001 | Scoped architecture review passed.
|
|
281
|
+
// - GR-006 | migration | db/migrations/20260831.sql | Migration verified.
|
|
282
|
+
// - GR-007 | note | Request authorization confirmed.
|
|
283
|
+
function finalGuardrailEvidence(taskBody) {
|
|
284
|
+
const content = sectionBody(taskBody, "Guardrail Evidence");
|
|
285
|
+
if (!content) return {};
|
|
286
|
+
const result = {};
|
|
287
|
+
for (const line of content.split(/\r?\n/)) {
|
|
288
|
+
if (!line.trim()) continue;
|
|
289
|
+
const raw = line.match(/^\s*-\s*(GR-\d{3,})\s*\|\s*([a-z-]+)\s*\|\s*(.*?)\s*$/i);
|
|
290
|
+
if (!raw) throw new Error(`Invalid Guardrail Evidence entry: ${line.trim()}`);
|
|
291
|
+
const [, guardrail, kind, remainder] = raw;
|
|
292
|
+
const parts = remainder.split("|").map((part) => part.trim()).filter(Boolean);
|
|
293
|
+
if (!parts.length) throw new Error(`${guardrail} needs evidence content.`);
|
|
294
|
+
const allowed = new Set(["approval", "command", "test", "file", "review", "decision", "insight", "risk", "note", "migration", "legacy", "guardrail", "mutation"]);
|
|
295
|
+
if (!allowed.has(kind)) throw new Error(`${guardrail} has unsupported evidence kind: ${kind}.`);
|
|
296
|
+
const evidence = { kind };
|
|
297
|
+
if (parts.length > 1) evidence.ref = parts.shift();
|
|
298
|
+
evidence.summary = parts.join(" | ") || (evidence.ref ? `${kind} evidence: ${evidence.ref}` : null);
|
|
299
|
+
if (!evidence.summary) throw new Error(`${guardrail} needs an evidence summary.`);
|
|
300
|
+
if (!result[guardrail]) result[guardrail] = [];
|
|
301
|
+
result[guardrail].push(evidence);
|
|
302
|
+
}
|
|
303
|
+
return result;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function finalizeRunUnlocked(projectRoot, runId, { note = null, summary = null, actor = "agent", occurredAt = null } = {}) {
|
|
307
|
+
const scrumDir = path.join(projectRoot, ".scrumrun");
|
|
308
|
+
recoverPendingTransactions(scrumDir);
|
|
309
|
+
const repository = new ArtifactRepository(scrumDir);
|
|
310
|
+
const runArtifact = repository.read("run", runId);
|
|
311
|
+
if (!runArtifact || runArtifact.errors.length) throw new Error(`Run not found or invalid: ${runId}`);
|
|
312
|
+
if (runArtifact.record.status !== "executing") throw new Error(`Lightweight finalization requires an executing Run; ${runId} is ${runArtifact.record.status}.`);
|
|
313
|
+
const taskArtifact = repository.read("task", runArtifact.record.task);
|
|
314
|
+
if (!taskArtifact || taskArtifact.errors.length) throw new Error(`Task not found or invalid for ${runId}.`);
|
|
315
|
+
const technicalSummary = summary || sectionBody(taskArtifact.body, "Technical Summary");
|
|
316
|
+
if (!technicalSummary) throw new Error(`TASK_SUMMARY_REQUIRED: add ## Technical Summary to ${taskArtifact.record.id}, or pass --summary.`);
|
|
317
|
+
const effectiveActor = actor === "agent" ? (agentIdentity(scrumDir) || "agent") : actor;
|
|
318
|
+
const prepared = prepareSessionCompletion(projectRoot, runArtifact, {
|
|
319
|
+
note,
|
|
320
|
+
actor: effectiveActor,
|
|
321
|
+
occurredAt,
|
|
322
|
+
guardrailEvidence: finalGuardrailEvidence(taskArtifact.body)
|
|
323
|
+
});
|
|
324
|
+
let runContent = serializeArtifact(prepared.record, prepared.body);
|
|
325
|
+
for (const [status, evidence] of [
|
|
326
|
+
["validating", [{ kind: "test", summary: "Final session integrity audit passed." }]],
|
|
327
|
+
["learning", [{ kind: "insight", summary: "Learning extraction scheduled from the completed session." }]],
|
|
328
|
+
["completed", [{ kind: "note", summary: note || "Completed through the final session checkpoint." }]]
|
|
329
|
+
]) {
|
|
330
|
+
runContent = transitionedRunContent(runContent, status, { actor: effectiveActor, occurredAt, note, evidence }).content;
|
|
331
|
+
}
|
|
332
|
+
runContent = appendTechnicalSummary(parseArtifact(runContent).record, runContent, technicalSummary);
|
|
333
|
+
const taskPrevious = fs.readFileSync(taskArtifact.file, "utf8");
|
|
334
|
+
let taskContent = taskPrevious;
|
|
335
|
+
let taskNext = null;
|
|
336
|
+
for (const status of ["validating", "learning", "completed"]) {
|
|
337
|
+
taskNext = transitionedArtifactContent(taskContent, "task", status, new Date().toISOString().slice(0, 10));
|
|
338
|
+
taskContent = taskNext.content;
|
|
339
|
+
}
|
|
340
|
+
runKernelTransaction(scrumDir, "finalize-lightweight-session", [
|
|
341
|
+
{ file: runArtifact.file, previous: fs.readFileSync(runArtifact.file, "utf8"), next: runContent },
|
|
342
|
+
{ file: taskArtifact.file, previous: taskPrevious, next: taskNext.content }
|
|
343
|
+
]);
|
|
344
|
+
refreshState(scrumDir);
|
|
345
|
+
let learning = null;
|
|
346
|
+
try {
|
|
347
|
+
learning = extractLearningCandidates(projectRoot, runId);
|
|
348
|
+
} catch (error) {
|
|
349
|
+
learning = { created: [], warnings: [`Learning extraction did not block finalization: ${error.message}`] };
|
|
350
|
+
}
|
|
351
|
+
refreshState(scrumDir);
|
|
352
|
+
return { run: parseArtifact(runContent).record, task: taskNext.record, changes: prepared.changes.length, resolved: prepared.resolved, learning };
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function finalizeRun(projectRoot, runId, options = {}) {
|
|
356
|
+
return withArtifactLock(path.join(projectRoot, ".scrumrun"), `run-${String(runId).toLowerCase()}`, () => finalizeRunUnlocked(projectRoot, runId, options));
|
|
357
|
+
}
|
|
358
|
+
|
|
270
359
|
function transitionRunUnlocked(projectRoot, runId, nextStatus, { note = null, evidence = [], actor = "agent", occurredAt = null, summary = null, failurePoint = null, interruptPoint = null } = {}) {
|
|
271
360
|
const scrumDir = path.join(projectRoot, ".scrumrun");
|
|
272
361
|
const effectiveActor = actor === "agent" ? (agentIdentity(scrumDir) || "agent") : actor;
|
|
@@ -376,6 +465,7 @@ function retryTaskUnlocked(projectRoot, taskId, { note = "Retry explicitly appro
|
|
|
376
465
|
ledger: 1,
|
|
377
466
|
guardrails: 1,
|
|
378
467
|
workspace: 1,
|
|
468
|
+
execution: "session",
|
|
379
469
|
approval_id: taskArtifact.record.approval_id || null
|
|
380
470
|
};
|
|
381
471
|
const runBody = createRunBody(run, {
|
|
@@ -447,6 +537,7 @@ function startBacklogTaskUnlocked(projectRoot, taskId, { note = "Backlog Task st
|
|
|
447
537
|
ledger: 1,
|
|
448
538
|
guardrails: 1,
|
|
449
539
|
workspace: 1,
|
|
540
|
+
execution: "session",
|
|
450
541
|
approval_id: taskArtifact.record.approval_id || null
|
|
451
542
|
};
|
|
452
543
|
const runBody = createRunBody(run, {
|
|
@@ -598,4 +689,4 @@ function addPlanArtifact(projectRoot, kind, label, options = {}) {
|
|
|
598
689
|
return withArtifactLock(scrumDir, "create", () => addPlanArtifactUnlocked(projectRoot, kind, label, options));
|
|
599
690
|
}
|
|
600
691
|
|
|
601
|
-
module.exports = { addPlanArtifact, approveRequest, nextBacklogTask, nextId, refreshErrors, refreshState, renderState, retryTask, startBacklogTask, stateFingerprint, stateIsStale, transitionRun };
|
|
692
|
+
module.exports = { addPlanArtifact, approveRequest, finalizeRun, nextBacklogTask, nextId, refreshErrors, refreshState, renderState, retryTask, startBacklogTask, stateFingerprint, stateIsStale, transitionRun };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# AGENTS.md - {{PROJECT_NAME}}
|
|
2
2
|
|
|
3
|
-
## ScrumRun
|
|
3
|
+
## ScrumRun 3.0
|
|
4
4
|
|
|
5
5
|
This project uses ScrumRun. The method is mandatory; `/sc` is its single optional shortcut.
|
|
6
6
|
|
|
@@ -20,9 +20,11 @@ After approval:
|
|
|
20
20
|
- Run is one execution attempt and follows `executing → validating → learning → completed|failed|blocked`;
|
|
21
21
|
- a retry creates a new Run and preserves the old one;
|
|
22
22
|
- record a `## Technical Summary` at completion so the next agent inherits what was done;
|
|
23
|
+
- work directly in code and the linked Task Markdown after approval; do not call `npx scrumrun@latest` during execution;
|
|
24
|
+
- record one `## Guardrail Evidence` line per non-automatic guardrail in the Task, then run `scrumrun sc plan run --finalize RUN-NNN` once to validate and close the Run;
|
|
23
25
|
- when a Run completes and work remains queued, surface it with `sc plan task --next` and start it with `sc plan task --start` — starting is explicit approval;
|
|
24
26
|
- learning proposes evidence-backed Knowledge, Decisions, or candidate Insights;
|
|
25
|
-
-
|
|
27
|
+
- the final checkpoint verifies all Guardrails, workspace changes, protected paths, and secret boundaries before completion; use the path-scoped Mutation Gateway only when the owner requests strict execution.
|
|
26
28
|
|
|
27
29
|
Never bypass guardrails or edit around the Mutation Gateway, overwrite owner work, treat generated state/cache as truth, auto-confirm AI knowledge, auto-migrate a v1 project, or print vault values.
|
|
28
30
|
|
|
@@ -36,7 +36,7 @@ Normal hot path:
|
|
|
36
36
|
5. follow the briefing's pointers to only the relevant canonical artifacts; go deeper only when the briefing lacks what you need (`## Where to look`, `sc knowledge study "<topic>"`);
|
|
37
37
|
6. load `.scrumrun/core.md` when the method contract or an exceptional transition is needed.
|
|
38
38
|
|
|
39
|
-
**Never write Run events by hand.**
|
|
39
|
+
**Never write Run events by hand.** Work directly in code and the linked Task Markdown after approval, then let the one final CLI checkpoint mutate `runs/RUN-NNN.md`: `scrumrun sc plan run --finalize RUN-NNN`. It creates the validated transition ledger, audits the full workspace delta, and records the Guardrail results together. Do not invoke `npx scrumrun@latest` during normal execution. The older `--validate | --learn | --complete | --satisfy-guardrail | --authorize-mutation | --record-mutation` commands remain only for owner-requested strict mode. Recover hand-written Runs via `sc plan run --normalize-legacy` — originals are preserved byte-exact under `.scrumrun/.migration-backup/runs/`.
|
|
40
40
|
|
|
41
41
|
Lean mode is a read policy, not an incomplete store. Generated files and `.scrumrun/.cache/` are never authoritative.
|
|
42
42
|
|
|
@@ -101,19 +101,17 @@ During execution:
|
|
|
101
101
|
1. keep the change inside the approved Task scope;
|
|
102
102
|
2. preserve existing owner work and unrelated dirty files;
|
|
103
103
|
3. define or confirm the Task's `## Acceptance Criteria` before execution and check them off as evidence;
|
|
104
|
-
4.
|
|
104
|
+
4. work normally: edit code and update the Task's `## Technical Summary` and, for non-automatic rules, `## Guardrail Evidence` as evidence becomes available;
|
|
105
105
|
5. validate in proportion to risk and against the acceptance criteria;
|
|
106
|
-
6.
|
|
107
|
-
7. run
|
|
108
|
-
8.
|
|
109
|
-
9. record a `## Technical Summary` at completion (`sc plan run --complete --summary "…"`) so the next agent inherits what was actually done;
|
|
110
|
-
10. complete the Run, then Task, and only then the Sprint when its whole batch is done.
|
|
106
|
+
6. run configured reviewers when a Guardrail requires one;
|
|
107
|
+
7. finish once with `scrumrun sc plan run --finalize RUN-NNN`; it verifies every changed path, policy, secret boundary, and guardrail evidence before creating the structured Run events and completing the Task;
|
|
108
|
+
8. use path-scoped Mutation Gateway commands only when the owner explicitly requests strict execution.
|
|
111
109
|
|
|
112
110
|
Never overwrite a prior attempt. Never mark work complete because time/token budget ended.
|
|
113
111
|
|
|
114
112
|
When a Run completes and work remains queued, the briefing's `## Next Up` names the next backlog Task. Surface it with `sc plan task --next` and start it with `sc plan task --start [TASK-NNN]` — starting is the explicit approval; the owner can always decline. Each agent declares its identity via `SCRUMRUN_AGENT` (or `Agent Identity` in `config.md`); it is recorded as the Task `assignee` and the Run event `actor`.
|
|
115
113
|
|
|
116
|
-
Every deferred policy result is an append-only Run obligation.
|
|
114
|
+
Every deferred policy result is an append-only Run obligation. The final checkpoint fails closed on policy drift, protected-path changes, unsafe symlinks, unscannable content, newly introduced secret-like content, or missing Guardrail Evidence. In strict mode it additionally requires the permit chain. The ignored permit cache is disposable; deleting it invalidates outstanding strict-mode permits and never creates authority.
|
|
117
115
|
|
|
118
116
|
Run is the sole operational-history authority. Task synchronizes current status without copying Run events. Validation, learning, completion, failure, block, and resume require a reason or structured evidence; completion also requires evidenced validation and learning. Early v2 prose Runs are migrated explicitly, with deterministic chains recovered and uncertain history represented as an evidenced snapshot.
|
|
119
117
|
|