@haposoft/cafekit 0.16.7 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -22
- package/bin/__tests__/codex-hooks-ownership.test.js +2 -2
- package/bin/__tests__/codex-hooks.test.js +0 -36
- package/bin/__tests__/codex-native.test.js +46 -88
- package/bin/__tests__/develop-contract.test.js +26 -57
- package/bin/__tests__/docs-sync-on-demand.test.js +93 -0
- package/bin/__tests__/grok-envelope.test.js +0 -56
- package/bin/__tests__/hook-payload.test.js +4 -6
- package/bin/__tests__/legacy-authority-decoupled.test.js +159 -0
- package/bin/__tests__/legacy-hooks-retired.test.js +221 -0
- package/bin/__tests__/omp-bridge.test.js +20 -17
- package/bin/__tests__/omp-hooks.test.js +0 -19
- package/bin/__tests__/optional-skill-inventory.test.js +112 -4
- package/bin/__tests__/orca-session.test.js +0 -2
- package/bin/__tests__/package-inventory.test.js +100 -541
- package/bin/__tests__/runtime-dir.test.js +0 -3
- package/bin/__tests__/runtime-schema.test.js +3 -2
- package/bin/__tests__/skill-routing-source.test.js +27 -15
- package/bin/__tests__/spec-narrowing.test.js +0 -27
- package/bin/__tests__/specs-v2-execution-closeout.test.js +4 -69
- package/bin/__tests__/ui-ux-skill.test.js +101 -0
- package/bin/__tests__/usage-hook-retired.test.js +114 -0
- package/bin/__tests__/validator-grounding.test.js +9 -28
- package/bin/phases/codex-runtime.js +24 -0
- package/bin/phases/omp-runtime.js +22 -0
- package/package.json +1 -1
- package/src/claude/CLAUDE.md +3 -10
- package/src/claude/agents/brainstormer.md +7 -3
- package/src/claude/agents/code-auditor.md +35 -50
- package/src/claude/agents/debugger.md +5 -79
- package/src/claude/agents/deployer.md +3 -5
- package/src/claude/agents/docs-keeper.md +2 -7
- package/src/claude/agents/implementer.md +6 -9
- package/src/claude/agents/inspector.md +2 -3
- package/src/claude/agents/project-manager.md +7 -15
- package/src/claude/agents/researcher.md +9 -3
- package/src/claude/agents/spec-maker.md +11 -11
- package/src/claude/hooks/__tests__/fixtures/statusline-default.golden +1 -1
- package/src/claude/hooks/__tests__/spec-gate.test.js +74 -3
- package/src/claude/hooks/__tests__/spec-state-touched.test.js +134 -0
- package/src/claude/hooks/__tests__/state.test.js +68 -0
- package/src/claude/hooks/__tests__/statusline.test.js +140 -11
- package/src/claude/hooks/agent.cjs +2 -1
- package/src/claude/hooks/lib/context.cjs +0 -89
- package/src/claude/hooks/lib/hook-payload.cjs +2 -2
- package/src/claude/hooks/rules.cjs +2 -1
- package/src/claude/hooks/session.cjs +0 -1
- package/src/claude/hooks/spec-gate.cjs +17 -28
- package/src/claude/hooks/spec-state.cjs +95 -1
- package/src/claude/hooks/state.cjs +21 -2
- package/src/claude/migration-manifest.json +30 -15
- package/src/claude/rules/ai-dev-rules.md +1 -1
- package/src/claude/rules/hook-protocols.md +8 -15
- package/src/claude/rules/manage-docs.md +9 -23
- package/src/claude/rules/orchestrator.md +3 -4
- package/src/claude/rules/review-audit-self-decision.md +3 -3
- package/src/claude/rules/skill-domain-routing.md +1 -3
- package/src/claude/rules/skill-workflow-routing.md +4 -5
- package/src/claude/rules/state-sync.md +3 -12
- package/src/claude/rules/workflow.md +10 -18
- package/src/claude/runtime.schema.json +5 -3
- package/src/claude/scripts/generate-skill-catalog.cjs +7 -6
- package/src/claude/scripts/provenance.cjs +23 -5
- package/src/claude/scripts/spec-receipt.cjs +24 -7
- package/src/claude/scripts/spec-resolver.cjs +1 -2
- package/src/claude/scripts/validate-spec-output.cjs +5 -32
- package/src/claude/settings/settings.json +1 -32
- package/src/claude/skills/{question → ask}/SKILL.md +9 -75
- package/src/claude/skills/brainstorm/SKILL.md +43 -131
- package/src/claude/skills/code-review/SKILL.md +48 -17
- package/src/claude/skills/code-review/references/spec-compliance-review.md +2 -3
- package/src/claude/skills/code-review/references/verification-gate.md +22 -27
- package/src/claude/skills/debug/SKILL.md +29 -149
- package/src/claude/skills/develop/SKILL.md +27 -36
- package/src/claude/skills/develop/references/parallel-waves.md +0 -8
- package/src/claude/skills/develop/references/quality-gate.md +15 -13
- package/src/claude/skills/docs/SKILL.md +2 -2
- package/src/claude/skills/docs/references/update-workflow.md +1 -1
- package/src/claude/skills/fix/SKILL.md +153 -0
- package/src/claude/skills/{hotfix → fix}/references/diagnosis-protocol.md +2 -2
- package/src/claude/skills/{hotfix → fix}/references/parallel-patterns.md +2 -2
- package/src/claude/skills/{hotfix → fix}/references/prevention-gate.md +3 -3
- package/src/claude/skills/{hotfix → fix}/references/review-cycle.md +1 -1
- package/src/claude/skills/git/SKILL.md +48 -9
- package/src/claude/skills/git/references/commit-protocols.md +35 -16
- package/src/claude/skills/git/references/worktree-blueprint.md +42 -6
- package/src/claude/skills/orca/SKILL.md +9 -8
- package/src/claude/skills/research/SKILL.md +2 -0
- package/src/claude/skills/scout/SKILL.md +61 -0
- package/src/claude/skills/{inspect → scout}/references/internal-inspection.md +26 -10
- package/src/claude/skills/specs/SKILL.md +22 -29
- package/src/claude/skills/specs/references/review.md +8 -6
- package/src/claude/skills/specs/references/templates.md +34 -41
- package/src/claude/skills/sync/SKILL.md +8 -15
- package/src/claude/skills/sync/references/rebind-and-audit.md +59 -0
- package/src/claude/skills/sync/references/sync-protocols.md +4 -13
- package/src/claude/skills/test/SKILL.md +22 -3
- package/src/claude/skills/test/references/execution-strategy.md +83 -0
- package/src/claude/skills/test/references/failure-triage.md +1 -1
- package/src/claude/skills/ui-ux/LICENSE +21 -0
- package/src/claude/skills/ui-ux/SKILL.md +497 -0
- package/src/claude/skills/ui-ux/references/brand-tokens.md +90 -0
- package/src/claude/skills/ui-ux/references/budgets.md +113 -0
- package/src/claude/skills/ui-ux/references/checklist.md +271 -0
- package/src/claude/skills/ui-ux/references/components/accordion.md +246 -0
- package/src/claude/skills/ui-ux/references/components/avatar.md +153 -0
- package/src/claude/skills/ui-ux/references/components/banner.md +43 -0
- package/src/claude/skills/ui-ux/references/components/breadcrumb.md +201 -0
- package/src/claude/skills/ui-ux/references/components/button.md +142 -0
- package/src/claude/skills/ui-ux/references/components/card.md +113 -0
- package/src/claude/skills/ui-ux/references/components/charts.md +376 -0
- package/src/claude/skills/ui-ux/references/components/chat.md +132 -0
- package/src/claude/skills/ui-ux/references/components/choice-controls.md +369 -0
- package/src/claude/skills/ui-ux/references/components/comment-thread.md +17 -0
- package/src/claude/skills/ui-ux/references/components/description-list.md +116 -0
- package/src/claude/skills/ui-ux/references/components/empty-state.md +98 -0
- package/src/claude/skills/ui-ux/references/components/file-upload.md +107 -0
- package/src/claude/skills/ui-ux/references/components/inline-edit.md +128 -0
- package/src/claude/skills/ui-ux/references/components/input.md +161 -0
- package/src/claude/skills/ui-ux/references/components/list-row.md +56 -0
- package/src/claude/skills/ui-ux/references/components/loading.md +198 -0
- package/src/claude/skills/ui-ux/references/components/logo.md +147 -0
- package/src/claude/skills/ui-ux/references/components/otp-input.md +43 -0
- package/src/claude/skills/ui-ux/references/components/quantity-input.md +100 -0
- package/src/claude/skills/ui-ux/references/components/range-slider.md +16 -0
- package/src/claude/skills/ui-ux/references/components/small-controls.md +340 -0
- package/src/claude/skills/ui-ux/references/components/sortable-header.md +86 -0
- package/src/claude/skills/ui-ux/references/components/tag-input.md +14 -0
- package/src/claude/skills/ui-ux/references/components/timeline.md +38 -0
- package/src/claude/skills/ui-ux/references/components/tree.md +30 -0
- package/src/claude/skills/ui-ux/references/design-process.md +650 -0
- package/src/claude/skills/ui-ux/references/layouts/app-kanban.html +303 -0
- package/src/claude/skills/ui-ux/references/layouts/app.md +1369 -0
- package/src/claude/skills/ui-ux/references/layouts/form.md +430 -0
- package/src/claude/skills/ui-ux/references/layouts/overlay.md +552 -0
- package/src/claude/skills/ui-ux/references/layouts/pricing.md +204 -0
- package/src/claude/skills/ui-ux/references/locked-rules.md +35 -0
- package/src/claude/skills/ui-ux/references/principles.md +408 -0
- package/src/claude/skills/ui-ux/references/refactor.md +245 -0
- package/src/claude/skills/ui-ux/references/responsive.md +195 -0
- package/src/claude/skills/ui-ux/references/review.md +528 -0
- package/src/claude/skills/ui-ux/references/rules-color.md +623 -0
- package/src/claude/skills/ui-ux/references/rules-form.md +235 -0
- package/src/claude/skills/ui-ux/references/rules-state.md +680 -0
- package/src/claude/skills/ui-ux/references/rules-type.md +369 -0
- package/src/claude/skills/ui-ux/references/styles.md +311 -0
- package/src/claude/skills/ui-ux/references/system.md +219 -0
- package/src/claude/skills/ui-ux/references/tailwind-v4-traps.md +237 -0
- package/src/claude/skills/ui-ux/references/tokens.css +362 -0
- package/src/claude/skills/ui-ux/scripts/lint-skill.mjs +216 -0
- package/src/claude/skills/ui-ux/scripts/package.json +9 -0
- package/src/claude/skills/ui-ux/scripts/probe.mjs +4447 -0
- package/src/claude/skills/ui-ux-pro-max/SKILL.md +1 -1
- package/src/claude/status.cjs +149 -144
- package/src/codex/AGENTS.md +8 -13
- package/src/codex/hooks/agent.cjs +2 -1
- package/src/codex/hooks/lib/spec-receipt.cjs +2 -2
- package/src/codex/hooks/lib/spec-utils.cjs +0 -7
- package/src/codex/hooks/rules.cjs +2 -1
- package/src/codex/hooks/session.cjs +0 -2
- package/src/codex/hooks/spec-gate.cjs +7 -18
- package/src/codex/hooks.json +0 -20
- package/src/codex/rules/state-sync.md +3 -12
- package/src/common/AGENTS.md +2 -2
- package/src/omp/extensions/cafekit-bridge.mjs +6 -6
- package/src/claude/hooks/__tests__/completion-authority.test.js +0 -990
- package/src/claude/hooks/__tests__/semantic-review-authority.test.js +0 -259
- package/src/claude/hooks/completion-authority-check.cjs +0 -56
- package/src/claude/hooks/completion-authority-state.cjs +0 -527
- package/src/claude/hooks/completion-authority.cjs +0 -163
- package/src/claude/hooks/semantic-review-authority.cjs +0 -154
- package/src/claude/hooks/task-scaffold-guard.cjs +0 -114
- package/src/claude/hooks/usage.cjs +0 -197
- package/src/claude/skills/delegate/SKILL.md +0 -124
- package/src/claude/skills/delegate/references/codex-delegation.md +0 -71
- package/src/claude/skills/delegate/references/grok-delegation.md +0 -84
- package/src/claude/skills/hotfix/SKILL.md +0 -289
- package/src/claude/skills/inspect/SKILL.md +0 -142
- package/src/claude/skills/loop/SKILL.md +0 -114
- package/src/claude/skills/loop/references/bounded-loop-protocol.md +0 -104
- package/src/claude/skills/loop/references/metric-and-guard-contract.md +0 -76
- package/src/claude/skills/route/SKILL.md +0 -94
- package/src/claude/skills/route/references/agent-timing.md +0 -46
- package/src/claude/skills/route/references/chaining-patterns.md +0 -53
- package/src/claude/skills/route/references/task-taxonomy.md +0 -48
- package/src/codex/hooks/completion-authority-check.cjs +0 -67
- package/src/codex/hooks/completion-authority-state.cjs +0 -527
- package/src/codex/hooks/completion-authority.cjs +0 -109
- package/src/codex/hooks/semantic-review-authority.cjs +0 -130
- package/src/codex/hooks/task-scaffold-guard.cjs +0 -63
- /package/src/claude/skills/{question → ask}/templates/question.md +0 -0
- /package/src/claude/skills/{hotfix → fix}/references/escalation-tactics.md +0 -0
- /package/src/claude/skills/{hotfix → fix}/references/workflow-specialized.md +0 -0
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const fs = require('fs');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
const { runtimeDirName, runtimeDir, runtimePath } = require('./lib/runtime-dir.cjs');
|
|
7
|
-
const { spawnSync } = require('child_process');
|
|
8
|
-
const STATE = require('./completion-authority-state.cjs');
|
|
9
|
-
const PATH_SAFETY = require('./lib/runtime-path-safety.cjs');
|
|
10
|
-
|
|
11
|
-
const SCHEMA_VERSION = 1;
|
|
12
|
-
const MARKER = 'CAFEKIT_SEMANTIC_REVIEW_ATTESTATION ';
|
|
13
|
-
const SHA256_RE = /^sha256:[a-f0-9]{64}$/;
|
|
14
|
-
const REVIEWER_ROLE_ALLOWLIST = new Set(['code-auditor', 'code_auditor']);
|
|
15
|
-
const RECORD_FIELDS = ['schema_version', 'kind', 'identity', 'semantic_digest', 'event', 'verdict', 'host_session_id', 'reviewer_agent_id', 'reviewer_agent_type', 'issued_at', 'mac'];
|
|
16
|
-
|
|
17
|
-
function plain(value) {
|
|
18
|
-
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function exactKeys(value, fields) {
|
|
22
|
-
return plain(value) && JSON.stringify(Object.keys(value).sort()) === JSON.stringify([...fields].sort());
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function inside(root, target) {
|
|
26
|
-
const relative = path.relative(root, target);
|
|
27
|
-
return relative !== '' && relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function projectRoot(payload = {}) {
|
|
31
|
-
const installedRoot = path.resolve(__dirname, '..', '..');
|
|
32
|
-
if (fs.existsSync(runtimePath(installedRoot, 'hooks', path.basename(__filename)))) {
|
|
33
|
-
try { return STATE.canonicalProjectRoot(installedRoot); } catch {}
|
|
34
|
-
}
|
|
35
|
-
for (const candidate of [process.env.CLAUDE_PROJECT_DIR, process.env.PROJECT_ROOT]) {
|
|
36
|
-
if (typeof candidate === 'string' && candidate.trim()) {
|
|
37
|
-
try { return STATE.canonicalProjectRoot(candidate); } catch { /* continue */ }
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
const cwd = typeof payload.cwd === 'string' && payload.cwd.trim() ? payload.cwd : process.cwd();
|
|
41
|
-
return STATE.canonicalProjectRoot(cwd);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function identityFor(root, specFile, featureName) {
|
|
45
|
-
if (typeof featureName !== 'string' || !featureName.trim() || featureName.includes('/') || featureName.includes('\\') || featureName === '.' || featureName === '..' || featureName.includes('..')) throw new Error('feature identity is malformed');
|
|
46
|
-
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(featureName)) throw new Error('feature identity is malformed');
|
|
47
|
-
const canonicalRoot = STATE.canonicalProjectRoot(root);
|
|
48
|
-
const canonicalSpec = fs.realpathSync(path.resolve(specFile));
|
|
49
|
-
if (!inside(canonicalRoot, canonicalSpec) || path.basename(canonicalSpec) !== 'spec.json') throw new Error('spec identity escapes the project');
|
|
50
|
-
const relative = path.relative(canonicalRoot, canonicalSpec);
|
|
51
|
-
const expected = path.join('specs', featureName, 'spec.json');
|
|
52
|
-
if (relative !== expected) throw new Error(`spec identity must be ${expected}, not ${relative}`);
|
|
53
|
-
const spec = JSON.parse(fs.readFileSync(canonicalSpec, 'utf8'));
|
|
54
|
-
if (spec.feature_name !== featureName) throw new Error('feature identity does not match spec.json');
|
|
55
|
-
return { project_root: canonicalRoot, spec_file: canonicalSpec, feature_name: featureName };
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function recordPath(root, identity) {
|
|
59
|
-
const name = STATE.digest({ namespace: 'cafekit-semantic-review-observation-v1', identity });
|
|
60
|
-
return path.join(STATE.stateDir(root), 'semantic-reviews', `observation-${name}.json`);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function parseClaim(message) {
|
|
64
|
-
const lines = String(message || '').split(/\r?\n/).filter((line) => line.startsWith(MARKER));
|
|
65
|
-
if (lines.length === 0) return null;
|
|
66
|
-
if (lines.length !== 1) throw new Error('exactly one semantic review attestation marker is required');
|
|
67
|
-
const claim = JSON.parse(lines[0].slice(MARKER.length));
|
|
68
|
-
if (!exactKeys(claim, ['feature_name', 'spec_file', 'semantic_digest', 'verdict'])) throw new Error('semantic review claim fields are malformed');
|
|
69
|
-
if (claim.verdict !== 'PASS' || !SHA256_RE.test(claim.semantic_digest || '')) throw new Error('semantic review claim must contain literal PASS and a sha256 digest');
|
|
70
|
-
return claim;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function validatorFor(root) {
|
|
74
|
-
const installedRoot = runtimeDir(root);
|
|
75
|
-
const installedHook = path.join(installedRoot, 'hooks', path.basename(__filename));
|
|
76
|
-
if (fs.existsSync(installedHook)) {
|
|
77
|
-
const canonicalHook = PATH_SAFETY.canonicalRegularFile(root, installedHook, 'installed semantic hook');
|
|
78
|
-
if (canonicalHook !== fs.realpathSync(__filename)) throw new Error('installed semantic hook identity is invalid');
|
|
79
|
-
const validator = path.join(installedRoot, 'scripts', 'validate-spec-output.cjs');
|
|
80
|
-
PATH_SAFETY.verifyCommonJsClosure(installedRoot, validator);
|
|
81
|
-
return PATH_SAFETY.canonicalRegularFile(installedRoot, validator, 'semantic validator');
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const sourceRuntime = path.resolve(__dirname, '..');
|
|
85
|
-
const sourceRoot = path.resolve(sourceRuntime, '..');
|
|
86
|
-
if (`.${path.basename(sourceRuntime)}` !== runtimeDirName() || path.basename(sourceRoot) !== 'src') {
|
|
87
|
-
throw new Error('semantic validator runtime root is not recognized');
|
|
88
|
-
}
|
|
89
|
-
const validator = path.join(sourceRuntime, 'scripts', 'validate-spec-output.cjs');
|
|
90
|
-
PATH_SAFETY.verifyCommonJsClosure(sourceRoot, validator);
|
|
91
|
-
return PATH_SAFETY.canonicalRegularFile(sourceRoot, validator, 'semantic validator');
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function currentDigest(root, specFile) {
|
|
95
|
-
const validator = validatorFor(root);
|
|
96
|
-
const result = spawnSync(process.execPath, [validator, path.dirname(specFile), '--semantic-digest'], { cwd: root, encoding: 'utf8' });
|
|
97
|
-
const digest = result.stdout.trim();
|
|
98
|
-
if (result.status !== 0 || !SHA256_RE.test(digest)) throw new Error(`semantic digest could not be recomputed (${result.stderr.trim() || `exit ${result.status}`})`);
|
|
99
|
-
return digest;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function recordFromSubagentStop(payload) {
|
|
103
|
-
// Honest-agent guardrail: the host hook observes this SubagentStop payload.
|
|
104
|
-
// The worker-authored claim is not authenticated input or a security boundary.
|
|
105
|
-
if (!plain(payload) || payload.hook_event_name !== 'SubagentStop') return { recorded: false, reason: 'event' };
|
|
106
|
-
for (const field of ['session_id', 'agent_id', 'agent_type', 'last_assistant_message']) {
|
|
107
|
-
if (typeof payload[field] !== 'string' || !payload[field].trim()) return { recorded: false, reason: `missing-${field}` };
|
|
108
|
-
}
|
|
109
|
-
if (!REVIEWER_ROLE_ALLOWLIST.has(payload.agent_type)) return { recorded: false, reason: 'reviewer-role' };
|
|
110
|
-
const root = projectRoot(payload);
|
|
111
|
-
const claim = parseClaim(payload.last_assistant_message);
|
|
112
|
-
if (claim === null) return { recorded: false, reason: 'no-claim' };
|
|
113
|
-
const requestedSpec = path.resolve(root, claim.spec_file);
|
|
114
|
-
const identity = identityFor(root, requestedSpec, claim.feature_name);
|
|
115
|
-
const digest = currentDigest(root, identity.spec_file);
|
|
116
|
-
if (digest !== claim.semantic_digest) throw new Error('claimed semantic digest does not match current artifacts');
|
|
117
|
-
const key = STATE.ensureKey();
|
|
118
|
-
const record = {
|
|
119
|
-
schema_version: SCHEMA_VERSION, kind: 'semantic-review-observation', identity,
|
|
120
|
-
semantic_digest: digest, event: 'SubagentStop', verdict: 'PASS',
|
|
121
|
-
host_session_id: payload.session_id, reviewer_agent_id: payload.agent_id,
|
|
122
|
-
reviewer_agent_type: payload.agent_type, issued_at: new Date().toISOString(),
|
|
123
|
-
};
|
|
124
|
-
record.mac = STATE.recordMac(record, key);
|
|
125
|
-
STATE.atomicWrite(recordPath(root, identity), record);
|
|
126
|
-
return { recorded: true, record };
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function verifyAttestation(root, specFile, featureName, semanticDigest) {
|
|
130
|
-
let identity;
|
|
131
|
-
try { identity = identityFor(root, specFile, featureName); } catch (error) { return { ok: false, reason: error.message }; }
|
|
132
|
-
const file = recordPath(root, identity);
|
|
133
|
-
let record;
|
|
134
|
-
try { record = JSON.parse(fs.readFileSync(file, 'utf8')); } catch (error) { return { ok: false, reason: error.code === 'ENOENT' ? 'matching host-hook observation is missing' : 'host-hook observation is unreadable' }; }
|
|
135
|
-
const key = STATE.readKey();
|
|
136
|
-
if (key.missing) return { ok: false, reason: 'completion authority key is missing' };
|
|
137
|
-
if (!exactKeys(record, RECORD_FIELDS) || !STATE.macMatches(record, key.key)) return { ok: false, reason: 'host-hook observation is malformed or its MAC is invalid' };
|
|
138
|
-
if (record.schema_version !== SCHEMA_VERSION || record.kind !== 'semantic-review-observation' || record.event !== 'SubagentStop' || record.verdict !== 'PASS') return { ok: false, reason: 'host-hook observation event or verdict is invalid' };
|
|
139
|
-
if (STATE.stableStringify(record.identity) !== STATE.stableStringify(identity)) return { ok: false, reason: 'host-hook observation project, feature, or spec identity is invalid' };
|
|
140
|
-
if (record.semantic_digest !== semanticDigest) return { ok: false, reason: 'host-hook observation semantic digest is stale' };
|
|
141
|
-
return { ok: true, record };
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (require.main === module) {
|
|
145
|
-
try {
|
|
146
|
-
const raw = fs.readFileSync(0, 'utf8').trim();
|
|
147
|
-
const { normalizeHookPayload } = require('./lib/hook-payload.cjs');
|
|
148
|
-
if (raw) recordFromSubagentStop(normalizeHookPayload(JSON.parse(raw)));
|
|
149
|
-
} catch (error) {
|
|
150
|
-
process.stderr.write(`CafeKit semantic review SubagentStop claim rejected: ${error.message}\n`);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
module.exports = { MARKER, REVIEWER_ROLE_ALLOWLIST, recordFromSubagentStop, verifyAttestation, identityFor, recordPath };
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Copyright (c) 2026 Haposoft. MIT License.
|
|
4
|
-
*
|
|
5
|
-
* PreToolUse Hook — task-scaffold-guard.cjs
|
|
6
|
-
* Implements: https://docs.anthropic.com/en/docs/claude-code/hooks
|
|
7
|
-
*
|
|
8
|
-
* Forces spec task files to be GENERATED via spec-scaffold.cjs instead of
|
|
9
|
-
* hand-written. Field tests showed the model dodges the (opt-in) scaffold step
|
|
10
|
-
* and raw-`Write`s every task file — the single biggest output-token cost of a
|
|
11
|
-
* spec run. This hook blocks `Write` to `specs/<feature>/tasks/task-*.md`, so
|
|
12
|
-
* the only path to a task file is: scaffold (creates the stub) -> Edit (fills it).
|
|
13
|
-
*
|
|
14
|
-
* Scope is deliberately narrow — it only ever blocks the `Write` tool on a task
|
|
15
|
-
* file path. `Edit`/`MultiEdit` (filling a stub), and `Write` to any other file
|
|
16
|
-
* (requirements.md, design.md, source code) are never touched. The scaffold
|
|
17
|
-
* script itself writes via Node fs through a Bash call, not the `Write` tool, so
|
|
18
|
-
* it is never blocked.
|
|
19
|
-
*
|
|
20
|
-
* Safety valves:
|
|
21
|
-
* 1. fail-open when spec-scaffold.cjs is absent — you cannot force a tool that
|
|
22
|
-
* is not installed, so a hook shipped without its script must not deadlock.
|
|
23
|
-
* 2. actionable block message — prints the exact scaffold command to run.
|
|
24
|
-
* 3. escape hatch — "spec": { "scaffold_guard": false } in <runtime>/runtime.json.
|
|
25
|
-
*
|
|
26
|
-
* Exit: 0 = allow, 2 = block.
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
try {
|
|
30
|
-
const fs = require('fs');
|
|
31
|
-
const path = require('path');
|
|
32
|
-
const { runtimeDirName, runtimeDir, runtimePath } = require('./lib/runtime-dir.cjs');
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Deny the call with a reason every host can read.
|
|
36
|
-
*
|
|
37
|
-
* The reason is the JSON `permissionDecisionReason`, which Claude Code reads natively and
|
|
38
|
-
* grok honours "regardless of exit code"; the same text also goes to stderr, because on
|
|
39
|
-
* an exit-2 denial that is the channel Claude Code feeds back, and grok reads only the
|
|
40
|
-
* first stderr line. Exit 2 stays so a host that reads neither still blocks.
|
|
41
|
-
*/
|
|
42
|
-
function denyWithReason(reason) {
|
|
43
|
-
process.stdout.write(JSON.stringify({
|
|
44
|
-
hookSpecificOutput: {
|
|
45
|
-
hookEventName: 'PreToolUse',
|
|
46
|
-
permissionDecision: 'deny',
|
|
47
|
-
permissionDecisionReason: reason
|
|
48
|
-
}
|
|
49
|
-
}) + '\n');
|
|
50
|
-
process.stderr.write(reason + '\n');
|
|
51
|
-
process.exit(2);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const stdin = fs.readFileSync(0, 'utf8').trim();
|
|
55
|
-
if (!stdin) process.exit(0);
|
|
56
|
-
|
|
57
|
-
const { normalizeHookPayload } = require('./lib/hook-payload.cjs');
|
|
58
|
-
const data = normalizeHookPayload(JSON.parse(stdin));
|
|
59
|
-
const toolName = data.tool_name || '';
|
|
60
|
-
const toolInput = data.tool_input || {};
|
|
61
|
-
const cwd = data.cwd || process.cwd();
|
|
62
|
-
|
|
63
|
-
// Only `Write` can create a file from scratch. Edit/MultiEdit require an
|
|
64
|
-
// existing file, so filling a scaffolded stub is always allowed.
|
|
65
|
-
if (toolName !== 'Write') process.exit(0);
|
|
66
|
-
|
|
67
|
-
const filePath = toolInput.file_path || toolInput.path || '';
|
|
68
|
-
if (!filePath) process.exit(0);
|
|
69
|
-
|
|
70
|
-
// Match a spec task file: .../specs/<feature>/tasks/task-<...>.md
|
|
71
|
-
const norm = filePath.replace(/\\/g, '/');
|
|
72
|
-
const isTaskFile = /(^|\/)specs\/[^/]+\/tasks\/task-[^/]+\.md$/.test(norm);
|
|
73
|
-
if (!isTaskFile) process.exit(0);
|
|
74
|
-
|
|
75
|
-
// Valve 3: explicit escape hatch via <runtime>/runtime.json (fail-closed: a
|
|
76
|
-
// missing/broken runtime.json keeps the guard ON).
|
|
77
|
-
let runtime = {};
|
|
78
|
-
try {
|
|
79
|
-
const rp = runtimePath(cwd, 'runtime.json');
|
|
80
|
-
if (fs.existsSync(rp)) runtime = JSON.parse(fs.readFileSync(rp, 'utf8'));
|
|
81
|
-
} catch { /* malformed runtime.json -> keep guard on */ }
|
|
82
|
-
if (runtime.spec && runtime.spec.scaffold_guard === false) process.exit(0);
|
|
83
|
-
|
|
84
|
-
// Valve 1: fail-open if the scaffold script is not installed next to this hook
|
|
85
|
-
// (hook lives in <runtime>/hooks/, script in <runtime>/scripts/). Forcing a tool
|
|
86
|
-
// that does not exist would deadlock task creation.
|
|
87
|
-
const scaffold = path.join(__dirname, '..', 'scripts', 'spec-scaffold.cjs');
|
|
88
|
-
if (!fs.existsSync(scaffold)) process.exit(0);
|
|
89
|
-
|
|
90
|
-
// Valve 2: block with an actionable message carrying the exact command.
|
|
91
|
-
// NOTE: deliberately does NOT mention the runtime.json escape hatch — an
|
|
92
|
-
// Smoke testing proved the model reads the advertised override and
|
|
93
|
-
// simply disables the guard itself. The hatch stays functional for humans.
|
|
94
|
-
const m = norm.match(/(^|\/)specs\/([^/]+)\/tasks\//);
|
|
95
|
-
const feature = m ? m[2] : '<feature>';
|
|
96
|
-
denyWithReason(
|
|
97
|
-
`TASK SCAFFOLD REQUIRED: task files must be generated, not hand-written.\n` +
|
|
98
|
-
`Blocked Write: ${filePath}\n\n` +
|
|
99
|
-
`Generate the stub(s), then Edit-fill the {{...}} placeholders:\n` +
|
|
100
|
-
` node ${runtimeDirName()}/scripts/spec-scaffold.cjs ${feature} --tasks "R0-01-slug,R1-01-slug,..." --tasks-only\n` +
|
|
101
|
-
`Then use Edit (not Write) on each tasks/task-*.md stub.`
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
} catch (e) {
|
|
105
|
-
// Never let a hook crash block the user — log and fail-open.
|
|
106
|
-
try {
|
|
107
|
-
const fs = require('fs'), p = require('path');
|
|
108
|
-
const d = require('./lib/hook-state-dir.cjs').hookStateDir();
|
|
109
|
-
if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true });
|
|
110
|
-
fs.appendFileSync(p.join(d, 'hook-log.jsonl'),
|
|
111
|
-
JSON.stringify({ ts: new Date().toISOString(), hook: 'task-scaffold-guard', status: 'crash', error: e.message }) + '\n');
|
|
112
|
-
} catch (_) {}
|
|
113
|
-
process.exit(0);
|
|
114
|
-
}
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Copyright (c) 2026 Haposoft. MIT License.
|
|
4
|
-
*
|
|
5
|
-
* UserPromptSubmit & PostToolUse Hook — usage.cjs
|
|
6
|
-
* Implements: https://docs.anthropic.com/en/docs/claude-code/hooks
|
|
7
|
-
*
|
|
8
|
-
* Fetches Claude Code usage limits from Anthropic OAuth API and writes to cache.
|
|
9
|
-
* Cache is read by status.cjs for statusline display.
|
|
10
|
-
*
|
|
11
|
-
* ⚠ EXPERIMENTAL / UNDOCUMENTED API: api.anthropic.com/api/oauth/usage with the
|
|
12
|
-
* "oauth-2025-04-20" beta header is not a public contract and may break or
|
|
13
|
-
* change shape without notice. Every failure path degrades to
|
|
14
|
-
* status:"unavailable" (statusline hides the segment) — never block on it.
|
|
15
|
-
* Disable entirely with "usage": { "enabled": false } if it misbehaves.
|
|
16
|
-
*
|
|
17
|
-
* Features:
|
|
18
|
-
* - Cross-platform credential retrieval (macOS Keychain, file-based)
|
|
19
|
-
* - Throttled API calls: 1 min (prompt) / 5 min (tool use)
|
|
20
|
-
* - Disable: set "usage": { "enabled": false } in <runtime>/runtime.json
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
// Crash wrapper
|
|
24
|
-
try {
|
|
25
|
-
const fs = require("fs");
|
|
26
|
-
const path = require("path");
|
|
27
|
-
const os = require("os");
|
|
28
|
-
const { execSync } = require("child_process");
|
|
29
|
-
|
|
30
|
-
const { runtimeDirName, runtimeDir, runtimePath } = require('./lib/runtime-dir.cjs');
|
|
31
|
-
function readRuntime(cwd) {
|
|
32
|
-
try {
|
|
33
|
-
const rp = runtimePath(cwd, 'runtime.json');
|
|
34
|
-
if (fs.existsSync(rp)) {
|
|
35
|
-
return JSON.parse(fs.readFileSync(rp, 'utf-8'));
|
|
36
|
-
}
|
|
37
|
-
} catch { /* fail-open */ }
|
|
38
|
-
return {};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Cache configuration
|
|
42
|
-
const USAGE_CACHE_FILE = path.join(os.tmpdir(), "ck-usage-limits-cache.json");
|
|
43
|
-
const CACHE_TTL_MS = 60000; // 60 seconds
|
|
44
|
-
const FETCH_INTERVAL_MS = 300000; // 5 minutes for PostToolUse
|
|
45
|
-
const FETCH_INTERVAL_PROMPT_MS = 60000; // 1 minute for UserPromptSubmit
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Get Claude OAuth credentials (cross-platform)
|
|
49
|
-
*/
|
|
50
|
-
function getClaudeCredentials() {
|
|
51
|
-
// macOS: Try Keychain first
|
|
52
|
-
if (os.platform() === "darwin") {
|
|
53
|
-
try {
|
|
54
|
-
const result = execSync('security find-generic-password -s "Claude Code-credentials" -w', {
|
|
55
|
-
timeout: 5000,
|
|
56
|
-
encoding: "utf-8",
|
|
57
|
-
stdio: ["pipe", "pipe", "ignore"],
|
|
58
|
-
}).trim();
|
|
59
|
-
const parsed = JSON.parse(result);
|
|
60
|
-
if (parsed.claudeAiOauth?.accessToken) {
|
|
61
|
-
return parsed.claudeAiOauth.accessToken;
|
|
62
|
-
}
|
|
63
|
-
} catch {
|
|
64
|
-
// Fallback to file-based
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// File-based credentials (Linux/Windows, or macOS fallback)
|
|
69
|
-
const credPath = path.join(os.homedir(), ".claude", ".credentials.json");
|
|
70
|
-
try {
|
|
71
|
-
const content = fs.readFileSync(credPath, "utf-8");
|
|
72
|
-
const parsed = JSON.parse(content);
|
|
73
|
-
return parsed.claudeAiOauth?.accessToken || null;
|
|
74
|
-
} catch {
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Check if we should fetch (throttled)
|
|
81
|
-
*/
|
|
82
|
-
function shouldFetch(isUserPrompt = false) {
|
|
83
|
-
const interval = isUserPrompt ? FETCH_INTERVAL_PROMPT_MS : FETCH_INTERVAL_MS;
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
if (fs.existsSync(USAGE_CACHE_FILE)) {
|
|
87
|
-
const cache = JSON.parse(fs.readFileSync(USAGE_CACHE_FILE, "utf-8"));
|
|
88
|
-
if (Date.now() - cache.timestamp < interval) {
|
|
89
|
-
return false;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
} catch {}
|
|
93
|
-
return true;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Write cache with status (available or unavailable)
|
|
98
|
-
*/
|
|
99
|
-
function writeCache(status, data = null) {
|
|
100
|
-
fs.writeFileSync(
|
|
101
|
-
USAGE_CACHE_FILE,
|
|
102
|
-
JSON.stringify({
|
|
103
|
-
timestamp: Date.now(),
|
|
104
|
-
status,
|
|
105
|
-
data,
|
|
106
|
-
})
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Fetch usage limits from Anthropic OAuth API and write to cache
|
|
112
|
-
* Always writes status to cache (available or unavailable) for statusline fallback
|
|
113
|
-
*/
|
|
114
|
-
async function fetchAndCacheUsageLimits() {
|
|
115
|
-
const token = getClaudeCredentials();
|
|
116
|
-
if (!token) {
|
|
117
|
-
writeCache("unavailable");
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
try {
|
|
122
|
-
const response = await fetch("https://api.anthropic.com/api/oauth/usage", {
|
|
123
|
-
method: "GET",
|
|
124
|
-
headers: {
|
|
125
|
-
Accept: "application/json",
|
|
126
|
-
"Content-Type": "application/json",
|
|
127
|
-
Authorization: `Bearer ${token}`,
|
|
128
|
-
"anthropic-beta": "oauth-2025-04-20",
|
|
129
|
-
"User-Agent": "cafekit/1.0",
|
|
130
|
-
},
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
if (!response.ok) {
|
|
134
|
-
writeCache("unavailable");
|
|
135
|
-
return false;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const data = await response.json();
|
|
139
|
-
writeCache("available", data);
|
|
140
|
-
return true;
|
|
141
|
-
} catch {
|
|
142
|
-
writeCache("unavailable");
|
|
143
|
-
return false;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Main hook execution - just fetch and cache, no injection
|
|
149
|
-
*/
|
|
150
|
-
async function main() {
|
|
151
|
-
// Always allow operation to continue
|
|
152
|
-
const result = { continue: true };
|
|
153
|
-
|
|
154
|
-
try {
|
|
155
|
-
// Read hook input
|
|
156
|
-
let inputStr = "";
|
|
157
|
-
try {
|
|
158
|
-
inputStr = fs.readFileSync(0, "utf-8");
|
|
159
|
-
} catch {}
|
|
160
|
-
|
|
161
|
-
const input = JSON.parse(inputStr || "{}");
|
|
162
|
-
const cwd = input.cwd || process.cwd();
|
|
163
|
-
const runtime = readRuntime(cwd);
|
|
164
|
-
if (runtime.usage?.enabled === false) {
|
|
165
|
-
console.log(JSON.stringify(result));
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Detect hook type
|
|
170
|
-
const isUserPrompt = typeof input.prompt === "string";
|
|
171
|
-
|
|
172
|
-
// Check if we should fetch (throttled)
|
|
173
|
-
if (shouldFetch(isUserPrompt)) {
|
|
174
|
-
await fetchAndCacheUsageLimits();
|
|
175
|
-
}
|
|
176
|
-
} catch {}
|
|
177
|
-
|
|
178
|
-
// Output result (no injection, just continue)
|
|
179
|
-
console.log(JSON.stringify(result));
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
main().catch(() => {
|
|
183
|
-
console.log(JSON.stringify({ continue: true }));
|
|
184
|
-
process.exit(0);
|
|
185
|
-
});
|
|
186
|
-
} catch (e) {
|
|
187
|
-
// Minimal crash logging (zero deps — only Node builtins)
|
|
188
|
-
try {
|
|
189
|
-
const fs = require('fs');
|
|
190
|
-
const p = require('path');
|
|
191
|
-
const logDir = require('./lib/hook-state-dir.cjs').hookStateDir();
|
|
192
|
-
if (!fs.existsSync(logDir)) fs.mkdirSync(logDir, { recursive: true });
|
|
193
|
-
fs.appendFileSync(p.join(logDir, 'hook-log.jsonl'),
|
|
194
|
-
JSON.stringify({ ts: new Date().toISOString(), hook: p.basename(__filename, '.cjs'), status: 'crash', error: e.message }) + '\n');
|
|
195
|
-
} catch (_) {}
|
|
196
|
-
process.exit(0); // fail-open
|
|
197
|
-
}
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: cf:delegate
|
|
3
|
-
description: "Delegate implementation tasks from Claude Code to external CLI agents (Codex or Grok). Covers task-brief authoring, non-interactive dispatch, permission setup, background monitoring, resume, and independent verification of the returned work."
|
|
4
|
-
user-invocable: true
|
|
5
|
-
when_to_use: "Invoke when the user asks to assign/offload a coding task to codex or grok, or to run work on an external agent CLI."
|
|
6
|
-
category: orchestration
|
|
7
|
-
keywords: [delegate, codex, grok, external-agent, offload, assign]
|
|
8
|
-
argument-hint: "<codex|grok> <task-description | path/to/task-brief.md>"
|
|
9
|
-
metadata:
|
|
10
|
-
author: haposoft
|
|
11
|
-
version: "1.0.0"
|
|
12
|
-
---
|
|
13
|
-
# Delegate to External Agents (Codex / Grok)
|
|
14
|
-
|
|
15
|
-
Dispatch a well-scoped implementation task to an external agentic CLI — **Codex** (OpenAI) or **Grok** (xAI Grok Build) — from a Claude Code session acting as orchestrator. Claude Code stays responsible for: writing the task brief, choosing safe permissions, monitoring, and **independently verifying** the result before reporting completion.
|
|
16
|
-
|
|
17
|
-
**Mantra:** Brief precisely, sandbox tightly, verify independently.
|
|
18
|
-
|
|
19
|
-
## When to Use
|
|
20
|
-
|
|
21
|
-
- User explicitly asks to hand a task to codex/grok ("giao cho codex", "assign to grok").
|
|
22
|
-
- Parallel workstreams: Claude Code keeps orchestrating while an external agent implements a scoped task.
|
|
23
|
-
- Cross-checking: a second model implements or reviews independently.
|
|
24
|
-
|
|
25
|
-
Do NOT delegate: tasks touching secrets/`.env`, deploys to shared environments, destructive migrations on live data, or tasks whose scope is still unclear (clarify first — a vague brief wastes an entire agent run).
|
|
26
|
-
|
|
27
|
-
## Workflow
|
|
28
|
-
|
|
29
|
-
### 1. Write the task brief (mandatory, file-based)
|
|
30
|
-
|
|
31
|
-
External agents run **fresh-context** — they see none of your conversation. Write the brief to a file (convention: `plans/<slug>-task.md` in the target repo), never inline-only. Follow the orchestrator prompt rules (`.claude/rules/orchestrator.md`): the brief must be self-contained.
|
|
32
|
-
|
|
33
|
-
Required sections:
|
|
34
|
-
|
|
35
|
-
```markdown
|
|
36
|
-
# Task: <title>
|
|
37
|
-
|
|
38
|
-
> Self-contained brief for a fresh-context agent. Date: <YYYY-MM-DD>.
|
|
39
|
-
> FOUNDATION: <state of the working tree the agent builds on — branch,
|
|
40
|
-
> uncommitted changes it must NOT revert, verified test/build status>
|
|
41
|
-
|
|
42
|
-
## Context — project, architecture, why this task exists
|
|
43
|
-
## Scope — numbered work items with exact file paths
|
|
44
|
-
## Constraints — what must NOT be touched (staging data, .env, deploys,
|
|
45
|
-
unrelated refactors); repo rules to obey (CLAUDE.md)
|
|
46
|
-
## Completion Criteria — verifiable, numbered
|
|
47
|
-
## Evidence required — exact commands whose output proves completion
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
Rules of thumb:
|
|
51
|
-
- Name exact files and line-level anchors where known; agents burn tokens rediscovering what you already know.
|
|
52
|
-
- State dependency order explicitly if this task builds on another agent's uncommitted work ("do NOT revert/stash existing changes").
|
|
53
|
-
- Put user-pending decisions in Constraints as forbidden actions (e.g. "do NOT touch staging rows until user confirms").
|
|
54
|
-
|
|
55
|
-
### 2. Protect the working tree
|
|
56
|
-
|
|
57
|
-
Before dispatch, snapshot state you cannot afford to lose:
|
|
58
|
-
|
|
59
|
-
```bash
|
|
60
|
-
git stash push -u -m "backup: <reason>" && git stash apply -q # snapshot, keep tree intact
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
Prefer isolation when the task is risky or parallel to other edits:
|
|
64
|
-
- Grok: `--worktree=<name>` (built-in).
|
|
65
|
-
- Codex: run with `-C <worktree-dir>` after creating a git worktree yourself.
|
|
66
|
-
|
|
67
|
-
If the agent must build on uncommitted changes, isolation is not possible — use the stash-backup pattern above and say so in the brief.
|
|
68
|
-
|
|
69
|
-
### 3. Dispatch
|
|
70
|
-
|
|
71
|
-
Load the matching reference for exact flags, permission model, and pitfalls:
|
|
72
|
-
- **Codex:** `./references/codex-delegation.md`
|
|
73
|
-
- **Grok:** `./references/grok-delegation.md`
|
|
74
|
-
|
|
75
|
-
Common principles:
|
|
76
|
-
- Run non-interactive/headless mode in a **background** shell; both CLIs stream progress to stdout.
|
|
77
|
-
- Grant the **minimum** permission set that lets the agent write code and run its Evidence commands (npm/node). Never grant full-access/bypass modes for routine tasks.
|
|
78
|
-
- Model/effort overrides: pass explicitly rather than relying on user config, so the run is reproducible.
|
|
79
|
-
- Capture the output file path; check progress after ~20–30s to confirm the agent actually started (auth errors, bad flags, and permission blocks surface early).
|
|
80
|
-
|
|
81
|
-
### 4. Monitor
|
|
82
|
-
|
|
83
|
-
- Poll the background output periodically; look for permission-block messages — both CLIs degrade to "design-only" output instead of failing loudly when a write tool is blocked.
|
|
84
|
-
- If blocked on permissions: **resume the same session** with corrected flags (both CLIs support resume; see references) — context is preserved, work continues instead of restarting.
|
|
85
|
-
- Apply the 3-strike rule from `.claude/rules/orchestrator.md`: same failure 3+ times → stop, escalate to user with evidence.
|
|
86
|
-
|
|
87
|
-
### 5. Verify independently (non-negotiable)
|
|
88
|
-
|
|
89
|
-
External-agent claims are **not** evidence (CLAUDE.md: no completion claim without fresh proof from the current run). After the agent reports done:
|
|
90
|
-
|
|
91
|
-
1. `git status` / `git diff` — confirm the changed file set matches the brief's Scope; flag out-of-scope edits.
|
|
92
|
-
2. Run the brief's Evidence commands yourself (tests, build). The agent's transcript of a test run does not count.
|
|
93
|
-
3. Check Completion Criteria one by one.
|
|
94
|
-
4. Report to user: what was delegated, what came back, verification results, and any deviations.
|
|
95
|
-
|
|
96
|
-
### 6. Sync
|
|
97
|
-
|
|
98
|
-
- Keep the brief file updated (checklist ticks / blockers) so it doubles as task state.
|
|
99
|
-
- If a spec (`specs/<slug>/`) governs the work, follow the Tollgate Protocol (`.claude/rules/state-sync.md`): update `spec.json` + task files only after verification passes.
|
|
100
|
-
- The user decides commit/merge/deploy — delegation ends at verified working-tree changes unless told otherwise.
|
|
101
|
-
|
|
102
|
-
## Choosing Codex vs Grok
|
|
103
|
-
|
|
104
|
-
Both are capable general coding agents; prefer the one the user names. If unspecified:
|
|
105
|
-
|
|
106
|
-
| Factor | Codex | Grok |
|
|
107
|
-
|---|---|---|
|
|
108
|
-
| Isolation | manual git worktree + `-C` | built-in `--worktree=<name>` |
|
|
109
|
-
| Structured output | `--output-schema` (JSON Schema) | `--json-schema` |
|
|
110
|
-
| Self-verification | ask in the brief | `--check` flag appends a verify loop |
|
|
111
|
-
| Resume | `codex exec resume <id>` | `grok --resume <id>` |
|
|
112
|
-
|
|
113
|
-
## Rules
|
|
114
|
-
|
|
115
|
-
- Never grant `--dangerously-bypass-approvals-and-sandbox` (codex) or `bypassPermissions` (grok) for routine delegation.
|
|
116
|
-
- Never let an external agent touch `.env`, secrets, staging/production data, or run deploys.
|
|
117
|
-
- Never report the agent's own success claim as completion — verify first.
|
|
118
|
-
- Never re-dispatch a blocked task unchanged (BLOCKED ≠ retry; fix the cause: permissions, context, or scope).
|
|
119
|
-
- One writer per file set: don't run two agents (or an agent + yourself) editing the same files concurrently.
|
|
120
|
-
|
|
121
|
-
## References
|
|
122
|
-
|
|
123
|
-
- `./references/codex-delegation.md` — Codex CLI: exec mode, sandbox/approval model, resume, pitfalls
|
|
124
|
-
- `./references/grok-delegation.md` — Grok CLI: headless mode, worktree, permission rules, resume, pitfalls
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
# Delegating to Codex CLI
|
|
2
|
-
|
|
3
|
-
Verified against codex-cli 0.144.4 (2026-07). Re-check `codex exec --help` if flags fail — the CLI evolves quickly.
|
|
4
|
-
|
|
5
|
-
## Preflight
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
which codex && codex --version # installed?
|
|
9
|
-
codex doctor # auth + runtime health (slow; run only when suspicious)
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
User-level defaults live in `~/.codex/config.toml` (`model`, `model_reasoning_effort`, agents, MCP servers). Overrides via `-m` and `-c key=value` beat config — pass them explicitly for reproducible runs.
|
|
13
|
-
|
|
14
|
-
## Non-interactive dispatch: `codex exec`
|
|
15
|
-
|
|
16
|
-
Reads the prompt from an argument, or from **stdin** when the argument is `-`. Prefer stdin + brief file:
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
codex exec \
|
|
20
|
-
-m gpt-5.6-terra \
|
|
21
|
-
-c model_reasoning_effort='"max"' \
|
|
22
|
-
--sandbox workspace-write \
|
|
23
|
-
-o /tmp/codex-last-message.txt \
|
|
24
|
-
- < plans/<slug>-task.md
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
Run it in a background shell and poll the output.
|
|
28
|
-
|
|
29
|
-
### Key flags
|
|
30
|
-
|
|
31
|
-
| Flag | Notes |
|
|
32
|
-
|---|---|
|
|
33
|
-
| `-m <model>` | model ID, e.g. `gpt-5.6-terra` (note spelling: *terra*) |
|
|
34
|
-
| `-c model_reasoning_effort='"max"'` | TOML-parsed value — keep the inner quotes |
|
|
35
|
-
| `--sandbox <mode>` | `read-only` \| `workspace-write` \| `danger-full-access` |
|
|
36
|
-
| `-o <file>` | write the agent's final message to a file — easiest completion check |
|
|
37
|
-
| `-C <dir>` | working root (use for delegating into a git worktree) |
|
|
38
|
-
| `--add-dir <dir>` | extra writable dirs alongside the workspace |
|
|
39
|
-
| `--output-schema <file>` | JSON Schema constraining the final response |
|
|
40
|
-
| `--json` | JSONL event stream on stdout (machine parsing) |
|
|
41
|
-
| `--ephemeral` | don't persist session files |
|
|
42
|
-
|
|
43
|
-
### Pitfalls
|
|
44
|
-
|
|
45
|
-
- **`-a` / `--ask-for-approval` is TUI-only.** `codex exec` rejects it (`error: unexpected argument '-a' found`). Exec mode is implicitly `approval: never` — sandbox mode is your only control there.
|
|
46
|
-
- **`workspace-write` limits writes, not reads**, to: workdir, `/tmp`, `$TMPDIR`. Network is available. Commands needing writes outside (global installs) will fail — keep Evidence commands repo-local.
|
|
47
|
-
- **Effort value is TOML**: `-c model_reasoning_effort='"max"'` (double-quoted inside single quotes). Bare `max` may parse as a literal string too, but the quoted form is unambiguous.
|
|
48
|
-
- Startup banner echoes `model:`, `reasoning effort:`, `sandbox:` — **verify these lines** ~20s after dispatch instead of assuming flags took effect.
|
|
49
|
-
- User config may define agents/hooks/MCP servers that activate automatically; `--ignore-user-config` gives a clean run if they interfere.
|
|
50
|
-
|
|
51
|
-
## Monitor & verify liveness
|
|
52
|
-
|
|
53
|
-
```bash
|
|
54
|
-
# ~20-30s after dispatch: banner shows model/sandbox; then agent narration follows
|
|
55
|
-
head -c 1200 <output-file>
|
|
56
|
-
tail -c 1500 <output-file> # progress
|
|
57
|
-
cat /tmp/codex-last-message.txt # exists → run finished; content = final report
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## Resume
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
codex exec resume --last "<follow-up>" # most recent session
|
|
64
|
-
codex exec resume <SESSION_ID> "<follow-up>" # by id (session id printed in banner)
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
Use resume (not a fresh dispatch) when: permission-blocked mid-run, follow-up fixes after your verification failed, or continuing a design into implementation.
|
|
68
|
-
|
|
69
|
-
## Review mode
|
|
70
|
-
|
|
71
|
-
`codex review` (or `codex exec review`) runs a non-interactive code review of the current repo — useful as an independent second-opinion pass on another agent's diff.
|