docutrack 0.1.0 → 0.1.6

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.
@@ -0,0 +1,50 @@
1
+ 'use strict'
2
+
3
+ const { describe, it } = require('node:test')
4
+ const assert = require('node:assert/strict')
5
+
6
+ const DocuTrackServer = require('./server')
7
+
8
+ const s = new DocuTrackServer('/fake/root', 4242)
9
+
10
+ describe('moduleDocName', () => {
11
+ it('drops leading src/ for shallow files', () => {
12
+ assert.equal(s.moduleDocName('src/utils.ts'), 'utils')
13
+ })
14
+
15
+ it('uses last 2 segments for deeper paths', () => {
16
+ assert.equal(s.moduleDocName('app/dashboard/SearchBar.tsx'), 'dashboard-SearchBar')
17
+ })
18
+
19
+ it('handles lib/ prefix', () => {
20
+ assert.equal(s.moduleDocName('lib/rules-engine.ts'), 'rules-engine')
21
+ })
22
+
23
+ it('handles deeply nested paths', () => {
24
+ assert.equal(s.moduleDocName('src/components/ui/Button.tsx'), 'ui-Button')
25
+ })
26
+ })
27
+
28
+ describe('routeDocName', () => {
29
+ it('converts simple route', () => {
30
+ assert.equal(s.routeDocName('app/api/documentos/route.ts'), 'documentos')
31
+ })
32
+
33
+ it('strips dynamic segments brackets', () => {
34
+ assert.equal(s.routeDocName('app/api/documentos/[id]/evaluar/route.ts'), 'documentos-id-evaluar')
35
+ })
36
+
37
+ it('handles nested api routes', () => {
38
+ assert.equal(s.routeDocName('app/api/metricas/route.ts'), 'metricas')
39
+ })
40
+ })
41
+
42
+ describe('fileToApiPath', () => {
43
+ it('converts dynamic segments to OpenAPI params', () => {
44
+ assert.equal(s.fileToApiPath('app/api/documentos/[id]/route.ts'), '/api/documentos/{id}')
45
+ })
46
+
47
+ it('handles simple routes', () => {
48
+ assert.equal(s.fileToApiPath('app/api/status/route.ts'), '/api/status')
49
+ })
50
+ })
@@ -9,50 +9,75 @@ You are the **documentalista** — a specialized documentation agent. Your only
9
9
 
10
10
  When invoked, always follow these steps in order:
11
11
 
12
+ **0. Read project preferences**
13
+ ```bash
14
+ cat docutrack.config.json
15
+ ```
16
+ This tells you how to write docs for this specific project:
17
+ - `lang`: write **all** documentation in this language (e.g. `"es"` = Spanish, `"en"` = English)
18
+ - `projectDescription`: the project's purpose — use it for context when writing module docs
19
+ - `audience`: `"team"` = technical/concise, `"onboarding"` = explain more context for new devs, `"mixed"` = balanced
20
+ - `docDepth`: `"concise"` = summary + public API only, `"standard"` = + design decisions and gotchas, `"detailed"` = + examples and full context
21
+
22
+ Adapt every doc you write to these preferences. If `lang` is `"es"`, write in Spanish. If `docDepth` is `"concise"`, keep docs short.
23
+
12
24
  **1. Read the queue**
13
25
  ```bash
14
26
  cat .docutrack/queue.json
15
27
  ```
16
- This shows which files were modified and need documentation.
28
+ This shows which files need documentation.
17
29
 
18
30
  **2. Understand what changed**
19
31
  Read each file in the queue. Understand its purpose, its public API, and how it fits into the system.
20
32
 
21
33
  **3. Update or create module docs**
22
- For each file in the queue, update or create `docs/modules/<module-name>.md` using this exact structure:
34
+ For each file in the queue, update or create `docs/modules/<module-name>.md`.
23
35
 
