@clipboard-health/ai-rules 2.26.0 → 2.28.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.
Files changed (39) hide show
  1. package/README.md +56 -39
  2. package/package.json +1 -1
  3. package/rules/backend/architecture.md +4 -0
  4. package/rules/backend/asyncMessaging.md +4 -0
  5. package/rules/backend/infrastructure.md +4 -0
  6. package/rules/backend/mongodb.md +4 -33
  7. package/rules/backend/notifications.md +4 -0
  8. package/rules/backend/postgres.md +4 -0
  9. package/rules/backend/restApiDesign.md +10 -54
  10. package/rules/backend/serviceTests.md +4 -0
  11. package/rules/common/configuration.md +4 -0
  12. package/rules/common/coreLibraries.md +4 -0
  13. package/rules/common/featureFlags.md +4 -0
  14. package/rules/common/gitWorkflow.md +4 -0
  15. package/rules/common/libraryAuthoring.md +13 -0
  16. package/rules/common/loggingObservability.md +4 -0
  17. package/rules/common/testing.md +5 -1
  18. package/rules/common/typeScript.md +12 -61
  19. package/rules/datamodeling/analytics.md +22 -15
  20. package/rules/datamodeling/castingDbtStagingModels.md +4 -0
  21. package/rules/datamodeling/dbtModelDevelopment.md +16 -12
  22. package/rules/datamodeling/dbtYamlDocumentation.md +4 -0
  23. package/rules/frontend/{fileOrganization.md → architecture.md} +22 -1
  24. package/rules/frontend/customHooks.md +4 -12
  25. package/rules/frontend/dataFetching.md +25 -16
  26. package/rules/frontend/e2eTesting.md +5 -10
  27. package/rules/frontend/reactComponents.md +48 -42
  28. package/rules/frontend/styling.md +91 -6
  29. package/rules/frontend/testing.md +6 -35
  30. package/scripts/constants.js +1 -79
  31. package/scripts/rules.js +126 -0
  32. package/scripts/sync.js +20 -61
  33. package/rules/frontend/bottomSheets.md +0 -25
  34. package/rules/frontend/businessLogicPlacement.md +0 -3
  35. package/rules/frontend/errorHandling.md +0 -39
  36. package/rules/frontend/frontendTechnologyStack.md +0 -10
  37. package/rules/frontend/interactiveElements.md +0 -19
  38. package/rules/frontend/modalRoutes.md +0 -15
  39. package/skills/flaky-test-bulk-debugger/SKILL.md +0 -89
package/scripts/sync.js CHANGED
@@ -8,6 +8,7 @@ const promises_1 = require("node:fs/promises");
8
8
  const node_path_1 = __importDefault(require("node:path"));
9
9
  const constants_1 = require("./constants");
10
10
  const execAndLog_1 = require("./execAndLog");
11
+ const rules_1 = require("./rules");
11
12
  const toErrorMessage_1 = require("./toErrorMessage");
