Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .please/docs/tracks.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
{"id":"npm-publish-release-please-20260408","type":"chore","status":"planned","phase":"spec","issue":"#16","created":"2026-04-08","section":"active"}
{"id":"maven-resolver-20260408","type":"feature","status":"in_progress","phase":"implement","issue":"#15","created":"2026-04-08","section":"active"}
{"id":"extract-shared-registry-20260408","type":"refactor","status":"in_progress","phase":"implement","issue":"#14","created":"2026-04-08","section":"active"}
{"id":"ignore-vendored-docs-20260408","type":"feature","status":"review","phase":"review","issue":"#24","pr":"#26","created":"2026-04-08","section":"completed"}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"track_id": "ignore-vendored-docs-20260408",
"type": "feature",
"status": "review",
"created_at": "2026-04-08T00:00:00Z",
"updated_at": "2026-04-08T00:00:00Z",
"issue": "#24",
"pr": "#26",
"project": "",
"project_item_id": ""
}
164 changes: 164 additions & 0 deletions .please/docs/tracks/completed/ignore-vendored-docs-20260408/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# Plan: Vendored Docs Ignore Management

> Track: ignore-vendored-docs-20260408
> Spec: [spec.md](./spec.md)

## Overview

- **Source**: /please:plan
- **Track**: ignore-vendored-docs-20260408
- **Issue**: TBD
- **Created**: 2026-04-08
- **Approach**: Hybrid — self-contained nested config files + extend AGENTS.md auto-generated block + targeted root patching for nested-unaware tools

## Purpose

Mark `.ask/docs/` as vendored so lint/format/code-review tools skip it, while keeping the directory readable as AI context. Achieved by combining nested config files inside `.ask/docs/` (for tools that support hierarchical resolution) with an intent notice inside the existing AGENTS.md auto-generated block (consumed by AI review tools), and minimal root patching only for Prettier and SonarQube.

## Context

The existing CLI already manages `AGENTS.md` via `agents.ts:8-89` using a marker block pair (`<!-- BEGIN:ask-docs-auto-generated -->` / `<!-- END:ask-docs-auto-generated -->`) and writes `CLAUDE.md` with an `@AGENTS.md` import line. The new functionality plugs into that same lifecycle: `add`, `sync`, and `remove` already call `generateAgentsMd(projectDir)` at the tail end of their flow (`index.ts:282, 419-421, 469`) — `manageIgnoreFiles(projectDir)` is invoked at the same locations. The Zod `ConfigSchema` (`schemas.ts:57-60`) gains an optional `manageIgnores` field with default `true`.

## Architecture Decision

### Three categories, three strategies

1. **Category A (self-contained)** — write nested config files inside `.ask/docs/`. ESLint flat config, Biome, markdownlint-cli2, and Git all walk up from each file to find the nearest config, so a config dropped inside `.ask/docs/` automatically scopes itself to that directory without touching anything at the root.
2. **Category B (intent notice)** — extend the existing `generateAgentsMd` auto-generated block to prepend a "vendored / read-only" notice section. Avoids introducing a second marker convention; the existing block is already idempotently rewritten on every `add`/`sync`/`remove`.
3. **Category C (root patching)** — only Prettier (`.prettierignore`) and SonarQube (`sonar-project.properties`) lack nested resolution. Patch them through a small `MarkerBlock` helper that injects/refreshes/removes a `# <!-- ask:start -->` ... `# <!-- ask:end -->` block in-place.

### Why not patch root ESLint/Biome/Cursor?

- Root flat config files are user-owned JS/JSON whose AST surface is too broad to mutate safely.
- `.cursorignore` would block Cursor from reading docs as AI context — the opposite of ASK's value proposition.
- Nested config covers ESLint and Biome perfectly without touching the root.

### Why extend the existing auto-generated block?

`agents.ts` already owns a marker block. Introducing a second `<!-- ask:start -->` block would duplicate the idempotency machinery and confuse readers. Prepending the notice as a sub-section inside the existing block is a one-line edit.

