border-customs 0.8.1 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "border-customs",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "//": "publint is a runtime dep (exact pin): the npm-stage engine is invoked via bin resolution against the installed layout, so consumers get the publint-fail leg out of the box; the dev suite resolves the same node_modules/.bin/publint — no devDependencies entry is needed for it to stay green.",
5
5
  "description": "Fail-closed git push gate: secret scanning + supply-chain checks before any remote leaves the machine",
6
6
  "type": "module",
@@ -18,7 +18,7 @@
18
18
  "index.ts"
19
19
  ],
20
20
  "engines": {
21
- "node": ">=22.0.0"
21
+ "node": ">=22.22.2"
22
22
  },
23
23
  "license": "MIT",
24
24
  "private": false,
@@ -28,22 +28,25 @@
28
28
  },
29
29
  "scripts": {
30
30
  "typecheck": "tsc --noEmit",
31
- "build": "esbuild src/index.ts --bundle --format=esm --platform=node --banner:js='import{createRequire as __borderRequire}from\"node:module\";const require=__borderRequire(import.meta.url);' --outfile=dist/index.js && node tools/copy-assets.mjs",
31
+ "build": "node tools/build.mjs",
32
32
  "pretest": "node -e \"const fs=require('node:fs');fs.rmSync('test/tmp',{recursive:true,force:true});fs.mkdirSync('test/tmp',{recursive:true})\"",
33
- "test": "node --import ./tools/register-ts.mjs --test --test-reporter=tap test/*.test.ts"
33
+ "test": "node --import ./tools/register-ts.mjs --test --test-reporter=tap test/*.test.ts",
34
+ "version": "node tools/version-sync.mjs",
35
+ "version:check": "node tools/version-sync.mjs --check"
34
36
  },
35
37
  "dependencies": {
36
- "@opencode-ai/plugin": "^1.17.4",
37
38
  "@secretlint/core": "13.0.5",
38
39
  "@secretlint/secretlint-rule-no-dotenv": "13.0.5",
39
40
  "@secretlint/secretlint-rule-no-homedir": "13.0.5",
40
- "@secretlint/secretlint-rule-pattern": "13.0.5",
41
41
  "@secretlint/secretlint-rule-preset-recommend": "13.0.5",
42
+ "@textlint/regexp-string-matcher": "2.0.2",
42
43
  "publint": "0.3.24",
43
44
  "yaml": "2.9.0",
44
45
  "zod": "4.5.4"
45
46
  },
46
47
  "devDependencies": {
48
+ "@opencode-ai/plugin": "^1.19.4",
49
+ "@secretlint/secretlint-rule-pattern": "13.0.5",
47
50
  "@types/node": "22.19.0",
48
51
  "esbuild": "0.25.12",
49
52
  "secretlint": "13.0.5",
package/plugin/border.ts CHANGED
@@ -1,4 +1,4 @@
1
- // border-opencode-plugin v0.8.1
1
+ // border-opencode-plugin v0.8.2
2
2
  // Official opencode plugin adapter for the border fail-closed push gate.
3
3
  // Registers ONE agent tool, `border`, that drives the border CLI — argv-only
4
4
  // (node:child_process execFile, never a shell), top-level commands restricted
@@ -20,13 +20,16 @@
20
20
  // This file ships as-is (package.json "files") and reaches a session by two
21
21
  // routes, neither compiling it through border's tsc:
22
22
  // (A) copied into <CONFIG>/plugins/ by `border opencode install`, which also
23
- // drops commands/border.md; "@opencode-ai/plugin" then resolves in
24
- // opencode's config-directory node_modules.
23
+ // drops commands/border.md.
25
24
  // (B) served as the package's "./server" export when the npm package name is
26
- // listed in opencode.jsonc "plugin" — opencode's arborist install places
27
- // @opencode-ai/plugin (its runtime dependency) next to the package in the
28
- // cache, so the same import resolves there too. With no commands/border.md
29
- // on disk, the config hook below self-registers the slash command instead.
25
+ // listed in opencode.jsonc "plugin". With no commands/border.md on disk,
26
+ // the config hook below self-registers the slash command instead.
27
+ // Since 2026-10-23 NEITHER route resolves "@opencode-ai/plugin" at runtime:
28
+ // the `tool` helper is vendored (identity function, see below) and Config is a
29
+ // type-only import, so the host's loader strips them. The package remains a
30
+ // devDependency for live-host probe tests; upstream's runtime chain
31
+ // (effect@beta, toml) — advisory-flagged and pointless for an identity call —
32
+ // left border's production install graph.
30
33
  // On this route the CLI is spawned FROM THE PACKAGE ITSELF (../dist/index.js
31
34
  // lives inside the same tarball), so no global install or PATH entry is
32
35
  // needed — that is what makes Route B genuinely install-free.
@@ -45,7 +48,21 @@
45
48
  import { execFile } from "node:child_process";
46
49
  import { existsSync } from "node:fs";
47
50
  import { fileURLToPath } from "node:url";
48
- import { tool, type Config } from "@opencode-ai/plugin";
51
+ import { z } from "zod";
52
+
53
+ import type { Config } from "@opencode-ai/plugin";
54
+
55
+ // Vendored from @opencode-ai/plugin's tool.js (MIT, (c) 2025 opencode):
56
+ // upstream's body is literally `return input` (typing pass-through). Owning
57
+ // the identity keeps a security tool's production dependency graph free of
58
+ // the package's runtime chain; the live-host probe (test/opencode.probe.test.ts)
59
+ // is the standing proof that stripped-import loading still activates.
60
+ function tool<T>(input: T): T {
61
+ return input;
62
+ }
63
+ // upstream attaches the zod namespace here (tool.schema = z) and hosts read
64
+ // it for arg-schema rendering — border's own zod dependency fills the slot.
65
+ tool.schema = z;
49
66
 
50
67
  /** Spawn cap: a CLI call that outlives this is killed and reported, never awaited forever. */
51
68
  const TIMEOUT_MS = 300_000;
@@ -423,7 +440,14 @@ export default {
423
440
  .describe("remaining argv tokens, e.g. ['--force']"),
424
441
  },
425
442
  // V1 edge: gate → V1-only metadata → shared substance (see gateArgv).
426
- execute: async (args, context) => {
443
+ // The arg/context surface is declared locally (mirrors the args schema
444
+ // above and the host's metadata hook — the vendored tool() no longer
445
+ // injects upstream's inference); test/opencode.probe.test.ts runs the
446
+ // real host call, so a drifted host contract fails the live probe.
447
+ execute: async (
448
+ args: { command: string; extra?: string[] },
449
+ context: { metadata(o: { title: string }): void },
450
+ ) => {
427
451
  const argv: readonly string[] = [args.command.trim(), ...(args.extra ?? [])];
428
452
  const refusal = gateArgv(argv);
429
453
  if (refusal !== null) return refusal;