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
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ jobs:
with:
node-version: 20
cache: npm
# Catches a mis-bump (manifest/package drift, missing versions.json entry)
# at PR time rather than at tag-push time.
- run: npm run check:versions
- run: npm ci
- run: npm run lint
# esbuild strips types without checking them, so build alone cannot catch
# a type error. This is the only gate that does.
- run: npm run typecheck
- run: npm run build
- run: npm test
7 changes: 7 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,19 @@ jobs:
node-version: "20"
cache: "npm"

# Runs before `npm ci` so a mistyped tag costs seconds, not a whole run.
- name: Verify tag matches manifest, package and versions.json
run: node scripts/check-version-sync.mjs "${GITHUB_REF_NAME}"

- name: Install dependencies
run: npm ci

- name: Lint
run: npm run lint

- name: Typecheck
run: npm run typecheck

- name: Test
run: npm test

Expand Down
13 changes: 11 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,25 @@ visual glow instead of hunting filenames.
- **Repo:** https://github.com/WhatsYourWhy/Cognitive-Glow
- **Build:** `npm run build` (esbuild → `main.js` at repo root)
- **Lint:** `npm run lint` (must pass clean before any release)
- **Typecheck:** `npm run typecheck` — esbuild strips types WITHOUT checking them,
so this is the only gate that catches a type error. Covers both tsconfigs.
- **Test:** `npm test`
- **Version sync:** `npm run check:versions` (see Release process below)

## Release process — DO NOT bypass the workflow

Releases ship via `.github/workflows/release.yml`. The workflow builds, signs
the artifacts with GitHub artifact attestations, and uploads `main.js`,
`styles.css`, and `manifest.json` to the release.

The release workflow runs `lint` + `test` before `build`, so a broken commit
cannot ship. CI also runs on push-to-main as a second gate.
The release workflow runs `check:versions` + `lint` + `typecheck` + `test` before
`build`, so a broken commit cannot ship. CI also runs on push-to-main as a second
gate.

Steps 1, 2 and 4 below are enforced by `scripts/check-version-sync.mjs`, which
runs in CI (without the tag check) and as the FIRST release step, before
`npm ci` — so a mistyped tag fails in seconds. It is a guard, not a substitute:
you still have to do the bumps.

To cut a release:

Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
"build": "node esbuild.config.mjs",
"dev": "node esbuild.config.mjs --watch",
"lint": "eslint plugin/ core/ ui/ manifest.json",
"typecheck": "tsc --noEmit && tsc -p tsconfig.test.json --noEmit",
"check:versions": "node scripts/check-version-sync.mjs",
"test": "cross-env TS_NODE_PROJECT=tsconfig.test.json TS_NODE_TRANSPILE_ONLY=1 node --test --loader ts-node/esm tests/glow-score.test.ts tests/store-sanitize.test.ts"
},
"devDependencies": {
Expand Down
61 changes: 61 additions & 0 deletions scripts/check-version-sync.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/usr/bin/env node
// Enforces the release version invariants from CLAUDE.md so a mis-bump cannot
// reach a tag:
//
// 1. manifest.json version === package.json version
// 2. versions.json has an entry for that version, mapping to minAppVersion
// 3. when a tag is supplied, tag === manifest.json version
//
// Obsidian's plugin store matches the release tag against manifest.json as a
// plain string, so a mismatch publishes a release the store cannot resolve.
//
// Usage:
// node scripts/check-version-sync.mjs # checks 1 + 2 (CI, any branch)
// node scripts/check-version-sync.mjs 0.5.0 # also checks 3 (release)

import { readFileSync } from "node:fs";

const read = (name) =>
JSON.parse(readFileSync(new URL(`../${name}`, import.meta.url), "utf8"));

const manifest = read("manifest.json");
const pkg = read("package.json");
const versions = read("versions.json");

const tag = (process.argv[2] ?? "").trim();
const errors = [];

if (pkg.version !== manifest.version) {
errors.push(
`package.json version '${pkg.version}' does not match manifest.json version '${manifest.version}'`,
);
}

const mapped = versions[manifest.version];
if (mapped === undefined) {
errors.push(
`versions.json has no entry for '${manifest.version}'. Add "${manifest.version}": "${manifest.minAppVersion}".`,
);
} else if (mapped !== manifest.minAppVersion) {
errors.push(
`versions.json['${manifest.version}'] is '${mapped}' but manifest.json minAppVersion is '${manifest.minAppVersion}'`,
);
}

if (tag && tag !== manifest.version) {
errors.push(
`tag '${tag}' does not match manifest.json version '${manifest.version}'`,
);
}

if (errors.length > 0) {
const prefix = process.env.GITHUB_ACTIONS ? "::error::" : "error: ";
for (const message of errors) console.error(`${prefix}${message}`);
process.exit(1);
}

console.log(
`version sync OK — manifest=${manifest.version} package=${pkg.version} ` +
`minAppVersion=${manifest.minAppVersion} versions.json=${mapped}` +
(tag ? ` tag=${tag}` : " (no tag checked)"),
);
6 changes: 4 additions & 2 deletions tests/glow-score.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,12 @@ test("computeGlowScore matches the expected formula", () => {
});

test("computeGlowScore uses fallback mtime when lastOpened is missing", () => {
const stats: NoteStats = {
// Deliberately degraded record: store.ts guarantees lastOpened post-sanitize,
// so the cast is what lets us reach the SPEC 3.2 mtime-fallback branch.
const stats = {
path: "notes/fallback.md",
hitCount: 1,
};
} as NoteStats;
const fallbackMtime = now - 2 * 24 * 60 * 60 * 1000;

const expected = expectedGlowScore(stats, fallbackMtime);
Expand Down
6 changes: 5 additions & 1 deletion tsconfig.test.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
"compilerOptions": {
"types": [
"node"
]
],
// Tests import with explicit .ts extensions so ts-node/ESM can resolve them
// at runtime; this makes `tsc --noEmit` accept the same paths.
"allowImportingTsExtensions": true,
"noEmit": true
},
"include": [
"core/**/*.ts",
Expand Down