scrumrun 2.5.1 → 2.6.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,27 @@ All notable changes follow Semantic Versioning.
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 2.6.0 - 2026-08-21
8
+
9
+ ### Added
10
+
11
+ - **Project Briefing** — `state.md` is now a structured briefing (`## Now`, `## Recent`, `## Open Decisions`, `## Active Memory`, `## Next Up`, `## Where to look`) instead of a flat active-work list. It is the progressive-disclosure entry point: agents read the briefing first and only go deeper when it lacks what they need. Recent completions surface their technical summaries so the next agent knows what was just done without reading full ledgers. `buildContextPackage` now includes a `briefing` field.
12
+ - **Acceptance Criteria** — every approved Task body gains an `## Acceptance Criteria` section so "done" is defined before execution. Conformance emits a non-blocking `ACCEPTANCE_CRITERIA_MISSING` warning for active Tasks that lack it.
13
+ - **Technical Summary** — `sc plan run --complete --summary "…"` stores a `## Technical Summary` section on the Run, surfaced later in the briefing and context package so completed work informs future intake. `appendTechnicalSummary`/`extractTechnicalSummary` are exported for library consumers.
14
+ - **`--type` override** — `sc plan intake "…" --type fix|task|feature|docs|discovery` lets the agent assert the classification explicitly, overriding keyword inference with validation and a stable reason.
15
+ - **`--preview`** — `sc plan intake "…" --preview "technical summary"` renders a cyan Preview field in the pretty terminal layout (mirroring the landing page), binds it into the approval token, and stores it as `## Preview` on the Task.
16
+ - **Auto-sequencing** — `sc plan task --next` surfaces the oldest backlog Task; `sc plan task --start [TASK-NNN]` promotes it to `running`, creates a Run, re-evaluates policy, and records the agent identity. The briefing's `## Next Up` lists the queue.
17
+ - **Agent identity and assignment** — `SCRUMRUN_AGENT` env var (or `Agent Identity` in `config.md`) sets the agent identity recorded as the `assignee` on approved/started Tasks and as the `actor` on Run transitions. The briefing shows the assignee next to active work for team visibility.
18
+
19
+ ### Changed
20
+
21
+ - `state.md` projection schema bumped 1 → 2 (regenerated automatically; the previous format is superseded, never migrated by hand).
22
+ - `update` now regenerates the disposable briefing after installing integrations, so upgrading from 2.5.x has no friction with the previous `state.md` format. All other changes are additive (new sections and fields) and do not require migration.
23
+
24
+ ### Validation
25
+
26
+ - Test suite grew from 192 to 213 passing.
27
+
7
28
  ## 2.5.1 - 2026-07-23
8
29
 
9
30
  ### Fixed
package/CORE.md CHANGED
@@ -108,10 +108,12 @@ Normal read path:
108
108
 
109
109
  1. `AGENTS.md`;
110
110
  2. `.scrumrun/guardrails.md`;
111
- 3. `.scrumrun/state.md`;
111
+ 3. `.scrumrun/state.md` — the **briefing**: a bounded summary of active work, recent completions with their technical summaries, open decisions, active memory, the backlog queue, and pointers;
112
112
  4. only the canonical ids and evidence relevant to current work;
113
113
  5. `.scrumrun/core.md` when method details or exceptional transitions are needed.
114
114
 
115
+ The briefing is a progressive-disclosure index, not full context. Read it first and follow its pointers to specific artifacts (`## Where to look`, `sc knowledge study "<topic>"`). Go deeper — a Task body, a Run ledger, a Decision — only when the briefing lacks what you need. Never treat the briefing as a substitute for the canonical artifact it points to.
116
+
115
117
  Lean mode is this bounded read policy; it is not permission to omit canonical truth.
116
118
 
117
119
  `guardrails.md` is the sole canonical project-policy file. `golden-rules.md` is a v1 migration source/compatibility pointer, not a competing authority. `config.md` stores interaction preferences and cannot weaken guardrails.
@@ -154,6 +156,8 @@ Intake must:
154
156
 
155
157
  Policy evaluation is structured per active `GR-NNN`: `passed`, `blocked`, or `deferred`. A block must cite the exact Guardrail id and reason code. Deferred checks must be visible in the plan and re-evaluated at their named execution boundary; they are never silently counted as passed. Invalid/duplicate Guardrails or configuration that weakens approval block conformance and intake.
156
158
 
159
+ The agent may assert the classification explicitly with `sc plan intake "…" --type fix|task|feature|docs|discovery`, overriding keyword inference (validated, with a stable reason). It may also attach a short technical explanation with `--preview "…"`, rendered in the pretty terminal layout, bound into the approval token, and stored as `## Preview` on the Task.
160
+
157
161
  Before approval, do not create canonical files, update status, edit application code, or retain request content outside ignored disposable context cache. The approval token binds both canonical context and the workspace fingerprint; drift in either requires a new intake. Ambiguous acknowledgement is not approval.
