openclew 0.2.0 → 0.3.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/README.md +28 -122
- package/UPGRADING.md +167 -0
- package/bin/openclew.js +46 -18
- package/lib/checkout.js +26 -18
- package/lib/index-gen.js +100 -25
- package/lib/init.js +70 -37
- package/lib/inject.js +16 -7
- package/lib/mcp-server.js +313 -0
- package/lib/new-doc.js +15 -7
- package/lib/new-log.js +5 -5
- package/lib/search.js +242 -0
- package/lib/status.js +151 -0
- package/lib/templates.js +270 -94
- package/package.json +24 -3
- package/skills/openclew-checkpoint/SKILL.md +36 -0
- package/skills/openclew-init/SKILL.md +49 -0
- package/skills/openclew-search/SKILL.md +45 -0
- package/templates/FORMAT.md +299 -0
- package/templates/log.md +5 -8
- package/templates/onboarding/flow.md +59 -0
- package/templates/onboarding/scaffold_index.md +31 -0
- package/templates/{living.md → refdoc.md} +5 -9
- package/hooks/generate-index.py +0 -157
package/README.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/openclew/openclew/main/assets/logo.png" alt="openclew" width="200">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
1
5
|
# openclew
|
|
2
6
|
|
|
3
7
|
> Long Life Memory for LLMs
|
|
@@ -70,152 +74,54 @@ L1 answers "should I read this?" L2 answers "what do I need to know?" L3 is ther
|
|
|
70
74
|
|
|
71
75
|
| Type | Location | Role | Mutability |
|
|
72
76
|
|------|----------|------|------------|
|
|
73
|
-
| **
|
|
77
|
+
| **Refdoc** | `doc/_SUBJECT.md` | Reference knowledge (architecture, conventions, decisions) | Updated over time |
|
|
74
78
|
| **Log** | `doc/log/YYYY-MM-DD_subject.md` | Frozen facts (what happened, what was decided) | Never modified |
|
|
75
79
|
|
|
76
|
-
**
|
|
80
|
+
**Refdocs** are your project's brain — they evolve as the project evolves.
|
|
77
81
|
**Logs** are your project's journal — immutable records of what happened and why.
|
|
78
82
|
|
|
79
|
-
Together, they form the thread. The
|
|
83
|
+
Together, they form the thread. The refdocs tell you where you are. The logs tell you how you got here.
|
|
80
84
|
|
|
81
85
|
---
|
|
82
86
|
|
|
83
|
-
## Quick start (
|
|
87
|
+
## Quick start (2 minutes)
|
|
84
88
|
|
|
85
|
-
### 1.
|
|
89
|
+
### 1. Install
|
|
86
90
|
|
|
87
91
|
```bash
|
|
88
|
-
|
|
92
|
+
npx openclew init
|
|
89
93
|
```
|
|
90
94
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
<summary><b>templates/living.md</b> — for living knowledge</summary>
|
|
97
|
-
|
|
98
|
-
```markdown
|
|
99
|
-
<!-- L1_START -->
|
|
100
|
-
# L1 - Metadata
|
|
101
|
-
type: Reference | Architecture | Guide | Analysis
|
|
102
|
-
subject: Short title (< 60 chars)
|
|
103
|
-
created: YYYY-MM-DD
|
|
104
|
-
updated: YYYY-MM-DD
|
|
105
|
-
short_story: 1-2 sentences. What this doc covers and what it concludes.
|
|
106
|
-
status: Active | Stable | Archived
|
|
107
|
-
category: Main domain (e.g. Auth, API, Database, UI...)
|
|
108
|
-
keywords: [tag1, tag2, tag3]
|
|
109
|
-
<!-- L1_END -->
|
|
110
|
-
|
|
111
|
-
---
|
|
112
|
-
|
|
113
|
-
<!-- L2_START -->
|
|
114
|
-
# L2 - Summary
|
|
115
|
-
|
|
116
|
-
## Objective
|
|
117
|
-
<!-- Why this document exists -->
|
|
95
|
+
This:
|
|
96
|
+
- Creates `doc/` with a guide, an example doc, and an example log
|
|
97
|
+
- Detects your instruction file (CLAUDE.md, .cursorrules, AGENTS.md...)
|
|
98
|
+
- Injects a block that teaches your agent about the doc structure
|
|
99
|
+
- Installs a pre-commit hook that auto-generates `doc/_INDEX.md`
|
|
118
100
|
|
|
119
|
-
|
|
120
|
-
<!-- 3-5 essential takeaways -->
|
|
101
|
+
### 2. Start a session with your agent
|
|
121
102
|
|
|
122
|
-
|
|
123
|
-
<!-- Recommended approach or pattern -->
|
|
124
|
-
<!-- L2_END -->
|
|
125
|
-
|
|
126
|
-
---
|
|
103
|
+
Ask it:
|
|
127
104
|
|
|
128
|
-
|
|
129
|
-
# L3 - Details
|
|
105
|
+
> Read doc/_USING_OPENCLEW.md and document our architecture.
|
|
130
106
|
|
|
131
|
-
|
|
107
|
+
Your agent reads the guide, understands the L1/L2/L3 format, and creates `doc/_ARCHITECTURE.md` with your project's actual architecture.
|
|
132
108
|
|
|
133
|
-
|
|
109
|
+
### 3. There is no step 3
|
|
134
110
|
|
|
135
|
-
|
|
136
|
-
|------|--------|
|
|
137
|
-
| YYYY-MM-DD | Initial creation |
|
|
138
|
-
<!-- L3_END -->
|
|
139
|
-
```
|
|
111
|
+
Next session, your agent reads the index, finds the doc, has the context. No re-explanation needed. As your project evolves, your agent creates and updates docs during sessions — refdocs for ongoing knowledge, logs for frozen facts.
|
|
140
112
|
|
|
141
|
-
|
|
113
|
+
The index auto-regenerates on every commit. Never edit it manually.
|
|
142
114
|
|
|
143
115
|
<details>
|
|
144
|
-
<summary><b>
|
|
145
|
-
|
|
146
|
-
```markdown
|
|
147
|
-
<!-- L1_START -->
|
|
148
|
-
# L1 - Metadata
|
|
149
|
-
date: YYYY-MM-DD
|
|
150
|
-
type: Bug | Feature | Refactor | Doc | Deploy
|
|
151
|
-
subject: Short title (< 60 chars)
|
|
152
|
-
short_story: 1-2 sentences. What happened and what was the outcome.
|
|
153
|
-
status: Done | In progress | Abandoned
|
|
154
|
-
category: Main domain
|
|
155
|
-
keywords: [tag1, tag2, tag3]
|
|
156
|
-
<!-- L1_END -->
|
|
157
|
-
|
|
158
|
-
---
|
|
159
|
-
|
|
160
|
-
<!-- L2_START -->
|
|
161
|
-
# L2 - Summary
|
|
162
|
-
|
|
163
|
-
## Problem
|
|
164
|
-
<!-- What was observed -->
|
|
165
|
-
|
|
166
|
-
## Solution
|
|
167
|
-
<!-- How it was resolved -->
|
|
168
|
-
<!-- L2_END -->
|
|
169
|
-
|
|
170
|
-
---
|
|
116
|
+
<summary><b>Manual setup</b> — if you prefer not to use the CLI</summary>
|
|
171
117
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
<!-- L3_END -->
|
|
177
|
-
```
|
|
118
|
+
1. Create `doc/` and `doc/log/`
|
|
119
|
+
2. Copy templates from [`templates/`](templates/) (refdoc.md, log.md)
|
|
120
|
+
3. Add the openclew block to your instruction file (see `doc/_USING_OPENCLEW.md` after init for the exact format)
|
|
121
|
+
4. Run `openclew index` to generate `doc/_INDEX.md` (or wire it as a pre-commit hook)
|
|
178
122
|
|
|
179
123
|
</details>
|
|
180
124
|
|
|
181
|
-
### 3. Write your first doc
|
|
182
|
-
|
|
183
|
-
```bash
|
|
184
|
-
cp templates/living.md doc/_ARCHITECTURE.md
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
Edit it — describe your project's architecture. Fill in L1 (metadata), L2 (summary), skip L3 if you don't need it yet.
|
|
188
|
-
|
|
189
|
-
### 4. Point your agent to it
|
|
190
|
-
|
|
191
|
-
Add this to your `CLAUDE.md`, `.cursorrules`, or `AGENTS.md`:
|
|
192
|
-
|
|
193
|
-
```markdown
|
|
194
|
-
## Project knowledge
|
|
195
|
-
|
|
196
|
-
Documentation lives in `doc/`. Each doc has 3 levels (L1/L2/L3).
|
|
197
|
-
- Read L1 first to decide if you need more
|
|
198
|
-
- Living docs: `doc/_*.md` (living knowledge, updated)
|
|
199
|
-
- Logs: `doc/log/YYYY-MM-DD_*.md` (frozen facts, never modified)
|
|
200
|
-
- Index: `doc/_INDEX.md` (auto-generated, start here)
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
### 5. Auto-generate the index (optional)
|
|
204
|
-
|
|
205
|
-
Copy [`hooks/generate-index.py`](hooks/generate-index.py) to your project and add it as a pre-commit hook:
|
|
206
|
-
|
|
207
|
-
```bash
|
|
208
|
-
# Option A: git hook
|
|
209
|
-
cp hooks/generate-index.py .git/hooks/generate-index.py
|
|
210
|
-
echo 'python .git/hooks/generate-index.py && git add doc/_INDEX.md' >> .git/hooks/pre-commit
|
|
211
|
-
chmod +x .git/hooks/pre-commit
|
|
212
|
-
|
|
213
|
-
# Option B: pre-commit framework
|
|
214
|
-
# See hooks/README.md for .pre-commit-config.yaml setup
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
The index auto-regenerates on every commit. Never edit it manually.
|
|
218
|
-
|
|
219
125
|
---
|
|
220
126
|
|
|
221
127
|
## How it works in practice
|
|
@@ -263,7 +169,7 @@ doc/
|
|
|
263
169
|
- **Shared knowledge** — Same docs for humans and AI. One source, multiple readers.
|
|
264
170
|
- **SSOT** (Single Source of Truth) — Each piece of information lives in one place.
|
|
265
171
|
- **Logs are immutable** — Once written, never modified. Frozen facts.
|
|
266
|
-
- **
|
|
172
|
+
- **Refdocs evolve** — They evolve as the project evolves.
|
|
267
173
|
- **Index is auto-generated** — Never edit `_INDEX.md` manually.
|
|
268
174
|
|
|
269
175
|
---
|
package/UPGRADING.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Upgrading openclew
|
|
2
|
+
|
|
3
|
+
openclew evolves. When the format changes, your existing docs still work — parsers
|
|
4
|
+
are backward-compatible. But new features expect the current format.
|
|
5
|
+
|
|
6
|
+
`openclew migrate` bridges the gap.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Quick version
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install -g openclew@latest # or: npx openclew@latest
|
|
14
|
+
openclew status # shows legacy doc count
|
|
15
|
+
openclew migrate # dry-run: what would change
|
|
16
|
+
openclew migrate --write # apply
|
|
17
|
+
git diff # review
|
|
18
|
+
git add doc/ && git commit -m "chore: migrate docs to openclew format"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## When to upgrade
|
|
24
|
+
|
|
25
|
+
After updating openclew, run `openclew status`. If it reports legacy docs,
|
|
26
|
+
run `openclew migrate` to see what needs changing.
|
|
27
|
+
|
|
28
|
+
You can also check directly:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
openclew migrate
|
|
32
|
+
# → 12 to migrate, 45 already current, 0 errors (57 total)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
No output = nothing to do.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## How it works
|
|
40
|
+
|
|
41
|
+
`migrate` converts docs from older formats to the current openclew format.
|
|
42
|
+
It is **safe by default**:
|
|
43
|
+
|
|
44
|
+
- **Dry-run first** — shows what would change without touching files
|
|
45
|
+
- **`--write` to apply** — only modifies files when you explicitly ask
|
|
46
|
+
- **Git-friendly** — files are tracked, `git diff` shows exactly what changed
|
|
47
|
+
- **Idempotent** — running it twice produces the same result
|
|
48
|
+
- **Skips current docs** — only touches files that need updating
|
|
49
|
+
|
|
50
|
+
### What it converts
|
|
51
|
+
|
|
52
|
+
| Before | After |
|
|
53
|
+
|--------|-------|
|
|
54
|
+
| `R.AlphA.Doc@7.0.0` (line 1) | `openclew@0.3.0 · created: ... · type: ... · ...` |
|
|
55
|
+
| `subject: Title` (plain L1) | `**subject:** Title` (bold L1) |
|
|
56
|
+
| `summary: ...` | `**doc_brief:** ...` |
|
|
57
|
+
| `# 📋 L1 · Métadonnées` | _(removed — metadata is on line 1)_ |
|
|
58
|
+
| `# 📝 L2 · Résumé` | `# L2 - Summary` |
|
|
59
|
+
| `# 🔧 L3 · Détails` | `# L3 - Details` |
|
|
60
|
+
| YAML frontmatter (`---`) | Replaced by line 1 + L1 block |
|
|
61
|
+
| `status: Vivant` | `status: Active` |
|
|
62
|
+
| `status: Terminé` | `status: Done` |
|
|
63
|
+
|
|
64
|
+
### What it does NOT change
|
|
65
|
+
|
|
66
|
+
- **L2/L3 body content** — only headers are normalized, your content is untouched
|
|
67
|
+
- **Sub-headers** — `## Objective`, `## Key points` etc. are preserved as-is
|
|
68
|
+
- **`related_docs` paths** — kept on line 1 but not repointed if you move files
|
|
69
|
+
- **Files already in openclew format** — skipped entirely
|
|
70
|
+
- **`_INDEX.md`** — auto-generated, never touched
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Step by step
|
|
75
|
+
|
|
76
|
+
### 1. Update openclew
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npm install -g openclew@latest
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 2. Check your docs
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
openclew status
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Look for the "Legacy format" line. If it says 0, you're done.
|
|
89
|
+
|
|
90
|
+
### 3. Preview changes
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
openclew migrate
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
This lists every file that would be converted. No files are modified.
|
|
97
|
+
|
|
98
|
+
### 4. Apply
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
openclew migrate --write
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Each converted file is printed with `✓`.
|
|
105
|
+
|
|
106
|
+
### 5. Review and commit
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
git diff # inspect the changes
|
|
110
|
+
openclew index # regenerate the index
|
|
111
|
+
git add doc/ && git commit -m "chore: migrate docs to openclew format"
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 6. Verify
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
openclew status # should show 0 legacy docs
|
|
118
|
+
openclew migrate # should show "0 to migrate"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## After migrating
|
|
124
|
+
|
|
125
|
+
- **New docs** you create with `openclew add ref` / `openclew add log` already
|
|
126
|
+
use the current format. No action needed.
|
|
127
|
+
- **Docs created by AI agents** will follow the format they see in your codebase.
|
|
128
|
+
Once your existing docs are migrated, agents will generate in the new format.
|
|
129
|
+
- **Empty `doc_brief`** — some old docs may have no brief after migration.
|
|
130
|
+
Run `openclew status` to find them and fill them in.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Version-specific notes
|
|
135
|
+
|
|
136
|
+
### → 0.4.0 (format migration)
|
|
137
|
+
|
|
138
|
+
First migration release. Converts from the legacy format (YAML frontmatter,
|
|
139
|
+
plain `key: value` L1, emoji headers) to the openclew format (condensed line 1,
|
|
140
|
+
bold L1 fields, clean headers).
|
|
141
|
+
|
|
142
|
+
**Scope**: line 1 + L1 block + L2/L3 main headers.
|
|
143
|
+
|
|
144
|
+
**Not in scope**: sub-header emojis, `related_docs` repointing, recursive
|
|
145
|
+
`doc/` subdirectory scanning (refdocs must be in `doc/_*.md`, not `doc/ref/`).
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Limitations
|
|
150
|
+
|
|
151
|
+
### Flat `doc/` structure required
|
|
152
|
+
|
|
153
|
+
All openclew tools (search, index, status, migrate) scan `doc/_*.md` for refdocs
|
|
154
|
+
and `doc/log/*.md` for logs. Subdirectories like `doc/ref/` are not scanned yet.
|
|
155
|
+
|
|
156
|
+
If you plan to reorganize into subdirectories, wait for recursive scan support
|
|
157
|
+
(tracked in the openclew roadmap).
|
|
158
|
+
|
|
159
|
+
### `related_docs` are not repointed
|
|
160
|
+
|
|
161
|
+
If a doc references `related_docs: [doc/_AUTH.md]` and you rename that file,
|
|
162
|
+
the reference breaks. `migrate` preserves paths as-is — manual update required.
|
|
163
|
+
|
|
164
|
+
### Parsers are backward-compatible
|
|
165
|
+
|
|
166
|
+
Even without migrating, your docs are still readable by openclew tools.
|
|
167
|
+
Migration improves consistency and enables new features, but is not blocking.
|
package/bin/openclew.js
CHANGED
|
@@ -9,39 +9,67 @@ const USAGE = `
|
|
|
9
9
|
openclew — Long Life Memory for LLMs
|
|
10
10
|
|
|
11
11
|
Usage:
|
|
12
|
-
openclew init Set up openclew in
|
|
13
|
-
openclew
|
|
14
|
-
openclew log <title>
|
|
15
|
-
openclew
|
|
16
|
-
openclew
|
|
17
|
-
openclew help Show this help
|
|
12
|
+
openclew init Set up openclew in your project
|
|
13
|
+
openclew add ref <title> Create a refdoc (evolves with the project)
|
|
14
|
+
openclew add log <title> Create a session log (frozen facts)
|
|
15
|
+
openclew search <query> Search docs by keyword
|
|
16
|
+
openclew checkout End-of-session summary
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
Run 'openclew help --all' for advanced commands.
|
|
19
|
+
More at: https://github.com/openclew/openclew
|
|
20
|
+
`.trim();
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
# Edit doc/_ARCHITECTURE.md 2. Replace the example with your project's architecture
|
|
26
|
-
openclew new "API design" 3. Create your own living docs
|
|
27
|
-
git commit 4. Index auto-regenerates on commit
|
|
22
|
+
const USAGE_ALL = `
|
|
23
|
+
openclew — Long Life Memory for LLMs
|
|
28
24
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
Usage:
|
|
26
|
+
openclew init Set up openclew in your project
|
|
27
|
+
openclew add ref <title> Create a refdoc (evolves with the project)
|
|
28
|
+
openclew add log <title> Create a session log (frozen facts)
|
|
29
|
+
openclew search <query> Search docs by keyword
|
|
30
|
+
openclew checkout End-of-session summary
|
|
31
|
+
|
|
32
|
+
Advanced:
|
|
33
|
+
openclew status Documentation health dashboard
|
|
34
|
+
openclew index Regenerate doc/_INDEX.md
|
|
35
|
+
openclew mcp Start MCP server (stdio JSON-RPC)
|
|
36
|
+
|
|
37
|
+
Options (init):
|
|
38
|
+
--no-hook Skip pre-commit hook installation
|
|
39
|
+
--no-inject Skip instruction file injection
|
|
32
40
|
`.trim();
|
|
33
41
|
|
|
34
42
|
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
35
|
-
|
|
43
|
+
const showAll = args.includes("--all");
|
|
44
|
+
console.log(showAll ? USAGE_ALL : USAGE);
|
|
45
|
+
process.exit(0);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Handle "add ref" / "add log" subcommands
|
|
49
|
+
if (command === "add") {
|
|
50
|
+
const sub = args[1];
|
|
51
|
+
if (sub === "ref") {
|
|
52
|
+
require("../lib/new-doc");
|
|
53
|
+
} else if (sub === "log") {
|
|
54
|
+
require("../lib/new-log");
|
|
55
|
+
} else {
|
|
56
|
+
console.error(`Unknown type: ${sub || "(none)"}`);
|
|
57
|
+
console.error('Usage: openclew add ref <title> or openclew add log <title>');
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
36
60
|
process.exit(0);
|
|
37
61
|
}
|
|
38
62
|
|
|
63
|
+
// Legacy aliases
|
|
39
64
|
const commands = {
|
|
40
65
|
init: () => require("../lib/init"),
|
|
41
66
|
new: () => require("../lib/new-doc"),
|
|
42
67
|
log: () => require("../lib/new-log"),
|
|
43
68
|
checkout: () => require("../lib/checkout"),
|
|
69
|
+
search: () => require("../lib/search"),
|
|
70
|
+
status: () => require("../lib/status"),
|
|
44
71
|
index: () => require("../lib/index-gen"),
|
|
72
|
+
mcp: () => require("../lib/mcp-server"),
|
|
45
73
|
};
|
|
46
74
|
|
|
47
75
|
if (!commands[command]) {
|
package/lib/checkout.js
CHANGED
|
@@ -10,9 +10,18 @@
|
|
|
10
10
|
const fs = require("fs");
|
|
11
11
|
const path = require("path");
|
|
12
12
|
const { execSync } = require("child_process");
|
|
13
|
-
const {
|
|
13
|
+
const { slugifyLog, today } = require("./templates");
|
|
14
14
|
const { readConfig } = require("./config");
|
|
15
15
|
|
|
16
|
+
function ocVersion() {
|
|
17
|
+
try {
|
|
18
|
+
const pkg = require(path.join(__dirname, "..", "package.json"));
|
|
19
|
+
return pkg.version;
|
|
20
|
+
} catch {
|
|
21
|
+
return "0.0.0";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
16
25
|
const PROJECT_ROOT = process.cwd();
|
|
17
26
|
const DOC_DIR = path.join(PROJECT_ROOT, "doc");
|
|
18
27
|
const LOG_DIR = path.join(DOC_DIR, "log");
|
|
@@ -53,12 +62,12 @@ function collectGitActivity() {
|
|
|
53
62
|
? fs.readdirSync(LOG_DIR).filter((f) => f.startsWith(date))
|
|
54
63
|
: [];
|
|
55
64
|
|
|
56
|
-
//
|
|
57
|
-
const
|
|
65
|
+
// Refdocs
|
|
66
|
+
const refdocs = fs.existsSync(DOC_DIR)
|
|
58
67
|
? fs.readdirSync(DOC_DIR).filter((f) => f.startsWith("_") && f !== "_INDEX.md" && f.endsWith(".md"))
|
|
59
68
|
: [];
|
|
60
69
|
|
|
61
|
-
return { date, commits, uncommitted, files, existingLogs,
|
|
70
|
+
return { date, commits, uncommitted, files, existingLogs, refdocs };
|
|
62
71
|
}
|
|
63
72
|
|
|
64
73
|
function extractActions(commits) {
|
|
@@ -98,7 +107,7 @@ function typeLabel(type) {
|
|
|
98
107
|
}
|
|
99
108
|
|
|
100
109
|
function displaySummary(activity) {
|
|
101
|
-
const { date, commits, uncommitted, existingLogs,
|
|
110
|
+
const { date, commits, uncommitted, existingLogs, refdocs } = activity;
|
|
102
111
|
const actions = extractActions(commits);
|
|
103
112
|
|
|
104
113
|
console.log(`\nopenclew checkout — ${date}\n`);
|
|
@@ -152,10 +161,10 @@ function displaySummary(activity) {
|
|
|
152
161
|
}
|
|
153
162
|
console.log("");
|
|
154
163
|
|
|
155
|
-
//
|
|
156
|
-
if (
|
|
157
|
-
console.log(" 📚
|
|
158
|
-
for (const doc of
|
|
164
|
+
// Refdocs reminder
|
|
165
|
+
if (refdocs.length > 0) {
|
|
166
|
+
console.log(" 📚 Refdocs — check if any need updating:");
|
|
167
|
+
for (const doc of refdocs) {
|
|
159
168
|
console.log(` ${doc}`);
|
|
160
169
|
}
|
|
161
170
|
console.log("");
|
|
@@ -189,15 +198,14 @@ function generateSessionLog(activity, actions) {
|
|
|
189
198
|
.map((a) => `- ${typeLabel(a.type)}: ${a.desc} (${a.hash})`)
|
|
190
199
|
.join("\n");
|
|
191
200
|
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
date: ${date}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
keywords: ${keywordsStr}
|
|
201
|
+
const ver = ocVersion();
|
|
202
|
+
const logType = actions.length === 1 ? actions[0].type === "fix" ? "Bug" : "Feature" : "Feature";
|
|
203
|
+
const content = `openclew@${ver} · date: ${date} · type: ${logType} · status: Done · category: · keywords: ${keywordsStr}
|
|
204
|
+
|
|
205
|
+
<!-- L1_START -->
|
|
206
|
+
**subject:** ${sessionTitle}
|
|
207
|
+
|
|
208
|
+
**doc_brief:** ${actions.map((a) => a.desc).join(". ")}.
|
|
201
209
|
<!-- L1_END -->
|
|
202
210
|
|
|
203
211
|
---
|
package/lib/index-gen.js
CHANGED
|
@@ -1,40 +1,115 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* openclew index — regenerate doc/_INDEX.md
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Pure JS implementation. Reuses parsers from search.js (SSOT).
|
|
5
|
+
* Zero dependencies — Node 16+ standard library only.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
const { execSync } = require("child_process");
|
|
9
8
|
const fs = require("fs");
|
|
10
9
|
const path = require("path");
|
|
10
|
+
const { collectDocs } = require("./search");
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Generate _INDEX.md content from parsed docs.
|
|
14
|
+
*
|
|
15
|
+
* @param {string} docDir - Absolute path to doc/ directory
|
|
16
|
+
* @returns {string} Generated index content
|
|
17
|
+
*/
|
|
18
|
+
function generateIndex(docDir) {
|
|
19
|
+
const docs = collectDocs(docDir);
|
|
20
|
+
const refdocs = docs.filter((d) => d.kind === "refdoc");
|
|
21
|
+
const logs = docs.filter((d) => d.kind === "log");
|
|
22
|
+
|
|
23
|
+
const now = new Date();
|
|
24
|
+
const timestamp = `${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")}`;
|
|
25
|
+
|
|
26
|
+
const lines = [
|
|
27
|
+
"# Project Knowledge Index",
|
|
28
|
+
"",
|
|
29
|
+
`> Auto-generated by [openclew](https://github.com/openclew/openclew) on ${timestamp}.`,
|
|
30
|
+
"> Do not edit manually — rebuilt from L1 metadata on every commit.",
|
|
31
|
+
"",
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
// Refdocs section
|
|
35
|
+
lines.push("## Refdocs");
|
|
36
|
+
lines.push("");
|
|
37
|
+
if (refdocs.length) {
|
|
38
|
+
lines.push("| Document | Subject | Status | Category |");
|
|
39
|
+
lines.push("|----------|---------|--------|----------|");
|
|
40
|
+
for (const doc of refdocs) {
|
|
41
|
+
const name = path.basename(doc.filepath);
|
|
42
|
+
const subject = doc.meta.subject || "—";
|
|
43
|
+
const status = doc.meta.status || "—";
|
|
44
|
+
const category = doc.meta.category || "—";
|
|
45
|
+
const relPath = path.relative(path.dirname(docDir), doc.filepath);
|
|
46
|
+
lines.push(`| [${name}](${relPath}) | ${subject} | ${status} | ${category} |`);
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
lines.push("_No refdocs yet. Create one with `npx openclew add ref \"Title\"`._");
|
|
50
|
+
}
|
|
51
|
+
lines.push("");
|
|
52
|
+
|
|
53
|
+
// Logs section (last 20)
|
|
54
|
+
const displayLogs = logs.slice(0, 20);
|
|
55
|
+
lines.push("## Recent logs");
|
|
56
|
+
lines.push("");
|
|
57
|
+
if (displayLogs.length) {
|
|
58
|
+
lines.push("| Date | Subject | Status | Category |");
|
|
59
|
+
lines.push("|------|---------|--------|----------|");
|
|
60
|
+
for (const doc of displayLogs) {
|
|
61
|
+
const date = doc.meta.date || path.basename(doc.filepath).slice(0, 10);
|
|
62
|
+
const subject = doc.meta.subject || "—";
|
|
63
|
+
const status = doc.meta.status || "—";
|
|
64
|
+
const category = doc.meta.category || "—";
|
|
65
|
+
const relPath = path.relative(path.dirname(docDir), doc.filepath);
|
|
66
|
+
lines.push(`| ${date} | [${subject}](${relPath}) | ${status} | ${category} |`);
|
|
67
|
+
}
|
|
68
|
+
if (logs.length > 20) {
|
|
69
|
+
lines.push("");
|
|
70
|
+
lines.push(`_${logs.length - 20} older logs not shown._`);
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
lines.push("_No logs yet. Create one with `npx openclew add log \"Title\"`._");
|
|
74
|
+
}
|
|
75
|
+
lines.push("");
|
|
13
76
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
77
|
+
// Stats
|
|
78
|
+
lines.push("---");
|
|
79
|
+
lines.push(`**${refdocs.length}** refdocs, **${logs.length}** logs.`);
|
|
80
|
+
lines.push("");
|
|
81
|
+
|
|
82
|
+
return lines.join("\n");
|
|
17
83
|
}
|
|
18
84
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
85
|
+
/**
|
|
86
|
+
* Write _INDEX.md to disk.
|
|
87
|
+
*
|
|
88
|
+
* @param {string} docDir - Absolute path to doc/ directory
|
|
89
|
+
* @returns {{ refdocs: number, logs: number }} Counts
|
|
90
|
+
*/
|
|
91
|
+
function writeIndex(docDir) {
|
|
92
|
+
const content = generateIndex(docDir);
|
|
93
|
+
const indexPath = path.join(docDir, "_INDEX.md");
|
|
94
|
+
fs.writeFileSync(indexPath, content, "utf-8");
|
|
95
|
+
|
|
96
|
+
const docs = collectDocs(docDir);
|
|
97
|
+
const refdocs = docs.filter((d) => d.kind === "refdoc").length;
|
|
98
|
+
const logs = docs.filter((d) => d.kind === "log").length;
|
|
99
|
+
return { refdocs, logs };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// CLI runner
|
|
103
|
+
if (require.main === module || process.argv.includes("index")) {
|
|
104
|
+
const docDir = process.argv[2] || path.join(process.cwd(), "doc");
|
|
105
|
+
|
|
106
|
+
if (!fs.existsSync(docDir)) {
|
|
107
|
+
console.error("No doc/ directory found. Run 'openclew init' first.");
|
|
35
108
|
process.exit(1);
|
|
36
109
|
}
|
|
110
|
+
|
|
111
|
+
const { refdocs, logs } = writeIndex(docDir);
|
|
112
|
+
console.log(`Generated doc/_INDEX.md (${refdocs} refdocs, ${logs} logs)`);
|
|
37
113
|
}
|
|
38
114
|
|
|
39
|
-
|
|
40
|
-
process.exit(1);
|
|
115
|
+
module.exports = { generateIndex, writeIndex };
|