delimit-cli 3.13.3 → 3.14.1
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/bin/delimit-cli.js +304 -1
- package/bin/delimit-setup.js +10 -4
- package/gateway/ai/inbox_daemon.py +623 -0
- package/gateway/ai/ledger_manager.py +88 -19
- package/gateway/ai/notify.py +975 -0
- package/gateway/ai/server.py +3570 -426
- package/gateway/ai/social.py +504 -0
- package/gateway/ai/tool_metadata.py +201 -0
- package/lib/cross-model-hooks.js +173 -43
- package/package.json +1 -1
- package/scripts/crosspost_devto.py +304 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tool Metadata — Tier mappings for all Delimit MCP tools.
|
|
3
|
+
|
|
4
|
+
Each tool is assigned a visibility tier:
|
|
5
|
+
- "public": Always visible to all MCP clients.
|
|
6
|
+
- "ops_pack": Visible when DELIMIT_SHOW_OPS=1 (or SHOW_INTERNAL/SHOW_EXPERIMENTAL).
|
|
7
|
+
- "internal": Visible when DELIMIT_SHOW_INTERNAL=1 (or SHOW_EXPERIMENTAL).
|
|
8
|
+
- "experimental": Visible when DELIMIT_SHOW_EXPERIMENTAL=1.
|
|
9
|
+
|
|
10
|
+
Tier cascade: experimental > internal > ops_pack > public.
|
|
11
|
+
|
|
12
|
+
Reference: Consensus 118/119/120 — Tool Segmentation Architecture.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from typing import Dict, Literal
|
|
16
|
+
|
|
17
|
+
Tier = Literal["public", "ops_pack", "internal", "experimental"]
|
|
18
|
+
|
|
19
|
+
# ─────────────────────────────────────────────────────────────────────
|
|
20
|
+
# TOOL_TIERS: canonical tier assignment for every registered tool.
|
|
21
|
+
#
|
|
22
|
+
# ops_pack (~8): workspace automation tools (social, notify, design
|
|
23
|
+
# validation, screenshots, screen recording, docs gen)
|
|
24
|
+
#
|
|
25
|
+
# internal (~18): founder-only tools (content pipeline, design internals,
|
|
26
|
+
# storybook, data ops, social management, scaffolding)
|
|
27
|
+
#
|
|
28
|
+
# experimental: stubs and pass-throughs hidden behind LED-044
|
|
29
|
+
#
|
|
30
|
+
# public: everything else (the default)
|
|
31
|
+
# ─────────────────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
TOOL_TIERS: Dict[str, Tier] = {
|
|
34
|
+
# === Govern domain (all public) ===
|
|
35
|
+
"delimit_lint": "public",
|
|
36
|
+
"delimit_diff": "public",
|
|
37
|
+
"delimit_policy": "public",
|
|
38
|
+
"delimit_semver": "public",
|
|
39
|
+
"delimit_explain": "public",
|
|
40
|
+
"delimit_zero_spec": "public",
|
|
41
|
+
"delimit_init": "public",
|
|
42
|
+
"delimit_gov_health": "public",
|
|
43
|
+
"delimit_gov_status": "public",
|
|
44
|
+
"delimit_gov_policy": "public",
|
|
45
|
+
"delimit_gov_evaluate": "public",
|
|
46
|
+
"delimit_gov_new_task": "public",
|
|
47
|
+
"delimit_gov_run": "public",
|
|
48
|
+
"delimit_gov_verify": "public",
|
|
49
|
+
"delimit_ledger": "public",
|
|
50
|
+
"delimit_impact": "public",
|
|
51
|
+
|
|
52
|
+
# === Context domain (all public) ===
|
|
53
|
+
"delimit_memory_search": "public",
|
|
54
|
+
"delimit_memory_store": "public",
|
|
55
|
+
"delimit_memory_recent": "public",
|
|
56
|
+
"delimit_vault_search": "public",
|
|
57
|
+
"delimit_vault_health": "public",
|
|
58
|
+
"delimit_vault_snapshot": "public",
|
|
59
|
+
"delimit_context_init": "public",
|
|
60
|
+
"delimit_context_write": "public",
|
|
61
|
+
"delimit_context_read": "public",
|
|
62
|
+
"delimit_context_list": "public",
|
|
63
|
+
"delimit_context_snapshot": "public",
|
|
64
|
+
"delimit_context_branch": "public",
|
|
65
|
+
"delimit_secret_store": "public",
|
|
66
|
+
"delimit_secret_get": "public",
|
|
67
|
+
"delimit_secret_list": "public",
|
|
68
|
+
"delimit_secret_revoke": "public",
|
|
69
|
+
"delimit_secret_access_log": "public",
|
|
70
|
+
|
|
71
|
+
# === Ship domain (public + experimental) ===
|
|
72
|
+
"delimit_deploy_plan": "public",
|
|
73
|
+
"delimit_deploy_build": "public",
|
|
74
|
+
"delimit_deploy_publish": "public",
|
|
75
|
+
"delimit_deploy_verify": "experimental",
|
|
76
|
+
"delimit_deploy_rollback": "public",
|
|
77
|
+
"delimit_deploy_status": "public",
|
|
78
|
+
"delimit_deploy_site": "public",
|
|
79
|
+
"delimit_deploy_npm": "public",
|
|
80
|
+
"delimit_release_plan": "public",
|
|
81
|
+
"delimit_release_validate": "experimental",
|
|
82
|
+
"delimit_release_status": "public",
|
|
83
|
+
"delimit_release_rollback": "experimental",
|
|
84
|
+
"delimit_release_history": "experimental",
|
|
85
|
+
"delimit_release_sync": "public",
|
|
86
|
+
|
|
87
|
+
# === Observe domain (public + experimental) ===
|
|
88
|
+
"delimit_obs_metrics": "public",
|
|
89
|
+
"delimit_obs_logs": "public",
|
|
90
|
+
"delimit_obs_alerts": "experimental",
|
|
91
|
+
"delimit_obs_status": "public",
|
|
92
|
+
"delimit_cost_analyze": "public",
|
|
93
|
+
"delimit_cost_optimize": "public",
|
|
94
|
+
"delimit_cost_alert": "public",
|
|
95
|
+
"delimit_cost_controls": "public",
|
|
96
|
+
|
|
97
|
+
# === Security domain (all public) ===
|
|
98
|
+
"delimit_security_scan": "public",
|
|
99
|
+
"delimit_security_ingest": "public",
|
|
100
|
+
"delimit_security_deliberate": "public",
|
|
101
|
+
"delimit_security_audit": "public",
|
|
102
|
+
"delimit_evidence_collect": "public",
|
|
103
|
+
"delimit_evidence_verify": "public",
|
|
104
|
+
|
|
105
|
+
# === Intel domain (all public) ===
|
|
106
|
+
"delimit_intel_dataset_register": "public",
|
|
107
|
+
"delimit_intel_dataset_list": "public",
|
|
108
|
+
"delimit_intel_dataset_freeze": "public",
|
|
109
|
+
"delimit_intel_snapshot_ingest": "public",
|
|
110
|
+
"delimit_intel_query": "public",
|
|
111
|
+
|
|
112
|
+
# === Repo domain (experimental stubs) ===
|
|
113
|
+
"delimit_repo_diagnose": "experimental",
|
|
114
|
+
"delimit_repo_analyze": "experimental",
|
|
115
|
+
"delimit_repo_config_validate": "experimental",
|
|
116
|
+
"delimit_repo_config_audit": "experimental",
|
|
117
|
+
|
|
118
|
+
# === Test domain (public + experimental) ===
|
|
119
|
+
"delimit_test_generate": "public",
|
|
120
|
+
"delimit_test_coverage": "experimental",
|
|
121
|
+
"delimit_test_smoke": "public",
|
|
122
|
+
|
|
123
|
+
# === Orchestrate domain (all public) ===
|
|
124
|
+
"delimit_os_plan": "public",
|
|
125
|
+
"delimit_os_status": "public",
|
|
126
|
+
"delimit_os_gates": "public",
|
|
127
|
+
"delimit_ledger_add": "public",
|
|
128
|
+
"delimit_ledger_done": "public",
|
|
129
|
+
"delimit_ledger_list": "public",
|
|
130
|
+
"delimit_ledger_context": "public",
|
|
131
|
+
"delimit_ventures": "public",
|
|
132
|
+
"delimit_models": "public",
|
|
133
|
+
"delimit_deliberate": "public",
|
|
134
|
+
"delimit_sensor_github_issue": "public",
|
|
135
|
+
"delimit_resource_list": "public",
|
|
136
|
+
"delimit_resource_get": "public",
|
|
137
|
+
"delimit_resource_drivers": "public",
|
|
138
|
+
"delimit_tracker_sync": "public",
|
|
139
|
+
"delimit_webhook_manage": "public",
|
|
140
|
+
"delimit_agent_dispatch": "public",
|
|
141
|
+
"delimit_agent_status": "public",
|
|
142
|
+
"delimit_agent_complete": "public",
|
|
143
|
+
"delimit_agent_handoff": "public",
|
|
144
|
+
"delimit_next_task": "public",
|
|
145
|
+
"delimit_task_complete": "public",
|
|
146
|
+
"delimit_loop_status": "public",
|
|
147
|
+
"delimit_loop_config": "public",
|
|
148
|
+
"delimit_daemon_status": "public",
|
|
149
|
+
"delimit_daemon_run": "public",
|
|
150
|
+
"delimit_daemon_classify": "public",
|
|
151
|
+
"delimit_config_export": "public",
|
|
152
|
+
"delimit_config_import": "public",
|
|
153
|
+
"delimit_changelog": "public",
|
|
154
|
+
|
|
155
|
+
# === Utility (all public) ===
|
|
156
|
+
"delimit_version": "public",
|
|
157
|
+
"delimit_help": "public",
|
|
158
|
+
"delimit_diagnose": "public",
|
|
159
|
+
"delimit_activate": "public",
|
|
160
|
+
"delimit_license_status": "public",
|
|
161
|
+
"delimit_scan": "public",
|
|
162
|
+
"delimit_quickstart": "public",
|
|
163
|
+
|
|
164
|
+
# ═══════════════════════════════════════════════════════════════
|
|
165
|
+
# Workspace-ops tier (~8 tools)
|
|
166
|
+
# Visible with DELIMIT_SHOW_OPS=1 or DELIMIT_SHOW_INTERNAL=1
|
|
167
|
+
# ═══════════════════════════════════════════════════════════════
|
|
168
|
+
"delimit_social_post": "ops_pack",
|
|
169
|
+
"delimit_social_generate": "ops_pack",
|
|
170
|
+
"delimit_social_approve": "ops_pack",
|
|
171
|
+
"delimit_notify": "ops_pack",
|
|
172
|
+
"delimit_design_validate_responsive": "ops_pack",
|
|
173
|
+
"delimit_screenshot": "ops_pack",
|
|
174
|
+
"delimit_screen_record": "ops_pack",
|
|
175
|
+
"delimit_docs_generate": "ops_pack",
|
|
176
|
+
|
|
177
|
+
# ═══════════════════════════════════════════════════════════════
|
|
178
|
+
# Internal tier (~18 tools)
|
|
179
|
+
# Visible with DELIMIT_SHOW_INTERNAL=1
|
|
180
|
+
# ═══════════════════════════════════════════════════════════════
|
|
181
|
+
"delimit_content_schedule": "internal",
|
|
182
|
+
"delimit_content_publish": "internal",
|
|
183
|
+
"delimit_content_queue": "internal",
|
|
184
|
+
"delimit_inbox_daemon": "internal",
|
|
185
|
+
"delimit_notify_inbox": "internal",
|
|
186
|
+
"delimit_design_generate_component": "internal",
|
|
187
|
+
"delimit_design_extract_tokens": "internal",
|
|
188
|
+
"delimit_design_component_library": "internal",
|
|
189
|
+
"delimit_design_generate_tailwind": "internal",
|
|
190
|
+
"delimit_story_generate": "internal",
|
|
191
|
+
"delimit_story_build": "internal",
|
|
192
|
+
"delimit_story_visual_test": "internal",
|
|
193
|
+
"delimit_story_accessibility": "internal",
|
|
194
|
+
"delimit_data_backup": "internal",
|
|
195
|
+
"delimit_data_migrate": "internal",
|
|
196
|
+
"delimit_data_validate": "internal",
|
|
197
|
+
"delimit_social_history": "internal",
|
|
198
|
+
"delimit_social_accounts": "internal",
|
|
199
|
+
"delimit_generate_scaffold": "internal",
|
|
200
|
+
"delimit_generate_template": "internal",
|
|
201
|
+
}
|
package/lib/cross-model-hooks.js
CHANGED
|
@@ -30,6 +30,8 @@ function loadHookConfig() {
|
|
|
30
30
|
session_start: true,
|
|
31
31
|
pre_tool: true,
|
|
32
32
|
pre_commit: true,
|
|
33
|
+
conditional_hooks: true,
|
|
34
|
+
deploy_audit: true,
|
|
33
35
|
deliberate_on_commit: false,
|
|
34
36
|
show_strategy_items: true,
|
|
35
37
|
};
|
|
@@ -125,9 +127,63 @@ function detectAITools() {
|
|
|
125
127
|
// Hook installers per tool
|
|
126
128
|
// ---------------------------------------------------------------------------
|
|
127
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Check if a Claude Code hook group array already contains a delimit hook
|
|
132
|
+
* matching the given command substring.
|
|
133
|
+
*/
|
|
134
|
+
function findClaudeHookGroup(hookGroups, commandSubstring) {
|
|
135
|
+
if (!Array.isArray(hookGroups)) return null;
|
|
136
|
+
for (const group of hookGroups) {
|
|
137
|
+
// Support both nested format (group.hooks[].command) and flat format (group.command)
|
|
138
|
+
if (group.hooks && Array.isArray(group.hooks)) {
|
|
139
|
+
if (group.hooks.some(h => h.command && h.command.includes(commandSubstring))) {
|
|
140
|
+
return group;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (group.command && group.command.includes(commandSubstring)) {
|
|
144
|
+
return group;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Migrate a flat-format hook entry to the nested Claude Code format.
|
|
152
|
+
* Flat: { type, command, matcher, if }
|
|
153
|
+
* Nested: { matcher, if, hooks: [{ type, command }] }
|
|
154
|
+
*/
|
|
155
|
+
function migrateToNestedFormat(hookGroup) {
|
|
156
|
+
if (hookGroup.hooks && Array.isArray(hookGroup.hooks)) {
|
|
157
|
+
return hookGroup; // Already nested
|
|
158
|
+
}
|
|
159
|
+
const nested = { matcher: hookGroup.matcher || '' };
|
|
160
|
+
if (hookGroup.if) nested.if = hookGroup.if;
|
|
161
|
+
nested.hooks = [{ type: hookGroup.type || 'command', command: hookGroup.command }];
|
|
162
|
+
return nested;
|
|
163
|
+
}
|
|
164
|
+
|
|
128
165
|
/**
|
|
129
166
|
* Install hooks into Claude Code's ~/.claude/settings.json
|
|
130
|
-
*
|
|
167
|
+
*
|
|
168
|
+
* Claude Code hook format (nested):
|
|
169
|
+
* {
|
|
170
|
+
* "hooks": {
|
|
171
|
+
* "EventName": [
|
|
172
|
+
* {
|
|
173
|
+
* "matcher": "ToolPattern",
|
|
174
|
+
* "if": "condition expression",
|
|
175
|
+
* "hooks": [
|
|
176
|
+
* { "type": "command", "command": "...", "timeout": 30 }
|
|
177
|
+
* ]
|
|
178
|
+
* }
|
|
179
|
+
* ]
|
|
180
|
+
* }
|
|
181
|
+
* }
|
|
182
|
+
*
|
|
183
|
+
* LED-234: Adds conditional hooks that fire only when relevant files change:
|
|
184
|
+
* 1. PostToolUse (Edit|Write) + spec patterns -> delimit lint
|
|
185
|
+
* 2. PreToolUse (Bash) + git commit -> delimit doctor
|
|
186
|
+
* 3. PreToolUse (Bash) + deploy patterns -> delimit security-audit
|
|
131
187
|
*/
|
|
132
188
|
function installClaudeHooks(tool, hookConfig) {
|
|
133
189
|
const configPath = tool.configPath;
|
|
@@ -148,73 +204,133 @@ function installClaudeHooks(tool, hookConfig) {
|
|
|
148
204
|
const npxCmd = 'npx delimit-cli';
|
|
149
205
|
const changes = [];
|
|
150
206
|
|
|
151
|
-
// SessionStart hook
|
|
207
|
+
// --- SessionStart hook (no condition) ---
|
|
152
208
|
if (hookConfig.session_start) {
|
|
153
|
-
const sessionHook = {
|
|
154
|
-
type: 'command',
|
|
155
|
-
command: `${npxCmd} hook session-start`,
|
|
156
|
-
};
|
|
157
209
|
if (!config.hooks.SessionStart) {
|
|
158
210
|
config.hooks.SessionStart = [];
|
|
159
211
|
}
|
|
160
|
-
|
|
161
|
-
const existing = config.hooks.SessionStart.find(
|
|
162
|
-
h => h.command && h.command.includes('delimit-cli hook session-start')
|
|
163
|
-
);
|
|
212
|
+
const existing = findClaudeHookGroup(config.hooks.SessionStart, 'delimit-cli hook session-start');
|
|
164
213
|
if (!existing) {
|
|
165
|
-
config.hooks.SessionStart.push(
|
|
214
|
+
config.hooks.SessionStart.push({
|
|
215
|
+
matcher: '',
|
|
216
|
+
hooks: [{
|
|
217
|
+
type: 'command',
|
|
218
|
+
command: `${npxCmd} hook session-start`,
|
|
219
|
+
}],
|
|
220
|
+
});
|
|
166
221
|
changes.push('SessionStart');
|
|
167
222
|
}
|
|
168
223
|
}
|
|
169
224
|
|
|
170
|
-
// PreToolUse hook
|
|
225
|
+
// --- PreToolUse: pre-tool hook scoped to Edit/Write on spec files ---
|
|
171
226
|
if (hookConfig.pre_tool) {
|
|
172
|
-
const preToolHook = {
|
|
173
|
-
type: 'command',
|
|
174
|
-
command: `${npxCmd} hook pre-tool $TOOL_NAME`,
|
|
175
|
-
matcher: 'Edit|Write',
|
|
176
|
-
if: "Edit && (path_matches('**/openapi*') || path_matches('**/swagger*') || path_matches('**/*.yaml') || path_matches('**/*.yml'))",
|
|
177
|
-
};
|
|
178
227
|
if (!config.hooks.PreToolUse) {
|
|
179
228
|
config.hooks.PreToolUse = [];
|
|
180
229
|
}
|
|
181
|
-
const existing = config.hooks.PreToolUse
|
|
182
|
-
h => h.command && h.command.includes('delimit-cli hook pre-tool')
|
|
183
|
-
);
|
|
230
|
+
const existing = findClaudeHookGroup(config.hooks.PreToolUse, 'delimit-cli hook pre-tool');
|
|
184
231
|
if (existing) {
|
|
185
|
-
// Upgrade
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
232
|
+
// Upgrade flat-format hook to nested + add if condition if missing
|
|
233
|
+
const migrated = migrateToNestedFormat(existing);
|
|
234
|
+
if (!migrated.if) {
|
|
235
|
+
const idx = config.hooks.PreToolUse.indexOf(existing);
|
|
236
|
+
migrated.matcher = 'Edit|Write';
|
|
237
|
+
migrated.if = "Edit && (path_matches('**/openapi*') || path_matches('**/swagger*') || path_matches('**/*.yaml') || path_matches('**/*.yml'))";
|
|
238
|
+
migrated.hooks = [{ type: 'command', command: `${npxCmd} hook pre-tool $TOOL_NAME` }];
|
|
239
|
+
config.hooks.PreToolUse[idx] = migrated;
|
|
190
240
|
changes.push('PreToolUse (upgraded)');
|
|
191
241
|
}
|
|
192
242
|
} else {
|
|
193
|
-
config.hooks.PreToolUse.push(
|
|
243
|
+
config.hooks.PreToolUse.push({
|
|
244
|
+
matcher: 'Edit|Write',
|
|
245
|
+
if: "Edit && (path_matches('**/openapi*') || path_matches('**/swagger*') || path_matches('**/*.yaml') || path_matches('**/*.yml'))",
|
|
246
|
+
hooks: [{
|
|
247
|
+
type: 'command',
|
|
248
|
+
command: `${npxCmd} hook pre-tool $TOOL_NAME`,
|
|
249
|
+
}],
|
|
250
|
+
});
|
|
194
251
|
changes.push('PreToolUse');
|
|
195
252
|
}
|
|
196
253
|
}
|
|
197
254
|
|
|
198
|
-
// PreToolUse
|
|
255
|
+
// --- PreToolUse: pre-commit governance on git commit/push ---
|
|
199
256
|
if (hookConfig.pre_commit) {
|
|
200
|
-
const preCommitHook = {
|
|
201
|
-
type: 'command',
|
|
202
|
-
command: `${npxCmd} hook pre-commit`,
|
|
203
|
-
matcher: 'Bash',
|
|
204
|
-
if: "Bash && (input_contains('git commit') || input_contains('git push'))",
|
|
205
|
-
};
|
|
206
257
|
if (!config.hooks.PreToolUse) {
|
|
207
258
|
config.hooks.PreToolUse = [];
|
|
208
259
|
}
|
|
209
|
-
const existing = config.hooks.PreToolUse
|
|
210
|
-
h => h.command && h.command.includes('delimit-cli hook pre-commit')
|
|
211
|
-
);
|
|
260
|
+
const existing = findClaudeHookGroup(config.hooks.PreToolUse, 'delimit-cli hook pre-commit');
|
|
212
261
|
if (!existing) {
|
|
213
|
-
config.hooks.PreToolUse.push(
|
|
262
|
+
config.hooks.PreToolUse.push({
|
|
263
|
+
matcher: 'Bash',
|
|
264
|
+
if: "Bash && (input_contains('git commit') || input_contains('git push'))",
|
|
265
|
+
hooks: [{
|
|
266
|
+
type: 'command',
|
|
267
|
+
command: `${npxCmd} hook pre-commit`,
|
|
268
|
+
}],
|
|
269
|
+
});
|
|
214
270
|
changes.push('PreCommit');
|
|
215
271
|
}
|
|
216
272
|
}
|
|
217
273
|
|
|
274
|
+
// --- LED-234: Conditional hooks (opt-in via conditional_hooks config) ---
|
|
275
|
+
if (hookConfig.conditional_hooks !== false) {
|
|
276
|
+
|
|
277
|
+
// 1. PostToolUse: auto-lint after editing OpenAPI spec files
|
|
278
|
+
if (!config.hooks.PostToolUse) {
|
|
279
|
+
config.hooks.PostToolUse = [];
|
|
280
|
+
}
|
|
281
|
+
const specLintCmd = 'delimit-cli lint';
|
|
282
|
+
const existingSpecLint = findClaudeHookGroup(config.hooks.PostToolUse, specLintCmd);
|
|
283
|
+
if (!existingSpecLint) {
|
|
284
|
+
config.hooks.PostToolUse.push({
|
|
285
|
+
matcher: 'Edit|Write',
|
|
286
|
+
if: "filePath matches '**/*openapi*.yaml' or filePath matches '**/*openapi*.yml' or filePath matches '**/*openapi*.json' or filePath matches '**/*swagger*.yaml' or filePath matches '**/*swagger*.yml' or filePath matches '**/*swagger*.json' or filePath matches '**/api/*.yaml' or filePath matches '**/api/*.yml' or filePath matches '**/specs/**'",
|
|
287
|
+
hooks: [{
|
|
288
|
+
type: 'command',
|
|
289
|
+
command: `${npxCmd} lint "$DELIMIT_FILE_PATH"`,
|
|
290
|
+
timeout: 30,
|
|
291
|
+
}],
|
|
292
|
+
});
|
|
293
|
+
changes.push('PostToolUse:spec-lint');
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// 2. PreToolUse: repo diagnose before git commit (uses doctor command)
|
|
297
|
+
if (!config.hooks.PreToolUse) {
|
|
298
|
+
config.hooks.PreToolUse = [];
|
|
299
|
+
}
|
|
300
|
+
const doctorCmd = 'delimit-cli doctor';
|
|
301
|
+
const existingDoctor = findClaudeHookGroup(config.hooks.PreToolUse, doctorCmd);
|
|
302
|
+
if (!existingDoctor) {
|
|
303
|
+
config.hooks.PreToolUse.push({
|
|
304
|
+
matcher: 'Bash',
|
|
305
|
+
if: "command matches 'git commit'",
|
|
306
|
+
hooks: [{
|
|
307
|
+
type: 'command',
|
|
308
|
+
command: `${npxCmd} doctor`,
|
|
309
|
+
timeout: 15,
|
|
310
|
+
}],
|
|
311
|
+
});
|
|
312
|
+
changes.push('PreToolUse:doctor');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// 3. PreToolUse: security audit before deploy/publish/release commands
|
|
316
|
+
if (hookConfig.deploy_audit !== false) {
|
|
317
|
+
const securityCmd = 'delimit-cli security-audit';
|
|
318
|
+
const existingSecurity = findClaudeHookGroup(config.hooks.PreToolUse, securityCmd);
|
|
319
|
+
if (!existingSecurity) {
|
|
320
|
+
config.hooks.PreToolUse.push({
|
|
321
|
+
matcher: 'Bash',
|
|
322
|
+
if: "command matches 'npm publish' or command matches 'npx deploy' or command matches 'deploy' or command matches 'release'",
|
|
323
|
+
hooks: [{
|
|
324
|
+
type: 'command',
|
|
325
|
+
command: `${npxCmd} security-audit`,
|
|
326
|
+
timeout: 30,
|
|
327
|
+
}],
|
|
328
|
+
});
|
|
329
|
+
changes.push('PreToolUse:deploy-audit');
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
218
334
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
219
335
|
return changes;
|
|
220
336
|
}
|
|
@@ -235,10 +351,12 @@ function installCodexHooks(tool, hookConfig) {
|
|
|
235
351
|
const delimitBlock = `<!-- delimit:hooks-start -->
|
|
236
352
|
## Delimit Governance
|
|
237
353
|
|
|
238
|
-
On every session start
|
|
354
|
+
On every session start:
|
|
355
|
+
1. Run this command to check governance status:
|
|
239
356
|
\`\`\`
|
|
240
357
|
npx delimit-cli hook session-start
|
|
241
358
|
\`\`\`
|
|
359
|
+
2. Call \`delimit_inbox_daemon(action="start")\` to start email polling for approvals.
|
|
242
360
|
|
|
243
361
|
Before committing changes, run:
|
|
244
362
|
\`\`\`
|
|
@@ -311,7 +429,8 @@ function installGeminiHooks(tool, hookConfig) {
|
|
|
311
429
|
|
|
312
430
|
const govInstructions = [
|
|
313
431
|
'Delimit governance is active.',
|
|
314
|
-
'On session start
|
|
432
|
+
'On session start: (1) run: npx delimit-cli hook session-start,',
|
|
433
|
+
'(2) call delimit_inbox_daemon(action="start") to start email polling.',
|
|
315
434
|
'Before committing, run: npx delimit-cli hook pre-commit',
|
|
316
435
|
'After editing API specs, run: npx delimit-cli hook pre-tool Edit',
|
|
317
436
|
'Call delimit_ledger_context and delimit_gov_health via MCP when available.',
|
|
@@ -331,6 +450,7 @@ function installGeminiHooks(tool, hookConfig) {
|
|
|
331
450
|
|
|
332
451
|
On every session start:
|
|
333
452
|
1. Run \`npx delimit-cli hook session-start\` to check open tasks and governance status
|
|
453
|
+
2. Call \`delimit_inbox_daemon(action="start")\` to start email polling for approvals
|
|
334
454
|
|
|
335
455
|
After editing code:
|
|
336
456
|
- After editing API specs: run \`npx delimit-cli hook pre-tool Edit\`
|
|
@@ -392,9 +512,14 @@ function removeClaudeHooks() {
|
|
|
392
512
|
for (const event of ['SessionStart', 'PreToolUse', 'PostToolUse']) {
|
|
393
513
|
if (Array.isArray(config.hooks[event])) {
|
|
394
514
|
const before = config.hooks[event].length;
|
|
395
|
-
config.hooks[event] = config.hooks[event].filter(
|
|
396
|
-
|
|
397
|
-
|
|
515
|
+
config.hooks[event] = config.hooks[event].filter(h => {
|
|
516
|
+
// Nested format: check hooks[].command
|
|
517
|
+
if (h.hooks && Array.isArray(h.hooks)) {
|
|
518
|
+
return !h.hooks.some(inner => inner.command && inner.command.includes('delimit-cli'));
|
|
519
|
+
}
|
|
520
|
+
// Flat format: check h.command directly
|
|
521
|
+
return !(h.command && h.command.includes('delimit-cli'));
|
|
522
|
+
});
|
|
398
523
|
if (config.hooks[event].length === 0) {
|
|
399
524
|
delete config.hooks[event];
|
|
400
525
|
}
|
|
@@ -671,6 +796,9 @@ async function hookSessionStart() {
|
|
|
671
796
|
}
|
|
672
797
|
} catch { /* not in git repo */ }
|
|
673
798
|
|
|
799
|
+
// Remind AI to start inbox daemon
|
|
800
|
+
lines.push('[Delimit] Action required: call delimit_inbox_daemon(action="start") to enable email-based approvals');
|
|
801
|
+
|
|
674
802
|
lines.push('');
|
|
675
803
|
process.stdout.write(lines.join('\n') + '\n');
|
|
676
804
|
}
|
|
@@ -849,4 +977,6 @@ module.exports = {
|
|
|
849
977
|
hookPreCommit,
|
|
850
978
|
countPendingStrategyItems,
|
|
851
979
|
getTopStrategyItem,
|
|
980
|
+
findClaudeHookGroup,
|
|
981
|
+
migrateToNestedFormat,
|
|
852
982
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "delimit-cli",
|
|
3
3
|
"mcpName": "io.github.delimit-ai/delimit-mcp-server",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.14.1",
|
|
5
5
|
"description": "Unify Claude Code, Codex, Cursor, and Gemini CLI with persistent context, governance, and multi-model debate.",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"files": [
|