@ekkos/cli 1.3.7 → 1.3.8

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.
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Stack Detection — detect project language, framework, and package manager
3
+ *
4
+ * Pure functions with zero dependencies. Works on both local filesystem
5
+ * (via file list) and remote (via GitHub tree listing).
6
+ *
7
+ * Living Docs V4 — Phase 4: Language-Adapted Discovery
8
+ */
9
+ export interface StackInfo {
10
+ language: string;
11
+ framework?: string;
12
+ packageManager?: string;
13
+ version?: string;
14
+ }
15
+ /**
16
+ * Detect the stack for a directory by checking for manifest files.
17
+ * Works on both local filesystem (check file existence) and remote (file list).
18
+ * @param files - list of relative file paths in the directory
19
+ * @param readFile - optional async function to read file contents for deeper detection
20
+ */
21
+ export declare function detectStack(files: string[], readFile?: (path: string) => Promise<string | null>): Promise<StackInfo>;
@@ -0,0 +1,406 @@
1
+ "use strict";
2
+ /**
3
+ * Stack Detection — detect project language, framework, and package manager
4
+ *
5
+ * Pure functions with zero dependencies. Works on both local filesystem
6
+ * (via file list) and remote (via GitHub tree listing).
7
+ *
8
+ * Living Docs V4 — Phase 4: Language-Adapted Discovery
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.detectStack = detectStack;
12
+ /**
13
+ * Detect the stack for a directory by checking for manifest files.
14
+ * Works on both local filesystem (check file existence) and remote (file list).
15
+ * @param files - list of relative file paths in the directory
16
+ * @param readFile - optional async function to read file contents for deeper detection
17
+ */
18
+ async function detectStack(files, readFile) {
19
+ const fileSet = new Set(files.map(f => f.replace(/^\.\//, '')));
20
+ const hasFile = (name) => fileSet.has(name);
21
+ const hasGlob = (pattern) => {
22
+ // Simple suffix match for patterns like '*.csproj'
23
+ if (pattern.startsWith('*')) {
24
+ const suffix = pattern.slice(1);
25
+ return files.some(f => f.endsWith(suffix));
26
+ }
27
+ return fileSet.has(pattern);
28
+ };
29
+ // ── Package manager detection ──────────────────────────────────────────
30
+ const packageManager = detectPackageManager(fileSet);
31
+ // ── Language + framework detection (ordered by specificity) ────────────
32
+ // TypeScript / JavaScript
33
+ if (hasFile('package.json')) {
34
+ const info = await detectNodeStack(readFile);
35
+ return { ...info, packageManager: packageManager ?? info.packageManager };
36
+ }
37
+ // Rust
38
+ if (hasFile('Cargo.toml')) {
39
+ const info = await detectRustStack(readFile);
40
+ return { ...info, packageManager: packageManager ?? 'cargo' };
41
+ }
42
+ // Go
43
+ if (hasFile('go.mod')) {
44
+ const info = await detectGoStack(readFile);
45
+ return { ...info, packageManager: packageManager ?? 'go' };
46
+ }
47
+ // Python
48
+ if (hasFile('pyproject.toml') || hasFile('setup.py') || hasFile('requirements.txt') || hasFile('Pipfile') || hasFile('setup.cfg')) {
49
+ const info = await detectPythonStack(fileSet, readFile);
50
+ return { ...info, packageManager: packageManager ?? info.packageManager };
51
+ }
52
+ // Ruby
53
+ if (hasFile('Gemfile')) {
54
+ const info = await detectRubyStack(readFile);
55
+ return { ...info, packageManager: packageManager ?? 'bundle' };
56
+ }
57
+ // Java / Kotlin
58
+ if (hasFile('pom.xml') || hasFile('build.gradle') || hasFile('build.gradle.kts') || hasFile('settings.gradle') || hasFile('settings.gradle.kts')) {
59
+ const info = await detectJavaStack(fileSet, readFile);
60
+ return { ...info, packageManager: packageManager ?? info.packageManager };
61
+ }
62
+ // Dart / Flutter
63
+ if (hasFile('pubspec.yaml')) {
64
+ return { language: 'dart', framework: 'flutter', packageManager: 'pub' };
65
+ }
66
+ // Elixir / Phoenix
67
+ if (hasFile('mix.exs')) {
68
+ const info = await detectElixirStack(readFile);
69
+ return { ...info, packageManager: 'mix' };
70
+ }
71
+ // PHP / Laravel
72
+ if (hasFile('composer.json')) {
73
+ const info = await detectPhpStack(readFile);
74
+ return { ...info, packageManager: 'composer' };
75
+ }
76
+ // C# / .NET
77
+ if (hasGlob('*.csproj') || hasGlob('*.sln') || hasGlob('*.fsproj')) {
78
+ return { language: 'csharp', framework: 'dotnet', packageManager: 'nuget' };
79
+ }
80
+ // C/C++ — only if no other manifest was found
81
+ if (hasFile('Makefile') || hasFile('CMakeLists.txt') || hasFile('meson.build')) {
82
+ return { language: 'cpp', packageManager: packageManager ?? undefined };
83
+ }
84
+ return { language: 'unknown', packageManager: packageManager ?? undefined };
85
+ }
86
+ // ── Helpers ────────────────────────────────────────────────────────────────
87
+ function detectPackageManager(fileSet) {
88
+ if (fileSet.has('pnpm-lock.yaml') || fileSet.has('pnpm-workspace.yaml'))
89
+ return 'pnpm';
90
+ if (fileSet.has('yarn.lock'))
91
+ return 'yarn';
92
+ if (fileSet.has('package-lock.json'))
93
+ return 'npm';
94
+ if (fileSet.has('bun.lockb') || fileSet.has('bun.lock'))
95
+ return 'bun';
96
+ if (fileSet.has('poetry.lock'))
97
+ return 'poetry';
98
+ if (fileSet.has('Pipfile.lock'))
99
+ return 'pipenv';
100
+ if (fileSet.has('uv.lock'))
101
+ return 'uv';
102
+ if (fileSet.has('Cargo.lock'))
103
+ return 'cargo';
104
+ if (fileSet.has('go.sum'))
105
+ return 'go';
106
+ if (fileSet.has('Gemfile.lock'))
107
+ return 'bundle';
108
+ return undefined;
109
+ }
110
+ async function detectNodeStack(readFile) {
111
+ const base = { language: 'typescript', packageManager: 'npm' };
112
+ if (!readFile)
113
+ return base;
114
+ const raw = await readFile('package.json');
115
+ if (!raw)
116
+ return base;
117
+ try {
118
+ const pkg = JSON.parse(raw);
119
+ const allDeps = {
120
+ ...pkg.dependencies,
121
+ ...pkg.devDependencies,
122
+ };
123
+ // Check if actually TypeScript
124
+ if (!allDeps['typescript'] && !allDeps['ts-node'] && !allDeps['tsx']) {
125
+ base.language = 'javascript';
126
+ }
127
+ // Framework detection — ordered by specificity (most specific first)
128
+ const frameworkMap = [
129
+ ['next', 'next'],
130
+ ['nuxt', 'nuxt'],
131
+ ['remix', 'remix'],
132
+ ['@sveltejs/kit', 'sveltekit'],
133
+ ['svelte', 'svelte'],
134
+ ['@angular/core', 'angular'],
135
+ ['vue', 'vue'],
136
+ ['react', 'react'],
137
+ ['hono', 'hono'],
138
+ ['fastify', 'fastify'],
139
+ ['express', 'express'],
140
+ ['koa', 'koa'],
141
+ ['@nestjs/core', 'nest'],
142
+ ['astro', 'astro'],
143
+ ['gatsby', 'gatsby'],
144
+ ['@electron/rebuild', 'electron'],
145
+ ['electron', 'electron'],
146
+ ];
147
+ for (const [dep, framework] of frameworkMap) {
148
+ if (allDeps[dep]) {
149
+ base.framework = framework;
150
+ base.version = allDeps[dep]?.replace(/^[\^~>=<]/, '');
151
+ break;
152
+ }
153
+ }
154
+ return base;
155
+ }
156
+ catch {
157
+ return base;
158
+ }
159
+ }
160
+ async function detectRustStack(readFile) {
161
+ const base = { language: 'rust', packageManager: 'cargo' };
162
+ if (!readFile)
163
+ return base;
164
+ const raw = await readFile('Cargo.toml');
165
+ if (!raw)
166
+ return base;
167
+ const content = raw.toLowerCase();
168
+ const frameworkMap = [
169
+ ['actix-web', 'actix'],
170
+ ['axum', 'axum'],
171
+ ['rocket', 'rocket'],
172
+ ['warp', 'warp'],
173
+ ['tide', 'tide'],
174
+ ['tokio', 'tokio'],
175
+ ['leptos', 'leptos'],
176
+ ['yew', 'yew'],
177
+ ['tauri', 'tauri'],
178
+ ];
179
+ for (const [dep, framework] of frameworkMap) {
180
+ if (content.includes(dep)) {
181
+ base.framework = framework;
182
+ // Try to extract version: dep = "X.Y.Z" or dep = { version = "X.Y.Z" }
183
+ const versionMatch = raw.match(new RegExp(`${dep}\\s*=\\s*"([^"]+)"`, 'i'))
184
+ || raw.match(new RegExp(`${dep}\\s*=\\s*\\{[^}]*version\\s*=\\s*"([^"]+)"`, 'i'));
185
+ if (versionMatch)
186
+ base.version = versionMatch[1];
187
+ break;
188
+ }
189
+ }
190
+ return base;
191
+ }
192
+ async function detectGoStack(readFile) {
193
+ const base = { language: 'go', packageManager: 'go' };
194
+ if (!readFile)
195
+ return base;
196
+ const raw = await readFile('go.mod');
197
+ if (!raw)
198
+ return base;
199
+ const frameworkMap = [
200
+ ['github.com/gin-gonic/gin', 'gin'],
201
+ ['github.com/labstack/echo', 'echo'],
202
+ ['github.com/gofiber/fiber', 'fiber'],
203
+ ['github.com/go-chi/chi', 'chi'],
204
+ ['github.com/gorilla/mux', 'gorilla'],
205
+ ['github.com/beego/beego', 'beego'],
206
+ ['github.com/revel/revel', 'revel'],
207
+ ['google.golang.org/grpc', 'grpc'],
208
+ ['github.com/bufbuild/connect-go', 'connect'],
209
+ ];
210
+ for (const [dep, framework] of frameworkMap) {
211
+ if (raw.includes(dep)) {
212
+ base.framework = framework;
213
+ // Try to extract version from go.mod require block
214
+ const versionMatch = raw.match(new RegExp(`${dep.replace(/\//g, '\\/')}\\s+v([\\d.]+)`));
215
+ if (versionMatch)
216
+ base.version = versionMatch[1];
217
+ break;
218
+ }
219
+ }
220
+ return base;
221
+ }
222
+ async function detectPythonStack(fileSet, readFile) {
223
+ const base = { language: 'python', packageManager: 'pip' };
224
+ // Refine package manager
225
+ if (fileSet.has('poetry.lock') || fileSet.has('pyproject.toml'))
226
+ base.packageManager = 'poetry';
227
+ if (fileSet.has('Pipfile') || fileSet.has('Pipfile.lock'))
228
+ base.packageManager = 'pipenv';
229
+ if (fileSet.has('uv.lock'))
230
+ base.packageManager = 'uv';
231
+ if (!readFile)
232
+ return base;
233
+ // Read all possible manifest files for framework detection
234
+ const contents = [];
235
+ for (const manifest of ['requirements.txt', 'pyproject.toml', 'Pipfile', 'setup.py', 'setup.cfg']) {
236
+ if (fileSet.has(manifest)) {
237
+ const raw = await readFile(manifest);
238
+ if (raw)
239
+ contents.push(raw.toLowerCase());
240
+ }
241
+ }
242
+ const combined = contents.join('\n');
243
+ const frameworkMap = [
244
+ ['django', 'django'],
245
+ ['fastapi', 'fastapi'],
246
+ ['flask', 'flask'],
247
+ ['starlette', 'starlette'],
248
+ ['tornado', 'tornado'],
249
+ ['sanic', 'sanic'],
250
+ ['aiohttp', 'aiohttp'],
251
+ ['streamlit', 'streamlit'],
252
+ ['gradio', 'gradio'],
253
+ ['celery', 'celery'],
254
+ ['scrapy', 'scrapy'],
255
+ ];
256
+ for (const [dep, framework] of frameworkMap) {
257
+ if (combined.includes(dep)) {
258
+ base.framework = framework;
259
+ // Try to extract version from requirements.txt format: django==4.2.0
260
+ const versionMatch = combined.match(new RegExp(`${dep}[=~><]+([\\d.]+)`));
261
+ if (versionMatch)
262
+ base.version = versionMatch[1];
263
+ break;
264
+ }
265
+ }
266
+ return base;
267
+ }
268
+ async function detectRubyStack(readFile) {
269
+ const base = { language: 'ruby', packageManager: 'bundle' };
270
+ if (!readFile)
271
+ return base;
272
+ const raw = await readFile('Gemfile');
273
+ if (!raw)
274
+ return base;
275
+ const content = raw.toLowerCase();
276
+ const frameworkMap = [
277
+ ['rails', 'rails'],
278
+ ['sinatra', 'sinatra'],
279
+ ['hanami', 'hanami'],
280
+ ['grape', 'grape'],
281
+ ['roda', 'roda'],
282
+ ];
283
+ for (const [dep, framework] of frameworkMap) {
284
+ if (content.includes(`'${dep}'`) || content.includes(`"${dep}"`)) {
285
+ base.framework = framework;
286
+ // Try to extract version: gem 'rails', '~> 7.0'
287
+ const versionMatch = raw.match(new RegExp(`['"]${dep}['"]\\s*,\\s*['"][~>=<]*\\s*([\\d.]+)['"]`, 'i'));
288
+ if (versionMatch)
289
+ base.version = versionMatch[1];
290
+ break;
291
+ }
292
+ }
293
+ return base;
294
+ }
295
+ async function detectJavaStack(fileSet, readFile) {
296
+ const base = { language: 'java' };
297
+ // Package manager
298
+ if (fileSet.has('pom.xml')) {
299
+ base.packageManager = 'maven';
300
+ }
301
+ else if (fileSet.has('build.gradle') || fileSet.has('build.gradle.kts')) {
302
+ base.packageManager = 'gradle';
303
+ }
304
+ if (!readFile)
305
+ return base;
306
+ // Check for Kotlin
307
+ const gradleKts = fileSet.has('build.gradle.kts');
308
+ if (gradleKts) {
309
+ const raw = await readFile('build.gradle.kts');
310
+ if (raw && (raw.includes('kotlin') || raw.includes('org.jetbrains.kotlin'))) {
311
+ base.language = 'kotlin';
312
+ }
313
+ }
314
+ // Framework detection from build files
315
+ const buildFiles = ['pom.xml', 'build.gradle', 'build.gradle.kts'];
316
+ for (const buildFile of buildFiles) {
317
+ if (!fileSet.has(buildFile))
318
+ continue;
319
+ const raw = await readFile(buildFile);
320
+ if (!raw)
321
+ continue;
322
+ const content = raw.toLowerCase();
323
+ if (content.includes('spring-boot') || content.includes('springframework')) {
324
+ base.framework = 'spring';
325
+ const versionMatch = raw.match(/spring-boot[^'"]*['"](\d+\.\d+\.\d+)['"]/i)
326
+ || raw.match(/<spring-boot\.version>(\d+\.\d+\.\d+)<\/spring-boot\.version>/i);
327
+ if (versionMatch)
328
+ base.version = versionMatch[1];
329
+ break;
330
+ }
331
+ if (content.includes('quarkus')) {
332
+ base.framework = 'quarkus';
333
+ break;
334
+ }
335
+ if (content.includes('micronaut')) {
336
+ base.framework = 'micronaut';
337
+ break;
338
+ }
339
+ if (content.includes('ktor')) {
340
+ base.framework = 'ktor';
341
+ break;
342
+ }
343
+ if (content.includes('vertx') || content.includes('vert.x')) {
344
+ base.framework = 'vertx';
345
+ break;
346
+ }
347
+ }
348
+ return base;
349
+ }
350
+ async function detectElixirStack(readFile) {
351
+ const base = { language: 'elixir', packageManager: 'mix' };
352
+ if (!readFile)
353
+ return base;
354
+ const raw = await readFile('mix.exs');
355
+ if (!raw)
356
+ return base;
357
+ const content = raw.toLowerCase();
358
+ if (content.includes(':phoenix')) {
359
+ base.framework = 'phoenix';
360
+ const versionMatch = raw.match(/:phoenix\s*,\s*"~>\s*([\d.]+)"/);
361
+ if (versionMatch)
362
+ base.version = versionMatch[1];
363
+ }
364
+ else if (content.includes(':plug')) {
365
+ base.framework = 'plug';
366
+ }
367
+ else if (content.includes(':nerves')) {
368
+ base.framework = 'nerves';
369
+ }
370
+ return base;
371
+ }
372
+ async function detectPhpStack(readFile) {
373
+ const base = { language: 'php', packageManager: 'composer' };
374
+ if (!readFile)
375
+ return base;
376
+ const raw = await readFile('composer.json');
377
+ if (!raw)
378
+ return base;
379
+ try {
380
+ const composer = JSON.parse(raw);
381
+ const allDeps = {
382
+ ...composer.require,
383
+ ...composer['require-dev'],
384
+ };
385
+ const frameworkMap = [
386
+ ['laravel/framework', 'laravel'],
387
+ ['symfony/symfony', 'symfony'],
388
+ ['symfony/framework-bundle', 'symfony'],
389
+ ['slim/slim', 'slim'],
390
+ ['cakephp/cakephp', 'cakephp'],
391
+ ['codeigniter4/framework', 'codeigniter'],
392
+ ['yiisoft/yii2', 'yii'],
393
+ ];
394
+ for (const [dep, framework] of frameworkMap) {
395
+ if (allDeps[dep]) {
396
+ base.framework = framework;
397
+ base.version = allDeps[dep]?.replace(/^[\^~>=<*]/, '');
398
+ break;
399
+ }
400
+ }
401
+ return base;
402
+ }
403
+ catch {
404
+ return base;
405
+ }
406
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ekkos/cli",
3
- "version": "1.3.7",
3
+ "version": "1.3.8",
4
4
  "description": "Setup ekkOS memory for AI coding assistants (Claude Code, Cursor, Windsurf)",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -8,6 +8,13 @@
8
8
  "cli": "dist/index.js",
9
9
  "ekkos-capture": "dist/cache/capture.js"
10
10
  },
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "dev": "tsx src/index.ts",
14
+ "prepack": "node scripts/build-templates.js prepack",
15
+ "postpack": "node scripts/build-templates.js postpack",
16
+ "prepublishOnly": "npm run build"
17
+ },
11
18
  "keywords": [
12
19
  "ekkos",
13
20
  "ai",
@@ -51,9 +58,5 @@
51
58
  "!dist/cron",
52
59
  "templates/CLAUDE.md",
53
60
  "templates/skills"
54
- ],
55
- "scripts": {
56
- "build": "tsc",
57
- "dev": "tsx src/index.ts"
58
- }
59
- }
61
+ ]
62
+ }
@@ -1,131 +1,121 @@
1
- # ekkOS_ Memory System
1
+ <!-- ekkOS:begin managed by ekkos init, do not remove this marker -->
2
+ # ekkOS Memory
2
3
 
