ultra-builder-pro-cli 0.13.0 → 0.15.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 (172) hide show
  1. package/.ultra-template/docs/research/README.md +11 -7
  2. package/.ultra-template/specs/architecture.md +36 -36
  3. package/.ultra-template/specs/discovery.md +46 -33
  4. package/.ultra-template/specs/product.md +38 -29
  5. package/.ultra-template/specs/research-distillate.md +48 -0
  6. package/.ultra-template/tasks/contexts/TEMPLATE.md +11 -28
  7. package/CHANGELOG.md +86 -1
  8. package/README.md +40 -26
  9. package/adapters/_shared/codex-assets.cjs +29 -165
  10. package/adapters/_shared/file-ops.cjs +6 -2
  11. package/adapters/_shared/runtime-assets.cjs +1 -0
  12. package/adapters/claude.js +3 -2
  13. package/adapters/codex.js +1 -0
  14. package/adapters/kimi.js +3 -2
  15. package/adapters/opencode.js +33 -122
  16. package/agents/review-coordinator.md +9 -6
  17. package/docs/AGENT-CONTEXT.md +21 -11
  18. package/docs/ARCHITECTURE.md +41 -13
  19. package/docs/PLAN.zh-CN.md +3 -2
  20. package/docs/ROADMAP.md +9 -5
  21. package/docs/RUNTIME-COMPAT-MATRIX.md +6 -6
  22. package/docs/STATE-DB-ACCESS-POLICY.md +27 -25
  23. package/docs/WORKFLOW-LIFECYCLE.md +266 -0
  24. package/hooks/README.md +6 -1
  25. package/hooks/active_task_context.py +22 -50
  26. package/hooks/context_spine.py +61 -332
  27. package/hooks/health_check.py +29 -18
  28. package/hooks/pre_stop_check.py +28 -33
  29. package/hooks/subagent_tracker.py +54 -85
  30. package/hooks/workflow_checkpoint.py +39 -47
  31. package/hooks/workflow_context.py +4 -9
  32. package/hooks/workflow_resume.py +48 -128
  33. package/mcp-server/breadcrumb.cjs +22 -0
  34. package/mcp-server/hook-event.cjs +34 -0
  35. package/mcp-server/lib/archive-journal.cjs +17 -5
  36. package/mcp-server/lib/baseline-workflow.cjs +158 -18
  37. package/mcp-server/lib/change-workflow.cjs +550 -85
  38. package/mcp-server/lib/context-spine.cjs +242 -24
  39. package/mcp-server/lib/doctor.cjs +33 -3
  40. package/mcp-server/lib/init-project.cjs +210 -16
  41. package/mcp-server/lib/plan-store.cjs +4 -1
  42. package/mcp-server/lib/prd-parser.cjs +14 -41
  43. package/mcp-server/lib/pricing.cjs +7 -10
  44. package/mcp-server/lib/project-breadcrumb.cjs +145 -0
  45. package/mcp-server/lib/projector.cjs +57 -3
  46. package/mcp-server/lib/spec-learning.cjs +79 -2
  47. package/mcp-server/lib/state-db.cjs +145 -8
  48. package/mcp-server/lib/state-ops.cjs +160 -9
  49. package/mcp-server/lib/task-expander.cjs +9 -38
  50. package/mcp-server/lib/workflow-state.cjs +2292 -0
  51. package/mcp-server/server.cjs +182 -50
  52. package/orchestrator/daemon.cjs +1 -2
  53. package/orchestrator/dispatch-rules.cjs +5 -31
  54. package/orchestrator/planner/plan-builder.cjs +21 -12
  55. package/package.json +14 -6
  56. package/skills/cc-collab/SKILL.md +2 -2
  57. package/skills/codex-collab/SKILL.md +2 -3
  58. package/skills/ultra-change/SKILL.md +53 -68
  59. package/skills/ultra-change/references/change-contract.md +36 -0
  60. package/skills/ultra-deliver/SKILL.md +69 -79
  61. package/skills/ultra-deliver/references/baseline-reconciliation.md +20 -0
  62. package/skills/ultra-dev/SKILL.md +54 -59
  63. package/skills/ultra-doctor/SKILL.md +40 -71
  64. package/skills/ultra-init/SKILL.md +96 -103
  65. package/skills/ultra-init/references/brownfield-adoption.md +2 -2
  66. package/skills/ultra-plan/SKILL.md +79 -75
  67. package/skills/ultra-plan/references/semantic-preflight.md +18 -0
  68. package/skills/ultra-research/SKILL.md +87 -60
  69. package/skills/ultra-research/references/00-problem-validation.md +36 -0
  70. package/skills/ultra-research/references/01-opportunity-discovery.md +33 -0
  71. package/skills/ultra-research/references/02-market-assessment.md +30 -0
  72. package/skills/ultra-research/references/03-competitive-landscape.md +31 -0
  73. package/skills/ultra-research/references/04-product-strategy.md +32 -0
  74. package/skills/ultra-research/references/05-assumptions-validation.md +31 -0
  75. package/skills/ultra-research/references/10-user-personas.md +29 -0
  76. package/skills/ultra-research/references/11-user-scenarios.md +28 -0
  77. package/skills/ultra-research/references/20-user-stories.md +30 -0
  78. package/skills/ultra-research/references/21-features-scope.md +29 -0
  79. package/skills/ultra-research/references/22-success-metrics.md +28 -0
  80. package/skills/ultra-research/references/30-architecture-context.md +30 -0
  81. package/skills/ultra-research/references/31-solution-strategy.md +28 -0
  82. package/skills/ultra-research/references/32-building-blocks.md +28 -0
  83. package/skills/ultra-research/references/40-deployment.md +29 -0
  84. package/skills/ultra-research/references/41-quality-risks.md +28 -0
  85. package/skills/ultra-research/references/99-synthesis.md +35 -0
  86. package/skills/ultra-review/SKILL.md +57 -73
  87. package/skills/ultra-review/references/review-modes.md +27 -0
  88. package/skills/ultra-review/references/unified-schema.md +25 -0
  89. package/skills/ultra-review/scripts/review_wait.py +40 -3
  90. package/skills/ultra-status/SKILL.md +41 -45
  91. package/skills/ultra-test/SKILL.md +48 -55
  92. package/skills/ultra-verify/SKILL.md +22 -31
  93. package/skills/ultra-verify/references/{confidence-system.md → evidence-status.md} +3 -3
  94. package/skills/ultra-verify/scripts/verify_wait.py +54 -54
  95. package/spec/cli-protocol.md +7 -2
  96. package/spec/mcp-tools.yaml +422 -73
  97. package/spec/schemas/baseline-reconciliation.v1.schema.json +73 -0
  98. package/spec/schemas/delivery-report.v1.schema.json +111 -0
  99. package/spec/schemas/mcp-tools.schema.json +1 -1
  100. package/spec/schemas/state-db.sql +82 -1
  101. package/spec/schemas/tasks.v4.5.schema.json +8 -1
  102. package/spec/schemas/test-report.v1.schema.json +194 -0
  103. package/ultra-tools/cli.cjs +1 -1
  104. package/ultra-tools/commands/migrate.cjs +3 -3
  105. package/ultra-tools/commands/status.cjs +213 -12
  106. package/.ultra-template/delivery-report.json +0 -9
  107. package/.ultra-template/test-report.json +0 -8
  108. package/adapters/_shared/tests/file-ops.test.cjs +0 -54
  109. package/adapters/_shared/tests/frontmatter.test.cjs +0 -47
  110. package/adapters/_shared/tests/handbook.test.cjs +0 -101
  111. package/adapters/_shared/tests/path-rewrite.test.cjs +0 -60
  112. package/adapters/_shared/tests/provenance.test.cjs +0 -133
  113. package/adapters/_shared/tests/resolve-target.test.cjs +0 -75
  114. package/adapters/_shared/tests/runtime-assets.test.cjs +0 -131
  115. package/adapters/_shared/tests/settings-merge.test.cjs +0 -85
  116. package/adapters/_shared/tests/validate.test.cjs +0 -30
  117. package/adapters/tests/claude.test.cjs +0 -137
  118. package/adapters/tests/codex-hook.test.cjs +0 -115
  119. package/adapters/tests/codex.test.cjs +0 -423
  120. package/adapters/tests/kimi-hook.test.cjs +0 -122
  121. package/adapters/tests/kimi.test.cjs +0 -259
  122. package/adapters/tests/opencode.test.cjs +0 -371
  123. package/mcp-server/lib/archive-journal.test.cjs +0 -35
  124. package/mcp-server/lib/baseline-workflow.test.cjs +0 -477
  125. package/mcp-server/lib/change-workflow.test.cjs +0 -971
  126. package/mcp-server/lib/context-spine.test.cjs +0 -93
  127. package/mcp-server/lib/doctor.test.cjs +0 -148
  128. package/mcp-server/lib/llm-client.cjs +0 -157
  129. package/mcp-server/lib/llm-client.test.cjs +0 -156
  130. package/mcp-server/lib/plan-store.test.cjs +0 -132
  131. package/mcp-server/lib/prd-parser.test.cjs +0 -171
  132. package/mcp-server/lib/runtime-state.test.cjs +0 -92
  133. package/mcp-server/lib/spec-learning.test.cjs +0 -67
  134. package/mcp-server/lib/state-db.test.cjs +0 -314
  135. package/mcp-server/lib/task-expander.test.cjs +0 -324
  136. package/mcp-server/lib/topo.test.cjs +0 -158
  137. package/mcp-server/tests/circuit-breaker.test.cjs +0 -125
  138. package/mcp-server/tests/commit-hash-flow.test.cjs +0 -117
  139. package/mcp-server/tests/concurrency-update.test.cjs +0 -96
  140. package/mcp-server/tests/concurrency.test.cjs +0 -112
  141. package/mcp-server/tests/db-ops.test.cjs +0 -121
  142. package/mcp-server/tests/init-project.test.cjs +0 -393
  143. package/mcp-server/tests/migrate.test.cjs +0 -232
  144. package/mcp-server/tests/projector.test.cjs +0 -162
  145. package/mcp-server/tests/server-contract.test.cjs +0 -1471
  146. package/mcp-server/tests/staleness.test.cjs +0 -190
  147. package/mcp-server/tests/state-db.test.cjs +0 -106
  148. package/mcp-server/tests/state-ops.test.cjs +0 -449
  149. package/mcp-server/tests/tagged-tasks.test.cjs +0 -163
  150. package/mcp-server/tests/telemetry.test.cjs +0 -219
  151. package/orchestrator/planner/expand-prompt.cjs +0 -62
  152. package/orchestrator/planner/expand-prompt.test.cjs +0 -52
  153. package/orchestrator/planner/plan-builder.test.cjs +0 -165
  154. package/orchestrator/planner/prd-prompt.cjs +0 -44
  155. package/orchestrator/planner/prd-prompt.test.cjs +0 -30
  156. package/orchestrator/tests/auto-merge.test.cjs +0 -216
  157. package/orchestrator/tests/daemon.test.cjs +0 -311
  158. package/orchestrator/tests/dispatch-rules.test.cjs +0 -124
  159. package/orchestrator/tests/parallel-orchestrator.test.cjs +0 -233
  160. package/orchestrator/tests/phase-8b-gate.test.cjs +0 -183
  161. package/orchestrator/tests/recovery.test.cjs +0 -190
  162. package/orchestrator/tests/session-runner.test.cjs +0 -314
  163. package/orchestrator/tests/skill-miner.test.cjs +0 -147
  164. package/orchestrator/tests/worktree-manager.test.cjs +0 -148
  165. package/skills/ultra-research/references/architecture.md +0 -38
  166. package/skills/ultra-research/references/problem-product.md +0 -39
  167. package/skills/ultra-research/references/quality-delivery.md +0 -34
  168. package/skills/ultra-research/references/synthesis.md +0 -38
  169. package/skills/ultra-research/references/users-scope.md +0 -40
  170. package/ultra-tools/legacy-memory.test.cjs +0 -80
  171. package/ultra-tools/status-command.test.cjs +0 -181
  172. package/ultra-tools/system-command.test.cjs +0 -309
