ultra-builder-pro-cli 0.22.0 → 0.23.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 (36) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +55 -25
  3. package/adapters/_shared/codex-assets.cjs +8 -3
  4. package/adapters/_shared/interaction-contract.cjs +13 -6
  5. package/adapters/_shared/runtime-assets.cjs +1 -1
  6. package/adapters/claude.js +1 -1
  7. package/adapters/kimi.js +1 -1
  8. package/adapters/opencode.js +1 -1
  9. package/docs/AGENT-CONTEXT.md +38 -40
  10. package/docs/ARCHITECTURE.md +29 -33
  11. package/docs/ARTIFACT-AUTHORITY.md +15 -16
  12. package/docs/ROADMAP.md +2 -2
  13. package/docs/RUNTIME-COMPAT-MATRIX.md +7 -6
  14. package/docs/STATE-DB-ACCESS-POLICY.md +14 -14
  15. package/docs/WORKFLOW-LIFECYCLE.md +39 -33
  16. package/mcp-server/lib/artifact-registry.cjs +25 -1
  17. package/mcp-server/lib/change-workflow.cjs +42 -6
  18. package/mcp-server/lib/ultra-facade.cjs +646 -0
  19. package/mcp-server/lib/workflow-state.cjs +166 -2
  20. package/mcp-server/server.cjs +73 -21
  21. package/package.json +1 -1
  22. package/skills/ultra-change/SKILL.md +38 -98
  23. package/skills/ultra-deliver/SKILL.md +42 -65
  24. package/skills/ultra-dev/SKILL.md +34 -88
  25. package/skills/ultra-doctor/SKILL.md +35 -57
  26. package/skills/ultra-init/SKILL.md +37 -78
  27. package/skills/ultra-plan/SKILL.md +52 -132
  28. package/skills/ultra-research/SKILL.md +41 -100
  29. package/skills/ultra-review/SKILL.md +25 -46
  30. package/skills/ultra-status/SKILL.md +21 -44
  31. package/skills/ultra-test/SKILL.md +25 -61
  32. package/skills/ultra-think/SKILL.md +29 -48
  33. package/skills/ultra-think/references/decision-dialogue.md +6 -4
  34. package/spec/cli-protocol.md +12 -0
  35. package/spec/mcp-tools.yaml +159 -0
  36. package/spec/schemas/mcp-tools.schema.json +1 -1
