openclew 0.1.0 → 0.2.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 CHANGED
@@ -70,13 +70,13 @@ 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
- | **Permanent** | `doc/_SUBJECT.md` | Living knowledge (architecture, conventions, decisions) | Updated over time |
73
+ | **Living** | `doc/_SUBJECT.md` | Living 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
- **Permanents** are your project's brain — they evolve as the project evolves.
76
+ **Living docs** 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 permanent docs tell you where you are. The logs tell you how you got here.
79
+ Together, they form the thread. The living docs tell you where you are. The logs tell you how you got here.
80
80
 
81
81
  ---
82
82
 
@@ -93,7 +93,7 @@ mkdir -p doc/log
93
93
  Download from [`templates/`](templates/) or create manually:
94
94
 
95
95
  <details>
96
- <summary><b>templates/permanent.md</b> — for living knowledge</summary>
96
+ <summary><b>templates/living.md</b> — for living knowledge</summary>
97
97
 
98
98
  ```markdown
99
99
  <!-- L1_START -->
@@ -181,7 +181,7 @@ keywords: [tag1, tag2, tag3]
181
181
  ### 3. Write your first doc
182
182
 
183
183
  ```bash
184
- cp templates/permanent.md doc/_ARCHITECTURE.md
184
+ cp templates/living.md doc/_ARCHITECTURE.md
185
185
  ```
186
186
 
187
187
  Edit it — describe your project's architecture. Fill in L1 (metadata), L2 (summary), skip L3 if you don't need it yet.
@@ -195,7 +195,7 @@ Add this to your `CLAUDE.md`, `.cursorrules`, or `AGENTS.md`:
195
195
 
196
196
  Documentation lives in `doc/`. Each doc has 3 levels (L1/L2/L3).
197
197
  - Read L1 first to decide if you need more
198
- - Permanent docs: `doc/_*.md` (living knowledge, updated)
198
+ - Living docs: `doc/_*.md` (living knowledge, updated)
199
199
  - Logs: `doc/log/YYYY-MM-DD_*.md` (frozen facts, never modified)
200
200
  - Index: `doc/_INDEX.md` (auto-generated, start here)
201
201
  ```
@@ -263,7 +263,7 @@ doc/
263
263
  - **Shared knowledge** — Same docs for humans and AI. One source, multiple readers.
264
264
  - **SSOT** (Single Source of Truth) — Each piece of information lives in one place.
265
265
  - **Logs are immutable** — Once written, never modified. Frozen facts.
266
- - **Permanents are living** — They evolve as the project evolves.
266
+ - **Living docs evolve** — They evolve as the project evolves.
267
267
  - **Index is auto-generated** — Never edit `_INDEX.md` manually.
268
268
 
269
269
  ---
package/bin/openclew.js CHANGED
@@ -10,14 +10,25 @@ 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 new permanent doc
14
- openclew log <title> Create a new session log
13
+ openclew new <title> Create a living doc (evolves with the project)
14
+ openclew log <title> Create a session log (frozen facts)
15
+ openclew checkout End-of-session summary + log creation
15
16
  openclew index Regenerate doc/_INDEX.md
16
17
  openclew help Show this help
17
18
 
18
19
  Options:
19
20
  --no-hook Skip pre-commit hook installation (init)
20
21
  --no-inject Skip instruction file injection (init)
