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
80 changes: 80 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: Release

on:
push:
branches:
- main

permissions:
contents: write
pull-requests: write
id-token: write

jobs:
release:
name: Version, changelog, and publish
runs-on: ubuntu-latest

steps:
- name: Create or finalize release
id: release
uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4
with:
config-file: release-please-config.json
manifest-file: .release-please-manifest.json

- name: Check out repository
if: ${{ steps.release.outputs.release_created == 'true' }}
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
persist-credentials: false

- name: Set up Node.js
if: ${{ steps.release.outputs.release_created == 'true' }}
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 24
registry-url: https://registry.npmjs.org
Comment thread
coderabbitai[bot] marked this conversation as resolved.
package-manager-cache: false

- name: Install dependencies
if: ${{ steps.release.outputs.release_created == 'true' }}
run: npm ci

- name: Test package
if: ${{ steps.release.outputs.release_created == 'true' }}
run: npm test

- name: Generate OpenCode release diff summary
if: ${{ steps.release.outputs.release_created == 'true' }}
continue-on-error: true
env:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
OPENCODE_MODEL: ${{ vars.OPENCODE_MODEL }}
RELEASE_TAG_NAME: ${{ steps.release.outputs.tag_name }}
run: node scripts/summarize-release-diff.mjs

- name: Append OpenCode summary to GitHub release
if: ${{ steps.release.outputs.release_created == 'true' }}
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG_NAME: ${{ steps.release.outputs.tag_name }}
run: |
if [ ! -s opencode-release-summary.md ]; then
echo "No OpenCode release summary was generated."
exit 0
fi

gh release view "$RELEASE_TAG_NAME" --json body --jq '.body // ""' > release-notes.md
{
cat release-notes.md
printf '\n\n## OpenCode Diff Summary\n\n'
cat opencode-release-summary.md
} > release-notes-with-opencode.md
gh release edit "$RELEASE_TAG_NAME" --notes-file release-notes-with-opencode.md
Comment thread
the-Drunken-coder marked this conversation as resolved.

- name: Publish to npm
if: ${{ steps.release.outputs.release_created == 'true' }}
run: npm publish --access public
3 changes: 3 additions & 0 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
".": "0.0.0"
}
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ The role of this file is to describe common mistakes and confusion points that a
If you encounter something surprising, tell the developer and add the lesson here so future agents do not rediscover the same failure.

- For number-based MIL-STD-2525D/APP-6D SIDCs rendered by `milsymbol`, digits 9-10 are the echelon/mobility field and digits 11-20 are the function ID. Keep curated land-unit fixtures aligned with `milsymbol`'s number-SIDC metadata and land-unit function table.
- Release automation uses Release Please plus npm trusted publishing in `.github/workflows/release.yml`. Keep release-driving commits in Conventional Commit format so version bumps and `CHANGELOG.md` stay automated.
- The release workflow optionally uses `OPENCODE_API_KEY` through `scripts/summarize-release-diff.mjs` to append an OpenCode-generated diff summary to GitHub release notes. Keep that step non-blocking so npm publishing is not blocked by LLM availability.
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@ V0 intentionally supports only a tiny curated set of 30-digit SIDCs. It does not

## Install

This package is private while the V0 API is being validated. Clone the repository and install dependencies locally:
Install from npm after the first release is published:

```sh
npm install sidc-kit
```

For local development, clone the repository and install dependencies:

