From 52dd94457ed824de983177d6b9b96c2a79038a61 Mon Sep 17 00:00:00 2001 From: JRedeker Date: Tue, 21 Jul 2026 16:50:26 -0400 Subject: [PATCH] chore(dev-tooling): adopt canonical Bun+TS baseline Per ~/toolbox/docs/dev-tooling-baseline.md (D1-D7): - Add packageManager (bun@1.3.14) + engines.node (>=24.0.0) declarations - Migrate bun-types -> @types/bun + tsconfig types: ["bun"] - Add eslint.config.js (flat, untyped recommended + argsIgnorePattern ^_) - Add .prettierrc (defaults) + .prettierignore (.adv/, .opencode/, bun.lock, docs/) - Add canonical scripts: lint, lint:fix, format, format:check, check - Update ci script to call bun run check - Fix 6 pre-existing lint errors (unused imports, constant-condition, unnecessary escape) - Reformat existing source files with prettier defaults (baseline adoption) Verified: bun run check passes (typecheck + lint + format:check + test 137/137) Refs: optimizeBunTsDevToolingV4 --- .prettierignore | 11 ++ .prettierrc | 1 + README.md | 60 +++++----- bun.lock | 215 +++++++++++++++++++++++++++++++++++- eslint.config.js | 21 ++++ index.test.ts | 128 +++++++++++++-------- index.ts | 7 +- instructions/morph-tools.md | 18 +-- package.json | 22 +++- src/constants.ts | 3 +- src/execute.ts | 42 +++---- src/imports.ts | 69 +++++++++--- src/path-confinement.ts | 2 +- tsconfig.json | 2 +- 14 files changed, 454 insertions(+), 147 deletions(-) create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 eslint.config.js diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..a5e0081 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,11 @@ +# ADV state — not source code +.adv/ + +# OpenCode runtime artifacts +.opencode/ + +# Lockfile (machine-generated) +bun.lock + +# Generated docs (not source) +docs/ diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/.prettierrc @@ -0,0 +1 @@ +{} diff --git a/README.md b/README.md index 79a5fd1..44314c4 100644 --- a/README.md +++ b/README.md @@ -28,12 +28,8 @@ Add to your global config (`~/.config/opencode/opencode.json`): ```json { - "instructions": [ - "~/.config/opencode/instructions/morph-tools.md" - ], - "plugin": [ - "github:JRedeker/opencode-morph-fast-apply" - ] + "instructions": ["~/.config/opencode/instructions/morph-tools.md"], + "plugin": ["github:JRedeker/opencode-morph-fast-apply"] } ``` @@ -41,12 +37,8 @@ Or pin to a specific version: ```json { - "instructions": [ - "~/.config/opencode/instructions/morph-tools.md" - ], - "plugin": [ - "github:JRedeker/opencode-morph-fast-apply#v1.10.2" - ] + "instructions": ["~/.config/opencode/instructions/morph-tools.md"], + "plugin": ["github:JRedeker/opencode-morph-fast-apply#v1.10.2"] } ``` @@ -65,9 +57,7 @@ instead: "instructions": [ "~/.config/opencode/node_modules/opencode-morph-fast-apply/instructions/morph-tools.md" ], - "plugin": [ - "github:JRedeker/opencode-morph-fast-apply" - ] + "plugin": ["github:JRedeker/opencode-morph-fast-apply"] } ``` @@ -117,36 +107,36 @@ function validateToken(token) { ### When to use `morph_edit` vs `edit` -| Situation | Tool | Reason | -|-----------|------|--------| -| Small, exact replacement | `edit` | Fast, no API call | -| Large file (500+ lines) | `morph_edit` | Handles partial snippets | -| Multiple scattered changes | `morph_edit` | Batch efficiently | -| Whitespace-sensitive | `morph_edit` | Forgiving with formatting | +| Situation | Tool | Reason | +| -------------------------- | ------------ | ------------------------- | +| Small, exact replacement | `edit` | Fast, no API call | +| Large file (500+ lines) | `morph_edit` | Handles partial snippets | +| Multiple scattered changes | `morph_edit` | Batch efficiently | +| Whitespace-sensitive | `morph_edit` | Forgiving with formatting | ## Configuration -| Variable | Default | Description | -|----------|---------|-------------| -| `MORPH_API_KEY` | (required) | Your Morph API key | -| `MORPH_API_URL` | `https://api.morphllm.com` | API endpoint | -| `MORPH_MODEL` | `morph-v3-fast` | Model to use (see below) | -| `MORPH_TIMEOUT` | `30000` | Request timeout in ms | +| Variable | Default | Description | +| --------------- | -------------------------- | ------------------------ | +| `MORPH_API_KEY` | (required) | Your Morph API key | +| `MORPH_API_URL` | `https://api.morphllm.com` | API endpoint | +| `MORPH_MODEL` | `morph-v3-fast` | Model to use (see below) | +| `MORPH_TIMEOUT` | `30000` | Request timeout in ms | ### Available Models -| Model | Speed | Accuracy | Best For | -|-------|-------|----------|----------| -| `morph-v3-fast` | 10,500+ tok/sec | 96% | Real-time applications, quick edits | -| `morph-v3-large` | 5,000+ tok/sec | 98% | Complex changes, highest accuracy | -| `auto` | 5,000-10,500 tok/sec | ~98% | Recommended - automatically selects optimal model | +| Model | Speed | Accuracy | Best For | +| ---------------- | -------------------- | -------- | ------------------------------------------------- | +| `morph-v3-fast` | 10,500+ tok/sec | 96% | Real-time applications, quick edits | +| `morph-v3-large` | 5,000+ tok/sec | 98% | Complex changes, highest accuracy | +| `auto` | 5,000-10,500 tok/sec | ~98% | Recommended - automatically selects optimal model | ## How It Works 1. Reads the original file content 2. Sends to Morph API: - `` - Your intent description - - `` - The complete original file + - `` - The complete original file - `` - Your partial edit with markers 3. Morph intelligently merges the lazy edit markers with original code 4. Writes the merged result back to the file @@ -158,6 +148,7 @@ function validateToken(token) { Ensure the environment variable is set and exported in the shell or service that starts OpenCode: + ```bash echo $MORPH_API_KEY # Should show your key ``` @@ -168,6 +159,7 @@ updated OpenCode process environment. ### Timeout errors Increase the timeout for large files: + ```bash export MORPH_TIMEOUT=60000 # 60 seconds ``` @@ -190,7 +182,7 @@ In all guard cases, `morph_edit` returns a detailed error with recovery options ### Markdown-fenced `code_edit` input -If an AI agent wraps `code_edit` in markdown fences (for example, ```` ```typescript ... ``` ````), `morph_edit` strips the outer fence before sending content to Morph. +If an AI agent wraps `code_edit` in markdown fences (for example, ` ```typescript ... ``` `), `morph_edit` strips the outer fence before sending content to Morph. ### Wrong edit location diff --git a/bun.lock b/bun.lock index 3c81457..14f3bd0 100644 --- a/bun.lock +++ b/bun.lock @@ -9,13 +9,46 @@ "diff": "^8.0.2", }, "devDependencies": { + "@eslint/js": "^9.0.0", + "@types/bun": "latest", "@types/diff": "^7.0.2", - "bun-types": "latest", + "eslint": "^9.0.0", + "eslint-config-prettier": "^10.0.0", + "prettier": "^3.0.0", "typescript": "^5.0.0", + "typescript-eslint": "^8.0.0", }, }, }, "packages": { + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], + + "@eslint/config-array": ["@eslint/config-array@0.21.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="], + + "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], + + "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], + + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.6", "", { "dependencies": { "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.3.0", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" } }, "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA=="], + + "@eslint/js": ["@eslint/js@9.39.5", "", {}, "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A=="], + + "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], + + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], + + "@humanfs/core": ["@humanfs/core@0.19.2", "", { "dependencies": { "@humanfs/types": "^0.15.0" } }, "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA=="], + + "@humanfs/node": ["@humanfs/node@0.16.8", "", { "dependencies": { "@humanfs/core": "^0.19.2", "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ=="], + + "@humanfs/types": ["@humanfs/types@0.15.0", "", {}, "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], + "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ=="], "@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w=="], @@ -34,58 +67,238 @@ "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], + "@types/diff": ["@types/diff@7.0.2", "", {}, "sha512-JSWRMozjFKsGlEjiiKajUjIJVKuKdE3oVy2DNtK+fUo8q82nhFZ2CPQwicAIkXrofahDXrWJ7mjelvZphMS98Q=="], + "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], + + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + "@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.65.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.65.0", "@typescript-eslint/type-utils": "8.65.0", "@typescript-eslint/utils": "8.65.0", "@typescript-eslint/visitor-keys": "8.65.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.65.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA=="], + + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.65.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.65.0", "@typescript-eslint/types": "8.65.0", "@typescript-eslint/typescript-estree": "8.65.0", "@typescript-eslint/visitor-keys": "8.65.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA=="], + + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.65.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.65.0", "@typescript-eslint/types": "^8.65.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q=="], + + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.65.0", "", { "dependencies": { "@typescript-eslint/types": "8.65.0", "@typescript-eslint/visitor-keys": "8.65.0" } }, "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg=="], + + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.65.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg=="], + + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.65.0", "", { "dependencies": { "@typescript-eslint/types": "8.65.0", "@typescript-eslint/typescript-estree": "8.65.0", "@typescript-eslint/utils": "8.65.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g=="], + + "@typescript-eslint/types": ["@typescript-eslint/types@8.65.0", "", {}, "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg=="], + + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.65.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.65.0", "@typescript-eslint/tsconfig-utils": "8.65.0", "@typescript-eslint/types": "8.65.0", "@typescript-eslint/visitor-keys": "8.65.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg=="], + + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.65.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.65.0", "@typescript-eslint/types": "8.65.0", "@typescript-eslint/typescript-estree": "8.65.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA=="], + + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.65.0", "", { "dependencies": { "@typescript-eslint/types": "8.65.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A=="], + + "acorn": ["acorn@8.17.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + + "ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "brace-expansion": ["brace-expansion@1.1.16", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw=="], + "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="], "effect": ["effect@4.0.0-beta.74", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.8.0", "find-my-way-ts": "^0.1.6", "ini": "^7.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^2.0.1", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^14.0.0", "yaml": "^2.9.0" } }, "sha512-Yx+Kh12U+i2FmjwEfKs+ePFmpMd43RPD1oGqc/VraSS9bYzvF0Ff3PojwEFEVEewp8xc92Uxu28gTspU4qyvHA=="], + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "eslint": ["eslint@9.39.5", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.6", "@eslint/js": "9.39.5", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw=="], + + "eslint-config-prettier": ["eslint-config-prettier@10.1.8", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w=="], + + "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], + + "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], + + "esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + "fast-check": ["fast-check@4.8.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-GOJ158CUMnN6cSahsv4+ExARvIDuzzinFjkp0E9WtiBa5zcVeLozVkWaE4IzFcc+Y48Wp1EDlUZsXRyAztQcSg=="], + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + "find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="], + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], + + "flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="], + + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + "ini": ["ini@7.0.0", "", {}, "sha512-ifK0CgjALofS5bkrcTy4RaQ9Vx2Knf/eLeIO+NaswQEpH1UblrtTSCIvN71qQDMq0PeQ/SSPojvEJp9vvvfr+w=="], + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "js-yaml": ["js-yaml@4.3.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q=="], + + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + "kubernetes-types": ["kubernetes-types@1.30.0", "", {}, "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q=="], + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + + "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "msgpackr": ["msgpackr@2.0.2", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.4" } }, "sha512-c5hYOXFbP79Slh6Dzd2wzk+jnV7mX1UxfMYtilnY1NmalXPqG8DGb5cYCMBrW4AsH3zekBBZd4QrKz9NhtvYLQ=="], "msgpackr-extract": ["msgpackr-extract@3.0.4", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw=="], "multipasta": ["multipasta@0.2.7", "", {}, "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA=="], + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="], + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + "picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="], + + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + + "prettier": ["prettier@3.9.6", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + "pure-rand": ["pure-rand@8.4.0", "", {}, "sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A=="], + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], + "toml": ["toml@4.1.1", "", {}, "sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw=="], + "ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="], + + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "typescript-eslint": ["typescript-eslint@8.65.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.65.0", "@typescript-eslint/parser": "8.65.0", "@typescript-eslint/typescript-estree": "8.65.0", "@typescript-eslint/utils": "8.65.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA=="], + "undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + "uuid": ["uuid@14.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg=="], "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + "yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + "zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="], + + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.6", "", {}, "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw=="], + + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], + + "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.7", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA=="], + + "@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], } } diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..707c685 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,21 @@ +// ESLint flat config — see ~/toolbox/docs/dev-tooling-baseline.md § D6 +import js from "@eslint/js"; +import tseslint from "typescript-eslint"; +import prettierConfig from "eslint-config-prettier"; + +export default tseslint.config( + js.configs.recommended, + ...tseslint.configs.recommended, + { + rules: { + "@typescript-eslint/no-unused-vars": [ + "error", + { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }, + ], + }, + }, + prettierConfig, + { + ignores: ["dist/**", "node_modules/**", "instructions/**"], + }, +); diff --git a/index.test.ts b/index.test.ts index a688e05..9aa2374 100644 --- a/index.test.ts +++ b/index.test.ts @@ -10,15 +10,10 @@ import { import { normalizeCodeEditInput } from "./src/normalize.js"; import { executeMorphEdit, - type ExecuteMorphEditArgs, type ExecuteMorphEditRuntime, } from "./src/execute.js"; import { generateUnifiedDiff, countChanges } from "./src/diff.js"; -import { - callMorphApply, - scrubSecrets, - type FailureKind, -} from "./index.js"; +import { callMorphApply, scrubSecrets } from "./index.js"; describe("EXISTING_CODE_MARKER", () => { test("is the canonical marker string", () => { @@ -49,9 +44,7 @@ describe("packaged tool-selection instructions", () => { test("README documents packaged instruction path", () => { const content = readFileSync(join(import.meta.dir, "README.md"), "utf-8"); - expect(content).toContain( - "~/.config/opencode/instructions/morph-tools.md", - ); + expect(content).toContain("~/.config/opencode/instructions/morph-tools.md"); expect(content).toContain( "~/.config/opencode/node_modules/opencode-morph-fast-apply/instructions/morph-tools.md", ); @@ -323,7 +316,6 @@ describe("marker leakage detection logic", () => { }); test("not triggered when no markers in input", () => { - const originalCode = "function foo() { return 1 }"; const mergedCode = `function foo() { return 1 }\n${EXISTING_CODE_MARKER}`; const hasMarkers = false; @@ -449,7 +441,7 @@ describe("truncation detection logic", () => { const lineLoss = (originalLineCount - mergedLineCount) / originalLineCount; // NaN > 0.6 is false, so this should NOT trigger - const triggered = true && charLoss > 0.6 && lineLoss > 0.5; + const triggered = charLoss > 0.6 && lineLoss > 0.5; expect(triggered).toBe(false); }); @@ -489,7 +481,8 @@ describe("truncation detection logic", () => { describe("extractImportedIdentifiers", () => { test("extracts Python from-import identifiers", () => { - const code = "from asyncpg import PostgresError\nfrom services.ops import DataOps"; + const code = + "from asyncpg import PostgresError\nfrom services.ops import DataOps"; const ids = extractImportedIdentifiers(code, "svc.py"); expect(ids).toContain("PostgresError"); expect(ids).toContain("DataOps"); @@ -578,7 +571,8 @@ describe("extractImportedIdentifiers", () => { }); test("extracts Rust use statement", () => { - const code = "use std::collections::HashMap;\nuse tokio::io::{AsyncRead, AsyncWrite};"; + const code = + "use std::collections::HashMap;\nuse tokio::io::{AsyncRead, AsyncWrite};"; const ids = extractImportedIdentifiers(code, "main.rs"); expect(ids).toContain("HashMap"); expect(ids).toContain("AsyncRead"); @@ -586,7 +580,8 @@ describe("extractImportedIdentifiers", () => { }); test("extracts Java import", () => { - const code = "import java.util.ArrayList;\nimport static org.junit.Assert.*;"; + const code = + "import java.util.ArrayList;\nimport static org.junit.Assert.*;"; const ids = extractImportedIdentifiers(code, "App.java"); expect(ids).toContain("ArrayList"); }); @@ -611,13 +606,15 @@ describe("extractImportedIdentifiers", () => { }); test("deduplicates identifiers", () => { - const code = 'import { Router } from "express";\nimport { Router } from "express";'; + const code = + 'import { Router } from "express";\nimport { Router } from "express";'; const ids = extractImportedIdentifiers(code, "app.ts"); expect(ids.filter((id) => id === "Router")).toHaveLength(1); }); test("skips comment lines", () => { - const code = '// import { Fake } from "nowhere";\nimport { Real } from "somewhere";'; + const code = + '// import { Fake } from "nowhere";\nimport { Real } from "somewhere";'; const ids = extractImportedIdentifiers(code, "app.ts"); expect(ids).toContain("Real"); expect(ids).not.toContain("Fake"); @@ -777,7 +774,7 @@ describe("extractImportEntries", () => { }); test("returns stable entries for C include", () => { - const code = '#include '; + const code = "#include "; const entries = extractImportEntries(code, "main.c"); expect(entries).toHaveLength(1); expect(entries[0]).toMatchObject({ @@ -799,7 +796,8 @@ describe("extractImportEntries", () => { }); test("deduplicates identical entries", () => { - const code = 'import { Router } from "express";\nimport { Router } from "express";'; + const code = + 'import { Router } from "express";\nimport { Router } from "express";'; const entries = extractImportEntries(code, "app.ts"); expect(entries).toHaveLength(1); }); @@ -816,10 +814,7 @@ describe("findDroppedIdentifiers", () => { ].join("\n"); // Simulate Morph dropping imports — identifiers not present anywhere - const merged = [ - "async def main():", - " pass", - ].join("\n"); + const merged = ["async def main():", " pass"].join("\n"); const dropped = findDroppedIdentifiers(original, merged, "svc.py"); expect(dropped).toContain("PostgresError"); @@ -854,7 +849,8 @@ describe("findDroppedIdentifiers", () => { }); test("flags identifier dropped from import and not used elsewhere", () => { - const original = 'import { Router, Request, Response } from "express";\nconst app = Router();'; + const original = + 'import { Router, Request, Response } from "express";\nconst app = Router();'; const merged = 'import { Router } from "express";\nconst app = Router();'; // Request and Response are in original imports but not in merged imports const dropped = findDroppedIdentifiers(original, merged, "app.ts"); @@ -879,11 +875,17 @@ describe("findDroppedIdentifiers", () => { " ResultMapper,", ")", ].join("\n"); - const originalBody = Array.from({ length: 1360 }, (_, i) => `# line ${i + 30}`).join("\n"); + const originalBody = Array.from( + { length: 1360 }, + (_, i) => `# line ${i + 30}`, + ).join("\n"); const original = originalImports + "\n" + originalBody; // Morph drops the import block silently - const merged = Array.from({ length: 1360 }, (_, i) => `# line ${i + 30}`).join("\n"); + const merged = Array.from( + { length: 1360 }, + (_, i) => `# line ${i + 30}`, + ).join("\n"); const dropped = findDroppedIdentifiers(original, merged, "service.py"); expect(dropped.length).toBeGreaterThan(0); @@ -897,7 +899,8 @@ describe("findDroppedIdentifiers", () => { // If an identifier is still imported, just in a different declaration form, // it should NOT be flagged. const original = 'import { Router } from "express";'; - const merged = 'import express from "express";\nconst { Router } = express;'; + const merged = + 'import express from "express";\nconst { Router } = express;'; // Note: Router is no longer in an import declaration, so this WILL be flagged // with declaration-level comparison. This is correct behavior. const dropped = findDroppedIdentifiers(original, merged, "app.ts"); @@ -913,13 +916,14 @@ describe("findDroppedIdentifiers", () => { test("declaration-level comparison: flags dropped alias binding", () => { const original = 'import { createRouter as cr } from "express";'; - const merged = '// import dropped'; + const merged = "// import dropped"; const dropped = findDroppedIdentifiers(original, merged, "app.ts"); expect(dropped).toContain("cr"); }); test("declaration-level comparison: flags dropped require destructure alias binding", () => { - const original = 'const { readFile: rf } = require("node:fs");\nrf("file");'; + const original = + 'const { readFile: rf } = require("node:fs");\nrf("file");'; const merged = '// require dropped\nrf("file");'; const dropped = findDroppedIdentifiers(original, merged, "app.js"); expect(dropped).toContain("rf"); @@ -928,7 +932,13 @@ describe("findDroppedIdentifiers", () => { import { resolveTargetPath } from "./src/path-confinement.js"; import { tmpdir } from "node:os"; -import { mkdtempSync, writeFileSync, symlinkSync, mkdirSync, rmSync } from "node:fs"; +import { + mkdtempSync, + writeFileSync, + symlinkSync, + mkdirSync, + rmSync, +} from "node:fs"; describe("resolveTargetPath", () => { let tmpDir: string; @@ -1006,9 +1016,13 @@ describe("resolveTargetPath", () => { writeFileSync(join(evilDir, "secret.txt"), "secret"); // Create symlink inside root pointing outside symlinkSync(evilDir, join(root, "link-out")); - const result = resolveTargetPath(join(root, "link-out", "secret.txt"), root, { - targetExists: true, - }); + const result = resolveTargetPath( + join(root, "link-out", "secret.txt"), + root, + { + targetExists: true, + }, + ); expect("error" in result).toBe(true); if ("error" in result) { expect(result.error).toContain("outside"); @@ -1033,9 +1047,13 @@ describe("resolveTargetPath", () => { const evilDir = join(tmpDir, "evil"); mkdirSync(evilDir, { recursive: true }); symlinkSync(evilDir, join(root, "link-out")); - const result = resolveTargetPath(join(root, "link-out", "new-file.txt"), root, { - targetExists: false, - }); + const result = resolveTargetPath( + join(root, "link-out", "new-file.txt"), + root, + { + targetExists: false, + }, + ); expect("error" in result).toBe(true); if ("error" in result) { expect(result.error).toContain("outside"); @@ -1045,9 +1063,13 @@ describe("resolveTargetPath", () => { test("new target allows when nearest existing parent is real directory inside root", () => { const subDir = join(root, "sub"); mkdirSync(subDir, { recursive: true }); - const result = resolveTargetPath(join(root, "sub", "new", "file.txt"), root, { - targetExists: false, - }); + const result = resolveTargetPath( + join(root, "sub", "new", "file.txt"), + root, + { + targetExists: false, + }, + ); expect("path" in result).toBe(true); if ("path" in result) { expect(result.path).toBe(join(root, "sub", "new", "file.txt")); @@ -1090,13 +1112,13 @@ function makeMockRuntime( success: false, error: "mock-not-configured", }), - resolveTargetPath: (targetPath, root, _options?) => - ({ path: join(root, targetPath) }), + resolveTargetPath: (targetPath, root, _options?) => ({ + path: join(root, targetPath), + }), normalizeCodeEditInput: (s) => normalizeCodeEditInput(s), findDroppedIdentifiers: (orig, merged, fp) => findDroppedIdentifiers(orig, merged, fp), - generateUnifiedDiff: (fp, orig, mod) => - generateUnifiedDiff(fp, orig, mod), + generateUnifiedDiff: (fp, orig, mod) => generateUnifiedDiff(fp, orig, mod), countChanges: (diff) => countChanges(diff), constants: { MORPH_API_KEY: "fake-key", @@ -1268,7 +1290,10 @@ describe("executeMorphEdit - unsafe Morph output refusal", () => { test("rejects catastrophic truncation", async () => { // original: 20 lines, ~80 chars - const original = Array.from({ length: 20 }, (_, i) => `const x${i} = ${i};`).join("\n"); + const original = Array.from( + { length: 20 }, + (_, i) => `const x${i} = ${i};`, + ).join("\n"); // merged: 2 lines, ~10 chars (>60% char loss, >50% line loss) const merged = "const a = 1;\n"; @@ -1320,9 +1345,10 @@ describe("executeMorphEdit - unsafe Morph output refusal", () => { }); describe("executeMorphEdit - no-marker guard coverage", () => { - const tenLines = Array.from({ length: 10 }, (_, i) => `const v${i} = ${i};`).join( - "\n", - ); + const tenLines = Array.from( + { length: 10 }, + (_, i) => `const v${i} = ${i};`, + ).join("\n"); test("blocks catastrophic shrink on no-marker edit (vs code_edit baseline)", async () => { // 10-line file (≤10 so the missing-marker refusal does not trigger), @@ -1362,7 +1388,10 @@ describe("executeMorphEdit - no-marker guard coverage", () => { let wrote = false; const runtime = makeMockRuntime({ - readFile: async () => ({ exists: true, text: "const a = 1;\nconst b = 2;\n" }), + readFile: async () => ({ + exists: true, + text: "const a = 1;\nconst b = 2;\n", + }), callMorphApply: async () => ({ success: true, content: replacement }), writeFile: async () => { wrote = true; @@ -1385,7 +1414,10 @@ describe("executeMorphEdit - no-marker guard coverage", () => { test("blocks marker leakage on no-marker edit", async () => { let wrote = false; const runtime = makeMockRuntime({ - readFile: async () => ({ exists: true, text: "const a = 1;\nconst b = 2;\n" }), + readFile: async () => ({ + exists: true, + text: "const a = 1;\nconst b = 2;\n", + }), callMorphApply: async () => ({ success: true, content: `const a = 1;\n${EXISTING_CODE_MARKER}\nconst b = 2;\n`, diff --git a/index.ts b/index.ts index 80ab227..56e3284 100644 --- a/index.ts +++ b/index.ts @@ -57,7 +57,7 @@ export function scrubSecrets(message: unknown, apiKey?: unknown): string { } // Scrub generic Bearer tokens result = result.replace( - /Bearer\s+[A-Za-z0-9_\-\.]{10,}/gi, + /Bearer\s+[A-Za-z0-9_.-]{10,}/gi, "Bearer ***REDACTED***", ); return result; @@ -192,10 +192,7 @@ export async function callMorphApply( } return { success: false, - error: scrubSecrets( - `Morph API request failed: ${error.message}`, - apiKey, - ), + error: scrubSecrets(`Morph API request failed: ${error.message}`, apiKey), kind: "api_request_failed", }; } diff --git a/instructions/morph-tools.md b/instructions/morph-tools.md index 7bc4f38..8ff2800 100644 --- a/instructions/morph-tools.md +++ b/instructions/morph-tools.md @@ -15,15 +15,15 @@ faster or more reliable than exact-string replacement. ### First-Action Policy -| Editing task | First tool | Why | -|---|---|---| -| Large file edits (300+ lines) | `morph_edit` | Avoids fragile exact-string matching | -| Multiple scattered changes in one file | `morph_edit` | Batch edits efficiently | -| Whitespace-sensitive edits | `morph_edit` | More forgiving with formatting/context | -| Complex refactors inside an existing file | `morph_edit` | Better partial-file merge behavior | -| Small exact replacement | `edit` | Faster, local, no API call | -| Single-line rename/fix | `edit` | Simpler exact replacement | -| New file creation | `write` | `morph_edit` only edits existing files | +| Editing task | First tool | Why | +| ----------------------------------------- | ------------ | -------------------------------------- | +| Large file edits (300+ lines) | `morph_edit` | Avoids fragile exact-string matching | +| Multiple scattered changes in one file | `morph_edit` | Batch edits efficiently | +| Whitespace-sensitive edits | `morph_edit` | More forgiving with formatting/context | +| Complex refactors inside an existing file | `morph_edit` | Better partial-file merge behavior | +| Small exact replacement | `edit` | Faster, local, no API call | +| Single-line rename/fix | `edit` | Simpler exact replacement | +| New file creation | `write` | `morph_edit` only edits existing files | ### When NOT to Use morph_edit diff --git a/package.json b/package.json index df774c8..7f773cb 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,10 @@ "description": "OpenCode plugin for Morph Fast Apply - 10x faster code editing", "type": "module", "main": "index.ts", + "packageManager": "bun@1.3.14", + "engines": { + "node": ">=24.0.0" + }, "files": [ "index.ts", "src/", @@ -12,18 +16,28 @@ "instructions/" ], "scripts": { - "test": "bun test", "typecheck": "tsc --noEmit", - "ci": "bun test && tsc --noEmit" + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "format": "prettier --write .", + "format:check": "prettier --check .", + "test": "bun test", + "check": "bun run typecheck && bun run lint && bun run format:check && bun run test", + "ci": "bun run check" }, "dependencies": { "@opencode-ai/plugin": "latest", "diff": "^8.0.2" }, "devDependencies": { + "@eslint/js": "^9.0.0", + "@types/bun": "latest", "@types/diff": "^7.0.2", - "bun-types": "latest", - "typescript": "^5.0.0" + "eslint": "^9.0.0", + "eslint-config-prettier": "^10.0.0", + "prettier": "^3.0.0", + "typescript": "^5.0.0", + "typescript-eslint": "^8.0.0" }, "keywords": [ "opencode", diff --git a/src/constants.ts b/src/constants.ts index d675948..86bc6ed 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -20,6 +20,7 @@ export const PLUGIN_VERSION = "1.10.2"; // Get API key from the OpenCode/plugin process environment. export const MORPH_API_KEY = process.env.MORPH_API_KEY; -export const MORPH_API_URL = process.env.MORPH_API_URL || "https://api.morphllm.com"; +export const MORPH_API_URL = + process.env.MORPH_API_URL || "https://api.morphllm.com"; export const MORPH_MODEL = process.env.MORPH_MODEL || "morph-v3-fast"; export const MORPH_TIMEOUT = parseInt(process.env.MORPH_TIMEOUT || "30000", 10); diff --git a/src/execute.ts b/src/execute.ts index 2a332c5..ea5c4c6 100644 --- a/src/execute.ts +++ b/src/execute.ts @@ -1,6 +1,4 @@ -import type { - ResolveTargetPathResult, -} from "./path-confinement.js"; +import type { ResolveTargetPathResult } from "./path-confinement.js"; import type { FailureKind } from "../index.js"; import { scrubSecrets } from "../index.js"; @@ -22,15 +20,18 @@ export interface ExecuteMorphEditRuntime { directory?: string; }; now: () => number; - readFile: ( - filepath: string, - ) => Promise<{ exists: boolean; text: string }>; + readFile: (filepath: string) => Promise<{ exists: boolean; text: string }>; writeFile: (filepath: string, content: string) => Promise; callMorphApply: ( originalCode: string, codeEdit: string, instructions: string, - ) => Promise<{ success: boolean; content?: string; error?: string; kind?: FailureKind }>; + ) => Promise<{ + success: boolean; + content?: string; + error?: string; + kind?: FailureKind; + }>; resolveTargetPath: ( targetPath: string, root: string, @@ -69,11 +70,7 @@ export async function executeMorphEdit( args: ExecuteMorphEditArgs, runtime: ExecuteMorphEditRuntime, ): Promise { - const { - target_filepath, - instructions, - code_edit, - } = args; + const { target_filepath, instructions, code_edit } = args; const { log, @@ -101,10 +98,7 @@ export async function executeMorphEdit( const normalizedCodeEdit = normalizeCodeEditInput(code_edit); // Block usage in readonly agents (plan, explore) unless overridden - if ( - !ALLOW_READONLY_AGENTS && - READONLY_AGENTS.includes(context.agent) - ) { + if (!ALLOW_READONLY_AGENTS && READONLY_AGENTS.includes(context.agent)) { await log( "debug", `Blocked morph_edit in readonly agent: ${context.agent}`, @@ -172,7 +166,10 @@ export async function executeMorphEdit( const apiDuration = now() - startTime; if (!result.success || !result.content) { - const safeError = scrubSecrets(result.error || "unknown error", MORPH_API_KEY); + const safeError = scrubSecrets( + result.error || "unknown error", + MORPH_API_KEY, + ); return `Morph API failed: ${safeError}\n\nSuggestion: Try using the native 'edit' tool instead with exact string replacement.\nThe edit tool requires matching the exact text in the file.`; } @@ -185,10 +182,7 @@ export async function executeMorphEdit( // `!originalHadMarker` precondition avoids false positives on files that // legitimately contain the marker string. const originalHadMarker = originalCode.includes(EXISTING_CODE_MARKER); - if ( - !originalHadMarker && - mergedCode.includes(EXISTING_CODE_MARKER) - ) { + if (!originalHadMarker && mergedCode.includes(EXISTING_CODE_MARKER)) { await log( "warn", `Marker leakage detected in merged output for ${target_filepath}`, @@ -254,11 +248,7 @@ export async function executeMorphEdit( } // Generate unified diff - const diff = generateUnifiedDiff( - target_filepath, - originalCode, - mergedCode, - ); + const diff = generateUnifiedDiff(target_filepath, originalCode, mergedCode); // Calculate change stats const { added, removed } = countChanges(diff); diff --git a/src/imports.ts b/src/imports.ts index 88129a2..437a642 100644 --- a/src/imports.ts +++ b/src/imports.ts @@ -37,7 +37,10 @@ function parseTsNamedBindings(bindingList: string): string[] { return bindingList .split(",") .map((s) => { - const parts = s.trim().replace(/^type\s+/, "").split(/\s+as\s+/); + const parts = s + .trim() + .replace(/^type\s+/, "") + .split(/\s+as\s+/); return (parts.length > 1 ? parts[parts.length - 1] : parts[0])?.trim(); }) .filter((s): s is string => !!s && s.length > 0); @@ -81,9 +84,20 @@ export function extractImportEntries( let result = ""; let depth = 0; for (const ch of code) { - if (ch === "(") { depth++; result += ch; continue; } - if (ch === ")") { depth--; result += ch; continue; } - if (ch === "\n" && depth > 0) { result += " "; continue; } + if (ch === "(") { + depth++; + result += ch; + continue; + } + if (ch === ")") { + depth--; + result += ch; + continue; + } + if (ch === "\n" && depth > 0) { + result += " "; + continue; + } result += ch; } normalizedCode = result; @@ -95,7 +109,8 @@ export function extractImportEntries( // Skip empty lines and comment lines (but NOT #include / #using directives) if (!trimmed) continue; - if (trimmed.startsWith("#") && ext !== "py" && !cExts.includes(ext)) continue; + if (trimmed.startsWith("#") && ext !== "py" && !cExts.includes(ext)) + continue; // Python: from X import Y, Z | import X, Y if (ext === "py") { @@ -111,9 +126,13 @@ export function extractImportEntries( .map((s) => { const parts = s.trim().split(/\s+as\s+/); // If aliased, take the alias (last part); otherwise take the name - return (parts.length > 1 ? parts[parts.length - 1] : parts[0])?.trim(); + return ( + parts.length > 1 ? parts[parts.length - 1] : parts[0] + )?.trim(); }) - .filter((s): s is string => !!s && s.length > 0 && !s.startsWith("*")); + .filter( + (s): s is string => !!s && s.length > 0 && !s.startsWith("*"), + ); if (names.length > 0) { entries.push({ kind: "py-from", source, bindings: names }); } @@ -126,7 +145,9 @@ export function extractImportEntries( .split(",") .map((s) => { const parts = s.trim().split(/\s+as\s+/); - return (parts.length > 1 ? parts[parts.length - 1] : parts[0])?.trim(); + return ( + parts.length > 1 ? parts[parts.length - 1] : parts[0] + )?.trim(); }) .filter((s): s is string => !!s && s.length > 0); for (const name of names) { @@ -139,7 +160,9 @@ export function extractImportEntries( // TypeScript / JavaScript: import { X, Y } from ... | import X from ... if (jsExts.includes(ext)) { // import X, { Y as Z } from '...' - const combinedImport = trimmed.match(/^import\s+(\w+)\s*,\s*\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/); + const combinedImport = trimmed.match( + /^import\s+(\w+)\s*,\s*\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/, + ); if (combinedImport) { const source = combinedImport[3]; const names = parseTsNamedBindings(combinedImport[2]); @@ -155,7 +178,9 @@ export function extractImportEntries( } // import { X, Y as Z } from '...' - const namedImport = trimmed.match(/^import\s+(?:type\s+)?\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/); + const namedImport = trimmed.match( + /^import\s+(?:type\s+)?\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/, + ); if (namedImport) { const source = namedImport[2]; const names = parseTsNamedBindings(namedImport[1]); @@ -166,7 +191,9 @@ export function extractImportEntries( } // import X from '...' (default import) - const defaultImport = trimmed.match(/^import\s+(?:type\s+)?(\w+)\s+from\s+['"]([^'"]+)['"]/); + const defaultImport = trimmed.match( + /^import\s+(?:type\s+)?(\w+)\s+from\s+['"]([^'"]+)['"]/, + ); if (defaultImport) { entries.push({ kind: "ts-default", @@ -177,7 +204,9 @@ export function extractImportEntries( } // import * as X from '...' - const namespaceImport = trimmed.match(/^import\s+(?:type\s+)?\*\s+as\s+(\w+)\s+from\s+['"]([^'"]+)['"]/); + const namespaceImport = trimmed.match( + /^import\s+(?:type\s+)?\*\s+as\s+(\w+)\s+from\s+['"]([^'"]+)['"]/, + ); if (namespaceImport) { entries.push({ kind: "ts-namespace", @@ -208,7 +237,11 @@ export function extractImportEntries( const source = requireDestructure[2]; const names = parseRequireDestructureBindings(requireDestructure[1]); if (names.length > 0) { - entries.push({ kind: "ts-require-destructure", source, bindings: names }); + entries.push({ + kind: "ts-require-destructure", + source, + bindings: names, + }); } continue; } @@ -216,9 +249,7 @@ export function extractImportEntries( // Go: import "pkg" | import ( "pkg1" \n "pkg2" ) if (ext === "go") { - const singleImport = trimmed.match( - /^import\s+(?:(\w+)\s+)?"([^"]+)"/, - ); + const singleImport = trimmed.match(/^import\s+(?:(\w+)\s+)?"([^"]+)"/); if (singleImport) { const source = singleImport[2]; const alias = singleImport[1]; @@ -329,7 +360,11 @@ export function extractImportEntries( const seen = new Set(); const deduped: ImportEntry[] = []; for (const entry of entries) { - const key = JSON.stringify({ kind: entry.kind, source: entry.source, bindings: entry.bindings }); + const key = JSON.stringify({ + kind: entry.kind, + source: entry.source, + bindings: entry.bindings, + }); if (!seen.has(key)) { seen.add(key); deduped.push(entry); diff --git a/src/path-confinement.ts b/src/path-confinement.ts index 615a30d..3d90b17 100644 --- a/src/path-confinement.ts +++ b/src/path-confinement.ts @@ -1,4 +1,4 @@ -import { existsSync, realpathSync, statSync } from "node:fs"; +import { existsSync, realpathSync } from "node:fs"; import * as path from "node:path"; export interface ResolveResult { diff --git a/tsconfig.json b/tsconfig.json index 7415fac..ac6f134 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,7 @@ "declaration": true, "outDir": "./dist", "rootDir": ".", - "types": ["bun-types"] + "types": ["bun"] }, "include": ["*.ts", "src/**/*.ts"], "exclude": ["node_modules", "dist"]