ultra-builder-pro-cli 0.7.0 → 0.9.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 (47) hide show
  1. package/CHANGELOG.md +60 -1
  2. package/README.md +49 -23
  3. package/adapters/_shared/codex-assets.cjs +28 -0
  4. package/adapters/_shared/handbook.cjs +9 -1
  5. package/adapters/_shared/path-rewrite.cjs +3 -0
  6. package/adapters/_shared/provenance.cjs +286 -0
  7. package/adapters/_shared/runtime-assets.cjs +2 -1
  8. package/adapters/_shared/tests/handbook.test.cjs +6 -1
  9. package/adapters/_shared/tests/path-rewrite.test.cjs +3 -1
  10. package/adapters/_shared/tests/provenance.test.cjs +133 -0
  11. package/adapters/_shared/tests/runtime-assets.test.cjs +4 -1
  12. package/adapters/claude.js +33 -0
  13. package/adapters/codex.js +87 -0
  14. package/adapters/kimi.js +642 -0
  15. package/adapters/opencode.js +66 -0
  16. package/adapters/tests/codex.test.cjs +37 -0
  17. package/adapters/tests/kimi-hook.test.cjs +122 -0
  18. package/adapters/tests/kimi.test.cjs +255 -0
  19. package/agents/debugger.md +12 -0
  20. package/bin/handbook.js +2 -2
  21. package/bin/install.js +72 -12
  22. package/bin/orchestrator.js +1 -1
  23. package/commands/ultra-change.md +3 -2
  24. package/docs/AGENT-CONTEXT.md +5 -3
  25. package/docs/ARCHITECTURE.md +40 -6
  26. package/docs/PLAN.zh-CN.md +8 -6
  27. package/docs/ROADMAP.md +4 -4
  28. package/docs/RUNTIME-COMPAT-MATRIX.md +109 -58
  29. package/docs/USER-HANDBOOK-CONTRACT.md +4 -1
  30. package/hooks/README.md +6 -2
  31. package/hooks/adapters/kimi.py +162 -0
  32. package/hooks/workflow_checkpoint.py +15 -1
  33. package/hooks/workflow_resume.py +96 -7
  34. package/mcp-server/lib/change-workflow.cjs +69 -0
  35. package/mcp-server/lib/change-workflow.test.cjs +87 -0
  36. package/mcp-server/lib/state-db.cjs +85 -2
  37. package/mcp-server/lib/state-db.test.cjs +56 -0
  38. package/mcp-server/tests/init-project.test.cjs +2 -2
  39. package/mcp-server/tests/state-db.test.cjs +3 -3
  40. package/package.json +4 -2
  41. package/skills/ultra-change/SKILL.md +12 -3
  42. package/skills/ultra-deliver/SKILL.md +3 -1
  43. package/skills/ultra-doctor/SKILL.md +10 -3
  44. package/skills/ultra-review/SKILL.md +3 -3
  45. package/spec/mcp-tools.yaml +5 -5
  46. package/spec/schemas/skill-manifest.schema.json +2 -2
  47. package/spec/schemas/state-db.sql +4 -2
