ultra-builder-pro-cli 0.11.0 → 0.13.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 (75) hide show
  1. package/.ultra-template/docs/research/README.md +21 -44
  2. package/.ultra-template/specs/architecture.md +54 -223
  3. package/.ultra-template/specs/discovery.md +41 -228
  4. package/.ultra-template/specs/product.md +44 -200
  5. package/.ultra-template/tasks/contexts/TEMPLATE.md +36 -0
  6. package/.ultra-template/tasks/tasks.json +6 -0
  7. package/CHANGELOG.md +94 -1
  8. package/README.md +37 -9
  9. package/adapters/_shared/codex-assets.cjs +1 -1
  10. package/adapters/_shared/handbook.cjs +5 -4
  11. package/adapters/_shared/tests/handbook.test.cjs +4 -0
  12. package/adapters/opencode.js +8 -2
  13. package/adapters/tests/codex.test.cjs +10 -2
  14. package/adapters/tests/kimi-hook.test.cjs +1 -1
  15. package/adapters/tests/opencode.test.cjs +36 -1
  16. package/commands/ultra-init.md +3 -3
  17. package/docs/AGENT-CONTEXT.md +17 -8
  18. package/docs/ARCHITECTURE.md +51 -8
  19. package/docs/PLAN.zh-CN.md +9 -6
  20. package/docs/ROADMAP.md +5 -3
  21. package/docs/RUNTIME-COMPAT-MATRIX.md +8 -5
  22. package/docs/STATE-DB-ACCESS-POLICY.md +19 -13
  23. package/docs/USER-HANDBOOK-CONTRACT.md +9 -5
  24. package/hooks/README.md +4 -3
  25. package/hooks/adapters/codex.py +1 -1
  26. package/hooks/adapters/kimi.py +1 -1
  27. package/hooks/context_spine.py +153 -16
  28. package/hooks/health_check.py +11 -0
  29. package/hooks/pre_stop_check.py +6 -5
  30. package/mcp-server/lib/archive-journal.cjs +192 -0
  31. package/mcp-server/lib/archive-journal.test.cjs +35 -0
  32. package/mcp-server/lib/baseline-workflow.cjs +781 -0
  33. package/mcp-server/lib/baseline-workflow.test.cjs +477 -0
  34. package/mcp-server/lib/change-workflow.cjs +256 -85
  35. package/mcp-server/lib/change-workflow.test.cjs +436 -6
  36. package/mcp-server/lib/context-spine.cjs +74 -9
  37. package/mcp-server/lib/context-spine.test.cjs +20 -0
  38. package/mcp-server/lib/doctor.cjs +28 -1
  39. package/mcp-server/lib/doctor.test.cjs +63 -1
  40. package/mcp-server/lib/init-project.cjs +334 -16
  41. package/mcp-server/lib/provider-refs.cjs +42 -0
  42. package/mcp-server/lib/spec-learning.test.cjs +5 -0
  43. package/mcp-server/lib/state-authority.cjs +9 -3
  44. package/mcp-server/lib/state-db.cjs +202 -21
  45. package/mcp-server/lib/state-db.test.cjs +140 -3
  46. package/mcp-server/lib/state-ops.cjs +68 -6
  47. package/mcp-server/lib/task-expander.cjs +7 -1
  48. package/mcp-server/lib/task-expander.test.cjs +71 -0
  49. package/mcp-server/server.cjs +62 -6
  50. package/mcp-server/tests/init-project.test.cjs +216 -9
  51. package/mcp-server/tests/migrate.test.cjs +33 -0
  52. package/mcp-server/tests/server-contract.test.cjs +478 -3
  53. package/mcp-server/tests/state-db.test.cjs +23 -2
  54. package/mcp-server/tests/state-ops.test.cjs +108 -0
  55. package/package.json +1 -1
  56. package/skills/ultra-change/SKILL.md +32 -13
  57. package/skills/ultra-deliver/SKILL.md +19 -5
  58. package/skills/ultra-dev/SKILL.md +8 -2
  59. package/skills/ultra-doctor/SKILL.md +30 -9
  60. package/skills/ultra-init/SKILL.md +107 -66
  61. package/skills/ultra-init/references/brownfield-adoption.md +93 -0
  62. package/skills/ultra-plan/SKILL.md +15 -5
  63. package/skills/ultra-research/SKILL.md +17 -5
  64. package/skills/ultra-status/SKILL.md +15 -6
  65. package/spec/cli-protocol.md +15 -1
  66. package/spec/mcp-tools.yaml +316 -11
  67. package/spec/schemas/mcp-tools.schema.json +1 -1
  68. package/spec/schemas/state-db.sql +44 -2
  69. package/ultra-tools/cli.cjs +9 -5
  70. package/ultra-tools/commands/migrate.cjs +62 -36
  71. package/ultra-tools/commands/status.cjs +29 -2
  72. package/ultra-tools/commands/system.cjs +340 -6
  73. package/ultra-tools/commands/task.cjs +11 -2
  74. package/ultra-tools/status-command.test.cjs +49 -0
  75. package/ultra-tools/system-command.test.cjs +256 -0
