ultra-builder-pro-cli 0.13.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.
Files changed (172) hide show
  1. package/.ultra-template/docs/research/README.md +11 -7
  2. package/.ultra-template/specs/architecture.md +36 -36
  3. package/.ultra-template/specs/discovery.md +46 -33
  4. package/.ultra-template/specs/product.md +38 -29
  5. package/.ultra-template/specs/research-distillate.md +48 -0
  6. package/.ultra-template/tasks/contexts/TEMPLATE.md +11 -28
  7. package/CHANGELOG.md +86 -1
  8. package/README.md +40 -26
  9. package/adapters/_shared/codex-assets.cjs +29 -165
  10. package/adapters/_shared/file-ops.cjs +6 -2
  11. package/adapters/_shared/runtime-assets.cjs +1 -0
  12. package/adapters/claude.js +3 -2
  13. package/adapters/codex.js +1 -0
  14. package/adapters/kimi.js +3 -2
  15. package/adapters/opencode.js +33 -122
  16. package/agents/review-coordinator.md +9 -6
  17. package/docs/AGENT-CONTEXT.md +21 -11
  18. package/docs/ARCHITECTURE.md +41 -13
  19. package/docs/PLAN.zh-CN.md +3 -2
  20. package/docs/ROADMAP.md +9 -5
  21. package/docs/RUNTIME-COMPAT-MATRIX.md +6 -6
  22. package/docs/STATE-DB-ACCESS-POLICY.md +27 -25
  23. package/docs/WORKFLOW-LIFECYCLE.md +266 -0
  24. package/hooks/README.md +6 -1
  25. package/hooks/active_task_context.py +22 -50
  26. package/hooks/context_spine.py +61 -332
  27. package/hooks/health_check.py +29 -18
  28. package/hooks/pre_stop_check.py +28 -33
  29. package/hooks/subagent_tracker.py +54 -85
  30. package/hooks/workflow_checkpoint.py +39 -47
  31. package/hooks/workflow_context.py +4 -9
  32. package/hooks/workflow_resume.py +48 -128
  33. package/mcp-server/breadcrumb.cjs +22 -0
  34. package/mcp-server/hook-event.cjs +34 -0
  35. package/mcp-server/lib/archive-journal.cjs +17 -5
  36. package/mcp-server/lib/baseline-workflow.cjs +158 -18
  37. package/mcp-server/lib/change-workflow.cjs +550 -85
  38. package/mcp-server/lib/context-spine.cjs +242 -24
  39. package/mcp-server/lib/doctor.cjs +33 -3
  40. package/mcp-server/lib/init-project.cjs +210 -16
  41. package/mcp-server/lib/plan-store.cjs +4 -1
  42. package/mcp-server/lib/prd-parser.cjs +14 -41
  43. package/mcp-server/lib/pricing.cjs +7 -10
  44. package/mcp-server/lib/project-breadcrumb.cjs +145 -0
  45. package/mcp-server/lib/projector.cjs +57 -3
  46. package/mcp-server/lib/spec-learning.cjs +79 -2
  47. package/mcp-server/lib/state-db.cjs +145 -8
  48. package/mcp-server/lib/state-ops.cjs +160 -9
  49. package/mcp-server/lib/task-expander.cjs +9 -38
  50. package/mcp-server/lib/workflow-state.cjs +2292 -0
  51. package/mcp-server/server.cjs +182 -50
  52. package/orchestrator/daemon.cjs +1 -2
  53. package/orchestrator/dispatch-rules.cjs +5 -31
  54. package/orchestrator/planner/plan-builder.cjs +21 -12
  55. package/package.json +14 -6
  56. package/skills/cc-collab/SKILL.md +2 -2
  57. package/skills/codex-collab/SKILL.md +2 -3
  58. package/skills/ultra-change/SKILL.md +53 -68
  59. package/skills/ultra-change/references/change-contract.md +36 -0
  60. package/skills/ultra-deliver/SKILL.md +69 -79
  61. package/skills/ultra-deliver/references/baseline-reconciliation.md +20 -0
  62. package/skills/ultra-dev/SKILL.md +54 -59
  63. package/skills/ultra-doctor/SKILL.md +40 -71
  64. package/skills/ultra-init/SKILL.md +96 -103
  65. package/skills/ultra-init/references/brownfield-adoption.md +2 -2
  66. package/skills/ultra-plan/SKILL.md +79 -75
  67. package/skills/ultra-plan/references/semantic-preflight.md +18 -0
  68. package/skills/ultra-research/SKILL.md +87 -60
  69. package/skills/ultra-research/references/00-problem-validation.md +36 -0
  70. package/skills/ultra-research/references/01-opportunity-discovery.md +33 -0
  71. package/skills/ultra-research/references/02-market-assessment.md +30 -0
  72. package/skills/ultra-research/references/03-competitive-landscape.md +31 -0
  73. package/skills/ultra-research/references/04-product-strategy.md +32 -0
  74. package/skills/ultra-research/references/05-assumptions-validation.md +31 -0
  75. package/skills/ultra-research/references/10-user-personas.md +29 -0
  76. package/skills/ultra-research/references/11-user-scenarios.md +28 -0
  77. package/skills/ultra-research/references/20-user-stories.md +30 -0
  78. package/skills/ultra-research/references/21-features-scope.md +29 -0
  79. package/skills/ultra-research/references/22-success-metrics.md +28 -0
  80. package/skills/ultra-research/references/30-architecture-context.md +30 -0
  81. package/skills/ultra-research/references/31-solution-strategy.md +28 -0
  82. package/skills/ultra-research/references/32-building-blocks.md +28 -0
  83. package/skills/ultra-research/references/40-deployment.md +29 -0
  84. package/skills/ultra-research/references/41-quality-risks.md +28 -0
  85. package/skills/ultra-research/references/99-synthesis.md +35 -0
  86. package/skills/ultra-review/SKILL.md +57 -73
  87. package/skills/ultra-review/references/review-modes.md +27 -0
  88. package/skills/ultra-review/references/unified-schema.md +25 -0
  89. package/skills/ultra-review/scripts/review_wait.py +40 -3
  90. package/skills/ultra-status/SKILL.md +41 -45
  91. package/skills/ultra-test/SKILL.md +48 -55
  92. package/skills/ultra-verify/SKILL.md +22 -31
  93. package/skills/ultra-verify/references/{confidence-system.md → evidence-status.md} +3 -3
  94. package/skills/ultra-verify/scripts/verify_wait.py +54 -54
  95. package/spec/cli-protocol.md +7 -2
  96. package/spec/mcp-tools.yaml +422 -73
  97. package/spec/schemas/baseline-reconciliation.v1.schema.json +73 -0
  98. package/spec/schemas/delivery-report.v1.schema.json +111 -0
  99. package/spec/schemas/mcp-tools.schema.json +1 -1
  100. package/spec/schemas/state-db.sql +82 -1
  101. package/spec/schemas/tasks.v4.5.schema.json +8 -1
  102. package/spec/schemas/test-report.v1.schema.json +194 -0
  103. package/ultra-tools/cli.cjs +1 -1
  104. package/ultra-tools/commands/migrate.cjs +3 -3
  105. package/ultra-tools/commands/status.cjs +213 -12
  106. package/.ultra-template/delivery-report.json +0 -9
  107. package/.ultra-template/test-report.json +0 -8
  108. package/adapters/_shared/tests/file-ops.test.cjs +0 -54
  109. package/adapters/_shared/tests/frontmatter.test.cjs +0 -47
  110. package/adapters/_shared/tests/handbook.test.cjs +0 -101
  111. package/adapters/_shared/tests/path-rewrite.test.cjs +0 -60
  112. package/adapters/_shared/tests/provenance.test.cjs +0 -133
  113. package/adapters/_shared/tests/resolve-target.test.cjs +0 -75
  114. package/adapters/_shared/tests/runtime-assets.test.cjs +0 -131
  115. package/adapters/_shared/tests/settings-merge.test.cjs +0 -85
  116. package/adapters/_shared/tests/validate.test.cjs +0 -30
  117. package/adapters/tests/claude.test.cjs +0 -137
  118. package/adapters/tests/codex-hook.test.cjs +0 -115
  119. package/adapters/tests/codex.test.cjs +0 -423
  120. package/adapters/tests/kimi-hook.test.cjs +0 -122
  121. package/adapters/tests/kimi.test.cjs +0 -259
  122. package/adapters/tests/opencode.test.cjs +0 -371
  123. package/mcp-server/lib/archive-journal.test.cjs +0 -35
  124. package/mcp-server/lib/baseline-workflow.test.cjs +0 -477
  125. package/mcp-server/lib/change-workflow.test.cjs +0 -971
  126. package/mcp-server/lib/context-spine.test.cjs +0 -93
  127. package/mcp-server/lib/doctor.test.cjs +0 -148
  128. package/mcp-server/lib/llm-client.cjs +0 -157
  129. package/mcp-server/lib/llm-client.test.cjs +0 -156
  130. package/mcp-server/lib/plan-store.test.cjs +0 -132
  131. package/mcp-server/lib/prd-parser.test.cjs +0 -171
  132. package/mcp-server/lib/runtime-state.test.cjs +0 -92
  133. package/mcp-server/lib/spec-learning.test.cjs +0 -67
  134. package/mcp-server/lib/state-db.test.cjs +0 -314
  135. package/mcp-server/lib/task-expander.test.cjs +0 -324
  136. package/mcp-server/lib/topo.test.cjs +0 -158
  137. package/mcp-server/tests/circuit-breaker.test.cjs +0 -125
  138. package/mcp-server/tests/commit-hash-flow.test.cjs +0 -117
  139. package/mcp-server/tests/concurrency-update.test.cjs +0 -96
  140. package/mcp-server/tests/concurrency.test.cjs +0 -112
  141. package/mcp-server/tests/db-ops.test.cjs +0 -121
  142. package/mcp-server/tests/init-project.test.cjs +0 -393
  143. package/mcp-server/tests/migrate.test.cjs +0 -232
  144. package/mcp-server/tests/projector.test.cjs +0 -162
  145. package/mcp-server/tests/server-contract.test.cjs +0 -1471
  146. package/mcp-server/tests/staleness.test.cjs +0 -190
  147. package/mcp-server/tests/state-db.test.cjs +0 -106
  148. package/mcp-server/tests/state-ops.test.cjs +0 -449
  149. package/mcp-server/tests/tagged-tasks.test.cjs +0 -163
  150. package/mcp-server/tests/telemetry.test.cjs +0 -219
  151. package/orchestrator/planner/expand-prompt.cjs +0 -62
  152. package/orchestrator/planner/expand-prompt.test.cjs +0 -52
  153. package/orchestrator/planner/plan-builder.test.cjs +0 -165
  154. package/orchestrator/planner/prd-prompt.cjs +0 -44
  155. package/orchestrator/planner/prd-prompt.test.cjs +0 -30
  156. package/orchestrator/tests/auto-merge.test.cjs +0 -216
  157. package/orchestrator/tests/daemon.test.cjs +0 -311
  158. package/orchestrator/tests/dispatch-rules.test.cjs +0 -124
  159. package/orchestrator/tests/parallel-orchestrator.test.cjs +0 -233
  160. package/orchestrator/tests/phase-8b-gate.test.cjs +0 -183
  161. package/orchestrator/tests/recovery.test.cjs +0 -190
  162. package/orchestrator/tests/session-runner.test.cjs +0 -314
  163. package/orchestrator/tests/skill-miner.test.cjs +0 -147
  164. package/orchestrator/tests/worktree-manager.test.cjs +0 -148
  165. package/skills/ultra-research/references/architecture.md +0 -38
  166. package/skills/ultra-research/references/problem-product.md +0 -39
  167. package/skills/ultra-research/references/quality-delivery.md +0 -34
  168. package/skills/ultra-research/references/synthesis.md +0 -38
  169. package/skills/ultra-research/references/users-scope.md +0 -40
  170. package/ultra-tools/legacy-memory.test.cjs +0 -80
  171. package/ultra-tools/status-command.test.cjs +0 -181
  172. package/ultra-tools/system-command.test.cjs +0 -309
