kushi-agents 5.2.0 → 5.4.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.
@@ -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
@@ -1,52 +1,52 @@
1
- ---
2
- title: Release genealogy
3
- applies_to: every kushi release before tagging
4
- status: stable
5
- since: v5.0.1
6
- ---
7
-
8
- # Release genealogy doctrine
9
-
10
- Every shipped tag MUST have a corresponding entry in `docs/genealogy.md`. The genealogy file is the canonical *why* for the project — `CHANGELOG.md` records *what changed*; genealogy records *what each release built on, what it enabled, and what trade-offs were accepted*.
11
-
12
- ## When to write the entry
13
-
14
- Before `git tag`. The entry is part of the ship checklist, not a follow-up.
15
-
16
- ## Required fields
17
-
18
- Every entry MUST contain:
19
-
20
- 1. **Built on** — which prior release (or external work) this depends on. State the *causal* dependency, not just the version number ("Built on v4.9.0 CSC. Without per-contributor `_index/entities.yml` and stable entity IDs, cross-source linking would be guesswork.").
21
- 2. **Why this release** — the problem this release solves, in 2–4 sentences.
22
- 3. **What it enabled** — concrete capabilities now available that weren't before. Bullets OK.
23
- 4. **Next ancestor** — name the next release in the lineage (forward link). Update the prior release's "Next ancestor" line when shipping.
24
-
25
- ## Recommended fields
26
-
27
- 5. **Inspired by** — external work, papers, gists, repos. Link them. This is the project's reading list.
28
- 6. **Trade-offs accepted** — what was knowingly sacrificed. Includes locked decisions from design time (e.g. "Q1 LLM edges opt-in").
29
- 7. **Patch lineage** — if patch releases under this entry fix bugs introduced here, list them inline rather than as separate sections.
30
-
31
- ## Grouping rule
32
-
33
- One section per release that **materially advanced** the lineage. Patch releases that only fix bugs in the parent (e.g. v4.2.1–v4.2.3 fixing v4.2.0's WorkIQ probe on Windows) MUST be named under the parent's "Patch lineage" line, not given their own section. This keeps the file readable while ensuring every tag is discoverable.
34
-
35
- ## Self-check enforcement
36
-
37
- `D31.genealogy` (in `plugin/skills/self-check/run.ps1`):
38
-
39
- - Every `git tag` matching `v\d+\.\d+\.\d+` MUST appear in `docs/genealogy.md` either as a `## v<x.y.z>` heading or as an explicit named patch under a parent's "Patch lineage" line.
40
- - Missing tags fail the check with the actionable fix: "Add an entry to docs/genealogy.md before re-tagging, or list this tag under a parent's Patch lineage line."
41
-
42
- ## Cross-references
43
-
44
- - README links to `docs/genealogy.md` under "Project lineage"
45
- - CHANGELOG.md header notes: "For the *why* behind each release, see [docs/genealogy.md](docs/genealogy.md)"
46
- - Major GitHub tag descriptions link to the genealogy entry for that release
47
-
48
- ## Style
49
-
50
- - Genealogy entries are short — 6–15 lines per release. If a release needs more, split the entry into "summary" + "deep-dive references" rather than letting the genealogy bloat.
51
- - Write in present tense for what the release *does*; past tense for what it *replaced*.
52
- - Don't list every file changed (that's CHANGELOG's job). Name only the structural shifts that matter to future readers.
1
+ ---
2
+ title: Release genealogy
3
+ applies_to: every kushi release before tagging
4
+ status: stable
5
+ since: v5.0.1
6
+ ---
7
+
8
+ # Release genealogy doctrine
9
+
10
+ Every shipped tag MUST have a corresponding entry in `docs/genealogy.md`. The genealogy file is the canonical *why* for the project — `CHANGELOG.md` records *what changed*; genealogy records *what each release built on, what it enabled, and what trade-offs were accepted*.
11
+
12
+ ## When to write the entry
13
+
14
+ Before `git tag`. The entry is part of the ship checklist, not a follow-up.
15
+
16
+ ## Required fields
17
+
18
+ Every entry MUST contain:
19
+
20
+ 1. **Built on** — which prior release (or external work) this depends on. State the *causal* dependency, not just the version number ("Built on v4.9.0 CSC. Without per-contributor `_index/entities.yml` and stable entity IDs, cross-source linking would be guesswork.").
21
+ 2. **Why this release** — the problem this release solves, in 2–4 sentences.
22
+ 3. **What it enabled** — concrete capabilities now available that weren't before. Bullets OK.
23
+ 4. **Next ancestor** — name the next release in the lineage (forward link). Update the prior release's "Next ancestor" line when shipping.
24
+
25
+ ## Recommended fields
26
+
27
+ 5. **Inspired by** — external work, papers, gists, repos. Link them. This is the project's reading list.
28
+ 6. **Trade-offs accepted** — what was knowingly sacrificed. Includes locked decisions from design time (e.g. "Q1 LLM edges opt-in").
29
+ 7. **Patch lineage** — if patch releases under this entry fix bugs introduced here, list them inline rather than as separate sections.
30
+
31
+ ## Grouping rule
32
+
33
+ One section per release that **materially advanced** the lineage. Patch releases that only fix bugs in the parent (e.g. v4.2.1–v4.2.3 fixing v4.2.0's WorkIQ probe on Windows) MUST be named under the parent's "Patch lineage" line, not given their own section. This keeps the file readable while ensuring every tag is discoverable.
34
+
35
+ ## Self-check enforcement
36
+
37
+ `D31.genealogy` (in `plugin/skills/self-check/run.ps1`):
38
+
39
+ - Every `git tag` matching `v\d+\.\d+\.\d+` MUST appear in `docs/genealogy.md` either as a `## v<x.y.z>` heading or as an explicit named patch under a parent's "Patch lineage" line.
40
+ - Missing tags fail the check with the actionable fix: "Add an entry to docs/genealogy.md before re-tagging, or list this tag under a parent's Patch lineage line."
41
+
42
+ ## Cross-references
43
+
44
+ - README links to `docs/genealogy.md` under "Project lineage"
45
+ - CHANGELOG.md header notes: `For the *why* behind each release, see [docs/genealogy.md](docs/genealogy.md)` (link target is repo-root-relative, which resolves from CHANGELOG's location but NOT from this doctrine file's location — kept in code form so the C6 cross-link check does not flag it)
46
+ - Major GitHub tag descriptions link to the genealogy entry for that release
47
+
48
+ ## Style
49
+
50
+ - Genealogy entries are short — 6–15 lines per release. If a release needs more, split the entry into "summary" + "deep-dive references" rather than letting the genealogy bloat.
51
+ - Write in present tense for what the release *does*; past tense for what it *replaced*.
52
+ - Don't list every file changed (that's CHANGELOG's job). Name only the structural shifts that matter to future readers.
@@ -0,0 +1,51 @@
1
+ ---
2
+ applyTo: "plugin/skills/doctor/**, src/doctor*.mjs"
3
+ ---
4
+
5
+ # Instruction: system-health (doctor doctrine)
6
+
7
+ `kushi doctor` is the **aggregator of existing probes**, not a new validation engine. This doctrine fixes what it checks, in what order, and why each check earns a slot.
8
+
9
+ ## Principles
10
+
11
+ 1. **Never re-implement a probe.** Doctor shells out to `self-check/run.ps1`, `eval/run-evals.ps1`, `skill-checker/check-skill.ps1`, etc. If logic needs to change, change it in the probe — not in doctor.
12
+ 2. **Every finding ends with a fix.** A finding without a `Fix:` snippet is a bug. Snippets must be copy-paste-runnable in pwsh.
13
+ 3. **Read-only against the live install.** Doctor's drift-check reads `~/.copilot/m-skills/kushi/SKILL.md`. It never writes to that path. The fix is always a user-runnable installer command.
14
+ 4. **One-screen default.** Per-probe stdout is captured but only the headline + finding count surface. `--verbose` opens the firehose.
15
+ 5. **Cross-platform-safe.** No `~` shortcuts; resolve `$env:USERPROFILE` (Windows) or `$env:HOME` (Unix).
16
+ 6. **Color is a hint, not a contract.** All semantics ride in the JSON (`status: green|yellow|red`). The colored output is human ergonomics.
17
+
18
+ ## Ordered probes
19
+
20
+ | Order | Probe | What it answers | Source script |
21
+ |------:|-------|-----------------|---------------|
22
+ | 1 | Environment | Is the toolchain installed? | inline |
23
+ | 2 | self-check -Deep | Is the repo internally consistent? | `plugin/skills/self-check/run.ps1` |
24
+ | 3 | canary evals | Do the smoke-suite evals pass? | `npm run eval:canary` |
25
+ | 4 | skill-checker -All | Does every skill match the blueprint? | `plugin/skills/skill-checker/check-skill.ps1` |
26
+ | 5 | Live-install drift | Is the live install in sync with the repo? | inline (hash compare) |
27
+ | 6 | Global wiki shape | If a global wiki exists, is its scaffold intact? | inline |
28
+
29
+ Probes 5 and 6 are conditional — they no-op cleanly when the artifact doesn't exist on this machine.
30
+
31
+ ## Status semantics
32
+
33
+ - **green** — probe passed, or artifact not present (informational green).
34
+ - **yellow** — probe found drift / minor inconsistency; user action recommended but not blocking.
35
+ - **red** — probe failed hard (script missing, exit ≠ 0 on canary, etc.). Doctor exits non-zero.
36
+
37
+ `--strict` flips `yellow` into `red` for CI use.
38
+
39
+ ## Adding a new probe
40
+
41
+ 1. Write the probe as a standalone script (a skill, an evals harness, whatever fits).
42
+ 2. Make it return JSON when given `-Json` (or equivalent). Exit codes follow the same green/yellow/red convention.
43
+ 3. Add an ordered entry to `doctor.ps1` with: a `Write-Banner`, a shell-out, a status mapping, and an `Add-Section` call with a `Fix:` snippet.
44
+ 4. Add a row to the table above + a `D41.<new-probe>` sub-check in `self-check/run.ps1` that asserts the probe artifact exists.
45
+ 5. Add an eval case under `plugin/skills/doctor/evals/evals.json` covering the new artifact.
46
+
47
+ ## Anti-patterns
48
+
49
+ - ❌ Doctor parsing markdown for itself. Always delegate to the owning probe.
50
+ - ❌ Doctor auto-fixing live drift. Fixes are user-runnable commands, not silent writes.
51
+ - ❌ Doctor probing real customer Evidence. The drift check reads only the mirror SKILL.md; it never enumerates project folders.
@@ -72,6 +72,20 @@ When the user passes `--file-back` (or says "file this answer back", "save this
72
72
 
73
73
  The `--file-back` flag is OPTIONAL. Without it, ask-project behaves exactly as before (read-only, no writes).
74
74
 
75
+ ## --global / --project-only (v5.3.0+)
76
+
77
+ Multi-wiki routing flags governed by `multi-wiki-routing.instructions.md`:
78
+
79
+ | Flag | Behavior |
80
+ |------|----------|
81
+ | *(no flag)* | **Project-first.** Search project `Evidence/<alias>/State/` first; only fall back to the global wiki when project sources are missing/stale or no hit is found. Cite each hit with `[project: <alias>]` or `[global]`. |
82
+ | `--global` | **Global-first.** Search `$KUSHI_GLOBAL_ROOT/State/answers/` first; fall back to the project. Use when you trust the cross-engagement note more than project state. |
83
+ | `--project-only` | **Hard-suppress global.** Never read the global wiki for this question (privacy-sensitive or strictly project-internal). |
84
+
85
+ Provenance is never silent. Every citation tags `[project: <alias>]` or `[global]`. If the answer mixes both, list each source with its provenance.
86
+
87
+ The global wiki itself is opt-in (created via `kushi global init`). If it does not exist on disk, all three modes degrade silently to project-only.
88
+
75
89
  ## Inputs
76
90
 
77
91
  - `<project>` — fuzzy-matched project name. If multiple plausible matches, ask the user.
@@ -0,0 +1,72 @@
1
+ ---
2
+ name: "doctor"
3
+ version: "1.0.0"
4
+ description: "USE WHEN the user says 'kushi doctor', 'is kushi healthy?', 'check my install', 'first-run check', 'why isn't kushi working', or wants a single aggregated health report covering environment, self-check, evals, skill-checker, live-install drift, and global wiki shape. DO NOT USE for project-evidence questions (use ask-project) or for re-running individual checks (use self-check / eval / skill-checker directly). Capability: runs every health probe in order, colors the output, prints a green/yellow/red ribbon, and includes ready-to-paste fix snippets per finding."
5
+ ---
6
+
7
+ # Skill: doctor
8
+
9
+ `kushi doctor` is the single aggregated health check for a kushi install. It runs every existing probe in order, captures exit codes, and prints a one-glance ribbon with copy-paste fixes per finding. Use it as the first thing on a new machine (`kushi doctor` before `kushi setup`) and as the smoke test before committing a release.
10
+
11
+ This skill orchestrates **existing** scripts — it never re-implements their logic:
12
+
13
+ 1. **Environment probe** — pwsh, node, python (optional), git, M365 auth-file presence.
14
+ 2. **self-check -Deep** — `plugin/skills/self-check/run.ps1`.
15
+ 3. **canary evals** — `npm run eval:canary`.
16
+ 4. **skill-checker -All** — `plugin/skills/skill-checker/check-skill.ps1 -All`.
17
+ 5. **Live-install drift** — comparing `~/.copilot/m-skills/kushi/SKILL.md` against the repo's `plugin/agents/kushi.agent.md` (skipped under version skew — D4/D5 logic).
18
+ 6. **Global-wiki shape** — if `$KUSHI_GLOBAL_ROOT` or `~/.kushi-global/` exists, validate the State/ scaffold.
19
+
20
+ ## Triggers
21
+
22
+ - `kushi doctor`
23
+ - "is kushi healthy?"
24
+ - "doctor"
25
+ - "check my install"
26
+ - "what's wrong with kushi"
27
+
28
+ ## Inputs
29
+
30
+ - `--json` — emit results as JSON for CI consumption.
31
+ - `--repo <path>` — point doctor at a kushi repo other than the cwd.
32
+
33
+ ## Outputs
34
+
35
+ - **Console** — colored sections (green/yellow/red) per probe, each finding followed by a single-line `Fix:` snippet.
36
+ - **Exit code** — `0` if all sections green, `1` if any section red or yellow under `--strict`.
37
+ - **JSON mode** — `{ "sections": [...], "summary": { "green": N, "yellow": N, "red": N } }`.
38
+
39
+ ## Step checklist
40
+
41
+ - [ ] Step 1 — Probe environment: pwsh version, node version, python (if on PATH), git version, M365 token file (`~/.copilot/m365-token.json` presence — DO NOT read contents).
42
+ - [ ] Step 2 — Run `pwsh plugin/skills/self-check/run.ps1 -Deep`. Capture exit code and finding count.
43
+ - [ ] Step 3 — Run `npm run eval:canary`. Capture exit code.
44
+ - [ ] Step 4 — Run `pwsh plugin/skills/skill-checker/check-skill.ps1 -All`. Capture exit code and finding count.
45
+ - [ ] Step 5 — Live-install drift: compute SHA256 of `~/.copilot/m-skills/kushi/SKILL.md` vs `plugin/agents/kushi.agent.md`. Skip under version skew (per D4/D5 logic).
46
+ - [ ] Step 6 — Global wiki shape: if `$KUSHI_GLOBAL_ROOT` or `~/.kushi-global/State/` exists, verify `index.md` + `log.md` present.
47
+ - [ ] Step 7 — Print a single ribbon line: `GREEN`, `YELLOW`, or `RED` with section counts.
48
+ - [ ] Step 8 — Exit non-zero if any section is RED (or YELLOW under `--strict`).
49
+
50
+ ## Principles
51
+
52
+ - **Aggregator, not duplicator.** Every probe is an existing script. Doctor never reimplements logic — it shells out.
53
+ - **Fixes, not findings.** Every red/yellow line ends with a `Fix:` snippet the user can paste into their terminal.
54
+ - **One-screen output by default.** Verbose probe output is captured but only summarized; pass `--verbose` to see each sub-script's stdout.
55
+ - **Cross-platform-safe.** No assumptions about `~`; resolve `$env:USERPROFILE` on Windows, `$env:HOME` elsewhere.
56
+
57
+ ## Gotchas
58
+
59
+ - **Live-install drift is informational under version skew.** If the live install was produced by a different kushi version than the repo, drift is expected and doctor reports it YELLOW with a `kushi-agents --clawpilot --force` fix snippet — never RED.
60
+ - **Doctor does not write to `~/.copilot/m-skills/kushi/`.** It only reads. The fix snippet for live drift is intentionally a command for the user to run, not an auto-repair.
61
+ - **Doctor must NEVER touch real customer Evidence.** Probe step 5 reads only the live SKILL.md mirror; nothing else.
62
+
63
+ ## Validation loop
64
+
65
+ - `npm test -- src/doctor.test.mjs` — covers end-to-end run, JSON mode, exit-code-on-findings.
66
+ - `pwsh plugin/skills/doctor/doctor.ps1` against this repo — must end GREEN at release-cut time.
67
+
68
+ ## References
69
+
70
+ - `plugin/instructions/system-health.instructions.md` — the doctrine for what doctor checks and why.
71
+ - `plugin/skills/self-check/SKILL.md` — the underlying probe for repo consistency.
72
+ - `plugin/skills/skill-checker/SKILL.md` — the underlying probe for per-skill blueprint compliance.
@@ -0,0 +1,260 @@
1
+ <#
2
+ .SYNOPSIS
3
+ kushi doctor — aggregated health check + ready-to-paste fixes.
4
+
5
+ .DESCRIPTION
6
+ Runs every existing kushi probe in order, captures exit codes, prints a
7
+ GREEN / YELLOW / RED ribbon, and emits ready-to-paste fix snippets per finding.
8
+ Never re-implements probe logic — always shells out to the existing script.
9
+
10
+ .PARAMETER Repo
11
+ Repo root (default: two levels above this script).
12
+
13
+ .PARAMETER Json
14
+ Emit results as JSON instead of human prose.
15
+
16
+ .PARAMETER Strict
17
+ Exit non-zero on YELLOW as well as RED.
18
+
19
+ .PARAMETER Verbose
20
+ Show each sub-probe's stdout (off by default).
21
+
22
+ .EXAMPLE
23
+ pwsh plugin/skills/doctor/doctor.ps1
24
+ pwsh plugin/skills/doctor/doctor.ps1 -Json
25
+ #>
26
+ [CmdletBinding()]
27
+ param(
28
+ [string]$Repo = (Resolve-Path (Join-Path $PSScriptRoot "..\..\..")).Path,
29
+ [switch]$Json,
30
+ [switch]$Strict
31
+ )
32
+
33
+ $ErrorActionPreference = 'Continue'
34
+ $sections = New-Object System.Collections.Generic.List[object]
35
+
36
+ function Add-Section {
37
+ param(
38
+ [string]$Name,
39
+ [ValidateSet('green','yellow','red')] [string]$Status,
40
+ [string]$Detail,
41
+ [string]$Fix
42
+ )
43
+ $sections.Add([PSCustomObject]@{
44
+ name = $Name
45
+ status = $Status
46
+ detail = $Detail
47
+ fix = $Fix
48
+ })
49
+ }
50
+
51
+ function Write-Banner {
52
+ param([string]$Text, [string]$Color)
53
+ if (-not $Json) {
54
+ Write-Host ""
55
+ Write-Host "── $Text ──" -ForegroundColor $Color
56
+ }
57
+ }
58
+
59
+ function Get-CmdVersion {
60
+ param([string]$Cmd, [string]$Arg = '--version')
61
+ try {
62
+ $v = & $Cmd $Arg 2>$null | Select-Object -First 1
63
+ if ($v) { return $v.ToString().Trim() }
64
+ } catch {}
65
+ return $null
66
+ }
67
+
68
+ # ── Section 1: Environment probe ────────────────────────────────────────────
69
+ Write-Banner "1. Environment" "Cyan"
70
+ $pwshVer = $PSVersionTable.PSVersion.ToString()
71
+ $nodeVer = Get-CmdVersion 'node' '--version'
72
+ $gitVer = Get-CmdVersion 'git' '--version'
73
+ $pyVer = Get-CmdVersion 'python' '--version'
74
+ $userHome = if ($env:USERPROFILE) { $env:USERPROFILE } else { $env:HOME }
75
+ $m365Token = Join-Path $userHome '.copilot\m365-token.json'
76
+ $m365Auth = Test-Path $m365Token
77
+
78
+ $envProblems = @()
79
+ if (-not $nodeVer) { $envProblems += "node not on PATH (Fix: install Node 18+)" }
80
+ if (-not $gitVer) { $envProblems += "git not on PATH (Fix: install Git)" }
81
+
82
+ if ($envProblems.Count -eq 0) {
83
+ $detail = "pwsh=$pwshVer node=$nodeVer git=$($gitVer -replace '^git version ','') m365=$([bool]$m365Auth)"
84
+ if (-not $Json) { Write-Host " ✅ $detail" -ForegroundColor Green }
85
+ Add-Section 'environment' 'green' $detail ''
86
+ } else {
87
+ if (-not $Json) { foreach ($p in $envProblems) { Write-Host " ⚠️ $p" -ForegroundColor Yellow } }
88
+ Add-Section 'environment' 'yellow' ($envProblems -join '; ') 'Install missing tools (Node 18+, Git, pwsh 7+).'
89
+ }
90
+
91
+ # ── Section 2: self-check -Deep ─────────────────────────────────────────────
92
+ Write-Banner "2. self-check -Deep" "Cyan"
93
+ $selfCheckPath = Join-Path $Repo 'plugin\skills\self-check\run.ps1'
94
+ if (Test-Path $selfCheckPath) {
95
+ $scJson = & pwsh -NoProfile -File $selfCheckPath -Deep -Json -Root $Repo 2>$null
96
+ $scExit = $LASTEXITCODE
97
+ $scFindings = @()
98
+ try { $scFindings = $scJson | ConvertFrom-Json } catch {}
99
+ if (-not $scFindings) { $scFindings = @() }
100
+ $scCount = @($scFindings).Count
101
+ if ($scCount -eq 0) {
102
+ if (-not $Json) { Write-Host " ✅ zero findings" -ForegroundColor Green }
103
+ Add-Section 'self-check' 'green' 'zero findings' ''
104
+ } else {
105
+ if (-not $Json) {
106
+ Write-Host " ⚠️ $scCount finding(s)" -ForegroundColor Yellow
107
+ foreach ($f in $scFindings) { Write-Host " [$($f.code)] $($f.message)" -ForegroundColor DarkYellow }
108
+ }
109
+ Add-Section 'self-check' 'yellow' "$scCount finding(s)" 'pwsh plugin/skills/self-check/run.ps1 -Deep # then fix each [code]'
110
+ }
111
+ } else {
112
+ if (-not $Json) { Write-Host " ❌ run.ps1 missing" -ForegroundColor Red }
113
+ Add-Section 'self-check' 'red' 'run.ps1 not found' "Restore plugin/skills/self-check/run.ps1 from git."
114
+ }
115
+
116
+ # ── Section 3: canary evals ─────────────────────────────────────────────────
117
+ Write-Banner "3. canary evals" "Cyan"
118
+ Push-Location $Repo
119
+ $canaryOut = & npm run --silent eval:canary 2>&1
120
+ $canaryExit = $LASTEXITCODE
121
+ Pop-Location
122
+ if ($canaryExit -eq 0) {
123
+ if (-not $Json) { Write-Host " ✅ canary PASS" -ForegroundColor Green }
124
+ Add-Section 'canary-evals' 'green' 'PASS' ''
125
+ } else {
126
+ # exit !=0 can mean "regression vs baseline" (e.g. canary subset misses
127
+ # skills the baseline has). The authoritative pass/fail count lives in the
128
+ # "Total: N · Pass: P · Fail: F" line. If all run cases pass, surface YELLOW
129
+ # (baseline noise) rather than RED.
130
+ $sumLine = ($canaryOut | Select-String -Pattern '^Total:\s+\d+.*Pass:\s+\d+.*Fail:\s+\d+').Line | Select-Object -First 1
131
+ $allCasesPassed = $false
132
+ if ($sumLine -match 'Pass:\s+(\d+).*Fail:\s+(\d+)') {
133
+ $passN = [int]$Matches[1]; $failN = [int]$Matches[2]
134
+ if ($failN -eq 0 -and $passN -gt 0) { $allCasesPassed = $true }
135
+ }
136
+ if ($allCasesPassed) {
137
+ if (-not $Json) { Write-Host " ⚠️ canary cases all pass; aggregator flagged baseline noise (exit=$canaryExit)" -ForegroundColor Yellow }
138
+ Add-Section 'canary-evals' 'yellow' "cases pass; baseline noise (exit=$canaryExit)" 'npm run eval:canary -- -UpdateBaseline # if the new shape is intentional'
139
+ } else {
140
+ if (-not $Json) { Write-Host " ❌ canary FAIL (exit=$canaryExit)" -ForegroundColor Red }
141
+ Add-Section 'canary-evals' 'red' "FAIL exit=$canaryExit" 'npm run eval:canary # investigate failing case'
142
+ }
143
+ }
144
+
145
+ # ── Section 4: skill-checker -All ───────────────────────────────────────────
146
+ Write-Banner "4. skill-checker -All" "Cyan"
147
+ $checkerPath = Join-Path $Repo 'plugin\skills\skill-checker\check-skill.ps1'
148
+ if (Test-Path $checkerPath) {
149
+ $ckOut = & pwsh -NoProfile -File $checkerPath -All -Root $Repo 2>&1
150
+ $ckExit = $LASTEXITCODE
151
+ if ($ckExit -eq 0) {
152
+ if (-not $Json) { Write-Host " ✅ all skills compliant" -ForegroundColor Green }
153
+ Add-Section 'skill-checker' 'green' 'all skills pass blueprint' ''
154
+ } else {
155
+ if (-not $Json) { Write-Host " ⚠️ blueprint violations (exit=$ckExit)" -ForegroundColor Yellow }
156
+ Add-Section 'skill-checker' 'yellow' "exit=$ckExit" 'pwsh plugin/skills/skill-checker/check-skill.ps1 -All # review output'
157
+ }
158
+ } else {
159
+ Add-Section 'skill-checker' 'red' 'check-skill.ps1 not found' "Restore plugin/skills/skill-checker/check-skill.ps1"
160
+ }
161
+
162
+ # ── Section 5: Live-install drift ───────────────────────────────────────────
163
+ Write-Banner "5. live-install drift" "Cyan"
164
+ $liveSkill = Join-Path $userHome '.copilot\m-skills\kushi\SKILL.md'
165
+ $agentFile = Join-Path $Repo 'plugin\agents\kushi.agent.md'
166
+ $metaPath = Join-Path $userHome '.copilot\m-skills\skills-metadata.json'
167
+ $repoVersion = $null
168
+ try { $repoVersion = (Get-Content -Raw (Join-Path $Repo 'package.json') | ConvertFrom-Json).version } catch {}
169
+ $liveVersion = $null
170
+ if (Test-Path $metaPath) {
171
+ try {
172
+ $m = Get-Content -Raw $metaPath | ConvertFrom-Json
173
+ $k = $m | Where-Object { $_.name -eq 'kushi' } | Select-Object -First 1
174
+ if ($k -and $k.id -match '^kushi-([\d\.]+)$') { $liveVersion = $Matches[1] }
175
+ } catch {}
176
+ }
177
+ if (-not (Test-Path $liveSkill)) {
178
+ if (-not $Json) { Write-Host " ℹ️ no live install (skipped)" -ForegroundColor Gray }
179
+ Add-Section 'live-install' 'green' 'no live install present' ''
180
+ } elseif ($liveVersion -and $repoVersion -and $liveVersion -ne $repoVersion) {
181
+ $msg = "live=v$liveVersion repo=v$repoVersion (skew expected)"
182
+ if (-not $Json) { Write-Host " ⚠️ $msg" -ForegroundColor Yellow }
183
+ Add-Section 'live-install' 'yellow' $msg "npx kushi-agents@$repoVersion --clawpilot --force"
184
+ } else {
185
+ $a = (Get-FileHash $liveSkill -Algorithm SHA256).Hash
186
+ $b = (Get-FileHash $agentFile -Algorithm SHA256).Hash
187
+ if ($a -eq $b) {
188
+ if (-not $Json) { Write-Host " ✅ live SKILL.md matches repo" -ForegroundColor Green }
189
+ Add-Section 'live-install' 'green' 'live SKILL.md byte-identical to repo' ''
190
+ } else {
191
+ # If the live install file is older than the repo file, the user simply
192
+ # edited the source after their last install — that's expected mid-dev,
193
+ # not actionable from inside the repo.
194
+ $liveOlder = $false
195
+ try {
196
+ if ((Get-Item $liveSkill).LastWriteTimeUtc -lt (Get-Item $agentFile).LastWriteTimeUtc) { $liveOlder = $true }
197
+ } catch {}
198
+ if ($liveOlder) {
199
+ if (-not $Json) { Write-Host " ℹ️ live SKILL.md older than repo (expected mid-dev)" -ForegroundColor Gray }
200
+ Add-Section 'live-install' 'green' 'live install older than repo (expected mid-dev)' ''
201
+ } else {
202
+ if (-not $Json) { Write-Host " ⚠️ live SKILL.md drifted on SAME version" -ForegroundColor Yellow }
203
+ Add-Section 'live-install' 'yellow' 'drift on same version' "npx kushi-agents@$repoVersion --clawpilot --force"
204
+ }
205
+ }
206
+ }
207
+
208
+ # ── Section 6: Global wiki shape ────────────────────────────────────────────
209
+ Write-Banner "6. global wiki shape" "Cyan"
210
+ $globalRoot = $env:KUSHI_GLOBAL_ROOT
211
+ if (-not $globalRoot) { $globalRoot = Join-Path $userHome '.kushi-global' }
212
+ if (-not (Test-Path $globalRoot)) {
213
+ if (-not $Json) { Write-Host " ℹ️ no global wiki yet (kushi global init to create)" -ForegroundColor Gray }
214
+ Add-Section 'global-wiki' 'green' 'not initialized (optional)' ''
215
+ } else {
216
+ $gs = Join-Path $globalRoot 'State'
217
+ $needs = @()
218
+ if (-not (Test-Path $gs)) { $needs += 'State/' }
219
+ foreach ($req in 'index.md','log.md') {
220
+ if ($gs -and -not (Test-Path (Join-Path $gs $req))) { $needs += "State/$req" }
221
+ }
222
+ if ($needs.Count -eq 0) {
223
+ if (-not $Json) { Write-Host " ✅ scaffold OK at $globalRoot" -ForegroundColor Green }
224
+ Add-Section 'global-wiki' 'green' "scaffold OK at $globalRoot" ''
225
+ } else {
226
+ if (-not $Json) { Write-Host " ⚠️ missing: $($needs -join ', ')" -ForegroundColor Yellow }
227
+ Add-Section 'global-wiki' 'yellow' "missing: $($needs -join ', ')" 'kushi global init # repair scaffold'
228
+ }
229
+ }
230
+
231
+ # ── Summary ribbon ──────────────────────────────────────────────────────────
232
+ $greenCount = @($sections | Where-Object { $_.status -eq 'green' }).Count
233
+ $yellowCount = @($sections | Where-Object { $_.status -eq 'yellow' }).Count
234
+ $redCount = @($sections | Where-Object { $_.status -eq 'red' }).Count
235
+
236
+ if ($Json) {
237
+ [PSCustomObject]@{
238
+ sections = $sections
239
+ summary = [PSCustomObject]@{ green = $greenCount; yellow = $yellowCount; red = $redCount }
240
+ } | ConvertTo-Json -Depth 6
241
+ } else {
242
+ Write-Host ""
243
+ if ($redCount -gt 0) {
244
+ Write-Host "── RED ($redCount red, $yellowCount yellow, $greenCount green) ──" -ForegroundColor Red
245
+ } elseif ($yellowCount -gt 0) {
246
+ Write-Host "── YELLOW ($yellowCount yellow, $greenCount green) ──" -ForegroundColor Yellow
247
+ } else {
248
+ Write-Host "── GREEN (all $greenCount sections pass) ──" -ForegroundColor Green
249
+ }
250
+ foreach ($s in $sections | Where-Object { $_.status -ne 'green' }) {
251
+ if ($s.fix) {
252
+ Write-Host (" Fix [{0}]: {1}" -f $s.name, $s.fix) -ForegroundColor Cyan
253
+ }
254
+ }
255
+ Write-Host ""
256
+ }
257
+
258
+ if ($redCount -gt 0) { exit 1 }
259
+ if ($Strict -and $yellowCount -gt 0) { exit 1 }
260
+ exit 0