jsdoczoom 0.4.20 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -7,6 +7,7 @@ import { JsdocError } from "./errors.js";
7
7
  import { lint, lintFiles } from "./lint.js";
8
8
  import { parseSelector } from "./selector.js";
9
9
  import { RULE_EXPLANATIONS, SKILL_TEXT } from "./skill-text.js";
10
+ import { formatTextOutput } from "./text-format.js";
10
11
  import { DEFAULT_CACHE_DIR, VALIDATION_STATUS_PRIORITY } from "./types.js";
11
12
  import { validate, validateFiles } from "./validate.js";
12
13
 
@@ -30,7 +31,8 @@ Options:
30
31
  -c, --check Run validation mode
31
32
  -l, --lint Run lint mode (comprehensive JSDoc quality)
32
33
  -s, --skill Print JSDoc writing guidelines
33
- --pretty Format JSON output with 2-space indent
34
+ --json Output as JSON (default is plain text)
35
+ --pretty Format JSON output with 2-space indent (use with --json)
34
36
  --limit N Max results shown (default 500)
35
37
  --no-gitignore Include files ignored by .gitignore
36
38
  --disable-cache Skip all cache operations
@@ -51,8 +53,12 @@ Stdin:
51
53
  find . -name "*.ts" | jsdoczoom -c
52
54
 
53
55
  Output:
