continuous-improvement 3.1.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.
- package/.claude-plugin/marketplace.json +78 -0
- package/CHANGELOG.md +191 -0
- package/LICENSE +21 -21
- package/QUICKSTART.md +101 -81
- package/README.md +207 -359
- package/SKILL.md +87 -9
- package/action.yml +33 -33
- package/bin/analyze.sh +161 -153
- package/bin/backfill.mjs +172 -0
- package/bin/check-docs-substrings.mjs +333 -0
- package/bin/check-everything-mirror.mjs +145 -0
- package/bin/check-routing-targets.mjs +151 -0
- package/bin/check-skill-law-tag.mjs +128 -0
- package/bin/check-skill-mirror.mjs +119 -0
- package/bin/check-skill-tiers.mjs +116 -0
- package/bin/check-third-party-shape.mjs +202 -0
- package/bin/generate-plugin-manifests.mjs +169 -0
- package/bin/harvest-friction.mjs +279 -0
- package/bin/hook-stats.mjs +258 -0
- package/bin/install.mjs +417 -516
- package/bin/lint-transcript.mjs +182 -210
- package/bin/mcp-server.mjs +840 -617
- package/bin/observe.mjs +148 -0
- package/bin/pre-commit-block-strays.sh +49 -0
- package/bin/refresh-third-party.mjs +416 -0
- package/bin/unified-cli.mjs +533 -0
- package/commands/continuous-improvement.md +115 -74
- package/commands/dashboard.md +56 -56
- package/commands/discipline.md +51 -37
- package/commands/harvest.md +76 -0
- package/commands/learn-eval.md +117 -0
- package/commands/planning-with-files.md +66 -0
- package/commands/proceed-with-the-recommendation.md +62 -0
- package/commands/ralph.md +103 -0
- package/commands/release-train.md +81 -0
- package/commands/seven-laws.md +16 -0
- package/commands/superpowers.md +153 -0
- package/commands/swarm.md +101 -0
- package/commands/workspace-surface-audit.md +77 -0
- package/hooks/observe.sh +172 -134
- package/hooks/session.sh +106 -106
- package/hooks/three-section-close.mjs +181 -0
- package/instinct-packs/go.json +58 -58
- package/instinct-packs/meta.json +16 -0
- package/instinct-packs/python.json +58 -58
- package/instinct-packs/react.json +58 -58
- package/lib/cli-anything.mjs +401 -0
- package/lib/compound-engineering.mjs +831 -0
- package/lib/observe-event.mjs +128 -0
- package/lib/plugin-metadata.mjs +432 -0
- package/lib/pm-marketplace.mjs +61 -0
- package/lib/pm-skills.mjs +1274 -0
- package/lib/resolve-home-dir.mjs +43 -0
- package/lib/skill-tiers.mjs +137 -0
- package/lib/unified-plugin.mjs +924 -0
- package/llms.txt +68 -43
- package/package.json +28 -19
- package/plugins/beginner.json +17 -6
- package/plugins/continuous-improvement/.claude-plugin/marketplace.json +20 -0
- package/plugins/continuous-improvement/.claude-plugin/plugin.json +26 -0
- package/plugins/continuous-improvement/LICENSE +21 -0
- package/plugins/continuous-improvement/README.md +56 -0
- package/plugins/continuous-improvement/bin/backfill.mjs +172 -0
- package/plugins/continuous-improvement/bin/mcp-server.mjs +886 -0
- package/plugins/continuous-improvement/bin/observe.mjs +148 -0
- package/plugins/continuous-improvement/commands/continuous-improvement.md +115 -0
- package/plugins/continuous-improvement/commands/dashboard.md +56 -0
- package/plugins/continuous-improvement/commands/discipline.md +51 -0
- package/plugins/continuous-improvement/commands/harvest.md +76 -0
- package/plugins/continuous-improvement/commands/learn-eval.md +117 -0
- package/plugins/continuous-improvement/commands/planning-with-files.md +66 -0
- package/plugins/continuous-improvement/commands/proceed-with-the-recommendation.md +62 -0
- package/plugins/continuous-improvement/commands/ralph.md +103 -0
- package/plugins/continuous-improvement/commands/release-train.md +81 -0
- package/plugins/continuous-improvement/commands/seven-laws.md +16 -0
- package/plugins/continuous-improvement/commands/superpowers.md +153 -0
- package/plugins/continuous-improvement/commands/swarm.md +101 -0
- package/plugins/continuous-improvement/commands/workspace-surface-audit.md +77 -0
- package/plugins/continuous-improvement/hooks/hooks.json +60 -0
- package/plugins/continuous-improvement/hooks/observe.sh +172 -0
- package/plugins/continuous-improvement/hooks/session.sh +106 -0
- package/plugins/continuous-improvement/hooks/three-section-close.mjs +181 -0
- package/plugins/continuous-improvement/instinct-packs/go.json +58 -0
- package/plugins/continuous-improvement/instinct-packs/meta.json +16 -0
- package/plugins/continuous-improvement/instinct-packs/python.json +58 -0
- package/plugins/continuous-improvement/instinct-packs/react.json +58 -0
- package/plugins/continuous-improvement/lib/observe-event.mjs +128 -0
- package/plugins/continuous-improvement/lib/plugin-metadata.mjs +432 -0
- package/plugins/continuous-improvement/lib/resolve-home-dir.mjs +43 -0
- package/plugins/continuous-improvement/skills/README.md +34 -0
- package/plugins/continuous-improvement/skills/continuous-improvement/SKILL.md +249 -0
- package/plugins/continuous-improvement/skills/deploy-receipt/SKILL.md +131 -0
- package/plugins/continuous-improvement/skills/gateguard/SKILL.md +155 -0
- package/plugins/continuous-improvement/skills/para-memory-files/SKILL.md +108 -0
- package/plugins/continuous-improvement/skills/proceed-with-the-recommendation/SKILL.md +454 -0
- package/plugins/continuous-improvement/skills/ralph/SKILL.md +221 -0
- package/plugins/continuous-improvement/skills/safety-guard/SKILL.md +76 -0
- package/plugins/continuous-improvement/skills/strategic-compact/SKILL.md +104 -0
- package/plugins/continuous-improvement/skills/superpowers/SKILL.md +212 -0
- package/plugins/continuous-improvement/skills/tdd-workflow/SKILL.md +411 -0
- package/plugins/continuous-improvement/skills/token-budget-advisor/SKILL.md +136 -0
- package/plugins/continuous-improvement/skills/verification-loop/SKILL.md +192 -0
- package/plugins/continuous-improvement/skills/wild-risa-balance/SKILL.md +191 -0
- package/plugins/continuous-improvement/skills/workspace-surface-audit/SKILL.md +147 -0
- package/plugins/continuous-improvement/templates/planning-with-files/findings.md +8 -0
- package/plugins/continuous-improvement/templates/planning-with-files/progress.md +7 -0
- package/plugins/continuous-improvement/templates/planning-with-files/task_plan.md +23 -0
- package/plugins/expert.json +26 -5
- package/skills/README.md +79 -0
- package/skills/deploy-receipt.md +131 -0
- package/skills/gateguard.md +155 -0
- package/skills/para-memory-files.md +108 -0
- package/skills/proceed-with-the-recommendation.md +454 -0
- package/skills/ralph.md +221 -0
- package/skills/safety-guard.md +76 -0
- package/skills/strategic-compact.md +104 -0
- package/skills/superpowers.md +212 -0
- package/skills/tdd-workflow.md +411 -0
- package/skills/token-budget-advisor.md +136 -0
- package/skills/verification-loop.md +192 -0
- package/skills/wild-risa-balance.md +191 -0
- package/skills/workspace-surface-audit.md +147 -0
- package/templates/planning-with-files/findings.md +8 -0
- package/templates/planning-with-files/progress.md +7 -0
- package/templates/planning-with-files/task_plan.md +23 -0
- 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})`);
|