forgedev 1.2.0 → 1.4.0

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 (183) hide show
  1. package/README.md +57 -10
  2. package/bin/chainproof.js +126 -0
  3. package/bin/devforge.js +1 -1
  4. package/package.json +25 -7
  5. package/src/chainproof-bridge.js +330 -0
  6. package/src/ci-mode.js +85 -0
  7. package/src/claude-configurator.js +171 -78
  8. package/src/cli.js +30 -7
  9. package/src/composer.js +242 -214
  10. package/src/doctor-checks-chainproof.js +106 -0
  11. package/src/doctor-checks.js +39 -20
  12. package/src/doctor-prompts.js +9 -9
  13. package/src/doctor.js +37 -4
  14. package/src/guided.js +3 -3
  15. package/src/index.js +31 -10
  16. package/src/init-mode.js +76 -12
  17. package/src/menu.js +178 -0
  18. package/src/prompts.js +5 -12
  19. package/src/recommender.js +163 -30
  20. package/src/scanner.js +57 -2
  21. package/src/uat-generator.js +204 -189
  22. package/src/update-check.js +9 -4
  23. package/src/update.js +57 -13
  24. package/src/utils.js +162 -5
  25. package/templates/ai/guardrails-py/backend/app/ai/__init__.py +29 -0
  26. package/templates/ai/guardrails-py/backend/app/ai/audit_log.py +133 -0
  27. package/templates/ai/guardrails-py/backend/app/ai/client.py.template +323 -0
  28. package/templates/ai/guardrails-py/backend/app/ai/health.py.template +157 -0
  29. package/templates/ai/guardrails-py/backend/app/ai/input_guard.py +98 -0
  30. package/templates/ai/guardrails-ts/src/lib/ai/audit-log.ts.template +164 -0
  31. package/templates/ai/guardrails-ts/src/lib/ai/client.ts.template +403 -0
  32. package/templates/ai/guardrails-ts/src/lib/ai/health.ts.template +165 -0
  33. package/templates/ai/guardrails-ts/src/lib/ai/index.ts.template +17 -0
  34. package/templates/ai/guardrails-ts/src/lib/ai/input-guard.ts.template +124 -0
  35. package/templates/auth/nextauth/src/lib/auth.ts.template +12 -7
  36. package/templates/backend/express/Dockerfile.template +18 -0
  37. package/templates/backend/express/package.json.template +33 -0
  38. package/templates/backend/express/src/index.ts.template +34 -0
  39. package/templates/backend/express/src/routes/health.ts.template +27 -0
  40. package/templates/backend/express/tsconfig.json +17 -0
  41. package/templates/backend/fastapi/backend/Dockerfile.template +5 -0
  42. package/templates/backend/fastapi/backend/app/api/health.py.template +1 -1
  43. package/templates/backend/fastapi/backend/app/core/config.py.template +1 -1
  44. package/templates/backend/fastapi/backend/app/core/errors.py +1 -1
  45. package/templates/backend/fastapi/backend/app/main.py.template +3 -1
  46. package/templates/backend/fastapi/backend/requirements.txt.template +2 -0
  47. package/templates/backend/hono/Dockerfile.template +18 -0
  48. package/templates/backend/hono/package.json.template +31 -0
  49. package/templates/backend/hono/src/index.ts.template +32 -0
  50. package/templates/backend/hono/src/routes/health.ts.template +27 -0
  51. package/templates/backend/hono/tsconfig.json +18 -0
  52. package/templates/base/.gitignore.template +3 -0
  53. package/templates/base/docs/uat/UAT_TEMPLATE.md.template +1 -1
  54. package/templates/chainproof/base/.chainproof/config.json.template +11 -0
  55. package/templates/chainproof/base/.chainproof/mcp-server.mjs +310 -0
  56. package/templates/chainproof/base/.mcp.json +9 -0
  57. package/templates/chainproof/fastapi/.chainproof/middleware.json.template +14 -0
  58. package/templates/chainproof/nextjs/.chainproof/hooks.json.template +19 -0
  59. package/templates/chainproof/polyglot/.chainproof/config.json.template +21 -0
  60. package/templates/claude-code/agents/architect.md +25 -11
  61. package/templates/claude-code/agents/build-error-resolver.md +19 -5
  62. package/templates/claude-code/agents/chief-of-staff.md +42 -8
  63. package/templates/claude-code/agents/code-quality-reviewer.md +14 -0
  64. package/templates/claude-code/agents/database-reviewer.md +15 -1
  65. package/templates/claude-code/agents/deep-reviewer.md +191 -0
  66. package/templates/claude-code/agents/doc-updater.md +19 -5
  67. package/templates/claude-code/agents/docs-lookup.md +19 -5
  68. package/templates/claude-code/agents/e2e-runner.md +26 -12
  69. package/templates/claude-code/agents/enforcement-gate.md +102 -0
  70. package/templates/claude-code/agents/frontend-builder.md +188 -0
  71. package/templates/claude-code/agents/harness-optimizer.md +36 -1
  72. package/templates/claude-code/agents/loop-operator.md +27 -13
  73. package/templates/claude-code/agents/planner.md +21 -7
  74. package/templates/claude-code/agents/product-strategist.md +24 -10
  75. package/templates/claude-code/agents/production-readiness.md +14 -0
  76. package/templates/claude-code/agents/prompt-auditor.md +115 -0
  77. package/templates/claude-code/agents/refactor-cleaner.md +22 -8
  78. package/templates/claude-code/agents/security-reviewer.md +14 -0
  79. package/templates/claude-code/agents/spec-validator.md +15 -1
  80. package/templates/claude-code/agents/tdd-guide.md +21 -7
  81. package/templates/claude-code/agents/uat-validator.md +14 -0
  82. package/templates/claude-code/claude-md/base.md +14 -7
  83. package/templates/claude-code/claude-md/fastapi.md +8 -8
  84. package/templates/claude-code/claude-md/fullstack.md +6 -6
  85. package/templates/claude-code/claude-md/hono.md +18 -0
  86. package/templates/claude-code/claude-md/nextjs.md +5 -5
  87. package/templates/claude-code/claude-md/remix.md +18 -0
  88. package/templates/claude-code/commands/audit-security.md +14 -0
  89. package/templates/claude-code/commands/audit-spec.md +14 -0
  90. package/templates/claude-code/commands/audit-wiring.md +14 -0
  91. package/templates/claude-code/commands/build-fix.md +28 -0
  92. package/templates/claude-code/commands/build-ui.md +59 -0
  93. package/templates/claude-code/commands/code-review.md +53 -31
  94. package/templates/claude-code/commands/fix-loop.md +211 -0
  95. package/templates/claude-code/commands/full-audit.md +36 -8
  96. package/templates/claude-code/commands/generate-prd.md +1 -1
  97. package/templates/claude-code/commands/generate-sdd.md +74 -0
  98. package/templates/claude-code/commands/generate-uat.md +107 -35
  99. package/templates/claude-code/commands/help.md +68 -0
  100. package/templates/claude-code/commands/live-uat.md +268 -0
  101. package/templates/claude-code/commands/optimize-claude-md.md +15 -1
  102. package/templates/claude-code/commands/plan.md +3 -3
  103. package/templates/claude-code/commands/pre-pr.md +57 -19
  104. package/templates/claude-code/commands/product-strategist.md +21 -0
  105. package/templates/claude-code/commands/resume-session.md +10 -10
  106. package/templates/claude-code/commands/run-uat.md +59 -2
  107. package/templates/claude-code/commands/save-session.md +10 -10
  108. package/templates/claude-code/commands/simplify.md +36 -0
  109. package/templates/claude-code/commands/tdd.md +17 -18
  110. package/templates/claude-code/commands/verify-all.md +24 -0
  111. package/templates/claude-code/commands/verify-intent.md +55 -0
  112. package/templates/claude-code/commands/workflows.md +52 -40
  113. package/templates/claude-code/hooks/polyglot.json +10 -1
  114. package/templates/claude-code/hooks/python.json +10 -1
  115. package/templates/claude-code/hooks/scripts/autofix-polyglot.mjs +2 -2
  116. package/templates/claude-code/hooks/scripts/autofix-python.mjs +1 -1
  117. package/templates/claude-code/hooks/scripts/autofix-typescript.mjs +1 -1
  118. package/templates/claude-code/hooks/scripts/code-hygiene.mjs +293 -0
  119. package/templates/claude-code/hooks/scripts/pre-commit-gate.mjs +207 -0
  120. package/templates/claude-code/hooks/typescript.json +10 -1
  121. package/templates/claude-code/skills/ai-prompts/SKILL.md +119 -41
  122. package/templates/claude-code/skills/git-workflow/SKILL.md +5 -5
  123. package/templates/claude-code/skills/nextjs/SKILL.md +1 -1
  124. package/templates/claude-code/skills/playwright/SKILL.md +5 -5
  125. package/templates/claude-code/skills/security-api/SKILL.md +1 -1
  126. package/templates/claude-code/skills/security-web/SKILL.md +1 -1
  127. package/templates/claude-code/skills/testing-patterns/SKILL.md +9 -9
  128. package/templates/database/prisma-postgres/{.env.example → .env.example.template} +1 -0
  129. package/templates/database/sqlalchemy-postgres/{.env.example → .env.example.template} +1 -0
  130. package/templates/docs-portal/fastapi/backend/app/portal/__pycache__/docs_reader.cpython-314.pyc +0 -0
  131. package/templates/docs-portal/fastapi/backend/app/portal/docs_reader.py +201 -0
  132. package/templates/docs-portal/fastapi/backend/app/portal/html_renderer.py +229 -0
  133. package/templates/docs-portal/fastapi/backend/app/portal/router.py.template +35 -0
  134. package/templates/docs-portal/nextjs/src/app/portal/[category]/[slug]/page.tsx +81 -0
  135. package/templates/docs-portal/nextjs/src/app/portal/[category]/page.tsx +65 -0
  136. package/templates/docs-portal/nextjs/src/app/portal/layout.tsx.template +54 -0
  137. package/templates/docs-portal/nextjs/src/app/portal/page.tsx +85 -0
  138. package/templates/docs-portal/nextjs/src/components/portal/markdown-renderer.tsx +101 -0
  139. package/templates/docs-portal/nextjs/src/components/portal/mobile-portal-nav.tsx +81 -0
  140. package/templates/docs-portal/nextjs/src/components/portal/portal-nav.tsx +86 -0
  141. package/templates/docs-portal/nextjs/src/lib/docs.ts +139 -0
  142. package/templates/frontend/nextjs/package.json.template +3 -1
  143. package/templates/frontend/react/index.html.template +12 -0
  144. package/templates/frontend/react/package.json.template +34 -0
  145. package/templates/frontend/react/src/App.tsx.template +10 -0
  146. package/templates/frontend/react/src/index.css +1 -0
  147. package/templates/frontend/react/src/main.tsx +10 -0
  148. package/templates/frontend/react/tsconfig.json +17 -0
  149. package/templates/frontend/react/vite.config.ts.template +15 -0
  150. package/templates/frontend/react/vitest.config.ts +9 -0
  151. package/templates/frontend/remix/app/root.tsx.template +31 -0
  152. package/templates/frontend/remix/app/routes/_index.tsx.template +19 -0
  153. package/templates/frontend/remix/app/routes/api.health.ts.template +10 -0
  154. package/templates/frontend/remix/app/tailwind.css +1 -0
  155. package/templates/frontend/remix/package.json.template +39 -0
  156. package/templates/frontend/remix/tsconfig.json +18 -0
  157. package/templates/frontend/remix/vite.config.ts.template +7 -0
  158. package/templates/infra/github-actions/.github/workflows/ci.yml.template +3 -0
  159. package/templates/infra/k8s/k8s/deployment.yml.template +70 -0
  160. package/templates/infra/k8s/k8s/hpa.yml.template +24 -0
  161. package/templates/infra/k8s/k8s/ingress.yml.template +26 -0
  162. package/templates/infra/k8s/k8s/kustomization.yml.template +13 -0
  163. package/templates/infra/k8s/k8s/namespace.yml.template +4 -0
  164. package/templates/infra/k8s/k8s/networkpolicy.yml.template +41 -0
  165. package/templates/infra/k8s/k8s/secrets.yml.template +10 -0
  166. package/templates/infra/k8s/k8s/service.yml.template +15 -0
  167. package/templates/testing/load/k6/README.md.template +48 -0
  168. package/templates/testing/load/k6/load-test.js.template +57 -0
  169. package/docs/00-README.md +0 -310
  170. package/docs/01-universal-prompt-library.md +0 -1049
  171. package/docs/02-claude-code-mastery-playbook.md +0 -283
  172. package/docs/03-multi-agent-verification.md +0 -565
  173. package/docs/04-errata-and-verification-checklist.md +0 -284
  174. package/docs/05-universal-scaffolder-vision.md +0 -452
  175. package/docs/06-confidence-assessment-and-repo-prompt.md +0 -407
  176. package/docs/errata.md +0 -58
  177. package/docs/multi-agent-verification.md +0 -66
  178. package/docs/playbook.md +0 -95
  179. package/docs/prompt-library.md +0 -160
  180. package/docs/uat/UAT_CHECKLIST.csv +0 -9
  181. package/docs/uat/UAT_TEMPLATE.md +0 -163
  182. package/templates/claude-code/commands/done.md +0 -19
  183. /package/{docs/plans/.gitkeep → templates/docs-portal/fastapi/backend/app/portal/__init__.py} +0 -0
