ultra-builder-pro-cli 0.14.0 → 0.16.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 (163) 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 +77 -1
  8. package/README.md +46 -24
  9. package/adapters/_shared/codex-assets.cjs +6 -2
  10. package/adapters/_shared/file-ops.cjs +6 -2
  11. package/adapters/_shared/handbook.cjs +2 -1
  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 +3 -2
  16. package/agents/review-coordinator.md +9 -6
  17. package/commands/ultra-change.md +1 -1
  18. package/commands/ultra-plan.md +1 -1
  19. package/commands/ultra-research.md +1 -1
  20. package/commands/ultra-think.md +2 -2
  21. package/docs/AGENT-CONTEXT.md +43 -19
  22. package/docs/ARCHITECTURE.md +54 -13
  23. package/docs/PLAN.zh-CN.md +4 -2
  24. package/docs/ROADMAP.md +10 -4
  25. package/docs/RUNTIME-COMPAT-MATRIX.md +4 -4
  26. package/docs/STATE-DB-ACCESS-POLICY.md +33 -29
  27. package/docs/USER-HANDBOOK-CONTRACT.md +8 -2
  28. package/docs/WORKFLOW-LIFECYCLE.md +342 -0
  29. package/hooks/README.md +6 -1
  30. package/hooks/health_check.py +12 -3
  31. package/hooks/subagent_tracker.py +52 -77
  32. package/mcp-server/hook-event.cjs +34 -0
  33. package/mcp-server/lib/archive-journal.cjs +17 -5
  34. package/mcp-server/lib/baseline-workflow.cjs +158 -18
  35. package/mcp-server/lib/change-workflow.cjs +598 -85
  36. package/mcp-server/lib/context-spine.cjs +305 -25
  37. package/mcp-server/lib/decision-dialogue.cjs +713 -0
  38. package/mcp-server/lib/doctor.cjs +42 -3
  39. package/mcp-server/lib/init-project.cjs +210 -16
  40. package/mcp-server/lib/plan-store.cjs +4 -1
  41. package/mcp-server/lib/pricing.cjs +7 -10
  42. package/mcp-server/lib/project-breadcrumb.cjs +15 -0
  43. package/mcp-server/lib/projector.cjs +57 -3
  44. package/mcp-server/lib/spec-learning.cjs +79 -2
  45. package/mcp-server/lib/state-db.cjs +168 -8
  46. package/mcp-server/lib/state-ops.cjs +160 -9
  47. package/mcp-server/lib/workflow-state.cjs +2305 -0
  48. package/mcp-server/server.cjs +215 -15
  49. package/orchestrator/daemon.cjs +1 -2
  50. package/orchestrator/dispatch-rules.cjs +5 -31
  51. package/orchestrator/planner/plan-builder.cjs +21 -12
  52. package/package.json +13 -3
  53. package/skills/ultra-change/SKILL.md +94 -80
  54. package/skills/ultra-change/references/change-contract.md +36 -0
  55. package/skills/ultra-deliver/SKILL.md +76 -79
  56. package/skills/ultra-deliver/references/baseline-reconciliation.md +20 -0
  57. package/skills/ultra-dev/SKILL.md +62 -59
  58. package/skills/ultra-doctor/SKILL.md +61 -82
  59. package/skills/ultra-init/SKILL.md +101 -103
  60. package/skills/ultra-init/references/brownfield-adoption.md +2 -2
  61. package/skills/ultra-plan/SKILL.md +99 -85
  62. package/skills/ultra-plan/references/semantic-preflight.md +18 -0
  63. package/skills/ultra-research/SKILL.md +102 -60
  64. package/skills/ultra-research/references/00-problem-validation.md +36 -0
  65. package/skills/ultra-research/references/01-opportunity-discovery.md +33 -0
  66. package/skills/ultra-research/references/02-market-assessment.md +30 -0
  67. package/skills/ultra-research/references/03-competitive-landscape.md +31 -0
  68. package/skills/ultra-research/references/04-product-strategy.md +32 -0
  69. package/skills/ultra-research/references/05-assumptions-validation.md +31 -0
  70. package/skills/ultra-research/references/10-user-personas.md +29 -0
  71. package/skills/ultra-research/references/11-user-scenarios.md +28 -0
  72. package/skills/ultra-research/references/20-user-stories.md +30 -0
  73. package/skills/ultra-research/references/21-features-scope.md +29 -0
  74. package/skills/ultra-research/references/22-success-metrics.md +28 -0
  75. package/skills/ultra-research/references/30-architecture-context.md +30 -0
  76. package/skills/ultra-research/references/31-solution-strategy.md +28 -0
  77. package/skills/ultra-research/references/32-building-blocks.md +28 -0
  78. package/skills/ultra-research/references/40-deployment.md +29 -0
  79. package/skills/ultra-research/references/41-quality-risks.md +28 -0
  80. package/skills/ultra-research/references/99-synthesis.md +35 -0
  81. package/skills/ultra-review/SKILL.md +63 -73
  82. package/skills/ultra-review/references/review-modes.md +27 -0
  83. package/skills/ultra-review/references/unified-schema.md +25 -0
  84. package/skills/ultra-review/scripts/review_wait.py +40 -3
  85. package/skills/ultra-status/SKILL.md +41 -41
  86. package/skills/ultra-test/SKILL.md +54 -55
  87. package/skills/ultra-think/SKILL.md +26 -12
  88. package/skills/ultra-think/references/decision-dialogue.md +130 -0
  89. package/skills/ultra-verify/SKILL.md +1 -1
  90. package/skills/ultra-verify/references/{confidence-system.md → evidence-status.md} +3 -3
  91. package/spec/cli-protocol.md +17 -3
  92. package/spec/mcp-tools.yaml +683 -64
  93. package/spec/schemas/baseline-reconciliation.v1.schema.json +73 -0
  94. package/spec/schemas/delivery-report.v1.schema.json +111 -0
  95. package/spec/schemas/mcp-tools.schema.json +1 -1
  96. package/spec/schemas/state-db.sql +140 -1
  97. package/spec/schemas/tasks.v4.5.schema.json +8 -1
  98. package/spec/schemas/test-report.v1.schema.json +194 -0
  99. package/ultra-tools/cli.cjs +1 -1
  100. package/ultra-tools/commands/migrate.cjs +3 -3
  101. package/ultra-tools/commands/status.cjs +228 -12
  102. package/.ultra-template/delivery-report.json +0 -9
  103. package/.ultra-template/test-report.json +0 -8
  104. package/adapters/_shared/tests/file-ops.test.cjs +0 -54
  105. package/adapters/_shared/tests/frontmatter.test.cjs +0 -47
  106. package/adapters/_shared/tests/handbook.test.cjs +0 -101
  107. package/adapters/_shared/tests/path-rewrite.test.cjs +0 -60
  108. package/adapters/_shared/tests/provenance.test.cjs +0 -133
  109. package/adapters/_shared/tests/resolve-target.test.cjs +0 -75
  110. package/adapters/_shared/tests/runtime-assets.test.cjs +0 -149
  111. package/adapters/_shared/tests/settings-merge.test.cjs +0 -85
  112. package/adapters/_shared/tests/validate.test.cjs +0 -30
  113. package/adapters/tests/claude.test.cjs +0 -138
  114. package/adapters/tests/codex-hook.test.cjs +0 -142
  115. package/adapters/tests/codex.test.cjs +0 -430
  116. package/adapters/tests/kimi-hook.test.cjs +0 -149
  117. package/adapters/tests/kimi.test.cjs +0 -259
  118. package/adapters/tests/opencode.test.cjs +0 -357
  119. package/mcp-server/lib/archive-journal.test.cjs +0 -35
  120. package/mcp-server/lib/baseline-workflow.test.cjs +0 -477
  121. package/mcp-server/lib/change-workflow.test.cjs +0 -971
  122. package/mcp-server/lib/context-spine.test.cjs +0 -93
  123. package/mcp-server/lib/doctor.test.cjs +0 -148
  124. package/mcp-server/lib/plan-store.test.cjs +0 -132
  125. package/mcp-server/lib/prd-parser.test.cjs +0 -82
  126. package/mcp-server/lib/project-breadcrumb.test.cjs +0 -112
  127. package/mcp-server/lib/runtime-state.test.cjs +0 -92
  128. package/mcp-server/lib/spec-learning.test.cjs +0 -67
  129. package/mcp-server/lib/state-db.test.cjs +0 -314
  130. package/mcp-server/lib/task-expander.test.cjs +0 -169
  131. package/mcp-server/lib/topo.test.cjs +0 -158
  132. package/mcp-server/tests/circuit-breaker.test.cjs +0 -125
  133. package/mcp-server/tests/commit-hash-flow.test.cjs +0 -117
  134. package/mcp-server/tests/concurrency-update.test.cjs +0 -96
  135. package/mcp-server/tests/concurrency.test.cjs +0 -112
  136. package/mcp-server/tests/db-ops.test.cjs +0 -121
  137. package/mcp-server/tests/init-project.test.cjs +0 -393
  138. package/mcp-server/tests/migrate.test.cjs +0 -232
  139. package/mcp-server/tests/projector.test.cjs +0 -162
  140. package/mcp-server/tests/server-contract.test.cjs +0 -1467
  141. package/mcp-server/tests/staleness.test.cjs +0 -190
  142. package/mcp-server/tests/state-db.test.cjs +0 -106
  143. package/mcp-server/tests/state-ops.test.cjs +0 -449
  144. package/mcp-server/tests/tagged-tasks.test.cjs +0 -163
  145. package/mcp-server/tests/telemetry.test.cjs +0 -219
  146. package/orchestrator/planner/plan-builder.test.cjs +0 -165
  147. package/orchestrator/tests/auto-merge.test.cjs +0 -216
  148. package/orchestrator/tests/daemon.test.cjs +0 -311
  149. package/orchestrator/tests/dispatch-rules.test.cjs +0 -124
  150. package/orchestrator/tests/parallel-orchestrator.test.cjs +0 -233
  151. package/orchestrator/tests/phase-8b-gate.test.cjs +0 -183
  152. package/orchestrator/tests/recovery.test.cjs +0 -190
  153. package/orchestrator/tests/session-runner.test.cjs +0 -314
  154. package/orchestrator/tests/skill-miner.test.cjs +0 -147
  155. package/orchestrator/tests/worktree-manager.test.cjs +0 -148
  156. package/skills/ultra-research/references/architecture.md +0 -38
  157. package/skills/ultra-research/references/problem-product.md +0 -39
  158. package/skills/ultra-research/references/quality-delivery.md +0 -34
  159. package/skills/ultra-research/references/synthesis.md +0 -38
  160. package/skills/ultra-research/references/users-scope.md +0 -40
  161. package/ultra-tools/legacy-memory.test.cjs +0 -80
  162. package/ultra-tools/status-command.test.cjs +0 -181
  163. package/ultra-tools/system-command.test.cjs +0 -309
