@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.
- package/dist/commands/dashboard.js +67 -11
- package/dist/commands/gemini.js +34 -8
- package/dist/commands/init.js +92 -21
- package/dist/commands/living-docs.d.ts +8 -0
- package/dist/commands/living-docs.js +66 -0
- package/dist/commands/run.js +55 -6
- package/dist/commands/scan.d.ts +68 -0
- package/dist/commands/scan.js +318 -22
- package/dist/commands/setup.js +2 -6
- package/dist/deploy/index.d.ts +1 -0
- package/dist/deploy/index.js +1 -0
- package/dist/deploy/instructions.d.ts +10 -3
- package/dist/deploy/instructions.js +34 -7
- package/dist/index.js +91 -53
- package/dist/lib/usage-parser.js +18 -2
- package/dist/local/index.d.ts +4 -0
- package/dist/local/index.js +14 -1
- package/dist/local/language-config.d.ts +55 -0
- package/dist/local/language-config.js +729 -0
- package/dist/local/living-docs-manager.d.ts +59 -0
- package/dist/local/living-docs-manager.js +1084 -0
- package/dist/local/stack-detection.d.ts +21 -0
- package/dist/local/stack-detection.js +406 -0
- package/package.json +10 -7
- package/templates/CLAUDE.md +89 -99
- package/LICENSE +0 -21
|
@@ -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.
|
|
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
|
-
|
|
56
|
-
"build": "tsc",
|
|
57
|
-
"dev": "tsx src/index.ts"
|
|
58
|
-
}
|
|
59
|
-
}
|
|
61
|
+
]
|
|
62
|
+
}
|
package/templates/CLAUDE.md
CHANGED
|
@@ -1,131 +1,121 @@
|
|
|
1
|
-
|
|
1
|
+
<!-- ekkOS:begin — managed by ekkos init, do not remove this marker -->
|
|
2
|
+
# ekkOS Memory
|
|
2
3
|
|
|
3
|
-
|
|
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
|
|
8
|
+
The **ekkOS proxy** transparently enriches conversations. It automatically:
|
|
14
9
|
|
|
15
|
-
- Injects **relevant patterns**
|
|
16
|
-
- Injects **your directives** (MUST/NEVER/PREFER/AVOID rules)
|
|
17
|
-
- Injects **episodic recall** when you reference past conversations
|
|
18
|
-
-
|
|
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
|
|
16
|
+
You also have MCP tools for explicit memory operations.
|
|
23
17
|
|
|
24
|
-
|
|
18
|
+
## Rules
|
|
25
19
|
|
|
26
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
35
|
+
### ekkOS_Forge — Save what you learned
|
|
38
36
|
```
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
49
|
+
### ekkOS_Outcome — Report if a pattern worked
|
|
47
50
|
```
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
###
|
|
55
|
-
When you
|
|
56
|
-
|
|
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
|
-
|
|
65
|
+
Directives are injected into every future conversation automatically.
|
|
59
66
|
|
|
60
|
-
|
|
67
|
+
### ekkOS_Recall — Remember past conversations
|
|
61
68
|
```
|
|
62
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
116
|
-
-
|
|
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
|
-
##
|
|
109
|
+
## Agents
|
|
121
110
|
|
|
122
|
-
|
|
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
|
-
|
|
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 -->
|