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,477 +0,0 @@
1
- 'use strict';
2
-
3
- const test = require('node:test');
4
- const assert = require('node:assert/strict');
5
- const crypto = require('node:crypto');
6
- const fs = require('node:fs');
7
- const os = require('node:os');
8
- const path = require('node:path');
9
- const { execFileSync } = require('node:child_process');
10
-
11
- const { initStateDb, closeStateDb } = require('./state-db.cjs');
12
- const baselines = require('./baseline-workflow.cjs');
13
-
14
- function fixture() {
15
- const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-baseline-'));
16
- execFileSync('git', ['init', '-q'], { cwd: rootDir });
17
- execFileSync('git', ['config', 'user.email', 'test@ubp.dev'], { cwd: rootDir });
18
- execFileSync('git', ['config', 'user.name', 'ubp-test'], { cwd: rootDir });
19
- fs.mkdirSync(path.join(rootDir, '.ultra', 'specs'), { recursive: true });
20
- fs.mkdirSync(path.join(rootDir, 'src'), { recursive: true });
21
- fs.writeFileSync(path.join(rootDir, '.ultra', 'specs', 'product.md'), '# Product\n\nObserved behavior.\n');
22
- fs.writeFileSync(path.join(rootDir, '.ultra', 'specs', 'architecture.md'), '# Architecture\n\nCurrent boundary.\n');
23
- fs.writeFileSync(path.join(rootDir, 'src', 'index.js'), 'module.exports = true;\n');
24
- execFileSync('git', ['add', '.'], { cwd: rootDir });
25
- execFileSync('git', ['commit', '-q', '-m', 'seed'], { cwd: rootDir });
26
- const revision = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: rootDir, encoding: 'utf8' }).trim();
27
- const { db } = initStateDb(path.join(rootDir, '.ultra', 'state.db'));
28
- return { rootDir, db, revision };
29
- }
30
-
31
- function cleanup(fx) {
32
- closeStateDb(fx.db);
33
- fs.rmSync(fx.rootDir, { recursive: true, force: true });
34
- }
35
-
36
- function adoptionEvidence() {
37
- return {
38
- spec_refs: [
39
- { kind: 'product', path: '.ultra/specs/product.md' },
40
- { kind: 'architecture', path: '.ultra/specs/architecture.md' },
41
- ],
42
- evidence: [
43
- { kind: 'source', ref: 'src/index.js', summary: 'Current public module entry point.' },
44
- { kind: 'docs', ref: '.ultra/specs/product.md', summary: 'Observed behavior baseline.' },
45
- ],
46
- verification: [
47
- { name: 'module smoke', command: 'node -e "require(\'./src\')"', status: 'pass', evidence: 'exit 0' },
48
- ],
49
- unknowns: [],
50
- };
51
- }
52
-
53
- test('brownfield baseline converges only after current specs, source evidence, verification, and approval', () => {
54
- const fx = fixture();
55
- try {
56
- const started = baselines.startBaseline(fx.db, {
57
- id: 'project-baseline', project_name: 'fixture', project_type: 'cli', stack: 'node',
58
- mode: 'brownfield', repository_revision: fx.revision, scope: ['.'],
59
- }, { rootDir: fx.rootDir });
60
- assert.equal(started.status, 'adopting');
61
-
62
- const recorded = baselines.recordBaseline(fx.db, {
63
- id: started.id, repository_revision: fx.revision, ...adoptionEvidence(),
64
- }, { rootDir: fx.rootDir });
65
- assert.match(recorded.spec_refs[0].digest, /^[0-9a-f]{64}$/);
66
- assert.equal(recorded.evidence[0].summary, 'Current public module entry point.');
67
-
68
- const converged = baselines.convergeBaseline(fx.db, {
69
- id: started.id,
70
- expected_revision: fx.revision,
71
- approved_by: 'project-owner',
72
- approval_note: 'The captured baseline matches the current checkout.',
73
- accept_known_red: false,
74
- }, { rootDir: fx.rootDir });
75
- assert.deepEqual(converged.blockers, []);
76
- assert.equal(converged.ready, true);
77
- assert.equal(converged.baseline.status, 'ready');
78
- assert.equal(converged.baseline.approved_by, 'project-owner');
79
- } finally {
80
- cleanup(fx);
81
- }
82
- });
83
-
84
- test('baseline convergence records deterministic blockers for missing evidence and unaccepted known-red verification', () => {
85
- const fx = fixture();
86
- try {
87
- baselines.startBaseline(fx.db, {
88
- id: 'blocked-baseline', project_name: 'fixture', mode: 'brownfield',
89
- repository_revision: fx.revision,
90
- }, { rootDir: fx.rootDir });
91
- baselines.recordBaseline(fx.db, {
92
- id: 'blocked-baseline',
93
- repository_revision: fx.revision,
94
- spec_refs: [{ kind: 'product', path: '.ultra/specs/product.md' }],
95
- evidence: [],
96
- verification: [{
97
- name: 'legacy suite', command: 'npm test', status: 'known_red',
98
- evidence: 'Two failures predate adoption.', rationale: 'Tracked as existing debt.',
99
- }],
100
- unknowns: [{ summary: 'Production authorization behavior is not verified.', blocking: true }],
101
- }, { rootDir: fx.rootDir });
102
-
103
- const result = baselines.convergeBaseline(fx.db, {
104
- id: 'blocked-baseline', expected_revision: fx.revision,
105
- approved_by: 'project-owner', approval_note: 'Review attempted.', accept_known_red: false,
106
- }, { rootDir: fx.rootDir });
107
- assert.equal(result.ready, false);
108
- assert.equal(result.status, 'blocked');
109
- assert.ok(result.blockers.includes('BASELINE_SPEC_MISSING:architecture'));
110
- assert.ok(result.blockers.includes('BASELINE_SOURCE_EVIDENCE_MISSING'));
111
- assert.ok(result.blockers.includes('BASELINE_KNOWN_RED_NOT_ACCEPTED:legacy suite'));
112
- assert.ok(result.blockers.includes('BASELINE_UNKNOWN_BLOCKING:Production authorization behavior is not verified.'));
113
- } finally {
114
- cleanup(fx);
115
- }
116
- });
117
-
118
- test('baseline stores provider metadata only and detects specification drift after convergence', () => {
119
- const fx = fixture();
120
- try {
121
- baselines.startBaseline(fx.db, {
122
- id: 'provider-baseline', project_name: 'fixture', mode: 'brownfield',
123
- repository_revision: fx.revision,
124
- }, { rootDir: fx.rootDir });
125
- assert.throws(
126
- () => baselines.recordBaseline(fx.db, {
127
- id: 'provider-baseline',
128
- provider_refs: { memory: { provider: 'cloud-mem', content: 'captured prompt' } },
129
- }, { rootDir: fx.rootDir }),
130
- (error) => error.code === 'PROVIDER_CONTENT_FORBIDDEN',
131
- );
132
- baselines.recordBaseline(fx.db, {
133
- id: 'provider-baseline', repository_revision: fx.revision,
134
- provider_refs: { memory: { provider: 'cloud-mem', reference: 'cmem://fixture', status: 'fresh' } },
135
- ...adoptionEvidence(),
136
- }, { rootDir: fx.rootDir });
137
- baselines.convergeBaseline(fx.db, {
138
- id: 'provider-baseline', expected_revision: fx.revision,
139
- approved_by: 'project-owner', approval_note: 'Approved current behavior.',
140
- }, { rootDir: fx.rootDir });
141
-
142
- fs.appendFileSync(path.join(fx.rootDir, '.ultra', 'specs', 'product.md'), '\nChanged outside convergence.\n');
143
- const health = baselines.inspectBaseline(fx.db, { rootDir: fx.rootDir });
144
- assert.equal(health.status, 'fail');
145
- assert.ok(health.blockers.includes('BASELINE_SPEC_STALE:.ultra/specs/product.md'));
146
- } finally {
147
- cleanup(fx);
148
- }
149
- });
150
-
151
- test('re-adoption requires explicit replacement and supersedes the prior ready baseline', () => {
152
- const fx = fixture();
153
- try {
154
- const digest = (file) => crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
155
- fx.db.prepare(
156
- `INSERT INTO baselines
157
- (id, project_name, mode, status, repository_revision, spec_refs_json, evidence_json, verification_json,
158
- approved_by, approval_note, converged_at)
159
- VALUES (?, ?, 'brownfield', 'ready', ?, ?, ?, ?, ?, ?, ?)`,
160
- ).run(
161
- 'ready-baseline', 'fixture', fx.revision,
162
- JSON.stringify([
163
- { kind: 'product', path: '.ultra/specs/product.md', digest: digest(path.join(fx.rootDir, '.ultra/specs/product.md')) },
164
- { kind: 'architecture', path: '.ultra/specs/architecture.md', digest: digest(path.join(fx.rootDir, '.ultra/specs/architecture.md')) },
165
- ]),
166
- JSON.stringify([{ kind: 'source', ref: 'src/index.js', summary: 'Current entry point.' }]),
167
- JSON.stringify([{ name: 'smoke', command: 'node -e "require(\'./src\')"', status: 'pass', evidence: 'exit 0' }]),
168
- 'owner', 'approved', new Date().toISOString(),
169
- );
170
-
171
- assert.throws(
172
- () => baselines.startBaseline(fx.db, {
173
- id: 'replacement', project_name: 'fixture', mode: 'brownfield', repository_revision: fx.revision,
174
- }, { rootDir: fx.rootDir }),
175
- (error) => error.code === 'BASELINE_EXISTS',
176
- );
177
- assert.throws(
178
- () => baselines.startBaseline(fx.db, {
179
- id: 'replacement', project_name: 'fixture', mode: 'brownfield',
180
- repository_revision: fx.revision, replace_ready: true,
181
- }, { rootDir: fx.rootDir }),
182
- (error) => error.code === 'BASELINE_REPLACEMENT_AUTHORIZATION_REQUIRED',
183
- );
184
- const replacement = baselines.startBaseline(fx.db, {
185
- id: 'replacement', project_name: 'fixture', mode: 'brownfield',
186
- repository_revision: fx.revision, replace_ready: true,
187
- replacement_authorization: {
188
- approved_by: 'project-owner',
189
- reason: 'The maintained system boundary changed and requires a new evidence baseline.',
190
- },
191
- }, { rootDir: fx.rootDir });
192
- assert.equal(replacement.status, 'adopting');
193
- assert.equal(baselines.readBaseline(fx.db, 'ready-baseline').status, 'superseded');
194
- const supersededHealth = baselines.inspectBaseline(fx.db, {
195
- rootDir: fx.rootDir, id: 'ready-baseline',
196
- });
197
- assert.ok(supersededHealth.blockers.includes('BASELINE_NOT_READY:superseded'));
198
- const event = fx.db.prepare(
199
- "SELECT payload_json FROM events WHERE type = 'baseline_started' ORDER BY id DESC LIMIT 1",
200
- ).get();
201
- assert.deepEqual(JSON.parse(event.payload_json).replacement_authorization, {
202
- approved_by: 'project-owner',
203
- reason: 'The maintained system boundary changed and requires a new evidence baseline.',
204
- recorded_at: replacement.started_at,
205
- });
206
- } finally {
207
- cleanup(fx);
208
- }
209
- });
210
-
211
- test('greenfield baseline derives a stable workspace revision when Git is not initialized', () => {
212
- const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-greenfield-baseline-'));
213
- fs.mkdirSync(path.join(rootDir, '.ultra', 'specs'), { recursive: true });
214
- fs.writeFileSync(path.join(rootDir, '.ultra', 'specs', 'product.md'), '# Product\n\nAccepted intent.\n');
215
- fs.writeFileSync(path.join(rootDir, '.ultra', 'specs', 'architecture.md'), '# Architecture\n\nAccepted constraints.\n');
216
- const { db } = initStateDb(path.join(rootDir, '.ultra', 'state.db'));
217
- try {
218
- baselines.startBaseline(db, {
219
- id: 'greenfield', project_name: 'new-project', mode: 'greenfield', scope: ['.'],
220
- }, { rootDir });
221
- const recorded = baselines.recordBaseline(db, {
222
- id: 'greenfield',
223
- spec_refs: [
224
- { kind: 'product', path: '.ultra/specs/product.md' },
225
- { kind: 'architecture', path: '.ultra/specs/architecture.md' },
226
- ],
227
- evidence: [{ kind: 'docs', ref: '.ultra/specs/product.md', summary: 'Owner-approved product intent.' }],
228
- verification: [{
229
- name: 'spec review', command: 'review baseline specifications', status: 'pass',
230
- evidence: 'Product and architecture contracts agree.',
231
- }],
232
- unknowns: [],
233
- }, { rootDir });
234
- assert.match(recorded.repository_revision, /^workspace:[0-9a-f]{64}$/);
235
- const result = baselines.convergeBaseline(db, {
236
- id: 'greenfield', expected_revision: recorded.repository_revision,
237
- approved_by: 'owner', approval_note: 'Approved before planning.',
238
- }, { rootDir });
239
- assert.equal(result.ready, true);
240
- } finally {
241
- closeStateDb(db);
242
- fs.rmSync(rootDir, { recursive: true, force: true });
243
- }
244
- });
245
-
246
- test('brownfield evidence and scope must resolve to current project files', () => {
247
- const fx = fixture();
248
- try {
249
- assert.throws(
250
- () => baselines.startBaseline(fx.db, {
251
- id: 'missing-scope', project_name: 'fixture', mode: 'brownfield',
252
- scope: ['src/does-not-exist'], repository_revision: fx.revision,
253
- }, { rootDir: fx.rootDir }),
254
- (error) => error.code === 'BASELINE_SCOPE_MISSING',
255
- );
256
-
257
- baselines.startBaseline(fx.db, {
258
- id: 'missing-source', project_name: 'fixture', mode: 'brownfield',
259
- scope: ['src'], repository_revision: fx.revision,
260
- }, { rootDir: fx.rootDir });
261
- assert.throws(
262
- () => baselines.recordBaseline(fx.db, {
263
- id: 'missing-source', repository_revision: fx.revision,
264
- spec_refs: adoptionEvidence().spec_refs,
265
- evidence: [{
266
- kind: 'source', ref: 'src/does-not-exist.js', summary: 'Claimed entry point.',
267
- }],
268
- verification: adoptionEvidence().verification,
269
- unknowns: [],
270
- }, { rootDir: fx.rootDir }),
271
- (error) => error.code === 'BASELINE_EVIDENCE_MISSING',
272
- );
273
- } finally {
274
- cleanup(fx);
275
- }
276
- });
277
-
278
- test('brownfield convergence rejects a dirty checkout outside Ultra state', () => {
279
- const fx = fixture();
280
- try {
281
- baselines.startBaseline(fx.db, {
282
- id: 'dirty-baseline', project_name: 'fixture', mode: 'brownfield',
283
- repository_revision: fx.revision, scope: ['src'],
284
- }, { rootDir: fx.rootDir });
285
- baselines.recordBaseline(fx.db, {
286
- id: 'dirty-baseline', repository_revision: fx.revision, ...adoptionEvidence(),
287
- }, { rootDir: fx.rootDir });
288
- fs.appendFileSync(path.join(fx.rootDir, 'src', 'index.js'), 'throw new Error("dirty");\n');
289
-
290
- const result = baselines.convergeBaseline(fx.db, {
291
- id: 'dirty-baseline', expected_revision: fx.revision,
292
- approved_by: 'project-owner', approval_note: 'Approve only a clean checkout.',
293
- }, { rootDir: fx.rootDir });
294
- assert.equal(result.ready, false);
295
- assert.ok(result.blockers.includes('BASELINE_WORKTREE_DIRTY'));
296
- assert.ok(result.blockers.includes('BASELINE_EVIDENCE_STALE:src/index.js'));
297
- } finally {
298
- cleanup(fx);
299
- }
300
- });
301
-
302
- test('a scoped monorepo baseline records only dirty files inside the selected scope', () => {
303
- const fx = fixture();
304
- try {
305
- fs.mkdirSync(path.join(fx.rootDir, 'packages', 'api'), { recursive: true });
306
- fs.mkdirSync(path.join(fx.rootDir, 'packages', 'web'), { recursive: true });
307
- fs.writeFileSync(path.join(fx.rootDir, 'packages', 'api', 'index.js'), 'module.exports = "api";\n');
308
- fs.writeFileSync(path.join(fx.rootDir, 'packages', 'web', 'index.js'), 'module.exports = "web";\n');
309
- execFileSync('git', ['add', 'packages'], { cwd: fx.rootDir });
310
- execFileSync('git', ['commit', '-q', '-m', 'add workspaces'], { cwd: fx.rootDir });
311
- const revision = execFileSync('git', ['rev-parse', 'HEAD'], {
312
- cwd: fx.rootDir, encoding: 'utf8',
313
- }).trim();
314
-
315
- fs.appendFileSync(path.join(fx.rootDir, 'packages', 'web', 'index.js'), '// unrelated work\n');
316
- const started = baselines.startBaseline(fx.db, {
317
- id: 'scoped-dirty', project_name: 'fixture', mode: 'brownfield',
318
- repository_revision: revision, scope: ['packages/api'],
319
- }, { rootDir: fx.rootDir });
320
- assert.equal(started.worktree_state, 'clean');
321
- assert.deepEqual(started.worktree_files, []);
322
-
323
- fs.appendFileSync(path.join(fx.rootDir, 'packages', 'api', 'index.js'), '// adopted work\n');
324
- const recorded = baselines.recordBaseline(fx.db, {
325
- id: started.id, repository_revision: revision,
326
- }, { rootDir: fx.rootDir });
327
- assert.equal(recorded.worktree_state, 'dirty');
328
- assert.ok(recorded.worktree_files.some((entry) => entry.includes('packages/api/index.js')));
329
- assert.equal(
330
- recorded.worktree_files.some((entry) => entry.includes('packages/web/index.js')),
331
- false,
332
- );
333
- } finally { cleanup(fx); }
334
- });
335
-
336
- test('ready baseline detects source evidence drift without a new commit', () => {
337
- const fx = fixture();
338
- try {
339
- baselines.startBaseline(fx.db, {
340
- id: 'source-drift', project_name: 'fixture', mode: 'brownfield',
341
- repository_revision: fx.revision, scope: ['src'],
342
- }, { rootDir: fx.rootDir });
343
- const recorded = baselines.recordBaseline(fx.db, {
344
- id: 'source-drift', repository_revision: fx.revision, ...adoptionEvidence(),
345
- }, { rootDir: fx.rootDir });
346
- assert.match(recorded.evidence[0].digest, /^[0-9a-f]{64}$/);
347
- baselines.convergeBaseline(fx.db, {
348
- id: 'source-drift', expected_revision: fx.revision,
349
- approved_by: 'project-owner', approval_note: 'Approved clean source evidence.',
350
- }, { rootDir: fx.rootDir });
351
-
352
- fs.writeFileSync(path.join(fx.rootDir, 'src', 'index.js'), 'throw new Error("regression");\n');
353
- const health = baselines.inspectBaseline(fx.db, { rootDir: fx.rootDir });
354
- assert.equal(health.status, 'fail');
355
- assert.ok(health.blockers.includes('BASELINE_WORKTREE_DIRTY'));
356
- assert.ok(health.blockers.includes('BASELINE_EVIDENCE_STALE:src/index.js'));
357
- } finally {
358
- cleanup(fx);
359
- }
360
- });
361
-
362
- test('migrated compatibility baseline requires explicit brownfield re-adoption', () => {
363
- const fx = fixture();
364
- try {
365
- fx.db.prepare(
366
- `INSERT INTO baselines
367
- (id, project_name, mode, status, approved_by, approval_note, converged_at)
368
- VALUES ('migrated-baseline', 'legacy', 'migrated', 'ready',
369
- 'schema-migration', 'compatibility row', ?)`,
370
- ).run(new Date().toISOString());
371
- const health = baselines.inspectBaseline(fx.db, {
372
- rootDir: fx.rootDir, id: 'migrated-baseline',
373
- });
374
- assert.equal(health.status, 'fail');
375
- assert.deepEqual(health.blockers, ['BASELINE_MIGRATION_REVIEW_REQUIRED']);
376
- } finally {
377
- cleanup(fx);
378
- }
379
- });
380
-
381
- test('open gap-ledger blockers prevent adoption while accepted debt remains visible', () => {
382
- const fx = fixture();
383
- try {
384
- baselines.startBaseline(fx.db, {
385
- id: 'gap-baseline', project_name: 'fixture', mode: 'brownfield',
386
- repository_revision: fx.revision, scope: ['src'],
387
- }, { rootDir: fx.rootDir });
388
- const recorded = baselines.recordBaseline(fx.db, {
389
- id: 'gap-baseline', repository_revision: fx.revision, ...adoptionEvidence(),
390
- gaps: [
391
- {
392
- id: 'missing-auth-proof', category: 'baseline_blocker', status: 'open',
393
- summary: 'Authorization authority has not been verified.', blocking: true,
394
- evidence_refs: ['src/index.js'], owner: 'security-owner',
395
- },
396
- {
397
- id: 'legacy-test-debt', category: 'technical_debt', status: 'accepted',
398
- summary: 'Legacy integration tests are slow.', blocking: false,
399
- evidence_refs: ['npm test'], owner: 'maintainers',
400
- },
401
- ],
402
- }, { rootDir: fx.rootDir });
403
- assert.deepEqual(recorded.gaps.map((gap) => gap.id), [
404
- 'missing-auth-proof', 'legacy-test-debt',
405
- ]);
406
-
407
- const result = baselines.convergeBaseline(fx.db, {
408
- id: 'gap-baseline', expected_revision: fx.revision,
409
- approved_by: 'project-owner', approval_note: 'Reviewed the evidence ledger.',
410
- }, { rootDir: fx.rootDir });
411
- assert.equal(result.ready, false);
412
- assert.ok(result.blockers.includes('BASELINE_GAP_BLOCKING:missing-auth-proof'));
413
- } finally { cleanup(fx); }
414
- });
415
-
416
- test('a dirty adoption snapshot requires explicit acceptance and remains drift-detectable', () => {
417
- const fx = fixture();
418
- try {
419
- fs.appendFileSync(path.join(fx.rootDir, 'src', 'index.js'), '// accepted local patch\n');
420
- baselines.startBaseline(fx.db, {
421
- id: 'dirty-accepted', project_name: 'fixture', mode: 'brownfield',
422
- repository_revision: fx.revision, scope: ['src'],
423
- }, { rootDir: fx.rootDir });
424
- const recorded = baselines.recordBaseline(fx.db, {
425
- id: 'dirty-accepted', repository_revision: fx.revision, ...adoptionEvidence(),
426
- }, { rootDir: fx.rootDir });
427
- assert.equal(recorded.worktree_state, 'dirty');
428
- assert.match(recorded.worktree_digest, /^[0-9a-f]{64}$/);
429
-
430
- const rejected = baselines.convergeBaseline(fx.db, {
431
- id: 'dirty-accepted', expected_revision: fx.revision,
432
- approved_by: 'project-owner', approval_note: 'Review dirty adoption snapshot.',
433
- }, { rootDir: fx.rootDir });
434
- assert.ok(rejected.blockers.includes('BASELINE_DIRTY_WORKTREE_NOT_ACCEPTED'));
435
-
436
- const accepted = baselines.convergeBaseline(fx.db, {
437
- id: 'dirty-accepted', expected_revision: fx.revision,
438
- approved_by: 'project-owner', approval_note: 'Accept the recorded local patch as baseline evidence.',
439
- accept_dirty_worktree: true,
440
- }, { rootDir: fx.rootDir });
441
- assert.equal(accepted.ready, true);
442
- assert.equal(accepted.baseline.worktree_accepted, true);
443
-
444
- fs.appendFileSync(path.join(fx.rootDir, 'src', 'index.js'), '// later drift\n');
445
- const health = baselines.inspectBaseline(fx.db, { rootDir: fx.rootDir });
446
- assert.ok(health.blockers.includes('BASELINE_WORKTREE_STALE'));
447
- } finally { cleanup(fx); }
448
- });
449
-
450
- test('a migrated compatibility row can only be superseded by explicit brownfield re-adoption', () => {
451
- const fx = fixture();
452
- try {
453
- fx.db.prepare(
454
- `INSERT INTO baselines (id, project_name, mode, status)
455
- VALUES ('migrated-baseline', 'legacy', 'migrated', 'adopting')`,
456
- ).run();
457
- assert.throws(
458
- () => baselines.startBaseline(fx.db, {
459
- id: 'replacement', project_name: 'fixture', mode: 'brownfield', scope: ['src'],
460
- }, { rootDir: fx.rootDir }),
461
- (error) => error.code === 'BASELINE_IN_PROGRESS',
462
- );
463
- assert.throws(
464
- () => baselines.startBaseline(fx.db, {
465
- id: 'wrong-mode', project_name: 'fixture', mode: 'greenfield', scope: ['src'],
466
- replace_migrated: true,
467
- }, { rootDir: fx.rootDir }),
468
- (error) => error.code === 'BASELINE_REPLACEMENT_INVALID',
469
- );
470
- const replacement = baselines.startBaseline(fx.db, {
471
- id: 'replacement', project_name: 'fixture', mode: 'brownfield', scope: ['src'],
472
- replace_migrated: true,
473
- }, { rootDir: fx.rootDir });
474
- assert.equal(replacement.status, 'adopting');
475
- assert.equal(baselines.readBaseline(fx.db, 'migrated-baseline').status, 'superseded');
476
- } finally { cleanup(fx); }
477
- });