pumuki-ast-hooks 5.3.19 → 5.3.21

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 (46) hide show
  1. package/docs/RELEASE_NOTES.md +35 -0
  2. package/docs/VIOLATIONS_RESOLUTION_PLAN.md +64 -60
  3. package/package.json +9 -3
  4. package/scripts/hooks-system/.AI_TOKEN_STATUS.txt +1 -1
  5. package/scripts/hooks-system/.audit-reports/notifications.log +935 -0
  6. package/scripts/hooks-system/.audit-reports/token-monitor.log +2809 -0
  7. package/scripts/hooks-system/application/CompositionRoot.js +38 -22
  8. package/scripts/hooks-system/application/services/DynamicRulesLoader.js +2 -1
  9. package/scripts/hooks-system/application/services/GitTreeState.js +2 -1
  10. package/scripts/hooks-system/application/services/PlaybookRunner.js +1 -1
  11. package/scripts/hooks-system/application/services/RealtimeGuardService.js +71 -14
  12. package/scripts/hooks-system/application/services/guard/GuardAutoManagerService.js +31 -2
  13. package/scripts/hooks-system/application/services/guard/GuardConfig.js +17 -9
  14. package/scripts/hooks-system/application/services/guard/GuardHeartbeatMonitor.js +6 -9
  15. package/scripts/hooks-system/application/services/guard/GuardProcessManager.js +23 -0
  16. package/scripts/hooks-system/application/services/installation/GitEnvironmentService.js +1 -1
  17. package/scripts/hooks-system/application/services/installation/HookInstaller.js +62 -5
  18. package/scripts/hooks-system/application/services/installation/McpConfigurator.js +2 -1
  19. package/scripts/hooks-system/application/services/logging/AuditLogger.js +0 -4
  20. package/scripts/hooks-system/application/services/logging/UnifiedLogger.js +13 -4
  21. package/scripts/hooks-system/application/services/monitoring/EvidenceMonitorService.js +4 -3
  22. package/scripts/hooks-system/application/services/token/TokenMetricsService.js +2 -1
  23. package/scripts/hooks-system/bin/cli.js +15 -1
  24. package/scripts/hooks-system/bin/guard-env.sh +18 -38
  25. package/scripts/hooks-system/bin/guard-supervisor.js +5 -515
  26. package/scripts/hooks-system/bin/session-loader.sh +3 -262
  27. package/scripts/hooks-system/bin/start-guards.sh +21 -184
  28. package/scripts/hooks-system/bin/update-evidence.sh +10 -1161
  29. package/scripts/hooks-system/config/project.config.json +1 -1
  30. package/scripts/hooks-system/domain/events/index.js +32 -6
  31. package/scripts/hooks-system/domain/exceptions/index.js +87 -0
  32. package/scripts/hooks-system/infrastructure/ast/android/analyzers/AndroidAnalysisOrchestrator.js +3 -2
  33. package/scripts/hooks-system/infrastructure/ast/ast-core.js +12 -20
  34. package/scripts/hooks-system/infrastructure/ast/ast-intelligence.js +8 -18
  35. package/scripts/hooks-system/infrastructure/ast/backend/analyzers/BackendPatternDetector.js +2 -1
  36. package/scripts/hooks-system/infrastructure/ast/backend/ast-backend.js +10 -8
  37. package/scripts/hooks-system/infrastructure/ast/frontend/ast-frontend.js +196 -196
  38. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSASTIntelligentAnalyzer.js +3 -2
  39. package/scripts/hooks-system/infrastructure/config/config.js +5 -0
  40. package/scripts/hooks-system/infrastructure/hooks/skill-activation-prompt.js +3 -2
  41. package/scripts/hooks-system/infrastructure/logging/UnifiedLoggerFactory.js +5 -4
  42. package/scripts/hooks-system/infrastructure/mcp/ast-intelligence-automation.js +88 -0
  43. package/scripts/hooks-system/infrastructure/orchestration/intelligent-audit.js +17 -16
  44. package/scripts/hooks-system/infrastructure/telemetry/metric-scope.js +98 -0
  45. package/scripts/hooks-system/infrastructure/telemetry/metrics-server.js +3 -2
  46. package/scripts/hooks-system/infrastructure/validators/enforce-english-literals.js +6 -8
