-
Notifications
You must be signed in to change notification settings - Fork 1
Add Copilot package-resolution hook to the VS Code plugin #42
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
Merged
Merged
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
5d9c909
JFSK-19 - Add package resolution hook assembly
omerzi 09a3141
JFSK-19 - Document VS Code hooks setting
omerzi c677ec2
JFSK-19 - Vendor and validate VS Code hooks
omerzi 7ac4a73
JFSK-19 - Keep plugin validation focused
omerzi eafdc9b
JFSK-19 - Align package hook with Agent Guard skill
omerzi 2bd079e
JFSK-19 - Sync harness detection review fix
omerzi 7b8a514
JFSK-19 - Refresh vendored modules after upstream rebase
omerzi 36ef43f
JFSK-19 - Refresh vendored modules for harness fingerprint detection
omerzi 1509c43
JFSK-19 - Cover the routing path and restore the manual sync entry point
omerzi 95dc527
JFSK-19 - Bump plugin version above the skills sync on main
omerzi b56aa03
JFSK-19 - Rename vendored adapter to copilot-session-start.mjs
omerzi a34693c
JFSK-19 - Re-pin vendor source after upstream rebase
omerzi b3fc5e8
JFSK-19 - Re-pin vendor source to merged master SHA now that agent-ho…
omerzi a8141e8
JFSK-19 - Allow verified repository resolution time
omerzi e66cb32
JFSK-19 - Prepare VS Code plugin for hardened hook release
omerzi e8449d1
JFSK-19 - Pin VS Code plugin to agent-hooks v0.9.0
omerzi 17d08c3
JFSK-19 - Cover verified package-resolution paths
omerzi eca3db4
JFSK-19 - Keep plugin CI independent of upstream host
omerzi d357336
JFSK-19 - Cover vendored setup support modules
omerzi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| #!/usr/bin/env node | ||
|
|
||
| import { createHash } from "node:crypto"; | ||
| import { readFile, readdir, writeFile } from "node:fs/promises"; | ||
| import path from "node:path"; | ||
| import { fileURLToPath } from "node:url"; | ||
|
|
||
| const root = path.resolve( | ||
| path.dirname(fileURLToPath(import.meta.url)), | ||
| "../..", | ||
| ); | ||
| const modulesRoot = path.join(root, "plugin", "modules"); | ||
| const manifestFile = path.join( | ||
| root, | ||
| ".github", | ||
| "scripts", | ||
| "sync-modules-integrity.json", | ||
| ); | ||
|
|
||
| async function filesUnder(dir, prefix = "") { | ||
| const entries = await readdir(dir, { withFileTypes: true }); | ||
| const files = []; | ||
| for (const entry of entries) { | ||
| const relative = path.join(prefix, entry.name); | ||
| if (entry.isDirectory()) | ||
| files.push(...(await filesUnder(path.join(dir, entry.name), relative))); | ||
| else files.push(relative); | ||
| } | ||
| return files.sort(); | ||
| } | ||
|
|
||
| async function digest(relative) { | ||
| const bytes = await readFile(path.join(modulesRoot, relative)); | ||
| return createHash("sha256").update(bytes).digest("hex"); | ||
| } | ||
|
|
||
| async function snapshot(pin) { | ||
| const files = {}; | ||
| for (const relative of await filesUnder(modulesRoot)) | ||
| files[relative] = await digest(relative); | ||
| return { schemaVersion: 1, pin, files }; | ||
| } | ||
|
|
||
| const vendor = JSON.parse( | ||
| await readFile( | ||
| path.join(root, ".github", "scripts", "sync-modules-vendor.json"), | ||
| "utf8", | ||
| ), | ||
| ); | ||
| const actual = await snapshot(vendor.pin); | ||
|
|
||
| if (process.argv.includes("--write")) { | ||
| await writeFile(manifestFile, `${JSON.stringify(actual, null, 2)}\n`); | ||
| console.log(`wrote ${path.relative(root, manifestFile)}`); | ||
| process.exit(0); | ||
| } | ||
|
|
||
| const expected = JSON.parse(await readFile(manifestFile, "utf8")); | ||
| if (expected.pin !== vendor.pin) | ||
| throw new Error( | ||
| `integrity pin mismatch: manifest=${expected.pin} vendor=${vendor.pin}`, | ||
| ); | ||
| if (JSON.stringify(expected.files) !== JSON.stringify(actual.files)) | ||
| throw new Error( | ||
| "vendored modules differ from sync-modules-integrity.json; re-vendor and update the manifest", | ||
| ); | ||
| console.log(`vendored modules match pin ${vendor.pin}`); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| { | ||
| "schemaVersion": 1, | ||
| "pin": "jfrog-agent-hooks/v0.9.0", | ||
| "files": { | ||
| "assets/agents-default-conf.json": "04aae9b1dcfc75271c3ed786adceea1635b0a1ae0be64fadd7b1111229f11f01", | ||
| "claude-session-start.mjs": "2ca1edc6b939cdff6c5faa6ac4b69636e6e92bc7b079316e1fdee7c53c6e837b", | ||
| "copilot-session-start.mjs": "8811e0829c90ff0987bed158f5ef571195ee5eb54dfc8021b4396fe76ad8a499", | ||
| "core/agents-config.mjs": "3ade16fd6e08b8ac6cb8570edfbed1e9677b26d9c31513720680dd17513480af", | ||
| "core/io.mjs": "63ea75df635a4e15cf36f2158fe78ae42e3ca886abe267ee5ed2577042eb153d", | ||
| "core/jf-identity.mjs": "9d0301d4a60b9c9297cde24e0bab0c2660c56f831617f2b69db17c844276b19b", | ||
| "core/logger.mjs": "1ebdffcdf4af14b19e3ee8e82cfeb377fb9961a09d4e9d6ecdc922d07b0848c2", | ||
| "core/run-capability.mjs": "9fac890b7fd4866f9d3322469b2a7301e28cebfa3faebd77857f9f79d2d1c532", | ||
| "cursor-session-start.mjs": "37dd25ffee18e9f357e3cbb8453552766fd89295e85aa09bf93bc208df74aa20", | ||
| "package-resolution/scripts/eager-setup-receipt.mjs": "69213084bc1976ec63b346ca26e8a63eb713b0da7ad6ab4fc018934e70fef091", | ||
| "package-resolution/scripts/eager-setup.mjs": "d78fc422d15a271e9ae68b754a28fa72ea25a9e86b505b1a0e5e053add864c0d", | ||
| "package-resolution/scripts/feature-flag.mjs": "18b258e4d1999de31bad54a1f7f3c3f9cf65c598bbb83f328b45f68a739821f3", | ||
| "package-resolution/scripts/index.mjs": "f3ea8f71ecd156515a4a5eb14e33de5c61287f4f2e0e7ca90f9b7d010c4d6567", | ||
| "package-resolution/scripts/package-manager-family.mjs": "50d066910638e37c2375f696094fde1252181398be56c4218ca14342a76a34e5", | ||
| "package-resolution/scripts/print-policy.mjs": "02593c6e401006d226b908221d007ba9e1951a61c4cb060789877c1fe919faa2", | ||
| "package-resolution/scripts/render-instruction.mjs": "9e4205b5715e2c79515de19d473a6487d61971368103f2851388530ed0a180c4", | ||
| "package-resolution/scripts/repo-types.mjs": "b432bcdd6e77f80ca2c9dddfbf4d9e1299019758fd58b04b29fc21b18a86f788", | ||
| "package-resolution/scripts/resolver.mjs": "3485575a65fd5579420d69a51f723047d44f3cfa246511565c6e89ca32b02b4d", | ||
| "package-resolution/scripts/setup-conflict.mjs": "fdc589561813a9c5e708f50a20a87bacdad3f490158c973b12b217cb984e2845", | ||
| "package-resolution/scripts/workspace-config.mjs": "f8f8eaaf0fb8a0c3691938e99afebbc87d8779e508db0ef821fa23f0a55b786a", | ||
| "package-resolution/templates/package-resolution-unconfigured.md": "e7645b89d1c4d618fb45692de084d627ca24b5e2e975416176115d3c233e9c00", | ||
| "package-resolution/templates/package-resolution.md": "c305751d24fe352b6334a208f7831eb9db58704f1baa693c6921264f6a4456d1" | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| { | ||
| "repo": "JFROG/jfrog-agent-hooks", | ||
| "pin": "jfrog-agent-hooks/v0.9.0", | ||
| "paths": ["modules"], | ||
| "dest_prefix": "plugin" | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| #!/usr/bin/env node | ||
| // Vendors modules bundle from jfrog-agent-hooks into plugin/. | ||
| // | ||
| // Usage: | ||
| // JFROG_AGENT_HOOKS_PATH=/path/to/jfrog-agent-hooks node .github/scripts/sync-modules.mjs | ||
| // | ||
| // Defaults JFROG_AGENT_HOOKS_PATH to ../jfrog-agent-hooks (sibling clone). | ||
| // Reads paths from sync-modules-vendor.json. | ||
|
|
||
| import { promises as fs } from "node:fs"; | ||
| import path from "node:path"; | ||
| import { fileURLToPath } from "node:url"; | ||
|
|
||
| const scriptDir = path.dirname(fileURLToPath(import.meta.url)); | ||
| const repoRoot = path.resolve(scriptDir, "..", ".."); | ||
| const vendorPath = path.join(scriptDir, "sync-modules-vendor.json"); | ||
|
|
||
| async function fileExists(p) { | ||
| try { | ||
| await fs.access(p); | ||
| return true; | ||
| } catch { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| async function copyPath(fromDir, toDir, relativePath) { | ||
| const from = path.join(fromDir, relativePath); | ||
| const to = path.join(toDir, relativePath); | ||
| if (!(await fileExists(from))) { | ||
| throw new Error(`path missing in upstream: ${relativePath}`); | ||
| } | ||
| await fs.rm(to, { recursive: true, force: true }); | ||
| await fs.mkdir(path.dirname(to), { recursive: true }); | ||
| await fs.cp(from, to, { recursive: true }); | ||
| console.log(` ${relativePath} -> ${path.relative(process.cwd(), to)}`); | ||
| } | ||
|
|
||
| async function main() { | ||
| const vendor = JSON.parse(await fs.readFile(vendorPath, "utf8")); | ||
| const paths = vendor.paths; | ||
| if (!Array.isArray(paths) || paths.length === 0) { | ||
| throw new Error(`${vendorPath} must define a non-empty paths array`); | ||
| } | ||
|
|
||
| const hooksRoot = | ||
| process.env.JFROG_AGENT_HOOKS_PATH?.trim() || | ||
| path.resolve(repoRoot, "..", "jfrog-agent-hooks"); | ||
|
|
||
| if (!(await fileExists(hooksRoot))) { | ||
| throw new Error( | ||
| `jfrog-agent-hooks not found at ${hooksRoot}. Set JFROG_AGENT_HOOKS_PATH.`, | ||
| ); | ||
| } | ||
|
|
||
| const destPrefix = (vendor.dest_prefix ?? "").replace(/^\/+|\/+$/g, ""); | ||
| const destRoot = destPrefix ? path.join(repoRoot, destPrefix) : repoRoot; | ||
|
|
||
| console.log(`--- sync from ${hooksRoot} (pin: ${vendor.pin ?? "local"}) ---`); | ||
| for (const rel of paths) { | ||
| await copyPath(hooksRoot, destRoot, rel); | ||
| } | ||
| console.log("done."); | ||
| } | ||
|
|
||
| await main(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| # Copyright (c) JFrog Ltd. 2026 | ||
| # Licensed under the Apache License, Version 2.0 | ||
| # https://www.apache.org/licenses/LICENSE-2.0 | ||
|
|
||
| name: Validate package resolution hook | ||
|
|
||
| on: | ||
| pull_request: | ||
| branches: [main] | ||
| paths: | ||
| - "plugin/hooks/hooks.json" | ||
| - "plugin/modules/**" | ||
| - "plugin/.claude-plugin/plugin.json" | ||
| - "marketplace.json" | ||
| - "scripts/validate-package-resolution-hook.mjs" | ||
| - ".github/scripts/sync-modules-vendor.json" | ||
| - ".github/scripts/sync-modules.mjs" | ||
|
omerzi marked this conversation as resolved.
|
||
| - ".github/scripts/sync-modules-integrity.json" | ||
| - ".github/scripts/check-vendored-modules.mjs" | ||
| - ".github/workflows/validate-package-resolution-hook.yml" | ||
|
|
||
| jobs: | ||
| validate: | ||
| name: Validate package resolution hook | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Set up Node.js | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: "20" | ||
|
|
||
| - name: Validate hook assembly | ||
| run: node scripts/validate-package-resolution-hook.mjs | ||
|
|
||
| - name: Verify vendored module integrity | ||
| run: node .github/scripts/check-vendored-modules.mjs | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| # Agent Package Resolution — VS Code administrator guide | ||
|
|
||
| Agent Package Resolution is opt-in. Deploy | ||
| `~/.jfrog/agents-conf.json` through the organization’s normal device-management | ||
| system; the plugin never overwrites an existing file. | ||
|
|
||
| ## Recommended configuration | ||
|
|
||
| Declare only repositories approved for the organization: | ||
|
|
||
| ```json | ||
| { | ||
| "logLevel": "info", | ||
| "packageResolution": { | ||
| "enabled": true, | ||
| "verifyRepos": true, | ||
| "cacheTtlDays": 7, | ||
| "defaultGlobalRepos": { | ||
| "npm": "npm-virtual", | ||
| "pypi": "pypi-virtual" | ||
| }, | ||
| "autoSetup": [] | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| Repository keys are verified against Artifactory before routing. Invalid or | ||
| unreachable repositories remain unresolved rather than falling back to public | ||
| registries. Workspace files may replace a repository for an already-governed | ||
| package type, but cannot expand the governed scope. | ||
|
|
||
| `cacheTtlDays: 0` re-checks all cached state on every session, including | ||
| eligible zero-touch `jf setup` receipts. Use it temporarily when troubleshooting, | ||
| not as a normal operating setting. | ||
|
|
||
| ## Rollout checklist | ||
|
|
||
| 1. Configure and test `jf` on a non-production server. | ||
| 2. Deploy the configuration to a pilot group. | ||
| 3. Enable `"chat.plugins.enabled": true` and `"chat.useHooks": true` in VS Code. | ||
| 4. Start a new Copilot chat and verify the resolved repository table. | ||
| 5. Monitor `~/.jfrog/logs/agent-hooks.log` before expanding rollout. |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.