Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 51 additions & 38 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,58 +1,71 @@
# Manual release publishing using npm Trusted Publishing (OIDC).
# Never triggered by a GitHub release or tag push.
name: Publish to npm

on:
push:
tags:
- "v*.*.*"
workflow_dispatch:
inputs:
tag:
description: "Existing release tag (for example v0.3.0)"
required: true
type: string

permissions:
contents: read
id-token: write

jobs:
publish:
name: Publish package
runs-on: ubuntu-latest

steps:
- name: Checkout repo
- name: Validate input and checkout tag
env:
RELEASE_TAG: ${{ inputs.tag }}
run: |
set -eu
if ! printf '%s' "$RELEASE_TAG" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "Expected a stable release tag such as v1.2.3" >&2
exit 1
fi
- name: Checkout release tag
uses: actions/checkout@v4
with:
ref: ${{ inputs.tag }}
persist-credentials: false

- name: Setup Node
- name: Set up Node for npm Trusted Publishing
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
registry-url: https://registry.npmjs.org/

- name: Install dependencies
run: npm ci

- name: Compile detection rules from TOML
run: npm run build:rules

- name: Typecheck
run: npm run typecheck
node-version: '24'
registry-url: https://registry.npmjs.org
package-manager-cache: false

- name: Lint
run: npm run lint
- name: Require current npm OIDC client
run: npm install --global 'npm@^11.5.1'

- name: Unit tests
run: npm run test:unit

- name: Integration tests
run: npm run test:integration

- name: Security regression tests
run: npm run test:security

- name: Build
run: npm run build
- name: Verify version and tag
env:
RELEASE_TAG: ${{ inputs.tag }}
run: |
set -eu
actual="$(node -p "require('./package.json').version")"
test "$RELEASE_TAG" = "v$actual" || {
echo "Version mismatch: $RELEASE_TAG vs $actual" >&2
exit 1
}
echo "Verified release $RELEASE_TAG"

- name: Package validation
run: npm run verify:package
- name: Install dependencies
run: npm ci
- name: Build and validate
run: |
npm run build
npm run typecheck
npm run lint
npm run test:unit
npm run test:integration
npm run test:security
npm run verify:package

- name: Publish to npm
run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Publish using OIDC
run: npm publish --access public
24 changes: 22 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,29 @@
# Changelog

## 0.2.1 (2026-10-29)

Packaging and npm-identity fix. The 0.2.0 source was distributed only as a
GitHub release; it was never installable from npm.

### Fixed
- **Restored the published npm package identity.** `package.json` named the
package `@gitdamnit/stranger-danger`, which has never existed on npm, so
`npm install` could not resolve it. The package is again
`@gitdamnit/opencode-stranger-danger`, the name published since 0.1.0.
- **`npm run verify:package` no longer hardcodes the package name.** It reads
the name from `package.json`, so packaging validation cannot drift from what
is actually published. The previous hardcoding is what made the packed-artifact
check fail as soon as the name was corrected.

### Changed
- **npm publication is manual and OIDC-based.** `.github/workflows/publish.yml`
now runs only on `workflow_dispatch`, checks out and verifies the release tag,
and publishes through npm Trusted Publishing instead of an `NPM_TOKEN` secret.

## 0.2.0 (2026-10-16)

Security hardening and OpenCode V2 migration. Published as a GitHub release;
npm publication of `@gitdamnit/stranger-danger` is pending registry setup
npm publication of `@gitdamnit/opencode-stranger-danger` is pending registry setup
(install from the repository in the meantime).