@@ -10,6 +10,8 @@ const { execFileSync } = require('node:child_process');
10
10
  const { initStateDb, closeStateDb } = require('./state-db.cjs');
11
11
  const ops = require('./state-ops.cjs');
12
12
  const changes = require('./change-workflow.cjs');
13
+ const baselines = require('./baseline-workflow.cjs');
14
+ const archiveJournal = require('./archive-journal.cjs');
13
15
 
14
16
  function fixture() {
15
17
  const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-change-'));
@@ -20,6 +22,11 @@ function fixture() {
20
22
  execFileSync('git', ['add', 'README.md'], { cwd: rootDir });
21
23
  execFileSync('git', ['commit', '-q', '-m', 'seed'], { cwd: rootDir });
22
24
  const { db } = initStateDb(path.join(rootDir, '.ultra', 'state.db'));
25
+ db.prepare(
26
+ `INSERT INTO baselines
27
+ (id, project_name, mode, status, approved_by, approval_note, converged_at)
28
+ VALUES ('test-baseline', 'fixture', 'greenfield', 'ready', 'test', 'accepted fixture', ?)`,
29
+ ).run(new Date().toISOString());
23
30
  return { rootDir, db };
24
31
  }
25
32
 
@@ -124,6 +131,163 @@ test('createChange persists a change and an inspectable external-provider contex
124
131
  }
125
132
  });
126
133
 
