ultra-builder-pro-cli 0.15.0 → 0.17.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 (71) hide show
  1. package/.ultra-template/specs/research-distillate.md +4 -3
  2. package/CHANGELOG.md +120 -1
  3. package/README.md +67 -30
  4. package/adapters/_shared/codex-assets.cjs +10 -5
  5. package/adapters/_shared/handbook.cjs +3 -2
  6. package/adapters/_shared/interaction-contract.cjs +61 -0
  7. package/adapters/claude.js +1 -1
  8. package/adapters/opencode.js +1 -1
  9. package/bin/orchestrator.js +175 -19
  10. package/commands/ultra-change.md +1 -1
  11. package/commands/ultra-deliver.md +3 -3
  12. package/commands/ultra-init.md +1 -1
  13. package/commands/ultra-plan.md +1 -1
  14. package/commands/ultra-research.md +2 -2
  15. package/commands/ultra-review.md +10 -0
  16. package/commands/ultra-status.md +1 -1
  17. package/commands/ultra-test.md +1 -1
  18. package/commands/ultra-think.md +2 -2
  19. package/docs/AGENT-CONTEXT.md +38 -18
  20. package/docs/ARCHITECTURE.md +91 -13
  21. package/docs/LEGACY-CLI-CROSSWALK.md +163 -0
  22. package/docs/PLAN.zh-CN.md +19 -12
  23. package/docs/ROADMAP.md +5 -3
  24. package/docs/RUNTIME-COMPAT-MATRIX.md +4 -4
  25. package/docs/STATE-DB-ACCESS-POLICY.md +8 -6
  26. package/docs/USER-HANDBOOK-CONTRACT.md +8 -2
  27. package/docs/WORKFLOW-LIFECYCLE.md +179 -39
  28. package/hooks/pre_stop_check.py +3 -2
  29. package/mcp-server/lib/baseline-workflow.cjs +84 -16
  30. package/mcp-server/lib/change-workflow.cjs +107 -39
  31. package/mcp-server/lib/context-spine.cjs +215 -87
  32. package/mcp-server/lib/decision-dialogue.cjs +713 -0
  33. package/mcp-server/lib/doctor.cjs +9 -0
  34. package/mcp-server/lib/git-bootstrap.cjs +240 -0
  35. package/mcp-server/lib/init-project.cjs +178 -41
  36. package/mcp-server/lib/project-breadcrumb.cjs +23 -14
  37. package/mcp-server/lib/state-db.cjs +279 -9
  38. package/mcp-server/lib/state-ops.cjs +64 -1
  39. package/mcp-server/lib/workflow-state.cjs +313 -205
  40. package/mcp-server/server.cjs +112 -34
  41. package/orchestrator/auto-merge.cjs +19 -0
  42. package/orchestrator/daemon.cjs +104 -0
  43. package/orchestrator/parallel-orchestrator.cjs +371 -49
  44. package/orchestrator/session-runner.cjs +354 -25
  45. package/package.json +2 -1
  46. package/skills/ultra-change/SKILL.md +69 -65
  47. package/skills/ultra-deliver/SKILL.md +37 -58
  48. package/skills/ultra-dev/SKILL.md +55 -49
  49. package/skills/ultra-doctor/SKILL.md +44 -49
  50. package/skills/ultra-init/SKILL.md +78 -100
  51. package/skills/ultra-plan/SKILL.md +78 -79
  52. package/skills/ultra-research/SKILL.md +99 -98
  53. package/skills/ultra-review/SKILL.md +38 -47
  54. package/skills/ultra-review/references/review-modes.md +5 -3
  55. package/skills/ultra-review/references/unified-schema.md +26 -6
  56. package/skills/ultra-review/scripts/review_wait.py +12 -0
  57. package/skills/ultra-status/SKILL.md +34 -40
  58. package/skills/ultra-test/SKILL.md +61 -48
  59. package/skills/ultra-think/SKILL.md +47 -47
  60. package/skills/ultra-think/references/decision-dialogue.md +106 -0
  61. package/spec/cli-protocol.md +46 -2
  62. package/spec/mcp-tools.yaml +444 -29
  63. package/spec/schemas/delivery-report.v1.schema.json +1 -46
  64. package/spec/schemas/mcp-tools.schema.json +1 -1
  65. package/spec/schemas/state-db.sql +66 -2
  66. package/spec/schemas/test-report.v1.schema.json +45 -14
  67. package/ultra-tools/cli.cjs +1 -1
  68. package/ultra-tools/commands/session.cjs +10 -3
  69. package/ultra-tools/commands/status.cjs +34 -14
  70. package/ultra-tools/commands/system.cjs +2 -2
  71. package/ultra-tools/commands/task.cjs +3 -0
