kushi-agents 5.3.0 → 5.4.1
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/bin/cli.mjs +77 -0
- package/package.json +2 -2
- package/plugin/agents/kushi.agent.md +194 -193
- package/plugin/instructions/release-genealogy.instructions.md +52 -52
- package/plugin/instructions/system-health.instructions.md +51 -0
- package/plugin/skills/doctor/SKILL.md +72 -0
- package/plugin/skills/doctor/doctor.ps1 +260 -0
- package/plugin/skills/doctor/evals/evals.json +28 -0
- package/plugin/skills/self-check/SKILL.md +1 -0
- package/plugin/skills/self-check/run.ps1 +115 -14
- package/src/cli-no-args-tty.test.mjs +59 -0
- package/src/cli-no-args.test.mjs +30 -0
- package/src/doctor.test.mjs +93 -0
- package/src/setup-wizard.mjs +133 -0
- package/src/setup-wizard.test.mjs +74 -0
|
@@ -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:
|
|
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.
|
|
@@ -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
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"skill": "doctor",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "doctor.ps1 ships in the skill folder, is parseable, and the SKILL.md is present.",
|
|
5
|
+
"cases": [
|
|
6
|
+
{
|
|
7
|
+
"id": "doctor-skill-md-exists",
|
|
8
|
+
"name": "SKILL.md is present and parseable",
|
|
9
|
+
"input": "verify doctor SKILL.md exists",
|
|
10
|
+
"canary": true,
|
|
11
|
+
"grader_type": "script",
|
|
12
|
+
"expected_assertions": [
|
|
13
|
+
{ "type": "file-exists", "path": "plugin/skills/doctor/SKILL.md" },
|
|
14
|
+
{ "type": "file-exists", "path": "plugin/skills/doctor/doctor.ps1" }
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": "doctor-doctrine-exists",
|
|
19
|
+
"name": "system-health doctrine ships alongside the skill",
|
|
20
|
+
"input": "verify doctor doctrine exists",
|
|
21
|
+
"canary": false,
|
|
22
|
+
"grader_type": "script",
|
|
23
|
+
"expected_assertions": [
|
|
24
|
+
{ "type": "file-exists", "path": "plugin/instructions/system-health.instructions.md" }
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
@@ -77,6 +77,7 @@ Checks split into **core** (always run) and **deep** (opt-in).
|
|
|
77
77
|
| D38.parallel | Parallel execution (v5.2.0+) | `parallel-execution.instructions.md` exists, `refresh-project/SKILL.md` references parallel dispatch, doctrine documents deterministic output ordering. Sub-checks: `D38.parallel-doctrine-exists`, `D38.refresh-supports-parallel`, `D38.deterministic-order`. |
|
|
78
78
|
| D39.otel | OpenTelemetry export (v5.2.0+) | `otel.instructions.md` exists, `Emit-OtelSpan.ps1` helper exists, helper short-circuits when `KUSHI_OTEL_ENDPOINT` is unset. Sub-checks: `D39.otel-doctrine-exists`, `D39.otel-helper-exists`, `D39.otel-noop-when-unset`. |
|
|
79
79
|
| D40.global-wiki | Global wiki + multi-wiki routing (v5.3.0+) | `global-wiki.instructions.md` + `multi-wiki-routing.instructions.md` both exist; `src/global-wiki.mjs` exports the init/promote surface; resolved `$KUSHI_GLOBAL_ROOT`'s `State/` (when initialized) carries `scope: global` frontmatter on all root pages; no `> [!warning] potential-customer-leak` callouts are unresolved. Sub-checks: `D40.global-wiki-doctrine-exists`, `D40.routing-doctrine-exists`, `D40.global-wiki-module-exists`, `D40.promote-module-exists`, `D40.global-wiki-shape`, `D40.no-customer-leak`. |
|
|
80
|
+
| D41.stabilization | v5.4.0 stabilization + first-run polish | `plugin/skills/doctor/SKILL.md` + `doctor.ps1` ship; `docs/quickstart.md` exists and is ≤200 lines; `docs/migration/v4-to-v5.md` exists; `docs/audits/v5.4.0-soak-fixture.md` exists and mentions all 10 soak steps; `.github/workflows/pr-checks.yml` invokes both self-check and skill-checker with `-StrictExit`. Sub-checks: `D41.doctor-exists`, `D41.quickstart-exists`, `D41.migration-notes-exist`, `D41.soak-audit-exists`, `D41.pr-checks-strict`. |
|
|
80
81
|
| **CSC weekly-layout checks (kushi v4.9.0)** | | gated on `Resolve-EngagementRoots` — no-ops on the kushi repo itself. |
|
|
81
82
|
| D11.csc | CSC entity coverage + depth | every `Evidence/<alias>/<source>/weekly/*-csc.md` has ≥ 1 entity heading; per-source minimum bullet count + populated-section count (meetings 25/6, email 8/4, teams 6/3, onenote 10/4, sharepoint 8/3, crm 12/5, ado 8/4). Coverage-Notes-only blocks (low-signal escape) are exempt. |
|
|
82
83
|
| D12.csc | CSC section order | every entity block's `###` section headings appear in the canonical order: Participants → Topics → Q&A → Who Said What → Decisions → Dates & Numbers → Action Items → Next Steps → Open Questions → Risks → Customer Asks → Artifacts → Coverage Notes. |
|