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