* docs: align PR-FLOW push gate from gsd-test-summary to gsd-test gsd-test is the application (open-gsd/gsd-test-runner); gsd-test-summary is the legacy local wrapper. RULESET.PR-FLOW.docker-before-push now names gsd-test as the pre-push gate (exit 0 / verdict outcome 'passed'). * docs: drop WORKTREE.SEAM local node --test rule; align PROC dispatch to gsd-test - Remove WORKTREE.SEAM.execution-rule (prefer local node --test) — contradicts CLAUDE.md 'NEVER run node --test locally'; CLAUDE.md wins. - PROC.PARALLEL-FIX-DISPATCH: gsd-test-summary --both -> gsd-test (app; --both was a legacy wrapper flag). * feat(#1682): OpenCode companion-MCP binding (mcp.gsd) — Phase 5 Slice 1a configureOpencodePermissions registers the Phase-4 companion MCP server (gsd-mcp-server) as opencode mcp.gsd, so OpenCode connects to GSD's command (point 1) + state-IO (point 5) with no bespoke plugin (ADR-1239 Phase D). Idempotent + non-clobbering (add-if-absent; respects a user-defined mcp.gsd). Local-stdio schema per OpenCode config (packages/core/src/config/mcp.ts); `-p @opengsd/gsd-core` resolves the bin (name != package) under npx. Tests: registers-on-object-config; does-not-clobber-user-entry. * docs(changeset): OpenCode companion-MCP binding (#1682) * fix(#1682): use PACKAGE_NAME single-source (#516) + refresh opencode golden parity - mcp.gsd command: replace hardcoded '@opengsd/gsd-core' literal with PACKAGE_NAME from gsd-core/bin/lib/package-identity.cjs (#516 single-source). - opencode golden-install-parity fixture: refresh opencode.json hash for the added mcp.gsd block (configureOpencodePermissions output change). * docs(changeset): add docs-exempt marker (Phase 5 slice) * docs(changeset): backfill PR #1929
This commit is contained in:
6
.changeset/1682-opencode-mcp-binding.md
Normal file
6
.changeset/1682-opencode-mcp-binding.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
type: Added
|
||||
pr: 1929
|
||||
---
|
||||
<!-- docs-exempt: Phase 5 (#1682) Slice 1a; consolidated Phase 5 (Embeddable Orchestration) docs land with phase completion. -->
|
||||
**OpenCode installs now auto-register the GSD companion MCP server (`mcp.gsd`)** — `--opencode` install writes a `mcp.gsd` entry (local stdio → `gsd-mcp-server`) into `opencode.json`, so OpenCode drives GSD's command + planning-state surface over MCP with no bespoke plugin (ADR-1239 Phase D / #1682). Idempotent and non-clobbering; a user-defined `mcp.gsd` is preserved. (#1682)
|
||||
@@ -487,7 +487,6 @@ The prompt-level data/instruction isolation seam for untrusted web/document ingr
|
||||
`WORKTREE.SEAM.test-policy=cover all decision branches in policy module before changing prune behavior`
|
||||
`WORKTREE.SEAM.test-anchors=[resolveWorktreeContext:has_local_planning|linked_worktree|not_git_repo|main_worktree, planWorktreePrune:git_list_failed|worktrees_present|no_worktrees|parser_throw_fallback, executeWorktreePrunePlan:missing_plan|skip_passthrough|unsupported_action|metadata_prune_only]`
|
||||
`WORKTREE.SEAM.invariant=parser failure must degrade to metadata_prune_only and never escalate to destructive removal`
|
||||
`WORKTREE.SEAM.execution-rule=prefer node --test tests/worktree-safety-policy.test.cjs for fast seam validation; avoid full npm test loop for seam-only changes`
|
||||
`WORKTREE.SEAM.inventory-interface=[listLinkedWorktreePaths, inspectWorktreeHealth]`
|
||||
`WORKTREE.SEAM.caller-rule=verify.cjs must consume inspectWorktreeHealth for W017 classification; no ad-hoc porcelain parsing in callers`
|
||||
`WORKTREE.SEAM.test-anchor-w017=tests/orphan-worktree-detection.test.cjs + tests/worktree-safety-policy.test.cjs`
|
||||
@@ -870,7 +869,7 @@ Migration plan: Phase 1 (#3465) seam additions complete; Phase 2 (#3466) targets
|
||||
|
||||
`RULESET.HARNESS.test-memory-guard=~/.claude/hooks/test-memory-guard.sh fires on every Bash PreToolUse; if argv[0]∈{node|vitest|jest|mocha|tsx|ts-node|tap|ava|playwright|cypress} OR matches (npm|pnpm|yarn|bun) (run )?(t|test|tests|vitest|jest); blocks via hookSpecificOutput.permissionDecision=deny when sum(RSS of running matching procs, excluding tsserver|*-mcp|claude|Electron|...) ≥ 4 GiB OR when argv[0] basename matches a running process's argv[0]. Exception: node --version|-v|--help|-h|-p|-e are trivial probes and skip the check. Designed for a 24 GB Mac where prior accidental fan-out exhausted RAM`
|
||||
|
||||
`RULESET.PR-FLOW.docker-before-push=before ANY git push of any fix to any PR, run gsd-test-summary (docker on the remote, mirrors ubuntu CI) and confirm exit 0. macOS-local node --test is NOT a substitute — many failures are platform-specific (path separators, case sensitivity, locale, fs semantics). Watchdog with Monitor on the output log; never set a sleep/timer and walk away. Source: user feedback 2026-05-16 — "we don't set a timer we actively watch and record results in real time as possible"`
|
||||
`RULESET.PR-FLOW.docker-before-push=before ANY git push of any fix to any PR, run gsd-test (docker on the remote, mirrors ubuntu CI) and confirm exit 0. macOS-local node --test is NOT a substitute — many failures are platform-specific (path separators, case sensitivity, locale, fs semantics). Watchdog with Monitor on the output log; never set a sleep/timer and walk away. Source: user feedback 2026-05-16 — "we don't set a timer we actively watch and record results in real time as possible"`
|
||||
|
||||
`RULESET.PR-FLOW.templates-mandatory=every gh pr create|edit|gh issue create|edit MUST first invoke the gh-templates-first skill and Read (Read tool, not Bash cat — k321 read-tracking) the matching template in .github/. Apply ALL required sections; never write freeform bodies. Repo enforces this via gsd-pr-template-policy GitHub Action which flags any non-templated body — the bot allows the PR to stay open only because authors are contributors-or-higher, but the warning is a real complaint that must be cured. Source: user feedback 2026-05-16 (multi-message escalation) — "the whole reason i have that github action is because you fucking blow through and ignore using the templates"`
|
||||
|
||||
@@ -908,7 +907,7 @@ Migration plan: Phase 1 (#3465) seam additions complete; Phase 2 (#3466) targets
|
||||
`DEFECT.AGENT-RETIRED-SLASH-SYNTAX-DRIFT.detect=tests/bug-2543-gsd-slash-namespace.test.cjs prints "Found N retired /gsd-<cmd> reference(s) — use /gsd:<cmd> instead" with line-number-precise violations`
|
||||
`DEFECT.AGENT-RETIRED-SLASH-SYNTAX-DRIFT.fix-forward=replace /gsd-<cmd> with /gsd:<cmd> at the cited file:line; healthy emergent property — project-wide invariant test catches drift agents would never self-correct`
|
||||
`DEFECT.AGENT-RETIRED-SLASH-SYNTAX-DRIFT.lesson=agent-trust-but-verify is load-bearing — sub-agent reporting "done" is not a substitute for running the full suite; the invariant test surfaces drift even in doc-only changes`
|
||||
`PROC.PARALLEL-FIX-DISPATCH.pattern=bot triage brief → worktree per branch → parallel sub-agents do rubber-duck/RCA/TDD implementation only → top-level orchestrator owns commit + gsd-test-summary --both + push + PR + changeset-pr-backfill`
|
||||
`PROC.PARALLEL-FIX-DISPATCH.pattern=bot triage brief → worktree per branch → parallel sub-agents do rubber-duck/RCA/TDD implementation only → top-level orchestrator owns commit + gsd-test + push + PR + changeset-pr-backfill`
|
||||
`PROC.PARALLEL-FIX-DISPATCH.rationale=long-running test runs need cross-turn notifications (orchestrator-only); CONTRIBUTING.md gh-templates-first hook requires session-scoped Read calls sub-agents wouldn't otherwise make; sequencing test runs avoids GSD-TEST-CONCURRENT-OUTPUT-COLLISION`
|
||||
`PROC.PARALLEL-FIX-DISPATCH.observed=#3541 + #3542 dispatched simultaneously this session; PRs #3546 #3547 opened green; one syntax slip caught by AGENT-RETIRED-SLASH-SYNTAX-DRIFT and fixed before second PR opened`
|
||||
|
||||
|
||||
@@ -374,6 +374,7 @@ const {
|
||||
} = require(path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'legacy-cleanup.cjs'));
|
||||
const {
|
||||
updateCacheFileName,
|
||||
PACKAGE_NAME,
|
||||
} = require(path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'package-identity.cjs'));
|
||||
|
||||
// ADR-1239 Phase B: runtime-artifact install cluster extracted to install-engine.cjs.
|
||||
@@ -7718,7 +7719,7 @@ function configureOpencodePermissions(isGlobal = true, configDir = null) {
|
||||
modified = true;
|
||||
}
|
||||
|
||||
// Configure external_directory permission (the safety guard for paths outside project)
|
||||
// Configure external_directory permission (the safety guard for paths outside)
|
||||
if (!config.permission.external_directory || typeof config.permission.external_directory !== 'object') {
|
||||
config.permission.external_directory = {};
|
||||
}
|
||||
@@ -7727,6 +7728,25 @@ function configureOpencodePermissions(isGlobal = true, configDir = null) {
|
||||
modified = true;
|
||||
}
|
||||
|
||||
// ADR-1239 Phase D / #1682 — register the companion MCP server (Phase 4) so
|
||||
// OpenCode connects to GSD's command (point 1) + state-IO (point 5) surface
|
||||
// with NO bespoke plugin. Idempotent + non-clobbering: only added when
|
||||
// `mcp.gsd` is absent (a user-defined `mcp.gsd` is respected — Hyrum's Law).
|
||||
// Local-stdio schema per OpenCode config (packages/core/src/config/mcp.ts).
|
||||
// `-p @opengsd/gsd-core` resolves the `gsd-mcp-server` bin from this package
|
||||
// (bin name != package name) regardless of global-install state.
|
||||
if (!config.mcp || typeof config.mcp !== 'object') {
|
||||
config.mcp = {};
|
||||
}
|
||||
if (config.mcp.gsd === undefined) {
|
||||
config.mcp.gsd = {
|
||||
type: 'local',
|
||||
command: ['npx', '-y', '-p', PACKAGE_NAME, 'gsd-mcp-server'],
|
||||
enabled: true,
|
||||
};
|
||||
modified = true;
|
||||
}
|
||||
|
||||
if (!modified) {
|
||||
return; // Already configured
|
||||
}
|
||||
|
||||
@@ -390,7 +390,7 @@
|
||||
"hooks/lib/git-cmd.js": "268ba15992ca0b23",
|
||||
"hooks/lib/gsd-graphify-rebuild.sh": "66af89601074d2a9",
|
||||
"hooks/managed-hooks-registry.cjs": "763730ef31e5fd1c",
|
||||
"opencode.json": "13151e97ff23c1aa",
|
||||
"opencode.json": "2c12c446a88f2f36",
|
||||
"package.json": "dbf8353f77358bc1",
|
||||
"plugins/gsd-core.js": "8ae69107bf3036a0",
|
||||
"scripts/changeset/README.md": "86ff89331dfd94b2",
|
||||
|
||||
@@ -21,6 +21,7 @@ const path = require('node:path');
|
||||
|
||||
const { createTempDir, cleanup } = require('./helpers.cjs');
|
||||
const { configureOpencodePermissions } = require('../bin/install.js');
|
||||
const { PACKAGE_NAME } = require('../gsd-core/bin/lib/package-identity.cjs');
|
||||
|
||||
const installSrc = fs.readFileSync(path.join(__dirname, '..', 'bin', 'install.js'), 'utf8');
|
||||
|
||||
@@ -78,6 +79,34 @@ describe('configureOpencodePermissions', () => {
|
||||
assert.strictEqual(config.permission.external_directory[gsdPath], 'allow');
|
||||
});
|
||||
|
||||
test('registers the companion MCP server (mcp.gsd) for object configs (#1682)', () => {
|
||||
const configPath = path.join(configDir, 'opencode.json');
|
||||
fs.writeFileSync(configPath, JSON.stringify({ permission: {} }, null, 2) + '\n');
|
||||
process.env.OPENCODE_CONFIG_DIR = configDir;
|
||||
|
||||
configureOpencodePermissions(true, configDir);
|
||||
|
||||
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||
assert.deepEqual(config.mcp.gsd, {
|
||||
type: 'local',
|
||||
command: ['npx', '-y', '-p', PACKAGE_NAME, 'gsd-mcp-server'],
|
||||
enabled: true,
|
||||
});
|
||||
});
|
||||
|
||||
test('does not clobber a user-defined mcp.gsd entry (#1682)', () => {
|
||||
const configPath = path.join(configDir, 'opencode.json');
|
||||
const userMcp = { type: 'local', command: ['node', '/custom/server.js'], enabled: false };
|
||||
fs.writeFileSync(configPath, JSON.stringify({ permission: {}, mcp: { gsd: userMcp } }, null, 2) + '\n');
|
||||
process.env.OPENCODE_CONFIG_DIR = configDir;
|
||||
|
||||
configureOpencodePermissions(true, configDir);
|
||||
|
||||
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||
// User's own mcp.gsd is preserved untouched (Hyrum's Law — non-clobbering).
|
||||
assert.deepEqual(config.mcp.gsd, userMcp);
|
||||
});
|
||||
|
||||
test('finishInstall passes the actual config dir to OpenCode permissions', () => {
|
||||
assert.ok(
|
||||
installSrc.includes('configureOpencodePermissions(isGlobal, configDir);'),
|
||||
|
||||
Reference in New Issue
Block a user