@codyswann/lisa 2.325.0 → 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.
- package/dist/core/upstream-evidence-manifest.js +2 -2
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-detect-tooling/SKILL.md +2 -2
- package/plugins/lisa/.codex-plugin/skills/lisa-detect-tooling/scripts/detect-tooling.mjs +64 -7
- package/plugins/lisa/skills/lisa-detect-tooling/SKILL.md +2 -2
- package/plugins/lisa/skills/lisa-detect-tooling/scripts/detect-tooling.mjs +64 -7
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-detect-tooling/SKILL.md +2 -2
- package/plugins/lisa-agy/skills/lisa-detect-tooling/scripts/detect-tooling.mjs +64 -7
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/skills/lisa-detect-tooling/SKILL.md +2 -2
- package/plugins/lisa-copilot/skills/lisa-detect-tooling/scripts/detect-tooling.mjs +64 -7
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/skills/lisa-detect-tooling/SKILL.md +2 -2
- package/plugins/lisa-cursor/skills/lisa-detect-tooling/scripts/detect-tooling.mjs +64 -7
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/skills/lisa-detect-tooling/SKILL.md +2 -2
- package/plugins/src/base/skills/lisa-detect-tooling/scripts/detect-tooling.mjs +64 -7
|
@@ -424,8 +424,8 @@ export const UPSTREAM_EVIDENCE_MANIFEST = Object.freeze({
|
|
|
424
424
|
"plugins/src/base/skills/lisa-debrief-apply/SKILL.md": "90c5f01994bb0f8e71947e8a2f765ccf9a6aa3da47b8d7b8e2022150ef89c2b2",
|
|
425
425
|
"plugins/src/base/skills/lisa-debrief/SKILL.md": "47e4cda36b07994ff47ab15fb04a17dd6b0c310ad804f9cae9d69637edaaa72a",
|
|
426
426
|
"plugins/src/base/skills/lisa-delivery-effectiveness/SKILL.md": "21bc55fa0e86a9694bd22269fd089dbfae0c54c199262f46a4955447acea0f35",
|
|
427
|
-
"plugins/src/base/skills/lisa-detect-tooling/SKILL.md": "
|
|
428
|
-
"plugins/src/base/skills/lisa-detect-tooling/scripts/detect-tooling.mjs": "
|
|
427
|
+
"plugins/src/base/skills/lisa-detect-tooling/SKILL.md": "1778a009d06099b3bd2d9a33b37bec34836b295e21fad220ae926491d3557208",
|
|
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.
|
|
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": {
|
|
@@ -13,7 +13,7 @@ Lisa provisions tooling through four unrelated mechanisms, and only one of them
|
|
|
13
13
|
| playwright, stryker | npm devDependency from a stack template | nothing — a local `node_modules` binary |
|
|
14
14
|
| maestro | npm **scripts** in the Expo template | **nothing** |
|
|
15
15
|
| sonar | `src/sonar/sonar-installer.ts` | nothing |
|
|
16
|
-
| linear | MCP server
|
|
16
|
+
| linear | MCP server or `LINEAR_API_KEY` | `lisa-linear-access`, not a CLI |
|
|
17
17
|
| bws, gh | `remoteEnv.tools` — pinned and checksummed | this, and only this |
|
|
18
18
|
|
|
19
19
|
Nothing populates that last row. So a project can ship scripts that invoke `maestro`, wire an MCP server whose CLI it also shells out to, and configure Playwright thresholds, while the manifest that actually provisions binaries stays empty — and every one of those fails at the moment of use rather than at setup.
|
|
@@ -25,7 +25,7 @@ That is the same failure this repository has now paid for twice: `gh` was declar
|
|
|
25
25
|
Reads four signals, subtracts what `remoteEnv.tools` already declares, and prints what is left with the evidence for each:
|
|
26
26
|
|
|
27
27
|
- **npm scripts** that invoke a binary. The strongest signal there is — a script running `maestro test` is the project stating a dependency in executable form. Matched on the script *body*, not its name, because the name is a label.
|
|
28
|
-
- **MCP servers with a CLI equivalent.** An MCP server is not a substitute for the binary: several authenticate by browser OAuth, which a container cannot do, so a project relying on one remotely has *no* integration rather than a degraded one.
|
|
28
|
+
- **MCP servers with a CLI equivalent.** An MCP server is not a substitute for the binary: several authenticate by browser OAuth, which a container cannot do, so a project relying on one remotely has *no* integration rather than a degraded one. Do not infer a CLI for access layers that already define their own headless substrate; Linear is handled by `lisa-linear-access` through `LINEAR_API_KEY` + GraphQL when MCP OAuth is unavailable.
|
|
29
29
|
- **Credential usage notes.** A note explaining what a token is for usually names the program that consumes it. `lisa-secrets-access` already exposes these without touching a value, which makes them a first-class input rather than a trick.
|
|
30
30
|
- **Quality configuration**, where a threshold implies the tool that produces it.
|
|
31
31
|
|
|
@@ -51,8 +51,13 @@ const KNOWN_TOOLS = Object.freeze({
|
|
|
51
51
|
viaNpm: "@playwright/test",
|
|
52
52
|
},
|
|
53
53
|
linear: {
|
|
54
|
-
why: "Linear
|
|
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
|
|
|
@@ -99,12 +104,43 @@ export function toolsFromScripts(pkg) {
|
|
|
99
104
|
}
|
|
100
105
|
|
|
101
106
|
/**
|
|
102
|
-
*
|
|
107
|
+
* MCP servers that are access substrates rather than evidence for a CLI.
|
|
103
108
|
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
109
|
+
* Linear is the important case: Lisa's `lisa-linear-access` skill owns the
|
|
110
|
+
* headless path through `LINEAR_API_KEY` + GraphQL. There is no official Linear
|
|
111
|
+
* CLI to pin, and the unrelated `linear` npm package is not a tracker binary.
|
|
112
|
+
* Treating the MCP server as CLI evidence would push operators toward an
|
|
113
|
+
* arbitrary third-party executable instead of the access-layer contract.
|
|
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
|
+
|
|
123
|
+
const MCP_ACCESS_LAYER_SUBSTRATES = Object.freeze({
|
|
124
|
+
"linear-server":
|
|
125
|
+
"lisa-linear-access uses LINEAR_API_KEY in headless sessions",
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
/**
|
|
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.
|
|
134
|
+
*
|
|
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".
|
|
108
144
|
* @param {object|null} mcp Parsed .mcp.json.
|
|
109
145
|
* @returns {Map<string, string>} Tool name to the server that implies it.
|
|
110
146
|
*/
|
|
@@ -114,10 +150,19 @@ export function toolsFromMcp(mcp) {
|
|
|
114
150
|
for (const [tool, meta] of Object.entries(KNOWN_TOOLS)) {
|
|
115
151
|
if (!meta.mcpFallback) continue;
|
|
116
152
|
const server = servers.find(name => name.includes(meta.mcpFallback));
|
|
153
|
+
if (
|
|
154
|
+
server &&
|
|
155
|
+
Object.keys(MCP_ACCESS_LAYER_SUBSTRATES).some(substrate =>
|
|
156
|
+
server.includes(substrate)
|
|
157
|
+
)
|
|
158
|
+
) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
117
161
|
if (server) {
|
|
118
162
|
found.set(
|
|
119
163
|
tool,
|
|
120
|
-
|
|
164
|
+
`${MCP_EVIDENCE} server "${server}" — an interactively authenticated ` +
|
|
165
|
+
`MCP server has no remote path; confirm this integration has one`
|
|
121
166
|
);
|
|
122
167
|
}
|
|
123
168
|
}
|
|
@@ -241,6 +286,17 @@ export function detectTooling(cwd = process.cwd()) {
|
|
|
241
286
|
* @returns {{install: object[], require: object[]}} Manifest skeletons.
|
|
242
287
|
*/
|
|
243
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
|
+
}
|
|
244
300
|
const viaNpm = KNOWN_TOOLS[proposal.name]?.viaNpm;
|
|
245
301
|
if (viaNpm) {
|
|
246
302
|
// npm resolves per platform, so one entry serves every surface.
|
|
@@ -295,6 +351,7 @@ if (process.argv[1] && import.meta.url === `file://${process.argv[1]}`) {
|
|
|
295
351
|
console.log(` evidence: ${evidence}`);
|
|
296
352
|
}
|
|
297
353
|
const entries = proposedEntries(proposal);
|
|
354
|
+
if (entries.question) console.log(` ${entries.question}`);
|
|
298
355
|
for (const entry of entries.install) {
|
|
299
356
|
console.log(` tools.install: ${JSON.stringify(entry)}`);
|
|
300
357
|
}
|
|
@@ -13,7 +13,7 @@ Lisa provisions tooling through four unrelated mechanisms, and only one of them
|
|
|
13
13
|
| playwright, stryker | npm devDependency from a stack template | nothing — a local `node_modules` binary |
|
|
14
14
|
| maestro | npm **scripts** in the Expo template | **nothing** |
|
|
15
15
|
| sonar | `src/sonar/sonar-installer.ts` | nothing |
|
|
16
|
-
| linear | MCP server
|
|
16
|
+
| linear | MCP server or `LINEAR_API_KEY` | `lisa-linear-access`, not a CLI |
|
|
17
17
|
| bws, gh | `remoteEnv.tools` — pinned and checksummed | this, and only this |
|
|
18
18
|
|
|
19
19
|
Nothing populates that last row. So a project can ship scripts that invoke `maestro`, wire an MCP server whose CLI it also shells out to, and configure Playwright thresholds, while the manifest that actually provisions binaries stays empty — and every one of those fails at the moment of use rather than at setup.
|
|
@@ -25,7 +25,7 @@ That is the same failure this repository has now paid for twice: `gh` was declar
|
|
|
25
25
|
Reads four signals, subtracts what `remoteEnv.tools` already declares, and prints what is left with the evidence for each:
|
|
26
26
|
|
|
27
27
|
- **npm scripts** that invoke a binary. The strongest signal there is — a script running `maestro test` is the project stating a dependency in executable form. Matched on the script *body*, not its name, because the name is a label.
|
|
28
|
-
- **MCP servers with a CLI equivalent.** An MCP server is not a substitute for the binary: several authenticate by browser OAuth, which a container cannot do, so a project relying on one remotely has *no* integration rather than a degraded one.
|
|
28
|
+
- **MCP servers with a CLI equivalent.** An MCP server is not a substitute for the binary: several authenticate by browser OAuth, which a container cannot do, so a project relying on one remotely has *no* integration rather than a degraded one. Do not infer a CLI for access layers that already define their own headless substrate; Linear is handled by `lisa-linear-access` through `LINEAR_API_KEY` + GraphQL when MCP OAuth is unavailable.
|
|
29
29
|
- **Credential usage notes.** A note explaining what a token is for usually names the program that consumes it. `lisa-secrets-access` already exposes these without touching a value, which makes them a first-class input rather than a trick.
|
|
30
30
|
- **Quality configuration**, where a threshold implies the tool that produces it.
|
|
31
31
|
|
|
@@ -51,8 +51,13 @@ const KNOWN_TOOLS = Object.freeze({
|
|
|
51
51
|
viaNpm: "@playwright/test",
|
|
52
52
|
},
|
|
53
53
|
linear: {
|
|
54
|
-
why: "Linear
|
|
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
|
|
|
@@ -99,12 +104,43 @@ export function toolsFromScripts(pkg) {
|
|
|
99
104
|
}
|
|
100
105
|
|
|
101
106
|
/**
|
|
102
|
-
*
|
|
107
|
+
* MCP servers that are access substrates rather than evidence for a CLI.
|
|
103
108
|
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
109
|
+
* Linear is the important case: Lisa's `lisa-linear-access` skill owns the
|
|
110
|
+
* headless path through `LINEAR_API_KEY` + GraphQL. There is no official Linear
|
|
111
|
+
* CLI to pin, and the unrelated `linear` npm package is not a tracker binary.
|
|
112
|
+
* Treating the MCP server as CLI evidence would push operators toward an
|
|
113
|
+
* arbitrary third-party executable instead of the access-layer contract.
|
|
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
|
+
|
|
123
|
+
const MCP_ACCESS_LAYER_SUBSTRATES = Object.freeze({
|
|
124
|
+
"linear-server":
|
|
125
|
+
"lisa-linear-access uses LINEAR_API_KEY in headless sessions",
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
/**
|
|
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.
|
|
134
|
+
*
|
|
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".
|
|
108
144
|
* @param {object|null} mcp Parsed .mcp.json.
|
|
109
145
|
* @returns {Map<string, string>} Tool name to the server that implies it.
|
|
110
146
|
*/
|
|
@@ -114,10 +150,19 @@ export function toolsFromMcp(mcp) {
|
|
|
114
150
|
for (const [tool, meta] of Object.entries(KNOWN_TOOLS)) {
|
|
115
151
|
if (!meta.mcpFallback) continue;
|
|
116
152
|
const server = servers.find(name => name.includes(meta.mcpFallback));
|
|
153
|
+
if (
|
|
154
|
+
server &&
|
|
155
|
+
Object.keys(MCP_ACCESS_LAYER_SUBSTRATES).some(substrate =>
|
|
156
|
+
server.includes(substrate)
|
|
157
|
+
)
|
|
158
|
+
) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
117
161
|
if (server) {
|
|
118
162
|
found.set(
|
|
119
163
|
tool,
|
|
120
|
-
|
|
164
|
+
`${MCP_EVIDENCE} server "${server}" — an interactively authenticated ` +
|
|
165
|
+
`MCP server has no remote path; confirm this integration has one`
|
|
121
166
|
);
|
|
122
167
|
}
|
|
123
168
|
}
|
|
@@ -241,6 +286,17 @@ export function detectTooling(cwd = process.cwd()) {
|
|
|
241
286
|
* @returns {{install: object[], require: object[]}} Manifest skeletons.
|
|
242
287
|
*/
|
|
243
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
|
+
}
|
|
244
300
|
const viaNpm = KNOWN_TOOLS[proposal.name]?.viaNpm;
|
|
245
301
|
if (viaNpm) {
|
|
246
302
|
// npm resolves per platform, so one entry serves every surface.
|
|
@@ -295,6 +351,7 @@ if (process.argv[1] && import.meta.url === `file://${process.argv[1]}`) {
|
|
|
295
351
|
console.log(` evidence: ${evidence}`);
|
|
296
352
|
}
|
|
297
353
|
const entries = proposedEntries(proposal);
|
|
354
|
+
if (entries.question) console.log(` ${entries.question}`);
|
|
298
355
|
for (const entry of entries.install) {
|
|
299
356
|
console.log(` tools.install: ${JSON.stringify(entry)}`);
|
|
300
357
|
}
|
|
@@ -13,7 +13,7 @@ Lisa provisions tooling through four unrelated mechanisms, and only one of them
|
|
|
13
13
|
| playwright, stryker | npm devDependency from a stack template | nothing — a local `node_modules` binary |
|
|
14
14
|
| maestro | npm **scripts** in the Expo template | **nothing** |
|
|
15
15
|
| sonar | `src/sonar/sonar-installer.ts` | nothing |
|
|
16
|
-
| linear | MCP server
|
|
16
|
+
| linear | MCP server or `LINEAR_API_KEY` | `lisa-linear-access`, not a CLI |
|
|
17
17
|
| bws, gh | `remoteEnv.tools` — pinned and checksummed | this, and only this |
|
|
18
18
|
|
|
19
19
|
Nothing populates that last row. So a project can ship scripts that invoke `maestro`, wire an MCP server whose CLI it also shells out to, and configure Playwright thresholds, while the manifest that actually provisions binaries stays empty — and every one of those fails at the moment of use rather than at setup.
|
|
@@ -25,7 +25,7 @@ That is the same failure this repository has now paid for twice: `gh` was declar
|
|
|
25
25
|
Reads four signals, subtracts what `remoteEnv.tools` already declares, and prints what is left with the evidence for each:
|
|
26
26
|
|
|
27
27
|
- **npm scripts** that invoke a binary. The strongest signal there is — a script running `maestro test` is the project stating a dependency in executable form. Matched on the script *body*, not its name, because the name is a label.
|
|
28
|
-
- **MCP servers with a CLI equivalent.** An MCP server is not a substitute for the binary: several authenticate by browser OAuth, which a container cannot do, so a project relying on one remotely has *no* integration rather than a degraded one.
|
|
28
|
+
- **MCP servers with a CLI equivalent.** An MCP server is not a substitute for the binary: several authenticate by browser OAuth, which a container cannot do, so a project relying on one remotely has *no* integration rather than a degraded one. Do not infer a CLI for access layers that already define their own headless substrate; Linear is handled by `lisa-linear-access` through `LINEAR_API_KEY` + GraphQL when MCP OAuth is unavailable.
|
|
29
29
|
- **Credential usage notes.** A note explaining what a token is for usually names the program that consumes it. `lisa-secrets-access` already exposes these without touching a value, which makes them a first-class input rather than a trick.
|
|
30
30
|
- **Quality configuration**, where a threshold implies the tool that produces it.
|
|
31
31
|
|
|
@@ -51,8 +51,13 @@ const KNOWN_TOOLS = Object.freeze({
|
|
|
51
51
|
viaNpm: "@playwright/test",
|
|
52
52
|
},
|
|
53
53
|
linear: {
|
|
54
|
-
why: "Linear
|
|
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
|
|
|
@@ -99,12 +104,43 @@ export function toolsFromScripts(pkg) {
|
|
|
99
104
|
}
|
|
100
105
|
|
|
101
106
|
/**
|
|
102
|
-
*
|
|
107
|
+
* MCP servers that are access substrates rather than evidence for a CLI.
|
|
103
108
|
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
109
|
+
* Linear is the important case: Lisa's `lisa-linear-access` skill owns the
|
|
110
|
+
* headless path through `LINEAR_API_KEY` + GraphQL. There is no official Linear
|
|
111
|
+
* CLI to pin, and the unrelated `linear` npm package is not a tracker binary.
|
|
112
|
+
* Treating the MCP server as CLI evidence would push operators toward an
|
|
113
|
+
* arbitrary third-party executable instead of the access-layer contract.
|
|
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
|
+
|
|
123
|
+
const MCP_ACCESS_LAYER_SUBSTRATES = Object.freeze({
|
|
124
|
+
"linear-server":
|
|
125
|
+
"lisa-linear-access uses LINEAR_API_KEY in headless sessions",
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
/**
|
|
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.
|
|
134
|
+
*
|
|
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".
|
|
108
144
|
* @param {object|null} mcp Parsed .mcp.json.
|
|
109
145
|
* @returns {Map<string, string>} Tool name to the server that implies it.
|
|
110
146
|
*/
|
|
@@ -114,10 +150,19 @@ export function toolsFromMcp(mcp) {
|
|
|
114
150
|
for (const [tool, meta] of Object.entries(KNOWN_TOOLS)) {
|
|
115
151
|
if (!meta.mcpFallback) continue;
|
|
116
152
|
const server = servers.find(name => name.includes(meta.mcpFallback));
|
|
153
|
+
if (
|
|
154
|
+
server &&
|
|
155
|
+
Object.keys(MCP_ACCESS_LAYER_SUBSTRATES).some(substrate =>
|
|
156
|
+
server.includes(substrate)
|
|
157
|
+
)
|
|
158
|
+
) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
117
161
|
if (server) {
|
|
118
162
|
found.set(
|
|
119
163
|
tool,
|
|
120
|
-
|
|
164
|
+
`${MCP_EVIDENCE} server "${server}" — an interactively authenticated ` +
|
|
165
|
+
`MCP server has no remote path; confirm this integration has one`
|
|
121
166
|
);
|
|
122
167
|
}
|
|
123
168
|
}
|
|
@@ -241,6 +286,17 @@ export function detectTooling(cwd = process.cwd()) {
|
|
|
241
286
|
* @returns {{install: object[], require: object[]}} Manifest skeletons.
|
|
242
287
|
*/
|
|
243
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
|
+
}
|
|
244
300
|
const viaNpm = KNOWN_TOOLS[proposal.name]?.viaNpm;
|
|
245
301
|
if (viaNpm) {
|
|
246
302
|
// npm resolves per platform, so one entry serves every surface.
|
|
@@ -295,6 +351,7 @@ if (process.argv[1] && import.meta.url === `file://${process.argv[1]}`) {
|
|
|
295
351
|
console.log(` evidence: ${evidence}`);
|
|
296
352
|
}
|
|
297
353
|
const entries = proposedEntries(proposal);
|
|
354
|
+
if (entries.question) console.log(` ${entries.question}`);
|
|
298
355
|
for (const entry of entries.install) {
|
|
299
356
|
console.log(` tools.install: ${JSON.stringify(entry)}`);
|
|
300
357
|
}
|
|
@@ -13,7 +13,7 @@ Lisa provisions tooling through four unrelated mechanisms, and only one of them
|
|
|
13
13
|
| playwright, stryker | npm devDependency from a stack template | nothing — a local `node_modules` binary |
|
|
14
14
|
| maestro | npm **scripts** in the Expo template | **nothing** |
|
|
15
15
|
| sonar | `src/sonar/sonar-installer.ts` | nothing |
|
|
16
|
-
| linear | MCP server
|
|
16
|
+
| linear | MCP server or `LINEAR_API_KEY` | `lisa-linear-access`, not a CLI |
|
|
17
17
|
| bws, gh | `remoteEnv.tools` — pinned and checksummed | this, and only this |
|
|
18
18
|
|
|
19
19
|
Nothing populates that last row. So a project can ship scripts that invoke `maestro`, wire an MCP server whose CLI it also shells out to, and configure Playwright thresholds, while the manifest that actually provisions binaries stays empty — and every one of those fails at the moment of use rather than at setup.
|
|
@@ -25,7 +25,7 @@ That is the same failure this repository has now paid for twice: `gh` was declar
|
|
|
25
25
|
Reads four signals, subtracts what `remoteEnv.tools` already declares, and prints what is left with the evidence for each:
|
|
26
26
|
|
|
27
27
|
- **npm scripts** that invoke a binary. The strongest signal there is — a script running `maestro test` is the project stating a dependency in executable form. Matched on the script *body*, not its name, because the name is a label.
|
|
28
|
-
- **MCP servers with a CLI equivalent.** An MCP server is not a substitute for the binary: several authenticate by browser OAuth, which a container cannot do, so a project relying on one remotely has *no* integration rather than a degraded one.
|
|
28
|
+
- **MCP servers with a CLI equivalent.** An MCP server is not a substitute for the binary: several authenticate by browser OAuth, which a container cannot do, so a project relying on one remotely has *no* integration rather than a degraded one. Do not infer a CLI for access layers that already define their own headless substrate; Linear is handled by `lisa-linear-access` through `LINEAR_API_KEY` + GraphQL when MCP OAuth is unavailable.
|
|
29
29
|
- **Credential usage notes.** A note explaining what a token is for usually names the program that consumes it. `lisa-secrets-access` already exposes these without touching a value, which makes them a first-class input rather than a trick.
|
|
30
30
|
- **Quality configuration**, where a threshold implies the tool that produces it.
|
|
31
31
|
|
|
@@ -51,8 +51,13 @@ const KNOWN_TOOLS = Object.freeze({
|
|
|
51
51
|
viaNpm: "@playwright/test",
|
|
52
52
|
},
|
|
53
53
|
linear: {
|
|
54
|
-
why: "Linear
|
|
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
|
|
|
@@ -99,12 +104,43 @@ export function toolsFromScripts(pkg) {
|
|
|
99
104
|
}
|
|
100
105
|
|
|
101
106
|
/**
|
|
102
|
-
*
|
|
107
|
+
* MCP servers that are access substrates rather than evidence for a CLI.
|
|
103
108
|
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
109
|
+
* Linear is the important case: Lisa's `lisa-linear-access` skill owns the
|
|
110
|
+
* headless path through `LINEAR_API_KEY` + GraphQL. There is no official Linear
|
|
111
|
+
* CLI to pin, and the unrelated `linear` npm package is not a tracker binary.
|
|
112
|
+
* Treating the MCP server as CLI evidence would push operators toward an
|
|
113
|
+
* arbitrary third-party executable instead of the access-layer contract.
|
|
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
|
+
|
|
123
|
+
const MCP_ACCESS_LAYER_SUBSTRATES = Object.freeze({
|
|
124
|
+
"linear-server":
|
|
125
|
+
"lisa-linear-access uses LINEAR_API_KEY in headless sessions",
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
/**
|
|
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.
|
|
134
|
+
*
|
|
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".
|
|
108
144
|
* @param {object|null} mcp Parsed .mcp.json.
|
|
109
145
|
* @returns {Map<string, string>} Tool name to the server that implies it.
|
|
110
146
|
*/
|
|
@@ -114,10 +150,19 @@ export function toolsFromMcp(mcp) {
|
|
|
114
150
|
for (const [tool, meta] of Object.entries(KNOWN_TOOLS)) {
|
|
115
151
|
if (!meta.mcpFallback) continue;
|
|
116
152
|
const server = servers.find(name => name.includes(meta.mcpFallback));
|
|
153
|
+
if (
|
|
154
|
+
server &&
|
|
155
|
+
Object.keys(MCP_ACCESS_LAYER_SUBSTRATES).some(substrate =>
|
|
156
|
+
server.includes(substrate)
|
|
157
|
+
)
|
|
158
|
+
) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
117
161
|
if (server) {
|
|
118
162
|
found.set(
|
|
119
163
|
tool,
|
|
120
|
-
|
|
164
|
+
`${MCP_EVIDENCE} server "${server}" — an interactively authenticated ` +
|
|
165
|
+
`MCP server has no remote path; confirm this integration has one`
|
|
121
166
|
);
|
|
122
167
|
}
|
|
123
168
|
}
|
|
@@ -241,6 +286,17 @@ export function detectTooling(cwd = process.cwd()) {
|
|
|
241
286
|
* @returns {{install: object[], require: object[]}} Manifest skeletons.
|
|
242
287
|
*/
|
|
243
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
|
+
}
|
|
244
300
|
const viaNpm = KNOWN_TOOLS[proposal.name]?.viaNpm;
|
|
245
301
|
if (viaNpm) {
|
|
246
302
|
// npm resolves per platform, so one entry serves every surface.
|
|
@@ -295,6 +351,7 @@ if (process.argv[1] && import.meta.url === `file://${process.argv[1]}`) {
|
|
|
295
351
|
console.log(` evidence: ${evidence}`);
|
|
296
352
|
}
|
|
297
353
|
const entries = proposedEntries(proposal);
|
|
354
|
+
if (entries.question) console.log(` ${entries.question}`);
|
|
298
355
|
for (const entry of entries.install) {
|
|
299
356
|
console.log(` tools.install: ${JSON.stringify(entry)}`);
|
|
300
357
|
}
|