@codyswann/lisa 2.327.0 → 2.328.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.
Files changed (109) hide show
  1. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  2. package/dist/core/upstream-evidence-manifest.js +30 -6
  3. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  4. package/package.json +1 -1
  5. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  6. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  7. package/plugins/lisa/.codex-plugin/skills/lisa-detect-tooling/SKILL.md +21 -1
  8. package/plugins/lisa/.codex-plugin/skills/lisa-detect-tooling/scripts/detect-tooling.mjs +25 -13
  9. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/validate-config.mjs +72 -10
  10. package/plugins/lisa/.codex-plugin/skills/lisa-setup-local-env/SKILL.md +95 -0
  11. package/plugins/lisa/.codex-plugin/skills/lisa-setup-local-env/agents/openai.yaml +4 -0
  12. package/plugins/lisa/.codex-plugin/skills/lisa-setup-local-env/scripts/local-env.mjs +174 -0
  13. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/SKILL.md +27 -11
  14. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +70 -30
  15. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/toolchain.mjs +132 -6
  16. package/plugins/lisa/commands/setup/local-env.md +7 -0
  17. package/plugins/lisa/skills/lisa-detect-tooling/SKILL.md +21 -1
  18. package/plugins/lisa/skills/lisa-detect-tooling/scripts/detect-tooling.mjs +25 -13
  19. package/plugins/lisa/skills/lisa-secrets-access/scripts/validate-config.mjs +72 -10
  20. package/plugins/lisa/skills/lisa-setup-local-env/SKILL.md +95 -0
  21. package/plugins/lisa/skills/lisa-setup-local-env/agents/openai.yaml +4 -0
  22. package/plugins/lisa/skills/lisa-setup-local-env/scripts/local-env.mjs +174 -0
  23. package/plugins/lisa/skills/lisa-setup-remote-env/SKILL.md +27 -11
  24. package/plugins/lisa/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +70 -30
  25. package/plugins/lisa/skills/lisa-setup-remote-env/scripts/toolchain.mjs +132 -6
  26. package/plugins/lisa-agy/commands/lisa/setup/local-env.md +7 -0
  27. package/plugins/lisa-agy/plugin.json +1 -1
  28. package/plugins/lisa-agy/skills/lisa-detect-tooling/SKILL.md +21 -1
  29. package/plugins/lisa-agy/skills/lisa-detect-tooling/scripts/detect-tooling.mjs +25 -13
  30. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/validate-config.mjs +72 -10
  31. package/plugins/lisa-agy/skills/lisa-setup-local-env/SKILL.md +95 -0
  32. package/plugins/lisa-agy/skills/lisa-setup-local-env/scripts/local-env.mjs +174 -0
  33. package/plugins/lisa-agy/skills/lisa-setup-remote-env/SKILL.md +27 -11
  34. package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +70 -30
  35. package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/toolchain.mjs +132 -6
  36. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  38. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  39. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-copilot/commands/lisa/setup/local-env.md +7 -0
  43. package/plugins/lisa-copilot/skills/lisa-detect-tooling/SKILL.md +21 -1
  44. package/plugins/lisa-copilot/skills/lisa-detect-tooling/scripts/detect-tooling.mjs +25 -13
  45. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/validate-config.mjs +72 -10
  46. package/plugins/lisa-copilot/skills/lisa-setup-local-env/SKILL.md +95 -0
  47. package/plugins/lisa-copilot/skills/lisa-setup-local-env/scripts/local-env.mjs +174 -0
  48. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/SKILL.md +27 -11
  49. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +70 -30
  50. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/toolchain.mjs +132 -6
  51. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-cursor/commands/lisa/setup/local-env.md +7 -0
  53. package/plugins/lisa-cursor/skills/lisa-detect-tooling/SKILL.md +21 -1
  54. package/plugins/lisa-cursor/skills/lisa-detect-tooling/scripts/detect-tooling.mjs +25 -13
  55. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/validate-config.mjs +72 -10
  56. package/plugins/lisa-cursor/skills/lisa-setup-local-env/SKILL.md +95 -0
  57. package/plugins/lisa-cursor/skills/lisa-setup-local-env/scripts/local-env.mjs +174 -0
  58. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/SKILL.md +27 -11
  59. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +70 -30
  60. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/toolchain.mjs +132 -6
  61. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  63. package/plugins/lisa-expo-agy/plugin.json +1 -1
  64. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  67. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  68. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  69. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  70. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  71. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  72. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  73. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  74. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  75. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  76. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  77. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  78. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  79. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  80. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  81. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  82. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  83. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  84. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  85. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  86. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  87. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  88. package/plugins/lisa-rails-agy/plugin.json +1 -1
  89. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  90. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  91. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  92. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  93. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  94. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  95. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  96. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  97. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  98. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  99. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  100. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  101. package/plugins/src/base/commands/setup/local-env.md +7 -0
  102. package/plugins/src/base/skills/lisa-detect-tooling/SKILL.md +21 -1
  103. package/plugins/src/base/skills/lisa-detect-tooling/scripts/detect-tooling.mjs +25 -13
  104. package/plugins/src/base/skills/lisa-secrets-access/scripts/validate-config.mjs +72 -10
  105. package/plugins/src/base/skills/lisa-setup-local-env/SKILL.md +95 -0
  106. package/plugins/src/base/skills/lisa-setup-local-env/scripts/local-env.mjs +174 -0
  107. package/plugins/src/base/skills/lisa-setup-remote-env/SKILL.md +27 -11
  108. package/plugins/src/base/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +70 -30
  109. package/plugins/src/base/skills/lisa-setup-remote-env/scripts/toolchain.mjs +132 -6
