From 9412bd18e2eafd5e476474c8ba62babc27124c91 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Sat, 24 Oct 2026 15:42:49 +0400 Subject: [PATCH] =?UTF-8?q?fix(dsh):=20=D1=81=D0=BE=D0=BE=D0=B1=D1=89?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=85=D1=83=D0=BA=D0=BE=D0=B2=20?= =?UTF-8?q?=D0=B1=D0=B5=D0=B7=20namespace=20superpowers:?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/superpowers/plans/2026-10-24-dsh-port-spike.md | 2 +- dsh/PORT.md | 2 +- dsh/bundle/lib/guards.js | 9 ++++++++- dsh/tests/guard.test.mjs | 13 ++++++++++++- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/docs/superpowers/plans/2026-10-24-dsh-port-spike.md b/docs/superpowers/plans/2026-10-24-dsh-port-spike.md index 2c8f5f7..fd6d423 100644 --- a/docs/superpowers/plans/2026-10-24-dsh-port-spike.md +++ b/docs/superpowers/plans/2026-10-24-dsh-port-spike.md @@ -34,7 +34,7 @@ - [x] Bootstrap `using-superpowers`: резолв версии, marker, dedupe, compaction. - [x] `/role`: парсинг, два слоя, инъекция, followup. - [x] `dsh/install.sh`, `mcp.codegraph.yml`, README/PORT. -- [x] `bash dsh/tests/run.sh` — 25 тестов зелёные. +- [x] `bash dsh/tests/run.sh` — 26 тестов зелёные. - [x] `bash tests/run.sh` — 630 тестов зелёные (регрессий нет). - [x] Живой `guard-main`: `write` и `bash` на main → `BLOCKED`. - [x] Живой `require-plan`: `src/probe.ts` без `.bounded` → `BLOCKED`; diff --git a/dsh/PORT.md b/dsh/PORT.md index fd3493b..ec2dab5 100644 --- a/dsh/PORT.md +++ b/dsh/PORT.md @@ -98,7 +98,7 @@ from the profile, not from the DSH installation, so a DSH-internal import fails at activation with `ERR_MODULE_NOT_FOUND`. `lib/message.js` is a local copy of the tiny `createUserMessage` constructor, and `Config` was replaced by defaults normalized in `apply()`. The pure modules are testable with plain Node; this is -why the port's 25 tests run without a DSH process. +why the port's 26 tests run without a DSH process. ### Guard flow diff --git a/dsh/bundle/lib/guards.js b/dsh/bundle/lib/guards.js index 60561d5..096e6ff 100644 --- a/dsh/bundle/lib/guards.js +++ b/dsh/bundle/lib/guards.js @@ -28,6 +28,13 @@ const PLAN_TOOLS = new Set(['write', 'edit', 'multi_edit', 'str_replace_editor'] const DEFAULT_TIMEOUT_MS = 15_000 +/** Claude skill namespaces do not exist in DSH; rewrite the hook messages. */ +export function forDshReason(reason) { + return String(reason) + .replaceAll('superpowers:using-git-worktrees', 'using-git-worktrees') + .replaceAll('superpowers:brainstorming', 'brainstorming') +} + /** * Run one hook script with the synthesized payload. * @returns the denial text for exit 2, otherwise undefined. @@ -49,7 +56,7 @@ export function runHook({ script, hooksDir, payload, logger, timeoutMs = DEFAULT } catch (error) { if (error?.status === 2) { const reason = String(error.stderr ?? '').trim() - return reason || `BLOCKED by ${script}` + return forDshReason(reason || `BLOCKED by ${script}`) } // Fail open: a broken hook must never stop the agent. logger?.warn?.('[dsh-harness] %s failed open: %s', script, error?.message ?? String(error)) diff --git a/dsh/tests/guard.test.mjs b/dsh/tests/guard.test.mjs index 4c210c3..1c367fc 100644 --- a/dsh/tests/guard.test.mjs +++ b/dsh/tests/guard.test.mjs @@ -2,7 +2,7 @@ import assert from 'node:assert/strict' import { join } from 'node:path' import { fileURLToPath } from 'node:url' import test from 'node:test' -import { checkTool } from '../bundle/lib/guards.js' +import { checkTool, forDshReason } from '../bundle/lib/guards.js' import { cleanup, git, makeRepo, write } from './helpers.mjs' const hooksDir = fileURLToPath(new URL('../bundle/hooks/', import.meta.url)) @@ -20,11 +20,20 @@ test('guard-main blocks a write on main', () => { hooksDir, }) assert.match(reason ?? '', /BLOCKED \[guard-main\]/) + assert.doesNotMatch(reason ?? '', /superpowers:/) + assert.match(reason ?? '', /using-git-worktrees/) } finally { cleanup(repo) } }) +test('forDshReason removes the Claude skill namespace from hook messages', () => { + assert.equal( + forDshReason('Run superpowers:brainstorming; use superpowers:using-git-worktrees.'), + 'Run brainstorming; use using-git-worktrees.', + ) +}) + test('guard-main allows a write on a feature branch', () => { const repo = makeRepo() try { @@ -72,6 +81,8 @@ test('require-plan blocks src edits without a plan or .bounded', () => { hooksDir, }) assert.match(blocked ?? '', /BLOCKED \[require-plan\]/) + assert.doesNotMatch(blocked ?? '', /superpowers:/) + assert.match(blocked ?? '', /brainstorming/) write(join(repo, '.bounded'), '') const bounded = checkTool({