@liangjie559567/ultrapower 5.2.12 → 5.3.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ultrapower",
3
3
  "description": "Disciplined multi-agent orchestration: workflow enforcement + parallel execution. Combines superpowers' TDD/debugging discipline with OMC's multi-agent execution capabilities.",
4
- "version": "5.2.12",
4
+ "version": "5.3.0",
5
5
  "author": {
6
6
  "name": "Jesse Vincent & oh-my-claudecode contributors"
7
7
  },
package/.mcp.json CHANGED
@@ -2,15 +2,15 @@
2
2
  "mcpServers": {
3
3
  "t": {
4
4
  "command": "node",
5
- "args": ["${CLAUDE_PLUGIN_ROOT}/bridge/mcp-server.cjs"]
5
+ "args": ["./bridge/mcp-server.cjs"]
6
6
  },
7
7
  "x": {
8
8
  "command": "node",
9
- "args": ["${CLAUDE_PLUGIN_ROOT}/bridge/codex-server.cjs"]
9
+ "args": ["./bridge/codex-server.cjs"]
10
10
  },
11
11
  "g": {
12
12
  "command": "node",
13
- "args": ["${CLAUDE_PLUGIN_ROOT}/bridge/gemini-server.cjs"]
13
+ "args": ["./bridge/gemini-server.cjs"]
14
14
  }
15
15
  }
16
16
  }
@@ -191,12 +191,19 @@ notepad_write_working("full_context", {
191
191
 
192
192
  ## Hooks
193
193
 
194
- ultrapower 在 `src/hooks/` 中包含 35 个 hook,用于生命周期事件:
194
+ ultrapower 在 `src/hooks/` 中包含 47 个 hook,用于生命周期事件:
195
195
 
196
196
  | 事件 | 用途 |
197
197
  |-------|---------|
198
198
  | `UserPromptSubmit` | 关键词检测、模式激活 |
199
- | `Stop` | 续行强制、会话结束 |
199
+ | `SubagentStop` | agent 完成时触发 |
200
+ | `PreCompact` | 上下文压缩前触发 |
201
+ | `TeammateIdle` | 团队成员空闲时触发 |
202
+ | `SessionEnd` | 会话结束时触发 |
203
+ | `UserPromptSubmit` | 用户提交提示词前触发 |
204
+ | `PermissionRequest` | 工具权限请求时触发 |
205
+ | `TaskCompleted` | 任务完成时触发 |
206
+ | `ConfigChange` | 配置变更时触发 |
200
207
  | `PreToolUse` | 权限验证 |
201
208
  | `PostToolUse` | 错误恢复、规则注入 |
202
209
 
package/docs/REFERENCE.md CHANGED
@@ -434,7 +434,7 @@ omc config-stop-callback discord --show
434
434
 
435
435
  ## Hooks System
436
436
 
437
- ultrapower 包含 35 个生命周期 hooks,用于增强 Claude Code 的行为。
437
+ ultrapower 包含 47 个生命周期 hooks,用于增强 Claude Code 的行为。
438
438
 
439
439
  ### 执行模式 Hooks
440
440
 
@@ -467,6 +467,13 @@ ultrapower 包含 35 个生命周期 hooks,用于增强 Claude Code 的行为
467
467
  | `recovery` | 编辑错误、会话和上下文窗口恢复 |
468
468
  | `preemptive-compaction` | 监控上下文使用量以防止超限 |
469
469
  | `pre-compact` | 压缩前处理 |
470
+ | `subagent-stop` | 子 agent 完成时触发,防止无限循环 |
471
+ | `teammate-idle` | 团队成员空闲时触发,默认允许 |
472
+ | `session-end` | 会话结束时触发,清理临时状态 |
473
+ | `user-prompt-submit` | 用户提交提示词前触发,用于关键词检测 |
474
+ | `permission-request` | 工具权限请求时触发 |
475
+ | `task-completed` | 任务完成时触发 |
476
+ | `config-change` | 配置变更时触发 |
470
477
  | `directory-readme-injector` | README 上下文注入 |
471
478
 
472
479
  ### 质量与验证 Hooks
package/hooks/hooks.json CHANGED
@@ -59,6 +59,101 @@
59
59
  }
60
60
  ]
61
61
  }
