cabloy 5.1.190 → 5.1.192

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 (101) hide show
  1. package/.cabloy-version +1 -1
  2. package/.github/workflows/agent-governance.yml +39 -0
  3. package/.github/workflows/docs-pages.yml +1 -0
  4. package/.gitignore +3 -0
  5. package/CHANGELOG.md +17 -0
  6. package/README.md +2 -2
  7. package/package.json +11 -5
  8. package/repo-agent-governance/adapters/claude/contract-loop-gate.ts +199 -0
  9. package/{.claude → repo-agent-governance}/commands/release.md +20 -20
  10. package/repo-agent-governance/managed-assets.json +887 -0
  11. package/repo-agent-governance/manifest.json +56 -0
  12. package/{CLAUDE.md → repo-agent-governance/policies/repository.md} +4 -2
  13. package/repo-agent-governance/scripts/governance.mjs +570 -0
  14. package/repo-agent-governance/scripts/pack-check.mjs +43 -0
  15. package/{.claude → repo-agent-governance}/skills/cabloy-contract-loop/references/contract-loop-map.md +4 -4
  16. package/{.claude → repo-agent-governance}/skills/cabloy-master-detail/SKILL.md +1 -1
  17. package/{.claude → repo-agent-governance}/skills/cabloy-module-removal/SKILL.md +1 -1
  18. package/{.claude → repo-agent-governance}/skills/cabloy-spec-execution/SKILL.md +1 -1
  19. package/{.claude → repo-agent-governance}/skills/cabloy-spec-execution/references/execution-protocol.md +8 -8
  20. package/{.claude → repo-agent-governance}/skills/cabloy-spec-execution/references/status-and-evidence.md +9 -9
  21. package/{.claude → repo-agent-governance}/skills/cabloy-spec-generation/SKILL.md +2 -2
  22. package/{.claude → repo-agent-governance}/skills/cabloy-spec-generation/references/repo-aware-discovery.md +7 -7
  23. package/{.claude → repo-agent-governance}/skills/cabloy-spec-generation/references/repo-specs-document-set.md +30 -30
  24. package/{.claude → repo-agent-governance}/skills/cabloy-spec-generation/references/traceability-and-status-rules.md +9 -9
  25. package/{.claude → repo-agent-governance}/skills/cabloy-workflow/SKILL.md +7 -8
  26. package/{.claude → repo-agent-governance}/skills/cabloy-worktree-environment/SKILL.md +9 -9
  27. package/{.claude → repo-agent-governance}/skills/cabloy-zova-source-reading/SKILL.md +5 -5
  28. package/repo-agent-governance/tests/governance.test.mjs +295 -0
  29. package/repo-agent-governance/tools/contract-loop/check.mjs +71 -0
  30. package/repo-agent-governance/tools/contract-loop/core.mjs +189 -0
  31. package/repo-agent-governance/tools/spec-charts/generate-implementation-charts.mjs +858 -0
  32. package/{.claude/skills/cabloy-spec-generation/scripts → repo-agent-governance/tools/spec-charts}/generate-implementation-charts.test.mjs +95 -20
  33. package/repo-docs/.vitepress/config.mjs +2 -0
  34. package/repo-docs/ai/agent-governance.md +80 -0
  35. package/repo-docs/ai/cli-to-skill-map.md +3 -3
  36. package/repo-docs/ai/docs-skills-rules-mapping.md +27 -26
  37. package/repo-docs/ai/edition-consistency-checklist.md +8 -6
  38. package/repo-docs/ai/future-skill-roadmap.md +2 -2
  39. package/repo-docs/ai/introduction.md +7 -6
  40. package/repo-docs/ai/repo-guidance.md +3 -2
  41. package/repo-docs/ai/rules-and-config.md +23 -15
  42. package/repo-docs/ai/skills.md +7 -7
  43. package/repo-docs/ai/verification.md +2 -1
  44. package/repo-docs/demo.md +12 -0
  45. package/repo-docs/fullstack/contract-loop-playbook.md +1 -1
  46. package/repo-docs/fullstack/quickstart.md +1 -1
  47. package/repo-docs/index.md +3 -0
  48. package/repo-docs/reference/package-map.md +3 -2
  49. package/repo-docs/reference/repo-scripts.md +17 -2
  50. package/scripts/bootstrapAgentGovernance.mjs +81 -0
  51. package/scripts/init.ts +1 -0
  52. package/scripts/release.ts +15 -0
  53. package/scripts/upgrade.ts +66 -7
  54. package/zova/packages-zova/zova/package.json +2 -2
  55. package/zova/pnpm-lock.yaml +3 -3
  56. package/zova/src/suite/a-home/modules/home-passport/src/model/passport.ts +2 -7
  57. package/zova/src/suite/cabloy-basic/modules/basic-details/src/bean/tableCell.actionOperationsRow.tsx +19 -13
  58. package/zova/src/suite/cabloy-basic/modules/basic-details/src/component/blockDetails/controller.tsx +11 -0
  59. package/zova/src/suite/cabloy-basic/modules/basic-details/src/component/blockToolbarBulk/controller.tsx +7 -4
  60. package/zova/src/suite/cabloy-basic/modules/basic-details/src/component/formFieldDetails/controller.tsx +14 -0
  61. package/zova/src/suite/cabloy-basic/modules/basic-details/src/lib/detailsPermissions.ts +65 -0
  62. package/zova/src/suite/cabloy-basic/modules/basic-details/src/lib/index.ts +1 -0
  63. package/zova/src/suite/cabloy-basic/modules/basic-details/src/lib/utils.ts +3 -3
  64. package/zova/src/suite/cabloy-basic/modules/basic-details/src/types/details.ts +6 -1
  65. package/zova/src/suite/cabloy-basic/modules/basic-details/test/lib/detailsPermissions.test.ts +125 -0
  66. package/zova/src/suite-vendor/a-zova/modules/a-openapi/package.json +1 -1
  67. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/permissions.ts +2 -8
  68. package/zova/src/suite-vendor/a-zova/modules/a-table/package.json +1 -1
  69. package/zova/src/suite-vendor/a-zova/modules/a-table/src/component/table/controller.tsx +2 -1
  70. package/zova/src/suite-vendor/a-zova/package.json +3 -3
  71. package/.claude/hooks/contract-loop-gate.ts +0 -421
  72. package/.claude/scheduled_tasks.lock +0 -1
  73. package/.claude/skills/cabloy-spec-generation/scripts/generate-implementation-charts.mjs +0 -385
  74. /package/{.claude → repo-agent-governance/adapters/claude}/settings.json +0 -0
  75. /package/{.claude → repo-agent-governance}/skills/cabloy-backend-scaffold/SKILL.md +0 -0
  76. /package/{.claude → repo-agent-governance}/skills/cabloy-backend-scaffold/evals/evals.json +0 -0
  77. /package/{.claude → repo-agent-governance}/skills/cabloy-backend-scaffold/references/backend-thread-map.md +0 -0
  78. /package/{.claude → repo-agent-governance}/skills/cabloy-backend-scaffold/references/follow-up-checklist.md +0 -0
  79. /package/{.claude → repo-agent-governance}/skills/cabloy-contract-loop/SKILL.md +0 -0
  80. /package/{.claude → repo-agent-governance}/skills/cabloy-contract-loop/evals/evals.json +0 -0
  81. /package/{.claude → repo-agent-governance}/skills/cabloy-contract-loop/references/resource-custom-state-pattern.md +0 -0
  82. /package/{.claude → repo-agent-governance}/skills/cabloy-contract-loop/references/verification-checklist.md +0 -0
  83. /package/{.claude → repo-agent-governance}/skills/cabloy-domain-planning/SKILL.md +0 -0
  84. /package/{.claude → repo-agent-governance}/skills/cabloy-frontend-scaffold/SKILL.md +0 -0
  85. /package/{.claude → repo-agent-governance}/skills/cabloy-frontend-scaffold/evals/evals.json +0 -0
  86. /package/{.claude → repo-agent-governance}/skills/cabloy-frontend-scaffold/references/follow-up-checklist.md +0 -0
  87. /package/{.claude → repo-agent-governance}/skills/cabloy-frontend-scaffold/references/frontend-thread-map.md +0 -0
  88. /package/{.claude → repo-agent-governance}/skills/cabloy-resource-field-update/SKILL.md +0 -0
  89. /package/{.claude → repo-agent-governance}/skills/cabloy-resource-field-update/evals/evals.json +0 -0
  90. /package/{.claude → repo-agent-governance}/skills/cabloy-resource-field-update/references/custom-renderer-demo-checklist.md +0 -0
  91. /package/{.claude → repo-agent-governance}/skills/cabloy-resource-field-update/references/field-update-decision-tree.md +0 -0
  92. /package/{.claude → repo-agent-governance}/skills/cabloy-resource-field-update/references/follow-up-checklist.md +0 -0
  93. /package/{.claude → repo-agent-governance}/skills/cabloy-resource-field-update/references/verification-checklist.md +0 -0
  94. /package/{.claude → repo-agent-governance}/skills/cabloy-spec-execution/evals/evals.json +0 -0
  95. /package/{.claude → repo-agent-governance}/skills/cabloy-spec-generation/evals/evals.json +0 -0
  96. /package/{.claude → repo-agent-governance}/skills/cabloy-workflow/evals/evals.json +0 -0
  97. /package/{.claude → repo-agent-governance}/skills/cabloy-workflow/references/cli-strategy.md +0 -0
  98. /package/{.claude → repo-agent-governance}/skills/cabloy-workflow/references/edition-detection.md +0 -0
  99. /package/{.claude → repo-agent-governance}/skills/cabloy-worktree-environment/evals/evals.json +0 -0
  100. /package/{.claude → repo-agent-governance}/skills/cabloy-zova-source-reading/references/analysis-modes.md +0 -0
  101. /package/{.claude → repo-agent-governance}/skills/cabloy-zova-source-reading/references/core-reading-paths.md +0 -0
