ultra-builder-pro-cli 0.14.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 (154) 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 +44 -1
  8. package/README.md +34 -19
  9. package/adapters/_shared/codex-assets.cjs +6 -2
  10. package/adapters/_shared/file-ops.cjs +6 -2
  11. package/adapters/claude.js +3 -2
  12. package/adapters/codex.js +1 -0
  13. package/adapters/kimi.js +3 -2
  14. package/adapters/opencode.js +3 -2
  15. package/agents/review-coordinator.md +9 -6
  16. package/docs/AGENT-CONTEXT.md +15 -8
  17. package/docs/ARCHITECTURE.md +33 -12
  18. package/docs/PLAN.zh-CN.md +3 -2
  19. package/docs/ROADMAP.md +8 -4
  20. package/docs/RUNTIME-COMPAT-MATRIX.md +4 -4
  21. package/docs/STATE-DB-ACCESS-POLICY.md +27 -25
  22. package/docs/WORKFLOW-LIFECYCLE.md +266 -0
  23. package/hooks/README.md +6 -1
  24. package/hooks/health_check.py +12 -3
  25. package/hooks/subagent_tracker.py +52 -77
  26. package/mcp-server/hook-event.cjs +34 -0
  27. package/mcp-server/lib/archive-journal.cjs +17 -5
  28. package/mcp-server/lib/baseline-workflow.cjs +158 -18
  29. package/mcp-server/lib/change-workflow.cjs +550 -85
  30. package/mcp-server/lib/context-spine.cjs +242 -24
  31. package/mcp-server/lib/doctor.cjs +33 -3
  32. package/mcp-server/lib/init-project.cjs +210 -16
  33. package/mcp-server/lib/plan-store.cjs +4 -1
  34. package/mcp-server/lib/pricing.cjs +7 -10
  35. package/mcp-server/lib/project-breadcrumb.cjs +6 -0
  36. package/mcp-server/lib/projector.cjs +57 -3
  37. package/mcp-server/lib/spec-learning.cjs +79 -2
  38. package/mcp-server/lib/state-db.cjs +145 -8
  39. package/mcp-server/lib/state-ops.cjs +160 -9
  40. package/mcp-server/lib/workflow-state.cjs +2292 -0
  41. package/mcp-server/server.cjs +161 -15
  42. package/orchestrator/daemon.cjs +1 -2
  43. package/orchestrator/dispatch-rules.cjs +5 -31
  44. package/orchestrator/planner/plan-builder.cjs +21 -12
  45. package/package.json +13 -3
  46. package/skills/ultra-change/SKILL.md +53 -68
  47. package/skills/ultra-change/references/change-contract.md +36 -0
  48. package/skills/ultra-deliver/SKILL.md +69 -79
  49. package/skills/ultra-deliver/references/baseline-reconciliation.md +20 -0
  50. package/skills/ultra-dev/SKILL.md +54 -59
  51. package/skills/ultra-doctor/SKILL.md +40 -71
  52. package/skills/ultra-init/SKILL.md +96 -103
  53. package/skills/ultra-init/references/brownfield-adoption.md +2 -2
  54. package/skills/ultra-plan/SKILL.md +79 -85
  55. package/skills/ultra-plan/references/semantic-preflight.md +18 -0
  56. package/skills/ultra-research/SKILL.md +87 -60
  57. package/skills/ultra-research/references/00-problem-validation.md +36 -0
  58. package/skills/ultra-research/references/01-opportunity-discovery.md +33 -0
  59. package/skills/ultra-research/references/02-market-assessment.md +30 -0
  60. package/skills/ultra-research/references/03-competitive-landscape.md +31 -0
  61. package/skills/ultra-research/references/04-product-strategy.md +32 -0
  62. package/skills/ultra-research/references/05-assumptions-validation.md +31 -0
  63. package/skills/ultra-research/references/10-user-personas.md +29 -0
  64. package/skills/ultra-research/references/11-user-scenarios.md +28 -0
  65. package/skills/ultra-research/references/20-user-stories.md +30 -0
  66. package/skills/ultra-research/references/21-features-scope.md +29 -0
  67. package/skills/ultra-research/references/22-success-metrics.md +28 -0
  68. package/skills/ultra-research/references/30-architecture-context.md +30 -0
  69. package/skills/ultra-research/references/31-solution-strategy.md +28 -0
  70. package/skills/ultra-research/references/32-building-blocks.md +28 -0
  71. package/skills/ultra-research/references/40-deployment.md +29 -0
  72. package/skills/ultra-research/references/41-quality-risks.md +28 -0
  73. package/skills/ultra-research/references/99-synthesis.md +35 -0
  74. package/skills/ultra-review/SKILL.md +57 -73
  75. package/skills/ultra-review/references/review-modes.md +27 -0
  76. package/skills/ultra-review/references/unified-schema.md +25 -0
  77. package/skills/ultra-review/scripts/review_wait.py +40 -3
  78. package/skills/ultra-status/SKILL.md +41 -45
  79. package/skills/ultra-test/SKILL.md +48 -55
  80. package/skills/ultra-verify/SKILL.md +1 -1
  81. package/skills/ultra-verify/references/{confidence-system.md → evidence-status.md} +3 -3
  82. package/spec/cli-protocol.md +7 -2
  83. package/spec/mcp-tools.yaml +384 -63
  84. package/spec/schemas/baseline-reconciliation.v1.schema.json +73 -0
  85. package/spec/schemas/delivery-report.v1.schema.json +111 -0
  86. package/spec/schemas/mcp-tools.schema.json +1 -1
  87. package/spec/schemas/state-db.sql +82 -1
  88. package/spec/schemas/tasks.v4.5.schema.json +8 -1
  89. package/spec/schemas/test-report.v1.schema.json +194 -0
  90. package/ultra-tools/cli.cjs +1 -1
  91. package/ultra-tools/commands/migrate.cjs +3 -3
  92. package/ultra-tools/commands/status.cjs +213 -12
  93. package/.ultra-template/delivery-report.json +0 -9
  94. package/.ultra-template/test-report.json +0 -8
  95. package/adapters/_shared/tests/file-ops.test.cjs +0 -54
  96. package/adapters/_shared/tests/frontmatter.test.cjs +0 -47
  97. package/adapters/_shared/tests/handbook.test.cjs +0 -101
  98. package/adapters/_shared/tests/path-rewrite.test.cjs +0 -60
  99. package/adapters/_shared/tests/provenance.test.cjs +0 -133
  100. package/adapters/_shared/tests/resolve-target.test.cjs +0 -75
  101. package/adapters/_shared/tests/runtime-assets.test.cjs +0 -149
  102. package/adapters/_shared/tests/settings-merge.test.cjs +0 -85
  103. package/adapters/_shared/tests/validate.test.cjs +0 -30
  104. package/adapters/tests/claude.test.cjs +0 -138
  105. package/adapters/tests/codex-hook.test.cjs +0 -142
  106. package/adapters/tests/codex.test.cjs +0 -430
  107. package/adapters/tests/kimi-hook.test.cjs +0 -149
  108. package/adapters/tests/kimi.test.cjs +0 -259
  109. package/adapters/tests/opencode.test.cjs +0 -357
  110. package/mcp-server/lib/archive-journal.test.cjs +0 -35
  111. package/mcp-server/lib/baseline-workflow.test.cjs +0 -477
  112. package/mcp-server/lib/change-workflow.test.cjs +0 -971
  113. package/mcp-server/lib/context-spine.test.cjs +0 -93
  114. package/mcp-server/lib/doctor.test.cjs +0 -148
  115. package/mcp-server/lib/plan-store.test.cjs +0 -132
  116. package/mcp-server/lib/prd-parser.test.cjs +0 -82
  117. package/mcp-server/lib/project-breadcrumb.test.cjs +0 -112
  118. package/mcp-server/lib/runtime-state.test.cjs +0 -92
  119. package/mcp-server/lib/spec-learning.test.cjs +0 -67
  120. package/mcp-server/lib/state-db.test.cjs +0 -314
  121. package/mcp-server/lib/task-expander.test.cjs +0 -169
  122. package/mcp-server/lib/topo.test.cjs +0 -158
  123. package/mcp-server/tests/circuit-breaker.test.cjs +0 -125
  124. package/mcp-server/tests/commit-hash-flow.test.cjs +0 -117
  125. package/mcp-server/tests/concurrency-update.test.cjs +0 -96
  126. package/mcp-server/tests/concurrency.test.cjs +0 -112
  127. package/mcp-server/tests/db-ops.test.cjs +0 -121
  128. package/mcp-server/tests/init-project.test.cjs +0 -393
  129. package/mcp-server/tests/migrate.test.cjs +0 -232
  130. package/mcp-server/tests/projector.test.cjs +0 -162
  131. package/mcp-server/tests/server-contract.test.cjs +0 -1467
  132. package/mcp-server/tests/staleness.test.cjs +0 -190
  133. package/mcp-server/tests/state-db.test.cjs +0 -106
  134. package/mcp-server/tests/state-ops.test.cjs +0 -449
  135. package/mcp-server/tests/tagged-tasks.test.cjs +0 -163
  136. package/mcp-server/tests/telemetry.test.cjs +0 -219
  137. package/orchestrator/planner/plan-builder.test.cjs +0 -165
  138. package/orchestrator/tests/auto-merge.test.cjs +0 -216
  139. package/orchestrator/tests/daemon.test.cjs +0 -311
  140. package/orchestrator/tests/dispatch-rules.test.cjs +0 -124
  141. package/orchestrator/tests/parallel-orchestrator.test.cjs +0 -233
  142. package/orchestrator/tests/phase-8b-gate.test.cjs +0 -183
  143. package/orchestrator/tests/recovery.test.cjs +0 -190
  144. package/orchestrator/tests/session-runner.test.cjs +0 -314
  145. package/orchestrator/tests/skill-miner.test.cjs +0 -147
  146. package/orchestrator/tests/worktree-manager.test.cjs +0 -148
  147. package/skills/ultra-research/references/architecture.md +0 -38
  148. package/skills/ultra-research/references/problem-product.md +0 -39
  149. package/skills/ultra-research/references/quality-delivery.md +0 -34
  150. package/skills/ultra-research/references/synthesis.md +0 -38
  151. package/skills/ultra-research/references/users-scope.md +0 -40
  152. package/ultra-tools/legacy-memory.test.cjs +0 -80
  153. package/ultra-tools/status-command.test.cjs +0 -181
  154. package/ultra-tools/system-command.test.cjs +0 -309
@@ -1,971 +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 { execFileSync } = require('node:child_process');
9
-
10
- const { initStateDb, closeStateDb } = require('./state-db.cjs');
11
- const ops = require('./state-ops.cjs');
12
- const changes = require('./change-workflow.cjs');
13
- const baselines = require('./baseline-workflow.cjs');
14
- const archiveJournal = require('./archive-journal.cjs');
15
-
16
- function fixture() {
17
- const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-change-'));
18
- execFileSync('git', ['init', '-q'], { cwd: rootDir });
19
- execFileSync('git', ['config', 'user.email', 'test@ubp.dev'], { cwd: rootDir });
20
- execFileSync('git', ['config', 'user.name', 'ubp-test'], { cwd: rootDir });
21
- fs.writeFileSync(path.join(rootDir, 'README.md'), '# Fixture\n');
22
- execFileSync('git', ['add', 'README.md'], { cwd: rootDir });
23
- execFileSync('git', ['commit', '-q', '-m', 'seed'], { cwd: rootDir });
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());
30
- return { rootDir, db };
31
- }
32
-
33
- function cleanup({ rootDir, db }) {
34
- closeStateDb(db);
35
- fs.rmSync(rootDir, { recursive: true, force: true });
36
- }
37
-
38
- function standardEvidence() {
39
- return [
40
- { category: 'diff', status: 'pass', evidence: 'git diff reviewed' },
41
- {
42
- category: 'tests', status: 'pass', evidence: 'node --test passed',
43
- seam: 'public change workflow contract',
44
- },
45
- { category: 'spec', status: 'pass', evidence: 'delta reconciled' },
46
- { category: 'docs', status: 'pass', evidence: 'docs/feature.md updated' },
47
- {
48
- category: 'review', axis: 'spec_fidelity', status: 'pass',
49
- evidence: 'Acceptance behavior matches the approved delta.',
50
- },
51
- {
52
- category: 'review', axis: 'engineering_standards', status: 'pass',
53
- evidence: 'Engineering findings are resolved.',
54
- },
55
- ];
56
- }
57
-
58
- function incidentEvidence() {
59
- return [
60
- { category: 'diagnosis', status: 'pass', evidence: 'Structured diagnosis artifact reviewed.' },
61
- { category: 'diff', status: 'pass', evidence: 'Root-cause fix diff reviewed.' },
62
- {
63
- category: 'tests', status: 'pass', evidence: 'Regression test reproduces then verifies the fix.',
64
- seam: 'projection recovery boundary',
65
- signal: {
66
- command: 'node --test recovery.test.cjs',
67
- expected_red: 'stale projection remains running',
68
- observed_red: true,
69
- observed_green: true,
70
- deterministic: true,
71
- duration_ms: 25,
72
- },
73
- },
74
- ];
75
- }
76
-
77
- function executionContext(taskId, overrides = {}) {
78
- return {
79
- task_id: taskId,
80
- role: 'implement',
81
- gate: 'implementation',
82
- next_action: `Implement and verify ${taskId}.`,
83
- context_refs: [
84
- {
85
- ref: 'README.md', kind: 'spec', reason: 'Public behavior contract', required: true,
86
- },
87
- ],
88
- budget: { max_tokens: 2_000, max_files: 4 },
89
- execution_contract: {
90
- slice_kind: 'tracer_bullet',
91
- public_seam: `public seam for ${taskId}`,
92
- verification_command: `node --test ${taskId}.test.cjs`,
93
- context_budget_percent: 40,
94
- },
95
- ...overrides,
96
- };
97
- }
98
-
99
- test('createChange persists a change and an inspectable external-provider context manifest', () => {
100
- const fx = fixture();
101
- try {
102
- const created = changes.createChange(fx.db, {
103
- id: 'chg-context',
104
- title: 'Keep context current',
105
- kind: 'standard',
106
- intent: 'Add a continuous change lane after project delivery.',
107
- provider_refs: {
108
- memory: { provider: 'cloud-mem', reference: 'cmem://project/fixture', status: 'available' },
109
- code_graph: {
110
- provider: 'codebase-memory-mcp', project: 'fixture', revision: 'graph-7',
111
- indexed_head: execFileSync('git', ['rev-parse', 'HEAD'], { cwd: fx.rootDir, encoding: 'utf8' }).trim(),
112
- status: 'fresh',
113
- },
114
- },
115
- }, { rootDir: fx.rootDir });
116
-
117
- assert.equal(created.change.status, 'active');
118
- assert.equal(created.change.kind, 'standard');
119
- assert.ok(fs.existsSync(created.intent_path));
120
- assert.ok(fs.existsSync(created.context_manifest_path));
121
-
122
- const manifest = JSON.parse(fs.readFileSync(created.context_manifest_path, 'utf8'));
123
- assert.equal(manifest.change.id, 'chg-context');
124
- assert.equal(manifest.providers.memory.provider, 'cloud-mem');
125
- assert.equal(manifest.providers.code_graph.provider, 'codebase-memory-mcp');
126
- assert.equal(manifest.providers.code_graph.status, 'fresh');
127
- assert.equal(JSON.stringify(manifest).includes('prompt'), false);
128
- assert.equal(JSON.stringify(manifest).includes('transcript'), false);
129
- } finally {
130
- cleanup(fx);
131
- }
132
- });
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
-
291
- test('convergeChange blocks incomplete work and marks a fully evidenced standard change ready', () => {
292
- const fx = fixture();
293
- try {
294
- changes.createChange(fx.db, {
295
- id: 'chg-converge', title: 'Converge artifacts', kind: 'standard',
296
- intent: 'Require code, specs, tests, docs, and review evidence to agree.',
297
- }, { rootDir: fx.rootDir });
298
-
299
- const blocked = changes.convergeChange(fx.db, {
300
- id: 'chg-converge', evidence: standardEvidence(),
301
- }, { rootDir: fx.rootDir });
302
- assert.equal(blocked.ready, false);
303
- assert.ok(blocked.blockers.includes('NO_TASKS'));
304
- assert.ok(blocked.blockers.includes('DOCS_IMPACT_UNKNOWN'));
305
- assert.ok(blocked.blockers.includes('SPEC_DELTA_MISSING'));
306
- const blockedEvent = fx.db.prepare(
307
- "SELECT type, change_id FROM events WHERE change_id = ? ORDER BY id DESC LIMIT 1",
308
- ).get('chg-converge');
309
- assert.deepEqual(blockedEvent, { type: 'change_blocked', change_id: 'chg-converge' });
310
-
311
- fs.mkdirSync(path.join(fx.rootDir, 'docs'), { recursive: true });
312
- fs.writeFileSync(path.join(fx.rootDir, 'docs', 'feature.md'), '# Continuous changes\n');
313
- const deltaDir = path.join(fx.rootDir, '.ultra', 'changes', 'active', 'chg-converge', 'delta');
314
- fs.mkdirSync(deltaDir, { recursive: true });
315
- fs.writeFileSync(path.join(deltaDir, 'product.md'), '# Delta\n\nDaily changes remain traceable.\n');
316
- fs.writeFileSync(
317
- path.join(fx.rootDir, '.ultra', 'changes', 'active', 'chg-converge', 'plan.md'),
318
- '# Plan\n\nImplement and verify the continuous change contract.\n',
319
- );
320
-
321
- changes.updateChange(fx.db, 'chg-converge', {
322
- docs_impact: { status: 'required', files: ['docs/feature.md'], rationale: 'User-facing workflow changed.' },
323
- });
324
- ops.createTask(fx.db, {
325
- id: 'change-task', title: 'Implement change lifecycle', type: 'feature', priority: 'P0',
326
- change_id: 'chg-converge',
327
- });
328
- ops.updateTaskStatus(fx.db, 'change-task', 'in_progress');
329
- ops.updateTaskStatus(fx.db, 'change-task', 'completed');
330
- changes.compileContext(fx.db, {
331
- id: 'chg-converge', ...executionContext('change-task'),
332
- }, { rootDir: fx.rootDir });
333
-
334
- const ready = changes.convergeChange(fx.db, {
335
- id: 'chg-converge', evidence: standardEvidence(),
336
- }, { rootDir: fx.rootDir });
337
- assert.equal(ready.ready, true);
338
- assert.equal(ready.status, 'ready');
339
- assert.deepEqual(ready.blockers, []);
340
- assert.ok(fs.existsSync(ready.verification_path));
341
- } finally {
342
- cleanup(fx);
343
- }
344
- });
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
-
402
- test('archiveChange moves a ready change into immutable history and records baseline reconciliation', () => {
403
- const fx = fixture();
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
- );
426
- changes.createChange(fx.db, {
427
- id: 'chg-archive', title: 'Archive verified change', kind: 'quick',
428
- intent: 'Preserve the completed change as project history.',
429
- docs_impact: { status: 'none', files: [], rationale: 'Internal test-only behavior.' },
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'));
443
- ops.createTask(fx.db, {
444
- id: 'archive-task', title: 'Complete archive path', type: 'bugfix', priority: 'P1',
445
- change_id: 'chg-archive',
446
- });
447
- ops.updateTaskStatus(fx.db, 'archive-task', 'in_progress');
448
- ops.updateTaskStatus(fx.db, 'archive-task', 'completed');
449
- changes.compileContext(fx.db, {
450
- id: 'chg-archive', ...executionContext('archive-task'),
451
- }, { rootDir: fx.rootDir });
452
- const converged = changes.convergeChange(fx.db, {
453
- id: 'chg-archive',
454
- evidence: [
455
- { category: 'diff', status: 'pass', evidence: 'diff reviewed' },
456
- {
457
- category: 'tests', status: 'pass', evidence: 'tests passed', seam: 'archive command',
458
- signal: {
459
- command: 'node --test archive-task.test.cjs', expected_red: 'archive accepts stale context',
460
- observed_red: true, observed_green: true, deterministic: true, duration_ms: 10,
461
- },
462
- },
463
- { category: 'spec', status: 'not_applicable', evidence: 'No behavior contract changed.' },
464
- ],
465
- }, { rootDir: fx.rootDir });
466
- assert.equal(converged.ready, true);
467
-
468
- const archived = changes.archiveChange(fx.db, {
469
- id: 'chg-archive', summary: 'Verified quick fix archived.',
470
- baseline_updates: ['.ultra/specs/product.md'],
471
- }, { rootDir: fx.rootDir });
472
- assert.equal(archived.change.status, 'archived');
473
- assert.ok(fs.existsSync(archived.archive_path));
474
- assert.equal(fs.existsSync(path.join(fx.rootDir, '.ultra', 'changes', 'active', 'chg-archive')), false);
475
- const artifacts = fx.db.prepare(
476
- 'SELECT kind, path, status FROM artifacts WHERE change_id = ? ORDER BY kind',
477
- ).all('chg-archive');
478
- assert.ok(artifacts.some((artifact) => artifact.kind === 'archive_summary'));
479
- for (const artifact of artifacts) {
480
- assert.equal(artifact.status, 'archived');
481
- assert.doesNotMatch(artifact.path, /changes\/active/);
482
- assert.ok(fs.existsSync(path.join(fx.rootDir, artifact.path)), artifact.path);
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);
617
- } finally {
618
- cleanup(fx);
619
- }
620
- });
621
-
622
- test('provider references reject embedded memory or graph payloads', () => {
623
- const fx = fixture();
624
- try {
625
- assert.throws(
626
- () => changes.createChange(fx.db, {
627
- id: 'chg-provider-content', title: 'Reject provider payload', kind: 'quick',
628
- intent: 'Keep external provider content outside Ultra state.',
629
- provider_refs: {
630
- memory: { provider: 'cloud-mem', status: 'available', content: 'captured transcript' },
631
- },
632
- }, { rootDir: fx.rootDir }),
633
- (error) => error.code === 'PROVIDER_CONTENT_FORBIDDEN',
634
- );
635
- } finally {
636
- cleanup(fx);
637
- }
638
- });
639
-
640
- test('createChange rejects whitespace-only title and intent outside the MCP boundary', () => {
641
- const fx = fixture();
642
- try {
643
- assert.throws(
644
- () => changes.createChange(fx.db, {
645
- id: 'chg-empty-title', title: ' ', kind: 'quick', intent: 'Valid intent.',
646
- }, { rootDir: fx.rootDir }),
647
- (error) => error.code === 'VALIDATION_ERROR',
648
- );
649
- assert.throws(
650
- () => changes.createChange(fx.db, {
651
- id: 'chg-empty-intent', title: 'Valid title', kind: 'quick', intent: '\t',
652
- }, { rootDir: fx.rootDir }),
653
- (error) => error.code === 'VALIDATION_ERROR',
654
- );
655
- } finally {
656
- cleanup(fx);
657
- }
658
- });
659
-
660
- test('updateChange keeps intent.md synchronized with authoritative change metadata', () => {
661
- const fx = fixture();
662
- try {
663
- const created = changes.createChange(fx.db, {
664
- id: 'chg-update-intent', title: 'Original title', kind: 'quick',
665
- intent: 'Original intent.',
666
- docs_impact: { status: 'none', files: [], rationale: 'Internal-only change.' },
667
- }, { rootDir: fx.rootDir });
668
-
669
- changes.updateChange(fx.db, 'chg-update-intent', {
670
- title: 'Updated title',
671
- intent: 'Updated intent with current acceptance behavior.',
672
- docs_impact: { status: 'required', files: ['README.md'], rationale: 'Usage changed.' },
673
- }, { rootDir: fx.rootDir });
674
-
675
- const text = fs.readFileSync(created.intent_path, 'utf8');
676
- assert.match(text, /^# Updated title/m);
677
- assert.match(text, /Updated intent with current acceptance behavior\./);
678
- assert.match(text, /Documentation impact: `required`/);
679
- const artifact = fx.db.prepare(
680
- "SELECT content_hash FROM artifacts WHERE change_id = ? AND kind = 'intent'",
681
- ).get('chg-update-intent');
682
- assert.match(artifact.content_hash, /^[0-9a-f]{64}$/);
683
- } finally {
684
- cleanup(fx);
685
- }
686
- });
687
-
688
- test('updateChange rejects whitespace-only title or intent outside the MCP boundary', () => {
689
- const fx = fixture();
690
- try {
691
- changes.createChange(fx.db, {
692
- id: 'chg-update-validation', title: 'Valid title', kind: 'quick',
693
- intent: 'Valid intent.',
694
- }, { rootDir: fx.rootDir });
695
- assert.throws(
696
- () => changes.updateChange(fx.db, 'chg-update-validation', { title: ' ' }, { rootDir: fx.rootDir }),
697
- (error) => error.code === 'VALIDATION_ERROR',
698
- );
699
- assert.throws(
700
- () => changes.updateChange(fx.db, 'chg-update-validation', { intent: '\t' }, { rootDir: fx.rootDir }),
701
- (error) => error.code === 'VALIDATION_ERROR',
702
- );
703
- } finally {
704
- cleanup(fx);
705
- }
706
- });
707
-
708
- test('incident changes create a durable structured diagnosis artifact', () => {
709
- const fx = fixture();
710
- try {
711
- const created = changes.createChange(fx.db, {
712
- id: 'chg-incident-diagnosis', title: 'Diagnose runtime failure', kind: 'incident',
713
- intent: 'Reproduce and fix the runtime failure at its root cause.',
714
- docs_impact: { status: 'none', files: [], rationale: 'Internal runtime repair.' },
715
- }, { rootDir: fx.rootDir });
716
-
717
- const diagnosisPath = path.join(path.dirname(created.intent_path), 'diagnosis.md');
718
- assert.ok(fs.existsSync(diagnosisPath));
719
- const text = fs.readFileSync(diagnosisPath, 'utf8');
720
- for (const heading of ['Reproduction', 'Hypotheses', 'Root cause', 'Regression test', 'Recovery']) {
721
- assert.match(text, new RegExp(`^## ${heading}$`, 'm'));
722
- }
723
- const artifact = fx.db.prepare(
724
- "SELECT kind, path, content_hash FROM artifacts WHERE change_id = ? AND kind = 'diagnosis'",
725
- ).get('chg-incident-diagnosis');
726
- assert.equal(artifact.kind, 'diagnosis');
727
- assert.match(artifact.content_hash, /^[0-9a-f]{64}$/);
728
- assert.equal(path.join(fx.rootDir, artifact.path), diagnosisPath);
729
- } finally {
730
- cleanup(fx);
731
- }
732
- });
733
-
734
- test('incident convergence requires every structured diagnosis section and refreshes its artifact hash', () => {
735
- const fx = fixture();
736
- try {
737
- const created = changes.createChange(fx.db, {
738
- id: 'chg-incident-converge', title: 'Converge runtime diagnosis', kind: 'incident',
739
- intent: 'Require inspectable debugging evidence before incident closure.',
740
- docs_impact: { status: 'none', files: [], rationale: 'Internal runtime repair.' },
741
- }, { rootDir: fx.rootDir });
742
- ops.createTask(fx.db, {
743
- id: 'incident-task', title: 'Fix diagnosed failure', type: 'bugfix', priority: 'P0',
744
- change_id: 'chg-incident-converge',
745
- });
746
- ops.updateTaskStatus(fx.db, 'incident-task', 'in_progress');
747
- ops.updateTaskStatus(fx.db, 'incident-task', 'completed');
748
- changes.compileContext(fx.db, {
749
- id: 'chg-incident-converge', ...executionContext('incident-task'),
750
- }, { rootDir: fx.rootDir });
751
-
752
- const blocked = changes.convergeChange(fx.db, {
753
- id: 'chg-incident-converge', evidence: incidentEvidence(),
754
- }, { rootDir: fx.rootDir });
755
- assert.equal(blocked.ready, false);
756
- assert.ok(blocked.blockers.includes('DIAGNOSIS_SECTION_MISSING:reproduction'));
757
- assert.ok(blocked.blockers.includes('DIAGNOSIS_SECTION_MISSING:root-cause'));
758
-
759
- const diagnosisPath = path.join(path.dirname(created.intent_path), 'diagnosis.md');
760
- const beforeHash = fx.db.prepare(
761
- "SELECT content_hash FROM artifacts WHERE change_id = ? AND kind = 'diagnosis'",
762
- ).get('chg-incident-converge').content_hash;
763
- fs.writeFileSync(diagnosisPath, [
764
- '# Incident diagnosis: Converge runtime diagnosis', '',
765
- '## Reproduction', '', 'The projection worker fails after an interrupted claim.', '',
766
- '## Hypotheses', '', 'A stale running job is never returned to the pending queue.', '',
767
- '## Root cause', '', 'Boot recovery did not consume interrupted projection state.', '',
768
- '## Regression test', '', 'The test seeds a stale running job and verifies requeue.', '',
769
- '## Recovery', '', 'Requeue the job after the stale cutoff and replay projection.', '',
770
- ].join('\n'));
771
-
772
- const ready = changes.convergeChange(fx.db, {
773
- id: 'chg-incident-converge', evidence: incidentEvidence(),
774
- }, { rootDir: fx.rootDir });
775
- assert.equal(ready.ready, true);
776
- const afterHash = fx.db.prepare(
777
- "SELECT content_hash FROM artifacts WHERE change_id = ? AND kind = 'diagnosis'",
778
- ).get('chg-incident-converge').content_hash;
779
- assert.notEqual(afterHash, beforeHash);
780
- assert.equal(
781
- afterHash,
782
- require('node:crypto').createHash('sha256').update(fs.readFileSync(diagnosisPath)).digest('hex'),
783
- );
784
- } finally {
785
- cleanup(fx);
786
- }
787
- });
788
-
789
- test('compileContext v2 persists role-scoped context, budget, execution contract, and breadcrumb in state.db', () => {
790
- const fx = fixture();
791
- try {
792
- changes.createChange(fx.db, {
793
- id: 'chg-context-v2', title: 'Compile role context', kind: 'standard',
794
- intent: 'Give each execution role only the context it needs.',
795
- }, { rootDir: fx.rootDir });
796
- ops.createTask(fx.db, {
797
- id: 'context-v2-task', title: 'Build role-scoped manifest', type: 'feature', priority: 'P0',
798
- change_id: 'chg-context-v2',
799
- });
800
-
801
- const compiled = changes.compileContext(fx.db, {
802
- id: 'chg-context-v2',
803
- ...executionContext('context-v2-task'),
804
- }, { rootDir: fx.rootDir });
805
-
806
- assert.equal(compiled.manifest.schema_version, '2.0');
807
- assert.equal(compiled.manifest.role, 'implement');
808
- assert.equal(compiled.manifest.gate, 'implementation');
809
- assert.equal(compiled.manifest.readiness.status, 'ready');
810
- assert.equal(compiled.manifest.context.items[0].ref, 'README.md');
811
- assert.match(compiled.manifest.context.items[0].digest, /^[0-9a-f]{64}$/);
812
- assert.equal(compiled.manifest.execution_contract.slice_kind, 'tracer_bullet');
813
- assert.equal(compiled.manifest.resume.task_id, 'context-v2-task');
814
- assert.match(compiled.manifest.next_action, /Implement and verify/);
815
-
816
- const snapshot = fx.db.prepare(
817
- 'SELECT role, gate, next_action, readiness, context_json, token_budget FROM context_snapshots ORDER BY created_at DESC LIMIT 1',
818
- ).get();
819
- assert.equal(snapshot.role, 'implement');
820
- assert.equal(snapshot.gate, 'implementation');
821
- assert.equal(snapshot.readiness, 'ready');
822
- assert.equal(snapshot.token_budget, 2_000);
823
- assert.equal(JSON.parse(snapshot.context_json).execution_contract.public_seam, 'public seam for context-v2-task');
824
-
825
- const breadcrumb = changes.readBreadcrumb(fx.db, { id: 'chg-context-v2' }, { rootDir: fx.rootDir });
826
- assert.equal(breadcrumb.change_id, 'chg-context-v2');
827
- assert.equal(breadcrumb.task_id, 'context-v2-task');
828
- assert.equal(breadcrumb.role, 'implement');
829
- assert.equal(breadcrumb.readiness, 'ready');
830
- assert.match(breadcrumb.context_manifest_hash, /^[0-9a-f]{64}$/);
831
- } finally {
832
- cleanup(fx);
833
- }
834
- });
835
-
836
- test('compileContext blocks a required missing ref', () => {
837
- const fx = fixture();
838
- try {
839
- changes.createChange(fx.db, {
840
- id: 'chg-context-budget', title: 'Bound context packet', kind: 'quick',
841
- intent: 'Fail closed when required context is missing while reporting size pressure.',
842
- }, { rootDir: fx.rootDir });
843
- ops.createTask(fx.db, {
844
- id: 'budget-task', title: 'Compile bounded packet', type: 'bugfix', priority: 'P0',
845
- change_id: 'chg-context-budget',
846
- });
847
-
848
- const compiled = changes.compileContext(fx.db, {
849
- id: 'chg-context-budget',
850
- ...executionContext('budget-task', {
851
- context_refs: [
852
- { ref: 'missing-contract.md', kind: 'spec', reason: 'Required contract', required: true },
853
- { ref: 'README.md', kind: 'source', reason: 'Current implementation', required: true },
854
- ],
855
- budget: { max_tokens: 2_000, max_files: 4 },
856
- }),
857
- }, { rootDir: fx.rootDir });
858
-
859
- assert.equal(compiled.manifest.readiness.status, 'blocked');
860
- assert.ok(compiled.manifest.readiness.blockers.includes('CONTEXT_REQUIRED_REF_MISSING:missing-contract.md'));
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);
907
- } finally {
908
- cleanup(fx);
909
- }
910
- });
911
-
912
- test('spec-learning candidates are approval-gated and unresolved candidates block convergence', () => {
913
- const fx = fixture();
914
- try {
915
- changes.createChange(fx.db, {
916
- id: 'chg-learning', title: 'Converge learned contract', kind: 'quick',
917
- intent: 'Promote stable implementation discoveries into an approved baseline update.',
918
- docs_impact: { status: 'none', files: [], rationale: 'Fixture exercises the approval state machine.' },
919
- }, { rootDir: fx.rootDir });
920
- ops.createTask(fx.db, {
921
- id: 'learning-task', title: 'Capture stable contract', type: 'bugfix', priority: 'P0',
922
- change_id: 'chg-learning',
923
- });
924
- ops.updateTaskStatus(fx.db, 'learning-task', 'in_progress');
925
- ops.updateTaskStatus(fx.db, 'learning-task', 'completed');
926
- changes.compileContext(fx.db, {
927
- id: 'chg-learning', ...executionContext('learning-task'),
928
- }, { rootDir: fx.rootDir });
929
-
930
- const proposed = changes.proposeSpecLearning(fx.db, {
931
- id: 'learning-1', change_id: 'chg-learning', task_id: 'learning-task',
932
- target_ref: 'README.md#contract', summary: 'The public command fails closed on stale context.',
933
- evidence: ['node --test learning-task.test.cjs'],
934
- }, { rootDir: fx.rootDir });
935
- assert.equal(proposed.status, 'proposed');
936
-
937
- const blocked = changes.convergeChange(fx.db, {
938
- id: 'chg-learning',
939
- evidence: [
940
- { category: 'diff', status: 'pass', evidence: 'diff reviewed' },
941
- {
942
- category: 'tests', status: 'pass', evidence: 'regression passed', seam: 'public command',
943
- signal: {
944
- command: 'node --test learning-task.test.cjs', expected_red: 'stale context accepted',
945
- observed_red: true, observed_green: true, deterministic: true, duration_ms: 20,
946
- },
947
- },
948
- { category: 'spec', status: 'pass', evidence: 'candidate recorded' },
949
- ],
950
- }, { rootDir: fx.rootDir });
951
- assert.ok(blocked.blockers.includes('SPEC_LEARNING_UNRESOLVED:learning-1'));
952
-
953
- assert.equal(changes.resolveSpecLearning(fx.db, {
954
- change_id: 'chg-learning', candidate_id: 'learning-1', decision: 'approve',
955
- resolution: 'Stable public contract.',
956
- }, { rootDir: fx.rootDir }).status, 'approved');
957
- assert.equal(changes.resolveSpecLearning(fx.db, {
958
- change_id: 'chg-learning', candidate_id: 'learning-1', decision: 'apply',
959
- resolution: 'Applied to README.md#contract.',
960
- }, { rootDir: fx.rootDir }).status, 'applied');
961
-
962
- const projection = JSON.parse(fs.readFileSync(
963
- path.join(fx.rootDir, '.ultra', 'changes', 'active', 'chg-learning', 'spec-learning.json'),
964
- 'utf8',
965
- ));
966
- assert.equal(projection.source, '.ultra/state.db');
967
- assert.equal(projection.candidates[0].status, 'applied');
968
- } finally {
969
- cleanup(fx);
970
- }
971
- });