pasika 0.1.6 → 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.
Files changed (68) hide show
  1. package/README.md +40 -55
  2. package/dist/eslint/pasika/index.d.ts +18 -0
  3. package/dist/eslint/pasika/index.js +18 -3
  4. package/dist/eslint/pasika/rules/enforce-barrel-exports.d.ts +9 -0
  5. package/dist/eslint/pasika/rules/enforce-barrel-exports.js +78 -0
  6. package/dist/eslint/pasika/rules/enforce-cn-merge.d.ts +9 -0
  7. package/dist/eslint/pasika/rules/enforce-cn-merge.js +84 -0
  8. package/dist/eslint/pasika/rules/enforce-cva-variant-props.d.ts +9 -0
  9. package/dist/eslint/pasika/rules/enforce-cva-variant-props.js +76 -0
  10. package/dist/eslint/pasika/rules/filename-case.js +10 -0
  11. package/dist/eslint/pasika/rules/import-boundaries.d.ts +2 -0
  12. package/dist/eslint/pasika/rules/{organization-imports.js → import-boundaries.js} +11 -1
  13. package/dist/eslint/pasika/rules/no-arbitrary-tailwind.d.ts +9 -0
  14. package/dist/eslint/pasika/rules/no-arbitrary-tailwind.js +107 -0
  15. package/dist/eslint/pasika/rules/no-mixed-concerns.d.ts +9 -0
  16. package/dist/eslint/pasika/rules/no-mixed-concerns.js +84 -0
  17. package/package.json +5 -16
  18. package/claude/hooks/.vulyk +0 -3
  19. package/claude/hooks/AGENTS.md +0 -3
  20. package/claude/hooks/CLAUDE.md +0 -1
  21. package/claude/hooks/claude-hooks.md +0 -30
  22. package/claude/hooks/notification.sh +0 -38
  23. package/claude/hooks/protect-files.sh +0 -21
  24. package/claude/hooks/status-line/index.js +0 -57
  25. package/claude/scripts/render-settings.ts +0 -223
  26. package/claude/settings.base.json +0 -38
  27. package/dist/claude/scripts/render-settings.js +0 -145
  28. package/dist/eslint/pasika/rules/organization-imports.d.ts +0 -2
  29. package/dist/eslint.config.js +0 -7
  30. package/dist/scripts/pasika.js +0 -59
  31. package/docs/agent-conventions.md +0 -27
  32. package/docs/claude/hooks.md +0 -30
  33. package/docs/code-organization-guide/code-organization-guide.md +0 -69
  34. package/docs/code-organization-guide/references/application-architecture-reference.md +0 -149
  35. package/docs/code-organization-guide/rules/component-placement-rule.md +0 -119
  36. package/docs/code-organization-guide/rules/configuration-rule.md +0 -42
  37. package/docs/code-organization-guide/rules/constants-rule.md +0 -74
  38. package/docs/code-organization-guide/rules/exports-and-imports-rule.md +0 -84
  39. package/docs/code-organization-guide/rules/folder-nesting-rule.md +0 -82
  40. package/docs/code-organization-guide/rules/hook-extraction-rule.md +0 -141
  41. package/docs/code-organization-guide/rules/interactive-component-rule.md +0 -97
  42. package/docs/code-organization-guide/rules/jsx-hygiene-rule.md +0 -67
  43. package/docs/code-organization-guide/rules/locales-rule.md +0 -53
  44. package/docs/code-organization-guide/rules/nameable-visual-concept-rule.md +0 -66
  45. package/docs/code-organization-guide/rules/no-mixed-concerns-rule.md +0 -63
  46. package/docs/code-organization-guide/rules/repeated-structure-rule.md +0 -93
  47. package/docs/code-organization-guide/rules/smart-vs-dumb-component-rule.md +0 -112
  48. package/docs/code-organization-guide/rules/sole-state-owner-rule.md +0 -101
  49. package/docs/code-organization-guide/rules/types-and-schemas-rule.md +0 -139
  50. package/docs/code-organization-guide/rules/utilities-rule.md +0 -86
  51. package/docs/documentation-guide/_templates/grouped-reference.md +0 -11
  52. package/docs/documentation-guide/_templates/guide.md +0 -19
  53. package/docs/documentation-guide/_templates/rule.md +0 -21
  54. package/docs/documentation-guide/_templates/single-lookup-reference.md +0 -5
  55. package/docs/documentation-guide/documentation-guide.md +0 -13
  56. package/docs/documentation-guide/references/documentation-types-reference.md +0 -9
  57. package/docs/documentation-guide/rules/guide-creation-rule.md +0 -113
  58. package/docs/documentation-guide/rules/reference-creation-rule.md +0 -132
  59. package/docs/documentation-guide/rules/rule-creation-rule.md +0 -81
  60. package/docs/documentation-guide/rules/template-usage-rule.md +0 -49
  61. package/docs/shadcn-theme.md +0 -121
  62. package/docs/styling-guide/rules/arbitrary-value-rule.md +0 -31
  63. package/docs/styling-guide/rules/class-composition-rule.md +0 -52
  64. package/docs/styling-guide/rules/component-ui-state-rule.md +0 -53
  65. package/docs/styling-guide/rules/component-variant-rule.md +0 -125
  66. package/docs/styling-guide/rules/global-stylesheet-rule.md +0 -67
  67. package/docs/styling-guide/rules/theme-and-utility-definition-rule.md +0 -86
  68. package/docs/styling-guide/styling-guide.md +0 -14
