openclew 0.0.1 → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 R.AlphA
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,299 @@
1
+ # openclew
2
+
3
+ > Long Life Memory for LLMs
4
+
5
+ **Your agent forgets. Your project remembers.**
6
+
7
+ In Greek mythology, Ariadne gave Theseus a *clew* — a ball of thread — to find his way out of the Minotaur's labyrinth. That thread is the etymological origin of the word "clue." It wasn't a map. It wasn't a search engine. It was a continuous trail that connected where you've been to where you are.
8
+
9
+ That's what openclew does for your project. Every decision, every architectural choice, every hard-won lesson — laid down as a thread that any reader (human or AI) can follow. Not scattered across wikis, chat logs, and CLAUDE.md files that grow until they're unreadable. One trail. One source of truth.
10
+
11
+ ---
12
+
13
+ ## Why this exists
14
+
15
+ AI agents are powerful, but they're amnesiac. Every new session starts from zero. The usual fixes don't work:
16
+
17
+ | Approach | What goes wrong |
18
+ |----------|----------------|
19
+ | CLAUDE.md / .cursorrules | Grows into an unreadable wall of text. Agent loads everything, wastes tokens on irrelevant context |
20
+ | Agent memory (Claude, Copilot) | Opaque, not versioned, not shareable with the team |
21
+ | Wiki / Notion | Disconnected from the code, goes stale |
22
+ | README.md | Not structured for AI consumption |
23
+ | Nothing | Re-explain everything every session |
24
+
25
+ The deeper problem isn't *storage* — it's **navigation**. A project with 50 documents and 200K tokens of knowledge can't be loaded in full. The real question an agent (or a human) needs to answer is:
26
+
27
+ > **"Should I read this document?"**
28
+
29
+ Not "does this file contain the word `auth`?" — that's pattern matching. The question is about *relevance*. And you can only answer it if documents are designed to be skimmed before they're read.
30
+
31
+ ---
32
+
33
+ ## The idea: 3 levels of depth
34
+
35
+ Every openclew document has 3 levels. Same file, different depths — for different needs.
36
+
37
+ ```
38
+ ┌─────────────────────────────────────────────┐
39
+ │ L1 — Metadata │
40
+ │ type, subject, status, keywords │
41
+ │ → "Should I read this?" — decidable in │
42
+ │ 2 seconds, ~40 tokens per doc │
43
+ │ → Auto-indexed, machine-parseable │
44
+ ├─────────────────────────────────────────────┤
45
+ │ L2 — Summary │
46
+ │ Objective, key points, solution │
47
+ │ → The full picture in 30 seconds │
48
+ │ → Enough for most decisions │
49
+ ├─────────────────────────────────────────────┤
50
+ │ L3 — Details │
51
+ │ Code, examples, history, edge cases │
52
+ │ → Deep-dive only when actually needed │
53
+ │ → Most readers never go here │
54
+ └─────────────────────────────────────────────┘
55
+ ```
56
+
57
+ This isn't just an organizational trick — it's a **token efficiency strategy**. A project with 50 docs:
58
+
59
+ | Strategy | Tokens consumed | Relevance |
60
+ |----------|----------------|-----------|
61
+ | Load everything | ~200K | Mostly noise |
62
+ | Grep for keywords | Variable | Misses context, false positives |
63
+ | **Read all L1s, then L2 of relevant docs** | **~2K + 2-3 docs** | **Precise, contextual** |
64
+
65
+ L1 answers "should I read this?" L2 answers "what do I need to know?" L3 is there when you need the details. Most of the time, you don't.
66
+
67
+ ---
68
+
69
+ ## Two types of docs
70
+
71
+ | Type | Location | Role | Mutability |
72
+ |------|----------|------|------------|
73
+ | **Permanent** | `doc/_SUBJECT.md` | Living knowledge (architecture, conventions, decisions) | Updated over time |
74
+ | **Log** | `doc/log/YYYY-MM-DD_subject.md` | Frozen facts (what happened, what was decided) | Never modified |
75
+
76
+ **Permanents** are your project's brain — they evolve as the project evolves.
77
+ **Logs** are your project's journal — immutable records of what happened and why.
78
+
79
+ Together, they form the thread. The permanent docs tell you where you are. The logs tell you how you got here.
80
+
81
+ ---
82
+
83
+ ## Quick start (5 minutes)
84
+
85
+ ### 1. Create the structure
86
+
87
+ ```bash
88
+ mkdir -p doc/log
89
+ ```
90
+
91
+ ### 2. Copy the templates
92
+
93
+ Download from [`templates/`](templates/) or create manually:
94
+
95
+ <details>
96
+ <summary><b>templates/permanent.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 -->
118
+
119
+ ## Key points
120
+ <!-- 3-5 essential takeaways -->
121
+
122
+ ## Solution
123
+ <!-- Recommended approach or pattern -->
124
+ <!-- L2_END -->
125
+
126
+ ---
127
+
128
+ <!-- L3_START -->
129
+ # L3 - Details
130
+
131
+ <!-- Full technical content: examples, code, references... -->
132
+
133
+ ## Changelog
134
+
135
+ | Date | Change |
136
+ |------|--------|
137
+ | YYYY-MM-DD | Initial creation |
138
+ <!-- L3_END -->
139
+ ```
140
+
141
+ </details>
142
+
143
+ <details>
144
+ <summary><b>templates/log.md</b> — for frozen facts</summary>
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
+ ---
171
+
172
+ <!-- L3_START -->
173
+ # L3 - Details
174
+
175
+ <!-- Technical details: code changes, debugging steps, references... -->
176
+ <!-- L3_END -->
177
+ ```
178
+
179
+ </details>
180
+
181
+ ### 3. Write your first doc
182
+
183
+ ```bash
184
+ cp templates/permanent.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
+ - Permanent 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
+ ---
220
+
221
+ ## How it works in practice
222
+
223
+ **Session 1** — You're setting up auth:
224
+ ```
225
+ doc/
226
+ ├── _ARCHITECTURE.md # Your stack, main patterns
227
+ └── log/
228
+ └── 2026-03-07_setup-auth.md # What you did, decisions made
229
+ ```
230
+
231
+ **Session 5** — New agent session, different feature:
232
+ ```
233
+ Agent reads doc/_INDEX.md (auto-generated)
234
+ → Scans all L1s: "Should I read this?"
235
+ → _ARCHITECTURE.md → yes → reads L2
236
+ → setup-auth log → relevant → reads L2
237
+ → Skips the rest
238
+ → Full context in ~1K tokens instead of 50K
239
+ ```
240
+
241
+ **Session 20** — Your project has grown:
242
+ ```
243
+ doc/
244
+ ├── _INDEX.md # Auto-generated, 30 entries
245
+ ├── _ARCHITECTURE.md # Updated 12 times
246
+ ├── _AUTH.md # Extracted when auth got complex
247
+ ├── _API_CONVENTIONS.md # Team conventions
248
+ ├── _KNOWN_ISSUES.md # Active gotchas
249
+ └── log/
250
+ ├── 2026-03-07_setup-auth.md
251
+ ├── 2026-03-10_migrate-db.md
252
+ ├── 2026-03-15_fix-token-refresh.md
253
+ └── ... (20 more)
254
+ ```
255
+
256
+ 30 docs. The agent scans all L1s in 2 seconds, reads the 3 that matter, and starts working with full context. A new teammate does the same — reads L2s to get up to speed in minutes. Same docs, same truth, different depth.
257
+
258
+ ---
259
+
260
+ ## Principles
261
+
262
+ - **"Should I read this?"** — L1 exists to answer this question. If it can't, the L1 is poorly written.
263
+ - **Shared knowledge** — Same docs for humans and AI. One source, multiple readers.
264
+ - **SSOT** (Single Source of Truth) — Each piece of information lives in one place.
265
+ - **Logs are immutable** — Once written, never modified. Frozen facts.
266
+ - **Permanents are living** — They evolve as the project evolves.
267
+ - **Index is auto-generated** — Never edit `_INDEX.md` manually.
268
+
269
+ ---
270
+
271
+ ## Works with everything
272
+
273
+ **AI agents:** Claude Code, Cursor, Copilot, Windsurf, Codex, Zed, Kiro, Aider, Cline, Gemini CLI...
274
+
275
+ **Workflow frameworks:** BMAD, Spec Kit, or any methodology — openclew handles knowledge, your framework handles process.
276
+
277
+ **It's just Markdown.** No runtime, no dependencies, no lock-in. Git-versioned, diffable, reviewable in PRs. If you stop using it, the docs are still useful — to humans and agents alike.
278
+
279
+ ---
280
+
281
+ ## Compared to alternatives
282
+
283
+ | Feature | CLAUDE.md | Cline Memory Bank | BMAD | openclew |
284
+ |---------|-----------|-------------------|------|----------|
285
+ | Readable by humans AND agents | partial | partial | yes | **yes** |
286
+ | Levels of depth (L1/L2/L3) | - | - | - | **yes** |
287
+ | "Should I read this?" (L1 triage) | - | - | - | **yes** |
288
+ | Token-efficient navigation | - | - | partial | **yes** |
289
+ | Auto-generated index | - | - | CSV | **yes** |
290
+ | Immutable logs | - | - | - | **yes** |
291
+ | Git-versioned | yes | yes | yes | **yes** |
292
+ | Cross-project | - | - | - | **yes** |
293
+ | Tool-agnostic | Claude only | Cline only | multi | **yes** |
294
+
295
+ ---
296
+
297
+ ## License
298
+
299
+ MIT — use it however you want.
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { resolve } = require("path");
4
+
5
+ const args = process.argv.slice(2);
6
+ const command = args[0];
7
+
8
+ const USAGE = `
9
+ openclew — Long Life Memory for LLMs
10
+
11
+ Usage:
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
15
+ openclew index Regenerate doc/_INDEX.md
16
+ openclew help Show this help
17
+
18
+ Options:
19
+ --no-hook Skip pre-commit hook installation (init)
20
+ --no-inject Skip instruction file injection (init)
21
+ `.trim();
22
+
23
+ if (!command || command === "help" || command === "--help" || command === "-h") {
24
+ console.log(USAGE);
25
+ process.exit(0);
26
+ }
27
+
28
+ const commands = {
29
+ init: () => require("../lib/init"),
30
+ new: () => require("../lib/new-doc"),
31
+ log: () => require("../lib/new-log"),
32
+ index: () => require("../lib/index-gen"),
33
+ };
34
+
35
+ if (!commands[command]) {
36
+ console.error(`Unknown command: ${command}`);
37
+ console.error(`Run 'openclew help' for usage.`);
38
+ process.exit(1);
39
+ }
40
+
41
+ commands[command]();
@@ -0,0 +1,157 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ openclew index generator.
4
+
5
+ Scans doc/_*.md (permanents) and doc/log/*.md (logs),
6
+ parses L1 metadata blocks, and generates doc/_INDEX.md.
7
+
8
+ Usage:
9
+ python generate-index.py # from project root
10
+ python generate-index.py /path/to/doc # custom doc directory
11
+
12
+ Idempotent: running twice produces the same output.
13
+ Zero dependencies: Python 3.8+ standard library only.
14
+ """
15
+
16
+ import os
17
+ import re
18
+ import sys
19
+ from datetime import datetime
20
+ from pathlib import Path
21
+
22
+
23
+ def find_doc_dir():
24
+ """Find the doc/ directory."""
25
+ if len(sys.argv) > 1:
26
+ doc_dir = Path(sys.argv[1])
27
+ else:
28
+ doc_dir = Path("doc")
29
+
30
+ if not doc_dir.is_dir():
31
+ print(f"No '{doc_dir}' directory found. Nothing to index.")
32
+ sys.exit(0)
33
+
34
+ return doc_dir
35
+
36
+
37
+ def parse_l1(filepath):
38
+ """Extract L1 metadata from a file."""
39
+ try:
40
+ content = filepath.read_text(encoding="utf-8")
41
+ except (OSError, UnicodeDecodeError):
42
+ return None
43
+
44
+ match = re.search(
45
+ r"<!--\s*L1_START\s*-->(.+?)<!--\s*L1_END\s*-->",
46
+ content,
47
+ re.DOTALL,
48
+ )
49
+ if not match:
50
+ return None
51
+
52
+ block = match.group(1)
53
+ meta = {}
54
+ for line in block.splitlines():
55
+ line = line.strip()
56
+ if line.startswith("#") or not line:
57
+ continue
58
+ if ":" in line:
59
+ key, _, value = line.partition(":")
60
+ meta[key.strip().lower()] = value.strip()
61
+
62
+ return meta
63
+
64
+
65
+ def collect_docs(doc_dir):
66
+ """Collect permanents and logs with their L1 metadata."""
67
+ permanents = []
68
+ logs = []
69
+
70
+ # Permanent docs: doc/_*.md
71
+ for f in sorted(doc_dir.glob("_*.md")):
72
+ if f.name == "_INDEX.md":
73
+ continue
74
+ meta = parse_l1(f)
75
+ if meta:
76
+ permanents.append((f, meta))
77
+
78
+ # Log docs: doc/log/*.md
79
+ log_dir = doc_dir / "log"
80
+ if log_dir.is_dir():
81
+ for f in sorted(log_dir.glob("*.md"), reverse=True):
82
+ meta = parse_l1(f)
83
+ if meta:
84
+ logs.append((f, meta))
85
+
86
+ return permanents, logs
87
+
88
+
89
+ def generate_index(doc_dir, permanents, logs):
90
+ """Generate _INDEX.md content."""
91
+ now = datetime.now().strftime("%Y-%m-%d %H:%M")
92
+ lines = [
93
+ f"# Project Knowledge Index",
94
+ f"",
95
+ f"> Auto-generated by [openclew](https://github.com/openclew/openclew) on {now}.",
96
+ f"> Do not edit manually — rebuilt from L1 metadata on every commit.",
97
+ f"",
98
+ ]
99
+
100
+ # Permanents section
101
+ lines.append("## Permanent docs")
102
+ lines.append("")
103
+ if permanents:
104
+ lines.append("| Document | Subject | Status | Category |")
105
+ lines.append("|----------|---------|--------|----------|")
106
+ for f, meta in permanents:
107
+ name = f.name
108
+ subject = meta.get("subject", "—")
109
+ status = meta.get("status", "—")
110
+ category = meta.get("category", "—")
111
+ rel_path = f.relative_to(doc_dir.parent)
112
+ lines.append(f"| [{name}]({rel_path}) | {subject} | {status} | {category} |")
113
+ else:
114
+ lines.append("_No permanent docs yet. Create one with `templates/permanent.md`._")
115
+ lines.append("")
116
+
117
+ # Logs section (last 20)
118
+ lines.append("## Recent logs")
119
+ lines.append("")
120
+ display_logs = logs[:20]
121
+ if display_logs:
122
+ lines.append("| Date | Subject | Status | Category |")
123
+ lines.append("|------|---------|--------|----------|")
124
+ for f, meta in display_logs:
125
+ date = meta.get("date", f.stem[:10])
126
+ subject = meta.get("subject", "—")
127
+ status = meta.get("status", "—")
128
+ category = meta.get("category", "—")
129
+ rel_path = f.relative_to(doc_dir.parent)
130
+ lines.append(f"| {date} | [{subject}]({rel_path}) | {status} | {category} |")
131
+ if len(logs) > 20:
132
+ lines.append(f"")
133
+ lines.append(f"_{len(logs) - 20} older logs not shown._")
134
+ else:
135
+ lines.append("_No logs yet. Create one with `templates/log.md`._")
136
+ lines.append("")
137
+
138
+ # Stats
139
+ lines.append("---")
140
+ lines.append(f"**{len(permanents)}** permanent docs, **{len(logs)}** logs.")
141
+ lines.append("")
142
+
143
+ return "\n".join(lines)
144
+
145
+
146
+ def main():
147
+ doc_dir = find_doc_dir()
148
+ permanents, logs = collect_docs(doc_dir)
149
+ index_content = generate_index(doc_dir, permanents, logs)
150
+
151
+ index_path = doc_dir / "_INDEX.md"
152
+ index_path.write_text(index_content, encoding="utf-8")
153
+ print(f"Generated {index_path} ({len(permanents)} permanents, {len(logs)} logs)")
154
+
155
+
156
+ if __name__ == "__main__":
157
+ main()
package/lib/detect.js ADDED
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Detect existing AI instruction files in the project root.
3
+ * Returns an array of { tool, file, path } objects.
4
+ */
5
+
6
+ const fs = require("fs");
7
+ const path = require("path");
8
+
9
+ const INSTRUCTION_FILES = [
10
+ { tool: "Claude Code", file: "CLAUDE.md" },
11
+ { tool: "Cursor", file: ".cursorrules" },
12
+ { tool: "Cursor", file: ".cursor/rules" },
13
+ { tool: "GitHub Copilot", file: ".github/copilot-instructions.md" },
14
+ { tool: "Windsurf", file: ".windsurfrules" },
15
+ { tool: "Windsurf", file: ".windsurf/rules" },
16
+ { tool: "Cline", file: ".clinerules" },
17
+ { tool: "Codex / Gemini", file: "AGENTS.md" },
18
+ { tool: "Aider", file: "CONVENTIONS.md" },
19
+ ];
20
+
21
+ function detectInstructionFiles(projectRoot) {
22
+ const found = [];
23
+ for (const entry of INSTRUCTION_FILES) {
24
+ const fullPath = path.join(projectRoot, entry.file);
25
+ if (fs.existsSync(fullPath)) {
26
+ const stat = fs.statSync(fullPath);
27
+ // For directories (.cursor/rules, .windsurf/rules), note it but don't inject
28
+ found.push({
29
+ tool: entry.tool,
30
+ file: entry.file,
31
+ fullPath,
32
+ isDir: stat.isDirectory(),
33
+ });
34
+ }
35
+ }
36
+ return found;
37
+ }
38
+
39
+ module.exports = { detectInstructionFiles, INSTRUCTION_FILES };
@@ -0,0 +1,40 @@
1
+ /**
2
+ * openclew index — regenerate doc/_INDEX.md
3
+ *
4
+ * Wraps hooks/generate-index.py. Falls back to a JS implementation
5
+ * if Python is not available.
6
+ */
7
+
8
+ const { execSync } = require("child_process");
9
+ const fs = require("fs");
10
+ const path = require("path");
11
+
12
+ const docDir = path.join(process.cwd(), "doc");
13
+
14
+ if (!fs.existsSync(docDir)) {
15
+ console.error("No doc/ directory found. Run 'openclew init' first.");
16
+ process.exit(1);
17
+ }
18
+
19
+ // Try local generate-index.py first
20
+ const localScript = path.join(docDir, "generate-index.py");
21
+ const packageScript = path.join(__dirname, "..", "hooks", "generate-index.py");
22
+ const script = fs.existsSync(localScript) ? localScript : packageScript;
23
+
24
+ if (fs.existsSync(script)) {
25
+ try {
26
+ const output = execSync(`python3 "${script}" "${docDir}"`, {
27
+ encoding: "utf-8",
28
+ });
29
+ console.log(output.trim());
30
+ process.exit(0);
31
+ } catch {
32
+ console.error(
33
+ "python3 not available. Install Python 3.8+ or regenerate manually."
34
+ );
35
+ process.exit(1);
36
+ }
37
+ }
38
+
39
+ console.error("generate-index.py not found. Run 'openclew init' first.");
40
+ process.exit(1);
package/lib/init.js ADDED
@@ -0,0 +1,212 @@
1
+ /**
2
+ * openclew init — set up openclew in the current project.
3
+ *
4
+ * 1. Create doc/ and doc/log/
5
+ * 2. Detect existing instruction files
6
+ * 3. Propose to inject openclew block
7
+ * 4. Install pre-commit hook for index generation
8
+ * 5. Generate initial _INDEX.md
9
+ */
10
+
11
+ const fs = require("fs");
12
+ const path = require("path");
13
+ const readline = require("readline");
14
+ const { detectInstructionFiles } = require("./detect");
15
+ const { inject, isAlreadyInjected } = require("./inject");
16
+
17
+ const PROJECT_ROOT = process.cwd();
18
+ const DOC_DIR = path.join(PROJECT_ROOT, "doc");
19
+ const LOG_DIR = path.join(DOC_DIR, "log");
20
+ const GIT_DIR = path.join(PROJECT_ROOT, ".git");
21
+
22
+ const args = process.argv.slice(2);
23
+ const noHook = args.includes("--no-hook");
24
+ const noInject = args.includes("--no-inject");
25
+
26
+ function ask(question) {
27
+ const rl = readline.createInterface({
28
+ input: process.stdin,
29
+ output: process.stdout,
30
+ });
31
+ return new Promise((resolve) => {
32
+ rl.question(question, (answer) => {
33
+ rl.close();
34
+ resolve(answer.trim().toLowerCase());
35
+ });
36
+ });
37
+ }
38
+
39
+ function createDirs() {
40
+ let created = false;
41
+ if (!fs.existsSync(DOC_DIR)) {
42
+ fs.mkdirSync(DOC_DIR, { recursive: true });
43
+ console.log(" Created doc/");
44
+ created = true;
45
+ } else {
46
+ console.log(" doc/ already exists");
47
+ }
48
+
49
+ if (!fs.existsSync(LOG_DIR)) {
50
+ fs.mkdirSync(LOG_DIR, { recursive: true });
51
+ console.log(" Created doc/log/");
52
+ created = true;
53
+ } else {
54
+ console.log(" doc/log/ already exists");
55
+ }
56
+
57
+ return created;
58
+ }
59
+
60
+ function installPreCommitHook() {
61
+ if (!fs.existsSync(GIT_DIR)) {
62
+ console.log(" No .git/ found — skipping hook installation");
63
+ return false;
64
+ }
65
+
66
+ const hooksDir = path.join(GIT_DIR, "hooks");
67
+ if (!fs.existsSync(hooksDir)) {
68
+ fs.mkdirSync(hooksDir, { recursive: true });
69
+ }
70
+
71
+ 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`;
87
+
88
+ const MARKER = "# openclew-index";
89
+
90
+ if (fs.existsSync(preCommitPath)) {
91
+ const existing = fs.readFileSync(preCommitPath, "utf-8");
92
+ if (existing.includes(MARKER)) {
93
+ console.log(" Pre-commit hook already contains openclew index generation");
94
+ return false;
95
+ }
96
+ // Append to existing hook
97
+ fs.appendFileSync(
98
+ preCommitPath,
99
+ `\n\n${MARKER}\n${indexScript}\n`,
100
+ "utf-8"
101
+ );
102
+ console.log(" Appended openclew index generation to existing pre-commit hook");
103
+ } else {
104
+ fs.writeFileSync(
105
+ preCommitPath,
106
+ `#!/bin/sh\n\n${MARKER}\n${indexScript}\n`,
107
+ "utf-8"
108
+ );
109
+ fs.chmodSync(preCommitPath, "755");
110
+ console.log(" Created pre-commit hook for index generation");
111
+ }
112
+
113
+ return true;
114
+ }
115
+
116
+ function copyGenerateIndex() {
117
+ const src = path.join(__dirname, "..", "hooks", "generate-index.py");
118
+ const dst = path.join(DOC_DIR, "generate-index.py");
119
+
120
+ if (fs.existsSync(dst)) {
121
+ console.log(" doc/generate-index.py already exists");
122
+ return false;
123
+ }
124
+
125
+ if (fs.existsSync(src)) {
126
+ fs.copyFileSync(src, dst);
127
+ console.log(" Copied generate-index.py to doc/");
128
+ return true;
129
+ }
130
+
131
+ console.log(" generate-index.py not found in package — skipping");
132
+ return false;
133
+ }
134
+
135
+ function runIndexGenerator() {
136
+ const indexScript = path.join(DOC_DIR, "generate-index.py");
137
+ if (!fs.existsSync(indexScript)) return;
138
+
139
+ try {
140
+ const { execSync } = require("child_process");
141
+ execSync(`python3 "${indexScript}" "${DOC_DIR}"`, { stdio: "pipe" });
142
+ console.log(" Generated doc/_INDEX.md");
143
+ } catch {
144
+ console.log(" Could not generate index (python3 not available)");
145
+ }
146
+ }
147
+
148
+ async function main() {
149
+ console.log("\nopenclew init\n");
150
+
151
+ // Step 1: Create directories
152
+ console.log("1. Project structure");
153
+ createDirs();
154
+
155
+ // Step 2: Copy index generator
156
+ console.log("\n2. Index generator");
157
+ copyGenerateIndex();
158
+
159
+ // Step 3: Detect and inject into instruction files
160
+ console.log("\n3. Instruction files");
161
+ const found = detectInstructionFiles(PROJECT_ROOT);
162
+
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
+ }
190
+ }
191
+ }
192
+
193
+ // Step 4: Pre-commit hook
194
+ console.log("\n4. Pre-commit hook");
195
+ if (noHook) {
196
+ console.log(" Skipping (--no-hook)");
197
+ } else {
198
+ installPreCommitHook();
199
+ }
200
+
201
+ // Step 5: Generate initial index
202
+ console.log("\n5. Index");
203
+ runIndexGenerator();
204
+
205
+ // 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");
209
+ console.log("");
210
+ }
211
+
212
+ main();
package/lib/inject.js ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Inject openclew block into an existing instruction file.
3
+ */
4
+
5
+ const fs = require("fs");
6
+
7
+ const OPENCLEW_BLOCK = `
8
+ ## Project knowledge (openclew)
9
+
10
+ Structured documentation lives in \`doc/\`. Each doc has 3 levels (L1/L2/L3).
11
+
12
+ **Before any task:** read \`doc/_INDEX.md\`, identify docs related to the task, read them.
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
17
+ `.trim();
18
+
19
+ const MARKER_START = "<!-- openclew_START -->";
20
+ const MARKER_END = "<!-- openclew_END -->";
21
+
22
+ function isAlreadyInjected(filePath) {
23
+ const content = fs.readFileSync(filePath, "utf-8");
24
+ return content.includes(MARKER_START);
25
+ }
26
+
27
+ function inject(filePath) {
28
+ if (isAlreadyInjected(filePath)) {
29
+ return false;
30
+ }
31
+
32
+ const content = fs.readFileSync(filePath, "utf-8");
33
+ const block = `\n\n${MARKER_START}\n${OPENCLEW_BLOCK}\n${MARKER_END}\n`;
34
+ fs.writeFileSync(filePath, content + block, "utf-8");
35
+ return true;
36
+ }
37
+
38
+ module.exports = { inject, isAlreadyInjected, OPENCLEW_BLOCK, MARKER_START };
package/lib/new-doc.js ADDED
@@ -0,0 +1,36 @@
1
+ /**
2
+ * openclew new <title> — create a new permanent doc.
3
+ */
4
+
5
+ const fs = require("fs");
6
+ const path = require("path");
7
+ const { permanentContent, slugify } = require("./templates");
8
+
9
+ const args = process.argv.slice(2);
10
+ // Remove "new" command from args
11
+ const cmdIndex = args.indexOf("new");
12
+ const titleArgs = cmdIndex >= 0 ? args.slice(cmdIndex + 1) : args.slice(1);
13
+ const title = titleArgs.join(" ");
14
+
15
+ if (!title) {
16
+ console.error('Usage: openclew new "Title of the document"');
17
+ process.exit(1);
18
+ }
19
+
20
+ const docDir = path.join(process.cwd(), "doc");
21
+ if (!fs.existsSync(docDir)) {
22
+ console.error("No doc/ directory found. Run 'openclew init' first.");
23
+ process.exit(1);
24
+ }
25
+
26
+ const slug = slugify(title);
27
+ const filename = `_${slug}.md`;
28
+ const filepath = path.join(docDir, filename);
29
+
30
+ if (fs.existsSync(filepath)) {
31
+ console.error(`File already exists: doc/${filename}`);
32
+ process.exit(1);
33
+ }
34
+
35
+ fs.writeFileSync(filepath, permanentContent(title), "utf-8");
36
+ console.log(`Created doc/${filename}`);
package/lib/new-log.js ADDED
@@ -0,0 +1,37 @@
1
+ /**
2
+ * openclew log <title> — create a new session log.
3
+ */
4
+
5
+ const fs = require("fs");
6
+ const path = require("path");
7
+ const { logContent, slugifyLog, today } = require("./templates");
8
+
9
+ const args = process.argv.slice(2);
10
+ // Remove "log" command from args
11
+ const cmdIndex = args.indexOf("log");
12
+ const titleArgs = cmdIndex >= 0 ? args.slice(cmdIndex + 1) : args.slice(1);
13
+ const title = titleArgs.join(" ");
14
+
15
+ if (!title) {
16
+ console.error('Usage: openclew log "Title of the log"');
17
+ process.exit(1);
18
+ }
19
+
20
+ const logDir = path.join(process.cwd(), "doc", "log");
21
+ if (!fs.existsSync(logDir)) {
22
+ console.error("No doc/log/ directory found. Run 'openclew init' first.");
23
+ process.exit(1);
24
+ }
25
+
26
+ const slug = slugifyLog(title);
27
+ const date = today();
28
+ const filename = `${date}_${slug}.md`;
29
+ const filepath = path.join(logDir, filename);
30
+
31
+ if (fs.existsSync(filepath)) {
32
+ console.error(`File already exists: doc/log/${filename}`);
33
+ process.exit(1);
34
+ }
35
+
36
+ fs.writeFileSync(filepath, logContent(title), "utf-8");
37
+ console.log(`Created doc/log/${filename}`);
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Template content for permanent docs and logs.
3
+ * Embedded here so the CLI works standalone without needing to locate template files.
4
+ */
5
+
6
+ function today() {
7
+ return new Date().toISOString().slice(0, 10);
8
+ }
9
+
10
+ function slugify(title) {
11
+ return title
12
+ .toUpperCase()
13
+ .replace(/[^A-Z0-9]+/g, "_")
14
+ .replace(/^_|_$/g, "");
15
+ }
16
+
17
+ function slugifyLog(title) {
18
+ return title
19
+ .toLowerCase()
20
+ .replace(/[^a-z0-9]+/g, "-")
21
+ .replace(/^-|-$/g, "");
22
+ }
23
+
24
+ function permanentContent(title) {
25
+ const date = today();
26
+ return `<!-- L1_START -->
27
+ # L1 - Metadata
28
+ type: Reference
29
+ subject: ${title}
30
+ created: ${date}
31
+ updated: ${date}
32
+ short_story:
33
+ status: Active
34
+ category:
35
+ keywords: []
36
+ <!-- L1_END -->
37
+
38
+ ---
39
+
40
+ <!-- L2_START -->
41
+ # L2 - Summary
42
+
43
+ ## Objective
44
+ <!-- Why this document exists -->
45
+
46
+ ## Key points
47
+ <!-- 3-5 essential takeaways -->
48
+ <!-- L2_END -->
49
+
50
+ ---
51
+
52
+ <!-- L3_START -->
53
+ # L3 - Details
54
+
55
+ <!-- Full technical content -->
56
+
57
+ ---
58
+
59
+ ## Changelog
60
+
61
+ | Date | Change |
62
+ |------|--------|
63
+ | ${date} | Initial creation |
64
+ <!-- L3_END -->
65
+ `;
66
+ }
67
+
68
+ function logContent(title) {
69
+ const date = today();
70
+ return `<!-- L1_START -->
71
+ # L1 - Metadata
72
+ date: ${date}
73
+ type: Feature
74
+ subject: ${title}
75
+ short_story:
76
+ status: In progress
77
+ category:
78
+ keywords: []
79
+ <!-- L1_END -->
80
+
81
+ ---
82
+
83
+ <!-- L2_START -->
84
+ # L2 - Summary
85
+
86
+ ## Objective
87
+ <!-- Why this work was undertaken -->
88
+
89
+ ## Problem
90
+ <!-- What was observed -->
91
+
92
+ ## Solution
93
+ <!-- How it was resolved -->
94
+ <!-- L2_END -->
95
+
96
+ ---
97
+
98
+ <!-- L3_START -->
99
+ # L3 - Details
100
+
101
+ <!-- Technical details, code changes, debugging steps... -->
102
+ <!-- L3_END -->
103
+ `;
104
+ }
105
+
106
+ module.exports = { permanentContent, logContent, slugify, slugifyLog, today };
package/package.json CHANGED
@@ -1,10 +1,35 @@
1
1
  {
2
2
  "name": "openclew",
3
- "version": "0.0.1",
4
- "description": "Long Life Memory for LLMs",
3
+ "version": "0.1.0",
4
+ "description": "Long Life Memory for LLMs — structured project knowledge for AI agents and humans",
5
5
  "license": "MIT",
6
+ "bin": {
7
+ "openclew": "./bin/openclew.js"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "lib/",
12
+ "templates/",
13
+ "hooks/",
14
+ "README.md",
15
+ "LICENSE"
16
+ ],
17
+ "keywords": [
18
+ "ai",
19
+ "llm",
20
+ "documentation",
21
+ "knowledge",
22
+ "memory",
23
+ "claude",
24
+ "cursor",
25
+ "copilot",
26
+ "agents"
27
+ ],
6
28
  "repository": {
7
29
  "type": "git",
8
30
  "url": "https://github.com/openclew/openclew"
31
+ },
32
+ "engines": {
33
+ "node": ">=16"
9
34
  }
10
35
  }
