mishkan-harness 0.2.0 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) 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/research/graphify-token-saving-poc.md +108 -0
  7. package/docs/usage/09-workflows.md +74 -4
  8. package/docs/usage/10-observability.md +37 -13
  9. package/docs/usage/11-graphify.md +264 -0
  10. package/docs/usage/12-skill-discovery.md +294 -0
  11. package/docs/usage/README.md +5 -3
  12. package/package.json +1 -1
  13. package/payload/install/settings.hooks.json +24 -0
  14. package/payload/mishkan/AGENT_SPEC.md +25 -7
  15. package/payload/mishkan/agents/ahikam.md +4 -2
  16. package/payload/mishkan/agents/aholiab.md +4 -2
  17. package/payload/mishkan/agents/asaph.md +5 -2
  18. package/payload/mishkan/agents/baruch.md +19 -2
  19. package/payload/mishkan/agents/benaiah.md +4 -2
  20. package/payload/mishkan/agents/bezalel.md +4 -2
  21. package/payload/mishkan/agents/caleb.md +4 -2
  22. package/payload/mishkan/agents/deborah.md +4 -2
  23. package/payload/mishkan/agents/elasah.md +4 -2
  24. package/payload/mishkan/agents/eliashib.md +4 -2
  25. package/payload/mishkan/agents/ezra.md +4 -2
  26. package/payload/mishkan/agents/hanun.md +5 -2
  27. package/payload/mishkan/agents/hiram.md +5 -2
  28. package/payload/mishkan/agents/hizkiah.md +5 -2
  29. package/payload/mishkan/agents/huldah.md +4 -2
  30. package/payload/mishkan/agents/huram.md +4 -2
  31. package/payload/mishkan/agents/hushai.md +5 -2
  32. package/payload/mishkan/agents/igal.md +4 -2
  33. package/payload/mishkan/agents/ira.md +5 -2
  34. package/payload/mishkan/agents/jahaziel.md +5 -2
  35. package/payload/mishkan/agents/jakin.md +4 -2
  36. package/payload/mishkan/agents/jehonathan.md +5 -2
  37. package/payload/mishkan/agents/jehoshaphat.md +4 -2
  38. package/payload/mishkan/agents/joab.md +5 -2
  39. package/payload/mishkan/agents/joah.md +5 -2
  40. package/payload/mishkan/agents/maaseiah.md +4 -2
  41. package/payload/mishkan/agents/meremoth.md +5 -2
  42. package/payload/mishkan/agents/meshullam.md +5 -2
  43. package/payload/mishkan/agents/nathan.md +5 -2
  44. package/payload/mishkan/agents/nehemiah.md +4 -2
  45. package/payload/mishkan/agents/obed.md +4 -2
  46. package/payload/mishkan/agents/oholiab.md +5 -2
  47. package/payload/mishkan/agents/palal.md +5 -2
  48. package/payload/mishkan/agents/phinehas.md +4 -2
  49. package/payload/mishkan/agents/rehum.md +4 -2
  50. package/payload/mishkan/agents/salma.md +5 -2
  51. package/payload/mishkan/agents/seraiah.md +4 -2
  52. package/payload/mishkan/agents/shallum.md +5 -2
  53. package/payload/mishkan/agents/shaphan.md +4 -2
  54. package/payload/mishkan/agents/shemaiah.md +4 -2
  55. package/payload/mishkan/agents/shevna.md +5 -2
  56. package/payload/mishkan/agents/uriah.md +5 -2
  57. package/payload/mishkan/agents/zaccur.md +4 -2
  58. package/payload/mishkan/agents/zadok.md +5 -2
  59. package/payload/mishkan/agents/zerubbabel.md +4 -2
  60. package/payload/mishkan/commands/code-graph.md +35 -0
  61. package/payload/mishkan/commands/eval-baruch.md +57 -0
  62. package/payload/mishkan/commands/mishkan-org-reference.md +58 -0
  63. package/payload/mishkan/commands/mishkan-skills-misses.md +47 -0
  64. package/payload/mishkan/commands/mishkan-skills-reindex.md +33 -0
  65. package/payload/mishkan/commands/skills.md +35 -0
  66. package/payload/mishkan/commands/sprint-close.md +3 -1
  67. package/payload/mishkan/hooks/pre-tool-knowledge-route.sh +231 -0
  68. package/payload/mishkan/hooks/pre-tool-task-skill-route.sh +122 -0
  69. package/payload/mishkan/hooks/session-start-skill-index.sh +35 -0
  70. package/payload/mishkan/observability/schema.json +1 -1
  71. package/payload/mishkan/observability/watch/src/mishkan_watch/__main__.py +84 -2
  72. package/payload/mishkan/observability/watch/src/mishkan_watch/app.py +64 -3
  73. package/payload/mishkan/observability/watch/src/mishkan_watch/org_data.py +79 -0
  74. package/payload/mishkan/observability/watch/src/mishkan_watch/skills_data.py +267 -0
  75. package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/agents.py +33 -1
  76. package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/knowledge.py +118 -9
  77. package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/live.py +102 -2
  78. package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/org.py +139 -0
  79. package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/skills.py +187 -0
  80. package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/usage.py +377 -0
  81. package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/workflows.py +199 -27
  82. package/payload/mishkan/observability/watch/src/mishkan_watch/theme.tcss +78 -4
  83. package/payload/mishkan/observability/watchd/src/mishkan_watchd/__main__.py +36 -6
  84. package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/bus_tail.py +16 -3
  85. package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/graphify_tail.py +220 -0
  86. package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/worktree_poll.py +13 -2
  87. package/payload/mishkan/observability/watchd/src/mishkan_watchd/state.py +157 -3
  88. package/payload/mishkan/observability/watchd/tests/test_state.py +6 -2
  89. package/payload/mishkan/org/org.json +141 -0
  90. package/payload/mishkan/scripts/pre-commit-eval-gate.sh +50 -0
  91. package/payload/mishkan/scripts/skill-discovery-indexer.py +462 -0
  92. package/payload/mishkan/scripts/skill-discovery-misses.py +173 -0
  93. package/payload/mishkan/scripts/skill-discovery-router.py +457 -0
  94. package/payload/mishkan/scripts/skill-discovery-smoke.sh +156 -0
  95. package/payload/mishkan/skills/graphify-query-craft/SKILL.md +162 -0
  96. package/payload/mishkan/skills/mishkan-init/SKILL.md +15 -0
  97. package/payload/mishkan/skills/skill-discovery/SKILL.md +120 -0
  98. package/payload/mishkan/templates/case-node.schema.json +1 -0
  99. package/payload/mishkan/templates/observability-log.schema.json +1 -0
  100. package/payload/mishkan/workflows/README.md +78 -13
  101. package/payload/mishkan/workflows/chosheb-feature-ship.js +75 -0
  102. package/payload/mishkan/workflows/migdal-dr-drill.js +94 -0
  103. package/payload/mishkan/workflows/migdal-infra-change.js +67 -0
  104. package/payload/mishkan/workflows/mishkan-blast-radius.js +144 -0
  105. package/payload/mishkan/workflows/mishkan-init.js +58 -0
  106. package/payload/mishkan/workflows/mishkan-knowledge-gap-discovery.js +121 -0
  107. package/payload/mishkan/workflows/mishkan-standards-rollout.js +153 -0
  108. package/payload/mishkan/workflows/mishmar-security-gate.js +83 -0
  109. package/payload/mishkan/workflows/panim-ds-rollout.js +83 -0
  110. package/payload/mishkan/workflows/sefer-release-notes.js +85 -0
  111. package/payload/mishkan/workflows/yasad-data-migration-wave.js +78 -0
  112. package/payload/mishkan/workflows/yasad-schema-evolution.js +76 -0
  113. package/payload/mishkan/templates/user-CLAUDE.md +0 -62
