Skip to content

feat(cli): prompt to select docs paths on ask add#101

Merged
amondnet merged 4 commits intomainfrom
amondnet/ask-add-docs-paths
Apr 17, 2026
Merged

feat(cli): prompt to select docs paths on ask add#101
amondnet merged 4 commits intomainfrom
amondnet/ask-add-docs-paths

Conversation

@amondnet
Copy link
Copy Markdown
Contributor

@amondnet amondnet commented Apr 17, 2026

Summary

  • User-facing change: ask add <spec> now detects candidate documentation paths from already-installed node_modules and the local ask cache, and shows an interactive multiselect prompt when more than one candidate exists. The selected paths are saved in ask.json as an object entry ({ "spec": "npm:next", "docsPaths": ["docs", "dist/docs"] }); plain string entries remain fully supported.
  • New flags: --docs-paths <csv> skips the prompt and records the supplied paths directly; --clear-docs-paths downgrades an object entry back to a bare string, removing the override.
  • Schema evolution: ask.json libraries entries now accept string | { spec, docsPaths[] }. The change is back-compatible — all existing string entries continue to parse unchanged. Helpers specFromEntry, docsPathsFromEntry, and entryFromSpec are exported from @pleaseai/schema and used throughout the CLI so there is a single source of truth for entry coercion.
  • Offline-first design: ask add never triggers a fresh clone or network request. Candidate gathering reads only node_modules/<pkg>/ and whatever is already in the ask store (<askHome>/github/…). This keeps ask add fast and usable without network access.
  • ask docs honors the override: When docsPaths is stored, ask docs <spec> resolves each path against both roots and emits only matched paths. If every stored path is stale it warns to stderr and falls back to the full unfiltered walk, so the command never silently returns nothing.

Test plan

  • bun run build — clean across the workspace
  • bun run --cwd packages/cli lint — clean
  • bun run --cwd packages/schema lint — clean
  • bun run --cwd packages/cli test — 497 pass, 0 fail (32 new tests: add.test.ts, candidates.test.ts, 4 new cases in docs.test.ts)
  • bun run --cwd packages/schema test — 63 pass, 0 fail (9 new tests in ask-json.test.ts)

Summary by cubic

Adds an interactive docs-paths picker to ask add <spec> and makes ask docs honor saved paths. Also hardens path handling and preserves root selections.

  • New Features

    • ask add <spec> scans node_modules/<pkg> and any cached checkout, shows a multiselect when multiple doc-like dirs exist, and saves chosen relative paths under docsPaths in ask.json.
    • Flags: --docs-paths <csv> to skip the prompt; --clear-docs-paths to remove the override.
    • ask docs <spec> resolves stored paths against both roots and emits only matches; warns and falls back to a full walk if all are stale.
    • Offline-first: never fetches or clones on add.
    • Schema: ask.json libraries accept string | { spec, docsPaths[] } (back-compatible). Helpers specFromEntry, docsPathsFromEntry, entryFromSpec exported from @pleaseai/schema; CLI adds findEntry for lookups.
  • Bug Fixes

    • Guard against path traversal in stored docsPaths when resolving in ask docs; invalid entries are dropped and the stale-path fallback is used.
    • Sanitize --docs-paths values to reject absolute and parent-traversal paths; unsafe entries are ignored.
    • Preserve a root pick from the prompt by storing "." so it round-trips correctly.

Written for commit df95077. Summary will update on new commits.

When a user runs `ask add <spec>`, the CLI now gathers candidate
documentation paths from two offline sources: `node_modules/<pkg>/`
for npm specs that are already installed locally, and any cached git
checkout already present in the ask store. If more than one candidate
is found and stdout is a TTY, a `consola.prompt({ type: 'multiselect' })`
lets the user pick which paths to record. This design is intentionally
offline-first — no fresh clone or network request is triggered; `ask add`
relies entirely on what is already on disk.

The user's selection is written to `ask.json` as an object entry:

  { "spec": "npm:next", "docsPaths": ["docs", "dist/docs"] }

Plain string entries remain fully supported; the object form is only
emitted when an explicit override is needed. `ask docs <spec>` now reads
the stored `docsPaths` and resolves each path against both the npm
`node_modules` root and the cached checkout, emitting only the matched
paths. If every stored path is stale (none found on disk) it warns to
stderr and falls back to the full unfiltered walk.

New flags on `ask add`:
- `--docs-paths <csv>` — supply paths directly and skip the prompt
- `--clear-docs-paths` — downgrade an existing object entry back to a
  bare string, removing the stored override

Schema change (back-compat): `ask.json` `libraries` entries now accept
`string | { spec, docsPaths[] }`. Helpers `specFromEntry`,
`docsPathsFromEntry`, and `entryFromSpec` are exported from
`@pleaseai/schema` so all internal callers (`install`, `interactive`,
`storage`, `agents`) use a single source of truth instead of raw
string coercions.