22
+
23
+ Getting started:
24
+ npx openclew init 1. Set up doc/ + guide + examples + git hook
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
28
+
29
+ Docs have 3 levels: L1 (metadata) → L2 (summary) → L3 (details).
30
+ Agents read L1 to decide what's relevant, then L2 for context.
31
+ More at: https://github.com/openclew/openclew
21
32
  `.trim();
22
33
 
23
34
  if (!command || command === "help" || command === "--help" || command === "-h") {
@@ -29,6 +40,7 @@ const commands = {
29
40
  init: () => require("../lib/init"),
30
41
  new: () => require("../lib/new-doc"),
31
42
  log: () => require("../lib/new-log"),
43
+ checkout: () => require("../lib/checkout"),
32
44
  index: () => require("../lib/index-gen"),
33
45
  };
34
46
 
@@ -2,7 +2,7 @@
2
2
  """
3
3
  openclew index generator.
4
4
 
5
- Scans doc/_*.md (permanents) and doc/log/*.md (logs),
5
+ Scans doc/_*.md (living docs) and doc/log/*.md (logs),
6
6
  parses L1 metadata blocks, and generates doc/_INDEX.md.
7
7
 
8
8
  Usage:
@@ -63,17 +63,17 @@ def parse_l1(filepath):
63
63
 
64
64
 
65
65
  def collect_docs(doc_dir):
66
- """Collect permanents and logs with their L1 metadata."""
67
- permanents = []
66
+ """Collect living docs and logs with their L1 metadata."""
67
+ living_docs = []
68
68
  logs = []
69
69
 
70
- # Permanent docs: doc/_*.md
70
+ # Living docs: doc/_*.md
71
71
  for f in sorted(doc_dir.glob("_*.md")):
72
72
  if f.name == "_INDEX.md":
73
73
  continue
74
74
  meta = parse_l1(f)
75
75
  if meta:
76
- permanents.append((f, meta))
76
+ living_docs.append((f, meta))
77
77
 
78
78
  # Log docs: doc/log/*.md
79
79
  log_dir = doc_dir / "log"
@@ -83,10 +83,10 @@ def collect_docs(doc_dir):
83
83
  if meta:
84
84
  logs.append((f, meta))
85
85
 
86
- return permanents, logs
86
+ return living_docs, logs
87
87
 
88
88
 
89
- def generate_index(doc_dir, permanents, logs):
89
+ def generate_index(doc_dir, living_docs, logs):
90
90
  """Generate _INDEX.md content."""
91
91
  now = datetime.now().strftime("%Y-%m-%d %H:%M")
92
92
  lines = [
@@ -97,13 +97,13 @@ def generate_index(doc_dir, permanents, logs):
97
97
  f"",
98
98
  ]
99
99
 
100
- # Permanents section
101
- lines.append("## Permanent docs")
100
+ # Living docs section
101
+ lines.append("## Living docs")
102
102
  lines.append("")
103
- if permanents:
103
+ if living_docs:
104
104
  lines.append("| Document | Subject | Status | Category |")
105
105
  lines.append("|----------|---------|--------|----------|")
106
- for f, meta in permanents:
106
+ for f, meta in living_docs:
107
107
  name = f.name
108
108
  subject = meta.get("subject", "—")
109
109
  status = meta.get("status", "—")
@@ -111,7 +111,7 @@ def generate_index(doc_dir, permanents, logs):
111
111
  rel_path = f.relative_to(doc_dir.parent)
112
112
  lines.append(f"| [{name}]({rel_path}) | {subject} | {status} | {category} |")
113
113
  else:
114
- lines.append("_No permanent docs yet. Create one with `templates/permanent.md`._")
114
+ lines.append("_No living docs yet. Create one with `templates/living.md`._")
115
115
  lines.append("")
116
116
 
117
117
  # Logs section (last 20)
@@ -137,7 +137,7 @@ def generate_index(doc_dir, permanents, logs):
137
137
 
138
138
  # Stats
139
139
  lines.append("---")
140
- lines.append(f"**{len(permanents)}** permanent docs, **{len(logs)}** logs.")
140
+ lines.append(f"**{len(living_docs)}** living docs, **{len(logs)}** logs.")
141
141
  lines.append("")
142
142
 
143
143
  return "\n".join(lines)
@@ -145,12 +145,12 @@ def generate_index(doc_dir, permanents, logs):
145
145
 
146
146
  def main():
147
147
  doc_dir = find_doc_dir()
148
- permanents, logs = collect_docs(doc_dir)
149
- index_content = generate_index(doc_dir, permanents, logs)
148
+ living_docs, logs = collect_docs(doc_dir)
149
+ index_content = generate_index(doc_dir, living_docs, logs)
150
150
 
151
151
  index_path = doc_dir / "_INDEX.md"
152
152
  index_path.write_text(index_content, encoding="utf-8")
153
- print(f"Generated {index_path} ({len(permanents)} permanents, {len(logs)} logs)")
153
+ print(f"Generated {index_path} ({len(living_docs)} living docs, {len(logs)} logs)")
154
154
 
155
155
 
156
156
  if __name__ == "__main__":
@@ -0,0 +1,288 @@
1
+ /**
2
+ * openclew checkout — end-of-session summary + log creation.
3
+ *
4
+ * 1. Collect git activity (today's commits, uncommitted changes)
5
+ * 2. Display summary table
6
+ * 3. Create a session log pre-filled with the activity
7
+ * 4. Regenerate the index
8
+ */
9
+
10
+ const fs = require("fs");
11
+ const path = require("path");
12
+ const { execSync } = require("child_process");
13
+ const { logContent, slugifyLog, today } = require("./templates");
14
+ const { readConfig } = require("./config");
15
+
16
+ const PROJECT_ROOT = process.cwd();
17
+ const DOC_DIR = path.join(PROJECT_ROOT, "doc");
18
+ const LOG_DIR = path.join(DOC_DIR, "log");
19
+
20
+ function run(cmd) {
21
+ try {
22
+ return execSync(cmd, { cwd: PROJECT_ROOT, encoding: "utf-8" }).trim();
23
+ } catch {
24
+ return "";
25
+ }
26
+ }
27
+
28
+ function collectGitActivity() {
29
+ const date = today();
30
+
31
+ // Today's commits
32
+ const commitLog = run(
33
+ `git log --since="${date} 00:00" --format="%h %s" --no-merges`
34
+ );
35
+ const commits = commitLog
36
+ ? commitLog.split("\n").filter((l) => l.trim())
37
+ : [];
38
+
39
+ // Uncommitted changes
40
+ const status = run("git status --porcelain");
41
+ const uncommitted = status ? status.split("\n").filter((l) => l.trim()) : [];
42
+
43
+ // Files changed today (committed)
44
+ const changedFiles = run(
45
+ `git diff --name-only HEAD~${Math.max(commits.length, 1)}..HEAD 2>/dev/null`
46
+ );
47
+ const files = changedFiles
48
+ ? changedFiles.split("\n").filter((l) => l.trim())
49
+ : [];
50
+
51
+ // Today's logs already created
52
+ const existingLogs = fs.existsSync(LOG_DIR)
53
+ ? fs.readdirSync(LOG_DIR).filter((f) => f.startsWith(date))
54
+ : [];
55
+
56
+ // Living docs
57
+ const livingDocs = fs.existsSync(DOC_DIR)
58
+ ? fs.readdirSync(DOC_DIR).filter((f) => f.startsWith("_") && f !== "_INDEX.md" && f.endsWith(".md"))
59
+ : [];
60
+
61
+ return { date, commits, uncommitted, files, existingLogs, livingDocs };
62
+ }
63
+
64
+ function extractActions(commits) {
65
+ // Group commits by type (feat, fix, refactor, docs, etc.)
66
+ return commits.map((c) => {
67
+ const match = c.match(/^([a-f0-9]+)\s+(\w+)(?:\(([^)]*)\))?:\s*(.+)$/);
68
+ if (match) {
69
+ return {
70
+ hash: match[1],
71
+ type: match[2],
72
+ scope: match[3] || "",
73
+ desc: match[4],
74
+ };
75
+ }
76
+ // Non-conventional commit
77
+ const parts = c.match(/^([a-f0-9]+)\s+(.+)$/);
78
+ return {
79
+ hash: parts ? parts[1] : "",
80
+ type: "other",
81
+ scope: "",
82
+ desc: parts ? parts[2] : c,
83
+ };
84
+ });
85
+ }
86
+
87
+ function typeLabel(type) {
88
+ const labels = {
89
+ feat: "Feature",
90
+ fix: "Fix",
91
+ refactor: "Refactor",
92
+ docs: "Doc",
93
+ test: "Test",
94
+ build: "Build",
95
+ chore: "Chore",
96
+ };
97
+ return labels[type] || type.charAt(0).toUpperCase() + type.slice(1);
98
+ }
99
+
100
+ function displaySummary(activity) {
101
+ const { date, commits, uncommitted, existingLogs, livingDocs } = activity;
102
+ const actions = extractActions(commits);
103
+
104
+ console.log(`\nopenclew checkout — ${date}\n`);
105
+
106
+ if (actions.length === 0 && uncommitted.length === 0) {
107
+ console.log(" Nothing to report — no commits or changes today.");
108
+ console.log("");
109
+ return null;
110
+ }
111
+
112
+ // Summary table
113
+ if (actions.length > 0) {
114
+ console.log(" Commits today:");
115
+ console.log(
116
+ " ┌─────┬──────────────────────────────────────────────┬─────┐"
117
+ );
118
+ console.log(
119
+ " │ Sta │ Action │ Com │"
120
+ );
121
+ console.log(
122
+ " ├─────┼──────────────────────────────────────────────┼─────┤"
123
+ );
124
+ for (const a of actions) {
125
+ const label = `${typeLabel(a.type)} : ${a.desc}`;
126
+ const truncated = label.length > 44 ? label.slice(0, 43) + "…" : label;
127
+ const padded = truncated.padEnd(44);
128
+ console.log(` │ ✅ │ ${padded} │ 🟢 │`);
129
+ }
130
+ console.log(
131
+ " └─────┴──────────────────────────────────────────────┴─────┘"
132
+ );
133
+ console.log("");
134
+ }
135
+
136
+ if (uncommitted.length > 0) {
137
+ console.log(` Uncommitted changes: ${uncommitted.length} file(s)`);
138
+ for (const line of uncommitted.slice(0, 10)) {
139
+ console.log(` ${line}`);
140
+ }
141
+ if (uncommitted.length > 10) {
142
+ console.log(` ... and ${uncommitted.length - 10} more`);
143
+ }
144
+ console.log("");
145
+ }
146
+
147
+ // Documentation status
148
+ if (existingLogs.length > 0) {
149
+ console.log(` 📗 Today's logs: ${existingLogs.join(", ")}`);
150
+ } else {
151
+ console.log(" 📕 No log created today");
152
+ }
153
+ console.log("");
154
+
155
+ // Living docs reminder
156
+ if (livingDocs.length > 0) {
157
+ console.log(" 📚 Living docs — check if any need updating:");
158
+ for (const doc of livingDocs) {
159
+ console.log(` ${doc}`);
160
+ }
161
+ console.log("");
162
+ }
163
+
164
+ return actions;
165
+ }
166
+
167
+ function generateSessionLog(activity, actions) {
168
+ const { date } = activity;
169
+
170
+ // Build a descriptive title from actions
171
+ let sessionTitle;
172
+ if (actions.length === 1) {
173
+ sessionTitle = actions[0].desc;
174
+ } else if (actions.length > 1) {
175
+ const types = [...new Set(actions.map((a) => typeLabel(a.type)))];
176
+ sessionTitle = types.join(" + ") + " session";
177
+ } else {
178
+ sessionTitle = "Work session";
179
+ }
180
+
181
+ // Build pre-filled log content
182
+ const keywords = [
183
+ ...new Set(actions.map((a) => a.scope).filter(Boolean)),
184
+ ];
185
+ const keywordsStr =
186
+ keywords.length > 0 ? `[${keywords.join(", ")}]` : "[]";
187
+
188
+ const commitList = actions
189
+ .map((a) => `- ${typeLabel(a.type)}: ${a.desc} (${a.hash})`)
190
+ .join("\n");
191
+
192
+ const content = `<!-- L1_START -->
193
+ # L1 - Metadata
194
+ date: ${date}
195
+ type: ${actions.length === 1 ? actions[0].type === "fix" ? "Bug" : "Feature" : "Feature"}
196
+ subject: ${sessionTitle}
197
+ short_story: ${actions.map((a) => a.desc).join(". ")}.
198
+ status: Done
199
+ category:
200
+ keywords: ${keywordsStr}
201
+ <!-- L1_END -->
202
+
203
+ ---
204
+
205
+ <!-- L2_START -->
206
+ # L2 - Summary
207
+
208
+ ## Objective
209
+ <!-- Why this work was undertaken -->
210
+
211
+ ## What was done
212
+ ${commitList}
213
+
214
+ ## Result
215
+ <!-- Outcome — what works now that didn't before -->
216
+ <!-- L2_END -->
217
+
218
+ ---
219
+
220
+ <!-- L3_START -->
221
+ # L3 - Details
222
+
223
+ <!-- Technical details, code changes, debugging steps... -->
224
+ <!-- L3_END -->
225
+ `;
226
+
227
+ const slug = slugifyLog(sessionTitle);
228
+ const filename = `${date}_${slug}.md`;
229
+ const filepath = path.join(LOG_DIR, filename);
230
+
231
+ if (fs.existsSync(filepath)) {
232
+ console.log(` Log already exists: doc/log/${filename}`);
233
+ return null;
234
+ }
235
+
236
+ if (!fs.existsSync(LOG_DIR)) {
237
+ console.log(" No doc/log/ directory. Run 'openclew init' first.");
238
+ return null;
239
+ }
240
+
241
+ fs.writeFileSync(filepath, content, "utf-8");
242
+ console.log(` 📝 Created doc/log/${filename}`);
243
+ console.log(" Pre-filled with today's commits. Edit to add context.");
244
+ return filename;
245
+ }
246
+
247
+ function regenerateIndex() {
248
+ const indexScript = path.join(DOC_DIR, "generate-index.py");
249
+ if (!fs.existsSync(indexScript)) return;
250
+
251
+ try {
252
+ execSync(`python3 "${indexScript}" "${DOC_DIR}"`, { stdio: "pipe" });
253
+ console.log(" 📋 Regenerated doc/_INDEX.md");
254
+ } catch {
255
+ // Silent — index will be regenerated on next commit anyway
256
+ }
257
+ }
258
+
259
+ function main() {
260
+ if (!fs.existsSync(DOC_DIR)) {
261
+ console.error("No doc/ directory found. Run 'openclew init' first.");
262
+ process.exit(1);
263
+ }
264
+
265
+ if (!readConfig(PROJECT_ROOT)) {
266
+ console.warn("Warning: no .openclew.json found. Run 'openclew init' first.\n");
267
+ }
268
+
269
+ const activity = collectGitActivity();
270
+ const actions = displaySummary(activity);
271
+
272
+ if (!actions || actions.length === 0) {
273
+ return;
274
+ }
275
+
276
+ // Create session log
277
+ console.log("─── Log ───");
278
+ const created = generateSessionLog(activity, actions);
279
+
280
+ // Regenerate index
281
+ if (created) {
282
+ regenerateIndex();
283
+ }
284
+
285
+ console.log("");
286
+ }
287
+
288
+ main();
package/lib/config.js ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Read/write .openclew.json config at project root.
3
+ */
4
+
5
+ const fs = require("fs");
6
+ const path = require("path");
7
+
8
+ const CONFIG_FILE = ".openclew.json";
9
+
10
+ function configPath(projectRoot) {
11
+ return path.join(projectRoot || process.cwd(), CONFIG_FILE);
12
+ }
13
+
14
+ function readConfig(projectRoot) {
15
+ const p = configPath(projectRoot);
16
+ if (!fs.existsSync(p)) return null;
17
+ try {
18
+ return JSON.parse(fs.readFileSync(p, "utf-8"));
19
+ } catch {
20
+ return null;
21
+ }
22
+ }
23
+
24
+ function writeConfig(config, projectRoot) {
25
+ const p = configPath(projectRoot);
26
+ fs.writeFileSync(p, JSON.stringify(config, null, 2) + "\n", "utf-8");
27
+ }
28
+
29
+ function getEntryPoint(projectRoot) {
30
+ const config = readConfig(projectRoot);
31
+ return config && config.entryPoint ? config.entryPoint : null;
32
+ }
33
+
34
+ module.exports = { readConfig, writeConfig, getEntryPoint, CONFIG_FILE };
package/lib/detect.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Detect existing AI instruction files in the project root.
3
- * Returns an array of { tool, file, path } objects.
3
+ * Returns an array of { tool, file, fullPath, isDir } objects.
4
4
  */