3
- ## What is ekkOS?
4
-
5
- ekkOS is persistent memory for your AI coding assistant. It learns from every session — remembering mistakes, proven solutions, your preferences, and project context — so your AI gets smarter over time.
6
-
7
- **Failures are just as valuable as successes.** Forge anti-patterns too.
8
-
9
- ---
4
+ ekkOS gives you persistent memory across sessions — mistakes, solutions, preferences, and project context carry forward so you get smarter over time.
10
5
 
11
6
  ## How It Works
12
7
 
13
- ekkOS uses **proxy injection** to transparently enrich your conversations with relevant context. You don't need to do anything special — the proxy automatically:
8
+ The **ekkOS proxy** transparently enriches conversations. It automatically:
14
9
 
15
- - Injects **relevant patterns** from past sessions when you ask technical questions
16
- - Injects **your directives** (MUST/NEVER/PREFER/AVOID rules) so your AI follows your preferences
17
- - Injects **episodic recall** when you reference past conversations ("yesterday", "last week")
18
- - Injects **schema context** when working with databases
19
- - **Auto-forges patterns** when it detects bug fixes or learned solutions
10
+ - Injects **relevant patterns** when you ask technical questions
11
+ - Injects **your directives** (MUST/NEVER/PREFER/AVOID rules)
12
+ - Injects **episodic recall** when you reference past conversations
13
+ - **Auto-forges patterns** when it detects bug fixes
20
14
  - Injects **active goals** and plan progress
