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,34 +0,0 @@
1
- # Quality and delivery research
2
-
3
- Read this reference when acceptance depends on operational risk, security, migration,
4
- deployment, recovery, or release constraints.
5
-
6
- ## Risk-driven quality
7
-
8
- Identify failures that would materially affect users, data, authorization, money,
9
- availability, or recoverability. For each relevant risk, record:
10
-
11
- - trigger and affected boundary;
12
- - expected behavior and observable signal;
13
- - prevention, detection, and recovery;
14
- - verification method and owner.
15
-
16
- Apply security, performance, accessibility, and resilience checks according to the
17
- actual product surface. Do not impose generic categories or coverage percentages when
18
- they do not define acceptance.
19
-
20
- ## Delivery path
21
-
22
- Capture the environments, configuration authority, migration order, compatibility
23
- window, health and readiness checks, observability, rollout, rollback, and operator
24
- actions needed for this product.
25
-
26
- Prefer existing repository and platform workflows. Add a new service, dependency, or
27
- deployment mechanism only when the accepted architecture requires it.
28
-
29
- ## Record the result
30
-
31
- Update the architecture and product specifications with testable quality scenarios,
32
- delivery constraints, failure behavior, recovery evidence, and release acceptance.
33
- Keep exact volatile commands in the repository's runbook when they are operational
34
- procedure rather than durable architecture.
@@ -1,38 +0,0 @@
1
- # Baseline synthesis and validation
2
-
3
- Read this reference after the selected research lanes are complete.
4
-
5
- ## Traceability check
6
-
7
- Verify that each accepted outcome has:
8
-
9
- 1. supporting problem, user, or constraint evidence;
10
- 2. an explicit decision and scope boundary;
11
- 3. testable acceptance behavior;
12
- 4. a compatible architecture or delivery path;
13
- 5. a named unresolved blocker when any link is missing.
14
-
15
- Check the reverse direction as well: every proposed component, dependency, and quality
16
- requirement must support an accepted outcome or constraint.
17
-
18
- ## Consistency check
19
-
20
- - Use one term for each actor, boundary, and authoritative data source.
21
- - Remove duplicate narratives and stale alternatives.
22
- - Keep source links next to the claims they support.
23
- - Separate durable decisions from volatile external facts.
24
- - Preserve explicit non-goals and rejected alternatives only when they prevent a
25
- likely future mistake.
26
-
27
- ## Handoff
28
-
29
- Report:
30
-
31
- - validated scope and decisive evidence;
32
- - specification files changed;
33
- - accepted decisions and material uncertainty;
34
- - blockers, or an explicit statement that none remain;
35
- - one next action.
36
-
37
- Do not create a parallel summary document solely for model consumption. The planning
38
- workflow should read the smallest authoritative baseline sections it needs.
@@ -1,40 +0,0 @@
1
- # Users, behavior, and scope
2
-
3
- Read this reference when actors, workflows, acceptance, boundaries, or success signals
4
- are unclear.
5
-
6
- ## Actors and scenarios
7
-
8
- Model only actors supported by evidence. Prefer roles and jobs over fictional persona
9
- biographies. For each material scenario, capture:
10
-
11
- - trigger and preconditions;
12
- - actor goal;
13
- - main flow and system response;
14
- - important failure or recovery path;
15
- - observable completion signal.
16
-
17
- ## Acceptance and scope
18
-
19
- Turn scenarios into testable behavior. Each requirement should name the observable
20
- outcome, relevant constraints, and how success can be verified.
21
-
22
- Define:
23
-
24
- - included behavior required for the accepted outcome;
25
- - explicit non-goals;
26
- - dependencies and assumptions;
27
- - deferred work only when a real sequencing decision exists.
28
-
29
- Avoid arbitrary release buckets or a fixed number of stories. Scope follows the
30
- validated outcome and delivery constraint.
31
-
32
- ## Success signals
33
-
34
- Choose metrics only when they will guide a decision. Define the event, population,
35
- window, source, owner, and interpretation. Do not invent target values; record a
36
- baseline, a user-approved target, or the experiment needed to establish one.
37
-
38
- Update the product specification with actors, scenarios, acceptance, scope, non-goals,
39
- and decision-relevant success signals. Keep business facts in their source system and
40
- link them rather than copying volatile datasets into the specification.
@@ -1,80 +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 legacy = require('./commands/legacy-memory.cjs');
11
-
12
- function fixture() {
13
- const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-legacy-memory-'));
14
- const ultra = path.join(root, '.ultra');
15
- fs.mkdirSync(path.join(ultra, 'memory'), { recursive: true });
16
- fs.writeFileSync(path.join(ultra, 'memory', 'memory.db'), 'legacy-hook-store');
17
- const db = new Database(path.join(ultra, 'state.db'));
18
- db.exec(`
19
- CREATE TABLE memory_entries (
20
- id INTEGER PRIMARY KEY, kind TEXT, content TEXT, task_id TEXT,
21
- session_id TEXT, tag TEXT, source TEXT, ts TEXT
22
- );
23
- INSERT INTO memory_entries
24
- (kind, content, task_id, session_id, tag, source, ts)
25
- VALUES ('decision', 'legacy decision', 't1', 's1', 'arch', 'test', '2026-01-01T00:00:00Z');
26
- `);
27
- db.close();
28
- return root;
29
- }
30
-
31
- test('inspect reports both legacy stores without mutating them', () => {
32
- const root = fixture();
33
- try {
34
- const result = legacy.inspectLegacy(root);
35
- assert.equal(result.hook_memory_dir, true);
36
- assert.equal(result.state_memory_table, true);
37
- assert.equal(result.state_memory_entries, 1);
38
- assert.ok(fs.existsSync(path.join(root, '.ultra', 'memory', 'memory.db')));
39
- } finally {
40
- fs.rmSync(root, { recursive: true, force: true });
41
- }
42
- });
43
- test('archive exports legacy state rows and copies hook memory without deletion', () => {
44
- const root = fixture();
45
- try {
46
- const result = legacy.archiveLegacy(root, { timestamp: '2026-07-15T00-00-00-000Z' });
47
- assert.equal(result.archived, true);
48
- assert.ok(fs.existsSync(path.join(result.archive_dir, 'hook-memory', 'memory.db')));
49
- const exported = JSON.parse(fs.readFileSync(path.join(result.archive_dir, 'state-memory-entries.json'), 'utf8'));
50
- assert.equal(exported.entries.length, 1);
51
- assert.equal(exported.entries[0].content, 'legacy decision');
52
- assert.ok(fs.existsSync(path.join(root, '.ultra', 'memory', 'memory.db')));
53
- const db = new Database(path.join(root, '.ultra', 'state.db'), { readonly: true });
54
- assert.ok(db.prepare("SELECT 1 FROM sqlite_master WHERE name = 'memory_entries'").get());
55
- db.close();
56
- } finally {
57
- fs.rmSync(root, { recursive: true, force: true });
58
- }
59
- });
60
-
61
- test('prune requires an exact confirmation and archives before deleting', () => {
62
- const root = fixture();
63
- try {
64
- assert.throws(() => legacy.pruneLegacy(root, { confirm: 'yes' }), /DELETE_ULTRA_LEGACY_MEMORY/);
65
- assert.ok(fs.existsSync(path.join(root, '.ultra', 'memory', 'memory.db')));
66
-
67
- const result = legacy.pruneLegacy(root, {
68
- confirm: 'DELETE_ULTRA_LEGACY_MEMORY',
69
- timestamp: '2026-07-15T00-00-01-000Z',
70
- });
71
- assert.equal(result.pruned, true);
72
- assert.ok(fs.existsSync(result.archive_dir));
73
- assert.ok(!fs.existsSync(path.join(root, '.ultra', 'memory')));
74
- const db = new Database(path.join(root, '.ultra', 'state.db'), { readonly: true });
75
- assert.equal(db.prepare("SELECT 1 FROM sqlite_master WHERE name = 'memory_entries'").get(), undefined);
76
- db.close();
77
- } finally {
78
- fs.rmSync(root, { recursive: true, force: true });
79
- }
80
- });
@@ -1,181 +0,0 @@
1
- 'use strict';
2
-
3
- // Phase 6.3 — ultra-tools status cost panel:
4
- // • `ultra-tools status --cost --json` prints {period, by_runtime,
5
- // top_tasks, total_cost_usd}.
6
- // • Default human-readable mode renders a summary table.
7
- // • --since 7d filters telemetry to the last 7 days.
8
-
9
- const { test } = require('node:test');
10
- const assert = require('node:assert/strict');
11
- const fs = require('node:fs');
12
- const os = require('node:os');
13
- const path = require('node:path');
14
-
15
- const { initStateDb, closeStateDb } = require('../mcp-server/lib/state-db.cjs');
16
- const ops = require('../mcp-server/lib/state-ops.cjs');
17
- const telemetry = require('../mcp-server/lib/telemetry.cjs');
18
- const statusCmd = require('./commands/status.cjs');
19
-
20
- function freshFixture() {
21
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-status-'));
22
- const file = path.join(dir, '.ultra', 'state.db');
23
- const init = initStateDb(file);
24
- return { dir, db: init.db };
25
- }
26
-
27
- function teardown(dir, db) {
28
- try { closeStateDb(db); } catch (_) { /* ignore */ }
29
- try { fs.rmSync(dir, { recursive: true, force: true }); } catch (_) { /* ignore */ }
30
- }
31
-
32
- function seedCalls(db, dir) {
33
- ops.createTask(db, { id: 't-cheap', title: 'cheap', type: 'feature', priority: 'P2' });
34
- ops.createTask(db, { id: 't-pricey', title: 'pricey', type: 'feature', priority: 'P1' });
35
- ops.createSession(db, { sid: 's-x', task_id: 't-cheap', runtime: 'claude', worktree_path: `${dir}/w1`, artifact_dir: `${dir}/a1` });
36
- ops.createSession(db, { sid: 's-y', task_id: 't-pricey', runtime: 'codex', worktree_path: `${dir}/w2`, artifact_dir: `${dir}/a2` });
37
- telemetry.appendTelemetry(db, { event_type: 'token_usage', tool_name: 't', session_id: 's-x', runtime: 'claude', tokens_input: 500, tokens_output: 100, rootDir: dir, payload: { model: 'claude-haiku-4-5' } });
38
- telemetry.appendTelemetry(db, { event_type: 'token_usage', tool_name: 't', session_id: 's-y', runtime: 'codex', tokens_input: 10000, tokens_output: 2000, rootDir: dir, payload: { model: 'gpt-5.4' } });
39
- telemetry.appendTelemetry(db, { event_type: 'tool_call', tool_name: 'task.list', session_id: null, rootDir: dir });
40
- }
41
-
42
- test('status --json --cost returns by_runtime + top_tasks + total_cost', () => {
43
- const { dir, db } = freshFixture();
44
- try {
45
- seedCalls(db, dir);
46
- const out = statusCmd.buildCostPanel(db, { since: null, limit: 10 });
47
- assert.ok(Array.isArray(out.by_runtime));
48
- assert.ok(Array.isArray(out.top_tasks));
49
- assert.ok(typeof out.total_cost_usd === 'number');
50
- const byR = Object.fromEntries(out.by_runtime.map((r) => [r.runtime, r]));
51
- assert.equal(byR.claude.calls, 1);
52
- assert.equal(byR.codex.calls, 1);
53
- assert.equal(byR.unknown.calls, 1); // CLI call with null session
54
- assert.equal(out.top_tasks[0].task_id, 't-pricey');
55
- assert.ok(out.total_cost_usd > 0);
56
- } finally { teardown(dir, db); }
57
- });
58
-
59
- test('buildCostPanel: empty db → zero totals, empty arrays', () => {
60
- const { dir, db } = freshFixture();
61
- try {
62
- const out = statusCmd.buildCostPanel(db, {});
63
- assert.equal(out.total_cost_usd, 0);
64
- assert.equal(out.by_runtime.length, 0);
65
- assert.equal(out.top_tasks.length, 0);
66
- assert.deepEqual(out.baseline, {
67
- id: null, mode: null, status: 'missing', health: 'fail',
68
- repository_revision: null, blockers: ['BASELINE_MISSING'], warnings: [],
69
- });
70
- } finally { teardown(dir, db); }
71
- });
72
-
73
- test('status exposes an in-progress brownfield baseline from authoritative state', () => {
74
- const { dir, db } = freshFixture();
75
- try {
76
- db.prepare(
77
- `INSERT INTO baselines (id, project_name, mode, status)
78
- VALUES ('adoption', 'legacy', 'brownfield', 'adopting')`,
79
- ).run();
80
- const out = statusCmd.buildCostPanel(db, { rootDir: dir });
81
- assert.equal(out.baseline.id, 'adoption');
82
- assert.equal(out.baseline.mode, 'brownfield');
83
- assert.equal(out.baseline.status, 'adopting');
84
- assert.deepEqual(out.baseline.blockers, ['BASELINE_NOT_READY:adopting']);
85
- assert.match(statusCmd.renderHuman(out), /Baseline: brownfield\/adopting.*blocked/i);
86
- } finally { teardown(dir, db); }
87
- });
88
-
89
- test('status reports a legacy schema as migration-required instead of throwing SQLite errors', () => {
90
- const { dir, db } = freshFixture();
91
- try {
92
- db.exec('DROP TABLE baselines');
93
- const out = statusCmd.buildCostPanel(db, { rootDir: dir });
94
- assert.equal(out.baseline.status, 'migration_required');
95
- assert.deepEqual(out.baseline.blockers, ['BASELINE_SCHEMA_MIGRATION_REQUIRED']);
96
- assert.match(statusCmd.renderHuman(out), /migration_required/i);
97
- } finally { teardown(dir, db); }
98
- });
99
-
100
- test('status summarizes the authoritative baseline gap ledger', () => {
101
- const { dir, db } = freshFixture();
102
- try {
103
- db.prepare(
104
- `INSERT INTO baselines (id, project_name, mode, status, gaps_json)
105
- VALUES ('adoption', 'legacy', 'brownfield', 'adopting', ?)`,
106
- ).run(JSON.stringify([
107
- { id: 'blocker', category: 'baseline_blocker', status: 'open', blocking: true, summary: 'Missing proof', evidence_refs: [] },
108
- { id: 'debt', category: 'technical_debt', status: 'accepted', blocking: false, summary: 'Slow test', evidence_refs: [] },
109
- ]));
110
- const out = statusCmd.buildCostPanel(db, { rootDir: dir });
111
- assert.deepEqual(out.baseline.gaps, {
112
- total: 2, open: 1, blocking: 1,
113
- by_category: { baseline_blocker: 1, technical_debt: 1 },
114
- });
115
- } finally { teardown(dir, db); }
116
- });
117
-
118
- test('buildCostPanel: since cutoff in future → all filtered out', () => {
119
- const { dir, db } = freshFixture();
120
- try {
121
- seedCalls(db, dir);
122
- const future = new Date(Date.now() + 60 * 1000).toISOString();
123
- const out = statusCmd.buildCostPanel(db, { since: future });
124
- assert.equal(out.by_runtime.length, 0);
125
- assert.equal(out.top_tasks.length, 0);
126
- } finally { teardown(dir, db); }
127
- });
128
-
129
- test('parseSince: 7d / 24h / iso passthrough', () => {
130
- const now = Date.parse('2026-04-17T12:00:00Z');
131
- const sevenDaysAgo = statusCmd.parseSince('7d', now);
132
- const expectedDayCutoff = new Date(now - 7 * 24 * 3600 * 1000).toISOString();
133
- assert.equal(sevenDaysAgo, expectedDayCutoff);
134
- assert.equal(statusCmd.parseSince('24h', now), new Date(now - 24 * 3600 * 1000).toISOString());
135
- assert.equal(statusCmd.parseSince('2026-04-10T00:00:00Z'), '2026-04-10T00:00:00Z');
136
- assert.equal(statusCmd.parseSince(null), null);
137
- });
138
-
139
- test('parseSince: invalid → throws', () => {
140
- assert.throws(() => statusCmd.parseSince('bogus'), /since/i);
141
- });
142
-
143
- test('renderHuman: includes key section headers', () => {
144
- const { dir, db } = freshFixture();
145
- try {
146
- seedCalls(db, dir);
147
- const panel = statusCmd.buildCostPanel(db, {});
148
- const text = statusCmd.renderHuman(panel);
149
- assert.match(text, /Cost by runtime/i);
150
- assert.match(text, /Top tasks/i);
151
- assert.match(text, /claude/);
152
- assert.match(text, /codex/);
153
- } finally { teardown(dir, db); }
154
- });
155
-
156
- test('dispatch: --json echoes buildCostPanel output shape', () => {
157
- const { dir, db } = freshFixture();
158
- try {
159
- seedCalls(db, dir);
160
- closeStateDb(db);
161
- const captured = [];
162
- const origWrite = process.stdout.write.bind(process.stdout);
163
- process.stdout.write = (chunk) => { captured.push(String(chunk)); return true; };
164
- const envBackup = process.env.UBP_DB_PATH;
165
- process.env.UBP_DB_PATH = path.join(dir, '.ultra', 'state.db');
166
- try {
167
- const code = statusCmd.dispatch(['--cost', '--json']);
168
- assert.equal(code, 0);
169
- } finally {
170
- process.stdout.write = origWrite;
171
- if (envBackup === undefined) delete process.env.UBP_DB_PATH; else process.env.UBP_DB_PATH = envBackup;
172
- }
173
- const joined = captured.join('');
174
- const parsed = JSON.parse(joined);
175
- assert.ok(parsed.ok);
176
- assert.ok(parsed.data.by_runtime);
177
- assert.ok(parsed.data.top_tasks);
178
- } finally {
179
- try { fs.rmSync(dir, { recursive: true, force: true }); } catch (_) { /* ignore */ }
180
- }
181
- });