cabloy 5.1.192 → 5.1.193
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.
- package/.cabloy-version +1 -1
- package/CHANGELOG.md +7 -0
- package/package.json +1 -1
- package/repo-agent-governance/adapters/claude/contract-loop-gate.ts +15 -187
- package/repo-agent-governance/adapters/codex/contract-loop-gate.mjs +25 -0
- package/repo-agent-governance/adapters/codex/hooks.json +19 -0
- package/repo-agent-governance/adapters/cursor/contract-loop-gate.mjs +28 -0
- package/repo-agent-governance/adapters/cursor/hooks.json +19 -0
- package/repo-agent-governance/managed-assets.json +38 -10
- package/repo-agent-governance/manifest.json +24 -0
- package/repo-agent-governance/policies/repository.md +1 -1
- package/repo-agent-governance/scripts/governance.mjs +101 -2
- package/repo-agent-governance/scripts/pack-check.mjs +5 -0
- package/repo-agent-governance/skills/cabloy-contract-loop/SKILL.md +9 -2
- package/repo-agent-governance/skills/cabloy-contract-loop/references/verification-checklist.md +5 -3
- package/repo-agent-governance/tests/governance.test.mjs +274 -0
- package/repo-agent-governance/tools/contract-loop/core.mjs +30 -14
- package/repo-agent-governance/tools/contract-loop/hook-runtime.mjs +367 -0
- package/repo-docs/ai/agent-governance.md +8 -6
- package/repo-docs/ai/docs-skills-rules-mapping.md +4 -1
- package/repo-docs/ai/edition-consistency-checklist.md +1 -1
- package/repo-docs/ai/introduction.md +1 -1
- package/repo-docs/ai/repo-guidance.md +1 -1
- package/repo-docs/ai/rules-and-config.md +5 -3
- package/repo-docs/reference/package-map.md +1 -1
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, readFileSync, realpathSync, statSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
analyze,
|
|
8
|
+
analyzePath,
|
|
9
|
+
buildMessages,
|
|
10
|
+
isCodeFile,
|
|
11
|
+
isHighConfidenceReverseSource,
|
|
12
|
+
normalizePath,
|
|
13
|
+
resolveEdition,
|
|
14
|
+
toPosixPath,
|
|
15
|
+
} from './core.mjs';
|
|
16
|
+
|
|
17
|
+
const STATE_FILE = path.join(os.tmpdir(), 'cabloy-contract-loop-gate-state.json');
|
|
18
|
+
const AUTO_SYNC_WINDOW_SECONDS = 300;
|
|
19
|
+
|
|
20
|
+
function readText(filePath) {
|
|
21
|
+
try {
|
|
22
|
+
return readFileSync(filePath, 'utf8');
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isDirectory(filePath) {
|
|
29
|
+
try {
|
|
30
|
+
return statSync(filePath).isDirectory();
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function canonicalPath(filePath) {
|
|
37
|
+
let current = filePath;
|
|
38
|
+
const missingSegments = [];
|
|
39
|
+
for (;;) {
|
|
40
|
+
try {
|
|
41
|
+
return path.resolve(realpathSync(current), ...missingSegments);
|
|
42
|
+
} catch {
|
|
43
|
+
const parent = path.dirname(current);
|
|
44
|
+
if (parent === current) return filePath;
|
|
45
|
+
missingSegments.unshift(path.basename(current));
|
|
46
|
+
current = parent;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isInside(root, candidate) {
|
|
52
|
+
const relative = path.relative(canonicalPath(root), canonicalPath(candidate));
|
|
53
|
+
return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function loadState() {
|
|
57
|
+
if (!existsSync(STATE_FILE)) return {};
|
|
58
|
+
try {
|
|
59
|
+
return JSON.parse(readFileSync(STATE_FILE, 'utf8'));
|
|
60
|
+
} catch {
|
|
61
|
+
return {};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function saveState(state) {
|
|
66
|
+
try {
|
|
67
|
+
writeFileSync(STATE_FILE, JSON.stringify(state), 'utf8');
|
|
68
|
+
} catch {
|
|
69
|
+
// State prevents duplicate convenience syncs only; it must not block guidance.
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function syncFingerprint(filePath) {
|
|
74
|
+
try {
|
|
75
|
+
const stats = statSync(filePath, { bigint: true });
|
|
76
|
+
return `${filePath}:${stats.mtimeNs.toString()}`;
|
|
77
|
+
} catch {
|
|
78
|
+
return filePath;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function shouldSkipAutoSync(root, filePath) {
|
|
83
|
+
const state = loadState();
|
|
84
|
+
const entry = state[toPosixPath(root)];
|
|
85
|
+
return Boolean(
|
|
86
|
+
entry &&
|
|
87
|
+
entry.fingerprint === syncFingerprint(filePath) &&
|
|
88
|
+
Date.now() - entry.timestamp <= AUTO_SYNC_WINDOW_SECONDS * 1000,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function markAutoSync(root, filePath) {
|
|
93
|
+
const state = loadState();
|
|
94
|
+
state[toPosixPath(root)] = { fingerprint: syncFingerprint(filePath), timestamp: Date.now() };
|
|
95
|
+
saveState(state);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function runNpm(root, args) {
|
|
99
|
+
const command = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
100
|
+
return spawnSync(command, [...args], { cwd: root, encoding: 'utf8' });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function summarizeProcess(result) {
|
|
104
|
+
if (result.error) return result.error.message;
|
|
105
|
+
const combined = [result.stdout?.trim(), result.stderr?.trim()].filter(Boolean).join('\n');
|
|
106
|
+
const exitCode = result.status ?? 1;
|
|
107
|
+
if (!combined) return `command exited with code ${exitCode}`;
|
|
108
|
+
return `exit ${exitCode}: ${combined
|
|
109
|
+
.split(/\r?\n/)
|
|
110
|
+
.map(line => line.trim())
|
|
111
|
+
.filter(Boolean)
|
|
112
|
+
.slice(-3)
|
|
113
|
+
.join(' | ')}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function autoSyncReverse(root, filePath, edition) {
|
|
117
|
+
for (const command of edition.reverseAutoSyncCommands) {
|
|
118
|
+
const result = runNpm(root, command.args);
|
|
119
|
+
if (result.status !== 0) {
|
|
120
|
+
return {
|
|
121
|
+
kind: 'failure',
|
|
122
|
+
message: `Auto-sync failed during \`${command.display}\`: ${summarizeProcess(result)}`,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
markAutoSync(root, filePath);
|
|
127
|
+
return {
|
|
128
|
+
kind: 'success',
|
|
129
|
+
message: `Auto-sync ran ${edition.reverseAutoSyncCommands
|
|
130
|
+
.map(command => `\`${command.display}\``)
|
|
131
|
+
.join(' and ')} for this ${edition.label} reverse-chain edit.`,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function resolveReverseSyncOutcome(root, filePath, reverseReason) {
|
|
136
|
+
if (!reverseReason) return { kind: 'not-applicable', message: '' };
|
|
137
|
+
if (!isHighConfidenceReverseSource(filePath)) {
|
|
138
|
+
return {
|
|
139
|
+
kind: 'not-applicable',
|
|
140
|
+
message:
|
|
141
|
+
'Auto-sync did not run because this reverse-chain signal came from the consumer side rather than a high-confidence frontend source edit.',
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
const resolution = resolveEdition(root);
|
|
145
|
+
if (resolution.kind !== 'resolved') {
|
|
146
|
+
return {
|
|
147
|
+
kind: 'not-applicable',
|
|
148
|
+
message: `Auto-sync did not run because ${resolution.kind === 'ambiguous' ? 'both Cabloy edition markers are present' : 'no Cabloy edition marker is present'}.`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
if (shouldSkipAutoSync(root, filePath)) {
|
|
152
|
+
return {
|
|
153
|
+
kind: 'skipped',
|
|
154
|
+
message:
|
|
155
|
+
'Auto-sync skipped because the same reverse-source edit was already synced recently in this repo.',
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
return autoSyncReverse(root, filePath, resolution.edition);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function readHookPayload() {
|
|
162
|
+
try {
|
|
163
|
+
return JSON.parse(readFileSync(0, 'utf8'));
|
|
164
|
+
} catch {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function extractClaudeEditedFilePath(payload) {
|
|
170
|
+
const filePath = payload?.tool_input?.file_path;
|
|
171
|
+
return typeof filePath === 'string' && filePath ? filePath : null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function extractCodexEditedFiles(payload) {
|
|
175
|
+
const command = payload?.tool_input?.command;
|
|
176
|
+
if (typeof command !== 'string') return [];
|
|
177
|
+
const entries = [];
|
|
178
|
+
let pendingUpdate = null;
|
|
179
|
+
const headerPattern =
|
|
180
|
+
/^\*\*\* (Add|Update|Delete) File:\s*(.+?)\s*$|^\*\*\* Move to:\s*(.+?)\s*$/gm;
|
|
181
|
+
|
|
182
|
+
const flushUpdate = () => {
|
|
183
|
+
if (pendingUpdate) entries.push(pendingUpdate);
|
|
184
|
+
pendingUpdate = null;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
for (const match of command.matchAll(headerPattern)) {
|
|
188
|
+
const [, verb, sourcePath, destinationPath] = match;
|
|
189
|
+
if (destinationPath !== undefined) {
|
|
190
|
+
if (pendingUpdate) pendingUpdate.filePath = destinationPath.trim();
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
flushUpdate();
|
|
194
|
+
const filePath = sourcePath.trim();
|
|
195
|
+
if (!filePath) continue;
|
|
196
|
+
const operation = verb.toLowerCase();
|
|
197
|
+
if (operation === 'update') {
|
|
198
|
+
pendingUpdate = { operation, filePath };
|
|
199
|
+
} else {
|
|
200
|
+
entries.push({ operation, filePath });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
flushUpdate();
|
|
204
|
+
return entries;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Retain the path-only export for callers that only need the historical view.
|
|
208
|
+
export function extractCodexEditedFilePaths(payload) {
|
|
209
|
+
return extractCodexEditedFiles(payload).map(entry => entry.filePath);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function extractCursorEditedFilePath(payload) {
|
|
213
|
+
if (!payload || typeof payload !== 'object') return null;
|
|
214
|
+
if (typeof payload.file_path === 'string' && payload.file_path) return payload.file_path;
|
|
215
|
+
const input = payload.tool_input;
|
|
216
|
+
if (!input || typeof input !== 'object') return null;
|
|
217
|
+
for (const key of ['path', 'file_path', 'target_file']) {
|
|
218
|
+
const value = input[key];
|
|
219
|
+
if (typeof value === 'string' && value) return value;
|
|
220
|
+
}
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function inferCursorHookEvent(payload) {
|
|
225
|
+
const eventName = payload?.hook_event_name;
|
|
226
|
+
if (
|
|
227
|
+
eventName === 'afterFileEdit' ||
|
|
228
|
+
eventName === 'afterTabFileEdit' ||
|
|
229
|
+
eventName === 'postToolUse'
|
|
230
|
+
) {
|
|
231
|
+
return eventName;
|
|
232
|
+
}
|
|
233
|
+
if (typeof payload?.file_path === 'string' && payload.file_path && !payload.tool_name) {
|
|
234
|
+
return 'afterFileEdit';
|
|
235
|
+
}
|
|
236
|
+
return 'postToolUse';
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function formatClaudeHookOutput(message) {
|
|
240
|
+
return {
|
|
241
|
+
hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: message },
|
|
242
|
+
systemMessage: message,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function formatCodexHookOutput(message) {
|
|
247
|
+
return {
|
|
248
|
+
hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: message },
|
|
249
|
+
systemMessage: message,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function formatCursorHookOutput(message, eventName) {
|
|
254
|
+
if (eventName !== 'postToolUse') return null;
|
|
255
|
+
return { additional_context: message };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function resolvePathBase(root, candidate) {
|
|
259
|
+
if (typeof candidate !== 'string' || !candidate) return root;
|
|
260
|
+
const pathBase = normalizePath(root, candidate);
|
|
261
|
+
if (!pathBase || !isInside(root, pathBase) || !isDirectory(pathBase)) return root;
|
|
262
|
+
return pathBase;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function createInspectionFailure(root, filePath, operation) {
|
|
266
|
+
const relativePath = toPosixPath(path.relative(root, filePath));
|
|
267
|
+
return {
|
|
268
|
+
kind: 'failure',
|
|
269
|
+
filePath,
|
|
270
|
+
message: `Contract-loop gate: could not inspect edited source \`${relativePath}\` after this ${operation} patch; no contract-loop guidance or auto-sync ran.`,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function inspectEditedFile(root, rawPath, { operation = 'update', pathBase = root } = {}) {
|
|
275
|
+
const filePath = normalizePath(pathBase, rawPath);
|
|
276
|
+
if (!filePath || !isInside(root, filePath) || !isCodeFile(filePath)) return null;
|
|
277
|
+
|
|
278
|
+
const resolution = resolveEdition(root);
|
|
279
|
+
if (operation === 'delete') {
|
|
280
|
+
const result = analyzePath(filePath);
|
|
281
|
+
if (!result.forwardReason && !result.reverseReason) return null;
|
|
282
|
+
return { kind: 'signal', filePath, resolution, result };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const content = readText(filePath);
|
|
286
|
+
if (content === null) return createInspectionFailure(root, filePath, operation);
|
|
287
|
+
const result = analyze(
|
|
288
|
+
filePath,
|
|
289
|
+
content,
|
|
290
|
+
resolution.kind === 'resolved' ? resolution.edition : null,
|
|
291
|
+
);
|
|
292
|
+
if (!result.forwardReason && !result.reverseReason) return null;
|
|
293
|
+
return { kind: 'signal', filePath, resolution, result };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export function evaluateEditedFile(root, rawPath) {
|
|
297
|
+
const inspected = inspectEditedFile(root, rawPath);
|
|
298
|
+
if (!inspected || inspected.kind !== 'signal') return null;
|
|
299
|
+
const { filePath, resolution, result } = inspected;
|
|
300
|
+
const reverseSyncOutcome = resolveReverseSyncOutcome(root, filePath, result.reverseReason);
|
|
301
|
+
return {
|
|
302
|
+
filePath,
|
|
303
|
+
message: buildMessages(result, resolution, reverseSyncOutcome),
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export function evaluateCodexEditedFiles(root, entries, payloadCwd) {
|
|
308
|
+
const pathBase = resolvePathBase(root, payloadCwd);
|
|
309
|
+
const seenEntries = new Set();
|
|
310
|
+
const inspected = [];
|
|
311
|
+
for (const entry of entries) {
|
|
312
|
+
if (!entry || typeof entry.filePath !== 'string') continue;
|
|
313
|
+
const operation = ['add', 'update', 'delete'].includes(entry.operation)
|
|
314
|
+
? entry.operation
|
|
315
|
+
: 'update';
|
|
316
|
+
const normalizedPath = normalizePath(pathBase, entry.filePath);
|
|
317
|
+
if (!normalizedPath || !isInside(root, normalizedPath)) continue;
|
|
318
|
+
const key = `${operation}:${normalizedPath}`;
|
|
319
|
+
if (seenEntries.has(key)) continue;
|
|
320
|
+
seenEntries.add(key);
|
|
321
|
+
const item = inspectEditedFile(root, entry.filePath, { operation, pathBase });
|
|
322
|
+
if (item) inspected.push(item);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const autoSyncItem = inspected.find(
|
|
326
|
+
item =>
|
|
327
|
+
item.kind === 'signal' &&
|
|
328
|
+
item.result.reverseReason &&
|
|
329
|
+
isHighConfidenceReverseSource(item.filePath),
|
|
330
|
+
);
|
|
331
|
+
const autoSyncOutcome = autoSyncItem
|
|
332
|
+
? resolveReverseSyncOutcome(root, autoSyncItem.filePath, autoSyncItem.result.reverseReason)
|
|
333
|
+
: null;
|
|
334
|
+
|
|
335
|
+
return inspected.map(item => {
|
|
336
|
+
if (item.kind === 'failure') return { filePath: item.filePath, message: item.message };
|
|
337
|
+
|
|
338
|
+
let reverseSyncOutcome;
|
|
339
|
+
if (item === autoSyncItem) {
|
|
340
|
+
reverseSyncOutcome = autoSyncOutcome;
|
|
341
|
+
} else if (item.result.reverseReason && isHighConfidenceReverseSource(item.filePath)) {
|
|
342
|
+
reverseSyncOutcome = {
|
|
343
|
+
kind: 'skipped',
|
|
344
|
+
message:
|
|
345
|
+
'Auto-sync skipped because another high-confidence reverse-source file in this Codex edit was already handled.',
|
|
346
|
+
};
|
|
347
|
+
} else {
|
|
348
|
+
reverseSyncOutcome = resolveReverseSyncOutcome(
|
|
349
|
+
root,
|
|
350
|
+
item.filePath,
|
|
351
|
+
item.result.reverseReason,
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
return {
|
|
355
|
+
filePath: item.filePath,
|
|
356
|
+
message: buildMessages(item.result, item.resolution, reverseSyncOutcome),
|
|
357
|
+
};
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Keep the prior general API for direct callers. Codex now uses the operation-aware evaluator.
|
|
362
|
+
export function evaluateEditedFiles(root, rawPaths) {
|
|
363
|
+
return evaluateCodexEditedFiles(
|
|
364
|
+
root,
|
|
365
|
+
rawPaths.map(filePath => ({ operation: 'update', filePath })),
|
|
366
|
+
);
|
|
367
|
+
}
|
|
@@ -15,14 +15,16 @@ npm run agent:governance:check
|
|
|
15
15
|
|
|
16
16
|
The committed repository adapter outputs are:
|
|
17
17
|
|
|
18
|
-
| Agent surface | Generated repository assets
|
|
19
|
-
| ------------- |
|
|
20
|
-
| Claude Code | `CLAUDE.md`, `.claude/skills/`, `.claude/commands/`, `.claude/settings.json`, `.claude/hooks/`
|
|
21
|
-
| Codex | `AGENTS.md`, `.agents/skills/`
|
|
22
|
-
| Cursor | `.cursor/rules/cabloy-governance.mdc`, `.cursor/skills/`
|
|
18
|
+
| Agent surface | Generated repository assets |
|
|
19
|
+
| ------------- | ------------------------------------------------------------------------------------------------ |
|
|
20
|
+
| Claude Code | `CLAUDE.md`, `.claude/skills/`, `.claude/commands/`, `.claude/settings.json`, `.claude/hooks/` |
|
|
21
|
+
| Codex | `AGENTS.md`, `.agents/skills/`, `.codex/hooks.json`, `.codex/hooks/` |
|
|
22
|
+
| Cursor | `.cursor/rules/cabloy-governance.mdc`, `.cursor/skills/`, `.cursor/hooks.json`, `.cursor/hooks/` |
|
|
23
23
|
|
|
24
24
|
The same root rule and skill sources are copied to every listed adapter. Platform-specific discovery metadata is intentionally thin and lives in the adapter layer. The generated Codex and Cursor skill paths are configured adapter locations; their client discovery behavior requires a version-pinned external smoke check before it is claimed as supported.
|
|
25
25
|
|
|
26
|
+
Codex requires the user to trust project-local hooks before it runs `.codex/hooks.json`; that local trust state is not generated or committed by this repository. The repository launcher resolves a hook from nested working directories without Git once Codex invokes it, but Codex discovery remains client-version behavior. For non-Git projects opened from nested directories, configure local Codex `project_root_markers` to include `__CABLOY_BASIC__` and `__CABLOY_START__` alongside `.git`; do not generate that user setting from this repository.
|
|
27
|
+
|
|
26
28
|
### Package migration bridge
|
|
27
29
|
|
|
28
30
|
The transition package deliberately excludes generated root adapters because the immediately preceding upgrader overwrites those paths before it starts the incoming initializer. This prevents that legacy path from silently replacing customized adapters. The package still includes canonical governance and `scripts/init.ts`; `npm run init` performs ownership-aware adoption for both a staged upgrade and a fresh project. A source checkout retains its committed generated adapters. This bridge is package-transition behavior, not a claim that agents generate instructions at startup.
|
|
@@ -31,7 +33,7 @@ The transition package deliberately excludes generated root adapters because the
|
|
|
31
33
|
|
|
32
34
|
Rules, procedural skills, references, eval fixtures, contract-loop analysis, and ordinary deterministic repository tools are portable.
|
|
33
35
|
|
|
34
|
-
Hooks, tool permissions, local settings, credentials, MCP registration, slash-command discovery, confirmation UI, and automatic command execution are not portable contracts. Claude Code
|
|
36
|
+
Hooks, tool permissions, local settings, credentials, MCP registration, slash-command discovery, confirmation UI, and automatic command execution are not portable contracts. Claude Code, Codex, and Cursor each have a platform-specific contract-loop convenience hook over the same portable classifier and reverse auto-sync runtime. Their hook payloads and stdout contracts differ, so each adapter must remain provider-specific even when it shares the same runtime.
|
|
35
37
|
|
|
36
38
|
No adapter output owns `settings.local.json`, `CLAUDE.local.md`, worktree state, scheduled-task state, credentials, or arbitrary user-owned agent files.
|
|
37
39
|
|
|
@@ -81,6 +81,9 @@ Generated adapter locations include:
|
|
|
81
81
|
|
|
82
82
|
- policy adapters: `CLAUDE.md`, `AGENTS.md`, and `.cursor/rules/`
|
|
83
83
|
- Claude-specific command adapter: `.claude/commands/`
|
|
84
|
+
- Claude-specific hook adapter: `.claude/settings.json`, `.claude/hooks/`
|
|
85
|
+
- Codex-specific hook adapter: `.codex/hooks.json`, `.codex/hooks/`
|
|
86
|
+
- Cursor-specific hook adapter: `.cursor/hooks.json`, `.cursor/hooks/`
|
|
84
87
|
|
|
85
88
|
Codex and Cursor adapter paths are configured output locations; verify active-client discovery externally before claiming that behavior is supported.
|
|
86
89
|
|
|
@@ -98,7 +101,7 @@ Canonical location:
|
|
|
98
101
|
|
|
99
102
|
- `repo-agent-governance/skills/`
|
|
100
103
|
|
|
101
|
-
Generated adapter locations include `.claude/skills/`, `.agents/skills/`, and `.cursor/skills/`. The Codex and Cursor locations are configured output paths, not verified client-discovery claims; validate the active client version externally before relying on discovery.
|
|
104
|
+
Generated adapter locations include `.claude/skills/`, `.agents/skills/`, `.codex/`, and `.cursor/skills/`. The Codex and Cursor locations are configured output paths, not verified client-discovery claims; validate the active client version externally before relying on discovery.
|
|
102
105
|
|
|
103
106
|
Use skills for:
|
|
104
107
|
|
|
@@ -7,7 +7,7 @@ Use it whenever you add or revise:
|
|
|
7
7
|
- edition-aware docs
|
|
8
8
|
- `repo-agent-governance/` rules, commands, and skill bundles
|
|
9
9
|
- generated Claude, Codex, or Cursor adapter outputs
|
|
10
|
-
- platform-specific Claude commands, hooks, or settings when they are affected
|
|
10
|
+
- platform-specific Claude, Codex, or Cursor commands, hooks, or settings when they are affected
|
|
11
11
|
- AI workflow playbooks
|
|
12
12
|
|
|
13
13
|
## The baseline story that should stay consistent
|
|
@@ -52,7 +52,7 @@ Use `repo-agent-governance/` as the authored source for portable repository rule
|
|
|
52
52
|
|
|
53
53
|
### Platform runtime integration
|
|
54
54
|
|
|
55
|
-
`CLAUDE.md`, `.claude/commands/`, `.claude/skills/`, `AGENTS.md`, `.agents/skills/`, and `.cursor/` are adapter/runtime surfaces. Hooks, permissions, local settings, credentials, and provider-specific command discovery stay platform-specific rather than becoming shared framework facts.
|
|
55
|
+
`CLAUDE.md`, `.claude/commands/`, `.claude/skills/`, `AGENTS.md`, `.agents/skills/`, `.codex/`, and `.cursor/` are adapter/runtime surfaces. Hooks, permissions, local settings, credentials, and provider-specific command discovery stay platform-specific rather than becoming shared framework facts.
|
|
56
56
|
|
|
57
57
|
## AI reading paths
|
|
58
58
|
|
|
@@ -11,7 +11,7 @@ Check the root repository signals first:
|
|
|
11
11
|
- `repo-docs-internal/`
|
|
12
12
|
- `repo-specs/` when the task concerns a suite plan, WBS increment, acceptance record, or evidence
|
|
13
13
|
- `repo-agent-governance/` canonical rules, skill bundles, and deterministic tools
|
|
14
|
-
- generated platform adapter assets such as `CLAUDE.md`, `AGENTS.md`, `.claude/`, `.agents/`, and `.cursor/`
|
|
14
|
+
- generated platform adapter assets such as `CLAUDE.md`, `AGENTS.md`, `.claude/`, `.agents/`, `.codex/`, and `.cursor/`
|
|
15
15
|
|
|
16
16
|
These surfaces tell the agent which repo it is in, which scripts are canonical, and where shared agent instructions are authored. Treat platform directories as generated adapters; regenerate them from `repo-agent-governance/` rather than hand-editing them. Inspect relevant `repo-docs-internal/` records as supporting context rather than as a gate to workflow guidance.
|
|
17
17
|
|
|
@@ -21,10 +21,12 @@ npm run test:agent-governance
|
|
|
21
21
|
The following are committed generated outputs, not parallel authored knowledge sources:
|
|
22
22
|
|
|
23
23
|
- Claude Code: `CLAUDE.md`, `.claude/commands/`, `.claude/skills/`, `.claude/settings.json`, `.claude/hooks/`
|
|
24
|
-
- Codex: `AGENTS.md`, `.agents/skills/`
|
|
25
|
-
- Cursor: `.cursor/rules/cabloy-governance.mdc`, `.cursor/skills/`
|
|
24
|
+
- Codex: `AGENTS.md`, `.agents/skills/`, `.codex/hooks.json`, `.codex/hooks/`
|
|
25
|
+
- Cursor: `.cursor/rules/cabloy-governance.mdc`, `.cursor/skills/`, `.cursor/hooks.json`, `.cursor/hooks/`
|
|
26
26
|
|
|
27
|
-
The Codex and Cursor skill paths are configured generated adapter locations; verify client discovery with a version-pinned external smoke check before presenting it as supported behavior. The adapter model shares rules and procedures, not
|
|
27
|
+
The Codex and Cursor skill paths are configured generated adapter locations; verify client discovery with a version-pinned external smoke check before presenting it as supported behavior. The adapter model shares rules and procedures, not hook payload formats. Claude Code, Codex, and Cursor each register a platform-specific contract-loop convenience hook over the same portable runtime.
|
|
28
|
+
|
|
29
|
+
Codex runs project-local `.codex/hooks.json` only after the user trusts that hook definition. Hook trust is local state and remains outside generated governance ownership. The generated launcher is Git-free once Codex invokes it, but non-Git project discovery from a nested directory remains client-controlled; configure local Codex `project_root_markers` with Cabloy edition markers when that scenario must be supported.
|
|
28
30
|
|
|
29
31
|
## Local configuration and permissions
|
|
30
32
|
|
|
@@ -8,7 +8,7 @@ This monorepo is organized around a small set of stable top-level areas.
|
|
|
8
8
|
- `lerna.json` — package topology
|
|
9
9
|
- `repo-docs-internal/` — edition-local maintainer notes and ADRs
|
|
10
10
|
- `repo-agent-governance/` — authored portable agent rules, root skill bundles, adapter definitions, and deterministic tools
|
|
11
|
-
- `CLAUDE.md`, `AGENTS.md`, `.claude/`, `.agents/`, `.cursor/` — generated platform adapters plus platform-specific runtime integration
|
|
11
|
+
- `CLAUDE.md`, `AGENTS.md`, `.claude/`, `.agents/`, `.codex/`, `.cursor/` — generated platform adapters plus platform-specific runtime integration
|
|
12
12
|
- `repo-docs/` — unified public docs
|
|
13
13
|
- `repo-specs/` — suite-local product, technical, delivery, ATP, progress, observed-evidence, and ADR authority
|
|
14
14
|
|