bug-2801, bug-3072, bug-3321 each read .md workflow files from disk and
parse fenced code blocks with /\`\`\`bash\n(...)\`\`\`/ — on a Windows
checkout with autocrlf=true the file content is CRLF, the regex never
matches, code blocks come back empty, and the assertion fails with
"expected bash code blocks in workflow" (and similar).
Fix: accept optional \r before each literal \n in the fence delimiter
and split lines on /\r?\n/. Same pattern bug-2839 had with its local
parseFrontmatter copy (already migrated to the CRLF-aware shared helper
in 1be0e4e2). bug-2995 + tests/security-scan.test.cjs also match this
grep but use \n in literal-string fixtures (not file-content regexes);
not affected.
Validated: holodeck (ubuntu docker) 11224/0 pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(#2851): replace bare gsd-tools invocations with absolute path
`gsd-tools` is not a published bin entry — package.json declares only
get-shit-done-cc and gsd-sdk. The shipped invocation pattern is
`node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" <subcommand>`,
used by every other workflow file.
Two leaked bare invocations:
- get-shit-done/workflows/plan-phase.md §13e (gap-analysis)
— reported in #2851; gap-analysis silently skipped on every plan-phase run
- get-shit-done/workflows/ingest-docs.md §finalize (commit)
— caught by the new structural test; ingest-docs commit step was broken
Both updated to canonical absolute-path form.
Adds tests/bug-2851-workflow-bare-gsd-tools.test.cjs which parses every
markdown file under get-shit-done/workflows/, extracts shell-fenced code
blocks, tokenizes each line, and asserts no token in command position is
the bare string `gsd-tools` (the trailing `.cjs` is a different token).
The test also asserts plan-phase.md's gap-analysis call uses the canonical
`node …/gsd-tools.cjs` form.
Closes#2851
* fix(#2851): catch third bare gsd-tools call in ingest-docs.md init
After the first commit, the structural test was strengthened to detect
bare `gsd-tools` inside `$(...)` and backtick command-substitution forms.
The improved test surfaced a third leak:
ingest-docs.md:55: INIT=$(gsd-tools init ingest-docs)
Fixed to canonical form
INIT=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" init ingest-docs)
plus the standard `@file:` handoff line that every other workflow uses
when capturing INIT (required by tests/windows-robustness.test.cjs).
Updated tests/bug-2801-ingest-docs-handler.test.cjs to match either
the bare `gsd-tools init ingest-docs` or canonical
`gsd-tools.cjs" init ingest-docs` form — the test's intent is to verify
the dispatch handler is wired, not to lock the bare-bin form that #2851
removes.
Closes#2851
* test(#2851): tighten ingest-docs and gap-analysis assertions to canonical form
CodeRabbit caught two soft assertions in the regression tests:
1. tests/bug-2801: the init-ingest-docs assertion accepted both the
legacy bare `gsd-tools` form and the canonical node-path form.
Since #2851 is the fix that removes the bare form, the test should
only accept the canonical absolute-path invocation. Switched to
parsed-bash-block extraction with an anchored regex on the full
`node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs"` path.
2. tests/bug-2851: the gap-analysis assertion used two loose
.includes()/word-boundary checks. Replaced with a single
assert.match() against the full canonical path so non-canonical
forms fail.
* test(#2851): env-assignment skip accepts lowercase identifiers too
CodeRabbit caught: the cmdIdx-skip regex /^[A-Z_][A-Z0-9_]*=/ only
matched uppercase variable names, so a line like `tmp=1 gsd-tools init`
would tokenize to ['tmp=1','gsd-tools','init'], the regex would fail on
'tmp=1', cmdIdx would stay at 0, and the command-position check would
compare 'tmp=1' against 'gsd-tools' — false negative.
POSIX shell variable names are [A-Za-z_][A-Za-z0-9_]*. Widen the regex
to match the actual lexical rule. Existing uppercase forms still work
(FOO=bar gsd-tools); now lowercase forms (tmp=1 gsd-tools) and mixed
case forms are also detected.
* fix(#2801): add ingest-docs handler to gsd-tools init dispatch
The `/gsd-ingest-docs` workflow was broken because `workflows/ingest-docs.md`
called `gsd-sdk query init.ingest-docs` but the installed binary is `gsd-tools`,
and `gsd-tools init` had no `ingest-docs` case in its dispatch switch.
- Added `cmdInitIngestDocs` function to `init.cjs` and exported it; returns
`project_exists`, `planning_exists`, `has_git`, `project_path`, `commit_docs`
- Added `case 'ingest-docs'` to the `init` switch in `gsd-tools.cjs`
- Updated `workflows/ingest-docs.md` to call `gsd-tools init ingest-docs`
(line 55) and `gsd-tools commit` (line 292) instead of `gsd-sdk query ...`
- Regression test: `tests/bug-2801-ingest-docs-handler.test.cjs`
Closes#2801
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#2801): address CodeRabbit — commit_docs assertion, broader gsd-sdk detection, bash fence
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>