ultra-builder-pro-cli 0.17.1 → 0.19.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 (42) hide show
  1. package/CHANGELOG.md +77 -6
  2. package/README.md +380 -261
  3. package/adapters/_shared/codex-assets.cjs +24 -16
  4. package/adapters/_shared/file-ops.cjs +40 -3
  5. package/adapters/_shared/provenance.cjs +90 -6
  6. package/adapters/_shared/runtime-assets.cjs +0 -11
  7. package/adapters/claude.js +3 -1
  8. package/adapters/kimi.js +4 -30
  9. package/adapters/opencode.js +50 -14
  10. package/docs/AGENT-CONTEXT.md +11 -14
  11. package/docs/ARCHITECTURE.md +23 -15
  12. package/docs/DECISIONS.md +75 -0
  13. package/docs/LEGACY-CLI-CROSSWALK.md +3 -3
  14. package/docs/PLUGIN-ISOLATION-CONTRACT.md +54 -0
  15. package/docs/ROADMAP.md +13 -13
  16. package/docs/RUNTIME-COMPAT-MATRIX.md +24 -21
  17. package/docs/STATE-DB-ACCESS-POLICY.md +2 -2
  18. package/docs/WORKFLOW-LIFECYCLE.md +12 -10
  19. package/hooks/active_task_context.py +1 -1
  20. package/hooks/context_spine.py +1 -1
  21. package/hooks/health_check.py +19 -0
  22. package/hooks/pre_stop_check.py +1 -1
  23. package/hooks/workflow_checkpoint.py +1 -1
  24. package/hooks/workflow_context.py +1 -1
  25. package/hooks/workflow_resume.py +1 -1
  26. package/mcp-server/lib/workflow-state.cjs +67 -54
  27. package/mcp-server/server.cjs +19 -7
  28. package/orchestrator/dispatch-rules.cjs +1 -1
  29. package/package.json +4 -5
  30. package/skills/ultra-change/SKILL.md +4 -3
  31. package/skills/ultra-init/SKILL.md +2 -1
  32. package/skills/ultra-research/SKILL.md +7 -4
  33. package/spec/cli-protocol.md +4 -4
  34. package/spec/mcp-tools.yaml +6 -6
  35. package/spec/schemas/context-file.v4.5.schema.json +1 -1
  36. package/spec/schemas/mcp-tools.schema.json +3 -3
  37. package/spec/schemas/state-db.sql +3 -3
  38. package/spec/schemas/tasks.v4.5.schema.json +1 -1
  39. package/adapters/_shared/handbook.cjs +0 -156
  40. package/bin/handbook.js +0 -54
  41. package/docs/PLAN.zh-CN.md +0 -1696
  42. package/docs/USER-HANDBOOK-CONTRACT.md +0 -81
@@ -110,15 +110,9 @@ const RESEARCH_DISPOSITIONS = new Set([
110
110
  'execute', 'verify_existing', 'reuse', 'not_applicable', 'deferred',
111
111
  ]);
112
112
 
113
- const RESEARCH_MODES = Object.freeze({
114
- full: WORKFLOW_DEFINITIONS.research.map((item) => item.id),
115
- adoption: WORKFLOW_DEFINITIONS.research.map((item) => item.id),
116
- product: WORKFLOW_DEFINITIONS.research
117
- .filter((item) => Number.parseInt(item.id, 10) < 30 || item.id === '99-synthesis')
118
- .map((item) => item.id),
119
- feature: ['10-user-personas', '11-user-scenarios', '20-user-stories', '21-features-scope', '22-success-metrics', '99-synthesis'],
120
- architecture: ['30-architecture-context', '31-solution-strategy', '32-building-blocks', '40-deployment', '41-quality-risks', '99-synthesis'],
121
- });
113
+ const RESEARCH_MODE_NAMES = new Set([
114
+ 'full', 'adoption', 'product', 'feature', 'architecture', 'custom',
115
+ ]);
122
116
 
