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