From 6a28487531c7cd00f0e935e86e8c857b6a58dd10 Mon Sep 17 00:00:00 2001 From: chtnnh <59027776+chtnnh@users.noreply.github.com> Date: Sun, 23 Aug 2026 19:00:23 +0400 Subject: [PATCH 1/3] fix: harden Umami proxy worker and CLI gate workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Repair the Umami proxy worker’s request handling while aligning the CLI gate commands, range flow, and test coverage with the updated behavior. Clean up Biome findings by replacing unsafe non-null assertions with explicit validated narrowing and removing stale imports and bindings. Validation: npm run lint -- --max-diagnostics=100 Know-Code-Verified: 9cfe4be37591656fc9d85d26a540ec901f37f9cbb9a043db6a127a693f7e35f9 --- .github/workflows/ci.yml | 1 + biome.json | 41 ++++++ infra/umami-proxy/src/index.ts | 1 - package-lock.json | 166 ++++++++++++++++++++++++ package.json | 6 + packages/cli/package.json | 2 + packages/cli/src/cli-surface.test.ts | 2 +- packages/cli/src/commands-core.test.ts | 8 +- packages/cli/src/commands/amend.ts | 2 +- packages/cli/src/commands/check.ts | 7 +- packages/cli/src/commands/config.ts | 2 +- packages/cli/src/commands/grade.ts | 22 +++- packages/cli/src/commands/pass.ts | 4 +- packages/cli/src/commands/range.ts | 5 +- packages/cli/src/commands/reset.ts | 2 +- packages/cli/src/commands/status.ts | 4 +- packages/cli/src/e2e-workflow.test.ts | 2 +- packages/cli/src/exploit-matrix.test.ts | 1 - packages/cli/src/gate-cmd.ts | 2 +- packages/cli/src/gate-drift.test.ts | 4 +- packages/cli/src/hooks.ts | 2 +- packages/cli/src/index.ts | 2 +- packages/cli/src/package.test.ts | 29 +++++ packages/cli/src/questions.ts | 2 +- packages/cli/src/range.ts | 2 +- packages/cli/src/seal.test.ts | 12 +- packages/cli/src/seal.ts | 2 - packages/cli/src/trailers.ts | 2 +- packages/cli/src/verify-walk.test.ts | 4 +- packages/cli/src/verify.test.ts | 14 +- 30 files changed, 306 insertions(+), 49 deletions(-) create mode 100644 biome.json create mode 100644 packages/cli/src/package.test.ts 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..4dc52f0 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -11,6 +11,8 @@ "files": [ "bin", "dist", + "!dist/**/*.test.*", + "!dist/test-helpers.*", "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..aa0fbf5 100644 --- a/packages/cli/src/commands/range.ts +++ b/packages/cli/src/commands/range.ts @@ -22,7 +22,6 @@ import { applyTrailerToRange } from "../trailers.js"; import type { RangeSealReceipt, RangeSealMode } from "../types.js"; export function cmdRangeBegin(opts: { from?: string }): void { - const repoRoot = findGitRoot(); try { const session = beginRangeSession(repoRoot, opts.from); console.log(`know-code: range began at ${session.fromOid.slice(0, 12)}…`); @@ -35,7 +34,6 @@ export function cmdRangeBegin(opts: { from?: string }): void { } export function cmdRangeStatus(json = false): void { - const repoRoot = findGitRoot(); const config = readConfig(repoRoot); const session = readRangeSession(repoRoot); const state = resolveEffectiveQuizState(repoRoot, config); @@ -95,7 +93,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 +205,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..6f65907 --- /dev/null +++ b/packages/cli/src/package.test.ts @@ -0,0 +1,29 @@ +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.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..3102825 100644 --- a/packages/cli/src/verify-walk.test.ts +++ b/packages/cli/src/verify-walk.test.ts @@ -107,7 +107,7 @@ describe("partitionPushWalk", () => { it("splits stacked trailer hashes into independent runs", () => { const repo = initLab("kc-part-stack-"); try { - const base = commitFile(repo, "a.txt", "0\n", "base"); + commitFile(repo, "a.txt", "0\n", "base"); commitFile(repo, "a.txt", "1\n", "one"); const s1 = stampTrailer(repo, "a".repeat(64), "one"); commitFile(repo, "a.txt", "2\n", "two"); @@ -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 }); } From 8881a233cc7b9d20c2dfc4b050fc25c81106a708 Mon Sep 17 00:00:00 2001 From: chtnnh <59027776+chtnnh@users.noreply.github.com> Date: Sun, 23 Aug 2026 19:11:47 +0400 Subject: [PATCH 2/3] fix: revert erroneous variable declaration deletes Know-Code-Verified: d69b59b1a5efa389338aa2202576ab814cfe6f05014e0e2ff99b5d35a057e090 --- packages/cli/src/commands/range.ts | 2 ++ packages/cli/src/verify-walk.test.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/commands/range.ts b/packages/cli/src/commands/range.ts index aa0fbf5..c180f13 100644 --- a/packages/cli/src/commands/range.ts +++ b/packages/cli/src/commands/range.ts @@ -22,6 +22,7 @@ import { applyTrailerToRange } from "../trailers.js"; import type { RangeSealReceipt, RangeSealMode } from "../types.js"; export function cmdRangeBegin(opts: { from?: string }): void { + const repoRoot = findGitRoot(); try { const session = beginRangeSession(repoRoot, opts.from); console.log(`know-code: range began at ${session.fromOid.slice(0, 12)}…`); @@ -34,6 +35,7 @@ export function cmdRangeBegin(opts: { from?: string }): void { } export function cmdRangeStatus(json = false): void { + const repoRoot = findGitRoot(); const config = readConfig(repoRoot); const session = readRangeSession(repoRoot); const state = resolveEffectiveQuizState(repoRoot, config); diff --git a/packages/cli/src/verify-walk.test.ts b/packages/cli/src/verify-walk.test.ts index 3102825..f386d1f 100644 --- a/packages/cli/src/verify-walk.test.ts +++ b/packages/cli/src/verify-walk.test.ts @@ -107,7 +107,7 @@ describe("partitionPushWalk", () => { it("splits stacked trailer hashes into independent runs", () => { const repo = initLab("kc-part-stack-"); try { - commitFile(repo, "a.txt", "0\n", "base"); + const base = commitFile(repo, "a.txt", "0\n", "base"); commitFile(repo, "a.txt", "1\n", "one"); const s1 = stampTrailer(repo, "a".repeat(64), "one"); commitFile(repo, "a.txt", "2\n", "two"); From ad7fee93a7aaf82c2bbbb2e3771e7cbc8ecc4f58 Mon Sep 17 00:00:00 2001 From: chtnnh <59027776+chtnnh@users.noreply.github.com> Date: Sun, 23 Aug 2026 19:28:06 +0400 Subject: [PATCH 3/3] fix: remove source maps from package Know-Code-Verified: ca887796f3dcd7818755f17440456bdd47abf2793d69eae56f290f4a4938b6b0 --- packages/cli/package.json | 1 + packages/cli/src/package.test.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/cli/package.json b/packages/cli/package.json index 4dc52f0..17f9e9a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -13,6 +13,7 @@ "dist", "!dist/**/*.test.*", "!dist/test-helpers.*", + "!dist/**/*.map", "hooks", "README.md", "LICENSE" diff --git a/packages/cli/src/package.test.ts b/packages/cli/src/package.test.ts index 6f65907..9a6513d 100644 --- a/packages/cli/src/package.test.ts +++ b/packages/cli/src/package.test.ts @@ -23,6 +23,7 @@ describe("published package contents", () => { 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"))); });