kushi-agents 5.1.0 → 5.3.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.
Files changed (40) hide show
  1. package/README.md +85 -0
  2. package/bin/cli.mjs +188 -1
  3. package/package.json +2 -2
  4. package/plugin/agents/kushi.agent.md +5 -0
  5. package/plugin/instructions/global-wiki.instructions.md +79 -0
  6. package/plugin/instructions/hooks.instructions.md +84 -0
  7. package/plugin/instructions/multi-wiki-routing.instructions.md +117 -0
  8. package/plugin/instructions/otel.instructions.md +75 -0
  9. package/plugin/instructions/parallel-execution.instructions.md +81 -0
  10. package/plugin/instructions/schema-evolve.instructions.md +73 -0
  11. package/plugin/skills/_shared/Emit-OtelSpan.ps1 +111 -0
  12. package/plugin/skills/_shared/Invoke-Hooks.ps1 +177 -0
  13. package/plugin/skills/_shared/hook-templates/console-debug.ps1 +15 -0
  14. package/plugin/skills/_shared/hook-templates/teams-notify.ps1 +47 -0
  15. package/plugin/skills/ask-project/SKILL.md +14 -0
  16. package/plugin/skills/global-wiki/.created-by-skill-creator +1 -0
  17. package/plugin/skills/global-wiki/SKILL.md +87 -0
  18. package/plugin/skills/global-wiki/evals/evals.json +43 -0
  19. package/plugin/skills/promote/.created-by-skill-creator +1 -0
  20. package/plugin/skills/promote/SKILL.md +125 -0
  21. package/plugin/skills/promote/evals/evals.json +35 -0
  22. package/plugin/skills/refresh-project/SKILL.md +8 -4
  23. package/plugin/skills/schema-evolve/.created-by-skill-creator +0 -0
  24. package/plugin/skills/schema-evolve/SKILL.md +106 -0
  25. package/plugin/skills/schema-evolve/evals/evals.json +37 -0
  26. package/plugin/skills/self-check/SKILL.md +12 -54
  27. package/plugin/skills/self-check/references/algorithm.md +55 -0
  28. package/plugin/skills/self-check/run.ps1 +155 -0
  29. package/plugin/skills/teach/.created-by-skill-creator +0 -0
  30. package/plugin/skills/teach/SKILL.md +79 -0
  31. package/plugin/skills/teach/evals/evals.json +59 -0
  32. package/src/global-wiki-cli.mjs +158 -0
  33. package/src/global-wiki.mjs +503 -0
  34. package/src/global-wiki.test.mjs +135 -0
  35. package/src/hooks-dispatcher.test.mjs +135 -0
  36. package/src/otel-emit.test.mjs +73 -0
  37. package/src/parallel-refresh.test.mjs +50 -0
  38. package/src/promote.test.mjs +161 -0
  39. package/src/schema-evolve.test.mjs +78 -0
  40. package/src/teach.test.mjs +45 -0
