The codebase preferences block was accepted as a known key but never
validated or assigned in validatePreferences(), causing all user-configured
codebase defaults to be silently discarded. Adds validation for
exclude_patterns (string[]), max_files (positive int), and collapse_threshold
(positive int) with unknown-key warnings and 4 new tests.
Add configurable codebase map options via preferences.md (exclude_patterns,
max_files, collapse_threshold), expose --collapse-threshold as a CLI flag,
and auto-generate CODEBASE.md during project init for instant agent orientation.
Closes#3509
The ensureDbOpen catch block now logs via logWarning with error message
instead of structured diagnostic object. Update source-level assertion
to match the new pattern.
Update existing workflow-logger tests to use logError for audit
persistence assertions (warnings are now ephemeral). Add void
expression to empty catch blocks in detectMainBranch to satisfy
the no-empty-catch CI check.
Only persist error-severity entries to audit-log.jsonl (warnings stay
ephemeral in stderr + buffer). Sanitize persisted entries with message
truncation and context field allowlisting. Demote expected main/master
branch probe failures to silent control flow. Remove JSON.stringify of
diagnostic objects embedding cwd/paths in warning messages.
Addresses Codex adversarial review findings on workflow-logger migration.
workflow-events.ts: stop logging raw event line content to audit log —
log byte length only to avoid persisting potentially sensitive payload
fragments to .gsd/audit-log.jsonl.
parallel-orchestrator.ts: revert worker NDJSON parse failure to silent
drop — non-JSON lines (progress text, tool output) are expected in
worker stdout and logging each one creates I/O pressure and audit log
bloat in the parallel execution hot path.
Replace raw process.stderr.write(), console.error(), and empty catch
blocks across 50 GSD files with structured logWarning/logError calls
from the centralized workflow-logger system.
Add 13 new LogComponent types to cover all subsystems: recovery,
session, prompt, dashboard, timer, worktree, command, parallel, fs,
bootstrap, guided, registry, renderer.
Every migrated catch block now automatically:
- Shows in terminal (stderr) with component tag
- Gets buffered for auto-loop stuck-detection summary
- Persists to .gsd/audit-log.jsonl for post-mortem analysis
Update regression test to verify catch blocks use workflow-logger
instead of raw stderr/console, covering auto-mode files and all
explicitly migrated infrastructure files.
Closes#3506
Supersedes the approach in #3496
- Stop/backtrack guard now calls pauseAuto before marking captures executed,
and returns break on any exception to prevent silently dropping user halt intent
- Backtrack target parsing excludes current milestone ID and rejects ambiguous
multi-target strings instead of guessing first match
- Fixed gsd_skip_slice parameter names in rethink prompt (milestone_id → milestoneId)
Auto-mode has empty catch blocks across 11 files that silently
swallow errors. When these operations fail (DB writes, git commands,
file sync, worktree operations), the error is lost and downstream
systems see stale or inconsistent state — leading to stuck loops,
phantom milestones, and silent data loss.
Replace every empty catch with a process.stderr.write() call that
logs the operation context and error message. Format:
gsd [filename]: <operation> failed: <error.message>
For catches already annotated with /* non-fatal */ or /* best-effort */
comments, the logging is added alongside the annotation to preserve
the original intent while making failures observable.
Adds a regression test that scans all auto-mode source files and
asserts no empty catch blocks remain.
Files modified (11):
auto-worktree.ts, auto.ts, auto-recovery.ts, auto-prompts.ts,
auto-dashboard.ts, auto-start.ts, auto-timers.ts, auto-post-unit.ts,
auto-dispatch.ts, auto-unit-closeout.ts, auto/phases.ts
No behavioral changes — only diagnostic output added.
Addresses #3348, addresses #3345
Users with existing lsp.json overrides referencing the old
"kotlin-language-server" key would silently lose their Kotlin
LSP config after the rename to "kotlin-lsp". LEGACY_ALIASES
map remaps old keys during mergeServers() so overrides still
merge correctly.
Exercises the goNextOrSubmit → notes auto-open path to ensure:
- Enter after typing a note advances instead of looping
- Empty notes still trigger the auto-open
- Normal option selection is unaffected
Fixes#3502
goNextOrSubmit() unconditionally reopened the notes field whenever the
cursor sat on the "None of the above" slot, even after the user had
already typed a note and pressed Enter. This trapped users in an
endless loop where Enter always bounced back to notes mode.
Add a `!states[currentIdx].notes` guard so the auto-open only fires
when notes are still empty.
Fixes#3502
The feature branch intends capability_routing to default to true when
routing is enabled. Conflict resolution incorrectly kept the false
default from the earlier commit.
- Full pipeline with capability_routing: true returns capability-scored decision
- capability_routing: false falls back to tier-only with no capabilityScores
- Single eligible model (pinned) skips scoring and uses tier-only
- Unknown model gets uniform score of 50 and competes in scoring
- capabilityOverrides change scoring outcome in scoreEligibleModels
- capabilityOverrides pass through resolveModelForComplexity to STEP 2
- Regression guards: routing disabled, unknown model, no-downgrade-needed all pass
- All 51 tests pass (42 existing + 9 new integration)
- Fire pi.emitBeforeModelSelect() in selectAndApplyModel before resolveModelForComplexity
- Hook override bypasses capability scoring entirely with tier-only selectionMethod
- Verbose output shows capability-scored breakdown: model scores sorted descending
- Add loadCapabilityOverrides() to model-router.ts for deep-merge with built-in profiles
- Extend resolveModelForComplexity signature with optional capabilityOverrides parameter
- Pass capabilityOverrides through to scoreEligibleModels in STEP 2
- Add before_model_select handler registration inside registerHooks()
- Handler returns undefined (no override) to let capability scoring proceed
- Comment references ADR-004 for traceability
- Serves as documentation and ensures event type is registered for Plan 05 wiring
- Add BeforeModelSelectEvent interface and BeforeModelSelectResult type to types.ts
- Add on('before_model_select') subscription overload to ExtensionAPI interface
- Add emitBeforeModelSelect() method to ExtensionAPI interface and ExtensionRuntimeState
- Implement emitBeforeModelSelect() on ExtensionRunner using invokeHandlers (first-override-wins)
- Bind runner's emitBeforeModelSelect into shared runtime at construction time
- Wire emitBeforeModelSelect delegation through createExtensionAPI in loader.ts
- Pass unitType and classification.taskMetadata as 5th and 6th args to resolveModelForComplexity
- Completes end-to-end data pipeline: classifier extracts metadata, attaches to ClassificationResult, auto-model-selection passes through to router for capability scoring
- Add unitType and taskMetadata optional params to resolveModelForComplexity
- Replace findModelForTier with getEligibleModels for multi-model eligible set
- Insert STEP 2 scoring block: activates when capability_routing enabled, eligible.length > 1, unitType provided
- Add buildFallbackChain helper to deduplicate fallback assembly logic
- Scoring returns capability-scored selectionMethod with capabilityScores and taskRequirements
- Single-model and zero-model paths fall through to tier-only behavior
- All 42 existing tests pass unchanged (backward compat via optional params)
- Add taskMetadata?: TaskMetadata to ClassificationResult in complexity-classifier.ts
- Add taskMetadata?: TaskMetadata to ClassificationResult in types.ts (duplicate interface)
- Export extractTaskMetadata() so it can be imported by model-router.ts
- Refactor classifyUnitComplexity() to extract metadata once for execute-task (eliminates double-extraction at adaptive learning step)
- Populate taskMetadata field on ClassificationResult for execute-task units
- Set taskMetadata: undefined explicitly on hook unit fast-path
Add 102 integration tests across two new files covering state machine
edge cases, runtime failures, and boundary conditions not exercised
by the existing live-validation suite.
Closes#3498
* feat: add stop/backtrack capture classifications for milestone regression (#3487)
Adds 4-layer methodology for halting auto-mode and backtracking to
previous milestones when captures indicate the user wants to stop or
that a milestone missed critical features:
1. Type layer: "stop" and "backtrack" classification types in captures.ts
2. Guard layer: pre-dispatch stop check in runGuards() pauses auto-mode
before the next unit dispatches
3. Resolution layer: executeBacktrack() writes BACKTRACK-TRIGGER.md and
milestone regression markers for state machine detection
4. Protection layer: revertExecutorResolvedCaptures() detects and reverts
captures silenced by non-triage agents (resolved without classification)
Also adds fast-path stop detection in auto-post-unit.ts that pattern-matches
pending capture text for stop keywords without waiting for triage.
Closes#3487
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add slice-level skip with gsd_skip_slice tool (#3477)
Adds "skipped" as a closed status alongside "complete" and "done":
- status-guards.ts: isClosedStatus() recognizes "skipped"
- state.ts: isStatusDone() recognizes "skipped"
- gsd-db.ts: getActiveSliceFromDb() skips slices with status "skipped"
- db-tools.ts: new gsd_skip_slice tool for rethink and manual use
- rethink.md: added "Skip a slice" operation to rethink prompt
- rethink.ts: buildRethinkData shows skipped slice counts
Skipped slices satisfy dependencies for downstream slices, allowing
auto-mode to advance past them. Slice data is preserved for reference.
Relates to #3477
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: resolve 4 issues found in adversarial review of PR #3488
1. triage-ui.ts: Restore stop/backtrack entries in CLASSIFICATION_LABELS
and ALL_CLASSIFICATIONS — the Record<Classification, ...> type requires
all union members, and runtime lookups would crash on stop/backtrack.
Also auto-confirm stop/backtrack in the triage confirmation flow
(matching the triage-captures.md prompt directive).
2. triage-resolution.ts: Replace require("node:fs") in clearBacktrackTrigger
with ESM import of unlinkSync — consistent with the rest of the codebase.
3. auto-post-unit.ts: Anchor STOP_PATTERN regex to start-of-string (^) to
prevent false positives on captures like "add a pause button" or "stop
the timer from re-rendering" which are feature descriptions, not halt
directives.
4. status-guards.test.ts: Add missing test case for isClosedStatus("skipped")
to cover the new status value.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: update tool-naming test count for gsd_skip_slice
The new gsd_skip_slice tool (no alias) brings the total from 29 to 30.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add context optimization design spec, implementation plan, and pi-layer research
- Spec: 6-change design for GSD extension context optimization
- Plan: 9-task TDD implementation plan with exact file paths and code
- Pi-layer doc: 10 infrastructure opportunities (research only, not planned)
Part of #3171, #3406, #3452, #3433.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(context): add observation masking for auto-mode sessions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(context): add phase handoff anchors for auto-mode
Introduces PhaseAnchor read/write utilities so downstream agents can
inherit decisions, blockers, and intent written at phase boundaries
without re-inferring from conversation history.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(context): add capability-aware model routing and context management preferences
Implement ADR-004 Phase 2 capability scoring with 7-dimension model
profiles, task requirement vectors, and weighted scoring. Add
ContextManagementConfig preferences for observation masking thresholds.
Wire capability scoring into auto-model-selection dispatch path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(context): wire observation masking, phase anchors, and tool truncation
Register observation masker in before_provider_request hook to replace
old tool results with placeholders during auto-mode. Add tool result
truncation (configurable via context_management.tool_result_max_chars).
Inject phase handoff anchors into prompt builders so downstream phases
inherit decisions from research/planning. Write anchors after successful
phase completion. Update ADR-004 status to Implemented.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove internal planning artifacts from PR
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add capability routing, observation masking, and context management
Update dynamic-model-routing.md with capability-aware scoring section.
Update token-optimization.md with observation masking, tool truncation,
and phase handoff anchor documentation. Update configuration.md with
context_management preference block and capability_routing flag.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Merge branch 'main' into feat/gsd-context-optimization
* fix: add context_management to known keys and prevent tool truncation state corruption
- Add missing 'context_management' to KNOWN_PREFERENCE_KEYS set so users
don't get spurious unknown-key warnings when configuring it.
- Replace in-place mutation of tool result content with immutable spread
to prevent corrupting shared conversation message objects.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add stop and backtrack to triage-ui classification labels
The Classification type gained stop and backtrack variants from main
but triage-ui.ts was not updated, causing a TypeScript build failure.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: context masker and tool truncation operate on correct pi-ai message format
The observation masker and tool result truncation in before_provider_request
were checking m.type === "toolResult" but the actual pi-ai payload uses
m.role === "toolResult" with content as TextContent[] arrays (not strings).
bashExecution messages are converted to {role:"user"} by convertToLlm before
the hook fires, so checking m.type === "bashExecution" was a no-op.
- Fix context-masker to match on role, handle array content, detect bash
results by their "Ran `" prefix
- Fix register-hooks truncation to operate on role:"toolResult" with
array content blocks
- Update tests to use correct pi-ai LLM payload format
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
tryRemoteQuestions was gated behind if (!ctx.hasUI), so Telegram/Slack/
Discord were never contacted when GSD ran with a terminal UI. The test
message sent during setup always worked (direct API call, no guard), which
made the feature appear configured but non-functional in practice.
Move the remote call before the hasUI guard so configured channels fire
regardless of UI availability. When no remote channel is configured,
tryRemoteQuestions returns null and the local UI is used as before.
Adds a source-level regression test asserting that tryRemoteQuestions is
called before the !ctx.hasUI branch.
Closes#3480
Verified with AI.
The TUI slash dispatcher started treating any unrecognized /command as handled before session.prompt() could resolve extension commands, prompt templates, or /skill:* inputs. That blocked valid non-builtin slash commands and also let /export swallow unrelated /export* prefixes.
Move unknown-command detection to the interactive entry points, allow only known builtins or session-resolved slash commands through, gate /skill:* on the skill-command setting, and tighten /export matching to exact command tokens.