21
15
 
22
- You also have **MCP tools** for explicit memory operations when needed.
16
+ You also have MCP tools for explicit memory operations.
23
17
 
24
- ---
18
+ ## Rules
25
19
 
26
- ## Core Rules
20
+ 1. **Search before answering** — call `ekkOS_Search` before any technical question
21
+ 2. **Apply patterns silently** — use retrieved patterns without emitting acknowledgment blocks
22
+ 3. **Record outcomes** — call `ekkOS_Outcome` after applying a pattern
23
+ 4. **Forge what you learn** — call `ekkOS_Forge` after fixing bugs or learning something new
27
24
 
28
- ### RULE 1: SEARCH BEFORE ANSWERING
29
- Before answering ANY technical question, call `ekkOS_Search` first.
30
- Do not skip this. Do not assume you know the answer.
31
-
32
- ### RULE 2: ACKNOWLEDGE PATTERNS (PatternGuard)
33
- When patterns are retrieved (via proxy injection or search), acknowledge every pattern — either use it or skip it.
25
+ ## MCP Tools
34
26
 
35
- **Exception:** Suppress SELECT/SKIP blocks for non-technical turns.
27
+ ### ekkOS_Search Search memory before answering
28
+ ```
29
+ ekkOS_Search({ query: "the problem or question", sources: ["all"], limit: 10 })
30
+ ```
31
+ - `query` (required): what you're looking for
32
+ - `sources`: `"patterns"`, `"episodic"`, `"semantic"`, `"collective"`, `"codebase"`, `"directives"`, or `"all"`
33
+ - Returns matched patterns with `retrieval_token` for tracking
36
34
 
