kushi-agents 5.1.0 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -0
- package/bin/cli.mjs +138 -0
- package/package.json +2 -2
- package/plugin/agents/kushi.agent.md +3 -0
- package/plugin/instructions/hooks.instructions.md +84 -0
- package/plugin/instructions/otel.instructions.md +75 -0
- package/plugin/instructions/parallel-execution.instructions.md +81 -0
- package/plugin/instructions/schema-evolve.instructions.md +73 -0
- package/plugin/skills/_shared/Emit-OtelSpan.ps1 +111 -0
- package/plugin/skills/_shared/Invoke-Hooks.ps1 +177 -0
- package/plugin/skills/_shared/hook-templates/console-debug.ps1 +15 -0
- package/plugin/skills/_shared/hook-templates/teams-notify.ps1 +47 -0
- package/plugin/skills/refresh-project/SKILL.md +8 -4
- package/plugin/skills/schema-evolve/.created-by-skill-creator +0 -0
- package/plugin/skills/schema-evolve/SKILL.md +106 -0
- package/plugin/skills/schema-evolve/evals/evals.json +37 -0
- package/plugin/skills/self-check/SKILL.md +9 -54
- package/plugin/skills/self-check/references/algorithm.md +55 -0
- package/plugin/skills/self-check/run.ps1 +92 -0
- package/plugin/skills/teach/.created-by-skill-creator +0 -0
- package/plugin/skills/teach/SKILL.md +77 -0
- package/plugin/skills/teach/evals/evals.json +37 -0
- package/src/hooks-dispatcher.test.mjs +135 -0
- package/src/otel-emit.test.mjs +73 -0
- package/src/parallel-refresh.test.mjs +50 -0
- package/src/schema-evolve.test.mjs +78 -0
- package/src/teach.test.mjs +45 -0
package/README.md
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
[](https://gim-home.github.io/kushi/)
|
|
8
8
|
[](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,65 @@ 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
|
+
|
|
96
157
|
## Three install profiles
|
|
97
158
|
|
|
98
159
|
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
|
@@ -26,6 +26,40 @@ if (args.length > 0 && args[0] === 'lint') {
|
|
|
26
26
|
process.exit(0);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
// ── hooks verb (v5.2.0+) ─────────────────────────────────────────────────────
|
|
30
|
+
if (args.length > 0 && args[0] === 'hooks') {
|
|
31
|
+
const sub = args[1] || '';
|
|
32
|
+
const project = args[2] || '';
|
|
33
|
+
if (!sub || !project || !['list', 'test'].includes(sub)) {
|
|
34
|
+
console.error('\n Usage: kushi hooks list <project>\n kushi hooks test <project> <event>\n');
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
await dispatchHooks(sub, project, args.slice(3));
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── explain verb (v5.2.0+) ───────────────────────────────────────────────────
|
|
42
|
+
if (args.length > 0 && args[0] === 'explain') {
|
|
43
|
+
const topic = args.slice(1).join(' ');
|
|
44
|
+
if (!topic) {
|
|
45
|
+
console.error('\n Usage: kushi explain <topic>\n\n Available topics: contradictions, refresh, state, hooks, parallel, otel, csc, graph, workiq, schema, install, evals\n');
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
await dispatchExplain(topic);
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ── remember verb (v5.2.0+) ──────────────────────────────────────────────────
|
|
53
|
+
if (args.length > 0 && args[0] === 'remember') {
|
|
54
|
+
const rule = args.slice(1).join(' ');
|
|
55
|
+
if (!rule) {
|
|
56
|
+
console.error('\n Usage: kushi remember <rule>\n\n Example: kushi remember "always use HCA not Healthcare Accelerator"\n');
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
await dispatchRemember(rule);
|
|
60
|
+
process.exit(0);
|
|
61
|
+
}
|
|
62
|
+
|
|
29
63
|
if (args.includes('--help') || args.includes('-h')) {
|
|
30
64
|
console.log(`
|
|
31
65
|
Usage: npx kushi-agents [options]
|
|
@@ -75,6 +109,12 @@ if (args.includes('--help') || args.includes('-h')) {
|
|
|
75
109
|
Wiki maintenance (v5.1.0+):
|
|
76
110
|
lint <project> Run wiki-lint checks on State/ (contradictions, stale claims, orphans).
|
|
77
111
|
|
|
112
|
+
Hooks & observability (v5.2.0+):
|
|
113
|
+
hooks list <project> List configured hooks for a project.
|
|
114
|
+
hooks test <project> <event> Fire a synthetic hook event for testing.
|
|
115
|
+
explain <topic> Explain a kushi concept (pedagogical, read-only).
|
|
116
|
+
remember <rule> Persist a project convention to CLAUDE.md.
|
|
117
|
+
|
|
78
118
|
After install, talk to Kushi:
|
|
79
119
|
bootstrap <project> First-time setup
|
|
80
120
|
refresh <project> Incremental refresh + rebuild State/
|
|
@@ -279,3 +319,101 @@ function pickFlag(args, flag) {
|
|
|
279
319
|
const m = args.find((a) => a.startsWith(prefix));
|
|
280
320
|
return m ? m.slice(prefix.length) : undefined;
|
|
281
321
|
}
|
|
322
|
+
|
|
323
|
+
// ── v5.2.0 dispatch helpers ──────────────────────────────────────────────────
|
|
324
|
+
|
|
325
|
+
async function dispatchHooks(sub, project, extra) {
|
|
326
|
+
const { spawn } = await import('node:child_process');
|
|
327
|
+
const { resolve } = await import('node:path');
|
|
328
|
+
const { fileURLToPath } = await import('node:url');
|
|
329
|
+
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
330
|
+
const invokeHooks = resolve(__dirname, '..', 'plugin', 'skills', '_shared', 'Invoke-Hooks.ps1');
|
|
331
|
+
|
|
332
|
+
if (sub === 'list') {
|
|
333
|
+
// List hooks from .kushi/hooks.yml
|
|
334
|
+
const { existsSync, readFileSync } = await import('node:fs');
|
|
335
|
+
const hooksYml = resolve(process.cwd(), project, '.kushi', 'hooks.yml');
|
|
336
|
+
if (!existsSync(hooksYml)) {
|
|
337
|
+
console.log(`\n No hooks configured for '${project}'. Create ${hooksYml} to add hooks.\n`);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
console.log(`\n Hooks for '${project}' (${hooksYml}):\n`);
|
|
341
|
+
console.log(readFileSync(hooksYml, 'utf8'));
|
|
342
|
+
} else if (sub === 'test') {
|
|
343
|
+
const event = extra[0] || 'post-pull';
|
|
344
|
+
const script = `
|
|
345
|
+
$payload = @{ project = '${project}'; source = 'test'; success = $true; duration_ms = 0; event = '${event}' }
|
|
346
|
+
& '${invokeHooks.replace(/\\/g, '\\\\')}' -ProjectRoot '${resolve(process.cwd(), project).replace(/\\/g, '\\\\')}' -Event '${event}' -Payload $payload
|
|
347
|
+
`;
|
|
348
|
+
const child = spawn('pwsh', ['-NoProfile', '-Command', script], { stdio: 'inherit' });
|
|
349
|
+
return new Promise((res, rej) => {
|
|
350
|
+
child.on('close', (code) => { if (code !== 0) rej(new Error(`hooks test exited ${code}`)); else res(); });
|
|
351
|
+
child.on('error', rej);
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async function dispatchExplain(topic) {
|
|
357
|
+
const { resolve } = await import('node:path');
|
|
358
|
+
const { existsSync, readFileSync } = await import('node:fs');
|
|
359
|
+
const { fileURLToPath } = await import('node:url');
|
|
360
|
+
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
361
|
+
const repoRoot = resolve(__dirname, '..');
|
|
362
|
+
const instructionsDir = resolve(repoRoot, 'plugin', 'instructions');
|
|
363
|
+
|
|
364
|
+
const topicMap = {
|
|
365
|
+
contradictions: 'living-wiki.instructions.md',
|
|
366
|
+
conflicts: 'living-wiki.instructions.md',
|
|
367
|
+
refresh: 'parallel-execution.instructions.md',
|
|
368
|
+
pull: 'parallel-execution.instructions.md',
|
|
369
|
+
state: 'living-wiki.instructions.md',
|
|
370
|
+
'build-state': 'living-wiki.instructions.md',
|
|
371
|
+
wiki: 'living-wiki.instructions.md',
|
|
372
|
+
hooks: 'hooks.instructions.md',
|
|
373
|
+
events: 'hooks.instructions.md',
|
|
374
|
+
webhooks: 'hooks.instructions.md',
|
|
375
|
+
parallel: 'parallel-execution.instructions.md',
|
|
376
|
+
workers: 'parallel-execution.instructions.md',
|
|
377
|
+
otel: 'otel.instructions.md',
|
|
378
|
+
telemetry: 'otel.instructions.md',
|
|
379
|
+
tracing: 'otel.instructions.md',
|
|
380
|
+
csc: 'comprehensive-structured-capture.instructions.md',
|
|
381
|
+
capture: 'comprehensive-structured-capture.instructions.md',
|
|
382
|
+
graph: 'entity-graph.instructions.md',
|
|
383
|
+
entities: 'entity-graph.instructions.md',
|
|
384
|
+
workiq: 'workiq-only.instructions.md',
|
|
385
|
+
schema: 'schema-evolve.instructions.md',
|
|
386
|
+
conventions: 'schema-evolve.instructions.md',
|
|
387
|
+
remember: 'schema-evolve.instructions.md',
|
|
388
|
+
install: 'multi-host-install.instructions.md',
|
|
389
|
+
setup: 'multi-host-install.instructions.md',
|
|
390
|
+
evals: 'skill-evals.instructions.md',
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
const key = Object.keys(topicMap).find(k => topic.toLowerCase().includes(k));
|
|
394
|
+
if (!key) {
|
|
395
|
+
console.log(`\n Topic not found: "${topic}"\n`);
|
|
396
|
+
console.log(' Available topics:', Object.keys(topicMap).filter((v, i, a) => a.indexOf(v) === i).join(', '));
|
|
397
|
+
console.log('');
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const docFile = resolve(instructionsDir, topicMap[key]);
|
|
402
|
+
if (!existsSync(docFile)) {
|
|
403
|
+
console.error(` Doctrine file missing: ${topicMap[key]}`);
|
|
404
|
+
process.exit(1);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const content = readFileSync(docFile, 'utf8');
|
|
408
|
+
const lines = content.split('\n').slice(0, 40);
|
|
409
|
+
console.log(`\n 📖 Topic: ${key} → ${topicMap[key]}\n`);
|
|
410
|
+
console.log(lines.join('\n'));
|
|
411
|
+
console.log(`\n ... (full doctrine at: plugin/instructions/${topicMap[key]})\n`);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async function dispatchRemember(rule) {
|
|
415
|
+
console.log(`\n ✅ Rule noted: "${rule}"`);
|
|
416
|
+
console.log(' To persist this rule, run schema-evolve from within a project context:');
|
|
417
|
+
console.log(' @Kushi remember ' + rule);
|
|
418
|
+
console.log(' This will write to Evidence/<alias>/State/CLAUDE.md\n');
|
|
419
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kushi-agents",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.2.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",
|
|
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,9 @@ 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. |
|
|
54
57
|
|
|
55
58
|
**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
59
|
|
|
@@ -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,75 @@
|
|
|
1
|
+
# OpenTelemetry export
|
|
2
|
+
|
|
3
|
+
> Doctrine: `otel.instructions.md` — kushi v5.2.0+
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
Opt-in observability for kushi pipelines via OpenTelemetry Protocol (OTLP). Enables enterprise teams to trace kushi runs in their existing observability stack (Jaeger, Grafana Tempo, Azure Monitor, Datadog, etc.).
|
|
8
|
+
|
|
9
|
+
## Opt-in activation
|
|
10
|
+
|
|
11
|
+
Set the environment variable:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
KUSHI_OTEL_ENDPOINT=http://localhost:4318/v1/traces
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
When unset or empty, all OTel helpers are **no-ops** with zero overhead (no HTTP calls, no object allocation beyond the guard check).
|
|
18
|
+
|
|
19
|
+
## Events emitted
|
|
20
|
+
|
|
21
|
+
| Span name | Fires during | Attributes |
|
|
22
|
+
|-----------|-------------|------------|
|
|
23
|
+
| `kushi.pull` | Each `pull-*` source | `project`, `alias`, `source`, `duration_ms`, `success`, `evidence_count` |
|
|
24
|
+
| `kushi.build_state` | `build-state` | `project`, `alias`, `pages_written`, `contradictions_flagged`, `duration_ms`, `success` |
|
|
25
|
+
| `kushi.lint` | `lint-state` | `project`, `alias`, `findings_count`, `duration_ms`, `success` |
|
|
26
|
+
| `kushi.contradiction.flagged` | Inside `build-state` when a new contradiction is detected | `project`, `alias`, `entity`, `field`, `source` |
|
|
27
|
+
| `kushi.hook.invoked` | Each hook invocation | `project`, `alias`, `event`, `hook_type`, `target`, `duration_ms`, `success` |
|
|
28
|
+
|
|
29
|
+
## Wire format
|
|
30
|
+
|
|
31
|
+
Uses the [OTLP/HTTP JSON](https://opentelemetry.io/docs/specs/otlp/#otlphttp) format:
|
|
32
|
+
- Endpoint: `$KUSHI_OTEL_ENDPOINT` (must include path, e.g. `/v1/traces`)
|
|
33
|
+
- Method: POST
|
|
34
|
+
- Content-Type: `application/json`
|
|
35
|
+
- No external dependencies — pure PowerShell via `Invoke-RestMethod`
|
|
36
|
+
|
|
37
|
+
## Privacy contract
|
|
38
|
+
|
|
39
|
+
**CRITICAL**: OTel spans carry ONLY:
|
|
40
|
+
- Operation metadata (names, counts, durations, success/failure)
|
|
41
|
+
- Project/alias identifiers
|
|
42
|
+
|
|
43
|
+
OTel spans NEVER carry:
|
|
44
|
+
- Evidence content (email bodies, meeting transcripts, etc.)
|
|
45
|
+
- PII (user emails, file paths with usernames beyond alias)
|
|
46
|
+
- Authentication tokens or secrets
|
|
47
|
+
|
|
48
|
+
This is enforced structurally: `Emit-OtelSpan.ps1` accepts only a fixed set of attribute keys.
|
|
49
|
+
|
|
50
|
+
## Implementation
|
|
51
|
+
|
|
52
|
+
The shared helper `plugin/skills/_shared/Emit-OtelSpan.ps1`:
|
|
53
|
+
1. Checks `$env:KUSHI_OTEL_ENDPOINT` — returns immediately if unset/empty.
|
|
54
|
+
2. Constructs a minimal OTLP JSON payload with a single span.
|
|
55
|
+
3. POSTs via `Invoke-RestMethod` with a 5s timeout.
|
|
56
|
+
4. On failure: logs a warning to stderr, never throws, never blocks the pipeline.
|
|
57
|
+
|
|
58
|
+
## Service identity
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"resource": {
|
|
63
|
+
"attributes": [
|
|
64
|
+
{ "key": "service.name", "value": { "stringValue": "kushi" } },
|
|
65
|
+
{ "key": "service.version", "value": { "stringValue": "<package.json version>" } }
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## References
|
|
72
|
+
|
|
73
|
+
- `Emit-OtelSpan.ps1` — shared helper (`plugin/skills/_shared/Emit-OtelSpan.ps1`)
|
|
74
|
+
- `hooks.instructions.md` — hook invocations emit `kushi.hook.invoked` spans
|
|
75
|
+
- `parallel-execution.instructions.md` — parallel pulls emit one span per worker
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Parallel execution
|
|
2
|
+
|
|
3
|
+
> Doctrine: `parallel-execution.instructions.md` — kushi v5.2.0+
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
Defines when and how kushi parallelizes work. The primary use case is parallel `pull-*` dispatch during `refresh-project`, but the patterns apply to any future parallel workload.
|
|
8
|
+
|
|
9
|
+
## When to parallelize
|
|
10
|
+
|
|
11
|
+
| Scenario | Parallel? | Why |
|
|
12
|
+
|----------|-----------|-----|
|
|
13
|
+
| Multiple `pull-*` in a refresh | ✅ Yes | Sources are independent; each writes to its own `Evidence/<alias>/<source>/` subtree |
|
|
14
|
+
| `build-state` page writes | ❌ No | Pages may reference each other; deterministic ordering required |
|
|
15
|
+
| `lint-state` finding checks | ✅ Yes (future) | Read-only checks are embarrassingly parallel |
|
|
16
|
+
| Hook dispatch | ❌ No | Sequential for predictable log ordering |
|
|
17
|
+
|
|
18
|
+
## Configuration
|
|
19
|
+
|
|
20
|
+
In `.kushi/config.yml` (project-level) or `~/.kushi/config.yml` (user-level):
|
|
21
|
+
|
|
22
|
+
```yaml
|
|
23
|
+
parallel:
|
|
24
|
+
max_workers: 4 # default; 1 = sequential
|
|
25
|
+
throttle_ms: 200 # minimum delay between worker starts (rate-limit courtesy)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Override at runtime: `kushi refresh <project> --sequential` forces `max_workers: 1`.
|
|
29
|
+
|
|
30
|
+
## Throttling considerations
|
|
31
|
+
|
|
32
|
+
M365 Graph (via WorkIQ) enforces per-user rate limits:
|
|
33
|
+
- **429 Too Many Requests** — back off exponentially.
|
|
34
|
+
- Kushi's `throttle_ms` provides a minimum inter-start delay (not a per-request throttle — individual pull-* skills handle their own retries via `auth-and-retry.instructions.md`).
|
|
35
|
+
- Default 200ms is conservative; increase for tenants with aggressive throttling.
|
|
36
|
+
|
|
37
|
+
## Error aggregation
|
|
38
|
+
|
|
39
|
+
- Each worker runs independently. A failure in `pull-email` does NOT halt `pull-teams`.
|
|
40
|
+
- Worker results are collected into a `Map<source, {status, items, duration, error?}>`.
|
|
41
|
+
- After all workers complete, results are sorted in **canonical source order** (same order as sequential dispatch per `refresh-project/SKILL.md` Step 2).
|
|
42
|
+
- The run summary and run-log entries are written in canonical order regardless of completion order.
|
|
43
|
+
|
|
44
|
+
## Deterministic output ordering
|
|
45
|
+
|
|
46
|
+
Despite parallel execution, all observable outputs (run-log entries, refresh report tables, State/log.md entries) are written in **canonical source order**:
|
|
47
|
+
|
|
48
|
+
1. pull-email
|
|
49
|
+
2. pull-teams
|
|
50
|
+
3. pull-meetings
|
|
51
|
+
4. pull-onenote
|
|
52
|
+
5. pull-loop
|
|
53
|
+
6. pull-sharepoint
|
|
54
|
+
7. pull-crm
|
|
55
|
+
8. pull-ado
|
|
56
|
+
9. pull-misc
|
|
57
|
+
|
|
58
|
+
This ensures `git diff` is stable across runs and self-check D38 passes.
|
|
59
|
+
|
|
60
|
+
## Implementation pattern (PowerShell)
|
|
61
|
+
|
|
62
|
+
```powershell
|
|
63
|
+
# Worker pool using Start-ThreadJob (PowerShell 7+)
|
|
64
|
+
$maxWorkers = $config.parallel.max_workers ?? 4
|
|
65
|
+
$throttleMs = $config.parallel.throttle_ms ?? 200
|
|
66
|
+
$jobs = @()
|
|
67
|
+
foreach ($source in $enabledSources) {
|
|
68
|
+
while (($jobs | Where-Object { $_.State -eq 'Running' }).Count -ge $maxWorkers) {
|
|
69
|
+
Start-Sleep -Milliseconds 100
|
|
70
|
+
}
|
|
71
|
+
$jobs += Start-ThreadJob -ScriptBlock { param($s) & pull-$s ... } -ArgumentList $source
|
|
72
|
+
Start-Sleep -Milliseconds $throttleMs
|
|
73
|
+
}
|
|
74
|
+
$results = $jobs | Receive-Job -Wait -AutoRemoveJob
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## References
|
|
78
|
+
|
|
79
|
+
- `refresh-project/SKILL.md` — orchestrator that dispatches parallel pulls
|
|
80
|
+
- `auth-and-retry.instructions.md` — per-request throttling within each worker
|
|
81
|
+
- `hooks.instructions.md` — hooks fire AFTER parallel aggregation completes
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Schema evolve
|
|
2
|
+
|
|
3
|
+
> Doctrine: `schema-evolve.instructions.md` — kushi v5.2.0+
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
Allows users to teach kushi project-specific conventions that persist across runs. When a user says "from now on always do X for this project," kushi captures the rule and applies it in all future operations.
|
|
8
|
+
|
|
9
|
+
## Storage location
|
|
10
|
+
|
|
11
|
+
Rules are stored in `Evidence/<alias>/State/CLAUDE.md` under the project's evidence tree.
|
|
12
|
+
|
|
13
|
+
**Decision rationale**: `CLAUDE.md` is the Karpathy-pattern file for agent-specific conventions (already present in the State layout since v5.0.0). Placing rules here means:
|
|
14
|
+
- They're versioned alongside evidence (git-tracked engagement roots get history).
|
|
15
|
+
- They're co-located with the State pages that apply them.
|
|
16
|
+
- They're readable by any agent (not kushi-specific format).
|
|
17
|
+
|
|
18
|
+
## Rule format
|
|
19
|
+
|
|
20
|
+
Rules are appended to `CLAUDE.md` with structure:
|
|
21
|
+
|
|
22
|
+
```markdown
|
|
23
|
+
## Rule: <short-title>
|
|
24
|
+
|
|
25
|
+
- **Added**: 2026-05-29T14:30:00Z
|
|
26
|
+
- **Scope**: project | alias | global
|
|
27
|
+
- **Source**: user (natural language) | schema-evolve skill
|
|
28
|
+
|
|
29
|
+
<rule text as stated by the user>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Scope levels
|
|
33
|
+
|
|
34
|
+
| Scope | Stored at | Applies to |
|
|
35
|
+
|-------|-----------|------------|
|
|
36
|
+
| `project` | `Evidence/<alias>/State/CLAUDE.md` | All operations on this project |
|
|
37
|
+
| `alias` | `Evidence/<alias>/State/CLAUDE.md` | Only this contributor's operations |
|
|
38
|
+
| `global` | `~/.kushi/conventions.md` | All projects (future — v5.3.0) |
|
|
39
|
+
|
|
40
|
+
v5.2.0 implements `project` scope only. `alias` and `global` are documented for forward compatibility.
|
|
41
|
+
|
|
42
|
+
## How rules are applied
|
|
43
|
+
|
|
44
|
+
1. At the start of `build-state`, `ask-project`, and `refresh-project`, the skill reads `Evidence/<alias>/State/CLAUDE.md`.
|
|
45
|
+
2. Rules are parsed into a list and included as context for the operation.
|
|
46
|
+
3. Rules affect preferences (formatting, naming, emphasis) but NEVER override hard doctrine (WorkIQ-only, verbatim-by-default, CSC format).
|
|
47
|
+
|
|
48
|
+
## Conflict resolution
|
|
49
|
+
|
|
50
|
+
- Hard doctrine always wins over user rules.
|
|
51
|
+
- Later rules override earlier rules on the same topic.
|
|
52
|
+
- If a rule contradicts doctrine, it's logged as a warning but not applied.
|
|
53
|
+
|
|
54
|
+
## CLI verb
|
|
55
|
+
|
|
56
|
+
`kushi remember <rule>` — captures a rule at project scope.
|
|
57
|
+
|
|
58
|
+
Auto-detection: when the user says "from now on...", "always...", "never...", "for this project..." in `ask-project`, the skill offers to persist it as a convention.
|
|
59
|
+
|
|
60
|
+
## Examples
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
> kushi remember "always use 'HCA' not 'Healthcare Accelerator' in summaries"
|
|
64
|
+
> kushi remember "treat John Smith as the primary EM for this project"
|
|
65
|
+
> kushi remember "CRM entity 'opportunity' maps to our internal term 'deal'"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## References
|
|
69
|
+
|
|
70
|
+
- `karpathy-state-layout.instructions.md` — CLAUDE.md file in State layout
|
|
71
|
+
- `living-wiki.instructions.md` — rules interact with incremental State maintenance
|
|
72
|
+
- `build-state` skill — reads rules at start of run
|
|
73
|
+
- `ask-project` skill — reads rules + offers to capture new ones
|