@canonical/pragma-cli 0.35.0 → 0.36.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 +16 -15
- package/dist/pragma.conf.js +236 -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/colophon/colophon.render.js +1 -1
- package/dist/src/capabilities/colophon/colophon.render.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/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/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/setupGenerator.js +92 -45
- package/dist/src/capabilities/setup/operations/setupGenerator.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupLsp.js +88 -36
- package/dist/src/capabilities/setup/operations/setupLsp.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupMcp.js +97 -51
- package/dist/src/capabilities/setup/operations/setupMcp.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupSkills.js +325 -47
- 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 +86 -33
- package/dist/src/capabilities/setup/setup.verb.js.map +1 -1
- package/dist/src/capabilities/setup/targets.js +110 -45
- package/dist/src/capabilities/setup/targets.js.map +1 -1
- package/dist/src/capabilities/shared/index.js +6 -5
- 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 +6 -1
- 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 +306 -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 +23 -23
- 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 +34 -4
- 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/spec/emitReference.js +1 -1
- package/dist/src/kernel/spec/emitReference.js.map +1 -1
- package/package.json +16 -15
- package/pragma.conf.ts +241 -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/colophon/colophon.render.ts +1 -2
- package/src/capabilities/colophon/colophon.test.ts +2 -2
- package/src/capabilities/create/createGrammar.test.ts +1 -1
- package/src/capabilities/create/mount.ts +3 -3
- package/src/capabilities/distribution.parity.test.ts +4 -0
- 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/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/prompt/prompt.render.ts +2 -2
- package/src/capabilities/renderVocabularySync.test.ts +82 -0
- 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/setupGenerator.ts +144 -70
- package/src/capabilities/setup/operations/setupLsp.ts +129 -37
- package/src/capabilities/setup/operations/setupMcp.ts +132 -61
- package/src/capabilities/setup/operations/setupSkills.ts +392 -58
- 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 +881 -50
- package/src/capabilities/setup/setup.verb.ts +91 -33
- package/src/capabilities/setup/targets.ts +120 -52
- package/src/capabilities/setup/types.ts +10 -18
- package/src/capabilities/shared/index.ts +17 -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 +2 -2
- 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 +6 -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 +2 -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 +323 -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 +25 -24
- 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 +36 -3
- package/src/kernel/project/mcp/registerVerb.ts +20 -1
- 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/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/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
|
@@ -9,18 +9,18 @@
|
|
|
9
9
|
* only the self-verb is an MCP tool. `destructive: false` keeps MCP from
|
|
10
10
|
* advertising the tool as destructive.
|
|
11
11
|
*
|
|
12
|
-
* THE
|
|
13
|
-
* runs each harness's own default
|
|
12
|
+
* THE SCOPE DEFAULT IS GLOBAL. `--scope` used to default to `both`, and `both`
|
|
13
|
+
* runs each harness's own default scope — which is `project` for most of the
|
|
14
14
|
* registry — so a bare `setup` scattered `.mcp.json`, `.gemini/settings.json`
|
|
15
15
|
* and `opencode.json` into whatever directory you happened to be standing in.
|
|
16
|
-
* The user
|
|
16
|
+
* The user scope is what a machine-level installer configures; the project scope
|
|
17
17
|
* is a deliberate, checked-in, team-shared choice, and is now chosen with
|
|
18
18
|
* `--local` rather than assumed.
|
|
19
19
|
*
|
|
20
|
-
* Every sub-verb registers the scope flags UNIFORMLY. That is what lets the
|
|
20
|
+
* Every sub-verb registers the scope flags UNIFORMLY. That is what lets the scope
|
|
21
21
|
* answer come from the table instead of per-verb flag wiring — previously
|
|
22
22
|
* `setup completions --local` died as an unknown option, while `setup mcp
|
|
23
|
-
* --local` worked, for no reason a user could see. Asking a target for a
|
|
23
|
+
* --local` worked, for no reason a user could see. Asking a target for a scope it
|
|
24
24
|
* cannot honour is a usage error (exit 2) with the corrected command; asking the
|
|
25
25
|
* RUN-ALL for such a band merely filters, and the out-of-band target still shows
|
|
26
26
|
* as a named row.
|
|
@@ -43,20 +43,25 @@ import type {
|
|
|
43
43
|
} from "../../kernel/spec/index.js";
|
|
44
44
|
import { resolveSetupMode, setupModeInput } from "./mode.js";
|
|
45
45
|
import {
|
|
46
|
+
isActionable,
|
|
46
47
|
planExitFailed,
|
|
47
48
|
planTally,
|
|
48
49
|
type SetupPlan,
|
|
49
50
|
TARGET_IDS,
|
|
50
51
|
} from "./plan.js";
|
|
51
|
-
import {
|
|
52
|
+
import {
|
|
53
|
+
renderDetectionSummary,
|
|
54
|
+
renderProgressLine,
|
|
55
|
+
renderRecap,
|
|
56
|
+
} from "./plan.render.js";
|
|
52
57
|
import { renderDryRun, setupFormatters } from "./setup.render.js";
|
|
53
|
-
import type {
|
|
58
|
+
import type { Scope, ScopeSelection, SetupMode } from "./types.js";
|
|
54
59
|
|
|
55
60
|
/**
|
|
56
61
|
* The `--scope` flag + its `--global`/`--local` boolean sugars. Registered by
|
|
57
|
-
* EVERY setup verb, including the single-
|
|
58
|
-
* sub-verbs and not others is a grammar the user has to memorise, and the
|
|
59
|
-
* a single-
|
|
62
|
+
* EVERY setup verb, including the single-scope ones: a flag that exists on some
|
|
63
|
+
* sub-verbs and not others is a grammar the user has to memorise, and the scope
|
|
64
|
+
* a single-scope target can honour is a question the target table answers.
|
|
60
65
|
*
|
|
61
66
|
* `--global` is a synonym of the default; it stays for scripts and for symmetry
|
|
62
67
|
* with `--local`.
|
|
@@ -65,19 +70,19 @@ const SCOPE_PARAMS: readonly ParamSpec[] = [
|
|
|
65
70
|
{
|
|
66
71
|
kind: "enum",
|
|
67
72
|
name: "scope",
|
|
68
|
-
doc: "
|
|
73
|
+
doc: "Where to configure: global (your home directory, the default), project (this repository), or both.",
|
|
69
74
|
values: ["project", "global", "both"],
|
|
70
75
|
default: "global",
|
|
71
76
|
},
|
|
72
77
|
{
|
|
73
78
|
kind: "boolean",
|
|
74
79
|
name: "global",
|
|
75
|
-
doc: "Shorthand for --scope global
|
|
80
|
+
doc: "Shorthand for --scope global — configure your home directory.",
|
|
76
81
|
},
|
|
77
82
|
{
|
|
78
83
|
kind: "boolean",
|
|
79
84
|
name: "local",
|
|
80
|
-
doc: "Shorthand for --scope project
|
|
85
|
+
doc: "Shorthand for --scope project — configure this project only.",
|
|
81
86
|
},
|
|
82
87
|
];
|
|
83
88
|
|
|
@@ -96,34 +101,35 @@ function resolveScope(params: Record<string, unknown>): ScopeSelection {
|
|
|
96
101
|
}
|
|
97
102
|
|
|
98
103
|
/**
|
|
99
|
-
* Reject a sub-verb asked for a
|
|
104
|
+
* Reject a sub-verb asked for a scope its target cannot hold.
|
|
100
105
|
*
|
|
101
106
|
* This is a usage error, not a skip: the user typed a contradiction, and
|
|
102
|
-
* correcting it is exactly what exit 2 means. The message names the
|
|
107
|
+
* correcting it is exactly what exit 2 means. The message names the scope the
|
|
103
108
|
* target DOES have and prints the command that works, so the correction is one
|
|
104
109
|
* paste rather than one manual page.
|
|
105
110
|
*
|
|
106
111
|
* @param mode - The sub-verb's target, or `all`.
|
|
107
112
|
* @param scope - The resolved selection.
|
|
108
|
-
* @throws PragmaError INVALID_INPUT when the target has no such
|
|
113
|
+
* @throws PragmaError INVALID_INPUT when the target has no such scope.
|
|
109
114
|
*/
|
|
110
|
-
async function
|
|
115
|
+
async function assertScopeIsPossible(
|
|
111
116
|
mode: SetupMode,
|
|
112
117
|
scope: ScopeSelection,
|
|
113
118
|
): Promise<void> {
|
|
114
119
|
if (mode === "all" || scope === "both") return;
|
|
115
120
|
const { findTarget } = await import("./targets.js");
|
|
116
121
|
const target = findTarget(mode);
|
|
117
|
-
if (target === undefined || target.
|
|
118
|
-
const only = target.
|
|
119
|
-
const level = only === "global" ? "
|
|
122
|
+
if (target === undefined || target.scopes.includes(scope as Scope)) return;
|
|
123
|
+
const only = target.scopes[0] as Scope;
|
|
124
|
+
const level = only === "global" ? "global only" : "per-project only";
|
|
125
|
+
const asked = scope === "global" ? "globally" : "per project";
|
|
120
126
|
const corrected =
|
|
121
127
|
only === "global"
|
|
122
128
|
? `${BIN_NAME} setup ${mode}`
|
|
123
129
|
: `${BIN_NAME} setup ${mode} --local`;
|
|
124
130
|
throw new PragmaError({
|
|
125
131
|
code: "INVALID_INPUT",
|
|
126
|
-
message: `${mode} is ${level} — it
|
|
132
|
+
message: `${mode} is ${level} — it cannot be configured ${asked}.`,
|
|
127
133
|
recovery: { message: `Run: ${corrected}` },
|
|
128
134
|
});
|
|
129
135
|
}
|
|
@@ -169,7 +175,7 @@ const SUB_CAPABILITY = {
|
|
|
169
175
|
*
|
|
170
176
|
* @param mode - Which entry point (`all` or one target).
|
|
171
177
|
* @param rt - The per-invocation runtime (mutated: `rt.exec` and `rt.planData`).
|
|
172
|
-
* @param scope - The resolved
|
|
178
|
+
* @param scope - The resolved scope selection.
|
|
173
179
|
* @returns The `Task<SetupPlan>` the dispatcher/MCP handler interprets.
|
|
174
180
|
* @note Impure — runs every target's real detection before returning a Task.
|
|
175
181
|
*/
|
|
@@ -178,7 +184,7 @@ async function runSetup(
|
|
|
178
184
|
rt: PragmaRuntime,
|
|
179
185
|
scope: ScopeSelection,
|
|
180
186
|
): Promise<Task<SetupPlan>> {
|
|
181
|
-
await
|
|
187
|
+
await assertScopeIsPossible(mode, scope);
|
|
182
188
|
|
|
183
189
|
// Lazy dynamic imports (lazy-React discipline): summon-core's barrel is
|
|
184
190
|
// React-free, and the non-TTY branch picks autoPrompt/mcpPrompt (never mounts
|
|
@@ -203,6 +209,28 @@ async function runSetup(
|
|
|
203
209
|
// to know which of the two it is.
|
|
204
210
|
const previewing = interactionMode === "batch-dry-run";
|
|
205
211
|
|
|
212
|
+
// A CONVERGED machine has no question to ask. Every row is `none` or `skip`,
|
|
213
|
+
// so nothing composes an effect — and the wizard's confirm gate then mounted
|
|
214
|
+
// Ink to render summon-core's "No operations planned." above a "Proceed?"
|
|
215
|
+
// over an empty tally. For a sub-verb, whose prompt list is empty, that
|
|
216
|
+
// contentless gate was the FIRST AND ONLY thing the user ever saw. The
|
|
217
|
+
// explanation existed the whole time (each row's `none` detail, one child row
|
|
218
|
+
// per file/editor marked `unchanged`) and was dropped at the render seam.
|
|
219
|
+
//
|
|
220
|
+
// So: report the plan instead of asking a question with no content. There is
|
|
221
|
+
// no `preview: true` here — the run really did complete. `applied({})` marks
|
|
222
|
+
// each `none` row `{status:"noop", note:"unchanged"}`, `wasApplied` is then
|
|
223
|
+
// true, and `setupFormatters.plain` renders the recap. That also retires a
|
|
224
|
+
// second defect: a non-interactive converged run used to print the misleading
|
|
225
|
+
// "Nothing was applied. Run again with --yes to apply." over an all-`none`
|
|
226
|
+
// plan. A run whose every DETECTION threw is not converged in this sense —
|
|
227
|
+
// its rows are skips that report `failed` — so it falls through to the normal
|
|
228
|
+
// path, which is the one that raises.
|
|
229
|
+
if (!previewing && run.plan.rows.every((row) => !isActionable(row.action))) {
|
|
230
|
+
const converged = run.applied({});
|
|
231
|
+
if (!planExitFailed(converged)) return pure<SetupPlan>(converged);
|
|
232
|
+
}
|
|
233
|
+
|
|
206
234
|
// Adaptation (b): a non-interactive run without `--yes` previews rather than
|
|
207
235
|
// mutating. It is the same plan the dry-run prints, plus one hint line, at
|
|
208
236
|
// exit 0 — the invocation completed a real read-only unit of work.
|
|
@@ -210,6 +238,23 @@ async function runSetup(
|
|
|
210
238
|
return pure<SetupPlan>({ ...run.plan, preview: true });
|
|
211
239
|
}
|
|
212
240
|
|
|
241
|
+
// Say what was DETECTED before asking anything about it. The data has been
|
|
242
|
+
// sitting in `run.plan` since before the prompt list was built — `inkPrompt`
|
|
243
|
+
// mounts no React until the first Prompt effect — so this is a rendering gap,
|
|
244
|
+
// not an ordering one, and closing it is nearly free.
|
|
245
|
+
//
|
|
246
|
+
// It MUST be emitted here, before `execute` is driven: the summary and the
|
|
247
|
+
// Ink frame both write to stderr, and only a line written first lands in
|
|
248
|
+
// scrollback ABOVE the frame. `rt.report` is the non-Ink seam — `--quiet`
|
|
249
|
+
// silences it and it is a no-op over MCP — so no React is loaded to print it.
|
|
250
|
+
// A preview says nothing here: the plan table IS its output.
|
|
251
|
+
if (!previewing) {
|
|
252
|
+
const summary = renderDetectionSummary(run.plan, {
|
|
253
|
+
verbose: rt.globalFlags.verbose,
|
|
254
|
+
});
|
|
255
|
+
if (summary !== undefined) rt.report?.(summary);
|
|
256
|
+
}
|
|
257
|
+
|
|
213
258
|
const { transport, yes, signal, abort } = rt.interaction ?? {
|
|
214
259
|
isTTY: false,
|
|
215
260
|
transport: "cli" as const,
|
|
@@ -226,6 +271,19 @@ async function runSetup(
|
|
|
226
271
|
signal,
|
|
227
272
|
onCancel: abort,
|
|
228
273
|
});
|
|
274
|
+
// Live progress in the PLAN's units, not the interpreter's: the wizard's
|
|
275
|
+
// default execution view is the per-effect transcript — right for `create`,
|
|
276
|
+
// whose unit of work IS the file, and wrong here, where one unit is one
|
|
277
|
+
// configured target and the transcript renders eighteen symlink rows for
|
|
278
|
+
// the row the plan called `skills link 9 skills → 2 folders`. Each row
|
|
279
|
+
// event carries the SAME sentence `renderProgressLine` prints, so the
|
|
280
|
+
// watcher and the recap reader see one dialect. `--verbose` withholds the
|
|
281
|
+
// listener, which falls the wizard back to the full effect transcript —
|
|
282
|
+
// the same detected-only-unless-verbose rule the detection summary and
|
|
283
|
+
// doctor already follow.
|
|
284
|
+
if (rt.globalFlags.verbose !== true) {
|
|
285
|
+
run.setRowListener((event) => session.reportStep(event));
|
|
286
|
+
}
|
|
229
287
|
rt.exec = {
|
|
230
288
|
promptHandler: session.promptHandler,
|
|
231
289
|
onEffectStart: session.onEffectStart,
|
|
@@ -345,24 +403,24 @@ function setupVerb(
|
|
|
345
403
|
|
|
346
404
|
const setupAllVerb = setupVerb(
|
|
347
405
|
["setup"],
|
|
348
|
-
"
|
|
406
|
+
"Set up your config file, TAB completion, the editor extension, MCP, and skills.",
|
|
349
407
|
"all",
|
|
350
408
|
SELF_CAPABILITY,
|
|
351
409
|
{
|
|
352
|
-
doc: "
|
|
410
|
+
doc: "Shows what each target needs, then applies the ones you keep. Everything is configured in your home directory by default; the scope option moves the run to this project alone, or covers both. Without an attended terminal the plan is printed and nothing is written unless the run is explicitly confirmed.",
|
|
353
411
|
examples: [
|
|
354
412
|
{ cmd: `${BIN_NAME} setup` },
|
|
355
413
|
{
|
|
356
414
|
cmd: `${BIN_NAME} setup --dry-run`,
|
|
357
|
-
note: "
|
|
415
|
+
note: "show the plan, write nothing",
|
|
358
416
|
},
|
|
359
417
|
{
|
|
360
418
|
cmd: `${BIN_NAME} setup --local`,
|
|
361
|
-
note: "configure
|
|
419
|
+
note: "configure this project instead of your home directory",
|
|
362
420
|
},
|
|
363
421
|
{
|
|
364
422
|
cmd: `${BIN_NAME} setup mcp`,
|
|
365
|
-
note: "
|
|
423
|
+
note: "only register the MCP server",
|
|
366
424
|
},
|
|
367
425
|
],
|
|
368
426
|
},
|
|
@@ -370,35 +428,35 @@ const setupAllVerb = setupVerb(
|
|
|
370
428
|
|
|
371
429
|
const configVerb = setupVerb(
|
|
372
430
|
["setup", "config"],
|
|
373
|
-
"Create
|
|
431
|
+
"Create your global config file, filled in with the defaults.",
|
|
374
432
|
"config",
|
|
375
433
|
SUB_CAPABILITY,
|
|
376
434
|
);
|
|
377
435
|
|
|
378
436
|
const mcpVerb = setupVerb(
|
|
379
437
|
["setup", "mcp"],
|
|
380
|
-
`Register the ${BIN_NAME} MCP server
|
|
438
|
+
`Register the ${BIN_NAME} MCP server with the AI harnesses on this machine.`,
|
|
381
439
|
"mcp",
|
|
382
440
|
SUB_CAPABILITY,
|
|
383
441
|
);
|
|
384
442
|
|
|
385
443
|
const completionsVerb = setupVerb(
|
|
386
444
|
["setup", "completions"],
|
|
387
|
-
"Install
|
|
445
|
+
"Install TAB completion for the shell you are running.",
|
|
388
446
|
"completions",
|
|
389
447
|
SUB_CAPABILITY,
|
|
390
448
|
);
|
|
391
449
|
|
|
392
450
|
const skillsVerb = setupVerb(
|
|
393
451
|
["setup", "skills"],
|
|
394
|
-
"
|
|
452
|
+
"Link the skills you have installed into every AI harness that reads them.",
|
|
395
453
|
"skills",
|
|
396
454
|
SUB_CAPABILITY,
|
|
397
455
|
);
|
|
398
456
|
|
|
399
457
|
const lspVerb = setupVerb(
|
|
400
458
|
["setup", "lsp"],
|
|
401
|
-
"
|
|
459
|
+
"Install the Terrazzo design-token extension into your VS Code-family editors.",
|
|
402
460
|
"lsp",
|
|
403
461
|
SUB_CAPABILITY,
|
|
404
462
|
);
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* THE target table — the one list `setup` and `doctor` both read.
|
|
3
3
|
*
|
|
4
|
-
* Five rows, each with a
|
|
4
|
+
* Five rows, each with a scope column. This table drives the `setup <target>`
|
|
5
5
|
* sub-verbs, the wizard's choices, the `--dry-run` plan, the progress lines, the
|
|
6
6
|
* recap, the doctor rows, and each doctor row's `fix:` line. Adding a sixth
|
|
7
7
|
* target is one row here — not a new sub-verb, a new check, a new recap branch
|
|
8
8
|
* and a new fix string that four separate files have to agree about.
|
|
9
9
|
*
|
|
10
10
|
* A row owns: its identity (`id` is BOTH the sub-verb argument and the doctor
|
|
11
|
-
* row name, so `mcp` and `setup mcp` visibly share a token), which
|
|
11
|
+
* row name, so `mcp` and `setup mcp` visibly share a token), which scopes it can
|
|
12
12
|
* be installed into, how to DETECT its current state, how to PLAN a run from
|
|
13
13
|
* that detection, and how to COMPOSE the forward and removal effects. Detection
|
|
14
14
|
* runs once per invocation and feeds every reader, so setup and doctor cannot
|
|
@@ -47,7 +47,7 @@ import {
|
|
|
47
47
|
type LspDetection,
|
|
48
48
|
lspEditorNames,
|
|
49
49
|
lspSkipReason,
|
|
50
|
-
|
|
50
|
+
ownedLspEditors,
|
|
51
51
|
} from "./operations/setupLsp.js";
|
|
52
52
|
import {
|
|
53
53
|
composeMcp,
|
|
@@ -65,6 +65,8 @@ import {
|
|
|
65
65
|
ownedSkillLinks,
|
|
66
66
|
type SkillsDetection,
|
|
67
67
|
skillsSkipReason,
|
|
68
|
+
skillsSkipRemedy,
|
|
69
|
+
staleSkillLinks,
|
|
68
70
|
} from "./operations/setupSkills.js";
|
|
69
71
|
import {
|
|
70
72
|
type PlanAction,
|
|
@@ -73,7 +75,7 @@ import {
|
|
|
73
75
|
shortenPath,
|
|
74
76
|
type TargetId,
|
|
75
77
|
} from "./plan.js";
|
|
76
|
-
import type {
|
|
78
|
+
import type { Scope } from "./types.js";
|
|
77
79
|
|
|
78
80
|
/** What a target contributes to one plan row, before selection is applied. */
|
|
79
81
|
export interface TargetDraft {
|
|
@@ -98,13 +100,13 @@ export interface TargetDefinition<D> {
|
|
|
98
100
|
readonly id: TargetId;
|
|
99
101
|
/** Human title, used where a sentence needs one; the id is the row name. */
|
|
100
102
|
readonly title: string;
|
|
101
|
-
readonly
|
|
102
|
-
/** Real reads, up front, once per (target,
|
|
103
|
-
detect(rt: PragmaRuntime,
|
|
103
|
+
readonly scopes: readonly Scope[];
|
|
104
|
+
/** Real reads, up front, once per (target, scope) per invocation. */
|
|
105
|
+
detect(rt: PragmaRuntime, scope: Scope): Promise<D>;
|
|
104
106
|
/** What a forward run would do. */
|
|
105
|
-
plan(detection: D,
|
|
107
|
+
plan(detection: D, scope: Scope, roots: Roots): TargetDraft;
|
|
106
108
|
/** What a removal would do — composed from what detection says we own. */
|
|
107
|
-
removalPlan(detection: D,
|
|
109
|
+
removalPlan(detection: D, scope: Scope, roots: Roots): TargetDraft;
|
|
108
110
|
/**
|
|
109
111
|
* The forward effects. `chosen` is the row's per-child selection (MCP file
|
|
110
112
|
* paths); a row without children ignores it.
|
|
@@ -128,21 +130,21 @@ const defineTarget = <D>(target: TargetDefinition<D>): AnyTarget =>
|
|
|
128
130
|
const configTarget = defineTarget<ConfigDetection>({
|
|
129
131
|
id: "config",
|
|
130
132
|
title: "Global configuration",
|
|
131
|
-
|
|
133
|
+
scopes: ["global"],
|
|
132
134
|
detect: () => detectConfigFile(),
|
|
133
|
-
plan: (d,
|
|
135
|
+
plan: (d, _scope, roots) => {
|
|
134
136
|
const path = shortenPath(d.path, roots);
|
|
135
137
|
if (!d.exists) return { action: "install", detail: path };
|
|
136
138
|
return { action: "none", detail: `${path} — present` };
|
|
137
139
|
},
|
|
138
|
-
removalPlan: (d,
|
|
140
|
+
removalPlan: (d, _scope, roots) => {
|
|
139
141
|
const path = shortenPath(d.path, roots);
|
|
140
142
|
if (!d.exists) return { action: "none", detail: `${path} — absent` };
|
|
141
143
|
if (!d.isSeed) {
|
|
142
144
|
return {
|
|
143
145
|
action: "skip",
|
|
144
146
|
detail: path,
|
|
145
|
-
reason:
|
|
147
|
+
reason: `it holds your own settings, so ${path} stays`,
|
|
146
148
|
};
|
|
147
149
|
}
|
|
148
150
|
return { action: "remove", detail: path };
|
|
@@ -154,9 +156,9 @@ const configTarget = defineTarget<ConfigDetection>({
|
|
|
154
156
|
const completionsTarget = defineTarget<CompletionsDetection>({
|
|
155
157
|
id: "completions",
|
|
156
158
|
title: "Shell completions",
|
|
157
|
-
|
|
159
|
+
scopes: ["global"],
|
|
158
160
|
detect: (rt) => detectCompletions(rt.cwd),
|
|
159
|
-
plan: (d,
|
|
161
|
+
plan: (d, _scope, roots) => {
|
|
160
162
|
// Never guess a shell. Installing for the wrong one is invisible until the
|
|
161
163
|
// user presses TAB and nothing happens, so an unresolved shell is a named
|
|
162
164
|
// skip whose remedy is the one action that DOES settle it.
|
|
@@ -187,13 +189,18 @@ const completionsTarget = defineTarget<CompletionsDetection>({
|
|
|
187
189
|
};
|
|
188
190
|
}
|
|
189
191
|
const where = `${d.shell} → ${shortenPath(d.path, roots)}`;
|
|
190
|
-
|
|
192
|
+
// A no-op row says what it FOUND, not just where it looked: `no change`
|
|
193
|
+
// beside a bare path leaves the reader to guess whether the script is
|
|
194
|
+
// there and current or missing and unreachable.
|
|
195
|
+
if (d.state === "installed") {
|
|
196
|
+
return { action: "none", detail: `${where} — already up to date` };
|
|
197
|
+
}
|
|
191
198
|
return {
|
|
192
199
|
action: d.state === "stale" ? "update" : "install",
|
|
193
200
|
detail: where,
|
|
194
201
|
};
|
|
195
202
|
},
|
|
196
|
-
removalPlan: (d,
|
|
203
|
+
removalPlan: (d, _scope, roots) => {
|
|
197
204
|
if (d.path === null || d.state === "absent") {
|
|
198
205
|
return { action: "none", detail: "no script installed" };
|
|
199
206
|
}
|
|
@@ -206,7 +213,7 @@ const completionsTarget = defineTarget<CompletionsDetection>({
|
|
|
206
213
|
const lspTarget = defineTarget<LspDetection>({
|
|
207
214
|
id: "lsp",
|
|
208
215
|
title: "Terrazzo LSP extension",
|
|
209
|
-
|
|
216
|
+
scopes: ["global"],
|
|
210
217
|
detect: (rt) => detectLsp(rt.cwd),
|
|
211
218
|
plan: (d) => {
|
|
212
219
|
if (d.state === "unknown") {
|
|
@@ -225,32 +232,71 @@ const lspTarget = defineTarget<LspDetection>({
|
|
|
225
232
|
label: `${e.editor.cli} — ${e.editor.name}`,
|
|
226
233
|
action: e.installed ? ("unchanged" as const) : ("add" as const),
|
|
227
234
|
}));
|
|
235
|
+
// The detail names WHAT is being placed and WHERE — never the row's own
|
|
236
|
+
// state, which the action column already carries. `installed (VSCodium)`
|
|
237
|
+
// beside a child row reading `codium — VSCodium (unchanged)` said the
|
|
238
|
+
// editor's name twice and "nothing happens here" twice.
|
|
228
239
|
if (d.state === "installed") {
|
|
229
240
|
return {
|
|
230
241
|
action: "none",
|
|
231
|
-
detail: `
|
|
242
|
+
detail: `Terrazzo extension in ${lspEditorNames(d).join(", ")}`,
|
|
232
243
|
children,
|
|
233
244
|
};
|
|
234
245
|
}
|
|
235
246
|
const pending = d.editors.filter((e) => !e.installed);
|
|
236
247
|
return {
|
|
237
248
|
action: "install",
|
|
238
|
-
detail:
|
|
249
|
+
detail: `Terrazzo extension → ${pending
|
|
250
|
+
.map((e) => e.editor.name)
|
|
251
|
+
.join(", ")}`,
|
|
239
252
|
children,
|
|
240
253
|
};
|
|
241
254
|
},
|
|
242
|
-
removalPlan: (d) =>
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
255
|
+
removalPlan: (d) => {
|
|
256
|
+
// No editor CLI on PATH is the SAME named skip the forward plan reports:
|
|
257
|
+
// there is nothing on this machine to run an uninstall with.
|
|
258
|
+
if (d.state === "unknown") {
|
|
259
|
+
return {
|
|
260
|
+
action: "skip",
|
|
261
|
+
detail: lspSkipReason(d),
|
|
262
|
+
reason: lspSkipReason(d),
|
|
263
|
+
remedy: LSP_SKIP_REMEDY,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
// Removal is composed from what detection says we OWN — a copy that is
|
|
267
|
+
// present, whatever its version. The forward plan's version gate would let
|
|
268
|
+
// a dead pre-0.8.3 copy this command installed survive its own `--undo`.
|
|
269
|
+
const owned = ownedLspEditors(d);
|
|
270
|
+
if (owned.length === 0) {
|
|
271
|
+
return { action: "none", detail: "no editor carries the extension" };
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
action: "remove",
|
|
275
|
+
detail: `Terrazzo extension from ${owned
|
|
276
|
+
.map((e) => e.editor.name)
|
|
277
|
+
.join(", ")}`,
|
|
278
|
+
children: owned.map((e) => ({
|
|
279
|
+
key: e.editor.cli,
|
|
280
|
+
label: `${e.editor.cli} — ${e.editor.name}`,
|
|
281
|
+
action: "update" as const,
|
|
282
|
+
})),
|
|
283
|
+
};
|
|
284
|
+
},
|
|
250
285
|
compose: (d, chosen) => composeLsp(d, chosen),
|
|
251
286
|
composeRemoval: (d) => composeLspRemoval(d),
|
|
252
287
|
});
|
|
253
288
|
|
|
289
|
+
/**
|
|
290
|
+
* Why the `mcp` row has nothing to write in a scope — authored once, because
|
|
291
|
+
* `setup`'s row and `doctor`'s row are the same finding and must not word it
|
|
292
|
+
* differently. Neither sentence says "scope": the two scopes are `global` and
|
|
293
|
+
* `local project`, after the `--global` / `--local` flags that select them.
|
|
294
|
+
*/
|
|
295
|
+
export const MCP_NO_LOCATION: Record<Scope, string> = {
|
|
296
|
+
global: "no AI harness on this machine keeps a global config",
|
|
297
|
+
project: "no AI harness in this project keeps a per-project config",
|
|
298
|
+
};
|
|
299
|
+
|
|
254
300
|
/** One MCP file as a plan child: its path, the harnesses sharing it, its state. */
|
|
255
301
|
const mcpChild = (
|
|
256
302
|
d: McpDetection,
|
|
@@ -262,7 +308,7 @@ const mcpChild = (
|
|
|
262
308
|
key: group.path,
|
|
263
309
|
label: shortenPath(group.path, roots),
|
|
264
310
|
action:
|
|
265
|
-
state === "
|
|
311
|
+
state === "registered"
|
|
266
312
|
? "unchanged"
|
|
267
313
|
: state === "drifted"
|
|
268
314
|
? "update"
|
|
@@ -273,35 +319,34 @@ const mcpChild = (
|
|
|
273
319
|
const mcpTarget = defineTarget<McpDetection>({
|
|
274
320
|
id: "mcp",
|
|
275
321
|
title: "MCP server registration",
|
|
276
|
-
|
|
277
|
-
detect: (rt,
|
|
278
|
-
plan: (d,
|
|
322
|
+
scopes: ["global", "project"],
|
|
323
|
+
detect: (rt, scope) => detectMcp(rt, scope),
|
|
324
|
+
plan: (d, scope, roots) => {
|
|
279
325
|
if (d.groups.length === 0) {
|
|
280
326
|
return {
|
|
281
327
|
action: "skip",
|
|
282
|
-
detail:
|
|
283
|
-
reason:
|
|
284
|
-
band === "project"
|
|
285
|
-
? "no AI harness in this project writes a per-repository config"
|
|
286
|
-
: "no detected AI harness declares a user-level config location",
|
|
328
|
+
detail: MCP_NO_LOCATION[scope],
|
|
329
|
+
reason: MCP_NO_LOCATION[scope],
|
|
287
330
|
};
|
|
288
331
|
}
|
|
289
332
|
const children = d.groups.map((group) => mcpChild(d, group, roots));
|
|
290
333
|
const pending = children.filter((c) => c.action !== "unchanged");
|
|
291
334
|
return {
|
|
292
335
|
action: pending.length === 0 ? "none" : "update",
|
|
293
|
-
detail: `${d.groups.length} ${
|
|
336
|
+
detail: `${d.groups.length} ${
|
|
337
|
+
d.groups.length === 1 ? "config file" : "config files"
|
|
338
|
+
}`,
|
|
294
339
|
children,
|
|
295
340
|
};
|
|
296
341
|
},
|
|
297
|
-
removalPlan: (d,
|
|
342
|
+
removalPlan: (d, _scope, roots) => {
|
|
298
343
|
const owned = ownedMcpGroups(d);
|
|
299
344
|
if (owned.length === 0) {
|
|
300
345
|
return { action: "none", detail: "no entry to remove" };
|
|
301
346
|
}
|
|
302
347
|
return {
|
|
303
348
|
action: "remove",
|
|
304
|
-
detail: `${owned.length} ${owned.length === 1 ? "file" : "files"}`,
|
|
349
|
+
detail: `${owned.length} ${owned.length === 1 ? "config file" : "config files"}`,
|
|
305
350
|
children: owned.map((group) => ({
|
|
306
351
|
key: group.path,
|
|
307
352
|
label: shortenPath(group.path, roots),
|
|
@@ -317,19 +362,42 @@ const mcpTarget = defineTarget<McpDetection>({
|
|
|
317
362
|
const skillsTarget = defineTarget<SkillsDetection>({
|
|
318
363
|
id: "skills",
|
|
319
364
|
title: "Skill symlinks",
|
|
320
|
-
|
|
321
|
-
detect: (rt,
|
|
322
|
-
plan: (d,
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
365
|
+
scopes: ["global", "project"],
|
|
366
|
+
detect: (rt, scope) => detectSkills(rt, scope),
|
|
367
|
+
plan: (d, scope, roots) => {
|
|
368
|
+
// A forward run RECONCILES: stale links this scope owns are its work too, so
|
|
369
|
+
// an orphan-only tree is actionable rather than a row that plans `none` and
|
|
370
|
+
// is then deselected into doing nothing. `staleSkillLinks` carries the
|
|
371
|
+
// absent-root safety gate, so a machine whose source root does not exist
|
|
372
|
+
// contributes no sweep and the row falls back to the honest skip.
|
|
373
|
+
const stale = staleSkillLinks(d);
|
|
374
|
+
if (!d.available && stale.length === 0) {
|
|
375
|
+
const short = shortenPath(d.sourceRoot, roots);
|
|
376
|
+
const reason = skillsSkipReason(short, scope, d.rootExists);
|
|
377
|
+
// A skip with no remedy is a dead end — this one names the command that
|
|
378
|
+
// fills the scope's source root, exactly as the completions and lsp skips
|
|
379
|
+
// beside it name theirs.
|
|
380
|
+
return {
|
|
381
|
+
action: "skip",
|
|
382
|
+
detail: reason,
|
|
383
|
+
reason,
|
|
384
|
+
remedy: skillsSkipRemedy(short, scope),
|
|
385
|
+
};
|
|
326
386
|
}
|
|
327
387
|
const dirs = d.targets.map((t) => shortenPath(t.dir, roots)).join(", ");
|
|
328
|
-
const where = `${d.skillCount} ${d.skillCount === 1 ? "skill" : "skills"} → ${d.targets.length} ${d.targets.length === 1 ? "
|
|
388
|
+
const where = `${d.skillCount} ${d.skillCount === 1 ? "skill" : "skills"} → ${d.targets.length} ${d.targets.length === 1 ? "folder" : "folders"} (${dirs})`;
|
|
389
|
+
const detail =
|
|
390
|
+
stale.length === 0
|
|
391
|
+
? where
|
|
392
|
+
: `${where}, ${stale.length} stale ${stale.length === 1 ? "link" : "links"} to remove`;
|
|
329
393
|
const pending = d.actions.filter((a) => a.action !== "skipped");
|
|
330
|
-
|
|
394
|
+
if (pending.length > 0) return { action: "link", detail };
|
|
395
|
+
// Nothing to link, but something to retire: `update` is the table's word
|
|
396
|
+
// for "this row has work that is not a fresh install".
|
|
397
|
+
if (stale.length > 0) return { action: "update", detail };
|
|
398
|
+
return { action: "none", detail };
|
|
331
399
|
},
|
|
332
|
-
removalPlan: (d,
|
|
400
|
+
removalPlan: (d, _scope, roots) => {
|
|
333
401
|
const owned = ownedSkillLinks(d);
|
|
334
402
|
if (owned.length === 0) {
|
|
335
403
|
return { action: "none", detail: "no link to remove" };
|
|
@@ -362,6 +430,6 @@ export const TARGETS: readonly AnyTarget[] = [
|
|
|
362
430
|
export const findTarget = (id: string): AnyTarget | undefined =>
|
|
363
431
|
TARGETS.find((target) => target.id === id);
|
|
364
432
|
|
|
365
|
-
/** Whether a target can be installed into a
|
|
366
|
-
export const
|
|
367
|
-
target.
|
|
433
|
+
/** Whether a target can be installed into a scope. */
|
|
434
|
+
export const supportsScope = (target: AnyTarget, scope: Scope): boolean =>
|
|
435
|
+
target.scopes.includes(scope);
|
|
@@ -15,31 +15,23 @@ export type SetupMode =
|
|
|
15
15
|
| "mcp"
|
|
16
16
|
| "skills";
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* The resolved `--scope` selection: which config band(s) a run touches.
|
|
28
|
-
* `global` is the DEFAULT — the user/home band a machine-level installer
|
|
29
|
-
* configures; `project` is the opt-in per-repository band; `both` is the
|
|
30
|
-
* explicit "run each band in one invocation". Structurally mirrors the
|
|
31
|
-
* harnesses `ScopeSelection`.
|
|
32
|
-
*/
|
|
33
|
-
export type ScopeSelection = "project" | "global" | "both";
|
|
18
|
+
// The scope types are part of the rendering vocabulary — the one module that
|
|
19
|
+
// owns both the states and the words for them. Re-exported here so setup's
|
|
20
|
+
// type surface stays complete; the definitions (and the structural pin against
|
|
21
|
+
// `@canonical/harnesses`) live with the vocabulary.
|
|
22
|
+
export type {
|
|
23
|
+
Scope,
|
|
24
|
+
ScopeSelection,
|
|
25
|
+
} from "../../kernel/render/vocabulary.js";
|
|
34
26
|
|
|
35
27
|
/**
|
|
36
28
|
* The prior state of an MCP target file, read up front by `detectMcp`:
|
|
37
|
-
* `absent` (no pragma entry yet), `
|
|
29
|
+
* `absent` (no pragma entry yet), `registered` (a matching pragma entry already
|
|
38
30
|
* present in every write — a re-run skips it), or `drifted` (a pragma entry
|
|
39
31
|
* exists but differs, so a write updates it). Mirrors the skills step's
|
|
40
32
|
* created/skipped/replaced idempotency at the file grain.
|
|
41
33
|
*/
|
|
42
|
-
export type McpTargetState = "absent" | "
|
|
34
|
+
export type McpTargetState = "absent" | "registered" | "drifted";
|
|
43
35
|
|
|
44
36
|
/**
|
|
45
37
|
* The prior on-disk state of the shell-completion script, read up front by
|