37
- **For patterns you USE:**
35
+ ### ekkOS_Forge — Save what you learned
38
36
  ```
39
- [ekkOS_SELECT]
40
- - id: <pattern_id>
41
- reason: <why using>
42
- confidence: <0.0-1.0>
43
- [/ekkOS_SELECT]
37
+ ekkOS_Forge({
38
+ title: "Short descriptive title",
39
+ problem: "What was broken or unclear",
40
+ solution: "What fixed it and why",
41
+ context: "Full reasoning, code snippets, debugging steps — this is what makes the pattern useful later",
42
+ tags: ["language", "framework", "domain"],
43
+ anti_patterns: ["what NOT to do"]
44
+ })
44
45
  ```
46
+ - `title`, `problem`, `solution` are required
47
+ - `context` is optional but high-value — include everything that would help a future model
45
48
 
46
- **For patterns NOT relevant:**
49
+ ### ekkOS_Outcome Report if a pattern worked
47
50
  ```
48
- [ekkOS_SKIP]
49
- - id: <pattern_id>
50
- reason: <why not relevant>
51
- [/ekkOS_SKIP]
51
+ ekkOS_Outcome({ success: true })
52
+ ekkOS_Outcome({ success: false, memory_ids: ["pattern-id"] })
52
53
  ```