5
5
 
6
6
  const fs = require("fs");
@@ -15,25 +15,60 @@ const INSTRUCTION_FILES = [
15
15
  { tool: "Windsurf", file: ".windsurf/rules" },
16
16
  { tool: "Cline", file: ".clinerules" },
17
17
  { tool: "Codex / Gemini", file: "AGENTS.md" },
18
+ { tool: "Antigravity", file: ".antigravity/rules.md" },
19
+ { tool: "Gemini CLI", file: ".gemini/GEMINI.md" },
18
20
  { tool: "Aider", file: "CONVENTIONS.md" },
19
21
  ];
20
22
 
23
+ /**
24
+ * Find AGENTS.md case-insensitively in projectRoot.
25
+ * Returns the actual filename (e.g. "agents.md", "Agents.md") or null.
26
+ */
27
+ function findAgentsMdCaseInsensitive(projectRoot) {
28
+ try {
29
+ const entries = fs.readdirSync(projectRoot);
30
+ const match = entries.find(
31
+ (e) => e.toLowerCase() === "agents.md" && fs.statSync(path.join(projectRoot, e)).isFile()
32
+ );
33
+ return match || null;
34
+ } catch {
35
+ return null;
36
+ }
37
+ }
38
+
21
39
  function detectInstructionFiles(projectRoot) {
22
40
  const found = [];
41
+ const seenLower = new Set();
42
+
23
43
  for (const entry of INSTRUCTION_FILES) {
44
+ // Skip AGENTS.md in the static list — handled by case-insensitive scan
45
+ if (entry.file.toLowerCase() === "agents.md") continue;
46
+
24
47
  const fullPath = path.join(projectRoot, entry.file);
25
48
  if (fs.existsSync(fullPath)) {
26
49
  const stat = fs.statSync(fullPath);
27
- // For directories (.cursor/rules, .windsurf/rules), note it but don't inject
28
50
  found.push({
29
51
  tool: entry.tool,
30
52
  file: entry.file,
31
53
  fullPath,
32
54
  isDir: stat.isDirectory(),
33
55
  });
56
+ seenLower.add(entry.file.toLowerCase());
34
57
  }
35
58
  }
59
+
60
+ // Case-insensitive AGENTS.md detection
61
+ const agentsFile = findAgentsMdCaseInsensitive(projectRoot);
62
+ if (agentsFile && !seenLower.has(agentsFile.toLowerCase())) {
63
+ found.push({
64
+ tool: "Codex / Gemini",
65
+ file: agentsFile,
66
+ fullPath: path.join(projectRoot, agentsFile),
67
+ isDir: false,
68
+ });
69
+ }
70
+
36
71
  return found;
37
72
  }
