ultra-builder-pro-cli 0.15.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 (42) hide show
  1. package/CHANGELOG.md +34 -1
  2. package/README.md +18 -11
  3. package/adapters/_shared/handbook.cjs +2 -1
  4. package/commands/ultra-change.md +1 -1
  5. package/commands/ultra-plan.md +1 -1
  6. package/commands/ultra-research.md +1 -1
  7. package/commands/ultra-think.md +2 -2
  8. package/docs/AGENT-CONTEXT.md +30 -13
  9. package/docs/ARCHITECTURE.md +24 -4
  10. package/docs/PLAN.zh-CN.md +3 -2
  11. package/docs/ROADMAP.md +5 -3
  12. package/docs/RUNTIME-COMPAT-MATRIX.md +4 -4
  13. package/docs/STATE-DB-ACCESS-POLICY.md +8 -6
  14. package/docs/USER-HANDBOOK-CONTRACT.md +8 -2
  15. package/docs/WORKFLOW-LIFECYCLE.md +89 -13
  16. package/mcp-server/lib/change-workflow.cjs +52 -4
  17. package/mcp-server/lib/context-spine.cjs +65 -3
  18. package/mcp-server/lib/decision-dialogue.cjs +713 -0
  19. package/mcp-server/lib/doctor.cjs +9 -0
  20. package/mcp-server/lib/project-breadcrumb.cjs +9 -0
  21. package/mcp-server/lib/state-db.cjs +29 -6
  22. package/mcp-server/lib/workflow-state.cjs +13 -0
  23. package/mcp-server/server.cjs +54 -0
  24. package/package.json +1 -1
  25. package/skills/ultra-change/SKILL.md +41 -12
  26. package/skills/ultra-deliver/SKILL.md +10 -3
  27. package/skills/ultra-dev/SKILL.md +11 -3
  28. package/skills/ultra-doctor/SKILL.md +10 -0
  29. package/skills/ultra-init/SKILL.md +6 -1
  30. package/skills/ultra-plan/SKILL.md +25 -5
  31. package/skills/ultra-research/SKILL.md +38 -23
  32. package/skills/ultra-review/SKILL.md +9 -3
  33. package/skills/ultra-status/SKILL.md +8 -4
  34. package/skills/ultra-test/SKILL.md +9 -3
  35. package/skills/ultra-think/SKILL.md +26 -12
  36. package/skills/ultra-think/references/decision-dialogue.md +130 -0
  37. package/spec/cli-protocol.md +11 -2
  38. package/spec/mcp-tools.yaml +299 -1
  39. package/spec/schemas/mcp-tools.schema.json +1 -1
  40. package/spec/schemas/state-db.sql +58 -0
  41. package/ultra-tools/cli.cjs +1 -1
  42. package/ultra-tools/commands/status.cjs +15 -0
@@ -12,7 +12,7 @@ host and project owner. It is the operational companion to
12
12
  | Active host and owner | Product judgment, research content, implementation, review findings, release authorization | Durable workflow status or fabricated evidence digests |
13
13
  | Skill | One reusable procedure and its evidence requirements | Project state, static product doctrine, host-specific tool fiction |
14
14
  | MCP | IDs, transitions, task contracts, evidence references, output hashes, gate verdicts, next action | Research prose, code generation, memory, or model calls |
15
- | `.ultra/state.db` | Baseline, change, task, workflow, session, event, incident, projection, and evidence authority | External memory or code-graph payloads |
15
+ | `.ultra/state.db` | Baseline, change, decision, task, workflow, session, event, incident, projection, and evidence authority | Prompts, transcripts, external memory, or code-graph payloads |
16
16
  | Generated JSON and Markdown | Read-only projections and durable artifacts | Independent lifecycle authority |
17
17
  | Hook | Compact DB-derived lifecycle observation, minimal event metadata, and projection protection | Generic engineering judgment, prompt/transcript capture, parallel lifecycle logs, or arbitrary edit blocking |
18
18
 
