ultra-builder-pro-cli 0.12.1 → 0.14.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 (88) hide show
  1. package/.ultra-template/docs/research/README.md +21 -44
  2. package/.ultra-template/specs/discovery.md +10 -0
  3. package/CHANGELOG.md +95 -1
  4. package/README.md +31 -16
  5. package/adapters/_shared/codex-assets.cjs +23 -163
  6. package/adapters/_shared/handbook.cjs +2 -2
  7. package/adapters/_shared/runtime-assets.cjs +1 -0
  8. package/adapters/_shared/tests/runtime-assets.test.cjs +18 -0
  9. package/adapters/opencode.js +30 -120
  10. package/adapters/tests/claude.test.cjs +3 -2
  11. package/adapters/tests/codex-hook.test.cjs +34 -7
  12. package/adapters/tests/codex.test.cjs +21 -6
  13. package/adapters/tests/kimi-hook.test.cjs +34 -7
  14. package/adapters/tests/opencode.test.cjs +70 -84
  15. package/commands/ultra-init.md +1 -1
  16. package/docs/AGENT-CONTEXT.md +10 -6
  17. package/docs/ARCHITECTURE.md +40 -17
  18. package/docs/PLAN.zh-CN.md +5 -4
  19. package/docs/ROADMAP.md +2 -2
  20. package/docs/RUNTIME-COMPAT-MATRIX.md +4 -4
  21. package/docs/STATE-DB-ACCESS-POLICY.md +3 -3
  22. package/docs/USER-HANDBOOK-CONTRACT.md +5 -4
  23. package/hooks/active_task_context.py +22 -50
  24. package/hooks/context_spine.py +61 -299
  25. package/hooks/health_check.py +19 -17
  26. package/hooks/pre_stop_check.py +28 -33
  27. package/hooks/subagent_tracker.py +13 -19
  28. package/hooks/workflow_checkpoint.py +39 -47
  29. package/hooks/workflow_context.py +4 -9
  30. package/hooks/workflow_resume.py +48 -128
  31. package/mcp-server/breadcrumb.cjs +22 -0
  32. package/mcp-server/lib/archive-journal.cjs +192 -0
  33. package/mcp-server/lib/archive-journal.test.cjs +35 -0
  34. package/mcp-server/lib/baseline-workflow.cjs +404 -38
  35. package/mcp-server/lib/baseline-workflow.test.cjs +252 -0
  36. package/mcp-server/lib/change-workflow.cjs +249 -75
  37. package/mcp-server/lib/change-workflow.test.cjs +235 -1
  38. package/mcp-server/lib/context-spine.test.cjs +5 -0
  39. package/mcp-server/lib/doctor.cjs +14 -1
  40. package/mcp-server/lib/doctor.test.cjs +42 -4
  41. package/mcp-server/lib/init-project.cjs +292 -9
  42. package/mcp-server/lib/prd-parser.cjs +14 -41
  43. package/mcp-server/lib/prd-parser.test.cjs +49 -138
  44. package/mcp-server/lib/project-breadcrumb.cjs +139 -0
  45. package/mcp-server/lib/project-breadcrumb.test.cjs +112 -0
  46. package/mcp-server/lib/spec-learning.test.cjs +1 -1
  47. package/mcp-server/lib/state-authority.cjs +9 -3
  48. package/mcp-server/lib/state-db.cjs +154 -32
  49. package/mcp-server/lib/state-db.test.cjs +136 -4
  50. package/mcp-server/lib/state-ops.cjs +68 -6
  51. package/mcp-server/lib/task-expander.cjs +14 -37
  52. package/mcp-server/lib/task-expander.test.cjs +80 -164
  53. package/mcp-server/server.cjs +34 -37
  54. package/mcp-server/tests/init-project.test.cjs +162 -1
  55. package/mcp-server/tests/migrate.test.cjs +33 -0
  56. package/mcp-server/tests/server-contract.test.cjs +391 -17
  57. package/mcp-server/tests/state-db.test.cjs +14 -2
  58. package/mcp-server/tests/state-ops.test.cjs +108 -0
  59. package/package.json +2 -4
  60. package/skills/cc-collab/SKILL.md +2 -2
  61. package/skills/codex-collab/SKILL.md +2 -3
  62. package/skills/ultra-change/SKILL.md +18 -10
  63. package/skills/ultra-deliver/SKILL.md +13 -9
  64. package/skills/ultra-dev/SKILL.md +6 -4
  65. package/skills/ultra-doctor/SKILL.md +22 -7
  66. package/skills/ultra-init/SKILL.md +104 -59
  67. package/skills/ultra-init/references/brownfield-adoption.md +65 -37
  68. package/skills/ultra-plan/SKILL.md +20 -7
  69. package/skills/ultra-research/SKILL.md +12 -5
  70. package/skills/ultra-status/SKILL.md +6 -4
  71. package/skills/ultra-verify/SKILL.md +21 -30
  72. package/skills/ultra-verify/scripts/verify_wait.py +54 -54
  73. package/spec/cli-protocol.md +11 -1
  74. package/spec/mcp-tools.yaml +133 -19
  75. package/spec/schemas/state-db.sql +11 -0
  76. package/ultra-tools/cli.cjs +9 -5
  77. package/ultra-tools/commands/migrate.cjs +62 -36
  78. package/ultra-tools/commands/status.cjs +18 -9
  79. package/ultra-tools/commands/system.cjs +340 -6
  80. package/ultra-tools/commands/task.cjs +7 -1
  81. package/ultra-tools/status-command.test.cjs +29 -0
  82. package/ultra-tools/system-command.test.cjs +252 -1
  83. package/mcp-server/lib/llm-client.cjs +0 -157
  84. package/mcp-server/lib/llm-client.test.cjs +0 -156
  85. package/orchestrator/planner/expand-prompt.cjs +0 -62
  86. package/orchestrator/planner/expand-prompt.test.cjs +0 -52
  87. package/orchestrator/planner/prd-prompt.cjs +0 -44
  88. package/orchestrator/planner/prd-prompt.test.cjs +0 -30