### Idempotency model

All writes are read-modify-write with deterministic templates. The `MarkerBlock` helper (new file `markers.ts`) provides:

- `inject(content, block)` — insert if absent, replace if marker pair found
- `remove(content)` — strip the marker block; return content unchanged if not found
- `wrap(payload, syntax)` — wrap a payload in marker pair using the requested comment syntax (`html` for markdown, `hash` for properties/ignore)

## Architecture Diagram

```
ask docs add / sync / remove
generateAgentsMd (existing — extended)
manageIgnoreFiles (new)
┌────┴────┬─────────────┐
↓ ↓ ↓
A B (folded C
into A's (root patch)
agents call)
↓ ↓
.ask/docs/ .prettierignore
.gitattributes sonar-project.properties
eslint.config.mjs .markdownlintignore (legacy)
biome.json
.markdownlint-cli2.jsonc
```

## Tasks

- [x] **T-1** — Add `manageIgnores` to `ConfigSchema` (`packages/cli/src/schemas.ts`), update `packages/cli/test/schemas.test.ts`
- [x] **T-2** — Create `packages/cli/src/markers.ts` (pure helpers: `inject`, `remove`, `wrap`) + `packages/cli/test/markers.test.ts`
- [x] **T-3** — Create `packages/cli/src/ignore-files.ts` with `writeNestedConfigs` / `removeNestedConfigs` for `.ask/docs/.gitattributes`, `eslint.config.mjs`, `biome.json`, `.markdownlint-cli2.jsonc` + tests
- [x] **T-4** — Extend `packages/cli/src/agents.ts` to prepend vendored-docs notice inside the existing auto-generated block + update `packages/cli/test/agents.test.ts`
- [x] **T-5** — Add `patchRootIgnores` / `unpatchRootIgnores` in `ignore-files.ts` for `.prettierignore`, `sonar-project.properties`, legacy `.markdownlintignore` (detection-only, marker-block based) + tests
- [x] **T-6** — Orchestrator `manageIgnoreFiles(projectDir, mode)` in `ignore-files.ts`; respects `manageIgnores` config; consola logging
- [x] **T-7** — Wire `manageIgnoreFiles` into `addCmd`, `runSync`, `removeCmd` in `packages/cli/src/index.ts`
- [x] **T-8** — Integration test covering full add → remove lifecycle (`packages/cli/test/ignore-lifecycle.test.ts`)
- [x] **T-9** — Documentation: update root `CLAUDE.md` Gotchas and, if applicable, `packages/cli/README.md`

## Dependencies

```
T-1 (schema) ──┐
├──> T-6 (orchestrator) ──> T-7 (wiring) ──> T-8 (e2e)
T-2 (markers) ─┤ ↑
│ │
T-3 (cat A) ───┤ │
│ │
T-4 (cat B) ───┤ │
│ │
T-5 (cat C) ───┘ │
T-9 (docs)
```

T-1, T-2, T-3, T-4, T-5 are independent and can land in any order. T-6 needs T-1/T-3/T-5. T-7 needs T-6 and (for the agents notice) T-4. T-8 needs all of the above.

## Key Files

| File | Role | New / Modified |
|---|---|---|
| `packages/cli/src/schemas.ts` | Add `manageIgnores` field | Modified |
| `packages/cli/src/markers.ts` | Marker block helpers (pure) | New |
| `packages/cli/src/ignore-files.ts` | Categories A/C + orchestrator | New |
| `packages/cli/src/agents.ts` | Extend block with vendored notice | Modified |
| `packages/cli/src/index.ts` | Wire `manageIgnoreFiles` into add/sync/remove | Modified |
| `packages/cli/test/markers.test.ts` | Marker helper unit tests | New |
| `packages/cli/test/ignore-files.test.ts` | Categories A/C unit tests | New |
| `packages/cli/test/ignore-lifecycle.test.ts` | E2E add/remove lifecycle | New |
| `packages/cli/test/agents.test.ts` | Vendored notice presence | Modified |
| `packages/cli/test/schemas.test.ts` | `manageIgnores` schema | Modified |

