ultra-builder-pro-cli 0.16.0 → 0.17.1

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 (66) hide show
  1. package/.ultra-template/reports/templates/delivery-report.json +17 -0
  2. package/.ultra-template/reports/templates/test-report.json +18 -0
  3. package/.ultra-template/specs/research-distillate.md +4 -3
  4. package/CHANGELOG.md +95 -0
  5. package/README.md +54 -24
  6. package/adapters/_shared/codex-assets.cjs +10 -5
  7. package/adapters/_shared/handbook.cjs +1 -1
  8. package/adapters/_shared/interaction-contract.cjs +61 -0
  9. package/adapters/claude.js +1 -1
  10. package/adapters/opencode.js +1 -1
  11. package/bin/orchestrator.js +175 -19
  12. package/commands/ultra-change.md +1 -1
  13. package/commands/ultra-deliver.md +3 -3
  14. package/commands/ultra-init.md +1 -1
  15. package/commands/ultra-plan.md +1 -1
  16. package/commands/ultra-research.md +2 -2
  17. package/commands/ultra-review.md +10 -0
  18. package/commands/ultra-status.md +1 -1
  19. package/commands/ultra-test.md +1 -1
  20. package/docs/AGENT-CONTEXT.md +11 -8
  21. package/docs/ARCHITECTURE.md +67 -9
  22. package/docs/LEGACY-CLI-CROSSWALK.md +163 -0
  23. package/docs/PLAN.zh-CN.md +19 -13
  24. package/docs/RUNTIME-COMPAT-MATRIX.md +2 -2
  25. package/docs/WORKFLOW-LIFECYCLE.md +123 -59
  26. package/hooks/pre_stop_check.py +3 -2
  27. package/mcp-server/lib/baseline-workflow.cjs +84 -16
  28. package/mcp-server/lib/change-workflow.cjs +55 -35
  29. package/mcp-server/lib/context-spine.cjs +171 -105
  30. package/mcp-server/lib/git-bootstrap.cjs +240 -0
  31. package/mcp-server/lib/init-project.cjs +178 -41
  32. package/mcp-server/lib/project-breadcrumb.cjs +14 -14
  33. package/mcp-server/lib/state-db.cjs +257 -10
  34. package/mcp-server/lib/state-ops.cjs +64 -1
  35. package/mcp-server/lib/workflow-state.cjs +300 -205
  36. package/mcp-server/server.cjs +58 -34
  37. package/orchestrator/auto-merge.cjs +19 -0
  38. package/orchestrator/daemon.cjs +104 -0
  39. package/orchestrator/parallel-orchestrator.cjs +371 -49
  40. package/orchestrator/session-runner.cjs +354 -25
  41. package/package.json +2 -1
  42. package/skills/ultra-change/SKILL.md +69 -94
  43. package/skills/ultra-deliver/SKILL.md +37 -65
  44. package/skills/ultra-dev/SKILL.md +50 -52
  45. package/skills/ultra-doctor/SKILL.md +34 -49
  46. package/skills/ultra-init/SKILL.md +78 -105
  47. package/skills/ultra-plan/SKILL.md +78 -99
  48. package/skills/ultra-research/SKILL.md +99 -113
  49. package/skills/ultra-review/SKILL.md +38 -53
  50. package/skills/ultra-review/references/review-modes.md +5 -3
  51. package/skills/ultra-review/references/unified-schema.md +26 -6
  52. package/skills/ultra-review/scripts/review_wait.py +12 -0
  53. package/skills/ultra-status/SKILL.md +34 -44
  54. package/skills/ultra-test/SKILL.md +61 -54
  55. package/skills/ultra-think/SKILL.md +37 -51
  56. package/skills/ultra-think/references/decision-dialogue.md +87 -111
  57. package/spec/cli-protocol.md +35 -0
  58. package/spec/mcp-tools.yaml +145 -28
  59. package/spec/schemas/delivery-report.v1.schema.json +1 -46
  60. package/spec/schemas/state-db.sql +8 -2
  61. package/spec/schemas/test-report.v1.schema.json +45 -14
  62. package/ultra-tools/cli.cjs +1 -1
  63. package/ultra-tools/commands/session.cjs +10 -3
  64. package/ultra-tools/commands/status.cjs +19 -14
  65. package/ultra-tools/commands/system.cjs +2 -2
  66. package/ultra-tools/commands/task.cjs +3 -0