36
+ If `docDepth` is `"concise"`:
24
37
  ```markdown
25
38
  # <Module Name>
26
39
 
27
- **Responsibility**: [one sentence — what this module does]
40
+ **Responsibility**: [one sentence]
28
41
 
29
42
  ## Public API
43
+ | Export | Type | Description |
44
+ |--------|------|-------------|
45
+ | `name` | function | what it does |
46
+ ```
47
+
48
+ If `docDepth` is `"standard"` (default):
49
+ ```markdown
50
+ # <Module Name>
30
51
 
52
+ **Responsibility**: [one sentence]
53
+
54
+ ## Public API
31
55
  | Export | Type | Description |
32
56
  |--------|------|-------------|
33
- | `functionName` | function | what it does |
57
+ | `name` | function | what it does |
34
58
 
35
59
  ## Dependencies
36
-
37
- - **Imports from**: list of modules/packages this depends on
38
- - **Used by**: list of modules that import from this one
60
+ - **Imports from**: modules/packages this depends on
61
+ - **Used by**: modules that import this one
39
62
 
40
63
  ## Data Shapes
41
-
42
64
  ```typescript
43
65
  // Key types, interfaces, or schemas
44
66
  ```
45
67
 
46
68
  ## Notes
47
-
48
- [Non-obvious constraints, gotchas, or design decisions]
69
+ [Non-obvious constraints, gotchas, design decisions]
49
70
  ```
50
71
 
72
+ If `docDepth` is `"detailed"`, add a `## Examples` section with a usage snippet for the key exports.
73
+
74
+ If `audience` is `"onboarding"`, add a brief **Context** paragraph at the top explaining where this module fits in the system — assume the reader is new to the codebase.
75
+
51
76
  **4. Update ARCHITECTURE.md if needed**
52
- - If a new module was added: add a row to the Module Map table
53
- - If a new external service was added: add to the Integrations table
54
- - If a new env variable was added: add to the Environment Variables table
55
- - If the tech stack changed: update the Tech Stack table
77
+ - New module added add a row to the Module Map table
78
+ - New external service add to the Integrations table
79
+ - New env variable add to the Environment Variables table
80
+ - Tech stack changed update the Tech Stack table
56
81
 
57
82
  **5. Create an ADR for significant decisions**
58
83
  Create `docs/decisions/ADR-NNN-<slug>.md` when you detect:
@@ -69,18 +94,12 @@ ADR format:
69
94
  **Date**: YYYY-MM-DD
70
95
 
71
96
  ## Context
72
- Why was this decision needed?
73
-
74
97
  ## Decision
75
- What was decided?
76
-
77
98
  ## Consequences
78
- Trade-offs, implications, and things to watch for.
79
99
  ```
80
100
 
81
101
  **6. Update API docs if needed**
82
102
  If the modified file defines routes/endpoints, update or create `docs/api/<service>.md`:
83
-
84
103
  ```markdown
85
104
  # <Service> API
86
105
 
@@ -92,22 +111,22 @@ If the modified file defines routes/endpoints, update or create `docs/api/<servi
92
111
  ```
93
112
 
94
113
  **7. Clear the queue**
95
- After all documentation is updated, run:
96
114
  ```bash
97
115
  npx docutrack clear
98
116
  ```
99
117
 
100
118
  ## Quality rules
101
119
 
120
+ - Write in the language specified in `docutrack.config.json` — every word
102
121
  - Write for the next engineer, not for yourself
103
- - One responsibility per module doc — if you can't describe it in one sentence, the module does too much
122
+ - One responsibility per module doc
104
123
  - Never copy-paste code into docs — describe behavior, not implementation
105
- - ADRs are permanent records — mark old ones as `Deprecated`, never delete them
106
- - If you're unsure about something, write what you can observe and add a `> Note: verify this with the team` callout
124
+ - ADRs are permanent — mark old ones as `Deprecated`, never delete
125
+ - If unsure, write what you observe and add `> Note: verify with the team`
107
126
 
108
127
  ## What you don't do
109
128
 
