continuous-improvement 3.0.0 → 3.8.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.
Files changed (126) hide show
  1. package/.claude-plugin/marketplace.json +78 -0
  2. package/CHANGELOG.md +210 -0
  3. package/LICENSE +21 -21
  4. package/QUICKSTART.md +101 -81
  5. package/README.md +224 -198
  6. package/SKILL.md +87 -9
  7. package/action.yml +33 -0
  8. package/bin/analyze.sh +161 -153
  9. package/bin/backfill.mjs +172 -0
  10. package/bin/check-docs-substrings.mjs +333 -0
  11. package/bin/check-everything-mirror.mjs +145 -0
  12. package/bin/check-routing-targets.mjs +151 -0
  13. package/bin/check-skill-law-tag.mjs +128 -0
  14. package/bin/check-skill-mirror.mjs +119 -0
  15. package/bin/check-skill-tiers.mjs +116 -0
  16. package/bin/check-third-party-shape.mjs +202 -0
  17. package/bin/generate-plugin-manifests.mjs +169 -0
  18. package/bin/harvest-friction.mjs +279 -0
  19. package/bin/hook-stats.mjs +258 -0
  20. package/bin/install.mjs +418 -456
  21. package/bin/lint-transcript.mjs +239 -0
  22. package/bin/mcp-server.mjs +842 -499
  23. package/bin/observe.mjs +148 -0
  24. package/bin/pre-commit-block-strays.sh +49 -0
  25. package/bin/refresh-third-party.mjs +416 -0
  26. package/bin/unified-cli.mjs +533 -0
  27. package/commands/continuous-improvement.md +115 -74
  28. package/commands/dashboard.md +56 -0
  29. package/commands/discipline.md +51 -0
  30. package/commands/harvest.md +76 -0
  31. package/commands/learn-eval.md +117 -0
  32. package/commands/planning-with-files.md +66 -0
  33. package/commands/proceed-with-the-recommendation.md +62 -0
  34. package/commands/ralph.md +103 -0
  35. package/commands/release-train.md +81 -0
  36. package/commands/seven-laws.md +16 -0
  37. package/commands/superpowers.md +153 -0
  38. package/commands/swarm.md +101 -0
  39. package/commands/workspace-surface-audit.md +77 -0
  40. package/hooks/observe.sh +172 -134
  41. package/hooks/session.sh +106 -106
  42. package/hooks/three-section-close.mjs +181 -0
  43. package/instinct-packs/go.json +58 -0
  44. package/instinct-packs/meta.json +16 -0
  45. package/instinct-packs/python.json +58 -0
  46. package/instinct-packs/react.json +58 -0
  47. package/lib/cli-anything.mjs +401 -0
  48. package/lib/compound-engineering.mjs +831 -0
  49. package/lib/observe-event.mjs +128 -0
  50. package/lib/plugin-metadata.mjs +432 -0
  51. package/lib/pm-marketplace.mjs +61 -0
  52. package/lib/pm-skills.mjs +1274 -0
  53. package/lib/resolve-home-dir.mjs +43 -0
  54. package/lib/skill-tiers.mjs +137 -0
  55. package/lib/unified-plugin.mjs +924 -0
  56. package/llms.txt +68 -0
  57. package/package.json +38 -15
  58. package/plugins/beginner.json +17 -6
  59. package/plugins/continuous-improvement/.claude-plugin/marketplace.json +20 -0
  60. package/plugins/continuous-improvement/.claude-plugin/plugin.json +26 -0
  61. package/plugins/continuous-improvement/LICENSE +21 -0
  62. package/plugins/continuous-improvement/README.md +56 -0
  63. package/plugins/continuous-improvement/bin/backfill.mjs +172 -0
  64. package/plugins/continuous-improvement/bin/mcp-server.mjs +886 -0
  65. package/plugins/continuous-improvement/bin/observe.mjs +148 -0
  66. package/plugins/continuous-improvement/commands/continuous-improvement.md +115 -0
  67. package/plugins/continuous-improvement/commands/dashboard.md +56 -0
  68. package/plugins/continuous-improvement/commands/discipline.md +51 -0
  69. package/plugins/continuous-improvement/commands/harvest.md +76 -0
  70. package/plugins/continuous-improvement/commands/learn-eval.md +117 -0
  71. package/plugins/continuous-improvement/commands/planning-with-files.md +66 -0
  72. package/plugins/continuous-improvement/commands/proceed-with-the-recommendation.md +62 -0
  73. package/plugins/continuous-improvement/commands/ralph.md +103 -0
  74. package/plugins/continuous-improvement/commands/release-train.md +81 -0
  75. package/plugins/continuous-improvement/commands/seven-laws.md +16 -0
  76. package/plugins/continuous-improvement/commands/superpowers.md +153 -0
  77. package/plugins/continuous-improvement/commands/swarm.md +101 -0
  78. package/plugins/continuous-improvement/commands/workspace-surface-audit.md +77 -0
  79. package/plugins/continuous-improvement/hooks/hooks.json +60 -0
  80. package/plugins/continuous-improvement/hooks/observe.sh +172 -0
  81. package/plugins/continuous-improvement/hooks/session.sh +106 -0
  82. package/plugins/continuous-improvement/hooks/three-section-close.mjs +181 -0
  83. package/plugins/continuous-improvement/instinct-packs/go.json +58 -0
  84. package/plugins/continuous-improvement/instinct-packs/meta.json +16 -0
  85. package/plugins/continuous-improvement/instinct-packs/python.json +58 -0
  86. package/plugins/continuous-improvement/instinct-packs/react.json +58 -0
  87. package/plugins/continuous-improvement/lib/observe-event.mjs +128 -0
  88. package/plugins/continuous-improvement/lib/plugin-metadata.mjs +432 -0
  89. package/plugins/continuous-improvement/lib/resolve-home-dir.mjs +43 -0
  90. package/plugins/continuous-improvement/skills/README.md +34 -0
  91. package/plugins/continuous-improvement/skills/continuous-improvement/SKILL.md +249 -0
  92. package/plugins/continuous-improvement/skills/deploy-receipt/SKILL.md +131 -0
  93. package/plugins/continuous-improvement/skills/gateguard/SKILL.md +155 -0
  94. package/plugins/continuous-improvement/skills/para-memory-files/SKILL.md +108 -0
  95. package/plugins/continuous-improvement/skills/proceed-with-the-recommendation/SKILL.md +454 -0
  96. package/plugins/continuous-improvement/skills/ralph/SKILL.md +221 -0
  97. package/plugins/continuous-improvement/skills/safety-guard/SKILL.md +76 -0
  98. package/plugins/continuous-improvement/skills/strategic-compact/SKILL.md +104 -0
  99. package/plugins/continuous-improvement/skills/superpowers/SKILL.md +212 -0
  100. package/plugins/continuous-improvement/skills/tdd-workflow/SKILL.md +411 -0
  101. package/plugins/continuous-improvement/skills/token-budget-advisor/SKILL.md +136 -0
  102. package/plugins/continuous-improvement/skills/verification-loop/SKILL.md +192 -0
  103. package/plugins/continuous-improvement/skills/wild-risa-balance/SKILL.md +191 -0
  104. package/plugins/continuous-improvement/skills/workspace-surface-audit/SKILL.md +147 -0
  105. package/plugins/continuous-improvement/templates/planning-with-files/findings.md +8 -0
  106. package/plugins/continuous-improvement/templates/planning-with-files/progress.md +7 -0
  107. package/plugins/continuous-improvement/templates/planning-with-files/task_plan.md +23 -0
  108. package/plugins/expert.json +34 -5
  109. package/skills/README.md +79 -0
  110. package/skills/deploy-receipt.md +131 -0
  111. package/skills/gateguard.md +155 -0
  112. package/skills/para-memory-files.md +108 -0
  113. package/skills/proceed-with-the-recommendation.md +454 -0
  114. package/skills/ralph.md +221 -0
  115. package/skills/safety-guard.md +76 -0
  116. package/skills/strategic-compact.md +104 -0
  117. package/skills/superpowers.md +212 -0
  118. package/skills/tdd-workflow.md +411 -0
  119. package/skills/token-budget-advisor.md +136 -0
  120. package/skills/verification-loop.md +192 -0
  121. package/skills/wild-risa-balance.md +191 -0
  122. package/skills/workspace-surface-audit.md +147 -0
  123. package/templates/planning-with-files/findings.md +8 -0
  124. package/templates/planning-with-files/progress.md +7 -0
  125. package/templates/planning-with-files/task_plan.md +23 -0
  126. package/templates/verify-ladder.example.json +47 -0
