@codyswann/lisa 2.325.1 → 2.325.2

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 (58) hide show
  1. package/dist/core/upstream-evidence-manifest.js +1 -1
  2. package/package.json +1 -1
  3. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  4. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  5. package/plugins/lisa/.codex-plugin/skills/lisa-detect-tooling/scripts/detect-tooling.mjs +42 -7
  6. package/plugins/lisa/skills/lisa-detect-tooling/scripts/detect-tooling.mjs +42 -7
  7. package/plugins/lisa-agy/plugin.json +1 -1
  8. package/plugins/lisa-agy/skills/lisa-detect-tooling/scripts/detect-tooling.mjs +42 -7
  9. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  10. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  11. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  12. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  13. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  14. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  15. package/plugins/lisa-copilot/skills/lisa-detect-tooling/scripts/detect-tooling.mjs +42 -7
  16. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  17. package/plugins/lisa-cursor/skills/lisa-detect-tooling/scripts/detect-tooling.mjs +42 -7
  18. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  19. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  20. package/plugins/lisa-expo-agy/plugin.json +1 -1
  21. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  22. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  24. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  25. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  26. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  27. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  28. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  29. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  30. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  31. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  32. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  33. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  34. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  35. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  36. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  39. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  40. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  41. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  45. package/plugins/lisa-rails-agy/plugin.json +1 -1
  46. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  50. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  51. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  55. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  56. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  58. package/plugins/src/base/skills/lisa-detect-tooling/scripts/detect-tooling.mjs +42 -7
@@ -425,7 +425,7 @@ export const UPSTREAM_EVIDENCE_MANIFEST = Object.freeze({
425
425
  "plugins/src/base/skills/lisa-debrief/SKILL.md": "47e4cda36b07994ff47ab15fb04a17dd6b0c310ad804f9cae9d69637edaaa72a",
426
426
  "plugins/src/base/skills/lisa-delivery-effectiveness/SKILL.md": "21bc55fa0e86a9694bd22269fd089dbfae0c54c199262f46a4955447acea0f35",
427
427
  "plugins/src/base/skills/lisa-detect-tooling/SKILL.md": "1778a009d06099b3bd2d9a33b37bec34836b295e21fad220ae926491d3557208",
428
- "plugins/src/base/skills/lisa-detect-tooling/scripts/detect-tooling.mjs": "90086ca11dafb5250ee930fa78fd3cc7d4bd601ad57f3e3c1a49920f7d7a1493",
428
+ "plugins/src/base/skills/lisa-detect-tooling/scripts/detect-tooling.mjs": "ccbd2cf7251e792467ca74c664088a22b1c50ead579ad2279a55daa5d0ba0693",
429
429
  "plugins/src/base/skills/lisa-doctor/SKILL.md": "fb41a1e63c5332d47a46e715aff52551f3df867033b5e4f37dfaeee30019b891",
430
430
  "plugins/src/base/skills/lisa-drive-pr-to-merge/SKILL.md": "c78ae2ba32c830d0b2cd7d24de91adf99e2b3cef4c7cab1c417580d86f41351f",
431
431
  "plugins/src/base/skills/lisa-epic-triage/SKILL.md": "d02760411249bddbd396f283191fe3e82bb7b95bf9393a19a7025dc5a57c3ab7",
package/package.json CHANGED
@@ -115,7 +115,7 @@
115
115
  "brace-expansion": ">=5.0.9"
116
116
  },
117
117
  "name": "@codyswann/lisa",
118
- "version": "2.325.1",
118
+ "version": "2.325.2",
119
119
  "description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
120
120
  "main": "dist/index.js",
121
121
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.325.1",
3
+ "version": "2.325.2",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.325.1",
3
+ "version": "2.325.2",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -51,8 +51,13 @@ const KNOWN_TOOLS = Object.freeze({
51
51
  viaNpm: "@playwright/test",
52
52
  },
53
53
  linear: {
54
- why: "Linear is wired as an MCP server, which needs browser OAuth and so cannot authenticate in a container.",
54
+ why: "Linear reaches a container through lisa-linear-access with LINEAR_API_KEY; only the MCP path needs browser OAuth, and there is no official CLI to pin.",
55
55
  mcpFallback: "linear-server",
56
+ // No official Linear CLI exists, so no proposal can name one. Kept even
57
+ // though the substrate allowlist normally removes Linear before this
58
+ // point: a second signal reaching here must not resurrect a binary that
59
+ // does not exist.
60
+ noCli: true,
56
61
  },
57
62
  });
