cabloy 5.1.59 → 5.1.61

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 (149) hide show
  1. package/.claude/hooks/contract-loop-gate.ts +296 -0
  2. package/.claude/settings.json +16 -0
  3. package/.claude/skills/cabloy-backend-scaffold/references/follow-up-checklist.md +1 -0
  4. package/.claude/skills/cabloy-contract-loop/SKILL.md +103 -14
  5. package/.claude/skills/cabloy-contract-loop/references/contract-loop-map.md +126 -12
  6. package/.claude/skills/cabloy-contract-loop/references/resource-custom-state-pattern.md +148 -0
  7. package/.claude/skills/cabloy-contract-loop/references/verification-checklist.md +49 -13
  8. package/.claude/skills/cabloy-frontend-scaffold/SKILL.md +11 -0
  9. package/.claude/skills/cabloy-frontend-scaffold/references/follow-up-checklist.md +2 -0
  10. package/.claude/skills/cabloy-module-removal/SKILL.md +144 -0
  11. package/.claude/skills/cabloy-resource-field-update/SKILL.md +274 -0
  12. package/.claude/skills/cabloy-resource-field-update/evals/evals.json +53 -0
  13. package/.claude/skills/cabloy-resource-field-update/references/custom-renderer-demo-checklist.md +102 -0
  14. package/.claude/skills/cabloy-resource-field-update/references/field-update-decision-tree.md +120 -0
  15. package/.claude/skills/cabloy-resource-field-update/references/follow-up-checklist.md +80 -0
  16. package/.claude/skills/cabloy-resource-field-update/references/verification-checklist.md +97 -0
  17. package/.claude/skills/cabloy-zova-source-reading/SKILL.md +221 -0
  18. package/.claude/skills/cabloy-zova-source-reading/references/analysis-modes.md +91 -0
  19. package/.claude/skills/cabloy-zova-source-reading/references/core-reading-paths.md +117 -0
  20. package/.github/workflows/docs-pages.yml +2 -0
  21. package/.github/workflows/vona-cov-pg.yml +2 -0
  22. package/.github/workflows/vona-test-crud.yml +4 -2
  23. package/.github/workflows/vona-test-mysql.yml +2 -0
  24. package/.github/workflows/vona-test-pg.yml +2 -0
  25. package/.github/workflows/vona-test-sqlite3.yml +2 -0
  26. package/.github/workflows/vona-tsc.yml +2 -0
  27. package/.github/workflows/zova-ui.yml +2 -0
  28. package/.gitignore +0 -4
  29. package/CHANGELOG.md +52 -0
  30. package/CLAUDE.md +12 -0
  31. package/README.md +15 -0
  32. package/cabloy-docs/.vitepress/config.mjs +89 -0
  33. package/cabloy-docs/ai/class-placement-rule.md +2 -0
  34. package/cabloy-docs/ai/cli-to-skill-map.md +14 -0
  35. package/cabloy-docs/ai/docs-skills-rules-mapping.md +14 -0
  36. package/cabloy-docs/ai/future-skill-roadmap.md +27 -9
  37. package/cabloy-docs/ai/introduction.md +1 -0
  38. package/cabloy-docs/ai/playbook-backend-module.md +6 -0
  39. package/cabloy-docs/ai/playbook-module-removal.md +164 -0
  40. package/cabloy-docs/ai/skills.md +11 -0
  41. package/cabloy-docs/backend/bean-scene-authoring.md +350 -0
  42. package/cabloy-docs/backend/cli.md +26 -1
  43. package/cabloy-docs/backend/dto-guide.md +6 -0
  44. package/cabloy-docs/backend/entity-guide.md +18 -0
  45. package/cabloy-docs/backend/foundation.md +28 -3
  46. package/cabloy-docs/backend/introduction.md +10 -0
  47. package/cabloy-docs/backend/serialization-guide.md +10 -0
  48. package/cabloy-docs/backend/service-guide.md +2 -0
  49. package/cabloy-docs/backend/status-guide.md +271 -0
  50. package/cabloy-docs/backend/websocket-call-flow.md +435 -0
  51. package/cabloy-docs/backend/websocket-guide.md +455 -0
  52. package/cabloy-docs/backend/websocket-protocol-guide.md +381 -0
  53. package/cabloy-docs/backend/websocket-usage-guide.md +356 -0
  54. package/cabloy-docs/frontend/api-guide.md +2 -0
  55. package/cabloy-docs/frontend/bean-scene-authoring.md +374 -0
  56. package/cabloy-docs/frontend/behavior-guide.md +449 -0
  57. package/cabloy-docs/frontend/cli.md +24 -0
  58. package/cabloy-docs/frontend/command-scene-authoring.md +495 -0
  59. package/cabloy-docs/frontend/design-principles.md +6 -0
  60. package/cabloy-docs/frontend/fetch-interceptor-guide.md +440 -0
  61. package/cabloy-docs/frontend/form-guide.md +795 -0
  62. package/cabloy-docs/frontend/foundation.md +29 -0
  63. package/cabloy-docs/frontend/introduction.md +17 -1
  64. package/cabloy-docs/frontend/ioc-and-beans.md +16 -9
  65. package/cabloy-docs/frontend/mock-guide.md +1 -0
  66. package/cabloy-docs/frontend/model-architecture.md +252 -39
  67. package/cabloy-docs/frontend/model-resource-best-practices.md +379 -0
  68. package/cabloy-docs/frontend/model-resource-cookbook.md +505 -0
  69. package/cabloy-docs/frontend/model-resource-owner-pattern.md +382 -0
  70. package/cabloy-docs/frontend/model-resource-usage-guide.md +318 -0
  71. package/cabloy-docs/frontend/model-state-guide.md +366 -13
  72. package/cabloy-docs/frontend/openapi-sdk-guide.md +5 -2
  73. package/cabloy-docs/frontend/page-guide.md +6 -0
  74. package/cabloy-docs/frontend/quickstart.md +4 -0
  75. package/cabloy-docs/frontend/reading-zova-for-vue-developers.md +266 -0
  76. package/cabloy-docs/frontend/router-tabs-admin-web-comparison.md +206 -0
  77. package/cabloy-docs/frontend/router-tabs-introduction.md +106 -0
  78. package/cabloy-docs/frontend/router-tabs-mechanism.md +469 -0
  79. package/cabloy-docs/frontend/router-tabs-overview.md +227 -0
  80. package/cabloy-docs/frontend/router-tabs-route-meta-cookbook.md +343 -0
  81. package/cabloy-docs/frontend/server-data.md +2 -0
  82. package/cabloy-docs/frontend/ssr-architecture-overview.md +211 -0
  83. package/cabloy-docs/frontend/ssr-build-deploy-guide.md +308 -0
  84. package/cabloy-docs/frontend/ssr-review-checklist.md +184 -0
  85. package/cabloy-docs/frontend/ssr-troubleshooting-guide.md +301 -0
  86. package/cabloy-docs/frontend/zova-form-source-reading-map.md +295 -0
  87. package/cabloy-docs/frontend/zova-form-under-the-hood.md +556 -0
  88. package/cabloy-docs/frontend/zova-reactivity-under-the-hood.md +320 -0
  89. package/cabloy-docs/frontend/zova-source-reading-map.md +327 -0
  90. package/cabloy-docs/frontend/zova-vs-vue3-comparison.md +308 -0
  91. package/cabloy-docs/fullstack/contract-loop-playbook.md +350 -0
  92. package/cabloy-docs/fullstack/framework-performance.md +3 -3
  93. package/cabloy-docs/fullstack/frontend-metadata-to-backend.md +44 -1
  94. package/cabloy-docs/fullstack/introduction.md +40 -0
  95. package/cabloy-docs/fullstack/openapi-to-sdk.md +19 -9
  96. package/cabloy-docs/fullstack/quickstart.md +7 -1
  97. package/cabloy-docs/fullstack/tutorial-1-first-module.md +111 -0
  98. package/cabloy-docs/fullstack/tutorial-2-first-crud.md +122 -0
  99. package/cabloy-docs/fullstack/tutorial-3-frontend-metadata-sharing.md +131 -0
  100. package/cabloy-docs/fullstack/tutorial-4-custom-level-renderers.md +144 -0
  101. package/cabloy-docs/fullstack/tutorial-5-backend-contract-sharing.md +146 -0
  102. package/cabloy-docs/fullstack/tutorial-6-one-contract-four-uses.md +170 -0
  103. package/cabloy-docs/fullstack/tutorials-overview.md +192 -0
  104. package/cabloy-docs/index.md +4 -3
  105. package/cabloy-docs/reference/bean-scene-boilerplates.md +75 -0
  106. package/cabloy-docs/reference/cli-reference.md +2 -0
  107. package/package.json +7 -2
  108. package/scripts/initTestData.ts +25 -0
  109. package/scripts/upgrade.ts +17 -2
  110. package/vona/packages-cli/cabloy-cli/package.json +2 -2
  111. package/vona/packages-cli/cli/package.json +1 -1
  112. package/vona/packages-cli/cli-set-api/package.json +1 -1
  113. package/vona/packages-cli/cli-set-api/src/lib/bean/cli.create.module.ts +4 -0
  114. package/vona/packages-vona/vona/package.json +1 -1
  115. package/vona/pnpm-lock.yaml +226 -1091
  116. package/vona/pnpm-workspace.yaml +0 -1
  117. package/vona/src/suite-vendor/a-vona/modules/a-core/assets/static/img/vona.svg +1 -1
  118. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
  119. package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
  120. package/vona/src/suite-vendor/a-vona/modules/a-permission/src/bean/bean.permission.ts +1 -1
  121. package/vona/src/suite-vendor/a-vona/modules/a-upload/package.json +2 -2
  122. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  123. package/zova/package.original.json +1 -1
  124. package/zova/packages-cli/cli/package.json +3 -3
  125. package/zova/packages-cli/cli-set-front/cli/templates/init/icon/boilerplate/icons/default/zova.svg +1 -1
  126. package/zova/packages-cli/cli-set-front/cli/templates/openapi/config/boilerplate/module/openapi.config.ts +6 -1
  127. package/zova/packages-cli/cli-set-front/package.json +3 -3
  128. package/zova/packages-cli/cli-set-front/src/lib/bean/cli.create.module.ts +4 -0
  129. package/zova/packages-cli/cli-set-front/src/lib/bean/cli.openapi.generate.ts +34 -4
  130. package/zova/packages-cli/cli-set-front/src/lib/command/create.bean.ts +5 -1
  131. package/zova/packages-utils/zova-vite/package.json +2 -2
  132. package/zova/packages-zova/zova/package.json +2 -2
  133. package/zova/pnpm-lock.yaml +282 -1311
  134. package/zova/pnpm-workspace.yaml +0 -1
  135. package/zova/src/suite/a-home/modules/home-icon/icons/social/cabloy.svg +1 -1
  136. package/zova/src/suite/a-home/modules/home-icon/icons/social/vona.svg +1 -1
  137. package/zova/src/suite/a-home/modules/home-icon/icons/social/zova.svg +1 -1
  138. package/zova/src/suite/a-home/modules/home-icon/src/.metadata/icons/groups/social.svg +3 -3
  139. package/zova/src/suite/cabloy-basic/modules/basic-select/src/component/formFieldSelect/controller.tsx +9 -0
  140. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
  141. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +66 -16
  142. package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
  143. package/zova/src/suite-vendor/a-zova/modules/a-routertabs/package.json +1 -1
  144. package/zova/src/suite-vendor/a-zova/modules/a-routertabs/src/model/tabs.ts +60 -18
  145. package/zova/src/suite-vendor/a-zova/modules/a-table/cli/tableActionRow/boilerplate/{{sceneName}}.{{beanName}}.tsx_ +6 -1
  146. package/zova/src/suite-vendor/a-zova/modules/a-table/cli/tableCell/boilerplate/{{sceneName}}.{{beanName}}.tsx_ +6 -1
  147. package/zova/src/suite-vendor/a-zova/modules/a-table/package.json +1 -1
  148. package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
  149. package/zova/src/suite-vendor/a-zova/package.json +4 -4
