Commit Graph

16 Commits

Author SHA1 Message Date
Jeremy McSpadden
9e87d43831 fix(build): include gsd-read-injection-scanner in hooks/dist (#2406)
The scanner was added in #2201 but never added to the HOOKS_TO_COPY
allowlist in scripts/build-hooks.js, so it never landed in hooks/dist/.
install.js reads from hooks/dist/, so every install on 1.37.0/1.37.1
emitted "Skipped read injection scanner hook — not found at target"
and the read-time prompt-injection scanner was silently disabled.

- Add gsd-read-injection-scanner.js to HOOKS_TO_COPY
- Add it to EXPECTED_ALL_HOOKS regression test in install-hooks-copy

Fixes #2406

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 18:39:20 -05:00
Tom Boucher
c35997fb0b feat(hooks): add gsd-read-injection-scanner PostToolUse hook (#2201) (#2328)
* feat: add /gsd-spec-phase — Socratic spec refinement with ambiguity scoring (#2213)

Introduces `/gsd-spec-phase <phase>` as an optional pre-step before discuss-phase.
Clarifies WHAT a phase delivers (requirements, boundaries, acceptance criteria) with
quantitative ambiguity scoring before discuss-phase handles HOW to implement.

- `commands/gsd/spec-phase.md` — slash command routing to workflow
- `get-shit-done/workflows/spec-phase.md` — full Socratic interview loop (up to 6
  rounds, 5 rotating perspectives: Researcher, Simplifier, Boundary Keeper, Failure
  Analyst, Seed Closer) with weighted 4-dimension ambiguity gate (≤ 0.20 to write SPEC.md)
- `get-shit-done/templates/spec.md` — SPEC.md template with falsifiable requirements
  (Current/Target/Acceptance per requirement), Boundaries, Acceptance Criteria,
  Ambiguity Report, and Interview Log; includes two full worked examples
- `get-shit-done/workflows/discuss-phase.md` — new `check_spec` step detects
  `{padded_phase}-SPEC.md` at startup; displays "Found SPEC.md — N requirements
  locked. Focusing on implementation decisions."; `analyze_phase` respects `spec_loaded`
  flag to skip "what/why" gray areas; `write_context` emits `<spec_lock>` section
  with boundary summary and canonical ref to SPEC.md
- `docs/ARCHITECTURE.md` — update command/workflow counts (74→75, 71→72)

Closes #2213

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(hooks): add gsd-read-injection-scanner PostToolUse hook (#2201)

Adds a new PostToolUse hook that scans content returned by the Read tool
for prompt injection patterns, including four summarisation-specific patterns
(retention-directive, permanence-claim, etc.) that survive context compression.

Defense-in-depth for long GSD sessions where the context summariser cannot
distinguish user instructions from content read from external files.

- Advisory-only (warns without blocking), consistent with gsd-prompt-guard.js
- LOW severity for 1-2 patterns, HIGH for 3+
- Inlined pattern library (hook independence)
- Exclusion list: .planning/, REVIEW.md, CHECKPOINT, security docs, hook sources
- Wired in install.js as PostToolUse matcher: Read, timeout: 5s
- Added to MANAGED_HOOKS for staleness detection
- 19 tests covering all 13 acceptance criteria (SCAN-01–07, EXCL-01–06, EDGE-01–06)

Closes #2201

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): add read-injection-scanner files to prompt-injection-scan allowlist

Test payloads in tests/read-injection-scanner.test.cjs and inlined patterns
in hooks/gsd-read-injection-scanner.js legitimately contain injection strings.
Add both to the CI script allowlist to prevent false-positive failures.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(test): assert exitCode, stdout, and signal explicitly in EDGE-05

Addresses CodeRabbit feedback: the success path discarded the return
value so a malformed-JSON input that produced stdout would still pass.
Now captures and asserts all three observable properties.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 17:22:31 -04:00
Tom Boucher
50f61bfd9a fix(hooks): complete stale-hooks false-positive fix — stamp .sh version headers + fix detector regex (#2224)
* fix(hooks): stamp gsd-hook-version in .sh hooks and fix stale detection regex (#2136, #2206)

Three-part fix for the persistent "⚠ stale hooks — run /gsd-update" false
positive that appeared on every session after a fresh install.

Root cause: the stale-hook detector (gsd-check-update.js) could only match
the JS comment syntax // in its version regex — never the bash # syntax used
in .sh hooks. And the bash hooks had no version header at all, so they always
landed in the "unknown / stale" branch regardless.

Neither partial fix (PR #2207 regex only, PR #2215 install stamping only) was
sufficient alone:
  - Regex fix without install stamping: hooks install with literal
    "{{GSD_VERSION}}", the {{-guard silently skips them, bash hook staleness
    permanently undetectable after future updates.
  - Install stamping without regex fix: hooks are stamped correctly with
    "# gsd-hook-version: 1.36.0" but the detector's // regex can't read it;
    still falls to the unknown/stale branch on every session.

Fix:
  1. Add "# gsd-hook-version: {{GSD_VERSION}}" header to
     gsd-phase-boundary.sh, gsd-session-state.sh, gsd-validate-commit.sh
  2. Extend install.js (both bundled and Codex paths) to substitute
     {{GSD_VERSION}} in .sh files at install time (same as .js hooks)
  3. Extend gsd-check-update.js versionMatch regex to handle bash "#"
     comment syntax: /(?:\/\/|#) gsd-hook-version:\s*(.+)/

Tests: 11 new assertions across 5 describe blocks covering all three fix
parts independently plus an E2E install+detect round-trip. 3885/3885 pass.

Approach credit: PR #2207 (j2h4u / Maxim Brashenko) for the regex fix;
PR #2215 (nitsan2dots) for the install.js substitution approach.

Closes #2136, #2206, #2209, #2210, #2212

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(hooks): extract check-update worker to dedicated file, eliminating template-literal regex escaping

Move stale-hook detection logic from inline `node -e '<template literal>'` subprocess
to a standalone gsd-check-update-worker.js. Benefits:
- Regex is plain JS with no double-escaping (root cause of the (?:\\/\\/|#) confusion)
- Worker is independently testable and can be read directly by tests
- Uses execFileSync (array args) to satisfy security hook that blocks execSync
- MANAGED_HOOKS now includes gsd-check-update-worker.js itself

Update tests to read worker file instead of main hook for regex/configDir assertions.
All 3886 tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 17:57:38 -04:00
Tom Boucher
2703422be8 refactor(tests): standardize to node:assert/strict and t.after() per CONTRIBUTING.md (#1675)
* refactor(tests): standardize to node:assert/strict and t.after() per CONTRIBUTING.md

- Replace require('node:assert') with require('node:assert/strict') across
  all 73 test files to enforce strict equality (no type coercion)
- Replace try/finally cleanup blocks with t.after() hooks in core.test.cjs
  and hooks-opt-in.test.cjs per the test lifecycle standards
- Utility functions in codex-config and security-scan retain try/finally
  as that is appropriate for per-function resource guards, not lifecycle hooks

Closes #1674

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* perf(tests): add --test-concurrency=4 to test runner for parallel file execution

Node.js --test-concurrency controls how many test files run as parallel child
processes. Set to 4 by default, configurable via TEST_CONCURRENCY env var.
Fixes tests at a known level rather than inheriting os.availableParallelism()
which varies across CI environments.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(security): allowlist verify.test.cjs in prompt-injection scanner

tests/verify.test.cjs uses <human>...</human> as GSD phase task-type
XML (meaning "a human should verify this step"), which matches the
scanner's fake-message-boundary pattern for LLM APIs. This is a
false positive — add it to the allowlist alongside the other test files
that legitimately contain injection-adjacent patterns.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 14:29:03 -04:00
Tom Boucher
ca6a273685 fix: remove marketing text from runtime prompt, fix #1656 and #1657 (#1672)
* chore: ignore .worktrees directory

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(install): remove marketing taglines from runtime selection prompt

Closes #1654

The runtime selection menu had promotional copy appended to some
entries ("open source, the #1 AI coding platform on OpenRouter",
"open source, free models"). Replaced with just the name and path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(kilo): update test to assert marketing tagline is removed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(tests): use process.execPath so tests pass in shells without node on PATH

Three test patterns called bare `node` via shell, which fails in Claude Code
sessions where `node` is not on PATH:

- helpers.cjs string branch: execSync(`node ...`) → execFileSync(process.execPath)
  with a shell-style tokenizer that handles quoted args and inner-quote stripping
- hooks-opt-in.test.cjs: spawnSync('bash', ...) for hooks that call `node`
  internally → spawnHook() wrapper that injects process.execPath dir into PATH
- concurrency-safety.test.cjs: exec(`node ...`) for concurrent patch test
  → exec(`"${process.execPath}" ...`)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: resolve #1656 and #1657 — bash hooks missing from dist, SDK install prompt

#1656: Community bash hooks (gsd-session-state.sh, gsd-validate-commit.sh,
gsd-phase-boundary.sh) were never included in HOOKS_TO_COPY in build-hooks.js,
so hooks/dist/ never contained them and the installer could not copy them to
user machines. Fixed by adding the three .sh files to the copy array with
chmod +x preservation and skipping JS syntax validation for shell scripts.

#1657: promptSdk() called installSdk() which ran `npm install -g @gsd-build/sdk`
— a package that does not exist on npm, causing visible errors during interactive
installs. Removed promptSdk(), installSdk(), --sdk flag, and all call sites.

Regression tests in tests/bugs-1656-1657.test.cjs guard both fixes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: sort runtime list alphabetically after Claude Code

- Claude Code stays pinned at position 1
- Remaining 10 runtimes sorted A-Z: Antigravity(2), Augment(3), Codex(4),
  Copilot(5), Cursor(6), Gemini(7), Kilo(8), OpenCode(9), Trae(10), Windsurf(11)
- Updated runtimeMap, allRuntimes, and prompt display in promptRuntime()
- Updated multi-runtime-select, kilo-install, copilot-install tests to match

Also fix #1656 regression test: run build-hooks.js in before() hook so
hooks/dist/ is populated on CI (directory is gitignored; build runs via
prepublishOnly before publish, not during npm ci).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 14:15:30 -04:00
Tom Boucher
9d626de5fa fix(hooks): add read-before-edit guard for non-Claude runtimes (#1645)
* fix(hooks): add read-before-edit guidance for non-Claude runtimes

When models that don't natively enforce read-before-edit hit the guard,
the error message now includes explicit instruction to Read first.
This prevents infinite retry loops that burn through usage.

Closes #1628

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(build): register gsd-read-guard.js in HOOKS_TO_COPY and harden tests

The hook was missing from scripts/build-hooks.js, so global installs
would never receive the hook file in hooks/dist/. Also adds tests for
build registration, install uninstall list, and non-string file_path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 07:35:18 -04:00
Tom Boucher
98f05d43b8 fix: security scan self-detection and Windows test compatibility
- Add base64-scan.sh and secret-scan.sh to prompt injection scanner
  allowlist (scanner was flagging its own pattern strings)
- Skip executable bit check on Windows (no Unix permissions)
- Skip bash script execution tests on Windows (requires Git Bash)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 13:30:15 -04:00
Tom Boucher
feec5a37a2 ci(security): add prompt injection, base64, and secret scanning
Add CI security pipeline to catch prompt injection attacks, base64-obfuscated
payloads, leaked secrets, and .planning/ directory commits in PRs.

This is critical for get-shit-done because the entire codebase is markdown
prompts — a prompt injection in a workflow file IS the attack surface.

New files:
- scripts/prompt-injection-scan.sh: scans for instruction override, role
  manipulation, system boundary injection, DAN/jailbreak, and tool call
  injection patterns in changed files
- scripts/base64-scan.sh: extracts base64 blobs >= 40 chars, decodes them,
  and checks decoded content against injection patterns (skips data URIs
  and binary content)
- scripts/secret-scan.sh: detects AWS keys, OpenAI/Anthropic keys, GitHub
  PATs, Stripe keys, private key headers, and generic credential patterns
- .github/workflows/security-scan.yml: runs all three scans plus a
  .planning/ directory check on every PR
- .base64scanignore / .secretscanignore: per-repo false positive allowlists
- tests/security-scan.test.cjs: 51 tests covering script existence,
  pattern matching, false positive avoidance, and workflow structure

All scripts support --diff (CI), --file, and --dir modes. Cross-platform
(macOS + Linux). SHA-pinned actions. Environment variables used for
github context in run blocks (no direct interpolation).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 13:23:51 -04:00
Tom Boucher
62db008570 security: add prompt injection guards, path traversal prevention, and input validation
Defense-in-depth security hardening for a codebase where markdown files become
LLM system prompts. Adds centralized security module, PreToolUse hook for
injection detection, and CI-ready codebase scan.

New files:
- security.cjs: path traversal prevention, prompt injection scanner/sanitizer,
  safe JSON parsing, field name validation, shell arg validation
- gsd-prompt-guard.js: PreToolUse hook scans .planning/ writes for injection
- security.test.cjs: 62 unit tests for all security functions
- prompt-injection-scan.test.cjs: CI scan of all agent/workflow/command files

Hardened code paths:
- readTextArgOrFile: path traversal guard (--prd, --text-file)
- cmdStateUpdate/Patch: field name validation prevents regex injection
- cmdCommit: sanitizeForPrompt strips invisible chars from commit messages
- gsd-tools --fields: safeJsonParse wraps unprotected JSON.parse
- cmdFrontmatterGet/Set: null byte rejection
- cmdVerifyPathExists: null byte rejection
- install.js: registers prompt guard hook, updates uninstaller

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 11:38:26 -04:00
Tom Boucher
a6ba3e268e feat: PreToolUse workflow guard hook for rogue edit prevention (#678) (#1197)
New opt-in PreToolUse hook that warns when Claude edits files outside
a GSD workflow context (no active /gsd: command or subagent).

Soft guard — advises, does not block. The edit proceeds but Claude
sees a reminder to use /gsd:fast or /gsd:quick for state tracking.

Enable: set hooks.workflow_guard: true in .planning/config.json
Default: disabled (false)

Allows without warning:
- .planning/ files (GSD state management)
- Config files (.gitignore, .env, CLAUDE.md, settings.json)
- Subagent contexts (executor, planner, etc.)

Includes 3s stdin timeout guard and silent fail-safe.

Closes #678
2026-03-18 17:36:07 -04:00
Tom Boucher
14c1dd845b fix(build): add syntax validation to hook build script (#1165)
Prevents shipping hooks with JavaScript SyntaxError (like the duplicate
const cwd declaration that caused PostToolUse errors for all users in
v1.25.1).

The build script now validates each hook file's syntax via vm.Script
before copying to dist/. If any hook has a SyntaxError, the build fails
with a clear error message and exits non-zero, blocking npm publish.

Refs #1107, #1109, #1125, #1161
2026-03-18 09:56:51 -06:00
Lex Christopherson
02a5319777 fix(ci): propagate coverage env in cross-platform test runner
The run-tests.cjs child process now inherits NODE_V8_COVERAGE from the
parent so c8 collects coverage data. Also restores npm scripts to use
the cross-platform runner for both test and test:coverage commands.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 10:07:02 -06:00
Lex Christopherson
ccb8ae1d18 fix(ci): cross-platform test runner for Windows glob expansion
npm scripts pass `tests/*.test.cjs` to node/c8 as a literal string on
Windows (PowerShell/cmd don't expand globs). Adding `shell: bash` to CI
steps doesn't help because c8 spawns node as a child process using the
system shell. Use a Node script to enumerate test files cross-platform.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 10:00:26 -06:00
vinicius-tersi
7542d364b4 feat: context window monitor hook with agent-side WARNING/CRITICAL alerts
Adds PostToolUse hook that reads context metrics from statusline bridge file and injects alerts into agent conversation when context is low.

Features:
- Two-tier alerts: WARNING (<=35% remaining) and CRITICAL (<=25%)
- Smart debounce: 5 tool uses between warnings, severity escalation bypasses
- Silent fail: never blocks tool execution
- Security: session_id sanitized to prevent path traversal

Ref #212
2026-02-20 14:40:08 -06:00
Lex Christopherson
d1fda80c7f revert: remove codebase intelligence system
Rolled back the intel system due to overengineering concerns:
- 1200+ line hook with SQLite graph database
- 21MB sql.js dependency
- Entity generation spawning additional Claude calls
- Complex system with unclear value

Removed:
- /gsd:analyze-codebase command
- /gsd:query-intel command
- gsd-intel-index.js, gsd-intel-session.js, gsd-intel-prune.js hooks
- gsd-entity-generator, gsd-indexer agents
- entity.md template
- sql.js dependency

Preserved:
- Model profiles feature
- Statusline hook
- All other v1.9.x improvements

-3,065 lines removed

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 10:28:53 -06:00
Lex Christopherson
cdad7b8ad7 fix: update build script to use gsd-statusline.js
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 12:11:08 -06:00