58
63
 
@@ -107,18 +112,35 @@ export function toolsFromScripts(pkg) {
107
112
  * Treating the MCP server as CLI evidence would push operators toward an
108
113
  * arbitrary third-party executable instead of the access-layer contract.
109
114
  */
115
+ /** Prefix marking evidence that shows a path is missing, not what fills it. */
116
+ const MCP_EVIDENCE = "MCP";
117
+
118
+ /** What to ask when the only evidence is an MCP-only integration. */
119
+ const REMOTE_PATH_QUESTION =
120
+ "no manifest entry proposed — confirm how this reaches a container: a CLI, " +
121
+ "a key-authenticated API call, or deliberately local-only";
122
+
110
123
  const MCP_ACCESS_LAYER_SUBSTRATES = Object.freeze({
111
124
  "linear-server":
112
125
  "lisa-linear-access uses LINEAR_API_KEY in headless sessions",
113
126
  });
114
127
 
115
128
  /**
116
- * Tools implied by MCP servers that also have a CLI.
129
+ * Integrations wired only as an MCP server the weakest signal here, and the
130
+ * easiest to over-read.
131
+ *
132
+ * True: an MCP server that authenticates interactively cannot authenticate in a
133
+ * container, so THAT path does not survive the trip.
117
134
  *
118
- * An MCP server is not a substitute for the binary. Several authenticate by
119
- * browser OAuth, which a container cannot do at all, so a project relying on one
120
- * remotely has no integration rather than a degraded one — the CLI is the form
121
- * that survives the trip.
135
+ * Not implied: that the integration is unavailable, or that a CLI is the
136
+ * answer. Linear proves both halves its MCP server needs browser OAuth, and
137
+ * its GraphQL API is key-authenticated, so `lisa-linear-access` already reaches
138
+ * it headlessly with LINEAR_API_KEY and there is no official CLI to pin at all.
139
+ * A server whose access layer owns headless auth is skipped entirely.
140
+ *
141
+ * For anything else, this raises a QUESTION rather than asserting a need: this
142
+ * integration has no remote path as configured, confirm it has one. The answer
143
+ * may be a CLI, may be a direct API call, and may be "local-only on purpose".
122
144
  * @param {object|null} mcp Parsed .mcp.json.
123
145
  * @returns {Map<string, string>} Tool name to the server that implies it.
124
146
  */
@@ -139,7 +161,8 @@ export function toolsFromMcp(mcp) {
139
161
  if (server) {
140
162
  found.set(
141
163
  tool,
142
- `MCP server "${server}" (browser OAuth cannot run remotely)`
164
+ `${MCP_EVIDENCE} server "${server}" an interactively authenticated ` +
165
+ `MCP server has no remote path; confirm this integration has one`
143
166
  );
144
167
  }
145
168
  }
@@ -263,6 +286,17 @@ export function detectTooling(cwd = process.cwd()) {
263
286
  * @returns {{install: object[], require: object[]}} Manifest skeletons.
264
287
  */
265
288
  export function proposedEntries(proposal) {
289
+ // Whether a binary can be proposed is a fact about the TOOL, not about how it
290
+ // was detected. Keying this on "the evidence was an MCP server" suppressed
291
+ // exactly the wrong case: the substrate allowlist already removes Linear
292
+ // before it becomes evidence, so the only tools reaching that branch were
293
+ // ones like Maestro that genuinely do have a CLI — the binary the Expo
294
+ // template invokes and nothing installs.
295
+ //
296
+ // So the question is asked only where there is no pinnable CLI to name.
297
+ if (KNOWN_TOOLS[proposal.name]?.noCli) {
298
+ return { install: [], require: [], question: REMOTE_PATH_QUESTION };
299
+ }
266
300
  const viaNpm = KNOWN_TOOLS[proposal.name]?.viaNpm;
267
301
  if (viaNpm) {
268
302
  // npm resolves per platform, so one entry serves every surface.
@@ -317,6 +351,7 @@ if (process.argv[1] && import.meta.url === `file://${process.argv[1]}`) {
317
351
  console.log(` evidence: ${evidence}`);
318
352
  }
319
353
  const entries = proposedEntries(proposal);
354
+ if (entries.question) console.log(` ${entries.question}`);
320
355
  for (const entry of entries.install) {
321
356
  console.log(` tools.install: ${JSON.stringify(entry)}`);
322
357
  }
@@ -51,8 +51,13 @@ const KNOWN_TOOLS = Object.freeze({
51
51
  viaNpm: "@playwright/test",
52
52
  },
53
53
  linear: {
54
- why: "Linear is wired as an MCP server, which needs browser OAuth and so cannot authenticate in a container.",
54
+ why: "Linear reaches a container through lisa-linear-access with LINEAR_API_KEY; only the MCP path needs browser OAuth, and there is no official CLI to pin.",
55
55
  mcpFallback: "linear-server",
56
+ // No official Linear CLI exists, so no proposal can name one. Kept even
57
+ // though the substrate allowlist normally removes Linear before this
58
+ // point: a second signal reaching here must not resurrect a binary that
59
+ // does not exist.
60
+ noCli: true,
56
61
  },
57
62
  });
58
63
 
@@ -107,18 +112,35 @@ export function toolsFromScripts(pkg) {
107
112
  * Treating the MCP server as CLI evidence would push operators toward an
108
113
  * arbitrary third-party executable instead of the access-layer contract.
109
114
  */
115
+ /** Prefix marking evidence that shows a path is missing, not what fills it. */
116
+ const MCP_EVIDENCE = "MCP";
117
+
118
+ /** What to ask when the only evidence is an MCP-only integration. */
119
+ const REMOTE_PATH_QUESTION =
120
+ "no manifest entry proposed — confirm how this reaches a container: a CLI, " +
121
+ "a key-authenticated API call, or deliberately local-only";
122
+
110
123
  const MCP_ACCESS_LAYER_SUBSTRATES = Object.freeze({
111
124
  "linear-server":
112
125
  "lisa-linear-access uses LINEAR_API_KEY in headless sessions",
113
126
  });
114
127
 
115
128
  /**
116
- * Tools implied by MCP servers that also have a CLI.
129
+ * Integrations wired only as an MCP server the weakest signal here, and the
130
+ * easiest to over-read.
131
+ *
132
+ * True: an MCP server that authenticates interactively cannot authenticate in a
133
+ * container, so THAT path does not survive the trip.
117
134
  *
118
- * An MCP server is not a substitute for the binary. Several authenticate by
119
- * browser OAuth, which a container cannot do at all, so a project relying on one
120
- * remotely has no integration rather than a degraded one — the CLI is the form
121
- * that survives the trip.
135
+ * Not implied: that the integration is unavailable, or that a CLI is the
136
+ * answer. Linear proves both halves its MCP server needs browser OAuth, and
137
+ * its GraphQL API is key-authenticated, so `lisa-linear-access` already reaches
138
+ * it headlessly with LINEAR_API_KEY and there is no official CLI to pin at all.
139
+ * A server whose access layer owns headless auth is skipped entirely.
140
+ *
141
+ * For anything else, this raises a QUESTION rather than asserting a need: this
142
+ * integration has no remote path as configured, confirm it has one. The answer
143
+ * may be a CLI, may be a direct API call, and may be "local-only on purpose".
122
144
  * @param {object|null} mcp Parsed .mcp.json.
123
145
  * @returns {Map<string, string>} Tool name to the server that implies it.
124
146
  */
@@ -139,7 +161,8 @@ export function toolsFromMcp(mcp) {
139
161
  if (server) {
140
162
  found.set(
141
163
  tool,
142
- `MCP server "${server}" (browser OAuth cannot run remotely)`
164
+ `${MCP_EVIDENCE} server "${server}" an interactively authenticated ` +
165
+ `MCP server has no remote path; confirm this integration has one`
143
166
  );
144
167
  }
145
168
  }
@@ -263,6 +286,17 @@ export function detectTooling(cwd = process.cwd()) {
263
286
  * @returns {{install: object[], require: object[]}} Manifest skeletons.
264
287
  */
265
288
  export function proposedEntries(proposal) {
289
+ // Whether a binary can be proposed is a fact about the TOOL, not about how it
290
+ // was detected. Keying this on "the evidence was an MCP server" suppressed
291
+ // exactly the wrong case: the substrate allowlist already removes Linear
292
+ // before it becomes evidence, so the only tools reaching that branch were
293
+ // ones like Maestro that genuinely do have a CLI — the binary the Expo
294
+ // template invokes and nothing installs.
295
+ //
296
+ // So the question is asked only where there is no pinnable CLI to name.
297
+ if (KNOWN_TOOLS[proposal.name]?.noCli) {
298
+ return { install: [], require: [], question: REMOTE_PATH_QUESTION };
299
+ }
266
300
  const viaNpm = KNOWN_TOOLS[proposal.name]?.viaNpm;
267
301
  if (viaNpm) {
268
302
  // npm resolves per platform, so one entry serves every surface.
@@ -317,6 +351,7 @@ if (process.argv[1] && import.meta.url === `file://${process.argv[1]}`) {
317
351
  console.log(` evidence: ${evidence}`);
318
352
  }
319
353
  const entries = proposedEntries(proposal);
354
+ if (entries.question) console.log(` ${entries.question}`);
320
355
  for (const entry of entries.install) {
321
356
  console.log(` tools.install: ${JSON.stringify(entry)}`);
322
357
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.325.1",
3
+ "version": "2.325.2",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -51,8 +51,13 @@ const KNOWN_TOOLS = Object.freeze({
51
51
  viaNpm: "@playwright/test",
52
52
  },
53
53
  linear: {
54
- why: "Linear is wired as an MCP server, which needs browser OAuth and so cannot authenticate in a container.",
54
+ why: "Linear reaches a container through lisa-linear-access with LINEAR_API_KEY; only the MCP path needs browser OAuth, and there is no official CLI to pin.",
55
55
  mcpFallback: "linear-server",
56
+ // No official Linear CLI exists, so no proposal can name one. Kept even
57
+ // though the substrate allowlist normally removes Linear before this
58
+ // point: a second signal reaching here must not resurrect a binary that
59
+ // does not exist.
60
+ noCli: true,
56
61
  },
57
62
  });
58
63
 
@@ -107,18 +112,35 @@ export function toolsFromScripts(pkg) {
107
112
  * Treating the MCP server as CLI evidence would push operators toward an
108
113
  * arbitrary third-party executable instead of the access-layer contract.
109
114
  */
115
+ /** Prefix marking evidence that shows a path is missing, not what fills it. */
116
+ const MCP_EVIDENCE = "MCP";
117
+
118
+ /** What to ask when the only evidence is an MCP-only integration. */
119
+ const REMOTE_PATH_QUESTION =
120
+ "no manifest entry proposed — confirm how this reaches a container: a CLI, " +
121
+ "a key-authenticated API call, or deliberately local-only";
122
+
110
123
  const MCP_ACCESS_LAYER_SUBSTRATES = Object.freeze({
111
124
  "linear-server":
112
125
  "lisa-linear-access uses LINEAR_API_KEY in headless sessions",
113
126
  });
114
127
 
115
128
  /**
116
- * Tools implied by MCP servers that also have a CLI.
129
+ * Integrations wired only as an MCP server the weakest signal here, and the
130
+ * easiest to over-read.
131
+ *
132
+ * True: an MCP server that authenticates interactively cannot authenticate in a
133
+ * container, so THAT path does not survive the trip.
117
134
  *
118
- * An MCP server is not a substitute for the binary. Several authenticate by
119
- * browser OAuth, which a container cannot do at all, so a project relying on one
120
- * remotely has no integration rather than a degraded one — the CLI is the form
121
- * that survives the trip.
135
+ * Not implied: that the integration is unavailable, or that a CLI is the
136
+ * answer. Linear proves both halves its MCP server needs browser OAuth, and
137
+ * its GraphQL API is key-authenticated, so `lisa-linear-access` already reaches
138
+ * it headlessly with LINEAR_API_KEY and there is no official CLI to pin at all.
139
+ * A server whose access layer owns headless auth is skipped entirely.
140
+ *
141
+ * For anything else, this raises a QUESTION rather than asserting a need: this
142
+ * integration has no remote path as configured, confirm it has one. The answer
143
+ * may be a CLI, may be a direct API call, and may be "local-only on purpose".
122
144
  * @param {object|null} mcp Parsed .mcp.json.
123
145
  * @returns {Map<string, string>} Tool name to the server that implies it.
124
146
  */
@@ -139,7 +161,8 @@ export function toolsFromMcp(mcp) {
139
161
  if (server) {
140
162
  found.set(
141
163
  tool,
142
- `MCP server "${server}" (browser OAuth cannot run remotely)`
164
+ `${MCP_EVIDENCE} server "${server}" an interactively authenticated ` +
165
+ `MCP server has no remote path; confirm this integration has one`
143
166
  );
144
167
  }
145
168
  }
@@ -263,6 +286,17 @@ export function detectTooling(cwd = process.cwd()) {
263
286
  * @returns {{install: object[], require: object[]}} Manifest skeletons.
264
287
  */
265
288
  export function proposedEntries(proposal) {
289
+ // Whether a binary can be proposed is a fact about the TOOL, not about how it
290
+ // was detected. Keying this on "the evidence was an MCP server" suppressed
291
+ // exactly the wrong case: the substrate allowlist already removes Linear
292
+ // before it becomes evidence, so the only tools reaching that branch were
293
+ // ones like Maestro that genuinely do have a CLI — the binary the Expo
294
+ // template invokes and nothing installs.
295
+ //
296
+ // So the question is asked only where there is no pinnable CLI to name.
297
+ if (KNOWN_TOOLS[proposal.name]?.noCli) {
298
+ return { install: [], require: [], question: REMOTE_PATH_QUESTION };
299
+ }
266
300
  const viaNpm = KNOWN_TOOLS[proposal.name]?.viaNpm;
267
301
  if (viaNpm) {
268
302
  // npm resolves per platform, so one entry serves every surface.
@@ -317,6 +351,7 @@ if (process.argv[1] && import.meta.url === `file://${process.argv[1]}`) {
317
351
  console.log(` evidence: ${evidence}`);
318
352
  }
319
353
  const entries = proposedEntries(proposal);
354
+ if (entries.question) console.log(` ${entries.question}`);
320
355
  for (const entry of entries.install) {
321
356
  console.log(` tools.install: ${JSON.stringify(entry)}`);
322
357
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.325.1",
3
+ "version": "2.325.2",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.325.1",
3
+ "version": "2.325.2",
4
4
  "description": "AWS CDK-specific Lisa plugin.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.325.1",
3
+ "version": "2.325.2",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.325.1",
3
+ "version": "2.325.2",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.325.1",
3
+ "version": "2.325.2",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.325.1",
3
+ "version": "2.325.2",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -51,8 +51,13 @@ const KNOWN_TOOLS = Object.freeze({
51
51
  viaNpm: "@playwright/test",
52
52
  },
53
53
  linear: {
54
- why: "Linear is wired as an MCP server, which needs browser OAuth and so cannot authenticate in a container.",
54
+ why: "Linear reaches a container through lisa-linear-access with LINEAR_API_KEY; only the MCP path needs browser OAuth, and there is no official CLI to pin.",
55
55
  mcpFallback: "linear-server",
56
+ // No official Linear CLI exists, so no proposal can name one. Kept even
57
+ // though the substrate allowlist normally removes Linear before this
58
+ // point: a second signal reaching here must not resurrect a binary that
59
+ // does not exist.
60
+ noCli: true,
56
61
  },
57
62
  });
58
63
 
@@ -107,18 +112,35 @@ export function toolsFromScripts(pkg) {
107
112
  * Treating the MCP server as CLI evidence would push operators toward an
108
113
  * arbitrary third-party executable instead of the access-layer contract.
109
114
  */
115
+ /** Prefix marking evidence that shows a path is missing, not what fills it. */
116
+ const MCP_EVIDENCE = "MCP";
117
+
118
+ /** What to ask when the only evidence is an MCP-only integration. */
119
+ const REMOTE_PATH_QUESTION =
120
+ "no manifest entry proposed — confirm how this reaches a container: a CLI, " +
121
+ "a key-authenticated API call, or deliberately local-only";
122
+
110
123
  const MCP_ACCESS_LAYER_SUBSTRATES = Object.freeze({
111
124
  "linear-server":
112
125
  "lisa-linear-access uses LINEAR_API_KEY in headless sessions",
113
126
  });
114
127
 
115
128
  /**
116
- * Tools implied by MCP servers that also have a CLI.
129
+ * Integrations wired only as an MCP server the weakest signal here, and the
130
+ * easiest to over-read.
131
+ *
132
+ * True: an MCP server that authenticates interactively cannot authenticate in a
133
+ * container, so THAT path does not survive the trip.
117
134
  *
118
- * An MCP server is not a substitute for the binary. Several authenticate by
119
- * browser OAuth, which a container cannot do at all, so a project relying on one
120
- * remotely has no integration rather than a degraded one — the CLI is the form
121
- * that survives the trip.
135
+ * Not implied: that the integration is unavailable, or that a CLI is the
136
+ * answer. Linear proves both halves its MCP server needs browser OAuth, and
137
+ * its GraphQL API is key-authenticated, so `lisa-linear-access` already reaches
138
+ * it headlessly with LINEAR_API_KEY and there is no official CLI to pin at all.
139
+ * A server whose access layer owns headless auth is skipped entirely.
140
+ *
141
+ * For anything else, this raises a QUESTION rather than asserting a need: this
142
+ * integration has no remote path as configured, confirm it has one. The answer
143
+ * may be a CLI, may be a direct API call, and may be "local-only on purpose".
122
144
  * @param {object|null} mcp Parsed .mcp.json.
123
145
  * @returns {Map<string, string>} Tool name to the server that implies it.
124
146
  */
@@ -139,7 +161,8 @@ export function toolsFromMcp(mcp) {
139
161
  if (server) {
140
162
  found.set(
141
163
  tool,
142
- `MCP server "${server}" (browser OAuth cannot run remotely)`
164
+ `${MCP_EVIDENCE} server "${server}" an interactively authenticated ` +
165
+ `MCP server has no remote path; confirm this integration has one`
143
166
  );
144
167
  }
145
168
  }
@@ -263,6 +286,17 @@ export function detectTooling(cwd = process.cwd()) {
263
286
  * @returns {{install: object[], require: object[]}} Manifest skeletons.
264
287
  */
265
288
  export function proposedEntries(proposal) {
289
+ // Whether a binary can be proposed is a fact about the TOOL, not about how it
290
+ // was detected. Keying this on "the evidence was an MCP server" suppressed
291
+ // exactly the wrong case: the substrate allowlist already removes Linear
292
+ // before it becomes evidence, so the only tools reaching that branch were
293
+ // ones like Maestro that genuinely do have a CLI — the binary the Expo
294
+ // template invokes and nothing installs.
295
+ //
296
+ // So the question is asked only where there is no pinnable CLI to name.
297
+ if (KNOWN_TOOLS[proposal.name]?.noCli) {
298
+ return { install: [], require: [], question: REMOTE_PATH_QUESTION };
299
+ }
266
300
  const viaNpm = KNOWN_TOOLS[proposal.name]?.viaNpm;
267
301
  if (viaNpm) {
268
302
  // npm resolves per platform, so one entry serves every surface.
@@ -317,6 +351,7 @@ if (process.argv[1] && import.meta.url === `file://${process.argv[1]}`) {
317
351
  console.log(` evidence: ${evidence}`);
318
352
  }
319
353
  const entries = proposedEntries(proposal);
354
+ if (entries.question) console.log(` ${entries.question}`);
320
355
  for (const entry of entries.install) {
321
356
  console.log(` tools.install: ${JSON.stringify(entry)}`);
322
357
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.325.1",
3
+ "version": "2.325.2",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -51,8 +51,13 @@ const KNOWN_TOOLS = Object.freeze({
51
51
  viaNpm: "@playwright/test",
52
52
  },
53
53
  linear: {
54
- why: "Linear is wired as an MCP server, which needs browser OAuth and so cannot authenticate in a container.",
54
+ why: "Linear reaches a container through lisa-linear-access with LINEAR_API_KEY; only the MCP path needs browser OAuth, and there is no official CLI to pin.",
55
55
  mcpFallback: "linear-server",
56
+ // No official Linear CLI exists, so no proposal can name one. Kept even
57
+ // though the substrate allowlist normally removes Linear before this
58
+ // point: a second signal reaching here must not resurrect a binary that
59
+ // does not exist.
60
+ noCli: true,
56
61
  },
57
62
  });
58
63
 
@@ -107,18 +112,35 @@ export function toolsFromScripts(pkg) {
107
112
  * Treating the MCP server as CLI evidence would push operators toward an
108
113
  * arbitrary third-party executable instead of the access-layer contract.
109
114
  */
115
+ /** Prefix marking evidence that shows a path is missing, not what fills it. */
116
+ const MCP_EVIDENCE = "MCP";
117
+
118
+ /** What to ask when the only evidence is an MCP-only integration. */
119
+ const REMOTE_PATH_QUESTION =
120
+ "no manifest entry proposed — confirm how this reaches a container: a CLI, " +
121
+ "a key-authenticated API call, or deliberately local-only";
122
+
110
123
  const MCP_ACCESS_LAYER_SUBSTRATES = Object.freeze({
111
124
  "linear-server":
112
125
  "lisa-linear-access uses LINEAR_API_KEY in headless sessions",
113
126
  });
114
127
 
115
128
  /**
116
- * Tools implied by MCP servers that also have a CLI.
129
+ * Integrations wired only as an MCP server the weakest signal here, and the
130
+ * easiest to over-read.
131
+ *
132
+ * True: an MCP server that authenticates interactively cannot authenticate in a
133
+ * container, so THAT path does not survive the trip.
117
134
  *
118
- * An MCP server is not a substitute for the binary. Several authenticate by
119
- * browser OAuth, which a container cannot do at all, so a project relying on one
120
- * remotely has no integration rather than a degraded one — the CLI is the form
121
- * that survives the trip.
135
+ * Not implied: that the integration is unavailable, or that a CLI is the
136
+ * answer. Linear proves both halves its MCP server needs browser OAuth, and
137
+ * its GraphQL API is key-authenticated, so `lisa-linear-access` already reaches
138
+ * it headlessly with LINEAR_API_KEY and there is no official CLI to pin at all.
139
+ * A server whose access layer owns headless auth is skipped entirely.
140
+ *
141
+ * For anything else, this raises a QUESTION rather than asserting a need: this
142
+ * integration has no remote path as configured, confirm it has one. The answer
143
+ * may be a CLI, may be a direct API call, and may be "local-only on purpose".
122
144
  * @param {object|null} mcp Parsed .mcp.json.
123
145
  * @returns {Map<string, string>} Tool name to the server that implies it.
124
146
  */
@@ -139,7 +161,8 @@ export function toolsFromMcp(mcp) {
139
161
  if (server) {
140
162
  found.set(
141
163
  tool,
142
- `MCP server "${server}" (browser OAuth cannot run remotely)`
164
+ `${MCP_EVIDENCE} server "${server}" an interactively authenticated ` +
165
+ `MCP server has no remote path; confirm this integration has one`
143
166
  );
144
167
  }
145
168
  }
@@ -263,6 +286,17 @@ export function detectTooling(cwd = process.cwd()) {
263
286
  * @returns {{install: object[], require: object[]}} Manifest skeletons.
264
287
  */
265
288
  export function proposedEntries(proposal) {
289
+ // Whether a binary can be proposed is a fact about the TOOL, not about how it
290
+ // was detected. Keying this on "the evidence was an MCP server" suppressed
291
+ // exactly the wrong case: the substrate allowlist already removes Linear
292
+ // before it becomes evidence, so the only tools reaching that branch were
293
+ // ones like Maestro that genuinely do have a CLI — the binary the Expo
294
+ // template invokes and nothing installs.
295
+ //
296
+ // So the question is asked only where there is no pinnable CLI to name.
297
+ if (KNOWN_TOOLS[proposal.name]?.noCli) {
298
+ return { install: [], require: [], question: REMOTE_PATH_QUESTION };
299
+ }
266
300
  const viaNpm = KNOWN_TOOLS[proposal.name]?.viaNpm;
267
301
  if (viaNpm) {
268
302
  // npm resolves per platform, so one entry serves every surface.
@@ -317,6 +351,7 @@ if (process.argv[1] && import.meta.url === `file://${process.argv[1]}`) {
317
351
  console.log(` evidence: ${evidence}`);
318
352
  }
319
353
  const entries = proposedEntries(proposal);
354
+ if (entries.question) console.log(` ${entries.question}`);
320
355
  for (const entry of entries.install) {
321
356
  console.log(` tools.install: ${JSON.stringify(entry)}`);
322
357
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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.325.1",
3
+ "version": "2.325.2",
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"
@@ -51,8 +51,13 @@ const KNOWN_TOOLS = Object.freeze({
51
51
  viaNpm: "@playwright/test",
52
52
  },
53
53
  linear: {
54
- why: "Linear is wired as an MCP server, which needs browser OAuth and so cannot authenticate in a container.",
54
+ why: "Linear reaches a container through lisa-linear-access with LINEAR_API_KEY; only the MCP path needs browser OAuth, and there is no official CLI to pin.",
55
55
  mcpFallback: "linear-server",
56
+ // No official Linear CLI exists, so no proposal can name one. Kept even
57
+ // though the substrate allowlist normally removes Linear before this
58
+ // point: a second signal reaching here must not resurrect a binary that
59
+ // does not exist.
60
+ noCli: true,
56
61
  },
57
62
  });
58
63
 
@@ -107,18 +112,35 @@ export function toolsFromScripts(pkg) {
107
112
  * Treating the MCP server as CLI evidence would push operators toward an
108
113
  * arbitrary third-party executable instead of the access-layer contract.
109
114
  */
115
+ /** Prefix marking evidence that shows a path is missing, not what fills it. */
116
+ const MCP_EVIDENCE = "MCP";
117
+
118
+ /** What to ask when the only evidence is an MCP-only integration. */
119
+ const REMOTE_PATH_QUESTION =
120
+ "no manifest entry proposed — confirm how this reaches a container: a CLI, " +
121
+ "a key-authenticated API call, or deliberately local-only";
122
+
110
123
  const MCP_ACCESS_LAYER_SUBSTRATES = Object.freeze({
111
124
  "linear-server":
112
125
  "lisa-linear-access uses LINEAR_API_KEY in headless sessions",
113
126
  });
114
127
 
115
128
  /**
116
- * Tools implied by MCP servers that also have a CLI.
129
+ * Integrations wired only as an MCP server the weakest signal here, and the
130
+ * easiest to over-read.
131
+ *
132
+ * True: an MCP server that authenticates interactively cannot authenticate in a
133
+ * container, so THAT path does not survive the trip.
117
134
  *
118
- * An MCP server is not a substitute for the binary. Several authenticate by
119
- * browser OAuth, which a container cannot do at all, so a project relying on one
120
- * remotely has no integration rather than a degraded one — the CLI is the form
121
- * that survives the trip.
135
+ * Not implied: that the integration is unavailable, or that a CLI is the
136
+ * answer. Linear proves both halves its MCP server needs browser OAuth, and
137
+ * its GraphQL API is key-authenticated, so `lisa-linear-access` already reaches
138
+ * it headlessly with LINEAR_API_KEY and there is no official CLI to pin at all.
139
+ * A server whose access layer owns headless auth is skipped entirely.
140
+ *
141
+ * For anything else, this raises a QUESTION rather than asserting a need: this
142
+ * integration has no remote path as configured, confirm it has one. The answer
143
+ * may be a CLI, may be a direct API call, and may be "local-only on purpose".
122
144
  * @param {object|null} mcp Parsed .mcp.json.
123
145
  * @returns {Map<string, string>} Tool name to the server that implies it.
124
146
  */
@@ -139,7 +161,8 @@ export function toolsFromMcp(mcp) {
139
161
  if (server) {
140
162
  found.set(
141
163
  tool,
142
- `MCP server "${server}" (browser OAuth cannot run remotely)`
164
+ `${MCP_EVIDENCE} server "${server}" an interactively authenticated ` +
165
+ `MCP server has no remote path; confirm this integration has one`
143
166
  );
144
167
  }
145
168
  }
@@ -263,6 +286,17 @@ export function detectTooling(cwd = process.cwd()) {
263
286
  * @returns {{install: object[], require: object[]}} Manifest skeletons.
264
287
  */
265
288
  export function proposedEntries(proposal) {
289
+ // Whether a binary can be proposed is a fact about the TOOL, not about how it
290
+ // was detected. Keying this on "the evidence was an MCP server" suppressed
291
+ // exactly the wrong case: the substrate allowlist already removes Linear
292
+ // before it becomes evidence, so the only tools reaching that branch were
293
+ // ones like Maestro that genuinely do have a CLI — the binary the Expo
294
+ // template invokes and nothing installs.
295
+ //
296
+ // So the question is asked only where there is no pinnable CLI to name.
297
+ if (KNOWN_TOOLS[proposal.name]?.noCli) {
298
+ return { install: [], require: [], question: REMOTE_PATH_QUESTION };
299
+ }
266
300
  const viaNpm = KNOWN_TOOLS[proposal.name]?.viaNpm;
267
301
  if (viaNpm) {
268
302
  // npm resolves per platform, so one entry serves every surface.
@@ -317,6 +351,7 @@ if (process.argv[1] && import.meta.url === `file://${process.argv[1]}`) {
317
351
  console.log(` evidence: ${evidence}`);
318
352
  }
319
353
  const entries = proposedEntries(proposal);
354
+ if (entries.question) console.log(` ${entries.question}`);
320
355
  for (const entry of entries.install) {
321
356
  console.log(` tools.install: ${JSON.stringify(entry)}`);
322
357
  }