@@ -5,7 +5,7 @@
5
5
  "type": "object",
6
6
  "required": [
7
7
  "$schema", "change_id", "archive_status", "baseline_id", "baseline_status",
8
- "git_commit", "worktree_digest", "context_digest", "checks", "release", "rollback", "timestamp"
8
+ "git_commit", "worktree_digest", "context_digest", "checks", "rollback", "timestamp"
9
9
  ],
10
10
  "additionalProperties": false,
11
11
  "properties": {
@@ -41,51 +41,6 @@
41
41
  ]
42
42
  }
43
43
  },
44
- "release": {
45
- "type": "object",
46
- "required": ["authorized", "performed", "evidence"],
47
- "additionalProperties": false,
48
- "properties": {
49
- "authorized": { "type": "boolean" },
50
- "performed": { "type": "boolean" },
51
- "evidence": {
52
- "type": "array",
53
- "items": {
54
- "type": "object",
55
- "required": ["kind", "reference", "status", "evidence"],
56
- "additionalProperties": false,
57
- "properties": {
58
- "kind": { "enum": ["commit", "push", "tag", "registry", "deploy", "release", "other"] },
59
- "reference": { "type": "string", "minLength": 1 },
60
- "status": { "const": "pass" },
61
- "evidence": { "type": "string", "minLength": 1 }
62
- }
63
- }
64
- },
65
- "version": { "type": "string" },
66
- "tag": { "type": "string" },
67
- "commit": { "type": "string" },
68
- "registry": { "type": "string" },
69
- "remote": { "type": "string" }
70
- },
71
- "allOf": [
72
- {
73
- "if": { "properties": { "authorized": { "const": true } } },
74
- "then": {
75
- "properties": {
76
- "performed": { "const": true },
77
- "evidence": { "minItems": 1 }
78
- }
79
- },
80
- "else": {
81
- "properties": {
82
- "performed": { "const": false },
83
- "evidence": { "maxItems": 0 }
84
- }
85
- }
86
- }
87
- ]
88
- },
89
44
  "rollback": { "type": ["string", "null"] },
90
45
  "timestamp": { "type": ["string", "null"], "format": "date-time" }
91
46
  },
@@ -60,7 +60,7 @@
60
60
  },
61
61
  "family": {
62
62
  "type": "string",
63
- "enum": ["task", "memory", "review", "impact", "skill", "session", "ask", "plan", "baseline", "change", "workflow", "system"]
63
+ "enum": ["task", "memory", "review", "impact", "skill", "session", "ask", "plan", "baseline", "change", "decision", "workflow", "system"]
64
64
  },
65
65
  "description": { "type": "string", "minLength": 10 },
