feat(cli): prompt to select docs paths on ask add#101
Merged
Conversation
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).
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Deploying ask-registry with
|
| 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 |
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.
Contributor
There was a problem hiding this comment.
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
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
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.
Contributor
There was a problem hiding this comment.
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.
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.
4 tasks
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ask add <spec>now detects candidate documentation paths from already-installednode_modulesand the local ask cache, and shows an interactive multiselect prompt when more than one candidate exists. The selected paths are saved inask.jsonas an object entry ({ "spec": "npm:next", "docsPaths": ["docs", "dist/docs"] }); plain string entries remain fully supported.--docs-paths <csv>skips the prompt and records the supplied paths directly;--clear-docs-pathsdowngrades an object entry back to a bare string, removing the override.ask.jsonlibrariesentries now acceptstring | { spec, docsPaths[] }. The change is back-compatible — all existing string entries continue to parse unchanged. HelpersspecFromEntry,docsPathsFromEntry, andentryFromSpecare exported from@pleaseai/schemaand used throughout the CLI so there is a single source of truth for entry coercion.ask addnever triggers a fresh clone or network request. Candidate gathering reads onlynode_modules/<pkg>/and whatever is already in the ask store (<askHome>/github/…). This keepsask addfast and usable without network access.ask docshonors the override: WhendocsPathsis 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 workspacebun run --cwd packages/cli lint— cleanbun run --cwd packages/schema lint— cleanbun run --cwd packages/cli test— 497 pass, 0 fail (32 new tests:add.test.ts,candidates.test.ts, 4 new cases indocs.test.ts)bun run --cwd packages/schema test— 63 pass, 0 fail (9 new tests inask-json.test.ts)Summary by cubic
Adds an interactive docs-paths picker to
ask add <spec>and makesask docshonor saved paths. Also hardens path handling and preserves root selections.New Features
ask add <spec>scansnode_modules/<pkg>and any cached checkout, shows a multiselect when multiple doc-like dirs exist, and saves chosen relative paths underdocsPathsinask.json.--docs-paths <csv>to skip the prompt;--clear-docs-pathsto 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.ask.jsonlibrariesacceptstring | { spec, docsPaths[] }(back-compatible). HelpersspecFromEntry,docsPathsFromEntry,entryFromSpecexported from@pleaseai/schema; CLI addsfindEntryfor lookups.Bug Fixes
docsPathswhen resolving inask docs; invalid entries are dropped and the stale-path fallback is used.--docs-pathsvalues to reject absolute and parent-traversal paths; unsafe entries are ignored."."so it round-trips correctly.Written for commit df95077. Summary will update on new commits.