@@ -0,0 +1,886 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * continuous-improvement MCP Server
4
+ *
5
+ * Exposes instincts, observations, and reflection as MCP tools + resources.
6
+ * Two modes: beginner (3 tools) and expert (all tools).
7
+ *
8
+ * Usage:
9
+ * node bin/mcp-server.mjs # default: beginner mode
10
+ * node bin/mcp-server.mjs --mode expert # all tools
11
+ * node bin/mcp-server.mjs --mode beginner # explicit beginner
12
+ */
13
+ import { execSync } from "node:child_process";
14
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
15
+ import { homedir } from "node:os";
16
+ import { basename, dirname, join } from "node:path";
17
+ import { createInterface } from "node:readline";
18
+ import { fileURLToPath } from "node:url";
19
+ import { createHash } from "node:crypto";
20
+ import { PACKAGE_NAME, VERSION, getToolDefinitions, isPluginMode, } from "../lib/plugin-metadata.mjs";
21
+ function getHomeDir() {
22
+ return process.env.HOME || process.env.USERPROFILE || homedir();
23
+ }
24
+ function asRecord(value) {
25
+ return value && typeof value === "object" && !Array.isArray(value)
26
+ ? value
27
+ : null;
28
+ }
29
+ function getString(value, fallback = "") {
30
+ return typeof value === "string" ? value : fallback;
31
+ }
32
+ function getNumber(value, fallback = 0) {
33
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
34
+ }
35
+ function getBoolean(value, fallback = false) {
36
+ return typeof value === "boolean" ? value : fallback;
37
+ }
38
+ function toInstinct(value) {
39
+ const record = asRecord(value);
40
+ if (!record) {
41
+ return null;
42
+ }
43
+ const id = getString(record.id);
44
+ const trigger = getString(record.trigger);
45
+ const body = getString(record.body);
46
+ if (!id || !trigger || !body) {
47
+ return null;
48
+ }
49
+ let confidence = getNumber(record.confidence, 0.6);
50
+ // Validate confidence bounds (0.0 - 0.9)
51
+ confidence = Math.max(0, Math.min(0.9, confidence));
52
+ const scopeValue = getString(record.scope);
53
+ const scope = scopeValue === "global" || scopeValue === "project"
54
+ ? scopeValue
55
+ : undefined;
56
+ return {
57
+ id,
58
+ trigger,
59
+ body,
60
+ confidence,
61
+ domain: getString(record.domain) || undefined,
62
+ source: getString(record.source) || undefined,
63
+ scope,
64
+ project_id: getString(record.project_id) || undefined,
65
+ created: getString(record.created) || undefined,
66
+ last_seen: getString(record.last_seen) || undefined,
67
+ observation_count: typeof record.observation_count === "number" ? record.observation_count : undefined,
68
+ };
69
+ }
70
+ const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
71
+ const INSTINCTS_DIR = join(getHomeDir(), ".claude", "instincts");
72
+ const GLOBAL_DIR = join(INSTINCTS_DIR, "global");
73
+ const PACKS_DIR = join(REPO_ROOT, "instinct-packs");
74
+ const PLANNING_TEMPLATES_DIR = join(REPO_ROOT, "templates", "planning-with-files");
75
+ const DEFAULT_PLANNING_PHASES = ["Research", "Plan", "Execute", "Verify", "Reflect"];
76
+ const PLANNING_FILES = {
77
+ taskPlan: "task_plan.md",
78
+ findings: "findings.md",
79
+ progress: "progress.md",
80
+ };
81
+ const args = process.argv.slice(2);
82
+ const modeIndex = args.indexOf("--mode");
83
+ const requestedMode = args[modeIndex + 1];
84
+ const MODE = isPluginMode(requestedMode) ? requestedMode : "beginner";
85
+ function getProjectHash() {
86
+ try {
87
+ const root = execSync("git rev-parse --show-toplevel 2>/dev/null", { encoding: "utf8" }).trim();
88
+ const hash = createHash("sha256").update(root).digest("hex").slice(0, 12);
89
+ return { root, hash, name: basename(root) };
90
+ }
91
+ catch {
92
+ return { root: "global", hash: "global", name: "global" };
93
+ }
94
+ }
95
+ function readInstincts(projectHash) {
96
+ const instincts = [];
97
+ const dirs = [GLOBAL_DIR];
98
+ if (projectHash !== "global") {
99
+ dirs.push(join(INSTINCTS_DIR, projectHash));
100
+ }
101
+ for (const dir of dirs) {
102
+ if (!existsSync(dir)) {
103
+ continue;
104
+ }
105
+ for (const file of readdirSync(dir)) {
106
+ if (!file.endsWith(".yaml")) {
107
+ continue;
108
+ }
109
+ try {
110
+ const parsed = parseYamlInstinct(readFileSync(join(dir, file), "utf8"));
111
+ if (parsed) {
112
+ instincts.push(parsed);
113
+ }
114
+ }
115
+ catch {
116
+ // skip malformed files
117
+ }
118
+ }
119
+ }
120
+ return instincts;
121
+ }
122
+ function parseYamlInstinct(content) {
123
+ const lines = content.split("\n");
124
+ const meta = {};
125
+ let body = "";
126
+ let inBody = false;
127
+ for (const line of lines) {
128
+ if (line.trim() === "---" && Object.keys(meta).length > 0) {
129
+ inBody = true;
130
+ continue;
131
+ }
132
+ if (inBody) {
133
+ body += `${line}\n`;
134
+ continue;
135
+ }
136
+ const match = line.match(/^(\w[\w_-]*):\s*(.+)/);
137
+ if (!match) {
138
+ continue;
139
+ }
140
+ const rawValue = match[2].trim().replace(/^["']|["']$/g, "");
141
+ const value = rawValue !== "" && !Number.isNaN(Number(rawValue))
142
+ ? Number(rawValue)
143
+ : rawValue;
144
+ meta[match[1]] = value;
145
+ }
146
+ if (typeof meta.id !== "string" || typeof meta.trigger !== "string") {
147
+ return null;
148
+ }
149
+ return {
150
+ id: meta.id,
151
+ trigger: meta.trigger,
152
+ body: body.trim(),
153
+ confidence: typeof meta.confidence === "number" ? meta.confidence : 0.6,
154
+ domain: typeof meta.domain === "string" ? meta.domain : undefined,
155
+ source: typeof meta.source === "string" ? meta.source : undefined,
156
+ scope: meta.scope === "global" || meta.scope === "project" ? meta.scope : undefined,
157
+ project_id: typeof meta.project_id === "string" ? meta.project_id : undefined,
158
+ created: typeof meta.created === "string" ? meta.created : undefined,
159
+ last_seen: typeof meta.last_seen === "string" ? meta.last_seen : undefined,
160
+ observation_count: typeof meta.observation_count === "number" ? meta.observation_count : undefined,
161
+ };
162
+ }
163
+ function countObservations(projectHash) {
164
+ const observationsFile = join(INSTINCTS_DIR, projectHash, "observations.jsonl");
165
+ if (!existsSync(observationsFile)) {
166
+ return 0;
167
+ }
168
+ try {
169
+ const content = readFileSync(observationsFile, "utf8");
170
+ return content.split("\n").filter((line) => line.trim().length > 0).length;
171
+ }
172
+ catch {
173
+ return 0;
174
+ }
175
+ }
176
+ function getRecentObservations(projectHash, limit = 50) {
177
+ const observationsFile = join(INSTINCTS_DIR, projectHash, "observations.jsonl");
178
+ if (!existsSync(observationsFile)) {
179
+ return [];
180
+ }
181
+ try {
182
+ const lines = readFileSync(observationsFile, "utf8")
183
+ .split("\n")
184
+ .filter((line) => line.trim().length > 0);
185
+ return lines.slice(-limit).flatMap((line) => {
186
+ try {
187
+ return [JSON.parse(line)];
188
+ }
189
+ catch {
190
+ return [];
191
+ }
192
+ });
193
+ }
194
+ catch {
195
+ return [];
196
+ }
197
+ }
198
+ function detectLevel(projectHash) {
199
+ const observationCount = countObservations(projectHash);
200
+ const instincts = readInstincts(projectHash);
201
+ const hasHighConfidence = instincts.some((instinct) => instinct.confidence >= 0.7);
202
+ const hasMidConfidence = instincts.some((instinct) => instinct.confidence >= 0.5 && instinct.confidence < 0.7);
203
+ if (hasHighConfidence) {
204
+ return "AUTO-APPLY";
205
+ }
206
+ if (hasMidConfidence) {
207
+ return "SUGGEST";
208
+ }
209
+ if (observationCount >= 20 || instincts.length > 0) {
210
+ return "ANALYZE";
211
+ }
212
+ return "CAPTURE";
213
+ }
214
+ function writeInstinct(projectHash, instinct) {
215
+ const scope = instinct.scope === "global" ? "global" : "project";
216
+ const dir = scope === "global" ? GLOBAL_DIR : join(INSTINCTS_DIR, projectHash);
217
+ mkdirSync(dir, { recursive: true });
218
+ const today = new Date().toISOString().split("T")[0];
219
+ const yaml = [
220
+ `id: ${instinct.id}`,
221
+ `trigger: "${instinct.trigger}"`,
222
+ `confidence: ${instinct.confidence}`,
223
+ `domain: ${instinct.domain || "workflow"}`,
224
+ `source: ${instinct.source || "manual"}`,
225
+ `scope: ${scope}`,
226
+ `project_id: ${projectHash}`,
227
+ `created: "${instinct.created || today}"`,
228
+ `last_seen: "${instinct.last_seen || today}"`,
229
+ `observation_count: ${instinct.observation_count || 1}`,
230
+ "---",
231
+ instinct.body,
232
+ ].join("\n");
233
+ writeFileSync(join(dir, `${instinct.id}.yaml`), `${yaml}\n`);
234
+ }
235
+ function updateInstinctConfidence(projectHash, instinctId, delta) {
236
+ const instincts = readInstincts(projectHash);
237
+ const instinct = instincts.find((candidate) => candidate.id === instinctId);
238
+ if (!instinct) {
239
+ return null;
240
+ }
241
+ const newConfidence = Math.max(0, Math.min(0.9, instinct.confidence + delta));
242
+ instinct.confidence = Math.round(newConfidence * 100) / 100;
243
+ instinct.last_seen = new Date().toISOString().split("T")[0];
244
+ writeInstinct(projectHash, instinct);
245
+ return instinct;
246
+ }
247
+ function getWorkspaceRoot() {
248
+ try {
249
+ const root = execSync("git rev-parse --show-toplevel 2>/dev/null", { encoding: "utf8" }).trim();
250
+ return root || process.cwd();
251
+ }
252
+ catch {
253
+ return process.cwd();
254
+ }
255
+ }
256
+ function readPlanningTemplate(fileName, fallback) {
257
+ const templatePath = join(PLANNING_TEMPLATES_DIR, fileName);
258
+ if (!existsSync(templatePath)) {
259
+ return fallback;
260
+ }
261
+ try {
262
+ return readFileSync(templatePath, "utf8");
263
+ }
264
+ catch {
265
+ return fallback;
266
+ }
267
+ }
268
+ function replaceTemplateToken(template, token, value) {
269
+ return template.split(`{{${token}}}`).join(value);
270
+ }
271
+ function getToday() {
272
+ return new Date().toISOString().split("T")[0];
273
+ }
274
+ function normalizePhases(value) {
275
+ if (!Array.isArray(value)) {
276
+ return [...DEFAULT_PLANNING_PHASES];
277
+ }
278
+ const phases = value
279
+ .map((phase) => typeof phase === "string" ? phase.trim() : "")
280
+ .filter((phase) => phase.length > 0);
281
+ return phases.length > 0 ? phases : [...DEFAULT_PLANNING_PHASES];
282
+ }
283
+ function formatPhaseChecklist(phases) {
284
+ return phases.map((phase) => `- [ ] ${phase}`).join("\n");
285
+ }
286
+ function renderTaskPlan(goal, phases) {
287
+ const fallback = `# Task Plan
288
+
289
+ ## Goal
290
+ {{goal}}
291
+
292
+ ## Status
293
+ {{status}}
294
+
295
+ ## Phases
296
+ {{phases}}
297
+
298
+ ## Key Questions
299
+ - [ ] What existing implementation or context should be reviewed first?
300
+ - [ ] What constraints or risks need validation before execution?
301
+ - [ ] What verification proves the task is complete?
302
+
303
+ ## Decisions Made
304
+ - None yet.
305
+
306
+ ## Errors Encountered
307
+ | Error | Attempt | Resolution |
308
+ | --- | --- | --- |
309
+ | None yet | - | - |
310
+ `;
311
+ let template = readPlanningTemplate(PLANNING_FILES.taskPlan, fallback);
312
+ template = replaceTemplateToken(template, "goal", goal);
313
+ template = replaceTemplateToken(template, "status", "Not started");
314
+ template = replaceTemplateToken(template, "phases", formatPhaseChecklist(phases));
315
+ template = replaceTemplateToken(template, "date", getToday());
316
+ return template.endsWith("\n") ? template : `${template}\n`;
317
+ }
318
+ function renderFindings() {
319
+ const fallback = `# Findings
320
+
321
+ ## Research Notes
322
+ - Created on {{date}}.
323
+ - Record source links, code references, and synthesized findings here.
324
+
325
+ ## Sources
326
+ - Add links and short summaries here.
327
+ `;
328
+ let template = readPlanningTemplate(PLANNING_FILES.findings, fallback);
329
+ template = replaceTemplateToken(template, "date", getToday());
330
+ return template.endsWith("\n") ? template : `${template}\n`;
331
+ }
332
+ function renderProgress() {
333
+ const fallback = `# Progress
334
+
335
+ ## Session Log
336
+ - {{date}}: Initialized planning files.
337
+
338
+ ## Verification Notes
339
+ - Record commands, tests, outputs, and checkpoints here.
340
+ `;
341
+ let template = readPlanningTemplate(PLANNING_FILES.progress, fallback);
342
+ template = replaceTemplateToken(template, "date", getToday());
343
+ return template.endsWith("\n") ? template : `${template}\n`;
344
+ }
345
+ function writePlanningFile(filePath, content, force) {
346
+ const alreadyExists = existsSync(filePath);
347
+ if (alreadyExists && !force) {
348
+ return "preserved";
349
+ }
350
+ writeFileSync(filePath, content);
351
+ return alreadyExists ? "overwritten" : "created";
352
+ }
353
+ function getMarkdownSection(content, heading) {
354
+ const lines = content.split(/\r?\n/);
355
+ const sectionLines = [];
356
+ let inSection = false;
357
+ for (const line of lines) {
358
+ if (line.startsWith("## ")) {
359
+ if (inSection) {
360
+ break;
361
+ }
362
+ inSection = line.trim() === `## ${heading}`;
363
+ continue;
364
+ }
365
+ if (inSection) {
366
+ sectionLines.push(line);
367
+ }
368
+ }
369
+ return sectionLines.join("\n").trim();
370
+ }
371
+ function parseTaskPlan(content) {
372
+ const checkedPhases = [];
373
+ const uncheckedPhases = [];
374
+ for (const line of content.split(/\r?\n/)) {
375
+ const match = line.match(/^- \[([ xX])\] (.+)$/);
376
+ if (!match) {
377
+ continue;
378
+ }
379
+ const phase = match[2].trim();
380
+ if (match[1].toLowerCase() === "x") {
381
+ checkedPhases.push(phase);
382
+ }
383
+ else {
384
+ uncheckedPhases.push(phase);
385
+ }
386
+ }
387
+ const inlineStatus = content.match(/^\*\*Status:\*\*\s*(.+)$/m) ?? content.match(/^Status:\s*(.+)$/m);
388
+ const statusFromSection = getMarkdownSection(content, "Status")
389
+ .split(/\r?\n/)
390
+ .map((line) => line.trim())
391
+ .find(Boolean);
392
+ return {
393
+ status: inlineStatus?.[1]?.trim() || statusFromSection || "Unknown",
394
+ checkedPhases,
395
+ uncheckedPhases,
396
+ };
397
+ }
398
+ function hasMeaningfulContent(content, placeholders) {
399
+ return content
400
+ .split(/\r?\n/)
401
+ .map((line) => line.trim())
402
+ .filter((line) => line.length > 0)
403
+ .filter((line) => !line.startsWith("#"))
404
+ .filter((line) => !/^[-|: ]+$/.test(line))
405
+ .some((line) => line !== "- None yet." &&
406
+ line !== "| None yet | - | - |" &&
407
+ !placeholders.some((placeholder) => line.includes(placeholder)));
408
+ }
409
+ function getAllTools() {
410
+ return getToolDefinitions(MODE);
411
+ }
412
+ function text(value) {
413
+ return { content: [{ type: "text", text: value }] };
414
+ }
415
+ function error(value) {
416
+ return { content: [{ type: "text", text: value }], isError: true };
417
+ }
418
+ function handleTool(name, params) {
419
+ const project = getProjectHash();
420
+ switch (name) {
421
+ case "ci_status": {
422
+ const level = detectLevel(project.hash);
423
+ const observationCount = countObservations(project.hash);
424
+ const instincts = readInstincts(project.hash);
425
+ const byConfidence = {
426
+ silent: instincts.filter((instinct) => instinct.confidence < 0.5).length,
427
+ suggest: instincts.filter((instinct) => instinct.confidence >= 0.5 && instinct.confidence < 0.7).length,
428
+ autoApply: instincts.filter((instinct) => instinct.confidence >= 0.7).length,
429
+ };
430
+ return text([
431
+ "## continuous-improvement Status",
432
+ "",
433
+ `**Project:** ${project.name}`,
434
+ `**Level:** ${level}`,
435
+ `**Observations:** ${observationCount}`,
436
+ `**Instincts:** ${instincts.length} total`,
437
+ ` - Silent (< 0.5): ${byConfidence.silent}`,
438
+ ` - Suggest (0.5-0.69): ${byConfidence.suggest}`,
439
+ ` - Auto-apply (0.7+): ${byConfidence.autoApply}`,
440
+ "",
441
+ `**Mode:** ${MODE}`,
442
+ level === "CAPTURE" ? "\n_Keep working — hooks are capturing. Analysis begins at 20 observations._" : "",
443
+ ].join("\n"));
444
+ }
445
+ case "ci_instincts": {
446
+ const minConfidence = getNumber(params.min_confidence, 0);
447
+ const instincts = readInstincts(project.hash).filter((instinct) => instinct.confidence >= minConfidence);
448
+ if (instincts.length === 0) {
449
+ return text("No instincts found. Keep working — the system learns from your sessions.");
450
+ }
451
+ const lines = instincts
452
+ .sort((left, right) => right.confidence - left.confidence)
453
+ .map((instinct) => {
454
+ const behavior = instinct.confidence >= 0.7
455
+ ? "AUTO-APPLY"
456
+ : instinct.confidence >= 0.5
457
+ ? "SUGGEST"
458
+ : "silent";
459
+ return `- **${instinct.id}** (${instinct.confidence}) [${behavior}]\n Trigger: ${instinct.trigger}\n ${instinct.body}`;
460
+ });
461
+ return text(`## Instincts for ${project.name}\n\n${lines.join("\n\n")}`);
462
+ }
463
+ case "ci_reflect": {
464
+ const summary = getString(params.summary, "No summary provided");
465
+ const reflection = [
466
+ `## Reflection — ${new Date().toISOString().split("T")[0]}`,
467
+ "",
468
+ `**Session summary:** ${summary}`,
469
+ "",
470
+ "Use this template to reflect:",
471
+ "- **What worked:**",
472
+ "- **What failed:**",
473
+ "- **What I'd do differently:**",
474
+ "- **Rule to add:** (becomes an instinct at 0.6 confidence)",
475
+ "- **Iteration — Next best recommendations (ranked, top 3):**",
476
+ " 1. <primary — strongest next core-development move>",
477
+ " 2. <alternative — different angle>",
478
+ " 3. <alternative — smaller or larger scope>",
479
+ " (or just `1. None — goal met, stop.` if the original goal is fully resolved)",
480
+ ].join("\n");
481
+ return text(reflection);
482
+ }
483
+ case "ci_reinforce": {
484
+ if (MODE !== "expert") {
485
+ return error("ci_reinforce requires expert mode. Start server with --mode expert");
486
+ }
487
+ const instinctId = getString(params.instinct_id);
488
+ const delta = getBoolean(params.accepted) ? 0.15 : -0.1;
489
+ const updated = updateInstinctConfidence(project.hash, instinctId, delta);
490
+ if (!updated) {
491
+ return error(`Instinct "${instinctId}" not found`);
492
+ }
493
+ return text(`${getBoolean(params.accepted) ? "Accepted" : "Rejected"} **${updated.id}** — confidence now ${updated.confidence}`);
494
+ }
495
+ case "ci_create_instinct": {
496
+ if (MODE !== "expert") {
497
+ return error("ci_create_instinct requires expert mode");
498
+ }
499
+ const scope = getString(params.scope) === "global" ? "global" : "project";
500
+ const instinct = {
501
+ id: getString(params.id),
502
+ trigger: getString(params.trigger),
503
+ body: getString(params.body),
504
+ confidence: getNumber(params.confidence, 0.6),
505
+ domain: getString(params.domain, "workflow"),
506
+ source: "manual",
507
+ scope,
508
+ observation_count: 1,
509
+ };
510
+ writeInstinct(project.hash, instinct);
511
+ return text(`Created instinct **${instinct.id}** with confidence ${instinct.confidence}`);
512
+ }
513
+ case "ci_observations": {
514
+ if (MODE !== "expert") {
515
+ return error("ci_observations requires expert mode");
516
+ }
517
+ const limit = getNumber(params.limit, 20);
518
+ const observations = getRecentObservations(project.hash, limit);
519
+ if (observations.length === 0) {
520
+ return text("No observations yet. Hooks capture tool calls automatically.");
521
+ }
522
+ const lines = observations.map((observation) => `[${observation.ts}] ${observation.event} — ${observation.tool}`);
523
+ return text(`## Recent Observations (${observations.length})\n\n${lines.join("\n")}`);
524
+ }
525
+ case "ci_export": {
526
+ if (MODE !== "expert") {
527
+ return error("ci_export requires expert mode");
528
+ }
529
+ const scope = getString(params.scope, "all");
530
+ let instincts = readInstincts(project.hash);
531
+ if (scope === "project") {
532
+ instincts = instincts.filter((instinct) => instinct.scope === "project");
533
+ }
534
+ if (scope === "global") {
535
+ instincts = instincts.filter((instinct) => instinct.scope === "global");
536
+ }
537
+ return text(JSON.stringify(instincts, null, 2));
538
+ }
539
+ case "ci_import": {
540
+ if (MODE !== "expert") {
541
+ return error("ci_import requires expert mode");
542
+ }
543
+ const rawJson = getString(params.instincts_json);
544
+ let parsed;
545
+ try {
546
+ parsed = JSON.parse(rawJson);
547
+ }
548
+ catch {
549
+ return error("Invalid JSON. Provide a JSON array of instinct objects.");
550
+ }
551
+ if (!Array.isArray(parsed)) {
552
+ return error("Expected a JSON array");
553
+ }
554
+ const scope = getString(params.scope, "project") === "global" ? "global" : "project";
555
+ const existing = readInstincts(project.hash);
556
+ const existingIds = new Set(existing.map((instinct) => instinct.id));
557
+ let imported = 0;
558
+ for (const candidate of parsed) {
559
+ const instinct = toInstinct(candidate);
560
+ if (!instinct || existingIds.has(instinct.id)) {
561
+ continue;
562
+ }
563
+ instinct.scope = instinct.scope || scope;
564
+ instinct.source = "imported";
565
+ writeInstinct(project.hash, instinct);
566
+ imported++;
567
+ }
568
+ return text(`Imported ${imported} instincts (${parsed.length - imported} skipped as duplicates)`);
569
+ }
570
+ case "ci_plan_init": {
571
+ if (MODE !== "expert") {
572
+ return error("ci_plan_init requires expert mode");
573
+ }
574
+ const goal = getString(params.goal).trim();
575
+ if (!goal) {
576
+ return error("goal is required");
577
+ }
578
+ const phases = normalizePhases(params.phases);
579
+ const force = getBoolean(params.force, false);
580
+ const workspaceRoot = getWorkspaceRoot();
581
+ const taskPlanStatus = writePlanningFile(join(workspaceRoot, PLANNING_FILES.taskPlan), renderTaskPlan(goal, phases), force);
582
+ const findingsStatus = writePlanningFile(join(workspaceRoot, PLANNING_FILES.findings), renderFindings(), force);
583
+ const progressStatus = writePlanningFile(join(workspaceRoot, PLANNING_FILES.progress), renderProgress(), force);
584
+ return text([
585
+ "## Planning-With-Files Initialized",
586
+ "",
587
+ `**Project root:** ${workspaceRoot}`,
588
+ `**Goal:** ${goal}`,
589
+ `**Force overwrite:** ${force ? "yes" : "no"}`,
590
+ "",
591
+ "**Files:**",
592
+ `- ${PLANNING_FILES.taskPlan}: ${taskPlanStatus}`,
593
+ `- ${PLANNING_FILES.findings}: ${findingsStatus}`,
594
+ `- ${PLANNING_FILES.progress}: ${progressStatus}`,
595
+ "",
596
+ "**Phases:**",
597
+ ...phases.map((phase) => `- [ ] ${phase}`),
598
+ ].join("\n"));
599
+ }
600
+ case "ci_plan_status": {
601
+ if (MODE !== "expert") {
602
+ return error("ci_plan_status requires expert mode");
603
+ }
604
+ const workspaceRoot = getWorkspaceRoot();
605
+ const includeContents = getBoolean(params.include_contents, false);
606
+ const taskPlanPath = join(workspaceRoot, PLANNING_FILES.taskPlan);
607
+ const findingsPath = join(workspaceRoot, PLANNING_FILES.findings);
608
+ const progressPath = join(workspaceRoot, PLANNING_FILES.progress);
609
+ const taskPlanExists = existsSync(taskPlanPath);
610
+ const findingsExists = existsSync(findingsPath);
611
+ const progressExists = existsSync(progressPath);
612
+ if (!taskPlanExists && !findingsExists && !progressExists) {
613
+ return text(`No planning files found in ${workspaceRoot}. Run ci_plan_init or /planning-with-files to create ${PLANNING_FILES.taskPlan}, ${PLANNING_FILES.findings}, and ${PLANNING_FILES.progress}.`);
614
+ }
615
+ const taskPlanContent = taskPlanExists ? readFileSync(taskPlanPath, "utf8") : "";
616
+ const findingsContent = findingsExists ? readFileSync(findingsPath, "utf8") : "";
617
+ const progressContent = progressExists ? readFileSync(progressPath, "utf8") : "";
618
+ const summary = taskPlanExists
619
+ ? parseTaskPlan(taskPlanContent)
620
+ : { status: "No task plan yet", checkedPhases: [], uncheckedPhases: [] };
621
+ const findingsState = findingsExists
622
+ ? hasMeaningfulContent(findingsContent, [
623
+ "Created on",
624
+ "Record source links, code references, and synthesized findings here.",
625
+ "Add links and short summaries here.",
626
+ ])
627
+ ? "has notes"
628
+ : "template only"
629
+ : "missing";
630
+ const progressState = progressExists
631
+ ? hasMeaningfulContent(progressContent, [
632
+ "Initialized planning files.",
633
+ "Record commands, tests, outputs, and checkpoints here.",
634
+ ])
635
+ ? "has entries"
636
+ : "template only"
637
+ : "missing";
638
+ const lines = [
639
+ "## Planning-With-Files Status",
640
+ "",
641
+ `**Project root:** ${workspaceRoot}`,
642
+ "",
643
+ "**Files:**",
644
+ `- ${PLANNING_FILES.taskPlan}: ${taskPlanExists ? "present" : "missing"}`,
645
+ `- ${PLANNING_FILES.findings}: ${findingsExists ? "present" : "missing"} (${findingsState})`,
646
+ `- ${PLANNING_FILES.progress}: ${progressExists ? "present" : "missing"} (${progressState})`,
647
+ "",
648
+ `**Status:** ${summary.status}`,
649
+ `**Completed phases:** ${summary.checkedPhases.length}/${summary.checkedPhases.length + summary.uncheckedPhases.length}`,
650
+ `**Checked phases:** ${summary.checkedPhases.length > 0 ? summary.checkedPhases.join(", ") : "(none)"}`,
651
+ `**Remaining phases:** ${summary.uncheckedPhases.length > 0 ? summary.uncheckedPhases.join(", ") : "(none)"}`,
652
+ ];
653
+ if (includeContents) {
654
+ const filesToInclude = [
655
+ [PLANNING_FILES.taskPlan, taskPlanContent],
656
+ [PLANNING_FILES.findings, findingsContent],
657
+ [PLANNING_FILES.progress, progressContent],
658
+ ];
659
+ for (const [fileName, content] of filesToInclude) {
660
+ if (!content) {
661
+ continue;
662
+ }
663
+ lines.push("", `## ${fileName}`, "```md", content.trimEnd(), "```");
664
+ }
665
+ }
666
+ return text(lines.join("\n"));
667
+ }
668
+ case "ci_dashboard": {
669
+ if (MODE !== "expert") {
670
+ return error("ci_dashboard requires expert mode");
671
+ }
672
+ const level = detectLevel(project.hash);
673
+ const observationCount = countObservations(project.hash);
674
+ const instincts = readInstincts(project.hash);
675
+ const byConfidence = {
676
+ auto: instincts.filter((instinct) => instinct.confidence >= 0.7),
677
+ suggest: instincts.filter((instinct) => instinct.confidence >= 0.5 && instinct.confidence < 0.7),
678
+ silent: instincts.filter((instinct) => instinct.confidence < 0.5),
679
+ };
680
+ const globalCount = instincts.filter((instinct) => instinct.scope === "global").length;
681
+ const projectCount = instincts.length - globalCount;
682
+ const today = Date.now();
683
+ const stale = instincts.filter((instinct) => {
684
+ if (!instinct.last_seen) {
685
+ return false;
686
+ }
687
+ const diff = (today - new Date(instinct.last_seen).getTime()) / (1000 * 60 * 60 * 24);
688
+ return diff > 30;
689
+ });
690
+ const autoBar = "█".repeat(Math.min(10, byConfidence.auto.length)) + "░".repeat(Math.max(0, 10 - byConfidence.auto.length));
691
+ const suggestBar = "█".repeat(Math.min(10, byConfidence.suggest.length)) + "░".repeat(Math.max(0, 10 - byConfidence.suggest.length));
692
+ const silentBar = "█".repeat(Math.min(10, byConfidence.silent.length)) + "░".repeat(Math.max(0, 10 - byConfidence.silent.length));
693
+ const top5 = instincts
694
+ .sort((left, right) => right.confidence - left.confidence)
695
+ .slice(0, 5)
696
+ .map((instinct) => ` ${"█".repeat(Math.round(instinct.confidence * 10)) + "░".repeat(10 - Math.round(instinct.confidence * 10))} ${instinct.confidence.toFixed(2)} ${instinct.id}`)
697
+ .join("\n");
698
+ let packInfo = "";
699
+ if (existsSync(PACKS_DIR)) {
700
+ const packs = readdirSync(PACKS_DIR)
701
+ .filter((file) => file.endsWith(".json"))
702
+ .map((file) => file.replace(".json", ""));
703
+ packInfo = `\n Available packs: ${packs.join(", ")}`;
704
+ }
705
+ return text([
706
+ "╔══════════════════════════════════════════════════════════════╗",
707
+ "║ continuous-improvement Dashboard ║",
708
+ "╠══════════════════════════════════════════════════════════════╣",
709
+ "║ ║",
710
+ `║ Project: ${project.name.padEnd(20)} Level: ${level.padEnd(12)} ║`,
711
+ `║ Sessions: ~${String(Math.floor(observationCount / 10)).padEnd(17)} Mode: ${MODE.padEnd(13)} ║`,
712
+ "║ ║",
713
+ "║ ┌─ Observations ────────────────────────────────────────┐ ║",
714
+ `║ │ Total: ${String(observationCount).padEnd(48)} │ ║`,
715
+ "║ └───────────────────────────────────────────────────────┘ ║",
716
+ "║ ║",
717
+ "║ ┌─ Instincts ───────────────────────────────────────────┐ ║",
718
+ `║ │ Total: ${String(instincts.length).padEnd(48)} │ ║`,
719
+ `║ │ ${autoBar} Auto-apply (0.7+): ${String(byConfidence.auto.length).padEnd(20)} │ ║`,
720
+ `║ │ ${suggestBar} Suggest (0.5-0.69): ${String(byConfidence.suggest.length).padEnd(19)} │ ║`,
721
+ `║ │ ${silentBar} Silent (< 0.5): ${String(byConfidence.silent.length).padEnd(23)} │ ║`,
722
+ `║ │ Global: ${String(globalCount).padEnd(10)} Project: ${String(projectCount).padEnd(28)} │ ║`,
723
+ "║ └───────────────────────────────────────────────────────┘ ║",
724
+ "║ ║",
725
+ instincts.length > 0
726
+ ? [
727
+ "║ ┌─ Top Instincts ───────────────────────────────────────┐ ║",
728
+ ...top5.split("\n").map((line) => `║ │${line.padEnd(56)}│ ║`),
729
+ "║ └───────────────────────────────────────────────────────┘ ║",
730
+ ].join("\n")
731
+ : "",
732
+ "║ ║",
733
+ "║ ┌─ Health ──────────────────────────────────────────────┐ ║",
734
+ `║ │ Stale (30+ days): ${String(stale.length).padEnd(38)} │ ║`,
735
+ "║ └───────────────────────────────────────────────────────┘ ║",
736
+ packInfo ? `║${packInfo.padEnd(63)}║` : "",
737
+ "║ ║",
738
+ "╚══════════════════════════════════════════════════════════════╝",
739
+ ].filter(Boolean).join("\n"));
740
+ }
741
+ case "ci_load_pack": {
742
+ if (MODE !== "expert") {
743
+ return error("ci_load_pack requires expert mode");
744
+ }
745
+ const packName = getString(params.pack);
746
+ const packPath = join(PACKS_DIR, `${packName}.json`);
747
+ if (!existsSync(packPath)) {
748
+ const available = existsSync(PACKS_DIR)
749
+ ? readdirSync(PACKS_DIR)
750
+ .filter((file) => file.endsWith(".json"))
751
+ .map((file) => file.replace(".json", ""))
752
+ : [];
753
+ return error(`Unknown pack: ${packName}. Available: ${available.join(", ")}`);
754
+ }
755
+ const packInstincts = JSON.parse(readFileSync(packPath, "utf8"));
756
+ const existing = readInstincts(project.hash);
757
+ const existingIds = new Set(existing.map((instinct) => instinct.id));
758
+ let loaded = 0;
759
+ for (const candidate of packInstincts) {
760
+ const instinct = toInstinct(candidate);
761
+ if (!instinct || existingIds.has(instinct.id)) {
762
+ continue;
763
+ }
764
+ instinct.source = `pack-${packName}`;
765
+ instinct.scope = "project";
766
+ instinct.observation_count = 0;
767
+ writeInstinct(project.hash, instinct);
768
+ loaded++;
769
+ }
770
+ return text(`Loaded ${loaded}/${packInstincts.length} instincts from **${packName}** pack (${packInstincts.length - loaded} already existed)`);
771
+ }
772
+ default:
773
+ return error(`Unknown tool: ${name}`);
774
+ }
775
+ }
776
+ const rl = createInterface({ input: process.stdin, terminal: false });
777
+ let buffer = "";
778
+ rl.on("line", (line) => {
779
+ buffer += line;
780
+ try {
781
+ const message = JSON.parse(buffer);
782
+ buffer = "";
783
+ handleMessage(message);
784
+ }
785
+ catch {
786
+ // incomplete JSON, keep buffering
787
+ }
788
+ });
789
+ function send(response) {
790
+ const json = JSON.stringify(response);
791
+ process.stdout.write(`Content-Length: ${Buffer.byteLength(json)}\r\n\r\n${json}`);
792
+ }
793
+ function handleMessage(message) {
794
+ const id = message.id;
795
+ const method = message.method;
796
+ const params = asRecord(message.params) ?? {};
797
+ switch (method) {
798
+ case "initialize":
799
+ send({
800
+ jsonrpc: "2.0",
801
+ id,
802
+ result: {
803
+ protocolVersion: "2024-11-05",
804
+ capabilities: {
805
+ tools: { listChanged: false },
806
+ resources: { subscribe: false, listChanged: false },
807
+ },
808
+ serverInfo: {
809
+ name: PACKAGE_NAME,
810
+ version: VERSION,
811
+ },
812
+ },
813
+ });
814
+ break;
815
+ case "notifications/initialized":
816
+ break;
817
+ case "tools/list":
818
+ send({
819
+ jsonrpc: "2.0",
820
+ id,
821
+ result: { tools: getAllTools() },
822
+ });
823
+ break;
824
+ case "tools/call": {
825
+ const toolName = getString(params.name);
826
+ const toolArgs = asRecord(params.arguments) ?? {};
827
+ const result = handleTool(toolName, toolArgs);
828
+ send({ jsonrpc: "2.0", id, result });
829
+ break;
830
+ }
831
+ case "resources/list": {
832
+ const project = getProjectHash();
833
+ send({
834
+ jsonrpc: "2.0",
835
+ id,
836
+ result: {
837
+ resources: [
838
+ {
839
+ uri: `instincts://project/${project.hash}`,
840
+ name: `${project.name} instincts`,
841
+ description: `Learned instincts for ${project.name}`,
842
+ mimeType: "application/json",
843
+ },
844
+ {
845
+ uri: "instincts://global",
846
+ name: "Global instincts",
847
+ description: "Cross-project instincts",
848
+ mimeType: "application/json",
849
+ },
850
+ ],
851
+ },
852
+ });
853
+ break;
854
+ }
855
+ case "resources/read": {
856
+ const uri = getString(params.uri);
857
+ const project = getProjectHash();
858
+ const instincts = uri === "instincts://global"
859
+ ? readInstincts("global").filter((instinct) => instinct.scope === "global")
860
+ : readInstincts(project.hash);
861
+ send({
862
+ jsonrpc: "2.0",
863
+ id,
864
+ result: {
865
+ contents: [
866
+ {
867
+ uri,
868
+ text: JSON.stringify(instincts, null, 2),
869
+ mimeType: "application/json",
870
+ },
871
+ ],
872
+ },
873
+ });
874
+ break;
875
+ }
876
+ default:
877
+ if (id !== undefined) {
878
+ send({
879
+ jsonrpc: "2.0",
880
+ id,
881
+ error: { code: -32601, message: `Method not found: ${method}` },
882
+ });
883
+ }
884
+ }
885
+ }
886
+ console.error(`${PACKAGE_NAME} MCP server v${VERSION} started (mode: ${MODE})`);