Skip to content

Ambiguous project basenames: list -p and project verbs refuse, --group project labels apart (T458) - #293

Merged
smarzban merged 2 commits into
mainfrom
fix/t458-ambiguous-project
Nov 6, 2026
Merged

smarzban merged 2 commits into
mainfrom
fix/t458-ambiguous-project

Conversation

@smarzban

@smarzban smarzban commented Nov 6, 2026

Copy link
Copy Markdown
Owner

Summary

Two projects that share a basename (~/sites/site-a/wordpress/wp-content, ~/sites/site-b/wordpress/wp-content) were handled inconsistently. tsk add -p wp-content refused as ambiguous, but:

  • tsk list -p wp-content silently printed nothing ([] with --json), exit 0.
  • tsk project archive|unarchive wp-content said no project named wp-content has tasks, although both projects have tasks.
  • tsk list --all --group project painted both projects under one wp-content heading, and JSON group was wp-content for both.

Changes:

  • resolve_permissive_project_path (src/scope.rs) now returns Result: 2+ distinct, non-equivalent basename matches give ProjectResolveError::Ambiguous, with the same project <name> is ambiguous: <paths> text as add. 0 matches keep the literal fallback (legacy relative scopes, removed directories), and path tokens stay literal.
  • tsk list -p: new ListError::UnknownProject prints tsk list: unknown-project: <message>, exit 1, text and --json alike.
  • tsk project archive|unarchive: refuses through the existing ArchiveCliError::UnknownProject (code unknown-project). Output stays message-only (tsk project: <message>, exit 1), because cli.md documents project refusals as message-only. Adding a code prefix there would change that contract.
  • --group project: groups whose basenames collide are relabelled by their shortest unique path suffix, in text and JSON group. This reuses the existing --all scope-label widener (scope_labels/ScopeLabel in presenter.rs, now exposed as distinct_scope_labels), so there is still one disambiguation helper. Equivalent spellings of one project (/tmp/x, /private/tmp/x) collapse to one representative first, so one group keeps one label.
  • Help: tsk list --help gains an unknown-project refusal; tsk project --help names the ambiguous refusal.
  • Docs: cli.md (scope notes, project refusals, refusal-code table gains a list row, --group label note), CHANGELOG Unreleased > Fixed (two lines, credit Suggestion: Add a description field to projects, add a title field to projects #249). The tsk-cli skill line about list -p is updated; version stays 1.7.0 (already unreleased), hash pin refreshed. llms.txt did not state the old behaviour.

Board TUI and project titles are out of scope.

Seed script

Run with each binary. It uses an isolated TSK_STATE_DIR under /tmp/tsk-249-* and never touches ~/.tsk. The .git marker is enough for tsk to treat each directory as a repository.

#!/bin/sh
# Usage: seed-249.sh <path-to-tsk-binary>
set -u
TSK="$1"
ROOT=$(mktemp -d /tmp/tsk-249-XXXXXX)
export TSK_STATE_DIR="$ROOT/state"
export TSK_CONFIG_DIR="$ROOT/config"
unset HERDR_ENV HERDR_PLUGIN_CONTEXT_JSON REX_SESSION
mkdir -p "$TSK_STATE_DIR" "$TSK_CONFIG_DIR"
for site in site-a site-b; do
  mkdir -p "$ROOT/$site/wordpress/wp-content/.git"
done
cd "$ROOT"
run() { echo "\$ tsk $*"; "$TSK" "$@"; echo "[exit $?]"; echo; }
run add -t "site-a task" -p "$ROOT/site-a/wordpress/wp-content"
run add -t "site-b task" -p "$ROOT/site-b/wordpress/wp-content"
run add -t "ambiguous add" -p wp-content
run list -p wp-content
run list -p wp-content --json
run project archive wp-content
run list --all --group project
run list --all --group project --json

Before (origin/main fa36b9e2, release build)

$ tsk add -t site-a task -p /tmp/tsk-249-JxtkNv/site-a/wordpress/wp-content
added site-a task
[exit 0]

$ tsk add -t site-b task -p /tmp/tsk-249-JxtkNv/site-b/wordpress/wp-content
added site-b task
[exit 0]

$ tsk add -t ambiguous add -p wp-content
tsk add: unknown-project: project wp-content is ambiguous: /tmp/tsk-249-JxtkNv/site-a/wordpress/wp-content, /tmp/tsk-249-JxtkNv/site-b/wordpress/wp-content
[exit 1]

$ tsk list -p wp-content
[exit 0]

$ tsk list -p wp-content --json
[]
[exit 0]

$ tsk project archive wp-content
tsk project: no project named wp-content has tasks
[exit 1]

$ tsk list --all --group project
OPEN
  wp-content
    - 2 site-b task
    - 1 site-a task
[exit 0]

$ tsk list --all --group project --json
[{...,"group":"wp-content",...,"project":"/tmp/tsk-249-JxtkNv/site-b/wordpress/wp-content",...,"title":"site-b task"},{...,"group":"wp-content",...,"project":"/tmp/tsk-249-JxtkNv/site-a/wordpress/wp-content",...,"title":"site-a task"}]
[exit 0]

After (this branch, release build)

$ tsk add -t site-a task -p /tmp/tsk-249-dVbOzV/site-a/wordpress/wp-content
added site-a task
[exit 0]

$ tsk add -t site-b task -p /tmp/tsk-249-dVbOzV/site-b/wordpress/wp-content
added site-b task
[exit 0]

$ tsk add -t ambiguous add -p wp-content
tsk add: unknown-project: project wp-content is ambiguous: /tmp/tsk-249-dVbOzV/site-a/wordpress/wp-content, /tmp/tsk-249-dVbOzV/site-b/wordpress/wp-content
[exit 1]

$ tsk list -p wp-content
tsk list: unknown-project: project wp-content is ambiguous: /tmp/tsk-249-dVbOzV/site-a/wordpress/wp-content, /tmp/tsk-249-dVbOzV/site-b/wordpress/wp-content
[exit 1]

$ tsk list -p wp-content --json
tsk list: unknown-project: project wp-content is ambiguous: /tmp/tsk-249-dVbOzV/site-a/wordpress/wp-content, /tmp/tsk-249-dVbOzV/site-b/wordpress/wp-content
[exit 1]

$ tsk project archive wp-content
tsk project: project wp-content is ambiguous: /tmp/tsk-249-dVbOzV/site-a/wordpress/wp-content, /tmp/tsk-249-dVbOzV/site-b/wordpress/wp-content
[exit 1]

$ tsk list --all --group project
OPEN
  site-b/wordpress/wp-content
    - 2 site-b task
  site-a/wordpress/wp-content
    - 1 site-a task
[exit 0]

$ tsk list --all --group project --json
[{...,"group":"site-b/wordpress/wp-content",...,"project":"/tmp/tsk-249-dVbOzV/site-b/wordpress/wp-content",...,"title":"site-b task"},{...,"group":"site-a/wordpress/wp-content",...,"project":"/tmp/tsk-249-dVbOzV/site-a/wordpress/wp-content",...,"title":"site-a task"}]
[exit 0]

Tests

New regression tests use real temp directories. Each one fails with its fix hunk reverted by hand (verified: list -p exits 0 instead of 1; archive prints no project named wp-content has tasks; group labels read wp-content twice).

  • cli_list::list_project_refuses_a_basename_shared_by_two_projects: text and --json, case-insensitive name; a full path still lists one project; an unknown name still lists nothing.
  • cli_list::list_group_project_labels_colliding_basenames_by_their_shortest_unique_suffix: JSON group and text headings.
  • cli_archive::project_archive_refuses_a_basename_shared_by_two_projects: archive and unarchive refuse with nothing archived; a full path still archives.

Green bar on the rebased head: cargo fmt --check && cargo clippy --all-targets -- -D warnings && cargo test passed. Site npm test was not run locally; CI covers it.

Live Herdr smoke was not run: this is a headless CLI change with no board, host, or pane code.

Fixes part of #249 (ambiguous basenames); titles tracked separately.

…oup project labels them apart (T458)

tsk list -p and tsk project archive|unarchive refused nothing for a basename two
projects share: list printed nothing, archive said no project has tasks. The
permissive resolver now refuses an ambiguous name with add's message; an unknown
name keeps the literal fallback. --group project relabels colliding basenames by
their shortest unique path suffix, reusing the --all scope label helper.

Refs #249
@vercel

vercel Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
herdr-tsk Ignored Ignored Preview Nov 6, 2026 9:07pm UTC

…ath (T458 review)

An exact stored scope matching the -p token wins before the ambiguity check, so a
legacy bare 'atlas' beside '/…/atlas' stays reachable. Tests cover that, the
absolute-only ambiguous message, and equivalent spellings sharing one
--group project label.
@smarzban
smarzban merged commit 94b1dc6 into main Nov 6, 2026
10 checks passed
@smarzban
smarzban deleted the fix/t458-ambiguous-project branch November 6, 2026 21:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant