Browse Source

chore: prepare 2.0.6 release

Zerdeşt Taifour 1 month ago
parent
commit
482db66c3d

+ 3 - 1
README.md

@@ -198,6 +198,8 @@ verification while specialists do the work in their own lanes.
 - **[oh-my-opencode-slim skill](#oh-my-opencode-slim-skill)** — a bundled
   configuration skill that helps tune models, prompts, custom agents, MCP access,
   presets, and plugin behavior safely.
+- **Release Smoke Test** — validates packed release candidates and bugfixes
+  before public publish with isolated OpenCode runtime smokes.
 
 #### Background Agents
 
@@ -662,7 +664,7 @@ Use this section as a map: start with installation, then jump to features, confi
 | **[Configuration](docs/configuration.md)** | Config file locations, JSONC support, prompt overrides, and full option reference |
 | **[Background Orchestration](docs/background-orchestration.md)** | Scheduler-first orchestrator model built around native background subagents |
 | **[Maintainer Guide](docs/maintainers.md)** | Issue triage rules, label meanings, support routing, and repo maintenance workflow |
-| **[Skills](docs/skills.md)** | Bundled skills such as `simplify`, `codemap`, `clonedeps`, `deepwork`, `reflect`, `worktrees`, and `oh-my-opencode-slim` |
+| **[Skills](docs/skills.md)** | Bundled skills such as `simplify`, `codemap`, `clonedeps`, `deepwork`, `reflect`, `worktrees`, `release-smoke-test`, and `oh-my-opencode-slim` |
 | **[MCPs](docs/mcps.md)** | `websearch`, `context7`, `gh_grep`, and how MCP permissions work per agent |
 | **[Tools](docs/tools.md)** | Built-in tool capabilities like `webfetch`, LSP tools, code search, and formatters |
 

+ 27 - 0
docs/skills.md

@@ -18,6 +18,7 @@ Bundled skills are installed by the `oh-my-opencode-slim` installer.
 | [`deepwork`](#deepwork) | Heavy/complex coding sessions workflow | `orchestrator` |
 | [`reflect`](#reflect) | Review repeated work and suggest reusable workflow improvements | `orchestrator` |
 | [`worktrees`](#worktrees) | Safe Git worktree lane management | `orchestrator` |
+| [`release-smoke-test`](#release-smoke-test) | Packed release-candidate and bugfix smoke validation | `orchestrator` |
 | [`oh-my-opencode-slim`](#oh-my-opencode-slim) | Plugin configuration and self-improvement guidance | `orchestrator` |
 
 ---
@@ -210,6 +211,32 @@ This should apply on the next OpenCode run; restart OpenCode if you need it imme
 
 ---
 
+## release-smoke-test
+
+**Validate packed release candidates and bugfixes before public publish.**
+
+`release-smoke-test` is an orchestrator-only skill for proving a release branch
+works as an installed package artifact. It builds and packs the candidate,
+installs the tarball into a throwaway app, runs OpenCode with a sanitized
+temporary config, verifies the active `plugin_origins`, and searches isolated
+logs for the crash signature being fixed.
+
+Use it for release hardening, runtime compatibility checks, and model-specific
+smokes such as OpenCode 1.17.11 malformed message transform regressions.
+
+Typical request:
+
+```text
+Use release-smoke-test to validate this release candidate before npm publish.
+```
+
+The skill distinguishes fully isolated smokes from host-provider smokes. If a
+model such as GPT-5.5 Fast needs provider aliases from the current machine, the
+skill records that limitation instead of treating it as equivalent to a clean
+`env -i` smoke.
+
+---
+
 ## Skills Assignment
 
 Control which skills each agent can use in `~/.config/opencode/oh-my-opencode-slim.json` (or `.jsonc`):

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "oh-my-opencode-slim",
-  "version": "2.0.5",
+  "version": "2.0.6",
   "description": "Lightweight agent orchestration plugin for OpenCode - a slimmed-down fork of oh-my-opencode",
   "main": "dist/index.js",
   "types": "dist/index.d.ts",

+ 1 - 0
scripts/verify-release-artifact.ts

@@ -37,6 +37,7 @@ const packagedRequiredFiles = [
   'src/skills/deepwork/SKILL.md',
   'src/skills/reflect/SKILL.md',
   'src/skills/oh-my-opencode-slim/SKILL.md',
+  'src/skills/release-smoke-test/SKILL.md',
   'src/skills/worktrees/SKILL.md',
 ];
 

+ 1 - 0
src/agents/index.test.ts

@@ -296,6 +296,7 @@ describe('skill permissions', () => {
     // CUSTOM_SKILLS loop must also add a named codemap entry for orchestrator
     expect(skillPerm?.codemap).toBe('allow');
     expect(skillPerm?.clonedeps).toBe('allow');
+    expect(skillPerm?.['release-smoke-test']).toBe('allow');
   });
 
   test('fixer does not get codemap skill allowed by default', () => {

+ 7 - 0
src/cli/custom-skills.ts

@@ -67,6 +67,13 @@ export const CUSTOM_SKILLS: CustomSkill[] = [
     allowedAgents: ['orchestrator'],
     sourcePath: 'src/skills/oh-my-opencode-slim',
   },
+  {
+    name: 'release-smoke-test',
+    description:
+      'Validate packed release candidates and bugfixes before public publish',
+    allowedAgents: ['orchestrator'],
+    sourcePath: 'src/skills/release-smoke-test',
+  },
   {
     name: 'worktrees',
     description:

+ 1 - 0
src/cli/skills.test.ts

@@ -25,6 +25,7 @@ describe('skills permissions', () => {
     expect(orchestratorPerms.clonedeps).toBe('allow');
     expect(orchestratorPerms.deepwork).toBe('allow');
     expect(orchestratorPerms.reflect).toBe('allow');
+    expect(orchestratorPerms['release-smoke-test']).toBe('allow');
     expect(orchestratorPerms.worktrees).toBe('allow');
     expect(orchestratorPerms['oh-my-opencode-slim']).toBe('allow');
   });

+ 3 - 3
src/hooks/filter-available-skills/index.ts

@@ -119,9 +119,9 @@ export function createFilterAvailableSkillsHook(
       _input: Record<string, never>,
       output: { messages?: unknown },
     ): Promise<void> => {
-      const messages = (Array.isArray(output.messages) ? output.messages : []).filter(
-        isMessageWithParts,
-      );
+      const messages = (
+        Array.isArray(output.messages) ? output.messages : []
+      ).filter(isMessageWithParts);
       if (messages.length === 0) {
         return;
       }

+ 1 - 1
src/hooks/phase-reminder/index.ts

@@ -7,7 +7,7 @@
  */
 import { PHASE_REMINDER } from '../../config/constants';
 import { SLIM_INTERNAL_INITIATOR_MARKER } from '../../utils';
-import { isUserMessageWithParts, type MessageWithParts } from '../types';
+import { isUserMessageWithParts } from '../types';
 
 export { PHASE_REMINDER };
 

+ 3 - 1
src/hooks/task-session-manager/index.test.ts

@@ -55,7 +55,9 @@ describe('task-session-manager hook', () => {
     const messages = {
       messages: [
         {},
-        { info: { role: 'user', agent: 'orchestrator', sessionID: 'parent-1' } },
+        {
+          info: { role: 'user', agent: 'orchestrator', sessionID: 'parent-1' },
+        },
         { parts: [{ type: 'text', text: 'missing info' }] },
         {
           info: { role: 'assistant' },

+ 3 - 1
src/hooks/types.ts

@@ -25,7 +25,9 @@ export type MessageWithParts = {
   parts: MessagePart[];
 };
 
-export function isMessageWithParts(message: unknown): message is MessageWithParts {
+export function isMessageWithParts(
+  message: unknown,
+): message is MessageWithParts {
   if (!message || typeof message !== 'object') {
     return false;
   }

+ 7 - 12
src/index.ts

@@ -32,11 +32,7 @@ import {
   ForegroundFallbackManager,
 } from './hooks';
 import { processImageAttachments } from './hooks/image-hook';
-import {
-  isMessageWithParts,
-  isUserMessageWithParts,
-  type MessageWithParts,
-} from './hooks/types';
+import { isMessageWithParts, isUserMessageWithParts } from './hooks/types';
 import { createInterviewManager } from './interview';
 import { createBuiltinMcps } from './mcp';
 import {
@@ -1037,9 +1033,9 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
       input: Record<string, never>,
       output: { messages?: unknown },
     ): Promise<void> => {
-      const messages = (Array.isArray(output.messages) ? output.messages : []).filter(
-        isMessageWithParts,
-      );
+      const messages = (
+        Array.isArray(output.messages) ? output.messages : []
+      ).filter(isMessageWithParts);
 
       for (const message of messages) {
         if (!isUserMessageWithParts(message)) {
@@ -1069,10 +1065,9 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
         input,
         { messages },
       );
-      await phaseReminderHook['experimental.chat.messages.transform'](
-        input,
-        { messages },
-      );
+      await phaseReminderHook['experimental.chat.messages.transform'](input, {
+        messages,
+      });
       await filterAvailableSkillsHook['experimental.chat.messages.transform'](
         input,
         { messages },

+ 3 - 1
src/skills/codemap.md

@@ -22,6 +22,7 @@
   - `src/skills/reflect/` (orchestrator-only workflow for learning from repeated work and suggesting reusable improvements)
   - `src/skills/worktrees/` (orchestrator-only workflow for safe Git worktree lanes)
   - `src/skills/oh-my-opencode-slim/` (orchestrator-only plugin configuration and self-improvement guidance)
+  - `src/skills/release-smoke-test/` (orchestrator-only workflow for packed release-candidate runtime validation)
 - Files are considered static runtime payload. No plugin TS module in `src/` imports these files directly; they
   are loaded by OpenCode via filesystem installation.
 
@@ -44,6 +45,7 @@
   bundled skill payloads such as `src/skills/simplify/SKILL.md`,
   `src/skills/codemap/SKILL.md`, `src/skills/clonedeps/SKILL.md`, and
   `src/skills/deepwork/SKILL.md`, `src/skills/reflect/SKILL.md`,
-  `src/skills/worktrees/SKILL.md`, plus `src/skills/oh-my-opencode-slim/SKILL.md`,
+  `src/skills/worktrees/SKILL.md`, `src/skills/release-smoke-test/SKILL.md`,
+  plus `src/skills/oh-my-opencode-slim/SKILL.md`,
   are present in the tarball.
 - `package.json` scripts (`verify:release`, `build`) rely on these assets to ensure install-time skill availability.

+ 160 - 0
src/skills/release-smoke-test/SKILL.md

@@ -0,0 +1,160 @@
+---
+name: release-smoke-test
+description: Test an oh-my-opencode-slim release candidate or bugfix before publishing. Use when validating a packed plugin artifact, release branch, crash fix, OpenCode runtime compatibility, or model-specific smoke test such as OpenCode 1.17.11 message transform regressions.
+---
+
+# Release Smoke Test
+
+Use this skill to validate an `oh-my-opencode-slim` release candidate before
+public npm publish. Test the packed artifact, not `@latest` and not the source
+tree.
+
+## Core Workflow
+
+1. Start from the release-prep branch or commit.
+2. Build and pack the candidate.
+3. Install the tarball into a throwaway app.
+4. Create an isolated OpenCode config pointing at the installed
+   `node_modules/oh-my-opencode-slim/dist/index.js`.
+5. Run `opencode debug config` and verify `plugin_origins` contains only the
+   intended plugin when doing an isolation smoke.
+6. Run non-pure `opencode run --print-logs --log-level DEBUG`.
+7. Search isolated logs for the original crash signature.
+8. Record exact artifact, model, OpenCode version, command shape, result, and
+   limitations on the release issue or PR.
+
+## Pack Candidate
+
+Use a temp directory so release validation never depends on the local package
+cache.
+
+```bash
+SMOKE=/tmp/oh-my-opencode-slim-release-smoke
+rm -rf "$SMOKE"
+mkdir -p "$SMOKE/pkg" "$SMOKE/app" "$SMOKE/home" "$SMOKE/xdg/opencode" "$SMOKE/run"
+
+bun run build
+npm pack --pack-destination "$SMOKE/pkg"
+```
+
+Install the tarball:
+
+```bash
+cd "$SMOKE/app"
+bun init -y
+bun add "$SMOKE/pkg"/oh-my-opencode-slim-*.tgz
+node -p "require('./node_modules/oh-my-opencode-slim/package.json').version"
+```
+
+## Isolated Config
+
+Write the minimal OpenCode config:
+
+```bash
+cat > "$SMOKE/xdg/opencode/opencode.json" <<EOF
+{
+  "model": "opencode/deepseek-v4-flash-free",
+  "plugin": [
+    "file://$SMOKE/app/node_modules/oh-my-opencode-slim/dist/index.js"
+  ],
+  "agent": {
+    "orchestrator": {
+      "model": "opencode/deepseek-v4-flash-free"
+    }
+  }
+}
+EOF
+```
+
+Use `env -i` for the cleanest smoke. This strips host `OPENCODE_*`, `ORCA_*`,
+and project overlay variables that can silently add plugins or provider aliases.
+
+```bash
+env -i PATH="$PATH" HOME="$SMOKE/home" XDG_CONFIG_HOME="$SMOKE/xdg" \
+  opencode debug config
+```
+
+Confirm:
+
+- `plugin_origins` has exactly one entry.
+- That entry points to the temp app's packed `dist/index.js`.
+- The model is the one intended for the smoke.
+
+If OpenCode needs provider aliases from the host environment, run a second
+non-isolated model-specific smoke and clearly label it as weaker isolation.
+
+## Runtime Smoke
+
+Run the actual prompt with timeout:
+
+```bash
+env -i PATH="$PATH" HOME="$SMOKE/home" XDG_CONFIG_HOME="$SMOKE/xdg" \
+  timeout 120 \
+  opencode run --print-logs --log-level DEBUG "Say OK only."
+```
+
+Expected result:
+
+```text
+OK
+```
+
+Search logs for the bug signature. For the OpenCode 1.17.11 malformed-message
+crash, use:
+
+```bash
+rg "message\\.info\\.role|undefined is not an object|Cannot read properties of undefined|TypeError" \
+  "$SMOKE/home/.local/share/opencode/log" -n 2>/dev/null || true
+```
+
+No matches should appear.
+
+## OpenAI / Host-Provider Smoke
+
+If the fully isolated environment cannot resolve OpenAI provider aliases, run a
+separate host-provider smoke while keeping the plugin path pointed at the
+tarball install.
+
+```bash
+mkdir -p "$SMOKE/config"
+cat > "$SMOKE/config/opencode.json" <<EOF
+{
+  "model": "openai/gpt-5.5-fast",
+  "plugin": [
+    "file://$SMOKE/app/node_modules/oh-my-opencode-slim/dist/index.js"
+  ],
+  "agent": {
+    "orchestrator": {
+      "model": "openai/gpt-5.5-fast"
+    }
+  }
+}
+EOF
+
+OPENCODE_CONFIG_DIR="$SMOKE/config" \
+  timeout 120 \
+  opencode run --print-logs --log-level DEBUG "Say OK only."
+```
+
+Report this as a host-provider smoke because existing project, user, or Orca
+OpenCode config may still merge in. Use `opencode debug config` to disclose
+what else loaded.
+
+## Reporting Template
+
+```markdown
+## Release-candidate smoke validation
+
+- Commit under test:
+- Tarball:
+- Installed package version:
+- OpenCode version:
+- Config isolation: sanitized `env -i` / host-provider
+- Plugin origin:
+- Model:
+- Command:
+- Result:
+- Crash signature search:
+- Limitations:
+```
+