54
+ - `success` (required): did the applied pattern solve the problem?
55
+ - `retrieval_token`: from the `ekkOS_Search` that found the pattern (most reliable)
56
+ - `memory_ids`: pattern IDs if you don't have the token
57
+ - This drives the learning loop — patterns with low success rates get refined or retired
53
58
 
54
- ### RULE 3: FORGE WHAT YOU LEARN
55
- When you fix a bug, get corrected, or learn something new, call `ekkOS_Forge` immediately.
56
- Failures are valuable forge anti-patterns too.
59
+ ### Directives Automatic preference capture
60
+ When you say "always", "never", "prefer", or "avoid", the proxy automatically creates a directive. Examples:
61
+ - "Always use TypeScript strict mode" → creates a MUST directive
62
+ - "Never use the any type" → creates a NEVER directive
63
+ - "Prefer functional components" → creates a PREFER directive
57
64
 
58
- ### RULE 4: CONTEXT CONTINUITY
65
+ Directives are injected into every future conversation automatically.
59
66
 
60
- **Post-clear restoration:** After `/clear`, when you see restored turns in `<system-reminder>`, confirm:
67
+ ### ekkOS_Recall Remember past conversations
61
68
  ```
62
- ✓ **Session continued (Turn N)** X turns preserved, context restored
69
+ ekkOS_Recall({ days_ago: 7, group_by_sessions: true, include_patterns: true })
70
+ ekkOS_Recall({ semantic_query: "that Redis timeout fix from last week" })
63
71
  ```