32 new tests across schema, CLI commands, and the new candidates
discovery module (497 CLI + 63 schema passing).
@dosubot dosubot bot added size:XL This PR changes 500-999 lines, ignoring generated files. type/feature New feature type:feature New feature or request labels Apr 17, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 17, 2026

Codecov Report

❌ Patch coverage is 93.88489% with 17 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
packages/cli/src/io.ts 63.15% 7 Missing ⚠️
packages/cli/src/commands/add.ts 96.62% 6 Missing ⚠️
packages/cli/src/interactive.ts 25.00% 3 Missing ⚠️
packages/cli/src/storage.ts 66.66% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Apr 17, 2026

Deploying ask-registry with  Cloudflare Pages  Cloudflare Pages

Latest commit: df95077
Status: ✅  Deploy successful!
Preview URL: https://e10fecac.ask-registry.pages.dev
Branch Preview URL: https://amondnet-ask-add-docs-paths.ask-registry.pages.dev

View logs

README:
- New "Selecting docs paths at add time" subsection covering the
  multiselect prompt, the `--docs-paths` / `--clear-docs-paths` flags,
  the object entry form in `ask.json`, and the offline-first cache
  warm-up workflow.
- Note in `ask docs` section that stored `docsPaths` filter the output
  and how the stale-fallback warning surfaces.
- Top-level `ask.json` example now cross-references the subsection so
  readers know the object form exists.

CLAUDE.md:
- Schema union gotcha: consumers must wrap entries with
  `specFromEntry`; helpers and `findEntry` live next to the schema.
- `ask add` offline-first gotcha: `gatherDocsCandidates` uses
  `noFetch: true`, silently skips on cache miss, and exposes
  `--docs-paths` / `--clear-docs-paths` for non-interactive flows.
- `ask docs` override gotcha: stored paths resolve against both npm
  root and checkoutDir, zero-survivor falls back to the walk.
- Expand the CLI module tree with the new `commands/` layout
  (`add.ts`, `docs.ts`, `src.ts`, `ensure-checkout.ts`, `find-doc-paths.ts`)
  and the new `discovery/candidates.ts`.
- Split the data-flow section into three explicit flows: `ask add`
  (parse → offline-first probe → optional prompt → persist), `ask install`
  (lazy: version → SKILL.md → AGENTS.md), and `ask docs` (ensureCheckout
  → docsPaths override → walk).
- Add `LibraryEntry` union and `CandidateGroup` to the Key Types
  reference so readers see how the object entry form and the probe
  contract slot in.
- Annotate the generated-files tree to call out the two possible
  entry shapes in `ask.json`.
- Bump "Last updated" to 2026-04-17.
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 13 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/cli/src/commands/docs.ts">

<violation number="1" location="packages/cli/src/commands/docs.ts:90">
P1: `docsPaths` entries are not constrained to stay under the allowed roots, so `..`/absolute values can escape and emit arbitrary filesystem paths.</violation>
</file>

<file name="packages/cli/src/commands/add.ts">

<violation number="1" location="packages/cli/src/commands/add.ts:115">
P1: Validate `--docs-paths` entries to reject absolute paths and traversal segments before persisting.</violation>

<violation number="2" location="packages/cli/src/commands/add.ts:171">
P2: Root docs-path selections are silently discarded because empty relative paths are filtered out.</violation>
</file>
Architecture diagram
sequenceDiagram
    participant User
    participant CLI as CLI (add/docs)
    participant Discovery as Discovery Logic
    participant FS as Local FS (node_modules/cache)
    participant Prompt as consola.prompt
    participant Store as ask.json

    Note over User, Store: Flow: ask add <spec>

    User->>CLI: ask add <spec>
    CLI->>Discovery: NEW: gatherDocsCandidates(spec)
    
    Discovery->>FS: Check node_modules/<pkg>
    Discovery->>FS: Check <askHome>/github/... (offline only)
    FS-->>Discovery: Return found doc-like paths
    Discovery-->>CLI: Return candidate groups

    alt NEW: User provided --docs-paths <csv>
        CLI->>CLI: Parse CSV paths
    else NEW: Interactive TTY + Multiple candidates
        CLI->>Prompt: Show multiselect picker
        Prompt-->>CLI: User selected paths
    else No candidates or --clear-docs-paths
        CLI->>CLI: Fallback to bare spec string
    end

    CLI->>CLI: NEW: entryFromSpec(spec, paths)
    CLI->>Store: CHANGED: Save entry (string | {spec, docsPaths[]})
    Store-->>CLI: Success
    CLI-->>User: Entry added/updated

    Note over User, Store: Flow: ask docs <spec> (Consumer)

    User->>CLI: ask docs <spec>
    CLI->>Store: Read ask.json
    Store-->>CLI: Return entries
    
    CLI->>CLI: NEW: docsPathsFromEntry(entry)
    
    alt NEW: docsPaths override exists
        loop For each stored path
            CLI->>FS: Resolve relative path against roots
            opt Path exists on disk
                CLI-->>User: Emit absolute path
            end
        end
        opt NEW: All stored paths are stale
            CLI->>CLI: Warn and fall back to full directory walk
        end
    else No override
        CLI->>CLI: Execute default unfiltered walk
    end