38
73
 
39
- module.exports = { detectInstructionFiles, INSTRUCTION_FILES };
74
+ module.exports = { detectInstructionFiles, findAgentsMdCaseInsensitive, INSTRUCTION_FILES };
package/lib/init.js CHANGED
@@ -2,17 +2,20 @@
2
2
  * openclew init — set up openclew in the current project.
3
3
  *
4
4
  * 1. Create doc/ and doc/log/
5
- * 2. Detect existing instruction files
6
- * 3. Propose to inject openclew block
5
+ * 2. Detect entry point (AGENTS.md case-insensitive by default)
6
+ * 3. Inject openclew block into entry point
7
7
  * 4. Install pre-commit hook for index generation
8
- * 5. Generate initial _INDEX.md
8
+ * 5. Create guide + example docs
9
+ * 6. Generate initial _INDEX.md
9
10
  */
10
11
 
11
12
  const fs = require("fs");
12
13
  const path = require("path");
13
14
  const readline = require("readline");
14
- const { detectInstructionFiles } = require("./detect");
15
+ const { detectInstructionFiles, findAgentsMdCaseInsensitive } = require("./detect");
15
16
  const { inject, isAlreadyInjected } = require("./inject");
17
+ const { writeConfig } = require("./config");
18
+ const { guideContent, exampleLivingDocContent, exampleLogContent, today } = require("./templates");
16
19
 
17
20
  const PROJECT_ROOT = process.cwd();
18
21
  const DOC_DIR = path.join(PROJECT_ROOT, "doc");
@@ -31,17 +34,15 @@ function ask(question) {
31
34
  return new Promise((resolve) => {
32
35
  rl.question(question, (answer) => {
33
36
  rl.close();
34
- resolve(answer.trim().toLowerCase());
37
+ resolve(answer.trim());
35
38
  });
36
39
  });
37
40
  }
38
41
 