@@ -53,6 +53,94 @@ function resolveRepoRoot() {
53
53
 
54
54
  const REPO_ROOT = resolveRepoRoot();
55
55
 
56
+ const MCP_LOCK_DIR = path.join(REPO_ROOT, '.audit_tmp', 'mcp-singleton.lock');
57
+ const MCP_LOCK_PID = path.join(MCP_LOCK_DIR, 'pid');
58
+
59
+ function isPidRunning(pid) {
60
+ if (!pid || !Number.isFinite(pid) || pid <= 0) return false;
61
+ try {
62
+ process.kill(pid, 0);
63
+ return true;
64
+ } catch {
65
+ return false;
66
+ }
67
+ }
68
+
69
+ function safeReadPid(filePath) {
70
+ try {
71
+ if (!fs.existsSync(filePath)) return null;
72
+ const raw = String(fs.readFileSync(filePath, 'utf8') || '').trim();
73
+ const pid = Number(raw);
74
+ if (!Number.isFinite(pid) || pid <= 0) return null;
75
+ return pid;
76
+ } catch {
77
+ return null;
78
+ }
79
+ }
80
+
81
+ function removeLockDir() {
82
+ try {
83
+ if (fs.existsSync(MCP_LOCK_PID)) {
84
+ fs.unlinkSync(MCP_LOCK_PID);
85
+ }
86
+ } catch {
87
+ // ignore
88
+ }
89
+ try {
90
+ if (fs.existsSync(MCP_LOCK_DIR)) {
91
+ fs.rmdirSync(MCP_LOCK_DIR);
92
+ }
93
+ } catch {
94
+ // ignore
95
+ }
96
+ }
97
+
98
+ function acquireSingletonLock() {
99
+ try {
100
+ fs.mkdirSync(path.join(REPO_ROOT, '.audit_tmp'), { recursive: true });
101
+ } catch {
102
+ // ignore
103
+ }
104
+
105
+ try {
106
+ fs.mkdirSync(MCP_LOCK_DIR);
107
+ } catch (error) {
108
+ const existingPid = safeReadPid(MCP_LOCK_PID);
109
+ if (existingPid && isPidRunning(existingPid)) {
110
+ process.stderr.write(`[MCP] Another instance is already running (pid ${existingPid}). Exiting.\n`);
111
+ process.exit(0);
112
+ }
113
+
114
+ removeLockDir();
115
+ fs.mkdirSync(MCP_LOCK_DIR);
116
+ }
117
+
118
+ try {
119
+ fs.writeFileSync(MCP_LOCK_PID, String(process.pid), { encoding: 'utf8' });
120
+ } catch {
121
+ // ignore
122
+ }
123
+
124
+ const cleanup = () => {
125
+ const pid = safeReadPid(MCP_LOCK_PID);
126
+ if (pid === process.pid) {
127
+ removeLockDir();
128
+ }
129
+ };
130
+
131
+ process.on('exit', cleanup);
132
+ process.on('SIGINT', () => {
133
+ cleanup();
134
+ process.exit(0);
135
+ });
136
+ process.on('SIGTERM', () => {
137
+ cleanup();
138
+ process.exit(0);
139
+ });
140
+ }
141
+
142
+ acquireSingletonLock();
143
+
56
144
  // Lazy-loaded CompositionRoot - only initialized when first needed
57
145
  let _compositionRoot = null;
58
146
  function getCompositionRoot() {
@@ -8,9 +8,10 @@ const { TokenManager } = require('../utils/token-manager');
8
8
  const { toErrorMessage } = require('../utils/error-utils');
9
9
  const fs = require('fs');
10
10
  const path = require('path');
11
+ const env = require('../../config/env');
11
12
 
12
13
  function resolveAuditTmpDir() {
13
- const configured = (process.env.AUDIT_TMP || '').trim();
14
+ const configured = (env.get('AUDIT_TMP', '') || '').trim();
14
15
  if (configured.length > 0) {
15
16
  return path.isAbsolute(configured) ? configured : path.join(process.cwd(), configured);
16
17
  }
@@ -28,7 +29,7 @@ async function runIntelligentAudit() {
28
29
  const rawViolations = loadRawViolations();
29
30
  console.log(`[Intelligent Audit] Loaded ${rawViolations.length} violations from AST`);
30
31
 
31
- const gateScope = String(process.env.AI_GATE_SCOPE || 'staging').trim().toLowerCase();
32
+ const gateScope = String(env.get('AI_GATE_SCOPE', 'staging') || 'staging').trim().toLowerCase();
32
33
  const isRepoScope = gateScope === 'repo' || gateScope === 'repository';
33
34
 
34
35
  let violationsForGate = [];
@@ -212,21 +213,21 @@ function updateAIEvidence(violations, gateResult, tokenUsage) {
212
213
  const currentBranch = execSync('git branch --show-current', { encoding: 'utf8' }).trim();
213
214
 
214
215
  const resolveBaseBranch = () => {
215
- const configured = process.env.AST_BASE_BRANCH;
216
- if (configured && configured.trim().length > 0) {
217
- return configured.trim();
218
- }
216
+ const configured = env.get('AST_BASE_BRANCH', '');
217
+ if (configured && configured.trim().length > 0) {
218
+ return configured.trim();
219
+ }
220
+ try {
221
+ execSync('git show-ref --verify --quiet refs/heads/develop', { stdio: 'ignore' });
222
+ return 'develop';
223
+ } catch {
219
224
  try {
220
- execSync('git show-ref --verify --quiet refs/heads/develop', { stdio: 'ignore' });
221
- return 'develop';
225
+ execSync('git show-ref --verify --quiet refs/heads/main', { stdio: 'ignore' });
226
+ return 'main';
222
227
  } catch {
223
- try {
224
- execSync('git show-ref --verify --quiet refs/heads/main', { stdio: 'ignore' });
225
- return 'main';
226
- } catch {
227
- return 'main';
228
- }
228
+ return 'main';
229
229
  }
230
+ }
230
231
  };
231
232
  const baseBranch = resolveBaseBranch();
232
233
  const isProtected = ['main', 'master', baseBranch].includes(currentBranch);
@@ -234,12 +235,12 @@ function updateAIEvidence(violations, gateResult, tokenUsage) {
234
235
  const highViolations = violations.filter(v => v.severity === 'HIGH');
235
236
  const blockingViolations = [...criticalViolations, ...highViolations].slice(0, 50);
236
237
 
237
- const gateScope = String(process.env.AI_GATE_SCOPE || 'staging').trim().toLowerCase();
238
+ const gateScope = String(env.get('AI_GATE_SCOPE', 'staging') || 'staging').trim().toLowerCase();
238
239
 
239
240
  const existingGate = evidence.ai_gate && typeof evidence.ai_gate === 'object' ? evidence.ai_gate : null;
240
241
  let preserveExistingRepoGate = false;
241
242
  if (gateScope !== 'repo' && gateScope !== 'repository' && existingGate && existingGate.scope === 'repo' && existingGate.status === 'BLOCKED') {
242
- const preserveMs = Number(process.env.AI_GATE_REPO_PRESERVE_MS || 600000);
243
+ const preserveMs = env.getNumber('AI_GATE_REPO_PRESERVE_MS', 600000);
243
244
  const lastCheckMs = Date.parse(existingGate.last_check || '');
244
245
  if (!Number.isNaN(preserveMs) && preserveMs > 0 && !Number.isNaN(lastCheckMs)) {
245
246
  const ageMs = Date.now() - lastCheckMs;
@@ -0,0 +1,98 @@
1
+ const { recordMetric } = require('./metrics-logger');
2
+
3
+ function truncateString(value, maxLen) {
4
+ if (typeof value !== 'string') {
5
+ return value;
6
+ }
7
+ if (!Number.isFinite(maxLen) || maxLen <= 0) {
8
+ return value;
9
+ }
10
+ if (value.length <= maxLen) {
11
+ return value;
12
+ }
13
+ return value.substring(0, maxLen);
14
+ }
15
+
16
+ function sanitizeMeta(meta, { maxStringLength = 120 } = {}) {
17
+ if (!meta || typeof meta !== 'object') {
18
+ return {};
19
+ }
20
+
21
+ const out = {};
22
+ for (const [k, v] of Object.entries(meta)) {
23
+ if (v == null) {
24
+ continue;
25
+ }
26
+ if (typeof v === 'string') {
27
+ out[k] = truncateString(v, maxStringLength);
28
+ continue;
29
+ }
30
+ if (typeof v === 'number' || typeof v === 'boolean') {
31
+ out[k] = v;
32
+ continue;
33
+ }
34
+ if (v instanceof Error) {
35
+ out[k] = truncateString(v.message, maxStringLength);
36
+ continue;
37
+ }
38
+
39
+ try {
40
+ out[k] = JSON.parse(JSON.stringify(v));
41
+ } catch {
42
+ out[k] = truncateString(String(v), maxStringLength);
43
+ }
44
+ }
45
+ return out;
46
+ }
47
+
48
+ function toErrorMeta(error, { maxStringLength = 160 } = {}) {
49
+ if (!error) {
50
+ return {};
51
+ }
52
+
53
+ if (typeof error === 'string') {
54
+ return { error: truncateString(error, maxStringLength) };
55
+ }
56
+
57
+ const err = error instanceof Error ? error : null;
58
+ if (!err) {
59
+ return { error: truncateString(String(error), maxStringLength) };
60
+ }
61
+
62
+ return {
63
+ error: truncateString(err.message, maxStringLength),
64
+ errorName: truncateString(err.name, 80)
65
+ };
66
+ }
67
+
68
+ function createMetricScope({ hook, operation, baseMeta = {}, options = {} } = {}) {
69
+ const base = sanitizeMeta({ ...baseMeta }, options);
70
+
71
+ const startedAt = Date.now();
72
+
73
+ function emit(status, meta = {}) {
74
+ recordMetric({
75
+ hook,
76
+ operation,
77
+ status,
78
+ ...base,
79
+ ...sanitizeMeta(meta, options)
80
+ });
81
+ }
82
+
83
+ return {
84
+ started(meta = {}) {
85
+ emit('started', meta);
86
+ },
87
+ success(meta = {}) {
88
+ emit('success', { durationMs: Date.now() - startedAt, ...meta });
89
+ },
90
+ failed(error, meta = {}) {
91
+ emit('failed', { durationMs: Date.now() - startedAt, ...toErrorMeta(error, options), ...meta });
92
+ }
93
+ };
94
+ }
95
+
96
+ module.exports = {
97
+ createMetricScope
98
+ };
@@ -3,9 +3,10 @@
3
3
  const http = require('http');
4
4
  const fs = require('fs');
5
5
  const path = require('path');
6
+ const env = require('../../config/env');
6
7
 
7
- const PORT = Number(process.env.HOOK_METRICS_PORT || 9464);
8
- const METRICS_FILE = path.join(process.cwd(), '.audit_tmp', 'hook-metrics.jsonl');
8
+ const PORT = env.getNumber('HOOK_METRICS_PORT', 9464);
9
+ const METRICS_FILE = path.join(process.cwd(), env.get('HOOK_METRICS_FILE', '.audit_tmp/hook-metrics.jsonl'));
9
10
 
10
11
  function loadMetrics() {
11
12
  if (!fs.existsSync(METRICS_FILE)) return [];
@@ -4,8 +4,9 @@
4
4
  const fs = require('fs');
5
5
  const path = require('path');
6
6
  const { execSync } = require('child_process');
7
+ const env = require('../../config/env');
7
8
 
8
- const REPO_ROOT = process.env.HOOK_GUARD_REPO_ROOT || process.cwd();
9
+ const REPO_ROOT = env.get('HOOK_GUARD_REPO_ROOT', process.cwd());
9
10
  const CONFIG_PATH = path.join(REPO_ROOT, 'scripts', 'hooks-system', 'config', 'language-guard.json');
10
11
  const DEFAULT_IGNORED_SEGMENTS = [
11
12
  `${path.sep}node_modules${path.sep}`,
@@ -22,7 +23,7 @@ function decodeUnicode(value) {
22
23
  try {
23
24
  return JSON.parse(`"${value}"`);
24
25
  } catch (error) {
25
- if (process.env.NODE_ENV === 'development' || process.env.DEBUG) {
26
+ if (env.isDev || env.getBool('DEBUG', false)) {
26
27
  console.debug(`[enforce-english-literals] Failed to decode Unicode value "${value}": ${error.message}`);
27
28
  }
28
29
  return value;
@@ -119,13 +120,10 @@ function analyzeFile(relativePath, config) {
119
120
 
120
121
  function collectStagedFiles() {
121
122
  try {
122
- const raw = execSync('git diff --cached --name-only --diff-filter=ACMR', {
123
- cwd: REPO_ROOT,
124
- encoding: 'utf8'
125
- });
126
- return raw.split('\n').map(entry => entry.trim()).filter(Boolean);
123
+ const stagedFilesRaw = execSync('git diff --cached --name-only', { encoding: 'utf8' });
124
+ return stagedFilesRaw.split('\n').filter(Boolean).map(file => file.trim());
127
125
  } catch (error) {
128
- if (process.env.NODE_ENV === 'development' || process.env.DEBUG) {
126
+ if (env.isDev || env.getBool('DEBUG', false)) {
129
127
  console.debug(`[enforce-english-literals] Failed to collect staged files: ${error.message}`);
130
128
  }
131
129
  return [];