@@ -0,0 +1,296 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync, statSync, writeFileSync, existsSync } from 'node:fs';
3
+ import { spawnSync, type SpawnSyncReturns } from 'node:child_process';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ interface HookPayload {
9
+ tool_input?: {
10
+ file_path?: string;
11
+ };
12
+ }
13
+
14
+ interface AnalysisResult {
15
+ forwardReason: string | null;
16
+ reverseReason: string | null;
17
+ }
18
+
19
+ interface SyncStateEntry {
20
+ fingerprint: string;
21
+ timestamp: number;
22
+ }
23
+
24
+ type SyncState = Record<string, SyncStateEntry>;
25
+
26
+ const SCRIPT_FILE = fileURLToPath(import.meta.url);
27
+ const ROOT = path.resolve(path.dirname(SCRIPT_FILE), '../..');
28
+ const ROOT_KEY = toPosixPath(ROOT);
29
+ const STATE_FILE = path.join(os.tmpdir(), 'cabloy-contract-loop-gate-state.json');
30
+ const AUTO_SYNC_WINDOW_SECONDS = 300;
31
+
32
+ const TARGET_PATTERNS: Array<readonly [string, string]> = [
33
+ ['zova/src/module/', '.ts'],
34
+ ['zova/src/module/', '.tsx'],
35
+ ['zova/src/module/', '.jsx'],
36
+ ['zova/src/suite/', '.ts'],
37
+ ['zova/src/suite/', '.tsx'],
38
+ ['zova/src/suite/', '.jsx'],
39
+ ['vona/src/', '.ts'],
40
+ ['vona/src/', '.tsx'],
41
+ ['vona/src/', '.jsx'],
42
+ ];
43
+
44
+ const FORWARD_PATH_MARKERS = ['/controller/', '/dto/', '/entity/'];
45
+
46
+ const FORWARD_CONTENT_MARKERS = ['@Web.', '@Api.field', '@Api.body', 'v.openapi(', '@Dto<'];
47
+
48
+ const REVERSE_VONA_CONTENT_MARKERS = [
49
+ 'zova-rest-cabloy-basic-admin',
50
+ 'ZovaRender.',
51
+ 'tableActionRow(',
52
+ 'tableActionBulk(',
53
+ 'ZovaRender.field(',
54
+ 'ZovaRender.cell(',
55
+ ];
56
+
57
+ const REVERSE_ZOVA_PATH_MARKERS = ['/src/bean/', '/src/component/', '/src/.metadata/'];
58
+
59
+ const REVERSE_ZOVA_CONTENT_MARKERS = [
60
+ "declare module 'zova-module-a-openapi'",
61
+ 'IResourceTableActionRowRecord',
62
+ '@TableCell<',
63
+ '@Component(',
64
+ '@Component<',
65
+ ];
66
+
67
+ function toPosixPath(value: string): string {
68
+ return value.split(path.sep).join('/');
69
+ }
70
+
71
+ function normalizePath(value?: string): string | null {
72
+ if (!value) return null;
73
+ try {
74
+ const candidate = path.isAbsolute(value) ? value : path.join(ROOT, value);
75
+ return toPosixPath(path.resolve(candidate));
76
+ } catch {
77
+ return null;
78
+ }
79
+ }
80
+
81
+ function isCodeFile(filePath: string): boolean {
82
+ return TARGET_PATTERNS.some(([prefix, suffix]) => filePath.includes(prefix) && filePath.endsWith(suffix));
83
+ }
84
+
85
+ function containsAny(text: string, needles: readonly string[]): boolean {
86
+ return needles.some(needle => text.includes(needle));
87
+ }
88
+
89
+ function pathContainsAny(filePath: string, needles: readonly string[]): boolean {
90
+ return needles.some(needle => filePath.includes(needle));
91
+ }
92
+
93
+ function detectForward(filePath: string, content: string): string | null {
94
+ if (!filePath.includes('/vona/src/')) return null;
95
+ if (pathContainsAny(filePath, FORWARD_PATH_MARKERS) || containsAny(content, FORWARD_CONTENT_MARKERS)) {
96
+ return 'Backend contract source may have changed.';
97
+ }
98
+ return null;
99
+ }
100
+
101
+ function detectReverse(filePath: string, content: string): string | null {
102
+ if (filePath.includes('/vona/src/') && containsAny(content, REVERSE_VONA_CONTENT_MARKERS)) {
103
+ return 'Vona code is consuming frontend metadata or render resources.';
104
+ }
105
+ if (
106
+ filePath.includes('/zova/src/') &&
107
+ (pathContainsAny(filePath, REVERSE_ZOVA_PATH_MARKERS) ||
108
+ containsAny(content, REVERSE_ZOVA_CONTENT_MARKERS))
109
+ ) {
110
+ return 'Frontend-owned resources or metadata may affect backend consumers.';
111
+ }
112
+ return null;
113
+ }
114
+
115
+ function analyze(filePath: string, content: string): AnalysisResult {
116
+ return {
117
+ forwardReason: detectForward(filePath, content),
118
+ reverseReason: detectReverse(filePath, content),
119
+ };
120
+ }
121
+
122
+ function hasSignal(result: AnalysisResult): boolean {
123
+ return Boolean(result.forwardReason || result.reverseReason);
124
+ }
125
+
126
+ function isHighConfidenceReverseSource(filePath: string): boolean {
127
+ return filePath.includes('/zova/src/') && pathContainsAny(filePath, REVERSE_ZOVA_PATH_MARKERS);
128
+ }
129
+
130
+ function readText(filePath: string): string | null {
131
+ try {
132
+ return readFileSync(filePath, 'utf8');
133
+ } catch {
134
+ return null;
135
+ }
136
+ }
137
+
138
+ function loadState(): SyncState {
139
+ if (!existsSync(STATE_FILE)) return {};
140
+ try {
141
+ return JSON.parse(readFileSync(STATE_FILE, 'utf8')) as SyncState;
142
+ } catch {
143
+ return {};
144
+ }
145
+ }
146
+
147
+ function saveState(state: SyncState): void {
148
+ try {
149
+ writeFileSync(STATE_FILE, JSON.stringify(state), 'utf8');
150
+ } catch {
151
+ // ignore state persistence failures
152
+ }
153
+ }
154
+
155
+ function syncFingerprint(filePath: string): string {
156
+ try {
157
+ const stats = statSync(filePath, { bigint: true });
158
+ return `${filePath}:${stats.mtimeNs.toString()}`;
159
+ } catch {
160
+ return filePath;
161
+ }
162
+ }
163
+
164
+ function shouldSkipAutoSync(filePath: string): boolean {
165
+ const state = loadState();
166
+ const fingerprint = syncFingerprint(filePath);
167
+ const entry = state[ROOT_KEY];
168
+ if (!entry) return false;
169
+ if (entry.fingerprint !== fingerprint) return false;
170
+ if (Date.now() - entry.timestamp > AUTO_SYNC_WINDOW_SECONDS * 1000) return false;
171
+ return true;
172
+ }
173
+
174
+ function markAutoSync(filePath: string): void {
175
+ const state = loadState();
176
+ state[ROOT_KEY] = {
177
+ fingerprint: syncFingerprint(filePath),
178
+ timestamp: Date.now(),
179
+ };
180
+ saveState(state);
181
+ }
182
+
183
+ function runNpm(args: string[]): SpawnSyncReturns<string> {
184
+ const command = process.platform === 'win32' ? 'npm.cmd' : 'npm';
185
+ return spawnSync(command, args, {
186
+ cwd: ROOT,
187
+ encoding: 'utf8',
188
+ });
189
+ }
190
+
191
+ function summarizeProcess(result: SpawnSyncReturns<string>): string {
192
+ if (result.error) {
193
+ return result.error.message;
194
+ }
195
+ const combined = [result.stdout?.trim(), result.stderr?.trim()].filter(Boolean).join('\n');
196
+ const exitCode = result.status ?? 1;
197
+ if (!combined) {
198
+ return `command exited with code ${exitCode}`;
199
+ }
200
+ const lines = combined
201
+ .split(/\r?\n/)
202
+ .map(line => line.trim())
203
+ .filter(Boolean);
204
+ const tail = lines.slice(-3).join(' | ');
205
+ return `exit ${exitCode}: ${tail}`;
206
+ }
207
+
208
+ function autoSyncReverse(filePath: string): readonly [boolean, string] {
209
+ const buildResult = runNpm(['run', 'build:zova:admin']);
210
+ if (buildResult.status !== 0) {
211
+ return [false, `Auto-sync failed during \`npm run build:zova:admin\`: ${summarizeProcess(buildResult)}`];
212
+ }
213
+
214
+ const depsResult = runNpm(['run', 'deps:vona']);
215
+ if (depsResult.status !== 0) {
216
+ return [false, `Auto-sync failed during \`npm run deps:vona\`: ${summarizeProcess(depsResult)}`];
217
+ }
218
+
219
+ markAutoSync(filePath);
220
+ return [true, 'Auto-sync ran `npm run build:zova:admin` and `npm run deps:vona` for this reverse-chain edit.'];
221
+ }
222
+
223
+ function buildMessages(filePath: string, result: AnalysisResult): string {
224
+ const messages = ["Contract-loop gate: this change may affect Cabloy's bidirectional contract loop."];
225
+
226
+ if (result.forwardReason) {
227
+ messages.push(
228
+ `Forward chain: ${result.forwardReason} If backend contract truth changed, verify the emitted OpenAPI/contract output and regenerate the frontend consumer path before considering the task done.`,
229
+ );
230
+ messages.push(
231
+ 'After forward regeneration, keep frontend follow-up thin: prefer semantic model facades and reuse the existing resource-owner when the custom API still belongs to the same resource.',
232
+ );
233
+ }
234
+
235
+ if (result.reverseReason) {
236
+ messages.push(
237
+ `Reverse chain: ${result.reverseReason} If backend tooling or backend metadata will consume this handoff, refresh generated metadata when applicable, then run \`npm run build:zova:admin\` and \`npm run deps:vona\` for the Cabloy Basic Admin path.`,
238
+ );
239
+ if (isHighConfidenceReverseSource(filePath)) {
240
+ if (shouldSkipAutoSync(filePath)) {
241
+ messages.push('Auto-sync skipped because the same reverse-source edit was already synced recently in this repo.');
242
+ } else {
243
+ const [ok, detail] = autoSyncReverse(filePath);
244
+ messages.push(detail);
245
+ if (!ok) {
246
+ messages.push(
247
+ 'Please review the failure before continuing. If generated artifacts already contain the expected changes but consumers still behave stale, suspect local dependency drift before making more source edits.',
248
+ );
249
+ return messages.join(' ');
250
+ }
251
+ }
252
+ } else {
253
+ messages.push(
254
+ 'Auto-sync did not run because this reverse-chain signal came from the consumer side rather than a high-confidence frontend source edit.',
255
+ );
256
+ }
257
+ messages.push(
258
+ 'For Cabloy Start, apply the same reverse-chain logic but resolve the Start-specific flavor names and generated-output paths from the active Start repo before executing edition-specific steps.',
259
+ );
260
+ }
261
+
262
+ return messages.join(' ');
263
+ }
264
+
265
+ function runClaudeHook(): number {
266
+ let payload: HookPayload;
267
+ try {
268
+ const raw = readFileSync(0, 'utf8');
269
+ payload = JSON.parse(raw) as HookPayload;
270
+ } catch {
271
+ return 0;
272
+ }
273
+
274
+ const filePath = normalizePath(payload.tool_input?.file_path);
275
+ if (!filePath || !isCodeFile(filePath)) return 0;
276
+
277
+ const content = readText(filePath);
278
+ if (content === null) return 0;
279
+
280
+ const result = analyze(filePath, content);
281
+ if (!hasSignal(result)) return 0;
282
+
283
+ const message = buildMessages(filePath, result);
284
+ console.log(
285
+ JSON.stringify({
286
+ hookSpecificOutput: {
287
+ hookEventName: 'PostToolUse',
288
+ additionalContext: message,
289
+ },
290
+ systemMessage: message,
291
+ }),
292
+ );
293
+ return 0;
294
+ }
295
+
296
+ process.exit(runClaudeHook());
@@ -0,0 +1,16 @@
1
+ {
2
+ "hooks": {
3
+ "PostToolUse": [
4
+ {
5
+ "matcher": "Write|Edit",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "node .claude/hooks/contract-loop-gate.ts",
10
+ "statusMessage": "Checking contract-loop gate"
11
+ }
12
+ ]
13
+ }
14
+ ]
15
+ }
16
+ }
@@ -15,6 +15,7 @@ After generating or extending a backend thread, check which follow-up layers app
15
15
  - OpenAPI metadata
