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
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
-
3
2
  /**
4
3
  * continuous-improvement MCP Server
5
4
  *
@@ -11,533 +10,877 @@
11
10
  * node bin/mcp-server.mjs --mode expert # all tools
12
11
  * node bin/mcp-server.mjs --mode beginner # explicit beginner
13
12
  */
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
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";
19
17
  import { createInterface } from "node:readline";
20
-
21
- // ---------------------------------------------------------------------------
22
- // Config
23
- // ---------------------------------------------------------------------------
24
- const VERSION = "3.0.0";
25
- const INSTINCTS_DIR = join(homedir(), ".claude", "instincts");
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");
26
72
  const GLOBAL_DIR = join(INSTINCTS_DIR, "global");
27
-
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
+ };
28
81
  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
-
82
+ const modeIndex = args.indexOf("--mode");
83
+ const requestedMode = args[modeIndex + 1];
84
+ const MODE = isPluginMode(requestedMode) ? requestedMode : "beginner";
36
85
  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
-
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
+ }
49
95
  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
- }
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
+ }
67
119
  }
68
- }
69
- return instincts;
120
+ return instincts;
70
121
  }
71
-
72
122
  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;
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;
82
145
  }
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
- }
146
+ if (typeof meta.id !== "string" || typeof meta.trigger !== "string") {
147
+ return null;
92
148
  }
93
- }
94
-
95
- if (!meta.id) return null;
96
- return { ...meta, body: body.trim() };
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
+ };
97
162
  }
98
-
99
163
  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
-
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
+ }
110
176
  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
-
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
+ }
123
198
  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
-
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
+ }
135
214
  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
-
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
+ }
157
235
  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"));
236
+ const instincts = readInstincts(projectHash);
237
+ const instinct = instincts.find((candidate) => candidate.id === instinctId);
238
+ if (!instinct) {
239
+ return null;
305
240
  }
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")}`);
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();
321
251
  }
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);
252
+ catch {
253
+ return process.cwd();
338
254
  }
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}`);
255
+ }
256
+ function readPlanningTemplate(fileName, fallback) {
257
+ const templatePath = join(PLANNING_TEMPLATES_DIR, fileName);
258
+ if (!existsSync(templatePath)) {
259
+ return fallback;
346
260
  }
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}`);
261
+ try {
262
+ return readFileSync(templatePath, "utf8");
361
263
  }
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")}`);
264
+ catch {
265
+ return fallback;
370
266
  }
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));
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];
379
277
  }
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)`);
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}`);
407
774
  }
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
-
775
+ }
426
776
  const rl = createInterface({ input: process.stdin, terminal: false });
427
777
  let buffer = "";
428
-
429
778
  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;
779
+ buffer += line;
780
+ try {
781
+ const message = JSON.parse(buffer);
782
+ buffer = "";
783
+ handleMessage(message);
483
784
  }
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;
785
+ catch {
786
+ // incomplete JSON, keep buffering
503
787
  }
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;
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
+ }
530
884
  }
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
885
  }
542
-
543
- console.error(`continuous-improvement MCP server v${VERSION} started (mode: ${MODE})`);
886
+ console.error(`${PACKAGE_NAME} MCP server v${VERSION} started (mode: ${MODE})`);