pumuki-ast-hooks 5.3.17 → 5.3.18
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/VIOLATIONS_RESOLUTION_PLAN.md +9 -9
- package/docs/alerting-system.md +51 -0
- package/docs/observability.md +36 -0
- package/docs/type-safety.md +8 -0
- package/package.json +1 -1
- package/scripts/hooks-system/.AI_TOKEN_STATUS.txt +1 -1
- package/scripts/hooks-system/.audit-reports/notifications.log +11 -0
- package/scripts/hooks-system/.audit-reports/token-monitor.log +72 -0
- package/scripts/hooks-system/application/CompositionRoot.js +73 -24
- package/scripts/hooks-system/application/services/DynamicRulesLoader.js +2 -1
- package/scripts/hooks-system/application/services/RealtimeGuardService.js +85 -15
- 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 +29 -0
- package/scripts/hooks-system/application/services/installation/GitEnvironmentService.js +3 -0
- package/scripts/hooks-system/application/services/installation/McpConfigurator.js +2 -1
- package/scripts/hooks-system/application/services/logging/AuditLogger.js +88 -0
- package/scripts/hooks-system/application/services/logging/UnifiedLogger.js +13 -4
- package/scripts/hooks-system/application/services/monitoring/EvidenceMonitorService.js +7 -3
- package/scripts/hooks-system/application/services/token/TokenMetricsService.js +14 -1
- package/scripts/hooks-system/config/env.js +33 -0
- package/scripts/hooks-system/domain/events/__tests__/EventBus.spec.js +33 -0
- package/scripts/hooks-system/domain/events/index.js +16 -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/hooks/skill-activation-prompt.js +3 -2
- package/scripts/hooks-system/infrastructure/logging/UnifiedLoggerFactory.js +35 -5
- package/scripts/hooks-system/infrastructure/orchestration/intelligent-audit.js +86 -16
- package/scripts/hooks-system/infrastructure/telemetry/metrics-server.js +51 -2
- package/scripts/hooks-system/infrastructure/validators/enforce-english-literals.js +6 -8
|
@@ -57,8 +57,8 @@ function runFrontendIntelligence(project, findings, platform) {
|
|
|
57
57
|
return 0.6745 * (x - med) / madValue;
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
-
const pOutlier =
|
|
61
|
-
const pExtreme =
|
|
60
|
+
const pOutlier = env.getNumber('AST_GODCLASS_P_OUTLIER', 90);
|
|
61
|
+
const pExtreme = env.getNumber('AST_GODCLASS_P_EXTREME', 97);
|
|
62
62
|
|
|
63
63
|
const componentPattern = /^(export\s+)?(?:const|function)\s+([A-Z]\w+)\s*[=:].*(?:React\.FC|JSX\.Element|\(\)\s*=>|function)/gm;
|
|
64
64
|
const metrics = [];
|
|
@@ -1129,10 +1129,10 @@ function runFrontendIntelligence(project, findings, platform) {
|
|
|
1129
1129
|
}
|
|
1130
1130
|
if (/page\.tsx$/.test(filePath)) {
|
|
1131
1131
|
const candidate = filePath.replace(/\/app\//, '/e2e/').replace(/\.tsx$/, '.spec.ts');
|
|
1132
|
-
try {
|
|
1133
|
-
if (!fs.existsSync(candidate)) {
|
|
1134
|
-
pushFinding("frontend.testing.missing_e2e", "info", sf, sf, "No E2E spec found for this page (heuristic)", findings);
|
|
1135
|
-
}
|
|
1132
|
+
try {
|
|
1133
|
+
if (!fs.existsSync(candidate)) {
|
|
1134
|
+
pushFinding("frontend.testing.missing_e2e", "info", sf, sf, "No E2E spec found for this page (heuristic)", findings);
|
|
1135
|
+
}
|
|
1136
1136
|
} catch (error) {
|
|
1137
1137
|
if (process.env.DEBUG) {
|
|
1138
1138
|
console.debug(`[frontend-ast] Failed to check E2E spec file: ${error.message}`);
|
|
@@ -1181,14 +1181,14 @@ function runFrontendIntelligence(project, findings, platform) {
|
|
|
1181
1181
|
const extreme = totalComplexityZ >= godComponentBaseline.thresholds.extreme.totalComplexityZ || bodyLinesZ >= godComponentBaseline.thresholds.extreme.bodyLinesZ;
|
|
1182
1182
|
|
|
1183
1183
|
if (extreme || (complexityOutlier && sizeOutlier)) {
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1184
|
+
pushFinding(
|
|
1185
|
+
"frontend.solid.srp_god_component",
|
|
1186
|
+
"critical",
|
|
1187
|
+
sf,
|
|
1188
|
+
sf,
|
|
1189
|
+
`Component '${componentName}' has ${hookCount} hooks + ${functionCount} functions = ${totalComplexity} (z=${totalComplexityZ.toFixed(2)}), ${bodyLines} lines (z=${bodyLinesZ.toFixed(2)}) - split responsibilities (SRP)`,
|
|
1190
|
+
findings
|
|
1191
|
+
);
|
|
1192
1192
|
}
|
|
1193
1193
|
}
|
|
1194
1194
|
});
|
|
@@ -1823,269 +1823,269 @@ function runFrontendIntelligence(project, findings, platform) {
|
|
|
1823
1823
|
}
|
|
1824
1824
|
|
|
1825
1825
|
const useStateCallsInComponent = sf.getDescendantsOfKind(SyntaxKind.CallExpression)
|
|
1826
|
-
|
|
1826
|
+
.filter(call => call.getExpression().getText() === 'useState');
|
|
1827
|
+
|
|
1828
|
+
sf.getDescendantsOfKind(SyntaxKind.FunctionDeclaration).forEach(fn => {
|
|
1829
|
+
const name = fn.getName();
|
|
1830
|
+
if (name && /^[A-Z]/.test(name)) {
|
|
1831
|
+
const useStatesInFn = useStateCallsInComponent.filter(call =>
|
|
1832
|
+
fn.getDescendants().includes(call)
|
|
1833
|
+
);
|
|
1834
|
+
|
|
1835
|
+
if (useStatesInFn.length >= 4) {
|
|
1836
|
+
pushFinding(
|
|
1837
|
+
"frontend.hooks.useState_overuse",
|
|
1838
|
+
"medium",
|
|
1839
|
+
sf,
|
|
1840
|
+
fn,
|
|
1841
|
+
`Component ${name} has ${useStatesInFn.length}+ useState. Consider useReducer for complex state. Benefits: Single state object, predictable updates, easier testing.`,
|
|
1842
|
+
findings
|
|
1843
|
+
);
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
});
|
|
1847
|
+
|
|
1848
|
+
const contextCreations = sf.getDescendantsOfKind(SyntaxKind.CallExpression)
|
|
1849
|
+
.filter(call => call.getExpression().getText() === 'createContext');
|
|
1827
1850
|
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1851
|
+
if (contextCreations.length >= 3 && !sf.getFilePath().includes('context')) {
|
|
1852
|
+
pushFinding(
|
|
1853
|
+
"frontend.state.context_overuse",
|
|
1854
|
+
"medium",
|
|
1855
|
+
sf,
|
|
1856
|
+
sf,
|
|
1857
|
+
`Multiple Context creations (${contextCreations.length}). Consider Zustand for global state. Context rerenders all consumers. Zustand: Selective subscriptions, better performance.`,
|
|
1858
|
+
findings
|
|
1833
1859
|
);
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
sf.getDescendantsOfKind(SyntaxKind.FunctionDeclaration).forEach(fn => {
|
|
1863
|
+
const name = fn.getName();
|
|
1864
|
+
const body = fn.getBody()?.getText() || '';
|
|
1865
|
+
const hasHookCall = /use[A-Z]/.test(body);
|
|
1834
1866
|
|
|
1835
|
-
if (
|
|
1867
|
+
if (name && hasHookCall && !/^use[A-Z]/.test(name)) {
|
|
1836
1868
|
pushFinding(
|
|
1837
|
-
"frontend.hooks.
|
|
1869
|
+
"frontend.hooks.naming_convention",
|
|
1838
1870
|
"medium",
|
|
1839
1871
|
sf,
|
|
1840
1872
|
fn,
|
|
1841
|
-
`
|
|
1873
|
+
`Function ${name} uses hooks but doesn't start with 'use'. Rename to use${name[0].toUpperCase()}${name.slice(1)}. React ESLint requires 'use' prefix for hook functions.`,
|
|
1842
1874
|
findings
|
|
1843
1875
|
);
|
|
1844
1876
|
}
|
|
1845
|
-
}
|
|
1846
|
-
});
|
|
1847
|
-
|
|
1848
|
-
const contextCreations = sf.getDescendantsOfKind(SyntaxKind.CallExpression)
|
|
1849
|
-
.filter(call => call.getExpression().getText() === 'createContext');
|
|
1850
|
-
|
|
1851
|
-
if (contextCreations.length >= 3 && !sf.getFilePath().includes('context')) {
|
|
1852
|
-
pushFinding(
|
|
1853
|
-
"frontend.state.context_overuse",
|
|
1854
|
-
"medium",
|
|
1855
|
-
sf,
|
|
1856
|
-
sf,
|
|
1857
|
-
`Multiple Context creations (${contextCreations.length}). Consider Zustand for global state. Context rerenders all consumers. Zustand: Selective subscriptions, better performance.`,
|
|
1858
|
-
findings
|
|
1859
|
-
);
|
|
1860
|
-
}
|
|
1877
|
+
});
|
|
1861
1878
|
|
|
1862
|
-
|
|
1863
|
-
const
|
|
1864
|
-
|
|
1865
|
-
const hasHookCall = /use[A-Z]/.test(body);
|
|
1879
|
+
const fullText = sf.getFullText();
|
|
1880
|
+
const hasLargeImports = fullText.includes('import * as') ||
|
|
1881
|
+
fullText.match(/import\s+\{[^}]{200,}\}/);
|
|
1866
1882
|
|
|
1867
|
-
if (
|
|
1883
|
+
if (hasLargeImports) {
|
|
1868
1884
|
pushFinding(
|
|
1869
|
-
"frontend.
|
|
1885
|
+
"frontend.performance.large_imports",
|
|
1870
1886
|
"medium",
|
|
1871
1887
|
sf,
|
|
1872
|
-
|
|
1873
|
-
|
|
1888
|
+
sf,
|
|
1889
|
+
'Large import detected. Use tree-shaking: import { Button } from \'@mui/material/Button\' (not from \'@mui/material\'). Reduces bundle size by importing only needed components.',
|
|
1874
1890
|
findings
|
|
1875
1891
|
);
|
|
1876
1892
|
}
|
|
1877
|
-
});
|
|
1878
1893
|
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
fullText.match(/import\s+\{[^}]{200,}\}/);
|
|
1882
|
-
|
|
1883
|
-
if (hasLargeImports) {
|
|
1884
|
-
pushFinding(
|
|
1885
|
-
"frontend.performance.large_imports",
|
|
1886
|
-
"medium",
|
|
1887
|
-
sf,
|
|
1888
|
-
sf,
|
|
1889
|
-
'Large import detected. Use tree-shaking: import { Button } from \'@mui/material/Button\' (not from \'@mui/material\'). Reduces bundle size by importing only needed components.',
|
|
1890
|
-
findings
|
|
1891
|
-
);
|
|
1892
|
-
}
|
|
1894
|
+
if (content.includes('class') && content.includes('extends Component') &&
|
|
1895
|
+
!content.includes('componentDidCatch') && !content.includes('getDerivedStateFromError')) {
|
|
1893
1896
|
|
|
1894
|
-
|
|
1895
|
-
!content.includes('componentDidCatch') && !content.includes('getDerivedStateFromError')) {
|
|
1897
|
+
const hasChildrenRender = content.includes('render()') && content.includes('children');
|
|
1896
1898
|
|
|
1897
|
-
|
|
1899
|
+
if (hasChildrenRender) {
|
|
1900
|
+
pushFinding(
|
|
1901
|
+
"frontend.error_handling.missing_error_boundary",
|
|
1902
|
+
"medium",
|
|
1903
|
+
sf,
|
|
1904
|
+
sf,
|
|
1905
|
+
'Component renders children without error boundary. Add: componentDidCatch(error, errorInfo) { logError(error); }. Prevents whole app crash from single component error.',
|
|
1906
|
+
findings
|
|
1907
|
+
);
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
const hasRoutes = content.includes('Route') || content.includes('router');
|
|
1912
|
+
const hasLazy = content.includes('React.lazy') || content.includes('dynamic(');
|
|
1898
1913
|
|
|
1899
|
-
if (
|
|
1914
|
+
if (hasRoutes && !hasLazy && sf.getFilePath().includes('app')) {
|
|
1900
1915
|
pushFinding(
|
|
1901
|
-
"frontend.
|
|
1916
|
+
"frontend.performance.missing_lazy_loading",
|
|
1902
1917
|
"medium",
|
|
1903
1918
|
sf,
|
|
1904
1919
|
sf,
|
|
1905
|
-
'
|
|
1920
|
+
'Routes without lazy loading. Use: const Dashboard = lazy(() => import(\'./Dashboard\')). Wrap with <Suspense>. Reduces initial bundle, faster First Contentful Paint.',
|
|
1906
1921
|
findings
|
|
1907
1922
|
);
|
|
1908
1923
|
}
|
|
1909
|
-
}
|
|
1910
1924
|
|
|
1911
|
-
const hasRoutes = content.includes('Route') || content.includes('router');
|
|
1912
|
-
const hasLazy = content.includes('React.lazy') || content.includes('dynamic(');
|
|
1913
|
-
|
|
1914
|
-
if (hasRoutes && !hasLazy && sf.getFilePath().includes('app')) {
|
|
1915
|
-
pushFinding(
|
|
1916
|
-
"frontend.performance.missing_lazy_loading",
|
|
1917
|
-
"medium",
|
|
1918
|
-
sf,
|
|
1919
|
-
sf,
|
|
1920
|
-
'Routes without lazy loading. Use: const Dashboard = lazy(() => import(\'./Dashboard\')). Wrap with <Suspense>. Reduces initial bundle, faster First Contentful Paint.',
|
|
1921
|
-
findings
|
|
1922
|
-
);
|
|
1923
|
-
}
|
|
1924
1925
|
|
|
1926
|
+
if (sf.getFilePath().includes('layout.tsx') || sf.getFilePath().includes('page.tsx')) {
|
|
1927
|
+
const hasMetadata = content.includes('metadata') || content.includes('generateMetadata');
|
|
1928
|
+
|
|
1929
|
+
if (!hasMetadata) {
|
|
1930
|
+
pushFinding(
|
|
1931
|
+
"frontend.seo.missing_metadata",
|
|
1932
|
+
"low",
|
|
1933
|
+
sf,
|
|
1934
|
+
sf,
|
|
1935
|
+
'Missing SEO metadata. Export: export const metadata = { title, description, openGraph }. Improves Google ranking, social media previews.',
|
|
1936
|
+
findings
|
|
1937
|
+
);
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1925
1940
|
|
|
1926
|
-
|
|
1927
|
-
|
|
1941
|
+
if (sf.getFilePath().includes('sitemap')) {
|
|
1942
|
+
const hasDynamicRoutes = content.includes('fetch') || content.includes('database');
|
|
1928
1943
|
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1944
|
+
if (!hasDynamicRoutes && content.length < 200) {
|
|
1945
|
+
pushFinding(
|
|
1946
|
+
"frontend.seo.static_sitemap",
|
|
1947
|
+
"low",
|
|
1948
|
+
sf,
|
|
1949
|
+
sf,
|
|
1950
|
+
'Static sitemap. Generate dynamically: fetch all routes from API/DB. Update automatically when content changes. Better SEO indexing.',
|
|
1951
|
+
findings
|
|
1952
|
+
);
|
|
1953
|
+
}
|
|
1938
1954
|
}
|
|
1939
|
-
}
|
|
1940
1955
|
|
|
1941
|
-
|
|
1942
|
-
|
|
1956
|
+
if (sf.getFilePath().includes('robots')) {
|
|
1957
|
+
const hasDisallow = content.includes('Disallow');
|
|
1943
1958
|
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1959
|
+
if (!hasDisallow) {
|
|
1960
|
+
pushFinding(
|
|
1961
|
+
"frontend.seo.robots_config",
|
|
1962
|
+
"low",
|
|
1963
|
+
sf,
|
|
1964
|
+
sf,
|
|
1965
|
+
'Robots.txt incomplete. Add Disallow rules for: /api, /admin, /_next. Prevents search engines indexing private routes.',
|
|
1966
|
+
findings
|
|
1967
|
+
);
|
|
1968
|
+
}
|
|
1953
1969
|
}
|
|
1954
|
-
}
|
|
1955
1970
|
|
|
1956
|
-
|
|
1957
|
-
|
|
1971
|
+
if (sf.getFilePath().includes('manifest')) {
|
|
1972
|
+
const hasIcons = content.includes('icons');
|
|
1973
|
+
const hasThemeColor = content.includes('theme_color');
|
|
1974
|
+
|
|
1975
|
+
if (!hasIcons || !hasThemeColor) {
|
|
1976
|
+
pushFinding(
|
|
1977
|
+
"frontend.pwa.manifest_incomplete",
|
|
1978
|
+
"low",
|
|
1979
|
+
sf,
|
|
1980
|
+
sf,
|
|
1981
|
+
'PWA manifest incomplete. Add: icons (192x192, 512x512), theme_color, background_color, display: standalone. Enables Add to Home Screen.',
|
|
1982
|
+
findings
|
|
1983
|
+
);
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1958
1986
|
|
|
1959
|
-
if (!
|
|
1987
|
+
if (content.includes('serviceWorker') && !content.includes('unregister')) {
|
|
1960
1988
|
pushFinding(
|
|
1961
|
-
"frontend.
|
|
1989
|
+
"frontend.pwa.missing_sw_unregister",
|
|
1962
1990
|
"low",
|
|
1963
1991
|
sf,
|
|
1964
1992
|
sf,
|
|
1965
|
-
'
|
|
1993
|
+
'Service Worker registration without unregister fallback. Add: if (!production) navigator.serviceWorker.unregister(). Prevents caching issues in development.',
|
|
1966
1994
|
findings
|
|
1967
1995
|
);
|
|
1968
1996
|
}
|
|
1969
|
-
}
|
|
1970
1997
|
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1998
|
+
if (sf.getFilePath().includes('lighthouse') || sf.getFilePath().includes('performance')) {
|
|
1999
|
+
const hasThresholds = content.includes('performance') && content.includes('accessibility');
|
|
2000
|
+
|
|
2001
|
+
if (!hasThresholds) {
|
|
2002
|
+
pushFinding(
|
|
2003
|
+
"frontend.performance.lighthouse_monitoring",
|
|
2004
|
+
"low",
|
|
2005
|
+
sf,
|
|
2006
|
+
sf,
|
|
2007
|
+
'Lighthouse config without thresholds. Set CI thresholds: performance: 90, accessibility: 95, best-practices: 90, seo: 90. Prevents performance regressions.',
|
|
2008
|
+
findings
|
|
2009
|
+
);
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
const hasWebVitals = content.includes('reportWebVitals') || content.includes('web-vitals');
|
|
2014
|
+
const hasAnalytics = content.includes('analytics') || content.includes('gtag');
|
|
1974
2015
|
|
|
1975
|
-
if (
|
|
2016
|
+
if (hasWebVitals && !hasAnalytics) {
|
|
1976
2017
|
pushFinding(
|
|
1977
|
-
"frontend.
|
|
2018
|
+
"frontend.monitoring.web_vitals_no_analytics",
|
|
1978
2019
|
"low",
|
|
1979
2020
|
sf,
|
|
1980
2021
|
sf,
|
|
1981
|
-
'
|
|
2022
|
+
'Web Vitals captured but not sent to analytics. Send to Google Analytics: gtag(\'event\', metric.name, { value: metric.value }). Track LCP, FID, CLS in production.',
|
|
1982
2023
|
findings
|
|
1983
2024
|
);
|
|
1984
2025
|
}
|
|
1985
|
-
}
|
|
1986
|
-
|
|
1987
|
-
if (content.includes('serviceWorker') && !content.includes('unregister')) {
|
|
1988
|
-
pushFinding(
|
|
1989
|
-
"frontend.pwa.missing_sw_unregister",
|
|
1990
|
-
"low",
|
|
1991
|
-
sf,
|
|
1992
|
-
sf,
|
|
1993
|
-
'Service Worker registration without unregister fallback. Add: if (!production) navigator.serviceWorker.unregister(). Prevents caching issues in development.',
|
|
1994
|
-
findings
|
|
1995
|
-
);
|
|
1996
|
-
}
|
|
1997
|
-
|
|
1998
|
-
if (sf.getFilePath().includes('lighthouse') || sf.getFilePath().includes('performance')) {
|
|
1999
|
-
const hasThresholds = content.includes('performance') && content.includes('accessibility');
|
|
2000
2026
|
|
|
2001
|
-
if (!
|
|
2027
|
+
if (sf.getFilePath().includes('layout') && !content.includes('analytics') && !content.includes('gtag')) {
|
|
2002
2028
|
pushFinding(
|
|
2003
|
-
"frontend.
|
|
2029
|
+
"frontend.monitoring.missing_analytics",
|
|
2004
2030
|
"low",
|
|
2005
2031
|
sf,
|
|
2006
2032
|
sf,
|
|
2007
|
-
'
|
|
2033
|
+
'Root layout without analytics. Add Google Analytics: <Script src="https://www.googletagmanager.com/gtag/js" />. Track user behavior, conversion rates.',
|
|
2008
2034
|
findings
|
|
2009
2035
|
);
|
|
2010
2036
|
}
|
|
2011
|
-
}
|
|
2012
2037
|
|
|
2013
|
-
|
|
2014
|
-
const hasAnalytics = content.includes('analytics') || content.includes('gtag');
|
|
2015
|
-
|
|
2016
|
-
if (hasWebVitals && !hasAnalytics) {
|
|
2017
|
-
pushFinding(
|
|
2018
|
-
"frontend.monitoring.web_vitals_no_analytics",
|
|
2019
|
-
"low",
|
|
2020
|
-
sf,
|
|
2021
|
-
sf,
|
|
2022
|
-
'Web Vitals captured but not sent to analytics. Send to Google Analytics: gtag(\'event\', metric.name, { value: metric.value }). Track LCP, FID, CLS in production.',
|
|
2023
|
-
findings
|
|
2024
|
-
);
|
|
2025
|
-
}
|
|
2026
|
-
|
|
2027
|
-
if (sf.getFilePath().includes('layout') && !content.includes('analytics') && !content.includes('gtag')) {
|
|
2028
|
-
pushFinding(
|
|
2029
|
-
"frontend.monitoring.missing_analytics",
|
|
2030
|
-
"low",
|
|
2031
|
-
sf,
|
|
2032
|
-
sf,
|
|
2033
|
-
'Root layout without analytics. Add Google Analytics: <Script src="https://www.googletagmanager.com/gtag/js" />. Track user behavior, conversion rates.',
|
|
2034
|
-
findings
|
|
2035
|
-
);
|
|
2036
|
-
}
|
|
2037
|
-
|
|
2038
|
-
if (sf.getFilePath().includes('error.tsx') && !content.includes('Sentry') && !content.includes('captureException')) {
|
|
2039
|
-
pushFinding(
|
|
2040
|
-
"frontend.monitoring.missing_error_tracking",
|
|
2041
|
-
"low",
|
|
2042
|
-
sf,
|
|
2043
|
-
sf,
|
|
2044
|
-
'Error page without error tracking. Install: npm i @sentry/nextjs. Track production errors: Sentry.captureException(error). Get alerts when users hit errors.',
|
|
2045
|
-
findings
|
|
2046
|
-
);
|
|
2047
|
-
}
|
|
2048
|
-
|
|
2049
|
-
if (content.includes('if (') && /feature|experiment|rollout|beta/i.test(content)) {
|
|
2050
|
-
const hasFeatureFlagLib = content.includes('unleash') || content.includes('launchdarkly') || content.includes('flagsmith');
|
|
2051
|
-
|
|
2052
|
-
if (!hasFeatureFlagLib) {
|
|
2038
|
+
if (sf.getFilePath().includes('error.tsx') && !content.includes('Sentry') && !content.includes('captureException')) {
|
|
2053
2039
|
pushFinding(
|
|
2054
|
-
"frontend.
|
|
2040
|
+
"frontend.monitoring.missing_error_tracking",
|
|
2055
2041
|
"low",
|
|
2056
2042
|
sf,
|
|
2057
2043
|
sf,
|
|
2058
|
-
'
|
|
2044
|
+
'Error page without error tracking. Install: npm i @sentry/nextjs. Track production errors: Sentry.captureException(error). Get alerts when users hit errors.',
|
|
2059
2045
|
findings
|
|
2060
2046
|
);
|
|
2061
2047
|
}
|
|
2062
|
-
}
|
|
2063
2048
|
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
"frontend.devops.manual_ab_testing",
|
|
2067
|
-
"low",
|
|
2068
|
-
sf,
|
|
2069
|
-
sf,
|
|
2070
|
-
'Manual A/B testing with Math.random. Use: Google Optimize, Optimizely, VWO. Benefits: Statistical significance, reporting, targeting rules.',
|
|
2071
|
-
findings
|
|
2072
|
-
);
|
|
2073
|
-
}
|
|
2049
|
+
if (content.includes('if (') && /feature|experiment|rollout|beta/i.test(content)) {
|
|
2050
|
+
const hasFeatureFlagLib = content.includes('unleash') || content.includes('launchdarkly') || content.includes('flagsmith');
|
|
2074
2051
|
|
|
2075
|
-
|
|
2076
|
-
|
|
2052
|
+
if (!hasFeatureFlagLib) {
|
|
2053
|
+
pushFinding(
|
|
2054
|
+
"frontend.devops.hardcoded_feature_flag",
|
|
2055
|
+
"low",
|
|
2056
|
+
sf,
|
|
2057
|
+
sf,
|
|
2058
|
+
'Hardcoded feature flag. Use feature flag service: Unleash, LaunchDarkly, Flagsmith. Benefits: Toggle features without deployment, gradual rollout, A/B testing.',
|
|
2059
|
+
findings
|
|
2060
|
+
);
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2077
2063
|
|
|
2078
|
-
if (
|
|
2064
|
+
if (content.includes('variant') && content.includes('Math.random')) {
|
|
2079
2065
|
pushFinding(
|
|
2080
|
-
"frontend.
|
|
2066
|
+
"frontend.devops.manual_ab_testing",
|
|
2081
2067
|
"low",
|
|
2082
2068
|
sf,
|
|
2083
2069
|
sf,
|
|
2084
|
-
'
|
|
2070
|
+
'Manual A/B testing with Math.random. Use: Google Optimize, Optimizely, VWO. Benefits: Statistical significance, reporting, targeting rules.',
|
|
2085
2071
|
findings
|
|
2086
2072
|
);
|
|
2087
2073
|
}
|
|
2088
|
-
|
|
2074
|
+
|
|
2075
|
+
if (content.includes('fetch') && !content.includes('cache') && !content.includes('revalidate')) {
|
|
2076
|
+
const isFetchInServerComponent = content.includes('async function') && !content.includes('\'use client\'');
|
|
2077
|
+
|
|
2078
|
+
if (isFetchInServerComponent) {
|
|
2079
|
+
pushFinding(
|
|
2080
|
+
"frontend.performance.missing_cache_strategy",
|
|
2081
|
+
"low",
|
|
2082
|
+
sf,
|
|
2083
|
+
sf,
|
|
2084
|
+
'Fetch without cache strategy. Add: { cache: \'force-cache\' } or { next: { revalidate: 3600 } }. Default behavior may change. Be explicit.',
|
|
2085
|
+
findings
|
|
2086
|
+
);
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
2089
|
});
|
|
2090
2090
|
}
|
|
2091
2091
|
|
|
@@ -815,8 +815,9 @@ class iOSASTIntelligentAnalyzer {
|
|
|
815
815
|
return 0.6745 * (x - med) / madValue;
|
|
816
816
|
};
|
|
817
817
|
|
|
818
|
-
const
|
|
819
|
-
const
|
|
818
|
+
const env = require('../../../../config/env');
|
|
819
|
+
const pOutlier = env.getNumber('AST_GODCLASS_P_OUTLIER', 90);
|
|
820
|
+
const pExtreme = env.getNumber('AST_GODCLASS_P_EXTREME', 97);
|
|
820
821
|
|
|
821
822
|
const methods = this.godClassCandidates.map(c => c.methodsCount);
|
|
822
823
|
const props = this.godClassCandidates.map(c => c.propertiesCount);
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { execSync } = require('child_process');
|
|
6
|
+
const env = require('../../config/env');
|
|
6
7
|
|
|
7
|
-
const projectDir =
|
|
8
|
+
const projectDir = env.get('CLAUDE_PROJECT_DIR', process.cwd());
|
|
8
9
|
const rulesPath = path.join(projectDir, '.cursor', 'ai-skills', 'skill-rules.json');
|
|
9
10
|
const mdcRulesDir = path.join(projectDir, '.cursor', 'rules');
|
|
10
11
|
const debugLogPath = path.join(projectDir, '.audit_tmp', 'skill-activation-debug.log');
|
|
@@ -54,7 +55,7 @@ function sendMacNotification(title, message) {
|
|
|
54
55
|
return;
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
const enabled =
|
|
58
|
+
const enabled = env.getBool('HOOK_GUARD_ENABLE_SKILL_ACTIVATION', true);
|
|
58
59
|
if (!enabled) {
|
|
59
60
|
appendDebugLog('Notifications disabled via HOOK_GUARD_ENABLE_SKILL_ACTIVATION');
|
|
60
61
|
return;
|
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const env = require('../../config/env');
|
|
4
|
+
|
|
5
|
+
// AuditLogger import for logging critical operations
|
|
6
|
+
const AuditLogger = require('../services/logging/AuditLogger');
|
|
7
|
+
|
|
8
|
+
// Initialize audit logger
|
|
9
|
+
const auditLogger = new AuditLogger({
|
|
10
|
+
repoRoot: process.cwd(),
|
|
11
|
+
filename: path.join('.audit_tmp', 'logger-factory-audit.log')
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// Import recordMetric for prometheus metrics
|
|
15
|
+
const { recordMetric } = require('../telemetry/metrics-logger');
|
|
3
16
|
|
|
4
17
|
const UnifiedLogger = require('../../application/services/logging/UnifiedLogger');
|
|
5
18
|
|
|
6
19
|
function createUnifiedLogger({
|
|
7
20
|
repoRoot = process.cwd(),
|
|
8
21
|
component = 'HookSystem',
|
|
9
|
-
consoleLevel =
|
|
10
|
-
fileLevel =
|
|
22
|
+
consoleLevel = env.get('HOOK_LOG_CONSOLE_LEVEL', 'info'),
|
|
23
|
+
fileLevel = env.get('HOOK_LOG_FILE_LEVEL', 'debug'),
|
|
11
24
|
fileName = null,
|
|
12
25
|
defaultData = {}
|
|
13
26
|
} = {}) {
|
|
@@ -17,10 +30,10 @@ function createUnifiedLogger({
|
|
|
17
30
|
const logFileName = fileName || `${component.replace(/\s+/g, '-').toLowerCase()}.log`;
|
|
18
31
|
const filePath = path.join(reportsDir, logFileName);
|
|
19
32
|
|
|
20
|
-
const maxSizeBytes =
|
|
21
|
-
const maxFiles =
|
|
33
|
+
const maxSizeBytes = env.getNumber('HOOK_LOG_MAX_SIZE', 5 * 1024 * 1024);
|
|
34
|
+
const maxFiles = env.getNumber('HOOK_LOG_MAX_FILES', 5);
|
|
22
35
|
|
|
23
|
-
|
|
36
|
+
const logger = new UnifiedLogger({
|
|
24
37
|
component,
|
|
25
38
|
console: {
|
|
26
39
|
enabled: true,
|
|
@@ -35,6 +48,23 @@ function createUnifiedLogger({
|
|
|
35
48
|
},
|
|
36
49
|
defaultData
|
|
37
50
|
});
|
|
51
|
+
|
|
52
|
+
auditLogger.log({
|
|
53
|
+
action: 'logger_created',
|
|
54
|
+
category: 'system',
|
|
55
|
+
severity: 'info',
|
|
56
|
+
message: `UnifiedLogger created for component: ${component}`,
|
|
57
|
+
metadata: {
|
|
58
|
+
component,
|
|
59
|
+
filePath,
|
|
60
|
+
consoleLevel,
|
|
61
|
+
fileLevel,
|
|
62
|
+
maxSizeBytes,
|
|
63
|
+
maxFiles
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return logger;
|
|
38
68
|
}
|
|
39
69
|
|
|
40
70
|
module.exports = { createUnifiedLogger };
|