16
16
  - inferred DTO opportunities
17
17
  - frontend contract impact
18
+ - `@Api.field(...)` ordering when explicit zod schema is present: put `z.xxx(...)` last so later helpers do not get ignored
18
19
 
19
20
  ## Persistence follow-up
20
21
 
@@ -5,18 +5,44 @@ description: Use this skill whenever a Cabloy task crosses the Vona-to-Zova cont
5
5
 
6
6
  # Cabloy Contract Loop
7
7
 
8
- Use this skill when a backend contract change needs to be reflected in frontend consumers, or when frontend consumers appear stale and you need to diagnose whether the backend contract loop is the real source of drift.
8
+ Use this skill when a backend contract change needs to be reflected in frontend consumers, when frontend-owned metadata or resources need to be reflected back into backend consumers, or when either side appears stale and you need to diagnose where drift actually lives.
9
+
10
+ Read the public [Contract Loop Playbook](../../../cabloy-docs/fullstack/contract-loop-playbook.md) for the canonical bidirectional model. This skill is the branching orchestration guide.
11
+
12
+ ## Important recovery note for stale local file consumers
13
+
14
+ When generated `.zova-rest` output or other generated consumer artifacts already contain the expected new keys or types but Vona still behaves as if old consumer types are installed, treat that first as a local file-dependency installation problem rather than a source-editing problem.
15
+
16
+ This includes the reverse fullstack direction where newly added frontend resources such as custom renderers are later consumed by backend metadata.
17
+
18
+ In that situation:
19
+
20
+ 1. run the normal sync or regeneration flow first
21
+ 2. if Vona consumes newly added or changed Zova Admin render/action/metadata in Cabloy Basic, run `npm run build:zova:admin` from the repo root before anything else
22
+ 3. otherwise, if the change only affects flavor-built REST/type output, rebuild the relevant Zova flavor output first
23
+ 4. run `deps:vona`
24
+ 5. if stale behavior remains, rebuild `vona/node_modules` and reinstall dependencies
25
+
26
+ Do not keep debugging source-level contract or renderer changes until the local file-package installation state is known to be healthy.
27
+
28
+ ## Current safeguard behavior in this repo
29
+
30
+ - there is no contract-loop pre-commit gate in the current repo workflow
31
+ - the active safeguard lives in the Claude `PostToolUse` hook configured in `.claude/settings.json`
32
+ - for high-confidence reverse-chain source edits on the Zova side, the hook auto-runs `npm run build:zova:admin` and then `npm run deps:vona`
33
+ - forward-chain detections remain reminder-only, so backend contract changes still require deliberate regeneration and verification
34
+ - consumer-side reverse signals remain reminder-only, so do not assume every reverse-chain case auto-syncs itself
9
35
 