@@ -22,6 +22,12 @@ inputs, hashes outputs, and derives status. Prompt input cannot override a task'
22
22
  public seam, verification command, context references, workflow summary, gate
23
23
  verdict, output digest, or next action.
24
24
 
25
+ Load-bearing owner choices are normalized through `decision.*`; Prompt prose is not
26
+ durable authority. The host acquires observable facts autonomously, exposes one
27
+ current question, and stops. A workflow cannot advance while a matching decision
28
+ thread is active, waiting for checkpoint confirmation, or carrying a blocking
29
+ deferral.
30
+
25
31
  Every change-bound research, plan, dev, test, review, and deliver run inherits
26
32
  the baseline id from the owning change workflow. A Prompt cannot switch that
27
33
  authority, and no new stage may start on an archived or cancelled change. Doctor
@@ -135,15 +141,32 @@ output cannot complete without it. When the final required step completes, the
135
141
  run becomes `ready`; `workflow.complete` revalidates the entire stage before
136
142
  making it `completed`.
137
143
 
144
+ ### Decision thread and item
145
+
146
+ ```text
147
+ thread: active -> checkpoint_ready -> confirmed
148
+ ^ |
149
+ |--------------+ revision/supersession
150
+
151
+ item: open -> answered | delegated | deferred | superseded
152
+ ```
153
+
154
+ A thread is bound to baseline, change, or workflow authority. A partial unique index
155
+ allows only one `open` item per thread. Resolved items remain immutable history;
156
+ changed evidence or intent uses `decision.supersede` and reopens the checkpoint.
157
+ `decision.checkpoint` first freezes a normalized decision digest, then binds explicit
158
+ owner approval to current artifact digests or a justified standalone no-artifact
159
+ case. It never stores the conversation transcript.
160
+
138
161
  ## 4. Write and verification lifecycle
139
162
 
140
163
  | Stage | Authoritative writes | Completion verification | Next route |
141
164
  |---|---|---|---|
142
165
  | `init` | Baseline classification/scope, init run, research run, initialization event, projection job | DB/schema opens, scaffold/projection succeeds; baseline convergence later completes init | `research`, re-adoption, or `change` for an already-ready baseline |
143
- | `research` | Ordered step evidence, decisions, typed semantic records with source digests, one immutable report per selected step, output paths and SHA-256 digests | Every report has the required structure; semantic kinds/attributes/source anchors are valid and current; full/adoption synthesis binds the current three specifications and distillate; all digests remain current | `baseline.record`, then approval/convergence; bounded change research returns to its change |
166
+ | `research` | Ordered step evidence, decision threads/checkpoints, typed semantic records with source digests, one immutable report per selected step, output paths and SHA-256 digests | Every load-bearing owner decision is confirmed; every report has the required structure; semantic kinds/attributes/source anchors are valid and current; full/adoption synthesis binds the current three specifications and distillate; all digests remain current | `baseline.record`, then approval/convergence; bounded change research returns to its change |
144
167
  | baseline convergence | Spec/source/runtime refs, verification results, known failures, unknowns, gaps, branch/HEAD/worktree, approval | Full/adoption research complete; required discovery/product/architecture refs current; revision/worktree exact; blocking gaps resolved or explicitly accepted where allowed | `change` for every initial or daily outcome |
