📦 deps(thirdparty): update snapshots
This commit is contained in:
@@ -0,0 +1,424 @@
|
||||
---
|
||||
name: docs-audit
|
||||
description: |
|
||||
Comprehensive documentation audit against current code state. Checks markdown files for accuracy, link validity, code example correctness, and docstring coverage. Uses efficient discovery to minimize context usage while providing thorough analysis.
|
||||
context: fork
|
||||
agent: editor
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
- Write
|
||||
- Glob
|
||||
- Grep
|
||||
- TaskCreate
|
||||
- TaskUpdate
|
||||
- TaskList
|
||||
- TaskGet
|
||||
metadata:
|
||||
arg-schema:
|
||||
path:
|
||||
type: string
|
||||
description: Optional path to limit audit scope (e.g., "docs/", "src/")
|
||||
focus:
|
||||
type: string
|
||||
enum: [stale, all, recent]
|
||||
default: stale
|
||||
description: Which files to prioritize - stale (default), all, or recently modified
|
||||
limit:
|
||||
type: number
|
||||
default: 10
|
||||
description: Maximum number of files to analyze deeply (5-20 recommended)
|
||||
output:
|
||||
type: string
|
||||
description: Output path for report. Defaults to .pack/reports/{timestamp}-docs-audit-{sessionShort}.md
|
||||
multi:
|
||||
type: boolean
|
||||
default: false
|
||||
description: Multi-file mode - creates .pack/reports/{timestamp}-docs-audit-{sessionShort}/ directory
|
||||
---
|
||||
|
||||
# Documentation Audit Skill
|
||||
|
||||
Audit documentation files against the current codebase state, checking for accuracy, completeness, and freshness.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Stage 1: Discovery
|
||||
|
||||
Run the discovery script to get a manifest of all markdown files with git metadata:
|
||||
|
||||
```bash
|
||||
bun "$(dirname "$0")/scripts/discover-docs.ts" ${path ? `--path "${path}"` : ""} --limit 50 --sort staleness
|
||||
```
|
||||
|
||||
Parse the JSON output to understand:
|
||||
- Total documentation files in scope
|
||||
- Activity status distribution (active/recent/idle/stale/ancient)
|
||||
- Files with related code changes (potential staleness indicators)
|
||||
|
||||
### Stage 2: Prioritization
|
||||
|
||||
Select files for deep analysis based on `focus` argument:
|
||||
|
||||
| Focus | Strategy |
|
||||
|-------|----------|
|
||||
| `stale` | Prioritize files with oldest commits, especially those with recent related code changes |
|
||||
| `all` | Balanced sampling across activity statuses |
|
||||
| `recent` | Focus on recently modified docs that may have introduced errors |
|
||||
|
||||
Target: Select top `limit` files (default 10) for deep analysis.
|
||||
|
||||
### Stage 3: Deep Analysis
|
||||
|
||||
For each selected file, perform these checks:
|
||||
|
||||
#### 3.1 Correctness (see references/correctness-checklist.md)
|
||||
|
||||
- [ ] Code examples use correct imports/require paths
|
||||
- [ ] Function signatures match current implementation
|
||||
- [ ] Configuration examples reflect current schema
|
||||
- [ ] CLI commands and flags are accurate
|
||||
- [ ] Environment variables mentioned actually exist
|
||||
|
||||
#### 3.2 Link Validation
|
||||
|
||||
- [ ] Internal markdown links (`[text](./other.md)`) resolve
|
||||
- [ ] Anchor links (`[text](#section)`) point to existing headings
|
||||
- [ ] Image references exist
|
||||
- [ ] External URLs (sample only if many) - note but don't block on these
|
||||
|
||||
#### 3.3 Completeness (see references/completeness-checklist.md)
|
||||
|
||||
- [ ] Required sections present (varies by doc type)
|
||||
- [ ] All public exports documented (for API docs)
|
||||
- [ ] Examples provided for complex features
|
||||
- [ ] Error handling documented where relevant
|
||||
|
||||
### Stage 4: Docstring Coverage
|
||||
|
||||
Check TSDoc/JSDoc/docstring coverage for code files related to the documentation:
|
||||
|
||||
**TypeScript/JavaScript:**
|
||||
```bash
|
||||
# Find exports without TSDoc
|
||||
grep -rn "^export " --include="*.ts" --include="*.tsx" | head -20
|
||||
# vs exports with TSDoc (/** precedes export)
|
||||
grep -B1 "^export " --include="*.ts" --include="*.tsx" | grep -c "/\*\*"
|
||||
```
|
||||
|
||||
**Python:**
|
||||
```bash
|
||||
# Find functions/classes without docstrings
|
||||
grep -rn "^def \|^class " --include="*.py" | head -20
|
||||
```
|
||||
|
||||
**Rust:**
|
||||
```bash
|
||||
# Find pub items without doc comments
|
||||
grep -rn "^pub " --include="*.rs" | head -20
|
||||
```
|
||||
|
||||
**Go:**
|
||||
```bash
|
||||
# Find exported funcs without godoc
|
||||
grep -rn "^func [A-Z]" --include="*.go" | head -20
|
||||
```
|
||||
|
||||
Calculate coverage percentage per language detected.
|
||||
|
||||
### Stage 5: Report Generation
|
||||
|
||||
First, generate the report path using the helper script:
|
||||
|
||||
```bash
|
||||
REPORT_PATH=$(bun "$(dirname "$0")/scripts/report-path.ts" --session "${CLAUDE_SESSION_ID}" --json)
|
||||
# Extracts: timestamp, sessionShort, path, timestampISO
|
||||
```
|
||||
|
||||
Write the report to the generated path (e.g., `.pack/reports/202601251900-docs-audit-a7b3c2d1.md`):
|
||||
|
||||
```markdown
|
||||
---
|
||||
type: docs-audit
|
||||
generated: {timestampISO}
|
||||
timestamp: "{timestamp}"
|
||||
session: "{CLAUDE_SESSION_ID}"
|
||||
session_short: "{sessionShort}"
|
||||
scope: {path or "entire repo"}
|
||||
focus: {focus}
|
||||
files_analyzed: {count}
|
||||
files_total: {total}
|
||||
status: {pass|needs-work|critical}
|
||||
---
|
||||
|
||||
# Documentation Audit Report
|
||||
|
||||
**Generated**: {timestamp}
|
||||
**Session**: `{sessionShort}`
|
||||
**Scope**: {path or "entire repo"}
|
||||
**Files analyzed**: {count} / {total}
|
||||
**Focus**: {focus}
|
||||
|
||||
## Summary
|
||||
|
||||
| Dimension | Status | Score |
|
||||
|-----------|--------|-------|
|
||||
| Correctness | {PASS/NEEDS WORK} | {x}/{y} files |
|
||||
| Links | {PASS/NEEDS WORK} | {valid}/{total} |
|
||||
| Docstrings | {GOOD/ACCEPTABLE/POOR} | {x}% |
|
||||
| Freshness | {CURRENT/STALE} | {stale_count} files |
|
||||
|
||||
## Critical Issues (blocking)
|
||||
|
||||
Issues that could cause user confusion or errors:
|
||||
- {file}: {issue description}
|
||||
|
||||
## Warnings (should fix)
|
||||
|
||||
Non-blocking but should be addressed:
|
||||
- {file}: {issue description}
|
||||
|
||||
## Stale Documentation
|
||||
|
||||
Files that may need review (old docs + recent code changes):
|
||||
- {file}: Last updated {days}d ago, related code changed {code_days}d ago
|
||||
|
||||
## Docstring Coverage by Language
|
||||
|
||||
| Language | Coverage | Files Checked |
|
||||
|----------|----------|---------------|
|
||||
| TypeScript | {x}% | {n} |
|
||||
| Python | {x}% | {n} |
|
||||
|
||||
## Recommendations
|
||||
|
||||
1. {Prioritized recommendation}
|
||||
2. {Next recommendation}
|
||||
```
|
||||
|
||||
## Report Output
|
||||
|
||||
### Path Generation & Scaffolding
|
||||
|
||||
Use the `report-path.ts` helper script to generate paths and scaffold directories:
|
||||
|
||||
```bash
|
||||
# Get just the path
|
||||
bun scripts/report-path.ts --session "${CLAUDE_SESSION_ID}"
|
||||
# → .pack/reports/202601251900-docs-audit-a7b3c2d1.md
|
||||
|
||||
# Scaffold the directory structure (creates .pack/reports/)
|
||||
bun scripts/report-path.ts --scaffold --session "${CLAUDE_SESSION_ID}"
|
||||
|
||||
# Multi-file mode: scaffold with placeholder files
|
||||
bun scripts/report-path.ts --scaffold --multi --session "${CLAUDE_SESSION_ID}"
|
||||
# Creates:
|
||||
# .pack/reports/202601251900-docs-audit/
|
||||
# .pack/reports/202601251900-docs-audit/summary.md
|
||||
# .pack/reports/202601251900-docs-audit/markdown-docs.md
|
||||
# .pack/reports/202601251900-docs-audit/docstrings.md
|
||||
# .pack/reports/202601251900-docs-audit/recommendations.md
|
||||
# .pack/reports/202601251900-docs-audit/meta.json
|
||||
|
||||
# Get all components as JSON (includes scaffolded paths if --scaffold used)
|
||||
bun scripts/report-path.ts --scaffold --multi --session "${CLAUDE_SESSION_ID}" --json
|
||||
```
|
||||
|
||||
### Default Location
|
||||
|
||||
Reports are written to `.pack/reports/` with frontloaded timestamp:
|
||||
|
||||
```
|
||||
.pack/reports/202601251900-docs-audit-a7b3c2d1.md # Single file (with session)
|
||||
.pack/reports/202601251900-docs-audit.md # Single file (no session)
|
||||
.pack/reports/202601251900-docs-audit/ # Multi-file (no session in dir name)
|
||||
```
|
||||
|
||||
**Filename patterns:**
|
||||
- **Single-file**: `{timestamp}-docs-audit-{sessionShort}.md` (session for parallel disambiguation)
|
||||
- **Multi-file**: `{timestamp}-docs-audit/` (session tracked in frontmatter inside files)
|
||||
|
||||
### Multi-File Mode
|
||||
|
||||
For comprehensive audits covering different documentation types, use `--multi`:
|
||||
|
||||
```
|
||||
.pack/reports/202601251900-docs-audit/
|
||||
├── summary.md # Overall findings + links to other reports
|
||||
├── markdown-docs.md # docs/, README, etc.
|
||||
├── docstrings.md # TSDoc/JSDoc/docstring coverage
|
||||
├── recommendations.md # Prioritized actionable recommendations
|
||||
└── meta.json # Session metadata (structured, machine-readable)
|
||||
```
|
||||
|
||||
Each file includes frontmatter with full session ID for traceability.
|
||||
|
||||
### Frontmatter Schema
|
||||
|
||||
All report artifacts include YAML frontmatter for searchability:
|
||||
|
||||
```yaml
|
||||
---
|
||||
type: docs-audit # Report type (searchable)
|
||||
generated: 2026-01-25T19:00:00Z
|
||||
timestamp: "202601251900"
|
||||
session: abc123-def456... # Full session ID
|
||||
session_short: a7b3c2d1 # First 8 chars (matches filename)
|
||||
scope: docs/ # Audit scope
|
||||
focus: stale # Focus strategy used
|
||||
files_analyzed: 10
|
||||
files_total: 47
|
||||
status: needs-work # pass | needs-work | critical
|
||||
---
|
||||
```
|
||||
|
||||
**Why frontmatter:**
|
||||
- Grep/ripgrep searchable (`rg "session: abc123"`)
|
||||
- Tooling can parse and aggregate reports
|
||||
- Enables filtering by status, scope, date range
|
||||
- Parallel agent runs are distinguishable by session
|
||||
|
||||
### Session ID for Parallel Agents
|
||||
|
||||
**Single-file mode** uses session suffix for parallel disambiguation:
|
||||
|
||||
```
|
||||
.pack/reports/202601251900-docs-audit-a7b3c2d1.md # Agent 1
|
||||
.pack/reports/202601251900-docs-audit-f8e9d0c1.md # Agent 2
|
||||
.pack/reports/202601251900-docs-audit-12345678.md # Agent 3
|
||||
```
|
||||
|
||||
**Multi-file mode** relies on timestamps (coordinated audits typically don't run in parallel). Session is tracked inside each file's frontmatter for traceability.
|
||||
|
||||
### Custom Output
|
||||
|
||||
Override the default location (session ID still included in frontmatter):
|
||||
|
||||
```
|
||||
/docs-audit --output docs/audits/latest.md
|
||||
/docs-audit --multi --output .pack/reports/202601-quarterly/
|
||||
```
|
||||
|
||||
Note: Custom filenames don't auto-include session ID prefix - use frontmatter for tracking.
|
||||
|
||||
### Output Behavior
|
||||
|
||||
1. **Create directory** if it doesn't exist
|
||||
2. **Write report(s)** with session ID embedded
|
||||
3. **Print summary** to conversation (critical issues + file path)
|
||||
4. **Return path** so user can open/commit the report
|
||||
|
||||
### Git Considerations
|
||||
|
||||
The default `.pack/reports/` location:
|
||||
- Should be gitignored for ephemeral reports
|
||||
- Can be selectively committed for audit history
|
||||
- Keeps reports separate from actual documentation
|
||||
|
||||
## Context Efficiency
|
||||
|
||||
This skill uses `context: fork` to run in isolation. The token budget strategy:
|
||||
|
||||
| Stage | Token Target | Strategy |
|
||||
|-------|--------------|----------|
|
||||
| Discovery | ~200-500 | Script output is compact JSON |
|
||||
| Prioritization | ~100 | Selection logic only |
|
||||
| Deep Analysis | ~500-2000/file | Read only selected files |
|
||||
| Docstring Check | ~500 | Grep summaries, not full files |
|
||||
| Report | ~1000 | Structured output |
|
||||
|
||||
**Total target**: 15-30k tokens for a typical audit.
|
||||
|
||||
## Task Management
|
||||
|
||||
Use task tools (`TaskCreate`, `TaskUpdate`, `TaskList`) to track progress through stages. Tasks survive context compaction and allow resumption if the audit is interrupted.
|
||||
|
||||
### Initial Task Setup
|
||||
|
||||
After discovery, create tasks for the audit stages:
|
||||
|
||||
```
|
||||
TaskCreate:
|
||||
subject: "Run docs-audit discovery"
|
||||
activeForm: "Running discovery script"
|
||||
description: "Execute discover-docs.ts, parse manifest, identify {n} files in scope"
|
||||
|
||||
TaskCreate:
|
||||
subject: "Analyze {n} priority docs"
|
||||
activeForm: "Analyzing documentation"
|
||||
description: "Deep analysis of top {limit} files for correctness, links, completeness"
|
||||
|
||||
TaskCreate:
|
||||
subject: "Check docstring coverage"
|
||||
activeForm: "Checking docstring coverage"
|
||||
description: "Grep exports vs documented exports per detected language"
|
||||
|
||||
TaskCreate:
|
||||
subject: "Generate audit report"
|
||||
activeForm: "Generating report"
|
||||
description: "Compile findings into .pack/reports/{timestamp}-docs-audit-{sessionShort}.md"
|
||||
```
|
||||
|
||||
### Progress Tracking
|
||||
|
||||
Update tasks as you work:
|
||||
|
||||
1. **Before starting a stage** → `TaskUpdate` with `status: in_progress`
|
||||
2. **After completing a stage** → `TaskUpdate` with `status: completed`, update description with key findings
|
||||
3. **If issues found** → `TaskCreate` follow-up tasks for fixes
|
||||
|
||||
### State Persistence
|
||||
|
||||
Before context approaches limit, update task descriptions with checkpoint data:
|
||||
|
||||
```
|
||||
TaskUpdate:
|
||||
taskId: "2"
|
||||
description: |
|
||||
[CHECKPOINT] Analyzed 7/10 docs.
|
||||
Critical: 2 broken imports in api.md (lines 45, 89)
|
||||
Warnings: 3 stale files (config.md, setup.md, advanced.md)
|
||||
Remaining: config.md, setup.md, advanced.md
|
||||
```
|
||||
|
||||
This ensures findings survive compaction even if the stage isn't complete.
|
||||
|
||||
### Resumption
|
||||
|
||||
If context resets mid-audit:
|
||||
1. `TaskList` to see current state
|
||||
2. `TaskGet` on `in_progress` task to read checkpoint data
|
||||
3. Skip completed stages
|
||||
4. Resume from checkpoint, don't re-analyze completed files
|
||||
5. Reference persisted findings in final report
|
||||
|
||||
## Handling Edge Cases
|
||||
|
||||
**No documentation found:**
|
||||
Report "No markdown files found in {scope}. Consider adding documentation for your project."
|
||||
|
||||
**Very large repos (100+ docs):**
|
||||
- Stick to `limit` parameter strictly
|
||||
- Focus on highest-staleness files
|
||||
- Note total count in report for context
|
||||
|
||||
**Non-git repos:**
|
||||
- Skip git-based metadata (SHA, author, etc.)
|
||||
- Use file modification times as fallback
|
||||
- Note "Git metadata unavailable" in report
|
||||
|
||||
**Mixed language repos:**
|
||||
- Detect languages from file extensions
|
||||
- Report coverage per detected language
|
||||
- Skip languages with no source files
|
||||
|
||||
## Example Invocations
|
||||
|
||||
```
|
||||
/docs-audit # Single report to .pack/reports/
|
||||
/docs-audit --path docs/ # Scope to docs/ directory
|
||||
/docs-audit --focus all --limit 20 # Analyze 20 files across all statuses
|
||||
/docs-audit --multi # Multi-file mode with separate reports
|
||||
/docs-audit --output docs/audits/latest.md # Custom output location
|
||||
/docs-audit --multi --path src/ # Multi-file audit of src/ docs
|
||||
```
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
# Documentation Completeness Checklist
|
||||
|
||||
Use this checklist when auditing documentation for coverage and required content.
|
||||
|
||||
## Export Coverage
|
||||
|
||||
### TypeScript/JavaScript
|
||||
|
||||
Every public export should have documentation:
|
||||
|
||||
```typescript
|
||||
// ✅ Documented export
|
||||
/**
|
||||
* Processes user input and returns validated result.
|
||||
* @param input - Raw user input string
|
||||
* @returns Validated and sanitized input
|
||||
* @throws {ValidationError} If input fails validation
|
||||
*/
|
||||
export function processInput(input: string): ValidatedInput { ... }
|
||||
|
||||
// ❌ Undocumented export
|
||||
export function processInput(input: string): ValidatedInput { ... }
|
||||
```
|
||||
|
||||
**Check coverage:**
|
||||
```bash
|
||||
# Count exports
|
||||
EXPORTS=$(grep -c "^export " src/**/*.ts)
|
||||
# Count documented exports (/** before export)
|
||||
DOCUMENTED=$(grep -B1 "^export " src/**/*.ts | grep -c "/\*\*")
|
||||
# Coverage = DOCUMENTED / EXPORTS * 100
|
||||
```
|
||||
|
||||
### Python
|
||||
|
||||
Every public function/class should have a docstring:
|
||||
|
||||
```python
|
||||
# ✅ Documented
|
||||
def process_input(input: str) -> ValidatedInput:
|
||||
"""Process user input and return validated result.
|
||||
|
||||
Args:
|
||||
input: Raw user input string
|
||||
|
||||
Returns:
|
||||
Validated and sanitized input
|
||||
|
||||
Raises:
|
||||
ValidationError: If input fails validation
|
||||
"""
|
||||
...
|
||||
|
||||
# ❌ Undocumented
|
||||
def process_input(input: str) -> ValidatedInput:
|
||||
...
|
||||
```
|
||||
|
||||
### Rust
|
||||
|
||||
Every public item should have doc comments:
|
||||
|
||||
```rust
|
||||
// ✅ Documented
|
||||
/// Processes user input and returns validated result.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `input` - Raw user input string
|
||||
///
|
||||
/// # Returns
|
||||
/// Validated and sanitized input
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns `ValidationError` if input fails validation
|
||||
pub fn process_input(input: &str) -> Result<ValidatedInput, ValidationError> { ... }
|
||||
|
||||
// ❌ Undocumented
|
||||
pub fn process_input(input: &str) -> Result<ValidatedInput, ValidationError> { ... }
|
||||
```
|
||||
|
||||
### Go
|
||||
|
||||
Every exported function should have a godoc comment:
|
||||
|
||||
```go
|
||||
// ✅ Documented
|
||||
// ProcessInput processes user input and returns validated result.
|
||||
// It returns a ValidationError if input fails validation.
|
||||
func ProcessInput(input string) (ValidatedInput, error) { ... }
|
||||
|
||||
// ❌ Undocumented
|
||||
func ProcessInput(input string) (ValidatedInput, error) { ... }
|
||||
```
|
||||
|
||||
## Required Sections by Document Type
|
||||
|
||||
### README.md
|
||||
|
||||
- [ ] **Title** - Clear project name
|
||||
- [ ] **Description** - What it does (1-2 sentences)
|
||||
- [ ] **Installation** - How to install/setup
|
||||
- [ ] **Quick Start** - Minimal working example
|
||||
- [ ] **Usage** - Basic usage patterns
|
||||
- [ ] **License** - License type or link
|
||||
|
||||
**Nice to have:**
|
||||
- [ ] Badges (build status, version, etc.)
|
||||
- [ ] Table of contents (for long READMEs)
|
||||
- [ ] Contributing guidelines or link
|
||||
- [ ] Changelog or link
|
||||
|
||||
### API Reference
|
||||
|
||||
- [ ] **Overview** - What the API does
|
||||
- [ ] **Authentication** - How to authenticate
|
||||
- [ ] **Base URL** - API endpoint base
|
||||
- [ ] **Endpoints** - All public endpoints documented
|
||||
- [ ] **Request/Response** - Schemas for each endpoint
|
||||
- [ ] **Errors** - Common error codes and meanings
|
||||
|
||||
### Configuration Reference
|
||||
|
||||
- [ ] **Overview** - What can be configured
|
||||
- [ ] **File Location** - Where config lives
|
||||
- [ ] **Format** - JSON, YAML, TOML, etc.
|
||||
- [ ] **All Options** - Each config key documented
|
||||
- [ ] **Defaults** - Default values listed
|
||||
- [ ] **Examples** - Working config examples
|
||||
|
||||
### CLI Reference
|
||||
|
||||
- [ ] **Installation** - How to install
|
||||
- [ ] **Commands** - All commands documented
|
||||
- [ ] **Options** - Global and command-specific options
|
||||
- [ ] **Examples** - Common usage examples
|
||||
- [ ] **Exit Codes** - What different exit codes mean
|
||||
|
||||
### Contributing Guide
|
||||
|
||||
- [ ] **Setup** - Development environment setup
|
||||
- [ ] **Workflow** - How to submit changes
|
||||
- [ ] **Standards** - Code style, testing requirements
|
||||
- [ ] **Review Process** - What to expect
|
||||
|
||||
### Changelog
|
||||
|
||||
- [ ] **Version Numbers** - Semantic versioning
|
||||
- [ ] **Dates** - Release dates
|
||||
- [ ] **Categories** - Added, Changed, Fixed, Removed
|
||||
- [ ] **Migration Notes** - For breaking changes
|
||||
|
||||
## Cross-Reference Completeness
|
||||
|
||||
### Internal Links
|
||||
- [ ] All mentioned features link to their docs
|
||||
- [ ] Related concepts are cross-linked
|
||||
- [ ] No dead internal links
|
||||
|
||||
### External Links
|
||||
- [ ] Dependencies link to their docs
|
||||
- [ ] Standards link to specifications
|
||||
- [ ] Tools link to official sites
|
||||
|
||||
## Example Completeness
|
||||
|
||||
### Code Examples Should Include
|
||||
- [ ] Necessary imports
|
||||
- [ ] Variable declarations with types
|
||||
- [ ] Error handling (where appropriate)
|
||||
- [ ] Expected output (for non-obvious cases)
|
||||
|
||||
### Example Types Needed
|
||||
- [ ] **Minimal** - Simplest possible usage
|
||||
- [ ] **Typical** - Common real-world usage
|
||||
- [ ] **Advanced** - Complex scenarios (if applicable)
|
||||
- [ ] **Edge Cases** - Unusual but valid inputs
|
||||
|
||||
## Accessibility
|
||||
|
||||
- [ ] **Alt text** - Images have descriptive alt text
|
||||
- [ ] **Headings** - Proper heading hierarchy (h1 > h2 > h3)
|
||||
- [ ] **Code blocks** - Language specified for syntax highlighting
|
||||
- [ ] **Tables** - Headers on tables
|
||||
|
||||
## Severity Classification
|
||||
|
||||
| Severity | Criteria | Example |
|
||||
|----------|----------|---------|
|
||||
| **Critical** | Core functionality undocumented | No installation instructions, main API undocumented |
|
||||
| **High** | Important features undocumented | Missing error handling docs, no config reference |
|
||||
| **Medium** | Nice-to-have sections missing | No contributing guide, missing advanced examples |
|
||||
| **Low** | Polish items | Missing badges, no table of contents |
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
# Documentation Correctness Checklist
|
||||
|
||||
Use this checklist when auditing documentation for accuracy against the current codebase.
|
||||
|
||||
## Code Examples
|
||||
|
||||
### Import Statements
|
||||
- [ ] Import paths resolve to existing files
|
||||
- [ ] Named imports match actual exports
|
||||
- [ ] Package names match `package.json` / `Cargo.toml` / `pyproject.toml`
|
||||
- [ ] Relative vs absolute imports are correct for the context
|
||||
|
||||
**How to verify:**
|
||||
```bash
|
||||
# Extract import from doc, check if file exists
|
||||
grep -E "^import|^from|^require" {doc_file} | head -5
|
||||
# Then verify each path exists
|
||||
```
|
||||
|
||||
### Function Signatures
|
||||
- [ ] Function names exist in codebase
|
||||
- [ ] Parameter names match implementation
|
||||
- [ ] Parameter types are accurate
|
||||
- [ ] Return types are accurate
|
||||
- [ ] Optional parameters marked correctly
|
||||
|
||||
**How to verify:**
|
||||
```bash
|
||||
# Find function definition in code
|
||||
grep -rn "function {name}\|{name} = \|def {name}\|fn {name}" --include="*.ts" --include="*.py" --include="*.rs"
|
||||
```
|
||||
|
||||
### Configuration Examples
|
||||
- [ ] Config keys exist in schema/types
|
||||
- [ ] Default values match implementation
|
||||
- [ ] Required vs optional fields accurate
|
||||
- [ ] Value types (string, number, boolean) correct
|
||||
|
||||
**How to verify:**
|
||||
```bash
|
||||
# Find config type/interface
|
||||
grep -rn "interface.*Config\|type.*Config\|Config = " --include="*.ts"
|
||||
```
|
||||
|
||||
## CLI Documentation
|
||||
|
||||
### Commands
|
||||
- [ ] Command names are correct
|
||||
- [ ] Subcommands exist
|
||||
- [ ] Command descriptions accurate
|
||||
|
||||
### Flags/Options
|
||||
- [ ] Flag names (short and long) correct
|
||||
- [ ] Flag descriptions accurate
|
||||
- [ ] Default values documented correctly
|
||||
- [ ] Required flags marked as such
|
||||
|
||||
**How to verify:**
|
||||
```bash
|
||||
# Run help command
|
||||
{cli} --help
|
||||
{cli} {subcommand} --help
|
||||
```
|
||||
|
||||
## API Documentation
|
||||
|
||||
### Endpoints
|
||||
- [ ] HTTP methods correct (GET, POST, etc.)
|
||||
- [ ] URL paths accurate
|
||||
- [ ] Query parameters documented
|
||||
- [ ] Request body schema matches implementation
|
||||
- [ ] Response schema matches implementation
|
||||
- [ ] Status codes documented
|
||||
|
||||
**How to verify:**
|
||||
```bash
|
||||
# Find route definitions
|
||||
grep -rn "app.get\|app.post\|router\." --include="*.ts" --include="*.js"
|
||||
# Or for OpenAPI
|
||||
cat openapi.yaml | grep "paths:" -A 100
|
||||
```
|
||||
|
||||
### Authentication
|
||||
- [ ] Auth methods accurate (Bearer, API key, etc.)
|
||||
- [ ] Required headers documented
|
||||
- [ ] Error responses for auth failures documented
|
||||
|
||||
## Environment Variables
|
||||
|
||||
- [ ] Variable names match actual usage
|
||||
- [ ] Descriptions accurate
|
||||
- [ ] Required vs optional clearly marked
|
||||
- [ ] Example values are realistic (not revealing secrets)
|
||||
|
||||
**How to verify:**
|
||||
```bash
|
||||
# Find env var usage
|
||||
grep -rn "process.env\|os.environ\|env::" --include="*.ts" --include="*.py" --include="*.rs"
|
||||
# Or check .env.example
|
||||
cat .env.example
|
||||
```
|
||||
|
||||
## Error Messages
|
||||
|
||||
- [ ] Documented errors actually thrown by code
|
||||
- [ ] Error codes/types match implementation
|
||||
- [ ] Troubleshooting steps are accurate
|
||||
|
||||
**How to verify:**
|
||||
```bash
|
||||
# Find error definitions
|
||||
grep -rn "throw new\|raise \|Error::" --include="*.ts" --include="*.py" --include="*.rs"
|
||||
```
|
||||
|
||||
## Version-Specific Features
|
||||
|
||||
- [ ] Features available in documented version
|
||||
- [ ] Deprecated features marked
|
||||
- [ ] Breaking changes noted with versions
|
||||
- [ ] Minimum version requirements accurate
|
||||
|
||||
## Severity Classification
|
||||
|
||||
When an issue is found, classify it:
|
||||
|
||||
| Severity | Criteria | Example |
|
||||
|----------|----------|---------|
|
||||
| **Critical** | Will cause errors if user follows docs | Wrong import path, non-existent function |
|
||||
| **High** | Will cause confusion or unexpected behavior | Wrong default value, missing required param |
|
||||
| **Medium** | Incomplete but not wrong | Missing optional parameters, outdated example |
|
||||
| **Low** | Cosmetic or minor | Typo in description, suboptimal example |
|
||||
+366
@@ -0,0 +1,366 @@
|
||||
#!/usr/bin/env bun
|
||||
/**
|
||||
* discover-docs.ts - Markdown documentation discovery with git metadata
|
||||
*
|
||||
* Extracts a compact manifest of all markdown files with git history,
|
||||
* activity status, and related code file tracking for staleness detection.
|
||||
*
|
||||
* Usage:
|
||||
* ./discover-docs.ts # All markdown files
|
||||
* ./discover-docs.ts --path src/ # Specific directory
|
||||
* ./discover-docs.ts --limit 20 # Limit results
|
||||
* ./discover-docs.ts --sort staleness # Sort by staleness (default)
|
||||
* ./discover-docs.ts --sort alpha # Sort alphabetically
|
||||
* ./discover-docs.ts --format json # JSON output (default)
|
||||
* ./discover-docs.ts --format text # Human-readable output
|
||||
*/
|
||||
|
||||
import { $ } from "bun";
|
||||
import { parseArgs } from "util";
|
||||
import { stat } from "node:fs/promises";
|
||||
|
||||
/**
|
||||
* Documentation file with git metadata.
|
||||
*/
|
||||
interface DocFile {
|
||||
/** File path relative to repo root */
|
||||
path: string;
|
||||
/** Short SHA of last commit touching this file */
|
||||
lastCommitSha: string;
|
||||
/** ISO date of last commit */
|
||||
lastCommitDate: string;
|
||||
/** Author name of last commit */
|
||||
lastAuthor: string;
|
||||
/** Days since last modification */
|
||||
daysAgo: number;
|
||||
/** Activity classification based on age */
|
||||
activityStatus: "active" | "recent" | "idle" | "stale" | "ancient";
|
||||
/** Line count */
|
||||
lines: number;
|
||||
/** File size in bytes */
|
||||
bytes: number;
|
||||
/** Code files modified in same commits */
|
||||
relatedCodeFiles: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Discovery manifest with all markdown files.
|
||||
*/
|
||||
interface Manifest {
|
||||
/** Generation timestamp */
|
||||
generated: string;
|
||||
/** Repository root path */
|
||||
repoRoot: string;
|
||||
/** Whether this is a git repository */
|
||||
isGitRepo: boolean;
|
||||
/** Total files discovered */
|
||||
totalFiles: number;
|
||||
/** Documentation files with metadata */
|
||||
files: DocFile[];
|
||||
}
|
||||
|
||||
// Activity status thresholds (days)
|
||||
const ACTIVITY_THRESHOLDS = {
|
||||
active: 7,
|
||||
recent: 30,
|
||||
idle: 90,
|
||||
stale: 365,
|
||||
// ancient: > 365
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Gets activity status classification based on days since last modification.
|
||||
* @param daysAgo - Days since last modification
|
||||
* @returns Activity status classification
|
||||
*/
|
||||
function getActivityStatus(daysAgo: number): DocFile["activityStatus"] {
|
||||
if (daysAgo < ACTIVITY_THRESHOLDS.active) return "active";
|
||||
if (daysAgo < ACTIVITY_THRESHOLDS.recent) return "recent";
|
||||
if (daysAgo < ACTIVITY_THRESHOLDS.idle) return "idle";
|
||||
if (daysAgo < ACTIVITY_THRESHOLDS.stale) return "stale";
|
||||
return "ancient";
|
||||
}
|
||||
|
||||
function daysBetween(date1: Date, date2: Date): number {
|
||||
const msPerDay = 1000 * 60 * 60 * 24;
|
||||
return Math.floor((date2.getTime() - date1.getTime()) / msPerDay);
|
||||
}
|
||||
|
||||
async function isGitRepo(): Promise<boolean> {
|
||||
try {
|
||||
await $`git rev-parse --is-inside-work-tree`.quiet();
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function getRepoRoot(): Promise<string> {
|
||||
try {
|
||||
const result = await $`git rev-parse --show-toplevel`.quiet();
|
||||
return result.text().trim();
|
||||
} catch {
|
||||
return process.cwd();
|
||||
}
|
||||
}
|
||||
|
||||
async function findMarkdownFiles(searchPath?: string): Promise<string[]> {
|
||||
const pattern = searchPath
|
||||
? `${searchPath.replace(/\/$/, "")}/**/*.md*`
|
||||
: "**/*.md*";
|
||||
|
||||
try {
|
||||
// Use git ls-files if in a git repo (respects .gitignore)
|
||||
const result = await $`git ls-files --cached --others --exclude-standard "${pattern}"`.quiet();
|
||||
return result
|
||||
.text()
|
||||
.trim()
|
||||
.split("\n")
|
||||
.filter((f) => f.length > 0);
|
||||
} catch {
|
||||
// Fallback to find command
|
||||
const result = await $`find ${searchPath || "."} -name "*.md" -o -name "*.mdx" 2>/dev/null`.quiet();
|
||||
return result
|
||||
.text()
|
||||
.trim()
|
||||
.split("\n")
|
||||
.filter((f) => f.length > 0);
|
||||
}
|
||||
}
|
||||
|
||||
async function getGitInfo(
|
||||
filePath: string
|
||||
): Promise<Pick<DocFile, "lastCommitSha" | "lastCommitDate" | "lastAuthor" | "daysAgo">> {
|
||||
try {
|
||||
// Get last commit info: short SHA, ISO date, author name
|
||||
const result = await $`git log -1 --format="%h|%aI|%an" -- "${filePath}"`.quiet();
|
||||
const output = result.text().trim();
|
||||
|
||||
if (!output) {
|
||||
// File not yet committed
|
||||
return {
|
||||
lastCommitSha: "uncommitted",
|
||||
lastCommitDate: new Date().toISOString(),
|
||||
lastAuthor: "unknown",
|
||||
daysAgo: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const [sha, date, author] = output.split("|");
|
||||
const commitDate = new Date(date);
|
||||
const daysAgo = daysBetween(commitDate, new Date());
|
||||
|
||||
return {
|
||||
lastCommitSha: sha,
|
||||
lastCommitDate: date,
|
||||
lastAuthor: author,
|
||||
daysAgo,
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
lastCommitSha: "unknown",
|
||||
lastCommitDate: new Date().toISOString(),
|
||||
lastAuthor: "unknown",
|
||||
daysAgo: 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function getFileStats(filePath: string): Promise<Pick<DocFile, "lines" | "bytes">> {
|
||||
try {
|
||||
const stats = await stat(filePath);
|
||||
const bytes = stats.size;
|
||||
|
||||
// Count lines
|
||||
const file = Bun.file(filePath);
|
||||
const content = await file.text();
|
||||
const lines = content.split("\n").length;
|
||||
|
||||
return { lines, bytes };
|
||||
} catch {
|
||||
return { lines: 0, bytes: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
async function getRelatedCodeFiles(docPath: string): Promise<string[]> {
|
||||
try {
|
||||
// Find code files that have been modified in the same commits as this doc
|
||||
// Look at last 10 commits that touched this file
|
||||
const result = await $`git log -10 --format="%H" -- "${docPath}"`.quiet();
|
||||
const commits = result.text().trim().split("\n").filter(Boolean);
|
||||
|
||||
if (commits.length === 0) return [];
|
||||
|
||||
const codeExtensions = [
|
||||
".ts",
|
||||
".tsx",
|
||||
".js",
|
||||
".jsx",
|
||||
".py",
|
||||
".rs",
|
||||
".go",
|
||||
".java",
|
||||
".rb",
|
||||
".c",
|
||||
".cpp",
|
||||
".h",
|
||||
];
|
||||
const relatedFiles = new Set<string>();
|
||||
|
||||
for (const commit of commits.slice(0, 5)) {
|
||||
// Limit to 5 commits for speed
|
||||
try {
|
||||
const filesResult = await $`git diff-tree --no-commit-id --name-only -r ${commit}`.quiet();
|
||||
const files = filesResult.text().trim().split("\n");
|
||||
|
||||
for (const file of files) {
|
||||
if (file !== docPath && codeExtensions.some((ext) => file.endsWith(ext))) {
|
||||
relatedFiles.add(file);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Skip this commit if we can't get its files
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(relatedFiles).slice(0, 10); // Limit to 10 related files
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function analyzeFile(filePath: string, useGit: boolean): Promise<DocFile> {
|
||||
const [gitInfo, fileStats, relatedCodeFiles] = await Promise.all([
|
||||
useGit ? getGitInfo(filePath) : Promise.resolve({
|
||||
lastCommitSha: "n/a",
|
||||
lastCommitDate: new Date().toISOString(),
|
||||
lastAuthor: "n/a",
|
||||
daysAgo: 0,
|
||||
}),
|
||||
getFileStats(filePath),
|
||||
useGit ? getRelatedCodeFiles(filePath) : Promise.resolve([]),
|
||||
]);
|
||||
|
||||
return {
|
||||
path: filePath,
|
||||
...gitInfo,
|
||||
activityStatus: getActivityStatus(gitInfo.daysAgo),
|
||||
...fileStats,
|
||||
relatedCodeFiles,
|
||||
};
|
||||
}
|
||||
|
||||
function sortFiles(files: DocFile[], sortBy: "staleness" | "alpha"): DocFile[] {
|
||||
if (sortBy === "alpha") {
|
||||
return files.sort((a, b) => a.path.localeCompare(b.path));
|
||||
}
|
||||
|
||||
// Sort by staleness (most stale first)
|
||||
return files.sort((a, b) => b.daysAgo - a.daysAgo);
|
||||
}
|
||||
|
||||
function formatText(manifest: Manifest): string {
|
||||
const lines: string[] = [
|
||||
`Documentation Audit Discovery`,
|
||||
`Generated: ${manifest.generated}`,
|
||||
`Repo: ${manifest.repoRoot}`,
|
||||
`Git: ${manifest.isGitRepo ? "yes" : "no"}`,
|
||||
`Total files: ${manifest.totalFiles}`,
|
||||
``,
|
||||
`Files:`,
|
||||
``,
|
||||
];
|
||||
|
||||
const statusEmoji: Record<DocFile["activityStatus"], string> = {
|
||||
active: "🟢",
|
||||
recent: "🟡",
|
||||
idle: "🟠",
|
||||
stale: "🔴",
|
||||
ancient: "⚫",
|
||||
};
|
||||
|
||||
for (const file of manifest.files) {
|
||||
lines.push(`${statusEmoji[file.activityStatus]} ${file.path}`);
|
||||
lines.push(` SHA: ${file.lastCommitSha} | ${file.daysAgo}d ago | ${file.lastAuthor}`);
|
||||
lines.push(` Size: ${file.lines} lines, ${file.bytes} bytes`);
|
||||
if (file.relatedCodeFiles.length > 0) {
|
||||
lines.push(` Related: ${file.relatedCodeFiles.slice(0, 3).join(", ")}${file.relatedCodeFiles.length > 3 ? "..." : ""}`);
|
||||
}
|
||||
lines.push(``);
|
||||
}
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
// Main
|
||||
async function main() {
|
||||
const { values } = parseArgs({
|
||||
args: Bun.argv.slice(2),
|
||||
options: {
|
||||
path: { type: "string", short: "p" },
|
||||
limit: { type: "string", short: "l" },
|
||||
sort: { type: "string", short: "s", default: "staleness" },
|
||||
format: { type: "string", short: "f", default: "json" },
|
||||
help: { type: "boolean", short: "h" },
|
||||
},
|
||||
allowPositionals: true,
|
||||
});
|
||||
|
||||
if (values.help) {
|
||||
console.log(`
|
||||
discover-docs.ts - Markdown documentation discovery
|
||||
|
||||
Usage:
|
||||
./discover-docs.ts [options]
|
||||
|
||||
Options:
|
||||
--path, -p <dir> Search in specific directory
|
||||
--limit, -l <n> Limit number of results
|
||||
--sort, -s <by> Sort by: staleness (default), alpha
|
||||
--format, -f <fmt> Output format: json (default), text
|
||||
--help, -h Show this help
|
||||
`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const useGit = await isGitRepo();
|
||||
const repoRoot = await getRepoRoot();
|
||||
const files = await findMarkdownFiles(values.path);
|
||||
|
||||
// Analyze files in parallel (batched for large repos)
|
||||
const batchSize = 20;
|
||||
const docFiles: DocFile[] = [];
|
||||
|
||||
for (let i = 0; i < files.length; i += batchSize) {
|
||||
const batch = files.slice(i, i + batchSize);
|
||||
const results = await Promise.all(batch.map((f) => analyzeFile(f, useGit)));
|
||||
docFiles.push(...results);
|
||||
}
|
||||
|
||||
// Sort
|
||||
const sortBy = (values.sort === "alpha" ? "alpha" : "staleness") as "staleness" | "alpha";
|
||||
const sorted = sortFiles(docFiles, sortBy);
|
||||
|
||||
// Limit
|
||||
const limit = values.limit ? parseInt(values.limit, 10) : sorted.length;
|
||||
const limited = sorted.slice(0, limit);
|
||||
|
||||
const manifest: Manifest = {
|
||||
generated: new Date().toISOString(),
|
||||
repoRoot,
|
||||
isGitRepo: useGit,
|
||||
totalFiles: files.length,
|
||||
files: limited,
|
||||
};
|
||||
|
||||
if (values.format === "text") {
|
||||
console.log(formatText(manifest));
|
||||
} else {
|
||||
console.log(JSON.stringify(manifest, null, 2));
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error("Error:", err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
+311
@@ -0,0 +1,311 @@
|
||||
#!/usr/bin/env bun
|
||||
/**
|
||||
* report-path.ts - Generate report paths with timestamp and session ID
|
||||
*
|
||||
* Constructs standardized report filenames following the pattern:
|
||||
* {timestamp}-{type}-{sessionShort}.md
|
||||
* {timestamp}-{type}/ (for multi-file)
|
||||
*
|
||||
* Usage:
|
||||
* ./report-path.ts # Uses CLAUDE_SESSION_ID env var
|
||||
* ./report-path.ts --session abc123-def456 # Explicit session ID
|
||||
* ./report-path.ts --type docs-audit # Report type (default: docs-audit)
|
||||
* ./report-path.ts --multi # Output directory path instead of file
|
||||
* ./report-path.ts --base .pack/reports # Base directory (default: .pack/reports)
|
||||
* ./report-path.ts --json # Output as JSON with all components
|
||||
* ./report-path.ts --scaffold # Create directory structure
|
||||
* ./report-path.ts --scaffold --multi # Scaffold multi-file with placeholders
|
||||
*/
|
||||
|
||||
import { parseArgs } from "util";
|
||||
import { mkdir, writeFile } from "node:fs/promises";
|
||||
import { existsSync } from "node:fs";
|
||||
|
||||
/**
|
||||
* Components of a report path.
|
||||
*/
|
||||
interface ReportPathComponents {
|
||||
/** Timestamp as YYYYMMDDhhmm */
|
||||
timestamp: string;
|
||||
/** ISO 8601 timestamp for frontmatter */
|
||||
timestampISO: string;
|
||||
/** Report type (e.g., "docs-audit") */
|
||||
type: string;
|
||||
/** Full session ID (or empty) */
|
||||
sessionFull: string;
|
||||
/** First 8 chars of session ID (or empty) */
|
||||
sessionShort: string;
|
||||
/** Just the filename */
|
||||
filename: string;
|
||||
/** Full path including base */
|
||||
path: string;
|
||||
/** Whether this is a directory mode report */
|
||||
isMulti: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets current timestamp in both formatted and ISO formats.
|
||||
* @returns Object with formatted (YYYYMMDDhhmm) and ISO timestamps
|
||||
*/
|
||||
function getTimestamp(): { formatted: string; iso: string } {
|
||||
const now = new Date();
|
||||
const formatted = [
|
||||
now.getFullYear(),
|
||||
String(now.getMonth() + 1).padStart(2, "0"),
|
||||
String(now.getDate()).padStart(2, "0"),
|
||||
String(now.getHours()).padStart(2, "0"),
|
||||
String(now.getMinutes()).padStart(2, "0"),
|
||||
].join("");
|
||||
|
||||
return {
|
||||
formatted,
|
||||
iso: now.toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncates session ID to first 8 characters.
|
||||
* @param sessionId - Full session ID
|
||||
* @returns First 8 characters of session ID
|
||||
*/
|
||||
function getShortSessionId(sessionId: string): string {
|
||||
if (!sessionId) return "";
|
||||
// Handle UUIDs (take first segment) or just first 8 chars
|
||||
const firstSegment = sessionId.split("-")[0];
|
||||
return firstSegment.length >= 8 ? firstSegment.slice(0, 8) : firstSegment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a filename from components.
|
||||
* @param timestamp - Formatted timestamp
|
||||
* @param type - Report type
|
||||
* @param sessionShort - Short session ID
|
||||
* @param isMulti - Whether this is a multi-file report
|
||||
* @returns Filename or directory name
|
||||
*/
|
||||
function buildFilename(
|
||||
timestamp: string,
|
||||
type: string,
|
||||
sessionShort: string,
|
||||
isMulti: boolean
|
||||
): string {
|
||||
// Multi-file mode: no session in directory name (session tracked in frontmatter)
|
||||
// Single-file mode: session suffix for parallel disambiguation
|
||||
if (isMulti) {
|
||||
return `${timestamp}-${type}`;
|
||||
}
|
||||
const parts = [timestamp, type];
|
||||
if (sessionShort) {
|
||||
parts.push(sessionShort);
|
||||
}
|
||||
return `${parts.join("-")}.md`;
|
||||
}
|
||||
|
||||
// Multi-file report structure
|
||||
const MULTI_FILE_STRUCTURE = [
|
||||
{ name: "summary.md", description: "Overall findings and links to other reports" },
|
||||
{ name: "markdown-docs.md", description: "Analysis of docs/, README, etc." },
|
||||
{ name: "docstrings.md", description: "TSDoc/JSDoc/docstring coverage" },
|
||||
{ name: "recommendations.md", description: "Prioritized actionable recommendations" },
|
||||
{ name: "meta.json", description: "Session metadata (structured)" },
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Generates YAML frontmatter for report file.
|
||||
* @param components - Report path components
|
||||
* @param fileType - Type of file within report
|
||||
* @returns Frontmatter string
|
||||
*/
|
||||
function generateFrontmatter(components: ReportPathComponents, fileType: string): string {
|
||||
return `---
|
||||
type: ${components.type}
|
||||
file_type: ${fileType}
|
||||
generated: ${components.timestampISO}
|
||||
timestamp: "${components.timestamp}"
|
||||
session: "${components.sessionFull}"
|
||||
session_short: "${components.sessionShort}"
|
||||
status: pending
|
||||
---
|
||||
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates meta.json content for multi-file reports.
|
||||
* @param components - Report path components
|
||||
* @returns JSON string
|
||||
*/
|
||||
function generateMetaJson(components: ReportPathComponents): string {
|
||||
return JSON.stringify(
|
||||
{
|
||||
type: components.type,
|
||||
generated: components.timestampISO,
|
||||
timestamp: components.timestamp,
|
||||
session: components.sessionFull,
|
||||
session_short: components.sessionShort,
|
||||
path: components.path,
|
||||
status: "pending",
|
||||
files: MULTI_FILE_STRUCTURE.map((f) => f.name),
|
||||
},
|
||||
null,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates directory structure and placeholder files for report.
|
||||
* @param components - Report path components
|
||||
* @returns Array of created paths
|
||||
*/
|
||||
async function scaffoldReport(components: ReportPathComponents): Promise<string[]> {
|
||||
const created: string[] = [];
|
||||
|
||||
if (components.isMulti) {
|
||||
// Create directory and placeholder files
|
||||
await mkdir(components.path, { recursive: true });
|
||||
created.push(components.path);
|
||||
|
||||
for (const file of MULTI_FILE_STRUCTURE) {
|
||||
const filePath = `${components.path}/${file.name}`;
|
||||
if (!existsSync(filePath)) {
|
||||
let content: string;
|
||||
|
||||
if (file.name.endsWith(".json")) {
|
||||
// JSON files get structured metadata
|
||||
content = generateMetaJson(components);
|
||||
} else {
|
||||
// Markdown files get frontmatter + placeholder
|
||||
const fileType = file.name.replace(".md", "");
|
||||
const frontmatter = generateFrontmatter(components, fileType);
|
||||
content = `${frontmatter}# ${file.description}\n\n<!-- TODO: Populate during audit -->\n`;
|
||||
}
|
||||
|
||||
await writeFile(filePath, content);
|
||||
created.push(filePath);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Create base directory only
|
||||
const baseDir = components.path.substring(0, components.path.lastIndexOf("/"));
|
||||
await mkdir(baseDir, { recursive: true });
|
||||
created.push(baseDir);
|
||||
}
|
||||
|
||||
return created;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates report path components from options.
|
||||
* @param options - Report path options
|
||||
* @returns Report path components
|
||||
*/
|
||||
function generateReportPath(options: {
|
||||
session?: string;
|
||||
type?: string;
|
||||
base?: string;
|
||||
multi?: boolean;
|
||||
}): ReportPathComponents {
|
||||
const sessionFull = options.session || process.env.CLAUDE_SESSION_ID || "";
|
||||
const sessionShort = getShortSessionId(sessionFull);
|
||||
const type = options.type || "docs-audit";
|
||||
const base = options.base || ".pack/reports";
|
||||
const isMulti = options.multi || false;
|
||||
|
||||
const { formatted: timestamp, iso: timestampISO } = getTimestamp();
|
||||
const filename = buildFilename(timestamp, type, sessionShort, isMulti);
|
||||
const path = `${base}/${filename}`;
|
||||
|
||||
return {
|
||||
timestamp,
|
||||
timestampISO,
|
||||
type,
|
||||
sessionFull,
|
||||
sessionShort,
|
||||
filename,
|
||||
path,
|
||||
isMulti,
|
||||
};
|
||||
}
|
||||
|
||||
// Main
|
||||
async function main() {
|
||||
const { values } = parseArgs({
|
||||
args: Bun.argv.slice(2),
|
||||
options: {
|
||||
session: { type: "string", short: "s" },
|
||||
type: { type: "string", short: "t", default: "docs-audit" },
|
||||
base: { type: "string", short: "b", default: ".pack/reports" },
|
||||
multi: { type: "boolean", short: "m" },
|
||||
scaffold: { type: "boolean" },
|
||||
json: { type: "boolean", short: "j" },
|
||||
help: { type: "boolean", short: "h" },
|
||||
},
|
||||
allowPositionals: true,
|
||||
});
|
||||
|
||||
if (values.help) {
|
||||
console.log(`
|
||||
report-path.ts - Generate report paths with timestamp and session ID
|
||||
|
||||
Usage:
|
||||
./report-path.ts [options]
|
||||
|
||||
Options:
|
||||
--session, -s <id> Session ID (defaults to CLAUDE_SESSION_ID env var)
|
||||
--type, -t <type> Report type (default: docs-audit)
|
||||
--base, -b <dir> Base directory (default: .pack/reports)
|
||||
--multi, -m Directory mode (no session suffix, files inside)
|
||||
--scaffold Create directory structure (with placeholders for --multi)
|
||||
--json, -j Output as JSON with all components
|
||||
--help, -h Show this help
|
||||
|
||||
Examples:
|
||||
./report-path.ts
|
||||
# Output: .pack/reports/202601251900-docs-audit.md
|
||||
|
||||
./report-path.ts --session abc12345-def-ghi
|
||||
# Output: .pack/reports/202601251900-docs-audit-abc12345.md
|
||||
|
||||
./report-path.ts --multi
|
||||
# Output: .pack/reports/202601251900-docs-audit
|
||||
|
||||
./report-path.ts --scaffold --multi --session abc123
|
||||
# Creates: .pack/reports/202601251900-docs-audit/
|
||||
# .pack/reports/202601251900-docs-audit/summary.md
|
||||
# .pack/reports/202601251900-docs-audit/markdown-docs.md
|
||||
# ...
|
||||
|
||||
./report-path.ts --json
|
||||
# Output: {"timestamp":"202601251900","sessionShort":"abc12345",...}
|
||||
`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const result = generateReportPath({
|
||||
session: values.session,
|
||||
type: values.type,
|
||||
base: values.base,
|
||||
multi: values.multi,
|
||||
});
|
||||
|
||||
if (values.scaffold) {
|
||||
const created = await scaffoldReport(result);
|
||||
if (values.json) {
|
||||
console.log(JSON.stringify({ ...result, scaffolded: created }, null, 2));
|
||||
} else {
|
||||
console.log(`Scaffolded: ${result.path}`);
|
||||
for (const path of created) {
|
||||
console.log(` ${path}`);
|
||||
}
|
||||
}
|
||||
} else if (values.json) {
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
} else {
|
||||
console.log(result.path);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error("Error:", err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user