mishkan-harness 0.2.5 → 0.2.7
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 +49 -11
- package/bin/mishkan.js +562 -79
- package/docs/design/MISHKAN_decisions.md +398 -8
- package/docs/design/MISHKAN_observability.md +8 -8
- package/docs/engineer/profile-readable.md +201 -0
- package/docs/engineer/profile.md +754 -0
- package/docs/usage/01-installation.md +22 -8
- package/docs/usage/02-project-init.md +30 -13
- package/docs/usage/03-orchestration.md +46 -7
- package/docs/usage/04-memory-layer.md +135 -69
- package/docs/usage/06-llm-providers.md +2 -1
- package/docs/usage/07-troubleshooting.md +23 -0
- package/docs/usage/08-glossary.md +12 -3
- package/docs/usage/09-workflows.md +15 -3
- package/docs/usage/10-observability.md +3 -3
- package/docs/usage/11-graphify.md +38 -18
- package/docs/usage/12-cli.md +87 -0
- package/docs/usage/12-skill-discovery.md +11 -0
- package/docs/usage/README.md +35 -19
- package/package.json +1 -1
- package/payload/mishkan/AGENT_SPEC.md +3 -1
- package/payload/mishkan/agents/ahikam.md +3 -1
- package/payload/mishkan/agents/aholiab.md +3 -1
- package/payload/mishkan/agents/asaph.md +3 -1
- package/payload/mishkan/agents/baruch.md +8 -1
- package/payload/mishkan/agents/benaiah.md +3 -1
- package/payload/mishkan/agents/bezalel.md +3 -1
- package/payload/mishkan/agents/caleb.md +3 -1
- package/payload/mishkan/agents/deborah.md +3 -1
- package/payload/mishkan/agents/elasah.md +3 -1
- package/payload/mishkan/agents/eliashib.md +3 -1
- package/payload/mishkan/agents/ezra.md +3 -1
- package/payload/mishkan/agents/hanun.md +3 -1
- package/payload/mishkan/agents/hiram.md +3 -1
- package/payload/mishkan/agents/hizkiah.md +3 -1
- package/payload/mishkan/agents/huldah.md +3 -1
- package/payload/mishkan/agents/huram.md +3 -1
- package/payload/mishkan/agents/hushai.md +3 -1
- package/payload/mishkan/agents/igal.md +3 -1
- package/payload/mishkan/agents/ira.md +3 -1
- package/payload/mishkan/agents/jahaziel.md +3 -1
- package/payload/mishkan/agents/jakin.md +3 -1
- package/payload/mishkan/agents/jehonathan.md +3 -1
- package/payload/mishkan/agents/jehoshaphat.md +3 -1
- package/payload/mishkan/agents/joab.md +3 -1
- package/payload/mishkan/agents/joah.md +3 -1
- package/payload/mishkan/agents/maaseiah.md +3 -1
- package/payload/mishkan/agents/meremoth.md +3 -1
- package/payload/mishkan/agents/meshullam.md +3 -1
- package/payload/mishkan/agents/nathan.md +3 -1
- package/payload/mishkan/agents/nehemiah.md +3 -1
- package/payload/mishkan/agents/obed.md +3 -1
- package/payload/mishkan/agents/oholiab.md +3 -1
- package/payload/mishkan/agents/palal.md +3 -1
- package/payload/mishkan/agents/phinehas.md +3 -1
- package/payload/mishkan/agents/rehum.md +3 -1
- package/payload/mishkan/agents/salma.md +3 -1
- package/payload/mishkan/agents/seraiah.md +3 -1
- package/payload/mishkan/agents/shallum.md +3 -1
- package/payload/mishkan/agents/shaphan.md +3 -1
- package/payload/mishkan/agents/shemaiah.md +6 -1
- package/payload/mishkan/agents/shevna.md +3 -1
- package/payload/mishkan/agents/uriah.md +3 -1
- package/payload/mishkan/agents/zaccur.md +3 -1
- package/payload/mishkan/agents/zadok.md +3 -1
- package/payload/mishkan/agents/zerubbabel.md +3 -1
- package/payload/mishkan/cognee/.env.example +11 -0
- package/payload/mishkan/cognee/Dockerfile +20 -0
- package/payload/mishkan/cognee/README.md +34 -14
- package/payload/mishkan/cognee/docker-compose.curated.yml +33 -6
- package/payload/mishkan/cognee/docker-compose.selfhosted.yml +35 -5
- package/payload/mishkan/cognee/docker-compose.work.yml +191 -0
- package/payload/mishkan/cognee/docker-compose.yml +18 -3
- package/payload/mishkan/cognee/patches/cognee-mcp-core-align.py +72 -0
- package/payload/mishkan/cognee/patches/cognee-mcp-recall-user.py +82 -0
- package/payload/mishkan/cognee/promote-curated.py +77 -0
- package/payload/mishkan/cognee/prune-store.py +23 -0
- package/payload/mishkan/commands/mishkan-init.md +4 -1
- package/payload/mishkan/commands/mishkan-org-reference.md +2 -2
- package/payload/mishkan/config/curated-library.yaml +1 -0
- package/payload/mishkan/config/model-routing.yaml +23 -14
- package/payload/mishkan/evals/baruch/fixtures/invalid/bad-curated-candidate.json +22 -0
- package/payload/mishkan/evals/baruch/fixtures/valid/resolved-curated-candidate.json +23 -0
- package/payload/mishkan/hooks/model-route.py +14 -1
- package/payload/mishkan/hooks/post-tool-observe.sh +55 -7
- package/payload/mishkan/hooks/pre-tool-trace.sh +40 -6
- package/payload/mishkan/observability/README.md +15 -12
- package/payload/mishkan/observability/schema.json +18 -1
- package/payload/mishkan/observability/usage_parser.py +1 -0
- package/payload/mishkan/observability/watch/pyproject.toml +1 -1
- package/payload/mishkan/observability/watch/src/mishkan_watch/__init__.py +1 -1
- package/payload/mishkan/observability/watch/src/mishkan_watch/__main__.py +78 -21
- package/payload/mishkan/observability/watch/src/mishkan_watch/app.py +56 -3
- package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/agents.py +29 -7
- package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/knowledge.py +83 -8
- package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/live.py +42 -13
- package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/usage.py +10 -3
- package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/workflows.py +47 -7
- package/payload/mishkan/observability/watchd/pyproject.toml +1 -1
- package/payload/mishkan/observability/watchd/src/mishkan_watchd/__init__.py +1 -1
- package/payload/mishkan/observability/watchd/src/mishkan_watchd/__main__.py +87 -11
- package/payload/mishkan/observability/watchd/src/mishkan_watchd/server.py +126 -9
- package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/cognee_poll.py +136 -49
- package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/graphify_tail.py +38 -64
- package/payload/mishkan/observability/watchd/src/mishkan_watchd/state.py +222 -34
- package/payload/mishkan/observability/watchd/tests/test_state.py +502 -4
- package/payload/mishkan/ontology.md +8 -1
- package/payload/mishkan/ontology.ttl +188 -0
- package/payload/mishkan/scripts/ensure-work-store.sh +230 -0
- package/payload/mishkan/scripts/mishkan-ingest.sh +53 -3
- package/payload/mishkan/scripts/promote-curated.sh +77 -0
- package/payload/mishkan/scripts/reset-knowledge-data.sh +70 -0
- package/payload/mishkan/scripts/validate-research-log.sh +27 -0
- package/payload/mishkan/skills/baruch-research-reporting-craft/SKILL.md +42 -8
- package/payload/mishkan/skills/cognee-promote/SKILL.md +13 -0
- package/payload/mishkan/skills/cognee-quickstart/SKILL.md +7 -1
- package/payload/mishkan/skills/ezra-research-formulation-craft/SKILL.md +9 -0
- package/payload/mishkan/skills/mishkan-ingest/SKILL.md +22 -0
- package/payload/mishkan/skills/mishkan-init/SKILL.md +31 -19
- package/payload/mishkan/skills/shemaiah-evaluation-craft/SKILL.md +33 -5
- package/payload/mishkan/skills/team-lead-craft/SKILL.md +66 -0
- package/payload/mishkan/templates/mcp.json +7 -2
- package/payload/mishkan/templates/observability-log.schema.json +1 -1
- package/payload/mishkan/templates/research-log.schema.json +15 -1
- package/payload/mishkan/workflows/README.md +19 -13
- package/payload/mishkan/workflows/chosheb-feature-ship.js +63 -18
- package/payload/mishkan/workflows/migdal-dr-drill.js +4 -0
- package/payload/mishkan/workflows/migdal-infra-change.js +48 -7
- package/payload/mishkan/workflows/mishkan-architecture-panel.js +4 -0
- package/payload/mishkan/workflows/mishkan-blast-radius.js +4 -0
- package/payload/mishkan/workflows/mishkan-codebase-audit.js +4 -0
- package/payload/mishkan/workflows/mishkan-deep-research.js +4 -0
- package/payload/mishkan/workflows/mishkan-init.js +4 -0
- package/payload/mishkan/workflows/mishkan-knowledge-gap-discovery.js +4 -0
- package/payload/mishkan/workflows/mishkan-migration-wave.js +4 -0
- package/payload/mishkan/workflows/mishkan-release-readiness.js +4 -0
- package/payload/mishkan/workflows/mishkan-sprint-close.js +4 -0
- package/payload/mishkan/workflows/mishkan-standards-rollout.js +4 -0
- package/payload/mishkan/workflows/mishmar-security-gate.js +45 -6
- package/payload/mishkan/workflows/panim-ds-rollout.js +4 -0
- package/payload/mishkan/workflows/panim-feature-ship.js +132 -0
- package/payload/mishkan/workflows/sefer-release-notes.js +4 -0
- package/payload/mishkan/workflows/yasad-data-migration-wave.js +4 -0
- package/payload/mishkan/workflows/yasad-feature-ship.js +130 -0
- package/payload/mishkan/workflows/yasad-schema-evolution.js +4 -0
package/bin/mishkan.js
CHANGED
|
@@ -382,9 +382,9 @@ DB_PASSWORD=${stackSecrets.DB_PASSWORD}
|
|
|
382
382
|
DEFAULT_USER_EMAIL=${adminEmail}
|
|
383
383
|
DEFAULT_USER_PASSWORD=${stackSecrets.DEFAULT_USER_PASSWORD}`;
|
|
384
384
|
|
|
385
|
-
const final = `# Generated by mishkan
|
|
385
|
+
const final = `# Generated by 'mishkan knowledge configure' on ${new Date().toISOString()}
|
|
386
386
|
# Profile: ${profile.name}
|
|
387
|
-
# Re-run \`
|
|
387
|
+
# Re-run \`mishkan knowledge configure\` to switch providers (secrets preserved).
|
|
388
388
|
|
|
389
389
|
${profile.block(apiKeys)}
|
|
390
390
|
|
|
@@ -420,10 +420,11 @@ ${tail}`;
|
|
|
420
420
|
console.log();
|
|
421
421
|
console.log(c.bold("Quick reference (also written to ACCESS.txt):"));
|
|
422
422
|
console.log(c.dim(" ──────────────────────────────────────────────────────────────"));
|
|
423
|
-
console.log(`
|
|
424
|
-
console.log(`
|
|
425
|
-
console.log(`
|
|
426
|
-
console.log(`
|
|
423
|
+
console.log(` Memory · MCP ${c.cyan("http://127.0.0.1:7777/mcp")} ${c.dim("cognee-memory — shared session memory")}`);
|
|
424
|
+
console.log(` Memory · Graph UI ${c.cyan("http://127.0.0.1:7724")} ${c.dim(`${adminEmail} / DEFAULT_USER_PASSWORD`)}`);
|
|
425
|
+
console.log(` Memory · Neo4j ${c.cyan("http://127.0.0.1:7716")} ${c.dim("neo4j / GRAPH_DATABASE_PASSWORD")}`);
|
|
426
|
+
console.log(` Memory · REST ${c.cyan("http://127.0.0.1:7737")}`);
|
|
427
|
+
console.log(` Per-project work ${c.dim("provisioned per project at /mishkan-init (own port, embedded Ladybug) — ADR D-012")}`);
|
|
427
428
|
console.log(` Curated · MCP ${c.cyan("http://127.0.0.1:7730/mcp")}`);
|
|
428
429
|
console.log(` Curated · Graph ${c.cyan("http://127.0.0.1:7734")}`);
|
|
429
430
|
console.log(` Curated · Neo4j ${c.cyan("http://127.0.0.1:7731")}`);
|
|
@@ -432,9 +433,8 @@ ${tail}`;
|
|
|
432
433
|
console.log(` Ollama ${c.cyan("http://127.0.0.1:11434")} ${c.dim("local LLM / embeddings")}`);
|
|
433
434
|
}
|
|
434
435
|
console.log();
|
|
435
|
-
console.log(c.bold("Bring up the
|
|
436
|
-
console.log(c.dim(
|
|
437
|
-
console.log(c.dim(" docker compose -f docker-compose.yml -f docker-compose.hardening.yml up -d --build"));
|
|
436
|
+
console.log(c.bold("Bring up the knowledge stack:"));
|
|
437
|
+
console.log(c.dim(" mishkan knowledge-stack up") + c.dim(" (guided: preflights config, then memory :7777 + curated :7730)"));
|
|
438
438
|
console.log(c.dim(`\n Full guide (incl. SSH-tunnel for remote hosts): ${tilde(accessPath)}`));
|
|
439
439
|
}
|
|
440
440
|
|
|
@@ -464,16 +464,20 @@ endpoint needs, and how to reach them from a remote machine. Keep it private:
|
|
|
464
464
|
it carries plaintext passwords. Mode 0600. Gitignored.
|
|
465
465
|
|
|
466
466
|
${sep}
|
|
467
|
-
Cognee
|
|
467
|
+
Cognee MEMORY store (shared session memory — alias cognee-memory, :7777)
|
|
468
468
|
${sep}
|
|
469
469
|
|
|
470
|
-
|
|
471
|
-
|
|
470
|
+
This is the kept Neo4j box, repurposed to hold only claude_code_memory (per-client
|
|
471
|
+
session memory). Per-project KNOWLEDGE lives in SEPARATE per-project work stores
|
|
472
|
+
(embedded Ladybug, own port each), provisioned by ensure-work-store.sh at
|
|
473
|
+
/mishkan-init — ADR D-012. This box is no longer the project work store.
|
|
474
|
+
|
|
475
|
+
MCP endpoint : http://127.0.0.1:7777/mcp (alias: cognee-memory)
|
|
472
476
|
- Health check: \`curl -sf http://127.0.0.1:7777/mcp\` returns 406 = healthy
|
|
473
477
|
(the endpoint requires the MCP handshake; a vanilla GET is rejected).
|
|
474
478
|
|
|
475
479
|
Cognee Graph Explorer : http://127.0.0.1:7724
|
|
476
|
-
- Web UI to browse the
|
|
480
|
+
- Web UI to browse the session-memory graph.
|
|
477
481
|
- Login email : ${adminEmail}
|
|
478
482
|
- Login password : ${secrets.DEFAULT_USER_PASSWORD}
|
|
479
483
|
|
|
@@ -481,7 +485,7 @@ Cognee Backend REST : http://127.0.0.1:7737
|
|
|
481
485
|
- Backend API the Graph Explorer calls. Same creds as above.
|
|
482
486
|
|
|
483
487
|
Neo4j Browser : http://127.0.0.1:7716
|
|
484
|
-
- Direct cypher access to the
|
|
488
|
+
- Direct cypher access to the session-memory graph (read-only recommended).
|
|
485
489
|
- Username : ${secrets.GRAPH_DATABASE_USERNAME}
|
|
486
490
|
- Password : ${secrets.GRAPH_DATABASE_PASSWORD}
|
|
487
491
|
- Bolt URI : bolt://127.0.0.1:7709 (for desktop neo4j clients)
|
|
@@ -500,8 +504,8 @@ Web login : ${adminEmail} / ${secrets.DEFAULT_USER_PASSWORD}
|
|
|
500
504
|
Neo4j login : ${secrets.GRAPH_DATABASE_USERNAME} / ${secrets.GRAPH_DATABASE_PASSWORD}
|
|
501
505
|
|
|
502
506
|
Note: the curated stack runs its own neo4j with the SAME admin password by
|
|
503
|
-
default (set by configure
|
|
504
|
-
for the curated containers — re-run
|
|
507
|
+
default (set by 'mishkan knowledge configure'). The compose stack reads .env.curated
|
|
508
|
+
for the curated containers — re-run 'mishkan knowledge configure' to sync both.
|
|
505
509
|
${ollamaSection}
|
|
506
510
|
${sep}
|
|
507
511
|
Reaching a remote host (SSH tunnel)
|
|
@@ -539,7 +543,7 @@ ${sep}
|
|
|
539
543
|
Re-running this wizard
|
|
540
544
|
${sep}
|
|
541
545
|
|
|
542
|
-
npx mishkan-harness configure
|
|
546
|
+
mishkan knowledge configure (or: npx mishkan-harness knowledge configure)
|
|
543
547
|
|
|
544
548
|
Re-runs preserve the three local secrets (neo4j, pg, admin) so an
|
|
545
549
|
initialised neo4j volume keeps working. Both .env and ACCESS.txt are
|
|
@@ -551,18 +555,17 @@ regenerated; previous .env is backed up to .env.bak.
|
|
|
551
555
|
|
|
552
556
|
function installObservabilityStack() {
|
|
553
557
|
console.log();
|
|
554
|
-
console.log(c.bold(c.cyan("Observability
|
|
558
|
+
console.log(" " + c.bold(c.cyan("▸ Observability")) + c.dim(" live cross-session daemon + TUI"));
|
|
555
559
|
console.log(c.dim(
|
|
556
|
-
"
|
|
557
|
-
"
|
|
558
|
-
" Requires `uv` (https://astral.sh/uv) and Python 3.11+."));
|
|
560
|
+
" Aggregates the harness event bus into a live snapshot you can watch.\n" +
|
|
561
|
+
" Needs `uv` (https://astral.sh/uv) + Python 3.11+ · docs/design/MISHKAN_observability.md."));
|
|
559
562
|
|
|
560
563
|
if (!commandExists("uv")) {
|
|
561
564
|
console.log(c.yellow(" uv not found — skipping observability install."));
|
|
562
565
|
console.log(c.dim(
|
|
563
566
|
" Install uv with:\n" +
|
|
564
567
|
" curl -LsSf https://astral.sh/uv/install.sh | sh\n" +
|
|
565
|
-
" Then re-run: npx mishkan-harness observability"));
|
|
568
|
+
" Then re-run: npx mishkan-harness observability install"));
|
|
566
569
|
return { installed: false, reason: "uv-missing" };
|
|
567
570
|
}
|
|
568
571
|
|
|
@@ -585,15 +588,35 @@ function installObservabilityStack() {
|
|
|
585
588
|
{ stdio: "inherit" });
|
|
586
589
|
if (r2.status !== 0) { warn("mishkan-watch install failed"); return { installed: false, reason: "install-failed" }; }
|
|
587
590
|
|
|
588
|
-
console.log(c.green("
|
|
591
|
+
console.log(" " + c.green("✓ observability installed") + c.dim(" · 1 executable: mishkan-watch"));
|
|
589
592
|
console.log(c.dim(
|
|
590
|
-
"
|
|
591
|
-
"
|
|
592
|
-
"
|
|
593
|
-
"
|
|
593
|
+
" Open the TUI mishkan-watch (auto-starts the daemon)\n" +
|
|
594
|
+
" Two-terminal mishkan-watchd start, then mishkan-watch --no-autostart\n" +
|
|
595
|
+
" Stop the daemon mishkan-watchd stop\n" +
|
|
596
|
+
" Start at login mishkan-watchd install-service"));
|
|
594
597
|
return { installed: true };
|
|
595
598
|
}
|
|
596
599
|
|
|
600
|
+
// ─── post-install sign-off ───────────────────────────────────────────────────
|
|
601
|
+
// The wordmark + a short, builder-to-builder note. ASCII art (ANSI Shadow); the
|
|
602
|
+
// block glyphs are plain Unicode and render uncolored under NO_COLOR.
|
|
603
|
+
const WORDMARK = [
|
|
604
|
+
"███╗ ███╗██╗███████╗██╗ ██╗██╗ ██╗ █████╗ ███╗ ██╗",
|
|
605
|
+
"████╗ ████║██║██╔════╝██║ ██║██║ ██╔╝██╔══██╗████╗ ██║",
|
|
606
|
+
"██╔████╔██║██║███████╗███████║█████╔╝ ███████║██╔██╗ ██║",
|
|
607
|
+
"██║╚██╔╝██║██║╚════██║██╔══██║██╔═██╗ ██╔══██║██║╚██╗██║",
|
|
608
|
+
"██║ ╚═╝ ██║██║███████║██║ ██║██║ ██╗██║ ██║██║ ╚████║",
|
|
609
|
+
"╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝",
|
|
610
|
+
];
|
|
611
|
+
|
|
612
|
+
function printBanner(version) {
|
|
613
|
+
console.log();
|
|
614
|
+
for (const line of WORDMARK) console.log(" " + c.cyan(line));
|
|
615
|
+
console.log();
|
|
616
|
+
console.log(" " + c.bold("מִשְׁכָּן") + c.dim(" · a dwelling place for your engineering work"));
|
|
617
|
+
console.log(" " + c.dim(`v${version} · 45 agents · 6 teams · research pipeline · knowledge graph`));
|
|
618
|
+
}
|
|
619
|
+
|
|
597
620
|
async function install() {
|
|
598
621
|
const totalPhases = 7;
|
|
599
622
|
log(`installing into ${tilde(CLAUDE)} (home resolved at runtime)`);
|
|
@@ -606,6 +629,23 @@ async function install() {
|
|
|
606
629
|
ensureDir(join(MISHKAN, "logs"));
|
|
607
630
|
ensureDir(join(MISHKAN, "cognee"));
|
|
608
631
|
|
|
632
|
+
// D-017: place the model-routing overlay once (the engineer's tier overrides).
|
|
633
|
+
// copyDir ships no such file, so it is never clobbered on refresh — place-once,
|
|
634
|
+
// then preserve (same philosophy as engineer-standards.md). `mishkan model` edits it.
|
|
635
|
+
const overlayPath = join(MISHKAN, "config", "model-routing.local.yaml");
|
|
636
|
+
if (!existsSync(overlayPath)) {
|
|
637
|
+
ensureDir(join(MISHKAN, "config"));
|
|
638
|
+
writeFileSync(overlayPath,
|
|
639
|
+
"# MISHKAN model-routing OVERLAY (D-017) — your per-agent tier overrides.\n" +
|
|
640
|
+
"# Preserved across `mishkan install`. Managed by `mishkan model set/reset`\n" +
|
|
641
|
+
"# (hand-edits are fine). Entries here WIN over config/model-routing.yaml.\n" +
|
|
642
|
+
"# Empty = no overrides (shipped defaults apply). Tiers: opus, sonnet, haiku, fable.\n\n" +
|
|
643
|
+
"agents: {}\n");
|
|
644
|
+
log("placed model-routing overlay ~/.claude/mishkan/config/model-routing.local.yaml");
|
|
645
|
+
} else {
|
|
646
|
+
log("preserved your ~/.claude/mishkan/config/model-routing.local.yaml");
|
|
647
|
+
}
|
|
648
|
+
|
|
609
649
|
// D-011 Phase 2: rebuild the universal skill-discovery index at install
|
|
610
650
|
// time so the router has a live index.json before the first session boots.
|
|
611
651
|
// The SessionStart hook keeps it fresh thereafter; this seeds it.
|
|
@@ -677,7 +717,7 @@ async function install() {
|
|
|
677
717
|
if (await promptYN(" Install observability stack now?", true)) {
|
|
678
718
|
installObservabilityStack();
|
|
679
719
|
} else {
|
|
680
|
-
console.log(c.dim(" Skipped. Re-run later: npx mishkan-harness observability"));
|
|
720
|
+
console.log(c.dim(" Skipped. Re-run later: npx mishkan-harness observability install"));
|
|
681
721
|
}
|
|
682
722
|
|
|
683
723
|
// Auto-symlink ~/.local/bin/mishkan -> this package's bin/mishkan.js so
|
|
@@ -717,39 +757,58 @@ async function install() {
|
|
|
717
757
|
}
|
|
718
758
|
const pathHasLocalBin = (process.env.PATH || "").split(":").includes(localBin);
|
|
719
759
|
|
|
760
|
+
printBanner(version);
|
|
761
|
+
|
|
762
|
+
// Whether the bare `mishkan` command works yet (symlinked AND on PATH) decides
|
|
763
|
+
// how we spell the commands below — bare, or via `npx mishkan-harness`.
|
|
764
|
+
const onPath = directAccess === "linked" && pathHasLocalBin;
|
|
765
|
+
const m = onPath ? "mishkan" : "npx mishkan-harness";
|
|
766
|
+
|
|
720
767
|
console.log();
|
|
721
|
-
console.log(c.green(
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
"
|
|
735
|
-
"
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
"
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
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"));
|
|
768
|
+
console.log(" " + c.green("✓") + c.dim(" It's in place — every agent, rule, skill and command is"));
|
|
769
|
+
console.log(c.dim(" wired into ~/.claude and ready to work."));
|
|
770
|
+
|
|
771
|
+
console.log();
|
|
772
|
+
console.log(" " + c.bold(c.cyan("▸ Start here")));
|
|
773
|
+
console.log(c.dim(" Open a Claude session and talk to ") + "Nehemiah" + c.dim(" — your PM —"));
|
|
774
|
+
console.log(c.dim(" or run ") + "/mishkan-init" + c.dim(" inside a project to scaffold it."));
|
|
775
|
+
|
|
776
|
+
console.log();
|
|
777
|
+
console.log(" " + c.bold(c.cyan("▸ Commands")) + (onPath ? c.dim(" on your PATH now — no npx needed") : ""));
|
|
778
|
+
{
|
|
779
|
+
const rows = [
|
|
780
|
+
["knowledge configure", "LLM provider + cognee secrets"],
|
|
781
|
+
["knowledge-stack up", "bring the knowledge layer up (guided)"],
|
|
782
|
+
["status", "install state + live stack health"],
|
|
783
|
+
["project-work-store up", "this project's own store"],
|
|
784
|
+
["knowledge ingest docs/…", "add docs to memory"],
|
|
785
|
+
["observability open", "the live TUI"],
|
|
786
|
+
];
|
|
787
|
+
const w = Math.max(...rows.map(([s]) => `${m} ${s}`.length)) + 3;
|
|
788
|
+
for (const [s, d] of rows) console.log(" " + `${m} ${s}`.padEnd(w) + c.dim(d));
|
|
752
789
|
}
|
|
790
|
+
if (directAccess === "linked" && !pathHasLocalBin)
|
|
791
|
+
console.log(c.dim(" (add ~/.local/bin to PATH for the bare `mishkan` command)"));
|
|
792
|
+
else if (directAccess !== "linked")
|
|
793
|
+
console.log(c.dim(` (for a bare \`mishkan\`: ln -sf ${linkTarget} ~/.local/bin/mishkan)`));
|
|
794
|
+
|
|
795
|
+
// Knowledge stack (Cognee) is opt-in. The three stores answer three different
|
|
796
|
+
// questions (D-008/D-012) — name each one plainly here, then the two bring-up
|
|
797
|
+
// steps. The wizard + ACCESS.txt carry the full URL/cred detail.
|
|
798
|
+
console.log();
|
|
799
|
+
console.log(" " + c.bold(c.cyan("▸ Knowledge")) + c.dim(" three cognee stores + your code graph — optional, opt-in"));
|
|
800
|
+
console.log(" " + "memory".padEnd(9) + c.cyan(":7777") + c.dim(" what you learn across your sessions — kept and shared"));
|
|
801
|
+
console.log(" " + "curated".padEnd(9) + c.cyan(":7730") + c.dim(" a reference library you mostly read from"));
|
|
802
|
+
console.log(" " + "work".padEnd(9) + " " + c.dim("this project's private notes — never shared with other"));
|
|
803
|
+
console.log(c.dim(" projects; created the first time you run /mishkan-init"));
|
|
804
|
+
console.log(" " + "graphify".padEnd(9) + " " + c.dim("this project's code structure — a separate CLI, no server"));
|
|
805
|
+
console.log(c.dim(" (refresh with `mishkan code-graph scan`)"));
|
|
806
|
+
console.log();
|
|
807
|
+
console.log(c.dim(" Bring it up — two commands:"));
|
|
808
|
+
console.log(c.dim(" 1. ") + `${m} knowledge configure` + c.dim(" → writes .env + ACCESS.txt"));
|
|
809
|
+
console.log(c.dim(" 2. ") + `${m} knowledge-stack up` + c.dim(" → starts memory + curated (~5min first boot)"));
|
|
810
|
+
console.log(c.dim(" Aliases cognee-memory / cognee-curated · full URLs + creds in ACCESS.txt."));
|
|
811
|
+
console.log();
|
|
753
812
|
}
|
|
754
813
|
|
|
755
814
|
function uninstallObservabilityHint() {
|
|
@@ -820,7 +879,10 @@ function status() {
|
|
|
820
879
|
log(`installed v${st.version} (${st.installedAt})`);
|
|
821
880
|
log(`linked: agents=${count("agents")}, skills=${count("skills")}, commands=${count("commands")}, mishkan hooks=${hooks}`);
|
|
822
881
|
log(`runtime profile: ${existsSync(join(MISHKAN,"profile.md")) ? "present" : "MISSING"}`);
|
|
823
|
-
log(`cognee dir: ${existsSync(join(MISHKAN,"cognee")) ? "present
|
|
882
|
+
log(`cognee dir: ${existsSync(join(MISHKAN,"cognee")) ? "present" : "missing"}`);
|
|
883
|
+
// Fold in live knowledge-stack health so `mishkan status` answers "how's mishkan"
|
|
884
|
+
// (install + infra) in one view; `mishkan knowledge-stack status` is the detail.
|
|
885
|
+
if (existsSync(join(MISHKAN, "cognee")) && commandExists("docker")) printStackHealth();
|
|
824
886
|
}
|
|
825
887
|
|
|
826
888
|
// ─── org reference ─────────────────────────────────────────────────────────
|
|
@@ -919,16 +981,437 @@ function printOrgRef({ json = false } = {}) {
|
|
|
919
981
|
console.log(c.dim(`source: ${data.generated_from || "docs/design/MISHKAN_agent_aliases.md"}`));
|
|
920
982
|
}
|
|
921
983
|
|
|
984
|
+
// ─── backend control surface (ADR D-015) ────────────────────────────────────
|
|
985
|
+
// One SEMANTIC control surface for the cognee knowledge layer the engineer
|
|
986
|
+
// otherwise drives by hand across scattered compose files + scripts. Every
|
|
987
|
+
// command is `mishkan <object> <verb>`. The CLI EXECUTES because the HUMAN
|
|
988
|
+
// invokes it — rule 5 forbids *agents* from running stateful ops, not the
|
|
989
|
+
// engineer's own tool (agents never get this bin; the TUI only surfaces, never
|
|
990
|
+
// runs). Destructive ops gate on a confirm. Wraps the real scripts/compose — it
|
|
991
|
+
// does not replace them, so the scripts stay the single source of truth.
|
|
992
|
+
const COGNEE_DIR = join(MISHKAN, "cognee");
|
|
993
|
+
const SCRIPTS_DIR = join(MISHKAN, "scripts");
|
|
994
|
+
// knowledge-stack overlay order (base → hardening → selfhosted backends).
|
|
995
|
+
const STACK_FILES = ["docker-compose.yml", "docker-compose.hardening.yml", "docker-compose.selfhosted.yml"];
|
|
996
|
+
const stackComposeArgs = () => STACK_FILES.flatMap(f => ["-f", f]);
|
|
997
|
+
|
|
998
|
+
function requireCogneeDir() {
|
|
999
|
+
if (!existsSync(COGNEE_DIR)) {
|
|
1000
|
+
console.error(c.red("cognee dir not found: ") + tilde(COGNEE_DIR));
|
|
1001
|
+
console.log("run the installer first: " + c.bold("mishkan install"));
|
|
1002
|
+
process.exit(1);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
// Derive a project slug from the current dir (matches the scripts' sanitisation).
|
|
1007
|
+
function slugFromCwd() {
|
|
1008
|
+
return (process.cwd().split("/").pop() || "")
|
|
1009
|
+
.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// Live health of every mishkan-* container. Used by `knowledge-stack status`
|
|
1013
|
+
// and folded into `mishkan status`.
|
|
1014
|
+
function printStackHealth() {
|
|
1015
|
+
const r = spawnSync("docker",
|
|
1016
|
+
["ps", "-a", "--filter", "name=mishkan-", "--format", "{{.Names}}\t{{.Status}}"],
|
|
1017
|
+
{ encoding: "utf8" });
|
|
1018
|
+
if (r.status !== 0) { console.log(" " + c.dim("(docker not available — knowledge-stack health unknown)")); return; }
|
|
1019
|
+
const lines = (r.stdout || "").trim();
|
|
1020
|
+
console.log("");
|
|
1021
|
+
console.log(" " + c.bold("knowledge-stack — containers"));
|
|
1022
|
+
console.log("");
|
|
1023
|
+
if (!lines) { console.log(" " + c.dim("none up — start it: ") + c.bold("mishkan knowledge-stack up")); console.log(""); return; }
|
|
1024
|
+
for (const line of lines.split("\n")) {
|
|
1025
|
+
const [name, ...rest] = line.split("\t");
|
|
1026
|
+
const status = rest.join(" ");
|
|
1027
|
+
const dot = /unhealthy/.test(status) ? c.yellow("⚠")
|
|
1028
|
+
: /Exited|Created|Dead|Restarting/.test(status) ? c.red("✗")
|
|
1029
|
+
: c.green("●");
|
|
1030
|
+
console.log(" " + dot + " " + (name || "").padEnd(28) + c.dim(status));
|
|
1031
|
+
}
|
|
1032
|
+
console.log("");
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// Preflight: what's missing before the knowledge-stack can come up. Each gap
|
|
1036
|
+
// carries a one-line fix, so `up` never dumps a cryptic docker error — it tells
|
|
1037
|
+
// you what to finish and stops, instead of failing halfway.
|
|
1038
|
+
function preflightStack() {
|
|
1039
|
+
const gaps = [];
|
|
1040
|
+
if (!commandExists("docker")) {
|
|
1041
|
+
gaps.push(["Docker isn't available on PATH", "install Docker and start the daemon, then re-run"]);
|
|
1042
|
+
}
|
|
1043
|
+
const env = join(COGNEE_DIR, ".env");
|
|
1044
|
+
if (!existsSync(env)) {
|
|
1045
|
+
gaps.push(["cognee .env not configured (LLM provider + local secrets)",
|
|
1046
|
+
"run " + c.bold("mishkan knowledge configure")]);
|
|
1047
|
+
} else {
|
|
1048
|
+
let txt = ""; try { txt = readFileSync(env, "utf8"); } catch {}
|
|
1049
|
+
if (!/^\s*COGNEE_MCP_REF=\S/m.test(txt)) {
|
|
1050
|
+
gaps.push(["COGNEE_MCP_REF (pinned cognee image tag) not set in cognee/.env",
|
|
1051
|
+
"run " + c.bold("mishkan knowledge configure")]);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
return gaps;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
function guideGaps(gaps, retryCmd) {
|
|
1058
|
+
console.log(c.yellow("\n Not ready yet — finish setup first:\n"));
|
|
1059
|
+
gaps.forEach(([problem, fix], i) => {
|
|
1060
|
+
console.log(" " + c.bold(`${i + 1}. ${problem}`));
|
|
1061
|
+
console.log(" " + c.dim("→ ") + fix + "\n");
|
|
1062
|
+
});
|
|
1063
|
+
console.log(" Then re-run " + c.bold(retryCmd) + ".\n");
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
async function stackUp(opts) {
|
|
1067
|
+
requireCogneeDir();
|
|
1068
|
+
const gaps = preflightStack();
|
|
1069
|
+
if (gaps.length) { guideGaps(gaps, "mishkan knowledge-stack up"); process.exit(1); }
|
|
1070
|
+
const build = opts.has("--build");
|
|
1071
|
+
const args = ["compose", ...stackComposeArgs(), "up", "-d"];
|
|
1072
|
+
if (build) args.push("--build");
|
|
1073
|
+
log("starting the knowledge-stack" + (build ? " (rebuilding image — first build only)…" : "…"));
|
|
1074
|
+
const r = spawnSync("docker", args, { stdio: "inherit", cwd: COGNEE_DIR });
|
|
1075
|
+
if (r.status !== 0) { warn("knowledge-stack up failed"); process.exit(r.status || 1); }
|
|
1076
|
+
log("ensuring the curated reference box…");
|
|
1077
|
+
spawnSync("bash", [join(SCRIPTS_DIR, "ensure-curated-box.sh")], { stdio: "inherit", cwd: COGNEE_DIR });
|
|
1078
|
+
console.log(c.green("\n✓ knowledge-stack up.") + c.dim(" First boot is slow (~4-5 min) — check with ") + c.bold("mishkan status") + c.dim("."));
|
|
1079
|
+
console.log(" Then you can:");
|
|
1080
|
+
console.log(" " + c.bold("mishkan project-work-store up") + c.dim(" provision this project's private store"));
|
|
1081
|
+
console.log(" " + c.bold("mishkan knowledge ingest docs/…") + c.dim(" add documents to memory"));
|
|
1082
|
+
console.log(" " + c.bold("mishkan observability open") + c.dim(" watch it live\n"));
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
async function stackDown({ confirm = true } = {}) {
|
|
1086
|
+
requireCogneeDir();
|
|
1087
|
+
if (confirm) {
|
|
1088
|
+
const ok = await promptYN("Stop the knowledge-stack? Containers stop; volumes/data survive.", false);
|
|
1089
|
+
if (!ok) { console.log("aborted."); return; }
|
|
1090
|
+
}
|
|
1091
|
+
spawnSync("docker", ["compose", ...stackComposeArgs(), "down"], { stdio: "inherit", cwd: COGNEE_DIR });
|
|
1092
|
+
// The curated box is its own compose project — bring it down too.
|
|
1093
|
+
if (existsSync(join(COGNEE_DIR, ".env.curated"))) {
|
|
1094
|
+
spawnSync("docker", ["compose", "--env-file", ".env.curated", "-f", "docker-compose.curated.yml", "down"],
|
|
1095
|
+
{ stdio: "inherit", cwd: COGNEE_DIR });
|
|
1096
|
+
}
|
|
1097
|
+
console.log(c.green("✓ knowledge-stack stopped.") + c.dim(" (project work stores are separate — `mishkan project-work-store down`)"));
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
async function knowledgeStackCmd(argv) {
|
|
1101
|
+
const sub = argv[0] || "status";
|
|
1102
|
+
const opts = new Set(argv.slice(1));
|
|
1103
|
+
if (sub === "status") { requireCogneeDir(); printStackHealth(); return; }
|
|
1104
|
+
if (sub === "up") { await stackUp(opts); return; }
|
|
1105
|
+
if (sub === "down") { await stackDown({ confirm: true }); return; }
|
|
1106
|
+
if (sub === "restart") { await stackDown({ confirm: false }); await stackUp(opts); return; }
|
|
1107
|
+
console.error("unknown subcommand: " + sub);
|
|
1108
|
+
console.log("use one of: up [--build] | down | restart | status");
|
|
1109
|
+
process.exit(1);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
function knowledgeIngest(argv) {
|
|
1113
|
+
const script = join(SCRIPTS_DIR, "mishkan-ingest.sh");
|
|
1114
|
+
if (!existsSync(script)) { console.error(c.red("mishkan-ingest.sh not found — run `mishkan install` first.")); process.exit(1); }
|
|
1115
|
+
// Run in the user's CWD: the script derives the project store from basename($PWD).
|
|
1116
|
+
const r = spawnSync("bash", [script, ...argv], { stdio: "inherit", cwd: process.cwd() });
|
|
1117
|
+
process.exit(r.status || 0);
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// D-016 — engineer-gated promotion of research-found resources into the shared
|
|
1121
|
+
// curated library. Baruch queues candidates (one JSON object per line) into
|
|
1122
|
+
// ~/.claude/mishkan/curated-candidates.jsonl; this walks them, asks per candidate,
|
|
1123
|
+
// and on approval runs the ADDITIVE promote-curated.sh (no prune, dedup by url).
|
|
1124
|
+
// Stateful by design — a human runs this CLI; agents never get the bin (rule 5).
|
|
1125
|
+
async function knowledgeCurate() {
|
|
1126
|
+
const queue = join(MISHKAN, "curated-candidates.jsonl");
|
|
1127
|
+
if (!existsSync(queue)) {
|
|
1128
|
+
console.log("No pending curated-library candidates.");
|
|
1129
|
+
console.log(c.dim(" Baruch queues them here when a resolved research run finds a reusable,"));
|
|
1130
|
+
console.log(c.dim(" not-yet-curated resource: " + tilde(queue)));
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
const lines = readFileSync(queue, "utf8").split("\n").map(s => s.trim()).filter(Boolean);
|
|
1134
|
+
if (lines.length === 0) { console.log("No pending curated-library candidates."); return; }
|
|
1135
|
+
const script = join(SCRIPTS_DIR, "promote-curated.sh");
|
|
1136
|
+
if (!existsSync(script)) { console.error(c.red("promote-curated.sh not found — run `mishkan install` first.")); process.exit(1); }
|
|
1137
|
+
|
|
1138
|
+
const tmp = join(COGNEE_DIR, ".curate-candidate.json");
|
|
1139
|
+
const remaining = []; // kept for retry (e.g. container down)
|
|
1140
|
+
let approved = 0, rejected = 0;
|
|
1141
|
+
const processedOut = [];
|
|
1142
|
+
for (const line of lines) {
|
|
1143
|
+
let cand;
|
|
1144
|
+
try { cand = JSON.parse(line); }
|
|
1145
|
+
catch { console.error(c.red("skipping malformed queue line: ") + line.slice(0, 80)); continue; }
|
|
1146
|
+
console.log();
|
|
1147
|
+
console.log(c.bold(cand.name || "(unnamed)") + c.dim(" [" + (cand.team || "?") + " · " + (cand.problem_class || "?") + "]"));
|
|
1148
|
+
console.log(" " + (cand.url || c.red("(no url)")));
|
|
1149
|
+
if (cand.why) console.log(c.dim(" why: " + cand.why));
|
|
1150
|
+
const ok = await promptYN("Promote this into the shared curated library?", false);
|
|
1151
|
+
if (!ok) { rejected++; processedOut.push(JSON.stringify({ ...cand, decision: "rejected" })); console.log(c.dim(" rejected — nothing written.")); continue; }
|
|
1152
|
+
writeFileSync(tmp, JSON.stringify(cand) + "\n");
|
|
1153
|
+
const r = spawnSync("bash", [script, tmp], { stdio: "inherit" });
|
|
1154
|
+
if (r.status === 0) { approved++; processedOut.push(JSON.stringify({ ...cand, decision: "approved" })); }
|
|
1155
|
+
else { console.error(c.red(" promotion failed (see above) — kept in the queue for retry.")); remaining.push(line); }
|
|
1156
|
+
}
|
|
1157
|
+
try { if (existsSync(tmp)) rmSync(tmp); } catch { /* best-effort cleanup */ }
|
|
1158
|
+
// Rewrite the queue with only the lines kept for retry; record decisions durably.
|
|
1159
|
+
writeFileSync(queue, remaining.length ? remaining.join("\n") + "\n" : "");
|
|
1160
|
+
if (processedOut.length) {
|
|
1161
|
+
const log = join(MISHKAN, "curated-candidates.processed.jsonl");
|
|
1162
|
+
const prev = existsSync(log) ? readFileSync(log, "utf8") : "";
|
|
1163
|
+
writeFileSync(log, prev + processedOut.join("\n") + "\n");
|
|
1164
|
+
}
|
|
1165
|
+
console.log();
|
|
1166
|
+
console.log(c.green(`curate: ${approved} approved, ${rejected} rejected`) + (remaining.length ? c.dim(`, ${remaining.length} kept for retry`) : ""));
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
// D-017 cleanup — full reset of the knowledge layer to the stable baseline.
|
|
1170
|
+
// Wipes all work stores, prunes cognee-memory, re-seeds curated. Stateful
|
|
1171
|
+
// (docker rm / docker exec): the human runs it; the script type-to-confirms.
|
|
1172
|
+
function knowledgeReset(argv) {
|
|
1173
|
+
const script = join(SCRIPTS_DIR, "reset-knowledge-data.sh");
|
|
1174
|
+
if (!existsSync(script)) { console.error(c.red("reset-knowledge-data.sh not found — run `mishkan install` first.")); process.exit(1); }
|
|
1175
|
+
warn("FULL knowledge-data reset — wipes all work stores, prunes memory, re-seeds curated to baseline.");
|
|
1176
|
+
const r = spawnSync("bash", [script, ...argv], { stdio: "inherit" });
|
|
1177
|
+
process.exit(r.status || 0);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
async function knowledgeCmd(argv) {
|
|
1181
|
+
const sub = argv[0];
|
|
1182
|
+
if (sub === "configure") { await configureKnowledge(); return; }
|
|
1183
|
+
if (sub === "ingest") { knowledgeIngest(argv.slice(1)); return; }
|
|
1184
|
+
if (sub === "curate") { await knowledgeCurate(); return; }
|
|
1185
|
+
if (sub === "reset") { knowledgeReset(argv.slice(1)); return; }
|
|
1186
|
+
console.error("usage: mishkan knowledge <configure | ingest [paths…] | curate | reset>");
|
|
1187
|
+
console.log(" configure wizard: LLM provider + cognee secrets");
|
|
1188
|
+
console.log(" ingest [paths…] add docs to THIS project's store");
|
|
1189
|
+
console.log(" curate review + approve research-found resources into the shared curated library (D-016)");
|
|
1190
|
+
console.log(" reset wipe all stores → re-seed curated to the stable baseline (destructive; confirms)");
|
|
1191
|
+
process.exit(1);
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
async function projectWorkStoreCmd(argv) {
|
|
1195
|
+
requireCogneeDir();
|
|
1196
|
+
const VERBS = new Set(["up", "down", "reset"]);
|
|
1197
|
+
let slug, sub;
|
|
1198
|
+
if (argv.length >= 2 && VERBS.has(argv[1])) { slug = argv[0]; sub = argv[1]; }
|
|
1199
|
+
else if (argv.length === 1 && VERBS.has(argv[0])) { slug = slugFromCwd(); sub = argv[0]; }
|
|
1200
|
+
else { console.error("usage: mishkan project-work-store [<slug>] <up|down|reset>"); process.exit(1); }
|
|
1201
|
+
if (!slug) { console.error("could not derive a project slug from the current directory; pass one explicitly."); process.exit(1); }
|
|
1202
|
+
const container = `mishkan-work-${slug}`;
|
|
1203
|
+
const volume = `${container}_work_data`;
|
|
1204
|
+
if (sub === "up") {
|
|
1205
|
+
const ps = spawnSync("docker", ["ps", "--format", "{{.Names}}"], { encoding: "utf8" });
|
|
1206
|
+
if (ps.status === 0 && !/mishkan-ollama|mishkan-cognee-mcp/.test(ps.stdout || "")) {
|
|
1207
|
+
warn("the knowledge-stack looks down — start it first: " + c.bold("mishkan knowledge-stack up"));
|
|
1208
|
+
}
|
|
1209
|
+
const r = spawnSync("bash", [join(SCRIPTS_DIR, "ensure-work-store.sh"), slug], { stdio: "inherit", cwd: COGNEE_DIR });
|
|
1210
|
+
if (r.status === 0) console.log(c.green(`✓ work store '${slug}' up.`) + c.dim(" add docs: ") + c.bold("mishkan knowledge ingest docs/…"));
|
|
1211
|
+
process.exit(r.status || 0);
|
|
1212
|
+
}
|
|
1213
|
+
if (sub === "down") {
|
|
1214
|
+
log(`removing ${container} (data volume ${volume} kept)…`);
|
|
1215
|
+
const r = spawnSync("docker", ["rm", "-f", container], { stdio: "inherit" });
|
|
1216
|
+
process.exit(r.status || 0);
|
|
1217
|
+
}
|
|
1218
|
+
if (sub === "reset") {
|
|
1219
|
+
const ok = await promptYN(`RESET ${slug}? Removes ${container} AND wipes its data volume ${volume}.`, false);
|
|
1220
|
+
if (!ok) { console.log("aborted."); return; }
|
|
1221
|
+
spawnSync("docker", ["rm", "-f", container], { stdio: "inherit" });
|
|
1222
|
+
const vr = spawnSync("docker", ["volume", "rm", volume], { stdio: "inherit" });
|
|
1223
|
+
if (vr.status !== 0) warn(`volume ${volume} not removed (already gone?)`);
|
|
1224
|
+
console.log(c.green(`✓ ${slug} reset.`) + c.dim(" re-provision: ") + c.bold(`mishkan project-work-store ${slug} up`));
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
// unreachable given the VERBS guard above — explicit so a future verb can't fall through silently
|
|
1228
|
+
console.error("internal error: unexpected project-work-store verb: " + sub);
|
|
1229
|
+
process.exit(1);
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
function openWatchTui(argv) {
|
|
1233
|
+
if (!commandExists("mishkan-watch")) {
|
|
1234
|
+
console.error(c.red("mishkan-watch not on PATH."));
|
|
1235
|
+
console.log("install it: " + c.bold("mishkan observability install"));
|
|
1236
|
+
process.exit(1);
|
|
1237
|
+
}
|
|
1238
|
+
const r = spawnSync("mishkan-watch", argv, { stdio: "inherit" });
|
|
1239
|
+
process.exit(r.status || 0);
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
function observabilityCmd(argv) {
|
|
1243
|
+
const sub = argv[0] || "install";
|
|
1244
|
+
if (sub === "install") { installObservabilityStack(); return; }
|
|
1245
|
+
if (sub === "open") { openWatchTui(argv.slice(1)); return; }
|
|
1246
|
+
console.error("usage: mishkan observability <install | open>");
|
|
1247
|
+
process.exit(1);
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
function orgCmd(argv) {
|
|
1251
|
+
const sub = argv[0] || "show";
|
|
1252
|
+
if (sub === "show") { printOrgRef({ json: argv.includes("--json") }); return; }
|
|
1253
|
+
console.error("usage: mishkan org show [--json]");
|
|
1254
|
+
process.exit(1);
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
// D-017 — user-editable model-tier routing. The hook (hooks/model-route.py) reads
|
|
1258
|
+
// the shipped default (config/model-routing.yaml) then overlays the engineer's
|
|
1259
|
+
// overrides (config/model-routing.local.yaml, preserved across installs). This
|
|
1260
|
+
// command edits ONLY the overlay — never the shipped default, never the 45
|
|
1261
|
+
// agent frontmatter files. So defaults keep flowing on update while your deltas persist.
|
|
1262
|
+
const ROUTING_DEFAULT = join(MISHKAN, "config", "model-routing.yaml");
|
|
1263
|
+
const ROUTING_LOCAL = join(MISHKAN, "config", "model-routing.local.yaml");
|
|
1264
|
+
const ORG_JSON = join(MISHKAN, "org", "org.json");
|
|
1265
|
+
const TIERS = new Set(["opus", "sonnet", "haiku", "fable"]);
|
|
1266
|
+
const DORMANT_TIERS = new Set(["fable"]); // valid but currently unavailable (Fable 5 suspended 2026-06-12)
|
|
1267
|
+
|
|
1268
|
+
// Minimal reader matching hooks/model-route.py parse_routing: agents map + default tier.
|
|
1269
|
+
function readRouting(path) {
|
|
1270
|
+
const agents = {};
|
|
1271
|
+
let dflt = "sonnet", section = null;
|
|
1272
|
+
if (!existsSync(path)) return { agents, dflt };
|
|
1273
|
+
for (const raw of readFileSync(path, "utf8").split("\n")) {
|
|
1274
|
+
const line = raw.split("#")[0].replace(/\s+$/, "");
|
|
1275
|
+
if (!line.trim()) continue;
|
|
1276
|
+
if (!/^[ \t]/.test(line)) { section = line.trim().replace(/:$/, ""); continue; }
|
|
1277
|
+
const idx = line.indexOf(":");
|
|
1278
|
+
if (idx < 0) continue;
|
|
1279
|
+
const key = line.slice(0, idx).trim();
|
|
1280
|
+
const val = line.slice(idx + 1).trim();
|
|
1281
|
+
if (!val) continue;
|
|
1282
|
+
if (section === "defaults" && key === "unlisted_agent" && TIERS.has(val)) dflt = val;
|
|
1283
|
+
else if (section === "agents" && TIERS.has(val)) agents[key] = val;
|
|
1284
|
+
}
|
|
1285
|
+
return { agents, dflt };
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
function writeOverlay(map) {
|
|
1289
|
+
const keys = Object.keys(map).sort();
|
|
1290
|
+
let out =
|
|
1291
|
+
"# MISHKAN model-routing OVERLAY (D-017) — your per-agent tier overrides.\n" +
|
|
1292
|
+
"# Preserved across `mishkan install`. Managed by `mishkan model set/reset`\n" +
|
|
1293
|
+
"# (hand-edits are fine). Same shape as model-routing.yaml; entries here WIN\n" +
|
|
1294
|
+
"# over the shipped default. Empty = no overrides. Tiers: opus, sonnet, haiku, fable.\n\n";
|
|
1295
|
+
out += keys.length ? "agents:\n" + keys.map(k => ` ${k}: ${map[k]}`).join("\n") + "\n" : "agents: {}\n";
|
|
1296
|
+
ensureDir(dirname(ROUTING_LOCAL));
|
|
1297
|
+
writeFileSync(ROUTING_LOCAL, out);
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
function allMishkanAgents() { return Object.keys(readRouting(ROUTING_DEFAULT).agents).sort(); }
|
|
1301
|
+
|
|
1302
|
+
function teamAgents(teamId) {
|
|
1303
|
+
if (!existsSync(ORG_JSON)) return null;
|
|
1304
|
+
let org; try { org = JSON.parse(readFileSync(ORG_JSON, "utf8")); } catch { return null; }
|
|
1305
|
+
const t = teamId.toLowerCase();
|
|
1306
|
+
for (const g of (org.groups || [])) {
|
|
1307
|
+
if ((g.id || "").toLowerCase() === t || (g.label || "").toLowerCase() === t) {
|
|
1308
|
+
return (g.agents || []).map(a => (typeof a === "string" ? a : a.alias)).filter(Boolean);
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
return null;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
// Resolve <agent|team|all> to a list of known MISHKAN agent aliases.
|
|
1315
|
+
function resolveTargets(target, all) {
|
|
1316
|
+
if (target === "all") return all;
|
|
1317
|
+
if (all.includes(target)) return [target];
|
|
1318
|
+
const team = teamAgents(target);
|
|
1319
|
+
if (team && team.length) return team.filter(a => all.includes(a));
|
|
1320
|
+
return null;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
async function modelCmd(argv) {
|
|
1324
|
+
const sub = argv[0] || "show";
|
|
1325
|
+
const def = readRouting(ROUTING_DEFAULT);
|
|
1326
|
+
const overlay = readRouting(ROUTING_LOCAL).agents;
|
|
1327
|
+
|
|
1328
|
+
if (sub === "show") {
|
|
1329
|
+
const names = allMishkanAgents();
|
|
1330
|
+
if (names.length === 0) { console.error(c.red("no model-routing.yaml found — run `mishkan install` first.")); process.exit(1); }
|
|
1331
|
+
const nOver = Object.keys(overlay).length;
|
|
1332
|
+
console.log(c.bold("MISHKAN model-tier routing") + c.dim(" " + (nOver ? nOver + " override(s)" : "no overrides — shipped defaults")));
|
|
1333
|
+
let dormantSeen = false;
|
|
1334
|
+
for (const n of names) {
|
|
1335
|
+
const eff = overlay[n] || def.agents[n];
|
|
1336
|
+
const overridden = overlay[n] && overlay[n] !== def.agents[n];
|
|
1337
|
+
const dormant = DORMANT_TIERS.has(eff);
|
|
1338
|
+
if (dormant) dormantSeen = true;
|
|
1339
|
+
const mark = overridden ? c.bold(" ←override (was " + def.agents[n] + ")") : "";
|
|
1340
|
+
const warnTxt = dormant ? c.red(" ⚠ DORMANT — " + eff + " unavailable, will fail to spawn") : "";
|
|
1341
|
+
console.log(" " + n.padEnd(12) + " " + eff.padEnd(7) + mark + warnTxt);
|
|
1342
|
+
}
|
|
1343
|
+
console.log(c.dim("\n set: mishkan model set <agent|team|all> <tier> · revert: mishkan model reset [target]"));
|
|
1344
|
+
if (dormantSeen) console.log(c.red(" ⚠ agent(s) route to a dormant tier — re-tier them (mishkan model set …) or restore access."));
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
if (sub === "set") {
|
|
1349
|
+
const target = argv[1], tier = argv[2];
|
|
1350
|
+
if (!target || !tier) { console.error("usage: mishkan model set <agent|team|all> <tier>"); process.exit(1); }
|
|
1351
|
+
if (!TIERS.has(tier)) { console.error(c.red(`invalid tier '${tier}' — valid: opus, sonnet, haiku, fable`)); process.exit(1); }
|
|
1352
|
+
const all = allMishkanAgents();
|
|
1353
|
+
const targets = resolveTargets(target, all);
|
|
1354
|
+
if (!targets) { console.error(c.red(`unknown agent or team '${target}'.`) + c.dim(" see: mishkan model show / mishkan org show")); process.exit(1); }
|
|
1355
|
+
if (DORMANT_TIERS.has(tier)) {
|
|
1356
|
+
warn(`'${tier}' is DORMANT — Claude Fable 5 was suspended 2026-06-12; agents routed here will fail to spawn.`);
|
|
1357
|
+
const ok = await promptYN(`Route ${targets.length} agent(s) to '${tier}' anyway?`, false);
|
|
1358
|
+
if (!ok) { console.log("aborted."); return; }
|
|
1359
|
+
}
|
|
1360
|
+
for (const a of targets) overlay[a] = tier;
|
|
1361
|
+
writeOverlay(overlay);
|
|
1362
|
+
console.log(c.green(`✓ set ${targets.length} agent(s) → ${tier}`) + c.dim(" overlay: " + tilde(ROUTING_LOCAL)));
|
|
1363
|
+
console.log(c.dim(" live on the next delegation (hook reads the overlay) · survives `mishkan install`."));
|
|
1364
|
+
return;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
if (sub === "reset") {
|
|
1368
|
+
const target = argv[1];
|
|
1369
|
+
if (!target) {
|
|
1370
|
+
const n = Object.keys(overlay).length;
|
|
1371
|
+
if (n === 0) { console.log("no overrides to reset."); return; }
|
|
1372
|
+
const ok = await promptYN(`Clear ALL ${n} routing override(s) — revert the whole fleet to shipped defaults?`, false);
|
|
1373
|
+
if (!ok) { console.log("aborted."); return; }
|
|
1374
|
+
writeOverlay({});
|
|
1375
|
+
console.log(c.green(`✓ cleared ${n} override(s) — fleet back to shipped defaults.`));
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
const all = allMishkanAgents();
|
|
1379
|
+
const targets = target === "all" ? Object.keys(overlay) : (resolveTargets(target, all) || []);
|
|
1380
|
+
let removed = 0;
|
|
1381
|
+
for (const a of targets) if (overlay[a]) { delete overlay[a]; removed++; }
|
|
1382
|
+
writeOverlay(overlay);
|
|
1383
|
+
console.log(removed ? c.green(`✓ reset ${removed} override(s) → shipped default.`) : `no overrides on '${target}'.`);
|
|
1384
|
+
return;
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
console.error("usage: mishkan model <show | set <agent|team|all> <tier> | reset [agent|team|all]>");
|
|
1388
|
+
console.log(" show effective tier per agent (shipped default + your overrides)");
|
|
1389
|
+
console.log(" set <target> <tier> override a tier — target is an agent alias, a team id, or 'all'");
|
|
1390
|
+
console.log(" reset [target] drop override(s); no target = clear them all");
|
|
1391
|
+
console.log(" tiers: opus · sonnet · haiku · fable" + c.dim(" (fable dormant — suspended 2026-06-12)"));
|
|
1392
|
+
process.exit(1);
|
|
1393
|
+
}
|
|
1394
|
+
|
|
922
1395
|
const cmd = process.argv[2];
|
|
923
1396
|
const flags = new Set(process.argv.slice(3));
|
|
924
1397
|
switch (cmd) {
|
|
1398
|
+
// self-management — object is the harness itself (bare verbs; `npx mishkan-harness <verb>`)
|
|
925
1399
|
case "install": await install(); break;
|
|
926
1400
|
case "uninstall": uninstall({ purge: flags.has("--purge") }); break;
|
|
927
1401
|
case "status": status(); break;
|
|
928
|
-
|
|
929
|
-
case "
|
|
930
|
-
case "
|
|
1402
|
+
// subsystems — mishkan <object> <verb>
|
|
1403
|
+
case "knowledge": await knowledgeCmd(process.argv.slice(3)); break;
|
|
1404
|
+
case "knowledge-stack": await knowledgeStackCmd(process.argv.slice(3)); break;
|
|
1405
|
+
case "project-work-store": await projectWorkStoreCmd(process.argv.slice(3)); break;
|
|
931
1406
|
case "code-graph": codeGraphCmd(process.argv.slice(3)); break;
|
|
1407
|
+
case "observability": observabilityCmd(process.argv.slice(3)); break;
|
|
1408
|
+
case "org": orgCmd(process.argv.slice(3)); break;
|
|
1409
|
+
case "model": await modelCmd(process.argv.slice(3)); break;
|
|
1410
|
+
// deprecated flat aliases — kept working (not advertised) so nothing breaks mid-migration
|
|
1411
|
+
case "configure-knowledge": await knowledgeCmd(["configure"]); break;
|
|
1412
|
+
case "ingest": knowledgeIngest(process.argv.slice(3)); break;
|
|
1413
|
+
case "watch": openWatchTui(process.argv.slice(3)); break;
|
|
1414
|
+
case "org-ref": orgCmd(["show", ...(flags.has("--json") ? ["--json"] : [])]); break;
|
|
932
1415
|
case "help": case "--help": case "-h":
|
|
933
1416
|
printHelp(); break;
|
|
934
1417
|
default:
|
|
@@ -964,24 +1447,24 @@ function printHelp() {
|
|
|
964
1447
|
console.log(c.dim(" After `install` the harness can symlink for direct access automatically."));
|
|
965
1448
|
}
|
|
966
1449
|
console.log("");
|
|
967
|
-
console.log(c.bold("
|
|
968
|
-
console.log(" " + c.bold(`${prefix} install`)
|
|
969
|
-
console.log(" " + c.bold(`${prefix}
|
|
970
|
-
console.log(" " + c.bold(`${prefix}
|
|
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)");
|
|
1450
|
+
console.log(c.bold("Manage the harness"));
|
|
1451
|
+
console.log(" " + c.bold(`${prefix} install`) + " Install / refresh into ~/.claude (idempotent)");
|
|
1452
|
+
console.log(" " + c.bold(`${prefix} uninstall [--purge]`) + " Remove it (--purge also drops y4nn-standards.md)");
|
|
1453
|
+
console.log(" " + c.bold(`${prefix} status`) + " Install state + live knowledge-stack health");
|
|
975
1454
|
console.log("");
|
|
976
|
-
console.log(c.bold("
|
|
977
|
-
console.log(" " + c.bold(`${prefix}
|
|
978
|
-
console.log(" " + c.bold(`${prefix}
|
|
979
|
-
console.log(" " + c.bold(`${prefix}
|
|
1455
|
+
console.log(c.bold("Knowledge") + c.dim(" (you run these — agents never do)"));
|
|
1456
|
+
console.log(" " + c.bold(`${prefix} knowledge configure`) + " Wizard: LLM provider + cognee secrets");
|
|
1457
|
+
console.log(" " + c.bold(`${prefix} knowledge ingest [paths…]`) + " Add docs to THIS project's store");
|
|
1458
|
+
console.log(" " + c.bold(`${prefix} knowledge-stack up|down|restart|status`) + " The shared running infra (up = guided)");
|
|
1459
|
+
console.log(" " + c.bold(`${prefix} knowledge reset`) + " Wipe all stores → re-seed curated baseline (destructive)");
|
|
1460
|
+
console.log(" " + c.bold(`${prefix} project-work-store [<slug>] up|down|reset`) + " A project's own store");
|
|
980
1461
|
console.log("");
|
|
981
|
-
console.log(c.bold("
|
|
982
|
-
console.log(" " + c.bold(
|
|
983
|
-
console.log(" " + c.bold(
|
|
984
|
-
console.log(" " + c.bold(
|
|
1462
|
+
console.log(c.bold("Inspect / observe"));
|
|
1463
|
+
console.log(" " + c.bold(`${prefix} code-graph status|open|scan`) + " The project's code graph (Graphify)");
|
|
1464
|
+
console.log(" " + c.bold(`${prefix} observability install|open`) + " The live monitor (daemon + TUI)");
|
|
1465
|
+
console.log(" " + c.bold(`${prefix} org show [--json]`) + " The 45-agent reference");
|
|
1466
|
+
console.log(" " + c.bold(`${prefix} model show|set|reset`) + " Re-tier agents (per-agent/team/all) — survives updates");
|
|
1467
|
+
console.log(" " + c.dim("(the TUI binary is ") + c.bold("mishkan-watch") + c.dim("; `mishkan-watchd start|stop|status` for manual daemon control)"));
|
|
985
1468
|
console.log("");
|
|
986
1469
|
console.log(c.bold("Inside a Claude Code session"));
|
|
987
1470
|
console.log(" " + c.dim("Talk to Nehemiah (PM) in plain language — exploration mode is the default."));
|