mishkan-harness 0.2.0 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/README.md +108 -172
  2. package/bin/mishkan.js +677 -16
  3. package/docs/design/MISHKAN_decisions.md +450 -8
  4. package/docs/design/MISHKAN_harness_design.md +1 -33
  5. package/docs/design/MISHKAN_observability.md +13 -9
  6. package/docs/engineer/profile-readable.md +201 -0
  7. package/docs/engineer/profile.md +754 -0
  8. package/docs/research/graphify-token-saving-poc.md +108 -0
  9. package/docs/usage/09-workflows.md +74 -4
  10. package/docs/usage/10-observability.md +37 -13
  11. package/docs/usage/11-graphify.md +264 -0
  12. package/docs/usage/12-skill-discovery.md +294 -0
  13. package/docs/usage/README.md +5 -3
  14. package/package.json +1 -1
  15. package/payload/install/settings.hooks.json +24 -0
  16. package/payload/mishkan/AGENT_SPEC.md +25 -7
  17. package/payload/mishkan/agents/ahikam.md +4 -2
  18. package/payload/mishkan/agents/aholiab.md +4 -2
  19. package/payload/mishkan/agents/asaph.md +5 -2
  20. package/payload/mishkan/agents/baruch.md +19 -2
  21. package/payload/mishkan/agents/benaiah.md +4 -2
  22. package/payload/mishkan/agents/bezalel.md +4 -2
  23. package/payload/mishkan/agents/caleb.md +4 -2
  24. package/payload/mishkan/agents/deborah.md +4 -2
  25. package/payload/mishkan/agents/elasah.md +4 -2
  26. package/payload/mishkan/agents/eliashib.md +4 -2
  27. package/payload/mishkan/agents/ezra.md +4 -2
  28. package/payload/mishkan/agents/hanun.md +5 -2
  29. package/payload/mishkan/agents/hiram.md +5 -2
  30. package/payload/mishkan/agents/hizkiah.md +5 -2
  31. package/payload/mishkan/agents/huldah.md +4 -2
  32. package/payload/mishkan/agents/huram.md +4 -2
  33. package/payload/mishkan/agents/hushai.md +5 -2
  34. package/payload/mishkan/agents/igal.md +4 -2
  35. package/payload/mishkan/agents/ira.md +5 -2
  36. package/payload/mishkan/agents/jahaziel.md +5 -2
  37. package/payload/mishkan/agents/jakin.md +4 -2
  38. package/payload/mishkan/agents/jehonathan.md +5 -2
  39. package/payload/mishkan/agents/jehoshaphat.md +4 -2
  40. package/payload/mishkan/agents/joab.md +5 -2
  41. package/payload/mishkan/agents/joah.md +5 -2
  42. package/payload/mishkan/agents/maaseiah.md +4 -2
  43. package/payload/mishkan/agents/meremoth.md +5 -2
  44. package/payload/mishkan/agents/meshullam.md +5 -2
  45. package/payload/mishkan/agents/nathan.md +5 -2
  46. package/payload/mishkan/agents/nehemiah.md +4 -2
  47. package/payload/mishkan/agents/obed.md +4 -2
  48. package/payload/mishkan/agents/oholiab.md +5 -2
  49. package/payload/mishkan/agents/palal.md +5 -2
  50. package/payload/mishkan/agents/phinehas.md +4 -2
  51. package/payload/mishkan/agents/rehum.md +4 -2
  52. package/payload/mishkan/agents/salma.md +5 -2
  53. package/payload/mishkan/agents/seraiah.md +4 -2
  54. package/payload/mishkan/agents/shallum.md +5 -2
  55. package/payload/mishkan/agents/shaphan.md +4 -2
  56. package/payload/mishkan/agents/shemaiah.md +4 -2
  57. package/payload/mishkan/agents/shevna.md +5 -2
  58. package/payload/mishkan/agents/uriah.md +5 -2
  59. package/payload/mishkan/agents/zaccur.md +4 -2
  60. package/payload/mishkan/agents/zadok.md +5 -2
  61. package/payload/mishkan/agents/zerubbabel.md +4 -2
  62. package/payload/mishkan/commands/code-graph.md +35 -0
  63. package/payload/mishkan/commands/eval-baruch.md +57 -0
  64. package/payload/mishkan/commands/mishkan-org-reference.md +58 -0
  65. package/payload/mishkan/commands/mishkan-resume.md +19 -5
  66. package/payload/mishkan/commands/mishkan-skills-misses.md +47 -0
  67. package/payload/mishkan/commands/mishkan-skills-reindex.md +33 -0
  68. package/payload/mishkan/commands/skills.md +35 -0
  69. package/payload/mishkan/commands/sprint-close.md +3 -1
  70. package/payload/mishkan/hooks/pre-tool-knowledge-route.sh +231 -0
  71. package/payload/mishkan/hooks/pre-tool-task-skill-route.sh +122 -0
  72. package/payload/mishkan/hooks/session-start-skill-index.sh +35 -0
  73. package/payload/mishkan/observability/schema.json +1 -1
  74. package/payload/mishkan/observability/watch/src/mishkan_watch/__main__.py +84 -2
  75. package/payload/mishkan/observability/watch/src/mishkan_watch/app.py +64 -3
  76. package/payload/mishkan/observability/watch/src/mishkan_watch/org_data.py +79 -0
  77. package/payload/mishkan/observability/watch/src/mishkan_watch/skills_data.py +267 -0
  78. package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/agents.py +33 -1
  79. package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/knowledge.py +118 -9
  80. package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/live.py +102 -2
  81. package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/org.py +139 -0
  82. package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/skills.py +187 -0
  83. package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/usage.py +377 -0
  84. package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/workflows.py +199 -27
  85. package/payload/mishkan/observability/watch/src/mishkan_watch/theme.tcss +78 -4
  86. package/payload/mishkan/observability/watchd/src/mishkan_watchd/__main__.py +36 -6
  87. package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/bus_tail.py +16 -3
  88. package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/graphify_tail.py +220 -0
  89. package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/worktree_poll.py +13 -2
  90. package/payload/mishkan/observability/watchd/src/mishkan_watchd/state.py +157 -3
  91. package/payload/mishkan/observability/watchd/tests/test_state.py +6 -2
  92. package/payload/mishkan/org/org.json +141 -0
  93. package/payload/mishkan/scripts/pre-commit-eval-gate.sh +50 -0
  94. package/payload/mishkan/scripts/skill-discovery-indexer.py +462 -0
  95. package/payload/mishkan/scripts/skill-discovery-misses.py +173 -0
  96. package/payload/mishkan/scripts/skill-discovery-router.py +457 -0
  97. package/payload/mishkan/scripts/skill-discovery-smoke.sh +156 -0
  98. package/payload/mishkan/skills/graphify-query-craft/SKILL.md +162 -0
  99. package/payload/mishkan/skills/mishkan-init/SKILL.md +15 -0
  100. package/payload/mishkan/skills/skill-discovery/SKILL.md +120 -0
  101. package/payload/mishkan/templates/case-node.schema.json +1 -0
  102. package/payload/mishkan/templates/observability-log.schema.json +1 -0
  103. package/payload/mishkan/workflows/README.md +78 -13
  104. package/payload/mishkan/workflows/chosheb-feature-ship.js +75 -0
  105. package/payload/mishkan/workflows/migdal-dr-drill.js +94 -0
  106. package/payload/mishkan/workflows/migdal-infra-change.js +67 -0
  107. package/payload/mishkan/workflows/mishkan-blast-radius.js +144 -0
  108. package/payload/mishkan/workflows/mishkan-init.js +58 -0
  109. package/payload/mishkan/workflows/mishkan-knowledge-gap-discovery.js +121 -0
  110. package/payload/mishkan/workflows/mishkan-standards-rollout.js +153 -0
  111. package/payload/mishkan/workflows/mishmar-security-gate.js +83 -0
  112. package/payload/mishkan/workflows/panim-ds-rollout.js +83 -0
  113. package/payload/mishkan/workflows/sefer-release-notes.js +85 -0
  114. package/payload/mishkan/workflows/yasad-data-migration-wave.js +78 -0
  115. package/payload/mishkan/workflows/yasad-schema-evolution.js +76 -0
  116. 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 } from "node:fs";
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 prompt ────────────────────────────────
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
- " Start the daemon: " + c.bold("mishkan-watchd start") + "\n" +
195
- " Open the TUI: " + c.bold("mishkan-watch") + "\n" +
196
- " Auto-start unit: mishkan-watchd install-service"));
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
- console.log(c.dim(
265
- " Run a Claude session and talk to Nehemiah, or /mishkan-init in a project.\n" +
266
- " Status: npx mishkan-harness status\n" +
267
- " Re-add obs stack: npx mishkan-harness observability"));
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
- console.log(`MISHKAN harness installer
338
- Usage:
339
- npx mishkan-harness install Install/refresh into ~/.claude (idempotent)
340
- npx mishkan-harness observability Install only the observability stack (daemon + TUI, needs uv)
341
- npx mishkan-harness status Show install state
342
- npx mishkan-harness uninstall Remove harness (keeps your CLAUDE.md & rules)
343
- npx mishkan-harness uninstall --purge Also remove user-level rule`);
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
  }