110
- - You don't write feature code
111
- - You don't modify source files
112
- - You don't create tests
113
- - You don't refactor anything
129
+ - Write feature code
130
+ - Modify source files
131
+ - Create tests
132
+ - Refactor anything
@@ -1,39 +1,53 @@
1
- ## Documentation Protocol (DocuTrack)
2
-
3
- This project uses DocuTrack to maintain living documentation. After every code change, follow this protocol:
4
-
5
- ### When you create or modify a module
6
-
7
- Update or create `docs/modules/<module-name>.md` with:
8
- - **Responsibility**: what this module does (one sentence)
9
- - **Public API**: exported functions/classes with brief descriptions
10
- - **Dependencies**: what it imports from and what depends on it
11
- - **Data shapes**: key types, schemas, or interfaces
12
- - **Notes**: constraints, gotchas, non-obvious design decisions
13
-
14
- ### When you add or change an API endpoint
15
-
16
- Update `docs/api/` one file per service/router. Document:
17
- - Method + path
18
- - Request body / query params
19
- - Response shape and status codes
20
- - Auth requirements
21
-
22
- ### When you make a significant architectural decision
23
-
24
- Create `docs/decisions/ADR-<NNN>-<slug>.md`. Use this format:
25
- ```
26
- # ADR-NNN: Title
27
- ## Status: Proposed | Accepted | Deprecated
28
- ## Context: why this decision was needed
29
- ## Decision: what was decided
30
- ## Consequences: trade-offs and implications
31
- ```
32
-
33
- ### When you modify `ARCHITECTURE.md`
34
-
35
- Keep the Module Map table current. If you add a new service, integration, or env variable, add it to the relevant section.
36
-
37
- ### The Stop hook will warn you
38
-
39
- If you end the session with modified files and no doc update, the Stop hook prints a list of what needs attention. Clear the queue with `npx docutrack clear` only after documentation is updated.
1
+ ## DocuTrack documentation auto-pilot
2
+
3
+ This project uses DocuTrack for living documentation.
4
+
5
+ ### At the start of every session
6
+
7
+ Run this check silently (no output to user needed):
8
+
9
+ ```bash
10
+ node -e "const fs=require('fs');if(!fs.existsSync('.docutrack')){console.log('NEEDS_SETUP')}else{try{const q=JSON.parse(fs.readFileSync('.docutrack/queue.json','utf8'));console.log('PENDING:'+(q.pending||[]).length)}catch{console.log('PENDING:0')}}"
11
+ ```
12
+
13
+ Based on the output:
14
+ - **`NEEDS_SETUP`** ask the user these questions (in the conversation), then run `docutrack init` with the answers as flags:
15
+ 1. *"Documentation language? (e.g. Spanish, English, Portuguese…)"*
16
+ 2. *"Describe this project in one sentence"*
17
+ 3. *"Who reads the docs? → `team` (technical) / `onboarding` (new devs) / `mixed`"*
18
+ 4. *"Documentation depth? → `concise` / `standard` / `detailed`"*
19
+
20
+ Then run:
21
+ ```bash
22
+ docutrack init --lang=<es|en|…> --description="<desc>" --audience=<team|onboarding|mixed> --depth=<concise|standard|detailed>
23
+ ```
24
+ - **`PENDING:N`** (N > 0) → run the **documentalista** subagent to process the queue
25
+ - **`PENDING:0`** → documentation is up to date ✓
26
+
27
+ ### The documentalista subagent
28
+
29
+ The documentalista is at `.claude/agents/documentalista.md`. It reads `.docutrack/queue.json`, writes a doc for every queued file, and clears the queue. To invoke it, say: *"Run the documentalista"* — Claude will delegate to it automatically.
30
+
31
+ ### During the session — documentation protocol
32
+
33
+ When you create or modify a module, update or create `docs/modules/<name>.md` with:
34
+ - **Responsibility**: what this module does (one sentence)
35
+ - **Public API**: exported functions/classes with brief descriptions
36
+ - **Dependencies**: what it imports and what depends on it
37
+ - **Data shapes**: key types, schemas, interfaces
38
+ - **Notes**: constraints, gotchas, non-obvious design decisions
39
+
40
+ When you add or change an API endpoint, update `docs/api/` — one file per service/router.
41
+
42
+ When you make a significant architectural decision, create `docs/decisions/ADR-<NNN>-<slug>.md`:
43
+ ```
44
+ # ADR-NNN: Title
45
+ ## Status: Proposed | Accepted | Deprecated
46
+ ## Context
47
+ ## Decision
48
+ ## Consequences
49
+ ```
50
+
51
+ ### The Stop hook
52
+
53
+ The Stop hook fires when the session ends. If files are still pending, it will list them and ask you to run the documentalista before closing. This keeps docs in sync with the code automatically.
@@ -1,7 +1,9 @@
1
1
  'use strict'
