@canonical/pragma-cli 0.36.0 → 0.38.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 +3 -2
- package/bundled-skills/adoption-a1-styles/SKILL.md +24 -0
- package/bundled-skills/adoption-a2-components/SKILL.md +22 -6
- package/bundled-skills/anatomy-author/ANATOMY_DSL_SPEC.md +1397 -327
- package/bundled-skills/anatomy-author/SKILL.md +840 -137
- package/bundled-skills/design-auditor/SKILL.md +133 -9
- package/bundled-skills/implement-component/SKILL.md +192 -23
- package/bundled-skills/specify-component/SKILL.md +124 -12
- package/bundled-skills/specify-pattern/SKILL.md +47 -8
- package/dist/package.json +23 -20
- package/dist/pragma.conf.js +963 -40
- package/dist/pragma.conf.js.map +1 -1
- package/dist/src/capabilities/capabilities/catalog.js +14 -5
- package/dist/src/capabilities/capabilities/catalog.js.map +1 -1
- package/dist/src/capabilities/capabilities/hints.js +33 -5
- package/dist/src/capabilities/capabilities/hints.js.map +1 -1
- package/dist/src/capabilities/config/show.render.js +30 -2
- package/dist/src/capabilities/config/show.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/doctor/checks/checkNodeVersion.js +11 -6
- package/dist/src/capabilities/doctor/checks/checkNodeVersion.js.map +1 -1
- package/dist/src/capabilities/doctor/checks/harnessInventory.js +1 -1
- package/dist/src/capabilities/doctor/checks/harnessInventory.js.map +1 -1
- 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 +104 -14
- package/dist/src/capabilities/doctor/checks/targetHealth.js.map +1 -1
- package/dist/src/capabilities/graph/connect.render.js +101 -0
- package/dist/src/capabilities/graph/connect.render.js.map +1 -0
- package/dist/src/capabilities/graph/connect.types.js +21 -0
- package/dist/src/capabilities/graph/connect.types.js.map +1 -0
- package/dist/src/capabilities/graph/connect.verb.js +147 -0
- package/dist/src/capabilities/graph/connect.verb.js.map +1 -0
- package/dist/src/capabilities/graph/index.js +5 -3
- package/dist/src/capabilities/graph/index.js.map +1 -1
- package/dist/src/capabilities/graph/queryText.js +206 -0
- package/dist/src/capabilities/graph/queryText.js.map +1 -0
- package/dist/src/capabilities/graph/relationIndex.js +302 -0
- package/dist/src/capabilities/graph/relationIndex.js.map +1 -0
- package/dist/src/capabilities/graph/runConnect.js +456 -0
- package/dist/src/capabilities/graph/runConnect.js.map +1 -0
- package/dist/src/capabilities/graph/runQuery.js +21 -4
- package/dist/src/capabilities/graph/runQuery.js.map +1 -1
- package/dist/src/capabilities/setup/buildPlan.js +6 -2
- package/dist/src/capabilities/setup/buildPlan.js.map +1 -1
- package/dist/src/capabilities/setup/operations/index.js +7 -2
- 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 +72 -14
- package/dist/src/capabilities/setup/operations/setupGenerator.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupLsp.js +372 -62
- package/dist/src/capabilities/setup/operations/setupLsp.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupMcp.js +150 -5
- package/dist/src/capabilities/setup/operations/setupMcp.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupSkills.js +4 -2
- package/dist/src/capabilities/setup/operations/setupSkills.js.map +1 -1
- package/dist/src/capabilities/setup/operations/writability.js +114 -0
- package/dist/src/capabilities/setup/operations/writability.js.map +1 -0
- package/dist/src/capabilities/setup/plan.js.map +1 -1
- package/dist/src/capabilities/setup/setup.verb.js +80 -31
- package/dist/src/capabilities/setup/setup.verb.js.map +1 -1
- package/dist/src/capabilities/setup/targets.js +124 -42
- package/dist/src/capabilities/setup/targets.js.map +1 -1
- package/dist/src/capabilities/shared/index.js +1 -1
- package/dist/src/capabilities/shared/index.js.map +1 -1
- package/dist/src/constants.js +7 -1
- package/dist/src/constants.js.map +1 -1
- package/dist/src/kernel/error/renderError.js +3 -2
- package/dist/src/kernel/error/renderError.js.map +1 -1
- package/dist/src/kernel/error/validOptions.js +41 -0
- package/dist/src/kernel/error/validOptions.js.map +1 -0
- package/dist/src/kernel/packs/compile.js +90 -6
- package/dist/src/kernel/packs/compile.js.map +1 -1
- package/dist/src/kernel/packs/cursor.js +135 -0
- package/dist/src/kernel/packs/cursor.js.map +1 -0
- package/dist/src/kernel/packs/graphql/buildLookupDocument.js +9 -2
- package/dist/src/kernel/packs/graphql/buildLookupDocument.js.map +1 -1
- package/dist/src/kernel/packs/paging.js +108 -0
- package/dist/src/kernel/packs/paging.js.map +1 -0
- package/dist/src/kernel/packs/renderPack.js +154 -5
- package/dist/src/kernel/packs/renderPack.js.map +1 -1
- package/dist/src/kernel/packs/resolveEntity.js +118 -19
- package/dist/src/kernel/packs/resolveEntity.js.map +1 -1
- package/dist/src/kernel/packs/runBodies.js +156 -18
- package/dist/src/kernel/packs/runBodies.js.map +1 -1
- package/dist/src/kernel/packs/schema.js +160 -12
- package/dist/src/kernel/packs/schema.js.map +1 -1
- package/dist/src/kernel/packs/sparql/authorQuery.js +227 -0
- package/dist/src/kernel/packs/sparql/authorQuery.js.map +1 -0
- package/dist/src/kernel/packs/sparql/buildListQuery.js +251 -0
- package/dist/src/kernel/packs/sparql/buildListQuery.js.map +1 -0
- package/dist/src/kernel/packs/sparql/buildLookupQuery.js +125 -22
- package/dist/src/kernel/packs/sparql/buildLookupQuery.js.map +1 -1
- package/dist/src/kernel/packs/sparql/filterValues.js +145 -0
- package/dist/src/kernel/packs/sparql/filterValues.js.map +1 -0
- package/dist/src/kernel/packs/sparql/index.js +24 -14
- package/dist/src/kernel/packs/sparql/index.js.map +1 -1
- package/dist/src/kernel/packs/sparql/runSelect.js +10 -4
- package/dist/src/kernel/packs/sparql/runSelect.js.map +1 -1
- package/dist/src/kernel/packs/sparql/searchTerm.js +25 -0
- package/dist/src/kernel/packs/sparql/searchTerm.js.map +1 -0
- package/dist/src/kernel/packs/storyRules.js +151 -0
- package/dist/src/kernel/packs/storyRules.js.map +1 -0
- package/dist/src/kernel/packs/tierScope.js +310 -0
- package/dist/src/kernel/packs/tierScope.js.map +1 -0
- package/dist/src/kernel/packs/types.js +80 -0
- package/dist/src/kernel/packs/types.js.map +1 -1
- package/dist/src/kernel/project/cli/buildProgram.js +10 -1
- package/dist/src/kernel/project/cli/buildProgram.js.map +1 -1
- package/dist/src/kernel/project/cli/dispatch.js +104 -3
- package/dist/src/kernel/project/cli/dispatch.js.map +1 -1
- package/dist/src/kernel/project/cli/rootHelp.js +8 -5
- package/dist/src/kernel/project/cli/rootHelp.js.map +1 -1
- package/dist/src/kernel/project/cli/suggestNames.js +31 -7
- package/dist/src/kernel/project/cli/suggestNames.js.map +1 -1
- package/dist/src/kernel/project/mcp/registerVerb.js +5 -1
- package/dist/src/kernel/project/mcp/registerVerb.js.map +1 -1
- package/dist/src/kernel/render/renderers.js +34 -2
- package/dist/src/kernel/render/renderers.js.map +1 -1
- 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 +17 -10
- 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/readEntity.js +19 -4
- package/dist/src/kernel/runtime/readEntity.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 +38 -5
- 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 +23 -20
- package/pragma.conf.ts +983 -40
- package/src/capabilities/block.parity.test.ts +207 -27
- package/src/capabilities/block.shipped.exec.test.ts +156 -14
- package/src/capabilities/block.tierRank.exec.test.ts +46 -9
- package/src/capabilities/capabilities/catalog.ts +14 -5
- package/src/capabilities/capabilities/hints.ts +40 -5
- package/src/capabilities/config/collectConfigShow.test.ts +1 -0
- package/src/capabilities/config/set.test.ts +24 -25
- package/src/capabilities/config/show.render.test.ts +9 -4
- package/src/capabilities/config/show.render.ts +30 -2
- 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 +4 -1
- package/src/capabilities/create/createSurface.generated.ts +23 -1
- package/src/capabilities/create/crossCli.subprocess.test.ts +10 -3
- package/src/capabilities/distribution.parity.test.ts +12 -11
- package/src/capabilities/distribution.test.ts +77 -7
- package/src/capabilities/doctor/checks/checkNodeVersion.test.ts +49 -0
- package/src/capabilities/doctor/checks/checkNodeVersion.ts +11 -7
- package/src/capabilities/doctor/checks/harnessInventory.test.ts +13 -6
- package/src/capabilities/doctor/checks/harnessInventory.ts +8 -4
- 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 +114 -13
- package/src/capabilities/doctor/doctor.test.ts +327 -36
- package/src/capabilities/emptyRecovery.test.ts +84 -28
- package/src/capabilities/graph/connect.render.ts +141 -0
- package/src/capabilities/graph/connect.shipped.exec.test.ts +370 -0
- package/src/capabilities/graph/connect.test.ts +361 -0
- package/src/capabilities/graph/connect.types.ts +136 -0
- package/src/capabilities/graph/connect.verb.ts +165 -0
- package/src/capabilities/graph/index.ts +5 -3
- package/src/capabilities/graph/query.shipped.exec.test.ts +66 -0
- package/src/capabilities/graph/query.test.ts +17 -0
- package/src/capabilities/graph/queryText.test.ts +121 -0
- package/src/capabilities/graph/queryText.ts +240 -0
- package/src/capabilities/graph/relationIndex.ts +372 -0
- package/src/capabilities/graph/runConnect.ts +577 -0
- package/src/capabilities/graph/runQuery.ts +21 -6
- package/src/capabilities/lazy.test.ts +32 -2
- package/src/capabilities/listBudget.shipped.exec.test.ts +239 -0
- package/src/capabilities/listLookup.shipped.exec.test.ts +387 -0
- package/src/capabilities/listQuery.shipped.exec.test.ts +400 -0
- package/src/capabilities/resources/resources.test.ts +38 -2
- package/src/capabilities/setup/buildPlan.ts +7 -1
- package/src/capabilities/setup/operations/index.ts +14 -0
- 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 +93 -15
- package/src/capabilities/setup/operations/setupLsp.ts +544 -85
- package/src/capabilities/setup/operations/setupMcp.ts +212 -4
- package/src/capabilities/setup/operations/setupSkills.ts +7 -2
- package/src/capabilities/setup/operations/writability.test.ts +151 -0
- package/src/capabilities/setup/operations/writability.ts +135 -0
- package/src/capabilities/setup/plan.ts +11 -0
- package/src/capabilities/setup/setup.test.ts +992 -16
- package/src/capabilities/setup/setup.verb.ts +84 -36
- package/src/capabilities/setup/targets.ts +152 -48
- package/src/capabilities/setup/types.ts +41 -5
- package/src/capabilities/shared/index.ts +1 -0
- package/src/capabilities/sources/sources.test.ts +55 -0
- package/src/capabilities/surface.test.ts +59 -11
- package/src/capabilities/tier.parity.test.ts +2 -2
- package/src/capabilities/token.parity.test.ts +784 -0
- package/src/capabilities/tokenGraph.cli.test.ts +279 -0
- package/src/capabilities/tokenGraph.shipped.exec.test.ts +653 -0
- package/src/capabilities/variable.parity.test.ts +448 -0
- package/src/constants.ts +8 -0
- package/src/kernel/completion/shellDrive.test.ts +32 -30
- package/src/kernel/config/defaults.test.ts +21 -2
- package/src/kernel/config/readConfig.test.ts +1 -1
- package/src/kernel/error/renderError.ts +3 -2
- package/src/kernel/error/validOptions.test.ts +57 -0
- package/src/kernel/error/validOptions.ts +45 -0
- package/src/kernel/packs/collect.test.ts +2 -0
- package/src/kernel/packs/compile.test.ts +188 -15
- package/src/kernel/packs/compile.ts +109 -14
- package/src/kernel/packs/cursor.ts +135 -0
- package/src/kernel/packs/filteredEmpty.test.ts +213 -0
- package/src/kernel/packs/graphql/buildLookupDocument.ts +16 -9
- package/src/kernel/packs/graphqlSource.test.ts +152 -0
- package/src/kernel/packs/listNotice.test.ts +156 -0
- package/src/kernel/packs/paging.ts +110 -0
- package/src/kernel/packs/renderPack.ts +186 -13
- package/src/kernel/packs/resolveEntity.test.ts +62 -3
- package/src/kernel/packs/resolveEntity.ts +186 -26
- package/src/kernel/packs/runBodies.ts +196 -26
- package/src/kernel/packs/schema.ts +180 -13
- package/src/kernel/packs/sparql/authorQuery.test.ts +179 -0
- package/src/kernel/packs/sparql/authorQuery.ts +257 -0
- package/src/kernel/packs/sparql/buildListQuery.test.ts +344 -0
- package/src/kernel/packs/sparql/buildListQuery.ts +344 -0
- package/src/kernel/packs/sparql/buildLookupQuery.test.ts +138 -0
- package/src/kernel/packs/sparql/buildLookupQuery.ts +140 -20
- package/src/kernel/packs/sparql/filterValues.test.ts +86 -0
- package/src/kernel/packs/sparql/filterValues.ts +182 -0
- package/src/kernel/packs/sparql/index.ts +30 -14
- package/src/kernel/packs/sparql/runSelect.test.ts +63 -0
- package/src/kernel/packs/sparql/runSelect.ts +42 -2
- package/src/kernel/packs/sparql/searchTerm.ts +29 -0
- package/src/kernel/packs/storyRules.ts +180 -0
- package/src/kernel/packs/tierScope.test.ts +487 -0
- package/src/kernel/packs/tierScope.ts +409 -0
- package/src/kernel/packs/toolDescription.test.ts +13 -10
- package/src/kernel/packs/types.ts +311 -9
- package/src/kernel/project/cli/buildProgram.ts +17 -1
- package/src/kernel/project/cli/dispatch.test.ts +87 -0
- package/src/kernel/project/cli/dispatch.ts +114 -4
- package/src/kernel/project/cli/rootHelp.ts +8 -5
- package/src/kernel/project/cli/suggest.test.ts +35 -0
- package/src/kernel/project/cli/suggestNames.ts +34 -7
- package/src/kernel/project/mcp/registerVerb.ts +5 -1
- package/src/kernel/render/contracts.ts +2 -0
- package/src/kernel/render/prefixes.test.ts +2 -0
- package/src/kernel/render/renderers.test.ts +43 -0
- package/src/kernel/render/renderers.ts +34 -1
- 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 +17 -13
- package/src/kernel/runtime/loadSession.ts +3 -1
- package/src/kernel/runtime/probe.ts +1 -1
- package/src/kernel/runtime/readEntity.ts +19 -4
- 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 +39 -5
- package/src/kernel/runtime/types.ts +30 -0
- package/src/kernel/spec/emitReference.ts +1 -1
- package/src/kernel/spec/surfaceConformance.test.ts +2 -2
- package/src/kernel/spec/types.ts +17 -0
- package/src/testing/PARITY_GAPS.ts +4 -4
- package/src/testing/behavioral/agentSession.mcp.test.ts +43 -39
- package/src/testing/behavioral/completion.test.ts +7 -6
- package/src/testing/behavioral/disclosure.test.ts +39 -37
- package/src/testing/behavioral/errorMatrix.mcp.test.ts +82 -64
- package/src/testing/behavioral/errorMatrix.storeless.test.ts +11 -10
- package/src/testing/behavioral/journeys.cli.test.ts +71 -32
- package/src/testing/behavioral/journeys.defaultPack.test.ts +6 -4
- package/src/testing/behavioral/journeys.firstInstall.test.ts +70 -5
- package/src/testing/behavioral/journeys.mcpLifecycle.mcp.test.ts +4 -3
- package/src/testing/behavioral/journeys.packageStories.test.ts +2 -1
- package/src/testing/behavioral/mcpSurface.test.ts +28 -25
- package/src/testing/behavioral/parity.test.ts +25 -23
- package/src/testing/eval/__snapshots__/eval.test.ts.snap +20 -0
- package/src/testing/eval/cases/effectful.ts +7 -1
- package/src/testing/eval/cases/readNouns.ts +100 -11
- package/src/testing/fixtures/blockGraph.ts +89 -5
- package/src/testing/fixtures/connectGraph.ts +159 -0
- package/src/testing/fixtures/graph/canonical.ts +128 -32
- package/src/testing/helpers/fsProbe.ts +88 -0
- package/src/testing/helpers/runCli.test.ts +44 -0
- package/src/testing/helpers/runCli.ts +27 -3
- package/src/testing/perf/budgets.test.ts +3 -1
- package/src/testing/perf/budgets.ts +138 -54
- package/dist/src/kernel/packs/sparql/applyFilters.js +0 -167
- package/dist/src/kernel/packs/sparql/applyFilters.js.map +0 -1
- package/dist/src/kernel/packs/sparql/applySearch.js +0 -25
- package/dist/src/kernel/packs/sparql/applySearch.js.map +0 -1
- package/src/kernel/packs/sparql/applyFilters.ts +0 -203
- package/src/kernel/packs/sparql/applySearch.ts +0 -33
|
@@ -209,6 +209,31 @@ async function runSetup(
|
|
|
209
209
|
// to know which of the two it is.
|
|
210
210
|
const previewing = interactionMode === "batch-dry-run";
|
|
211
211
|
|
|
212
|
+
// The undo truth channel. A real `--undo` interprets this verb's task ONLY
|
|
213
|
+
// as the undo interpreter's mocked collection walk — every exec exits 0,
|
|
214
|
+
// every prompt answers its default, and the OutcomeSink is never written —
|
|
215
|
+
// so nothing the task body could report describes what the collected
|
|
216
|
+
// reversals later did. The body therefore says NOTHING on the undo path
|
|
217
|
+
// (see the guard in the gen body below); the kernel calls this seam after
|
|
218
|
+
// the reversals actually ran, handing over the interpreter's per-undo
|
|
219
|
+
// outcomes, and `appliedUndo` projects them onto the rows through the SAME
|
|
220
|
+
// sink + `applied` path the forward run reports through. One outcome
|
|
221
|
+
// model, both directions.
|
|
222
|
+
if (!previewing && input.undo) {
|
|
223
|
+
rt.undoReport = (outcomes) => {
|
|
224
|
+
const applied = run.appliedUndo(outcomes);
|
|
225
|
+
const width = Math.max(...applied.rows.map((row) => row.target.length));
|
|
226
|
+
for (const row of applied.rows) {
|
|
227
|
+
if (row.outcome === undefined) continue;
|
|
228
|
+
rt.report?.(renderProgressLine(row, width));
|
|
229
|
+
}
|
|
230
|
+
if (planExitFailed(applied)) {
|
|
231
|
+
rt.report?.(renderRecap(applied, "Removed"));
|
|
232
|
+
raiseFailedRows(applied, true);
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
212
237
|
// A CONVERGED machine has no question to ask. Every row is `none` or `skip`,
|
|
213
238
|
// so nothing composes an effect — and the wizard's confirm gate then mounted
|
|
214
239
|
// Ink to render summon-core's "No operations planned." above a "Proceed?"
|
|
@@ -313,6 +338,16 @@ async function runSetup(
|
|
|
313
338
|
const result = yield* $(task);
|
|
314
339
|
const applied = run.applied(result.answers);
|
|
315
340
|
|
|
341
|
+
// A real `--undo` only ever drives this body under the undo
|
|
342
|
+
// interpreter's COLLECTION walk, with every effect mocked. `applied` is
|
|
343
|
+
// then a projection of the mocked walk (`removed` rows for reversals
|
|
344
|
+
// that have not run yet), so reporting it here printed green checks
|
|
345
|
+
// before anything happened. Say nothing; the kernel hands the executed
|
|
346
|
+
// reversals' outcomes to `rt.undoReport` afterwards, and `appliedUndo`
|
|
347
|
+
// grades the rows from those. (`--undo --dry-run` stays on the shared
|
|
348
|
+
// preview path below, where the same suppressions already apply.)
|
|
349
|
+
if (input.undo && !previewing) return applied;
|
|
350
|
+
|
|
316
351
|
// Progress: one line per row on stderr for an unattended run (the Ink
|
|
317
352
|
// session already draws them for the wizard). stdout stays the data stream.
|
|
318
353
|
//
|
|
@@ -333,45 +368,58 @@ async function runSetup(
|
|
|
333
368
|
// stdout on a failing run.
|
|
334
369
|
if (planExitFailed(applied)) {
|
|
335
370
|
rt.report?.(renderRecap(applied));
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
// given ("`bun` is not found on your PATH") and a remedy that runs on
|
|
342
|
-
// this machine; replacing them with a count would throw away the only
|
|
343
|
-
// two sentences that say what is wrong and what to do about it.
|
|
344
|
-
const only = failed.length === 1 ? failed[0] : undefined;
|
|
345
|
-
if (only?.outcome !== undefined) {
|
|
346
|
-
throw new PragmaError({
|
|
347
|
-
code: "UNSUPPORTED",
|
|
348
|
-
message: `${only.target}: ${only.outcome.note ?? "the step did not complete"}`,
|
|
349
|
-
...(only.outcome.remedy === undefined
|
|
350
|
-
? {}
|
|
351
|
-
: { recovery: { message: only.outcome.remedy } }),
|
|
352
|
-
});
|
|
353
|
-
}
|
|
371
|
+
raiseFailedRows(applied);
|
|
372
|
+
}
|
|
373
|
+
return applied;
|
|
374
|
+
});
|
|
375
|
+
}
|
|
354
376
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
377
|
+
/**
|
|
378
|
+
* Raise the run's failure from its rows — shared by the forward run's recap
|
|
379
|
+
* and the post-undo truth report, so the two paths name a shortfall in the
|
|
380
|
+
* same words. `undo` only re-words the multi-failure recovery line: the
|
|
381
|
+
* command that retries a removal is `setup <target> --undo`, not the
|
|
382
|
+
* installer.
|
|
383
|
+
*
|
|
384
|
+
* @param applied - The plan with outcomes filled in (at least one `failed`).
|
|
385
|
+
* @param undo - Whether the failed run was a removal.
|
|
386
|
+
* @throws PragmaError naming the failed row(s), always.
|
|
387
|
+
*/
|
|
388
|
+
function raiseFailedRows(applied: SetupPlan, undo = false): never {
|
|
389
|
+
const failed = applied.rows.filter((row) => row.outcome?.status === "failed");
|
|
390
|
+
|
|
391
|
+
// ONE failure reports itself. The row already carries the cause it was
|
|
392
|
+
// given ("`bun` is not found on your PATH") and a remedy that runs on
|
|
393
|
+
// this machine; replacing them with a count would throw away the only
|
|
394
|
+
// two sentences that say what is wrong and what to do about it.
|
|
395
|
+
const only = failed.length === 1 ? failed[0] : undefined;
|
|
396
|
+
if (only?.outcome !== undefined) {
|
|
397
|
+
throw new PragmaError({
|
|
398
|
+
code: "UNSUPPORTED",
|
|
399
|
+
message: `${only.target}: ${only.outcome.note ?? "the step did not complete"}`,
|
|
400
|
+
...(only.outcome.remedy === undefined
|
|
401
|
+
? {}
|
|
402
|
+
: { recovery: { message: only.outcome.remedy } }),
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Several failures: name every one with its own cause, then point at the
|
|
407
|
+
// recap above. A bare list of target names would make the reader re-run
|
|
408
|
+
// each one to find out why it failed.
|
|
409
|
+
const named = failed
|
|
410
|
+
.map((row) => `${row.target} (${row.outcome?.note ?? "no cause recorded"})`)
|
|
411
|
+
.join("; ");
|
|
412
|
+
throw new PragmaError({
|
|
413
|
+
code: "UNSUPPORTED",
|
|
414
|
+
message: `${failed.length} of ${planTally(applied).accountable} targets did not complete: ${named}.`,
|
|
415
|
+
recovery: {
|
|
416
|
+
message: `The other targets are ${undo ? "removed" : "configured"}. Re-run the ones that did not complete: ${failed
|
|
359
417
|
.map(
|
|
360
418
|
(row) =>
|
|
361
|
-
|
|
419
|
+
`\`${BIN_NAME} setup ${row.target}${undo ? " --undo" : ""}\``,
|
|
362
420
|
)
|
|
363
|
-
.join("
|
|
364
|
-
|
|
365
|
-
code: "UNSUPPORTED",
|
|
366
|
-
message: `${failed.length} of ${planTally(applied).accountable} targets did not complete: ${named}.`,
|
|
367
|
-
recovery: {
|
|
368
|
-
message: `The other targets are configured. Re-run the ones that did not complete: ${failed
|
|
369
|
-
.map((row) => `\`${BIN_NAME} setup ${row.target}\``)
|
|
370
|
-
.join(", ")}.`,
|
|
371
|
-
},
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
return applied;
|
|
421
|
+
.join(", ")}.`,
|
|
422
|
+
},
|
|
375
423
|
});
|
|
376
424
|
}
|
|
377
425
|
|
|
@@ -456,7 +504,7 @@ const skillsVerb = setupVerb(
|
|
|
456
504
|
|
|
457
505
|
const lspVerb = setupVerb(
|
|
458
506
|
["setup", "lsp"],
|
|
459
|
-
"Install the Terrazzo design-token extension into
|
|
507
|
+
"Install the Terrazzo design-token extension into the VS Code-family editors on this machine (on PATH, under /Applications or ~/Applications, or by its user directory).",
|
|
460
508
|
"lsp",
|
|
461
509
|
SUB_CAPABILITY,
|
|
462
510
|
);
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
* doctor's), so nothing here lands on the `--help`/`__complete` fast path.
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
|
+
import { dirname } from "node:path";
|
|
27
28
|
import type { Task } from "@canonical/task";
|
|
28
29
|
import { BIN_NAME } from "../../constants.js";
|
|
29
30
|
import type { PragmaRuntime } from "../../kernel/runtime/index.js";
|
|
@@ -42,9 +43,13 @@ import {
|
|
|
42
43
|
import {
|
|
43
44
|
composeLsp,
|
|
44
45
|
composeLspRemoval,
|
|
46
|
+
describeEditorSource,
|
|
45
47
|
detectLsp,
|
|
48
|
+
firstLspBlock,
|
|
49
|
+
installableEditors,
|
|
46
50
|
LSP_SKIP_REMEDY,
|
|
47
51
|
type LspDetection,
|
|
52
|
+
lspBlockReason,
|
|
48
53
|
lspEditorNames,
|
|
49
54
|
lspSkipReason,
|
|
50
55
|
ownedLspEditors,
|
|
@@ -53,7 +58,10 @@ import {
|
|
|
53
58
|
composeMcp,
|
|
54
59
|
composeMcpRemoval,
|
|
55
60
|
detectMcp,
|
|
61
|
+
firstMcpBlock,
|
|
56
62
|
type McpDetection,
|
|
63
|
+
mcpBlockReason,
|
|
64
|
+
mcpGroupBlock,
|
|
57
65
|
mcpGroupState,
|
|
58
66
|
ownedMcpGroups,
|
|
59
67
|
selectedGroups,
|
|
@@ -68,10 +76,11 @@ import {
|
|
|
68
76
|
skillsSkipRemedy,
|
|
69
77
|
staleSkillLinks,
|
|
70
78
|
} from "./operations/setupSkills.js";
|
|
79
|
+
import type { FsProbe } from "./operations/writability.js";
|
|
71
80
|
import {
|
|
72
81
|
type PlanAction,
|
|
73
82
|
type PlanChildRow,
|
|
74
|
-
type
|
|
83
|
+
type Roots,
|
|
75
84
|
shortenPath,
|
|
76
85
|
type TargetId,
|
|
77
86
|
} from "./plan.js";
|
|
@@ -88,9 +97,6 @@ export interface TargetDraft {
|
|
|
88
97
|
readonly remedy?: string;
|
|
89
98
|
}
|
|
90
99
|
|
|
91
|
-
/** The context a draft is rendered against — the plan's two named roots. */
|
|
92
|
-
export type Roots = SetupPlan["roots"];
|
|
93
|
-
|
|
94
100
|
/**
|
|
95
101
|
* One row of the table, generic over its own detection type. Every consumer
|
|
96
102
|
* treats the detection as opaque and hands it straight back to the same row,
|
|
@@ -101,8 +107,15 @@ export interface TargetDefinition<D> {
|
|
|
101
107
|
/** Human title, used where a sentence needs one; the id is the row name. */
|
|
102
108
|
readonly title: string;
|
|
103
109
|
readonly scopes: readonly Scope[];
|
|
104
|
-
/**
|
|
105
|
-
|
|
110
|
+
/**
|
|
111
|
+
* Real reads, up front, once per (target, scope) per invocation.
|
|
112
|
+
*
|
|
113
|
+
* `probe` is the writability filesystem seam, threaded from `detectTargets`
|
|
114
|
+
* so a test can drive the two arms no CI host has — a `/nix/store` path, and
|
|
115
|
+
* (as root) a directory that refuses `W_OK` — through the SAME entry points
|
|
116
|
+
* production uses. Rows that write nothing ignore it.
|
|
117
|
+
*/
|
|
118
|
+
detect(rt: PragmaRuntime, scope: Scope, probe?: FsProbe): Promise<D>;
|
|
106
119
|
/** What a forward run would do. */
|
|
107
120
|
plan(detection: D, scope: Scope, roots: Roots): TargetDraft;
|
|
108
121
|
/** What a removal would do — composed from what detection says we own. */
|
|
@@ -112,8 +125,12 @@ export interface TargetDefinition<D> {
|
|
|
112
125
|
* paths); a row without children ignores it.
|
|
113
126
|
*/
|
|
114
127
|
compose(detection: D, chosen?: readonly string[]): Task<void>;
|
|
115
|
-
/**
|
|
116
|
-
|
|
128
|
+
/**
|
|
129
|
+
* The removal effects: forward re-assertion carrying the reversal as
|
|
130
|
+
* `undo`. `undoKey` (the row's identity) is stamped on every reversal so
|
|
131
|
+
* the undo interpreter's outcomes can be read back per row.
|
|
132
|
+
*/
|
|
133
|
+
composeRemoval(detection: D, undoKey?: string): Task<void>;
|
|
117
134
|
}
|
|
118
135
|
|
|
119
136
|
/** A table row with its detection type erased — how every consumer holds one. */
|
|
@@ -150,7 +167,7 @@ const configTarget = defineTarget<ConfigDetection>({
|
|
|
150
167
|
return { action: "remove", detail: path };
|
|
151
168
|
},
|
|
152
169
|
compose: (d) => composeConfigFile(d),
|
|
153
|
-
composeRemoval: (d) => composeConfigRemoval(d),
|
|
170
|
+
composeRemoval: (d, undoKey) => composeConfigRemoval(d, undoKey),
|
|
154
171
|
});
|
|
155
172
|
|
|
156
173
|
const completionsTarget = defineTarget<CompletionsDetection>({
|
|
@@ -207,66 +224,123 @@ const completionsTarget = defineTarget<CompletionsDetection>({
|
|
|
207
224
|
return { action: "remove", detail: shortenPath(d.path, roots) };
|
|
208
225
|
},
|
|
209
226
|
compose: (d) => composeCompletions(d),
|
|
210
|
-
composeRemoval: (d) => composeCompletionsRemoval(d),
|
|
227
|
+
composeRemoval: (d, undoKey) => composeCompletionsRemoval(d, undoKey),
|
|
211
228
|
});
|
|
212
229
|
|
|
230
|
+
/**
|
|
231
|
+
* The named skip for a machine with no VS Code-family editor at all — the one
|
|
232
|
+
* finding the forward plan and the removal share verbatim.
|
|
233
|
+
*/
|
|
234
|
+
const draftLspNothingFound = (d: LspDetection): TargetDraft => ({
|
|
235
|
+
action: "skip",
|
|
236
|
+
detail: lspSkipReason(d),
|
|
237
|
+
reason: lspSkipReason(d),
|
|
238
|
+
remedy: LSP_SKIP_REMEDY,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* One child per detected editor, exactly like the mcp row's files: a machine
|
|
243
|
+
* with several VS Code forks should not have the extension pushed into all of
|
|
244
|
+
* them because they happen to be installed.
|
|
245
|
+
*
|
|
246
|
+
* Each label carries HOW the editor was found, because that is the fact a user
|
|
247
|
+
* whose run did nothing needs — whether pragma missed their editor or found it
|
|
248
|
+
* and could not act.
|
|
249
|
+
*
|
|
250
|
+
* An INSTALLED editor is `unchanged` even when its folder is blocked: the copy
|
|
251
|
+
* really is there, and a child reading `skip — make the folder writable` told
|
|
252
|
+
* a user to chmod and reinstall an extension they already have. A block only
|
|
253
|
+
* decides the child when there is nothing installed for it to be about.
|
|
254
|
+
*/
|
|
255
|
+
const lspChildren = (d: LspDetection, roots: Roots): PlanChildRow[] =>
|
|
256
|
+
d.editors.map((e): PlanChildRow => {
|
|
257
|
+
const label = `${e.editor.cli} — ${e.editor.name} · ${describeEditorSource(e, roots)}`;
|
|
258
|
+
if (e.installed) {
|
|
259
|
+
return { key: e.editor.cli, label, action: "unchanged" };
|
|
260
|
+
}
|
|
261
|
+
const reason = lspBlockReason(e);
|
|
262
|
+
if (reason !== undefined) {
|
|
263
|
+
return { key: e.editor.cli, label, action: "skip", reason };
|
|
264
|
+
}
|
|
265
|
+
return { key: e.editor.cli, label, action: "add" };
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* The row-level skip for a machine where nothing can be done at all —
|
|
270
|
+
* `undefined` when there is still something the row can say for itself.
|
|
271
|
+
*
|
|
272
|
+
* `actionable` is the row's own count of editors it could act on (pending
|
|
273
|
+
* installs going forward, owned copies on a removal) PLUS the ones already
|
|
274
|
+
* installed: a machine that is fully installed everywhere it can be is a `no
|
|
275
|
+
* change` row, not a permanent skip telling its owner to fix a folder whose
|
|
276
|
+
* extension is in place. Zero of those AND at least one block means the row's
|
|
277
|
+
* whole answer is the block, so it carries the FIRST one's reason and the
|
|
278
|
+
* remedy that matches it: a Nix declaration, a `chmod`, or the palette
|
|
279
|
+
* command, never one sentence standing for all three.
|
|
280
|
+
*/
|
|
281
|
+
const draftLspRowBlock = (
|
|
282
|
+
d: LspDetection,
|
|
283
|
+
actionable: number,
|
|
284
|
+
): TargetDraft | undefined => {
|
|
285
|
+
if (actionable > 0) return undefined;
|
|
286
|
+
const block = firstLspBlock(d);
|
|
287
|
+
if (block === undefined) return undefined;
|
|
288
|
+
return {
|
|
289
|
+
action: "skip",
|
|
290
|
+
detail: block.headline,
|
|
291
|
+
reason: block.headline,
|
|
292
|
+
remedy: block.remedy,
|
|
293
|
+
};
|
|
294
|
+
};
|
|
295
|
+
|
|
213
296
|
const lspTarget = defineTarget<LspDetection>({
|
|
214
297
|
id: "lsp",
|
|
215
298
|
title: "Terrazzo LSP extension",
|
|
216
299
|
scopes: ["global"],
|
|
217
|
-
detect: (rt) => detectLsp(rt.cwd),
|
|
218
|
-
plan: (d) => {
|
|
219
|
-
if (d.state === "unknown")
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
227
|
-
// One child per detected editor, exactly like the mcp row's files: a
|
|
228
|
-
// machine with several VS Code forks on PATH should not have the extension
|
|
229
|
-
// pushed into all of them because they happen to be installed.
|
|
230
|
-
const children: PlanChildRow[] = d.editors.map((e) => ({
|
|
231
|
-
key: e.editor.cli,
|
|
232
|
-
label: `${e.editor.cli} — ${e.editor.name}`,
|
|
233
|
-
action: e.installed ? ("unchanged" as const) : ("add" as const),
|
|
234
|
-
}));
|
|
300
|
+
detect: (rt, _scope, probe) => detectLsp(rt.cwd, undefined, probe),
|
|
301
|
+
plan: (d, _scope, roots) => {
|
|
302
|
+
if (d.state === "unknown") return draftLspNothingFound(d);
|
|
303
|
+
const children = lspChildren(d, roots);
|
|
304
|
+
// The editors an install could actually act on: missing the extension AND
|
|
305
|
+
// not blocked. A blocked editor is not a candidate the user declined.
|
|
306
|
+
const installable = installableEditors(d);
|
|
307
|
+
const installed = d.editors.filter((e) => e.installed);
|
|
308
|
+
const blocked = draftLspRowBlock(d, installable.length + installed.length);
|
|
309
|
+
if (blocked !== undefined) return { ...blocked, children };
|
|
235
310
|
// The detail names WHAT is being placed and WHERE — never the row's own
|
|
236
311
|
// state, which the action column already carries. `installed (VSCodium)`
|
|
237
312
|
// beside a child row reading `codium — VSCodium (unchanged)` said the
|
|
238
313
|
// editor's name twice and "nothing happens here" twice.
|
|
239
|
-
if (
|
|
314
|
+
if (installable.length === 0) {
|
|
240
315
|
return {
|
|
241
316
|
action: "none",
|
|
242
317
|
detail: `Terrazzo extension in ${lspEditorNames(d).join(", ")}`,
|
|
243
318
|
children,
|
|
244
319
|
};
|
|
245
320
|
}
|
|
246
|
-
const pending = d.editors.filter((e) => !e.installed);
|
|
247
321
|
return {
|
|
248
322
|
action: "install",
|
|
249
|
-
detail: `Terrazzo extension → ${
|
|
323
|
+
detail: `Terrazzo extension → ${installable
|
|
250
324
|
.map((e) => e.editor.name)
|
|
251
325
|
.join(", ")}`,
|
|
252
326
|
children,
|
|
253
327
|
};
|
|
254
328
|
},
|
|
255
|
-
removalPlan: (d) => {
|
|
256
|
-
// No editor
|
|
329
|
+
removalPlan: (d, _scope, roots) => {
|
|
330
|
+
// No editor found at all is the SAME named skip the forward plan reports:
|
|
257
331
|
// 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
|
-
}
|
|
332
|
+
if (d.state === "unknown") return draftLspNothingFound(d);
|
|
266
333
|
// Removal is composed from what detection says we OWN — a copy that is
|
|
267
334
|
// present, whatever its version. The forward plan's version gate would let
|
|
268
335
|
// a dead pre-0.8.3 copy this command installed survive its own `--undo`.
|
|
269
336
|
const owned = ownedLspEditors(d);
|
|
337
|
+
// Nothing owned AND something blocked is the same skip the forward row
|
|
338
|
+
// reports, for the same reason: the uninstall would need a CLI this
|
|
339
|
+
// machine has not got, or a write pragma must not make.
|
|
340
|
+
const blocked = draftLspRowBlock(d, owned.length);
|
|
341
|
+
if (blocked !== undefined) {
|
|
342
|
+
return { ...blocked, children: lspChildren(d, roots) };
|
|
343
|
+
}
|
|
270
344
|
if (owned.length === 0) {
|
|
271
345
|
return { action: "none", detail: "no editor carries the extension" };
|
|
272
346
|
}
|
|
@@ -283,7 +357,7 @@ const lspTarget = defineTarget<LspDetection>({
|
|
|
283
357
|
};
|
|
284
358
|
},
|
|
285
359
|
compose: (d, chosen) => composeLsp(d, chosen),
|
|
286
|
-
composeRemoval: (d) => composeLspRemoval(d),
|
|
360
|
+
composeRemoval: (d, undoKey) => composeLspRemoval(d, undoKey),
|
|
287
361
|
});
|
|
288
362
|
|
|
289
363
|
/**
|
|
@@ -303,10 +377,18 @@ const mcpChild = (
|
|
|
303
377
|
group: McpDetection["groups"][number],
|
|
304
378
|
roots: Roots,
|
|
305
379
|
): PlanChildRow => {
|
|
380
|
+
const label = shortenPath(group.path, roots);
|
|
381
|
+
// A file pragma cannot write is a child SKIP with its own reason, not an
|
|
382
|
+
// `add` that fails. The probe ran before anything was composed, so the row
|
|
383
|
+
// knows this without having tried.
|
|
384
|
+
const blocked = mcpBlockReason(d, group, roots);
|
|
385
|
+
if (blocked !== undefined) {
|
|
386
|
+
return { key: group.path, label, action: "skip", reason: blocked };
|
|
387
|
+
}
|
|
306
388
|
const state = mcpGroupState(d, group.path);
|
|
307
389
|
return {
|
|
308
390
|
key: group.path,
|
|
309
|
-
label
|
|
391
|
+
label,
|
|
310
392
|
action:
|
|
311
393
|
state === "registered"
|
|
312
394
|
? "unchanged"
|
|
@@ -320,7 +402,7 @@ const mcpTarget = defineTarget<McpDetection>({
|
|
|
320
402
|
id: "mcp",
|
|
321
403
|
title: "MCP server registration",
|
|
322
404
|
scopes: ["global", "project"],
|
|
323
|
-
detect: (rt, scope) => detectMcp(rt, scope),
|
|
405
|
+
detect: (rt, scope, probe) => detectMcp(rt, scope, probe),
|
|
324
406
|
plan: (d, scope, roots) => {
|
|
325
407
|
if (d.groups.length === 0) {
|
|
326
408
|
return {
|
|
@@ -330,7 +412,24 @@ const mcpTarget = defineTarget<McpDetection>({
|
|
|
330
412
|
};
|
|
331
413
|
}
|
|
332
414
|
const children = d.groups.map((group) => mcpChild(d, group, roots));
|
|
333
|
-
|
|
415
|
+
// Every file blocked means the row's whole answer is the block: it carries
|
|
416
|
+
// the first one's reason and the entry to declare by hand.
|
|
417
|
+
const writable = d.groups.filter(
|
|
418
|
+
(group) => mcpGroupBlock(d, group.path) === undefined,
|
|
419
|
+
);
|
|
420
|
+
const block = writable.length === 0 ? firstMcpBlock(d, roots) : undefined;
|
|
421
|
+
if (block !== undefined) {
|
|
422
|
+
return {
|
|
423
|
+
action: "skip",
|
|
424
|
+
detail: block.reason,
|
|
425
|
+
reason: block.reason,
|
|
426
|
+
remedy: block.remedy,
|
|
427
|
+
children,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
const pending = children.filter(
|
|
431
|
+
(c) => c.action !== "unchanged" && c.action !== "skip",
|
|
432
|
+
);
|
|
334
433
|
return {
|
|
335
434
|
action: pending.length === 0 ? "none" : "update",
|
|
336
435
|
detail: `${d.groups.length} ${
|
|
@@ -356,7 +455,7 @@ const mcpTarget = defineTarget<McpDetection>({
|
|
|
356
455
|
},
|
|
357
456
|
compose: (d, chosen) =>
|
|
358
457
|
composeMcp(d, chosen ? selectedGroups(d, chosen) : d.groups),
|
|
359
|
-
composeRemoval: (d) => composeMcpRemoval(d),
|
|
458
|
+
composeRemoval: (d, undoKey) => composeMcpRemoval(d, undoKey),
|
|
360
459
|
});
|
|
361
460
|
|
|
362
461
|
const skillsTarget = defineTarget<SkillsDetection>({
|
|
@@ -402,7 +501,12 @@ const skillsTarget = defineTarget<SkillsDetection>({
|
|
|
402
501
|
if (owned.length === 0) {
|
|
403
502
|
return { action: "none", detail: "no link to remove" };
|
|
404
503
|
}
|
|
405
|
-
|
|
504
|
+
// Dedupe to the FOLDERS the links live in — `linkPath` itself is one path
|
|
505
|
+
// per link, so a Set over it deduped nothing and the removal preview
|
|
506
|
+
// printed an 18-path wall where the forward plan says `… → 2 folders`.
|
|
507
|
+
const dirs = [
|
|
508
|
+
...new Set(owned.map((a) => shortenPath(dirname(a.linkPath), roots))),
|
|
509
|
+
];
|
|
406
510
|
return {
|
|
407
511
|
action: "remove",
|
|
408
512
|
detail: `${owned.length} ${owned.length === 1 ? "link" : "links"}`,
|
|
@@ -414,7 +518,7 @@ const skillsTarget = defineTarget<SkillsDetection>({
|
|
|
414
518
|
};
|
|
415
519
|
},
|
|
416
520
|
compose: (d) => composeSkills(d),
|
|
417
|
-
composeRemoval: (d) => composeSkillsRemoval(d),
|
|
521
|
+
composeRemoval: (d, undoKey) => composeSkillsRemoval(d, undoKey),
|
|
418
522
|
});
|
|
419
523
|
|
|
420
524
|
/** THE table, in display order. */
|
|
@@ -43,10 +43,46 @@ export type CompletionsState = "absent" | "installed" | "stale";
|
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
45
|
* The detected state of the Terrazzo LSP extension across the VS Code-family
|
|
46
|
-
* editors on this machine, probed up front by `detectLsp
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
46
|
+
* editors on this machine, probed up front by `detectLsp`.
|
|
47
|
+
*
|
|
48
|
+
* An editor is FOUND by any of three probes — its CLI on PATH, its CLI inside
|
|
49
|
+
* its macOS app bundle, or its per-user configuration directory — and the
|
|
50
|
+
* state is about every editor found, whichever probe found it:
|
|
51
|
+
*
|
|
52
|
+
* - `installed`: every found editor already carries a working copy, so a
|
|
53
|
+
* re-run skips. A BLOCKED editor (no CLI to run, or an extensions folder
|
|
54
|
+
* this command must not write to) counts here when it is installed: the
|
|
55
|
+
* copy really is there, and the block is only about changing it.
|
|
56
|
+
* - `absent`: at least one found editor is missing it. The sideload runs for
|
|
57
|
+
* the ones that are missing it AND can be written to — never for a blocked
|
|
58
|
+
* editor, whose row carries its own remedy instead.
|
|
59
|
+
* - `unknown`: NO editor was found by any of the three probes, so the step is
|
|
60
|
+
* a named skip: there is nothing on this machine to install into.
|
|
51
61
|
*/
|
|
52
62
|
export type LspState = "installed" | "absent" | "unknown";
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Why a write cannot happen at a path — a fact with a remedy, never an error.
|
|
66
|
+
*
|
|
67
|
+
* The two kinds are kept apart because their remedies are unrelated: a store
|
|
68
|
+
* path needs a DECLARATION in the config that produced it, and a read-only
|
|
69
|
+
* path needs permissions changed. Collapsing them to one "not writable" would
|
|
70
|
+
* print the `chmod` advice at a Nix user, which cannot work — `chmod` on the
|
|
71
|
+
* store is not the fix, and the next `nixos-rebuild` would undo it.
|
|
72
|
+
*
|
|
73
|
+
* It lives here, beside {@link LspState} and {@link McpTargetState}, because
|
|
74
|
+
* it is the same kind of thing: a setup-domain state vocabulary that the lsp
|
|
75
|
+
* detection, the mcp detection, the target table and doctor all read.
|
|
76
|
+
* `probeWritable` (`operations/writability.ts`) is what produces one.
|
|
77
|
+
*/
|
|
78
|
+
export type WriteBlock =
|
|
79
|
+
| {
|
|
80
|
+
readonly kind: "nix-store";
|
|
81
|
+
/** The store path the target resolves to — the evidence, and the remedy's subject. */
|
|
82
|
+
readonly resolved: string;
|
|
83
|
+
}
|
|
84
|
+
| {
|
|
85
|
+
readonly kind: "read-only";
|
|
86
|
+
/** The existing node that refused `W_OK` — the path a remedy must name. */
|
|
87
|
+
readonly path: string;
|
|
88
|
+
};
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
} from "../../kernel/runtime/paths.js";
|
|
34
34
|
import {
|
|
35
35
|
detectPrefixClashes,
|
|
36
|
+
harvestPrefixes,
|
|
36
37
|
resolvePackageJson,
|
|
37
38
|
} from "../../kernel/runtime/refs/resolve.js";
|
|
38
39
|
import { createLazyStore } from "../../kernel/runtime/store.js";
|
|
@@ -333,6 +334,60 @@ describe("sources update — conflicting @prefix detection (A5)", () => {
|
|
|
333
334
|
]);
|
|
334
335
|
});
|
|
335
336
|
|
|
337
|
+
it("ignores a prefix declaration inside a string literal", () => {
|
|
338
|
+
// The code-standards pack ships guidance ABOUT Turtle: its `cs:code`
|
|
339
|
+
// examples contain declarations, including a deliberately discouraged one.
|
|
340
|
+
// Scanning raw text reported a clash on every single update for a file that
|
|
341
|
+
// asserts no triple in that namespace at all.
|
|
342
|
+
const clashes = detectPrefixClashes([
|
|
343
|
+
{ content: "@prefix ex: <https://a.test/#> .\nex:One a ex:T ." },
|
|
344
|
+
{
|
|
345
|
+
content: [
|
|
346
|
+
"@prefix cs: <https://cs.test/#> .",
|
|
347
|
+
'cs:rule cs:code """',
|
|
348
|
+
"# Bad: do not do this",
|
|
349
|
+
"@prefix ex: <https://wrong.test/data/> .",
|
|
350
|
+
'""" .',
|
|
351
|
+
].join("\n"),
|
|
352
|
+
},
|
|
353
|
+
]);
|
|
354
|
+
|
|
355
|
+
expect(clashes).toEqual([]);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it("does not let a literal rebind a real prefix in the harvested map", () => {
|
|
359
|
+
// Worse than a spurious warning: last-wins meant a code sample could
|
|
360
|
+
// overwrite a real namespace, so every entity of the losing package
|
|
361
|
+
// compacted to the wrong prefix.
|
|
362
|
+
const prefixes = harvestPrefixes([
|
|
363
|
+
{ content: "@prefix ex: <https://a.test/#> .\nex:One a ex:T ." },
|
|
364
|
+
{
|
|
365
|
+
content: [
|
|
366
|
+
'cs:rule cs:code """',
|
|
367
|
+
"@prefix ex: <https://wrong.test/data/> .",
|
|
368
|
+
'""" .',
|
|
369
|
+
].join("\n"),
|
|
370
|
+
},
|
|
371
|
+
]);
|
|
372
|
+
|
|
373
|
+
expect(prefixes.ex).toBe("https://a.test/#");
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it("still reads declarations that follow a literal in the same file", () => {
|
|
377
|
+
// Masking must not swallow the rest of the file.
|
|
378
|
+
const prefixes = harvestPrefixes([
|
|
379
|
+
{
|
|
380
|
+
content: [
|
|
381
|
+
'cs:rule cs:code """@prefix ignored: <https://no.test/> .""" .',
|
|
382
|
+
"@prefix real: <https://yes.test/#> .",
|
|
383
|
+
].join("\n"),
|
|
384
|
+
},
|
|
385
|
+
]);
|
|
386
|
+
|
|
387
|
+
expect(prefixes.real).toBe("https://yes.test/#");
|
|
388
|
+
expect(prefixes.ignored).toBeUndefined();
|
|
389
|
+
});
|
|
390
|
+
|
|
336
391
|
it("does NOT flag harmless same-label/same-IRI redeclarations", () => {
|
|
337
392
|
const clashes = detectPrefixClashes([
|
|
338
393
|
{ content: "@prefix ex: <https://a.test/#> .\nex:One a ex:T ." },
|