pnpm's virtual-store layout doesn't hoist @gsd/* workspace scopes to
the parent node_modules, so the simple symlink-to-hoisted approach from
the original fix (#3529) left workspace packages unresolvable.
Detect when workspace scopes are missing from the hoisted root and
create a real node_modules directory with symlinks from both the hoisted
root (external deps) and internal root (workspace packages). A .gsd-merged
marker file skips rebuild on subsequent startups.
Restores behavioral tests deleted in the original PR and adds unit tests
for the pnpm merge path and scope detection logic.
Reported-by: @moekify
Fixes: #3564 (comment)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extension-provided models (e.g. claude-code/*) were unavailable during
findInitialModel() because pendingProviderRegistrations had not been
flushed yet, causing the fallback chain to select Google Gemini even
when the user explicitly configured claude-code as their default.
Three compounding issues fixed:
(A) Flush pendingProviderRegistrations in createAgentSession() before
findInitialModel() runs, so extension models are in the registry
when initial model selection happens.
(B) Re-apply the validated model to the session after
validateConfiguredModel() in both print and interactive CLI paths.
Previously, validation updated settingsManager but never called
session.setModel(), leaving the session on the wrong model.
(C) Update defaultModelPerProvider.anthropic from "claude-opus-4-6[1m]"
to "claude-opus-4-6" — the [1m] variant was removed from the model
registry when the base model was upgraded to 1M context, causing the
Anthropic fallback to silently fail and skip to Google.
Closes#3534
* fix(perf): share jiti module cache across extension loads (#2108)
Each extension was creating a new jiti instance with moduleCache: false,
causing shared dependencies to be recompiled for every extension. Use a
shared singleton with moduleCache: true so shared modules are compiled once.
Export resetExtensionLoaderCache() for test teardown and explicit reload.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: correct loader path in extension-load-perf test (4 → 3 levels up)
The test file is at src/tests/ (2 levels deep from repo root), so
fileURLToPath(import.meta.url) + 3x'..' reaches the repo root.
Using 4 levels exits the repo into the GitHub Actions workspace parent,
causing ERR_MODULE_NOT_FOUND for loader.js in dist/.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: use process.cwd() for loader path in perf test (source/compiled portability)
import.meta.url resolves to different depths in source (src/tests/) vs compiled
(dist-test/src/tests/), so relative '../' navigation produces the wrong path in
the build phase. process.cwd() is always the repo root in CI regardless of
where the test file is compiled to.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: trek-e <trek-e@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(resource-sync): prune removed bundled subdirectory extensions on upgrade
The managed-resources manifest and pruning system only tracked root-level
files, not subdirectory extensions. When a bundled subdirectory extension
like mcporter/ was removed from the bundle in a newer GSD version, the
previously-synced copy in ~/.gsd/agent/extensions/ persisted indefinitely,
causing tool name conflicts with its replacement (mcp-client/).
- Add installedExtensionDirs to the manifest alongside installedExtensionRootFiles,
recording directory names present in the bundled extensions dir at sync time.
- In pruneRemovedBundledExtensions, diff previous installedExtensionDirs against
current bundled dirs and rmSync({ recursive: true }) any that were removed.
- Add mcporter to the hardcoded stale-entry list for pre-manifest upgrades.
- Fix extension conflict error prefix: also match "conflicts with" (not just
"supersedes") so extension-vs-extension conflicts are classified as warnings
rather than hard errors.
Fixes#1955
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(resource-loader): repair mangled lines from conflict resolution
The Python regex used to resolve cherry-pick conflicts stripped trailing
newlines, causing declarations and comments to merge onto the same line.
Replace the file with the upstream/main version which contains all the
installedExtensionDirs logic correctly formatted.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(resource-loader): sweep all installed extension dirs not in current bundle
The manifest-based pruner only removed dirs it had previously recorded.
Extensions installed by pre-manifest versions (or manually) were never
tracked, so they survived upgrades. Add a sweep of the actual installed
extensions directory that removes any subdirectory absent from the current
bundle, regardless of manifest history.
Fixes the mcporter stale-dir regression test (#1972).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: check external-state DB path before symlink-resolved handler (#2952)
The external-state handler added in c609d813 was placed after the generic
symlink-resolved handler, which matches the same /.gsd/projects/<hash>/worktrees/
pattern and short-circuits to the wrong result. Move the external-state check
(which uses the more specific hex-hash regex) first so it takes precedence.
Fixes shared-wal test: external-state worktree path resolves to project state DB.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test: update db-path-worktree-symlink expectations for external-state (#2952)
/.gsd/projects/<hash>/worktrees/ paths now resolve to <hash>/gsd.db
after the external-state handler from #2952 was placed before the
symlink-resolved handler. On POSIX, getcwd() returns canonical paths so
<proj>/.gsd/projects/<hash>/worktrees/ would in practice appear as
~/.gsd/projects/<hash>/worktrees/ after OS symlink resolution — both
correctly handled by the external-state behavior.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: trek-e <trek-e@users.noreply.github.com>
* refactor(web): consolidate subprocess boilerplate into shared runner
Extract subprocess-runner.ts with runSubprocess<T>() and resolveModulePaths()
to replace identical execFile+Promise+JSON.parse callback blocks duplicated
across 12 web service files. Adds 30s default timeout to all subprocess calls.
Fixes#1888
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: check external-state DB path before symlink-resolved handler (#2952)
The external-state handler added in c609d813 was placed after the generic
symlink-resolved handler, which matches the same /.gsd/projects/<hash>/worktrees/
pattern and short-circuits to the wrong result. Move the external-state check
(which uses the more specific hex-hash regex) first so it takes precedence.
Fixes shared-wal test: external-state worktree path resolves to project state DB.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test: update db-path-worktree-symlink expectations for external-state (#2952)
/.gsd/projects/<hash>/worktrees/ paths now resolve to <hash>/gsd.db
after the external-state handler from #2952 was placed before the
symlink-resolved handler. On POSIX, getcwd() returns canonical paths so
<proj>/.gsd/projects/<hash>/worktrees/ would in practice appear as
~/.gsd/projects/<hash>/worktrees/ after OS symlink resolution — both
correctly handled by the external-state behavior.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: trek-e <trek-e@users.noreply.github.com>
Replace raw fileURLToPath in getDefaultPackageRoot with safePackageRootFromImportUrl
which returns null instead of throwing when the URL is not a valid local file URL.
This prevents the standalone bundle from crashing on Windows when import.meta.url
is baked in at build time with a Linux file:// path.
Co-authored-by: trek-e <trek-e@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(mcp-client): add OAuth auth provider for HTTP transport
MCP HTTP transport was created without any auth options, causing 401
errors when connecting to remote servers (Sentry, Linear, etc.) that
require OAuth or bearer token authentication.
Add a new auth module that builds StreamableHTTPClientTransport options
from two config fields:
- `headers`: static headers with ${VAR} env resolution (for bearer tokens)
- `oauth`: full OAuthClientProvider for servers implementing MCP OAuth
The config is parsed from .mcp.json / .gsd/mcp.json and passed through
to the SDK transport constructor.
Fixes#2160
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: retrigger CI
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: trek-e <trek-e@users.noreply.github.com>
Fixes#2075
The isBuiltIn check in detectExtensionConflicts used a path-based
heuristic that excluded paths containing /.gsd/agent/extensions/.
Since initResources() syncs all bundled extensions into that same
directory, the heuristic could never return true for bundled extensions,
preventing the "supersedes" hint from being appended. This caused
downstream cli.ts to display "Extension load error" instead of the
intended "Extension conflict" for benign precedence collisions.
Replace the path heuristic with an explicit bundledExtensionKeys set
(already computed by buildResourceLoader) threaded through to
detectExtensionConflicts. The set is matched against the extension
directory name extracted from the owner path.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: track remote-questions extension in managed-resources manifest
writeManagedResourceManifest only checked for index.js/index.ts when
deciding if a subdirectory is an extension. remote-questions uses mod.ts
as its entry point and was missed, causing it to be pruned on upgrades.
Also check for extension-manifest.json which is the canonical marker for
bundled extensions.
Fixes#2367
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: retrigger CI
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: trek-e <trek-e@users.noreply.github.com>
* docs: add provider setup guide and improve onboarding hints
Fixes#2161
Add docs/providers.md with step-by-step setup instructions for every
supported LLM provider: OpenRouter, Ollama, LM Studio, vLLM, SGLang,
and all built-in providers. Includes env var names, example configs,
common pitfalls, and verification steps.
Improve onboarding wizard:
- Add URL hints to provider selection list
- Show common local endpoints when choosing Custom (OpenAI-compatible)
- Add post-setup guidance for OpenRouter and custom endpoints
- Reference docs/providers.md for compat troubleshooting
Update cross-references in getting-started.md, troubleshooting.md,
docs/README.md, and help-text.ts to link to the new guide.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: verify config help mentions OpenRouter, Ollama, and docs/providers.md
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: trek-e <trek-e@users.noreply.github.com>
Move regression tests and override tests from standalone files into
the existing test files introduced by PR #666:
- resolve-config-value.test.ts: add REGRESSION #666 describe block
and setAllowedCommandPrefixes override tests
- url-utils.test.ts: add REGRESSION #666 describe block and
setFetchAllowedUrls override tests
- Delete: regression-666.test.ts, resolve-config-value-override.test.ts,
url-utils-override.test.ts
Same 59 tests, fewer files, tests live next to the code they test.
Two regression tests that prove the bug introduced by PR #666:
1. Non-default credential tool (sops) is silently blocked by the
hardcoded SAFE_COMMAND_PREFIXES with no way to override.
2. Private IP URL is silently blocked by isBlockedUrl() with no
way to allowlist.
Both tests use dynamic import to check for the override functions,
so they run cleanly on both main (where they fail) and this branch
(where they pass). Verified in a git worktree of main.
PR #666 introduced hardcoded SAFE_COMMAND_PREFIXES and SSRF URL
blocklists with no override mechanism. Users with non-standard
credential tools (sops, doppler, age, infisical) or needing to fetch
from internal URLs (self-hosted docs, VPN services) were silently
blocked with no recourse.
Add two global-only settings (ignored in project-level settings.json
to preserve the security property against malicious repos):
- allowedCommandPrefixes: replaces the built-in command allowlist
- fetchAllowedUrls: exempts hostnames from SSRF blocking
Both also support env var overrides (GSD_ALLOWED_COMMAND_PREFIXES,
GSD_FETCH_ALLOWED_URLS) for CI/container environments. Env vars
take precedence over settings.json.
Security model: global-only keys are stripped from project settings
at load time via stripGlobalOnlyKeys(), applied at all three
assignment points for this.projectSettings. The merge function
stays untouched — no future caller can accidentally skip stripping.
15 new tests covering override behavior, cache invalidation,
allowlist exemptions, and global-only enforcement.
On Windows, child_process.spawn() and execFile() open a visible console
window by default. The web server spawn, RPC bridge, browser opener, and
all 15 web service subprocess calls were missing windowsHide: true,
causing constant console window flashing when running gsd --web.
Closes#2628
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When an agent requests read(file, offset: 30) on a 13-line file, the
read tool threw "Offset 30 is beyond end of file" which propagated as
invalid JSON downstream during milestone completion. Now clamps the
offset to the last line and prepends a notice, allowing the agent to
continue with valid content.
Fixes both read.ts and hashline-read.ts variants.
Closes#3007
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Two root causes destroyed terminal state during normal navigation:
1. The pagehide handler fired a shutdown beacon unconditionally, but on
mobile/Safari tab switches pagehide fires with event.persisted=true
(bfcache entry). This killed the server and all PTY sessions when the
user merely switched browser tabs. Fix: check event.persisted and skip
the beacon when the page is being cached, not unloaded.
2. ShellTerminal used project-agnostic session IDs ("default"), so
switching projects and switching back either collided with the old
session or spawned a new one, losing terminal state. Fix: scope session
IDs by project path (e.g. "default:/path/to/project") so the server's
getOrCreateSession returns the existing live PTY on reconnect.
Closes#2701
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
`gsd auto` was not handled as a subcommand — it fell through to the
interactive TUI, which hangs indefinitely when stdin/stdout are piped
(non-TTY). Add `auto` as a recognized subcommand that rewrites argv
and delegates to `runHeadless(parseHeadlessArgs(...))`, matching the
existing `gsd headless auto` behavior.
Also adds `gsd auto` to TTY error hints and help text.
Closes#2732
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Closes#2711
Two changes fix the tab-reset-to-dashboard bug:
1. Remove the forced `gsd:navigate-view` dispatch to "dashboard" in
ProjectsPanel.handleSelectProject — this was unconditionally resetting
the view on every project switch.
2. Add a useEffect in WorkspaceChrome that resets `viewRestored` when
`projectPath` changes, so the per-project sessionStorage view restore
fires for the newly-selected project.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: defer model validation until after extensions register (#2626)
Extension-provided models (e.g. claude-code/claude-sonnet-4-6) were
silently overwritten on every startup because the model validation ran
before createAgentSession(), which is where extensions register their
models in the ModelRegistry. At validation time, extension models did
not exist in the registry, so the user's valid choice was replaced
with a built-in fallback.
Extract validation into validateConfiguredModel() and call it after
createAgentSession() in both print-mode and interactive-mode paths.
Closes#2626
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: align MinimalSettingsManager interface with SettingsManager
The MinimalSettingsManager interface used `string` for thinking level
types, but SettingsManager uses a specific union type and returns
`undefined`. This caused TS2345 at cli.ts lines 448 and 587.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Three root causes addressed:
1. PtyChatParser: user input echoed after a bare prompt line (e.g. "❯ \n"
followed by "hello\n") was misclassified as assistant content. Added
_awaitingInput flag that flips true on prompt boundary and classifies the
next content line as role=user.
2. Chat mode "looks stuck": when the session is idle (connected, not
streaming, has timeline content), no visual cue indicated GSD was waiting
for input. Added a "Ready for your input" indicator with a pulsing dot.
3. Transcript overflow misalignment: chatUserMessages was not trimmed when
liveTranscript/completedTurnSegments overflowed MAX_TRANSCRIPT_BLOCKS,
causing index-based interleaving to pair user messages with wrong
assistant responses.
Also exposed isAwaitingInput() on PtyChatParser so chat UIs can query
whether the session is waiting for user input, and widened the > and $
prompt marker regexes to match bare prompts after trimEnd strips trailing
whitespace.
Closes#2707
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The ensure-workspace-builds.cjs postinstall script falsely detected
workspace packages as stale in npm tarball installs. npm sets all
tarball entries to a canonical timestamp (Oct 26 1985), but extraction
ordering causes src/ files to appear 1-2 seconds newer than dist/
files. This triggered a rebuild attempt that either failed silently
(no tsc available) or — when tsc was globally installed — could
produce broken dist/ output, corrupting the known-good pre-built
files and causing the DefaultResourceLoader export error on startup.
The fix gates the src-vs-dist staleness check behind a .git directory
check: only development clones (with .git/) perform the timestamp
comparison. npm tarball installs (no .git/) only check for missing
dist/index.js, which is the safe and correct behavior.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: TÂCHES <afromanguy@me.com>
Community extensions must be placed in ~/.pi/agent/extensions/, not
~/.gsd/agent/extensions/ which is reserved for bundled extensions synced
from the gsd-pi package. Extensions placed in the wrong path are silently
ignored by the loader.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When gsd is spawned as an RPC bridge child process, stdout is a pipe
(process.stdout.isTTY === undefined). The TUI render loop would run at
~4,600 renders/sec writing ANSI escape codes to the pipe, consuming
500%+ CPU per process while idle.
Add isTTY guard to Terminal interface, ProcessTerminal.start(), TUI.start(),
and requestRender() so the entire render pipeline is skipped on non-TTY stdout.
RemoteTerminal (browser-backed) correctly reports isTTY=true.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The milestones list only refreshed on agent_end events, causing stale
milestone state during multi-turn agent execution. Add turn_end as a
workspace cache invalidation trigger so the UI reflects milestone
changes after each turn boundary.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The subprocess spawned by collectAuthoritativeAutoDashboardData always
starts with fresh module state (s.active === false), so the web UI
always showed "Start auto" even while auto mode was running. After
obtaining the subprocess result, reconcile active/paused state with
the on-disk session lock (.gsd/auto.lock) and paused-session metadata
(.gsd/runtime/paused-session.json).
Closes#2705
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When `gsd auto` is run with piped stdout (e.g. `gsd auto | cat` or
`gsd auto > file`), the TUI cannot render on a non-terminal output
stream, causing the process to hang indefinitely.
This fix:
- Detects piped stdout before entering interactive mode and redirects
`gsd auto` to headless mode automatically
- Extends the interactive mode TTY gate to also check process.stdout.isTTY
(previously only checked stdin), with a descriptive error message
- Adds `gsd headless` to the non-interactive alternatives hint
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The searchWithOAuth() function sent a request body that the Cloud Code
Assist API rejected with 400 INVALID_ARGUMENT. Two issues:
1. URL was missing ?alt=sse query parameter (endpoint returns SSE format)
2. Request body was missing the required userAgent field
Also adds regression tests that capture the fetch call and assert the
request URL and body match the Cloud Code Assist wire contract.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add configured remote channel (Discord/Slack/Telegram) as a checkmark
in the tools row alongside Brave/Answers/Jina. Remove verbose remote
status lines and duplicate display from header-renderer and register-hooks.
Previously, headless --verbose mode accumulated text_delta events into a
buffer and displayed a single truncated 120-char [thinking] line before
tool calls. The model's actual text responses between tool calls were
effectively invisible.
Changes:
- Stream text_delta and thinking_delta events directly to stderr in
verbose mode with [text] and [thinking] block markers
- No truncation — full model output is visible
- Fix non-verbose fallback: read from ame.delta (correct field) instead
of ame.text (always undefined for text_delta events)
- Track inTextBlock/inThinkingBlock state to properly close streaming
blocks before tool calls
- Expand summarizeToolArgs with support for async_bash, await_job,
cancel_job, find, ls, lsp, hashline_edit, subagent, browser_navigate,
and gsd_* tools
- Add streaming formatter functions: formatTextStart, formatTextEnd,
formatThinkingStart, formatThinkingEnd
- Update tests for new tool arg summarization and path field handling
Multi-turn commands (auto, next) have their own completion signals via
isTerminalNotification ("Auto-mode stopped..."/"Step-mode stopped...").
The execution_complete event fires after command setup before any real
work begins, causing these commands to exit immediately with zero work done.
Closes#2917
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
mapStatusToExitCode only handled "complete" but RPC v2 emits "completed",
causing all headless sessions to falsely timeout and restart.
Also emits milestone-ready notification in checkAutoStartAfterDiscuss so
headless parent can detect and chain into auto-mode.
Closes#2914
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
`gsd headless new-milestone --auto --verbose` now works — flags are
parsed regardless of position relative to the command word.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(test): wire src/resources/extensions/shared/tests/ into test:unit runner
The test:unit glob excluded src/resources/extensions/shared/tests/ entirely,
leaving format-utils.test.ts (and any future tests there) silently unfired.
- Add shared/tests/*.test.ts to the test:unit glob in package.json
- Export newestSrcMtime from ensure-workspace-builds.cjs (require.main guard
prevents side-effects on require) so the staleness logic can be tested
- Add src/tests/ensure-workspace-builds.test.ts covering newestSrcMtime:
non-existent dir, no .ts files, single file, max of multiple, recursion,
node_modules skip
Closes#2808
* perf(test): compile unit tests with esbuild and fix dist-test/node_modules
Replace per-file --experimental-strip-types with a single esbuild compilation
step (scripts/compile-tests.mjs) that compiles all src/ TypeScript to dist-test/
in ~3s, then runs the pre-compiled JS. Eliminates ~1.7s Node startup overhead
per test file.
- scripts/compile-tests.mjs: esbuild compilation, asset copy, .ts→.js rewrite,
stale file cleanup; creates dist-test/node_modules symlink so resource-loader.ts
resolves gsdNodeModules to a real path (fixes node-modules-symlink test failure)
- scripts/dist-test-resolve.mjs: ESM loader hook for @gsd/* bare specifiers and
.ts→.js fallback rewriting at runtime
- .gitignore: exclude dist-test/ from version control
- package.json: add test:compile script; update test:unit to compile-then-run;
update test:integration globs to cover new integration/ subdirectories
- worker-registry.ts: unref() cleanup timer so it does not keep the Node process
alive after tests complete
Closes#2858
* fix(test): update relative imports in tests/integration/ after directory move
When tests were moved from tests/ to tests/integration/ in the previous
commit, relative imports weren't updated. ../foo now resolves one level
too shallow.
Fix all 117 import paths across 43 test files:
- ../foo → ../../foo (source files at gsd/ level)
- ../../get-secrets-from-user.ts → ../../../ (at extensions/ level)
- ../../subagent/worker-registry.ts → ../../../ (at extensions/ level)
- ./marketplace-test-fixtures.js → ../marketplace-test-fixtures.ts
- ./test-helpers.ts → ../test-helpers.ts
typecheck:extensions now passes with zero errors.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(integration): set 10-minute timeout for integration test runner
build job takes ~7min on main. Without a global timeout, hanging tests
block the suite indefinitely. --test-timeout=600000 caps each test at 10min.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Revert "test(integration): set 10-minute timeout for integration test runner"
This reverts commit be77ead77d369ad8569292ae6b69ba56435f5433.
* fix(test): correct formatDuration(0) edge case and docker test root path
- formatDuration(0) now returns '0s' instead of '0ms' by guarding the
sub-second branch with ms > 0
- docker-template.test.ts root path goes ../../.. from dist-test/src/tests/
to reach project root instead of landing in dist-test/
- replace require() calls in skill-health.ts and visualizer-overlay.ts
with proper ES module imports
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(test): correct relative import paths in integration tests
All affected tests were one directory level off — importing from ../web/
and ../resources/ when the correct paths are ../../web/ and ../../resources/.
Tests live at src/tests/integration/, not src/tests/.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(test): add esbuild to root devDeps and wire dist-test-resolve hook
P1: esbuild was only in web/package.json — compile-tests.mjs requires it
at the root node_modules path, so CI failed on clean installs.
P2: dist-test-resolve.mjs existed but was never loaded; @gsd/* imports in
compiled tests resolved to installed workspace packages instead of freshly
compiled dist-test output. Add --import to test:unit.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deps): align esbuild version with lock file (0.25.12)
^0.27.4 didn't satisfy the existing lock file entry. Use the version
already present so npm ci passes without regenerating the lock file.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(test): correct all relative import depths in src/tests/integration/
Tests in src/tests/integration/ need 3 levels up (../../..) to reach
project-root dirs (web/, packages/) and 2 levels up (../..) to reach
src-level dirs (src/web/, src/cli-web-branch.ts).
Fixes:
- ../../web/lib/ → ../../../web/lib/ (Next.js app, not src/web/)
- ../../web/app/ → ../../../web/app/
- ../../packages/ → ../../../packages/
- ../cli-web-branch.ts → ../../cli-web-branch.ts
- ../web-mode.ts → ../../web-mode.ts
- ../resources/extensions/ → ../../resources/extensions/
- ci_monitor ROOT path: 2 levels up → 3 levels up
- web-responsive WEB_ROOT: 2 levels up → 3 levels up
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(test): use dot reporter for test:unit to reduce noise
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(test): switch test:unit reporter to tap
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(test): compact test reporter — silent on pass, failures + summary only
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(test): include shared/tests in test:coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(test): correct path depths in tests moved to integration/
Tests moved from tests/ to tests/integration/ need one extra ../
to reach the same source files. Also fix web component paths — those
files live at web/ not src/web/.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(test): fix web component paths in web-session-parity-contract
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(test): use process.cwd() for project root in docker-template test
Resolving relative to __dirname breaks under test:coverage which runs
source files directly from src/tests/ — needs ../.. not ../../..
(the extra level only exists in the compiled dist-test/ output).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* ci: retrigger CI
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Unify the Power Mode xterm light palette behind a shared helper and replace low-contrast ANSI white/yellow entries with contrast-safe values.
Add a regression test that guards both the readable light-theme palette and the shared helper wiring so the duplicated terminal palettes do not drift again.
Closes#2810
When devRoot pointed at a monorepo, discoverProjects scanned one level
deep and listed each workspace/package as a separate project. Now it
checks for monorepo markers (pnpm-workspace.yaml, lerna.json, turbo.json,
nx.json, rush.json, package.json workspaces) before scanning children.
If the root is a monorepo, it returns it as a single project entry.
- Add detectMonorepo() to bridge-service with support for 6 monorepo formats
- Add isMonorepo signal to ProjectDetectionSignals
- Update discoverProjects to short-circuit when root is a monorepo
- Show 'Monorepo' tag in project list UI
- Add 24 tests covering all monorepo detection scenarios
* feat: Migrated headless orchestrator to use execution_complete events,…
- "src/headless.ts"
- "src/headless-ui.ts"
- "src/tests/headless-v2-migration.test.ts"
GSD-Task: S06/T02
* test: Wired pi-coding-agent to re-export JSONL utils from @gsd/rpc-clie…
- "packages/pi-coding-agent/src/modes/rpc/jsonl.ts"
- "packages/pi-coding-agent/package.json"
- "packages/rpc-client/src/index.ts"
- "packages/rpc-client/src/jsonl.ts"
- "packages/rpc-client/src/rpc-client.ts"
- "packages/rpc-client/src/rpc-types.ts"
- "packages/rpc-client/src/rpc-client.test.ts"
- "packages/rpc-client/package.json"
GSD-Task: S06/T03
* feat: Wire --resume flag to resolve session IDs via prefix matching and…
- "src/headless.ts"
- "dist/headless.js"
GSD-Task: S01/T01
* test: Added 5 e2e integration tests proving headless JSON batch, SIGINT…
- "src/tests/integration/e2e-headless.test.ts"
GSD-Task: S01/T02
* test: Updated @gsd/rpc-client and @gsd/mcp-server to 2.52.0 with publis…
- "packages/rpc-client/package.json"
- "packages/mcp-server/package.json"
- "packages/rpc-client/.npmignore"
- "packages/mcp-server/.npmignore"
GSD-Task: S02/T01
* chore: auto-commit after complete-milestone
GSD-Unit: M002-gzq23a
* fix: revert jsonl.ts to inline implementation — @gsd-build/rpc-client not available at source-level test time in CI
The re-export from @gsd-build/rpc-client fails in CI because tests run against
TypeScript source (--experimental-strip-types) before any build step. The npm
dependency resolves to node_modules/ which requires dist/ to exist. Reverting
to the original inline implementation eliminates the cross-package dependency
for source-level imports.
* fix: use localStorage for auth token to enable multi-tab usage
sessionStorage is tab-scoped, so manually opened second tabs cannot
access the auth token delivered via URL fragment to the first tab.
localStorage is shared across all tabs on the same origin, and since
each GSD instance binds to a unique random port the origin already
scopes the token to that instance.
Also adds a `storage` event listener so already-open tabs pick up
token changes immediately.
Closes#2714
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: update web-auth-token test for localStorage migration
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>