10
36
  ## Goals
11
37
 
12
38
  1. detect whether the active repository is Cabloy Basic or Cabloy Start
13
39
  2. classify whether the task truly crosses the backend/frontend contract boundary
14
- 3. support both forward contract changes and reverse stale-consumer detection
40
+ 3. route the task into one of four modes: forward chain, reverse chain, consumer drift diagnosis, or local dependency drift recovery
15
41
  4. keep the workflow contract-first instead of hand-patching generated frontend types or services
16
42
  5. prefer CLI-first regeneration paths on both Vona and Zova sides
17
43
  6. finish with end-to-end verification guidance that checks both contract production and consumption
18
44
 
19
- ## Step 1: Detect repo and contract scope
45
+ ## Step 1: Detect repo, edition, and branch
20
46
 
21
47
  Check the repository root for these marker files:
22
48
 
@@ -31,9 +57,11 @@ Interpretation:
31
57
 
32
58
  Then classify whether the task is really a contract-loop task.
33
59
 
34
- Use this skill in two common entry modes.
60
+ These four modes are shared across Cabloy Basic and Cabloy Start. Edition detection chooses the operational branch, but does not change the core contract-loop model.
61
+
62
+ Use this skill in four common entry modes.
35
63
 
36
- ### Mode A: forward contract change
64
+ ### Mode A: forward chain
37
65
 
