mvc-kit 2.13.0 → 2.13.2

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 (218) hide show
  1. package/BEST_PRACTICES.md +1390 -0
  2. package/agent-config/claude-code/agents/mvc-kit-architect.md +8 -3
  3. package/agent-config/claude-code/skills/{guide → mvc-kit}/SKILL.md +10 -1
  4. package/agent-config/lib/install-claude.mjs +39 -110
  5. package/examples/primitive/channel.ts +109 -0
  6. package/examples/primitive/collection.ts +118 -0
  7. package/examples/primitive/controller.ts +118 -0
  8. package/examples/primitive/counter.ts +108 -0
  9. package/examples/primitive/env.d.ts +1 -0
  10. package/examples/primitive/eventbus.ts +77 -0
  11. package/examples/primitive/feed.ts +162 -0
  12. package/examples/primitive/model.ts +82 -0
  13. package/examples/primitive/pagination.ts +91 -0
  14. package/examples/primitive/pending.ts +189 -0
  15. package/examples/primitive/persistent-collection.ts +116 -0
  16. package/examples/primitive/resource.ts +114 -0
  17. package/examples/primitive/selection.ts +96 -0
  18. package/examples/primitive/sorting.ts +112 -0
  19. package/examples/primitive/timer.ts +58 -0
  20. package/examples/primitive/trackable.ts +225 -0
  21. package/examples/primitive/tsconfig.json +20 -0
  22. package/examples/primitive/viewmodel-service.ts +161 -0
  23. package/examples/react/AuthExample/index.html +12 -0
  24. package/examples/react/AuthExample/src/App.tsx +29 -0
  25. package/examples/react/AuthExample/src/components/AdminPage.tsx +51 -0
  26. package/examples/react/AuthExample/src/components/AppHeader.tsx +32 -0
  27. package/examples/react/AuthExample/src/components/AuthGuard.tsx +50 -0
  28. package/examples/react/AuthExample/src/components/AuthScreen.tsx +181 -0
  29. package/examples/react/AuthExample/src/components/DashboardPage.tsx +41 -0
  30. package/examples/react/AuthExample/src/components/ProfilePage.tsx +44 -0
  31. package/examples/react/AuthExample/src/components/Toast.tsx +41 -0
  32. package/examples/react/AuthExample/src/env.d.ts +10 -0
  33. package/examples/react/AuthExample/src/events/AppEventBus.ts +7 -0
  34. package/examples/react/AuthExample/src/main.tsx +10 -0
  35. package/examples/react/AuthExample/src/mock/api.ts +78 -0
  36. package/examples/react/AuthExample/src/models/LoginFormModel.ts +19 -0
  37. package/examples/react/AuthExample/src/models/RegisterFormModel.ts +25 -0
  38. package/examples/react/AuthExample/src/services/AuthService.ts +21 -0
  39. package/examples/react/AuthExample/src/styles.css +445 -0
  40. package/examples/react/AuthExample/src/types/auth.ts +12 -0
  41. package/examples/react/AuthExample/src/viewmodels/AuthViewModel.ts +111 -0
  42. package/examples/react/AuthExample/tsconfig.json +22 -0
  43. package/examples/react/AuthExample/vite.config.ts +18 -0
  44. package/examples/react/ComplexApp/index.html +12 -0
  45. package/examples/react/ComplexApp/src/App.tsx +17 -0
  46. package/examples/react/ComplexApp/src/channels/ActivityChannel.ts +24 -0
  47. package/examples/react/ComplexApp/src/channels/DashboardChannel.ts +26 -0
  48. package/examples/react/ComplexApp/src/channels/ErrorsChannel.ts +5 -0
  49. package/examples/react/ComplexApp/src/channels/LatencyChannel.ts +5 -0
  50. package/examples/react/ComplexApp/src/channels/OrdersChannel.ts +5 -0
  51. package/examples/react/ComplexApp/src/channels/RevenueChannel.ts +5 -0
  52. package/examples/react/ComplexApp/src/channels/TrafficChannel.ts +5 -0
  53. package/examples/react/ComplexApp/src/channels/UsersMetricChannel.ts +5 -0
  54. package/examples/react/ComplexApp/src/collections/DashboardCollection.ts +6 -0
  55. package/examples/react/ComplexApp/src/collections/ErrorsCollection.ts +3 -0
  56. package/examples/react/ComplexApp/src/collections/LatencyCollection.ts +3 -0
  57. package/examples/react/ComplexApp/src/collections/OrdersCollection.ts +3 -0
  58. package/examples/react/ComplexApp/src/collections/RevenueCollection.ts +3 -0
  59. package/examples/react/ComplexApp/src/collections/TrafficCollection.ts +3 -0
  60. package/examples/react/ComplexApp/src/collections/UsersMetricCollection.ts +3 -0
  61. package/examples/react/ComplexApp/src/components/activity/ActivityFeed.tsx +31 -0
  62. package/examples/react/ComplexApp/src/components/activity/ActivityItemRow.tsx +35 -0
  63. package/examples/react/ComplexApp/src/components/dashboard/DashboardCard.tsx +37 -0
  64. package/examples/react/ComplexApp/src/components/dashboard/DashboardPage.tsx +34 -0
  65. package/examples/react/ComplexApp/src/components/layout/Navbar.tsx +32 -0
  66. package/examples/react/ComplexApp/src/components/layout/SocialFeedPanel.tsx +57 -0
  67. package/examples/react/ComplexApp/src/components/shared/Spinner.tsx +3 -0
  68. package/examples/react/ComplexApp/src/components/shared/StatusIndicator.tsx +13 -0
  69. package/examples/react/ComplexApp/src/components/shared/Toast.tsx +40 -0
  70. package/examples/react/ComplexApp/src/env.d.ts +10 -0
  71. package/examples/react/ComplexApp/src/events/AppEventBus.ts +7 -0
  72. package/examples/react/ComplexApp/src/main.tsx +10 -0
  73. package/examples/react/ComplexApp/src/mock-remote/MockWebSocket.ts +38 -0
  74. package/examples/react/ComplexApp/src/mock-remote/activity-api.ts +48 -0
  75. package/examples/react/ComplexApp/src/mock-remote/dashboard-generators.ts +45 -0
  76. package/examples/react/ComplexApp/src/mock-remote/delay.ts +18 -0
  77. package/examples/react/ComplexApp/src/mock-remote/social-api.ts +55 -0
  78. package/examples/react/ComplexApp/src/resources/ActivityResource.ts +12 -0
  79. package/examples/react/ComplexApp/src/resources/SocialFeedResource.ts +17 -0
  80. package/examples/react/ComplexApp/src/styles.css +463 -0
  81. package/examples/react/ComplexApp/src/types/activity.ts +8 -0
  82. package/examples/react/ComplexApp/src/types/dashboard.ts +5 -0
  83. package/examples/react/ComplexApp/src/types/social.ts +8 -0
  84. package/examples/react/ComplexApp/src/types/users.ts +6 -0
  85. package/examples/react/ComplexApp/src/viewmodels/ActivityFeedViewModel.ts +68 -0
  86. package/examples/react/ComplexApp/src/viewmodels/AppStateViewModel.ts +26 -0
  87. package/examples/react/ComplexApp/src/viewmodels/DashboardCardViewModel.ts +69 -0
  88. package/examples/react/ComplexApp/src/viewmodels/ErrorsCardViewModel.ts +9 -0
  89. package/examples/react/ComplexApp/src/viewmodels/LatencyCardViewModel.ts +9 -0
  90. package/examples/react/ComplexApp/src/viewmodels/OrdersCardViewModel.ts +9 -0
  91. package/examples/react/ComplexApp/src/viewmodels/RevenueCardViewModel.ts +9 -0
  92. package/examples/react/ComplexApp/src/viewmodels/SocialFeedViewModel.ts +39 -0
  93. package/examples/react/ComplexApp/src/viewmodels/TrafficCardViewModel.ts +9 -0
  94. package/examples/react/ComplexApp/src/viewmodels/UsersMetricCardViewModel.ts +9 -0
  95. package/examples/react/ComplexApp/tsconfig.json +22 -0
  96. package/examples/react/ComplexApp/vite.config.ts +18 -0
  97. package/examples/react/FullApp/index.html +12 -0
  98. package/examples/react/FullApp/src/App.tsx +28 -0
  99. package/examples/react/FullApp/src/collections/ConversationsCollection.ts +4 -0
  100. package/examples/react/FullApp/src/collections/LocationsCollection.ts +4 -0
  101. package/examples/react/FullApp/src/components/auth/LoginPage.tsx +80 -0
  102. package/examples/react/FullApp/src/components/dashboard/DashboardPage.tsx +29 -0
  103. package/examples/react/FullApp/src/components/dashboard/RecentActivityCard.tsx +35 -0
  104. package/examples/react/FullApp/src/components/dashboard/StatsCard.tsx +19 -0
  105. package/examples/react/FullApp/src/components/layout/AppShell.tsx +31 -0
  106. package/examples/react/FullApp/src/components/layout/Header.tsx +25 -0
  107. package/examples/react/FullApp/src/components/layout/Sidebar.tsx +29 -0
  108. package/examples/react/FullApp/src/components/locations/LocationFilters.tsx +60 -0
  109. package/examples/react/FullApp/src/components/locations/LocationForm.tsx +112 -0
  110. package/examples/react/FullApp/src/components/locations/LocationProfilePage.tsx +81 -0
  111. package/examples/react/FullApp/src/components/locations/LocationsPage.tsx +127 -0
  112. package/examples/react/FullApp/src/components/messaging/ConversationList.tsx +59 -0
  113. package/examples/react/FullApp/src/components/messaging/MessageBubble.tsx +22 -0
  114. package/examples/react/FullApp/src/components/messaging/MessageThread.tsx +100 -0
  115. package/examples/react/FullApp/src/components/messaging/MessagingPage.tsx +52 -0
  116. package/examples/react/FullApp/src/components/shared/ErrorBanner.tsx +3 -0
  117. package/examples/react/FullApp/src/components/shared/Spinner.tsx +7 -0
  118. package/examples/react/FullApp/src/components/shared/Toast.tsx +41 -0
  119. package/examples/react/FullApp/src/components/users/UserFilters.tsx +59 -0
  120. package/examples/react/FullApp/src/components/users/UsersPage.tsx +80 -0
  121. package/examples/react/FullApp/src/components/users/UsersTable.tsx +52 -0
  122. package/examples/react/FullApp/src/env.d.ts +10 -0
  123. package/examples/react/FullApp/src/events/AppEventBus.ts +7 -0
  124. package/examples/react/FullApp/src/main.tsx +10 -0
  125. package/examples/react/FullApp/src/mock/delay.ts +21 -0
  126. package/examples/react/FullApp/src/mock/locations.ts +76 -0
  127. package/examples/react/FullApp/src/mock/messages.ts +237 -0
  128. package/examples/react/FullApp/src/mock/users.ts +84 -0
  129. package/examples/react/FullApp/src/models/LocationFormModel.ts +31 -0
  130. package/examples/react/FullApp/src/models/LoginFormModel.ts +19 -0
  131. package/examples/react/FullApp/src/resources/UsersResource.ts +12 -0
  132. package/examples/react/FullApp/src/services/AuthService.ts +18 -0
  133. package/examples/react/FullApp/src/services/LocationService.ts +23 -0
  134. package/examples/react/FullApp/src/services/MessageService.ts +65 -0
  135. package/examples/react/FullApp/src/services/UserService.ts +23 -0
  136. package/examples/react/FullApp/src/styles.css +767 -0
  137. package/examples/react/FullApp/src/types/conversation.ts +7 -0
  138. package/examples/react/FullApp/src/types/location.ts +12 -0
  139. package/examples/react/FullApp/src/types/message.ts +7 -0
  140. package/examples/react/FullApp/src/types/user.ts +10 -0
  141. package/examples/react/FullApp/src/viewmodels/AuthViewModel.ts +51 -0
  142. package/examples/react/FullApp/src/viewmodels/ConversationsViewModel.ts +89 -0
  143. package/examples/react/FullApp/src/viewmodels/DashboardViewModel.ts +56 -0
  144. package/examples/react/FullApp/src/viewmodels/LocationProfileViewModel.ts +81 -0
  145. package/examples/react/FullApp/src/viewmodels/LocationsViewModel.ts +113 -0
  146. package/examples/react/FullApp/src/viewmodels/MessageThreadViewModel.ts +83 -0
  147. package/examples/react/FullApp/src/viewmodels/UsersViewModel.ts +88 -0
  148. package/examples/react/FullApp/tsconfig.json +22 -0
  149. package/examples/react/FullApp/vite.config.ts +18 -0
  150. package/examples/react/WorkerApp/index.html +12 -0
  151. package/examples/react/WorkerApp/src/App.tsx +24 -0
  152. package/examples/react/WorkerApp/src/channels/MessagingChannel.ts +46 -0
  153. package/examples/react/WorkerApp/src/channels/WorkerStatusChannel.ts +35 -0
  154. package/examples/react/WorkerApp/src/components/auth/LoginPage.tsx +60 -0
  155. package/examples/react/WorkerApp/src/components/layout/AppShell.tsx +31 -0
  156. package/examples/react/WorkerApp/src/components/layout/Header.tsx +23 -0
  157. package/examples/react/WorkerApp/src/components/layout/Sidebar.tsx +28 -0
  158. package/examples/react/WorkerApp/src/components/messaging/ComposeBar.tsx +33 -0
  159. package/examples/react/WorkerApp/src/components/messaging/ConversationList.tsx +59 -0
  160. package/examples/react/WorkerApp/src/components/messaging/MessageBubble.tsx +45 -0
  161. package/examples/react/WorkerApp/src/components/messaging/MessageThread.tsx +93 -0
  162. package/examples/react/WorkerApp/src/components/messaging/MessagingPage.tsx +53 -0
  163. package/examples/react/WorkerApp/src/components/shared/ErrorBanner.tsx +3 -0
  164. package/examples/react/WorkerApp/src/components/shared/PendingBanner.tsx +37 -0
  165. package/examples/react/WorkerApp/src/components/shared/Spinner.tsx +7 -0
  166. package/examples/react/WorkerApp/src/components/shared/Toast.tsx +41 -0
  167. package/examples/react/WorkerApp/src/components/shift/ShiftPage.tsx +98 -0
  168. package/examples/react/WorkerApp/src/components/shift/ShiftTimer.tsx +24 -0
  169. package/examples/react/WorkerApp/src/components/shift/SiteSelector.tsx +27 -0
  170. package/examples/react/WorkerApp/src/components/sites/SiteFilters.tsx +61 -0
  171. package/examples/react/WorkerApp/src/components/sites/SitesPage.tsx +102 -0
  172. package/examples/react/WorkerApp/src/env.d.ts +10 -0
  173. package/examples/react/WorkerApp/src/events/AppEventBus.ts +7 -0
  174. package/examples/react/WorkerApp/src/main.tsx +10 -0
  175. package/examples/react/WorkerApp/src/mock/MockWebSocket.ts +38 -0
  176. package/examples/react/WorkerApp/src/mock/delay.ts +31 -0
  177. package/examples/react/WorkerApp/src/mock/messages.ts +120 -0
  178. package/examples/react/WorkerApp/src/mock/shifts.ts +57 -0
  179. package/examples/react/WorkerApp/src/mock/sites.ts +14 -0
  180. package/examples/react/WorkerApp/src/mock/workers.ts +12 -0
  181. package/examples/react/WorkerApp/src/models/ComposeMessageModel.ts +17 -0
  182. package/examples/react/WorkerApp/src/resources/ConversationsResource.ts +10 -0
  183. package/examples/react/WorkerApp/src/resources/MessagesResource.ts +32 -0
  184. package/examples/react/WorkerApp/src/resources/ShiftResource.ts +73 -0
  185. package/examples/react/WorkerApp/src/resources/SitesResource.ts +11 -0
  186. package/examples/react/WorkerApp/src/resources/WorkersResource.ts +11 -0
  187. package/examples/react/WorkerApp/src/styles.css +756 -0
  188. package/examples/react/WorkerApp/src/types/conversation.ts +7 -0
  189. package/examples/react/WorkerApp/src/types/message.ts +7 -0
  190. package/examples/react/WorkerApp/src/types/shift.ts +13 -0
  191. package/examples/react/WorkerApp/src/types/site.ts +8 -0
  192. package/examples/react/WorkerApp/src/types/worker.ts +8 -0
  193. package/examples/react/WorkerApp/src/viewmodels/AuthViewModel.ts +41 -0
  194. package/examples/react/WorkerApp/src/viewmodels/ConversationsViewModel.ts +83 -0
  195. package/examples/react/WorkerApp/src/viewmodels/MessageThreadViewModel.ts +113 -0
  196. package/examples/react/WorkerApp/src/viewmodels/ShiftViewModel.ts +147 -0
  197. package/examples/react/WorkerApp/src/viewmodels/SitesViewModel.ts +82 -0
  198. package/examples/react/WorkerApp/tsconfig.json +22 -0
  199. package/examples/react/WorkerApp/vite.config.ts +18 -0
  200. package/package.json +4 -2
  201. /package/agent-config/claude-code/skills/{guide → mvc-kit}/anti-patterns.md +0 -0
  202. /package/agent-config/claude-code/skills/{guide → mvc-kit}/api-reference.md +0 -0
  203. /package/agent-config/claude-code/skills/{guide → mvc-kit}/patterns.md +0 -0
  204. /package/agent-config/claude-code/skills/{guide → mvc-kit}/recipes.md +0 -0
  205. /package/agent-config/claude-code/skills/{guide → mvc-kit}/testing.md +0 -0
  206. /package/agent-config/claude-code/skills/{review → mvc-kit-review}/SKILL.md +0 -0
  207. /package/agent-config/claude-code/skills/{review → mvc-kit-review}/checklist.md +0 -0
  208. /package/agent-config/claude-code/skills/{scaffold → mvc-kit-scaffold}/SKILL.md +0 -0
  209. /package/agent-config/claude-code/skills/{scaffold → mvc-kit-scaffold}/templates/channel.md +0 -0
  210. /package/agent-config/claude-code/skills/{scaffold → mvc-kit-scaffold}/templates/collection.md +0 -0
  211. /package/agent-config/claude-code/skills/{scaffold → mvc-kit-scaffold}/templates/controller.md +0 -0
  212. /package/agent-config/claude-code/skills/{scaffold → mvc-kit-scaffold}/templates/eventbus.md +0 -0
  213. /package/agent-config/claude-code/skills/{scaffold → mvc-kit-scaffold}/templates/model.md +0 -0
  214. /package/agent-config/claude-code/skills/{scaffold → mvc-kit-scaffold}/templates/page-component.md +0 -0
  215. /package/agent-config/claude-code/skills/{scaffold → mvc-kit-scaffold}/templates/persistent-collection.md +0 -0
  216. /package/agent-config/claude-code/skills/{scaffold → mvc-kit-scaffold}/templates/resource.md +0 -0
  217. /package/agent-config/claude-code/skills/{scaffold → mvc-kit-scaffold}/templates/service.md +0 -0
  218. /package/agent-config/claude-code/skills/{scaffold → mvc-kit-scaffold}/templates/viewmodel.md +0 -0
