ultra-builder-pro-cli 0.5.3 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/.ultra-template/changes/active/.gitkeep +1 -0
  2. package/.ultra-template/changes/archive/.gitkeep +1 -0
  3. package/CHANGELOG.md +90 -14
  4. package/README.md +38 -32
  5. package/adapters/_shared/codex-assets.cjs +32 -151
  6. package/adapters/_shared/frontmatter.cjs +2 -2
  7. package/adapters/_shared/handbook.cjs +10 -10
  8. package/adapters/_shared/path-rewrite.cjs +0 -3
  9. package/adapters/_shared/runtime-assets.cjs +13 -4
  10. package/adapters/_shared/tests/handbook.test.cjs +4 -1
  11. package/adapters/_shared/tests/path-rewrite.test.cjs +1 -3
  12. package/adapters/_shared/tests/resolve-target.test.cjs +1 -11
  13. package/adapters/_shared/tests/runtime-assets.test.cjs +7 -6
  14. package/adapters/opencode.js +82 -26
  15. package/adapters/tests/claude.test.cjs +1 -4
  16. package/adapters/tests/codex.test.cjs +9 -8
  17. package/adapters/tests/opencode.test.cjs +17 -11
  18. package/bin/install.js +6 -28
  19. package/bin/orchestrator.js +12 -30
  20. package/commands/ultra-change.md +47 -0
  21. package/commands/ultra-deliver.md +14 -7
  22. package/commands/ultra-dev.md +6 -3
  23. package/commands/ultra-doctor.md +36 -0
  24. package/commands/ultra-init.md +4 -3
  25. package/commands/ultra-status.md +7 -5
  26. package/commands/ultra-test.md +2 -1
  27. package/docs/AGENT-CONTEXT.md +35 -28
  28. package/docs/ARCHITECTURE.md +49 -21
  29. package/docs/PLAN.zh-CN.md +67 -68
  30. package/docs/ROADMAP.md +17 -17
  31. package/docs/RUNTIME-COMPAT-MATRIX.md +63 -62
  32. package/hooks/README.md +8 -7
  33. package/hooks/active_task_context.py +22 -15
  34. package/hooks/health_check.py +136 -38
  35. package/hooks/workflow_context.py +120 -30
  36. package/mcp-server/lib/change-workflow.cjs +556 -0
  37. package/mcp-server/lib/change-workflow.test.cjs +259 -0
  38. package/mcp-server/lib/doctor.cjs +112 -0
  39. package/mcp-server/lib/doctor.test.cjs +86 -0
  40. package/mcp-server/lib/init-project.cjs +32 -6
  41. package/mcp-server/lib/pricing.cjs +0 -6
  42. package/mcp-server/lib/runtime-state.cjs +222 -0
  43. package/mcp-server/lib/runtime-state.test.cjs +92 -0
  44. package/mcp-server/lib/state-db.cjs +31 -8
  45. package/mcp-server/lib/state-ops.cjs +24 -3
  46. package/mcp-server/lib/telemetry.cjs +4 -0
  47. package/mcp-server/server.cjs +102 -10
  48. package/mcp-server/tests/db-ops.test.cjs +5 -1
  49. package/mcp-server/tests/init-project.test.cjs +45 -0
  50. package/mcp-server/tests/server-contract.test.cjs +140 -1
  51. package/mcp-server/tests/state-db.test.cjs +44 -7
  52. package/mcp-server/tests/state-ops.test.cjs +42 -0
  53. package/mcp-server/tests/telemetry.test.cjs +14 -0
  54. package/orchestrator/daemon.cjs +39 -0
  55. package/orchestrator/dispatch-rules.cjs +15 -4
  56. package/orchestrator/tests/daemon.test.cjs +63 -12
  57. package/orchestrator/tests/dispatch-rules.test.cjs +12 -10
  58. package/package.json +5 -7
  59. package/settings.json +1 -2
  60. package/skills/ultra-change/SKILL.md +140 -0
  61. package/skills/ultra-deliver/SKILL.md +81 -15
  62. package/skills/ultra-dev/SKILL.md +32 -12
  63. package/skills/ultra-doctor/SKILL.md +90 -0
  64. package/skills/ultra-init/SKILL.md +8 -6
  65. package/skills/ultra-review/SKILL.md +16 -0
  66. package/skills/ultra-status/SKILL.md +30 -7
  67. package/skills/ultra-test/SKILL.md +14 -2
  68. package/skills/ultra-verify/SKILL.md +22 -41
  69. package/skills/ultra-verify/evals/evals.json +22 -24
  70. package/skills/ultra-verify/references/confidence-system.md +15 -32
  71. package/skills/ultra-verify/references/cross-verify-modes.md +14 -59
  72. package/skills/ultra-verify/scripts/verify_wait.py +44 -81
  73. package/spec/cli-protocol.md +12 -1
  74. package/spec/mcp-tools.yaml +340 -4
  75. package/spec/schemas/mcp-tools.schema.json +1 -1
  76. package/spec/schemas/skill-manifest.schema.json +2 -2
  77. package/spec/schemas/state-db.sql +119 -2
  78. package/ultra-tools/cli.cjs +3 -0
  79. package/ultra-tools/commands/system.cjs +57 -0
  80. package/ultra-tools/system-command.test.cjs +53 -0
  81. package/adapters/_shared/md-to-toml.cjs +0 -54
  82. package/adapters/_shared/rtk-detect.cjs +0 -86
  83. package/adapters/_shared/tests/md-to-toml.test.cjs +0 -48
  84. package/adapters/_shared/tests/rtk-detect.test.cjs +0 -116
  85. package/adapters/gemini.js +0 -204
  86. package/adapters/tests/gemini.test.cjs +0 -112
  87. package/docs/LEGACY-HERMES.md +0 -1175
  88. package/orchestrator/code-graph-watcher.cjs +0 -121
  89. package/orchestrator/tests/code-graph-watcher.test.cjs +0 -183
  90. package/skills/gemini-collab/SKILL.md +0 -51
  91. package/skills/gemini-collab/evals/evals.json +0 -42
@@ -1,121 +0,0 @@
1
- 'use strict';
2
-
3
- // Phase 6.4 — code-review-graph live watcher.
4
- //
5
- // fs-watch the project's source tree, debounce bursts of editor saves, and
6
- // hand batches off to an onBatch callback. Callers wire the callback to
7
- // whatever keeps the graph fresh (e.g. the external code-review-graph MCP
8
- // server's build_or_update_graph_tool). Keeping the glue in callers means
9
- // this module has no MCP-client dependency and is trivial to unit-test.
10
- //
11
- // PLAN D24: real-time feedback beats startup rebuilds; agents learn about
12
- // impact radius immediately after save, not after the next phase boot.
13
-
14
- const chokidar = require('chokidar');
15
-
16
- const DEFAULT_INCLUDE_EXT = new Set([
17
- '.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs',
18
- '.py', '.go', '.rs', '.java', '.rb', '.php',
19
- '.c', '.cc', '.cpp', '.h', '.hpp',
20
- '.swift', '.kt', '.scala',
21
- ]);
22
-
23
- const DEFAULT_EXCLUDE_GLOBS = Object.freeze([
24
- '**/node_modules/**',
25
- '**/.git/**',
26
- '**/.ultra/**',
27
- '**/.code-review-graph/**',
28
- '**/dist/**',
29
- '**/build/**',
30
- '**/coverage/**',
31
- '**/.next/**',
32
- ]);
33
-
34
- function hasCodeExt(p, includeExt) {
35
- const idx = p.lastIndexOf('.');
36
- if (idx < 0) return false;
37
- return includeExt.has(p.slice(idx).toLowerCase());
38
- }
39
-
40
- function startWatcher({
41
- repoRoot,
42
- debounceMs = 500,
43
- includeExt = DEFAULT_INCLUDE_EXT,
44
- excludeGlobs = DEFAULT_EXCLUDE_GLOBS,
45
- largeBatchThreshold = 50,
46
- onBatch,
47
- } = {}) {
48
- if (!repoRoot) throw new Error('startWatcher: repoRoot required');
49
- if (typeof onBatch !== 'function') throw new Error('startWatcher: onBatch required');
50
-
51
- let stopped = false;
52
- let ready = false;
53
- let timer = null;
54
- // kind → latest kind; path → {kind, ts} so rapid edit+add collapses cleanly.
55
- const pending = new Map();
56
-
57
- const w = chokidar.watch(repoRoot, {
58
- ignored: excludeGlobs,
59
- ignoreInitial: true,
60
- persistent: true,
61
- awaitWriteFinish: { stabilityThreshold: 20, pollInterval: 10 },
62
- });
63
-
64
- const readyPromise = new Promise((resolve) => {
65
- w.once('ready', () => { ready = true; resolve(); });
66
- });
67
-
68
- function record(kind, p) {
69
- // Drop events fired during chokidar's initial scan — ignoreInitial=true
70
- // mostly handles this but awaitWriteFinish can still surface add events
71
- // from files that existed when the watcher started.
72
- if (stopped || !ready) return;
73
- if (!hasCodeExt(p, includeExt)) return;
74
- pending.set(p, { kind, ts: Date.now() });
75
- schedule();
76
- }
77
-
78
- function schedule() {
79
- if (timer) return;
80
- timer = setTimeout(flush, debounceMs);
81
- }
82
-
83
- function flush() {
84
- timer = null;
85
- if (pending.size === 0) return;
86
- const changes = Array.from(pending.entries()).map(([p, meta]) => ({ path: p, kind: meta.kind, ts: meta.ts }));
87
- pending.clear();
88
- const batch = {
89
- changes,
90
- large_batch: changes.length >= largeBatchThreshold,
91
- ts: new Date().toISOString(),
92
- };
93
- try { onBatch(batch); }
94
- catch (err) { process.stderr.write(`code-graph-watcher onBatch error: ${err.message}\n`); }
95
- }
96
-
97
- w.on('add', (p) => record('add', p));
98
- w.on('change', (p) => record('change', p));
99
- w.on('unlink', (p) => record('unlink', p));
100
- w.on('error', (err) => process.stderr.write(`watcher error: ${err.message}\n`));
101
-
102
- return {
103
- stop() {
104
- if (stopped) return;
105
- stopped = true;
106
- if (timer) clearTimeout(timer);
107
- timer = null;
108
- w.close().catch(() => { /* ignore close errors */ });
109
- },
110
- ready() { return readyPromise; },
111
- get running() { return !stopped; },
112
- get pendingCount() { return pending.size; },
113
- flushNow: flush,
114
- };
115
- }
116
-
117
- module.exports = {
118
- startWatcher,
119
- DEFAULT_INCLUDE_EXT,
120
- DEFAULT_EXCLUDE_GLOBS,
121
- };
@@ -1,183 +0,0 @@
1
- 'use strict';
2
-
3
- // Phase 6.4 — code-graph watcher:
4
- // • fs-watch project code paths, debounce bursts, deliver batches via
5
- // onBatch callback. The watcher itself does not talk to the external
6
- // code-review-graph MCP server — that glue lives in callers (daemon /
7
- // CLI / smoke scripts) so the watcher stays testable with just an
8
- // onBatch spy.
9
-
10
- const { test } = require('node:test');
11
- const assert = require('node:assert/strict');
12
- const fs = require('node:fs');
13
- const os = require('node:os');
14
- const path = require('node:path');
15
-
16
- const watcher = require('../code-graph-watcher.cjs');
17
-
18
- function mkTree(layout = {}) {
19
- const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-watch-'));
20
- for (const [rel, content] of Object.entries(layout)) {
21
- const file = path.join(root, rel);
22
- fs.mkdirSync(path.dirname(file), { recursive: true });
23
- fs.writeFileSync(file, content);
24
- }
25
- return root;
26
- }
27
-
28
- function cleanupTree(root) {
29
- try { fs.rmSync(root, { recursive: true, force: true }); } catch (_) { /* ignore */ }
30
- }
31
-
32
- function waitForBatches(batches, count, timeoutMs = 1500) {
33
- return new Promise((resolve, reject) => {
34
- const start = Date.now();
35
- const timer = setInterval(() => {
36
- if (batches.length >= count) { clearInterval(timer); resolve(); return; }
37
- if (Date.now() - start > timeoutMs) { clearInterval(timer); reject(new Error(`timed out waiting for ${count} batches, got ${batches.length}`)); }
38
- }, 20);
39
- });
40
- }
41
-
42
- async function waitStable(handle) {
43
- // Block on chokidar's 'ready' so initial add events from pre-existing
44
- // files don't leak into test assertions.
45
- await handle.ready();
46
- }
47
-
48
- test('touch a watched file → onBatch fires with that path after debounce', async () => {
49
- const root = mkTree({ 'src/a.js': '// initial\n' });
50
- const batches = [];
51
- const handle = watcher.startWatcher({
52
- repoRoot: root,
53
- debounceMs: 80,
54
- onBatch: (b) => batches.push(b),
55
- });
56
- try {
57
- await waitStable(handle);
58
- fs.writeFileSync(path.join(root, 'src/a.js'), '// changed\n');
59
- await waitForBatches(batches, 1);
60
- assert.equal(batches.length, 1);
61
- assert.ok(batches[0].changes.some((c) => c.path.endsWith('src/a.js')));
62
- } finally {
63
- handle.stop();
64
- cleanupTree(root);
65
- }
66
- });
67
-
68
- test('debounce merges bursts: 3 writes within window → 1 batch', async () => {
69
- const root = mkTree({ 'src/a.js': '0', 'src/b.js': '0', 'src/c.js': '0' });
70
- const batches = [];
71
- const handle = watcher.startWatcher({
72
- repoRoot: root,
73
- debounceMs: 100,
74
- onBatch: (b) => batches.push(b),
75
- });
76
- try {
77
- await waitStable(handle);
78
- fs.writeFileSync(path.join(root, 'src/a.js'), '1');
79
- fs.writeFileSync(path.join(root, 'src/b.js'), '1');
80
- fs.writeFileSync(path.join(root, 'src/c.js'), '1');
81
- await waitForBatches(batches, 1);
82
- // Give a bit extra time in case a 2nd batch is coming (it shouldn't).
83
- await new Promise((r) => setTimeout(r, 150));
84
- assert.equal(batches.length, 1, `expected 1 merged batch, got ${batches.length}`);
85
- const paths = new Set(batches[0].changes.map((c) => path.basename(c.path)));
86
- assert.ok(paths.has('a.js') && paths.has('b.js') && paths.has('c.js'));
87
- } finally {
88
- handle.stop();
89
- cleanupTree(root);
90
- }
91
- });
92
-
93
- test('excludePaths: node_modules changes are ignored', async () => {
94
- const root = mkTree({
95
- 'src/a.js': '0',
96
- 'node_modules/lib/x.js': '0',
97
- });
98
- const batches = [];
99
- const handle = watcher.startWatcher({
100
- repoRoot: root,
101
- debounceMs: 80,
102
- onBatch: (b) => batches.push(b),
103
- });
104
- try {
105
- await waitStable(handle);
106
- fs.writeFileSync(path.join(root, 'node_modules/lib/x.js'), '1');
107
- await new Promise((r) => setTimeout(r, 200));
108
- // If only node_modules was changed, no batch should fire.
109
- assert.equal(batches.length, 0, 'node_modules write should be ignored');
110
- // Sanity: a real source change still fires.
111
- fs.writeFileSync(path.join(root, 'src/a.js'), '1');
112
- await waitForBatches(batches, 1);
113
- assert.equal(batches.length, 1);
114
- assert.ok(batches[0].changes.every((c) => !c.path.includes('node_modules')));
115
- } finally {
116
- handle.stop();
117
- cleanupTree(root);
118
- }
119
- });
120
-
121
- test('stop(): subsequent writes do not deliver', async () => {
122
- const root = mkTree({ 'src/a.js': '0' });
123
- const batches = [];
124
- const handle = watcher.startWatcher({
125
- repoRoot: root,
126
- debounceMs: 80,
127
- onBatch: (b) => batches.push(b),
128
- });
129
- try {
130
- await waitStable(handle);
131
- handle.stop();
132
- fs.writeFileSync(path.join(root, 'src/a.js'), '1');
133
- await new Promise((r) => setTimeout(r, 200));
134
- assert.equal(batches.length, 0);
135
- } finally {
136
- cleanupTree(root);
137
- }
138
- });
139
-
140
- test('large_batch flag: >threshold changes in one burst', async () => {
141
- const files = {};
142
- for (let i = 0; i < 12; i++) files[`src/f${i}.js`] = '0';
143
- const root = mkTree(files);
144
- const batches = [];
145
- const handle = watcher.startWatcher({
146
- repoRoot: root,
147
- debounceMs: 120,
148
- largeBatchThreshold: 10, // lower for test speed
149
- onBatch: (b) => batches.push(b),
150
- });
151
- try {
152
- await waitStable(handle);
153
- for (let i = 0; i < 12; i++) fs.writeFileSync(path.join(root, `src/f${i}.js`), '1');
154
- await waitForBatches(batches, 1);
155
- await new Promise((r) => setTimeout(r, 150));
156
- assert.equal(batches.length, 1);
157
- assert.equal(batches[0].changes.length, 12);
158
- assert.equal(batches[0].large_batch, true);
159
- } finally {
160
- handle.stop();
161
- cleanupTree(root);
162
- }
163
- });
164
-
165
- test('non-code files ignored (e.g. .log, .tmp)', async () => {
166
- const root = mkTree({ 'src/a.js': '0', 'run.log': '0', 'tmp.tmp': '0' });
167
- const batches = [];
168
- const handle = watcher.startWatcher({
169
- repoRoot: root,
170
- debounceMs: 80,
171
- onBatch: (b) => batches.push(b),
172
- });
173
- try {
174
- await waitStable(handle);
175
- fs.writeFileSync(path.join(root, 'run.log'), '1');
176
- fs.writeFileSync(path.join(root, 'tmp.tmp'), '1');
177
- await new Promise((r) => setTimeout(r, 200));
178
- assert.equal(batches.length, 0);
179
- } finally {
180
- handle.stop();
181
- cleanupTree(root);
182
- }
183
- });
@@ -1,51 +0,0 @@
1
- ---
2
- name: gemini-collab
3
- description: Ask Gemini CLI for an explicitly requested, read-only second opinion on a bounded review, diagnosis, architecture, or comparison question.
4
- ---
5
-
6
- # Gemini Collaboration
7
-
8
- Claude Code remains primary and owns scope, evidence, edits, verification, and the final answer.
9
- Gemini is an untrusted read-only advisor. Use this skill only when the user explicitly requests a
10
- Gemini perspective.
11
-
12
- ## Preconditions
13
-
14
- 1. Confirm `gemini --version` and authentication.
15
- 2. Fix the workspace, files or diff range, question, evidence standard, and expected response shape.
16
- 3. Write the primary analysis first when independence matters.
17
- 4. Never send secrets, unrelated files, or an unbounded home directory.
18
-
19
- ## Modes
20
-
21
- - `review`: findings against an exact diff or file set.
22
- - `understand`: a bounded architecture or flow question.
23
- - `opinion`: one decision with named constraints and alternatives.
24
- - `compare`: independent answers to the same question.
25
- - `free`: another tightly scoped read-only prompt.
26
-
27
- ## Safe invocation
28
-
29
- Create a session directory under `.ultra/collab/`, then use Gemini's plan approval mode:
30
-
31
- ```bash
32
- SESSION_PATH=".ultra/collab/$(date +%Y%m%d-%H%M%S)-gemini-<mode>"
33
- mkdir -p "${SESSION_PATH}"
34
- gemini --approval-mode plan \
35
- --output-format text \
36
- -p "<bounded prompt>" \
37
- > "${SESSION_PATH}/gemini-output.md" \
38
- 2> "${SESSION_PATH}/gemini-error.log"
39
- ```
40
-
41
- Do not use auto-edit, YOLO, or permission bypass.
42
-
43
- ## Synthesis
44
-
45
- 1. Read the output only after the process exits and the file is non-empty.
46
- 2. Verify consequential claims against the current checkout, tests, runtime, or primary docs.
47
- 3. Separate agreement, useful dissent, and unsupported assertions.
48
- 4. Return one Claude Code-owned conclusion; do not paste an unreviewed advisor transcript.
49
-
50
- If Gemini is missing, unauthenticated, times out, or returns empty output, report the degraded path
51
- and continue with primary evidence. Never block the user's task solely on advisor failure.
@@ -1,42 +0,0 @@
1
- {
2
- "skill_name": "gemini-collab",
3
- "evals": [
4
- {
5
- "id": 1,
6
- "prompt": "/gemini-collab review",
7
- "expected_output": "Claude creates a session directory, invokes gemini CLI to review current changes, reads the output file, and presents a synthesis report",
8
- "assertions": [
9
- "Creates a session directory under .ultra/collab/ with gemini-review in the name",
10
- "Uses 'gemini -p' with --yolo flag to invoke Gemini CLI",
11
- "Does NOT use '-o text' flag (Gemini outputs to stdout by default)",
12
- "Redirects output to a file (> output.md)",
13
- "Uses Read tool to read the output file",
14
- "Presents a synthesis with Consensus and Divergent Views sections"
15
- ]
16
- },
17
- {
18
- "id": 2,
19
- "prompt": "/gemini-collab compare 'Should we use Redis or Memcached for our caching layer?'",
20
- "expected_output": "Claude answers independently first, then sends the same question to Gemini, reads output, and presents a comparative synthesis",
21
- "assertions": [
22
- "Claude writes its own answer BEFORE invoking Gemini",
23
- "Sends the question to Gemini without revealing Claude's position",
24
- "Creates session directory under .ultra/collab/",
25
- "Reads Gemini output with Read tool",
26
- "Presents both analyses side by side with synthesis"
27
- ]
28
- },
29
- {
30
- "id": 3,
31
- "prompt": "/gemini-collab understand",
32
- "expected_output": "Claude invokes Gemini in sandbox mode to analyze the project, reads output, and synthesizes with its own understanding",
33
- "assertions": [
34
- "Uses --sandbox flag for read-only analysis",
35
- "Uses gemini -p with a project analysis prompt",
36
- "Installs from @google/gemini-cli (not @anthropic-ai/gemini-cli)",
37
- "Redirects to file, reads with Read tool",
38
- "Produces a unified project understanding report"
39
- ]
40
- }
41
- ]
42
- }