From 582b36ceefcdf5fa99575abe3131e1c00bf0374c Mon Sep 17 00:00:00 2001 From: nakolus Date: Sat, 21 Feb 2026 22:32:32 +0800 Subject: [PATCH 1/7] refactor: move research docs into docs/researches --- AGENTS.md | 18 +++++++++++++++++- docs/plans/jobs/2026-01-12-themes-and-rwd.md | 2 +- .../jobs/2026-01-13-implement-gwtt-workflow.md | 6 +++--- .../jobs/2026-01-13-theme-config-and-env.md | 2 +- .../2026-01-19-consolidate-normalize-path.md | 2 +- .../jobs/2026-01-19-integration-tests-ci.md | 2 +- .../2026-01-27-config-refactor-grid-flags.md | 4 ++-- .../jobs/2026-01-27-project-root-tests.md | 2 +- .../jobs/2026-02-04-codex-apply-terminology.md | 2 +- .../2026-02-07-codex-apply-phase1-phase3.md | 4 ++-- .../2026-02-07-codex-apply-phase4-phase5.md | 4 ++-- .../2026-02-07-codex-apply-phase6-phase7.md | 4 ++-- ...2026-02-07-fix-dry-run-home-mask-quoting.md | 2 +- docs/plans/plan-2026-01-12-init-phase.md | 2 +- .../plan-2026-01-12-path-display-readme.md | 2 +- docs/plans/plan-2026-01-12-themes-and-rwd.md | 2 +- ...n-2026-01-13-build-and-distribution-flow.md | 2 +- .../plan-2026-01-13-theme-config-and-env.md | 2 +- .../plan-2026-01-18-open-source-readiness.md | 2 +- .../plans/plan-2026-01-27-extensible-config.md | 2 +- .../plan-2026-02-04-mode-classic-and-codex.md | 4 ++-- ...026-02-07-codex-apply-two-way-directions.md | 2 +- .../plan-2026-02-07-dry-run-path-masking.md | 2 +- .../research-2026-01-12-themes-and-table.md | 0 .../research-2026-01-12-worktree-ops-matrix.md | 0 ...research-2026-01-13-homebrew-integration.md | 0 .../research-2026-01-13-toml-config-options.md | 0 .../research-2026-01-19-create-default-base.md | 0 .../research-2026-01-27-extensible-config.md | 0 ...esearch-2026-02-04-mode-classic-vs-codex.md | 2 +- ...odex-apply-direction-and-source-checkout.md | 2 +- 31 files changed, 48 insertions(+), 32 deletions(-) rename docs/{ => researches}/research-2026-01-12-themes-and-table.md (100%) rename docs/{ => researches}/research-2026-01-12-worktree-ops-matrix.md (100%) rename docs/{ => researches}/research-2026-01-13-homebrew-integration.md (100%) rename docs/{ => researches}/research-2026-01-13-toml-config-options.md (100%) rename docs/{ => researches}/research-2026-01-19-create-default-base.md (100%) rename docs/{ => researches}/research-2026-01-27-extensible-config.md (100%) rename docs/{ => researches}/research-2026-02-04-mode-classic-vs-codex.md (99%) rename docs/{ => researches}/research-2026-02-07-codex-apply-direction-and-source-checkout.md (99%) diff --git a/AGENTS.md b/AGENTS.md index 742de0c..582b507 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -70,12 +70,13 @@ Use research docs for exploratory work that is not yet ready for a plan but may Location: ```text -docs/research-YYYY-MM-DD-.md +docs/researches/research-YYYY-MM-DD-.md ``` Notes: - Use the creation date and a short, kebab-case title. +- Keep all research docs inside `docs/researches/` (not directly under `docs/`). - Keep scope focused on a single topic or question. - If research becomes actionable, create a plan doc and link to it. @@ -94,11 +95,15 @@ agent: Suggested sections: - Goal +- Milestone Goal (optional but recommended) - Key Findings - Implications or Recommendations - Open Questions (optional) - References (use footnote-style links) +Optional metadata: +- `milestone: v0.1.0` (or another release milestone) for feature-track research. + Traceability: - Research docs should include a short "Related Plans" section when applicable, with links to plan docs. @@ -132,6 +137,17 @@ agent: --- +### Path Reference Policy (Default + Exceptions) + +- Use repository-relative paths for repository files in documentation (for example: `.github/workflows/release.yml`, `docs/plans/jobs/...`, `src/...`). +- Do not include real machine-specific absolute paths that may reveal local user/home details from the current environment. +- When useful for explanation, sanitized OS-specific absolute-path examples are allowed (for example: `/Users/alice/...`, `/home/alice/...`, `C:\Users\Alice\...`, `$HOME/.config/...`, `%USERPROFILE%\...`). +- Prefer placeholder usernames like `alice` or `bob` in examples. +- Keep this case-by-case: prefer clarity for behavior/docs examples, but avoid disclosing actual local paths. +- This policy applies to plan, research, and job documents, including summaries, change lists, and verification notes. + +--- + ### Status Meanings - `draft` — idea or exploration, not executed diff --git a/docs/plans/jobs/2026-01-12-themes-and-rwd.md b/docs/plans/jobs/2026-01-12-themes-and-rwd.md index 59694e7..ce158d3 100644 --- a/docs/plans/jobs/2026-01-12-themes-and-rwd.md +++ b/docs/plans/jobs/2026-01-12-themes-and-rwd.md @@ -20,4 +20,4 @@ Add multiple UI themes selectable via `--theme` and improve table rendering for ## Related Research -- ../research-2026-01-12-themes-and-table.md +- docs/researches/research-2026-01-12-themes-and-table.md diff --git a/docs/plans/jobs/2026-01-13-implement-gwtt-workflow.md b/docs/plans/jobs/2026-01-13-implement-gwtt-workflow.md index 5b7298b..6f1e047 100644 --- a/docs/plans/jobs/2026-01-13-implement-gwtt-workflow.md +++ b/docs/plans/jobs/2026-01-13-implement-gwtt-workflow.md @@ -201,9 +201,9 @@ README.md ✅ Updated with dist/ references ## Related Documents -- [Build and installation flow plan](../plan-2026-01-13-build-and-distribution-flow.md) — Parent plan -- [Homebrew integration research](../research-2026-01-13-homebrew-integration.md) — Future distribution strategy -- [Verify root command configuration](./2026-01-13-verify-root-command-config.md) — Initial investigation +- `docs/plans/plan-2026-01-13-build-and-distribution-flow.md` — Parent plan +- `docs/researches/research-2026-01-13-homebrew-integration.md` — Future distribution strategy +- `docs/plans/jobs/2026-01-13-verify-root-command-config.md` — Initial investigation ## Notes diff --git a/docs/plans/jobs/2026-01-13-theme-config-and-env.md b/docs/plans/jobs/2026-01-13-theme-config-and-env.md index 5b60c4c..46952f2 100644 --- a/docs/plans/jobs/2026-01-13-theme-config-and-env.md +++ b/docs/plans/jobs/2026-01-13-theme-config-and-env.md @@ -16,7 +16,7 @@ agent: codex - `internal/config/theme.go` — resolve theme name from `GWTT_THEME`, project config, and user config. - `cli/root.go` — respect config/env when `--theme` is not explicitly set. - `README.md` — configuration section with precedence and examples. -- `docs/research-2026-01-13-toml-config-options.md` — TOML library options and pros/cons. +- `docs/researches/research-2026-01-13-toml-config-options.md` — TOML library options and pros/cons. - `internal/config/theme_test.go` — tests for precedence and parsing errors. ## Rationale diff --git a/docs/plans/jobs/2026-01-19-consolidate-normalize-path.md b/docs/plans/jobs/2026-01-19-consolidate-normalize-path.md index b840d16..8dd9047 100644 --- a/docs/plans/jobs/2026-01-19-consolidate-normalize-path.md +++ b/docs/plans/jobs/2026-01-19-consolidate-normalize-path.md @@ -20,4 +20,4 @@ agent: Zed Agent ## Related Plans -- ../plan-2026-01-18-open-source-readiness.md +- `docs/plans/plan-2026-01-18-open-source-readiness.md` diff --git a/docs/plans/jobs/2026-01-19-integration-tests-ci.md b/docs/plans/jobs/2026-01-19-integration-tests-ci.md index 9ebf3aa..129adb4 100644 --- a/docs/plans/jobs/2026-01-19-integration-tests-ci.md +++ b/docs/plans/jobs/2026-01-19-integration-tests-ci.md @@ -17,4 +17,4 @@ agent: Codex ## Related Plans -- ../plan-2026-01-18-open-source-readiness.md +- `docs/plans/plan-2026-01-18-open-source-readiness.md` diff --git a/docs/plans/jobs/2026-01-27-config-refactor-grid-flags.md b/docs/plans/jobs/2026-01-27-config-refactor-grid-flags.md index fbf52e1..a53835b 100644 --- a/docs/plans/jobs/2026-01-27-config-refactor-grid-flags.md +++ b/docs/plans/jobs/2026-01-27-config-refactor-grid-flags.md @@ -73,5 +73,5 @@ Added `TestLoadConfigExplicitGridPreserved` to verify: ## Files Modified -- [internal/config/config.go](../../../internal/config/config.go) -- [internal/config/config_test.go](../../../internal/config/config_test.go) +- `internal/config/config.go` +- `internal/config/config_test.go` diff --git a/docs/plans/jobs/2026-01-27-project-root-tests.md b/docs/plans/jobs/2026-01-27-project-root-tests.md index 7edbf1a..6166894 100644 --- a/docs/plans/jobs/2026-01-27-project-root-tests.md +++ b/docs/plans/jobs/2026-01-27-project-root-tests.md @@ -42,4 +42,4 @@ The initial fix had coverage only via integration tests in `config_test.go`. Dir ## Related Plans -- [plan-2026-01-27-fix-project-config-root-resolution.md](../plan-2026-01-27-fix-project-config-root-resolution.md) +- `docs/plans/plan-2026-01-27-fix-project-config-root-resolution.md` diff --git a/docs/plans/jobs/2026-02-04-codex-apply-terminology.md b/docs/plans/jobs/2026-02-04-codex-apply-terminology.md index ace2d35..4a2307d 100644 --- a/docs/plans/jobs/2026-02-04-codex-apply-terminology.md +++ b/docs/plans/jobs/2026-02-04-codex-apply-terminology.md @@ -19,7 +19,7 @@ agent: codex ## Files Updated -- `docs/research-2026-02-04-mode-classic-vs-codex.md` +- `docs/researches/research-2026-02-04-mode-classic-vs-codex.md` - `docs/plans/plan-2026-02-04-mode-classic-and-codex.md` - `docs/schemas/config-gwtt.md` - `cli/apply.go` diff --git a/docs/plans/jobs/2026-02-07-codex-apply-phase1-phase3.md b/docs/plans/jobs/2026-02-07-codex-apply-phase1-phase3.md index 1042e2f..1babc6d 100644 --- a/docs/plans/jobs/2026-02-07-codex-apply-phase1-phase3.md +++ b/docs/plans/jobs/2026-02-07-codex-apply-phase1-phase3.md @@ -26,7 +26,7 @@ agent: codex - `cli/apply_test.go` - `cli/integration_test.go` - `docs/plans/plan-2026-02-07-codex-apply-two-way-directions.md` -- `docs/research-2026-02-07-codex-apply-direction-and-source-checkout.md` +- `docs/researches/research-2026-02-07-codex-apply-direction-and-source-checkout.md` ## Verification @@ -38,4 +38,4 @@ agent: codex ## Related Research -- `docs/research-2026-02-07-codex-apply-direction-and-source-checkout.md` +- `docs/researches/research-2026-02-07-codex-apply-direction-and-source-checkout.md` diff --git a/docs/plans/jobs/2026-02-07-codex-apply-phase4-phase5.md b/docs/plans/jobs/2026-02-07-codex-apply-phase4-phase5.md index 800fa20..88ea5c5 100644 --- a/docs/plans/jobs/2026-02-07-codex-apply-phase4-phase5.md +++ b/docs/plans/jobs/2026-02-07-codex-apply-phase4-phase5.md @@ -32,7 +32,7 @@ agent: codex - `man/man1/gwtt.1` - `man/man1/git-worktree-tasks.1` - `docs/plans/plan-2026-02-07-codex-apply-two-way-directions.md` -- `docs/research-2026-02-07-codex-apply-direction-and-source-checkout.md` +- `docs/researches/research-2026-02-07-codex-apply-direction-and-source-checkout.md` ## Verification @@ -45,4 +45,4 @@ agent: codex ## Related Research -- `docs/research-2026-02-07-codex-apply-direction-and-source-checkout.md` +- `docs/researches/research-2026-02-07-codex-apply-direction-and-source-checkout.md` diff --git a/docs/plans/jobs/2026-02-07-codex-apply-phase6-phase7.md b/docs/plans/jobs/2026-02-07-codex-apply-phase6-phase7.md index a82a94f..30ac2a4 100644 --- a/docs/plans/jobs/2026-02-07-codex-apply-phase6-phase7.md +++ b/docs/plans/jobs/2026-02-07-codex-apply-phase6-phase7.md @@ -29,7 +29,7 @@ agent: codex - `cli/apply_files.go` - `cli/apply.go` (removed) - `docs/plans/plan-2026-02-07-codex-apply-two-way-directions.md` -- `docs/research-2026-02-07-codex-apply-direction-and-source-checkout.md` +- `docs/researches/research-2026-02-07-codex-apply-direction-and-source-checkout.md` ## Verification @@ -43,4 +43,4 @@ agent: codex ## Related Research -- `docs/research-2026-02-07-codex-apply-direction-and-source-checkout.md` +- `docs/researches/research-2026-02-07-codex-apply-direction-and-source-checkout.md` diff --git a/docs/plans/jobs/2026-02-07-fix-dry-run-home-mask-quoting.md b/docs/plans/jobs/2026-02-07-fix-dry-run-home-mask-quoting.md index d7c4ca8..1c2b607 100644 --- a/docs/plans/jobs/2026-02-07-fix-dry-run-home-mask-quoting.md +++ b/docs/plans/jobs/2026-02-07-fix-dry-run-home-mask-quoting.md @@ -18,7 +18,7 @@ Updated dry-run command formatting so masked POSIX home paths remain executable ## Why -Single-quoting `$HOME` in dry-run output prevented shell expansion, so copy-pasted commands failed. The new formatting preserves masking while keeping commands runnable. +Single-quoting the home token in dry-run output prevented shell expansion, so copy-pasted commands failed. The new formatting preserves masking while keeping commands runnable. ## Validation diff --git a/docs/plans/plan-2026-01-12-init-phase.md b/docs/plans/plan-2026-01-12-init-phase.md index fa0606a..ee41a00 100644 --- a/docs/plans/plan-2026-01-12-init-phase.md +++ b/docs/plans/plan-2026-01-12-init-phase.md @@ -61,4 +61,4 @@ Establish the initial CLI structure, UX patterns, and repository scaffolding for ## Related Research -- docs/research-2026-01-12-worktree-ops-matrix.md +- docs/researches/research-2026-01-12-worktree-ops-matrix.md diff --git a/docs/plans/plan-2026-01-12-path-display-readme.md b/docs/plans/plan-2026-01-12-path-display-readme.md index 9299fa2..08aa020 100644 --- a/docs/plans/plan-2026-01-12-path-display-readme.md +++ b/docs/plans/plan-2026-01-12-path-display-readme.md @@ -24,7 +24,7 @@ Default list/status output to relative paths with a flag to show absolute paths, ## Related Research -- docs/research-2026-01-12-worktree-ops-matrix.md +- docs/researches/research-2026-01-12-worktree-ops-matrix.md ## Plan diff --git a/docs/plans/plan-2026-01-12-themes-and-rwd.md b/docs/plans/plan-2026-01-12-themes-and-rwd.md index 7217e46..4504bd1 100644 --- a/docs/plans/plan-2026-01-12-themes-and-rwd.md +++ b/docs/plans/plan-2026-01-12-themes-and-rwd.md @@ -23,4 +23,4 @@ Deliver multiple selectable themes and improve CLI/TUI table responsiveness for ## Related Research -- ../research-2026-01-12-themes-and-table.md +- docs/researches/research-2026-01-12-themes-and-table.md diff --git a/docs/plans/plan-2026-01-13-build-and-distribution-flow.md b/docs/plans/plan-2026-01-13-build-and-distribution-flow.md index 79d88c6..bdde2cc 100644 --- a/docs/plans/plan-2026-01-13-build-and-distribution-flow.md +++ b/docs/plans/plan-2026-01-13-build-and-distribution-flow.md @@ -211,7 +211,7 @@ rm $(which gwtt) # If symlink was created ## Related Documents -- [Homebrew integration strategies](../research-2026-01-13-homebrew-integration.md) — Future distribution approach for broader user base +- `docs/researches/research-2026-01-13-homebrew-integration.md` — Future distribution approach for broader user base - [Verify root command configuration for gwtt alias](jobs/2026-01-13-verify-root-command-config.md) — Initial investigation ## Notes diff --git a/docs/plans/plan-2026-01-13-theme-config-and-env.md b/docs/plans/plan-2026-01-13-theme-config-and-env.md index 565843e..1fcef02 100644 --- a/docs/plans/plan-2026-01-13-theme-config-and-env.md +++ b/docs/plans/plan-2026-01-13-theme-config-and-env.md @@ -26,7 +26,7 @@ Add config/env-driven theme selection so users can set a default theme without a ## Related Research -- ../research-2026-01-13-toml-config-options.md +- docs/researches/research-2026-01-13-toml-config-options.md ## Design Decisions diff --git a/docs/plans/plan-2026-01-18-open-source-readiness.md b/docs/plans/plan-2026-01-18-open-source-readiness.md index 2694178..27f734f 100644 --- a/docs/plans/plan-2026-01-18-open-source-readiness.md +++ b/docs/plans/plan-2026-01-18-open-source-readiness.md @@ -39,4 +39,4 @@ Prepare the project for an open-source release by addressing correctness gaps, U ## Related Research -- docs/research-2026-01-19-create-default-base.md +- docs/researches/research-2026-01-19-create-default-base.md diff --git a/docs/plans/plan-2026-01-27-extensible-config.md b/docs/plans/plan-2026-01-27-extensible-config.md index ee6521d..0d71ae8 100644 --- a/docs/plans/plan-2026-01-27-extensible-config.md +++ b/docs/plans/plan-2026-01-27-extensible-config.md @@ -73,4 +73,4 @@ Ship an extensible config system beyond theme, prioritizing low-risk defaults an ## Related Research -- `docs/research-2026-01-27-extensible-config.md` +- `docs/researches/research-2026-01-27-extensible-config.md` diff --git a/docs/plans/plan-2026-02-04-mode-classic-and-codex.md b/docs/plans/plan-2026-02-04-mode-classic-and-codex.md index 0aee80c..ea1a484 100644 --- a/docs/plans/plan-2026-02-04-mode-classic-and-codex.md +++ b/docs/plans/plan-2026-02-04-mode-classic-and-codex.md @@ -30,7 +30,7 @@ Add a new global `--mode` (`classic` default, `codex` optional) to support Codex ### Phase 1: Docs & Spec Design -- [x] Update `docs/research-2026-02-04-mode-classic-vs-codex.md` to reflect final decisions as implementation progresses. +- [x] Update `docs/researches/research-2026-02-04-mode-classic-vs-codex.md` to reflect final decisions as implementation progresses. - [x] Update `docs/schemas/config-gwtt.md` to include `mode` and env var `GWTT_MODE`. - [x] Write a short CLI spec section (either in the research doc or a new schema doc) covering: - [x] Codex-mode worktree selection: `` resolution rules and error messages. @@ -108,4 +108,4 @@ Add a new global `--mode` (`classic` default, `codex` optional) to support Codex ## Related Research -- `docs/research-2026-02-04-mode-classic-vs-codex.md` +- `docs/researches/research-2026-02-04-mode-classic-vs-codex.md` diff --git a/docs/plans/plan-2026-02-07-codex-apply-two-way-directions.md b/docs/plans/plan-2026-02-07-codex-apply-two-way-directions.md index 8f742c9..3c938f5 100644 --- a/docs/plans/plan-2026-02-07-codex-apply-two-way-directions.md +++ b/docs/plans/plan-2026-02-07-codex-apply-two-way-directions.md @@ -91,4 +91,4 @@ Implement a two-way `apply`/`overwrite` command model in codex mode with explici ## Related Research -- `docs/research-2026-02-07-codex-apply-direction-and-source-checkout.md` +- `docs/researches/research-2026-02-07-codex-apply-direction-and-source-checkout.md` diff --git a/docs/plans/plan-2026-02-07-dry-run-path-masking.md b/docs/plans/plan-2026-02-07-dry-run-path-masking.md index 2048f67..eac791e 100644 --- a/docs/plans/plan-2026-02-07-dry-run-path-masking.md +++ b/docs/plans/plan-2026-02-07-dry-run-path-masking.md @@ -42,7 +42,7 @@ Reduce accidental disclosure of user-identifying local paths in `--dry-run` outp - `formatGitCommandForDryRun(args []string, mask bool) string` - Matching rules: - Exact home path maps to platform home token (`$HOME` or `%USERPROFILE%`). - - Descendants map to `/` on POSIX and `\` on Windows. + - Descendants map to `$HOME/` on POSIX and `%USERPROFILE%\` on Windows. - Prefix-safe matching only (separator-aware). - On Windows, matching is case-insensitive. - If home lookup fails, path is left unchanged. diff --git a/docs/research-2026-01-12-themes-and-table.md b/docs/researches/research-2026-01-12-themes-and-table.md similarity index 100% rename from docs/research-2026-01-12-themes-and-table.md rename to docs/researches/research-2026-01-12-themes-and-table.md diff --git a/docs/research-2026-01-12-worktree-ops-matrix.md b/docs/researches/research-2026-01-12-worktree-ops-matrix.md similarity index 100% rename from docs/research-2026-01-12-worktree-ops-matrix.md rename to docs/researches/research-2026-01-12-worktree-ops-matrix.md diff --git a/docs/research-2026-01-13-homebrew-integration.md b/docs/researches/research-2026-01-13-homebrew-integration.md similarity index 100% rename from docs/research-2026-01-13-homebrew-integration.md rename to docs/researches/research-2026-01-13-homebrew-integration.md diff --git a/docs/research-2026-01-13-toml-config-options.md b/docs/researches/research-2026-01-13-toml-config-options.md similarity index 100% rename from docs/research-2026-01-13-toml-config-options.md rename to docs/researches/research-2026-01-13-toml-config-options.md diff --git a/docs/research-2026-01-19-create-default-base.md b/docs/researches/research-2026-01-19-create-default-base.md similarity index 100% rename from docs/research-2026-01-19-create-default-base.md rename to docs/researches/research-2026-01-19-create-default-base.md diff --git a/docs/research-2026-01-27-extensible-config.md b/docs/researches/research-2026-01-27-extensible-config.md similarity index 100% rename from docs/research-2026-01-27-extensible-config.md rename to docs/researches/research-2026-01-27-extensible-config.md diff --git a/docs/research-2026-02-04-mode-classic-vs-codex.md b/docs/researches/research-2026-02-04-mode-classic-vs-codex.md similarity index 99% rename from docs/research-2026-02-04-mode-classic-vs-codex.md rename to docs/researches/research-2026-02-04-mode-classic-vs-codex.md index 0e02f2b..7ed6803 100644 --- a/docs/research-2026-02-04-mode-classic-vs-codex.md +++ b/docs/researches/research-2026-02-04-mode-classic-vs-codex.md @@ -49,7 +49,7 @@ To keep `classic` stable and keep `codex` aligned with Codex App: - Do not introduce new state files like `registry.json`/TOML for codex mode. - For `list`/`status`, inspect `$CODEX_HOME/worktrees/**` (and/or `git worktree list --porcelain` scoped to the local checkout) to discover worktrees and compute status. - In codex mode, `` is the **opaque ID directory** directly under `$CODEX_HOME/worktrees`. - - Example path: `~/.codex/worktrees/bf15/git-worktree-tasks` + - Example path: `$CODEX_HOME/worktrees/bf15/git-worktree-tasks` - `` is `bf15` (the opaque ID), **not** `git-worktree-tasks`. - **Create is detached-only:** in `codex` mode, `create` should not offer a `--branch` escape hatch; the default stays detached to avoid future complexity. - **Finish is classic-only:** in `codex` mode, `finish` is not a good fit; use a dedicated `apply` command instead. diff --git a/docs/research-2026-02-07-codex-apply-direction-and-source-checkout.md b/docs/researches/research-2026-02-07-codex-apply-direction-and-source-checkout.md similarity index 99% rename from docs/research-2026-02-07-codex-apply-direction-and-source-checkout.md rename to docs/researches/research-2026-02-07-codex-apply-direction-and-source-checkout.md index 8bcb61c..ff76051 100644 --- a/docs/research-2026-02-07-codex-apply-direction-and-source-checkout.md +++ b/docs/researches/research-2026-02-07-codex-apply-direction-and-source-checkout.md @@ -192,7 +192,7 @@ Output requirements: - Related mode plan: [^mode-plan] [^apply-code]: `cli/apply_command.go`, `cli/apply_resolve.go`, `cli/apply_conflicts.go`, `cli/apply_transfer.go`, `cli/apply_files.go` -[^mode-research]: `docs/research-2026-02-04-mode-classic-vs-codex.md` +[^mode-research]: `docs/researches/research-2026-02-04-mode-classic-vs-codex.md` [^mode-plan]: `docs/plans/plan-2026-02-04-mode-classic-and-codex.md` ## Related Plans From 50eb9f385b507ebe5449cb290e824cf438baaa49 Mon Sep 17 00:00:00 2001 From: nakolus Date: Sun, 22 Feb 2026 00:05:37 +0800 Subject: [PATCH 2/7] docs: add task inference docs --- cli/root.go | 2 +- ...-21-classic-task-inference-custom-paths.md | 88 +++++++++++++ ...21-task-inference-custom-worktree-paths.md | 119 ++++++++++++++++++ 3 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 docs/plans/plan-2026-02-21-classic-task-inference-custom-paths.md create mode 100644 docs/researches/research-2026-02-21-task-inference-custom-worktree-paths.md diff --git a/cli/root.go b/cli/root.go index 4bdffe9..ea2be3e 100644 --- a/cli/root.go +++ b/cli/root.go @@ -11,7 +11,7 @@ import ( "github.com/spf13/cobra" ) -var Version = "0.1.2" +var Version = "0.1.3-alpha.0" var ( errCanceled = errors.New("git worktree task process canceled") diff --git a/docs/plans/plan-2026-02-21-classic-task-inference-custom-paths.md b/docs/plans/plan-2026-02-21-classic-task-inference-custom-paths.md new file mode 100644 index 0000000..48e67c9 --- /dev/null +++ b/docs/plans/plan-2026-02-21-classic-task-inference-custom-paths.md @@ -0,0 +1,88 @@ +--- +title: "Classic task inference for custom worktree paths" +created-date: 2026-02-21 +status: active +agent: codex +--- + +## Context + +Issue `#23` shows that `list -o raw` can return the main worktree path when the target worktree uses a custom path layout (for example, `.claude/worktrees/`) instead of the default `_` naming convention. + +Current classic-mode behavior derives task names from path only. When derivation fails, task becomes `-`, task filtering misses the row, and raw mode falls back to the main worktree path. + +## Goal + +Make `list` and `status` task lookup reliable for custom worktree paths in classic mode, without breaking default naming behavior or existing `--branch` workflows. + +## Non-Goals + +- Redesigning `create` path behavior in this phase. +- Introducing persistent task metadata storage. +- Changing codex-mode task lookup behavior. + +## Decisions + +- Keep current query semantics: + - default remains fuzzy (`contains`) when `--strict` is not set + - `--strict` remains exact matching on normalized task values +- For inferred branch tasks, do not add separate raw-text strict matching. +- Exclude main-worktree inference by path only (`repoRoot`), not by branch name (`main`, `master`). + +## Proposed Behavior + +1. In classic mode, task derivation for each worktree row should be: + - first: existing `TaskFromPath(repo, wt.Path)` logic + - fallback: derive from the row branch name when available +2. Main worktree row (repo root path) should stay `task = "-"`. +3. Detached rows should stay `task = "-"`. +4. `list ` and `status ` should use the same derivation behavior. + +## Implementation Plan + +### Phase 1: Shared Task Derivation Helper + +- [ ] Add a helper in `cli` for classic-mode row task derivation. +- [ ] Keep current path-based extraction as first priority. +- [ ] Add branch-backed fallback for non-main, non-detached rows. +- [ ] Normalize inferred branch task values consistently for matching. + +### Phase 2: Command Integration + +- [ ] Replace direct `TaskFromPath(...)` usage in `cli/list.go` with the helper. +- [ ] Replace direct `TaskFromPath(...)` usage in `cli/status.go` with the same helper. +- [ ] Preserve current codex-mode behavior unchanged. + +### Phase 3: Verification Tests + +- [ ] Add/extend tests to cover custom-path worktree + branch inference. +- [ ] Verify `list new-task -o raw` resolves to custom worktree path. +- [ ] Verify `status new-task` resolves custom worktree row. +- [ ] Verify main worktree row remains `task = "-"`. +- [ ] Verify fuzzy/default and `--strict` semantics remain unchanged. + +### Phase 4: Optional Follow-up Fix + +- [ ] Evaluate and, if approved, patch raw fallback to honor `--field` when no rows match and fallback branch is used. + +### Phase 5: Documentation + +- [ ] Update user-facing docs if behavior examples need clarification. +- [ ] Add/refresh a job record under `docs/plans/jobs/` when implementation starts. + +## Acceptance Criteria + +- `list ` works for classic-mode worktrees whose paths do not match `_` when the worktree has a branch. +- `list -o raw` returns the target custom worktree path (not main fallback) when that worktree exists. +- `status ` resolves the same task consistently. +- Main worktree still displays `task = "-"`. +- Existing default-path behavior remains unchanged. + +## Risks / Notes + +- Branch-backed inference increases the number of rows that are task-searchable, so first-match outcomes should be validated with tests. +- Normalization and matching rules must stay aligned with current `normalizeTaskQuery` and `matchesTask` behavior to avoid subtle regressions. + +## Related Research + +- `docs/researches/research-2026-02-21-task-inference-custom-worktree-paths.md` diff --git a/docs/researches/research-2026-02-21-task-inference-custom-worktree-paths.md b/docs/researches/research-2026-02-21-task-inference-custom-worktree-paths.md new file mode 100644 index 0000000..6874298 --- /dev/null +++ b/docs/researches/research-2026-02-21-task-inference-custom-worktree-paths.md @@ -0,0 +1,119 @@ +--- +title: "Task inference for custom worktree paths" +created-date: 2026-02-21 +modified-date: 2026-02-21 +status: in-progress +agent: codex +--- + +## Goal + +Evaluate a safe, backward-compatible way to support task lookup when worktree paths do not follow the default `_` naming convention (for example, `.claude/worktrees/`), while preserving current `gwtt` shell workflows. + +## Milestone Goal + +Define a concrete approach that can be implemented in a small patch for the next release without changing the `create` command contract. + +## Key Findings + +- Classic-mode task discovery is path-derived only: `list`/`status` call `TaskFromPath(repo, wt.Path)`, and custom path layouts therefore produce `task = "-"`. [^cli-list] [^cli-status] [^worktree-naming] +- `list -o raw` only resolves rows by task match in classic mode; when no row matches, it falls back to the main worktree path if the branch exists. This makes task lookups on custom paths return `"."` instead of the target worktree path. [^cli-list] [^cli-common] [^readme-fallback] +- The behavior is reproducible with `create --path ./.claude/worktrees/new-task` and is tracked as issue `#23`. [^issue-23] +- Current docs already acknowledge that path templating can break `TaskFromPath` discovery unless `{task}` is preserved, so this is a known design boundary. [^plan-extensible] + +## Option Analysis + +### Option A: Keep current behavior and require `--branch` + +- Behavior: + - Keep task inference path-only. + - Users must use `gwtt list --branch -o raw` for custom layouts. +- Pros: + - Zero code risk. + - No output behavior changes. +- Cons: + - `list ` behaves inconsistently across path layouts. + - Shell flows like `cd "$(gwtt list -o raw)"` are brittle for `.claude/worktrees/*`. + +### Option B: Add branch-backed task inference fallback (recommended) + +- Behavior: + 1. Keep existing `_` parse as first priority. + 2. If parse fails and row has `refs/heads/`, infer task from that row’s branch name. + 3. Preserve `task = "-"` for detached rows. + 4. Preserve `task = "-"` for the main worktree row (repo root) to avoid changing established main-row semantics. +- Pros: + - Fixes `list ` and `status ` for custom path layouts. + - Keeps compatibility for existing default naming users. + - No new persisted state. +- Risks: + - More rows become task-searchable, which may change first-match results in edge cases with similar names. + - Branch names containing unusual characters may interact with current task-query slugification. +- Mitigations: + - Keep strict mode behavior unchanged for path-derived tasks. + - Add tests for branch inference and custom-path lookup to lock expected behavior. + +### Option C: Persist explicit task metadata at create-time + +- Behavior: + - Store task identity in worktree-local metadata and read it during list/status. +- Pros: + - Most explicit and layout-independent model. +- Cons: + - Does not solve externally created worktrees. + - Adds storage/read complexity and migration behavior. + - Larger scope than needed for issue `#23`. + +## Recommendation + +Implement Option B with two compatibility guardrails: + +1. Do not infer task for the main worktree row. +2. Keep branch filter (`--branch`) behavior unchanged and authoritative. + +This addresses the problem with minimal scope and preserves existing workflows. + +## Suggested Implementation Outline + +1. Add a helper in `cli` to derive classic-mode task for a row: + - Try `TaskFromPath`. + - If no match, use branch-backed fallback with: + - task normalization via `SlugifyTask(branch)` for matching consistency + - path-based main-worktree exclusion (`repoRoot`) so the primary checkout still renders as `-`. +2. Use that helper in both `list` and `status` to keep behavior aligned. +3. Add unit/integration tests for: + - custom path `.claude/worktrees/new-task` + branch `new-task` + - `list new-task -o raw` returns custom worktree path + - `status new-task` resolves custom worktree row + - main worktree remains `task = "-"`. +4. Optional follow-up in the same patch: + - Fix raw fallback honoring `--field` (`path|task|branch`) when no row matches and fallback branch is used. + +## Decision Notes + +- Query behavior remains unchanged: + - default (without `--strict`) stays fuzzy/contains matching + - `--strict` remains exact matching against normalized task values. +- For inferred branch tasks, do not add extra raw-text matching in strict mode. Keep strict deterministic by matching normalized values only. +- Main-worktree exclusion is path-based only (`repoRoot`), not branch-name based. Branch-name checks can misclassify legitimate non-main worktrees that happen to use names like `main` or `master`. + +## References + +[^issue-23]: `https://github.com/pi2pie/git-worktree-tasks/issues/23` + +[^cli-list]: `cli/list.go` + +[^cli-status]: `cli/status.go` + +[^cli-common]: `cli/common.go` + +[^worktree-naming]: `internal/worktree/naming.go` + +[^readme-fallback]: `README.md` + +[^plan-extensible]: `docs/plans/plan-2026-01-27-extensible-config.md` + +## Related Plans + +- `docs/plans/plan-2026-01-13-worktree-raw-fallback.md` +- `docs/plans/plan-2026-01-27-extensible-config.md` From 9a1191726839f46c9617abbd8219ed520bd7467a Mon Sep 17 00:00:00 2001 From: nakolus Date: Sun, 22 Feb 2026 00:09:03 +0800 Subject: [PATCH 3/7] docs: add doc checklist for classic task inference --- .../plan-2026-02-21-classic-task-inference-custom-paths.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/plans/plan-2026-02-21-classic-task-inference-custom-paths.md b/docs/plans/plan-2026-02-21-classic-task-inference-custom-paths.md index 48e67c9..76debf5 100644 --- a/docs/plans/plan-2026-02-21-classic-task-inference-custom-paths.md +++ b/docs/plans/plan-2026-02-21-classic-task-inference-custom-paths.md @@ -67,7 +67,10 @@ Make `list` and `status` task lookup reliable for custom worktree paths in class ### Phase 5: Documentation -- [ ] Update user-facing docs if behavior examples need clarification. +- [ ] Update `README.md` to reflect classic-mode task inference fallback for custom worktree paths. +- [ ] Update `README.md` examples for `list ` / `status ` to align with post-enhancement behavior. +- [ ] Clarify in `README.md` that `--branch` remains explicit/authoritative filtering. +- [ ] Review and revise `docs/schemas/config-gwtt.md` wording around `create.path.format` and task discovery constraints. - [ ] Add/refresh a job record under `docs/plans/jobs/` when implementation starts. ## Acceptance Criteria From 10010ce649974504a7cba25b5c792f5804fcc4aa Mon Sep 17 00:00:00 2001 From: nakolus Date: Sun, 22 Feb 2026 00:23:28 +0800 Subject: [PATCH 4/7] feat: implement classic task inference for custom paths --- cli/common.go | 25 ++++ cli/common_test.go | 64 +++++++++++ cli/integration_test.go | 107 ++++++++++++++++++ cli/list.go | 13 ++- cli/status.go | 5 +- ...k-inference-custom-paths-implementation.md | 37 ++++++ ...-21-classic-task-inference-custom-paths.md | 29 ++--- 7 files changed, 263 insertions(+), 17 deletions(-) create mode 100644 docs/plans/jobs/2026-02-21-classic-task-inference-custom-paths-implementation.md diff --git a/cli/common.go b/cli/common.go index b5c791f..f38c7aa 100644 --- a/cli/common.go +++ b/cli/common.go @@ -114,6 +114,31 @@ func fallbackPathForBranch(ctx context.Context, runner git.Runner, repoRoot, bra return path, true, nil } +func deriveClassicTask(repoRoot, repo string, wt worktree.Worktree) (string, error) { + if task, ok := worktree.TaskFromPath(repo, wt.Path); ok && task != "" { + return task, nil + } + + branch := strings.TrimSpace(strings.TrimPrefix(wt.Branch, "refs/heads/")) + if branch == "" { + return "", nil + } + + repoAbs, err := worktree.NormalizePath(repoRoot, repoRoot) + if err != nil { + return "", err + } + wtAbs, err := worktree.NormalizePath(repoRoot, wt.Path) + if err != nil { + return "", err + } + if wtAbs == repoAbs { + return "", nil + } + + return worktree.SlugifyTask(branch), nil +} + func normalizeTaskQuery(raw string) (string, error) { trimmed := strings.TrimSpace(raw) if trimmed == "" { diff --git a/cli/common_test.go b/cli/common_test.go index 07194e3..766078c 100644 --- a/cli/common_test.go +++ b/cli/common_test.go @@ -5,6 +5,8 @@ import ( "fmt" "strings" "testing" + + "github.com/pi2pie/git-worktree-tasks/internal/worktree" ) type fakeRunner struct { @@ -126,6 +128,68 @@ func TestFallbackPathForBranch(t *testing.T) { } } +func TestDeriveClassicTask(t *testing.T) { + repoRoot := "/tmp/repo" + repo := "repo" + tests := []struct { + name string + wt worktree.Worktree + want string + }{ + { + name: "path naming convention wins", + wt: worktree.Worktree{ + Path: "/tmp/repo_task-from-path", + Branch: "refs/heads/other-branch", + }, + want: "task-from-path", + }, + { + name: "fallback to branch task for custom path", + wt: worktree.Worktree{ + Path: "/tmp/repo/.claude/worktrees/new-task", + Branch: "refs/heads/new-task", + }, + want: "new-task", + }, + { + name: "fallback branch task is slugified", + wt: worktree.Worktree{ + Path: "/tmp/repo/.claude/worktrees/release", + Branch: "refs/heads/release/1.0", + }, + want: "release/1-0", + }, + { + name: "main worktree path stays empty task", + wt: worktree.Worktree{ + Path: "/tmp/repo", + Branch: "refs/heads/main", + }, + want: "", + }, + { + name: "detached stays empty task", + wt: worktree.Worktree{ + Path: "/tmp/repo/.claude/worktrees/detached", + }, + want: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := deriveClassicTask(repoRoot, repo, tt.wt) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got != tt.want { + t.Fatalf("deriveClassicTask() = %q, want %q", got, tt.want) + } + }) + } +} + func TestFormatGitCommand(t *testing.T) { tests := []struct { name string diff --git a/cli/integration_test.go b/cli/integration_test.go index be9c071..4ab2c72 100644 --- a/cli/integration_test.go +++ b/cli/integration_test.go @@ -94,6 +94,113 @@ func TestIntegrationCreateListStatusFinish(t *testing.T) { } } +func TestIntegrationListStatusCustomPathTaskInference(t *testing.T) { + repoDir := initRepo(t, true) + customRelPath := filepath.Join(".claude", "worktrees", "new-task") + + worktreePath := strings.TrimSpace(runCLI(t, repoDir, "", "--nocolor", "create", "new-task", "--path", customRelPath, "--output", "raw")) + if worktreePath != customRelPath { + t.Fatalf("expected custom worktree path %q, got %q", customRelPath, worktreePath) + } + + rawPath := strings.TrimSpace(runCLI(t, repoDir, "", "--nocolor", "list", "new-task", "--output", "raw")) + if rawPath != customRelPath { + t.Fatalf("list raw path = %q, want %q", rawPath, customRelPath) + } + + listOutput := runCLI(t, repoDir, "", "--nocolor", "list", "new-task", "--output", "json") + var listRows []listRow + if err := json.Unmarshal([]byte(listOutput), &listRows); err != nil { + t.Fatalf("parse list json: %v", err) + } + if len(listRows) != 1 { + t.Fatalf("expected 1 list row, got %d", len(listRows)) + } + if listRows[0].Task != "new-task" { + t.Fatalf("expected inferred task new-task, got %q", listRows[0].Task) + } + if listRows[0].Branch != "new-task" { + t.Fatalf("expected branch new-task, got %q", listRows[0].Branch) + } + if listRows[0].Path != customRelPath { + t.Fatalf("expected path %q, got %q", customRelPath, listRows[0].Path) + } + + statusOutput := runCLI(t, repoDir, "", "--nocolor", "status", "new-task", "--output", "json") + var statusRows []statusRow + if err := json.Unmarshal([]byte(statusOutput), &statusRows); err != nil { + t.Fatalf("parse status json: %v", err) + } + if len(statusRows) != 1 { + t.Fatalf("expected 1 status row, got %d", len(statusRows)) + } + if statusRows[0].Task != "new-task" { + t.Fatalf("expected inferred task new-task, got %q", statusRows[0].Task) + } + if statusRows[0].Branch != "new-task" { + t.Fatalf("expected branch new-task, got %q", statusRows[0].Branch) + } + if statusRows[0].Path != customRelPath { + t.Fatalf("expected path %q, got %q", customRelPath, statusRows[0].Path) + } + + strictMismatch := runCLI(t, repoDir, "", "--nocolor", "list", "new", "--strict", "--output", "json") + var strictRows []listRow + if err := json.Unmarshal([]byte(strictMismatch), &strictRows); err != nil { + t.Fatalf("parse strict list json: %v", err) + } + if len(strictRows) != 0 { + t.Fatalf("expected 0 strict rows for partial query, got %d", len(strictRows)) + } + + fuzzyOutput := runCLI(t, repoDir, "", "--nocolor", "list", "new", "--output", "json") + var fuzzyRows []listRow + if err := json.Unmarshal([]byte(fuzzyOutput), &fuzzyRows); err != nil { + t.Fatalf("parse fuzzy list json: %v", err) + } + if len(fuzzyRows) != 1 || fuzzyRows[0].Task != "new-task" { + t.Fatalf("expected fuzzy match for new-task, got %+v", fuzzyRows) + } + + allListOutput := runCLI(t, repoDir, "", "--nocolor", "list", "--output", "json") + var allRows []listRow + if err := json.Unmarshal([]byte(allListOutput), &allRows); err != nil { + t.Fatalf("parse full list json: %v", err) + } + foundMain := false + for _, row := range allRows { + if row.Path == "." { + foundMain = true + if row.Task != "-" { + t.Fatalf("expected main worktree task '-', got %q", row.Task) + } + } + } + if !foundMain { + t.Fatalf("expected to find main worktree row") + } +} + +func TestIntegrationListRawFallbackHonorsField(t *testing.T) { + repoDir := initRepo(t, true) + runGit(t, repoDir, "branch", "feature") + + pathRaw := strings.TrimSpace(runCLI(t, repoDir, "", "--nocolor", "list", "feature", "--output", "raw")) + if pathRaw != "." { + t.Fatalf("expected fallback path '.', got %q", pathRaw) + } + + branchRaw := strings.TrimSpace(runCLI(t, repoDir, "", "--nocolor", "list", "feature", "--output", "raw", "--field", "branch")) + if branchRaw != "feature" { + t.Fatalf("expected fallback branch 'feature', got %q", branchRaw) + } + + taskRaw := strings.TrimSpace(runCLI(t, repoDir, "", "--nocolor", "list", "feature", "--output", "raw", "--field", "task")) + if taskRaw != "-" { + t.Fatalf("expected fallback task '-', got %q", taskRaw) + } +} + func TestIntegrationStatusNoCommits(t *testing.T) { repoDir := initRepo(t, false) statusOutput := runCLI(t, repoDir, "", "--nocolor", "status", "--output", "json") diff --git a/cli/list.go b/cli/list.go index 703c5b4..84fb63c 100644 --- a/cli/list.go +++ b/cli/list.go @@ -146,7 +146,10 @@ func newListCommand() *cobra.Command { continue } } - task, _ = worktree.TaskFromPath(repo, wt.Path) + task, err = deriveClassicTask(repoRoot, repo, wt) + if err != nil { + return err + } } if task == "" { task = "-" @@ -205,7 +208,13 @@ func newListCommand() *cobra.Command { return err } if ok { - if _, err := fmt.Fprintln(cmd.OutOrStdout(), displayPath(repoRoot, path, opts.abs)); err != nil { + row := listRow{ + Task: "-", + Branch: fallbackBranch, + Path: displayPath(repoRoot, path, opts.abs), + Present: true, + } + if _, err := fmt.Fprintln(cmd.OutOrStdout(), listFieldValue(row, field)); err != nil { return err } return nil diff --git a/cli/status.go b/cli/status.go index ba6a4ea..c6aefe5 100644 --- a/cli/status.go +++ b/cli/status.go @@ -165,7 +165,10 @@ func newStatusCommand() *cobra.Command { continue } } - task, _ = worktree.TaskFromPath(repo, wt.Path) + task, err = deriveClassicTask(repoRoot, repo, wt) + if err != nil { + return err + } if query != "" && !matchesTask(task, query, opts.strict) { continue } diff --git a/docs/plans/jobs/2026-02-21-classic-task-inference-custom-paths-implementation.md b/docs/plans/jobs/2026-02-21-classic-task-inference-custom-paths-implementation.md new file mode 100644 index 0000000..b7533fc --- /dev/null +++ b/docs/plans/jobs/2026-02-21-classic-task-inference-custom-paths-implementation.md @@ -0,0 +1,37 @@ +--- +title: "Implement classic task inference for custom paths (phase 1-4)" +created-date: 2026-02-21 +status: completed +agent: codex +--- + +## Summary + +- Added shared classic-mode task derivation that keeps path-based parsing first and falls back to branch-based inference for custom worktree paths. +- Kept main-worktree exclusion path-based (`repoRoot`) and left detached rows as `-`. +- Applied the shared derivation to both `list` and `status` for consistent behavior. +- Patched raw fallback output to honor `--field` when no worktree row matches but branch fallback is available. + +## Changes + +- `cli/common.go` + - Added `deriveClassicTask(repoRoot, repo, wt)` helper. +- `cli/list.go` + - Replaced direct `TaskFromPath` use with `deriveClassicTask`. + - Updated raw fallback to output selected field (`path|task|branch`) via synthetic fallback row. +- `cli/status.go` + - Replaced direct `TaskFromPath` use with `deriveClassicTask`. +- `cli/common_test.go` + - Added `TestDeriveClassicTask` coverage for path-first derivation, branch fallback, slugification, main-path exclusion, and detached behavior. +- `cli/integration_test.go` + - Added custom-path inference integration test covering `list/status` query behavior and strict/fuzzy expectations. + - Added raw-fallback field integration test (`path`, `branch`, `task`). + +## Verification + +- `GOCACHE=/tmp/gocache-gwtt go test ./cli -run 'TestDeriveClassicTask|TestIntegrationListStatusCustomPathTaskInference|TestIntegrationListRawFallbackHonorsField' -v` +- `GOCACHE=/tmp/gocache-gwtt go test ./...` + +## Related Plan + +- `docs/plans/plan-2026-02-21-classic-task-inference-custom-paths.md` diff --git a/docs/plans/plan-2026-02-21-classic-task-inference-custom-paths.md b/docs/plans/plan-2026-02-21-classic-task-inference-custom-paths.md index 76debf5..19c97c3 100644 --- a/docs/plans/plan-2026-02-21-classic-task-inference-custom-paths.md +++ b/docs/plans/plan-2026-02-21-classic-task-inference-custom-paths.md @@ -1,6 +1,7 @@ --- title: "Classic task inference for custom worktree paths" created-date: 2026-02-21 +modified-date: 2026-02-21 status: active agent: codex --- @@ -42,28 +43,28 @@ Make `list` and `status` task lookup reliable for custom worktree paths in class ### Phase 1: Shared Task Derivation Helper -- [ ] Add a helper in `cli` for classic-mode row task derivation. -- [ ] Keep current path-based extraction as first priority. -- [ ] Add branch-backed fallback for non-main, non-detached rows. -- [ ] Normalize inferred branch task values consistently for matching. +- [x] Add a helper in `cli` for classic-mode row task derivation. +- [x] Keep current path-based extraction as first priority. +- [x] Add branch-backed fallback for non-main, non-detached rows. +- [x] Normalize inferred branch task values consistently for matching. ### Phase 2: Command Integration -- [ ] Replace direct `TaskFromPath(...)` usage in `cli/list.go` with the helper. -- [ ] Replace direct `TaskFromPath(...)` usage in `cli/status.go` with the same helper. -- [ ] Preserve current codex-mode behavior unchanged. +- [x] Replace direct `TaskFromPath(...)` usage in `cli/list.go` with the helper. +- [x] Replace direct `TaskFromPath(...)` usage in `cli/status.go` with the same helper. +- [x] Preserve current codex-mode behavior unchanged. ### Phase 3: Verification Tests -- [ ] Add/extend tests to cover custom-path worktree + branch inference. -- [ ] Verify `list new-task -o raw` resolves to custom worktree path. -- [ ] Verify `status new-task` resolves custom worktree row. -- [ ] Verify main worktree row remains `task = "-"`. -- [ ] Verify fuzzy/default and `--strict` semantics remain unchanged. +- [x] Add/extend tests to cover custom-path worktree + branch inference. +- [x] Verify `list new-task -o raw` resolves to custom worktree path. +- [x] Verify `status new-task` resolves custom worktree row. +- [x] Verify main worktree row remains `task = "-"`. +- [x] Verify fuzzy/default and `--strict` semantics remain unchanged. ### Phase 4: Optional Follow-up Fix -- [ ] Evaluate and, if approved, patch raw fallback to honor `--field` when no rows match and fallback branch is used. +- [x] Evaluate and, if approved, patch raw fallback to honor `--field` when no rows match and fallback branch is used. ### Phase 5: Documentation @@ -71,7 +72,7 @@ Make `list` and `status` task lookup reliable for custom worktree paths in class - [ ] Update `README.md` examples for `list ` / `status ` to align with post-enhancement behavior. - [ ] Clarify in `README.md` that `--branch` remains explicit/authoritative filtering. - [ ] Review and revise `docs/schemas/config-gwtt.md` wording around `create.path.format` and task discovery constraints. -- [ ] Add/refresh a job record under `docs/plans/jobs/` when implementation starts. +- [x] Add/refresh a job record under `docs/plans/jobs/` when implementation starts. ## Acceptance Criteria From 801742e6d857afb4dbdf286b985781687194b9f5 Mon Sep 17 00:00:00 2001 From: nakolus Date: Sun, 22 Feb 2026 00:41:57 +0800 Subject: [PATCH 5/7] docs: clarify classic-mode task lookup fallback --- README.md | 31 +++++++++++++++++-- ...-21-classic-task-inference-custom-paths.md | 12 +++---- ...21-task-inference-custom-worktree-paths.md | 10 +++--- docs/schemas/config-gwtt.md | 7 +++-- 4 files changed, 44 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 9212a2d..6463c61 100644 --- a/README.md +++ b/README.md @@ -324,6 +324,10 @@ gwtt list "my-task" --strict # Filter by branch gwtt list --branch feature-branch +# Custom path layout still searchable by task name +gwtt create new-task -p ./.worktrees/new-task +gwtt list new-task -o raw + # Show absolute paths gwtt list --abs @@ -344,6 +348,15 @@ gwtt --mode codex list | `--strict` | | Require exact task match | | `--grid` | | Render table with grid borders | +**Task lookup behavior (classic mode):** + +- `list ` first resolves task names from `_` paths. +- If the path does not match that convention, task lookup falls back to branch-backed inference for non-main, non-detached worktrees. +- `--branch` remains the explicit/authoritative branch filter. + +> [!NOTE] +> If you place worktrees under a nested path inside the main repo (for example, `./.worktrees/`), add that root path (for example, `.worktrees/`) to `.gitignore` in the main checkout. + ### Checking Status ```bash @@ -376,6 +389,11 @@ gwtt --mode codex status | `--strict` | | Require exact task match | | `--grid` | | Render table with grid borders | +**Task lookup behavior (classic mode):** + +- `status ` uses the same task resolution as `list ` (path-first, then branch-backed fallback for eligible rows). +- `--branch` remains the explicit/authoritative branch filter. + ### Finishing Tasks ```bash @@ -572,12 +590,21 @@ gwtt-new() { When using `--output raw` with `list`: -- If no matching worktree exists but the branch does, returns the main worktree path -- Requires either a task filter or `--branch` flag +- If a matching worktree row exists (including custom path layouts), raw output uses that row. +- If no matching worktree exists but the branch does, raw output falls back to a synthetic main-worktree row. +- Fallback row output respects `--field`: + - `path` -> main worktree path + - `branch` -> fallback branch name + - `task` -> `-` +- Requires either a task filter or `--branch` flag. +- `--branch` remains the explicit/authoritative branch selector. ```bash # Returns path even if no worktree exists (fallback to main repo) gwtt list feature-branch -o raw + +# Field-aware fallback output +gwtt list feature-branch -o raw -f branch ``` --- diff --git a/docs/plans/plan-2026-02-21-classic-task-inference-custom-paths.md b/docs/plans/plan-2026-02-21-classic-task-inference-custom-paths.md index 19c97c3..7316813 100644 --- a/docs/plans/plan-2026-02-21-classic-task-inference-custom-paths.md +++ b/docs/plans/plan-2026-02-21-classic-task-inference-custom-paths.md @@ -2,13 +2,13 @@ title: "Classic task inference for custom worktree paths" created-date: 2026-02-21 modified-date: 2026-02-21 -status: active +status: completed agent: codex --- ## Context -Issue `#23` shows that `list -o raw` can return the main worktree path when the target worktree uses a custom path layout (for example, `.claude/worktrees/`) instead of the default `_` naming convention. +Issue `#23` shows that `list -o raw` can return the main worktree path when the target worktree uses a custom path layout (for example, `.worktrees/`) instead of the default `_` naming convention. Current classic-mode behavior derives task names from path only. When derivation fails, task becomes `-`, task filtering misses the row, and raw mode falls back to the main worktree path. @@ -68,10 +68,10 @@ Make `list` and `status` task lookup reliable for custom worktree paths in class ### Phase 5: Documentation -- [ ] Update `README.md` to reflect classic-mode task inference fallback for custom worktree paths. -- [ ] Update `README.md` examples for `list ` / `status ` to align with post-enhancement behavior. -- [ ] Clarify in `README.md` that `--branch` remains explicit/authoritative filtering. -- [ ] Review and revise `docs/schemas/config-gwtt.md` wording around `create.path.format` and task discovery constraints. +- [x] Update `README.md` to reflect classic-mode task inference fallback for custom worktree paths. +- [x] Update `README.md` examples for `list ` / `status ` to align with post-enhancement behavior. +- [x] Clarify in `README.md` that `--branch` remains explicit/authoritative filtering. +- [x] Review and revise `docs/schemas/config-gwtt.md` wording around `create.path.format` and task discovery constraints. - [x] Add/refresh a job record under `docs/plans/jobs/` when implementation starts. ## Acceptance Criteria diff --git a/docs/researches/research-2026-02-21-task-inference-custom-worktree-paths.md b/docs/researches/research-2026-02-21-task-inference-custom-worktree-paths.md index 6874298..63e395d 100644 --- a/docs/researches/research-2026-02-21-task-inference-custom-worktree-paths.md +++ b/docs/researches/research-2026-02-21-task-inference-custom-worktree-paths.md @@ -2,13 +2,13 @@ title: "Task inference for custom worktree paths" created-date: 2026-02-21 modified-date: 2026-02-21 -status: in-progress +status: completed agent: codex --- ## Goal -Evaluate a safe, backward-compatible way to support task lookup when worktree paths do not follow the default `_` naming convention (for example, `.claude/worktrees/`), while preserving current `gwtt` shell workflows. +Evaluate a safe, backward-compatible way to support task lookup when worktree paths do not follow the default `_` naming convention (for example, `.worktrees/`), while preserving current `gwtt` shell workflows. ## Milestone Goal @@ -18,7 +18,7 @@ Define a concrete approach that can be implemented in a small patch for the next - Classic-mode task discovery is path-derived only: `list`/`status` call `TaskFromPath(repo, wt.Path)`, and custom path layouts therefore produce `task = "-"`. [^cli-list] [^cli-status] [^worktree-naming] - `list -o raw` only resolves rows by task match in classic mode; when no row matches, it falls back to the main worktree path if the branch exists. This makes task lookups on custom paths return `"."` instead of the target worktree path. [^cli-list] [^cli-common] [^readme-fallback] -- The behavior is reproducible with `create --path ./.claude/worktrees/new-task` and is tracked as issue `#23`. [^issue-23] +- The behavior is reproducible with `create --path ./.worktrees/new-task` and is tracked as issue `#23`. [^issue-23] - Current docs already acknowledge that path templating can break `TaskFromPath` discovery unless `{task}` is preserved, so this is a known design boundary. [^plan-extensible] ## Option Analysis @@ -33,7 +33,7 @@ Define a concrete approach that can be implemented in a small patch for the next - No output behavior changes. - Cons: - `list ` behaves inconsistently across path layouts. - - Shell flows like `cd "$(gwtt list -o raw)"` are brittle for `.claude/worktrees/*`. + - Shell flows like `cd "$(gwtt list -o raw)"` are brittle for `.worktrees/*`. ### Option B: Add branch-backed task inference fallback (recommended) @@ -82,7 +82,7 @@ This addresses the problem with minimal scope and preserves existing workflows. - path-based main-worktree exclusion (`repoRoot`) so the primary checkout still renders as `-`. 2. Use that helper in both `list` and `status` to keep behavior aligned. 3. Add unit/integration tests for: - - custom path `.claude/worktrees/new-task` + branch `new-task` + - custom path `.worktrees/new-task` + branch `new-task` - `list new-task -o raw` returns custom worktree path - `status new-task` resolves custom worktree row - main worktree remains `task = "-"`. diff --git a/docs/schemas/config-gwtt.md b/docs/schemas/config-gwtt.md index a100a3c..77dec02 100644 --- a/docs/schemas/config-gwtt.md +++ b/docs/schemas/config-gwtt.md @@ -1,7 +1,7 @@ --- title: "gwtt configuration schema" created-date: 2026-01-27 -modified-date: 2026-02-07 +modified-date: 2026-02-21 status: in-progress agent: codex --- @@ -66,7 +66,8 @@ Define the authoritative configuration schema for `gwtt`, including keys, types, - `root` (string, default: `"../"`) - `format` (string, default: `"{repo}_{task}"`) - - Must include `{task}`. + - Recommended to include `{task}` for predictable path-derived task names. + - If omitted, task lookup can still fall back to branch-backed inference for non-main, non-detached worktrees. - `{repo}` is optional. ### `[list]` @@ -113,7 +114,7 @@ Define the authoritative configuration schema for `gwtt`, including keys, types, ## Decisions -- `create.path.format` must include `{task}` to preserve task discovery. +- `create.path.format` should include `{task}` for predictable path-derived discovery; branch-backed fallback covers custom path layouts for eligible rows. - `merge_mode` is exclusive; only one strategy may be active at a time. - Codex-mode uses an `apply` command for hand-off changes; there are no config keys for it yet. - No config defaults for `create.base` or `status/finish.target`. From 231e571227cd45d53bb712875a1a3f4671bc8f75 Mon Sep 17 00:00:00 2001 From: nakolus Date: Sun, 22 Feb 2026 00:46:58 +0800 Subject: [PATCH 6/7] patch version update --- cli/root.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/root.go b/cli/root.go index ea2be3e..979cce6 100644 --- a/cli/root.go +++ b/cli/root.go @@ -11,7 +11,7 @@ import ( "github.com/spf13/cobra" ) -var Version = "0.1.3-alpha.0" +var Version = "0.1.3" var ( errCanceled = errors.New("git worktree task process canceled") From 86edf1180824436efa76451d1ba72e1d6aadcbef Mon Sep 17 00:00:00 2001 From: nakolus Date: Sun, 22 Feb 2026 01:16:37 +0800 Subject: [PATCH 7/7] feat: use main worktree when deriving task --- cli/common.go | 9 ++++++--- cli/common_test.go | 41 ++++++++++++++++++++++++++++++++++++----- cli/integration_test.go | 36 ++++++++++++++++++++++++++++++++++++ cli/list.go | 9 ++++++++- cli/status.go | 9 ++++++++- 5 files changed, 94 insertions(+), 10 deletions(-) diff --git a/cli/common.go b/cli/common.go index f38c7aa..95960f4 100644 --- a/cli/common.go +++ b/cli/common.go @@ -114,7 +114,7 @@ func fallbackPathForBranch(ctx context.Context, runner git.Runner, repoRoot, bra return path, true, nil } -func deriveClassicTask(repoRoot, repo string, wt worktree.Worktree) (string, error) { +func deriveClassicTask(repoRoot, mainWorktree, repo string, wt worktree.Worktree) (string, error) { if task, ok := worktree.TaskFromPath(repo, wt.Path); ok && task != "" { return task, nil } @@ -124,7 +124,10 @@ func deriveClassicTask(repoRoot, repo string, wt worktree.Worktree) (string, err return "", nil } - repoAbs, err := worktree.NormalizePath(repoRoot, repoRoot) + if strings.TrimSpace(mainWorktree) == "" { + mainWorktree = repoRoot + } + mainWorktreeAbs, err := worktree.NormalizePath(repoRoot, mainWorktree) if err != nil { return "", err } @@ -132,7 +135,7 @@ func deriveClassicTask(repoRoot, repo string, wt worktree.Worktree) (string, err if err != nil { return "", err } - if wtAbs == repoAbs { + if wtAbs == mainWorktreeAbs { return "", nil } diff --git a/cli/common_test.go b/cli/common_test.go index 766078c..24afe6b 100644 --- a/cli/common_test.go +++ b/cli/common_test.go @@ -129,12 +129,15 @@ func TestFallbackPathForBranch(t *testing.T) { } func TestDeriveClassicTask(t *testing.T) { - repoRoot := "/tmp/repo" + defaultRepoRoot := "/tmp/repo" + defaultMainWorktree := "/tmp/repo" repo := "repo" tests := []struct { - name string - wt worktree.Worktree - want string + name string + repoRoot string + mainWorktree string + wt worktree.Worktree + want string }{ { name: "path naming convention wins", @@ -168,6 +171,26 @@ func TestDeriveClassicTask(t *testing.T) { }, want: "", }, + { + name: "main worktree stays empty when invoked from linked worktree", + repoRoot: "/tmp/repo/.claude/worktrees/new-task", + mainWorktree: "/tmp/repo", + wt: worktree.Worktree{ + Path: "/tmp/repo", + Branch: "refs/heads/main", + }, + want: "", + }, + { + name: "linked worktree still infers task when invoked from linked worktree", + repoRoot: "/tmp/repo/.claude/worktrees/new-task", + mainWorktree: "/tmp/repo", + wt: worktree.Worktree{ + Path: "/tmp/repo/.claude/worktrees/new-task", + Branch: "refs/heads/new-task", + }, + want: "new-task", + }, { name: "detached stays empty task", wt: worktree.Worktree{ @@ -179,7 +202,15 @@ func TestDeriveClassicTask(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := deriveClassicTask(repoRoot, repo, tt.wt) + repoRoot := tt.repoRoot + if repoRoot == "" { + repoRoot = defaultRepoRoot + } + mainWorktree := tt.mainWorktree + if mainWorktree == "" { + mainWorktree = defaultMainWorktree + } + got, err := deriveClassicTask(repoRoot, mainWorktree, repo, tt.wt) if err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/cli/integration_test.go b/cli/integration_test.go index 4ab2c72..81a6518 100644 --- a/cli/integration_test.go +++ b/cli/integration_test.go @@ -179,6 +179,42 @@ func TestIntegrationListStatusCustomPathTaskInference(t *testing.T) { if !foundMain { t.Fatalf("expected to find main worktree row") } + + linkedDir := filepath.Join(repoDir, customRelPath) + + listFromLinkedOutput := runCLI(t, linkedDir, "", "--nocolor", "list", "--output", "json") + var linkedRows []listRow + if err := json.Unmarshal([]byte(listFromLinkedOutput), &linkedRows); err != nil { + t.Fatalf("parse linked list json: %v", err) + } + if len(linkedRows) != 2 { + t.Fatalf("expected 2 linked list rows, got %d", len(linkedRows)) + } + tasksByBranch := make(map[string]string, len(linkedRows)) + for _, row := range linkedRows { + tasksByBranch[row.Branch] = row.Task + } + if tasksByBranch["main"] != "-" { + t.Fatalf("expected main branch task '-', got %q", tasksByBranch["main"]) + } + if tasksByBranch["new-task"] != "new-task" { + t.Fatalf("expected linked branch task 'new-task', got %q", tasksByBranch["new-task"]) + } + + statusFromLinkedOutput := runCLI(t, linkedDir, "", "--nocolor", "status", "new-task", "--output", "json") + var linkedStatusRows []statusRow + if err := json.Unmarshal([]byte(statusFromLinkedOutput), &linkedStatusRows); err != nil { + t.Fatalf("parse linked status json: %v", err) + } + if len(linkedStatusRows) != 1 { + t.Fatalf("expected 1 linked status row for new-task, got %d", len(linkedStatusRows)) + } + if linkedStatusRows[0].Task != "new-task" { + t.Fatalf("expected linked status task new-task, got %q", linkedStatusRows[0].Task) + } + if linkedStatusRows[0].Branch != "new-task" { + t.Fatalf("expected linked status branch new-task, got %q", linkedStatusRows[0].Branch) + } } func TestIntegrationListRawFallbackHonorsField(t *testing.T) { diff --git a/cli/list.go b/cli/list.go index 84fb63c..1de37e6 100644 --- a/cli/list.go +++ b/cli/list.go @@ -81,6 +81,13 @@ func newListCommand() *cobra.Command { if err != nil { return err } + mainWorktree := "" + if mode != modeCodex { + mainWorktree, err = mainWorktreePath(ctx, runner, repoRoot) + if err != nil { + return err + } + } if _, err := git.CurrentBranch(ctx, runner); err != nil { return err } @@ -146,7 +153,7 @@ func newListCommand() *cobra.Command { continue } } - task, err = deriveClassicTask(repoRoot, repo, wt) + task, err = deriveClassicTask(repoRoot, mainWorktree, repo, wt) if err != nil { return err } diff --git a/cli/status.go b/cli/status.go index c6aefe5..e3da28c 100644 --- a/cli/status.go +++ b/cli/status.go @@ -78,6 +78,13 @@ func newStatusCommand() *cobra.Command { if err != nil { return err } + mainWorktree := "" + if mode != modeCodex { + mainWorktree, err = mainWorktreePath(ctx, runner, repoRoot) + if err != nil { + return err + } + } if len(args) == 1 && opts.task != "" { return fmt.Errorf("use either --task or [task], not both") } @@ -165,7 +172,7 @@ func newStatusCommand() *cobra.Command { continue } } - task, err = deriveClassicTask(repoRoot, repo, wt) + task, err = deriveClassicTask(repoRoot, mainWorktree, repo, wt) if err != nil { return err }