@@ -6,14 +6,14 @@ tools: Read, Grep, Glob, Bash
6
6
  memory: project
7
7
  color: blue
8
8
  skills:
9
- - mvc-kit-guide
9
+ - mvc-kit
10
10
  ---
11
11
 
12
12
  You are an architecture planning agent for applications built with **mvc-kit**, a TypeScript-first reactive state management library for React. You help developers plan features by deciding which classes to create, designing state shapes, and selecting sharing patterns.
13
13
 
14
14
  ## Documentation
15
15
 
16
- The mvc-kit framework reference skill is preloaded into this agent's context. For deeper reference, read the supporting files in the mvc-kit skill directory (search for `.claude/skills/mvc-kit/` or `node_modules/mvc-kit/agent-config/claude-code/skills/guide/`):
16
+ The mvc-kit framework reference skill is preloaded into this agent's context. For deeper reference, read the supporting files in the mvc-kit skill directory (search for `.claude/skills/mvc-kit/` or `node_modules/mvc-kit/agent-config/claude-code/skills/mvc-kit/`):
17
17
 
18
18
  - `api-reference.md` — Full API reference for all classes and hooks
19
19
  - `patterns.md` — Prescribed patterns with code examples
@@ -21,7 +21,12 @@ The mvc-kit framework reference skill is preloaded into this agent's context. Fo
21
21
  - `recipes.md` — Composition recipes for real-world features
