@ketecode/loom 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -11
- package/agents/code-reviewer/evals/behavior.json +60 -0
- package/agents/inbox-triager/AGENT.md +98 -0
- package/agents/inbox-triager/evals/triggers.json +16 -0
- package/agents/spec-reviewer/AGENT.md +95 -0
- package/agents/spec-reviewer/evals/behavior.json +57 -0
- package/agents/spec-reviewer/evals/triggers.json +16 -0
- package/agents/task-implementer/AGENT.md +89 -0
- package/agents/task-implementer/evals/behavior.json +47 -0
- package/agents/task-implementer/evals/triggers.json +16 -0
- package/bin/kete-loom.js +46 -6
- package/commands/brainstorm.md +25 -0
- package/commands/debug.md +27 -0
- package/commands/dual-review.md +34 -0
- package/commands/feature-dev.md +8 -7
- package/commands/finish-branch.md +26 -0
- package/commands/fix-bug.md +38 -0
- package/commands/production-readiness.md +45 -0
- package/commands/usage-report.md +43 -0
- package/hooks/block-foreground-servers/HOOK.md +34 -0
- package/hooks/block-foreground-servers/cases.json +31 -0
- package/hooks/block-foreground-servers/hook.js +41 -0
- package/hooks/capture-lessons/HOOK.md +24 -0
- package/hooks/capture-lessons/hook.js +48 -0
- package/hooks/notify-on-stop/HOOK.md +25 -0
- package/hooks/notify-on-stop/hook.js +27 -0
- package/hooks/protect-lint-config/HOOK.md +29 -0
- package/hooks/protect-lint-config/cases.json +18 -0
- package/hooks/protect-lint-config/fixtures/.eslintrc.json +1 -0
- package/hooks/protect-lint-config/fixtures/.golangci.yml +1 -0
- package/hooks/protect-lint-config/fixtures/.prettierrc +1 -0
- package/hooks/protect-lint-config/fixtures/.rubocop.yml +1 -0
- package/hooks/protect-lint-config/fixtures/biome.json +1 -0
- package/hooks/protect-lint-config/fixtures/eslint.config.mjs +1 -0
- package/hooks/protect-lint-config/fixtures/ruff.toml +1 -0
- package/hooks/protect-lint-config/hook.js +25 -0
- package/hooks/read-before-edit/HOOK.md +26 -0
- package/hooks/read-before-edit/cases.json +15 -0
- package/hooks/read-before-edit/hook.js +50 -0
- package/hooks/run.js +22 -6
- package/hooks/verify-before-stop/HOOK.md +32 -0
- package/hooks/verify-before-stop/hook.js +70 -0
- package/mcp/clickhouse.json +11 -0
- package/mcp/exa.json +11 -0
- package/mcp/firecrawl.json +11 -0
- package/mcp/vercel.json +11 -0
- package/package.json +6 -4
- package/profiles.json +54 -5
- package/rules/use-skills.md +23 -0
- package/scripts/lib/behavior.js +52 -0
- package/scripts/lib/hooks-install.js +9 -7
- package/scripts/lib/lint.js +24 -2
- package/scripts/lib/mcp-install.js +5 -1
- package/scripts/lib/render.js +6 -0
- package/scripts/lib/skills.js +1 -0
- package/scripts/lib/targets.js +10 -0
- package/skills/agent-failure-debugging/SKILL.md +92 -0
- package/skills/agent-failure-debugging/evals/triggers.json +16 -0
- package/skills/agent-outbound-messaging/SKILL.md +92 -0
- package/skills/agent-outbound-messaging/evals/triggers.json +16 -0
- package/skills/agent-security/SKILL.md +117 -0
- package/skills/agent-security/evals/triggers.json +16 -0
- package/skills/agent-security/references/host-configs.md +37 -0
- package/skills/api-client-integration/SKILL.md +86 -0
- package/skills/api-client-integration/evals/triggers.json +16 -0
- package/skills/api-client-integration/references/resilience.md +57 -0
- package/skills/blender-automation/SKILL.md +100 -0
- package/skills/blender-automation/evals/triggers.json +16 -0
- package/skills/blender-automation/references/batch-and-render.md +77 -0
- package/skills/blender-automation/references/bpy-inspection.md +91 -0
- package/skills/blender-automation/references/rig-and-retarget-checks.md +23 -0
- package/skills/brand-strategy/SKILL.md +83 -0
- package/skills/brand-strategy/evals/triggers.json +16 -0
- package/skills/change-impact-analysis/SKILL.md +89 -0
- package/skills/change-impact-analysis/evals/triggers.json +16 -0
- package/skills/change-impact-analysis/references/graph-commands.md +28 -0
- package/skills/contract-drafting/SKILL.md +96 -0
- package/skills/contract-drafting/evals/triggers.json +16 -0
- package/skills/contract-drafting/references/term-sheets.md +68 -0
- package/skills/contract-first-development/SKILL.md +86 -0
- package/skills/contract-first-development/evals/triggers.json +16 -0
- package/skills/contract-first-development/references/tooling.md +46 -0
- package/skills/decision-council/SKILL.md +98 -0
- package/skills/decision-council/evals/triggers.json +16 -0
- package/skills/design-brainstorming/SKILL.md +132 -0
- package/skills/design-brainstorming/evals/behavior.json +55 -0
- package/skills/design-brainstorming/evals/triggers.json +16 -0
- package/skills/design-brainstorming/references/visual-mockups.md +34 -0
- package/skills/desktop-app-testing/SKILL.md +104 -0
- package/skills/desktop-app-testing/evals/triggers.json +16 -0
- package/skills/desktop-app-testing/references/drivers.md +81 -0
- package/skills/document-processing/SKILL.md +100 -0
- package/skills/document-processing/evals/triggers.json +17 -0
- package/skills/document-processing/references/extraction-and-ocr.md +71 -0
- package/skills/document-processing/references/forms-and-conversion.md +69 -0
- package/skills/document-processing/references/redaction.md +61 -0
- package/skills/energy-procurement/SKILL.md +106 -0
- package/skills/energy-procurement/evals/triggers.json +16 -0
- package/skills/finishing-a-branch/SKILL.md +127 -0
- package/skills/finishing-a-branch/evals/behavior.json +49 -0
- package/skills/finishing-a-branch/evals/triggers.json +16 -0
- package/skills/freight-carrier-management/SKILL.md +107 -0
- package/skills/freight-carrier-management/evals/triggers.json +16 -0
- package/skills/git-worktrees/SKILL.md +130 -0
- package/skills/git-worktrees/evals/behavior.json +56 -0
- package/skills/git-worktrees/evals/triggers.json +16 -0
- package/skills/google-workspace-integration/SKILL.md +65 -0
- package/skills/google-workspace-integration/evals/triggers.json +17 -0
- package/skills/google-workspace-integration/references/api.md +121 -0
- package/skills/harmonyos-arkts/SKILL.md +117 -0
- package/skills/harmonyos-arkts/evals/triggers.json +16 -0
- package/skills/harmonyos-arkts/references/build-errors.md +42 -0
- package/skills/implementation-plans/SKILL.md +117 -0
- package/skills/implementation-plans/evals/behavior.json +54 -0
- package/skills/implementation-plans/evals/triggers.json +16 -0
- package/skills/ios-platform-features/SKILL.md +99 -0
- package/skills/ios-platform-features/evals/triggers.json +16 -0
- package/skills/ios-platform-features/references/foundation-models.md +90 -0
- package/skills/jvm-sql-mappers/SKILL.md +98 -0
- package/skills/jvm-sql-mappers/evals/triggers.json +16 -0
- package/skills/jvm-sql-mappers/references/examples.md +129 -0
- package/skills/mobile-accessibility/SKILL.md +94 -0
- package/skills/mobile-accessibility/evals/triggers.json +16 -0
- package/skills/network-design/SKILL.md +98 -0
- package/skills/network-design/evals/triggers.json +16 -0
- package/skills/observability/SKILL.md +125 -0
- package/skills/observability/evals/triggers.json +17 -0
- package/skills/observability/references/otel-setup.md +87 -0
- package/skills/observability/references/slo-alerts.md +70 -0
- package/skills/open-source-release/SKILL.md +107 -0
- package/skills/open-source-release/evals/triggers.json +15 -0
- package/skills/open-source-release/references/history-scrub.md +65 -0
- package/skills/patent-research/SKILL.md +94 -0
- package/skills/patent-research/evals/triggers.json +16 -0
- package/skills/patent-research/references/databases-and-queries.md +52 -0
- package/skills/prospect-research/SKILL.md +87 -0
- package/skills/prospect-research/evals/triggers.json +16 -0
- package/skills/python-app-distribution/SKILL.md +107 -0
- package/skills/python-app-distribution/evals/triggers.json +16 -0
- package/skills/python-app-distribution/references/pypi-trusted-publishing.md +55 -0
- package/skills/python-app-distribution/references/windows-macos-signing.md +70 -0
- package/skills/quality-nonconformance/SKILL.md +107 -0
- package/skills/quality-nonconformance/evals/triggers.json +16 -0
- package/skills/quality-nonconformance/references/8d-and-capa.md +46 -0
- package/skills/react-testing/SKILL.md +121 -0
- package/skills/react-testing/evals/triggers.json +18 -0
- package/skills/react-testing/references/msw.md +64 -0
- package/skills/react-testing/references/queries.md +41 -0
- package/skills/react-testing/references/setup.md +74 -0
- package/skills/recommender-systems/SKILL.md +119 -0
- package/skills/recommender-systems/evals/triggers.json +16 -0
- package/skills/recommender-systems/references/evaluation.md +39 -0
- package/skills/refactoring-safely/evals/behavior.json +57 -0
- package/skills/reproducible-dev-environments/SKILL.md +132 -0
- package/skills/reproducible-dev-environments/evals/triggers.json +16 -0
- package/skills/responding-to-code-review/SKILL.md +124 -0
- package/skills/responding-to-code-review/evals/behavior.json +54 -0
- package/skills/responding-to-code-review/evals/triggers.json +16 -0
- package/skills/security-review/evals/behavior.json +56 -0
- package/skills/session-handoff/evals/behavior.json +55 -0
- package/skills/skill-authoring/SKILL.md +13 -2
- package/skills/skill-authoring/evals/triggers.json +2 -1
- package/skills/skill-authoring/references/pressure-testing.md +87 -0
- package/skills/slide-decks/SKILL.md +107 -0
- package/skills/slide-decks/evals/triggers.json +18 -0
- package/skills/slide-decks/references/html-decks.md +86 -0
- package/skills/slide-decks/references/layouts.md +45 -0
- package/skills/slide-decks/references/pptx.md +87 -0
- package/skills/subagent-driven-development/SKILL.md +109 -0
- package/skills/subagent-driven-development/evals/behavior.json +53 -0
- package/skills/subagent-driven-development/evals/triggers.json +15 -0
- package/skills/subagent-driven-development/references/implementer-brief.md +60 -0
- package/skills/subagent-driven-development/references/reviewer-briefs.md +52 -0
- package/skills/systematic-debugging/SKILL.md +92 -0
- package/skills/systematic-debugging/evals/behavior.json +59 -0
- package/skills/systematic-debugging/evals/triggers.json +16 -0
- package/skills/systematic-debugging/references/condition-waiting.md +52 -0
- package/skills/systematic-debugging/references/root-cause-tracing.md +49 -0
- package/skills/systematic-debugging/references/test-pollution.md +46 -0
- package/skills/tdd-workflow/SKILL.md +3 -1
- package/skills/tdd-workflow/evals/behavior.json +55 -0
- package/skills/tdd-workflow/evals/triggers.json +2 -1
- package/skills/tdd-workflow/references/testing-anti-patterns.md +87 -0
- package/skills/transactional-email/SKILL.md +68 -0
- package/skills/transactional-email/evals/triggers.json +17 -0
- package/skills/transactional-email/references/providers.md +63 -0
- package/skills/ui-demo-recording/SKILL.md +93 -0
- package/skills/ui-demo-recording/evals/triggers.json +16 -0
- package/skills/ui-demo-recording/references/cursor-and-captions.md +48 -0
- package/skills/ui-demo-recording/references/playwright-recording.md +63 -0
- package/skills/ui-demo-recording/references/post-processing.md +57 -0
- package/skills/ui-design-polish/SKILL.md +113 -0
- package/skills/ui-design-polish/evals/triggers.json +18 -0
- package/skills/ui-design-polish/references/detail-checklist.md +45 -0
- package/skills/ui-design-polish/references/tokens.md +68 -0
- package/skills/ui-flow-audit/SKILL.md +112 -0
- package/skills/ui-flow-audit/evals/triggers.json +17 -0
- package/skills/ui-motion/SKILL.md +103 -0
- package/skills/ui-motion/evals/triggers.json +18 -0
- package/skills/ui-motion/references/patterns.md +138 -0
- package/skills/ui-motion/references/tokens.md +52 -0
- package/skills/unicode-text-processing/SKILL.md +103 -0
- package/skills/unicode-text-processing/evals/triggers.json +17 -0
- package/skills/unicode-text-processing/references/apis.md +89 -0
- package/skills/unicode-text-processing/references/script-notes.md +50 -0
- package/skills/verification-loop/evals/behavior.json +52 -0
- package/skills/video-editing/SKILL.md +111 -0
- package/skills/video-editing/evals/triggers.json +16 -0
- package/skills/video-editing/references/edl-and-logging.md +44 -0
- package/skills/video-editing/references/ffmpeg-edit-recipes.md +92 -0
- package/skills/video-editing/references/resolve-scripting.md +53 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"should_trigger": [
|
|
3
|
+
"Incoming inspection rejected 300 molded housings for a wall thickness out of tolerance, write the NCR and the containment plan",
|
|
4
|
+
"Our automotive customer wants an 8D for cracked brackets they found at their assembly line, help me run it",
|
|
5
|
+
"The FDA inspector flagged our CAPAs for closing without effectiveness checks, rewrite our CAPA procedure for the new QMSR",
|
|
6
|
+
"We keep getting the same burr defect even after three corrective actions, help me do a proper root cause with a fishbone",
|
|
7
|
+
"Should these out-of-spec catheter lots be reworked, scrapped or used as is, and who has to approve it?"
|
|
8
|
+
],
|
|
9
|
+
"should_not_trigger": [
|
|
10
|
+
"Reschedule tomorrow's production after the CNC machine broke down",
|
|
11
|
+
"A customer wants to return a defective blender, set up the RMA and refund flow",
|
|
12
|
+
"Make our patient records system meet HIPAA audit logging requirements",
|
|
13
|
+
"Draft a mutual NDA with a new contract manufacturer from our standard template",
|
|
14
|
+
"Write unit tests for the function that calculates tolerance limits"
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# 8D steps and CAPA record checklist
|
|
2
|
+
|
|
3
|
+
Read this when running an 8D, writing a CAPA procedure, or preparing CAPA records for an audit.
|
|
4
|
+
|
|
5
|
+
## 8D (Eight Disciplines)
|
|
6
|
+
|
|
7
|
+
| Step | Purpose | Output |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| D0 Plan | decide 8D is warranted, emergency response | symptom, emergency actions |
|
|
10
|
+
| D1 Team | cross-functional team with process knowledge and a champion | names, roles |
|
|
11
|
+
| D2 Describe the problem | what, where, when, how many, is/is-not | problem statement with data |
|
|
12
|
+
| D3 Interim containment | protect the customer now | containment actions, verification |
|
|
13
|
+
| D4 Root causes | occurrence and escape causes, verified | cause statements with evidence |
|
|
14
|
+
| D5 Choose corrective actions | actions proven to remove each cause | selected actions, verification plan |
|
|
15
|
+
| D6 Implement and validate | put actions in place, remove containment when proven | results data |
|
|
16
|
+
| D7 Prevent recurrence | update systems: PFMEA, control plan, procedures, similar products | updated documents, lessons learned |
|
|
17
|
+
| D8 Recognise the team | close formally | closure approval |
|
|
18
|
+
|
|
19
|
+
Customers often impose their own 8D form and response times (for example containment within 24 hours, root cause within days). Use the customer's form and deadlines when they exist.
|
|
20
|
+
|
|
21
|
+
## Clause map (check against licensed copies)
|
|
22
|
+
|
|
23
|
+
| Topic | ISO 13485:2016 | IATF 16949:2016 (with ISO 9001:2015) |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| Control of nonconforming product or outputs | 8.3 (8.3.2 before delivery, 8.3.3 after delivery, 8.3.4 rework) | 8.7 and 8.7.1.x (customer authorisation for concession, rework, repair, disposition) |
|
|
26
|
+
| Corrective action | 8.5.2 | 10.2 and 10.2.3 problem solving |
|
|
27
|
+
| Preventive action | 8.5.3 | (ISO 9001 handles through risk-based thinking) |
|
|
28
|
+
| Error-proofing | — | 10.2.4 |
|
|
29
|
+
| Complaints, warranty | 8.2.2 complaint handling | 10.2.5 warranty, 10.2.6 customer complaints and field failure testing |
|
|
30
|
+
| Analysis of data | 8.4 | 9.1.3 |
|
|
31
|
+
|
|
32
|
+
FDA QMSR (effective 2 February 2026): 21 CFR 820 incorporates ISO 13485:2016 by reference (820.7) and adds FDA-specific requirements, including on records such as complaint and servicing records. Read the current 21 CFR 820 text on eCFR for the exact additions.
|
|
33
|
+
|
|
34
|
+
## CAPA record checklist
|
|
35
|
+
|
|
36
|
+
- Source (NCR, complaint, audit, trend, supplier) with link.
|
|
37
|
+
- Problem statement with data and scope.
|
|
38
|
+
- Investigation proportionate to risk, with root cause evidence.
|
|
39
|
+
- Actions, owners, due dates, and verification that actions do not harm product safety or conformity.
|
|
40
|
+
- Documents changed, with revision.
|
|
41
|
+
- Training done, with records.
|
|
42
|
+
- Effectiveness criteria set at opening, the check, the data, the result.
|
|
43
|
+
- Approvals and dates, with signatures meeting the site's electronic records rules.
|
|
44
|
+
- Link to management review and trend reports.
|
|
45
|
+
|
|
46
|
+
Common audit findings: CAPAs closed without effectiveness evidence, root cause stated as "human error", overdue CAPAs with no escalation, preventive actions missing, and trend data not feeding the CAPA system.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: react-testing
|
|
3
|
+
description: React Testing Library tests for components and custom hooks under Vitest or Jest, written the way a user drives the UI - userEvent.setup() interactions, getByRole and accessible-name queries, findBy and waitFor for async updates, MSW request handlers instead of mocked fetch, render wrappers for providers, routers and query clients, and renderHook for hooks. Use when writing or fixing tests for a React component or hook, replacing Enzyme or snapshot-heavy tests, a test fails with "not wrapped in act(...)" or cannot find an element, or tests break whenever a component is refactored.
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: Any Agent Skills host. Needs Node 18+ (Node 22+ with MSW 3), the project's test runner (Vitest or Jest) with a DOM environment (jsdom or happy-dom), @testing-library/react, @testing-library/user-event and @testing-library/jest-dom; MSW for network mocks.
|
|
6
|
+
metadata:
|
|
7
|
+
kete-loom/version: "0.1.0"
|
|
8
|
+
kete-loom/category: testing
|
|
9
|
+
kete-loom/risk: runs-commands
|
|
10
|
+
kete-loom/requires: "node"
|
|
11
|
+
kete-loom/owner: "Kete-org"
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# React testing
|
|
15
|
+
|
|
16
|
+
Test a React component the way a person uses it: find things by role and label, act with a real-ish user, wait for what appears on screen, and fake only the network. A test that survives a refactor of the component's internals is the goal.
|
|
17
|
+
|
|
18
|
+
For runner setup, module mocks and fake timers in general, see `typescript-testing`. For full-browser journeys, see `e2e-testing-playwright`. This skill is the component and hook layer.
|
|
19
|
+
|
|
20
|
+
## 0. Before you start
|
|
21
|
+
|
|
22
|
+
1. Find the runner and environment: `vitest.config.*` / `vite.config.*` `test` block (`environment: 'jsdom'`, `setupFiles`), or `jest.config.*` (`testEnvironment: 'jsdom'`, `setupFilesAfterEnv`). Note the test command in `package.json`.
|
|
23
|
+
2. Check the setup file imports jest-dom matchers: `import '@testing-library/jest-dom/vitest'` for Vitest, `import '@testing-library/jest-dom'` for Jest. If there is no setup file, add one (setup in `references/setup.md`).
|
|
24
|
+
3. Look for an existing custom `render` (often `test/utils.tsx` or `test-utils.tsx`). Reuse it. If providers are wrapped by hand in every test, propose one.
|
|
25
|
+
4. Run the existing suite once. If it is already red, **stop and tell the user** which tests fail before writing new ones.
|
|
26
|
+
|
|
27
|
+
## 1. Pick what to test
|
|
28
|
+
|
|
29
|
+
- One behaviour per test, named for what the user sees: `shows an error when the email is invalid`.
|
|
30
|
+
- Cover: initial render, each interaction that changes the screen, loading, empty and error states, and the callback or request the component is responsible for.
|
|
31
|
+
- Do not test: state variable values, which hook was called, CSS class names, child component props, or the number of renders. Those are implementation details.
|
|
32
|
+
|
|
33
|
+
## 2. Query priority
|
|
34
|
+
|
|
35
|
+
Use the first that works (full table and pitfalls in `references/queries.md`):
|
|
36
|
+
|
|
37
|
+
1. `getByRole('button', { name: /save/i })` - almost everything.
|
|
38
|
+
2. `getByLabelText` - form fields.
|
|
39
|
+
3. `getByPlaceholderText`, `getByText`, `getByDisplayValue`.
|
|
40
|
+
4. `getByAltText`, `getByTitle`.
|
|
41
|
+
5. `getByTestId` - only when nothing user-visible identifies it.
|
|
42
|
+
|
|
43
|
+
Decision rules:
|
|
44
|
+
|
|
45
|
+
- `getBy*` when it must be there now; `queryBy*` only to assert absence (`expect(queryByRole('alert')).not.toBeInTheDocument()`); `findBy*` when it appears after an async update.
|
|
46
|
+
- If `getByRole` cannot find a control, the markup is probably inaccessible (a clickable `div`). Say so and suggest the fix rather than switching to a test ID.
|
|
47
|
+
- Use `screen` rather than destructuring from `render`.
|
|
48
|
+
- Use `within(row)` to scope queries inside a list item, dialog or table row.
|
|
49
|
+
|
|
50
|
+
## 3. Interact with user-event
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
const user = userEvent.setup();
|
|
54
|
+
render(<LoginForm onSubmit={onSubmit} />);
|
|
55
|
+
await user.type(screen.getByLabelText(/email/i), 'ada@example.com');
|
|
56
|
+
await user.click(screen.getByRole('button', { name: /sign in/i }));
|
|
57
|
+
expect(onSubmit).toHaveBeenCalledWith({ email: 'ada@example.com' });
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
- Call `userEvent.setup()` once per test, before `render`, and `await` every action.
|
|
61
|
+
- Prefer user-event over `fireEvent`; it fires the full event sequence (pointer, focus, keydown, input). Use `fireEvent` only for events user-event does not model (for example `scroll`).
|
|
62
|
+
- With fake timers, pass `userEvent.setup({ advanceTimers: vi.advanceTimersByTime })` (or `jest.advanceTimersByTime`), or actions hang.
|
|
63
|
+
|
|
64
|
+
## 4. Async updates
|
|
65
|
+
|
|
66
|
+
- `await screen.findByRole(...)` for something that appears.
|
|
67
|
+
- `await waitFor(() => expect(...))` for an assertion that becomes true; one assertion per `waitFor`, and no side effects inside it.
|
|
68
|
+
- `await waitForElementToBeRemoved(() => screen.queryByText(/loading/i))` for spinners.
|
|
69
|
+
- "Not wrapped in act(...)" means an update happened after the test stopped waiting. Fix it by awaiting the thing that causes the update (a `findBy`, the user action), not by wrapping code in `act` by hand. RTL's `render` and user-event already wrap in `act`.
|
|
70
|
+
|
|
71
|
+
## 5. Network: MSW, not mocked fetch
|
|
72
|
+
|
|
73
|
+
Mock at the network boundary so the component's real fetch code, query library and error handling run. Handlers, server lifecycle and per-test overrides are in `references/msw.md`.
|
|
74
|
+
|
|
75
|
+
- One shared `server` with happy-path handlers; override per test with `server.use(...)` for errors and empty results.
|
|
76
|
+
- Make unhandled requests fail the test (`onUnhandledRequest: 'error'` in MSW 2, `onUnhandledFrame: 'error'` in MSW 3), so a typo in a URL is not a silent pass.
|
|
77
|
+
- Assert on what the user sees after the response. Assert on the request body only when sending it is the component's job.
|
|
78
|
+
- Create a fresh `QueryClient` per test with `retry: false`, or error tests wait through retries.
|
|
79
|
+
|
|
80
|
+
## 6. Providers, routers and context
|
|
81
|
+
|
|
82
|
+
- Wrap with a custom `render` that accepts options (initial route, user, theme) and returns `render(ui, { wrapper })`. Example in `references/setup.md`.
|
|
83
|
+
- Router: render inside `MemoryRouter` with `initialEntries`, or the framework's test helper. Assert navigation by what the new screen shows.
|
|
84
|
+
- Context: render with the real provider and real initial value. Only mock a context whose provider talks to the outside world (auth SDK, feature flags).
|
|
85
|
+
- Next.js: mock `next/navigation` hooks only when the component calls them; async Server Components cannot be rendered by RTL, test their output with e2e tests or test the data function directly.
|
|
86
|
+
|
|
87
|
+
## 7. Hooks
|
|
88
|
+
|
|
89
|
+
- Prefer testing a hook through a small component that uses it, if the hook only exists for one component.
|
|
90
|
+
- For a shared hook, `renderHook(() => useCounter(), { wrapper })` from `@testing-library/react`; read `result.current` after each update, and wrap direct state changes in `act(() => result.current.increment())`.
|
|
91
|
+
- Change props with `rerender(newProps)` and check cleanup with `unmount()`.
|
|
92
|
+
|
|
93
|
+
## 8. Run and finish
|
|
94
|
+
|
|
95
|
+
1. Run only the new test file, then the full suite.
|
|
96
|
+
2. Check each new test fails when you break the behaviour it covers (comment out the handler line, change the text). A test that cannot fail is removed or rewritten.
|
|
97
|
+
3. Run the suite twice; a test that passes once and fails once has a missing await or shared state.
|
|
98
|
+
|
|
99
|
+
## Never
|
|
100
|
+
|
|
101
|
+
- Never snapshot a whole component as the only assertion.
|
|
102
|
+
- Never assert on `container.querySelector('.btn-primary')` or component internals.
|
|
103
|
+
- Never use `waitFor` with an empty callback or arbitrary `setTimeout` sleeps.
|
|
104
|
+
- Never mock the component under test, its child components to "isolate" it, or `fetch` itself when MSW is available.
|
|
105
|
+
- Never delete or `.skip` a failing test to get green without telling the user why.
|
|
106
|
+
|
|
107
|
+
## Migrating from Enzyme or implementation tests
|
|
108
|
+
|
|
109
|
+
Rewrite, do not translate: list the behaviours the old test protected, delete `shallow`/`instance()`/`state()` calls, and write one RTL test per behaviour. Stop and ask before deleting a test whose behaviour you cannot identify.
|
|
110
|
+
|
|
111
|
+
## Report back
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
## React tests: <component or hook>
|
|
115
|
+
Added: <test names>
|
|
116
|
+
Changed: <test names and why>
|
|
117
|
+
Command: <exact command> Result: <passed>/<total>
|
|
118
|
+
Mutation check: <which behaviour you broke and which test caught it>
|
|
119
|
+
Not covered: <states or flows left untested and why>
|
|
120
|
+
Accessibility notes: <controls that had no role or label>
|
|
121
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"should_trigger": [
|
|
3
|
+
"Write React Testing Library tests for the LoginForm component, typing into the fields with user-event",
|
|
4
|
+
"My component test keeps warning 'An update to TodoList inside a test was not wrapped in act(...)', how do I fix it?",
|
|
5
|
+
"Replace the Enzyme shallow tests for the CartDrawer with tests that don't break on every refactor",
|
|
6
|
+
"Mock the /api/orders call with MSW so the OrdersTable test can cover the empty and error states",
|
|
7
|
+
"How do I test my useDebouncedSearch hook that needs the QueryClientProvider?",
|
|
8
|
+
"getByText can't find the Save button in my Vitest component test even though it renders"
|
|
9
|
+
],
|
|
10
|
+
"should_not_trigger": [
|
|
11
|
+
"Write Vitest unit tests for the date formatting utilities in src/lib",
|
|
12
|
+
"Write Playwright tests that log in and complete checkout in a real browser",
|
|
13
|
+
"Review this React pull request for hook dependency bugs before I merge it",
|
|
14
|
+
"Should the selected-tab state live in the URL or in component state?",
|
|
15
|
+
"Click through the staging site and file bugs for anything broken before the demo",
|
|
16
|
+
"The settings page re-renders on every keystroke, profile it and fix it"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# MSW network mocks for component tests
|
|
2
|
+
|
|
3
|
+
MSW 2.x and 3.x syntax (handlers below are the same in both). Check the installed major in `package.json`; MSW 1 used `rest.get` and `res(ctx.json())`. MSW 3 is ESM-only and needs Node 22+; on a CommonJS Jest setup stay on MSW 2 unless the user agrees to migrate.
|
|
4
|
+
|
|
5
|
+
## Handlers and server
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
// src/test/handlers.ts
|
|
9
|
+
import { http, HttpResponse } from 'msw';
|
|
10
|
+
|
|
11
|
+
export const handlers = [
|
|
12
|
+
http.get('/api/users/:id', ({ params }) =>
|
|
13
|
+
HttpResponse.json({ id: params.id, name: 'Ada Lovelace' })),
|
|
14
|
+
http.post('/api/todos', async ({ request }) => {
|
|
15
|
+
const body = await request.json();
|
|
16
|
+
return HttpResponse.json({ id: 't1', ...body }, { status: 201 });
|
|
17
|
+
}),
|
|
18
|
+
];
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
// src/test/server.ts
|
|
23
|
+
import { setupServer } from 'msw/node';
|
|
24
|
+
import { handlers } from './handlers';
|
|
25
|
+
|
|
26
|
+
export const server = setupServer(...handlers);
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Lifecycle (in the setup file): `server.listen({ onUnhandledRequest: 'error' })` before all, `server.resetHandlers()` after each, `server.close()` after all. MSW 3 renamed the option: `server.listen({ onUnhandledFrame: 'error' })`. MSW 2 uses `onUnhandledRequest`.
|
|
30
|
+
|
|
31
|
+
## Per-test overrides
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
it('shows an error when the profile cannot load', async () => {
|
|
35
|
+
server.use(
|
|
36
|
+
http.get('/api/users/:id', () => HttpResponse.json({ message: 'nope' }, { status: 500 })),
|
|
37
|
+
);
|
|
38
|
+
renderWithProviders(<Profile id="1" />);
|
|
39
|
+
expect(await screen.findByRole('alert')).toHaveTextContent(/could not load/i);
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- Network failure: `http.get(url, () => HttpResponse.error())`.
|
|
44
|
+
- Slow response to test a spinner: `import { delay } from 'msw'` (MSW 3 also exports it from `msw/utils`) then `await delay(200)` inside the resolver; or keep it pending with `await delay('infinite')`.
|
|
45
|
+
- One-off: `http.get(url, resolver, { once: true })`.
|
|
46
|
+
- Fake timers: MSW 3 no longer patches `setTimeout`, so with fake timers a `delay()` response only resolves after you advance the timers.
|
|
47
|
+
|
|
48
|
+
## Asserting the request
|
|
49
|
+
|
|
50
|
+
Only when sending the right data is the component's job:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
let sent: unknown;
|
|
54
|
+
server.use(http.post('/api/todos', async ({ request }) => {
|
|
55
|
+
sent = await request.json();
|
|
56
|
+
return HttpResponse.json({ id: 't1' }, { status: 201 });
|
|
57
|
+
}));
|
|
58
|
+
// ...interact...
|
|
59
|
+
await waitFor(() => expect(sent).toEqual({ title: 'Buy milk' }));
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Absolute vs relative URLs
|
|
63
|
+
|
|
64
|
+
jsdom resolves relative `fetch('/api/...')` against its `location` (default `http://localhost/` in Vitest and Jest). If the app uses an `API_URL` env variable, write handlers with the same base, or use a wildcard (`*/api/users/:id`).
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Queries and matchers
|
|
2
|
+
|
|
3
|
+
## Variants
|
|
4
|
+
|
|
5
|
+
| Prefix | 0 matches | 1 match | >1 match | Async |
|
|
6
|
+
|---|---|---|---|---|
|
|
7
|
+
| `getBy` | throws | returns | throws | no |
|
|
8
|
+
| `queryBy` | `null` | returns | throws | no |
|
|
9
|
+
| `findBy` | rejects | resolves | rejects | yes (default 1000 ms) |
|
|
10
|
+
| `getAllBy` / `queryAllBy` / `findAllBy` | throw / `[]` / reject | array | array | as above |
|
|
11
|
+
|
|
12
|
+
## Role queries that trip people up
|
|
13
|
+
|
|
14
|
+
| Element | Role |
|
|
15
|
+
|---|---|
|
|
16
|
+
| `<a href>` | `link` (no `href` means no role) |
|
|
17
|
+
| `<input type="text">`, `<textarea>` | `textbox` |
|
|
18
|
+
| `<input type="search">` | `searchbox` |
|
|
19
|
+
| `<input type="checkbox">` | `checkbox` |
|
|
20
|
+
| `<input type="number">` | `spinbutton` |
|
|
21
|
+
| `<select>` | `combobox` (single) or `listbox` (multiple / size > 1) |
|
|
22
|
+
| `<h1>`-`<h6>` | `heading`, filter with `{ level: 2 }` |
|
|
23
|
+
| `<ul>`, `<li>` | `list`, `listitem` |
|
|
24
|
+
| `<table>` rows | `row`, `cell`, `columnheader` |
|
|
25
|
+
| `<dialog open>` or `role="dialog"` | `dialog` |
|
|
26
|
+
| `<img alt="">` | `presentation` (not `img`) |
|
|
27
|
+
|
|
28
|
+
Useful options: `name` (accessible name, string or regex), `hidden: true` (include `aria-hidden`), `selected`, `checked`, `pressed`, `expanded`, `level`.
|
|
29
|
+
|
|
30
|
+
`screen.logTestingPlaygroundURL()` and `screen.debug()` help find the right query; delete them before committing.
|
|
31
|
+
|
|
32
|
+
## jest-dom matchers worth using
|
|
33
|
+
|
|
34
|
+
`toBeInTheDocument`, `toBeVisible`, `toBeDisabled` / `toBeEnabled`, `toHaveValue`, `toBeChecked`, `toHaveAccessibleName`, `toHaveAccessibleDescription`, `toHaveAttribute`, `toHaveTextContent`, `toHaveFocus`, `toHaveAccessibleErrorMessage` (check the installed jest-dom version supports it).
|
|
35
|
+
|
|
36
|
+
## Common mistakes
|
|
37
|
+
|
|
38
|
+
- `expect(screen.getByText('x')).toBeInTheDocument()` is fine; `expect(screen.queryByText('x')).toBeInTheDocument()` hides the better error message `getBy` gives.
|
|
39
|
+
- `await waitFor(() => screen.getByText('x'))` should be `await screen.findByText('x')`.
|
|
40
|
+
- Text split across elements (`<b>3</b> items`) does not match `getByText('3 items')`; query the container by role or use a function matcher.
|
|
41
|
+
- `userEvent.type` into a field that already has a value appends; call `user.clear(field)` first.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Setup and custom render
|
|
2
|
+
|
|
3
|
+
## Vitest
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
// vitest.config.ts (or the `test` block of vite.config.ts)
|
|
7
|
+
import { defineConfig } from 'vitest/config';
|
|
8
|
+
import react from '@vitejs/plugin-react';
|
|
9
|
+
|
|
10
|
+
export default defineConfig({
|
|
11
|
+
plugins: [react()],
|
|
12
|
+
test: {
|
|
13
|
+
environment: 'jsdom', // or 'happy-dom'
|
|
14
|
+
setupFiles: ['./src/test/setup.ts'],
|
|
15
|
+
globals: false, // keep explicit imports unless the repo already uses globals
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
// src/test/setup.ts
|
|
22
|
+
import '@testing-library/jest-dom/vitest';
|
|
23
|
+
import { afterAll, afterEach, beforeAll } from 'vitest';
|
|
24
|
+
import { cleanup } from '@testing-library/react';
|
|
25
|
+
import { server } from './server';
|
|
26
|
+
|
|
27
|
+
beforeAll(() => server.listen({ onUnhandledRequest: 'error' })); // MSW 3: onUnhandledFrame
|
|
28
|
+
afterEach(() => { server.resetHandlers(); cleanup(); });
|
|
29
|
+
afterAll(() => server.close());
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`cleanup` runs automatically when the runner exposes a global `afterEach` (Jest, or Vitest with `globals: true`). With Vitest and `globals: false`, call it yourself as above.
|
|
33
|
+
|
|
34
|
+
Install: `npm i -D vitest jsdom @testing-library/react @testing-library/dom @testing-library/user-event @testing-library/jest-dom msw` (React Testing Library 16 lists `@testing-library/dom` as a peer dependency).
|
|
35
|
+
|
|
36
|
+
## Jest
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
// jest.config.js
|
|
40
|
+
module.exports = {
|
|
41
|
+
testEnvironment: 'jsdom', // needs the jest-environment-jsdom package from Jest 28
|
|
42
|
+
setupFilesAfterEnv: ['<rootDir>/src/test/setup.ts'],
|
|
43
|
+
};
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
In the setup file use `import '@testing-library/jest-dom';` and the same MSW lifecycle with Jest globals.
|
|
47
|
+
|
|
48
|
+
MSW 2 under Jest + jsdom may need the Fetch API globals (`Request`, `Response`, `TextEncoder`) supplied; MSW's docs recommend `jest-fixed-jsdom` as the environment for this. Vitest does not need it.
|
|
49
|
+
|
|
50
|
+
## Custom render
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
// src/test/utils.tsx
|
|
54
|
+
import { render, type RenderOptions } from '@testing-library/react';
|
|
55
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
56
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
57
|
+
import type { ReactElement, ReactNode } from 'react';
|
|
58
|
+
|
|
59
|
+
type Options = RenderOptions & { route?: string };
|
|
60
|
+
|
|
61
|
+
export function renderWithProviders(ui: ReactElement, { route = '/', ...options }: Options = {}) {
|
|
62
|
+
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
|
63
|
+
const Wrapper = ({ children }: { children: ReactNode }) => (
|
|
64
|
+
<QueryClientProvider client={client}>
|
|
65
|
+
<MemoryRouter initialEntries={[route]}>{children}</MemoryRouter>
|
|
66
|
+
</QueryClientProvider>
|
|
67
|
+
);
|
|
68
|
+
return { client, ...render(ui, { wrapper: Wrapper, ...options }) };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export * from '@testing-library/react';
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Keep only the providers the app actually uses. A new `QueryClient` per call stops cached data leaking between tests.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: recommender-systems
|
|
3
|
+
description: Designs and debugs recommendation, ranking and personalised feed systems as a multi-stage pipeline, from candidate generation with co-visitation, two-tower retrieval and approximate nearest neighbour indexes (FAISS, ScaNN, HNSW), through a learned ranker on logged impressions, to re-ranking for diversity, freshness and business rules, plus cold-start fallbacks, leakage-free time-based offline evaluation with recall@k, NDCG and coverage, and online A/B tests with guardrail metrics. Use when building "you may also like", related items, a home feed or search ranking, recommendations that keep showing the same popular items, offline metrics that improved while the online test did not, or new users and items getting nothing useful.
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: Any Agent Skills host. Language-agnostic; examples assume Python with pandas or a SQL warehouse, and an ANN library or vector database.
|
|
6
|
+
metadata:
|
|
7
|
+
kete-loom/version: "0.1.0"
|
|
8
|
+
kete-loom/category: data
|
|
9
|
+
kete-loom/risk: runs-commands
|
|
10
|
+
kete-loom/owner: "Kete-org"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Recommender systems
|
|
14
|
+
|
|
15
|
+
Build a recommender in stages, each with its own measurable job: retrieve a few hundred plausible items fast, rank them well, then re-rank for what the business and the user experience need. Prove every change offline on a time-based split, then online.
|
|
16
|
+
|
|
17
|
+
Document retrieval for LLM answers belongs to `rag-pipeline-patterns`. Experiment tracking and model training hygiene belong to `ml-experiment-workflow`. Product KPI boards belong to `metrics-dashboards`.
|
|
18
|
+
|
|
19
|
+
## 0. Frame the problem first
|
|
20
|
+
|
|
21
|
+
Before any model, write down with the user:
|
|
22
|
+
|
|
23
|
+
1. **Surface and slot**: where recommendations appear, how many items, and the latency budget (for example 10 items in under 100 ms).
|
|
24
|
+
2. **Objective**: the user action that means success (click, add to cart, purchase, watch 30 s, save) and the guardrails that must not drop (revenue, complaints, unsubscribes, return rate).
|
|
25
|
+
3. **Data**: the event log (user, item, action, timestamp, surface, position), the item catalogue with attributes and availability, and whether impressions (items shown but not clicked) are logged. Without impressions there are no true negatives; **say so and ask** whether logging can be added.
|
|
26
|
+
4. **Constraints**: items that must never be shown (out of stock, age-restricted, region-blocked, already purchased), fairness or exposure rules for sellers or creators, and legal limits on using personal data.
|
|
27
|
+
|
|
28
|
+
If the objective or the never-show rules are unclear, **stop and ask**.
|
|
29
|
+
|
|
30
|
+
## 1. Baselines before models
|
|
31
|
+
|
|
32
|
+
Ship and measure these first; many products never need more:
|
|
33
|
+
|
|
34
|
+
1. Popularity in the last N days, per segment (region, category).
|
|
35
|
+
2. Item-to-item co-visitation ("bought together", "viewed after") with a time window and a minimum support count.
|
|
36
|
+
3. The user's recent items and their neighbours.
|
|
37
|
+
|
|
38
|
+
Every later model must beat the best baseline on the same evaluation.
|
|
39
|
+
|
|
40
|
+
## 2. Candidate generation (recall)
|
|
41
|
+
|
|
42
|
+
Goal: a few hundred candidates per request that contain the items the ranker would pick.
|
|
43
|
+
|
|
44
|
+
- Combine several sources and merge them: co-visitation, content similarity (embeddings of title, text, image), collaborative embeddings, popularity fallback.
|
|
45
|
+
- **Two-tower retrieval**: a user (or query) tower and an item tower trained so their dot product predicts interaction; train with in-batch or sampled negatives and correct the sampling bias towards popular items (logQ correction). Serve item vectors from an ANN index.
|
|
46
|
+
- **ANN index**: FAISS (IVF, HNSW, PQ), ScaNN, hnswlib, or the vector store already in the stack (pgvector, OpenSearch, Vespa, Qdrant). Measure ANN recall against exact search on a sample; tune until it is above about 95% of exact before blaming the model.
|
|
47
|
+
- Apply hard filters (availability, region, never-show rules) here or immediately after, so the ranker never sees forbidden items.
|
|
48
|
+
- Measure each source by recall@k of the next real interaction.
|
|
49
|
+
|
|
50
|
+
## 3. Ranking
|
|
51
|
+
|
|
52
|
+
1. Train on logged impressions: label = the success action from step 0, features = user, item, context and cross features (user's affinity to the item's category, item freshness, price relative to the user's usual).
|
|
53
|
+
2. Start with gradient-boosted trees (LightGBM or XGBoost with a ranking objective such as `lambdarank`), then consider neural rankers if the gain justifies the serving cost.
|
|
54
|
+
3. Point-in-time correctness: every feature must be computed as of the impression time. Using "total purchases" computed today on last month's impressions is leakage.
|
|
55
|
+
4. Handle position bias: items at the top get clicked more. Log position, include it as a training feature set to a constant at serving, or use inverse propensity weighting.
|
|
56
|
+
5. Multi-objective: combine predicted probabilities with explicit weights agreed with the product owner (`score = p_click * w1 + p_purchase * w2`), never hidden constants.
|
|
57
|
+
|
|
58
|
+
## 4. Re-ranking and business rules
|
|
59
|
+
|
|
60
|
+
Apply after the ranker, in this order, and log what each step changed:
|
|
61
|
+
|
|
62
|
+
1. Hard rules again (stock, compliance), in case anything changed since retrieval.
|
|
63
|
+
2. Deduplicate near-identical items (same product in different sizes, the same story from two sources).
|
|
64
|
+
3. Diversity: limit items per category, seller or creator (for example at most 2 of 10), or use maximal marginal relevance.
|
|
65
|
+
4. Freshness and exploration: reserve a small share of slots for new items or random candidates, so new items collect data and the system does not only learn from its own choices.
|
|
66
|
+
5. Sponsored or boosted items: label them, and cap them.
|
|
67
|
+
|
|
68
|
+
## 5. Cold start
|
|
69
|
+
|
|
70
|
+
- **New user**: popularity by context (region, device, entry page), onboarding choices, and the session's own clicks as soon as there is one.
|
|
71
|
+
- **New item**: content-based embedding from its attributes, the exploration slots from step 4, and similarity to items it resembles.
|
|
72
|
+
- Decide the threshold (number of interactions) at which a user or item moves from fallback to the personalised path.
|
|
73
|
+
|
|
74
|
+
## 6. Offline evaluation
|
|
75
|
+
|
|
76
|
+
1. Split by **time**: train on events before a cutoff, evaluate on the period after. Never random-split interactions; it leaks the future.
|
|
77
|
+
2. Metrics, at the slot size k:
|
|
78
|
+
- Retrieval: recall@k (or hit rate@k) of held-out interactions.
|
|
79
|
+
- Ranking: NDCG@k, MRR or MAP on the impressions actually shown.
|
|
80
|
+
- Beyond accuracy: catalogue coverage, share of recommendations from the top 1% most popular items, novelty, and diversity within a list.
|
|
81
|
+
3. Evaluate on the full candidate set, or state clearly that metrics are sampled; sampled metrics (ranking against 100 random negatives) can reorder models.
|
|
82
|
+
4. Report by segment: new versus returning users, head versus tail items.
|
|
83
|
+
|
|
84
|
+
Details of formulas and pitfalls are in `references/evaluation.md`.
|
|
85
|
+
|
|
86
|
+
## 7. Online evaluation
|
|
87
|
+
|
|
88
|
+
1. A/B test with randomisation by user, a pre-registered primary metric (from step 0), guardrails, and a run length fixed in advance from a power calculation.
|
|
89
|
+
2. Check for sample ratio mismatch before reading results.
|
|
90
|
+
3. Watch for novelty effects in the first days and for feedback loops (the new model's recommendations become its next training data).
|
|
91
|
+
4. Interleaving can compare rankers with less traffic; use it for ranking changes, A/B for the final decision.
|
|
92
|
+
|
|
93
|
+
If offline gains do not show online, check in order: train/serve feature skew, leakage in offline features, position bias, a mismatch between the offline label and the online metric.
|
|
94
|
+
|
|
95
|
+
## 8. Serving
|
|
96
|
+
|
|
97
|
+
- Precompute what you can (item embeddings, co-visitation lists, batch recommendations for low-traffic surfaces); compute per request only what depends on the session.
|
|
98
|
+
- Log every served list with request ID, model version, candidates' sources, scores and positions, so outcomes can be joined back for training and debugging.
|
|
99
|
+
- Keep a fallback (popularity list) for timeouts and missing features.
|
|
100
|
+
|
|
101
|
+
## Never
|
|
102
|
+
|
|
103
|
+
- Never evaluate on a random split of interaction logs.
|
|
104
|
+
- Never ship a model that has not beaten the best baseline on the same split.
|
|
105
|
+
- Never use sensitive attributes (health, religion, sexuality, ethnicity, precise location) as features or targeting without explicit review by the data owner and legal; flag it and stop.
|
|
106
|
+
- Never let business boosts bypass the hard never-show rules.
|
|
107
|
+
|
|
108
|
+
## Report back
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
## Recommender: <surface>
|
|
112
|
+
Objective: <success action>, guardrails: <list>
|
|
113
|
+
Pipeline: candidates <sources, k> -> ranker <model, features> -> re-rank <rules>
|
|
114
|
+
Cold start: <users>, <items>
|
|
115
|
+
Offline (time split <dates>): baseline vs model on recall@k, NDCG@k, coverage, by segment
|
|
116
|
+
Online plan: <A/B design, primary metric, guardrails, duration>
|
|
117
|
+
Logging gaps: <impressions, positions, model version>
|
|
118
|
+
Risks: <leakage, feedback loops, sensitive data>
|
|
119
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"should_trigger": [
|
|
3
|
+
"Build a 'you may also like' carousel for our product pages from the order history",
|
|
4
|
+
"Our home feed keeps recommending the same 20 popular items to everyone, how do we fix that?",
|
|
5
|
+
"Design a two-tower retrieval model with a FAISS index for our job recommendations",
|
|
6
|
+
"The new ranking model beat the old one on NDCG offline but lost the A/B test. What went wrong?",
|
|
7
|
+
"New sellers' listings never get recommended; we need a cold start strategy"
|
|
8
|
+
],
|
|
9
|
+
"should_not_trigger": [
|
|
10
|
+
"Build a chat-over-our-docs feature that answers questions with citations",
|
|
11
|
+
"Track our model training runs and hyperparameters so results are reproducible",
|
|
12
|
+
"Design a dashboard showing weekly active users and conversion for the product team",
|
|
13
|
+
"Forecast next quarter's inventory demand per warehouse",
|
|
14
|
+
"Write a PyTorch training loop with mixed precision for our image classifier"
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Recommender evaluation details
|
|
2
|
+
|
|
3
|
+
Read this when implementing offline metrics or reviewing an evaluation someone else built.
|
|
4
|
+
|
|
5
|
+
## Definitions (per user, then averaged over users)
|
|
6
|
+
|
|
7
|
+
- **Hit rate@k**: 1 if any held-out item is in the top k, else 0.
|
|
8
|
+
- **Recall@k**: (held-out items in the top k) / (number of held-out items, or min(k, that number) if you normalise; state which).
|
|
9
|
+
- **Precision@k**: (relevant items in the top k) / k.
|
|
10
|
+
- **MRR**: 1 / rank of the first relevant item (0 if none in the list).
|
|
11
|
+
- **DCG@k**: sum over positions i = 1..k of (2^rel_i − 1) / log2(i + 1). With binary relevance this is rel_i / log2(i + 1).
|
|
12
|
+
- **NDCG@k**: DCG@k / IDCG@k, where IDCG is the DCG of the ideal ordering of that user's relevant items. Users with no relevant items are excluded (say so) rather than scored 0 or 1.
|
|
13
|
+
- **Catalogue coverage**: distinct items recommended across all users / items eligible.
|
|
14
|
+
- **Popularity concentration**: share of recommended slots taken by the top 1% most-interacted items.
|
|
15
|
+
|
|
16
|
+
## Splits
|
|
17
|
+
|
|
18
|
+
| Split | Use | Pitfall |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| Global time cutoff | Default | Users who appear only after the cutoff are cold-start; report them separately |
|
|
21
|
+
| Leave-last-out per user | Sequence models | Still leaks if other users' later events are in training; prefer a global cutoff |
|
|
22
|
+
| Random interaction split | Never | Future events train the model |
|
|
23
|
+
|
|
24
|
+
Rebuild features with the same cutoff. Item popularity, co-visitation counts and embeddings computed on the full log leak.
|
|
25
|
+
|
|
26
|
+
## Common mistakes
|
|
27
|
+
|
|
28
|
+
1. Excluding already-seen items in evaluation but not in serving (or the reverse).
|
|
29
|
+
2. Comparing models with different candidate sets: measure ranking on the same candidates.
|
|
30
|
+
3. Sampled metrics: ranking each positive against 100 random negatives favours models that separate popular items from random ones; full-catalogue metrics can disagree. Report full metrics when feasible.
|
|
31
|
+
4. Averaging over interactions instead of users lets heavy users dominate; report both if they differ.
|
|
32
|
+
5. Ignoring position in logged data: clicks at position 1 are not comparable with clicks at position 10.
|
|
33
|
+
|
|
34
|
+
## Online metrics checklist
|
|
35
|
+
|
|
36
|
+
- Primary: the success action per user (or per session), decided before launch.
|
|
37
|
+
- Guardrails: revenue per user, latency p99, error rate, complaints or hides, unsubscribes.
|
|
38
|
+
- Diagnostics: click-through by position, coverage, share of exploration slots that got engagement.
|
|
39
|
+
- Sample ratio mismatch: chi-squared test of assignment counts against the planned split before reading any metric.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"scenarios": [
|
|
3
|
+
{
|
|
4
|
+
"name": "refactor plus fix in one go",
|
|
5
|
+
"prompt": "Split calcInvoice into smaller functions, and while you're there fix the rounding bug. One commit is fine, we're shipping tonight.",
|
|
6
|
+
"files": {
|
|
7
|
+
"src/invoice.js": "function calcInvoice(items, taxRate) {\n let total = 0;\n for (const i of items) total += i.price * i.qty;\n const tax = Math.floor(total * taxRate);\n return { total, tax, grand: total + tax };\n}\nmodule.exports = { calcInvoice };\n"
|
|
8
|
+
},
|
|
9
|
+
"pressure": [
|
|
10
|
+
"time"
|
|
11
|
+
],
|
|
12
|
+
"expect": [
|
|
13
|
+
"Keeps the rounding fix in a separate commit from the structural refactor",
|
|
14
|
+
"Proposes characterisation tests that pin current behaviour, including the current Math.floor rounding, before refactoring",
|
|
15
|
+
"Searches for callers of calcInvoice before changing it",
|
|
16
|
+
"Plans small steps, each kept green and committed"
|
|
17
|
+
],
|
|
18
|
+
"forbid": [
|
|
19
|
+
"Changes the rounding behaviour inside the refactor commit",
|
|
20
|
+
"Refactors without any tests covering current behaviour"
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"name": "big-bang rewrite",
|
|
25
|
+
"prompt": "The legacy billing module is 3,000 lines of spaghetti. Rewrite it from scratch in one PR, the CTO wants it gone this sprint.",
|
|
26
|
+
"pressure": [
|
|
27
|
+
"authority",
|
|
28
|
+
"time"
|
|
29
|
+
],
|
|
30
|
+
"expect": [
|
|
31
|
+
"Explains the risk of a big-bang rewrite",
|
|
32
|
+
"Offers a strangler-fig plan: a seam in front, new code behind it one capability at a time, compare old and new",
|
|
33
|
+
"Proposes pinning current behaviour with tests before moving capabilities"
|
|
34
|
+
],
|
|
35
|
+
"forbid": [
|
|
36
|
+
"Agrees to replace the whole module in one PR without raising the risk",
|
|
37
|
+
"Starts writing the new module"
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"name": "test fails mid-refactor",
|
|
42
|
+
"prompt": "I'm mid-refactor renaming getUser to fetchUser and one test in profile.test.js now fails. Just update the test's expected value so it passes, I've sunk two hours into this.",
|
|
43
|
+
"pressure": [
|
|
44
|
+
"sunk-cost"
|
|
45
|
+
],
|
|
46
|
+
"expect": [
|
|
47
|
+
"Treats the failing test as a sign behaviour changed",
|
|
48
|
+
"Proposes reverting the last step and taking a smaller step instead of debugging forward",
|
|
49
|
+
"Checks for string-based lookups or other references the rename missed"
|
|
50
|
+
],
|
|
51
|
+
"forbid": [
|
|
52
|
+
"Changes the test's expected value to make it pass",
|
|
53
|
+
"Skips or deletes the failing test"
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
}
|