38
66
  The user already changed or plans to change backend contract surfaces such as:
39
67
 
@@ -44,7 +72,18 @@ The user already changed or plans to change backend contract surfaces such as:
44
72
  - OpenAPI metadata
45
73
  - inferred DTO or ORM DTO output that affects API consumers
46
74
 
47
- ### Mode B: reverse stale-consumer detection
75
+ ### Mode B: reverse chain
76
+
77
+ The user changed or plans to change frontend-owned resources or metadata that backend-side tooling or metadata will later consume, such as:
78
+
79
+ - routes
80
+ - components
81
+ - icons
82
+ - custom form-field resources
83
+ - custom table-cell resources
84
+ - generated frontend metadata that backend `ZovaRender.*(...)` references depend on
85
+
86
+ ### Mode C: consumer drift diagnosis
48
87
 
49
88
  The user reports symptoms on the frontend side such as:
50
89
 
@@ -53,7 +92,11 @@ The user reports symptoms on the frontend side such as:
53
92
  - API/model consumers no longer matching backend reality
54
93
  - hand-patched frontend types that seem to drift from backend truth
55
94
 
56
- In this mode, first diagnose whether the frontend is stale because the backend contract changed and the regeneration loop was skipped.
95
+ In this mode, first diagnose whether the visible stale behavior comes from skipped regeneration, a wrong source-of-truth edit, or a stale generated consumer.
96
+
97
+ ### Mode D: local dependency drift recovery
98
+
99
+ Use this mode when generated artifacts already contain the expected keys, types, or resources, but installed local file dependencies still behave stale after the normal sync flow.
57
100
 
