diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6c5e983..3c2671d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,7 @@ jobs: node-version: "22" cache: npm - run: npm install + - run: npm run lint - run: npm run build - run: npm test - name: Umami proxy tests diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..ec4ee29 --- /dev/null +++ b/biome.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.5.10/schema.json", + "files": { + "includes": [ + "**", + "!packages/cli/dist", + "!website/.docusaurus", + "!website/build", + "!coverage" + ] + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2 + }, + "linter": { + "enabled": true, + "rules": { + "preset": "recommended" + } + }, + "overrides": [ + { + "includes": ["website/static/img/logo.svg"], + "linter": { + "rules": { + "a11y": { + "noSvgWithoutTitle": "off" + } + } + } + } + ], + "javascript": { + "formatter": { + "quoteStyle": "double", + "trailingCommas": "all" + } + } +} diff --git a/infra/umami-proxy/src/index.ts b/infra/umami-proxy/src/index.ts index eac83ca..6d3fb14 100644 --- a/infra/umami-proxy/src/index.ts +++ b/infra/umami-proxy/src/index.ts @@ -10,7 +10,6 @@ */ import { - COLLECT_PATH, ORIGIN_COLLECT, ORIGIN_SCRIPT, matchProxyPath, diff --git a/package-lock.json b/package-lock.json index bb45c6e..9bd76a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,9 @@ "packages/cli", "website" ], + "devDependencies": { + "@biomejs/biome": "^2.5.10" + }, "engines": { "node": ">=20" } @@ -1997,6 +2000,169 @@ "node": ">=6.9.0" } }, + "node_modules/@biomejs/biome": { + "version": "2.5.10", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.5.10.tgz", + "integrity": "sha512-WRKXARA3kTuiV5sxqTpobJ/I0MVd4vk3pOL6wnp5az4LntFIhWTj1RWZq3DI9PCEN3lXcqy7p5aqUHzvq8AXyQ==", + "dev": true, + "license": "MIT OR Apache-2.0", + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "2.5.10", + "@biomejs/cli-darwin-x64": "2.5.10", + "@biomejs/cli-linux-arm64": "2.5.10", + "@biomejs/cli-linux-arm64-musl": "2.5.10", + "@biomejs/cli-linux-x64": "2.5.10", + "@biomejs/cli-linux-x64-musl": "2.5.10", + "@biomejs/cli-win32-arm64": "2.5.10", + "@biomejs/cli-win32-x64": "2.5.10" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "2.5.10", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.5.10.tgz", + "integrity": "sha512-ItCrxKK6SXVT6flYs0qIuBd4AA3TTTl4d66Re6YI2FuGZnN85NmuYNzkiTJUyYw8qBLv69L5zTUB6uyWd++h3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "2.5.10", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.5.10.tgz", + "integrity": "sha512-yLsPU9pAmtChXDu8vhKAzErqe+LeeYuwuUB2FZMkRitsmdodxsYRa9KHrFispsUHzzOu+9HB3nP/TQxyia+Sjw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "2.5.10", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.5.10.tgz", + "integrity": "sha512-VG8uQW/86a1roLaIFvtIbEigxIdzdJ190oGyg1tV7VYeQtOS+x10sflk7WbuXgw91EtZX5DlIIIej1YqkNLlcg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "2.5.10", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.5.10.tgz", + "integrity": "sha512-t1QAKZwQJRB4dvgJSgFiQ4BNfNPChg69BNonz854qLVxnjT3UvDzQg9mbkTJRu35ZqU0Rw10A73J8Urgbg2RPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "2.5.10", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.5.10.tgz", + "integrity": "sha512-4O6T0eq2heoHZN0a9UX+rWQoxXEBaKf+lRi2hbsGlHneUz9BWXM76nEWMK7Eeq8gzMxR1khQB6BFpAASpeXqGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "2.5.10", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.5.10.tgz", + "integrity": "sha512-pgDDqp9JybHm2I0KRgzN6i4+lt8xu4iqxUwLzglUMmOmyRTU1AYBGKzh9sNMOtIjah7xoWvKHlLVetvyifzoiQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "2.5.10", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.5.10.tgz", + "integrity": "sha512-pxAbxduPO4xq/Cvgaa2lOrs9BB0hEXmmDqfMNP4ZOffGOkUrD1/QGw9UAMpFQpX2P8MqTIIRuQKcmetum4Oa6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "2.5.10", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.5.10.tgz", + "integrity": "sha512-M+2dgBsl3lXRiTfgPVc2p3anS4Tocojke4rzFLScZ2Y/wmF+36dRb1iHCLiyGqOzQGyTplZH1HnEYviiAqi3nA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, "node_modules/@braintree/sanitize-url": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", diff --git a/package.json b/package.json index e1aac94..cb79208 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,9 @@ "prepare": "node scripts/link-skills.mjs && node scripts/sync-hooks.mjs", "build": "npm run build -w @chtnnh/know-code", "build:docs": "npm run build -w website", + "lint": "biome lint .", + "lint:fix": "biome lint --write .", + "format:check": "biome format .", "test": "npm run test -w @chtnnh/know-code", "smoke": "bash scripts/smoke-enforcement.sh", "smoke:verify": "bash scripts/smoke-verify-ci.sh", @@ -26,5 +29,8 @@ "repository": { "type": "git", "url": "git+https://github.com/chtnnh/know-code.git" + }, + "devDependencies": { + "@biomejs/biome": "^2.5.10" } } diff --git a/packages/cli/package.json b/packages/cli/package.json index 6afe075..17f9e9a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -11,6 +11,9 @@ "files": [ "bin", "dist", + "!dist/**/*.test.*", + "!dist/test-helpers.*", + "!dist/**/*.map", "hooks", "README.md", "LICENSE" diff --git a/packages/cli/src/cli-surface.test.ts b/packages/cli/src/cli-surface.test.ts index 2ed4559..0d23573 100644 --- a/packages/cli/src/cli-surface.test.ts +++ b/packages/cli/src/cli-surface.test.ts @@ -192,7 +192,7 @@ describe("cli surface (spawned)", () => { assert.equal(r.status, 0); const session = readRangeSession(root); assert.ok(session, "expected a new range session"); - assert.equal(session!.fromOid, head); + assert.equal(session?.fromOid, head); } finally { cleanup(); } diff --git a/packages/cli/src/commands-core.test.ts b/packages/cli/src/commands-core.test.ts index 6c191d7..3d2ad93 100644 --- a/packages/cli/src/commands-core.test.ts +++ b/packages/cli/src/commands-core.test.ts @@ -283,7 +283,7 @@ describe("commands: config / init / quiz / doctor / reset / ship", () => { assert.match(workflow, /github\.event\.before/); assert.match(workflow, /github\.event\.pull_request\.head\.sha \|\| github\.sha/); assert.match(workflow, /new branch push/); - assert.match(workflow, /else\n know-code verify\n/); + assert.match(workflow, /else\n {12}know-code verify\n/); const action = readFileSync(join(repoRoot, "action", "action.yml"), "utf8"); assert.match(action, /actions\/setup-node@v5/); @@ -333,10 +333,12 @@ describe("commands: config / init / quiz / doctor / reset / ship", () => { const checks = await runDoctor(root); const hooks = checks.find((c) => c.name === "git-hooks"); assert.ok(hooks); - assert.equal(hooks!.ok, false); + assert.equal(hooks.ok, false); installGitHooks(root); const after = await runDoctor(root); - assert.equal(after.find((c) => c.name === "git-hooks")!.ok, true); + const hooksAfterInstall = after.find((c) => c.name === "git-hooks"); + assert.ok(hooksAfterInstall); + assert.equal(hooksAfterInstall.ok, true); } finally { cleanup(); } diff --git a/packages/cli/src/commands/amend.ts b/packages/cli/src/commands/amend.ts index 8ac1025..e389b58 100644 --- a/packages/cli/src/commands/amend.ts +++ b/packages/cli/src/commands/amend.ts @@ -21,7 +21,7 @@ export function buildAmendArgs( ): string[] { const noTrailer = rawArgs.includes("--no-trailer"); const gitArgs = rawArgs.filter((a) => a !== "--no-trailer"); - let finalArgs = ["--amend", ...gitArgs]; + const finalArgs = ["--amend", ...gitArgs]; if (noTrailer) return finalArgs; diff --git a/packages/cli/src/commands/check.ts b/packages/cli/src/commands/check.ts index 031c6ec..f182eb8 100644 --- a/packages/cli/src/commands/check.ts +++ b/packages/cli/src/commands/check.ts @@ -128,7 +128,8 @@ export function cmdCheck(opts: RunCheckOptions = {}): never { "know-code: KNOW_CODE_OVERRIDE=1 — check passed via human override (logged).", ); } else if (isSealedRewriteRangeOpen(repoRoot)) { - const seal = readRangeSeal(repoRoot)!; + const seal = readRangeSeal(repoRoot); + if (!seal) throw new Error("sealed rewrite range missing seal"); console.error( `know-code: gate open (sealed rewrite range) for ${seal.diffHash.slice(0, 12)}…`, ); @@ -136,11 +137,11 @@ export function cmdCheck(opts: RunCheckOptions = {}): never { const receipt = readGateSafe(repoRoot); if (commitDrift) { console.error( - `know-code: gate open (${receipt!.level}, ${ctx.scope}) — tree unchanged since pass (${effectiveHash.slice(0, 12)}…)`, + `know-code: gate open (${receipt?.level}, ${ctx.scope}) — tree unchanged since pass (${effectiveHash.slice(0, 12)}…)`, ); } else { console.error( - `know-code: gate open (${receipt!.level}, ${ctx.scope}) for ${ctx.diffHash.slice(0, 12)}…`, + `know-code: gate open (${receipt?.level}, ${ctx.scope}) for ${ctx.diffHash.slice(0, 12)}…`, ); } } diff --git a/packages/cli/src/commands/config.ts b/packages/cli/src/commands/config.ts index ffa4d77..1843cfb 100644 --- a/packages/cli/src/commands/config.ts +++ b/packages/cli/src/commands/config.ts @@ -1,4 +1,4 @@ -import { readConfig, setConfigValue, writeConfig } from "../config.js"; +import { readConfig, setConfigValue } from "../config.js"; import { resolveQuizContext } from "../hash.js"; import { findGitRoot, diff --git a/packages/cli/src/commands/grade.ts b/packages/cli/src/commands/grade.ts index 6d919db..1abd5c1 100644 --- a/packages/cli/src/commands/grade.ts +++ b/packages/cli/src/commands/grade.ts @@ -80,7 +80,7 @@ export function cmdGradePropose(opts: { json?: boolean }): void { const config = readConfig(repoRoot); const ctx = resolveQuizContext(repoRoot, config); - let answers; + let answers: ReturnType; try { answers = assertAnswersForHash(repoRoot, ctx.diffHash); } catch (err) { @@ -155,20 +155,25 @@ async function cmdGradeReview(opts: { process.exit(1); } - let answers; + let answers: ReturnType; try { answers = assertAnswersForHash(repoRoot, ctx.diffHash); } catch (err) { console.error(err instanceof Error ? err.message : err); process.exit(1); } + const answersDigest = answers.answersDigest; + if (!answersDigest) { + console.error("know-code: answers digest is missing"); + process.exit(1); + } let proposal: GradeProposal; try { proposal = assertGradeProposalForHash( repoRoot, ctx.diffHash, - answers.answersDigest!, + answersDigest, ); } catch (err) { console.error(err instanceof Error ? err.message : err); @@ -243,7 +248,7 @@ async function cmdGradeReview(opts: { const unsigned = buildGradeReceipt( ctx, - answers.answersDigest!, + answersDigest, finalScore, level, proposal, @@ -346,13 +351,18 @@ async function cmdGradeSelfScore(opts: { process.exit(1); } - let answers; + let answers: ReturnType; try { answers = assertAnswersForHash(repoRoot, ctx.diffHash); } catch (err) { console.error(err instanceof Error ? err.message : err); process.exit(1); } + const answersDigest = answers.answersDigest; + if (!answersDigest) { + console.error("know-code: answers digest is missing"); + process.exit(1); + } let level: Level; if (opts.level) { @@ -367,7 +377,7 @@ async function cmdGradeSelfScore(opts: { const unsigned = buildGradeReceipt( ctx, - answers.answersDigest!, + answersDigest, score, level, ); diff --git a/packages/cli/src/commands/pass.ts b/packages/cli/src/commands/pass.ts index 555ce10..80b47f8 100644 --- a/packages/cli/src/commands/pass.ts +++ b/packages/cli/src/commands/pass.ts @@ -56,8 +56,8 @@ export async function cmdPass(opts: { process.exit(1); } - let answers; - let grade; + let answers: ReturnType; + let grade: ReturnType; try { assertTaughtForHash(repoRoot, ctx.diffHash); answers = assertAnswersForHash(repoRoot, ctx.diffHash); diff --git a/packages/cli/src/commands/range.ts b/packages/cli/src/commands/range.ts index 502ab9e..c180f13 100644 --- a/packages/cli/src/commands/range.ts +++ b/packages/cli/src/commands/range.ts @@ -95,7 +95,6 @@ export function cmdRangeAbort(opts: { keepSeal?: boolean } = {}): void { } export function cmdRangeContinue(opts: { yes?: boolean } = {}): void { - const repoRoot = findGitRoot(); if (!opts.yes && process.stdin.isTTY) { process.stderr.write( "Start a new range for upcoming commits? Run with --yes to confirm.\n", @@ -208,7 +207,7 @@ export async function cmdRangeSeal(opts: { rangeFromOid: fromOid, commitCount: ctx.commitCount, sealMode, - gateKeyId: gate!.keyId || "unsigned", + gateKeyId: gate?.keyId || "unsigned", sealedAt: new Date().toISOString(), sealedHeadOid, ...(commitDrift ? { gatePassHash: effectiveHash } : {}), diff --git a/packages/cli/src/commands/reset.ts b/packages/cli/src/commands/reset.ts index 1dc0772..74129fd 100644 --- a/packages/cli/src/commands/reset.ts +++ b/packages/cli/src/commands/reset.ts @@ -1,4 +1,4 @@ -import { existsSync, readdirSync, unlinkSync } from "node:fs"; +import { existsSync, unlinkSync } from "node:fs"; import { join } from "node:path"; import { findGitRoot, knowCodeDir } from "../paths.js"; diff --git a/packages/cli/src/commands/status.ts b/packages/cli/src/commands/status.ts index 947ae75..d39c6c5 100644 --- a/packages/cli/src/commands/status.ts +++ b/packages/cli/src/commands/status.ts @@ -132,9 +132,7 @@ export function cmdStatus(opts: { json?: boolean; next?: boolean } = {}): void { gradeDetail: gradeStaleDetail, gradeProposal: proposalR.corrupt ? "corrupt" - : proposal?.diffHash === effectiveHash - ? true - : false, + : proposal?.diffHash === effectiveHash, gradeProposalDetail: proposalStaleDetail, overrideEnv: process.env.KNOW_CODE_OVERRIDE === "1", overrideAllow: hasValidOverrideAllow(repoRoot), diff --git a/packages/cli/src/e2e-workflow.test.ts b/packages/cli/src/e2e-workflow.test.ts index f7e3f11..31d7f4c 100644 --- a/packages/cli/src/e2e-workflow.test.ts +++ b/packages/cli/src/e2e-workflow.test.ts @@ -79,7 +79,7 @@ describe("e2e workflows", () => { baseBranch: "trunk", }); writeConfig(root, cfg); - const session = beginRangeSession(root, baseOid); + beginRangeSession(root, baseOid); assert.ok(readRangeSession(root)); writeFile(root, "a.txt", "feat\n"); diff --git a/packages/cli/src/exploit-matrix.test.ts b/packages/cli/src/exploit-matrix.test.ts index c0eca75..43a9295 100644 --- a/packages/cli/src/exploit-matrix.test.ts +++ b/packages/cli/src/exploit-matrix.test.ts @@ -23,7 +23,6 @@ import { agentHookAutoStagesCommit, agentHookBypassesGitHooks, agentHookCommitHasPathspec, - agentHookCommitOnly, agentHookFixupSquash, agentHookImplicitCommit, agentHookResetHard, diff --git a/packages/cli/src/gate-cmd.ts b/packages/cli/src/gate-cmd.ts index 0892d5d..c1a487a 100644 --- a/packages/cli/src/gate-cmd.ts +++ b/packages/cli/src/gate-cmd.ts @@ -70,7 +70,7 @@ function scanGitPushInvocation(cmd: string): string { * parsed command field is passed (never the raw hook stdin blob). */ export function shouldGate(cmd: string): boolean { - if (!cmd || !cmd.trim()) return false; + if (!cmd.trim()) return false; return ( GIT_COMMIT_CMD.test(cmd) || GIT_PUSH_CMD.test(cmd) || diff --git a/packages/cli/src/gate-drift.test.ts b/packages/cli/src/gate-drift.test.ts index a372b62..2a2d81a 100644 --- a/packages/cli/src/gate-drift.test.ts +++ b/packages/cli/src/gate-drift.test.ts @@ -174,7 +174,9 @@ describe("gate survives commit when tree unchanged (range drift)", () => { const after = resolveEffectiveQuizState(repoRoot); assert.equal(after.commitDrift, false); assert.equal(after.effectiveHash, hash); - assert.equal(readGate(repoRoot)!.gatedTreeOid, gatedTreeOid); + const gate = readGate(repoRoot); + assert.ok(gate); + assert.equal(gate.gatedTreeOid, gatedTreeOid); assert.equal(runCheck(repoRoot).allowed, true); assert.equal( diff --git a/packages/cli/src/hooks.ts b/packages/cli/src/hooks.ts index 37d741c..41c9efd 100644 --- a/packages/cli/src/hooks.ts +++ b/packages/cli/src/hooks.ts @@ -116,7 +116,7 @@ export function gitGateHookIsCurrent(content: string): boolean { content.includes("unset KNOW_CODE_COMMIT") && content.includes("--push") && // 0.3.0: empty CHECK_ARGS under bash 3.2 + set -u - content.includes("${#CHECK_ARGS[@]}") + content.includes("$" + "{#CHECK_ARGS[@]}") ); } diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 760ba9f..91ea212 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -179,7 +179,7 @@ function failAsync(err: unknown): never { } function main(): void { - const { command, subcommand, subsub, flags, rest } = parseArgs( + const { command, subcommand, flags, rest } = parseArgs( process.argv.slice(2), ); const passphrase = diff --git a/packages/cli/src/package.test.ts b/packages/cli/src/package.test.ts new file mode 100644 index 0000000..9a6513d --- /dev/null +++ b/packages/cli/src/package.test.ts @@ -0,0 +1,30 @@ +import assert from "node:assert/strict"; +import { execFileSync } from "node:child_process"; +import { describe, it } from "node:test"; +import { dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +interface PackResult { + files: Array<{ path: string }>; +} + +describe("published package contents", () => { + it("contains runtime files but not tests, test helpers, or documentation builds", () => { + const packageRoot = dirname(dirname(fileURLToPath(import.meta.url))); + const output = execFileSync( + "npm", + ["pack", "--dry-run", "--json", "--ignore-scripts"], + { cwd: packageRoot, encoding: "utf8" }, + ); + const [{ files }] = JSON.parse(output) as PackResult[]; + const paths = files.map(({ path }) => path); + + assert.ok(paths.includes("dist/index.js")); + assert.ok(paths.includes("bin/know-code.js")); + assert.ok(paths.includes("hooks/check-shell.sh")); + assert.ok(paths.every((path) => !/\.test\./.test(path))); + assert.ok(paths.every((path) => !path.endsWith(".map"))); + assert.ok(paths.every((path) => !path.startsWith("website/"))); + assert.ok(paths.every((path) => !path.includes("test-helpers"))); + }); +}); diff --git a/packages/cli/src/questions.ts b/packages/cli/src/questions.ts index 7dd8b89..39a2a97 100644 --- a/packages/cli/src/questions.ts +++ b/packages/cli/src/questions.ts @@ -116,7 +116,7 @@ function languagesFromPaths(paths: string[]): string[] { const counts = new Map(); for (const p of paths) { const base = p.split("/").pop() || p; - const ext = base.includes(".") ? base.split(".").pop()!.toLowerCase() : ""; + const ext = base.includes(".") ? base.split(".").pop()?.toLowerCase() ?? "" : ""; const lang = EXT_LANG[ext]; if (!lang) continue; counts.set(lang, (counts.get(lang) || 0) + 1); diff --git a/packages/cli/src/range.ts b/packages/cli/src/range.ts index 8843a2e..9a9051e 100644 --- a/packages/cli/src/range.ts +++ b/packages/cli/src/range.ts @@ -119,7 +119,7 @@ export function clearRangeSeal(repoRoot: string): void { /** After range seal --rewrite, tip hash / trailers match seal even if gate was passHash. */ export function isSealedRewriteRangeOpen(repoRoot: string): boolean { const seal = readRangeSeal(repoRoot); - if (!seal || seal.sealMode !== "rewrite" || !seal.rangeFromOid) { + if (seal?.sealMode !== "rewrite" || !seal.rangeFromOid) { return false; } // New commits after seal move HEAD — rewrite-open only at the sealed tip. diff --git a/packages/cli/src/seal.test.ts b/packages/cli/src/seal.test.ts index c0a3301..14f8216 100644 --- a/packages/cli/src/seal.test.ts +++ b/packages/cli/src/seal.test.ts @@ -54,7 +54,9 @@ describe("attest seals (Ed25519)", () => { }; const { keyId, sig } = signPayload(repoRoot, passphrase, payload); const sealed = { ...payload, keyId, sig }; - const pub = readAttestMeta(repoRoot)!.pubKey; + const attestMeta = readAttestMeta(repoRoot); + assert.ok(attestMeta); + const pub = attestMeta.pubKey; assert.equal(verifyPayload(pub, sealed), true); }); @@ -66,7 +68,9 @@ describe("attest seals (Ed25519)", () => { skipped: false, }; const { keyId, sig } = signPayload(repoRoot, passphrase, payload); - const pub = readAttestMeta(repoRoot)!.pubKey; + const attestMeta = readAttestMeta(repoRoot); + assert.ok(attestMeta); + const pub = attestMeta.pubKey; assert.equal( verifyPayload(pub, { ...payload, skipped: true, keyId, sig }), false, @@ -104,7 +108,9 @@ describe("attest seals (Ed25519)", () => { }); it("forged sig without private key fails verify", () => { - const pub = readAttestMeta(repoRoot)!.pubKey; + const attestMeta = readAttestMeta(repoRoot); + assert.ok(attestMeta); + const pub = attestMeta.pubKey; assert.equal( verifyPayload(pub, { version: 1, diff --git a/packages/cli/src/seal.ts b/packages/cli/src/seal.ts index a0f8ebf..edbd3c4 100644 --- a/packages/cli/src/seal.ts +++ b/packages/cli/src/seal.ts @@ -33,8 +33,6 @@ import { } from "node:fs"; import { homedir } from "node:os"; import { join } from "node:path"; -import { createInterface } from "node:readline"; -import { stdin as input, stderr as output } from "node:process"; import { findGitRoot } from "./paths.js"; import { readConfig } from "./config.js"; import { promptSecretHidden } from "./prompt.js"; diff --git a/packages/cli/src/trailers.ts b/packages/cli/src/trailers.ts index b593ea3..eca13ae 100644 --- a/packages/cli/src/trailers.ts +++ b/packages/cli/src/trailers.ts @@ -115,7 +115,7 @@ fi } // Drop filter-branch backup refs to avoid clutter (best-effort). - git(["update-ref", "-d", "refs/original/refs/heads/" + branchName(repoRoot)], repoRoot, { + git(["update-ref", "-d", `refs/original/refs/heads/${branchName(repoRoot)}`], repoRoot, { allowFail: true, }); git(["for-each-ref", "--format=%(refname)", "refs/original/"], repoRoot, { diff --git a/packages/cli/src/verify-walk.test.ts b/packages/cli/src/verify-walk.test.ts index 3625621..f386d1f 100644 --- a/packages/cli/src/verify-walk.test.ts +++ b/packages/cli/src/verify-walk.test.ts @@ -167,7 +167,7 @@ describe("partitionPushWalk", () => { it("fails when a merge has no current run to attach to", () => { const repo = initLab("kc-part-orphan-merge-"); try { - const base = commitFile(repo, "a.txt", "0\n", "base"); + commitFile(repo, "a.txt", "0\n", "base"); git(repo, ["checkout", "-b", "feat"]); commitFile(repo, "feat.txt", "f\n", "feat"); const tip = stampTrailer(repo, "c".repeat(64), "feat"); diff --git a/packages/cli/src/verify.test.ts b/packages/cli/src/verify.test.ts index 49ed3f7..a4e9e50 100644 --- a/packages/cli/src/verify.test.ts +++ b/packages/cli/src/verify.test.ts @@ -133,7 +133,7 @@ describe("verify hash candidates", () => { const candidates = collectVerifyHashCandidates(repo, cfg); const match = matchHeadTrailer(repo, "HEAD", candidates); assert.ok(match); - assert.equal(match!.hash, rangeHash); + assert.equal(match.hash, rangeHash); } finally { rmSync(repo, { recursive: true, force: true }); } @@ -194,10 +194,10 @@ describe("verify hash candidates", () => { ); const range = candidates.find((c) => c.label === "merge-base..HEAD"); assert.ok(range); - assert.equal(range!.hash, passHash); + assert.equal(range.hash, passHash); const match = matchHeadTrailer(repo, "HEAD", candidates); assert.ok(match); - assert.equal(match!.hash, passHash); + assert.equal(match.hash, passHash); } finally { rmSync(repo, { recursive: true, force: true }); } @@ -424,7 +424,7 @@ describe("verify hash candidates", () => { const range = candidates.find((c) => c.label === "merge-base..HEAD"); assert.ok(index); assert.ok(range); - assert.notEqual(index!.hash, range!.hash); + assert.notEqual(index.hash, range.hash); } finally { rmSync(repo, { recursive: true, force: true }); } @@ -482,7 +482,7 @@ describe("verify hash candidates", () => { ); const match = matchHeadTrailer(repo, "HEAD", candidates); assert.ok(match); - assert.equal(match!.hash, stagedHash); + assert.equal(match.hash, stagedHash); } finally { rmSync(repo, { recursive: true, force: true }); } @@ -692,7 +692,7 @@ describe("verify hash candidates", () => { collectVerifyHashCandidates(repo, cfg), ); assert.ok(headMatch); - assert.equal(headMatch!.hash, passHash); + assert.equal(headMatch.hash, passHash); } finally { rmSync(repo, { recursive: true, force: true }); } @@ -738,7 +738,7 @@ describe("verify hash candidates", () => { collectVerifyHashCandidates(repo, cfg), ); assert.ok(headMatch); - assert.equal(headMatch!.hash, passHash); + assert.equal(headMatch.hash, passHash); } finally { rmSync(repo, { recursive: true, force: true }); }