@@ -1,421 +0,0 @@
1
- #!/usr/bin/env node
2
- import { existsSync, readFileSync, statSync, writeFileSync } 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
- interface CommandPlan {
25
- args: readonly string[];
26
- display: string;
27
- }
28
-
29
- interface EditionConfig {
30
- id: 'basic' | 'start';
31
- label: string;
32
- reverseVonaContentMarkers: readonly string[];
33
- reverseAutoSyncCommands: readonly CommandPlan[];
34
- reverseWebBuildCommand: string;
35
- }
36
-
37
- type ReverseSyncOutcome =
38
- | { kind: 'not-applicable'; message: string }
39
- | { kind: 'skipped'; message: string }
40
- | { kind: 'success'; message: string }
41
- | { kind: 'failure'; message: string };
42
-
43
- type SyncState = Record<string, SyncStateEntry>;
44
-
45
- type EditionResolution =
46
- | { kind: 'resolved'; edition: EditionConfig }
47
- | { kind: 'ambiguous' }
48
- | { kind: 'missing' };
49
-
50
- const SCRIPT_FILE = fileURLToPath(import.meta.url);
51
- const ROOT = path.resolve(path.dirname(SCRIPT_FILE), '../..');
52
- const ROOT_KEY = toPosixPath(ROOT);
53
- const STATE_FILE = path.join(os.tmpdir(), 'cabloy-contract-loop-gate-state.json');
54
- const AUTO_SYNC_WINDOW_SECONDS = 300;
55
-
56
- const TARGET_PATTERNS: Array<readonly [string, string]> = [
57
- ['zova/src/module/', '.ts'],
58
- ['zova/src/module/', '.tsx'],
59
- ['zova/src/module/', '.jsx'],
60
- ['zova/src/suite/', '.ts'],
61
- ['zova/src/suite/', '.tsx'],
62
- ['zova/src/suite/', '.jsx'],
63
- ['vona/src/', '.ts'],
64
- ['vona/src/', '.tsx'],
65
- ['vona/src/', '.jsx'],
66
- ];
67
-
68
- const FORWARD_PATH_MARKERS = ['/controller/', '/dto/', '/entity/'];
69
-
70
- const FORWARD_CONTENT_MARKERS = ['@Web.', '@Api.field', '@Api.body', 'v.openapi(', '@Dto<'];
71
-
72
- const SHARED_REVERSE_VONA_CONTENT_MARKERS = [
73
- 'ZovaRender.',
74
- 'tableActionRow(',
75
- 'tableActionBulk(',
76
- ];
77
-
78
- const REVERSE_ZOVA_PATH_MARKERS = ['/src/bean/', '/src/component/', '/src/.metadata/'];
79
-
80
- const REVERSE_ZOVA_CONTENT_MARKERS = [
81
- "declare module 'zova-module-a-openapi'",
82
- 'IResourceTableActionRowRecord',
83
- '@TableCell<',
84
- '@Component(',
85
- '@Component<',
86
- ];
87
-
88
- const REVERSE_AUTO_SYNC_COMMANDS: readonly CommandPlan[] = [
89
- { args: ['run', 'build:zova:admin'], display: 'npm run build:zova:admin' },
90
- { args: ['run', 'deps:vona'], display: 'npm run deps:vona' },
91
- ];
92
-
93
- const EDITION_CONFIGS: Record<'basic' | 'start', EditionConfig> = {
94
- basic: {
95
- id: 'basic',
96
- label: 'Cabloy Basic',
97
- reverseVonaContentMarkers: ['zova-rest-cabloy-basic-admin'],
98
- reverseAutoSyncCommands: REVERSE_AUTO_SYNC_COMMANDS,
99
- reverseWebBuildCommand: 'npm run build:zova:web',
100
- },
101
- start: {
102
- id: 'start',
103
- label: 'Cabloy Start',
104
- reverseVonaContentMarkers: ['zova-rest-cabloy-start-admin'],
105
- reverseAutoSyncCommands: REVERSE_AUTO_SYNC_COMMANDS,
106
- reverseWebBuildCommand: 'npm run build:zova:web',
107
- },
108
- };
109
-
110
- const ACTIVE_EDITION_RESOLUTION = resolveEdition();
111
- const ACTIVE_EDITION =
112
- ACTIVE_EDITION_RESOLUTION.kind === 'resolved' ? ACTIVE_EDITION_RESOLUTION.edition : null;
113
- const FALLBACK_REVERSE_VONA_CONTENT_MARKERS = [
114
- ...SHARED_REVERSE_VONA_CONTENT_MARKERS,
115
- ...EDITION_CONFIGS.basic.reverseVonaContentMarkers,
116
- ...EDITION_CONFIGS.start.reverseVonaContentMarkers,
117
- ];
118
-
119
- function toPosixPath(value: string): string {
120
- return value.split(path.sep).join('/');
121
- }
122
-
123
- function normalizePath(value?: string): string | null {
124
- if (!value) return null;
125
- try {
126
- const candidate = path.isAbsolute(value) ? value : path.join(ROOT, value);
127
- return toPosixPath(path.resolve(candidate));
128
- } catch {
129
- return null;
130
- }
131
- }
132
-
133
- function resolveEdition(): EditionResolution {
134
- const hasBasic = existsSync(path.resolve(ROOT, '__CABLOY_BASIC__'));
135
- const hasStart = existsSync(path.resolve(ROOT, '__CABLOY_START__'));
136
- if (hasBasic && hasStart) return { kind: 'ambiguous' };
137
- if (!hasBasic && !hasStart) return { kind: 'missing' };
138
- return { kind: 'resolved', edition: hasBasic ? EDITION_CONFIGS.basic : EDITION_CONFIGS.start };
139
- }
140
-
141
- function getReverseVonaContentMarkers(edition: EditionConfig | null): readonly string[] {
142
- if (!edition) return FALLBACK_REVERSE_VONA_CONTENT_MARKERS;
143
- return [...SHARED_REVERSE_VONA_CONTENT_MARKERS, ...edition.reverseVonaContentMarkers];
144
- }
145
-
146
- function isCodeFile(filePath: string): boolean {
147
- return TARGET_PATTERNS.some(([prefix, suffix]) => filePath.includes(prefix) && filePath.endsWith(suffix));
148
- }
149
-
150
- function containsAny(text: string, needles: readonly string[]): boolean {
151
- return needles.some(needle => text.includes(needle));
152
- }
153
-
154
- function pathContainsAny(filePath: string, needles: readonly string[]): boolean {
155
- return needles.some(needle => filePath.includes(needle));
156
- }
157
-
158
- function detectForward(filePath: string, content: string): string | null {
159
- if (!filePath.includes('/vona/src/')) return null;
160
- if (pathContainsAny(filePath, FORWARD_PATH_MARKERS) || containsAny(content, FORWARD_CONTENT_MARKERS)) {
161
- return 'Backend contract source may have changed.';
162
- }
163
- return null;
164
- }
165
-
166
- function detectReverse(
167
- filePath: string,
168
- content: string,
169
- reverseVonaContentMarkers: readonly string[],
170
- ): string | null {
171
- if (filePath.includes('/vona/src/') && containsAny(content, reverseVonaContentMarkers)) {
172
- return 'Vona code is consuming frontend metadata or render resources.';
173
- }
174
- if (
175
- filePath.includes('/zova/src/') &&
176
- (pathContainsAny(filePath, REVERSE_ZOVA_PATH_MARKERS) ||
177
- containsAny(content, REVERSE_ZOVA_CONTENT_MARKERS))
178
- ) {
179
- return 'Frontend-owned resources or metadata may affect backend consumers.';
180
- }
181
- return null;
182
- }
183
-
184
- function analyze(filePath: string, content: string, edition: EditionConfig | null): AnalysisResult {
185
- return {
186
- forwardReason: detectForward(filePath, content),
187
- reverseReason: detectReverse(filePath, content, getReverseVonaContentMarkers(edition)),
188
- };
189
- }
190
-
191
- function hasSignal(result: AnalysisResult): boolean {
192
- return Boolean(result.forwardReason || result.reverseReason);
193
- }
194
-
195
- function isHighConfidenceReverseSource(filePath: string): boolean {
196
- return filePath.includes('/zova/src/') && pathContainsAny(filePath, REVERSE_ZOVA_PATH_MARKERS);
197
- }
198
-
199
- function readText(filePath: string): string | null {
200
- try {
201
- return readFileSync(filePath, 'utf8');
202
- } catch {
203
- return null;
204
- }
205
- }
206
-
207
- function loadState(): SyncState {
208
- if (!existsSync(STATE_FILE)) return {};
209
- try {
210
- return JSON.parse(readFileSync(STATE_FILE, 'utf8')) as SyncState;
211
- } catch {
212
- return {};
213
- }
214
- }
215
-
216
- function saveState(state: SyncState): void {
217
- try {
218
- writeFileSync(STATE_FILE, JSON.stringify(state), 'utf8');
219
- } catch {
220
- // ignore state persistence failures
221
- }
222
- }
223
-
224
- function syncFingerprint(filePath: string): string {
225
- try {
226
- const stats = statSync(filePath, { bigint: true });
227
- return `${filePath}:${stats.mtimeNs.toString()}`;
228
- } catch {
229
- return filePath;
230
- }
231
- }
232
-
233
- function shouldSkipAutoSync(filePath: string): boolean {
234
- const state = loadState();
235
- const fingerprint = syncFingerprint(filePath);
236
- const entry = state[ROOT_KEY];
237
- if (!entry) return false;
238
- if (entry.fingerprint !== fingerprint) return false;
239
- if (Date.now() - entry.timestamp > AUTO_SYNC_WINDOW_SECONDS * 1000) return false;
240
- return true;
241
- }
242
-
243
- function markAutoSync(filePath: string): void {
244
- const state = loadState();
245
- state[ROOT_KEY] = {
246
- fingerprint: syncFingerprint(filePath),
247
- timestamp: Date.now(),
248
- };
249
- saveState(state);
250
- }
251
-
252
- function runNpm(args: readonly string[]): SpawnSyncReturns<string> {
253
- const command = process.platform === 'win32' ? 'npm.cmd' : 'npm';
254
- return spawnSync(command, [...args], {
255
- cwd: ROOT,
256
- encoding: 'utf8',
257
- });
258
- }
259
-
260
- function summarizeProcess(result: SpawnSyncReturns<string>): string {
261
- if (result.error) {
262
- return result.error.message;
263
- }
264
- const combined = [result.stdout?.trim(), result.stderr?.trim()].filter(Boolean).join('\n');
265
- const exitCode = result.status ?? 1;
266
- if (!combined) {
267
- return `command exited with code ${exitCode}`;
268
- }
269
- const lines = combined
270
- .split(/\r?\n/)
271
- .map(line => line.trim())
272
- .filter(Boolean);
273
- const tail = lines.slice(-3).join(' | ');
274
- return `exit ${exitCode}: ${tail}`;
275
- }
276
-
277
- function autoSyncReverse(filePath: string, edition: EditionConfig): ReverseSyncOutcome {
278
- for (const command of edition.reverseAutoSyncCommands) {
279
- const result = runNpm(command.args);
280
- if (result.status !== 0) {
281
- return {
282
- kind: 'failure',
283
- message: `Auto-sync failed during \`${command.display}\`: ${summarizeProcess(result)}`,
284
- };
285
- }
286
- }
287
-
288
- markAutoSync(filePath);
289
- const commands = edition.reverseAutoSyncCommands.map(command => `\`${command.display}\``).join(' and ');
290
- return {
291
- kind: 'success',
292
- message: `Auto-sync ran ${commands} for this ${edition.label} reverse-chain edit.`,
293
- };
294
- }
295
-
296
- function buildReverseGuidance(edition: EditionConfig | null): string {
297
- if (!edition) {
298
- const resolutionMessage =
299
- ACTIVE_EDITION_RESOLUTION.kind === 'ambiguous'
300
- ? 'Both Cabloy edition markers are present, so stop before choosing an edition-specific build or generated-output path.'
301
- : 'No Cabloy edition marker is present, so inspect the active package scripts and repository shape before choosing an edition-specific build or generated-output path.';
302
- return `If backend tooling or backend metadata will consume this handoff, refresh generated metadata when applicable. ${resolutionMessage} Then run \`npm run deps:vona\` once the active edition is resolved.`;
303
- }
304
-
305
- const autoSyncCommands = edition.reverseAutoSyncCommands
306
- .map(command => `\`${command.display}\``)
307
- .join(', then ');
308
- return `If backend tooling or backend metadata will consume this handoff, refresh generated metadata when applicable, run ${autoSyncCommands}, and also run \`${edition.reverseWebBuildCommand}\` when the Web flavor is affected in this ${edition.label} repo.`;
309
- }
310
-
311
- function resolveReverseSyncOutcome(
312
- filePath: string,
313
- result: AnalysisResult,
314
- edition: EditionConfig | null,
315
- ): ReverseSyncOutcome {
316
- if (!result.reverseReason) {
317
- return {
318
- kind: 'not-applicable',
319
- message: '',
320
- };
321
- }
322
-
323
- if (!isHighConfidenceReverseSource(filePath)) {
324
- return {
325
- kind: 'not-applicable',
326
- message:
327
- 'Auto-sync did not run because this reverse-chain signal came from the consumer side rather than a high-confidence frontend source edit.',
328
- };
329
- }
330
-
331
- if (!edition) {
332
- const reason =
333
- ACTIVE_EDITION_RESOLUTION.kind === 'ambiguous'
334
- ? 'both Cabloy edition markers are present'
335
- : 'no Cabloy edition marker is present';
336
- return {
337
- kind: 'not-applicable',
338
- message: `Auto-sync did not run because ${reason}.`,
339
- };
340
- }
341
-
342
- if (shouldSkipAutoSync(filePath)) {
343
- return {
344
- kind: 'skipped',
345
- message: 'Auto-sync skipped because the same reverse-source edit was already synced recently in this repo.',
346
- };
347
- }
348
-
349
- return autoSyncReverse(filePath, edition);
350
- }
351
-
352
- function buildMessages(
353
- result: AnalysisResult,
354
- edition: EditionConfig | null,
355
- reverseSyncOutcome: ReverseSyncOutcome,
356
- ): string {
357
- const messages = ["Contract-loop gate: this change may affect Cabloy's bidirectional contract loop."];
358
-
359
- if (ACTIVE_EDITION_RESOLUTION.kind === 'ambiguous') {
360
- messages.push(
361
- 'Both Cabloy edition markers are present, so stop before choosing an edition-specific build, generated-output path, or auto-sync action.',
362
- );
363
- }
364
-
365
- if (result.forwardReason) {
366
- messages.push(
367
- `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.`,
368
- );
369
- messages.push(
370
- '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.',
371
- );
372
- }
373
-
374
- if (result.reverseReason) {
375
- messages.push(`Reverse chain: ${result.reverseReason} ${buildReverseGuidance(edition)}`);
376
- if (reverseSyncOutcome.message) {
377
- messages.push(reverseSyncOutcome.message);
378
- }
379
- if (reverseSyncOutcome.kind === 'failure') {
380
- messages.push(
381
- '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.',
382
- );
383
- }
384
- }
385
-
386
- return messages.join(' ');
387
- }
388
-
389
- function runClaudeHook(): number {
390
- let payload: HookPayload;
391
- try {
392
- const raw = readFileSync(0, 'utf8');
393
- payload = JSON.parse(raw) as HookPayload;
394
- } catch {
395
- return 0;
396
- }
397
-
398
- const filePath = normalizePath(payload.tool_input?.file_path);
399
- if (!filePath || !isCodeFile(filePath)) return 0;
400
-
401
- const content = readText(filePath);
402
- if (content === null) return 0;
403
-
404
- const result = analyze(filePath, content, ACTIVE_EDITION);
405
- if (!hasSignal(result)) return 0;
406
-
407
- const reverseSyncOutcome = resolveReverseSyncOutcome(filePath, result, ACTIVE_EDITION);
408
- const message = buildMessages(result, ACTIVE_EDITION, reverseSyncOutcome);
409
- console.log(
410
- JSON.stringify({
411
- hookSpecificOutput: {
412
- hookEventName: 'PostToolUse',
413
- additionalContext: message,
414
- },
415
- systemMessage: message,
416
- }),
417
- );
418
- return 0;
419
- }
420
-
421
- process.exit(runClaudeHook());
@@ -1 +0,0 @@
1
- {"sessionId":"f94cb5dc-de70-445c-8874-218b19d56077","pid":47778,"procStart":"Sun Sep 20 12:39:48 2026","acquiredAt":1789961541374}