QA11-1: Expand recursive-scan ignore set to skip common heavyweight
folders (.venv, venv, Pods, bin, obj, .gradle, DerivedData, out)
so the bounded scan is far less likely to exhaust its budget before
reaching relevant nested project files.
QA11-2: Remove the arbitrary 10-file cap from FastAPI dependency reads.
All discovered requirements.txt / pyproject.toml files within the bounded
scan are now checked, eliminating traversal-order dependence in
multi-service repos.
QA11-3: Normalize safe nested project markers from the recursive scan
back into PROJECT_FILES markers (e.g. nested next.config.ts, manage.py,
requirements.txt, prisma/schema.prisma, app/build.gradle) while keeping
noisy root-only markers like package.json and generic build.gradle
root-only. Add regression tests for these nested layouts and Android
root-only exclusion behavior.
QA8-1: Add case-insensitivity test for FastAPI detection using PyPI
canonical name 'FastAPI' (mixed case) in pyproject.toml.
QA8-2: False positive — comment matching is acceptable tradeoff;
mentions of 'fastapi' in requirements comments almost always correlate
with actual FastAPI usage or intent.
QA8-3: Empty catch block is trivially correct; skipping test for it.
Replace the lazy 'no brownfield detection' approach with proper
dependency-based detection. Scan requirements.txt and pyproject.toml
for the 'fastapi' package name (case-insensitive word-boundary match)
using the existing readBounded() utility (64KB cap).
Adds 'dep:fastapi' synthetic marker to detectedFiles when found,
which the FastAPI skill pack matches via matchFiles: ['dep:fastapi'].
This ensures only actual FastAPI projects get the pack recommended,
not all Python projects.
Tests: 3 new detection tests (requirements.txt, pyproject.toml,
negative Django case) + 1 new catalog test (dep:fastapi matching).
Total: 50 detection + 17 catalog + 5 activation + 12 smoke = 84.
QA6-1: Add 16 matchPacksForProject unit tests covering language match,
file match, Xcode platform match, matchAlways, and isolation checks
(FastAPI not in generic Python, Spring Boot not via language alone,
Unity/Godot don't cross-contaminate).
QA6-2: Remove FastAPI from brownfield auto-detection entirely — no
matchFiles or matchLanguages. Available via greenfield stack selection
or manual install only, since no FastAPI-specific file marker exists
on disk.
QA6-3: Remove matchLanguages from Java & Spring Boot pack so it only
triggers via matchFiles (pom.xml, build.gradle*). Prevents Android
projects from getting Spring Boot recommended via language match.
QA6-4: False positive — F# gets 'csharp' label cosmetically but the
.NET packs are correct for F# developers.
QA5-1: Remove matchLanguages from FastAPI pack so it only triggers via
file-based detection (matchFiles), not for all Python projects. Aligns
with Django precedent of framework-specific file matching.
QA5-2: Add requirements.txt to pytest verification command check so
requirements.txt-only Python projects get pytest suggested.
QA5-3: Add FastAPI greenfield stack entry (packs: FastAPI + Python +
Python Advanced) for parity with Django.
QA5-4: False positive — Vue src/ scanning is non-recursive by design;
all standard Vue scaffolds place App.vue directly in src/.
QA5-5: Add 11 new detection tests covering Vue .vue scanning, Vue CLI
config, requirements.txt Python detection, Android app/build.gradle,
Unity, Godot, Airflow, Kubernetes Helm, Blockchain Hardhat, CI/CD
.github/workflows, and Tailwind config. Test count: 36 → 47.
QA4-1: Split Game Development into separate Unity and Godot packs to
avoid cross-contamination between engines.
QA4-2: Add requirements.txt to Python base pack matchFiles so Python
Advanced doesn't trigger without the base pack.
QA4-3: Add Prisma to fullstack-js greenfield stack to differentiate
it from react-web.
QA4-4: Remove Spring Boot from Android greenfield stack — server-side
Java framework is irrelevant for Android development.
QA4-5: Extract FastAPI into its own pack (consistent with Django being
separate) instead of bundling in Python Advanced.
QA4-6: Add airflow.cfg to PROJECT_FILES and Data Engineering matchFiles
so Airflow-only projects also trigger data engineering skills.
QA4-7: Add Tailwind CSS to react-web, fullstack-js, nextjs, and
svelte greenfield stacks.
QA4-8: Add Unity and Godot as separate greenfield stack options.
QA4-9: Fix Vue.js detection for non-Nuxt projects by adding .vue
file extension scanning (scans src/ for *.vue files), vue.config.*
to PROJECT_FILES, and *.vue synthetic marker to Vue.js pack matchFiles.
QA4-10: Add app/build.gradle and app/build.gradle.kts to LANGUAGE_MAP
for Android project language detection.
- Fix Windows fd leak: close markerFd before unlinkSync in catch block
- Increase pbxproj read limit from 256KB to 1MB (SDKROOT appears after
file references/build phases in large projects)
- Fix matchPacksForProject docstring: returns catalog order, not sorted
- Add legacy dir to skill telemetry (captureAvailableSkills, detectStaleSkills)
so pre-migration skills appear in health/telemetry reports
Greenfield projects (empty directories) no longer ask users to pick a tech
stack during init. Non-technical users may not know their architecture yet.
Instead, only essential packs (find-skills, skill-creator, agent-browser,
document handling) are installed. Stack-specific skills auto-detect later
via brownfield detection once the LLM creates project files.
GREENFIELD_STACKS retained as a reference for programmatic use by the LLM
and the /gsd skills command.
- Add Firebase, Azure, and AWS skill packs to curated catalog
- Add cloud config files (firebase.json, cdk.json, samconfig.toml, serverless.yml)
and React Native markers (metro.config.js/ts, react-native.config.js) to detection
- Fix React Native detection to use matchFiles instead of broad language match
- Add matchAlways flag for essential packs so they are offered to all projects
- Add Firebase/AWS/Azure to greenfield tech stack wizard
- Improve Xcode detection: scan ios/macos/app subdirs, use bounded fd read,
set primaryLanguage=swift when xcodeproj found, handle SDKROOT=auto via
SUPPORTED_PLATFORMS fallback
- Fix migration: handle symlinked skill dirs, use atomic wx flag for race safety
Catalog changes:
- Added: find-skills (#1 on skills.sh, 657K installs), shadcn/ui (#5,
130K), skill-creator (#26, 98K), agent-browser (#23, 120K)
- Removed from React pack: web-design-guidelines (thin wrapper that
just fetches an external URL; redundant with the Vercel ecosystem
version and userinterface-wiki)
- The old bundled skills (18 total, 11 web-frontend-focused) are no
longer force-synced. The catalog now points to the better ecosystem
versions from Vercel, Anthropic, and shadcn.
Reasoning: The original bundled skills were heavily web-frontend biased
with significant overlap (3 UI polish skills, 4 web audit skills). The
catalog should surface the highest-quality ecosystem skills rather than
force-installing duplicative bundled ones.
Existing GSD users have skills in ~/.gsd/agent/skills/ that would
silently vanish after the directory switch. This adds:
1. One-time migration in initResources() — copies skill directories
from ~/.gsd/agent/skills/ to ~/.agents/skills/ (collision-safe,
writes .migrated-to-agents marker so it runs at most once).
2. Legacy fallback reads in loadSkills() and getSkillSearchDirs() —
the old directory is scanned as a low-priority fallback so skills
work immediately, even before the migration runs on next restart.
The old directory is NOT deleted — users can safely downgrade to a
pre-migration GSD version without losing skills.
Replace boolean hasXcodeProject with xcodePlatforms array that reads
SDKROOT values from *.xcodeproj/project.pbxproj files. iOS skill packs
now only match when SDKROOT=iphoneos, so macOS / watchOS / visionOS /
tvOS Xcode projects won't get iOS-specific skills.
Also splits the monolithic "Swift / iOS" pack into 8 granular bundles
matching dpearson2699/swift-ios-skills plugin structure:
- SwiftUI + Swift Core (any Swift project)
- iOS App Frameworks, Data Frameworks, AI & ML, Engineering,
Hardware, Platform (iphoneos projects only)
Adds batched installation to minimize npx invocations when multiple
packs share the same repo.
Stop force-syncing bundled skills to ~/.gsd/agent/skills/ on every launch.
Instead, use ~/.agents/skills/ (the industry-standard skills.sh directory)
as the primary global skills location, and .agents/skills/ for project-local
skills.
Changes:
- loadSkills() now scans ~/.agents/skills/ (global) and .agents/skills/ (project)
instead of ~/.gsd/agent/skills/ and .gsd/skills/
- initResources() no longer syncs src/resources/skills/ → ~/.gsd/agent/skills/
- skill-discovery, skill-telemetry, skill-health, preferences-skills all updated
to use the ecosystem directory
- New skill-catalog.ts: curated skill packs mapped to tech stacks, with
brownfield auto-detection and greenfield tech stack selection
- Init wizard gains a skill installation step that presents relevant packs
and installs via `npx skills add`
- Export ECOSYSTEM_SKILLS_DIR and ECOSYSTEM_PROJECT_SKILLS_DIR from pi-coding-agent
Fixes#2004
* test: add assertion messages to fix Assertion Roulette in GSD tests
Add descriptive messages to multi-assertion tests where a bare failure
output ("expected true, got false") wouldn't identify which assertion
broke. Affected tests: auto-secrets-gate, search-tavily, search-provider-
command, tavily-helpers.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test: fix Eager Test smell in captures and worktree-manager tests
- Split captures: loadPendingCaptures test — extracted loadAllCaptures
assertion into its own focused test
- Refactor worktree-manager: replace monolithic main() script with 11
isolated test() calls, each with its own repo setup via helpers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test: add assertion messages to remaining test files
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test: fix contract test gate, dynamic roots, and shared fetch helpers
- Fix reject-notice sub-test gated on outcome.kind (actual) instead of
expectedKind (map value) in web-command-parity-contract.test.ts
- Restore dynamic loop over registered non-gsd passthrough roots with
an explicit count assertion so new registrations fail loudly
- Extract normalizeHeaders/parseJsonBody to src/tests/fetch-test-helpers.ts
and import in both search-tavily and llm-context-tavily tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Use realpathSync.native() on Windows in canonicalizeExistingPath to resolve
8.3 short names (RUNNER~1 → runneradmin). Fixes isInheritedRepo path
comparison failures on Windows CI.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The roadmap-done condition checked whether the missing-summary issue was
detected in the issues array, but at fixLevel="task" the summary is
detected and never fixed (deferred via COMPLETION_TRANSITION_CODES).
This caused the roadmap checkbox to be marked without the summary on
disk, making deriveState() skip the summarizing phase and hard-stop at
validating-milestone.
Replace the issues.some() fallback with an existsSync re-check so the
roadmap is only marked when the summary actually exists — either
pre-existing or created earlier in the same doctor run.
Fixes#1910
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
mergeMilestoneToMain now detects when the squash-merge commit contains
only .gsd/ metadata files and no actual code changes. The worktree
resolver surfaces a clear warning so users know the milestone summary
may describe planned work that was never implemented.
The complete-milestone prompt now requires the LLM to verify code
changes exist on the branch before declaring verification passed.
Fixes#1906
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Resolve Windows 8.3 short paths (RUNNER~1 → runneradmin) via realpathSync.native()
and use shell mode for .bat/.cmd files in worktree post-create hooks. Fixes
pre-existing windows-portability CI failure.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Unify dispatch rules and hooks into a flat rule registry, add structured event journal with causal tracing, expose journal query as an LLM tool, and adopt gsd_concept_action tool naming.
- RuleRegistry class absorbs dispatch rules + hooks into UnifiedRule objects with common when/where/then shape
- post-unit-hooks.ts refactored from 524 lines → 90-line thin facade delegating to the registry
- Event journal emits structured JSONL events with per-iteration flowId grouping and causedBy chains
- gsd_journal_query LLM-callable tool for AI self-debugging of autonomous runs
- 4 DB tools renamed to gsd_concept_action pattern with backward-compatible aliases
- 164 new tests, zero regressions
Closes#1763, closes#1764, closes#1766
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add made_by attribution field to decisions (human/agent/collaborative)
Add a 'made_by' field to the Decision type that tracks whether a
decision was made by the human, the agent, or collaboratively. This
enables ADR-style accountability — you can always tell who actually
made each call.
Schema:
- New DecisionMadeBy type: 'human' | 'agent' | 'collaborative'
- DB schema v3 → v4: ALTER TABLE decisions ADD COLUMN made_by
- Existing decisions default to 'agent' (backward compatible)
- DECISIONS.md gains a 'Made By' column
- Parser handles old 7-column format gracefully (defaults to 'agent')
Surfaces updated:
- gsd_save_decision tool accepts optional made_by parameter
- Markdown generator/parser round-trips the new column
- Prompt formatter shows attribution in LLM context
- Compact formatter includes made_by in pipe-separated output
- Worktree reconciliation includes made_by in conflict detection + merge
Tests: 476 assertions across 9 test suites, all passing.
* fix(gsd-db): resolve CI failures and address review findings
- Update memory-store.test.ts to expect schema version 4
- Recreate active_decisions view in v4 migration to pick up new made_by column
- Handle missing made_by column in older worktrees during reconciliation
- Optimize VALID_MADE_BY Set by moving it outside the parser loop
* fix(types): resolve missing made_by property errors in context-store and tests
Next.js 16 auto-detects web/proxy.ts as middleware, gating all /api/*
routes behind bearer token validation. The token was only cached in
memory (lost on page refresh) and extracted from the URL hash fragment
(cleared after first extraction). This caused 401 errors on page
refresh and broke the sendBeacon shutdown call which cannot set
custom headers.
Changes:
- Persist the auth token to sessionStorage after extracting from the
URL fragment so it survives page refreshes within the same tab
- Fall back to sessionStorage when the URL hash is absent (refresh,
bookmark without hash)
- Pass the auth token as a _token query parameter in the sendBeacon
shutdown call since sendBeacon cannot set Authorization headers
- Add regression tests for token persistence, sessionStorage fallback,
and sendBeacon authentication
Fixes#1851
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Several test files used assert.ok(Array.isArray(x)) or assert.ok(result)
patterns that verify structure/existence without checking actual values.
These pass even when the code returns wrong data.
- web-diagnostics-contract: Array.isArray() checks → deepEqual([], [])
for fields constructed as empty; DoctorFixResult uses deepEqual(["fix1"])
instead of Array.isArray + length; InstanceType<typeof GSDWorkspaceStore>
for type assertions from dynamic import
- skill-lifecycle: computeStaleAvoidList → deepEqual(result, []) since
nonexistent path must return empty
- blob-store: remove redundant assert.ok(retrieved) before deepEqual
- discovery-cache: assert.ok(entry) existence check → verify models[0].id
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Three changes to prevent data loss and persistent doctor errors in the
worktree merge-back lifecycle:
1. After nativeCommit in mergeMilestoneToMain, explicitly delete
.git/SQUASH_MSG. The native libgit2 path and git commit -F - on
some versions do not auto-remove it, causing doctor to report
corrupt_merge_state on every run.
2. Before worktree removal (step 11), check for uncommitted changes
and force a final auto-commit if dirty. This prevents code files
written by task agents from being destroyed by git worktree remove.
3. Invalidate the nativeHasChanges 10-second cache before the
post-unit auto-commit in auto-post-unit.ts. A stale false result
causes autoCommit to skip staging entirely.
Fixes#1853
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The buildRecoveryContext callback in auto/phases.ts returned an empty
object instead of a valid RecoveryContext. When the idle watchdog detected
a stalled tool and called recoverTimedOutUnit, basePath was undefined,
causing join(undefined, ".gsd") to throw "The path argument must be of
type string. Received undefined". The error left the session permanently
hung because the unit promise was never resolved.
Fixes#1855
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When .gsd/ is a symlink to an external state directory, git registers
worktrees at the resolved (real) path. If syncStateToProjectRoot later
creates a real .gsd/ directory that shadows the symlink, worktreePath()
computes a local path that diverges from git's registered path. The
stale local directory passes existsSync but is not a git worktree, so
nativeWorktreeRemove fails silently.
removeWorktree now queries nativeWorktreeList to find the actual
git-registered path by matching on branch name before attempting
removal, falling back to the computed path if the lookup fails.
Fixes#1852
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Node v24 forbids --experimental-strip-types for files under node_modules/.
When GSD is globally installed, all src/ files live under node_modules/gsd-pi/,
causing every subprocess worker to crash with ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING.
Bug 1: Extract resolveTypeStrippingFlag() into src/web/ts-subprocess-flags.ts.
When the package root is under node_modules/ and Node >= 22.7, the function
returns --experimental-transform-types (which handles node_modules paths).
All 15 service files and cli-entry.ts now call this function instead of
hardcoding --experimental-strip-types.
Bug 2: waitForBootReady() now tracks consecutive 5xx responses and aborts
after 3 in a row, including the response body in the error message.
Connection-level errors (transient during cold start) reset the counter.
Bug 3: The /api/boot route handler now wraps collectBootPayload() in
try/catch and returns { error: message } with status 500, matching the
error response pattern used by other API routes.
Fixes#1849
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add `/gsd fast [on|off|flex|status]` command for toggling OpenAI service
tiers, with `supportsServiceTier()` gating so the status bar icon only
appears on models that actually support service tiers (gpt-5.4 variants).
Fixes#1848
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- preferences.test.ts: hook config tests were testing Math.max/min and a
locally-constructed Set (testing JS builtins, not production code); replaced
with validatePreferences calls that exercise real clamping in
preferences-validation.ts and action validation for pre_dispatch_hooks.
assert.ok(prefs) existence checks replaced with assert.notEqual(prefs, null).
- routing-history.test.ts: removed assert.ok(history) and assert.ok(pattern)
guards that only verified object existence; assertions now go directly to
the values that matter.
* fix(auto): use PROJECT_FILES from detection.ts in worktree health check
The worktree health check introduced in #1833 hard-coded package.json
and src/ as the only valid project markers, blocking auto-mode dispatch
for Rust (Cargo.toml), Go (go.mod), Python (pyproject.toml), and 14
other ecosystems. Replace the JS-centric heuristic with the shared
PROJECT_FILES array from detection.ts which already covers 17+
ecosystems.
Fixes#1843
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update test assertion to match new project files message
The health check now says "no recognized project files" instead of
"no package.json or src/" after broadening to PROJECT_FILES.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When tasks are [x] done but no T##-SUMMARY.md exists, doctor unchecks
the tasks but left the slice [x] done in the roadmap. The state machine
skips done slices, so unchecked tasks never re-execute and doctor fires
again on every start — infinite loop.
After unchecking tasks via task_done_missing_summary, also uncheck the
slice in the roadmap so the state machine re-enters the executing phase.
Fixes#1850
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: defend exit path against ESM module cache mismatch (#1839)
Wrap the stopAuto import/call in exit-command.ts with try/catch so
that a mid-session gsd-pi update (which causes stale ESM cache for
native-git-bridge.js exports) does not crash the /exit handler.
A warning is emitted instead. The user's work is already saved;
this path is cleanup only.
Fixes#1839
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use "warning" not "warn" for notify severity type
TS2345: "warn" is not in the severity union type.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update test assertion to match "warning" severity
The source was corrected to "warning" (valid union type), test must match.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wire CLI flags through parseCliArgs → runWebCliBranch → launchWebMode
so users can bind to a custom host/port and whitelist CORS origins for
LAN/Tailscale access.
- Add webHost, webPort, webAllowedOrigins to CliFlags
- Parse --host, --port (validated 1-65535), --allowed-origins (csv)
- Forward into launchWebMode options
- Set GSD_WEB_ALLOWED_ORIGINS in subprocess env when provided
- Add allowedOrigins to WebModeLaunchOptions
Usage: gsd --web --host 0.0.0.0 --port 8080 --allowed-origins http://192.168.1.10:8080Closes#1847
When GSD is launched in a new empty directory that happens to be inside
an existing git repo (e.g. mkdir ~/Projects/newproject where ~/Projects
has a .git), repoIdentity() resolves to the parent repo's hash and
loads milestones from an unrelated project.
Add isInheritedRepo() to detect when basePath inherits a parent repo's
git root without having its own .gsd. When detected, git init creates
an independent repo so the directory gets a unique identity hash.
Legitimate subdirectory access (cd src/ inside an existing GSD project)
is preserved — the check only triggers when the parent repo has no .gsd.
Closes#1639
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the worktree HEAD detaches and advances past the named milestone
branch, the branch ref becomes stale. The squash merge only captured
the stale ref, silently orphaning all commits between the branch ref
and the actual worktree HEAD.
Before the squash merge, compare the milestone branch ref with the
worktree's actual HEAD. If the branch ref is an ancestor of the
worktree HEAD, fast-forward the branch ref. If they have diverged,
throw a clear error instead of silently losing commits.
Guarded by worktreeCwd !== originalBasePath_ so non-worktree merge
paths (e.g. parallel-merge) are unaffected.
Fixes#1846
On Windows, paths embedded in bash command strings have backslashes
stripped by the shell (e.g. C:\Users\user becomes C:Useruser), causing
cd and other commands to fail silently. This left ~1.4 GB orphaned
worktree directories after milestone completion.
- Normalize all paths to forward slashes before embedding in the
subagent cmux bash script (cd, tee, process args)
- Add post-teardown orphan detection: warn and attempt rmSync fallback
if the worktree directory persists after removeWorktree
- Add regression tests for Windows path normalization
Closes#1436
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove hardcoded /^M\d{3}/ regex filter from syncGsdStateToWorktree and
syncWorktreeStateBack so milestone directories with non-standard names
(e.g. sprint-alpha, M001-abc123) are synced between main and worktree.
Closes#1547
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the ## Slices section exists but contains H3 prose headers instead of
checkboxes, parseRoadmapSlices returned an empty array because the prose
fallback was only invoked when the ## Slices heading was entirely absent.
Now, when the checkbox parser finds zero slices, it falls through to
parseProseSliceHeaders as a second-chance fallback.
Also adds a missing_slice_dir diagnostic in doctor.ts when resolveSlicePath
returns null, with auto-fix via mkdir.
Fixes#1711
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>