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 +12 -0
- package/README.md +1 -1
- package/bin/scrumrun.js +14 -3
- package/lib/commands/manifest.js +1 -0
- package/lib/memory/index.js +9 -4
- package/lib/runtime/briefing.js +37 -1
- package/lib/runtime/orchestrator.js +17 -3
- package/lib/runtime/workspace-state.js +11 -0
- package/package.json +1 -1
- package/types/index.d.ts +1 -0
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.
|
|
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
|
|
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);
|
package/lib/commands/manifest.js
CHANGED
|
@@ -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
|
}
|
package/lib/memory/index.js
CHANGED
|
@@ -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 =
|
|
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) =>
|
|
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",
|
package/lib/runtime/briefing.js
CHANGED
|
@@ -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
|
-
|
|
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
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;
|