158
162
 
159
163
  ## Execution lifecycle
@@ -177,11 +181,13 @@ Rules:
177
181
  - the Run ledger is operational history; Task synchronizes current status without copying the Run event;
178
182
  - validation, learning, completion, failure, block, and resume require a reason or evidence;
179
183
  - validation must match the risk and acceptance criteria;
184
+ - each Task declares its `## Acceptance Criteria` before execution; check them off as evidence, never mark done on vibes;
180
185
  - configured reviews run before completion;
181
186
  - every deferred policy result is persisted as a Run Guardrail obligation;
182
187
  - before changing application/source files, issue a short-lived path-scoped mutation permit and record the verified before/after hashes in the Run;
183
188
  - unrecorded workspace drift, policy drift, out-of-scope paths, unsafe symlinks, new secret-like content, or unresolved obligations block validation/completion;
184
189
  - learning proposes memory candidates after validation and never auto-confirms AI inference;
190
+ - record a `## Technical Summary` at completion with `sc plan run --complete --summary "…"` so the next agent inherits what was actually done;
185
191
  - complete a Sprint only when all its included Tasks meet the Sprint exit gate;
186
192
  - do not mark work complete merely because time or token budget ended.
187
193
 
@@ -189,6 +195,14 @@ Canonical mutations are schema-validated, lossless, and atomic. Preserve unknown
189
195
 
190
196
  Task/Run pair mutations use an ignored durable journal under `.scrumrun/.backup/transactions/`. A captured failure rolls back immediately; an interrupted `prepared` transaction is rolled back before the next approved mutation, while an interrupted `committed` transaction is verified and finalized. Audit only reports `TRANSACTION_PENDING`. Recovery writes occur only when the approved operation is retried or `doctor --recover` is explicitly invoked, and recovery refuses to overwrite bytes that match neither side of the journal.
191
197
 
198
+ ### Backlog and sequencing
199
+
200
+ Backlog is the queue of Tasks with `status: backlog`, ordered oldest-first by id. When a Run completes, the briefing's `## Next Up` names the next backlog Task. `sc plan task --next` shows it; `sc plan task --start [TASK-NNN]` promotes it to `running`, creates its first Run, re-evaluates policy, and records the agent identity. Starting is itself the explicit approval (I-01): no work executes silently, and the owner can always say "not now".
201
+
202
+ ### Agent identity and assignment
203
+
204
+ Each agent declares an identity via the `SCRUMRUN_AGENT` environment variable or the `Agent Identity:` field in `config.md` (single-agent default). The identity is recorded as the `assignee` on approved/started Tasks and as the `actor` on Run events. In `--shared` teams, every agent sets its own `SCRUMRUN_AGENT` and reads the briefing's `## Now` to see who owns each active Task before picking up work; file locks and the transaction journal prevent corruption on concurrent edits.
205
+
192
206
  ## Semantic memory
193
207
 
194
208
  ### Knowledge
@@ -252,11 +266,11 @@ Legacy sprint plan entries normally become Tasks. History attempts become Runs w
252
266
 
253
267
  ### `/sc plan`
254
268
 
255
- - `task --add|--list|--show|--run|--audit|--cancel|--retry`
269
+ - `task --add|--list|--show|--run|--audit|--cancel|--retry|--next|--start [TASK-NNN]`
256
270
  - `sprint --add|--list|--show|--start|--complete|--block`
257
271
  - `feature --add|--list|--show|--activate|--complete`
258
- - `run --list|--show|--authorize-mutation|--record-mutation|--satisfy-guardrail|--validate|--learn|--complete|--resume|--fail|--block`
259
- - `intake <request>`
272
+ - `run --list|--show|--render|--stats|--normalize-legacy|--authorize-mutation|--record-mutation|--satisfy-guardrail|--validate|--learn|--complete [--summary "…"]|--resume|--fail|--block`
273
+ - `intake <request> [--type fix|task|feature|docs|discovery] [--preview "…"]`
260
274
  - `challenge <question>`
261
275
 
262
276
  ### `/sc knowledge`
package/DECISIONS.md CHANGED
@@ -350,3 +350,23 @@ Date: 2026-07-22
350
350
  **Consequences** — Normal queries stay fast while content remains the final verifier. State and intake share one canonical fingerprint implementation. Cost: metadata-only touches may trigger one full verification, and scanner changes require a cache-schema bump.
351
351
 
352
352
  **Alternatives considered** — Hash every source on every query: maximally simple but O(project) on the hot path. Timestamp-only cache validity: faster but allows derived metadata to masquerade as truth.