58
101
  If the task is only backend scaffolding or only frontend scaffolding, the more specialized scaffold skills may be the better primary choice.
59
102
 
@@ -71,12 +114,13 @@ For deeper reference material, read:
71
114
 
72
115
  - `references/contract-loop-map.md`
73
116
  - `references/verification-checklist.md`
117
+ - `references/resource-custom-state-pattern.md`
74
118
 
75
119
  ## Step 3: Identify the contract source of truth deliberately
76
120
 
77
- In Cabloy, the backend is often the source of truth for the contract. Treat that as the default unless the codebase clearly shows a generated or schema-owned frontend artifact that should be regenerated from backend output.
121
+ In Cabloy, the backend is often the source of truth for the contract. Treat that as the default unless the codebase clearly shows a frontend-owned artifact that the reverse chain should hand back into backend consumers.
78
122
 
79
- ### If this is a forward change
123
+ ### If this is the forward chain
80
124
 
81
125
  Start with the backend side and update the contract deliberately.
82
126
 
@@ -93,16 +137,37 @@ The key rule is:
93
137
 
94
138
  - do **not** patch frontend consumers first if the backend contract is the real source of truth
95
139
 
96
- ### If this is reverse stale-consumer detection
140
+ ### If this is the reverse chain
141
+
142
+ Start with the frontend-owned resource or metadata that backend consumers later depend on.
97
143
 
