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.
- package/.ultra-template/changes/active/.gitkeep +1 -0
- package/.ultra-template/changes/archive/.gitkeep +1 -0
- package/CHANGELOG.md +90 -14
- package/README.md +38 -32
- package/adapters/_shared/codex-assets.cjs +32 -151
- package/adapters/_shared/frontmatter.cjs +2 -2
- package/adapters/_shared/handbook.cjs +10 -10
- package/adapters/_shared/path-rewrite.cjs +0 -3
- package/adapters/_shared/runtime-assets.cjs +13 -4
- package/adapters/_shared/tests/handbook.test.cjs +4 -1
- package/adapters/_shared/tests/path-rewrite.test.cjs +1 -3
- package/adapters/_shared/tests/resolve-target.test.cjs +1 -11
- package/adapters/_shared/tests/runtime-assets.test.cjs +7 -6
- package/adapters/opencode.js +82 -26
- package/adapters/tests/claude.test.cjs +1 -4
- package/adapters/tests/codex.test.cjs +9 -8
- package/adapters/tests/opencode.test.cjs +17 -11
- package/bin/install.js +6 -28
- package/bin/orchestrator.js +12 -30
- package/commands/ultra-change.md +47 -0
- package/commands/ultra-deliver.md +14 -7
- package/commands/ultra-dev.md +6 -3
- package/commands/ultra-doctor.md +36 -0
- package/commands/ultra-init.md +4 -3
- package/commands/ultra-status.md +7 -5
- package/commands/ultra-test.md +2 -1
- package/docs/AGENT-CONTEXT.md +35 -28
- package/docs/ARCHITECTURE.md +49 -21
- package/docs/PLAN.zh-CN.md +67 -68
- package/docs/ROADMAP.md +17 -17
- package/docs/RUNTIME-COMPAT-MATRIX.md +63 -62
- package/hooks/README.md +8 -7
- package/hooks/active_task_context.py +22 -15
- package/hooks/health_check.py +136 -38
- package/hooks/workflow_context.py +120 -30
- package/mcp-server/lib/change-workflow.cjs +556 -0
- package/mcp-server/lib/change-workflow.test.cjs +259 -0
- package/mcp-server/lib/doctor.cjs +112 -0
- package/mcp-server/lib/doctor.test.cjs +86 -0
- package/mcp-server/lib/init-project.cjs +32 -6
- package/mcp-server/lib/pricing.cjs +0 -6
- package/mcp-server/lib/runtime-state.cjs +222 -0
- package/mcp-server/lib/runtime-state.test.cjs +92 -0
- package/mcp-server/lib/state-db.cjs +31 -8
- package/mcp-server/lib/state-ops.cjs +24 -3
- package/mcp-server/lib/telemetry.cjs +4 -0
- package/mcp-server/server.cjs +102 -10
- package/mcp-server/tests/db-ops.test.cjs +5 -1
- package/mcp-server/tests/init-project.test.cjs +45 -0
- package/mcp-server/tests/server-contract.test.cjs +140 -1
- package/mcp-server/tests/state-db.test.cjs +44 -7
- package/mcp-server/tests/state-ops.test.cjs +42 -0
- package/mcp-server/tests/telemetry.test.cjs +14 -0
- package/orchestrator/daemon.cjs +39 -0
- package/orchestrator/dispatch-rules.cjs +15 -4
- package/orchestrator/tests/daemon.test.cjs +63 -12
- package/orchestrator/tests/dispatch-rules.test.cjs +12 -10
- package/package.json +5 -7
- package/settings.json +1 -2
- package/skills/ultra-change/SKILL.md +140 -0
- package/skills/ultra-deliver/SKILL.md +81 -15
- package/skills/ultra-dev/SKILL.md +32 -12
- package/skills/ultra-doctor/SKILL.md +90 -0
- package/skills/ultra-init/SKILL.md +8 -6
- package/skills/ultra-review/SKILL.md +16 -0
- package/skills/ultra-status/SKILL.md +30 -7
- package/skills/ultra-test/SKILL.md +14 -2
- package/skills/ultra-verify/SKILL.md +22 -41
- package/skills/ultra-verify/evals/evals.json +22 -24
- package/skills/ultra-verify/references/confidence-system.md +15 -32
- package/skills/ultra-verify/references/cross-verify-modes.md +14 -59
- package/skills/ultra-verify/scripts/verify_wait.py +44 -81
- package/spec/cli-protocol.md +12 -1
- package/spec/mcp-tools.yaml +340 -4
- package/spec/schemas/mcp-tools.schema.json +1 -1
- package/spec/schemas/skill-manifest.schema.json +2 -2
- package/spec/schemas/state-db.sql +119 -2
- package/ultra-tools/cli.cjs +3 -0
- package/ultra-tools/commands/system.cjs +57 -0
- package/ultra-tools/system-command.test.cjs +53 -0
- package/adapters/_shared/md-to-toml.cjs +0 -54
- package/adapters/_shared/rtk-detect.cjs +0 -86
- package/adapters/_shared/tests/md-to-toml.test.cjs +0 -48
- package/adapters/_shared/tests/rtk-detect.test.cjs +0 -116
- package/adapters/gemini.js +0 -204
- package/adapters/tests/gemini.test.cjs +0 -112
- package/docs/LEGACY-HERMES.md +0 -1175
- package/orchestrator/code-graph-watcher.cjs +0 -121
- package/orchestrator/tests/code-graph-watcher.test.cjs +0 -183
- package/skills/gemini-collab/SKILL.md +0 -51
- package/skills/gemini-collab/evals/evals.json +0 -42
package/orchestrator/daemon.cjs
CHANGED
|
@@ -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', '
|
|
18
|
-
sonnet: ['claude', 'codex', 'opencode'
|
|
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(
|
|
86
|
+
if (rule.when(normalizedCtx)) {
|
|
79
87
|
let runtime = null;
|
|
80
88
|
if (rule.action === 'spawn_agent') {
|
|
81
|
-
runtime = rule.resolve ? rule.resolve(
|
|
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'
|
|
59
|
+
['claude', 'opencode', 'codex'],
|
|
59
60
|
);
|
|
60
|
-
assert.
|
|
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'
|
|
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'
|
|
75
|
+
['claude', 'opencode', 'codex'],
|
|
75
76
|
);
|
|
76
|
-
assert.ok(['claude', 'codex', 'opencode'
|
|
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'
|
|
83
|
+
['opencode'],
|
|
83
84
|
);
|
|
84
85
|
// Prefers claude/codex but those aren't available → fall back to any.
|
|
85
|
-
assert.
|
|
86
|
+
assert.equal(runtime, 'opencode');
|
|
86
87
|
});
|
|
87
88
|
|
|
88
|
-
test('routeTask: no hint
|
|
89
|
-
const runtime = daemon.routeTask({ id: 't' }, [
|
|
90
|
-
assert.
|
|
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'
|
|
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
|
|
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'
|
|
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'
|
|
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:
|
|
62
|
+
test('evaluate: retired runtimes are discarded before fallback routing', () => {
|
|
62
63
|
const d = evaluate(baseCtx({
|
|
63
64
|
task: { id: 't' },
|
|
64
|
-
available_runtimes: [
|
|
65
|
+
available_runtimes: [RETIRED_RUNTIME, 'claude'],
|
|
65
66
|
}));
|
|
66
67
|
assert.equal(d.action, 'spawn_agent');
|
|
67
|
-
assert.equal(d.runtime, '
|
|
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
|
|
101
|
+
test('evaluate: custom rules cannot select an unsupported runtime', () => {
|
|
101
102
|
const customRules = [
|
|
102
103
|
...DEFAULT_RULES,
|
|
103
104
|
{
|
|
104
|
-
id: 'force-
|
|
105
|
+
id: 'force-retired-runtime',
|
|
105
106
|
priority: 999,
|
|
106
107
|
when: () => true,
|
|
107
108
|
action: 'spawn_agent',
|
|
108
|
-
resolve: () =>
|
|
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.
|
|
113
|
-
assert.equal(d.
|
|
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.
|
|
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,
|
|
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
|
@@ -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.
|