353
+
354
+ ---
355
+
356
+ ## ADR-025 — v1 compatibility cycle sunset target
357
+ Status: accepted
358
+ Date: 2026-08-20
359
+
360
+ **Context** — Since the 2.0.0 release (2026-07-21), ScrumRun carries a v1 compatibility surface: generated command aliases (`sc-backlog`, `sc-know`, `sc-sprint`, etc.), v1 layout files (`golden-rules.md`, `backlog.md`, `knowledge.md`, `goals/main/`), the v1 `runGuardrails`/`renderState` builders, and the `migrateLegacyProject` pre-`.scrumrun` layout migration. SPEC §12 states these "may remain for one compatibility cycle" but never defines when that cycle ends. This creates ongoing maintenance cost (the `bin/scrumrun.js` monolith carries ~1200 lines of v1 code), ambiguity about which files are canonical, and a wider attack surface for secret/path bugs.
361
+
362
+ **Decision** — Target the next major version (3.0.0) as the hard sunset for v1 compatibility. Until then:
363
+
364
+ 1. `doctor` emits a `warning` finding (`V1_COMPAT_IN_USE`) when it detects v1 aliases, v1 layout files, or the legacy layout migration trigger, so maintainers can track remaining v1 usage.
365
+ 2. The CHANGELOG and README gain a deprecation notice pointing to `npx scrumrun@latest update --migrate`.
366
+ 3. No new v1 features are added; v1 code is frozen and only receives security fixes.
367
+
368
+ At 3.0.0: v1 aliases are removed, v1 layout files are no longer auto-created, `migrateLegacyProject` and the v1 builders are deleted, and the bin file is refactored to route only `/sc` commands.
369
+
370
+ **Consequences** — Clear end date for v1 code; maintainers can plan the bin refactor and deletion with confidence. Users on v1 have a full major-version cycle to migrate using the existing idempotent, reversible `migrate --to 2 --apply` tooling. Cost: the v1 surface remains in the tarball until 3.0.0, so the bin monolith and v1 tests are not yet removable.
371
+
372
+ **Alternatives considered** — Keep v1 forever: maintenance burden grows and the canonical surface stays diluted. Hard-cut immediately (2.6.0): breaks users who have not migrated and gives no warning. Remove v1 code now behind a flag: adds complexity to an already large bin file without a clear deadline.
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.5.1` · **Method target:** `2.0.0` · **Runtime:** Node.js `>=22.13.0` · **License:** MIT
7
+ **Package:** `2.6.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
 
@@ -65,13 +65,15 @@ RECEIVED → CONTEXTUALIZING → POLICY → RISK → CLASSIFICATION
65
65
  → PLANNING → AWAITING_APPROVAL
66
66
  ```
67
67
 
68
+ The agent may assert the classification (`--type fix|task|feature|docs|discovery`) and attach a short technical preview (`--preview "…"`), rendered with color in the terminal before any Task exists. Nothing canonical is persisted before approval.
69
+
68
70
  Explicit approval atomically creates a Task and a Run. Execution then follows:
69
71
 
70
72
  ```text
71
73
  EXECUTING → VALIDATING → LEARNING → COMPLETED | FAILED | BLOCKED
72
74
  ```
73
75
 
74
- Nothing canonical is persisted before approval. Failed retries remain available as separate Runs.
76
+ 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.
75
77
 
76
78
  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.
77
79
 
@@ -114,6 +116,21 @@ The fast graph/search layer is `.scrumrun/.cache/semantic-index.sqlite`. It is i
114
116
 
115
117
  `map.md` is shown only when its source fingerprint matches the current semantic index. A fresh placeholder or stale map is rejected with an explicit rebuild instruction instead of being presented as project truth.
116
118
 
119
+ ## Project briefing
120
+
121
+ `state.md` is the **briefing**: a bounded summary an agent reads first, before touching anything else.
122
+
123
+ ```text
124
+ ## Now active work, with each Task's assignee
125
+ ## Recent last completions + their technical summaries
126
+ ## Open Decisions titles only, with pointers
127
+ ## Active Memory confirmed facts / insights / decisions
128
+ ## Next Up the backlog queue, oldest first
129
+ ## Where to look pointers to guardrails, tasks, runs, semantic search
130
+ ```
131
+
132
+ It is progressive disclosure: the briefing is enough for most work; the agent follows its pointers and goes deeper only when the briefing lacks what it needs. The intake context package embeds the same briefing. Each agent declares an identity (`SCRUMRUN_AGENT` or `Agent Identity` in `config.md`), recorded as the Task `assignee` and Run `actor`, so `--shared` teams can see who owns each active Task from the briefing alone.
133
+
117
134
  ## Migrating an ongoing v1 project
118
135
 
119
136
  Update the client integrations and automatically run a read-only migration preflight:
@@ -208,9 +225,16 @@ Rules:
208
225
  npx scrumrun@latest commands
209
226
 
210
227
  # plan without writes, then approve the emitted token
211
- npx scrumrun@latest sc plan intake "Fix pricing rounding"
228
+ npx scrumrun@latest sc plan intake "Fix pricing rounding" --type fix --preview "Rounding moved after tax calc"
212
229
  npx scrumrun@latest sc plan intake --approve <token>
213
230
 
231
+ # record what was done at completion, so the next agent inherits it
232
+ 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"
233
+
234
+ # auto-sequencing: surface and start the next backlog Task
235
+ npx scrumrun@latest sc plan task --next
236
+ npx scrumrun@latest sc plan task --start TASK-009
237
+
214
238
  # authorize and record a material source mutation
215
239
  npx scrumrun@latest sc plan run --authorize-mutation RUN-001 --path src/pricing.ts
216
240
  npx scrumrun@latest sc plan run --record-mutation RUN-001 --permit MUT-... --note "Pricing change recorded"
package/SPEC.md CHANGED
@@ -83,6 +83,8 @@ method: 2.0.0
83
83
 
84
84
  IDs, filenames, kind, status, real ISO dates, and method version must agree. Unknown fields and authored prose are preserved. Duplicate fields, malformed frontmatter, unsafe paths, and symlinked canonical paths are invalid.
85
85
 
86
+ A Task may carry an optional `assignee` scalar recording the agent identity (`SCRUMRUN_AGENT` or `config.md` `Agent Identity`) that owns the work. It is descriptive metadata, never a competing authority: it does not change status transitions or block conformance.
87
+
86
88
  ### 3.2 Stable identifiers
87
89
 
88
90
  | Kind | Identifier |
@@ -202,7 +204,11 @@ Approval atomically creates one Task and its first Run. If either write fails, n
202
204
 
203
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.
204
206
 
205
- 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. 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.
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
+
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.
210
+
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.
206
212
 
207
213
  ## 6. Policy and precedence
208
214
 
@@ -252,6 +258,8 @@ The agreed v1 control-context baseline is 48,000 characters. A fresh lean v2 int
252
258
 
253
259
  `state.md`, `map.md`, context packages, and `.cache/semantic-index.sqlite` are non-authoritative projections. They carry source fingerprints or are treated as stale. `state.md` uses the exact canonical fingerprint bound into intake, plus a projection schema, RFC3339 generation time, and a disposable watch fingerprint. SQLite stores its source and watch fingerprints with an explicit cache schema.
254
260
 
261
+ `state.md` is the **briefing**: a bounded, progressive-disclosure index of active work (with each Task's `assignee`), recent completions (with their technical summaries), open decisions, active memory, and the backlog queue, plus pointers to deeper artifacts. Agents read the briefing first and follow its pointers, going deeper only when the briefing lacks what they need. The briefing is a convenience view, never authority; a canonical artifact the briefing summarizes remains the source of truth.
262
+
255
263
  Freshness checks use a two-tier strategy: unchanged path/stat identity proves that no source read or reparse is needed; changed metadata triggers a complete canonical/source content fingerprint before staleness is asserted. Cache metadata may optimize verification but never supplies project truth. A cache-schema mismatch forces one disposable rebuild. Deleting `.cache/` must not remove authored knowledge, and rebuilding it from unchanged sources must yield equivalent query results.
256
264
 
257
265
  The index must never scan or store `vault.local.md`. Source scanning is bounded, skips dependencies/build output and symlinks, and uses replaceable language adapters.
package/bin/scrumrun.js CHANGED
@@ -1,5 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ (function checkNodeVersion() {
4
+ const [major, minor] = process.versions.node.split(".").map(Number);
5
+ if (major < 22 || (major === 22 && minor < 13)) {
6
+ process.stderr.write(
7
+ `SR-E-501 Unsupported Node.js runtime: ${process.versions.node}. `
8
+ + `ScrumRun requires Node.js >=22.13.0 for native SQLite. `
9
+ + `Upgrade Node and retry.\n`
10
+ );
11
+ process.exit(1);
12
+ }
13
+ })();
14
+
3
15
  const crypto = require("crypto");
4
16
  const fs = require("fs");
5
17
  const path = require("path");
@@ -19,7 +31,7 @@ const { ARTIFACT_TYPES, ArtifactRepository } = require(path.join(root, "lib", "v
19
31
  const { aliases: COMMAND_ALIASES, resolveAlias, resolveRoute } = require(path.join(root, "lib", "commands", "manifest"));
20
32
  const { renderCommandHelp, renderCompatibilityPrompt, renderRootPrompt } = require(path.join(root, "lib", "commands", "render"));
21
33
  const { planRequest } = require(path.join(root, "lib", "runtime", "request-engine"));
22
- const { approveRequest, refreshState, retryTask, transitionRun } = require(path.join(root, "lib", "runtime", "orchestrator"));
34
+ const { approveRequest, nextBacklogTask, refreshState, retryTask, startBacklogTask, transitionRun } = require(path.join(root, "lib", "runtime", "orchestrator"));
23
35
  const { authorizeMutation, recordMutation, satisfyGuardrail } = require(path.join(root, "lib", "runtime", "mutation-gateway"));
24
36
  const { recordArtifactReview } = require(path.join(root, "lib", "runtime", "review-service"));
25
37
  const { createMemory, listMemory, showMemory, transitionMemory } = require(path.join(root, "lib", "memory", "service"));
@@ -331,6 +343,14 @@ function migrationPreflightOnUpdate({ apply = false } = {}) {
331
343
  function updateInstallation(target, { migrate = false } = {}) {
332
344
  const migration = migrationPreflightOnUpdate({ apply: migrate });
333
345
  install(target, true, { compatibility: true });
346
+ if (migrate && v2Project()) {
347
+ try {
348
+ refreshState(path.join(process.cwd(), ".scrumrun"));
349
+ } catch {
350
+ // Best-effort: regenerating the disposable briefing must never block an integration update.
351
+ // Conformance reports stale or unsafe state separately.
352
+ }
353
+ }
334
354
  return migration;
335
355
  }
336
356
 
@@ -1212,15 +1232,17 @@ function runTransitionOptions(args) {
1212
1232
  let note = null;
1213
1233
  let actor = "agent";
1214
1234
  let occurredAt = null;
1235
+ let summary = null;
1215
1236
  for (let index = 2; index < args.length; index++) {
1216
1237
  const token = args[index];
1217
1238
  const value = args[index + 1] && !args[index + 1].startsWith("--") ? args[index + 1] : null;
1218
- if (token === "--note" || token === "--actor" || token === "--at" || token === "--evidence" || evidenceFlags.has(token)) {
1239
+ if (token === "--note" || token === "--actor" || token === "--at" || token === "--evidence" || token === "--summary" || evidenceFlags.has(token)) {
1219
1240
  if (!value) throw new Error(`${token} requires a value.`);
1220
1241
  index++;
1221
1242
  if (token === "--note") note = value;
1222
1243
  else if (token === "--actor") actor = value;
1223
1244
  else if (token === "--at") occurredAt = value;
1245
+ else if (token === "--summary") summary = value;
1224
1246
  else if (token === "--evidence") {
1225
1247
  const separator = value.indexOf(":");
1226
1248
  const kind = separator > 0 ? value.slice(0, separator) : "note";
@@ -1235,7 +1257,7 @@ function runTransitionOptions(args) {
1235
1257
  if (token.startsWith("--")) throw new Error(`Unknown Run evidence option: ${token}`);
1236
1258
  noteParts.push(token);
1237
1259
  }
1238
- return { note: note || noteParts.join(" ").trim() || null, evidence, actor, occurredAt };
1260
+ return { note: note || noteParts.join(" ").trim() || null, evidence, actor, occurredAt, summary };
1239
1261
  }
1240
1262
 
1241
1263
  function removeOptionPairs(args, names) {
@@ -1349,15 +1371,19 @@ function executeRootRoute(route) {
1349
1371
  console.log(`${result.status === "already-approved" ? "Already approved" : "Approved"}: ${result.task.id} → ${result.run.id}`);
1350
1372
  return;
1351
1373
  }
1352
- const request = routeArgs[0] === "--request" ? routeArgs.slice(1).join(" ") : routeArgs.join(" ");
1353
- const plan = planRequest(process.cwd(), request);
1374
+ const typeOverride = optionValue(routeArgs, "--type");
1375
+ const preview = optionValue(routeArgs, "--preview");
1376
+ const cleanArgs = removeOptionPairs(routeArgs, ["--type", "--preview"]);
1377
+ const startIndex = cleanArgs[0] === "--request" ? 1 : 0;
1378
+ const request = cleanArgs.slice(startIndex).filter((arg) => !arg.startsWith("--")).join(" ").trim();
1379
+ const plan = planRequest(process.cwd(), request, { typeOverride, preview });
1354
1380
  if (routeArgs.includes("--json")) {
1355
1381
  console.log(JSON.stringify(plan, null, 2));
1356
1382
  return;
1357
1383
  }
1358
1384
  const { canRenderPretty, renderIntake, renderIntakePlain } = require(path.join(root, "lib", "commands", "pretty-intake"));
1359
1385
  const forcePlain = routeArgs.includes("--plain");
1360
- if (!forcePlain && canRenderPretty()) {
1386
+ if (!forcePlain && process.stdout.isTTY && canRenderPretty()) {
1361
1387
  console.log(renderIntake(plan));
1362
1388
  } else {
1363
1389
  console.log(renderIntakePlain(plan));
@@ -1370,6 +1396,33 @@ function executeRootRoute(route) {
1370
1396
  console.log(`Created retry ${result.run.id} for ${result.task.id} (attempt ${result.run.attempt}).`);
1371
1397
  return;
1372
1398
  }
1399
+ if (noun === "plan" && subject === "task" && routeArgs[0] === "--next") {
1400
+ const repository = new ArtifactRepository(projectFile());
1401
+ const next = nextBacklogTask(repository);
1402
+ if (!next) {
1403
+ console.log("No backlog Tasks to start.");
1404
+ return;
1405
+ }
1406
+ console.log(`Next backlog Task: ${next.id} — ${next.title || next.id}`);
1407
+ console.log(`Start it with: scrumrun sc plan task --start ${next.id}`);
1408
+ return;
1409
+ }
1410
+ if (noun === "plan" && subject === "task" && routeArgs[0] === "--start") {
1411
+ let target = routeArgs[1];
1412
+ if (!target) {
1413
+ const repository = new ArtifactRepository(projectFile());
1414
+ const next = nextBacklogTask(repository);
1415
+ if (!next) {
1416
+ console.error("No backlog Tasks to start.");
1417
+ process.exitCode = 1;
1418
+ return;
1419
+ }
1420
+ target = next.id;
1421
+ }
1422
+ const result = startBacklogTask(process.cwd(), target);
1423
+ console.log(`Started ${result.task.id} (${result.run.id}) assigned to ${result.task.assignee || "agent"}.`);
1424
+ return;
1425
+ }
1373
1426
  if (noun === "plan" && subject === "run" && routeArgs[0] === "--render") {
1374
1427
  const { renderRunFromDisk } = require(path.join(root, "lib", "commands", "run-render"));
1375
1428
  const runId = routeArgs[1];
@@ -6,7 +6,7 @@ const nouns = Object.freeze({
6
6
  plan: {
7
7
  description: "turn intent into Features, Tasks, Sprints, and Runs",
8
8
  subjects: {
9
- task: ["--add [--type fix] [--status backlog]", "--list", "--show", "--run", "--audit", "--cancel", "--retry"],
9
+ task: ["--add [--type fix] [--status backlog]", "--list", "--show", "--run", "--audit", "--cancel", "--retry", "--next", "--start [TASK-NNN]"],
10
10
  sprint: ["--add", "--list", "--show", "--start", "--complete", "--block"],
11
11
  feature: ["--add", "--list", "--show", "--activate", "--complete"],
12
12
  run: [
@@ -20,12 +20,12 @@ const nouns = Object.freeze({
20
20
  "--satisfy-guardrail <RUN-NNN> --guardrail <GR-NNN> [--note] [--evidence] [--review] [--migration] [--actor]",
21
21
  "--validate [--note] [--evidence] [--command] [--test] [--file] [--review] [--actor] [--at]",
22
22
  "--learn [--note] [--evidence] [--decision] [--insight] [--file] [--actor] [--at]",
23
- "--complete [--note] [--evidence] [--review] [--test] [--file] [--actor] [--at]",
23
+ "--complete [--note] [--evidence] [--review] [--test] [--file] [--actor] [--at] [--summary \"technical recap for future tasks\"]",
24
24
  "--resume [--note] [--evidence] [--risk] [--actor] [--at]",
25
25
  "--fail [--note] [--evidence] [--risk] [--test] [--actor] [--at]",
26
26
  "--block [--note] [--evidence] [--risk] [--actor] [--at]"
27
27
  ],
28
- intake: ["<request>", "--request", "--approve", "--plain", "--json"],
28
+ intake: ["<request>", "--request", "--approve", "--plain", "--json", "--type <fix|task|feature|docs|discovery>", "--preview \"technical summary\""],
29
29
  challenge: ["<question>"]
30
30
  }
31
31
  },
@@ -211,6 +211,9 @@ function renderIntake(plan) {
211
211
  const classificationText = classificationLabel(plan.classification);
212
212
  lines.push(...fieldLine("Classification", classificationText, width, ACID_FG));
213
213
  lines.push(...fieldLine("Why", plan.classification.reason, width, FG.gray));
214
+ if (plan.preview) {
215
+ lines.push(...fieldLine("Preview", plan.preview, width, FG.cyan));
216
+ }
214
217
  const riskColor = RISK_TONE[plan.risk.level] || FG.white;
215
218
  lines.push(...fieldLine("Risk", `${plan.risk.level} · ${plan.risk.reasons.join("; ")}`, width, riskColor));
216
219
 
@@ -258,6 +261,7 @@ function renderIntakePlain(plan) {
258
261
  lines.push("# ScrumRun Intake");
259
262
  lines.push(`State: ${plan.state}`);
260
263
  lines.push(`Classification: ${plan.classification.type} (${plan.classification.reason})`);
264
+ if (plan.preview) lines.push(`Preview: ${plan.preview}`);
261
265
  lines.push(`Risk: ${plan.risk.level} — ${plan.risk.reasons.join("; ")}`);
262
266
  lines.push(`Policy: ${plan.policy.status} (${plan.policy.checked.length} checked; ${plan.policy.deferred.length} deferred)`);
263
267
  for (const violation of plan.policy.violations || []) lines.push(`BLOCKED: ${violation}`);
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+
3
+ const { ARTIFACT_TYPES } = require("../v2/schema");
4
+ const { ArtifactRepository } = require("../v2/artifacts");
5
+ const { artifactSnapshot, canonicalFingerprint, canonicalWatchSnapshot } = require("./canonical-snapshot");
6
+ const { extractTechnicalSummary } = require("./run-ledger");
7
+
8
+ const TERMINAL = new Set(["completed", "failed", "cancelled", "resolved", "rejected", "deprecated", "invalidated", "archived", "passed"]);
9
+
10
+ function briefLine(record, kind) {
11
+ const marker = record.status === "completed" || record.status === "passed" || record.status === "resolved" ? "\u2713" : record.status === "failed" ? "\u2717" : "\u2022";
12
+ return `- ${record.id} ${marker} ${record.title || record.id}`;
13
+ }
14
+
15
+ function generateBriefing(scrumDir, repository) {
16
+ const repo = repository || new ArtifactRepository(scrumDir);
17
+ const snapshot = artifactSnapshot(scrumDir);
18
+ const sourceFingerprint = canonicalFingerprint(scrumDir, snapshot.hashes);
19
+ const watch = canonicalWatchSnapshot(scrumDir);
20
+
21
+ const activeWork = ["feature", "task", "sprint", "run", "review"]
22
+ .flatMap((kind) => (snapshot.records[kind] || [])
23
+ .filter((r) => r.id && !TERMINAL.has(r.status))
24
+ .map((r) => r.assignee && r.assignee !== "agent"
25
+ ? `- ${r.id} | ${r.status} | ${r.assignee} | ${r.title || r.id}`
26
+ : `- ${r.id} | ${r.status} | ${r.title || r.id}`))
27
+ .slice(0, 5);
28
+
29
+ const completedRuns = (snapshot.records.run || [])
30
+ .filter((r) => r.status === "completed")
31
+ .slice(-5)
32
+ .reverse();
33
+
34
+ const recentLines = [];
35
+ for (const run of completedRuns.slice(0, 3)) {
36
+ const artifact = repo.read("run", run.id);
37
+ const summary = artifact ? extractTechnicalSummary(artifact.body) : null;
38
+ const text = summary ? summary.slice(0, 120) : (run.title || run.id);
39
+ recentLines.push(`- ${run.id} \u2713 ${text}`);
40
+ }
41
+
42
+ const openDecisions = (snapshot.records.decision || [])
43
+ .filter((r) => r.status === "open")
44
+ .slice(-5)
45
+ .map((r) => `- ${r.id} \u2014 ${r.title || r.id}`);
46
+
47
+ const activeMemory = [
48
+ ...(snapshot.records.knowledge || []).filter((r) => r.status === "approved").slice(-3),
49
+ ...(snapshot.records.insight || []).filter((r) => r.status === "confirmed").slice(-3),
50
+ ...(snapshot.records.decision || []).filter((r) => r.status === "resolved").slice(-3)
51
+ ].map((r) => `- ${r.id} \u2014 ${r.title || r.id} (${r.status})`);
52
+
53
+ const backlogTasks = (snapshot.records.task || [])
54
+ .filter((r) => r.status === "backlog")
55
+ .slice(0, 3)
56
+ .map((r) => `- ${r.id} \u2014 ${r.title || r.id}`);
57
+
58
+ const counts = {};
59
+ for (const kind of Object.keys(ARTIFACT_TYPES)) {
60
+ counts[kind] = (snapshot.records[kind] || []).length;
61
+ }
62
+
63
+ const content = `# ScrumRun Briefing
64
+
65
+ Projection schema: 2
66
+ Generated: ${new Date().toISOString()}
67
+ Source fingerprint: ${sourceFingerprint}
68
+ Watch fingerprint: ${watch.fingerprint}
69
+ Authority: none; rebuild from canonical artifacts.
70
+ Progressive disclosure: read this first; go deeper only if the briefing lacks what you need.
71
+
72
+ ## Now
73
+
74
+ ${activeWork.length ? activeWork.join("\n") : "- No active canonical work."}
75
+
76
+ ## Recent
77
+
78
+ ${recentLines.length ? recentLines.join("\n") : "- No completed Runs."}
79
+
80
+ ## Open Decisions
81
+
82
+ ${openDecisions.length ? openDecisions.join("\n") : "- None."}
83
+
84
+ ## Active Memory
85
+
86
+ ${activeMemory.length ? activeMemory.slice(0, 5).join("\n") : "- No active canonical memory."}
87
+
88
+ ## Next Up
89
+
90
+ ${backlogTasks.length ? backlogTasks.join("\n") : "- No backlog Tasks."}
91
+
92
+ ## Where to look
93
+
94
+ - Guardrails: .scrumrun/guardrails.md
95
+ - Tasks: .scrumrun/tasks/ (${counts.task || 0} total)
96
+ - Runs: .scrumrun/runs/ (${counts.run || 0} total)
97
+ - Semantic: sc knowledge study "<topic>" for deep queries
98
+ - Full state: rebuild with sc config doctor --recover
99
+ `;
100
+
101
+ return { content, sourceFingerprint, watchFingerprint: watch.fingerprint, sourceFiles: watch.files };
102
+ }
103
+
104
+ module.exports = { TERMINAL, generateBriefing };
@@ -35,7 +35,8 @@ function artifactSnapshot(scrumDir) {
35
35
  title: ((artifact.body || "").match(/^# ([^\r\n]+)/m) || [])[1] || artifact.record.id,
36
36
  task: artifact.record.task || null,
37
37
  sprint: artifact.record.sprint || null,
38
- feature: artifact.record.feature || null
38
+ feature: artifact.record.feature || null,
39
+ assignee: artifact.record.assignee || null
39
40
  } : { id: path.basename(artifact.file, ".md"), status: "invalid", errors: artifact.errors };
40
41
  });
41
42
  }
@@ -6,6 +6,9 @@ const { readProjectModel } = require("../v2/project-store");
6
6
  const { containsSecret } = require("../security/secrets");
7
7
  const { parseGuardrails } = require("./policy-engine");
8
8
  const { artifactSnapshot, canonicalFingerprint } = require("./canonical-snapshot");
9
+ const { ArtifactRepository } = require("../v2/artifacts");
10
+ const { extractTechnicalSummary } = require("./run-ledger");
11
+ const { generateBriefing } = require("./briefing");
9
12
 
10
13
  const EXCLUDED_ROOT_ENTRIES = new Set([".git", ".scrumrun", "node_modules", "dist", "build", "coverage"]);
11
14
 
@@ -43,6 +46,18 @@ function projectScan(projectRoot) {
43
46
 
44
47
  const guardrailRecords = parseGuardrails;
45
48
 
49
+ function recentTechnicalSummaries(scrumDir, runRecords, limit = 5) {
50
+ const repository = new ArtifactRepository(scrumDir);
51
+ const summaries = [];
52
+ for (const run of (runRecords || []).filter((r) => r.status === "completed").slice(0, limit)) {
53
+ const artifact = repository.read("run", run.id);
54
+ if (!artifact || !artifact.body) continue;
55
+ const summary = extractTechnicalSummary(artifact.body);
56
+ if (summary) summaries.push({ run: run.id, summary: capped(summary, 500) });
57
+ }
58
+ return summaries;
59
+ }
60
+
46
61
  function contextFingerprint(scrumDir, artifactHashes) {
47
62
  return canonicalFingerprint(scrumDir, artifactHashes);
48
63
  }
@@ -66,6 +81,7 @@ function buildContextPackage(projectRoot, request) {
66
81
  const artifacts = model.source === "canonical-v2" ? artifactSnapshot(scrumDir) : { records: {}, hashes: [], warnings: [] };
67
82
  const openDecisions = (artifacts.records.decision || []).filter((record) => record.status === "open").slice(-10);
68
83
  const recentRuns = (artifacts.records.run || []).slice(-10).reverse();
84
+ const recentSummaries = recentTechnicalSummaries(scrumDir, recentRuns);
69
85
  const activeWork = ["feature", "task", "sprint", "run"].flatMap((kind) =>
70
86
  (artifacts.records[kind] || []).filter((record) => !["completed", "failed", "cancelled"].includes(record.status))
71
87
  ).slice(0, 50);
@@ -83,11 +99,12 @@ function buildContextPackage(projectRoot, request) {
83
99
  layout: model.layout,
84
100
  source: model.source,
85
101
  fingerprint,
102
+ briefing: capped(generateBriefing(scrumDir).content, 2000),
86
103
  projectScan: projectScan(projectRoot),
87
104
  guardrails: guardrailRecords(guardrailsContent).filter((record) => record.status === "active").slice(0, 100),
88
105
  config,
89
106
  project: project || "[missing]",
90
- history: { recentRuns },
107
+ history: { recentRuns, recentSummaries },
91
108
  decisions: { open: openDecisions },
92
109
  activeWork,
93
110
  graph: {