Skip to content

Commit 5e7931a

Browse files
committed
feat: add release preflight script and CI checks; fix multi-pattern glob types; v12.1.0
- Add scripts/release-preflight.sh with 10-check local gate (version agreement, CHANGELOG, README What's New, lint, type firewall, tests, pack dry-runs) - Wire npm run release:preflight in package.json - Add CHANGELOG and README What's New enforcement to release.yml verify job - Rewrite docs/release.md with full preflight checklist and runbook - Sync jsr.json version to 12.1.0 (was 12.0.0) - Update README What's New section for v12.1.0 - Fix index.d.ts: QueryBuilder.match() and ObserverConfig.match accept string | string[] - Fix JSDoc types in ObserverView, QueryBuilder, TranslationCost, query.methods
1 parent 4a14caf commit 5e7931a

12 files changed

Lines changed: 234 additions & 29 deletions

File tree

.github/workflows/release.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,30 @@ jobs:
9292
echo "tag: $TAG_VERSION"
9393
test "$JSR_VERSION" = "$TAG_VERSION"
9494
95+
- name: Verify CHANGELOG has dated entry for this version
96+
shell: bash
97+
run: |
98+
set -euo pipefail
99+
TAG_VERSION="${{ steps.meta.outputs.tag_version }}"
100+
if grep -qE "^\#*\s*\[?${TAG_VERSION}\]?\s*[—–-]\s*[0-9]{4}-[0-9]{2}-[0-9]{2}" CHANGELOG.md; then
101+
echo "CHANGELOG entry found for $TAG_VERSION"
102+
else
103+
echo "ERROR: No dated CHANGELOG entry for $TAG_VERSION"
104+
exit 1
105+
fi
106+
107+
- name: Verify README What's New section
108+
shell: bash
109+
run: |
110+
set -euo pipefail
111+
TAG_VERSION="${{ steps.meta.outputs.tag_version }}"
112+
if grep -qiE "what.s new.*(in|for)?\s*v?${TAG_VERSION}" README.md; then
113+
echo "README What's New found for v$TAG_VERSION"
114+
else
115+
echo "ERROR: README 'What's New' section not updated for v$TAG_VERSION"
116+
exit 1
117+
fi
118+
95119
- name: Verify published files
96120
run: npm pack --dry-run
97121

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313

1414
- **Multi-pattern glob support**`graph.observer()`, `query().match()`, and `translationCost()` now accept an array of glob patterns (e.g. `['campaign:*', 'milestone:*']`). Nodes matching *any* pattern in the array are included (OR semantics).
1515
- **Centralized `matchGlob` utility** (`src/domain/utils/matchGlob.js`) — unified glob matching logic with regex caching and support for array-based multi-pattern matching.
16+
- **Release preflight**`npm run release:preflight` runs a 10-check local gate before tagging. CI (`release.yml`) now also enforces CHANGELOG and README checks on tag push.
17+
18+
### Fixed
19+
20+
- **Type declarations for multi-pattern glob**`index.d.ts` `QueryBuilder.match()` and `ObserverConfig.match` now accept `string | string[]`, matching runtime behavior. JSDoc annotations updated in `ObserverView`, `QueryBuilder`, `TranslationCost`, and `query.methods`.
1621

1722
### [12.0.0] — 2026-02-25
1823

README.md

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,10 @@
88
<img src="docs/images/hero.gif" alt="git-warp CLI demo" width="600">
99
</p>
1010

11-
## What's New in v12.0.0
12-
13-
- **MaterializedViewService** — unified service orchestrating build, persist, and load of bitmap indexes and property readers as a single coherent materialized view. Checkpoints now embed the index (schema:4) for instant hydration on open.
14-
- **GraphTraversal engine (11 algorithms)** — BFS, DFS, shortest path, Dijkstra, A\*, bidirectional A\*, topological sort, longest path, connected component, reachability, and common ancestors. All accessible via `graph.traverse.*`.
15-
- **NeighborProviderPort abstraction** — decouples traversal algorithms from storage. Two implementations: `AdjacencyNeighborProvider` (in-memory) and `BitmapNeighborProvider` (O(1) bitmap lookups).
16-
- **Logical bitmap index** — CBOR-sharded Roaring bitmap index with labeled edges, stable numeric IDs, and property indexes. `IncrementalIndexUpdater` enables O(diff) updates.
17-
- **`nodeWeightFn`** — node-weighted graph algorithms (Dijkstra, A\*, longest path) as an alternative to edge-weight functions.
18-
- **CLI: `verify-index` and `reindex`** — new commands for index integrity checks and forced rebuilds.
19-
- **Cross-runtime hardening** — eliminated bare `Buffer` usage across the index subsystem; bitmap indexes now work on Node, Bun, and Deno.
11+
## What's New in v12.1.0
12+
13+
- **Multi-pattern glob support**`graph.observer()`, `query().match()`, and `translationCost()` now accept an array of glob patterns (e.g. `['campaign:*', 'milestone:*']`). Nodes matching *any* pattern in the array are included (OR semantics).
14+
- **Release preflight**`npm run release:preflight` runs a 10-check local gate (version agreement, CHANGELOG, README, lint, types, tests, pack dry-runs) before tagging.
2015

2116
See the [full changelog](CHANGELOG.md) for details.
2217

docs/release.md

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,25 +32,59 @@ Both registries use OIDC trusted publishing -- no stored tokens.
3232
- GitHub environments `npm` and `jsr` exist (OIDC -- no secrets required).
3333
- npm and JSR trusted publisher connections are configured (see above).
3434

35+
## Preflight checklist
36+
37+
Run the local preflight before tagging:
38+
39+
```bash
40+
npm run release:preflight
41+
```
42+
43+
This script (`scripts/release-preflight.sh`) checks:
44+
45+
| # | Check | Blocking? |
46+
|---|-------|-----------|
47+
| 1 | `package.json` version == `jsr.json` version | Yes |
48+
| 2 | Clean working tree (no uncommitted changes) | Yes |
49+
| 3 | On `main` branch | Warning |
50+
| 4 | CHANGELOG has a dated `[X.Y.Z] — YYYY-MM-DD` entry | Yes |
51+
| 5 | README "What's New" section updated for version | Yes |
52+
| 6 | ESLint clean | Yes |
53+
| 7 | Type firewall (tsc + IRONCLAD policy + consumer + surface) | Yes |
54+
| 8 | Unit tests pass | Yes |
55+
| 9 | `npm pack --dry-run` + `jsr publish --dry-run` | Yes |
56+
| 10 | `npm audit` (runtime deps, high/critical) | Warning |
57+
58+
If all checks pass, the script prints the exact tag + push commands.
59+
3560
## Steps
3661

37-
1. Update `package.json` and `jsr.json` versions to the target version.
38-
2. Merge to `main` (all checks green).
39-
3. Tag the release:
62+
1. Prepare the release content:
63+
- Bump version in **both** `package.json` and `jsr.json`.
64+
- Move `[Unreleased]` items in `CHANGELOG.md` to a dated `[X.Y.Z] — YYYY-MM-DD` section.
65+
- Update the `## What's New in vX.Y.Z` section in `README.md`.
66+
- Commit: `git commit -m "release: vX.Y.Z"`
67+
2. Run preflight:
68+
```bash
69+
npm run release:preflight
70+
```
71+
3. Merge to `main` (all CI checks green).
72+
4. Tag the release:
4073
- Stable: `git tag -s vX.Y.Z -m "release: vX.Y.Z"`
4174
- RC: `git tag -s vX.Y.Z-rc.N -m "release: vX.Y.Z-rc.N"`
42-
4. Push the tag:
75+
5. Push the tag:
4376
```bash
4477
git push origin vX.Y.Z
4578
```
46-
5. Watch the Actions pipeline:
47-
- **CI** -- full test suite (lint, Docker tests, BATS CLI)
48-
- **verify** -- version match, dry-run pack, dry-run JSR
79+
6. Watch the Actions pipeline:
80+
- **tag-guard** -- validates tag format (`vX.Y.Z` or `vX.Y.Z-(rc|beta|alpha).N`)
81+
- **CI** -- full test suite (type firewall, lint, Docker tests across Node/Bun/Deno)
82+
- **verify** -- version match (package.json == jsr.json == tag), CHANGELOG entry, README What's New, dry-run pack, dry-run JSR
4983
- **publish_npm** -- publishes to npm via OIDC with provenance
5084
- **publish_jsr** -- publishes to JSR via OIDC
5185
- **github_release** -- creates GitHub Release with auto-generated notes
52-
6. If one registry fails, re-run only that job from the Actions UI.
53-
7. Confirm:
86+
7. If one registry fails, re-run only that job from the Actions UI.
87+
8. Confirm:
5488
- npm dist-tag is correct (`latest` for stable, `next`/`beta`/`alpha` for prereleases)
5589
- JSR version is visible
5690
- GitHub Release notes are generated

index.d.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ export interface HopOptions {
226226
* Fluent query builder.
227227
*/
228228
export class QueryBuilder {
229-
match(pattern: string): QueryBuilder;
229+
match(pattern: string | string[]): QueryBuilder;
230230
where(fn: ((node: QueryNodeSnapshot) => boolean) | Record<string, unknown>): QueryBuilder;
231231
outgoing(label?: string, options?: HopOptions): QueryBuilder;
232232
incoming(label?: string, options?: HopOptions): QueryBuilder;
@@ -1194,8 +1194,8 @@ export const CONTENT_PROPERTY_KEY: '_content';
11941194
* Configuration for an observer view.
11951195
*/
11961196
export interface ObserverConfig {
1197-
/** Glob pattern for visible nodes (e.g. 'user:*') */
1198-
match: string;
1197+
/** Glob pattern or array of patterns for visible nodes (e.g. 'user:*' or ['user:*', 'team:*']) */
1198+
match: string | string[];
11991199
/** Property keys to include (whitelist). If omitted, all non-redacted properties are visible. */
12001200
expose?: string[];
12011201
/** Property keys to exclude (blacklist). Takes precedence over expose. */

jsr.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@git-stunts/git-warp",
3-
"version": "12.0.0",
3+
"version": "12.1.0",
44
"imports": {
55
"roaring": "npm:roaring@^2.7.0"
66
},

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
"setup:hooks": "node scripts/setup-hooks.js",
8080
"prepare": "patch-package && node scripts/setup-hooks.js",
8181
"prepack": "npm run lint && npm run test:local && npm run typecheck:consumer",
82+
"release:preflight": "bash scripts/release-preflight.sh",
8283
"install:git-warp": "bash scripts/install-git-warp.sh",
8384
"uninstall:git-warp": "bash scripts/uninstall-git-warp.sh",
8485
"test:node20": "docker compose -f docker-compose.test.yml --profile node20 run --build --rm test-node20",

scripts/release-preflight.sh

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
#!/usr/bin/env bash
2+
# ──────────────────────────────────────────────────────────────────────────────
3+
# Release Preflight — local sanity check before tagging a release.
4+
#
5+
# Usage: npm run release:preflight
6+
# bash scripts/release-preflight.sh
7+
#
8+
# Exits 0 if all checks pass, 1 if any hard check fails.
9+
# ──────────────────────────────────────────────────────────────────────────────
10+
set -euo pipefail
11+
12+
RED='\033[0;31m'
13+
GREEN='\033[0;32m'
14+
YELLOW='\033[1;33m'
15+
BOLD='\033[1m'
16+
NC='\033[0m'
17+
18+
pass() { echo -e " ${GREEN}${NC} $1"; }
19+
fail() { echo -e " ${RED}${NC} $1"; EXIT=1; }
20+
warn() { echo -e " ${YELLOW}!${NC} $1"; }
21+
22+
EXIT=0
23+
24+
echo ""
25+
echo -e "${BOLD}═══ Release Preflight ═══${NC}"
26+
echo ""
27+
28+
# ── 1. Version agreement ─────────────────────────────────────────────────────
29+
PKG=$(node -p "require('./package.json').version")
30+
JSR=$(node -p "require('./jsr.json').version")
31+
echo "Versions:"
32+
if [ "$PKG" = "$JSR" ]; then
33+
pass "package.json ($PKG) == jsr.json ($JSR)"
34+
else
35+
fail "package.json ($PKG) != jsr.json ($JSR)"
36+
fi
37+
38+
# ── 2. Clean working tree ────────────────────────────────────────────────────
39+
echo "Working tree:"
40+
if git diff --quiet && git diff --cached --quiet; then
41+
pass "clean (no uncommitted changes)"
42+
else
43+
fail "dirty working tree — commit or stash first"
44+
fi
45+
46+
# ── 3. Branch ─────────────────────────────────────────────────────────────────
47+
BRANCH=$(git rev-parse --abbrev-ref HEAD)
48+
echo "Branch:"
49+
if [ "$BRANCH" = "main" ]; then
50+
pass "on main"
51+
else
52+
warn "on '$BRANCH' (expected main)"
53+
fi
54+
55+
# ── 4. CHANGELOG has a dated entry for this version ──────────────────────────
56+
echo "CHANGELOG:"
57+
if grep -qE "^\#*\s*\[?${PKG}\]?\s*[—–-]\s*[0-9]{4}-[0-9]{2}-[0-9]{2}" CHANGELOG.md; then
58+
pass "found dated entry for $PKG"
59+
else
60+
fail "no dated entry for $PKG in CHANGELOG.md"
61+
fi
62+
63+
# ── 5. README "What's New" section ───────────────────────────────────────────
64+
echo "README:"
65+
if grep -qiE "what.s new.*(in|for)?\s*v?${PKG}" README.md; then
66+
pass "README mentions What's New for v$PKG"
67+
else
68+
fail "README 'What's New' section not updated for v$PKG"
69+
fi
70+
71+
# ── 6. Lint ───────────────────────────────────────────────────────────────────
72+
echo "Lint:"
73+
if npm run lint --silent 2>/dev/null; then
74+
pass "ESLint clean"
75+
else
76+
fail "ESLint errors"
77+
fi
78+
79+
# ── 7. Type firewall ─────────────────────────────────────────────────────────
80+
echo "Type firewall:"
81+
if npm run typecheck --silent 2>/dev/null; then
82+
pass "tsc --noEmit"
83+
else
84+
fail "TypeScript errors"
85+
fi
86+
if npm run typecheck:policy --silent 2>/dev/null; then
87+
pass "IRONCLAD policy"
88+
else
89+
fail "Policy violations"
90+
fi
91+
if npm run typecheck:consumer --silent 2>/dev/null; then
92+
pass "Consumer type surface"
93+
else
94+
fail "Consumer type test failed"
95+
fi
96+
if npm run typecheck:surface --silent 2>/dev/null; then
97+
pass "Declaration surface"
98+
else
99+
fail "Declaration surface mismatch"
100+
fi
101+
102+
# ── 8. Unit tests ────────────────────────────────────────────────────────────
103+
echo "Tests:"
104+
if npm run test:local --silent 2>/dev/null; then
105+
pass "unit tests"
106+
else
107+
fail "unit test failures"
108+
fi
109+
110+
# ── 9. Pack dry-runs ─────────────────────────────────────────────────────────
111+
echo "Pack:"
112+
if npm pack --dry-run 2>&1 | grep -q "total files"; then
113+
pass "npm pack dry-run"
114+
else
115+
fail "npm pack dry-run failed"
116+
fi
117+
if npx -y jsr publish --dry-run --allow-dirty 2>/dev/null; then
118+
pass "JSR publish dry-run"
119+
else
120+
fail "JSR publish dry-run failed"
121+
fi
122+
123+
# ── 10. Security audit (warning only) ────────────────────────────────────────
124+
echo "Security:"
125+
if npm audit --omit=dev --audit-level=high 2>/dev/null; then
126+
pass "no high/critical vulnerabilities"
127+
else
128+
warn "npm audit found issues (non-blocking)"
129+
fi
130+
131+
# ── Summary ───────────────────────────────────────────────────────────────────
132+
echo ""
133+
if [ "$EXIT" -eq 0 ]; then
134+
echo -e "${GREEN}All preflight checks passed.${NC}"
135+
echo ""
136+
echo "Ready to tag:"
137+
echo " git tag -s v${PKG} -m 'release: v${PKG}'"
138+
echo " git push origin v${PKG}"
139+
else
140+
echo -e "${RED}Preflight failed. Fix the issues above before tagging.${NC}"
141+
fi
142+
echo ""
143+
exit $EXIT

src/domain/services/ObserverView.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ function sortNeighbors(list) {
6666
* Builds filtered adjacency maps by scanning all edges in the OR-Set.
6767
*
6868
* @param {import('./JoinReducer.js').WarpStateV5} state
69-
* @param {string} pattern
69+
* @param {string|string[]} pattern
7070
* @returns {{ outgoing: Map<string, NeighborEntry[]>, incoming: Map<string, NeighborEntry[]> }}
7171
*/
7272
function buildAdjacencyFromEdges(state, pattern) {
@@ -168,7 +168,7 @@ export default class ObserverView {
168168
/** @type {string} */
169169
this._name = name;
170170

171-
/** @type {string} */
171+
/** @type {string|string[]} */
172172
this._matchPattern = config.match;
173173

174174
/** @type {string[]|undefined} */

src/domain/services/QueryBuilder.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,7 @@ export default class QueryBuilder {
463463
*/
464464
constructor(graph) {
465465
this._graph = graph;
466-
/** @type {string|null} */
466+
/** @type {string|string[]|null} */
467467
this._pattern = null;
468468
/** @type {Array<{type: string, fn?: (node: QueryNodeSnapshot) => boolean, label?: string, depth?: [number, number]}>} */
469469
this._operations = [];
@@ -488,6 +488,7 @@ export default class QueryBuilder {
488488
*/
489489
match(pattern) {
490490
assertMatchPattern(pattern);
491+
/** @type {string|string[]|null} */
491492
this._pattern = pattern;
492493
return this;
493494
}

0 commit comments

Comments
 (0)