## Verification

- `bun run --cwd packages/cli test` — all unit + e2e tests green
- `bun run --cwd packages/cli lint` — no new lint warnings
- Manual smoke: in a temp dir with mock project (containing `.prettierignore` and `sonar-project.properties`), run `node packages/cli/dist/index.js docs add npm:react`, verify all artifacts. Then `docs remove react` and verify cleanup.
- Manual: confirm `.ask/docs/.gitattributes` is honored by `git check-attr` for a file inside `.ask/docs/`.
- AC-1 through AC-8 from spec each map to a test in T-3/T-4/T-5/T-8.

## Progress

- 2026-04-08: All 9 tasks complete. Test suite: 199 pass, 0 fail across 21 files. Lint clean for all new files (only pre-existing `package.json` sort-keys errors remain).

## Decision Log

- **2026-04-08**: Reuse existing `<!-- BEGIN:ask-docs-auto-generated -->` block in `agents.ts` instead of introducing a second marker convention (`<!-- ask:start -->`). Reduces code duplication and avoids confusion for readers.
- **2026-04-08**: Skip auto-patching root ESLint flat config / Biome / `.cursorignore`. ESLint and Biome are covered by nested configs inside `.ask/docs/`; `.cursorignore` would block AI context access (anti-goal). Documented in spec Out of Scope.
- **2026-04-08**: cubic and CodeRabbit need no dedicated configuration file patching — both auto-consume AGENTS.md/CLAUDE.md as context (verified via vendor docs). The single AGENTS.md notice transitively covers them.

## Outcomes & Retrospective

### What Was Shipped

- `markers.ts` (pure inject/remove/wrap helpers, two comment syntaxes)
- `ignore-files.ts` (nested-config writer, root-file patcher, top-level orchestrator with `manageIgnores` opt-out)
- Extended `agents.ts` auto-generated block with vendored-docs notice
- Schema field `ConfigSchema.manageIgnores` (optional, default `true`)
- Wired `manageIgnoreFiles(projectDir, mode)` into `addCmd`/`runSync`/`removeCmd`
- 43 new tests across markers, ignore-files, and a full add → remove lifecycle

### What Went Well

- Existing `agents.ts` marker block was reusable — no second marker convention needed
- Bun's nested workspaces let the existing test runner pick up new test files with zero config
- Spec carefully separated nested-capable tools (Cat A) from root-only tools (Cat C), so the implementation matched the design 1:1

### What Could Improve

- WebSearch verification of "tool X supports nested config" went through several wrong answers before landing on accurate sources. A canonical compatibility table inside the spec would have saved iterations.
- Worktrees require a manual `bun run --cwd packages/registry-schema build` before tests pass — surprising and easy to miss. Worth a `prepare` script.
- The PR creation hit a `PreToolUse` review-state hook mid-finalize. Documented in CLAUDE.md gotchas but easy to forget when chaining `/please:*` commands.

### Tech Debt Created

- Sub-threshold review note: ESLint flat config nested resolution should be empirically verified inside `.ask/docs/` (run `eslint .` from project root and confirm files inside `.ask/docs/` are skipped). My research supported it but I did not run an end-to-end check.
- Root `eslint.config.{js,mjs,ts}` and `biome.json` automatic patching is intentionally out of scope; if user feedback shows the nested-config approach is insufficient, revisit.

## Surprises & Discoveries

- **registry-schema not pre-built in worktree**: Full test suite failed with `Cannot find module '@pleaseai/registry-schema'` until `bun run --cwd packages/registry-schema build` was run. The CLI package imports compiled output from `dist/`, so worktrees need an initial build of the shared package. Candidate for a `postinstall` hook or test-time `prepare` step.
- **Marker `remove()` trailing-newline edge case**: First implementation left a stray `\n` after stripping a block in the middle of a file. Fixed by normalising the "after" segment's leading whitespace and only adding a trailing newline if the preserved tail doesn't already have one.
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
---
product_spec_domain: cli/ignore-management
---

