scrumrun 2.5.1 → 2.6.1
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 +21 -0
- package/CORE.md +18 -4
- package/DECISIONS.md +20 -0
- package/README.md +27 -3
- package/SPEC.md +9 -1
- package/bin/scrumrun.js +64 -11
- package/lib/commands/manifest.js +3 -3
- package/lib/commands/pretty-intake.js +4 -0
- package/lib/runtime/briefing.js +104 -0
- package/lib/runtime/canonical-snapshot.js +2 -1
- package/lib/runtime/context.js +18 -1
- package/lib/runtime/orchestrator.js +92 -18
- package/lib/runtime/policy-engine.js +22 -0
- package/lib/runtime/request-engine.js +22 -2
- package/lib/runtime/run-ledger.js +16 -0
- package/lib/v2/conformance.js +7 -0
- package/package.json +13 -3
- package/scripts/sync-readme-version.js +20 -0
- package/templates/project/.scrumrun/config.md +3 -0
- package/templates/project/AGENTS.md +6 -4
- package/templates/project-lean/AGENTS.md +3 -3
- package/templates/shared/skills/scrumrun/SKILL.md +17 -11
- package/types/index.d.ts +730 -0
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.
|
|
7
|
+
**Package:** `2.6.1` · **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
|
-
|
|
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
|
-
|
|
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"));
|
|
@@ -40,7 +52,7 @@ Usage:
|
|
|
40
52
|
scrumrun sc
|
|
41
53
|
scrumrun sc <noun> <subject> <action> [args]
|
|
42
54
|
scrumrun install [all|codex|opencode|claude] [--force]
|
|
43
|
-
scrumrun update [all|codex|opencode|claude] [--migrate]
|
|
55
|
+
scrumrun update [all|codex|opencode|claude] [--no-migrate]
|
|
44
56
|
scrumrun init [--local|--shared] [--lean] [--no-agent-hint] [--force]
|
|
45
57
|
scrumrun status
|
|
46
58
|
scrumrun core [--path|--prompt]
|
|
@@ -287,7 +299,7 @@ function migrationPreflightOnUpdate({ apply = false } = {}) {
|
|
|
287
299
|
return { status: "blocked" };
|
|
288
300
|
}
|
|
289
301
|
if (!apply) {
|
|
290
|
-
console.log("\nThe project remains unchanged.
|
|
302
|
+
console.log("\nThe project remains unchanged. Re-run update to apply the verified plan, or keep opt-out with: npx scrumrun@latest update --no-migrate");
|
|
291
303
|
return { status: "ready" };
|
|
292
304
|
}
|
|
293
305
|
const result = applyRunLedgerMigration(process.cwd());
|
|
@@ -313,7 +325,7 @@ function migrationPreflightOnUpdate({ apply = false } = {}) {
|
|
|
313
325
|
return { status: "blocked" };
|
|
314
326
|
}
|
|
315
327
|
if (!apply) {
|
|
316
|
-
console.log("\nThe project remains unchanged.
|
|
328
|
+
console.log("\nThe project remains unchanged. Re-run update to apply the verified plan, or keep opt-out with: npx scrumrun@latest update --no-migrate");
|
|
317
329
|
return { status: "ready" };
|
|
318
330
|
}
|
|
319
331
|
const result = applyMigration(process.cwd(), { plan: preview.plan });
|
|
@@ -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
|
|
1353
|
-
const
|
|
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];
|
|
@@ -1517,7 +1570,7 @@ function executeRootRoute(route) {
|
|
|
1517
1570
|
}
|
|
1518
1571
|
if (noun === "config" && subject === "update") {
|
|
1519
1572
|
const target = ["all", "codex", "opencode", "claude"].includes(routeArgs[0]) ? routeArgs[0] : "all";
|
|
1520
|
-
return updateInstallation(target, { migrate: routeArgs.includes("--migrate") });
|
|
1573
|
+
return updateInstallation(target, { migrate: !routeArgs.includes("--no-migrate") });
|
|
1521
1574
|
}
|
|
1522
1575
|
if (noun === "config" && subject === "init") {
|
|
1523
1576
|
const localMode = routeArgs.includes("--local");
|
|
@@ -2442,7 +2495,7 @@ if (!command || command === "--help" || command === "-h") {
|
|
|
2442
2495
|
console.log(`ScrumRun ${version}`);
|
|
2443
2496
|
} else if (command === "install" || command === "update") {
|
|
2444
2497
|
const target = ["all", "codex", "opencode", "claude"].includes(args[1]) ? args[1] : "all";
|
|
2445
|
-
if (command === "update") updateInstallation(target, { migrate: args.includes("--migrate") });
|
|
2498
|
+
if (command === "update") updateInstallation(target, { migrate: !args.includes("--no-migrate") });
|
|
2446
2499
|
else install(target, true, { compatibility: false });
|
|
2447
2500
|
} else if (command === "sc") {
|
|
2448
2501
|
runRoot(args.slice(1));
|
package/lib/commands/manifest.js
CHANGED
|
@@ -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
|
}
|
package/lib/runtime/context.js
CHANGED
|
@@ -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: {
|