scrumrun 2.7.10 → 2.7.11
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/README.md +1 -1
- package/lib/commands/repair.js +45 -0
- package/package.json +1 -1
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.11` · **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/lib/commands/repair.js
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
// complete→completed, in_progress→executing.
|
|
13
13
|
// 3. frontmatter method field: any value that isn't "2.0.0" → "2.0.0".
|
|
14
14
|
// 4. run.attempt missing → 1 (documented default).
|
|
15
|
+
// 5. Sprint task projections: add Task.sprint references absent from the
|
|
16
|
+
// Sprint's `## Tasks` list. Extra entries remain for human review.
|
|
15
17
|
//
|
|
16
18
|
// Categories intentionally NOT touched (need human judgment):
|
|
17
19
|
// - Secret-like content (SECRET_CANONICAL)
|
|
@@ -132,6 +134,48 @@ function scanTaskIds(scrumDir) {
|
|
|
132
134
|
return ids;
|
|
133
135
|
}
|
|
134
136
|
|
|
137
|
+
function planSprintMembership(scrumDir) {
|
|
138
|
+
const tasksBySprint = new Map();
|
|
139
|
+
for (const file of listDir(path.join(scrumDir, "tasks"), TASK_FILE)) {
|
|
140
|
+
const parsed = splitFrontmatter(readIf(file));
|
|
141
|
+
if (!parsed) continue;
|
|
142
|
+
const sprint = extractField(parsed.header, "sprint");
|
|
143
|
+
const id = extractField(parsed.header, "id") || path.basename(file, ".md");
|
|
144
|
+
if (!/^SPRINT-\d{3,}$/.test(sprint || "") || !TASK_REF.test(id)) continue;
|
|
145
|
+
if (!tasksBySprint.has(sprint)) tasksBySprint.set(sprint, []);
|
|
146
|
+
tasksBySprint.get(sprint).push(id);
|
|
147
|
+
}
|
|
148
|
+
const entries = [];
|
|
149
|
+
for (const file of listDir(path.join(scrumDir, "sprints"), SPRINT_FILE)) {
|
|
150
|
+
const original = readIf(file);
|
|
151
|
+
const parsed = splitFrontmatter(original);
|
|
152
|
+
if (!parsed) continue;
|
|
153
|
+
const sprintId = extractField(parsed.header, "id") || path.basename(file, ".md");
|
|
154
|
+
const expected = tasksBySprint.get(sprintId) || [];
|
|
155
|
+
if (!expected.length) continue;
|
|
156
|
+
const heading = /^## Tasks[ \t]*$/m.exec(parsed.body);
|
|
157
|
+
if (!heading) continue;
|
|
158
|
+
const tail = parsed.body.slice(heading.index + heading[0].length);
|
|
159
|
+
const nextHeading = /^## [^\r\n]+$/m.exec(tail);
|
|
160
|
+
const sectionEnd = nextHeading ? heading.index + heading[0].length + nextHeading.index : parsed.body.length;
|
|
161
|
+
const section = parsed.body.slice(heading.index + heading[0].length, sectionEnd);
|
|
162
|
+
const listed = new Set([...section.matchAll(/^-\s+(TASK-\d{3,})\s*$/gm)].map((match) => match[1]));
|
|
163
|
+
const missing = expected.filter((id) => !listed.has(id));
|
|
164
|
+
if (!missing.length) continue;
|
|
165
|
+
const before = parsed.body.slice(0, sectionEnd).replace(/\s*$/, "\n");
|
|
166
|
+
const after = parsed.body.slice(sectionEnd).replace(/^\n/, "");
|
|
167
|
+
const body = `${before}${missing.map((id) => `- ${id}`).join("\n")}\n${after}`;
|
|
168
|
+
entries.push({
|
|
169
|
+
file,
|
|
170
|
+
kind: "sprint-membership",
|
|
171
|
+
changes: [{ field: "sprint.tasks", from: "missing projection", to: missing.join(", ") }],
|
|
172
|
+
nextText: `---\n${parsed.header}\n---${parsed.sep}${body}`,
|
|
173
|
+
originalText: original
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return entries;
|
|
177
|
+
}
|
|
178
|
+
|
|
135
179
|
function legacyMetadata(body) {
|
|
136
180
|
const metadata = {};
|
|
137
181
|
const section = String(body || "").match(/^## Metadata\s*$([\s\S]*?)(?=^##\s+|(?![\s\S]))/im);
|
|
@@ -629,6 +673,7 @@ function analyze(scrumDir) {
|
|
|
629
673
|
const missingFrontmatter = new Set(entries.filter((entry) => entry.kind === "legacy-frontmatter").map((entry) => entry.file));
|
|
630
674
|
entries.push(...planAcceptanceCriteria(scrumDir).filter((entry) => !missingFrontmatter.has(entry.file)));
|
|
631
675
|
entries.push(...planSecretRedaction(scrumDir));
|
|
676
|
+
entries.push(...planSprintMembership(scrumDir));
|
|
632
677
|
|
|
633
678
|
for (const file of listDir(path.join(scrumDir, "tasks"), TASK_FILE)) {
|
|
634
679
|
const plan = planFile(file, "task", ctx);
|