Skip to content

Commit 0958350

Browse files
committed
feat: align semantic decisions across Ultra hosts
Cause: host-specific question surfaces and checkpoint-only decision closure could blur user intent ownership and stall unrelated work. Scope: add the four-host interaction contract, owner-selected research/change/plan routing, decision.complete, schema 19 migration, non-blocking decision gates, documentation, regression coverage, and the v0.20.0 release metadata. Verification: npm run verify:release; npm pack --dry-run --ignore-scripts --json; Skill Creator validation for the five affected workflow skills; git diff --cached --check. Residual risk: native structured-question availability remains host-version and mode dependent; the contract falls back to direct interaction when permitted and otherwise leaves intent unanswered. Rollback: revert this commit for source behavior; schema upgrades are backup-first and existing project authority can be restored from the managed pre-migration backup.
1 parent 1d63892 commit 0958350

50 files changed

Lines changed: 1161 additions & 215 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.20.0] — 2026-07-28
11+
12+
### Added
13+
14+
- Added schema 19 and `decision.complete` so settled normalized intent reaches a
15+
terminal lifecycle state without fabricating a second user approval or an
16+
artifact checkpoint. Existing settled schema-18 threads migrate safely.
17+
18+
### Changed
19+
20+
- Unified semantic selection across Claude Code, Codex, OpenCode, and Kimi Code
21+
as `inspect -> suggest -> ask if unresolved -> normalize -> persist`, using
22+
each host's native structured question surface with a direct-question
23+
fallback.
24+
- Made research coverage and planning posture owner-selected after model
25+
recommendation while preserving autonomous fact finding, synthesis,
26+
decomposition, and reversible implementation judgment.
27+
- Stopped requiring a ceremonial decision checkpoint after every normalized
28+
answer. Artifact checkpoints remain explicit digest-bound freshness gates.
29+
- Made unanswered non-blocking follow-ups advisory instead of global workflow
30+
gates; unanswered blocking choices and blocking deferrals still fail closed.
31+
1032
## [0.19.1] — 2026-07-26
1133

1234
### Fixed
@@ -817,7 +839,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
817839
skill manifest, CLI protocol + mapping table; 5 spec validators.
818840
- **Phase 0 — skeleton**: multi-runtime installer scaffolding.
819841

820-
[Unreleased]: https://github.com/rocky2431/ultra-builder-pro-cli/compare/v0.19.1...HEAD
842+
[Unreleased]: https://github.com/rocky2431/ultra-builder-pro-cli/compare/v0.20.0...HEAD
843+
[0.20.0]: https://github.com/rocky2431/ultra-builder-pro-cli/compare/v0.19.1...v0.20.0
821844
[0.19.1]: https://github.com/rocky2431/ultra-builder-pro-cli/compare/v0.19.0...v0.19.1
822845
[0.19.0]: https://github.com/rocky2431/ultra-builder-pro-cli/compare/v0.18.0...v0.19.0
823846
[0.18.0]: https://github.com/rocky2431/ultra-builder-pro-cli/compare/v0.17.1...v0.18.0

README.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ The responsibility split is deliberate:
7373

7474
| Owner | Responsibility |
7575
|---|---|
76-
| **User** | Product intent, material scope and trade-offs, destructive actions, publishing and deployment authorization |
77-
| **Host model** | Fact-finding, synthesis, research coverage, route recommendation, reversible implementation decisions |
76+
| **User** | Product intent, semantic route selection, material scope and trade-offs, risk acceptance, destructive actions, publishing and deployment authorization |
77+
| **Host model** | Fact-finding, synthesis, research-coverage and route recommendations, reversible implementation decisions |
7878
| **Ultra MCP** | Durable state, evidence references, digests, freshness, locks, valid transitions and hard recovery |
7979
| **Host adapter** | Native Skill discovery, user questions, tool invocation, installation, and runtime wiring |
8080
| **Hooks** | Fast lifecycle observation, current breadcrumb injection, and protection of generated projections |
@@ -189,7 +189,7 @@ Initialization:
189189
- identifies the repository root and scope;
190190
- classifies the repository as greenfield, brownfield, or migrated;
191191
- initializes Git when needed;
192-
- creates `.ultra/` and schema 18 project authority;
192+
- creates `.ultra/` and schema 19 project authority;
193193
- verifies that the scaffold and database can be read back;
194194
- completes without silently starting research, creating a commit, adding a
195195
remote, or pushing anything.
@@ -231,9 +231,11 @@ product story. It builds a current-system baseline from:
231231
- APIs, data, permissions, integrations, deployment, and recovery seams;
232232
- known failures, documentation drift, technical debt, and unresolved unknowns.
233233