72
+ - `days_ago`: how far back to look (default: 1)
73
+ - `semantic_query`: search by meaning instead of time
74
+ - `extract_decisions`: pull out key decisions made
64
75
 
65
- **Auto-restore after compaction:** When you see `<ekkos-context-preserved>`:
66
- 1. Call `ekkOS_RestoreContext` with the `restoration_token` from the tag
67
- 2. Confirm: `✓ **Context restored** ekkOS preserved X turns before compaction`
68
-
69
- ---
70
-
71
- ## MCP Tools
76
+ ### ekkOS_Conflict Check before destructive actions
77
+ ```
78
+ ekkOS_Conflict({ proposed_action: "delete all test files" })
79
+ ```
80
+ - Call before deleting files, deploying, dropping tables, or any destructive operation
81
+ - Returns conflicts with user directives or patterns
72
82
 
73
- ### Core Memory
74
- | Tool | When to Use |
75
- |------|-------------|
76
- | `ekkOS_Search` | Before answering technical questions |
77
- | `ekkOS_Forge` | After fixing bugs, learning something new |
78
- | `ekkOS_Track` | When applying a retrieved pattern |
79
- | `ekkOS_Outcome` | When a pattern worked or failed |
80
-
81
- ### Directives
82
- | Tool | When to Use |
83
- |------|-------------|
84
- | `ekkOS_Directive` | User says "always" → MUST, "never" → NEVER, "prefer" → PREFER, "avoid" → AVOID |
85
- | `ekkOS_Conflict` | Before deleting files, deploying, destructive commands |
86
-
87
- ### Plans
88
- | Tool | When to Use |
89
- |------|-------------|
90
- | `ekkOS_Plan` | Task has 3+ steps |
91
- | `ekkOS_PlanStep` | Mark step complete |
92
-
93
- ### Secrets
94
- | Tool | When to Use |
95
- |------|-------------|
96
- | `ekkOS_StoreSecret` | User shares API key, token, password |
97
- | `ekkOS_GetSecret` | Need a stored credential |
98
-
99
- ### Context
100
- | Tool | When to Use |
101
- |------|-------------|
102
- | `ekkOS_RestoreContext` | See `<ekkos-context-preserved>` tag |
103
- | `ekkOS_Recall` | "Yesterday", "last week", "remember when..." |
104
-
105
- ---
106
-
107
- ## Response Format
108
-
109
- Every response ends with:
83
+ ### ekkOS_StoreSecret / ekkOS_GetSecret — Credential management
110
84
  ```
111
- ---
112
- {IDE} ({Model}) · 🧠 **ekkOS_™** · {SessionName} · 📅 {Timestamp}
85
+ ekkOS_StoreSecret({ service: "github", value: "ghp_xxx" })
86
+ ekkOS_GetSecret({ service: "github" })
113
87
  ```
