ultra-builder-pro-cli 0.14.0 → 0.16.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/docs/research/README.md +11 -7
- package/.ultra-template/specs/architecture.md +36 -36
- package/.ultra-template/specs/discovery.md +46 -33
- package/.ultra-template/specs/product.md +38 -29
- package/.ultra-template/specs/research-distillate.md +48 -0
- package/.ultra-template/tasks/contexts/TEMPLATE.md +11 -28
- package/CHANGELOG.md +77 -1
- package/README.md +46 -24
- package/adapters/_shared/codex-assets.cjs +6 -2
- package/adapters/_shared/file-ops.cjs +6 -2
- package/adapters/_shared/handbook.cjs +2 -1
- package/adapters/claude.js +3 -2
- package/adapters/codex.js +1 -0
- package/adapters/kimi.js +3 -2
- package/adapters/opencode.js +3 -2
- package/agents/review-coordinator.md +9 -6
- package/commands/ultra-change.md +1 -1
- package/commands/ultra-plan.md +1 -1
- package/commands/ultra-research.md +1 -1
- package/commands/ultra-think.md +2 -2
- package/docs/AGENT-CONTEXT.md +43 -19
- package/docs/ARCHITECTURE.md +54 -13
- package/docs/PLAN.zh-CN.md +4 -2
- package/docs/ROADMAP.md +10 -4
- package/docs/RUNTIME-COMPAT-MATRIX.md +4 -4
- package/docs/STATE-DB-ACCESS-POLICY.md +33 -29
- package/docs/USER-HANDBOOK-CONTRACT.md +8 -2
- package/docs/WORKFLOW-LIFECYCLE.md +342 -0
- package/hooks/README.md +6 -1
- package/hooks/health_check.py +12 -3
- package/hooks/subagent_tracker.py +52 -77
- package/mcp-server/hook-event.cjs +34 -0
- package/mcp-server/lib/archive-journal.cjs +17 -5
- package/mcp-server/lib/baseline-workflow.cjs +158 -18
- package/mcp-server/lib/change-workflow.cjs +598 -85
- package/mcp-server/lib/context-spine.cjs +305 -25
- package/mcp-server/lib/decision-dialogue.cjs +713 -0
- package/mcp-server/lib/doctor.cjs +42 -3
- package/mcp-server/lib/init-project.cjs +210 -16
- package/mcp-server/lib/plan-store.cjs +4 -1
- package/mcp-server/lib/pricing.cjs +7 -10
- package/mcp-server/lib/project-breadcrumb.cjs +15 -0
- package/mcp-server/lib/projector.cjs +57 -3
- package/mcp-server/lib/spec-learning.cjs +79 -2
- package/mcp-server/lib/state-db.cjs +168 -8
- package/mcp-server/lib/state-ops.cjs +160 -9
- package/mcp-server/lib/workflow-state.cjs +2305 -0
- package/mcp-server/server.cjs +215 -15
- package/orchestrator/daemon.cjs +1 -2
- package/orchestrator/dispatch-rules.cjs +5 -31
- package/orchestrator/planner/plan-builder.cjs +21 -12
- package/package.json +13 -3
- package/skills/ultra-change/SKILL.md +94 -80
- package/skills/ultra-change/references/change-contract.md +36 -0
- package/skills/ultra-deliver/SKILL.md +76 -79
- package/skills/ultra-deliver/references/baseline-reconciliation.md +20 -0
- package/skills/ultra-dev/SKILL.md +62 -59
- package/skills/ultra-doctor/SKILL.md +61 -82
- package/skills/ultra-init/SKILL.md +101 -103
- package/skills/ultra-init/references/brownfield-adoption.md +2 -2
- package/skills/ultra-plan/SKILL.md +99 -85
- package/skills/ultra-plan/references/semantic-preflight.md +18 -0
- package/skills/ultra-research/SKILL.md +102 -60
- package/skills/ultra-research/references/00-problem-validation.md +36 -0
- package/skills/ultra-research/references/01-opportunity-discovery.md +33 -0
- package/skills/ultra-research/references/02-market-assessment.md +30 -0
- package/skills/ultra-research/references/03-competitive-landscape.md +31 -0
- package/skills/ultra-research/references/04-product-strategy.md +32 -0
- package/skills/ultra-research/references/05-assumptions-validation.md +31 -0
- package/skills/ultra-research/references/10-user-personas.md +29 -0
- package/skills/ultra-research/references/11-user-scenarios.md +28 -0
- package/skills/ultra-research/references/20-user-stories.md +30 -0
- package/skills/ultra-research/references/21-features-scope.md +29 -0
- package/skills/ultra-research/references/22-success-metrics.md +28 -0
- package/skills/ultra-research/references/30-architecture-context.md +30 -0
- package/skills/ultra-research/references/31-solution-strategy.md +28 -0
- package/skills/ultra-research/references/32-building-blocks.md +28 -0
- package/skills/ultra-research/references/40-deployment.md +29 -0
- package/skills/ultra-research/references/41-quality-risks.md +28 -0
- package/skills/ultra-research/references/99-synthesis.md +35 -0
- package/skills/ultra-review/SKILL.md +63 -73
- package/skills/ultra-review/references/review-modes.md +27 -0
- package/skills/ultra-review/references/unified-schema.md +25 -0
- package/skills/ultra-review/scripts/review_wait.py +40 -3
- package/skills/ultra-status/SKILL.md +41 -41
- package/skills/ultra-test/SKILL.md +54 -55
- package/skills/ultra-think/SKILL.md +26 -12
- package/skills/ultra-think/references/decision-dialogue.md +130 -0
- package/skills/ultra-verify/SKILL.md +1 -1
- package/skills/ultra-verify/references/{confidence-system.md → evidence-status.md} +3 -3
- package/spec/cli-protocol.md +17 -3
- package/spec/mcp-tools.yaml +683 -64
- package/spec/schemas/baseline-reconciliation.v1.schema.json +73 -0
- package/spec/schemas/delivery-report.v1.schema.json +111 -0
- package/spec/schemas/mcp-tools.schema.json +1 -1
- package/spec/schemas/state-db.sql +140 -1
- package/spec/schemas/tasks.v4.5.schema.json +8 -1
- package/spec/schemas/test-report.v1.schema.json +194 -0
- package/ultra-tools/cli.cjs +1 -1
- package/ultra-tools/commands/migrate.cjs +3 -3
- package/ultra-tools/commands/status.cjs +228 -12
- package/.ultra-template/delivery-report.json +0 -9
- package/.ultra-template/test-report.json +0 -8
- package/adapters/_shared/tests/file-ops.test.cjs +0 -54
- package/adapters/_shared/tests/frontmatter.test.cjs +0 -47
- package/adapters/_shared/tests/handbook.test.cjs +0 -101
- package/adapters/_shared/tests/path-rewrite.test.cjs +0 -60
- package/adapters/_shared/tests/provenance.test.cjs +0 -133
- package/adapters/_shared/tests/resolve-target.test.cjs +0 -75
- package/adapters/_shared/tests/runtime-assets.test.cjs +0 -149
- package/adapters/_shared/tests/settings-merge.test.cjs +0 -85
- package/adapters/_shared/tests/validate.test.cjs +0 -30
- package/adapters/tests/claude.test.cjs +0 -138
- package/adapters/tests/codex-hook.test.cjs +0 -142
- package/adapters/tests/codex.test.cjs +0 -430
- package/adapters/tests/kimi-hook.test.cjs +0 -149
- package/adapters/tests/kimi.test.cjs +0 -259
- package/adapters/tests/opencode.test.cjs +0 -357
- package/mcp-server/lib/archive-journal.test.cjs +0 -35
- package/mcp-server/lib/baseline-workflow.test.cjs +0 -477
- package/mcp-server/lib/change-workflow.test.cjs +0 -971
- package/mcp-server/lib/context-spine.test.cjs +0 -93
- package/mcp-server/lib/doctor.test.cjs +0 -148
- package/mcp-server/lib/plan-store.test.cjs +0 -132
- package/mcp-server/lib/prd-parser.test.cjs +0 -82
- package/mcp-server/lib/project-breadcrumb.test.cjs +0 -112
- package/mcp-server/lib/runtime-state.test.cjs +0 -92
- package/mcp-server/lib/spec-learning.test.cjs +0 -67
- package/mcp-server/lib/state-db.test.cjs +0 -314
- package/mcp-server/lib/task-expander.test.cjs +0 -169
- package/mcp-server/lib/topo.test.cjs +0 -158
- package/mcp-server/tests/circuit-breaker.test.cjs +0 -125
- package/mcp-server/tests/commit-hash-flow.test.cjs +0 -117
- package/mcp-server/tests/concurrency-update.test.cjs +0 -96
- package/mcp-server/tests/concurrency.test.cjs +0 -112
- package/mcp-server/tests/db-ops.test.cjs +0 -121
- package/mcp-server/tests/init-project.test.cjs +0 -393
- package/mcp-server/tests/migrate.test.cjs +0 -232
- package/mcp-server/tests/projector.test.cjs +0 -162
- package/mcp-server/tests/server-contract.test.cjs +0 -1467
- package/mcp-server/tests/staleness.test.cjs +0 -190
- package/mcp-server/tests/state-db.test.cjs +0 -106
- package/mcp-server/tests/state-ops.test.cjs +0 -449
- package/mcp-server/tests/tagged-tasks.test.cjs +0 -163
- package/mcp-server/tests/telemetry.test.cjs +0 -219
- package/orchestrator/planner/plan-builder.test.cjs +0 -165
- package/orchestrator/tests/auto-merge.test.cjs +0 -216
- package/orchestrator/tests/daemon.test.cjs +0 -311
- package/orchestrator/tests/dispatch-rules.test.cjs +0 -124
- package/orchestrator/tests/parallel-orchestrator.test.cjs +0 -233
- package/orchestrator/tests/phase-8b-gate.test.cjs +0 -183
- package/orchestrator/tests/recovery.test.cjs +0 -190
- package/orchestrator/tests/session-runner.test.cjs +0 -314
- package/orchestrator/tests/skill-miner.test.cjs +0 -147
- package/orchestrator/tests/worktree-manager.test.cjs +0 -148
- package/skills/ultra-research/references/architecture.md +0 -38
- package/skills/ultra-research/references/problem-product.md +0 -39
- package/skills/ultra-research/references/quality-delivery.md +0 -34
- package/skills/ultra-research/references/synthesis.md +0 -38
- package/skills/ultra-research/references/users-scope.md +0 -40
- package/ultra-tools/legacy-memory.test.cjs +0 -80
- package/ultra-tools/status-command.test.cjs +0 -181
- package/ultra-tools/system-command.test.cjs +0 -309
package/mcp-server/server.cjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// MCP server (stdio JSON-RPC) exposing task.* + session.* tools backed by
|
|
4
4
|
// .ultra/state.db. Uses the low-level Server API from
|
|
5
|
-
// @modelcontextprotocol/
|
|
5
|
+
// @modelcontextprotocol/server so we can pass raw JSON Schema (Draft 2020-12)
|
|
6
6
|
// straight from spec/mcp-tools.yaml without translating to zod.
|
|
7
7
|
|
|
8
8
|
const fs = require('node:fs');
|
|
@@ -12,12 +12,13 @@ const yaml = require('js-yaml');
|
|
|
12
12
|
const Ajv = require('ajv/dist/2020');
|
|
13
13
|
const addFormats = require('ajv-formats');
|
|
14
14
|
|
|
15
|
-
const { Server } = require('@modelcontextprotocol/
|
|
16
|
-
const { StdioServerTransport } = require('@modelcontextprotocol/
|
|
17
|
-
const { ListToolsRequestSchema, CallToolRequestSchema } = require('@modelcontextprotocol/sdk/types.js');
|
|
15
|
+
const { Server } = require('@modelcontextprotocol/server');
|
|
16
|
+
const { StdioServerTransport } = require('@modelcontextprotocol/server/stdio');
|
|
18
17
|
const { version: PACKAGE_VERSION } = require('../package.json');
|
|
19
18
|
|
|
20
|
-
const {
|
|
19
|
+
const {
|
|
20
|
+
initStateDb, closeStateDb, openStateDb, ensureSchemaVersion,
|
|
21
|
+
} = require('./lib/state-db.cjs');
|
|
21
22
|
const { assertStateAuthority } = require('./lib/state-authority.cjs');
|
|
22
23
|
const ops = require('./lib/state-ops.cjs');
|
|
23
24
|
const projector = require('./lib/projector.cjs');
|
|
@@ -30,6 +31,8 @@ const { initProject } = require('./lib/init-project.cjs');
|
|
|
30
31
|
const baselines = require('./lib/baseline-workflow.cjs');
|
|
31
32
|
const changes = require('./lib/change-workflow.cjs');
|
|
32
33
|
const runtimeState = require('./lib/runtime-state.cjs');
|
|
34
|
+
const workflows = require('./lib/workflow-state.cjs');
|
|
35
|
+
const decisions = require('./lib/decision-dialogue.cjs');
|
|
33
36
|
const doctor = require('./lib/doctor.cjs');
|
|
34
37
|
const {
|
|
35
38
|
readProjectBreadcrumb,
|
|
@@ -91,12 +94,34 @@ const CHANGE_TOOLS = Object.freeze([
|
|
|
91
94
|
'change.archive',
|
|
92
95
|
]);
|
|
93
96
|
|
|
97
|
+
const WORKFLOW_TOOLS = Object.freeze([
|
|
98
|
+
'workflow.start',
|
|
99
|
+
'workflow.get',
|
|
100
|
+
'workflow.list',
|
|
101
|
+
'workflow.step',
|
|
102
|
+
'workflow.complete',
|
|
103
|
+
]);
|
|
104
|
+
|
|
105
|
+
const DECISION_TOOLS = Object.freeze([
|
|
106
|
+
'decision.thread_start',
|
|
107
|
+
'decision.get',
|
|
108
|
+
'decision.list',
|
|
109
|
+
'decision.open',
|
|
110
|
+
'decision.resolve',
|
|
111
|
+
'decision.delegate',
|
|
112
|
+
'decision.defer',
|
|
113
|
+
'decision.supersede',
|
|
114
|
+
'decision.checkpoint',
|
|
115
|
+
]);
|
|
116
|
+
|
|
94
117
|
const SYSTEM_TOOLS = Object.freeze([
|
|
95
118
|
'system.doctor',
|
|
96
119
|
]);
|
|
97
120
|
|
|
98
121
|
const REGISTERED_TOOLS = Object.freeze([
|
|
99
122
|
...TASK_TOOLS, ...SESSION_TOOLS, ...PLAN_TOOLS, ...BASELINE_TOOLS, ...CHANGE_TOOLS,
|
|
123
|
+
...DECISION_TOOLS,
|
|
124
|
+
...WORKFLOW_TOOLS,
|
|
100
125
|
...SYSTEM_TOOLS,
|
|
101
126
|
]);
|
|
102
127
|
|
|
@@ -112,6 +137,9 @@ const MUTATING_TOOLS = new Set([
|
|
|
112
137
|
'baseline.start', 'baseline.record', 'baseline.converge',
|
|
113
138
|
'change.create', 'change.update', 'change.context', 'change.converge', 'change.archive',
|
|
114
139
|
'change.learning_propose', 'change.learning_resolve',
|
|
140
|
+
'decision.thread_start', 'decision.open', 'decision.resolve', 'decision.delegate',
|
|
141
|
+
'decision.defer', 'decision.supersede', 'decision.checkpoint',
|
|
142
|
+
'workflow.start', 'workflow.step', 'workflow.complete',
|
|
115
143
|
]);
|
|
116
144
|
|
|
117
145
|
function loadRegisteredTools() {
|
|
@@ -252,10 +280,33 @@ async function dispatchTool(name, input, db, ctx = {}) {
|
|
|
252
280
|
return ops.switchTaskTag(db, input.id, input.tag);
|
|
253
281
|
}
|
|
254
282
|
case 'task.dependency_topo': {
|
|
255
|
-
const
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
283
|
+
const hasTaskIds = Array.isArray(input && input.task_ids);
|
|
284
|
+
const hasChangeId = typeof input?.change_id === 'string' && input.change_id.trim() !== '';
|
|
285
|
+
if (hasTaskIds === hasChangeId) {
|
|
286
|
+
const err = new Error('provide exactly one of task_ids or change_id');
|
|
287
|
+
err.code = 'VALIDATION_ERROR';
|
|
288
|
+
throw err;
|
|
289
|
+
}
|
|
290
|
+
let rows;
|
|
291
|
+
if (hasTaskIds) {
|
|
292
|
+
rows = input.task_ids.map((id) => {
|
|
293
|
+
const task = ops.readTask(db, id);
|
|
294
|
+
if (!task) {
|
|
295
|
+
const err = new Error(`task ${id} not found`);
|
|
296
|
+
err.code = 'TASK_NOT_FOUND';
|
|
297
|
+
throw err;
|
|
298
|
+
}
|
|
299
|
+
return task;
|
|
300
|
+
});
|
|
301
|
+
} else {
|
|
302
|
+
const change = changes.readChange(db, input.change_id);
|
|
303
|
+
if (!change) {
|
|
304
|
+
const err = new Error(`change ${input.change_id} not found`);
|
|
305
|
+
err.code = 'CHANGE_NOT_FOUND';
|
|
306
|
+
throw err;
|
|
307
|
+
}
|
|
308
|
+
rows = ops.listTasks(db, { change_id: change.id });
|
|
309
|
+
}
|
|
259
310
|
const graph = rows.map((t) => ({
|
|
260
311
|
id: t.id,
|
|
261
312
|
deps: Array.isArray(t.deps) ? t.deps : [],
|
|
@@ -272,6 +323,11 @@ async function dispatchTool(name, input, db, ctx = {}) {
|
|
|
272
323
|
case 'task.parse_prd': {
|
|
273
324
|
const dryRun = input.dry_run === true;
|
|
274
325
|
const rootDir = ctx.rootDir || process.cwd();
|
|
326
|
+
if (!dryRun && !String(input.change_id || '').trim()) {
|
|
327
|
+
const err = new Error('persisting a parsed task graph requires change_id');
|
|
328
|
+
err.code = 'CHANGE_REQUIRED';
|
|
329
|
+
throw err;
|
|
330
|
+
}
|
|
275
331
|
const creationAuthority = { change_id: input.change_id };
|
|
276
332
|
if (!dryRun) changes.assertTaskCreationAllowed(db, creationAuthority, { rootDir });
|
|
277
333
|
const parsed = parser.parsePrd(input.tasks, {
|
|
@@ -330,20 +386,34 @@ async function dispatchTool(name, input, db, ctx = {}) {
|
|
|
330
386
|
}
|
|
331
387
|
case 'plan.export': {
|
|
332
388
|
const rootDir = ctx.rootDir || process.cwd();
|
|
389
|
+
const change = changes.readChange(db, input.change_id);
|
|
390
|
+
if (!change) {
|
|
391
|
+
const err = new Error(`change ${input.change_id} not found`);
|
|
392
|
+
err.code = 'CHANGE_NOT_FOUND';
|
|
393
|
+
throw err;
|
|
394
|
+
}
|
|
395
|
+
if (!['active', 'blocked'].includes(change.status)) {
|
|
396
|
+
const err = new Error(`change ${change.id} is ${change.status}`);
|
|
397
|
+
err.code = 'CHANGE_NOT_MUTABLE';
|
|
398
|
+
throw err;
|
|
399
|
+
}
|
|
333
400
|
const abs = path.isAbsolute(input.out_path)
|
|
334
401
|
? input.out_path
|
|
335
402
|
: path.join(rootDir, input.out_path);
|
|
336
|
-
const tasks = ops.listTasks(db, { tag: input.tag });
|
|
403
|
+
const tasks = ops.listTasks(db, { tag: input.tag, change_id: change.id });
|
|
337
404
|
if (tasks.length === 0) {
|
|
338
405
|
const err = new Error('no tasks to plan');
|
|
339
406
|
err.code = 'NO_TASKS';
|
|
340
407
|
throw err;
|
|
341
408
|
}
|
|
342
|
-
const plan = planStore.buildPlan(tasks, {});
|
|
409
|
+
const plan = planStore.buildPlan(tasks, { changeId: change.id });
|
|
343
410
|
const { plan_path } = planStore.savePlanArtifact(plan, abs, input.format || 'json');
|
|
344
411
|
ops.appendEvent(db, {
|
|
345
|
-
type: '
|
|
346
|
-
|
|
412
|
+
type: 'plan_exported',
|
|
413
|
+
change_id: change.id,
|
|
414
|
+
payload: {
|
|
415
|
+
change_id: change.id, plan_path, wave_count: plan.waves.length, tag: input.tag || null,
|
|
416
|
+
},
|
|
347
417
|
});
|
|
348
418
|
return { plan_path, wave_count: plan.waves.length };
|
|
349
419
|
}
|
|
@@ -355,6 +425,13 @@ async function dispatchTool(name, input, db, ctx = {}) {
|
|
|
355
425
|
err.code = 'NO_PLAN';
|
|
356
426
|
throw err;
|
|
357
427
|
}
|
|
428
|
+
if (loaded.change_id !== input.change_id) {
|
|
429
|
+
const err = new Error(
|
|
430
|
+
`execution plan belongs to ${loaded.change_id || '(unbound)'}, not ${input.change_id}`,
|
|
431
|
+
);
|
|
432
|
+
err.code = 'PLAN_CHANGE_MISMATCH';
|
|
433
|
+
throw err;
|
|
434
|
+
}
|
|
358
435
|
return { plan: planStore.selectSection(loaded, input.section) };
|
|
359
436
|
}
|
|
360
437
|
case 'baseline.start': {
|
|
@@ -456,6 +533,82 @@ async function dispatchTool(name, input, db, ctx = {}) {
|
|
|
456
533
|
case 'change.archive': {
|
|
457
534
|
return changes.archiveChange(db, input, { rootDir: ctx.rootDir || process.cwd() });
|
|
458
535
|
}
|
|
536
|
+
case 'decision.thread_start': {
|
|
537
|
+
return { thread: decisions.startDecisionThread(db, input) };
|
|
538
|
+
}
|
|
539
|
+
case 'decision.get': {
|
|
540
|
+
const thread = decisions.readDecisionThread(db, input.id);
|
|
541
|
+
if (!thread) {
|
|
542
|
+
const error = new Error(`decision thread ${input.id} not found`);
|
|
543
|
+
error.code = 'DECISION_THREAD_NOT_FOUND';
|
|
544
|
+
throw error;
|
|
545
|
+
}
|
|
546
|
+
return { thread };
|
|
547
|
+
}
|
|
548
|
+
case 'decision.list': {
|
|
549
|
+
const threads = decisions.listDecisionThreads(db, input || {});
|
|
550
|
+
return { threads, count: threads.length };
|
|
551
|
+
}
|
|
552
|
+
case 'decision.open': {
|
|
553
|
+
return { thread: decisions.openDecision(db, input) };
|
|
554
|
+
}
|
|
555
|
+
case 'decision.resolve': {
|
|
556
|
+
return { thread: decisions.resolveDecision(db, input) };
|
|
557
|
+
}
|
|
558
|
+
case 'decision.delegate': {
|
|
559
|
+
return { thread: decisions.delegateDecision(db, input) };
|
|
560
|
+
}
|
|
561
|
+
case 'decision.defer': {
|
|
562
|
+
return { thread: decisions.deferDecision(db, input) };
|
|
563
|
+
}
|
|
564
|
+
case 'decision.supersede': {
|
|
565
|
+
return { thread: decisions.supersedeDecision(db, input) };
|
|
566
|
+
}
|
|
567
|
+
case 'decision.checkpoint': {
|
|
568
|
+
return {
|
|
569
|
+
thread: decisions.checkpointDecisionThread(
|
|
570
|
+
db, input, { rootDir: ctx.rootDir || process.cwd() },
|
|
571
|
+
),
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
case 'workflow.start': {
|
|
575
|
+
return {
|
|
576
|
+
workflow: workflows.startWorkflow(
|
|
577
|
+
db, input, { rootDir: ctx.rootDir || process.cwd() },
|
|
578
|
+
),
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
case 'workflow.get': {
|
|
582
|
+
const workflow = workflows.readWorkflow(
|
|
583
|
+
db, input.id, { rootDir: ctx.rootDir || process.cwd() },
|
|
584
|
+
);
|
|
585
|
+
if (!workflow) {
|
|
586
|
+
const error = new Error(`workflow ${input.id} not found`);
|
|
587
|
+
error.code = 'WORKFLOW_NOT_FOUND';
|
|
588
|
+
throw error;
|
|
589
|
+
}
|
|
590
|
+
return { workflow };
|
|
591
|
+
}
|
|
592
|
+
case 'workflow.list': {
|
|
593
|
+
const rows = workflows.listWorkflows(
|
|
594
|
+
db, input || {}, { rootDir: ctx.rootDir || process.cwd() },
|
|
595
|
+
);
|
|
596
|
+
return { workflows: rows, count: rows.length };
|
|
597
|
+
}
|
|
598
|
+
case 'workflow.step': {
|
|
599
|
+
return {
|
|
600
|
+
workflow: workflows.recordWorkflowStep(
|
|
601
|
+
db, input, { rootDir: ctx.rootDir || process.cwd() },
|
|
602
|
+
),
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
case 'workflow.complete': {
|
|
606
|
+
return {
|
|
607
|
+
workflow: workflows.completeWorkflow(
|
|
608
|
+
db, input, { rootDir: ctx.rootDir || process.cwd() },
|
|
609
|
+
),
|
|
610
|
+
};
|
|
611
|
+
}
|
|
459
612
|
case 'system.doctor': {
|
|
460
613
|
return doctor.runDoctor(db, {
|
|
461
614
|
rootDir: ctx.rootDir || process.cwd(),
|
|
@@ -501,7 +654,7 @@ function startServer({ dbPath, rootDir, projectOnWrite = true, project = project
|
|
|
501
654
|
{ capabilities: { tools: {} } },
|
|
502
655
|
);
|
|
503
656
|
|
|
504
|
-
server.setRequestHandler(
|
|
657
|
+
server.setRequestHandler('tools/list', async () => ({
|
|
505
658
|
tools: tools.map((t) => ({
|
|
506
659
|
name: t.name,
|
|
507
660
|
description: t.description,
|
|
@@ -509,7 +662,7 @@ function startServer({ dbPath, rootDir, projectOnWrite = true, project = project
|
|
|
509
662
|
})),
|
|
510
663
|
}));
|
|
511
664
|
|
|
512
|
-
server.setRequestHandler(
|
|
665
|
+
server.setRequestHandler('tools/call', async (request) => {
|
|
513
666
|
const { name, arguments: args = {} } = request.params;
|
|
514
667
|
const toolStart = Date.now();
|
|
515
668
|
let toolError = null;
|
|
@@ -623,6 +776,51 @@ async function main() {
|
|
|
623
776
|
process.on('SIGINT', cleanup);
|
|
624
777
|
}
|
|
625
778
|
|
|
779
|
+
function hookMetadata(value, fallback = 'unknown', maxLength = 256) {
|
|
780
|
+
const normalized = typeof value === 'string' ? value.trim() : '';
|
|
781
|
+
return (normalized || fallback).slice(0, maxLength);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function appendHookLifecycleEvent({ rootDir, action, hookInput = {} } = {}) {
|
|
785
|
+
if (!['start', 'stop'].includes(action)) {
|
|
786
|
+
const error = new Error('hook lifecycle action must be start or stop');
|
|
787
|
+
error.code = 'VALIDATION_ERROR';
|
|
788
|
+
throw error;
|
|
789
|
+
}
|
|
790
|
+
const root = path.resolve(rootDir || process.cwd());
|
|
791
|
+
const dbPath = path.join(root, '.ultra', 'state.db');
|
|
792
|
+
if (!fs.existsSync(dbPath)) return { recorded: false, reason: 'STATE_DB_MISSING' };
|
|
793
|
+
|
|
794
|
+
const db = openStateDb(dbPath);
|
|
795
|
+
try {
|
|
796
|
+
ensureSchemaVersion(db);
|
|
797
|
+
const change = db.prepare(
|
|
798
|
+
`SELECT id FROM changes WHERE status IN ('active', 'blocked', 'ready')
|
|
799
|
+
ORDER BY updated_at DESC, rowid DESC LIMIT 1`,
|
|
800
|
+
).get();
|
|
801
|
+
if (!change) return { recorded: false, reason: 'NO_ACTIVE_CHANGE' };
|
|
802
|
+
const task = db.prepare(
|
|
803
|
+
`SELECT id FROM tasks WHERE change_id = ? AND status IN ('in_progress', 'pending', 'blocked')
|
|
804
|
+
ORDER BY CASE status WHEN 'in_progress' THEN 0 WHEN 'blocked' THEN 1 ELSE 2 END,
|
|
805
|
+
updated_at DESC, rowid DESC LIMIT 1`,
|
|
806
|
+
).get(change.id);
|
|
807
|
+
const payload = {
|
|
808
|
+
agent_id: hookMetadata(hookInput.agent_id),
|
|
809
|
+
agent_type: hookMetadata(hookInput.agent_type),
|
|
810
|
+
host_session_id: hookMetadata(hookInput.session_id, '', 256),
|
|
811
|
+
};
|
|
812
|
+
ops.appendEvent(db, {
|
|
813
|
+
type: action === 'start' ? 'subagent_started' : 'subagent_stopped',
|
|
814
|
+
change_id: change.id,
|
|
815
|
+
task_id: task?.id || null,
|
|
816
|
+
payload,
|
|
817
|
+
});
|
|
818
|
+
return { recorded: true, change_id: change.id, task_id: task?.id || null };
|
|
819
|
+
} finally {
|
|
820
|
+
closeStateDb(db);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
626
824
|
if (require.main === module) {
|
|
627
825
|
main().catch((err) => {
|
|
628
826
|
process.stderr.write(`mcp-server fatal: ${err.message}\n`);
|
|
@@ -639,10 +837,12 @@ module.exports = {
|
|
|
639
837
|
PLAN_TOOLS,
|
|
640
838
|
BASELINE_TOOLS,
|
|
641
839
|
CHANGE_TOOLS,
|
|
840
|
+
WORKFLOW_TOOLS,
|
|
642
841
|
SYSTEM_TOOLS,
|
|
643
842
|
REGISTERED_TOOLS,
|
|
644
843
|
STATELESS_TOOLS,
|
|
645
844
|
MUTATING_TOOLS,
|
|
845
|
+
appendHookLifecycleEvent,
|
|
646
846
|
readProjectBreadcrumb,
|
|
647
847
|
renderProjectBreadcrumb,
|
|
648
848
|
};
|
package/orchestrator/daemon.cjs
CHANGED
|
@@ -16,7 +16,7 @@ const runtimeState = require('../mcp-server/lib/runtime-state.cjs');
|
|
|
16
16
|
const projector = require('../mcp-server/lib/projector.cjs');
|
|
17
17
|
const runner = require('./session-runner.cjs');
|
|
18
18
|
const recovery = require('./recovery.cjs');
|
|
19
|
-
const { evaluate, DEFAULT_RULES
|
|
19
|
+
const { evaluate, DEFAULT_RULES } = require('./dispatch-rules.cjs');
|
|
20
20
|
|
|
21
21
|
// Phase 8B.1 — routeTask is now a thin wrapper over evaluate() so Phase 5.4
|
|
22
22
|
// callers see identical behavior while the parallel orchestrator (8B.2) can
|
|
@@ -146,6 +146,5 @@ function runDaemon({
|
|
|
146
146
|
module.exports = {
|
|
147
147
|
runDaemon,
|
|
148
148
|
routeTask,
|
|
149
|
-
ROUTE_PREFERENCES,
|
|
150
149
|
maintainState,
|
|
151
150
|
};
|
|
@@ -8,28 +8,13 @@
|
|
|
8
8
|
// • defer — leave task pending, revisit next tick (deps/wave wait)
|
|
9
9
|
// • block — terminal: don't spawn (breaker, no runtimes, etc.)
|
|
10
10
|
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
11
|
+
// Runtime order is caller-owned. The orchestrator does not infer model quality,
|
|
12
|
+
// price, or capability from a host name because every host can be configured
|
|
13
|
+
// with different models. The parallel orchestrator supplies richer dimensions:
|
|
14
|
+
// wave state, dependency readiness, and explicit custom rules.
|
|
15
15
|
|
|
16
16
|
const { isSupportedRuntime } = require('../adapters/_shared/runtime-assets.cjs');
|
|
17
17
|
|
|
18
|
-
const ROUTE_PREFERENCES = Object.freeze({
|
|
19
|
-
haiku: ['opencode', 'claude', 'codex'],
|
|
20
|
-
sonnet: ['claude', 'codex', 'opencode'],
|
|
21
|
-
opus: ['claude', 'codex'],
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
function resolveByPreference(ctx) {
|
|
25
|
-
const hint = ctx.task && ctx.task.complexity_hint;
|
|
26
|
-
const pref = hint ? ROUTE_PREFERENCES[hint] : null;
|
|
27
|
-
if (pref) {
|
|
28
|
-
for (const r of pref) if (ctx.available_runtimes.includes(r)) return r;
|
|
29
|
-
}
|
|
30
|
-
return ctx.available_runtimes[0];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
18
|
const DEFAULT_RULES = Object.freeze([
|
|
34
19
|
{
|
|
35
20
|
id: 'breaker-blocked',
|
|
@@ -56,17 +41,7 @@ const DEFAULT_RULES = Object.freeze([
|
|
|
56
41
|
action: 'defer',
|
|
57
42
|
},
|
|
58
43
|
{
|
|
59
|
-
id: '
|
|
60
|
-
priority: 10,
|
|
61
|
-
when: (ctx) => {
|
|
62
|
-
const hint = ctx.task && ctx.task.complexity_hint;
|
|
63
|
-
return !!(hint && ROUTE_PREFERENCES[hint]);
|
|
64
|
-
},
|
|
65
|
-
action: 'spawn_agent',
|
|
66
|
-
resolve: resolveByPreference,
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
id: 'fallback-first-available',
|
|
44
|
+
id: 'first-authorized-runtime',
|
|
70
45
|
priority: 0,
|
|
71
46
|
when: () => true,
|
|
72
47
|
action: 'spawn_agent',
|
|
@@ -100,5 +75,4 @@ function evaluate(ctx, rules = DEFAULT_RULES) {
|
|
|
100
75
|
module.exports = {
|
|
101
76
|
evaluate,
|
|
102
77
|
DEFAULT_RULES,
|
|
103
|
-
ROUTE_PREFERENCES,
|
|
104
78
|
};
|
|
@@ -15,18 +15,22 @@ const DEFAULT_COMPLEXITY = 3;
|
|
|
15
15
|
const TOKENS_PER_COMPLEXITY = Object.freeze({ input: 5000, output: 2000 });
|
|
16
16
|
const DURATION_MIN_PER_COMPLEXITY = 5;
|
|
17
17
|
|
|
18
|
-
function buildPlan(tasks, { runtime =
|
|
18
|
+
function buildPlan(tasks, { runtime = null, model = null, changeId = null } = {}) {
|
|
19
19
|
if (!Array.isArray(tasks)) {
|
|
20
20
|
throw new TypeError('buildPlan: tasks must be an array');
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
// Plan artifacts are durable and compared by digest. Keep their ordering
|
|
24
|
+
// independent from SQLite insertion order or caller enumeration order.
|
|
25
|
+
const orderedTasks = [...tasks].sort((left, right) => String(left.id).localeCompare(String(right.id)));
|
|
26
|
+
|
|
27
|
+
const graph = orderedTasks.map((t) => ({
|
|
24
28
|
id: t.id,
|
|
25
29
|
deps: Array.isArray(t.deps) ? t.deps : [],
|
|
26
30
|
}));
|
|
27
31
|
const { waves, cycles } = computeWaves(graph);
|
|
28
32
|
|
|
29
|
-
const byId = new Map(
|
|
33
|
+
const byId = new Map(orderedTasks.map((t) => [t.id, t]));
|
|
30
34
|
|
|
31
35
|
const conflict_surface = [];
|
|
32
36
|
const wavePayload = waves.map((waveTasks, idx) => {
|
|
@@ -52,18 +56,21 @@ function buildPlan(tasks, { runtime = 'claude', model = null } = {}) {
|
|
|
52
56
|
});
|
|
53
57
|
|
|
54
58
|
const ownership_forecast = {};
|
|
55
|
-
for (const t of
|
|
59
|
+
for (const t of orderedTasks) {
|
|
56
60
|
ownership_forecast[t.id] = Array.isArray(t.files_modified) ? t.files_modified : [];
|
|
57
61
|
}
|
|
58
62
|
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
const taskCosts = orderedTasks.map((t) => {
|
|
64
|
+
const complexity = Number(t.complexity || DEFAULT_COMPLEXITY);
|
|
65
|
+
const ti = complexity * TOKENS_PER_COMPLEXITY.input;
|
|
66
|
+
const to = complexity * TOKENS_PER_COMPLEXITY.output;
|
|
67
|
+
return computeCost(runtime, model, ti, to);
|
|
68
|
+
});
|
|
69
|
+
const estimated_cost_usd = orderedTasks.length === 0
|
|
70
|
+
? 0
|
|
71
|
+
: (taskCosts.some((cost) => cost === null)
|
|
72
|
+
? null
|
|
73
|
+
: round4(taskCosts.reduce((sum, cost) => sum + cost, 0)));
|
|
67
74
|
|
|
68
75
|
const estimated_duration_min = wavePayload.reduce((acc, wv) => {
|
|
69
76
|
const complexities = wv.tasks.map(
|
|
@@ -76,6 +83,8 @@ function buildPlan(tasks, { runtime = 'claude', model = null } = {}) {
|
|
|
76
83
|
}, 0);
|
|
77
84
|
|
|
78
85
|
return {
|
|
86
|
+
schema_version: '1.0',
|
|
87
|
+
change_id: changeId,
|
|
79
88
|
waves: wavePayload,
|
|
80
89
|
ownership_forecast,
|
|
81
90
|
conflict_surface,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultra-builder-pro-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
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 Kimi Code.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ultra-builder-pro-cli": "bin/install.js",
|
|
@@ -49,13 +49,20 @@
|
|
|
49
49
|
".ultra-template",
|
|
50
50
|
"docs/ROADMAP.md",
|
|
51
51
|
"docs/ARCHITECTURE.md",
|
|
52
|
+
"docs/WORKFLOW-LIFECYCLE.md",
|
|
52
53
|
"docs/AGENT-CONTEXT.md",
|
|
53
54
|
"docs/SKILL-AUTHORING.md",
|
|
54
55
|
"docs/COMMIT-HASH-BACKFILL.md",
|
|
55
56
|
"docs/PLAN.zh-CN.md",
|
|
56
57
|
"docs/STATE-DB-ACCESS-POLICY.md",
|
|
57
58
|
"docs/USER-HANDBOOK-CONTRACT.md",
|
|
58
|
-
"docs/RUNTIME-COMPAT-MATRIX.md"
|
|
59
|
+
"docs/RUNTIME-COMPAT-MATRIX.md",
|
|
60
|
+
"!**/__pycache__/**",
|
|
61
|
+
"!**/*.pyc",
|
|
62
|
+
"!**/*.pyo",
|
|
63
|
+
"!**/*.test.cjs",
|
|
64
|
+
"!**/tests/**",
|
|
65
|
+
"!mcp-server/test-support/**"
|
|
59
66
|
],
|
|
60
67
|
"keywords": [
|
|
61
68
|
"claude-code",
|
|
@@ -98,11 +105,14 @@
|
|
|
98
105
|
"install:all": "node bin/install.js --all --local"
|
|
99
106
|
},
|
|
100
107
|
"dependencies": {
|
|
101
|
-
"@modelcontextprotocol/
|
|
108
|
+
"@modelcontextprotocol/server": "2.0.0-beta.5",
|
|
102
109
|
"@vercel/ncc": "0.38.4",
|
|
103
110
|
"ajv": "^8.17.1",
|
|
104
111
|
"ajv-formats": "^3.0.1",
|
|
105
112
|
"better-sqlite3": "^11.5.0",
|
|
106
113
|
"js-yaml": "^4.1.0"
|
|
114
|
+
},
|
|
115
|
+
"devDependencies": {
|
|
116
|
+
"@modelcontextprotocol/client": "2.0.0-beta.5"
|
|
107
117
|
}
|
|
108
118
|
}
|