test(hooks): subagent-prompt-prefix — failing tests (TDD red)
5 тестов для Task git-safety inject хука: - inject SUBAGENT GIT-SAFETY HEADER в Task-prompt - inject real cwd/branch/HEAD/worktree-root - passes through non-Task tools - fail-open on malformed stdin - fail-open when git unavailable Tests FAIL — hook implementation в следующем коммите (TDD green-phase). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Tests for tools/subagent-prompt-prefix.mjs — PreToolUse Task git-safety header inject.
|
||||
*
|
||||
* Per Pravila §15.1 — hook injects cwd/branch/HEAD/worktree-root into каждый Task-prompt.
|
||||
* FAIL-OPEN: any error → return {continue: true} без модификации.
|
||||
*
|
||||
* Spec: docs/superpowers/specs/2026-05-18-parallel-sessions-coordination-design.md §4
|
||||
*/
|
||||
import { test } from 'node:test';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join } from 'node:path';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const HOOK_PATH = join(__dirname, 'subagent-prompt-prefix.mjs');
|
||||
|
||||
function runHook(stdinJson) {
|
||||
return spawnSync('node', [HOOK_PATH], {
|
||||
input: JSON.stringify(stdinJson),
|
||||
encoding: 'utf8',
|
||||
timeout: 5000,
|
||||
});
|
||||
}
|
||||
|
||||
test('hook injects SUBAGENT GIT-SAFETY HEADER into Task prompt', () => {
|
||||
const result = runHook({
|
||||
tool_name: 'Task',
|
||||
tool_input: { prompt: 'do something' },
|
||||
});
|
||||
assert.equal(result.status, 0, `stderr: ${result.stderr}`);
|
||||
const out = JSON.parse(result.stdout);
|
||||
assert.ok(out.hookSpecificOutput, 'has hookSpecificOutput');
|
||||
assert.equal(out.hookSpecificOutput.hookEventName, 'PreToolUse');
|
||||
assert.equal(out.hookSpecificOutput.permissionDecision, 'allow');
|
||||
const newPrompt = out.hookSpecificOutput.updatedInput.prompt;
|
||||
assert.match(newPrompt, /=== SUBAGENT GIT-SAFETY HEADER/);
|
||||
assert.match(newPrompt, /=== END SUBAGENT GIT-SAFETY HEADER ===/);
|
||||
assert.match(newPrompt, /do something/, 'preserves original prompt');
|
||||
});
|
||||
|
||||
test('hook injects real cwd, branch, HEAD values', () => {
|
||||
const result = runHook({
|
||||
tool_name: 'Task',
|
||||
tool_input: { prompt: 'noop' },
|
||||
});
|
||||
assert.equal(result.status, 0);
|
||||
const out = JSON.parse(result.stdout);
|
||||
const newPrompt = out.hookSpecificOutput.updatedInput.prompt;
|
||||
// cwd — absolute path
|
||||
assert.match(newPrompt, /Working directory \(cwd\): [A-Za-z]:[\\/]|Working directory \(cwd\): \//);
|
||||
// branch — non-empty
|
||||
assert.match(newPrompt, /Branch \(git branch --show-current\): \S+/);
|
||||
// HEAD — 40-char SHA
|
||||
assert.match(newPrompt, /Parent commit \(git rev-parse HEAD\): [0-9a-f]{40}/);
|
||||
});
|
||||
|
||||
test('hook passes through non-Task tools without modification', () => {
|
||||
const result = runHook({
|
||||
tool_name: 'Edit',
|
||||
tool_input: { file_path: '/foo', old_string: 'a', new_string: 'b' },
|
||||
});
|
||||
assert.equal(result.status, 0);
|
||||
const out = JSON.parse(result.stdout);
|
||||
// Non-Task → either {continue: true} OR no updatedInput
|
||||
if (out.hookSpecificOutput) {
|
||||
assert.equal(out.hookSpecificOutput.updatedInput, undefined);
|
||||
} else {
|
||||
assert.equal(out.continue, true);
|
||||
}
|
||||
});
|
||||
|
||||
test('hook fail-open on malformed stdin', () => {
|
||||
const result = spawnSync('node', [HOOK_PATH], {
|
||||
input: 'not-json',
|
||||
encoding: 'utf8',
|
||||
timeout: 5000,
|
||||
});
|
||||
assert.equal(result.status, 0, `should not crash; stderr: ${result.stderr}`);
|
||||
// Either {continue: true} or empty output — both acceptable fail-open
|
||||
});
|
||||
|
||||
test('hook fail-open when git not available', () => {
|
||||
const result = spawnSync('node', [HOOK_PATH], {
|
||||
input: JSON.stringify({ tool_name: 'Task', tool_input: { prompt: 'x' } }),
|
||||
encoding: 'utf8',
|
||||
timeout: 5000,
|
||||
env: { ...process.env, PATH: '' }, // strip PATH → git not found
|
||||
});
|
||||
assert.equal(result.status, 0, `should not crash when git missing; stderr: ${result.stderr}`);
|
||||
});
|
||||
Reference in New Issue
Block a user