@canonical/pragma-cli 0.35.0 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/bundled-skills/add-standard/SKILL.md +338 -0
- package/bundled-skills/adoption-a1-styles/SKILL.md +269 -0
- package/bundled-skills/adoption-a2-components/SKILL.md +291 -0
- package/bundled-skills/adoption-a3-forms/SKILL.md +260 -0
- package/bundled-skills/anatomy-author/ANATOMY_DSL_SPEC.md +406 -0
- package/bundled-skills/anatomy-author/SKILL.md +1226 -0
- package/bundled-skills/design-auditor/SKILL.md +455 -0
- package/bundled-skills/implement-component/SKILL.md +333 -0
- package/bundled-skills/specify-component/SKILL.md +331 -0
- package/bundled-skills/specify-pattern/SKILL.md +292 -0
- package/dist/package.json +24 -20
- package/dist/pragma.conf.js +266 -34
- package/dist/pragma.conf.js.map +1 -1
- package/dist/src/bin.js +1 -1
- package/dist/src/bin.js.map +1 -1
- package/dist/src/capabilities/capabilities/hints.js +1 -1
- package/dist/src/capabilities/capabilities/hints.js.map +1 -1
- package/dist/src/capabilities/colophon/colophon.render.js +1 -1
- package/dist/src/capabilities/colophon/colophon.render.js.map +1 -1
- package/dist/src/capabilities/create/create.verb.js +2 -1
- package/dist/src/capabilities/create/create.verb.js.map +1 -1
- package/dist/src/capabilities/create/createSurface.generated.js +23 -1
- package/dist/src/capabilities/create/createSurface.generated.js.map +1 -1
- package/dist/src/capabilities/create/mount.js +4 -3
- package/dist/src/capabilities/create/mount.js.map +1 -1
- package/dist/src/capabilities/doctor/checks/checkNodeVersion.js +11 -6
- package/dist/src/capabilities/doctor/checks/checkNodeVersion.js.map +1 -1
- package/dist/src/capabilities/doctor/checks/checkPackageRefs.js +60 -4
- package/dist/src/capabilities/doctor/checks/checkPackageRefs.js.map +1 -1
- package/dist/src/capabilities/doctor/checks/checkShellCompletions.js +8 -8
- package/dist/src/capabilities/doctor/checks/checkShellCompletions.js.map +1 -1
- package/dist/src/capabilities/doctor/checks/harnessInventory.js +195 -0
- package/dist/src/capabilities/doctor/checks/harnessInventory.js.map +1 -0
- package/dist/src/capabilities/doctor/checks/index.js +6 -6
- package/dist/src/capabilities/doctor/checks/isSupportedNodeVersion.js +38 -0
- package/dist/src/capabilities/doctor/checks/isSupportedNodeVersion.js.map +1 -0
- package/dist/src/capabilities/doctor/checks/targetHealth.js +194 -50
- package/dist/src/capabilities/doctor/checks/targetHealth.js.map +1 -1
- package/dist/src/capabilities/doctor/doctor.render.js +43 -64
- package/dist/src/capabilities/doctor/doctor.render.js.map +1 -1
- package/dist/src/capabilities/doctor/doctor.verb.js +6 -2
- package/dist/src/capabilities/doctor/doctor.verb.js.map +1 -1
- package/dist/src/capabilities/doctor/runChecks.js +14 -11
- package/dist/src/capabilities/doctor/runChecks.js.map +1 -1
- package/dist/src/capabilities/graph/query.render.js +2 -2
- package/dist/src/capabilities/graph/query.render.js.map +1 -1
- package/dist/src/capabilities/info/collectInfo.js +8 -2
- package/dist/src/capabilities/info/collectInfo.js.map +1 -1
- package/dist/src/capabilities/info/info.render.js +6 -2
- package/dist/src/capabilities/info/info.render.js.map +1 -1
- package/dist/src/capabilities/prompt/prompt.render.js +2 -2
- package/dist/src/capabilities/prompt/prompt.render.js.map +1 -1
- package/dist/src/capabilities/setup/buildPlan.js +43 -39
- package/dist/src/capabilities/setup/buildPlan.js.map +1 -1
- package/dist/src/capabilities/setup/mode.js +2 -2
- package/dist/src/capabilities/setup/operations/index.js +4 -4
- package/dist/src/capabilities/setup/operations/index.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupCompletions.js +6 -2
- package/dist/src/capabilities/setup/operations/setupCompletions.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupConfig.js +4 -2
- package/dist/src/capabilities/setup/operations/setupConfig.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupGenerator.js +156 -58
- package/dist/src/capabilities/setup/operations/setupGenerator.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupLsp.js +136 -38
- package/dist/src/capabilities/setup/operations/setupLsp.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupMcp.js +101 -52
- package/dist/src/capabilities/setup/operations/setupMcp.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupSkills.js +329 -49
- package/dist/src/capabilities/setup/operations/setupSkills.js.map +1 -1
- package/dist/src/capabilities/setup/plan.js.map +1 -1
- package/dist/src/capabilities/setup/plan.render.js +144 -66
- package/dist/src/capabilities/setup/plan.render.js.map +1 -1
- package/dist/src/capabilities/setup/setup.render.js +1 -1
- package/dist/src/capabilities/setup/setup.verb.js +165 -63
- package/dist/src/capabilities/setup/setup.verb.js.map +1 -1
- package/dist/src/capabilities/setup/targets.js +122 -51
- package/dist/src/capabilities/setup/targets.js.map +1 -1
- package/dist/src/capabilities/shared/index.js +7 -6
- package/dist/src/capabilities/shared/index.js.map +1 -1
- package/dist/src/capabilities/shared/packageManager.js +300 -54
- package/dist/src/capabilities/shared/packageManager.js.map +1 -1
- package/dist/src/capabilities/skill/discover.js +97 -11
- package/dist/src/capabilities/skill/discover.js.map +1 -1
- package/dist/src/capabilities/skill/render.js +2 -2
- package/dist/src/capabilities/skill/render.js.map +1 -1
- package/dist/src/capabilities/sources/installSkills.js +145 -3
- package/dist/src/capabilities/sources/installSkills.js.map +1 -1
- package/dist/src/capabilities/sources/runUpdate.js +68 -2
- package/dist/src/capabilities/sources/runUpdate.js.map +1 -1
- package/dist/src/capabilities/sources/update.verb.js +3 -3
- package/dist/src/capabilities/sources/update.verb.js.map +1 -1
- package/dist/src/capabilities/upgrade/runUpgrade.js +43 -7
- package/dist/src/capabilities/upgrade/runUpgrade.js.map +1 -1
- package/dist/src/capabilities/upgrade/upgrade.render.js +10 -2
- package/dist/src/capabilities/upgrade/upgrade.render.js.map +1 -1
- package/dist/src/constants.js +13 -2
- package/dist/src/constants.js.map +1 -1
- package/dist/src/kernel/index.js +11 -2
- package/dist/src/kernel/index.js.map +1 -1
- package/dist/src/kernel/interactivity.js +68 -0
- package/dist/src/kernel/interactivity.js.map +1 -0
- package/dist/src/kernel/packs/renderPack.js +3 -3
- package/dist/src/kernel/packs/renderPack.js.map +1 -1
- package/dist/src/kernel/packs/resolveEntity.js +63 -12
- package/dist/src/kernel/packs/resolveEntity.js.map +1 -1
- package/dist/src/kernel/packs/runBodies.js +57 -4
- package/dist/src/kernel/packs/runBodies.js.map +1 -1
- package/dist/src/kernel/packs/schema.js +54 -2
- package/dist/src/kernel/packs/schema.js.map +1 -1
- package/dist/src/kernel/packs/sparql/applyFilters.js +104 -5
- package/dist/src/kernel/packs/sparql/applyFilters.js.map +1 -1
- package/dist/src/kernel/packs/sparql/buildLookupQuery.js +310 -43
- package/dist/src/kernel/packs/sparql/buildLookupQuery.js.map +1 -1
- package/dist/src/kernel/packs/types.js.map +1 -1
- package/dist/src/kernel/project/cli/buildProgram.js +1 -1
- package/dist/src/kernel/project/cli/buildProgram.js.map +1 -1
- package/dist/src/kernel/project/cli/dispatch.js +46 -24
- package/dist/src/kernel/project/cli/dispatch.js.map +1 -1
- package/dist/src/kernel/project/cli/globalFlags.js +4 -1
- package/dist/src/kernel/project/cli/globalFlags.js.map +1 -1
- package/dist/src/kernel/project/cli/helpFormat.js +1 -2
- package/dist/src/kernel/project/cli/helpFormat.js.map +1 -1
- package/dist/src/kernel/project/cli/rootHelp.js +42 -9
- package/dist/src/kernel/project/cli/rootHelp.js.map +1 -1
- package/dist/src/kernel/project/mcp/registerVerb.js +16 -1
- package/dist/src/kernel/project/mcp/registerVerb.js.map +1 -1
- package/dist/src/kernel/render/renderers.js +1 -1
- package/dist/src/kernel/render/style.js +29 -9
- package/dist/src/kernel/render/style.js.map +1 -1
- package/dist/src/kernel/render/vocabulary.js +174 -0
- package/dist/src/kernel/render/vocabulary.js.map +1 -0
- package/dist/src/kernel/runtime/graphpack/embedded/pack.generated.js +2 -3
- package/dist/src/kernel/runtime/graphpack/embedded/pack.generated.js.map +1 -1
- package/dist/src/kernel/runtime/graphpack/embedded/pack.index.generated.js +1 -1
- package/dist/src/kernel/runtime/graphpack/embedded/pack.index.generated.js.map +1 -1
- package/dist/src/kernel/runtime/graphpack/embedded/pack.manifest.generated.js +4 -0
- package/dist/src/kernel/runtime/graphpack/embedded/pack.manifest.generated.js.map +1 -0
- package/dist/src/kernel/runtime/graphpack/embedded.js +13 -6
- package/dist/src/kernel/runtime/graphpack/embedded.js.map +1 -1
- package/dist/src/kernel/runtime/graphpack/read.js +14 -9
- package/dist/src/kernel/runtime/graphpack/read.js.map +1 -1
- package/dist/src/kernel/runtime/loadSession.js +3 -1
- package/dist/src/kernel/runtime/loadSession.js.map +1 -1
- package/dist/src/kernel/runtime/probe.js +1 -1
- package/dist/src/kernel/runtime/probe.js.map +1 -1
- package/dist/src/kernel/runtime/refs/parseRef.js +17 -1
- package/dist/src/kernel/runtime/refs/parseRef.js.map +1 -1
- package/dist/src/kernel/runtime/refs/resolve.js +9 -3
- package/dist/src/kernel/runtime/refs/resolve.js.map +1 -1
- package/dist/src/kernel/spec/emitReference.js +1 -1
- package/dist/src/kernel/spec/emitReference.js.map +1 -1
- package/package.json +24 -20
- package/pragma.conf.ts +272 -34
- package/src/bin.ts +8 -1
- package/src/capabilities/block.parity.test.ts +25 -11
- package/src/capabilities/block.shipped.exec.test.ts +282 -0
- package/src/capabilities/block.tierRank.exec.test.ts +300 -0
- package/src/capabilities/capabilities/hints.ts +1 -1
- package/src/capabilities/colophon/colophon.render.ts +1 -2
- package/src/capabilities/colophon/colophon.test.ts +2 -2
- package/src/capabilities/config/collectConfigShow.test.ts +1 -0
- package/src/capabilities/create/byteEquality.test.ts +28 -1
- package/src/capabilities/create/create.test.ts +1 -0
- package/src/capabilities/create/create.verb.ts +2 -1
- package/src/capabilities/create/createGrammar.test.ts +5 -2
- package/src/capabilities/create/createSurface.generated.ts +23 -1
- package/src/capabilities/create/crossCli.subprocess.test.ts +10 -3
- package/src/capabilities/create/mount.ts +3 -3
- package/src/capabilities/distribution.parity.test.ts +4 -0
- package/src/capabilities/doctor/checks/checkNodeVersion.test.ts +49 -0
- package/src/capabilities/doctor/checks/checkNodeVersion.ts +11 -7
- package/src/capabilities/doctor/checks/checkPackageRefs.ts +69 -4
- package/src/capabilities/doctor/checks/checkShellCompletions.test.ts +6 -4
- package/src/capabilities/doctor/checks/checkShellCompletions.ts +8 -8
- package/src/capabilities/doctor/checks/harnessInventory.test.ts +219 -0
- package/src/capabilities/doctor/checks/harnessInventory.ts +298 -0
- package/src/capabilities/doctor/checks/index.ts +6 -6
- package/src/capabilities/doctor/checks/isSupportedNodeVersion.test.ts +111 -0
- package/src/capabilities/doctor/checks/isSupportedNodeVersion.ts +34 -0
- package/src/capabilities/doctor/checks/mcpCommand.test.ts +7 -1
- package/src/capabilities/doctor/checks/targetHealth.ts +229 -59
- package/src/capabilities/doctor/doctor.render.test.ts +108 -32
- package/src/capabilities/doctor/doctor.render.ts +56 -80
- package/src/capabilities/doctor/doctor.test.ts +247 -35
- package/src/capabilities/doctor/doctor.verb.ts +7 -2
- package/src/capabilities/doctor/runChecks.ts +14 -11
- package/src/capabilities/doctor/types.ts +15 -24
- package/src/capabilities/emptyRecovery.test.ts +10 -2
- package/src/capabilities/graph/query.render.ts +2 -2
- package/src/capabilities/info/collectInfo.ts +8 -1
- package/src/capabilities/info/info.render.ts +6 -2
- package/src/capabilities/info/info.test.ts +37 -1
- package/src/capabilities/info/types.ts +10 -2
- package/src/capabilities/lazy.test.ts +26 -2
- package/src/capabilities/listLookup.shipped.exec.test.ts +340 -0
- package/src/capabilities/prompt/prompt.render.ts +2 -2
- package/src/capabilities/renderVocabularySync.test.ts +82 -0
- package/src/capabilities/resources/resources.test.ts +38 -2
- package/src/capabilities/scopeTypeSync.test.ts +29 -15
- package/src/capabilities/setup/buildPlan.ts +45 -41
- package/src/capabilities/setup/index.ts +1 -1
- package/src/capabilities/setup/mode.ts +2 -2
- package/src/capabilities/setup/operations/index.ts +6 -1
- package/src/capabilities/setup/operations/setupCompletions.ts +9 -2
- package/src/capabilities/setup/operations/setupConfig.ts +7 -2
- package/src/capabilities/setup/operations/setupGenerator.ts +227 -84
- package/src/capabilities/setup/operations/setupLsp.ts +203 -38
- package/src/capabilities/setup/operations/setupMcp.ts +139 -62
- package/src/capabilities/setup/operations/setupSkills.ts +399 -60
- package/src/capabilities/setup/plan.render.ts +171 -76
- package/src/capabilities/setup/plan.ts +17 -12
- package/src/capabilities/setup/setup.mcp.correctness.test.ts +9 -9
- package/src/capabilities/setup/setup.render.test.ts +50 -36
- package/src/capabilities/setup/setup.render.ts +1 -1
- package/src/capabilities/setup/setup.test.ts +1126 -50
- package/src/capabilities/setup/setup.verb.ts +174 -68
- package/src/capabilities/setup/targets.ts +138 -60
- package/src/capabilities/setup/types.ts +10 -18
- package/src/capabilities/shared/index.ts +18 -6
- package/src/capabilities/shared/packageManager.test.ts +421 -6
- package/src/capabilities/shared/packageManager.ts +394 -59
- package/src/capabilities/skill/bundledSkills.test.ts +347 -0
- package/src/capabilities/skill/discover.ts +100 -11
- package/src/capabilities/skill/render.ts +2 -2
- package/src/capabilities/skill/skill.test.ts +17 -4
- package/src/capabilities/sources/installSkills.ts +181 -3
- package/src/capabilities/sources/runUpdate.ts +78 -2
- package/src/capabilities/sources/sources.test.ts +228 -0
- package/src/capabilities/sources/update.verb.ts +3 -3
- package/src/capabilities/surface.test.ts +14 -6
- package/src/capabilities/upgrade/runUpgrade.ts +45 -6
- package/src/capabilities/upgrade/types.ts +10 -2
- package/src/capabilities/upgrade/upgrade.render.ts +9 -2
- package/src/capabilities/upgrade/upgrade.test.ts +72 -0
- package/src/constants.ts +14 -1
- package/src/identity.test.ts +18 -5
- package/src/kernel/completion/entitySource.test.ts +15 -2
- package/src/kernel/completion/safety.test.ts +16 -9
- package/src/kernel/config/defaults.test.ts +19 -3
- package/src/kernel/config/readConfig.test.ts +1 -1
- package/src/kernel/index.ts +11 -2
- package/src/kernel/interactivity.test.ts +76 -0
- package/src/kernel/interactivity.ts +69 -0
- package/src/kernel/packs/compile.test.ts +92 -0
- package/src/kernel/packs/renderPack.ts +3 -3
- package/src/kernel/packs/resolveEntity.test.ts +75 -14
- package/src/kernel/packs/resolveEntity.ts +88 -24
- package/src/kernel/packs/runBodies.ts +84 -19
- package/src/kernel/packs/schema.ts +60 -4
- package/src/kernel/packs/sparql/applyFilters.ts +125 -4
- package/src/kernel/packs/sparql/buildLookupQuery.test.ts +290 -0
- package/src/kernel/packs/sparql/buildLookupQuery.ts +327 -45
- package/src/kernel/packs/toolDescription.test.ts +35 -1
- package/src/kernel/packs/types.ts +125 -2
- package/src/kernel/project/cli/buildProgram.test.ts +73 -2
- package/src/kernel/project/cli/buildProgram.ts +8 -1
- package/src/kernel/project/cli/dispatch.test.ts +5 -4
- package/src/kernel/project/cli/dispatch.ts +48 -25
- package/src/kernel/project/cli/globalFlags.ts +4 -1
- package/src/kernel/project/cli/helpFormat.ts +1 -2
- package/src/kernel/project/cli/rootHelp.ts +44 -8
- package/src/kernel/project/mcp/registerVerb.ts +20 -1
- package/src/kernel/render/prefixes.test.ts +2 -0
- package/src/kernel/render/renderers.ts +1 -1
- package/src/kernel/render/style.ts +32 -9
- package/src/kernel/render/vocabulary.ts +276 -0
- package/src/kernel/runtime/graphpack/embedded/pack.generated.ts +2 -3
- package/src/kernel/runtime/graphpack/embedded/pack.index.generated.ts +1 -1
- package/src/kernel/runtime/graphpack/embedded/pack.manifest.generated.ts +3 -0
- package/src/kernel/runtime/graphpack/embedded.ts +14 -6
- package/src/kernel/runtime/graphpack/graphpack.test.ts +31 -5
- package/src/kernel/runtime/graphpack/read.ts +14 -12
- package/src/kernel/runtime/loadSession.ts +3 -1
- package/src/kernel/runtime/probe.ts +1 -1
- package/src/kernel/runtime/refs/parseRef.test.ts +49 -1
- package/src/kernel/runtime/refs/parseRef.ts +25 -1
- package/src/kernel/runtime/refs/resolve.ts +9 -3
- package/src/kernel/runtime/types.ts +17 -0
- package/src/kernel/spec/emitReference.ts +1 -1
- package/src/kernel/spec/types.ts +35 -6
- package/src/testing/PARITY_GAPS.ts +9 -3
- package/src/testing/behavioral/docExamples.test.ts +4 -2
- package/src/testing/behavioral/errorMatrix.mcp.test.ts +110 -7
- package/src/testing/behavioral/journeys.firstInstall.test.ts +7 -2
- package/src/testing/behavioral/journeys.packageStories.test.ts +6 -3
- package/src/testing/eval/__snapshots__/eval.test.ts.snap +26 -1
- package/src/testing/eval/cases/readNouns.ts +245 -9
- package/src/testing/fixtures/blockGraph.ts +28 -13
- package/src/testing/fixtures/graph/canonical.ts +95 -12
- package/src/testing/helpers/runCli.test.ts +44 -0
- package/src/testing/helpers/runCli.ts +27 -3
- package/dist/src/capabilities/shared/bands.js +0 -16
- package/dist/src/capabilities/shared/bands.js.map +0 -1
- package/src/capabilities/shared/bands.ts +0 -16
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The harness inventory: one row per harness per scope, saying what was detected
|
|
3
|
+
* and whether pragma is registered in it.
|
|
4
|
+
*
|
|
5
|
+
* Every fact this needs was ALREADY COMPUTED and thrown away.
|
|
6
|
+
* `TargetGroup.harnessNames` carries every harness sharing a config file and
|
|
7
|
+
* both consumers reduced it to `shortenPath(group.path)`; `McpDetection`
|
|
8
|
+
* carries the `absent`/`registered`/`drifted` classification of every WRITE in
|
|
9
|
+
* that file. The only thing detection could NOT answer is the negative —
|
|
10
|
+
* `detectHarnesses` filters to hits, so a harness the machine does not have was
|
|
11
|
+
* unrepresentable — which is why the roll-up takes the REGISTRY alongside the
|
|
12
|
+
* groups: the registry supplies the universe, the groups supply the state.
|
|
13
|
+
*
|
|
14
|
+
* The state arrives PER HARNESS, never per file. A shared `.vscode/mcp.json`
|
|
15
|
+
* holds VS Code's `servers` and Cline's `mcpServers` as two independent
|
|
16
|
+
* entries, so the file's aggregate — `drifted`, when one key is current and the
|
|
17
|
+
* other absent — is the standing of NEITHER harness. Pairing each name with
|
|
18
|
+
* the write it owns is the caller's job (it holds the registry's `mcpKey`s and
|
|
19
|
+
* the group's writes); by the time a group reaches this module the association
|
|
20
|
+
* is resolved and every name carries its own state.
|
|
21
|
+
*
|
|
22
|
+
* Three deliberate constraints:
|
|
23
|
+
*
|
|
24
|
+
* 1. **No `@canonical/harnesses` import, of any kind.** `capabilities/lazy.test`
|
|
25
|
+
* forbids one from any file statically reachable from `capabilities/index`,
|
|
26
|
+
* and its regex has no `import type` exemption. This module is reached only
|
|
27
|
+
* through `runChecks`'s dynamic import today, but it is written so that
|
|
28
|
+
* staying legal never depends on that: the inputs are declared here as
|
|
29
|
+
* narrow structural records.
|
|
30
|
+
*
|
|
31
|
+
* 2. **Narrowed to DATA, so `mutates: false` is enforced rather than asserted.**
|
|
32
|
+
* `McpDetection` is write-capable — it carries `writeMcpConfigTargets` and
|
|
33
|
+
* `removeMcpConfigFrom` as live function references. Projecting it down to
|
|
34
|
+
* {@link InventoryGroup} before it reaches this file means a read-only
|
|
35
|
+
* report literally cannot reach a writer.
|
|
36
|
+
*
|
|
37
|
+
* 3. **It never invents a failure.** The states an inventory can report map onto
|
|
38
|
+
* `pass`/`available`/`skip` and NEVER `fail` (see `../types.ts`): a machine
|
|
39
|
+
* that simply does not have Windsurf is not a broken machine, and the `mcp`
|
|
40
|
+
* row already owns the actionable finding for the harnesses it does have.
|
|
41
|
+
* Reporting it twice is how a failure count becomes noise.
|
|
42
|
+
*
|
|
43
|
+
* Placement: this is a doctor helper because doctor is its only consumer today,
|
|
44
|
+
* and `shared/` is explicitly not a utility drawer (see `shared/index.ts`). It
|
|
45
|
+
* is written to MOVE there unchanged — pure, no capability imports beyond the
|
|
46
|
+
* scope type — the moment `setup`'s detection summary becomes the second caller.
|
|
47
|
+
*/
|
|
48
|
+
/** Whether a state means "this harness is on the machine". */
|
|
49
|
+
export const isDetectedState = (state) => state === "registered" || state === "drifted" || state === "detected";
|
|
50
|
+
/**
|
|
51
|
+
* Roll one scope's detected config groups up against the registry into one row
|
|
52
|
+
* per harness.
|
|
53
|
+
*
|
|
54
|
+
* Groups are keyed by harness NAME because that is the only identity
|
|
55
|
+
* `TargetGroup` records; the registry is the map back to an id, which is why it
|
|
56
|
+
* is a parameter rather than a lookup. A harness naming no registry row is
|
|
57
|
+
* ignored rather than invented — the inventory reports on the registry, not on
|
|
58
|
+
* whatever a group happened to say.
|
|
59
|
+
*
|
|
60
|
+
* @param registry - Every known harness, each flagged for scope membership.
|
|
61
|
+
* @param groups - This scope's detected config files (narrowed, path-shortened),
|
|
62
|
+
* each naming the harnesses that share it with their own per-write state.
|
|
63
|
+
* @param scope - The scope being rolled up.
|
|
64
|
+
* @returns One row per registry harness, in registry order.
|
|
65
|
+
* @note Pure — it reads no filesystem and holds no live effect.
|
|
66
|
+
*/
|
|
67
|
+
export function harnessInventory(registry, groups, scope) {
|
|
68
|
+
const states = new Map();
|
|
69
|
+
const locations = new Map();
|
|
70
|
+
for (const group of groups) {
|
|
71
|
+
for (const member of group.harnesses) {
|
|
72
|
+
const paths = locations.get(member.name) ?? [];
|
|
73
|
+
paths.push(group.path);
|
|
74
|
+
locations.set(member.name, paths);
|
|
75
|
+
// A harness sharing two files is registered only where EVERY one of them
|
|
76
|
+
// carries the current entry — the same "all or nothing" rule
|
|
77
|
+
// `aggregateMcpStates` applies across the writes within a single file.
|
|
78
|
+
const prior = states.get(member.name);
|
|
79
|
+
states.set(member.name, prior === undefined || prior === member.state
|
|
80
|
+
? member.state
|
|
81
|
+
: "drifted");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return registry.map((harness) => {
|
|
85
|
+
const detected = states.get(harness.name);
|
|
86
|
+
const paths = locations.get(harness.name) ?? [];
|
|
87
|
+
const state = detected === undefined
|
|
88
|
+
? harness.inScope
|
|
89
|
+
? "undetected"
|
|
90
|
+
: "unscoped"
|
|
91
|
+
: detected === "registered"
|
|
92
|
+
? "registered"
|
|
93
|
+
: detected === "drifted"
|
|
94
|
+
? "drifted"
|
|
95
|
+
: "detected";
|
|
96
|
+
return {
|
|
97
|
+
harnessId: harness.id,
|
|
98
|
+
harnessName: harness.name,
|
|
99
|
+
scope,
|
|
100
|
+
state,
|
|
101
|
+
locations: paths,
|
|
102
|
+
};
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
/** The per-item status each state renders as. Never `fail` — see the docblock. */
|
|
106
|
+
const STATE_STATUS = {
|
|
107
|
+
registered: "pass",
|
|
108
|
+
drifted: "available",
|
|
109
|
+
detected: "available",
|
|
110
|
+
undetected: "skip",
|
|
111
|
+
unscoped: "skip",
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* The phrase each state reads as, scope-aware for the unscoped case.
|
|
115
|
+
*
|
|
116
|
+
* The unscoped line says WHERE the harness keeps its config rather than naming
|
|
117
|
+
* an internal partition. `no Global band` told the reader nothing: "band" was
|
|
118
|
+
* this repository's word, and the fact underneath it — VS Code stores its MCP
|
|
119
|
+
* config per project, so there is no global file for pragma to be in — is the
|
|
120
|
+
* thing that explains the row.
|
|
121
|
+
*/
|
|
122
|
+
const stateDetail = (row) => {
|
|
123
|
+
switch (row.state) {
|
|
124
|
+
case "registered":
|
|
125
|
+
return "registered";
|
|
126
|
+
case "drifted":
|
|
127
|
+
return "registered, entry differs from current";
|
|
128
|
+
case "detected":
|
|
129
|
+
return "detected, not registered";
|
|
130
|
+
case "undetected":
|
|
131
|
+
return "not detected";
|
|
132
|
+
case "unscoped":
|
|
133
|
+
return row.scope === "global"
|
|
134
|
+
? "keeps no global config — it is per-project only"
|
|
135
|
+
: "keeps no per-project config — it is global only";
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
/**
|
|
139
|
+
* Project inventory rows into doctor sub-items.
|
|
140
|
+
*
|
|
141
|
+
* The default listing is DETECTED HARNESSES ONLY — a report that names every
|
|
142
|
+
* tool the user does not have buries the ones they do. `--verbose` widens it to
|
|
143
|
+
* the whole registry, which is the only view in which `undetected`/`unscoped`
|
|
144
|
+
* rows appear at all.
|
|
145
|
+
*
|
|
146
|
+
* @param rows - This scope's inventory rows.
|
|
147
|
+
* @param verbose - Whether to list the whole registry rather than the hits.
|
|
148
|
+
* @returns The sub-items, harness name in the left column.
|
|
149
|
+
*/
|
|
150
|
+
export function inventoryItems(rows, verbose) {
|
|
151
|
+
const shown = verbose
|
|
152
|
+
? rows
|
|
153
|
+
: rows.filter((row) => isDetectedState(row.state));
|
|
154
|
+
return shown.map((row) => {
|
|
155
|
+
const where = row.locations.join(", ");
|
|
156
|
+
return {
|
|
157
|
+
label: row.harnessName,
|
|
158
|
+
status: STATE_STATUS[row.state],
|
|
159
|
+
detail: where ? `${stateDetail(row)} — ${where}` : stateDetail(row),
|
|
160
|
+
};
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* The whole inventory row for one scope: a listing, not a verdict.
|
|
165
|
+
*
|
|
166
|
+
* Its status is only ever `pass` (this scope has harnesses) or `skip` (it has
|
|
167
|
+
* none). It is the one scoped row that is not a setup target, so it derives no
|
|
168
|
+
* `fix:` line — the `mcp` and `skills` rows beside it own every action a
|
|
169
|
+
* harness can need, and a second row proposing the same command would be the
|
|
170
|
+
* "same finding seen twice" the report is built to avoid.
|
|
171
|
+
*
|
|
172
|
+
* @param rows - This scope's inventory rows.
|
|
173
|
+
* @param verbose - Whether the listing widens to the whole registry.
|
|
174
|
+
* @returns The status, headline and sub-items for the scope's inventory check.
|
|
175
|
+
*/
|
|
176
|
+
export function inventoryHealth(rows, verbose) {
|
|
177
|
+
const detected = rows.filter((row) => isDetectedState(row.state));
|
|
178
|
+
const registered = rows.filter((row) => row.state === "registered");
|
|
179
|
+
const items = inventoryItems(rows, verbose);
|
|
180
|
+
if (detected.length === 0) {
|
|
181
|
+
return {
|
|
182
|
+
status: "skip",
|
|
183
|
+
detail: verbose
|
|
184
|
+
? `none of ${rows.length} known harnesses detected`
|
|
185
|
+
: "no harnesses detected",
|
|
186
|
+
items,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
status: "pass",
|
|
191
|
+
detail: `${detected.length} detected · ${registered.length} registered${verbose ? ` · ${rows.length} known` : ""}`,
|
|
192
|
+
items,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=harnessInventory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harnessInventory.js","sourceRoot":"","sources":["../../../../../src/capabilities/doctor/checks/harnessInventory.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,22 +6,22 @@
|
|
|
6
6
|
* them apart from the report body is what lets `runChecks` stay a composition
|
|
7
7
|
* — it orders and awaits, and knows nothing about what any check looks at.
|
|
8
8
|
*
|
|
9
|
-
* Two kinds live here, and the barrel exposes them differently. The
|
|
9
|
+
* Two kinds live here, and the barrel exposes them differently. The UNSCOPED
|
|
10
10
|
* checks diagnose what `setup` cannot install — the Node floor, the CLI's own
|
|
11
11
|
* version and install source, the pack answering this project's reads, and the
|
|
12
|
-
* store — so each is its own named entry point. The
|
|
13
|
-
* per target, per
|
|
12
|
+
* store — so each is its own named entry point. The SCOPED rows are derived
|
|
13
|
+
* per target, per scope, from the setup target table rather than authored here,
|
|
14
14
|
* so they surface as the one generator that derives them, never as a list of
|
|
15
15
|
* row names this file would then have to keep in step with `setup`.
|
|
16
16
|
*
|
|
17
17
|
* The per-target probes (the completions script check, the MCP command
|
|
18
|
-
* resolution helpers) stay internal on purpose: `
|
|
19
|
-
* they are meant to be reached, and a row reachable past its
|
|
18
|
+
* resolution helpers) stay internal on purpose: `scopedChecks` is the only way
|
|
19
|
+
* they are meant to be reached, and a row reachable past its scope is a row
|
|
20
20
|
* `doctor` can report and `setup` cannot fix.
|
|
21
21
|
*/
|
|
22
22
|
export { checkKeStore } from "./checkKeStore.js";
|
|
23
23
|
export { checkNodeVersion } from "./checkNodeVersion.js";
|
|
24
24
|
export { checkPackageRefs } from "./checkPackageRefs.js";
|
|
25
25
|
export { checkPragmaVersion } from "./checkPragmaVersion.js";
|
|
26
|
-
export {
|
|
26
|
+
export { scopedChecks } from "./targetHealth.js";
|
|
27
27
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a Node.js version satisfies the range this package declares in its
|
|
3
|
+
* own `engines.node`, surfaced as `SUPPORTED_NODE_RANGE` in `constants.ts`.
|
|
4
|
+
*
|
|
5
|
+
* The range is expressed here as a comparison rather than parsed: the CLI
|
|
6
|
+
* carries no semver dependency, and one range does not justify a range parser.
|
|
7
|
+
* What keeps the two honest is `isSupportedNodeVersion.test.ts`, which builds an
|
|
8
|
+
* independent predicate straight from the declared range string and requires
|
|
9
|
+
* the two to agree across a version grid — so a change to `engines.node` that
|
|
10
|
+
* is not mirrored here fails the suite rather than shipping.
|
|
11
|
+
*
|
|
12
|
+
* A major-only comparison cannot express this range: it cannot say "22.18 but
|
|
13
|
+
* not 22.0", and it cannot exclude the 23.0–23.5 window.
|
|
14
|
+
*
|
|
15
|
+
* @param version - A Node version string, e.g. `process.versions.node`.
|
|
16
|
+
* @returns Whether the version falls inside the declared range.
|
|
17
|
+
*/
|
|
18
|
+
export function isSupportedNodeVersion(version) {
|
|
19
|
+
// A semver range excludes prereleases unless a comparator opts into them, and
|
|
20
|
+
// `engines.node` opts into none. So `22.18.0-pre` is outside the declared
|
|
21
|
+
// range even though its numeric part is inside it, and reporting it as
|
|
22
|
+
// supported would be doctor passing a runtime the install contract rejects.
|
|
23
|
+
if (version.includes("-"))
|
|
24
|
+
return false;
|
|
25
|
+
const [rawMajor, rawMinor] = version.split(".");
|
|
26
|
+
const major = Number(rawMajor);
|
|
27
|
+
const minor = Number(rawMinor);
|
|
28
|
+
if (!Number.isInteger(major) || !Number.isInteger(minor))
|
|
29
|
+
return false;
|
|
30
|
+
if (major < 22)
|
|
31
|
+
return false;
|
|
32
|
+
if (major === 22)
|
|
33
|
+
return minor >= 18;
|
|
34
|
+
if (major === 23)
|
|
35
|
+
return minor >= 6;
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=isSupportedNodeVersion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isSupportedNodeVersion.js","sourceRoot":"","sources":["../../../../../src/capabilities/doctor/checks/isSupportedNodeVersion.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAAe;IACpD,8EAA8E;IAC9E,0EAA0E;IAC1E,uEAAuE;IACvE,4EAA4E;IAC5E,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAExC,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAChD,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC/B,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IAE/B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACvE,IAAI,KAAK,GAAG,EAAE;QAAE,OAAO,KAAK,CAAC;IAC7B,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,KAAK,IAAI,EAAE,CAAC;IACrC,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,KAAK,IAAI,CAAC,CAAC;IACpC,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
2
|
+
* The scoped doctor rows, derived from the setup target table.
|
|
3
3
|
*
|
|
4
|
-
* `doctor` and `setup` are the SAME LIST. Every
|
|
4
|
+
* `doctor` and `setup` are the SAME LIST. Every scoped row here carries a
|
|
5
5
|
* target id verbatim — `completions`, not "Shell completions"; `mcp`, not "MCP
|
|
6
6
|
* configured" — because the row name IS the fix command's argument, and a user
|
|
7
7
|
* who reads `✗ mcp` beside `fix: pragma setup mcp` can see that they are the
|
|
@@ -11,23 +11,32 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Every row's detection comes from the target table's own `detect`, so the two
|
|
13
13
|
* surfaces cannot disagree about what they looked at, and every `fix:` is
|
|
14
|
-
* derived from the row's id and
|
|
14
|
+
* derived from the row's id and scope rather than authored per check.
|
|
15
15
|
*
|
|
16
|
-
* Doctor's
|
|
16
|
+
* Doctor's UNSCOPED environment checks (Node version, the CLI's own version,
|
|
17
17
|
* pack refs, the store) diagnose things setup cannot install and stay outside
|
|
18
18
|
* this list.
|
|
19
|
+
*
|
|
20
|
+
* ONE scoped row is not a target: `harnesses`, the per-scope inventory. It is an
|
|
21
|
+
* inventory of the machine the targets are measured against, not a target that
|
|
22
|
+
* can be set up, so it deliberately carries no `fix:` and can never be `fail`
|
|
23
|
+
* or `available` — see {@link inventoryChecks}. The bijection between the other
|
|
24
|
+
* rows and the setup table is untouched: `harnesses` is not a `TargetId`, and
|
|
25
|
+
* nothing derives a command from it.
|
|
19
26
|
*/
|
|
20
27
|
import { MCP_SERVER_NAME } from "../../../constants.js";
|
|
21
28
|
import { detectionFailure, detectTargets, resolveRoots, } from "../../setup/buildPlan.js";
|
|
22
|
-
import { lspEditorNames, lspSkipReason, mcpGroupState, skillsSkipReason, } from "../../setup/operations/index.js";
|
|
29
|
+
import { lspEditorNames, lspSkipReason, mcpGroupState, mcpWriteState, skillsSkipReason, skillsSkipRemedy, } from "../../setup/operations/index.js";
|
|
23
30
|
import { shortenPath, TARGET_IDS } from "../../setup/plan.js";
|
|
31
|
+
import { MCP_NO_LOCATION } from "../../setup/targets.js";
|
|
24
32
|
import { checkShellCompletions } from "./checkShellCompletions.js";
|
|
33
|
+
import { harnessInventory, inventoryHealth, } from "./harnessInventory.js";
|
|
25
34
|
import { commandResolves } from "./mcpCommand.js";
|
|
26
35
|
/** The `config` row: the global config file exists and parses. */
|
|
27
36
|
const configHealth = (d, roots) => {
|
|
28
37
|
const path = shortenPath(d.path, roots);
|
|
29
38
|
if (!d.exists)
|
|
30
|
-
return { status: "available", detail: `${path} not
|
|
39
|
+
return { status: "available", detail: `${path} does not exist yet` };
|
|
31
40
|
if (!d.parses) {
|
|
32
41
|
return { status: "fail", detail: `${path} cannot be parsed as JSON` };
|
|
33
42
|
}
|
|
@@ -40,8 +49,8 @@ const lspHealth = (d) => {
|
|
|
40
49
|
}
|
|
41
50
|
const editors = lspEditorNames(d).join(", ");
|
|
42
51
|
return d.state === "installed"
|
|
43
|
-
? { status: "pass", detail: `installed
|
|
44
|
-
: { status: "available", detail: `not installed
|
|
52
|
+
? { status: "pass", detail: `installed in ${editors}` }
|
|
53
|
+
: { status: "available", detail: `not installed in ${editors}` };
|
|
45
54
|
};
|
|
46
55
|
/**
|
|
47
56
|
* The `mcp` row: one sub-item per file, each saying whether the entry is
|
|
@@ -49,19 +58,16 @@ const lspHealth = (d) => {
|
|
|
49
58
|
* one for presence, one for command resolution — and the second judged every
|
|
50
59
|
* server in the file, so a foreign server's dead command failed this CLI's row.
|
|
51
60
|
*/
|
|
52
|
-
async function mcpHealth(d,
|
|
61
|
+
async function mcpHealth(d, scope, cwd, roots) {
|
|
53
62
|
if (d.groups.length === 0) {
|
|
54
|
-
return {
|
|
55
|
-
status: "skip",
|
|
56
|
-
detail: "no harness config location in this band",
|
|
57
|
-
};
|
|
63
|
+
return { status: "skip", detail: MCP_NO_LOCATION[scope] };
|
|
58
64
|
}
|
|
59
65
|
const resolves = await commandResolves(MCP_SERVER_NAME, cwd);
|
|
60
66
|
const items = d.groups.map((group) => {
|
|
61
67
|
const state = mcpGroupState(d, group.path);
|
|
62
68
|
const label = shortenPath(group.path, roots);
|
|
63
69
|
if (state === "absent") {
|
|
64
|
-
return { label, status: "available", detail: "not
|
|
70
|
+
return { label, status: "available", detail: "not registered" };
|
|
65
71
|
}
|
|
66
72
|
if (state === "drifted") {
|
|
67
73
|
return { label, status: "fail", detail: "entry differs from current" };
|
|
@@ -70,8 +76,8 @@ async function mcpHealth(d, band, cwd, roots) {
|
|
|
70
76
|
label,
|
|
71
77
|
status: resolves ? "pass" : "fail",
|
|
72
78
|
detail: resolves
|
|
73
|
-
? `
|
|
74
|
-
: `
|
|
79
|
+
? `registered, and \`${MCP_SERVER_NAME}\` is on PATH`
|
|
80
|
+
: `registered, but \`${MCP_SERVER_NAME}\` is not on PATH`,
|
|
75
81
|
};
|
|
76
82
|
});
|
|
77
83
|
const configured = items.filter((item) => item.status === "pass").length;
|
|
@@ -79,33 +85,51 @@ async function mcpHealth(d, band, cwd, roots) {
|
|
|
79
85
|
if (failing > 0) {
|
|
80
86
|
return {
|
|
81
87
|
status: "fail",
|
|
82
|
-
detail: `${failing} of ${items.length} need attention`,
|
|
88
|
+
detail: `${failing} of ${items.length} config files need attention`,
|
|
83
89
|
items,
|
|
84
90
|
};
|
|
85
91
|
}
|
|
86
92
|
if (configured === 0) {
|
|
87
|
-
//
|
|
88
|
-
// repository with no checked-in MCP config is
|
|
89
|
-
|
|
90
|
-
|
|
93
|
+
// Absence in the LOCAL PROJECT is not a failure: registering there is
|
|
94
|
+
// opt-in, so a repository with no checked-in MCP config is healthy. The
|
|
95
|
+
// line says so rather than leaving "(opt-in)" to be decoded.
|
|
96
|
+
return scope === "project"
|
|
97
|
+
? {
|
|
98
|
+
status: "skip",
|
|
99
|
+
detail: "not registered for this project — per-project registration is opt-in",
|
|
100
|
+
}
|
|
91
101
|
: {
|
|
92
102
|
status: "available",
|
|
93
|
-
detail:
|
|
103
|
+
detail: `not registered in any of ${items.length} config files`,
|
|
94
104
|
items,
|
|
95
105
|
};
|
|
96
106
|
}
|
|
97
107
|
return {
|
|
98
108
|
status: configured === items.length ? "pass" : "available",
|
|
99
|
-
detail:
|
|
109
|
+
detail: `registered in ${configured} of ${items.length} config files`,
|
|
100
110
|
items,
|
|
101
111
|
};
|
|
102
112
|
}
|
|
103
113
|
/** The `skills` row: every expected link present, lstat-verified, none stale. */
|
|
104
|
-
function skillsHealth(d,
|
|
105
|
-
|
|
114
|
+
function skillsHealth(d, scope, roots) {
|
|
115
|
+
const short = shortenPath(d.sourceRoot, roots);
|
|
116
|
+
// Stale links this scope owns are work even when the root holds no skill, so
|
|
117
|
+
// the row is only a skip once there is nothing left to reconcile either.
|
|
118
|
+
const orphans = d.rootExists ? d.orphans.length : 0;
|
|
119
|
+
if (!d.available && orphans === 0) {
|
|
120
|
+
// The skip carries a remedy — `setup`'s row and this row are the same
|
|
121
|
+
// finding, and the dead-end version of it ("no skills installed", no next
|
|
122
|
+
// step) was reported identically by both surfaces.
|
|
106
123
|
return {
|
|
107
124
|
status: "skip",
|
|
108
|
-
detail: skillsSkipReason(
|
|
125
|
+
detail: skillsSkipReason(short, scope, d.rootExists),
|
|
126
|
+
remedy: skillsSkipRemedy(short, scope),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (orphans > 0) {
|
|
130
|
+
return {
|
|
131
|
+
status: "available",
|
|
132
|
+
detail: `${orphans} link${orphans === 1 ? "" : "s"} point at a skill that is gone`,
|
|
109
133
|
};
|
|
110
134
|
}
|
|
111
135
|
const stale = d.actions.filter((a) => a.action === "replaced").length;
|
|
@@ -120,6 +144,19 @@ function skillsHealth(d, band, roots) {
|
|
|
120
144
|
// and reporting the two the same way made this row say "links current" where
|
|
121
145
|
// no link exists at all. Setup will never clear such a path — it is not this
|
|
122
146
|
// command's to delete — so the row carries the only remedy that settles it.
|
|
147
|
+
// A symlink pointing outside every root pragma owns is `skipped` so setup
|
|
148
|
+
// will never delete it — which would make this row claim "links current" over
|
|
149
|
+
// a path where the user's own link shadows a shipped skill. `skipped` also
|
|
150
|
+
// covers an already-correct link (owned) and a real directory (blocked), so
|
|
151
|
+
// the foreign case is what is left.
|
|
152
|
+
const foreign = d.actions.filter((a) => a.action === "skipped" && !a.owned && !a.blocked).length;
|
|
153
|
+
if (foreign > 0) {
|
|
154
|
+
return {
|
|
155
|
+
status: "available",
|
|
156
|
+
detail: `${foreign} of ${d.actions.length} link paths hold a symlink pragma does not own`,
|
|
157
|
+
remedy: "Move or delete the symlink at that path, then link the skills again.",
|
|
158
|
+
};
|
|
159
|
+
}
|
|
123
160
|
const blocked = d.actions.filter((a) => a.blocked).length;
|
|
124
161
|
if (blocked > 0) {
|
|
125
162
|
return {
|
|
@@ -143,7 +180,7 @@ function skillsHealth(d, band, roots) {
|
|
|
143
180
|
*/
|
|
144
181
|
async function healthOf(row, rt, roots) {
|
|
145
182
|
// A detection that threw is reported as ITS OWN failing row. Letting the
|
|
146
|
-
// rejection escape took the whole
|
|
183
|
+
// rejection escape took the whole scoped section of the report with it, so a
|
|
147
184
|
// single unreadable config file left the user with no rows at all — the one
|
|
148
185
|
// moment the report is most worth having.
|
|
149
186
|
const failure = detectionFailure(row);
|
|
@@ -163,43 +200,150 @@ async function healthOf(row, rt, roots) {
|
|
|
163
200
|
case "lsp":
|
|
164
201
|
return lspHealth(row.detection);
|
|
165
202
|
case "mcp":
|
|
166
|
-
return mcpHealth(row.detection, row.
|
|
203
|
+
return mcpHealth(row.detection, row.scope, rt.cwd, roots);
|
|
167
204
|
default:
|
|
168
|
-
return skillsHealth(row.detection, row.
|
|
205
|
+
return skillsHealth(row.detection, row.scope, roots);
|
|
169
206
|
}
|
|
170
207
|
}
|
|
208
|
+
/** The scoped inventory row's name — a LISTING, not a setup target id. */
|
|
209
|
+
export const INVENTORY_CHECK = "harnesses";
|
|
210
|
+
/**
|
|
211
|
+
* The `harnesses` row for one scope: what this machine has, and where pragma
|
|
212
|
+
* stands in each.
|
|
213
|
+
*
|
|
214
|
+
* It is the one scoped row that is NOT a target, and it earns that by never
|
|
215
|
+
* competing with the ones that are. Its status is only `pass` or `skip`, so it
|
|
216
|
+
* adds no failure and no `available` to the tally, and it derives no `fix:` —
|
|
217
|
+
* the `mcp` and `skills` rows beside it already name every command a harness
|
|
218
|
+
* needs, and repeating them here would be the same finding seen twice.
|
|
219
|
+
*
|
|
220
|
+
* The registry is the only thing detection could not supply: `detectHarnesses`
|
|
221
|
+
* filters to hits, so "Windsurf is not on this machine" was unrepresentable
|
|
222
|
+
* until the universe came from somewhere. Everything else — which harnesses
|
|
223
|
+
* share which file, and whether each one's own entry in it is current — is read
|
|
224
|
+
* straight off the `mcp` detection this report already ran. The projection to
|
|
225
|
+
* {@link InventoryGroup} is deliberate: `McpDetection` holds
|
|
226
|
+
* `writeMcpConfigTargets`/`removeMcpConfigFrom` as live functions, and a
|
|
227
|
+
* `mutates: false` command should be unable to reach a writer, not merely
|
|
228
|
+
* trusted not to.
|
|
229
|
+
*
|
|
230
|
+
* This is also where the harness↔`mcpKey` association is REJOINED, because it
|
|
231
|
+
* is the one place that holds both halves: `TargetGroup` records the names
|
|
232
|
+
* sharing a file and, separately, one write per distinct `mcpKey`, and the
|
|
233
|
+
* registry is what says which key a given harness writes. Without that join the
|
|
234
|
+
* only state available per harness is the file's aggregate — and a
|
|
235
|
+
* `.vscode/mcp.json` where VS Code's `servers` entry is current while Cline's
|
|
236
|
+
* `mcpServers` is absent aggregates to `drifted`, which would report BOTH
|
|
237
|
+
* harnesses as drifted when neither one is.
|
|
238
|
+
*
|
|
239
|
+
* @param rt - The per-invocation runtime (read for `--verbose`).
|
|
240
|
+
* @param rows - Every detected row, both scopes, as `scopedChecks` has them.
|
|
241
|
+
* @param roots - The roots every path renders relative to.
|
|
242
|
+
* @returns One inventory {@link CheckResult} per scope, global then project.
|
|
243
|
+
* @note Impure — dynamically imports the harness registry.
|
|
244
|
+
*/
|
|
245
|
+
async function inventoryChecks(rt, rows, roots) {
|
|
246
|
+
const { harnesses, isHarnessInBand } = await import("@canonical/harnesses");
|
|
247
|
+
const verbose = rt.globalFlags.verbose;
|
|
248
|
+
const scopes = ["global", "project"];
|
|
249
|
+
// The key each harness NAME writes under — the half of the association
|
|
250
|
+
// `groupConfigTargets` consumed and did not record. Names are the only
|
|
251
|
+
// identity a group carries, and the registry's are unique.
|
|
252
|
+
const keyByName = new Map(harnesses.map((h) => [h.name, h.mcpKey]));
|
|
253
|
+
return scopes.map((scope) => {
|
|
254
|
+
// `isHarnessInBand(harness.scope, scope, scope)` — the scope asked for as
|
|
255
|
+
// its OWN selection ("band" is the harnesses package's name for it) — is
|
|
256
|
+
// exactly "does this harness have a config location here", which is the
|
|
257
|
+
// question `detectMcp` already answers for that scope. Asking it under the
|
|
258
|
+
// `both` selection would instead answer "who writes here when both scopes
|
|
259
|
+
// run", and report every dual-scope harness as having no global location.
|
|
260
|
+
const registry = harnesses.map((harness) => ({
|
|
261
|
+
id: harness.id,
|
|
262
|
+
name: harness.name,
|
|
263
|
+
inScope: isHarnessInBand(harness.scope, scope, scope),
|
|
264
|
+
}));
|
|
265
|
+
const mcpRow = rows.find((row) => row.target.id === "mcp" && row.scope === scope);
|
|
266
|
+
if (mcpRow === undefined || detectionFailure(mcpRow) !== undefined) {
|
|
267
|
+
return {
|
|
268
|
+
name: INVENTORY_CHECK,
|
|
269
|
+
status: "skip",
|
|
270
|
+
detail: "harness detection did not settle — see the `mcp` row",
|
|
271
|
+
scope,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
const detection = mcpRow.detection;
|
|
275
|
+
const groups = detection.groups.map((group) => ({
|
|
276
|
+
path: shortenPath(group.path, roots),
|
|
277
|
+
harnesses: group.harnessNames.map((name) => {
|
|
278
|
+
// The harness's OWN write in this file. A name the registry does not
|
|
279
|
+
// know (or a key no write carries — impossible while the group was
|
|
280
|
+
// built from the same registry) falls back to the file's aggregate:
|
|
281
|
+
// strictly no worse than the state before this join existed.
|
|
282
|
+
const write = group.writes.find((w) => w.mcpKey === keyByName.get(name));
|
|
283
|
+
return {
|
|
284
|
+
name,
|
|
285
|
+
state: write === undefined
|
|
286
|
+
? mcpGroupState(detection, group.path)
|
|
287
|
+
: mcpWriteState(detection, write),
|
|
288
|
+
};
|
|
289
|
+
}),
|
|
290
|
+
}));
|
|
291
|
+
const health = inventoryHealth(harnessInventory(registry, groups, scope), verbose);
|
|
292
|
+
return {
|
|
293
|
+
name: INVENTORY_CHECK,
|
|
294
|
+
status: health.status,
|
|
295
|
+
detail: health.detail,
|
|
296
|
+
scope,
|
|
297
|
+
...(health.items.length === 0 ? {} : { items: health.items }),
|
|
298
|
+
};
|
|
299
|
+
});
|
|
300
|
+
}
|
|
171
301
|
/**
|
|
172
|
-
* The command that repairs a row — derived from its id and
|
|
302
|
+
* The command that repairs a row — derived from its id and scope, never
|
|
173
303
|
* authored. This is the bijection made mechanical: a row exists because a
|
|
174
304
|
* target exists, and its fix is that target's own invocation.
|
|
175
305
|
*/
|
|
176
|
-
export const fixCommandFor = (id,
|
|
306
|
+
export const fixCommandFor = (id, scope, bin) => `${bin} setup ${id}${scope === "project" ? " --local" : ""}`;
|
|
177
307
|
/**
|
|
178
|
-
* Run every
|
|
308
|
+
* Run every scoped check: the target table, both scopes, in table order, then
|
|
309
|
+
* each scope's harness inventory.
|
|
310
|
+
*
|
|
311
|
+
* The inventory rows come LAST within the array, and the renderer partitions by
|
|
312
|
+
* scope while preserving order, so each scope's section ends with the listing of
|
|
313
|
+
* what that scope actually holds — the targets first, then the machine they were
|
|
314
|
+
* measured against. They ride on the SAME detection pass: no target is probed a
|
|
315
|
+
* second time to produce them.
|
|
179
316
|
*
|
|
180
317
|
* @param rt - The per-invocation runtime.
|
|
181
318
|
* @param bin - The binary name the `fix:` lines are derived from.
|
|
182
|
-
* @returns One {@link CheckResult} per (target,
|
|
319
|
+
* @returns One {@link CheckResult} per (target, scope), plus one per scope.
|
|
183
320
|
* @note Impure — every target's detection reads the real filesystem.
|
|
184
321
|
*/
|
|
185
|
-
export async function
|
|
322
|
+
export async function scopedChecks(rt, bin) {
|
|
186
323
|
const roots = await resolveRoots(rt);
|
|
187
324
|
const detected = await detectTargets(rt, [...TARGET_IDS], "both");
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
:
|
|
202
|
-
|
|
203
|
-
|
|
325
|
+
const [targetRows, inventoryRows] = await Promise.all([
|
|
326
|
+
Promise.all(detected.map(async (row) => {
|
|
327
|
+
const health = await healthOf(row, rt, roots);
|
|
328
|
+
const needsFix = health.status === "fail" || health.status === "available";
|
|
329
|
+
// A `skip` gets no DERIVED fix — re-running the target's own setup command
|
|
330
|
+
// would reproduce the skip. But a skip that AUTHORED a remedy has found a
|
|
331
|
+
// real next step on this machine (fill the scope's skill root, say), and
|
|
332
|
+
// dropping it is what made the skip a dead end on both surfaces.
|
|
333
|
+
const remedy = needsFix
|
|
334
|
+
? (health.remedy ?? fixCommandFor(row.target.id, row.scope, bin))
|
|
335
|
+
: health.remedy;
|
|
336
|
+
return {
|
|
337
|
+
name: row.target.id,
|
|
338
|
+
status: health.status,
|
|
339
|
+
detail: health.detail,
|
|
340
|
+
scope: row.scope,
|
|
341
|
+
...(health.items === undefined ? {} : { items: health.items }),
|
|
342
|
+
...(remedy === undefined ? {} : { remedy }),
|
|
343
|
+
};
|
|
344
|
+
})),
|
|
345
|
+
inventoryChecks(rt, detected, roots),
|
|
346
|
+
]);
|
|
347
|
+
return [...targetRows, ...inventoryRows];
|
|
204
348
|
}
|
|
205
349
|
//# sourceMappingURL=targetHealth.js.map
|