@@ -4,6 +4,7 @@
4
4
  * ubp-orchestrator — Phase 5.4 daemon CLI.
5
5
  *
6
6
  * Subcommands:
7
+ * execute-plan resume the current plan through the first unfinished dependency wave.
7
8
  * run foreground daemon (debug / test).
8
9
  * start detached background daemon, writes .ultra/orchestrator.pid.
9
10
  * stop reads pidfile, SIGTERM the process, deletes pidfile.
@@ -12,9 +13,8 @@
12
13
  * Opt-in gate: `start` and `run` require settings.json
13
14
  * `orchestrator.auto_dispatch: true` — default off per PLAN Phase 5.4 AC.
14
15
  *
15
- * The daemon never launches real runtime children in this phase — session
16
- * rows + git worktrees are created, external adapters (Phase 4) attach to
17
- * the worktree out-of-band. command/args can be passed via env for testing.
16
+ * Dispatch requires both explicit opt-in and an explicit executable command.
17
+ * A daemon may never reserve a session/worktree without a real worker process.
18
18
  */
19
19
 
20
20
  'use strict';
@@ -56,6 +56,124 @@ function parseRuntimes() {
56
56
  return runtimes;
57
57
  }
58
58
 
59
+ function commandConfigError(code, message) {
60
+ const error = new Error(message);
61
+ error.code = code;
62
+ return error;
63
+ }
64
+
65
+ function resolveDispatchCommand(settings, env = process.env) {
66
+ const configured = settings?.orchestrator || {};
67
+ const fromEnvironment = typeof env.UBP_ORCH_COMMAND === 'string'
68
+ && env.UBP_ORCH_COMMAND.trim() !== '';
69
+ const command = fromEnvironment
70
+ ? env.UBP_ORCH_COMMAND.trim()
71
+ : (typeof configured.command === 'string' ? configured.command.trim() : '');
72
+ if (!command) {
73
+ throw commandConfigError(
74
+ 'ORCHESTRATOR_COMMAND_REQUIRED',
75
+ 'orchestrator dispatch requires orchestrator.command or UBP_ORCH_COMMAND',
76
+ );
77
+ }
78
+ if (/\s/.test(command) && !fs.existsSync(command)) {
79
+ throw commandConfigError(
80
+ 'ORCHESTRATOR_COMMAND_INVALID',
81
+ 'orchestrator.command must contain only the executable; put arguments in command_args',
82
+ );
83
+ }
84
+
85
+ let commandArgs;
86
+ if (fromEnvironment && env.UBP_ORCH_ARGS_JSON !== undefined) {
87
+ try {
88
+ commandArgs = JSON.parse(env.UBP_ORCH_ARGS_JSON);
89
+ } catch (error) {
90
+ throw commandConfigError(
91
+ 'ORCHESTRATOR_COMMAND_INVALID',
92
+ `UBP_ORCH_ARGS_JSON must be a JSON array: ${error.message}`,
93
+ );
94
+ }
95
+ } else {
96
+ commandArgs = configured.command_args === undefined ? [] : configured.command_args;
97
+ }
98
+ if (!Array.isArray(commandArgs) || commandArgs.some((arg) => typeof arg !== 'string')) {
99
+ throw commandConfigError(
100
+ 'ORCHESTRATOR_COMMAND_INVALID',
101
+ 'orchestrator command arguments must be an array of strings',
102
+ );
103
+ }
104
+ return {
105
+ command,
106
+ commandArgs: commandArgs.slice(),
107
+ source: fromEnvironment ? 'environment' : 'settings',
108
+ };
109
+ }
110
+
111
+ function parseExecutePlanArgs(argv = []) {
112
+ const options = {
113
+ planPath: null,
114
+ autoMerge: false,
115
+ mergeBaseBranch: 'main',
116
+ };
117
+ for (let index = 0; index < argv.length; index += 1) {
118
+ const arg = argv[index];
119
+ if (arg === '--auto-merge') {
120
+ options.autoMerge = true;
121
+ continue;
122
+ }
123
+ if (arg === '--plan' || arg === '--base-branch') {
124
+ const value = argv[index + 1];
125
+ if (!value || value.startsWith('--')) {
126
+ throw commandConfigError(
127
+ 'ORCHESTRATOR_ARGUMENT_INVALID',
128
+ `${arg} requires a value`,
129
+ );
130
+ }
131
+ if (arg === '--plan') options.planPath = value;
132
+ else options.mergeBaseBranch = value;
133
+ index += 1;
134
+ continue;
135
+ }
136
+ throw commandConfigError(
137
+ 'ORCHESTRATOR_ARGUMENT_INVALID',
138
+ `unknown execute-plan option: ${arg}`,
139
+ );
140
+ }
141
+ return options;
142
+ }
143
+
144
+ async function cmdExecutePlan(argv = []) {
145
+ const settings = readSettings();
146
+ const dispatch = resolveDispatchCommand(settings);
147
+ const options = parseExecutePlanArgs(argv);
148
+ const { initStateDb, closeStateDb } = require('../mcp-server/lib/state-db.cjs');
149
+ const parallelOrchestrator = require('../orchestrator/parallel-orchestrator.cjs');
150
+ const { db } = initStateDb(path.join(REPO_ROOT, '.ultra', 'state.db'));
151
+ try {
152
+ const result = await parallelOrchestrator.runPlan({
153
+ db,
154
+ repoRoot: REPO_ROOT,
155
+ planPath: options.planPath ? path.resolve(REPO_ROOT, options.planPath) : undefined,
156
+ runtimes: parseRuntimes(),
157
+ command: dispatch.command,
158
+ commandArgs: dispatch.commandArgs,
159
+ autoMerge: options.autoMerge,
160
+ mergeBaseBranch: options.mergeBaseBranch,
161
+ onError: (error) => process.stderr.write(`orchestrator error: ${error.message}\n`),
162
+ });
163
+ process.stdout.write(`${JSON.stringify({
164
+ ok: true,
165
+ data: {
166
+ ...result,
167
+ auto_merge: options.autoMerge,
168
+ merge_base_branch: options.mergeBaseBranch,
169
+ },
170
+ })}\n`);
171
+ return result;
172
+ } finally {
173
+ closeStateDb(db);
174
+ }
175
+ }
176
+
59
177
  function cmdRun(opts = {}) {
60
178
  const settings = readSettings();
61
179
  if (!opts.skipOptIn && !optInAllowed(settings)) {
@@ -68,11 +186,14 @@ function cmdRun(opts = {}) {
68
186
  const { initStateDb } = require('../mcp-server/lib/state-db.cjs');
69
187
  const { runDaemon } = require('../orchestrator/daemon.cjs');
70
188
  const runtimes = parseRuntimes();
189
+ const dispatch = resolveDispatchCommand(settings);
71
190
  const { db } = initStateDb(path.join(REPO_ROOT, '.ultra', 'state.db'));
72
191
  const handle = runDaemon({
73
192
  db,
74
193
  repoRoot: REPO_ROOT,
75
194
  runtimes,
195
+ command: dispatch.command,
196
+ commandArgs: dispatch.commandArgs,
76
197
  pollMs: Number(process.env.UBP_ORCH_POLL_MS || 1000),
77
198
  onError: (err) => process.stderr.write(`orchestrator error: ${err.message}\n`),
78
199
  });
@@ -85,7 +206,10 @@ function cmdRun(opts = {}) {
85
206
  };
86
207
  process.on('SIGTERM', () => shutdown('SIGTERM'));
87
208
  process.on('SIGINT', () => shutdown('SIGINT'));
88
- process.stderr.write(`orchestrator running (pollMs=${Number(process.env.UBP_ORCH_POLL_MS || 1000)}, runtimes=${runtimes.join(',')})\n`);
209
+ process.stderr.write(
210
+ `orchestrator running (pollMs=${Number(process.env.UBP_ORCH_POLL_MS || 1000)}, ` +
211
+ `runtimes=${runtimes.join(',')}, worker=${dispatch.command}, source=${dispatch.source})\n`,
212
+ );
89
213
  // Keep process alive while the setInterval is unref'd.
90
214
  setInterval(() => {}, 60000);
91
215
  }
@@ -96,6 +220,7 @@ function cmdStart() {
96
220
  process.stderr.write('orchestrator.auto_dispatch is not enabled; refusing to start.\n');
97
221
  process.exit(2);
98
222
  }
223
+ resolveDispatchCommand(settings);
99
224
  if (fs.existsSync(PIDFILE)) {
100
225
  const existing = Number(fs.readFileSync(PIDFILE, 'utf8'));
101
226
  try { process.kill(existing, 0); }
@@ -170,8 +295,9 @@ function cmdStatus() {
170
295
 
171
296
  function usage() {
172
297
  process.stderr.write(
173
- 'usage: ubp-orchestrator <run|start|stop|status>\n' +
298
+ 'usage: ubp-orchestrator <execute-plan|run|start|stop|status>\n' +
174
299
  '\n' +
300
+ ' execute-plan resume the first unfinished dependency wave [--plan <path>] [--auto-merge] [--base-branch <name>]\n' +
175
301
  ' run foreground daemon (requires opt-in)\n' +
176
302
  ' start detached background daemon (requires opt-in)\n' +
177
303
  ' stop terminate running daemon\n' +
@@ -179,18 +305,48 @@ function usage() {
179
305
  );
180
306
  }
181
307
 
182
- const argv = process.argv.slice(2);
183
- const subcommand = argv[0];
184
- switch (subcommand) {
185
- case 'run': cmdRun(); break;
186
- case 'start': cmdStart(); break;
187
- case 'stop': cmdStop(); break;
188
- case 'status': cmdStatus(); break;
189
- case '-h':
190
- case '--help':
191
- case 'help':
192
- usage(); process.exit(0);
193
- break;
194
- default:
195
- usage(); process.exit(1);
308
+ async function main(argv = process.argv.slice(2)) {
309
+ const subcommand = argv[0];
310
+ try {
311
+ switch (subcommand) {
312
+ case 'execute-plan': await cmdExecutePlan(argv.slice(1)); break;
313
+ case 'run': cmdRun(); break;
314
+ case 'start': cmdStart(); break;
315
+ case 'stop': cmdStop(); break;
316
+ case 'status': cmdStatus(); break;
317
+ case '-h':
318
+ case '--help':
319
+ case 'help':
320
+ usage(); process.exit(0);
321
+ break;
322
+ default:
323
+ usage(); process.exit(1);
324
+ }
325
+ } catch (error) {
326
+ process.stderr.write(
327
+ `orchestrator configuration error${error.code ? ` [${error.code}]` : ''}: ${error.message}\n`,
328
+ );
329
+ process.exit(2);
330
+ }
331
+ }
332
+
333
+ if (require.main === module) {
334
+ main().catch((error) => {
335
+ process.stderr.write(`orchestrator fatal error: ${error.message}\n`);
336
+ process.exit(2);
337
+ });
196
338
  }
339
+
340
+ module.exports = {
341
+ readSettings,
342
+ optInAllowed,
343
+ parseRuntimes,
344
+ parseExecutePlanArgs,
345
+ resolveDispatchCommand,
346
+ cmdExecutePlan,
347
+ cmdRun,
348
+ cmdStart,
349
+ cmdStop,
350
+ cmdStatus,
351
+ main,
352
+ };
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: Align, open, and route one bounded continuous change through research, plan, and delivery
2
+ description: Capture or revise one bounded Change Contract without starting downstream workflows
3
3
  argument-hint: "[change-id-or-title] [quick|standard|major|incident]"
4
4
  workflow-ref: "@skills/ultra-change/SKILL.md"
5
5
  ---
@@ -1,10 +1,10 @@
1
1
  ---
2
- description: Converge current evidence and specification learning into an archived or released baseline
3
- argument-hint: "[version-type]"
2
+ description: Converge current evidence and archive local Ultra authority without external release effects
3
+ argument-hint: "[change-id]"
4
4
  workflow-ref: "@skills/ultra-deliver/SKILL.md"
5
5
  ---
6
6
 
7
7
  # Converge and deliver
8
8
 
9
- Read and follow `@skills/ultra-deliver/SKILL.md`, using `$ARGUMENTS` only for an explicitly requested version policy.
9
+ Read and follow `@skills/ultra-deliver/SKILL.md`, using `$ARGUMENTS` only to select the change.
10
10
  Treat the referenced skill as the only workflow definition.
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: Initialize a new project or adopt an existing repository into authoritative Ultra baseline state
2
+ description: Create or recover Ultra project authority without implicitly starting research
3
3
  argument-hint: "[name] [type] [stack] [auto|greenfield|brownfield] [--scope <path>]"
4
4
  workflow-ref: "@skills/ultra-init/SKILL.md"
5
5
  ---
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: Interactively review and persist dependency-valid tasks with bounded context and exact verification
2
+ description: Persist dependency-valid vertical task contracts from an accepted Change Contract
3
3
  argument-hint: "[scope]"
4
4
  workflow-ref: "@skills/ultra-plan/SKILL.md"
5
5
  ---
@@ -1,10 +1,10 @@
1
1
  ---
2
- description: Build complete research evidence while presenting only one load-bearing owner decision at a time
2
+ description: Build evidence with model-selected semantic coverage and adaptive user alignment
3
3
  argument-hint: "[scope-or-question]"
4
4
  workflow-ref: "@skills/ultra-research/SKILL.md"
5
5
  ---
6
6
 
7
- # Research a baseline
7
+ # Research one bounded question or baseline
8
8
 
9
9
  Read and follow `@skills/ultra-research/SKILL.md`, using `$ARGUMENTS` to bound the research scope.
10
10
  Treat the referenced skill as the only workflow definition.
@@ -0,0 +1,10 @@
1
+ ---
2
+ description: Review one plan, task diff, or aggregate change on independent specification and engineering axes
3
+ argument-hint: "[plan|task|change] [id]"
4
+ workflow-ref: "@skills/ultra-review/SKILL.md"
5
+ ---
6
+
7
+ # Review one bounded Ultra scope
8
+
9
+ Read and follow `@skills/ultra-review/SKILL.md`, using `$ARGUMENTS` to select the review mode and authority.
10
+ Treat the referenced skill as the only workflow definition.
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: Report authoritative Ultra health, readiness, evidence freshness, and one next action
2
+ description: Report authoritative Ultra health, evidence freshness, and valid capability transitions
3
3
  argument-hint: "[task-or-change-id]"
4
4
  workflow-ref: "@skills/ultra-status/SKILL.md"
5
5
  ---
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: Run the independent pre-delivery verification gate and record current acceptance evidence
2
+ description: Run independent, risk-selected verification and record current acceptance evidence
3
3
  argument-hint: "[scope]"
4
4
  workflow-ref: "@skills/ultra-test/SKILL.md"
5
5
  ---
@@ -80,23 +80,26 @@ review, and deliver route back to the same thread instead of deciding for the ow
80
80
  One partial unique index permits only one open item per thread. `decision.resolve`,
81
81
  `decision.delegate`, and `decision.defer` preserve the source of authority;
82
82
  `decision.supersede` preserves history when evidence or intent changes. Prepare and
83
- confirm checkpointing bind the accepted cluster to current artifact digests before a
84
- matching workflow may advance. Status and breadcrumb return only the current question
85
- and one route, so recovery does not require replaying conversation history.
83
+ confirm checkpointing bind a material accepted cluster to current artifact digests
84
+ when interruption recovery needs that boundary. Status and breadcrumb return only the
85
+ current question plus allowed and mechanically required transitions, so recovery does
86
+ not require replaying conversation history.
86
87
 
87
88
  ## 5. Context Spine contract
88
89
 
89
- Context Manifest v2 is an immutable DB-backed role handoff, not a static codebase summary.
90
+ Context Manifest v3 is an immutable DB-backed role handoff, not a static codebase summary.
90
91
  `change.context` compiles required references, digests, readiness, context budget,
91
- public seam, exact verification command, and one next action for `plan`,
92
- `implement`, `check`, or `review`. The default 12-file, about 12k-token, and 40%
92
+ public seam, exact verification command, Change/task authority digests, and valid
93
+ transitions for `plan`, `implement`, `check`, or `review`. The default 12-file,
94
+ about 12k-token, and 40%
93
95
  fresh-context values are attention guidance. Overflow produces warnings; it does
94
96
  not block work or require raising a threshold. Prefer direct reads, bounded
95
97
  excerpts, or a smaller slice when they preserve correctness, and retain all
96
98
  necessary context when they do not.
97
99
 
98
100
  `change.breadcrumb` is the only compact router. Hooks may inject its change/task,
99
- role, gate, readiness, blockers, and one next action. They must not inject intent
101
+ role, gate, readiness, blockers, `allowed_transitions`, and `required_transition`.
102
+ They must not inject intent
100
103
  bodies, transcripts, external memory, or graph payloads. Missing references,
101
104
  digest drift, HEAD drift, or a missing execution seam blocks readiness. Context
102
105
  size and baseline drift are advisory for a change that is already active. New ordinary
@@ -176,7 +179,7 @@ Archive before prune; the confirmation token is intentionally required.
176
179
  ├── runtime/
177
180
  │ └── checkpoint.json # advisory breadcrumb recovery projection
178
181
  ├── changes/
179
- │ ├── active/<id>/ # intent, delta, plan, context v2, learning, verification
182
+ │ ├── active/<id>/ # intent, delta, plan, context v3, learning, verification
180
183
  │ └── archive/ # converged packets after baseline reconciliation
181
184
  ├── tasks/ # projections and task contexts
182
185
  ├── specs/ # research/product/architecture artifacts
@@ -74,7 +74,7 @@ covers twenty-one tables:
74
74
 
75
75
  | Table | Holds | Phase |
76
76
  |--------------------|---------------------------------------------------|-------|
77
- | `baselines` | repository classification, greenfield/brownfield adoption, worktree snapshot, specs, evidence, verification, gap ledger, persisted known-red acceptance, and approval | 12→14 |
77
+ | `baselines` | repository classification, greenfield/brownfield adoption, explicit unborn/clean/dirty Git snapshot, specs, evidence, verification, gap ledger, persisted known-red acceptance, and approval | 12→17 |
78
78
  | `tasks` | task rows — id, status, deps, files_modified, … | 2 |
79
79
  | `events` | append-only event stream; `id` is subscription cursor (D31) | 2 |
80
80
  | `sessions` | execution sessions, **including lease + heartbeat fields** (D32) | 4.5 |
@@ -141,6 +141,19 @@ scripts, and monorepo markers. It detects `greenfield` or `brownfield`, records
141
141
  bounded classification evidence and selected repository scope, and creates the
142
142
  same authoritative state shape without rewriting application code.
143
143
 
144
+ Initialization completes after local authority, classification, Git/scaffold setup,
145
+ and read-back verification. It does not start or complete research. An explicit
146
+ `ultra-research` invocation then records a disposition for each of the seventeen
147
+ coverage areas and executes, verifies, or reuses only the evidence needed to converge
148
+ the baseline.
149
+
150
+ Auto initialization preserves an existing Git repository and HEAD. When Git is absent,
151
+ it initializes `main`, adds the symlink-safe `.ultra` rule to `.gitignore`, and persists
152
+ the resulting `unborn` snapshot. Existing repositories keep their tracked ignore file
153
+ unchanged. It never creates a commit, remote, tag, or push. An initialized
154
+ repository cannot use a non-Git workspace hash: research must obtain approval for a
155
+ local checkpoint commit before `baseline.record`.
156
+
144
157
  Projection-only v4.4 and v4.5 projects use `ultra-tools migrate` to preserve the
145
158
  entire `.ultra` tree, import tasks and events, and create a `migrated/adopting`
146
159
  compatibility row. Schema upgrades use `ultra-tools system doctor --repair` and
@@ -212,7 +225,7 @@ impact, and no open incident. Memory and graph payloads never enter Ultra;
212
225
 
213
226
  ### Context Spine
214
227
 
215
- Context Manifest v2 is the immutable handoff contract between planning,
228
+ Context Manifest v3 is the immutable handoff contract between planning,
216
229
  implementation, checking, review, convergence, and recovery. Each snapshot records:
217
230
 
218
231
  - one role (`plan`, `implement`, `check`, or `review`) and lifecycle gate;
@@ -221,19 +234,24 @@ implementation, checking, review, convergence, and recovery. Each snapshot recor
221
234
  execution contract;
222
235
  - advisory attention budgets (12 files / about 12k tokens / 40% by default);
223
236
  - a DB-derived execution contract (`slice_kind`, public seam, exact verification command);
224
- - one deterministic next action.
237
+ - mechanically valid `allowed_transitions` and a `required_transition` only when a
238
+ hard invariant leaves one recovery route;
239
+ - a digest of the accepted Change authority so semantic updates invalidate the
240
+ snapshot.
225
241
 
226
242
  Prompt input may identify the intended role/gate, add bounded reference candidates,
227
243
  and lower advisory budgets. It cannot supply or override the task seam, verification
228
244
  command, task context references, evidence digest, gate verdict, workflow summary, or
229
- next action. Critical change/dev/test/review/deliver workflow steps record the matching
245
+ machine transitions. A host may attach a clearly non-authoritative recommendation.
246
+ Critical dev/test/review/deliver workflow steps record the matching
230
247
  snapshot as an output; test, review, and delivery reports carry its digest forward.
231
248
 
232
249
  `change.breadcrumb` derives the compact current position from state.db. Session,
233
250
  edit, resume, and OpenCode lifecycle hooks invoke one bundled read-only reader
234
251
  and inject only this breadcrumb, never the intent body,
235
- provider content, or a conversation summary. A changed git HEAD marks the
236
- snapshot stale and routes back to `change.context`.
252
+ provider content, or a conversation summary. A changed Git HEAD, task contract, or
253
+ Change semantic authority marks the snapshot stale and permits recompilation through
254
+ `change.context`.
237
255
 
238
256
  PRD decomposition and complex-task subdivision use the active host model. The
239
257
  MCP accepts the resulting structured tasks, validates schema, topology,
@@ -275,10 +293,13 @@ authority and the legacy task projection before creating a new brownfield adopti
275
293
  A **session** is the standard unit of execution across all four runtimes
276
294
  (D20). One session =
277
295
 
278
- - a fresh OS process for the runtime,
279
- - an isolated `git worktree`,
296
+ - one authoritative task/runtime lease;
297
+ - an isolated `git worktree` created by `session.spawn`,
298
+ - an ignored `.ultra` link from that checkout to the one central authority,
280
299
  - a lease + heartbeat row in `sessions` (D32),
281
- - an `artifact_dir` under `.ultra/sessions/<sid>/` for logs and scratch.
300
+ - an `artifact_dir` under `.ultra/sessions/<sid>/` for logs and scratch;
301
+ - either the active host operating in that exact worktree or an explicitly configured
302
+ worker process.
282
303
 
283
304
  Spawning a new session for a task is gated by `session.admission_check`
284
305
  (D33). If another session already owns an active lease on the same task,
@@ -292,6 +313,43 @@ This admission gate is the smallest piece that prevents two agents from
292
313
  silently double-writing the same task — it is part of the v0.1 minimum
293
314
  execution layer, not deferred to Phase 5.
294
315
 
316
+ Process status is not task status. Exit zero closes transport evidence but leaves the
317
+ task `in_progress` until workflow gates pass; failure blocks the task and records
318
+ circuit evidence. `session.close` preserves its worktree by default. Explicit cleanup
319
+ is allowed only when the worktree is clean and its commit is an ancestor of the
320
+ current checkout.
321
+
322
+ The optional `ubp-orchestrator` daemon requires both `auto_dispatch: true` and an
323
+ explicit executable plus argument array. It refuses to create a session when no real
324
+ worker can consume it. Before launch, the runner verifies that Git ignores the
325
+ symlink itself. When a legacy repository has only the directory-specific `.ultra/`
326
+ rule, the runner adds `.ultra` to the repository-local `info/exclude` file without
327
+ changing a tracked file. It then binds the checkout to the central `.ultra` and rolls
328
+ the worktree back if the ignore rule or binding cannot be established safely. The
329
+ executable is launched without a shell and receives
330
+ the session, task, runtime, worktree, artifact, central DB, checkout root, and
331
+ authority-root paths through reserved `UBP_*` environment variables that caller
332
+ configuration cannot override.
333
+ Pending tasks are skipped while stale, dependency-blocked, circuit-broken, leased, or
334
+ overlapping the declared files of an active task.
335
+
336
+ `execute-plan` is a resumable dispatcher, not a second workflow authority. It runs
337
+ only the exact current DB task graph whose change-bound `plan` workflow is completed,
338
+ healthy, and current. Empty, cyclic, duplicate-task, cross-change, stale, and merely
339
+ exported plans fail before session creation. Owner approval is present only when a
340
+ material planning choice required it. The dispatcher then runs only pending tasks whose
341
+ dependencies are DB-terminal and re-reads task state. If a
342
+ worker exits before the task converges, the current wave and plan become `paused`;
343
+ later waves remain pending. Re-running the command skips completed waves and resumes
344
+ the first unfinished one. `wave_completed` and `plan_completed` therefore describe
345
+ Ultra task convergence, while `wave_paused` and `plan_paused` preserve unfinished
346
+ gate state. Even with explicit auto-merge, integration is attempted only when the
347
+ change task is `completed`, its completion commit matches the session HEAD, its dev
348
+ workflow is ready, and its task review is current; process exit zero is insufficient.
349
+ If task or plan authority changes after wave selection but before worktree creation,
350
+ dispatch reports an authority pause without recording a worker failure or incrementing
351
+ the circuit breaker.
352
+
295
353
  ## 6. Compaction recovery — checkpoint as a validated consumer
296
354
 
297
355
  `workflow_checkpoint.py` validates the current non-terminal workflow and writes