scrumrun 2.7.11 → 3.0.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.
package/lib/errors.js CHANGED
@@ -31,44 +31,44 @@ const CATALOG = Object.freeze({
31
31
  "SR-E-052": { summary: "Approval token expired.", remediation: "Re-run intake to obtain a fresh token before approving." },
32
32
 
33
33
  // Runs and ledger
34
- "SR-E-100": { summary: "Run not found.", remediation: "Check the RUN-NNN id with `sc plan run --list`." },
35
- "SR-E-101": { summary: "Run ledger contains an invalid event.", remediation: "Inspect the reported event id; use `sc plan run --render` for a human view or restore from git history." },
34
+ "SR-E-100": { summary: "Run not found.", remediation: "Check the RUN-NNN id with `scrumrun plan run --list`." },
35
+ "SR-E-101": { summary: "Run ledger contains an invalid event.", remediation: "Inspect the reported event id; use `scrumrun plan run --render` for a human view or restore from git history." },
36
36
  "SR-E-102": { summary: "Run transition rejected: missing validation or learning evidence.", remediation: "Complete `--validate` and `--learn` with typed evidence before `--complete`." },
37
37
  "SR-E-103": { summary: "Retry rejected because the previous Run is still active.", remediation: "Complete, fail, or block the current Run before creating a retry." },
38
38
 
39
39
  // Guardrails
40
40
  "SR-E-150": { summary: "Guardrail check blocked the operation.", remediation: "Read the reported GR-NNN, satisfy or retire it explicitly; guardrails never bypass silently." },
41
- "SR-E-151": { summary: "Guardrail obligation is still pending.", remediation: "Resolve each `pending guardrail` via `sc plan run --satisfy-guardrail` before completing the Run." },
41
+ "SR-E-151": { summary: "Guardrail obligation is still pending.", remediation: "Resolve each `pending guardrail` via `scrumrun plan run --satisfy-guardrail` before completing the Run." },
42
42
  "SR-E-152": { summary: "Guardrail declaration is malformed.", remediation: "Every active guardrail requires Status, Enforcement, Scope, and Rule fields; check .scrumrun/guardrails.md." },
43
43
 
44
44
  // Edit permits (Mutation Gateway)
45
- "SR-E-200": { summary: "No edit permit for this path.", remediation: "Request one with `sc plan run --authorize-mutation RUN-NNN --path <path>` before editing canonical or source files." },
45
+ "SR-E-200": { summary: "No edit permit for this path.", remediation: "Request one with `scrumrun plan run --authorize-mutation RUN-NNN --path <path>` before editing canonical or source files." },
46
46
  "SR-E-201": { summary: "Edit permit expired.", remediation: "Permits last 15 minutes. Authorize a new one and record the change immediately." },
47
47
  "SR-E-202": { summary: "Edit permit path scope mismatch.", remediation: "The permit does not cover the modified path. Request a new permit that lists it." },
48
48
  "SR-E-203": { summary: "File hash changed unexpectedly since the permit was issued.", remediation: "Someone else modified the file. Re-plan the change and request a fresh permit." },
49
49
 
50
50
  // Transactions and recovery
51
- "SR-E-250": { summary: "Pending kernel transaction cannot be recovered automatically.", remediation: "Run `sc config doctor --recover --dry-run` to preview; if it shows 'would overwrite owner changes', reconcile the file manually before applying." },
51
+ "SR-E-250": { summary: "Pending kernel transaction cannot be recovered automatically.", remediation: "Run `scrumrun config doctor --recover --dry-run` to preview; if it shows 'would overwrite owner changes', reconcile the file manually before applying." },
52
52
  "SR-E-251": { summary: "Journal fails integrity check.", remediation: "Inspect .scrumrun/.transactions/pending. Do not delete; contact support or restore from backup." },
53
53
 
54
54
  // Memory
55
- "SR-E-300": { summary: "Memory candidate rejected: missing resolvable evidence.", remediation: "Attach at least one --evidence path or `sc knowledge <subject> --propose` before `--confirm`." },
55
+ "SR-E-300": { summary: "Memory candidate rejected: missing resolvable evidence.", remediation: "Attach at least one --evidence path or `scrumrun knowledge <subject> --propose` before `--confirm`." },
56
56
  "SR-E-301": { summary: "Attempt to write into vault via canonical channel.", remediation: "vault.local.md is local-only and never indexed. Edit the file directly." },
57
57
 
58
58
  // Semantic index / code intel
59
- "SR-E-350": { summary: "Semantic index is stale.", remediation: "Rebuild with `sc knowledge map --build`. Cache is disposable; canonical memory is unaffected." },
60
- "SR-E-351": { summary: "Search backend advertised in the cache does not match this runtime.", remediation: "Delete .scrumrun/.cache/semantic-index.sqlite and re-run any `sc knowledge` query to rebuild against the current runtime." },
59
+ "SR-E-350": { summary: "Semantic index is stale.", remediation: "Rebuild with `scrumrun knowledge map --build`. Cache is disposable; canonical memory is unaffected." },
60
+ "SR-E-351": { summary: "Search backend advertised in the cache does not match this runtime.", remediation: "Delete .scrumrun/.cache/semantic-index.sqlite and re-run any `scrumrun knowledge` query to rebuild against the current runtime." },
61
61
 
62
62
  // Migration
63
- "SR-E-400": { summary: "Migration preflight failed.", remediation: "Run `npx scrumrun@latest update` (dry) to see blockers, resolve them, then apply with `--migrate`." },
63
+ "SR-E-400": { summary: "Migration preflight failed.", remediation: "Run `scrumrun update` (dry) to see blockers, resolve them, then apply with `--migrate`." },
64
64
  "SR-E-401": { summary: "Migration rollback requested but no backup was found.", remediation: "Rollback needs the ignored byte-exact backup created during --migrate. Restore from version control if the backup is gone." },
65
65
 
66
66
  // Conformance / doctor
67
67
  "SR-E-450": { summary: "Conformance check failed.", remediation: "The reported invariant identifies the exact violation; the message includes the file and expected shape." },
68
- "SR-E-451": { summary: "Installed client asset is stale.", remediation: "Re-run `npx scrumrun@latest update` for the specific client. `doctor --strict` shows which files diverge." },
68
+ "SR-E-451": { summary: "Installed client asset is stale.", remediation: "Re-run `scrumrun update` for the specific client. `doctor --strict` shows which files diverge." },
69
69
 
70
70
  // Configuration / install
71
- "SR-E-500": { summary: "ScrumRun project not initialized.", remediation: "Run `npx scrumrun@latest init` in the repository root." },
71
+ "SR-E-500": { summary: "ScrumRun project not initialized.", remediation: "Run `scrumrun init` in the repository root." },
72
72
  "SR-E-501": { summary: "Unsupported Node.js runtime.", remediation: "ScrumRun requires Node.js >=22.13.0 for native SQLite. Upgrade Node and retry." }
73
73
  });