145
- | `change` | Complete Change Contract, profile/risk rationale, research disposition, intent/delta artifacts, linked change run, initial context snapshot | Blocking decisions resolved; selected change research current; current plan task set matches DB; task-bound context is ready; one next action is derived | selected `research`, then `plan`, or `dev` for an already approved current plan |
146
- | `plan` | Plan-step evidence, complete change-owned task rows, acceptance coverage matrix, change-bound `.ultra/execution-plan.json`, explicit plan approval | Every Change acceptance id is owned by a task; task trace targets resolve; quick has one task; ownership/dependencies valid; no cycles; exported topology and digest exactly match the current change task set | `dev` for the first dependency-ready task |
168
+ | `change` | Baseline-bound alignment checkpoint, complete Change Contract, profile/risk rationale, research disposition, intent/delta artifacts, linked change run, initial context snapshot | The linked alignment thread is confirmed; selected change research is current; current plan task set matches DB; task-bound context is ready; one next action is derived | selected `research`, then `plan`, or `dev` for an already approved current plan |
169
+ | `plan` | Plan decision checkpoint, plan-step evidence, complete change-owned task rows, acceptance coverage matrix, change-bound `.ultra/execution-plan.json`, explicit plan approval | Blocking planning choices are confirmed; every Change acceptance id is owned by a task; task trace targets resolve; quick has one task; ownership/dependencies valid; no cycles; exported topology and digest exactly match the current change task set | `dev` for the first dependency-ready task |
147
170
  | `dev` | Task/session transitions, step evidence, immutable implementation context, completion/review references | Task completed; no live session; task contract unchanged; starting context and review artifacts valid; completion commits remain ancestors of current HEAD | Next task, then aggregate `test` |
148
171
  | `test` | Immutable checking context and `.ultra/reports/tests/<workflow-id>.json` | Report change/task set, full HEAD, worktree digest, context digest, commands, public seam, blockers, and all acceptance/regression/integration/static/build/performance/security/recovery dimensions agree; every omission has a not-applicable rationale | `review` when passing, otherwise resume `dev`/`test` |
149
172
  | `review` | Immutable review context, `task`/`change`/`plan` mode, worker selection provenance, two specialist axes, coordinated summary | Current diff/HEAD and context match; selected/skipped workers match recorded rationale; both axes complete; P0/P1 and axis verdicts deterministically derive final verdict | `deliver` only from approved `change` mode, otherwise resume implementation |
@@ -157,7 +180,38 @@ a retryable incident; it does not silently convert a failed write into success.
157
180
  `task.append_event` accepts only non-authoritative observations. Lifecycle events are
158
181
  emitted by their owning DB mutation and never act as a substitute state transition.
159
182
 
160
- ## 5. Context and prompt follow-through
183
+ ## 5. Human-agent alignment lifecycle
184
+
185
+ The host first reads `decision.list` and resumes matching authority. It starts a new
186
+ thread only for a new decision cluster. For every current decision it must:
187
+
188
+ 1. inspect repository, runtime, tests, specifications, and primary-source evidence;
189
+ 2. resolve evidence-answerable facts without asking the owner;
190
+ 3. call `decision.open` for the earliest unresolved owner choice with a recommendation,
191
+ no more than three credible alternatives, evidence refs, and durable effects;
192
+ 4. present that one choice and end the turn;
193
+ 5. normalize the next owner response with `decision.resolve`, explicit reversible
194
+ delegation with `decision.delegate`, or a consequence-bearing deferral with
195
+ `decision.defer`;
196
+ 6. prepare one compact checkpoint, obtain one owner approval, update the affected
197
+ artifact, and confirm the checkpoint with its current digest. Workflow- and
198
+ change-bound threads must bind an artifact. If a shared specification will keep
199
+ evolving, bind a stable per-thread decision projection rather than the mutable
200
+ working draft.
201
+
202
+ `ultra-think` owns this interaction protocol. `ultra-research`, `ultra-change`, and
203
+ `ultra-plan` invoke it at their decision boundaries. `ultra-dev`, `ultra-test`,
204
+ `ultra-review`, and `ultra-deliver` stop and route back to the same thread when new
205
+ owner authority is required; they never answer on the owner's behalf. `ultra-status`
206
+ shows only the current decision and exact route, while `ultra-doctor` diagnoses stale
207
+ checkpoint artifacts and recovery state.
208
+
209
+ When only a bound artifact changes, the same resolved thread is prepared and approved
210
+ again against the current digest; this is checkpoint renewal, not a fabricated decision
211
+ revision. When the owner choice itself changes, supersession preserves the old item and
212
+ opens one replacement.
213
+
214
+ ## 6. Context and prompt follow-through
161
215
 