@@ -18,6 +18,7 @@ from __future__ import annotations
18
18
  from collections import deque
19
19
  from dataclasses import dataclass, field
20
20
  from datetime import datetime, timezone
21
+ from time import monotonic
21
22
  from typing import Any, Optional
22
23
 
23
24
 
@@ -26,6 +27,12 @@ def _now() -> str:
26
27
  f"{datetime.now(tz=timezone.utc).microsecond // 1000:03d}Z"
27
28
 
28
29
 
30
+ # How long an event for an unconfirmed session is buffered before being
31
+ # dropped. session_discover polls every 10s, so 15s covers the case where
32
+ # a hook fires for a brand-new session before its first JSONL flush.
33
+ _PENDING_TTL_S = 15.0
34
+
35
+
29
36
  @dataclass
30
37
  class AgentState:
31
38
  name: str
@@ -36,6 +43,13 @@ class AgentState:
36
43
  tokens_in: int = 0
37
44
  tokens_out: int = 0
38
45
  cost_estimate_usd: float = 0.0
46
+ # Claude tier this agent runs on (opus / sonnet / haiku). Lifted from
47
+ # post-tool-observe.sh's `agent_spawn` payload, which already carries the
48
+ # model field that model-route.py injected. Consumed by the Usage tab to
49
+ # pick the per-agent context window (Sonnet 4.6 = 1M, Opus 4.x = 1M,
50
+ # Haiku 4.5 = 200k) instead of pinning the bar at a single fleet-wide
51
+ # default. Empty string when the spawner didn't surface a model.
52
+ model: str = ""
39
53
 
40
54
 
41
55
  @dataclass
@@ -93,24 +107,97 @@ class CogneeStoreState:
93
107
  last_probe: Optional[str] = None
