llm-mock-server 1.0.4 → 1.0.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.
- package/.desloppify/query.json +1162 -62
- package/.desloppify/review_packet_blind.json +18 -18
- package/.desloppify/review_packets/holistic_packet_20260315_185401.json +1407 -0
- package/.desloppify/review_packets/holistic_packet_20260315_185613.json +1407 -0
- package/.desloppify/state-typescript.json +2530 -645
- package/.desloppify/state-typescript.json.bak +2494 -582
- package/.desloppify/subagents/runs/20260315_185401/logs/batch-1.log +384 -0
- package/.desloppify/subagents/runs/20260315_185401/logs/batch-10.log +484 -0
- package/.desloppify/subagents/runs/20260315_185401/logs/batch-2.log +408 -0
- package/.desloppify/subagents/runs/20260315_185401/logs/batch-3.log +416 -0
- package/.desloppify/subagents/runs/20260315_185401/logs/batch-4.log +360 -0
- package/.desloppify/subagents/runs/20260315_185401/logs/batch-5.log +360 -0
- package/.desloppify/subagents/runs/20260315_185401/logs/batch-6.log +364 -0
- package/.desloppify/subagents/runs/20260315_185401/logs/batch-7.log +428 -0
- package/.desloppify/subagents/runs/20260315_185401/logs/batch-8.log +388 -0
- package/.desloppify/subagents/runs/20260315_185401/logs/batch-9.log +500 -0
- package/.desloppify/subagents/runs/20260315_185401/prompts/batch-1.md +83 -0
- package/.desloppify/subagents/runs/20260315_185401/prompts/batch-10.md +108 -0
- package/.desloppify/subagents/runs/20260315_185401/prompts/batch-2.md +89 -0
- package/.desloppify/subagents/runs/20260315_185401/prompts/batch-3.md +91 -0
- package/.desloppify/subagents/runs/20260315_185401/prompts/batch-4.md +77 -0
- package/.desloppify/subagents/runs/20260315_185401/prompts/batch-5.md +77 -0
- package/.desloppify/subagents/runs/20260315_185401/prompts/batch-6.md +78 -0
- package/.desloppify/subagents/runs/20260315_185401/prompts/batch-7.md +94 -0
- package/.desloppify/subagents/runs/20260315_185401/prompts/batch-8.md +84 -0
- package/.desloppify/subagents/runs/20260315_185401/prompts/batch-9.md +112 -0
- package/.desloppify/subagents/runs/20260315_185401/results/batch-1.raw.txt +0 -0
- package/.desloppify/subagents/runs/20260315_185401/results/batch-10.raw.txt +0 -0
- package/.desloppify/subagents/runs/20260315_185401/results/batch-2.raw.txt +0 -0
- package/.desloppify/subagents/runs/20260315_185401/results/batch-3.raw.txt +0 -0
- package/.desloppify/subagents/runs/20260315_185401/results/batch-4.raw.txt +0 -0
- package/.desloppify/subagents/runs/20260315_185401/results/batch-5.raw.txt +0 -0
- package/.desloppify/subagents/runs/20260315_185401/results/batch-6.raw.txt +0 -0
- package/.desloppify/subagents/runs/20260315_185401/results/batch-7.raw.txt +0 -0
- package/.desloppify/subagents/runs/20260315_185401/results/batch-8.raw.txt +0 -0
- package/.desloppify/subagents/runs/20260315_185401/results/batch-9.raw.txt +0 -0
- package/.desloppify/subagents/runs/20260315_185401/run.log +36 -0
- package/.desloppify/subagents/runs/20260315_185401/run_summary.json +156 -0
- package/.desloppify/subagents/runs/20260315_185613/holistic_findings_merged.json +741 -0
- package/.desloppify/subagents/runs/20260315_185613/logs/batch-1.log +579 -0
- package/.desloppify/subagents/runs/20260315_185613/logs/batch-10.log +1537 -0
- package/.desloppify/subagents/runs/20260315_185613/logs/batch-2.log +829 -0
- package/.desloppify/subagents/runs/20260315_185613/logs/batch-3.log +927 -0
- package/.desloppify/subagents/runs/20260315_185613/logs/batch-4.log +429 -0
- package/.desloppify/subagents/runs/20260315_185613/logs/batch-5.log +276 -0
- package/.desloppify/subagents/runs/20260315_185613/logs/batch-6.log +450 -0
- package/.desloppify/subagents/runs/20260315_185613/logs/batch-7.log +730 -0
- package/.desloppify/subagents/runs/20260315_185613/logs/batch-8.log +698 -0
- package/.desloppify/subagents/runs/20260315_185613/logs/batch-9.log +938 -0
- package/.desloppify/subagents/runs/20260315_185613/prompts/batch-1.md +83 -0
- package/.desloppify/subagents/runs/20260315_185613/prompts/batch-10.md +108 -0
- package/.desloppify/subagents/runs/20260315_185613/prompts/batch-2.md +89 -0
- package/.desloppify/subagents/runs/20260315_185613/prompts/batch-3.md +91 -0
- package/.desloppify/subagents/runs/20260315_185613/prompts/batch-4.md +77 -0
- package/.desloppify/subagents/runs/20260315_185613/prompts/batch-5.md +77 -0
- package/.desloppify/subagents/runs/20260315_185613/prompts/batch-6.md +78 -0
- package/.desloppify/subagents/runs/20260315_185613/prompts/batch-7.md +94 -0
- package/.desloppify/subagents/runs/20260315_185613/prompts/batch-8.md +84 -0
- package/.desloppify/subagents/runs/20260315_185613/prompts/batch-9.md +112 -0
- package/.desloppify/subagents/runs/20260315_185613/results/batch-1.raw.txt +78 -0
- package/.desloppify/subagents/runs/20260315_185613/results/batch-10.raw.txt +242 -0
- package/.desloppify/subagents/runs/20260315_185613/results/batch-2.raw.txt +102 -0
- package/.desloppify/subagents/runs/20260315_185613/results/batch-3.raw.txt +94 -0
- package/.desloppify/subagents/runs/20260315_185613/results/batch-4.raw.txt +86 -0
- package/.desloppify/subagents/runs/20260315_185613/results/batch-5.raw.txt +1 -0
- package/.desloppify/subagents/runs/20260315_185613/results/batch-6.raw.txt +87 -0
- package/.desloppify/subagents/runs/20260315_185613/results/batch-7.raw.txt +1 -0
- package/.desloppify/subagents/runs/20260315_185613/results/batch-8.raw.txt +107 -0
- package/.desloppify/subagents/runs/20260315_185613/results/batch-9.raw.txt +67 -0
- package/.desloppify/subagents/runs/20260315_185613/run.log +96 -0
- package/.desloppify/subagents/runs/20260315_185613/run_summary.json +156 -0
- package/.github/workflows/docs.yml +46 -0
- package/.github/workflows/test.yml +3 -0
- package/README.md +8 -4
- package/docs/ARCHITECTURE.md +11 -11
- package/package.json +18 -11
- package/scorecard.png +0 -0
- package/src/{cli.ts → cli/cli.ts} +6 -11
- package/src/{cli-validators.ts → cli/validators.ts} +10 -9
- package/src/formats/anthropic/index.ts +2 -2
- package/src/formats/anthropic/parse.ts +5 -2
- package/src/formats/anthropic/serialize.ts +3 -3
- package/src/formats/openai/{index.ts → chat-completions/index.ts} +3 -3
- package/src/formats/openai/{parse.ts → chat-completions/parse.ts} +5 -2
- package/src/formats/openai/{serialize.ts → chat-completions/serialize.ts} +3 -3
- package/src/formats/{responses → openai/responses}/index.ts +2 -2
- package/src/formats/{responses → openai/responses}/parse.ts +5 -2
- package/src/formats/{responses → openai/responses}/serialize.ts +3 -3
- package/src/formats/request-helpers.ts +6 -1
- package/src/formats/serialize-helpers.ts +9 -4
- package/src/formats/types.ts +2 -6
- package/src/history.ts +6 -2
- package/src/loader.ts +2 -1
- package/src/mock-server.ts +55 -106
- package/src/route-handler.ts +7 -11
- package/src/rule-builder.ts +73 -0
- package/src/rule-engine.ts +3 -10
- package/src/sse-writer.ts +1 -1
- package/src/types/reply.ts +51 -8
- package/src/types/request.ts +21 -6
- package/src/types/rule.ts +65 -7
- package/test/cli-validators.test.ts +13 -5
- package/test/formats/openai.test.ts +40 -28
- package/test/formats/responses.test.ts +2 -2
- package/test/history.test.ts +1 -1
- package/test/loader.test.ts +3 -3
- package/test/logger.test.ts +2 -2
- package/test/mock-server.test.ts +1 -1
- package/test/rule-engine.test.ts +1 -1
- package/tsconfig.json +2 -4
- package/typedoc.json +9 -0
- /package/src/formats/openai/{schema.ts → chat-completions/schema.ts} +0 -0
- /package/src/formats/{responses → openai/responses}/schema.ts +0 -0
|
@@ -0,0 +1,1537 @@
|
|
|
1
|
+
ATTEMPT 1/3
|
|
2
|
+
$ codex exec --ephemeral -C /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server -s workspace-write -c approval_policy="never" -c model_reasoning_effort="low" -o /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/results/batch-10.raw.txt You are a focused subagent reviewer for a single holistic investigation batch.
|
|
3
|
+
|
|
4
|
+
Repository root: /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server
|
|
5
|
+
Blind packet: /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/review_packet_blind.json
|
|
6
|
+
Batch index: 10
|
|
7
|
+
Batch name: Full Codebase Sweep
|
|
8
|
+
Batch dimensions: cross_module_architecture, convention_outlier, error_consistency, abstraction_fitness, api_surface_coherence, authorization_consistency, ai_generated_debt, incomplete_migration, package_organization, high_level_elegance, mid_level_elegance, low_level_elegance, design_coherence
|
|
9
|
+
Batch rationale: thorough default: evaluate cross-cutting quality across all production files
|
|
10
|
+
|
|
11
|
+
Files assigned:
|
|
12
|
+
- src/cli-validators.ts
|
|
13
|
+
- src/cli.ts
|
|
14
|
+
- src/formats/anthropic/index.ts
|
|
15
|
+
- src/formats/anthropic/parse.ts
|
|
16
|
+
- src/formats/anthropic/schema.ts
|
|
17
|
+
- src/formats/anthropic/serialize.ts
|
|
18
|
+
- src/formats/openai/index.ts
|
|
19
|
+
- src/formats/openai/parse.ts
|
|
20
|
+
- src/formats/openai/schema.ts
|
|
21
|
+
- src/formats/openai/serialize.ts
|
|
22
|
+
- src/formats/request-helpers.ts
|
|
23
|
+
- src/formats/responses/index.ts
|
|
24
|
+
- src/formats/responses/parse.ts
|
|
25
|
+
- src/formats/responses/schema.ts
|
|
26
|
+
- src/formats/responses/serialize.ts
|
|
27
|
+
- src/formats/serialize-helpers.ts
|
|
28
|
+
- src/formats/types.ts
|
|
29
|
+
- src/history.ts
|
|
30
|
+
- src/index.ts
|
|
31
|
+
- src/loader.ts
|
|
32
|
+
- src/logger.ts
|
|
33
|
+
- src/mock-server.ts
|
|
34
|
+
- src/route-handler.ts
|
|
35
|
+
- src/rule-engine.ts
|
|
36
|
+
- src/sse-writer.ts
|
|
37
|
+
- src/types.ts
|
|
38
|
+
- src/types/reply.ts
|
|
39
|
+
- src/types/request.ts
|
|
40
|
+
- src/types/rule.ts
|
|
41
|
+
- vitest.config.ts
|
|
42
|
+
|
|
43
|
+
Task requirements:
|
|
44
|
+
1. Read the blind packet and follow `system_prompt` constraints exactly.
|
|
45
|
+
1a. If previously flagged issues are listed above, use them as context for your review.
|
|
46
|
+
Verify whether each still applies to the current code. Do not re-report fixed or
|
|
47
|
+
wontfix issues. Use them as starting points to look deeper — inspect adjacent code
|
|
48
|
+
and related modules for defects the prior review may have missed.
|
|
49
|
+
1c. Think structurally: when you spot multiple individual issues that share a common
|
|
50
|
+
root cause (missing abstraction, duplicated pattern, inconsistent convention),
|
|
51
|
+
explain the deeper structural issue in the finding, not just the surface symptom.
|
|
52
|
+
If the pattern is significant enough, report the structural issue as its own finding
|
|
53
|
+
with appropriate fix_scope ('multi_file_refactor' or 'architectural_change') and
|
|
54
|
+
use `root_cause_cluster` to connect related symptom findings together.
|
|
55
|
+
2. Evaluate ONLY listed files and ONLY listed dimensions for this batch.
|
|
56
|
+
3. Return 0-13 high-quality findings for this batch (empty array allowed).
|
|
57
|
+
3a. Do not suppress real defects to keep scores high; report every material issue you can support with evidence.
|
|
58
|
+
3b. Do not default to 100. Reserve 100 for genuinely exemplary evidence in this batch.
|
|
59
|
+
4. Score/finding consistency is required: broader or more severe findings MUST lower dimension scores.
|
|
60
|
+
4a. Any dimension scored below 85.0 MUST include explicit feedback: add at least one finding with the same `dimension` and a non-empty actionable `suggestion`.
|
|
61
|
+
5. Every finding must include `related_files` with at least 2 files when possible.
|
|
62
|
+
6. Every finding must include `dimension`, `identifier`, `summary`, `evidence`, `suggestion`, and `confidence`.
|
|
63
|
+
7. Every finding must include `impact_scope` and `fix_scope`.
|
|
64
|
+
8. Every scored dimension MUST include dimension_notes with concrete evidence.
|
|
65
|
+
9. If a dimension score is >85.0, include `issues_preventing_higher_score` in dimension_notes.
|
|
66
|
+
10. Use exactly one decimal place for every assessment and abstraction sub-axis score.
|
|
67
|
+
9a. For package_organization, ground scoring in objective structure signals from `holistic_context.structure` (root_files fan_in/fan_out roles, directory_profiles, coupling_matrix). Prefer thresholded evidence (for example: fan_in < 5 for root stragglers, import-affinity > 60%, directories > 10 files with mixed concerns).
|
|
68
|
+
9b. Suggestions must include a staged reorg plan (target folders, move order, and import-update/validation commands).
|
|
69
|
+
11. Ignore prior chat context and any target-threshold assumptions.
|
|
70
|
+
12. Do not edit repository files.
|
|
71
|
+
13. Return ONLY valid JSON, no markdown fences.
|
|
72
|
+
|
|
73
|
+
Scope enums:
|
|
74
|
+
- impact_scope: "local" | "module" | "subsystem" | "codebase"
|
|
75
|
+
- fix_scope: "single_edit" | "multi_file_refactor" | "architectural_change"
|
|
76
|
+
|
|
77
|
+
Output schema:
|
|
78
|
+
{
|
|
79
|
+
"batch": "Full Codebase Sweep",
|
|
80
|
+
"batch_index": 10,
|
|
81
|
+
"assessments": {"<dimension>": <0-100 with one decimal place>},
|
|
82
|
+
"dimension_notes": {
|
|
83
|
+
"<dimension>": {
|
|
84
|
+
"evidence": ["specific code observations"],
|
|
85
|
+
"impact_scope": "local|module|subsystem|codebase",
|
|
86
|
+
"fix_scope": "single_edit|multi_file_refactor|architectural_change",
|
|
87
|
+
"confidence": "high|medium|low",
|
|
88
|
+
"issues_preventing_higher_score": "required when score >85.0",
|
|
89
|
+
"sub_axes": {"abstraction_leverage": 0-100 with one decimal place, "indirection_cost": 0-100 with one decimal place, "interface_honesty": 0-100 with one decimal place} // required for abstraction_fitness when evidence supports it
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"findings": [{
|
|
93
|
+
"dimension": "<dimension>",
|
|
94
|
+
"identifier": "short_id",
|
|
95
|
+
"summary": "one-line defect summary",
|
|
96
|
+
"related_files": ["relative/path.py"],
|
|
97
|
+
"evidence": ["specific code observation"],
|
|
98
|
+
"suggestion": "concrete fix recommendation",
|
|
99
|
+
"confidence": "high|medium|low",
|
|
100
|
+
"impact_scope": "local|module|subsystem|codebase",
|
|
101
|
+
"fix_scope": "single_edit|multi_file_refactor|architectural_change",
|
|
102
|
+
"root_cause_cluster": "optional_cluster_name_when_supported_by_history"
|
|
103
|
+
}],
|
|
104
|
+
"retrospective": {
|
|
105
|
+
"root_causes": ["optional: concise root-cause hypotheses"],
|
|
106
|
+
"likely_symptoms": ["optional: identifiers that look symptom-level"],
|
|
107
|
+
"possible_false_positives": ["optional: prior concept keys likely mis-scoped"]
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
STDOUT:
|
|
113
|
+
{
|
|
114
|
+
"batch": "Full Codebase Sweep",
|
|
115
|
+
"batch_index": 10,
|
|
116
|
+
"assessments": {
|
|
117
|
+
"cross_module_architecture": 90.0,
|
|
118
|
+
"convention_outlier": 92.0,
|
|
119
|
+
"error_consistency": 82.0,
|
|
120
|
+
"abstraction_fitness": 88.0,
|
|
121
|
+
"api_surface_coherence": 89.0,
|
|
122
|
+
"authorization_consistency": 95.0,
|
|
123
|
+
"ai_generated_debt": 93.0,
|
|
124
|
+
"incomplete_migration": 97.0,
|
|
125
|
+
"package_organization": 82.0,
|
|
126
|
+
"high_level_elegance": 88.0,
|
|
127
|
+
"mid_level_elegance": 86.0,
|
|
128
|
+
"low_level_elegance": 84.0,
|
|
129
|
+
"design_coherence": 87.0
|
|
130
|
+
},
|
|
131
|
+
"dimension_notes": {
|
|
132
|
+
"cross_module_architecture": {
|
|
133
|
+
"evidence": [
|
|
134
|
+
"Core types are centralized via `src/types.ts` and imported widely; this creates a hub module with broad blast radius for changes.",
|
|
135
|
+
"Formats depend on shared helpers in `src/formats/request-helpers.ts` and `src/formats/serialize-helpers.ts`, keeping boundary direction mostly consistent."
|
|
136
|
+
],
|
|
137
|
+
"impact_scope": "subsystem",
|
|
138
|
+
"fix_scope": "multi_file_refactor",
|
|
139
|
+
"confidence": "medium",
|
|
140
|
+
"issues_preventing_higher_score": "The type hub in `src/types.ts` concentrates change impact and could be split by concern if the surface grows further."
|
|
141
|
+
},
|
|
142
|
+
"convention_outlier": {
|
|
143
|
+
"evidence": [
|
|
144
|
+
"Format packages follow a consistent `index/parse/schema/serialize` pattern across `src/formats/*`.",
|
|
145
|
+
"Top-level `src/` mixes executable entry points, server runtime, and utilities in one directory, which is consistent but raises organization concerns rather than convention drift."
|
|
146
|
+
],
|
|
147
|
+
"impact_scope": "module",
|
|
148
|
+
"fix_scope": "single_edit",
|
|
149
|
+
"confidence": "low",
|
|
150
|
+
"issues_preventing_higher_score": "The repository largely follows consistent naming and export patterns; remaining issues are about placement rather than conventions."
|
|
151
|
+
},
|
|
152
|
+
"error_consistency": {
|
|
153
|
+
"evidence": [
|
|
154
|
+
"Resolver exceptions in `src/route-handler.ts` are caught and converted into a normal fallback reply, returning HTTP 200 without an error payload.",
|
|
155
|
+
"In `src/formats/responses/parse.ts`, malformed tool entries are silently dropped via `safeParse`, while `src/formats/openai/parse.ts` and `src/formats/anthropic/parse.ts` hard-fail on schema validation."
|
|
156
|
+
],
|
|
157
|
+
"impact_scope": "subsystem",
|
|
158
|
+
"fix_scope": "multi_file_refactor",
|
|
159
|
+
"confidence": "high"
|
|
160
|
+
},
|
|
161
|
+
"abstraction_fitness": {
|
|
162
|
+
"evidence": [
|
|
163
|
+
"The `Format` interface in `src/formats/types.ts` is implemented three times with small per-format differences, so the abstraction is useful but only minimally polymorphic.",
|
|
164
|
+
"`createRouteHandler` in `src/route-handler.ts` bundles parsing, matching, logging, and streaming in one function, suggesting boundary abstractions are only partially leveraged."
|
|
165
|
+
],
|
|
166
|
+
"impact_scope": "module",
|
|
167
|
+
"fix_scope": "multi_file_refactor",
|
|
168
|
+
"confidence": "medium",
|
|
169
|
+
"issues_preventing_higher_score": "Some abstractions are thin wrappers with limited leverage outside the current three formats.",
|
|
170
|
+
"sub_axes": {
|
|
171
|
+
"abstraction_leverage": 86.0,
|
|
172
|
+
"indirection_cost": 88.0,
|
|
173
|
+
"interface_honesty": 90.0
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
"api_surface_coherence": {
|
|
177
|
+
"evidence": [
|
|
178
|
+
"`src/cli-validators.ts` exposes both sync and async parsers (`parseHost` is async, others are sync), which makes the module’s API shape uneven.",
|
|
179
|
+
"The server surface is small and consistent (single `MockServer` entry point plus `createMock`)."
|
|
180
|
+
],
|
|
181
|
+
"impact_scope": "local",
|
|
182
|
+
"fix_scope": "single_edit",
|
|
183
|
+
"confidence": "medium",
|
|
184
|
+
"issues_preventing_higher_score": "Mixed async/sync validators in a single module create minor API irregularity for consumers."
|
|
185
|
+
},
|
|
186
|
+
"authorization_consistency": {
|
|
187
|
+
"evidence": [
|
|
188
|
+
"The only route handler is created in `src/mock-server.ts` and has no auth checks, indicating a fully public mock server."
|
|
189
|
+
],
|
|
190
|
+
"impact_scope": "module",
|
|
191
|
+
"fix_scope": "single_edit",
|
|
192
|
+
"confidence": "low",
|
|
193
|
+
"issues_preventing_higher_score": "Auth posture is implicit rather than explicit; there is no in-code signal that the endpoints are intentionally unauthenticated."
|
|
194
|
+
},
|
|
195
|
+
"ai_generated_debt": {
|
|
196
|
+
"evidence": [
|
|
197
|
+
"Comment ratio is high in `src/types/request.ts` and `src/types/rule.ts` compared with the codebase average, suggesting some doc-comment bulk.",
|
|
198
|
+
"Comments mostly describe semantics and appear purposeful rather than restating code."
|
|
199
|
+
],
|
|
200
|
+
"impact_scope": "local",
|
|
201
|
+
"fix_scope": "single_edit",
|
|
202
|
+
"confidence": "low",
|
|
203
|
+
"issues_preventing_higher_score": "Comment density in the types layer is higher than typical and could become noise if it grows further."
|
|
204
|
+
},
|
|
205
|
+
"incomplete_migration": {
|
|
206
|
+
"evidence": [
|
|
207
|
+
"No deprecated markers or migration TODOs appear in the reviewed production files.",
|
|
208
|
+
"No mixed JS/TS implementations exist for the same module in the reviewed set."
|
|
209
|
+
],
|
|
210
|
+
"impact_scope": "local",
|
|
211
|
+
"fix_scope": "single_edit",
|
|
212
|
+
"confidence": "high",
|
|
213
|
+
"issues_preventing_higher_score": "No explicit migration scaffolding is visible; score held below 100 only due to limited evidence scope."
|
|
214
|
+
},
|
|
215
|
+
"package_organization": {
|
|
216
|
+
"evidence": [
|
|
217
|
+
"`src/` contains 11 mixed-concern files (cli, server runtime, loader, logger, rule engine), exceeding the >10 file flat-directory threshold.",
|
|
218
|
+
"`src/` avg fan-in is 3.8 and coupling shows bidirectional edges between `src/` and `src/formats/`, suggesting responsibilities are not strongly separated."
|
|
219
|
+
],
|
|
220
|
+
"impact_scope": "codebase",
|
|
221
|
+
"fix_scope": "architectural_change",
|
|
222
|
+
"confidence": "high"
|
|
223
|
+
},
|
|
224
|
+
"high_level_elegance": {
|
|
225
|
+
"evidence": [
|
|
226
|
+
"Primary domain concepts (formats, server, rules) are clear, but are all housed in a flat `src/` root alongside CLI utilities.",
|
|
227
|
+
"The public API surface in `src/index.ts` is concise but coupled to internal layout."
|
|
228
|
+
],
|
|
229
|
+
"impact_scope": "subsystem",
|
|
230
|
+
"fix_scope": "multi_file_refactor",
|
|
231
|
+
"confidence": "medium",
|
|
232
|
+
"issues_preventing_higher_score": "Top-level decomposition would read cleaner if CLI/runtime utilities were separated from core server and rule-engine concerns."
|
|
233
|
+
},
|
|
234
|
+
"mid_level_elegance": {
|
|
235
|
+
"evidence": [
|
|
236
|
+
"`createRouteHandler` orchestrates parsing, matching, error handling, logging, and streaming directly, making handoffs between layers less explicit.",
|
|
237
|
+
"Format-specific parsing and serialization are cleanly separated, reducing translation friction."
|
|
238
|
+
],
|
|
239
|
+
"impact_scope": "module",
|
|
240
|
+
"fix_scope": "multi_file_refactor",
|
|
241
|
+
"confidence": "medium",
|
|
242
|
+
"issues_preventing_higher_score": "Integration seams are mostly predictable but could be clearer with narrower handler responsibilities."
|
|
243
|
+
},
|
|
244
|
+
"low_level_elegance": {
|
|
245
|
+
"evidence": [
|
|
246
|
+
"ID generation in `src/formats/serialize-helpers.ts` uses `Date.now()` without collision avoidance, which can produce duplicate IDs under same-millisecond calls."
|
|
247
|
+
],
|
|
248
|
+
"impact_scope": "subsystem",
|
|
249
|
+
"fix_scope": "single_edit",
|
|
250
|
+
"confidence": "high"
|
|
251
|
+
},
|
|
252
|
+
"design_coherence": {
|
|
253
|
+
"evidence": [
|
|
254
|
+
"`createRouteHandler` bundles multiple responsibilities (parsing, matching, metrics/logging, response mode selection), which could be separated for clearer intent.",
|
|
255
|
+
"Helper modules under `src/formats/` are focused and coherent."
|
|
256
|
+
],
|
|
257
|
+
"impact_scope": "module",
|
|
258
|
+
"fix_scope": "multi_file_refactor",
|
|
259
|
+
"confidence": "medium",
|
|
260
|
+
"issues_preventing_higher_score": "Some core control flow aggregates multiple responsibilities in one function, reducing separation of concerns."
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
"findings": [
|
|
264
|
+
{
|
|
265
|
+
"dimension": "error_consistency",
|
|
266
|
+
"identifier": "resolver_errors_swallowed",
|
|
267
|
+
"summary": "Resolver exceptions are swallowed and returned as normal fallback replies",
|
|
268
|
+
"related_files": [
|
|
269
|
+
"src/route-handler.ts",
|
|
270
|
+
"src/types/reply.ts",
|
|
271
|
+
"src/mock-server.ts"
|
|
272
|
+
],
|
|
273
|
+
"evidence": [
|
|
274
|
+
"`resolveReply` catches resolver errors and returns `normalizeReply(fallback)` with no error signal.",
|
|
275
|
+
"Downstream in the handler, the fallback reply is serialized as a normal 200 response if it doesn’t set `error`."
|
|
276
|
+
],
|
|
277
|
+
"suggestion": "Surface resolver failures explicitly: either rethrow and let Fastify return 500, or translate to `ReplyObject.error` with a 5xx status. Update `resolveReply` and `createRouteHandler` to propagate an error reply, and document the error contract in `src/types/reply.ts`.",
|
|
278
|
+
"confidence": "high",
|
|
279
|
+
"impact_scope": "subsystem",
|
|
280
|
+
"fix_scope": "multi_file_refactor",
|
|
281
|
+
"root_cause_cluster": "error_handling_policy"
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
"dimension": "error_consistency",
|
|
285
|
+
"identifier": "responses_tools_silent_drop",
|
|
286
|
+
"summary": "Responses tool parsing silently drops invalid tools unlike other formats",
|
|
287
|
+
"related_files": [
|
|
288
|
+
"src/formats/responses/parse.ts",
|
|
289
|
+
"src/formats/openai/parse.ts",
|
|
290
|
+
"src/formats/anthropic/parse.ts"
|
|
291
|
+
],
|
|
292
|
+
"evidence": [
|
|
293
|
+
"`parseTools` in responses uses `FunctionToolSchema.safeParse` and filters out failed parses, so malformed tool entries are ignored.",
|
|
294
|
+
"OpenAI and Anthropic requests rely on schema parsing that fails the whole request on invalid structures."
|
|
295
|
+
],
|
|
296
|
+
"suggestion": "Align tool-validation behavior across formats: either treat invalid tools as a request error for responses, or document and apply a consistent “best-effort” policy across all formats. Prefer failing the request for invalid tools to keep error handling predictable.",
|
|
297
|
+
"confidence": "medium",
|
|
298
|
+
"impact_scope": "module",
|
|
299
|
+
"fix_scope": "multi_file_refactor",
|
|
300
|
+
"root_cause_cluster": "error_handling_policy"
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
"dimension": "low_level_elegance",
|
|
304
|
+
"identifier": "non_unique_ids",
|
|
305
|
+
"summary": "ID generation relies on Date.now(), risking collisions under concurrency",
|
|
306
|
+
"related_files": [
|
|
307
|
+
"src/formats/serialize-helpers.ts",
|
|
308
|
+
"src/formats/openai/serialize.ts",
|
|
309
|
+
"src/formats/responses/serialize.ts"
|
|
310
|
+
],
|
|
311
|
+
"evidence": [
|
|
312
|
+
"`genId` and `toolId` use `Date.now().toString(36)` which can repeat in the same millisecond.",
|
|
313
|
+
"Streaming serializers call these helpers multiple times per request, and concurrent requests can produce duplicate IDs."
|
|
314
|
+
],
|
|
315
|
+
"suggestion": "Switch to a collision-resistant ID source (e.g., `crypto.randomUUID()` or a process-wide monotonic counter with a random prefix). Update `genId`/`toolId` and regenerate IDs in the serializers to avoid duplicate response IDs.",
|
|
316
|
+
"confidence": "high",
|
|
317
|
+
"impact_scope": "subsystem",
|
|
318
|
+
"fix_scope": "single_edit"
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
"dimension": "package_organization",
|
|
322
|
+
"identifier": "flat_src_mixed_concerns",
|
|
323
|
+
"summary": "Flat `src/` directory mixes CLI, server, and rule-engine concerns",
|
|
324
|
+
"related_files": [
|
|
325
|
+
"src/cli.ts",
|
|
326
|
+
"src/mock-server.ts",
|
|
327
|
+
"src/route-handler.ts",
|
|
328
|
+
"src/loader.ts"
|
|
329
|
+
],
|
|
330
|
+
"evidence": [
|
|
331
|
+
"`src/` contains 11 files across CLI, HTTP server, rule engine, logging, and utilities (directory profile shows `file_count: 11`).",
|
|
332
|
+
"Coupling shows bidirectional edges between `src/` and `src/formats/`, indicating blurred boundaries at the top level."
|
|
333
|
+
],
|
|
334
|
+
"suggestion": "Stage a reorg to clarify ownership: 1) create `src/server/` and move `mock-server.ts`, `route-handler.ts`, `sse-writer.ts`; 2) create `src/cli/` and move `cli.ts`, `cli-validators.ts`; 3) create `src/core/` (or `src/rules/`) and move `rule-engine.ts`, `loader.ts`, `history.ts`, `logger.ts`; 4) update imports and `src/index.ts` exports; 5) run `npm test` or `vitest` to validate and adjust any path-based tooling.",
|
|
335
|
+
"confidence": "high",
|
|
336
|
+
"impact_scope": "codebase",
|
|
337
|
+
"fix_scope": "architectural_change"
|
|
338
|
+
}
|
|
339
|
+
],
|
|
340
|
+
"retrospective": {
|
|
341
|
+
"root_causes": [
|
|
342
|
+
"Error handling policy is inconsistent across request parsing, resolver execution, and tool validation paths.",
|
|
343
|
+
"Top-level `src/` is serving as a catch-all for multiple concerns, suggesting missing package boundaries."
|
|
344
|
+
],
|
|
345
|
+
"likely_symptoms": [
|
|
346
|
+
"resolver_errors_swallowed",
|
|
347
|
+
"responses_tools_silent_drop",
|
|
348
|
+
"flat_src_mixed_concerns"
|
|
349
|
+
],
|
|
350
|
+
"possible_false_positives": [
|
|
351
|
+
"non_unique_ids"
|
|
352
|
+
]
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
STDERR:
|
|
358
|
+
OpenAI Codex v0.114.0 (research preview)
|
|
359
|
+
--------
|
|
360
|
+
workdir: /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server
|
|
361
|
+
model: gpt-5.2-codex
|
|
362
|
+
provider: openai
|
|
363
|
+
approval: never
|
|
364
|
+
sandbox: workspace-write [workdir, /tmp, $TMPDIR, /Users/suyash.x.srijan/.codex/memories]
|
|
365
|
+
reasoning effort: low
|
|
366
|
+
reasoning summaries: none
|
|
367
|
+
session id: 019cf2e6-7457-76b3-b1f4-47142d799606
|
|
368
|
+
--------
|
|
369
|
+
user
|
|
370
|
+
You are a focused subagent reviewer for a single holistic investigation batch.
|
|
371
|
+
|
|
372
|
+
Repository root: /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server
|
|
373
|
+
Blind packet: /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/review_packet_blind.json
|
|
374
|
+
Batch index: 10
|
|
375
|
+
Batch name: Full Codebase Sweep
|
|
376
|
+
Batch dimensions: cross_module_architecture, convention_outlier, error_consistency, abstraction_fitness, api_surface_coherence, authorization_consistency, ai_generated_debt, incomplete_migration, package_organization, high_level_elegance, mid_level_elegance, low_level_elegance, design_coherence
|
|
377
|
+
Batch rationale: thorough default: evaluate cross-cutting quality across all production files
|
|
378
|
+
|
|
379
|
+
Files assigned:
|
|
380
|
+
- src/cli-validators.ts
|
|
381
|
+
- src/cli.ts
|
|
382
|
+
- src/formats/anthropic/index.ts
|
|
383
|
+
- src/formats/anthropic/parse.ts
|
|
384
|
+
- src/formats/anthropic/schema.ts
|
|
385
|
+
- src/formats/anthropic/serialize.ts
|
|
386
|
+
- src/formats/openai/index.ts
|
|
387
|
+
- src/formats/openai/parse.ts
|
|
388
|
+
- src/formats/openai/schema.ts
|
|
389
|
+
- src/formats/openai/serialize.ts
|
|
390
|
+
- src/formats/request-helpers.ts
|
|
391
|
+
- src/formats/responses/index.ts
|
|
392
|
+
- src/formats/responses/parse.ts
|
|
393
|
+
- src/formats/responses/schema.ts
|
|
394
|
+
- src/formats/responses/serialize.ts
|
|
395
|
+
- src/formats/serialize-helpers.ts
|
|
396
|
+
- src/formats/types.ts
|
|
397
|
+
- src/history.ts
|
|
398
|
+
- src/index.ts
|
|
399
|
+
- src/loader.ts
|
|
400
|
+
- src/logger.ts
|
|
401
|
+
- src/mock-server.ts
|
|
402
|
+
- src/route-handler.ts
|
|
403
|
+
- src/rule-engine.ts
|
|
404
|
+
- src/sse-writer.ts
|
|
405
|
+
- src/types.ts
|
|
406
|
+
- src/types/reply.ts
|
|
407
|
+
- src/types/request.ts
|
|
408
|
+
- src/types/rule.ts
|
|
409
|
+
- vitest.config.ts
|
|
410
|
+
|
|
411
|
+
Task requirements:
|
|
412
|
+
1. Read the blind packet and follow `system_prompt` constraints exactly.
|
|
413
|
+
1a. If previously flagged issues are listed above, use them as context for your review.
|
|
414
|
+
Verify whether each still applies to the current code. Do not re-report fixed or
|
|
415
|
+
wontfix issues. Use them as starting points to look deeper — inspect adjacent code
|
|
416
|
+
and related modules for defects the prior review may have missed.
|
|
417
|
+
1c. Think structurally: when you spot multiple individual issues that share a common
|
|
418
|
+
root cause (missing abstraction, duplicated pattern, inconsistent convention),
|
|
419
|
+
explain the deeper structural issue in the finding, not just the surface symptom.
|
|
420
|
+
If the pattern is significant enough, report the structural issue as its own finding
|
|
421
|
+
with appropriate fix_scope ('multi_file_refactor' or 'architectural_change') and
|
|
422
|
+
use `root_cause_cluster` to connect related symptom findings together.
|
|
423
|
+
2. Evaluate ONLY listed files and ONLY listed dimensions for this batch.
|
|
424
|
+
3. Return 0-13 high-quality findings for this batch (empty array allowed).
|
|
425
|
+
3a. Do not suppress real defects to keep scores high; report every material issue you can support with evidence.
|
|
426
|
+
3b. Do not default to 100. Reserve 100 for genuinely exemplary evidence in this batch.
|
|
427
|
+
4. Score/finding consistency is required: broader or more severe findings MUST lower dimension scores.
|
|
428
|
+
4a. Any dimension scored below 85.0 MUST include explicit feedback: add at least one finding with the same `dimension` and a non-empty actionable `suggestion`.
|
|
429
|
+
5. Every finding must include `related_files` with at least 2 files when possible.
|
|
430
|
+
6. Every finding must include `dimension`, `identifier`, `summary`, `evidence`, `suggestion`, and `confidence`.
|
|
431
|
+
7. Every finding must include `impact_scope` and `fix_scope`.
|
|
432
|
+
8. Every scored dimension MUST include dimension_notes with concrete evidence.
|
|
433
|
+
9. If a dimension score is >85.0, include `issues_preventing_higher_score` in dimension_notes.
|
|
434
|
+
10. Use exactly one decimal place for every assessment and abstraction sub-axis score.
|
|
435
|
+
9a. For package_organization, ground scoring in objective structure signals from `holistic_context.structure` (root_files fan_in/fan_out roles, directory_profiles, coupling_matrix). Prefer thresholded evidence (for example: fan_in < 5 for root stragglers, import-affinity > 60%, directories > 10 files with mixed concerns).
|
|
436
|
+
9b. Suggestions must include a staged reorg plan (target folders, move order, and import-update/validation commands).
|
|
437
|
+
11. Ignore prior chat context and any target-threshold assumptions.
|
|
438
|
+
12. Do not edit repository files.
|
|
439
|
+
13. Return ONLY valid JSON, no markdown fences.
|
|
440
|
+
|
|
441
|
+
Scope enums:
|
|
442
|
+
- impact_scope: "local" | "module" | "subsystem" | "codebase"
|
|
443
|
+
- fix_scope: "single_edit" | "multi_file_refactor" | "architectural_change"
|
|
444
|
+
|
|
445
|
+
Output schema:
|
|
446
|
+
{
|
|
447
|
+
"batch": "Full Codebase Sweep",
|
|
448
|
+
"batch_index": 10,
|
|
449
|
+
"assessments": {"<dimension>": <0-100 with one decimal place>},
|
|
450
|
+
"dimension_notes": {
|
|
451
|
+
"<dimension>": {
|
|
452
|
+
"evidence": ["specific code observations"],
|
|
453
|
+
"impact_scope": "local|module|subsystem|codebase",
|
|
454
|
+
"fix_scope": "single_edit|multi_file_refactor|architectural_change",
|
|
455
|
+
"confidence": "high|medium|low",
|
|
456
|
+
"issues_preventing_higher_score": "required when score >85.0",
|
|
457
|
+
"sub_axes": {"abstraction_leverage": 0-100 with one decimal place, "indirection_cost": 0-100 with one decimal place, "interface_honesty": 0-100 with one decimal place} // required for abstraction_fitness when evidence supports it
|
|
458
|
+
}
|
|
459
|
+
},
|
|
460
|
+
"findings": [{
|
|
461
|
+
"dimension": "<dimension>",
|
|
462
|
+
"identifier": "short_id",
|
|
463
|
+
"summary": "one-line defect summary",
|
|
464
|
+
"related_files": ["relative/path.py"],
|
|
465
|
+
"evidence": ["specific code observation"],
|
|
466
|
+
"suggestion": "concrete fix recommendation",
|
|
467
|
+
"confidence": "high|medium|low",
|
|
468
|
+
"impact_scope": "local|module|subsystem|codebase",
|
|
469
|
+
"fix_scope": "single_edit|multi_file_refactor|architectural_change",
|
|
470
|
+
"root_cause_cluster": "optional_cluster_name_when_supported_by_history"
|
|
471
|
+
}],
|
|
472
|
+
"retrospective": {
|
|
473
|
+
"root_causes": ["optional: concise root-cause hypotheses"],
|
|
474
|
+
"likely_symptoms": ["optional: identifiers that look symptom-level"],
|
|
475
|
+
"possible_false_positives": ["optional: prior concept keys likely mis-scoped"]
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
mcp startup: no servers
|
|
480
|
+
|
|
481
|
+
thinking
|
|
482
|
+
**Planning to read blind packet**
|
|
483
|
+
exec
|
|
484
|
+
/bin/zsh -lc ls in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
485
|
+
LICENCE
|
|
486
|
+
README.md
|
|
487
|
+
coverage
|
|
488
|
+
dist
|
|
489
|
+
docs
|
|
490
|
+
node_modules
|
|
491
|
+
package-lock.json
|
|
492
|
+
package.json
|
|
493
|
+
scorecard.png
|
|
494
|
+
src
|
|
495
|
+
test
|
|
496
|
+
tsconfig.json
|
|
497
|
+
tsconfig.test.json
|
|
498
|
+
vitest.config.ts
|
|
499
|
+
exec
|
|
500
|
+
/bin/zsh -lc 'cat /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/review_packet_blind.json' in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
501
|
+
{
|
|
502
|
+
"command": "review",
|
|
503
|
+
"mode": "holistic",
|
|
504
|
+
"language": "typescript",
|
|
505
|
+
"dimensions": [
|
|
506
|
+
"cross_module_architecture",
|
|
507
|
+
"convention_outlier",
|
|
508
|
+
"error_consistency",
|
|
509
|
+
"abstraction_fitness",
|
|
510
|
+
"api_surface_coherence",
|
|
511
|
+
"authorization_consistency",
|
|
512
|
+
"ai_generated_debt",
|
|
513
|
+
"incomplete_migration",
|
|
514
|
+
"package_organization",
|
|
515
|
+
"high_level_elegance",
|
|
516
|
+
"mid_level_elegance",
|
|
517
|
+
"low_level_elegance",
|
|
518
|
+
"design_coherence"
|
|
519
|
+
],
|
|
520
|
+
"dimension_prompts": {
|
|
521
|
+
|
|
522
|
+
thinking
|
|
523
|
+
**Choosing file reading approach**
|
|
524
|
+
exec
|
|
525
|
+
/bin/zsh -lc "sed -n '1,220p' src/cli-validators.ts" in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
526
|
+
import { isIP } from "node:net";
|
|
527
|
+
import { lookup } from "node:dns/promises";
|
|
528
|
+
import { LEVEL_PRIORITY, type LogLevel } from "./logger.js";
|
|
529
|
+
|
|
530
|
+
const VALID_LOG_LEVELS = Object.keys(LEVEL_PRIORITY) as LogLevel[];
|
|
531
|
+
|
|
532
|
+
function isLogLevel(value: string): value is LogLevel {
|
|
533
|
+
return value in LEVEL_PRIORITY;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const MAX_PORT = 65535;
|
|
537
|
+
|
|
538
|
+
export function parsePort(value: string): number {
|
|
539
|
+
const port = parseInt(value, 10);
|
|
540
|
+
if (isNaN(port) || port < 1 || port > MAX_PORT) {
|
|
541
|
+
throw new Error(`Invalid port "${value}". Must be 1-${String(MAX_PORT)}.`);
|
|
542
|
+
}
|
|
543
|
+
return port;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
exec
|
|
547
|
+
/bin/zsh -lc "sed -n '1,260p' src/cli.ts" in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
548
|
+
#!/usr/bin/env node
|
|
549
|
+
|
|
550
|
+
import { watch } from "node:fs";
|
|
551
|
+
import { createRequire } from "node:module";
|
|
552
|
+
import { Command } from "commander";
|
|
553
|
+
import pc from "picocolors";
|
|
554
|
+
import { MockServer } from "./mock-server.js";
|
|
555
|
+
import { Logger } from "./logger.js";
|
|
556
|
+
import {
|
|
557
|
+
parsePort,
|
|
558
|
+
parseHost,
|
|
559
|
+
parseLogLevel,
|
|
560
|
+
parseChunkSize,
|
|
561
|
+
parseLatency,
|
|
562
|
+
} from "./cli-validators.js";
|
|
563
|
+
|
|
564
|
+
const require = createRequire(import.meta.url);
|
|
565
|
+
const { version } = require("../package.json") as { version: string };
|
|
566
|
+
|
|
567
|
+
const WATCH_DEBOUNCE_MS = 100;
|
|
568
|
+
exec
|
|
569
|
+
/bin/zsh -lc "sed -n '1,220p' src/formats/anthropic/index.ts" in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
570
|
+
import type { Format } from "../types.js";
|
|
571
|
+
import { isStreaming } from "../request-helpers.js";
|
|
572
|
+
import { parseRequest } from "./parse.js";
|
|
573
|
+
import { serialize, serializeComplete, serializeError } from "./serialize.js";
|
|
574
|
+
|
|
575
|
+
export const anthropicFormat: Format = {
|
|
576
|
+
name: "anthropic",
|
|
577
|
+
route: "/v1/messages",
|
|
578
|
+
parseRequest,
|
|
579
|
+
isStreaming,
|
|
580
|
+
serialize,
|
|
581
|
+
serializeComplete,
|
|
582
|
+
serializeError,
|
|
583
|
+
};
|
|
584
|
+
exec
|
|
585
|
+
/bin/zsh -lc "sed -n '1,260p' src/formats/anthropic/parse.ts" in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
586
|
+
import type { MockRequest, Message, ToolDef } from "../../types.js";
|
|
587
|
+
import { buildMockRequest, type RequestMeta } from "../request-helpers.js";
|
|
588
|
+
import { AnthropicRequestSchema, type AnthropicRequest } from "./schema.js";
|
|
589
|
+
|
|
590
|
+
function extractSystem(system: AnthropicRequest["system"]): Message[] {
|
|
591
|
+
if (system == null) return [];
|
|
592
|
+
if (typeof system === "string")
|
|
593
|
+
return system ? [{ role: "system", content: system }] : [];
|
|
594
|
+
const text = system.map((b) => b.text).join("\n");
|
|
595
|
+
return text ? [{ role: "system", content: text }] : [];
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function extractContent(
|
|
599
|
+
content: AnthropicRequest["messages"][number]["content"],
|
|
600
|
+
): {
|
|
601
|
+
content: string;
|
|
602
|
+
toolCallId?: string | undefined;
|
|
603
|
+
} {
|
|
604
|
+
if (typeof content === "string") return { content };
|
|
605
|
+
const text = content
|
|
606
|
+
exec
|
|
607
|
+
/bin/zsh -lc "sed -n '1,260p' src/formats/anthropic/schema.ts" in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
608
|
+
import { z } from "zod";
|
|
609
|
+
|
|
610
|
+
export {
|
|
611
|
+
AnthropicRequestSchema,
|
|
612
|
+
type AnthropicRequest,
|
|
613
|
+
} from "llm-schemas/anthropic";
|
|
614
|
+
|
|
615
|
+
const ResponseContentBlockSchema = z.object({
|
|
616
|
+
type: z.string(),
|
|
617
|
+
text: z.string().optional(),
|
|
618
|
+
thinking: z.string().optional(),
|
|
619
|
+
id: z.string().optional(),
|
|
620
|
+
name: z.string().optional(),
|
|
621
|
+
input: z.unknown().optional(),
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
export const AnthropicMessageStartSchema = z.object({
|
|
625
|
+
message: z.object({
|
|
626
|
+
id: z.string(),
|
|
627
|
+
type: z.literal("message"),
|
|
628
|
+
exec
|
|
629
|
+
/bin/zsh -lc "sed -n '1,260p' src/formats/anthropic/serialize.ts" in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
630
|
+
import type { ReplyObject, ReplyOptions } from "../../types.js";
|
|
631
|
+
import type { SSEChunk } from "../types.js";
|
|
632
|
+
import {
|
|
633
|
+
splitText,
|
|
634
|
+
genId,
|
|
635
|
+
toolId,
|
|
636
|
+
shouldEmitText,
|
|
637
|
+
finishReason,
|
|
638
|
+
DEFAULT_USAGE,
|
|
639
|
+
} from "../serialize-helpers.js";
|
|
640
|
+
|
|
641
|
+
function buildUsage(usage: { input: number; output: number }) {
|
|
642
|
+
return { input_tokens: usage.input, output_tokens: usage.output };
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function contentBlock(
|
|
646
|
+
index: number,
|
|
647
|
+
startBlock: unknown,
|
|
648
|
+
deltas: SSEChunk[],
|
|
649
|
+
): SSEChunk[] {
|
|
650
|
+
exec
|
|
651
|
+
/bin/zsh -lc "sed -n '1,240p' src/formats/openai/index.ts" in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
652
|
+
import type { Format } from "../types.js";
|
|
653
|
+
import { isStreaming } from "../request-helpers.js";
|
|
654
|
+
import { parseRequest } from "./parse.js";
|
|
655
|
+
import { serialize, serializeComplete, serializeError } from "./serialize.js";
|
|
656
|
+
|
|
657
|
+
export const openaiFormat: Format = {
|
|
658
|
+
name: "openai",
|
|
659
|
+
route: "/v1/chat/completions",
|
|
660
|
+
parseRequest,
|
|
661
|
+
isStreaming,
|
|
662
|
+
serialize,
|
|
663
|
+
serializeComplete,
|
|
664
|
+
serializeError,
|
|
665
|
+
};
|
|
666
|
+
exec
|
|
667
|
+
/bin/zsh -lc "sed -n '1,260p' src/formats/openai/parse.ts" in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
668
|
+
import type { MockRequest, Message, ToolDef } from "../../types.js";
|
|
669
|
+
import { buildMockRequest, type RequestMeta } from "../request-helpers.js";
|
|
670
|
+
import { OpenAIRequestSchema, type OpenAIRequest } from "./schema.js";
|
|
671
|
+
|
|
672
|
+
function extractContent(
|
|
673
|
+
content: OpenAIRequest["messages"][number]["content"],
|
|
674
|
+
): string {
|
|
675
|
+
if (content == null) return "";
|
|
676
|
+
if (typeof content === "string") return content;
|
|
677
|
+
return content
|
|
678
|
+
.filter((p) => p.type === "text" && p.text !== undefined)
|
|
679
|
+
.map((p) => p.text!)
|
|
680
|
+
.join("\n");
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
function parseMessages(req: OpenAIRequest): readonly Message[] {
|
|
684
|
+
return req.messages.map((m) => ({
|
|
685
|
+
role: m.role === "developer" ? "system" : (m.role ?? "user"),
|
|
686
|
+
content: extractContent(m.content),
|
|
687
|
+
...(m.tool_call_id !== undefined && { toolCallId: m.tool_call_id }),
|
|
688
|
+
exec
|
|
689
|
+
/bin/zsh -lc "sed -n '1,260p' src/formats/openai/schema.ts" in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
690
|
+
import { z } from "zod";
|
|
691
|
+
|
|
692
|
+
export {
|
|
693
|
+
OpenAIRequestSchema,
|
|
694
|
+
type OpenAIRequest,
|
|
695
|
+
} from "llm-schemas/openai/chat-completions";
|
|
696
|
+
|
|
697
|
+
const ToolCallResponseSchema = z.object({
|
|
698
|
+
id: z.string(),
|
|
699
|
+
type: z.string(),
|
|
700
|
+
function: z.object({ name: z.string(), arguments: z.string() }),
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
const UsageSchema = z.object({
|
|
704
|
+
prompt_tokens: z.number(),
|
|
705
|
+
completion_tokens: z.number(),
|
|
706
|
+
total_tokens: z.number(),
|
|
707
|
+
prompt_tokens_details: z
|
|
708
|
+
.object({
|
|
709
|
+
cached_tokens: z.number().optional(),
|
|
710
|
+
exec
|
|
711
|
+
/bin/zsh -lc "sed -n '1,260p' src/formats/openai/serialize.ts" in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
712
|
+
import type { ReplyObject, ReplyOptions } from "../../types.js";
|
|
713
|
+
import type { SSEChunk } from "../types.js";
|
|
714
|
+
import {
|
|
715
|
+
splitText,
|
|
716
|
+
genId,
|
|
717
|
+
toolId,
|
|
718
|
+
finishReason,
|
|
719
|
+
MS_PER_SECOND,
|
|
720
|
+
DEFAULT_USAGE,
|
|
721
|
+
} from "../serialize-helpers.js";
|
|
722
|
+
|
|
723
|
+
function buildUsage(usage: { input: number; output: number }) {
|
|
724
|
+
return {
|
|
725
|
+
prompt_tokens: usage.input,
|
|
726
|
+
completion_tokens: usage.output,
|
|
727
|
+
total_tokens: usage.input + usage.output,
|
|
728
|
+
prompt_tokens_details: { cached_tokens: 0, audio_tokens: 0 },
|
|
729
|
+
completion_tokens_details: {
|
|
730
|
+
reasoning_tokens: 0,
|
|
731
|
+
audio_tokens: 0,
|
|
732
|
+
exec
|
|
733
|
+
/bin/zsh -lc "sed -n '1,260p' src/formats/request-helpers.ts" in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
734
|
+
import type { FormatName, Message, MockRequest, ToolDef } from "../types.js";
|
|
735
|
+
|
|
736
|
+
function asRecord(body: unknown): Record<string, unknown> {
|
|
737
|
+
if (typeof body === "object" && body !== null)
|
|
738
|
+
return body as Record<string, unknown>;
|
|
739
|
+
return {};
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
export function isStreaming(body: unknown): boolean {
|
|
743
|
+
return asRecord(body)["stream"] !== false;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
export interface RequestMeta {
|
|
747
|
+
readonly headers: Readonly<Record<string, string | undefined>>;
|
|
748
|
+
readonly path: string;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
const EMPTY_META: RequestMeta = { headers: {}, path: "" };
|
|
752
|
+
|
|
753
|
+
interface ParsedBody {
|
|
754
|
+
exec
|
|
755
|
+
/bin/zsh -lc "sed -n '1,260p' src/formats/responses/index.ts" in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
756
|
+
import type { Format } from "../types.js";
|
|
757
|
+
import { isStreaming } from "../request-helpers.js";
|
|
758
|
+
import { parseRequest } from "./parse.js";
|
|
759
|
+
import { serialize, serializeComplete, serializeError } from "./serialize.js";
|
|
760
|
+
|
|
761
|
+
export const responsesFormat: Format = {
|
|
762
|
+
name: "responses",
|
|
763
|
+
route: "/v1/responses",
|
|
764
|
+
parseRequest,
|
|
765
|
+
isStreaming,
|
|
766
|
+
serialize,
|
|
767
|
+
serializeComplete,
|
|
768
|
+
serializeError,
|
|
769
|
+
};
|
|
770
|
+
exec
|
|
771
|
+
/bin/zsh -lc "sed -n '1,260p' src/formats/responses/parse.ts" in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
772
|
+
import type { MockRequest, Message, ToolDef } from "../../types.js";
|
|
773
|
+
import { buildMockRequest, type RequestMeta } from "../request-helpers.js";
|
|
774
|
+
import {
|
|
775
|
+
ResponsesRequestSchema,
|
|
776
|
+
FunctionToolSchema,
|
|
777
|
+
type ResponsesRequest,
|
|
778
|
+
} from "./schema.js";
|
|
779
|
+
|
|
780
|
+
function extractInputContent(
|
|
781
|
+
content: string | Record<string, unknown>[],
|
|
782
|
+
): string {
|
|
783
|
+
if (typeof content === "string") return content;
|
|
784
|
+
return content
|
|
785
|
+
.filter((b) => b["type"] === "input_text" || b["type"] === "text")
|
|
786
|
+
.map((b) => String(b["text"] ?? ""))
|
|
787
|
+
.join("\n");
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
function parseInput(req: ResponsesRequest): readonly Message[] {
|
|
791
|
+
const instructions: Message[] = req.instructions
|
|
792
|
+
exec
|
|
793
|
+
/bin/zsh -lc "sed -n '1,260p' src/formats/responses/schema.ts" in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
794
|
+
import { z } from "zod";
|
|
795
|
+
|
|
796
|
+
export {
|
|
797
|
+
ResponsesRequestSchema,
|
|
798
|
+
FunctionToolSchema,
|
|
799
|
+
type ResponsesRequest,
|
|
800
|
+
} from "llm-schemas/openai/responses";
|
|
801
|
+
|
|
802
|
+
const OutputContentSchema = z.object({
|
|
803
|
+
type: z.string(),
|
|
804
|
+
text: z.string(),
|
|
805
|
+
annotations: z.array(z.unknown()).optional(),
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
const OutputItemSchema = z.object({
|
|
809
|
+
type: z.string(),
|
|
810
|
+
id: z.string().optional(),
|
|
811
|
+
status: z.string().optional(),
|
|
812
|
+
role: z.string().optional(),
|
|
813
|
+
content: z.array(OutputContentSchema).optional(),
|
|
814
|
+
exec
|
|
815
|
+
/bin/zsh -lc "sed -n '1,260p' src/formats/responses/serialize.ts" in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
816
|
+
import type { ReplyObject, ReplyOptions, ToolCall } from "../../types.js";
|
|
817
|
+
import type { SSEChunk } from "../types.js";
|
|
818
|
+
import {
|
|
819
|
+
splitText,
|
|
820
|
+
genId,
|
|
821
|
+
toolId,
|
|
822
|
+
shouldEmitText,
|
|
823
|
+
MS_PER_SECOND,
|
|
824
|
+
DEFAULT_USAGE,
|
|
825
|
+
} from "../serialize-helpers.js";
|
|
826
|
+
|
|
827
|
+
function buildUsage(usage: { input: number; output: number }) {
|
|
828
|
+
return {
|
|
829
|
+
input_tokens: usage.input,
|
|
830
|
+
output_tokens: usage.output,
|
|
831
|
+
total_tokens: usage.input + usage.output,
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
interface StreamBlock {
|
|
836
|
+
exec
|
|
837
|
+
/bin/zsh -lc "sed -n '260,520p' src/formats/responses/serialize.ts" in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
838
|
+
reply: ReplyObject,
|
|
839
|
+
model: string,
|
|
840
|
+
): Record<string, unknown> {
|
|
841
|
+
const id = genId("resp");
|
|
842
|
+
const createdAt = Math.floor(Date.now() / MS_PER_SECOND);
|
|
843
|
+
const usage = reply.usage ?? DEFAULT_USAGE;
|
|
844
|
+
|
|
845
|
+
const output: unknown[] = [
|
|
846
|
+
...(reply.reasoning
|
|
847
|
+
? [
|
|
848
|
+
{
|
|
849
|
+
type: "reasoning",
|
|
850
|
+
id: `rs_${genId("rs")}`,
|
|
851
|
+
status: "completed",
|
|
852
|
+
summary: [{ type: "summary_text", text: reply.reasoning }],
|
|
853
|
+
},
|
|
854
|
+
]
|
|
855
|
+
: []),
|
|
856
|
+
...(shouldEmitText(reply)
|
|
857
|
+
? [
|
|
858
|
+
exec
|
|
859
|
+
/bin/zsh -lc "sed -n '1,260p' src/formats/serialize-helpers.ts" in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
860
|
+
import type { ReplyObject } from "../types.js";
|
|
861
|
+
|
|
862
|
+
export const MS_PER_SECOND = 1000;
|
|
863
|
+
const BASE_36 = 36;
|
|
864
|
+
export const DEFAULT_USAGE = { input: 10, output: 5 } as const;
|
|
865
|
+
|
|
866
|
+
export function splitText(text: string, chunkSize: number): string[] {
|
|
867
|
+
if (chunkSize <= 0 || text.length <= chunkSize) return [text];
|
|
868
|
+
const chunks: string[] = [];
|
|
869
|
+
for (let i = 0; i < text.length; i += chunkSize) {
|
|
870
|
+
chunks.push(text.slice(i, i + chunkSize));
|
|
871
|
+
}
|
|
872
|
+
return chunks;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
export function genId(prefix: string): string {
|
|
876
|
+
return `${prefix}_${Date.now().toString(BASE_36)}`;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
export function toolId(
|
|
880
|
+
exec
|
|
881
|
+
/bin/zsh -lc "sed -n '1,260p' src/formats/types.ts" in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
882
|
+
import type {
|
|
883
|
+
FormatName,
|
|
884
|
+
MockRequest,
|
|
885
|
+
ReplyObject,
|
|
886
|
+
ReplyOptions,
|
|
887
|
+
} from "../types.js";
|
|
888
|
+
import type { RequestMeta } from "./request-helpers.js";
|
|
889
|
+
|
|
890
|
+
export interface SSEChunk {
|
|
891
|
+
readonly event?: string | undefined;
|
|
892
|
+
readonly data: string;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
export interface Format {
|
|
896
|
+
readonly name: FormatName;
|
|
897
|
+
readonly route: string;
|
|
898
|
+
parseRequest(body: unknown, meta?: RequestMeta): MockRequest;
|
|
899
|
+
isStreaming(body: unknown): boolean;
|
|
900
|
+
serialize(
|
|
901
|
+
reply: ReplyObject,
|
|
902
|
+
exec
|
|
903
|
+
/bin/zsh -lc "sed -n '1,260p' src/history.ts" in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
904
|
+
import type { MockRequest } from "./types.js";
|
|
905
|
+
|
|
906
|
+
/** A recorded request with the rule that matched and when it happened. */
|
|
907
|
+
export interface RecordedRequest {
|
|
908
|
+
readonly request: MockRequest;
|
|
909
|
+
/** The rule that matched, or `undefined` if the fallback was used. */
|
|
910
|
+
readonly rule: string | undefined;
|
|
911
|
+
readonly timestamp: number;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
/**
|
|
915
|
+
* Records every request the server handles.
|
|
916
|
+
* Iterable and has fluent query methods for test assertions.
|
|
917
|
+
*
|
|
918
|
+
* @example
|
|
919
|
+
* ```ts
|
|
920
|
+
* expect(server.history.count()).toBe(3);
|
|
921
|
+
* expect(server.history.last()?.request.lastMessage).toBe("hello");
|
|
922
|
+
* const matched = server.history.where(r => r.rule !== undefined);
|
|
923
|
+
* ```
|
|
924
|
+
exec
|
|
925
|
+
/bin/zsh -lc "sed -n '1,220p' src/index.ts" in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
926
|
+
export { MockServer } from "./mock-server.js";
|
|
927
|
+
export type { MockServerOptions } from "./mock-server.js";
|
|
928
|
+
export type { LogLevel } from "./logger.js";
|
|
929
|
+
export { RequestHistory } from "./history.js";
|
|
930
|
+
export type { RecordedRequest } from "./history.js";
|
|
931
|
+
export type {
|
|
932
|
+
FormatName,
|
|
933
|
+
MockRequest,
|
|
934
|
+
Message,
|
|
935
|
+
ToolDef,
|
|
936
|
+
Reply,
|
|
937
|
+
ReplyObject,
|
|
938
|
+
ToolCall,
|
|
939
|
+
Resolver,
|
|
940
|
+
Match,
|
|
941
|
+
MatchObject,
|
|
942
|
+
ReplyOptions,
|
|
943
|
+
ErrorReply,
|
|
944
|
+
PendingRule,
|
|
945
|
+
RuleHandle,
|
|
946
|
+
exec
|
|
947
|
+
/bin/zsh -lc "sed -n '1,260p' src/loader.ts" in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
948
|
+
import { readFile, readdir, stat } from "node:fs/promises";
|
|
949
|
+
import { join, extname } from "node:path";
|
|
950
|
+
import JSON5 from "json5";
|
|
951
|
+
import { z } from "zod";
|
|
952
|
+
import type { Handler, Match, MatchObject, Reply } from "./types.js";
|
|
953
|
+
import { type RuleEngine, createSequenceResolver } from "./rule-engine.js";
|
|
954
|
+
|
|
955
|
+
interface LoadContext {
|
|
956
|
+
engine: RuleEngine;
|
|
957
|
+
setFallback?: (reply: Reply) => void;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
const json5MatchSchema = z.union([
|
|
961
|
+
z.string(),
|
|
962
|
+
z.object({
|
|
963
|
+
message: z.string().optional(),
|
|
964
|
+
model: z.string().optional(),
|
|
965
|
+
system: z.string().optional(),
|
|
966
|
+
format: z.enum(["openai", "anthropic", "responses"]).optional(),
|
|
967
|
+
}),
|
|
968
|
+
exec
|
|
969
|
+
/bin/zsh -lc "sed -n '1,260p' src/logger.ts" in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
970
|
+
import pc from "picocolors";
|
|
971
|
+
|
|
972
|
+
export const LEVEL_PRIORITY = {
|
|
973
|
+
none: 0,
|
|
974
|
+
error: 1,
|
|
975
|
+
warning: 2,
|
|
976
|
+
info: 3,
|
|
977
|
+
debug: 4,
|
|
978
|
+
all: 5,
|
|
979
|
+
} as const satisfies Record<string, number>;
|
|
980
|
+
|
|
981
|
+
/** Log verbosity, from `"none"` (silent) through to `"all"` (everything). */
|
|
982
|
+
export type LogLevel = keyof typeof LEVEL_PRIORITY;
|
|
983
|
+
|
|
984
|
+
const LEVEL_STYLE = {
|
|
985
|
+
error: { label: pc.red(pc.bold("ERROR")), symbol: pc.red("✗") },
|
|
986
|
+
warn: { label: pc.yellow(pc.bold("WARN")), symbol: pc.yellow("!") },
|
|
987
|
+
info: { label: pc.cyan("INFO"), symbol: pc.cyan("●") },
|
|
988
|
+
debug: { label: pc.dim("DEBUG"), symbol: pc.dim("·") },
|
|
989
|
+
} as const;
|
|
990
|
+
exec
|
|
991
|
+
/bin/zsh -lc "sed -n '1,260p' src/mock-server.ts" in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
992
|
+
import Fastify from "fastify";
|
|
993
|
+
import type { FastifyInstance } from "fastify";
|
|
994
|
+
import type {
|
|
995
|
+
Match,
|
|
996
|
+
PendingRule,
|
|
997
|
+
Reply,
|
|
998
|
+
ReplyOptions,
|
|
999
|
+
Resolver,
|
|
1000
|
+
Rule,
|
|
1001
|
+
RuleHandle,
|
|
1002
|
+
RuleSummary,
|
|
1003
|
+
SequenceEntry,
|
|
1004
|
+
} from "./types.js";
|
|
1005
|
+
import { RuleEngine, createSequenceResolver } from "./rule-engine.js";
|
|
1006
|
+
import { RequestHistory } from "./history.js";
|
|
1007
|
+
import { openaiFormat } from "./formats/openai/index.js";
|
|
1008
|
+
import { anthropicFormat } from "./formats/anthropic/index.js";
|
|
1009
|
+
import { responsesFormat } from "./formats/responses/index.js";
|
|
1010
|
+
import type { Format } from "./formats/types.js";
|
|
1011
|
+
import { Logger } from "./logger.js";
|
|
1012
|
+
exec
|
|
1013
|
+
/bin/zsh -lc "sed -n '1,260p' src/route-handler.ts" in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
1014
|
+
import type { FastifyReply, FastifyRequest } from "fastify";
|
|
1015
|
+
import { ZodError } from "zod";
|
|
1016
|
+
import type {
|
|
1017
|
+
Reply,
|
|
1018
|
+
ReplyObject,
|
|
1019
|
+
ReplyOptions,
|
|
1020
|
+
MockRequest,
|
|
1021
|
+
Rule,
|
|
1022
|
+
} from "./types.js";
|
|
1023
|
+
import type { Format } from "./formats/types.js";
|
|
1024
|
+
import type { RuleEngine } from "./rule-engine.js";
|
|
1025
|
+
import type { RequestHistory } from "./history.js";
|
|
1026
|
+
import type { Logger } from "./logger.js";
|
|
1027
|
+
import { writeSSE } from "./sse-writer.js";
|
|
1028
|
+
|
|
1029
|
+
const HTTP_BAD_REQUEST = 400;
|
|
1030
|
+
|
|
1031
|
+
function normalizeReply(reply: Reply): ReplyObject {
|
|
1032
|
+
if (typeof reply === "string") return { text: reply };
|
|
1033
|
+
return reply;
|
|
1034
|
+
exec
|
|
1035
|
+
/bin/zsh -lc "sed -n '1,260p' src/rule-engine.ts" in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
1036
|
+
import type {
|
|
1037
|
+
Match,
|
|
1038
|
+
MatchObject,
|
|
1039
|
+
MockRequest,
|
|
1040
|
+
Resolver,
|
|
1041
|
+
Reply,
|
|
1042
|
+
ReplyOptions,
|
|
1043
|
+
Rule,
|
|
1044
|
+
RuleSummary,
|
|
1045
|
+
} from "./types.js";
|
|
1046
|
+
|
|
1047
|
+
function safeRegex(re: RegExp): RegExp {
|
|
1048
|
+
return re.global || re.sticky
|
|
1049
|
+
? new RegExp(re.source, re.flags.replace(/[gy]/g, ""))
|
|
1050
|
+
: re;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
function compilePattern(pattern: string | RegExp): (value: string) => boolean {
|
|
1054
|
+
if (typeof pattern === "string") {
|
|
1055
|
+
const lower = pattern.toLowerCase();
|
|
1056
|
+
exec
|
|
1057
|
+
/bin/zsh -lc "sed -n '1,260p' src/sse-writer.ts" in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
1058
|
+
import type { FastifyReply } from "fastify";
|
|
1059
|
+
import type { SSEChunk } from "./formats/types.js";
|
|
1060
|
+
import type { ReplyOptions } from "./types.js";
|
|
1061
|
+
|
|
1062
|
+
const HTTP_OK = 200;
|
|
1063
|
+
|
|
1064
|
+
function sleep(ms: number): Promise<void> {
|
|
1065
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
function formatSSEChunk(chunk: SSEChunk): string {
|
|
1069
|
+
const eventLine = chunk.event ? `event: ${chunk.event}\n` : "";
|
|
1070
|
+
return `${eventLine}data: ${chunk.data}\n\n`;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
export async function writeSSE(
|
|
1074
|
+
reply: FastifyReply,
|
|
1075
|
+
chunks: readonly SSEChunk[],
|
|
1076
|
+
options: ReplyOptions = {},
|
|
1077
|
+
): Promise<void> {
|
|
1078
|
+
exec
|
|
1079
|
+
/bin/zsh -lc "sed -n '1,260p' src/types.ts" in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
1080
|
+
export type {
|
|
1081
|
+
FormatName,
|
|
1082
|
+
MockRequest,
|
|
1083
|
+
Message,
|
|
1084
|
+
ToolDef,
|
|
1085
|
+
} from "./types/request.js";
|
|
1086
|
+
export type {
|
|
1087
|
+
Reply,
|
|
1088
|
+
ReplyObject,
|
|
1089
|
+
ErrorReply,
|
|
1090
|
+
ToolCall,
|
|
1091
|
+
Resolver,
|
|
1092
|
+
ReplyOptions,
|
|
1093
|
+
SequenceEntry,
|
|
1094
|
+
} from "./types/reply.js";
|
|
1095
|
+
export type {
|
|
1096
|
+
Match,
|
|
1097
|
+
MatchObject,
|
|
1098
|
+
PendingRule,
|
|
1099
|
+
RuleHandle,
|
|
1100
|
+
exec
|
|
1101
|
+
/bin/zsh -lc "sed -n '1,260p' src/types/reply.ts" in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
1102
|
+
import type { MockRequest } from "./request.js";
|
|
1103
|
+
|
|
1104
|
+
/** A reply is either a plain string (turns into `{ text: "..." }`) or a full reply object. */
|
|
1105
|
+
export type Reply = string | ReplyObject;
|
|
1106
|
+
|
|
1107
|
+
/** A structured reply. Text, reasoning, tool calls, usage, and errors are all optional. */
|
|
1108
|
+
export interface ReplyObject {
|
|
1109
|
+
readonly text?: string | undefined;
|
|
1110
|
+
/** Extended thinking or chain-of-thought. Works with Anthropic and Responses formats. */
|
|
1111
|
+
readonly reasoning?: string | undefined;
|
|
1112
|
+
readonly tools?: readonly ToolCall[] | undefined;
|
|
1113
|
+
/** Falls back to `{ input: 10, output: 5 }` if omitted. */
|
|
1114
|
+
readonly usage?:
|
|
1115
|
+
| { readonly input: number; readonly output: number }
|
|
1116
|
+
| undefined;
|
|
1117
|
+
/** When set, the server responds with this HTTP error instead of a normal reply. */
|
|
1118
|
+
readonly error?: ErrorReply | undefined;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
/** An HTTP error response. The server returns this status code with a format-appropriate body. */
|
|
1122
|
+
exec
|
|
1123
|
+
/bin/zsh -lc "sed -n '1,260p' src/types/request.ts" in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
1124
|
+
/** The LLM API wire format that was detected for a request. */
|
|
1125
|
+
export type FormatName = "openai" | "anthropic" | "responses";
|
|
1126
|
+
|
|
1127
|
+
/** A normalised view of an incoming request, regardless of the original wire format. */
|
|
1128
|
+
export interface MockRequest {
|
|
1129
|
+
readonly format: FormatName;
|
|
1130
|
+
readonly model: string;
|
|
1131
|
+
readonly streaming: boolean;
|
|
1132
|
+
/** Full conversation, normalised from whatever format came in. */
|
|
1133
|
+
readonly messages: readonly Message[];
|
|
1134
|
+
/** The last user message's text. This is what most matchers check. */
|
|
1135
|
+
readonly lastMessage: string;
|
|
1136
|
+
/** Empty string if there wasn't one. */
|
|
1137
|
+
readonly systemMessage: string;
|
|
1138
|
+
readonly tools?: readonly ToolDef[] | undefined;
|
|
1139
|
+
/** Pulled out from `tools` for quick lookups. */
|
|
1140
|
+
readonly toolNames: readonly string[];
|
|
1141
|
+
/** Set when the last message was a tool result. */
|
|
1142
|
+
readonly lastToolCallId: string | undefined;
|
|
1143
|
+
/** The raw request body, for anything we don't extract. */
|
|
1144
|
+
exec
|
|
1145
|
+
/bin/zsh -lc "sed -n '1,260p' src/types/rule.ts" in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
1146
|
+
import type { MockRequest, FormatName } from "./request.js";
|
|
1147
|
+
import type { Resolver, ReplyOptions, Reply, SequenceEntry } from "./reply.js";
|
|
1148
|
+
|
|
1149
|
+
/**
|
|
1150
|
+
* Determines whether a rule matches an incoming request.
|
|
1151
|
+
*
|
|
1152
|
+
* A `string` does a case-insensitive substring match on the last user message.
|
|
1153
|
+
* A `RegExp` gets tested against the last user message.
|
|
1154
|
+
* A `MatchObject` checks multiple fields at once with AND logic.
|
|
1155
|
+
* A function receives the normalised request and returns a boolean.
|
|
1156
|
+
*/
|
|
1157
|
+
export type Match =
|
|
1158
|
+
| string
|
|
1159
|
+
| RegExp
|
|
1160
|
+
| MatchObject
|
|
1161
|
+
| ((req: MockRequest) => boolean);
|
|
1162
|
+
|
|
1163
|
+
/** A structured matcher. Every field you set must match for the rule to fire. */
|
|
1164
|
+
export interface MatchObject {
|
|
1165
|
+
readonly message?: string | RegExp;
|
|
1166
|
+
exec
|
|
1167
|
+
/bin/zsh -lc "sed -n '1,260p' vitest.config.ts" in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
1168
|
+
import { defineConfig } from "vitest/config";
|
|
1169
|
+
|
|
1170
|
+
export default defineConfig({
|
|
1171
|
+
test: {
|
|
1172
|
+
globals: true,
|
|
1173
|
+
testTimeout: 10_000,
|
|
1174
|
+
coverage: {
|
|
1175
|
+
provider: "v8",
|
|
1176
|
+
include: ["src/**/*.ts"],
|
|
1177
|
+
exclude: [
|
|
1178
|
+
"src/cli.ts",
|
|
1179
|
+
"src/types.ts",
|
|
1180
|
+
"src/types/**",
|
|
1181
|
+
"src/formats/types.ts",
|
|
1182
|
+
],
|
|
1183
|
+
},
|
|
1184
|
+
},
|
|
1185
|
+
});
|
|
1186
|
+
|
|
1187
|
+
thinking
|
|
1188
|
+
**Confirming data source for structure**
|
|
1189
|
+
|
|
1190
|
+
I'll read the `.desloppify/query.json` file as instructed to access the holistic package organization structure needed for the task, despite the mention of a blind packet.
|
|
1191
|
+
exec
|
|
1192
|
+
/bin/zsh -lc 'cat /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/query.json' in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
1193
|
+
{
|
|
1194
|
+
"command": "review",
|
|
1195
|
+
"mode": "holistic",
|
|
1196
|
+
"language": "typescript",
|
|
1197
|
+
"dimensions": [
|
|
1198
|
+
"cross_module_architecture",
|
|
1199
|
+
"convention_outlier",
|
|
1200
|
+
"error_consistency",
|
|
1201
|
+
"abstraction_fitness",
|
|
1202
|
+
"api_surface_coherence",
|
|
1203
|
+
"authorization_consistency",
|
|
1204
|
+
"ai_generated_debt",
|
|
1205
|
+
"incomplete_migration",
|
|
1206
|
+
"package_organization",
|
|
1207
|
+
"high_level_elegance",
|
|
1208
|
+
"mid_level_elegance",
|
|
1209
|
+
"low_level_elegance",
|
|
1210
|
+
"design_coherence"
|
|
1211
|
+
],
|
|
1212
|
+
"dimension_prompts": {
|
|
1213
|
+
|
|
1214
|
+
thinking
|
|
1215
|
+
**Checking for structure in context files**
|
|
1216
|
+
|
|
1217
|
+
I'm investigating whether holistic_context.structure is included or truncated in query.json and searching .desloppify for "structure" to find its full presence or location.
|
|
1218
|
+
exec
|
|
1219
|
+
/bin/zsh -lc "rg -n \"\\\"structure\\\"\" /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify -S" in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
1220
|
+
/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/review_packet_blind.json:573: "structure": {
|
|
1221
|
+
/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/query.json:573: "structure": {
|
|
1222
|
+
/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/review_packets/holistic_packet_20260315_000339.json:560: "structure": {
|
|
1223
|
+
/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/review_packets/holistic_packet_20260315_185613.json:573: "structure": {
|
|
1224
|
+
/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/review_packets/holistic_packet_20260315_185401.json:573: "structure": {
|
|
1225
|
+
/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/review_packets/holistic_packet_20260315_045546.json:573: "structure": {
|
|
1226
|
+
/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/logs/batch-8.log:523:I'm preparing to extract package organization and architecture details from a JSON structure file, considering use of JSON querying tools and searching within the package for key terms like "structure" to locate relevant data for metrics like fan-in/out and coupling.
|
|
1227
|
+
/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/logs/batch-8.log:525:/bin/zsh -lc 'rg -n "structure" .desloppify/review_packet_blind.json | head' in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 113ms:
|
|
1228
|
+
/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/logs/batch-8.log:533:573: "structure": {
|
|
1229
|
+
/Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/subagents/runs/20260315_185613/logs/batch-8.log:549: "structure": {
|
|
1230
|
+
|
|
1231
|
+
thinking
|
|
1232
|
+
**Planning structure block insertion**
|
|
1233
|
+
|
|
1234
|
+
I'll insert the needed structure block around line 573 using sed commands.
|
|
1235
|
+
exec
|
|
1236
|
+
/bin/zsh -lc "sed -n '573,820p' /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server/.desloppify/review_packet_blind.json" in /Users/suyash.x.srijan/Documents/Personal_Projects/llm-mock-server succeeded in 0ms:
|
|
1237
|
+
"structure": {
|
|
1238
|
+
"directory_profiles": {
|
|
1239
|
+
"src/": {
|
|
1240
|
+
"file_count": 11,
|
|
1241
|
+
"files": [
|
|
1242
|
+
"cli-validators.ts",
|
|
1243
|
+
"cli.ts",
|
|
1244
|
+
"history.ts",
|
|
1245
|
+
"index.ts",
|
|
1246
|
+
"loader.ts",
|
|
1247
|
+
"logger.ts",
|
|
1248
|
+
"mock-server.ts",
|
|
1249
|
+
"route-handler.ts",
|
|
1250
|
+
"rule-engine.ts",
|
|
1251
|
+
"sse-writer.ts",
|
|
1252
|
+
"types.ts"
|
|
1253
|
+
],
|
|
1254
|
+
"total_loc": 1273,
|
|
1255
|
+
"avg_fan_in": 3.8,
|
|
1256
|
+
"avg_fan_out": 2.9,
|
|
1257
|
+
|
|
1258
|
+
|
|
1259
|
+
|
|
1260
|
+
thinking
|
|
1261
|
+
**Identifying error handling inconsistencies and streaming flags**
|
|
1262
|
+
|
|
1263
|
+
Noticed error handling differs: route-handler returns 400 on ZodError but other errors may crash; resolveReply swallows errors and serves fallback silently, possibly hiding failures. Also spotted potential mismatch in streaming flags between parseRequest and format implementations, needing schema confirmation for consistent use.
|
|
1264
|
+
thinking
|
|
1265
|
+
**Detailing unique ID collision risks**
|
|
1266
|
+
|
|
1267
|
+
ID generation with Date.now plus prefix can produce duplicate IDs when multiple calls occur in the same millisecond, especially for SSE events across concurrent requests; this is a low-level design defect needing stronger uniqueness (e.g., monotonic counters or UUIDs).
|
|
1268
|
+
|
|
1269
|
+
**Highlighting silent error swallowing in replies**
|
|
1270
|
+
|
|
1271
|
+
resolveReply catches errors and returns fallback replies with HTTP 200, hiding failures without marking errors in the response, which breaks error consistency and warrants refactoring to propagate or signal errors explicitly.
|
|
1272
|
+
|
|
1273
|
+
**Noting package organization issues**
|
|
1274
|
+
|
|
1275
|
+
The src/ directory is flat with mixed concerns across 11 files, suggesting a reorganization into clear folders like cli/, server/, and core/ to improve maintainability and modularity.
|
|
1276
|
+
|
|
1277
|
+
**Identifying inconsistent tool parsing behavior**
|
|
1278
|
+
|
|
1279
|
+
The Responses format silently drops invalid tool definitions during parsing, unlike OpenAI/Anthropic formats that throw on invalid tools, causing inconsistent error handling and potential API surface incoherence.
|
|
1280
|
+
|
|
1281
|
+
**Naming convention mismatch in log levels**
|
|
1282
|
+
|
|
1283
|
+
There’s a minor inconsistency between log level naming keys ("warning" vs. "warn") across internal mappings and CLI flags, but this does not cause functional issues.
|
|
1284
|
+
thinking
|
|
1285
|
+
**Refining error consistency with tool parsing and reply handling**
|
|
1286
|
+
|
|
1287
|
+
Noted inconsistent error handling: Responses format silently drops invalid tools unlike OpenAI/Anthropic which error on invalid tools; combined with resolveReply swallowing errors and returning fallback 200 responses, this fragment confirms multiple error consistency defects.
|
|
1288
|
+
|
|
1289
|
+
**Planning dimension scoring with findings and notes**
|
|
1290
|
+
|
|
1291
|
+
Preparing dimension scores mostly ≥82 with two main error consistency findings, low-level ID collision issue, and package organization defect; will include dimension notes detailing evidence and impact, plus a staged reorganization plan for package structure.
|
|
1292
|
+
codex
|
|
1293
|
+
{
|
|
1294
|
+
"batch": "Full Codebase Sweep",
|
|
1295
|
+
"batch_index": 10,
|
|
1296
|
+
"assessments": {
|
|
1297
|
+
"cross_module_architecture": 90.0,
|
|
1298
|
+
"convention_outlier": 92.0,
|
|
1299
|
+
"error_consistency": 82.0,
|
|
1300
|
+
"abstraction_fitness": 88.0,
|
|
1301
|
+
"api_surface_coherence": 89.0,
|
|
1302
|
+
"authorization_consistency": 95.0,
|
|
1303
|
+
"ai_generated_debt": 93.0,
|
|
1304
|
+
"incomplete_migration": 97.0,
|
|
1305
|
+
"package_organization": 82.0,
|
|
1306
|
+
"high_level_elegance": 88.0,
|
|
1307
|
+
"mid_level_elegance": 86.0,
|
|
1308
|
+
"low_level_elegance": 84.0,
|
|
1309
|
+
"design_coherence": 87.0
|
|
1310
|
+
},
|
|
1311
|
+
"dimension_notes": {
|
|
1312
|
+
"cross_module_architecture": {
|
|
1313
|
+
"evidence": [
|
|
1314
|
+
"Core types are centralized via `src/types.ts` and imported widely; this creates a hub module with broad blast radius for changes.",
|
|
1315
|
+
"Formats depend on shared helpers in `src/formats/request-helpers.ts` and `src/formats/serialize-helpers.ts`, keeping boundary direction mostly consistent."
|
|
1316
|
+
],
|
|
1317
|
+
"impact_scope": "subsystem",
|
|
1318
|
+
"fix_scope": "multi_file_refactor",
|
|
1319
|
+
"confidence": "medium",
|
|
1320
|
+
"issues_preventing_higher_score": "The type hub in `src/types.ts` concentrates change impact and could be split by concern if the surface grows further."
|
|
1321
|
+
},
|
|
1322
|
+
"convention_outlier": {
|
|
1323
|
+
"evidence": [
|
|
1324
|
+
"Format packages follow a consistent `index/parse/schema/serialize` pattern across `src/formats/*`.",
|
|
1325
|
+
"Top-level `src/` mixes executable entry points, server runtime, and utilities in one directory, which is consistent but raises organization concerns rather than convention drift."
|
|
1326
|
+
],
|
|
1327
|
+
"impact_scope": "module",
|
|
1328
|
+
"fix_scope": "single_edit",
|
|
1329
|
+
"confidence": "low",
|
|
1330
|
+
"issues_preventing_higher_score": "The repository largely follows consistent naming and export patterns; remaining issues are about placement rather than conventions."
|
|
1331
|
+
},
|
|
1332
|
+
"error_consistency": {
|
|
1333
|
+
"evidence": [
|
|
1334
|
+
"Resolver exceptions in `src/route-handler.ts` are caught and converted into a normal fallback reply, returning HTTP 200 without an error payload.",
|
|
1335
|
+
"In `src/formats/responses/parse.ts`, malformed tool entries are silently dropped via `safeParse`, while `src/formats/openai/parse.ts` and `src/formats/anthropic/parse.ts` hard-fail on schema validation."
|
|
1336
|
+
],
|
|
1337
|
+
"impact_scope": "subsystem",
|
|
1338
|
+
"fix_scope": "multi_file_refactor",
|
|
1339
|
+
"confidence": "high"
|
|
1340
|
+
},
|
|
1341
|
+
"abstraction_fitness": {
|
|
1342
|
+
"evidence": [
|
|
1343
|
+
"The `Format` interface in `src/formats/types.ts` is implemented three times with small per-format differences, so the abstraction is useful but only minimally polymorphic.",
|
|
1344
|
+
"`createRouteHandler` in `src/route-handler.ts` bundles parsing, matching, logging, and streaming in one function, suggesting boundary abstractions are only partially leveraged."
|
|
1345
|
+
],
|
|
1346
|
+
"impact_scope": "module",
|
|
1347
|
+
"fix_scope": "multi_file_refactor",
|
|
1348
|
+
"confidence": "medium",
|
|
1349
|
+
"issues_preventing_higher_score": "Some abstractions are thin wrappers with limited leverage outside the current three formats.",
|
|
1350
|
+
"sub_axes": {
|
|
1351
|
+
"abstraction_leverage": 86.0,
|
|
1352
|
+
"indirection_cost": 88.0,
|
|
1353
|
+
"interface_honesty": 90.0
|
|
1354
|
+
}
|
|
1355
|
+
},
|
|
1356
|
+
"api_surface_coherence": {
|
|
1357
|
+
"evidence": [
|
|
1358
|
+
"`src/cli-validators.ts` exposes both sync and async parsers (`parseHost` is async, others are sync), which makes the module’s API shape uneven.",
|
|
1359
|
+
"The server surface is small and consistent (single `MockServer` entry point plus `createMock`)."
|
|
1360
|
+
],
|
|
1361
|
+
"impact_scope": "local",
|
|
1362
|
+
"fix_scope": "single_edit",
|
|
1363
|
+
"confidence": "medium",
|
|
1364
|
+
"issues_preventing_higher_score": "Mixed async/sync validators in a single module create minor API irregularity for consumers."
|
|
1365
|
+
},
|
|
1366
|
+
"authorization_consistency": {
|
|
1367
|
+
"evidence": [
|
|
1368
|
+
"The only route handler is created in `src/mock-server.ts` and has no auth checks, indicating a fully public mock server."
|
|
1369
|
+
],
|
|
1370
|
+
"impact_scope": "module",
|
|
1371
|
+
"fix_scope": "single_edit",
|
|
1372
|
+
"confidence": "low",
|
|
1373
|
+
"issues_preventing_higher_score": "Auth posture is implicit rather than explicit; there is no in-code signal that the endpoints are intentionally unauthenticated."
|
|
1374
|
+
},
|
|
1375
|
+
"ai_generated_debt": {
|
|
1376
|
+
"evidence": [
|
|
1377
|
+
"Comment ratio is high in `src/types/request.ts` and `src/types/rule.ts` compared with the codebase average, suggesting some doc-comment bulk.",
|
|
1378
|
+
"Comments mostly describe semantics and appear purposeful rather than restating code."
|
|
1379
|
+
],
|
|
1380
|
+
"impact_scope": "local",
|
|
1381
|
+
"fix_scope": "single_edit",
|
|
1382
|
+
"confidence": "low",
|
|
1383
|
+
"issues_preventing_higher_score": "Comment density in the types layer is higher than typical and could become noise if it grows further."
|
|
1384
|
+
},
|
|
1385
|
+
"incomplete_migration": {
|
|
1386
|
+
"evidence": [
|
|
1387
|
+
"No deprecated markers or migration TODOs appear in the reviewed production files.",
|
|
1388
|
+
"No mixed JS/TS implementations exist for the same module in the reviewed set."
|
|
1389
|
+
],
|
|
1390
|
+
"impact_scope": "local",
|
|
1391
|
+
"fix_scope": "single_edit",
|
|
1392
|
+
"confidence": "high",
|
|
1393
|
+
"issues_preventing_higher_score": "No explicit migration scaffolding is visible; score held below 100 only due to limited evidence scope."
|
|
1394
|
+
},
|
|
1395
|
+
"package_organization": {
|
|
1396
|
+
"evidence": [
|
|
1397
|
+
"`src/` contains 11 mixed-concern files (cli, server runtime, loader, logger, rule engine), exceeding the >10 file flat-directory threshold.",
|
|
1398
|
+
"`src/` avg fan-in is 3.8 and coupling shows bidirectional edges between `src/` and `src/formats/`, suggesting responsibilities are not strongly separated."
|
|
1399
|
+
],
|
|
1400
|
+
"impact_scope": "codebase",
|
|
1401
|
+
"fix_scope": "architectural_change",
|
|
1402
|
+
"confidence": "high"
|
|
1403
|
+
},
|
|
1404
|
+
"high_level_elegance": {
|
|
1405
|
+
"evidence": [
|
|
1406
|
+
"Primary domain concepts (formats, server, rules) are clear, but are all housed in a flat `src/` root alongside CLI utilities.",
|
|
1407
|
+
"The public API surface in `src/index.ts` is concise but coupled to internal layout."
|
|
1408
|
+
],
|
|
1409
|
+
"impact_scope": "subsystem",
|
|
1410
|
+
"fix_scope": "multi_file_refactor",
|
|
1411
|
+
"confidence": "medium",
|
|
1412
|
+
"issues_preventing_higher_score": "Top-level decomposition would read cleaner if CLI/runtime utilities were separated from core server and rule-engine concerns."
|
|
1413
|
+
},
|
|
1414
|
+
"mid_level_elegance": {
|
|
1415
|
+
"evidence": [
|
|
1416
|
+
"`createRouteHandler` orchestrates parsing, matching, error handling, logging, and streaming directly, making handoffs between layers less explicit.",
|
|
1417
|
+
"Format-specific parsing and serialization are cleanly separated, reducing translation friction."
|
|
1418
|
+
],
|
|
1419
|
+
"impact_scope": "module",
|
|
1420
|
+
"fix_scope": "multi_file_refactor",
|
|
1421
|
+
"confidence": "medium",
|
|
1422
|
+
"issues_preventing_higher_score": "Integration seams are mostly predictable but could be clearer with narrower handler responsibilities."
|
|
1423
|
+
},
|
|
1424
|
+
"low_level_elegance": {
|
|
1425
|
+
"evidence": [
|
|
1426
|
+
"ID generation in `src/formats/serialize-helpers.ts` uses `Date.now()` without collision avoidance, which can produce duplicate IDs under same-millisecond calls."
|
|
1427
|
+
],
|
|
1428
|
+
"impact_scope": "subsystem",
|
|
1429
|
+
"fix_scope": "single_edit",
|
|
1430
|
+
"confidence": "high"
|
|
1431
|
+
},
|
|
1432
|
+
"design_coherence": {
|
|
1433
|
+
"evidence": [
|
|
1434
|
+
"`createRouteHandler` bundles multiple responsibilities (parsing, matching, metrics/logging, response mode selection), which could be separated for clearer intent.",
|
|
1435
|
+
"Helper modules under `src/formats/` are focused and coherent."
|
|
1436
|
+
],
|
|
1437
|
+
"impact_scope": "module",
|
|
1438
|
+
"fix_scope": "multi_file_refactor",
|
|
1439
|
+
"confidence": "medium",
|
|
1440
|
+
"issues_preventing_higher_score": "Some core control flow aggregates multiple responsibilities in one function, reducing separation of concerns."
|
|
1441
|
+
}
|
|
1442
|
+
},
|
|
1443
|
+
"findings": [
|
|
1444
|
+
{
|
|
1445
|
+
"dimension": "error_consistency",
|
|
1446
|
+
"identifier": "resolver_errors_swallowed",
|
|
1447
|
+
"summary": "Resolver exceptions are swallowed and returned as normal fallback replies",
|
|
1448
|
+
"related_files": [
|
|
1449
|
+
"src/route-handler.ts",
|
|
1450
|
+
"src/types/reply.ts",
|
|
1451
|
+
"src/mock-server.ts"
|
|
1452
|
+
],
|
|
1453
|
+
"evidence": [
|
|
1454
|
+
"`resolveReply` catches resolver errors and returns `normalizeReply(fallback)` with no error signal.",
|
|
1455
|
+
"Downstream in the handler, the fallback reply is serialized as a normal 200 response if it doesn’t set `error`."
|
|
1456
|
+
],
|
|
1457
|
+
"suggestion": "Surface resolver failures explicitly: either rethrow and let Fastify return 500, or translate to `ReplyObject.error` with a 5xx status. Update `resolveReply` and `createRouteHandler` to propagate an error reply, and document the error contract in `src/types/reply.ts`.",
|
|
1458
|
+
"confidence": "high",
|
|
1459
|
+
"impact_scope": "subsystem",
|
|
1460
|
+
"fix_scope": "multi_file_refactor",
|
|
1461
|
+
"root_cause_cluster": "error_handling_policy"
|
|
1462
|
+
},
|
|
1463
|
+
{
|
|
1464
|
+
"dimension": "error_consistency",
|
|
1465
|
+
"identifier": "responses_tools_silent_drop",
|
|
1466
|
+
"summary": "Responses tool parsing silently drops invalid tools unlike other formats",
|
|
1467
|
+
"related_files": [
|
|
1468
|
+
"src/formats/responses/parse.ts",
|
|
1469
|
+
"src/formats/openai/parse.ts",
|
|
1470
|
+
"src/formats/anthropic/parse.ts"
|
|
1471
|
+
],
|
|
1472
|
+
"evidence": [
|
|
1473
|
+
"`parseTools` in responses uses `FunctionToolSchema.safeParse` and filters out failed parses, so malformed tool entries are ignored.",
|
|
1474
|
+
"OpenAI and Anthropic requests rely on schema parsing that fails the whole request on invalid structures."
|
|
1475
|
+
],
|
|
1476
|
+
"suggestion": "Align tool-validation behavior across formats: either treat invalid tools as a request error for responses, or document and apply a consistent “best-effort” policy across all formats. Prefer failing the request for invalid tools to keep error handling predictable.",
|
|
1477
|
+
"confidence": "medium",
|
|
1478
|
+
"impact_scope": "module",
|
|
1479
|
+
"fix_scope": "multi_file_refactor",
|
|
1480
|
+
"root_cause_cluster": "error_handling_policy"
|
|
1481
|
+
},
|
|
1482
|
+
{
|
|
1483
|
+
"dimension": "low_level_elegance",
|
|
1484
|
+
"identifier": "non_unique_ids",
|
|
1485
|
+
"summary": "ID generation relies on Date.now(), risking collisions under concurrency",
|
|
1486
|
+
"related_files": [
|
|
1487
|
+
"src/formats/serialize-helpers.ts",
|
|
1488
|
+
"src/formats/openai/serialize.ts",
|
|
1489
|
+
"src/formats/responses/serialize.ts"
|
|
1490
|
+
],
|
|
1491
|
+
"evidence": [
|
|
1492
|
+
"`genId` and `toolId` use `Date.now().toString(36)` which can repeat in the same millisecond.",
|
|
1493
|
+
"Streaming serializers call these helpers multiple times per request, and concurrent requests can produce duplicate IDs."
|
|
1494
|
+
],
|
|
1495
|
+
"suggestion": "Switch to a collision-resistant ID source (e.g., `crypto.randomUUID()` or a process-wide monotonic counter with a random prefix). Update `genId`/`toolId` and regenerate IDs in the serializers to avoid duplicate response IDs.",
|
|
1496
|
+
"confidence": "high",
|
|
1497
|
+
"impact_scope": "subsystem",
|
|
1498
|
+
"fix_scope": "single_edit"
|
|
1499
|
+
},
|
|
1500
|
+
{
|
|
1501
|
+
"dimension": "package_organization",
|
|
1502
|
+
"identifier": "flat_src_mixed_concerns",
|
|
1503
|
+
"summary": "Flat `src/` directory mixes CLI, server, and rule-engine concerns",
|
|
1504
|
+
"related_files": [
|
|
1505
|
+
"src/cli.ts",
|
|
1506
|
+
"src/mock-server.ts",
|
|
1507
|
+
"src/route-handler.ts",
|
|
1508
|
+
"src/loader.ts"
|
|
1509
|
+
],
|
|
1510
|
+
"evidence": [
|
|
1511
|
+
"`src/` contains 11 files across CLI, HTTP server, rule engine, logging, and utilities (directory profile shows `file_count: 11`).",
|
|
1512
|
+
"Coupling shows bidirectional edges between `src/` and `src/formats/`, indicating blurred boundaries at the top level."
|
|
1513
|
+
],
|
|
1514
|
+
"suggestion": "Stage a reorg to clarify ownership: 1) create `src/server/` and move `mock-server.ts`, `route-handler.ts`, `sse-writer.ts`; 2) create `src/cli/` and move `cli.ts`, `cli-validators.ts`; 3) create `src/core/` (or `src/rules/`) and move `rule-engine.ts`, `loader.ts`, `history.ts`, `logger.ts`; 4) update imports and `src/index.ts` exports; 5) run `npm test` or `vitest` to validate and adjust any path-based tooling.",
|
|
1515
|
+
"confidence": "high",
|
|
1516
|
+
"impact_scope": "codebase",
|
|
1517
|
+
"fix_scope": "architectural_change"
|
|
1518
|
+
}
|
|
1519
|
+
],
|
|
1520
|
+
"retrospective": {
|
|
1521
|
+
"root_causes": [
|
|
1522
|
+
"Error handling policy is inconsistent across request parsing, resolver execution, and tool validation paths.",
|
|
1523
|
+
"Top-level `src/` is serving as a catch-all for multiple concerns, suggesting missing package boundaries."
|
|
1524
|
+
],
|
|
1525
|
+
"likely_symptoms": [
|
|
1526
|
+
"resolver_errors_swallowed",
|
|
1527
|
+
"responses_tools_silent_drop",
|
|
1528
|
+
"flat_src_mixed_concerns"
|
|
1529
|
+
],
|
|
1530
|
+
"possible_false_positives": [
|
|
1531
|
+
"non_unique_ids"
|
|
1532
|
+
]
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
tokens used
|
|
1536
|
+
59,885
|
|
1537
|
+
|