You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix: PowerShell hooks audit, script hardening, and workflow fixes (#20)
* fix: rename reserved $input variable in PowerShell hooks (#17)
Assigning parsed hook JSON to $input (PowerShell's automatic pipeline
variable) throws a non-terminating ParameterBindingException whenever
the hook receives real piped/redirected stdin - i.e. on every actual
Claude Code invocation. The assignment silently fails, $input stays
unbound, and the hook exits before reading any input. Renamed to
$hookInput in subagent-retro.ps1, prompt-router.ps1, and spec-gate.ps1.
Discovered while reproducing the 01-bug-subagent-retro-ps1-matches-clobber
fixture; that bug was unreachable until this one was fixed.
* fix: stop $Matches clobber in subagent-retro.ps1 spec-ID extraction (#18)
* fix: rename reserved $input variable in PowerShell hooks
Assigning parsed hook JSON to $input (PowerShell's automatic pipeline
variable) throws a non-terminating ParameterBindingException whenever
the hook receives real piped/redirected stdin - i.e. on every actual
Claude Code invocation. The assignment silently fails, $input stays
unbound, and the hook exits before reading any input. Renamed to
$hookInput in subagent-retro.ps1, prompt-router.ps1, and spec-gate.ps1.
Discovered while reproducing the 01-bug-subagent-retro-ps1-matches-clobber
fixture; that bug was unreachable until this one was fixed.
* fix: stop $Matches clobber in subagent-retro.ps1 spec-ID extraction
Get-IndexSpecs ran the spec-ID regex -match before the in-progress
literal -match, so the second match overwrote $Matches and every
in-progress spec was reported under the literal ID "in-progress"
rather than its real ID. The retro-staleness check then looked for
<specDir>/in-progress/05-retro.md, which never exists, so every
in-progress spec was reported stale under the wrong ID.
Swap the match order (in-progress first, ID regex last) to match
the already-correct sister hooks prompt-router.ps1:167 and
spec-gate.ps1:145.
Verified against a fixture index (FEAT-123 in-progress, RCA-77
in-progress) - PowerShell now reports FEAT-123 and skips the RCA
row, matching hooks/bash/subagent-retro.sh output exactly.
* fix: same-line in-progress detection parity in spec-gate.sh
bash used two independent file-wide greps for the in-progress marker
and a spec ID, letting a legend/header line combine with an unrelated
done row. spec-gate.ps1 and prompt-router.sh already require both on
the same line; spec-gate.sh now matches.
* fix: align feature.md and refactor.md subagent field names with contracts
sd-spec-architect and sd-code-explorer read TASK/SPEC/IMPACT, not the
TASK_TYPE/SPEC_REF/IMPACT_REF/TICKET_DATA names feature.md sent them.
Both refine invocations were also missing the required SPEC path, and
the TEMPLATE field was bare instead of the full template filename.
refactor.md's characterization-test loop sent sd-implementer an
unrecognized TASK_TYPE field instead of TASK_DETAILS/SPEC_REF/
WORKFLOW_TYPE. Reviewer invocations, which own TASK_TYPE/SPEC_REF as
their own contract, are unchanged.
* fix: make /sd:spec validate plan/tasks check type-aware
The in-progress file-presence rule exempted only RCA from requiring
01-plan.md/02-tasks.md, but bug and perf workflows never produce those
artifacts either (their artifact set is spec, decisions, artifacts,
retro). Validate failed every correctly executed bug/perf spec.
Narrowed the requirement to feature and refactor, and fixed the same
overgeneralized claim in docs/usage.md's resume heuristic.
* fix: parse debounce timestamp as UTC in subagent-retro.ps1
[datetime]::Parse of a "...Z" string returns Kind=Local, converted
from UTC to local time. Subtracting that from a UTC now skewed $age
by the machine's UTC offset -- wrongly suppressing reminders on
UTC+N machines, never suppressing on UTC-N. Verified on a UTC+7
machine under Windows PowerShell 5.1 (the hooks' target runtime):
old code showed a 420-minute skew immediately after save; fixed code
shows ~0.
* fix: apply per-workflow keyword defaults in prompt-router.ps1
PS only fell back to built-in defaults when project-config.json was
absent or unparseable, then silently skipped any workflow whose
keyword list was null once a config file existed. A valid config
that omitted workflow.keywords (or one workflow's entry) lost
keyword routing hints on Windows while bash kept emitting them from
defaults, matching bash's per-workflow "// []" + empty-list fallback
semantics. Verified against both a config missing workflow.keywords
entirely and one overriding a single workflow, under Windows
PowerShell 5.1.
* fix: replace stale MCP tool names in agents, skills, and explore command
* fix: remove hardcoded MSSQL/C#/TS stack references from agents and commands
* fix: code-explorer impact-map returns analysis instead of self-appending
* fix: walk full lifecycle state machine in bug/rca/perf workflows
* fix: guard workflow Phase 0 against missing or malformed Layer-2 context
* refactor: dedup rules copy-pasted from skills into agent/command bodies
* docs: fix README/ROADMAP/usage drift after v1.3.0+ shipped
* refactor: derive validate.sh/ps1 install counts from source tree
* feat: add macOS to CI matrix and hook smoke tests
Also fixes two PowerShell-7-only debounce bugs in subagent-retro.ps1
that the new smoke test caught: an unresolvable Split-Path parameter
set, and re-Parse-ing an already-converted [datetime] as local time.
* fix: harden install.sh - strict mode, prefix guard, quoting
* fix: strip CRLF from jq keyword output in prompt-router.sh
Some jq builds (observed with Windows jq.exe) emit CRLF for join("\n")
output even from LF-only input, so every keyword but a workflow's last
kept a trailing \r and never matched, silently dropping keyword-routing
hints whenever project-config.json defined workflow.keywords. Fixture
in scripts/smoke-hooks.sh/.ps1 now sets workflow.keywords so this path
is actually exercised.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix: make prompt-router.sh bash 3.2 compatible
macOS runners execute hooks with system bash 3.2, which has no
associative arrays: declare -A silently failed and every string
subscript arithmetic-evaluated to 0, so the router emitted
'- /sd:0' instead of '- /sd:bug' and the macOS smoke test failed.
Replace the two associative arrays with parallel indexed arrays.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: add review findings TODO before merge
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: CONTRIBUTING.md
+4Lines changed: 4 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -126,15 +126,19 @@ A subagent is a markdown file with YAML frontmatter consumed by the `Task` tool.
126
126
```yaml
127
127
---
128
128
name: sd-<role>
129
+
color: <color> # e.g. cyan, orange, purple, green, blue - used for display only
129
130
description: One-line summary used by routing.
130
131
model: sonnet # MUST be an alias: sonnet | haiku | opus | inherit
131
132
tools: Read, Grep, Glob, ... # MINIMAL allowlist
133
+
skills:
134
+
- sd-<shared-rule-pack> # any skill this agent's body references; see Skills below
132
135
---
133
136
```
134
137
135
138
**Critical:**
136
139
-`model:` MUST be an alias. Full IDs like `claude-sonnet-4-7` are not portable and may not even exist. The alias `sonnet` auto-resolves to the latest Sonnet.
137
140
-`tools:` should be the minimum set the agent needs. Read-only agents do not get `Write`. Implementer does not get `WebSearch`.
141
+
-`skills:` must list every skill the agent body references (`**skill-name**` in prose). A rule used by multiple agents lives in one `SKILL.md`, never copy-pasted into agent bodies.
138
142
- Agent must read `CLAUDE.md` and `constitution.md` at runtime. No hardcoded stack assumptions (no `cs`, `csproj`, `dotnet`, etc. literal references unless they come from project config).
139
143
- Every finding cites `file:line`. No prose without citations.
Copy file name to clipboardExpand all lines: README.md
+9-5Lines changed: 9 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,7 +1,7 @@
1
1
# specwright
2
2
3
3
> **Spec-driven development workflows for Claude Code.**
4
-
> Ten slash commands, five specialized subagents, three guard-rail hooks, nine templates, six reusable skills - all under the `sd:` namespace, stack-agnostic, cross-platform, and ready to drop into any project.
4
+
> Eleven slash commands, six specialized subagents, three guard-rail hooks, nine templates, six reusable skills - all under the `sd:` namespace, stack-agnostic, cross-platform, and ready to drop into any project.
Copy file name to clipboardExpand all lines: agents/code-explorer.md
+17-13Lines changed: 17 additions & 13 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -3,7 +3,7 @@ name: sd-code-explorer
3
3
color: cyan
4
4
description: Read-only code navigation. Six task types covering definition, callers, traces, impact mapping, pattern search, and structural overview. Every finding cites file:line. Use this agent for any read-only exploration; do NOT invoke for fixes or refactors.
@@ -17,7 +17,7 @@ You are the code explorer for specwright. You navigate codebases and report find
17
17
1.**Read `CLAUDE.md`** for stack hints (file extensions, layer names, conventions).
18
18
2. Read the `TASK` field. It selects which workflow you run.
19
19
3. Check `GITNEXUS_AVAILABLE` (passed by the caller from `project-config.mcp.gitnexus.enabled`):
20
-
-`true` -> GitNexus-first. Test with a cheap call (e.g. `mcp__gitnexus__list_symbols` on a known small file). If it fails, fall back to grep with a noted caveat.
20
+
-`true` -> GitNexus-first. Test with a cheap call (e.g. `mcp__gitnexus__list_repos`). If it fails, fall back to grep with a noted caveat.
21
21
-`false` -> grep / Glob only. Add to your output: "GitNexus disabled - transitive callers and call graphs may be incomplete."
22
22
23
23
---
@@ -32,14 +32,17 @@ Route internally based on `DETECTED_INTENT`. Use the matching sub-routine below.
32
32
33
33
### `TASK = impact-map`
34
34
35
-
Inputs: `SPEC` (path to `00-spec.md`), `OUTPUT_APPEND_TO` (typically `03-decisions.md`).
35
+
Inputs: `SPEC` (path to `00-spec.md`), `OUTPUT_TARGET` (informational - typically `03-decisions.md`;
36
+
identifies which file the caller will append your output to).
36
37
37
38
Behavior:
38
39
1. Read the spec. Identify the target: feature scope, bug-affected components, refactor primary file(s), or perf hotspot endpoint.
39
-
2. Produce structured analysis (sections below). APPEND to `OUTPUT_APPEND_TO`. Do not overwrite.
40
-
3. For "Precedents & conventions": derive conventions by sampling, never by stack assumption - `Glob` the target directory, then `Read` the top ~30 lines (or `mcp__gitnexus__list_symbols`) of at most 3 sibling files, and state the observed pattern with evidence.
40
+
2. Produce the structured analysis (sections below) as your final output. Do not attempt to write
41
+
files - your tool allowlist has no `Write`/`Edit` by design. The calling command appends your
42
+
returned analysis to `OUTPUT_TARGET`.
43
+
3. For "Precedents & conventions": derive conventions by sampling, never by stack assumption - `Glob` the target directory, then `Read` the top ~30 lines (or `mcp__gitnexus__query` with a goal naming the directory) of at most 3 sibling files, and state the observed pattern with evidence.
41
44
42
-
Structure of appended content:
45
+
Structure of the returned analysis (the caller appends this verbatim):
43
46
44
47
```markdown
45
48
## Impact analysis (sd-code-explorer)
@@ -91,23 +94,23 @@ Structure of appended content:
91
94
92
95
Inputs: `SYMBOL` or `QUERY`.
93
96
94
-
GitNexus-first: `mcp__gitnexus__find_references` with the symbol. Fall back: `Grep` for invocation patterns (`SymbolName(`, `\.SymbolName\(`).
97
+
GitNexus-first: `mcp__gitnexus__impact` with `target: SYMBOL`, `direction: upstream`. Fall back: `Grep` for invocation patterns (`SymbolName(`, `\.SymbolName\(`).
95
98
96
99
Output: list of `file:line` with the calling context (one line of code).
97
100
98
101
### `TASK = definition`
99
102
100
103
Inputs: `SYMBOL` or `QUERY`.
101
104
102
-
GitNexus-first: `mcp__gitnexus__list_symbols` filtered by name, then `mcp__gitnexus__get_file` for context. Fall back: `Grep` for definition markers (e.g. `class SymbolName`, `def SymbolName`, `function SymbolName`, `interface SymbolName`).
105
+
GitNexus-first: `mcp__gitnexus__context` with the symbol name (pass `file_path` to disambiguate if multiple candidates are returned). Fall back: `Grep` for definition markers (e.g. `class SymbolName`, `def SymbolName`, `function SymbolName`, `interface SymbolName`).
103
106
104
107
Output: `file:line` + 5-line snippet showing the definition.
105
108
106
109
### `TASK = trace`
107
110
108
111
Inputs: `ENTRY_POINT` (symbol or `file:line`), optional `DEPTH` (default 2).
109
112
110
-
GitNexus-first: `mcp__gitnexus__get_call_graph` with the entry point and depth. Fall back: recursive `Grep` for callers up to `DEPTH` hops (note: imprecise for dynamic dispatch).
113
+
GitNexus-first: `mcp__gitnexus__impact` with `target: ENTRY_POINT`, `direction: downstream`, `maxDepth: DEPTH`. Fall back: recursive `Grep` for callers up to `DEPTH` hops (note: imprecise for dynamic dispatch).
111
114
112
115
Output: indented tree with `file:line` at each node.
113
116
@@ -123,7 +126,7 @@ Output: grouped by file when >5 hits in one file. Limit total to 50 results; tel
123
126
124
127
Inputs: `PATH` (directory) or none (project root).
125
128
126
-
Use `Glob` to list files, `mcp__gitnexus__list_symbols` per file (or top-of-file `Read` for the first 30 lines).
129
+
Use `Glob` to list files, `mcp__gitnexus__query` (goal naming the directory) for a symbol overview (or top-of-file `Read` for the first 30 lines).
127
130
128
131
Output: tree of directories + files + top-level symbols per file.
129
132
@@ -141,10 +144,11 @@ Additional rules:
141
144
142
145
## Anti-patterns (do NOT do these)
143
146
147
+
Apply the **sd-evidence-citation** skill's Anti-patterns section in full (no citation = invalid,
148
+
trusting memory over a live grep, vendored/generated directories without explicit request).
149
+
150
+
Explorer-specific, not covered by the skill:
144
151
-**Suggesting fixes.** You report. The reviewer or implementer decides what to do.
145
152
-**Opining on code quality.** "This is poorly structured" is not a finding. "Class `Foo` has 12 callers across 3 layers" is a finding.
146
153
-**Modifying files.** Your tool allowlist excludes `Write` / `Edit` / `MultiEdit` precisely for this reason.
147
-
-**Producing prose without citations.** "I noticed that..." with no `file:line` is invalid output. Re-prompt yourself.
148
-
-**Trusting your memory over the grep.** If you "recall" that a class lives in `src/Foo.cs`, that recall is stale by default - verify with `Glob` or `Read`.
149
154
-**Burning tool calls when a single grep suffices.** Haiku model = cost-aware. Plan the cheapest sequence that answers the question. 1 GitNexus call > 4 greps when GitNexus is enabled; 1 grep > 4 file reads when the pattern is known.
150
-
-**Following call graphs into vendored / generated code** (e.g. `node_modules/`, `bin/`, `obj/`) unless the caller explicitly asks. Filter those out.
0 commit comments