ultra-builder-pro-cli 0.9.0 → 0.10.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 (52) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +11 -6
  3. package/adapters/_shared/codex-assets.cjs +10 -0
  4. package/adapters/_shared/handbook.cjs +2 -0
  5. package/adapters/_shared/runtime-assets.cjs +1 -0
  6. package/adapters/_shared/tests/handbook.test.cjs +3 -0
  7. package/adapters/_shared/tests/runtime-assets.test.cjs +1 -0
  8. package/adapters/kimi.js +2 -2
  9. package/adapters/opencode.js +46 -17
  10. package/adapters/tests/claude.test.cjs +4 -1
  11. package/adapters/tests/codex.test.cjs +4 -3
  12. package/adapters/tests/kimi.test.cjs +6 -2
  13. package/adapters/tests/opencode.test.cjs +46 -3
  14. package/docs/AGENT-CONTEXT.md +34 -12
  15. package/docs/ARCHITECTURE.md +31 -5
  16. package/docs/PLAN.zh-CN.md +3 -2
  17. package/docs/ROADMAP.md +5 -4
  18. package/docs/RUNTIME-COMPAT-MATRIX.md +7 -5
  19. package/docs/USER-HANDBOOK-CONTRACT.md +9 -2
  20. package/hooks/README.md +6 -4
  21. package/hooks/active_task_context.py +14 -0
  22. package/hooks/context_spine.py +218 -0
  23. package/hooks/health_check.py +1 -1
  24. package/hooks/workflow_context.py +16 -123
  25. package/hooks/workflow_resume.py +21 -0
  26. package/mcp-server/lib/change-workflow.cjs +89 -11
  27. package/mcp-server/lib/change-workflow.test.cjs +202 -7
  28. package/mcp-server/lib/context-spine.cjs +331 -0
  29. package/mcp-server/lib/context-spine.test.cjs +73 -0
  30. package/mcp-server/lib/spec-learning.cjs +191 -0
  31. package/mcp-server/lib/spec-learning.test.cjs +62 -0
  32. package/mcp-server/lib/state-db.cjs +72 -10
  33. package/mcp-server/lib/state-db.test.cjs +7 -2
  34. package/mcp-server/server.cjs +31 -1
  35. package/mcp-server/tests/db-ops.test.cjs +3 -3
  36. package/mcp-server/tests/server-contract.test.cjs +74 -0
  37. package/mcp-server/tests/state-db.test.cjs +20 -3
  38. package/package.json +1 -1
  39. package/skills/learn/SKILL.md +1 -0
  40. package/skills/ultra-change/SKILL.md +91 -118
  41. package/skills/ultra-deliver/SKILL.md +93 -221
  42. package/skills/ultra-dev/SKILL.md +81 -310
  43. package/skills/ultra-doctor/SKILL.md +1 -0
  44. package/skills/ultra-init/SKILL.md +1 -0
  45. package/skills/ultra-plan/SKILL.md +72 -292
  46. package/skills/ultra-review/SKILL.md +79 -407
  47. package/skills/ultra-status/SKILL.md +55 -168
  48. package/skills/ultra-test/SKILL.md +64 -183
  49. package/skills/ultra-think/SKILL.md +1 -0
  50. package/spec/cli-protocol.md +3 -0
  51. package/spec/mcp-tools.yaml +133 -4
  52. package/spec/schemas/state-db.sql +31 -0
package/CHANGELOG.md CHANGED
@@ -7,6 +7,53 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.10.0] — 2026-07-18
11
+
12
+ ### Added
13
+
14
+ - Added Context Manifest v2 and a DB-derived Context Spine across planning,
15
+ implementation, checking, review, convergence, and recovery. Context snapshots
16
+ now carry role, gate, readiness blockers, bounded references and digests,
17
+ fresh-context budgets, a public seam, an exact verification command, and one
18
+ deterministic next action.
19
+ - Added `change.breadcrumb`, `change.learning_propose`, and
20
+ `change.learning_resolve`, advancing the live MCP contract to 32 tools and
21
+ `.ultra/state.db` to schema 10.0 with 16 tables.
22
+ - Added approval-gated specification-learning candidates. Proposed or approved
23
+ discoveries block convergence until rejected or applied to a declared baseline
24
+ target, and `change.get` exposes their authoritative state.
25
+ - Added independent `spec_fidelity` and `engineering_standards` review evidence,
26
+ plus public-seam and exact red/green signal fields for convergence.
27
+
28
+ ### Changed
29
+
30
+ - Rebuilt the daily harness around one status router, one highest-value alignment
31
+ question, fresh-context tracer bullets, explicit expand contracts, independent
32
+ check/review contexts, and convergence before release.
33
+ - Reduced the seven core execution workflow prompts from 1,922 to 758 lines and
34
+ added a 220-line ceiling for every public Ultra skill so host prompts cannot
35
+ silently accumulate duplicated procedure. All twelve public workflows are now
36
+ explicitly user-invocable; four rule skills remain agent-only.
37
+ - Session-start, pre-edit, and recovery hooks now share one read-only
38
+ `context_spine.py` helper and inject only the compact DB breadcrumb. Intent,
39
+ transcript, memory-provider, and graph-provider payloads never enter hook context.
40
+ - OpenCode now consumes the DB-generated Context Manifest v2 projection, strips
41
+ provider/intent content, and fails context readiness when its compiled HEAD
42
+ differs from the current repository.
43
+ - Updated Claude Code, Codex, OpenCode, and Kimi Code native renderings, managed
44
+ handbook blocks, MCP mapping, architecture, and compatibility documentation.
45
+
46
+ ### Fixed
47
+
48
+ - Prevented plan-role task context from becoming ready without an execution
49
+ contract, public seam, and verification command.
50
+ - Prevented breadcrumbs from reusing a snapshot after task state or git HEAD
51
+ changed, and made an explicitly missing change id fail closed.
52
+ - Added auditable 9.1 → 10.0 migration history while preserving existing runtime
53
+ rows, references, projections, and foreign-key integrity.
54
+ - Corrected hook conformance to package `context_spine.py` as an imported helper
55
+ without registering it as a duplicate lifecycle hook.
56
+
10
57
  ## [0.9.0] — 2026-07-17