12
13
  const PATHS = {
13
14
  projectRoot: node_path_1.default.join(__dirname, "../../../.."),
@@ -16,8 +17,18 @@ const PATHS = {
16
17
  async function sync() {
17
18
  try {
18
19
  const parsedArguments = parseArguments();
19
- const ruleIds = resolveRuleIds(parsedArguments);
20
- if (ruleIds.length === 0) {
20
+ const allRules = await (0, rules_1.discoverRules)(node_path_1.default.join(PATHS.packageRoot, "rules"));
21
+ const { rules, unknownIds } = (0, rules_1.resolveRules)({
22
+ rules: allRules,
23
+ profileCategories: constants_1.PROFILES[parsedArguments.profile].include,
24
+ includes: parsedArguments.extraIncludes,
25
+ excludes: parsedArguments.excludes,
26
+ });
27
+ if (unknownIds.length > 0) {
28
+ console.warn(`⚠️ Ignoring unknown rules: ${unknownIds.join(", ")}`);
29
+ console.warn(`Available rules: ${allRules.map((rule) => rule.id).join(", ")}`);
30
+ }
31
+ if (rules.length === 0) {
21
32
  console.error("❌ Error: No rules remaining after excludes");
22
33
  process.exit(1);
23
34
  }
@@ -31,16 +42,16 @@ async function sync() {
31
42
  (0, promises_1.rm)(libraryOutput, { recursive: true, force: true }),
32
43
  ]);
33
44
  const [, skillsSyncResult, librarySyncResult] = await Promise.all([
34
- copyRuleFiles(ruleIds, rulesOutput),
45
+ copyRuleFiles(rules, rulesOutput),
35
46
  syncAgentDirectory("skills", skillsOutput),
36
47
  syncAgentDirectory("lib", libraryOutput),
37
48
  copySetupScript(),
38
49
  mergeSessionStartHook(),
39
50
  ]);
40
- const agentsContent = await generateAgentsIndex(ruleIds);
51
+ const agentsContent = (0, rules_1.generateAgentsIndex)(rules);
41
52
  await (0, promises_1.writeFile)(node_path_1.default.join(PATHS.projectRoot, constants_1.FILES.agents), agentsContent, "utf8");
42
53
  await (0, promises_1.writeFile)(node_path_1.default.join(PATHS.projectRoot, constants_1.FILES.claude), "@AGENTS.md\n", "utf8");
43
- console.log(`✅ @clipboard-health/ai-rules synced ${parsedArguments.profile} (${ruleIds.length} rules)`);
54
+ console.log(`✅ @clipboard-health/ai-rules synced ${parsedArguments.profile} (${rules.length} rules)`);
44
55
  await appendOverlay(PATHS.projectRoot);
45
56
  await formatOutputFiles(PATHS.projectRoot, {
46
57
  skillsCopied: skillsSyncResult === "copied",
@@ -53,9 +64,6 @@ async function sync() {
53
64
  process.exit(0);
54
65
  }
55
66
  }
56
- function isRuleId(value) {
57
- return value in constants_1.RULE_FILES;
58
- }
59
67
  function isProfileName(value) {
60
68
  return value in constants_1.PROFILES;
61
69
  }
@@ -83,11 +91,6 @@ function parseArguments() {
83
91
  console.error(`❌ Error: Unexpected argument "${argument}"`);
84
92
  printUsageAndExit();
85
93
  }
86
- else if (!isRuleId(argument)) {
87
- console.error(`❌ Error: Unknown rule "${argument}"`);
88
- console.error(`Available rules: ${Object.keys(constants_1.RULE_FILES).join(", ")}`);
89
- process.exit(1);
90
- }
91
94
  else if (mode === "include") {
92
95
  extraIncludes.push(argument);
93
96
  }
@@ -106,21 +109,11 @@ function printUsageAndExit() {
106
109
  console.error(` node sync.js common --include backend/architecture`);
107
110
  process.exit(1);
108
111
  }
109
- function resolveRuleIds(parsedArguments) {
110
- const { profile, extraIncludes, excludes } = parsedArguments;
111
- const profileRules = constants_1.PROFILES[profile].include.flatMap((category) => constants_1.CATEGORIES[category]);
112
- const ruleSet = new Set([...profileRules, ...extraIncludes]);
113
- for (const ruleId of excludes) {
114
- ruleSet.delete(ruleId);
115
- }
116
- return [...ruleSet];
117
- }
118
- async function copyRuleFiles(ruleIds, rulesOutput) {
119
- await Promise.all(ruleIds.map(async (ruleId) => {
120
- const rulePath = (0, constants_1.toRulePath)(ruleId);
121
- const destination = node_path_1.default.join(rulesOutput, rulePath);
112
+ async function copyRuleFiles(rules, rulesOutput) {
113
+ await Promise.all(rules.map(async (rule) => {
114
+ const destination = node_path_1.default.join(rulesOutput, rule.relativePath);
122
115
  await (0, promises_1.mkdir)(node_path_1.default.dirname(destination), { recursive: true });
123
- await (0, promises_1.cp)(node_path_1.default.join(PATHS.packageRoot, "rules", rulePath), destination);
116
+ await (0, promises_1.cp)(node_path_1.default.join(PATHS.packageRoot, "rules", rule.relativePath), destination);
124
117
  }));
125
118
  }
126
119
  async function syncAgentDirectory(directoryName, destination) {
@@ -230,40 +223,6 @@ async function mergeSessionStartHook() {
230
223
  const action = hasStale || currentCount > 1 ? "Updated" : "Added";
231
224
  console.log(`📋 ${action} SessionStart hook in .claude/settings.json`);
232
225
  }
233
- async function extractHeading(filePath) {
234
- try {
235
- const content = await (0, promises_1.readFile)(filePath, "utf8");
236
- const match = /^#\s+(?<heading>.+)$/m.exec(content);
237
- return match?.groups?.["heading"] ?? node_path_1.default.basename(filePath, ".md");
238
- }
239
- catch {
240
- return node_path_1.default.basename(filePath, ".md");
241
- }
242
- }
243
- async function generateAgentsIndex(ruleIds) {
244
- const rows = await Promise.all(ruleIds.map(async (ruleId) => {
245
- const rulePath = (0, constants_1.toRulePath)(ruleId);
246
- const heading = await extractHeading(node_path_1.default.join(PATHS.packageRoot, "rules", rulePath));
247
- return `| ${heading} | .rules/${rulePath} | ${constants_1.RULE_FILES[ruleId]} |`;
248
- }));
249
- return [
250
- "<!-- Generated by @clipboard-health/ai-rules -->",
251
- "",
252
- "# Coding Rules",
253
- "",
254
- "IMPORTANT: You MUST read the relevant rule files below before writing or reviewing code.",
255
- "",
256
- "| Rule | File | When to Read |",
257
- "|------|------|-------------|",
258
- ...rows,
259
- "",
260
- "## Agent Skills",
261
- "",
262
- "Agent skills are linked from `node_modules/@clipboard-health/ai-rules` into `.agents/`.",
263
- "If a referenced skill is missing or unreadable, run `npm ci` from the repository root and retry.",
264
- "",
265
- ].join("\n");
266
- }
267
226
  async function appendOverlay(projectRoot) {
268
227
  const overlayPath = node_path_1.default.join(projectRoot, "OVERLAY.md");
269
228
  let overlayContent;
@@ -1,25 +0,0 @@
1
- # Bottom Sheets
2
-
3
- ## Closing
4
-
5
- Every bottom sheet must include a close button in the top-right corner via `BottomSheetHeader`:
6
-
7
- ```typescript
8
- <BottomSheet
9
- modalState={modalState}
10
- header={<BottomSheetHeader onClose={onClose} />}
11
- >
12
- ```
13
-
14
- ## Component Structure
15
-
16
- Split each bottom sheet into two components for Storybook testability:
17
-
18
- - **`SomethingBottomSheet`** — connected component that owns data fetching, analytics, and state; renders `<BottomSheet>` and delegates content to the presentational component.
19
- - **`SomethingBottomSheetContent`** — pure presentational component that accepts only primitive/callback props; has a `.stories.tsx` file. The stories file should render a button that opens the content in a `<BottomSheet>`.
20
-
21
- An exception to this rule is if the bottom sheet itself is just presentational with no data fetching/mutations. Then it can just be one .tsx file.
22
-
23
- ## Button Layout
24
-
25
- Buttons inside a bottom sheet must always stack vertically (never side by side). Use `<DialogFooter orientation="vertical">` or a vertical `<Stack>`.
@@ -1,3 +0,0 @@
1
- # Business Logic Placement
2
-
3
- Flag business logic in frontend code that should be a backend API call instead. Frontend/backend divergence causes bugs.
@@ -1,39 +0,0 @@
1
- # Error Handling
2
-
3
- ## Component Level
4
-
5
- ```typescript
6
- export function DataComponent() {
7
- const { data, isLoading, isError, refetch } = useGetData();
8
-
9
- if (isLoading) return <LoadingState />;
10
- if (isError) return <ErrorState onRetry={refetch} />;
11
-
12
- return <DataDisplay data={data} />;
13
- }
14
- ```
15
-
16
- ## Mutation Level
17
-
18
- ```typescript
19
- useMutation({
20
- mutationFn: createItem,
21
- onSuccess: () => showSuccessToast("Created"),
22
- meta: {
23
- logErrorMessage: APP_EVENTS.CREATE_FAILURE,
24
- userErrorMessage: "Failed to create",
25
- },
26
- });
27
- ```
28
-
29
- ## Validation
30
-
31
- ```typescript
32
- try {
33
- const validated = formSchema.parse(formData);
34
- } catch (error) {
35
- if (error instanceof z.ZodError) {
36
- error.errors.forEach((err) => showFieldError(err.path.join("."), err.message));
37
- }
38
- }
39
- ```
@@ -1,10 +0,0 @@
1
- # Frontend Technology Stack
2
-
3
- - **React** with TypeScript (strict mode)
4
- - **MUI** for UI components
5
- - **React Query** (@tanstack/react-query) for data fetching
6
- - **Zod** for runtime validation
7
- - **Vitest** + **@testing-library/react** for testing
8
- - **MSW** for API mocking
9
- - **Playwright** for E2E tests
10
- - **constate** for shared state
@@ -1,19 +0,0 @@
1
- # Interactive Elements
2
-
3
- Never add `onClick` to `div` or `span`. Use:
4
-
5
- - `<button>` for actions
6
- - `<a>` (Link) for navigation
7
- - MUI interactive components (`Button`, `IconButton`, `ListItemButton`)
8
-
9
- This ensures proper accessibility: focus states, keyboard navigation, ARIA roles.
10
-
11
- ## Keyboard Accessibility
12
-
13
- Every interactive element must be:
14
-
15
- - Tab focusable (proper tab order)
16
- - Visually indicated when focused (visible focus indicator)
17
- - Assigned appropriate ARIA roles
18
- - Equipped with keyboard event handling (Enter/Space for buttons, Enter for links)
19
- - Styled with pointer cursor
@@ -1,15 +0,0 @@
1
- # Modal Routes
2
-
3
- Modal visibility is driven by URL, not local state:
4
-
5
- ```typescript
6
- <ModalRoute
7
- path={`${basePath}/confirm`}
8
- closeModalPath={basePath}
9
- render={({ modalState }) => (
10
- <ConfirmDialog modalState={modalState} />
11
- )}
12
- />
13
- ```
14
-
15
- Use `history.replace` (not `push`) when navigating between modals to avoid awkward back-button behavior.
@@ -1,89 +0,0 @@
1
- ---
2
- name: flaky-test-bulk-debugger
3
- description: Bulk-triage flaky test investigation tickets by clustering sightings, sharing artifacts, and delegating per-cluster diagnosis to flaky-test-debugger. Use when investigating many flaky test tickets, Linear issues tagged flaky-investigation, CI flake bursts, or repeated failures that may share a root cause.
4
- ---
5
-
6
- # Flaky Test Bulk Debugger
7
-
8
- Use this skill to investigate a queue of flaky test sightings efficiently. Its purpose is orchestration: collect tickets, build a compact manifest, cluster related failures, fetch shared artifacts once, then run `flaky-test-debugger` per cluster.
9
-
10
- Do not duplicate the detailed diagnosis workflow from `flaky-test-debugger`. When a cluster is ready for root-cause analysis, use `flaky-test-debugger` in plan mode unless the user explicitly asks to implement fixes.
11
-
12
- ## Rules
13
-
14
- - Treat bulky data as external artifacts. Save full issue descriptions, CI logs, LLM reports, Playwright traces, screenshots, and telemetry extracts to files; keep only manifests and summaries in conversation context.
15
- - Cluster before per-test debugging. Do not read each test file independently until shared setup, CI, auth, static asset, backend, or infrastructure failures have been ruled out.
16
- - Prefer one implementation ticket per root cause, not one per sighting.
17
- - Preserve the source ticket instructions for labels, status, linked issues, PR body requirements, and close-out comments.
18
- - If parallel workers are available, use them per cluster with minimal context. If not, process clusters serially and keep a running manifest file.
19
- - Keep the coordinator responsible for queue state, deduplication, and Linear/bookkeeping; keep cluster workers responsible for evidence and diagnosis.
20
-
21
- ## Phase 1: Build Queue
22
-
23
- Fetch or receive all candidate tickets. For Linear, filter by the user-provided project/status/label, commonly `Todo` plus `flaky-investigation`.
24
-
25
- For each ticket, extract one manifest row: `issueId`, `repo`, `framework`, `testFile`, `testName`, `runUrl`, `commit`, `branch`, `shard`, `timestamp`, `firstError`, `firstStackFrame`, `priorTickets`, and `sourceInstructions`. Write full raw ticket data to a local artifact file if it is large.
26
-
27
- ## Phase 2: Cluster
28
-
29
- Normalize errors before grouping:
30
-
31
- - Replace random IDs, emails, names, phone numbers, shift/facility IDs, UUIDs, ObjectIds, hashes, ports, and timestamps with placeholders.
32
- - Collapse generated asset filenames to their logical shape, for example `main-<hash>.<hash>.js`.
33
- - Keep HTTP status codes, helper names, route names, endpoint paths, and lifecycle stages intact.
34
-
35
- Cluster by strongest shared evidence first:
36
-
37
- 1. Same CI run, commit, timestamp window, and setup/helper stack.
38
- 2. Same failure surface from `flaky-test-debugger`: CI/job setup, test setup/auth/data, app bootstrap/navigation, user action, backend request, post-success render, assertion/locator.
39
- 3. Same first project stack frame or setup helper.
40
- 4. Same endpoint/static asset/status-code pattern.
41
- 5. Same test file or prior related tickets.
42
-
43
- Do not merge clusters only because they are in the same run. Same-run failures can still have different root causes.
44
-
45
- ## Phase 3: Fetch Artifacts
46
-
47
- For each unique CI run, fetch the available reports once and store them under a predictable temporary path. For Playwright LLM reports in Clipboard repos, use the repository helper when available:
48
-
49
- ```bash
50
- bash scripts/fetch-llm-report.sh "<github-actions-url>"
51
- ```
52
-
53
- Record artifact paths in the manifest. Workers should receive paths and the relevant manifest rows, not the full artifact contents.
54
-
55
- ## Phase 4: Diagnose Clusters
56
-
57
- For each cluster, run `flaky-test-debugger` in plan mode. If the agent supports skills, explicitly load or invoke `flaky-test-debugger`; otherwise follow its workflow manually.
58
-
59
- Use this worker prompt shape:
60
-
61
- ```text
62
- Use flaky-test-debugger in plan mode.
63
- Investigate this cluster as one possible shared root cause, not as isolated tickets.
64
-
65
- Inputs: cluster summary, manifest rows, artifact/report paths, prior related tickets, and source ticket close-out instructions.
66
-
67
- Return: final failure surface, evidence artifacts, root cause diagnosis, confidence score, whether one implementation ticket covers all issues, implementation plan or no-code disposition, and exact ticket action recommendation.
68
- ```
69
-
70
- If confidence is below 5/5, the worker must include the observability or artifact changes needed to make the next occurrence diagnosable.
71
-
72
- ## Phase 5: Act
73
-
74
- Merge worker outputs into a coordinator summary:
75
-
76
- - Cluster name and issue IDs
77
- - Shared vs independent root cause
78
- - Evidence level and confidence
79
- - Recommended implementation ticket count
80
- - Tickets to mark duplicate/no-code/human-needed
81
- - Remaining unknowns
82
-
83
- Create or recommend implementation tickets only after clustering. Link all investigation tickets covered by the same root cause, plus prior related tickets from the source descriptions.
84
-
85
- When updating investigation tickets, comment with the implementation ticket ID or no-code disposition, link related issues as requested, and move the ticket only after the comment/link exists.
86
-
87
- ## Output Format
88
-
89
- End with a compact bulk triage report containing: queue size, cluster count, unique CI runs, each cluster's issue IDs, surface, recommendation, confidence, next ticket action, artifact index, and risks such as missing evidence or likely false merges.