openclew 0.2.0 → 0.2.1
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 +24 -122
- package/bin/openclew.js +2 -2
- package/hooks/generate-index.py +95 -26
- package/lib/checkout.js +26 -18
- package/lib/init.js +36 -14
- package/lib/inject.js +1 -1
- package/lib/new-doc.js +3 -3
- package/lib/new-log.js +1 -1
- package/lib/templates.js +80 -84
- package/package.json +1 -1
- package/templates/log.md +5 -8
- package/templates/{living.md → refdoc.md} +5 -9
package/README.md
CHANGED
|
@@ -70,152 +70,54 @@ L1 answers "should I read this?" L2 answers "what do I need to know?" L3 is ther
|
|
|
70
70
|
|
|
71
71
|
| Type | Location | Role | Mutability |
|
|
72
72
|
|------|----------|------|------------|
|
|
73
|
-
| **
|
|
73
|
+
| **Refdoc** | `doc/_SUBJECT.md` | Reference knowledge (architecture, conventions, decisions) | Updated over time |
|
|
74
74
|
| **Log** | `doc/log/YYYY-MM-DD_subject.md` | Frozen facts (what happened, what was decided) | Never modified |
|
|
75
75
|
|
|
76
|
-
**
|
|
76
|
+
**Refdocs** are your project's brain — they evolve as the project evolves.
|
|
77
77
|
**Logs** are your project's journal — immutable records of what happened and why.
|
|
78
78
|
|
|
79
|
-
Together, they form the thread. The
|
|
79
|
+
Together, they form the thread. The refdocs tell you where you are. The logs tell you how you got here.
|
|
80
80
|
|
|
81
81
|
---
|
|
82
82
|
|
|
83
|
-
## Quick start (
|
|
83
|
+
## Quick start (2 minutes)
|
|
84
84
|
|
|
85
|
-
### 1.
|
|
85
|
+
### 1. Install
|
|
86
86
|
|
|
87
87
|
```bash
|
|
88
|
-
|
|
88
|
+
npx openclew init
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
This:
|
|
92
|
+
- Creates `doc/` with a guide, an example doc, and an example log
|
|
93
|
+
- Detects your instruction file (CLAUDE.md, .cursorrules, AGENTS.md...)
|
|
94
|
+
- Injects a block that teaches your agent about the doc structure
|
|
95
|
+
- Installs a pre-commit hook that auto-generates `doc/_INDEX.md`
|
|
92
96
|
|
|
93
|
-
|
|
97
|
+
### 2. Start a session with your agent
|
|
94
98
|
|
|
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 -->
|
|
99
|
+
Ask it:
|
|
118
100
|
|
|
119
|
-
|
|
120
|
-
<!-- 3-5 essential takeaways -->
|
|
101
|
+
> Read doc/_USING_OPENCLEW.md and document our architecture.
|
|
121
102
|
|
|
122
|
-
|
|
123
|
-
<!-- Recommended approach or pattern -->
|
|
124
|
-
<!-- L2_END -->
|
|
103
|
+
Your agent reads the guide, understands the L1/L2/L3 format, and creates `doc/_ARCHITECTURE.md` with your project's actual architecture.
|
|
125
104
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
<!-- L3_START -->
|
|
129
|
-
# L3 - Details
|
|
130
|
-
|
|
131
|
-
<!-- Full technical content: examples, code, references... -->
|
|
105
|
+
### 3. There is no step 3
|
|
132
106
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
| Date | Change |
|
|
136
|
-
|------|--------|
|
|
137
|
-
| YYYY-MM-DD | Initial creation |
|
|
138
|
-
<!-- L3_END -->
|
|
139
|
-
```
|
|
107
|
+
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
108
|
|
|
141
|
-
|
|
109
|
+
The index auto-regenerates on every commit. Never edit it manually.
|
|
142
110
|
|
|
143
111
|
<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
|
|
112
|
+
<summary><b>Manual setup</b> — if you prefer not to use the CLI</summary>
|
|
162
113
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
<!-- How it was resolved -->
|
|
168
|
-
<!-- L2_END -->
|
|
169
|
-
|
|
170
|
-
---
|
|
171
|
-
|
|
172
|
-
<!-- L3_START -->
|
|
173
|
-
# L3 - Details
|
|
174
|
-
|
|
175
|
-
<!-- Technical details: code changes, debugging steps, references... -->
|
|
176
|
-
<!-- L3_END -->
|
|
177
|
-
```
|
|
114
|
+
1. Create `doc/` and `doc/log/`
|
|
115
|
+
2. Copy templates from [`templates/`](templates/) (refdoc.md, log.md)
|
|
116
|
+
3. Add the openclew block to your instruction file (see `doc/_USING_OPENCLEW.md` after init for the exact format)
|
|
117
|
+
4. Copy [`hooks/generate-index.py`](hooks/generate-index.py) and wire it as a pre-commit hook
|
|
178
118
|
|
|
179
119
|
</details>
|
|
180
120
|
|
|
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
121
|
---
|
|
220
122
|
|
|
221
123
|
## How it works in practice
|
|
@@ -263,7 +165,7 @@ doc/
|
|
|
263
165
|
- **Shared knowledge** — Same docs for humans and AI. One source, multiple readers.
|
|
264
166
|
- **SSOT** (Single Source of Truth) — Each piece of information lives in one place.
|
|
265
167
|
- **Logs are immutable** — Once written, never modified. Frozen facts.
|
|
266
|
-
- **
|
|
168
|
+
- **Refdocs evolve** — They evolve as the project evolves.
|
|
267
169
|
- **Index is auto-generated** — Never edit `_INDEX.md` manually.
|
|
268
170
|
|
|
269
171
|
---
|
package/bin/openclew.js
CHANGED
|
@@ -10,7 +10,7 @@ openclew — Long Life Memory for LLMs
|
|
|
10
10
|
|
|
11
11
|
Usage:
|
|
12
12
|
openclew init Set up openclew in the current project
|
|
13
|
-
openclew new <title> Create a
|
|
13
|
+
openclew new <title> Create a refdoc (evolves with the project)
|
|
14
14
|
openclew log <title> Create a session log (frozen facts)
|
|
15
15
|
openclew checkout End-of-session summary + log creation
|
|
16
16
|
openclew index Regenerate doc/_INDEX.md
|
|
@@ -23,7 +23,7 @@ Options:
|
|
|
23
23
|
Getting started:
|
|
24
24
|
npx openclew init 1. Set up doc/ + guide + examples + git hook
|
|
25
25
|
# Edit doc/_ARCHITECTURE.md 2. Replace the example with your project's architecture
|
|
26
|
-
openclew new "API design" 3. Create your own
|
|
26
|
+
openclew new "API design" 3. Create your own refdocs
|
|
27
27
|
git commit 4. Index auto-regenerates on commit
|
|
28
28
|
|
|
29
29
|
Docs have 3 levels: L1 (metadata) → L2 (summary) → L3 (details).
|
package/hooks/generate-index.py
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"""
|
|
3
3
|
openclew index generator.
|
|
4
4
|
|
|
5
|
-
Scans doc/_*.md (
|
|
6
|
-
parses L1
|
|
5
|
+
Scans doc/_*.md (refdocs) and doc/log/*.md (logs),
|
|
6
|
+
parses metadata line + L1 blocks, and generates doc/_INDEX.md.
|
|
7
7
|
|
|
8
8
|
Usage:
|
|
9
9
|
python generate-index.py # from project root
|
|
@@ -34,23 +34,67 @@ def find_doc_dir():
|
|
|
34
34
|
return doc_dir
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
def
|
|
38
|
-
"""Extract
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
def parse_metadata_line(content):
|
|
38
|
+
"""Extract metadata from the first line (before L1_START).
|
|
39
|
+
|
|
40
|
+
Format: openclew@VERSION · key: value · key: value · ...
|
|
41
|
+
"""
|
|
42
|
+
meta = {}
|
|
43
|
+
first_line = content.split("\n", 1)[0].strip()
|
|
44
|
+
if not first_line.startswith("openclew@"):
|
|
45
|
+
return meta
|
|
46
|
+
|
|
47
|
+
parts = first_line.split(" · ")
|
|
48
|
+
for part in parts:
|
|
49
|
+
part = part.strip()
|
|
50
|
+
if part.startswith("openclew@"):
|
|
51
|
+
meta["version"] = part.split("@", 1)[1]
|
|
52
|
+
continue
|
|
53
|
+
if ":" in part:
|
|
54
|
+
key, _, value = part.partition(":")
|
|
55
|
+
meta[key.strip().lower()] = value.strip()
|
|
56
|
+
|
|
57
|
+
return meta
|
|
43
58
|
|
|
59
|
+
|
|
60
|
+
def parse_l1(content):
|
|
61
|
+
"""Extract L1 fields (subject, doc_brief) from L1_START/L1_END block."""
|
|
62
|
+
meta = {}
|
|
44
63
|
match = re.search(
|
|
45
64
|
r"<!--\s*L1_START\s*-->(.+?)<!--\s*L1_END\s*-->",
|
|
46
65
|
content,
|
|
47
66
|
re.DOTALL,
|
|
48
67
|
)
|
|
49
68
|
if not match:
|
|
50
|
-
return
|
|
69
|
+
return meta
|
|
51
70
|
|
|
52
71
|
block = match.group(1)
|
|
72
|
+
|
|
73
|
+
# Extract **subject:** value
|
|
74
|
+
subject_match = re.search(r"\*\*subject:\*\*\s*(.+)", block)
|
|
75
|
+
if subject_match:
|
|
76
|
+
meta["subject"] = subject_match.group(1).strip()
|
|
77
|
+
|
|
78
|
+
# Extract **doc_brief:** value
|
|
79
|
+
brief_match = re.search(r"\*\*doc_brief:\*\*\s*(.+)", block)
|
|
80
|
+
if brief_match:
|
|
81
|
+
meta["doc_brief"] = brief_match.group(1).strip()
|
|
82
|
+
|
|
83
|
+
return meta
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def parse_l1_legacy(content):
|
|
87
|
+
"""Fallback parser for old format (key: value lines inside L1 block)."""
|
|
53
88
|
meta = {}
|
|
89
|
+
match = re.search(
|
|
90
|
+
r"<!--\s*L1_START\s*-->(.+?)<!--\s*L1_END\s*-->",
|
|
91
|
+
content,
|
|
92
|
+
re.DOTALL,
|
|
93
|
+
)
|
|
94
|
+
if not match:
|
|
95
|
+
return meta
|
|
96
|
+
|
|
97
|
+
block = match.group(1)
|
|
54
98
|
for line in block.splitlines():
|
|
55
99
|
line = line.strip()
|
|
56
100
|
if line.startswith("#") or not line:
|
|
@@ -62,31 +106,56 @@ def parse_l1(filepath):
|
|
|
62
106
|
return meta
|
|
63
107
|
|
|
64
108
|
|
|
109
|
+
def parse_file(filepath):
|
|
110
|
+
"""Parse a file and return combined metadata + L1 fields."""
|
|
111
|
+
try:
|
|
112
|
+
content = filepath.read_text(encoding="utf-8")
|
|
113
|
+
except (OSError, UnicodeDecodeError):
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
# Try new format first
|
|
117
|
+
meta_line = parse_metadata_line(content)
|
|
118
|
+
l1 = parse_l1(content)
|
|
119
|
+
|
|
120
|
+
if l1.get("subject"):
|
|
121
|
+
# New format
|
|
122
|
+
meta_line.update(l1)
|
|
123
|
+
return meta_line
|
|
124
|
+
|
|
125
|
+
# Fallback to legacy format
|
|
126
|
+
legacy = parse_l1_legacy(content)
|
|
127
|
+
if legacy:
|
|
128
|
+
meta_line.update(legacy)
|
|
129
|
+
return meta_line
|
|
130
|
+
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
|
|
65
134
|
def collect_docs(doc_dir):
|
|
66
|
-
"""Collect
|
|
67
|
-
|
|
135
|
+
"""Collect refdocs and logs with their metadata."""
|
|
136
|
+
refdocs = []
|
|
68
137
|
logs = []
|
|
69
138
|
|
|
70
|
-
#
|
|
139
|
+
# Refdocs: doc/_*.md
|
|
71
140
|
for f in sorted(doc_dir.glob("_*.md")):
|
|
72
141
|
if f.name == "_INDEX.md":
|
|
73
142
|
continue
|
|
74
|
-
meta =
|
|
143
|
+
meta = parse_file(f)
|
|
75
144
|
if meta:
|
|
76
|
-
|
|
145
|
+
refdocs.append((f, meta))
|
|
77
146
|
|
|
78
147
|
# Log docs: doc/log/*.md
|
|
79
148
|
log_dir = doc_dir / "log"
|
|
80
149
|
if log_dir.is_dir():
|
|
81
150
|
for f in sorted(log_dir.glob("*.md"), reverse=True):
|
|
82
|
-
meta =
|
|
151
|
+
meta = parse_file(f)
|
|
83
152
|
if meta:
|
|
84
153
|
logs.append((f, meta))
|
|
85
154
|
|
|
86
|
-
return
|
|
155
|
+
return refdocs, logs
|
|
87
156
|
|
|
88
157
|
|
|
89
|
-
def generate_index(doc_dir,
|
|
158
|
+
def generate_index(doc_dir, refdocs, logs):
|
|
90
159
|
"""Generate _INDEX.md content."""
|
|
91
160
|
now = datetime.now().strftime("%Y-%m-%d %H:%M")
|
|
92
161
|
lines = [
|
|
@@ -97,13 +166,13 @@ def generate_index(doc_dir, living_docs, logs):
|
|
|
97
166
|
f"",
|
|
98
167
|
]
|
|
99
168
|
|
|
100
|
-
#
|
|
101
|
-
lines.append("##
|
|
169
|
+
# Refdocs section
|
|
170
|
+
lines.append("## Refdocs")
|
|
102
171
|
lines.append("")
|
|
103
|
-
if
|
|
172
|
+
if refdocs:
|
|
104
173
|
lines.append("| Document | Subject | Status | Category |")
|
|
105
174
|
lines.append("|----------|---------|--------|----------|")
|
|
106
|
-
for f, meta in
|
|
175
|
+
for f, meta in refdocs:
|
|
107
176
|
name = f.name
|
|
108
177
|
subject = meta.get("subject", "—")
|
|
109
178
|
status = meta.get("status", "—")
|
|
@@ -111,7 +180,7 @@ def generate_index(doc_dir, living_docs, logs):
|
|
|
111
180
|
rel_path = f.relative_to(doc_dir.parent)
|
|
112
181
|
lines.append(f"| [{name}]({rel_path}) | {subject} | {status} | {category} |")
|
|
113
182
|
else:
|
|
114
|
-
lines.append("_No
|
|
183
|
+
lines.append("_No refdocs yet. Create one with `templates/refdoc.md`._")
|
|
115
184
|
lines.append("")
|
|
116
185
|
|
|
117
186
|
# Logs section (last 20)
|
|
@@ -137,7 +206,7 @@ def generate_index(doc_dir, living_docs, logs):
|
|
|
137
206
|
|
|
138
207
|
# Stats
|
|
139
208
|
lines.append("---")
|
|
140
|
-
lines.append(f"**{len(
|
|
209
|
+
lines.append(f"**{len(refdocs)}** refdocs, **{len(logs)}** logs.")
|
|
141
210
|
lines.append("")
|
|
142
211
|
|
|
143
212
|
return "\n".join(lines)
|
|
@@ -145,12 +214,12 @@ def generate_index(doc_dir, living_docs, logs):
|
|
|
145
214
|
|
|
146
215
|
def main():
|
|
147
216
|
doc_dir = find_doc_dir()
|
|
148
|
-
|
|
149
|
-
index_content = generate_index(doc_dir,
|
|
217
|
+
refdocs, logs = collect_docs(doc_dir)
|
|
218
|
+
index_content = generate_index(doc_dir, refdocs, logs)
|
|
150
219
|
|
|
151
220
|
index_path = doc_dir / "_INDEX.md"
|
|
152
221
|
index_path.write_text(index_content, encoding="utf-8")
|
|
153
|
-
print(f"Generated {index_path} ({len(
|
|
222
|
+
print(f"Generated {index_path} ({len(refdocs)} refdocs, {len(logs)} logs)")
|
|
154
223
|
|
|
155
224
|
|
|
156
225
|
if __name__ == "__main__":
|
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/init.js
CHANGED
|
@@ -15,7 +15,7 @@ const readline = require("readline");
|
|
|
15
15
|
const { detectInstructionFiles, findAgentsMdCaseInsensitive } = require("./detect");
|
|
16
16
|
const { inject, isAlreadyInjected } = require("./inject");
|
|
17
17
|
const { writeConfig } = require("./config");
|
|
18
|
-
const { guideContent,
|
|
18
|
+
const { guideContent, exampleRefdocContent, exampleLogContent, today } = require("./templates");
|
|
19
19
|
|
|
20
20
|
const PROJECT_ROOT = process.cwd();
|
|
21
21
|
const DOC_DIR = path.join(PROJECT_ROOT, "doc");
|
|
@@ -150,6 +150,24 @@ fi`;
|
|
|
150
150
|
return true;
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
function updateGitignore() {
|
|
154
|
+
const gitignorePath = path.join(PROJECT_ROOT, ".gitignore");
|
|
155
|
+
const entry = "doc/log/";
|
|
156
|
+
|
|
157
|
+
if (fs.existsSync(gitignorePath)) {
|
|
158
|
+
const content = fs.readFileSync(gitignorePath, "utf-8");
|
|
159
|
+
if (content.includes(entry)) {
|
|
160
|
+
console.log(" .gitignore already ignores doc/log/");
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
fs.appendFileSync(gitignorePath, `\n${entry}\n`, "utf-8");
|
|
164
|
+
console.log(" Added doc/log/ to .gitignore");
|
|
165
|
+
} else {
|
|
166
|
+
fs.writeFileSync(gitignorePath, `${entry}\n`, "utf-8");
|
|
167
|
+
console.log(" Created .gitignore with doc/log/");
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
153
171
|
function copyGenerateIndex() {
|
|
154
172
|
const src = path.join(__dirname, "..", "hooks", "generate-index.py");
|
|
155
173
|
const dst = path.join(DOC_DIR, "generate-index.py");
|
|
@@ -179,11 +197,11 @@ function createDocs() {
|
|
|
179
197
|
console.log(" doc/_USING_OPENCLEW.md already exists");
|
|
180
198
|
}
|
|
181
199
|
|
|
182
|
-
// Example
|
|
200
|
+
// Example refdoc
|
|
183
201
|
const examplePath = path.join(DOC_DIR, "_ARCHITECTURE.md");
|
|
184
202
|
if (!fs.existsSync(examplePath)) {
|
|
185
|
-
fs.writeFileSync(examplePath,
|
|
186
|
-
console.log(" Created doc/_ARCHITECTURE.md (example
|
|
203
|
+
fs.writeFileSync(examplePath, exampleRefdocContent(), "utf-8");
|
|
204
|
+
console.log(" Created doc/_ARCHITECTURE.md (example refdoc)");
|
|
187
205
|
} else {
|
|
188
206
|
console.log(" doc/_ARCHITECTURE.md already exists");
|
|
189
207
|
}
|
|
@@ -218,12 +236,16 @@ async function main() {
|
|
|
218
236
|
console.log("1. Project structure");
|
|
219
237
|
createDirs();
|
|
220
238
|
|
|
221
|
-
// Step 2:
|
|
222
|
-
console.log("\n2.
|
|
239
|
+
// Step 2: Gitignore
|
|
240
|
+
console.log("\n2. Gitignore");
|
|
241
|
+
updateGitignore();
|
|
242
|
+
|
|
243
|
+
// Step 3: Copy index generator
|
|
244
|
+
console.log("\n3. Index generator");
|
|
223
245
|
copyGenerateIndex();
|
|
224
246
|
|
|
225
|
-
// Step
|
|
226
|
-
console.log("\
|
|
247
|
+
// Step 4: Entry point
|
|
248
|
+
console.log("\n4. Entry point");
|
|
227
249
|
const entryPoint = await resolveEntryPoint();
|
|
228
250
|
|
|
229
251
|
if (entryPoint) {
|
|
@@ -241,20 +263,20 @@ async function main() {
|
|
|
241
263
|
writeConfig({ entryPoint: null }, PROJECT_ROOT);
|
|
242
264
|
}
|
|
243
265
|
|
|
244
|
-
// Step
|
|
245
|
-
console.log("\
|
|
266
|
+
// Step 5: Pre-commit hook
|
|
267
|
+
console.log("\n5. Pre-commit hook");
|
|
246
268
|
if (noHook) {
|
|
247
269
|
console.log(" Skipping (--no-hook)");
|
|
248
270
|
} else {
|
|
249
271
|
installPreCommitHook();
|
|
250
272
|
}
|
|
251
273
|
|
|
252
|
-
// Step
|
|
253
|
-
console.log("\
|
|
274
|
+
// Step 6: Docs
|
|
275
|
+
console.log("\n6. Docs");
|
|
254
276
|
createDocs();
|
|
255
277
|
|
|
256
|
-
// Step
|
|
257
|
-
console.log("\
|
|
278
|
+
// Step 7: Generate index
|
|
279
|
+
console.log("\n7. Index");
|
|
258
280
|
runIndexGenerator();
|
|
259
281
|
|
|
260
282
|
// Done
|
package/lib/inject.js
CHANGED
|
@@ -12,7 +12,7 @@ This file is the **entry point** for project documentation.
|
|
|
12
12
|
**Doc-first rule:** before any task, read \`doc/_INDEX.md\` to find docs related to the task. Read them before exploring code.
|
|
13
13
|
|
|
14
14
|
Two types of docs in \`doc/\`:
|
|
15
|
-
- **
|
|
15
|
+
- **Refdocs** (\`doc/_*.md\`) — evolve with the project (architecture, conventions, decisions)
|
|
16
16
|
- **Logs** (\`doc/log/YYYY-MM-DD_*.md\`) — frozen facts from a session, never modified after
|
|
17
17
|
|
|
18
18
|
Each doc has 3 levels: **L1** (metadata — read first to decide relevance) → **L2** (summary) → **L3** (full details, only when needed).
|
package/lib/new-doc.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* openclew new <title> — create a new
|
|
2
|
+
* openclew new <title> — create a new refdoc.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const fs = require("fs");
|
|
6
6
|
const path = require("path");
|
|
7
|
-
const {
|
|
7
|
+
const { refdocContent, slugify } = require("./templates");
|
|
8
8
|
const { readConfig } = require("./config");
|
|
9
9
|
|
|
10
10
|
const args = process.argv.slice(2);
|
|
@@ -37,7 +37,7 @@ if (fs.existsSync(filepath)) {
|
|
|
37
37
|
process.exit(1);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
fs.writeFileSync(filepath,
|
|
40
|
+
fs.writeFileSync(filepath, refdocContent(title), "utf-8");
|
|
41
41
|
console.log(`Created doc/${filename}`);
|
|
42
42
|
console.log("");
|
|
43
43
|
console.log("Next: open the file and fill in:");
|
package/lib/new-log.js
CHANGED
|
@@ -42,7 +42,7 @@ fs.writeFileSync(filepath, logContent(title), "utf-8");
|
|
|
42
42
|
console.log(`Created doc/log/${filename}`);
|
|
43
43
|
console.log("");
|
|
44
44
|
console.log("Next: open the file and fill in:");
|
|
45
|
-
console.log(" L1 —
|
|
45
|
+
console.log(" L1 — subject + doc_brief (what happened in 1-2 sentences)");
|
|
46
46
|
console.log(" L2 — problem + solution (the facts, frozen after this session)");
|
|
47
47
|
console.log("");
|
|
48
48
|
console.log("Logs are immutable — once written, never modified.");
|
package/lib/templates.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Template content for
|
|
2
|
+
* Template content for refdocs and logs.
|
|
3
3
|
* Embedded here so the CLI works standalone without needing to locate template files.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
const path = require("path");
|
|
7
|
+
|
|
6
8
|
function today() {
|
|
7
9
|
return new Date().toISOString().slice(0, 10);
|
|
8
10
|
}
|
|
@@ -21,18 +23,24 @@ function slugifyLog(title) {
|
|
|
21
23
|
.replace(/^-|-$/g, "");
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
function
|
|
26
|
+
function ocVersion() {
|
|
27
|
+
try {
|
|
28
|
+
const pkg = require(path.join(__dirname, "..", "package.json"));
|
|
29
|
+
return pkg.version;
|
|
30
|
+
} catch {
|
|
31
|
+
return "0.0.0";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function refdocContent(title) {
|
|
25
36
|
const date = today();
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
status: Active
|
|
34
|
-
category:
|
|
35
|
-
keywords: []
|
|
37
|
+
const ver = ocVersion();
|
|
38
|
+
return `openclew@${ver} · created: ${date} · updated: ${date} · type: Reference · status: Active · category: · keywords: []
|
|
39
|
+
|
|
40
|
+
<!-- L1_START -->
|
|
41
|
+
**subject:** ${title}
|
|
42
|
+
|
|
43
|
+
**doc_brief:**
|
|
36
44
|
<!-- L1_END -->
|
|
37
45
|
|
|
38
46
|
---
|
|
@@ -67,15 +75,13 @@ keywords: []
|
|
|
67
75
|
|
|
68
76
|
function logContent(title) {
|
|
69
77
|
const date = today();
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
subject
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
category:
|
|
78
|
-
keywords: []
|
|
78
|
+
const ver = ocVersion();
|
|
79
|
+
return `openclew@${ver} · date: ${date} · type: Feature · status: In progress · category: · keywords: []
|
|
80
|
+
|
|
81
|
+
<!-- L1_START -->
|
|
82
|
+
**subject:** ${title}
|
|
83
|
+
|
|
84
|
+
**doc_brief:**
|
|
79
85
|
<!-- L1_END -->
|
|
80
86
|
|
|
81
87
|
---
|
|
@@ -109,16 +115,13 @@ keywords: []
|
|
|
109
115
|
*/
|
|
110
116
|
function guideContent() {
|
|
111
117
|
const date = today();
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
status: Active
|
|
120
|
-
category: Documentation
|
|
121
|
-
keywords: [openclew, L1, L2, L3, index, living-doc, log]
|
|
118
|
+
const ver = ocVersion();
|
|
119
|
+
return `openclew@${ver} · created: ${date} · updated: ${date} · type: Guide · status: Active · category: Documentation · keywords: [openclew, L1, L2, L3, index, refdoc, log]
|
|
120
|
+
|
|
121
|
+
<!-- L1_START -->
|
|
122
|
+
**subject:** How openclew works
|
|
123
|
+
|
|
124
|
+
**doc_brief:** How openclew structures project knowledge in 3 levels (L1/L2/L3) so AI agents and humans navigate efficiently.
|
|
122
125
|
<!-- L1_END -->
|
|
123
126
|
|
|
124
127
|
---
|
|
@@ -136,7 +139,7 @@ Before starting any task, read \`doc/_INDEX.md\` to find docs related to the tas
|
|
|
136
139
|
|
|
137
140
|
## Two types of docs
|
|
138
141
|
|
|
139
|
-
**
|
|
142
|
+
**Refdocs** (\`doc/_*.md\`): knowledge that evolves with the project.
|
|
140
143
|
Architecture decisions, conventions, known pitfalls — anything that stays relevant over time.
|
|
141
144
|
Naming: \`doc/_UPPER_SNAKE_CASE.md\` (e.g. \`doc/_AUTH_DESIGN.md\`)
|
|
142
145
|
|
|
@@ -144,13 +147,17 @@ Naming: \`doc/_UPPER_SNAKE_CASE.md\` (e.g. \`doc/_AUTH_DESIGN.md\`)
|
|
|
144
147
|
What happened, what was decided, what was tried. Never modified after the session.
|
|
145
148
|
Naming: \`doc/log/YYYY-MM-DD_lowercase-slug.md\` (e.g. \`doc/log/2026-01-15_setup-auth.md\`)
|
|
146
149
|
|
|
147
|
-
##
|
|
150
|
+
## Document structure
|
|
151
|
+
|
|
152
|
+
Every doc has a metadata line + 3 levels. Read only what you need:
|
|
148
153
|
|
|
149
|
-
|
|
154
|
+
**Line 1 — Metadata**: version, date, type, status, category, keywords. For indexing and triage.
|
|
150
155
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
156
|
+
**L1 — Subject + Brief** (~40 tokens): what the doc is about and what it concludes. Read this first to decide if the doc is relevant.
|
|
157
|
+
|
|
158
|
+
**L2 — Summary**: the essential context — objective, key points, decisions.
|
|
159
|
+
|
|
160
|
+
**L3 — Details**: full technical content. Only read when deep-diving.
|
|
154
161
|
|
|
155
162
|
## Index
|
|
156
163
|
|
|
@@ -163,21 +170,17 @@ Never edit it manually. To force a rebuild: \`openclew index\`
|
|
|
163
170
|
<!-- L3_START -->
|
|
164
171
|
# L3 - Details
|
|
165
172
|
|
|
166
|
-
## Creating a
|
|
173
|
+
## Creating a refdoc
|
|
167
174
|
|
|
168
175
|
Create \`doc/_TITLE.md\` (uppercase snake_case) with this structure:
|
|
169
176
|
|
|
170
177
|
\`\`\`
|
|
178
|
+
openclew@${ver} · created: YYYY-MM-DD · updated: YYYY-MM-DD · type: Reference · status: Active · category: · keywords: []
|
|
179
|
+
|
|
171
180
|
<!-- L1_START -->
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
created: YYYY-MM-DD
|
|
176
|
-
updated: YYYY-MM-DD
|
|
177
|
-
short_story:
|
|
178
|
-
status: Active
|
|
179
|
-
category:
|
|
180
|
-
keywords: []
|
|
181
|
+
**subject:** Title
|
|
182
|
+
|
|
183
|
+
**doc_brief:**
|
|
181
184
|
<!-- L1_END -->
|
|
182
185
|
|
|
183
186
|
---
|
|
@@ -205,15 +208,12 @@ keywords: []
|
|
|
205
208
|
Create \`doc/log/YYYY-MM-DD_slug.md\` (lowercase, hyphens) with this structure:
|
|
206
209
|
|
|
207
210
|
\`\`\`
|
|
211
|
+
openclew@${ver} · date: YYYY-MM-DD · type: Feature · status: In progress · category: · keywords: []
|
|
212
|
+
|
|
208
213
|
<!-- L1_START -->
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
subject: Title
|
|
213
|
-
short_story:
|
|
214
|
-
status: In progress
|
|
215
|
-
category:
|
|
216
|
-
keywords: []
|
|
214
|
+
**subject:** Title
|
|
215
|
+
|
|
216
|
+
**doc_brief:**
|
|
217
217
|
<!-- L1_END -->
|
|
218
218
|
|
|
219
219
|
---
|
|
@@ -243,9 +243,10 @@ Logs are immutable — once the session ends, the log is never modified.
|
|
|
243
243
|
|
|
244
244
|
1. At session start: read the entry point file
|
|
245
245
|
2. Before any task: read \`doc/_INDEX.md\`, scan L1 metadata, identify relevant docs
|
|
246
|
-
3. Read
|
|
247
|
-
4.
|
|
248
|
-
5.
|
|
246
|
+
3. Read L1 (subject + brief) of relevant docs to confirm relevance
|
|
247
|
+
4. Read L2 for context
|
|
248
|
+
5. Only read L3 when you need implementation details
|
|
249
|
+
6. After significant work: create or update refdocs and logs directly
|
|
249
250
|
|
|
250
251
|
The index (\`doc/_INDEX.md\`) auto-regenerates on every git commit. To force a rebuild: \`openclew index\`
|
|
251
252
|
|
|
@@ -261,20 +262,17 @@ The index (\`doc/_INDEX.md\`) auto-regenerates on every git commit. To force a r
|
|
|
261
262
|
}
|
|
262
263
|
|
|
263
264
|
/**
|
|
264
|
-
* Example
|
|
265
|
+
* Example refdoc — shows what a filled-in doc looks like.
|
|
265
266
|
*/
|
|
266
|
-
function
|
|
267
|
+
function exampleRefdocContent() {
|
|
267
268
|
const date = today();
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
status: Active
|
|
276
|
-
category: Architecture
|
|
277
|
-
keywords: [architecture, overview, components]
|
|
269
|
+
const ver = ocVersion();
|
|
270
|
+
return `openclew@${ver} · created: ${date} · updated: ${date} · type: Reference · status: Active · category: Architecture · keywords: [architecture, overview, components]
|
|
271
|
+
|
|
272
|
+
<!-- L1_START -->
|
|
273
|
+
**subject:** Architecture overview
|
|
274
|
+
|
|
275
|
+
**doc_brief:** High-level architecture of the project — components, data flow, key decisions.
|
|
278
276
|
<!-- L1_END -->
|
|
279
277
|
|
|
280
278
|
---
|
|
@@ -298,7 +296,7 @@ Document the high-level architecture so new contributors and AI agents understan
|
|
|
298
296
|
|
|
299
297
|
<!-- Replace this with your actual architecture details -->
|
|
300
298
|
|
|
301
|
-
This is an example
|
|
299
|
+
This is an example refdoc created by \`openclew init\`.
|
|
302
300
|
Edit it to document your project's architecture, or delete it and create your own.
|
|
303
301
|
|
|
304
302
|
---
|
|
@@ -317,15 +315,13 @@ Edit it to document your project's architecture, or delete it and create your ow
|
|
|
317
315
|
*/
|
|
318
316
|
function exampleLogContent() {
|
|
319
317
|
const date = today();
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
subject
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
category: Tooling
|
|
328
|
-
keywords: [openclew, setup, documentation]
|
|
318
|
+
const ver = ocVersion();
|
|
319
|
+
return `openclew@${ver} · date: ${date} · type: Feature · status: Done · category: Tooling · keywords: [openclew, setup, documentation]
|
|
320
|
+
|
|
321
|
+
<!-- L1_START -->
|
|
322
|
+
**subject:** Set up openclew
|
|
323
|
+
|
|
324
|
+
**doc_brief:** Initialized openclew for structured project knowledge. Created doc/ structure, git hook, guide, and example docs.
|
|
329
325
|
<!-- L1_END -->
|
|
330
326
|
|
|
331
327
|
---
|
|
@@ -340,7 +336,7 @@ Set up structured documentation so AI agents and new contributors can navigate p
|
|
|
340
336
|
Project knowledge was scattered — README, inline comments, tribal knowledge. Each new AI session started from zero.
|
|
341
337
|
|
|
342
338
|
## Solution
|
|
343
|
-
Installed openclew. Every doc now has
|
|
339
|
+
Installed openclew. Every doc now has a metadata line (for triage) + L1 (subject and brief), L2 (summary for context), L3 (details when needed).
|
|
344
340
|
The index auto-regenerates on each commit via a git hook.
|
|
345
341
|
<!-- L2_END -->
|
|
346
342
|
|
|
@@ -351,16 +347,16 @@ The index auto-regenerates on each commit via a git hook.
|
|
|
351
347
|
|
|
352
348
|
This log was created by \`openclew init\`.
|
|
353
349
|
It shows what a filled-in log looks like. Logs are immutable — once the session ends, the log is frozen.
|
|
354
|
-
For evolving knowledge, use
|
|
350
|
+
For evolving knowledge, use refdocs (\`doc/_*.md\`).
|
|
355
351
|
<!-- L3_END -->
|
|
356
352
|
`;
|
|
357
353
|
}
|
|
358
354
|
|
|
359
355
|
module.exports = {
|
|
360
|
-
|
|
356
|
+
refdocContent,
|
|
361
357
|
logContent,
|
|
362
358
|
guideContent,
|
|
363
|
-
|
|
359
|
+
exampleRefdocContent,
|
|
364
360
|
exampleLogContent,
|
|
365
361
|
slugify,
|
|
366
362
|
slugifyLog,
|
package/package.json
CHANGED
package/templates/log.md
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
|
+
openclew@VERSION · date: YYYY-MM-DD · type: Bug | Feature | Refactor | Doc | Deploy · status: Done | In progress | Abandoned · category: Main domain · keywords: [tag1, tag2, tag3]
|
|
2
|
+
|
|
1
3
|
<!-- L1_START -->
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
subject: Short title (< 60 chars)
|
|
6
|
-
short_story: 1-2 sentences. What happened and what was the outcome. Include conclusions, not just process.
|
|
7
|
-
status: Done | In progress | Abandoned
|
|
8
|
-
category: Main domain (e.g. Auth, API, Database, UI...)
|
|
9
|
-
keywords: [tag1, tag2, tag3]
|
|
4
|
+
**subject:** Short title (< 60 chars)
|
|
5
|
+
|
|
6
|
+
**doc_brief:** 1-2 sentences. What happened and what was the outcome. Include conclusions, not just process.
|
|
10
7
|
<!-- L1_END -->
|
|
11
8
|
|
|
12
9
|
---
|
|
@@ -1,13 +1,9 @@
|
|
|
1
|
+
openclew@VERSION · created: YYYY-MM-DD · updated: YYYY-MM-DD · type: Reference | Architecture | Guide | Analysis · status: Active | Stable | Archived · category: Main domain · keywords: [tag1, tag2, tag3]
|
|
2
|
+
|
|
1
3
|
<!-- L1_START -->
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
created: YYYY-MM-DD
|
|
6
|
-
updated: YYYY-MM-DD
|
|
7
|
-
short_story: 1-2 sentences. What this doc covers and what it concludes. Must be enough to decide if you need to read further.
|
|
8
|
-
status: Active | Stable | Archived
|
|
9
|
-
category: Main domain (e.g. Auth, API, Database, UI...)
|
|
10
|
-
keywords: [tag1, tag2, tag3]
|
|
4
|
+
**subject:** Short title (< 60 chars)
|
|
5
|
+
|
|
6
|
+
**doc_brief:** 1-2 sentences. What this doc covers and what it concludes. Must be enough to decide if you need to read further.
|
|
11
7
|
<!-- L1_END -->
|
|
12
8
|
|
|
13
9
|
---
|