scrumrun 2.7.11 → 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 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.** Runs are only mutated through the CLI: `sc plan run --validate | --learn | --complete | --resume | --fail | --block | --satisfy-guardrail | --authorize-mutation | --record-mutation`. Editing `runs/RUN-NNN.md` directly bypasses schema validation, produces invalid ledger events (invalid `type`, unknown evidence `kind`, missing snapshot, wrong `from`), and breaks conformance for the entire project. If the CLI does not expose the shape you need, propose a spec change through an ADR do not invent event vocabulary. Existing hand-written Runs can be recovered with `sc plan run --normalize-legacy` (byte-exact original preserved in `.scrumrun/.migration-backup/runs/`).
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:** `2.7.11` · **Method target:** `2.0.0` · **Runtime:** Node.js `>=22.13.0` · **License:** MIT
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
- `npx scrumrun@latest <command>` also works if you prefer not to install globally, but pin the version explicitly (`@latest` or `@2.6.1`) `npx scrumrun` alone will happily reuse a stale cached version.
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; `--complete --summary "…"` stores a `## Technical Summary` on the Run so the next agent inherits what was actually done. Failed retries remain available as separate Runs.
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. Material source edits use a 15-minute, path-scoped Mutation Gateway permit. Recording verifies before/after hashes, owner/read-only scope, symlinks, new secret-like content, and workspace drift. Validation and completion fail closed when edits bypass that chain or obligations remain unresolved.
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
- npx scrumrun@latest commands
252
+ scrumrun commands
247
253
 
248
254
  # plan without writes, then approve the emitted token
249
- npx scrumrun@latest sc plan intake "Fix pricing rounding" --type fix --preview "Rounding moved after tax calc"
250
- npx scrumrun@latest sc plan intake --approve <token>
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
- npx scrumrun@latest sc plan run --complete RUN-001 --note "Done" --evidence "npm test: passed" --summary "Moved rounding after tax calc in checkout/pricing.ts"
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
- npx scrumrun@latest sc plan task --next
257
- npx scrumrun@latest sc plan task --start TASK-009
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
- npx scrumrun@latest sc plan run --authorize-mutation RUN-001 --path src/pricing.ts
261
- npx scrumrun@latest sc plan run --record-mutation RUN-001 --permit MUT-... --note "Pricing change recorded"
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
- npx scrumrun@latest sc review artifact --run
265
- npx scrumrun@latest sc review artifact --record --task TASK-001 --run RUN-001 --evidence "npm test: passed"
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
- npx scrumrun@latest sc knowledge insight --propose "Pricing stays in backend" --evidence src/pricing.ts
270
- npx scrumrun@latest sc knowledge insight --confirm INS-001
271
- npx scrumrun@latest sc knowledge study calculateFinalPrice
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
- npx scrumrun@latest sc knowledge map --build
275
- npx scrumrun@latest sc knowledge map --show
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
- npx scrumrun@latest sc plan run --render RUN-001
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
- npx scrumrun@latest sc plan run --stats
282
- npx scrumrun@latest sc plan run --stats --task TASK-001 --json
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. A material source mutation requires a short-lived, path-scoped permit issued from that baseline; recording it verifies before/after hashes, policy freshness, path scope, read-only boundaries, symlink safety, and newly introduced secret-like content. Unrecorded workspace drift fails closed. A Run cannot complete while an obligation is unresolved or the workspace differs from its last recorded mutation.
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. Validation, learning, completion, failure, block, and resume transitions require a reason or typed evidence. Task status changes without receiving a duplicate narrative history. On completion, an optional `## Technical Summary` section records what was actually done, surfaced to later intake through the briefing. 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.
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 2.0 Command Reference
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 `npx scrumrun@latest sc ...`. CLI-native workflows execute immediately; reasoning-heavy routes tell the installed agent to execute the validated workflow.
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
- CLI-native: intake/approval, Task/Run list/show, Task retry, Mutation Gateway actions, Guardrail satisfaction, and Run transitions. A retry requires a failed, blocked, or partial Task and creates a new Run. Mutation permits expire after 15 minutes, authorize explicit relative paths only, and must be recorded immediately after the edit.
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 `npx scrumrun@latest commands` for grammar rendered directly from the current manifest.
76
+ Run `scrumrun commands` for grammar rendered directly from the current manifest.
@@ -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]",
@@ -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
- - Persist deferred checks as Run obligations. Every material source edit requires a short-lived path-scoped Mutation Gateway permit and immediate hash recording; policy/workspace drift or unresolved obligations block completion.
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.
@@ -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
  {
2
2
  "name": "scrumrun",
3
- "version": "2.7.11",
3
+ "version": "3.0.0",
4
4
  "description": "Evidence-driven Agile runtime and semantic project memory for AI coding agents.",
5
5
  "bin": {
6
6
  "scrumrun": "bin/scrumrun.js",
@@ -1,6 +1,6 @@
1
1
  # AGENTS.md - {{PROJECT_NAME}}
2
2
 
3
- ## ScrumRun 2.0
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
- - every application/source edit requires a path-scoped Mutation Gateway permit, immediate hash recording, and resolution of the Run's Guardrail obligations before completion.
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.** Only the CLI mutates `runs/RUN-NNN.md`: `sc plan run --validate | --learn | --complete | --resume | --fail | --block | --satisfy-guardrail | --authorize-mutation | --record-mutation`. Direct edits produce invalid ledger events (unknown `type` like `execution`/`validation`/`learning`, unknown evidence `kind` like `guardrail-check`/`build`/`task-status`, missing snapshot invariant) and break project conformance. If the CLI does not expose the shape you need, propose a spec change instead of inventing vocabulary. Recover hand-written Runs via `sc plan run --normalize-legacy` — originals are preserved byte-exact under `.scrumrun/.migration-backup/runs/`.
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. enforce guardrails before every material mutation: obtain a short-lived path-scoped Mutation Gateway permit before editing and record its verified before/after hashes immediately afterward;
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. record exactly one structured `RUN-NNN-EVT-NNN` event per state transition, with RFC3339 time, actor, reason, and typed evidence;
107
- 7. run configured reviewers;
108
- 8. extract candidate learning only after validation;
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. Unrecorded workspace drift, policy drift, an expired/missing permit, out-of-scope changes, unsafe symlinks, newly introduced secret-like content, or an unresolved obligation blocks validation/completion. The ignored permit cache is disposable; deleting it invalidates outstanding permits and never creates authority.
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