74
74
 
@@ -97,8 +97,8 @@ ${backlogTasks.length ? backlogTasks.join("\n") : "- No backlog Tasks."}
97
97
  - Guardrails: .scrumrun/guardrails.md
98
98
  - Tasks: .scrumrun/tasks/ (${counts.task || 0} total)
99
99
  - Runs: .scrumrun/runs/ (${counts.run || 0} total)
100
- - Semantic: sc knowledge study "<topic>" for deep queries
101
- - Full state: rebuild with sc config doctor --recover
100
+ - Semantic: scrumrun knowledge study "<topic>" for deep queries
101
+ - Full state: rebuild with scrumrun config doctor --recover
102
102
  `;
103
103
 
104
104
  return { content, sourceFingerprint, watchFingerprint: watch.fingerprint, sourceFiles: watch.files };
@@ -419,9 +419,82 @@ function prepareCompletion(projectRoot, runArtifact, options = {}) {
419
419
  return { record, body, resolved, status: "passed" };
420
420
  }
421
421
 
422
+ // The normal v3 path is session based: an agent works freely after approval
423
+ // and the kernel verifies the complete workspace delta once, at completion.
424
+ // The older permit chain remains available for strict teams, but is no longer
425
+ // required to produce trustworthy evidence for ordinary work.
426
+ function prepareSessionCompletion(projectRoot, runArtifact, options = {}) {
427
+ if (runArtifact.record.guardrails !== 1 || runArtifact.record.workspace !== 1) {
428
+ return { record: runArtifact.record, body: runArtifact.body, changes: [], resolved: [], status: "legacy" };
429
+ }
430
+ const policy = policyState(projectRoot);
431
+ assertPolicyBound(runArtifact, policy);
432
+ const baseline = expectedWorkspace(runArtifact);
433
+ if (!baseline) throw new Error(`${runArtifact.record.id} has no workspace baseline; retry the Task before finalizing.`);
434
+ const actual = workspaceState(projectRoot);
435
+ const changes = changesBetween(baseline, actual);
436
+ const readOnly = readOnlyPaths(policy.config).map((value) => normalizedRelative(projectRoot, value));
437
+ const protectedChanges = changes.filter((change) => readOnly.some((item) => change.path === item || change.path.startsWith(`${item}/`)));
438
+ if (protectedChanges.length) throw new Error(`READ_ONLY_PATH: final workspace check found changes in ${protectedChanges.map((item) => item.path).join(", ")}.`);
439
+ const unsafeKinds = changes.filter((change) => !["file", "missing", "clean"].includes(change.after_kind));
440
+ if (unsafeKinds.length) throw new Error(`UNSAFE_MUTATION_TYPE: final workspace check found ${unsafeKinds.map((item) => item.path).join(", ")}.`);
441
+ const unscannable = changes.filter((change) => change.after_kind === "file" && change.after_scan !== "text");
442
+ if (unscannable.length) throw new Error(`UNSCANNABLE_MUTATION: final workspace check cannot inspect ${unscannable.map((item) => item.path).join(", ")}.`);
443
+ const leaked = changes.filter((change) => change.new_secret_fingerprints.length);
444
+ if (leaked.length) throw new Error(`SECRET_BOUNDARY: final workspace check found secret-like content in ${leaked.map((item) => item.path).join(", ")}.`);
445
+
446
+ let record = runArtifact.record;
447
+ let body = runArtifact.body;
448
+ if (changes.length) {
449
+ const appended = appendMutationEvent(record, body, {
450
+ mutation_id: `MUT-${sha256(`${runArtifact.record.id}:${baseline.fingerprint}:${actual.fingerprint}`).slice(0, 16)}`,
451
+ paths: changes.map((change) => change.path).sort(),
452
+ changes: changes.map(({ new_secret_fingerprints, ...change }) => change),
453
+ workspace_before: baseline.fingerprint,
454
+ workspace_after: publicWorkspace(actual),
455
+ policy_fingerprint: policy.fingerprint
456
+ }, {
457
+ note: options.note || `Final session audit verified ${changes.length} workspace change(s).`,
458
+ actor: options.actor || "agent",
459
+ evidence: [{ kind: "mutation", summary: `${changes.length} changed path(s) passed the final workspace audit.` }]
460
+ });
461
+ record = appended.record;
462
+ body = appended.body;
463
+ }
464
+ const resolved = [];
465
+ const repository = new ArtifactRepository(path.join(projectRoot, ".scrumrun"));
466
+ const supplied = options.guardrailEvidence || {};
467
+ const automatic = new Set(["builtin:owner-work", "builtin:read-only-path", "builtin:secret-boundary"]);
468
+ for (const obligation of guardrailState(record, body).filter((item) => item.status !== "passed")) {
469
+ const evidence = automatic.has(obligation.enforcement)
470
+ ? [{ kind: "mutation", summary: "Verified by the final workspace audit." }]
471
+ : Array.isArray(supplied[obligation.guardrail]) ? supplied[obligation.guardrail] : [];
472
+ if (!evidence.length) {
473
+ throw new Error(`GUARDRAILS_PENDING: ${obligation.guardrail} needs final evidence in the Task's ## Guardrail Evidence section.`);
474
+ }
475
+ assertGateEvidence(projectRoot, repository, requiredEvidenceKind(obligation.enforcement), evidence, obligation.guardrail);
476
+ const appended = appendGuardrailEvent(record, body, obligation, "passed", {
477
+ note: `${obligation.guardrail} passed at the final session checkpoint.`,
478
+ actor: "finalize",
479
+ occurredAt: options.occurredAt,
480
+ evidence
481
+ });
482
+ record = appended.record;
483
+ body = appended.body;
484
+ resolved.push(obligation.guardrail);
485
+ }
486
+ const transactions = pendingTransactionStatus(path.join(projectRoot, ".scrumrun"));
487
+ if (transactions.error || transactions.pending.length) throw new Error(`TRANSACTION_GATE: ${transactions.error || "pending canonical transaction"}.`);
488
+ return { record, body, changes, resolved, status: "passed", policy: policy.fingerprint };
489
+ }
490
+
422
491
  function auditActiveWorkspace(projectRoot, runArtifact) {
423
492
  try {
424
493
  if (!runArtifact.record || runArtifact.record.workspace !== 1 || ["completed", "failed", "blocked"].includes(runArtifact.record.status)) return null;
494
+ // Session Runs intentionally accumulate unrecorded workspace changes until
495
+ // their single final checkpoint. Those changes are audited by --finalize,
496
+ // not reported as a strict-mode bypass while work is still in progress.
497
+ if (runArtifact.record.execution === "session") return null;
425
498
  verifyWorkspaceIntegrity(projectRoot, runArtifact);
426
499
  return null;
427
500
  } catch (error) {
@@ -440,6 +513,7 @@ module.exports = {
440
513
  permitsOverlap,
441
514
  policyState,
442
515
  prepareCompletion,
516
+ prepareSessionCompletion,
443
517
  publicWorkspace,
444
518
  recordMutation,
445
519
  satisfyGuardrail,
@@ -23,7 +23,7 @@ const { currentBranch } = require("./workspace-state");
23
23
  const { extractLearningCandidates } = require("../code-intel/learning");
24
24
  const { appendRunEvent, appendTechnicalSummary, createRunBody, instant } = require("./run-ledger");
25
25
  const { generateBriefing, generateErrorsReport } = require("./briefing");
26
- const { assertCanonicalWrite, policyState, prepareCompletion, publicWorkspace, verifyWorkspaceIntegrity, workspaceState } = require("./mutation-gateway");
26
+ const { assertCanonicalWrite, policyState, prepareCompletion, prepareSessionCompletion, publicWorkspace, verifyWorkspaceIntegrity, workspaceState } = require("./mutation-gateway");
27
27
  const { recoverPendingTransactions, runKernelTransaction } = require("../v2/transaction");
28
28
 
29
29
  const TERMINAL = new Set(["completed", "failed", "cancelled", "resolved", "rejected", "deprecated", "invalidated", "archived", "passed"]);
@@ -164,6 +164,7 @@ function approveRequestUnlocked(projectRoot, token, { failurePoint = null, inter
164
164
  ledger: 1,
165
165
  guardrails: 1,
166
166
  workspace: 1,
167
+ execution: "session",
167
168
  approval_id: approvalId
168
169
  };
169
170
  const taskLinks = { feature: featureId, sprint: sprintId };
@@ -267,6 +268,94 @@ function transitionedRunContent(content, nextStatus, options = {}) {
267
268
  };
268
269
  }
269
270
 
271
+ function sectionBody(markdown, heading) {
272
+ const match = new RegExp(`^## ${heading}[ \\t]*\\r?\\n\\r?\\n([\\s\\S]*?)(?=^## |(?![\\s\\S]))`, "m").exec(markdown || "");
273
+ return match ? match[1].trim() : null;
274
+ }
275
+
276
+ // Human-authored evidence stays beside the work in the Task, rather than
277
+ // forcing a separate CLI invocation for every guardrail. The single final
278
+ // checkpoint parses it deterministically and rejects malformed entries.
279
+ //
280
+ // - GR-005 | review | REV-001 | Scoped architecture review passed.
281
+ // - GR-006 | migration | db/migrations/20260831.sql | Migration verified.
282
+ // - GR-007 | note | Request authorization confirmed.
283
+ function finalGuardrailEvidence(taskBody) {
284
+ const content = sectionBody(taskBody, "Guardrail Evidence");
285
+ if (!content) return {};
286
+ const result = {};
287
+ for (const line of content.split(/\r?\n/)) {
288
+ if (!line.trim()) continue;
289
+ const raw = line.match(/^\s*-\s*(GR-\d{3,})\s*\|\s*([a-z-]+)\s*\|\s*(.*?)\s*$/i);
290
+ if (!raw) throw new Error(`Invalid Guardrail Evidence entry: ${line.trim()}`);
291
+ const [, guardrail, kind, remainder] = raw;
292
+ const parts = remainder.split("|").map((part) => part.trim()).filter(Boolean);
293
+ if (!parts.length) throw new Error(`${guardrail} needs evidence content.`);
294
+ const allowed = new Set(["approval", "command", "test", "file", "review", "decision", "insight", "risk", "note", "migration", "legacy", "guardrail", "mutation"]);
295
+ if (!allowed.has(kind)) throw new Error(`${guardrail} has unsupported evidence kind: ${kind}.`);
296
+ const evidence = { kind };
297
+ if (parts.length > 1) evidence.ref = parts.shift();
298
+ evidence.summary = parts.join(" | ") || (evidence.ref ? `${kind} evidence: ${evidence.ref}` : null);
299
+ if (!evidence.summary) throw new Error(`${guardrail} needs an evidence summary.`);
300
+ if (!result[guardrail]) result[guardrail] = [];
301
+ result[guardrail].push(evidence);
302
+ }
303
+ return result;
304
+ }
305
+
306
+ function finalizeRunUnlocked(projectRoot, runId, { note = null, summary = null, actor = "agent", occurredAt = null } = {}) {
307
+ const scrumDir = path.join(projectRoot, ".scrumrun");
308
+ recoverPendingTransactions(scrumDir);
309
+ const repository = new ArtifactRepository(scrumDir);
310
+ const runArtifact = repository.read("run", runId);
311
+ if (!runArtifact || runArtifact.errors.length) throw new Error(`Run not found or invalid: ${runId}`);
312
+ if (runArtifact.record.status !== "executing") throw new Error(`Lightweight finalization requires an executing Run; ${runId} is ${runArtifact.record.status}.`);
313
+ const taskArtifact = repository.read("task", runArtifact.record.task);
314
+ if (!taskArtifact || taskArtifact.errors.length) throw new Error(`Task not found or invalid for ${runId}.`);
315
+ const technicalSummary = summary || sectionBody(taskArtifact.body, "Technical Summary");
316
+ if (!technicalSummary) throw new Error(`TASK_SUMMARY_REQUIRED: add ## Technical Summary to ${taskArtifact.record.id}, or pass --summary.`);
317
+ const effectiveActor = actor === "agent" ? (agentIdentity(scrumDir) || "agent") : actor;
318
+ const prepared = prepareSessionCompletion(projectRoot, runArtifact, {
319
+ note,
320
+ actor: effectiveActor,
321
+ occurredAt,
322
+ guardrailEvidence: finalGuardrailEvidence(taskArtifact.body)
323
+ });
324
+ let runContent = serializeArtifact(prepared.record, prepared.body);
325
+ for (const [status, evidence] of [
326
+ ["validating", [{ kind: "test", summary: "Final session integrity audit passed." }]],
327
+ ["learning", [{ kind: "insight", summary: "Learning extraction scheduled from the completed session." }]],
328
+ ["completed", [{ kind: "note", summary: note || "Completed through the final session checkpoint." }]]
329
+ ]) {
330
+ runContent = transitionedRunContent(runContent, status, { actor: effectiveActor, occurredAt, note, evidence }).content;
331
+ }
332
+ runContent = appendTechnicalSummary(parseArtifact(runContent).record, runContent, technicalSummary);
333
+ const taskPrevious = fs.readFileSync(taskArtifact.file, "utf8");
334
+ let taskContent = taskPrevious;
335
+ let taskNext = null;
336
+ for (const status of ["validating", "learning", "completed"]) {
337
+ taskNext = transitionedArtifactContent(taskContent, "task", status, new Date().toISOString().slice(0, 10));
338
+ taskContent = taskNext.content;
339
+ }
340
+ runKernelTransaction(scrumDir, "finalize-lightweight-session", [
341
+ { file: runArtifact.file, previous: fs.readFileSync(runArtifact.file, "utf8"), next: runContent },
342
+ { file: taskArtifact.file, previous: taskPrevious, next: taskNext.content }
343
+ ]);
344
+ refreshState(scrumDir);
345
+ let learning = null;
346
+ try {
347
+ learning = extractLearningCandidates(projectRoot, runId);
348
+ } catch (error) {
349
+ learning = { created: [], warnings: [`Learning extraction did not block finalization: ${error.message}`] };
350
+ }
351
+ refreshState(scrumDir);
352
+ return { run: parseArtifact(runContent).record, task: taskNext.record, changes: prepared.changes.length, resolved: prepared.resolved, learning };
353
+ }
354
+
355
+ function finalizeRun(projectRoot, runId, options = {}) {
356
+ return withArtifactLock(path.join(projectRoot, ".scrumrun"), `run-${String(runId).toLowerCase()}`, () => finalizeRunUnlocked(projectRoot, runId, options));
357
+ }
358
+
270
359
  function transitionRunUnlocked(projectRoot, runId, nextStatus, { note = null, evidence = [], actor = "agent", occurredAt = null, summary = null, failurePoint = null, interruptPoint = null } = {}) {
271
360
  const scrumDir = path.join(projectRoot, ".scrumrun");
272
361
  const effectiveActor = actor === "agent" ? (agentIdentity(scrumDir) || "agent") : actor;
@@ -376,6 +465,7 @@ function retryTaskUnlocked(projectRoot, taskId, { note = "Retry explicitly appro
376
465
  ledger: 1,
377
466
  guardrails: 1,
378
467
  workspace: 1,
468
+ execution: "session",
379
469
  approval_id: taskArtifact.record.approval_id || null
380
470
  };
381
471
  const runBody = createRunBody(run, {
@@ -447,6 +537,7 @@ function startBacklogTaskUnlocked(projectRoot, taskId, { note = "Backlog Task st
447
537
  ledger: 1,
448
538
  guardrails: 1,
449
539
  workspace: 1,
540
+ execution: "session",
450
541
  approval_id: taskArtifact.record.approval_id || null
451
542
  };
452
543
  const runBody = createRunBody(run, {
@@ -495,7 +586,7 @@ const ADD_PLAN_INITIAL_STATUS = Object.freeze({
495
586
  const TASK_TYPES = new Set(["task", "fix", "docs", "discovery"]);
496
587
 
497
588
  function planArtifactBody(kind, title, created) {
498
- const source = `- ${created}: created via CLI (\`sc plan ${kind} --add\`).`;
589
+ const source = `- ${created}: created via CLI (\`scrumrun plan ${kind} --add\`).`;
499
590
  if (kind === "task") {
500
591
  return [
501
592
  `# ${title}`,
@@ -598,4 +689,4 @@ function addPlanArtifact(projectRoot, kind, label, options = {}) {
598
689
  return withArtifactLock(scrumDir, "create", () => addPlanArtifactUnlocked(projectRoot, kind, label, options));
599
690
  }
600
691
 
601
- module.exports = { addPlanArtifact, approveRequest, nextBacklogTask, nextId, refreshErrors, refreshState, renderState, retryTask, startBacklogTask, stateFingerprint, stateIsStale, transitionRun };
692
+ module.exports = { addPlanArtifact, approveRequest, finalizeRun, nextBacklogTask, nextId, refreshErrors, refreshState, renderState, retryTask, startBacklogTask, stateFingerprint, stateIsStale, transitionRun };
@@ -79,7 +79,7 @@ function auditProject(projectRoot) {
79
79
  if (methodMarker.method !== METHOD_VERSION) findings.push(finding("critical", "METHOD_VERSION", `method.json must declare ${METHOD_VERSION}.`, marker));
80
80
  const expectedPaths = canonicalPaths();
81
81
  if (!methodMarker.paths || typeof methodMarker.paths !== "object" || Array.isArray(methodMarker.paths)) {
82
- findings.push(finding("high", "METHOD_PATHS_MISSING", `method.json must declare a canonical "paths" block so agents navigate by declaration, not by search. Run \`npx scrumrun@latest update --migrate\` to backfill.`, marker));
82
+ findings.push(finding("high", "METHOD_PATHS_MISSING", `method.json must declare a canonical "paths" block so agents navigate by declaration, not by search. Run \`scrumrun update --migrate\` to backfill.`, marker));
83
83
  } else {
84
84
  const drift = diffPathIndex(expectedPaths, methodMarker.paths);
85
85
  for (const entry of drift) findings.push(finding("high", "METHOD_PATHS_DRIFT", `method.json paths[${entry.label}] is ${entry.actual === undefined ? "missing" : `"${entry.actual}"`}; expected "${entry.expected}". Regenerate via \`update --migrate\`.`, marker));
@@ -152,7 +152,7 @@ function auditProject(projectRoot) {
152
152
  if (run.record && run.record.ledger === RUN_LEDGER_VERSION) {
153
153
  const ledger = validateRunLedger(run.record, run.body);
154
154
  if (ledger.errors.length) {
155
- findings.push(finding("high", "RUN_WRITE_BYPASS", `${run.record.id}: ledger has ${ledger.errors.length} validation error(s); this Run was likely written without going through the CLI. Recover with \`sc plan run --normalize-legacy --dry-run\` then re-run without --dry-run.`, run.file));
155
+ findings.push(finding("high", "RUN_WRITE_BYPASS", `${run.record.id}: ledger has ${ledger.errors.length} validation error(s); this Run was likely written without going through the CLI. Recover with \`scrumrun plan run --normalize-legacy --dry-run\` then re-run without --dry-run.`, run.file));
156
156
  for (const error of ledger.errors) findings.push(finding("high", "RUN_LEDGER_INVALID", `${run.record.id}: ${error}`, run.file));
157
157
  }
158
158
  } else if (run.record) {
@@ -198,7 +198,17 @@ function auditProject(projectRoot) {
198
198
  const attempts = (records.run || [])
199
199
  .filter((run) => run.record && !run.errors.length && run.record.task === task.record.id)
200
200
  .sort((left, right) => right.record.attempt - left.record.attempt);
201
- if (!attempts.length) continue;
201
+ if (!attempts.length) {
202
+ if (["running", "validating", "learning", "partial"].includes(task.record.status)) {
203
+ findings.push(finding(
204
+ "high",
205
+ "TASK_ORPHANED",
206
+ `${task.record.id}.status is ${task.record.status}, but no canonical Run references it. Review with \`scrumrun repair --recover-orphan-tasks\`, then apply the explicit recovery and start the Task to create its first Run.`,
207
+ task.file
208
+ ));
209
+ }
210
+ continue;
211
+ }
202
212
  const latest = attempts[0].record;
203
213
  const taskStatuses = {
204
214
  executing: "running",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scrumrun",
3
- "version": "2.7.11",
3
+ "version": "3.0.1",
4
4
  "description": "Evidence-driven Agile runtime and semantic project memory for AI coding agents.",
5
5
  "bin": {
6
6
  "scrumrun": "bin/scrumrun.js",
@@ -1,8 +1,8 @@
1
1
  # AGENTS.md - {{PROJECT_NAME}}
2
2
 
3
- ## ScrumRun 2.0
3
+ ## ScrumRun 3.0
4
4
 
5
- This project uses ScrumRun. The method is mandatory; `/sc` is its single optional shortcut.
5
+ This project uses ScrumRun. The method is mandatory; the direct CLI is `scrumrun <noun> <subject> <action>`. `/sc` is only an optional client shortcut.
6
6
 
7
7
  For normal work, read:
8
8
 
@@ -20,10 +20,12 @@ After approval:
20
20
  - Run is one execution attempt and follows `executing → validating → learning → completed|failed|blocked`;
21
21
  - a retry creates a new Run and preserves the old one;
22
22
  - record a `## Technical Summary` at completion so the next agent inherits what was done;
23
- - when a Run completes and work remains queued, surface it with `sc plan task --next` and start it with `sc plan task --start` — starting is explicit approval;
23
+ - work directly in code and the linked Task Markdown after approval; do not call `npx scrumrun@latest` during execution;
24
+ - record one `## Guardrail Evidence` line per non-automatic guardrail in the Task, then run `scrumrun plan run --finalize RUN-NNN` once to validate and close the Run;
25
+ - when a Run completes and work remains queued, surface it with `scrumrun plan task --next` and start it with `scrumrun plan task --start` — starting is explicit approval;
24
26
  - learning proposes evidence-backed Knowledge, Decisions, or candidate Insights;
25
- - every application/source edit requires a path-scoped Mutation Gateway permit, immediate hash recording, and resolution of the Run's Guardrail obligations before completion.
27
+ - the final checkpoint verifies all Guardrails, workspace changes, protected paths, and secret boundaries before completion; use the path-scoped Mutation Gateway only when the owner requests strict execution.
26
28
 
27
29
  Never bypass guardrails or edit around the Mutation Gateway, overwrite owner work, treat generated state/cache as truth, auto-confirm AI knowledge, auto-migrate a v1 project, or print vault values.
28
30
 
29
- If `/sc` is unavailable, follow the equivalent workflow in `.scrumrun/core.md` manually.
31
+ Never use `npx scrumrun@latest` in the normal work loop. If the installed CLI is unavailable, stop and report that blocker rather than substituting a network command.
@@ -5,13 +5,13 @@ description: Use when initializing or migrating ScrumRun, handling product reque
5
5
 
6
6
  # ScrumRun 2.0
7
7
 
8
- ScrumRun is an evidence-driven Agile runtime for AI agents. Its canonical command is:
8
+ ScrumRun is an evidence-driven Agile runtime for AI agents. Its canonical shell command is:
9
9
 
10
10
  ```text
11
- /sc <noun> <subject> <action> [args]
11
+ scrumrun <noun> <subject> <action> [args]
12
12
  ```
13
13
 
14
- The five nouns are `plan`, `knowledge`, `rules`, `review`, and `config`. If the command is incomplete, show only the valid next tokens and do not guess.
14
+ The five nouns are `plan`, `knowledge`, `rules`, `review`, and `config`. `/sc` is an optional AI-client shortcut; `scrumrun sc ...` is a compatibility alias. If the command is incomplete, show only the valid next tokens and do not guess.
15
15
 
16
16
  When a project contains `.scrumrun/`, use ScrumRun for product work even if the user did not type `/sc`. Natural-language work begins as `plan intake`.
17
17
 
@@ -33,10 +33,10 @@ Normal hot path:
33
33
  2. read `AGENTS.md`;
34
34
  3. read `.scrumrun/guardrails.md`;
35
35
  4. read `.scrumrun/state.md` — the **briefing**: active work, recent completions with their technical summaries, open decisions, active memory, backlog queue, and pointers;
36
- 5. follow the briefing's pointers to only the relevant canonical artifacts; go deeper only when the briefing lacks what you need (`## Where to look`, `sc knowledge study "<topic>"`);
36
+ 5. follow the briefing's pointers to only the relevant canonical artifacts; go deeper only when the briefing lacks what you need (`## Where to look`, `scrumrun knowledge study "<topic>"`);
37
37
  6. load `.scrumrun/core.md` when the method contract or an exceptional transition is needed.
38
38
 
39
- **Never write Run events by hand.** Only the CLI mutates `runs/RUN-NNN.md`: `sc plan run --validate | --learn | --complete | --resume | --fail | --block | --satisfy-guardrail | --authorize-mutation | --record-mutation`. Direct edits produce invalid ledger events (unknown `type` like `execution`/`validation`/`learning`, unknown evidence `kind` like `guardrail-check`/`build`/`task-status`, missing snapshot invariant) and break project conformance. If the CLI does not expose the shape you need, propose a spec change instead of inventing vocabulary. Recover hand-written Runs via `sc plan run --normalize-legacy` — originals are preserved byte-exact under `.scrumrun/.migration-backup/runs/`.
39
+ **Never write Run events by hand.** Work directly in code and the linked Task Markdown after approval, then let the one final CLI checkpoint mutate `runs/RUN-NNN.md`: `scrumrun plan run --finalize RUN-NNN`. It creates the validated transition ledger, audits the full workspace delta, and records the Guardrail results together. Do not invoke `scrumrun` during normal execution. The older `--validate | --learn | --complete | --satisfy-guardrail | --authorize-mutation | --record-mutation` commands remain only for owner-requested strict mode. Recover hand-written Runs via `scrumrun plan run --normalize-legacy` — originals are preserved byte-exact under `.scrumrun/.migration-backup/runs/`.
40
40
 
41
41
  Lean mode is a read policy, not an incomplete store. Generated files and `.scrumrun/.cache/` are never authoritative.
42
42
 
@@ -81,7 +81,7 @@ At intake:
81
81
 
82
82
  Evaluate every active Guardrail into a structured `passed`, `blocked`, or `deferred` result. Report blocked results with the exact `GR-NNN` id and reason code. Keep deferred results visible and enforce them at the mutation, migration, review, or owner gate they identify; never describe a deferred check as passed.
83
83
 
84
- Assert the classification explicitly when the keyword inference is wrong: `sc plan intake "…" --type fix|task|feature|docs|discovery`. Attach a short technical explanation before approval with `--preview "…"` (rendered in the terminal, bound into the token, stored as `## Preview` on the Task).
84
+ Assert the classification explicitly when the keyword inference is wrong: `scrumrun plan intake "…" --type fix|task|feature|docs|discovery`. Attach a short technical explanation before approval with `--preview "…"` (rendered in the terminal, bound into the token, stored as `## Preview` on the Task).
85
85
 
86
86
  Do not create canonical artifacts, change status, edit application code, or treat ambiguous acknowledgement as approval. Temporary context may exist only in ignored disposable cache.
87
87
 
@@ -101,19 +101,17 @@ During execution:
101
101
  1. keep the change inside the approved Task scope;
102
102
  2. preserve existing owner work and unrelated dirty files;
103
103
  3. define or confirm the Task's `## Acceptance Criteria` before execution and check them off as evidence;
104
- 4. enforce guardrails before every material mutation: obtain a short-lived path-scoped Mutation Gateway permit before editing and record its verified before/after hashes immediately afterward;
104
+ 4. work normally: edit code and update the Task's `## Technical Summary` and, for non-automatic rules, `## Guardrail Evidence` as evidence becomes available;
105
105
  5. validate in proportion to risk and against the acceptance criteria;
106
- 6. record exactly one structured `RUN-NNN-EVT-NNN` event per state transition, with RFC3339 time, actor, reason, and typed evidence;
107
- 7. run configured reviewers;
108
- 8. extract candidate learning only after validation;
109
- 9. record a `## Technical Summary` at completion (`sc plan run --complete --summary "…"`) so the next agent inherits what was actually done;
110
- 10. complete the Run, then Task, and only then the Sprint when its whole batch is done.
106
+ 6. run configured reviewers when a Guardrail requires one;
107
+ 7. finish once with `scrumrun plan run --finalize RUN-NNN`; it verifies every changed path, policy, secret boundary, and guardrail evidence before creating the structured Run events and completing the Task;
108
+ 8. use path-scoped Mutation Gateway commands only when the owner explicitly requests strict execution.
111
109
 
112
110
  Never overwrite a prior attempt. Never mark work complete because time/token budget ended.
113
111
 
114
- When a Run completes and work remains queued, the briefing's `## Next Up` names the next backlog Task. Surface it with `sc plan task --next` and start it with `sc plan task --start [TASK-NNN]` — starting is the explicit approval; the owner can always decline. Each agent declares its identity via `SCRUMRUN_AGENT` (or `Agent Identity` in `config.md`); it is recorded as the Task `assignee` and the Run event `actor`.
112
+ When a Run completes and work remains queued, the briefing's `## Next Up` names the next backlog Task. Surface it with `scrumrun plan task --next` and start it with `scrumrun plan task --start [TASK-NNN]` — starting is the explicit approval; the owner can always decline. Each agent declares its identity via `SCRUMRUN_AGENT` (or `Agent Identity` in `config.md`); it is recorded as the Task `assignee` and the Run event `actor`.
115
113
 
116
- Every deferred policy result is an append-only Run obligation. Unrecorded workspace drift, policy drift, an expired/missing permit, out-of-scope changes, unsafe symlinks, newly introduced secret-like content, or an unresolved obligation blocks validation/completion. The ignored permit cache is disposable; deleting it invalidates outstanding permits and never creates authority.
114
+ Every deferred policy result is an append-only Run obligation. The final checkpoint fails closed on policy drift, protected-path changes, unsafe symlinks, unscannable content, newly introduced secret-like content, or missing Guardrail Evidence. In strict mode it additionally requires the permit chain. The ignored permit cache is disposable; deleting it invalidates outstanding strict-mode permits and never creates authority.
117
115
 
118
116
  Run is the sole operational-history authority. Task synchronizes current status without copying Run events. Validation, learning, completion, failure, block, and resume require a reason or structured evidence; completion also requires evidenced validation and learning. Early v2 prose Runs are migrated explicitly, with deterministic chains recovered and uncertain history represented as an evidenced snapshot.
119
117
 
@@ -157,13 +155,13 @@ scrumrun migrate --to 2 --apply
157
155
  scrumrun migrate --to 2 --rollback
158
156
  ```
159
157
 
160
- Inside a v1 project, `npx scrumrun@latest update` runs a read-only preflight. `update --migrate` explicitly approves application of that verified plan and keeps rollback available.
158
+ Inside a v1 project, `scrumrun update` runs a read-only preflight. `update --migrate` explicitly approves application of that verified plan and keeps rollback available.
161
159
 
162
160
  Dry-run must not write project data. Apply requires a hashed inventory, byte-exact local backup, staged validation, atomic switch, mapping report, and idempotent replay. Incomplete hybrid trees reuse existing evidenced v2 relations rather than duplicating them; early v2 prose Runs upgrade to ledger schema 1 only through the same explicit apply gate. Legacy-only aggregates leave the active tree but remain byte-exact in the ignored backup. Ambiguous records are preserved as warnings or evidenced snapshots, never guessed. Vault content remains local and is never rendered. Rollback must refuse if it would erase post-migration changes.
163
161
 
164
162
  ## Command grammar
165
163
 
166
- ### `/sc plan`
164
+ ### `scrumrun plan`
167
165
 
168
166
  - `task`: add/list/show/run/audit/cancel/retry atomic work; use `type: fix` for fixes and `status: backlog` for parked work. `--next` surfaces the oldest backlog Task; `--start [TASK-NNN]` promotes it and creates its first Run.
169
167
  - `sprint`: add/list/show/start/complete/block a real Task batch/timebox.
@@ -172,7 +170,7 @@ Dry-run must not write project data. Apply requires a hashed inventory, byte-exa
172
170
  - `intake <request>`: execute the read-only request pipeline; accepts `--type fix|task|feature|docs|discovery` and `--preview "…"`.
173
171
  - `challenge <question>`: deep read-only analysis with evidence, risks, options, and recommendation.
174
172
 
175
- ### `/sc knowledge`
173
+ ### `scrumrun knowledge`
176
174
 
177
175
  - `fact`: add/list/show/approve/reject/deprecate/invalidate `K-NNN` records.
178
176
  - `decision`: add/list/show/resolve/deprecate/invalidate `DEC-NNN` records.
@@ -183,12 +181,12 @@ Dry-run must not write project data. Apply requires a hashed inventory, byte-exa
183
181
  - `study <focus>`: deep read-only discovery with precise evidence.
184
182
  - `vault`: add/list/show/remove/path for explicitly requested local development values; list redacts values.
185
183
 
186
- ### `/sc rules`
184
+ ### `scrumrun rules`
187
185
 
188
186
  - `guardrail`: add/list/show/retire project rules. Adding/retiring requires explicit approval.
189
187
  - `reviewer`: add/list/show/run configured review roles.
190
188
 
191
- ### `/sc review`
189
+ ### `scrumrun review`
192
190
 
193
191
  - `code --run`: review code changes by severity with file/line evidence.
194
192
  - `artifact --run`: validate method artifacts and relations.
@@ -197,7 +195,7 @@ Dry-run must not write project data. Apply requires a hashed inventory, byte-exa
197
195
 
198
196
  Review is read-only unless the user separately authorizes fixes.
199
197
 
200
- ### `/sc config`
198
+ ### `scrumrun config`
201
199
 
202
200
  - `project`: show/set language, interaction, approval, and quick-task preferences.
203
201
  - `init`: initialize ScrumRun without overwriting existing state silently.
@@ -209,16 +207,16 @@ Review is read-only unless the user separately authorizes fixes.
209
207
 
210
208
  ## Compatibility
211
209
 
212
- Upgrade installs may provide thin v1 adapters for one release cycle. An adapter must execute the mapped `/sc` route in the same turn and emit one concise deprecation note; it must not merely ask the user to invoke another command.
210
+ Upgrade installs may provide thin v1 adapters for one release cycle. An adapter must execute the mapped direct route in the same turn and emit one concise deprecation note; it must not merely ask the user to invoke another command.
213
211
 
214
212
  Important mappings:
215
213
 
216
- - `/sc-sprint` → `/sc plan sprint`
217
- - `/sc-fix` → `/sc plan task` with `type: fix`
218
- - `/sc-backlog` → `/sc plan task` with `status: backlog`
219
- - `/sc-decisions` → `/sc knowledge decision`
220
- - `/sc-know` → `/sc knowledge fact`
221
- - `/sc-golden` → `/sc rules guardrail`
222
- - `/sc-agent` → `/sc rules reviewer`
214
+ - `/sc-sprint` → `scrumrun plan sprint`
215
+ - `/sc-fix` → `scrumrun plan task` with `type: fix`
216
+ - `/sc-backlog` → `scrumrun plan task` with `status: backlog`
217
+ - `/sc-decisions` → `scrumrun knowledge decision`
218
+ - `/sc-know` → `scrumrun knowledge fact`
219
+ - `/sc-golden` → `scrumrun rules guardrail`
220
+ - `/sc-agent` → `scrumrun rules reviewer`
223
221
 
224
- Always generate and recommend the canonical `/sc` grammar.
222
+ Always generate and recommend the canonical direct CLI grammar.