162
216
  `change.context` compiles an immutable Context Manifest v2 snapshot. It combines:
163
217
 
@@ -185,7 +239,7 @@ different context. File and token budgets warn; they do not block legitimate
185
239
  work. Missing authority, required refs, task contracts, outputs, approval, or
186
240
  current gate evidence do block.
187
241
 
188
- ## 6. Invalidation rules
242
+ ## 7. Invalidation rules
189
243
 
190
244
  - Changing a task contract invalidates contexts and gates that depended on the
191
245
  former contract.
@@ -204,22 +258,24 @@ current gate evidence do block.
204
258
  their own recorded SHA-256 digests are still authoritative.
205
259
  - Generated `tasks.json` and task-context headers are projections. Manual edits
206
260
  are overwritten and never repair DB authority.
261
+ - A changed checkpoint artifact digest or superseded decision invalidates the linked
262
+ decision checkpoint and blocks matching workflow advancement until reconfirmed.
207
263
 
208
- ## 7. Entry flows
264
+ ## 8. Entry flows
209
265
 
210
266
  ### New project
211
267
 
212
268
  ```text
213
- ultra-init -> full research (17 steps) -> baseline approval
214
- -> initial change + plan -> dev per task -> test -> review -> deliver
269
+ ultra-init -> full research (17 steps with one-decision checkpoints) -> baseline approval
270
+ -> initial change alignment -> plan checkpoint -> dev per task -> test -> review -> deliver
215
271
  ```
216
272
 
217
273
  ### Existing project
218
274
 
219
275
  ```text
220
- ultra-init -> brownfield adoption research (17 steps)
276
+ ultra-init -> brownfield adoption research (17 steps with one-decision checkpoints)
221
277
  -> characterization and known-red evidence -> gap ledger -> owner approval
222
- -> first selected change -> plan -> dev -> test -> review -> deliver
278
+ -> first selected change alignment -> plan checkpoint -> dev -> test -> review -> deliver
223
279
  ```
224
280
 
225
281
  Adoption records the system that exists. It does not force the owner to recreate
@@ -238,7 +294,7 @@ Migration preserves prior files and rows but never carries old approval forward.
238
294
  ### Daily maintenance
239
295
 
240
296
  ```text
241
- ultra-status -> ultra-change -> plan if task contracts are absent
297
+ ultra-status -> ultra-change capture/alignment -> plan if task contracts are absent
242
298
  -> dev -> test -> review -> deliver -> reconciled baseline
243
299
  ```
244
300
 
@@ -247,7 +303,27 @@ they do not skip task ownership, executable acceptance, current checking, review
247
303
  or baseline reconciliation. Incidents add structured diagnosis and recovery
248
304
  evidence rather than bypassing them.
249
305
 
250
- ## 8. Recovery and diagnosis
306
+ ### Command handoff graph
307
+
308
+ | Entry | Owns | Normal handoff |
309
+ |---|---|---|
310
+ | `ultra-init` | classification, scope, scaffold, baseline/run creation | `ultra-research`; an already-ready project routes to `ultra-change` |
311
+ | `ultra-research` | evidence acquisition, seventeen-step semantic coverage, research decisions | baseline approval then `ultra-change`; bounded research returns to `ultra-plan` |
312
+ | `ultra-think` | one current owner decision or read-only diagnosis | exact invoking workflow and step after checkpoint confirmation |
313
+ | `ultra-change` | outcome capture, alignment, Change Contract, risk/research routing | selected `ultra-research`, then `ultra-plan`; approved existing plan may route to `ultra-dev` |
314
+ | `ultra-plan` | plan decisions, task contracts, dependencies, approval | first ready task through `ultra-dev` |
315
+ | `ultra-dev` | one task/session vertical slice and focused review | next `ultra-dev` task or aggregate `ultra-test` |
316
+ | `ultra-test` | change-level acceptance and regression gate | `ultra-review` on pass, `ultra-dev` on implementation failure |
317
+ | `ultra-review` | independent specification and engineering verdicts | `ultra-deliver` on approval; otherwise `ultra-dev` or `ultra-test` |
318
+ | `ultra-deliver` | convergence, reconciliation, archive, authorized release evidence | `ultra-status` or the next `ultra-change` |
319
+ | `ultra-status` | read-only authoritative position and one route | whichever exact workflow owns the current step |
320
+ | `ultra-doctor` | read-only diagnosis and authorized mechanical recovery | exact recovered workflow, `ultra-init`, or `ultra-think` |
321
+
322
+ Every row routes to `ultra-think` when owner authority is missing and to
323
+ `ultra-doctor` when state or installed-runtime authority is unhealthy. Neither route
324
+ creates a parallel workflow or substitutes a Prompt summary for the DB state.
325
+
326
+ ## 9. Recovery and diagnosis
251
327
 