22
22
  - `testing.md` — Testing patterns
23
23
 
24
- For detailed per-class documentation, search the `.md` files colocated with source in `node_modules/mvc-kit/src/`.
24
+ For deep dives on any specific class or hook, search for the `.md` file by name in `node_modules/mvc-kit/src/`:
25
+
26
+ - **Core:** `ViewModel.md`, `Model.md`, `Collection.md`, `PersistentCollection.md`, `Resource.md`, `Service.md`, `EventBus.md`, `Channel.md`, `Controller.md`, `Trackable.md`, `singleton.md`
27
+ - **Helpers:** `Sorting.md`, `Pagination.md`, `Selection.md`, `Feed.md`, `Pending.md`, `produceDraft.md`
28
+ - **React hooks:** `react/use-local.md`, `react/use-instance.md`, `react/use-singleton.md`, `react/use-model.md`, `react/use-event-bus.md`, `react/use-teardown.md`
29
+ - **Components:** `react/components/DataTable.md`, `react/components/CardList.md`, `react/components/InfiniteScroll.md`
25
30
 
26
31
  ## Core Classes
27
32
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: mvc-kit-guide
2
+ name: mvc-kit
3
3
  description: "mvc-kit framework reference — class roles, architecture rules, React hooks, and decision framework. Use when working with mvc-kit imports or planning mvc-kit features."
4
4
  user-invocable: false
5
5
  ---
@@ -97,3 +97,12 @@ For full composition recipes with code, see [recipes.md](recipes.md).
97
97
  - [anti-patterns.md](anti-patterns.md) — Anti-patterns to reject with fixes
98
98
  - [recipes.md](recipes.md) — Composition recipes for real-world features
99
99
  - [testing.md](testing.md) — Testing patterns (teardownAll, async assertions, memoization verification)
100
+
101
+ ### Per-Class Documentation (in `node_modules/mvc-kit/src/`)
102
+
103
+ For deep dives on any specific class or hook, search for the `.md` file by name:
104
+
105
+ - **Core:** `ViewModel.md`, `Model.md`, `Collection.md`, `PersistentCollection.md`, `Resource.md`, `Service.md`, `EventBus.md`, `Channel.md`, `Controller.md`, `Trackable.md`, `singleton.md`
106
+ - **Helpers:** `Sorting.md`, `Pagination.md`, `Selection.md`, `Feed.md`, `Pending.md`, `produceDraft.md`
107
+ - **React hooks:** `react/use-local.md`, `react/use-instance.md`, `react/use-singleton.md`, `react/use-model.md`, `react/use-event-bus.md`, `react/use-teardown.md`
108
+ - **Components:** `react/components/DataTable.md`, `react/components/CardList.md`, `react/components/InfiniteScroll.md`
@@ -1,37 +1,36 @@
1
- import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, copyFileSync } from 'node:fs';
2
- import { join, dirname } from 'node:path';
1
+ import { cpSync, mkdirSync, readdirSync, statSync } from 'node:fs';
2
+ import { join, dirname, relative } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
 