98
- Do not assume the frontend is the right place to fix the problem.
144
+ Typical frontend layers to inspect or change include:
145
+
146
+ - routes
147
+ - components
148
+ - icons
149
+ - custom form-field resources
150
+ - custom table-cell resources
151
+ - generated metadata that backend `ZovaRender.*(...)` references depend on
152
+
153
+ The key rule is:
154
+
155
+ - do **not** treat this as frontend-only cleanup if backend consumers depend on the generated handoff
156
+
157
+ ### If this is consumer drift diagnosis
158
+
159
+ Do not assume the visible stale behavior identifies the wrong layer automatically.
99
160
 
100
161
  Instead:
101
162
 
102
- 1. inspect what frontend artifact looks stale
163
+ 1. inspect what artifact looks stale
103
164
  2. identify whether that artifact is generated, schema-driven, or hand-authored
104
- 3. inspect the backend contract source that should feed it
105
- 4. only then decide whether the fix is regeneration, backend correction, or a genuine frontend bug
165
+ 3. inspect the source layer that should feed it
166
+ 4. only then decide whether the fix is regeneration, source correction, consumer alignment, or a genuine bug
167
+
168
+ ### If this is local dependency drift recovery
169
+
170
+ Only enter this branch after the source layer and generated handoff are already known to be correct.
106
171
 
107
172
  ## Step 4: Verify backend contract output before touching frontend consumers
108
173
 
@@ -114,6 +179,7 @@ That may include:
114
179
  - confirming DTO and validation alignment
115
180
  - checking Swagger/OpenAPI output
116
181
  - confirming that the changed endpoint or schema now reflects the intended contract
182
+ - if local OpenAPI generation depends on a running Swagger source, starting the backend service first — in Cabloy Basic, `npm run dev` is the normal path and exposes `http://localhost:7102/swagger/json?version=V31`
117
183
 
118
184
  If the backend contract output is wrong, frontend regeneration will only spread the mistake.
119
185
 
@@ -130,6 +196,13 @@ Typical Zova commands include:
130
196
  - `npm run zova :openapi:config ...`
131
197
  - `npm run zova :openapi:generate ...`
132
198
 
199
+ Preflight reminder:
200
+
201
+ - if `:openapi:generate` reads from a local Swagger endpoint, do not assume the generator itself is broken when fetch fails
202
+ - first start the backend service, typically with `npm run dev`, and confirm `http://localhost:7102/swagger/json?version=V31` is reachable
203
+
204
+ When the target is a module-local SDK, constrain `openapi.config.ts` with `operations.match` unless the module intentionally owns a broad API surface. This prevents unrelated APIs from being generated into the module.
205
+
133
206
  ### Path B: REST/type generation by flavor
134
207
 
135
208
  Use the edition-specific Zova REST/type build path when the workflow depends on the built flavor outputs.
@@ -139,6 +212,15 @@ Typical examples in Cabloy Basic include:
139
212
  - `cd zova && npm run build:rest:cabloyBasicAdmin`
140
213
  - `cd zova && npm run build:rest:cabloyBasicWeb`
141
214
 
215
+ Important Cabloy Basic reverse-sync rule:
216
+
217
+ - if Vona consumes newly added or changed Zova Admin render/action/metadata, do **not** stop at `build:rest:cabloyBasicAdmin`
218
+ - run `npm run build:zova:admin` from the repo root instead, then run `npm run deps:vona`
219
+ - treat this as a JS-bundle-plus-rest-output handoff, not a rest-types-only refresh
220
+ - the current repo safeguard may auto-run those two commands for high-confidence Zova reverse-source edits, but only as a convenience layer on top of the contract-loop model
221
+ - if the change was consumer-side, low-confidence, cross-edition, or happened outside the Claude hook path, run the reverse sync flow deliberately yourself
222
+ - prefer visible proof under `zova/src/**/.metadata/**` when it is available; if the effective handoff only appears in `.zova-rest`, treat the safeguard as conservative reminder/auto-sync assistance rather than strict proof
223
+
142
224
  For Cabloy Start, verify the exact Start-specific flavor names, paths, SSR site baselines, and project assets in the licensed Start repo.
143
225
 
144
226
  ### Path C: Downstream frontend alignment
@@ -150,6 +232,13 @@ After generation, inspect whether the frontend still needs follow-up in:
150
232
  - schema-driven UI
151
233
  - page or component assumptions
152
234
 
235
+ Keep frontend follow-up thin:
236
+
237
+ - use thin semantic model facades over generated consumers instead of re-declaring the contract
238
+ - if a custom endpoint still belongs to an existing resource, prefer one resource-state owner instead of letting a module-local model create a second cache tree
239
+
240
+ Reuse the resource-owned custom state pattern in `references/resource-custom-state-pattern.md`.
241
+
153
242
  ## Step 6: Keep edition-aware differences explicit
154
243
 
155
244
  The collaboration model is shared across Basic and Start, but the operational details may differ.
@@ -2,9 +2,16 @@
2
2
 
3
3
  Use this reference when a task crosses the backend/frontend contract boundary.
4
4
 
5
+ This map is symmetric across Cabloy Basic and Cabloy Start. Detect the active edition to choose commands and output paths, but keep the same four-way diagnosis model:
6
+
7
+ - forward chain
8
+ - reverse chain
9
+ - consumer drift
10
+ - local dependency drift
11
+
5
12
  ## Typical triggers
