kushi-agents 3.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/.github/config/m365-auth.json.example +56 -0
- package/.github/config/m365-mutable.json.example +11 -0
- package/LICENSE +201 -0
- package/README.md +159 -0
- package/bin/cli.mjs +75 -0
- package/package.json +35 -0
- package/plugin/agents/kushi.agent.md +147 -0
- package/plugin/instructions/answer-from-evidence.instructions.md +73 -0
- package/plugin/instructions/auth-and-retry.instructions.md +116 -0
- package/plugin/instructions/az-auth-conditional.instructions.md +39 -0
- package/plugin/instructions/azure-auth-patterns.instructions.md +226 -0
- package/plugin/instructions/citation-ledger.instructions.md +52 -0
- package/plugin/instructions/engagement-root-resolution.instructions.md +82 -0
- package/plugin/instructions/evidence-thoroughness.instructions.md +62 -0
- package/plugin/instructions/side-by-side-config.instructions.md +56 -0
- package/plugin/instructions/snapshot-vs-stream.instructions.md +87 -0
- package/plugin/instructions/thoroughness-detector.instructions.md +105 -0
- package/plugin/instructions/workiq-first.instructions.md +47 -0
- package/plugin/plugin.json +96 -0
- package/plugin/prompts/aggregate.prompt.md +24 -0
- package/plugin/prompts/ask.prompt.md +16 -0
- package/plugin/prompts/bootstrap.prompt.md +23 -0
- package/plugin/prompts/consolidate.prompt.md +21 -0
- package/plugin/prompts/fde-intake.prompt.md +41 -0
- package/plugin/prompts/fde-report.prompt.md +46 -0
- package/plugin/prompts/fde-triage.prompt.md +46 -0
- package/plugin/prompts/refresh.prompt.md +17 -0
- package/plugin/prompts/state.prompt.md +17 -0
- package/plugin/prompts/status.prompt.md +17 -0
- package/plugin/reference-packs/README.md +74 -0
- package/plugin/reference-packs/fde/README.md +62 -0
- package/plugin/reference-packs/fde/core-fde-reference.md +427 -0
- package/plugin/reference-packs/fde/intake-questions.md +168 -0
- package/plugin/reference-packs/fde/report-doctrine.md +189 -0
- package/plugin/skills/aggregate-project/SKILL.md +72 -0
- package/plugin/skills/ask-project/SKILL.md +162 -0
- package/plugin/skills/bootstrap-project/SKILL.md +129 -0
- package/plugin/skills/build-state/SKILL.md +69 -0
- package/plugin/skills/consolidate-evidence/SKILL.md +47 -0
- package/plugin/skills/fde-intake/SKILL.md +147 -0
- package/plugin/skills/fde-report/SKILL.md +159 -0
- package/plugin/skills/fde-triage/SKILL.md +114 -0
- package/plugin/skills/intro/SKILL.md +449 -0
- package/plugin/skills/project-status/SKILL.md +61 -0
- package/plugin/skills/pull-ado/SKILL.md +77 -0
- package/plugin/skills/pull-crm/SKILL.md +75 -0
- package/plugin/skills/pull-email/SKILL.md +75 -0
- package/plugin/skills/pull-meetings/SKILL.md +77 -0
- package/plugin/skills/pull-onenote/SKILL.md +82 -0
- package/plugin/skills/pull-sharepoint/SKILL.md +85 -0
- package/plugin/skills/pull-teams/SKILL.md +75 -0
- package/plugin/skills/refresh-project/SKILL.md +89 -0
- package/plugin/skills/self-check/SKILL.md +166 -0
- package/plugin/skills/self-check/run.ps1 +517 -0
- package/plugin/skills/self-check/run.sh +33 -0
- package/plugin/templates/fde/intake.md +114 -0
- package/plugin/templates/fde/report-fitness.md +151 -0
- package/plugin/templates/fde/report-long.md +109 -0
- package/plugin/templates/fde/report-short.md +45 -0
- package/plugin/templates/fde/report-stage-readiness.md +70 -0
- package/plugin/templates/fde/report-weekly.md +73 -0
- package/plugin/templates/fde/triage-00-fde-analysis.md +78 -0
- package/plugin/templates/fde/triage-02-risk-analysis.md +76 -0
- package/plugin/templates/fde/triage-03-6Q.md +40 -0
- package/plugin/templates/fde/triage-04-readiness-checklist.md +82 -0
- package/plugin/templates/fde/triage-05-executive-consolidated.md +78 -0
- package/plugin/templates/fde/triage-06-global-opportunity.md +70 -0
- package/plugin/templates/fde/triage-07-validation-warnings.md +60 -0
- package/plugin/templates/init/ado-config.template.yml +21 -0
- package/plugin/templates/init/crm-config.template.yml +16 -0
- package/plugin/templates/init/kushi-projects.template.json +14 -0
- package/plugin/templates/init/m365-auth.template.json +67 -0
- package/plugin/templates/init/m365-mutable.template.json +19 -0
- package/plugin/templates/init/project-contributors.template.yml +27 -0
- package/plugin/templates/init/project-evidence.template.yml +32 -0
- package/plugin/templates/init/project-integrations.template.yml +34 -0
- package/plugin/templates/init/project-user-settings.template.yml +71 -0
- package/plugin/templates/paste-prompt.md +35 -0
- package/plugin/templates/snapshot/ado-item.template.md +45 -0
- package/plugin/templates/snapshot/crm-record.template.md +34 -0
- package/plugin/templates/snapshot/meetings-series-index.template.md +32 -0
- package/plugin/templates/snapshot/onenote-page.template.md +28 -0
- package/plugin/templates/snapshot/sharepoint-file.template.md +31 -0
- package/plugin/templates/snapshot/sharepoint-tree.template.md +39 -0
- package/plugin/templates/snapshot/teams-roster.template.md +27 -0
- package/plugin/templates/state/00_overview.template.md +44 -0
- package/plugin/templates/state/01_decisions.template.md +41 -0
- package/plugin/templates/state/02_stakeholders.template.md +48 -0
- package/plugin/templates/state/03_architecture-and-solution.template.md +56 -0
- package/plugin/templates/state/04_workshops-and-key-meetings.template.md +43 -0
- package/plugin/templates/state/05_action-items.template.md +29 -0
- package/plugin/templates/state/06_risks-and-issues.template.md +43 -0
- package/plugin/templates/state/07_timeline-and-milestones.template.md +45 -0
- package/plugin/templates/state/08_artifacts-and-deliverables.template.md +55 -0
- package/plugin/templates/state/09_open-questions.template.md +62 -0
- package/plugin/templates/state/README.md +41 -0
- package/plugin/templates/weekly/ado-stream.template.md +71 -0
- package/plugin/templates/weekly/consolidated.template.md +98 -0
- package/plugin/templates/weekly/crm-stream.template.md +74 -0
- package/plugin/templates/weekly/email-stream.template.md +103 -0
- package/plugin/templates/weekly/meetings-stream.template.md +182 -0
- package/plugin/templates/weekly/onenote-stream.template.md +106 -0
- package/plugin/templates/weekly/run-log.template.md +88 -0
- package/plugin/templates/weekly/sharepoint-stream.template.md +121 -0
- package/plugin/templates/weekly/teams-stream.template.md +121 -0
- package/src/constants.mjs +49 -0
- package/src/copy-assets.mjs +183 -0
- package/src/main.mjs +262 -0
- package/src/profile-resolver.mjs +168 -0
- package/src/prompt.mjs +42 -0
- package/src/settings.mjs +77 -0
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Kushi self-check — validates docs/skill consistency against the file system.
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Source of truth is plugin/skills/. Reports gaps as warnings with concrete fixes.
|
|
7
|
+
Returns exit code 0 always (warnings don't fail the run); use -StrictExit to flip
|
|
8
|
+
that behaviour for CI.
|
|
9
|
+
|
|
10
|
+
.PARAMETER Root
|
|
11
|
+
Repo root (default: two levels above this script — i.e. kushi repo root).
|
|
12
|
+
|
|
13
|
+
.PARAMETER Deep
|
|
14
|
+
Also run D-checks (template references, snapshot/stream, WorkIQ hygiene, live install sync).
|
|
15
|
+
|
|
16
|
+
.PARAMETER Json
|
|
17
|
+
Emit findings as JSON (for CI / agent consumption) instead of human prose.
|
|
18
|
+
|
|
19
|
+
.PARAMETER StrictExit
|
|
20
|
+
Exit with code 1 if any findings are produced.
|
|
21
|
+
|
|
22
|
+
.EXAMPLE
|
|
23
|
+
pwsh plugin/skills/self-check/run.ps1
|
|
24
|
+
pwsh plugin/skills/self-check/run.ps1 -Deep
|
|
25
|
+
pwsh plugin/skills/self-check/run.ps1 -Deep -Json | ConvertFrom-Json
|
|
26
|
+
#>
|
|
27
|
+
[CmdletBinding()]
|
|
28
|
+
param(
|
|
29
|
+
[string]$Root = (Resolve-Path (Join-Path $PSScriptRoot "..\..\..")).Path,
|
|
30
|
+
[switch]$Deep,
|
|
31
|
+
[switch]$Json,
|
|
32
|
+
[switch]$StrictExit
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
$ErrorActionPreference = "Stop"
|
|
36
|
+
$findings = New-Object System.Collections.Generic.List[object]
|
|
37
|
+
|
|
38
|
+
function Add-Finding {
|
|
39
|
+
param($Code, $Surface, $Severity, $Message, $Fix, $File, $Line)
|
|
40
|
+
$findings.Add([PSCustomObject]@{
|
|
41
|
+
code = $Code; surface = $Surface; severity = $Severity
|
|
42
|
+
message = $Message; fix = $Fix; file = $File; line = $Line
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function Get-Frontmatter {
|
|
47
|
+
param([string]$Path)
|
|
48
|
+
$text = Get-Content -Raw -Path $Path
|
|
49
|
+
if ($text -notmatch '^(?s)---\r?\n(.*?)\r?\n---') { return @{} }
|
|
50
|
+
$yaml = $Matches[1]
|
|
51
|
+
$fm = @{}
|
|
52
|
+
foreach ($line in $yaml -split "`n") {
|
|
53
|
+
if ($line -match '^\s*([a-zA-Z0-9_-]+)\s*:\s*"?([^"]*?)"?\s*$') {
|
|
54
|
+
$fm[$Matches[1]] = $Matches[2]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return $fm
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# === Discovery (source of truth) ===
|
|
61
|
+
$pluginDir = Join-Path $Root 'plugin'
|
|
62
|
+
$skillsDir = Join-Path $pluginDir 'skills'
|
|
63
|
+
$instructionsDir = Join-Path $pluginDir 'instructions'
|
|
64
|
+
$promptsDir = Join-Path $pluginDir 'prompts'
|
|
65
|
+
$agentFile = Join-Path $pluginDir 'agents\kushi.agent.md'
|
|
66
|
+
$readmeFile = Join-Path $Root 'README.md'
|
|
67
|
+
$wtlFile = Join-Path $Root 'docs\reference\where-things-live.md'
|
|
68
|
+
|
|
69
|
+
if (-not (Test-Path $pluginDir)) {
|
|
70
|
+
Write-Error "plugin/ not found at $pluginDir — run from kushi repo root or pass -Root."
|
|
71
|
+
exit 2
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
$skillDirs = Get-ChildItem $skillsDir -Directory | Sort-Object Name
|
|
75
|
+
$skillNames = $skillDirs.Name
|
|
76
|
+
$instructionFiles = Get-ChildItem $instructionsDir -Filter '*.instructions.md' -EA SilentlyContinue
|
|
77
|
+
$promptFiles = Get-ChildItem $promptsDir -Filter '*.prompt.md' -EA SilentlyContinue
|
|
78
|
+
|
|
79
|
+
# Pre-load text of all plugin md files for cross-reference scans
|
|
80
|
+
$allMd = Get-ChildItem $pluginDir -Recurse -Filter '*.md'
|
|
81
|
+
$mdText = @{}
|
|
82
|
+
foreach ($f in $allMd) { $mdText[$f.FullName] = Get-Content -Raw -Path $f.FullName }
|
|
83
|
+
|
|
84
|
+
# === C1, C2: frontmatter + name match ===
|
|
85
|
+
foreach ($d in $skillDirs) {
|
|
86
|
+
$skillFile = Join-Path $d.FullName 'SKILL.md'
|
|
87
|
+
if (-not (Test-Path $skillFile)) {
|
|
88
|
+
Add-Finding C1 'Frontmatter' 'error' "Skill `"$($d.Name)`" has no SKILL.md" "Create plugin/skills/$($d.Name)/SKILL.md with name/version/description frontmatter." $d.FullName 0
|
|
89
|
+
continue
|
|
90
|
+
}
|
|
91
|
+
$fm = Get-Frontmatter $skillFile
|
|
92
|
+
foreach ($key in @('name','version','description')) {
|
|
93
|
+
if (-not $fm[$key]) {
|
|
94
|
+
Add-Finding C1 'Frontmatter' 'warning' "Skill `"$($d.Name)`" missing frontmatter key '$key'" "Add `"$key`": `"...`" between the leading --- separators in $skillFile." $skillFile 1
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if ($fm['name'] -and ($fm['name'].ToLower() -ne $d.Name.ToLower())) {
|
|
98
|
+
Add-Finding C2 'Frontmatter' 'warning' "Directory `"$($d.Name)/`" declares name `"$($fm['name'])`"" "Rename one of them so they match." $skillFile 1
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# === C3: every skill referenced in agent ===
|
|
103
|
+
if (Test-Path $agentFile) {
|
|
104
|
+
$agentText = $mdText[$agentFile]
|
|
105
|
+
foreach ($name in $skillNames) {
|
|
106
|
+
$patternBacktick = "``$name``"
|
|
107
|
+
if ($agentText -notmatch [regex]::Escape($name)) {
|
|
108
|
+
Add-Finding C3 'Agent inventory' 'warning' "Skill `"$name`" not mentioned in kushi.agent.md" "Add a row to the routing table: ``| <verb> | $name | <one-line description> |``" $agentFile 0
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
Add-Finding C3 'Agent inventory' 'error' "Agent file missing: $agentFile" "Create plugin/agents/kushi.agent.md as the orchestrator." $agentFile 0
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# === C4: prompts route to real skills ===
|
|
116
|
+
foreach ($p in $promptFiles) {
|
|
117
|
+
$text = $mdText[$p.FullName]
|
|
118
|
+
# Find phrases like "delegates to `name`" or "Delegates to `name` skill"
|
|
119
|
+
$matches2 = [regex]::Matches($text, "(?i)delegates? to ``([a-z0-9-]+)``")
|
|
120
|
+
foreach ($m in $matches2) {
|
|
121
|
+
$referenced = $m.Groups[1].Value
|
|
122
|
+
if ($skillNames -notcontains $referenced) {
|
|
123
|
+
Add-Finding C4 'Prompts' 'warning' "Prompt $($p.Name) references skill `"$referenced`" which doesn't exist" "Either create plugin/skills/$referenced/ or update the prompt to point at an existing skill." $p.FullName 0
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
# === C5: instructions referenced somewhere ===
|
|
129
|
+
foreach ($inst in $instructionFiles) {
|
|
130
|
+
$needle = $inst.Name # e.g. workiq-first.instructions.md
|
|
131
|
+
$referenced = $false
|
|
132
|
+
foreach ($key in $mdText.Keys) {
|
|
133
|
+
if ($key -eq $inst.FullName) { continue }
|
|
134
|
+
if ($mdText[$key] -match [regex]::Escape($needle)) { $referenced = $true; break }
|
|
135
|
+
}
|
|
136
|
+
if (-not $referenced) {
|
|
137
|
+
Add-Finding C5 'Instructions' 'warning' "Instruction `"$needle`" is orphan (not referenced from any SKILL.md or agent.md)" "Either reference it in a relevant skill (e.g. add a `## References` bullet) or delete it." $inst.FullName 0
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
# === C6: cross-links resolve ===
|
|
142
|
+
foreach ($f in $allMd) {
|
|
143
|
+
$text = $mdText[$f.FullName]
|
|
144
|
+
# Strip fenced code blocks first (```...```), then inline code spans (`...`).
|
|
145
|
+
# This keeps regex examples from being treated as real links.
|
|
146
|
+
$stripped = [regex]::Replace($text, '(?s)```.*?```', { param($m) ("`n" * ($m.Value -split "`n").Count) })
|
|
147
|
+
$stripped = [regex]::Replace($stripped, '`[^`\n]+`', '')
|
|
148
|
+
$linkRx = [regex]'\]\(([^)]+)\)'
|
|
149
|
+
$i = 0
|
|
150
|
+
foreach ($line in ($stripped -split "`n")) {
|
|
151
|
+
$i++
|
|
152
|
+
foreach ($m in $linkRx.Matches($line)) {
|
|
153
|
+
$tgt = $m.Groups[1].Value.Trim()
|
|
154
|
+
# Skip absolute URLs, anchors, mailto, angle-bracket placeholders, and template variables.
|
|
155
|
+
if ($tgt -match '^(https?:|mailto:|#|<)' -or $tgt -match '^[a-zA-Z]+:') { continue }
|
|
156
|
+
if ($tgt -match '^\{\{.+\}\}$') { continue } # {{URL}} template placeholders
|
|
157
|
+
if ($tgt -match '\.\.\.') { continue } # path/... example syntax
|
|
158
|
+
# Strip query / fragment.
|
|
159
|
+
$tgt = ($tgt -split '[#?]')[0].Trim()
|
|
160
|
+
if (-not $tgt) { continue }
|
|
161
|
+
$resolved = Join-Path (Split-Path $f.FullName) $tgt
|
|
162
|
+
try { $resolved = [System.IO.Path]::GetFullPath($resolved) } catch { continue }
|
|
163
|
+
if (-not (Test-Path $resolved)) {
|
|
164
|
+
Add-Finding C6 'Cross-links' 'warning' "$($f.Name):$i links to `"$tgt`" which doesn't resolve" "Update or remove the link." $f.FullName $i
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
# === C7: verbs table matches prompts ===
|
|
171
|
+
if (Test-Path $readmeFile) {
|
|
172
|
+
$readme = Get-Content -Raw $readmeFile
|
|
173
|
+
foreach ($p in $promptFiles) {
|
|
174
|
+
$verb = $p.BaseName -replace '\.prompt$',''
|
|
175
|
+
if ($readme -notmatch "``$verb``") {
|
|
176
|
+
Add-Finding C7 'Verbs (README)' 'warning' "Verb `"$verb`" has a prompt file but no row in README" "Add to the verbs table in README.md: ``| ``$verb`` | <window> | <description> |``" $readmeFile 0
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
Add-Finding C7 'Verbs (README)' 'warning' "README.md not found at $readmeFile" "Create README.md and document the verbs surface." $readmeFile 0
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
# === C8: docs/reference/where-things-live.md references all top-level plugin items ===
|
|
184
|
+
if (Test-Path $wtlFile) {
|
|
185
|
+
$wtl = Get-Content -Raw $wtlFile
|
|
186
|
+
foreach ($child in (Get-ChildItem $pluginDir)) {
|
|
187
|
+
if ($wtl -notmatch [regex]::Escape($child.Name)) {
|
|
188
|
+
Add-Finding C8 'Layout (WTL)' 'warning' "plugin/$($child.Name) is missing from docs/reference/where-things-live.md tree" "Add it under '## 1. This repo'." $wtlFile 0
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
# === C9: plugin.json profiles block is well-formed and references real assets ===
|
|
194
|
+
$pluginJsonFile = Join-Path $pluginDir 'plugin.json'
|
|
195
|
+
if (Test-Path $pluginJsonFile) {
|
|
196
|
+
try {
|
|
197
|
+
$pj = Get-Content -Raw $pluginJsonFile | ConvertFrom-Json
|
|
198
|
+
if (-not $pj.profiles) {
|
|
199
|
+
Add-Finding C9 'Profiles' 'error' 'plugin.json has no profiles block' "Add a 'profiles' block with at least core/standard/full." $pluginJsonFile 0
|
|
200
|
+
} else {
|
|
201
|
+
$profileNames = @($pj.profiles.PSObject.Properties.Name)
|
|
202
|
+
$defaultProfile = $pj.default_profile
|
|
203
|
+
if (-not $defaultProfile) {
|
|
204
|
+
Add-Finding C9 'Profiles' 'warning' "plugin.json missing 'default_profile'" "Set 'default_profile' to one of: $($profileNames -join ', ')." $pluginJsonFile 0
|
|
205
|
+
} elseif ($profileNames -notcontains $defaultProfile) {
|
|
206
|
+
Add-Finding C9 'Profiles' 'error' "default_profile '$defaultProfile' is not declared in profiles{}" "Use one of: $($profileNames -join ', ')." $pluginJsonFile 0
|
|
207
|
+
}
|
|
208
|
+
# Resolve each profile chain and verify every referenced skill/prompt/instruction exists
|
|
209
|
+
function Resolve-ProfileChain($name, $seen) {
|
|
210
|
+
if ($seen -contains $name) { return @() }
|
|
211
|
+
$p = $pj.profiles.$name
|
|
212
|
+
if (-not $p) { return @() }
|
|
213
|
+
$chain = @()
|
|
214
|
+
if ($p.extends) { $chain += Resolve-ProfileChain $p.extends ($seen + $name) }
|
|
215
|
+
$chain += $name
|
|
216
|
+
return $chain
|
|
217
|
+
}
|
|
218
|
+
$skillNames = (Get-ChildItem $skillsDir -Directory).Name
|
|
219
|
+
$promptStems = (Get-ChildItem (Join-Path $pluginDir 'prompts') -File -Filter '*.prompt.md' -ErrorAction SilentlyContinue) `
|
|
220
|
+
| ForEach-Object { $_.Name -replace '\.prompt\.md$','' }
|
|
221
|
+
$instructionStems = (Get-ChildItem (Join-Path $pluginDir 'instructions') -File -Filter '*.instructions.md' -ErrorAction SilentlyContinue) `
|
|
222
|
+
| ForEach-Object { $_.Name -replace '\.instructions\.md$','' }
|
|
223
|
+
foreach ($pn in $profileNames) {
|
|
224
|
+
$chain = Resolve-ProfileChain $pn @()
|
|
225
|
+
$skills = New-Object System.Collections.Generic.HashSet[string]
|
|
226
|
+
$prompts = New-Object System.Collections.Generic.HashSet[string]
|
|
227
|
+
$instr = $null
|
|
228
|
+
foreach ($link in $chain) {
|
|
229
|
+
$node = $pj.profiles.$link
|
|
230
|
+
if ($node.skills) { foreach ($s in $node.skills) { $null = $skills.Add($s) } }
|
|
231
|
+
if ($node.prompts) { foreach ($s in $node.prompts) { $null = $prompts.Add($s) } }
|
|
232
|
+
if ($node.instructions) { $instr = $node.instructions }
|
|
233
|
+
}
|
|
234
|
+
foreach ($s in $skills) {
|
|
235
|
+
if ($skillNames -notcontains $s) {
|
|
236
|
+
Add-Finding C9 'Profiles' 'warning' "Profile '$pn' references skill '$s' which doesn't exist under plugin/skills/" "Create plugin/skills/$s/ or remove it from the profile." $pluginJsonFile 0
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
foreach ($s in $prompts) {
|
|
240
|
+
if ($promptStems -notcontains $s) {
|
|
241
|
+
Add-Finding C9 'Profiles' 'warning' "Profile '$pn' references prompt '$s.prompt.md' which doesn't exist" "Create plugin/prompts/$s.prompt.md or remove it from the profile." $pluginJsonFile 0
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if ($instr -and $instr -ne '*') {
|
|
245
|
+
foreach ($s in $instr) {
|
|
246
|
+
if ($instructionStems -notcontains $s) {
|
|
247
|
+
Add-Finding C9 'Profiles' 'warning' "Profile '$pn' references instruction '$s.instructions.md' which doesn't exist" "Create plugin/instructions/$s.instructions.md or remove it from the profile." $pluginJsonFile 0
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
# === C10: reference packs referenced by any profile must exist and have a README.md ===
|
|
255
|
+
$refPacksDir = Join-Path $pluginDir 'reference-packs'
|
|
256
|
+
$profileNames = @($pj.profiles.PSObject.Properties.Name)
|
|
257
|
+
foreach ($pn in $profileNames) {
|
|
258
|
+
$packs = $pj.profiles.$pn.reference_packs
|
|
259
|
+
if ($null -eq $packs) { continue }
|
|
260
|
+
foreach ($pack in $packs) {
|
|
261
|
+
$packDir = Join-Path $refPacksDir $pack
|
|
262
|
+
if (-not (Test-Path $packDir)) {
|
|
263
|
+
Add-Finding C10 'Reference packs' 'warning' "Profile '$pn' references reference pack '$pack' which doesn't exist" "Create plugin/reference-packs/$pack/ with a README.md, or remove the pack from the profile." $pluginJsonFile 0
|
|
264
|
+
continue
|
|
265
|
+
}
|
|
266
|
+
$readme = Join-Path $packDir 'README.md'
|
|
267
|
+
if (-not (Test-Path $readme)) {
|
|
268
|
+
Add-Finding C10 'Reference packs' 'warning' "Reference pack '$pack' is missing a README.md" "Create plugin/reference-packs/$pack/README.md documenting pack scope, consumers, read order, and conflict-resolution." $packDir 0
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
# Also: every folder UNDER reference-packs/ should have a README.md, even if not yet wired into a profile.
|
|
274
|
+
if (Test-Path $refPacksDir) {
|
|
275
|
+
$packDirs = Get-ChildItem -Path $refPacksDir -Directory -ErrorAction SilentlyContinue
|
|
276
|
+
foreach ($pd in $packDirs) {
|
|
277
|
+
$readme = Join-Path $pd.FullName 'README.md'
|
|
278
|
+
if (-not (Test-Path $readme)) {
|
|
279
|
+
Add-Finding C10 'Reference packs' 'warning' "Reference pack folder '$($pd.Name)' has no README.md" "Create plugin/reference-packs/$($pd.Name)/README.md documenting pack scope, consumers, read order, and conflict-resolution." $pd.FullName 0
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
} catch {
|
|
284
|
+
Add-Finding C9 'Profiles' 'error' "Failed to parse plugin.json: $_" "Validate JSON syntax." $pluginJsonFile 0
|
|
285
|
+
}
|
|
286
|
+
} else {
|
|
287
|
+
Add-Finding C9 'Profiles' 'error' "plugin.json not found at $pluginJsonFile" "Create plugin/plugin.json with a 'profiles' block." $pluginJsonFile 0
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
# === C11: FDE skills must cite at least one reference-pack file in SKILL.md ===
|
|
291
|
+
$fdeSkills = @('fde-intake','fde-report','fde-triage')
|
|
292
|
+
foreach ($name in $fdeSkills) {
|
|
293
|
+
$f = Join-Path $skillsDir "$name\SKILL.md"
|
|
294
|
+
if (Test-Path $f) {
|
|
295
|
+
$text = Get-Content -Raw $f
|
|
296
|
+
if ($text -notmatch 'reference-packs/fde/[a-zA-Z0-9_.-]+\.md') {
|
|
297
|
+
Add-Finding C11 'FDE skills' 'warning' "Skill $name doesn't cite any reference-packs/fde/*.md file" "FDE skills must ground in the FDE reference pack — cite report-doctrine.md / intake-questions.md / core-fde-reference.md as appropriate." $f 0
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
# === C12: pull-* skills must reference thoroughness-detector + evidence templates carry Validation block ===
|
|
303
|
+
foreach ($d in $skillDirs | Where-Object { $_.Name -like 'pull-*' }) {
|
|
304
|
+
$f = Join-Path $d.FullName 'SKILL.md'
|
|
305
|
+
$text = $mdText[$f]
|
|
306
|
+
if (-not $text) { continue }
|
|
307
|
+
if ($text -notmatch 'thoroughness-detector\.instructions\.md') {
|
|
308
|
+
Add-Finding C12 'Thoroughness enforcement' 'warning' "Skill $($d.Name) doesn't reference thoroughness-detector.instructions.md" "Add 'runtime detector + auto-retry + paste-prompt per ``thoroughness-detector.instructions.md``' to the skill intro." $f 0
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
$evidenceTemplateDirs = @('templates\weekly','templates\snapshot')
|
|
312
|
+
$nonEvidenceTemplates = @('consolidated.template.md','run-log.template.md')
|
|
313
|
+
foreach ($td in $evidenceTemplateDirs) {
|
|
314
|
+
$dir = Join-Path $pluginDir $td
|
|
315
|
+
if (-not (Test-Path $dir)) { continue }
|
|
316
|
+
Get-ChildItem $dir -Filter '*.template.md' -ErrorAction SilentlyContinue | ForEach-Object {
|
|
317
|
+
if ($nonEvidenceTemplates -contains $_.Name) { return }
|
|
318
|
+
$tx = Get-Content -Raw $_.FullName
|
|
319
|
+
if ($tx -notmatch '##\s+Validation') {
|
|
320
|
+
Add-Finding C12 'Thoroughness enforcement' 'warning' "Evidence template $($_.Name) is missing '## Validation' block" "Append a Validation checklist per thoroughness-detector.instructions.md so the skill can tick checks on write." $_.FullName 0
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
$pasteTpl = Join-Path $pluginDir 'templates\paste-prompt.md'
|
|
325
|
+
if (-not (Test-Path $pasteTpl)) {
|
|
326
|
+
Add-Finding C12 'Thoroughness enforcement' 'warning' "plugin/templates/paste-prompt.md is missing" "Create the paste-prompt template referenced by thoroughness-detector.instructions.md." $pluginDir 0
|
|
327
|
+
}
|
|
328
|
+
$detectorFile = Join-Path $pluginDir 'instructions\thoroughness-detector.instructions.md'
|
|
329
|
+
if (-not (Test-Path $detectorFile)) {
|
|
330
|
+
Add-Finding C12 'Thoroughness enforcement' 'warning' "plugin/instructions/thoroughness-detector.instructions.md is missing" "Create the detector instructions file — pull-* skills cite it." $pluginDir 0
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
# === Deep checks ===
|
|
334
|
+
if ($Deep) {
|
|
335
|
+
# D1: templates referenced from skills exist
|
|
336
|
+
$templatesDir = Join-Path $pluginDir 'templates'
|
|
337
|
+
foreach ($d in $skillDirs) {
|
|
338
|
+
$f = Join-Path $d.FullName 'SKILL.md'
|
|
339
|
+
$text = $mdText[$f]
|
|
340
|
+
if (-not $text) { continue }
|
|
341
|
+
foreach ($m in [regex]::Matches($text, '``templates/([a-zA-Z0-9_./-]+\.template\.[a-z]+)``')) {
|
|
342
|
+
$rel = $m.Groups[1].Value
|
|
343
|
+
$abs = Join-Path $templatesDir $rel
|
|
344
|
+
if (-not (Test-Path $abs)) {
|
|
345
|
+
Add-Finding D1 'Templates' 'warning' "Skill $($d.Name) references templates/$rel which doesn't exist" "Create the template or update the path." $f 0
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
# D2: pull-* skills must reference snapshot-vs-stream
|
|
350
|
+
foreach ($d in $skillDirs | Where-Object { $_.Name -like 'pull-*' }) {
|
|
351
|
+
$f = Join-Path $d.FullName 'SKILL.md'
|
|
352
|
+
$text = $mdText[$f]
|
|
353
|
+
if ($text -notmatch 'snapshot-vs-stream\.instructions\.md') {
|
|
354
|
+
Add-Finding D2 'Snapshot/Stream' 'warning' "Skill $($d.Name) doesn't cite snapshot-vs-stream.instructions.md" "Add a reference (e.g. 'per snapshot-vs-stream.instructions.md') in the skill body." $f 0
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
# D3: pull-* must put WorkIQ first
|
|
358
|
+
foreach ($d in $skillDirs | Where-Object { $_.Name -like 'pull-*' }) {
|
|
359
|
+
$f = Join-Path $d.FullName 'SKILL.md'
|
|
360
|
+
$text = $mdText[$f]
|
|
361
|
+
if ($text -notmatch '(?ms)1\.\s*\*\*WorkIQ\*\*') {
|
|
362
|
+
Add-Finding D3 'WorkIQ-first' 'warning' "Skill $($d.Name) doesn't list **WorkIQ** as tool #1" "Restructure the Tools section so WorkIQ is item 1." $f 0
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
# D4: live install SKILL.md = agent file
|
|
366
|
+
$liveSkill = Join-Path $env:USERPROFILE '.copilot\m-skills\kushi\SKILL.md'
|
|
367
|
+
if (Test-Path $liveSkill) {
|
|
368
|
+
$a = Get-FileHash $liveSkill -Algorithm SHA256
|
|
369
|
+
$b = Get-FileHash $agentFile -Algorithm SHA256
|
|
370
|
+
if ($a.Hash -ne $b.Hash) {
|
|
371
|
+
Add-Finding D4 'Live install sync' 'warning' "Live SKILL.md hash differs from plugin/agents/kushi.agent.md" "Re-run installer: ``node bin/cli.mjs --clawpilot --force``" $liveSkill 0
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
# D5: skills-metadata description matches frontmatter
|
|
375
|
+
$metaPath = Join-Path $env:USERPROFILE '.copilot\m-skills\skills-metadata.json'
|
|
376
|
+
if (Test-Path $metaPath) {
|
|
377
|
+
try {
|
|
378
|
+
$meta = Get-Content -Raw $metaPath | ConvertFrom-Json
|
|
379
|
+
$kushiMeta = $meta | Where-Object { $_.name -eq 'kushi' } | Select-Object -First 1
|
|
380
|
+
if ($kushiMeta) {
|
|
381
|
+
$fmAgent = Get-Frontmatter $agentFile
|
|
382
|
+
if ($fmAgent['description'] -and $kushiMeta.description -ne $fmAgent['description']) {
|
|
383
|
+
Add-Finding D5 'Live install sync' 'warning' "skills-metadata.json kushi description drifted from agent frontmatter" "Re-run installer or sync the description manually." $metaPath 0
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
} catch {
|
|
387
|
+
Add-Finding D5 'Live install sync' 'warning' "Failed to parse skills-metadata.json: $_" "Validate JSON syntax." $metaPath 0
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
# D6: side-by-side rule cited where user config is touched
|
|
391
|
+
$configTouchers = @('bootstrap-project','refresh-project') # heuristic
|
|
392
|
+
foreach ($name in $configTouchers) {
|
|
393
|
+
$f = Join-Path $skillsDir "$name\SKILL.md"
|
|
394
|
+
if (Test-Path $f) {
|
|
395
|
+
$text = $mdText[$f]
|
|
396
|
+
if ($text -notmatch 'side-by-side-config\.instructions\.md') {
|
|
397
|
+
Add-Finding D6 'Instructions' 'warning' "Skill $name doesn't cite side-by-side-config.instructions.md" "Add a reference; this skill writes user-visible config files." $f 0
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
# D7: live install kushi-install.json matches plugin.json profile spec
|
|
402
|
+
$liveManifest = Join-Path $env:USERPROFILE '.copilot\m-skills\kushi\kushi-install.json'
|
|
403
|
+
if (Test-Path $liveManifest) {
|
|
404
|
+
try {
|
|
405
|
+
$live = Get-Content -Raw $liveManifest | ConvertFrom-Json
|
|
406
|
+
$pluginJsonFile2 = Join-Path $pluginDir 'plugin.json'
|
|
407
|
+
$pj2 = Get-Content -Raw $pluginJsonFile2 | ConvertFrom-Json
|
|
408
|
+
$liveProfile = $live.profile
|
|
409
|
+
if ($pj2.profiles.$liveProfile) {
|
|
410
|
+
# Re-resolve chain
|
|
411
|
+
function Resolve-ChainD7($name, $seen) {
|
|
412
|
+
if ($seen -contains $name) { return @() }
|
|
413
|
+
$p = $pj2.profiles.$name
|
|
414
|
+
if (-not $p) { return @() }
|
|
415
|
+
$chain = @()
|
|
416
|
+
if ($p.extends) { $chain += Resolve-ChainD7 $p.extends ($seen + $name) }
|
|
417
|
+
$chain += $name
|
|
418
|
+
return $chain
|
|
419
|
+
}
|
|
420
|
+
$chain = Resolve-ChainD7 $liveProfile @()
|
|
421
|
+
$expectedSkills = New-Object System.Collections.Generic.HashSet[string]
|
|
422
|
+
foreach ($link in $chain) {
|
|
423
|
+
$node = $pj2.profiles.$link
|
|
424
|
+
if ($node.skills) { foreach ($s in $node.skills) { $null = $expectedSkills.Add($s) } }
|
|
425
|
+
}
|
|
426
|
+
$missing = @($expectedSkills | Where-Object { $live.skills -notcontains $_ })
|
|
427
|
+
$extra = @($live.skills | Where-Object { -not $expectedSkills.Contains($_) })
|
|
428
|
+
if ($missing.Count -gt 0 -or $extra.Count -gt 0) {
|
|
429
|
+
Add-Finding D7 'Live install manifest' 'warning' "Live kushi-install.json (profile '$liveProfile') drifted from plugin.json — missing: [$($missing -join ', ')]; extra: [$($extra -join ', ')]" "Re-run installer: ``node bin/cli.mjs --clawpilot --profile $liveProfile --force``" $liveManifest 0
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
# Reference packs drift check
|
|
433
|
+
$expectedPacks = New-Object System.Collections.Generic.HashSet[string]
|
|
434
|
+
foreach ($link in $chain) {
|
|
435
|
+
$node = $pj2.profiles.$link
|
|
436
|
+
if ($node.reference_packs) { foreach ($s in $node.reference_packs) { $null = $expectedPacks.Add($s) } }
|
|
437
|
+
}
|
|
438
|
+
$livePacks = @()
|
|
439
|
+
if ($live.PSObject.Properties.Name -contains 'reference_packs') { $livePacks = @($live.reference_packs) }
|
|
440
|
+
$packMissing = @($expectedPacks | Where-Object { $livePacks -notcontains $_ })
|
|
441
|
+
$packExtra = @($livePacks | Where-Object { -not $expectedPacks.Contains($_) })
|
|
442
|
+
if ($packMissing.Count -gt 0 -or $packExtra.Count -gt 0) {
|
|
443
|
+
Add-Finding D7 'Live install manifest' 'warning' "Live kushi-install.json reference_packs (profile '$liveProfile') drifted from plugin.json — missing: [$($packMissing -join ', ')]; extra: [$($packExtra -join ', ')]" "Re-run installer: ``node bin/cli.mjs --clawpilot --profile $liveProfile --force``" $liveManifest 0
|
|
444
|
+
}
|
|
445
|
+
} else {
|
|
446
|
+
Add-Finding D7 'Live install manifest' 'warning' "Live install profile '$liveProfile' not declared in plugin.json" "Re-install with a known profile (core/standard/full)." $liveManifest 0
|
|
447
|
+
}
|
|
448
|
+
} catch {
|
|
449
|
+
Add-Finding D7 'Live install manifest' 'warning' "Failed to parse live kushi-install.json: $_" "Re-run installer to rewrite the manifest." $liveManifest 0
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
# D8: live templates/fde/ matches repo
|
|
454
|
+
$liveFdeTpl = Join-Path $env:USERPROFILE '.copilot\m-skills\kushi\templates\fde'
|
|
455
|
+
$repoFdeTpl = Join-Path $pluginDir 'templates\fde'
|
|
456
|
+
if ((Test-Path $liveFdeTpl) -and (Test-Path $repoFdeTpl)) {
|
|
457
|
+
$liveFiles = Get-ChildItem -Path $liveFdeTpl -File -Filter '*.md' -ErrorAction SilentlyContinue
|
|
458
|
+
$repoFiles = Get-ChildItem -Path $repoFdeTpl -File -Filter '*.md' -ErrorAction SilentlyContinue
|
|
459
|
+
$liveNames = @($liveFiles | ForEach-Object { $_.Name })
|
|
460
|
+
$repoNames = @($repoFiles | ForEach-Object { $_.Name })
|
|
461
|
+
$missing = @($repoNames | Where-Object { $liveNames -notcontains $_ })
|
|
462
|
+
$extra = @($liveNames | Where-Object { $repoNames -notcontains $_ })
|
|
463
|
+
if ($missing.Count -gt 0 -or $extra.Count -gt 0) {
|
|
464
|
+
Add-Finding D8 'Live templates' 'warning' "Live templates/fde/ drifted from repo — missing: [$($missing -join ', ')]; extra: [$($extra -join ', ')]" "Re-run installer to sync FDE templates." $liveFdeTpl 0
|
|
465
|
+
} else {
|
|
466
|
+
foreach ($rf in $repoFiles) {
|
|
467
|
+
$lf = Join-Path $liveFdeTpl $rf.Name
|
|
468
|
+
$rh = (Get-FileHash $rf.FullName -Algorithm SHA256).Hash
|
|
469
|
+
$lh = (Get-FileHash $lf -Algorithm SHA256).Hash
|
|
470
|
+
if ($rh -ne $lh) {
|
|
471
|
+
Add-Finding D8 'Live templates' 'warning' "FDE template $($rf.Name) hash differs between repo and live install" "Re-run installer to sync." $lf 0
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
# === Output ===
|
|
479
|
+
if ($Json) {
|
|
480
|
+
$findings | ConvertTo-Json -Depth 6
|
|
481
|
+
} else {
|
|
482
|
+
$version = (Get-Frontmatter $agentFile).version
|
|
483
|
+
if (-not $version) {
|
|
484
|
+
try { $version = (Get-Content -Raw (Join-Path $Root 'package.json') | ConvertFrom-Json).version } catch {}
|
|
485
|
+
}
|
|
486
|
+
$stamp = Get-Date -Format 'yyyy-MM-dd HH:mm'
|
|
487
|
+
Write-Host ""
|
|
488
|
+
Write-Host "### Self-Check Results — Kushi v$version @ $stamp"
|
|
489
|
+
Write-Host ""
|
|
490
|
+
|
|
491
|
+
$bySurface = $findings | Group-Object surface
|
|
492
|
+
if ($findings.Count -eq 0) {
|
|
493
|
+
Write-Host "✅ All checks passed. Safe to commit."
|
|
494
|
+
} else {
|
|
495
|
+
$coreCount = ($findings | Where-Object { $_.code -notlike 'D*' }).Count
|
|
496
|
+
$deepCount = ($findings | Where-Object { $_.code -like 'D*' }).Count
|
|
497
|
+
Write-Host "| Surface | Count |"
|
|
498
|
+
Write-Host "|---------|------:|"
|
|
499
|
+
foreach ($g in $bySurface | Sort-Object Name) {
|
|
500
|
+
Write-Host ("| {0} | {1} |" -f $g.Name, $g.Count)
|
|
501
|
+
}
|
|
502
|
+
Write-Host ""
|
|
503
|
+
Write-Host "Findings:"
|
|
504
|
+
Write-Host ""
|
|
505
|
+
foreach ($x in $findings) {
|
|
506
|
+
$loc = if ($x.line) { ":$($x.line)" } else { "" }
|
|
507
|
+
Write-Host "⚠️ [$($x.code)] $($x.message)"
|
|
508
|
+
Write-Host " file: $($x.file)$loc"
|
|
509
|
+
Write-Host " fix : $($x.fix)"
|
|
510
|
+
Write-Host ""
|
|
511
|
+
}
|
|
512
|
+
Write-Host "⚠️ Found $($findings.Count) gap(s) — $coreCount core, $deepCount deep. Review and update before committing."
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if ($StrictExit -and $findings.Count -gt 0) { exit 1 }
|
|
517
|
+
exit 0
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Cross-OS wrapper for run.ps1.
|
|
3
|
+
# pwsh 7+ runs the same script unchanged on macOS / Linux / WSL / Windows.
|
|
4
|
+
#
|
|
5
|
+
# Why a wrapper instead of a native bash port?
|
|
6
|
+
# The check logic relies on regex over markdown + YAML frontmatter parsing
|
|
7
|
+
# + SHA256 hashing. PowerShell 7 has all of this in stdlib and is officially
|
|
8
|
+
# supported on macOS and Linux. A bash port would double the maintenance
|
|
9
|
+
# burden and drift over time. self-check D-checks already verify the .ps1
|
|
10
|
+
# stays consistent with the doctrine in SKILL.md.
|
|
11
|
+
#
|
|
12
|
+
# Install pwsh on macOS: brew install --cask powershell
|
|
13
|
+
# Install pwsh on Ubuntu: https://learn.microsoft.com/powershell/scripting/install/install-ubuntu
|
|
14
|
+
#
|
|
15
|
+
# Usage (matches run.ps1 exactly):
|
|
16
|
+
# ./run.sh # core checks, human output
|
|
17
|
+
# ./run.sh -Deep # core + deep checks
|
|
18
|
+
# ./run.sh -Json # JSON output (empty array = no findings)
|
|
19
|
+
# ./run.sh -Deep -StrictExit # CI-friendly: exit 1 on any finding
|
|
20
|
+
# ./run.sh -Root /path/to/kushi # check a specific kushi root
|
|
21
|
+
|
|
22
|
+
set -euo pipefail
|
|
23
|
+
|
|
24
|
+
if ! command -v pwsh >/dev/null 2>&1; then
|
|
25
|
+
echo "Error: pwsh (PowerShell 7+) is required." >&2
|
|
26
|
+
echo " macOS: brew install --cask powershell" >&2
|
|
27
|
+
echo " Ubuntu: see https://learn.microsoft.com/powershell/scripting/install/install-ubuntu" >&2
|
|
28
|
+
echo " Other: https://github.com/PowerShell/PowerShell/releases" >&2
|
|
29
|
+
exit 127
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
33
|
+
exec pwsh -NoProfile -File "${SCRIPT_DIR}/run.ps1" "$@"
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# FDE Intake — {{customer_name}} · {{engagement_title}} ({{division_or_team}})
|
|
2
|
+
|
|
3
|
+
**MSX Opportunity:** `{{msx_opportunity_id}}`
|
|
4
|
+
|
|
5
|
+
> **Document type:** FDE Intake (first artifact in this engagement)
|
|
6
|
+
> **Last updated:** {{generated_at}}
|
|
7
|
+
> **Generated by:** Kushi `fde-intake` v{{kushi_version}}
|
|
8
|
+
|
|
9
|
+
{{freshness_warning_block}}
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Q1 — Who you are currently engaged with or have engaged with
|
|
14
|
+
|
|
15
|
+
{{q1_stakeholders}}
|
|
16
|
+
|
|
17
|
+
> Required: ATU representation, Industry Team representation.
|
|
18
|
+
> Optional but explicitly stated: ISE, ISD, Executive Sponsor, Product Group.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Q2 — Business scenario or technical blocker FDE would solve
|
|
23
|
+
|
|
24
|
+
{{q2_scenario}}
|
|
25
|
+
|
|
26
|
+
**Quantified business impact:** {{q2_impact_dollars}}
|
|
27
|
+
|
|
28
|
+
**Outcome chain (technical intervention → KPI → business outcome):**
|
|
29
|
+
|
|
30
|
+
- {{q2_chain_1}}
|
|
31
|
+
- {{q2_chain_2}}
|
|
32
|
+
- {{q2_chain_3}}
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Q3 — Why current offers / solutions are not suitable
|
|
37
|
+
|
|
38
|
+
{{q3_gaps}}
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Q4 — What does success look like for the customer
|
|
43
|
+
|
|
44
|
+
### Outcome hypothesis
|
|
45
|
+
|
|
46
|
+
> *"If we {{q4_intervention}}, then {{q4_kpi}} will improve by {{q4_target}} within {{q4_timeframe}}, as measured by {{q4_method}} against {{q4_baseline}}."*
|
|
47
|
+
|
|
48
|
+
### Quantified success criteria (≥ 2 KPIs)
|
|
49
|
+
|
|
50
|
+
| KPI | Baseline | Target | Timeframe | Measurement method |
|
|
51
|
+
|---|---|---|---|---|
|
|
52
|
+
| {{kpi1_name}} | {{kpi1_baseline}} | {{kpi1_target}} | {{kpi1_timeframe}} | {{kpi1_method}} |
|
|
53
|
+
| {{kpi2_name}} | {{kpi2_baseline}} | {{kpi2_target}} | {{kpi2_timeframe}} | {{kpi2_method}} |
|
|
54
|
+
|
|
55
|
+
### Baseline
|
|
56
|
+
|
|
57
|
+
{{q4_baseline_detail}}
|
|
58
|
+
|
|
59
|
+
### ROI framing
|
|
60
|
+
|
|
61
|
+
> {{q4_roi_statement}}
|
|
62
|
+
|
|
63
|
+
### Value realization plan
|
|
64
|
+
|
|
65
|
+
{{q4_value_realization}}
|
|
66
|
+
|
|
67
|
+
### Catcher team
|
|
68
|
+
|
|
69
|
+
{{q4_catcher_team}}
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Q5 — Relevant technology workloads
|
|
74
|
+
|
|
75
|
+
{{q5_technologies}}
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Q6 — Work completed so far
|
|
80
|
+
|
|
81
|
+
{{q6_completed_work}}
|
|
82
|
+
|
|
83
|
+
### Commercial posture
|
|
84
|
+
|
|
85
|
+
| Item | Value | Source |
|
|
86
|
+
|---|---|---|
|
|
87
|
+
| Current yearly ACR | {{q6_acr_current}} | {{q6_acr_source}} |
|
|
88
|
+
| ACR growth trajectory | {{q6_acr_growth}} | {{q6_acr_growth_source}} |
|
|
89
|
+
| Expected program ACR (if successful) | {{q6_acr_expected}} | {{q6_acr_expected_source}} |
|
|
90
|
+
| MACC in place | {{q6_macc_in_place}} | {{q6_macc_source}} |
|
|
91
|
+
| MACC pending | {{q6_macc_pending}} | {{q6_macc_pending_source}} |
|
|
92
|
+
| Customer ROI (their own) | {{q6_customer_roi}} | {{q6_customer_roi_source}} |
|
|
93
|
+
|
|
94
|
+
### Customer readiness
|
|
95
|
+
|
|
96
|
+
{{q6_customer_readiness}}
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Validation warnings (open)
|
|
101
|
+
|
|
102
|
+
{{validation_warnings_block}}
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Source coverage
|
|
107
|
+
|
|
108
|
+
| Source | Status |
|
|
109
|
+
|---|---|
|
|
110
|
+
{{source_coverage_rows}}
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
> _This document was generated by Kushi (`fde-intake`) from `<project>/Evidence/` and the FDE reference pack. Every assertion carries an inline `[source: …]` citation. Edit in place as the engagement evolves — Kushi will preserve manual edits on re-run when the `--merge` flag is supplied._
|