scrumrun 2.0.0 → 2.1.1

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/CORE.md +17 -3
  3. package/DECISIONS.md +56 -0
  4. package/MIGRATION-1-to-2.md +11 -0
  5. package/README.md +23 -5
  6. package/SPEC.md +30 -10
  7. package/bin/scrumrun.js +175 -11
  8. package/docs/COMMANDS.md +10 -4
  9. package/docs/ENTITY-MODEL.md +1 -1
  10. package/docs/RELEASE-SCORECARD.md +43 -0
  11. package/docs/RELEASE.md +19 -12
  12. package/docs/SCHEMA.md +11 -0
  13. package/docs/SEMANTIC-MEMORY.md +1 -1
  14. package/docs/TROUBLESHOOTING.md +13 -1
  15. package/lib/commands/manifest.js +15 -3
  16. package/lib/commands/render.js +4 -0
  17. package/lib/memory/index.js +201 -41
  18. package/lib/memory/service.js +3 -0
  19. package/lib/runtime/budgets.js +4 -0
  20. package/lib/runtime/canonical-snapshot.js +110 -0
  21. package/lib/runtime/context.js +5 -45
  22. package/lib/runtime/mutation-gateway.js +434 -0
  23. package/lib/runtime/orchestrator.js +130 -65
  24. package/lib/runtime/policy-engine.js +267 -0
  25. package/lib/runtime/request-engine.js +32 -24
  26. package/lib/runtime/review-service.js +92 -0
  27. package/lib/runtime/run-ledger.js +546 -0
  28. package/lib/runtime/workspace-state.js +146 -0
  29. package/lib/security/secrets.js +15 -1
  30. package/lib/v2/artifacts.js +24 -1
  31. package/lib/v2/conformance.js +78 -12
  32. package/lib/v2/migration.js +74 -10
  33. package/lib/v2/run-ledger-migration.js +268 -0
  34. package/lib/v2/schema.js +28 -1
  35. package/lib/v2/transaction.js +254 -0
  36. package/package.json +1 -1
  37. package/scripts/generate-contract-docs.js +11 -0
  38. package/templates/project/.scrumrun/guardrails.md +8 -0
  39. package/templates/project/.scrumrun/map.md +4 -3
  40. package/templates/project/.scrumrun/method.json +7 -1
  41. package/templates/project/.scrumrun/state.md +7 -14
  42. package/templates/project/AGENTS.md +2 -1
  43. package/templates/project-lean/AGENTS.md +3 -1
  44. package/templates/shared/skills/scrumrun/SKILL.md +19 -5
package/bin/scrumrun.js CHANGED
@@ -9,14 +9,23 @@ const root = path.resolve(__dirname, "..");
9
9
  const templates = path.join(root, "templates");
10
10
  const { version } = require(path.join(root, "package.json"));
11
11
  const { applyMigration, dryRunMigration, rollbackMigration } = require(path.join(root, "lib", "v2", "migration"));
12
+ const {
13
+ applyRunLedgerMigration,
14
+ planRunLedgerMigration,
15
+ reportRunLedgerMigration,
16
+ rollbackRunLedgerMigration
17
+ } = require(path.join(root, "lib", "v2", "run-ledger-migration"));
12
18
  const { ARTIFACT_TYPES, ArtifactRepository } = require(path.join(root, "lib", "v2", "artifacts"));
13
19
  const { aliases: COMMAND_ALIASES, resolveAlias, resolveRoute } = require(path.join(root, "lib", "commands", "manifest"));
14
20
  const { renderCommandHelp, renderCompatibilityPrompt, renderRootPrompt } = require(path.join(root, "lib", "commands", "render"));
15
21
  const { planRequest } = require(path.join(root, "lib", "runtime", "request-engine"));
16
22
  const { approveRequest, refreshState, retryTask, transitionRun } = require(path.join(root, "lib", "runtime", "orchestrator"));
23
+ const { authorizeMutation, recordMutation, satisfyGuardrail } = require(path.join(root, "lib", "runtime", "mutation-gateway"));
24
+ const { recordArtifactReview } = require(path.join(root, "lib", "runtime", "review-service"));
17
25
  const { createMemory, listMemory, showMemory, transitionMemory } = require(path.join(root, "lib", "memory", "service"));
18
- const { indexPath, indexStatus, queryIndex, rebuildIndex, writeMap } = require(path.join(root, "lib", "memory", "index"));
26
+ const { indexPath, indexStatus, mapStatus, queryIndex, rebuildIndex, writeMap } = require(path.join(root, "lib", "memory", "index"));
19
27
  const { auditProject } = require(path.join(root, "lib", "v2", "conformance"));
28
+ const { recoverPendingTransactions } = require(path.join(root, "lib", "v2", "transaction"));
20
29
  const { containsSecret } = require(path.join(root, "lib", "security", "secrets"));
21
30
 
22
31
  const COMMANDS = ["sc"];
@@ -39,7 +48,7 @@ Usage:
39
48
  scrumrun migrate --to 2 --dry-run
40
49
  scrumrun migrate --to 2 --apply
41
50
  scrumrun migrate --to 2 --rollback
42
- scrumrun doctor [all|codex|opencode|claude] [--strict]
51
+ scrumrun doctor [all|codex|opencode|claude] [--strict] [--recover]
43
52
  scrumrun uninstall [--force]
44
53
 
45
54
  Examples:
@@ -267,7 +276,27 @@ function migrationPreflightOnUpdate({ apply = false } = {}) {
267
276
  const marker = path.join(scrumDir, "method.json");
268
277
  if (fs.existsSync(marker)) {
269
278
  try {
270
- if (JSON.parse(fs.readFileSync(marker, "utf8")).method === "2.0.0") return { status: "already-v2" };
279
+ if (JSON.parse(fs.readFileSync(marker, "utf8")).method === "2.0.0") {
280
+ const preview = planRunLedgerMigration(process.cwd());
281
+ if (preview.status === "current") return { status: "already-v2" };
282
+ console.log("\n## Project schema migration preflight\n");
283
+ console.log(reportRunLedgerMigration(preview).trimEnd());
284
+ if (preview.status === "blocked") {
285
+ for (const error of preview.errors) console.error(`BLOCKED: ${error}`);
286
+ process.exitCode = 1;
287
+ return { status: "blocked" };
288
+ }
289
+ if (!apply) {
290
+ console.log("\nThe project remains unchanged. Apply the verified plan with: npx scrumrun@latest update --migrate");
291
+ return { status: "ready" };
292
+ }
293
+ const result = applyRunLedgerMigration(process.cwd());
294
+ refreshState(path.join(process.cwd(), ".scrumrun"));
295
+ console.log("\nApplied the verified ScrumRun Run ledger schema migration.");
296
+ console.log(`Source fingerprint: ${result.plan.fingerprint}`);
297
+ console.log("Rollback remains available with: npx scrumrun@latest migrate --to 2 --rollback");
298
+ return { status: result.status, result };
299
+ }
271
300
  } catch {
272
301
  console.error("Project migration preflight failed: .scrumrun/method.json is malformed.");
273
302
  process.exitCode = 1;
@@ -1167,6 +1196,61 @@ function memoryOptions(args) {
1167
1196
  };
1168
1197
  }
1169
1198
 
1199
+ function runTransitionOptions(args) {
1200
+ const evidenceFlags = new Map([
1201
+ ["--command", "command"],
1202
+ ["--test", "test"],
1203
+ ["--file", "file"],
1204
+ ["--review", "review"],
1205
+ ["--decision", "decision"],
1206
+ ["--insight", "insight"],
1207
+ ["--risk", "risk"]
1208
+ ]);
1209
+ const referenceKinds = new Set(["file", "review", "decision", "insight"]);
1210
+ const evidence = [];
1211
+ const noteParts = [];
1212
+ let note = null;
1213
+ let actor = "agent";
1214
+ let occurredAt = null;
1215
+ for (let index = 2; index < args.length; index++) {
1216
+ const token = args[index];
1217
+ const value = args[index + 1] && !args[index + 1].startsWith("--") ? args[index + 1] : null;
1218
+ if (token === "--note" || token === "--actor" || token === "--at" || token === "--evidence" || evidenceFlags.has(token)) {
1219
+ if (!value) throw new Error(`${token} requires a value.`);
1220
+ index++;
1221
+ if (token === "--note") note = value;
1222
+ else if (token === "--actor") actor = value;
1223
+ else if (token === "--at") occurredAt = value;
1224
+ else if (token === "--evidence") {
1225
+ const separator = value.indexOf(":");
1226
+ const kind = separator > 0 ? value.slice(0, separator) : "note";
1227
+ const content = separator > 0 ? value.slice(separator + 1) : value;
1228
+ evidence.push(referenceKinds.has(kind) ? { kind, ref: content } : { kind, summary: content });
1229
+ } else {
1230
+ const kind = evidenceFlags.get(token);
1231
+ evidence.push(referenceKinds.has(kind) ? { kind, ref: value } : { kind, summary: value });
1232
+ }
1233
+ continue;
1234
+ }
1235
+ if (token.startsWith("--")) throw new Error(`Unknown Run evidence option: ${token}`);
1236
+ noteParts.push(token);
1237
+ }
1238
+ return { note: note || noteParts.join(" ").trim() || null, evidence, actor, occurredAt };
1239
+ }
1240
+
1241
+ function removeOptionPairs(args, names) {
1242
+ const hidden = new Set(names);
1243
+ const next = [];
1244
+ for (let index = 0; index < args.length; index++) {
1245
+ if (hidden.has(args[index])) {
1246
+ index++;
1247
+ continue;
1248
+ }
1249
+ next.push(args[index]);
1250
+ }
1251
+ return next;
1252
+ }
1253
+
1170
1254
  function printMemoryArtifact(artifact) {
1171
1255
  if (!artifact) return false;
1172
1256
  console.log(readIfExists(artifact.file));
@@ -1233,9 +1317,8 @@ function runSemanticContext(subject, args) {
1233
1317
  return;
1234
1318
  }
1235
1319
  const mapFile = path.join(process.cwd(), ".scrumrun", "map.md");
1236
- if (!fs.existsSync(mapFile)) throw new Error("Generated map.md is missing; run /sc knowledge map --build.");
1237
- const status = indexStatus(process.cwd());
1238
- if (status.stale) console.warn("WARNING: map.md is stale; rebuild it before relying on the view.");
1320
+ const status = mapStatus(process.cwd());
1321
+ if (status.stale) throw new Error(`Generated map.md is stale (${status.reason || status.error || "unknown reason"}); run /sc knowledge map --build.`);
1239
1322
  console.log(fs.readFileSync(mapFile, "utf8"));
1240
1323
  return;
1241
1324
  }
@@ -1272,8 +1355,11 @@ function executeRootRoute(route) {
1272
1355
  console.log(`State: ${plan.state}`);
1273
1356
  console.log(`Classification: ${plan.classification.type} (${plan.classification.reason})`);
1274
1357
  console.log(`Risk: ${plan.risk.level} — ${plan.risk.reasons.join("; ")}`);
1275
- console.log(`Policy: ${plan.policy.status}`);
1358
+ console.log(`Policy: ${plan.policy.status} (${plan.policy.checked.length} checked; ${plan.policy.deferred.length} deferred)`);
1276
1359
  for (const violation of plan.policy.violations) console.log(`BLOCKED: ${violation}`);
1360
+ for (const result of plan.policy.evaluations.filter((item) => item.status === "deferred")) {
1361
+ console.log(`DEFERRED: ${result.guardrail} ${result.code}: ${result.message}`);
1362
+ }
1277
1363
  for (const warning of plan.context.warnings) console.log(`WARNING: ${warning}`);
1278
1364
  if (plan.approvalToken) {
1279
1365
  console.log(`Approval: scrumrun sc plan intake --approve ${plan.approvalToken}`);
@@ -1302,6 +1388,33 @@ function executeRootRoute(route) {
1302
1388
  return;
1303
1389
  }
1304
1390
  if (noun === "plan" && subject === "run") {
1391
+ if (routeArgs[0] === "--authorize-mutation") {
1392
+ const paths = optionValues(routeArgs.slice(2), "--path");
1393
+ const result = authorizeMutation(process.cwd(), routeArgs[1], paths);
1394
+ console.log(`Authorized ${result.permit} for ${result.run} until ${result.expiresAt}.`);
1395
+ console.log(`Paths: ${result.paths.join(", ")}`);
1396
+ return;
1397
+ }
1398
+ if (routeArgs[0] === "--record-mutation") {
1399
+ const permit = optionValue(routeArgs.slice(2), "--permit");
1400
+ if (!permit) throw new Error("--record-mutation requires --permit MUT-id.");
1401
+ const parsed = runTransitionOptions(removeOptionPairs(routeArgs, ["--permit"]));
1402
+ const result = recordMutation(process.cwd(), routeArgs[1], permit, parsed);
1403
+ refreshState(projectFile());
1404
+ console.log(`Recorded ${result.mutation} for ${result.run.id}: ${result.changes.length} verified change(s).`);
1405
+ return;
1406
+ }
1407
+ if (routeArgs[0] === "--satisfy-guardrail") {
1408
+ const guardrail = optionValue(routeArgs.slice(2), "--guardrail");
1409
+ if (!guardrail) throw new Error("--satisfy-guardrail requires --guardrail GR-NNN.");
1410
+ const migration = optionValues(routeArgs.slice(2), "--migration").map((ref) => ({ kind: "migration", ref }));
1411
+ const parsed = runTransitionOptions(removeOptionPairs(routeArgs, ["--guardrail", "--migration"]));
1412
+ parsed.evidence.push(...migration);
1413
+ const result = satisfyGuardrail(process.cwd(), routeArgs[1], guardrail, parsed);
1414
+ refreshState(projectFile());
1415
+ console.log(`${result.run.id}: ${result.guardrail} ${result.status}.`);
1416
+ return;
1417
+ }
1305
1418
  const transitions = {
1306
1419
  "--validate": "validating",
1307
1420
  "--learn": "learning",
@@ -1311,7 +1424,7 @@ function executeRootRoute(route) {
1311
1424
  "--block": "blocked"
1312
1425
  };
1313
1426
  if (transitions[routeArgs[0]]) {
1314
- const result = transitionRun(process.cwd(), routeArgs[1], transitions[routeArgs[0]], { note: routeArgs.slice(2).join(" ") || null });
1427
+ const result = transitionRun(process.cwd(), routeArgs[1], transitions[routeArgs[0]], runTransitionOptions(routeArgs));
1315
1428
  console.log(`${result.run.id}: ${result.run.status}; ${result.task.id}: ${result.task.status}.`);
1316
1429
  if (result.learning) {
1317
1430
  if (result.learning.created.length) console.log(`Learning candidates: ${result.learning.created.join(", ")}.`);
@@ -1332,10 +1445,24 @@ function executeRootRoute(route) {
1332
1445
  if (!audit.passed) process.exitCode = 1;
1333
1446
  return;
1334
1447
  }
1448
+ if (noun === "review" && subject === "artifact" && routeArgs[0] === "--record") {
1449
+ const task = optionValue(routeArgs.slice(1), "--task");
1450
+ if (!task) throw new Error("--record requires --task TASK-NNN.");
1451
+ const result = recordArtifactReview(process.cwd(), {
1452
+ task,
1453
+ run: optionValue(routeArgs.slice(1), "--run"),
1454
+ title: optionValue(routeArgs.slice(1), "--title"),
1455
+ evidence: optionValues(routeArgs.slice(1), "--evidence")
1456
+ });
1457
+ refreshState(projectFile());
1458
+ console.log(`${result.review.record.id}: ${result.review.record.status}; ${result.audit.invariants} invariants; ${result.audit.findings.length} finding(s).`);
1459
+ if (!result.audit.passed) process.exitCode = 1;
1460
+ return;
1461
+ }
1335
1462
  if (noun === "config" && subject === "migrate") return runMigration(routeArgs);
1336
1463
  if (noun === "config" && subject === "doctor") {
1337
1464
  const target = ["all", "codex", "opencode", "claude"].includes(routeArgs[0]) ? routeArgs[0] : "all";
1338
- return doctor(target);
1465
+ return doctor(target, { strict: routeArgs.includes("--strict"), recover: routeArgs.includes("--recover") });
1339
1466
  }
1340
1467
  if (noun === "config" && subject === "update") {
1341
1468
  const target = ["all", "codex", "opencode", "claude"].includes(routeArgs[0]) ? routeArgs[0] : "all";
@@ -2097,6 +2224,33 @@ function runMigration(parts) {
2097
2224
  return;
2098
2225
  }
2099
2226
  try {
2227
+ const ledgerManifest = path.join(process.cwd(), ".scrumrun", ".migration", "run-ledger-v1", "manifest.json");
2228
+ if (v2Project() && actions[0] !== "--rollback") {
2229
+ if (actions[0] === "--dry-run") {
2230
+ const plan = planRunLedgerMigration(process.cwd());
2231
+ console.log(reportRunLedgerMigration(plan).trimEnd());
2232
+ if (plan.status === "blocked") process.exitCode = 1;
2233
+ } else {
2234
+ const result = applyRunLedgerMigration(process.cwd());
2235
+ if (result.status === "current") {
2236
+ console.log("ScrumRun project is already migrated to method 2.0.0 and the current Run ledger; no files changed.");
2237
+ } else {
2238
+ refreshState(path.join(process.cwd(), ".scrumrun"));
2239
+ console.log(result.output.trimEnd());
2240
+ console.log("Rollback remains available with: scrumrun migrate --to 2 --rollback");
2241
+ }
2242
+ }
2243
+ return;
2244
+ }
2245
+ if (actions[0] === "--rollback" && fs.existsSync(ledgerManifest)) {
2246
+ const manifest = JSON.parse(fs.readFileSync(ledgerManifest, "utf8"));
2247
+ if (["applied", "prepared"].includes(manifest.status)) {
2248
+ const result = rollbackRunLedgerMigration(process.cwd());
2249
+ refreshState(path.join(process.cwd(), ".scrumrun"));
2250
+ console.log(`Rolled back ScrumRun Run ledger migration: ${result.status}.`);
2251
+ return;
2252
+ }
2253
+ }
2100
2254
  if (actions[0] === "--dry-run") {
2101
2255
  const result = dryRunMigration(process.cwd());
2102
2256
  console.log(result.output.trimEnd());
@@ -2125,12 +2279,22 @@ function runMigration(parts) {
2125
2279
  }
2126
2280
  }
2127
2281
 
2128
- function doctor(target = "all", { compatibility = false, strict = false } = {}) {
2282
+ function doctor(target = "all", { compatibility = false, strict = false, recover = false } = {}) {
2129
2283
  const home = os.homedir();
2130
2284
  const checks = [];
2131
2285
  const commands = compatibility ? [...COMMANDS, ...COMPATIBILITY_COMMANDS] : COMMANDS;
2132
2286
  const skillContent = fs.readFileSync(path.join(templates, "shared", "skills", "scrumrun", "SKILL.md"), "utf8");
2133
2287
 
2288
+ if (recover) {
2289
+ const scrumDir = path.join(process.cwd(), ".scrumrun");
2290
+ if (!fs.existsSync(scrumDir)) throw new Error("Cannot recover transactions outside a ScrumRun project.");
2291
+ const recovered = recoverPendingTransactions(scrumDir);
2292
+ console.log(recovered.length
2293
+ ? `recovered ${recovered.map((item) => `${item.id}:${item.action}`).join(", ")}`
2294
+ : "recovered no pending kernel transactions");
2295
+ refreshState(scrumDir);
2296
+ }
2297
+
2134
2298
  function commandContent(command) {
2135
2299
  return command === "sc" ? renderRootPrompt() : renderCompatibilityPrompt(command);
2136
2300
  }
@@ -2245,7 +2409,7 @@ if (!command || command === "--help" || command === "-h") {
2245
2409
  runMigration(args.slice(1));
2246
2410
  } else if (command === "doctor") {
2247
2411
  const target = ["all", "codex", "opencode", "claude"].includes(args[1]) ? args[1] : "all";
2248
- doctor(target, { compatibility: args.includes("--compat"), strict: args.includes("--strict") });
2412
+ doctor(target, { compatibility: args.includes("--compat"), strict: args.includes("--strict"), recover: args.includes("--recover") });
2249
2413
  } else if (command === "claude") {
2250
2414
  const sub = args[1];
2251
2415
  if (sub === "install" || sub === "update") {
package/docs/COMMANDS.md CHANGED
@@ -16,11 +16,14 @@ Use `/sc` inside a supported AI client. The equivalent CLI form is `npx scrumrun
16
16
  /sc plan task --add|--list|--show|--run|--audit|--cancel|--retry
17
17
  /sc plan sprint --add|--list|--show|--start|--complete|--block
18
18
  /sc plan feature --add|--list|--show|--activate|--complete
19
- /sc plan run --list|--show|--validate|--learn|--complete|--resume|--fail|--block
19
+ /sc plan run --list|--show|--validate|--learn|--complete|--resume|--fail|--block [--note] [typed evidence flags]
20
+ /sc plan run --authorize-mutation RUN-NNN --path <relative-path> [--path ...]
21
+ /sc plan run --record-mutation RUN-NNN --permit MUT-id [--note] [--actor]
22
+ /sc plan run --satisfy-guardrail RUN-NNN --guardrail GR-NNN [typed evidence flags]
20
23
  /sc plan challenge <question>
21
24
  ```
22
25
 
23
- CLI-native: intake/approval, Task/Run list/show, Task retry, and Run transitions. A retry requires a failed, blocked, or partial Task and creates a new Run.
26
+ CLI-native: intake/approval, Task/Run list/show, Task retry, Mutation Gateway actions, Guardrail satisfaction, and Run transitions. A retry requires a failed, blocked, or partial Task and creates a new Run. Mutation permits expire after 15 minutes, authorize explicit relative paths only, and must be recorded immediately after the edit.
24
27
 
25
28
  ## Knowledge
26
29
 
@@ -46,11 +49,12 @@ Creation options include `--title`, `--content`, repeated `--evidence`, repeated
46
49
  /sc rules reviewer --add|--list|--show|--run
47
50
  /sc review code --run
48
51
  /sc review artifact --run
52
+ /sc review artifact --record --task TASK-NNN [--run RUN-NNN] [--title "..."] [--evidence "..."]
49
53
  /sc review migration --run
50
54
  /sc review release --run
51
55
  ```
52
56
 
53
- `review artifact --run` is CLI-native and returns a machine-readable 20-invariant project audit. Other review routes require repository reasoning and remain read-only unless fixes receive separate approval.
57
+ `review artifact --run` is read-only and returns a machine-readable 21-invariant project audit. `--record` reruns that audit and persists its exact pass/fail result as a canonical `REV-NNN`; supplied evidence is additive and cannot turn a failed audit into a pass. Other review routes require repository reasoning and remain read-only unless fixes receive separate approval.
54
58
 
55
59
  ## Config and lifecycle
56
60
 
@@ -59,11 +63,13 @@ Creation options include `--title`, `--content`, repeated `--evidence`, repeated
59
63
  /sc config init --local|--shared|--lean|--no-agent-hint|--force
60
64
  /sc config update [all|codex|opencode|claude] [--migrate]
61
65
  /sc config migrate --to 2 --dry-run|--apply|--rollback
62
- /sc config doctor [all|codex|opencode|claude] [--strict]
66
+ /sc config doctor [all|codex|opencode|claude] [--strict] [--recover]
63
67
  /sc config uninstall --force
64
68
  /sc config help <topic>
65
69
  ```
66
70
 
67
71
  Top-level CLI aliases (`init`, `update`, `migrate`, `doctor`, `uninstall`, `status`) remain available for shell automation. Ordinary update runs only a read-only migration preflight; `--migrate` is explicit application consent.
68
72
 
73
+ Run transitions accept typed evidence through `--command`, `--test`, `--file`, `--review`, `--decision`, `--insight`, `--risk`, or generic `--evidence kind:value`. `doctor --recover` is an explicit write that resolves only safe pending kernel transactions; doctor without it remains read-only.
74
+
69
75
  Run `npx scrumrun@latest commands` for grammar rendered directly from the current manifest.
@@ -35,4 +35,4 @@ Feature and Sprint provide context/grouping and do not own execution history. Re
35
35
 
36
36
  ## Generated projections
37
37
 
38
- `state.md` summarizes active work/memory. `map.md` summarizes bounded nodes/edges. SQLite stores the complete derived graph/search index. All are fingerprinted or explicitly stale and can be rebuilt from Markdown plus source code.
38
+ `state.md` summarizes active work/memory. `map.md` summarizes bounded nodes/edges. SQLite stores the complete derived graph/search index. All are fingerprinted or explicitly stale and can be rebuilt from Markdown plus source code. Freshness uses a metadata watch fast path and content-hash fallback; the watch is disposable evidence, never truth.
@@ -0,0 +1,43 @@
1
+ # ScrumRun 2.1.1 Local Release Scorecard
2
+
3
+ - Date: 2026-07-22
4
+ - Package: `2.1.1`
5
+ - Method contract: `2.0.0`
6
+ - Scope: local implementation and package readiness; external registry/tag/release verification remains owner-gated.
7
+
8
+ ## Scoring rule
9
+
10
+ A score of 9.5 or higher requires a single documented contract, machine enforcement on the critical path, adversarial/failure tests, deterministic recovery where mutation is involved, bounded performance, and an explicit residual-risk statement. Documentation alone cannot earn 9.5.
11
+
12
+ | Area | Score | Executable evidence |
13
+ |---|---:|---|
14
+ | Conceptual model | 9.7 | Frozen Feature → Task → Sprint → Run → Memory schema; Task/Sprint and retry invariants; ADR-015 and ADR-018. |
15
+ | Documentation architecture | 9.7 | SPEC is normative, CORE is operational, ADRs explain trade-offs, generated SCHEMA is drift-checked, README/skill/templates are conformance-tested. |
16
+ | Artifact kernel | 9.7 | One executable schema, safe-path checks, lossless frontmatter transitions, conflict refusal, fsync atomic writes, and durable multi-file transactions with failure injection. |
17
+ | Run history and audit | 9.8 | Stable ordered event ids, RFC3339 timestamps, typed evidence, state reconstruction, completion gates, retry preservation, explicit early-v2 migration, and byte-exact rollback. |
18
+ | Real conformance | 9.7 | Twenty normative invariants point to executable tests; clean-project audit, malformed artifacts, secrets, symlinks, migration interruption, transaction interruption, and cache corruption are exercised. |
19
+ | Local state and retrieval | 9.7 | Intake/state share one canonical fingerprint; state/map/SQLite expose staleness; metadata fast path falls back to full content hashes; cache schema mismatch rebuilds once; FTS5 capability is detected and a deterministic parameterized fallback preserves Node 22.13 retrieval. |
20
+ | Overall operation | 9.7 | Read-only intake, explicit approval, atomic Task/Run creation, Policy Engine ids, migration preflight/apply/rollback, package E2E, installed-asset doctor, exact Node 22.13 regression coverage, Node 22/24/26 CI definition, and release budgets. |
21
+
22
+ Minimum local score: **9.7/10**.
23
+
24
+ ## Release evidence
25
+
26
+ - Full suite: `npm test`.
27
+ - Performance suite: `npm run benchmark`.
28
+ - Contract drift: `scripts/generate-contract-docs.js --check` runs before tests.
29
+ - Project conformance: `/sc review artifact --run`, twenty-one invariants, zero findings at the release checkpoint.
30
+ - Installed integration: `doctor codex --strict`, exact prompt/skill hashes and zero project findings.
31
+ - Package boundary: `npm pack --dry-run --json`, explicit file inventory, no repository-local `.scrumrun/`, tests, vault, backup, migration state, or cache.
32
+ - Tarball E2E: install, v2 memory, ongoing v1 migration, rollback, doctor, and uninstall run from the packed package in the test suite.
33
+
34
+ The exact final tarball checksum belongs in the owner-gated release Review/Run after all included files are frozen; embedding a tarball's own checksum inside an included document would change that checksum.
35
+
36
+ ## Residual risks and gates
37
+
38
+ - Registry smoke, npm dist-tags, `v2.1.1` tag, push, and GitHub release are not proven by local tests and require explicit owner authorization.
39
+ - The built-in code-intelligence adapter currently covers JavaScript/TypeScript; other languages require replaceable adapters.
40
+ - Remote Node 22/24/26 CI must pass on the release commit; the exact Node 22.13 container passes locally but does not replace the external gate.
41
+ - Semantic retrieval is intentionally lexical/structural rather than a probabilistic embedding system; confirmed Markdown evidence remains the authority.
42
+
43
+ These are bounded release or extension risks, not hidden correctness claims. A failed external gate stops promotion and results in a new immutable SemVer; it never rewrites an existing npm version.
package/docs/RELEASE.md CHANGED
@@ -1,7 +1,9 @@
1
- # ScrumRun 2.0 Release Procedure
1
+ # ScrumRun Package Release Procedure
2
2
 
3
3
  Publication is owner-gated. Tests, packaging, local commits, and release metadata preparation do not authorize npm publication, git push, tags, GitHub releases, or dist-tag changes.
4
4
 
5
+ The npm package/CLI follows SemVer independently from the ScrumRun method contract. For this release, package `2.1.1` implements method `2.0.0`. Published packages `2.0.0` and `2.1.0` are immutable and must never be overwritten or reused.
6
+
5
7
  ## Local release gate
6
8
 
7
9
  ```bash
@@ -12,11 +14,13 @@ npm pack --dry-run
12
14
  npx scrumrun@latest sc review artifact --run # in a clean v2 fixture/current package equivalent
13
15
  ```
14
16
 
15
- Confirm package contents exclude repository-local `.scrumrun/` state, caches, migration records, backups, vaults, tests, and secrets. The `.scrumrun/` directory inside project templates is expected. Confirm `package.json`, README, changelog, SPEC, CORE, skill, migration guide, and tag all name 2.0.0.
17
+ Confirm package contents exclude repository-local `.scrumrun/` state, caches, migration records, backups, vaults, tests, and secrets. The `.scrumrun/` directory inside project templates is expected. Confirm package metadata, README, changelog, tarball filename, checksum, and Git tag agree on `2.1.1`; SPEC, CORE, artifact frontmatter, migration, and installed skill continue to declare method `2.0.0`.
18
+
19
+ Record the local evidence and residual risks against [`RELEASE-SCORECARD.md`](./RELEASE-SCORECARD.md). Scores describe local readiness only and never replace registry smoke or owner approval.
16
20
 
17
- ## Release candidate (owner approval required)
21
+ ## Optional registry candidate (separate owner approval required)
18
22
 
19
- 1. Set `2.0.0-rc.1` metadata and create a reviewed commit.
23
+ 1. Set a new unpublished prerelease such as `2.1.1-rc.1` and create a reviewed commit.
20
24
  2. `npm pack`; record tarball SHA-256/integrity.
21
25
  3. Publish to `next`, never `latest`:
22
26
 
@@ -28,17 +32,20 @@ Confirm package contents exclude repository-local `.scrumrun/` state, caches, mi
28
32
  5. Run init, intake/approval, memory confirmation/query, v1 update preflight/apply/rollback, doctor, and uninstall.
29
33
  6. Fix findings in a new RC; never replace an already-published version.
30
34
 
31
- ## Final promotion (new owner approval required)
35
+ An RC is immutable. Fixes create `rc.2`, `rc.3`, and so on. Skipping an RC does not skip any local, tarball, owner, registry-smoke, or final-promotion gate.
36
+
37
+ ## Final release (new owner approval required)
32
38
 
33
- 1. Set `2.0.0`, rerun every local/registry gate, and create the final metadata commit.
34
- 2. Create/push tag `v2.0.0` and publish npm `2.0.0`.
35
- 3. Promote `latest` only after registry smoke tests pass.
36
- 4. Create the GitHub release from `CHANGELOG.md` and link the migration guide.
37
- 5. Verify npm metadata, Git tag/commit, GitHub release, README, and checksums agree.
39
+ 1. Set `2.1.1`, rerun every local gate, inspect the exact tarball, and create the final local metadata commit.
40
+ 2. Stop and request explicit owner authorization for each external boundary.
41
+ 3. Publish npm `2.1.1` to `next` using the reviewed tarball/source commit (`npm publish --tag next`); never publish from a changed worktree and do not move `latest` yet.
42
+ 4. Install the registry artifact in a clean fixture and run init, intake/approval, Run lifecycle, memory query, v1 migration apply/rollback, doctor, and uninstall.
43
+ 5. Only after registry smoke passes, create/push tag `v2.1.1`, promote/verify `latest` with an explicit dist-tag command, and create the GitHub release from `CHANGELOG.md` with the migration guide and checksum.
44
+ 6. Verify npm metadata, dist-tags, Git tag/commit, GitHub release, README, and checksums all agree.
38
45
 
39
46
  ## Recovery
40
47
 
41
48
  - RC defect: publish another RC; keep `latest` unchanged.
42
- - Final package defect before `latest`: do not promote; publish `2.0.1` after correction if 2.0.0 already exists.
43
- - Defect after `latest`: assess deprecation vs immediate 2.0.1; never overwrite/unpublish without explicit owner decision and npm-policy review.
49
+ - Final package defect before `latest`: do not promote; publish a new patch after correction because `2.1.1` cannot be replaced.
50
+ - Defect after `latest`: assess deprecation vs immediate patch; never overwrite/unpublish without explicit owner decision and current npm-policy review.
44
51
  - Migration issue: stop promotion, preserve registry artifact/checksum, use the documented rollback fixture, and publish a corrected version.
package/docs/SCHEMA.md CHANGED
@@ -46,9 +46,20 @@ Task is the atomic unit. A Task may have zero or one Sprint. A Task may have man
46
46
  | Field | Kinds | Presence | Type | Meaning |
47
47
  |---|---|---|---|---|
48
48
  | `attempt` | run | required | positive integer | monotonic execution-attempt number within one Task |
49
+ | `ledger` | run | optional | integer 1 | canonical Run event-ledger schema; required for newly authored Runs |
50
+ | `guardrails` | run | optional | integer 1 | append-only Guardrail obligation schema |
51
+ | `workspace` | run | optional | integer 1 | workspace mutation-gateway schema |
49
52
 
50
53
  Native creation uses the declared initial statuses. Migration may restore a historical non-initial status only with provenance and validation.
51
54
 
55
+ ## Run event ledger
56
+
57
+ Newly authored Runs use `ledger: 1`. Their `## Events` section contains append-only JSON event blocks with stable ids in the form `RUN-NNN-EVT-NNN`.
58
+
59
+ Every event requires `schema`, `id`, contiguous `sequence`, RFC3339 `occurred_at`, `timestamp_precision`, `actor`, `from`, `to`, `reason`, and structured `evidence`. Event types are `transition`, `snapshot`, `guardrail`, `mutation`. Evidence kinds are `approval`, `command`, `test`, `file`, `review`, `decision`, `insight`, `risk`, `note`, `migration`, `legacy`, `guardrail`, `mutation`.
60
+
61
+ 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.
62
+
52
63
  ## Truth questions
53
64
 
54
65
  - **feature:** Why does this initiative exist?
@@ -65,4 +65,4 @@ npx scrumrun@latest sc knowledge study calculateFinalPrice
65
65
  npx scrumrun@latest sc knowledge context --clear
66
66
  ```
67
67
 
68
- SQLite is ignored and disposable. Queries default to 10 records/40 relations and hard-cap at 100/100. Match type, truth state, warnings, relation counts, and evidence are returned so recommendations remain explainable.
68
+ SQLite is ignored and disposable. The derived index records its search backend: FTS5/BM25 is selected when the current Node.js SQLite build supports it; otherwise ScrumRun uses deterministic parameterized token matching over the same artifact, code, and relation tables. Queries default to 10 records/40 relations and hard-cap at 100/100. Match type, truth state, warnings, relation counts, and evidence are returned so recommendations remain explainable.
@@ -25,8 +25,20 @@ Rollback detected post-migration work that it would erase. Copy/export wanted ch
25
25
  npx scrumrun@latest sc knowledge map --build
26
26
  ```
27
27
 
28
+ Fresh projections include a source fingerprint and a watch fingerprint. A matching watch avoids a full read. If file metadata changed, ScrumRun hashes canonical/source content before deciding whether the projection is actually stale. `check: "schema"` means the disposable SQLite format changed and one rebuild is required; `check: "hash"` means the safe fallback was used.
29
+
28
30
  Stale generated views are warnings, not canonical corruption.
29
31
 
32
+ ## Doctor reports `TRANSACTION_PENDING`
33
+
34
+ An approved multi-file mutation was interrupted after its durable journal was prepared. Ordinary doctor/audit is read-only and leaves the evidence untouched. Either retry the same approved operation, which recovers before writing, or explicitly run:
35
+
36
+ ```bash
37
+ npx scrumrun@latest doctor codex --recover --strict
38
+ ```
39
+
40
+ Prepared transactions restore their original bytes; committed transactions verify the applied bytes and finalize. Recovery refuses if a target changed to content matching neither journal side, because that would overwrite later owner work. Receipts contain hashes and outcomes, not file contents.
41
+
30
42
  ## SQLite is missing/corrupt
31
43
 
32
44
  Delete or clear only the disposable cache:
@@ -59,7 +71,7 @@ Use `doctor --compat` only while validating one-cycle v1 adapters.
59
71
 
60
72
  ## Node.js is unsupported
61
73
 
62
- ScrumRun 2.0 requires Node.js `>=22.13.0` because semantic indexing uses native `node:sqlite`. Upgrade Node, then rerun doctor.
74
+ ScrumRun 2.0 requires Node.js `>=22.13.0` because semantic indexing uses native `node:sqlite`. FTS5/BM25 is used when the runtime provides it; Node builds without FTS5 use the deterministic lexical fallback. Upgrade Node, then rerun doctor.
63
75
 
64
76
  ## Safe uninstall
65
77
 
@@ -9,7 +9,19 @@ const nouns = Object.freeze({
9
9
  task: ["--add [--type fix] [--status backlog]", "--list", "--show", "--run", "--audit", "--cancel", "--retry"],
10
10
  sprint: ["--add", "--list", "--show", "--start", "--complete", "--block"],
11
11
  feature: ["--add", "--list", "--show", "--activate", "--complete"],
12
- run: ["--list", "--show", "--validate", "--learn", "--complete", "--resume", "--fail", "--block"],
12
+ run: [
13
+ "--list",
14
+ "--show",
15
+ "--authorize-mutation <RUN-NNN> --path <relative-path>",
16
+ "--record-mutation <RUN-NNN> --permit <MUT-id> [--note] [--actor]",
17
+ "--satisfy-guardrail <RUN-NNN> --guardrail <GR-NNN> [--note] [--evidence] [--review] [--migration] [--actor]",
18
+ "--validate [--note] [--evidence] [--command] [--test] [--file] [--review] [--actor] [--at]",
19
+ "--learn [--note] [--evidence] [--decision] [--insight] [--file] [--actor] [--at]",
20
+ "--complete [--note] [--evidence] [--review] [--test] [--file] [--actor] [--at]",
21
+ "--resume [--note] [--evidence] [--risk] [--actor] [--at]",
22
+ "--fail [--note] [--evidence] [--risk] [--test] [--actor] [--at]",
23
+ "--block [--note] [--evidence] [--risk] [--actor] [--at]"
24
+ ],
13
25
  intake: ["<request>", "--request", "--approve"],
14
26
  challenge: ["<question>"]
15
27
  }
@@ -38,7 +50,7 @@ const nouns = Object.freeze({
38
50
  description: "run scoped evidence-based quality gates",
39
51
  subjects: {
40
52
  code: ["--run"],
41
- artifact: ["--run"],
53
+ artifact: ["--run", "--record --task <TASK-NNN> [--run <RUN-NNN>] [--title] [--evidence]"],
42
54
  migration: ["--run"],
43
55
  release: ["--run"]
44
56
  }
@@ -50,7 +62,7 @@ const nouns = Object.freeze({
50
62
  init: ["--local", "--shared", "--lean", "--no-agent-hint", "--force"],
51
63
  update: ["all [--migrate]", "codex [--migrate]", "opencode [--migrate]", "claude [--migrate]"],
52
64
  migrate: ["--to 2 --dry-run", "--to 2 --apply", "--to 2 --rollback"],
53
- doctor: ["all [--strict]", "codex [--strict]", "opencode [--strict]", "claude [--strict]"],
65
+ doctor: ["all [--strict] [--recover]", "codex [--strict] [--recover]", "opencode [--strict] [--recover]", "claude [--strict] [--recover]"],
54
66
  uninstall: ["--force"],
55
67
  help: ["<topic>"]
56
68
  }
@@ -31,7 +31,11 @@ ${grammarLines().join("\n")}
31
31
  - Intake, contextualization, policy, risk, classification, and planning are read-only until explicit approval.
32
32
  - Approved work creates/updates a Task and creates a Run; a Sprint only groups Tasks when a real timebox/batch exists.
33
33
  - Run execution follows \`executing → validating → learning → completed|failed|blocked\`.
34
+ - Run owns one structured, evidenced event ledger; Task synchronizes current status without copying Run history.
35
+ - Linked Task/Run writes use a durable recovery journal; audit reports pending recovery and never repairs without explicit authorization.
34
36
  - \`guardrails.md\` is canonical project policy; \`golden-rules.md\` is v1 compatibility only.
37
+ - Evaluate active Guardrails as \`passed\`, \`blocked\`, or \`deferred\`; cite exact \`GR-NNN\` ids and keep deferred execution gates visible.
38
+ - Persist deferred checks as Run obligations. Every material source edit requires a short-lived path-scoped Mutation Gateway permit and immediate hash recording; policy/workspace drift or unresolved obligations block completion.
35
39
  - Knowledge/Decision/Insight records require evidence; AI-proposed Insights remain \`candidate\` until confirmed.
36
40
  - Never print vault values or write before approval.
37
41
  - Unknown nouns, subjects, actions, ids, or ambiguous approval must produce a deterministic explanation, never a guessed mutation.