ultra-builder-pro-cli 0.5.3 → 0.7.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 (91) hide show
  1. package/.ultra-template/changes/active/.gitkeep +1 -0
  2. package/.ultra-template/changes/archive/.gitkeep +1 -0
  3. package/CHANGELOG.md +90 -14
  4. package/README.md +38 -32
  5. package/adapters/_shared/codex-assets.cjs +32 -151
  6. package/adapters/_shared/frontmatter.cjs +2 -2
  7. package/adapters/_shared/handbook.cjs +10 -10
  8. package/adapters/_shared/path-rewrite.cjs +0 -3
  9. package/adapters/_shared/runtime-assets.cjs +13 -4
  10. package/adapters/_shared/tests/handbook.test.cjs +4 -1
  11. package/adapters/_shared/tests/path-rewrite.test.cjs +1 -3
  12. package/adapters/_shared/tests/resolve-target.test.cjs +1 -11
  13. package/adapters/_shared/tests/runtime-assets.test.cjs +7 -6
  14. package/adapters/opencode.js +82 -26
  15. package/adapters/tests/claude.test.cjs +1 -4
  16. package/adapters/tests/codex.test.cjs +9 -8
  17. package/adapters/tests/opencode.test.cjs +17 -11
  18. package/bin/install.js +6 -28
  19. package/bin/orchestrator.js +12 -30
  20. package/commands/ultra-change.md +47 -0
  21. package/commands/ultra-deliver.md +14 -7
  22. package/commands/ultra-dev.md +6 -3
  23. package/commands/ultra-doctor.md +36 -0
  24. package/commands/ultra-init.md +4 -3
  25. package/commands/ultra-status.md +7 -5
  26. package/commands/ultra-test.md +2 -1
  27. package/docs/AGENT-CONTEXT.md +35 -28
  28. package/docs/ARCHITECTURE.md +49 -21
  29. package/docs/PLAN.zh-CN.md +67 -68
  30. package/docs/ROADMAP.md +17 -17
  31. package/docs/RUNTIME-COMPAT-MATRIX.md +63 -62
  32. package/hooks/README.md +8 -7
  33. package/hooks/active_task_context.py +22 -15
  34. package/hooks/health_check.py +136 -38
  35. package/hooks/workflow_context.py +120 -30
  36. package/mcp-server/lib/change-workflow.cjs +556 -0
  37. package/mcp-server/lib/change-workflow.test.cjs +259 -0
  38. package/mcp-server/lib/doctor.cjs +112 -0
  39. package/mcp-server/lib/doctor.test.cjs +86 -0
  40. package/mcp-server/lib/init-project.cjs +32 -6
  41. package/mcp-server/lib/pricing.cjs +0 -6
  42. package/mcp-server/lib/runtime-state.cjs +222 -0
  43. package/mcp-server/lib/runtime-state.test.cjs +92 -0
  44. package/mcp-server/lib/state-db.cjs +31 -8
  45. package/mcp-server/lib/state-ops.cjs +24 -3
  46. package/mcp-server/lib/telemetry.cjs +4 -0
  47. package/mcp-server/server.cjs +102 -10
  48. package/mcp-server/tests/db-ops.test.cjs +5 -1
  49. package/mcp-server/tests/init-project.test.cjs +45 -0
  50. package/mcp-server/tests/server-contract.test.cjs +140 -1
  51. package/mcp-server/tests/state-db.test.cjs +44 -7
  52. package/mcp-server/tests/state-ops.test.cjs +42 -0
  53. package/mcp-server/tests/telemetry.test.cjs +14 -0
  54. package/orchestrator/daemon.cjs +39 -0
  55. package/orchestrator/dispatch-rules.cjs +15 -4
  56. package/orchestrator/tests/daemon.test.cjs +63 -12
  57. package/orchestrator/tests/dispatch-rules.test.cjs +12 -10
  58. package/package.json +5 -7
  59. package/settings.json +1 -2
  60. package/skills/ultra-change/SKILL.md +140 -0
  61. package/skills/ultra-deliver/SKILL.md +81 -15
  62. package/skills/ultra-dev/SKILL.md +32 -12
  63. package/skills/ultra-doctor/SKILL.md +90 -0
  64. package/skills/ultra-init/SKILL.md +8 -6
  65. package/skills/ultra-review/SKILL.md +16 -0
  66. package/skills/ultra-status/SKILL.md +30 -7
  67. package/skills/ultra-test/SKILL.md +14 -2
  68. package/skills/ultra-verify/SKILL.md +22 -41
  69. package/skills/ultra-verify/evals/evals.json +22 -24
  70. package/skills/ultra-verify/references/confidence-system.md +15 -32
  71. package/skills/ultra-verify/references/cross-verify-modes.md +14 -59
  72. package/skills/ultra-verify/scripts/verify_wait.py +44 -81
  73. package/spec/cli-protocol.md +12 -1
  74. package/spec/mcp-tools.yaml +340 -4
  75. package/spec/schemas/mcp-tools.schema.json +1 -1
  76. package/spec/schemas/skill-manifest.schema.json +2 -2
  77. package/spec/schemas/state-db.sql +119 -2
  78. package/ultra-tools/cli.cjs +3 -0
  79. package/ultra-tools/commands/system.cjs +57 -0
  80. package/ultra-tools/system-command.test.cjs +53 -0
  81. package/adapters/_shared/md-to-toml.cjs +0 -54
  82. package/adapters/_shared/rtk-detect.cjs +0 -86
  83. package/adapters/_shared/tests/md-to-toml.test.cjs +0 -48
  84. package/adapters/_shared/tests/rtk-detect.test.cjs +0 -116
  85. package/adapters/gemini.js +0 -204
  86. package/adapters/tests/gemini.test.cjs +0 -112
  87. package/docs/LEGACY-HERMES.md +0 -1175
  88. package/orchestrator/code-graph-watcher.cjs +0 -121
  89. package/orchestrator/tests/code-graph-watcher.test.cjs +0 -183
  90. package/skills/gemini-collab/SKILL.md +0 -51
  91. package/skills/gemini-collab/evals/evals.json +0 -42
