pumuki-ast-hooks 5.6.4 → 5.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/README.md +48 -5
  2. package/docs/images/ai-start.png +0 -0
  3. package/docs/images/ai_gate.png +0 -0
  4. package/docs/images/pre-flight-check.png +0 -0
  5. package/hooks/pre-tool-use-guard.ts +105 -1
  6. package/package.json +2 -2
  7. package/scripts/hooks-system/.audit-reports/auto-recovery.log +3 -0
  8. package/scripts/hooks-system/.audit-reports/install-wizard.log +12 -0
  9. package/scripts/hooks-system/.audit_tmp/hook-metrics.jsonl +72 -0
  10. package/scripts/hooks-system/bin/__tests__/cli-audit-no-stack.spec.js +22 -0
  11. package/scripts/hooks-system/bin/cli.js +176 -7
  12. package/scripts/hooks-system/bin/update-evidence.sh +8 -0
  13. package/scripts/hooks-system/infrastructure/ast/android/analyzers/AndroidSOLIDAnalyzer.js +33 -5
  14. package/scripts/hooks-system/infrastructure/ast/backend/ast-backend.js +7 -1
  15. package/scripts/hooks-system/infrastructure/ast/common/__tests__/ast-common.spec.js +19 -0
  16. package/scripts/hooks-system/infrastructure/ast/common/ast-common.js +24 -19
  17. package/scripts/hooks-system/infrastructure/ast/ios/__tests__/forbidden-testable-import.spec.js +21 -0
  18. package/scripts/hooks-system/infrastructure/ast/ios/__tests__/missing-makesut-leaks.spec.js +64 -0
  19. package/scripts/hooks-system/infrastructure/ast/ios/ast-ios.js +74 -33
  20. package/scripts/hooks-system/infrastructure/ast/ios/detectors/__tests__/ios-encapsulation-public-mutable.spec.js +63 -0
  21. package/scripts/hooks-system/infrastructure/ast/ios/detectors/__tests__/ios-unused-imports.spec.js +34 -0
  22. package/scripts/hooks-system/infrastructure/ast/ios/detectors/ios-ast-intelligent-strategies.js +15 -2
  23. package/scripts/hooks-system/infrastructure/mcp/__tests__/preflight-check-blocks-tests.spec.js +14 -0
  24. package/scripts/hooks-system/infrastructure/mcp/ast-intelligence-automation.js +154 -50
  25. package/scripts/hooks-system/infrastructure/orchestration/__tests__/intelligent-audit.spec.js +39 -1
  26. package/scripts/hooks-system/infrastructure/orchestration/intelligent-audit.js +67 -0
  27. package/scripts/hooks-system/infrastructure/watchdog/__tests__/.audit-reports/token-monitor.log +9 -0
package/README.md CHANGED
@@ -100,6 +100,9 @@ Pumuki addresses these issues by **removing trust from the AI** and replacing it
100
100
  * **In-Memory AST Analysis**: `analyzeCodeInMemory()` for proposed code validation
101
101
  * **IDE Hooks**: Real-time blocking in Windsurf, Claude Code, OpenCode
102
102
 
103
+ <img src="./assets/Hook_02.png" alt="Windsurf pre-write hook output" width="100%" />
104
+
105
+ <img src="./assets/Hook_01.png" alt="Windsurf post-write hook output" width="100%" />
103
106
  ### NEW: MCP Integration
104
107
 
105
108
  * **MCP Server**: Full Model Context Protocol integration
@@ -107,6 +110,8 @@ Pumuki addresses these issues by **removing trust from the AI** and replacing it
107
110
  * **pre_flight_check**: Validate proposed code before writing
108
111
  * **set_human_intent**: Track user goals across sessions
109
112
 
113
+ ![MCP ai_gate_check BLOCKED example](docs/images/ai_gate.png)
114
+
110
115
  ### NEW: Cognitive Layers
111
116
 
112
117
  * **Human Intent**: Persistent user goal tracking with confidence levels
@@ -122,15 +127,15 @@ Pumuki addresses these issues by **removing trust from the AI** and replacing it
122
127
 
123
128
  ## Visual Overview
124
129
 
125
- <img src="https://raw.githubusercontent.com/SwiftEnProfundidad/ast-intelligence-hooks/main/docs/images/ast_intelligence_01.svg" alt="AST Intelligence System Overview" width="100%" />
130
+ ![AST Intelligence System Overview](docs/images/ast_intelligence_01.svg)
126
131
 