88
+ - AES-256-GCM encrypted storage
89
+ - Call when user shares an API key, token, or password
90
+
91
+ ### Context Continuity
92
+ The proxy automatically preserves and restores your working memory during context compaction. When you see `<ekkos-context-preserved>`, your context has already been saved — no action needed.
93
+
94
+ ## ekkOS_CONTEXT.md — Living System Knowledge
95
+
96
+ Every system directory contains an auto-generated `ekkOS_CONTEXT.md` file. These stay current automatically when source files change.
97
+
98
+ **Read the nearest `ekkOS_CONTEXT.md` when entering any directory you're about to work in.** Walk up the tree for broader context.
99
+
100
+ **What they contain:** system architecture, dependencies, environment variables, file composition, activity status (`active` / `stale` / `dormant`), and recent git changes.
114
101
 
115
- - **Session name** comes from the `<ekkos-session>` tag
116
- - **Timestamp** from `<current-time>` tag or current date/time
102
+ **When to read them:**
103
+ - Before modifying code in a system you haven't touched yet
104
+ - When investigating a bug in an unfamiliar area
105
+ - `activity_status: dormant` (90+ days) = potential dead code — flag it
117
106
 
118
- ---
107
+ **DO NOT write to or edit `ekkOS_CONTEXT.md` files.** They are generated and maintained by a separate background process. Read them only.
119
108
 
120
- ## Quick Reference
109
+ ## Agents
121
110
 
122
- - Technical question `ekkOS_Search` first
123
- - Patterns retrieved → SELECT or SKIP each one
124
- - Problem solved → `ekkOS_Forge`
125
- - User preference → `ekkOS_Directive`
126
- - Destructive action → `ekkOS_Conflict` first
127
- - Store credentials → `ekkOS_StoreSecret`
111
+ ekkOS ships 4 agents you can delegate to:
128
112
 
129
- ## Documentation
113
+ | Agent | Trigger | What it does |
114
+ |-------|---------|--------------|
115
+ | **trace** | error, bug, broken, not working | Memory-first debugger — searches past failures before investigating, forges every fix |
116
+ | **scout** | onboard, what is this codebase | Scans a project, pulls collective knowledge, gives a briefing, forges initial patterns |
117
+ | **rewind** | retro, what did I do this week | Summarizes work, learnings, failures, and pattern stats over a time period |
118
+ | **prune** | clean up memory, prune, audit | Finds stale/conflicting/duplicate patterns, recommends merges and retirements |
130
119
 
131
- https://docs.ekkos.dev
120
+ Docs: https://docs.ekkos.dev
121
+ <!-- ekkOS:end -->