@@ -12,7 +12,10 @@
12
12
  // calling runDaemon — the runtime layer itself has no opt-in concept.
13
13
 
14
14
  const ops = require('../mcp-server/lib/state-ops.cjs');
15
+ const runtimeState = require('../mcp-server/lib/runtime-state.cjs');
16
+ const projector = require('../mcp-server/lib/projector.cjs');
15
17
  const runner = require('./session-runner.cjs');
18
+ const recovery = require('./recovery.cjs');
16
19
  const { evaluate, DEFAULT_RULES, ROUTE_PREFERENCES } = require('./dispatch-rules.cjs');
17
20
 
18
21
  // Phase 8B.1 — routeTask is now a thin wrapper over evaluate() so Phase 5.4
@@ -29,6 +32,19 @@ function routeTask(task, availableRuntimes) {
29
32
  return d.action === 'spawn_agent' ? d.runtime : null;
30
33
  }
31
34
 
35
+ function maintainState({ db, repoRoot, project = projector.projectAll } = {}) {
36
+ if (!db) throw new Error('maintainState: db required');
37
+ if (!repoRoot) throw new Error('maintainState: repoRoot required');
38
+ const cursor = runtimeState.readConsumerCursor(db, 'spec-staleness');
39
+ const staleness = ops.consumeSpecChangedEvents(db, { since_id: cursor, limit: 500 });
40
+ runtimeState.writeConsumerCursor(db, 'spec-staleness', staleness.next_since_id);
41
+ const ensured = runtimeState.ensureProjectionJob(db, { tool_name: 'orchestrator.maintenance' });
42
+ const projections = runtimeState.processProjectionJobs(db, {
43
+ rootDir: repoRoot, project, limit: 500,
44
+ });
45
+ return { staleness, ensured, projections };
46
+ }
47
+
32
48
  function runDaemon({
33
49
  db,
34
50
  repoRoot,
@@ -47,9 +63,30 @@ function runDaemon({
47
63
 
48
64
  let stopped = false;
49
65
  const children = [];
66
+ let bootRecovery;
67
+ try {
68
+ bootRecovery = recovery.recoverOnBoot(db);
69
+ } catch (error) {
70
+ runtimeState.recordIncident(db, {
71
+ code: 'BOOT_RECOVERY_FAILED', severity: 'critical', retryable: true,
72
+ message: error.message, source_kind: 'orchestrator', source_id: 'boot',
73
+ });
74
+ if (onError) onError(error); else throw error;
75
+ bootRecovery = { recovered: [], count: 0, error: error.message };
76
+ }
50
77
 
51
78
  function tick() {
52
79
  if (stopped) return;
80
+ try {
81
+ maintainState({ db, repoRoot });
82
+ } catch (error) {
83
+ runtimeState.recordIncident(db, {
84
+ code: 'RUNTIME_MAINTENANCE_FAILED', severity: 'error', retryable: true,
85
+ message: error.message, source_kind: 'orchestrator', source_id: 'maintenance',
86
+ });
87
+ if (onError) onError(error); else throw error;
88
+ return;
89
+ }
53
90
  let pending;
54
91
  try {
55
92
  const filter = { status: 'pending' };
@@ -102,6 +139,7 @@ function runDaemon({
102
139
  },
103
140
  get running() { return !stopped; },
104
141
  get children() { return children.slice(); },
142
+ get bootRecovery() { return bootRecovery; },
105
143
  };
106
144
  }
107
145
 
@@ -109,4 +147,5 @@ module.exports = {
109
147
  runDaemon,
110
148
  routeTask,
111
149
  ROUTE_PREFERENCES,
150
+ maintainState,
112
151
  };
@@ -13,9 +13,11 @@
13
13
  // parallel orchestrator (8B.2) gets a richer decision surface: wave state,
14
14
  // deps readiness, and custom rule injection.
15
15
 
16
+ const { isSupportedRuntime } = require('../adapters/_shared/runtime-assets.cjs');
17
+
16
18
  const ROUTE_PREFERENCES = Object.freeze({
17
- haiku: ['opencode', 'gemini', 'claude', 'codex'],
18
- sonnet: ['claude', 'codex', 'opencode', 'gemini'],
19
+ haiku: ['opencode', 'claude', 'codex'],
20
+ sonnet: ['claude', 'codex', 'opencode'],
19
21
  opus: ['claude', 'codex'],
20
22
  });
21
23
 
@@ -73,12 +75,21 @@ const DEFAULT_RULES = Object.freeze([
73
75
  ]);
74
76
 
75
77
  function evaluate(ctx, rules = DEFAULT_RULES) {
78
+ const normalizedCtx = {
79
+ ...ctx,
80
+ available_runtimes: Array.isArray(ctx && ctx.available_runtimes)
81
+ ? [...new Set(ctx.available_runtimes.filter(isSupportedRuntime))]
82
+ : [],
83
+ };
76
84
  const sorted = [...rules].sort((a, b) => (b.priority || 0) - (a.priority || 0));
77
85
  for (const rule of sorted) {
78
- if (rule.when(ctx)) {
86
+ if (rule.when(normalizedCtx)) {
79
87
  let runtime = null;
80
88
  if (rule.action === 'spawn_agent') {
81
- runtime = rule.resolve ? rule.resolve(ctx) : (rule.runtime || null);
89
+ runtime = rule.resolve ? rule.resolve(normalizedCtx) : (rule.runtime || null);
90
+ if (!isSupportedRuntime(runtime)) {
91
+ return { rule_id: rule.id, action: 'block', runtime: null };
92
+ }
82
93
  }
83
94
  return { rule_id: rule.id, action: rule.action, runtime };
84
95
  }
@@ -15,6 +15,7 @@ const { execFileSync } = require('node:child_process');
15
15
  const { initStateDb, closeStateDb } = require('../../mcp-server/lib/state-db.cjs');
16
16
  const ops = require('../../mcp-server/lib/state-ops.cjs');
17
17
  const daemon = require('../daemon.cjs');
18
+ const RETIRED_RUNTIME = ['gem', 'ini'].join('');
18
19
 
19
20
  const LONG_SLEEP_CMD = process.execPath;
20
21
  const LONG_SLEEP_ARGS = ['-e', 'setInterval(() => {}, 60000);'];
@@ -55,15 +56,15 @@ function cleanup(repoRoot, db, handle) {
55
56
  test('routeTask: haiku hint prefers cheap runtimes', () => {
56
57
  const runtime = daemon.routeTask(
57
58
  { id: 't', complexity_hint: 'haiku' },
58
- ['claude', 'opencode', 'codex', 'gemini'],
59
+ ['claude', 'opencode', 'codex'],
59
60
  );
60
- assert.ok(['opencode', 'gemini'].includes(runtime), `got ${runtime}`);
61
+ assert.equal(runtime, 'opencode');
61
62
  });
62
63
 
63
64
  test('routeTask: opus hint prefers strong runtimes', () => {
64
65
  const runtime = daemon.routeTask(
65
66
  { id: 't', complexity_hint: 'opus' },
66
- ['claude', 'opencode', 'codex', 'gemini'],
67
+ ['claude', 'opencode', 'codex'],
67
68
  );
68
69
  assert.ok(['claude', 'codex'].includes(runtime), `got ${runtime}`);
69
70
  });
@@ -71,23 +72,23 @@ test('routeTask: opus hint prefers strong runtimes', () => {
71
72
  test('routeTask: sonnet hint middle-ground', () => {
72
73
  const runtime = daemon.routeTask(
73
74
  { id: 't', complexity_hint: 'sonnet' },
74
- ['claude', 'opencode', 'codex', 'gemini'],
75
+ ['claude', 'opencode', 'codex'],
75
76
  );
76
- assert.ok(['claude', 'codex', 'opencode', 'gemini'].includes(runtime));
77
+ assert.ok(['claude', 'codex', 'opencode'].includes(runtime));
77
78
  });
78
79
 
79
80
  test('routeTask: respects availability constraint', () => {
80
81
  const runtime = daemon.routeTask(
81
82
  { id: 't', complexity_hint: 'opus' },
82
- ['opencode', 'gemini'],
83
+ ['opencode'],
83
84
  );
84
85
  // Prefers claude/codex but those aren't available → fall back to any.
85
- assert.ok(['opencode', 'gemini'].includes(runtime));
86
+ assert.equal(runtime, 'opencode');
86
87
  });
87
88
 
88
- test('routeTask: no hint → first available', () => {
89
- const runtime = daemon.routeTask({ id: 't' }, ['gemini', 'claude']);
90
- assert.ok(['gemini', 'claude'].includes(runtime));
89
+ test('routeTask: no hint skips unsupported runtimes', () => {
90
+ const runtime = daemon.routeTask({ id: 't' }, [RETIRED_RUNTIME, 'claude']);
91
+ assert.equal(runtime, 'claude');
91
92
  });
92
93
 
93
94
  test('routeTask: no runtimes available → null', () => {
@@ -244,7 +245,7 @@ test('runDaemon applies complexity_hint route', async () => {
244
245
  });
245
246
  handle = daemon.runDaemon({
246
247
  db, repoRoot,
247
- runtimes: ['claude', 'opencode', 'gemini'],
248
+ runtimes: ['claude', 'opencode'],
248
249
  pollMs: 50,
249
250
  command: LONG_SLEEP_CMD,
250
251
  commandArgs: LONG_SLEEP_ARGS,
@@ -252,9 +253,59 @@ test('runDaemon applies complexity_hint route', async () => {
252
253
  await new Promise((r) => setTimeout(r, 300));
253
254
  const sessions = db.prepare("SELECT * FROM sessions WHERE task_id = 'd-opus'").all();
254
255
  assert.equal(sessions.length, 1);
255
- // opus should prefer claude over opencode/gemini.
256
+ // opus should prefer claude over opencode.
256
257
  assert.equal(sessions[0].runtime, 'claude');
257
258
  } finally {
258
259
  cleanup(repoRoot, db, handle);
259
260
  }
260
261
  });
262
+
263
+ test('maintainState consumes spec changes with a durable cursor', () => {
264
+ const repoRoot = mkRepo();
265
+ const db = mkDb(repoRoot);
266
+ try {
267
+ ops.createTask(db, {
268
+ id: 'maintain-stale', title: 'stale target', type: 'feature', priority: 'P1',
269
+ trace_to: '.ultra/specs/product.md#daily-change',
270
+ });
271
+ ops.appendEvent(db, {
272
+ type: 'spec_changed', payload: { sections: ['.ultra/specs/product.md#daily-change'] },
273
+ });
274
+ const first = daemon.maintainState({ db, repoRoot });
275
+ assert.equal(first.staleness.processed, 1);
276
+ assert.equal(ops.readTask(db, 'maintain-stale').stale, true);
277
+ const second = daemon.maintainState({ db, repoRoot });
278
+ assert.equal(second.staleness.processed, 0);
279
+ } finally {
280
+ cleanup(repoRoot, db);
281
+ }
282
+ });
283
+
284
+ test('runDaemon performs crash recovery before dispatch polling', async () => {
285
+ const repoRoot = mkRepo();
286
+ const db = mkDb(repoRoot);
287
+ let handle;
288
+ try {
289
+ ops.createTask(db, { id: 'recover-boot', title: 'recover boot', type: 'bugfix', priority: 'P0' });
290
+ ops.updateTaskStatus(db, 'recover-boot', 'in_progress');
291
+ ops.createSession(db, {
292
+ sid: 'dead-session', task_id: 'recover-boot', runtime: 'claude', pid: 999999,
293
+ worktree_path: '/tmp/dead-worktree', artifact_dir: '/tmp/dead-artifact', lease_seconds: 1,
294
+ });
295
+ db.prepare(
296
+ "UPDATE sessions SET heartbeat_at = '2000-01-01T00:00:00.000Z', lease_expires_at = '2000-01-01T00:00:00.000Z' WHERE sid = 'dead-session'",
297
+ ).run();
298
+
299
+ handle = daemon.runDaemon({
300
+ db, repoRoot, runtimes: ['claude'], pollMs: 50,
301
+ command: LONG_SLEEP_CMD, commandArgs: LONG_SLEEP_ARGS,
302
+ });
303
+ await new Promise((resolve) => setTimeout(resolve, 100));
304
+ const session = ops.readSession(db, 'dead-session');
305
+ assert.equal(session.status, 'crashed');
306
+ const failure = db.prepare("SELECT * FROM circuit_breaker WHERE task_id = 'recover-boot'").get();
307
+ assert.equal(failure.failure_count, 1);
308
+ } finally {
309
+ cleanup(repoRoot, db, handle);
310
+ }
311
+ });
@@ -11,12 +11,13 @@ const { test } = require('node:test');
11
11
  const assert = require('node:assert/strict');
12
12
 
13
13
  const { evaluate, DEFAULT_RULES, ROUTE_PREFERENCES } = require('../dispatch-rules.cjs');
14
+ const RETIRED_RUNTIME = ['gem', 'ini'].join('');
14
15
 
15
16
  function baseCtx(overrides = {}) {
16
17
  return {
17
18
  task: { id: 't1' },
18
19
  deps_ready: true,
19
- available_runtimes: ['claude', 'opencode', 'codex', 'gemini'],
20
+ available_runtimes: ['claude', 'opencode', 'codex'],
20
21
  breaker_state: 'ok',
21
22
  wave: null,
22
23
  ...overrides,
@@ -42,7 +43,7 @@ test('evaluate: opus hint + only codex available → codex', () => {
42
43
  test('evaluate: opus hint + preference exhausted → first available fallback', () => {
43
44
  const d = evaluate(baseCtx({
44
45
  task: { id: 't', complexity_hint: 'opus' },
45
- available_runtimes: ['opencode', 'gemini'],
46
+ available_runtimes: ['opencode'],
46
47
  }));
47
48
  assert.equal(d.action, 'spawn_agent');
48
49
  assert.equal(d.runtime, 'opencode');
@@ -58,13 +59,13 @@ test('evaluate: sonnet hint + all available → claude', () => {
58
59
  assert.equal(d.runtime, 'claude');
59
60
  });
60
61
 
61
- test('evaluate: no hint → first available fallback', () => {
62
+ test('evaluate: retired runtimes are discarded before fallback routing', () => {
62
63
  const d = evaluate(baseCtx({
63
64
  task: { id: 't' },
64
- available_runtimes: ['gemini', 'claude'],
65
+ available_runtimes: [RETIRED_RUNTIME, 'claude'],
65
66
  }));
66
67
  assert.equal(d.action, 'spawn_agent');
67
- assert.equal(d.runtime, 'gemini');
68
+ assert.equal(d.runtime, 'claude');
68
69
  });
69
70
 
70
71
  // ─── block / defer paths (new in 8B.1) ────────────────────────────────────
@@ -97,20 +98,21 @@ test('evaluate: serial wave already has running task → defer', () => {
97
98
 
98
99
  // ─── composability: custom rules override defaults ────────────────────────
99
100
 
100
- test('evaluate: custom high-priority rule overrides defaults', () => {
101
+ test('evaluate: custom rules cannot select an unsupported runtime', () => {
101
102
  const customRules = [
102
103
  ...DEFAULT_RULES,
103
104
  {
104
- id: 'force-gemini',
105
+ id: 'force-retired-runtime',
105
106
  priority: 999,
106
107
  when: () => true,
107
108
  action: 'spawn_agent',
108
- resolve: () => 'gemini',
109
+ resolve: () => RETIRED_RUNTIME,
109
110
  },
110
111
  ];
111
112
  const d = evaluate(baseCtx({ task: { id: 't', complexity_hint: 'opus' } }), customRules);
112
- assert.equal(d.runtime, 'gemini');
113
- assert.equal(d.rule_id, 'force-gemini');
113
+ assert.equal(d.action, 'block');
114
+ assert.equal(d.runtime, null);
115
+ assert.equal(d.rule_id, 'force-retired-runtime');
114
116
  });
115
117
 
116
118
  // ─── ROUTE_PREFERENCES shape guard (regression: don't drift from Phase 5.4) ─
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ultra-builder-pro-cli",
3
- "version": "0.5.3",
4
- "description": "Multi-runtime installer for the Ultra Builder Pro agent engineering system — deploys commands, agents, skills, hooks, and MCP server to Claude Code, OpenCode, Codex CLI, and Gemini CLI.",
3
+ "version": "0.7.0",
4
+ "description": "Multi-runtime installer for the Ultra Builder Pro agent engineering system — deploys commands, agents, skills, hooks, and MCP server to Claude Code, OpenCode, and Codex CLI.",
5
5
  "bin": {
6
6
  "ultra-builder-pro-cli": "bin/install.js",
7
7
  "ubp": "bin/install.js",
@@ -27,13 +27,14 @@
27
27
  "skills/ultra-deliver",
28
28
  "skills/ultra-status",
29
29
  "skills/ultra-think",
30
+ "skills/ultra-change",
31
+ "skills/ultra-doctor",
30
32
  "skills/code-review-expert",
31
33
  "skills/security-rules",
32
34
  "skills/integration-rules",
33
35
  "skills/testing-rules",
34
36
  "skills/cc-collab",
35
37
  "skills/codex-collab",
36
- "skills/gemini-collab",
37
38
  "skills/ultra-verify",
38
39
  "hooks/*.py",
39
40
  "hooks/adapters/*.py",
@@ -50,7 +51,6 @@
50
51
  "docs/ARCHITECTURE.md",
51
52
  "docs/AGENT-CONTEXT.md",
52
53
  "docs/COMMIT-HASH-BACKFILL.md",
53
- "docs/LEGACY-HERMES.md",
54
54
  "docs/PLAN.zh-CN.md",
55
55
  "docs/STATE-DB-ACCESS-POLICY.md",
56
56
  "docs/USER-HANDBOOK-CONTRACT.md",
@@ -60,7 +60,6 @@
60
60
  "claude-code",
61
61
  "opencode",
62
62
  "codex",
63
- "gemini",
64
63
  "agent",
65
64
  "cli",
66
65
  "multi-runtime",
@@ -87,7 +86,7 @@
87
86
  "test:state": "node --test 'mcp-server/**/*.test.cjs'",
88
87
  "test:orch": "node --test 'orchestrator/**/*.test.cjs'",
89
88
  "test:spec": "node spec/scripts/test-all.cjs",
90
- "test:rest": "node --test adapters/_shared/tests/*.test.cjs adapters/tests/*.test.cjs ultra-tools/*.test.cjs tests/conformance/**/*.test.cjs tests/install.test.cjs tests/package-smoke.test.cjs",
89
+ "test:rest": "node --test adapters/_shared/tests/*.test.cjs adapters/tests/*.test.cjs ultra-tools/*.test.cjs tests/conformance/**/*.test.cjs tests/install.test.cjs tests/package-smoke.test.cjs tests/retired-runtime.test.cjs",
91
90
  "test:all": "npm run test:state && npm run test:orch && npm run test:spec && npm run test:rest",
92
91
  "test:hooks": "pytest hooks/tests -q",
93
92
  "verify:release": "npm run test:all && npm run test:hooks && npm audit --omit=dev --audit-level=high",
@@ -102,7 +101,6 @@
102
101
  "ajv": "^8.17.1",
103
102
  "ajv-formats": "^3.0.1",
104
103
  "better-sqlite3": "^11.5.0",
105
- "chokidar": "^3.6.0",
106
104
  "js-yaml": "^4.1.0",
107
105
  "openai": "^6.34.0"
108
106
  }
package/settings.json CHANGED
@@ -2,7 +2,6 @@
2
2
  "_comment": "Ultra Builder Pro runtime settings. Host hooks and plugin permissions live in each native plugin manifest.",
3
3
  "cleanupPeriodDays": 30,
4
4
  "orchestrator": {
5
- "auto_dispatch": false,
6
- "graph_watcher": false
5
+ "auto_dispatch": false
7
6
  }
8
7
  }
@@ -0,0 +1,140 @@
1
+ ---
2
+ name: ultra-change
3
+ description: "Start or resume a continuous post-delivery change, compile bounded context, declare documentation impact, and route the change through implementation and convergence."
4
+ runtime: all
5
+ mcp_tools_required:
6
+ - change.create
7
+ - change.update
8
+ - change.get
9
+ - change.list
10
+ - change.context
11
+ - task.create
12
+ - task.list
13
+ ---
14
+
15
+ # ultra-change — Continuous Change Entry
16
+
17
+ Use this workflow for daily fixes, small features, redesigns, and incidents after
18
+ the initial `init → research → plan → dev → test → deliver` baseline exists.
19
+ Each piece of work becomes one durable change unit instead of an informal edit
20
+ that drifts away from specifications and verification evidence.
21
+
22
+ ## Authority and provider boundary
23
+
24
+ - `.ultra/state.db` is authoritative for the change, its tasks, events, incidents,
25
+ context snapshots, and projection status.
26
+ - `.ultra/changes/active/<change-id>/` contains inspectable artifacts, not a
27
+ second state authority.
28
+ - Memory and code graph are external providers. Pass only metadata references:
29
+ `provider`, `project`, `reference`, `revision`, `indexed_head`, and `status`.
30
+ - Never paste recalled memories, graph nodes, embeddings, summaries, or provider
31
+ payloads into `provider_refs`. If provider data is unavailable, record
32
+ `status: unavailable` and continue with current-checkout evidence.
33
+ - If any required change/task MCP operation is unavailable, stop. Do not edit
34
+ `state.db`, `tasks.json`, or context projections directly.
35
+
36
+ ## Arguments
37
+
38
+ - `$1`: existing change id, or a short title for a new change.
39
+ - Optional flags/concepts: `quick`, `standard`, `major`, `incident` and an
40
+ explicit documentation impact.
41
+
42
+ ## 1. Resume before creating
43
+
44
+ Call `change.list` for `active` and `blocked` changes. If `$1` matches an
45
+ existing id, call `change.get` and resume it. If the request clearly belongs to
46
+ an active change, show the match and continue there rather than creating a
47
+ duplicate.
48
+
49
+ Do not resume `ready`, `archived`, or `cancelled` work as a new mutation. Create
50
+ a follow-up change that links to the prior archive in its intent.
51
+
52
+ ## 2. Classify the change
53
+
54
+ Choose the smallest honest kind:
55
+
56
+ | Kind | Use when | Required convergence evidence |
57
+ |---|---|---|
58
+ | `quick` | localized fix or tiny behavior change | diff, tests, spec |
59
+ | `standard` | normal feature or cross-file change | diff, tests, spec, docs, review |
60
+ | `major` | architecture, public contract, migration, or broad redesign | diff, tests, spec, docs, review |
61
+ | `incident` | production/runtime failure requiring diagnosis | diagnosis, diff, tests |
62
+
63
+ When uncertain between two kinds, choose the higher-risk kind. Classification
64
+ changes the proof required; it must not be used to bypass evidence.
65
+
66
+ ## 3. Declare intent and documentation impact
67
+
68
+ Before implementation, write a concise intent with:
69
+
70
+ - observed problem or requested outcome;
71
+ - acceptance behavior and non-goals;
72
+ - affected contract/surface;
73
+ - recovery or rollback concern when material.
74
+
75
+ Documentation impact is mandatory:
76
+
77
+ - `required`: list every baseline document expected to change;
78
+ - `none`: provide a concrete rationale;
79
+ - `unknown`: allowed only while investigating and blocks convergence.
80
+
81
+ Create the change with `change.create`. Use a stable kebab-case id. External
82
+ provider references are optional and metadata-only.
83
+
84
+ ## 4. Build the delta packet
85
+
86
+ The change directory is the bounded execution packet:
87
+
88
+ ```text
89
+ .ultra/changes/active/<change-id>/
90
+ intent.md
91
+ context-manifest.json
92
+ delta/ # required for standard and major
93
+ plan.md # required for standard and major
94
+ verification.md # generated by convergence
95
+ ```
96
+
97
+ For `standard` and `major`, write delta documents under `delta/` describing
98
+ what changes relative to the current baseline. Do not copy the whole baseline.
99
+ Write `plan.md` with live entry points, files/contracts, tests, documentation,
100
+ and rollback. For `quick` and `incident`, add only artifacts needed to make the
101
+ decision and evidence inspectable.
102
+
103
+ Create at least one task through `task.create` with `change_id` set. Split only
104
+ when ownership, dependencies, or independent verification justify it. All task
105
+ status changes remain MCP-authoritative.
106
+
107
+ ## 5. Compile task context
108
+
109
+ Call `change.context` before implementation and again whenever any of these
110
+ changes: git HEAD, task set, relevant specs, allowed paths, or provider revision.
111
+ Supply only the minimum task/spec/path references needed for the current slice.
112
+
113
+ Inspect `context-manifest.json` before handing work to an agent. A stale git head
114
+ or task set is a hard signal to recompile. Provider `status: stale` is visible
115
+ context, not permission to silently refresh or mutate the external provider.
116
+
117
+ ## 6. Route execution
118
+
119
+ - Implement one linked task with `/ultra-dev <task-id>`.
120
+ - Run `/ultra-test` for project-level evidence.
121
+ - Run `/ultra-review` when the change kind requires review.
122
+ - Update baseline docs declared in `docs_impact` during the change, not after
123
+ memory has gone cold.
124
+ - Finish with `/ultra-deliver`; it runs deterministic change convergence and
125
+ archive reconciliation before release actions.
126
+
127
+ If work exposes a new requirement, update the change intent/delta and recompile
128
+ context before continuing. Never let code become the only record of the change.
129
+
130
+ ## 7. Report
131
+
132
+ Return the change id, kind, artifact root, linked tasks, documentation impact,
133
+ context freshness, provider-reference status, and exact next workflow. Separate
134
+ provider unavailability from an Ultra runtime incident.
135
+
136
+ ## What this skill does not do
137
+
138
+ - It does not implement, test, review, release, or repair runtime state.
139
+ - It does not own or synchronize Memory/code-graph content.
140
+ - It does not bypass user approval for destructive or external release actions.