94
108
 
95
109
 
110
+ @dataclass
111
+ class GraphifyState:
112
+ """Aggregated Graphify state across all watched projects.
113
+
114
+ graphify_tail emits scan events whose payload carries the latest
115
+ nodes/edges for a single project; we keep the most recent per-project
116
+ rollup so the snapshot served to a new TUI client carries the current
117
+ state even if the scan event itself fired before the client connected.
118
+ """
119
+ nodes: int = 0
120
+ edges: int = 0
121
+ communities: int = 0
122
+ scans: int = 0
123
+ queries: int = 0
124
+ last_scan_project: Optional[str] = None
125
+ last_scan_at: Optional[str] = None
126
+ last_query_project: Optional[str] = None
127
+ last_query_at: Optional[str] = None
128
+ last_query_text: Optional[str] = None
129
+
130
+
96
131
  @dataclass
97
132
  class HarnessState:
98
133
  sessions: dict[str, SessionState] = field(default_factory=dict)
99
134
  worktrees: dict[str, WorktreeState] = field(default_factory=dict)
100
135
  mcp_servers: dict[str, MCPServerState] = field(default_factory=dict)
101
136
  cognee: dict[str, CogneeStoreState] = field(default_factory=dict)
137
+ graphify: GraphifyState = field(default_factory=GraphifyState)
102
138
  recent_hooks: deque[dict[str, Any]] = field(default_factory=lambda: deque(maxlen=50))
103
139
  started: str = field(default_factory=_now)
140
+ # Bounded ring of recently-stopped session ids. Any event for an id
141
+ # in this ring is dropped silently — prevents background-hook events
142
+ # (e.g. CLAUDE_PROJECT_DIR leaking from a stale shell env) from
143
+ # resurrecting a stopped session via _ensure_session. Capacity 256 is
144
+ # ~256 × poll_interval = several minutes of memory; cheap.
145
+ _stopped_recently: deque[str] = field(default_factory=lambda: deque(maxlen=256))
146
+
147
+ # Sessions confirmed alive by session_discover. Only sessions in this
148
+ # set are eligible to have non-session_start events applied to state.
149
+ # session_discover emits session_start when it observes an active
150
+ # JSONL; that adds to this set. session_stop removes from it.
151
+ _confirmed_alive: set[str] = field(default_factory=set)
152
+
153
+ # Per-session buffer of events that arrived before the session_id was
154
+ # confirmed alive by session_discover. Each entry: (monotonic ts, event).
155
+ # On session_start the buffer for that sid is flushed in order. Entries
156
+ # older than _PENDING_TTL_S are dropped during apply() sweep — bounds
157
+ # memory for sids that session_discover never confirms (typically
158
+ # hooks firing for already-gone sessions).
159
+ _pending: dict[str, deque] = field(default_factory=dict)
104
160
 
105
161
  # ----- event application -------------------------------------------------
106
162
 
107
163
  def apply(self, event: dict[str, Any]) -> None:
108
- """Apply a bus-format event to the state. Idempotent on unknowns."""
164
+ """Apply a bus-format event to the state.
165
+
166
+ Authority gate: session_discover is the source of truth for alive
167
+ sessions. Only sessions confirmed by session_discover (via
168
+ session_start) are eligible to have events applied. Events for
169
+ unknown session_ids are buffered briefly (_PENDING_TTL_S) and
170
+ flushed if confirmation arrives; otherwise dropped.
171
+
172
+ This kills the phantom-session bug at its root: bus_tail can no
173
+ longer resurrect stopped or never-alive sessions, no matter what
174
+ old hook events surface from disk.
175
+ """
109
176
  try:
177
+ self._sweep_pending()
110
178
  etype = event.get("type")
111
179
  session_id = event.get("session")
180
+
112
181
  if session_id:
113
- self._ensure_session(session_id, event.get("project") or "unknown")
182
+ if etype == "session_start":
183
+ # session_discover has just confirmed this session is alive.
184
+ self._confirmed_alive.add(session_id)
185
+ self._ensure_session(session_id, event.get("project") or "unknown")
186
+ self._flush_pending(session_id)
187
+ elif etype == "session_stop":
188
+ # Honoured below; remove from confirmed + tombstone.
189
+ pass
190
+ elif session_id in self._stopped_recently:
191
+ # Stopped session — drop silently. Tombstone window handles
192
+ # lagging hook events for sessions whose stop just propagated.
193
+ return
194
+ elif session_id not in self._confirmed_alive:
195
+ # Unknown session. Buffer with TTL — if session_discover
196
+ # confirms within _PENDING_TTL_S we replay in order;
197
+ # otherwise the sweep drops it.
198
+ buf = self._pending.setdefault(session_id, deque(maxlen=200))
199
+ buf.append((monotonic(), event))
200
+ return
114
201
 