@@ -657,8 +657,9 @@ function inspectRunHealth(run, steps, rootDir) {
657
657
  const firstOpen = steps.find(
658
658
  (item) => item.required && !['completed', 'skipped'].includes(item.status),
659
659
  );
660
- if (!firstOpen) blockers.push('WORKFLOW_ACTIVE_WITHOUT_OPEN_STEP');
661
- else if (run.current_step !== firstOpen.step_id) {
660
+ if (!firstOpen) {
661
+ if (runMetadata.draft_dirty !== true) blockers.push('WORKFLOW_ACTIVE_WITHOUT_OPEN_STEP');
662
+ } else if (run.current_step !== firstOpen.step_id) {
662
663
  blockers.push(`WORKFLOW_CURRENT_STEP_INVALID:${run.current_step || 'missing'}`);
663
664
  }
664
665
  }
@@ -1196,6 +1197,166 @@ function startWorkflow(db, input = {}, { rootDir = process.cwd() } = {}) {
1196
1197
  return readWorkflow(db, id, { rootDir });
1197
1198
  }
1198
1199
 
1200
+ function abandonWorkflow(db, input = {}, { rootDir = process.cwd() } = {}) {
1201
+ const id = nonEmpty(input.id, 'id');
1202
+ const reason = nonEmpty(input.reason, 'reason');
1203
+ return ops.tx(db, () => {
1204
+ const run = readWorkflow(db, id, { rootDir });
1205
+ if (!run) throw new WorkflowStateError('WORKFLOW_NOT_FOUND', `workflow ${id} not found`);
1206
+ if (run.status === 'cancelled') return run;
1207
+ if (run.status === 'completed') {
1208
+ throw new WorkflowStateError(
1209
+ 'WORKFLOW_NOT_MUTABLE',
1210
+ `accepted workflow ${id} is immutable; create a new draft revision instead`,
1211
+ );
1212
+ }
1213
+ const metadata = {
1214
+ ...run.metadata,
1215
+ abandoned: {
1216
+ reason,
1217
+ abandoned_at: nowIso(),
1218
+ },
1219
+ };
1220
+ delete metadata.authority_invalidation;
1221
+ const ts = nowIso();
1222
+ db.prepare(
1223
+ `UPDATE workflow_runs
1224
+ SET status = 'cancelled', current_step = NULL, blockers_json = '[]',
1225
+ metadata_json = ?, completed_at = ?, updated_at = ?
1226
+ WHERE id = ?`,
1227
+ ).run(JSON.stringify(metadata), ts, ts, id);
1228
+ ops.appendEventInTx(db, {
1229
+ type: 'workflow_abandoned',
1230
+ task_id: run.task_id,
1231
+ change_id: run.change_id,
1232
+ payload: { workflow_id: id, kind: run.kind, reason },
1233
+ });
1234
+ return readWorkflow(db, id, { rootDir });
1235
+ });
1236
+ }
1237
+
1238
+ function prepareWorkflowCheckpoint(db, input = {}, { rootDir = process.cwd() } = {}) {
1239
+ const id = nonEmpty(input.id, 'id');
1240
+ const suppliedSteps = new Map(
1241
+ (Array.isArray(input.steps) ? input.steps : []).map((item) => [
1242
+ nonEmpty(item.step_id, 'steps.step_id'),
1243
+ item,
1244
+ ]),
1245
+ );
1246
+ const defaultEvidence = Array.isArray(input.evidence) && input.evidence.length > 0
1247
+ ? normalizeEvidence(input.evidence)
1248
+ : [{
1249
+ kind: 'checkpoint',
1250
+ ref: `workflow:${id}`,
1251
+ summary: 'The model recorded this semantic stage in one checkpoint.',
1252
+ }];
1253
+ return ops.tx(db, () => {
1254
+ const run = readWorkflow(db, id, { rootDir });
1255
+ if (!run) throw new WorkflowStateError('WORKFLOW_NOT_FOUND', `workflow ${id} not found`);
1256
+ if (run.status === 'completed') return { workflow: run, blockers: [], ready: true };
1257
+ if (run.status === 'cancelled') {
1258
+ throw new WorkflowStateError('WORKFLOW_NOT_MUTABLE', `workflow ${id} is cancelled`);
1259
+ }
1260
+ const blockers = [];
1261
+ const ts = nowIso();
1262
+ for (const workflowStep of run.steps) {
1263
+ if (!workflowStep.required) continue;
1264
+ const supplied = suppliedSteps.get(workflowStep.step_id) || {};
1265
+ const definition = WORKFLOW_DEFINITIONS[run.kind]
1266
+ .find((item) => item.id === workflowStep.step_id);
1267
+ const outputs = supplied.outputs === undefined
1268
+ ? workflowStep.outputs
1269
+ : normalizeOutputs(supplied.outputs, rootDir);
1270
+ const evidence = supplied.evidence === undefined
1271
+ ? (workflowStep.evidence.length > 0 ? workflowStep.evidence : defaultEvidence)
1272
+ : normalizeEvidence(supplied.evidence);
1273
+ const semanticRecords = supplied.semantic_records === undefined
1274
+ ? workflowStep.semantic_records
1275
+ : (run.kind === 'research'
1276
+ ? normalizeSemanticRecords(supplied.semantic_records, workflowStep.step_id, rootDir)
1277
+ : normalizeObjects(supplied.semantic_records, 'semantic_records'));
1278
+ if (definition.output_required && outputs.length === 0) {
1279
+ blockers.push(`WORKFLOW_OUTPUT_REQUIRED:${workflowStep.step_id}`);
1280
+ continue;
1281
+ }
1282
+ if (definition.evidence_required && evidence.length === 0) {
1283
+ blockers.push(`WORKFLOW_EVIDENCE_REQUIRED:${workflowStep.step_id}`);
1284
+ continue;
1285
+ }
1286
+ if (run.kind === 'research' && semanticRecords.length === 0) {
1287
+ blockers.push(`WORKFLOW_SEMANTIC_RECORDS_REQUIRED:${workflowStep.step_id}`);
1288
+ continue;
1289
+ }
1290
+ db.prepare(
1291
+ `UPDATE workflow_steps
1292
+ SET status = 'completed', evidence_json = ?, outputs_json = ?,
1293
+ decisions_json = ?, semantic_records_json = ?, blockers_json = '[]',
1294
+ started_at = COALESCE(started_at, ?), completed_at = ?, updated_at = ?
1295
+ WHERE run_id = ? AND step_id = ?`,
1296
+ ).run(
1297
+ JSON.stringify(evidence),
1298
+ JSON.stringify(outputs),
1299
+ JSON.stringify(supplied.decisions === undefined
1300
+ ? workflowStep.decisions
1301
+ : normalizeObjects(supplied.decisions, 'decisions')),
1302
+ JSON.stringify(semanticRecords),
1303
+ ts,
1304
+ ts,
1305
+ ts,
1306
+ id,
1307
+ workflowStep.step_id,
1308
+ );
1309
+ }
1310
+ const metadata = { ...run.metadata };
1311
+ delete metadata.authority_invalidation;
1312
+ metadata.draft_dirty = blockers.length > 0;
1313
+ db.prepare(
1314
+ `UPDATE workflow_runs
1315
+ SET status = ?, current_step = NULL, blockers_json = ?, metadata_json = ?, updated_at = ?
1316
+ WHERE id = ?`,
1317
+ ).run(
1318
+ blockers.length > 0 ? 'active' : 'ready',
1319
+ JSON.stringify(blockers),
1320
+ JSON.stringify(metadata),
1321
+ ts,
1322
+ id,
1323
+ );
1324
+ ops.appendEventInTx(db, {
1325
+ type: blockers.length > 0 ? 'workflow_checkpoint_rejected' : 'workflow_checkpoint_prepared',
1326
+ task_id: run.task_id,
1327
+ change_id: run.change_id,
1328
+ payload: { workflow_id: id, kind: run.kind, blockers },
1329
+ });
1330
+ return {
1331
+ workflow: readWorkflow(db, id, { rootDir }),
1332
+ blockers,
1333
+ ready: blockers.length === 0,
1334
+ };
1335
+ });
1336
+ }
1337
+
1338
+ function reopenWorkflowDraft(db, input = {}, { rootDir = process.cwd() } = {}) {
1339
+ const id = nonEmpty(input.id, 'id');
1340
+ const blocker = typeof input.blocker === 'string' && input.blocker.trim()
1341
+ ? input.blocker.trim()
1342
+ : 'CHECKPOINT_REJECTED';
1343
+ return ops.tx(db, () => {
1344
+ const run = readWorkflow(db, id, { rootDir });
1345
+ if (!run) throw new WorkflowStateError('WORKFLOW_NOT_FOUND', `workflow ${id} not found`);
1346
+ if (run.status === 'completed' || run.status === 'cancelled') return run;
1347
+ const metadata = { ...run.metadata, draft_dirty: true };
1348
+ delete metadata.authority_invalidation;
1349
+ const blockers = [...new Set([...(run.blockers || []), blocker])];
1350
+ db.prepare(
1351
+ `UPDATE workflow_runs
1352
+ SET status = 'active', blockers_json = ?, metadata_json = ?,
1353
+ updated_at = ?
1354
+ WHERE id = ?`,
1355
+ ).run(JSON.stringify(blockers), JSON.stringify(metadata), nowIso(), id);
1356
+ return readWorkflow(db, id, { rootDir });
1357
+ });
1358
+ }
1359
+
1199
1360
  function reviseWorkflow(db, input = {}, { rootDir = process.cwd() } = {}) {
1200
1361
  const id = nonEmpty(input.id, 'id');
1201
1362
  const replacementId = nonEmpty(input.replacement_id, 'replacement_id');
@@ -2948,6 +3109,9 @@ module.exports = {
2948
3109
  WorkflowStateError,
2949
3110
  insertWorkflowInTx,
2950
3111
  startWorkflow,
3112
+ abandonWorkflow,
3113
+ prepareWorkflowCheckpoint,
3114
+ reopenWorkflowDraft,
2951
3115
  readWorkflow,
2952
3116
  listWorkflows,
2953
3117
  recordWorkflowStep,
@@ -39,6 +39,7 @@ const workflows = require('./lib/workflow-state.cjs');
39
39
  const decisions = require('./lib/decision-dialogue.cjs');
40
40
  const doctor = require('./lib/doctor.cjs');
41
41
  const artifactRegistry = require('./lib/artifact-registry.cjs');
42
+ const ultraFacade = require('./lib/ultra-facade.cjs');
42
43
  const {
43
44
  readProjectBreadcrumb,
44
45
  renderProjectBreadcrumb,
@@ -137,19 +138,29 @@ const ARTIFACT_TOOLS = Object.freeze([
137
138
  'artifact.get',
138
139
  ]);
139
140
 
140
- const REGISTERED_TOOLS = Object.freeze([
141
+ const LEGACY_TOOLS = Object.freeze([
141
142
  ...TASK_TOOLS, ...SESSION_TOOLS, ...PLAN_TOOLS, ...BASELINE_TOOLS, ...CHANGE_TOOLS,
142
143
  ...DECISION_TOOLS,
143
144
  ...WORKFLOW_TOOLS,
144
145
  ...ARTIFACT_TOOLS,
145
146
  ...SYSTEM_TOOLS,
146
147
  ]);
148
+ const PUBLIC_TOOLS = ultraFacade.PUBLIC_TOOLS;
149
+ const REGISTERED_TOOLS = Object.freeze([...PUBLIC_TOOLS, ...LEGACY_TOOLS]);
147
150
 
148
151
  const STATELESS_TOOLS = new Set(['task.init_project']);
149
152
 
153
+ function isStatelessPublicCall(name, input = {}) {
154
+ return name === 'ultra.record'
155
+ && Array.isArray(input.entries)
156
+ && input.entries.length === 1
157
+ && input.entries[0]?.operation === 'task.init_project';
158
+ }
159
+
150
160
  // init_project owns its target project's schema, baseline seed, and first
151
161
  // projection internally. Do not enqueue a second projection on the caller DB.
152
162
  const MUTATING_TOOLS = new Set([
163
+ 'ultra.record', 'ultra.checkpoint', 'ultra.sync', 'ultra.session', 'ultra.archive',
153
164
  'task.create', 'task.update', 'task.delete', 'task.append_event', 'task.switch_tag',
154
165
  'task.parse_prd', 'task.expand',
155
166
  'task.ledger_import',
@@ -167,6 +178,7 @@ const MUTATING_TOOLS = new Set([
167
178
  ]);
168
179
 
169
180
  const TEAM_LEDGER_SYNC_TOOLS = new Set([
181
+ 'ultra.record', 'ultra.checkpoint', 'ultra.archive',
170
182
  'task.create', 'task.update', 'task.delete', 'task.parse_prd', 'task.expand',
171
183
  'plan.export',
172
184
  'baseline.start', 'baseline.record', 'baseline.converge',
@@ -651,10 +663,6 @@ async function dispatchTool(name, input, db, ctx = {}) {
651
663
  // caller must see a typed failure and later mutations must stop.
652
664
  throw error;
653
665
  }
654
- taskLedger.publishTaskLedger(db, {
655
- rootDir,
656
- reason: 'plan_accepted',
657
- });
658
666
  return {
659
667
  plan_path: publication.plan_path,
660
668
  plan_md_path: publication.plan_md_path,
@@ -969,6 +977,19 @@ function startServer({
969
977
  };
970
978
  const getDb = (toolName = null) => {
971
979
  if (!db) {
980
+ if (toolName === 'ultra.context') {
981
+ if (!fs.existsSync(authorityDbPath)) {
982
+ const error = new Error(
983
+ 'Ultra authority is not initialized; invoke ultra-init before reading project context',
984
+ );
985
+ error.code = 'STATE_DB_MISSING';
986
+ throw error;
987
+ }
988
+ db = openStateDb(authorityDbPath);
989
+ ensureSchemaVersion(db);
990
+ assertStateAuthority(db, rootDir, { importTeamLedger: false });
991
+ return db;
992
+ }
972
993
  const projectPaths = runtimePaths.pathsFor(rootDir);
973
994
  if ([projectPaths.legacyStateDbPath, projectPaths.stateDbPath].includes(authorityDbPath)) {
974
995
  // The packaged launcher has no explicit DB override. Its default path
@@ -1005,7 +1026,7 @@ function startServer({
1005
1026
  }
1006
1027
  planRecovery = planStore.recoverPlanPublications(db, { rootDir });
1007
1028
  }
1008
- if (toolName !== 'system.doctor'
1029
+ if (!['system.doctor', 'ultra.doctor'].includes(toolName)
1009
1030
  && (planRecovery?.pending > 0 || planRecovery?.issues?.length > 0)) {
1010
1031
  planRecovery = planStore.recoverPlanPublications(db, { rootDir });
1011
1032
  if (planRecovery.pending > 0 || planRecovery.issues.length > 0) {
@@ -1021,6 +1042,7 @@ function startServer({
1021
1042
  };
1022
1043
 
1023
1044
  const tools = loadRegisteredTools();
1045
+ const publicTools = tools.filter((tool) => PUBLIC_TOOLS.includes(tool.name));
1024
1046
  const ajv = buildAjv();
1025
1047
  const inputValidators = new Map();
1026
1048
  const outputValidators = new Map();
@@ -1035,7 +1057,7 @@ function startServer({
1035
1057
  );
1036
1058
 
1037
1059
  server.setRequestHandler('tools/list', async () => ({
1038
- tools: tools.map((t) => ({
1060
+ tools: publicTools.map((t) => ({
1039
1061
  name: t.name,
1040
1062
  description: t.description,
1041
1063
  inputSchema: t.input_schema,
@@ -1081,23 +1103,51 @@ function startServer({
1081
1103
  let result;
1082
1104
  let toolDb = null;
1083
1105
  try {
1084
- toolDb = STATELESS_TOOLS.has(name) ? null : getDb(name);
1106
+ toolDb = (STATELESS_TOOLS.has(name) || isStatelessPublicCall(name, args))
1107
+ ? null
1108
+ : getDb(name);
1085
1109
  if (toolDb) assertStateAuthority(toolDb, rootDir);
1086
1110
  if (toolDb && TEAM_LEDGER_SYNC_TOOLS.has(name)) {
1087
1111
  taskLedger.syncTaskLedger(toolDb, { rootDir });
1088
1112
  }
1089
- result = await dispatchTool(
1090
- name,
1091
- args,
1092
- toolDb,
1093
- {
1094
- rootDir,
1095
- sessionId,
1096
- projector: project,
1097
- markPlanRecoveryRequired,
1098
- },
1099
- );
1100
- if (name === 'system.doctor' && args.repair === true) {
1113
+ const callContext = {
1114
+ rootDir,
1115
+ sessionId,
1116
+ projector: project,
1117
+ markPlanRecoveryRequired,
1118
+ };
1119
+ const callLegacy = async (legacyName, legacyArgs = {}) => {
1120
+ if (!LEGACY_TOOLS.includes(legacyName)) {
1121
+ const error = new Error(`legacy operation ${legacyName} is not supported`);
1122
+ error.code = 'ULTRA_RECORD_OPERATION_UNSUPPORTED';
1123
+ throw error;
1124
+ }
1125
+ const validateLegacyInput = inputValidators.get(legacyName);
1126
+ if (!validateLegacyInput(legacyArgs)) {
1127
+ const error = new Error(ajv.errorsText(validateLegacyInput.errors));
1128
+ error.code = 'VALIDATION_ERROR';
1129
+ error.details = validateLegacyInput.errors;
1130
+ throw error;
1131
+ }
1132
+ const legacyResult = await dispatchTool(
1133
+ legacyName,
1134
+ legacyArgs,
1135
+ toolDb,
1136
+ callContext,
1137
+ );
1138
+ const validateLegacyOutput = outputValidators.get(legacyName);
1139
+ if (!validateLegacyOutput(legacyResult)) {
1140
+ const error = new Error(ajv.errorsText(validateLegacyOutput.errors));
1141
+ error.code = 'OUTPUT_SCHEMA_DRIFT';
1142
+ error.details = validateLegacyOutput.errors;
1143
+ throw error;
1144
+ }
1145
+ return legacyResult;
1146
+ };
1147
+ result = PUBLIC_TOOLS.includes(name)
1148
+ ? await ultraFacade.dispatch(name, args, toolDb, { ...callContext, callLegacy })
1149
+ : await dispatchTool(name, args, toolDb, callContext);
1150
+ if (['system.doctor', 'ultra.doctor'].includes(name) && args.repair === true) {
1101
1151
  planRecovery = result.repair?.plan_publications || null;
1102
1152
  }
1103
1153
  } catch (err) {
@@ -1122,7 +1172,7 @@ function startServer({
1122
1172
  projection_job_id: job.id,
1123
1173
  };
1124
1174
  if (own && own.incident_id) runtimeMeta.incident_id = own.incident_id;
1125
- } else if (toolDb && name === 'system.doctor' && args.repair === true) {
1175
+ } else if (toolDb && ['system.doctor', 'ultra.doctor'].includes(name) && args.repair === true) {
1126
1176
  runtimeMeta = {
1127
1177
  state_commit: 'committed',
1128
1178
  projection_status: result.checks.projections.status === 'pass' ? 'completed' : 'failed',
@@ -1260,6 +1310,8 @@ module.exports = {
1260
1310
  ARTIFACT_TOOLS,
1261
1311
  SYSTEM_TOOLS,
1262
1312
  REGISTERED_TOOLS,
1313
+ LEGACY_TOOLS,
1314
+ PUBLIC_TOOLS,
1263
1315
  STATELESS_TOOLS,
1264
1316
  MUTATING_TOOLS,
1265
1317
  appendHookLifecycleEvent,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ultra-builder-pro-cli",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
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",
@@ -3,105 +3,45 @@ name: ultra-change
3
3
  description: Capture or revise one bounded feature, fix, redesign, or incident contract against a ready Ultra baseline. Use when post-baseline work needs accepted intent before research, planning, or implementation.
4
4
  ---
5
5
 
6
- # Capture accepted change intent
6
+ # Capture accepted Change intent
7
7
 
8
- Change is the durable unit that connects later research, plan, implementation,
9
- verification, review, and baseline reconciliation. Capturing a change does not start
10
- those workflows or compile execution context.
11
-
12
- Read `../ultra-think/references/decision-dialogue.md` before asking a material question.
8
+ A Change is the durable unit connecting research, plan, implementation, verification,
9
+ review, documentation, and delivery. Its draft stays editable until a semantic
10
+ checkpoint accepts the downstream authority.
13
11
 
14
12
  ## Inspect and align
15
13
 
16
- 1. Read `system.doctor`, `task.ledger_get`, `baseline.get`, active changes, breadcrumb
17
- `accepted_intent`, decisions, and `change.breadcrumb`. Import a newer descendant
18
- checkpoint before capturing intent. Stop on a typed baseline, Change, task,
19
- ancestry, or active-session conflict.
20
- 2. Resume an existing change that matches the request. Do not duplicate authority.
21
- 3. Ordinary work requires a healthy ready baseline. An incident may use only an
22
- explicit `baseline_bypass` reason and approver; urgency is not approval.
23
- 4. Inspect the checkout and accepted baseline before asking the user. Resolve code and
24
- runtime facts yourself.
25
- 5. Convert the request into a Change Contract:
26
- - observable outcome and executable acceptance;
27
- - non-goals and public seams;
28
- - recovery strategy and verification;
29
- - unresolved material decisions;
30
- - documentation impact;
31
- - change kind and evidence-backed risk flags;
32
- - research disposition.
33
-
34
- Recommend `quick`, `standard`, `major`, or `incident` from actual scope and risk,
35
- together with the research disposition. A quick change may have no material risk,
36
- research obligation, or more than one task. MCP may reject a profile that contradicts
37
- hard risk invariants; it does not choose a replacement.
38
-
39
- After `change.create` or `change.update`, read back the complete Change Contract. When
40
- that write applies a durable decision, complete the decision thread with the change
41
- reference in `applied_refs`.
42
-
43
- Ask only when a choice changes accepted product intent, scope, public behavior,
44
- compatibility, security, material cost, external effects, or recovery. Use the host's
45
- native question UI when available. Normalize clear choices already in the user's
46
- request. When profile or research posture remains unresolved, present the recommended
47
- route and credible alternatives through that same UI, then stop. The user selects,
48
- modifies, delegates, or defers the semantic route. The model owns reversible
49
- implementation detail inside the accepted contract.
50
-
51
- ## Persist the contract
52
-
53
- When a durable alignment checkpoint is necessary, bind it to a compact artifact and
54
- pass its id as `alignment_thread_id`. Do not require a checkpoint for a complete,
55
- unambiguous request that already provides current authority.
56
-
57
- Call `change.create`. It records and completes exactly:
58
-
59
- 1. `bind-baseline`
60
- 2. `classify-change`
61
- 3. `record-intent`
62
-
63
- The returned change workflow must be `completed`; research, plan, context, dev, test,
64
- review, and deliver remain independent capabilities. Keep every Change-owned semantic
65
- artifact, finding, plan, context, test result, review result, documentation update,
66
- and progress projection below `.ultra/changes/active/<id>/`. Incidents maintain the
67
- structured diagnosis artifact there as well.
68
-
69
- `change.create` and `change.update` publish the portable Change Contract to
70
- `.ultra/tasks/tasks.json`, even before the Change owns tasks. The tracked Change
71
- artifacts carry semantic bodies; the checkpoint carries normalized baseline, Change,
72
- and task records with digest ancestry. Never edit either generated checkpoint or
73
- checkout-local projection directly.
74
-
75
- Before planning a standard or major change, write the accepted specification updates
76
- to the Change overlay and call `change.delta` with exact baseline id, repository
77
- revision, specification digests, decisions, non-goals, acceptance, documentation
78
- impact, unknowns, and mutation before/after digests. If accepted behavior truly does
79
- not change baseline semantics, record an empty typed delta with a specific
80
- `no_semantic_change_reason`; do not manufacture a mutation. Read back the registered
81
- delta and deterministic `progress.md`. This records proposed authority only and must
82
- not edit baseline specifications.
83
-
84
- Use `change.update` when accepted intent or evidence changes. Update the Change
85
- Contract, classification, research disposition, and docs impact together when their
86
- meaning is coupled. Downstream task, context, test, or review evidence affected by that
87
- change must become stale and be regenerated; never keep old evidence green by prompt
88
- assertion.
89
-
90
- If research is required, recommend `ultra-research` with the recorded disposition. If
91
- the contract is sufficiently evidenced, finish the typed delta and recommend
92
- `ultra-plan`. Direct Build skips Research only; it never skips Plan, DB-backed task
93
- contracts, or fresh task context. Return and wait for an explicit user invocation; do
94
- not start another workflow. `change.context` is compiled only for an actual plan,
95
- implementation, test, or review consumer.
96
-
97
- Stable implementation discoveries use the approval-gated specification-learning
98
- transitions. External memory and graph providers retain their own content; Ultra stores
99
- metadata references only.
100
-
101
- Return the normalized contract, change id and kind, research disposition, blockers,
102
- and `allowed_transitions`. Recommend a semantic next action from those transitions.
103
- If current intent does not already select it, present the recommendation and credible
104
- alternatives through the interaction protocol, then wait. MCP does not encode a
105
- semantic next action.
106
-
107
- Never invoke the recommended capability here; wait for an explicit user invocation.
14
+ 1. Call `ultra.context { stage: project, detail: full }`.
15
+ 2. Inspect or import the team checkpoint with `ultra.sync`. Stop only on a real
16
+ baseline, Change, task, ancestry, or active-session conflict.
17
+ 3. Reuse an existing matching Change. Do not duplicate authority.
18
+ 4. Convert the request into observable outcome, executable acceptance, non-goals,
19
+ public seams, recovery, verification, documentation impact, risk, and research
20
+ disposition.
21
+ 5. Recommend `quick`, `standard`, `major`, or `incident` from evidence. Ask only when
22
+ a choice changes accepted product intent, compatibility, security, material cost,
23
+ external effects, or recovery.
24
+
25
+ Read `../ultra-think/references/decision-dialogue.md` before asking. MCP may report
26
+ contradictory risk fields, but the model and owner choose the semantic correction.
27
+
28
+ ## Record the contract
29
+
30
+ Use one `ultra.record` batch for `change.create` or `change.update`, relevant
31
+ `decision.*`, and `change.delta` when the delta is already known. Every entry carries
32
+ a stable idempotency key. Read the result through `ultra.context`.
33
+
34
+ Keep every Change-owned semantic artifact below
35
+ `.ultra/changes/active/<change-id>/`. Standard and major Changes require a typed delta
36
+ before planning. A true no-semantic-change case records an explicit reason instead of
37
+ manufacturing a mutation.
38
+
39
+ Changing accepted intent reopens the draft and makes only genuinely dependent evidence
40
+ stale. `ready` is not an absorbing state. A semantic rejection returns diagnostics;
41
+ repair and retry the same draft. Use `workflow.abandon` only to preserve and cancel an
42
+ attempt intentionally.
43
+
44
+ Publish the durable Change contract with `ultra.sync { action: publish }` when it is a
45
+ useful team handoff. Recommend bounded `ultra-research` for a real evidence gap or
46
+ `ultra-plan` when the contract is sufficiently evidenced. Do not invoke the next
47
+ capability automatically.
@@ -1,71 +1,48 @@
1
1
  ---
2
2
  name: ultra-deliver
3
- description: Converge a verified Ultra change, reconcile baseline specifications, and archive it with recoverable local evidence. Use when implementation, testing, and review are current.
3
+ description: Converge a verified Ultra Change, reconcile baseline specifications, and archive it with recoverable local evidence. Use when implementation, testing, and review are current.
4
4
  ---
5
5
 
6
6
  # Converge and archive local authority
7
7
 
8
- Delivery closes Ultra's local change authority. It does not grant or perform commit,
9
- push, tag, registry publication, deployment, or another external effect.
10
-
11
- ## Bind current evidence
12
-
13
- 1. Read doctor, `task.ledger_get`, baseline, change, breadcrumb `accepted_intent`,
14
- decisions, tasks, test, review, learning, and current checkout. Import a newer
15
- descendant checkpoint before convergence; stop on any typed merge conflict.
16
- 2. Resume or start a deliver workflow bound to the change and baseline.
17
- 3. Require complete task evidence, a current test report, both review axes, current
18
- context, and resolved material decisions. Record `bind-evidence`.
19
-
20
- Evidence from another revision, task set, or change is invalid.
21
-
22
- ## Reconcile specifications
23
-
24
- Resolve every specification-learning candidate. Apply approved learning to its target
25
- inside the Change overlay; preserve rejection reasons. Read the current registered
26
- `change.delta` and verify every baseline anchor, payload digest, acceptance reference,
27
- and unknown.
28
-
29
- Write documentation updates below
30
- `.ultra/changes/active/<change-id>/documentation/`. Call
31
- `change.documentation_reconcile` for every Change: bind the current delta id and
32
- digest, exact before/after document digests, delta and acceptance references,
33
- verification, and each verified consumer. When no documentation changes, record an
34
- empty reconciliation with a specific `no_change_reason`. An unexplained orphan
35
- document is blocking. Record `reconcile-specifications`.
36
-
37
- Compile final convergence context and run the risk-selected candidate checks, install
38
- or doctor smoke when this change modifies distributed assets, public-seam verification,
39
- and recovery sanity. Record the immutable context under `verify-candidate`.
40
-
41
- Call `change.converge`; MCP derives readiness from durable dev, test, review,
42
- typed delta, documentation reconciliation, diagnosis, and current checkout evidence.
43
- Successful convergence publishes the current portable team checkpoint.
44
- Record
45
- `converge-authority` only when ready.
46
-
47
- Call `change.archive` with the summary. MCP preflights all target before states and
48
- overlay digests, applies the complete packet atomically, refreshes baseline authority,
49
- rebinds every registered artifact into the self-contained archive, and rolls back or
50
- resumes from its transaction journal after interruption. A partial success is not
51
- delivery. Archive publication refreshes the checkpoint with the archived Change and
52
- new baseline summary. Verify the refreshed baseline and archived packet, then record
53
- `archive-change`.
54
-
55
- Write `<archived-change-root>/delivery/<workflow-id>/report.json` with
56
- `ultra-delivery-report-v1`: archived change, baseline, HEAD, worktree and context
57
- digests, local checks, rollback notes, and timestamp. The report must not contain a
58
- release decision. Record it under `verify-delivery` and complete the workflow.
59
-
60
- ## External effects
61
-
62
- If the user separately requests commit, push, tag, publish, or deploy, handle that as a
63
- host-owned effect after Ultra delivery. Confirm only missing material authority, run
64
- the repository's release checks, and report remote evidence separately. Never infer an
65
- external effect from `ultra-deliver`, and never encode it as completed Ultra delivery
66
- state.
67
-
68
- Return archive and baseline state, evidence digests, recovery notes, residual risks,
69
- and allowed transitions.
70
-
71
- Never invoke the recommended capability here; wait for an explicit user invocation.
8
+ Delivery closes local Ultra authority. It never grants commit, push, tag, registry
9
+ publication, deployment, or another external effect.
10
+
11
+ ## Prepare the packet
12
+
13
+ 1. Call `ultra.context { stage: deliver, scope: { change_id }, detail: full }`.
14
+ 2. Import a newer team checkpoint with `ultra.sync`; stop on a real conflict.
15
+ 3. Require current task outcomes, test report, both review axes, accepted decisions,
16
+ typed delta, documentation reconciliation, and current checkout evidence.
17
+ 4. Resolve every specification-learning candidate. Keep updates inside the Change
18
+ overlay until archive.
19
+ 5. Write and register documentation reconciliation and an
20
+ `ultra-delivery-report-v1` report. The report binds Change, baseline, HEAD,
21
+ worktree/context digests, local checks, rollback guidance, and timestamp; it contains
22
+ no release decision.
23
+
24
+ Use one `ultra.record` batch for the reconciliation, report artifact, learning
25
+ resolutions, and other durable facts.
26
+
27
+ ## Archive once
28
+
29
+ Call `ultra.archive` once with the Change id, stable idempotency key, archive summary,
30
+ reconciliation fields, delivery evidence steps, and report output. The operation
31
+ internally:
32
+
33
+ - compiles or reuses convergence context;
34
+ - checks the deliver draft;
35
+ - derives convergence from current dev, test, review, delta, docs, and checkout;
36
+ - applies the overlay and archive through the recoverable filesystem/DB transaction;
37
+ - rebinds registered artifacts into a self-contained archive;
38
+ - completes the durable delivery checkpoint;
39
+ - publishes the updated team checkpoint.
40
+
41
+ A semantic rejection returns mutable blockers. Repair the same packet and retry. A
42
+ hard corruption, path, concurrency, or recovery error remains fail-closed and routes to
43
+ `ultra-doctor`.
44
+
45
+ Verify the archived packet, baseline, checkpoint, and rollback state. Handle any
46
+ separately authorized Git or release effect only after local delivery and report it as
47
+ external evidence, not Ultra delivery authority. Do not invoke another capability
48
+ automatically.