package/README.md CHANGED
@@ -7,6 +7,8 @@
7
7
  [![host: VS Code](https://img.shields.io/badge/host-VS%20Code-007acc)](https://gim-home.github.io/kushi/)
8
8
  [![spec: agentskills.io](https://img.shields.io/badge/spec-agentskills.io-22c55e)](https://agentskills.io/skill-creation/best-practices)
9
9
 
10
+ > **v5.2.0 — Hooks + parallel pulls + OTel + teach + schema-evolve.** Pipeline events trigger configurable hooks (`.kushi/hooks/`); pull dispatch is parallel by default (4 workers); OpenTelemetry export is opt-in via `KUSHI_OTEL_ENDPOINT`; `kushi explain <topic>` teaches concepts; `kushi remember <rule>` persists conventions.
11
+
10
12
  > **v5.1.0 — Living wiki.** Build-state is now incremental: human edits outside `<!-- kushi:auto -->` fences are preserved, contradictions are flagged with Obsidian-compatible callouts (`> [!warning]`), and a new `lint-state` skill monitors wiki health. State/ is a valid [Obsidian](https://obsidian.md) vault — callout syntax, Dataview-compatible frontmatter, and `[[wikilinks]]` all work natively.
11
13
 
12
14
  > **v5.0.1 spec-compliance pass.** Every `plugin/skills/<name>/SKILL.md` follows the [agentskills.io best practices](https://agentskills.io/skill-creation/best-practices) and [optimizing-descriptions](https://agentskills.io/skill-creation/optimizing-descriptions) guides: ≤ 500 lines & ≤ 5000 tokens, load-on-trigger references, top-5 gotchas, checklist orchestrators, validation loops, plan-validate-execute for graph + State writers, and "USE WHEN …" descriptions. Enforced by `self-check -Deep` D30.*.
@@ -93,6 +95,89 @@ To use: point Obsidian at `<project>/State/` as a vault. The `index.md` serves a
93
95
 
94
96
  ---
95
97
 
98
+ ## Hooks (v5.2.0+)
99
+
100
+ Pipeline events (`post-pull`, `post-state`, `post-contradiction`, `post-lint`) trigger configurable hooks — PowerShell scripts or webhooks. Failures are logged but never block the pipeline.
101
+
102
+ ```bash
103
+ # List configured hooks
104
+ kushi hooks list MyProject
105
+
106
+ # Test-fire a synthetic event
107
+ kushi hooks test MyProject post-pull
108
+ ```
109
+
110
+ Configure in `.kushi/hooks.yml` or drop scripts into `.kushi/hooks/<event>.ps1`. Templates at `plugin/skills/_shared/hook-templates/`.
111
+
112
+ ## Parallel refresh (v5.2.0+)
113
+
114
+ Pull dispatch is parallel by default (4 workers). Each source writes to its own isolated subtree. Results are aggregated in canonical source order for deterministic output.
115
+
116
+ ```bash
117
+ # Default: parallel (up to 4 workers)
118
+ kushi refresh MyProject
119
+
120
+ # Force sequential (old behavior)
121
+ kushi refresh MyProject --sequential
122
+ ```
123
+
124
+ Configure `parallel.max_workers` in `.kushi/config.yml`. See `parallel-execution.instructions.md`.
125
+
126
+ ## Telemetry (opt-in, v5.2.0+)
127
+
128
+ Set `KUSHI_OTEL_ENDPOINT` to export spans to any OTLP-compatible backend (Jaeger, Grafana Tempo, Azure Monitor). Zero overhead when unset. No evidence content or PII — metadata only.
129
+
130
+ ```bash
131
+ export KUSHI_OTEL_ENDPOINT=http://localhost:4318/v1/traces
132
+ kushi refresh MyProject # spans emitted for each pull + build-state + lint
133
+ ```
134
+
135
+ ## Teach mode (v5.2.0+)
136
+
137
+ Pedagogical, read-only. Explains kushi concepts by loading relevant doctrine + genealogy.
138
+
139
+ ```bash
140
+ kushi explain contradictions
141
+ kushi explain parallel
142
+ kushi explain hooks
143
+ ```
144
+
145
+ ## Schema evolve (v5.2.0+)
146
+
147
+ Teach kushi project-specific conventions that persist across runs.
148
+
149
+ ```bash
150
+ kushi remember "always use 'HCA' not 'Healthcare Accelerator' in summaries"
151
+ ```
152
+
153
+ Rules stored in `Evidence/<alias>/State/CLAUDE.md`. Read by `build-state`, `ask-project`, `refresh-project` at run start.
154
+
155
+ ---
156
+
157
+ ## Global wiki (v5.3.0+)
158
+
159
+ A per-user **global wiki** at `~/.kushi-global/State/` for cross-engagement knowledge — same Karpathy shape as a project `State/`, marked `scope: global`.
160
+
161
+ ```bash
162
+ kushi global init # scaffold ~/.kushi-global/State/
163
+ kushi global status # counts + last-modified summary
164
+ kushi global ask "what's our confidence ladder pattern?"
165
+ kushi global lint # privacy + structure pass
166
+
167
+ # Project → global is explicit-only:
168
+ kushi promote <project> <answer-page> # refuses if identifiers detected
169
+ kushi promote <project> <answer-page> --force # redacts + writes target + back-link
170
+
171
+ # Routing under ask-project:
172
+ kushi ask <project> "..." # project-first (default)
173
+ kushi ask <project> "..." --global # global-first
174
+ kushi ask <project> "..." --project-only # suppress global
175
+ ```
176
+
177
+ The global root honors `$KUSHI_GLOBAL_ROOT` (used by tests). There is no auto-promotion path — privacy is always your call. See `plugin/instructions/global-wiki.instructions.md` + `plugin/instructions/multi-wiki-routing.instructions.md`.
178
+
179
+ ---
180
+
96
181
  ## Three install profiles
97
182
 
98
183
  Kushi ships in three tiers. Pick how much you take — the default (`standard`) matches v2.x behavior end-to-end.
package/bin/cli.mjs CHANGED
@@ -17,15 +17,89 @@ if (args.length > 0 && SKILL_VERBS.has(args[0])) {
17
17
 
18
18
  // ── lint verb (v5.1.0+) ──────────────────────────────────────────────────────
19
19
  if (args.length > 0 && args[0] === 'lint') {
20
+ if (args.includes('--global')) {
21
+ const { runGlobalLint } = await import('../src/global-wiki-cli.mjs');
22
+ await runGlobalLint();
23
+ process.exit(0);
24
+ }
20
25
  const project = args[1] || '';
21
26
  if (!project) {
22
- console.error('\n Usage: kushi lint <project>\n');
27
+ console.error('\n Usage: kushi lint <project>\n kushi lint --global\n');
23
28
  process.exit(1);
24
29
  }
25
30
  await dispatchLint(project);
26
31
  process.exit(0);
27
32
  }
28
33
 
34
+ // ── global verb (v5.3.0+) ────────────────────────────────────────────────────
35
+ if (args.length > 0 && args[0] === 'global') {
36
+ const sub = args[1] || '';
37
+ const validSubs = ['init', 'status', 'ask', 'lint'];
38
+ if (!validSubs.includes(sub)) {
39
+ console.error('\n Usage: kushi global init Scaffold ~/.kushi-global/State/');
40
+ console.error(' kushi global status Show counts + freshness');
41
+ console.error(' kushi global ask <question> Ask the global wiki');
42
+ console.error(' kushi global lint Lint the global wiki\n');
43
+ process.exit(1);
44
+ }
45
+ const { runGlobalInit, runGlobalStatus, runGlobalAsk, runGlobalLint } = await import('../src/global-wiki-cli.mjs');
46
+ if (sub === 'init') await runGlobalInit();
47
+ else if (sub === 'status') await runGlobalStatus();
48
+ else if (sub === 'ask') await runGlobalAsk(args.slice(2).join(' '));
49
+ else if (sub === 'lint') await runGlobalLint();
50
+ process.exit(0);
51
+ }
52
+
53
+ // ── promote verb (v5.3.0+) ───────────────────────────────────────────────────
54
+ if (args.length > 0 && args[0] === 'promote') {
55
+ const project = args[1] || '';
56
+ const page = args[2] || '';
57
+ if (!project || !page) {
58
+ console.error('\n Usage: kushi promote <project> <page-path>\n');
59
+ console.error(' Copies a project State page into the global wiki with provenance metadata.');
60
+ console.error(' Refuses by default if customer identifiers are detected; pass --force after review.\n');
61
+ process.exit(1);
62
+ }
63
+ const force = args.includes('--force');
64
+ const { runPromote } = await import('../src/global-wiki-cli.mjs');
65
+ await runPromote(project, page, { force });
66
+ process.exit(0);
67
+ }
68
+
69
+ // ── hooks verb (v5.2.0+) ─────────────────────────────────────────────────────
70
+ if (args.length > 0 && args[0] === 'hooks') {
71
+ const sub = args[1] || '';
72
+ const project = args[2] || '';
73
+ if (!sub || !project || !['list', 'test'].includes(sub)) {
74
+ console.error('\n Usage: kushi hooks list <project>\n kushi hooks test <project> <event>\n');
75
+ process.exit(1);
76
+ }
77
+ await dispatchHooks(sub, project, args.slice(3));
78
+ process.exit(0);
79
+ }
80
+
81
+ // ── explain verb (v5.2.0+) ───────────────────────────────────────────────────
82
+ if (args.length > 0 && args[0] === 'explain') {
83
+ const topic = args.slice(1).join(' ');
84
+ if (!topic) {
85
+ console.error('\n Usage: kushi explain <topic>\n\n Available topics: contradictions, refresh, state, hooks, parallel, otel, csc, graph, workiq, schema, install, evals\n');
86
+ process.exit(1);
87
+ }
88
+ await dispatchExplain(topic);
89
+ process.exit(0);
90
+ }
91
+
92
+ // ── remember verb (v5.2.0+) ──────────────────────────────────────────────────
93
+ if (args.length > 0 && args[0] === 'remember') {
94
+ const rule = args.slice(1).join(' ');
95
+ if (!rule) {
96
+ console.error('\n Usage: kushi remember <rule>\n\n Example: kushi remember "always use HCA not Healthcare Accelerator"\n');
97
+ process.exit(1);
98
+ }
99
+ await dispatchRemember(rule);
100
+ process.exit(0);
101
+ }
102
+
29
103
  if (args.includes('--help') || args.includes('-h')) {
30
104
  console.log(`
31
105
  Usage: npx kushi-agents [options]
@@ -74,6 +148,21 @@ if (args.includes('--help') || args.includes('-h')) {
74
148
 
75
149
  Wiki maintenance (v5.1.0+):
76
150
  lint <project> Run wiki-lint checks on State/ (contradictions, stale claims, orphans).
151
+ lint --global Lint the global wiki at ~/.kushi-global/State/.
152
+
153
+ Hooks & observability (v5.2.0+):
154
+ hooks list <project> List configured hooks for a project.
155
+ hooks test <project> <event> Fire a synthetic hook event for testing.
156
+ explain <topic> Explain a kushi concept (pedagogical, read-only).
157
+ remember <rule> Persist a project convention to CLAUDE.md.
158
+
159
+ Global wiki (v5.3.0+):
160
+ global init Scaffold ~/.kushi-global/State/ (env: KUSHI_GLOBAL_ROOT)
161
+ global status Show page counts + freshness for the global wiki.
162
+ global ask <question> Search the global wiki specifically.
163
+ global lint Lint the global wiki (alias for 'lint --global').
164
+ promote <project> <page> Move a project State page into global with redaction + back-link.
165
+ Refuses if customer identifiers are detected; --force after review.
77
166
 
78
167
  After install, talk to Kushi:
79
168
  bootstrap <project> First-time setup
@@ -279,3 +368,101 @@ function pickFlag(args, flag) {
279
368
  const m = args.find((a) => a.startsWith(prefix));
280
369
  return m ? m.slice(prefix.length) : undefined;
281
370
  }
371
+
372
+ // ── v5.2.0 dispatch helpers ──────────────────────────────────────────────────
373
+
374
+ async function dispatchHooks(sub, project, extra) {
375
+ const { spawn } = await import('node:child_process');
376
+ const { resolve } = await import('node:path');
377
+ const { fileURLToPath } = await import('node:url');
378
+ const __dirname = fileURLToPath(new URL('.', import.meta.url));
379
+ const invokeHooks = resolve(__dirname, '..', 'plugin', 'skills', '_shared', 'Invoke-Hooks.ps1');
380
+
381
+ if (sub === 'list') {
382
+ // List hooks from .kushi/hooks.yml
383
+ const { existsSync, readFileSync } = await import('node:fs');
384
+ const hooksYml = resolve(process.cwd(), project, '.kushi', 'hooks.yml');
385
+ if (!existsSync(hooksYml)) {
386
+ console.log(`\n No hooks configured for '${project}'. Create ${hooksYml} to add hooks.\n`);
387
+ return;
388
+ }
389
+ console.log(`\n Hooks for '${project}' (${hooksYml}):\n`);
390
+ console.log(readFileSync(hooksYml, 'utf8'));
391
+ } else if (sub === 'test') {
392
+ const event = extra[0] || 'post-pull';
393
+ const script = `
394
+ $payload = @{ project = '${project}'; source = 'test'; success = $true; duration_ms = 0; event = '${event}' }
395
+ & '${invokeHooks.replace(/\\/g, '\\\\')}' -ProjectRoot '${resolve(process.cwd(), project).replace(/\\/g, '\\\\')}' -Event '${event}' -Payload $payload
396
+ `;
397
+ const child = spawn('pwsh', ['-NoProfile', '-Command', script], { stdio: 'inherit' });
398
+ return new Promise((res, rej) => {
399
+ child.on('close', (code) => { if (code !== 0) rej(new Error(`hooks test exited ${code}`)); else res(); });
400
+ child.on('error', rej);
401
+ });
402
+ }
403
+ }
404
+
405
+ async function dispatchExplain(topic) {
406
+ const { resolve } = await import('node:path');
407
+ const { existsSync, readFileSync } = await import('node:fs');
408
+ const { fileURLToPath } = await import('node:url');
409
+ const __dirname = fileURLToPath(new URL('.', import.meta.url));
410
+ const repoRoot = resolve(__dirname, '..');
411
+ const instructionsDir = resolve(repoRoot, 'plugin', 'instructions');
412
+
413
+ const topicMap = {
414
+ contradictions: 'living-wiki.instructions.md',
415
+ conflicts: 'living-wiki.instructions.md',
416
+ refresh: 'parallel-execution.instructions.md',
417
+ pull: 'parallel-execution.instructions.md',
418
+ state: 'living-wiki.instructions.md',
419
+ 'build-state': 'living-wiki.instructions.md',
420
+ wiki: 'living-wiki.instructions.md',
421
+ hooks: 'hooks.instructions.md',
422
+ events: 'hooks.instructions.md',
423
+ webhooks: 'hooks.instructions.md',
424
+ parallel: 'parallel-execution.instructions.md',
425
+ workers: 'parallel-execution.instructions.md',
426
+ otel: 'otel.instructions.md',
427
+ telemetry: 'otel.instructions.md',
428
+ tracing: 'otel.instructions.md',
429
+ csc: 'comprehensive-structured-capture.instructions.md',
430
+ capture: 'comprehensive-structured-capture.instructions.md',
431
+ graph: 'entity-graph.instructions.md',
432
+ entities: 'entity-graph.instructions.md',
433
+ workiq: 'workiq-only.instructions.md',
434
+ schema: 'schema-evolve.instructions.md',
435
+ conventions: 'schema-evolve.instructions.md',
436
+ remember: 'schema-evolve.instructions.md',
437
+ install: 'multi-host-install.instructions.md',
438
+ setup: 'multi-host-install.instructions.md',
439
+ evals: 'skill-evals.instructions.md',
440
+ };
441
+
442
+ const key = Object.keys(topicMap).find(k => topic.toLowerCase().includes(k));
443
+ if (!key) {
444
+ console.log(`\n Topic not found: "${topic}"\n`);
445
+ console.log(' Available topics:', Object.keys(topicMap).filter((v, i, a) => a.indexOf(v) === i).join(', '));
446
+ console.log('');
447
+ return;
448
+ }
449
+
450
+ const docFile = resolve(instructionsDir, topicMap[key]);
451
+ if (!existsSync(docFile)) {
452
+ console.error(` Doctrine file missing: ${topicMap[key]}`);
453
+ process.exit(1);
454
+ }
455
+
456
+ const content = readFileSync(docFile, 'utf8');
457
+ const lines = content.split('\n').slice(0, 40);
458
+ console.log(`\n 📖 Topic: ${key} → ${topicMap[key]}\n`);
459
+ console.log(lines.join('\n'));
460
+ console.log(`\n ... (full doctrine at: plugin/instructions/${topicMap[key]})\n`);
461
+ }
462
+
463
+ async function dispatchRemember(rule) {
464
+ console.log(`\n ✅ Rule noted: "${rule}"`);
465
+ console.log(' To persist this rule, run schema-evolve from within a project context:');
466
+ console.log(' @Kushi remember ' + rule);
467
+ console.log(' This will write to Evidence/<alias>/State/CLAUDE.md\n');
468
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kushi-agents",
3
- "version": "5.1.0",
3
+ "version": "5.3.0",
4
4
  "description": "Install Kushi — multi-source project evidence agent with Comprehensive Structured Capture (CSC) into weekly-only files across Email, Teams, OneNote, Loop, SharePoint, Meetings, CRM, ADO. Meetings retain a sibling verbatim/ audit folder. WorkIQ-only for M365 sources (Graph / m365_* FORBIDDEN as fallbacks; user-paste is first-class). Host-agnostic.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -41,7 +41,7 @@
41
41
  },
42
42
  "license": "MIT",
43
43
  "scripts": {
44
- "test": "node --test src/check-workiq.test.mjs src/seed-config.test.mjs src/sanitize-workiq-input.test.mjs src/detect-vertex-repo.test.mjs src/vertex-validate.test.mjs src/emit-vertex.e2e.test.mjs src/config-root-resolve.test.mjs src/forbidden-workiq-phrasings.test.mjs src/multi-host-install.test.mjs src/eval-aggregator.test.mjs src/eval-runner.test.mjs src/skill-creator.test.mjs src/skill-checker.test.mjs",
44
+ "test": "node --test src/check-workiq.test.mjs src/seed-config.test.mjs src/sanitize-workiq-input.test.mjs src/detect-vertex-repo.test.mjs src/vertex-validate.test.mjs src/emit-vertex.e2e.test.mjs src/config-root-resolve.test.mjs src/forbidden-workiq-phrasings.test.mjs src/multi-host-install.test.mjs src/eval-aggregator.test.mjs src/eval-runner.test.mjs src/skill-creator.test.mjs src/skill-checker.test.mjs src/hooks-dispatcher.test.mjs src/parallel-refresh.test.mjs src/otel-emit.test.mjs src/teach.test.mjs src/schema-evolve.test.mjs src/global-wiki.test.mjs src/promote.test.mjs",
45
45
  "test:integration:bootstrap": "node src/bootstrap-dryrun.integration.test.mjs",
46
46
  "smoke": "node scripts/smoke.mjs",
47
47
  "eval": "pwsh plugin/skills/eval/run-evals.ps1 -Skill",
@@ -51,6 +51,11 @@ The Evidence/ folder produced by `aggregate` is the **public contract** between
51
51
  | `@Kushi fde-triage <project>` | **standard+** | n/a (read-only) | `fde-triage` — full 7-file triage bundle at `Reports/triage/<YYYY-MM-DD>/` |
52
52
  | `@Kushi propose ado <project>` | **preview** | n/a (read-only) | `propose-ado-update` — generates `<engagement-root>/ado-updates/<YYYY-MM-DD>/proposed.md` from latest `_Consolidated/`. NO ADO writes. |
53
53
  | `@Kushi apply ado <project>` | **preview** | n/a (gated) | `apply-ado-update` — gated apply skill. v0.1.0-preview is dry-mode only (writes `planned.jsonl`, no real ADO calls). Governed by `update-ledger.instructions.md`. |
54
+ | `@Kushi teach <topic>` | standard+ | n/a (write-only) | v5.2.0 — `teach` — persist a reusable fact/preference/pattern to `.kushi/learnings/`. Lookup via `explain`. |
55
+ | `@Kushi explain <topic>` | standard+ | n/a (read-only) | v5.2.0 — `teach` (explain mode) — retrieve a previously taught fact/preference/pattern from `.kushi/learnings/`. |
56
+ | `@Kushi schema-evolve <project>` | standard+ | n/a | v5.2.0 — `schema-evolve` — detect schema drift in Evidence/ layouts and propose safe migrations with rollback plans. |
57
+ | `@Kushi global init` / `status` / `ask <q>` / `lint` | standard+ | n/a | v5.3.0 — `global-wiki` — manage the per-user cross-engagement wiki at `~/.kushi-global/State/` (env `KUSHI_GLOBAL_ROOT` for tests). |
58
+ | `@Kushi promote <project> <page>` | standard+ | n/a | v5.3.0 — `promote` — copy a project State page into the global wiki with identifier redaction + back-link + dual log. Refuses without `--force` when identifiers detected. |
54
59
 
55
60
  **Note on auto-routing**: `ask` does NOT require the `@Kushi ask` prefix. Any message that names a known project AND asks a question (what / who / when / status / summarize / etc.) auto-dispatches to `ask-project`. Producer verbs win in the unambiguous case.
56
61
 
@@ -0,0 +1,79 @@
1
+ ---
2
+ name: "global-wiki"
3
+ description: "v5.3.0 — Global wiki at ~/.kushi-global/State/ (env override KUSHI_GLOBAL_ROOT). Structurally identical to a project State/ wiki (Karpathy layout) but tagged scope: global in frontmatter. Holds cross-engagement patterns that don't belong in any one project. Never auto-populated — only explicit kushi promote moves content in. Linter checks for [!warning] potential-customer-leak callouts to enforce privacy posture."
4
+ applies_to: "global init/status/ask/lint, promote, ask-project routing, teach routing"
5
+ since: "kushi v5.3.0"
6
+ ---
7
+
8
+ # global-wiki — doctrine
9
+
10
+ > **Authored to [agentskills.io](https://agentskills.io/skill-creation/best-practices) spec.**
11
+ > Deltas from source (living-wiki Karpathy pattern + Obsidian global-vault + Claude.md global-memory): adds a separate root, `scope: global` frontmatter marker, explicit-promotion-only contract, and a privacy linter (`potential-customer-leak`).
12
+
13
+ The global wiki is a personal, cross-engagement knowledge base that lives **outside** any single project's `Evidence/<alias>/State/`. Consultants accumulate cross-cutting patterns — "how I structure FDE intake", "my preferred Status taxonomy", "tools and snippets that work across all customers" — and those patterns belong in a single durable home, not re-derived per engagement and not silently bleeding between projects.
14
+
15
+ ## Rules (HARD)
16
+
17
+ ### 1. Location
18
+
19
+ - **Default:** `~/.kushi-global/State/` (`$HOME` on POSIX, `$env:USERPROFILE` on Windows).
20
+ - **Override:** `$KUSHI_GLOBAL_ROOT` environment variable (absolute path; tilde-expanded). Tests MUST set this to `.testtmp/.kushi-global/` — never touch the real path.
21
+ - The global root is **per-user**, never per-project, never per-host.
22
+
23
+ ### 2. Shape
24
+
25
+ The global wiki MUST mirror the standard State/ layout (per `karpathy-state-layout.instructions.md` + `living-wiki.instructions.md`):
26
+
27
+ ```
28
+ ~/.kushi-global/State/
29
+ ├── index.md # entry point, links to all pages
30
+ ├── log.md # reverse-chronological op log (same format as project log.md)
31
+ ├── tour.md # guided tour pointer (optional)
32
+ ├── hot.md # hot-edits scratch (optional)
33
+ ├── conventions.md # cross-engagement conventions / CLAUDE.md-shape rules
34
+ ├── answers/ # promoted Q&A pages
35
+ ├── reports/ # global lint reports, status snapshots
36
+ └── _review-queue.md # open privacy / contradiction items
37
+ ```
38
+
39
+ Every markdown page in the global wiki MUST carry frontmatter with `scope: global`:
40
+
41
+ ```yaml
42
+ ---
43
+ kushi_state_page: true
44
+ scope: global
45
+ ---
46
+ ```
47
+
48
+ The `scope: global` marker distinguishes a true global page from a project page that was *copied* locally for reference.
49
+
50
+ ### 3. Initialization
51
+
52
+ - `kushi global init` creates the scaffold idempotently — re-running NEVER overwrites existing content.
53
+ - Skips files that already exist; only adds missing scaffolding.
54
+ - Logs `global-init` entry to `~/.kushi-global/State/log.md` on every run (even no-op).
55
+
56
+ ### 4. Privacy posture
57
+
58
+ The global wiki crosses engagements. **Customer identifiers MUST NOT leak in.**
59
+
60
+ - The promote operation runs an identifier scan and refuses unless explicit `--force` is given.
61
+ - `kushi global lint` (and `kushi lint --global`) scans `*.md` under the global root for `> [!warning] potential-customer-leak` callouts (left there by promote when it had to redact).
62
+ - The user is responsible for resolving those callouts before sharing the global wiki.
63
+
64
+ ### 5. Promotion is explicit only
65
+
66
+ - Pages NEVER auto-promote from a project to global. The only path is `kushi promote <project> <page-path>`.
67
+ - See `multi-wiki-routing.instructions.md` for the promotion contract + back-link rule.
68
+
69
+ ### 6. Storage outside engagement tree
70
+
71
+ The global wiki lives **outside** any engagement root. Self-check `D40.global-wiki-shape` checks the configured location (env-overridden in CI) and is warn-only — the absence of `~/.kushi-global/` is normal until the user runs `kushi global init`.
72
+
73
+ ## References
74
+
75
+ - `multi-wiki-routing.instructions.md` — routing across project + global
76
+ - `living-wiki.instructions.md` — incremental + contradiction lifecycle (same rules apply)
77
+ - `karpathy-state-layout.instructions.md` — page shape
78
+ - `schema-evolve.instructions.md` — `global` scope was placeholdered here in v5.2.0; v5.3.0 honors it.
79
+ - `wiki-lint.instructions.md` — finding classes; v5.3.0 adds `potential-customer-leak`.
@@ -0,0 +1,84 @@
1
+ # Hooks system
2
+
3
+ > Doctrine: `hooks.instructions.md` — kushi v5.2.0+
4
+
5
+ ## Purpose
6
+
7
+ Hooks are user-configurable scripts or webhooks triggered **after** kushi pipeline events. They enable integrations (Teams notifications, ticket updates, dashboards) without modifying kushi's core skills.
8
+
9
+ ## Events
10
+
11
+ | Event | Fires after | Payload keys |
12
+ |-------|-------------|--------------|
13
+ | `post-pull` | Each `pull-*` completes (per source) | `project`, `alias`, `source`, `items_pulled`, `duration_ms`, `success` |
14
+ | `post-state` | `build-state` completes | `project`, `alias`, `pages_written`, `contradictions_flagged`, `duration_ms` |
15
+ | `post-contradiction` | A new contradiction is flagged inside `build-state` | `project`, `alias`, `entity`, `field`, `old_value`, `new_value`, `source` |
16
+ | `post-lint` | `lint-state` completes | `project`, `alias`, `findings_count`, `report_path`, `duration_ms` |
17
+
18
+ ## Configuration
19
+
20
+ Hooks are declared per-project at:
21
+ - `.kushi/hooks.yml` — webhook URLs + settings
22
+ - `.kushi/hooks/<event>.ps1` — PowerShell hook scripts
23
+
24
+ ### `.kushi/hooks.yml` schema
25
+
26
+ ```yaml
27
+ hooks:
28
+ post-pull:
29
+ - type: webhook
30
+ url: https://example.webhook.office.com/...
31
+ timeout_ms: 5000
32
+ - type: script
33
+ path: .kushi/hooks/post-pull.ps1
34
+ post-state:
35
+ - type: webhook
36
+ url: https://...
37
+ post-contradiction:
38
+ - type: script
39
+ path: .kushi/hooks/post-contradiction.ps1
40
+ post-lint: []
41
+ ```
42
+
43
+ ### PowerShell hooks
44
+
45
+ - Receive the event payload as JSON on **stdin**.
46
+ - Exit 0 = success; non-zero = logged warning (never blocks pipeline).
47
+ - Timeout: 30s default (configurable per hook via `timeout_ms`).
48
+ - Working directory: project root.
49
+
50
+ ### Webhook hooks
51
+
52
+ - Payload sent as HTTP POST body (`Content-Type: application/json`).
53
+ - Timeout: 5000ms default.
54
+ - Non-2xx response = logged warning (never blocks).
55
+
56
+ ## Execution semantics
57
+
58
+ 1. **Warn-only**: Hook failures are logged to `Evidence/<alias>/State/hooks-log.md` but NEVER block the pipeline.
59
+ 2. **Order**: Hooks for the same event fire sequentially in declaration order.
60
+ 3. **Idempotent dispatch**: `Invoke-Hooks` is safe to call multiple times — the log deduplicates by event+timestamp.
61
+ 4. **No content**: Hooks receive metadata only. Evidence content is NEVER included in payloads (privacy).
62
+
63
+ ## CLI verbs
64
+
65
+ - `kushi hooks list <project>` — list configured hooks for a project.
66
+ - `kushi hooks test <project> <event>` — fire a synthetic event payload.
67
+
68
+ ## Output
69
+
70
+ Hook invocations are logged to `Evidence/<alias>/State/hooks-log.md` with format:
71
+
72
+ ```markdown
73
+ ## [2026-05-29 14:30] post-pull | hook: teams-notify
74
+
75
+ - status: success
76
+ - duration_ms: 342
77
+ - target: https://example.webhook.office.com/...
78
+ ```
79
+
80
+ ## References
81
+
82
+ - `Invoke-Hooks.ps1` — shared helper (`plugin/skills/_shared/Invoke-Hooks.ps1`)
83
+ - `log-format.instructions.md` — log entry format
84
+ - `living-wiki.instructions.md` — contradiction events trigger `post-contradiction`
@@ -0,0 +1,117 @@
1
+ ---
2
+ name: "multi-wiki-routing"
3
+ description: "v5.3.0 — Routing rules for project vs global wiki. ask-project: project-first; augment with global only if project confidence below threshold; --global forces global-first, --project-only suppresses global. build-state: project-only, never auto-promotes to global. lint-state: project-only; kushi lint --global lints global separately. teach: global-first for cross-cutting topics, project for project-specific. Promotion path: explicit kushi promote <project> <page>, never automatic."
4
+ applies_to: "ask-project, build-state, lint-state, teach, promote"
5
+ since: "kushi v5.3.0"
6
+ ---
7
+
8
+ # multi-wiki-routing — doctrine
9
+
10
+ > **Authored to [agentskills.io](https://agentskills.io/skill-creation/best-practices) spec.**
11
+ > Deltas from source: kushi previously had a single wiki per project. This doctrine formalizes the project↔global routing rules introduced in v5.3.0.
12
+
13
+ Kushi maintains two kinds of wikis:
14
+
15
+ | Kind | Path | Scope |
16
+ |---|---|---|
17
+ | **Project** | `Evidence/<alias>/State/` | One engagement only. |
18
+ | **Global** | `$KUSHI_GLOBAL_ROOT` or `~/.kushi-global/State/` | Cross-engagement; per-user. |
19
+
20
+ Every reader/writer skill MUST be explicit about which it touches.
21
+
22
+ ## Rules (HARD)
23
+
24
+ ### 1. ask-project — project-first, global-augment
25
+
26
+ Default behavior (`kushi ask <project> <q>`):
27
+
28
+ 1. Resolve the project and run the standard project-scoped answer chain (per `ask-project/SKILL.md`).
29
+ 2. Compute the confidence (per `evidence-confidence-ladder.instructions.md`).
30
+ 3. **If confidence is `low` (or `medium` with `--global-augment` set in config)** → also query `$KUSHI_GLOBAL_ROOT/State/answers/` for matching topics and append a `## From your global wiki` section, citing each hit with `[global: <page>]`.
31
+ 4. **If confidence is `high`** → do NOT consult global (avoid noise).
32
+
33
+ Flags:
34
+
35
+ | Flag | Behavior |
36
+ |---|---|
37
+ | `--global` | Reverse the order: search global first, project second. Show provenance `[global]` first, `[project: <alias>]` second. |
38
+ | `--project-only` | Hard-disable global consultation regardless of confidence. |
39
+ | (none) | Default project-first + augment-on-low-confidence. |
40
+
41
+ Source provenance MUST be visible per citation: `[project: <alias>]` vs `[global]`.
42
+
43
+ ### 2. build-state — project-only
44
+
45
+ `build-state` writes to `Evidence/<alias>/State/` and **NEVER** writes to the global wiki. There is no auto-promotion path. Even if a fact has appeared in 10 projects, build-state does not move it to global — only the user, via `kushi promote`, does.
46
+
47
+ ### 3. lint-state — project-only by default
48
+
49
+ - `kushi lint <project>` → lints the project State only. Unchanged from v5.1.0.
50
+ - `kushi lint --global` → lints the global wiki at `$KUSHI_GLOBAL_ROOT/State/` separately. Same finding classes plus `potential-customer-leak` (see `global-wiki.instructions.md`).
51
+ - Lint runs are independent; one does not implicitly run the other.
52
+
53
+ ### 4. teach — global-first for cross-cutting topics
54
+
55
+ - For **cross-cutting topics** (kushi concepts, doctrines, releases — e.g. "explain confidence ladder", "how does CSC work"): `teach` consults the global wiki FIRST (if a matching page exists in `~/.kushi-global/State/answers/`), then falls back to in-repo doctrine, then to genealogy.
56
+ - For **project-specific topics** ("how did we structure intake for AGCO?"): `teach` declines and suggests `kushi ask <project> <q>` instead — that's an `ask-project` job.
57
+ - Citations distinguish `[global: <page>]` vs `[doctrine: <file>]` vs `[genealogy: <version>]`.
58
+
59
+ ### 5. Promotion path — explicit only
60
+
61
+ `kushi promote <project> <page>` is the **only** way content moves project → global. See `Promote operation` below.
62
+
63
+ Auto-promotion is intentionally not supported because:
64
+
65
+ 1. Customer-identifier detection is best-effort; the user must review.
66
+ 2. What is "cross-cutting" depends on the consultant, not the machine.
67
+ 3. Silent promotion would surprise the user — global is personal space.
68
+
69
+ ### 6. Demotion / removal
70
+
71
+ There is no `kushi demote` in v5.3.0. To remove a global page the user deletes the file directly and appends a manual `log.md` entry. v6+ MAY add a managed demote verb.
72
+
73
+ ## Promote operation
74
+
75
+ `kushi promote <project> <page-path>` performs the following, in order:
76
+
77
+ 1. **Resolve source.** Find `<engagement-root>/<project>/Evidence/<alias>/State/<page-path>`. Must exist.
78
+ 2. **Read source body + frontmatter.**
79
+ 3. **Run identifier scan** against the body. Uses the same heuristics as `schema-evolve`-style detection:
80
+ - Known customer aliases registered in `.settings.yml` `discovery.project_aliases:` or the alias folder name itself.
81
+ - The literal project name.
82
+ - Email addresses with non-Microsoft domains.
83
+ - High-confidence proper-noun runs that match `<project>` aliases (case-insensitive, word boundaries).
84
+ 4. **Build redaction list.** For each hit:
85
+ - Replace the literal string with `[REDACTED]` in the global copy.
86
+ - Record `{ pattern, count, line_numbers }` in the redactions list.
87
+ 5. **Refuse without `--force`** if the redactions list is non-empty. Print the list with line numbers and the human-review prompt. The user is expected to inspect, then re-run with `--force` (which writes the redacted body and adds a `> [!warning] potential-customer-leak` callout near each redaction site for the lint pass to track).
88
+ 6. **Write target.** Path: `$KUSHI_GLOBAL_ROOT/State/answers/<source-slug>.md` (slugify source filename; collisions append `-N`).
89
+ Frontmatter MUST contain:
90
+ ```yaml
91
+ ---
92
+ kushi_state_page: true
93
+ scope: global
94
+ promoted_from: "<project>/<alias>/<relative-source-path>"
95
+ promoted_at: "<ISO-8601 UTC>"
96
+ redactions: ["<pattern1>", "<pattern2>"]
97
+ ---
98
+ ```
99
+ 7. **Add back-link to source.** Append (or update) a callout in the source page:
100
+ ```markdown
101
+ > [!info] Promoted to global wiki
102
+ > <source-slug>.md @ <ISO-8601 UTC> · redactions: <N>
103
+ ```
104
+ 8. **Dual log.** Append a `promote` entry to BOTH:
105
+ - `<project>/Evidence/<alias>/State/log.md` (op = `promote`, title = `Promoted <slug> to global`).
106
+ - `$KUSHI_GLOBAL_ROOT/State/log.md` (op = `promote-in`, title = `Imported <slug> from <project>`).
107
+ 9. **Print summary** with both file paths.
108
+
109
+ The operation is **atomic**: if any step fails (redaction refused, write denied, back-link fails), no writes are persisted. The implementation uses a stage-then-commit pattern.
110
+
111
+ ## References
112
+
113
+ - `global-wiki.instructions.md` — global wiki location + privacy rules
114
+ - `evidence-confidence-ladder.instructions.md` — confidence threshold for routing
115
+ - `living-wiki.instructions.md` — same incremental + contradiction rules apply to both wikis
116
+ - `wiki-lint.instructions.md` — finding classes; v5.3.0 adds `potential-customer-leak`
117
+ - `schema-evolve.instructions.md` — identifier-detection heuristics reused here