### Security fixes
Expand Down Expand Up @@ -73,7 +93,7 @@ npm publication of `@gitdamnit/stranger-danger` is pending registry setup
broken companion or bad event cannot cascade.
- Documented the ordering-independent guarantee (the `session.context` hook is
the backstop) and the single optional, read-only, semver-versioned interop
surface (`@gitdamnit/stranger-danger/engine`).
surface (`@gitdamnit/opencode-stranger-danger/engine`).
- Removed a stale reference to the retired CheapCode project from
`scripts/write_fixture.ts`; it now writes only inside this repository.
- Added an independence test suite (isolated instances, session isolation,
Expand Down
26 changes: 26 additions & 0 deletions DECISION_LOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,29 @@ Allowed statuses: `Proposed`, `Accepted`, `Superseded`, `Deprecated`, `Rejected`
the npm step because `NPM_TOKEN` is unset. This is expected and does not affect
the GitHub release. npm distribution must not be reported as complete.
- **Supersedes:** None

### D-0007 — Publish under the existing `@gitdamnit/opencode-stranger-danger` identity as 0.2.1

- **Date:** 2026-10-29
- **Status:** Accepted
- **Context:** `@gitdamnit/opencode-stranger-danger@0.1.0` is the package that has
been public on npm since 2026-05-14. The 0.2.0 release commit renamed the
package to `@gitdamnit/stranger-danger`, a name that has never existed on npm,
so the 0.2.0 GitHub release could not be installed from the registry. The
`v0.2.0` tag already points at that commit and its GitHub release is public.
- **Alternatives Considered:** publish `@gitdamnit/stranger-danger` as a second
package (rejected: splits the install base, silently abandons the existing
package, and contradicts the documented install command); move or delete the
`v0.2.0` tag so it can be reused (rejected: rewrites published history);
publish 0.2.0 from an untagged commit (rejected: breaks tag-to-artifact
provenance, which npm trusted publishing attests).
- **Decision:** Keep `@gitdamnit/opencode-stranger-danger` as the package
identity, release the identity and packaging fixes as `0.2.1`, tag it, and
publish through npm Trusted Publishing (GitHub Actions OIDC).
- **Rationale:** Restores installability under the name users already have, and
a patch bump keeps 0.2.0's unreachable release from being silently
overwritten or misrepresented.
- **Consequences:** npm's first 0.2.x artifact is 0.2.1, not 0.2.0. `D-0006`'s
"npm publication is a separate, manual step" still holds; the manual step is
now the OIDC `publish.yml` workflow rather than an `NPM_TOKEN` tag push.
- **Supersedes:** None
2 changes: 1 addition & 1 deletion PRODUCT_REQUIREMENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Project Identity

**Name:** strangerDanger (`@gitdamnit/stranger-danger`)
**Name:** strangerDanger (`@gitdamnit/opencode-stranger-danger`)
**Repository:** https://github.com/gitdamnit/opencode-stranger-danger
**Licence:** MIT
**Maintainer:** gitdamnit (Evan Zellner)
Expand Down
4 changes: 2 additions & 2 deletions PROJECT_STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Released
- [Implemented] CI and npm publish workflows — `.github/workflows/ci.yml`, `.github/workflows/publish.yml`
- [Implemented] MIT `LICENSE` and vulnerability-disclosure `SECURITY.md` — `LICENSE`, `SECURITY.md`
- [Published] GitHub release `v0.2.0` at merge commit `b634ce5cb84c533c1fd1f6802d922b6e812e5afb` — https://github.com/gitdamnit/opencode-stranger-danger/releases/tag/v0.2.0
- [Blocked] npm publication of `@gitdamnit/stranger-danger@0.2.0` — registry credentials absent (`ENEEDAUTH`, no `NPM_TOKEN`)
- [Blocked] npm publication of `@gitdamnit/opencode-stranger-danger@0.2.0` — registry credentials absent (`ENEEDAUTH`, no `NPM_TOKEN`)

## Tests

Expand Down Expand Up @@ -67,7 +67,7 @@ Released
## Next Actions

1. To publish to npm: create/claim the `@gitdamnit` scope, authenticate (`npm login`, or set the `NPM_TOKEN` repository secret), then re-run the `publish.yml` workflow for tag `v0.2.0` (or run `npm publish --access public` from a clean checkout of the tag). `publishConfig.access` is already `public` and `prepublishOnly` gates on typecheck, lint, tests, and build.
2. After npm publication, install with the documented `plugins: ["@gitdamnit/stranger-danger"]` form and re-verify.
2. After npm publication, install with the documented `plugins: ["@gitdamnit/opencode-stranger-danger"]` form and re-verify.

## Release Status

Expand Down
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
![strangerDanger logo](./strangerdanger2.jpeg)

# @gitdamnit/stranger-danger
# @gitdamnit/opencode-stranger-danger

Secret and PII redaction guard for AI coding agents.

[![npm version](https://img.shields.io/npm/v/@gitdamnit/stranger-danger.svg)](https://www.npmjs.com/package/@gitdamnit/stranger-danger)
[![license](https://img.shields.io/npm/l/@gitdamnit/stranger-danger.svg)](LICENSE)
[![node](https://img.shields.io/node/v/@gitdamnit/stranger-danger.svg)](https://nodejs.org)
[![npm version](https://img.shields.io/npm/v/@gitdamnit/opencode-stranger-danger.svg)](https://www.npmjs.com/package/@gitdamnit/opencode-stranger-danger)
[![license](https://img.shields.io/npm/l/@gitdamnit/opencode-stranger-danger.svg)](LICENSE)
[![node](https://img.shields.io/node/v/@gitdamnit/opencode-stranger-danger.svg)](https://nodejs.org)

## The Problem

Expand Down Expand Up @@ -45,11 +45,11 @@ Add to your `opencode.jsonc` (`plugins`, not the V1 `plugin` key):
```jsonc
{
"$schema": "https://opencode.ai/config.json",
"plugins": ["@gitdamnit/stranger-danger"]
"plugins": ["@gitdamnit/opencode-stranger-danger"]
}
```

> **0.2.0 is not yet on npm.** Until it is published, install it from the
> **0.2.1 is not yet on npm.** Until it is published, install it from the
> repository or a local checkout — see
> [Loading from a directory or a checkout](#loading-from-a-directory-or-a-checkout)
> below. The `plugins` entry above resolves once the package is published.
Expand Down Expand Up @@ -79,7 +79,7 @@ is unavailable, the plugin did not load.
### Standalone CLI

```bash
npm install -g @gitdamnit/stranger-danger # once published to npm
npm install -g @gitdamnit/opencode-stranger-danger # once published to npm
```

### Use with envsitter-guard
Expand Down Expand Up @@ -288,7 +288,7 @@ The only interop surface is the semver-versioned, read-only engine export:

```js
import { scan, redact, scanAndRedact, scanAndRedactBounded, toSafeFinding }
from "@gitdamnit/stranger-danger/engine";
from "@gitdamnit/opencode-stranger-danger/engine";
```

A companion plugin or the Docker Plugin Test Lab may call these to sanitize or
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@gitdamnit/stranger-danger",
"version": "0.2.0",
"name": "@gitdamnit/opencode-stranger-danger",
"version": "0.2.1",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Update the runtime version alongside the package version

The package is now version 0.2.1, but src/shared/version.ts still exports "0.2.0" and the help banner in src/cli/args.ts also hardcodes 0.2.0. Consequently, the new release still identifies itself as 0.2.0 in sd version, CLI help, and the plugin's status tool, making it impossible to distinguish the packaging fix through those interfaces. Running the checked-in CLI confirms strangerDanger v0.2.0. Update these runtime strings with the release bump; the package verifier currently accepts any semver-shaped version output and will not catch the mismatch.

Useful? React with 👍 / 👎.

"description": "Secret and PII redaction guard for AI coding agents — scans files, context windows, and outputs before secrets reach LLMs",
"keywords": [
"opencode",
Expand Down
18 changes: 12 additions & 6 deletions scripts/verify-package.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ const root = resolve(new URL("..", import.meta.url).pathname);
const run = (cmd, args, opts = {}) =>
execFileSync(cmd, args, { stdio: "pipe", encoding: "utf8", ...opts });

// Derive the identity from package.json so the validation can never drift from
// the name that is actually published.
const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
const pkgName = pkg.name;
const pkgPath = pkgName.split("/");
const tarballPrefix = pkgName.replace(/^@/, "").replace(/\//g, "-") + "-";

function step(label, fn) {
process.stdout.write(`- ${label} ... `);
const result = fn();
Expand Down Expand Up @@ -61,22 +68,21 @@ try {
});

step("standalone engine export resolves", () => {
const r = run(process.execPath, ["--input-type=module", "-e",
"import('@gitdamnit/stranger-danger/engine').then(m=>{if(typeof m.scan!=='function')process.exit(1)})",
run(process.execPath, ["--input-type=module", "-e",
`import(${JSON.stringify(`${pkgName}/engine`)}).then(m=>{if(typeof m.scan!=='function')process.exit(1)})`,
], { cwd: workdir });
void r;
});

step("plugin default export has the V2 shape", () => {
run(process.execPath, ["--input-type=module", "-e",
"import('@gitdamnit/stranger-danger').then(m=>{const p=m.default;if(!p||p.id!=='gitdamnit.stranger-danger'||typeof p.setup!=='function')process.exit(1)})",
`import(${JSON.stringify(pkgName)}).then(m=>{const p=m.default;if(!p||p.id!=='gitdamnit.stranger-danger'||typeof p.setup!=='function')process.exit(1)})`,
], { cwd: workdir });
});

// OpenCode resolves a plugin *directory* to <dir>/index.{ts,js} and ignores
// main/exports, so the shipped package must include a root entrypoint.
step("published package includes the root directory entrypoint", () => {
const entry = join(workdir, "node_modules", "@gitdamnit", "stranger-danger", "index.js");
const entry = join(workdir, "node_modules", ...pkgPath, "index.js");
if (!existsSync(entry)) throw new Error("package is missing root index.js (directory installs would not load)");
const src = readFileSync(entry, "utf8");
if (!/export\s*\{\s*default\s*\}/.test(src)) throw new Error("root index.js does not export a default plugin");
Expand All @@ -96,7 +102,7 @@ try {
// Remove any stray tarballs in the repo root.
try {
for (const f of readdirSync(root)) {
if (f.startsWith("gitdamnit-stranger-danger-") && f.endsWith(".tgz")) {
if (f.startsWith(tarballPrefix) && f.endsWith(".tgz")) {
rmSync(join(root, f), { force: true });
}
}
Expand Down
Loading