scrumrun 1.5.2 → 2.1.0
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 +64 -0
- package/CORE.md +231 -308
- package/DECISIONS.md +151 -11
- package/LICENSE +21 -0
- package/MIGRATION-1-to-2.md +120 -0
- package/README.md +147 -78
- package/SPEC.md +259 -251
- package/bin/claude-install.js +18 -132
- package/bin/scrumrun.js +1367 -174
- package/docs/COMMANDS.md +71 -0
- package/docs/ENTITY-MODEL.md +38 -0
- package/docs/RELEASE-SCORECARD.md +43 -0
- package/docs/RELEASE.md +51 -0
- package/docs/SCHEMA.md +89 -0
- package/docs/SEMANTIC-MEMORY.md +68 -0
- package/docs/TROUBLESHOOTING.md +78 -0
- package/lib/code-intel/adapter.js +20 -0
- package/lib/code-intel/javascript.js +199 -0
- package/lib/code-intel/learning.js +66 -0
- package/lib/code-intel/scanner.js +126 -0
- package/lib/commands/manifest.js +118 -0
- package/lib/commands/render.js +78 -0
- package/lib/memory/index.js +659 -0
- package/lib/memory/markdown.js +51 -0
- package/lib/memory/service.js +300 -0
- package/lib/runtime/budgets.js +23 -0
- package/lib/runtime/canonical-snapshot.js +110 -0
- package/lib/runtime/context.js +101 -0
- package/lib/runtime/orchestrator.js +303 -0
- package/lib/runtime/policy-engine.js +184 -0
- package/lib/runtime/request-engine.js +132 -0
- package/lib/runtime/run-ledger.js +324 -0
- package/lib/security/secrets.js +23 -0
- package/lib/v2/artifacts.js +363 -0
- package/lib/v2/conformance.js +214 -0
- package/lib/v2/migration.js +1221 -0
- package/lib/v2/project-store.js +44 -0
- package/lib/v2/run-ledger-migration.js +240 -0
- package/lib/v2/schema.js +148 -0
- package/lib/v2/transaction.js +254 -0
- package/package.json +16 -6
- package/scripts/generate-contract-docs.js +124 -0
- package/templates/project/.scrumrun/config.md +4 -7
- package/templates/project/.scrumrun/guardrails.md +31 -0
- package/templates/project/.scrumrun/map.md +5 -16
- package/templates/project/.scrumrun/memory/decisions/.gitkeep +1 -0
- package/templates/project/.scrumrun/memory/dossiers/.gitkeep +1 -0
- package/templates/project/.scrumrun/memory/insights/.gitkeep +1 -0
- package/templates/project/.scrumrun/memory/knowledge/.gitkeep +1 -0
- package/templates/project/.scrumrun/method.json +7 -0
- package/templates/project/.scrumrun/project.md +6 -12
- package/templates/project/.scrumrun/runs/.gitkeep +1 -0
- package/templates/project/.scrumrun/sprints/.gitkeep +1 -0
- package/templates/project/.scrumrun/state.md +14 -0
- package/templates/project/.scrumrun/tasks/.gitkeep +1 -0
- package/templates/project/AGENTS.md +16 -49
- package/templates/project-lean/AGENTS.md +18 -0
- package/templates/shared/skills/scrumrun/SKILL.md +211 -0
- package/templates/codex/prompts/sc-agent.md +0 -14
- package/templates/codex/prompts/sc-backlog.md +0 -13
- package/templates/codex/prompts/sc-challenge.md +0 -57
- package/templates/codex/prompts/sc-config.md +0 -18
- package/templates/codex/prompts/sc-context.md +0 -24
- package/templates/codex/prompts/sc-decisions.md +0 -8
- package/templates/codex/prompts/sc-feature.md +0 -16
- package/templates/codex/prompts/sc-fix.md +0 -21
- package/templates/codex/prompts/sc-goal.md +0 -14
- package/templates/codex/prompts/sc-golden.md +0 -14
- package/templates/codex/prompts/sc-help.md +0 -12
- package/templates/codex/prompts/sc-init.md +0 -14
- package/templates/codex/prompts/sc-intake.md +0 -22
- package/templates/codex/prompts/sc-know.md +0 -75
- package/templates/codex/prompts/sc-map.md +0 -13
- package/templates/codex/prompts/sc-review.md +0 -13
- package/templates/codex/prompts/sc-sprint.md +0 -28
- package/templates/codex/prompts/sc-study.md +0 -23
- package/templates/codex/prompts/sc-uninstall.md +0 -14
- package/templates/codex/prompts/sc-update.md +0 -8
- package/templates/codex/prompts/sc-vault.md +0 -27
- package/templates/codex/skills/scrumrun/SKILL.md +0 -412
- package/templates/opencode/commands/sc-agent.md +0 -14
- package/templates/opencode/commands/sc-backlog.md +0 -13
- package/templates/opencode/commands/sc-challenge.md +0 -57
- package/templates/opencode/commands/sc-config.md +0 -18
- package/templates/opencode/commands/sc-context.md +0 -24
- package/templates/opencode/commands/sc-decisions.md +0 -8
- package/templates/opencode/commands/sc-feature.md +0 -16
- package/templates/opencode/commands/sc-fix.md +0 -21
- package/templates/opencode/commands/sc-goal.md +0 -14
- package/templates/opencode/commands/sc-golden.md +0 -14
- package/templates/opencode/commands/sc-help.md +0 -12
- package/templates/opencode/commands/sc-init.md +0 -14
- package/templates/opencode/commands/sc-intake.md +0 -22
- package/templates/opencode/commands/sc-know.md +0 -75
- package/templates/opencode/commands/sc-map.md +0 -13
- package/templates/opencode/commands/sc-review.md +0 -13
- package/templates/opencode/commands/sc-sprint.md +0 -28
- package/templates/opencode/commands/sc-study.md +0 -23
- package/templates/opencode/commands/sc-uninstall.md +0 -14
- package/templates/opencode/commands/sc-update.md +0 -8
- package/templates/opencode/commands/sc-vault.md +0 -27
- package/templates/opencode/skills/scrumrun/SKILL.md +0 -412
- package/templates/project/.scrumrun/agents.md +0 -36
- package/templates/project/.scrumrun/backlog.md +0 -7
- package/templates/project/.scrumrun/context.md +0 -61
- package/templates/project/.scrumrun/goals/main/decisions.md +0 -9
- package/templates/project/.scrumrun/goals/main/history.md +0 -51
- package/templates/project/.scrumrun/goals/main/sprint.md +0 -54
- package/templates/project/.scrumrun/golden-rules.md +0 -9
- package/templates/project/.scrumrun/knowledge.md +0 -15
- package/templates/project/.scrumrun/runbook.md +0 -101
- package/templates/project/.scrumrun/token-policy.md +0 -43
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const crypto = require("node:crypto");
|
|
4
|
+
const fs = require("node:fs");
|
|
5
|
+
const path = require("node:path");
|
|
6
|
+
const {
|
|
7
|
+
assertNoSymlinkPath,
|
|
8
|
+
atomicWrite,
|
|
9
|
+
sha256,
|
|
10
|
+
withArtifactLock
|
|
11
|
+
} = require("./artifacts");
|
|
12
|
+
|
|
13
|
+
const TRANSACTION_SCHEMA = 1;
|
|
14
|
+
const TRANSACTION_ROOT = path.join(".backup", "transactions");
|
|
15
|
+
const PENDING_DIR = path.join(TRANSACTION_ROOT, "pending");
|
|
16
|
+
const RECEIPT_DIR = path.join(TRANSACTION_ROOT, "receipts");
|
|
17
|
+
const FORBIDDEN = [".backup/", ".cache/", ".migration/", "vault.local.md"];
|
|
18
|
+
|
|
19
|
+
function posix(value) {
|
|
20
|
+
return value.split(path.sep).join("/");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function transactionId() {
|
|
24
|
+
return `TXN-${Date.now()}-${crypto.randomBytes(6).toString("hex")}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function safeTransactionPath(scrumDir, relative) {
|
|
28
|
+
return assertNoSymlinkPath(scrumDir, path.join(scrumDir, relative));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function relativeTarget(scrumDir, file) {
|
|
32
|
+
const target = assertNoSymlinkPath(scrumDir, path.resolve(file));
|
|
33
|
+
const relative = posix(path.relative(path.resolve(scrumDir), target));
|
|
34
|
+
if (!relative || relative.startsWith("../") || path.isAbsolute(relative)) throw new Error(`Transaction target escapes .scrumrun: ${file}`);
|
|
35
|
+
if (FORBIDDEN.some((entry) => relative === entry.replace(/\/$/, "") || relative.startsWith(entry))) {
|
|
36
|
+
throw new Error(`Transaction target is outside the canonical mutation boundary: ${relative}`);
|
|
37
|
+
}
|
|
38
|
+
return { relative, file: target };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function snapshot(content) {
|
|
42
|
+
if (content === null) return { exists: false, sha256: "missing", content_base64: null };
|
|
43
|
+
const value = Buffer.isBuffer(content) ? content : Buffer.from(String(content));
|
|
44
|
+
return { exists: true, sha256: sha256(value), content_base64: value.toString("base64") };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function fileSnapshot(scrumDir, relative) {
|
|
48
|
+
const file = safeTransactionPath(scrumDir, relative);
|
|
49
|
+
if (!fs.existsSync(file)) return snapshot(null);
|
|
50
|
+
if (!fs.lstatSync(file).isFile()) throw new Error(`Transaction target is not a regular file: ${relative}`);
|
|
51
|
+
return snapshot(fs.readFileSync(file));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function sameSnapshot(left, right) {
|
|
55
|
+
return Boolean(left && right && left.exists === right.exists && left.sha256 === right.sha256);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function applySnapshot(scrumDir, relative, value) {
|
|
59
|
+
const file = safeTransactionPath(scrumDir, relative);
|
|
60
|
+
if (!value.exists) {
|
|
61
|
+
if (fs.existsSync(file)) fs.rmSync(file, { force: true });
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
atomicWrite(file, Buffer.from(value.content_base64, "base64"));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function pendingDirectory(scrumDir) {
|
|
68
|
+
return safeTransactionPath(scrumDir, PENDING_DIR);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function receipt(transaction, status, details = {}) {
|
|
72
|
+
return {
|
|
73
|
+
schema: TRANSACTION_SCHEMA,
|
|
74
|
+
id: transaction.id,
|
|
75
|
+
name: transaction.name,
|
|
76
|
+
status,
|
|
77
|
+
created_at: transaction.created_at,
|
|
78
|
+
finished_at: new Date().toISOString(),
|
|
79
|
+
changes: transaction.changes.map((change) => ({
|
|
80
|
+
relative: change.relative,
|
|
81
|
+
before_sha256: change.before.sha256,
|
|
82
|
+
after_sha256: change.after.sha256
|
|
83
|
+
})),
|
|
84
|
+
...details
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function finishJournal(scrumDir, journalFile, transaction, status, details = {}) {
|
|
89
|
+
const receiptFile = safeTransactionPath(scrumDir, path.join(RECEIPT_DIR, `${transaction.id}.json`));
|
|
90
|
+
atomicWrite(receiptFile, `${JSON.stringify(receipt(transaction, status, details), null, 2)}\n`);
|
|
91
|
+
fs.rmSync(journalFile, { force: true });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function pendingTransactions(scrumDir) {
|
|
95
|
+
const directory = pendingDirectory(scrumDir);
|
|
96
|
+
if (!fs.existsSync(directory)) return [];
|
|
97
|
+
if (!fs.lstatSync(directory).isDirectory()) throw new Error("Transaction pending path is not a directory.");
|
|
98
|
+
const entries = fs.readdirSync(directory, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
|
|
99
|
+
for (const entry of entries) {
|
|
100
|
+
if (entry.isSymbolicLink() || !entry.isFile() || !/^TXN-[a-zA-Z0-9-]+\.json$/.test(entry.name)) {
|
|
101
|
+
throw new Error(`Unexpected transaction journal entry: ${entry.name}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return entries.map((entry) => {
|
|
105
|
+
const file = safeTransactionPath(scrumDir, path.join(PENDING_DIR, entry.name));
|
|
106
|
+
try {
|
|
107
|
+
const transaction = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
108
|
+
return { file, transaction };
|
|
109
|
+
} catch (error) {
|
|
110
|
+
throw new Error(`Transaction journal is malformed: ${entry.name}: ${error.message}`);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function validateJournal(transaction) {
|
|
116
|
+
const errors = [];
|
|
117
|
+
if (!transaction || transaction.schema !== TRANSACTION_SCHEMA) errors.push(`schema must be ${TRANSACTION_SCHEMA}`);
|
|
118
|
+
if (!/^TXN-[a-zA-Z0-9-]+$/.test(transaction && transaction.id || "")) errors.push("id is invalid");
|
|
119
|
+
if (!transaction || !["prepared", "committed"].includes(transaction.status)) errors.push("status must be prepared or committed");
|
|
120
|
+
if (!transaction || !Array.isArray(transaction.changes) || !transaction.changes.length) errors.push("changes must be a non-empty array");
|
|
121
|
+
for (const change of transaction && Array.isArray(transaction.changes) ? transaction.changes : []) {
|
|
122
|
+
if (typeof change.relative !== "string" || !change.relative) errors.push("change.relative is required");
|
|
123
|
+
for (const key of ["before", "after"]) {
|
|
124
|
+
const value = change[key];
|
|
125
|
+
if (!value || typeof value.exists !== "boolean" || typeof value.sha256 !== "string") errors.push(`${change.relative || "change"}.${key} is malformed`);
|
|
126
|
+
if (value && value.exists && typeof value.content_base64 !== "string") errors.push(`${change.relative || "change"}.${key} content is missing`);
|
|
127
|
+
if (value && value.exists && typeof value.content_base64 === "string") {
|
|
128
|
+
const decoded = Buffer.from(value.content_base64, "base64");
|
|
129
|
+
if (decoded.toString("base64") !== value.content_base64 || sha256(decoded) !== value.sha256) {
|
|
130
|
+
errors.push(`${change.relative || "change"}.${key} content hash is invalid`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (value && !value.exists && (value.sha256 !== "missing" || value.content_base64 !== null)) {
|
|
134
|
+
errors.push(`${change.relative || "change"}.${key} missing snapshot is invalid`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return errors;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function recoverPendingTransactionsUnlocked(scrumDir) {
|
|
142
|
+
const recovered = [];
|
|
143
|
+
for (const { file, transaction } of pendingTransactions(scrumDir)) {
|
|
144
|
+
const errors = validateJournal(transaction);
|
|
145
|
+
if (errors.length) throw new Error(`Unsafe transaction journal ${path.basename(file)}: ${errors.join("; ")}`);
|
|
146
|
+
for (const change of transaction.changes) {
|
|
147
|
+
const target = relativeTarget(scrumDir, path.join(scrumDir, change.relative));
|
|
148
|
+
if (target.relative !== change.relative) throw new Error(`Unsafe transaction target in ${transaction.id}: ${change.relative}`);
|
|
149
|
+
}
|
|
150
|
+
const current = transaction.changes.map((change) => ({ change, value: fileSnapshot(scrumDir, change.relative) }));
|
|
151
|
+
const unexpected = current.filter(({ change, value }) => !sameSnapshot(value, change.before) && !sameSnapshot(value, change.after));
|
|
152
|
+
if (unexpected.length) {
|
|
153
|
+
throw new Error(`Pending transaction ${transaction.id} cannot recover without overwriting owner changes: ${unexpected.map(({ change }) => change.relative).join(", ")}`);
|
|
154
|
+
}
|
|
155
|
+
if (transaction.status === "prepared") {
|
|
156
|
+
for (const change of [...transaction.changes].reverse()) applySnapshot(scrumDir, change.relative, change.before);
|
|
157
|
+
const unrestored = transaction.changes.filter((change) => !sameSnapshot(fileSnapshot(scrumDir, change.relative), change.before));
|
|
158
|
+
if (unrestored.length) throw new Error(`Transaction ${transaction.id} recovery verification failed: ${unrestored.map((change) => change.relative).join(", ")}`);
|
|
159
|
+
finishJournal(scrumDir, file, transaction, "recovered", { action: "rolled-back-prepared" });
|
|
160
|
+
recovered.push({ id: transaction.id, action: "rolled-back" });
|
|
161
|
+
} else {
|
|
162
|
+
const incomplete = current.filter(({ change, value }) => !sameSnapshot(value, change.after));
|
|
163
|
+
if (incomplete.length) throw new Error(`Committed transaction ${transaction.id} is incomplete: ${incomplete.map(({ change }) => change.relative).join(", ")}`);
|
|
164
|
+
finishJournal(scrumDir, file, transaction, "committed", { action: "verified-commit" });
|
|
165
|
+
recovered.push({ id: transaction.id, action: "verified" });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return recovered;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function recoverPendingTransactions(scrumDir) {
|
|
172
|
+
return withArtifactLock(scrumDir, "kernel", () => recoverPendingTransactionsUnlocked(scrumDir));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function normalizeChanges(scrumDir, changes) {
|
|
176
|
+
if (!Array.isArray(changes) || !changes.length) throw new Error("Kernel transaction requires at least one change.");
|
|
177
|
+
const seen = new Set();
|
|
178
|
+
return changes.map((change) => {
|
|
179
|
+
const target = relativeTarget(scrumDir, change.file);
|
|
180
|
+
if (seen.has(target.relative)) throw new Error(`Duplicate transaction target: ${target.relative}`);
|
|
181
|
+
seen.add(target.relative);
|
|
182
|
+
if (!Object.prototype.hasOwnProperty.call(change, "previous") || !Object.prototype.hasOwnProperty.call(change, "next")) {
|
|
183
|
+
throw new Error(`Transaction change requires previous and next content: ${target.relative}`);
|
|
184
|
+
}
|
|
185
|
+
return { relative: target.relative, before: snapshot(change.previous), after: snapshot(change.next) };
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function writeKernelTransactionUnlocked(scrumDir, name, changes, { failurePoint = null, interruptPoint = null } = {}) {
|
|
190
|
+
if (!/^[a-z0-9-]+$/i.test(name || "")) throw new Error(`Invalid transaction name: ${name || "missing"}`);
|
|
191
|
+
const normalized = normalizeChanges(scrumDir, changes);
|
|
192
|
+
const conflicts = normalized.filter((change) => !sameSnapshot(fileSnapshot(scrumDir, change.relative), change.before));
|
|
193
|
+
if (conflicts.length) throw new Error(`Transaction source changed before prepare: ${conflicts.map((change) => change.relative).join(", ")}`);
|
|
194
|
+
const transaction = {
|
|
195
|
+
schema: TRANSACTION_SCHEMA,
|
|
196
|
+
id: transactionId(),
|
|
197
|
+
name,
|
|
198
|
+
status: "prepared",
|
|
199
|
+
created_at: new Date().toISOString(),
|
|
200
|
+
changes: normalized
|
|
201
|
+
};
|
|
202
|
+
const journalFile = safeTransactionPath(scrumDir, path.join(PENDING_DIR, `${transaction.id}.json`));
|
|
203
|
+
atomicWrite(journalFile, `${JSON.stringify(transaction, null, 2)}\n`);
|
|
204
|
+
if (interruptPoint === "after-prepare") throw Object.assign(new Error("Simulated interruption after transaction prepare."), { code: "SCRUMRUN_INTERRUPTED" });
|
|
205
|
+
try {
|
|
206
|
+
for (let index = 0; index < transaction.changes.length; index++) {
|
|
207
|
+
const change = transaction.changes[index];
|
|
208
|
+
applySnapshot(scrumDir, change.relative, change.after);
|
|
209
|
+
if (interruptPoint === `after-${index + 1}`) {
|
|
210
|
+
throw Object.assign(new Error(`Simulated interruption after transaction write ${index + 1}.`), { code: "SCRUMRUN_INTERRUPTED" });
|
|
211
|
+
}
|
|
212
|
+
if (failurePoint === `after-${index + 1}`) throw new Error(`Injected transaction failure after write ${index + 1}.`);
|
|
213
|
+
}
|
|
214
|
+
const invalid = transaction.changes.filter((change) => !sameSnapshot(fileSnapshot(scrumDir, change.relative), change.after));
|
|
215
|
+
if (invalid.length) throw new Error(`Transaction verification failed: ${invalid.map((change) => change.relative).join(", ")}`);
|
|
216
|
+
const committed = { ...transaction, status: "committed", committed_at: new Date().toISOString() };
|
|
217
|
+
atomicWrite(journalFile, `${JSON.stringify(committed, null, 2)}\n`);
|
|
218
|
+
if (interruptPoint === "after-commit") {
|
|
219
|
+
throw Object.assign(new Error("Simulated interruption after transaction commit."), { code: "SCRUMRUN_INTERRUPTED" });
|
|
220
|
+
}
|
|
221
|
+
finishJournal(scrumDir, journalFile, committed, "committed", { action: "applied" });
|
|
222
|
+
return { id: transaction.id, status: "committed", changes: transaction.changes.length };
|
|
223
|
+
} catch (error) {
|
|
224
|
+
if (error.code === "SCRUMRUN_INTERRUPTED") throw error;
|
|
225
|
+
for (const change of [...transaction.changes].reverse()) applySnapshot(scrumDir, change.relative, change.before);
|
|
226
|
+
finishJournal(scrumDir, journalFile, transaction, "recovered", { action: "rolled-back-error", error: error.message });
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function runKernelTransaction(scrumDir, name, changes, options = {}) {
|
|
232
|
+
return withArtifactLock(scrumDir, "kernel", () => {
|
|
233
|
+
const recovered = recoverPendingTransactionsUnlocked(scrumDir);
|
|
234
|
+
const result = writeKernelTransactionUnlocked(scrumDir, name, changes, options);
|
|
235
|
+
return { ...result, recovered };
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function pendingTransactionStatus(scrumDir) {
|
|
240
|
+
try {
|
|
241
|
+
return { pending: pendingTransactions(scrumDir).map(({ transaction }) => ({ id: transaction.id, name: transaction.name, status: transaction.status })) };
|
|
242
|
+
} catch (error) {
|
|
243
|
+
return { pending: [], error: error.message };
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
module.exports = {
|
|
248
|
+
TRANSACTION_SCHEMA,
|
|
249
|
+
pendingTransactionStatus,
|
|
250
|
+
recoverPendingTransactions,
|
|
251
|
+
recoverPendingTransactionsUnlocked,
|
|
252
|
+
runKernelTransaction,
|
|
253
|
+
writeKernelTransactionUnlocked
|
|
254
|
+
};
|
package/package.json
CHANGED
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scrumrun",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "Evidence-driven Agile runtime and semantic project memory for AI coding agents.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"scrumrun": "bin/scrumrun.js",
|
|
7
7
|
"sr-claude": "bin/claude-install.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"bin",
|
|
11
|
+
"lib",
|
|
12
|
+
"scripts",
|
|
11
13
|
"templates",
|
|
12
14
|
"CORE.md",
|
|
13
15
|
"SPEC.md",
|
|
14
16
|
"DECISIONS.md",
|
|
15
|
-
"README.md"
|
|
17
|
+
"README.md",
|
|
18
|
+
"MIGRATION-1-to-2.md",
|
|
19
|
+
"CHANGELOG.md",
|
|
20
|
+
"LICENSE",
|
|
21
|
+
"docs"
|
|
16
22
|
],
|
|
17
23
|
"scripts": {
|
|
18
|
-
"
|
|
24
|
+
"docs:contract": "node scripts/generate-contract-docs.js",
|
|
25
|
+
"check:contract": "node scripts/generate-contract-docs.js --check",
|
|
26
|
+
"test": "node scripts/generate-contract-docs.js --check && node --test tests/*.test.js",
|
|
27
|
+
"benchmark": "node --test tests/performance.test.js"
|
|
19
28
|
},
|
|
20
29
|
"keywords": [
|
|
21
30
|
"ai",
|
|
@@ -23,7 +32,8 @@
|
|
|
23
32
|
"codex",
|
|
24
33
|
"opencode",
|
|
25
34
|
"agents",
|
|
26
|
-
"sprints"
|
|
35
|
+
"sprints",
|
|
36
|
+
"semantic-memory"
|
|
27
37
|
],
|
|
28
38
|
"author": {
|
|
29
39
|
"name": "Leander Costa",
|
|
@@ -43,6 +53,6 @@
|
|
|
43
53
|
},
|
|
44
54
|
"license": "MIT",
|
|
45
55
|
"engines": {
|
|
46
|
-
"node": ">=
|
|
56
|
+
"node": ">=22.13.0"
|
|
47
57
|
}
|
|
48
58
|
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const fs = require("node:fs");
|
|
5
|
+
const path = require("node:path");
|
|
6
|
+
const {
|
|
7
|
+
ARTIFACT_TRANSITIONS,
|
|
8
|
+
ARTIFACT_TYPES,
|
|
9
|
+
AUTHORITY,
|
|
10
|
+
METHOD_VERSION,
|
|
11
|
+
RUN_EVENT_TYPES,
|
|
12
|
+
RUN_EVIDENCE_KINDS,
|
|
13
|
+
RUN_LEDGER_VERSION,
|
|
14
|
+
SCALAR_FIELDS,
|
|
15
|
+
STRUCTURAL_RELATIONS,
|
|
16
|
+
TRUTH_OWNERSHIP
|
|
17
|
+
} = require("../lib/v2/schema");
|
|
18
|
+
|
|
19
|
+
const ROOT = path.resolve(__dirname, "..");
|
|
20
|
+
const OUTPUT = path.join(ROOT, "docs", "SCHEMA.md");
|
|
21
|
+
|
|
22
|
+
function inline(value) {
|
|
23
|
+
return String(value).replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function renderContract() {
|
|
27
|
+
const entityRows = Object.entries(ARTIFACT_TYPES).map(([kind, spec]) => {
|
|
28
|
+
const ownership = TRUTH_OWNERSHIP[kind];
|
|
29
|
+
return `| ${kind} | \`${spec.prefix}-NNN\` | \`${spec.directory}/\` | ${spec.initial.map((item) => `\`${item}\``).join(", ")} | ${inline(ownership.truth)} |`;
|
|
30
|
+
});
|
|
31
|
+
const relationRows = Object.entries(STRUCTURAL_RELATIONS).map(([field, relation]) => {
|
|
32
|
+
const target = ARTIFACT_TYPES[relation.targetKind];
|
|
33
|
+
return `| \`${field}\` | ${relation.targetKind} (\`${target.prefix}-NNN\`) | ${relation.cardinality} | ${inline(relation.meaning)} |`;
|
|
34
|
+
});
|
|
35
|
+
const scalarRows = Object.entries(SCALAR_FIELDS).map(([field, constraint]) => `| \`${field}\` | ${constraint.kinds.join(", ")} | ${constraint.required ? "required" : "optional"} | ${constraint.type} | ${inline(constraint.meaning)} |`);
|
|
36
|
+
const lifecycleRows = Object.entries(ARTIFACT_TYPES).map(([kind, spec]) => {
|
|
37
|
+
const transitions = ARTIFACT_TRANSITIONS[kind] || {};
|
|
38
|
+
const rendered = spec.statuses.map((status) => {
|
|
39
|
+
const targets = transitions[status];
|
|
40
|
+
return targets ? `\`${status}\` → ${targets.length ? targets.map((target) => `\`${target}\``).join(", ") : "terminal"}` : `\`${status}\` → terminal`;
|
|
41
|
+
}).join("<br>");
|
|
42
|
+
return `| ${kind} | ${rendered} |`;
|
|
43
|
+
});
|
|
44
|
+
const authorityRows = Object.entries(AUTHORITY).map(([concern, rule]) => `| ${concern} | \`${rule.source}\` | ${inline(rule.scope)} |`);
|
|
45
|
+
|
|
46
|
+
return `# ScrumRun ${METHOD_VERSION} Executable Schema
|
|
47
|
+
|
|
48
|
+
> Generated by \`scripts/generate-contract-docs.js\` from \`lib/v2/schema.js\`. Do not edit this file manually.
|
|
49
|
+
|
|
50
|
+
## Authority boundaries
|
|
51
|
+
|
|
52
|
+
| Concern | Authoritative source | Scope |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
${authorityRows.join("\n")}
|
|
55
|
+
|
|
56
|
+
The boundaries above are deliberately different: SPEC owns meanings, the executable schema owns mechanically enforced values, the command manifest owns grammar, project Markdown owns authored project truth, and generated views own nothing.
|
|
57
|
+
|
|
58
|
+
## Artifact contract
|
|
59
|
+
|
|
60
|
+
| Kind | Stable ID | Canonical directory | Allowed initial status | Truth owned by this artifact |
|
|
61
|
+
|---|---|---|---|---|
|
|
62
|
+
${entityRows.join("\n")}
|
|
63
|
+
|
|
64
|
+
Every artifact also requires \`id\`, \`kind\`, \`status\`, \`created\`, \`updated\`, and \`method: ${METHOD_VERSION}\`.
|
|
65
|
+
|
|
66
|
+
## Structural cardinalities
|
|
67
|
+
|
|
68
|
+
| Frontmatter field | Target | Cardinality | Meaning |
|
|
69
|
+
|---|---|---|---|
|
|
70
|
+
${relationRows.join("\n")}
|
|
71
|
+
|
|
72
|
+
Task is the atomic unit. A Task may have zero or one Sprint. A Task may have many Runs, but every Run belongs to exactly one Task and has a monotonically increasing attempt number within that Task. Sprint membership is authoritative on \`Task.sprint\`; a Sprint's \`## Tasks\` list is a human-readable projection that must agree with it.
|
|
73
|
+
|
|
74
|
+
## Scalar constraints
|
|
75
|
+
|
|
76
|
+
| Field | Kinds | Presence | Type | Meaning |
|
|
77
|
+
|---|---|---|---|---|
|
|
78
|
+
${scalarRows.join("\n")}
|
|
79
|
+
|
|
80
|
+
Native creation uses the declared initial statuses. Migration may restore a historical non-initial status only with provenance and validation.
|
|
81
|
+
|
|
82
|
+
## Run event ledger
|
|
83
|
+
|
|
84
|
+
Newly authored Runs use \`ledger: ${RUN_LEDGER_VERSION}\`. Their \`## Events\` section contains append-only JSON event blocks with stable ids in the form \`RUN-NNN-EVT-NNN\`.
|
|
85
|
+
|
|
86
|
+
Every event requires \`schema\`, \`id\`, contiguous \`sequence\`, RFC3339 \`occurred_at\`, \`timestamp_precision\`, \`actor\`, \`from\`, \`to\`, \`reason\`, and structured \`evidence\`. Event types are ${RUN_EVENT_TYPES.map((value) => `\`${value}\``).join(", ")}. Evidence kinds are ${RUN_EVIDENCE_KINDS.map((value) => `\`${value}\``).join(", ")}.
|
|
87
|
+
|
|
88
|
+
A native ledger begins with \`created → executing\`; an evidenced migration \`snapshot\` may establish one historical baseline without inventing missing transitions. Event order, transition legality, final status, updated date, and completion evidence are machine-validated. Run owns the event history; Task stores its intended scope and synchronized current status without copying Run events.
|
|
89
|
+
|
|
90
|
+
## Truth questions
|
|
91
|
+
|
|
92
|
+
${Object.entries(TRUTH_OWNERSHIP).map(([kind, owner]) => `- **${kind}:** ${owner.question}`).join("\n")}
|
|
93
|
+
|
|
94
|
+
## Lifecycles
|
|
95
|
+
|
|
96
|
+
| Kind | Declared transitions |
|
|
97
|
+
|---|---|
|
|
98
|
+
${lifecycleRows.join("\n")}
|
|
99
|
+
|
|
100
|
+
## Projections
|
|
101
|
+
|
|
102
|
+
\`state.md\`, \`map.md\`, context packages, and \`.cache/\` are disposable. They may summarize or index canonical artifacts, but they cannot introduce status, policy, relations, decisions, or knowledge.
|
|
103
|
+
`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function main(args = process.argv.slice(2)) {
|
|
107
|
+
const rendered = renderContract();
|
|
108
|
+
if (args.includes("--check")) {
|
|
109
|
+
const current = fs.existsSync(OUTPUT) ? fs.readFileSync(OUTPUT, "utf8") : "";
|
|
110
|
+
if (current !== rendered) {
|
|
111
|
+
console.error("docs/SCHEMA.md is stale; run `npm run docs:contract`.");
|
|
112
|
+
process.exitCode = 1;
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
console.log("ok docs/SCHEMA.md matches lib/v2/schema.js");
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
fs.writeFileSync(OUTPUT, rendered);
|
|
119
|
+
console.log(`generated ${path.relative(ROOT, OUTPUT)}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (require.main === module) main();
|
|
123
|
+
|
|
124
|
+
module.exports = { renderContract };
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
# ScrumRun Config - {{PROJECT_NAME}}
|
|
1
|
+
# ScrumRun Project Config - {{PROJECT_NAME}}
|
|
2
2
|
|
|
3
|
+
Method Version: 2.0.0
|
|
4
|
+
CLI Target: 2.0.0
|
|
3
5
|
Language: English
|
|
4
6
|
Interaction Mode: guided
|
|
5
7
|
Execution Approval: always
|
|
6
8
|
Quick Tasks: ask
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
- Keep sprint summaries concise and actionable.
|
|
11
|
-
- Ask only blocking questions before implementation.
|
|
12
|
-
- Preserve main goal history and feature lane history separately.
|
|
13
|
-
Sprint Automation: backlog
|
|
10
|
+
These are operating preferences. They can never weaken `.scrumrun/guardrails.md`.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# ScrumRun Project Guardrails - {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
Canonical project policy. Universal method invariants live in `core.md`; this file contains stable owner/project constraints.
|
|
4
|
+
|
|
5
|
+
## GR-001 - Protect secrets
|
|
6
|
+
|
|
7
|
+
Status: active
|
|
8
|
+
Enforcement: builtin:secret-boundary
|
|
9
|
+
Scope: all
|
|
10
|
+
Rule: Never commit or print real secrets. Keep local development values in `vault.local.md` and runtime values in environment/config.
|
|
11
|
+
|
|
12
|
+
## GR-002 - Preserve owner work
|
|
13
|
+
|
|
14
|
+
Status: active
|
|
15
|
+
Enforcement: builtin:owner-work
|
|
16
|
+
Scope: all
|
|
17
|
+
Rule: Never overwrite unrelated or pre-existing owner changes. Canonical mutations must be scoped, lossless, validated, and recoverable.
|
|
18
|
+
|
|
19
|
+
## GR-003 - Respect read-only paths
|
|
20
|
+
|
|
21
|
+
Status: active
|
|
22
|
+
Enforcement: builtin:read-only-path
|
|
23
|
+
Scope: all
|
|
24
|
+
Rule: Never modify a path marked read-only by the owner or project configuration.
|
|
25
|
+
|
|
26
|
+
## GR-004 - Approval gates execution
|
|
27
|
+
|
|
28
|
+
Status: active
|
|
29
|
+
Enforcement: builtin:approval-gate
|
|
30
|
+
Scope: intake, execution
|
|
31
|
+
Rule: Intake remains read-only. Create/update a Task and create a Run only after explicit valid approval.
|
|
@@ -1,20 +1,9 @@
|
|
|
1
|
-
# Project Map
|
|
1
|
+
# ScrumRun Project Map
|
|
2
2
|
|
|
3
3
|
Generated: {{DATE}}
|
|
4
|
+
Status: stale
|
|
5
|
+
Authority: none; rebuild from canonical artifacts and source code.
|
|
4
6
|
|
|
5
|
-
##
|
|
7
|
+
## Summary
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
## Top-Level Structure
|
|
10
|
-
|
|
11
|
-
- `AGENTS.md` — root agent instructions.
|
|
12
|
-
- `.scrumrun/` — ScrumRun project memory, plans, histories, decisions, and reviews.
|
|
13
|
-
|
|
14
|
-
## Important Commands
|
|
15
|
-
|
|
16
|
-
- Pending: run `/scr-map --build` after project files exist.
|
|
17
|
-
|
|
18
|
-
## Notes
|
|
19
|
-
|
|
20
|
-
- Update this file when important folders, modules, commands, routes, or config files are added or moved.
|
|
9
|
+
- No source fingerprint exists until `/sc knowledge map --build` creates the disposable semantic index and this bounded view.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -1,26 +1,20 @@
|
|
|
1
1
|
# Project - {{PROJECT_NAME}}
|
|
2
2
|
|
|
3
3
|
Created: {{DATE}}
|
|
4
|
+
Method: 2.0.0
|
|
4
5
|
|
|
5
6
|
## Purpose
|
|
6
7
|
|
|
7
|
-
Pending: define the
|
|
8
|
+
Pending: define the project outcome through `/sc plan intake <request>`.
|
|
8
9
|
|
|
9
10
|
## Stack
|
|
10
11
|
|
|
11
|
-
Pending:
|
|
12
|
+
Pending: discover or confirm runtime, frameworks, persistence, tests, deployment, and integrations.
|
|
12
13
|
|
|
13
14
|
## Architecture
|
|
14
15
|
|
|
15
|
-
Pending:
|
|
16
|
+
Pending: record verified boundaries and link evidence-backed Decisions/Insights.
|
|
16
17
|
|
|
17
|
-
##
|
|
18
|
+
## Read-only Paths
|
|
18
19
|
|
|
19
|
-
-
|
|
20
|
-
- Features: `.scrumrun/features/`
|
|
21
|
-
|
|
22
|
-
## Safety Boundaries
|
|
23
|
-
|
|
24
|
-
- Never commit real secrets.
|
|
25
|
-
- Runtime-specific values must come from env/config.
|
|
26
|
-
- Read-only source paths: pending confirmation.
|
|
20
|
+
- Pending: none declared.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# ScrumRun State
|
|
2
|
+
|
|
3
|
+
Projection schema: 1
|
|
4
|
+
Generated: {{DATE}}T00:00:00.000Z
|
|
5
|
+
Status: stale until `init` rebuilds this projection.
|
|
6
|
+
Authority: none; rebuild from canonical artifacts.
|
|
7
|
+
|
|
8
|
+
## Active Work
|
|
9
|
+
|
|
10
|
+
- No active canonical work.
|
|
11
|
+
|
|
12
|
+
## Relevant Memory
|
|
13
|
+
|
|
14
|
+
- No active canonical memory.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -1,59 +1,26 @@
|
|
|
1
1
|
# AGENTS.md - {{PROJECT_NAME}}
|
|
2
2
|
|
|
3
|
-
## ScrumRun
|
|
3
|
+
## ScrumRun 2.0
|
|
4
4
|
|
|
5
|
-
This project uses ScrumRun.
|
|
5
|
+
This project uses ScrumRun. The method is mandatory; `/sc` is its single optional shortcut.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
For normal work, read:
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
1. `.scrumrun/guardrails.md` — canonical project policy;
|
|
10
|
+
2. `.scrumrun/state.md` — disposable index of active ids;
|
|
11
|
+
3. the referenced Task, Sprint, Feature, Run, Memory, and Review artifacts relevant to the request;
|
|
12
|
+
4. `.scrumrun/core.md` when the method contract or an exceptional transition is needed.
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
2. `.scrumrun/golden-rules.md` — **MANDATORY. Highest priority. Never violate.**
|
|
13
|
-
3. `.scrumrun/config.md` — response language and ScrumRun behavior preferences.
|
|
14
|
-
4. `.scrumrun/token-policy.md` — context economy rules.
|
|
15
|
-
5. `.scrumrun/context.md` — token-safe snapshot, not source of truth.
|
|
16
|
-
6. `.scrumrun/map.md` — project folders, paths, modules, and key files.
|
|
17
|
-
7. `.scrumrun/project.md`
|
|
18
|
-
8. `.scrumrun/knowledge.md`
|
|
19
|
-
9. `.scrumrun/runbook.md` — execution protocol before reading operational work.
|
|
20
|
-
10. `.scrumrun/backlog.md`
|
|
21
|
-
11. `.scrumrun/goals/main/sprint.md`
|
|
22
|
-
12. `.scrumrun/agents.md`
|
|
23
|
-
13. `.scrumrun/goals/main/history.md`
|
|
24
|
-
14. `.scrumrun/goals/main/decisions.md`
|
|
14
|
+
Natural-language product requests automatically enter the read-only ScrumRun intake pipeline. Before explicit approval, do not create canonical records or modify application code.
|
|
25
15
|
|
|
26
|
-
|
|
16
|
+
After approval:
|
|
27
17
|
|
|
28
|
-
|
|
18
|
+
- Task is the atomic work item;
|
|
19
|
+
- Sprint is only a real timebox/batch of Tasks;
|
|
20
|
+
- Run is one execution attempt and follows `executing → validating → learning → completed|failed|blocked`;
|
|
21
|
+
- a retry creates a new Run and preserves the old one;
|
|
22
|
+
- learning proposes evidence-backed Knowledge, Decisions, or candidate Insights.
|
|
29
23
|
|
|
30
|
-
|
|
24
|
+
Never bypass guardrails, overwrite owner work, treat generated state/cache as truth, auto-confirm AI knowledge, auto-migrate a v1 project, or print vault values.
|
|
31
25
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
- Do not start a sprint unless the owner explicitly asks for that sprint.
|
|
35
|
-
- Do not convert an intake recommendation into a backlog item, sprint, feature lane, fix, or code change until the configured approval gate is satisfied.
|
|
36
|
-
- Before executing any main-goal sprint, check `.scrumrun/goals/main/history.md`.
|
|
37
|
-
- Before executing any feature sprint, check that feature lane's `history.md`.
|
|
38
|
-
- If a sprint is `completed`, ask whether to audit, rerun/fix, or continue to the next sprint.
|
|
39
|
-
- If a sprint is `partial` or `blocked`, ask whether to resume, audit, or move to another sprint.
|
|
40
|
-
- Never commit real secrets.
|
|
41
|
-
- Runtime-specific values must come from env/config, not hardcoded strings.
|
|
42
|
-
- If a project has a read-only source path, never modify it.
|
|
43
|
-
- Keep main goal history and feature lane history separate.
|
|
44
|
-
- Use `.scrumrun/context.md` to reduce unnecessary reading, but never as canonical truth.
|
|
45
|
-
- If context conflicts with golden rules, approved knowledge, history, decisions, or source code, context loses.
|
|
46
|
-
|
|
47
|
-
## Sprint Protocol
|
|
48
|
-
|
|
49
|
-
Every sprint must follow:
|
|
50
|
-
|
|
51
|
-
1. Entenda
|
|
52
|
-
2. Avalie Impactos
|
|
53
|
-
3. Tire Duvidas
|
|
54
|
-
4. Execute
|
|
55
|
-
5. Teste
|
|
56
|
-
|
|
57
|
-
## Handoff
|
|
58
|
-
|
|
59
|
-
At the end of each sprint, update the relevant history file with status, files changed, commands run, tests, decisions, env variable names, risks, and follow-ups.
|
|
26
|
+
If `/sc` is unavailable, follow the equivalent workflow in `.scrumrun/core.md` manually.
|