@@ -0,0 +1,157 @@
1
+ """AI Health Check — Observability endpoint for AI system metrics.
2
+
3
+ Compliance: NIST AI RMF Manage 3.2 (monitoring),
4
+ EU AI Act Art. 9 (risk management)
5
+
6
+ Mount as: router.include_router(ai_health_router, prefix="/api/ai")
7
+ """
8
+
9
+ import time
10
+ from collections import deque
11
+ from dataclasses import dataclass
12
+ from typing import Any
13
+
14
+ from fastapi import APIRouter
15
+
16
+ ai_health_router = APIRouter(tags=["ai-health"])
17
+
18
+
19
+ @dataclass
20
+ class CallMetric:
21
+ timestamp: float
22
+ model: str
23
+ latency_ms: float
24
+ confidence: float
25
+ success: bool
26
+ token_usage: dict[str, int] | None = None
27
+
28
+
29
+ class AIHealthMetrics:
30
+ def __init__(self, max_metrics: int = 5000):
31
+ self._metrics: deque[CallMetric] = deque(maxlen=max_metrics)
32
+
33
+ def record_call(
34
+ self,
35
+ model: str,
36
+ latency_ms: float,
37
+ confidence: float,
38
+ success: bool,
39
+ token_usage: dict[str, int] | None = None,
40
+ ) -> None:
41
+ self._metrics.append(CallMetric(
42
+ timestamp=time.time(),
43
+ model=model,
44
+ latency_ms=latency_ms,
45
+ confidence=confidence,
46
+ success=success,
47
+ token_usage=token_usage,
48
+ ))
49
+
50
+ def get_health_status(self, window_seconds: int = 3600) -> dict[str, Any]:
51
+ cutoff = time.time() - window_seconds
52
+ recent = [m for m in self._metrics if m.timestamp >= cutoff]
53
+
54
+ if not recent:
55
+ return {
56
+ "status": "ok",
57
+ "ai_available": True,
58
+ "message": "No AI calls in the monitoring window",
59
+ "window": f"{window_seconds // 60}m",
60
+ "metrics": self._empty_metrics(),
61
+ "models": {},
62
+ }
63
+
64
+ successes = [m for m in recent if m.success]
65
+ error_rate = 1 - (len(successes) / len(recent))
66
+ avg_confidence = (
67
+ sum(m.confidence for m in successes) / len(successes) if successes else 0
68
+ )
69
+ avg_latency = sum(m.latency_ms for m in recent) / len(recent)
70
+ low_confidence = [m for m in successes if m.confidence < 0.7]
71
+ low_confidence_rate = len(low_confidence) / max(len(successes), 1)
72
+
73
+ # Determine status
74
+ status = "ok"
75
+ warnings: list[str] = []
76
+
77
+ if error_rate > 0.5:
78
+ status = "unhealthy"
79
+ warnings.append(f"High error rate: {error_rate * 100:.1f}%")
80
+ elif error_rate > 0.1:
81
+ status = "degraded"
82
+ warnings.append(f"Elevated error rate: {error_rate * 100:.1f}%")
83
+
84
+ if avg_confidence < 0.5:
85
+ status = "degraded" if status == "ok" else status
86
+ warnings.append(f"Low average confidence: {avg_confidence * 100:.1f}%")
87
+
88
+ if avg_latency > 10_000:
89
+ status = "degraded" if status == "ok" else status
90
+ warnings.append(f"High average latency: {avg_latency:.0f}ms")
91
+
92
+ # Per-model breakdown
93
+ models: dict[str, dict] = {}
94
+ model_names = set(m.model for m in recent)
95
+ for model_name in model_names:
96
+ model_calls = [m for m in recent if m.model == model_name]
97
+ model_successes = [m for m in model_calls if m.success]
98
+ total_tokens = sum(
99
+ (m.token_usage.get("input_tokens", 0) + m.token_usage.get("output_tokens", 0))
100
+ for m in model_calls if m.token_usage
101
+ )
102
+ models[model_name] = {
103
+ "calls": len(model_calls),
104
+ "success_rate": len(model_successes) / len(model_calls),
105
+ "avg_latency_ms": sum(m.latency_ms for m in model_calls) / len(model_calls),
106
+ "avg_confidence": (
107
+ sum(m.confidence for m in model_successes) / len(model_successes)
108
+ if model_successes else 0
109
+ ),
110
+ "total_tokens": total_tokens,
111
+ }
112
+
113
+ return {
114
+ "status": status,
115
+ "ai_available": error_rate < 1,
116
+ "message": "; ".join(warnings) if warnings else "All AI systems operating normally",
117
+ "window": f"{window_seconds // 60}m",
118
+ "metrics": {
119
+ "total_calls": len(recent),
120
+ "success_rate": 1 - error_rate,
121
+ "avg_confidence": avg_confidence,
122
+ "avg_latency_ms": avg_latency,
123
+ "low_confidence_rate": low_confidence_rate,
124
+ "error_rate": error_rate,
125
+ },
126
+ "models": models,
127
+ }
128
+
129
+ def _empty_metrics(self) -> dict:
130
+ return {
131
+ "total_calls": 0,
132
+ "success_rate": 1.0,
133
+ "avg_confidence": 0.0,
134
+ "avg_latency_ms": 0.0,
135
+ "low_confidence_rate": 0.0,
136
+ "error_rate": 0.0,
137
+ }
138
+
139
+
140
+ # --- Singleton ---
141
+
142
+ ai_health_metrics = AIHealthMetrics()
143
+
144
+
145
+ # --- FastAPI Health Endpoint ---
146
+
147
+ @ai_health_router.get("/health")
148
+ async def ai_health():
149
+ """AI system health check.
150
+
151
+ Returns model availability, confidence distribution,
152
+ error rates, and per-model metrics.
153
+ """
154
+ return ai_health_metrics.get_health_status()
155
+
156
+
157
+ AIHealthStatus = dict # Type alias for documentation
@@ -0,0 +1,98 @@
1
+ """AI Input Guard — Prompt injection detection and input sanitization.
2
+
3
+ Compliance: EU AI Act Art. 15 (robustness), NIST AI RMF Manage 2.2
4
+ """
5
+
6
+ import re
7
+ from dataclasses import dataclass, field
8
+
9
+
10
+ @dataclass
11
+ class InputValidationResult:
12
+ blocked: bool
13
+ reason: str | None = None
14
+ detected_patterns: list[str] = field(default_factory=list)
15
+
16
+
17
+ # --- Injection Detection Patterns ---
18
+
19
+ INJECTION_PATTERNS: list[tuple[re.Pattern, str]] = [
20
+ # Direct instruction override
21
+ (re.compile(r"ignore\s+(all\s+)?(previous|prior|above)\s+(instructions|prompts|rules)", re.I), "instruction-override"),
22
+ (re.compile(r"disregard\s+(all\s+)?(previous|prior|above|your)\s+(instructions|prompts|rules|training)", re.I), "instruction-override"),
23
+ (re.compile(r"forget\s+(all\s+)?(previous|prior|your)\s+(instructions|context|rules)", re.I), "instruction-override"),
24
+
25
+ # Role manipulation
26
+ (re.compile(r"you\s+are\s+now\s+(a|an|the)\s+", re.I), "role-manipulation"),
27
+ (re.compile(r"act\s+as\s+(if\s+you\s+are|a|an)\s+", re.I), "role-manipulation"),
28
+ (re.compile(r"pretend\s+(to\s+be|you\s+are)\s+", re.I), "role-manipulation"),
29
+ (re.compile(r"from\s+now\s+on\s+(you|your)\s+", re.I), "role-manipulation"),
30
+
31
+ # System prompt extraction
32
+ (re.compile(r"what\s+(is|are)\s+your\s+(system\s+)?(prompt|instructions|rules)", re.I), "prompt-extraction"),
33
+ (re.compile(r"show\s+me\s+your\s+(system\s+)?(prompt|instructions)", re.I), "prompt-extraction"),
34
+ (re.compile(r"repeat\s+(your|the)\s+(system\s+)?(prompt|instructions)", re.I), "prompt-extraction"),
35
+ (re.compile(r"print\s+(your|the)\s+(system\s+)?(prompt|instructions)", re.I), "prompt-extraction"),
36
+
37
+ # Delimiter injection
38
+ (re.compile(r"</?system>", re.I), "delimiter-injection"),
39
+ (re.compile(r"\[INST\]", re.I), "delimiter-injection"),
40
+ (re.compile(r"<\|im_start\|", re.I), "delimiter-injection"),
41
+ (re.compile(r"###\s*(system|instruction|human|assistant)", re.I), "delimiter-injection"),
42
+
43
+ # Data exfiltration
44
+ (re.compile(r"send\s+(this|the|all)\s+(data|info|conversation)\s+to", re.I), "data-exfiltration"),
45
+ (re.compile(r"forward\s+(this|everything)\s+to", re.I), "data-exfiltration"),
46
+ ]
47
+
48
+ SUSPICIOUS_PATTERNS: list[tuple[re.Pattern, str]] = [
49
+ (re.compile(r"eval\s*\(", re.I), "code-execution"),
50
+ (re.compile(r"exec\s*\(", re.I), "code-execution"),
51
+ (re.compile(r"import\s+(?:os|subprocess)|subprocess\.", re.I), "code-execution"),
52
+ (re.compile(r"[A-Za-z0-9+/]{100,}={0,2}", re.I), "encoded-payload"),
53
+ ]
54
+
55
+
56
+ def validate_input(text: str) -> InputValidationResult:
57
+ """Validate input for prompt injection and safety concerns."""
58
+ import unicodedata
59
+ # Normalize Unicode to defeat homoglyph attacks (e.g., Cyrillic "а" for Latin "a")
60
+ normalized = unicodedata.normalize("NFKC", text)
61
+ detected: list[str] = []
62
+
63
+ for pattern, name in INJECTION_PATTERNS:
64
+ if pattern.search(normalized):
65
+ detected.append(name)
66
+
67
+ if detected:
68
+ unique = list(set(detected))
69
+ return InputValidationResult(
70
+ blocked=True,
71
+ reason=f"Potential prompt injection detected: {', '.join(unique)}",
72
+ detected_patterns=unique,
73
+ )
74
+
75
+ suspicious: list[str] = []
76
+ for pattern, name in SUSPICIOUS_PATTERNS:
77
+ if pattern.search(normalized):
78
+ suspicious.append(name)
79
+
80
+ if suspicious:
81
+ return InputValidationResult(
82
+ blocked=False,
83
+ reason=f"Suspicious patterns detected (not blocked): {', '.join(suspicious)}",
84
+ detected_patterns=suspicious,
85
+ )
86
+
87
+ return InputValidationResult(blocked=False)
88
+
89
+
90
+ def sanitize_input(text: str) -> str:
91
+ """Remove known dangerous patterns from input."""
92
+ sanitized = re.sub(r"</?system>", "", text, flags=re.I)
93
+ sanitized = re.sub(r"\[INST\]", "", sanitized, flags=re.I)
94
+ sanitized = re.sub(r"<\|im_start\|[^>]*>?", "", sanitized, flags=re.I)
95
+ sanitized = re.sub(r"<\|im_end\|>?", "", sanitized, flags=re.I)
96
+ sanitized = re.sub(r"###\s*(system|instruction|human|assistant)", "", sanitized, flags=re.I)
97
+ sanitized = sanitized.replace("\x00", "")
98
+ return sanitized
@@ -0,0 +1,164 @@
1
+ /**
2
+ * AI Audit Logger — Structured logging of all AI interactions.
3
+ *
4
+ * Compliance: EU AI Act Art. 12 (logging and traceability),
5
+ * NIST AI RMF Manage 1.3 (monitoring)
6
+ *
7
+ * Every AI call is logged with:
8
+ * - Input preview (truncated, no PII in logs)
9
+ * - Output confidence score
10
+ * - Model version and parameters
11
+ * - Human review decisions
12
+ * - Latency and token usage
13
+ * - Error details
14
+ *
15
+ * Logs are structured JSON for easy ingestion into observability platforms
16
+ * (Datadog, Grafana, ELK, etc.)
17
+ */
18
+
19
+ export interface AuditEntry {
20
+ /** Unique interaction ID (matches AIResponse.auditId) */
21
+ id: string;
22
+ /** ISO 8601 timestamp */
23
+ timestamp: string;
24
+ /** Model identifier */
25
+ model: string;
26
+ /** Business purpose of this AI call */
27
+ purpose: string;
28
+ /** Truncated input for traceability (never log full PII) */
29
+ inputPreview: string;
30
+ /** Confidence score (0-1) */
31
+ confidence: number;
32
+ /** Whether human review was triggered */
33
+ needsHumanReview: boolean;
34
+ /** Total latency in milliseconds */
35
+ latencyMs: number;
36
+ /** Token usage for cost tracking */
37
+ tokenUsage?: { inputTokens: number; outputTokens: number };
38
+ /** Whether the call succeeded */
39
+ success: boolean;
40
+ /** Error message if failed */
41
+ error?: string;
42
+ /** Human reviewer action (if reviewed) */
43
+ humanAction?: 'approved' | 'rejected' | 'modified';
44
+ /** Human reviewer ID (if reviewed) */
45
+ humanReviewerId?: string;
46
+ }
47
+
48
+ export interface AuditLogConfig {
49
+ /** Where to send logs: 'console' or custom handler */
50
+ destination: 'console' | 'custom';
51
+ /** Custom log handler */
52
+ handler?: (entry: AuditEntry) => void;
53
+ /** Log level filter: only log entries with confidence below this */
54
+ confidenceAlertThreshold?: number;
55
+ }
56
+
57
+ class AIAuditLog {
58
+ private config: AuditLogConfig;
59
+ private entries: AuditEntry[] = [];
60
+ private maxInMemory = 1000;
61
+
62
+ constructor(config?: Partial<AuditLogConfig>) {
63
+ this.config = {
64
+ destination: config?.destination || 'console',
65
+ handler: config?.handler,
66
+ confidenceAlertThreshold: config?.confidenceAlertThreshold ?? 0.5,
67
+ };
68
+ }
69
+
70
+ log(entry: AuditEntry): void {
71
+ // In-memory buffer for health metrics
72
+ this.entries.push(entry);
73
+ if (this.entries.length > this.maxInMemory) {
74
+ this.entries = this.entries.slice(-this.maxInMemory);
75
+ }
76
+
77
+ // Structured log output (exclude inputPreview to avoid PII in logs)
78
+ const { inputPreview: _preview, ...safeEntry } = entry;
79
+ const logEntry = {
80
+ level: entry.success ? 'info' : 'error',
81
+ type: 'ai_interaction',
82
+ ...safeEntry,
83
+ };
84
+
85
+ switch (this.config.destination) {
86
+ case 'console':
87
+ if (!entry.success || (entry.confidence < (this.config.confidenceAlertThreshold ?? 0.5))) {
88
+ console.warn('[AI_AUDIT]', JSON.stringify(logEntry));
89
+ } else {
90
+ console.log('[AI_AUDIT]', JSON.stringify(logEntry));
91
+ }
92
+ break;
93
+ case 'custom':
94
+ this.config.handler?.(entry);
95
+ break;
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Record a human review decision against an existing audit entry.
101
+ */
102
+ recordHumanReview(auditId: string, action: 'approved' | 'rejected' | 'modified', reviewerId?: string): void {
103
+ const entry = this.entries.find(e => e.id === auditId);
104
+ if (!entry) {
105
+ console.warn(`[AI_AUDIT] Cannot record human review: audit entry ${auditId} not found`);
106
+ return;
107
+ }
108
+ entry.humanAction = action;
109
+ entry.humanReviewerId = reviewerId;
110
+ // Re-emit to log destination without adding duplicate to buffer
111
+ const logEntry = {
112
+ level: 'info',
113
+ type: 'ai_interaction_review',
114
+ ...entry,
115
+ };
116
+ if (this.config.destination === 'console') {
117
+ console.log('[AI_AUDIT]', JSON.stringify(logEntry));
118
+ } else if (this.config.destination === 'custom') {
119
+ this.config.handler?.(entry);
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Get recent entries for monitoring dashboard.
125
+ */
126
+ getRecentEntries(count = 50): AuditEntry[] {
127
+ return this.entries.slice(-count);
128
+ }
129
+
130
+ /**
131
+ * Get aggregate stats for AI health reporting.
132
+ */
133
+ getStats(windowMs = 3600_000): {
134
+ totalCalls: number;
135
+ successRate: number;
136
+ avgConfidence: number;
137
+ avgLatencyMs: number;
138
+ humanReviewRate: number;
139
+ errorRate: number;
140
+ } {
141
+ const cutoff = new Date(Date.now() - windowMs).toISOString();
142
+ const recent = this.entries.filter(e => e.timestamp >= cutoff);
143
+
144
+ if (recent.length === 0) {
145
+ return { totalCalls: 0, successRate: 1, avgConfidence: 0, avgLatencyMs: 0, humanReviewRate: 0, errorRate: 0 };
146
+ }
147
+
148
+ const successes = recent.filter(e => e.success).length;
149
+ const reviews = recent.filter(e => e.needsHumanReview).length;
150
+
151
+ return {
152
+ totalCalls: recent.length,
153
+ successRate: successes / recent.length,
154
+ avgConfidence: recent.reduce((sum, e) => sum + e.confidence, 0) / recent.length,
155
+ avgLatencyMs: recent.reduce((sum, e) => sum + e.latencyMs, 0) / recent.length,
156
+ humanReviewRate: reviews / recent.length,
157
+ errorRate: 1 - (successes / recent.length),
158
+ };
159
+ }
160
+ }
161
+
162
+ // --- Singleton ---
163
+
164
+ export const aiAuditLog = new AIAuditLog();