@@ -1,145 +0,0 @@
1
- import { access, copyFile, mkdir, readFile, writeFile } from "node:fs/promises";
2
- import path from "node:path";
3
- import { constants } from "node:fs";
4
- import { fileURLToPath } from "node:url";
5
- const __filename = fileURLToPath(import.meta.url);
6
- const __dirname = path.dirname(__filename);
7
- const isRecord = (value) => typeof value === "object" && value !== null;
8
- const fileExists = async (filePath) => {
9
- try {
10
- await access(filePath, constants.F_OK);
11
- return true;
12
- }
13
- catch {
14
- return false;
15
- }
16
- };
17
- const isClaudeSettings = (value) => {
18
- if (!isRecord(value)) {
19
- return false;
20
- }
21
- const hooks = value.hooks;
22
- const statusLine = value.statusLine;
23
- if (!isRecord(hooks) || !isRecord(statusLine) || typeof statusLine.command !== "string") {
24
- return false;
25
- }
26
- const notification = hooks.Notification;
27
- if (notification !== undefined && !Array.isArray(notification)) {
28
- return false;
29
- }
30
- const preToolUse = hooks.PreToolUse;
31
- return preToolUse === undefined || Array.isArray(preToolUse);
32
- };
33
- const readClaudeSettings = async (templatePath) => {
34
- const templateRaw = await readFile(templatePath, "utf8");
35
- const parsed = JSON.parse(templateRaw);
36
- if (!isClaudeSettings(parsed)) {
37
- throw new Error("Claude settings template has an invalid shape.");
38
- }
39
- return parsed;
40
- };
41
- const mergeStringArrays = (left = [], right = []) => Array.from(new Set([...left, ...right]));
42
- const upsertHookMatcherEntry = (entries, matcher, command) => {
43
- const nextEntries = [...(entries ?? [])];
44
- const existingIndex = nextEntries.findIndex((entry) => (entry.matcher ?? "") === matcher);
45
- const hook = { type: "command", command };
46
- if (existingIndex === -1) {
47
- nextEntries.push({ matcher, hooks: [hook] });
48
- return nextEntries;
49
- }
50
- const existingEntry = nextEntries[existingIndex];
51
- nextEntries[existingIndex] = { ...existingEntry, hooks: [hook] };
52
- return nextEntries;
53
- };
54
- const mergeClaudeSettings = (existingSettings, baseSettings, packagePath) => {
55
- const notificationCommand = `${packagePath}/hooks/notification.sh`;
56
- const protectFilesCommand = `${packagePath}/hooks/protect-files.sh`;
57
- const statusLineCommand = `node ${packagePath}/hooks/status-line/index.js`;
58
- return {
59
- ...existingSettings,
60
- $schema: baseSettings.$schema ?? existingSettings.$schema,
61
- attribution: { ...(baseSettings.attribution ?? {}), ...(existingSettings.attribution ?? {}) },
62
- permissions: {
63
- ...(existingSettings.permissions ?? {}),
64
- ...(baseSettings.permissions ?? {}),
65
- ask: mergeStringArrays(baseSettings.permissions?.ask, existingSettings.permissions?.ask),
66
- deny: mergeStringArrays(baseSettings.permissions?.deny, existingSettings.permissions?.deny),
67
- },
68
- hooks: {
69
- ...existingSettings.hooks,
70
- Notification: upsertHookMatcherEntry(existingSettings.hooks.Notification, "", notificationCommand),
71
- PreToolUse: upsertHookMatcherEntry(existingSettings.hooks.PreToolUse, "Edit|Write", protectFilesCommand),
72
- },
73
- statusLine: {
74
- ...existingSettings.statusLine,
75
- ...baseSettings.statusLine,
76
- type: "command",
77
- command: statusLineCommand,
78
- },
79
- };
80
- };
81
- export const renderClaude = async (targetDir, force, packageRoot) => {
82
- const templatePath = path.join(packageRoot, "claude/settings.base.json");
83
- const baseSettings = await readClaudeSettings(templatePath);
84
- const packagePath = "./node_modules/pasika/claude";
85
- const notificationEntries = upsertHookMatcherEntry(baseSettings.hooks.Notification, "", `${packagePath}/hooks/notification.sh`);
86
- const preToolUseEntries = upsertHookMatcherEntry(baseSettings.hooks.PreToolUse, "Edit|Write", `${packagePath}/hooks/protect-files.sh`);
87
- const settings = {
88
- ...baseSettings,
89
- hooks: {
90
- ...baseSettings.hooks,
91
- Notification: notificationEntries,
92
- PreToolUse: preToolUseEntries,
93
- },
94
- statusLine: {
95
- ...baseSettings.statusLine,
96
- type: "command",
97
- command: `node ${packagePath}/hooks/status-line/index.js`,
98
- },
99
- };
100
- const outputDir = path.join(targetDir, ".claude");
101
- const outputPath = path.join(outputDir, "settings.json");
102
- const hooksOutputDir = path.join(outputDir, "hooks");
103
- await mkdir(outputDir, { recursive: true });
104
- await mkdir(hooksOutputDir, { recursive: true });
105
- const finalSettings = !force && (await fileExists(outputPath))
106
- ? mergeClaudeSettings(await readClaudeSettings(outputPath), settings, packagePath)
107
- : settings;
108
- await writeFile(outputPath, `${JSON.stringify(finalSettings, null, 2)}\n`, "utf8");
109
- const outputs = [outputPath];
110
- const hookDocFiles = ["AGENTS.md", "CLAUDE.md"];
111
- for (const fileName of hookDocFiles) {
112
- const sourcePath = path.join(packageRoot, "claude/hooks", fileName);
113
- if (!(await fileExists(sourcePath))) {
114
- continue;
115
- }
116
- const destinationPath = path.join(hooksOutputDir, fileName);
117
- await copyFile(sourcePath, destinationPath);
118
- outputs.push(destinationPath);
119
- }
120
- return outputs;
121
- };
122
- const isDirectRun = process.argv[1] ? path.resolve(process.argv[1]) === __filename : false;
123
- if (isDirectRun) {
124
- const args = process.argv.slice(2);
125
- const getArgValue = (flag) => {
126
- const index = args.indexOf(flag);
127
- if (index === -1) {
128
- return undefined;
129
- }
130
- return args[index + 1];
131
- };
132
- const targetDir = path.resolve(process.cwd(), getArgValue("--target-dir") ?? ".");
133
- const force = args.includes("--force");
134
- const packageRoot = path.resolve(__dirname, "../..");
135
- try {
136
- const outputs = await renderClaude(targetDir, force, packageRoot);
137
- for (const output of outputs) {
138
- process.stdout.write(`Wrote ${output}\n`);
139
- }
140
- }
141
- catch (error) {
142
- process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
143
- process.exit(1);
144
- }
145
- }
@@ -1,2 +0,0 @@
1
- import type { Rule } from "eslint";
2
- export declare const organizationImportsRule: Rule.RuleModule;
@@ -1,7 +0,0 @@
1
- import { RuleSeverity, styleguide } from "zirka";
2
- const { eslintConfig } = styleguide({
3
- node: RuleSeverity.Error,
4
- typescript: RuleSeverity.Error,
5
- ignores: ["dist/**", "node_modules/**", "prettier.config.mjs"],
6
- });
7
- export default eslintConfig;
@@ -1,59 +0,0 @@
1
- #!/usr/bin/env node
2
- import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
- import { renderClaude } from "../claude/scripts/render-settings.js";
5
- const __filename = fileURLToPath(import.meta.url);
6
- const __dirname = path.dirname(__filename);
7
- const packageRoot = path.basename(path.dirname(__dirname)) === "dist" ? path.resolve(__dirname, "../..") : path.resolve(__dirname, "..");
8
- const args = process.argv.slice(2);
9
- const helpText = `pasika
10
-
11
- Usage:
12
- pasika claude [--target-dir <path>] [--force]
13
-
14
- Platforms:
15
- claude
16
-
17
- Examples:
18
- npx pasika claude
19
- npx pasika claude --force
20
-
21
- Behavior:
22
- merges into existing .claude/settings.json by default
23
- replaces it only when --force is passed
24
- `;
25
- const getFlagValue = (flag) => {
26
- const index = args.indexOf(flag);
27
- if (index === -1) {
28
- return undefined;
29
- }
30
- return args[index + 1];
31
- };
32
- const hasFlag = (flag) => args.includes(flag);
33
- const platform = args[0];
34
- if (!platform || hasFlag("--help") || hasFlag("-h")) {
35
- process.stdout.write(helpText);
36
- process.exit(platform ? 0 : 1);
37
- }
38
- const targetDir = path.resolve(process.cwd(), getFlagValue("--target-dir") ?? ".");
39
- const force = hasFlag("--force");
40
- const run = async () => {
41
- switch (platform) {
42
- case "claude":
43
- return renderClaude(targetDir, force, packageRoot);
44
- default:
45
- throw new Error(`Unknown platform: ${platform}`);
46
- }
47
- };
48
- try {
49
- const outputs = await run();
50
- process.stdout.write(`Initialized ${platform} in ${targetDir}\n`);
51
- for (const output of outputs) {
52
- process.stdout.write(`Wrote ${output}\n`);
53
- }
54
- }
55
- catch (error) {
56
- process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
57
- process.stderr.write(helpText);
58
- process.exit(1);
59
- }
@@ -1,27 +0,0 @@
1
- # Agent Conventions
2
-
3
- Repo-wide engineering rules and commit hygiene for AI agents.
4
-
5
- ## Data Contracts
6
-
7
- - Use Zod schemas for runtime validation; prioritize them over ad hoc type guards.
8
- - Prefer strict-by-default data contracts. Make fields required unless real runtime evidence shows they must be nullable or optional.
9
- - Do not preserve backward compatibility by default. Prefer simplifying and updating all in-repo call sites unless the user explicitly asks for compatibility.
10
-
11
- ## Code Quality
12
-
13
- - Never use `eslint-disable` directives. Fix issues properly.
14
- - Never commit with the `--no-verify` flag.
15
-
16
- ## Browser Verification
17
-
18
- - Use `agent-browser` to verify browser behavior when a task requires it.
19
- - Before using `agent-browser`, run `agent-browser upgrade`.
20
- - Run `agent-browser --help` for available commands and `agent-browser <command> --help` for command-specific usage.
21
-
22
- ## Vulyk
23
-
24
- - Make Vulyk registry changes with Vulyk commands, not by editing generated files.
25
- - Treat `AGENTS.md`, `docs/external`, `.agents/skills`, and `.vulyk` as generated output.
26
- - Use ephemeral commands such as `npx vulyk@latest ...`.
27
- - Do not add Vulyk to `package.json` only to run the CLI.
@@ -1,30 +0,0 @@
1
- # Claude Hooks
2
-
3
- `pasika` ships the reusable shared Claude hooks.
4
-
5
- ## `status-line/index.js`
6
-
7
- Displays a two-line status view with:
8
-
9
- - model name
10
- - current folder
11
- - git branch when available
12
- - used context percentage as a 10-segment bar
13
- - session cost
14
- - session duration
15
-
16
- ## `notification.sh`
17
-
18
- Sends a desktop notification when Claude needs attention.
19
-
20
- - macOS: `terminal-notifier` with click-to-focus VS Code behavior, falls back to `osascript`
21
- - Linux: `notify-send`
22
- - Windows: PowerShell message box
23
-
24
- ## `protect-files.sh`
25
-
26
- Blocks edits to:
27
-
28
- - `.env` files
29
- - `package-lock.json`
30
- - files inside `.git/`
@@ -1,69 +0,0 @@
1
- # Code Organization Guide
2
-
3
- This guide covers how to organize code in the repository's `src/` tree — placement, extraction, and module conventions — so structure stays consistent across contributors and reviewers.
4
-
5
- ## How To Organize a Component
6
-
7
- Use this no matter whether you are adding a new component or extracting from existing code; the steps below apply in any case.
8
-
9
- 1. Read the [Application Architecture Reference](references/application-architecture-reference.md) to identify where the item belongs in `src/`.
10
- 2. Pick the component's placement per the [Component Placement Rule](rules/component-placement-rule.md).
11
- 3. Classify the component as smart or dumb per the [Smart vs Dumb Component Rule](rules/smart-vs-dumb-component-rule.md).
12
- 4. Keep each component file to exactly one React component per the [No Mixed Concerns Rule](rules/no-mixed-concerns-rule.md).
13
- 5. Extract interactive elements per the [Interactive Component Rule](rules/interactive-component-rule.md).
14
- 6. Extract sole-state-owner blocks per the [Sole State Owner Rule](rules/sole-state-owner-rule.md).
15
- 7. Extract repeated structures per the [Repeated Structure Rule](rules/repeated-structure-rule.md).
16
- 8. Extract a group of elements when one clear component name describes it per the [Nameable Visual Concept Rule](rules/nameable-visual-concept-rule.md).
17
- 9. For every component extracted in steps 5–8, repeat steps 2–4.
18
- 10. Repeat steps 5–8 for an extracted component only when one of those extraction conditions applies to its JSX; stop when no new component is required.
19
- 11. Nest a component when it gains exclusive children per the [Folder Nesting Rule](rules/folder-nesting-rule.md).
20
- 12. Keep the component's JSX clean per the [JSX Hygiene Rule](rules/jsx-hygiene-rule.md).
21
- 13. Follow the [Exports and Imports Rule](rules/exports-and-imports-rule.md) so every component module has predictable exports and import paths.
22
-
23
- ## How To Organize a Type or Schema
24
-
25
- Use this when adding or moving a type or schema.
26
-
27
- 1. Read the [Application Architecture Reference](references/application-architecture-reference.md) to identify where the item belongs in `src/`.
28
- 2. Follow the [Types and Schemas Rule](rules/types-and-schemas-rule.md).
29
- 3. Follow the [Exports and Imports Rule](rules/exports-and-imports-rule.md) so the type or schema module has predictable exports and import paths.
30
-
31
- ## How To Organize a Constant
32
-
33
- Use this when adding or moving a constant.
34
-
35
- 1. Read the [Application Architecture Reference](references/application-architecture-reference.md) to identify where the item belongs in `src/`.
36
- 2. Follow the [Constants Rule](rules/constants-rule.md).
37
- 3. Follow the [Exports and Imports Rule](rules/exports-and-imports-rule.md) so the constant module has predictable exports and import paths.
38
-
39
- ## How To Organize a Utility
40
-
41
- Use this when adding, extracting, or moving a pure function.
42
-
43
- 1. Read the [Application Architecture Reference](references/application-architecture-reference.md) to identify where the item belongs in `src/`.
44
- 2. Follow the [Utilities Rule](rules/utilities-rule.md).
45
- 3. Follow the [Exports and Imports Rule](rules/exports-and-imports-rule.md) so the utility module has predictable exports and import paths.
46
-
47
- ## How To Organize Configuration
48
-
49
- Use this when adding or moving an application configuration module.
50
-
51
- 1. Read the [Application Architecture Reference](references/application-architecture-reference.md) to identify where the item belongs in `src/`.
52
- 2. Follow the [Configuration Rule](rules/configuration-rule.md).
53
- 3. Follow the [Exports and Imports Rule](rules/exports-and-imports-rule.md) so the configuration module has predictable exports and import paths.
54
-
55
- ## How To Organize a Custom Hook
56
-
57
- Use this when extracting or moving a custom hook.
58
-
59
- 1. Read the [Application Architecture Reference](references/application-architecture-reference.md) to identify where the item belongs in `src/`.
60
- 2. Follow the [Hook Extraction Rule](rules/hook-extraction-rule.md).
61
- 3. Follow the [Exports and Imports Rule](rules/exports-and-imports-rule.md) so the hook module has predictable exports and import paths.
62
-
63
- ## How To Organize a Locale String
64
-
65
- Use this when adding or moving user-facing text.
66
-
67
- 1. Read the [Application Architecture Reference](references/application-architecture-reference.md) to identify where the item belongs in `src/`.
68
- 2. Follow the [Locales Rule](rules/locales-rule.md).
69
- 3. Follow the [Exports and Imports Rule](rules/exports-and-imports-rule.md) so the locale module has predictable exports and import paths.
@@ -1,149 +0,0 @@
1
- # Application Architecture Reference
2
-
3
- Use this reference to look up the canonical shape of the repository's `src/` tree and its Layer Model.
4
-
5
- ## Layer Model
6
-
7
- This section describes the repository's five layers and their dependency direction. Use it to decide where a component or support file belongs and which layers it can import from.
8
-
9
- | Layer | Path | Permitted contents | Imports from |
10
- | -------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
11
- | `app` | `src/app/` | [Next.js App Router routing files](https://nextjs.org/docs/app/getting-started/project-structure#routing-files), [metadata assets](https://nextjs.org/docs/app/getting-started/project-structure#metadata-file-conventions), and [styles required by routing files](https://nextjs.org/docs/app/getting-started/css) | compositions, features, shared, root |
12
- | `compositions` | `src/compositions/` | Components and support files (`hooks/`, `types/`, `schemas/`, `constants/`, `utils/`) | compositions, features, shared, root |
13
- | `features` | `src/features/<feature>/` | Components and support files (`hooks/`, `types/`, `schemas/`, `constants/`, `utils/`). `src/features/` itself holds feature folders only — never components or support folders | same feature, shared, root |
14
- | `shared` | `src/shared/` | Components and support files (`hooks/`, `types/`, `schemas/`, `constants/`, `utils/`) | shared, root |
15
- | `root` | `src/` | App-wide support folders: `hooks/`, `types/`, `schemas/`, `constants/`, `utils/`, `config/`, and `locales/` | root |
16
-
17
- ## Closest Common Folder (CCF)
18
-
19
- The CCF determines the placement of components, hooks, types, schemas, constants, and utilities. It is the closest folder shared by the files under `src/` that use the item.
20
-
21
- ```text
22
- src/features/payments/payment-card.tsx
23
- src/features/payments/payment-history.tsx
24
- └─ both import StatusBadge
25
-
26
- CCF: src/features/payments/
27
- Location: src/features/payments/status-badge.tsx
28
- ```
29
-
30
- For components, imports from `src/app/` and configuration modules do not affect the CCF. A component used by `src/app/` and a feature therefore stays with the feature.
31
-
32
- ```text
33
- src/app/products/page.tsx ─┐
34
- src/features/products/product-card.tsx ─┴─ imports ProductPrice
35
-
36
- CCF and location: src/features/products/product-price.tsx
37
- ```
38
-
39
- A component with no consumers outside `src/app/` or configuration modules stays in the feature it represents or supports. When no existing feature applies, it creates a feature folder.
40
-
41
- ```text
42
- src/app/search/page.tsx → SearchForm → src/features/search/search-form.tsx
43
-
44
- src/app/account/page.tsx ─┐
45
- src/app/orders/page.tsx ─┴→ AccountNav → src/features/account/account-nav.tsx
46
- ```
47
-
48
- When a component has consumers both inside and outside `src/compositions/`, only the consumers outside `src/compositions/` count. When all consumers are in `src/compositions/`, that folder counts.
49
-
50
- ```text
51
- src/compositions/checkout.tsx ─┐
52
- src/features/payments/payment-summary.tsx ─┴→ Total → src/features/payments/total.tsx
53
-
54
- src/compositions/checkout.tsx ─┐
55
- src/compositions/receipt.tsx ─┴→ Total → src/compositions/total.tsx
56
- ```
57
-
58
- A component stays at a nested component folder, feature folder, `src/compositions/`, or `src/shared/`. A CCF of `src/features/` places it in `src/shared/`.
59
-
60
- ```text
61
- src/features/payments/payment-card.tsx ─┐
62
- src/features/orders/order-card.tsx ─┴→ StatusBadge → src/shared/status-badge.tsx
63
- ```
64
-
65
- For a support file imported by a file under `src/app/`, use the matching root support folder, such as `src/hooks/` or `src/utils/`. Otherwise, the CCF passes through a support folder to its parent folder, and the file lives in the matching support folder directly below that parent.
66
-
67
- ```text
68
- src/app/products/page.tsx → useSearch → src/hooks/use-search.ts
69
- src/features/payments/hooks/use-payment.ts → formatAmount → src/features/payments/utils/format-amount.ts
70
- src/features/payments/payment-card.tsx ─┐
71
- src/features/orders/order-card.tsx ─┴→ formatDate → src/utils/format-date.ts
72
- ```
73
-
74
- ## Application Structure
75
-
76
- This tree shows the canonical directory layout and naming conventions for files and folders inside `src/`. It is included so readers can verify their structure matches the project standard at a glance.
77
-
78
- ```
79
- src/
80
- ├── app/ # Next.js framework-convention files, assets, and route styles
81
- ├── compositions/ # Components stay flat siblings by default
82
- │ ├── <ComponentA>.tsx # Smart component (PascalCase-named)
83
- │ ├── <component-b>.tsx # Dumb component (kebab-case-named)
84
- │ ├── <component-part>.tsx # Dumb component — shared by A and B, so it stays flat too
85
- │ ├── <NestedComponent>/ # Smart (PascalCase-named) — or kebab-case-named for dumb; nested only once it gains exclusive children
86
- │ │ ├── index.ts # Named-re-exports only the nested parent
87
- │ │ ├── <NestedComponent>.tsx # Smart (PascalCase-named) — or kebab-case-named for dumb
88
- │ │ ├── <nested-part>.tsx # Exclusive child — not in index.ts
89
- │ │ ├── hooks/ # Nested-scoped support folders
90
- │ │ ├── types/
91
- │ │ ├── schemas/
92
- │ │ ├── constants/
93
- │ │ └── utils/
94
- │ ├── hooks/ # Composition-scoped custom hooks
95
- │ ├── types/ # Composition-scoped TypeScript types
96
- │ ├── schemas/ # Composition-scoped validation schemas
97
- │ ├── constants/ # Composition-scoped constants
98
- │ └── utils/ # Composition-scoped pure functions
99
- ├── features/ # Feature folders only — no components, no support folders here
100
- │ └── <feature>/ # One folder per feature — a feature can hold several components
101
- │ ├── <ComponentA>.tsx # Smart component (PascalCase-named)
102
- │ ├── <component-b>.tsx # Dumb component (kebab-case-named)
103
- │ ├── <component-part>.tsx # Dumb component — shared by A and B, so it stays flat too
104
- │ ├── <NestedComponent>/ # Smart (PascalCase-named) — or kebab-case-named for dumb; nested only once it gains exclusive children
105
- │ │ ├── index.ts # Named-re-exports only the nested parent
106
- │ │ ├── <NestedComponent>.tsx # Smart (PascalCase-named) — or kebab-case-named for dumb
107
- │ │ ├── <nested-part>.tsx # Exclusive child — not in index.ts
108
- │ │ ├── hooks/ # Nested-scoped support folders
109
- │ │ ├── types/
110
- │ │ ├── schemas/
111
- │ │ ├── constants/
112
- │ │ └── utils/
113
- │ ├── hooks/ # Feature-scoped custom hooks
114
- │ ├── types/ # Feature-scoped TypeScript types
115
- │ ├── schemas/ # Feature-scoped validation schemas
116
- │ ├── constants/ # Feature-scoped constants
117
- │ └── utils/ # Feature-scoped pure functions
118
- ├── shared/ # Components stay flat siblings by default
119
- │ ├── <ComponentA>.tsx # Smart component (PascalCase-named)
120
- │ ├── <component-b>.tsx # Dumb component (kebab-case-named)
121
- │ ├── <component-part>.tsx # Dumb component — shared by A and B, so it stays flat too
122
- │ ├── <NestedComponent>/ # Smart (PascalCase-named) — or kebab-case-named for dumb; nested only once it gains exclusive children
123
- │ │ ├── index.ts # Named-re-exports only the nested parent
124
- │ │ ├── <NestedComponent>.tsx # Smart (PascalCase-named) — or kebab-case-named for dumb
125
- │ │ ├── <nested-part>.tsx # Exclusive child — not in index.ts
126
- │ │ ├── hooks/ # Nested-scoped support folders
127
- │ │ ├── types/
128
- │ │ ├── schemas/
129
- │ │ ├── constants/
130
- │ │ └── utils/
131
- │ ├── hooks/ # Shared-scoped custom hooks
132
- │ ├── types/ # Shared-scoped TypeScript types
133
- │ ├── schemas/ # Shared-scoped validation schemas
134
- │ ├── constants/ # Shared-scoped constants
135
- │ └── utils/ # Shared-scoped pure functions
136
- ├── hooks/ # App-wide custom hooks
137
- ├── types/ # App-wide TypeScript types
138
- ├── schemas/ # App-wide validation schemas
139
- ├── constants/ # App-wide constants
140
- ├── utils/ # App-wide pure functions
141
- ├── config/ # Configuration modules that centralize application behavior
142
- │ └── <config-name>/ # One folder per configuration module
143
- │ ├── index.ts # Configuration-module entry point
144
- │ ├── types/ # Config-only TypeScript types
145
- │ ├── schemas/ # Config-only validation schemas
146
- │ └── utils/ # Config-only pure functions
147
- └── locales/ # App-wide locale strings
148
- └── index.ts # Single locale-registration file
149
- ```
@@ -1,119 +0,0 @@
1
- # Component Placement Rule
2
-
3
- Without clear placement, it is hard to tell where a component belongs and reuse can create tangled dependencies. This rule gives each component a specific place in the application structure.
4
-
5
- - A component that imports from two or more feature folders MUST live in `src/compositions/` and is, by definition, a composition.
6
- - A component with no consumers outside `src/app/` or configuration modules, and that does not import from two or more feature folders, MUST live in the feature folder it represents or supports. If no existing feature applies, it MUST introduce a new feature folder.
7
- - A component with at least one consumer outside `src/app/` and configuration modules MUST live in its CCF, calculated without imports from `src/app/` or configuration modules.
8
- - When calculating a component's CCF, consumers under `src/compositions/` MUST count only when no consumer is outside `src/compositions/`.
9
- - A component whose CCF is `src/features/` MUST live in `src/shared/`.
10
- - `src/app/` MUST contain [Next.js App Router framework-convention files and assets](https://nextjs.org/docs/app/getting-started/project-structure#routing-files), plus styles required by routing files, but MUST NOT contain ordinary components or support folders.
11
- ## Incorrect — Cross-Feature Component Duplicated
12
-
13
- ```text
14
- src/features/
15
- ├── donation/
16
- │ ├── DonationCard.tsx
17
- │ └── status-badge.tsx
18
- └── stream/
19
- ├── StreamCard.tsx
20
- └── status-badge.tsx
21
- ```
22
-
23
- Why: two feature folders contain duplicate copies because neither feature may import from the other.
24
-
25
- ## Correct — Cross-Feature Component in `src/shared/`
26
-
27
- ```text
28
- src/
29
- ├── features/
30
- │ ├── donation/
31
- │ │ └── DonationCard.tsx
32
- │ └── stream/
33
- │ └── StreamCard.tsx
34
- └── shared/
35
- └── status-badge.tsx
36
- ```
37
-
38
- Why: the component's CCF is `src/features/`, so it lives in `src/shared/`.
39
-
40
- ## Incorrect — Multi-Feature Composition Inside One Feature
41
-
42
- ```tsx
43
- // src/features/billing/billing-aggregate-view.tsx
44
- import { BillingPanel } from "./BillingPanel";
45
- import { HomeBanner } from "@/features/home/HomeBanner";
46
- ```
47
-
48
- Why: the component imports from two feature folders, so it cannot live in either feature folder.
49
-
50
- ## Correct — Multi-Feature Component in `src/compositions/`
51
-
52
- ```tsx
53
- // src/compositions/billing-aggregate-view.tsx
54
- import { BillingPanel } from "@/features/billing/BillingPanel";
55
- import { HomeBanner } from "@/features/home/HomeBanner";
56
- ```
57
-
58
- Why: the component imports from two feature folders, so it lives in `src/compositions/`.
59
-
60
- ## Incorrect — Ordinary Component Under `src/app/`
61
-
62
- ```text
63
- src/app/contact/
64
- ├── _components/
65
- │ └── contact-page-content.tsx
66
- └── page.tsx
67
- ```
68
-
69
- Why: the route folder contains ordinary component structure even though `src/app/` is reserved for framework-convention files, assets, and styles.
70
-
71
- ## Correct — `src/app/` Imports a Page Composition
72
-
73
- ```text
74
- src/
75
- ├── app/contact/
76
- │ └── page.tsx
77
- ├── compositions/
78
- │ └── contact-page-content.tsx
79
- └── features/
80
- ├── contact/
81
- │ └── contact-information.tsx
82
- └── location/
83
- └── office-location.tsx
84
- ```
85
-
86
- ```tsx
87
- // src/compositions/contact-page-content.tsx
88
- import { ContactInformation } from "@/features/contact/contact-information";
89
- import { OfficeLocation } from "@/features/location/office-location";
90
- import { locales } from "@/locales";
91
-
92
- export function ContactPageContent(): React.JSX.Element {
93
- return (
94
- <main>
95
- <header>
96
- <h1>{locales.contactPageTitle}</h1>
97
- <p>{locales.contactPageDescription}</p>
98
- </header>
99
- <section>
100
- <h2>{locales.contactInformationHeading}</h2>
101
- <ContactInformation />
102
- </section>
103
- <section>
104
- <h2>{locales.officeLocationHeading}</h2>
105
- <OfficeLocation />
106
- </section>
107
- </main>
108
- );
109
- }
110
-
111
- // src/app/contact/page.tsx
112
- import { ContactPageContent } from "@/compositions/contact-page-content";
113
-
114
- export default function Page(): React.JSX.Element {
115
- return <ContactPageContent />;
116
- }
117
- ```
118
-
119
- Why: `src/app/` contains the routing file while the composition provides the contact page layout and combines components from two feature folders.
@@ -1,42 +0,0 @@
1
- # Configuration Rule
2
-
3
- Configuration modules centralize values that control application behavior. This rule keeps each module and the files that only support it together in `src/config/`.
4
-
5
- - A configuration module is an app-wide module that selects or parameterizes application behavior.
6
- - All configuration modules MUST live in `src/config/`.
7
- - A configuration module MUST be one `src/config/<config-name>/` folder with `index.ts` as its entry point.
8
- - A type, schema, or utility used only to implement one configuration module MUST be extracted even with one consumer.
9
- - An extracted configuration type, schema, or utility MUST live in its matching dedicated folder under `src/config/<config-name>/`.
10
- - An extracted configuration type, schema, or utility MUST move to its matching root support folder when a consumer outside its configuration module imports it.
11
-
12
- ## Incorrect — Supporting Schema Outside Its Config Folder
13
-
14
- ```text
15
- src/config/
16
- ├── home-feed/
17
- │ └── index.ts
18
- └── home-feed-schema.ts
19
- ```
20
-
21
- Why: the schema sits outside the `home-feed/` configuration folder.
22
-
23
- ## Correct — Configuration Module with Its Supporting Schema
24
-
25
- ```text
26
- src/config/
27
- └── home-feed/
28
- ├── index.ts
29
- └── schemas/
30
- └── index.ts
31
- ```
32
-
33
- ```ts
34
- // src/config/home-feed/index.ts
35
- import { homeFeedConfigSchema } from "./schemas";
36
-
37
- export const homeFeedConfig = homeFeedConfigSchema.parse({
38
- freshVideoMaxAgeDays: 7,
39
- });
40
- ```
41
-
42
- Why: the configuration module and its supporting schema are grouped in the same configuration folder.