-
Notifications
You must be signed in to change notification settings - Fork 0
ci(release): type-check the packed tarball from the consumer side (closes #77) #78
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
84b57b6
635218c
291cfed
bb07098
cde62c3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,7 +11,11 @@ name: Release (public npm) | |
| # PACKS the real tarball, installs it into a throwaway | ||
| # project, and imports it -- root AND the ./sdk-server | ||
| # subpath. ESM only: this package ships no `require` | ||
| # condition, so a CJS arm would be a false failure. | ||
| # condition, so a CJS arm would be a false failure. The | ||
| # smoke then type-checks the installed tarball from the | ||
| # CONSUMER's side (#77): root without the optional peer | ||
| # dependency, ./sdk-server with it. Shipping a .d.ts is not | ||
| # the same claim as shipping one that resolves. | ||
| # 3. publish — only after 1+2 pass. The tag version MUST equal | ||
| # package.json version, and the npm dist-tag is derived from | ||
| # the version: any prerelease -> `next`, stable -> `latest`, | ||
|
|
@@ -208,6 +212,82 @@ jobs: | |
| } | ||
| console.log('types ok:',[...targets].join(', '));" | ||
|
|
||
| # ------------------------------------------------------------------- | ||
| # Consumer-side TYPE RESOLUTION (#77). | ||
| # | ||
| # The check above proves the .d.ts files are IN the tarball. It does | ||
| # not prove they RESOLVE, and only the second is something a consumer | ||
| # experiences. `dist/sdk-server.d.ts` references a type from | ||
| # @anthropic-ai/claude-agent-sdk, which is an OPTIONAL peer, so the | ||
| # existence check would stay green on a package that fails to | ||
| # type-check for anyone who did not install that peer. | ||
| # | ||
| # Decision (a) on #77: requiring the peer to type-check ./sdk-server | ||
| # is honest -- that subpath exists to build a config for the Agent | ||
| # SDK, so a consumer using it has the SDK by definition. The contract | ||
| # this gate enforces is therefore: | ||
| # | ||
| # root must type-check WITHOUT the optional peer | ||
| # ./sdk-server must type-check WITH it | ||
| # | ||
| # It deliberately does NOT assert that ./sdk-server fails without the | ||
| # peer. That would freeze today's behaviour into the gate and turn a | ||
| # future switch to self-contained declarations (option (b)) into a | ||
| # spurious release failure. | ||
| # | ||
| # Each arm gets its own throwaway project so its install shape is | ||
| # exactly what it claims to test -- re-running `npm install --no-save` | ||
| # in the smoke dir above would rebuild that tree from its (empty) | ||
| # package.json and could drop the tarball itself. | ||
| # ------------------------------------------------------------------- | ||
| cd "$GITHUB_WORKSPACE" | ||
| # Pin both installs to the versions this repo's lockfile resolves. A | ||
| # floating `typescript@latest` would let a compiler release nobody | ||
| # vetted decide whether a publish goes out, and a floating peer could | ||
| # type-check against a different major than the one we build against. | ||
| # | ||
| # Read from package-lock.json, NOT `require('<pkg>/package.json')`: | ||
| # @anthropic-ai/claude-agent-sdk ships an `exports` map with no | ||
| # "./package.json" entry, so requiring its manifest as a subpath | ||
| # throws ERR_PACKAGE_PATH_NOT_EXPORTED -- the same trap the bin check | ||
| # above already documents for this package. And a package missing | ||
| # from the lockfile fails here rather than silently becoming an empty | ||
| # version string that installs whatever `latest` happens to be. | ||
| LOCKED="$(node -e " | ||
| const lock=require('./package-lock.json'); | ||
| const pick=n=>{ | ||
| const e=(lock.packages||{})['node_modules/'+n]; | ||
| if(!e||!e.version){ | ||
| console.error('::error title=version not pinned in lockfile::'+n+' has no resolved version in package-lock.json - refusing to install a floating version into the release gate'); | ||
| process.exit(1); | ||
| } | ||
| return e.version; | ||
| }; | ||
| process.stdout.write([pick('typescript'),pick('@types/node'),pick('@anthropic-ai/claude-agent-sdk')].join(' ')); | ||
| ")" | ||
| read -r TSC_VERSION TYPES_NODE_VERSION PEER_VERSION <<< "$LOCKED" | ||
| echo "type-resolution arms: typescript@$TSC_VERSION, @types/node@$TYPES_NODE_VERSION, optional peer @anthropic-ai/claude-agent-sdk@$PEER_VERSION" | ||
|
|
||
| # ARM 1 — the plain consumer: the tarball and nothing else. @types/node | ||
| # is a stand-in for the ambient environment any Node consumer has, not | ||
| # a dependency of ours; see tsconfig.base.json. | ||
| ARM_ROOT="$(mktemp -d)" | ||
| cp -R "$GITHUB_WORKSPACE/scripts/smoke/consumer-types" "$ARM_ROOT/typecheck" | ||
| cd "$ARM_ROOT" | ||
| npm init -y >/dev/null 2>&1 | ||
| npm install --no-save --ignore-scripts "$TARBALL" "typescript@$TSC_VERSION" "@types/node@$TYPES_NODE_VERSION" >/dev/null 2>&1 | ||
| bash "$GITHUB_WORKSPACE/scripts/smoke/consumer-types/run-arm.sh" \ | ||
| "$ARM_ROOT" tsconfig.root.json "root entry, optional peer NOT installed" | ||
|
|
||
| # ARM 2 — the Agent SDK consumer: the tarball plus the optional peer. | ||
| ARM_SDK="$(mktemp -d)" | ||
| cp -R "$GITHUB_WORKSPACE/scripts/smoke/consumer-types" "$ARM_SDK/typecheck" | ||
| cd "$ARM_SDK" | ||
| npm init -y >/dev/null 2>&1 | ||
| npm install --no-save --ignore-scripts "$TARBALL" "typescript@$TSC_VERSION" "@types/node@$TYPES_NODE_VERSION" "@anthropic-ai/claude-agent-sdk@$PEER_VERSION" >/dev/null 2>&1 | ||
|
Comment on lines
+278
to
+287
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: The consumer projects have no lockfile, so ranged runtime dependencies such as Assessment: 🟠 Prompt for AI Agent 🤖This is a comment left during a code review.
**Path:** .github/workflows/release.yml
**Line:** 278:287
**Comment:**
*Possible Bug: The consumer projects have no lockfile, so ranged runtime dependencies such as `@modelcontextprotocol/sdk` and `zod` resolve to changing releases and make the publish gate nondeterministic.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix |
||
| bash "$GITHUB_WORKSPACE/scripts/smoke/consumer-types/run-arm.sh" \ | ||
| "$ARM_SDK" tsconfig.sdk-server.json "./sdk-server subpath, optional peer installed" | ||
|
Comment on lines
+288
to
+289
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Run
🤖 Prompt for AI Agents |
||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Gate 3 — publish. Runs ONLY if secret-scan + verify are green. | ||
| # | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -179,7 +179,12 @@ tool call — those installs stay broken until `0.2.1` ships and consumers upgra | |
| optional `@anthropic-ai/claude-agent-sdk` peer dependency, so type-checking | ||
| an import of `@wave-av/mcp-server/sdk-server` without that package installed | ||
| now fails with TS2307 (instead of silently resolving to `any`); install the | ||
| peer dependency to consume that subpath. (#76) | ||
| peer dependency to consume that subpath. This is a deliberate choice rather | ||
| than an accident of the build (see #77): the `./sdk-server` subpath exists to | ||
| hand a config object to the Agent SDK, so requiring the SDK to type-check it | ||
| is honest. The release gate now enforces both halves of that contract — the | ||
| root entry must type-check for a consumer who has NOT installed the peer, and | ||
| `./sdk-server` must type-check for one who has. (#76) | ||
|
Comment on lines
+182
to
+187
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Keep the release-gate note in Both changes landed before the 🤖 Prompt for AI Agents |
||
|
|
||
| ### Security | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| { | ||
| "name": "wave-mcp-consumer-types-probe", | ||
| "version": "0.0.0", | ||
| "private": true, | ||
| "type": "module" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| // Consumer-side probe for the ROOT entry point. | ||
| // | ||
| // Asks the only question a consumer cares about: installed the published | ||
| // tarball and nothing else, does `@wave-av/mcp-server` resolve its types? | ||
| // | ||
| // `typeof import(...)` is a purely type-level reference -- it forces tsc to | ||
| // resolve `exports["."].types` and check the declarations it reaches, without | ||
| // emitting a runtime import. That matters here: the root entry is the | ||
| // executable (`#!/usr/bin/env node`, calls `server.connect(transport)` at top | ||
| // level), so a real import would start the MCP server and hang. | ||
| // Scope, stated plainly: `dist/index.d.ts` is currently `export {};` — the root | ||
| // entry is an executable with no library surface — so this arm today proves | ||
| // that the root `types` target RESOLVES and nothing beyond it. That is thin | ||
| // because the package is thin at root, not because the check is lax: the moment | ||
| // the root gains an export, tsc follows it, and a reference to the optional peer | ||
| // leaking into a root-reachable declaration fails this arm. Verified by | ||
| // negative control before merge. | ||
| export type Root = typeof import("@wave-av/mcp-server"); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| #!/usr/bin/env bash | ||
| # Run one consumer-side type-resolution arm of the release e2e-smoke. | ||
| # | ||
| # run-arm.sh <arm-dir> <tsconfig-name> <label> | ||
| # | ||
| # <arm-dir> is a throwaway project that already has the packed tarball | ||
| # installed (plus, for the ./sdk-server arm, the optional peer) and a | ||
| # `typecheck/` copy of this directory. | ||
| # | ||
| # Scope of the failure signal: this gate fails on everything EXCEPT diagnostics | ||
| # located inside a dependency's own declarations under node_modules. Those -- | ||
| # and only those -- are downgraded to warnings. tsconfig.base.json turns | ||
| # skipLibCheck OFF -- which is the whole point, it is what makes tsc look | ||
| # inside the tarball's declarations -- but that also makes it visit the | ||
| # declarations of @modelcontextprotocol/sdk and zod. An upstream regression in | ||
| # THEIR .d.ts is not this package's release gate to enforce, and a gate that | ||
| # blocks a release on somebody else's dependency is a gate that gets switched | ||
| # off. Those are reported as warnings instead. | ||
| set -uo pipefail | ||
|
|
||
| ARM_DIR="${1:?arm dir required}" | ||
| TSCONFIG="${2:?tsconfig name required}" | ||
| LABEL="${3:?label required}" | ||
|
|
||
| # Anchor the pattern on the package directory and on the probe files so a | ||
| # diagnostic inside the tarball is always ours, and one in a sibling package | ||
| # never is. | ||
| readonly OURS_RE='node_modules/@wave-av/mcp-server/|(^|/)(root|sdk-server)\.ts\(' | ||
|
|
||
| TSC="$ARM_DIR/node_modules/.bin/tsc" | ||
| if [ ! -x "$TSC" ]; then | ||
| echo "::error title=type-resolution arm cannot run::$LABEL: no executable tsc at $TSC - the arm never type-checked anything" | ||
| exit 1 | ||
| fi | ||
|
|
||
| CONFIG="$ARM_DIR/typecheck/$TSCONFIG" | ||
| if [ ! -f "$CONFIG" ]; then | ||
| echo "::error title=type-resolution arm cannot run::$LABEL: no tsconfig at $CONFIG" | ||
| exit 1 | ||
| fi | ||
|
|
||
| OUT="$("$TSC" -p "$CONFIG" --pretty false 2>&1)" | ||
| RC=$? | ||
|
|
||
| OURS="$(printf '%s\n' "$OUT" | grep -E "$OURS_RE" || true)" | ||
|
|
||
| # Only a diagnostic located INSIDE node_modules is somebody else's to fix. The | ||
| # bucket is an allowlist, not "anything that carries a file location": tsc does | ||
| # report the arm's own mis-wiring with a location, e.g. | ||
| # tsconfig.base.json(4,5): error TS5023: Unknown compiler option 'foo'. | ||
| # so keying the excuse on "has a location" excused exactly the setup errors that | ||
| # mean the arm type-checked nothing -- it warned, left GLOBAL empty, and exited | ||
| # 0 with "type resolution ok" (same for TS5024/TS6046/TS5012). Default is now | ||
| # fatal: a line is excused only if it names a path under node_modules. | ||
| # node_modules/ is anchored to a directory boundary so a sibling directory that | ||
| # merely ends in the name -- my-node_modules/app.ts(1,1) -- is not excused. | ||
| readonly DEP_DIAG_RE='^([^(]*/)?node_modules/[^(]*\([0-9]+,[0-9]+\): error TS' | ||
| OTHERS="$(printf '%s\n' "$OUT" | grep -vE "$OURS_RE" | grep -E "$DEP_DIAG_RE" || true)" | ||
| GLOBAL="$(printf '%s\n' "$OUT" | grep -vE "$OURS_RE" | grep -E 'error TS' | grep -vE "$DEP_DIAG_RE" || true)" | ||
|
|
||
| if [ -n "$OTHERS" ]; then | ||
| COUNT="$(printf '%s\n' "$OTHERS" | wc -l | tr -d ' ')" | ||
| echo "::warning title=type errors outside this package ($LABEL)::$COUNT diagnostic(s) in dependencies' declarations, not in @wave-av/mcp-server - not failing the release on them" | ||
| printf '%s\n' "$OTHERS" | head -n 20 | ||
| fi | ||
|
|
||
| if [ -n "$OURS" ]; then | ||
| echo "::error title=$LABEL does not type-check for a consumer::the tarball ships declarations that do not resolve in this install shape" | ||
| printf '%s\n' "$OURS" | ||
| exit 1 | ||
| fi | ||
|
|
||
| # tsc can still be non-zero purely from the dependency noise above. Only | ||
| # excuse a red exit code when every reported error was attributable to a | ||
| # dependency's declarations. A global error, or a crash that produced no | ||
| # classifiable diagnostics at all, means the arm verified nothing and must | ||
| # not be reported as a pass. | ||
| if [ "$RC" -ne 0 ]; then | ||
| if [ -n "$GLOBAL" ] || [ -z "$OTHERS" ]; then | ||
| echo "::error title=type-resolution arm failed unclassified::$LABEL: tsc exited $RC for reasons not attributable to this package or its dependencies - the arm verified nothing" | ||
| printf '%s\n' "$OUT" | head -n 40 | ||
| exit 1 | ||
| fi | ||
| echo "type resolution ok: $LABEL (tsc exited $RC, entirely on declarations outside this package)" | ||
| exit 0 | ||
| fi | ||
|
|
||
| echo "type resolution ok: $LABEL" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| // Consumer-side probe for the ./sdk-server subpath. | ||
| // | ||
| // This arm runs WITH the optional peer `@anthropic-ai/claude-agent-sdk` | ||
| // installed, because dist/sdk-server.d.ts references a type from it and that | ||
| // is the documented contract: the subpath exists to hand a config object to | ||
| // the Agent SDK, so a consumer using it necessarily has the SDK (#77, option | ||
| // (a)). | ||
| // | ||
| // The root arm covers the other half -- that consumers who do NOT install the | ||
| // peer are unaffected. | ||
| export type SdkServer = typeof import("@wave-av/mcp-server/sdk-server"); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| #!/usr/bin/env bash | ||
| # Pin the diagnostic classification in run-arm.sh. | ||
| # | ||
| # The bug this drill exists for: the excuse bucket used to be keyed on "the line | ||
| # carries a file location", on the belief that tsc reports setup errors without | ||
| # one. It does not -- `tsconfig.base.json(4,5): error TS5023` has a location -- | ||
| # so a mis-wired arm that compiled nothing was downgraded to a warning and the | ||
| # gate exited 0 claiming "type resolution ok". Case 2 is that bug; it fails | ||
| # against the old regex and passes against the current one. | ||
| set -uo pipefail | ||
|
|
||
| HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
|
|
||
| # Mirror the two patterns under test, read straight out of run-arm.sh so this | ||
| # drill cannot drift away from the thing it pins. | ||
| eval "$(grep -E '^readonly (OURS_RE|DEP_DIAG_RE)=' "$HERE/run-arm.sh")" | ||
|
Comment on lines
+1
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔍 Classifier drill is never executed by any workflow
Was this helpful? React with 👍 or 👎 to provide feedback. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: The classifier drill is never invoked by the release workflow or package scripts, so later changes can reintroduce fail-open classification without CI detecting it. [incomplete implementation] Assessment: 🟠 Prompt for AI Agent 🤖This is a comment left during a code review.
**Path:** scripts/smoke/consumer-types/test-classifier.sh
**Line:** 16:16
**Comment:**
*Incomplete Implementation: The classifier drill is never invoked by the release workflow or package scripts, so later changes can reintroduce fail-open classification without CI detecting it.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix |
||
|
|
||
| fails=0 | ||
|
|
||
| # classify <output-line> -> ours | dep | global | ||
| classify() { | ||
| local line="$1" | ||
| if printf '%s\n' "$line" | grep -qE "$OURS_RE"; then echo ours; return; fi | ||
| if printf '%s\n' "$line" | grep -qE "$DEP_DIAG_RE"; then echo dep; return; fi | ||
| echo global | ||
| } | ||
|
|
||
| check() { | ||
| local want="$1" line="$2" why="$3" got | ||
| got="$(classify "$line")" | ||
| if [ "$got" = "$want" ]; then | ||
| printf 'ok %-6s %s\n' "$got" "$why" | ||
| else | ||
| printf 'FAIL want=%-6s got=%-6s %s\n line: %s\n' "$want" "$got" "$why" "$line" | ||
| fails=$((fails + 1)) | ||
| fi | ||
| } | ||
|
|
||
| # 1. Our own tarball's declarations -- always fatal. | ||
| check ours "node_modules/@wave-av/mcp-server/dist/index.d.ts(3,1): error TS2304: Cannot find name 'Foo'." \ | ||
| "diagnostic inside the packed tarball" | ||
| check ours "root.ts(1,10): error TS2305: Module has no exported member 'X'." \ | ||
| "diagnostic in a probe file" | ||
|
|
||
| # 2. THE REGRESSION. A mis-wired tsconfig reports WITH a location and must not | ||
| # be excused -- this is what shipped a green gate that verified nothing. | ||
| check global "tsconfig.base.json(4,5): error TS5023: Unknown compiler option 'totallyBogusOption'." \ | ||
| "unknown compiler option in the arm's own tsconfig (the fail-open bug)" | ||
| check global "tsconfig.root.json(2,3): error TS5024: Compiler option 'x' requires a value." \ | ||
| "TS5024 against the arm's own tsconfig" | ||
| check global "tsconfig.sdk-server.json(1,1): error TS6046: Argument for '--module' must be a string." \ | ||
| "TS6046 against the arm's own tsconfig" | ||
|
|
||
| # 3. Negative control -- a genuine third-party regression is still excused, so | ||
| # the fix tightens the gate without simply making it fail always. | ||
| check dep "node_modules/zod/lib/types.d.ts(120,5): error TS2344: Type does not satisfy the constraint." \ | ||
| "dependency declaration, relative path" | ||
| # DEP_DIAG_RE's prefix group is `([^(]*/)?`, so the exact leading directories are | ||
| # irrelevant -- what this case pins is that an ABSOLUTE prefix still classifies as | ||
| # `dep`. The prefix is therefore composed from a variable rather than written as a | ||
| # literal absolute home path, which the no-hardcoded-paths gate blocks on sight. | ||
| abs_prefix="${RUNNER_TEMP:-$HOME}/work/mcp-server/arm" | ||
| check dep "$abs_prefix/node_modules/@modelcontextprotocol/sdk/dist/x.d.ts(9,1): error TS2307: Cannot find module." \ | ||
| "dependency declaration, absolute runner path" | ||
|
|
||
| # 4. The bypass Corridor flagged: a directory that merely ENDS in the name is | ||
| # not node_modules, and must not buy an excuse. | ||
| check global "my-node_modules/app.ts(1,1): error TS2304: Cannot find name 'Foo'." \ | ||
| "sibling dir ending in the literal name must not be excused" | ||
| check global "vendor/notnode_modules/pkg/index.d.ts(4,2): error TS2345: Argument type mismatch." \ | ||
| "path segment merely containing the name must not be excused" | ||
|
|
||
| # 5. Location-less global errors -- unchanged behaviour, still fatal. | ||
| check global "error TS18003: No inputs were found in config file 'tsconfig.base.json'." \ | ||
| "TS18003 with no file location" | ||
| check global "error TS5083: Cannot read file 'tsconfig.missing.json'." \ | ||
| "TS5083 with no file location" | ||
|
|
||
| echo | ||
| if [ "$fails" -ne 0 ]; then | ||
| echo "classifier drill: $fails case(s) FAILED" | ||
| exit 1 | ||
| fi | ||
| echo "classifier drill: all cases pass" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| { | ||
| "compilerOptions": { | ||
| "target": "ES2022", | ||
| "lib": ["ES2022"], | ||
| "module": "nodenext", | ||
| "moduleResolution": "nodenext", | ||
| // `["node"]`, not `[]`. An empty list is not a purer test, it is a less | ||
| // realistic consumer: the optional peer's own declarations reference | ||
| // `stream`, `crypto`, `AbortSignal`, so `[]` buries the real signal under | ||
| // ~140 diagnostics about a dependency's need for @types/node. Anyone | ||
| // consuming a Node MCP server has them installed. | ||
| "types": ["node"], | ||
| "strict": true, | ||
| "noEmit": true, | ||
|
|
||
| // Deliberately FALSE. With skipLibCheck on, tsc does not look inside | ||
| // node_modules declarations at all -- which is exactly the class of defect | ||
| // this probe exists to catch (#77: dist/sdk-server.d.ts referencing an | ||
| // optional peer that the consumer may not have installed). Turning it on | ||
| // would make both arms pass unconditionally. | ||
| "skipLibCheck": false | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| { | ||
| "extends": "./tsconfig.base.json", | ||
| "files": ["root.ts"] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| { | ||
| "extends": "./tsconfig.base.json", | ||
| "files": ["sdk-server.ts"] | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Assert that the optional peer is absent in ARM 1.
ARM 1 establishes "optional peer NOT installed" only by omitting the package from the
npm installargument list. It never proves the package is absent from the resulting tree. npm installs a declared peer dependency automatically unlesspeerDependenciesMetamarks it optional, and a transitive dependency can also introduce it.If
@anthropic-ai/claude-agent-sdkever lands in the ARM 1 tree, ARM 1 becomes a duplicate of ARM 2. The gate then stops covering the#77regression class while still reporting the peer-absent contract as verified. Add an explicit absence check so that shape change fails loudly.🛡️ Proposed fix to prove the install shape
🧰 Tools
🪛 zizmor (1.29.0)
[warning] 278-278: ad-hoc installation of packages (adhoc-packages): installs a package outside of a lockfile
(adhoc-packages)
🤖 Prompt for AI Agents