2
2
 
3
3
  // Stop hook — fires when the Claude Code session ends
4
- // Warns if there are files modified without documentation updates
4
+ // 1. Catch-all: queues any source files that the PostToolUse hook missed
5
+ // (happens when docutrack init ran mid-session, before hooks were active)
6
+ // 2. Reports pending files and asks Claude to run the documentalista
5
7
 
6
8
  const fs = require('fs')
7
9
  const path = require('path')
@@ -17,23 +19,69 @@ try {
17
19
  process.exit(0)
18
20
  }
19
21
 
20
- if (!queue.pending || queue.pending.length === 0) process.exit(0)
22
+ queue.pending = queue.pending || []
21
23
 
24
+ // ── Catch-all scan ─────────────────────────────────────────────
25
+ // Add any source files not yet in the queue (catches files created
26
+ // before the PostToolUse hook was active in this session)
27
+ const SOURCE_DIRS = ['src', 'lib', 'app', 'pkg', 'internal', 'api', 'routes', 'controllers', 'handlers', 'packages']
28
+ const SOURCE_EXTS = new Set(['.js', '.ts', '.mjs', '.cjs', '.jsx', '.tsx', '.py', '.go', '.rs', '.rb', '.java', '.cs', '.cpp', '.c', '.swift', '.kt'])
29
+ const IGNORE_DIRS = new Set(['node_modules', '.next', '.git', 'dist', 'build', '__pycache__', '.docutrack', 'docs', '.worktrees', 'coverage', '.turbo', '.claude'])
30
+ const IGNORE_RE = [/\.test\.[jt]sx?$/, /\.spec\.[jt]sx?$/, /\.d\.ts$/, /\.min\.js$/]
31
+
32
+ const queued = new Set(queue.pending.map(e => e.file))
33
+ const now = new Date().toISOString()
34
+ let caught = 0
35
+
36
+ const walk = (dir, root) => {
37
+ if (!fs.existsSync(dir)) return
38
+ let entries
39
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }) } catch { return }
40
+ for (const e of entries) {
41
+ if (e.isDirectory()) {
42
+ if (!IGNORE_DIRS.has(e.name) && !e.name.startsWith('.')) walk(path.join(dir, e.name), root)
43
+ } else if (e.isFile() && SOURCE_EXTS.has(path.extname(e.name).toLowerCase())) {
44
+ if (IGNORE_RE.some(re => re.test(e.name))) continue
45
+ const rel = path.relative(root, path.join(dir, e.name)).replace(/\\/g, '/')
46
+ if (!queued.has(rel)) {
47
+ queue.pending.push({ file: rel, addedAt: now })
48
+ queued.add(rel)
49
+ caught++
50
+ }
51
+ }
52
+ }
53
+ }
54
+
55
+ const root = process.cwd()
56
+ for (const dir of SOURCE_DIRS) walk(path.join(root, dir), root)
57
+
58
+ if (caught > 0) {
59
+ try { fs.writeFileSync(QUEUE_PATH, JSON.stringify(queue, null, 2)) } catch { /* ok */ }
60
+ }
61
+
62
+ // ── Report ─────────────────────────────────────────────────────
22
63
  const count = queue.pending.length
