From bbf549327998efea7d5a2a12746a87ba5b8edf68 Mon Sep 17 00:00:00 2001 From: haelyra <49814733+haelyra@users.noreply.github.com> Date: Mon, 24 Aug 2026 21:16:39 -0400 Subject: [PATCH] fix(release): clear final review blockers --- .github/workflows/release.yml | 2 +- .github/workflows/reusable-release.yml | 12 +------- scripts/install-apply.js | 2 +- scripts/lib/harness-capabilities.js | 3 +- scripts/lib/install-targets/helpers.js | 3 ++ scripts/lib/install-targets/opencode-home.js | 2 ++ scripts/lib/install-targets/registry.js | 1 + scripts/lib/install/apply.js | 13 ++++++++ scripts/lib/install/claude-skill-migration.js | 2 +- scripts/lib/mcp-inventory/readers/opencode.js | 8 +++-- scripts/lib/opencode-paths.js | 30 +++++++++++++++++++ 11 files changed, 60 insertions(+), 18 deletions(-) create mode 100644 scripts/lib/opencode-paths.js diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3714a51f8..01dd257d7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -199,6 +199,6 @@ jobs: uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2 with: body_path: release_body.md - generate_release_notes: true + generate_release_notes: false prerelease: ${{ contains(github.ref_name, '-') }} make_latest: ${{ contains(github.ref_name, '-') && 'false' || 'true' }} diff --git a/.github/workflows/reusable-release.yml b/.github/workflows/reusable-release.yml index 5259a2b04..392ccfb09 100644 --- a/.github/workflows/reusable-release.yml +++ b/.github/workflows/reusable-release.yml @@ -7,11 +7,6 @@ on: description: 'Version tag (e.g., v1.0.0)' required: true type: string - generate-notes: - description: 'Auto-generate release notes' - required: false - type: boolean - default: true secrets: NPM_TOKEN: required: false @@ -21,11 +16,6 @@ on: description: 'Version tag to release or republish (e.g., v2.0.0-rc.1)' required: true type: string - generate-notes: - description: 'Auto-generate release notes' - required: false - type: boolean - default: true permissions: contents: read @@ -224,6 +214,6 @@ jobs: with: tag_name: ${{ inputs.tag }} body_path: release_body.md - generate_release_notes: ${{ inputs.generate-notes }} + generate_release_notes: false prerelease: ${{ contains(inputs.tag, '-') }} make_latest: ${{ contains(inputs.tag, '-') && 'false' || 'true' }} diff --git a/scripts/install-apply.js b/scripts/install-apply.js index 8d0c4cf12..26c5be1c4 100755 --- a/scripts/install-apply.js +++ b/scripts/install-apply.js @@ -39,7 +39,7 @@ Targets: antigravity - Install rules, workflows, skills, and agents to ./.agents/ codex - Install shared agents/config into ~/.codex/ gemini - Install project-local Gemini config into ./.gemini/ - opencode - Install shared commands/hooks/config into ~/.config/opencode/ + opencode - Install into OPENCODE_CONFIG_DIR, XDG_CONFIG_HOME/opencode, or ~/.config/opencode/ codebuddy - Install commands, agents, skills, and flattened rules into ./.codebuddy/ joycode - Install commands, agents, skills, and flattened rules into ./.joycode/ qwen - Install commands, agents, skills, rules, and Qwen config into ~/.qwen/ diff --git a/scripts/lib/harness-capabilities.js b/scripts/lib/harness-capabilities.js index 063fde694..2dd265a26 100644 --- a/scripts/lib/harness-capabilities.js +++ b/scripts/lib/harness-capabilities.js @@ -136,6 +136,7 @@ const HARNESS_CAPABILITIES = deepFreeze([ guidedReady: false, availability: 'advanced', destination: '~/.config/opencode', + destinationResolution: 'OPENCODE_CONFIG_DIR, then XDG_CONFIG_HOME/opencode, then ~/.config/opencode', scopes: [scope('home', 'opencode', '~/.config/opencode')], hooks: hooks( 'adapter-opt-in', @@ -249,7 +250,7 @@ for (const harness of HARNESS_CAPABILITIES) { function expectedRootForAdapter(adapter) { const homeDir = path.resolve('/__ecc_catalog_home__'); const projectRoot = path.resolve('/__ecc_catalog_project__'); - const absoluteRoot = adapter.resolveRoot({ homeDir, projectRoot }); + const absoluteRoot = adapter.resolveRoot({ homeDir, projectRoot, env: {} }); const baseRoot = adapter.kind === 'home' ? homeDir : projectRoot; const prefix = adapter.kind === 'home' ? '~/' : './'; return `${prefix}${path.relative(baseRoot, absoluteRoot).replace(/\\/g, '/')}`; diff --git a/scripts/lib/install-targets/helpers.js b/scripts/lib/install-targets/helpers.js index 39a0c38f6..cb8f05898 100644 --- a/scripts/lib/install-targets/helpers.js +++ b/scripts/lib/install-targets/helpers.js @@ -264,6 +264,9 @@ function createInstallTargetAdapter(config) { }, resolveRoot(input = {}) { const baseRoot = resolveBaseRoot(config.kind, input); + if (typeof config.resolveRoot === 'function') { + return config.resolveRoot(input, baseRoot); + } return path.join(baseRoot, ...config.rootSegments); }, getInstallStatePath(input = {}) { diff --git a/scripts/lib/install-targets/opencode-home.js b/scripts/lib/install-targets/opencode-home.js index 7fc289469..d25fdf7da 100644 --- a/scripts/lib/install-targets/opencode-home.js +++ b/scripts/lib/install-targets/opencode-home.js @@ -6,6 +6,7 @@ const { buildValidationIssue, createInstallTargetAdapter, } = require('./helpers'); +const { resolveOpencodeConfigRoot } = require('../opencode-paths'); const COMPILED_PLUGIN_DIST_DIR = path.join('.opencode', 'dist'); const REQUIRED_COMPILED_ARTEFACTS = Object.freeze([ @@ -84,6 +85,7 @@ module.exports = createInstallTargetAdapter({ target: 'opencode', kind: 'home', rootSegments: ['.config', 'opencode'], + resolveRoot: resolveOpencodeConfigRoot, installStatePathSegments: ['ecc-install-state.json'], nativeRootRelativePath: '.opencode', validate: defaultValidateOpencodeHome, diff --git a/scripts/lib/install-targets/registry.js b/scripts/lib/install-targets/registry.js index 3f07320a2..368e1cfe5 100644 --- a/scripts/lib/install-targets/registry.js +++ b/scripts/lib/install-targets/registry.js @@ -52,6 +52,7 @@ function planInstallTargetScaffold(options = {}) { repoRoot: options.repoRoot, projectRoot: options.projectRoot || options.repoRoot, homeDir: options.homeDir, + env: options.env || process.env, }; const validationIssues = adapter.validate(planningInput); const blockingIssues = validationIssues.filter(issue => ( diff --git a/scripts/lib/install/apply.js b/scripts/lib/install/apply.js index b33e94057..2ca0e45cc 100644 --- a/scripts/lib/install/apply.js +++ b/scripts/lib/install/apply.js @@ -120,12 +120,25 @@ function readInstalledFileNoFollow(plan, operation) { } function stateWithContentDigests(state, plan) { + const currentDestinations = new Set((plan.operations || []) + .filter(operation => operation.destinationPath) + .map(operation => { + const resolved = path.resolve(operation.destinationPath); + return process.platform === 'win32' ? resolved.toLowerCase() : resolved; + })); return { ...state, operations: (state.operations || []).map(operation => { if (!operation.destinationPath) { return { ...operation }; } + const resolved = path.resolve(operation.destinationPath); + const destinationKey = process.platform === 'win32' + ? resolved.toLowerCase() + : resolved; + if (!currentDestinations.has(destinationKey)) { + return { ...operation }; + } const installedContent = readInstalledFileNoFollow(plan, operation); if (installedContent === null) { return { ...operation }; diff --git a/scripts/lib/install/claude-skill-migration.js b/scripts/lib/install/claude-skill-migration.js index 1b82f0629..adc9170b3 100644 --- a/scripts/lib/install/claude-skill-migration.js +++ b/scripts/lib/install/claude-skill-migration.js @@ -243,7 +243,7 @@ function createDisabledMigration(plan, previousState) { bridgeState: finalState, finalState, legacyOperationsToRemove: [], - requiresBridgeState: false, + requiresBridgeState: plan.operations.length > 0, }; } diff --git a/scripts/lib/mcp-inventory/readers/opencode.js b/scripts/lib/mcp-inventory/readers/opencode.js index 5e1a5a1f9..c85b89a31 100644 --- a/scripts/lib/mcp-inventory/readers/opencode.js +++ b/scripts/lib/mcp-inventory/readers/opencode.js @@ -3,8 +3,9 @@ const fs = require('fs'); const os = require('os'); const path = require('path'); +const { resolveOpencodeConfigRoot } = require('../../opencode-paths'); -// OpenCode stores MCP servers under "mcp" in ~/.config/opencode/opencode.json. +// OpenCode stores MCP servers under "mcp" in its resolved configuration root. // Shape differs from Claude/Codex: // { type: "local"|"remote", command: ["npx","-y","pkg"], environment: {}, // enabled: bool, url: "https://..." } @@ -38,11 +39,12 @@ function mapOpencodeServer(name, raw, configPath) { function readOpencodeMcp(options = {}) { const homeDir = options.homeDir || os.homedir(); + const configRoot = resolveOpencodeConfigRoot({ homeDir, env: options.env }); const candidatePaths = options.configPath ? [options.configPath] : [ - path.join(homeDir, '.config', 'opencode', 'opencode.json'), - path.join(homeDir, '.config', 'opencode', 'config.json'), + path.join(configRoot, 'opencode.json'), + path.join(configRoot, 'config.json'), path.join(homeDir, '.opencode.json') ]; diff --git a/scripts/lib/opencode-paths.js b/scripts/lib/opencode-paths.js new file mode 100644 index 000000000..c80cb3ca4 --- /dev/null +++ b/scripts/lib/opencode-paths.js @@ -0,0 +1,30 @@ +'use strict'; + +const os = require('os'); +const path = require('path'); + +function configuredDirectory(environment, name) { + const value = environment && environment[name]; + return typeof value === 'string' && value.trim() !== '' + ? path.resolve(value.trim()) + : null; +} + +function resolveOpencodeConfigRoot(options = {}) { + const environment = options.env || process.env; + const explicitRoot = configuredDirectory(environment, 'OPENCODE_CONFIG_DIR'); + if (explicitRoot) { + return explicitRoot; + } + + const xdgConfigRoot = configuredDirectory(environment, 'XDG_CONFIG_HOME'); + if (xdgConfigRoot) { + return path.join(xdgConfigRoot, 'opencode'); + } + + return path.join(path.resolve(options.homeDir || os.homedir()), '.config', 'opencode'); +} + +module.exports = { + resolveOpencodeConfigRoot, +};