@@ -1,309 +0,0 @@
1
- 'use strict';
2
-
3
- const { test } = require('node:test');
4
- const assert = require('node:assert/strict');
5
- const fs = require('node:fs');
6
- const os = require('node:os');
7
- const path = require('node:path');
8
- const { spawnSync } = require('node:child_process');
9
-
10
- const { initStateDb, closeStateDb } = require('../mcp-server/lib/state-db.cjs');
11
-
12
- const CLI = path.join(__dirname, 'cli.cjs');
13
-
14
- function fixture() {
15
- const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-system-cli-'));
16
- const { db } = initStateDb(path.join(rootDir, '.ultra', 'state.db'));
17
- db.prepare(
18
- `INSERT INTO baselines
19
- (id, project_name, mode, status, approved_by, approval_note, converged_at)
20
- VALUES ('test-baseline', 'fixture', 'greenfield', 'ready', 'test', 'accepted fixture', ?)`,
21
- ).run(new Date().toISOString());
22
- closeStateDb(db);
23
- return rootDir;
24
- }
25
-
26
- function invoke(rootDir, args) {
27
- return spawnSync(process.execPath, [CLI, 'system', ...args], {
28
- cwd: rootDir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'],
29
- });
30
- }
31
-
32
- test('system doctor provides a read-only JSON health envelope', () => {
33
- const rootDir = fixture();
34
- try {
35
- const result = invoke(rootDir, ['doctor']);
36
- assert.equal(result.status, 0, result.stderr);
37
- const envelope = JSON.parse(result.stdout.trim().split('\n').at(-1));
38
- assert.equal(envelope.ok, true);
39
- assert.equal(envelope.data.status, 'healthy');
40
- assert.equal(envelope.data.repair_performed, false);
41
- } finally {
42
- fs.rmSync(rootDir, { recursive: true, force: true });
43
- }
44
- });
45
-
46
- test('system doctor --repair creates a backup before recovery', () => {
47
- const rootDir = fixture();
48
- try {
49
- const result = invoke(rootDir, ['doctor', '--repair']);
50
- assert.equal(result.status, 0, result.stderr);
51
- const envelope = JSON.parse(result.stdout.trim().split('\n').at(-1));
52
- assert.equal(envelope.ok, true);
53
- assert.equal(envelope.data.repair_performed, true);
54
- assert.ok(fs.existsSync(envelope.data.backup_path));
55
- } finally {
56
- fs.rmSync(rootDir, { recursive: true, force: true });
57
- }
58
- });
59
-
60
- test('system doctor --repair migrates an older schema only after preserving a pre-migration backup', () => {
61
- const rootDir = fixture();
62
- try {
63
- const dbPath = path.join(rootDir, '.ultra', 'state.db');
64
- const Database = require('better-sqlite3');
65
- const db = new Database(dbPath);
66
- db.prepare("DELETE FROM schema_version WHERE version = '12.0'").run();
67
- db.close();
68
-
69
- const result = invoke(rootDir, ['doctor', '--repair']);
70
- assert.equal(result.status, 0, result.stderr);
71
- const envelope = JSON.parse(result.stdout.trim().split('\n').at(-1));
72
- assert.equal(envelope.ok, true);
73
- assert.equal(envelope.data.schema_version, '12.0');
74
- assert.ok(fs.existsSync(envelope.data.schema_migration_backup_path));
75
- assert.ok(fs.existsSync(envelope.data.backup_path));
76
- } finally {
77
- fs.rmSync(rootDir, { recursive: true, force: true });
78
- }
79
- });
80
-
81
- test('system doctor reports a corrupt database as structured recovery guidance', () => {
82
- const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-system-corrupt-'));
83
- try {
84
- fs.mkdirSync(path.join(rootDir, '.ultra'), { recursive: true });
85
- fs.writeFileSync(path.join(rootDir, '.ultra', 'state.db'), 'not a sqlite database');
86
- const result = invoke(rootDir, ['doctor']);
87
- assert.equal(result.status, 2);
88
- const envelope = JSON.parse(result.stdout.trim().split('\n').at(-1));
89
- assert.equal(envelope.ok, false);
90
- assert.equal(envelope.error.code, 'STATE_DB_CORRUPT');
91
- assert.match(envelope.error.message, /restore.*backup|rebaseline/i);
92
- } finally {
93
- fs.rmSync(rootDir, { recursive: true, force: true });
94
- }
95
- });
96
-
97
- test('system restore replaces only a corrupt state database from a verified managed backup', () => {
98
- const rootDir = fixture();
99
- try {
100
- const dbPath = path.join(rootDir, '.ultra', 'state.db');
101
- const backupsDir = path.join(rootDir, '.ultra', 'backups');
102
- fs.mkdirSync(backupsDir, { recursive: true });
103
- const sourceBackup = path.join(backupsDir, 'verified.db');
104
- fs.copyFileSync(dbPath, sourceBackup);
105
- fs.writeFileSync(dbPath, 'not a sqlite database');
106
-
107
- const refused = invoke(rootDir, ['restore', '--backup', sourceBackup]);
108
- assert.equal(refused.status, 1);
109
- assert.equal(JSON.parse(refused.stdout.trim()).error.code, 'CONFIRMATION_REQUIRED');
110
- assert.equal(fs.readFileSync(dbPath, 'utf8'), 'not a sqlite database');
111
-
112
- const restored = invoke(rootDir, [
113
- 'restore', '--backup', sourceBackup,
114
- '--confirm', 'REPLACE_CORRUPT_ULTRA_STATE',
115
- ]);
116
- assert.equal(restored.status, 0, restored.stderr);
117
- const envelope = JSON.parse(restored.stdout.trim());
118
- assert.equal(envelope.ok, true);
119
- assert.equal(envelope.data.schema_version, '12.0');
120
- assert.equal(envelope.data.source_backup, fs.realpathSync(sourceBackup));
121
- assert.ok(fs.existsSync(envelope.data.quarantined_state_path));
122
- assert.equal(
123
- fs.readFileSync(envelope.data.quarantined_state_path, 'utf8'),
124
- 'not a sqlite database',
125
- );
126
- const restoredDb = require('better-sqlite3')(dbPath, { readonly: true });
127
- try {
128
- assert.equal(restoredDb.prepare('SELECT COUNT(*) AS count FROM baselines').get().count, 1);
129
- } finally { restoredDb.close(); }
130
- } finally {
131
- fs.rmSync(rootDir, { recursive: true, force: true });
132
- }
133
- });
134
-
135
- test('system rebaseline preserves corrupt authority and legacy projection before starting brownfield adoption', () => {
136
- const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-system-rebaseline-'));
137
- try {
138
- fs.mkdirSync(path.join(rootDir, 'src'), { recursive: true });
139
- fs.mkdirSync(path.join(rootDir, '.ultra', 'tasks', 'contexts'), { recursive: true });
140
- fs.writeFileSync(path.join(rootDir, 'src', 'index.js'), 'module.exports = true;\n');
141
- fs.writeFileSync(path.join(rootDir, '.ultra', 'state.db'), 'broken authority');
142
- const tasksPath = path.join(rootDir, '.ultra', 'tasks', 'tasks.json');
143
- fs.writeFileSync(tasksPath, `${JSON.stringify({
144
- schema_version: '4.5', source: '.ultra/state.db',
145
- tasks: [{ id: 'legacy-task', title: 'Preserved task', type: 'feature', priority: 'P1', status: 'pending' }],
146
- }, null, 2)}\n`);
147
- fs.writeFileSync(
148
- path.join(rootDir, '.ultra', 'tasks', 'contexts', 'legacy-task.md'),
149
- '# Preserved task context\n',
150
- );
151
-
152
- const result = invoke(rootDir, [
153
- 'rebaseline', '--project-name', 'legacy-app',
154
- '--confirm', 'REBASELINE_CORRUPT_ULTRA_STATE',
155
- ]);
156
- assert.equal(result.status, 0, result.stderr);
157
- const envelope = JSON.parse(result.stdout.trim());
158
- assert.equal(envelope.ok, true);
159
- assert.equal(envelope.data.baseline.mode, 'brownfield');
160
- assert.equal(envelope.data.baseline.status, 'adopting');
161
- assert.ok(fs.existsSync(envelope.data.quarantined_state_path));
162
- assert.ok(fs.existsSync(envelope.data.legacy_projection_path));
163
- assert.equal(
164
- JSON.parse(fs.readFileSync(envelope.data.legacy_projection_path, 'utf8')).tasks[0].id,
165
- 'legacy-task',
166
- );
167
- assert.ok(fs.existsSync(path.join(
168
- envelope.data.recovery_backup_dir, 'tasks', 'contexts', 'legacy-task.md',
169
- )));
170
- assert.equal(
171
- fs.existsSync(path.join(rootDir, '.ultra', 'tasks', 'contexts', 'legacy-task.md')),
172
- false,
173
- );
174
- assert.deepEqual(JSON.parse(fs.readFileSync(tasksPath, 'utf8')).tasks, []);
175
- } finally {
176
- fs.rmSync(rootDir, { recursive: true, force: true });
177
- }
178
- });
179
-
180
- test('system restore rolls back the corrupt database and sidecars when replacement initialization fails', () => {
181
- const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-system-restore-rollback-'));
182
- try {
183
- const ultraDir = path.join(rootDir, '.ultra');
184
- const backupsDir = path.join(ultraDir, 'backups');
185
- fs.mkdirSync(backupsDir, { recursive: true });
186
- const statePath = path.join(ultraDir, 'state.db');
187
- const originals = {
188
- '': Buffer.from('broken authority'),
189
- '-wal': Buffer.from('broken wal'),
190
- '-shm': Buffer.from('broken shm'),
191
- };
192
- for (const [suffix, content] of Object.entries(originals)) {
193
- fs.writeFileSync(`${statePath}${suffix}`, content);
194
- }
195
-
196
- const incompatibleBackup = path.join(backupsDir, 'structurally-incompatible.db');
197
- const backupDb = require('better-sqlite3')(incompatibleBackup);
198
- backupDb.exec("CREATE TABLE schema_version (version TEXT NOT NULL); INSERT INTO schema_version VALUES ('11.0');");
199
- backupDb.close();
200
-
201
- const result = invoke(rootDir, [
202
- 'restore', '--backup', incompatibleBackup,
203
- '--confirm', 'REPLACE_CORRUPT_ULTRA_STATE',
204
- ]);
205
- assert.notEqual(result.status, 0, result.stderr);
206
- const envelope = JSON.parse(result.stdout.trim());
207
- assert.equal(envelope.ok, false);
208
- assert.equal(envelope.error.code, 'STATE_RESTORE_FAILED');
209
- for (const [suffix, content] of Object.entries(originals)) {
210
- assert.deepEqual(fs.readFileSync(`${statePath}${suffix}`), content);
211
- }
212
- } finally {
213
- fs.rmSync(rootDir, { recursive: true, force: true });
214
- }
215
- });
216
-
217
- test('system rebaseline rolls back authority, sidecars, and the full task projection when adoption fails', () => {
218
- const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-system-rebaseline-rollback-'));
219
- try {
220
- const ultraDir = path.join(rootDir, '.ultra');
221
- const tasksDir = path.join(ultraDir, 'tasks');
222
- fs.mkdirSync(path.join(tasksDir, 'contexts'), { recursive: true });
223
- fs.mkdirSync(path.join(rootDir, 'src'), { recursive: true });
224
- fs.writeFileSync(path.join(rootDir, 'src', 'index.js'), 'module.exports = true;\n');
225
- const statePath = path.join(ultraDir, 'state.db');
226
- const originals = {
227
- '': Buffer.from('broken authority'),
228
- '-wal': Buffer.from('broken wal'),
229
- '-shm': Buffer.from('broken shm'),
230
- };
231
- for (const [suffix, content] of Object.entries(originals)) {
232
- fs.writeFileSync(`${statePath}${suffix}`, content);
233
- }
234
- const tasksPath = path.join(tasksDir, 'tasks.json');
235
- const contextPath = path.join(tasksDir, 'contexts', 'legacy.md');
236
- const tasksContent = Buffer.from('{"schema_version":"4.5","source":".ultra/state.db","tasks":[]}\n');
237
- const contextContent = Buffer.from('# Legacy context\n');
238
- fs.writeFileSync(tasksPath, tasksContent);
239
- fs.writeFileSync(contextPath, contextContent);
240
-
241
- const result = invoke(rootDir, [
242
- 'rebaseline', '--project-name', 'legacy-app', '--scope', 'missing-workspace',
243
- '--confirm', 'REBASELINE_CORRUPT_ULTRA_STATE',
244
- ]);
245
- assert.notEqual(result.status, 0, result.stderr);
246
- const envelope = JSON.parse(result.stdout.trim());
247
- assert.equal(envelope.ok, false);
248
- assert.equal(envelope.error.code, 'IO_ERROR');
249
- for (const [suffix, content] of Object.entries(originals)) {
250
- assert.deepEqual(fs.readFileSync(`${statePath}${suffix}`), content);
251
- }
252
- assert.deepEqual(fs.readFileSync(tasksPath), tasksContent);
253
- assert.deepEqual(fs.readFileSync(contextPath), contextContent);
254
- } finally {
255
- fs.rmSync(rootDir, { recursive: true, force: true });
256
- }
257
- });
258
-
259
- test('system restore rejects backups outside the managed project backup directory', () => {
260
- const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-system-external-backup-'));
261
- const externalDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-external-backup-'));
262
- try {
263
- fs.mkdirSync(path.join(rootDir, '.ultra'), { recursive: true });
264
- fs.mkdirSync(path.join(rootDir, '.ultra', 'backups'), { recursive: true });
265
- fs.writeFileSync(path.join(rootDir, '.ultra', 'state.db'), 'broken authority');
266
- const external = path.join(externalDir, 'state.db');
267
- fs.writeFileSync(external, 'not trusted');
268
- const result = invoke(rootDir, [
269
- 'restore', '--backup', external,
270
- '--confirm', 'REPLACE_CORRUPT_ULTRA_STATE',
271
- ]);
272
- assert.equal(result.status, 1);
273
- assert.equal(JSON.parse(result.stdout.trim()).error.code, 'BACKUP_OUTSIDE_MANAGED_ROOT');
274
- } finally {
275
- fs.rmSync(rootDir, { recursive: true, force: true });
276
- fs.rmSync(externalDir, { recursive: true, force: true });
277
- }
278
- });
279
-
280
- test('system corrupt-state recovery commands refuse to replace readable authority', () => {
281
- const rootDir = fixture();
282
- try {
283
- const backupsDir = path.join(rootDir, '.ultra', 'backups');
284
- fs.mkdirSync(backupsDir, { recursive: true });
285
- const backup = path.join(backupsDir, 'healthy.db');
286
- fs.copyFileSync(path.join(rootDir, '.ultra', 'state.db'), backup);
287
-
288
- const restore = invoke(rootDir, [
289
- 'restore', '--backup', backup,
290
- '--confirm', 'REPLACE_CORRUPT_ULTRA_STATE',
291
- ]);
292
- assert.equal(restore.status, 2);
293
- assert.equal(JSON.parse(restore.stdout.trim()).error.code, 'STATE_DB_NOT_CORRUPT');
294
-
295
- const rebaseline = invoke(rootDir, [
296
- 'rebaseline', '--project-name', 'fixture',
297
- '--confirm', 'REBASELINE_CORRUPT_ULTRA_STATE',
298
- ]);
299
- assert.equal(rebaseline.status, 2);
300
- assert.equal(JSON.parse(rebaseline.stdout.trim()).error.code, 'STATE_DB_NOT_CORRUPT');
301
-
302
- const db = require('better-sqlite3')(path.join(rootDir, '.ultra', 'state.db'), { readonly: true });
303
- try {
304
- assert.equal(db.prepare('SELECT COUNT(*) AS count FROM baselines').get().count, 1);
305
- } finally { db.close(); }
306
- } finally {
307
- fs.rmSync(rootDir, { recursive: true, force: true });
308
- }
309
- });