mishkan-harness 0.2.0 → 0.2.3
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/README.md +108 -172
- package/bin/mishkan.js +677 -16
- package/docs/design/MISHKAN_decisions.md +450 -8
- package/docs/design/MISHKAN_harness_design.md +1 -33
- package/docs/design/MISHKAN_observability.md +13 -9
- package/docs/research/graphify-token-saving-poc.md +108 -0
- package/docs/usage/09-workflows.md +74 -4
- package/docs/usage/10-observability.md +37 -13
- package/docs/usage/11-graphify.md +264 -0
- package/docs/usage/12-skill-discovery.md +294 -0
- package/docs/usage/README.md +5 -3
- package/package.json +1 -1
- package/payload/install/settings.hooks.json +24 -0
- package/payload/mishkan/AGENT_SPEC.md +25 -7
- package/payload/mishkan/agents/ahikam.md +4 -2
- package/payload/mishkan/agents/aholiab.md +4 -2
- package/payload/mishkan/agents/asaph.md +5 -2
- package/payload/mishkan/agents/baruch.md +19 -2
- package/payload/mishkan/agents/benaiah.md +4 -2
- package/payload/mishkan/agents/bezalel.md +4 -2
- package/payload/mishkan/agents/caleb.md +4 -2
- package/payload/mishkan/agents/deborah.md +4 -2
- package/payload/mishkan/agents/elasah.md +4 -2
- package/payload/mishkan/agents/eliashib.md +4 -2
- package/payload/mishkan/agents/ezra.md +4 -2
- package/payload/mishkan/agents/hanun.md +5 -2
- package/payload/mishkan/agents/hiram.md +5 -2
- package/payload/mishkan/agents/hizkiah.md +5 -2
- package/payload/mishkan/agents/huldah.md +4 -2
- package/payload/mishkan/agents/huram.md +4 -2
- package/payload/mishkan/agents/hushai.md +5 -2
- package/payload/mishkan/agents/igal.md +4 -2
- package/payload/mishkan/agents/ira.md +5 -2
- package/payload/mishkan/agents/jahaziel.md +5 -2
- package/payload/mishkan/agents/jakin.md +4 -2
- package/payload/mishkan/agents/jehonathan.md +5 -2
- package/payload/mishkan/agents/jehoshaphat.md +4 -2
- package/payload/mishkan/agents/joab.md +5 -2
- package/payload/mishkan/agents/joah.md +5 -2
- package/payload/mishkan/agents/maaseiah.md +4 -2
- package/payload/mishkan/agents/meremoth.md +5 -2
- package/payload/mishkan/agents/meshullam.md +5 -2
- package/payload/mishkan/agents/nathan.md +5 -2
- package/payload/mishkan/agents/nehemiah.md +4 -2
- package/payload/mishkan/agents/obed.md +4 -2
- package/payload/mishkan/agents/oholiab.md +5 -2
- package/payload/mishkan/agents/palal.md +5 -2
- package/payload/mishkan/agents/phinehas.md +4 -2
- package/payload/mishkan/agents/rehum.md +4 -2
- package/payload/mishkan/agents/salma.md +5 -2
- package/payload/mishkan/agents/seraiah.md +4 -2
- package/payload/mishkan/agents/shallum.md +5 -2
- package/payload/mishkan/agents/shaphan.md +4 -2
- package/payload/mishkan/agents/shemaiah.md +4 -2
- package/payload/mishkan/agents/shevna.md +5 -2
- package/payload/mishkan/agents/uriah.md +5 -2
- package/payload/mishkan/agents/zaccur.md +4 -2
- package/payload/mishkan/agents/zadok.md +5 -2
- package/payload/mishkan/agents/zerubbabel.md +4 -2
- package/payload/mishkan/commands/code-graph.md +35 -0
- package/payload/mishkan/commands/eval-baruch.md +57 -0
- package/payload/mishkan/commands/mishkan-org-reference.md +58 -0
- package/payload/mishkan/commands/mishkan-skills-misses.md +47 -0
- package/payload/mishkan/commands/mishkan-skills-reindex.md +33 -0
- package/payload/mishkan/commands/skills.md +35 -0
- package/payload/mishkan/commands/sprint-close.md +3 -1
- package/payload/mishkan/hooks/pre-tool-knowledge-route.sh +231 -0
- package/payload/mishkan/hooks/pre-tool-task-skill-route.sh +122 -0
- package/payload/mishkan/hooks/session-start-skill-index.sh +35 -0
- package/payload/mishkan/observability/schema.json +1 -1
- package/payload/mishkan/observability/watch/src/mishkan_watch/__main__.py +84 -2
- package/payload/mishkan/observability/watch/src/mishkan_watch/app.py +64 -3
- package/payload/mishkan/observability/watch/src/mishkan_watch/org_data.py +79 -0
- package/payload/mishkan/observability/watch/src/mishkan_watch/skills_data.py +267 -0
- package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/agents.py +33 -1
- package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/knowledge.py +118 -9
- package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/live.py +102 -2
- package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/org.py +139 -0
- package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/skills.py +187 -0
- package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/usage.py +377 -0
- package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/workflows.py +199 -27
- package/payload/mishkan/observability/watch/src/mishkan_watch/theme.tcss +78 -4
- package/payload/mishkan/observability/watchd/src/mishkan_watchd/__main__.py +36 -6
- package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/bus_tail.py +16 -3
- package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/graphify_tail.py +220 -0
- package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/worktree_poll.py +13 -2
- package/payload/mishkan/observability/watchd/src/mishkan_watchd/state.py +157 -3
- package/payload/mishkan/observability/watchd/tests/test_state.py +6 -2
- package/payload/mishkan/org/org.json +141 -0
- package/payload/mishkan/scripts/pre-commit-eval-gate.sh +50 -0
- package/payload/mishkan/scripts/skill-discovery-indexer.py +462 -0
- package/payload/mishkan/scripts/skill-discovery-misses.py +173 -0
- package/payload/mishkan/scripts/skill-discovery-router.py +457 -0
- package/payload/mishkan/scripts/skill-discovery-smoke.sh +156 -0
- package/payload/mishkan/skills/graphify-query-craft/SKILL.md +162 -0
- package/payload/mishkan/skills/mishkan-init/SKILL.md +15 -0
- package/payload/mishkan/skills/skill-discovery/SKILL.md +120 -0
- package/payload/mishkan/templates/case-node.schema.json +1 -0
- package/payload/mishkan/templates/observability-log.schema.json +1 -0
- package/payload/mishkan/workflows/README.md +78 -13
- package/payload/mishkan/workflows/chosheb-feature-ship.js +75 -0
- package/payload/mishkan/workflows/migdal-dr-drill.js +94 -0
- package/payload/mishkan/workflows/migdal-infra-change.js +67 -0
- package/payload/mishkan/workflows/mishkan-blast-radius.js +144 -0
- package/payload/mishkan/workflows/mishkan-init.js +58 -0
- package/payload/mishkan/workflows/mishkan-knowledge-gap-discovery.js +121 -0
- package/payload/mishkan/workflows/mishkan-standards-rollout.js +153 -0
- package/payload/mishkan/workflows/mishmar-security-gate.js +83 -0
- package/payload/mishkan/workflows/panim-ds-rollout.js +83 -0
- package/payload/mishkan/workflows/sefer-release-notes.js +85 -0
- package/payload/mishkan/workflows/yasad-data-migration-wave.js +78 -0
- package/payload/mishkan/workflows/yasad-schema-evolution.js +76 -0
- package/payload/mishkan/templates/user-CLAUDE.md +0 -62
package/bin/mishkan.js
CHANGED
|
@@ -6,13 +6,15 @@
|
|
|
6
6
|
// No machine-specific paths are baked in. Idempotent: re-running install updates
|
|
7
7
|
// in place. Never clobbers user-edited files (CLAUDE.md, rules, real agents).
|
|
8
8
|
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync,
|
|
9
|
-
copyFileSync, lstatSync, readlinkSync, symlinkSync, rmSync, statSync
|
|
9
|
+
copyFileSync, lstatSync, readlinkSync, symlinkSync, rmSync, statSync,
|
|
10
|
+
chmodSync } from "node:fs";
|
|
10
11
|
import { homedir } from "node:os";
|
|
11
12
|
import { join, dirname, relative } from "node:path";
|
|
12
13
|
import { fileURLToPath } from "node:url";
|
|
13
14
|
import { spawnSync } from "node:child_process";
|
|
14
15
|
import { createInterface } from "node:readline";
|
|
15
16
|
import { stdin, stdout } from "node:process";
|
|
17
|
+
import { randomBytes } from "node:crypto";
|
|
16
18
|
|
|
17
19
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
18
20
|
const PKG = join(HERE, "..");
|
|
@@ -131,7 +133,7 @@ function removeHooks() {
|
|
|
131
133
|
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
132
134
|
}
|
|
133
135
|
|
|
134
|
-
// ─── tool availability + interactive
|
|
136
|
+
// ─── tool availability + interactive prompts ──────────────────────────────
|
|
135
137
|
|
|
136
138
|
function commandExists(cmd) {
|
|
137
139
|
const r = spawnSync(process.platform === "win32" ? "where" : "which",
|
|
@@ -151,6 +153,400 @@ async function promptYN(question, defaultYes = true) {
|
|
|
151
153
|
return a === "y" || a === "yes" || a === "o" || a === "oui";
|
|
152
154
|
}
|
|
153
155
|
|
|
156
|
+
async function promptText(question) {
|
|
157
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
158
|
+
const ans = await new Promise(r => rl.question(question, r));
|
|
159
|
+
rl.close();
|
|
160
|
+
return ans.trim();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Masked input: each typed char becomes "*". Ctrl-C aborts. Backspace works.
|
|
164
|
+
// Non-TTY: falls back to a normal readline with a one-line warning.
|
|
165
|
+
async function promptSecret(question) {
|
|
166
|
+
if (!stdin.isTTY) {
|
|
167
|
+
warn("non-TTY stdin — secret input will be visible.");
|
|
168
|
+
return promptText(question);
|
|
169
|
+
}
|
|
170
|
+
stdout.write(question);
|
|
171
|
+
return new Promise((resolve) => {
|
|
172
|
+
let buf = "";
|
|
173
|
+
stdin.setRawMode(true);
|
|
174
|
+
stdin.resume();
|
|
175
|
+
stdin.setEncoding("utf8");
|
|
176
|
+
const onData = (ch) => {
|
|
177
|
+
if (ch === "\r" || ch === "\n") {
|
|
178
|
+
stdin.setRawMode(false);
|
|
179
|
+
stdin.pause();
|
|
180
|
+
stdin.removeListener("data", onData);
|
|
181
|
+
stdout.write("\n");
|
|
182
|
+
resolve(buf);
|
|
183
|
+
} else if (ch === "") { // Ctrl-C
|
|
184
|
+
stdin.setRawMode(false);
|
|
185
|
+
stdin.pause();
|
|
186
|
+
stdin.removeListener("data", onData);
|
|
187
|
+
stdout.write("\n");
|
|
188
|
+
process.exit(130);
|
|
189
|
+
} else if (ch === "" || ch === "") { // backspace
|
|
190
|
+
if (buf.length > 0) { buf = buf.slice(0, -1); stdout.write("\b \b"); }
|
|
191
|
+
} else if (ch >= " " && ch.length === 1) {
|
|
192
|
+
buf += ch;
|
|
193
|
+
stdout.write("*");
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
stdin.on("data", onData);
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function promptChoice(question, choices) {
|
|
201
|
+
if (!stdin.isTTY) return choices[0]?.key;
|
|
202
|
+
console.log();
|
|
203
|
+
console.log(c.bold(question));
|
|
204
|
+
for (const ch of choices) {
|
|
205
|
+
console.log(` ${c.cyan(ch.key)}) ${c.bold(ch.name)}`);
|
|
206
|
+
if (ch.why) console.log(c.dim(` ${ch.why}`));
|
|
207
|
+
}
|
|
208
|
+
const valid = new Set(choices.map(c => c.key.toUpperCase()));
|
|
209
|
+
while (true) {
|
|
210
|
+
const ans = (await promptText(` Choose [${choices.map(c => c.key).join("/")}]: `)).toUpperCase();
|
|
211
|
+
if (valid.has(ans)) return ans;
|
|
212
|
+
console.log(c.yellow(` ? not a valid choice`));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ─── Knowledge stack configuration wizard ─────────────────────────────────
|
|
217
|
+
// Configures the whole cognee .env: LLM provider + key(s), neo4j/pg/admin
|
|
218
|
+
// secrets (generated or preserved), admin email. Without this, the stack
|
|
219
|
+
// doesn't have enough env to bring docker-compose up.
|
|
220
|
+
|
|
221
|
+
const LLM_PROFILES = {
|
|
222
|
+
A: {
|
|
223
|
+
name: "Ollama (fully self-hosted)",
|
|
224
|
+
why: "No key, no quota. Free but slower for cognify. Recommended for personal hosts.",
|
|
225
|
+
needs: [],
|
|
226
|
+
block: () => `# Provider profile: Ollama (local, no external dependency)
|
|
227
|
+
LLM_PROVIDER=ollama
|
|
228
|
+
LLM_MODEL=qwen2.5:3b
|
|
229
|
+
LLM_ENDPOINT=http://ollama:11434/v1
|
|
230
|
+
LLM_API_KEY=ollama
|
|
231
|
+
EMBEDDING_PROVIDER=ollama
|
|
232
|
+
EMBEDDING_MODEL=nomic-embed-text:latest
|
|
233
|
+
EMBEDDING_ENDPOINT=http://ollama:11434/api/embed
|
|
234
|
+
EMBEDDING_DIMENSIONS=768`,
|
|
235
|
+
},
|
|
236
|
+
B: {
|
|
237
|
+
name: "Google Gemini (cloud, paired LLM+embed)",
|
|
238
|
+
why: "Generous free tier at Google AI Studio. Single key. Strong for cognify volume.",
|
|
239
|
+
needs: [{ key: "LLM_API_KEY", prompt: " Google AI Studio key: " }],
|
|
240
|
+
block: (k) => `# Provider profile: Google Gemini (paired LLM + embeddings)
|
|
241
|
+
LLM_PROVIDER=gemini
|
|
242
|
+
LLM_MODEL=gemini/gemini-2.5-flash
|
|
243
|
+
LLM_API_KEY=${k.LLM_API_KEY}
|
|
244
|
+
EMBEDDING_PROVIDER=gemini
|
|
245
|
+
EMBEDDING_MODEL=gemini/gemini-embedding-001
|
|
246
|
+
EMBEDDING_DIMENSIONS=3072`,
|
|
247
|
+
},
|
|
248
|
+
C: {
|
|
249
|
+
name: "OpenAI (cloud, paired LLM+embed)",
|
|
250
|
+
why: "Pay per token, no free tier. Single key. Predictable quality.",
|
|
251
|
+
needs: [{ key: "LLM_API_KEY", prompt: " OpenAI API key (sk-...): " }],
|
|
252
|
+
block: (k) => `# Provider profile: OpenAI (paired LLM + embeddings)
|
|
253
|
+
LLM_PROVIDER=openai
|
|
254
|
+
LLM_MODEL=openai/gpt-5-mini
|
|
255
|
+
LLM_API_KEY=${k.LLM_API_KEY}
|
|
256
|
+
EMBEDDING_PROVIDER=openai
|
|
257
|
+
EMBEDDING_MODEL=openai/text-embedding-3-large
|
|
258
|
+
EMBEDDING_DIMENSIONS=3072`,
|
|
259
|
+
},
|
|
260
|
+
D: {
|
|
261
|
+
name: "Anthropic LLM + OpenAI embeddings (TWO keys)",
|
|
262
|
+
why: "Claude for reasoning; OpenAI for embed (Claude has none). Two providers, two keys.",
|
|
263
|
+
needs: [
|
|
264
|
+
{ key: "LLM_API_KEY", prompt: " Anthropic API key (sk-ant-...): " },
|
|
265
|
+
{ key: "EMBEDDING_API_KEY", prompt: " OpenAI API key for embeddings (sk-...): " },
|
|
266
|
+
],
|
|
267
|
+
block: (k) => `# Provider profile: Anthropic LLM + OpenAI embeddings
|
|
268
|
+
LLM_PROVIDER=anthropic
|
|
269
|
+
LLM_MODEL=anthropic/claude-sonnet-4-5
|
|
270
|
+
LLM_API_KEY=${k.LLM_API_KEY}
|
|
271
|
+
EMBEDDING_PROVIDER=openai
|
|
272
|
+
EMBEDDING_MODEL=openai/text-embedding-3-large
|
|
273
|
+
EMBEDDING_DIMENSIONS=3072
|
|
274
|
+
EMBEDDING_API_KEY=${k.EMBEDDING_API_KEY}`,
|
|
275
|
+
},
|
|
276
|
+
E: {
|
|
277
|
+
name: "NVIDIA API Catalog + Ollama embeddings",
|
|
278
|
+
why: "Free NVIDIA cloud LLM (rate-limited, OpenAI-compatible) + local Ollama embed.",
|
|
279
|
+
needs: [{ key: "LLM_API_KEY", prompt: " NVIDIA nvapi-... key (build.nvidia.com): " }],
|
|
280
|
+
block: (k) => `# Provider profile: NVIDIA API Catalog (LLM) + Ollama (embeddings)
|
|
281
|
+
LLM_PROVIDER=custom
|
|
282
|
+
LLM_MODEL=openai/meta/llama-3.1-70b-instruct
|
|
283
|
+
LLM_ENDPOINT=https://integrate.api.nvidia.com/v1
|
|
284
|
+
LLM_API_KEY=${k.LLM_API_KEY}
|
|
285
|
+
EMBEDDING_PROVIDER=ollama
|
|
286
|
+
EMBEDDING_MODEL=nomic-embed-text:latest
|
|
287
|
+
EMBEDDING_ENDPOINT=http://ollama:11434/api/embed
|
|
288
|
+
EMBEDDING_DIMENSIONS=768`,
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
// Parse a KEY=VALUE env file into a flat dict (ignores comments / blank lines).
|
|
293
|
+
function parseEnv(text) {
|
|
294
|
+
const out = {};
|
|
295
|
+
for (const raw of text.split("\n")) {
|
|
296
|
+
const line = raw.trim();
|
|
297
|
+
if (!line || line.startsWith("#")) continue;
|
|
298
|
+
const eq = line.indexOf("=");
|
|
299
|
+
if (eq === -1) continue;
|
|
300
|
+
const k = line.slice(0, eq).trim();
|
|
301
|
+
const v = line.slice(eq + 1);
|
|
302
|
+
out[k] = v;
|
|
303
|
+
}
|
|
304
|
+
return out;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// 32-char URL-safe random secret. Strong enough for local-only daemons.
|
|
308
|
+
function genSecret() { return randomBytes(16).toString("hex"); }
|
|
309
|
+
|
|
310
|
+
async function configureKnowledge() {
|
|
311
|
+
const targetDir = join(MISHKAN, "cognee");
|
|
312
|
+
const exampleEnv = join(targetDir, ".env.example");
|
|
313
|
+
const targetEnv = join(targetDir, ".env");
|
|
314
|
+
|
|
315
|
+
console.log();
|
|
316
|
+
console.log(c.bold(c.cyan("Configure the knowledge stack")));
|
|
317
|
+
console.log(c.dim(
|
|
318
|
+
" Writes ~/.claude/mishkan/cognee/.env (0600, gitignored).\n" +
|
|
319
|
+
" Sets the LLM provider + keys, generates neo4j + postgres + admin\n" +
|
|
320
|
+
" passwords on a fresh install, preserves them on a re-run so an\n" +
|
|
321
|
+
" initialised neo4j volume keeps working."));
|
|
322
|
+
|
|
323
|
+
if (!existsSync(targetDir)) {
|
|
324
|
+
warn("cognee dir missing. Run `mishkan-harness install` first.");
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (!existsSync(exampleEnv)) {
|
|
328
|
+
warn(`template missing: ${tilde(exampleEnv)}`);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Preserve existing local secrets if a .env is already initialised — neo4j
|
|
333
|
+
// and pg volumes are encrypted-at-rest with their first-boot password and
|
|
334
|
+
// would lock us out if we regenerated.
|
|
335
|
+
let preserved = {};
|
|
336
|
+
if (existsSync(targetEnv)) {
|
|
337
|
+
const ok = await promptYN(` .env exists. Overwrite (preserving the 3 local secrets)?`, false);
|
|
338
|
+
if (!ok) { console.log(c.dim(" Aborted.")); return; }
|
|
339
|
+
copyFileSync(targetEnv, targetEnv + ".bak");
|
|
340
|
+
preserved = parseEnv(readFileSync(targetEnv, "utf8"));
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// 1) provider profile
|
|
344
|
+
const profileKey = await promptChoice("LLM provider profiles:",
|
|
345
|
+
Object.entries(LLM_PROFILES).map(([key, p]) => ({ key, name: p.name, why: p.why })));
|
|
346
|
+
const profile = LLM_PROFILES[profileKey];
|
|
347
|
+
|
|
348
|
+
// 2) provider keys (masked)
|
|
349
|
+
const apiKeys = {};
|
|
350
|
+
for (const need of profile.needs) {
|
|
351
|
+
const v = await promptSecret(need.prompt);
|
|
352
|
+
if (!v) { console.log(c.yellow(" Aborted (empty key).")); return; }
|
|
353
|
+
apiKeys[need.key] = v;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// 3) admin email (cognee backend bootstrap user)
|
|
357
|
+
const defaultEmail = preserved.DEFAULT_USER_EMAIL || "admin@local.dev";
|
|
358
|
+
const emailIn = await promptText(` Cognee admin email [${defaultEmail}]: `);
|
|
359
|
+
const adminEmail = emailIn || defaultEmail;
|
|
360
|
+
|
|
361
|
+
// 4) stack secrets — preserve if .env existed, else generate
|
|
362
|
+
const stackSecrets = {
|
|
363
|
+
GRAPH_DATABASE_USERNAME: preserved.GRAPH_DATABASE_USERNAME || "neo4j",
|
|
364
|
+
GRAPH_DATABASE_PASSWORD: preserved.GRAPH_DATABASE_PASSWORD || genSecret(),
|
|
365
|
+
DB_PASSWORD: preserved.DB_PASSWORD || genSecret(),
|
|
366
|
+
DEFAULT_USER_PASSWORD: preserved.DEFAULT_USER_PASSWORD || genSecret(),
|
|
367
|
+
};
|
|
368
|
+
const generated = Object.entries(stackSecrets).filter(([k]) => !preserved[k]).map(([k]) => k);
|
|
369
|
+
|
|
370
|
+
// 5) build the .env. Strip provider + stack-secret lines from the example
|
|
371
|
+
// so our explicit blocks land cleanly, then append the rest verbatim so the
|
|
372
|
+
// engineer's tuning (rate limits, common defaults) survives.
|
|
373
|
+
const example = readFileSync(exampleEnv, "utf8");
|
|
374
|
+
const strip = /^(LLM|EMBEDDING)_(API_KEY|PROVIDER|MODEL|ENDPOINT|DIMENSIONS)=|^(GRAPH_DATABASE_USERNAME|GRAPH_DATABASE_PASSWORD|DB_PASSWORD|DEFAULT_USER_EMAIL|DEFAULT_USER_PASSWORD)=/;
|
|
375
|
+
const tail = example.split("\n").filter(l => !strip.test(l)).join("\n");
|
|
376
|
+
|
|
377
|
+
const stackBlock = `# Local-only stack secrets (neo4j, postgres, cognee backend admin).
|
|
378
|
+
# Generated fresh on first run; preserved across re-runs of configure-knowledge.
|
|
379
|
+
GRAPH_DATABASE_USERNAME=${stackSecrets.GRAPH_DATABASE_USERNAME}
|
|
380
|
+
GRAPH_DATABASE_PASSWORD=${stackSecrets.GRAPH_DATABASE_PASSWORD}
|
|
381
|
+
DB_PASSWORD=${stackSecrets.DB_PASSWORD}
|
|
382
|
+
DEFAULT_USER_EMAIL=${adminEmail}
|
|
383
|
+
DEFAULT_USER_PASSWORD=${stackSecrets.DEFAULT_USER_PASSWORD}`;
|
|
384
|
+
|
|
385
|
+
const final = `# Generated by mishkan-harness configure-knowledge on ${new Date().toISOString()}
|
|
386
|
+
# Profile: ${profile.name}
|
|
387
|
+
# Re-run \`npx mishkan-harness configure-knowledge\` to switch providers (secrets preserved).
|
|
388
|
+
|
|
389
|
+
${profile.block(apiKeys)}
|
|
390
|
+
|
|
391
|
+
${stackBlock}
|
|
392
|
+
|
|
393
|
+
${tail}`;
|
|
394
|
+
|
|
395
|
+
writeFileSync(targetEnv, final, { mode: 0o600 });
|
|
396
|
+
|
|
397
|
+
// Companion file: ACCESS.txt with every URL, every credential, and the
|
|
398
|
+
// remote-SSH-tunnel howto. Lives next to .env, same 0600 perms, gitignored
|
|
399
|
+
// because it carries plaintext passwords.
|
|
400
|
+
const accessPath = join(targetDir, "ACCESS.txt");
|
|
401
|
+
writeFileSync(accessPath, renderAccessTxt({
|
|
402
|
+
profile,
|
|
403
|
+
adminEmail,
|
|
404
|
+
secrets: stackSecrets,
|
|
405
|
+
}), { mode: 0o600 });
|
|
406
|
+
|
|
407
|
+
console.log();
|
|
408
|
+
console.log(c.green(`✓ wrote ${tilde(targetEnv)} (0600)`));
|
|
409
|
+
console.log(c.green(`✓ wrote ${tilde(accessPath)} (0600) — URLs + creds + connection guide`));
|
|
410
|
+
console.log(c.dim(` Profile: ${profile.name}`));
|
|
411
|
+
if (generated.length) {
|
|
412
|
+
console.log(c.yellow(` Generated fresh: ${generated.join(", ")}`));
|
|
413
|
+
console.log(c.dim(" All creds are in ACCESS.txt; back it up if you need them outside this host."));
|
|
414
|
+
} else {
|
|
415
|
+
console.log(c.dim(" Preserved existing neo4j / pg / admin secrets."));
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Live summary in the terminal — quick reference even without opening
|
|
419
|
+
// the file. URLs come straight from the docker-compose port mappings.
|
|
420
|
+
console.log();
|
|
421
|
+
console.log(c.bold("Quick reference (also written to ACCESS.txt):"));
|
|
422
|
+
console.log(c.dim(" ──────────────────────────────────────────────────────────────"));
|
|
423
|
+
console.log(` Work · MCP ${c.cyan("http://127.0.0.1:7777/mcp")}`);
|
|
424
|
+
console.log(` Work · Graph UI ${c.cyan("http://127.0.0.1:7724")} ${c.dim(`${adminEmail} / DEFAULT_USER_PASSWORD`)}`);
|
|
425
|
+
console.log(` Work · Neo4j ${c.cyan("http://127.0.0.1:7716")} ${c.dim("neo4j / GRAPH_DATABASE_PASSWORD")}`);
|
|
426
|
+
console.log(` Work · REST ${c.cyan("http://127.0.0.1:7737")}`);
|
|
427
|
+
console.log(` Curated · MCP ${c.cyan("http://127.0.0.1:7730/mcp")}`);
|
|
428
|
+
console.log(` Curated · Graph ${c.cyan("http://127.0.0.1:7734")}`);
|
|
429
|
+
console.log(` Curated · Neo4j ${c.cyan("http://127.0.0.1:7731")}`);
|
|
430
|
+
console.log(` Curated · REST ${c.cyan("http://127.0.0.1:7733")}`);
|
|
431
|
+
if (profile.name.includes("Ollama")) {
|
|
432
|
+
console.log(` Ollama ${c.cyan("http://127.0.0.1:11434")} ${c.dim("local LLM / embeddings")}`);
|
|
433
|
+
}
|
|
434
|
+
console.log();
|
|
435
|
+
console.log(c.bold("Bring up the Cognee stack:"));
|
|
436
|
+
console.log(c.dim(` cd ${tilde(targetDir)}`));
|
|
437
|
+
console.log(c.dim(" docker compose -f docker-compose.yml -f docker-compose.hardening.yml up -d --build"));
|
|
438
|
+
console.log(c.dim(`\n Full guide (incl. SSH-tunnel for remote hosts): ${tilde(accessPath)}`));
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Human-readable access guide written alongside .env. Sectioned plain text,
|
|
442
|
+
// 0600, gitignored. Contains every URL the Cognee stack exposes + the
|
|
443
|
+
// credentials each endpoint needs + the SSH-tunnel howto for remote access.
|
|
444
|
+
function renderAccessTxt({ profile, adminEmail, secrets }) {
|
|
445
|
+
const sep = "─".repeat(70);
|
|
446
|
+
const stamp = new Date().toISOString();
|
|
447
|
+
const ollamaSection = profile.name.includes("Ollama")
|
|
448
|
+
? `\n${sep}\nOllama (local LLM / embeddings)\n${sep}\n
|
|
449
|
+
URL : http://127.0.0.1:11434
|
|
450
|
+
Models : qwen2.5:3b (LLM), nomic-embed-text:latest (embeddings)
|
|
451
|
+
Auth : none (local)
|
|
452
|
+
Health : curl -fsS http://127.0.0.1:11434/api/tags
|
|
453
|
+
|
|
454
|
+
Used by : the Cognee containers via http://ollama:11434/* (internal DNS).
|
|
455
|
+
`
|
|
456
|
+
: "";
|
|
457
|
+
|
|
458
|
+
return `MISHKAN — Cognee access guide
|
|
459
|
+
Generated ${stamp}
|
|
460
|
+
Profile : ${profile.name}
|
|
461
|
+
|
|
462
|
+
This file lists every URL the Cognee stack exposes, the credentials each
|
|
463
|
+
endpoint needs, and how to reach them from a remote machine. Keep it private:
|
|
464
|
+
it carries plaintext passwords. Mode 0600. Gitignored.
|
|
465
|
+
|
|
466
|
+
${sep}
|
|
467
|
+
Cognee WORK store (per-project knowledge graph)
|
|
468
|
+
${sep}
|
|
469
|
+
|
|
470
|
+
MCP endpoint : http://127.0.0.1:7777/mcp
|
|
471
|
+
- The HTTP transport agents call to query/add knowledge.
|
|
472
|
+
- Health check: \`curl -sf http://127.0.0.1:7777/mcp\` returns 406 = healthy
|
|
473
|
+
(the endpoint requires the MCP handshake; a vanilla GET is rejected).
|
|
474
|
+
|
|
475
|
+
Cognee Graph Explorer : http://127.0.0.1:7724
|
|
476
|
+
- Web UI to browse the work graph.
|
|
477
|
+
- Login email : ${adminEmail}
|
|
478
|
+
- Login password : ${secrets.DEFAULT_USER_PASSWORD}
|
|
479
|
+
|
|
480
|
+
Cognee Backend REST : http://127.0.0.1:7737
|
|
481
|
+
- Backend API the Graph Explorer calls. Same creds as above.
|
|
482
|
+
|
|
483
|
+
Neo4j Browser : http://127.0.0.1:7716
|
|
484
|
+
- Direct cypher access to the work graph (read-only recommended).
|
|
485
|
+
- Username : ${secrets.GRAPH_DATABASE_USERNAME}
|
|
486
|
+
- Password : ${secrets.GRAPH_DATABASE_PASSWORD}
|
|
487
|
+
- Bolt URI : bolt://127.0.0.1:7709 (for desktop neo4j clients)
|
|
488
|
+
|
|
489
|
+
${sep}
|
|
490
|
+
Cognee CURATED store (cross-project reference library)
|
|
491
|
+
${sep}
|
|
492
|
+
|
|
493
|
+
MCP endpoint : http://127.0.0.1:7730/mcp
|
|
494
|
+
Cognee Graph Explorer : http://127.0.0.1:7734
|
|
495
|
+
Cognee Backend REST : http://127.0.0.1:7733
|
|
496
|
+
Neo4j Browser : http://127.0.0.1:7731
|
|
497
|
+
Neo4j Bolt : bolt://127.0.0.1:7732
|
|
498
|
+
|
|
499
|
+
Web login : ${adminEmail} / ${secrets.DEFAULT_USER_PASSWORD}
|
|
500
|
+
Neo4j login : ${secrets.GRAPH_DATABASE_USERNAME} / ${secrets.GRAPH_DATABASE_PASSWORD}
|
|
501
|
+
|
|
502
|
+
Note: the curated stack runs its own neo4j with the SAME admin password by
|
|
503
|
+
default (set by configure-knowledge). The compose stack reads .env.curated
|
|
504
|
+
for the curated containers — re-run configure-knowledge to sync both.
|
|
505
|
+
${ollamaSection}
|
|
506
|
+
${sep}
|
|
507
|
+
Reaching a remote host (SSH tunnel)
|
|
508
|
+
${sep}
|
|
509
|
+
|
|
510
|
+
If the harness runs on a remote VPS / dev server, forward the ports to your
|
|
511
|
+
laptop with one SSH session:
|
|
512
|
+
|
|
513
|
+
ssh -N -L 7777:127.0.0.1:7777 \\
|
|
514
|
+
-L 7724:127.0.0.1:7724 \\
|
|
515
|
+
-L 7716:127.0.0.1:7716 \\
|
|
516
|
+
-L 7730:127.0.0.1:7730 \\
|
|
517
|
+
-L 7734:127.0.0.1:7734 \\
|
|
518
|
+
-L 7731:127.0.0.1:7731 \\
|
|
519
|
+
<user>@<host>
|
|
520
|
+
|
|
521
|
+
Then open the URLs above on http://localhost:<port> from your laptop. The
|
|
522
|
+
ports are bound to 127.0.0.1 on the host (never 0.0.0.0), so tunneling is
|
|
523
|
+
the only remote access path by design.
|
|
524
|
+
|
|
525
|
+
${sep}
|
|
526
|
+
Postgres (internal only — not exposed)
|
|
527
|
+
${sep}
|
|
528
|
+
|
|
529
|
+
Container : mishkan-cognee-pg
|
|
530
|
+
Database : cognee
|
|
531
|
+
Password : ${secrets.DB_PASSWORD}
|
|
532
|
+
Reachable : from inside the docker network only.
|
|
533
|
+
|
|
534
|
+
To open psql for debugging:
|
|
535
|
+
docker exec -it mishkan-cognee-pg psql -U cognee -d cognee
|
|
536
|
+
(use the password above)
|
|
537
|
+
|
|
538
|
+
${sep}
|
|
539
|
+
Re-running this wizard
|
|
540
|
+
${sep}
|
|
541
|
+
|
|
542
|
+
npx mishkan-harness configure-knowledge
|
|
543
|
+
|
|
544
|
+
Re-runs preserve the three local secrets (neo4j, pg, admin) so an
|
|
545
|
+
initialised neo4j volume keeps working. Both .env and ACCESS.txt are
|
|
546
|
+
regenerated; previous .env is backed up to .env.bak.
|
|
547
|
+
`;
|
|
548
|
+
}
|
|
549
|
+
|
|
154
550
|
// ─── observability opt-in (Phase 1.5 of the install contract, §10 of doc) ──
|
|
155
551
|
|
|
156
552
|
function installObservabilityStack() {
|
|
@@ -191,9 +587,10 @@ function installObservabilityStack() {
|
|
|
191
587
|
|
|
192
588
|
console.log(c.green(" ✓ observability stack installed"));
|
|
193
589
|
console.log(c.dim(
|
|
194
|
-
"
|
|
195
|
-
"
|
|
196
|
-
"
|
|
590
|
+
" Open the TUI: " + c.bold("mishkan-watch") + " (auto-starts the daemon if needed)\n" +
|
|
591
|
+
" Two-terminal mode: --no-autostart on the TUI; run `mishkan-watchd start` first\n" +
|
|
592
|
+
" Stop the daemon: mishkan-watchd stop\n" +
|
|
593
|
+
" Auto-start at login: mishkan-watchd install-service"));
|
|
197
594
|
return { installed: true };
|
|
198
595
|
}
|
|
199
596
|
|
|
@@ -208,6 +605,30 @@ async function install() {
|
|
|
208
605
|
copyDir(join(PKG, "payload", "mishkan"), MISHKAN);
|
|
209
606
|
ensureDir(join(MISHKAN, "logs"));
|
|
210
607
|
ensureDir(join(MISHKAN, "cognee"));
|
|
608
|
+
|
|
609
|
+
// D-011 Phase 2: rebuild the universal skill-discovery index at install
|
|
610
|
+
// time so the router has a live index.json before the first session boots.
|
|
611
|
+
// The SessionStart hook keeps it fresh thereafter; this seeds it.
|
|
612
|
+
// Fail-open: a missing python3 or an indexer error is logged and the
|
|
613
|
+
// install continues — the router will surface `index_missing_or_unreadable`
|
|
614
|
+
// on its next call and /mishkan-skills-reindex is the recovery path.
|
|
615
|
+
const indexerPath = join(MISHKAN, "scripts", "skill-discovery-indexer.py");
|
|
616
|
+
if (existsSync(indexerPath) && commandExists("python3")) {
|
|
617
|
+
const r = spawnSync("python3", [indexerPath, "--rebuild", "--quiet"], {
|
|
618
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
619
|
+
timeout: 15000,
|
|
620
|
+
});
|
|
621
|
+
if (r.status === 0) {
|
|
622
|
+
log("seeded skill-discovery index at ~/.claude/mishkan/skill-discovery/index.json");
|
|
623
|
+
} else {
|
|
624
|
+
warn(`skill-discovery indexer exited ${r.status ?? "with error"}; install continues. ` +
|
|
625
|
+
`Run /mishkan-skills-reindex once the harness is available.`);
|
|
626
|
+
}
|
|
627
|
+
} else if (!commandExists("python3")) {
|
|
628
|
+
warn("python3 not found; skipping skill-discovery index seed. " +
|
|
629
|
+
"Install python3 then run /mishkan-skills-reindex.");
|
|
630
|
+
}
|
|
631
|
+
|
|
211
632
|
phase(2, totalPhases, "Engineer profile",
|
|
212
633
|
"place runtime profile — never overwrites an existing edited one");
|
|
213
634
|
const realProfile = join(PKG, "docs", "engineer", "profile.md");
|
|
@@ -259,12 +680,76 @@ async function install() {
|
|
|
259
680
|
console.log(c.dim(" Skipped. Re-run later: npx mishkan-harness observability"));
|
|
260
681
|
}
|
|
261
682
|
|
|
683
|
+
// Auto-symlink ~/.local/bin/mishkan -> this package's bin/mishkan.js so
|
|
684
|
+
// the user gets `mishkan <subcommand>` directly without `npx` or a
|
|
685
|
+
// global npm install. Skipped (silently) when:
|
|
686
|
+
// - ~/.local/bin doesn't exist (user didn't opt into local bins)
|
|
687
|
+
// - the link already exists pointing to a different mishkan install
|
|
688
|
+
// (don't clobber another version)
|
|
689
|
+
// - the link exists pointing to us (idempotent re-run)
|
|
690
|
+
const localBin = join(HOME, ".local", "bin");
|
|
691
|
+
const linkTarget = join(PKG, "bin", "mishkan.js");
|
|
692
|
+
const linkPath = join(localBin, "mishkan");
|
|
693
|
+
let directAccess = "absent";
|
|
694
|
+
if (existsSync(localBin)) {
|
|
695
|
+
if (existsSync(linkPath) || isSymlink(linkPath)) {
|
|
696
|
+
try {
|
|
697
|
+
const current = isSymlink(linkPath) ? readlinkSync(linkPath) : null;
|
|
698
|
+
if (current === linkTarget) {
|
|
699
|
+
directAccess = "linked";
|
|
700
|
+
} else {
|
|
701
|
+
directAccess = "blocked"; // a different file lives at the path
|
|
702
|
+
}
|
|
703
|
+
} catch { directAccess = "blocked"; }
|
|
704
|
+
} else {
|
|
705
|
+
try {
|
|
706
|
+
// Defensive +x — npm publish normally sets this from the package.json
|
|
707
|
+
// `bin` field, but a checkout-from-source flow may not.
|
|
708
|
+
try { const st = statSync(linkTarget); chmodSync(linkTarget, st.mode | 0o111); } catch {}
|
|
709
|
+
symlinkSync(linkTarget, linkPath);
|
|
710
|
+
directAccess = "linked";
|
|
711
|
+
log(`linked ${tilde(linkPath)} -> ${tilde(linkTarget)}`);
|
|
712
|
+
} catch (e) {
|
|
713
|
+
directAccess = "failed";
|
|
714
|
+
warn(`could not symlink ${tilde(linkPath)}: ${e.message}`);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
const pathHasLocalBin = (process.env.PATH || "").split(":").includes(localBin);
|
|
719
|
+
|
|
262
720
|
console.log();
|
|
263
721
|
console.log(c.green(`✓ MISHKAN v${version} installed.`));
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
722
|
+
if (directAccess === "linked" && pathHasLocalBin) {
|
|
723
|
+
console.log(c.dim(
|
|
724
|
+
" Run a Claude session and talk to Nehemiah, or /mishkan-init in a project.\n" +
|
|
725
|
+
"\n" +
|
|
726
|
+
" Commands available directly on your PATH (no `npx` needed):\n" +
|
|
727
|
+
" mishkan configure-knowledge wizard: LLM provider + cognee secrets\n" +
|
|
728
|
+
" mishkan code-graph status inspect the project's Graphify graph\n" +
|
|
729
|
+
" mishkan org print the 45-agent reference\n" +
|
|
730
|
+
" mishkan status show install state\n" +
|
|
731
|
+
" mishkan-watch live observability TUI (auto-starts daemon)"));
|
|
732
|
+
} else if (directAccess === "linked") {
|
|
733
|
+
console.log(c.dim(
|
|
734
|
+
" Run a Claude session and talk to Nehemiah, or /mishkan-init in a project.\n" +
|
|
735
|
+
"\n" +
|
|
736
|
+
` Symlinked ${tilde(linkPath)} — add it to PATH for direct access:\n` +
|
|
737
|
+
` echo 'export PATH=\"$HOME/.local/bin:$PATH\"' >> ~/.bashrc # or ~/.zshrc\n` +
|
|
738
|
+
" Until then, use `npx mishkan-harness <subcommand>`."));
|
|
739
|
+
} else {
|
|
740
|
+
console.log(c.dim(
|
|
741
|
+
" Run a Claude session and talk to Nehemiah, or /mishkan-init in a project.\n" +
|
|
742
|
+
"\n" +
|
|
743
|
+
" Day-to-day commands (via `npx mishkan-harness …`):\n" +
|
|
744
|
+
" npx mishkan-harness configure-knowledge wizard: LLM provider + cognee secrets\n" +
|
|
745
|
+
" npx mishkan-harness code-graph status inspect the project's Graphify graph\n" +
|
|
746
|
+
" npx mishkan-harness org print the 45-agent reference\n" +
|
|
747
|
+
" mishkan-watch live observability TUI (auto-starts daemon)\n" +
|
|
748
|
+
"\n" +
|
|
749
|
+
" Want direct access? Either:\n" +
|
|
750
|
+
" npm install -g mishkan-harness # global npm bin\n" +
|
|
751
|
+
" mkdir -p ~/.local/bin && ln -sf " + linkTarget + " ~/.local/bin/mishkan # symlink"));
|
|
752
|
+
}
|
|
268
753
|
}
|
|
269
754
|
|
|
270
755
|
function uninstallObservabilityHint() {
|
|
@@ -276,6 +761,18 @@ function uninstallObservabilityHint() {
|
|
|
276
761
|
}
|
|
277
762
|
|
|
278
763
|
function uninstall({ purge = false } = {}) {
|
|
764
|
+
// remove the ~/.local/bin/mishkan symlink if we created it (it points
|
|
765
|
+
// at this package's bin/mishkan.js).
|
|
766
|
+
const localLink = join(HOME, ".local", "bin", "mishkan");
|
|
767
|
+
if (isSymlink(localLink)) {
|
|
768
|
+
try {
|
|
769
|
+
const t = readlinkSync(localLink);
|
|
770
|
+
if (t.includes("/bin/mishkan.js")) {
|
|
771
|
+
rmSync(localLink);
|
|
772
|
+
log(`removed ${tilde(localLink)}`);
|
|
773
|
+
}
|
|
774
|
+
} catch { /* ignore */ }
|
|
775
|
+
}
|
|
279
776
|
// remove symlinks that point into mishkan
|
|
280
777
|
for (const [sub, dirEntries] of [["agents", false], ["commands", false], ["skills", true]]) {
|
|
281
778
|
const dir = join(CLAUDE, sub);
|
|
@@ -326,6 +823,102 @@ function status() {
|
|
|
326
823
|
log(`cognee dir: ${existsSync(join(MISHKAN,"cognee")) ? "present (deploy the container to activate the graph)" : "missing"}`);
|
|
327
824
|
}
|
|
328
825
|
|
|
826
|
+
// ─── org reference ─────────────────────────────────────────────────────────
|
|
827
|
+
// ─── code-graph (Graphify) inspection ──────────────────────────────────────
|
|
828
|
+
function codeGraphCmd(argv) {
|
|
829
|
+
const sub = argv[0] || "status";
|
|
830
|
+
const cwd = process.cwd();
|
|
831
|
+
const outDir = join(cwd, "graphify-out");
|
|
832
|
+
if (!existsSync(outDir)) {
|
|
833
|
+
console.error(c.red("no code-graph found in this project."));
|
|
834
|
+
console.log("run `graphify update .` from the project root, then retry.");
|
|
835
|
+
process.exit(1);
|
|
836
|
+
}
|
|
837
|
+
const graphJson = join(outDir, "graph.json");
|
|
838
|
+
const graphHtml = join(outDir, "graph.html");
|
|
839
|
+
if (sub === "status") {
|
|
840
|
+
let nodes = 0, edges = 0, lastScan = "?";
|
|
841
|
+
if (existsSync(graphJson)) {
|
|
842
|
+
try {
|
|
843
|
+
const g = JSON.parse(readFileSync(graphJson, "utf8"));
|
|
844
|
+
nodes = (g.nodes || []).length;
|
|
845
|
+
edges = (g.links || g.edges || []).length;
|
|
846
|
+
lastScan = statSync(graphJson).mtime.toISOString().replace("T", " ").slice(0, 19) + " UTC";
|
|
847
|
+
} catch {}
|
|
848
|
+
}
|
|
849
|
+
console.log("");
|
|
850
|
+
console.log(" " + c.bold("code-graph") + " " + c.dim(tilde(cwd)));
|
|
851
|
+
console.log("");
|
|
852
|
+
console.log(" nodes " + c.cyan(String(nodes)));
|
|
853
|
+
console.log(" edges " + c.cyan(String(edges)));
|
|
854
|
+
console.log(" last scan " + c.dim(lastScan));
|
|
855
|
+
console.log("");
|
|
856
|
+
if (existsSync(graphHtml)) {
|
|
857
|
+
console.log(" " + c.dim("open visualisation: ") + c.bold("npx mishkan-harness code-graph open"));
|
|
858
|
+
}
|
|
859
|
+
console.log(" " + c.dim("refresh: ") + c.bold("npx mishkan-harness code-graph scan"));
|
|
860
|
+
console.log("");
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
if (sub === "open") {
|
|
864
|
+
if (!existsSync(graphHtml)) {
|
|
865
|
+
console.error("graph.html missing. run `npx mishkan-harness code-graph scan` first.");
|
|
866
|
+
process.exit(1);
|
|
867
|
+
}
|
|
868
|
+
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
869
|
+
const r = spawnSync(opener, [graphHtml], { stdio: "ignore" });
|
|
870
|
+
if (r.status !== 0) {
|
|
871
|
+
console.log("could not auto-open. visualisation at:");
|
|
872
|
+
console.log(" file://" + graphHtml);
|
|
873
|
+
} else {
|
|
874
|
+
console.log(c.green("opened:") + " " + tilde(graphHtml));
|
|
875
|
+
}
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
if (sub === "scan") {
|
|
879
|
+
const r = spawnSync("graphify", ["update", "."], { stdio: "inherit", cwd });
|
|
880
|
+
process.exit(r.status || 0);
|
|
881
|
+
}
|
|
882
|
+
console.error("unknown subcommand: " + sub);
|
|
883
|
+
console.log("use one of: status | open | scan");
|
|
884
|
+
process.exit(1);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
function printOrgRef({ json = false } = {}) {
|
|
888
|
+
const candidates = [
|
|
889
|
+
join(MISHKAN, "org", "org.json"),
|
|
890
|
+
join(PKG, "payload", "mishkan", "org", "org.json"),
|
|
891
|
+
];
|
|
892
|
+
let data = null;
|
|
893
|
+
for (const p of candidates) {
|
|
894
|
+
if (existsSync(p)) {
|
|
895
|
+
try { data = JSON.parse(readFileSync(p, "utf8")); break; } catch {}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
if (!data) { console.error("org.json not found. Run `npx mishkan-harness install` first."); process.exit(2); }
|
|
899
|
+
if (json) { console.log(JSON.stringify(data, null, 2)); return; }
|
|
900
|
+
console.log(`\n${c.bold("MISHKAN")} — 45 agents across 8 groups\n`);
|
|
901
|
+
for (const grp of data.groups || []) {
|
|
902
|
+
const dom = grp.domain ? c.cyan(` · ${grp.domain}`) : "";
|
|
903
|
+
console.log(c.bold(grp.label) + dom + c.dim(` (${(grp.agents||[]).length})`));
|
|
904
|
+
if (grp.hebrew) {
|
|
905
|
+
const meaning = grp.hebrew_meaning ? c.dim(` — ${grp.hebrew_meaning}`) : "";
|
|
906
|
+
console.log(" " + c.cyan(grp.hebrew) + meaning);
|
|
907
|
+
}
|
|
908
|
+
if (grp.mission) console.log(" " + c.dim("mission ") + grp.mission);
|
|
909
|
+
if (grp.charter) console.log(" " + c.dim("charter ") + grp.charter);
|
|
910
|
+
if (grp.relationships) console.log(" " + c.dim("links ") + grp.relationships);
|
|
911
|
+
if (grp.mission || grp.charter || grp.relationships) console.log("");
|
|
912
|
+
for (const ag of grp.agents || []) {
|
|
913
|
+
const alias = ag.alias.charAt(0).toUpperCase() + ag.alias.slice(1);
|
|
914
|
+
console.log(" " + c.bold(alias.padEnd(14)) + c.dim("· ") + ag.role);
|
|
915
|
+
if (ag.description) console.log(" " + c.dim(ag.description));
|
|
916
|
+
}
|
|
917
|
+
console.log("");
|
|
918
|
+
}
|
|
919
|
+
console.log(c.dim(`source: ${data.generated_from || "docs/design/MISHKAN_agent_aliases.md"}`));
|
|
920
|
+
}
|
|
921
|
+
|
|
329
922
|
const cmd = process.argv[2];
|
|
330
923
|
const flags = new Set(process.argv.slice(3));
|
|
331
924
|
switch (cmd) {
|
|
@@ -333,12 +926,80 @@ switch (cmd) {
|
|
|
333
926
|
case "uninstall": uninstall({ purge: flags.has("--purge") }); break;
|
|
334
927
|
case "status": status(); break;
|
|
335
928
|
case "observability": installObservabilityStack(); break;
|
|
929
|
+
case "configure-knowledge": await configureKnowledge(); break;
|
|
930
|
+
case "org": case "org-ref": printOrgRef({ json: flags.has("--json") }); break;
|
|
931
|
+
case "code-graph": codeGraphCmd(process.argv.slice(3)); break;
|
|
932
|
+
case "help": case "--help": case "-h":
|
|
933
|
+
printHelp(); break;
|
|
336
934
|
default:
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
935
|
+
printHelp(); break;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// Help / usage — always shows. Detects whether ~/.local/bin/mishkan
|
|
939
|
+
// symlinks back to us; when yes we print the short `mishkan <subcommand>`
|
|
940
|
+
// form because that's what the user can actually type. Otherwise we
|
|
941
|
+
// print the `npx mishkan-harness <subcommand>` form so copy-paste works
|
|
942
|
+
// on a fresh shell without the symlink.
|
|
943
|
+
function printHelp() {
|
|
944
|
+
const link = join(HOME, ".local", "bin", "mishkan");
|
|
945
|
+
let direct = false;
|
|
946
|
+
if (isSymlink(link)) {
|
|
947
|
+
try {
|
|
948
|
+
const t = readlinkSync(link);
|
|
949
|
+
direct = t === join(PKG, "bin", "mishkan.js");
|
|
950
|
+
} catch { direct = false; }
|
|
951
|
+
}
|
|
952
|
+
const prefix = direct ? "mishkan" : "npx mishkan-harness";
|
|
953
|
+
const pkgVersion = (() => {
|
|
954
|
+
try { return JSON.parse(readFileSync(join(PKG, "package.json"), "utf8")).version; } catch { return "?"; }
|
|
955
|
+
})();
|
|
956
|
+
console.log("");
|
|
957
|
+
console.log(c.bold(`MISHKAN harness v${pkgVersion}`));
|
|
958
|
+
console.log("");
|
|
959
|
+
if (direct) {
|
|
960
|
+
console.log(c.dim(" Direct access detected: ~/.local/bin/mishkan -> bin/mishkan.js"));
|
|
961
|
+
console.log(c.dim(" All commands below run directly without `npx`."));
|
|
962
|
+
} else {
|
|
963
|
+
console.log(c.dim(" No ~/.local/bin/mishkan symlink — commands run via `npx mishkan-harness …`."));
|
|
964
|
+
console.log(c.dim(" After `install` the harness can symlink for direct access automatically."));
|
|
965
|
+
}
|
|
966
|
+
console.log("");
|
|
967
|
+
console.log(c.bold("Install / update"));
|
|
968
|
+
console.log(" " + c.bold(`${prefix} install`) + " Install/refresh into ~/.claude (idempotent)");
|
|
969
|
+
console.log(" " + c.bold(`${prefix} observability`) + " Install only the observability stack (daemon + TUI, needs uv)");
|
|
970
|
+
console.log(" " + c.bold(`${prefix} uninstall`) + " Remove harness (keeps your CLAUDE.md and rules)");
|
|
971
|
+
console.log(" " + c.bold(`${prefix} uninstall --purge`) + " Also remove the user-level y4nn-standards.md");
|
|
972
|
+
console.log("");
|
|
973
|
+
console.log(c.bold("Configure"));
|
|
974
|
+
console.log(" " + c.bold(`${prefix} configure-knowledge`) + " Wizard: LLM provider + cognee .env (neo4j/pg/admin secrets)");
|
|
975
|
+
console.log("");
|
|
976
|
+
console.log(c.bold("Inspect"));
|
|
977
|
+
console.log(" " + c.bold(`${prefix} status`) + " Show install state, runtime profile, cognee dir, version stamp");
|
|
978
|
+
console.log(" " + c.bold(`${prefix} org [--json]`) + " Print the 45-agent organisation reference (teams + roles + descriptions)");
|
|
979
|
+
console.log(" " + c.bold(`${prefix} code-graph [sub]`) + " Inspect the project's code-graph (Graphify): status | open | scan");
|
|
980
|
+
console.log("");
|
|
981
|
+
console.log(c.bold("Run the live observability TUI"));
|
|
982
|
+
console.log(" " + c.bold("mishkan-watch") + " Open the TUI; auto-starts the daemon if needed");
|
|
983
|
+
console.log(" " + c.bold("mishkan-watch --no-autostart") + " Refuse to fork the daemon; you start it yourself");
|
|
984
|
+
console.log(" " + c.bold("mishkan-watchd start") + " | " + c.bold("stop") + " | " + c.bold("status") + " Daemon lifecycle, when you want manual control");
|
|
985
|
+
console.log("");
|
|
986
|
+
console.log(c.bold("Inside a Claude Code session"));
|
|
987
|
+
console.log(" " + c.dim("Talk to Nehemiah (PM) in plain language — exploration mode is the default."));
|
|
988
|
+
console.log(" " + c.dim("Slash commands available after install:"));
|
|
989
|
+
console.log(" " + c.bold("/mishkan-init") + " Spec chain on a new project (PRD → SRS → CONTRACT → …)");
|
|
990
|
+
console.log(" " + c.bold("/mishkan-resume") + " Resume sprint state + open blockers");
|
|
991
|
+
console.log(" " + c.bold("/sprint-close") + " Reporters → aggregate → docs pull → graph promote");
|
|
992
|
+
console.log(" " + c.bold("/code-graph") + " status|open|scan" + " Inspect / open / refresh the Graphify graph");
|
|
993
|
+
console.log(" " + c.bold("/skills") + " <task description> Skill-discovery router — 3-bucket result");
|
|
994
|
+
console.log(" " + c.bold("/mishkan-skills-reindex") + " Rebuild the universal skill index");
|
|
995
|
+
console.log(" " + c.bold("/mishkan-skills-misses") + " Aggregate miss-log signal for skill-discovery tuning");
|
|
996
|
+
console.log(" " + c.bold("/mishkan-org-reference") + " Print the 45-agent reference inline");
|
|
997
|
+
console.log(" " + c.bold("/eval-baruch") + " Run the Baruch contract eval (schema + golden case)");
|
|
998
|
+
console.log(" " + c.bold("/dep-audit") + " Cross-project dependency + supply-chain audit");
|
|
999
|
+
console.log(" " + c.bold("/promote") + " Promote a learning into Cognee by blast radius");
|
|
1000
|
+
console.log(" " + c.bold("/sefer-pull") + " Trigger a documentation pull");
|
|
1001
|
+
console.log("");
|
|
1002
|
+
console.log(c.dim(" Docs: docs/usage/ · Decisions: docs/design/MISHKAN_decisions.md"));
|
|
1003
|
+
console.log(c.dim(" Repo: https://github.com/Y4NN777/mishkan-cc-harness"));
|
|
1004
|
+
console.log("");
|
|
344
1005
|
}
|