134
+ test('new ordinary changes require a ready baseline while incidents require an explicit break-glass record', () => {
135
+ const fx = fixture();
136
+ try {
137
+ assert.throws(
138
+ () => changes.createChange(fx.db, {
139
+ id: 'chg-invalid-bypass', title: 'Ordinary work', kind: 'quick',
140
+ intent: 'Do not attach break-glass approval to ordinary work.',
141
+ baseline_bypass: { reason: 'Not applicable.', approved_by: 'owner' },
142
+ }, { rootDir: fx.rootDir }),
143
+ (error) => error.code === 'VALIDATION_ERROR',
144
+ );
145
+ fx.db.prepare('DELETE FROM baselines').run();
146
+ assert.throws(
147
+ () => changes.createChange(fx.db, {
148
+ id: 'chg-normal-without-baseline', title: 'Do ordinary work', kind: 'quick',
149
+ intent: 'Do not start ordinary work before adoption.',
150
+ }, { rootDir: fx.rootDir }),
151
+ (error) => error.code === 'BASELINE_NOT_READY',
152
+ );
153
+ assert.throws(
154
+ () => changes.createChange(fx.db, {
155
+ id: 'chg-incident-without-approval', title: 'Recover production', kind: 'incident',
156
+ intent: 'Restore the public runtime without waiting for adoption.',
157
+ }, { rootDir: fx.rootDir }),
158
+ (error) => error.code === 'BASELINE_BYPASS_REQUIRED',
159
+ );
160
+
161
+ const created = changes.createChange(fx.db, {
162
+ id: 'chg-incident-break-glass', title: 'Recover production', kind: 'incident',
163
+ intent: 'Restore the public runtime without waiting for adoption.',
164
+ baseline_bypass: {
165
+ reason: 'Production is unavailable and baseline adoption cannot precede recovery.',
166
+ approved_by: 'incident-commander',
167
+ },
168
+ }, { rootDir: fx.rootDir });
169
+ assert.equal(created.change.baseline_bypass.approved_by, 'incident-commander');
170
+ assert.equal(created.change.baseline_bypass.mode, 'incident_break_glass');
171
+ assert.match(created.change.baseline_bypass.recorded_at, /^\d{4}-\d{2}-\d{2}T/);
172
+ } finally { cleanup(fx); }
173
+ });
174
+
175
+ test('task creation authority rejects missing and terminal change ownership even with a healthy baseline', () => {
176
+ const fx = fixture();
177
+ try {
178
+ assert.throws(
179
+ () => changes.assertTaskCreationAllowed(fx.db, { change_id: 'missing-change' }, { rootDir: fx.rootDir }),
180
+ (error) => error.code === 'CHANGE_NOT_FOUND',
181
+ );
182
+ for (const status of ['ready', 'archived', 'cancelled']) {
183
+ const id = `terminal-${status}`;
184
+ changes.createChange(fx.db, {
185
+ id, title: `Terminal ${status}`, kind: 'quick',
186
+ intent: 'Verify terminal changes cannot receive new tasks.',
187
+ }, { rootDir: fx.rootDir });
188
+ fx.db.prepare('UPDATE changes SET status = ? WHERE id = ?').run(status, id);
189
+ assert.throws(
190
+ () => changes.assertTaskCreationAllowed(fx.db, { change_id: id }, { rootDir: fx.rootDir }),
191
+ (error) => error.code === 'CHANGE_NOT_MUTABLE',
192
+ );
193
+ }
194
+ } finally { cleanup(fx); }
195
+ });
196
+
197
+ test('incident break-glass remains executable and archives with a mandatory baseline reconciliation gap', () => {
198
+ const fx = fixture();
199
+ try {
200
+ fx.db.prepare(
201
+ "UPDATE baselines SET mode = 'brownfield', status = 'adopting' WHERE id = 'test-baseline'",
202
+ ).run();
203
+ const created = changes.createChange(fx.db, {
204
+ id: 'chg-break-glass-closure', title: 'Restore the production path', kind: 'incident',
205
+ intent: 'Repair the urgent runtime failure before baseline adoption can finish.',
206
+ docs_impact: { status: 'none', files: [], rationale: 'Internal runtime recovery.' },
207
+ baseline_bypass: {
208
+ reason: 'Production recovery cannot wait for the incomplete brownfield baseline.',
209
+ approved_by: 'incident-commander',
210
+ },
211
+ }, { rootDir: fx.rootDir });
212
+ ops.createTask(fx.db, {
213
+ id: 'break-glass-task', title: 'Repair urgent runtime failure', type: 'bugfix', priority: 'P0',
214
+ change_id: 'chg-break-glass-closure',
215
+ });
216
+ ops.updateTaskStatus(fx.db, 'break-glass-task', 'in_progress');
217
+ ops.updateTaskStatus(fx.db, 'break-glass-task', 'completed');
218
+ const compiled = changes.compileContext(fx.db, {
219
+ id: 'chg-break-glass-closure', ...executionContext('break-glass-task'),
220
+ }, { rootDir: fx.rootDir });
221
+ assert.equal(compiled.manifest.readiness.status, 'ready');
222
+ assert.ok(compiled.manifest.readiness.warnings.includes('BASELINE_NOT_READY:adopting'));
223
+
224
+ const diagnosisPath = path.join(path.dirname(created.intent_path), 'diagnosis.md');
225
+ fs.writeFileSync(diagnosisPath, [
226
+ '# Incident diagnosis: Restore the production path', '',
227
+ '## Reproduction', '', 'The public runtime fails on the terminal transition.', '',
228
+ '## Hypotheses', '', 'The transition loses its durable terminal write.', '',
229
+ '## Root cause', '', 'The terminal path returns before the durable write completes.', '',
230
+ '## Regression test', '', 'The regression test reproduces the lost write and then passes.', '',
231
+ '## Recovery', '', 'Replay the terminal write and verify the public runtime.', '',
232
+ ].join('\n'));
233
+
234
+ const converged = changes.convergeChange(fx.db, {
235
+ id: 'chg-break-glass-closure', evidence: incidentEvidence(),
236
+ }, { rootDir: fx.rootDir });
237
+ assert.equal(converged.ready, true);
238
+
239
+ const archived = changes.archiveChange(fx.db, {
240
+ id: 'chg-break-glass-closure', summary: 'Production restored with regression evidence.',
241
+ no_baseline_change_reason: 'Baseline adoption is tracked by the reconciliation gap.',
242
+ }, { rootDir: fx.rootDir });
243
+ assert.equal(archived.change.status, 'archived');
244
+ assert.equal(archived.baseline_bypass, true);
245
+ const baseline = baselines.readBaseline(fx.db, 'test-baseline');
246
+ assert.equal(baseline.status, 'adopting');
247
+ assert.deepEqual(
248
+ baseline.gaps.map((gap) => ({ id: gap.id, category: gap.category, status: gap.status, blocking: gap.blocking })),
249
+ [{
250
+ id: 'incident-chg-break-glass-closure-reconciliation',
251
+ category: 'baseline_blocker', status: 'open', blocking: true,
252
+ }],
253
+ );
254
+ assert.equal(baseline.gaps[0].owner, 'incident-commander');
255
+ assert.ok(baseline.gaps[0].evidence_refs.some((ref) => ref.includes('changes/archive/')));
256
+ } finally { cleanup(fx); }
257
+ });
258
+
259
+ test('an already-active ordinary change remains executable when baseline adoption later becomes incomplete', () => {
260
+ const fx = fixture();
261
+ try {
262
+ changes.createChange(fx.db, {
263
+ id: 'chg-grandfathered-context', title: 'Finish active work', kind: 'quick',
264
+ intent: 'Preserve an already-active bounded change while baseline recovery runs.',
265
+ }, { rootDir: fx.rootDir });
266
+ ops.createTask(fx.db, {
267
+ id: 'grandfathered-task', title: 'Finish active work', type: 'bugfix', priority: 'P0',
268
+ change_id: 'chg-grandfathered-context',
269
+ });
270
+ fx.db.prepare(
271
+ "UPDATE baselines SET status = 'adopting', mode = 'brownfield' WHERE id = 'test-baseline'",
272
+ ).run();
273
+
274
+ assert.doesNotThrow(() => changes.assertTaskCreationAllowed(fx.db, {
275
+ change_id: 'chg-grandfathered-context',
276
+ }, { rootDir: fx.rootDir }));
277
+ assert.throws(
278
+ () => changes.assertTaskCreationAllowed(fx.db, {}, { rootDir: fx.rootDir }),
279
+ (error) => error.code === 'BASELINE_NOT_READY',
280
+ );
281
+
282
+ const compiled = changes.compileContext(fx.db, {
283
+ id: 'chg-grandfathered-context', ...executionContext('grandfathered-task'),
284
+ }, { rootDir: fx.rootDir });
285
+ assert.equal(compiled.manifest.readiness.status, 'ready');
286
+ assert.ok(compiled.manifest.readiness.warnings.includes('BASELINE_NOT_READY:adopting'));
287
+ assert.deepEqual(compiled.manifest.readiness.blockers, []);
288
+ } finally { cleanup(fx); }
289
+ });
290
+
127
291
  test('convergeChange blocks incomplete work and marks a fully evidenced standard change ready', () => {
128
292
  const fx = fixture();
129
293
  try {
@@ -179,14 +343,103 @@ test('convergeChange blocks incomplete work and marks a fully evidenced standard
179
343
  }
180
344
  });
181
345
 
346
+ test('convergeChange requires an approved baseline after active work has remained executable', () => {
347
+ const fx = fixture();
348
+ try {
349
+ changes.createChange(fx.db, {
350
+ id: 'chg-baseline-gate', title: 'Keep runtime work moving', kind: 'quick',
351
+ intent: 'Defer baseline closure to convergence without losing implementation progress.',
352
+ docs_impact: { status: 'none', files: [], rationale: 'Internal workflow behavior only.' },
353
+ }, { rootDir: fx.rootDir });
354
+ ops.createTask(fx.db, {
355
+ id: 'baseline-gate-task', title: 'Exercise convergence boundary', type: 'feature', priority: 'P0',
356
+ change_id: 'chg-baseline-gate',
357
+ });
358
+ ops.updateTaskStatus(fx.db, 'baseline-gate-task', 'in_progress');
359
+ ops.updateTaskStatus(fx.db, 'baseline-gate-task', 'completed');
360
+ changes.compileContext(fx.db, {
361
+ id: 'chg-baseline-gate', ...executionContext('baseline-gate-task'),
362
+ }, { rootDir: fx.rootDir });
363
+ fx.db.prepare('DELETE FROM baselines').run();
364
+
365
+ const result = changes.convergeChange(fx.db, {
366
+ id: 'chg-baseline-gate',
367
+ evidence: [
368
+ { category: 'diff', status: 'pass', evidence: 'diff reviewed' },
369
+ { category: 'tests', status: 'pass', evidence: 'workflow regression passed', seam: 'convergence gate' },
370
+ { category: 'spec', status: 'pass', evidence: 'change remains aligned' },
371
+ ],
372
+ }, { rootDir: fx.rootDir });
373
+
374
+ assert.equal(result.ready, false);
375
+ assert.ok(result.blockers.includes('BASELINE_MISSING'));
376
+ } finally {
377
+ cleanup(fx);
378
+ }
379
+ });
380
+
381
+ test('change creation rejects a schema-migration compatibility baseline until re-adoption', () => {
382
+ const fx = fixture();
383
+ try {
384
+ fx.db.prepare(
385
+ `UPDATE baselines SET mode = 'migrated', approved_by = 'schema-migration',
386
+ approval_note = 'compatibility only' WHERE id = 'test-baseline'`,
387
+ ).run();
388
+ assert.throws(
389
+ () => changes.createChange(fx.db, {
390
+ id: 'chg-migrated-gate', title: 'Require owner adoption', kind: 'quick',
391
+ intent: 'Do not let schema migration impersonate project-owner approval.',
392
+ docs_impact: { status: 'none', files: [], rationale: 'Gate behavior only.' },
393
+ }, { rootDir: fx.rootDir }),
394
+ (error) => error.code === 'BASELINE_NOT_READY'
395
+ && error.details.blockers.includes('BASELINE_MIGRATION_REVIEW_REQUIRED'),
396
+ );
397
+ } finally {
398
+ cleanup(fx);
399
+ }
400
+ });
401
+
182
402
  test('archiveChange moves a ready change into immutable history and records baseline reconciliation', () => {
183
403
  const fx = fixture();
184
404
  try {
405
+ fs.mkdirSync(path.join(fx.rootDir, '.ultra', 'specs'), { recursive: true });
406
+ fs.writeFileSync(path.join(fx.rootDir, '.ultra', 'specs', 'product.md'), '# Product\n\nCurrent behavior.\n');
407
+ fs.writeFileSync(path.join(fx.rootDir, '.ultra', 'specs', 'architecture.md'), '# Architecture\n\nCurrent boundary.\n');
408
+ const fileDigest = (file) => require('node:crypto').createHash('sha256')
409
+ .update(fs.readFileSync(file)).digest('hex');
410
+ fx.db.prepare(
411
+ `UPDATE baselines SET mode = 'brownfield', repository_revision = ?, spec_refs_json = ?
412
+ WHERE id = 'test-baseline'`,
413
+ ).run(
414
+ execFileSync('git', ['rev-parse', 'HEAD'], { cwd: fx.rootDir, encoding: 'utf8' }).trim(),
415
+ JSON.stringify([
416
+ {
417
+ kind: 'product', path: '.ultra/specs/product.md',
418
+ digest: fileDigest(path.join(fx.rootDir, '.ultra', 'specs', 'product.md')),
419
+ },
420
+ {
421
+ kind: 'architecture', path: '.ultra/specs/architecture.md',
422
+ digest: fileDigest(path.join(fx.rootDir, '.ultra', 'specs', 'architecture.md')),
423
+ },
424
+ ]),
425
+ );
185
426
  changes.createChange(fx.db, {
186
427
  id: 'chg-archive', title: 'Archive verified change', kind: 'quick',
187
428
  intent: 'Preserve the completed change as project history.',
188
429
  docs_impact: { status: 'none', files: [], rationale: 'Internal test-only behavior.' },
189
430
  }, { rootDir: fx.rootDir });
431
+ fs.appendFileSync(
432
+ path.join(fx.rootDir, '.ultra', 'specs', 'product.md'),
433
+ '\nBehavior learned and approved by this change.\n',
434
+ );
435
+ execFileSync('git', ['add', '.ultra/specs/product.md'], { cwd: fx.rootDir });
436
+ execFileSync('git', ['commit', '-q', '-m', 'update baseline behavior'], { cwd: fx.rootDir });
437
+ const changedHead = execFileSync(
438
+ 'git', ['rev-parse', 'HEAD'], { cwd: fx.rootDir, encoding: 'utf8' },
439
+ ).trim();
440
+ const drift = baselines.inspectBaseline(fx.db, { rootDir: fx.rootDir });
441
+ assert.ok(drift.blockers.includes('BASELINE_HEAD_STALE'));
442
+ assert.ok(drift.blockers.includes('BASELINE_SPEC_STALE:.ultra/specs/product.md'));
190
443
  ops.createTask(fx.db, {
191
444
  id: 'archive-task', title: 'Complete archive path', type: 'bugfix', priority: 'P1',
192
445
  change_id: 'chg-archive',
@@ -214,7 +467,7 @@ test('archiveChange moves a ready change into immutable history and records base
214
467
 
215
468
  const archived = changes.archiveChange(fx.db, {
216
469
  id: 'chg-archive', summary: 'Verified quick fix archived.',
217
- no_baseline_change_reason: 'No canonical spec changed.',
470
+ baseline_updates: ['.ultra/specs/product.md'],
218
471
  }, { rootDir: fx.rootDir });
219
472
  assert.equal(archived.change.status, 'archived');
220
473
  assert.ok(fs.existsSync(archived.archive_path));
@@ -228,6 +481,139 @@ test('archiveChange moves a ready change into immutable history and records base
228
481
  assert.doesNotMatch(artifact.path, /changes\/active/);
229
482
  assert.ok(fs.existsSync(path.join(fx.rootDir, artifact.path)), artifact.path);
230
483
  }
484
+ const reconciliation = fx.db.prepare(
485
+ "SELECT type, payload_json FROM events WHERE type = 'baseline_reconciled' ORDER BY id DESC LIMIT 1",
486
+ ).get();
487
+ assert.equal(reconciliation.type, 'baseline_reconciled');
488
+ assert.deepEqual(JSON.parse(reconciliation.payload_json).baseline_updates, ['.ultra/specs/product.md']);
489
+ const reconciled = baselines.inspectBaseline(fx.db, { rootDir: fx.rootDir });
490
+ assert.equal(reconciled.status, 'pass');
491
+ assert.equal(reconciled.baseline.repository_revision, changedHead);
492
+ } finally {
493
+ cleanup(fx);
494
+ }
495
+ });
496
+
497
+ test('archiveChange rolls back when declared reconciliation leaves a tracked specification stale', () => {
498
+ const fx = fixture();
499
+ try {
500
+ const specPath = path.join(fx.rootDir, '.ultra', 'specs', 'product.md');
501
+ fs.mkdirSync(path.dirname(specPath), { recursive: true });
502
+ fs.writeFileSync(specPath, '# Product\n\nOriginal behavior.\n');
503
+ const originalHead = execFileSync(
504
+ 'git', ['rev-parse', 'HEAD'], { cwd: fx.rootDir, encoding: 'utf8' },
505
+ ).trim();
506
+ const originalDigest = require('node:crypto').createHash('sha256')
507
+ .update(fs.readFileSync(specPath)).digest('hex');
508
+ fx.db.prepare(
509
+ `UPDATE baselines SET mode = 'brownfield', repository_revision = ?, spec_refs_json = ?
510
+ WHERE id = 'test-baseline'`,
511
+ ).run(originalHead, JSON.stringify([
512
+ { kind: 'product', path: '.ultra/specs/product.md', digest: originalDigest },
513
+ ]));
514
+ changes.createChange(fx.db, {
515
+ id: 'chg-reconcile-rollback', title: 'Reject incomplete reconciliation', kind: 'quick',
516
+ intent: 'Keep the active change recoverable until every tracked specification is reconciled.',
517
+ docs_impact: { status: 'none', files: [], rationale: 'Workflow integrity only.' },
518
+ }, { rootDir: fx.rootDir });
519
+ fs.appendFileSync(specPath, '\nChanged behavior that must be declared.\n');
520
+ execFileSync('git', ['add', '.ultra/specs/product.md'], { cwd: fx.rootDir });
521
+ execFileSync('git', ['commit', '-q', '-m', 'change tracked product spec'], { cwd: fx.rootDir });
522
+ ops.createTask(fx.db, {
523
+ id: 'reconcile-rollback-task', title: 'Exercise archive rollback', type: 'feature', priority: 'P0',
524
+ change_id: 'chg-reconcile-rollback',
525
+ });
526
+ ops.updateTaskStatus(fx.db, 'reconcile-rollback-task', 'in_progress');
527
+ ops.updateTaskStatus(fx.db, 'reconcile-rollback-task', 'completed');
528
+ changes.compileContext(fx.db, {
529
+ id: 'chg-reconcile-rollback', ...executionContext('reconcile-rollback-task'),
530
+ }, { rootDir: fx.rootDir });
531
+ const converged = changes.convergeChange(fx.db, {
532
+ id: 'chg-reconcile-rollback',
533
+ evidence: [
534
+ { category: 'diff', status: 'pass', evidence: 'archive diff reviewed' },
535
+ {
536
+ category: 'tests', status: 'pass', evidence: 'archive rollback regression passed',
537
+ seam: 'archive reconciliation boundary',
538
+ },
539
+ { category: 'spec', status: 'pass', evidence: 'tracked specification change identified' },
540
+ ],
541
+ }, { rootDir: fx.rootDir });
542
+ assert.equal(converged.ready, true);
543
+
544
+ assert.throws(
545
+ () => changes.archiveChange(fx.db, {
546
+ id: 'chg-reconcile-rollback', summary: 'Attempt an incomplete archive.',
547
+ no_baseline_change_reason: 'No baseline update declared.',
548
+ }, { rootDir: fx.rootDir }),
549
+ (error) => error.code === 'BASELINE_RECONCILIATION_INCOMPLETE'
550
+ && error.details.blockers.includes('BASELINE_SPEC_STALE:.ultra/specs/product.md'),
551
+ );
552
+
553
+ const active = changes.readChange(fx.db, 'chg-reconcile-rollback');
554
+ assert.equal(active.status, 'ready');
555
+ assert.ok(fs.existsSync(path.join(fx.rootDir, active.artifact_root)));
556
+ assert.equal(fs.existsSync(path.join(fx.rootDir, active.artifact_root, 'archive-summary.md')), false);
557
+ assert.equal(baselines.readBaseline(fx.db).repository_revision, originalHead);
558
+ const reconciliationEvents = fx.db.prepare(
559
+ "SELECT COUNT(*) AS count FROM events WHERE type = 'baseline_reconciled' AND change_id = ?",
560
+ ).get('chg-reconcile-rollback');
561
+ assert.equal(reconciliationEvents.count, 0);
562
+ } finally {
563
+ cleanup(fx);
564
+ }
565
+ });
566
+
567
+ test('archiveChange resumes a durable journal after the filesystem move outlives the process', () => {
568
+ const fx = fixture();
569
+ try {
570
+ changes.createChange(fx.db, {
571
+ id: 'chg-crash-resume', title: 'Resume interrupted archive', kind: 'quick',
572
+ intent: 'Finish an authorized archive after a process crash.',
573
+ docs_impact: { status: 'none', files: [], rationale: 'Recovery-only behavior.' },
574
+ }, { rootDir: fx.rootDir });
575
+ ops.createTask(fx.db, {
576
+ id: 'crash-resume-task', title: 'Verify archive recovery', type: 'bugfix', priority: 'P0',
577
+ change_id: 'chg-crash-resume',
578
+ });
579
+ ops.updateTaskStatus(fx.db, 'crash-resume-task', 'in_progress');
580
+ ops.updateTaskStatus(fx.db, 'crash-resume-task', 'completed');
581
+ changes.compileContext(fx.db, {
582
+ id: 'chg-crash-resume', ...executionContext('crash-resume-task'),
583
+ }, { rootDir: fx.rootDir });
584
+ const converged = changes.convergeChange(fx.db, {
585
+ id: 'chg-crash-resume',
586
+ evidence: [
587
+ { category: 'diff', status: 'pass', evidence: 'recovery diff reviewed' },
588
+ {
589
+ category: 'tests', status: 'pass', evidence: 'crash recovery passed', seam: 'archive retry',
590
+ signal: {
591
+ command: 'node --test archive-journal.test.cjs',
592
+ expected_red: 'archive retry fails after the filesystem move',
593
+ observed_red: true, observed_green: true, deterministic: true, duration_ms: 10,
594
+ },
595
+ },
596
+ { category: 'spec', status: 'not_applicable', evidence: 'No contract change.' },
597
+ ],
598
+ }, { rootDir: fx.rootDir });
599
+ assert.equal(converged.ready, true);
600
+ const input = {
601
+ id: 'chg-crash-resume', summary: 'Resume the interrupted archive safely.',
602
+ no_baseline_change_reason: 'No baseline content changed.',
603
+ };
604
+ const change = changes.readChange(fx.db, input.id);
605
+ const interrupted = archiveJournal.prepareArchiveMove({
606
+ rootDir: fx.rootDir, change, summary: input.summary, baselineUpdates: [],
607
+ noBaselineChangeReason: input.no_baseline_change_reason,
608
+ });
609
+ assert.equal(changes.readChange(fx.db, input.id).status, 'ready');
610
+ assert.equal(fs.existsSync(interrupted.source), false);
611
+ assert.equal(fs.existsSync(interrupted.destination), true);
612
+
613
+ const resumed = changes.archiveChange(fx.db, input, { rootDir: fx.rootDir });
614
+ assert.equal(resumed.change.status, 'archived');
615
+ assert.equal(resumed.archive_path, interrupted.destination);
616
+ assert.equal(archiveJournal.listArchiveIntents(fx.rootDir).length, 0);
231
617
  } finally {
232
618
  cleanup(fx);
233
619
  }
@@ -447,12 +833,12 @@ test('compileContext v2 persists role-scoped context, budget, execution contract
447
833
  }
448
834
  });
449
835
 
450
- test('compileContext blocks a required missing ref or an over-budget role packet', () => {
836
+ test('compileContext blocks a required missing ref', () => {
451
837
  const fx = fixture();
452
838
  try {
453
839
  changes.createChange(fx.db, {
454
840
  id: 'chg-context-budget', title: 'Bound context packet', kind: 'quick',
455
- intent: 'Fail closed when required context is missing or too large.',
841
+ intent: 'Fail closed when required context is missing while reporting size pressure.',
456
842
  }, { rootDir: fx.rootDir });
457
843
  ops.createTask(fx.db, {
458
844
  id: 'budget-task', title: 'Compile bounded packet', type: 'bugfix', priority: 'P0',
@@ -466,14 +852,58 @@ test('compileContext blocks a required missing ref or an over-budget role packet
466
852
  { ref: 'missing-contract.md', kind: 'spec', reason: 'Required contract', required: true },
467
853
  { ref: 'README.md', kind: 'source', reason: 'Current implementation', required: true },
468
854
  ],
469
- budget: { max_tokens: 1, max_files: 1 },
855
+ budget: { max_tokens: 2_000, max_files: 4 },
470
856
  }),
471
857
  }, { rootDir: fx.rootDir });
472
858
 
473
859
  assert.equal(compiled.manifest.readiness.status, 'blocked');
474
860
  assert.ok(compiled.manifest.readiness.blockers.includes('CONTEXT_REQUIRED_REF_MISSING:missing-contract.md'));
475
- assert.ok(compiled.manifest.readiness.blockers.includes('CONTEXT_FILE_BUDGET_EXCEEDED'));
476
- assert.ok(compiled.manifest.readiness.blockers.includes('CONTEXT_TOKEN_BUDGET_EXCEEDED'));
861
+ } finally {
862
+ cleanup(fx);
863
+ }
864
+ });
865
+
866
+ test('an incident can proceed with four necessary large files while context budgets remain advisory', () => {
867
+ const fx = fixture();
868
+ try {
869
+ const refs = [];
870
+ for (let index = 1; index <= 4; index += 1) {
871
+ const ref = `incident-runtime-${index}.cjs`;
872
+ fs.writeFileSync(path.join(fx.rootDir, ref), 'x'.repeat(8_000));
873
+ refs.push({
874
+ ref, kind: 'source', reason: `Required incident runtime boundary ${index}`, required: true,
875
+ });
876
+ }
877
+ changes.createChange(fx.db, {
878
+ id: 'chg-incident-context', title: 'Recover IM runtime', kind: 'incident',
879
+ intent: 'Keep an urgent root-cause fix executable when its required files exceed guidance.',
880
+ }, { rootDir: fx.rootDir });
881
+ ops.createTask(fx.db, {
882
+ id: 'incident-context-task', title: 'Repair IM terminal transition', type: 'bugfix', priority: 'P0',
883
+ change_id: 'chg-incident-context',
884
+ });
885
+
886
+ const compiled = changes.compileContext(fx.db, {
887
+ id: 'chg-incident-context',
888
+ ...executionContext('incident-context-task', {
889
+ context_refs: refs,
890
+ budget: { max_tokens: 100, max_files: 2 },
891
+ execution_contract: {
892
+ slice_kind: 'tracer_bullet',
893
+ public_seam: 'IM terminal transition',
894
+ verification_command: 'node --test im-terminal.test.cjs',
895
+ context_budget_percent: 80,
896
+ },
897
+ }),
898
+ }, { rootDir: fx.rootDir });
899
+
900
+ assert.equal(compiled.manifest.readiness.status, 'ready');
901
+ assert.deepEqual(compiled.manifest.readiness.blockers, []);
902
+ assert.ok(compiled.manifest.readiness.warnings.includes('CONTEXT_FILE_BUDGET_EXCEEDED'));
903
+ assert.ok(compiled.manifest.readiness.warnings.includes('CONTEXT_TOKEN_BUDGET_EXCEEDED'));
904
+ assert.ok(compiled.manifest.readiness.warnings.includes('EXECUTION_CONTEXT_BUDGET_ADVISORY'));
905
+ assert.equal(compiled.manifest.context.file_count, 4);
906
+ assert.ok(compiled.manifest.context.token_estimate > 100);
477
907
  } finally {
478
908
  cleanup(fx);
479
909
  }