diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e1f5a40..f7d3d2d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -98,7 +98,7 @@ jobs: run: npm run test:templates peer-floor: - name: Packed Askr peer floor (0.0.87) + name: Packed Askr peer floor (0.0.88) runs-on: ubuntu-latest timeout-minutes: 15 steps: diff --git a/docs/superpowers/plans/2026-07-30-hardcoded-theme-token-rule.md b/docs/superpowers/plans/2026-07-30-hardcoded-theme-token-rule.md index 608becd..b20c3b7 100644 --- a/docs/superpowers/plans/2026-07-30-hardcoded-theme-token-rule.md +++ b/docs/superpowers/plans/2026-07-30-hardcoded-theme-token-rule.md @@ -13,19 +13,19 @@ ## File Map - Modify `src/analyze/rules.ts`: define literal-segment extraction, implement the rule, and register it in `ANALYZE_RULES`. -- Modify `tests/analyze.rules.test.ts`: add focused rule behavior, exclusion, and workspace-identity regression tests. +- Modify `tests/analyze-rules.test.ts`: add focused rule behavior, exclusion, and workspace-identity regression tests. - Modify `docs/analyze.md`: document the new correctness rule and its exact exemption. ### Task 1: Detect hardcoded theme-token literals **Files:** -- Modify: `tests/analyze.rules.test.ts` in the `describe("analyzer rules")` block +- Modify: `tests/analyze-rules.test.ts` in the `describe("analyzer rules")` block - Modify: `src/analyze/rules.ts` near the shared `visit` helper and before `frameworkConfigRule` - [ ] **Step 1: Write the failing literal-detection test** -Add this test to `tests/analyze.rules.test.ts`: +Add this test to `tests/analyze-rules.test.ts`: ```ts it("reports hardcoded theme tokens in runtime literal segments", async () => { @@ -64,7 +64,7 @@ comment and a non-literal identifier. Run: ```bash -rtk npm test -- tests/analyze.rules.test.ts -t "reports hardcoded theme tokens in runtime literal segments" +rtk npm test -- tests/analyze-rules.test.ts -t "reports hardcoded theme tokens in runtime literal segments" ``` Expected: FAIL because `found` has length `0` instead of `5`. @@ -135,7 +135,7 @@ both through its `TemplateExpression` and as a child node. Run: ```bash -rtk npm test -- tests/analyze.rules.test.ts -t "reports hardcoded theme tokens in runtime literal segments" +rtk npm test -- tests/analyze-rules.test.ts -t "reports hardcoded theme tokens in runtime literal segments" ``` Expected: PASS with five diagnostics from `src/tokens.tsx`. @@ -145,7 +145,7 @@ Expected: PASS with five diagnostics from `src/tokens.tsx`. Run: ```bash -rtk npm test -- tests/analyze.rules.test.ts +rtk npm test -- tests/analyze-rules.test.ts ``` Expected: PASS; existing analyzer rules remain unchanged. @@ -153,7 +153,7 @@ Expected: PASS; existing analyzer rules remain unchanged. - [ ] **Step 6: Commit the detection behavior** ```bash -rtk git add src/analyze/rules.ts tests/analyze.rules.test.ts +rtk git add src/analyze/rules.ts tests/analyze-rules.test.ts rtk git commit -m "feat(analyze): report hardcoded theme tokens" ``` @@ -161,12 +161,12 @@ rtk git commit -m "feat(analyze): report hardcoded theme tokens" **Files:** -- Modify: `tests/analyze.rules.test.ts` in the `describe("analyzer rules")` block +- Modify: `tests/analyze-rules.test.ts` in the `describe("analyzer rules")` block - Modify: `src/analyze/rules.ts` in `hardcodedThemeTokenRule.analyze` - [ ] **Step 1: Write the failing exemption and exclusion test** -Add this test to `tests/analyze.rules.test.ts`: +Add this test to `tests/analyze-rules.test.ts`: ```ts it("honors theme-token exclusions and exempts only the exact theme owner", async () => { @@ -209,7 +209,7 @@ it("honors theme-token exclusions and exempts only the exact theme owner", async Run: ```bash -rtk npm test -- tests/analyze.rules.test.ts -t "honors theme-token exclusions and exempts only the exact theme owner" +rtk npm test -- tests/analyze-rules.test.ts -t "honors theme-token exclusions and exempts only the exact theme owner" ``` Expected: FAIL because `@askrjs/themes` still receives one diagnostic. The @@ -236,7 +236,7 @@ manifest-derived workspace name owns the token namespace. Run: ```bash -rtk npm test -- tests/analyze.rules.test.ts -t "honors theme-token exclusions and exempts only the exact theme owner" +rtk npm test -- tests/analyze-rules.test.ts -t "honors theme-token exclusions and exempts only the exact theme owner" ``` Expected: PASS: one included-file diagnostic, no owner diagnostic, and one @@ -247,7 +247,7 @@ lookalike-package diagnostic. Run: ```bash -rtk npm test -- tests/analyze.rules.test.ts tests/analyze.cli.test.ts +rtk npm test -- tests/analyze-rules.test.ts tests/analyze-cli.test.ts ``` Expected: PASS. @@ -255,7 +255,7 @@ Expected: PASS. - [ ] **Step 6: Commit the exemption behavior** ```bash -rtk git add src/analyze/rules.ts tests/analyze.rules.test.ts +rtk git add src/analyze/rules.ts tests/analyze-rules.test.ts rtk git commit -m "test(analyze): cover theme token rule boundaries" ``` @@ -302,7 +302,7 @@ package dry-run all pass. - [ ] **Step 4: Commit documentation or formatter changes** ```bash -rtk git add docs/analyze.md src/analyze/rules.ts tests/analyze.rules.test.ts +rtk git add docs/analyze.md src/analyze/rules.ts tests/analyze-rules.test.ts rtk git commit -m "docs(analyze): describe theme token diagnostics" ``` diff --git a/package-lock.json b/package-lock.json index 5b28025..ebd4385 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,22 +15,22 @@ "npm-registry-fetch": "^20.0.1", "parse5": "^8.0.1", "semver": "^7.8.5", - "tsx": "^4.23.9", + "tsx": "^4.23.11", "typescript": "npm:@typescript/typescript6@^6.0.2" }, "bin": { "askr": "dist/cli.js" }, "devDependencies": { - "@askrjs/askr": ">=0.0.87 <0.1.0", + "@askrjs/askr": ">=0.0.88 <0.1.0", "@askrjs/charts": ">=0.1.5 <0.2.0", "@askrjs/logos": ">=0.0.6 <0.1.0", "@askrjs/lucide": ">=0.0.8 <0.1.0", - "@askrjs/themes": ">=0.0.22 <0.1.0", - "@askrjs/ui": ">=0.0.25 <0.1.0", + "@askrjs/themes": ">=0.0.23 <0.1.0", + "@askrjs/ui": ">=0.0.26 <0.1.0", "@askrjs/vite": ">=0.0.12 <0.1.0", "@types/js-yaml": "^4.0.9", - "@types/node": "^26.1.2", + "@types/node": "^26.2.0", "@types/semver": "^7.8.0", "@typescript/native": "npm:typescript@^7.0.2", "@vitest/coverage-v8": "^4.1.10", @@ -42,7 +42,7 @@ "node": ">=24.0.0" }, "peerDependencies": { - "@askrjs/askr": ">=0.0.87 <0.1.0" + "@askrjs/askr": ">=0.0.88 <0.1.0" }, "peerDependenciesMeta": { "@askrjs/askr": { @@ -51,23 +51,23 @@ } }, "node_modules/@askrjs/askr": { - "version": "0.0.87", - "resolved": "https://registry.npmjs.org/@askrjs/askr/-/askr-0.0.87.tgz", - "integrity": "sha512-pfZ5s2z/MW0gHtM43dvnCqDF/0DBOYF2eeEE0B9YgBWN4Hljv89CHfpZf/d2TvHy1KIxNiR4DoAZs1g3212P4g==", + "version": "0.0.88", + "resolved": "https://registry.npmjs.org/@askrjs/askr/-/askr-0.0.88.tgz", + "integrity": "sha512-+LoZmOqTl6x3XENgKwYyXZQtgHMVw/rTitJ5VfjBmwU6EtkDz1XzZInb+fdDS+fVoobYyw1gc2PC933wodACNQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@askrjs/auth": ">=0.0.1 <0.1.0", - "@askrjs/schema": ">=0.0.1 <0.1.0" + "@askrjs/auth": ">=0.0.8 <0.1.0", + "@askrjs/schema": ">=0.0.5 <0.1.0" }, "engines": { "node": ">=24.0.0" } }, "node_modules/@askrjs/auth": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/@askrjs/auth/-/auth-0.0.7.tgz", - "integrity": "sha512-K1jjS6aznxXUt+v6gcOrtvy48P36sjt1yyhjENvVxVi3v3FEo21dg/hNJZExy+vejekmSuFyb5RSP06zQ2hBcg==", + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@askrjs/auth/-/auth-0.0.8.tgz", + "integrity": "sha512-0Lg2fvD21LIi1C8ZelC8tUZr6XBNW2DQil9HRuUqedc4TRNJqsqizH3nXB+sTyYLjbIj0lqZmdI2LBzmPsYMHA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -76,7 +76,7 @@ "xml-encryption": "5.0.0" }, "engines": { - "node": ">=22" + "node": ">=24.0.0" } }, "node_modules/@askrjs/charts": { @@ -134,13 +134,13 @@ } }, "node_modules/@askrjs/schema": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/@askrjs/schema/-/schema-0.0.4.tgz", - "integrity": "sha512-iedaDut/mf4cRqSEnZNaAaio694irI9Gr3JhOzEuclZt2NhRXg1DUD7O/t5LuIDDInLcPV6B0vbsBHoUfpMRKQ==", + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@askrjs/schema/-/schema-0.0.5.tgz", + "integrity": "sha512-E0dUDc/ceGPZBi+ShyZuutQ1o8gvYJ8H6/d8yAqPsnRCFRJfglYYTXivw8ySWDRlJrPOL01hZmuiuYQdE9NHkg==", "dev": true, "license": "Apache-2.0", "engines": { - "node": ">=18" + "node": ">=24.0.0" } }, "node_modules/@askrjs/server": { @@ -166,30 +166,30 @@ } }, "node_modules/@askrjs/themes": { - "version": "0.0.22", - "resolved": "https://registry.npmjs.org/@askrjs/themes/-/themes-0.0.22.tgz", - "integrity": "sha512-3hpFT81HgrNVFkqL9/45l9C1sAUt2Qa5tlDLCqA1ScsOG1rUfTFT3P0D9oQOInPrOA0kuYlgAofEJfhGjRUv5g==", + "version": "0.0.23", + "resolved": "https://registry.npmjs.org/@askrjs/themes/-/themes-0.0.23.tgz", + "integrity": "sha512-kD6oBcnjgucVj8duIUB9zK4m6kepHXlvLPh01nDd2Jq925ofvy43Ja0lMr2uqRwzBsDWFjCLXxou5LFkdMMOcQ==", "dev": true, "license": "Apache-2.0", "engines": { "node": ">=24.0.0" }, "peerDependencies": { - "@askrjs/askr": ">=0.0.85 <0.1.0", - "@askrjs/ui": ">=0.0.24 <0.1.0" + "@askrjs/askr": ">=0.0.87 <0.1.0", + "@askrjs/ui": ">=0.0.25 <0.1.0" } }, "node_modules/@askrjs/ui": { - "version": "0.0.25", - "resolved": "https://registry.npmjs.org/@askrjs/ui/-/ui-0.0.25.tgz", - "integrity": "sha512-cpmBsA3omTCnoIYnOTP6cex7XyIz5gAkTsdLtsEfIxKwJwHd1H8LiQ7bplFvy1Qw1JSkFF6kYuORwcxsbudBjA==", + "version": "0.0.26", + "resolved": "https://registry.npmjs.org/@askrjs/ui/-/ui-0.0.26.tgz", + "integrity": "sha512-4OV6C7YSYrLQAt+07mRPl70jZWtzhWzA5YFcZR2vnQf2Wg0K8fl/AyHfJcoDkiGy/GiJ9K2u8nTOkbQKR3Dzaw==", "dev": true, "license": "Apache-2.0", "engines": { "node": ">=24.0.0" }, "peerDependencies": { - "@askrjs/askr": ">=0.0.85 <0.1.0" + "@askrjs/askr": ">=0.0.88 <0.1.0" } }, "node_modules/@askrjs/vite": { @@ -2191,9 +2191,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "26.1.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.2.tgz", - "integrity": "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg==", + "version": "26.2.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz", + "integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==", "dev": true, "license": "MIT", "dependencies": { @@ -5121,9 +5121,9 @@ "optional": true }, "node_modules/tsx": { - "version": "4.23.10", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.10.tgz", - "integrity": "sha512-0Vb9eKU47njkxv/6B8CRZRDsxNDT/Pz+BIU+M5jw7xL3TdzAjSxlZUxu0xFL/kLpaG3sHZ0LH2wbK1T1yo7CUQ==", + "version": "4.23.11", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.11.tgz", + "integrity": "sha512-Ry2oTEUnhBdeEdWIztY8kf3/nBGnPnjMLVGL0YfdRXMORuPER5NlKmayqxtxRxwB1xBN+RivRaJfe7PM1rtiyw==", "license": "MIT", "dependencies": { "esbuild": "~0.28.0" diff --git a/package.json b/package.json index 64bab4b..e05d2e8 100644 --- a/package.json +++ b/package.json @@ -60,19 +60,19 @@ "npm-registry-fetch": "^20.0.1", "parse5": "^8.0.1", "semver": "^7.8.5", - "tsx": "^4.23.9", + "tsx": "^4.23.11", "typescript": "npm:@typescript/typescript6@^6.0.2" }, "devDependencies": { - "@askrjs/askr": ">=0.0.87 <0.1.0", + "@askrjs/askr": ">=0.0.88 <0.1.0", "@askrjs/charts": ">=0.1.5 <0.2.0", "@askrjs/logos": ">=0.0.6 <0.1.0", "@askrjs/lucide": ">=0.0.8 <0.1.0", - "@askrjs/themes": ">=0.0.22 <0.1.0", - "@askrjs/ui": ">=0.0.25 <0.1.0", + "@askrjs/themes": ">=0.0.23 <0.1.0", + "@askrjs/ui": ">=0.0.26 <0.1.0", "@askrjs/vite": ">=0.0.12 <0.1.0", "@types/js-yaml": "^4.0.9", - "@types/node": "^26.1.2", + "@types/node": "^26.2.0", "@types/semver": "^7.8.0", "@typescript/native": "npm:typescript@^7.0.2", "@vitest/coverage-v8": "^4.1.10", @@ -81,7 +81,7 @@ "vitest": "^4.1.10" }, "peerDependencies": { - "@askrjs/askr": ">=0.0.87 <0.1.0" + "@askrjs/askr": ">=0.0.88 <0.1.0" }, "peerDependenciesMeta": { "@askrjs/askr": { diff --git a/templates/spa/tests/app.test.tsx b/templates/spa/tests/app.test.tsx index 906faa7..06b6e7c 100644 --- a/templates/spa/tests/app.test.tsx +++ b/templates/spa/tests/app.test.tsx @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vite-plus/test'; describe('SPA template structure', () => { - it('documents the route-first shell and theme layers the app should keep', () => { + it('should document the route-first shell and theme layers the app should keep', () => { const structure = [ 'src/main.tsx', 'src/pages/_routes.tsx', diff --git a/templates/spa/tests/components/shared.test.tsx b/templates/spa/tests/components/shared.test.tsx index 0d1ba59..1570340 100644 --- a/templates/spa/tests/components/shared.test.tsx +++ b/templates/spa/tests/components/shared.test.tsx @@ -3,7 +3,7 @@ import MetricCard from '../../src/components/shared/metric-card'; import StatusBadge from '../../src/components/shared/status-badge'; describe('shared app components', () => { - it('exports small app-local wrappers around theme primitives', () => { + it('should export small app-local wrappers around theme primitives', () => { expect(MetricCard).toBeDefined(); expect(StatusBadge).toBeDefined(); expect(typeof MetricCard).toBe('function'); diff --git a/templates/spa/tests/resources.test.ts b/templates/spa/tests/resources.test.ts index 80349f1..e21b3e6 100644 --- a/templates/spa/tests/resources.test.ts +++ b/templates/spa/tests/resources.test.ts @@ -3,7 +3,7 @@ import { getOperationsSnapshot } from '../src/adapters/operations-client'; import { loadOperations } from '../src/features/operations/operations.query'; describe('operations data flow', () => { - it('loads the dashboard snapshot through the feature query boundary', async () => { + it('should load the dashboard snapshot through the feature query boundary', async () => { const snapshot = await loadOperations({}); expect(snapshot.metrics.length).toBeGreaterThan(0); @@ -11,7 +11,7 @@ describe('operations data flow', () => { expect(snapshot.lastEventId).toMatch(/^evt_/); }); - it('keeps cancellation owned by the adapter', async () => { + it('should keep cancellation owned by the adapter', async () => { const controller = new AbortController(); const request = getOperationsSnapshot({ signal: controller.signal }); diff --git a/templates/ssg/tests/app.test.tsx b/templates/ssg/tests/app.test.tsx index 15710c1..7dc757c 100644 --- a/templates/ssg/tests/app.test.tsx +++ b/templates/ssg/tests/app.test.tsx @@ -2,7 +2,7 @@ import { describe, expect, it } from 'vite-plus/test'; import { navItems } from '../src/app'; describe('App shell', () => { - it('exposes the workflow-focused navigation order', () => { + it('should expose the workflow-focused navigation order', () => { expect(navItems.map((item) => item.label)).toEqual([ 'Home', 'Workflow', diff --git a/templates/ssg/tests/components/counter.test.tsx b/templates/ssg/tests/components/counter.test.tsx index 8e99d19..bb0396a 100644 --- a/templates/ssg/tests/components/counter.test.tsx +++ b/templates/ssg/tests/components/counter.test.tsx @@ -3,7 +3,7 @@ import { renderToString } from '@askrjs/askr/ssr'; import Counter from '../../src/components/counter'; describe('Counter', () => { - it('renders the initial hydration check view', () => { + it('should render the initial hydration check view', () => { const html = renderToString(() => ); expect(html).toContain('Hydration check'); diff --git a/templates/ssg/tests/resources.test.ts b/templates/ssg/tests/resources.test.ts index c930fc1..9ea3cdd 100644 --- a/templates/ssg/tests/resources.test.ts +++ b/templates/ssg/tests/resources.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect } from 'vite-plus/test'; import { fetchUser } from '../src/resources/user'; describe('Resources', () => { - it('creates a resource for fetching user data', () => { + it('should create a resource for fetching user data', () => { // Resources are called during component render // Here we just verify the function is defined expect(fetchUser).toBeDefined(); diff --git a/templates/ssg/tests/ssg-config.test.ts b/templates/ssg/tests/ssg-config.test.ts index 5ece5a6..5b807f5 100644 --- a/templates/ssg/tests/ssg-config.test.ts +++ b/templates/ssg/tests/ssg-config.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from 'vite-plus/test'; import { staticConfig } from '../ssg.config'; describe('SSG config', () => { - it('keeps the sample route tree explicit', () => { + it('should keep the sample route tree explicit', () => { expect( staticConfig.registry.manifest.records.map((route) => route.path).sort() ).toEqual(['/', '/content', '/preview', '/workflow'].sort()); diff --git a/templates/ssr/tests/app.test.tsx b/templates/ssr/tests/app.test.tsx index ba72303..402449f 100644 --- a/templates/ssr/tests/app.test.tsx +++ b/templates/ssr/tests/app.test.tsx @@ -1,7 +1,7 @@ import { describe, it, expect } from 'vite-plus/test'; describe('App Component', () => { - it('renders layout with navigation', () => { + it('should render layout with navigation', () => { const app = (
diff --git a/templates/ssr/tests/components/counter.test.tsx b/templates/ssr/tests/components/counter.test.tsx index a7a7971..567e251 100644 --- a/templates/ssr/tests/components/counter.test.tsx +++ b/templates/ssr/tests/components/counter.test.tsx @@ -3,7 +3,7 @@ import { renderToString } from '@askrjs/askr/ssr'; import Counter from '../../src/components/counter'; describe('Counter', () => { - it('renders its initial state during SSR', () => { + it('should render its initial state during SSR', () => { const html = renderToString(() => ); expect(html).toContain('counter-value'); diff --git a/templates/ssr/tests/resources.test.ts b/templates/ssr/tests/resources.test.ts index fbb8aee..93fb6df 100644 --- a/templates/ssr/tests/resources.test.ts +++ b/templates/ssr/tests/resources.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect } from 'vite-plus/test'; import { fetchUser } from '../src/resources/user'; describe('SSR Resources', () => { - it('creates a resource for fetching user data', () => { + it('should create a resource for fetching user data', () => { // Resources are called during component render // Here we just verify the function is defined expect(fetchUser).toBeDefined(); diff --git a/templates/startkit/tests/app.test.tsx b/templates/startkit/tests/app.test.tsx index 74e3d18..4945cf2 100644 --- a/templates/startkit/tests/app.test.tsx +++ b/templates/startkit/tests/app.test.tsx @@ -1,7 +1,7 @@ import { describe, it, expect } from 'vite-plus/test'; describe('App Component', () => { - it('exposes the current starter route set', () => { + it('should expose the current starter route set', () => { const routes = [ '/', '/login', @@ -16,14 +16,14 @@ describe('App Component', () => { expect(routes).toContain('/login'); }); - it('uses the updated primary nav labels', () => { + it('should use the updated primary nav labels', () => { const navLabels = ['Dashboard', 'Accounts', 'Settings']; expect(navLabels).toContain('Accounts'); expect(navLabels).toContain('Settings'); }); - it('documents the starter appearance presets', () => { + it('should document the starter appearance presets', () => { const appearanceModes = ['Default', 'Harbor', 'Ink']; expect(appearanceModes).toContain('Harbor'); diff --git a/tests/analyze.cli.test.ts b/tests/analyze-cli.test.ts similarity index 92% rename from tests/analyze.cli.test.ts rename to tests/analyze-cli.test.ts index 12b23a9..a0ef55b 100644 --- a/tests/analyze.cli.test.ts +++ b/tests/analyze-cli.test.ts @@ -65,7 +65,7 @@ afterEach(async () => { }); describe("analyze CLI", () => { - it("parses repeated workspace filters and command options", () => { + it("should parse repeated workspace filters and command options", () => { expect( parseAnalyzeArgs([ "--cwd", @@ -86,7 +86,7 @@ describe("analyze CLI", () => { expect(() => parseAnalyzeArgs(["--unknown"])).toThrow(/unknown option/i); }); - it("scans all workspaces by default and filters repeated selections deterministically", async () => { + it("should scan all workspaces by default and filter repeated selections deterministically", async () => { const root = await workspaceFixture(); const all = await runAnalysis({ cwd: root, workspacePatterns: [], check: true }); const selected = await runAnalysis({ cwd: root, workspacePatterns: ["b"], check: true }); @@ -98,7 +98,7 @@ describe("analyze CLI", () => { expect(selected.diagnostics.map((entry) => entry.workspace)).toEqual(["b"]); }); - it("emits deterministic JSON and a blocking exit code", async () => { + it("should emit deterministic JSON and a blocking exit code", async () => { const root = await workspaceFixture(); const output = io(); expect(await runAnalyzeCli(["--cwd", root, "--json", "--check"], output.value)).toBe(1); @@ -112,7 +112,7 @@ describe("analyze CLI", () => { expect(output.logs[0]).toBe(JSON.stringify(report)); }); - it("dispatches through the unified CLI and prints human diagnostics", async () => { + it("should dispatch through the unified CLI and print human diagnostics", async () => { const root = await workspaceFixture(); const output = io(); expect( @@ -122,7 +122,7 @@ describe("analyze CLI", () => { expect(output.logs.at(-1)).toMatch(/Analyzed 1 workspace/); }); - it("keeps check mode immutable and applies a safe config fix transactionally by default", async () => { + it("should keep check mode immutable and apply a safe config fix transactionally by default", async () => { const root = await fs.mkdtemp(path.join(os.tmpdir(), "askr-analyze-fix-")); roots.push(root); await fs.mkdir(path.join(root, "src")); @@ -168,7 +168,7 @@ describe("analyze CLI", () => { }); }); - it("applies safe route path fixes but leaves semantic collection findings unresolved", async () => { + it("should apply safe route path fixes but leaves semantic collection findings unresolved", async () => { const root = await fs.mkdtemp(path.join(os.tmpdir(), "askr-analyze-semantic-")); roots.push(root); await fs.mkdir(path.join(root, "src")); @@ -216,7 +216,7 @@ describe("analyze CLI", () => { expect(await fs.readFile(filePath, "utf8")).toContain('route("/users/{id}", Page)'); }); - it("does not mutate files when transactional writing fails", async () => { + it("should not mutate files when transactional writing fails", async () => { const root = await fs.mkdtemp(path.join(os.tmpdir(), "askr-analyze-rollback-")); roots.push(root); await fs.mkdir(path.join(root, "src")); diff --git a/tests/analyze.rules.test.ts b/tests/analyze-rules.test.ts similarity index 95% rename from tests/analyze.rules.test.ts rename to tests/analyze-rules.test.ts index bacce8a..4100f0a 100644 --- a/tests/analyze.rules.test.ts +++ b/tests/analyze-rules.test.ts @@ -55,7 +55,7 @@ afterEach(async () => { }); describe("analyzer rules", () => { - it("recognizes canonical aliased and namespace imports without matching unrelated functions", async () => { + it("should recognize canonical aliased and namespace imports without matching unrelated functions", async () => { const root = await fixture({ "src/page.tsx": ` import { state as cell } from "@askrjs/askr"; @@ -73,7 +73,7 @@ describe("analyzer rules", () => { expect(found.every((entry) => entry.file === "src/page.tsx")).toBe(true); }); - it("reports unstable render calls and invalid state reads and writes", async () => { + it("should report unstable render calls and invalid state reads and writes", async () => { const root = await fixture({ "src/page.tsx": ` import { state, derive } from "@askrjs/askr"; @@ -96,7 +96,7 @@ describe("analyzer rules", () => { }); }); - it("reports state getters in value positions while preserving declaration and call syntax", async () => { + it("should report state getters in value positions while preserving declaration and call syntax", async () => { const root = await fixture({ "src/page.tsx": ` import { state } from "@askrjs/askr"; @@ -128,7 +128,7 @@ describe("analyzer rules", () => { expect(found.filter((entry) => entry.ruleId === "askr/state-access")).toHaveLength(7); }); - it("validates statically known For key strategies without rejecting dynamic values", async () => { + it("should validate statically known For key strategies without rejecting dynamic values", async () => { const root = await fixture({ "src/page.tsx": ` import { For } from "@askrjs/askr"; @@ -165,7 +165,7 @@ describe("analyzer rules", () => { ); }); - it("checks resource cancellation and stable dependencies while accepting forwarded signals", async () => { + it("should check resource cancellation and stable dependencies while accepting forwarded signals", async () => { const root = await fixture({ "src/page.tsx": ` import { resource } from "@askrjs/askr/resources"; @@ -185,7 +185,7 @@ describe("analyzer rules", () => { expect(found.filter((entry) => entry.ruleId === "askr/stable-dependencies")).toHaveLength(1); }); - it("checks For contracts, positional keys, and only reactive JSX map calls", async () => { + it("should check For contracts, positional keys, and only reactive JSX map calls", async () => { const root = await fixture({ "src/page.tsx": ` import { For, state } from "@askrjs/askr"; @@ -209,7 +209,7 @@ describe("analyzer rules", () => { expect(found.filter((entry) => entry.ruleId === "askr/stable-key")).toHaveLength(1); }); - it("reports async components, bad boot wiring, and SSR browser globals", async () => { + it("should report async components, bad boot wiring, and SSR browser globals", async () => { const root = await fixture({ "src/client.tsx": ` import { createSPA } from "@askrjs/askr/boot"; @@ -231,7 +231,7 @@ describe("analyzer rules", () => { expect(found.filter((entry) => entry.ruleId === "askr/ssr-browser-global")).toHaveLength(1); }); - it("checks route registry ownership, route syntax, controls, and data cancellation", async () => { + it("should check route registry ownership, route syntax, controls, and data cancellation", async () => { const root = await fixture({ "src/routes.tsx": ` import { Case, Match, Show } from "@askrjs/askr"; @@ -450,7 +450,7 @@ describe("analyzer rules", () => { expect(found).toHaveLength(0); }); - it("reports state writes during render but accepts event-handler writes", async () => { + it("should report state writes during render but accepts event-handler writes", async () => { const root = await fixture({ "src/page.tsx": ` import { state } from "@askrjs/askr"; @@ -466,7 +466,7 @@ describe("analyzer rules", () => { expect(found.filter((entry) => entry.ruleId === "askr/state-render-write")).toHaveLength(1); }); - it("reports malformed source and analyzes JavaScript without a tsconfig", async () => { + it("should report malformed source and analyze JavaScript without a tsconfig", async () => { const root = await fixture( { "src/broken.ts": "export function broken( {", @@ -484,7 +484,7 @@ describe("analyzer rules", () => { ).toBe(true); }); - it("keeps dependency declaration graphs out of analysis programs", async () => { + it("should keep dependency declaration graphs out of analysis programs", async () => { const root = await fixture({ "src/page.ts": 'import type { Huge } from "huge-package"; export type Page = Huge;', "node_modules/huge-package/package.json": JSON.stringify({ @@ -587,7 +587,7 @@ describe("analyzer rules", () => { ).toEqual([expect.objectContaining({ file: "src/theme.ts" })]); }); - it("honors exclusions and rule severity configuration", async () => { + it("should honor exclusions and rule severity configuration", async () => { const root = await fixture( { "src/page.ts": 'import { state } from "@askrjs/askr"; state(0);', @@ -612,7 +612,7 @@ describe("analyzer rules", () => { ]); }); - it("reports lifecycle, stream, data, invalidation, and island contract violations", async () => { + it("should report lifecycle, stream, data, invalidation, and island contract violations", async () => { const root = await fixture({ "src/contracts.tsx": ` import { on, stream as live, task, timer } from "@askrjs/askr/resources"; @@ -655,7 +655,7 @@ describe("analyzer rules", () => { ).toBe(true); }); - it("reports mixed execution models, action defects, discarded submits, and render allocations", async () => { + it("should report mixed execution models, action defects, discarded submits, and render allocations", async () => { const root = await fixture({ "src/app.tsx": ` import { state } from "@askrjs/askr"; @@ -694,7 +694,7 @@ describe("analyzer rules", () => { ).toBe(true); }); - it("accepts valid contracts and ignores similarly named unrelated APIs", async () => { + it("should accept valid contracts and ignore similarly named unrelated APIs", async () => { const root = await fixture({ "src/page.tsx": ` import { ActionForm, action, defineAction } from "@askrjs/askr/actions"; @@ -747,7 +747,7 @@ describe("analyzer rules", () => { expect(found.filter((entry) => newRules.has(entry.ruleId))).toEqual([]); }); - it("covers the static-analysis backlog with conservative positive and negative cases", async () => { + it("should cover the static-analysis backlog with conservative positive and negative cases", async () => { const root = await fixture({ "src/backlog.tsx": ` import { Case, For, Match, Show, defineScope, state } from "@askrjs/askr"; diff --git a/tests/changelog.test.ts b/tests/changelog.test.ts index 9425d59..4b3ac10 100644 --- a/tests/changelog.test.ts +++ b/tests/changelog.test.ts @@ -1,7 +1,7 @@ import { readFile } from "node:fs/promises"; import { expect, test } from "vitest"; -test("changelog has unreleased and current-version sections", async () => { +test("should ensure changelog has unreleased and current-version sections", async () => { const [manifestSource, changelog] = await Promise.all([ readFile(new URL("../package.json", import.meta.url), "utf8"), readFile(new URL("../CHANGELOG.md", import.meta.url), "utf8"), diff --git a/tests/cli.smoke.test.ts b/tests/cli-smoke.test.ts similarity index 93% rename from tests/cli.smoke.test.ts rename to tests/cli-smoke.test.ts index daeafba..b5bec02 100644 --- a/tests/cli.smoke.test.ts +++ b/tests/cli-smoke.test.ts @@ -112,7 +112,7 @@ function getSkillReviewDocEntries( })); } -test("runCli prints top-level help", async () => { +test("should ensure runCli prints top-level help", async () => { const { io, logs, errors } = createIo(); const code = await runCli(["--help"], io); @@ -127,7 +127,7 @@ test("runCli prints top-level help", async () => { expect(logs.join("\n")).toMatch(/openapi/); }); -test("package surface ships project templates for installed create commands", async () => { +test("should ensure package surface ships project templates for installed create commands", async () => { const manifest = JSON.parse( await fs.readFile(new URL("../package.json", import.meta.url), "utf8"), ) as { files: string[] }; @@ -143,7 +143,7 @@ test("package surface ships project templates for installed create commands", as } }); -test("runCli prints version for short and long flags", async () => { +test("should ensure runCli prints version for short and long flags", async () => { const packageJson = JSON.parse( await fs.readFile(new URL("../package.json", import.meta.url), "utf8"), ) as { @@ -162,7 +162,7 @@ test("runCli prints version for short and long flags", async () => { expect(shortFlag.logs).toEqual([packageJson.version]); }); -test("package exports only the canonical askr command", async () => { +test("should ensure package exports only the canonical askr command", async () => { const packageJson = JSON.parse( await fs.readFile(new URL("../package.json", import.meta.url), "utf8"), ) as { @@ -185,7 +185,7 @@ test("should ship the TypeScript 6 analyzer API given the TypeScript 7 compiler expect(packageJson.devDependencies?.typescript).toBeUndefined(); }); -test("public docs and templates use the clean-break scope vocabulary", async () => { +test("should ensure public docs and templates use the clean-break scope vocabulary", async () => { const root = fileURLToPath(new URL("..", import.meta.url)); const files = [ ...(await sourceFiles(path.join(root, "docs"))), @@ -212,7 +212,7 @@ test("public docs and templates use the clean-break scope vocabulary", async () expect(violations).toEqual([]); }); -test("runCreateCli defaults to startkit when template is omitted", async () => { +test("should ensure runCreateCli defaults to startkit when template is omitted", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-")); const previousCwd = process.cwd(); @@ -253,7 +253,7 @@ test("runCreateCli defaults to startkit when template is omitted", async () => { } }, 15_000); -test("runCreateCli rejects unsafe names, unknown options, and extra positional arguments", async () => { +test("should ensure runCreateCli rejects unsafe names, unknown options, and extra positional arguments", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-create-input-")); const previousCwd = process.cwd(); try { @@ -273,7 +273,7 @@ test("runCreateCli rejects unsafe names, unknown options, and extra positional a } }); -test("runCreateCli supports an explicit output directory without deriving it from the package name", async () => { +test("should ensure runCreateCli supports an explicit output directory without deriving it from the package name", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-create-dir-")); const target = path.join(tempRoot, "nested", "project"); try { @@ -295,7 +295,7 @@ test("runCreateCli supports an explicit output directory without deriving it fro } }); -test("runCreateCli preserves a file that occupies the requested target", async () => { +test("should ensure runCreateCli preserves a file that occupies the requested target", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-create-file-")); const previousCwd = process.cwd(); try { @@ -311,7 +311,7 @@ test("runCreateCli preserves a file that occupies the requested target", async ( } }); -test("runCreateCli scaffolds SPA with the route-first themed app shell", async () => { +test("should ensure runCreateCli scaffolds SPA with the route-first themed app shell", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-")); const previousCwd = process.cwd(); @@ -440,7 +440,7 @@ test("runCreateCli scaffolds SPA with the route-first themed app shell", async ( } }); -test("runCreateCli scaffolds SSG with shared route registration and current builder hints", async () => { +test("should ensure runCreateCli scaffolds SSG with shared route registration and current builder hints", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-")); const previousCwd = process.cwd(); @@ -499,7 +499,7 @@ test("runCreateCli scaffolds SSG with shared route registration and current buil } }, 15000); -test("runCreateCli derives a prompt-aware builder blueprint and installs skills", async () => { +test("should ensure runCreateCli derives a prompt-aware builder blueprint and installs skills", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-")); const previousCwd = process.cwd(); @@ -562,7 +562,7 @@ test("runCreateCli derives a prompt-aware builder blueprint and installs skills" } }); -test("runCreateCli scaffolds a function-first full-stack project", async () => { +test("should ensure runCreateCli scaffolds a function-first full-stack project", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-")); const previousCwd = process.cwd(); @@ -627,7 +627,7 @@ test("runCreateCli scaffolds a function-first full-stack project", async () => { } }); -test("template package floors require the clean-break scope vocabulary", async () => { +test("should ensure template package floors require the clean-break scope vocabulary", async () => { for (const template of ["full-stack", "spa", "ssr", "ssg", "startkit"]) { const manifest = JSON.parse( await fs.readFile(new URL(`../templates/${template}/package.json`, import.meta.url), "utf8"), @@ -646,7 +646,7 @@ test("template package floors require the clean-break scope vocabulary", async ( } }); -test("runCreateCli can skip bundled skills installation", async () => { +test("should ensure runCreateCli can skip bundled skills installation", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-")); const previousCwd = process.cwd(); @@ -682,7 +682,7 @@ test("runCreateCli can skip bundled skills installation", async () => { } }); -test("runAddCli scaffolds a page and registers the app route", async () => { +test("should ensure runAddCli scaffolds a page and registers the app route", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-add-")); const previousCwd = process.cwd(); @@ -718,7 +718,7 @@ test("runAddCli scaffolds a page and registers the app route", async () => { } }); -test("runAddCli rolls back page registration given a replacement failure", async () => { +test("should ensure runAddCli rolls back page registration given a replacement failure", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-add-rollback-")); const previousCwd = process.cwd(); try { @@ -753,7 +753,7 @@ test("runAddCli rolls back page registration given a replacement failure", async } }); -test("runCli routes add page through the top-level command", async () => { +test("should ensure runCli routes add page through the top-level command", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-add-")); const previousCwd = process.cwd(); @@ -801,7 +801,7 @@ test("runCli routes add page through the top-level command", async () => { } }); -test("runAddCli generates a browser-safe action and server registration", async () => { +test("should ensure runAddCli generates a browser-safe action and server registration", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-add-")); const previousCwd = process.cwd(); @@ -868,7 +868,7 @@ test("runAddCli generates a browser-safe action and server registration", async } }); -test("runSsgCli prints help without requiring config", async () => { +test("should ensure runSsgCli prints help without requiring config", async () => { const { io, logs, errors } = createIo(); const code = await runSsgCli(["--help"], undefined, io); @@ -877,7 +877,7 @@ test("runSsgCli prints help without requiring config", async () => { expect(logs.join("\n")).toMatch(/askr ssg - Static Site Generation for Askr/); }); -test("runSsgCli rejects unknown, missing, and invalid option values", async () => { +test("should ensure runSsgCli rejects unknown, missing, and invalid option values", async () => { for (const args of [["--config"], ["--unknown"], ["--workers", "garbage"], ["--workers", "0"]]) { const { io, errors } = createIo(); expect(await runSsgCli(args, undefined, io)).toBe(1); @@ -885,7 +885,7 @@ test("runSsgCli rejects unknown, missing, and invalid option values", async () = } }); -test("runSsgCli preserves live output when sitemap metadata fails", async () => { +test("should ensure runSsgCli preserves live output when sitemap metadata fails", async () => { const root = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-ssg-atomic-")); const output = path.join(root, "dist"); await fs.mkdir(output); @@ -929,7 +929,7 @@ test("runSsgCli preserves live output when sitemap metadata fails", async () => } }); -test("runSsgCli preserves a file that occupies the output path", async () => { +test("should ensure runSsgCli preserves a file that occupies the output path", async () => { const root = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-ssg-file-")); await fs.writeFile(path.join(root, "dist"), "keep"); try { @@ -946,7 +946,7 @@ test("runSsgCli preserves a file that occupies the output path", async () => { } }); -test("runSsgCli requires a canonical site URL unless sitemap generation is disabled", async () => { +test("should ensure runSsgCli requires a canonical site URL unless sitemap generation is disabled", async () => { const generate = async () => { throw new Error("generation should not start"); }; @@ -969,7 +969,7 @@ test("runSsgCli requires a canonical site URL unless sitemap generation is disab ); }); -test("runSsgCli loads TypeScript configs without an external loader", async () => { +test("should ensure runSsgCli loads TypeScript configs without an external loader", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-ssg-")); const configPath = path.join(tempRoot, "ssg.config.ts"); await fs.writeFile( @@ -1008,7 +1008,7 @@ test("runSsgCli loads TypeScript configs without an external loader", async () = } }); -test("askr ssg executes TSX route modules with the project JSX runtime", async () => { +test("should ensure askr ssg executes TSX route modules with the project JSX runtime", async () => { const tempRoot = await fs.mkdtemp(path.join(process.cwd(), ".tmp-askr-cli-ssg-")); const configPath = path.join(tempRoot, "ssg.config.ts"); const outputDir = path.join(tempRoot, "dist"); @@ -1062,7 +1062,7 @@ test("askr ssg executes TSX route modules with the project JSX runtime", async ( } }); -test("runSsgCli forwards complete registry-based static config", async () => { +test("should ensure runSsgCli forwards complete registry-based static config", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-ssg-options-")); const registry = { records: [] }; const document = () => ""; @@ -1121,7 +1121,7 @@ test("runSsgCli forwards complete registry-based static config", async () => { } }); -test("runSsgCli preserves the previous full output when sitemap generation fails", async () => { +test("should ensure runSsgCli preserves the previous full output when sitemap generation fails", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-sitemap-atomic-")); const outputDir = path.join(tempRoot, "dist"); await fs.mkdir(outputDir); @@ -1173,7 +1173,7 @@ test("runSsgCli preserves the previous full output when sitemap generation fails } }); -test("runSsgCli writes the default report before publishing staged output", async () => { +test("should ensure runSsgCli writes the default report before publishing staged output", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-output-report-")); const outputDir = path.join(tempRoot, "dist"); const { io, errors } = createIo(); @@ -1217,7 +1217,7 @@ test("runSsgCli writes the default report before publishing staged output", asyn } }); -test("runSsgCli preserves live output when an output budget fails", async () => { +test("should ensure runSsgCli preserves live output when an output budget fails", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-output-budget-")); const outputDir = path.join(tempRoot, "dist"); await fs.mkdir(outputDir); @@ -1265,7 +1265,7 @@ test("runSsgCli preserves live output when an output budget fails", async () => } }); -test("runSsgCli reports the complete staged result for incremental builds", async () => { +test("should ensure runSsgCli reports the complete staged result for incremental builds", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-output-incremental-")); const outputDir = path.join(tempRoot, "dist"); await fs.mkdir(path.join(outputDir, "old"), { recursive: true }); @@ -1316,7 +1316,7 @@ test("runSsgCli reports the complete staged result for incremental builds", asyn } }); -test("runSsgCli removes a retained output report when reporting is disabled", async () => { +test("should ensure runSsgCli removes a retained output report when reporting is disabled", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-output-disabled-")); const outputDir = path.join(tempRoot, "dist"); await fs.mkdir(path.join(outputDir, ".askr"), { recursive: true }); @@ -1358,7 +1358,7 @@ test("runSsgCli removes a retained output report when reporting is disabled", as } }); -test("runSkillsCli lists bundled skills", async () => { +test("should ensure runSkillsCli lists bundled skills", async () => { const { io, logs, errors } = createIo(); const code = await runSkillsCli(["list"], io); @@ -1375,7 +1375,7 @@ test("runSkillsCli lists bundled skills", async () => { expect(logs).toContain("askr-testing-determinism"); }); -test("runSkillsCli rejects missing cwd and unknown options before synchronization", async () => { +test("should ensure runSkillsCli rejects missing cwd and unknown options before synchronization", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-skills-input-")); const previousCwd = process.cwd(); try { @@ -1390,7 +1390,7 @@ test("runSkillsCli rejects missing cwd and unknown options before synchronizatio } }); -test("runSkillsCli bounds review input size", async () => { +test("should ensure runSkillsCli bounds review input size", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-skills-size-")); try { await fs.writeFile(path.join(tempRoot, "oversized.ts"), "x".repeat(2 * 1024 * 1024 + 1)); @@ -1402,7 +1402,7 @@ test("runSkillsCli bounds review input size", async () => { } }); -test("runSkillsCli preserves a file that occupies the skills target", async () => { +test("should ensure runSkillsCli preserves a file that occupies the skills target", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-skills-file-")); try { await fs.writeFile(path.join(tempRoot, "skills"), "keep"); @@ -1413,7 +1413,7 @@ test("runSkillsCli preserves a file that occupies the skills target", async () = } }); -test("runSkillsCli lists skill review prompts", async () => { +test("should ensure runSkillsCli lists skill review prompts", async () => { const { io, logs, errors } = createIo(); const code = await runSkillsCli(["review", "list"], io); @@ -1423,7 +1423,7 @@ test("runSkillsCli lists skill review prompts", async () => { expect(logs.join("\n")).toMatch(/reject-react-query/); }); -test("skill review prompts only reference bundled skills", async () => { +test("should ensure skill review prompts only reference bundled skills", async () => { const prompts = listSkillReviewPrompts(); const bundledSkillEntries = await fs.readdir(new URL("../skills/", import.meta.url), { withFileTypes: true, @@ -1441,7 +1441,7 @@ test("skill review prompts only reference bundled skills", async () => { } }); -test("skills docs stay aligned with bundled skill folders", async () => { +test("should ensure skills docs stay aligned with bundled skill folders", async () => { const skillsDoc = await fs.readFile(new URL("../docs/skills.md", import.meta.url), "utf8"); const documentedSkills = [ ...getBacktickedBulletItems(getMarkdownSection(skillsDoc, "Foundation sequence")), @@ -1460,7 +1460,7 @@ test("skills docs stay aligned with bundled skill folders", async () => { expect(documentedSkills).toEqual(bundledSkillNames); }); -test("workflow docs stay aligned with the layered skill system", async () => { +test("should ensure workflow docs stay aligned with the layered skill system", async () => { const workflowsDoc = await fs.readFile(new URL("../docs/workflows.md", import.meta.url), "utf8"); const skillsDoc = await fs.readFile(new URL("../docs/skills.md", import.meta.url), "utf8"); const bundledSkillEntries = await fs.readdir(new URL("../skills/", import.meta.url), { @@ -1500,7 +1500,7 @@ test("workflow docs stay aligned with the layered skill system", async () => { } }); -test("skill review prompt docs stay aligned with the prompt registry", async () => { +test("should ensure skill review prompt docs stay aligned with the prompt registry", async () => { const prompts = listSkillReviewPrompts(); const promptDoc = await fs.readFile( new URL("../docs/skill-review-prompts.md", import.meta.url), @@ -1517,7 +1517,7 @@ test("skill review prompt docs stay aligned with the prompt registry", async () expect(documentedPrompts).toEqual(registryPrompts); }); -test("runSkillsCli reviews a generated candidate with JSON output", async () => { +test("should ensure runSkillsCli reviews a generated candidate with JSON output", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-review-")); try { @@ -1571,7 +1571,7 @@ test("runSkillsCli reviews a generated candidate with JSON output", async () => } }); -test("runSkillsCli passes routing-layouts review for an idiomatic route tree", async () => { +test("should ensure runSkillsCli passes routing-layouts review for an idiomatic route tree", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-review-")); try { @@ -1624,7 +1624,7 @@ test("runSkillsCli passes routing-layouts review for an idiomatic route tree", a } }); -test("runSkillsCli passes auth-authorization review for route-owned access policy", async () => { +test("should ensure runSkillsCli passes auth-authorization review for route-owned access policy", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-review-")); try { @@ -1675,7 +1675,7 @@ test("runSkillsCli passes auth-authorization review for route-owned access polic } }); -test("runSkillsCli passes shared-data-consistency review for truthful query ownership", async () => { +test("should ensure runSkillsCli passes shared-data-consistency review for truthful query ownership", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-review-")); try { @@ -1722,7 +1722,7 @@ test("runSkillsCli passes shared-data-consistency review for truthful query owne } }); -test("runSkillsCli passes crud-forms review for explicit form and error state", async () => { +test("should ensure runSkillsCli passes crud-forms review for explicit form and error state", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-review-")); try { @@ -1771,7 +1771,7 @@ test("runSkillsCli passes crud-forms review for explicit form and error state", } }); -test("runSkillsCli passes realtime review for bounded reconnecting streams", async () => { +test("should ensure runSkillsCli passes realtime review for bounded reconnecting streams", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-review-")); try { @@ -1814,7 +1814,7 @@ test("runSkillsCli passes realtime review for bounded reconnecting streams", asy } }); -test("runSkillsCli passes agent-workflow-ui review for lifecycle-driven run screens", async () => { +test("should ensure runSkillsCli passes agent-workflow-ui review for lifecycle-driven run screens", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-review-")); try { @@ -1865,7 +1865,7 @@ test("runSkillsCli passes agent-workflow-ui review for lifecycle-driven run scre } }); -test("runSkillsCli passes theming-ui review for token-based theme primitives", async () => { +test("should ensure runSkillsCli passes theming-ui review for token-based theme primitives", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-review-")); try { @@ -1911,7 +1911,7 @@ test("runSkillsCli passes theming-ui review for token-based theme primitives", a } }); -test("runSkillsCli passes ssr-ssg review for environment-safe static routes", async () => { +test("should ensure runSkillsCli passes ssr-ssg review for environment-safe static routes", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-review-")); try { @@ -1950,7 +1950,7 @@ test("runSkillsCli passes ssr-ssg review for environment-safe static routes", as } }); -test("runSkillsCli fails a negative review when React defaults appear", async () => { +test("should ensure runSkillsCli fails a negative review when React defaults appear", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-review-")); try { @@ -1984,7 +1984,7 @@ test("runSkillsCli fails a negative review when React defaults appear", async () } }); -test("runSkillsCli fails a negative review when app-local primitive clones appear", async () => { +test("should ensure runSkillsCli fails a negative review when app-local primitive clones appear", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-review-")); try { @@ -2020,7 +2020,7 @@ test("runSkillsCli fails a negative review when app-local primitive clones appea } }); -test("runSkillsCli fails a negative review when one spinner models all async states", async () => { +test("should ensure runSkillsCli fails a negative review when one spinner models all async states", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-review-")); try { @@ -2048,7 +2048,7 @@ test("runSkillsCli fails a negative review when one spinner models all async sta } }); -test("runSkillsCli fails a negative review when parallel architecture drift appears", async () => { +test("should ensure runSkillsCli fails a negative review when parallel architecture drift appears", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-review-")); try { @@ -2112,7 +2112,7 @@ test("runSkillsCli fails a negative review when parallel architecture drift appe } }); -test("runSkillsCli installs bundled skills into project skills", async () => { +test("should ensure runSkillsCli installs bundled skills into project skills", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-skills-")); try { @@ -2139,7 +2139,7 @@ test("runSkillsCli installs bundled skills into project skills", async () => { } }); -test("runSkillsCli refuses install into non-empty skills without force", async () => { +test("should ensure runSkillsCli refuses install into non-empty skills without force", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-skills-")); try { @@ -2156,7 +2156,7 @@ test("runSkillsCli refuses install into non-empty skills without force", async ( } }); -test("runSkillsCli sync updates Askr skills and preserves unrelated skills", async () => { +test("should ensure runSkillsCli sync updates Askr skills and preserves unrelated skills", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-skills-")); try { diff --git a/tests/database.test.ts b/tests/database.test.ts index bb5e2be..9e3b643 100644 --- a/tests/database.test.ts +++ b/tests/database.test.ts @@ -22,7 +22,7 @@ function io() { type Loader = typeof loadOrmTooling; describe("database command routing", () => { - it("forwards all semantics to the project-installed ORM tooling", async () => { + it("should forward all semantics to the project-installed ORM tooling", async () => { const output = io(); const runDatabaseCli = vi.fn(async () => 0); const loader: Loader = vi.fn(async () => ({ runDatabaseCli })); @@ -42,7 +42,7 @@ describe("database command routing", () => { }); }); - it("reports a focused install error when tooling is unavailable", async () => { + it("should report a focused install error when tooling is unavailable", async () => { const output = io(); const loader: Loader = vi.fn(async () => { throw new Error("missing"); @@ -51,7 +51,7 @@ describe("database command routing", () => { expect(output.errors).toEqual(["missing"]); }); - it("captures lazy validation output for askr check", async () => { + it("should capture lazy validation output for askr check", async () => { const loader: Loader = vi.fn(async () => ({ runDatabaseCli: async ( _args: readonly string[], diff --git a/tests/generate.remote.test.ts b/tests/generate-remote.test.ts similarity index 100% rename from tests/generate.remote.test.ts rename to tests/generate-remote.test.ts diff --git a/tests/guardrails.test.ts b/tests/guardrails.test.ts index 3762fa1..2852cce 100644 --- a/tests/guardrails.test.ts +++ b/tests/guardrails.test.ts @@ -91,7 +91,7 @@ afterEach(async () => { }); describe("guardrail commands", () => { - it("parses shared cwd, workspace, JSON, and help options", () => { + it("should parse shared cwd, workspace, JSON, and help options", () => { expect( parseGuardrailArgs(["--cwd", "./fixture", "--workspace=a*", "--workspace", "b", "--json"]), ).toEqual({ @@ -104,7 +104,7 @@ describe("guardrail commands", () => { expect(() => parseGuardrailArgs(["--unknown"])).toThrow(/unknown option/i); }); - it("diagnoses environment, package manager, skills, framework, and analysis health", async () => { + it("should diagnose environment, package manager, skills, framework, and analysis health", async () => { const root = await fixture({ skills: true }); await syncBundledSkills({ cwd: root }); const report = await runDoctor({ cwd: root, workspacePatterns: [] }, { nodeVersion: "24.0.0" }); @@ -119,7 +119,7 @@ describe("guardrail commands", () => { ]); }); - it("reports actionable doctor failures without changing the project", async () => { + it("should report actionable doctor failures without changing the project", async () => { const root = await fixture({ lockfiles: ["package-lock.json", "pnpm-lock.yaml"], source: ['import { state } from "@askrjs/askr";', "export const count = state(0);", ""].join( @@ -143,7 +143,7 @@ describe("guardrail commands", () => { expect(await fs.readFile(path.join(root, "src", "app.ts"), "utf8")).toBe(before); }); - it("runs validation scripts in order after analysis passes", async () => { + it("should run validation scripts in order after analysis passes", async () => { const root = await fixture(); const executed: string[] = []; const report = await runCheck( @@ -165,7 +165,7 @@ describe("guardrail commands", () => { ]); }); - it("automatically validates a discovered database before project scripts", async () => { + it("should automatically validate a discovered database before project scripts", async () => { const root = await fixture(); await fs.mkdir(path.join(root, "database"), { recursive: true }); await fs.writeFile(path.join(root, "database", "index.ts"), "export default {};\n"); @@ -193,7 +193,7 @@ describe("guardrail commands", () => { }); }); - it("does not run project scripts until blocking analysis findings are repaired", async () => { + it("should not run project scripts until blocking analysis findings are repaired", async () => { const root = await fixture({ source: ['import { state } from "@askrjs/askr";', "export const count = state(0);", ""].join( "\n", @@ -208,7 +208,7 @@ describe("guardrail commands", () => { expect(report.scripts.every((entry) => entry.status === "skipped")).toBe(true); }); - it("stops after a failed validation script and explains skipped stages", async () => { + it("should stop after a failed validation script and explain skipped stages", async () => { const root = await fixture(); const report = await runCheck( { cwd: root, workspacePatterns: [] }, @@ -231,7 +231,7 @@ describe("guardrail commands", () => { ]); }); - it("applies safe repairs, reports semantic leftovers, and converges idempotently", async () => { + it("should apply safe repairs, report semantic leftovers, and converge idempotently", async () => { const root = await fixture({ source: [ 'import { createRouteRegistry, route } from "@askrjs/askr/router";', @@ -255,7 +255,7 @@ describe("guardrail commands", () => { ); }); - it("dispatches doctor, repair, and check through the canonical askr CLI", async () => { + it("should dispatch doctor, repair, and check through the canonical askr CLI", async () => { const root = await fixture({ skills: true }); await syncBundledSkills({ cwd: root }); const output = io(); @@ -269,7 +269,7 @@ describe("guardrail commands", () => { }); describe("shipped template guardrails", () => { - it("keeps every template analyzer-clean and wired to the unified check", async () => { + it("should keep every template analyzer-clean and wired to the unified check", async () => { const templatesRoot = fileURLToPath(new URL("../templates/", import.meta.url)); for (const name of ["full-stack", "spa", "ssg", "ssr", "startkit"]) { const root = path.join(templatesRoot, name); diff --git a/tests/integration/packed-templates.test.ts b/tests/integration/packed-templates.test.ts index 096d608..a72b6e0 100644 --- a/tests/integration/packed-templates.test.ts +++ b/tests/integration/packed-templates.test.ts @@ -19,7 +19,7 @@ async function run(command: string, args: string[], cwd: string): Promise { +test("should ensure packed CLI passes checks for every shipped template", async () => { const selected = process.env.ASKR_TEMPLATE ? allTemplates.filter((template) => template === process.env.ASKR_TEMPLATE) : allTemplates; diff --git a/tests/integration/peer-floor.test.ts b/tests/integration/peer-floor.test.ts index 7230df5..352b681 100644 --- a/tests/integration/peer-floor.test.ts +++ b/tests/integration/peer-floor.test.ts @@ -19,7 +19,7 @@ async function run(command: string, args: string[], cwd: string): Promise { +test("should ensure packed CLI works with the minimum supported Askr peer", async () => { const root = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-peer-floor-")); try { await run("npm", ["run", "build"], repository); @@ -38,7 +38,7 @@ test("packed CLI works with the minimum supported Askr peer", async () => { private: true, type: "module", dependencies: { - "@askrjs/askr": "0.0.87", + "@askrjs/askr": "0.0.88", "@askrjs/cli": `file:${archive}`, }, }, diff --git a/tests/openapi.test.ts b/tests/openapi.test.ts index e118a06..4e29fc3 100644 --- a/tests/openapi.test.ts +++ b/tests/openapi.test.ts @@ -36,7 +36,7 @@ export default api; `; describe("OpenAPI CLI", () => { - it("parses defaults and overrides", () => { + it("should parse defaults and overrides", () => { expect(parseOpenApiArgs([])).toEqual({ entry: "./src/api.ts", output: "./openapi.yml", @@ -53,7 +53,7 @@ describe("OpenAPI CLI", () => { }); }); - it("serializes exact ordered YAML without aliases and with one newline", () => { + it("should serialize exact ordered YAML without aliases and with one newline", () => { const shared = { type: "string" }; expect( serializeOpenApi({ @@ -101,7 +101,7 @@ components: expect(yaml).toContain('"#/components/schemas/User"'); }); - it("loads TypeScript and atomically generates the artifact", async () => { + it("should load TypeScript and atomically generates the artifact", async () => { const item = await fixture(validModule); try { const result = io(); @@ -121,7 +121,7 @@ paths: {} } }); - it("awaits asynchronous document exporters", async () => { + it("should await asynchronous document exporters", async () => { const item = await fixture(` export default { async toOpenApiDocument() { @@ -139,7 +139,7 @@ paths: {} } }); - it("prints machine-readable generation results", async () => { + it("should print machine-readable generation results", async () => { const item = await fixture(validModule); try { const result = io(); @@ -159,7 +159,7 @@ paths: {} } }); - it("checks fresh, stale, and missing artifacts without writing", async () => { + it("should check fresh, stale, and missing artifacts without writing", async () => { const item = await fixture(validModule); try { const writes = vi.fn(async () => undefined); @@ -244,7 +244,7 @@ paths: {} } }); - it("routes the top-level openapi command", async () => { + it("should route the top-level openapi command", async () => { const item = await fixture(validModule); try { const result = io(); @@ -257,7 +257,7 @@ paths: {} } }); - it("uses a temporary sibling before rename", async () => { + it("should use a temporary sibling before rename", async () => { const events: string[] = []; const result = io(); const cwd = path.resolve(path.sep, "work"); @@ -293,7 +293,7 @@ paths: {} ]); }); - it("refuses to overwrite the source entry", async () => { + it("should refuse to overwrite the source entry", async () => { const item = await fixture(validModule); try { const result = io(); diff --git a/tests/update.cli.test.ts b/tests/update-cli.test.ts similarity index 100% rename from tests/update.cli.test.ts rename to tests/update-cli.test.ts diff --git a/tests/update.discovery.test.ts b/tests/update-discovery.test.ts similarity index 100% rename from tests/update.discovery.test.ts rename to tests/update-discovery.test.ts diff --git a/tests/update.range.test.ts b/tests/update-range.test.ts similarity index 100% rename from tests/update.range.test.ts rename to tests/update-range.test.ts diff --git a/tests/update.registry.test.ts b/tests/update-registry.test.ts similarity index 100% rename from tests/update.registry.test.ts rename to tests/update-registry.test.ts