@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
|
@@ -13,85 +13,107 @@
|
|
|
13
13
|
* a piped run renders byte-for-byte plain.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import chalk from "chalk";
|
|
17
16
|
import { BIN_NAME } from "../../constants.js";
|
|
18
17
|
import { defaultStyle, type RenderStyle } from "../../kernel/render/style.js";
|
|
19
|
-
import { BAND_LABELS } from "../shared/index.js";
|
|
20
18
|
import {
|
|
19
|
+
NOT_RUN_GLYPH,
|
|
20
|
+
OUTCOME_GLYPHS,
|
|
21
|
+
outcomeRemedyWord,
|
|
22
|
+
SCOPE_LABELS,
|
|
23
|
+
SCOPE_PHRASES,
|
|
24
|
+
} from "../../kernel/render/vocabulary.js";
|
|
25
|
+
import {
|
|
26
|
+
type PlanAction,
|
|
21
27
|
type PlanChildRow,
|
|
22
28
|
type PlanRow,
|
|
23
29
|
planTally,
|
|
24
30
|
type SetupPlan,
|
|
25
31
|
shortenPath,
|
|
26
32
|
} from "./plan.js";
|
|
27
|
-
import type {
|
|
28
|
-
|
|
29
|
-
/** The
|
|
30
|
-
const
|
|
31
|
-
done: "✓",
|
|
32
|
-
noop: "✓",
|
|
33
|
-
removed: "✓",
|
|
34
|
-
kept: "○",
|
|
35
|
-
skipped: "○",
|
|
36
|
-
failed: "✗",
|
|
37
|
-
} as const;
|
|
33
|
+
import type { Scope } from "./types.js";
|
|
34
|
+
|
|
35
|
+
/** The header's scope phrase: `global`, `local project`, or both of them. */
|
|
36
|
+
const scopePhrase = (scope: SetupPlan["scope"]): string => SCOPE_PHRASES[scope];
|
|
38
37
|
|
|
39
38
|
/**
|
|
40
|
-
* The
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
39
|
+
* The header line. It names the scope and BOTH roots exactly once, which is what
|
|
40
|
+
* frees every row below to render its paths root-relative instead of repeating
|
|
41
|
+
* a 120-character prefix per line. The roots are LABELLED (`home:`, `project:`)
|
|
42
|
+
* because the `~` and `.` markers the rows use are only legible once something
|
|
43
|
+
* has said what they stand for.
|
|
44
44
|
*/
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
function header(plan: SetupPlan, lead: string): string {
|
|
46
|
+
const project = shortenPath(plan.roots.project, {
|
|
47
|
+
global: plan.roots.global,
|
|
48
|
+
project: "",
|
|
49
|
+
});
|
|
50
|
+
return `${lead} — ${scopePhrase(plan.scope)} (home: ~ · project: ${project})`;
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
54
|
+
* The plain verb each action reads as. The middle column is a column of VERBS:
|
|
55
|
+
* what this row will do. It used to render the raw {@link PlanAction} token, so
|
|
56
|
+
* it read as a column of status codes — and `none` was the worst of them,
|
|
57
|
+
* standing equally for "already correct" and "nothing found here". The two are
|
|
58
|
+
* now told apart by the pair: the word says nothing will happen, the detail
|
|
59
|
+
* beside it says what was found.
|
|
57
60
|
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
61
|
+
* `none` and `skip` are worded direction-neutrally on purpose. The SAME renderer
|
|
62
|
+
* draws the `--undo` plan, where a target-specific negation ("nothing to create"
|
|
63
|
+
* on the `config` row) would be plainly false — there the row is not creating
|
|
64
|
+
* anything, it is declining to delete.
|
|
65
|
+
*
|
|
66
|
+
* The token itself is untouched: `action` is the machine field `--format json`
|
|
67
|
+
* publishes, and this is the display layer over it.
|
|
60
68
|
*/
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
69
|
+
const ACTION_WORDS: Record<PlanAction, string> = {
|
|
70
|
+
install: "install",
|
|
71
|
+
update: "update",
|
|
72
|
+
link: "link",
|
|
73
|
+
remove: "remove",
|
|
74
|
+
none: "no change",
|
|
75
|
+
skip: "nothing to do",
|
|
76
|
+
};
|
|
65
77
|
|
|
66
|
-
/** The
|
|
67
|
-
const
|
|
68
|
-
|
|
78
|
+
/** The distinct actions a row's children carry (empty when it has none). */
|
|
79
|
+
const childActions = (row: PlanRow): Set<PlanChildRow["action"]> =>
|
|
80
|
+
new Set((row.children ?? []).map((child) => child.action));
|
|
69
81
|
|
|
70
82
|
/**
|
|
71
|
-
* The
|
|
72
|
-
*
|
|
73
|
-
*
|
|
83
|
+
* The middle column.
|
|
84
|
+
*
|
|
85
|
+
* A row with children takes its word from THEM when they agree, because the
|
|
86
|
+
* row-level token can disagree with every one of its own files: registering the
|
|
87
|
+
* MCP server in three files that have never held it plans as `update` (the
|
|
88
|
+
* table's word for "this row has work that is not a fresh install"), and
|
|
89
|
+
* printing `update` over three `(add)` children described the opposite of what
|
|
90
|
+
* the run would do. Before this the cell held the row's DETAIL instead — `3
|
|
91
|
+
* files`, which is a count, not an action, and left the plan with no column
|
|
92
|
+
* that said what would happen.
|
|
74
93
|
*/
|
|
75
|
-
function
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
return
|
|
94
|
+
function actionCell(row: PlanRow): string {
|
|
95
|
+
const kinds = childActions(row);
|
|
96
|
+
if (row.action === "update" && kinds.size === 1 && kinds.has("add")) {
|
|
97
|
+
return ACTION_WORDS.install;
|
|
98
|
+
}
|
|
99
|
+
return ACTION_WORDS[row.action];
|
|
81
100
|
}
|
|
82
101
|
|
|
83
|
-
/**
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
102
|
+
/**
|
|
103
|
+
* One child rendered inline. The per-child action is printed only when the
|
|
104
|
+
* children DISAGREE — when they all do the same thing the row's own verb has
|
|
105
|
+
* just said it, and `lsp no change codium — VSCodium (unchanged)` said
|
|
106
|
+
* "nothing happens here" twice in two columns.
|
|
107
|
+
*/
|
|
108
|
+
const childCell = (row: PlanRow, child: PlanChildRow): string =>
|
|
109
|
+
childActions(row).size === 1
|
|
110
|
+
? child.label
|
|
111
|
+
: `${child.label} (${child.action})`;
|
|
90
112
|
|
|
91
113
|
/** The right column: the children joined, or the row's own detail. */
|
|
92
114
|
const detailCell = (row: PlanRow): string =>
|
|
93
115
|
row.children && row.children.length > 0
|
|
94
|
-
? row.children.map(childCell).join(" · ")
|
|
116
|
+
? row.children.map((child) => childCell(row, child)).join(" · ")
|
|
95
117
|
: row.action === "skip"
|
|
96
118
|
? (row.reason ?? row.detail)
|
|
97
119
|
: row.detail;
|
|
@@ -100,13 +122,13 @@ const detailCell = (row: PlanRow): string =>
|
|
|
100
122
|
const widthOf = (rows: readonly PlanRow[], pick: (row: PlanRow) => string) =>
|
|
101
123
|
Math.max(0, ...rows.map((row) => pick(row).length));
|
|
102
124
|
|
|
103
|
-
/** Group rows by
|
|
104
|
-
function
|
|
105
|
-
const
|
|
106
|
-
return
|
|
107
|
-
.map((
|
|
108
|
-
|
|
109
|
-
plan.rows.filter((row) => row.
|
|
125
|
+
/** Group rows by scope, in the plan's own scope order. */
|
|
126
|
+
function byScope(plan: SetupPlan): [Scope, PlanRow[]][] {
|
|
127
|
+
const scopes: Scope[] = ["global", "project"];
|
|
128
|
+
return scopes
|
|
129
|
+
.map((scope): [Scope, PlanRow[]] => [
|
|
130
|
+
scope,
|
|
131
|
+
plan.rows.filter((row) => row.scope === scope),
|
|
110
132
|
])
|
|
111
133
|
.filter(([, rows]) => rows.length > 0);
|
|
112
134
|
}
|
|
@@ -128,9 +150,9 @@ export function renderPlanTable(
|
|
|
128
150
|
const idWidth = widthOf(plan.rows, (row) => row.target);
|
|
129
151
|
const actionWidth = widthOf(plan.rows, actionCell);
|
|
130
152
|
const lines = [style.bold(header(plan, options.lead)), ""];
|
|
131
|
-
const groups =
|
|
132
|
-
for (const [
|
|
133
|
-
if (plan.scope === "both") lines.push(style.bold(
|
|
153
|
+
const groups = byScope(plan);
|
|
154
|
+
for (const [scope, rows] of groups) {
|
|
155
|
+
if (plan.scope === "both") lines.push(style.bold(SCOPE_LABELS[scope]));
|
|
134
156
|
for (const row of rows) {
|
|
135
157
|
lines.push(
|
|
136
158
|
` ${row.target.padEnd(idWidth)} ${actionCell(row).padEnd(actionWidth)} ${style.dim(detailCell(row))}`,
|
|
@@ -141,6 +163,65 @@ export function renderPlanTable(
|
|
|
141
163
|
return lines.join("\n");
|
|
142
164
|
}
|
|
143
165
|
|
|
166
|
+
/**
|
|
167
|
+
* What was DETECTED, rendered before anything is asked.
|
|
168
|
+
*
|
|
169
|
+
* The gap this closes is a rendering one, not an ordering one: detection is
|
|
170
|
+
* complete before the prompt array is even constructed (`buildSetupRun` does
|
|
171
|
+
* resolveRoots → detectTargets → buildPlan, and only then builds `prompts`), and
|
|
172
|
+
* the Ink view mounts no React until the FIRST prompt effect. So the facts were
|
|
173
|
+
* sitting in `run.plan` the whole time with nothing rendering them — the wizard
|
|
174
|
+
* leaked detection only through prompt-label suffixes, and `renderPlanTable` is
|
|
175
|
+
* reached only by `--dry-run` or the non-interactive preview. A wizard run
|
|
176
|
+
* never rendered it at all, and the confirm gate shows an EFFECTS summary, not
|
|
177
|
+
* a detection one.
|
|
178
|
+
*
|
|
179
|
+
* It is a plain-string formatter on purpose. It rides `rt.report` — the non-Ink
|
|
180
|
+
* stderr seam, `--quiet`-aware and a no-op over MCP — because pragma contains no
|
|
181
|
+
* React and no Ink, and three PROTECTED tests exist to keep it that way.
|
|
182
|
+
*
|
|
183
|
+
* The default lists DETECTED rows only; `--verbose` lists everything. "Detected"
|
|
184
|
+
* is `action !== "skip"`, which is not a new partition: it is exactly the
|
|
185
|
+
* predicate the row multiselect already uses to build its choices, so the
|
|
186
|
+
* summary and the question that follows it describe the same set.
|
|
187
|
+
*
|
|
188
|
+
* @param plan - The plan as detected (no outcomes yet).
|
|
189
|
+
* @param options - `verbose` widens detected-only to the whole table.
|
|
190
|
+
* @param style - Injected for tests; defaults to the shared TTY seam.
|
|
191
|
+
* @returns The rendered block, or `undefined` when there is nothing to say.
|
|
192
|
+
*/
|
|
193
|
+
export function renderDetectionSummary(
|
|
194
|
+
plan: SetupPlan,
|
|
195
|
+
options: { verbose?: boolean } = {},
|
|
196
|
+
style: RenderStyle = defaultStyle(),
|
|
197
|
+
): string | undefined {
|
|
198
|
+
const shown = options.verbose
|
|
199
|
+
? plan.rows
|
|
200
|
+
: plan.rows.filter((row) => row.action !== "skip");
|
|
201
|
+
if (shown.length === 0) return undefined;
|
|
202
|
+
const hidden = plan.rows.length - shown.length;
|
|
203
|
+
const idWidth = widthOf(shown, (row) => row.target);
|
|
204
|
+
const actionWidth = widthOf(shown, actionCell);
|
|
205
|
+
const lines = [style.bold(header(plan, "Detected")), ""];
|
|
206
|
+
for (const [scope, rows] of byScope({ ...plan, rows: shown })) {
|
|
207
|
+
if (plan.scope === "both") lines.push(style.bold(SCOPE_LABELS[scope]));
|
|
208
|
+
for (const row of rows) {
|
|
209
|
+
lines.push(
|
|
210
|
+
` ${row.target.padEnd(idWidth)} ${actionCell(row).padEnd(actionWidth)} ${style.dim(detailCell(row))}`,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (hidden > 0) {
|
|
215
|
+
lines.push(
|
|
216
|
+
"",
|
|
217
|
+
style.dim(
|
|
218
|
+
`${hidden} target${hidden === 1 ? "" : "s"} not detected here — --verbose lists them`,
|
|
219
|
+
),
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
return lines.join("\n");
|
|
223
|
+
}
|
|
224
|
+
|
|
144
225
|
/**
|
|
145
226
|
* One progress line, emitted as a row's outcome lands. Same columns as the
|
|
146
227
|
* recap, so a reader watching the run and a reader reading the recap afterwards
|
|
@@ -156,27 +237,30 @@ export function renderProgressLine(
|
|
|
156
237
|
idWidth: number,
|
|
157
238
|
style: RenderStyle = defaultStyle(),
|
|
158
239
|
): string {
|
|
159
|
-
const painted = withRed(style);
|
|
160
240
|
const outcome = row.outcome;
|
|
161
241
|
|
|
162
242
|
// No outcome at all: the row was offered and left unselected. It is neither a
|
|
163
243
|
// success nor a skip-for-cause, and painting it green would claim work that
|
|
164
244
|
// never happened, so it gets a neutral marker and says plainly what it is.
|
|
165
245
|
if (outcome === undefined) {
|
|
166
|
-
return `${
|
|
246
|
+
return `${style.dim(NOT_RUN_GLYPH)} ${row.target.padEnd(idWidth)} ${style.dim(`${row.detail} — not selected`)}`;
|
|
167
247
|
}
|
|
168
248
|
|
|
169
|
-
const glyph =
|
|
249
|
+
const glyph = OUTCOME_GLYPHS[outcome.status];
|
|
170
250
|
const tinted =
|
|
171
251
|
outcome.status === "failed"
|
|
172
|
-
?
|
|
252
|
+
? style.red(glyph)
|
|
173
253
|
: outcome.status === "skipped" || outcome.status === "kept"
|
|
174
|
-
?
|
|
175
|
-
:
|
|
254
|
+
? style.yellow(glyph)
|
|
255
|
+
: style.green(glyph);
|
|
176
256
|
const note = outcome.note;
|
|
177
257
|
const body =
|
|
178
258
|
outcome.status === "skipped"
|
|
179
|
-
?
|
|
259
|
+
? // A colon, not a dash: the reasons carry their own em-dash clause
|
|
260
|
+
// ("no skills installed yet — pack skills arrive with …"), and two
|
|
261
|
+
// dashes in one line read as one run-on sentence rather than a label
|
|
262
|
+
// in front of a reason.
|
|
263
|
+
`skipped: ${row.reason ?? row.detail}`
|
|
180
264
|
: note
|
|
181
265
|
? `${row.detail} — ${note}`
|
|
182
266
|
: row.detail;
|
|
@@ -207,15 +291,19 @@ export function renderRecap(
|
|
|
207
291
|
`${lead}: ${configured} of ${accountable} targets configured — ${scopePhrase(plan.scope)}`,
|
|
208
292
|
),
|
|
209
293
|
];
|
|
210
|
-
for (const [
|
|
211
|
-
if (plan.scope === "both")
|
|
294
|
+
for (const [scope, rows] of byScope(plan)) {
|
|
295
|
+
if (plan.scope === "both")
|
|
296
|
+
lines.push(style.bold(` ${SCOPE_LABELS[scope]}`));
|
|
212
297
|
for (const row of rows) {
|
|
213
298
|
lines.push(` ${renderProgressLine(row, idWidth, style)}`);
|
|
214
299
|
const remedy = row.outcome?.remedy;
|
|
215
300
|
if (remedy) lines.push(` ${style.dim(remedy)}`);
|
|
216
301
|
}
|
|
217
302
|
}
|
|
218
|
-
lines.push(
|
|
303
|
+
lines.push(
|
|
304
|
+
"",
|
|
305
|
+
style.dim(`Check this again any time with \`${BIN_NAME} doctor\`.`),
|
|
306
|
+
);
|
|
219
307
|
return lines.join("\n");
|
|
220
308
|
}
|
|
221
309
|
|
|
@@ -227,15 +315,22 @@ export function renderPlanLlm(plan: SetupPlan, lead = "Setup"): string {
|
|
|
227
315
|
// Before a run there is no outcome to report, so the row shows its ACTION
|
|
228
316
|
// behind a neutral marker. Reusing the `noop` glyph here painted every row
|
|
229
317
|
// of an unapplied plan — skips included — with a green check.
|
|
230
|
-
const glyph = status === undefined ? NOT_RUN_GLYPH :
|
|
318
|
+
const glyph = status === undefined ? NOT_RUN_GLYPH : OUTCOME_GLYPHS[status];
|
|
231
319
|
const state = status ?? row.action;
|
|
232
320
|
lines.push(
|
|
233
|
-
`- ${glyph} **${row.target}** (${row.
|
|
321
|
+
`- ${glyph} **${row.target}** (${row.scope}): ${state} — ${detailCell(row)}`,
|
|
234
322
|
);
|
|
235
323
|
for (const child of row.children ?? []) {
|
|
236
|
-
lines.push(` - ${childCell(child)}`);
|
|
324
|
+
lines.push(` - ${childCell(row, child)}`);
|
|
325
|
+
}
|
|
326
|
+
// `fix:` for a row that failed, `next:` for one that skipped — a skip is
|
|
327
|
+
// not a fault, and labelling its instruction as a repair is what teaches a
|
|
328
|
+
// reader to treat skips as failures.
|
|
329
|
+
if (row.outcome?.remedy) {
|
|
330
|
+
lines.push(
|
|
331
|
+
` - _${outcomeRemedyWord(row.outcome.status)}:_ ${row.outcome.remedy}`,
|
|
332
|
+
);
|
|
237
333
|
}
|
|
238
|
-
if (row.outcome?.remedy) lines.push(` - _fix:_ ${row.outcome.remedy}`);
|
|
239
334
|
}
|
|
240
335
|
return lines.join("\n");
|
|
241
336
|
}
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import { isAbsolute, relative, sep } from "node:path";
|
|
21
|
-
import type {
|
|
21
|
+
import type { OutcomeStatus } from "../../kernel/render/vocabulary.js";
|
|
22
|
+
import type { Scope, ScopeSelection } from "./types.js";
|
|
22
23
|
|
|
23
24
|
/** The five setup targets, in table (and therefore display) order. */
|
|
24
25
|
export const TARGET_IDS = [
|
|
@@ -45,14 +46,11 @@ export type PlanAction =
|
|
|
45
46
|
| "none"
|
|
46
47
|
| "skip";
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
| "failed"
|
|
54
|
-
| "removed"
|
|
55
|
-
| "kept";
|
|
49
|
+
// How one row ended. Defined with the rendering vocabulary (one module owns
|
|
50
|
+
// the states and their glyphs); re-exported here because the plan is where
|
|
51
|
+
// every other consumer meets it. `skipped` is NOT a failure — see
|
|
52
|
+
// {@link planExitFailed}.
|
|
53
|
+
export type { OutcomeStatus } from "../../kernel/render/vocabulary.js";
|
|
56
54
|
|
|
57
55
|
/** Per-file / per-link detail under a row (MCP files; skill link dirs). */
|
|
58
56
|
export interface PlanChildRow {
|
|
@@ -62,7 +60,14 @@ export interface PlanChildRow {
|
|
|
62
60
|
* from {@link label}, which is display text and may be re-worded freely.
|
|
63
61
|
*/
|
|
64
62
|
readonly key: string;
|
|
65
|
-
/**
|
|
63
|
+
/**
|
|
64
|
+
* Display text as the producers actually emit it: the MCP row's children
|
|
65
|
+
* carry the file's shortened path alone (`~/.claude.json`), the LSP row's
|
|
66
|
+
* carry `cli — Editor Name` (`codium — VSCodium`). This doc once promised
|
|
67
|
+
* a path-plus-harness pairing (`~/.claude.json — Claude Code`) that no
|
|
68
|
+
* producer ever emitted; the harness names sharing an MCP file are the
|
|
69
|
+
* doctor inventory row's to report, not this label's.
|
|
70
|
+
*/
|
|
66
71
|
readonly label: string;
|
|
67
72
|
readonly action: "add" | "update" | "unchanged" | "skip";
|
|
68
73
|
/** REQUIRED when the action is `skip` — the named reason. */
|
|
@@ -82,10 +87,10 @@ export interface PlanOutcome {
|
|
|
82
87
|
readonly remedy?: string;
|
|
83
88
|
}
|
|
84
89
|
|
|
85
|
-
/** One row of the plan: one target in one
|
|
90
|
+
/** One row of the plan: one target in one scope. */
|
|
86
91
|
export interface PlanRow {
|
|
87
92
|
readonly target: TargetId;
|
|
88
|
-
readonly
|
|
93
|
+
readonly scope: Scope;
|
|
89
94
|
readonly action: PlanAction;
|
|
90
95
|
/** Right-hand column: what and where, rendered root-relative. */
|
|
91
96
|
readonly detail: string;
|
|
@@ -74,7 +74,7 @@ afterEach(() => {
|
|
|
74
74
|
roots.length = 0;
|
|
75
75
|
});
|
|
76
76
|
|
|
77
|
-
/** A project with Cursor detected — one project-
|
|
77
|
+
/** A project with Cursor detected — one project-scope file, `.cursor/mcp.json`. */
|
|
78
78
|
const cursorProject = (): { cwd: string; config: string } => {
|
|
79
79
|
const cwd = tmp("pragma-mcpc-proj-");
|
|
80
80
|
mkdirSync(join(cwd, ".cursor"), { recursive: true });
|
|
@@ -122,7 +122,7 @@ describe("installing twice never produces a second entry", () => {
|
|
|
122
122
|
const { cwd, config } = cursorProject();
|
|
123
123
|
await install(cwd);
|
|
124
124
|
const detection = await detectMcp(bootRuntime(FLAGS, cwd), "project");
|
|
125
|
-
expect(mcpGroupState(detection, config)).toBe("
|
|
125
|
+
expect(mcpGroupState(detection, config)).toBe("registered");
|
|
126
126
|
});
|
|
127
127
|
});
|
|
128
128
|
|
|
@@ -278,7 +278,7 @@ describe("every drift shape converges in one write, then stays converged", () =>
|
|
|
278
278
|
const repaired = readFileSync(config, "utf-8");
|
|
279
279
|
|
|
280
280
|
const after = await detectMcp(bootRuntime(FLAGS, cwd), "project");
|
|
281
|
-
expect(mcpGroupState(after, config)).toBe("
|
|
281
|
+
expect(mcpGroupState(after, config)).toBe("registered");
|
|
282
282
|
|
|
283
283
|
// A second run after the repair changes nothing.
|
|
284
284
|
await install(cwd);
|
|
@@ -341,7 +341,7 @@ describe("every drift shape converges in one write, then stays converged", () =>
|
|
|
341
341
|
expect(entry).toEqual({ command: "pragma", args: ["mcp", "serve"] });
|
|
342
342
|
expect(entry).not.toHaveProperty("cwd");
|
|
343
343
|
const after = await detectMcp(bootRuntime(FLAGS, cwd), "global");
|
|
344
|
-
expect(mcpGroupState(after, config)).toBe("
|
|
344
|
+
expect(mcpGroupState(after, config)).toBe("registered");
|
|
345
345
|
});
|
|
346
346
|
|
|
347
347
|
it("leaves extra keys on OUR entry alone — they are not ours to drop", async () => {
|
|
@@ -355,7 +355,7 @@ describe("every drift shape converges in one write, then stays converged", () =>
|
|
|
355
355
|
writeFileSync(config, `${JSON.stringify(parsed, null, 2)}\n`);
|
|
356
356
|
|
|
357
357
|
const detection = await detectMcp(bootRuntime(FLAGS, cwd), "project");
|
|
358
|
-
expect(mcpGroupState(detection, config)).toBe("
|
|
358
|
+
expect(mcpGroupState(detection, config)).toBe("registered");
|
|
359
359
|
|
|
360
360
|
await install(cwd);
|
|
361
361
|
expect(
|
|
@@ -363,8 +363,8 @@ describe("every drift shape converges in one write, then stays converged", () =>
|
|
|
363
363
|
).toBe(5000);
|
|
364
364
|
});
|
|
365
365
|
|
|
366
|
-
it("keeps the two
|
|
367
|
-
// Cursor has both a project file and a home file. Writing one
|
|
366
|
+
it("keeps the two scopes of one harness independent", async () => {
|
|
367
|
+
// Cursor has both a project file and a home file. Writing one scope must
|
|
368
368
|
// not touch the other, and each converges on its own terms — the project
|
|
369
369
|
// entry records a cwd, the global entry omits it.
|
|
370
370
|
const cwd = tmp("pragma-mcpc-proj-");
|
|
@@ -380,13 +380,13 @@ describe("every drift shape converges in one write, then stays converged", () =>
|
|
|
380
380
|
const projectBytes = readFileSync(projectConfig, "utf-8");
|
|
381
381
|
expect(existsSync(globalConfig)).toBe(true);
|
|
382
382
|
|
|
383
|
-
// Each
|
|
383
|
+
// Each scope carries its own shape.
|
|
384
384
|
expect(JSON.parse(projectBytes).mcpServers.pragma.cwd).toBe(cwd);
|
|
385
385
|
expect(
|
|
386
386
|
JSON.parse(readFileSync(globalConfig, "utf-8")).mcpServers.pragma,
|
|
387
387
|
).not.toHaveProperty("cwd");
|
|
388
388
|
|
|
389
|
-
// Undoing one
|
|
389
|
+
// Undoing one scope leaves the other intact.
|
|
390
390
|
await undo(cwd, GLOBAL);
|
|
391
391
|
expect(readFileSync(projectConfig, "utf-8")).toBe(projectBytes);
|
|
392
392
|
});
|