234-
During `ultra-research`, the host model selects the smallest evidence-backed set
235-
of applicable catalog areas. Every included area receives one explicit
236-
disposition:
234+
During `ultra-research`, the host model inspects current evidence and recommends
235+
the smallest sufficient route. The owner selects, modifies, delegates, or
236+
defers it through the host-native question surface unless the current request
237+
already resolves the route. The normalized accepted coverage is then stored in
238+
`.ultra/state.db`. Every included area receives one explicit disposition:
237239

238240
- `execute` — produce fresh evidence;
239241
- `verify_existing` — validate an existing artifact;
@@ -243,7 +245,8 @@ disposition:
243245

244246
The catalog is not a mandatory document set or questionnaire. Omitted areas
245247
create no workflow rows; an explicit exclusion is recorded only when retaining
246-
that rationale is useful.
248+
that rationale is useful. MCP validates state, evidence, and transitions; it
249+
does not store or prove the preceding UI interaction.
247250

248251
Older projection-only Ultra projects are preserved and routed through a
249252
backup-first migration or rebaseline. Use `ultra-doctor` when initialization

adapters/_shared/interaction-contract.cjs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,37 +4,44 @@ const SURFACES = Object.freeze({
44
claude: Object.freeze({
55
primary: 'AskUserQuestion',
66
fallback: 'direct_user_interaction',
7+
availability: 'interactive_session',
78
}),
89
codex: Object.freeze({
910
primary: 'request_user_input',
1011
fallback: 'direct_user_interaction',
12+
availability: 'current_mode_exposes_tool',
1113
}),
1214
opencode: Object.freeze({
1315
primary: 'question',
1416
fallback: 'direct_user_interaction',
17+
availability: 'question_permission_not_denied',
1518
}),
1619
kimi: Object.freeze({
17-
primary: 'native_question_surface',
20+
primary: 'AskUserQuestion',
1821
fallback: 'direct_user_interaction',
22+
availability: 'interactive_non_auto_mode',
1923
}),
2024
});
2125