252
328
  Use `system.doctor` for project authority and `ubp --doctor` for installed host
253
329
  assets. Both are read-only unless an explicit project repair command is chosen.
@@ -13,6 +13,7 @@ const providerRefs = require('./provider-refs.cjs');
13
13
  const baselines = require('./baseline-workflow.cjs');
14
14
  const archiveJournal = require('./archive-journal.cjs');
15
15
  const workflows = require('./workflow-state.cjs');
16
+ const decisions = require('./decision-dialogue.cjs');
16
17
  const reconciliationSchema = require('../../spec/schemas/baseline-reconciliation.v1.schema.json');
17
18
 
18
19
  const reconciliationAjv = new Ajv({ allErrors: true, strict: false });
@@ -437,6 +438,7 @@ function writeIntent(file, change) {
437
438
  `- Base commit: \`${change.base_commit || 'unavailable'}\``,
438
439
  `- Documentation impact: \`${impact.status}\``,
439
440
  `- Research disposition: \`${change.research_disposition.status}\``,
441
+ `- Alignment checkpoint: \`${change.alignment_thread_id || 'not-required'}\``,
440
442
  '',
441
443
  '## Intent',
442
444
  '',
@@ -685,11 +687,46 @@ function createChange(db, input, { rootDir = process.cwd() } = {}) {
685
687
  'VALIDATION_ERROR', 'baseline_bypass is not valid while the project baseline is healthy',
686
688
  );
687
689
  }
690
+ const alignmentGate = decisions.decisionGate(
691
+ db, { baseline_id: baselineHealth.baseline?.id || null }, { rootDir },
692
+ );
693
+ if (!alignmentGate.ready) {
694
+ throw new ChangeWorkflowError(
695
+ 'CHANGE_ALIGNMENT_REQUIRED',
696
+ `resolve and confirm the active baseline alignment before creating a change: ${alignmentGate.blockers.join(', ')}`,
697
+ { blockers: alignmentGate.blockers, thread_id: alignmentGate.thread?.id || null },
698
+ );
699
+ }
688
700
  const docsImpact = normalizeDocsImpact(input.docs_impact);
689
701
  const providers = normalizeProviderRefs(input.provider_refs);
690
702
  const contract = normalizeChangeContract(input.contract);
691
703
  const classification = normalizeClassification(input.classification, input.kind);
692
704
  const researchDisposition = normalizeResearchDisposition(input.research_disposition, input.kind);
705
+ let alignmentThread = null;
706
+ if (input.alignment_thread_id !== undefined) {
707
+ try {
708
+ alignmentThread = decisions.assertConfirmedDecisionCheckpoint(
709
+ db, input.alignment_thread_id, { rootDir, requireArtifact: true },
710
+ );
711
+ } catch (error) {
712
+ throw new ChangeWorkflowError(
713
+ 'CHANGE_ALIGNMENT_REQUIRED',
714
+ `alignment_thread_id must name a current artifact-bound checkpoint: ${error.message}`,
715
+ );
716
+ }
717
+ if (alignmentThread.change_id) {
718
+ throw new ChangeWorkflowError(
719
+ 'CHANGE_ALIGNMENT_REQUIRED', `alignment thread already belongs to ${alignmentThread.change_id}`,
720
+ );
721
+ }
722
+ if (!baselineHealth.baseline?.id || alignmentThread.baseline_id !== baselineHealth.baseline.id
723
+ || alignmentThread.workflow_run_id) {
724
+ throw new ChangeWorkflowError(
725
+ 'CHANGE_ALIGNMENT_REQUIRED',
726
+ 'change alignment must be bound only to the current baseline before change creation',
727
+ );
728
+ }
729
+ }
693
730
  const artifactRoot = path.join('.ultra', 'changes', 'active', input.id);
