mishkan-harness 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -24
- package/bin/mishkan.js +145 -22
- package/docs/design/MISHKAN_decisions.md +400 -0
- package/docs/design/MISHKAN_observability.md +1023 -0
- package/docs/usage/01-installation.md +40 -12
- package/docs/usage/10-observability.md +149 -0
- package/docs/usage/README.md +1 -0
- package/package.json +1 -1
- package/payload/install/settings.hooks.json +9 -0
- package/payload/mishkan/hooks/model-route.py +32 -0
- package/payload/mishkan/hooks/post-tool-observe.sh +222 -14
- package/payload/mishkan/hooks/pre-tool-security.sh +16 -0
- package/payload/mishkan/hooks/pre-tool-trace.sh +43 -0
- package/payload/mishkan/observability/README.md +100 -0
- package/payload/mishkan/observability/bus.py +89 -0
- package/payload/mishkan/observability/bus.sh +117 -0
- package/payload/mishkan/observability/schema.json +209 -0
- package/payload/mishkan/observability/usage_parser.py +211 -0
- package/payload/mishkan/observability/watch/README.md +62 -0
- package/payload/mishkan/observability/watch/pyproject.toml +22 -0
- package/payload/mishkan/observability/watch/src/mishkan_watch/__init__.py +3 -0
- package/payload/mishkan/observability/watch/src/mishkan_watch/__main__.py +29 -0
- package/payload/mishkan/observability/watch/src/mishkan_watch/app.py +235 -0
- package/payload/mishkan/observability/watch/src/mishkan_watch/client.py +134 -0
- package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/__init__.py +1 -0
- package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/activity.py +100 -0
- package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/agents.py +278 -0
- package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/knowledge.py +188 -0
- package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/live.py +307 -0
- package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/workflows.py +189 -0
- package/payload/mishkan/observability/watch/src/mishkan_watch/theme.tcss +254 -0
- package/payload/mishkan/observability/watchd/README.md +59 -0
- package/payload/mishkan/observability/watchd/pyproject.toml +23 -0
- package/payload/mishkan/observability/watchd/src/mishkan_watchd/__init__.py +3 -0
- package/payload/mishkan/observability/watchd/src/mishkan_watchd/__main__.py +230 -0
- package/payload/mishkan/observability/watchd/src/mishkan_watchd/lifecycle.py +68 -0
- package/payload/mishkan/observability/watchd/src/mishkan_watchd/server.py +134 -0
- package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/__init__.py +6 -0
- package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/bus_tail.py +97 -0
- package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/cognee_poll.py +228 -0
- package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/mcp_probe.py +230 -0
- package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/session_discover.py +74 -0
- package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/session_tail.py +161 -0
- package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/subagent_tail.py +195 -0
- package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/worktree_poll.py +107 -0
- package/payload/mishkan/observability/watchd/src/mishkan_watchd/state.py +288 -0
- package/payload/mishkan/observability/watchd/tests/test_state.py +77 -0
package/README.md
CHANGED
|
@@ -22,14 +22,19 @@ It's personal, opinionated infrastructure built around one engineer's standards
|
|
|
22
22
|
and shared openly for anyone who wants to try working this way. To make it yours,
|
|
23
23
|
replace one file (your profile) and re-sync; nothing else hardcodes the author.
|
|
24
24
|
|
|
25
|
-
> **Status:** early and evolving.
|
|
26
|
-
>
|
|
27
|
-
>
|
|
28
|
-
>
|
|
25
|
+
> **Status:** v0.2.0 — early and evolving. The agent fleet, rules, hooks, and
|
|
26
|
+
> installer are complete. The Cognee knowledge stack (work + curated stores) is
|
|
27
|
+
> ready to bring up locally. Live observability (daemon + Textual TUI) ships as
|
|
28
|
+
> an opt-in stack of two `uv tool install`-able Python packages.
|
|
29
29
|
|
|
30
30
|
---
|
|
31
31
|
|
|
32
|
-
##
|
|
32
|
+
## Three pillars
|
|
33
|
+
|
|
34
|
+
MISHKAN rests on three things that work together. They are independent — you
|
|
35
|
+
can use each without the others — but they compound:
|
|
36
|
+
|
|
37
|
+
### 1. A disciplined multi-agent organisation
|
|
33
38
|
|
|
34
39
|
Most "AI coding" is one model in one chat doing everything at once — generating,
|
|
35
40
|
judging its own output, skipping the spec, drifting scope. MISHKAN refuses that:
|
|
@@ -38,12 +43,36 @@ judging its own output, skipping the spec, drifting scope. MISHKAN refuses that:
|
|
|
38
43
|
Modeling → implementation. Agents won't jump to code without the upstream artifacts.
|
|
39
44
|
- **Generation ≠ evaluation.** The agent that writes is never the agent that reviews.
|
|
40
45
|
QA and reporters are structurally separate roles.
|
|
41
|
-
- **Deterministic guardrails.** Secrets, injection, unsafe deserialization, disabled
|
|
42
|
-
TLS, `:latest` tags, weak hashing — blocked by a hook *before* the write lands,
|
|
43
|
-
not flagged after.
|
|
44
46
|
- **Security & dependencies are first-class**, not an afterthought audit.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
+
|
|
48
|
+
### 2. Deterministic guardrails
|
|
49
|
+
|
|
50
|
+
Quality and security aren't *requested* of the model — they're **enforced by
|
|
51
|
+
the environment**: path-scoped rules, pre-write security hooks, structural
|
|
52
|
+
separation of generation from review. Secrets, injection, unsafe
|
|
53
|
+
deserialization, disabled TLS, `:latest` tags, weak hashing — blocked by a
|
|
54
|
+
hook *before* the write lands, not flagged after.
|
|
55
|
+
|
|
56
|
+
### 3. A knowledge stack that grows
|
|
57
|
+
|
|
58
|
+
MISHKAN is built around **persisting what you learn**, so future sessions
|
|
59
|
+
start where the last one stopped instead of from zero. Three complementary
|
|
60
|
+
layers (see [D-008](docs/design/MISHKAN_decisions.md)):
|
|
61
|
+
|
|
62
|
+
- **[Cognee work](payload/mishkan/cognee/) (`:7777`)** — per-project semantic
|
|
63
|
+
graph. ADRs, runbooks, decisions, resolved research — all ingested
|
|
64
|
+
selectively, then queryable by any agent that hits an unknown.
|
|
65
|
+
- **[Cognee curated](payload/mishkan/cognee/) (`:7730`)** — cross-project
|
|
66
|
+
reference library. Read-mostly, seeded once, shared across every project on
|
|
67
|
+
the host.
|
|
68
|
+
- **[Graphify](https://github.com/safishamsi/graphify) (planned, D-008
|
|
69
|
+
signed)** — deterministic code-structure graph for "who calls X, who depends
|
|
70
|
+
on Y", complementing Cognee's semantic layer.
|
|
71
|
+
|
|
72
|
+
Plus a **live observability TUI** ([mishkan-watch](payload/mishkan/observability/))
|
|
73
|
+
that shows every running agent, workflow, tool call, hook fire, token spend,
|
|
74
|
+
and MCP/Cognee status — in real time, across every session, with no overload.
|
|
75
|
+
See [`docs/design/MISHKAN_observability.md`](docs/design/MISHKAN_observability.md).
|
|
47
76
|
|
|
48
77
|
## The teams
|
|
49
78
|
|
|
@@ -81,10 +110,15 @@ install time — nothing machine-specific is baked in.
|
|
|
81
110
|
|
|
82
111
|
```bash
|
|
83
112
|
npx mishkan-harness status # what's installed
|
|
113
|
+
npx mishkan-harness observability # install/refresh just the daemon + TUI (needs `uv`)
|
|
84
114
|
npx mishkan-harness uninstall # remove harness; keep your CLAUDE.md & rules
|
|
85
115
|
npx mishkan-harness uninstall --purge # also remove the user-level rule
|
|
86
116
|
```
|
|
87
117
|
|
|
118
|
+
The installer walks 7 phases with clear progress, asks once whether to install
|
|
119
|
+
the observability stack at the end, and skips that step cleanly if `uv` isn't
|
|
120
|
+
installed.
|
|
121
|
+
|
|
88
122
|
## A first session
|
|
89
123
|
|
|
90
124
|
1. Run `claude` anywhere. You're talking to **Nehemiah** and **Bezalel** in
|
|
@@ -129,14 +163,14 @@ org-layer agent) owns re-deriving anything drawn from it. See
|
|
|
129
163
|
workspace (discovery-based, no hardcoded paths), aggregates shared CVEs and
|
|
130
164
|
version drift, and produces a coordinated, vetted update plan.
|
|
131
165
|
|
|
132
|
-
## Knowledge
|
|
166
|
+
## Knowledge stack
|
|
133
167
|
|
|
134
|
-
MISHKAN uses [Cognee](https://docs.cognee.ai) as
|
|
135
|
-
you work. Cognee core is a Python library; the harness
|
|
136
|
-
**`cognee-mcp`** server.
|
|
137
|
-
persistence is deferred. Run the **cognee-quickstart** skill for
|
|
138
|
-
the hardened Docker deployment under
|
|
139
|
-
|
|
168
|
+
MISHKAN uses [Cognee](https://docs.cognee.ai) as its **two-store knowledge
|
|
169
|
+
graph** that grows as you work. Cognee core is a Python library; the harness
|
|
170
|
+
consumes it through the **`cognee-mcp`** server. Agents can still work without
|
|
171
|
+
it — persistence is just deferred. Run the **cognee-quickstart** skill for
|
|
172
|
+
guided setup, or the hardened Docker deployment under
|
|
173
|
+
`~/.claude/mishkan/cognee/`:
|
|
140
174
|
|
|
141
175
|
```bash
|
|
142
176
|
cd ~/.claude/mishkan/cognee
|
|
@@ -146,13 +180,30 @@ nc -z localhost 7777 && echo up
|
|
|
146
180
|
~/.claude/mishkan/scripts/seed-curated-library.sh # 96 curated references
|
|
147
181
|
```
|
|
148
182
|
|
|
149
|
-
Agents reach
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
183
|
+
Agents reach the work store at `http://localhost:7777/mcp` and the curated
|
|
184
|
+
store at `:7730`, both seeded by `/mishkan-init` into each project's
|
|
185
|
+
`.mcp.json`. The deployment is pinned, SOPS-managed, hardening-overlay on
|
|
186
|
+
every recreate, `127.0.0.1`-bound. Full guide:
|
|
187
|
+
[`payload/mishkan/cognee/README.md`](payload/mishkan/cognee/README.md).
|
|
188
|
+
|
|
189
|
+
## Live observability (opt-in)
|
|
190
|
+
|
|
191
|
+
See **everything** that runs across your MISHKAN sessions in a single Textual
|
|
192
|
+
TUI: active agents, workflows-in-flight, token spend ($), file changes, hook
|
|
193
|
+
decisions, Cognee node counts, MCP server status. Cross-session,
|
|
194
|
+
cross-project, near-zero overhead. Two `uv tool`-installable Python packages:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
# the installer offers this automatically; or run it standalone any time:
|
|
198
|
+
npx mishkan-harness observability
|
|
199
|
+
|
|
200
|
+
# then, in two tmux panes (or any two terminals):
|
|
201
|
+
mishkan-watchd start # daemon — aggregates the bus
|
|
202
|
+
mishkan-watch # TUI client — 5 tabs, status bar
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Full operator guide: [`docs/usage/10-observability.md`](docs/usage/10-observability.md).
|
|
206
|
+
Design and event schema: [`docs/design/MISHKAN_observability.md`](docs/design/MISHKAN_observability.md).
|
|
156
207
|
|
|
157
208
|
## Token & context management
|
|
158
209
|
|
package/bin/mishkan.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// MISHKAN installer — dependency-free (Node >=18, built-ins only).
|
|
3
|
-
// Commands: install | uninstall | status
|
|
3
|
+
// Commands: install | uninstall | status | observability
|
|
4
4
|
//
|
|
5
5
|
// Portability by design: every path is resolved from os.homedir() at runtime.
|
|
6
6
|
// No machine-specific paths are baked in. Idempotent: re-running install updates
|
|
@@ -10,6 +10,9 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync,
|
|
|
10
10
|
import { homedir } from "node:os";
|
|
11
11
|
import { join, dirname, relative } from "node:path";
|
|
12
12
|
import { fileURLToPath } from "node:url";
|
|
13
|
+
import { spawnSync } from "node:child_process";
|
|
14
|
+
import { createInterface } from "node:readline";
|
|
15
|
+
import { stdin, stdout } from "node:process";
|
|
13
16
|
|
|
14
17
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
15
18
|
const PKG = join(HERE, "..");
|
|
@@ -18,8 +21,30 @@ const CLAUDE = join(HOME, ".claude");
|
|
|
18
21
|
const MISHKAN = join(CLAUDE, "mishkan");
|
|
19
22
|
const STAMP = join(MISHKAN, ".install-stamp");
|
|
20
23
|
|
|
24
|
+
// ─── output ────────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
const NO_COLOR = process.env.NO_COLOR || !process.stdout.isTTY;
|
|
27
|
+
const c = NO_COLOR
|
|
28
|
+
? { dim: s => s, bold: s => s, cyan: s => s, green: s => s, yellow: s => s, red: s => s }
|
|
29
|
+
: {
|
|
30
|
+
dim: s => `\x1b[2m${s}\x1b[0m`,
|
|
31
|
+
bold: s => `\x1b[1m${s}\x1b[0m`,
|
|
32
|
+
cyan: s => `\x1b[36m${s}\x1b[0m`,
|
|
33
|
+
green: s => `\x1b[32m${s}\x1b[0m`,
|
|
34
|
+
yellow: s => `\x1b[33m${s}\x1b[0m`,
|
|
35
|
+
red: s => `\x1b[31m${s}\x1b[0m`,
|
|
36
|
+
};
|
|
37
|
+
|
|
21
38
|
const log = (...a) => console.log("mishkan:", ...a);
|
|
22
|
-
const warn = (...a) => console.warn("mishkan: WARN", ...a);
|
|
39
|
+
const warn = (...a) => console.warn("mishkan: " + c.yellow("WARN"), ...a);
|
|
40
|
+
|
|
41
|
+
// Print a phase header with a one-line "why" subtitle. Helps the engineer
|
|
42
|
+
// see what each step does and why, instead of an unstructured wall of logs.
|
|
43
|
+
function phase(n, total, title, why) {
|
|
44
|
+
console.log();
|
|
45
|
+
console.log(c.bold(c.cyan(`[${n}/${total}] ${title}`)));
|
|
46
|
+
if (why) console.log(c.dim(` ${why}`));
|
|
47
|
+
}
|
|
23
48
|
|
|
24
49
|
function ensureDir(d) { mkdirSync(d, { recursive: true }); }
|
|
25
50
|
|
|
@@ -106,16 +131,85 @@ function removeHooks() {
|
|
|
106
131
|
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
107
132
|
}
|
|
108
133
|
|
|
109
|
-
|
|
134
|
+
// ─── tool availability + interactive prompt ────────────────────────────────
|
|
135
|
+
|
|
136
|
+
function commandExists(cmd) {
|
|
137
|
+
const r = spawnSync(process.platform === "win32" ? "where" : "which",
|
|
138
|
+
[cmd], { stdio: "ignore" });
|
|
139
|
+
return r.status === 0;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function promptYN(question, defaultYes = true) {
|
|
143
|
+
// Non-TTY (CI, piped install) -> use the default, never block.
|
|
144
|
+
if (!stdin.isTTY) return defaultYes;
|
|
145
|
+
const suffix = defaultYes ? " [Y/n] " : " [y/N] ";
|
|
146
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
147
|
+
const ans = await new Promise(r => rl.question(question + suffix, r));
|
|
148
|
+
rl.close();
|
|
149
|
+
const a = ans.trim().toLowerCase();
|
|
150
|
+
if (!a) return defaultYes;
|
|
151
|
+
return a === "y" || a === "yes" || a === "o" || a === "oui";
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ─── observability opt-in (Phase 1.5 of the install contract, §10 of doc) ──
|
|
155
|
+
|
|
156
|
+
function installObservabilityStack() {
|
|
157
|
+
console.log();
|
|
158
|
+
console.log(c.bold(c.cyan("Observability stack")));
|
|
159
|
+
console.log(c.dim(
|
|
160
|
+
" Optional cross-session daemon + TUI client that aggregates the event\n" +
|
|
161
|
+
" bus into a live snapshot. Read docs/design/MISHKAN_observability.md.\n" +
|
|
162
|
+
" Requires `uv` (https://astral.sh/uv) and Python 3.11+."));
|
|
163
|
+
|
|
164
|
+
if (!commandExists("uv")) {
|
|
165
|
+
console.log(c.yellow(" uv not found — skipping observability install."));
|
|
166
|
+
console.log(c.dim(
|
|
167
|
+
" Install uv with:\n" +
|
|
168
|
+
" curl -LsSf https://astral.sh/uv/install.sh | sh\n" +
|
|
169
|
+
" Then re-run: npx mishkan-harness observability"));
|
|
170
|
+
return { installed: false, reason: "uv-missing" };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const watchdSrc = join(PKG, "payload", "mishkan", "observability", "watchd");
|
|
174
|
+
const watchSrc = join(PKG, "payload", "mishkan", "observability", "watch");
|
|
175
|
+
for (const dir of [watchdSrc, watchSrc]) {
|
|
176
|
+
if (!existsSync(dir)) {
|
|
177
|
+
warn(`observability source missing: ${tilde(dir)}`);
|
|
178
|
+
return { installed: false, reason: "payload-missing" };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
console.log(c.dim(" Installing mishkan-watchd (daemon)…"));
|
|
183
|
+
const r1 = spawnSync("uv", ["tool", "install", "--from", watchdSrc, "mishkan-watchd"],
|
|
184
|
+
{ stdio: "inherit" });
|
|
185
|
+
if (r1.status !== 0) { warn("mishkan-watchd install failed"); return { installed: false, reason: "install-failed" }; }
|
|
186
|
+
|
|
187
|
+
console.log(c.dim(" Installing mishkan-watch (TUI client)…"));
|
|
188
|
+
const r2 = spawnSync("uv", ["tool", "install", "--from", watchSrc, "mishkan-watch"],
|
|
189
|
+
{ stdio: "inherit" });
|
|
190
|
+
if (r2.status !== 0) { warn("mishkan-watch install failed"); return { installed: false, reason: "install-failed" }; }
|
|
191
|
+
|
|
192
|
+
console.log(c.green(" ✓ observability stack installed"));
|
|
193
|
+
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"));
|
|
197
|
+
return { installed: true };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function install() {
|
|
201
|
+
const totalPhases = 7;
|
|
110
202
|
log(`installing into ${tilde(CLAUDE)} (home resolved at runtime)`);
|
|
111
203
|
ensureDir(CLAUDE);
|
|
204
|
+
|
|
205
|
+
phase(1, totalPhases, "Payload",
|
|
206
|
+
"copy agents, skills, commands, hooks, rules, cognee compose, observability sources");
|
|
112
207
|
// 1. payload/mishkan -> ~/.claude/mishkan
|
|
113
208
|
copyDir(join(PKG, "payload", "mishkan"), MISHKAN);
|
|
114
209
|
ensureDir(join(MISHKAN, "logs"));
|
|
115
210
|
ensureDir(join(MISHKAN, "cognee"));
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
// existing runtime profile the engineer may have edited in place.
|
|
211
|
+
phase(2, totalPhases, "Engineer profile",
|
|
212
|
+
"place runtime profile — never overwrites an existing edited one");
|
|
119
213
|
const realProfile = join(PKG, "docs", "engineer", "profile.md");
|
|
120
214
|
const exampleProfile = join(PKG, "docs", "engineer", "profile.example.md");
|
|
121
215
|
const runtimeProfile = join(MISHKAN, "profile.md");
|
|
@@ -126,33 +220,59 @@ function install() {
|
|
|
126
220
|
} else {
|
|
127
221
|
log("preserved existing ~/.claude/mishkan/profile.md");
|
|
128
222
|
}
|
|
129
|
-
|
|
223
|
+
|
|
224
|
+
phase(3, totalPhases, "User-level rules",
|
|
225
|
+
"refresh harness default; preserve your engineer-standards.md + CLAUDE.md");
|
|
130
226
|
ensureDir(join(CLAUDE, "rules"));
|
|
131
|
-
// y4nn-standards.md is the harness-maintained DEFAULT — always refreshed so
|
|
132
|
-
// updates flow. Customisation lives in engineer-standards.md instead.
|
|
133
227
|
copyFileSync(join(PKG, "payload", "user", "rules", "y4nn-standards.md"),
|
|
134
228
|
join(CLAUDE, "rules", "y4nn-standards.md"));
|
|
135
229
|
log("refreshed harness default ~/.claude/rules/y4nn-standards.md");
|
|
136
|
-
// engineer-standards.md is the USER's layer — placed once, never overwritten.
|
|
137
230
|
const engRule = join(CLAUDE, "rules", "engineer-standards.md");
|
|
138
231
|
if (!existsSync(engRule)) copyFileSync(join(PKG, "payload", "user", "rules", "engineer-standards.md"), engRule);
|
|
139
232
|
else log("preserved your ~/.claude/rules/engineer-standards.md");
|
|
140
|
-
// user CLAUDE.md — placed once, never overwritten.
|
|
141
233
|
const userClaude = join(CLAUDE, "CLAUDE.md");
|
|
142
234
|
if (!existsSync(userClaude)) copyFileSync(join(PKG, "payload", "user", "CLAUDE.md"), userClaude);
|
|
143
235
|
else log("preserved existing ~/.claude/CLAUDE.md");
|
|
144
|
-
|
|
236
|
+
|
|
237
|
+
phase(4, totalPhases, "Discovery symlinks",
|
|
238
|
+
"make agents, skills, commands visible to Claude Code");
|
|
145
239
|
const a = linkInto("agents", join(CLAUDE, "agents"), false);
|
|
146
240
|
const s = linkInto("skills", join(CLAUDE, "skills"), true);
|
|
147
|
-
const
|
|
148
|
-
log(`linked agents=${a.linked} (skipped ${a.skipped}), skills=${s.linked}, commands=${
|
|
149
|
-
|
|
241
|
+
const cm = linkInto("commands", join(CLAUDE, "commands"), false);
|
|
242
|
+
log(`linked agents=${a.linked} (skipped ${a.skipped}), skills=${s.linked}, commands=${cm.linked}`);
|
|
243
|
+
|
|
244
|
+
phase(5, totalPhases, "Hooks",
|
|
245
|
+
"merge MISHKAN hooks into ~/.claude/settings.json (preserves existing)");
|
|
150
246
|
mergeHooks();
|
|
151
|
-
log("hooks merged into settings.json
|
|
152
|
-
|
|
247
|
+
log("hooks merged into settings.json");
|
|
248
|
+
|
|
249
|
+
phase(6, totalPhases, "Stamp",
|
|
250
|
+
"record install version + timestamp for status / uninstall");
|
|
153
251
|
const version = JSON.parse(readFileSync(join(PKG, "package.json"), "utf8")).version;
|
|
154
252
|
writeFileSync(STAMP, JSON.stringify({ version, installedAt: new Date().toISOString() }, null, 2) + "\n");
|
|
155
|
-
|
|
253
|
+
|
|
254
|
+
phase(7, totalPhases, "Observability (opt-in)",
|
|
255
|
+
"Python daemon + Textual TUI for live cross-session monitoring");
|
|
256
|
+
if (await promptYN(" Install observability stack now?", true)) {
|
|
257
|
+
installObservabilityStack();
|
|
258
|
+
} else {
|
|
259
|
+
console.log(c.dim(" Skipped. Re-run later: npx mishkan-harness observability"));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
console.log();
|
|
263
|
+
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"));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function uninstallObservabilityHint() {
|
|
271
|
+
if (commandExists("uv")) {
|
|
272
|
+
console.log(c.dim(
|
|
273
|
+
" Observability stack installed via uv tool — remove manually if desired:\n" +
|
|
274
|
+
" uv tool uninstall mishkan-watch mishkan-watchd"));
|
|
275
|
+
}
|
|
156
276
|
}
|
|
157
277
|
|
|
158
278
|
function uninstall({ purge = false } = {}) {
|
|
@@ -180,6 +300,7 @@ function uninstall({ purge = false } = {}) {
|
|
|
180
300
|
} else {
|
|
181
301
|
log("kept user-level CLAUDE.md, y4nn-standards.md, and engineer-standards.md (use --purge to remove the default).");
|
|
182
302
|
}
|
|
303
|
+
uninstallObservabilityHint();
|
|
183
304
|
}
|
|
184
305
|
|
|
185
306
|
function status() {
|
|
@@ -208,14 +329,16 @@ function status() {
|
|
|
208
329
|
const cmd = process.argv[2];
|
|
209
330
|
const flags = new Set(process.argv.slice(3));
|
|
210
331
|
switch (cmd) {
|
|
211
|
-
case "install": install(); break;
|
|
332
|
+
case "install": await install(); break;
|
|
212
333
|
case "uninstall": uninstall({ purge: flags.has("--purge") }); break;
|
|
213
334
|
case "status": status(); break;
|
|
335
|
+
case "observability": installObservabilityStack(); break;
|
|
214
336
|
default:
|
|
215
337
|
console.log(`MISHKAN harness installer
|
|
216
338
|
Usage:
|
|
217
|
-
npx mishkan-harness install
|
|
218
|
-
npx mishkan-harness
|
|
219
|
-
npx mishkan-harness
|
|
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)
|
|
220
343
|
npx mishkan-harness uninstall --purge Also remove user-level rule`);
|
|
221
344
|
}
|