@@ -0,0 +1,133 @@
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
+ });
@@ -62,7 +62,10 @@ test('runtime asset manifest exposes only Ultra-owned core and internal skills',
62
62
  assert.deepEqual(COLLAB_SKILLS_BY_RUNTIME.opencode, [
63
63
  'cc-collab', 'codex-collab', 'ultra-verify',
64
64
  ]);
65
- assert.deepEqual(SUPPORTED_RUNTIMES, ['claude', 'opencode', 'codex']);
65
+ assert.deepEqual(COLLAB_SKILLS_BY_RUNTIME.kimi, [
66
+ 'cc-collab', 'codex-collab', 'ultra-verify',
67
+ ]);
68
+ assert.deepEqual(SUPPORTED_RUNTIMES, ['claude', 'opencode', 'codex', 'kimi']);
66
69
 
67
70
  for (const runtime of SUPPORTED_RUNTIMES) {
68
71
  const names = skillsForRuntime(runtime);
@@ -15,6 +15,7 @@ const {
15
15
  writeAtomic,
16
16
  } = require('./_shared/file-ops.cjs');
17
17
  const { buildMcpRuntime } = require('./_shared/codex-assets.cjs');
18
+ const provenance = require('./_shared/provenance.cjs');
18
19
  const {
19
20
  CORE_PUBLIC_SKILLS,
20
21
  WORKFLOW_HOOK_FILES,
@@ -26,6 +27,7 @@ const MCP_SERVER_NAME = PLUGIN_NAME;
26
27
  const SOURCE_TAG = 'ubp';
27
28
  const SENTINEL_KEY = '_ubp_manifest';
28
29
  const COMMAND_NAMES = CORE_PUBLIC_SKILLS.filter((name) => name !== 'ultra-review');
30
+ const PROVENANCE_FILE = 'provenance.json';
29
31
 
30
32
  function resolveTarget(ctx = {}) {
31
33
  if (ctx.configDir) return ctx.configDir;
@@ -155,9 +157,39 @@ function install(ctx = {}) {
155
157
  license: pkg.license || 'MIT',
156
158
  }, null, 2) + '\n');
157
159
  markManaged(target, { adapter: 'claude', plugin: PLUGIN_NAME });
160
+ const source = provenance.packageSource(repoRoot);
161
+ const provenanceFile = path.join(target, PROVENANCE_FILE);
162
+ report.provenance = provenance.writeProvenance({
163
+ file: provenanceFile,
164
+ adapter: 'claude',
165
+ ...source,
166
+ roots: { plugin: target },
167
+ assets: provenance.assetRefsForTree('plugin', target, {
168
+ exclude: ['.ubp-managed', PROVENANCE_FILE],
169
+ }),
170
+ contracts: {
171
+ plugin_manifest: { root: 'plugin', path: '.claude-plugin/plugin.json' },
172
+ mcp_registration: { root: 'plugin', path: '.mcp.json' },
173
+ mcp_launcher: { root: 'plugin', path: 'runtime/launch.cjs' },
174
+ hooks_manifest: { root: 'plugin', path: 'hooks/hooks.json' },
175
+ checkpoint_hook: { root: 'plugin', path: 'hooks/workflow_checkpoint.py' },
176
+ resume_hook: { root: 'plugin', path: 'hooks/workflow_resume.py' },
177
+ },
178
+ });
179
+ report.provenance.file = provenanceFile;
158
180
  return report;
159
181
  }
160
182
 
183
+ function doctor(ctx = {}) {
184
+ const repoRoot = resolveRepoRoot(ctx);
185
+ const source = provenance.packageSource(repoRoot);
186
+ return provenance.inspectProvenance({
187
+ file: path.join(resolvePluginRoot(ctx), PROVENANCE_FILE),
188
+ expectedAdapter: 'claude',
189
+ expectedPackageVersion: source.packageInfo.version,
190
+ });
191
+ }
192
+
161
193
  function uninstall(ctx = {}) {
162
194
  const target = resolvePluginRoot(ctx);
163
195
  const report = { target, removed: {}, config: { updated: false } };
@@ -179,4 +211,5 @@ module.exports = {
179
211
  resolvePluginRoot,
180
212
  install,
181
213
  uninstall,
214
+ doctor,
182
215
  };
package/adapters/codex.js CHANGED
@@ -33,6 +33,7 @@ const {
33
33
  buildPlugin,
34
34
  installAgents,
35
35
  } = require('./_shared/codex-assets.cjs');
36
+ const provenance = require('./_shared/provenance.cjs');
36
37
 
37
38
  const MCP_SERVER_NAME = PLUGIN_NAME;
38
39
  const SOURCE_TAG = 'ubp';
@@ -40,6 +41,7 @@ const MARKER_BEGIN = '# >>> ultra-builder-pro managed block — do not edit by h
40
41
  const MARKER_END = '# <<< ultra-builder-pro managed block';
41
42
  const RUNTIME_MANIFEST_DIR = 'ultra-builder-pro';
42
43
  const RUNTIME_MANIFEST_FILE = 'install-manifest.json';
44
+ const PROVENANCE_FILE = 'provenance.json';
43
45
  const HOOK_ADAPTER_RELATIVE = path.join('hooks', 'adapters', 'codex.py');
44
46
 
45
47
  function resolveTarget(ctx = {}) {
@@ -355,6 +357,30 @@ function install(ctx = {}) {
355
357
  agents: agents.installed,
356
358
  hookCacheVersions,
357
359
  });
360
+ const source = provenance.packageSource(repoRoot);
361
+ const provenanceFile = path.join(configDir, RUNTIME_MANIFEST_DIR, PROVENANCE_FILE);
362
+ const provenanceManifest = provenance.writeProvenance({
363
+ file: provenanceFile,
364
+ adapter: 'codex',
365
+ ...source,
366
+ roots: { plugin: pluginRoot, config: configDir },
367
+ assets: [
368
+ ...provenance.assetRefsForTree('plugin', pluginRoot, {
369
+ exclude: ['.ubp-managed'],
370
+ }),
371
+ ...agents.installed.map((file) => ({ root: 'config', path: path.join('agents', file) })),
372
+ { root: 'config', path: path.join(RUNTIME_MANIFEST_DIR, RUNTIME_MANIFEST_FILE) },
373
+ ],
374
+ contracts: {
375
+ plugin_manifest: { root: 'plugin', path: '.codex-plugin/plugin.json' },
376
+ mcp_registration: { root: 'plugin', path: '.mcp.json' },
377
+ mcp_launcher: { root: 'plugin', path: 'runtime/launch.cjs' },
378
+ hooks_manifest: { root: 'plugin', path: 'hooks/hooks.json' },
379
+ hook_adapter: { root: 'plugin', path: HOOK_ADAPTER_RELATIVE },
380
+ runtime_manifest: { root: 'config', path: path.join(RUNTIME_MANIFEST_DIR, RUNTIME_MANIFEST_FILE) },
381
+ },
382
+ });
383
+ provenanceManifest.file = provenanceFile;
358
384
 
359
385
  return {
360
386
  target: configDir,
@@ -365,10 +391,70 @@ function install(ctx = {}) {
365
391
  hookCompatibility,
366
392
  legacy,
367
393
  manifestFile,
394
+ provenance: provenanceManifest,
368
395
  config: { updated: legacy.configUpdated },
369
396
  };
370
397
  }
371
398
 
399
+ function doctor(ctx = {}) {
400
+ const configDir = resolveTarget(ctx);
401
+ const pluginRoot = resolvePluginRoot(ctx);
402
+ const repoRoot = resolveRepoRoot(ctx);
403
+ const marketplaceFile = resolveMarketplaceFile(ctx);
404
+ const source = provenance.packageSource(repoRoot);
405
+ const report = provenance.inspectProvenance({
406
+ file: path.join(configDir, RUNTIME_MANIFEST_DIR, PROVENANCE_FILE),
407
+ expectedAdapter: 'codex',
408
+ expectedPackageVersion: source.packageInfo.version,
409
+ });
410
+
411
+ let runtimeManifest = null;
412
+ try {
413
+ runtimeManifest = readRuntimeManifest(configDir);
414
+ } catch (error) {
415
+ report.issues.push({ code: 'RUNTIME_MANIFEST_INVALID', message: error.message });
416
+ }
417
+ const runtimeManifestOk = runtimeManifest?.source === SOURCE_TAG
418
+ && runtimeManifest.adapter === 'codex'
419
+ && runtimeManifest.plugin?.root === pluginRoot
420
+ && typeof runtimeManifest.plugin?.version === 'string';
421
+ if (!runtimeManifestOk && !report.issues.some((entry) => entry.code === 'RUNTIME_MANIFEST_INVALID')) {
422
+ report.issues.push({ code: 'RUNTIME_MANIFEST_INVALID', path: runtimeManifestFile(configDir) });
423
+ }
424
+ report.checks.runtime_manifest = { status: runtimeManifestOk ? 'pass' : 'fail' };
425
+
426
+ let marketplaceOk = false;
427
+ try {
428
+ const marketplace = loadMarketplace(marketplaceFile);
429
+ const entry = marketplace.plugins.find((plugin) => plugin?.name === PLUGIN_NAME);
430
+ marketplaceOk = entry?.source?.source === 'local'
431
+ && entry.source.path === `./plugins/${PLUGIN_NAME}`;
432
+ } catch (error) {
433
+ report.issues.push({ code: 'MARKETPLACE_REGISTRATION_INVALID', message: error.message });
434
+ }
435
+ if (!marketplaceOk && !report.issues.some((entry) => entry.code === 'MARKETPLACE_REGISTRATION_INVALID')) {
436
+ report.issues.push({ code: 'MARKETPLACE_REGISTRATION_INVALID', path: marketplaceFile });
437
+ }
438
+ report.checks.marketplace = { status: marketplaceOk ? 'pass' : 'fail' };
439
+
440
+ let hookTargetsOk = runtimeManifestOk;
441
+ if (runtimeManifestOk) {
442
+ const cacheRoot = pluginCacheRoot(configDir, runtimeManifest.marketplace?.name || 'personal');
443
+ const currentCacheRequired = shouldRunPluginCli(ctx);
444
+ for (const version of runtimeManifest.hook_cache_versions || []) {
445
+ if (version === runtimeManifest.plugin.version && !currentCacheRequired) continue;
446
+ const adapter = path.join(cacheRoot, version, HOOK_ADAPTER_RELATIVE);
447
+ if (!fs.existsSync(adapter)) {
448
+ hookTargetsOk = false;
449
+ report.issues.push({ code: 'HOOK_TARGET_MISSING', version, path: adapter });
450
+ }
451
+ }
452
+ }
453
+ report.checks.hook_targets = { status: hookTargetsOk ? 'pass' : 'fail' };
454
+ if (report.status !== 'missing') report.status = report.issues.length === 0 ? 'healthy' : 'degraded';
455
+ return report;
456
+ }
457
+
372
458
  function removeManagedAgents(configDir, manifest) {
373
459
  const agentRoot = path.join(configDir, 'agents');
374
460
  const requested = manifest && Array.isArray(manifest.agents)
@@ -440,6 +526,7 @@ module.exports = {
440
526
  resolveMarketplaceFile,
441
527
  install,
442
528
  uninstall,
529
+ doctor,
443
530
  _internal: {
444
531
  stripManagedBlock,
445
532
  hasManagedBlock,