127
- <img src="https://raw.githubusercontent.com/SwiftEnProfundidad/ast-intelligence-hooks/main/docs/images/ast_intelligence_02.svg" alt="AST Intelligence Workflow" width="100%" />
132
+ ![AST Intelligence Workflow](docs/images/ast_intelligence_02.svg)
128
133
 
129
- <img src="https://raw.githubusercontent.com/SwiftEnProfundidad/ast-intelligence-hooks/main/docs/images/ast_intelligence_03.svg" alt="AST Intelligence Audit - Part 1" width="100%" />
134
+ ![AST Intelligence Audit - Part 1](docs/images/ast_intelligence_03.svg)
130
135
 
131
- <img src="https://raw.githubusercontent.com/SwiftEnProfundidad/ast-intelligence-hooks/main/docs/images/ast_intelligence_04.svg" alt="AST Intelligence Audit - Part 2" width="100%" />
136
+ ![AST Intelligence Audit - Part 2](docs/images/ast_intelligence_04.svg)
132
137
 
133
- <img src="https://raw.githubusercontent.com/SwiftEnProfundidad/ast-intelligence-hooks/main/docs/images/ast_intelligence_05.svg" alt="AST Intelligence Audit - Part 3" width="100%" />
138
+ ![AST Intelligence Audit - Part 3](docs/images/ast_intelligence_05.svg)
134
139
 
135
140
  ---
136
141
 
