scrumrun 2.7.8 → 2.7.9
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/normalize-legacy.js +30 -8
- 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.9` · **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
|
|
|
@@ -23,6 +23,8 @@ const { RUN_LEDGER_VERSION } = require("../v2/schema");
|
|
|
23
23
|
|
|
24
24
|
const RUN_FILENAME_PATTERN = /^RUN-\d{3,}\.md$/;
|
|
25
25
|
const TASK_REF_PATTERN = /^TASK-\d{3,}$/;
|
|
26
|
+
const RUN_REF_PATTERN = /^RUN-\d{3,}$/;
|
|
27
|
+
const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
|
26
28
|
|
|
27
29
|
function readFrontmatterAndBody(text) {
|
|
28
30
|
const match = String(text || "").match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
@@ -46,6 +48,11 @@ function extractTitle(body, fallback) {
|
|
|
46
48
|
return match ? match[1] : fallback;
|
|
47
49
|
}
|
|
48
50
|
|
|
51
|
+
function extractTaskReference(body) {
|
|
52
|
+
const match = String(body || "").match(/\b(TASK-\d{3,})\b/);
|
|
53
|
+
return match ? match[1] : null;
|
|
54
|
+
}
|
|
55
|
+
|
|
49
56
|
function serializeFrontmatter(frontmatter) {
|
|
50
57
|
const lines = Object.entries(frontmatter).map(([key, value]) => {
|
|
51
58
|
if (value === null || value === undefined) return `${key}: null`;
|
|
@@ -71,27 +78,41 @@ function reserveBackupPath(backupDir, runId) {
|
|
|
71
78
|
|
|
72
79
|
function needsNormalization(record, body) {
|
|
73
80
|
if (!record) return { needs: true, reason: "frontmatter is malformed or missing" };
|
|
81
|
+
const identityErrors = [];
|
|
82
|
+
if (!RUN_REF_PATTERN.test(record.id || "")) identityErrors.push("id is missing or does not match RUN-NNN");
|
|
83
|
+
if (!DATE_PATTERN.test(record.created || "")) identityErrors.push("created is missing or is not YYYY-MM-DD");
|
|
74
84
|
if (typeof record.task === "string" && record.task && !TASK_REF_PATTERN.test(record.task)) {
|
|
75
|
-
|
|
85
|
+
identityErrors.push(`task field does not match TASK-NNN (was ${JSON.stringify(record.task)})`);
|
|
86
|
+
}
|
|
87
|
+
if ((record.task === null || record.task === undefined || record.task === "") && !extractTaskReference(body)) {
|
|
88
|
+
identityErrors.push("task is missing and cannot be inferred from the legacy body");
|
|
76
89
|
}
|
|
77
90
|
const result = validateRunLedger(record, body);
|
|
78
91
|
const errors = result.errors || [];
|
|
79
|
-
if (errors.length
|
|
92
|
+
if (errors.length || identityErrors.length) {
|
|
93
|
+
const allErrors = [...identityErrors, ...errors];
|
|
94
|
+
const primaryIdentityError = identityErrors.find((error) => error.startsWith("task field")) || identityErrors[0];
|
|
95
|
+
return { needs: true, reason: primaryIdentityError || `ledger validation failed (${errors.length} error${errors.length === 1 ? "" : "s"})`, errors: allErrors };
|
|
96
|
+
}
|
|
80
97
|
return { needs: false };
|
|
81
98
|
}
|
|
82
99
|
|
|
83
|
-
function buildNormalizedBody({ record, originalBody, backupRelative, reason, errors }) {
|
|
84
|
-
const
|
|
100
|
+
function buildNormalizedBody({ record, runId, originalBody, backupRelative, reason, errors }) {
|
|
101
|
+
const normalizedId = RUN_REF_PATTERN.test(record.id || "") ? record.id : runId;
|
|
102
|
+
const normalizedTask = TASK_REF_PATTERN.test(record.task || "") ? record.task : extractTaskReference(originalBody);
|
|
103
|
+
const snapshotDate = DATE_PATTERN.test(record.created || "")
|
|
104
|
+
? record.created
|
|
105
|
+
: DATE_PATTERN.test(record.updated || "") ? record.updated : new Date().toISOString().slice(0, 10);
|
|
106
|
+
const title = extractTitle(originalBody, `Run ${normalizedId}`);
|
|
85
107
|
const sha = crypto.createHash("sha256").update(originalBody).digest("hex");
|
|
86
|
-
const snapshotDate = record.created || new Date().toISOString().slice(0, 10);
|
|
87
108
|
const normalizedRecord = {
|
|
88
|
-
id:
|
|
109
|
+
id: normalizedId,
|
|
89
110
|
kind: "run",
|
|
90
111
|
status: record.status || "partial",
|
|
91
|
-
created:
|
|
112
|
+
created: snapshotDate,
|
|
92
113
|
updated: snapshotDate,
|
|
93
114
|
method: "2.0.0",
|
|
94
|
-
task:
|
|
115
|
+
task: normalizedTask,
|
|
95
116
|
sprint: record.sprint || null,
|
|
96
117
|
attempt: record.attempt || 1,
|
|
97
118
|
ledger: RUN_LEDGER_VERSION
|
|
@@ -157,6 +178,7 @@ function apply(scrumDir, plan) {
|
|
|
157
178
|
const backupRelative = path.relative(scrumDir, backupPath);
|
|
158
179
|
const normalized = buildNormalizedBody({
|
|
159
180
|
record: frontmatter,
|
|
181
|
+
runId: entry.id,
|
|
160
182
|
originalBody: raw,
|
|
161
183
|
backupRelative,
|
|
162
184
|
reason: entry.reason,
|