2226
function interactionContract(runtime) {
2327
const question = SURFACES[runtime];
2428
if (!question) throw new Error(`unsupported interaction runtime: ${runtime}`);
2529
return {
26-
schema_version: '1.0',
30+
schema_version: '1.1',
2731
runtime,
2832
authority: {
2933
user: [
3034
'product_intent',
3135
'material_scope_and_tradeoffs',
36+
'semantic_route_selection',
37+
'risk_acceptance',
3238
'destructive_or_external_effect_authorization',
3339
],
3440
host_model: [
3541
'fact_finding_and_synthesis',
3642
'reversible_implementation_judgment',
37-
'coverage_risk_and_route_recommendation',
43+
'semantic_route_recommendation',
44+
'accepted_intent_normalization',
3845
],
3946
ultra_mcp: [
4047
'durable_state_and_evidence_refs',
@@ -44,10 +51,25 @@ function interactionContract(runtime) {
4451
},
4552
interaction: {
4653
question_surface: question,
54+
semantic_selection_flow: [
55+
'inspect',
56+
'suggest',
57+
'ask_if_unresolved',
58+
'normalize',
59+
'persist',
60+
],
4761
dependent_decisions: 'one_at_a_time',
4862
independent_low_load_questions: { maximum: 3 },
4963
explicit_current_intent: 'normalize_without_reconfirmation',
50-
unavailable_native_surface: 'use_direct_interaction',
64+
dismissed_question: 'remain_unanswered',
65+
unavailable_native_surface: 'use_direct_interaction_if_permitted',
66+
host_forbids_interaction: 'remain_unanswered',
67+
},
68+
persistence: {
69+
accepted_intent: 'trust_as_current_authority',
70+
user_interaction_proof: 'not_required',
71+
raw_prompt_or_transcript: 'never_store',
72+
pending_question: 'store_only_when_recovery_requires_it',
5173
},
5274
routing: {
5375
semantic_recommendation_owner: 'host_model',

adapters/_shared/tests/interaction-contract.test.cjs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,40 @@ const {
88
} = require('../interaction-contract.cjs');
99

1010
test('every supported host receives the same authority split with a native question surface', () => {
11+
const expectedSurfaces = {
12+
claude: 'AskUserQuestion',
13+
codex: 'request_user_input',
14+
opencode: 'question',
15+
kimi: 'AskUserQuestion',
16+
};
17+
const expectedAvailability = {
18+
claude: 'interactive_session',
19+
codex: 'current_mode_exposes_tool',
20+
opencode: 'question_permission_not_denied',
21+
kimi: 'interactive_non_auto_mode',
22+
};
1123
for (const runtime of ['claude', 'codex', 'opencode', 'kimi']) {
1224
const contract = interactionContract(runtime);
1325
assert.equal(contract.runtime, runtime);
26+
assert.equal(contract.interaction.question_surface.primary, expectedSurfaces[runtime]);
1427
assert.equal(contract.interaction.question_surface.primary, SURFACES[runtime].primary);
1528
assert.equal(contract.interaction.question_surface.fallback, 'direct_user_interaction');
29+
assert.equal(contract.interaction.question_surface.availability, expectedAvailability[runtime]);
30+
assert.deepEqual(contract.interaction.semantic_selection_flow, [
31+
'inspect', 'suggest', 'ask_if_unresolved', 'normalize', 'persist',
32+
]);
33+
assert.equal(contract.interaction.dismissed_question, 'remain_unanswered');
34+
assert.equal(
35+
contract.interaction.unavailable_native_surface,
36+
'use_direct_interaction_if_permitted',
37+
);
38+
assert.equal(contract.interaction.host_forbids_interaction, 'remain_unanswered');
39+
assert.equal(contract.persistence.accepted_intent, 'trust_as_current_authority');
40+
assert.equal(contract.persistence.user_interaction_proof, 'not_required');
41+
assert.equal(contract.persistence.raw_prompt_or_transcript, 'never_store');
42+
assert.ok(contract.authority.user.includes('semantic_route_selection'));
43+
assert.ok(contract.authority.host_model.includes('semantic_route_recommendation'));
44+
assert.ok(!contract.authority.host_model.includes('semantic_route_selection'));
1645
assert.equal(contract.routing.semantic_recommendation_owner, 'host_model');
1746
assert.equal(contract.routing.durable_recommendation_authority, false);
1847
assert.equal(contract.routing.hard_invariant_field, 'required_transition');

adapters/tests/claude.test.cjs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ test('install builds the Claude-native plugin from the explicit Ultra allowlist'
3838
assert.ok(fs.existsSync(path.join(target, 'hooks', 'hooks.json')));
3939
assert.ok(fs.existsSync(path.join(target, '.mcp.json')));
4040
assert.ok(fs.existsSync(path.join(target, 'agents', 'code-reviewer.md')));
41+
const interaction = JSON.parse(
42+
fs.readFileSync(path.join(target, 'spec', 'interaction-contract.json'), 'utf8'),
43+
);
44+
assert.equal(interaction.interaction.question_surface.primary, 'AskUserQuestion');
45+
assert.equal(interaction.interaction.question_surface.availability, 'interactive_session');
46+
assert.equal(interaction.persistence.user_interaction_proof, 'not_required');
4147

4248
const hooks = JSON.parse(fs.readFileSync(path.join(target, 'hooks', 'hooks.json'), 'utf8'));
4349
const serialized = JSON.stringify(hooks);

adapters/tests/codex.test.cjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,8 +267,8 @@ test('plugin declares current Codex hooks and a project-local Ultra MCP server',
267267
const upstreamSpec = yaml.load(fs.readFileSync(path.join(layout.pluginRoot, 'spec', 'upstream-mcp-tools.yaml'), 'utf8'));
268268
const capabilityMap = JSON.parse(fs.readFileSync(path.join(layout.pluginRoot, 'spec', 'codex-capability-map.json'), 'utf8'));
269269
const interaction = JSON.parse(fs.readFileSync(path.join(layout.pluginRoot, 'spec', 'interaction-contract.json'), 'utf8'));
270-
assert.equal(liveSpec.tools.length, 50);
271-
assert.equal(upstreamSpec.tools.length, 50);
270+
assert.equal(liveSpec.tools.length, 51);
271+
assert.equal(upstreamSpec.tools.length, 51);
272272
assert.deepEqual(upstreamSpec.tools.map((tool) => tool.name).sort(), liveSpec.tools.map((tool) => tool.name).sort());
273273
assert.deepEqual(capabilityMap.live_mcp_tools.sort(), liveSpec.tools.map((tool) => tool.name).sort());
274274
assert.equal(Object.keys(capabilityMap.codex_native_replacements).length, 9);

adapters/tests/kimi.test.cjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ test('Kimi manifest exposes explicit native skills, commands, hooks, and MCP wit
9292
}
9393
assert.ok(fs.existsSync(path.join(pluginRoot, 'hooks', 'context_spine.py')));
9494
assert.doesNotMatch(hookText, /memory|recall|journal|prompt[_ -]?capture|block_dangerous|post_edit_guard/i);
95+
const interaction = readJson(path.join(pluginRoot, 'spec', 'interaction-contract.json'));
96+
assert.equal(interaction.interaction.question_surface.primary, 'AskUserQuestion');
97+
assert.equal(interaction.interaction.question_surface.availability, 'interactive_non_auto_mode');
9598

9699
const commands = fs.readdirSync(path.join(pluginRoot, 'commands')).sort();
97100
assert.deepEqual(commands, CORE_PUBLIC_SKILLS.map((name) => `${name}.md`).sort());

adapters/tests/opencode.test.cjs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,16 @@ test('install writes a schema-safe opencode.json and keeps ownership outside hos
183183
assert.ok(Array.isArray(config.mcp[opencode.MCP_SERVER_NAME].command));
184184
assert.equal('_ubp_manifest' in config, false);
185185
assert.ok(fs.existsSync(path.join(target, opencode.BUNDLE_DIR, '.ubp-managed')));
186+
const interaction = JSON.parse(fs.readFileSync(
187+
path.join(target, opencode.BUNDLE_DIR, 'spec', 'interaction-contract.json'),
188+
'utf8',
189+
));
190+
assert.equal(interaction.interaction.question_surface.primary, 'question');
191+
assert.equal(
192+
interaction.interaction.question_surface.availability,
193+
'question_permission_not_denied',
194+
);
195+
assert.equal(interaction.persistence.user_interaction_proof, 'not_required');
186196

187197
const plugin = fs.readFileSync(path.join(target, 'plugins', 'ultra-builder-pro.js'), 'utf8');
188198
assert.match(plugin, /experimental\.chat\.system\.transform/);

commands/ultra-research.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
description: Build evidence with model-selected semantic coverage and adaptive user alignment
2+
description: Build evidence with recommended, owner-selected semantic coverage and adaptive alignment
33
argument-hint: "[scope-or-question]"
44
workflow-ref: "@skills/ultra-research/SKILL.md"
55
---

docs/AGENT-CONTEXT.md

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ reports are projections or workflow artifacts.
4444

4545
## 3. Live MCP and declared contracts
4646

47-
`spec/mcp-tools.yaml` declares and the bundled server registers 50 tools across
47+
`spec/mcp-tools.yaml` declares and the bundled server registers 51 tools across
4848
eight families:
4949

5050
| Family | Live tools |
@@ -53,15 +53,16 @@ eight families:
5353
| `task.*` | create, update, list, get, switch_tag, delete, init_project, expand, parse_prd, dependency_topo, append_event, subscribe_events |
5454
| `session.*` | spawn, close, get, list, admission_check, heartbeat, subscribe_events |
5555
| `change.*` | create, update, get, list, context, breadcrumb, learning_propose, learning_resolve, converge, archive |
56-
| `decision.*` | thread_start, get, list, open, resolve, delegate, defer, supersede, checkpoint |
56+
| `decision.*` | thread_start, get, list, open, resolve, delegate, defer, supersede, complete, checkpoint |
5757
| `workflow.*` | start, get, list, step, complete |
5858
| `system.*` | doctor |
5959
| `plan.*` | export, get |
6060

6161
Review, repository impact discovery, skill loading, and decision presentation remain
6262
host-native capabilities rather than fake MCP contracts. MCP stores only normalized
63-
decision authority and checkpoints; it never generates questions or retains prompts
64-
and transcripts. The generated Codex capability map documents host replacements.
63+
decision authority, pending-question recovery state, lifecycle completion, and optional
64+
checkpoints; it never generates questions or retains prompts and transcripts. The
65+
generated Codex capability map documents host replacements.
6566

6667
The complete write, transition, invalidation, and recovery contract lives in
6768
[`WORKFLOW-LIFECYCLE.md`](./WORKFLOW-LIFECYCLE.md).
@@ -79,11 +80,13 @@ review, and deliver route back to the same thread instead of deciding for the ow
7980

8081
One partial unique index permits only one open item per thread. `decision.resolve`,
8182
`decision.delegate`, and `decision.defer` preserve the source of authority;
82-
`decision.supersede` preserves history when evidence or intent changes. Prepare and
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.
83+
`decision.supersede` preserves history when evidence or intent changes.
84+
`decision.complete` closes settled normalized state without manufacturing an approval
85+
receipt. Prepare and confirm checkpointing remain optional and bind only a material
86+
accepted cluster to current artifact digests when interruption recovery needs that
87+
boundary. Status and breadcrumb return only the current question plus allowed and
88+
mechanically required transitions, so recovery does not require replaying conversation
89+
history.
8790

8891
## 5. Context Spine contract
8992

0 commit comments

Comments
 (0)