66
66
  "phase": {
@@ -29,7 +29,7 @@ CREATE TABLE IF NOT EXISTS baselines (
29
29
  repository_revision TEXT,
30
30
  repository_branch TEXT,
31
31
  worktree_state TEXT NOT NULL DEFAULT 'unavailable'
32
- CHECK (worktree_state IN ('clean', 'dirty', 'unavailable')),
32
+ CHECK (worktree_state IN ('clean', 'dirty', 'unborn', 'unavailable')),
33
33
  worktree_digest TEXT,
34
34
  worktree_files_json TEXT NOT NULL DEFAULT '[]',
35
35
  worktree_accepted INTEGER NOT NULL DEFAULT 0 CHECK (worktree_accepted IN (0, 1)),
@@ -67,6 +67,7 @@ CREATE TABLE IF NOT EXISTS changes (
67
67
  contract_json TEXT NOT NULL DEFAULT '{}',
68
68
  classification_json TEXT NOT NULL DEFAULT '{}',
69
69
  research_disposition_json TEXT NOT NULL DEFAULT '{}',
70
+ alignment_thread_id TEXT REFERENCES decision_threads(id) ON DELETE SET NULL,
70
71
  base_commit TEXT,
71
72
  artifact_root TEXT NOT NULL,
72
73
  created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
@@ -251,7 +252,9 @@ CREATE TABLE IF NOT EXISTS context_snapshots (
251
252
  CHECK (role IN ('plan', 'implement', 'check', 'review')),
252
253
  gate TEXT NOT NULL DEFAULT 'alignment'
253
254
  CHECK (gate IN ('alignment', 'planning', 'implementation', 'verification', 'review', 'convergence', 'recovery')),
254
- next_action TEXT NOT NULL DEFAULT 'Resolve the next Ultra workflow action.',
255
+ next_action TEXT NOT NULL DEFAULT '',
256
+ allowed_transitions_json TEXT NOT NULL DEFAULT '[]',
257
+ required_transition TEXT,
255
258
  readiness TEXT NOT NULL DEFAULT 'ready'
256
259
  CHECK (readiness IN ('ready', 'blocked')),
257
260
  blockers_json TEXT NOT NULL DEFAULT '[]',
@@ -403,6 +406,61 @@ CREATE UNIQUE INDEX IF NOT EXISTS workflow_steps_position
403
406
  CREATE INDEX IF NOT EXISTS workflow_steps_status
404
407
  ON workflow_steps(run_id, status, position);
405
408
 
409
+ -- ──────────────────────────── decision dialogue ─────────────────────────
410
+ -- The host owns reasoning and normalized decision content. MCP owns one-at-a-
411
+ -- time presentation, durable authority, checkpoints, and recovery. Raw prompts
412
+ -- and transcripts never belong in these tables.
413
+ CREATE TABLE IF NOT EXISTS decision_threads (
414
+ id TEXT PRIMARY KEY,
415
+ purpose TEXT NOT NULL,
416
+ mode TEXT NOT NULL DEFAULT 'guided'
417
+ CHECK (mode IN ('guided', 'fast', 'autonomous', 'diagnostic')),
418
+ status TEXT NOT NULL DEFAULT 'active'
419
+ CHECK (status IN ('active', 'checkpoint_ready', 'confirmed', 'cancelled')),
420
+ baseline_id TEXT REFERENCES baselines(id) ON DELETE SET NULL,
421
+ change_id TEXT REFERENCES changes(id) ON DELETE CASCADE,
422
+ workflow_run_id TEXT REFERENCES workflow_runs(id) ON DELETE CASCADE,
423
+ summary_json TEXT NOT NULL DEFAULT '{}',
424
+ checkpoint_json TEXT NOT NULL DEFAULT '{}',
425
+ started_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
426
+ updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
427
+ confirmed_at TEXT,
428
+ CHECK (baseline_id IS NOT NULL OR change_id IS NOT NULL OR workflow_run_id IS NOT NULL)
429
+ );
430
+
431
+ CREATE INDEX IF NOT EXISTS decision_threads_status
432
+ ON decision_threads(status, updated_at);
433
+ CREATE INDEX IF NOT EXISTS decision_threads_authority
434
+ ON decision_threads(baseline_id, change_id, workflow_run_id, status);
435
+
436
+ CREATE TABLE IF NOT EXISTS decision_items (
437
+ id TEXT PRIMARY KEY,
438
+ thread_id TEXT NOT NULL REFERENCES decision_threads(id) ON DELETE CASCADE,
439
+ sequence INTEGER NOT NULL CHECK (sequence > 0),
440
+ phase TEXT NOT NULL,
441
+ question TEXT NOT NULL,
442
+ why_now TEXT NOT NULL,
443
+ recommendation TEXT NOT NULL,
444
+ options_json TEXT NOT NULL DEFAULT '[]',
445
+ evidence_refs_json TEXT NOT NULL DEFAULT '[]',
446
+ dependency_ids_json TEXT NOT NULL DEFAULT '[]',
447
+ effects_json TEXT NOT NULL DEFAULT '{}',
448
+ blocking INTEGER NOT NULL DEFAULT 1 CHECK (blocking IN (0, 1)),
449
+ status TEXT NOT NULL DEFAULT 'open'
450
+ CHECK (status IN ('open', 'answered', 'delegated', 'deferred', 'superseded')),
451
+ resolution_json TEXT NOT NULL DEFAULT '{}',
452
+ supersedes_id TEXT REFERENCES decision_items(id) ON DELETE SET NULL,
453
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
454
+ updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
455
+ resolved_at TEXT,
456
+ UNIQUE (thread_id, sequence)
457
+ );
458
+
459
+ CREATE UNIQUE INDEX IF NOT EXISTS decision_items_one_open
460
+ ON decision_items(thread_id) WHERE status = 'open';
461
+ CREATE INDEX IF NOT EXISTS decision_items_status
462
+ ON decision_items(thread_id, status, sequence);
463
+
406
464
  -- ──────────────────────────── seed: schema_version ────────────────────────
407
465
  INSERT OR IGNORE INTO schema_version (version, description)
408
466
  VALUES ('4.5', 'Phase 2 initial — tasks/events/sessions/schema_version/migration_history/telemetry/specs_refs');
@@ -428,3 +486,9 @@ INSERT OR IGNORE INTO schema_version (version, description)
428
486
  VALUES ('14.0', 'Continuously revalidated baseline authority and persisted known-red acceptance');
429
487
  INSERT OR IGNORE INTO schema_version (version, description)
430
488
  VALUES ('15.0', 'Typed research semantics, complete change contracts, verified learning application, and reconciliation provenance');
489
+ INSERT OR IGNORE INTO schema_version (version, description)
490
+ VALUES ('16.0', 'Durable one-question decision dialogue, owner checkpoints, and workflow alignment gates');
491
+ INSERT OR IGNORE INTO schema_version (version, description)
492
+ VALUES ('17.0', 'Explicit unborn Git authority and owner-authorized baseline checkpoints');
493
+ INSERT OR IGNORE INTO schema_version (version, description)
494
+ VALUES ('18.0', 'Adaptive capability transitions, independent initialization, and recoverable workflow migration');
@@ -6,7 +6,7 @@
6
6
  "required": [
7
7
  "$schema", "change_id", "task_ids", "git_commit", "worktree_digest",
8
8
  "context_digest", "acceptance", "commands", "public_seams", "failures",
9
- "recovery", "verification_dimensions", "regression_signal", "passed", "run_count",
9
+ "recovery", "verification_profile", "verification_dimensions", "regression_signal", "passed", "run_count",
10
10
  "timestamp", "blocking_issues"
11
11
  ],
12
12
  "additionalProperties": false,
@@ -93,12 +93,36 @@
93
93
  }
94
94
  }
95
95
  },
96
+ "verification_profile": {
97
+ "type": "object",
98
+ "required": ["rationale", "selected_dimensions", "excluded_dimensions"],
99
+ "additionalProperties": false,
100
+ "properties": {
101
+ "rationale": { "type": "string", "minLength": 3 },
102
+ "selected_dimensions": {
103
+ "type": "array",
104
+ "minItems": 1,
105
+ "uniqueItems": true,
106
+ "contains": { "const": "acceptance" },
107
+ "items": { "$ref": "#/$defs/verificationDimensionName" }
108
+ },
109
+ "excluded_dimensions": {
110
+ "type": "array",
111
+ "items": {
112
+ "type": "object",
113
+ "required": ["dimension", "rationale"],
114
+ "additionalProperties": false,
115
+ "properties": {
116
+ "dimension": { "$ref": "#/$defs/verificationDimensionName" },
117
+ "rationale": { "type": "string", "minLength": 3 }
118
+ }
119
+ }
120
+ }
121
+ }
122
+ },
96
123
  "verification_dimensions": {
97
124
  "type": "object",
98
- "required": [
99
- "acceptance", "regression", "integration", "static_analysis",
100
- "build", "performance", "security", "recovery"
101
- ],
125
+ "required": ["acceptance"],
102
126
  "additionalProperties": false,
103
127
  "properties": {
104
128
  "acceptance": { "$ref": "#/$defs/verificationDimension" },
@@ -131,12 +155,19 @@
131
155
  "blocking_issues": { "type": "array", "items": { "type": "string" } }
132
156
  },
133
157
  "$defs": {
158
+ "verificationDimensionName": {
159
+ "type": "string",
160
+ "enum": [
161
+ "acceptance", "regression", "integration", "static_analysis",
162
+ "build", "performance", "security", "recovery"
163
+ ]
164
+ },
134
165
  "verificationDimension": {
135
166
  "type": "object",
136
167
  "required": ["status", "evidence", "rationale"],
137
168
  "additionalProperties": false,
138
169
  "properties": {
139
- "status": { "enum": ["pass", "fail", "not_run", "not_applicable"] },
170
+ "status": { "enum": ["pass", "fail", "not_run"] },
140
171
  "evidence": {
141
172
  "type": "array",
142
173
  "items": { "type": "string", "minLength": 1 }
@@ -174,14 +205,14 @@
174
205
  "recovery": { "items": { "properties": { "status": { "const": "pass" } } } },
175
206
  "verification_dimensions": {
176
207
  "properties": {
177
- "acceptance": { "properties": { "status": { "enum": ["pass", "not_applicable"] } } },
178
- "regression": { "properties": { "status": { "enum": ["pass", "not_applicable"] } } },
179
- "integration": { "properties": { "status": { "enum": ["pass", "not_applicable"] } } },
180
- "static_analysis": { "properties": { "status": { "enum": ["pass", "not_applicable"] } } },
181
- "build": { "properties": { "status": { "enum": ["pass", "not_applicable"] } } },
182
- "performance": { "properties": { "status": { "enum": ["pass", "not_applicable"] } } },
183
- "security": { "properties": { "status": { "enum": ["pass", "not_applicable"] } } },
184
- "recovery": { "properties": { "status": { "enum": ["pass", "not_applicable"] } } }
208
+ "acceptance": { "properties": { "status": { "const": "pass" } } },
209
+ "regression": { "properties": { "status": { "const": "pass" } } },
210
+ "integration": { "properties": { "status": { "const": "pass" } } },
211
+ "static_analysis": { "properties": { "status": { "const": "pass" } } },
212
+ "build": { "properties": { "status": { "const": "pass" } } },
213
+ "performance": { "properties": { "status": { "const": "pass" } } },
214
+ "security": { "properties": { "status": { "const": "pass" } } },
215
+ "recovery": { "properties": { "status": { "const": "pass" } } }
185
216
  }
186
217
  },
187
218
  "run_count": { "minimum": 1 },
@@ -38,7 +38,7 @@ SUBCOMMANDS:
38
38
  system doctor [--repair] | restore | rebaseline
39
39
  status [--cost] [--since <duration>] [--json]
40
40
  db init | checkpoint | vacuum | integrity | backup (Phase 2)
41
- migrate --from=<4.4|4.5> --to=<4.5|15.0> [--dry|--rollback]
41
+ migrate --from=<4.4|4.5> --to=<4.5|18.0> [--dry|--rollback]
42
42
  legacy-memory inspect | archive | prune --confirm DELETE_ULTRA_LEGACY_MEMORY
43
43
 
44
44
  --help / -h show this message
@@ -9,6 +9,7 @@
9
9
  const path = require('node:path');
10
10
  const { initStateDb, closeStateDb } = require('../../mcp-server/lib/state-db.cjs');
11
11
  const ops = require('../../mcp-server/lib/state-ops.cjs');
12
+ const sessionRunner = require('../../orchestrator/session-runner.cjs');
12
13
 
13
14
  const DEFAULT_DB_PATH = path.join('.ultra', 'state.db');
14
15
 
@@ -20,11 +21,12 @@ VERBS:
20
21
  reap [--grace-seconds <n>] orphan sweeper (Phase 4.5.6)
21
22
  get --sid <sid>
22
23
  heartbeat --sid <sid>
23
- close --sid <sid> --status completed|crashed|orphan
24
+ close --sid <sid> --status completed|crashed|orphan [--remove-worktree]
24
25
  subscribe --since-id <n> [--sid <sid>] [--limit <n>]
25
26
 
26
27
  GLOBAL FLAGS:
27
28
  --db <path> path to state.db (default: .ultra/state.db)
29
+ --repo-root <path> repository root for verified worktree cleanup (default: cwd)
28
30
  -h, --help show this message
29
31
  `;
30
32
 
@@ -44,6 +46,8 @@ function parseFlags(args) {
44
46
  case '--limit': flags.limit = Number(args[++i]); break;
45
47
  case '--since-id': flags.since_id = Number(args[++i]); break;
46
48
  case '--grace-seconds': flags.grace_seconds = Number(args[++i]); break;
49
+ case '--repo-root': flags.repo_root = args[++i]; break;
50
+ case '--remove-worktree': flags.remove_worktree = true; break;
47
51
  case '-h': case '--help': flags.help = true; break;
48
52
  default: flags._.push(a);
49
53
  }
@@ -127,8 +131,11 @@ function cmdClose(flags) {
127
131
  if (!flags.sid || !flags.status) { emit({ ok: false, error: { code: 'VALIDATION_ERROR', message: 'missing --sid or --status' } }); return 1; }
128
132
  return withDb(flags, (db) => {
129
133
  try {
130
- ops.updateSession(db, flags.sid, { status: flags.status });
131
- emit({ ok: true, data: { sid: flags.sid, status: flags.status } });
134
+ const result = sessionRunner.closeSession(
135
+ { db, repoRoot: path.resolve(flags.repo_root || process.cwd()), sid: flags.sid },
136
+ { status: flags.status, remove_worktree: flags.remove_worktree === true },
137
+ );
138
+ emit({ ok: true, data: result });
132
139
  return 0;
133
140
  } catch (err) {
134
141
  emit({ ok: false, error: { code: err.code || 'STATE_DB_ERROR', message: err.message } });
@@ -12,11 +12,12 @@ const path = require('node:path');
12
12
  const ops = require('../../mcp-server/lib/state-ops.cjs');
13
13
  const baselines = require('../../mcp-server/lib/baseline-workflow.cjs');
14
14
  const workflowState = require('../../mcp-server/lib/workflow-state.cjs');
15
+ const decisionDialogue = require('../../mcp-server/lib/decision-dialogue.cjs');
15
16
  const { readBreadcrumb } = require('../../mcp-server/lib/context-spine.cjs');
16
17
 
17
18
  const USAGE = `ultra-tools status [flags]
18
19
 
19
- Reads .ultra/state.db and prints baseline, workflow, task, session, route, and
20
+ Reads .ultra/state.db and prints baseline, workflow, task, session, valid transitions, and
20
21
  cost-observability status.
21
22
 
22
23
  FLAGS:
@@ -167,20 +168,25 @@ function buildStatusPanel(db, { since = null, limit = 3, rootDir = process.cwd()
167
168
  };
168
169
  }
169
170
  if (!Array.isArray(workflows.current)) workflows.current = [];
170
- let next;
171
+ const decisions = tables.has('decision_threads') && tables.has('decision_items')
172
+ ? decisionDialogue.inspectDecisionHealth(db, { rootDir })
173
+ : {
174
+ status: 'unavailable', active: 0, awaiting_owner: 0, checkpoint_ready: 0,
175
+ deferred_blocking: 0, stale_artifacts: [], current: null, current_thread_id: null,
176
+ };
177
+ let transitions;
171
178
  let breadcrumb = null;
172
179
  try {
173
180
  if (!tables.has('baselines')) {
174
- next = {
175
- action: 'Resume project initialization to migrate and establish baseline authority.',
176
- recommended_workflow: 'ultra-init', readiness: 'blocked',
181
+ transitions = {
182
+ allowed: ['ultra-init', 'ultra-status'], required: 'ultra-init', readiness: 'blocked',
177
183
  blockers: ['BASELINE_SCHEMA_MIGRATION_REQUIRED'], warnings: [],
178
184
  };
179
185
  } else {
180
186
  breadcrumb = readBreadcrumb(db, {}, { rootDir });
181
- next = {
182
- action: breadcrumb.next_action,
183
- recommended_workflow: breadcrumb.recommended_workflow,
187
+ transitions = {
188
+ allowed: breadcrumb.allowed_transitions,
189
+ required: breadcrumb.required_transition,
184
190
  readiness: breadcrumb.readiness,
185
191
  blockers: breadcrumb.blockers,
186
192
  warnings: breadcrumb.warnings,
@@ -190,9 +196,8 @@ function buildStatusPanel(db, { since = null, limit = 3, rootDir = process.cwd()
190
196
  };
191
197
  }
192
198
  } catch (error) {
193
- next = {
194
- action: 'Run ultra-doctor to inspect authoritative state.',
195
- recommended_workflow: 'ultra-doctor', readiness: 'blocked',
199
+ transitions = {
200
+ allowed: ['ultra-doctor', 'ultra-status'], required: 'ultra-doctor', readiness: 'blocked',
196
201
  blockers: [`STATUS_AUTHORITY_UNAVAILABLE:${error.code || 'unknown'}`], warnings: [],
197
202
  };
198
203
  }
@@ -242,13 +247,14 @@ function buildStatusPanel(db, { since = null, limit = 3, rootDir = process.cwd()
242
247
  period: { since: since || 'all-time' },
243
248
  baseline,
244
249
  workflows,
250
+ decisions,
245
251
  current_change: currentChange,
246
252
  current_task: currentTask,
247
253
  evidence,
248
254
  changes,
249
255
  tasks: taskCounts,
250
256
  sessions,
251
- next,
257
+ transitions,
252
258
  by_runtime,
253
259
  top_tasks,
254
260
  total_cost_usd,
@@ -287,6 +293,13 @@ function renderHuman(panel) {
287
293
  `Workflow: ${current.id} (${current.kind}/${current.status}) · step=${current.current_step || 'finalize'} · outputs=${current.output_health}`,
288
294
  );
289
295
  }
296
+ if (panel.decisions.current) {
297
+ lines.push(
298
+ `Decision: ${panel.decisions.current.id} (${panel.decisions.current.phase}) — ${panel.decisions.current.question}`,
299
+ );
300
+ } else if (panel.decisions.checkpoint_ready > 0) {
301
+ lines.push(`Decision checkpoint: ${panel.decisions.current_thread_id} awaits confirmation`);
302
+ }
290
303
  lines.push(
291
304
  `Changes: active=${panel.changes.active} blocked=${panel.changes.blocked} ready=${panel.changes.ready} archived=${panel.changes.archived}`,
292
305
  );
@@ -300,7 +313,8 @@ function renderHuman(panel) {
300
313
  lines.push(
301
314
  `Sessions: running=${panel.sessions.running} crashed=${panel.sessions.crashed} orphan=${panel.sessions.orphan}`,
302
315
  );
303
- lines.push(`Next: ${panel.next.action} (${panel.next.recommended_workflow})`);
316
+ lines.push(`Allowed transitions: ${panel.transitions.allowed.join(', ') || 'none'}`);
317
+ if (panel.transitions.required) lines.push(`Required transition: ${panel.transitions.required}`);
304
318
  lines.push(
305
319
  `Evidence: test=${panel.evidence.test.status} review=${panel.evidence.review.status} delivery=${panel.evidence.delivery.status}`,
306
320
  );
@@ -345,6 +359,11 @@ function resolveDbPath() {
345
359
  return path.resolve('.ultra', 'state.db');
346
360
  }
347
361
 
362
+ function resolveRootDir(dbPath) {
363
+ if (process.env.UBP_ROOT_DIR) return path.resolve(process.env.UBP_ROOT_DIR);
364
+ return path.dirname(path.dirname(dbPath));
365
+ }
366
+
348
367
  function dispatch(args) {
349
368
  let flags;
350
369
  try { flags = parseFlags(args); } catch (err) { return fail('USAGE_ERROR', err.message); }
@@ -362,7 +381,7 @@ function dispatch(args) {
362
381
  const Database = require('better-sqlite3');
363
382
  const db = new Database(dbPath, { readonly: true });
364
383
  try {
365
- const rootDir = path.dirname(path.dirname(dbPath));
384
+ const rootDir = resolveRootDir(dbPath);
366
385
  const panel = buildStatusPanel(db, { since, limit: flags.limit, rootDir });
367
386
  if (flags.json) {
368
387
  emit({ ok: true, data: panel });
@@ -383,4 +402,5 @@ module.exports = {
383
402
  renderHuman,
384
403
  parseSince,
385
404
  parseFlags,
405
+ resolveRootDir,
386
406
  };
@@ -213,7 +213,7 @@ async function dispatchRestore(args) {
213
213
  quarantined_state_path: path.join(recoveryDir, 'state.db'),
214
214
  schema_version: initialized.schema_version,
215
215
  schema_migration_backup_path: initialized.backup_path || null,
216
- next_action: 'Run ultra-tools system doctor, then ultra-init to inspect baseline readiness.',
216
+ recovery_guidance: 'Run ultra-tools system doctor, then ultra-init to inspect baseline readiness.',
217
217
  },
218
218
  });
219
219
  return 0;
@@ -284,7 +284,7 @@ async function dispatchRebaseline(args) {
284
284
  : null,
285
285
  baseline: initialized.baseline,
286
286
  copied_files: initialized.copied_files,
287
- next_action: 'Inspect the preserved evidence, record the brownfield baseline, and obtain owner approval before convergence.',
287
+ recovery_guidance: 'Inspect the preserved evidence, run explicit adoption research, record the brownfield baseline, and obtain owner approval before convergence.',
288
288
  },
289
289
  });
290
290
  return 0;
@@ -17,6 +17,7 @@ FLAGS (init-project):
17
17
  --project-type <type> web | api | cli | fullstack | other
18
18
  --stack <stack> tech stack descriptor (comma-separated ok)
19
19
  --mode <mode> auto | greenfield | brownfield (default: auto)
20
+ --git-mode <mode> auto | initialize | skip (default: auto)
20
21
  --scope <path> baseline scope; repeat for multiple paths (default: .)
21
22
  --resume preserve existing .ultra/ and install only missing assets
22
23
  --overwrite replace existing .ultra/ (backup created)
@@ -38,6 +39,7 @@ function parseInitFlags(args) {
38
39
  case '--project-type': flags.project_type = args[++i]; break;
39
40
  case '--stack': flags.stack = args[++i]; break;
40
41
  case '--mode': flags.mode = args[++i]; break;
42
+ case '--git-mode': flags.git_mode = args[++i]; break;
41
43
  case '--scope': flags.scope.push(args[++i]); break;
42
44
  case '--resume': flags.resume = true; break;
43
45
  case '--source-template': flags.source_template = args[++i]; break;
@@ -66,6 +68,7 @@ function dispatchInitProject(rawArgs) {
66
68
  project_type: flags.project_type,
67
69
  stack: flags.stack,
68
70
  mode: flags.mode || 'auto',
71
+ git_mode: flags.git_mode || 'auto',
69
72
  scope: flags.scope.length > 0 ? flags.scope : undefined,
70
73
  resume: !!flags.resume,
71
74
  overwrite: !!flags.overwrite,