@@ -251,6 +256,7 @@ if (result.hasCritical) {
251
256
  * Detect critical violations before code is written
252
257
  * Platform-aware analysis (iOS, Android, Backend, Frontend)
253
258
  * Integration with IDE hooks for real-time blocking
259
+ ![Pre-flight check example](docs/images/pre-flight-check.png)
254
260
 
255
261
  ---
256
262
 
@@ -345,6 +351,26 @@ AI generates code → IDE Hook intercepts → AST Intelligence analyzes →
345
351
 
346
352
  > **Note**: For IDEs without pre-write hooks, the Git pre-commit hook provides 100% enforcement at commit time.
347
353
 
354
+ ### PreToolUse Guard (`hooks/pre-tool-use-guard.ts`)
355
+
356
+ This repository includes a **PreToolUse guard hook** that runs *before* `Edit` / `Write` / `MultiEdit` operations (where supported by the IDE integration).
357
+
358
+ When it runs:
359
+
360
+ * In IDEs that support **PreToolUse** interception (e.g. Claude Code integrations)
361
+ * Before code is written to disk
362
+
363
+ What it does:
364
+
365
+ * Analyzes the **proposed code** using `analyzeCodeInMemory()`.
366
+ * If the IDE only provides **partial diffs** (`tool_input.edits`), the hook reconstructs the final candidate file content by applying `old_string → new_string` edits over the current file content, then analyzes the result.
367
+ * **Blocks** the operation when **CRITICAL/HIGH** violations are detected (exit code `2`).
368
+
369
+ Why it exists:
370
+
371
+ * To ensure **enforcement happens before write**, not only at commit time.
372
+ * To prevent silent injection of high-impact patterns (e.g. empty `catch {}`) during AI-assisted edits.
373
+
348
374
  See [`scripts/hooks-system/infrastructure/cascade-hooks/README.md`](./scripts/hooks-system/infrastructure/cascade-hooks/README.md) for installation instructions.
349
375
 
350
376
  ---
@@ -384,6 +410,23 @@ Initialize AI evidence:
384
410
  npx ai-start
385
411
  ```
386
412
 
413
+ ### What is `ai-start` and when should you run it?
414
+
415
+ `ai-start` refreshes the project AI context and updates `.AI_EVIDENCE.json` so the system has a **fresh, auditable source of truth** before any AI-assisted work.
416
+
417
+ Run it when:
418
+
419
+ * **Starting a new day/session** (fresh context)
420
+ * **After pulling a lot of changes** (context drift prevention)
421
+ * **After fixing blocking violations** (to refresh gate + evidence)
422
+
423
+ What it does:
424
+
425
+ * Detects active platforms (iOS/Android/Backend/Frontend)
426
+ * Refreshes the evidence file (`.AI_EVIDENCE.json`)
427
+ * Helps the AI gate operate deterministically (block/allow)
428
+
429
+ ![ai-start execution example](docs/images/ai-start.png)
387
430
  ---
388
431
 
389
432
  ## Installation & Lifecycle Commands
Binary file
Binary file
Binary file
@@ -1,6 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFileSync, existsSync } from 'fs';
3
3
  import { join } from 'path';
4
+ import { createRequire } from 'module';
5
+
6
+ const require = createRequire(import.meta.url);
4
7
 
5
8
  interface ToolInput {
6
9
  tool_name: string;
@@ -9,6 +12,10 @@ interface ToolInput {
9
12
  target_file?: string;
10
13
  contents?: string;
11
14
  new_string?: string;
15
+ edits?: Array<{
16
+ old_string?: string;
17
+ new_string?: string;
18
+ }>;
12
19
  };
13
20
  }
14
21
 
@@ -58,6 +65,39 @@ function matchesPathPattern(filePath: string, patterns: string[]): boolean {
58
65
  return false;
59
66
  }
60
67
 
68
+ function resolveFilePath(projectDir: string, filePath: string): string {
69
+ if (!filePath) return filePath;
70
+ if (filePath.startsWith('/')) return filePath;
71
+ return join(projectDir, filePath);
72
+ }
73
+
74
+ function applyStringEdits(baseContent: string, edits: Array<{ old_string?: string; new_string?: string }>): {
75
+ content: string;
76
+ appliedCount: number;
77
+ } {
78
+ let content = baseContent;
79
+ let appliedCount = 0;
80
+
81
+ for (const edit of edits) {
82
+ const oldStr = edit?.old_string;
83
+ const newStr = edit?.new_string;
84
+ if (!newStr) continue;
85
+
86
+ if (oldStr && content.includes(oldStr)) {
87
+ content = content.replace(oldStr, newStr);
88
+ appliedCount += 1;
89
+ continue;
90
+ }
91
+
92
+ // If we can't locate old_string (or it's missing), we cannot safely place the change.
93
+ // Best-effort fallback: append to end so AST still sees the new code.
94
+ content = `${content}\n${newStr}`;
95
+ appliedCount += 1;
96
+ }
97
+
98
+ return { content, appliedCount };
99
+ }
100
+
61
101
  function shouldSkip(skipConditions?: SkipConditions, filePath?: string): boolean {
62
102
  if (!skipConditions) {
63
103
  return false;
@@ -94,7 +134,71 @@ async function main() {
94
134
  }
95
135
 
96
136
  const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
97
- const { getSkillRulesPath } = await import('./getSkillRulesPath.js');
137
+ const resolvedFilePath = resolveFilePath(projectDir, filePath);
138
+
139
+ const proposedCodeDirect = (input.tool_input?.contents || input.tool_input?.new_string || '').trim();
140
+ const isTestFile = /\.(spec|test)\.(js|ts|swift|kt)$/i.test(resolvedFilePath);
141
+
142
+ let candidateCode = proposedCodeDirect;
143
+ if (!candidateCode) {
144
+ const edits = Array.isArray(input.tool_input?.edits) ? input.tool_input.edits : [];
145
+ if (edits.length > 0) {
146
+ let baseContent = '';
147
+ try {
148
+ if (existsSync(resolvedFilePath)) {
149
+ baseContent = readFileSync(resolvedFilePath, 'utf8');
150
+ }
151
+ } catch {
152
+ baseContent = '';
153
+ }
154
+
155
+ const applied = applyStringEdits(baseContent, edits);
156
+ candidateCode = (applied.content || '').trim();
157
+ }
158
+ }
159
+
160
+ if (!isTestFile && candidateCode.length > 0) {
161
+ try {
162
+ const { analyzeCodeInMemory } = require(join(projectDir, 'scripts', 'hooks-system', 'infrastructure', 'ast', 'ast-core'));
163
+ const analysis = analyzeCodeInMemory(candidateCode, resolvedFilePath);
164
+ if (analysis && (analysis.hasCritical || analysis.hasHigh)) {
165
+ const violations = Array.isArray(analysis.violations)
166
+ ? analysis.violations.filter((v: unknown) => {
167
+ if (!v || typeof v !== 'object') return false;
168
+ const severity = (v as { severity?: unknown }).severity;
169
+ return severity === 'CRITICAL' || severity === 'HIGH';
170
+ })
171
+ : [];
172
+
173
+ const message = [
174
+ '',
175
+ '🚫 AST INTELLIGENCE BLOCKED THIS WRITE',
176
+ `File: ${resolvedFilePath}`,
177
+ ...violations.map((v: unknown) => {
178
+ const obj = (v && typeof v === 'object')
179
+ ? (v as { ruleId?: unknown; rule?: unknown; message?: unknown })
180
+ : {};
181
+ const ruleId = (typeof obj.ruleId === 'string' && obj.ruleId.length > 0)
182
+ ? obj.ruleId
183
+ : (typeof obj.rule === 'string' && obj.rule.length > 0)
184
+ ? obj.rule
185
+ : 'unknown';
186
+ const message = typeof obj.message === 'string' ? obj.message : '';
187
+ return ` ❌ [${ruleId}] ${message}`;
188
+ }),
189
+ ''
190
+ ].join('\n');
191
+ process.stderr.write(message);
192
+ process.exit(2);
193
+ }
194
+ } catch (error) {
195
+ if (process.env.DEBUG) {
196
+ process.stderr.write(`PreToolUse AST check failed: ${error instanceof Error ? error.message : String(error)}\n`);
197
+ }
198
+ }
199
+ }
200
+
201
+ const { getSkillRulesPath } = await import('./getSkillRulesPath.ts');
98
202
  const rulesPath = getSkillRulesPath(projectDir);
99
203
 
100
204
  if (!rulesPath || !existsSync(rulesPath)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki-ast-hooks",
3
- "version": "5.6.4",
3
+ "version": "5.6.6",
4
4
  "description": "Enterprise-grade AST Intelligence System with multi-platform support (iOS, Android, Backend, Frontend) and Feature-First + DDD + Clean Architecture enforcement. Includes dynamic violations API for intelligent querying.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -134,4 +134,4 @@
134
134
  "./skills": "./skills/skill-rules.json",
135
135
  "./hooks": "./hooks/index.js"
136
136
  }
137
- }
137
+ }
@@ -0,0 +1,3 @@
1
+ {"timestamp":"2026-01-11T00:41:26.979Z","level":"info","component":"AutoRecovery","event":"NotificationCenterService shutdown","data":{"totalEnqueued":0,"totalSent":0,"totalDeduplicated":0,"totalCooldownSkipped":0,"totalFailed":0,"totalRetries":0,"queueSize":0,"deduplication":{"size":0},"cooldowns":{"activeCooldowns":0}},"context":{}}
2
+ {"timestamp":"2026-01-11T00:47:24.400Z","level":"info","component":"AutoRecovery","event":"NotificationCenterService shutdown","data":{"totalEnqueued":0,"totalSent":0,"totalDeduplicated":0,"totalCooldownSkipped":0,"totalFailed":0,"totalRetries":0,"queueSize":0,"deduplication":{"size":0},"cooldowns":{"activeCooldowns":0}},"context":{}}
3
+ {"timestamp":"2026-01-11T00:48:07.599Z","level":"info","component":"AutoRecovery","event":"NotificationCenterService shutdown","data":{"totalEnqueued":0,"totalSent":0,"totalDeduplicated":0,"totalCooldownSkipped":0,"totalFailed":0,"totalRetries":0,"queueSize":0,"deduplication":{"size":0},"cooldowns":{"activeCooldowns":0}},"context":{}}
@@ -0,0 +1,12 @@
1
+ {"timestamp":"2026-01-11T00:41:26.266Z","level":"info","component":"InstallWizard","event":"INSTALL_WIZARD_START","data":{"repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"},"context":{}}
2
+ {"timestamp":"2026-01-11T00:41:26.274Z","level":"info","component":"InstallWizard","event":"INSTALL_WIZARD_CONFIG_EXISTS","data":{"configPath":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/.hook-system/config.json"},"context":{}}
3
+ {"timestamp":"2026-01-11T00:41:26.275Z","level":"error","component":"InstallWizard","event":"INSTALL_WIZARD_SYMLINK_FAILED","data":{"error":"EEXIST: file already exists, symlink '/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/scripts/hooks-system/bin/guard-supervisor.js' -> '/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/.git/hooks/guard-supervisor'"},"context":{}}
4
+ {"timestamp":"2026-01-11T00:41:26.275Z","level":"info","component":"InstallWizard","event":"INSTALL_WIZARD_COMPLETED","data":{},"context":{}}
5
+ {"timestamp":"2026-01-11T00:47:24.957Z","level":"info","component":"InstallWizard","event":"INSTALL_WIZARD_START","data":{"repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"},"context":{}}
6
+ {"timestamp":"2026-01-11T00:47:24.965Z","level":"info","component":"InstallWizard","event":"INSTALL_WIZARD_CONFIG_EXISTS","data":{"configPath":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/.hook-system/config.json"},"context":{}}
7
+ {"timestamp":"2026-01-11T00:47:24.965Z","level":"error","component":"InstallWizard","event":"INSTALL_WIZARD_SYMLINK_FAILED","data":{"error":"EEXIST: file already exists, symlink '/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/scripts/hooks-system/bin/guard-supervisor.js' -> '/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/.git/hooks/guard-supervisor'"},"context":{}}
8
+ {"timestamp":"2026-01-11T00:47:24.965Z","level":"info","component":"InstallWizard","event":"INSTALL_WIZARD_COMPLETED","data":{},"context":{}}
9
+ {"timestamp":"2026-01-11T00:48:08.380Z","level":"info","component":"InstallWizard","event":"INSTALL_WIZARD_START","data":{"repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"},"context":{}}
10
+ {"timestamp":"2026-01-11T00:48:08.399Z","level":"info","component":"InstallWizard","event":"INSTALL_WIZARD_CONFIG_EXISTS","data":{"configPath":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/.hook-system/config.json"},"context":{}}
11
+ {"timestamp":"2026-01-11T00:48:08.400Z","level":"error","component":"InstallWizard","event":"INSTALL_WIZARD_SYMLINK_FAILED","data":{"error":"EEXIST: file already exists, symlink '/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/scripts/hooks-system/bin/guard-supervisor.js' -> '/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/.git/hooks/guard-supervisor'"},"context":{}}
12
+ {"timestamp":"2026-01-11T00:48:08.400Z","level":"info","component":"InstallWizard","event":"INSTALL_WIZARD_COMPLETED","data":{},"context":{}}
@@ -230,3 +230,75 @@
230
230
  {"timestamp":1768038658259,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
231
231
  {"timestamp":1768038658259,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
232
232
  {"timestamp":1768038658259,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
233
+ {"timestamp":1768092086977,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
234
+ {"timestamp":1768092086978,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
235
+ {"timestamp":1768092086978,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
236
+ {"timestamp":1768092086978,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
237
+ {"timestamp":1768092086978,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
238
+ {"timestamp":1768092086978,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
239
+ {"timestamp":1768092086978,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
240
+ {"timestamp":1768092086978,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
241
+ {"timestamp":1768092086978,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
242
+ {"timestamp":1768092086978,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
243
+ {"timestamp":1768092086978,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
244
+ {"timestamp":1768092086978,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
245
+ {"timestamp":1768092086978,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
246
+ {"timestamp":1768092086978,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
247
+ {"timestamp":1768092086979,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
248
+ {"timestamp":1768092086979,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
249
+ {"timestamp":1768092086979,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
250
+ {"timestamp":1768092086979,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
251
+ {"timestamp":1768092086979,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
252
+ {"timestamp":1768092086979,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
253
+ {"timestamp":1768092086979,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
254
+ {"timestamp":1768092086979,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
255
+ {"timestamp":1768092086979,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
256
+ {"timestamp":1768092086979,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
257
+ {"timestamp":1768092444397,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
258
+ {"timestamp":1768092444398,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
259
+ {"timestamp":1768092444398,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
260
+ {"timestamp":1768092444398,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
261
+ {"timestamp":1768092444398,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
262
+ {"timestamp":1768092444398,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
263
+ {"timestamp":1768092444398,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
264
+ {"timestamp":1768092444398,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
265
+ {"timestamp":1768092444398,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
266
+ {"timestamp":1768092444399,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
267
+ {"timestamp":1768092444399,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
268
+ {"timestamp":1768092444399,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
269
+ {"timestamp":1768092444399,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
270
+ {"timestamp":1768092444399,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
271
+ {"timestamp":1768092444399,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
272
+ {"timestamp":1768092444399,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
273
+ {"timestamp":1768092444399,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
274
+ {"timestamp":1768092444399,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
275
+ {"timestamp":1768092444399,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
276
+ {"timestamp":1768092444399,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
277
+ {"timestamp":1768092444400,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
278
+ {"timestamp":1768092444400,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
279
+ {"timestamp":1768092444400,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
280
+ {"timestamp":1768092444400,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
281
+ {"timestamp":1768092487594,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
282
+ {"timestamp":1768092487595,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
283
+ {"timestamp":1768092487595,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
284
+ {"timestamp":1768092487595,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
285
+ {"timestamp":1768092487596,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
286
+ {"timestamp":1768092487596,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
287
+ {"timestamp":1768092487596,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
288
+ {"timestamp":1768092487596,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
289
+ {"timestamp":1768092487596,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
290
+ {"timestamp":1768092487596,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
291
+ {"timestamp":1768092487596,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
292
+ {"timestamp":1768092487596,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
293
+ {"timestamp":1768092487597,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
294
+ {"timestamp":1768092487597,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
295
+ {"timestamp":1768092487597,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
296
+ {"timestamp":1768092487597,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
297
+ {"timestamp":1768092487597,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
298
+ {"timestamp":1768092487598,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
299
+ {"timestamp":1768092487598,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
300
+ {"timestamp":1768092487598,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
301
+ {"timestamp":1768092487598,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
302
+ {"timestamp":1768092487598,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
303
+ {"timestamp":1768092487599,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
304
+ {"timestamp":1768092487599,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
@@ -0,0 +1,22 @@
1
+ jest.mock('child_process', () => ({
2
+ execSync: jest.fn(() => {
3
+ const err = new Error('audit failed');
4
+ err.status = 1;
5
+ throw err;
6
+ })
7
+ }));
8
+
9
+ describe('cli audit', () => {
10
+ it('exits with the same status instead of throwing a stacktrace', () => {
11
+ const cli = require('../cli.js');
12
+
13
+ const exitSpy = jest.spyOn(process, 'exit').mockImplementation((code) => {
14
+ throw new Error(`process.exit:${code}`);
15
+ });
16
+
17
+ expect(() => cli.commands.audit()).toThrow('process.exit:1');
18
+ expect(exitSpy).toHaveBeenCalledWith(1);
19
+
20
+ exitSpy.mockRestore();
21
+ });
22
+ });
@@ -47,6 +47,89 @@ function resolveRepoRoot() {
47
47
  return process.cwd();
48
48
  }
49
49
 
50
+ function getCurrentBranchSafe() {
51
+ try {
52
+ const output = execSync('git branch --show-current', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
53
+ return output.trim() || null;
54
+ } catch (e) {
55
+ return null;
56
+ }
57
+ }
58
+
59
+ function getStagedFilesSafe() {
60
+ try {
61
+ const output = execSync('git diff --cached --name-only', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
62
+ return output.trim().split('\n').filter(Boolean);
63
+ } catch (e) {
64
+ return [];
65
+ }
66
+ }
67
+
68
+ function proposeHumanIntent({ evidence, branch, stagedFiles }) {
69
+ const safeEvidence = (evidence && typeof evidence === 'object') ? evidence : {};
70
+ const safeBranch = branch || safeEvidence.current_context?.current_branch || 'unknown';
71
+ const staged = Array.isArray(stagedFiles) ? stagedFiles : [];
72
+
73
+ const detectedPlatforms = ['ios', 'android', 'backend', 'frontend']
74
+ .filter(p => safeEvidence.platforms && safeEvidence.platforms[p] && safeEvidence.platforms[p].detected);
75
+
76
+ const gateStatus = safeEvidence.ai_gate?.status || safeEvidence.severity_metrics?.gate_status || 'unknown';
77
+
78
+ const branchLower = String(safeBranch).toLowerCase();
79
+ const hasIosTouch = staged.some(f => String(f).toLowerCase().endsWith('.swift')) || detectedPlatforms.includes('ios');
80
+ const hasAndroidTouch = staged.some(f => /\.(kt|kts|java)$/i.test(String(f))) || detectedPlatforms.includes('android');
81
+ const hasBackendTouch = staged.some(f => String(f).toLowerCase().includes('backend') || String(f).toLowerCase().includes('services/')) || detectedPlatforms.includes('backend');
82
+ const hasFrontendTouch = staged.some(f => String(f).toLowerCase().includes('frontend') || /\.(tsx?|jsx?)$/i.test(String(f))) || detectedPlatforms.includes('frontend');
83
+
84
+ const platforms = [
85
+ hasIosTouch ? 'ios' : null,
86
+ hasAndroidTouch ? 'android' : null,
87
+ hasBackendTouch ? 'backend' : null,
88
+ hasFrontendTouch ? 'frontend' : null
89
+ ].filter(Boolean);
90
+
91
+ const platformLabel = platforms.length > 0 ? platforms.join('+') : (detectedPlatforms.length > 0 ? detectedPlatforms.join('+') : 'repo');
92
+
93
+ let primaryGoal = `Continue work on ${platformLabel} changes`;
94
+ if (gateStatus === 'BLOCKED') {
95
+ primaryGoal = `Unblock AI gate by fixing ${platformLabel} violations`;
96
+ }
97
+
98
+ if (branchLower.startsWith('fix/') || branchLower.startsWith('bugfix/') || branchLower.startsWith('hotfix/')) {
99
+ primaryGoal = gateStatus === 'BLOCKED'
100
+ ? `Unblock AI gate by fixing ${platformLabel} violations (bugfix)`
101
+ : `Fix ${platformLabel} issues on ${safeBranch}`;
102
+ }
103
+
104
+ const secondary = [];
105
+ if (gateStatus === 'BLOCKED') {
106
+ secondary.push('Fix HIGH/CRITICAL violations first');
107
+ }
108
+ if (platforms.includes('ios')) {
109
+ secondary.push('Keep tests compliant (makeSUT + trackForMemoryLeaks)');
110
+ }
111
+
112
+ const constraints = [];
113
+ constraints.push('Do not bypass hooks (--no-verify)');
114
+ constraints.push('Follow platform rules (rules*.mdc)');
115
+
116
+ const confidence = platforms.length > 0 || detectedPlatforms.length > 0 ? 'medium' : 'low';
117
+
118
+ return {
119
+ primary_goal: primaryGoal,
120
+ secondary_goals: secondary,
121
+ non_goals: [],
122
+ constraints,
123
+ confidence_level: confidence,
124
+ derived_from: {
125
+ branch: safeBranch,
126
+ staged_files_count: staged.length,
127
+ platforms: platforms.length > 0 ? platforms : detectedPlatforms,
128
+ gate_status: gateStatus
129
+ }
130
+ };
131
+ }
132
+
50
133
  function buildHealthSnapshot() {
51
134
  const repoRoot = resolveRepoRoot();
52
135
  const result = {
@@ -136,7 +219,55 @@ function buildHealthSnapshot() {
136
219
 
137
220
  const commands = {
138
221
  audit: () => {
139
- execSync(`bash ${path.join(HOOKS_ROOT, 'presentation/cli/audit.sh')}`, { stdio: 'inherit' });
222
+ try {
223
+ execSync(`bash ${path.join(HOOKS_ROOT, 'presentation/cli/audit.sh')}`, { stdio: 'inherit' });
224
+ } catch (err) {
225
+ const status = (err && typeof err.status === 'number') ? err.status : 1;
226
+ process.exit(status);
227
+ }
228
+ },
229
+
230
+ 'wrap-up': () => {
231
+ commands['evidence:full-update']();
232
+ try {
233
+ const repoRoot = resolveRepoRoot();
234
+ const evidencePath = path.join(repoRoot, '.AI_EVIDENCE.json');
235
+ if (!fs.existsSync(evidencePath)) {
236
+ return;
237
+ }
238
+
239
+ let evidence = {};
240
+ try {
241
+ evidence = JSON.parse(fs.readFileSync(evidencePath, 'utf8'));
242
+ } catch {
243
+ if (process.env.DEBUG) {
244
+ process.stderr.write('[wrap-up] Failed to parse .AI_EVIDENCE.json\n');
245
+ }
246
+ return;
247
+ }
248
+
249
+ const branch = getCurrentBranchSafe();
250
+ const stagedFiles = getStagedFilesSafe();
251
+ const proposed = proposeHumanIntent({ evidence, branch, stagedFiles });
252
+
253
+ console.log('\n💡 Suggested Human Intent (proposal only):');
254
+ console.log(` Primary Goal: ${proposed.primary_goal}`);
255
+ console.log(` Secondary: ${(proposed.secondary_goals || []).join(', ') || '(none)'}`);
256
+ console.log(` Constraints: ${(proposed.constraints || []).join(', ') || '(none)'}`);
257
+ console.log(` Confidence: ${proposed.confidence_level || 'unset'}`);
258
+ console.log(` Branch: ${(proposed.derived_from && proposed.derived_from.branch) || '(unknown)'}`);
259
+ console.log(` Gate: ${(proposed.derived_from && proposed.derived_from.gate_status) || '(unknown)'}`);
260
+
261
+ const suggestedCmd = `ast-hooks intent set --goal="${proposed.primary_goal}" --confidence=${proposed.confidence_level || 'medium'} --expires=24h`;
262
+ console.log('\n✅ To apply it, run:');
263
+ console.log(` ${suggestedCmd}`);
264
+ console.log('');
265
+ } catch (error) {
266
+ if (process.env.DEBUG) {
267
+ process.stderr.write(`[wrap-up] Intent suggestion failed: ${error && error.message ? error.message : String(error)}\n`);
268
+ }
269
+ // Best-effort: wrap-up should succeed even if suggestion fails
270
+ }
140
271
  },
141
272
 
142
273
  'evidence:update': () => {
@@ -326,6 +457,38 @@ const commands = {
326
457
  return;
327
458
  }
328
459
 
460
+ if (subcommand === 'suggest') {
461
+ if (!fs.existsSync(evidencePath)) {
462
+ console.log('❌ No .AI_EVIDENCE.json found');
463
+ process.exit(1);
464
+ }
465
+
466
+ let evidence = {};
467
+ try {
468
+ evidence = JSON.parse(fs.readFileSync(evidencePath, 'utf8'));
469
+ } catch (e) {
470
+ console.log(`❌ Failed to read .AI_EVIDENCE.json: ${e.message}`);
471
+ process.exit(1);
472
+ }
473
+
474
+ const branch = getCurrentBranchSafe();
475
+ const stagedFiles = getStagedFilesSafe();
476
+ const proposed = proposeHumanIntent({ evidence, branch, stagedFiles });
477
+
478
+ console.log('\n💡 Suggested Human Intent (proposal only):');
479
+ console.log(` Primary Goal: ${proposed.primary_goal}`);
480
+ console.log(` Secondary: ${(proposed.secondary_goals || []).join(', ') || '(none)'}`);
481
+ console.log(` Constraints: ${(proposed.constraints || []).join(', ') || '(none)'}`);
482
+ console.log(` Confidence: ${proposed.confidence_level || 'unset'}`);
483
+ console.log(` Branch: ${(proposed.derived_from && proposed.derived_from.branch) || '(unknown)'}`);
484
+ console.log(` Gate: ${(proposed.derived_from && proposed.derived_from.gate_status) || '(unknown)'}`);
485
+
486
+ const suggestedCmd = `ast-hooks intent set --goal="${proposed.primary_goal}" --confidence=${proposed.confidence_level || 'medium'} --expires=24h`;
487
+ console.log('\n✅ To apply it, run:');
488
+ console.log(` ${suggestedCmd}`);
489
+ console.log('');
490
+ return;
491
+ }
329
492
  if (subcommand === 'clear') {
330
493
  if (!fs.existsSync(evidencePath)) {
331
494
  console.log('❌ No .AI_EVIDENCE.json found');
@@ -350,7 +513,7 @@ const commands = {
350
513
  return;
351
514
  }
352
515
 
353
- console.log('❌ Unknown subcommand. Use: show, set, clear');
516
+ console.log('❌ Unknown subcommand. Use: show, suggest, set, clear');
354
517
  process.exit(1);
355
518
  },
356
519
 
@@ -363,24 +526,27 @@ Usage:
363
526
 
364
527
  Commands:
365
528
  audit Run interactive audit menu
529
+ wrap-up End of day: refresh evidence + propose human intent (no writes)
366
530
  ast Run AST Intelligence analysis only
367
531
  install Install hooks in new project
368
532
  verify-policy Verify --no-verify policy compliance
369
533
  progress Show violation progress report
370
534
  health Show hook-system health snapshot (JSON)
371
535
  gitflow Check Git Flow compliance (check|reset)
372
- intent Manage human intent (show|set|clear)
536
+ intent Manage human intent (show|suggest|set|clear)
373
537
  help Show this help message
374
538
  version Show version
375
539
 
376
540
  Examples:
377
541
  ast-hooks audit
542
+ ast-hooks wrap-up
378
543
  ast-hooks ast
379
544
  ast-hooks install
380
545
  ast-hooks verify-policy
381
546
  ast-hooks progress
382
547
  ast-hooks health
383
548
  ast-hooks intent show
549
+ ast-hooks intent suggest
384
550
  ast-hooks intent set --goal="Implement feature X" --expires=24h
385
551
  ast-hooks intent clear
386
552
 
@@ -408,9 +574,12 @@ Documentation:
408
574
  }
409
575
  };
410
576
 
411
- if (!command || !commands[command]) {
412
- commands.help();
413
- process.exit(command ? 1 : 0);
577
+ if (require.main === module) {
578
+ if (!command || !commands[command]) {
579
+ commands.help();
580
+ process.exit(command ? 1 : 0);
581
+ }
582
+ commands[command]();
414
583
  }
415
584
 
416
- commands[command]();
585
+ module.exports = { commands };
@@ -26,3 +26,11 @@ fi
26
26
 
27
27
  AUTO_EVIDENCE_TRIGGER="$AUTO_TRIGGER" AUTO_EVIDENCE_REASON="$AUTO_REASON" AUTO_EVIDENCE_SUMMARY="$AUTO_SUMMARY" \
28
28
  node "$CLI" evidence:full-update
29
+
30
+ EXIT_CODE=$?
31
+ if [[ "$EXIT_CODE" -ne 0 ]]; then
32
+ echo " Evidence updated but gate reported violations (exit code: $EXIT_CODE)." >&2
33
+ exit 0
34
+ fi
35
+
36
+ exit 0