5
5
  const __filename = fileURLToPath(import.meta.url);
6
6
  const __dirname = dirname(__filename);
7
- const SKILLS_DIR = join(__dirname, '..', 'claude-code', 'skills');
8
- const AGENTS_DIR = join(__dirname, '..', 'claude-code', 'agents');
9
-
10
- const AUTO_HEADER = '<!-- Auto-generated by mvc-kit. Updated on npm install/update. Do not edit. -->';
11
-
12
- function stripFrontmatter(content) {
13
- const match = content.match(/^---\n[\s\S]*?\n---\n([\s\S]*)$/);
14
- return match ? match[1].trim() : content.trim();
15
- }
16
-
17
- /** Insert auto-generated header after YAML frontmatter (preserves parsing). */
18
- function addHeader(content) {
19
- const match = content.match(/^(---\n[\s\S]*?\n---\n)([\s\S]*)$/);
20
- if (match) {
21
- return match[1] + '\n' + AUTO_HEADER + '\n\n' + match[2].trim() + '\n';
22
- }
23
- return AUTO_HEADER + '\n\n' + content;
24
- }
25
-
26
- function ensureDir(dir) {
27
- if (!existsSync(dir)) {
28
- mkdirSync(dir, { recursive: true });
7
+ const SOURCE_DIR = join(__dirname, '..', 'claude-code');
8
+
9
+ const SKILL_NAMES = ['mvc-kit', 'mvc-kit-review', 'mvc-kit-scaffold'];
10
+ const AGENT_FILES = ['mvc-kit-architect.md'];
11
+
12
+ function listFilesRecursive(dir) {
13
+ const out = [];
14
+ for (const entry of readdirSync(dir)) {
15
+ const full = join(dir, entry);
16
+ if (statSync(full).isDirectory()) {
17
+ for (const nested of listFilesRecursive(full)) {
18
+ out.push(join(entry, nested));
19
+ }
20
+ } else {
21
+ out.push(entry);
22
+ }
29
23
  }
24
+ return out;
30
25
  }
31
26
 
32
27
  /**
33
28
  * Install Claude Code integration files into a project's .claude/ directory.
34
29
  *
30
+ * Source directories under `agent-config/claude-code/` are copied verbatim —
31
+ * no frontmatter rewriting, no header injection. Source files are authored
32
+ * in their final installed form.
33
+ *
35
34
  * Creates:
36
35
  * .claude/skills/mvc-kit/ — Framework reference skill + supporting docs
37
36
  * .claude/skills/mvc-kit-review/ — Code review skill + checklist
@@ -39,103 +38,33 @@ function ensureDir(dir) {
39
38
  * .claude/agents/mvc-kit-architect.md — Architecture planning agent
40
39
  *
41
40
  * @param {string} projectRoot — Absolute path to the consuming project's root
42
- * @returns {{ files: string[] }} — List of created/updated file paths (relative to projectRoot)
41
+ * @returns {{ files: string[] }} — List of installed file paths (relative to projectRoot)
43
42
  */
44
43
  export function installClaude(projectRoot) {
45
44
  const claudeDir = join(projectRoot, '.claude');
46
- const guideDir = join(claudeDir, 'skills', 'mvc-kit');
47
- const reviewDir = join(claudeDir, 'skills', 'mvc-kit-review');
48
- const scaffoldDir = join(claudeDir, 'skills', 'mvc-kit-scaffold');
49
- const templatesDir = join(scaffoldDir, 'templates');
50
- const agentsDir = join(claudeDir, 'agents');
45
+ const skillsDest = join(claudeDir, 'skills');
46
+ const agentsDest = join(claudeDir, 'agents');
51
47
 
52
- ensureDir(guideDir);
53
- ensureDir(reviewDir);
54
- ensureDir(scaffoldDir);
55
- ensureDir(templatesDir);
56
- ensureDir(agentsDir);
48
+ mkdirSync(skillsDest, { recursive: true });
49
+ mkdirSync(agentsDest, { recursive: true });
57
50
 
58
51
  const files = [];
59
52
 
60
- // ─── 1. Guide skill framework reference (model-invocable, auto-loaded) ───
61
-
62
- const guideSkill = readFileSync(join(SKILLS_DIR, 'guide', 'SKILL.md'), 'utf-8');
63
- const guideBody = stripFrontmatter(guideSkill)
64
- // Remove the "Supporting Files" section — replaced with colocated file references below
65
- .replace(/\n## Supporting Files[\s\S]*$/, '');
66
-
67
- const GUIDE_FRONTMATTER = `---
68
- name: mvc-kit
69
- description: "mvc-kit framework reference — class roles, architecture rules, React hooks, and decision framework. Loaded on-demand when working with mvc-kit code."
70
- user-invocable: false
71
- ---`;
72
-
73
- const skillContent = GUIDE_FRONTMATTER + '\n\n' + AUTO_HEADER + '\n\n' + guideBody + `
74
-
75
- ## Supporting Files
76
-
77
- - [api-reference.md](api-reference.md) — Full API reference for all classes and hooks
78
- - [patterns.md](patterns.md) — Prescribed patterns with code examples
79
- - [anti-patterns.md](anti-patterns.md) — Anti-patterns to reject with fixes
80
- - [recipes.md](recipes.md) — Composition recipes for real-world features
81
- - [testing.md](testing.md) — Testing patterns (teardownAll, async assertions, memoization verification)
82
-
83
- For detailed per-class documentation, read the \`.md\` files colocated with source in:
84
- \`node_modules/mvc-kit/src/\`
85
- `;
86
-
87
- writeFileSync(join(guideDir, 'SKILL.md'), skillContent, 'utf-8');
88
- files.push('.claude/skills/mvc-kit/SKILL.md');
89
-
90
- // Copy supporting files alongside SKILL.md
91
- const guideSupportFiles = ['api-reference.md', 'patterns.md', 'anti-patterns.md', 'recipes.md', 'testing.md'];
92
- for (const file of guideSupportFiles) {
93
- const src = join(SKILLS_DIR, 'guide', file);
94
- if (existsSync(src)) {
95
- copyFileSync(src, join(guideDir, file));
96
- files.push(`.claude/skills/mvc-kit/${file}`);
53
+ for (const skill of SKILL_NAMES) {
54
+ const src = join(SOURCE_DIR, 'skills', skill);
55
+ const dest = join(skillsDest, skill);
56
+ cpSync(src, dest, { recursive: true, force: true });
57
+ for (const rel of listFilesRecursive(src)) {
58
+ files.push(`.claude/skills/${skill}/${rel}`);
97
59
  }
98
60
  }
99
61
 
100
- // ─── 2. Review skill — code review with checklist ──────────────────────────
101
-
102
- const reviewSkill = readFileSync(join(SKILLS_DIR, 'review', 'SKILL.md'), 'utf-8');
103
- writeFileSync(join(reviewDir, 'SKILL.md'), addHeader(reviewSkill), 'utf-8');
104
- files.push('.claude/skills/mvc-kit-review/SKILL.md');
105
-
106
- copyFileSync(
107
- join(SKILLS_DIR, 'review', 'checklist.md'),
108
- join(reviewDir, 'checklist.md'),
109
- );
110
- files.push('.claude/skills/mvc-kit-review/checklist.md');
111
-
112
- // ─── 3. Scaffold skill — code generation with templates ────────────────────
113
-
114
- const scaffoldSkill = readFileSync(join(SKILLS_DIR, 'scaffold', 'SKILL.md'), 'utf-8');
115
- writeFileSync(join(scaffoldDir, 'SKILL.md'), addHeader(scaffoldSkill), 'utf-8');
116
- files.push('.claude/skills/mvc-kit-scaffold/SKILL.md');
117
-
118
- const templatesSrc = join(SKILLS_DIR, 'scaffold', 'templates');
119
- if (existsSync(templatesSrc)) {
120
- for (const file of readdirSync(templatesSrc)) {
121
- if (file.endsWith('.md')) {
122
- copyFileSync(join(templatesSrc, file), join(templatesDir, file));
123
- files.push(`.claude/skills/mvc-kit-scaffold/templates/${file}`);
124
- }
125
- }
62
+ for (const agent of AGENT_FILES) {
63
+ const src = join(SOURCE_DIR, 'agents', agent);
64
+ const dest = join(agentsDest, agent);
65
+ cpSync(src, dest, { force: true });
66
+ files.push(`.claude/agents/${agent}`);
126
67
  }
127
68
 
128
- // ─── 4. Architect agent ────────────────────────────────────────────────────
129
-
130
- let architectAgent = readFileSync(join(AGENTS_DIR, 'mvc-kit-architect.md'), 'utf-8');
131
- // The source agent preloads "mvc-kit-guide" (the source skill name).
132
- // In the installed context the guide skill is named "mvc-kit", so fix the reference.
133
- architectAgent = architectAgent.replace(
134
- /^(\s*- )mvc-kit-guide$/m,
135
- '$1mvc-kit',
136
- );
137
- writeFileSync(join(agentsDir, 'mvc-kit-architect.md'), addHeader(architectAgent), 'utf-8');
138
- files.push('.claude/agents/mvc-kit-architect.md');
139
-
140
69
  return { files };
141
70
  }
@@ -0,0 +1,109 @@
1
+ import { Channel, singleton, teardownAll } from 'mvc-kit';
2
+ import type { ChannelStatus } from 'mvc-kit';
3
+
4
+ // Channel: Persistent connection with auto-reconnect and typed messages
5
+ //
6
+ // Extend Channel<MessageMap> and implement two abstract methods:
7
+ // open(signal) — establish the connection (WebSocket, SSE, etc.)
8
+ // close() — tear down the transport
9
+ //
10
+ // The framework handles connection status, reconnect with exponential
11
+ // backoff, message routing, and lifecycle management.
12
+
13
+ // --- Message type map ---
14
+
15
+ interface ChatMessages {
16
+ message: { userId: string; text: string };
17
+ typing: { userId: string };
18
+ presence: { online: string[] };
19
+ }
20
+
21
+ // --- Channel subclass ---
22
+
23
+ class ChatChannel extends Channel<ChatMessages> {
24
+ // Tune reconnect behavior via static overrides
25
+ static override RECONNECT_BASE = 1000; // initial backoff (ms)
26
+ static override RECONNECT_MAX = 30000; // max backoff cap (ms)
27
+ static override MAX_ATTEMPTS = 5; // give up after 5 attempts
28
+
29
+ private ws: { close(): void } | null = null;
30
+
31
+ // open() is called by the framework when connect() is invoked.
32
+ // The signal aborts on disconnect() or dispose().
33
+ protected open(signal: AbortSignal): void {
34
+ // In production, this would be:
35
+ // this.ws = new WebSocket('wss://chat.example.com');
36
+ // this.ws.onmessage = (e) => {
37
+ // const { type, payload } = JSON.parse(e.data);
38
+ // this.receive(type, payload);
39
+ // };
40
+
41
+ // Simulated connection
42
+ console.log('Connecting...');
43
+ const connectTimer = setTimeout(() => {
44
+ if (!signal.aborted) {
45
+ console.log('Connected!');
46
+ // Simulate incoming messages
47
+ this.receive('presence', { online: ['alice', 'bob'] });
48
+ this.receive('message', { userId: 'alice', text: 'Hello!' });
49
+ }
50
+ }, 100);
51
+
52
+ this.ws = {
53
+ close: () => clearTimeout(connectTimer),
54
+ };
55
+
56
+ // Always clean up when the signal aborts (disconnect or dispose)
57
+ signal.addEventListener('abort', () => this.ws?.close());
58
+ }
59
+
60
+ // close() tears down the transport. Must not throw.
61
+ protected close(): void {
62
+ this.ws?.close();
63
+ this.ws = null;
64
+ }
65
+ }
66
+
67
+ // --- Usage ---
68
+
69
+ const chat = singleton(ChatChannel);
70
+ chat.init();
71
+
72
+ // Subscribe to connection status changes
73
+ chat.subscribe((next: ChannelStatus, prev: ChannelStatus) => {
74
+ console.log(`Status: connected=${next.connected}, reconnecting=${next.reconnecting}`);
75
+ if (next.error) console.log(`Error: ${next.error}`);
76
+ });
77
+
78
+ // Subscribe to typed messages
79
+ const unsubMessage = chat.on('message', ({ userId, text }) => {
80
+ console.log(`${userId}: ${text}`);
81
+ });
82
+
83
+ // One-time subscription — auto-unsubscribes after first event
84
+ chat.once('presence', ({ online }) => {
85
+ console.log('Initial presence:', online);
86
+ });
87
+
88
+ // Initiate connection
89
+ chat.connect();
90
+
91
+ // Read current status at any time
92
+ console.log('Connected:', chat.state.connected);
93
+ console.log('Reconnecting:', chat.state.reconnecting);
94
+ console.log('Attempt:', chat.state.attempt);
95
+
96
+ // Manually disconnect (cancels pending reconnect, resets status)
97
+ // chat.disconnect();
98
+
99
+ // Reconnect after disconnect
100
+ // chat.connect();
101
+
102
+ // Unsubscribe from a message type
103
+ unsubMessage();
104
+
105
+ // Cleanup — cancels timers, aborts signals, calls close()
106
+ setTimeout(() => {
107
+ chat.dispose();
108
+ teardownAll();
109
+ }, 500);
@@ -0,0 +1,118 @@
1
+ import { Collection } from 'mvc-kit';
2
+
3
+ // Collection: Reactive typed array with CRUD and query methods
4
+
5
+ interface Todo {
6
+ id: string;
7
+ text: string;
8
+ done: boolean;
9
+ priority: 'low' | 'medium' | 'high';
10
+ }
11
+
12
+ // --- Basic usage ---
13
+
14
+ const todos = new Collection<Todo>();
15
+
16
+ // Subscribe to changes
17
+ todos.subscribe((items, prev) => {
18
+ console.log(`Collection changed: ${prev.length} → ${items.length} items`);
19
+ });
20
+
21
+ // CRUD operations (trigger notifications)
22
+
23
+ // Add items
24
+ todos.add(
25
+ { id: '1', text: 'Learn mvc-kit', done: false, priority: 'high' },
26
+ { id: '2', text: 'Build an app', done: false, priority: 'medium' },
27
+ { id: '3', text: 'Write tests', done: true, priority: 'low' }
28
+ );
29
+
30
+ console.log('Length:', todos.length); // 3
31
+ console.log('Items:', todos.items);
32
+
33
+ // Update an item
34
+ todos.update('1', { done: true });
35
+ console.log('After update:', todos.get('1')?.done); // true
36
+
37
+ // Remove an item
38
+ todos.remove('3');
39
+ console.log('After remove length:', todos.length); // 2
40
+
41
+ // Query operations (pure, no notifications)
42
+
43
+ // Get by id (O(1) lookup)
44
+ const todo = todos.get('1');
45
+ console.log('Get by id:', todo?.text);
46
+
47
+ // Check existence
48
+ console.log('Has id 1:', todos.has('1')); // true
49
+ console.log('Has id 99:', todos.has('99')); // false
50
+
51
+ // Find first match
52
+ const firstIncomplete = todos.find(t => !t.done);
53
+ console.log('First incomplete:', firstIncomplete?.text);
54
+
55
+ // Filter
56
+ const highPriority = todos.filter(t => t.priority === 'high');
57
+ console.log('High priority count:', highPriority.length);
58
+
59
+ // Sort (returns new array, doesn't mutate)
60
+ const sorted = todos.sorted((a, b) => a.text.localeCompare(b.text));
61
+ console.log('Sorted:', sorted.map(t => t.text));
62
+
63
+ // Map
64
+ const texts = todos.map(t => t.text);
65
+ console.log('Texts:', texts);
66
+
67
+ // Reset - replace all items
68
+ todos.reset([
69
+ { id: 'a', text: 'New todo', done: false, priority: 'medium' }
70
+ ]);
71
+ console.log('After reset length:', todos.length); // 1
72
+
73
+ // Clear all items
74
+ todos.clear();
75
+ console.log('After clear length:', todos.length); // 0
76
+
77
+ // --- Upsert: add-or-replace by ID ---
78
+
79
+ todos.reset([
80
+ { id: '1', text: 'Learn mvc-kit', done: false, priority: 'high' },
81
+ { id: '2', text: 'Build an app', done: false, priority: 'medium' },
82
+ ]);
83
+
84
+ // Upsert replaces existing items in-place and appends new ones
85
+ todos.upsert(
86
+ { id: '2', text: 'Build an app', done: true, priority: 'medium' }, // replaces in position
87
+ { id: '3', text: 'Ship it', done: false, priority: 'high' }, // appended
88
+ );
89
+ console.log('After upsert length:', todos.length); // 3
90
+ console.log('Item 2 done:', todos.get('2')?.done); // true (replaced)
91
+ console.log('Item 3 text:', todos.get('3')?.text); // 'Ship it' (new)
92
+
93
+ // --- Optimistic updates ---
94
+
95
+ // Reset with some data for the optimistic demo
96
+ todos.reset([
97
+ { id: '1', text: 'Learn mvc-kit', done: false, priority: 'high' },
98
+ { id: '2', text: 'Build an app', done: false, priority: 'medium' },
99
+ ]);
100
+
101
+ // Snapshot current state, apply mutations, get a rollback function
102
+ const rollback = todos.optimistic(() => {
103
+ todos.update('1', { done: true });
104
+ todos.remove('2');
105
+ });
106
+
107
+ console.log('After optimistic:', todos.length); // 1
108
+ console.log('Item 1 done:', todos.get('1')?.done); // true
109
+
110
+ // If the server call fails, rollback restores pre-optimistic state
111
+ rollback();
112
+
113
+ console.log('After rollback:', todos.length); // 2
114
+ console.log('Item 1 done:', todos.get('1')?.done); // false
115
+ console.log('Item 2 exists:', todos.has('2')); // true
116
+
117
+ // Cleanup
118
+ todos.dispose();
@@ -0,0 +1,118 @@
1
+ import { Controller, ViewModel, Collection, singleton, teardownAll } from 'mvc-kit';
2
+
3
+ // Controller: Stateless multi-ViewModel orchestrator
4
+ //
5
+ // Controllers coordinate between ViewModels, Models, and Services when
6
+ // a single ViewModel can't own the workflow. This is rare — most
7
+ // orchestration fits in a single ViewModel.
8
+ //
9
+ // Use Controller only for pure cross-cutting coordination with no state
10
+ // of its own: multi-step checkout, drag-and-drop between lists, etc.
11
+ //
12
+ // What Controller provides:
13
+ // - init() / dispose() lifecycle
14
+ // - subscribeTo() / listenTo() with auto-cleanup
15
+ // - disposeSignal for cancelling async operations
16
+ // - addCleanup() for custom teardown
17
+ //
18
+ // What Controller does NOT provide:
19
+ // - State (no set())
20
+ // - Computed getters
21
+ // - Async tracking
22
+ // - Events (no emit())
23
+
24
+ // --- Supporting types ---
25
+
26
+ interface Task {
27
+ id: string;
28
+ title: string;
29
+ status: 'todo' | 'done';
30
+ }
31
+
32
+ interface TaskListState {
33
+ items: Task[];
34
+ }
35
+
36
+ // --- Two ViewModels that the Controller coordinates ---
37
+
38
+ class TodoListViewModel extends ViewModel<TaskListState> {
39
+ removeItem(id: string) {
40
+ this.set({ items: this.state.items.filter(t => t.id !== id) });
41
+ }
42
+
43
+ addItem(task: Task) {
44
+ this.set({ items: [...this.state.items, task] });
45
+ }
46
+ }
47
+
48
+ class DoneListViewModel extends ViewModel<TaskListState> {
49
+ addItem(task: Task) {
50
+ this.set({ items: [...this.state.items, { ...task, status: 'done' as const }] });
51
+ }
52
+ }
53
+
54
+ // --- Controller definition ---
55
+
56
+ class TaskTransferController extends Controller {
57
+ constructor(
58
+ private todoVM: TodoListViewModel,
59
+ private doneVM: DoneListViewModel,
60
+ ) {
61
+ super();
62
+ }
63
+
64
+ protected onInit() {
65
+ // subscribeTo auto-cleans up on dispose
66
+ this.subscribeTo(this.todoVM, (state) => {
67
+ console.log(`Todo list: ${state.items.length} items`);
68
+ });
69
+
70
+ this.subscribeTo(this.doneVM, (state) => {
71
+ console.log(`Done list: ${state.items.length} items`);
72
+ });
73
+ }
74
+
75
+ // Pure coordination — moves an item from todo to done
76
+ completeTask(taskId: string) {
77
+ const task = this.todoVM.state.items.find(t => t.id === taskId);
78
+ if (!task) return;
79
+ this.todoVM.removeItem(taskId);
80
+ this.doneVM.addItem(task);
81
+ }
82
+
83
+ protected onDispose() {
84
+ console.log('TaskTransferController disposed');
85
+ }
86
+ }
87
+
88
+ // --- Usage ---
89
+
90
+ const todoVM = new TodoListViewModel({
91
+ items: [
92
+ { id: '1', title: 'Learn mvc-kit', status: 'todo' },
93
+ { id: '2', title: 'Build an app', status: 'todo' },
94
+ { id: '3', title: 'Write tests', status: 'todo' },
95
+ ],
96
+ });
97
+ todoVM.init();
98
+
99
+ const doneVM = new DoneListViewModel({ items: [] });
100
+ doneVM.init();
101
+
102
+ const controller = new TaskTransferController(todoVM, doneVM);
103
+ controller.init();
104
+
105
+ console.log('Todo:', todoVM.state.items.length); // 3
106
+ console.log('Done:', doneVM.state.items.length); // 0
107
+
108
+ // Controller coordinates the move
109
+ controller.completeTask('1');
110
+
111
+ console.log('Todo:', todoVM.state.items.length); // 2
112
+ console.log('Done:', doneVM.state.items.length); // 1
113
+ console.log('Done item:', doneVM.state.items[0]?.title); // 'Learn mvc-kit'
114
+
115
+ // Cleanup
116
+ controller.dispose();
117
+ todoVM.dispose();
118
+ doneVM.dispose();