62
+ ],
63
+ "SubagentStop": [
64
+ {
65
+ "matcher": "",
66
+ "hooks": [
67
+ {
68
+ "type": "command",
69
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/templates/hooks/subagent-stop.mjs",
70
+ "async": false
71
+ }
72
+ ]
73
+ }
74
+ ],
75
+ "PreCompact": [
76
+ {
77
+ "matcher": "",
78
+ "hooks": [
79
+ {
80
+ "type": "command",
81
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/templates/hooks/pre-compact.mjs",
82
+ "async": false
83
+ }
84
+ ]
85
+ }
86
+ ],
87
+ "TeammateIdle": [
88
+ {
89
+ "hooks": [
90
+ {
91
+ "type": "command",
92
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/templates/hooks/teammate-idle.mjs",
93
+ "async": false
94
+ }
95
+ ]
96
+ }
97
+ ],
98
+ "SessionEnd": [
99
+ {
100
+ "matcher": "",
101
+ "hooks": [
102
+ {
103
+ "type": "command",
104
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/templates/hooks/session-end.mjs",
105
+ "async": false
106
+ }
107
+ ]
108
+ }
109
+ ],
110
+ "UserPromptSubmit": [
111
+ {
112
+ "matcher": "",
113
+ "hooks": [
114
+ {
115
+ "type": "command",
116
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/templates/hooks/user-prompt-submit.mjs",
117
+ "async": false
118
+ }
119
+ ]
120
+ }
121
+ ],
122
+ "PermissionRequest": [
123
+ {
124
+ "matcher": "",
125
+ "hooks": [
126
+ {
127
+ "type": "command",
128
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/templates/hooks/permission-request.mjs",
129
+ "async": false
130
+ }
131
+ ]
132
+ }
133
+ ],
134
+ "TaskCompleted": [
135
+ {
136
+ "matcher": "",
137
+ "hooks": [
138
+ {
139
+ "type": "command",
140
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/templates/hooks/task-completed.mjs",
141
+ "async": false
142
+ }
143
+ ]
144
+ }
145
+ ],
146
+ "ConfigChange": [
147
+ {
148
+ "matcher": "",
149
+ "hooks": [
150
+ {
151
+ "type": "command",
152
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/templates/hooks/config-change.mjs",
153
+ "async": false
154
+ }
155
+ ]
156
+ }
62
157
  ]
63
158
  }
64
159
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liangjie559567/ultrapower",
3
- "version": "5.2.12",
3
+ "version": "5.3.0",
4
4
  "description": "Disciplined multi-agent orchestration: workflow enforcement + parallel execution",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -58,7 +58,8 @@
58
58
  "prepublishOnly": "npm run build && npm run compose-docs",
59
59
  "postinstall": "node scripts/plugin-setup.mjs",
60
60
  "release:local": "node scripts/release-local.mjs",
61
- "release:dry-run": "node scripts/release-local.mjs --dry-run"
61
+ "release:dry-run": "node scripts/release-local.mjs --dry-run",
62
+ "bump": "node scripts/bump-version.mjs"
62
63
  },
63
64
  "dependencies": {
64
65
  "@anthropic-ai/claude-agent-sdk": "^0.1.0",
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env node
2
+ // scripts/bump-version.mjs
3
+ // 用法: node scripts/bump-version.mjs <new-version>
4
+ // 统一更新 package.json / plugin.json / marketplace.json 并校验一致性
5
+
6
+ import { readFileSync, writeFileSync } from 'node:fs';
7
+ import { resolve } from 'node:path';
8
+
9
+ const FILES = {
10
+ pkg: resolve('package.json'),
11
+ plugin: resolve('.claude-plugin/plugin.json'),
12
+ marketplace: resolve('.claude-plugin/marketplace.json'),
13
+ };
14
+
15
+ export function readVersions() {
16
+ const pkg = JSON.parse(readFileSync(FILES.pkg, 'utf-8'));
17
+ const plugin = JSON.parse(readFileSync(FILES.plugin, 'utf-8'));
18
+ const market = JSON.parse(readFileSync(FILES.marketplace, 'utf-8'));
19
+ return {
20
+ pkg: pkg.version,
21
+ plugin: plugin.version,
22
+ marketplace: market.plugins?.[0]?.version,
23
+ };
24
+ }
25
+
26
+ export function assertVersionsSync() {
27
+ const v = readVersions();
28
+ const versions = Object.values(v);
29
+ if (new Set(versions).size !== 1) {
30
+ throw new Error(`Version mismatch: ${JSON.stringify(v)}`);
31
+ }
32
+ return versions[0];
33
+ }
34
+
35
+ export function bumpVersion(newVersion) {
36
+ if (!/^\d+\.\d+\.\d+/.test(newVersion)) {
37
+ throw new Error(`Invalid version format: ${newVersion}`);
38
+ }
39
+ // package.json
40
+ const pkg = JSON.parse(readFileSync(FILES.pkg, 'utf-8'));
41
+ pkg.version = newVersion;
42
+ writeFileSync(FILES.pkg, JSON.stringify(pkg, null, 2) + '\n');
43
+
44
+ // plugin.json
45
+ const plugin = JSON.parse(readFileSync(FILES.plugin, 'utf-8'));
46
+ plugin.version = newVersion;
47
+ writeFileSync(FILES.plugin, JSON.stringify(plugin, null, 2) + '\n');
48
+
49
+ // marketplace.json
50
+ const market = JSON.parse(readFileSync(FILES.marketplace, 'utf-8'));
51
+ for (const p of market.plugins ?? []) {
52
+ p.version = newVersion;
53
+ if (p.source) p.source.version = newVersion;
54
+ }
55
+ writeFileSync(FILES.marketplace, JSON.stringify(market, null, 2) + '\n');
56
+
57
+ console.log(`Bumped all version files to ${newVersion}`);
58
+ }
59
+
60
+ // CLI
61
+ const newVersion = process.argv[2];
62
+ if (newVersion) {
63
+ bumpVersion(newVersion);
64
+ } else {
65
+ const v = readVersions();
66
+ console.log('Current versions:', v);
67
+ try { assertVersionsSync(); console.log('✓ All in sync'); }
68
+ catch (e) { console.error('✗', e.message); process.exit(1); }
69
+ }
@@ -102,6 +102,7 @@ function fixNestedCacheDir() {
102
102
  // Pattern B: cache/omc/ultrapower/VERSION/ (versioned nesting)
103
103
  const pluginCacheBase = join(CLAUDE_DIR, 'plugins/cache/omc/ultrapower');
104
104
  if (!existsSync(pluginCacheBase)) return;
105
+ const versions = readdirSync(pluginCacheBase);
105
106
  for (const version of versions) {
106
107
  const versionDir = join(pluginCacheBase, version);
107
108
  const nestedDir = join(versionDir, 'ultrapower');
@@ -2,6 +2,7 @@
2
2
  import { execSync } from 'node:child_process';
3
3
  import { readFileSync } from 'node:fs';
4
4
  import { resolve } from 'node:path';
5
+ import { assertVersionsSync } from './bump-version.mjs';
5
6
 
6
7
  function getVersion() {
7
8
  const pkg = JSON.parse(readFileSync(resolve('package.json'), 'utf-8'));
@@ -16,6 +17,15 @@ function run(cmd, dryRun = false) {
16
17
  return execSync(cmd, { stdio: 'pipe', encoding: 'utf-8' });
17
18
  }
18
19
 
20
+ export async function preflight(opts = {}) {
21
+ try {
22
+ assertVersionsSync();
23
+ return { success: true };
24
+ } catch (err) {
25
+ return { success: false, output: err.message };
26
+ }
27
+ }
28
+
19
29
  export async function validateBuild(opts = {}) {
20
30
  const { skipTests = false, dryRun = false } = opts;
21
31
  try {
@@ -52,34 +62,15 @@ export async function createGithubRelease(opts = {}) {
52
62
  }
53
63
 
54
64
  export async function syncMarketplace(opts = {}) {
55
- const { version, dryRun = false } = opts;
56
- const v = version || getVersion();
57
- try {
58
- // Update marketplace.json version fields
59
- const marketplacePath = resolve('.claude-plugin/marketplace.json');
60
- const marketplace = JSON.parse(readFileSync(marketplacePath, 'utf-8'));
61
- for (const plugin of marketplace.plugins ?? []) {
62
- plugin.version = v;
63
- if (plugin.source) plugin.source.version = v;
64
- }
65
- if (!dryRun) {
66
- const { writeFileSync } = await import('node:fs');
67
- writeFileSync(marketplacePath, JSON.stringify(marketplace, null, 2) + '\n');
68
- } else {
69
- console.log(`[dry-run] Would write marketplace.json version -> ${v}`);
70
- }
71
- run(`git add .claude-plugin/marketplace.json`, dryRun);
72
- run(`git commit -m "chore: sync marketplace version to v${v}" --allow-empty`, dryRun);
73
- run(`git push origin HEAD`, dryRun);
74
- return { success: true };
75
- } catch (err) {
76
- return { success: false, output: err.message };
77
- }
65
+ const { dryRun = false } = opts;
66
+ // Version already in sync (bumped before tagging); no-op in CI (tag push is sufficient)
67
+ if (dryRun) { console.log('[dry-run] syncMarketplace: no-op'); }
68
+ return { success: true };
78
69
  }
79
70
 
80
71
  export async function runReleasePipeline(opts = {}) {
81
- const { dryRun = false, skipTests = false, startFrom = 'validate', version } = opts;
82
- const steps = ['validate', 'publish', 'release', 'sync'];
72
+ const { dryRun = false, skipTests = false, startFrom = 'preflight', version } = opts;
73
+ const steps = ['preflight', 'validate', 'publish', 'release', 'sync'];
83
74
  const startIdx = steps.indexOf(startFrom);
84
75
 
85
76
  if (startIdx === -1) {
@@ -88,30 +79,17 @@ export async function runReleasePipeline(opts = {}) {
88
79
  }
89
80
 
90
81
  const v = version || getVersion();
82
+ const run5 = async (label, fn, fnOpts) => {
83
+ console.log(label);
84
+ const r = await fn(fnOpts);
85
+ if (!r.success) { console.error(`${label} failed: ${r.output}`); process.exit(1); }
86
+ };
91
87
 
92
- if (startIdx <= 0) {
93
- console.log('Step 1/4: validateBuild...');
94
- const r = await validateBuild({ skipTests, dryRun });
95
- if (!r.success) { console.error(`validateBuild failed: ${r.output}`); process.exit(1); }
96
- }
97
-
98
- if (startIdx <= 1) {
99
- console.log('Step 2/4: publishNpm...');
100
- const r = await publishNpm({ dryRun });
101
- if (!r.success) { console.error(`publishNpm failed: ${r.output}`); process.exit(1); }
102
- }
103
-
104
- if (startIdx <= 2) {
105
- console.log('Step 3/4: createGithubRelease...');
106
- const r = await createGithubRelease({ version: v, dryRun });
107
- if (!r.success) { console.error(`createGithubRelease failed: ${r.output}`); process.exit(1); }
108
- }
109
-
110
- if (startIdx <= 3) {
111
- console.log('Step 4/4: syncMarketplace...');
112
- const r = await syncMarketplace({ version: v, dryRun });
113
- if (!r.success) { console.error(`syncMarketplace failed: ${r.output}`); process.exit(1); }
114
- }
88
+ if (startIdx <= 0) await run5('Step 1/5: preflight...', preflight, { dryRun });
89
+ if (startIdx <= 1) await run5('Step 2/5: validateBuild...', validateBuild, { skipTests, dryRun });
90
+ if (startIdx <= 2) await run5('Step 3/5: publishNpm...', publishNpm, { dryRun });
91
+ if (startIdx <= 3) await run5('Step 4/5: createGithubRelease...', createGithubRelease, { version: v, dryRun });
92
+ if (startIdx <= 4) await run5('Step 5/5: syncMarketplace...', syncMarketplace, { dryRun });
115
93
 
116
94
  console.log('Release pipeline completed successfully.');
117
95
  return { success: true };
@@ -119,15 +97,10 @@ export async function runReleasePipeline(opts = {}) {
119
97
 
120
98
  // CLI entry point (for direct GitHub Actions invocation)
121
99
  const cliStep = process.argv[2];
122
- if (cliStep && ['validate', 'publish', 'release', 'sync'].includes(cliStep)) {
100
+ if (cliStep && ['preflight', 'validate', 'publish', 'release', 'sync'].includes(cliStep)) {
123
101
  const dryRun = process.argv.includes('--dry-run');
124
102
  const version = process.env.GITHUB_REF_NAME?.replace(/^v/, '') || undefined;
125
- const stepMap = {
126
- validate: validateBuild,
127
- publish: publishNpm,
128
- release: createGithubRelease,
129
- sync: syncMarketplace,
130
- };
103
+ const stepMap = { preflight, validate: validateBuild, publish: publishNpm, release: createGithubRelease, sync: syncMarketplace };
131
104
  stepMap[cliStep]({ dryRun, version }).then(r => {
132
105
  if (!r.success) { console.error(`Step ${cliStep} failed`); process.exit(1); }
133
106
  });
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ // OMC ConfigChange Hook
3
+ // Fires when Claude Code configuration changes.
4
+
5
+ import { readStdin } from './lib/stdin.mjs';
6
+
7
+ async function main() {
8
+ const raw = await readStdin();
9
+ let data = {};
10
+ try { data = JSON.parse(raw); } catch {}
11
+
12
+ // key and value available in data
13
+ console.log(JSON.stringify({ continue: true, suppressOutput: true }));
14
+ }
15
+
16
+ main();
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ // OMC PermissionRequest Hook
3
+ // Fires when Claude requests permission to use a tool.
4
+
5
+ import { readStdin } from './lib/stdin.mjs';
6
+
7
+ async function main() {
8
+ const raw = await readStdin();
9
+ let data = {};
10
+ try { data = JSON.parse(raw); } catch {}
11
+
12
+ // tool_name and tool_input available in data
13
+ console.log(JSON.stringify({ continue: true, suppressOutput: true }));
14
+ }
15
+
16
+ main();
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ // OMC PreCompact Hook
3
+ // Fires before context compaction. Saves Axiom state to prevent context loss.
4
+
5
+ import { existsSync, readFileSync, writeFileSync } from 'fs';
6
+ import { join } from 'path';
7
+ import { readStdin } from './lib/stdin.mjs';
8
+
9
+ async function main() {
10
+ const raw = await readStdin();
11
+ let data = {};
12
+ try { data = JSON.parse(raw); } catch {}
13
+
14
+ const cwd = data.cwd || process.cwd();
15
+ const VALID_TRIGGERS = new Set(['auto', 'manual']);
16
+ const trigger = VALID_TRIGGERS.has(data.trigger) ? data.trigger : 'auto';
17
+ const axiomDir = join(cwd, '.omc', 'axiom');
18
+
19
+ // If Axiom is active, append a compact notice to active_context.md
20
+ const activeContextPath = join(axiomDir, 'active_context.md');
21
+ if (existsSync(activeContextPath)) {
22
+ try {
23
+ const content = readFileSync(activeContextPath, 'utf-8');
24
+ const notice = `\n\n<!-- PreCompact: context compacted (${trigger}) at ${new Date().toISOString()} -->`;
25
+ if (!content.includes('<!-- PreCompact:')) {
26
+ writeFileSync(activeContextPath, content + notice, 'utf-8');
27
+ }
28
+ } catch {}
29
+ }
30
+
31
+ console.log(JSON.stringify({ continue: true, suppressOutput: true }));
32
+ }
33
+
34
+ main();
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ // OMC SessionEnd Hook
3
+ // Fires when a session ends. Cleans up transient state.
4
+
5
+ import { readStdin } from './lib/stdin.mjs';
6
+
7
+ async function main() {
8
+ const raw = await readStdin();
9
+ let data = {};
10
+ try { data = JSON.parse(raw); } catch {}
11
+
12
+ // session_id and directory available in data
13
+ console.log(JSON.stringify({ continue: true, suppressOutput: true }));
14
+ }
15
+
16
+ main();
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ // OMC SubagentStop Hook
3
+ // Fires when a subagent finishes. Logs agent completion for trace/metrics.
4
+
5
+ import { readStdin } from './lib/stdin.mjs';
6
+
7
+ async function main() {
8
+ const raw = await readStdin();
9
+ let data = {};
10
+ try { data = JSON.parse(raw); } catch {}
11
+
12
+ const { agent_type, agent_id, last_assistant_message, stop_hook_active } = data;
13
+
14
+ // Prevent infinite loop: if hook is already active, exit silently
15
+ if (stop_hook_active) process.exit(0);
16
+
17
+ console.log(JSON.stringify({ continue: true, suppressOutput: true }));
18
+ }
19
+
20
+ main();
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ // OMC TaskCompleted Hook
3
+ // Fires when a task is marked complete.
4
+
5
+ import { readStdin } from './lib/stdin.mjs';
6
+
7
+ async function main() {
8
+ const raw = await readStdin();
9
+ let data = {};
10
+ try { data = JSON.parse(raw); } catch {}
11
+
12
+ // task_id and result available in data
13
+ console.log(JSON.stringify({ continue: true, suppressOutput: true }));
14
+ }
15
+
16
+ main();
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ // OMC TeammateIdle Hook
3
+ // Fires when a team teammate is about to go idle.
4
+ // Exit code 2 + stderr message forces teammate to continue working.
5
+
6
+ // Allow idle by default - enforcement is opt-in via user config
7
+ async function main() {
8
+ process.exit(0);
9
+ }
10
+
11
+ main();
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ // OMC UserPromptSubmit Hook
3
+ // Fires before user prompt is submitted. Used for keyword detection.
4
+
5
+ import { readStdin } from './lib/stdin.mjs';
6
+
7
+ async function main() {
8
+ const raw = await readStdin();
9
+ let data = {};
10
+ try { data = JSON.parse(raw); } catch {}
11
+
12
+ // prompt available in data.prompt
13
+ console.log(JSON.stringify({ continue: true, suppressOutput: true }));
14
+ }
15
+
16
+ main();