694
731
  const artifactDir = path.resolve(rootDir, artifactRoot);
695
732
  if (fs.existsSync(artifactDir)) {
@@ -700,6 +737,7 @@ function createChange(db, input, { rootDir = process.cwd() } = {}) {
700
737
  id: input.id, title, kind: input.kind, status: 'active',
701
738
  intent, docs_impact: docsImpact, provider_refs: providers, baseline_bypass: baselineBypass,
702
739
  contract, classification, research_disposition: researchDisposition,
740
+ alignment_thread_id: alignmentThread?.id || null,
703
741
  base_commit: input.base_commit || gitHead(rootDir), artifact_root: artifactRoot,
704
742
  created_at: ts, updated_at: ts,
705
743
  };
@@ -709,15 +747,22 @@ function createChange(db, input, { rootDir = process.cwd() } = {}) {
709
747
  `INSERT INTO changes
710
748
  (id, title, kind, status, intent, docs_impact_json, provider_refs_json,
711
749
  baseline_bypass_json, contract_json, classification_json,
712
- research_disposition_json, base_commit, artifact_root, created_at, updated_at)
713
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
750
+ research_disposition_json, alignment_thread_id, base_commit, artifact_root,
751
+ created_at, updated_at)
752
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
714
753
  ).run(
715
754
  row.id, row.title, row.kind, row.status, row.intent, JSON.stringify(row.docs_impact),
716
755
  JSON.stringify(row.provider_refs), row.baseline_bypass
717
756
  ? JSON.stringify(row.baseline_bypass) : null,
718
757
  JSON.stringify(row.contract), JSON.stringify(row.classification),
719
- JSON.stringify(row.research_disposition), row.base_commit, row.artifact_root, ts, ts,
758
+ JSON.stringify(row.research_disposition), row.alignment_thread_id,
759
+ row.base_commit, row.artifact_root, ts, ts,
720
760
  );
761
+ if (row.alignment_thread_id) {
762
+ db.prepare(
763
+ 'UPDATE decision_threads SET change_id = ?, updated_at = ? WHERE id = ?',
764
+ ).run(row.id, ts, row.alignment_thread_id);
765
+ }
721
766
  fs.mkdirSync(artifactDir, { recursive: true });
722
767
  const intentPath = path.join(artifactDir, 'intent.md');
723
768
  writeIntent(intentPath, row);
@@ -738,7 +783,10 @@ function createChange(db, input, { rootDir = process.cwd() } = {}) {
738
783
  }
739
784
  ops.appendEventInTx(db, {
740
785
  type: 'change_created', change_id: row.id,
741
- payload: { kind: row.kind, artifact_root: row.artifact_root },
786
+ payload: {
787
+ kind: row.kind, artifact_root: row.artifact_root,
788
+ alignment_thread_id: row.alignment_thread_id,
789
+ },
742
790
  });
743
791
  let workflow = workflows.startWorkflow(db, {
744
792
  kind: 'change', baseline_id: baselineHealth.baseline?.id || null,
@@ -6,6 +6,7 @@ const path = require('node:path');
6
6
  const { spawnSync } = require('node:child_process');
7
7
  const baselines = require('./baseline-workflow.cjs');
8
8
  const workflows = require('./workflow-state.cjs');
9
+ const decisions = require('./decision-dialogue.cjs');
9
10
  const ops = require('./state-ops.cjs');
10
11
 
11
12
  const ROLES = new Set(['plan', 'implement', 'check', 'review']);
@@ -452,6 +453,38 @@ function workflowNextAction(run) {
452
453
  return `Continue ${run.kind} workflow ${run.id} at ${run.current_step}.`;
453
454
  }
454
455
 
456
+ function summarizeDecision(gate) {
457
+ if (!gate?.thread) return null;
458
+ const current = gate.current_decision;
459
+ return {
460
+ thread_id: gate.thread.id,
461
+ status: gate.thread.status,
462
+ mode: gate.thread.mode,
463
+ current: current ? {
464
+ id: current.id,
465
+ phase: current.phase,
466
+ question: current.question,
467
+ why_now: current.why_now,
468
+ recommendation: current.recommendation,
469
+ options: current.options,
470
+ effects: current.effects,
471
+ } : null,
472
+ };
473
+ }
474
+
475
+ function decisionNextAction(gate) {
476
+ if (gate.blockers?.some((blocker) => blocker.startsWith('DECISION_CHECKPOINT_ARTIFACT_STALE:'))) {
477
+ return `Reprepare and confirm decision checkpoint ${gate.thread?.id || 'for the active alignment thread'} against the current artifact.`;
478
+ }
479
+ if (gate.current_decision) {
480
+ return `Answer decision ${gate.current_decision.id}: ${gate.current_decision.question}`;
481
+ }
482
+ if (gate.thread?.status === 'checkpoint_ready') {
483
+ return `Confirm decision checkpoint ${gate.thread.id} after reviewing its durable effects.`;
484
+ }
485
+ return `Prepare the confirmed decision checkpoint for ${gate.thread?.id || 'the active alignment thread'}.`;
486
+ }
487
+
455
488
  function readBreadcrumb(db, { id } = {}, { rootDir = process.cwd() } = {}) {
456
489
  const baselineHealth = baselines.inspectBaseline(db, { rootDir });
457
490
  const change = id
@@ -464,6 +497,23 @@ function readBreadcrumb(db, { id } = {}, { rootDir = process.cwd() } = {}) {
464
497
  }
465
498
  if (!change) {
466
499
  const baseline = baselineHealth.baseline;
500
+ const decisionGate = decisions.decisionGate(
501
+ db, { baseline_id: baseline?.id || null }, { rootDir },
502
+ );
503
+ if (!decisionGate.ready) {
504
+ return {
505
+ change_id: null, task_id: null, role: 'plan', gate: 'alignment', readiness: 'blocked',
506
+ blockers: [...new Set([...baselineHealth.blockers, ...decisionGate.blockers])],
507
+ warnings: baselineHealth.warnings,
508
+ next_action: decisionNextAction(decisionGate), recommended_workflow: 'ultra-think',
509
+ workflow: null, decision: summarizeDecision(decisionGate),
510
+ context_manifest_path: null, context_manifest_hash: null, git_head: currentHead(rootDir),
511
+ baseline: baseline ? {
512
+ id: baseline.id, mode: baseline.mode, status: baseline.status,
513
+ repository_revision: baseline.repository_revision,
514
+ } : null,
515
+ };
516
+ }
467
517
  if (baselineHealth.status !== 'pass') {
468
518
  const activeWorkflow = actionableBaselineWorkflow(db, baseline?.id, rootDir);
469
519
  const nextAction = activeWorkflow
@@ -475,7 +525,7 @@ function readBreadcrumb(db, { id } = {}, { rootDir = process.cwd() } = {}) {
475
525
  change_id: null, task_id: null, role: 'plan', gate: 'alignment', readiness: 'blocked',
476
526
  blockers: baselineHealth.blockers, warnings: baselineHealth.warnings, next_action: nextAction,
477
527
  recommended_workflow: activeWorkflow ? `ultra-${activeWorkflow.kind}` : 'ultra-init',
478
- workflow: summarizeWorkflow(activeWorkflow), context_manifest_path: null,
528
+ workflow: summarizeWorkflow(activeWorkflow), decision: null, context_manifest_path: null,
479
529
  context_manifest_hash: null, git_head: currentHead(rootDir),
480
530
  baseline: baseline ? {
481
531
  id: baseline.id, mode: baseline.mode, status: baseline.status,
@@ -487,6 +537,7 @@ function readBreadcrumb(db, { id } = {}, { rootDir = process.cwd() } = {}) {
487
537
  change_id: null, task_id: null, role: 'plan', gate: 'alignment', readiness: 'ready',
488
538
  next_action: 'Start daily work with ultra-change.', recommended_workflow: 'ultra-change',
489
539
  workflow: null,
540
+ decision: null,
490
541
  context_manifest_path: null, context_manifest_hash: null, git_head: currentHead(rootDir),
491
542
  blockers: [], warnings: baselineHealth.warnings,
492
543
  baseline: {
@@ -527,8 +578,14 @@ function readBreadcrumb(db, { id } = {}, { rootDir = process.cwd() } = {}) {
527
578
  );
528
579
  const blockers = [];
529
580
  const baselineGate = baselineGateForChange(db, change, baselineHealth);
581
+ const decisionGate = decisions.decisionGate(db, {
582
+ baseline_id: baselineHealth.baseline?.id || null,
583
+ change_id: change.id,
584
+ workflow_run_id: actionableChangeWorkflow(db, change.id, rootDir)?.id || null,
585
+ }, { rootDir });
530
586
  const warnings = [...baselineGate.warnings];
531
587
  blockers.push(...baselineGate.blockers);
588
+ blockers.push(...decisionGate.blockers);
532
589
  if (snapshotMissing) blockers.push('CONTEXT_NOT_COMPILED');
533
590
  else {
534
591
  if (legacyContext) blockers.push('CONTEXT_SNAPSHOT_UPGRADE_REQUIRED');
@@ -554,6 +611,8 @@ function readBreadcrumb(db, { id } = {}, { rootDir = process.cwd() } = {}) {
554
611
  change, tasks, task, role: snapshot?.role || 'plan', readiness: 'blocked',
555
612
  blockers: baselineGate.blockers,
556
613
  });
614
+ } else if (!decisionGate.ready) {
615
+ nextAction = decisionNextAction(decisionGate);
557
616
  } else if (activeWorkflow?.status === 'blocked') {
558
617
  nextAction = workflowNextAction(activeWorkflow);
559
618
  } else if (snapshotMissing) {
@@ -583,14 +642,16 @@ function readBreadcrumb(db, { id } = {}, { rootDir = process.cwd() } = {}) {
583
642
  task_status: task?.status || null,
584
643
  session_id: task?.session_id || null,
585
644
  role: snapshot?.role || 'plan',
586
- gate: snapshot?.gate || 'alignment',
645
+ gate: decisionGate.ready ? (snapshot?.gate || 'alignment') : 'alignment',
587
646
  readiness,
588
647
  blockers,
589
648
  warnings: [...new Set(warnings)],
590
649
  next_action: nextAction,
591
650
  recommended_workflow: baselineGate.blockers.length > 0
592
651
  ? (baselineHealth.baseline?.status === 'ready' ? 'ultra-doctor' : 'ultra-init')
593
- : activeWorkflow
652
+ : !decisionGate.ready
653
+ ? 'ultra-think'
654
+ : activeWorkflow
594
655
  ? `ultra-${activeWorkflow.kind}`
595
656
  : recommendedWorkflow(change, task, tasks, readiness),
596
657
  context_manifest_path: snapshot?.manifest_path || null,
@@ -602,6 +663,7 @@ function readBreadcrumb(db, { id } = {}, { rootDir = process.cwd() } = {}) {
602
663
  repository_revision: baselineHealth.baseline.repository_revision,
603
664
  } : null,
604
665
  workflow: summarizeWorkflow(activeWorkflow),
666
+ decision: summarizeDecision(decisionGate),
605
667
  };
606
668
  }
607
669