```sh
npm install
Expand Down Expand Up @@ -69,3 +75,24 @@ Renders a curated SIDC with `milsymbol` and returns SVG plus anchor and size met
The current curated set includes a few common land-unit examples such as friendly infantry platoon, hostile infantry platoon, armor platoon, artillery platoon, reconnaissance platoon, and infantry company.

Image-based reverse lookup is intentionally deferred.

## Release Automation

Releases are managed by Release Please. Commits merged to `main` should use Conventional Commit prefixes:

- `fix:` creates a patch release
- `feat:` creates a minor release
- `feat!:` or `fix!:` creates a major release

On `main`, the release workflow opens or updates a release PR that contains the version bump and `CHANGELOG.md`. Merging that release PR creates the GitHub release and publishes the package to npm.

Npm publishing uses trusted publishing with GitHub Actions OIDC. Configure the package on npm with these trusted publisher values:

- owner: `the-Drunken-coder`
- repository: `sidc-kit`
- workflow filename: `release.yml`
- allowed action: `npm publish`

No long-lived `NPM_TOKEN` is required for the release workflow.

If `OPENCODE_API_KEY` is configured as a GitHub Actions secret, the release workflow also asks OpenCode to summarize the diff between the new release tag and the previous release tag, then appends that summary to the GitHub release notes. Set the optional repository variable `OPENCODE_MODEL` to override the default `opencode/kimi-k2` model.
12 changes: 11 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"name": "sidc-kit",
"version": "0.0.0",
"private": true,
"description": "A small TypeScript toolkit for searching, explaining, building, and rendering curated SIDCs.",
"type": "module",
"main": "./dist/index.js",
Expand All @@ -16,6 +15,14 @@
"dist",
"README.md"
],
"repository": {
"type": "git",
"url": "git+https://github.com/the-Drunken-coder/sidc-kit.git"
},
"bugs": {
"url": "https://github.com/the-Drunken-coder/sidc-kit/issues"
},
"homepage": "https://github.com/the-Drunken-coder/sidc-kit#readme",
"scripts": {
"build": "tsc -p tsconfig.json",
"test": "npm run build && node --test"
Expand All @@ -28,6 +35,9 @@
"milsymbol"
],
"license": "MIT",
"publishConfig": {
"access": "public"
},
"dependencies": {
"milsymbol": "^3.0.4"
},
Expand Down
9 changes: 9 additions & 0 deletions release-please-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"packages": {
".": {
"release-type": "node",
"package-name": "sidc-kit",
"include-component-in-tag": false
}
}
}
114 changes: 114 additions & 0 deletions scripts/summarize-release-diff.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { execFileSync, spawnSync } from "node:child_process";
import { writeFileSync } from "node:fs";

const tagName = process.env.RELEASE_TAG_NAME;
const openCodeApiKey = process.env.OPENCODE_API_KEY;
const model = process.env.OPENCODE_MODEL || "opencode/kimi-k2";
const contextPath = ".release-diff-context.md";
const outputPath = "opencode-release-summary.md";
const maxPatchBytes = 120_000;

if (!tagName) {
console.log("No release tag was provided; skipping OpenCode release summary.");
process.exit(0);
}

if (!openCodeApiKey) {
console.log("OPENCODE_API_KEY is not configured; skipping OpenCode release summary.");
process.exit(0);
}

/**
* Runs a git command and returns trimmed stdout.
*/
function git(args) {
return execFileSync("git", args, { encoding: "utf8" }).trim();
}

/**
* Runs a git command that may validly fail for first-release history.
*/
function optionalGit(args) {
try {
return git(args);
} catch {
return "";
}
}

const previousTag = optionalGit(["describe", "--tags", "--abbrev=0", `${tagName}^`]);
const baseRef = previousTag || git(["rev-list", "--max-parents=0", tagName]);
const range = `${baseRef}..${tagName}`;
const diffStat = optionalGit(["diff", "--stat", range]);
const nameStatus = optionalGit(["diff", "--name-status", range]);
const commits = optionalGit(["log", "--oneline", "--no-decorate", range]);
const patch = optionalGit(["diff", "--no-ext-diff", "--unified=40", range, "--", ".", ":(exclude)package-lock.json"]);
const truncatedPatch =
Buffer.byteLength(patch, "utf8") > maxPatchBytes
? `${Buffer.from(patch).subarray(0, maxPatchBytes).toString("utf8")}\n\n[Diff truncated at ${maxPatchBytes} bytes.]`
: patch;

writeFileSync(
contextPath,
[
`# Release Diff Context`,
``,
`Current tag: ${tagName}`,
`Previous tag: ${previousTag || "(none; comparing from initial commit)"}`,
`Compared range: ${range}`,
``,
`## Commits`,
commits || "(none)",
``,
`## Changed Files`,
nameStatus || "(none)",
``,
`## Diff Stat`,
diffStat || "(none)",
``,
`## Patch`,
"```diff",
truncatedPatch || "(none)",
"```",
""
].join("\n")
);

const prompt = [
"Summarize this release diff for maintainers and npm users.",
"Use concise Markdown.",
"Focus on user-visible changes, migration notes, packaging changes, and risks.",
"Do not invent changes that are not supported by the attached diff context.",
"Do not include secrets, token values, or operational speculation.",
"Return only the summary body."
].join(" ");

const result = spawnSync(
"npx",
["-y", "opencode-ai@1.17.9", "run", "--pure", "--model", model, "--file", contextPath, prompt],
{
encoding: "utf8",
env: process.env,
killSignal: "SIGTERM",
timeout: 120_000
}
);
Comment thread
the-Drunken-coder marked this conversation as resolved.

if (result.error?.code === "ETIMEDOUT") {
console.error("OpenCode summary generation timed out.");
process.exit(1);
}

if (result.status !== 0) {
console.error(result.stderr || result.stdout || "OpenCode summary generation failed.");
process.exit(result.status ?? 1);
}

const summary = result.stdout.trim();
if (!summary) {
console.log("OpenCode returned an empty summary.");
process.exit(0);
}

writeFileSync(outputPath, `${summary}\n`);
console.log(`Wrote OpenCode release summary to ${outputPath}.`);
Loading