115
202
  if etype == "agent_spawn":
116
203
  self._on_agent_spawn(session_id, event)
@@ -128,16 +215,53 @@ class HarnessState:
128
215
  self._on_mcp(event)
129
216
  elif etype == "cognee_op":
130
217
  self._on_cognee_op(event)
218
+ elif etype in ("graphify_scan", "graphify_query"):
219
+ self._on_graphify(event)
131
220
  elif etype == "session_start":
132
221
  pass # ensured above
133
222
  elif etype == "session_stop":
134
- self.sessions.pop(session_id, None) if session_id else None
223
+ if session_id:
224
+ self.sessions.pop(session_id, None)
225
+ self._confirmed_alive.discard(session_id)
226
+ self._pending.pop(session_id, None)
227
+ # Tombstone so lagging hook events for this session
228
+ # don't resurrect it. Bounded ring — old tombstones age out.
229
+ self._stopped_recently.append(session_id)
135
230
 
136
231
  if session_id and session_id in self.sessions:
137
232
  self.sessions[session_id].recent_events.append(event)
138
233
  except Exception:
139
234
  return # fail-open on any state error
140
235
 
236
+ # ----- pending buffer (confirmed-alive gate) -----------------------------
237
+
238
+ def _sweep_pending(self) -> None:
239
+ """Drop pending events older than _PENDING_TTL_S.
240
+
241
+ Called at the head of every apply() — bounded O(buffered sids)
242
+ which stays small in practice (only sids session_discover hasn't
243
+ confirmed yet, mostly transient).
244
+ """
245
+ if not self._pending:
246
+ return
247
+ now = monotonic()
248
+ for sid in list(self._pending.keys()):
249
+ buf = self._pending[sid]
250
+ while buf and (now - buf[0][0]) > _PENDING_TTL_S:
251
+ buf.popleft()
252
+ if not buf:
253
+ del self._pending[sid]
254
+
255
+ def _flush_pending(self, session_id: str) -> None:
256
+ """Replay all buffered events for a now-confirmed session, in order."""
257
+ buf = self._pending.pop(session_id, None)
258
+ if not buf:
259
+ return
260
+ for _, ev in list(buf):
261
+ # Re-enter apply(); the sid is now in _confirmed_alive so the
262
+ # gate above accepts.
263
+ self.apply(ev)
264
+
141
265
  # ----- snapshot for new clients ------------------------------------------
142
266
 
143
267
  def to_snapshot(self) -> dict[str, Any]:
@@ -149,6 +273,7 @@ class HarnessState:
149
273
  "worktrees": {p: _dc_to_dict(w) for p, w in self.worktrees.items()},
150
274
  "mcp_servers": {n: _dc_to_dict(s) for n, s in self.mcp_servers.items()},
151
275
  "cognee": {n: _dc_to_dict(s) for n, s in self.cognee.items()},
276
+ "graphify": _dc_to_dict(self.graphify),
152
277
  "recent_hooks": list(self.recent_hooks),
153
278
  }
154
279
 
@@ -177,11 +302,13 @@ class HarnessState:
177
302
  agent_name = event.get("agent") or (event.get("payload") or {}).get("subagent_type")
178
303
  if not agent_name:
179
304
  return
305
+ payload = event.get("payload") or {}
180
306
  sess.agents_active[agent_name] = AgentState(
181
307
  name=agent_name,
182
308
  started=event.get("ts") or _now(),
183
309
  last_tool=event.get("tool"),
184
310
  status="running",
311
+ model=str(payload.get("model") or ""),
185
312
  )
186
313
 
187
314
  def _on_agent_complete(self, sid: Optional[str], event: dict[str, Any]) -> None:
@@ -260,6 +387,33 @@ class HarnessState:
260
387
  s.last_probe = event.get("ts")
261
388
  self.cognee[store] = s
262
389
 
390
+ def _on_graphify(self, event: dict[str, Any]) -> None:
391
+ """Aggregate Graphify scan/query events into a single rollup.
392
+
393
+ We don't break this down per-project here — the TUI cards show the
394
+ most recent scan's totals + a query counter, which is what a single
395
+ rollup needs. The snapshot serializer flattens this back to a dict
396
+ so the existing TUI Knowledge tab can read it directly.
397
+ """
398
+ p = event.get("payload") or {}
399
+ etype = event.get("type")
400
+ g = self.graphify
401
+ if etype == "graphify_scan":
402
+ if p.get("nodes") is not None:
403
+ g.nodes = int(p["nodes"])
404
+ if p.get("edges") is not None:
405
+ g.edges = int(p["edges"])
406
+ if p.get("communities") is not None:
407
+ g.communities = int(p["communities"])
408
+ g.scans += 1
409
+ g.last_scan_project = p.get("project")
410
+ g.last_scan_at = p.get("scanned_at") or event.get("ts")
411
+ elif etype == "graphify_query":
412
+ g.queries += 1
413
+ g.last_query_project = p.get("project")
414
+ g.last_query_at = event.get("ts")
415
+ g.last_query_text = p.get("question") or p.get("file")
416
+
263
417
 