123
117
  const RESEARCH_SEMANTIC_CONTRACTS = Object.freeze({
124
118
  '00-problem-validation': ['problem', ['actor', 'current_workaround', 'consequence', 'evidence_status']],
@@ -459,9 +453,20 @@ function decorateRun(db, row, rootDir) {
459
453
  return value;
460
454
  }
461
455
 
456
+ function definitionForRun(run) {
457
+ const definition = WORKFLOW_DEFINITIONS[run.kind] || [];
458
+ if (run.kind !== 'research') return definition;
459
+ const metadata = parseJson(run.metadata_json, 'workflow_runs.metadata_json', {});
460
+ if (!Array.isArray(metadata.coverage) || metadata.coverage.length === 0) {
461
+ return definition;
462
+ }
463
+ const covered = new Set(metadata.coverage.map((item) => item?.step_id).filter(Boolean));
464
+ return definition.filter((item) => covered.has(item.id));
465
+ }
466
+
462
467
  function inspectRunHealth(run, steps, rootDir) {
463
468
  const blockers = [...inspectOutputHealth(steps, rootDir).blockers];
464
- const definition = WORKFLOW_DEFINITIONS[run.kind] || [];
469
+ const definition = definitionForRun(run);
465
470
  const expectedIds = new Set(definition.map((item) => item.id));
466
471
  const actualIds = new Set(steps.map((item) => item.step_id));
467
472
  if (run.definition_version !== DEFINITION_VERSION) {
@@ -576,6 +581,9 @@ function listWorkflows(db, filter = {}, { rootDir = process.cwd() } = {}) {
576
581
  function normalizeResearchCoverage(mode, coverage, selectedSteps, metadata = {}) {
577
582
  const knownSteps = WORKFLOW_DEFINITIONS.research.map((item) => item.id);
578
583
  const known = new Set(knownSteps);
584
+ if (!RESEARCH_MODE_NAMES.has(mode)) {
585
+ throw new WorkflowStateError('VALIDATION_ERROR', `unsupported research mode: ${mode}`);
586
+ }
579
587
  if (coverage !== undefined) {
580
588
  if (!Array.isArray(coverage) || coverage.length === 0) {
581
589
  throw new WorkflowStateError(
@@ -635,55 +643,66 @@ function normalizeResearchCoverage(mode, coverage, selectedSteps, metadata = {})
635
643
  ...(consequence ? { consequence } : {}),
636
644
  };
637
645
  });
638
- if (['full', 'adoption'].includes(mode)) {
639
- const missing = knownSteps.filter((stepId) => !seen.has(stepId));
640
- if (missing.length > 0) {
641
- throw new WorkflowStateError(
642
- 'WORKFLOW_RESEARCH_COVERAGE_REQUIRED',
643
- `${mode} research must disposition every semantic area: ${missing.join(', ')}`,
644
- );
645
- }
646
- const synthesis = normalized.find((item) => item.step_id === '99-synthesis');
647
- if (!synthesis || !['execute', 'verify_existing', 'reuse'].includes(synthesis.disposition)) {
648
- throw new WorkflowStateError(
649
- 'WORKFLOW_RESEARCH_SYNTHESIS_REQUIRED',
650
- `${mode} research requires an executable or reusable 99-synthesis disposition`,
651
- );
652
- }
646
+ const selectionReason = String(metadata.selection_reason || '').trim();
647
+ if (selectionReason.length < 3) {
648
+ throw new WorkflowStateError(
649
+ 'WORKFLOW_SELECTION_REASON_REQUIRED',
650
+ `${mode} research requires the model's evidence-based coverage rationale`,
651
+ );
652
+ }
653
+ const synthesis = normalized.find((item) => item.step_id === '99-synthesis');
654
+ if (!synthesis || !['execute', 'verify_existing', 'reuse'].includes(synthesis.disposition)) {
655
+ throw new WorkflowStateError(
656
+ 'WORKFLOW_RESEARCH_SYNTHESIS_REQUIRED',
657
+ `${mode} research requires an executable or reusable 99-synthesis disposition`,
658
+ );
659
+ }
660
+ const selectedSemanticArea = normalized.some((item) => (
661
+ item.step_id !== '99-synthesis'
662
+ && (
663
+ ['execute', 'verify_existing', 'reuse'].includes(item.disposition)
664
+ || (item.disposition === 'deferred' && !item.accepted_by)
665
+ )
666
+ ));
667
+ if (!selectedSemanticArea) {
668
+ throw new WorkflowStateError(
669
+ 'WORKFLOW_RESEARCH_COVERAGE_REQUIRED',
670
+ `${mode} research requires at least one applicable semantic area besides synthesis`,
671
+ );
653
672
  }
654
673
  return normalized.sort(
655
674
  (left, right) => knownSteps.indexOf(left.step_id) - knownSteps.indexOf(right.step_id),
656
675
  );
657
676
  }
658
677
 
659
- if (['full', 'adoption'].includes(mode)) {
660
- throw new WorkflowStateError(
661
- 'WORKFLOW_RESEARCH_COVERAGE_REQUIRED',
662
- `${mode} research requires model-selected coverage dispositions`,
663
- );
664
- }
665
678
  if (mode === 'custom') {
666
679
  if (!Array.isArray(selectedSteps) || selectedSteps.length === 0) {
667
- throw new WorkflowStateError('VALIDATION_ERROR', 'custom research requires selected_steps');
680
+ throw new WorkflowStateError(
681
+ 'WORKFLOW_RESEARCH_COVERAGE_REQUIRED',
682
+ 'custom research requires model-selected coverage or selected_steps',
683
+ );
668
684
  }
669
- const known = new Set(WORKFLOW_DEFINITIONS.research.map((item) => item.id));
670
685
  const invalid = selectedSteps.find((item) => !known.has(item));
671
686
  if (invalid) throw new WorkflowStateError('VALIDATION_ERROR', `unknown research step: ${invalid}`);
672
- return [...new Set([...selectedSteps, '99-synthesis'])].map((stepId) => ({
687
+ const selectionReason = String(metadata.selection_reason || '').trim();
688
+ if (selectionReason.length < 3) {
689
+ throw new WorkflowStateError(
690
+ 'WORKFLOW_SELECTION_REASON_REQUIRED',
691
+ 'custom research requires the model\'s evidence-based coverage rationale',
692
+ );
693
+ }
694
+ const explicitCoverage = [...new Set([...selectedSteps, '99-synthesis'])].map((stepId) => ({
673
695
  step_id: stepId,
674
696
  disposition: 'execute',
675
- rationale: String(metadata.selection_reason || 'Explicit bounded research selection.').trim(),
697
+ rationale: selectionReason,
676
698
  evidence_refs: [],
677
699
  }));
700
+ return normalizeResearchCoverage(mode, explicitCoverage, undefined, metadata);
678
701
  }
679
- const selected = RESEARCH_MODES[mode];
680
- if (!selected) throw new WorkflowStateError('VALIDATION_ERROR', `unsupported research mode: ${mode}`);
681
- return selected.map((stepId) => ({
682
- step_id: stepId,
683
- disposition: 'execute',
684
- rationale: String(metadata.selection_reason || `${mode} research profile selected by the host model.`).trim(),
685
- evidence_refs: [],
686
- }));
702
+ throw new WorkflowStateError(
703
+ 'WORKFLOW_RESEARCH_COVERAGE_REQUIRED',
704
+ `${mode} research requires model-selected coverage dispositions`,
705
+ );
687
706
  }
688
707
 
689
708
  function assertReference(db, table, id, field) {
@@ -722,15 +741,7 @@ function assertWorkflowAuthority(db, input, mode) {
722
741
  'WORKFLOW_AUTHORITY_REQUIRED', 'research workflow requires baseline_id or change_id',
723
742
  );
724
743
  }
725
- if (['product', 'feature', 'architecture', 'custom'].includes(mode)) {
726
- requireField('change_id');
727
- if (!String(input.metadata?.selection_reason || '').trim()) {
728
- throw new WorkflowStateError(
729
- 'WORKFLOW_SELECTION_REASON_REQUIRED',
730
- `${mode} research requires an explicit owner-selected scope reason`,
731
- );
732
- }
733
- }
744
+ if (['product', 'feature', 'architecture', 'custom'].includes(mode)) requireField('change_id');
734
745
  if (input.baseline_id && !input.change_id) {
735
746
  const baseline = db.prepare('SELECT mode FROM baselines WHERE id = ?').get(input.baseline_id);
736
747
  const expectedMode = baseline?.mode === 'brownfield' ? 'adoption' : 'full';
@@ -887,7 +898,7 @@ function insertWorkflowInTx(db, input = {}, { rootDir = process.cwd() } = {}) {
887
898
  throw new WorkflowStateError('VALIDATION_ERROR', `unsupported workflow kind: ${input.kind}`);
888
899
  }
889
900
  input = bindChangeWorkflowAuthority(db, input);
890
- const definition = WORKFLOW_DEFINITIONS[input.kind];
901
+ const catalogDefinition = WORKFLOW_DEFINITIONS[input.kind];
891
902
  const mode = input.kind === 'research' ? (input.mode || 'full') : null;
892
903
  assertWorkflowAuthority(db, input, mode);
893
904
  const id = input.id || `wf-${input.kind}-${crypto.randomUUID().slice(0, 12)}`;
@@ -957,6 +968,9 @@ function insertWorkflowInTx(db, input = {}, { rootDir = process.cwd() } = {}) {
957
968
  ? normalizeResearchCoverage(mode, input.coverage, input.selected_steps, input.metadata)
958
969
  : null;
959
970
  const coverageByStep = new Map((researchCoverage || []).map((item) => [item.step_id, item]));
971
+ const definition = input.kind === 'research'
972
+ ? catalogDefinition.filter((item) => coverageByStep.has(item.id))
973
+ : catalogDefinition;
960
974
  const selected = new Set(input.kind === 'research'
961
975
  ? researchCoverage
962
976
  .filter((item) => ['execute', 'verify_existing', 'reuse'].includes(item.disposition)
@@ -2380,7 +2394,6 @@ function recoverUntrackedChangeWorkflows(db, { rootDir = process.cwd() } = {}) {
2380
2394
  module.exports = {
2381
2395
  DEFINITION_VERSION,
2382
2396
  WORKFLOW_DEFINITIONS,
2383
- RESEARCH_MODES,
2384
2397
  RUN_KINDS,
2385
2398
  RUN_STATUSES,
2386
2399
  WorkflowStateError,
@@ -818,16 +818,28 @@ function appendHookLifecycleEvent({ rootDir, action, hookInput = {} } = {}) {
818
818
  const db = openStateDb(dbPath);
819
819
  try {
820
820
  ensureSchemaVersion(db);
821
- const change = db.prepare(
822
- `SELECT id FROM changes WHERE status IN ('active', 'blocked', 'ready')
821
+ const workflow = db.prepare(
822
+ `SELECT change_id, task_id FROM workflow_runs
823
+ WHERE status IN ('active', 'blocked', 'ready') AND change_id IS NOT NULL
823
824
  ORDER BY updated_at DESC, rowid DESC LIMIT 1`,
824
825
  ).get();
826
+ if (!workflow) return { recorded: false, reason: 'NO_ACTIVE_WORKFLOW' };
827
+ const change = db.prepare(
828
+ `SELECT id FROM changes
829
+ WHERE id = ? AND status IN ('active', 'blocked', 'ready')`,
830
+ ).get(workflow.change_id);
825
831
  if (!change) return { recorded: false, reason: 'NO_ACTIVE_CHANGE' };
826
- const task = db.prepare(
827
- `SELECT id FROM tasks WHERE change_id = ? AND status IN ('in_progress', 'pending', 'blocked')
828
- ORDER BY CASE status WHEN 'in_progress' THEN 0 WHEN 'blocked' THEN 1 ELSE 2 END,
829
- updated_at DESC, rowid DESC LIMIT 1`,
830
- ).get(change.id);
832
+ const task = workflow.task_id
833
+ ? db.prepare(
834
+ `SELECT id FROM tasks
835
+ WHERE id = ? AND change_id = ?
836
+ AND status IN ('in_progress', 'pending', 'blocked')`,
837
+ ).get(workflow.task_id, change.id)
838
+ : db.prepare(
839
+ `SELECT id FROM tasks WHERE change_id = ? AND status IN ('in_progress', 'pending', 'blocked')
840
+ ORDER BY CASE status WHEN 'in_progress' THEN 0 WHEN 'blocked' THEN 1 ELSE 2 END,
841
+ updated_at DESC, rowid DESC LIMIT 1`,
842
+ ).get(change.id);
831
843
  const payload = {
832
844
  agent_id: hookMetadata(hookInput.agent_id),
833
845
  agent_type: hookMetadata(hookInput.agent_type),
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- // Phase 8B.1 — Declarative dispatch rules (GSD-2 pattern).
3
+ // Declarative dispatch rules for deterministic priority matching.
4
4
  //
5
5
  // evaluate(ctx, rules) walks a priority-sorted rule array and returns the
6
6
  // first matching { rule_id, action, runtime }. Actions:
package/package.json CHANGED
@@ -1,11 +1,10 @@
1
1
  {
2
2
  "name": "ultra-builder-pro-cli",
3
- "version": "0.17.1",
4
- "description": "Multi-runtime installer for the Ultra Builder Pro agent engineering system — deploys commands, agents, skills, hooks, and MCP server to Claude Code, OpenCode, Codex CLI, and Kimi Code.",
3
+ "version": "0.19.1",
4
+ "description": "Explicitly invoked Ultra Builder Pro workflows for Claude Code, OpenCode, Codex CLI, and Kimi Code, with project-local MCP authority and lifecycle recovery.",
5
5
  "bin": {
6
6
  "ultra-builder-pro-cli": "bin/install.js",
7
7
  "ubp": "bin/install.js",
8
- "ubp-handbook": "bin/handbook.js",
9
8
  "ubp-orchestrator": "bin/orchestrator.js",
10
9
  "ultra-tools": "ultra-tools/cli.cjs"
11
10
  },
@@ -54,9 +53,9 @@
54
53
  "docs/AGENT-CONTEXT.md",
55
54
  "docs/SKILL-AUTHORING.md",
56
55
  "docs/COMMIT-HASH-BACKFILL.md",
57
- "docs/PLAN.zh-CN.md",
56
+ "docs/DECISIONS.md",
58
57
  "docs/STATE-DB-ACCESS-POLICY.md",
59
- "docs/USER-HANDBOOK-CONTRACT.md",
58
+ "docs/PLUGIN-ISOLATION-CONTRACT.md",
60
59
  "docs/RUNTIME-COMPAT-MATRIX.md",
61
60
  "!**/__pycache__/**",
62
61
  "!**/*.pyc",
@@ -60,9 +60,10 @@ meaning is coupled. Downstream task, context, test, or review evidence affected
60
60
  change must become stale and be regenerated; never keep old evidence green by prompt
61
61
  assertion.
62
62
 
63
- If research is required, invoke `ultra-research` with the recorded disposition. If the
64
- contract is sufficiently evidenced, the host may recommend `ultra-plan`. `change.context`
65
- is compiled only for an actual plan, implementation, test, or review consumer.
63
+ If research is required, recommend `ultra-research` with the recorded disposition. If
64
+ the contract is sufficiently evidenced, recommend `ultra-plan`. Return and wait for an
65
+ explicit user invocation; do not start either workflow. `change.context` is compiled
66
+ only for an actual plan, implementation, test, or review consumer.
66
67
 
67
68
  Stable implementation discoveries use the approval-gated specification-learning
68
69
  transitions. External memory and graph providers retain their own content; Ultra stores
@@ -75,7 +75,8 @@ Read the returned `allowed_transitions` and `required_transition`:
75
75
  - a required transition represents a hard recovery invariant and must be followed;
76
76
  - otherwise the host model recommends among the allowed capabilities using the user's
77
77
  goal and current evidence;
78
- - start `ultra-research` only when the user invoked it or the host selects it after init.
78
+ - never invoke another public Ultra workflow automatically; recommend the best allowed
79
+ capability and wait for an explicit user invocation.
79
80
 
80
81
  Report classification, scope, schema and backup result, Git state, init workflow id,
81
82
  baseline state, and allowed transitions. Do not claim that a baseline is ready until a
@@ -7,8 +7,8 @@ description: Build or refresh an evidence-backed product and architecture baseli
7
7
 
8
8
  The host model owns investigation, synthesis, and coverage judgment. Ultra MCP owns the
9
9
  run, evidence references, semantic records, artifact digests, freshness, and
10
- convergence. The seventeen areas are a coverage catalog, not a mandatory questionnaire
11
- or a fixed sequence of user conversations.
10
+ convergence. The reference areas are an optional coverage catalog, not a mandatory
11
+ questionnaire or a fixed sequence of user conversations.
12
12
 
13
13
  Read `../ultra-think/references/decision-dialogue.md` before asking a material question.
14
14
 
@@ -21,16 +21,19 @@ Read `../ultra-think/references/decision-dialogue.md` before asking a material q
21
21
  - `adoption` for a brownfield baseline;
22
22
  - a bounded mode only for a recorded active-change research disposition.
23
23
  4. Before `workflow.start`, inspect current code, docs, tests, runtime, and prior
24
- artifacts. Provide one disposition for every catalog area in `full` or `adoption`:
24
+ artifacts. Select the smallest sufficient set of applicable catalog areas. Include
25
+ only areas that need work or whose exclusion must remain auditable:
25
26
  - `execute`: produce fresh evidence;
26
27
  - `verify_existing`: verify a current artifact against its source;
27
28
  - `reuse`: reuse evidence that is still current;
28
29
  - `not_applicable`: exclude with an evidence reference and rationale;
29
30
  - `deferred`: record the consequence and owner acceptance when deferral changes the
30
31
  accepted scope or leaves material risk.
32
+ 5. Pass the evidence-based coverage rationale as `metadata.selection_reason`. Omitted
33
+ catalog areas create no DB step and need no ceremonial disposition.
31
34
 
32
35
  `99-synthesis` must execute, verify, or reuse. A missing disposition is a coverage
33
- error; a non-executed area with evidence is not an incomplete workflow.
36
+ error only for an included area; a recorded exclusion with evidence is not incomplete.
34
37
 
35
38
  ## Coverage catalog
36
39
 
@@ -1,11 +1,11 @@
1
1
  # `ultra-tools` CLI protocol
2
2
 
3
- `ultra-tools` is the maintenance CLI around the live MCP runtime (PLAN §4,
4
- D12). The mapping registry below reserves stable command names for MCP tools;
3
+ `ultra-tools` is the maintenance CLI around the live MCP runtime. The mapping
4
+ registry below reserves stable command names for MCP tools;
5
5
  it does not mean every mapping is executable. Task lifecycle workflows must use
6
6
  the live MCP server and fail closed when it is unavailable.
7
7
 
8
- Trace: PLAN §6 Phase 1.4, decisions D12 / D29 / D33 / D37.
8
+ Authority boundary: [`docs/DECISIONS.md`](../docs/DECISIONS.md).
9
9
 
10
10
  ## 1. Invocation
11
11
 
@@ -159,7 +159,7 @@ The full access policy lives in
159
159
  The CLI follows the `version` field of `spec/mcp-tools.yaml`. Adding a new
160
160
  flag is a minor bump; renaming or removing a flag, or changing exit-code
161
161
  semantics, is a major bump. Major bumps require an entry in
162
- `docs/PLAN.zh-CN.md §14 Decision log`.
162
+ [`docs/DECISIONS.md`](../docs/DECISIONS.md) and the changelog.
163
163
 
164
164
  ## 7. `ubp-orchestrator`
165
165
 
@@ -1,14 +1,14 @@
1
1
  # Ultra Builder Pro — MCP tool contracts
2
2
  # Single source for tool name, schema, errors, and CLI mapping.
3
3
  # Validated by spec/schemas/mcp-tools.schema.json (run `npm run test:spec`).
4
- # Trace: PLAN §7.3, decisions D12 / D29 / D31-D34.
4
+ # Current authority boundary: docs/DECISIONS.md.
5
5
 
6
6
  version: '1.3'
7
7
 
8
8
  info:
9
9
  title: Ultra Builder Pro MCP tool contracts
10
10
  phase: '1.2'
11
- trace_to: 'PLAN docs/PLAN.zh-CN.md §7.3'
11
+ trace_to: 'docs/DECISIONS.md and mcp-server/server.cjs'
12
12
  notes: |
13
13
  This published contract contains only tools registered by the MCP server.
14
14
  Host-native review, impact, skill discovery, and human-interaction capabilities
@@ -397,7 +397,7 @@ tools:
397
397
  - { code: IO_ERROR, description: 'filesystem write failed', retriable: true }
398
398
  cli_subcommand: task init-project
399
399
  emits_events: [project_initialized, project_resumed]
400
- trace_to: 'PLAN §6 Phase 3.1'
400
+ trace_to: 'mcp-server/lib/init-project.cjs'
401
401
 
402
402
  - name: task.expand
403
403
  family: task
@@ -1833,7 +1833,7 @@ tools:
1833
1833
  # ────────────────────────── workflow family ────────────────────────────
1834
1834
  - name: workflow.start
1835
1835
  family: workflow
1836
- description: Start one durable Ultra workflow run. For full or adoption research, the host model must disposition every semantic coverage area without being forced to execute every area.
1836
+ description: Start one durable Ultra workflow run. For research, the host model supplies only evidence-relevant semantic coverage; MCP records and validates the selection without choosing it.
1837
1837
  phase: '13'
1838
1838
  writer_role: mcp
1839
1839
  input_schema:
@@ -1883,11 +1883,11 @@ tools:
1883
1883
  - { code: WORKFLOW_AUTHORITY_REQUIRED, description: 'the workflow is missing its required baseline, change, or task authority' }
1884
1884
  - { code: WORKFLOW_CHANGE_DECISION_BLOCKING, description: 'the change contains unresolved material owner decisions' }
1885
1885
  - { code: WORKFLOW_CHANGE_RESEARCH_INCOMPLETE, description: 'the change-bound research disposition is not current and complete' }
1886
- - { code: WORKFLOW_RESEARCH_COVERAGE_REQUIRED, description: 'full and adoption research require a disposition for every semantic area' }
1886
+ - { code: WORKFLOW_RESEARCH_COVERAGE_REQUIRED, description: 'research requires model-selected coverage with at least one applicable semantic area beyond synthesis' }
1887
1887
  - { code: WORKFLOW_RESEARCH_COVERAGE_EVIDENCE_REQUIRED, description: 'reuse, verification, and not-applicable dispositions require evidence references' }
1888
1888
  - { code: WORKFLOW_RESEARCH_SYNTHESIS_REQUIRED, description: 'research synthesis must execute, verify, or reuse durable evidence' }
1889
1889
  - { code: WORKFLOW_RESEARCH_MODE_MISMATCH, description: 'the selected research mode does not match its authority or baseline mode' }
1890
- - { code: WORKFLOW_SELECTION_REASON_REQUIRED, description: 'bounded research requires an explicit owner-selected scope reason' }
1890
+ - { code: WORKFLOW_SELECTION_REASON_REQUIRED, description: 'research requires the model evidence rationale for its selected coverage' }
1891
1891
  - { code: TASK_EXECUTION_CONTRACT_INCOMPLETE, description: 'dev cannot start until the linked task contract is executable' }
1892
1892
  - { code: TASK_CHANGE_OWNERSHIP_MISMATCH, description: 'linked task and change authority disagree' }
1893
1893
  - { code: TASK_DEPENDENCIES_INCOMPLETE, description: 'dev cannot start while a task dependency remains incomplete' }
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://ultra-builder-pro.dev/spec/context-file.v4.5.schema.json",
4
4
  "title": "context-file projection (v4.5)",
5
- "description": "Schema for the parsed representation of .ultra/tasks/contexts/task-*.md. The header is regenerated by the projector from state.db (D32). Body sections are author-owned; status MUST NOT appear in body. Trace: PLAN §7.1, D17, D21.",
5
+ "description": "Schema for the parsed representation of .ultra/tasks/contexts/task-*.md. The header is regenerated by the projector from state.db. Body sections are author-owned; status MUST NOT appear in body. Authority boundary: docs/DECISIONS.md.",
6
6
  "type": "object",
7
7
  "required": ["header", "body"],
8
8
  "additionalProperties": false,
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://ultra-builder-pro.dev/spec/mcp-tools.schema.json",
4
4
  "title": "MCP tools manifest",
5
- "description": "Meta-schema describing the shape of spec/mcp-tools.yaml. The manifest itself is the single source for MCP tool contracts (PLAN R19).",
5
+ "description": "Meta-schema describing the shape of spec/mcp-tools.yaml. The manifest is the single source for published MCP tool contracts.",
6
6
  "type": "object",
7
7
  "required": ["version", "info", "tools"],
8
8
  "additionalProperties": false,
@@ -56,7 +56,7 @@
56
56
  "name": {
57
57
  "type": "string",
58
58
  "pattern": "^[a-z][a-z_]*\\.[a-z][a-z_]*$",
59
- "description": "family.verb naming convention (PLAN §7.3)"
59
+ "description": "family.verb naming convention"
60
60
  },
61
61
  "family": {
62
62
  "type": "string",
@@ -103,7 +103,7 @@
103
103
  },
104
104
  "trace_to": {
105
105
  "type": "string",
106
- "description": "PLAN section/decision id (e.g. §7.3, D33)"
106
+ "description": "current source or decision reference"
107
107
  }
108
108
  }
109
109
  },
@@ -1,9 +1,9 @@
1
1
  -- Ultra Builder Pro — authoritative state schema
2
- -- Phase 2 builds .ultra/state.db from this file. Source of truth for all
2
+ -- This file builds .ultra/state.db, the source of truth for all
3
3
  -- baseline / change / task / session / event / telemetry data. tasks.json and context md status
4
- -- header are projections (PLAN §7.1, D18, D32).
4
+ -- headers are projections.
5
5
  --
6
- -- Trace: docs/PLAN.zh-CN.md §7.1; decisions D18/D30/D31/D32/D37/D52/D54.
6
+ -- Authority boundary: docs/DECISIONS.md.
7
7
  --
8
8
  -- PRAGMAs are applied by mcp-server/lib/state-db.ts on connection open;
9
9
  -- they cannot be persisted in CREATE statements but are documented here.
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://ultra-builder-pro.dev/spec/tasks.v4.5.schema.json",
4
4
  "title": "tasks.json projection (v4.5)",
5
- "description": "Read-only projection of the tasks table. Generated by the projector (Phase 2.6) — manual edits are overwritten. Status and dependencies are derived from state.db; do not write them through this file. Trace: PLAN §7.1, D21, D32.",
5
+ "description": "Read-only projection of the tasks table. Generated by the projector; manual edits are overwritten. Status and dependencies are derived from state.db. Authority boundary: docs/DECISIONS.md.",
6
6
  "type": "object",
7
7
  "required": ["schema_version", "generated_at", "tasks"],
8
8
  "additionalProperties": false,
@@ -1,156 +0,0 @@
1
- 'use strict';
2
-
3
- const fs = require('node:fs');
4
- const os = require('node:os');
5
- const path = require('node:path');
6
-
7
- const { writeAtomic } = require('./file-ops.cjs');
8
-
9
- const BEGIN_MARKER = '<!-- ultra-builder-pro:handbook:start -->';
10
- const END_MARKER = '<!-- ultra-builder-pro:handbook:end -->';
11
- const LEGACY_HEADING = '## Ultra Builder Pro Runtime Contract';
12
-
13
- const HOSTS = Object.freeze({
14
- claude: Object.freeze({
15
- name: 'Claude Code',
16
- handbook: ['.claude', 'CLAUDE.md'],
17
- invocation: '`/ultra-init`, `/ultra-research`, `/ultra-plan`, `/ultra-change`, `/ultra-dev`, `/ultra-test`, `/ultra-review`, and `/ultra-deliver`; diagnostics use `/ultra-status`, `/ultra-think`, and `/ultra-doctor`',
18
- collaboration: '`/codex-collab` is an explicitly requested read-only advisor',
19
- coordination: 'Claude Code task UI is host coordination; it does not replace Ultra task state',
20
- }),
21
- codex: Object.freeze({
22
- name: 'Codex',
23
- handbook: ['.codex', 'AGENTS.md'],
24
- invocation: '`$ultra-builder-pro:ultra-init`, `$ultra-builder-pro:ultra-research`, `$ultra-builder-pro:ultra-plan`, `$ultra-builder-pro:ultra-change`, `$ultra-builder-pro:ultra-dev`, `$ultra-builder-pro:ultra-test`, `$ultra-builder-pro:ultra-review`, and `$ultra-builder-pro:ultra-deliver`; diagnostics use `$ultra-builder-pro:ultra-status`, `$ultra-builder-pro:ultra-think`, and `$ultra-builder-pro:ultra-doctor`',
25
- collaboration: '`$ultra-builder-pro:cc-collab` is an explicitly requested read-only advisor',
26
- coordination: 'The Codex plan is turn-facing coordination; it does not replace Ultra task state',
27
- }),
28
- opencode: Object.freeze({
29
- name: 'OpenCode',
30
- handbook: ['.config', 'opencode', 'AGENTS.md'],
31
- invocation: '`/ultra-init`, `/ultra-research`, `/ultra-plan`, `/ultra-change`, `/ultra-dev`, `/ultra-test`, `/ultra-review`, and `/ultra-deliver`; diagnostics use `/ultra-status`, `/ultra-think`, and `/ultra-doctor`',
32
- collaboration: '`/cc-collab` and `/codex-collab` are explicitly requested read-only advisors',
33
- coordination: 'OpenCode session coordination does not replace Ultra task state',
34
- }),
35
- kimi: Object.freeze({
36
- name: 'Kimi Code',
37
- handbook: ['.kimi-code', 'AGENTS.md'],
38
- invocation: '`/ultra-builder-pro:ultra-init`, `/ultra-builder-pro:ultra-research`, `/ultra-builder-pro:ultra-plan`, `/ultra-builder-pro:ultra-change`, `/ultra-builder-pro:ultra-dev`, `/ultra-builder-pro:ultra-test`, `/ultra-builder-pro:ultra-review`, and `/ultra-builder-pro:ultra-deliver`; diagnostics use `/ultra-builder-pro:ultra-status`, `/ultra-builder-pro:ultra-think`, and `/ultra-builder-pro:ultra-doctor`',
39
- collaboration: '`/skill:cc-collab` and `/skill:codex-collab` are explicitly requested read-only advisors',
40
- coordination: 'Kimi `TodoList` and `Agent` / `AgentSwarm` are host coordination; they do not replace Ultra task state',
41
- }),
42
- });
43
-
44
- function host(runtime) {
45
- const value = HOSTS[runtime];
46
- if (!value) throw new Error(`unsupported handbook runtime: ${runtime}`);
47
- return value;
48
- }
49
-
50
- function renderHandbook(runtime) {
51
- const value = host(runtime);
52
- return [
53
- BEGIN_MARKER,
54
- '## Ultra Builder Pro Runtime Contract',
55
- '',
56
- `Ultra Builder Pro is installed as a native ${value.name} plugin. Keep general engineering`,
57
- 'policy in this handbook and detailed Ultra procedures in the plugin skills.',
58
- '',
59
- `- Workflow entry points: ${value.invocation}.`,
60
- `- Collaboration boundary: ${value.collaboration}. The current host remains primary and owns final verification.`,
61
- `- Coordination boundary: ${value.coordination}.`,
62
- '- Authority: `.ultra/state.db` is the only durable Ultra authority for baselines, changes, decision threads, tasks, sessions, events, incidents, projection state, telemetry, and review evidence. Generated JSON and Markdown are projections or workflow artifacts.',
63
- '- Baseline boundary: `task.init_project` classifies greenfield or brownfield work and records repository scope. Projection-only prior state uses the exact supported backup-first import returned by the authority check; migrated compatibility rows require explicit brownfield re-adoption. `baseline.record` owns evidence, verification, repository snapshot, and the gap ledger; `baseline.converge` requires explicit owner approval. New ordinary changes require a healthy ready baseline. Existing active work may continue with warnings, but baseline readiness blocks change convergence; normal drift reconciliation is health-checked atomically at archive. Only an explicitly approved incident break-glass may start without baseline readiness, and its archive creates a blocking reconciliation gap.',
64
- '- Decision boundary: project-bound owner choices use one resumable DB-backed decision thread. The host resolves observable facts autonomously, presents only the earliest unresolved decision with evidence and a recommendation, waits for the owner, and records the normalized decision rather than prompts or transcripts. A linked workflow cannot advance until blocking decisions are resolved and its owner-approved checkpoint is current.',
65
- '- Context Spine boundary: `change.context` compiles role/gate readiness, required references, a fresh-context budget, public seam, verification command, Change/task authority digests, allowed transitions, and any unique hard-recovery transition. File, token, and context-share budgets are advisory attention signals, not refusal gates. The host owns semantic recommendations. Hooks inject only the DB-derived `change.breadcrumb`; missing required evidence or stale execution context must be recompiled.',
66
- '- Specification learning boundary: stable implementation discoveries use approval-gated propose/approve-or-reject/apply transitions. Unresolved learning blocks convergence; review requires independent specification-fidelity and engineering-standards evidence.',
67
- '- Memory and graph boundary: Ultra Builder Pro does not capture prompts, transcripts, observations, summaries, cross-session memory, or code-graph content. Separately installed providers own that data; Ultra may store only their metadata references in a change context manifest.',
68
- '- Hook boundary: Ultra hooks observe workflow/change lifecycle only. Health/context may run when `.ultra/state.db` exists; advisory warnings never reject work, and an incomplete workflow never traps session stop. Direct projection protection remains authoritative; compact and subagent recovery stays active-workflow scoped. Generic command blocking and post-edit policy stay in user or repository governance.',
69
- '- Installation boundary: `ubp --doctor` is the read-only authority for installed asset provenance, content hashes, and host entry-point wiring. Project `system.doctor` diagnoses state and performs only authorized backup-first schema, projection, session, and archive-journal recovery; it never approves a baseline.',
70
- '- Agent boundary: the bundled review and debugging agents are bounded workers. They use the current checkout and parent-supplied context, do not own private persistent state, and never replace the primary agent.',
71
- '- Package boundary: only the twelve Ultra workflows, four internal review-rule skills, host-specific collaboration companions, and the minimal host bootstrap belong to this plugin. General browser, deployment, discovery, and framework skills must be installed from their owners.',
72
- END_MARKER,
73
- ].join('\n');
74
- }
75
-
76
- function markerCount(text, marker) {
77
- return text.split(marker).length - 1;
78
- }
79
-
80
- function replaceManagedBlock(existing, block) {
81
- const beginCount = markerCount(existing, BEGIN_MARKER);
82
- const endCount = markerCount(existing, END_MARKER);
83
- if (beginCount !== endCount || beginCount > 1) {
84
- throw new Error('handbook contains malformed or duplicate Ultra managed markers');
85
- }
86
- if (beginCount === 0) return null;
87
-
88
- const start = existing.indexOf(BEGIN_MARKER);
89
- const end = existing.indexOf(END_MARKER, start) + END_MARKER.length;
90
- return existing.slice(0, start) + block + existing.slice(end);
91
- }
92
-
93
- function replaceLegacyCodexSection(existing, block) {
94
- const heading = new RegExp(`^${LEGACY_HEADING.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[^\\n]*$`, 'm');
95
- const match = heading.exec(existing);
96
- if (!match) return null;
97
-
98
- const start = match.index;
99
- const remainder = existing.slice(start + match[0].length);
100
- const nextHeading = /^##\s+/m.exec(remainder);
101
- const end = nextHeading ? start + match[0].length + nextHeading.index : existing.length;
102
- const before = existing.slice(0, start).trimEnd();
103
- const after = existing.slice(end).trimStart();
104
- return `${before}${before ? '\n\n' : ''}${block}${after ? `\n\n${after}` : '\n'}`;
105
- }
106
-
107
- function mergeHandbook(existing, runtime) {
108
- host(runtime);
109
- const current = typeof existing === 'string' ? existing : '';
110
- const block = renderHandbook(runtime);
111
- const managed = replaceManagedBlock(current, block);
112
- if (managed !== null) return managed;
113
-
114
- if (runtime === 'codex') {
115
- const migrated = replaceLegacyCodexSection(current, block);
116
- if (migrated !== null) return migrated;
117
- }
118
-
119
- const before = current.trimEnd();
120
- return `${before}${before ? '\n\n' : ''}${block}\n`;
121
- }
122
-
123
- function resolveHandbookFile(runtime, { homeDir = os.homedir() } = {}) {
124
- return path.join(homeDir, ...host(runtime).handbook);
125
- }
126
-
127
- function timestamp() {
128
- return new Date().toISOString().replace(/[-:.]/g, '').replace(/\.\d{3}Z$/, 'Z');
129
- }
130
-
131
- function applyHandbook({ runtime, file, homeDir, now = timestamp() }) {
132
- const target = path.resolve(file || resolveHandbookFile(runtime, { homeDir }));
133
- const exists = fs.existsSync(target);
134
- const existing = exists ? fs.readFileSync(target, 'utf8') : '';
135
- const merged = mergeHandbook(existing, runtime);
136
- if (merged === existing) return { runtime, file: target, changed: false, backup: null };
137
-
138
- let backup = null;
139
- if (exists) {
140
- backup = `${target}.ubp-backup-${now}`;
141
- if (fs.existsSync(backup)) throw new Error(`handbook backup already exists: ${backup}`);
142
- writeAtomic(backup, existing);
143
- }
144
- writeAtomic(target, merged);
145
- return { runtime, file: target, changed: true, backup };
146
- }
147
-
148
- module.exports = {
149
- BEGIN_MARKER,
150
- END_MARKER,
151
- HOSTS,
152
- applyHandbook,
153
- mergeHandbook,
154
- renderHandbook,
155
- resolveHandbookFile,
156
- };