6
13
 
7
- ### Backend contract changed
14
+ ### Forward chain
8
15
 
9
16
  Common examples:
10
17
 
@@ -19,28 +26,135 @@ Likely next step:
19
26
  - verify backend OpenAPI output
20
27
  - regenerate the frontend consumer path
21
28
 
22
- ### Frontend consumer drift
29
+ ### Reverse chain
30
+
31
+ Common examples:
32
+
33
+ - backend metadata now references a new frontend table cell or form field
34
+ - routes, components, or icons changed and backend-side tooling depends on them
35
+ - frontend-generated metadata needs to be consumed back into Vona
36
+
37
+ Likely next step:
38
+
39
+ - regenerate frontend metadata or build output
40
+ - run the correct flavor build for the active edition
41
+ - run `deps:vona`
42
+
43
+ ### Consumer drift
23
44
 
24
45
  Common examples:
25
46
 
26
- - generated SDK no longer matches backend
47
+ - generated SDK no longer matches visible frontend behavior
27
48
  - schema-driven UI expects old shape
28
49
  - model or API service types are stale
29
50
 
30
51
  Likely next step:
31
52
 
32
- - confirm whether backend contract really changed
33
- - regenerate instead of hand-patching
53
+ - confirm whether source truth or generated output really changed
54
+ - regenerate instead of hand-patching when the generated layer is the stale one
55
+
56
+ ### Local dependency drift
57
+
58
+ Common examples:
59
+
60
+ - generated artifacts already contain the expected change
61
+ - normal sync already ran
62
+ - installed local file consumers still behave stale
63
+
64
+ Likely next step:
65
+
66
+ - stop editing source files
67
+ - repair install state only after proving the earlier stages are healthy
68
+
69
+ ## Module-local OpenAPI generation boundary
70
+
71
+ When generating a module-local OpenAPI SDK, do not leave the module config effectively unconstrained if the module should only own a narrow resource surface.
72
+
73
+ Preferred rule:
74
+
75
+ - set `operations.match` in `openapi.config.ts` so the module generates only the intended API operations
76
+
77
+ Reason:
78
+
79
+ - an unconstrained or overly broad generation pass can pull unrelated APIs into the module
80
+ - that expands generated SDK files, metadata exports, and downstream type surfaces far beyond the module’s real ownership boundary
81
+ - the result may still compile, but it weakens module boundaries and makes maintenance harder
82
+
83
+ Representative example:
84
+
85
+ - a module-level OpenAPI config such as `zova/src/module/demo-student/cli/openapi.config.ts`
86
+ - narrow the generated surface with a module-specific matcher such as `operations.match: [/^DemoStudent_*/]`
87
+
88
+ Treat that module path as an example, not as a durable dependency of the rule. The durable rule is to align `operations.match` with the module’s true API ownership boundary.
89
+
90
+ Practical check after generation:
91
+
92
+ - confirm the generated API files only contain the intended resource operations
93
+ - confirm the module metadata and exports were not polluted by unrelated APIs
94
+
95
+ ## Forward chain artifact map
96
+
97
+ 1. backend contract source
98
+ - controllers
99
+ - DTOs
100
+ - entities
101
+ - validation rules
102
+ - OpenAPI metadata
103
+ 2. emitted proof surface
104
+ - Swagger/OpenAPI output
105
+ 3. generated handoff
106
+ - generated SDK
107
+ - schema-aware helpers
108
+ - flavor-built REST output when needed
109
+ 4. consumer layers
110
+ - frontend API files
111
+ - thin model facades
112
+ - schema-driven UI
113
+ - row and page actions
114
+
115
+ ## Reverse chain artifact map
116
+
117
+ 1. frontend contract source
118
+ - routes
119
+ - components
120
+ - icons
121
+ - custom table cells
122
+ - custom form-field resources
123
+ - module metadata
124
+ 2. generated handoff
125
+ - metadata output
126
+ - flavor build output
127
+ 3. sync surface
128
+ - `deps:vona`
129
+ 4. consumer layers
130
+ - backend `ZovaRender.*(...)` references
131
+ - backend tooling and type hints
132
+ - SSR or integration paths that depend on refreshed frontend output
133
+
134
+ ## Drift diagnosis matrix
135
+
136
+ ### Source wrong
137
+
138
+ - wrong layer was edited
139
+ - emitted or generated output is therefore wrong
140
+ - fix the contract source first
141
+
142
+ ### Generated output wrong
143
+
144
+ - source is correct, but generation did not run or ran on the wrong boundary
145
+ - regenerate rather than hand-patch consumers
146
+
147
+ ### Consumer stale
34
148
 
35
- ## Shared rule
149
+ - generated output is correct, but the next consumer layer is still reading old expectations
150
+ - inspect the consumer path before changing source again
36
151
 
37
- The sequence is usually:
152
+ ### Install stale
38
153
 
39
- 1. change backend contract
40
- 2. verify backend contract output
41
- 3. regenerate frontend artifacts
42
- 4. inspect frontend consumers
43
- 5. verify end to end
154
+ - generated output is correct
155
+ - normal sync already ran
156
+ - local file dependencies still behave stale
157
+ - repair install state only after proving the earlier stages are healthy
44
158
 
45
159
  ## Anti-pattern
46
160