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
@@ -0,0 +1,67 @@
1
+ // migdal-infra-change — infra change validated by 5 orthogonal lenses.
2
+ //
3
+ // An infra change (Terraform module, Helm chart, K8s manifest, network rule)
4
+ // goes in; five lenses verify in parallel: design intent, systems/blast radius,
5
+ // devops/rollback path, observability impact, post-change health checks.
6
+ // Output is go/no-go with named blockers.
7
+ //
8
+ // Pattern: barrier `parallel()` per lens + synthesis.
9
+ // ADR D-010 check:
10
+ // - panel orthogonality: design / systems / devops / observability / health
11
+ // are distinct domains.
12
+ // - synthesis: structured go/no-go with rollback path attached.
13
+
14
+ export const meta = {
15
+ name: "migdal-infra-change",
16
+ description: "Infra change validated by 5 orthogonal lenses (design / systems / devops / observability / health).",
17
+ whenToUse: "Before applying any infra change with non-trivial blast radius (network, IAM, scaling, cross-service deps).",
18
+ phases: [{ title: "Review" }, { title: "Decide" }],
19
+ };
20
+
21
+ const changeDescription = args?.change_description;
22
+ const changeFiles = args?.change_files ?? [];
23
+ const project = args?.project ?? ".";
24
+ if (!changeDescription) throw new Error("args.change_description is required");
25
+
26
+ const VERDICT_SCHEMA = {
27
+ type: "object", required: ["safe", "rationale"],
28
+ properties: { safe: {type:"boolean"}, rationale: {type:"string"}, blockers: {type:"array", items:{type:"string"}}, mitigations: {type:"array", items:{type:"string"}}, rollback_steps: {type:"array", items:{type:"string"}} },
29
+ };
30
+
31
+ phase("Review");
32
+ const LENSES = [
33
+ { key: "design", agent: "meshullam", prompt: "Infrastructure design: does this change cohere with existing infra patterns? Any redesign required upstream?" },
34
+ { key: "systems", agent: "palal", prompt: "Systems / blast radius: network impact, OS-level changes, virt layer, dependent services. What goes down if this fails?" },
35
+ { key: "devops", agent: "meremoth", prompt: "Devops / pipeline: deployment path, rollback procedure, CI/CD impact, secret rotations needed." },
36
+ { key: "obs", agent: "hanun", prompt: "Observability: metrics/logs/traces impact, alert rules to add, dashboards to update, SLO implications." },
37
+ { key: "health", agent: "rehum", prompt: "Post-change health: health checks to run after apply, probes to add, smoke tests, expected steady-state metrics." },
38
+ ];
39
+
40
+ const verdicts = await parallel(LENSES.map(L => () =>
41
+ agent(
42
+ `Infra change: ${changeDescription}. Files: ${JSON.stringify(changeFiles)}. Project: ${project}. Your lens: ${L.prompt} Default safe=false if uncertain — infra changes are high-stakes.`,
43
+ { schema: VERDICT_SCHEMA, label: `review:${L.key}`, agentType: L.agent, phase: "Review" },
44
+ ).then(v => ({ lens: L.key, ...v }))
45
+ ));
46
+
47
+ const valid = verdicts.filter(Boolean);
48
+ const safeCount = valid.filter(v => v.safe).length;
49
+ const allBlockers = valid.flatMap(v => v.blockers ?? []);
50
+ const allMitigations = valid.flatMap(v => v.mitigations ?? []);
51
+ const rollback = valid.flatMap(v => v.rollback_steps ?? []);
52
+
53
+ phase("Decide");
54
+ return {
55
+ change: changeDescription,
56
+ change_files: changeFiles,
57
+ lens_verdicts: valid,
58
+ safe_count: safeCount,
59
+ all_safe: safeCount === LENSES.length,
60
+ blockers: allBlockers,
61
+ mitigations: allMitigations,
62
+ rollback_plan: rollback,
63
+ decision: safeCount === LENSES.length ? "go" : "no-go",
64
+ summary: safeCount === LENSES.length
65
+ ? `Infra change GO. ${safeCount}/5 lenses safe. Rollback path: ${rollback.length} steps documented.`
66
+ : `Infra change NO-GO. ${LENSES.length - safeCount}/5 lenses flagged blockers. Address: ${allBlockers.slice(0,3).join(" | ")}`,
67
+ };
@@ -0,0 +1,144 @@
1
+ // mishkan-blast-radius — what does this change actually touch?
2
+ //
3
+ // Given a refactor target (symbol/function/class), walk the Graphify
4
+ // reverse-call graph for each impacted call site, then adversarially
5
+ // verify each "this is impacted" claim with three load-bearing lenses
6
+ // (caller-side / data-contract / runtime-behavior) so false-positive
7
+ // impacted sites get filtered out before the engineer schedules work.
8
+ //
9
+ // Pattern: Graphify discovery → parallel fan-out per call site →
10
+ // adversarial verify (3-lens panel) → synthesis.
11
+ //
12
+ // Per PM+CTO portfolio review (2026-06-07):
13
+ // - Nehemiah: keep — recurrence justified for every non-trivial refactor.
14
+ // Must be gated by /plan approval before invocation. Renamed from
15
+ // `mishkan-graphify-impact-audit` to drop implementation detail from
16
+ // the user-facing name.
17
+ // - Bezalel: keep + rework with three EXPLICIT lenses that are
18
+ // orthogonal (not 70%-overlapping), and a short-circuit when Graphify
19
+ // returns < N nodes so we don't burn tokens on empty fanouts.
20
+ //
21
+ // Args (passed via Workflow `args`):
22
+ // { target: "process_payment", depth: 3, project: "/path/to/repo",
23
+ // min_sites_to_verify: 2 } // short-circuit threshold
24
+ //
25
+ // Pre-req: the project has been scanned (graphify update .). Workflow
26
+ // emits a single graphify_query to gather impacted sites; that's the budget.
27
+
28
+ export const meta = {
29
+ name: "mishkan-blast-radius",
30
+ description: "What does this change actually touch? Graphify discovery + 3-lens orthogonal verify per impacted site.",
31
+ whenToUse: "Before editing a function whose downstream impact you don't fully know. Gated by /plan approval per the harness sequence rule.",
32
+ phases: [
33
+ { title: "Discover" },
34
+ { title: "Verify" },
35
+ { title: "Synthesize" },
36
+ ],
37
+ };
38
+
39
+ const target = args?.target;
40
+ const depth = args?.depth ?? 3;
41
+ const project = args?.project ?? ".";
42
+ const MIN_SITES = args?.min_sites_to_verify ?? 2;
43
+ if (!target) throw new Error("args.target is required (the symbol/function/class to audit)");
44
+
45
+ const IMPACTED_SCHEMA = {
46
+ type: "object",
47
+ required: ["sites"],
48
+ properties: {
49
+ sites: {
50
+ type: "array",
51
+ items: {
52
+ type: "object",
53
+ required: ["file", "symbol", "reason"],
54
+ properties: {
55
+ file: { type: "string" },
56
+ line: { type: "integer" },
57
+ symbol: { type: "string" },
58
+ reason: { type: "string" },
59
+ relation: { type: "string" },
60
+ },
61
+ },
62
+ },
63
+ },
64
+ };
65
+
66
+ const VERDICT_SCHEMA = {
67
+ type: "object",
68
+ required: ["really_impacted", "confidence", "rationale"],
69
+ properties: {
70
+ really_impacted: { type: "boolean" },
71
+ confidence: { type: "string", enum: ["low", "medium", "high"] },
72
+ rationale: { type: "string" },
73
+ suggested_action: { type: "string" },
74
+ },
75
+ };
76
+
77
+ // Three ORTHOGONAL lenses per Bezalel — each load-bearing, no overlap.
78
+ // caller-side = the SHAPE of the call: signature, types, exception contract.
79
+ // data-contract = the DATA flowing through: invariants, schema, nullability.
80
+ // runtime = the RUNTIME behavior: ordering, side effects, timing, perf.
81
+ const LENSES = [
82
+ {
83
+ key: "caller",
84
+ prompt: "Will the SHAPE of THIS call site break? Signature change, type mismatch, new/removed exception, async/sync mismatch — purely the call protocol, not the data semantics.",
85
+ },
86
+ {
87
+ key: "data",
88
+ prompt: "Will the DATA semantics break? Invariants this call site assumes about the target's input/output, nullability, ordering of fields, encoding — purely the data contract, not the call protocol.",
89
+ },
90
+ {
91
+ key: "runtime",
92
+ prompt: "Will the RUNTIME behavior break? Side effects this call site depends on, timing/ordering guarantees, performance characteristics, thread/event-loop assumptions — purely runtime, not data or shape.",
93
+ },
94
+ ];
95
+
96
+ phase("Discover");
97
+ const discovery = await agent(
98
+ `Run \`graphify affected "${target}" --depth ${depth} --relations calls,imports --graph ${project}/graphify-out/graph.json\` and parse the impacted call sites it returns. Cite file:line per site. Return strictly the schema.`,
99
+ { schema: IMPACTED_SCHEMA, label: `discover:${target}` },
100
+ );
101
+ const sites = discovery?.sites ?? [];
102
+ log(`Discovered ${sites.length} potentially impacted sites.`);
103
+
104
+ // Short-circuit: empty fan-out is wasted tokens.
105
+ if (sites.length < MIN_SITES) {
106
+ return {
107
+ target,
108
+ impacted: sites,
109
+ short_circuited: true,
110
+ summary: `Discovered ${sites.length} sites (< MIN_SITES=${MIN_SITES}). Verification skipped. Either the target has no callers worth verifying, or the graph is stale — run \`graphify update .\` and retry.`,
111
+ };
112
+ }
113
+
114
+ phase("Verify");
115
+ const verifiedSites = await parallel(sites.map(site => () =>
116
+ parallel(LENSES.map(L => () =>
117
+ agent(
118
+ `Refactor target: ${target}. Impacted site: ${site.file}:${site.line ?? "?"} (${site.symbol}) — graph reason: "${site.reason}". Lens: ${L.prompt} Default to really_impacted=false if uncertain — we tolerate misses, not false positives.`,
119
+ { schema: VERDICT_SCHEMA, label: `verify:${L.key}:${site.symbol}`, phase: "Verify" },
120
+ )
121
+ ))
122
+ .then(verdicts => {
123
+ const valid = verdicts.filter(Boolean);
124
+ const really = valid.filter(v => v.really_impacted).length;
125
+ return {
126
+ site,
127
+ lens_verdicts: valid,
128
+ lens_really_count: really,
129
+ // Confirmed = majority across the THREE orthogonal lenses.
130
+ confirmed: really >= 2,
131
+ };
132
+ })
133
+ ));
134
+
135
+ const confirmed = verifiedSites.filter(v => v.confirmed);
136
+ log(`${confirmed.length}/${sites.length} sites confirmed by orthogonal majority (>=2 of 3 lenses).`);
137
+
138
+ phase("Synthesize");
139
+ const summary = await agent(
140
+ `Refactor target: ${target}. Confirmed impacted sites: ${JSON.stringify(confirmed.map(c => ({file: c.site.file, symbol: c.site.symbol, votes: c.lens_really_count, top_lens: c.lens_verdicts.find(v => v.really_impacted)?.rationale})))}. Produce a 6-10 line engineer-action summary: how many sites, the highest-risk one + which lens flagged it, suggested PR sequence (independent first, then coupled). No fluff.`,
141
+ { label: "synthesize", phase: "Synthesize" },
142
+ );
143
+
144
+ return { target, impacted: confirmed, summary };
@@ -22,7 +22,19 @@ export const meta = {
22
22
  name: 'mishkan-init',
23
23
  description: 'Pipelined PRD → SRS → CONTRACT → ARCHITECTURE → THREAT_MODEL → C4. Downstream stages start as soon as their upstream dependency lands; sequence rule preserved.',
24
24
  whenToUse: 'Once per project at /mishkan-init. Brownfield mode uses the same shape with read-existing instead of write-new at each stage.',
25
+ // Phase 1 canary for the skill-discovery layer (D-011). The router uses
26
+ // these to apply a ×1.5 category prior when surfacing candidate skills
27
+ // during the SkillRouter phase below. The actual loading decision stays
28
+ // with the model; the router is advisory.
29
+ relevant_skill_categories: [
30
+ 'craft',
31
+ 'mishkan-workflow',
32
+ 'docs',
33
+ 'security',
34
+ 'process',
35
+ ],
25
36
  phases: [
37
+ { title: 'SkillRouter', detail: 'Skill-discovery router surfaces relevant skills (advisory)' },
26
38
  { title: 'PRD', detail: 'Nehemiah writes the product requirement document' },
27
39
  { title: 'SRS', detail: 'Nathan turns PRD into a testable system requirement spec' },
28
40
  { title: 'CONTRACT+ARCH',detail: 'Zadok writes CONTRACT; Nathan writes ARCHITECTURE — in parallel' },
@@ -48,6 +60,51 @@ const ARTEFACT_SCHEMA = {
48
60
 
49
61
  const STACK = args.stack_hint || 'undetermined'
50
62
 
63
+ // --- Stage 0: SkillRouter (canary, advisory) --------------------------
64
+ // Surfaces candidate skills relevant to the init task. Pure read; never
65
+ // blocks. Result is passed into the Bezalel signoff prompt at Settle so
66
+ // the CTO sees what the model could have reached for. Fail-open: any
67
+ // router error is logged and the workflow continues.
68
+ phase('SkillRouter')
69
+ let skillCandidates = null
70
+ try {
71
+ const cats = (meta.relevant_skill_categories || []).join(',')
72
+ const routerTask =
73
+ `Initialise project "${args.project_name}" under MISHKAN. ` +
74
+ `Intent: ${args.raw_intent}. Stack: ${STACK}. ` +
75
+ `Produce PRD, SRS, CONTRACT, ARCHITECTURE, THREAT_MODEL, C4, docs scaffold.`
76
+ // The runner is expected to execute the python helper out-of-band; if it
77
+ // does not, the catch below logs the miss and we continue.
78
+ skillCandidates = await agent(
79
+ `Invoke the skill-discovery router as an advisory pass. Run:\n` +
80
+ ` python3 ~/.claude/mishkan/scripts/skill-discovery-router.py ` +
81
+ `--task ${JSON.stringify(routerTask)} ` +
82
+ `--workflow mishkan-init ` +
83
+ `--relevant-categories ${JSON.stringify(cats)}\n` +
84
+ `Return the parsed JSON output verbatim. Do not load any of the ` +
85
+ `surfaced skills. This pass is advisory only — the loading decision ` +
86
+ `stays with the model and with Bezalel at Settle.`,
87
+ {
88
+ label: 'skill-discovery:router',
89
+ phase: 'SkillRouter',
90
+ agentType: 'skill-discovery',
91
+ schema: {
92
+ type: 'object',
93
+ required: ['task_summary'],
94
+ properties: {
95
+ task_summary: { type: 'string' },
96
+ must_load: { type: 'array' },
97
+ should_consider: { type: 'array' },
98
+ adjacent: { type: 'array' },
99
+ warnings: { type: 'array', items: { type: 'string' } },
100
+ },
101
+ },
102
+ }
103
+ )
104
+ } catch (err) {
105
+ skillCandidates = { error: String(err), warnings: ['router_invocation_failed'] }
106
+ }
107
+
51
108
  // --- Stage 1: PRD ------------------------------------------------------
52
109
  phase('PRD')
53
110
  const prd = await agent(
@@ -149,6 +206,7 @@ return {
149
206
  prd, srs, contract, architecture, threat, c4,
150
207
  docs_scaffold: docsScaffold,
151
208
  },
209
+ skill_candidates: skillCandidates,
152
210
  bezalel_signoff: signoff,
153
211
  next: signoff?.status === 'accepted'
154
212
  ? `Init complete. Begin Sprint S0; Nehemiah leads. Hand the directory state to Y4NN for git init + first commit.`
@@ -0,0 +1,121 @@
1
+ // mishkan-knowledge-gap-discovery — find what we DON'T have in Cognee.
2
+ //
3
+ // Given an expected concept list (e.g. "what every project should have an
4
+ // ADR for"), probe Cognee work for each — when nothing relevant comes back
5
+ // for K rounds in a row, declare the concept a gap and dispatch a research
6
+ // pipeline run to fill it. Loop-until-dry on the gap discovery side.
7
+ //
8
+ // Pattern: parallel probe → loop-until-dry per concept → research fan-out
9
+ // for confirmed gaps → write findings back to Cognee work.
10
+ //
11
+ // Args:
12
+ // { concepts: ["authentication ADR", "rate-limit runbook", ...],
13
+ // dry_rounds: 2,
14
+ // research_budget_per_gap: 5000 }
15
+
16
+ export const meta = {
17
+ name: "mishkan-knowledge-gap-discovery",
18
+ description: "Probe Cognee work for an expected concept list, identify gaps with loop-until-dry, dispatch research to fill confirmed gaps.",
19
+ whenToUse: "Sprint-close audit, onboarding a new project, post-incident knowledge inventory. Anywhere you suspect 'we have less written down than we think'.",
20
+ phases: [
21
+ { title: "Probe" },
22
+ { title: "ConfirmGaps" },
23
+ { title: "Fill" },
24
+ ],
25
+ };
26
+
27
+ const concepts = args?.concepts;
28
+ const DRY_ROUNDS = args?.dry_rounds ?? 2;
29
+ const RESEARCH_BUDGET = args?.research_budget_per_gap ?? 5000;
30
+ if (!Array.isArray(concepts) || !concepts.length) {
31
+ throw new Error("args.concepts must be a non-empty list of expected-concept strings");
32
+ }
33
+
34
+ const PROBE_SCHEMA = {
35
+ type: "object",
36
+ required: ["concept", "matches"],
37
+ properties: {
38
+ concept: { type: "string" },
39
+ matches: {
40
+ type: "array",
41
+ items: {
42
+ type: "object",
43
+ required: ["node_id", "title", "relevance"],
44
+ properties: {
45
+ node_id: { type: "string" },
46
+ title: { type: "string" },
47
+ relevance: { type: "string", enum: ["strong", "weak", "none"] },
48
+ },
49
+ },
50
+ },
51
+ },
52
+ };
53
+
54
+ const RESEARCH_FINDING_SCHEMA = {
55
+ type: "object",
56
+ required: ["concept", "summary", "sources"],
57
+ properties: {
58
+ concept: { type: "string" },
59
+ summary: { type: "string" },
60
+ sources: { type: "array", items: { type: "string" } },
61
+ suggested_cognee_node_type: { type: "string" },
62
+ },
63
+ };
64
+
65
+ phase("Probe");
66
+ log(`Probing ${concepts.length} concepts against Cognee work…`);
67
+
68
+ const initial = await parallel(concepts.map(concept => () =>
69
+ agent(
70
+ `Search Cognee work store for "${concept}". Use the cognee-search MCP tool. Classify each hit's relevance to the literal concept (strong/weak/none). Return strictly the schema.`,
71
+ { schema: PROBE_SCHEMA, label: `probe:${concept.slice(0, 24)}` },
72
+ )
73
+ ));
74
+
75
+ const strong = (r) => (r?.matches ?? []).filter(m => m.relevance === "strong").length;
76
+
77
+ const gaps = initial.filter(Boolean).filter(r => strong(r) === 0).map(r => r.concept);
78
+ log(`Initial probe: ${gaps.length}/${concepts.length} concepts have NO strong match in Cognee work.`);
79
+
80
+ phase("ConfirmGaps");
81
+ const confirmedGaps = [];
82
+ for (const gap of gaps) {
83
+ let dryRounds = 0;
84
+ let rephraseCount = 0;
85
+ while (dryRounds < DRY_ROUNDS && rephraseCount < 4) {
86
+ rephraseCount++;
87
+ const reprobe = await agent(
88
+ `Search Cognee work for the same concept under a different phrasing (paraphrase ${rephraseCount}/4 of "${gap}"). Different wording, same semantic intent. Return the schema.`,
89
+ { schema: PROBE_SCHEMA, label: `reprobe:${gap.slice(0, 18)}/${rephraseCount}`, phase: "ConfirmGaps" },
90
+ );
91
+ if (reprobe && strong(reprobe) > 0) {
92
+ log(`"${gap}" — found via paraphrase ${rephraseCount}, NOT a gap.`);
93
+ dryRounds = -1;
94
+ break;
95
+ }
96
+ dryRounds++;
97
+ }
98
+ if (dryRounds >= DRY_ROUNDS) {
99
+ confirmedGaps.push(gap);
100
+ }
101
+ }
102
+ log(`${confirmedGaps.length} confirmed gaps after ${DRY_ROUNDS}-round dry-confirmation.`);
103
+
104
+ if (!confirmedGaps.length) {
105
+ return { probed: concepts.length, gaps: 0, summary: "No confirmed gaps — every expected concept has at least a paraphrased match in Cognee work." };
106
+ }
107
+
108
+ phase("Fill");
109
+ const findings = await parallel(confirmedGaps.map(gap => () =>
110
+ agent(
111
+ `Run the MISHKAN research pipeline (Jakin → Ezra → Caleb → Shaphan → Shemaiah → Baruch) for: "${gap}". Token budget: ${RESEARCH_BUDGET}. Produce a finding suitable for ingestion into Cognee work as a new node. Return the schema.`,
112
+ { schema: RESEARCH_FINDING_SCHEMA, label: `research:${gap.slice(0, 18)}`, phase: "Fill" },
113
+ )
114
+ ));
115
+
116
+ return {
117
+ probed: concepts.length,
118
+ gaps_confirmed: confirmedGaps.length,
119
+ findings: findings.filter(Boolean),
120
+ summary: `Probed ${concepts.length} concepts, confirmed ${confirmedGaps.length} gaps, produced ${findings.filter(Boolean).length} research findings ready to cognify.`,
121
+ };
@@ -0,0 +1,153 @@
1
+ // mishkan-standards-rollout — propagate a new standard across the 6 teams.
2
+ //
3
+ // When y4nn-standards.md gains a new rule (security, infra, doc convention,
4
+ // language standard), the rule needs to land in each Team's path-scoped rules
5
+ // + each Team Lead needs to verify their team's agents align + Bezalel
6
+ // ratifies the final state. Today this is manual and drifts — different
7
+ // teams adopt at different velocities and the harness silently diverges.
8
+ //
9
+ // Pattern: pipeline (propose → translate per team → verify per team → ratify)
10
+ // + barrier (all 6 Team Leads must verify before Bezalel can ratify)
11
+ // + judge panel (Bezalel + Phinehas + Jehoshaphat ratify).
12
+ //
13
+ // Per PM+CTO portfolio review (2026-06-07):
14
+ // - Bezalel's #1 pick: rules drift is the highest-cost silent failure
15
+ // mode in MISHKAN. A workflow gate is the only durable fix.
16
+ // - Nehemiah aligned: cross-team coordination of standards is recurrent
17
+ // each time a new rule lands.
18
+ //
19
+ // Args:
20
+ // { rule_text: "...the new standard text, in prose...",
21
+ // rule_id: "RULE-2026-06-007",
22
+ // scope_hint: "security" | "infra" | "doc" | "code" | "process" }
23
+
24
+ export const meta = {
25
+ name: "mishkan-standards-rollout",
26
+ description: "Propagate a new standard across the 6 teams with per-team translation, verification, and CTO ratification.",
27
+ whenToUse: "When y4nn-standards.md or a team rule gains a new rule that other teams must adopt. Run BEFORE the rule is considered shipped.",
28
+ phases: [
29
+ { title: "Translate" },
30
+ { title: "Verify" },
31
+ { title: "Ratify" },
32
+ ],
33
+ };
34
+
35
+ const ruleText = args?.rule_text;
36
+ const ruleId = args?.rule_id ?? `RULE-${new Date().toISOString().slice(0,10)}`;
37
+ const scopeHint = args?.scope_hint ?? "code";
38
+ if (!ruleText) throw new Error("args.rule_text is required");
39
+
40
+ const TEAMS = [
41
+ { name: "Chosheb", lead: "aholiab", domain: "Design / UX" },
42
+ { name: "Panim", lead: "huram", domain: "Frontend" },
43
+ { name: "Yasad", lead: "zerubbabel", domain: "Backend / data" },
44
+ { name: "Mishmar", lead: "phinehas", domain: "Security (cross-cutting)" },
45
+ { name: "Migdal", lead: "eliashib", domain: "Infrastructure / ops" },
46
+ { name: "Sefer", lead: "jehoshaphat", domain: "Documentation" },
47
+ ];
48
+
49
+ const TRANSLATION_SCHEMA = {
50
+ type: "object",
51
+ required: ["applies", "rule_in_team_voice", "impact"],
52
+ properties: {
53
+ applies: { type: "boolean" },
54
+ rule_in_team_voice: { type: "string" },
55
+ impact: { type: "string", enum: ["none", "small", "medium", "large"] },
56
+ breaking_for: { type: "array", items: { type: "string" } },
57
+ rationale: { type: "string" },
58
+ },
59
+ };
60
+
61
+ const VERIFICATION_SCHEMA = {
62
+ type: "object",
63
+ required: ["aligned", "drift_found"],
64
+ properties: {
65
+ aligned: { type: "boolean" },
66
+ drift_found: { type: "array", items: { type: "string" } },
67
+ remediation_needed: { type: "string" },
68
+ },
69
+ };
70
+
71
+ const RATIFY_SCHEMA = {
72
+ type: "object",
73
+ required: ["ratified", "rationale"],
74
+ properties: {
75
+ ratified: { type: "boolean" },
76
+ rationale: { type: "string" },
77
+ blockers: { type: "array", items: { type: "string" } },
78
+ },
79
+ };
80
+
81
+ phase("Translate");
82
+ // Per-team translation. Each Team Lead decides if the rule applies to
83
+ // their team and what it means in their team's voice / vocabulary /
84
+ // path-scoped rule file.
85
+ const translations = await parallel(TEAMS.map(T => () =>
86
+ agent(
87
+ `New ${scopeHint} standard ${ruleId} just landed at the harness level. Verbatim text: """${ruleText}""" In your role as ${T.name} (${T.domain}) team lead, decide: does this apply to YOUR team? If yes, how would you write it in your team's voice for your path-scoped rules file? If no, justify briefly. Return the schema.`,
88
+ { schema: TRANSLATION_SCHEMA, label: `translate:${T.name}`, agentType: T.lead, phase: "Translate" },
89
+ ).then(t => ({ ...T, ...t }))
90
+ ));
91
+
92
+ const applies = translations.filter(Boolean).filter(t => t.applies);
93
+ log(`Rule ${ruleId} applies to ${applies.length}/${TEAMS.length} teams.`);
94
+
95
+ if (!applies.length) {
96
+ return {
97
+ rule_id: ruleId,
98
+ decision: "noop",
99
+ summary: "No team's rules need updating for this standard. Y4NN-standards.md amendment alone is sufficient.",
100
+ };
101
+ }
102
+
103
+ phase("Verify");
104
+ // Per-team verification: does the team's CURRENT agents/specialists/skills
105
+ // already align with this rule, or is there drift to remediate?
106
+ const verifications = await parallel(applies.map(T => () =>
107
+ agent(
108
+ `Standard ${ruleId} now applies to your team (${T.name}). Translated form: "${T.rule_in_team_voice}" Check your team's agents, skills, and existing rules for DRIFT — places that already contradict or won't propagate this rule. Return the schema.`,
109
+ { schema: VERIFICATION_SCHEMA, label: `verify:${T.name}`, agentType: T.lead, phase: "Verify" },
110
+ ).then(v => ({ team: T.name, lead: T.lead, ...v }))
111
+ ));
112
+
113
+ const drifting = verifications.filter(Boolean).filter(v => !v.aligned || (v.drift_found?.length ?? 0) > 0);
114
+ log(`${drifting.length}/${applies.length} applying teams have drift to remediate.`);
115
+
116
+ phase("Ratify");
117
+ // Bezalel (CTO) + Phinehas (security cross-cutting if scope=security) +
118
+ // Jehoshaphat (Sefer/doc convention if scope=doc) ratify.
119
+ const ratifiers = [
120
+ { role: "bezalel", focus: "CTO — architecture and standards consistency across the harness" },
121
+ ];
122
+ if (scopeHint === "security") {
123
+ ratifiers.push({ role: "phinehas", focus: "Mishmar lead — cross-cutting security impact" });
124
+ }
125
+ if (scopeHint === "doc") {
126
+ ratifiers.push({ role: "jehoshaphat", focus: "Sefer lead — documentation convention impact" });
127
+ }
128
+
129
+ const ratifications = await parallel(ratifiers.map(R => () =>
130
+ agent(
131
+ `Standard ${ruleId} rollout review. ${R.focus} ` +
132
+ `Translations: ${JSON.stringify(applies.map(t => ({team: t.name, voice: t.rule_in_team_voice, impact: t.impact})))} ` +
133
+ `Verifications: ${JSON.stringify(verifications.filter(Boolean))} ` +
134
+ `Should this rule ship as-is, or are there blockers? Return the schema.`,
135
+ { schema: RATIFY_SCHEMA, label: `ratify:${R.role}`, agentType: R.role, phase: "Ratify" },
136
+ ).then(r => ({ role: R.role, ...r }))
137
+ ));
138
+
139
+ const allRatified = ratifications.filter(Boolean).every(r => r.ratified);
140
+ const allBlockers = ratifications.filter(Boolean).flatMap(r => r.blockers ?? []);
141
+
142
+ return {
143
+ rule_id: ruleId,
144
+ scope_hint: scopeHint,
145
+ applies_to_teams: applies.map(t => t.name),
146
+ drifting_teams: drifting.map(d => d.team),
147
+ ratifications,
148
+ decision: allRatified ? "ship" : "block",
149
+ blockers: allBlockers,
150
+ summary: allRatified
151
+ ? `Rule ${ruleId} ratified by ${ratifications.length} reviewers. Applies to ${applies.length} teams; ${drifting.length} need drift remediation per the verification report.`
152
+ : `Rule ${ruleId} BLOCKED. ${allBlockers.length} blockers across ${ratifications.length} reviewers. Address blockers before re-running.`,
153
+ };
@@ -0,0 +1,83 @@
1
+ // mishmar-security-gate — security gate before merge on sensitive surface.
2
+ //
3
+ // A diff that touches sensitive surface (auth, payment, PII, secrets, RBAC)
4
+ // goes in; three orthogonal security lenses verify in parallel with 3-vote
5
+ // refute pattern. Output is pass/block + structured findings.
6
+ //
7
+ // Pattern: barrier + 3-vote adversarial verify (each finding refuted by 2 of 3).
8
+ // ADR D-010 check:
9
+ // - panel orthogonality: Ira (code-level OWASP injection),
10
+ // Joab (surface attack vectors), Hushai (advisor on threat model) —
11
+ // distinct evaluation domains.
12
+ // - synthesis: pass/block decision with finding list.
13
+
14
+ export const meta = {
15
+ name: "mishmar-security-gate",
16
+ description: "Security gate before merge on sensitive surface — 3 orthogonal lenses + adversarial refute.",
17
+ whenToUse: "Before merging changes that touch auth, payment, PII, secrets, RBAC, crypto.",
18
+ phases: [{ title: "Find" }, { title: "Refute" }, { title: "Decide" }],
19
+ };
20
+
21
+ const diffRef = args?.diff_ref;
22
+ const surface = args?.surface;
23
+ const project = args?.project ?? ".";
24
+ if (!diffRef) throw new Error("args.diff_ref is required (PR URL, branch, or diff path)");
25
+ if (!surface) throw new Error("args.surface is required (e.g. 'auth' | 'payment' | 'pii' | 'rbac')");
26
+
27
+ const FINDING_SCHEMA = {
28
+ type: "object", required: ["findings"],
29
+ properties: { findings: { type: "array", items: { type: "object", required: ["title", "severity", "rationale"], properties: { title: {type:"string"}, severity: {type:"string", enum:["info","low","medium","high","critical"]}, file: {type:"string"}, line: {type:"integer"}, rationale: {type:"string"} } } } },
30
+ };
31
+ const REFUTE_SCHEMA = {
32
+ type: "object", required: ["refuted", "rationale"],
33
+ properties: { refuted: {type:"boolean"}, rationale: {type:"string"} },
34
+ };
35
+
36
+ phase("Find");
37
+ const LENSES = [
38
+ { key: "code-owasp", agent: "ira", prompt: "OWASP Top 10 at the code level: injection, broken auth, broken access, SSRF, deserialization, secrets. Inspect the diff line-by-line." },
39
+ { key: "surface", agent: "joab", prompt: "Surface attack vectors: input validation gaps, AuthZ boundary checks, CSRF/CORS, rate limits, error leakage at the endpoint level." },
40
+ { key: "threat", agent: "hushai", prompt: "Threat model: trust boundary changes, new abuse paths, secrets handling, audit trail completeness, privilege escalation surfaces." },
41
+ ];
42
+
43
+ const findings = await parallel(LENSES.map(L => () =>
44
+ agent(
45
+ `Surface: ${surface}. Diff: ${diffRef}. Project: ${project}. Your lens: ${L.prompt} Return the schema. Default to listing findings if uncertain — we tolerate false positives in this gate.`,
46
+ { schema: FINDING_SCHEMA, label: `find:${L.key}`, agentType: L.agent, phase: "Find" },
47
+ ).then(f => ({ lens: L.key, findings: f.findings ?? [] }))
48
+ ));
49
+
50
+ const allFindings = findings.filter(Boolean).flatMap(f => f.findings.map(x => ({ ...x, lens: f.lens })));
51
+ log(`${allFindings.length} raw findings across 3 lenses.`);
52
+
53
+ phase("Refute");
54
+ const refuters = ["ira", "joab", "hushai"];
55
+ const verified = await parallel(allFindings.map(F => () =>
56
+ parallel(refuters.map(r => () =>
57
+ agent(
58
+ `Surface: ${surface}. Finding: "${F.title}" — ${F.rationale}. Try to REFUTE this finding. Default refuted=true if uncertain — we want only confirmed findings to block.`,
59
+ { schema: REFUTE_SCHEMA, label: `refute:${r}:${F.title.slice(0,20)}`, agentType: r, phase: "Refute" },
60
+ )
61
+ )).then(votes => {
62
+ const valid = votes.filter(Boolean);
63
+ const refutedCount = valid.filter(v => v.refuted).length;
64
+ return { finding: F, refuted_votes: refutedCount, confirmed: refutedCount <= 1 };
65
+ })
66
+ ));
67
+
68
+ const confirmed = verified.filter(v => v.confirmed).map(v => v.finding);
69
+ const blockingSeverities = ["high", "critical"];
70
+ const blockers = confirmed.filter(f => blockingSeverities.includes(f.severity));
71
+
72
+ phase("Decide");
73
+ return {
74
+ surface,
75
+ diff_ref: diffRef,
76
+ total_findings_raw: allFindings.length,
77
+ confirmed_findings: confirmed,
78
+ blockers,
79
+ decision: blockers.length === 0 ? "pass" : "block",
80
+ summary: blockers.length === 0
81
+ ? `Security gate PASS. ${confirmed.length} non-blocking findings (info/low/medium). Merge allowed.`
82
+ : `Security gate BLOCK. ${blockers.length} confirmed high/critical findings on ${surface}. Address before merge.`,
83
+ };