ultra-builder-pro-cli 0.14.0 → 0.15.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 +44 -1
- package/README.md +34 -19
- package/adapters/_shared/codex-assets.cjs +6 -2
- package/adapters/_shared/file-ops.cjs +6 -2
- 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/docs/AGENT-CONTEXT.md +15 -8
- package/docs/ARCHITECTURE.md +33 -12
- package/docs/PLAN.zh-CN.md +3 -2
- package/docs/ROADMAP.md +8 -4
- package/docs/RUNTIME-COMPAT-MATRIX.md +4 -4
- package/docs/STATE-DB-ACCESS-POLICY.md +27 -25
- package/docs/WORKFLOW-LIFECYCLE.md +266 -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 +550 -85
- package/mcp-server/lib/context-spine.cjs +242 -24
- package/mcp-server/lib/doctor.cjs +33 -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 +6 -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 +145 -8
- package/mcp-server/lib/state-ops.cjs +160 -9
- package/mcp-server/lib/workflow-state.cjs +2292 -0
- package/mcp-server/server.cjs +161 -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 +53 -68
- package/skills/ultra-change/references/change-contract.md +36 -0
- package/skills/ultra-deliver/SKILL.md +69 -79
- package/skills/ultra-deliver/references/baseline-reconciliation.md +20 -0
- package/skills/ultra-dev/SKILL.md +54 -59
- package/skills/ultra-doctor/SKILL.md +40 -71
- package/skills/ultra-init/SKILL.md +96 -103
- package/skills/ultra-init/references/brownfield-adoption.md +2 -2
- package/skills/ultra-plan/SKILL.md +79 -85
- package/skills/ultra-plan/references/semantic-preflight.md +18 -0
- package/skills/ultra-research/SKILL.md +87 -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 +57 -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 -45
- package/skills/ultra-test/SKILL.md +48 -55
- 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 +7 -2
- package/spec/mcp-tools.yaml +384 -63
- 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 +82 -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 +213 -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,7 @@ 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');
|
|
33
35
|
const doctor = require('./lib/doctor.cjs');
|
|
34
36
|
const {
|
|
35
37
|
readProjectBreadcrumb,
|
|
@@ -91,12 +93,21 @@ const CHANGE_TOOLS = Object.freeze([
|
|
|
91
93
|
'change.archive',
|
|
92
94
|
]);
|
|
93
95
|
|
|
96
|
+
const WORKFLOW_TOOLS = Object.freeze([
|
|
97
|
+
'workflow.start',
|
|
98
|
+
'workflow.get',
|
|
99
|
+
'workflow.list',
|
|
100
|
+
'workflow.step',
|
|
101
|
+
'workflow.complete',
|
|
102
|
+
]);
|
|
103
|
+
|
|
94
104
|
const SYSTEM_TOOLS = Object.freeze([
|
|
95
105
|
'system.doctor',
|
|
96
106
|
]);
|
|
97
107
|
|
|
98
108
|
const REGISTERED_TOOLS = Object.freeze([
|
|
99
109
|
...TASK_TOOLS, ...SESSION_TOOLS, ...PLAN_TOOLS, ...BASELINE_TOOLS, ...CHANGE_TOOLS,
|
|
110
|
+
...WORKFLOW_TOOLS,
|
|
100
111
|
...SYSTEM_TOOLS,
|
|
101
112
|
]);
|
|
102
113
|
|
|
@@ -112,6 +123,7 @@ const MUTATING_TOOLS = new Set([
|
|
|
112
123
|
'baseline.start', 'baseline.record', 'baseline.converge',
|
|
113
124
|
'change.create', 'change.update', 'change.context', 'change.converge', 'change.archive',
|
|
114
125
|
'change.learning_propose', 'change.learning_resolve',
|
|
126
|
+
'workflow.start', 'workflow.step', 'workflow.complete',
|
|
115
127
|
]);
|
|
116
128
|
|
|
117
129
|
function loadRegisteredTools() {
|
|
@@ -252,10 +264,33 @@ async function dispatchTool(name, input, db, ctx = {}) {
|
|
|
252
264
|
return ops.switchTaskTag(db, input.id, input.tag);
|
|
253
265
|
}
|
|
254
266
|
case 'task.dependency_topo': {
|
|
255
|
-
const
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
267
|
+
const hasTaskIds = Array.isArray(input && input.task_ids);
|
|
268
|
+
const hasChangeId = typeof input?.change_id === 'string' && input.change_id.trim() !== '';
|
|
269
|
+
if (hasTaskIds === hasChangeId) {
|
|
270
|
+
const err = new Error('provide exactly one of task_ids or change_id');
|
|
271
|
+
err.code = 'VALIDATION_ERROR';
|
|
272
|
+
throw err;
|
|
273
|
+
}
|
|
274
|
+
let rows;
|
|
275
|
+
if (hasTaskIds) {
|
|
276
|
+
rows = input.task_ids.map((id) => {
|
|
277
|
+
const task = ops.readTask(db, id);
|
|
278
|
+
if (!task) {
|
|
279
|
+
const err = new Error(`task ${id} not found`);
|
|
280
|
+
err.code = 'TASK_NOT_FOUND';
|
|
281
|
+
throw err;
|
|
282
|
+
}
|
|
283
|
+
return task;
|
|
284
|
+
});
|
|
285
|
+
} else {
|
|
286
|
+
const change = changes.readChange(db, input.change_id);
|
|
287
|
+
if (!change) {
|
|
288
|
+
const err = new Error(`change ${input.change_id} not found`);
|
|
289
|
+
err.code = 'CHANGE_NOT_FOUND';
|
|
290
|
+
throw err;
|
|
291
|
+
}
|
|
292
|
+
rows = ops.listTasks(db, { change_id: change.id });
|
|
293
|
+
}
|
|
259
294
|
const graph = rows.map((t) => ({
|
|
260
295
|
id: t.id,
|
|
261
296
|
deps: Array.isArray(t.deps) ? t.deps : [],
|
|
@@ -272,6 +307,11 @@ async function dispatchTool(name, input, db, ctx = {}) {
|
|
|
272
307
|
case 'task.parse_prd': {
|
|
273
308
|
const dryRun = input.dry_run === true;
|
|
274
309
|
const rootDir = ctx.rootDir || process.cwd();
|
|
310
|
+
if (!dryRun && !String(input.change_id || '').trim()) {
|
|
311
|
+
const err = new Error('persisting a parsed task graph requires change_id');
|
|
312
|
+
err.code = 'CHANGE_REQUIRED';
|
|
313
|
+
throw err;
|
|
314
|
+
}
|
|
275
315
|
const creationAuthority = { change_id: input.change_id };
|
|
276
316
|
if (!dryRun) changes.assertTaskCreationAllowed(db, creationAuthority, { rootDir });
|
|
277
317
|
const parsed = parser.parsePrd(input.tasks, {
|
|
@@ -330,20 +370,34 @@ async function dispatchTool(name, input, db, ctx = {}) {
|
|
|
330
370
|
}
|
|
331
371
|
case 'plan.export': {
|
|
332
372
|
const rootDir = ctx.rootDir || process.cwd();
|
|
373
|
+
const change = changes.readChange(db, input.change_id);
|
|
374
|
+
if (!change) {
|
|
375
|
+
const err = new Error(`change ${input.change_id} not found`);
|
|
376
|
+
err.code = 'CHANGE_NOT_FOUND';
|
|
377
|
+
throw err;
|
|
378
|
+
}
|
|
379
|
+
if (!['active', 'blocked'].includes(change.status)) {
|
|
380
|
+
const err = new Error(`change ${change.id} is ${change.status}`);
|
|
381
|
+
err.code = 'CHANGE_NOT_MUTABLE';
|
|
382
|
+
throw err;
|
|
383
|
+
}
|
|
333
384
|
const abs = path.isAbsolute(input.out_path)
|
|
334
385
|
? input.out_path
|
|
335
386
|
: path.join(rootDir, input.out_path);
|
|
336
|
-
const tasks = ops.listTasks(db, { tag: input.tag });
|
|
387
|
+
const tasks = ops.listTasks(db, { tag: input.tag, change_id: change.id });
|
|
337
388
|
if (tasks.length === 0) {
|
|
338
389
|
const err = new Error('no tasks to plan');
|
|
339
390
|
err.code = 'NO_TASKS';
|
|
340
391
|
throw err;
|
|
341
392
|
}
|
|
342
|
-
const plan = planStore.buildPlan(tasks, {});
|
|
393
|
+
const plan = planStore.buildPlan(tasks, { changeId: change.id });
|
|
343
394
|
const { plan_path } = planStore.savePlanArtifact(plan, abs, input.format || 'json');
|
|
344
395
|
ops.appendEvent(db, {
|
|
345
|
-
type: '
|
|
346
|
-
|
|
396
|
+
type: 'plan_exported',
|
|
397
|
+
change_id: change.id,
|
|
398
|
+
payload: {
|
|
399
|
+
change_id: change.id, plan_path, wave_count: plan.waves.length, tag: input.tag || null,
|
|
400
|
+
},
|
|
347
401
|
});
|
|
348
402
|
return { plan_path, wave_count: plan.waves.length };
|
|
349
403
|
}
|
|
@@ -355,6 +409,13 @@ async function dispatchTool(name, input, db, ctx = {}) {
|
|
|
355
409
|
err.code = 'NO_PLAN';
|
|
356
410
|
throw err;
|
|
357
411
|
}
|
|
412
|
+
if (loaded.change_id !== input.change_id) {
|
|
413
|
+
const err = new Error(
|
|
414
|
+
`execution plan belongs to ${loaded.change_id || '(unbound)'}, not ${input.change_id}`,
|
|
415
|
+
);
|
|
416
|
+
err.code = 'PLAN_CHANGE_MISMATCH';
|
|
417
|
+
throw err;
|
|
418
|
+
}
|
|
358
419
|
return { plan: planStore.selectSection(loaded, input.section) };
|
|
359
420
|
}
|
|
360
421
|
case 'baseline.start': {
|
|
@@ -456,6 +517,44 @@ async function dispatchTool(name, input, db, ctx = {}) {
|
|
|
456
517
|
case 'change.archive': {
|
|
457
518
|
return changes.archiveChange(db, input, { rootDir: ctx.rootDir || process.cwd() });
|
|
458
519
|
}
|
|
520
|
+
case 'workflow.start': {
|
|
521
|
+
return {
|
|
522
|
+
workflow: workflows.startWorkflow(
|
|
523
|
+
db, input, { rootDir: ctx.rootDir || process.cwd() },
|
|
524
|
+
),
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
case 'workflow.get': {
|
|
528
|
+
const workflow = workflows.readWorkflow(
|
|
529
|
+
db, input.id, { rootDir: ctx.rootDir || process.cwd() },
|
|
530
|
+
);
|
|
531
|
+
if (!workflow) {
|
|
532
|
+
const error = new Error(`workflow ${input.id} not found`);
|
|
533
|
+
error.code = 'WORKFLOW_NOT_FOUND';
|
|
534
|
+
throw error;
|
|
535
|
+
}
|
|
536
|
+
return { workflow };
|
|
537
|
+
}
|
|
538
|
+
case 'workflow.list': {
|
|
539
|
+
const rows = workflows.listWorkflows(
|
|
540
|
+
db, input || {}, { rootDir: ctx.rootDir || process.cwd() },
|
|
541
|
+
);
|
|
542
|
+
return { workflows: rows, count: rows.length };
|
|
543
|
+
}
|
|
544
|
+
case 'workflow.step': {
|
|
545
|
+
return {
|
|
546
|
+
workflow: workflows.recordWorkflowStep(
|
|
547
|
+
db, input, { rootDir: ctx.rootDir || process.cwd() },
|
|
548
|
+
),
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
case 'workflow.complete': {
|
|
552
|
+
return {
|
|
553
|
+
workflow: workflows.completeWorkflow(
|
|
554
|
+
db, input, { rootDir: ctx.rootDir || process.cwd() },
|
|
555
|
+
),
|
|
556
|
+
};
|
|
557
|
+
}
|
|
459
558
|
case 'system.doctor': {
|
|
460
559
|
return doctor.runDoctor(db, {
|
|
461
560
|
rootDir: ctx.rootDir || process.cwd(),
|
|
@@ -501,7 +600,7 @@ function startServer({ dbPath, rootDir, projectOnWrite = true, project = project
|
|
|
501
600
|
{ capabilities: { tools: {} } },
|
|
502
601
|
);
|
|
503
602
|
|
|
504
|
-
server.setRequestHandler(
|
|
603
|
+
server.setRequestHandler('tools/list', async () => ({
|
|
505
604
|
tools: tools.map((t) => ({
|
|
506
605
|
name: t.name,
|
|
507
606
|
description: t.description,
|
|
@@ -509,7 +608,7 @@ function startServer({ dbPath, rootDir, projectOnWrite = true, project = project
|
|
|
509
608
|
})),
|
|
510
609
|
}));
|
|
511
610
|
|
|
512
|
-
server.setRequestHandler(
|
|
611
|
+
server.setRequestHandler('tools/call', async (request) => {
|
|
513
612
|
const { name, arguments: args = {} } = request.params;
|
|
514
613
|
const toolStart = Date.now();
|
|
515
614
|
let toolError = null;
|
|
@@ -623,6 +722,51 @@ async function main() {
|
|
|
623
722
|
process.on('SIGINT', cleanup);
|
|
624
723
|
}
|
|
625
724
|
|
|
725
|
+
function hookMetadata(value, fallback = 'unknown', maxLength = 256) {
|
|
726
|
+
const normalized = typeof value === 'string' ? value.trim() : '';
|
|
727
|
+
return (normalized || fallback).slice(0, maxLength);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
function appendHookLifecycleEvent({ rootDir, action, hookInput = {} } = {}) {
|
|
731
|
+
if (!['start', 'stop'].includes(action)) {
|
|
732
|
+
const error = new Error('hook lifecycle action must be start or stop');
|
|
733
|
+
error.code = 'VALIDATION_ERROR';
|
|
734
|
+
throw error;
|
|
735
|
+
}
|
|
736
|
+
const root = path.resolve(rootDir || process.cwd());
|
|
737
|
+
const dbPath = path.join(root, '.ultra', 'state.db');
|
|
738
|
+
if (!fs.existsSync(dbPath)) return { recorded: false, reason: 'STATE_DB_MISSING' };
|
|
739
|
+
|
|
740
|
+
const db = openStateDb(dbPath);
|
|
741
|
+
try {
|
|
742
|
+
ensureSchemaVersion(db);
|
|
743
|
+
const change = db.prepare(
|
|
744
|
+
`SELECT id FROM changes WHERE status IN ('active', 'blocked', 'ready')
|
|
745
|
+
ORDER BY updated_at DESC, rowid DESC LIMIT 1`,
|
|
746
|
+
).get();
|
|
747
|
+
if (!change) return { recorded: false, reason: 'NO_ACTIVE_CHANGE' };
|
|
748
|
+
const task = db.prepare(
|
|
749
|
+
`SELECT id FROM tasks WHERE change_id = ? AND status IN ('in_progress', 'pending', 'blocked')
|
|
750
|
+
ORDER BY CASE status WHEN 'in_progress' THEN 0 WHEN 'blocked' THEN 1 ELSE 2 END,
|
|
751
|
+
updated_at DESC, rowid DESC LIMIT 1`,
|
|
752
|
+
).get(change.id);
|
|
753
|
+
const payload = {
|
|
754
|
+
agent_id: hookMetadata(hookInput.agent_id),
|
|
755
|
+
agent_type: hookMetadata(hookInput.agent_type),
|
|
756
|
+
host_session_id: hookMetadata(hookInput.session_id, '', 256),
|
|
757
|
+
};
|
|
758
|
+
ops.appendEvent(db, {
|
|
759
|
+
type: action === 'start' ? 'subagent_started' : 'subagent_stopped',
|
|
760
|
+
change_id: change.id,
|
|
761
|
+
task_id: task?.id || null,
|
|
762
|
+
payload,
|
|
763
|
+
});
|
|
764
|
+
return { recorded: true, change_id: change.id, task_id: task?.id || null };
|
|
765
|
+
} finally {
|
|
766
|
+
closeStateDb(db);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
626
770
|
if (require.main === module) {
|
|
627
771
|
main().catch((err) => {
|
|
628
772
|
process.stderr.write(`mcp-server fatal: ${err.message}\n`);
|
|
@@ -639,10 +783,12 @@ module.exports = {
|
|
|
639
783
|
PLAN_TOOLS,
|
|
640
784
|
BASELINE_TOOLS,
|
|
641
785
|
CHANGE_TOOLS,
|
|
786
|
+
WORKFLOW_TOOLS,
|
|
642
787
|
SYSTEM_TOOLS,
|
|
643
788
|
REGISTERED_TOOLS,
|
|
644
789
|
STATELESS_TOOLS,
|
|
645
790
|
MUTATING_TOOLS,
|
|
791
|
+
appendHookLifecycleEvent,
|
|
646
792
|
readProjectBreadcrumb,
|
|
647
793
|
renderProjectBreadcrumb,
|
|
648
794
|
};
|
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.15.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
|
}
|
|
@@ -1,84 +1,69 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ultra-change
|
|
3
|
-
description: Open or resume
|
|
3
|
+
description: Open or resume one bounded post-baseline change and connect its intent, delta, task plan, fresh context, and convergence evidence. Use when handling daily feature work, fixes, redesigns, or incidents after an Ultra baseline exists.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# Maintain one continuous change
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
Use Ultra MCP for lifecycle writes. Keep external memory and code-graph content in
|
|
9
|
+
their providers; Ultra stores references only.
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Bind or create
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
1. Read `system.doctor`, `baseline.get`, `change.list`, and `change.breadcrumb`.
|
|
14
|
+
2. Resume the change matching the requested outcome. Require an id when several could
|
|
15
|
+
match; do not create a duplicate packet.
|
|
16
|
+
3. New ordinary work requires a ready baseline. An incident on unhealthy authority
|
|
17
|
+
requires an explicit `baseline_bypass` reason and approver. Never infer approval
|
|
18
|
+
from urgency.
|
|
19
|
+
4. Read `references/change-contract.md`. Persist the complete outcome, executable
|
|
20
|
+
acceptance, non-goals, public seams, recovery contract, unresolved decisions,
|
|
21
|
+
documentation impact, profile rationale, material risk flags, and research
|
|
22
|
+
disposition. Do not derive these fields from a title or free-form intent later.
|
|
23
|
+
5. Select `quick`, `standard`, `major`, or `incident` from current scope and risk.
|
|
24
|
+
Escalate a proposed quick change when its contract carries material risk or research.
|
|
25
|
+
Then call `change.create` or `change.update` and read the normalized record back.
|
|
26
|
+
6. Resume or start the linked `change` workflow with `workflow.list`/`workflow.start`.
|
|
16
27
|
|
|
17
|
-
##
|
|
28
|
+
## Record the change workflow
|
|
18
29
|
|
|
19
|
-
|
|
20
|
-
- Create ordinary work only when the baseline is healthy and `ready`.
|
|
21
|
-
- Resume a change that was already active when baseline drift appears; record the
|
|
22
|
-
condition as a warning and restore baseline readiness before ordinary convergence.
|
|
23
|
-
- Create an `incident` on an unhealthy baseline only with an explicit
|
|
24
|
-
`baseline_bypass` containing the reason and approver.
|
|
25
|
-
- Route every other missing, migrated, incomplete, or stale baseline to `ultra-init`.
|
|
26
|
-
2. Call `change.list` and `change.breadcrumb`.
|
|
27
|
-
- Resume the single change that matches the requested outcome.
|
|
28
|
-
- Require an explicit id when several changes could match.
|
|
29
|
-
- Create a new change only when no existing packet represents the same outcome.
|
|
30
|
-
3. Capture the observable outcome, acceptance, non-goals, affected public seam,
|
|
31
|
-
documentation impact, and unresolved decisions.
|
|
32
|
-
4. Classify the change as `quick`, `standard`, `major`, or `incident`. Ask only for a
|
|
33
|
-
material product, risk, or recovery decision that repository evidence cannot answer.
|
|
34
|
-
5. Call `change.create` with a stable kebab-case id, or `change.update` when resuming.
|
|
35
|
-
For incident break-glass, persist the approved reason in `baseline_bypass`; never
|
|
36
|
-
infer approval from urgency.
|
|
37
|
-
6. Write the minimum artifact set required by the classification:
|
|
30
|
+
Use `workflow.step` as evidence becomes durable:
|
|
38
31
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
context-manifest.json # generated projection
|
|
46
|
-
spec-learning.json # generated projection
|
|
47
|
-
verification.md # generated projection
|
|
48
|
-
```
|
|
32
|
+
- `bind-baseline`: current baseline id, revision, branch, and worktree evidence;
|
|
33
|
+
- `classify-change`: kind and rationale;
|
|
34
|
+
- `record-intent`: intent artifact, acceptance, scope, and resolved docs impact;
|
|
35
|
+
- `plan-change`: completed plan workflow and authoritative task ids;
|
|
36
|
+
- `compile-context`: current task-bound `change.context` manifest and digest;
|
|
37
|
+
- `verify-readiness`: ready breadcrumb with one executable next action.
|
|
49
38
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
one outcome, bounded ownership, dependencies, a live public seam, an exact
|
|
55
|
-
verification command, and documentation impact.
|
|
56
|
-
8. Compile `change.context` for the next role and gate. Include only required
|
|
57
|
-
references and the task execution contract. Missing required references, digest or
|
|
58
|
-
HEAD drift, an absent public seam or verification command, and unknown
|
|
59
|
-
documentation impact are blockers. File count, token estimate, and context-share
|
|
60
|
-
thresholds are advisory warnings: narrow reads or split the slice when useful, but
|
|
61
|
-
never raise a threshold merely to make the warning disappear or refuse necessary
|
|
62
|
-
incident context.
|
|
63
|
-
9. Call `change.breadcrumb` and return its single next action.
|
|
39
|
+
Standard and major changes keep deltas under
|
|
40
|
+
`.ultra/changes/active/<id>/delta/` and an evidence-linked `plan.md`. Deltas describe
|
|
41
|
+
differences from the baseline; they are not a second baseline. Incidents maintain the
|
|
42
|
+
structured diagnosis artifact and recovery evidence.
|
|
64
43
|
|
|
65
|
-
|
|
44
|
+
When `research_disposition.status` is `bounded` or `required`, run `ultra-research`
|
|
45
|
+
with the exact recorded mode and selected steps before planning. Planning is blocked
|
|
46
|
+
until that change-bound research run is complete, current, and selection-equivalent.
|
|
47
|
+
Use `none` only when the Change Contract already has sufficient evidence.
|
|
66
48
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
49
|
+
Use `ultra-plan` to create complete DB-backed task contracts. Recompile
|
|
50
|
+
`change.context` after any task, HEAD, specification, or decision change. The task's DB
|
|
51
|
+
contract is authoritative; Prompt input may not override its seam or verification.
|
|
52
|
+
Required missing or stale refs block. Budget warnings guide narrower context but never
|
|
53
|
+
justify dropping necessary evidence or refusing legitimate work.
|
|
72
54
|
|
|
73
|
-
|
|
55
|
+
An ordinary active change remains executable only while its durable `bind-baseline`
|
|
56
|
+
evidence still names the current approved-ready baseline. HEAD, worktree, specification,
|
|
57
|
+
or source-evidence drift created after that binding is advisory until delivery
|
|
58
|
+
reconciliation. Adoption/migration state, missing approval or evidence, blocking gaps,
|
|
59
|
+
and any other structural baseline failure block tasks, context, and later stages. Only
|
|
60
|
+
the recorded incident break-glass authority can bypass that gate.
|
|
74
61
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
caused by the active change is reconciled at archive.
|
|
62
|
+
Call `workflow.complete` only after all change workflow steps are current. Return the
|
|
63
|
+
change id, kind, task set, context digest, blockers or warnings, workflow state, and the
|
|
64
|
+
breadcrumb's one route.
|
|
79
65
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
no-change reason; failed ordinary reconciliation rolls back state and artifacts.
|
|
66
|
+
When implementation reveals a stable missing invariant, use
|
|
67
|
+
`change.learning_propose` and the approval/reject/apply transition. Unresolved learning
|
|
68
|
+
blocks delivery. An approved application must bind the declared target anchor, its
|
|
69
|
+
before and after digests, and current evidence before the proposal becomes `applied`.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Change Contract and profile
|
|
2
|
+
|
|
3
|
+
Create one contract before planning. Keep every field specific enough to verify and
|
|
4
|
+
stable enough to survive a fresh session.
|
|
5
|
+
|
|
6
|
+
## Contract
|
|
7
|
+
|
|
8
|
+
- `outcome`: one externally observable result.
|
|
9
|
+
- `acceptance`: stable ids with a criterion and exact verification for each accepted
|
|
10
|
+
behavior.
|
|
11
|
+
- `non_goals`: explicit boundaries that prevent accidental scope growth.
|
|
12
|
+
- `public_seams`: entry points or outputs through which acceptance is observed.
|
|
13
|
+
- `recovery`: a bounded reversal or recovery strategy and its verification.
|
|
14
|
+
- `unresolved_decisions`: stable ids, summary, blocking state, and owner when known.
|
|
15
|
+
|
|
16
|
+
Resolve every blocking decision before planning. Keep non-blocking uncertainty visible
|
|
17
|
+
in the task and verification contracts.
|
|
18
|
+
|
|
19
|
+
## Profile
|
|
20
|
+
|
|
21
|
+
- `quick`: one low-risk task, no material risk flags, and no research dependency.
|
|
22
|
+
- `standard`: a bounded change with multiple tasks or ordinary integration risk.
|
|
23
|
+
- `major`: a broad or consequential change with material contract, migration,
|
|
24
|
+
authorization, security, multi-module, external-integration, or release impact.
|
|
25
|
+
- `incident`: urgent diagnosis and recovery with a reproducible symptom. Baseline
|
|
26
|
+
break-glass still requires a recorded approver and recovery contract.
|
|
27
|
+
|
|
28
|
+
Record a profile rationale and every applicable risk flag. Select the smallest profile
|
|
29
|
+
whose guarantees cover the real blast radius; never use profile labels as estimates.
|
|
30
|
+
|
|
31
|
+
## Research disposition
|
|
32
|
+
|
|
33
|
+
Use `none` with a rationale when accepted evidence is sufficient. Use `bounded` or
|
|
34
|
+
`required` with a supported mode and the exact selected semantic steps when product,
|
|
35
|
+
behavior, architecture, deployment, or risk facts remain load-bearing. The recorded
|
|
36
|
+
selection is the planning gate.
|