54
- JSON items. Items with "next_id" have more detail; use that value as the next
55
- selector. Items with "id" (no next_id) are at terminal depth.
56
+ Plain text by default. Each item has a "# path@depth" header followed by
57
+ content. Use the header value as the next selector to drill deeper.
58
+ Use --json for machine-parseable JSON output.
59
+
60
+ Type declarations (@3) include source line annotations (// LN or // LN-LM)
61
+ so you can locate the implementation in the source file.
56
62
 
57
63
  Barrel gating (glob mode):
58
64
  A barrel's @summary and description reflect the cumulative functionality
@@ -70,15 +76,15 @@ Exit codes:
70
76
  2 Validation or lint failures found
71
77
 
72
78
  Workflow:
73
- $ jsdoczoom src/**/*.ts
74
- { "items": [{ "next_id": "src/utils@2", "text": "..." }, ...] }
75
-
76
- $ jsdoczoom src/utils@2 # use next_id from above
77
- { "items": [{ "next_id": "src/utils@3", "text": "..." }] }
79
+ $ jsdoczoom src/**/*.ts # list summaries
80
+ $ jsdoczoom src/utils@2 # drill into description
81
+ $ jsdoczoom src/utils@3 # see type declarations
78
82
 
79
- $ jsdoczoom src/utils@3 # use next_id again
80
- { "items": [{ "id": "src/utils@3", "text": "..." }] }
81
- # "id" instead of "next_id" means terminal depth stop here
83
+ Pipe examples:
84
+ $ jsdoczoom src/utils.ts@3 | grep "functionName" # find symbol + source line
85
+ $ jsdoczoom src/utils.ts@3 | grep "// L" # list all declarations with lines
86
+ $ jsdoczoom src/**/*.ts | grep "^#" # list all file headers
87
+ $ grep -rl "term" src/ --include="*.ts" | jsdoczoom # describe matching files
82
88
  `;
83
89
  /**
84
90
  * Parse a flag that requires a value argument.
@@ -97,6 +103,7 @@ function parseArgs(args) {
97
103
  checkMode: false,
98
104
  lintMode: false,
99
105
  skillMode: false,
106
+ json: false,
100
107
  pretty: false,
101
108
  limit: 500,
102
109
  gitignore: true,
@@ -128,6 +135,10 @@ function parseArgs(args) {
128
135
  parsed.skillMode = true;
129
136
  continue;
130
137
  }
138
+ if (arg === "--json") {
139
+ parsed.json = true;
140
+ continue;
141
+ }
131
142
  if (arg === "--pretty") {
132
143
  parsed.pretty = true;
133
144
  continue;
@@ -196,6 +207,7 @@ async function processStdin(
196
207
  selectorArg,
197
208
  checkMode,
198
209
  lintMode,
210
+ json,
199
211
  pretty,
200
212
  limit,
201
213
  cwd,
@@ -218,7 +230,7 @@ async function processStdin(
218
230
  limit,
219
231
  cacheConfig,
220
232
  );
221
- writeResult(result, pretty);
233
+ writeDrilldownResult(result, json, pretty);
222
234
  }
223
235
  }
224
236
  /**
@@ -228,6 +240,7 @@ async function processSelector(
228
240
  selectorArg,
229
241
  checkMode,
230
242
  lintMode,
243
+ json,
231
244
  pretty,
232
245
  limit,
233
246
  gitignore,
@@ -251,7 +264,7 @@ async function processSelector(
251
264
  limit,
252
265
  cacheConfig,
253
266
  );
254
- writeResult(result, pretty);
267
+ writeDrilldownResult(result, json, pretty);
255
268
  }
256
269
  }
257
270
  /**
@@ -352,6 +365,7 @@ export async function main(args, stdin) {
352
365
  parsed.selectorArg,
353
366
  parsed.checkMode,
354
367
  parsed.lintMode,
368
+ parsed.json,
355
369
  parsed.pretty,
356
370
  parsed.limit,
357
371
  cwd,
@@ -362,6 +376,7 @@ export async function main(args, stdin) {
362
376
  parsed.selectorArg,
363
377
  parsed.checkMode,
364
378
  parsed.lintMode,
379
+ parsed.json,
365
380
  parsed.pretty,
366
381
  parsed.limit,
367
382
  parsed.gitignore,
@@ -373,6 +388,16 @@ export async function main(args, stdin) {
373
388
  writeError(error);
374
389
  }
375
390
  }
391
+ /**
392
+ * Write a drilldown result to stdout as text (default) or JSON.
393
+ */
394
+ function writeDrilldownResult(result, json, pretty) {
395
+ if (json) {
396
+ writeResult(result, pretty);
397
+ } else {
398
+ void process.stdout.write(formatTextOutput(result));
399
+ }
400
+ }
376
401
  /**
377
402
  * Write a result to stdout as JSON.
378
403
  */
package/dist/drilldown.js CHANGED
@@ -335,11 +335,12 @@ export async function drilldown(
335
335
  const sorted = results.sort((a, b) =>
336
336
  sortKey(a).localeCompare(sortKey(b)),
337
337
  );
338
- const total = sorted.length;
339
- const truncated = total > limit;
338
+ const count = sorted.length;
339
+ const isTruncated = count > limit;
340
340
  return {
341
341
  items: sorted.slice(0, limit),
342
- truncated,
342
+ truncated: isTruncated,
343
+ ...(isTruncated ? { total: count } : {}),
343
344
  };
344
345
  }
345
346
  // Single file path — errors are fatal
@@ -394,11 +395,12 @@ export async function drilldown(
394
395
  const results = await processGlobWithBarrels(files, depth, cwd, config);
395
396
  // Sort alphabetically by key
396
397
  const sorted = results.sort((a, b) => sortKey(a).localeCompare(sortKey(b)));
397
- const total = sorted.length;
398
- const truncated = total > limit;
398
+ const count = sorted.length;
399
+ const isTruncated = count > limit;
399
400
  return {
400
401
  items: sorted.slice(0, limit),
401
- truncated,
402
+ truncated: isTruncated,
403
+ ...(isTruncated ? { total: count } : {}),
402
404
  };
403
405
  }
404
406
  /**
@@ -432,10 +434,11 @@ export async function drilldownFiles(
432
434
  });
433
435
  const results = await collectSafeResults(tsFiles, d, cwd, config);
434
436
  const sorted = results.sort((a, b) => sortKey(a).localeCompare(sortKey(b)));
435
- const total = sorted.length;
436
- const truncated = total > limit;
437
+ const count = sorted.length;
438
+ const isTruncated = count > limit;
437
439
  return {
438
440
  items: sorted.slice(0, limit),
439
- truncated,
441
+ truncated: isTruncated,
442
+ ...(isTruncated ? { total: count } : {}),
440
443
  };
441
444
  }
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@ export { discoverFiles } from "./file-discovery.js";
14
14
  export { extractFileJsdoc, parseFileSummaries } from "./jsdoc-parser.js";
15
15
  export { lint, lintFiles } from "./lint.js";
16
16
  export { parseSelector } from "./selector.js";
17
+ export { formatTextOutput } from "./text-format.js";
17
18
  export { generateTypeDeclarations } from "./type-declarations.js";
18
19
  export { DEFAULT_CACHE_DIR, VALIDATION_STATUS_PRIORITY } from "./types.js";
19
20
  export { validate, validateFiles } from "./validate.js";
@@ -368,6 +368,24 @@ function parse(input: string | Buffer): number {
368
368
  }
369
369
  \`\`\`
370
370
 
371
+ ## Text output and piping
372
+
373
+ jsdoczoom outputs human-readable text by default. Each item has a \`# path\` header followed by content. This makes it composable with standard Unix tools:
374
+
375
+ \`\`\`sh
376
+ jsdoczoom src/utils.ts@3 | grep "functionName" # find symbol + source line
377
+ jsdoczoom src/utils.ts@3 | grep "// L" # list all declarations with lines
378
+ jsdoczoom src/**/*.ts | grep "^#" # list all file summaries
379
+ grep -rl "term" src/ --include="*.ts" | jsdoczoom # describe matching files
380
+ \`\`\`
381
+
382
+ Type declarations include source line annotations (\`// LN\` or \`// LN-LM\` for ranges), so you can locate implementations in the source file without a separate search step.
383
+
384
+ For machine-parseable output, use \`--json\`:
385
+
386
+ \`\`\`sh
387
+ jsdoczoom --json src/**/*.ts | jq '.items[].text'
388
+ \`\`\`
371
389
  `;
372
390
  /** Explanation text for each lint rule, used by --explain-rule */
373
391
  export const RULE_EXPLANATIONS = {
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Formats drilldown results as human-readable text for shell piping.
3
+ * Each item gets a `# id` header line followed by its content.
4
+ * Items are separated by blank lines.
5
+ *
6
+ * @summary Plain-text output formatter for drilldown results
7
+ */
8
+ /**
9
+ * Format a single output entry as a text block (header + content).
10
+ */
11
+ function formatEntry(entry) {
12
+ if ("error" in entry) {
13
+ return `# ${entry.id} [${entry.error.code}]\n${entry.error.message}`;
14
+ }
15
+ const text = entry.text.trimEnd();
16
+ if ("next_id" in entry) {
17
+ const lines = [`# ${entry.next_id}`];
18
+ const children = entry.children;
19
+ if (children) {
20
+ lines.push(`## children: ${children.join(", ")}`);
21
+ }
22
+ if (text) lines.push(text);
23
+ return lines.join("\n");
24
+ }
25
+ // Terminal item
26
+ if (!text) return `# ${entry.id}`;
27
+ return `# ${entry.id}\n${text}`;
28
+ }
29
+ /**
30
+ * Format a DrilldownResult as plain text for CLI output.
31
+ * Returns the formatted string with a trailing newline.
32
+ */
33
+ export function formatTextOutput(result) {
34
+ const blocks = result.items.map(formatEntry);
35
+ let output = blocks.join("\n\n");
36
+ if (result.truncated && result.total !== undefined) {
37
+ output += `\n\n# truncated (showing ${result.items.length} of ${result.total})`;
38
+ } else if (result.truncated) {
39
+ output += "\n\n# truncated";
40
+ }
41
+ return `${output}\n`;
42
+ }
@@ -28,6 +28,7 @@ const serviceCache = new Map();
28
28
  export function resolveCompilerOptions(filePath) {
29
29
  // Required overrides that must always be present
30
30
  const requiredOverrides = {
31
+ noEmit: false,
31
32
  declaration: true,
32
33
  emitDeclarationOnly: true,
33
34
  removeComments: false,
@@ -152,6 +153,116 @@ export function resetCache() {
152
153
  compilerOptionsCache.clear();
153
154
  serviceCache.clear();
154
155
  }
156
+ /**
157
+ * Build a map of exported symbol names to their source line ranges.
158
+ * Walks top-level statements looking for export modifiers.
159
+ */
160
+ function buildSourceLineMap(filePath) {
161
+ const content = readFileSync(filePath, "utf-8");
162
+ const sourceFile = ts.createSourceFile(
163
+ filePath,
164
+ content,
165
+ ts.ScriptTarget.Latest,
166
+ true,
167
+ );
168
+ const map = new Map();
169
+ for (const statement of sourceFile.statements) {
170
+ // Only process exported declarations
171
+ const modifiers = ts.canHaveModifiers(statement)
172
+ ? ts.getModifiers(statement)
173
+ : undefined;
174
+ const isExported = modifiers?.some(
175
+ (m) => m.kind === ts.SyntaxKind.ExportKeyword,
176
+ );
177
+ if (!isExported) continue;
178
+ const start =
179
+ sourceFile.getLineAndCharacterOfPosition(statement.getStart(sourceFile))
180
+ .line + 1;
181
+ const end =
182
+ sourceFile.getLineAndCharacterOfPosition(statement.getEnd()).line + 1;
183
+ const addName = (name) => map.set(name, { start, end });
184
+ if (ts.isTypeAliasDeclaration(statement)) {
185
+ addName(statement.name.text);
186
+ } else if (ts.isInterfaceDeclaration(statement)) {
187
+ addName(statement.name.text);
188
+ } else if (ts.isFunctionDeclaration(statement) && statement.name) {
189
+ addName(statement.name.text);
190
+ } else if (ts.isClassDeclaration(statement) && statement.name) {
191
+ addName(statement.name.text);
192
+ } else if (ts.isEnumDeclaration(statement)) {
193
+ addName(statement.name.text);
194
+ } else if (ts.isVariableStatement(statement)) {
195
+ for (const decl of statement.declarationList.declarations) {
196
+ if (ts.isIdentifier(decl.name)) {
197
+ addName(decl.name.text);
198
+ }
199
+ }
200
+ }
201
+ }
202
+ return map;
203
+ }
204
+ /** Regex matching the start of a top-level export declaration in .d.ts output. */
205
+ const DECL_PATTERN =
206
+ /^export\s+(?:default\s+)?(?:declare\s+)?(?:abstract\s+)?(?:type|interface|function|const|let|var|class|enum)\s+(\w+)/;
207
+ /**
208
+ * Format a source range as a GitHub-style line annotation.
209
+ */
210
+ function formatLineRef(range) {
211
+ return range.start === range.end
212
+ ? `// L${range.start}`
213
+ : `// L${range.start}-L${range.end}`;
214
+ }
215
+ /**
216
+ * Find the start of the JSDoc block above a declaration line.
217
+ * Returns the declaration line index itself if no JSDoc is found.
218
+ */
219
+ function findChunkStart(lines, declLine) {
220
+ if (declLine === 0) return 0;
221
+ const prev = lines[declLine - 1]?.trimEnd();
222
+ if (prev === "*/" || prev?.endsWith("*/")) {
223
+ for (let j = declLine - 1; j >= 0; j--) {
224
+ if (lines[j].trimStart().startsWith("/**")) return j;
225
+ }
226
+ } else if (prev?.trimStart().startsWith("/**")) {
227
+ // Single-line JSDoc: /** comment */
228
+ return declLine - 1;
229
+ }
230
+ return declLine;
231
+ }
232
+ /**
233
+ * Annotate .d.ts text with source line references for each top-level declaration.
234
+ * Inserts GitHub-style `// LN` or `// LN-LM` on a separate line before each
235
+ * declaration chunk, with a blank line above for visual separation.
236
+ */
237
+ function annotateWithSourceLines(dtsText, filePath) {
238
+ const lineMap = buildSourceLineMap(filePath);
239
+ if (lineMap.size === 0) return dtsText;
240
+ const lines = dtsText.split("\n");
241
+ // First pass: map chunk start lines to their annotations
242
+ const annotations = new Map();
243
+ for (let i = 0; i < lines.length; i++) {
244
+ const match = lines[i].match(DECL_PATTERN);
245
+ if (!match) continue;
246
+ const range = lineMap.get(match[1]);
247
+ if (!range) continue;
248
+ annotations.set(findChunkStart(lines, i), formatLineRef(range));
249
+ }
250
+ if (annotations.size === 0) return dtsText;
251
+ // Second pass: build output with annotations inserted
252
+ const result = [];
253
+ for (let i = 0; i < lines.length; i++) {
254
+ const annotation = annotations.get(i);
255
+ if (annotation) {
256
+ // Blank line separator before annotation (if there's content above)
257
+ if (result.length > 0 && result[result.length - 1]?.trim() !== "") {
258
+ result.push("");
259
+ }
260
+ result.push(annotation);
261
+ }
262
+ result.push(lines[i]);
263
+ }
264
+ return result.join("\n");
265
+ }
155
266
  /**
156
267
  * Generates TypeScript declaration output from a source file.
157
268
  *
@@ -207,5 +318,8 @@ export function generateTypeDeclarations(filePath) {
207
318
  if (withoutComments === "export {};") {
208
319
  cleaned = "";
209
320
  }
321
+ if (cleaned.length > 0) {
322
+ cleaned = annotateWithSourceLines(cleaned, filePath);
323
+ }
210
324
  return cleaned;
211
325
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsdoczoom",
3
- "version": "0.4.20",
3
+ "version": "1.0.0",
4
4
  "description": "CLI tool for extracting JSDoc summaries at configurable depths",
5
5
  "type": "module",
6
6
  "sideEffects": false,
package/types/index.d.ts CHANGED
@@ -14,6 +14,7 @@ export { discoverFiles } from "./file-discovery.js";
14
14
  export { extractFileJsdoc, parseFileSummaries } from "./jsdoc-parser.js";
15
15
  export { lint, lintFiles } from "./lint.js";
16
16
  export { parseSelector } from "./selector.js";
17
+ export { formatTextOutput } from "./text-format.js";
17
18
  export { generateTypeDeclarations } from "./type-declarations.js";
18
19
  export {
19
20
  type CacheConfig,
@@ -11,6 +11,6 @@ import type { ValidationStatus } from "./types.js";
11
11
  /** Markdown guidance text for each validation status category */
12
12
  export declare const GUIDANCE: Record<ValidationStatus, string>;
13
13
  export declare const SKILL_TEXT =
14
- '# World-Class JSDoc Guidelines for TypeScript\n\nThese guidelines describe the *properties* of excellent inline JSDoc in TypeScript repositories. They are a target to aim for, not a checklist. Use judgment; clarity beats volume.\n\n## Core principle\n\nTypeScript projects already have explicit types. JSDoc should add **intent, behavior, and constraints** rather than repeat what the type system already expresses.\n\n## Would have (high-signal properties)\n\n- **Intent and constraints**: Explains the non-obvious decision, constraint, or tradeoff (e.g., why a particular algorithm is used, why a heuristic exists, or what the runtime environment forbids).\n- **Behavioral contract**: Clearly states what inputs are accepted, what outputs represent, and how boundary cases are treated.\n- **Domain vocabulary**: Uses project-specific terms consistently so readers can navigate the codebase by vocabulary.\n- **Examples that disambiguate**: Includes `@example` blocks when behavior is otherwise ambiguous (e.g., parsing rules, path formats, or object schemas). Examples are short and realistic.\n- **Runtime effects**: Calls out side effects, temporal dependency (polling vs. event-driven), filesystem reads/writes, or external service behavior when those matter.\n- **Edge-case prompts**: Captures "what happens if..." questions that a reviewer would ask, especially where external APIs or platform behavior has caveats.\n- **Consistency with existing style**: Matches the formatting conventions already established in the codebase (multi-line descriptions, bullet lists where helpful).\n- **Future-proof hints**: Notes invariants and assumptions that must hold if the code evolves.\n- **LLM-friendly structure**: Uses short, self-contained paragraphs written in clear International Business English. Avoids prescriptive headers (e.g., "Why:", "Constraint:") in favor of natural prose that states context, purpose, and caveats directly.\n\n## Would not have (low-signal or risky properties)\n\n- **Type restatements**: Repeating TypeScript types in prose (e.g., "@param options - The options object" when the type is already `Options`). Keep `@param`, `@returns`, and `@throws` tags\u2014just make the descriptions add meaning beyond the type.\n- **Obvious narration**: Comments that paraphrase the code or parameter name without additional insight.\n- **Incorrect authority**: Claims that are not enforced by code (e.g., "never throws" when it can, or "always" without guardrails).\n- **Redundant verbosity**: Long descriptions that could be expressed more directly, or boilerplate that hides the key idea.\n- **Unbounded examples**: Large blocks or full payloads when a minimal example would do.\n- **Out-of-date operational details**: References to tooling, CLI flags, or config knobs that are not enforced or checked.\n- **Implementation leakage**: Unnecessary internal steps or private details that are likely to change and add churn to docs.\n- **Non-ASCII decoration**: Fancy symbols or emojis that do not already exist in the file; keep ASCII unless needed.\n\n## Tag usage cues (not rules)\n\n### IntelliSense tags (always include)\n\nThese tags power IDE hover tooltips, autocomplete, and signature help. Always include them for public APIs, constructors, and functions:\n\n- **`@param`**: Include for every parameter. Describe the parameter\'s purpose, valid ranges, or constraints\u2014not just its type.\n- **`@returns`**: Include when the function returns a value. Describe what the return value represents, especially for edge cases.\n- **`@throws`**: Include when the function can throw. Describe the conditions that cause the error.\n- **`@template`**: Include for generic functions and types. Describe what the type parameter represents.\n\n### Cross-reference tags (use to aid navigation)\n\n- **`@see`**: Link to related functions, types, or external documentation.\n- **`{@link Symbol}`**: Inline reference within descriptions. Creates a clickable link to another symbol.\n\n### Structural tags (use when appropriate)\n\n- Use `@module` at the top of files that define a cohesive domain concept.\n- Use `@example` when parsing or formatting behavior could be misread, or when a type is complex.\n- Use `@deprecated` on exports that are retained for compatibility.\n- Prefer short description + bullets for concepts with multiple facets.\n\n---\n\n## Writing file-level @summary and description\n\nEvery TypeScript file should have a file-level JSDoc block before the first code statement. This block is what jsdoczoom reads for orientation and validation.\n\n### Structure\n\n```typescript\n/**\n * Description paragraph goes here. It explains the file\'s responsibilities,\n * invariants, trade-offs, and failure modes. This is the deepest level of\n * native documentation \u2014 enough for someone to understand why this file\n * exists and how it fits into the broader system.\n *\n * @summary Concise one-line overview for quick orientation when scanning a codebase.\n */\n```\n\n### The @summary tag\n\nThe `@summary` tag provides a one-line overview \u2014 the first thing someone sees when scanning with jsdoczoom at the shallowest depth.\n\n**Good summaries:**\n- State what the file *does* or *is responsible for*, not what it contains\n- Are self-contained \u2014 understandable without reading other files\n- Use domain vocabulary consistently with the rest of the codebase\n- Fit on a single line (joined if multi-line in source)\n\n**Examples:**\n- `@summary Barrel tree model for hierarchical gating in glob mode`\n- `@summary Resolve selector patterns to absolute file paths with gitignore filtering`\n- `@summary CLI entry point \u2014 argument parsing, mode dispatch, and exit code handling`\n\n**Avoid:**\n- `@summary This file contains utility functions` \u2014 says what it *contains*, not what it *does*\n- `@summary Helpers` \u2014 too vague, no domain context\n- `@summary The main module` \u2014 no information about purpose or scope\n\n### The description paragraph\n\nThe description is prose that appears before any `@` tags. It provides the deeper context that the summary cannot \u2014 responsibilities, invariants, trade-offs, and failure modes.\n\n**Good descriptions:**\n- Explain *why* this file exists and what problem it solves\n- State invariants and assumptions that callers or maintainers must know\n- Note trade-offs and design decisions (e.g., "uses priority-order fill to keep the limit algorithm simple")\n- Mention failure modes and edge cases relevant to the file as a whole\n- Are 1-4 sentences, not an essay\n\n**Examples:**\n```typescript\n/**\n * Walks .gitignore files from cwd to filesystem root, building an ignore\n * filter that glob results pass through. Direct-path lookups bypass the\n * filter since the user explicitly named the file. The ignore instance is\n * created per call \u2014 no caching \u2014 because cwd may differ between invocations.\n *\n * @summary Resolve selector patterns to absolute file paths with gitignore filtering\n */\n```\n\n```typescript\n/**\n * Each file is classified into exactly one status category: the first\n * failing check wins (syntax_error > missing_jsdoc > missing_summary >\n * missing_description). Valid files are omitted from output entirely.\n * The limit parameter caps the total number of invalid paths shown,\n * filled in priority order across groups.\n *\n * @summary Validate file-level JSDoc and group results by status category\n */\n```\n\n**Avoid:**\n- Restating the summary in longer words\n- Listing every function in the file\n- Implementation details that change frequently (line numbers, internal variable names)\n\n### Barrel files (index.ts / index.tsx)\n\nBarrel files represent their directory. Their `@summary` and description should describe the **cumulative functionality of the directory\'s children**, not the barrel file itself.\n\n- **`@summary`**: The collective purpose of the files in this directory\n- **Description**: The combined capabilities and responsibilities of child modules \u2014 what they do together, not their filenames or the re-export mechanism\n\n```typescript\n// packages/auth/src/index.ts\n/**\n * Provides session lifecycle management, token validation and refresh,\n * and middleware for route-level access control. OAuth2 provider\n * integration is handled here; cryptographic primitives are delegated\n * to the crypto package.\n *\n * @summary Authentication and authorization module\n */\n```\n\n**Avoid:**\n- `@summary Re-exports all functions and types` \u2014 describes the barrel mechanism, not what the children do\n- `@summary Exports for the auth module` \u2014 describes the mechanism, not the purpose\n- `@summary Public API barrel` \u2014 names the pattern rather than describing functionality\n- Listing child filenames or saying "This is the entry point"\n\n### Placement\n\nThe file-level JSDoc block must appear **before the first code statement** (imports are fine above it, but the block must precede any `export`, `const`, `function`, `class`, etc.). A common pattern is to place it immediately after imports:\n\n```typescript\nimport { resolve } from "node:path";\nimport { globSync } from "glob";\n\n/**\n * Description paragraph here.\n *\n * @summary One-line overview here\n */\n\nexport function discoverFiles(...) { ... }\n```\n\n### Common lint rules and examples\n\nThese rules are enforced in lint mode (`-l`). Understanding what passes and fails reduces trial-and-rerun cycles.\n\n#### `jsdoc/informative-docs` \u2014 descriptions must add meaning\n\nThis rule rejects descriptions that merely restate the parameter name, type, or containing symbol name. Descriptions must provide behavioral context.\n\n**Fails:**\n- `@param id - The id`\n- `@param options - The options object`\n- `@returns The result`\n- `@param name - The name string`\n\n**Passes:**\n- `@param id - Unique identifier used for cache lookup and deduplication`\n- `@param options - Controls retry behavior, timeout, and error handling strategy`\n- `@returns Parsed configuration with defaults applied for missing fields`\n- `@param name - Display name shown in the navigation sidebar`\n\n**Rule of thumb:** If removing the parameter name from the description leaves no useful information, the description is not informative enough.\n\n#### `jsdoc/check-tag-names` \u2014 allowed tags only\n\nThe lint configuration rejects non-standard JSDoc tags. Common tags to **avoid in JSDoc blocks**:\n- `@remarks` \u2014 move content to the description paragraph (prose before tags)\n- `@packageDocumentation` \u2014 use `@module` instead\n- `@concept`, `@constraint` \u2014 move content to the description paragraph\n\nFramework directives that look like tags (e.g., `@vitest-environment`) should use plain comments instead:\n```typescript\n// @vitest-environment node \u2190 correct (plain comment)\n/** @vitest-environment node */ \u2190 incorrect (treated as JSDoc tag)\n```\n\n#### `jsdoc/require-throws` \u2014 document throw conditions\n\nInclude `@throws` for any function that can throw, including catch-and-rethrow patterns:\n```typescript\n/**\n * @param path - File path to read\n * @returns Parsed configuration object\n * @throws {ConfigError} When the file is missing or contains invalid YAML\n */\n```\n\nIf a function catches errors and rethrows them (wrapped or unwrapped), it still needs `@throws`.\n\n### Nested object parameters\n\nWhen a function accepts an inline object parameter, document each property with a nested `@param` tag:\n\n```typescript\n/**\n * Create a new user account.\n *\n * @param data - Account creation payload\n * @param data.email - Email address used for login and notifications\n * @param data.displayName - Public-facing name shown in the UI\n * @param data.role - Initial permission level assigned to the account\n * @returns The created user record with generated ID\n */\nfunction createUser(data: { email: string; displayName: string; role: Role }): User {\n```\n\nFor React component props, use `props` (or `root0` if destructured) as the root:\n\n```typescript\n/**\n * @param props - Component properties\n * @param props.title - Page heading displayed at the top\n * @param props.onSubmit - Callback invoked when the form is submitted\n */\nfunction MyComponent(props: { title: string; onSubmit: () => void }) {\n```\n\n### Overload documentation\n\nWhen a function has TypeScript overload signatures, document **both** the overload declarations and the implementation signature:\n\n```typescript\n/**\n * Parse a value from a string representation.\n *\n * @param input - Raw string to parse\n * @returns Parsed numeric value\n */\nfunction parse(input: string): number;\n/**\n * Parse a value from a buffer.\n *\n * @param input - Binary buffer to parse\n * @returns Parsed numeric value\n */\nfunction parse(input: Buffer): number;\n/**\n * Parse a value from string or buffer input. String inputs are decoded\n * as UTF-8 before numeric parsing.\n *\n * @param input - String or buffer to parse\n * @returns Parsed numeric value\n * @throws {ParseError} When the input cannot be interpreted as a number\n */\nfunction parse(input: string | Buffer): number {\n // implementation\n}\n```\n\n';
14
+ '# World-Class JSDoc Guidelines for TypeScript\n\nThese guidelines describe the *properties* of excellent inline JSDoc in TypeScript repositories. They are a target to aim for, not a checklist. Use judgment; clarity beats volume.\n\n## Core principle\n\nTypeScript projects already have explicit types. JSDoc should add **intent, behavior, and constraints** rather than repeat what the type system already expresses.\n\n## Would have (high-signal properties)\n\n- **Intent and constraints**: Explains the non-obvious decision, constraint, or tradeoff (e.g., why a particular algorithm is used, why a heuristic exists, or what the runtime environment forbids).\n- **Behavioral contract**: Clearly states what inputs are accepted, what outputs represent, and how boundary cases are treated.\n- **Domain vocabulary**: Uses project-specific terms consistently so readers can navigate the codebase by vocabulary.\n- **Examples that disambiguate**: Includes `@example` blocks when behavior is otherwise ambiguous (e.g., parsing rules, path formats, or object schemas). Examples are short and realistic.\n- **Runtime effects**: Calls out side effects, temporal dependency (polling vs. event-driven), filesystem reads/writes, or external service behavior when those matter.\n- **Edge-case prompts**: Captures "what happens if..." questions that a reviewer would ask, especially where external APIs or platform behavior has caveats.\n- **Consistency with existing style**: Matches the formatting conventions already established in the codebase (multi-line descriptions, bullet lists where helpful).\n- **Future-proof hints**: Notes invariants and assumptions that must hold if the code evolves.\n- **LLM-friendly structure**: Uses short, self-contained paragraphs written in clear International Business English. Avoids prescriptive headers (e.g., "Why:", "Constraint:") in favor of natural prose that states context, purpose, and caveats directly.\n\n## Would not have (low-signal or risky properties)\n\n- **Type restatements**: Repeating TypeScript types in prose (e.g., "@param options - The options object" when the type is already `Options`). Keep `@param`, `@returns`, and `@throws` tags\u2014just make the descriptions add meaning beyond the type.\n- **Obvious narration**: Comments that paraphrase the code or parameter name without additional insight.\n- **Incorrect authority**: Claims that are not enforced by code (e.g., "never throws" when it can, or "always" without guardrails).\n- **Redundant verbosity**: Long descriptions that could be expressed more directly, or boilerplate that hides the key idea.\n- **Unbounded examples**: Large blocks or full payloads when a minimal example would do.\n- **Out-of-date operational details**: References to tooling, CLI flags, or config knobs that are not enforced or checked.\n- **Implementation leakage**: Unnecessary internal steps or private details that are likely to change and add churn to docs.\n- **Non-ASCII decoration**: Fancy symbols or emojis that do not already exist in the file; keep ASCII unless needed.\n\n## Tag usage cues (not rules)\n\n### IntelliSense tags (always include)\n\nThese tags power IDE hover tooltips, autocomplete, and signature help. Always include them for public APIs, constructors, and functions:\n\n- **`@param`**: Include for every parameter. Describe the parameter\'s purpose, valid ranges, or constraints\u2014not just its type.\n- **`@returns`**: Include when the function returns a value. Describe what the return value represents, especially for edge cases.\n- **`@throws`**: Include when the function can throw. Describe the conditions that cause the error.\n- **`@template`**: Include for generic functions and types. Describe what the type parameter represents.\n\n### Cross-reference tags (use to aid navigation)\n\n- **`@see`**: Link to related functions, types, or external documentation.\n- **`{@link Symbol}`**: Inline reference within descriptions. Creates a clickable link to another symbol.\n\n### Structural tags (use when appropriate)\n\n- Use `@module` at the top of files that define a cohesive domain concept.\n- Use `@example` when parsing or formatting behavior could be misread, or when a type is complex.\n- Use `@deprecated` on exports that are retained for compatibility.\n- Prefer short description + bullets for concepts with multiple facets.\n\n---\n\n## Writing file-level @summary and description\n\nEvery TypeScript file should have a file-level JSDoc block before the first code statement. This block is what jsdoczoom reads for orientation and validation.\n\n### Structure\n\n```typescript\n/**\n * Description paragraph goes here. It explains the file\'s responsibilities,\n * invariants, trade-offs, and failure modes. This is the deepest level of\n * native documentation \u2014 enough for someone to understand why this file\n * exists and how it fits into the broader system.\n *\n * @summary Concise one-line overview for quick orientation when scanning a codebase.\n */\n```\n\n### The @summary tag\n\nThe `@summary` tag provides a one-line overview \u2014 the first thing someone sees when scanning with jsdoczoom at the shallowest depth.\n\n**Good summaries:**\n- State what the file *does* or *is responsible for*, not what it contains\n- Are self-contained \u2014 understandable without reading other files\n- Use domain vocabulary consistently with the rest of the codebase\n- Fit on a single line (joined if multi-line in source)\n\n**Examples:**\n- `@summary Barrel tree model for hierarchical gating in glob mode`\n- `@summary Resolve selector patterns to absolute file paths with gitignore filtering`\n- `@summary CLI entry point \u2014 argument parsing, mode dispatch, and exit code handling`\n\n**Avoid:**\n- `@summary This file contains utility functions` \u2014 says what it *contains*, not what it *does*\n- `@summary Helpers` \u2014 too vague, no domain context\n- `@summary The main module` \u2014 no information about purpose or scope\n\n### The description paragraph\n\nThe description is prose that appears before any `@` tags. It provides the deeper context that the summary cannot \u2014 responsibilities, invariants, trade-offs, and failure modes.\n\n**Good descriptions:**\n- Explain *why* this file exists and what problem it solves\n- State invariants and assumptions that callers or maintainers must know\n- Note trade-offs and design decisions (e.g., "uses priority-order fill to keep the limit algorithm simple")\n- Mention failure modes and edge cases relevant to the file as a whole\n- Are 1-4 sentences, not an essay\n\n**Examples:**\n```typescript\n/**\n * Walks .gitignore files from cwd to filesystem root, building an ignore\n * filter that glob results pass through. Direct-path lookups bypass the\n * filter since the user explicitly named the file. The ignore instance is\n * created per call \u2014 no caching \u2014 because cwd may differ between invocations.\n *\n * @summary Resolve selector patterns to absolute file paths with gitignore filtering\n */\n```\n\n```typescript\n/**\n * Each file is classified into exactly one status category: the first\n * failing check wins (syntax_error > missing_jsdoc > missing_summary >\n * missing_description). Valid files are omitted from output entirely.\n * The limit parameter caps the total number of invalid paths shown,\n * filled in priority order across groups.\n *\n * @summary Validate file-level JSDoc and group results by status category\n */\n```\n\n**Avoid:**\n- Restating the summary in longer words\n- Listing every function in the file\n- Implementation details that change frequently (line numbers, internal variable names)\n\n### Barrel files (index.ts / index.tsx)\n\nBarrel files represent their directory. Their `@summary` and description should describe the **cumulative functionality of the directory\'s children**, not the barrel file itself.\n\n- **`@summary`**: The collective purpose of the files in this directory\n- **Description**: The combined capabilities and responsibilities of child modules \u2014 what they do together, not their filenames or the re-export mechanism\n\n```typescript\n// packages/auth/src/index.ts\n/**\n * Provides session lifecycle management, token validation and refresh,\n * and middleware for route-level access control. OAuth2 provider\n * integration is handled here; cryptographic primitives are delegated\n * to the crypto package.\n *\n * @summary Authentication and authorization module\n */\n```\n\n**Avoid:**\n- `@summary Re-exports all functions and types` \u2014 describes the barrel mechanism, not what the children do\n- `@summary Exports for the auth module` \u2014 describes the mechanism, not the purpose\n- `@summary Public API barrel` \u2014 names the pattern rather than describing functionality\n- Listing child filenames or saying "This is the entry point"\n\n### Placement\n\nThe file-level JSDoc block must appear **before the first code statement** (imports are fine above it, but the block must precede any `export`, `const`, `function`, `class`, etc.). A common pattern is to place it immediately after imports:\n\n```typescript\nimport { resolve } from "node:path";\nimport { globSync } from "glob";\n\n/**\n * Description paragraph here.\n *\n * @summary One-line overview here\n */\n\nexport function discoverFiles(...) { ... }\n```\n\n### Common lint rules and examples\n\nThese rules are enforced in lint mode (`-l`). Understanding what passes and fails reduces trial-and-rerun cycles.\n\n#### `jsdoc/informative-docs` \u2014 descriptions must add meaning\n\nThis rule rejects descriptions that merely restate the parameter name, type, or containing symbol name. Descriptions must provide behavioral context.\n\n**Fails:**\n- `@param id - The id`\n- `@param options - The options object`\n- `@returns The result`\n- `@param name - The name string`\n\n**Passes:**\n- `@param id - Unique identifier used for cache lookup and deduplication`\n- `@param options - Controls retry behavior, timeout, and error handling strategy`\n- `@returns Parsed configuration with defaults applied for missing fields`\n- `@param name - Display name shown in the navigation sidebar`\n\n**Rule of thumb:** If removing the parameter name from the description leaves no useful information, the description is not informative enough.\n\n#### `jsdoc/check-tag-names` \u2014 allowed tags only\n\nThe lint configuration rejects non-standard JSDoc tags. Common tags to **avoid in JSDoc blocks**:\n- `@remarks` \u2014 move content to the description paragraph (prose before tags)\n- `@packageDocumentation` \u2014 use `@module` instead\n- `@concept`, `@constraint` \u2014 move content to the description paragraph\n\nFramework directives that look like tags (e.g., `@vitest-environment`) should use plain comments instead:\n```typescript\n// @vitest-environment node \u2190 correct (plain comment)\n/** @vitest-environment node */ \u2190 incorrect (treated as JSDoc tag)\n```\n\n#### `jsdoc/require-throws` \u2014 document throw conditions\n\nInclude `@throws` for any function that can throw, including catch-and-rethrow patterns:\n```typescript\n/**\n * @param path - File path to read\n * @returns Parsed configuration object\n * @throws {ConfigError} When the file is missing or contains invalid YAML\n */\n```\n\nIf a function catches errors and rethrows them (wrapped or unwrapped), it still needs `@throws`.\n\n### Nested object parameters\n\nWhen a function accepts an inline object parameter, document each property with a nested `@param` tag:\n\n```typescript\n/**\n * Create a new user account.\n *\n * @param data - Account creation payload\n * @param data.email - Email address used for login and notifications\n * @param data.displayName - Public-facing name shown in the UI\n * @param data.role - Initial permission level assigned to the account\n * @returns The created user record with generated ID\n */\nfunction createUser(data: { email: string; displayName: string; role: Role }): User {\n```\n\nFor React component props, use `props` (or `root0` if destructured) as the root:\n\n```typescript\n/**\n * @param props - Component properties\n * @param props.title - Page heading displayed at the top\n * @param props.onSubmit - Callback invoked when the form is submitted\n */\nfunction MyComponent(props: { title: string; onSubmit: () => void }) {\n```\n\n### Overload documentation\n\nWhen a function has TypeScript overload signatures, document **both** the overload declarations and the implementation signature:\n\n```typescript\n/**\n * Parse a value from a string representation.\n *\n * @param input - Raw string to parse\n * @returns Parsed numeric value\n */\nfunction parse(input: string): number;\n/**\n * Parse a value from a buffer.\n *\n * @param input - Binary buffer to parse\n * @returns Parsed numeric value\n */\nfunction parse(input: Buffer): number;\n/**\n * Parse a value from string or buffer input. String inputs are decoded\n * as UTF-8 before numeric parsing.\n *\n * @param input - String or buffer to parse\n * @returns Parsed numeric value\n * @throws {ParseError} When the input cannot be interpreted as a number\n */\nfunction parse(input: string | Buffer): number {\n // implementation\n}\n```\n\n## Text output and piping\n\njsdoczoom outputs human-readable text by default. Each item has a `# path` header followed by content. This makes it composable with standard Unix tools:\n\n```sh\njsdoczoom src/utils.ts@3 | grep "functionName" # find symbol + source line\njsdoczoom src/utils.ts@3 | grep "// L" # list all declarations with lines\njsdoczoom src/**/*.ts | grep "^#" # list all file summaries\ngrep -rl "term" src/ --include="*.ts" | jsdoczoom # describe matching files\n```\n\nType declarations include source line annotations (`// LN` or `// LN-LM` for ranges), so you can locate implementations in the source file without a separate search step.\n\nFor machine-parseable output, use `--json`:\n\n```sh\njsdoczoom --json src/**/*.ts | jq \'.items[].text\'\n```\n';
15
15
  /** Explanation text for each lint rule, used by --explain-rule */
16
16
  export declare const RULE_EXPLANATIONS: Record<string, string>;
@@ -0,0 +1,6 @@
1
+ import type { DrilldownResult } from "./types.js";
2
+ /**
3
+ * Format a DrilldownResult as plain text for CLI output.
4
+ * Returns the formatted string with a trailing newline.
5
+ */
6
+ export declare function formatTextOutput(result: DrilldownResult): string;
package/types/types.d.ts CHANGED
@@ -32,6 +32,7 @@ export type OutputEntry = OutputItem | OutputErrorItem;
32
32
  export interface DrilldownResult {
33
33
  items: OutputEntry[];
34
34
  truncated: boolean;
35
+ total?: number;
35
36
  }
36
37
  /** All recognized error codes */
37
38
  export type ErrorCode =