@@ -1,133 +0,0 @@
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 { execFileSync } = require('node:child_process');
9
-
10
- const provenance = require('../provenance.cjs');
11
-
12
- function fixture() {
13
- const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-provenance-'));
14
- fs.mkdirSync(path.join(root, 'runtime'), { recursive: true });
15
- fs.mkdirSync(path.join(root, 'hooks'), { recursive: true });
16
- fs.writeFileSync(path.join(root, 'runtime', 'launch.cjs'), 'module.exports = true;\n');
17
- fs.writeFileSync(path.join(root, 'hooks', 'hooks.json'), '{}\n');
18
- const file = path.join(root, 'provenance.json');
19
- provenance.writeProvenance({
20
- file,
21
- adapter: 'claude',
22
- packageInfo: { name: 'ultra-builder-pro-cli', version: '1.2.3' },
23
- repository: 'https://example.test/ultra-builder-pro',
24
- sourceCommit: '0123456789abcdef0123456789abcdef01234567',
25
- roots: { plugin: root },
26
- assets: [
27
- { root: 'plugin', path: 'hooks/hooks.json' },
28
- { root: 'plugin', path: 'runtime/launch.cjs' },
29
- ],
30
- contracts: {
31
- hooks_manifest: { root: 'plugin', path: 'hooks/hooks.json' },
32
- mcp_launcher: { root: 'plugin', path: 'runtime/launch.cjs' },
33
- },
34
- });
35
- return { root, file };
36
- }
37
-
38
- test('writeProvenance + inspectProvenance produce a normalized healthy report', () => {
39
- const fx = fixture();
40
- try {
41
- const manifest = JSON.parse(fs.readFileSync(fx.file, 'utf8'));
42
- assert.equal(manifest.schema_version, 1);
43
- assert.equal(manifest.adapter, 'claude');
44
- assert.equal(manifest.package.version, '1.2.3');
45
- assert.equal(manifest.source.repository, 'https://example.test/ultra-builder-pro');
46
- assert.match(manifest.content.digest, /^[0-9a-f]{64}$/);
47
- assert.deepEqual(manifest.assets.map((asset) => asset.path), [
48
- 'hooks/hooks.json', 'runtime/launch.cjs',
49
- ]);
50
-
51
- const report = provenance.inspectProvenance({
52
- file: fx.file,
53
- expectedAdapter: 'claude',
54
- expectedPackageVersion: '1.2.3',
55
- });
56
- assert.equal(report.status, 'healthy');
57
- assert.deepEqual(report.issues, []);
58
- assert.equal(report.checks.assets.checked, 2);
59
- assert.equal(report.checks.contracts.checked, 2);
60
- } finally {
61
- fs.rmSync(fx.root, { recursive: true, force: true });
62
- }
63
- });
64
-
65
- test('inspectProvenance detects content drift and missing contract targets', () => {
66
- const fx = fixture();
67
- try {
68
- fs.writeFileSync(path.join(fx.root, 'runtime', 'launch.cjs'), 'tampered\n');
69
- fs.unlinkSync(path.join(fx.root, 'hooks', 'hooks.json'));
70
- const report = provenance.inspectProvenance({
71
- file: fx.file,
72
- expectedAdapter: 'claude',
73
- expectedPackageVersion: '1.2.3',
74
- });
75
- assert.equal(report.status, 'degraded');
76
- assert.ok(report.issues.some((issue) => issue.code === 'ASSET_HASH_MISMATCH'));
77
- assert.ok(report.issues.some(
78
- (issue) => issue.code === 'CONTRACT_TARGET_MISSING' && issue.contract === 'hooks_manifest',
79
- ));
80
- } finally {
81
- fs.rmSync(fx.root, { recursive: true, force: true });
82
- }
83
- });
84
-
85
- test('inspectProvenance fails closed for missing, corrupt, or mismatched manifests', () => {
86
- const fx = fixture();
87
- try {
88
- const mismatch = provenance.inspectProvenance({
89
- file: fx.file,
90
- expectedAdapter: 'codex',
91
- expectedPackageVersion: '9.9.9',
92
- });
93
- assert.equal(mismatch.status, 'degraded');
94
- assert.ok(mismatch.issues.some((issue) => issue.code === 'ADAPTER_MISMATCH'));
95
- assert.ok(mismatch.issues.some((issue) => issue.code === 'PACKAGE_VERSION_MISMATCH'));
96
-
97
- fs.writeFileSync(fx.file, 'not-json');
98
- const corrupt = provenance.inspectProvenance({ file: fx.file });
99
- assert.equal(corrupt.status, 'degraded');
100
- assert.equal(corrupt.issues[0].code, 'PROVENANCE_INVALID');
101
-
102
- fs.unlinkSync(fx.file);
103
- const missing = provenance.inspectProvenance({ file: fx.file });
104
- assert.equal(missing.status, 'missing');
105
- assert.equal(missing.issues[0].code, 'PROVENANCE_MISSING');
106
- } finally {
107
- fs.rmSync(fx.root, { recursive: true, force: true });
108
- }
109
- });
110
-
111
- test('packageSource never attributes an enclosing consumer repository commit to the package', () => {
112
- const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-provenance-source-'));
113
- try {
114
- execFileSync('git', ['init', '-q'], { cwd: root });
115
- execFileSync('git', ['config', 'user.email', 'test@ubp.dev'], { cwd: root });
116
- execFileSync('git', ['config', 'user.name', 'ubp-test'], { cwd: root });
117
- fs.writeFileSync(path.join(root, 'README.md'), '# consumer\n');
118
- execFileSync('git', ['add', 'README.md'], { cwd: root });
119
- execFileSync('git', ['commit', '-q', '-m', 'seed'], { cwd: root });
120
- const packageRoot = path.join(root, 'node_modules', 'ultra-builder-pro-cli');
121
- fs.mkdirSync(packageRoot, { recursive: true });
122
- fs.writeFileSync(path.join(packageRoot, 'package.json'), JSON.stringify({
123
- name: 'ultra-builder-pro-cli',
124
- version: '1.2.3',
125
- repository: { url: 'https://example.test/ultra-builder-pro' },
126
- }));
127
-
128
- const source = provenance.packageSource(packageRoot);
129
- assert.equal(source.sourceCommit, null);
130
- } finally {
131
- fs.rmSync(root, { recursive: true, force: true });
132
- }
133
- });
@@ -1,75 +0,0 @@
1
- 'use strict';
2
-
3
- // Scope-precedence contract across every supported adapter.resolveTarget.
4
- //
5
- // Each adapter honors the same ordering:
6
- // ctx.configDir > ctx.scope='global' → env var or homeDir/.<runtime>
7
- // > ctx.scope='local' → cwd/.<runtime>
8
-
9
- const { test } = require('node:test');
10
- const assert = require('node:assert/strict');
11
- const path = require('node:path');
12
-
13
- const claude = require('../../../adapters/claude.js');
14
- const opencode = require('../../../adapters/opencode.js');
15
- const codex = require('../../../adapters/codex.js');
16
-
17
- // Stable fake values so assertions don't depend on real $HOME / $CWD.
18
- const FAKE_HOME = '/fake-home';
19
- const FAKE_CWD = '/fake-cwd';
20
-
21
- function withEnv(overrides, fn) {
22
- const keys = Object.keys(overrides);
23
- const backup = {};
24
- for (const k of keys) { backup[k] = process.env[k]; }
25
- for (const k of keys) {
26
- if (overrides[k] === undefined) delete process.env[k];
27
- else process.env[k] = overrides[k];
28
- }
29
- try { return fn(); }
30
- finally {
31
- for (const k of keys) {
32
- if (backup[k] === undefined) delete process.env[k];
33
- else process.env[k] = backup[k];
34
- }
35
- }
36
- }
37
-
38
- const CASES = [
39
- { name: 'claude', resolve: claude.resolveTarget, localLeaf: '.claude', globalHome: '.claude', envKey: 'CLAUDE_CONFIG_DIR' },
40
- { name: 'opencode', resolve: opencode.resolveTarget, localLeaf: '.opencode', globalHome: path.join('.config', 'opencode'), envKey: 'OPENCODE_CONFIG_DIR' },
41
- { name: 'codex', resolve: codex.resolveTarget, localLeaf: '.codex', globalHome: '.codex', envKey: 'CODEX_HOME' },
42
- ];
43
-
44
- for (const tc of CASES) {
45
- test(`${tc.name}.resolveTarget — configDir wins over scope/env/home`, () => {
46
- withEnv({ [tc.envKey]: '/env-path', XDG_CONFIG_HOME: undefined, OPENCODE_CONFIG: undefined }, () => {
47
- const out = tc.resolve({ configDir: '/explicit/path', scope: 'global', homeDir: FAKE_HOME, cwd: FAKE_CWD });
48
- assert.equal(out, '/explicit/path');
49
- });
50
- });
51
-
52
- test(`${tc.name}.resolveTarget — global scope picks env var`, () => {
53
- withEnv({ [tc.envKey]: '/env-global', XDG_CONFIG_HOME: undefined, OPENCODE_CONFIG: undefined }, () => {
54
- const out = tc.resolve({ scope: 'global', homeDir: FAKE_HOME, cwd: FAKE_CWD });
55
- assert.equal(out, '/env-global');
56
- });
57
- });
58
-
59
- test(`${tc.name}.resolveTarget — global scope falls back to home dir when env absent`, () => {
60
- withEnv({ [tc.envKey]: undefined, XDG_CONFIG_HOME: undefined, OPENCODE_CONFIG: undefined }, () => {
61
- const out = tc.resolve({ scope: 'global', homeDir: FAKE_HOME, cwd: FAKE_CWD });
62
- assert.equal(out, path.join(FAKE_HOME, tc.globalHome));
63
- });
64
- });
65
-
66
- test(`${tc.name}.resolveTarget — local scope uses cwd/<leaf>`, () => {
67
- const out = tc.resolve({ scope: 'local', homeDir: FAKE_HOME, cwd: FAKE_CWD });
68
- assert.equal(out, path.join(FAKE_CWD, tc.localLeaf));
69
- });
70
-
71
- test(`${tc.name}.resolveTarget — missing scope defaults to local (cwd)`, () => {
72
- const out = tc.resolve({ homeDir: FAKE_HOME, cwd: FAKE_CWD });
73
- assert.equal(out, path.join(FAKE_CWD, tc.localLeaf));
74
- });
75
- }
@@ -1,131 +0,0 @@
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 path = require('node:path');
7
-
8
- const REPO_ROOT = path.resolve(__dirname, '..', '..', '..');
9
- const {
10
- CORE_PUBLIC_SKILLS,
11
- INTERNAL_AGENT_SKILLS,
12
- SUPPORTED_RUNTIMES,
13
- COLLAB_SKILLS_BY_RUNTIME,
14
- MCP_DEPENDENT_SKILLS,
15
- RETIRED_SKILLS,
16
- WORKFLOW_HOOK_FILES,
17
- skillPolicy,
18
- skillsForRuntime,
19
- } = require('../runtime-assets.cjs');
20
-
21
- const CORE = [
22
- 'learn',
23
- 'ultra-init',
24
- 'ultra-research',
25
- 'ultra-plan',
26
- 'ultra-dev',
27
- 'ultra-test',
28
- 'ultra-review',
29
- 'ultra-deliver',
30
- 'ultra-status',
31
- 'ultra-think',
32
- 'ultra-change',
33
- 'ultra-doctor',
34
- ];
35
-
36
- const INTERNAL = [
37
- 'code-review-expert',
38
- 'security-rules',
39
- 'integration-rules',
40
- 'testing-rules',
41
- ];
42
-
43
- const RETIRED = [
44
- 'agent-browser',
45
- 'find-skills',
46
- 'recall',
47
- 'use-railway',
48
- 'vercel-composition-patterns',
49
- 'vercel-react-best-practices',
50
- 'vercel-react-native-skills',
51
- ];
52
-
53
- test('runtime asset manifest exposes only Ultra-owned core and internal skills', () => {
54
- assert.deepEqual(CORE_PUBLIC_SKILLS, CORE);
55
- assert.deepEqual(INTERNAL_AGENT_SKILLS, INTERNAL);
56
- assert.deepEqual(RETIRED_SKILLS, RETIRED);
57
-
58
- assert.deepEqual(COLLAB_SKILLS_BY_RUNTIME.claude, [
59
- 'codex-collab', 'ultra-verify',
60
- ]);
61
- assert.deepEqual(COLLAB_SKILLS_BY_RUNTIME.codex, [
62
- 'cc-collab', 'ultra-verify',
63
- ]);
64
- assert.deepEqual(COLLAB_SKILLS_BY_RUNTIME.opencode, [
65
- 'cc-collab', 'codex-collab', 'ultra-verify',
66
- ]);
67
- assert.deepEqual(COLLAB_SKILLS_BY_RUNTIME.kimi, [
68
- 'cc-collab', 'codex-collab', 'ultra-verify',
69
- ]);
70
- assert.deepEqual(SUPPORTED_RUNTIMES, ['claude', 'opencode', 'codex', 'kimi']);
71
-
72
- for (const runtime of SUPPORTED_RUNTIMES) {
73
- const names = skillsForRuntime(runtime);
74
- for (const retired of RETIRED) assert.ok(!names.includes(retired));
75
- assert.ok(!names.some((name) => /impeccable/i.test(name)));
76
- }
77
- });
78
-
79
- test('host invocation and MCP metadata live outside source SKILL frontmatter', () => {
80
- const all = new Set(SUPPORTED_RUNTIMES.flatMap((runtime) => skillsForRuntime(runtime)));
81
- for (const name of all) {
82
- const policy = skillPolicy(name);
83
- assert.equal(policy.allowImplicitInvocation, false, name);
84
- assert.equal(policy.userInvocable, !INTERNAL_AGENT_SKILLS.includes(name), name);
85
- assert.equal(policy.requiresUltraMcp, MCP_DEPENDENT_SKILLS.includes(name), name);
86
- }
87
- assert.throws(() => skillPolicy('not-packaged'), /unknown packaged Ultra skill/);
88
- });
89
-
90
- test('workflow hook allowlist contains no memory, prompt capture, or generic policy hook', () => {
91
- assert.deepEqual(WORKFLOW_HOOK_FILES, [
92
- 'active_task_context.py',
93
- 'context_spine.py',
94
- 'health_check.py',
95
- 'pre_stop_check.py',
96
- 'subagent_tracker.py',
97
- 'workflow_checkpoint.py',
98
- 'workflow_context.py',
99
- 'workflow_resume.py',
100
- ]);
101
- assert.doesNotMatch(WORKFLOW_HOOK_FILES.join('\n'), /memory|recall|journal|observation|prompt|dangerous|post_edit/);
102
- });
103
-
104
- test('bundled agents never instruct workers to own persistent memory', () => {
105
- const agentsDir = path.join(REPO_ROOT, 'agents');
106
- const agentText = fs.readdirSync(agentsDir)
107
- .filter((name) => name.endsWith('.md'))
108
- .map((name) => fs.readFileSync(path.join(agentsDir, name), 'utf8'))
109
- .join('\n');
110
-
111
- assert.doesNotMatch(agentText, /^## Memory$/m);
112
- assert.doesNotMatch(agentText, /Update your project memory|Consult memory before starting work/);
113
- assert.doesNotMatch(agentText, /\.ultra\/memory|\/recall(?:\s|`|$)/m);
114
- });
115
-
116
- test('npm publish list uses the same explicit skill boundary', () => {
117
- const pkg = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, 'package.json'), 'utf8'));
118
- const publishedSkills = pkg.files
119
- .filter((entry) => entry.startsWith('skills/'))
120
- .map((entry) => entry.slice('skills/'.length))
121
- .sort();
122
- const expected = [...new Set([
123
- ...CORE_PUBLIC_SKILLS,
124
- ...INTERNAL_AGENT_SKILLS,
125
- ...Object.values(COLLAB_SKILLS_BY_RUNTIME).flat(),
126
- ])].sort();
127
-
128
- assert.ok(!pkg.files.includes('skills'));
129
- assert.ok(!pkg.files.includes('CLAUDE.md'));
130
- assert.deepEqual(publishedSkills, expected);
131
- });
@@ -1,85 +0,0 @@
1
- 'use strict';
2
-
3
- const { test } = require('node:test');
4
- const assert = require('node:assert/strict');
5
-
6
- const fs = require('node:fs');
7
- const os = require('node:os');
8
- const path = require('node:path');
9
-
10
- const {
11
- SENTINEL_VERSION,
12
- readJsonSafe,
13
- withSentinelBlock,
14
- removeSentinelBlock,
15
- hasSentinelBlock,
16
- mergeArrayField,
17
- } = require('../settings-merge.cjs');
18
-
19
- test('withSentinelBlock writes under the sentinel key only; user config untouched', () => {
20
- const existing = { user_prop: 'keep me', hooks: [{ name: 'user-hook' }] };
21
- const out = withSentinelBlock(existing, 'ultra_builder_pro', {
22
- mcp: { ubp: { command: 'node' } },
23
- });
24
-
25
- assert.equal(out.user_prop, 'keep me');
26
- assert.deepEqual(out.hooks, [{ name: 'user-hook' }]);
27
- assert.equal(out.ultra_builder_pro.__sentinel, SENTINEL_VERSION);
28
- assert.equal(out.ultra_builder_pro.__generated_by, 'ultra-builder-pro-cli');
29
- assert.deepEqual(out.ultra_builder_pro.mcp.ubp, { command: 'node' });
30
- });
31
-
32
- test('removeSentinelBlock leaves unrelated keys intact; noop if absent', () => {
33
- const with_ = { a: 1, mine: { __sentinel: 1, x: 2 } };
34
- const stripped = removeSentinelBlock(with_, 'mine');
35
- assert.deepEqual(stripped, { a: 1 });
36
- assert.ok(!hasSentinelBlock(stripped, 'mine'));
37
-
38
- const noop = removeSentinelBlock({ a: 1 }, 'mine');
39
- assert.deepEqual(noop, { a: 1 });
40
- });
41
-
42
- test('mergeArrayField dedupes; readJsonSafe handles missing/empty/malformed', () => {
43
- const existing = { hooks: [{ name: 'user' }, { name: 'ubp-one' }] };
44
- const out = mergeArrayField(existing, 'hooks', [
45
- { name: 'ubp-one' }, // dup — skipped
46
- { name: 'ubp-two' },
47
- ]);
48
- assert.deepEqual(out.hooks, [
49
- { name: 'user' },
50
- { name: 'ubp-one' },
51
- { name: 'ubp-two' },
52
- ]);
53
-
54
- // readJsonSafe: missing file → empty object
55
- const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-merge-'));
56
- try {
57
- assert.deepEqual(readJsonSafe(path.join(tmp, 'none.json')), {});
58
-
59
- const p = path.join(tmp, 'existing.json');
60
- fs.writeFileSync(p, '{"a":1}');
61
- assert.deepEqual(readJsonSafe(p), { a: 1 });
62
-
63
- // empty file treated as empty object
64
- fs.writeFileSync(p, ' \n ');
65
- assert.deepEqual(readJsonSafe(p), {});
66
-
67
- // malformed → throws
68
- fs.writeFileSync(p, '{not json');
69
- assert.throws(() => readJsonSafe(p), /cannot parse/);
70
-
71
- // P2 #7 / D45: rescue mode backs up corrupt file and returns {} instead
72
- // of throwing — lets `install` finish even if the user's settings.json
73
- // is syntactically broken.
74
- fs.writeFileSync(p, '{still not json');
75
- const rescued = readJsonSafe(p, { rescue: true });
76
- assert.deepEqual(rescued, {});
77
- const backups = fs.readdirSync(tmp).filter((f) => f.startsWith('existing.json.bak-'));
78
- assert.ok(backups.length > 0, `expected a .bak-* file in ${tmp}, saw ${fs.readdirSync(tmp).join(',')}`);
79
- } finally {
80
- fs.rmSync(tmp, { recursive: true, force: true });
81
- }
82
-
83
- // withSentinelBlock rejects bad key
84
- assert.throws(() => withSentinelBlock({}, '', {}), /non-empty string/);
85
- });
@@ -1,30 +0,0 @@
1
- 'use strict';
2
-
3
- // Phase 4 backlog P3 #13 / D45 — configDir validator.
4
-
5
- const { test } = require('node:test');
6
- const assert = require('node:assert/strict');
7
-
8
- const { validateConfigDir } = require('../validate.cjs');
9
-
10
- test('validateConfigDir: null/undefined → ok with configDir=null', () => {
11
- assert.deepEqual(validateConfigDir(null), { ok: true, configDir: null });
12
- assert.deepEqual(validateConfigDir(undefined), { ok: true, configDir: null });
13
- });
14
-
15
- test('validateConfigDir: normal path → ok', () => {
16
- const out = validateConfigDir('/tmp/work');
17
- assert.equal(out.ok, true);
18
- assert.equal(out.configDir, '/tmp/work');
19
- });
20
-
21
- test('validateConfigDir: rejects NUL byte', () => {
22
- const out = validateConfigDir('/tmp/foo\u0000bar');
23
- assert.equal(out.ok, false);
24
- assert.match(out.error, /NUL bytes/);
25
- });
26
-
27
- test('validateConfigDir: rejects non-string', () => {
28
- assert.equal(validateConfigDir(42).ok, false);
29
- assert.equal(validateConfigDir({}).ok, false);
30
- });
@@ -1,137 +0,0 @@
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
-
9
- const claude = require('../claude.js');
10
- const { parse: parseFrontmatter } = require('../_shared/frontmatter.cjs');
11
- const {
12
- INTERNAL_AGENT_SKILLS,
13
- skillsForRuntime,
14
- WORKFLOW_HOOK_FILES,
15
- } = require('../_shared/runtime-assets.cjs');
16
-
17
- const REPO_ROOT = path.resolve(__dirname, '..', '..');
18
-
19
- function mkTarget() {
20
- return fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-claude-'));
21
- }
22
-
23
- function skillNames(root) {
24
- return fs.readdirSync(path.join(root, 'skills'), { withFileTypes: true })
25
- .filter((entry) => entry.isDirectory() && fs.existsSync(path.join(root, 'skills', entry.name, 'SKILL.md')))
26
- .map((entry) => entry.name)
27
- .sort();
28
- }
29
-
30
- test('install builds the Claude-native plugin from the explicit Ultra allowlist', () => {
31
- const parent = mkTarget();
32
- const target = path.join(parent, 'skills', 'ultra-builder-pro');
33
- try {
34
- const report = claude.install({ configDir: parent, repoRoot: REPO_ROOT });
35
- assert.deepEqual(skillNames(target), skillsForRuntime('claude').sort());
36
- assert.deepEqual(report.copied.hooks.sort(), WORKFLOW_HOOK_FILES.slice().sort());
37
- assert.ok(fs.existsSync(path.join(target, '.claude-plugin', 'plugin.json')));
38
- assert.ok(fs.existsSync(path.join(target, 'hooks', 'hooks.json')));
39
- assert.ok(fs.existsSync(path.join(target, '.mcp.json')));
40
- assert.ok(fs.existsSync(path.join(target, 'agents', 'code-reviewer.md')));
41
-
42
- const hooks = JSON.parse(fs.readFileSync(path.join(target, 'hooks', 'hooks.json'), 'utf8'));
43
- const serialized = JSON.stringify(hooks);
44
- for (const name of WORKFLOW_HOOK_FILES.filter((value) => value !== 'context_spine.py')) {
45
- assert.match(serialized, new RegExp(name.replace('.', '\\.')));
46
- }
47
- assert.ok(fs.existsSync(path.join(target, 'hooks', 'context_spine.py')));
48
- assert.doesNotMatch(serialized, /memory|recall|journal|observation_capture|user_prompt_capture|block_dangerous|post_edit_guard/);
49
- assert.match(serialized, /\$\{CLAUDE_PLUGIN_ROOT\}/);
50
- } finally {
51
- fs.rmSync(parent, { recursive: true, force: true });
52
- }
53
- });
54
-
55
- test('Claude plugin collaboration and learn workflows are safe native plugin assets', () => {
56
- const parent = mkTarget();
57
- const target = path.join(parent, 'skills', 'ultra-builder-pro');
58
- try {
59
- claude.install({ configDir: parent, repoRoot: REPO_ROOT });
60
- const read = (name) => fs.readFileSync(path.join(target, 'skills', name, 'SKILL.md'), 'utf8');
61
- const learn = read('learn');
62
- const codexCollab = read('codex-collab');
63
- const verify = read('ultra-verify');
64
- const review = read('ultra-review');
65
- const plan = read('ultra-plan');
66
- const status = read('ultra-status');
67
-
68
- assert.match(learn, /`~\/.claude\/skills`/);
69
- assert.doesNotMatch(learn, /_unverified|learned-[^\s/]*-unverified/i);
70
- assert.match(codexCollab, /-s read-only/);
71
- assert.match(codexCollab, /--ephemeral/);
72
- assert.match(codexCollab, /--ignore-user-config/);
73
- assert.match(verify, /-s read-only/);
74
- assert.match(verify, /--ephemeral/);
75
- assert.match(verify, /scripts\/verify_wait\.py/);
76
- assert.match(review, /Claude Code Task workers/);
77
- assert.match(review, /scripts\/review_wait\.py/);
78
- assert.doesNotMatch(plan, /LEGACY_STATE_MIGRATION_REQUIRED|v4\.4|v4\.5/);
79
- assert.match(plan, /Never read or\s+write .*tasks\.json/i);
80
- assert.match(status, /Never fall\s+back to generated task JSON/i);
81
- assert.doesNotMatch(plan, /ultra-tools task create/);
82
-
83
- for (const name of skillsForRuntime('claude')) {
84
- const { fm } = parseFrontmatter(read(name));
85
- assert.equal(fm['user-invocable'], !INTERNAL_AGENT_SKILLS.includes(name), name);
86
- }
87
-
88
- for (const [name, contents] of Object.entries({ codexCollab, verify })) {
89
- assert.doesNotMatch(contents, /--yolo|--full-auto|\/codex:/, name);
90
- }
91
- } finally {
92
- fs.rmSync(parent, { recursive: true, force: true });
93
- }
94
- });
95
-
96
- test('global target is a Claude skills-directory plugin', () => {
97
- const homeDir = mkTarget();
98
- try {
99
- const ctx = { scope: 'global', homeDir };
100
- assert.equal(claude.resolveTarget(ctx), path.join(homeDir, '.claude'));
101
- assert.equal(claude.resolvePluginRoot(ctx), path.join(homeDir, '.claude', 'skills', 'ultra-builder-pro'));
102
- } finally {
103
- fs.rmSync(homeDir, { recursive: true, force: true });
104
- }
105
- });
106
-
107
- test('reinstall is deterministic and uninstall removes only the managed plugin root', () => {
108
- const parent = mkTarget();
109
- const target = path.join(parent, 'skills', 'ultra-builder-pro');
110
- const sibling = path.join(parent, 'user-plugin');
111
- try {
112
- fs.mkdirSync(sibling);
113
- fs.writeFileSync(path.join(sibling, 'keep.txt'), 'keep');
114
- claude.install({ configDir: parent, repoRoot: REPO_ROOT });
115
- claude.install({ configDir: parent, repoRoot: REPO_ROOT });
116
- claude.uninstall({ configDir: parent });
117
- assert.ok(!fs.existsSync(target));
118
- assert.equal(fs.readFileSync(path.join(sibling, 'keep.txt'), 'utf8'), 'keep');
119
- } finally {
120
- fs.rmSync(parent, { recursive: true, force: true });
121
- }
122
- });
123
-
124
- test('install refuses to replace an unmanaged directory', () => {
125
- const parent = mkTarget();
126
- const target = path.join(parent, 'skills', 'ultra-builder-pro');
127
- try {
128
- fs.mkdirSync(target, { recursive: true });
129
- fs.writeFileSync(path.join(target, 'user.txt'), 'keep');
130
- assert.throws(
131
- () => claude.install({ configDir: parent, repoRoot: REPO_ROOT }),
132
- /unmanaged Claude plugin/,
133
- );
134
- } finally {
135
- fs.rmSync(parent, { recursive: true, force: true });
136
- }
137
- });