@@ -304,135 +304,45 @@ function pluginSource() {
304
304
  import fs from "node:fs";
305
305
  import path from "node:path";
306
306
  import { execFileSync } from "node:child_process";
307
+ import { fileURLToPath } from "node:url";
307
308
 
308
- const TERMINAL = new Set(["committed", "completed", "done", "cancelled"]);
309
309
  const TASKS_PROJECTION = ".ultra/tasks/tasks.json";
310
+ const NODE_BINARY = ${JSON.stringify(process.execPath)};
311
+ const PLUGIN_DIR = path.dirname(fileURLToPath(import.meta.url));
312
+ const BREADCRUMB_CLI = path.resolve(
313
+ PLUGIN_DIR, "..", ${JSON.stringify(BUNDLE_DIR)}, "runtime", "breadcrumb.cjs",
314
+ );
310
315
 
311
- function gitHead(root) {
312
- try {
313
- return execFileSync("git", ["rev-parse", "HEAD"], {
314
- cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"],
315
- }).trim();
316
- } catch {
317
- return null;
318
- }
319
- }
320
-
321
- function findUltraContext(directory) {
316
+ function findUltraRoot(directory) {
322
317
  let current = path.resolve(directory);
323
318
  while (true) {
324
319
  const ultra = path.join(current, ".ultra");
325
- const currentHead = gitHead(current);
326
- const workflowFile = path.join(ultra, "workflow-state.json");
327
- const statePresent = fs.existsSync(path.join(ultra, "state.db"));
328
- let workflow = null;
329
- if (fs.existsSync(workflowFile)) {
330
- try {
331
- const state = JSON.parse(fs.readFileSync(workflowFile, "utf8"));
332
- workflow = state && !TERMINAL.has(state.status) ? state : null;
333
- } catch (error) {
334
- process.stderr.write(\`[ultra-builder-pro] cannot read \${workflowFile}: \${error.message}\\n\`);
335
- }
336
- }
337
- const changes = [];
338
- const changeRoot = path.join(ultra, "changes", "active");
339
- if (fs.existsSync(changeRoot)) {
340
- for (const entry of fs.readdirSync(changeRoot, { withFileTypes: true })) {
341
- if (!entry.isDirectory()) continue;
342
- const manifestPath = path.join(changeRoot, entry.name, "context-manifest.json");
343
- if (!fs.existsSync(manifestPath)) continue;
344
- try {
345
- const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
346
- if (manifest?.change) {
347
- const compiledHead = manifest.git?.head ?? null;
348
- const legacyContext = manifest.schema_version !== "2.0" || !manifest.baseline;
349
- const headStale = Boolean(compiledHead && currentHead && compiledHead !== currentHead);
350
- const blockers = Array.isArray(manifest.readiness?.blockers)
351
- ? [...manifest.readiness.blockers]
352
- : [];
353
- const warnings = Array.isArray(manifest.readiness?.warnings)
354
- ? [...manifest.readiness.warnings]
355
- : [];
356
- if (headStale && !blockers.includes("CONTEXT_HEAD_STALE")) blockers.push("CONTEXT_HEAD_STALE");
357
- if (legacyContext && !blockers.includes("CONTEXT_SNAPSHOT_UPGRADE_REQUIRED")) {
358
- blockers.unshift("CONTEXT_SNAPSHOT_UPGRADE_REQUIRED");
359
- }
360
- changes.push({
361
- id: manifest.change.id,
362
- kind: manifest.change.kind,
363
- status: manifest.change.status,
364
- role: manifest.role ?? "plan",
365
- gate: manifest.gate ?? "alignment",
366
- nextAction: legacyContext
367
- ? "Recompile change.context to upgrade this legacy context snapshot."
368
- : headStale
369
- ? "Git HEAD changed after context compilation; recompile change.context."
370
- : (manifest.next_action ?? "Inspect Ultra status."),
371
- readiness: legacyContext || headStale ? "blocked" : (manifest.readiness?.status ?? "ready"),
372
- blockers,
373
- warnings,
374
- baseline: manifest.baseline ?? null,
375
- taskId: manifest.selected_task?.id ?? manifest.resume?.task_id ?? null,
376
- taskStatus: manifest.selected_task?.status ?? manifest.resume?.task_status ?? null,
377
- tokenEstimate: manifest.context?.token_estimate ?? 0,
378
- manifestPath: path.relative(current, manifestPath),
379
- });
380
- }
381
- } catch (error) {
382
- process.stderr.write(\`[ultra-builder-pro] cannot read \${manifestPath}: \${error.message}\\n\`);
383
- }
384
- }
385
- }
386
- if (statePresent || workflow || changes.length > 0) {
387
- return { root: current, workflow, changes, statePresent };
388
- }
320
+ if (fs.existsSync(ultra) && fs.statSync(ultra).isDirectory()) return current;
389
321
  const parent = path.dirname(current);
390
322
  if (parent === current) return null;
391
323
  current = parent;
392
324
  }
393
325
  }
394
326
 
395
- function contextText(active) {
396
- const lines = [];
397
- if (active.workflow) {
398
- const state = active.workflow;
399
- lines.push(
400
- "[Ultra active workflow]",
401
- \`Project: \${active.root}\`,
402
- \`Command: \${state.command ?? "unknown"}\`,
403
- \`Task: \${state.task_id ?? state.task ?? "unknown"}\`,
404
- \`Step: \${state.step ?? "unknown"}\`,
405
- \`Status: \${state.status ?? "active"}\`,
406
- );
407
- }
408
- if (active.changes.length > 0) {
409
- const change = active.changes[0];
410
- lines.push(
411
- "[Ultra context spine]",
412
- \`Project: \${active.root}\`,
413
- \`Change: \${change.id} (\${change.kind ?? "unknown"}, \${change.status ?? "active"})\`,
414
- \`Task: \${change.taskId ?? "none"} (\${change.taskStatus ?? "none"})\`,
415
- \`Role: \${change.role}\`,
416
- \`Gate: \${change.gate}\`,
417
- \`Readiness: \${change.readiness}\`,
418
- ...(change.blockers.length > 0 ? [\`Blockers: \${change.blockers.join(", ")}\`] : []),
419
- ...(change.warnings.length > 0 ? [\`Warnings: \${change.warnings.join(", ")}\`] : []),
420
- \`Next: \${change.nextAction}\`,
421
- \`Context manifest: \${change.manifestPath}\`,
422
- );
423
- if (active.changes.length > 1) lines.push(
424
- \`Active change count: \${active.changes.length}; resolve scope with change.list before mutation.\`,
425
- );
426
- }
427
- if (!active.workflow && active.changes.length === 0) {
428
- lines.push(
429
- "[Ultra baseline]",
430
- \`Project: \${active.root}\`,
431
- "No active continuous change. Run ultra-status; resume ultra-init when the baseline is missing, incomplete, or stale, otherwise start ultra-change.",
432
- );
327
+ function readUltraContext(directory) {
328
+ const root = findUltraRoot(directory);
329
+ if (!root) return null;
330
+ try {
331
+ const raw = execFileSync(NODE_BINARY, [BREADCRUMB_CLI, root], {
332
+ cwd: root,
333
+ encoding: "utf8",
334
+ stdio: ["ignore", "pipe", "pipe"],
335
+ timeout: 5000,
336
+ });
337
+ const value = JSON.parse(raw);
338
+ if (!value?.breadcrumb || typeof value.text !== "string" || !value.text) {
339
+ throw new Error("canonical breadcrumb response is incomplete");
340
+ }
341
+ return { root, breadcrumb: value.breadcrumb, text: value.text };
342
+ } catch (error) {
343
+ process.stderr.write(\`[ultra-builder-pro] cannot read canonical breadcrumb: \${error.message}\\n\`);
344
+ return { root, breadcrumb: null, text: null };
433
345
  }
434
- lines.push("Authority: .ultra/state.db; JSON/Markdown remain projections or evidence artifacts.");
435
- return lines.join("\\n");
436
346
  }
437
347
 
438
348
  function targetPaths(tool, args) {
@@ -456,19 +366,19 @@ function isTasksProjection(active, candidate) {
456
366
 
457
367
  export const UltraBuilderProPlugin = async ({ directory, worktree }) => {
458
368
  const root = worktree || directory;
459
- let active = findUltraContext(root);
460
- const refresh = () => { active = findUltraContext(root); };
369
+ let active = readUltraContext(root);
370
+ const refresh = () => { active = readUltraContext(root); };
461
371
  return {
462
372
  event: async ({ event }) => {
463
373
  if (["session.created", "session.compacted", "session.idle"].includes(event?.type)) refresh();
464
374
  },
465
375
  "experimental.chat.system.transform": async (_input, output) => {
466
376
  refresh();
467
- if (active) output.system.push(contextText(active));
377
+ if (active?.text) output.system.push(active.text);
468
378
  },
469
379
  "experimental.session.compacting": async (_input, output) => {
470
380
  refresh();
471
- if (active) output.context.push(contextText(active));
381
+ if (active?.text) output.context.push(active.text);
472
382
  },
473
383
  "tool.execute.before": async (input, output) => {
474
384
  refresh();
@@ -70,8 +70,9 @@ test('Claude plugin collaboration and learn workflows are safe native plugin ass
70
70
  assert.match(codexCollab, /-s read-only/);
71
71
  assert.match(codexCollab, /--ephemeral/);
72
72
  assert.match(codexCollab, /--ignore-user-config/);
73
- assert.match(verify, /-s read-only/);
74
- assert.match(verify, /--ephemeral/);
73
+ assert.match(verify, /Keep the current host responsible/);
74
+ assert.match(verify, /installed collaboration companion/);
75
+ assert.doesNotMatch(verify, /codex exec|claude --safe-mode/);
75
76
  assert.match(verify, /scripts\/verify_wait\.py/);
76
77
  assert.match(review, /Claude Code Task workers/);
77
78
  assert.match(review, /scripts\/review_wait\.py/);
@@ -6,6 +6,10 @@ const fs = require('node:fs');
6
6
  const os = require('node:os');
7
7
  const path = require('node:path');
8
8
  const { spawnSync } = require('node:child_process');
9
+ const { initStateDb, closeStateDb } = require('../../mcp-server/lib/state-db.cjs');
10
+ const { createChange } = require('../../mcp-server/lib/change-workflow.cjs');
11
+ const { createTask } = require('../../mcp-server/lib/state-ops.cjs');
12
+ const { compileRoleContext } = require('../../mcp-server/lib/context-spine.cjs');
9
13
 
10
14
  const REPO_ROOT = path.resolve(__dirname, '..', '..');
11
15
  const ADAPTER = path.join(REPO_ROOT, 'hooks', 'adapters', 'codex.py');
@@ -19,6 +23,35 @@ function run(feature, payload) {
19
23
  });
20
24
  }
21
25
 
26
+ function seedContext(project, taskId = 'task-7') {
27
+ const state = initStateDb(path.join(project, '.ultra', 'state.db'));
28
+ state.db.prepare(
29
+ `INSERT INTO baselines
30
+ (id, project_name, mode, status, approved_by, approval_note, converged_at)
31
+ VALUES ('baseline', 'fixture', 'greenfield', 'ready', 'test', 'fixture', ?)`,
32
+ ).run(new Date().toISOString());
33
+ const { change } = createChange(state.db, {
34
+ id: 'hook-change', title: 'Hook change', kind: 'quick',
35
+ intent: 'Inject the authoritative task.',
36
+ docs_impact: { status: 'none', rationale: 'fixture' },
37
+ }, { rootDir: project });
38
+ const task = createTask(state.db, {
39
+ id: taskId, title: 'Hook task', type: 'bugfix', priority: 'P0', change_id: change.id,
40
+ });
41
+ compileRoleContext(state.db, {
42
+ input: {
43
+ task_id: task.id, role: 'implement', gate: 'implementation',
44
+ execution_contract: {
45
+ slice_kind: 'tracer_bullet', public_seam: 'hook injection',
46
+ verification_command: 'node --test adapters/tests/codex-hook.test.cjs',
47
+ },
48
+ next_action: 'Continue the authoritative hook task.',
49
+ },
50
+ change, tasks: [task], rootDir: project,
51
+ });
52
+ closeStateDb(state.db);
53
+ }
54
+
22
55
  test('Codex hook adapter rejects retired generic policy hooks', () => {
23
56
  const result = run('block_dangerous_commands.py', {
24
57
  session_id: 'session-1',
@@ -36,10 +69,7 @@ test('Codex hook adapter rejects retired generic policy hooks', () => {
36
69
  test('Codex hook adapter injects only an active Ultra task boundary before edits', () => {
37
70
  const project = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-codex-hook-'));
38
71
  try {
39
- fs.mkdirSync(path.join(project, '.ultra'));
40
- fs.writeFileSync(path.join(project, '.ultra', 'workflow-state.json'), JSON.stringify({
41
- command: 'ultra-dev', task_id: 'task-7', step: '4.5', status: 'review_pending',
42
- }));
72
+ seedContext(project);
43
73
  const result = run('active_task_context.py', {
44
74
  session_id: 'session-2',
45
75
  cwd: project,
@@ -78,9 +108,6 @@ test('Codex hook adapter denies apply_patch writes to tasks.json during an activ
78
108
  const project = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-codex-hook-deny-'));
79
109
  try {
80
110
  fs.mkdirSync(path.join(project, '.ultra'));
81
- fs.writeFileSync(path.join(project, '.ultra', 'workflow-state.json'), JSON.stringify({
82
- command: 'ultra-plan', task_id: 'plan', step: 'persist', status: 'active',
83
- }));
84
111
  const result = run('active_task_context.py', {
85
112
  session_id: 'session-deny',
86
113
  cwd: project,
@@ -9,6 +9,7 @@ const { spawnSync } = require('node:child_process');
9
9
  const yaml = require('js-yaml');
10
10
  const { Client } = require('@modelcontextprotocol/sdk/client/index.js');
11
11
  const { StdioClientTransport } = require('@modelcontextprotocol/sdk/client/stdio.js');
12
+ const { initStateDb, closeStateDb } = require('../../mcp-server/lib/state-db.cjs');
12
13
 
13
14
  const codex = require('../codex.js');
14
15
  const { parse: parseFrontmatter } = require('../_shared/frontmatter.cjs');
@@ -127,8 +128,13 @@ test('every generated skill is Codex-valid, UI-visible, and free of Claude host
127
128
  assert.doesNotMatch(text, /[\u3400-\u9fff]|ultra-review-findings-v1|Context7|Exa MCP|confidence\s*>=?\s*\d+/iu);
128
129
 
129
130
  const openai = yaml.load(fs.readFileSync(path.join(skillDir, 'agents', 'openai.yaml'), 'utf8'));
131
+ const openaiSource = fs.readFileSync(path.join(skillDir, 'agents', 'openai.yaml'), 'utf8');
130
132
  assert.ok(openai.interface.display_name);
131
133
  assert.ok(openai.interface.short_description);
134
+ assert.ok(openai.interface.short_description.length >= 25, name);
135
+ assert.ok(openai.interface.short_description.length <= 64, name);
136
+ assert.match(openaiSource, /short_description:\s+['"]/);
137
+ assert.match(openaiSource, /default_prompt:\s+['"]/);
132
138
  assert.match(openai.interface.default_prompt, new RegExp(`\\$ultra-builder-pro:${name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`));
133
139
  assert.equal(openai.policy.allow_implicit_invocation, false);
134
140
  if (MCP_DEPENDENT_SKILLS.includes(name)) {
@@ -147,14 +153,15 @@ test('every generated skill is Codex-valid, UI-visible, and free of Claude host
147
153
  assert.match(cc, /claude --safe-mode/);
148
154
 
149
155
  const verify = fs.readFileSync(path.join(layout.pluginRoot, 'skills', 'ultra-verify', 'SKILL.md'), 'utf8');
150
- assert.match(verify, /Codex owns the task/);
151
- assert.match(verify, /claude --safe-mode/);
152
- assert.match(verify, /codex-analysis\.md/);
153
- assert.doesNotMatch(verify, /codex-output\.md|Claude synthesizes|Claude-only/);
156
+ assert.match(verify, /Keep the current host responsible/);
157
+ assert.match(verify, /installed collaboration companion/);
158
+ assert.match(verify, /host-analysis\.md/);
159
+ assert.doesNotMatch(verify, /codex exec|claude --safe-mode|Claude synthesizes|Claude-only/);
154
160
 
155
161
  const waiter = fs.readFileSync(path.join(layout.pluginRoot, 'skills', 'ultra-verify', 'scripts', 'verify_wait.py'), 'utf8');
156
- assert.match(waiter, /ADVISOR = "claude"/);
157
- assert.match(waiter, /OUTPUT_FILE = "claude-output\.md"/);
162
+ assert.match(waiter, /--advisor/);
163
+ assert.match(waiter, /advisor-output\.md/);
164
+ assert.doesNotMatch(waiter, /^ADVISOR\s*=|^OUTPUT_FILE\s*=/m);
158
165
 
159
166
  const review = fs.readFileSync(path.join(layout.pluginRoot, 'skills', 'ultra-review', 'SKILL.md'), 'utf8');
160
167
  assert.match(review, /native Codex custom agents/);
@@ -230,6 +237,7 @@ test('plugin declares current Codex hooks and a project-local Ultra MCP server',
230
237
 
231
238
  for (const rel of [
232
239
  'runtime/index.cjs',
240
+ 'runtime/breadcrumb.cjs',
233
241
  'runtime/launch.cjs',
234
242
  'runtime/ultra-tools.cjs',
235
243
  'runtime/build/Release/better_sqlite3.node',
@@ -263,6 +271,13 @@ test('bundled plugin MCP runs outside the source checkout and keeps state in the
263
271
  let client;
264
272
  try {
265
273
  install(layout);
274
+ const seededState = initStateDb(path.join(projectDir, '.ultra', 'state.db'));
275
+ seededState.db.prepare(
276
+ `INSERT INTO baselines
277
+ (id, project_name, mode, status, approved_by, approval_note, converged_at)
278
+ VALUES ('test-baseline', 'codex-bundle', 'greenfield', 'ready', 'test', 'smoke fixture', ?)`,
279
+ ).run(new Date().toISOString());
280
+ closeStateDb(seededState.db);
266
281
  const mcp = JSON.parse(fs.readFileSync(path.join(layout.pluginRoot, '.mcp.json'), 'utf8'));
267
282
  const server = mcp.mcpServers['ultra-builder-pro'];
268
283
  const env = { ...process.env };
@@ -6,6 +6,10 @@ const fs = require('node:fs');
6
6
  const os = require('node:os');
7
7
  const path = require('node:path');
8
8
  const { spawnSync } = require('node:child_process');
9
+ const { initStateDb, closeStateDb } = require('../../mcp-server/lib/state-db.cjs');
10
+ const { createChange } = require('../../mcp-server/lib/change-workflow.cjs');
11
+ const { createTask } = require('../../mcp-server/lib/state-ops.cjs');
12
+ const { compileRoleContext } = require('../../mcp-server/lib/context-spine.cjs');
9
13
 
10
14
  const REPO_ROOT = path.resolve(__dirname, '..', '..');
11
15
  const ADAPTER = path.join(REPO_ROOT, 'hooks', 'adapters', 'kimi.py');
@@ -18,6 +22,35 @@ function run(feature, payload, args = []) {
18
22
  });
19
23
  }
20
24
 
25
+ function seedContext(project, taskId = 'task-7') {
26
+ const state = initStateDb(path.join(project, '.ultra', 'state.db'));
27
+ state.db.prepare(
28
+ `INSERT INTO baselines
29
+ (id, project_name, mode, status, approved_by, approval_note, converged_at)
30
+ VALUES ('baseline', 'fixture', 'greenfield', 'ready', 'test', 'fixture', ?)`,
31
+ ).run(new Date().toISOString());
32
+ const { change } = createChange(state.db, {
33
+ id: 'hook-change', title: 'Hook change', kind: 'quick',
34
+ intent: 'Inject the authoritative task.',
35
+ docs_impact: { status: 'none', rationale: 'fixture' },
36
+ }, { rootDir: project });
37
+ const task = createTask(state.db, {
38
+ id: taskId, title: 'Hook task', type: 'bugfix', priority: 'P0', change_id: change.id,
39
+ });
40
+ compileRoleContext(state.db, {
41
+ input: {
42
+ task_id: task.id, role: 'implement', gate: 'implementation',
43
+ execution_contract: {
44
+ slice_kind: 'tracer_bullet', public_seam: 'hook injection',
45
+ verification_command: 'node --test adapters/tests/kimi-hook.test.cjs',
46
+ },
47
+ next_action: 'Continue the authoritative hook task.',
48
+ },
49
+ change, tasks: [task], rootDir: project,
50
+ });
51
+ closeStateDb(state.db);
52
+ }
53
+
21
54
  test('Kimi hook adapter rejects non-workflow policy hooks without blocking the host', () => {
22
55
  const result = run('block_dangerous_commands.py', {
23
56
  session_id: 'session-1',
@@ -33,10 +66,7 @@ test('Kimi hook adapter rejects non-workflow policy hooks without blocking the h
33
66
  test('Kimi hook adapter translates active edit context to the native message field', () => {
34
67
  const project = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-kimi-hook-'));
35
68
  try {
36
- fs.mkdirSync(path.join(project, '.ultra'));
37
- fs.writeFileSync(path.join(project, '.ultra', 'workflow-state.json'), JSON.stringify({
38
- command: 'ultra-dev', task_id: 'task-7', step: '4.5', status: 'review_pending',
39
- }));
69
+ seedContext(project);
40
70
  const result = run('active_task_context.py', {
41
71
  session_id: 'session-2',
42
72
  cwd: project,
@@ -57,9 +87,6 @@ test('Kimi hook adapter emits the native deny contract for projection writes', (
57
87
  const project = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-kimi-hook-deny-'));
58
88
  try {
59
89
  fs.mkdirSync(path.join(project, '.ultra'));
60
- fs.writeFileSync(path.join(project, '.ultra', 'workflow-state.json'), JSON.stringify({
61
- command: 'ultra-plan', task_id: 'plan', step: 'persist', status: 'active',
62
- }));
63
90
  const result = run('active_task_context.py', {
64
91
  session_id: 'session-deny',
65
92
  cwd: project,
@@ -11,6 +11,9 @@ const { pathToFileURL } = require('node:url');
11
11
  const opencode = require('../opencode.js');
12
12
  const { parse: parseFm } = require('../_shared/frontmatter.cjs');
13
13
  const { skillsForRuntime } = require('../_shared/runtime-assets.cjs');
14
+ const { initStateDb, closeStateDb } = require('../../mcp-server/lib/state-db.cjs');
15
+ const changes = require('../../mcp-server/lib/change-workflow.cjs');
16
+ const { createTask } = require('../../mcp-server/lib/state-ops.cjs');
14
17
 
15
18
  const REPO_ROOT = path.resolve(__dirname, '..', '..');
16
19
 
@@ -69,10 +72,10 @@ test('install performs content-level OpenCode adaptation for commands, skills, r
69
72
  assert.match(review, /OpenCode `task` tool/);
70
73
  assert.match(review, /scripts\/review_wait\.py/);
71
74
  assert.match(codexCollab, /OpenCode remains primary/);
72
- assert.match(verify, /OpenCode writes the first analysis/);
73
- assert.match(verify, /opencode-analysis\.md/);
74
- assert.match(verify, /codex exec -s read-only/);
75
- assert.match(verify, /--ignore-user-config/);
75
+ assert.match(verify, /Keep OpenCode responsible/);
76
+ assert.match(verify, /host-analysis\.md/);
77
+ assert.match(verify, /installed collaboration companion/);
78
+ assert.doesNotMatch(verify, /codex exec|claude --safe-mode/);
76
79
  assert.doesNotMatch(plan, /LEGACY_STATE_MIGRATION_REQUIRED|v4\.4|v4\.5/);
77
80
  assert.match(plan, /never read or\s+write .*tasks\.json/i);
78
81
  assert.doesNotMatch(plan, /ultra-tools task create/);
@@ -176,93 +179,29 @@ test('install writes a schema-safe opencode.json and keeps ownership outside hos
176
179
  assert.match(plugin, /\.ultra[\\/]tasks[\\/]tasks\.json/);
177
180
  assert.match(plugin, /throw new Error/);
178
181
  assert.match(plugin, /session\.compacted/);
182
+ assert.match(plugin, /breadcrumb\.cjs/);
183
+ assert.doesNotMatch(plugin, /workflow-state\.json|context-manifest\.json/);
179
184
  assert.doesNotMatch(plugin, /memory\.(?:retain|recall|reflect)|journal|observation|prompt[_ -]?capture/);
180
- assert.match(plugin, /Ultra context spine/);
185
+ assert.match(plugin, /canonical breadcrumb/);
181
186
  assert.doesNotMatch(plugin, /providerMetadata|External providers/);
182
187
  } finally {
183
188
  fs.rmSync(target, { recursive: true, force: true });
184
189
  }
185
190
  });
186
191
 
187
- test('OpenCode context covers active changes and projection writes are blocked for Ultra projects', async () => {
192
+ test('OpenCode routes a missing authority to init and always protects task projections', async () => {
188
193
  const target = mkTarget();
189
194
  const project = mkTarget();
190
195
  try {
191
196
  opencode.install({ configDir: target, repoRoot: REPO_ROOT });
192
- const pluginFile = path.join(target, 'plugins', 'ultra-builder-pro.js');
193
- const module = await import(pathToFileURL(pluginFile).href);
194
- const plugin = await module.UltraBuilderProPlugin({ directory: project });
195
-
196
197
  fs.mkdirSync(path.join(project, '.ultra'), { recursive: true });
197
- fs.writeFileSync(path.join(project, '.ultra', 'state.db'), 'owned-by-ultra');
198
- execFileSync('git', ['init'], { cwd: project, stdio: 'ignore' });
199
- fs.writeFileSync(path.join(project, 'app.txt'), 'initial\n');
200
- execFileSync('git', ['add', 'app.txt'], { cwd: project, stdio: 'ignore' });
201
- execFileSync('git', ['-c', 'user.name=Ultra Tests', '-c', 'user.email=ultra@example.invalid', 'commit', '-m', 'initial'], {
202
- cwd: project, stdio: 'ignore',
203
- });
204
- const compiledHead = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: project, encoding: 'utf8' }).trim();
205
- const changeRoot = path.join(project, '.ultra', 'changes', 'active', 'daily-fix');
206
- fs.mkdirSync(changeRoot, { recursive: true });
207
- fs.writeFileSync(path.join(changeRoot, 'context-manifest.json'), JSON.stringify({
208
- schema_version: '2.0',
209
- baseline: { id: 'project-baseline', mode: 'brownfield', status: 'ready', repository_revision: compiledHead },
210
- change: { id: 'daily-fix', title: 'Daily fix', kind: 'quick', status: 'active', intent: 'Fix drift' },
211
- role: 'implement',
212
- gate: 'implementation',
213
- next_action: 'Run the exact regression test.',
214
- readiness: {
215
- status: 'ready', blockers: [], warnings: ['CONTEXT_FILE_BUDGET_EXCEEDED'],
216
- },
217
- git: { head: compiledHead },
218
- selected_task: { id: 'daily-task', status: 'in_progress' },
219
- context: { token_estimate: 1200, budget: { max_tokens: 8000, max_files: 8 } },
220
- providers: { memory: { provider: 'cloud-mem', status: 'available' } },
221
- }));
222
-
198
+ const module = await import(`${pathToFileURL(path.join(target, 'plugins', 'ultra-builder-pro.js')).href}?baseline`);
199
+ const plugin = await module.UltraBuilderProPlugin({ directory: project });
223
200
  const output = { system: [] };
224
201
  await plugin['experimental.chat.system.transform']({}, output);
225
- assert.match(output.system.join('\n'), /Ultra context spine/);
226
- assert.match(output.system.join('\n'), /daily-fix/);
227
- assert.match(output.system.join('\n'), /daily-task/);
228
- assert.match(output.system.join('\n'), /Role: implement/);
229
- assert.match(output.system.join('\n'), /Gate: implementation/);
230
- assert.match(output.system.join('\n'), /Readiness: ready/);
231
- assert.match(output.system.join('\n'), /Warnings: CONTEXT_FILE_BUDGET_EXCEEDED/);
232
- assert.match(output.system.join('\n'), /Run the exact regression test/);
233
- assert.doesNotMatch(output.system.join('\n'), /Fix drift|cloud-mem/);
234
-
235
- const manifestFile = path.join(changeRoot, 'context-manifest.json');
236
- const legacyManifest = JSON.parse(fs.readFileSync(manifestFile, 'utf8'));
237
- legacyManifest.schema_version = '1.0';
238
- fs.writeFileSync(manifestFile, JSON.stringify(legacyManifest));
239
- const legacyOutput = { system: [] };
240
- await plugin['experimental.chat.system.transform']({}, legacyOutput);
241
- assert.match(legacyOutput.system.join('\n'), /CONTEXT_SNAPSHOT_UPGRADE_REQUIRED/);
242
- legacyManifest.schema_version = '2.0';
243
- fs.writeFileSync(manifestFile, JSON.stringify(legacyManifest));
244
-
245
- delete legacyManifest.baseline;
246
- fs.writeFileSync(manifestFile, JSON.stringify(legacyManifest));
247
- const missingBaselineOutput = { system: [] };
248
- await plugin['experimental.chat.system.transform']({}, missingBaselineOutput);
249
- assert.match(missingBaselineOutput.system.join('\n'), /CONTEXT_SNAPSHOT_UPGRADE_REQUIRED/);
250
- legacyManifest.baseline = {
251
- id: 'project-baseline', mode: 'brownfield', status: 'ready', repository_revision: compiledHead,
252
- };
253
- fs.writeFileSync(manifestFile, JSON.stringify(legacyManifest));
254
-
255
- fs.writeFileSync(path.join(project, 'app.txt'), 'changed\n');
256
- execFileSync('git', ['add', 'app.txt'], { cwd: project, stdio: 'ignore' });
257
- execFileSync('git', ['-c', 'user.name=Ultra Tests', '-c', 'user.email=ultra@example.invalid', 'commit', '-m', 'change'], {
258
- cwd: project, stdio: 'ignore',
259
- });
260
- const staleOutput = { system: [] };
261
- await plugin['experimental.chat.system.transform']({}, staleOutput);
262
- assert.match(staleOutput.system.join('\n'), /Readiness: blocked/);
263
- assert.match(staleOutput.system.join('\n'), /CONTEXT_HEAD_STALE/);
264
- assert.match(staleOutput.system.join('\n'), /recompile change\.context/);
265
-
202
+ const text = output.system.join('\n');
203
+ assert.match(text, /STATE_DB_MISSING/);
204
+ assert.match(text, /ultra-init/);
266
205
  await assert.rejects(
267
206
  plugin['tool.execute.before'](
268
207
  { tool: 'apply_patch' },
@@ -276,22 +215,69 @@ test('OpenCode context covers active changes and projection writes are blocked f
276
215
  }
277
216
  });
278
217
 
279
- test('OpenCode does not assume a project baseline is ready when no active change manifest exists', async () => {
218
+ test('OpenCode injects the DB breadcrumb and ignores conflicting workflow projections', async () => {
280
219
  const target = mkTarget();
281
220
  const project = mkTarget();
221
+ let state;
282
222
  try {
283
223
  opencode.install({ configDir: target, repoRoot: REPO_ROOT });
284
- fs.mkdirSync(path.join(project, '.ultra'), { recursive: true });
285
- fs.writeFileSync(path.join(project, '.ultra', 'state.db'), 'owned-by-ultra');
286
- const module = await import(`${pathToFileURL(path.join(target, 'plugins', 'ultra-builder-pro.js')).href}?baseline`);
224
+ execFileSync('git', ['init'], { cwd: project, stdio: 'ignore' });
225
+ fs.writeFileSync(path.join(project, 'app.txt'), 'initial\n');
226
+ execFileSync('git', ['add', 'app.txt'], { cwd: project, stdio: 'ignore' });
227
+ execFileSync('git', ['-c', 'user.name=Ultra Tests', '-c', 'user.email=ultra@example.invalid', 'commit', '-m', 'initial'], {
228
+ cwd: project, stdio: 'ignore',
229
+ });
230
+ state = initStateDb(path.join(project, '.ultra', 'state.db'));
231
+ state.db.prepare(
232
+ `INSERT INTO baselines
233
+ (id, project_name, mode, status, approved_by, approval_note, converged_at)
234
+ VALUES ('baseline-db', 'fixture', 'greenfield', 'ready', 'test', 'fixture', ?)`,
235
+ ).run(new Date().toISOString());
236
+ const { change } = changes.createChange(state.db, {
237
+ id: 'db-authority-change', title: 'DB authority', kind: 'quick',
238
+ intent: 'Use one authoritative injected state.',
239
+ docs_impact: { status: 'none', rationale: 'test fixture' },
240
+ }, { rootDir: project });
241
+ const task = createTask(state.db, {
242
+ id: 'db-authority-task', title: 'Use DB authority', type: 'bugfix', priority: 'P0',
243
+ change_id: change.id,
244
+ });
245
+ changes.compileContext(state.db, {
246
+ id: change.id,
247
+ task_id: task.id,
248
+ role: 'implement',
249
+ gate: 'implementation',
250
+ execution_contract: {
251
+ slice_kind: 'tracer_bullet', public_seam: 'OpenCode injection',
252
+ verification_command: 'node --test adapters/tests/opencode.test.cjs',
253
+ },
254
+ next_action: 'Verify the DB-only OpenCode injection.',
255
+ }, { rootDir: project });
256
+
257
+ fs.writeFileSync(path.join(project, '.ultra', 'workflow-state.json'), JSON.stringify({
258
+ command: 'ultra-dev', task_id: 'projection-task', status: 'active', step: 'wrong',
259
+ }));
260
+ const projectionRoot = path.join(project, '.ultra', 'changes', 'active', 'projection-change');
261
+ fs.mkdirSync(projectionRoot, { recursive: true });
262
+ fs.writeFileSync(path.join(projectionRoot, 'context-manifest.json'), JSON.stringify({
263
+ schema_version: '2.0',
264
+ baseline: { id: 'projection-baseline', mode: 'brownfield', status: 'ready' },
265
+ change: { id: 'projection-change', kind: 'quick', status: 'active' },
266
+ selected_task: { id: 'projection-task', status: 'in_progress' },
267
+ readiness: { status: 'ready', blockers: [], warnings: [] },
268
+ }));
269
+
270
+ const module = await import(`${pathToFileURL(path.join(target, 'plugins', 'ultra-builder-pro.js')).href}?db-authority`);
287
271
  const plugin = await module.UltraBuilderProPlugin({ directory: project });
288
272
  const output = { system: [] };
289
273
  await plugin['experimental.chat.system.transform']({}, output);
290
274
  const text = output.system.join('\n');
291
- assert.match(text, /ultra-status/);
292
- assert.match(text, /ultra-init/);
293
- assert.doesNotMatch(text, /Start daily work with the ultra-change workflow/);
275
+ assert.match(text, /Change: db-authority-change/);
276
+ assert.match(text, /Task: db-authority-task/);
277
+ assert.match(text, /Role: implement/);
278
+ assert.doesNotMatch(text, /projection-change|projection-task|projection-baseline/);
294
279
  } finally {
280
+ if (state) closeStateDb(state.db);
295
281
  fs.rmSync(target, { recursive: true, force: true });
296
282
  fs.rmSync(project, { recursive: true, force: true });
297
283
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Initialize a new project or adopt an existing repository into authoritative Ultra baseline state
3
- argument-hint: "[name] [type] [stack] [auto|greenfield|brownfield]"
3
+ argument-hint: "[name] [type] [stack] [auto|greenfield|brownfield] [--scope <path>]"
4
4
  workflow-ref: "@skills/ultra-init/SKILL.md"
5
5
  ---
6
6