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.
- package/docs/RELEASE_NOTES.md +35 -0
- package/docs/VIOLATIONS_RESOLUTION_PLAN.md +64 -60
- package/package.json +9 -3
- package/scripts/hooks-system/.AI_TOKEN_STATUS.txt +1 -1
- package/scripts/hooks-system/.audit-reports/notifications.log +935 -0
- package/scripts/hooks-system/.audit-reports/token-monitor.log +2809 -0
- package/scripts/hooks-system/application/CompositionRoot.js +38 -22
- package/scripts/hooks-system/application/services/DynamicRulesLoader.js +2 -1
- package/scripts/hooks-system/application/services/GitTreeState.js +2 -1
- package/scripts/hooks-system/application/services/PlaybookRunner.js +1 -1
- package/scripts/hooks-system/application/services/RealtimeGuardService.js +71 -14
- package/scripts/hooks-system/application/services/guard/GuardAutoManagerService.js +31 -2
- package/scripts/hooks-system/application/services/guard/GuardConfig.js +17 -9
- package/scripts/hooks-system/application/services/guard/GuardHeartbeatMonitor.js +6 -9
- package/scripts/hooks-system/application/services/guard/GuardProcessManager.js +23 -0
- package/scripts/hooks-system/application/services/installation/GitEnvironmentService.js +1 -1
- package/scripts/hooks-system/application/services/installation/HookInstaller.js +62 -5
- package/scripts/hooks-system/application/services/installation/McpConfigurator.js +2 -1
- package/scripts/hooks-system/application/services/logging/AuditLogger.js +0 -4
- package/scripts/hooks-system/application/services/logging/UnifiedLogger.js +13 -4
- package/scripts/hooks-system/application/services/monitoring/EvidenceMonitorService.js +4 -3
- package/scripts/hooks-system/application/services/token/TokenMetricsService.js +2 -1
- package/scripts/hooks-system/bin/cli.js +15 -1
- package/scripts/hooks-system/bin/guard-env.sh +18 -38
- package/scripts/hooks-system/bin/guard-supervisor.js +5 -515
- package/scripts/hooks-system/bin/session-loader.sh +3 -262
- package/scripts/hooks-system/bin/start-guards.sh +21 -184
- package/scripts/hooks-system/bin/update-evidence.sh +10 -1161
- package/scripts/hooks-system/config/project.config.json +1 -1
- package/scripts/hooks-system/domain/events/index.js +32 -6
- package/scripts/hooks-system/domain/exceptions/index.js +87 -0
- package/scripts/hooks-system/infrastructure/ast/android/analyzers/AndroidAnalysisOrchestrator.js +3 -2
- package/scripts/hooks-system/infrastructure/ast/ast-core.js +12 -20
- package/scripts/hooks-system/infrastructure/ast/ast-intelligence.js +8 -18
- package/scripts/hooks-system/infrastructure/ast/backend/analyzers/BackendPatternDetector.js +2 -1
- package/scripts/hooks-system/infrastructure/ast/backend/ast-backend.js +10 -8
- package/scripts/hooks-system/infrastructure/ast/frontend/ast-frontend.js +196 -196
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSASTIntelligentAnalyzer.js +3 -2
- package/scripts/hooks-system/infrastructure/config/config.js +5 -0
- package/scripts/hooks-system/infrastructure/hooks/skill-activation-prompt.js +3 -2
- package/scripts/hooks-system/infrastructure/logging/UnifiedLoggerFactory.js +5 -4
- package/scripts/hooks-system/infrastructure/mcp/ast-intelligence-automation.js +88 -0
- package/scripts/hooks-system/infrastructure/orchestration/intelligent-audit.js +17 -16
- package/scripts/hooks-system/infrastructure/telemetry/metric-scope.js +98 -0
- package/scripts/hooks-system/infrastructure/telemetry/metrics-server.js +3 -2
- 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 = (
|
|
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(
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
221
|
-
|
|
225
|
+
execSync('git show-ref --verify --quiet refs/heads/main', { stdio: 'ignore' });
|
|
226
|
+
return 'main';
|
|
222
227
|
} catch {
|
|
223
|
-
|
|
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(
|
|
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 =
|
|
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 =
|
|
8
|
-
const METRICS_FILE = path.join(process.cwd(),
|
|
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 =
|
|
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 (
|
|
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
|
|
123
|
-
|
|
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 (
|
|
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 [];
|