# Vendored Docs Ignore Management

> Track: ignore-vendored-docs-20260408

## Overview

The `ask docs add` command marks `.ask/docs/` as **vendored third-party documentation**, achieving two goals simultaneously:

1. **AI agents must still be able to read it** (ASK's core value — docs are for reference)
2. **Excluded from lint / format / code review / modification** (because it's vendored)

To accomplish this, ASK (A) creates self-contained config files inside `.ask/docs/`, (B) injects an intent notice into ASK marker blocks in AGENTS.md/CLAUDE.md, and (C) patches root files only for tools that do not support nested ignore.

## Requirements

### Functional Requirements

#### A. Self-contained local config (created inside `.ask/docs/`)

- [ ] FR-A1: Create `.ask/docs/.gitattributes` — `* linguist-vendored=true` + `* linguist-generated=true` (collapses GitHub PR diffs + excludes from language statistics).
- [ ] FR-A2: Create `.ask/docs/eslint.config.mjs` — `export default [{ ignores: ['**/*'] }]` (ESLint flat config auto-discovers nested configs).
- [ ] FR-A3: Create `.ask/docs/biome.json` — exclude all files from processing (Biome auto-discovers nested configs).
- [ ] FR-A4: Create `.ask/docs/.markdownlint-cli2.jsonc` — `{ "ignores": ["**/*"] }` (markdownlint-cli2 supports nested config).

#### B. Intent notice (inside ASK marker block in AGENTS.md / CLAUDE.md)

- [ ] FR-B1: Auto-inject the following notice inside an ASK-managed marker block (`<!-- ask:start --> ... <!-- ask:end -->`):
> `.ask/docs/` contains vendored third-party documentation downloaded by ASK. Treat it as **read-only**: AI context should reference these files, but they are NOT subject to modification, lint, format, or code review. Updates are performed via `ask docs sync`.
- [ ] FR-B2: If AGENTS.md exists, inject into AGENTS.md. If CLAUDE.md exists, inject into CLAUDE.md. If both exist, inject into both. If neither exists, create AGENTS.md (standard preference).
- [ ] FR-B3: This single notice transitively affects the following AI tools (verified): CodeRabbit, cubic, Claude Code, Codex, Cursor (rules), GitHub Copilot, Continue, Aider, etc.

#### C. Root file patching (only for tools without nested support, only if detected)

- [ ] FR-C1: If root `.prettierignore` exists, add `.ask/docs/` exclusion patterns inside a marker block. (Prettier does not support nested ignore.)
- [ ] FR-C2: If root `sonar-project.properties` exists, append `.ask/docs/**` to `sonar.exclusions` inside a marker block.
- [ ] FR-C3: If root legacy `.markdownlintignore` (markdownlint-cli legacy) exists, add `.ask/docs/` inside a marker block. Also emit a recommendation to migrate to markdownlint-cli2.
- [ ] FR-C4: Do not create these files if absent — patch only when detected.

#### D. Marker block management

- [ ] FR-D1: Injection format follows each file's comment syntax. Properties/ignore files: `# <!-- ask:start -->` ... `# <!-- ask:end -->`. Markdown: `<!-- ask:start --> ... <!-- ask:end -->`. Properties (FR-C2): `# ask:start` ... `# ask:end`.
- [ ] FR-D2: Idempotent: running `add` repeatedly does not duplicate marker blocks; only the contents inside are refreshed.
- [ ] FR-D3: `ask docs sync` refreshes marker blocks. When `ask docs remove` removes the last docs entry, ASK deletes the local config files in (A) and removes the marker blocks in (B) and (C). Empty blocks are not left behind.
- [ ] FR-D4: ASK never modifies user content outside marker blocks.

#### E. Configuration and logging

- [ ] FR-E1: Add `manageIgnores: boolean` (default `true`) to `ask.config.json`. When `false`, all of categories A/B/C are skipped.
- [ ] FR-E2: During `add` / `sync` / `remove`, report the list of created/updated/removed files and skip reasons via consola.

### Non-functional Requirements

- [ ] NFR-1: Do **not** auto-patch root ESLint flat config (`eslint.config.{js,mjs,ts}`) — replaced by `.ask/docs/eslint.config.mjs` (FR-A2). Same applies to root `biome.json` and `.cursorignore` (the latter is intentionally avoided since it would block AI context access).
- [ ] NFR-2: Preserve existing EOL and encoding when writing files.
- [ ] NFR-3: Unit tests cover (A) local file creation, (B) AGENTS.md/CLAUDE.md injection·refresh·removal, (C) Prettier/Sonar marker block injection·refresh·removal, idempotency, and the `manageIgnores: false` skip path.
- [ ] NFR-4: All generated files follow the project's ESM conventions (2-space indent, single quotes, no semicolons where applicable).

## Acceptance Criteria

- [ ] AC-1: In an empty project, running `ask docs add npm:react` creates `.gitattributes`, `eslint.config.mjs`, `biome.json`, and `.markdownlint-cli2.jsonc` inside `.ask/docs/`.
- [ ] AC-2: In a project without AGENTS.md, after `add`, `AGENTS.md` is created and contains the vendored notice inside the ASK marker block.
- [ ] AC-3: In a project where `CLAUDE.md` exists, the same marker block is also injected into CLAUDE.md.
- [ ] AC-4: In a project with a root `.prettierignore`, after `add`, the marker block is appended to `.prettierignore`. In a project without `.prettierignore`, no file is created.
- [ ] AC-5: Running the same `add` command twice does not duplicate marker blocks in any file (idempotency).
- [ ] AC-6: When `ask docs remove` removes the last docs entry:
- Local config files inside `.ask/docs/` are deleted
- Marker blocks are removed from AGENTS.md/CLAUDE.md
- Marker blocks are removed from root `.prettierignore`/`sonar-project.properties` (the files themselves remain)
- [ ] AC-7: Setting `"manageIgnores": false` in `ask.config.json` causes categories A/B/C to be entirely skipped.
- [ ] AC-8: Root `.gitattributes` is left untouched (verified that `.ask/docs/.gitattributes` works correctly via Git's nested resolution).

## Out of Scope

- Auto-patching root ESLint flat config (`eslint.config.{js,mjs,ts}`) — replaced by `.ask/docs/eslint.config.mjs` nested config.
- Auto-patching root `biome.json` — replaced by `.ask/docs/biome.json` nested config.
- Creating `.cursorignore` — `.cursorignore` blocks AI access to file contents, which is the opposite of ASK's goal (AI must reference docs). Replaced by AGENTS.md notice.
- Auto-patching `.coderabbit.yaml` `path_filters` — CodeRabbit auto-reads AGENTS.md, so (B) suffices.
- Automating cubic cloud dashboard settings — cubic also auto-reads AGENTS.md/CLAUDE.md, so (B) suffices.
- `.gitignore` patching — whether to commit `.ask/docs/` is a user policy.
- IDE-specific settings (`.vscode/settings.json`, JetBrains scopes) — personal scope.
- Reorganizing/sorting other parts of existing ignore files.

## Assumptions

- The user does not directly edit `.ask/docs/` and treats it as vendored (`ask docs sync` overwrites it).
- The user's existing root ignore file content must be preserved; ASK only modifies marker blocks it owns.
- AI code review tools "respect" the AGENTS.md/CLAUDE.md notice but are not mechanically enforced. However, since CodeRabbit / cubic / Claude Code / Cursor all structurally consume context files, the practical effect is high.
- `manageIgnores` is a global switch applied to add/sync/remove uniformly.
Loading