ultra-builder-pro-cli 0.6.0 → 0.8.1

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 (75) 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 -11
  4. package/README.md +39 -19
  5. package/adapters/_shared/handbook.cjs +8 -7
  6. package/adapters/_shared/provenance.cjs +286 -0
  7. package/adapters/_shared/runtime-assets.cjs +2 -0
  8. package/adapters/_shared/tests/handbook.test.cjs +4 -1
  9. package/adapters/_shared/tests/provenance.test.cjs +133 -0
  10. package/adapters/_shared/tests/runtime-assets.test.cjs +2 -0
  11. package/adapters/claude.js +33 -0
  12. package/adapters/codex.js +87 -0
  13. package/adapters/opencode.js +147 -20
  14. package/adapters/tests/codex.test.cjs +42 -3
  15. package/adapters/tests/opencode.test.cjs +16 -9
  16. package/agents/debugger.md +12 -0
  17. package/bin/install.js +63 -27
  18. package/bin/orchestrator.js +1 -26
  19. package/commands/ultra-change.md +48 -0
  20. package/commands/ultra-deliver.md +14 -7
  21. package/commands/ultra-dev.md +6 -3
  22. package/commands/ultra-doctor.md +36 -0
  23. package/commands/ultra-init.md +4 -3
  24. package/commands/ultra-status.md +7 -5
  25. package/commands/ultra-test.md +2 -1
  26. package/docs/AGENT-CONTEXT.md +31 -23
  27. package/docs/ARCHITECTURE.md +80 -17
  28. package/docs/PLAN.zh-CN.md +37 -29
  29. package/docs/ROADMAP.md +14 -14
  30. package/docs/RUNTIME-COMPAT-MATRIX.md +19 -12
  31. package/hooks/README.md +14 -9
  32. package/hooks/active_task_context.py +22 -15
  33. package/hooks/health_check.py +136 -38
  34. package/hooks/workflow_checkpoint.py +15 -1
  35. package/hooks/workflow_context.py +120 -30
  36. package/hooks/workflow_resume.py +96 -7
  37. package/mcp-server/lib/change-workflow.cjs +625 -0
  38. package/mcp-server/lib/change-workflow.test.cjs +346 -0
  39. package/mcp-server/lib/doctor.cjs +112 -0
  40. package/mcp-server/lib/doctor.test.cjs +86 -0
  41. package/mcp-server/lib/init-project.cjs +32 -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 +17 -3
  46. package/mcp-server/server.cjs +102 -10
  47. package/mcp-server/tests/db-ops.test.cjs +5 -1
  48. package/mcp-server/tests/init-project.test.cjs +45 -0
  49. package/mcp-server/tests/server-contract.test.cjs +123 -1
  50. package/mcp-server/tests/state-db.test.cjs +44 -7
  51. package/mcp-server/tests/state-ops.test.cjs +19 -0
  52. package/orchestrator/daemon.cjs +39 -0
  53. package/orchestrator/tests/daemon.test.cjs +50 -0
  54. package/package.json +3 -3
  55. package/settings.json +1 -2
  56. package/skills/ultra-change/SKILL.md +149 -0
  57. package/skills/ultra-deliver/SKILL.md +83 -15
  58. package/skills/ultra-dev/SKILL.md +32 -12
  59. package/skills/ultra-doctor/SKILL.md +97 -0
  60. package/skills/ultra-init/SKILL.md +8 -5
  61. package/skills/ultra-review/SKILL.md +16 -0
  62. package/skills/ultra-status/SKILL.md +30 -7
  63. package/skills/ultra-test/SKILL.md +14 -2
  64. package/spec/cli-protocol.md +12 -1
  65. package/spec/mcp-tools.yaml +337 -1
  66. package/spec/schemas/mcp-tools.schema.json +1 -1
  67. package/spec/schemas/state-db.sql +117 -0
  68. package/ultra-tools/cli.cjs +3 -0
  69. package/ultra-tools/commands/system.cjs +57 -0
  70. package/ultra-tools/system-command.test.cjs +53 -0
  71. package/adapters/_shared/rtk-detect.cjs +0 -86
  72. package/adapters/_shared/tests/rtk-detect.test.cjs +0 -116
  73. package/docs/LEGACY-HERMES.md +0 -1099
  74. package/orchestrator/code-graph-watcher.cjs +0 -121
  75. package/orchestrator/tests/code-graph-watcher.test.cjs +0 -183
@@ -0,0 +1,53 @@
1
+ 'use strict';
2
+
3
+ const { test } = require('node:test');
4
+ const assert = require('node:assert/strict');
5
+ const fs = require('node:fs');
6
+ const os = require('node:os');
7
+ const path = require('node:path');
8
+ const { spawnSync } = require('node:child_process');
9
+
10
+ const { initStateDb, closeStateDb } = require('../mcp-server/lib/state-db.cjs');
11
+
12
+ const CLI = path.join(__dirname, 'cli.cjs');
13
+
14
+ function fixture() {
15
+ const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-system-cli-'));
16
+ const { db } = initStateDb(path.join(rootDir, '.ultra', 'state.db'));
17
+ closeStateDb(db);
18
+ return rootDir;
19
+ }
20
+
21
+ function invoke(rootDir, args) {
22
+ return spawnSync(process.execPath, [CLI, 'system', ...args], {
23
+ cwd: rootDir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'],
24
+ });
25
+ }
26
+
27
+ test('system doctor provides a read-only JSON health envelope', () => {
28
+ const rootDir = fixture();
29
+ try {
30
+ const result = invoke(rootDir, ['doctor']);
31
+ assert.equal(result.status, 0, result.stderr);
32
+ const envelope = JSON.parse(result.stdout.trim().split('\n').at(-1));
33
+ assert.equal(envelope.ok, true);
34
+ assert.equal(envelope.data.status, 'healthy');
35
+ assert.equal(envelope.data.repair_performed, false);
36
+ } finally {
37
+ fs.rmSync(rootDir, { recursive: true, force: true });
38
+ }
39
+ });
40
+
41
+ test('system doctor --repair creates a backup before recovery', () => {
42
+ const rootDir = fixture();
43
+ try {
44
+ const result = invoke(rootDir, ['doctor', '--repair']);
45
+ assert.equal(result.status, 0, result.stderr);
46
+ const envelope = JSON.parse(result.stdout.trim().split('\n').at(-1));
47
+ assert.equal(envelope.ok, true);
48
+ assert.equal(envelope.data.repair_performed, true);
49
+ assert.ok(fs.existsSync(envelope.data.backup_path));
50
+ } finally {
51
+ fs.rmSync(rootDir, { recursive: true, force: true });
52
+ }
53
+ });
@@ -1,86 +0,0 @@
1
- 'use strict';
2
-
3
- // Phase 6.1 — RTK soft dependency.
4
- //
5
- // RTK (Rust Token Killer) is an external CLI proxy the user installs
6
- // separately. We never hard-require it: installation continues even when
7
- // rtk is missing, we just print an install hint. This mirrors PLAN Phase
8
- // 6.1: "无 rtk → 提示安装 + 跳过(不硬依赖)".
9
- //
10
- // Tests inject a PATH override so we can exercise the happy/missing paths
11
- // without touching the real rtk binary on the machine.
12
-
13
- const { execFileSync } = require('node:child_process');
14
-
15
- const INSTALL_HINT =
16
- 'rtk not found. RTK is a token-saving proxy that rewrites Bash/git/npm\n' +
17
- ' commands transparently. Install: https://github.com/rcrocky/rtk\n' +
18
- ' (or run `cargo install rtk`). Continuing without RTK.';
19
-
20
- function envWithPath(pathOverride) {
21
- if (!pathOverride) return process.env;
22
- // Isolate rtk lookup to pathOverride + minimal system dirs (so shebangs
23
- // like `/usr/bin/env bash` still resolve). Tests use this to cut off the
24
- // real rtk binary on the developer's machine.
25
- return { ...process.env, PATH: `${pathOverride}:/usr/bin:/bin` };
26
- }
27
-
28
- function detectRtk({ pathOverride = null, timeoutMs = 3000 } = {}) {
29
- try {
30
- const stdout = execFileSync('rtk', ['--version'], {
31
- env: envWithPath(pathOverride),
32
- stdio: ['ignore', 'pipe', 'pipe'],
33
- timeout: timeoutMs,
34
- });
35
- return { available: true, version: stdout.toString().trim() };
36
- } catch (err) {
37
- return {
38
- available: false,
39
- reason: err && err.code === 'ENOENT' ? 'binary not on PATH' : (err && err.message) || 'unknown',
40
- };
41
- }
42
- }
43
-
44
- function initRtk({ pathOverride = null, scope = 'global', timeoutMs = 10000 } = {}) {
45
- const args = ['init'];
46
- if (scope === 'global') args.push('-g');
47
- try {
48
- const stdout = execFileSync('rtk', args, {
49
- env: envWithPath(pathOverride),
50
- stdio: ['ignore', 'pipe', 'pipe'],
51
- timeout: timeoutMs,
52
- });
53
- return { ok: true, stdout: stdout.toString() };
54
- } catch (err) {
55
- return { ok: false, error: (err && err.message) || 'unknown' };
56
- }
57
- }
58
-
59
- // High-level entry: detect + optionally init, never throws.
60
- // Caller passes {skip:true} to honor --skip-rtk flag.
61
- function installHook({ skip = false, pathOverride = null, scope = 'global' } = {}) {
62
- if (skip) return { skipped: true };
63
- const detection = detectRtk({ pathOverride });
64
- if (!detection.available) {
65
- return {
66
- available: false,
67
- initialized: false,
68
- reason: detection.reason,
69
- hint: INSTALL_HINT,
70
- };
71
- }
72
- const initResult = initRtk({ pathOverride, scope });
73
- return {
74
- available: true,
75
- initialized: initResult.ok,
76
- version: detection.version,
77
- init_error: initResult.ok ? undefined : initResult.error,
78
- };
79
- }
80
-
81
- module.exports = {
82
- detectRtk,
83
- initRtk,
84
- installHook,
85
- INSTALL_HINT,
86
- };
@@ -1,116 +0,0 @@
1
- 'use strict';
2
-
3
- // Phase 6.1 — RTK soft dependency:
4
- // • detectRtk(opts): locates the `rtk` binary via PATH; returns false
5
- // (never throws) when missing or on any exec error.
6
- // • initRtk(opts): runs `rtk init -g`; only call after detectRtk() true.
7
-
8
- const { test } = require('node:test');
9
- const assert = require('node:assert/strict');
10
- const fs = require('node:fs');
11
- const os = require('node:os');
12
- const path = require('node:path');
13
-
14
- const rtk = require('../rtk-detect.cjs');
15
-
16
- function mkEmptyBinDir() {
17
- return fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-rtk-empty-bin-'));
18
- }
19
-
20
- function mkFakeBinDir({ succeed = true } = {}) {
21
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-rtk-fake-bin-'));
22
- const script = succeed
23
- ? '#!/usr/bin/env bash\nif [ "$1" = "--version" ]; then echo "rtk 1.2.3"; exit 0; fi\nif [ "$1" = "init" ]; then echo "init ok"; exit 0; fi\nexit 0\n'
24
- : '#!/usr/bin/env bash\nexit 42\n';
25
- const target = path.join(dir, 'rtk');
26
- fs.writeFileSync(target, script);
27
- fs.chmodSync(target, 0o755);
28
- return dir;
29
- }
30
-
31
- test('detectRtk returns true when rtk --version succeeds', () => {
32
- const binDir = mkFakeBinDir({ succeed: true });
33
- try {
34
- const result = rtk.detectRtk({ pathOverride: binDir });
35
- assert.equal(result.available, true);
36
- assert.match(result.version, /rtk/i);
37
- } finally {
38
- fs.rmSync(binDir, { recursive: true, force: true });
39
- }
40
- });
41
-
42
- test('detectRtk returns false when rtk missing from PATH (no throw)', () => {
43
- const binDir = mkEmptyBinDir();
44
- try {
45
- const result = rtk.detectRtk({ pathOverride: binDir });
46
- assert.equal(result.available, false);
47
- assert.ok(result.reason);
48
- } finally {
49
- fs.rmSync(binDir, { recursive: true, force: true });
50
- }
51
- });
52
-
53
- test('detectRtk returns false when rtk exits non-zero (no throw)', () => {
54
- const binDir = mkFakeBinDir({ succeed: false });
55
- try {
56
- const result = rtk.detectRtk({ pathOverride: binDir });
57
- assert.equal(result.available, false);
58
- } finally {
59
- fs.rmSync(binDir, { recursive: true, force: true });
60
- }
61
- });
62
-
63
- test('initRtk runs rtk init -g and returns success', () => {
64
- const binDir = mkFakeBinDir({ succeed: true });
65
- try {
66
- const result = rtk.initRtk({ pathOverride: binDir, scope: 'global' });
67
- assert.equal(result.ok, true);
68
- assert.match(result.stdout, /init ok/);
69
- } finally {
70
- fs.rmSync(binDir, { recursive: true, force: true });
71
- }
72
- });
73
-
74
- test('initRtk reports failure when binary is missing (does not throw)', () => {
75
- const binDir = mkEmptyBinDir();
76
- try {
77
- const result = rtk.initRtk({ pathOverride: binDir, scope: 'global' });
78
- assert.equal(result.ok, false);
79
- assert.ok(result.error);
80
- } finally {
81
- fs.rmSync(binDir, { recursive: true, force: true });
82
- }
83
- });
84
-
85
- test('installHook: skip=true short-circuits with a skipped=true record', () => {
86
- const binDir = mkEmptyBinDir();
87
- try {
88
- const result = rtk.installHook({ skip: true, pathOverride: binDir });
89
- assert.equal(result.skipped, true);
90
- assert.equal(result.available, undefined);
91
- } finally {
92
- fs.rmSync(binDir, { recursive: true, force: true });
93
- }
94
- });
95
-
96
- test('installHook: missing rtk → returns {available:false} with install hint', () => {
97
- const binDir = mkEmptyBinDir();
98
- try {
99
- const result = rtk.installHook({ skip: false, pathOverride: binDir });
100
- assert.equal(result.available, false);
101
- assert.ok(result.hint, 'should surface an install hint');
102
- } finally {
103
- fs.rmSync(binDir, { recursive: true, force: true });
104
- }
105
- });
106
-
107
- test('installHook: present rtk → runs init and returns {available:true, initialized:true}', () => {
108
- const binDir = mkFakeBinDir({ succeed: true });
109
- try {
110
- const result = rtk.installHook({ skip: false, pathOverride: binDir, scope: 'global' });
111
- assert.equal(result.available, true);
112
- assert.equal(result.initialized, true);
113
- } finally {
114
- fs.rmSync(binDir, { recursive: true, force: true });
115
- }
116
- });