Loading

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread packages/cli/src/commands/docs.ts Outdated
Comment thread packages/cli/src/commands/add.ts Outdated
Comment thread packages/cli/src/commands/add.ts Outdated
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 3 files (changes from recent commits).

Requires human review: Auto-approval blocked by 3 unresolved issues from previous reviews.

Three issues flagged by cubic on PR #101:

- P1 (docs.ts:91) — stored docsPaths could escape their root via ..
  or absolute values. Added a containment guard: resolve both root
  and abs, require abs === rootAbs or abs.startsWith(rootAbs + sep)
  before emitting. Traversal entries are dropped; when every stored
  entry is dropped the existing stale-fallback warning + unfiltered
  walk takes over.

- P1 (add.ts:115) — --docs-paths CSV values were persisted verbatim.
  New sanitizeDocsPath helper rejects empty strings, absolute paths,
  and anything that normalizes to .. / ../.... Unsafe entries are
  skipped with a consola.warn; the remaining safe entries are
  persisted.

- P2 (add.ts:171) — root selections from the multiselect prompt were
  silently discarded (path.relative(root, root) === '' fails the
  schema's .min(1)). Now root selections persist as "." so the user's
  explicit "keep the whole tree" pick survives. sanitizeDocsPath is
  also applied to prompt-derived values for defense-in-depth.

Tests added:
- add.ts: --docs-paths rejects absolute / traversal, drops to bare
  string when every entry is unsafe, preserves root selection as "."
- docs.ts: containment guard drops traversal entries and emits the
  stale warning; stored "." resolves back to the root.
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 4 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/cli/src/commands/add.ts">

<violation number="1" location="packages/cli/src/commands/add.ts:36">
P2: Using `path.normalize` here stores OS-specific separators in `ask.json`, which breaks cross-platform portability of saved `docsPaths` (especially Windows `\\` paths used later on POSIX).</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread packages/cli/src/commands/add.ts
@amondnet amondnet merged commit eac1285 into main Apr 17, 2026
5 checks passed
@amondnet amondnet deleted the amondnet/ask-add-docs-paths branch April 17, 2026 06:58
@github-actions github-actions bot mentioned this pull request Apr 17, 2026
amondnet added a commit that referenced this pull request Apr 17, 2026
…portability

Cubic P2 flagged on the now-merged PR #101 but the fix was pushed
after the merge landed, so it never made it to main. This reintroduces
the change on top of the merged state, plus the agent-memory entry
that captures the reusable pattern.

- add.ts: replace `path.normalize(trimmed)` with
  `path.posix.normalize(trimmed.replaceAll('\\', '/'))` in
  `sanitizeDocsPath` so the value persisted in `ask.json` always uses
  forward slashes. A Windows user writing `docs\api` and a POSIX user
  writing `docs/api` now produce the same stored token `docs/api`.
  Traversal guard simplified to `startsWith('../')` — the normalized
  form is guaranteed POSIX after the fix.
- Read side unchanged — `path.resolve` accepts both separator styles
  on Windows and forward slashes on POSIX, so the containment guard
  in `docs.ts` still works.
- Tests: backslash→forward-slash normalization in stored output, and
  `..\escape` rejection on POSIX.
- Agent memory: record the `path.normalize` → `path.posix.normalize`
  pattern so the cubic-reviewer subagent can recognize it next time.
amondnet added a commit that referenced this pull request Apr 17, 2026
…portability (#103)

Cubic P2 flagged on the now-merged PR #101 but the fix was pushed
after the merge landed, so it never made it to main. This reintroduces
the change on top of the merged state, plus the agent-memory entry
that captures the reusable pattern.

- add.ts: replace `path.normalize(trimmed)` with
  `path.posix.normalize(trimmed.replaceAll('\\', '/'))` in
  `sanitizeDocsPath` so the value persisted in `ask.json` always uses
  forward slashes. A Windows user writing `docs\api` and a POSIX user
  writing `docs/api` now produce the same stored token `docs/api`.
  Traversal guard simplified to `startsWith('../')` — the normalized
  form is guaranteed POSIX after the fix.
- Read side unchanged — `path.resolve` accepts both separator styles
  on Windows and forward slashes on POSIX, so the containment guard
  in `docs.ts` still works.
- Tests: backslash→forward-slash normalization in stored output, and
  `..\escape` rejection on POSIX.
- Agent memory: record the `path.normalize` → `path.posix.normalize`
  pattern so the cubic-reviewer subagent can recognize it next time.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL This PR changes 500-999 lines, ignoring generated files. type:feature New feature or request type/feature New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant