ultra-builder-pro-cli 0.9.0 → 0.11.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.
- package/CHANGELOG.md +92 -1
- package/README.md +11 -6
- package/adapters/_shared/codex-assets.cjs +23 -331
- package/adapters/_shared/handbook.cjs +2 -0
- package/adapters/_shared/runtime-assets.cjs +25 -0
- package/adapters/_shared/tests/handbook.test.cjs +3 -0
- package/adapters/_shared/tests/runtime-assets.test.cjs +12 -4
- package/adapters/claude.js +28 -5
- package/adapters/kimi.js +15 -138
- package/adapters/opencode.js +59 -63
- package/adapters/tests/claude.test.cjs +26 -9
- package/adapters/tests/codex.test.cjs +23 -9
- package/adapters/tests/kimi.test.cjs +15 -11
- package/adapters/tests/opencode.test.cjs +57 -10
- package/agents/code-reviewer.md +24 -85
- package/agents/debugger.md +30 -132
- package/agents/review-code.md +24 -104
- package/agents/review-comments.md +20 -83
- package/agents/review-coordinator.md +40 -137
- package/agents/review-design.md +21 -118
- package/agents/review-errors.md +22 -70
- package/agents/review-spec.md +36 -0
- package/agents/review-tests.md +20 -76
- package/agents/tdd-runner.md +26 -84
- package/commands/learn.md +5 -36
- package/commands/ultra-change.md +5 -43
- package/commands/ultra-deliver.md +4 -46
- package/commands/ultra-dev.md +4 -48
- package/commands/ultra-doctor.md +4 -30
- package/commands/ultra-init.md +5 -47
- package/commands/ultra-plan.md +5 -48
- package/commands/ultra-research.md +5 -54
- package/commands/ultra-status.md +5 -44
- package/commands/ultra-test.md +5 -45
- package/commands/ultra-think.md +5 -40
- package/docs/AGENT-CONTEXT.md +34 -12
- package/docs/ARCHITECTURE.md +31 -5
- package/docs/PLAN.zh-CN.md +3 -2
- package/docs/ROADMAP.md +5 -4
- package/docs/RUNTIME-COMPAT-MATRIX.md +7 -5
- package/docs/SKILL-AUTHORING.md +75 -0
- package/docs/USER-HANDBOOK-CONTRACT.md +9 -2
- package/hooks/README.md +6 -4
- package/hooks/active_task_context.py +14 -0
- package/hooks/context_spine.py +218 -0
- package/hooks/health_check.py +1 -1
- package/hooks/workflow_context.py +16 -123
- package/hooks/workflow_resume.py +21 -0
- package/mcp-server/lib/change-workflow.cjs +89 -11
- package/mcp-server/lib/change-workflow.test.cjs +202 -7
- package/mcp-server/lib/context-spine.cjs +331 -0
- package/mcp-server/lib/context-spine.test.cjs +73 -0
- package/mcp-server/lib/spec-learning.cjs +191 -0
- package/mcp-server/lib/spec-learning.test.cjs +62 -0
- package/mcp-server/lib/state-db.cjs +72 -10
- package/mcp-server/lib/state-db.test.cjs +7 -2
- package/mcp-server/server.cjs +31 -1
- package/mcp-server/tests/db-ops.test.cjs +3 -3
- package/mcp-server/tests/server-contract.test.cjs +74 -0
- package/mcp-server/tests/state-db.test.cjs +20 -3
- package/package.json +3 -2
- package/skills/cc-collab/SKILL.md +16 -18
- package/skills/code-review-expert/SKILL.md +39 -114
- package/skills/code-review-expert/references/correctness-reliability.md +30 -0
- package/skills/code-review-expert/references/design-boundaries.md +26 -0
- package/skills/code-review-expert/references/removal-plan.md +17 -45
- package/skills/codex-collab/SKILL.md +23 -28
- package/skills/codex-collab/evals/evals.json +18 -23
- package/skills/integration-rules/SKILL.md +28 -67
- package/skills/learn/SKILL.md +46 -105
- package/skills/security-rules/SKILL.md +34 -56
- package/skills/testing-rules/SKILL.md +28 -52
- package/skills/ultra-change/SKILL.md +61 -145
- package/skills/ultra-deliver/SKILL.md +63 -229
- package/skills/ultra-dev/SKILL.md +46 -338
- package/skills/ultra-doctor/SKILL.md +39 -71
- package/skills/ultra-init/SKILL.md +59 -139
- package/skills/ultra-plan/SKILL.md +50 -310
- package/skills/ultra-research/SKILL.md +59 -152
- package/skills/ultra-research/references/architecture.md +38 -0
- package/skills/ultra-research/references/problem-product.md +39 -0
- package/skills/ultra-research/references/quality-delivery.md +34 -0
- package/skills/ultra-research/references/synthesis.md +38 -0
- package/skills/ultra-research/references/users-scope.md +40 -0
- package/skills/ultra-review/SKILL.md +55 -427
- package/skills/ultra-review/references/unified-schema.md +76 -101
- package/skills/ultra-review/scripts/review_wait.py +242 -77
- package/skills/ultra-status/SKILL.md +38 -173
- package/skills/ultra-test/SKILL.md +41 -195
- package/skills/ultra-think/SKILL.md +43 -101
- package/skills/ultra-verify/SKILL.md +44 -84
- package/skills/ultra-verify/evals/evals.json +24 -27
- package/spec/cli-protocol.md +3 -0
- package/spec/mcp-tools.yaml +133 -4
- package/spec/schemas/command-manifest.schema.json +7 -25
- package/spec/schemas/skill-manifest.schema.json +9 -79
- package/spec/schemas/state-db.sql +31 -0
- package/skills/code-review-expert/references/code-quality-checklist.md +0 -112
- package/skills/code-review-expert/references/integration-checklist.md +0 -46
- package/skills/code-review-expert/references/security-checklist.md +0 -100
- package/skills/code-review-expert/references/solid-checklist.md +0 -65
- package/skills/ultra-research/steps/step-00-problem-validation.md +0 -154
- package/skills/ultra-research/steps/step-01-opportunity-discovery.md +0 -172
- package/skills/ultra-research/steps/step-02-market-assessment.md +0 -195
- package/skills/ultra-research/steps/step-03-competitive-landscape.md +0 -197
- package/skills/ultra-research/steps/step-04-product-strategy.md +0 -196
- package/skills/ultra-research/steps/step-05-assumptions-validation.md +0 -234
- package/skills/ultra-research/steps/step-10-user-personas.md +0 -169
- package/skills/ultra-research/steps/step-11-user-scenarios.md +0 -181
- package/skills/ultra-research/steps/step-20-user-stories.md +0 -156
- package/skills/ultra-research/steps/step-21-features-scope.md +0 -157
- package/skills/ultra-research/steps/step-22-success-metrics.md +0 -195
- package/skills/ultra-research/steps/step-30-architecture-context.md +0 -154
- package/skills/ultra-research/steps/step-31-solution-strategy.md +0 -172
- package/skills/ultra-research/steps/step-32-building-blocks.md +0 -188
- package/skills/ultra-research/steps/step-40-deployment.md +0 -123
- package/skills/ultra-research/steps/step-41-quality-risks.md +0 -135
- package/skills/ultra-research/steps/step-99-synthesis.md +0 -188
- package/skills/ultra-review/scripts/review_verdict_update.py +0 -99
|
@@ -11,8 +11,10 @@ const {
|
|
|
11
11
|
INTERNAL_AGENT_SKILLS,
|
|
12
12
|
SUPPORTED_RUNTIMES,
|
|
13
13
|
COLLAB_SKILLS_BY_RUNTIME,
|
|
14
|
+
MCP_DEPENDENT_SKILLS,
|
|
14
15
|
RETIRED_SKILLS,
|
|
15
16
|
WORKFLOW_HOOK_FILES,
|
|
17
|
+
skillPolicy,
|
|
16
18
|
skillsForRuntime,
|
|
17
19
|
} = require('../runtime-assets.cjs');
|
|
18
20
|
|
|
@@ -74,15 +76,21 @@ test('runtime asset manifest exposes only Ultra-owned core and internal skills',
|
|
|
74
76
|
}
|
|
75
77
|
});
|
|
76
78
|
|
|
77
|
-
test('
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
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/);
|
|
81
88
|
});
|
|
82
89
|
|
|
83
90
|
test('workflow hook allowlist contains no memory, prompt capture, or generic policy hook', () => {
|
|
84
91
|
assert.deepEqual(WORKFLOW_HOOK_FILES, [
|
|
85
92
|
'active_task_context.py',
|
|
93
|
+
'context_spine.py',
|
|
86
94
|
'health_check.py',
|
|
87
95
|
'pre_stop_check.py',
|
|
88
96
|
'subagent_tracker.py',
|
package/adapters/claude.js
CHANGED
|
@@ -15,10 +15,12 @@ const {
|
|
|
15
15
|
writeAtomic,
|
|
16
16
|
} = require('./_shared/file-ops.cjs');
|
|
17
17
|
const { buildMcpRuntime } = require('./_shared/codex-assets.cjs');
|
|
18
|
+
const { parse: parseFrontmatter, serialize: serializeFrontmatter } = require('./_shared/frontmatter.cjs');
|
|
18
19
|
const provenance = require('./_shared/provenance.cjs');
|
|
19
20
|
const {
|
|
20
21
|
CORE_PUBLIC_SKILLS,
|
|
21
22
|
WORKFLOW_HOOK_FILES,
|
|
23
|
+
skillPolicy,
|
|
22
24
|
skillsForRuntime,
|
|
23
25
|
} = require('./_shared/runtime-assets.cjs');
|
|
24
26
|
|
|
@@ -46,12 +48,33 @@ function resolveRepoRoot(ctx = {}) {
|
|
|
46
48
|
return ctx.repoRoot || path.resolve(__dirname, '..');
|
|
47
49
|
}
|
|
48
50
|
|
|
49
|
-
function
|
|
51
|
+
function copySkills(repoRoot, target, names) {
|
|
50
52
|
const copied = [];
|
|
51
53
|
for (const name of names) {
|
|
52
|
-
const source = path.join(repoRoot,
|
|
53
|
-
if (!fs.existsSync(source)) throw new Error(`missing allowlisted Claude
|
|
54
|
-
const files = copyTree(source, path.join(target,
|
|
54
|
+
const source = path.join(repoRoot, 'skills', name);
|
|
55
|
+
if (!fs.existsSync(source)) throw new Error(`missing allowlisted Claude skill: ${name}`);
|
|
56
|
+
const files = copyTree(source, path.join(target, 'skills', name), {
|
|
57
|
+
transform(buf, rel) {
|
|
58
|
+
if (rel !== 'SKILL.md') return buf;
|
|
59
|
+
const { fm, body } = parseFrontmatter(buf.toString('utf8'));
|
|
60
|
+
if (!fm) throw new Error(`missing frontmatter in Claude skill: ${name}`);
|
|
61
|
+
let adaptedBody = body;
|
|
62
|
+
if (name === 'learn') {
|
|
63
|
+
adaptedBody = adaptedBody.replaceAll("current host's user skill directory", '`~/.claude/skills`');
|
|
64
|
+
}
|
|
65
|
+
if (name === 'ultra-review') {
|
|
66
|
+
adaptedBody = adaptedBody.replaceAll(
|
|
67
|
+
"the current host's native bounded-worker mechanism",
|
|
68
|
+
'Claude Code Task workers using the installed review agent definitions',
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
return Buffer.from(serializeFrontmatter({
|
|
72
|
+
name,
|
|
73
|
+
description: fm.description,
|
|
74
|
+
'user-invocable': skillPolicy(name).userInvocable,
|
|
75
|
+
}, adaptedBody));
|
|
76
|
+
},
|
|
77
|
+
});
|
|
55
78
|
copied.push(...files.map((rel) => path.join(name, rel)));
|
|
56
79
|
}
|
|
57
80
|
return copied;
|
|
@@ -126,7 +149,7 @@ function install(ctx = {}) {
|
|
|
126
149
|
|
|
127
150
|
const report = { target, copied: {}, config: { updated: false } };
|
|
128
151
|
report.copied.commands = copyCommands(repoRoot, target);
|
|
129
|
-
report.copied.skills =
|
|
152
|
+
report.copied.skills = copySkills(repoRoot, target, skillsForRuntime('claude'));
|
|
130
153
|
report.copied.agents = copyTree(path.join(repoRoot, 'agents'), path.join(target, 'agents'));
|
|
131
154
|
|
|
132
155
|
ensureDir(path.join(target, 'hooks'));
|
package/adapters/kimi.js
CHANGED
|
@@ -76,154 +76,31 @@ function kimiTextTransform(input, assetName = '') {
|
|
|
76
76
|
text = text.replaceAll('CLAUDE.md', 'AGENTS.md');
|
|
77
77
|
text = text.replace(/@skills\/([a-z0-9-]+)\/SKILL\.md/g, '$KIMI_PLUGIN_ROOT/skills/$1/SKILL.md');
|
|
78
78
|
|
|
79
|
-
text = text.replaceAll('`TaskCreate/TaskUpdate (session-local)`', 'Kimi `TodoList` (session-local)');
|
|
80
|
-
text = text.replaceAll('TaskCreate/TaskUpdate (session-local)', 'Kimi `TodoList` (session-local)');
|
|
81
|
-
text = text.replaceAll('`TaskCreate/TaskUpdate`', 'Kimi `TodoList`');
|
|
82
|
-
text = text.replaceAll('TaskCreate/TaskUpdate', 'Kimi `TodoList`');
|
|
83
|
-
text = text.replaceAll('`TaskCreate`', 'Kimi `TodoList`');
|
|
84
|
-
text = text.replaceAll('`TaskUpdate`', 'Kimi `TodoList`');
|
|
85
|
-
text = text.replaceAll('`TaskList`', 'the current Kimi `TodoList`');
|
|
86
|
-
text = text.replaceAll('TaskCreate', 'Kimi TodoList item creation');
|
|
87
|
-
text = text.replaceAll('TaskUpdate', 'Kimi TodoList updates');
|
|
88
|
-
text = text.replaceAll('TaskList', 'the current Kimi TodoList');
|
|
89
|
-
text = text.replaceAll('TaskOutput', 'subagent transcript output');
|
|
90
|
-
text = text.replaceAll('Claude Task tool', 'Kimi `AgentSwarm`');
|
|
91
|
-
text = text.replaceAll('Task tool', 'Kimi `AgentSwarm`');
|
|
92
|
-
text = text.replaceAll('multiple Task calls', 'one Kimi `AgentSwarm` call with multiple independent tasks');
|
|
93
|
-
text = text.replaceAll('Task calls', 'Kimi `AgentSwarm` items');
|
|
94
|
-
text = text.replaceAll('Task call', 'Kimi `AgentSwarm` item');
|
|
95
|
-
text = text.replaceAll('`run_in_background: true`', 'Kimi `AgentSwarm` parallel execution');
|
|
96
|
-
text = text.replaceAll('run_in_background: true', 'Kimi `AgentSwarm` parallel execution');
|
|
97
|
-
text = text.replaceAll('AskUserQuestion', 'a direct question to the user');
|
|
98
|
-
text = text.replaceAll('Bash tool', 'Kimi `Bash` tool');
|
|
99
|
-
text = text.replaceAll('Use Bash', 'Use Kimi `Bash`');
|
|
100
|
-
text = text.replaceAll('Read tool', 'Kimi `Read` tool');
|
|
101
|
-
text = text.replaceAll('Write tool', 'Kimi `Write` tool');
|
|
102
|
-
text = text.replaceAll('MCP `review.run`', 'Kimi `AgentSwarm` review workers');
|
|
103
|
-
text = text.replaceAll('`review.run`', 'Kimi `AgentSwarm` review workers');
|
|
104
|
-
text = text.replaceAll('review.run', 'Kimi `AgentSwarm` review workers');
|
|
105
|
-
text = text.replaceAll('MCP `ask.question`', 'a direct question to the user');
|
|
106
|
-
text = text.replaceAll('`ask.question`', 'a direct question to the user');
|
|
107
|
-
text = text.replaceAll('ask.question', 'a direct question to the user');
|
|
108
|
-
text = text.replaceAll('Claude runtime', 'Kimi runtime');
|
|
109
|
-
text = text.replaceAll('Claude-only', 'Kimi-only');
|
|
110
|
-
text = text.replaceAll('Claude Code、OpenCode、Codex 三个 runtime', 'Claude Code、OpenCode、Codex、Kimi Code 四个 runtime');
|
|
111
|
-
text = text.replaceAll('Claude Code, OpenCode, and Codex', 'Claude Code, OpenCode, Codex, and Kimi Code');
|
|
112
|
-
|
|
113
79
|
if (assetName === 'codex-collab') {
|
|
80
|
+
text = text.replaceAll('the current host', 'Kimi Code');
|
|
81
|
+
text = text.replaceAll('current host', 'Kimi Code');
|
|
114
82
|
text = text.replaceAll('Claude Code remains primary', 'Kimi Code remains primary');
|
|
115
83
|
text = text.replaceAll('one Claude Code-owned conclusion', 'one Kimi Code-owned conclusion');
|
|
116
84
|
}
|
|
117
85
|
if (assetName === 'ultra-verify') {
|
|
118
|
-
text = text.replaceAll('
|
|
119
|
-
text = text.replaceAll(
|
|
120
|
-
text = text.replaceAll('Claude
|
|
121
|
-
text = text.replaceAll('Claude
|
|
86
|
+
text = text.replaceAll('the current host', 'Kimi Code');
|
|
87
|
+
text = text.replaceAll('current host', 'Kimi Code');
|
|
88
|
+
text = text.replaceAll('Claude Code', 'Kimi Code');
|
|
89
|
+
text = text.replaceAll('Claude', 'Kimi');
|
|
122
90
|
text = text.replaceAll('claude-analysis.md', 'kimi-analysis.md');
|
|
123
|
-
text = text.replaceAll('one Claude Code-owned conclusion', 'one Kimi Code-owned conclusion');
|
|
124
91
|
}
|
|
125
|
-
if (assetName === '
|
|
126
|
-
text = text.replaceAll(
|
|
127
|
-
|
|
128
|
-
'~/.kimi-code/skills/learned-<name>-unverified/SKILL.md',
|
|
129
|
-
);
|
|
130
|
-
text = text.replaceAll(
|
|
131
|
-
'~/.kimi-code/skills/learned/<pattern-slug>_unverified.md',
|
|
132
|
-
'~/.kimi-code/skills/learned-<pattern-slug>-unverified/SKILL.md',
|
|
133
|
-
);
|
|
134
|
-
text = text.replaceAll(
|
|
135
|
-
'~/.kimi-code/skills/learned/<slug>_unverified.md',
|
|
136
|
-
'~/.kimi-code/skills/learned-<slug>-unverified/SKILL.md',
|
|
137
|
-
);
|
|
138
|
-
text = text.replaceAll('append the `_unverified` suffix to the filename', 'append `-unverified` to the skill directory name');
|
|
139
|
-
text = text.replaceAll('Never overwrite an existing unverified file', 'Never overwrite an existing learned skill directory');
|
|
140
|
-
text = text.replaceAll('remove the `_unverified` suffix', 'rename the directory to remove `-unverified` and update its frontmatter name');
|
|
141
|
-
text = text.replaceAll('(`pattern-slug-2_unverified.md`)', '(`learned-pattern-slug-2-unverified/`)');
|
|
142
|
-
}
|
|
143
|
-
if (assetName === 'ultra-init') {
|
|
144
|
-
text = text.replaceAll('调用方(Claude / CLI / SDK)', '调用方(Kimi / CLI / SDK)');
|
|
145
|
-
text = text.replace(
|
|
146
|
-
/- 如果调用方是 Claude,可在此用 Kimi `TodoList` 跟踪 Step 0–4 的 session 内进度\n (这是 runtime 的 session-local 跟踪,不走 MCP)/,
|
|
147
|
-
'- 在 Kimi 中,用原生 `TodoList` 跟踪 Step 0–4 的 session 内进度;这是 session-local 协调,不走 MCP',
|
|
148
|
-
);
|
|
149
|
-
text = text.replace(
|
|
150
|
-
/## 调用方式(按 runtime)[\s\S]*?## 输出锚点/,
|
|
151
|
-
[
|
|
152
|
-
'## Kimi 调用方式',
|
|
153
|
-
'',
|
|
154
|
-
'| Runtime | 调用形态 |',
|
|
155
|
-
'|---------|----------|',
|
|
156
|
-
'| Kimi | `/ultra-builder-pro:ultra-init [name] [type] [stack] [git]` — 原生命令拉起此 skill |',
|
|
157
|
-
'',
|
|
158
|
-
'## 输出锚点',
|
|
159
|
-
].join('\n'),
|
|
160
|
-
);
|
|
92
|
+
if (assetName === 'cc-collab') {
|
|
93
|
+
text = text.replaceAll('the current host', 'Kimi Code');
|
|
94
|
+
text = text.replaceAll('current host', 'Kimi Code');
|
|
161
95
|
}
|
|
162
96
|
if (assetName === 'ultra-review') {
|
|
163
|
-
text = text.
|
|
164
|
-
'
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
## Kimi native worker contract
|
|
168
|
-
|
|
169
|
-
Use one Kimi \`AgentSwarm\` call when two or more independent reviewers are selected; use one
|
|
170
|
-
foreground Kimi \`Agent\` call when only one reviewer is selected. Each reviewer first reads its
|
|
171
|
-
matching prompt template (for example \`$KIMI_PLUGIN_ROOT/agents/review-code.md\`) and writes only
|
|
172
|
-
its JSON artifact under \`SESSION_PATH\`. Run the coordinator as a final foreground Kimi \`Agent\`
|
|
173
|
-
after the reviewer artifacts are complete. Kimi has no custom-agent plugin manifest field; these
|
|
174
|
-
bundled files are bounded prompt templates, not separately registered host agents.`,
|
|
175
|
-
);
|
|
176
|
-
const executionContract = [
|
|
177
|
-
'### Phase 3: Kimi Reviewer Execution (File-Based)',
|
|
178
|
-
'',
|
|
179
|
-
'When two or more reviewers are selected, call Kimi `AgentSwarm` exactly once and make it the',
|
|
180
|
-
'only tool call in that response. Use `subagent_type: "coder"`, a `prompt_template` containing',
|
|
181
|
-
'`{{item}}`, and the selected reviewer names as the `items` array. The filled prompt must tell',
|
|
182
|
-
'each reviewer to:',
|
|
183
|
-
'',
|
|
184
|
-
'1. Read `$KIMI_PLUGIN_ROOT/agents/{{item}}.md` and follow that bounded worker contract.',
|
|
185
|
-
'2. Review only `DIFF_FILES` / `DIFF_RANGE` and write',
|
|
186
|
-
' `{SESSION_PATH}/{{item}}.json` using the schema at',
|
|
187
|
-
' `$KIMI_PLUGIN_ROOT/skills/ultra-review/references/unified-schema.md`.',
|
|
188
|
-
'3. Report at most 12 findings with confidence >= 75, ordered by severity then confidence.',
|
|
189
|
-
'4. Return only `Wrote N findings (P0:X P1:X P2:X P3:X) to <filepath>` after the file exists.',
|
|
190
|
-
'',
|
|
191
|
-
'Kimi `AgentSwarm` is foreground and accepts at least two items; it has no',
|
|
192
|
-
'`run_in_background` field. When exactly one reviewer is selected, launch one foreground Kimi',
|
|
193
|
-
'`Agent` with `subagent_type: "coder"`, `run_in_background: false`, and the same file contract.',
|
|
194
|
-
'The short final lines may appear in the parent context; the JSON files remain the only findings',
|
|
195
|
-
'input to the coordinator.',
|
|
196
|
-
'',
|
|
197
|
-
'### Phase 4: Validate & Coordinate',
|
|
198
|
-
'',
|
|
199
|
-
'**Step 4a: validate reviewer artifacts** — after the foreground call returns, run:',
|
|
200
|
-
'',
|
|
201
|
-
'```bash',
|
|
202
|
-
'python3 "$KIMI_PLUGIN_ROOT/skills/ultra-review/scripts/review_wait.py" {SESSION_PATH} agents {AGENT_COUNT}',
|
|
203
|
-
'```',
|
|
204
|
-
'',
|
|
205
|
-
'Use only the valid `review-*.json` files reported by the waiter. A partial result is explicit;',
|
|
206
|
-
'zero valid reviewer files skips coordination and reports the failure.',
|
|
207
|
-
'',
|
|
208
|
-
'**Step 4b: coordinate** — Launch the coordinator with one foreground Kimi `Agent` call using',
|
|
209
|
-
'`subagent_type: "coder"` and `run_in_background: false`. Its prompt must first read',
|
|
210
|
-
'`$KIMI_PLUGIN_ROOT/agents/review-coordinator.md`, then read the valid reviewer JSON files,',
|
|
211
|
-
'deduplicate findings, and write `SUMMARY.md` plus `SUMMARY.json` under `SESSION_PATH`.',
|
|
212
|
-
'',
|
|
213
|
-
'**Step 4c: validate the summary** — after the coordinator returns, run:',
|
|
214
|
-
'',
|
|
215
|
-
'```bash',
|
|
216
|
-
'python3 "$KIMI_PLUGIN_ROOT/skills/ultra-review/scripts/review_wait.py" {SESSION_PATH} summary',
|
|
217
|
-
'```',
|
|
218
|
-
'',
|
|
219
|
-
'Read `SUMMARY.json` only after this validation succeeds.',
|
|
220
|
-
].join('\n');
|
|
221
|
-
text = text.replace(
|
|
222
|
-
/### Phase 3:[\s\S]*?### Phase 5: Report to User/,
|
|
223
|
-
`${executionContract}\n\n### Phase 5: Report to User`,
|
|
97
|
+
text = text.replaceAll(
|
|
98
|
+
"the current host's native bounded-worker mechanism",
|
|
99
|
+
'Kimi `AgentSwarm` for parallel reviewers or one foreground Kimi `Agent` for a single reviewer, using the worker prompt files under `$KIMI_PLUGIN_ROOT/agents/`',
|
|
224
100
|
);
|
|
225
|
-
|
|
226
|
-
|
|
101
|
+
}
|
|
102
|
+
if (assetName === 'learn') {
|
|
103
|
+
text = text.replaceAll("current host's user skill directory", '`~/.kimi-code/skills`');
|
|
227
104
|
}
|
|
228
105
|
text = text.replace(
|
|
229
106
|
/(^|[\s`("'])\/ultra-(?!builder-pro:)([a-z][a-z0-9-]*)/gm,
|
package/adapters/opencode.js
CHANGED
|
@@ -70,62 +70,29 @@ function openCodeTextTransform(input, assetName = '') {
|
|
|
70
70
|
text = text.replaceAll('CLAUDE.md', 'AGENTS.md');
|
|
71
71
|
text = text.replace(/@skills\/([a-z0-9-]+)\/SKILL\.md/g, '~/.config/opencode/skills/$1/SKILL.md');
|
|
72
72
|
|
|
73
|
-
text = text.replaceAll('TaskCreate/TaskUpdate (session-local)', 'the OpenCode plan (session-local)');
|
|
74
|
-
text = text.replaceAll('TaskCreate', 'an OpenCode plan item');
|
|
75
|
-
text = text.replaceAll('TaskUpdate', 'an OpenCode plan update');
|
|
76
|
-
text = text.replaceAll('TaskList', 'the current OpenCode plan');
|
|
77
|
-
text = text.replaceAll('`run_in_background: true`', 'concurrent OpenCode task execution');
|
|
78
|
-
text = text.replaceAll('run_in_background: true', 'concurrent OpenCode task execution');
|
|
79
|
-
text = text.replaceAll('Claude Task tool', 'OpenCode `task` tool');
|
|
80
|
-
text = text.replaceAll('Task tool', 'OpenCode `task` tool');
|
|
81
|
-
text = text.replaceAll('multiple Task calls', 'multiple OpenCode `task` calls');
|
|
82
|
-
text = text.replaceAll('Bash tool', 'OpenCode `bash` tool');
|
|
83
|
-
text = text.replaceAll('Read tool', 'OpenCode `read` tool');
|
|
84
|
-
text = text.replaceAll('Write tool', 'OpenCode `edit` tool');
|
|
85
|
-
|
|
86
|
-
text = text.replaceAll('MCP `review.run`', 'OpenCode native review subagents');
|
|
87
|
-
text = text.replaceAll('`review.run`', 'OpenCode native review subagents');
|
|
88
|
-
text = text.replaceAll('review.run', 'OpenCode native review subagents');
|
|
89
|
-
text = text.replaceAll('MCP `ask.question`', 'OpenCode `question` tool');
|
|
90
|
-
text = text.replaceAll('`ask.question`', 'OpenCode `question` tool');
|
|
91
|
-
text = text.replaceAll('ask.question', 'OpenCode question tool');
|
|
92
|
-
text = text.replaceAll('AskUserQuestion', 'OpenCode `question` tool');
|
|
93
|
-
text = text.replaceAll('ultra-tools ask --question … --options …', 'OpenCode `question` tool');
|
|
94
|
-
text = text.replaceAll('ultra-tools ask --question …', 'OpenCode `question` tool');
|
|
95
|
-
text = text.replaceAll('ultra-tools ask …', 'OpenCode `question` tool');
|
|
96
|
-
text = text.replaceAll('Claude runtime', 'OpenCode runtime');
|
|
97
|
-
text = text.replaceAll('Claude-only', 'OpenCode-only');
|
|
98
|
-
|
|
99
73
|
if (assetName === 'codex-collab') {
|
|
100
|
-
text = text.replaceAll('
|
|
101
|
-
text = text.replaceAll('
|
|
102
|
-
text = text.replaceAll('Claude orchestrates', 'OpenCode remains primary and orchestrates');
|
|
103
|
-
text = text.replaceAll('Claude synthesizes', 'OpenCode verifies and synthesizes');
|
|
104
|
-
text = text.replaceAll("Claude's", "OpenCode's");
|
|
105
|
-
text = text.replaceAll('proceed with Claude-only analysis', 'proceed with OpenCode-only analysis');
|
|
74
|
+
text = text.replaceAll('the current host', 'OpenCode');
|
|
75
|
+
text = text.replaceAll('current host', 'OpenCode');
|
|
106
76
|
}
|
|
107
77
|
if (assetName === 'ultra-verify') {
|
|
78
|
+
text = text.replaceAll('the current host', 'OpenCode');
|
|
79
|
+
text = text.replaceAll('current host', 'OpenCode');
|
|
108
80
|
text = text.replaceAll('claude-analysis.md', 'opencode-analysis.md');
|
|
109
81
|
text = text.replaceAll('Claude Code', 'OpenCode');
|
|
110
82
|
text = text.replaceAll('Claude', 'OpenCode');
|
|
111
83
|
}
|
|
112
|
-
if (assetName === '
|
|
113
|
-
text = text.replaceAll(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
text = text.replaceAll(
|
|
118
|
-
'~/.config/opencode/skills/learned/<pattern-slug>_unverified.md',
|
|
119
|
-
'~/.config/opencode/skills/learned-<pattern-slug>-unverified/SKILL.md',
|
|
120
|
-
);
|
|
84
|
+
if (assetName === 'cc-collab') {
|
|
85
|
+
text = text.replaceAll('the current host', 'OpenCode');
|
|
86
|
+
text = text.replaceAll('current host', 'OpenCode');
|
|
87
|
+
}
|
|
88
|
+
if (assetName === 'ultra-review') {
|
|
121
89
|
text = text.replaceAll(
|
|
122
|
-
'
|
|
123
|
-
'
|
|
90
|
+
"the current host's native bounded-worker mechanism",
|
|
91
|
+
'the OpenCode `task` tool using the installed bounded review agents',
|
|
124
92
|
);
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
text = text.replaceAll('
|
|
128
|
-
text = text.replaceAll('(`pattern-slug-2_unverified.md`)', '(`learned-pattern-slug-2-unverified/`)');
|
|
93
|
+
}
|
|
94
|
+
if (assetName === 'learn') {
|
|
95
|
+
text = text.replaceAll("current host's user skill directory", '`~/.config/opencode/skills`');
|
|
129
96
|
}
|
|
130
97
|
return text;
|
|
131
98
|
}
|
|
@@ -336,25 +303,26 @@ function pluginSource() {
|
|
|
336
303
|
return `// Managed by Ultra Builder Pro. Rebuild through the adapter; do not edit in place.
|
|
337
304
|
import fs from "node:fs";
|
|
338
305
|
import path from "node:path";
|
|
306
|
+
import { execFileSync } from "node:child_process";
|
|
339
307
|
|
|
340
308
|
const TERMINAL = new Set(["committed", "completed", "done", "cancelled"]);
|
|
341
309
|
const TASKS_PROJECTION = ".ultra/tasks/tasks.json";
|
|
342
|
-
const PROVIDER_FIELDS = new Set(["provider", "project", "reference", "revision", "indexed_head", "status"]);
|
|
343
310
|
|
|
344
|
-
function
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
311
|
+
function gitHead(root) {
|
|
312
|
+
try {
|
|
313
|
+
return execFileSync("git", ["rev-parse", "HEAD"], {
|
|
314
|
+
cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"],
|
|
315
|
+
}).trim();
|
|
316
|
+
} catch {
|
|
317
|
+
return null;
|
|
350
318
|
}
|
|
351
|
-
return clean;
|
|
352
319
|
}
|
|
353
320
|
|
|
354
321
|
function findUltraContext(directory) {
|
|
355
322
|
let current = path.resolve(directory);
|
|
356
323
|
while (true) {
|
|
357
324
|
const ultra = path.join(current, ".ultra");
|
|
325
|
+
const currentHead = gitHead(current);
|
|
358
326
|
const workflowFile = path.join(ultra, "workflow-state.json");
|
|
359
327
|
const statePresent = fs.existsSync(path.join(ultra, "state.db"));
|
|
360
328
|
let workflow = null;
|
|
@@ -375,11 +343,36 @@ function findUltraContext(directory) {
|
|
|
375
343
|
if (!fs.existsSync(manifestPath)) continue;
|
|
376
344
|
try {
|
|
377
345
|
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
378
|
-
if (manifest?.change)
|
|
379
|
-
|
|
380
|
-
|
|
346
|
+
if (manifest?.change) {
|
|
347
|
+
const compiledHead = manifest.git?.head ?? null;
|
|
348
|
+
const legacyContext = manifest.schema_version !== "2.0";
|
|
349
|
+
const headStale = Boolean(compiledHead && currentHead && compiledHead !== currentHead);
|
|
350
|
+
const blockers = Array.isArray(manifest.readiness?.blockers)
|
|
351
|
+
? [...manifest.readiness.blockers]
|
|
352
|
+
: [];
|
|
353
|
+
if (headStale && !blockers.includes("CONTEXT_HEAD_STALE")) blockers.push("CONTEXT_HEAD_STALE");
|
|
354
|
+
if (legacyContext && !blockers.includes("CONTEXT_SNAPSHOT_UPGRADE_REQUIRED")) {
|
|
355
|
+
blockers.unshift("CONTEXT_SNAPSHOT_UPGRADE_REQUIRED");
|
|
356
|
+
}
|
|
357
|
+
changes.push({
|
|
358
|
+
id: manifest.change.id,
|
|
359
|
+
kind: manifest.change.kind,
|
|
360
|
+
status: manifest.change.status,
|
|
361
|
+
role: manifest.role ?? "plan",
|
|
362
|
+
gate: manifest.gate ?? "alignment",
|
|
363
|
+
nextAction: legacyContext
|
|
364
|
+
? "Recompile change.context to upgrade this legacy context snapshot."
|
|
365
|
+
: headStale
|
|
366
|
+
? "Git HEAD changed after context compilation; recompile change.context."
|
|
367
|
+
: (manifest.next_action ?? "Inspect Ultra status."),
|
|
368
|
+
readiness: legacyContext || headStale ? "blocked" : (manifest.readiness?.status ?? "ready"),
|
|
369
|
+
blockers,
|
|
370
|
+
taskId: manifest.selected_task?.id ?? manifest.resume?.task_id ?? null,
|
|
371
|
+
taskStatus: manifest.selected_task?.status ?? manifest.resume?.task_status ?? null,
|
|
372
|
+
tokenEstimate: manifest.context?.token_estimate ?? 0,
|
|
381
373
|
manifestPath: path.relative(current, manifestPath),
|
|
382
374
|
});
|
|
375
|
+
}
|
|
383
376
|
} catch (error) {
|
|
384
377
|
process.stderr.write(\`[ultra-builder-pro] cannot read \${manifestPath}: \${error.message}\\n\`);
|
|
385
378
|
}
|
|
@@ -410,13 +403,16 @@ function contextText(active) {
|
|
|
410
403
|
if (active.changes.length > 0) {
|
|
411
404
|
const change = active.changes[0];
|
|
412
405
|
lines.push(
|
|
413
|
-
"[Ultra
|
|
406
|
+
"[Ultra context spine]",
|
|
414
407
|
\`Project: \${active.root}\`,
|
|
415
408
|
\`Change: \${change.id} (\${change.kind ?? "unknown"}, \${change.status ?? "active"})\`,
|
|
416
|
-
\`
|
|
417
|
-
\`
|
|
409
|
+
\`Task: \${change.taskId ?? "none"} (\${change.taskStatus ?? "none"})\`,
|
|
410
|
+
\`Role: \${change.role}\`,
|
|
411
|
+
\`Gate: \${change.gate}\`,
|
|
412
|
+
\`Readiness: \${change.readiness}\`,
|
|
413
|
+
...(change.blockers.length > 0 ? [\`Blockers: \${change.blockers.join(", ")}\`] : []),
|
|
414
|
+
\`Next: \${change.nextAction}\`,
|
|
418
415
|
\`Context manifest: \${change.manifestPath}\`,
|
|
419
|
-
\`External providers (metadata references only): \${JSON.stringify(change.providers)}\`,
|
|
420
416
|
);
|
|
421
417
|
if (active.changes.length > 1) lines.push(
|
|
422
418
|
\`Active change count: \${active.changes.length}; resolve scope with change.list before mutation.\`,
|
|
@@ -429,7 +425,7 @@ function contextText(active) {
|
|
|
429
425
|
"No active continuous change. Start daily work with the ultra-change workflow.",
|
|
430
426
|
);
|
|
431
427
|
}
|
|
432
|
-
lines.push("Authority: .ultra/state.db;
|
|
428
|
+
lines.push("Authority: .ultra/state.db; JSON/Markdown remain projections or evidence artifacts.");
|
|
433
429
|
return lines.join("\\n");
|
|
434
430
|
}
|
|
435
431
|
|
|
@@ -7,7 +7,12 @@ const os = require('node:os');
|
|
|
7
7
|
const path = require('node:path');
|
|
8
8
|
|
|
9
9
|
const claude = require('../claude.js');
|
|
10
|
-
const {
|
|
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');
|
|
11
16
|
|
|
12
17
|
const REPO_ROOT = path.resolve(__dirname, '..', '..');
|
|
13
18
|
|
|
@@ -36,7 +41,10 @@ test('install builds the Claude-native plugin from the explicit Ultra allowlist'
|
|
|
36
41
|
|
|
37
42
|
const hooks = JSON.parse(fs.readFileSync(path.join(target, 'hooks', 'hooks.json'), 'utf8'));
|
|
38
43
|
const serialized = JSON.stringify(hooks);
|
|
39
|
-
for (const name of WORKFLOW_HOOK_FILES
|
|
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')));
|
|
40
48
|
assert.doesNotMatch(serialized, /memory|recall|journal|observation_capture|user_prompt_capture|block_dangerous|post_edit_guard/);
|
|
41
49
|
assert.match(serialized, /\$\{CLAUDE_PLUGIN_ROOT\}/);
|
|
42
50
|
} finally {
|
|
@@ -57,17 +65,26 @@ test('Claude plugin collaboration and learn workflows are safe native plugin ass
|
|
|
57
65
|
const plan = read('ultra-plan');
|
|
58
66
|
const status = read('ultra-status');
|
|
59
67
|
|
|
60
|
-
assert.match(learn,
|
|
61
|
-
assert.doesNotMatch(learn, /_unverified
|
|
68
|
+
assert.match(learn, /`~\/.claude\/skills`/);
|
|
69
|
+
assert.doesNotMatch(learn, /_unverified|learned-[^\s/]*-unverified/i);
|
|
62
70
|
assert.match(codexCollab, /-s read-only/);
|
|
71
|
+
assert.match(codexCollab, /--ephemeral/);
|
|
72
|
+
assert.match(codexCollab, /--ignore-user-config/);
|
|
63
73
|
assert.match(verify, /-s read-only/);
|
|
64
|
-
assert.match(verify,
|
|
65
|
-
assert.match(
|
|
66
|
-
assert.match(
|
|
67
|
-
assert.match(
|
|
68
|
-
assert.
|
|
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);
|
|
69
81
|
assert.doesNotMatch(plan, /ultra-tools task create/);
|
|
70
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
|
+
|
|
71
88
|
for (const [name, contents] of Object.entries({ codexCollab, verify })) {
|
|
72
89
|
assert.doesNotMatch(contents, /--yolo|--full-auto|\/codex:/, name);
|
|
73
90
|
}
|
|
@@ -14,6 +14,7 @@ const codex = require('../codex.js');
|
|
|
14
14
|
const { parse: parseFrontmatter } = require('../_shared/frontmatter.cjs');
|
|
15
15
|
const PACKAGE_VERSION = require('../../package.json').version;
|
|
16
16
|
const {
|
|
17
|
+
MCP_DEPENDENT_SKILLS,
|
|
17
18
|
skillsForRuntime,
|
|
18
19
|
WORKFLOW_HOOK_FILES,
|
|
19
20
|
} = require('../_shared/runtime-assets.cjs');
|
|
@@ -40,6 +41,7 @@ const AGENTS = [
|
|
|
40
41
|
'review-coordinator',
|
|
41
42
|
'review-design',
|
|
42
43
|
'review-errors',
|
|
44
|
+
'review-spec',
|
|
43
45
|
'review-tests',
|
|
44
46
|
'tdd-runner',
|
|
45
47
|
];
|
|
@@ -122,12 +124,22 @@ test('every generated skill is Codex-valid, UI-visible, and free of Claude host
|
|
|
122
124
|
/~\/\.claude|CLAUDE\.md|AskUserQuestion|(^|[\s`(>])\/(?:ultra-[a-z-]+|recall|learn|codex-collab)(?=$|[\s`,.;):])/m,
|
|
123
125
|
);
|
|
124
126
|
assert.doesNotMatch(text, /TaskCreate|TaskUpdate|TaskList|TaskOutput|run_in_background|~\/\.codex\/skills|mcp__claude/);
|
|
127
|
+
assert.doesNotMatch(text, /[\u3400-\u9fff]|ultra-review-findings-v1|Context7|Exa MCP|confidence\s*>=?\s*\d+/iu);
|
|
125
128
|
|
|
126
129
|
const openai = yaml.load(fs.readFileSync(path.join(skillDir, 'agents', 'openai.yaml'), 'utf8'));
|
|
127
130
|
assert.ok(openai.interface.display_name);
|
|
128
131
|
assert.ok(openai.interface.short_description);
|
|
129
132
|
assert.match(openai.interface.default_prompt, new RegExp(`\\$ultra-builder-pro:${name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`));
|
|
130
|
-
assert.equal(
|
|
133
|
+
assert.equal(openai.policy.allow_implicit_invocation, false);
|
|
134
|
+
if (MCP_DEPENDENT_SKILLS.includes(name)) {
|
|
135
|
+
assert.deepEqual(openai.dependencies.tools, [{
|
|
136
|
+
type: 'mcp',
|
|
137
|
+
value: 'ultra-builder-pro',
|
|
138
|
+
description: 'Ultra Builder Pro authoritative task and change runtime',
|
|
139
|
+
}]);
|
|
140
|
+
} else {
|
|
141
|
+
assert.equal(openai.dependencies, undefined);
|
|
142
|
+
}
|
|
131
143
|
}
|
|
132
144
|
|
|
133
145
|
const cc = fs.readFileSync(path.join(layout.pluginRoot, 'skills', 'cc-collab', 'SKILL.md'), 'utf8');
|
|
@@ -146,13 +158,14 @@ test('every generated skill is Codex-valid, UI-visible, and free of Claude host
|
|
|
146
158
|
|
|
147
159
|
const review = fs.readFileSync(path.join(layout.pluginRoot, 'skills', 'ultra-review', 'SKILL.md'), 'utf8');
|
|
148
160
|
assert.match(review, /native Codex custom agents/);
|
|
149
|
-
assert.match(review,
|
|
161
|
+
assert.match(review, /review-spec/);
|
|
162
|
+
assert.match(review, /scripts\/review_wait\.py/);
|
|
150
163
|
|
|
151
164
|
const plan = fs.readFileSync(path.join(layout.pluginRoot, 'skills', 'ultra-plan', 'SKILL.md'), 'utf8');
|
|
152
165
|
const status = fs.readFileSync(path.join(layout.pluginRoot, 'skills', 'ultra-status', 'SKILL.md'), 'utf8');
|
|
153
|
-
assert.
|
|
154
|
-
assert.match(plan, /Never read or
|
|
155
|
-
assert.match(status, /Never fall
|
|
166
|
+
assert.doesNotMatch(plan, /LEGACY_STATE_MIGRATION_REQUIRED|v4\.4|v4\.5/);
|
|
167
|
+
assert.match(plan, /Never read or\s+write .*tasks\.json/i);
|
|
168
|
+
assert.match(status, /Never fall\s+back to generated task JSON/i);
|
|
156
169
|
|
|
157
170
|
const coreWorkflowText = [
|
|
158
171
|
...COMMANDS,
|
|
@@ -168,7 +181,7 @@ test('every generated skill is Codex-valid, UI-visible, and free of Claude host
|
|
|
168
181
|
}
|
|
169
182
|
});
|
|
170
183
|
|
|
171
|
-
test('install converts all
|
|
184
|
+
test('install converts all bundled agent prompts into native Codex agent TOML', () => {
|
|
172
185
|
const layout = mkLayout();
|
|
173
186
|
try {
|
|
174
187
|
const report = install(layout);
|
|
@@ -198,9 +211,10 @@ test('plugin declares current Codex hooks and a project-local Ultra MCP server',
|
|
|
198
211
|
'SubagentStart', 'SubagentStop',
|
|
199
212
|
].sort());
|
|
200
213
|
const serializedHooks = JSON.stringify(hooks);
|
|
201
|
-
for (const feature of WORKFLOW_HOOK_FILES) {
|
|
214
|
+
for (const feature of WORKFLOW_HOOK_FILES.filter((value) => value !== 'context_spine.py')) {
|
|
202
215
|
assert.match(serializedHooks, new RegExp(feature.replace('.', '\\.')));
|
|
203
216
|
}
|
|
217
|
+
assert.ok(fs.existsSync(path.join(layout.pluginRoot, 'hooks', 'context_spine.py')));
|
|
204
218
|
assert.doesNotMatch(serializedHooks, /memory|recall|journal|observation_capture|user_prompt_capture|block_dangerous|post_edit_guard/);
|
|
205
219
|
assert.match(serializedHooks, /\$PLUGIN_ROOT\/hooks\/adapters\/codex\.py/);
|
|
206
220
|
assert.match(serializedHooks, /apply_patch/);
|
|
@@ -231,8 +245,8 @@ test('plugin declares current Codex hooks and a project-local Ultra MCP server',
|
|
|
231
245
|
const liveSpec = yaml.load(fs.readFileSync(path.join(layout.pluginRoot, 'spec', 'mcp-tools.yaml'), 'utf8'));
|
|
232
246
|
const upstreamSpec = yaml.load(fs.readFileSync(path.join(layout.pluginRoot, 'spec', 'upstream-mcp-tools.yaml'), 'utf8'));
|
|
233
247
|
const capabilityMap = JSON.parse(fs.readFileSync(path.join(layout.pluginRoot, 'spec', 'codex-capability-map.json'), 'utf8'));
|
|
234
|
-
assert.equal(liveSpec.tools.length,
|
|
235
|
-
assert.equal(upstreamSpec.tools.length,
|
|
248
|
+
assert.equal(liveSpec.tools.length, 32);
|
|
249
|
+
assert.equal(upstreamSpec.tools.length, 32);
|
|
236
250
|
assert.deepEqual(upstreamSpec.tools.map((tool) => tool.name).sort(), liveSpec.tools.map((tool) => tool.name).sort());
|
|
237
251
|
assert.deepEqual(capabilityMap.live_mcp_tools.sort(), liveSpec.tools.map((tool) => tool.name).sort());
|
|
238
252
|
assert.equal(Object.keys(capabilityMap.codex_native_replacements).length, 9);
|