39
42
  function createDirs() {
40
- let created = false;
41
43
  if (!fs.existsSync(DOC_DIR)) {
42
44
  fs.mkdirSync(DOC_DIR, { recursive: true });
43
45
  console.log(" Created doc/");
44
- created = true;
45
46
  } else {
46
47
  console.log(" doc/ already exists");
47
48
  }
@@ -49,12 +50,68 @@ function createDirs() {
49
50
  if (!fs.existsSync(LOG_DIR)) {
50
51
  fs.mkdirSync(LOG_DIR, { recursive: true });
51
52
  console.log(" Created doc/log/");
52
- created = true;
53
53
  } else {
54
54
  console.log(" doc/log/ already exists");
55
55
  }
56
+ }
57
+
58
+ /**
59
+ * Resolve the entry point file.
60
+ *
61
+ * Priority:
62
+ * 1. AGENTS.md (case-insensitive) — default, universal
63
+ * 2. Other detected instruction files — user picks one
64
+ * 3. Create AGENTS.md — if nothing exists
65
+ */
66
+ async function resolveEntryPoint() {
67
+ if (noInject) {
68
+ console.log(" Skipping entry point setup (--no-inject)");
69
+ return null;
70
+ }
71
+
72
+ // 1. Check for AGENTS.md (case-insensitive)
73
+ const agentsFile = findAgentsMdCaseInsensitive(PROJECT_ROOT);
74
+ if (agentsFile) {
75
+ if (!process.stdin.isTTY) {
76
+ // Non-interactive: accept AGENTS.md by default
77
+ console.log(` Using ${agentsFile} (non-interactive)`);
78
+ return { file: agentsFile, fullPath: path.join(PROJECT_ROOT, agentsFile), created: false };
79
+ }
80
+ const answer = await ask(` Found ${agentsFile} — use as entry point? [Y/n] `);
81
+ if (answer === "" || answer.toLowerCase() === "y" || answer.toLowerCase() === "yes") {
82
+ return { file: agentsFile, fullPath: path.join(PROJECT_ROOT, agentsFile), created: false };
83
+ }
84
+ }
85
+
86
+ // 2. Detect other instruction files
87
+ const others = detectInstructionFiles(PROJECT_ROOT).filter(
88
+ (f) => !f.isDir && f.file.toLowerCase() !== "agents.md"
89
+ );
90
+
91
+ if (others.length > 0) {
92
+ console.log(" Detected instruction files:");
93
+ others.forEach((f, i) => console.log(` ${i + 1}. ${f.file} (${f.tool})`));
94
+ console.log(` ${others.length + 1}. Create new AGENTS.md`);
95
+
96
+ if (!process.stdin.isTTY) {
97
+ // Non-interactive: default to first detected file
98
+ console.log(` Using ${others[0].file} (non-interactive)`);
99
+ return { file: others[0].file, fullPath: others[0].fullPath, created: false };
100
+ }
101
+
102
+ const choice = await ask(` Choose entry point [1-${others.length + 1}]: `);
103
+ const idx = parseInt(choice, 10) - 1;
104
+
105
+ if (idx >= 0 && idx < others.length) {
106
+ return { file: others[idx].file, fullPath: others[idx].fullPath, created: false };
107
+ }
108
+ }
56
109
 
57
- return created;
110
+ // 3. Create AGENTS.md
111
+ const agentsPath = path.join(PROJECT_ROOT, "AGENTS.md");
112
+ fs.writeFileSync(agentsPath, `# ${path.basename(PROJECT_ROOT)}\n\nProject instructions for AI agents.\n`, "utf-8");
113
+ console.log(" Created AGENTS.md");
114
+ return { file: "AGENTS.md", fullPath: agentsPath, created: true };
58
115
  }
59
116
 
60
117
  function installPreCommitHook() {
@@ -69,21 +126,10 @@ function installPreCommitHook() {
69
126
  }
70
127
 
71
128
  const preCommitPath = path.join(hooksDir, "pre-commit");
72
- const indexScript = `python3 -c "
73
- import subprocess, sys
74
- try:
75
- from pathlib import Path
76
- # Try local hook first, then npx-installed
77
- local = Path('hooks/generate-index.py')
78
- if local.exists():
79
- exec(local.read_text())
80
- else:
81
- # Fallback: run via npx
82
- subprocess.run([sys.executable, '-c', 'from openclew import generate_index; generate_index()'], check=True)
83
- except Exception as e:
84
- print(f'openclew index generation skipped: {e}')
85
- " 2>/dev/null
86
- git add doc/_INDEX.md 2>/dev/null`;
129
+ const indexScript = `if [ -f doc/generate-index.py ]; then
130
+ python3 doc/generate-index.py doc 2>/dev/null || echo "openclew: index generation failed"
131
+ git add doc/_INDEX.md 2>/dev/null
132
+ fi`;
87
133
 
88
134
  const MARKER = "# openclew-index";
89
135
 
@@ -93,19 +139,10 @@ git add doc/_INDEX.md 2>/dev/null`;
93
139
  console.log(" Pre-commit hook already contains openclew index generation");
94
140
  return false;
95
141
  }
96
- // Append to existing hook
97
- fs.appendFileSync(
98
- preCommitPath,
99
- `\n\n${MARKER}\n${indexScript}\n`,
100
- "utf-8"
101
- );
142
+ fs.appendFileSync(preCommitPath, `\n\n${MARKER}\n${indexScript}\n`, "utf-8");
102
143
  console.log(" Appended openclew index generation to existing pre-commit hook");
103
144
  } else {
104
- fs.writeFileSync(
105
- preCommitPath,
106
- `#!/bin/sh\n\n${MARKER}\n${indexScript}\n`,
107
- "utf-8"
108
- );
145
+ fs.writeFileSync(preCommitPath, `#!/bin/sh\n\n${MARKER}\n${indexScript}\n`, "utf-8");
109
146
  fs.chmodSync(preCommitPath, "755");
110
147
  console.log(" Created pre-commit hook for index generation");
111
148
  }
@@ -132,6 +169,35 @@ function copyGenerateIndex() {
132
169
  return false;
133
170
  }
134
171
 
