scrumrun 2.7.5 → 2.7.7

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,18 @@ All notable changes follow Semantic Versioning.
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 2.7.7 - 2026-08-24
8
+
9
+ ### Added
10
+
11
+ - **Error index.** `sc knowledge errors --show` renders `.scrumrun/errors.md` — a derived index grouping `type: fix` Tasks into Open and Resolved (with branch), so reported bugs have a dedicated project log. `sc plan task --list --type <type>` now filters Tasks by type (e.g. `--type fix`).
12
+
13
+ ## 2.7.6 - 2026-08-24
14
+
15
+ ### Added
16
+
17
+ - **Branch tracking on artifacts.** Task, Feature, Sprint, and Run artifacts now record the active git branch in a `branch` frontmatter field at creation time (`null` outside a git repo). `sc plan task --list` and the generated `map.md` show the branch, giving a human-readable "which branch did this work originate from" reference index to complement the exact commit SHA already captured in each Run's workspace baseline.
18
+
7
19
  ## 2.7.5 - 2026-08-22
8
20
 
9
21
  ### Added
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.5` · **Method target:** `2.0.0` · **Runtime:** Node.js `>=22.13.0` · **License:** MIT
7
+ **Package:** `2.7.7` · **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
 
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, refreshState, retryTask, startBacklogTask, transitionRun } = require(path.join(root, "lib", "runtime", "orchestrator"));
34
+ const { addPlanArtifact, approveRequest, 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"));
@@ -1378,6 +1378,11 @@ function runSemanticContext(subject, args) {
1378
1378
  console.log(JSON.stringify(result, null, 2));
1379
1379
  return;
1380
1380
  }
1381
+ if (subject === "errors") {
1382
+ const report = refreshErrors(path.join(process.cwd(), ".scrumrun"));
1383
+ console.log(report.content);
1384
+ return;
1385
+ }
1381
1386
  if (subject === "map") {
1382
1387
  if (action === "--build") {
1383
1388
  const built = rebuildIndex(process.cwd());
@@ -1537,7 +1542,13 @@ function executeRootRoute(route) {
1537
1542
  if (noun === "plan" && ["task", "run", "feature", "sprint"].includes(subject) && ["--list", "--show"].includes(routeArgs[0])) {
1538
1543
  const repository = new ArtifactRepository(projectFile());
1539
1544
  if (routeArgs[0] === "--list") {
1540
- const artifacts = repository.list(subject).map((artifact) => `${artifact.record.id} | ${artifact.record.status} | ${((artifact.body || "").match(/^# ([^\r\n]+)/m) || [])[1] || artifact.record.id}`);
1545
+ const typeFilter = optionValue(routeArgs, "--type");
1546
+ const listed = repository.list(subject).filter((artifact) => !typeFilter || artifact.record.type === typeFilter);
1547
+ const artifacts = listed.map((artifact) => {
1548
+ const title = ((artifact.body || "").match(/^# ([^\r\n]+)/m) || [])[1] || artifact.record.id;
1549
+ const branch = artifact.record.branch ? ` [${artifact.record.branch}]` : "";
1550
+ return `${artifact.record.id} | ${artifact.record.status} | ${title}${branch}`;
1551
+ });
1541
1552
  console.log(artifacts.length ? artifacts.join("\n") : `No ${subject} artifacts.`);
1542
1553
  } else {
1543
1554
  const artifact = repository.read(subject, routeArgs[1]);
@@ -1597,7 +1608,7 @@ function executeRootRoute(route) {
1597
1608
  }
1598
1609
  }
1599
1610
  if (noun === "knowledge" && ["fact", "decision", "insight", "dossier"].includes(subject) && v2Project()) return runV2Memory(subject, routeArgs);
1600
- if (noun === "knowledge" && ["context", "map", "study"].includes(subject) && v2Project()) return runSemanticContext(subject, routeArgs);
1611
+ if (noun === "knowledge" && ["context", "map", "study", "errors"].includes(subject) && v2Project()) return runSemanticContext(subject, routeArgs);
1601
1612
  if (noun === "knowledge" && subject === "decision") return runDecisions(routeArgs);
1602
1613
  if (noun === "knowledge" && subject === "fact") return runKnow(routeArgs);
1603
1614
  if (noun === "knowledge" && subject === "vault") return runVault(routeArgs);
@@ -38,6 +38,7 @@ const nouns = Object.freeze({
38
38
  dossier: ["--add [--title] [--content] [--evidence] [--relation] [--subject] [--source] [--source-id] [--valid-from] [--valid-until] [--review-trigger]", "--list [--include-inactive]", "--show", "--refresh [--evidence] [--note]", "--stale [--note]", "--deprecate [--note]", "--archive [--note]"],
39
39
  context: ["--build", "--update", "--show", "--clear"],
40
40
  map: ["--build", "--show"],
41
+ errors: ["--show"],
41
42
  study: ["<focus>"],
42
43
  vault: ["--add", "--list", "--show", "--remove", "--path"]
43
44
  }
@@ -12,7 +12,7 @@ const { containsSecret } = require("../security/secrets");
12
12
  const { canonicalWatchSnapshot, fileWatchSnapshot } = require("../runtime/canonical-snapshot");
13
13
 
14
14
  const CACHE_RELATIVE = path.join(".cache", "semantic-index.sqlite");
15
- const INDEX_SCHEMA_VERSION = 4;
15
+ const INDEX_SCHEMA_VERSION = 5;
16
16
  const INACTIVE = new Set(["rejected", "invalidated", "deprecated", "archived"]);
17
17
  const SEARCH_BACKENDS = new Set(["auto", "fts5", "like"]);
18
18
  const MAX_SEARCH_TOKENS = 32;
@@ -124,6 +124,7 @@ function createSchema(database, { searchBackend = "auto" } = {}) {
124
124
  path TEXT NOT NULL UNIQUE,
125
125
  hash TEXT NOT NULL,
126
126
  content TEXT NOT NULL,
127
+ branch TEXT,
127
128
  valid_from TEXT,
128
129
  valid_until TEXT,
129
130
  last_verified_commit TEXT,
@@ -380,7 +381,7 @@ function rebuildIndex(projectRoot, { searchBackend = "auto" } = {}) {
380
381
  try {
381
382
  database = new DatabaseSync(temp);
382
383
  selectedSearchBackend = createSchema(database, { searchBackend });
383
- const insertArtifact = database.prepare("INSERT INTO artifacts(id, kind, status, title, path, hash, content, valid_from, valid_until, last_verified_commit, review_trigger, search_text, active) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
384
+ const insertArtifact = database.prepare("INSERT INTO artifacts(id, kind, status, title, path, hash, content, branch, valid_from, valid_until, last_verified_commit, review_trigger, search_text, active) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
384
385
  const insertFts = selectedSearchBackend === "fts5"
385
386
  ? database.prepare("INSERT INTO artifacts_fts(id, title, content) VALUES (?, ?, ?)")
386
387
  : null;
@@ -400,6 +401,7 @@ function rebuildIndex(projectRoot, { searchBackend = "auto" } = {}) {
400
401
  artifact.path,
401
402
  sha256(artifact.content),
402
403
  artifact.content,
404
+ artifact.record.branch || null,
403
405
  artifact.record.valid_from || null,
404
406
  artifact.record.valid_until || null,
405
407
  artifact.record.last_verified_commit || null,
@@ -690,7 +692,7 @@ function graphIndex(projectRoot, { rebuild = true } = {}) {
690
692
  else if (status.stale) throw new Error("Semantic index is missing or stale; rebuild it first.");
691
693
  const database = new DatabaseSync(indexPath(projectRoot), { readOnly: true });
692
694
  try {
693
- const artifacts = database.prepare("SELECT id, kind, status, title, path, 'artifact' AS node_type FROM artifacts ORDER BY id").all();
695
+ const artifacts = database.prepare("SELECT id, kind, status, title, path, branch, 'artifact' AS node_type FROM artifacts ORDER BY id").all();
694
696
  const code = database.prepare("SELECT id, kind, status, name AS title, path, 'code' AS node_type, fingerprint, remapped_from FROM code_nodes ORDER BY id").all();
695
697
  return {
696
698
  nodes: [...artifacts, ...code],
@@ -725,7 +727,10 @@ function writeMap(projectRoot, { nodeLimit = 200, edgeLimit = 400 } = {}) {
725
727
  "",
726
728
  "## Nodes",
727
729
  "",
728
- ...(nodes.length ? nodes.map((node) => `- ${node.id} | ${node.node_type} | ${node.kind} | ${node.status} | ${node.title} | ${node.path}`) : ["- None."]),
730
+ ...(nodes.length ? nodes.map((node) => {
731
+ const branch = node.branch ? ` | branch:${node.branch}` : "";
732
+ return `- ${node.id} | ${node.node_type} | ${node.kind} | ${node.status} | ${node.title} | ${node.path}${branch}`;
733
+ }) : ["- None."]),
729
734
  ...(graph.nodes.length > nodes.length ? [`- … ${graph.nodes.length - nodes.length} more nodes in the semantic index.`] : []),
730
735
  "",
731
736
  "## Relations",
@@ -101,4 +101,40 @@ ${backlogTasks.length ? backlogTasks.join("\n") : "- No backlog Tasks."}
101
101
  return { content, sourceFingerprint, watchFingerprint: watch.fingerprint, sourceFiles: watch.files };
102
102
  }
103
103
 
104
- module.exports = { TERMINAL, generateBriefing };
104
+ function generateErrorsReport(scrumDir, repository) {
105
+ const repo = repository || new ArtifactRepository(scrumDir);
106
+ const snapshot = artifactSnapshot(scrumDir);
107
+ const sourceFingerprint = canonicalFingerprint(scrumDir, snapshot.hashes);
108
+ const fixes = repo.list("task")
109
+ .filter((artifact) => artifact.record && !artifact.errors.length && artifact.record.type === "fix")
110
+ .sort((left, right) => left.record.id.localeCompare(right.record.id));
111
+ const open = fixes.filter((artifact) => artifact.record.status !== "completed");
112
+ const resolved = fixes.filter((artifact) => artifact.record.status === "completed");
113
+ const line = (artifact) => {
114
+ const record = artifact.record;
115
+ const title = ((artifact.body || "").match(/^# ([^\r\n]+)/m) || [])[1] || record.id;
116
+ return `- ${record.id} | ${record.status} | ${title}${record.branch ? ` [${record.branch}]` : ""}`;
117
+ };
118
+ const content = `# ScrumRun Error Index
119
+
120
+ Projection schema: 1
121
+ Generated: ${new Date().toISOString()}
122
+ Source fingerprint: ${sourceFingerprint}
123
+ Authority: none; rebuild from canonical artifacts.
124
+
125
+ ## Open
126
+
127
+ ${open.length ? open.map(line).join("\n") : "- None."}
128
+
129
+ ## Resolved
130
+
131
+ ${resolved.length ? resolved.map(line).join("\n") : "- None."}
132
+
133
+ ## Summary
134
+
135
+ - Fix Tasks: ${fixes.length} (${open.length} open, ${resolved.length} resolved)
136
+ `;
137
+ return { content, sourceFingerprint };
138
+ }
139
+
140
+ module.exports = { TERMINAL, generateBriefing, generateErrorsReport };
@@ -19,9 +19,10 @@ const { agentIdentity, evaluatePolicy } = require("./policy-engine");
19
19
  const { artifactSnapshot, canonicalFingerprint, canonicalWatchSnapshot } = require("./canonical-snapshot");
20
20
  const { decodeApproval } = require("./request-engine");
21
21
  const { containsSecret } = require("../security/secrets");
22
+ const { currentBranch } = require("./workspace-state");
22
23
  const { extractLearningCandidates } = require("../code-intel/learning");
23
24
  const { appendRunEvent, appendTechnicalSummary, createRunBody, instant } = require("./run-ledger");
24
- const { generateBriefing } = require("./briefing");
25
+ const { generateBriefing, generateErrorsReport } = require("./briefing");
25
26
  const { assertCanonicalWrite, policyState, prepareCompletion, publicWorkspace, verifyWorkspaceIntegrity, workspaceState } = require("./mutation-gateway");
26
27
  const { recoverPendingTransactions, runKernelTransaction } = require("../v2/transaction");
27
28
 
@@ -64,6 +65,13 @@ function refreshState(scrumDir) {
64
65
  return projection;
65
66
  }
66
67
 
68
+ function refreshErrors(scrumDir) {
69
+ const repository = new ArtifactRepository(scrumDir);
70
+ const report = generateErrorsReport(scrumDir, repository);
71
+ atomicWrite(path.join(scrumDir, "errors.md"), report.content);
72
+ return report;
73
+ }
74
+
67
75
  function stateIsStale(scrumDir) {
68
76
  const content = fs.existsSync(path.join(scrumDir, "state.md")) ? fs.readFileSync(path.join(scrumDir, "state.md"), "utf8") : "";
69
77
  const stored = (content.match(/^Source fingerprint:\s*([a-f0-9]{64})$/m) || [])[1];
@@ -103,6 +111,7 @@ function approveRequestUnlocked(projectRoot, token, { failurePoint = null, inter
103
111
  const created = date();
104
112
  const enforceablePolicy = policyState(projectRoot);
105
113
  const baseline = publicWorkspace(workspace);
114
+ const branch = currentBranch(projectRoot);
106
115
  const taskId = nextId(repository, "task");
107
116
  const runId = nextId(repository, "run");
108
117
  const task = {
@@ -115,6 +124,7 @@ function approveRequestUnlocked(projectRoot, token, { failurePoint = null, inter
115
124
  method: METHOD_VERSION,
116
125
  feature: null,
117
126
  sprint: null,
127
+ branch,
118
128
  assignee: agentIdentity(scrumDir) || "agent",
119
129
  approval_id: approvalId,
120
130
  context_fingerprint: payload.fingerprint,
@@ -129,6 +139,7 @@ function approveRequestUnlocked(projectRoot, token, { failurePoint = null, inter
129
139
  method: METHOD_VERSION,
130
140
  task: taskId,
131
141
  sprint: null,
142
+ branch,
132
143
  attempt: 1,
133
144
  ledger: 1,
134
145
  guardrails: 1,
@@ -304,6 +315,7 @@ function retryTaskUnlocked(projectRoot, taskId, { note = "Retry explicitly appro
304
315
  method: METHOD_VERSION,
305
316
  task: taskId,
306
317
  sprint: taskArtifact.record.sprint || null,
318
+ branch: currentBranch(projectRoot),
307
319
  attempt: attempts.length ? Math.max(...attempts) + 1 : 1,
308
320
  ledger: 1,
309
321
  guardrails: 1,
@@ -374,6 +386,7 @@ function startBacklogTaskUnlocked(projectRoot, taskId, { note = "Backlog Task st
374
386
  method: METHOD_VERSION,
375
387
  task: taskId,
376
388
  sprint: taskArtifact.record.sprint || null,
389
+ branch: currentBranch(projectRoot),
377
390
  attempt: 1,
378
391
  ledger: 1,
379
392
  guardrails: 1,
@@ -504,7 +517,8 @@ function addPlanArtifactUnlocked(projectRoot, kind, label, { type = null, status
504
517
  status: chosenStatus,
505
518
  created,
506
519
  updated: created,
507
- method: METHOD_VERSION
520
+ method: METHOD_VERSION,
521
+ branch: currentBranch(projectRoot)
508
522
  };
509
523
  if (kind === "task") {
510
524
  record.type = chosenType;
@@ -528,4 +542,4 @@ function addPlanArtifact(projectRoot, kind, label, options = {}) {
528
542
  return withArtifactLock(scrumDir, "create", () => addPlanArtifactUnlocked(projectRoot, kind, label, options));
529
543
  }
530
544
 
531
- module.exports = { addPlanArtifact, approveRequest, nextBacklogTask, nextId, refreshState, renderState, retryTask, startBacklogTask, stateFingerprint, stateIsStale, transitionRun };
545
+ module.exports = { addPlanArtifact, approveRequest, nextBacklogTask, nextId, refreshErrors, refreshState, renderState, retryTask, startBacklogTask, stateFingerprint, stateIsStale, transitionRun };
@@ -110,6 +110,16 @@ function workspaceState(projectRoot) {
110
110
  return { ...state, fingerprint: sha256(stable(canonical)) };
111
111
  }
112
112
 
113
+ function currentBranch(projectRoot) {
114
+ try {
115
+ const branch = gitOutput(projectRoot, ["rev-parse", "--abbrev-ref", "HEAD"]).toString("utf8").trim();
116
+ if (!branch || branch === "HEAD") return null;
117
+ return branch;
118
+ } catch {
119
+ return null;
120
+ }
121
+ }
122
+
113
123
  function changesBetween(before, after) {
114
124
  if (!before || !after || before.mode !== after.mode || before.head !== after.head) throw new Error("Workspace baseline mode or Git HEAD changed during the Run.");
115
125
  const left = new Map(before.files.map((item) => [item.path, item]));
@@ -139,6 +149,7 @@ function pathAuthorized(relative, allowed) {
139
149
 
140
150
  module.exports = {
141
151
  changesBetween,
152
+ currentBranch,
142
153
  normalizedRelative,
143
154
  pathAuthorized,
144
155
  stable,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scrumrun",
3
- "version": "2.7.5",
3
+ "version": "2.7.7",
4
4
  "description": "Evidence-driven Agile runtime and semantic project memory for AI coding agents.",
5
5
  "bin": {
6
6
  "scrumrun": "bin/scrumrun.js",
package/types/index.d.ts CHANGED
@@ -392,6 +392,7 @@ export function retryTask(projectRoot: string, taskId: string, options?: { note?
392
392
  export function startBacklogTask(projectRoot: string, taskId: string, options?: { note?: string; failurePoint?: string | null; interruptPoint?: string | null }): { run: ArtifactRecord; task: ArtifactRecord; content: string; recovered?: unknown[] };
393
393
  export function nextBacklogTask(repository: ArtifactRepository): ArtifactRecord | null;
394
394
  export function refreshState(scrumDir: string): StateProjection;
395
+ export function refreshErrors(scrumDir: string): { content: string; sourceFingerprint: string };
395
396
  export function renderState(repository: ArtifactRepository): string;
396
397
  export function stateFingerprint(repository: ArtifactRepository): string;
397
398
  export function stateIsStale(scrumDir: string): boolean;