@@ -100,7 +100,7 @@ function planRequired(tool, found) {
100
100
  * @param {{version: string|null, present: boolean}} found Probe result.
101
101
  * @returns {{name: string, action: string, reason: string}} The decision.
102
102
  */
103
- function planInstallable(tool, found) {
103
+ function planInstallable(tool, found, pinIsFloor = false) {
104
104
  if (!tool.version) {
105
105
  return {
106
106
  name: tool.name,
@@ -115,6 +115,25 @@ function planInstallable(tool, found) {
115
115
  reason: `${tool.name} ${tool.version} already installed`,
116
116
  };
117
117
  }
118
+ // On a laptop the pin is a floor, not an equality. A container is disposable
119
+ // and reproducible by construction, so an exact match is right there. A
120
+ // developer's machine is shared with every other project they work on, and
121
+ // installing a pinned binary into ~/.local/bin ahead of a NEWER one already on
122
+ // PATH is a downgrade this project imposed on all of them — for gh, pinned at
123
+ // 2.83.0 against a workstation running 2.96.0, that is the likely case rather
124
+ // than the exotic one.
125
+ if (
126
+ pinIsFloor &&
127
+ found.present &&
128
+ found.version &&
129
+ compareVersions(found.version, tool.version) > 0
130
+ ) {
131
+ return {
132
+ name: tool.name,
133
+ action: "newer",
134
+ reason: `${tool.name} ${found.version} is newer than the pinned ${tool.version} — leaving it alone`,
135
+ };
136
+ }
118
137
  return {
119
138
  name: tool.name,
120
139
  action: "install",
@@ -127,6 +146,72 @@ function planInstallable(tool, found) {
127
146
  /** Surfaces a manifest entry may name. */
128
147
  const KNOWN_SURFACES = new Set(["local", "remote"]);
129
148
 
149
+ /**
150
+ * The platform key a manifest entry is resolved against.
151
+ *
152
+ * `<platform>-<arch>` rather than either alone, because both halves change the
153
+ * artifact: an Apple Silicon laptop and an Intel one run different builds of the
154
+ * same release, and so do an arm64 container and an amd64 one.
155
+ * @param {{platform: string, arch: string}} [runtime] Injectable, for tests.
156
+ * @returns {string} A key such as "darwin-arm64" or "linux-x64".
157
+ */
158
+ export function currentPlatform(runtime = process) {
159
+ return `${runtime.platform}-${runtime.arch}`;
160
+ }
161
+
162
+ /**
163
+ * Collapse a manifest entry to the artifact for one platform.
164
+ *
165
+ * A download URL is platform-specific and a checksum doubly so, which the
166
+ * single-URL shape could not express: the only way to stop a laptop being handed
167
+ * a Linux binary was `surfaces: ["remote"]`, which bought that safety by making
168
+ * the tool uninstallable on the laptop entirely. So `bws` and `gh` — the two
169
+ * tools Lisa's own guardrails shell out to — were declared, required, and
170
+ * unprovisionable on the machine most likely to be missing them.
171
+ *
172
+ * A `platforms` map fixes the cause instead of the symptom. `install` lives
173
+ * inside each block rather than beside it, because the method varies too: gh
174
+ * publishes a .tar.gz for Linux and a .zip for macOS, so a single install method
175
+ * would have forced one platform onto an archive kind its vendor does not ship.
176
+ *
177
+ * A flat entry is still valid and means "identical everywhere" — true of every
178
+ * `npm-global` install, which is genuinely platform-independent.
179
+ * @param {object} tool Manifest entry.
180
+ * @param {string} [platform] Platform key to resolve for.
181
+ * @returns {object} The entry with its platform block merged in.
182
+ */
183
+ export function resolvePlatform(tool, platform = currentPlatform()) {
184
+ const { platforms } = tool;
185
+ if (platforms === undefined) return tool;
186
+ if (
187
+ typeof platforms !== "object" ||
188
+ platforms === null ||
189
+ Array.isArray(platforms)
190
+ ) {
191
+ throw new Error(
192
+ `${tool.name}: platforms must be an object keyed by <platform>-<arch>, ` +
193
+ `got ${Array.isArray(platforms) ? "an array" : typeof platforms}.\n` +
194
+ `Omit it when one artifact serves every platform.`
195
+ );
196
+ }
197
+ const block = platforms[platform];
198
+ if (!block) {
199
+ const known = Object.keys(platforms).sort().join(", ");
200
+ throw new Error(
201
+ `${tool.name}: no pin for ${platform}.\n` +
202
+ `Declared platforms: ${known || "(none)"}.\n` +
203
+ `Add a block for ${platform} with its own url and sha256, or drop the ` +
204
+ `tool from this surface. Guessing an artifact would defeat the ` +
205
+ `checksum.`
206
+ );
207
+ }
208
+ // The platform block wins over the shared fields, and `platforms` itself is
209
+ // dropped so a resolved entry is indistinguishable from a flat one — that is
210
+ // what lets assertPinned and the installers stay unaware of any of this.
211
+ const { platforms: _discarded, ...shared } = tool;
212
+ return { ...shared, ...block };
213
+ }
214
+
130
215
  /**
131
216
  * Whether a manifest entry applies to the surface being provisioned.
132
217
  *
@@ -171,18 +256,48 @@ export function appliesToSurface(tool, surface) {
171
256
 
172
257
  /**
173
258
  * Produce the complete plan for a toolchain manifest.
259
+ *
260
+ * Install entries are resolved to the running platform here rather than at
261
+ * install time, so a tool with no artifact for this machine is reported
262
+ * alongside every other problem instead of aborting the run at the first one.
263
+ * An operator fixing a manifest wants the whole list.
264
+ *
265
+ * `require` entries are deliberately not resolved: they carry a name and a
266
+ * minimum version, nothing platform-specific, and inventing a per-platform shape
267
+ * for them would be ceremony with no artifact behind it.
174
268
  * @param {{require?: object[], install?: object[]}} tools Manifest.
175
269
  * @param {(name: string) => {version: string|null, present: boolean}} probe Version probe.
176
- * @returns {Array<{name: string, action: string, reason: string}>} Ordered decisions.
270
+ * @param {string} [surface] Surface being provisioned.
271
+ * @param {string} [platform] Platform key to resolve install entries against.
272
+ * @returns {Array<{name: string, action: string, reason: string, tool?: object}>} Ordered decisions.
177
273
  */
178
- export function planToolchain(tools, probe, surface = "remote") {
274
+ export function planToolchain(
275
+ tools,
276
+ probe,
277
+ surface = "remote",
278
+ platform = currentPlatform()
279
+ ) {
179
280
  const plan = [];
180
281
  for (const tool of tools.require ?? [])
181
282
  if (appliesToSurface(tool, surface))
182
283
  plan.push(planRequired(tool, probe(tool.name)));
183
- for (const tool of tools.install ?? [])
184
- if (appliesToSurface(tool, surface))
185
- plan.push(planInstallable(tool, probe(tool.name)));
284
+ for (const tool of tools.install ?? []) {
285
+ if (!appliesToSurface(tool, surface)) continue;
286
+ let resolved;
287
+ try {
288
+ resolved = resolvePlatform(tool, platform);
289
+ } catch (err) {
290
+ plan.push({ name: tool.name, action: "invalid", reason: err.message });
291
+ continue;
292
+ }
293
+ // The resolved entry travels with the decision so the installer never has to
294
+ // resolve a second time — two resolutions are two chances to disagree, and
295
+ // the one that installs would be the one nothing tested.
296
+ plan.push({
297
+ ...planInstallable(resolved, probe(tool.name), surface === "local"),
298
+ tool: resolved,
299
+ });
300
+ }
186
301
  return plan;
187
302
  }
188
303
 
@@ -195,6 +310,17 @@ export function planToolchain(tools, probe, surface = "remote") {
195
310
  * @param {object} tool Manifest entry.
196
311
  */
197
312
  export function assertPinned(tool) {
313
+ // An unresolved entry reaching here means a caller skipped resolvePlatform and
314
+ // is about to read a url and sha256 that belong to no platform in particular.
315
+ // Refusing is the point: the failure this whole change exists to prevent is
316
+ // exactly "downloaded the wrong platform's artifact", and a silent pass here
317
+ // would reintroduce it one call site at a time.
318
+ if (tool.platforms !== undefined) {
319
+ throw new Error(
320
+ `${tool.name}: platform-specific entry was not resolved before install.\n` +
321
+ `Call resolvePlatform() first — the shared fields alone do not name an artifact.`
322
+ );
323
+ }
198
324
  // Both archive kinds carry the same obligation, and differ only in how they
199
325
  // are unpacked. gh, for one, publishes no zip for Linux at all — only .deb,
200
326
  // .rpm and .tar.gz — so a zip-only installer could not pin the CLI that
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "Expo and React Native-specific skills, agents, rules, and MCP servers.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "Harper/Fabric-specific Lisa rules for TypeScript component apps.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "NestJS-specific skills and migration write-protection hooks.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "Ruby on Rails-specific skills and hooks for RuboCop and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "TypeScript-specific hooks for formatting, linting, and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "Distributable LLM Wiki kernel — ingest, query, lint, and maintain a git-native markdown knowledge base across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.327.0",
3
+ "version": "2.328.0",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -0,0 +1,7 @@
1
+ ---
2
+ description: "Bring this machine in line with the toolchain the project declares. Reports every tool in remoteEnv.tools that is missing, outdated, or unpinned for this platform, and installs the missing ones into ~/.local/bin from the same pinned, checksummed entries the remote surfaces use — but only when asked with --install-tools. A newer tool already on PATH is left alone."
3
+ allowed-tools: ["Skill"]
4
+ argument-hint: "[--install-tools] [--json]"
5
+ ---
6
+
7
+ Use the /lisa-setup-local-env skill to report and optionally install the project's declared toolchain on this machine. $ARGUMENTS
@@ -46,7 +46,27 @@ What genuinely differs is **consent**, not the list:
46
46
 
47
47
  Omitting `surfaces` means every surface, because that is true of most tools and the cost of forgetting should be a redundant check rather than a silent absence.
48
48
 
49
- A platform-specific pin is the exception worth knowing: a Linux release archive must be `surfaces: ["remote"]`, with a matching `require` entry for `local`, so a laptop asserts the tool without being offered a binary it cannot run.
49
+ Platform differences are **not** expressed with `surfaces`. A downloaded tool declares a
50
+ `platforms` map keyed `<platform>-<arch>`, each block carrying its own `install` method, `url`,
51
+ and `sha256`:
52
+
53
+ ```json
54
+ "platforms": {
55
+ "linux-x64": { "install": "release-tar", "url": "...", "sha256": "..." },
56
+ "darwin-arm64": { "install": "release-zip", "url": "...", "sha256": "..." }
57
+ }
58
+ ```
59
+
60
+ The method lives inside the block because it varies — gh ships a `.tar.gz` for Linux and a
61
+ `.zip` for macOS. A flat entry means one artifact serves everything, which is true of
62
+ `npm-global` and nothing else.
63
+
64
+ This replaces an earlier convention worth naming, because its residue may still be in a
65
+ manifest you read: a Linux archive used to be marked `surfaces: ["remote"]` with a bare
66
+ `require` entry for `local`, so a laptop asserted the tool without being offered a binary it
67
+ could not run. That kept the wrong binary off the laptop by making the tool uninstallable
68
+ there — which is how `bws` and `gh`, the two CLIs Lisa's own guardrails shell out to, came to
69
+ be required on developer machines and provisionable only in containers.
50
70
 
51
71
  ## Usage
52
72