11
58
 
12
59
  ### Added
package/README.md CHANGED
@@ -8,7 +8,7 @@ authoritative `.ultra/state.db`.
8
8
 
9
9
  <div align="center">
10
10
 
11
- [![Version](https://img.shields.io/badge/version-0.9.0-blue)](./CHANGELOG.md)
11
+ [![Version](https://img.shields.io/badge/version-0.10.0-blue)](./CHANGELOG.md)
12
12
  [![Tests](https://img.shields.io/badge/tests-passing-brightgreen)](#verification)
13
13
  [![License: MIT](https://img.shields.io/badge/license-MIT-green)](./LICENSE)
14
14
  [![Node](https://img.shields.io/badge/node-%E2%89%A522-informational)](./package.json)
@@ -36,8 +36,10 @@ authoritative `.ultra/state.db`.
36
36
  - **Runs real PRDs end-to-end.** `task.parse_prd` → `lib/topo.cjs` waves →
37
37
  `.ultra/execution-plan.json` → parallel worktree sessions → auto-merge back.
38
38
  - **Keeps daily work convergent.** `/ultra-change` creates a bounded delta and
39
- context manifest; `/ultra-deliver` requires docs/test/review evidence and
40
- baseline reconciliation before archive. `/ultra-doctor` reports incidents,
39
+ Context Manifest v2; the Context Spine assigns a role/gate, verifies required
40
+ references and fresh-context budget, exposes one DB-derived breadcrumb, and
41
+ routes a single next action. `/ultra-deliver` requires applied specification
42
+ learning plus docs/test/two-axis review evidence before archive. `/ultra-doctor` reports incidents,
41
43
  projection lag, orphan sessions, and backup-first mechanical recovery.
42
44
  Incident changes additionally require a five-section `diagnosis.md` covering
43
45
  reproduction, hypotheses, root cause, regression test, and recovery.
@@ -91,7 +93,7 @@ for per-runtime capabilities.
91
93
  The layers share one `.ultra/state.db`, but the CLI is not a mirror of every MCP
92
94
  tool: continuous change mutations are MCP-only and fail closed. See
93
95
  [`docs/ARCHITECTURE.md`](./docs/ARCHITECTURE.md) for the full contract and
94
- [`spec/cli-protocol.md`](./spec/cli-protocol.md) for the 29 live contracts.
96
+ [`spec/cli-protocol.md`](./spec/cli-protocol.md) for the 32 live contracts.
95
97
  Review, impact discovery, skill resolution, and user interaction stay on each
96
98
  Host's native surfaces; the generated Codex capability map documents those
97
99
  replacements without advertising non-existent MCP tools.
@@ -137,8 +139,11 @@ ultra-tools system doctor
137
139
 
138
140
  Or let the skills drive it. In Codex, invoke
139
141
  `$ultra-builder-pro:ultra-plan` → `$ultra-builder-pro:ultra-dev` →
140
- `$ultra-builder-pro:ultra-status`; after the baseline is delivered, daily work
141
- starts with `$ultra-builder-pro:ultra-change`. Kimi uses the corresponding
142
+ `$ultra-builder-pro:ultra-test` → `$ultra-builder-pro:ultra-review` →
143
+ `$ultra-builder-pro:ultra-deliver`; after the baseline is delivered, daily work
144
+ starts with `$ultra-builder-pro:ultra-change`, while
145
+ `$ultra-builder-pro:ultra-status` reads the compact authoritative breadcrumb.
146
+ Kimi uses the corresponding
142
147
  `/ultra-builder-pro:ultra-*` namespace. Other runtimes retain their native
143
148
  command form.
144
149
 
@@ -467,6 +467,16 @@ and does not require an interactive MCP or legacy CLI menu.
467
467
  }
468
468
 
469
469
  if (skillName === 'ultra-review') {
470
+ text = text.replace(
471
+ '# $ultra-builder-pro:ultra-review - Ultra Review System',
472
+ `# $ultra-builder-pro:ultra-review - Ultra Review System
473
+
474
+ ## Codex native worker contract
475
+
476
+ Use the installed native Codex custom agents as bounded review workers. Dispatch independent
477
+ workers concurrently, keep the current Codex task primary, and treat their JSON artifacts—not
478
+ their conversational transcripts—as review evidence.`,
479
+ );
470
480
  text = text.replaceAll('### Phase 3: Background Execution', '### Phase 3: Parallel Native Agent Execution');
471
481
  text = text.replaceAll('**Step 4b: Launch coordinator in background:**', '**Step 4b: Delegate coordination:**');
472
482
  text = text.replaceAll('in **background mode** (`native parallel execution`)', 'concurrently with native Codex subagent orchestration');
@@ -60,6 +60,8 @@ function renderHandbook(runtime) {
60
60
  `- Collaboration boundary: ${value.collaboration}. The current host remains primary and owns final verification.`,
61
61
  `- Coordination boundary: ${value.coordination}.`,
62
62
  '- Authority: `.ultra/state.db` is the only durable Ultra authority for changes, tasks, sessions, events, incidents, projection state, telemetry, and review evidence. Generated JSON and Markdown are projections or workflow artifacts.',
63
+ '- Context Spine boundary: `change.context` compiles role/gate readiness, bounded references, fresh-context budget, public seam, verification command, and one next action. Hooks inject only the DB-derived `change.breadcrumb`; stale or blocked context must be recompiled.',
64
+ '- 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.',
63
65
  '- 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.',
64
66
  '- Hook boundary: Ultra hooks observe workflow/change lifecycle only. Health/context may run when `.ultra/state.db` exists; projection protection is always authoritative, while edit/compact/stop/subagent enforcement remains active-workflow scoped. Generic command blocking and post-edit policy stay in user or repository governance.',
65
67
  '- Installation boundary: `ubp --doctor` is the read-only authority for installed asset provenance, content hashes, and host entry-point wiring; `system.doctor` remains scoped to project state and workflow recovery.',
@@ -50,6 +50,7 @@ const RETIRED_SKILLS = Object.freeze([
50
50
 
51
51
  const WORKFLOW_HOOK_FILES = Object.freeze([
52
52
  'active_task_context.py',
53
+ 'context_spine.py',
53
54
  'health_check.py',
54
55
  'pre_stop_check.py',
55
56
  'subagent_tracker.py',
@@ -33,6 +33,9 @@ test('renders one common contract with host-native invocation syntax', () => {
33
33
  assert.match(rendered, /ultra-change/);
34
34
  assert.match(rendered, /ultra-doctor/);
35
35
  assert.match(rendered, /metadata references/);
36
+ assert.match(rendered, /Context Spine boundary/);
37
+ assert.match(rendered, /change\.breadcrumb/);
38
+ assert.match(rendered, /Specification learning boundary/);
36
39
  assert.doesNotMatch(rendered, /\.ultra\/memory|memory\.retain|memory\.recall/);
37
40
  assert.match(rendered, new RegExp(BEGIN_MARKER.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
38
41
  assert.match(rendered, new RegExp(END_MARKER.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
@@ -83,6 +83,7 @@ test('code-review-expert and rule skills are internal, never implicitly exposed'
83
83
  test('workflow hook allowlist contains no memory, prompt capture, or generic policy hook', () => {
84
84
  assert.deepEqual(WORKFLOW_HOOK_FILES, [
85
85
  'active_task_context.py',
86
+ 'context_spine.py',
86
87
  'health_check.py',
87
88
  'pre_stop_check.py',
88
89
  'subagent_tracker.py',
package/adapters/kimi.js CHANGED
@@ -219,8 +219,8 @@ bundled files are bounded prompt templates, not separately registered host agent
219
219
  'Read `SUMMARY.json` only after this validation succeeds.',
220
220
  ].join('\n');
221
221
  text = text.replace(
222
- /### Phase 3:[\s\S]*?### Phase 5: Report to User/,
223
- `${executionContract}\n\n### Phase 5: Report to User`,
222
+ /#{2,3} Phase 3:[\s\S]*?#{2,3} Phase 5: Report to User/,
223
+ `${executionContract}\n\n## Phase 5: Report to User`,
224
224
  );
225
225
  text = text.replaceAll('Background Execution', 'Reviewer Execution');
226
226
  text = text.replaceAll('Wait & Coordinate', 'Validate & Coordinate');
@@ -336,25 +336,26 @@ function pluginSource() {
336
336
  return `// Managed by Ultra Builder Pro. Rebuild through the adapter; do not edit in place.
337
337
  import fs from "node:fs";
338
338
  import path from "node:path";
339
+ import { execFileSync } from "node:child_process";
339
340
 
340
341
  const TERMINAL = new Set(["committed", "completed", "done", "cancelled"]);
341
342
  const TASKS_PROJECTION = ".ultra/tasks/tasks.json";
342
- const PROVIDER_FIELDS = new Set(["provider", "project", "reference", "revision", "indexed_head", "status"]);
343
-
344
- function providerMetadata(value) {
345
- if (!value || typeof value !== "object" || Array.isArray(value)) return {};
346
- const clean = {};
347
- for (const [kind, ref] of Object.entries(value)) {
348
- if (!["memory", "code_graph"].includes(kind) || !ref || typeof ref !== "object" || Array.isArray(ref)) continue;
349
- clean[kind] = Object.fromEntries(Object.entries(ref).filter(([key]) => PROVIDER_FIELDS.has(key)));
343
+
344
+ function gitHead(root) {
345
+ try {
346
+ return execFileSync("git", ["rev-parse", "HEAD"], {
347
+ cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"],
348
+ }).trim();
349
+ } catch {
350
+ return null;
350
351
  }
351
- return clean;
352
352
  }
353
353
 
354
354
  function findUltraContext(directory) {
355
355
  let current = path.resolve(directory);
356
356
  while (true) {
357
357
  const ultra = path.join(current, ".ultra");
358
+ const currentHead = gitHead(current);
358
359
  const workflowFile = path.join(ultra, "workflow-state.json");
359
360
  const statePresent = fs.existsSync(path.join(ultra, "state.db"));
360
361
  let workflow = null;
@@ -375,11 +376,36 @@ function findUltraContext(directory) {
375
376
  if (!fs.existsSync(manifestPath)) continue;
376
377
  try {
377
378
  const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
378
- if (manifest?.change) changes.push({
379
- ...manifest.change,
380
- providers: providerMetadata(manifest.providers),
379
+ if (manifest?.change) {
380
+ const compiledHead = manifest.git?.head ?? null;
381
+ const legacyContext = manifest.schema_version !== "2.0";
382
+ const headStale = Boolean(compiledHead && currentHead && compiledHead !== currentHead);
383
+ const blockers = Array.isArray(manifest.readiness?.blockers)
384
+ ? [...manifest.readiness.blockers]
385
+ : [];
386
+ if (headStale && !blockers.includes("CONTEXT_HEAD_STALE")) blockers.push("CONTEXT_HEAD_STALE");
387
+ if (legacyContext && !blockers.includes("CONTEXT_SNAPSHOT_UPGRADE_REQUIRED")) {
388
+ blockers.unshift("CONTEXT_SNAPSHOT_UPGRADE_REQUIRED");
389
+ }
390
+ changes.push({
391
+ id: manifest.change.id,
392
+ kind: manifest.change.kind,
393
+ status: manifest.change.status,
394
+ role: manifest.role ?? "plan",
395
+ gate: manifest.gate ?? "alignment",
396
+ nextAction: legacyContext
397
+ ? "Recompile change.context to upgrade this legacy context snapshot."
398
+ : headStale
399
+ ? "Git HEAD changed after context compilation; recompile change.context."
400
+ : (manifest.next_action ?? "Inspect Ultra status."),
401
+ readiness: legacyContext || headStale ? "blocked" : (manifest.readiness?.status ?? "ready"),
402
+ blockers,
403
+ taskId: manifest.selected_task?.id ?? manifest.resume?.task_id ?? null,
404
+ taskStatus: manifest.selected_task?.status ?? manifest.resume?.task_status ?? null,
405
+ tokenEstimate: manifest.context?.token_estimate ?? 0,
381
406
  manifestPath: path.relative(current, manifestPath),
382
407
  });
408
+ }
383
409
  } catch (error) {
384
410
  process.stderr.write(\`[ultra-builder-pro] cannot read \${manifestPath}: \${error.message}\\n\`);
385
411
  }
@@ -410,13 +436,16 @@ function contextText(active) {
410
436
  if (active.changes.length > 0) {
411
437
  const change = active.changes[0];
412
438
  lines.push(
413
- "[Ultra continuous change]",
439
+ "[Ultra context spine]",
414
440
  \`Project: \${active.root}\`,
415
441
  \`Change: \${change.id} (\${change.kind ?? "unknown"}, \${change.status ?? "active"})\`,
416
- \`Title: \${change.title ?? "unknown"}\`,
417
- \`Intent: \${change.intent ?? "unknown"}\`,
442
+ \`Task: \${change.taskId ?? "none"} (\${change.taskStatus ?? "none"})\`,
443
+ \`Role: \${change.role}\`,
444
+ \`Gate: \${change.gate}\`,
445
+ \`Readiness: \${change.readiness}\`,
446
+ ...(change.blockers.length > 0 ? [\`Blockers: \${change.blockers.join(", ")}\`] : []),
447
+ \`Next: \${change.nextAction}\`,
418
448
  \`Context manifest: \${change.manifestPath}\`,
419
- \`External providers (metadata references only): \${JSON.stringify(change.providers)}\`,
420
449
  );
421
450
  if (active.changes.length > 1) lines.push(
422
451
  \`Active change count: \${active.changes.length}; resolve scope with change.list before mutation.\`,
@@ -429,7 +458,7 @@ function contextText(active) {
429
458
  "No active continuous change. Start daily work with the ultra-change workflow.",
430
459
  );
431
460
  }
432
- lines.push("Authority: .ultra/state.db; generated JSON/Markdown are projections or artifacts.");
461
+ lines.push("Authority: .ultra/state.db; JSON/Markdown remain projections or evidence artifacts.");
433
462
  return lines.join("\\n");
434
463
  }
435
464
 
@@ -36,7 +36,10 @@ test('install builds the Claude-native plugin from the explicit Ultra allowlist'
36
36
 
37
37
  const hooks = JSON.parse(fs.readFileSync(path.join(target, 'hooks', 'hooks.json'), 'utf8'));
38
38
  const serialized = JSON.stringify(hooks);
39
- for (const name of WORKFLOW_HOOK_FILES) assert.match(serialized, new RegExp(name.replace('.', '\\.')));
39
+ for (const name of WORKFLOW_HOOK_FILES.filter((value) => value !== 'context_spine.py')) {
40
+ assert.match(serialized, new RegExp(name.replace('.', '\\.')));
41
+ }
42
+ assert.ok(fs.existsSync(path.join(target, 'hooks', 'context_spine.py')));
40
43
  assert.doesNotMatch(serialized, /memory|recall|journal|observation_capture|user_prompt_capture|block_dangerous|post_edit_guard/);
41
44
  assert.match(serialized, /\$\{CLAUDE_PLUGIN_ROOT\}/);
42
45
  } finally {
@@ -198,9 +198,10 @@ test('plugin declares current Codex hooks and a project-local Ultra MCP server',
198
198
  'SubagentStart', 'SubagentStop',
199
199
  ].sort());
200
200
  const serializedHooks = JSON.stringify(hooks);
201
- for (const feature of WORKFLOW_HOOK_FILES) {
201
+ for (const feature of WORKFLOW_HOOK_FILES.filter((value) => value !== 'context_spine.py')) {
202
202
  assert.match(serializedHooks, new RegExp(feature.replace('.', '\\.')));
203
203
  }
204
+ assert.ok(fs.existsSync(path.join(layout.pluginRoot, 'hooks', 'context_spine.py')));
204
205
  assert.doesNotMatch(serializedHooks, /memory|recall|journal|observation_capture|user_prompt_capture|block_dangerous|post_edit_guard/);
205
206
  assert.match(serializedHooks, /\$PLUGIN_ROOT\/hooks\/adapters\/codex\.py/);
206
207
  assert.match(serializedHooks, /apply_patch/);
@@ -231,8 +232,8 @@ test('plugin declares current Codex hooks and a project-local Ultra MCP server',
231
232
  const liveSpec = yaml.load(fs.readFileSync(path.join(layout.pluginRoot, 'spec', 'mcp-tools.yaml'), 'utf8'));
232
233
  const upstreamSpec = yaml.load(fs.readFileSync(path.join(layout.pluginRoot, 'spec', 'upstream-mcp-tools.yaml'), 'utf8'));
233
234
  const capabilityMap = JSON.parse(fs.readFileSync(path.join(layout.pluginRoot, 'spec', 'codex-capability-map.json'), 'utf8'));
234
- assert.equal(liveSpec.tools.length, 29);
235
- assert.equal(upstreamSpec.tools.length, 29);
235
+ assert.equal(liveSpec.tools.length, 32);
236
+ assert.equal(upstreamSpec.tools.length, 32);
236
237
  assert.deepEqual(upstreamSpec.tools.map((tool) => tool.name).sort(), liveSpec.tools.map((tool) => tool.name).sort());
237
238
  assert.deepEqual(capabilityMap.live_mcp_tools.sort(), liveSpec.tools.map((tool) => tool.name).sort());
238
239
  assert.equal(Object.keys(capabilityMap.codex_native_replacements).length, 9);
@@ -85,7 +85,10 @@ test('Kimi manifest exposes native skills, commands, hooks, session bootstrap, a
85
85
 
86
86
  const hookText = JSON.stringify(manifest.hooks);
87
87
  assert.match(hookText, /hooks\/adapters\/kimi\.py/);
88
- for (const hook of WORKFLOW_HOOK_FILES) assert.match(hookText, new RegExp(hook.replace('.', '\\.')));
88
+ for (const hook of WORKFLOW_HOOK_FILES.filter((value) => value !== 'context_spine.py')) {
89
+ assert.match(hookText, new RegExp(hook.replace('.', '\\.')));
90
+ }
91
+ assert.ok(fs.existsSync(path.join(pluginRoot, 'hooks', 'context_spine.py')));
89
92
  assert.doesNotMatch(hookText, /memory|recall|journal|prompt[_ -]?capture|block_dangerous|post_edit_guard/i);
90
93
 
91
94
  const commands = fs.readdirSync(path.join(pluginRoot, 'commands')).sort();
@@ -120,7 +123,8 @@ test('Kimi assets are allowlisted and adapted to native tools and paths', () =>
120
123
  );
121
124
  assert.match(learn, /~\/.kimi-code\/skills\/learned-<pattern-slug>-unverified\/SKILL\.md/);
122
125
  assert.match(review, /Kimi `AgentSwarm`/);
123
- assert.match(review, /5 specialized reviewers/);
126
+ assert.match(review, /Five engineering specialists/);
127
+ assert.match(review, /axes\.spec_fidelity/);
124
128
  assert.doesNotMatch(review, /6 specialized|ALL 6/);
125
129
  assert.match(review, /\$KIMI_PLUGIN_ROOT\/agents\/review-code\.md/);
126
130
  assert.match(review, /`prompt_template` containing\s+`\{\{item\}\}`/);
@@ -5,6 +5,7 @@ const assert = require('node:assert/strict');
5
5
  const fs = require('node:fs');
6
6
  const os = require('node:os');
7
7
  const path = require('node:path');
8
+ const { execFileSync } = require('node:child_process');
8
9
  const { pathToFileURL } = require('node:url');
9
10
 
10
11
  const opencode = require('../opencode.js');
@@ -172,7 +173,8 @@ test('install writes a schema-safe opencode.json and keeps ownership outside hos
172
173
  assert.match(plugin, /throw new Error/);
173
174
  assert.match(plugin, /session\.compacted/);
174
175
  assert.doesNotMatch(plugin, /memory\.(?:retain|recall|reflect)|journal|observation|prompt[_ -]?capture/);
175
- assert.match(plugin, /metadata references only/);
176
+ assert.match(plugin, /Ultra context spine/);
177
+ assert.doesNotMatch(plugin, /providerMetadata|External providers/);
176
178
  } finally {
177
179
  fs.rmSync(target, { recursive: true, force: true });
178
180
  }
@@ -189,18 +191,59 @@ test('OpenCode context covers active changes and projection writes are blocked f
189
191
 
190
192
  fs.mkdirSync(path.join(project, '.ultra'), { recursive: true });
191
193
  fs.writeFileSync(path.join(project, '.ultra', 'state.db'), 'owned-by-ultra');
194
+ execFileSync('git', ['init'], { cwd: project, stdio: 'ignore' });
195
+ fs.writeFileSync(path.join(project, 'app.txt'), 'initial\n');
196
+ execFileSync('git', ['add', 'app.txt'], { cwd: project, stdio: 'ignore' });
197
+ execFileSync('git', ['-c', 'user.name=Ultra Tests', '-c', 'user.email=ultra@example.invalid', 'commit', '-m', 'initial'], {
198
+ cwd: project, stdio: 'ignore',
199
+ });
200
+ const compiledHead = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: project, encoding: 'utf8' }).trim();
192
201
  const changeRoot = path.join(project, '.ultra', 'changes', 'active', 'daily-fix');
193
202
  fs.mkdirSync(changeRoot, { recursive: true });
194
203
  fs.writeFileSync(path.join(changeRoot, 'context-manifest.json'), JSON.stringify({
204
+ schema_version: '2.0',
195
205
  change: { id: 'daily-fix', title: 'Daily fix', kind: 'quick', status: 'active', intent: 'Fix drift' },
206
+ role: 'implement',
207
+ gate: 'implementation',
208
+ next_action: 'Run the exact regression test.',
209
+ readiness: { status: 'ready', blockers: [] },
210
+ git: { head: compiledHead },
211
+ selected_task: { id: 'daily-task', status: 'in_progress' },
212
+ context: { token_estimate: 1200, budget: { max_tokens: 8000, max_files: 8 } },
196
213
  providers: { memory: { provider: 'cloud-mem', status: 'available' } },
197
214
  }));
198
215
 
199
216
  const output = { system: [] };
200
217
  await plugin['experimental.chat.system.transform']({}, output);
201
- assert.match(output.system.join('\n'), /Ultra continuous change/);
218
+ assert.match(output.system.join('\n'), /Ultra context spine/);
202
219
  assert.match(output.system.join('\n'), /daily-fix/);
203
- assert.match(output.system.join('\n'), /metadata references only/);
220
+ assert.match(output.system.join('\n'), /daily-task/);
221
+ assert.match(output.system.join('\n'), /Role: implement/);
222
+ assert.match(output.system.join('\n'), /Gate: implementation/);
223
+ assert.match(output.system.join('\n'), /Readiness: ready/);
224
+ assert.match(output.system.join('\n'), /Run the exact regression test/);
225
+ assert.doesNotMatch(output.system.join('\n'), /Fix drift|cloud-mem/);
226
+
227
+ const manifestFile = path.join(changeRoot, 'context-manifest.json');
228
+ const legacyManifest = JSON.parse(fs.readFileSync(manifestFile, 'utf8'));
229
+ legacyManifest.schema_version = '1.0';
230
+ fs.writeFileSync(manifestFile, JSON.stringify(legacyManifest));
231
+ const legacyOutput = { system: [] };
232
+ await plugin['experimental.chat.system.transform']({}, legacyOutput);
233
+ assert.match(legacyOutput.system.join('\n'), /CONTEXT_SNAPSHOT_UPGRADE_REQUIRED/);
234
+ legacyManifest.schema_version = '2.0';
235
+ fs.writeFileSync(manifestFile, JSON.stringify(legacyManifest));
236
+
237
+ fs.writeFileSync(path.join(project, 'app.txt'), 'changed\n');
238
+ execFileSync('git', ['add', 'app.txt'], { cwd: project, stdio: 'ignore' });
239
+ execFileSync('git', ['-c', 'user.name=Ultra Tests', '-c', 'user.email=ultra@example.invalid', 'commit', '-m', 'change'], {
240
+ cwd: project, stdio: 'ignore',
241
+ });
242
+ const staleOutput = { system: [] };
243
+ await plugin['experimental.chat.system.transform']({}, staleOutput);
244
+ assert.match(staleOutput.system.join('\n'), /Readiness: blocked/);
245
+ assert.match(staleOutput.system.join('\n'), /CONTEXT_HEAD_STALE/);
246
+ assert.match(staleOutput.system.join('\n'), /recompile change\.context/);
204
247
 
205
248
  await assert.rejects(
206
249
  plugin['tool.execute.before'](
@@ -44,14 +44,14 @@ reports are projections or workflow artifacts.
44
44
 
45
45
  ## 3. Live MCP and declared contracts
46
46
 
47
- `spec/mcp-tools.yaml` declares and the bundled server registers 29 tools across
47
+ `spec/mcp-tools.yaml` declares and the bundled server registers 32 tools across
48
48
  five families:
49
49
 
50
50
  | Family | Live tools |
51
51
  |---|---|
52
52
  | `task.*` | create, update, list, get, switch_tag, delete, init_project, expand, parse_prd, dependency_topo, append_event, subscribe_events |
53
53
  | `session.*` | spawn, close, get, list, admission_check, heartbeat, subscribe_events |
54
- | `change.*` | create, update, get, list, context, converge, archive |
54
+ | `change.*` | create, update, get, list, context, breadcrumb, learning_propose, learning_resolve, converge, archive |
55
55
  | `system.*` | doctor |
56
56
  | `plan.*` | export, get |
57
57
 
@@ -62,7 +62,26 @@ capability map documents those replacements.
62
62
  Any new MCP contract starts in `spec/mcp-tools.yaml` with valid and invalid
63
63
  fixtures. Do not add an ad-hoc server handler first.
64
64
 
65
- ## 4. Native host presentation
65
+ ## 4. Context Spine contract
66
+
67
+ Context Manifest v2 is a DB-backed role handoff, not a static codebase summary.
68
+ `change.context` compiles required references, digests, readiness, context budget,
69
+ public seam, exact verification command, and one next action for `plan`,
70
+ `implement`, `check`, or `review`. The default fresh-context contract is at most
71
+ 12 files, about 12k tokens, and 40% of the host context; wider work must use an
72
+ explicit `expand_contract`.
73
+
74
+ `change.breadcrumb` is the only compact router. Hooks may inject its change/task,
75
+ role, gate, readiness, blockers, and one next action. They must not inject intent
76
+ bodies, transcripts, external memory, or graph payloads. Missing references,
77
+ digest drift, HEAD drift, or budget overflow blocks readiness.
78
+
79
+ Stable discoveries use `change.learning_propose`; they reach the baseline only
80
+ through approve/reject/apply transitions in `change.learning_resolve`. Unresolved
81
+ learning blocks convergence. Review contributes two independent axes,
82
+ `spec_fidelity` and `engineering_standards`; neither can replace the other.
83
+
84
+ ## 5. Native host presentation
66
85
 
67
86
  | Host | Plugin form | Workflow entry | Hook form | Collaboration companions |
68
87
  |---|---|---|---|---|
@@ -75,9 +94,10 @@ The current host remains primary. Collaboration skills call another runtime
75
94
  only when explicitly requested, use it as a read-only advisor, and return the
76
95
  evidence to the primary host for final verification.
77
96
 
78
- ## 5. Hook boundary
97
+ ## 6. Hook boundary
79
98
 
80
- The canonical Python hook allowlist contains seven workflow-only adapters:
99
+ The canonical Python hook bundle contains seven executable workflow hooks plus
100
+ the shared read-only `context_spine.py` breadcrumb helper:
81
101
 
82
102
  - `health_check.py` and `workflow_context.py` on session start;
83
103
  - `active_task_context.py` before an edit;
@@ -86,7 +106,9 @@ The canonical Python hook allowlist contains seven workflow-only adapters:
86
106
  - `pre_stop_check.py` at stop;
87
107
  - `subagent_tracker.py` for bounded worker lifecycle evidence.
88
108
 
89
- `health_check.py` and `workflow_context.py` may inspect any initialized project;
109
+ `workflow_context.py`, `active_task_context.py`, and `workflow_resume.py` read the
110
+ same DB-derived breadcrumb through `context_spine.py`. `health_check.py` and
111
+ `workflow_context.py` may inspect any initialized project;
90
112
  `active_task_context.py` always protects the task projection but limits ordinary
91
113
  edit guidance to an active workflow. Compact/stop/subagent hooks remain
92
114
  active-workflow scoped. OpenCode natively injects baseline/change context and
@@ -95,7 +117,7 @@ protects the projection; full health inspection is available through
95
117
  Generic command blocking, post-edit governance, and unrelated user hooks are not
96
118
  copied into Ultra Builder Pro.
97
119
 
98
- ## 6. Memory boundary
120
+ ## 7. Memory boundary
99
121
 
100
122
  Ultra Builder Pro has no memory MCP family, recall skill, prompt capture,
101
123
  transcript capture, observation journal, or session-summary hook. Persistent
@@ -113,14 +135,14 @@ ultra-tools legacy-memory prune --confirm DELETE_ULTRA_LEGACY_MEMORY
113
135
 
114
136
  Archive before prune; the confirmation token is intentionally required.
115
137
 
116
- ## 7. Project state layout
138
+ ## 8. Project state layout
117
139
 
118
140
  ```text
119
141
  .ultra/
120
142
  ├── state.db # authoritative SQLite store
121
143
  ├── workflow-state.json # active workflow/recovery checkpoint
122
144
  ├── changes/
123
- │ ├── active/<id>/ # intent, delta, plan, compiled context, verification
145
+ │ ├── active/<id>/ # intent, delta, plan, context v2, learning, verification
124
146
  │ └── archive/ # converged packets after baseline reconciliation
125
147
  ├── tasks/ # projections and task contexts
126
148
  ├── specs/ # research/product/architecture artifacts
@@ -130,7 +152,7 @@ Archive before prune; the confirmation token is intentionally required.
130
152
  └── delivery-report.json
131
153
  ```
132
154
 
133
- ## 8. User handbook integration
155
+ ## 9. User handbook integration
134
156
 
135
157
  General engineering doctrine remains user-owned in `CLAUDE.md` or `AGENTS.md`.
136
158
  Ultra Builder Pro contributes one managed section only. Preview or apply it with:
@@ -140,12 +162,12 @@ ubp-handbook preview --runtime codex
140
162
  ubp-handbook apply --runtime codex
141
163
  ```
142
164
 
143
- Supported runtime names are `claude`, `codex`, and `opencode`. Apply creates a
165
+ Supported runtime names are `claude`, `codex`, `opencode`, and `kimi`. Apply creates a
144
166
  timestamped backup, replaces only the marked block, and can migrate the old
145
167
  Codex `## Ultra Builder Pro Runtime Contract` section without touching the next
146
168
  user section. Plugin adapters themselves do not silently overwrite handbooks.
147
169
 
148
- ## 9. Verification
170
+ ## 10. Verification
149
171
 
150
172
  Run `npm run test:all`, `python3 -m pytest hooks/tests -q`, and `npm audit`.
151
173
  Adapter tests assert the allowlisted assets, native manifests, hook boundary,
@@ -53,7 +53,7 @@ the supported hosts share.
53
53
  | Layer | Role | Form |
54
54
  |--------------|----------------------------------------------|-------------------------------------------------------|
55
55
  | **skill** | knowledge carrier; tells the runtime *what to do* | `skills/<name>/SKILL.md` discovered natively by all supported runtimes |
56
- | **MCP** | authoritative workflow-state API | stdio MCP server exposing 29 live tools across task/session/change/system/plan families in [`spec/mcp-tools.yaml`](../spec/mcp-tools.yaml) |
56
+ | **MCP** | authoritative workflow-state and Context Spine API | stdio MCP server exposing 32 live tools across task/session/change/system/plan families in [`spec/mcp-tools.yaml`](../spec/mcp-tools.yaml) |
57
57
  | **CLI** | explicit initialization, recovery, diagnostics, and orchestration | `ultra-tools` / `ubp-orchestrator`; only commands listed by `--help` are executable (see [`spec/cli-protocol.md`](../spec/cli-protocol.md)) |
58
58
 
59
59
  Why three: skills give us behavior portability across runtimes; MCP gives
@@ -70,7 +70,7 @@ RPC, and host-native loaders own resolution and invocation.
70
70
 
71
71
  All durable Ultra workflow state lives in one SQLite file with WAL enabled. Schema is
72
72
  fixed in [`spec/schemas/state-db.sql`](../spec/schemas/state-db.sql) and
73
- covers fifteen tables:
73
+ covers sixteen tables:
74
74
 
75
75
  | Table | Holds | Phase |
76
76
  |--------------------|---------------------------------------------------|-------|
@@ -84,7 +84,8 @@ covers fifteen tables:
84
84
  | `circuit_breaker` | bounded retry and halt state for failed tasks | 5 |
85
85
  | `changes` | continuous feature/fix/incident lifecycle | 8C |
86
86
  | `artifacts` | intent/context/verification artifact registry | 8C |
87
- | `context_snapshots`| compiled context hashes, git head, provider refs | 8C |
87
+ | `context_snapshots`| role/gate context, readiness, budget, execution seam, hashes, git head, provider refs | 10 |
88
+ | `spec_learning_candidates` | approval-gated implementation discoveries for baseline convergence | 10 |
88
89
  | `trace_links` | task/spec/change traceability | 8C |
89
90
  | `incidents` | structured runtime failures and resolutions | 8C |
90
91
  | `projection_jobs` | durable projection outbox/retry state | 8C |
@@ -101,7 +102,8 @@ Two rules make this work:
101
102
  2. **Single writer for mutable tables, multi-writer for `events`.**
102
103
  `.ultra/state.db` opens in WAL with `busy_timeout=5000`. The MCP
103
104
  server holds the single writer connection for `tasks`, `sessions`,
104
- `telemetry`, `specs_refs`, and `migration_history`; the CLI calls
105
+ `changes`, `context_snapshots`, `spec_learning_candidates`, `telemetry`,
106
+ `specs_refs`, and `migration_history`; the CLI calls
105
107
  those tools over stdio rather than opening its own writer. The
106
108
  `events` table is append-only and explicitly multi-writer — CLI and
107
109
  orchestrator processes append directly to it under the same WAL +
@@ -133,7 +135,8 @@ active -> blocked -> active -> ready -> archived
133
135
  | |
134
136
  ├─ intent + delta + plan └─ verification + baseline reconciliation
135
137
  ├─ linked tasks
136
- └─ context-manifest (current HEAD + provider metadata references)
138
+ ├─ context-manifest v2 (role + gate + readiness + execution seam)
139
+ └─ spec-learning candidates (proposed -> approved/rejected -> applied)
137
140
  ```
138
141
 
139
142
  `quick`, `standard`, `major`, and `incident` kinds require different evidence,
@@ -141,6 +144,29 @@ but all require completed linked tasks, current context, declared documentation
141
144
  impact, and no open incident. Memory and graph payloads never enter Ultra;
142
145
  `context_snapshots.provider_refs_json` stores metadata references only.
143
146
 
147
+ ### Context Spine
148
+
149
+ Context Manifest v2 is the handoff contract between planning, implementation,
150
+ checking, review, convergence, and recovery. Each snapshot records:
151
+
152
+ - one role (`plan`, `implement`, `check`, or `review`) and lifecycle gate;
153
+ - required context references with local digests and reasons;
154
+ - readiness blockers for missing/stale references or budget overflow;
155
+ - a fresh-context budget (12 files / about 12k tokens / 40% by default);
156
+ - an execution contract (`slice_kind`, public seam, exact verification command);
157
+ - one deterministic next action.
158
+
159
+ `change.breadcrumb` derives the compact current position from state.db. Session,
160
+ edit, and resume hooks inject only this breadcrumb, never the intent body,
161
+ provider content, or a conversation summary. A changed git HEAD marks the
162
+ snapshot stale and routes back to `change.context`.
163
+
164
+ `change.learning_propose` records a durable implementation discovery without
165
+ silently rewriting the baseline. `change.learning_resolve` enforces approval,
166
+ rejection, and applied transitions. Proposed or merely approved candidates block
167
+ convergence, so daily work cannot leave accepted behavior stranded in chat or a
168
+ delta packet.
169
+
144
170
  `incident` is the canonical debug lane. Creating one also registers a durable
145
171
  `diagnosis.md` artifact with five mandatory sections: reproduction, hypotheses,
146
172
  root cause, regression test, and recovery. Convergence validates the artifact
@@ -10,8 +10,9 @@
10
10
  > Kimi Code 采用各自 native plugin 呈现,其他旧 runtime adapter 已退役。下文所有与此冲突的
11
11
  > memory/hindsight 章节仅作为历史实施记录,不再是当前需求或运行时契约。
12
12
 
13
- **当前状态**:v0.9.0;四宿主 native plugin、29 个 live MCP tool、
14
- schema 9.1(15 表)、持续 change/context/convergence/doctor、incident debug lane、
13
+ **当前状态**:v0.10.0;四宿主 native plugin、32 个 live MCP tool、
14
+ schema 10.0(16 表)、Context Spine v2 / breadcrumb / spec-learning、持续
15
+ change/context/convergence/doctor、incident debug lane、
15
16
  checkpoint 恢复消费与跨宿主安装 provenance/doctor 已落地;npm 发布由
16
17
  `v*.*.*` tag 触发。以下 Phase 0-9 正文保留为 2026-04 的实施历史,不再作为
17
18
  当前 runtime 或发布状态的事实来源。