fix(dsh): сообщения хуков без namespace superpowers:
This commit is contained in:
parent
a6bddfc197
commit
9412bd18e2
@ -34,7 +34,7 @@
|
|||||||
- [x] Bootstrap `using-superpowers`: резолв версии, marker, dedupe, compaction.
|
- [x] Bootstrap `using-superpowers`: резолв версии, marker, dedupe, compaction.
|
||||||
- [x] `/role`: парсинг, два слоя, инъекция, followup.
|
- [x] `/role`: парсинг, два слоя, инъекция, followup.
|
||||||
- [x] `dsh/install.sh`, `mcp.codegraph.yml`, README/PORT.
|
- [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] `bash tests/run.sh` — 630 тестов зелёные (регрессий нет).
|
||||||
- [x] Живой `guard-main`: `write` и `bash` на main → `BLOCKED`.
|
- [x] Живой `guard-main`: `write` и `bash` на main → `BLOCKED`.
|
||||||
- [x] Живой `require-plan`: `src/probe.ts` без `.bounded` → `BLOCKED`;
|
- [x] Живой `require-plan`: `src/probe.ts` без `.bounded` → `BLOCKED`;
|
||||||
|
|||||||
@ -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
|
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
|
the tiny `createUserMessage` constructor, and `Config` was replaced by defaults
|
||||||
normalized in `apply()`. The pure modules are testable with plain Node; this is
|
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
|
### Guard flow
|
||||||
|
|
||||||
|
|||||||
@ -28,6 +28,13 @@ const PLAN_TOOLS = new Set(['write', 'edit', 'multi_edit', 'str_replace_editor']
|
|||||||
|
|
||||||
const DEFAULT_TIMEOUT_MS = 15_000
|
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.
|
* Run one hook script with the synthesized payload.
|
||||||
* @returns the denial text for exit 2, otherwise undefined.
|
* @returns the denial text for exit 2, otherwise undefined.
|
||||||
@ -49,7 +56,7 @@ export function runHook({ script, hooksDir, payload, logger, timeoutMs = DEFAULT
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error?.status === 2) {
|
if (error?.status === 2) {
|
||||||
const reason = String(error.stderr ?? '').trim()
|
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.
|
// Fail open: a broken hook must never stop the agent.
|
||||||
logger?.warn?.('[dsh-harness] %s failed open: %s', script, error?.message ?? String(error))
|
logger?.warn?.('[dsh-harness] %s failed open: %s', script, error?.message ?? String(error))
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import assert from 'node:assert/strict'
|
|||||||
import { join } from 'node:path'
|
import { join } from 'node:path'
|
||||||
import { fileURLToPath } from 'node:url'
|
import { fileURLToPath } from 'node:url'
|
||||||
import test from 'node:test'
|
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'
|
import { cleanup, git, makeRepo, write } from './helpers.mjs'
|
||||||
|
|
||||||
const hooksDir = fileURLToPath(new URL('../bundle/hooks/', import.meta.url))
|
const hooksDir = fileURLToPath(new URL('../bundle/hooks/', import.meta.url))
|
||||||
@ -20,11 +20,20 @@ test('guard-main blocks a write on main', () => {
|
|||||||
hooksDir,
|
hooksDir,
|
||||||
})
|
})
|
||||||
assert.match(reason ?? '', /BLOCKED \[guard-main\]/)
|
assert.match(reason ?? '', /BLOCKED \[guard-main\]/)
|
||||||
|
assert.doesNotMatch(reason ?? '', /superpowers:/)
|
||||||
|
assert.match(reason ?? '', /using-git-worktrees/)
|
||||||
} finally {
|
} finally {
|
||||||
cleanup(repo)
|
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', () => {
|
test('guard-main allows a write on a feature branch', () => {
|
||||||
const repo = makeRepo()
|
const repo = makeRepo()
|
||||||
try {
|
try {
|
||||||
@ -72,6 +81,8 @@ test('require-plan blocks src edits without a plan or .bounded', () => {
|
|||||||
hooksDir,
|
hooksDir,
|
||||||
})
|
})
|
||||||
assert.match(blocked ?? '', /BLOCKED \[require-plan\]/)
|
assert.match(blocked ?? '', /BLOCKED \[require-plan\]/)
|
||||||
|
assert.doesNotMatch(blocked ?? '', /superpowers:/)
|
||||||
|
assert.match(blocked ?? '', /brainstorming/)
|
||||||
|
|
||||||
write(join(repo, '.bounded'), '')
|
write(join(repo, '.bounded'), '')
|
||||||
const bounded = checkTool({
|
const bounded = checkTool({
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user