ultra-builder-pro-cli 0.10.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.ultra-template/specs/architecture.md +54 -223
- package/.ultra-template/specs/discovery.md +34 -231
- package/.ultra-template/specs/product.md +44 -200
- package/.ultra-template/tasks/contexts/TEMPLATE.md +36 -0
- package/.ultra-template/tasks/tasks.json +6 -0
- package/CHANGELOG.md +86 -1
- package/README.md +21 -9
- package/adapters/_shared/codex-assets.cjs +24 -342
- package/adapters/_shared/handbook.cjs +4 -3
- package/adapters/_shared/runtime-assets.cjs +24 -0
- package/adapters/_shared/tests/handbook.test.cjs +4 -0
- package/adapters/_shared/tests/runtime-assets.test.cjs +11 -4
- package/adapters/claude.js +28 -5
- package/adapters/kimi.js +15 -138
- package/adapters/opencode.js +22 -49
- package/adapters/tests/claude.test.cjs +22 -8
- package/adapters/tests/codex.test.cjs +21 -8
- package/adapters/tests/kimi-hook.test.cjs +1 -1
- package/adapters/tests/kimi.test.cjs +11 -11
- package/adapters/tests/opencode.test.cjs +47 -8
- 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 +16 -8
- package/docs/ARCHITECTURE.md +32 -5
- package/docs/PLAN.zh-CN.md +8 -6
- package/docs/ROADMAP.md +5 -3
- package/docs/RUNTIME-COMPAT-MATRIX.md +8 -5
- package/docs/SKILL-AUTHORING.md +75 -0
- package/docs/STATE-DB-ACCESS-POLICY.md +17 -11
- package/docs/USER-HANDBOOK-CONTRACT.md +8 -5
- package/hooks/README.md +4 -3
- package/hooks/adapters/codex.py +1 -1
- package/hooks/adapters/kimi.py +1 -1
- package/hooks/context_spine.py +120 -16
- package/hooks/health_check.py +11 -0
- package/hooks/pre_stop_check.py +6 -5
- package/mcp-server/lib/baseline-workflow.cjs +415 -0
- package/mcp-server/lib/baseline-workflow.test.cjs +225 -0
- package/mcp-server/lib/change-workflow.cjs +24 -27
- package/mcp-server/lib/change-workflow.test.cjs +202 -6
- package/mcp-server/lib/context-spine.cjs +74 -9
- package/mcp-server/lib/context-spine.test.cjs +15 -0
- package/mcp-server/lib/doctor.cjs +14 -0
- package/mcp-server/lib/doctor.test.cjs +25 -1
- package/mcp-server/lib/init-project.cjs +49 -14
- package/mcp-server/lib/provider-refs.cjs +42 -0
- package/mcp-server/lib/spec-learning.test.cjs +5 -0
- package/mcp-server/lib/state-db.cjs +63 -4
- package/mcp-server/lib/state-db.test.cjs +8 -3
- package/mcp-server/server.cjs +48 -3
- package/mcp-server/tests/init-project.test.cjs +54 -8
- package/mcp-server/tests/server-contract.test.cjs +99 -2
- package/mcp-server/tests/state-db.test.cjs +11 -2
- 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 -106
- package/skills/security-rules/SKILL.md +34 -56
- package/skills/testing-rules/SKILL.md +28 -52
- package/skills/ultra-change/SKILL.md +68 -114
- package/skills/ultra-deliver/SKILL.md +58 -86
- package/skills/ultra-dev/SKILL.md +46 -105
- package/skills/ultra-doctor/SKILL.md +45 -72
- package/skills/ultra-init/SKILL.md +62 -147
- package/skills/ultra-init/references/brownfield-adoption.md +65 -0
- package/skills/ultra-plan/SKILL.md +56 -89
- package/skills/ultra-research/SKILL.md +64 -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 +54 -98
- 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 +41 -56
- package/skills/ultra-test/SKILL.md +37 -72
- package/skills/ultra-think/SKILL.md +43 -102
- package/skills/ultra-verify/SKILL.md +44 -84
- package/skills/ultra-verify/evals/evals.json +24 -27
- package/spec/cli-protocol.md +4 -0
- package/spec/mcp-tools.yaml +223 -4
- package/spec/schemas/command-manifest.schema.json +7 -25
- package/spec/schemas/mcp-tools.schema.json +1 -1
- package/spec/schemas/skill-manifest.schema.json +9 -79
- package/spec/schemas/state-db.sql +33 -2
- package/ultra-tools/commands/status.cjs +20 -2
- package/ultra-tools/commands/task.cjs +4 -1
- package/ultra-tools/status-command.test.cjs +20 -0
- package/ultra-tools/system-command.test.cjs +5 -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
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
|
-
/#{2,3} Phase 3:[\s\S]*?#{2,3} 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
|
}
|
|
@@ -378,11 +345,14 @@ function findUltraContext(directory) {
|
|
|
378
345
|
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
379
346
|
if (manifest?.change) {
|
|
380
347
|
const compiledHead = manifest.git?.head ?? null;
|
|
381
|
-
const legacyContext = manifest.schema_version !== "2.0";
|
|
348
|
+
const legacyContext = manifest.schema_version !== "2.0" || !manifest.baseline;
|
|
382
349
|
const headStale = Boolean(compiledHead && currentHead && compiledHead !== currentHead);
|
|
383
350
|
const blockers = Array.isArray(manifest.readiness?.blockers)
|
|
384
351
|
? [...manifest.readiness.blockers]
|
|
385
352
|
: [];
|
|
353
|
+
const warnings = Array.isArray(manifest.readiness?.warnings)
|
|
354
|
+
? [...manifest.readiness.warnings]
|
|
355
|
+
: [];
|
|
386
356
|
if (headStale && !blockers.includes("CONTEXT_HEAD_STALE")) blockers.push("CONTEXT_HEAD_STALE");
|
|
387
357
|
if (legacyContext && !blockers.includes("CONTEXT_SNAPSHOT_UPGRADE_REQUIRED")) {
|
|
388
358
|
blockers.unshift("CONTEXT_SNAPSHOT_UPGRADE_REQUIRED");
|
|
@@ -400,6 +370,8 @@ function findUltraContext(directory) {
|
|
|
400
370
|
: (manifest.next_action ?? "Inspect Ultra status."),
|
|
401
371
|
readiness: legacyContext || headStale ? "blocked" : (manifest.readiness?.status ?? "ready"),
|
|
402
372
|
blockers,
|
|
373
|
+
warnings,
|
|
374
|
+
baseline: manifest.baseline ?? null,
|
|
403
375
|
taskId: manifest.selected_task?.id ?? manifest.resume?.task_id ?? null,
|
|
404
376
|
taskStatus: manifest.selected_task?.status ?? manifest.resume?.task_status ?? null,
|
|
405
377
|
tokenEstimate: manifest.context?.token_estimate ?? 0,
|
|
@@ -444,6 +416,7 @@ function contextText(active) {
|
|
|
444
416
|
\`Gate: \${change.gate}\`,
|
|
445
417
|
\`Readiness: \${change.readiness}\`,
|
|
446
418
|
...(change.blockers.length > 0 ? [\`Blockers: \${change.blockers.join(", ")}\`] : []),
|
|
419
|
+
...(change.warnings.length > 0 ? [\`Warnings: \${change.warnings.join(", ")}\`] : []),
|
|
447
420
|
\`Next: \${change.nextAction}\`,
|
|
448
421
|
\`Context manifest: \${change.manifestPath}\`,
|
|
449
422
|
);
|
|
@@ -455,7 +428,7 @@ function contextText(active) {
|
|
|
455
428
|
lines.push(
|
|
456
429
|
"[Ultra baseline]",
|
|
457
430
|
\`Project: \${active.root}\`,
|
|
458
|
-
"No active continuous change.
|
|
431
|
+
"No active continuous change. Run ultra-status; resume ultra-init when the baseline is missing, incomplete, or stale, otherwise start ultra-change.",
|
|
459
432
|
);
|
|
460
433
|
}
|
|
461
434
|
lines.push("Authority: .ultra/state.db; JSON/Markdown remain projections or evidence artifacts.");
|
|
@@ -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
|
|
|
@@ -60,17 +65,26 @@ test('Claude plugin collaboration and learn workflows are safe native plugin ass
|
|
|
60
65
|
const plan = read('ultra-plan');
|
|
61
66
|
const status = read('ultra-status');
|
|
62
67
|
|
|
63
|
-
assert.match(learn,
|
|
64
|
-
assert.doesNotMatch(learn, /_unverified
|
|
68
|
+
assert.match(learn, /`~\/.claude\/skills`/);
|
|
69
|
+
assert.doesNotMatch(learn, /_unverified|learned-[^\s/]*-unverified/i);
|
|
65
70
|
assert.match(codexCollab, /-s read-only/);
|
|
71
|
+
assert.match(codexCollab, /--ephemeral/);
|
|
72
|
+
assert.match(codexCollab, /--ignore-user-config/);
|
|
66
73
|
assert.match(verify, /-s read-only/);
|
|
67
|
-
assert.match(verify,
|
|
68
|
-
assert.match(
|
|
69
|
-
assert.match(
|
|
70
|
-
assert.match(
|
|
71
|
-
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);
|
|
72
81
|
assert.doesNotMatch(plan, /ultra-tools task create/);
|
|
73
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
|
+
|
|
74
88
|
for (const [name, contents] of Object.entries({ codexCollab, verify })) {
|
|
75
89
|
assert.doesNotMatch(contents, /--yolo|--full-auto|\/codex:/, name);
|
|
76
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);
|
|
@@ -232,8 +245,8 @@ test('plugin declares current Codex hooks and a project-local Ultra MCP server',
|
|
|
232
245
|
const liveSpec = yaml.load(fs.readFileSync(path.join(layout.pluginRoot, 'spec', 'mcp-tools.yaml'), 'utf8'));
|
|
233
246
|
const upstreamSpec = yaml.load(fs.readFileSync(path.join(layout.pluginRoot, 'spec', 'upstream-mcp-tools.yaml'), 'utf8'));
|
|
234
247
|
const capabilityMap = JSON.parse(fs.readFileSync(path.join(layout.pluginRoot, 'spec', 'codex-capability-map.json'), 'utf8'));
|
|
235
|
-
assert.equal(liveSpec.tools.length,
|
|
236
|
-
assert.equal(upstreamSpec.tools.length,
|
|
248
|
+
assert.equal(liveSpec.tools.length, 36);
|
|
249
|
+
assert.equal(upstreamSpec.tools.length, 36);
|
|
237
250
|
assert.deepEqual(upstreamSpec.tools.map((tool) => tool.name).sort(), liveSpec.tools.map((tool) => tool.name).sort());
|
|
238
251
|
assert.deepEqual(capabilityMap.live_mcp_tools.sort(), liveSpec.tools.map((tool) => tool.name).sort());
|
|
239
252
|
assert.equal(Object.keys(capabilityMap.codex_native_replacements).length, 9);
|
|
@@ -97,7 +97,7 @@ test('Kimi subagent fields are normalized for the existing lifecycle tracker', (
|
|
|
97
97
|
});
|
|
98
98
|
});
|
|
99
99
|
|
|
100
|
-
test('Kimi
|
|
100
|
+
test('Kimi adapter preserves an explicit Stop deny and recovery context schema', () => {
|
|
101
101
|
const script = [
|
|
102
102
|
'import importlib.util, json, pathlib',
|
|
103
103
|
`p = pathlib.Path(${JSON.stringify(ADAPTER)})`,
|
|
@@ -121,20 +121,19 @@ test('Kimi assets are allowlisted and adapted to native tools and paths', () =>
|
|
|
121
121
|
path.join(pluginRoot, 'skills', 'using-ultra-builder-pro', 'SKILL.md'),
|
|
122
122
|
'utf8',
|
|
123
123
|
);
|
|
124
|
-
assert.match(learn,
|
|
124
|
+
assert.match(learn, /`~\/.kimi-code\/skills`/);
|
|
125
|
+
assert.doesNotMatch(learn, /_unverified|learned-[^\s/]*-unverified/i);
|
|
125
126
|
assert.match(review, /Kimi `AgentSwarm`/);
|
|
126
|
-
assert.match(review,
|
|
127
|
-
assert.match(review, /
|
|
128
|
-
assert.
|
|
129
|
-
assert.match(review,
|
|
130
|
-
assert.
|
|
131
|
-
assert.match(
|
|
132
|
-
assert.
|
|
133
|
-
assert.doesNotMatch(review, /background mode|Set Kimi `AgentSwarm` parallel execution|Launch review-coordinator with Kimi `AgentSwarm`/);
|
|
134
|
-
assert.match(init, /\| Kimi \| `\/ultra-builder-pro:ultra-init/);
|
|
135
|
-
assert.doesNotMatch(init, /codex exec "run \/ultra-init/);
|
|
127
|
+
assert.match(review, /\$KIMI_PLUGIN_ROOT\/agents\//);
|
|
128
|
+
assert.match(review, /scripts\/review_wait\.py/);
|
|
129
|
+
assert.match(review, /spec_fidelity/);
|
|
130
|
+
assert.match(review, /engineering_standards/);
|
|
131
|
+
assert.doesNotMatch(review, /background mode|run_in_background/);
|
|
132
|
+
assert.match(init, /task\.init_project/);
|
|
133
|
+
assert.doesNotMatch(init, /Claude Code|OpenCode|Codex/);
|
|
136
134
|
assert.match(bootstrap, /\.ultra\/state\.db/);
|
|
137
135
|
assert.match(bootstrap, /external providers/);
|
|
136
|
+
assert.match(fs.readFileSync(path.join(pluginRoot, 'skills', 'codex-collab', 'SKILL.md'), 'utf8'), /--ephemeral/);
|
|
138
137
|
|
|
139
138
|
const allPromptAssets = [];
|
|
140
139
|
for (const root of ['commands', 'skills', 'agents']) {
|
|
@@ -153,6 +152,7 @@ test('Kimi assets are allowlisted and adapted to native tools and paths', () =>
|
|
|
153
152
|
const text = fs.readFileSync(file, 'utf8');
|
|
154
153
|
assert.doesNotMatch(text, foreign, file);
|
|
155
154
|
assert.doesNotMatch(text, /(^|[\s`("'])\/ultra-(?!builder-pro:)[a-z]/m, file);
|
|
155
|
+
assert.doesNotMatch(text, /[\u3400-\u9fff]|ultra-review-findings-v1|Context7|Exa MCP|confidence\s*>=?\s*\d+/iu, file);
|
|
156
156
|
}
|
|
157
157
|
} finally {
|
|
158
158
|
fs.rmSync(home, { recursive: true, force: true });
|
|
@@ -64,15 +64,17 @@ test('install performs content-level OpenCode adaptation for commands, skills, r
|
|
|
64
64
|
const verify = fs.readFileSync(path.join(target, 'skills', 'ultra-verify', 'SKILL.md'), 'utf8');
|
|
65
65
|
|
|
66
66
|
assert.deepEqual(Object.keys(parseFm(plan).fm), ['name', 'description']);
|
|
67
|
-
assert.match(learn,
|
|
68
|
-
assert.doesNotMatch(learn, /_unverified
|
|
69
|
-
assert.match(review,
|
|
67
|
+
assert.match(learn, /`~\/.config\/opencode\/skills`/);
|
|
68
|
+
assert.doesNotMatch(learn, /_unverified|learned-[^\s/]*-unverified/i);
|
|
69
|
+
assert.match(review, /OpenCode `task` tool/);
|
|
70
|
+
assert.match(review, /scripts\/review_wait\.py/);
|
|
70
71
|
assert.match(codexCollab, /OpenCode remains primary/);
|
|
71
|
-
assert.match(verify, /OpenCode
|
|
72
|
+
assert.match(verify, /OpenCode writes the first analysis/);
|
|
72
73
|
assert.match(verify, /opencode-analysis\.md/);
|
|
73
74
|
assert.match(verify, /codex exec -s read-only/);
|
|
74
|
-
assert.match(
|
|
75
|
-
assert.
|
|
75
|
+
assert.match(verify, /--ignore-user-config/);
|
|
76
|
+
assert.doesNotMatch(plan, /LEGACY_STATE_MIGRATION_REQUIRED|v4\.4|v4\.5/);
|
|
77
|
+
assert.match(plan, /never read or\s+write .*tasks\.json/i);
|
|
76
78
|
assert.doesNotMatch(plan, /ultra-tools task create/);
|
|
77
79
|
|
|
78
80
|
const markdown = [];
|
|
@@ -89,7 +91,9 @@ test('install performs content-level OpenCode adaptation for commands, skills, r
|
|
|
89
91
|
}
|
|
90
92
|
const incompatible = /~\/.claude|CLAUDE\.md|AskUserQuestion|TaskCreate|TaskUpdate|TaskList|run_in_background:\s*true|--yolo|--full-auto|\/codex:|ask\.question|review\.run/;
|
|
91
93
|
for (const file of markdown) {
|
|
92
|
-
|
|
94
|
+
const text = fs.readFileSync(file, 'utf8');
|
|
95
|
+
assert.doesNotMatch(text, incompatible, file);
|
|
96
|
+
assert.doesNotMatch(text, /[\u3400-\u9fff]|ultra-review-findings-v1|Context7|Exa MCP|confidence\s*>=?\s*\d+/iu, file);
|
|
93
97
|
}
|
|
94
98
|
} finally {
|
|
95
99
|
fs.rmSync(target, { recursive: true, force: true });
|
|
@@ -202,11 +206,14 @@ test('OpenCode context covers active changes and projection writes are blocked f
|
|
|
202
206
|
fs.mkdirSync(changeRoot, { recursive: true });
|
|
203
207
|
fs.writeFileSync(path.join(changeRoot, 'context-manifest.json'), JSON.stringify({
|
|
204
208
|
schema_version: '2.0',
|
|
209
|
+
baseline: { id: 'project-baseline', mode: 'brownfield', status: 'ready', repository_revision: compiledHead },
|
|
205
210
|
change: { id: 'daily-fix', title: 'Daily fix', kind: 'quick', status: 'active', intent: 'Fix drift' },
|
|
206
211
|
role: 'implement',
|
|
207
212
|
gate: 'implementation',
|
|
208
213
|
next_action: 'Run the exact regression test.',
|
|
209
|
-
readiness: {
|
|
214
|
+
readiness: {
|
|
215
|
+
status: 'ready', blockers: [], warnings: ['CONTEXT_FILE_BUDGET_EXCEEDED'],
|
|
216
|
+
},
|
|
210
217
|
git: { head: compiledHead },
|
|
211
218
|
selected_task: { id: 'daily-task', status: 'in_progress' },
|
|
212
219
|
context: { token_estimate: 1200, budget: { max_tokens: 8000, max_files: 8 } },
|
|
@@ -221,6 +228,7 @@ test('OpenCode context covers active changes and projection writes are blocked f
|
|
|
221
228
|
assert.match(output.system.join('\n'), /Role: implement/);
|
|
222
229
|
assert.match(output.system.join('\n'), /Gate: implementation/);
|
|
223
230
|
assert.match(output.system.join('\n'), /Readiness: ready/);
|
|
231
|
+
assert.match(output.system.join('\n'), /Warnings: CONTEXT_FILE_BUDGET_EXCEEDED/);
|
|
224
232
|
assert.match(output.system.join('\n'), /Run the exact regression test/);
|
|
225
233
|
assert.doesNotMatch(output.system.join('\n'), /Fix drift|cloud-mem/);
|
|
226
234
|
|
|
@@ -234,6 +242,16 @@ test('OpenCode context covers active changes and projection writes are blocked f
|
|
|
234
242
|
legacyManifest.schema_version = '2.0';
|
|
235
243
|
fs.writeFileSync(manifestFile, JSON.stringify(legacyManifest));
|
|
236
244
|
|
|
245
|
+
delete legacyManifest.baseline;
|
|
246
|
+
fs.writeFileSync(manifestFile, JSON.stringify(legacyManifest));
|
|
247
|
+
const missingBaselineOutput = { system: [] };
|
|
248
|
+
await plugin['experimental.chat.system.transform']({}, missingBaselineOutput);
|
|
249
|
+
assert.match(missingBaselineOutput.system.join('\n'), /CONTEXT_SNAPSHOT_UPGRADE_REQUIRED/);
|
|
250
|
+
legacyManifest.baseline = {
|
|
251
|
+
id: 'project-baseline', mode: 'brownfield', status: 'ready', repository_revision: compiledHead,
|
|
252
|
+
};
|
|
253
|
+
fs.writeFileSync(manifestFile, JSON.stringify(legacyManifest));
|
|
254
|
+
|
|
237
255
|
fs.writeFileSync(path.join(project, 'app.txt'), 'changed\n');
|
|
238
256
|
execFileSync('git', ['add', 'app.txt'], { cwd: project, stdio: 'ignore' });
|
|
239
257
|
execFileSync('git', ['-c', 'user.name=Ultra Tests', '-c', 'user.email=ultra@example.invalid', 'commit', '-m', 'change'], {
|
|
@@ -258,6 +276,27 @@ test('OpenCode context covers active changes and projection writes are blocked f
|
|
|
258
276
|
}
|
|
259
277
|
});
|
|
260
278
|
|
|
279
|
+
test('OpenCode does not assume a project baseline is ready when no active change manifest exists', async () => {
|
|
280
|
+
const target = mkTarget();
|
|
281
|
+
const project = mkTarget();
|
|
282
|
+
try {
|
|
283
|
+
opencode.install({ configDir: target, repoRoot: REPO_ROOT });
|
|
284
|
+
fs.mkdirSync(path.join(project, '.ultra'), { recursive: true });
|
|
285
|
+
fs.writeFileSync(path.join(project, '.ultra', 'state.db'), 'owned-by-ultra');
|
|
286
|
+
const module = await import(`${pathToFileURL(path.join(target, 'plugins', 'ultra-builder-pro.js')).href}?baseline`);
|
|
287
|
+
const plugin = await module.UltraBuilderProPlugin({ directory: project });
|
|
288
|
+
const output = { system: [] };
|
|
289
|
+
await plugin['experimental.chat.system.transform']({}, output);
|
|
290
|
+
const text = output.system.join('\n');
|
|
291
|
+
assert.match(text, /ultra-status/);
|
|
292
|
+
assert.match(text, /ultra-init/);
|
|
293
|
+
assert.doesNotMatch(text, /Start daily work with the ultra-change workflow/);
|
|
294
|
+
} finally {
|
|
295
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
296
|
+
fs.rmSync(project, { recursive: true, force: true });
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
|
|
261
300
|
test('install preserves user mcp entries; uninstall removes only owned Ultra assets', () => {
|
|
262
301
|
const target = mkTarget();
|
|
263
302
|
const configFile = path.join(target, 'opencode.json');
|