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.
- package/LICENSE +21 -0
- package/README.md +114 -65
- package/bin/docutrack.js +73 -67
- package/package.json +40 -38
- package/src/commands/init.js +245 -80
- package/src/commands/install-global.js +93 -0
- package/src/commands/scan.js +3 -15
- package/src/commands/setup.js +126 -0
- package/src/hooks/global-on-stop.js +18 -0
- package/src/hooks/global-post-tool-use.js +25 -0
- package/src/utils/daemon.js +48 -0
- package/src/utils/queue.js +11 -10
- package/src/utils/queue.test.js +54 -0
- package/src/viewer/index.html +1545 -1411
- package/src/viewer/server.js +383 -652
- package/src/viewer/server.test.js +50 -0
- package/templates/agents/documentalista.md +47 -28
- package/templates/claude-snippet.md +53 -39
- package/templates/hooks/on-stop.js +59 -11
- package/templates/hooks/post-tool-use.js +12 -8
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
| `
|
|
57
|
+
| `name` | function | what it does |
|
|
34
58
|
|
|
35
59
|
## Dependencies
|
|
36
|
-
|
|
37
|
-
- **
|
|
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
|
-
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
-
|
|
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
|
|
122
|
+
- One responsibility per module doc
|
|
104
123
|
- Never copy-paste code into docs — describe behavior, not implementation
|
|
105
|
-
- ADRs are permanent
|
|
106
|
-
- If
|
|
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
|
-
-
|
|
111
|
-
-
|
|
112
|
-
-
|
|
113
|
-
-
|
|
129
|
+
- Write feature code
|
|
130
|
+
- Modify source files
|
|
131
|
+
- Create tests
|
|
132
|
+
- Refactor anything
|
|
@@ -1,39 +1,53 @@
|
|
|
1
|
-
##
|
|
2
|
-
|
|
3
|
-
This project uses DocuTrack
|
|
4
|
-
|
|
5
|
-
###
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
28
|
-
|
|
72
|
+
╔══════════════════════════════════════════════════════════╗
|
|
73
|
+
║ DocuTrack — ${String(count).padEnd(3)} file(s) pending documentation ║
|
|
74
|
+
╚══════════════════════════════════════════════════════════╝
|
|
75
|
+
${caughtNote}
|
|
76
|
+
${files}${overflow}
|
|
29
77
|
|
|
30
|
-
|
|
78
|
+
Please run the documentalista subagent to document these
|
|
79
|
+
files before ending the session:
|
|
31
80
|
|
|
32
|
-
|
|
33
|
-
"Update the documentation for the files in .docutrack/queue.json"
|
|
81
|
+
Task: "Run the documentalista to document all pending files"
|
|
34
82
|
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (
|
|
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
|
|