continuous-improvement 2.2.0 → 3.0.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.
@@ -0,0 +1,543 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * continuous-improvement MCP Server
5
+ *
6
+ * Exposes instincts, observations, and reflection as MCP tools + resources.
7
+ * Two modes: beginner (3 tools) and expert (all tools).
8
+ *
9
+ * Usage:
10
+ * node bin/mcp-server.mjs # default: beginner mode
11
+ * node bin/mcp-server.mjs --mode expert # all tools
12
+ * node bin/mcp-server.mjs --mode beginner # explicit beginner
13
+ */
14
+
15
+ import { existsSync, readFileSync, readdirSync, writeFileSync, mkdirSync } from "node:fs";
16
+ import { join, basename } from "node:path";
17
+ import { homedir } from "node:os";
18
+ import { execSync } from "node:child_process";
19
+ import { createInterface } from "node:readline";
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Config
23
+ // ---------------------------------------------------------------------------
24
+ const VERSION = "3.0.0";
25
+ const INSTINCTS_DIR = join(homedir(), ".claude", "instincts");
26
+ const GLOBAL_DIR = join(INSTINCTS_DIR, "global");
27
+
28
+ const args = process.argv.slice(2);
29
+ const modeIdx = args.indexOf("--mode");
30
+ const MODE = modeIdx !== -1 && args[modeIdx + 1] ? args[modeIdx + 1] : "beginner";
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Helpers
34
+ // ---------------------------------------------------------------------------
35
+
36
+ function getProjectHash() {
37
+ try {
38
+ const root = execSync("git rev-parse --show-toplevel 2>/dev/null", { encoding: "utf8" }).trim();
39
+ const hash = execSync(`printf '%s' "${root}" | sha256sum | cut -c1-12`, {
40
+ encoding: "utf8",
41
+ shell: "/bin/bash",
42
+ }).trim();
43
+ return { root, hash, name: basename(root) };
44
+ } catch {
45
+ return { root: "global", hash: "global", name: "global" };
46
+ }
47
+ }
48
+
49
+ function readInstincts(projectHash) {
50
+ const instincts = [];
51
+ const dirs = [GLOBAL_DIR];
52
+ if (projectHash !== "global") {
53
+ dirs.push(join(INSTINCTS_DIR, projectHash));
54
+ }
55
+
56
+ for (const dir of dirs) {
57
+ if (!existsSync(dir)) continue;
58
+ for (const file of readdirSync(dir)) {
59
+ if (!file.endsWith(".yaml")) continue;
60
+ try {
61
+ const content = readFileSync(join(dir, file), "utf8");
62
+ const parsed = parseYamlInstinct(content);
63
+ if (parsed) instincts.push(parsed);
64
+ } catch {
65
+ // skip malformed files
66
+ }
67
+ }
68
+ }
69
+ return instincts;
70
+ }
71
+
72
+ function parseYamlInstinct(content) {
73
+ const lines = content.split("\n");
74
+ const meta = {};
75
+ let body = "";
76
+ let inBody = false;
77
+
78
+ for (const line of lines) {
79
+ if (line.trim() === "---" && Object.keys(meta).length > 0) {
80
+ inBody = true;
81
+ continue;
82
+ }
83
+ if (inBody) {
84
+ body += line + "\n";
85
+ } else {
86
+ const match = line.match(/^(\w[\w_-]*):\s*(.+)/);
87
+ if (match) {
88
+ let val = match[2].trim().replace(/^["']|["']$/g, "");
89
+ if (!isNaN(val) && val !== "") val = parseFloat(val);
90
+ meta[match[1]] = val;
91
+ }
92
+ }
93
+ }
94
+
95
+ if (!meta.id) return null;
96
+ return { ...meta, body: body.trim() };
97
+ }
98
+
99
+ function countObservations(projectHash) {
100
+ const obsFile = join(INSTINCTS_DIR, projectHash, "observations.jsonl");
101
+ if (!existsSync(obsFile)) return 0;
102
+ try {
103
+ const content = readFileSync(obsFile, "utf8");
104
+ return content.split("\n").filter((l) => l.trim()).length;
105
+ } catch {
106
+ return 0;
107
+ }
108
+ }
109
+
110
+ function getRecentObservations(projectHash, limit = 50) {
111
+ const obsFile = join(INSTINCTS_DIR, projectHash, "observations.jsonl");
112
+ if (!existsSync(obsFile)) return [];
113
+ try {
114
+ const lines = readFileSync(obsFile, "utf8").split("\n").filter((l) => l.trim());
115
+ return lines.slice(-limit).map((l) => {
116
+ try { return JSON.parse(l); } catch { return null; }
117
+ }).filter(Boolean);
118
+ } catch {
119
+ return [];
120
+ }
121
+ }
122
+
123
+ function detectLevel(projectHash) {
124
+ const obsCount = countObservations(projectHash);
125
+ const instincts = readInstincts(projectHash);
126
+ const hasHighConfidence = instincts.some((i) => i.confidence >= 0.7);
127
+ const hasMidConfidence = instincts.some((i) => i.confidence >= 0.5 && i.confidence < 0.7);
128
+
129
+ if (hasHighConfidence) return "AUTO-APPLY";
130
+ if (hasMidConfidence) return "SUGGEST";
131
+ if (obsCount >= 20 || instincts.length > 0) return "ANALYZE";
132
+ return "CAPTURE";
133
+ }
134
+
135
+ function writeInstinct(projectHash, instinct) {
136
+ const dir = instinct.scope === "global" ? GLOBAL_DIR : join(INSTINCTS_DIR, projectHash);
137
+ mkdirSync(dir, { recursive: true });
138
+
139
+ const yaml = [
140
+ `id: ${instinct.id}`,
141
+ `trigger: "${instinct.trigger}"`,
142
+ `confidence: ${instinct.confidence}`,
143
+ `domain: ${instinct.domain || "workflow"}`,
144
+ `source: ${instinct.source || "manual"}`,
145
+ `scope: ${instinct.scope || "project"}`,
146
+ `project_id: ${projectHash}`,
147
+ `created: "${new Date().toISOString().split("T")[0]}"`,
148
+ `last_seen: "${new Date().toISOString().split("T")[0]}"`,
149
+ `observation_count: ${instinct.observation_count || 1}`,
150
+ "---",
151
+ instinct.body,
152
+ ].join("\n");
153
+
154
+ writeFileSync(join(dir, `${instinct.id}.yaml`), yaml + "\n");
155
+ }
156
+
157
+ function updateInstinctConfidence(projectHash, instinctId, delta) {
158
+ const instincts = readInstincts(projectHash);
159
+ const instinct = instincts.find((i) => i.id === instinctId);
160
+ if (!instinct) return null;
161
+
162
+ const newConf = Math.max(0, Math.min(0.9, (instinct.confidence || 0.5) + delta));
163
+ instinct.confidence = Math.round(newConf * 100) / 100;
164
+ instinct.last_seen = new Date().toISOString().split("T")[0];
165
+ writeInstinct(projectHash, instinct);
166
+ return instinct;
167
+ }
168
+
169
+ // ---------------------------------------------------------------------------
170
+ // MCP Protocol (JSON-RPC over stdio, no SDK dependency)
171
+ // ---------------------------------------------------------------------------
172
+
173
+ const BEGINNER_TOOLS = [
174
+ {
175
+ name: "ci_status",
176
+ description: "Show current level, instinct count, and observation count for this project. Good starting point to see what the system has learned.",
177
+ inputSchema: { type: "object", properties: {}, required: [] },
178
+ },
179
+ {
180
+ name: "ci_instincts",
181
+ description: "List all learned instincts for this project with their confidence levels and behaviors.",
182
+ inputSchema: {
183
+ type: "object",
184
+ properties: {
185
+ min_confidence: { type: "number", description: "Minimum confidence to show (default: 0)", default: 0 },
186
+ },
187
+ required: [],
188
+ },
189
+ },
190
+ {
191
+ name: "ci_reflect",
192
+ description: "Generate a structured reflection for the current session. Provide a summary of what you worked on.",
193
+ inputSchema: {
194
+ type: "object",
195
+ properties: {
196
+ summary: { type: "string", description: "Brief summary of what was done this session" },
197
+ },
198
+ required: ["summary"],
199
+ },
200
+ },
201
+ ];
202
+
203
+ const EXPERT_TOOLS = [
204
+ {
205
+ name: "ci_reinforce",
206
+ description: "Accept or reject an instinct suggestion. Adjusts confidence: +0.15 for accept, -0.1 for reject.",
207
+ inputSchema: {
208
+ type: "object",
209
+ properties: {
210
+ instinct_id: { type: "string", description: "The instinct ID to reinforce" },
211
+ accepted: { type: "boolean", description: "true = accept (+0.15), false = reject (-0.1)" },
212
+ },
213
+ required: ["instinct_id", "accepted"],
214
+ },
215
+ },
216
+ {
217
+ name: "ci_create_instinct",
218
+ description: "Manually create a new instinct with a trigger, body, and starting confidence.",
219
+ inputSchema: {
220
+ type: "object",
221
+ properties: {
222
+ id: { type: "string", description: "Unique instinct ID (kebab-case)" },
223
+ trigger: { type: "string", description: "When this instinct applies" },
224
+ body: { type: "string", description: "The behavior to follow" },
225
+ confidence: { type: "number", description: "Starting confidence 0.0-0.9 (default: 0.6)", default: 0.6 },
226
+ domain: { type: "string", description: "Domain: workflow|tooling|testing|patterns|code-style", default: "workflow" },
227
+ scope: { type: "string", description: "Scope: project|global", default: "project" },
228
+ },
229
+ required: ["id", "trigger", "body"],
230
+ },
231
+ },
232
+ {
233
+ name: "ci_observations",
234
+ description: "View recent tool call observations captured by hooks.",
235
+ inputSchema: {
236
+ type: "object",
237
+ properties: {
238
+ limit: { type: "number", description: "Number of recent observations to return (default: 20)", default: 20 },
239
+ },
240
+ required: [],
241
+ },
242
+ },
243
+ {
244
+ name: "ci_export",
245
+ description: "Export all instincts as a JSON array for sharing or backup.",
246
+ inputSchema: {
247
+ type: "object",
248
+ properties: {
249
+ scope: { type: "string", description: "Which instincts: project|global|all (default: all)", default: "all" },
250
+ },
251
+ required: [],
252
+ },
253
+ },
254
+ {
255
+ name: "ci_import",
256
+ description: "Import instincts from a JSON array. Skips duplicates by ID.",
257
+ inputSchema: {
258
+ type: "object",
259
+ properties: {
260
+ instincts_json: { type: "string", description: "JSON array of instinct objects to import" },
261
+ scope: { type: "string", description: "Import to: project|global (default: project)", default: "project" },
262
+ },
263
+ required: ["instincts_json"],
264
+ },
265
+ },
266
+ ];
267
+
268
+ function getAllTools() {
269
+ if (MODE === "expert") return [...BEGINNER_TOOLS, ...EXPERT_TOOLS];
270
+ return BEGINNER_TOOLS;
271
+ }
272
+
273
+ // ---------------------------------------------------------------------------
274
+ // Tool handlers
275
+ // ---------------------------------------------------------------------------
276
+
277
+ function handleTool(name, params) {
278
+ const project = getProjectHash();
279
+
280
+ switch (name) {
281
+ case "ci_status": {
282
+ const level = detectLevel(project.hash);
283
+ const obsCount = countObservations(project.hash);
284
+ const instincts = readInstincts(project.hash);
285
+ const byConfidence = {
286
+ silent: instincts.filter((i) => i.confidence < 0.5).length,
287
+ suggest: instincts.filter((i) => i.confidence >= 0.5 && i.confidence < 0.7).length,
288
+ autoApply: instincts.filter((i) => i.confidence >= 0.7).length,
289
+ };
290
+
291
+ return text([
292
+ `## continuous-improvement Status`,
293
+ ``,
294
+ `**Project:** ${project.name}`,
295
+ `**Level:** ${level}`,
296
+ `**Observations:** ${obsCount}`,
297
+ `**Instincts:** ${instincts.length} total`,
298
+ ` - Silent (< 0.5): ${byConfidence.silent}`,
299
+ ` - Suggest (0.5-0.69): ${byConfidence.suggest}`,
300
+ ` - Auto-apply (0.7+): ${byConfidence.autoApply}`,
301
+ ``,
302
+ `**Mode:** ${MODE}`,
303
+ level === "CAPTURE" ? `\n_Keep working — hooks are capturing. Analysis begins at 20 observations._` : "",
304
+ ].join("\n"));
305
+ }
306
+
307
+ case "ci_instincts": {
308
+ const minConf = params.min_confidence || 0;
309
+ const instincts = readInstincts(project.hash).filter((i) => i.confidence >= minConf);
310
+
311
+ if (instincts.length === 0) return text("No instincts found. Keep working — the system learns from your sessions.");
312
+
313
+ const lines = instincts
314
+ .sort((a, b) => b.confidence - a.confidence)
315
+ .map((i) => {
316
+ const behavior = i.confidence >= 0.7 ? "AUTO-APPLY" : i.confidence >= 0.5 ? "SUGGEST" : "silent";
317
+ return `- **${i.id}** (${i.confidence}) [${behavior}]\n Trigger: ${i.trigger}\n ${i.body}`;
318
+ });
319
+
320
+ return text(`## Instincts for ${project.name}\n\n${lines.join("\n\n")}`);
321
+ }
322
+
323
+ case "ci_reflect": {
324
+ const summary = params.summary || "No summary provided";
325
+ const reflection = [
326
+ `## Reflection — ${new Date().toISOString().split("T")[0]}`,
327
+ ``,
328
+ `**Session summary:** ${summary}`,
329
+ ``,
330
+ `Use this template to reflect:`,
331
+ `- **What worked:**`,
332
+ `- **What failed:**`,
333
+ `- **What I'd do differently:**`,
334
+ `- **Rule to add:** (becomes an instinct at 0.6 confidence)`,
335
+ ].join("\n");
336
+
337
+ return text(reflection);
338
+ }
339
+
340
+ case "ci_reinforce": {
341
+ if (MODE !== "expert") return error("ci_reinforce requires expert mode. Start server with --mode expert");
342
+ const delta = params.accepted ? 0.15 : -0.1;
343
+ const updated = updateInstinctConfidence(project.hash, params.instinct_id, delta);
344
+ if (!updated) return error(`Instinct "${params.instinct_id}" not found`);
345
+ return text(`${params.accepted ? "Accepted" : "Rejected"} **${updated.id}** — confidence now ${updated.confidence}`);
346
+ }
347
+
348
+ case "ci_create_instinct": {
349
+ if (MODE !== "expert") return error("ci_create_instinct requires expert mode");
350
+ writeInstinct(project.hash, {
351
+ id: params.id,
352
+ trigger: params.trigger,
353
+ body: params.body,
354
+ confidence: params.confidence || 0.6,
355
+ domain: params.domain || "workflow",
356
+ source: "manual",
357
+ scope: params.scope || "project",
358
+ observation_count: 1,
359
+ });
360
+ return text(`Created instinct **${params.id}** with confidence ${params.confidence || 0.6}`);
361
+ }
362
+
363
+ case "ci_observations": {
364
+ if (MODE !== "expert") return error("ci_observations requires expert mode");
365
+ const limit = params.limit || 20;
366
+ const obs = getRecentObservations(project.hash, limit);
367
+ if (obs.length === 0) return text("No observations yet. Hooks capture tool calls automatically.");
368
+ const lines = obs.map((o) => `[${o.ts}] ${o.event} — ${o.tool}`);
369
+ return text(`## Recent Observations (${obs.length})\n\n${lines.join("\n")}`);
370
+ }
371
+
372
+ case "ci_export": {
373
+ if (MODE !== "expert") return error("ci_export requires expert mode");
374
+ const scope = params.scope || "all";
375
+ let instincts = readInstincts(project.hash);
376
+ if (scope === "project") instincts = instincts.filter((i) => i.scope === "project");
377
+ if (scope === "global") instincts = instincts.filter((i) => i.scope === "global");
378
+ return text(JSON.stringify(instincts, null, 2));
379
+ }
380
+
381
+ case "ci_import": {
382
+ if (MODE !== "expert") return error("ci_import requires expert mode");
383
+ let toImport;
384
+ try {
385
+ toImport = JSON.parse(params.instincts_json);
386
+ } catch {
387
+ return error("Invalid JSON. Provide a JSON array of instinct objects.");
388
+ }
389
+ if (!Array.isArray(toImport)) return error("Expected a JSON array");
390
+
391
+ const existing = readInstincts(project.hash);
392
+ const existingIds = new Set(existing.map((i) => i.id));
393
+ let imported = 0;
394
+
395
+ for (const inst of toImport) {
396
+ if (!inst.id || !inst.trigger || !inst.body) continue;
397
+ if (existingIds.has(inst.id)) continue;
398
+ writeInstinct(project.hash, {
399
+ ...inst,
400
+ scope: params.scope || inst.scope || "project",
401
+ source: "imported",
402
+ });
403
+ imported++;
404
+ }
405
+
406
+ return text(`Imported ${imported} instincts (${toImport.length - imported} skipped as duplicates)`);
407
+ }
408
+
409
+ default:
410
+ return error(`Unknown tool: ${name}`);
411
+ }
412
+ }
413
+
414
+ function text(t) {
415
+ return { content: [{ type: "text", text: t }] };
416
+ }
417
+
418
+ function error(t) {
419
+ return { content: [{ type: "text", text: t }], isError: true };
420
+ }
421
+
422
+ // ---------------------------------------------------------------------------
423
+ // JSON-RPC stdio transport (zero dependencies)
424
+ // ---------------------------------------------------------------------------
425
+
426
+ const rl = createInterface({ input: process.stdin, terminal: false });
427
+ let buffer = "";
428
+
429
+ rl.on("line", (line) => {
430
+ buffer += line;
431
+ try {
432
+ const msg = JSON.parse(buffer);
433
+ buffer = "";
434
+ handleMessage(msg);
435
+ } catch {
436
+ // incomplete JSON, keep buffering
437
+ }
438
+ });
439
+
440
+ function send(response) {
441
+ const json = JSON.stringify(response);
442
+ process.stdout.write(`Content-Length: ${Buffer.byteLength(json)}\r\n\r\n${json}`);
443
+ }
444
+
445
+ function handleMessage(msg) {
446
+ const { id, method, params } = msg;
447
+
448
+ switch (method) {
449
+ case "initialize":
450
+ send({
451
+ jsonrpc: "2.0",
452
+ id,
453
+ result: {
454
+ protocolVersion: "2024-11-05",
455
+ capabilities: {
456
+ tools: { listChanged: false },
457
+ resources: { subscribe: false, listChanged: false },
458
+ },
459
+ serverInfo: {
460
+ name: "continuous-improvement",
461
+ version: VERSION,
462
+ },
463
+ },
464
+ });
465
+ break;
466
+
467
+ case "notifications/initialized":
468
+ // no response needed
469
+ break;
470
+
471
+ case "tools/list":
472
+ send({
473
+ jsonrpc: "2.0",
474
+ id,
475
+ result: { tools: getAllTools() },
476
+ });
477
+ break;
478
+
479
+ case "tools/call": {
480
+ const result = handleTool(params.name, params.arguments || {});
481
+ send({ jsonrpc: "2.0", id, result });
482
+ break;
483
+ }
484
+
485
+ case "resources/list": {
486
+ const project = getProjectHash();
487
+ const resources = [
488
+ {
489
+ uri: `instincts://project/${project.hash}`,
490
+ name: `${project.name} instincts`,
491
+ description: `Learned instincts for ${project.name}`,
492
+ mimeType: "application/json",
493
+ },
494
+ {
495
+ uri: "instincts://global",
496
+ name: "Global instincts",
497
+ description: "Cross-project instincts",
498
+ mimeType: "application/json",
499
+ },
500
+ ];
501
+ send({ jsonrpc: "2.0", id, result: { resources } });
502
+ break;
503
+ }
504
+
505
+ case "resources/read": {
506
+ const uri = params.uri;
507
+ const project = getProjectHash();
508
+ let instincts;
509
+
510
+ if (uri === "instincts://global") {
511
+ instincts = readInstincts("global").filter((i) => i.scope === "global");
512
+ } else {
513
+ instincts = readInstincts(project.hash);
514
+ }
515
+
516
+ send({
517
+ jsonrpc: "2.0",
518
+ id,
519
+ result: {
520
+ contents: [
521
+ {
522
+ uri,
523
+ text: JSON.stringify(instincts, null, 2),
524
+ mimeType: "application/json",
525
+ },
526
+ ],
527
+ },
528
+ });
529
+ break;
530
+ }
531
+
532
+ default:
533
+ if (id) {
534
+ send({
535
+ jsonrpc: "2.0",
536
+ id,
537
+ error: { code: -32601, message: `Method not found: ${method}` },
538
+ });
539
+ }
540
+ }
541
+ }
542
+
543
+ console.error(`continuous-improvement MCP server v${VERSION} started (mode: ${MODE})`);
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env bash
2
+ # session.sh — SessionStart/SessionEnd hook for continuous-improvement
3
+ # SessionStart: loads instincts and prints status
4
+ # SessionEnd: prompts reflection
5
+ # Always exits 0 — never blocks the session
6
+ trap 'exit 0' EXIT ERR INT TERM
7
+
8
+ INSTINCTS_DIR="${HOME}/.claude/instincts"
9
+
10
+ # Read stdin
11
+ INPUT="$(cat)"
12
+ [[ -z "$INPUT" ]] && exit 0
13
+
14
+ # ---------------------------------------------------------------------------
15
+ # Detect event type from hook context
16
+ # ---------------------------------------------------------------------------
17
+ # SessionStart hooks receive no tool_name, SessionEnd hooks may vary.
18
+ # We detect based on the hook_type field or the calling context.
19
+ EVENT_TYPE=""
20
+ if command -v jq &>/dev/null; then
21
+ EVENT_TYPE="$(printf '%s' "$INPUT" | jq -r '.hook_type // .event_type // "unknown"' 2>/dev/null)"
22
+ else
23
+ if printf '%s' "$INPUT" | grep -q '"SessionStart"'; then
24
+ EVENT_TYPE="SessionStart"
25
+ elif printf '%s' "$INPUT" | grep -q '"SessionEnd"'; then
26
+ EVENT_TYPE="SessionEnd"
27
+ fi
28
+ fi
29
+
30
+ # ---------------------------------------------------------------------------
31
+ # Project detection (same as observe.sh)
32
+ # ---------------------------------------------------------------------------
33
+ PROJECT_ROOT=""
34
+ if [[ -n "${CLAUDE_PROJECT_DIR:-}" && -d "${CLAUDE_PROJECT_DIR}" ]]; then
35
+ PROJECT_ROOT="${CLAUDE_PROJECT_DIR}"
36
+ fi
37
+ if [[ -z "$PROJECT_ROOT" ]]; then
38
+ PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || true)"
39
+ fi
40
+ if [[ -z "$PROJECT_ROOT" ]]; then
41
+ PROJECT_ROOT="global"
42
+ fi
43
+
44
+ PROJECT_HASH="$(printf '%s' "$PROJECT_ROOT" | sha256sum | cut -c1-12)"
45
+ PROJECT_DIR="${INSTINCTS_DIR}/${PROJECT_HASH}"
46
+
47
+ # ---------------------------------------------------------------------------
48
+ # SessionStart: count observations and instincts, print brief status
49
+ # ---------------------------------------------------------------------------
50
+ if [[ "$EVENT_TYPE" == "SessionStart" || "$EVENT_TYPE" == "unknown" ]]; then
51
+ OBS_COUNT=0
52
+ INSTINCT_COUNT=0
53
+
54
+ OBS_FILE="${PROJECT_DIR}/observations.jsonl"
55
+ if [[ -f "$OBS_FILE" ]]; then
56
+ OBS_COUNT="$(wc -l < "$OBS_FILE" 2>/dev/null || echo 0)"
57
+ fi
58
+
59
+ # Count yaml files in project + global
60
+ for dir in "$PROJECT_DIR" "${INSTINCTS_DIR}/global"; do
61
+ if [[ -d "$dir" ]]; then
62
+ count="$(find "$dir" -maxdepth 1 -name '*.yaml' 2>/dev/null | wc -l)"
63
+ INSTINCT_COUNT=$((INSTINCT_COUNT + count))
64
+ fi
65
+ done
66
+
67
+ # Determine level
68
+ LEVEL="CAPTURE"
69
+ if (( OBS_COUNT >= 20 )) || (( INSTINCT_COUNT > 0 )); then
70
+ LEVEL="ANALYZE"
71
+ fi
72
+
73
+ # Check for high-confidence instincts
74
+ if (( INSTINCT_COUNT > 0 )); then
75
+ for dir in "$PROJECT_DIR" "${INSTINCTS_DIR}/global"; do
76
+ if [[ -d "$dir" ]]; then
77
+ for f in "$dir"/*.yaml; do
78
+ [[ -f "$f" ]] || continue
79
+ conf="$(grep '^confidence:' "$f" 2>/dev/null | head -1 | sed 's/confidence: *//')"
80
+ if [[ -n "$conf" ]]; then
81
+ # Compare as integer (multiply by 100)
82
+ int_conf="$(printf '%.0f' "$(echo "$conf * 100" | bc 2>/dev/null || echo 0)")"
83
+ if (( int_conf >= 70 )); then
84
+ LEVEL="AUTO-APPLY"
85
+ break 2
86
+ elif (( int_conf >= 50 )); then
87
+ LEVEL="SUGGEST"
88
+ fi
89
+ fi
90
+ done
91
+ fi
92
+ done
93
+ fi
94
+
95
+ # Write status to stderr (visible in hook output, not blocking)
96
+ echo "[continuous-improvement] Level: ${LEVEL} | Observations: ${OBS_COUNT} | Instincts: ${INSTINCT_COUNT}" >&2
97
+ fi
98
+
99
+ # ---------------------------------------------------------------------------
100
+ # SessionEnd: remind to reflect
101
+ # ---------------------------------------------------------------------------
102
+ if [[ "$EVENT_TYPE" == "SessionEnd" ]]; then
103
+ echo "[continuous-improvement] Session ending. Run /continuous-improvement to reflect and capture learnings." >&2
104
+ fi
105
+
106
+ exit 0
package/package.json CHANGED
@@ -1,21 +1,26 @@
1
1
  {
2
2
  "name": "continuous-improvement",
3
- "version": "2.2.0",
4
- "description": "7-law discipline framework with auto-leveling instinct learning for AI agents research, plan, execute, verify, reflect, learn, iterate",
3
+ "version": "3.0.0",
4
+ "description": "The 7 Laws of AI Agent Discipline — stop your agent from skipping steps, guessing, and declaring 'done' without verifying. Auto-leveling instinct learning with MCP server plugin for Claude Code, Cursor, Codex, Gemini CLI.",
5
5
  "keywords": [
6
- "ai-agent",
7
6
  "claude-code",
7
+ "claude-code-skill",
8
+ "ai-agent",
9
+ "agent-skill",
8
10
  "codex",
9
- "openclaw",
10
11
  "cursor",
11
- "skill",
12
- "continuous-improvement",
12
+ "gemini-cli",
13
+ "ai-discipline",
13
14
  "workflow",
14
15
  "productivity",
15
16
  "mulahazah",
16
17
  "instinct",
17
18
  "learning",
18
- "hooks"
19
+ "hooks",
20
+ "continuous-improvement",
21
+ "mcp",
22
+ "mcp-server",
23
+ "plugin"
19
24
  ],
20
25
  "author": "naimkatiman",
21
26
  "license": "MIT",
@@ -27,6 +32,9 @@
27
32
  "bin": {
28
33
  "continuous-improvement": "./bin/install.mjs"
29
34
  },
35
+ "scripts": {
36
+ "test": "node --test test/*.test.mjs"
37
+ },
30
38
  "files": [
31
39
  "SKILL.md",
32
40
  "QUICKSTART.md",
@@ -34,7 +42,8 @@
34
42
  "README.md",
35
43
  "bin/",
36
44
  "hooks/",
37
- "commands/"
45
+ "commands/",
46
+ "plugins/"
38
47
  ],
39
48
  "type": "module"
40
49
  }