172
+ function createDocs() {
173
+ // Guide — always created
174
+ const guidePath = path.join(DOC_DIR, "_USING_OPENCLEW.md");
175
+ if (!fs.existsSync(guidePath)) {
176
+ fs.writeFileSync(guidePath, guideContent(), "utf-8");
177
+ console.log(" Created doc/_USING_OPENCLEW.md (guide)");
178
+ } else {
179
+ console.log(" doc/_USING_OPENCLEW.md already exists");
180
+ }
181
+
182
+ // Example living doc
183
+ const examplePath = path.join(DOC_DIR, "_ARCHITECTURE.md");
184
+ if (!fs.existsSync(examplePath)) {
185
+ fs.writeFileSync(examplePath, exampleLivingDocContent(), "utf-8");
186
+ console.log(" Created doc/_ARCHITECTURE.md (example living doc)");
187
+ } else {
188
+ console.log(" doc/_ARCHITECTURE.md already exists");
189
+ }
190
+
191
+ // Example log
192
+ const logPath = path.join(LOG_DIR, `${today()}_setup-openclew.md`);
193
+ if (!fs.existsSync(logPath)) {
194
+ fs.writeFileSync(logPath, exampleLogContent(), "utf-8");
195
+ console.log(` Created doc/log/${today()}_setup-openclew.md (example log)`);
196
+ } else {
197
+ console.log(` doc/log/${today()}_setup-openclew.md already exists`);
198
+ }
199
+ }
200
+
135
201
  function runIndexGenerator() {
136
202
  const indexScript = path.join(DOC_DIR, "generate-index.py");
137
203
  if (!fs.existsSync(indexScript)) return;
@@ -156,38 +222,23 @@ async function main() {
156
222
  console.log("\n2. Index generator");
157
223
  copyGenerateIndex();
158
224
 
159
- // Step 3: Detect and inject into instruction files
160
- console.log("\n3. Instruction files");
161
- const found = detectInstructionFiles(PROJECT_ROOT);
225
+ // Step 3: Entry point
226
+ console.log("\n3. Entry point");
227
+ const entryPoint = await resolveEntryPoint();
162
228
 
163
- if (found.length === 0) {
164
- console.log(" No instruction file detected (CLAUDE.md, .cursorrules, AGENTS.md...)");
165
- console.log(" You can manually add the openclew block later. See: openclew help");
166
- } else if (noInject) {
167
- console.log(" Detected:", found.map((f) => f.file).join(", "));
168
- console.log(" Skipping injection (--no-inject)");
169
- } else {
170
- for (const entry of found) {
171
- if (entry.isDir) {
172
- console.log(` Found ${entry.file}/ (directory) — manual setup needed`);
173
- continue;
174
- }
175
-
176
- if (isAlreadyInjected(entry.fullPath)) {
177
- console.log(` ${entry.file} already has openclew block`);
178
- continue;
179
- }
180
-
181
- const answer = await ask(
182
- ` Inject openclew block into ${entry.file}? [Y/n] `
183
- );
184
- if (answer === "" || answer === "y" || answer === "yes") {
185
- inject(entry.fullPath);
186
- console.log(` Injected into ${entry.file}`);
187
- } else {
188
- console.log(` Skipped ${entry.file}`);
189
- }
229
+ if (entryPoint) {
230
+ if (isAlreadyInjected(entryPoint.fullPath)) {
231
+ console.log(` ${entryPoint.file} already has openclew block`);
232
+ } else {
233
+ inject(entryPoint.fullPath);
234
+ console.log(` Injected openclew block into ${entryPoint.file}`);
190
235
  }
236
+
237
+ writeConfig({ entryPoint: entryPoint.file }, PROJECT_ROOT);
238
+ console.log(` Saved entry point → .openclew.json`);
239
+ } else {
240
+ // --no-inject: still create config to mark init was done
241
+ writeConfig({ entryPoint: null }, PROJECT_ROOT);
191
242
  }
192
243
 
193
244
  // Step 4: Pre-commit hook
@@ -198,14 +249,24 @@ async function main() {
198
249
  installPreCommitHook();
199
250
  }
200
251
 
201
- // Step 5: Generate initial index
202
- console.log("\n5. Index");
252
+ // Step 5: Docs
253
+ console.log("\n5. Docs");
254
+ createDocs();
255
+
256
+ // Step 6: Generate index
257
+ console.log("\n6. Index");
203
258
  runIndexGenerator();
204
259
 
205
260
  // Done
206
- console.log("\nDone. Next steps:");
207
- console.log(" openclew new \"Architecture decisions\" — create your first doc");
208
- console.log(" openclew log \"Setup auth\" — create a session log");
261
+ console.log("\n─── Ready ───\n");
262
+ if (entryPoint) {
263
+ console.log(` Entry point: ${entryPoint.file}`);
264
+ }
265
+ console.log(" Guide: doc/_USING_OPENCLEW.md");
266
+ console.log("");
267
+ console.log(" Start a session with your agent now.");
268
+ console.log(' Ask it: "Read doc/_USING_OPENCLEW.md and document our architecture."');
269
+ console.log(" That's it — openclew works from here.");
209
270
  console.log("");
210
271
  }
211
272
 
package/lib/inject.js CHANGED
@@ -7,13 +7,17 @@ const fs = require("fs");
7
7
  const OPENCLEW_BLOCK = `
8
8
  ## Project knowledge (openclew)
9
9
 
10
- Structured documentation lives in \`doc/\`. Each doc has 3 levels (L1/L2/L3).
10
+ This file is the **entry point** for project documentation.
11
11
 
12
- **Before any task:** read \`doc/_INDEX.md\`, identify docs related to the task, read them.
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
- - Permanent docs: \`doc/_*.md\` — living knowledge (architecture, conventions, decisions)
15
- - Logs: \`doc/log/YYYY-MM-DD_*.md\` — frozen facts (what happened, never modified)
16
- - Read L1 first to decide if you need L2/L3
14
+ Two types of docs in \`doc/\`:
15
+ - **Living docs** (\`doc/_*.md\`)evolve with the project (architecture, conventions, decisions)
16
+ - **Logs** (\`doc/log/YYYY-MM-DD_*.md\`) frozen facts from a session, never modified after
17
+
18
+ Each doc has 3 levels: **L1** (metadata — read first to decide relevance) → **L2** (summary) → **L3** (full details, only when needed).
19
+
20
+ **Creating docs:** when a decision, convention, or significant event needs to be captured, create the file directly following the format in \`doc/_USING_OPENCLEW.md\`.
17
21
  `.trim();
18
22
 
19
23
  const MARKER_START = "<!-- openclew_START -->";
package/lib/new-doc.js CHANGED
@@ -1,10 +1,11 @@
1
1
  /**
2
- * openclew new <title> — create a new permanent doc.
2
+ * openclew new <title> — create a new living doc.
3
3
  */
4
4
 
5
5
  const fs = require("fs");
6
6
  const path = require("path");
7
- const { permanentContent, slugify } = require("./templates");
7
+ const { livingContent, slugify } = require("./templates");
8
+ const { readConfig } = require("./config");
8
9
 
9
10
  const args = process.argv.slice(2);
10
11
  // Remove "new" command from args
@@ -17,11 +18,15 @@ if (!title) {
17
18
  process.exit(1);
18
19
  }
19
20
 
20
- const docDir = path.join(process.cwd(), "doc");
21
+ const projectRoot = process.cwd();
22
+ const docDir = path.join(projectRoot, "doc");
21
23
  if (!fs.existsSync(docDir)) {
22
24
  console.error("No doc/ directory found. Run 'openclew init' first.");
23
25
  process.exit(1);
24
26
  }
27
+ if (!readConfig(projectRoot)) {
28
+ console.warn("Warning: no .openclew.json found. Run 'openclew init' first.");
29
+ }
25
30
 
26
31
  const slug = slugify(title);
27
32
  const filename = `_${slug}.md`;
@@ -32,5 +37,10 @@ if (fs.existsSync(filepath)) {
32
37
  process.exit(1);
33
38
  }
34
39
 
35
- fs.writeFileSync(filepath, permanentContent(title), "utf-8");
40
+ fs.writeFileSync(filepath, livingContent(title), "utf-8");
36
41
  console.log(`Created doc/${filename}`);
42
+ console.log("");
43
+ console.log("Next: open the file and fill in:");
44
+ console.log(" L1 — subject, status, keywords (so the index can find it)");
45
+ console.log(" L2 — objective + key points (what agents and humans need to know)");
46
+ console.log(" L3 — full details (only when needed)");
package/lib/new-log.js CHANGED
@@ -5,6 +5,7 @@
5
5
  const fs = require("fs");
6
6
  const path = require("path");
7
7
  const { logContent, slugifyLog, today } = require("./templates");
8
+ const { readConfig } = require("./config");
8
9
 
9
10
  const args = process.argv.slice(2);
10
11
  // Remove "log" command from args
@@ -17,11 +18,15 @@ if (!title) {
17
18
  process.exit(1);
18
19
  }
19
20
 
20
- const logDir = path.join(process.cwd(), "doc", "log");
21
+ const projectRoot = process.cwd();
22
+ const logDir = path.join(projectRoot, "doc", "log");
21
23
  if (!fs.existsSync(logDir)) {
22
24
  console.error("No doc/log/ directory found. Run 'openclew init' first.");
23
25
  process.exit(1);
24
26
  }
27
+ if (!readConfig(projectRoot)) {
28
+ console.warn("Warning: no .openclew.json found. Run 'openclew init' first.");
29
+ }
25
30
 
26
31
  const slug = slugifyLog(title);
27
32
  const date = today();
@@ -35,3 +40,9 @@ if (fs.existsSync(filepath)) {
35
40
 
36
41
  fs.writeFileSync(filepath, logContent(title), "utf-8");
37
42
  console.log(`Created doc/log/${filename}`);
43
+ console.log("");
44
+ console.log("Next: open the file and fill in:");
45
+ console.log(" L1 — type, status, short_story (what happened in 1-2 sentences)");
46
+ console.log(" L2 — problem + solution (the facts, frozen after this session)");
47
+ console.log("");
48
+ console.log("Logs are immutable — once written, never modified.");
package/lib/templates.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Template content for permanent docs and logs.
2
+ * Template content for living docs and logs.
3
3
  * Embedded here so the CLI works standalone without needing to locate template files.
4
4
  */
5
5
 
@@ -21,7 +21,7 @@ function slugifyLog(title) {
21
21
  .replace(/^-|-$/g, "");
22
22
  }
23
23
 
24
- function permanentContent(title) {
24
+ function livingContent(title) {
25
25
  const date = today();
26
26
  return `<!-- L1_START -->
27
27
  # L1 - Metadata
@@ -103,4 +103,266 @@ keywords: []
103
103
  `;
104
104
  }
105
105
 
106
- module.exports = { permanentContent, logContent, slugify, slugifyLog, today };
106
+ /**
107
+ * Guide doc — always created by init.
108
+ * This is what agents read to understand openclew.
109
+ */
110
+ function guideContent() {
111
+ const date = today();
112
+ return `<!-- L1_START -->
113
+ # L1 - Metadata
114
+ type: Guide
115
+ subject: How openclew works
116
+ created: ${date}
117
+ updated: ${date}
118
+ short_story: How openclew structures project knowledge in 3 levels (L1/L2/L3) so AI agents and humans navigate efficiently.
119
+ status: Active
120
+ category: Documentation
121
+ keywords: [openclew, L1, L2, L3, index, living-doc, log]
122
+ <!-- L1_END -->
123
+
124
+ ---
125
+
126
+ <!-- L2_START -->
127
+ # L2 - Summary
128
+
129
+ ## What is openclew?
130
+
131
+ openclew gives your project a structured memory that both humans and AI agents can navigate efficiently.
132
+
133
+ ## Doc-first rule
134
+
135
+ Before starting any task, read \`doc/_INDEX.md\` to find docs related to the task. Read them before exploring code. This avoids reinventing what's already documented.
136
+
137
+ ## Two types of docs
138
+
139
+ **Living docs** (\`doc/_*.md\`): knowledge that evolves with the project.
140
+ Architecture decisions, conventions, known pitfalls — anything that stays relevant over time.
141
+ Naming: \`doc/_UPPER_SNAKE_CASE.md\` (e.g. \`doc/_AUTH_DESIGN.md\`)
142
+
143
+ **Logs** (\`doc/log/YYYY-MM-DD_*.md\`): frozen facts from a work session.
144
+ What happened, what was decided, what was tried. Never modified after the session.
145
+ Naming: \`doc/log/YYYY-MM-DD_lowercase-slug.md\` (e.g. \`doc/log/2026-01-15_setup-auth.md\`)
146
+
147
+ ## Three levels per doc
148
+
149
+ Every doc has 3 levels. Read only what you need:
150
+
151
+ - **L1 — Metadata** (~40 tokens): subject, keywords, status. Read this first to decide if the doc is relevant.
152
+ - **L2 — Summary**: the essential context — objective, key points, decisions.
153
+ - **L3 — Details**: full technical content. Only read when deep-diving.
154
+
155
+ ## Index
156
+
157
+ \`doc/_INDEX.md\` is auto-generated from L1 metadata on every git commit (via a pre-commit hook).
158
+ Never edit it manually. To force a rebuild: \`openclew index\`
159
+ <!-- L2_END -->
160
+
161
+ ---
162
+
163
+ <!-- L3_START -->
164
+ # L3 - Details
165
+
166
+ ## Creating a living doc
167
+
168
+ Create \`doc/_TITLE.md\` (uppercase snake_case) with this structure:
169
+
170
+ \`\`\`
171
+ <!-- L1_START -->
172
+ # L1 - Metadata
173
+ type: Reference
174
+ subject: Title
175
+ created: YYYY-MM-DD
176
+ updated: YYYY-MM-DD
177
+ short_story:
178
+ status: Active
179
+ category:
180
+ keywords: []
181
+ <!-- L1_END -->
182
+
183
+ ---
184
+
185
+ <!-- L2_START -->
186
+ # L2 - Summary
187
+ ## Objective
188
+ ## Key points
189
+ <!-- L2_END -->
190
+
191
+ ---
192
+
193
+ <!-- L3_START -->
194
+ # L3 - Details
195
+ <!-- L3_END -->
196
+ \`\`\`
197
+
198
+ **When to create one:**
199
+ - A decision was made that others need to know (architecture, convention, API design)
200
+ - A pattern or pitfall keeps coming up
201
+ - You want an agent to know something at the start of every session
202
+
203
+ ## Creating a log
204
+
205
+ Create \`doc/log/YYYY-MM-DD_slug.md\` (lowercase, hyphens) with this structure:
206
+
207
+ \`\`\`
208
+ <!-- L1_START -->
209
+ # L1 - Metadata
210
+ date: YYYY-MM-DD
211
+ type: Feature
212
+ subject: Title
213
+ short_story:
214
+ status: In progress
215
+ category:
216
+ keywords: []
217
+ <!-- L1_END -->
218
+
219
+ ---
220
+
221
+ <!-- L2_START -->
222
+ # L2 - Summary
223
+ ## Objective
224
+ ## Problem
225
+ ## Solution
226
+ <!-- L2_END -->
227
+
228
+ ---
229
+
230
+ <!-- L3_START -->
231
+ # L3 - Details
232
+ <!-- L3_END -->
233
+ \`\`\`
234
+
235
+ **When to create one:**
236
+ - End of a work session (what was done, what's left)
237
+ - A bug was investigated and resolved
238
+ - A spike or experiment was conducted
239
+
240
+ Logs are immutable — once the session ends, the log is never modified.
241
+
242
+ ## How agents should use this
243
+
244
+ 1. At session start: read the entry point file
245
+ 2. Before any task: read \`doc/_INDEX.md\`, scan L1 metadata, identify relevant docs
246
+ 3. Read L2 of relevant docs for context
247
+ 4. Only read L3 when you need implementation details
248
+ 5. After significant work: create or update living docs and logs directly
249
+
250
+ The index (\`doc/_INDEX.md\`) auto-regenerates on every git commit. To force a rebuild: \`openclew index\`
251
+
252
+ ---
253
+
254
+ ## Changelog
255
+
256
+ | Date | Change |
257
+ |------|--------|
258
+ | ${date} | Created by openclew init |
259
+ <!-- L3_END -->
260
+ `;
261
+ }
262
+
263
+ /**
264
+ * Example living doc — shows what a filled-in doc looks like.
265
+ */
266
+ function exampleLivingDocContent() {
267
+ const date = today();
268
+ return `<!-- L1_START -->
269
+ # L1 - Metadata
270
+ type: Reference
271
+ subject: Architecture overview
272
+ created: ${date}
273
+ updated: ${date}
274
+ short_story: High-level architecture of the project — components, data flow, key decisions.
275
+ status: Active
276
+ category: Architecture
277
+ keywords: [architecture, overview, components]
278
+ <!-- L1_END -->
279
+
280
+ ---
281
+
282
+ <!-- L2_START -->
283
+ # L2 - Summary
284
+
285
+ ## Objective
286
+ Document the high-level architecture so new contributors and AI agents understand the system quickly.
287
+
288
+ ## Key points
289
+ - Replace this with your actual architecture
290
+ - Describe the main components and how they interact
291
+ - Note key technical decisions and their rationale
292
+ <!-- L2_END -->
293
+
294
+ ---
295
+
296
+ <!-- L3_START -->
297
+ # L3 - Details
298
+
299
+ <!-- Replace this with your actual architecture details -->
300
+
301
+ This is an example living doc created by \`openclew init\`.
302
+ Edit it to document your project's architecture, or delete it and create your own.
303
+
304
+ ---
305
+
306
+ ## Changelog
307
+
308
+ | Date | Change |
309
+ |------|--------|
310
+ | ${date} | Created by openclew init (example) |
311
+ <!-- L3_END -->
312
+ `;
313
+ }
314
+
315
+ /**
316
+ * Example log — shows what a filled-in log looks like.
317
+ */
318
+ function exampleLogContent() {
319
+ const date = today();
320
+ return `<!-- L1_START -->
321
+ # L1 - Metadata
322
+ date: ${date}
323
+ type: Feature
324
+ subject: Set up openclew
325
+ short_story: Initialized openclew for structured project knowledge. Created doc/ structure, git hook, guide, and example docs.
326
+ status: Done
327
+ category: Tooling
328
+ keywords: [openclew, setup, documentation]
329
+ <!-- L1_END -->
330
+
331
+ ---
332
+
333
+ <!-- L2_START -->
334
+ # L2 - Summary
335
+
336
+ ## Objective
337
+ Set up structured documentation so AI agents and new contributors can navigate project knowledge efficiently.
338
+
339
+ ## Problem
340
+ Project knowledge was scattered — README, inline comments, tribal knowledge. Each new AI session started from zero.
341
+
342
+ ## Solution
343
+ Installed openclew. Every doc now has L1 (metadata for triage), L2 (summary for context), L3 (details when needed).
344
+ The index auto-regenerates on each commit via a git hook.
345
+ <!-- L2_END -->
346
+
347
+ ---
348
+
349
+ <!-- L3_START -->
350
+ # L3 - Details
351
+
352
+ This log was created by \`openclew init\`.
353
+ 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 living docs (\`doc/_*.md\`).
355
+ <!-- L3_END -->
356
+ `;
357
+ }
358
+
359
+ module.exports = {
360
+ livingContent,
361
+ logContent,
362
+ guideContent,
363
+ exampleLivingDocContent,
364
+ exampleLogContent,
365
+ slugify,
366
+ slugifyLog,
367
+ today,
368
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclew",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Long Life Memory for LLMs — structured project knowledge for AI agents and humans",
5
5
  "license": "MIT",
6
6
  "bin": {
File without changes