23
- const files = queue.pending.map(e => ` - ${e.file}`).join('\n')
64
+ if (count === 0) process.exit(0)
65
+
66
+ const shown = queue.pending.slice(0, 8)
67
+ const files = shown.map(e => ` - ${e.file}`).join('\n')
68
+ const overflow = count > 8 ? `\n … and ${count - 8} more` : ''
69
+ const caughtNote = caught > 0 ? `\n (${caught} file(s) added by catch-all scan)\n` : ''
24
70
 
25
71
  console.log(`
26
- ╔════════════════════════════════════════════════════╗
27
- ║ DocuTrack: ${String(count).padEnd(3)} file(s) need documentation
28
- ╚════════════════════════════════════════════════════╝
72
+ ╔══════════════════════════════════════════════════════════╗
73
+ ║ DocuTrack ${String(count).padEnd(3)} file(s) pending documentation
74
+ ╚══════════════════════════════════════════════════════════╝
75
+ ${caughtNote}
76
+ ${files}${overflow}
29
77
 
30
- ${files}
78
+ Please run the documentalista subagent to document these
79
+ files before ending the session:
31
80
 
32
- To update the docs, tell the agent:
33
- "Update the documentation for the files in .docutrack/queue.json"
81
+ Task: "Run the documentalista to document all pending files"
34
82
 
35
- To clear the queue manually:
36
- npx docutrack clear
83
+ The documentalista reads .docutrack/queue.json, writes a doc
84
+ for each pending file, and clears the queue automatically.
37
85
  `)
38
86
 
39
87
  process.exit(0)
@@ -6,6 +6,9 @@
6
6
  const fs = require('fs')
7
7
  const path = require('path')
8
8
 
9
+ // Only source code files belong in the documentation queue
10
+ const SOURCE_EXTS = new Set(['.js', '.ts', '.mjs', '.cjs', '.jsx', '.tsx', '.py', '.go', '.rs', '.rb', '.java', '.cs', '.cpp', '.c', '.swift', '.kt'])
11
+
9
12
  let raw = ''
10
13
  process.stdin.setEncoding('utf8')
11
14
  process.stdin.on('data', chunk => { raw += chunk })
@@ -23,7 +26,6 @@ process.stdin.on('end', () => {
23
26
  function extractFilePath(event) {
24
27
  const { tool_name, tool_input } = event
25
28
  if (!tool_input) return null
26
-
27
29
  if (tool_name === 'Write' || tool_name === 'Edit' || tool_name === 'MultiEdit') {
28
30
  return tool_input.file_path || null
29
31
  }
@@ -32,18 +34,20 @@ function extractFilePath(event) {
32
34
 
33
35
  function addToQueue(filePath) {
34
36
  const normalized = filePath.replace(/\\/g, '/')
35
- const ignored = [
36
- 'docs/', '.docutrack/', '.claude/', 'node_modules/', '.git/',
37
- ]
38
- if (ignored.some(p => normalized.startsWith(p))) return
37
+
38
+ // Filter by extension — only source code files
39
+ const ext = path.extname(normalized).toLowerCase()
40
+ if (!SOURCE_EXTS.has(ext)) return
41
+
42
+ // Filter by path — works for both relative and absolute paths
43
+ const IGNORED = ['docs/', '.docutrack/', '.claude/', 'node_modules/', '.git/', 'dist/', 'build/', '.next/', 'coverage/']
44
+ if (IGNORED.some(seg => normalized.includes('/' + seg) || normalized.startsWith(seg))) return
39
45
 
40
46
  const queuePath = path.join('.docutrack', 'queue.json')
41
47
  if (!fs.existsSync(path.dirname(queuePath))) return // not initialized
42
48
 
43
49
  let queue = { pending: [], lastClear: null }
44
- try {
45
- queue = JSON.parse(fs.readFileSync(queuePath, 'utf8'))
46
- } catch { /* start fresh */ }
50
+ try { queue = JSON.parse(fs.readFileSync(queuePath, 'utf8')) } catch { /* start fresh */ }
47
51
 
48
52
  if (queue.pending.some(e => e.file === normalized)) return
49
53