@@ -0,0 +1,36 @@
1
+ <!-- L1_START -->
2
+ # L1 - Metadata
3
+ date: YYYY-MM-DD
4
+ type: Bug | Feature | Refactor | Doc | Deploy
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]
10
+ <!-- L1_END -->
11
+
12
+ ---
13
+
14
+ <!-- L2_START -->
15
+ # L2 - Summary
16
+
17
+ ## Objective
18
+ <!-- Why this work was undertaken -->
19
+
20
+ ## Problem
21
+ <!-- What was observed, context of discovery -->
22
+
23
+ ## Solution
24
+ <!-- How it was resolved -->
25
+
26
+ ## Watch out
27
+ <!-- Side effects, edge cases, things to know -->
28
+ <!-- L2_END -->
29
+
30
+ ---
31
+
32
+ <!-- L3_START -->
33
+ # L3 - Details
34
+
35
+ <!-- Technical details: code changes, debugging steps, commands, references... -->
36
+ <!-- L3_END -->
@@ -0,0 +1,45 @@
1
+ <!-- L1_START -->
2
+ # L1 - Metadata
3
+ type: Reference | Architecture | Guide | Analysis
4
+ subject: Short title (< 60 chars)
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]
11
+ <!-- L1_END -->
12
+
13
+ ---
14
+
15
+ <!-- L2_START -->
16
+ # L2 - Summary
17
+
18
+ ## Objective
19
+ <!-- Why this document exists, what need it covers -->
20
+
21
+ ## Key points
22
+ <!-- 3-5 essential takeaways -->
23
+
24
+ ## Solution
25
+ <!-- Recommended approach or pattern -->
26
+
27
+ ## Watch out
28
+ <!-- Common pitfalls, edge cases, things to be aware of -->
29
+ <!-- L2_END -->
30
+
31
+ ---
32
+
33
+ <!-- L3_START -->
34
+ # L3 - Details
35
+
36
+ <!-- Full technical content: code examples, diagrams, references, deep dives... -->
37
+
38
+ ---
39
+
40
+ ## Changelog
41
+
42
+ | Date | Change |
43
+ |------|--------|
44
+ | YYYY-MM-DD | Initial creation |
45
+ <!-- L3_END -->