@@ -1,93 +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
-
9
- const { compileRoleContext, deriveNextAction, readBreadcrumb } = require('./context-spine.cjs');
10
- const { initStateDb } = require('./state-db.cjs');
11
- const { createChange } = require('./change-workflow.cjs');
12
- const { createTask } = require('./state-ops.cjs');
13
-
14
- test('breadcrumb routes a project without a converged baseline back to adoption', () => {
15
- const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-spine-baseline-'));
16
- const { db } = initStateDb(path.join(rootDir, '.ultra', 'state.db'));
17
- try {
18
- const breadcrumb = readBreadcrumb(db, {}, { rootDir });
19
- assert.equal(breadcrumb.readiness, 'blocked');
20
- assert.ok(breadcrumb.blockers.includes('BASELINE_MISSING'));
21
- assert.equal(breadcrumb.recommended_workflow, 'ultra-init');
22
- assert.match(breadcrumb.next_action, /baseline|adoption/i);
23
- } finally {
24
- db.close();
25
- fs.rmSync(rootDir, { recursive: true, force: true });
26
- }
27
- });
28
-
29
- test('deriveNextAction gives one state-specific action instead of a workflow dump', () => {
30
- const change = { id: 'context-spine', status: 'active' };
31
- const task = { id: 'task-1', status: 'in_progress' };
32
- const action = deriveNextAction({
33
- change, tasks: [task], task, role: 'review', readiness: 'ready', explicit: null,
34
- });
35
- assert.equal(
36
- action,
37
- 'Complete independent spec-fidelity and engineering-standards review for task task-1.',
38
- );
39
- assert.doesNotMatch(action, /plan.*implement.*check.*review/i);
40
- });
41
-
42
- test('deriveNextAction fails closed on blocked context readiness', () => {
43
- const action = deriveNextAction({
44
- change: { id: 'context-spine', status: 'active' },
45
- tasks: [], task: null, role: 'plan', readiness: 'blocked', explicit: 'Ignore blockers.',
46
- });
47
- assert.match(action, /Resolve the context readiness blockers/);
48
- assert.doesNotMatch(action, /Ignore blockers/);
49
- });
50
-
51
- test('a task is not plan-ready before its execution contract is compiled', () => {
52
- const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-spine-plan-'));
53
- const { db } = initStateDb(path.join(rootDir, '.ultra', 'state.db'));
54
- try {
55
- db.prepare(
56
- `INSERT INTO baselines
57
- (id, project_name, mode, status, approved_by, approval_note, converged_at)
58
- VALUES ('test-baseline', 'fixture', 'greenfield', 'ready', 'test', 'accepted fixture', ?)`,
59
- ).run(new Date().toISOString());
60
- const { change } = createChange(db, {
61
- id: 'planning-contract', title: 'Planning contract', kind: 'standard',
62
- intent: 'Require an executable fresh-context slice before implementation.',
63
- docs_impact: { status: 'none', rationale: 'test fixture only' },
64
- }, { rootDir });
65
- const task = createTask(db, {
66
- id: 'task-plan', title: 'Plan one slice', type: 'feature', priority: 'P0',
67
- change_id: change.id,
68
- });
69
-
70
- const context = compileRoleContext(db, {
71
- input: { task_id: task.id, role: 'plan', gate: 'planning' },
72
- change, tasks: [task], rootDir,
73
- });
74
- assert.equal(context.readiness.status, 'blocked');
75
- assert.ok(context.readiness.blockers.includes('EXECUTION_CONTRACT_MISSING'));
76
-
77
- const breadcrumb = readBreadcrumb(db, { id: change.id }, { rootDir });
78
- assert.equal(breadcrumb.readiness, 'blocked');
79
- assert.ok(breadcrumb.blockers.includes('CONTEXT_TASK_STATE_STALE'));
80
- assert.throws(
81
- () => readBreadcrumb(db, { id: 'missing-change' }, { rootDir }),
82
- (error) => error.code === 'CHANGE_NOT_FOUND',
83
- );
84
-
85
- db.prepare("UPDATE context_snapshots SET context_json = '{}', readiness = 'ready'").run();
86
- const migratedLegacy = readBreadcrumb(db, { id: change.id }, { rootDir });
87
- assert.equal(migratedLegacy.readiness, 'blocked');
88
- assert.ok(migratedLegacy.blockers.includes('CONTEXT_SNAPSHOT_UPGRADE_REQUIRED'));
89
- } finally {
90
- db.close();
91
- fs.rmSync(rootDir, { recursive: true, force: true });
92
- }
93
- });
@@ -1,148 +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
-
9
- const { initStateDb, closeStateDb } = require('./state-db.cjs');
10
- const ops = require('./state-ops.cjs');
11
- const runtime = require('./runtime-state.cjs');
12
- const doctor = require('./doctor.cjs');
13
- const baselines = require('./baseline-workflow.cjs');
14
- const changes = require('./change-workflow.cjs');
15
- const archiveJournal = require('./archive-journal.cjs');
16
-
17
- function fixture({ baseline = true } = {}) {
18
- const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-doctor-'));
19
- const { db } = initStateDb(path.join(rootDir, '.ultra', 'state.db'));
20
- if (baseline) {
21
- db.prepare(
22
- `INSERT INTO baselines
23
- (id, project_name, mode, status, approved_by, approval_note, converged_at)
24
- VALUES ('test-baseline', 'fixture', 'greenfield', 'ready', 'test', 'accepted fixture', ?)`,
25
- ).run(new Date().toISOString());
26
- }
27
- return { rootDir, db };
28
- }
29
-
30
- function cleanup({ rootDir, db }) {
31
- closeStateDb(db);
32
- fs.rmSync(rootDir, { recursive: true, force: true });
33
- }
34
-
35
- test('doctor reports structured health for an initialized Ultra project', async () => {
36
- const fx = fixture();
37
- try {
38
- const report = await doctor.runDoctor(fx.db, { rootDir: fx.rootDir });
39
- assert.equal(report.status, 'healthy');
40
- assert.equal(report.repair_performed, false);
41
- assert.equal(report.checks.state_db.status, 'pass');
42
- assert.equal(report.checks.external_providers.ownership, 'external');
43
- } finally {
44
- cleanup(fx);
45
- }
46
- });
47
-
48
- test('doctor reports incomplete brownfield adoption as advisory rather than authority failure', async () => {
49
- const fx = fixture({ baseline: false });
50
- try {
51
- baselines.startBaseline(fx.db, {
52
- id: 'adoption', project_name: 'legacy', mode: 'brownfield', scope: ['.'],
53
- }, { rootDir: fx.rootDir, emitEvent: false });
54
- const report = await doctor.runDoctor(fx.db, { rootDir: fx.rootDir });
55
- assert.equal(report.status, 'healthy');
56
- assert.equal(report.checks.baseline.status, 'warning');
57
- assert.equal(report.checks.baseline.mode, 'brownfield');
58
- assert.deepEqual(report.checks.baseline.gaps, {
59
- total: 0, open: 0, blocking: 0, by_category: {},
60
- });
61
- assert.ok(report.checks.baseline.blockers.includes('BASELINE_NOT_READY:adopting'));
62
- } finally {
63
- cleanup(fx);
64
- }
65
- });
66
-
67
- test('doctor repair is backup-first and drains projection/spec-event recovery work', async () => {
68
- const fx = fixture();
69
- try {
70
- ops.createTask(fx.db, {
71
- id: 'stale-target', title: 'Refresh context', type: 'feature', priority: 'P1',
72
- trace_to: '.ultra/specs/product.md#continuous-change',
73
- });
74
- ops.appendEvent(fx.db, {
75
- type: 'spec_changed',
76
- payload: { sections: ['.ultra/specs/product.md#continuous-change'] },
77
- });
78
- runtime.enqueueProjection(fx.db, { tool_name: 'task.append_event', event_cursor: 2 });
79
-
80
- const report = await doctor.runDoctor(fx.db, { rootDir: fx.rootDir, repair: true });
81
- assert.equal(report.repair_performed, true);
82
- assert.ok(fs.existsSync(report.backup_path));
83
- assert.equal(ops.readTask(fx.db, 'stale-target').stale, true);
84
- assert.equal(runtime.readConsumerCursor(fx.db, 'spec-staleness'), 2);
85
- assert.equal(runtime.listProjectionJobs(fx.db, { status: 'pending' }).length, 0);
86
- assert.equal(report.status, 'healthy');
87
- } finally {
88
- cleanup(fx);
89
- }
90
- });
91
-
92
- test('doctor reports running projection work and repairs only stale interrupted jobs', async () => {
93
- const fx = fixture();
94
- try {
95
- ops.appendEvent(fx.db, { type: 'spec_changed', payload: { sections: [] } });
96
- const job = runtime.enqueueProjection(fx.db, { tool_name: 'task.append_event', event_cursor: 1 });
97
- fx.db.prepare("UPDATE projection_jobs SET status = 'running', updated_at = ? WHERE id = ?")
98
- .run('2000-01-01T00:00:00.000Z', job.id);
99
-
100
- const before = await doctor.runDoctor(fx.db, { rootDir: fx.rootDir });
101
- assert.equal(before.status, 'degraded');
102
- assert.equal(before.checks.projections.running, 1);
103
-
104
- const after = await doctor.runDoctor(fx.db, {
105
- rootDir: fx.rootDir,
106
- repair: true,
107
- projectionStaleAfterMs: 0,
108
- });
109
- assert.equal(after.repair.interrupted.requeued, 1);
110
- assert.equal(after.checks.projections.running, 0);
111
- assert.equal(after.status, 'healthy');
112
- } finally {
113
- cleanup(fx);
114
- }
115
- });
116
-
117
- test('doctor repair resumes an archive journal left after a process crash', async () => {
118
- const fx = fixture();
119
- try {
120
- const activeRoot = path.join(fx.rootDir, '.ultra', 'changes', 'active', 'doctor-archive');
121
- fs.mkdirSync(activeRoot, { recursive: true });
122
- fs.writeFileSync(path.join(activeRoot, 'intent.md'), '# Authorized archive\n');
123
- fx.db.prepare(
124
- `INSERT INTO changes
125
- (id, title, kind, status, intent, docs_impact_json, provider_refs_json, artifact_root)
126
- VALUES ('doctor-archive', 'Recover archive', 'quick', 'ready', 'Recover it',
127
- '{"status":"none","files":[],"rationale":"recovery"}', '{}',
128
- '.ultra/changes/active/doctor-archive')`,
129
- ).run();
130
- const prepared = archiveJournal.prepareArchiveMove({
131
- rootDir: fx.rootDir, change: changes.readChange(fx.db, 'doctor-archive'),
132
- summary: 'Resume the authorized archive.', baselineUpdates: [],
133
- noBaselineChangeReason: 'No baseline content changed.',
134
- });
135
- assert.equal(fs.existsSync(prepared.source), false);
136
- const before = doctor.inspectSystem(fx.db, { rootDir: fx.rootDir });
137
- assert.equal(before.status, 'degraded');
138
- assert.equal(before.checks.archive_recovery.pending, 1);
139
-
140
- const after = await doctor.runDoctor(fx.db, { rootDir: fx.rootDir, repair: true });
141
- assert.equal(after.repair.archives.resumed, 1);
142
- assert.equal(changes.readChange(fx.db, 'doctor-archive').status, 'archived');
143
- assert.equal(after.checks.archive_recovery.pending, 0);
144
- assert.equal(fs.existsSync(path.join(prepared.destination, archiveJournal.INTENT_FILE)), false);
145
- } finally {
146
- cleanup(fx);
147
- }
148
- });
@@ -1,132 +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
-
9
- const {
10
- savePlanArtifact,
11
- loadPlanArtifact,
12
- selectSection,
13
- renderPlanMd,
14
- DEFAULT_ARTIFACT_RELPATH,
15
- } = require('./plan-store.cjs');
16
-
17
- function tmpRoot() {
18
- return fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-plan-'));
19
- }
20
-
21
- const SAMPLE_PLAN = Object.freeze({
22
- waves: [
23
- { id: 1, tasks: ['a', 'b'], parallel: true },
24
- { id: 2, tasks: ['c'], parallel: false },
25
- ],
26
- ownership_forecast: { a: ['src/a.ts'], b: ['src/b.ts'], c: ['src/c.ts'] },
27
- conflict_surface: [],
28
- estimated_cost_usd: 0.12,
29
- estimated_duration_min: 35,
30
- cycles: [],
31
- });
32
-
33
- test('savePlanArtifact: json format writes a file we can JSON.parse back', () => {
34
- const root = tmpRoot();
35
- try {
36
- const outPath = path.join(root, '.ultra', 'execution-plan.json');
37
- const { plan_path } = savePlanArtifact(SAMPLE_PLAN, outPath, 'json');
38
- assert.equal(plan_path, path.resolve(outPath));
39
- assert.ok(fs.existsSync(plan_path));
40
- const parsed = JSON.parse(fs.readFileSync(plan_path, 'utf8'));
41
- assert.deepEqual(parsed.waves, SAMPLE_PLAN.waves);
42
- } finally {
43
- fs.rmSync(root, { recursive: true, force: true });
44
- }
45
- });
46
-
47
- test('savePlanArtifact: md format writes a human-readable report', () => {
48
- const root = tmpRoot();
49
- try {
50
- const outPath = path.join(root, '.ultra', 'execution-plan.md');
51
- savePlanArtifact(SAMPLE_PLAN, outPath, 'md');
52
- const text = fs.readFileSync(outPath, 'utf8');
53
- assert.match(text, /# Execution Plan/);
54
- assert.match(text, /Wave 1/);
55
- assert.match(text, /parallel/);
56
- } finally {
57
- fs.rmSync(root, { recursive: true, force: true });
58
- }
59
- });
60
-
61
- test('savePlanArtifact: missing out_path → WRITE_FAILED', () => {
62
- assert.throws(
63
- () => savePlanArtifact(SAMPLE_PLAN, '', 'json'),
64
- (err) => err.code === 'WRITE_FAILED',
65
- );
66
- });
67
-
68
- test('loadPlanArtifact: no file → null', () => {
69
- const root = tmpRoot();
70
- try {
71
- assert.equal(loadPlanArtifact(root), null);
72
- } finally {
73
- fs.rmSync(root, { recursive: true, force: true });
74
- }
75
- });
76
-
77
- test('loadPlanArtifact: round-trip via DEFAULT_ARTIFACT_RELPATH', () => {
78
- const root = tmpRoot();
79
- try {
80
- const target = path.join(root, DEFAULT_ARTIFACT_RELPATH);
81
- savePlanArtifact(SAMPLE_PLAN, target, 'json');
82
- const loaded = loadPlanArtifact(root);
83
- assert.deepEqual(loaded.waves, SAMPLE_PLAN.waves);
84
- assert.equal(loaded.estimated_cost_usd, 0.12);
85
- } finally {
86
- fs.rmSync(root, { recursive: true, force: true });
87
- }
88
- });
89
-
90
- test('loadPlanArtifact: corrupt file → null (no throw)', () => {
91
- const root = tmpRoot();
92
- try {
93
- const target = path.join(root, DEFAULT_ARTIFACT_RELPATH);
94
- fs.mkdirSync(path.dirname(target), { recursive: true });
95
- fs.writeFileSync(target, '{not json');
96
- assert.equal(loadPlanArtifact(root), null);
97
- } finally {
98
- fs.rmSync(root, { recursive: true, force: true });
99
- }
100
- });
101
-
102
- test('selectSection: tasks returns ownership_forecast only', () => {
103
- const r = selectSection(SAMPLE_PLAN, 'tasks');
104
- assert.deepEqual(Object.keys(r), ['ownership_forecast']);
105
- });
106
-
107
- test('selectSection: topo returns waves only', () => {
108
- const r = selectSection(SAMPLE_PLAN, 'topo');
109
- assert.deepEqual(Object.keys(r), ['waves']);
110
- });
111
-
112
- test('selectSection: conflicts returns conflict_surface only', () => {
113
- const r = selectSection(SAMPLE_PLAN, 'conflicts');
114
- assert.deepEqual(Object.keys(r), ['conflict_surface']);
115
- });
116
-
117
- test('selectSection: all (default) returns whole plan', () => {
118
- assert.deepEqual(selectSection(SAMPLE_PLAN, 'all'), SAMPLE_PLAN);
119
- assert.deepEqual(selectSection(SAMPLE_PLAN), SAMPLE_PLAN);
120
- });
121
-
122
- test('renderPlanMd: reports cycles + conflicts counts when present', () => {
123
- const planWithConflicts = {
124
- ...SAMPLE_PLAN,
125
- conflict_surface: [{ files: ['x.ts'], tasks: ['a', 'b'], recommend: 'sequentialize' }],
126
- cycles: [['z', 'y']],
127
- };
128
- const md = renderPlanMd(planWithConflicts);
129
- assert.match(md, /Conflicts: 1/);
130
- assert.match(md, /Cycles: 1/);
131
- assert.match(md, /## Conflict Surface/);
132
- });
@@ -1,82 +0,0 @@
1
- 'use strict';
2
-
3
- const test = require('node:test');
4
- const assert = require('node:assert/strict');
5
-
6
- const { parsePrd, PrdParseError, TASK_LIST_SCHEMA } = require('./prd-parser.cjs');
7
-
8
- const HAPPY_TASKS = [
9
- {
10
- id: 'task-1',
11
- title: 'Design auth schema',
12
- type: 'architecture',
13
- priority: 'P1',
14
- complexity: 5,
15
- deps: [],
16
- files_modified: ['src/auth/schema.ts'],
17
- },
18
- {
19
- id: 'task-2',
20
- title: 'Implement login endpoint',
21
- type: 'feature',
22
- priority: 'P1',
23
- complexity: 6,
24
- deps: ['task-1'],
25
- files_modified: ['src/auth/login.ts'],
26
- },
27
- ];
28
-
29
- test('parsePrd validates and normalizes host-derived tasks without a provider client', () => {
30
- const result = parsePrd(HAPPY_TASKS, { tag: 'feat-auth', changeId: 'auth-change' });
31
- assert.equal(result.tasks.length, 2);
32
- assert.equal(result.tasks[0].tag, 'feat-auth');
33
- assert.equal(result.tasks[0].change_id, 'auth-change');
34
- assert.deepEqual(result.tasks[1].deps, ['task-1']);
35
- assert.equal(Object.hasOwn(result, 'model'), false);
36
- assert.equal(Object.hasOwn(result, 'provider'), false);
37
- assert.equal(Object.hasOwn(result, 'usage'), false);
38
- });
39
-
40
- test('parsePrd requires at least one host-derived task', () => {
41
- for (const value of [undefined, null, [], 'not-an-array']) {
42
- assert.throws(
43
- () => parsePrd(value),
44
- (err) => err instanceof PrdParseError && err.code === 'NO_INPUT',
45
- );
46
- }
47
- });
48
-
49
- test('parsePrd rejects an invalid task shape', () => {
50
- assert.throws(
51
- () => parsePrd([{
52
- id: 'task-1', title: 'Missing priority', type: 'feature',
53
- }]),
54
- (err) => err instanceof PrdParseError && err.code === 'PARSE_FAILED' && /priority/.test(err.message),
55
- );
56
- });
57
-
58
- test('parsePrd rejects duplicate task ids', () => {
59
- assert.throws(
60
- () => parsePrd([
61
- { id: 'task-1', title: 'First copy', type: 'feature', priority: 'P2' },
62
- { id: 'task-1', title: 'Second copy', type: 'feature', priority: 'P2' },
63
- ]),
64
- (err) => err instanceof PrdParseError && err.code === 'PARSE_FAILED' && /duplicate/i.test(err.message),
65
- );
66
- });
67
-
68
- test('parsePrd normalizes optional fields', () => {
69
- const result = parsePrd([{
70
- id: 'task-1', title: 'Minimal task', type: 'feature', priority: 'P3',
71
- }]);
72
- assert.equal(result.tasks[0].complexity, null);
73
- assert.deepEqual(result.tasks[0].deps, []);
74
- assert.deepEqual(result.tasks[0].files_modified, []);
75
- assert.equal(result.tasks[0].tag, null);
76
- assert.equal(result.tasks[0].change_id, null);
77
- });
78
-
79
- test('TASK_LIST_SCHEMA remains available to MCP contract consumers', () => {
80
- assert.equal(TASK_LIST_SCHEMA.required[0], 'tasks');
81
- assert.equal(TASK_LIST_SCHEMA.properties.tasks.minItems, 1);
82
- });
@@ -1,112 +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 Database = require('better-sqlite3');
9
-
10
- const { initStateDb, closeStateDb } = require('./state-db.cjs');
11
- const { createChange } = require('./change-workflow.cjs');
12
- const { createTask } = require('./state-ops.cjs');
13
- const { compileRoleContext, readBreadcrumb } = require('./context-spine.cjs');
14
- const {
15
- readProjectBreadcrumb,
16
- renderProjectBreadcrumb,
17
- } = require('./project-breadcrumb.cjs');
18
-
19
- function tmpProject(prefix) {
20
- const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
21
- fs.mkdirSync(path.join(rootDir, '.ultra'), { recursive: true });
22
- return rootDir;
23
- }
24
-
25
- test('project reader returns the exact canonical breadcrumb from state.db', () => {
26
- const rootDir = tmpProject('ubp-project-breadcrumb-');
27
- const state = initStateDb(path.join(rootDir, '.ultra', 'state.db'));
28
- try {
29
- state.db.prepare(
30
- `INSERT INTO baselines
31
- (id, project_name, mode, status, approved_by, approval_note, converged_at)
32
- VALUES ('baseline-1', 'fixture', 'greenfield', 'ready', 'test', 'fixture', ?)`,
33
- ).run(new Date().toISOString());
34
- const { change } = createChange(state.db, {
35
- id: 'db-change', title: 'Database change', kind: 'quick',
36
- intent: 'Prove every host consumes one breadcrumb.',
37
- docs_impact: { status: 'none', rationale: 'test fixture' },
38
- }, { rootDir });
39
- const task = createTask(state.db, {
40
- id: 'db-task', title: 'Use one breadcrumb', type: 'bugfix', priority: 'P0',
41
- change_id: change.id,
42
- });
43
- compileRoleContext(state.db, {
44
- input: {
45
- task_id: task.id,
46
- role: 'implement',
47
- gate: 'implementation',
48
- execution_contract: {
49
- slice_kind: 'tracer_bullet',
50
- public_seam: 'all host injections',
51
- verification_command: 'npm test',
52
- },
53
- next_action: 'Run the cross-host breadcrumb regression test.',
54
- },
55
- change,
56
- tasks: [task],
57
- rootDir,
58
- });
59
-
60
- const expected = readBreadcrumb(state.db, {}, { rootDir });
61
- const actual = readProjectBreadcrumb(rootDir);
62
- assert.deepEqual(actual, expected);
63
- const rendered = renderProjectBreadcrumb(rootDir, actual);
64
- assert.match(rendered, /Change: db-change/);
65
- assert.match(rendered, /Task: db-task/);
66
- assert.ok(rendered.includes(actual.next_action));
67
- assert.match(rendered, /Authority: \.ultra\/state\.db/);
68
- } finally {
69
- closeStateDb(state.db);
70
- fs.rmSync(rootDir, { recursive: true, force: true });
71
- }
72
- });
73
-
74
- test('project reader routes a missing authority to ultra-init', () => {
75
- const rootDir = tmpProject('ubp-project-missing-db-');
76
- try {
77
- const breadcrumb = readProjectBreadcrumb(rootDir);
78
- assert.equal(breadcrumb.readiness, 'blocked');
79
- assert.deepEqual(breadcrumb.blockers, ['STATE_DB_MISSING']);
80
- assert.equal(breadcrumb.recommended_workflow, 'ultra-init');
81
- } finally {
82
- fs.rmSync(rootDir, { recursive: true, force: true });
83
- }
84
- });
85
-
86
- test('project reader routes an old schema to ultra-init without consulting projections', () => {
87
- const rootDir = tmpProject('ubp-project-old-schema-');
88
- const db = new Database(path.join(rootDir, '.ultra', 'state.db'));
89
- try {
90
- db.exec(`
91
- CREATE TABLE schema_version (
92
- version TEXT PRIMARY KEY,
93
- applied_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
94
- );
95
- INSERT INTO schema_version(version) VALUES ('10.0');
96
- `);
97
- } finally {
98
- db.close();
99
- }
100
- fs.writeFileSync(path.join(rootDir, '.ultra', 'workflow-state.json'), JSON.stringify({
101
- command: 'ultra-dev', task_id: 'projection-task', status: 'active',
102
- }));
103
- try {
104
- const breadcrumb = readProjectBreadcrumb(rootDir);
105
- assert.equal(breadcrumb.readiness, 'blocked');
106
- assert.ok(breadcrumb.blockers.includes('STATE_SCHEMA_MIGRATION_REQUIRED:10.0'));
107
- assert.equal(breadcrumb.recommended_workflow, 'ultra-init');
108
- assert.doesNotMatch(JSON.stringify(breadcrumb), /projection-task|ultra-dev/);
109
- } finally {
110
- fs.rmSync(rootDir, { recursive: true, force: true });
111
- }
112
- });
@@ -1,92 +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
-
9
- const { initStateDb, closeStateDb } = require('./state-db.cjs');
10
- const runtime = require('./runtime-state.cjs');
11
-
12
- function fixture() {
13
- const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-runtime-state-'));
14
- const { db } = initStateDb(path.join(rootDir, '.ultra', 'state.db'));
15
- return { rootDir, db };
16
- }
17
-
18
- function cleanup({ rootDir, db }) {
19
- closeStateDb(db);
20
- fs.rmSync(rootDir, { recursive: true, force: true });
21
- }
22
-
23
- test('projection jobs complete and retain their event cursor', () => {
24
- const fx = fixture();
25
- try {
26
- const job = runtime.enqueueProjection(fx.db, { tool_name: 'task.create', event_cursor: 7 });
27
- const result = runtime.processProjectionJobs(fx.db, {
28
- rootDir: fx.rootDir,
29
- project: () => ({ tasks_json: { count: 0 }, contexts: [] }),
30
- });
31
- assert.equal(result.processed, 1);
32
- assert.equal(result.jobs[0].status, 'completed');
33
- const stored = runtime.readProjectionJob(fx.db, job.id);
34
- assert.equal(stored.status, 'completed');
35
- assert.equal(stored.event_cursor, 7);
36
- } finally {
37
- cleanup(fx);
38
- }
39
- });
40
-
41
- test('projection failures are explicit, retryable incidents rather than swallowed warnings', () => {
42
- const fx = fixture();
43
- try {
44
- runtime.enqueueProjection(fx.db, { tool_name: 'task.update', event_cursor: 4, max_attempts: 1 });
45
- // Test Double rationale: deterministic projector failure is required; filesystem permission failures vary by host.
46
- const result = runtime.processProjectionJobs(fx.db, {
47
- rootDir: fx.rootDir,
48
- project: () => { throw new Error('projection disk unavailable'); },
49
- });
50
- assert.equal(result.jobs[0].status, 'failed');
51
- assert.match(result.jobs[0].error, /projection disk unavailable/);
52
- const incidents = runtime.listIncidents(fx.db, { status: 'open' });
53
- assert.equal(incidents.length, 1);
54
- assert.equal(incidents[0].code, 'PROJECTION_FAILED');
55
- assert.equal(incidents[0].retryable, true);
56
- } finally {
57
- cleanup(fx);
58
- }
59
- });
60
-
61
- test('event consumer cursors are durable and monotonic', () => {
62
- const fx = fixture();
63
- try {
64
- assert.equal(runtime.readConsumerCursor(fx.db, 'spec-staleness'), 0);
65
- runtime.writeConsumerCursor(fx.db, 'spec-staleness', 12);
66
- runtime.writeConsumerCursor(fx.db, 'spec-staleness', 9);
67
- assert.equal(runtime.readConsumerCursor(fx.db, 'spec-staleness'), 12);
68
- } finally {
69
- cleanup(fx);
70
- }
71
- });
72
-
73
- test('interrupted running projections are requeued only after the stale cutoff', () => {
74
- const fx = fixture();
75
- try {
76
- const stale = runtime.enqueueProjection(fx.db, { tool_name: 'task.update', event_cursor: 3 });
77
- const fresh = runtime.enqueueProjection(fx.db, { tool_name: 'task.create', event_cursor: 4 });
78
- fx.db.prepare("UPDATE projection_jobs SET status = 'running', updated_at = ? WHERE id = ?")
79
- .run('2000-01-01T00:00:00.000Z', stale.id);
80
- fx.db.prepare("UPDATE projection_jobs SET status = 'running', updated_at = ? WHERE id = ?")
81
- .run('2999-01-01T00:00:00.000Z', fresh.id);
82
-
83
- const result = runtime.requeueInterruptedProjections(fx.db, {
84
- staleBefore: '2026-01-01T00:00:00.000Z',
85
- });
86
- assert.equal(result.requeued, 1);
87
- assert.equal(runtime.readProjectionJob(fx.db, stale.id).status, 'pending');
88
- assert.equal(runtime.readProjectionJob(fx.db, fresh.id).status, 'running');
89
- } finally {
90
- cleanup(fx);
91
- }
92
- });