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.
Files changed (112) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/CORE.md +231 -308
  3. package/DECISIONS.md +151 -11
  4. package/LICENSE +21 -0
  5. package/MIGRATION-1-to-2.md +120 -0
  6. package/README.md +147 -78
  7. package/SPEC.md +259 -251
  8. package/bin/claude-install.js +18 -132
  9. package/bin/scrumrun.js +1367 -174
  10. package/docs/COMMANDS.md +71 -0
  11. package/docs/ENTITY-MODEL.md +38 -0
  12. package/docs/RELEASE-SCORECARD.md +43 -0
  13. package/docs/RELEASE.md +51 -0
  14. package/docs/SCHEMA.md +89 -0
  15. package/docs/SEMANTIC-MEMORY.md +68 -0
  16. package/docs/TROUBLESHOOTING.md +78 -0
  17. package/lib/code-intel/adapter.js +20 -0
  18. package/lib/code-intel/javascript.js +199 -0
  19. package/lib/code-intel/learning.js +66 -0
  20. package/lib/code-intel/scanner.js +126 -0
  21. package/lib/commands/manifest.js +118 -0
  22. package/lib/commands/render.js +78 -0
  23. package/lib/memory/index.js +659 -0
  24. package/lib/memory/markdown.js +51 -0
  25. package/lib/memory/service.js +300 -0
  26. package/lib/runtime/budgets.js +23 -0
  27. package/lib/runtime/canonical-snapshot.js +110 -0
  28. package/lib/runtime/context.js +101 -0
  29. package/lib/runtime/orchestrator.js +303 -0
  30. package/lib/runtime/policy-engine.js +184 -0
  31. package/lib/runtime/request-engine.js +132 -0
  32. package/lib/runtime/run-ledger.js +324 -0
  33. package/lib/security/secrets.js +23 -0
  34. package/lib/v2/artifacts.js +363 -0
  35. package/lib/v2/conformance.js +214 -0
  36. package/lib/v2/migration.js +1221 -0
  37. package/lib/v2/project-store.js +44 -0
  38. package/lib/v2/run-ledger-migration.js +240 -0
  39. package/lib/v2/schema.js +148 -0
  40. package/lib/v2/transaction.js +254 -0
  41. package/package.json +16 -6
  42. package/scripts/generate-contract-docs.js +124 -0
  43. package/templates/project/.scrumrun/config.md +4 -7
  44. package/templates/project/.scrumrun/guardrails.md +31 -0
  45. package/templates/project/.scrumrun/map.md +5 -16
  46. package/templates/project/.scrumrun/memory/decisions/.gitkeep +1 -0
  47. package/templates/project/.scrumrun/memory/dossiers/.gitkeep +1 -0
  48. package/templates/project/.scrumrun/memory/insights/.gitkeep +1 -0
  49. package/templates/project/.scrumrun/memory/knowledge/.gitkeep +1 -0
  50. package/templates/project/.scrumrun/method.json +7 -0
  51. package/templates/project/.scrumrun/project.md +6 -12
  52. package/templates/project/.scrumrun/runs/.gitkeep +1 -0
  53. package/templates/project/.scrumrun/sprints/.gitkeep +1 -0
  54. package/templates/project/.scrumrun/state.md +14 -0
  55. package/templates/project/.scrumrun/tasks/.gitkeep +1 -0
  56. package/templates/project/AGENTS.md +16 -49
  57. package/templates/project-lean/AGENTS.md +18 -0
  58. package/templates/shared/skills/scrumrun/SKILL.md +211 -0
  59. package/templates/codex/prompts/sc-agent.md +0 -14
  60. package/templates/codex/prompts/sc-backlog.md +0 -13
  61. package/templates/codex/prompts/sc-challenge.md +0 -57
  62. package/templates/codex/prompts/sc-config.md +0 -18
  63. package/templates/codex/prompts/sc-context.md +0 -24
  64. package/templates/codex/prompts/sc-decisions.md +0 -8
  65. package/templates/codex/prompts/sc-feature.md +0 -16
  66. package/templates/codex/prompts/sc-fix.md +0 -21
  67. package/templates/codex/prompts/sc-goal.md +0 -14
  68. package/templates/codex/prompts/sc-golden.md +0 -14
  69. package/templates/codex/prompts/sc-help.md +0 -12
  70. package/templates/codex/prompts/sc-init.md +0 -14
  71. package/templates/codex/prompts/sc-intake.md +0 -22
  72. package/templates/codex/prompts/sc-know.md +0 -75
  73. package/templates/codex/prompts/sc-map.md +0 -13
  74. package/templates/codex/prompts/sc-review.md +0 -13
  75. package/templates/codex/prompts/sc-sprint.md +0 -28
  76. package/templates/codex/prompts/sc-study.md +0 -23
  77. package/templates/codex/prompts/sc-uninstall.md +0 -14
  78. package/templates/codex/prompts/sc-update.md +0 -8
  79. package/templates/codex/prompts/sc-vault.md +0 -27
  80. package/templates/codex/skills/scrumrun/SKILL.md +0 -412
  81. package/templates/opencode/commands/sc-agent.md +0 -14
  82. package/templates/opencode/commands/sc-backlog.md +0 -13
  83. package/templates/opencode/commands/sc-challenge.md +0 -57
  84. package/templates/opencode/commands/sc-config.md +0 -18
  85. package/templates/opencode/commands/sc-context.md +0 -24
  86. package/templates/opencode/commands/sc-decisions.md +0 -8
  87. package/templates/opencode/commands/sc-feature.md +0 -16
  88. package/templates/opencode/commands/sc-fix.md +0 -21
  89. package/templates/opencode/commands/sc-goal.md +0 -14
  90. package/templates/opencode/commands/sc-golden.md +0 -14
  91. package/templates/opencode/commands/sc-help.md +0 -12
  92. package/templates/opencode/commands/sc-init.md +0 -14
  93. package/templates/opencode/commands/sc-intake.md +0 -22
  94. package/templates/opencode/commands/sc-know.md +0 -75
  95. package/templates/opencode/commands/sc-map.md +0 -13
  96. package/templates/opencode/commands/sc-review.md +0 -13
  97. package/templates/opencode/commands/sc-sprint.md +0 -28
  98. package/templates/opencode/commands/sc-study.md +0 -23
  99. package/templates/opencode/commands/sc-uninstall.md +0 -14
  100. package/templates/opencode/commands/sc-update.md +0 -8
  101. package/templates/opencode/commands/sc-vault.md +0 -27
  102. package/templates/opencode/skills/scrumrun/SKILL.md +0 -412
  103. package/templates/project/.scrumrun/agents.md +0 -36
  104. package/templates/project/.scrumrun/backlog.md +0 -7
  105. package/templates/project/.scrumrun/context.md +0 -61
  106. package/templates/project/.scrumrun/goals/main/decisions.md +0 -9
  107. package/templates/project/.scrumrun/goals/main/history.md +0 -51
  108. package/templates/project/.scrumrun/goals/main/sprint.md +0 -54
  109. package/templates/project/.scrumrun/golden-rules.md +0 -9
  110. package/templates/project/.scrumrun/knowledge.md +0 -15
  111. package/templates/project/.scrumrun/runbook.md +0 -101
  112. package/templates/project/.scrumrun/token-policy.md +0 -43
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
+ const { ArtifactRepository, graphFromRecords } = require("./artifacts");
6
+ const { MANIFEST_PATH, migrationPlan } = require("./migration");
7
+
8
+ const LEGACY_PATHS = [
9
+ "backlog.md",
10
+ "fixes.md",
11
+ "golden-rules.md",
12
+ "knowledge.md",
13
+ path.join("goals", "main", "sprint.md"),
14
+ path.join("goals", "main", "history.md"),
15
+ path.join("goals", "main", "decisions.md")
16
+ ];
17
+
18
+ function readProjectModel(projectRoot) {
19
+ const scrumDir = path.join(projectRoot, ".scrumrun");
20
+ if (!fs.existsSync(scrumDir)) throw new Error("Not a ScrumRun project: .scrumrun/ is missing.");
21
+ const hasV2Marker = fs.existsSync(path.join(scrumDir, "method.json")) || fs.existsSync(path.join(scrumDir, MANIFEST_PATH));
22
+ const hasLegacy = LEGACY_PATHS.some((relative) => fs.existsSync(path.join(scrumDir, relative)));
23
+
24
+ if (hasV2Marker) {
25
+ const repository = new ArtifactRepository(scrumDir);
26
+ return {
27
+ layout: hasLegacy ? "mixed" : "v2",
28
+ source: "canonical-v2",
29
+ graph: repository.graph(),
30
+ warnings: []
31
+ };
32
+ }
33
+
34
+ const plan = migrationPlan(projectRoot);
35
+ return {
36
+ layout: "v1",
37
+ source: "read-only-v2-projection",
38
+ graph: graphFromRecords(plan.artifacts.map((artifact) => artifact.record)),
39
+ warnings: plan.warnings,
40
+ errors: plan.errors
41
+ };
42
+ }
43
+
44
+ module.exports = { LEGACY_PATHS, readProjectModel };
@@ -0,0 +1,240 @@
1
+ "use strict";
2
+
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
+ const {
6
+ ArtifactRepository,
7
+ METHOD_VERSION,
8
+ assertNoSymlinkPath,
9
+ atomicWrite,
10
+ parseArtifact,
11
+ serializeArtifact,
12
+ sha256,
13
+ validateArtifact,
14
+ withArtifactLock
15
+ } = require("./artifacts");
16
+ const { RUN_LEDGER_VERSION } = require("./schema");
17
+ const { migrateLegacyRun, validateRunLedger } = require("../runtime/run-ledger");
18
+
19
+ const MIGRATION_NAME = "run-ledger-v1";
20
+ const MIGRATION_DIR = path.join(".migration", MIGRATION_NAME);
21
+ const MANIFEST_PATH = path.join(MIGRATION_DIR, "manifest.json");
22
+
23
+ function posix(value) {
24
+ return value.split(path.sep).join("/");
25
+ }
26
+
27
+ function backupPath(relative) {
28
+ return posix(path.join(MIGRATION_DIR, "backup", relative));
29
+ }
30
+
31
+ function readMarker(scrumDir) {
32
+ const file = assertNoSymlinkPath(scrumDir, path.join(scrumDir, "method.json"));
33
+ if (!fs.existsSync(file) || !fs.lstatSync(file).isFile()) throw new Error("Canonical method.json is missing or unsafe.");
34
+ try {
35
+ const value = JSON.parse(fs.readFileSync(file, "utf8"));
36
+ if (value.method !== METHOD_VERSION) throw new Error(`method must be ${METHOD_VERSION}`);
37
+ return { file, value, content: fs.readFileSync(file, "utf8") };
38
+ } catch (error) {
39
+ throw new Error(`Canonical method.json is malformed: ${error.message}`);
40
+ }
41
+ }
42
+
43
+ function planRunLedgerMigration(projectRoot) {
44
+ const scrumDir = path.join(projectRoot, ".scrumrun");
45
+ if (!fs.existsSync(scrumDir) || !fs.lstatSync(scrumDir).isDirectory()) throw new Error("Not a ScrumRun project: .scrumrun/ is missing.");
46
+ const marker = readMarker(scrumDir);
47
+ const repository = new ArtifactRepository(scrumDir);
48
+ const changes = [];
49
+ const warnings = [];
50
+ const errors = [];
51
+
52
+ for (const artifact of repository.list("run")) {
53
+ if (!artifact.record || artifact.errors.length) {
54
+ errors.push(`${path.relative(scrumDir, artifact.file)}: ${artifact.errors.join("; ")}`);
55
+ continue;
56
+ }
57
+ if (artifact.record.ledger === RUN_LEDGER_VERSION) {
58
+ const ledger = validateRunLedger(artifact.record, artifact.body);
59
+ for (const error of ledger.errors) errors.push(`${artifact.record.id}: ${error}`);
60
+ continue;
61
+ }
62
+ const relative = posix(path.relative(scrumDir, artifact.file));
63
+ const before = fs.readFileSync(artifact.file, "utf8");
64
+ try {
65
+ const migrated = migrateLegacyRun(artifact.record, artifact.body, {
66
+ legacyHash: sha256(before),
67
+ backupRef: `.scrumrun/${backupPath(relative)}`
68
+ });
69
+ const after = serializeArtifact(migrated.record, migrated.body);
70
+ const parsed = parseArtifact(after);
71
+ const validation = [...parsed.errors, ...validateArtifact(parsed.record, "run"), ...validateRunLedger(parsed.record, parsed.body).errors];
72
+ if (validation.length) throw new Error(validation.join("; "));
73
+ changes.push({ relative, before, after, beforeSha256: sha256(before), afterSha256: sha256(after), mode: migrated.mode });
74
+ if (migrated.mode === "snapshot") warnings.push(`${artifact.record.id}: missing transition history is preserved as an evidenced snapshot, not reconstructed.`);
75
+ } catch (error) {
76
+ errors.push(`${artifact.record.id}: ${error.message}`);
77
+ }
78
+ }
79
+
80
+ const markerValue = {
81
+ ...marker.value,
82
+ schemas: { ...(marker.value.schemas || {}), run_ledger: RUN_LEDGER_VERSION }
83
+ };
84
+ const markerAfter = `${JSON.stringify(markerValue, null, 2)}\n`;
85
+ if (marker.content !== markerAfter) {
86
+ changes.push({
87
+ relative: "method.json",
88
+ before: marker.content,
89
+ after: markerAfter,
90
+ beforeSha256: sha256(marker.content),
91
+ afterSha256: sha256(markerAfter),
92
+ mode: "schema-marker"
93
+ });
94
+ }
95
+ const fingerprint = sha256(changes.map((change) => `${change.relative}\0${change.beforeSha256}`).sort().join("\n"));
96
+ return {
97
+ projectRoot,
98
+ scrumDir,
99
+ status: errors.length ? "blocked" : changes.length ? "ready" : "current",
100
+ migration: MIGRATION_NAME,
101
+ ledger: RUN_LEDGER_VERSION,
102
+ fingerprint,
103
+ changes,
104
+ warnings,
105
+ errors
106
+ };
107
+ }
108
+
109
+ function reportRunLedgerMigration(plan, status = plan.status) {
110
+ const mappings = plan.changes.length
111
+ ? plan.changes.map((change) => `- \`${change.relative}\` · ${change.mode} · \`${change.beforeSha256.slice(0, 12)}\` → \`${change.afterSha256.slice(0, 12)}\``).join("\n")
112
+ : "- No changes required.";
113
+ const warnings = plan.warnings.length ? plan.warnings.map((warning) => `- ${warning}`).join("\n") : "- None.";
114
+ const errors = plan.errors.length ? plan.errors.map((error) => `- ${error}`).join("\n") : "- None.";
115
+ return `# ScrumRun Run Ledger Migration\n\nStatus: ${status}\nSchema: ${RUN_LEDGER_VERSION}\nSource fingerprint: \`${plan.fingerprint}\`\nChanged files: ${plan.changes.length}\n\n## Mappings\n\n${mappings}\n\n## Warnings\n\n${warnings}\n\n## Blockers\n\n${errors}\n`;
116
+ }
117
+
118
+ function manifestFile(scrumDir) {
119
+ return assertNoSymlinkPath(scrumDir, path.join(scrumDir, MANIFEST_PATH));
120
+ }
121
+
122
+ function loadManifest(scrumDir) {
123
+ const file = manifestFile(scrumDir);
124
+ if (!fs.existsSync(file)) return null;
125
+ if (!fs.lstatSync(file).isFile() || fs.lstatSync(file).isSymbolicLink()) throw new Error("Run ledger migration manifest is unsafe.");
126
+ return JSON.parse(fs.readFileSync(file, "utf8"));
127
+ }
128
+
129
+ function backupChanges(plan) {
130
+ for (const change of plan.changes) {
131
+ const target = assertNoSymlinkPath(plan.scrumDir, path.join(plan.scrumDir, backupPath(change.relative)));
132
+ if (fs.existsSync(target)) {
133
+ if (sha256(fs.readFileSync(target)) !== change.beforeSha256) throw new Error(`Migration backup conflicts for ${change.relative}.`);
134
+ continue;
135
+ }
136
+ atomicWrite(target, change.before);
137
+ }
138
+ }
139
+
140
+ function verifyChanges(scrumDir, changes, field) {
141
+ const problems = [];
142
+ for (const change of changes) {
143
+ const file = assertNoSymlinkPath(scrumDir, path.join(scrumDir, change.relative));
144
+ const actual = fs.existsSync(file) && fs.lstatSync(file).isFile() ? sha256(fs.readFileSync(file)) : "missing";
145
+ if (actual !== change[field]) problems.push(`${change.relative}: expected ${change[field]}, found ${actual}`);
146
+ }
147
+ return problems;
148
+ }
149
+
150
+ function restoreChanges(scrumDir, changes) {
151
+ for (const change of [...changes].reverse()) {
152
+ const backup = assertNoSymlinkPath(scrumDir, path.join(scrumDir, backupPath(change.relative)));
153
+ if (!fs.existsSync(backup) || sha256(fs.readFileSync(backup)) !== change.beforeSha256) {
154
+ throw new Error(`Run ledger backup is missing or corrupt for ${change.relative}.`);
155
+ }
156
+ const target = assertNoSymlinkPath(scrumDir, path.join(scrumDir, change.relative));
157
+ atomicWrite(target, fs.readFileSync(backup));
158
+ }
159
+ }
160
+
161
+ function recoverPrepared(scrumDir, manifest) {
162
+ if (!manifest || manifest.status !== "prepared") return false;
163
+ const allowed = [];
164
+ for (const change of manifest.changes) {
165
+ const file = path.join(scrumDir, change.relative);
166
+ const actual = fs.existsSync(file) ? sha256(fs.readFileSync(file)) : "missing";
167
+ if (![change.beforeSha256, change.afterSha256].includes(actual)) allowed.push(`${change.relative}: unexpected ${actual}`);
168
+ }
169
+ if (allowed.length) throw new Error(`Interrupted Run ledger migration cannot recover safely:\n${allowed.join("\n")}`);
170
+ restoreChanges(scrumDir, manifest.changes);
171
+ atomicWrite(manifestFile(scrumDir), `${JSON.stringify({ ...manifest, status: "recovered", recovered_at: new Date().toISOString() }, null, 2)}\n`);
172
+ return true;
173
+ }
174
+
175
+ function applyRunLedgerMigration(projectRoot, { failurePoint = null } = {}) {
176
+ const scrumDir = path.join(projectRoot, ".scrumrun");
177
+ const preview = planRunLedgerMigration(projectRoot);
178
+ if (preview.errors.length) throw new Error(`Run ledger migration is blocked:\n${preview.errors.join("\n")}`);
179
+ if (!preview.changes.length) return { status: "current", plan: preview, output: reportRunLedgerMigration(preview, "current") };
180
+ return withArtifactLock(scrumDir, "run-ledger-migration", () => {
181
+ const existing = loadManifest(scrumDir);
182
+ if (existing && existing.status === "prepared") recoverPrepared(scrumDir, existing);
183
+ const plan = planRunLedgerMigration(projectRoot);
184
+ if (plan.errors.length) throw new Error(`Run ledger migration is blocked:\n${plan.errors.join("\n")}`);
185
+ if (!plan.changes.length) return { status: "current", plan, output: reportRunLedgerMigration(plan, "current") };
186
+ const changed = verifyChanges(scrumDir, plan.changes, "beforeSha256");
187
+ if (changed.length) throw new Error(`Run ledger source changed after preflight:\n${changed.join("\n")}`);
188
+ backupChanges(plan);
189
+ const manifest = {
190
+ migration: MIGRATION_NAME,
191
+ schema: RUN_LEDGER_VERSION,
192
+ status: "prepared",
193
+ prepared_at: new Date().toISOString(),
194
+ source_fingerprint: plan.fingerprint,
195
+ changes: plan.changes.map(({ relative, beforeSha256, afterSha256, mode }) => ({ relative, beforeSha256, afterSha256, mode }))
196
+ };
197
+ atomicWrite(manifestFile(scrumDir), `${JSON.stringify(manifest, null, 2)}\n`);
198
+ try {
199
+ for (let index = 0; index < plan.changes.length; index++) {
200
+ const change = plan.changes[index];
201
+ const target = assertNoSymlinkPath(scrumDir, path.join(scrumDir, change.relative));
202
+ atomicWrite(target, change.after);
203
+ if (failurePoint === `after-${index + 1}`) throw new Error(`Injected Run ledger migration failure after write ${index + 1}.`);
204
+ }
205
+ const afterProblems = verifyChanges(scrumDir, plan.changes, "afterSha256");
206
+ if (afterProblems.length) throw new Error(`Run ledger migration verification failed:\n${afterProblems.join("\n")}`);
207
+ const applied = { ...manifest, status: "applied", applied_at: new Date().toISOString() };
208
+ atomicWrite(manifestFile(scrumDir), `${JSON.stringify(applied, null, 2)}\n`);
209
+ return { status: "applied", plan, manifest: applied, output: reportRunLedgerMigration(plan, "applied") };
210
+ } catch (error) {
211
+ restoreChanges(scrumDir, manifest.changes);
212
+ atomicWrite(manifestFile(scrumDir), `${JSON.stringify({ ...manifest, status: "recovered", recovered_at: new Date().toISOString() }, null, 2)}\n`);
213
+ throw error;
214
+ }
215
+ });
216
+ }
217
+
218
+ function rollbackRunLedgerMigration(projectRoot) {
219
+ const scrumDir = path.join(projectRoot, ".scrumrun");
220
+ return withArtifactLock(scrumDir, "run-ledger-migration", () => {
221
+ const manifest = loadManifest(scrumDir);
222
+ if (!manifest || !["applied", "prepared"].includes(manifest.status)) throw new Error("No applied Run ledger migration is available for rollback.");
223
+ if (manifest.status === "applied") {
224
+ const changed = verifyChanges(scrumDir, manifest.changes, "afterSha256");
225
+ if (changed.length) throw new Error(`Rollback would erase post-migration changes:\n${changed.join("\n")}`);
226
+ }
227
+ restoreChanges(scrumDir, manifest.changes);
228
+ const rolledBack = { ...manifest, status: "rolled-back", rolled_back_at: new Date().toISOString() };
229
+ atomicWrite(manifestFile(scrumDir), `${JSON.stringify(rolledBack, null, 2)}\n`);
230
+ return { status: "rolled-back", manifest: rolledBack };
231
+ });
232
+ }
233
+
234
+ module.exports = {
235
+ MIGRATION_NAME,
236
+ applyRunLedgerMigration,
237
+ planRunLedgerMigration,
238
+ reportRunLedgerMigration,
239
+ rollbackRunLedgerMigration
240
+ };
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+
3
+ function deepFreeze(value) {
4
+ if (!value || typeof value !== "object" || Object.isFrozen(value)) return value;
5
+ for (const nested of Object.values(value)) deepFreeze(nested);
6
+ return Object.freeze(value);
7
+ }
8
+
9
+ const METHOD_VERSION = "2.0.0";
10
+ const RUN_LEDGER_VERSION = 1;
11
+
12
+ const RUN_EVENT_TYPES = deepFreeze(["transition", "snapshot"]);
13
+ const RUN_EVIDENCE_KINDS = deepFreeze([
14
+ "approval",
15
+ "command",
16
+ "test",
17
+ "file",
18
+ "review",
19
+ "decision",
20
+ "insight",
21
+ "risk",
22
+ "note",
23
+ "migration",
24
+ "legacy"
25
+ ]);
26
+
27
+ const ARTIFACT_TYPES = deepFreeze({
28
+ feature: {
29
+ prefix: "FEAT",
30
+ directory: "features",
31
+ initial: ["backlog", "proposed"],
32
+ statuses: ["backlog", "proposed", "active", "completed", "paused", "cancelled"]
33
+ },
34
+ task: {
35
+ prefix: "TASK",
36
+ directory: "tasks",
37
+ initial: ["backlog", "proposed", "running"],
38
+ statuses: ["backlog", "proposed", "running", "validating", "learning", "partial", "completed", "failed", "blocked", "cancelled"]
39
+ },
40
+ sprint: {
41
+ prefix: "SPRINT",
42
+ directory: "sprints",
43
+ initial: ["proposed"],
44
+ statuses: ["proposed", "running", "partial", "completed", "blocked", "cancelled"]
45
+ },
46
+ run: {
47
+ prefix: "RUN",
48
+ directory: "runs",
49
+ initial: ["executing"],
50
+ statuses: ["executing", "validating", "learning", "partial", "completed", "failed", "blocked"]
51
+ },
52
+ review: {
53
+ prefix: "REV",
54
+ directory: "reviews",
55
+ initial: ["proposed"],
56
+ statuses: ["proposed", "running", "passed", "failed", "archived"]
57
+ },
58
+ knowledge: {
59
+ prefix: "K",
60
+ directory: "memory/knowledge",
61
+ initial: ["candidate"],
62
+ statuses: ["candidate", "approved", "rejected", "deprecated", "invalidated"]
63
+ },
64
+ decision: {
65
+ prefix: "DEC",
66
+ directory: "memory/decisions",
67
+ initial: ["open"],
68
+ statuses: ["open", "resolved", "deprecated", "invalidated"]
69
+ },
70
+ insight: {
71
+ prefix: "INS",
72
+ directory: "memory/insights",
73
+ initial: ["candidate"],
74
+ statuses: ["candidate", "confirmed", "stale", "deprecated", "invalidated"]
75
+ },
76
+ dossier: {
77
+ prefix: "DOS",
78
+ directory: "memory/dossiers",
79
+ initial: ["active"],
80
+ statuses: ["active", "stale", "deprecated", "archived"]
81
+ }
82
+ });
83
+
84
+ const ARTIFACT_TRANSITIONS = deepFreeze({
85
+ feature: {
86
+ backlog: ["proposed", "active", "cancelled"], proposed: ["active", "cancelled"], active: ["paused", "completed", "cancelled"], paused: ["active", "cancelled"]
87
+ },
88
+ task: {
89
+ backlog: ["proposed", "running", "cancelled"], proposed: ["running", "cancelled"], running: ["validating", "failed", "blocked", "cancelled"], validating: ["learning", "failed", "blocked"], learning: ["completed", "failed", "blocked"], partial: ["running", "cancelled"], failed: ["running", "cancelled"], blocked: ["running", "cancelled"]
90
+ },
91
+ sprint: {
92
+ proposed: ["running", "cancelled"], running: ["partial", "completed", "blocked", "cancelled"], partial: ["running", "completed", "cancelled"], blocked: ["running", "cancelled"]
93
+ },
94
+ run: {
95
+ executing: ["validating", "failed", "blocked"], validating: ["learning", "failed", "blocked"], learning: ["completed", "failed", "blocked"], partial: ["executing", "failed", "blocked"], blocked: ["executing", "failed"]
96
+ },
97
+ review: { proposed: ["running"], running: ["passed", "failed"], failed: ["running", "archived"], passed: ["archived"] },
98
+ knowledge: { candidate: ["approved", "rejected"], approved: ["deprecated", "invalidated"], rejected: ["candidate"], deprecated: ["approved"], invalidated: [] },
99
+ decision: { open: ["resolved", "deprecated", "invalidated"], resolved: ["deprecated", "invalidated"], deprecated: ["open"], invalidated: [] },
100
+ insight: { candidate: ["confirmed", "invalidated"], confirmed: ["stale", "deprecated", "invalidated"], stale: ["confirmed", "deprecated", "invalidated"], deprecated: ["confirmed"], invalidated: [] },
101
+ dossier: { active: ["stale", "deprecated", "archived"], stale: ["active", "deprecated", "archived"], deprecated: ["archived"], archived: [] }
102
+ });
103
+
104
+ const STRUCTURAL_RELATIONS = deepFreeze({
105
+ feature: { targetKind: "feature", cardinality: "0..1", meaning: "long-lived initiative containing the artifact" },
106
+ sprint: { targetKind: "sprint", cardinality: "0..1", meaning: "optional delivery batch containing a Task or Run" },
107
+ task: { targetKind: "task", cardinality: "1 for Run; otherwise 0..1", requiredFor: ["run"], meaning: "atomic work executed or reviewed by the artifact" }
108
+ });
109
+
110
+ const SCALAR_FIELDS = deepFreeze({
111
+ attempt: { kinds: ["run"], required: true, type: "positive integer", meaning: "monotonic execution-attempt number within one Task" },
112
+ ledger: { kinds: ["run"], required: false, type: `integer ${RUN_LEDGER_VERSION}`, meaning: "canonical Run event-ledger schema; required for newly authored Runs" }
113
+ });
114
+
115
+ const TRUTH_OWNERSHIP = deepFreeze({
116
+ feature: { question: "Why does this initiative exist?", truth: "initiative purpose, scope, dependencies, and lifecycle" },
117
+ task: { question: "What approved atomic outcome is intended?", truth: "scope, acceptance criteria, approval, and intended status" },
118
+ sprint: { question: "When are related Tasks grouped?", truth: "timebox or delivery-batch membership; never execution history" },
119
+ run: { question: "How did one execution attempt actually happen?", truth: "append-only execution events, evidence, result, and attempt number" },
120
+ review: { question: "What independent validation was performed?", truth: "scoped findings, checks, evidence, and verdict" },
121
+ knowledge: { question: "What verified project fact is reusable?", truth: "approved evidence-backed fact and validity" },
122
+ decision: { question: "What normative choice constrains future work?", truth: "decision, rationale, validity, and lifecycle" },
123
+ insight: { question: "Why is something arranged or constrained this way?", truth: "explanatory candidate/confirmed context and evidence" },
124
+ dossier: { question: "What evidence belongs to one retrieval topic?", truth: "reviewed topic bundle and freshness" }
125
+ });
126
+
127
+ const AUTHORITY = deepFreeze({
128
+ semantics: { source: "SPEC.md", scope: "normative meanings and invariants" },
129
+ schema: { source: "lib/v2/schema.js", scope: "machine-enforced ids, paths, statuses, transitions, relations, and ownership metadata" },
130
+ commands: { source: "lib/commands/manifest.js", scope: "public command grammar and compatibility routes" },
131
+ projectPolicy: { source: ".scrumrun/guardrails.md", scope: "owner/project constraints" },
132
+ projectTruth: { source: ".scrumrun/**/*.md", scope: "authored canonical project records" },
133
+ projections: { source: ".scrumrun/state.md, .scrumrun/map.md, .scrumrun/.cache/", scope: "disposable derived views; never authority" }
134
+ });
135
+
136
+ module.exports = {
137
+ ARTIFACT_TRANSITIONS,
138
+ ARTIFACT_TYPES,
139
+ AUTHORITY,
140
+ METHOD_VERSION,
141
+ RUN_EVENT_TYPES,
142
+ RUN_EVIDENCE_KINDS,
143
+ RUN_LEDGER_VERSION,
144
+ SCALAR_FIELDS,
145
+ STRUCTURAL_RELATIONS,
146
+ TRUTH_OWNERSHIP,
147
+ deepFreeze
148
+ };