264
418
  def _dc_to_dict(dc) -> dict[str, Any]:
265
419
  return {k: v for k, v in dc.__dict__.items()}
@@ -11,6 +11,7 @@ from mishkan_watchd.state import HarnessState
11
11
 
12
12
  def test_agent_spawn_creates_session_and_agent():
13
13
  s = HarnessState()
14
+ s.apply({"ts": "x", "session": "sess-1", "type": "session_start", "project": "/tmp/proj"})
14
15
  s.apply({
15
16
  "ts": "2026-06-05T12:00:00.000Z",
16
17
  "session": "sess-1",
@@ -27,6 +28,7 @@ def test_agent_spawn_creates_session_and_agent():
27
28
 
28
29
  def test_token_usage_accumulates_per_session():
29
30
  s = HarnessState()
31
+ s.apply({"ts": "x", "session": "sess-tok", "type": "session_start", "project": "/p"})
30
32
  for tin, tout in [(100, 20), (200, 40), (50, 10)]:
31
33
  s.apply({
32
34
  "ts": "2026-06-05T12:00:00.000Z",
@@ -51,6 +53,7 @@ def test_session_stop_removes_session():
51
53
  def test_snapshot_serializes_cleanly():
52
54
  import json
53
55
  s = HarnessState()
56
+ s.apply({"ts": "x", "session": "snap", "type": "session_start", "project": "/p"})
54
57
  s.apply({
55
58
  "ts": "x", "session": "snap", "project": "/p",
56
59
  "type": "agent_spawn", "tool": "Task", "agent": "caleb",
@@ -63,8 +66,9 @@ def test_snapshot_serializes_cleanly():
63
66
 
64
67
  def test_unknown_event_type_is_ignored_gracefully():
65
68
  s = HarnessState()
66
- s.apply({"ts": "x", "session": "weird", "type": "completely_unknown_type"})
67
- # Session is created but no specific state mutation; no exception.
69
+ s.apply({"ts": "x", "session": "weird", "type": "session_start", "project": "/p"})
70
+ s.apply({"ts": "y", "session": "weird", "type": "completely_unknown_type"})
71
+ # Session exists from session_start; unknown type triggers no exception.
68
72
  assert "weird" in s.sessions
69
73
 
70
74
 
@@ -0,0 +1,141 @@
1
+ {
2
+ "generated_from": "docs/design/MISHKAN_agent_aliases.md",
3
+ "schema_version": 1,
4
+ "groups": [
5
+ {
6
+ "id": "orchestration",
7
+ "label": "Orchestration",
8
+ "domain": null,
9
+ "mission": "Routes every request, owns scope, owns technical standards. The two interface layers of MISHKAN — PM (Nehemiah) talks to Y4NN, CTO (Bezalel) gates technical quality.",
10
+ "charter": "No specialist work happens without scope (Nehemiah) and technical fit (Bezalel) approved first.",
11
+ "agents": [
12
+ {"alias": "nehemiah", "role": "project_manager", "short": "PM", "description": "Scope, delivery, sprint state, user interface in exploration mode.", "source": "Book of Nehemiah"},
13
+ {"alias": "bezalel", "role": "cto", "short": "CTO", "description": "Technical standards, architecture, quality bar.", "source": "Exodus 31:2"}
14
+ ]
15
+ },
16
+ {
17
+ "id": "research",
18
+ "label": "Research Pipeline",
19
+ "domain": null,
20
+ "mission": "Shared research pipeline reachable by any agent facing an unknown. Six stages: clarify → formulate → research → summarise → evaluate → report.",
21
+ "charter": "No fabricated facts. If it's unknown, the pipeline runs. The report carries verifiable sources or marks the unknown explicitly.",
22
+ "agents": [
23
+ {"alias": "jakin", "role": "intent_clarificator", "short": "intent", "description": "Establishes the threshold — clarifies what the question really is before research starts.", "source": "1 Kings 7:21"},
24
+ {"alias": "ezra", "role": "research_details_formulator", "short": "formulate", "description": "Structures the research plan with precision.", "source": "Ezra 7:6"},
25
+ {"alias": "caleb", "role": "contextual_web_researcher", "short": "web", "description": "Goes wide and returns a faithful, fearless report.", "source": "Numbers 13:30"},
26
+ {"alias": "shaphan", "role": "contextual_research_summarizer", "short": "summarize", "description": "Compresses Caleb's findings into actionable summary.", "source": "2 Kings 22:3-10"},
27
+ {"alias": "shemaiah", "role": "contextual_research_results_evaluator", "short": "evaluate", "description": "Discerns true signal from false in the research output.", "source": "Nehemiah 6:10-13"},
28
+ {"alias": "baruch", "role": "research_reporter", "short": "report", "description": "Carries the final message back faithfully.", "source": "Jeremiah 36:4"}
29
+ ]
30
+ },
31
+ {
32
+ "id": "chosheb",
33
+ "label": "Chosheb",
34
+ "domain": "Design",
35
+ "hebrew": "חֹשֵׁב",
36
+ "hebrew_meaning": "Cunning work — deviser of artistic works (Exodus 31:4)",
37
+ "mission": "Designs the visible and felt experience before a line of frontend code is written. Owns UI, UX, design system tokens, accessibility intent.",
38
+ "charter": "Design handoff to Panim is complete or it doesn't ship. DS fit, a11y, asset manifest, QA matrix — all four lenses or the package is incomplete.",
39
+ "relationships": "Chosheb → Panim (design handoff · unidirectional)",
40
+ "agents": [
41
+ {"alias": "aholiab", "role": "chosheb_team_lead", "short": "lead", "description": "Bezalel's partner — leads design craftsmen, coordinates handoff.", "source": "Exodus 31:6"},
42
+ {"alias": "hiram", "role": "senior_ui_design_and_prototype_implementation", "short": "ui", "description": "Crafts the visible beautiful things — UI design and prototypes.", "source": "1 Kings 7:13-14"},
43
+ {"alias": "deborah", "role": "cognitive_and_emotional_ux_expert", "short": "ux", "description": "Sees what others miss — cognitive and emotional UX guidance.", "source": "Judges 4:4-5"},
44
+ {"alias": "elasah", "role": "chosheb_team_reporter", "short": "report", "description": "Carries the team's structured output faithfully.", "source": "Jeremiah 29:3"}
45
+ ]
46
+ },
47
+ {
48
+ "id": "panim",
49
+ "label": "Panim",
50
+ "domain": "Frontend",
51
+ "hebrew": "פָּנִים",
52
+ "hebrew_meaning": "Face · presence · what is turned toward (Exodus 33:14)",
53
+ "mission": "Implements every visible surface — components, pages, design system code, asset pipeline, a11y/SEO. Consumes Chosheb's handoff, contracts with Yasad's APIs.",
54
+ "charter": "What the user sees is correct, accessible, and consistent with the DS. Token changes propagate everywhere or nowhere — no half-rolled-out themes.",
55
+ "relationships": "Chosheb → Panim (design handoff) · Panim ↔ Yasad (API contracts)",
56
+ "agents": [
57
+ {"alias": "huram", "role": "panim_team_lead", "short": "lead", "description": "Leads all visible frontend works — cunning across stack.", "source": "2 Chronicles 2:13"},
58
+ {"alias": "oholiab", "role": "senior_frontend_engineer_frontend_design_system_expert", "short": "ds", "description": "Keeper of patterns and standards across components.", "source": "Exodus 35:34"},
59
+ {"alias": "salma", "role": "senior_frontend_developer", "short": "dev", "description": "Implements visible form — the builder of frontend code.", "source": "1 Chronicles 2:51"},
60
+ {"alias": "obed", "role": "smart_frontend_assets_feeder", "short": "assets", "description": "Faithful servant who supplies and sustains assets.", "source": "Ruth 4:17"},
61
+ {"alias": "asaph", "role": "seo_accessibility_expert", "short": "a11y", "description": "Makes the work heard and received by all — SEO + a11y.", "source": "1 Chronicles 16:5"},
62
+ {"alias": "jahaziel", "role": "frontend_qa_engineer", "short": "qa", "description": "Sees what others miss — frontend QA, evaluates without producing.", "source": "2 Chronicles 20:14"},
63
+ {"alias": "ahikam", "role": "panim_team_reporter", "short": "report", "description": "Carries the team's structured output faithfully.", "source": "2 Kings 22:12"}
64
+ ]
65
+ },
66
+ {
67
+ "id": "yasad",
68
+ "label": "Yasad",
69
+ "domain": "Backend",
70
+ "hebrew": "יָסַד",
71
+ "hebrew_meaning": "To lay the foundation · establish solidly (Psalm 24:2)",
72
+ "mission": "Lays and maintains the backend foundation — endpoints, contracts, data schema, persistence, business invariants. Contract-first.",
73
+ "charter": "Contracts before code. Migrations reviewed by 4 orthogonal lenses (contracts/perf/security/tests) before they ship.",
74
+ "relationships": "Panim ↔ Yasad (API contracts) · Yasad ↔ Mishmar (audit + remediation)",
75
+ "agents": [
76
+ {"alias": "zerubbabel", "role": "yasad_team_lead", "short": "lead", "description": "Governor who lays the deep base — leads backend foundation work.", "source": "Ezra 3:2"},
77
+ {"alias": "zadok", "role": "senior_software_engineer_design_system_master", "short": "patterns", "description": "Keeper of what must not change — backend patterns.", "source": "2 Samuel 8:17"},
78
+ {"alias": "nathan", "role": "senior_software_engineer_software_architecture_master", "short": "arch", "description": "Brings architectural vision — what should and should not be built.", "source": "2 Samuel 7:2"},
79
+ {"alias": "hizkiah", "role": "senior_backend_engineer_pure_backend_implementation", "short": "impl", "description": "Strength of Yah — does the direct backend implementation labour.", "source": "2 Chronicles 31:13"},
80
+ {"alias": "shallum", "role": "senior_backend_engineering_databases_expert", "short": "db", "description": "Keeper of what is stored — database design and migration.", "source": "2 Kings 15:10"},
81
+ {"alias": "uriah", "role": "backend_qa_engineer", "short": "qa", "description": "Absolute integrity — holds the line on backend standards.", "source": "2 Samuel 11"},
82
+ {"alias": "igal", "role": "yasad_team_reporter", "short": "report", "description": "Reports faithfully from his section.", "source": "Numbers 13:7"}
83
+ ]
84
+ },
85
+ {
86
+ "id": "mishmar",
87
+ "label": "Mishmar",
88
+ "domain": "Security",
89
+ "hebrew": "מִשְׁמָר",
90
+ "hebrew_meaning": "Guard · watch · the post set day and night (Nehemiah 4:9)",
91
+ "mission": "Cross-cutting security — code, surface, infrastructure, threat model. A constraint shaping every team's output from the start, not a team reviewing at the end.",
92
+ "charter": "Sensitive surface changes pass the 3-lens gate (code-OWASP / surface / threat-model) with adversarial refute before merge. Hardening overlay re-applied on every container recreate.",
93
+ "relationships": "Mishmar ↔ all teams (cross-cutting audit) · Mishmar → Migdal (security gates infra)",
94
+ "agents": [
95
+ {"alias": "phinehas", "role": "mishmar_team_lead", "short": "lead", "description": "Acted decisively to stop a breach — zealous for the boundary.", "source": "Numbers 25:7-8"},
96
+ {"alias": "benaiah", "role": "software_and_infrastructure_security_expert_devsecops", "short": "infra-sec", "description": "Mighty man who handles the hardest infrastructure threats.", "source": "2 Samuel 23:20"},
97
+ {"alias": "joab", "role": "web_mobile_desktop_security_expert", "short": "app-sec", "description": "Field general across all attack vectors — app surface security.", "source": "2 Samuel 8:16"},
98
+ {"alias": "ira", "role": "code_security_ops", "short": "code-sec", "description": "Keeps watch at the code level — security ops.", "source": "2 Samuel 20:26"},
99
+ {"alias": "hushai", "role": "software_security_advisor", "short": "advisor", "description": "Trusted advisor who outmanoeuvred the attacker — security counsel.", "source": "2 Samuel 15:37"},
100
+ {"alias": "maaseiah", "role": "mishmar_team_reporter", "short": "report", "description": "Carries the structured account faithfully.", "source": "Nehemiah 8:4"}
101
+ ]
102
+ },
103
+ {
104
+ "id": "migdal",
105
+ "label": "Migdal",
106
+ "domain": "Infrastructure",
107
+ "hebrew": "מִגְדָּל",
108
+ "hebrew_meaning": "Tower · load-bearing structures of the wall (Nehemiah 3:1)",
109
+ "mission": "Builds and operates the load-bearing infrastructure — systems, network, devops pipelines, observability, recovery. Gated by Mishmar at change time.",
110
+ "charter": "Infra change passes the 5-lens panel (design/systems/devops/observability/health) before apply. No `:latest` tags. Hardening overlay always re-applied. DR drilled quarterly.",
111
+ "relationships": "Mishmar → Migdal (security gates) · Migdal serves all teams' runtime",
112
+ "agents": [
113
+ {"alias": "eliashib", "role": "migdal_team_lead", "short": "lead", "description": "Organises the foundational infrastructure work.", "source": "Nehemiah 3:1"},
114
+ {"alias": "palal", "role": "systems_engineer_os_virtualisation_networks", "short": "systems", "description": "Works at the structural intersection — OS / virt / networks.", "source": "Nehemiah 3:25"},
115
+ {"alias": "meshullam", "role": "infrastructure_design_engineer", "short": "design", "description": "Designs connections between infrastructure parts.", "source": "Nehemiah 3:4"},
116
+ {"alias": "hanun", "role": "devsecops_practitioner_support_ops", "short": "devsecops", "description": "Long support stretch — devsecops practice, operational support.", "source": "Nehemiah 3:13"},
117
+ {"alias": "meremoth", "role": "devops_engineer", "short": "devops", "description": "Works at the delivery layer — pipelines, deploys.", "source": "Nehemiah 3:4"},
118
+ {"alias": "rehum", "role": "infrastructure_health_and_security_advisor", "short": "health", "description": "Watches for risk and advises — infrastructure health.", "source": "Nehemiah 3:17"},
119
+ {"alias": "zaccur", "role": "migdal_team_reporter", "short": "report", "description": "Keeps record, mindful of what happened.", "source": "Nehemiah 3:2"}
120
+ ]
121
+ },
122
+ {
123
+ "id": "sefer",
124
+ "label": "Sefer",
125
+ "domain": "Documentation",
126
+ "hebrew": "סֵפֶר",
127
+ "hebrew_meaning": "Book · scroll · official written record (Nehemiah 8, Ezra 6)",
128
+ "mission": "Documentation across three layers — organisation (Seraiah), project (Joah), team (Shevna). Pull-based: pulls at milestones and on trigger events, not on every commit.",
129
+ "charter": "What is not written down is forgotten. Release notes assembled from git log per category. ADRs for every consequential decision. Style guide applied uniformly.",
130
+ "relationships": "Sefer ← all teams (pulls at milestones + trigger events)",
131
+ "agents": [
132
+ {"alias": "jehoshaphat", "role": "sefer_team_lead", "short": "lead", "description": "First Recorder in David's court — cares for national archives.", "source": "2 Samuel 8:16"},
133
+ {"alias": "seraiah", "role": "organisation_layer_specialist", "short": "org", "description": "Operates at the highest state level — org-wide documentation.", "source": "2 Samuel 8:17"},
134
+ {"alias": "joah", "role": "project_layer_specialist", "short": "project", "description": "Documents specific events and decisions of each project.", "source": "2 Kings 18:18"},
135
+ {"alias": "shevna", "role": "team_layer_specialist", "short": "team", "description": "Embedded with teams — documents their specific outputs.", "source": "2 Kings 18:18"},
136
+ {"alias": "jehonathan", "role": "knowledge_publication_specialist", "short": "publish", "description": "Takes knowledge and makes it legible for others.", "source": "1 Chronicles 27:32"},
137
+ {"alias": "huldah", "role": "sefer_team_reporter", "short": "report", "description": "Verifies, interprets, reports back with authority.", "source": "2 Kings 22:14"}
138
+ ]
139
+ }
140
+ ]
141
+ }
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env bash
2
+ # pre-commit-eval-gate.sh — run MISHKAN evals if their contract surface changed.
3
+ #
4
+ # Optional install: symlink as your repo's .git/hooks/pre-commit so the gate
5
+ # fires before every commit. Detects whether the staged changes touch any
6
+ # eval's sentinel paths; runs only the affected eval(s).
7
+ #
8
+ # Install:
9
+ # ln -s "$(pwd)/payload/mishkan/scripts/pre-commit-eval-gate.sh" .git/hooks/pre-commit
10
+ # chmod +x .git/hooks/pre-commit
11
+ #
12
+ # Uninstall:
13
+ # rm .git/hooks/pre-commit
14
+ #
15
+ # Exit codes:
16
+ # 0 → no relevant changes OR all triggered evals passed
17
+ # 1 → at least one eval failed (commit blocked)
18
+ # 2 → an eval's environment is missing (jq, validator)
19
+ set -uo pipefail
20
+
21
+ REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || exit 0
22
+ cd "$REPO_ROOT"
23
+
24
+ changed=$(git diff --cached --name-only --diff-filter=ACMR)
25
+ [[ -z "$changed" ]] && exit 0
26
+
27
+ trigger_baruch=false
28
+ while IFS= read -r f; do
29
+ case "$f" in
30
+ payload/mishkan/templates/research-log.schema.json) trigger_baruch=true ;;
31
+ payload/mishkan/scripts/validate-research-log.sh) trigger_baruch=true ;;
32
+ payload/mishkan/agents/baruch.md) trigger_baruch=true ;;
33
+ payload/mishkan/skills/baruch-research-reporting-craft/*) trigger_baruch=true ;;
34
+ payload/mishkan/evals/baruch/*) trigger_baruch=true ;;
35
+ esac
36
+ done <<< "$changed"
37
+
38
+ overall=0
39
+ if [[ "$trigger_baruch" == "true" ]]; then
40
+ echo "→ Baruch eval (contract surface changed)"
41
+ if bash payload/mishkan/evals/baruch/run.sh; then
42
+ echo " ✓ Baruch eval passed"
43
+ else
44
+ rc=$?
45
+ echo " ✗ Baruch eval failed (exit $rc)" >&2
46
+ overall=$rc
47
+ fi
48
+ fi
49
+
50
+ exit "$overall"