opencode-swarm-plugin 0.44.0 → 0.44.1
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/bin/swarm.serve.test.ts +6 -4
- package/bin/swarm.ts +16 -10
- package/dist/compaction-prompt-scoring.js +139 -0
- package/dist/eval-capture.js +12811 -0
- package/dist/hive.d.ts.map +1 -1
- package/dist/index.js +7644 -62599
- package/dist/plugin.js +23766 -78721
- package/dist/swarm-orchestrate.d.ts.map +1 -1
- package/dist/swarm-prompts.d.ts.map +1 -1
- package/dist/swarm-review.d.ts.map +1 -1
- package/package.json +17 -5
- package/.changeset/swarm-insights-data-layer.md +0 -63
- package/.hive/analysis/eval-failure-analysis-2025-12-25.md +0 -331
- package/.hive/analysis/session-data-quality-audit.md +0 -320
- package/.hive/eval-results.json +0 -483
- package/.hive/issues.jsonl +0 -138
- package/.hive/memories.jsonl +0 -729
- package/.opencode/eval-history.jsonl +0 -327
- package/.turbo/turbo-build.log +0 -9
- package/CHANGELOG.md +0 -2286
- package/SCORER-ANALYSIS.md +0 -598
- package/docs/analysis/subagent-coordination-patterns.md +0 -902
- package/docs/analysis-socratic-planner-pattern.md +0 -504
- package/docs/planning/ADR-001-monorepo-structure.md +0 -171
- package/docs/planning/ADR-002-package-extraction.md +0 -393
- package/docs/planning/ADR-003-performance-improvements.md +0 -451
- package/docs/planning/ADR-004-message-queue-features.md +0 -187
- package/docs/planning/ADR-005-devtools-observability.md +0 -202
- package/docs/planning/ADR-007-swarm-enhancements-worktree-review.md +0 -168
- package/docs/planning/ADR-008-worker-handoff-protocol.md +0 -293
- package/docs/planning/ADR-009-oh-my-opencode-patterns.md +0 -353
- package/docs/planning/ADR-010-cass-inhousing.md +0 -1215
- package/docs/planning/ROADMAP.md +0 -368
- package/docs/semantic-memory-cli-syntax.md +0 -123
- package/docs/swarm-mail-architecture.md +0 -1147
- package/docs/testing/context-recovery-test.md +0 -470
- package/evals/ARCHITECTURE.md +0 -1189
- package/evals/README.md +0 -768
- package/evals/compaction-prompt.eval.ts +0 -149
- package/evals/compaction-resumption.eval.ts +0 -289
- package/evals/coordinator-behavior.eval.ts +0 -307
- package/evals/coordinator-session.eval.ts +0 -154
- package/evals/evalite.config.ts.bak +0 -15
- package/evals/example.eval.ts +0 -31
- package/evals/fixtures/cass-baseline.ts +0 -217
- package/evals/fixtures/compaction-cases.ts +0 -350
- package/evals/fixtures/compaction-prompt-cases.ts +0 -311
- package/evals/fixtures/coordinator-sessions.ts +0 -328
- package/evals/fixtures/decomposition-cases.ts +0 -105
- package/evals/lib/compaction-loader.test.ts +0 -248
- package/evals/lib/compaction-loader.ts +0 -320
- package/evals/lib/data-loader.evalite-test.ts +0 -289
- package/evals/lib/data-loader.test.ts +0 -345
- package/evals/lib/data-loader.ts +0 -281
- package/evals/lib/llm.ts +0 -115
- package/evals/scorers/compaction-prompt-scorers.ts +0 -145
- package/evals/scorers/compaction-scorers.ts +0 -305
- package/evals/scorers/coordinator-discipline.evalite-test.ts +0 -539
- package/evals/scorers/coordinator-discipline.ts +0 -325
- package/evals/scorers/index.test.ts +0 -146
- package/evals/scorers/index.ts +0 -328
- package/evals/scorers/outcome-scorers.evalite-test.ts +0 -27
- package/evals/scorers/outcome-scorers.ts +0 -349
- package/evals/swarm-decomposition.eval.ts +0 -121
- package/examples/commands/swarm.md +0 -745
- package/examples/plugin-wrapper-template.ts +0 -2515
- package/examples/skills/hive-workflow/SKILL.md +0 -212
- package/examples/skills/skill-creator/SKILL.md +0 -223
- package/examples/skills/swarm-coordination/SKILL.md +0 -292
- package/global-skills/cli-builder/SKILL.md +0 -344
- package/global-skills/cli-builder/references/advanced-patterns.md +0 -244
- package/global-skills/learning-systems/SKILL.md +0 -644
- package/global-skills/skill-creator/LICENSE.txt +0 -202
- package/global-skills/skill-creator/SKILL.md +0 -352
- package/global-skills/skill-creator/references/output-patterns.md +0 -82
- package/global-skills/skill-creator/references/workflows.md +0 -28
- package/global-skills/swarm-coordination/SKILL.md +0 -995
- package/global-skills/swarm-coordination/references/coordinator-patterns.md +0 -235
- package/global-skills/swarm-coordination/references/strategies.md +0 -138
- package/global-skills/system-design/SKILL.md +0 -213
- package/global-skills/testing-patterns/SKILL.md +0 -430
- package/global-skills/testing-patterns/references/dependency-breaking-catalog.md +0 -586
- package/opencode-swarm-plugin-0.30.7.tgz +0 -0
- package/opencode-swarm-plugin-0.31.0.tgz +0 -0
- package/scripts/cleanup-test-memories.ts +0 -346
- package/scripts/init-skill.ts +0 -222
- package/scripts/migrate-unknown-sessions.ts +0 -349
- package/scripts/validate-skill.ts +0 -204
- package/src/agent-mail.ts +0 -1724
- package/src/anti-patterns.test.ts +0 -1167
- package/src/anti-patterns.ts +0 -448
- package/src/compaction-capture.integration.test.ts +0 -257
- package/src/compaction-hook.test.ts +0 -838
- package/src/compaction-hook.ts +0 -1204
- package/src/compaction-observability.integration.test.ts +0 -139
- package/src/compaction-observability.test.ts +0 -187
- package/src/compaction-observability.ts +0 -324
- package/src/compaction-prompt-scorers.test.ts +0 -475
- package/src/compaction-prompt-scoring.ts +0 -300
- package/src/contributor-tools.test.ts +0 -133
- package/src/contributor-tools.ts +0 -201
- package/src/dashboard.test.ts +0 -611
- package/src/dashboard.ts +0 -462
- package/src/error-enrichment.test.ts +0 -403
- package/src/error-enrichment.ts +0 -219
- package/src/eval-capture.test.ts +0 -1015
- package/src/eval-capture.ts +0 -929
- package/src/eval-gates.test.ts +0 -306
- package/src/eval-gates.ts +0 -218
- package/src/eval-history.test.ts +0 -508
- package/src/eval-history.ts +0 -214
- package/src/eval-learning.test.ts +0 -378
- package/src/eval-learning.ts +0 -360
- package/src/eval-runner.test.ts +0 -223
- package/src/eval-runner.ts +0 -402
- package/src/export-tools.test.ts +0 -476
- package/src/export-tools.ts +0 -257
- package/src/hive.integration.test.ts +0 -2241
- package/src/hive.ts +0 -1628
- package/src/index.ts +0 -940
- package/src/learning.integration.test.ts +0 -1815
- package/src/learning.ts +0 -1079
- package/src/logger.test.ts +0 -189
- package/src/logger.ts +0 -135
- package/src/mandate-promotion.test.ts +0 -473
- package/src/mandate-promotion.ts +0 -239
- package/src/mandate-storage.integration.test.ts +0 -601
- package/src/mandate-storage.test.ts +0 -578
- package/src/mandate-storage.ts +0 -794
- package/src/mandates.ts +0 -540
- package/src/memory-tools.test.ts +0 -195
- package/src/memory-tools.ts +0 -344
- package/src/memory.integration.test.ts +0 -334
- package/src/memory.test.ts +0 -158
- package/src/memory.ts +0 -527
- package/src/model-selection.test.ts +0 -188
- package/src/model-selection.ts +0 -68
- package/src/observability-tools.test.ts +0 -359
- package/src/observability-tools.ts +0 -871
- package/src/output-guardrails.test.ts +0 -438
- package/src/output-guardrails.ts +0 -381
- package/src/pattern-maturity.test.ts +0 -1160
- package/src/pattern-maturity.ts +0 -525
- package/src/planning-guardrails.test.ts +0 -491
- package/src/planning-guardrails.ts +0 -438
- package/src/plugin.ts +0 -23
- package/src/post-compaction-tracker.test.ts +0 -251
- package/src/post-compaction-tracker.ts +0 -237
- package/src/query-tools.test.ts +0 -636
- package/src/query-tools.ts +0 -324
- package/src/rate-limiter.integration.test.ts +0 -466
- package/src/rate-limiter.ts +0 -774
- package/src/replay-tools.test.ts +0 -496
- package/src/replay-tools.ts +0 -240
- package/src/repo-crawl.integration.test.ts +0 -441
- package/src/repo-crawl.ts +0 -610
- package/src/schemas/cell-events.test.ts +0 -347
- package/src/schemas/cell-events.ts +0 -807
- package/src/schemas/cell.ts +0 -257
- package/src/schemas/evaluation.ts +0 -166
- package/src/schemas/index.test.ts +0 -199
- package/src/schemas/index.ts +0 -286
- package/src/schemas/mandate.ts +0 -232
- package/src/schemas/swarm-context.ts +0 -115
- package/src/schemas/task.ts +0 -161
- package/src/schemas/worker-handoff.test.ts +0 -302
- package/src/schemas/worker-handoff.ts +0 -131
- package/src/sessions/agent-discovery.test.ts +0 -137
- package/src/sessions/agent-discovery.ts +0 -112
- package/src/sessions/index.ts +0 -15
- package/src/skills.integration.test.ts +0 -1192
- package/src/skills.test.ts +0 -643
- package/src/skills.ts +0 -1549
- package/src/storage.integration.test.ts +0 -341
- package/src/storage.ts +0 -884
- package/src/structured.integration.test.ts +0 -817
- package/src/structured.test.ts +0 -1046
- package/src/structured.ts +0 -762
- package/src/swarm-decompose.test.ts +0 -188
- package/src/swarm-decompose.ts +0 -1302
- package/src/swarm-deferred.integration.test.ts +0 -157
- package/src/swarm-deferred.test.ts +0 -38
- package/src/swarm-insights.test.ts +0 -214
- package/src/swarm-insights.ts +0 -459
- package/src/swarm-mail.integration.test.ts +0 -970
- package/src/swarm-mail.ts +0 -739
- package/src/swarm-orchestrate.integration.test.ts +0 -282
- package/src/swarm-orchestrate.test.ts +0 -548
- package/src/swarm-orchestrate.ts +0 -3084
- package/src/swarm-prompts.test.ts +0 -1270
- package/src/swarm-prompts.ts +0 -2077
- package/src/swarm-research.integration.test.ts +0 -701
- package/src/swarm-research.test.ts +0 -698
- package/src/swarm-research.ts +0 -472
- package/src/swarm-review.integration.test.ts +0 -285
- package/src/swarm-review.test.ts +0 -879
- package/src/swarm-review.ts +0 -709
- package/src/swarm-strategies.ts +0 -407
- package/src/swarm-worktree.test.ts +0 -501
- package/src/swarm-worktree.ts +0 -575
- package/src/swarm.integration.test.ts +0 -2377
- package/src/swarm.ts +0 -38
- package/src/tool-adapter.integration.test.ts +0 -1221
- package/src/tool-availability.ts +0 -461
- package/tsconfig.json +0 -28
package/src/structured.test.ts
DELETED
|
@@ -1,1046 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Comprehensive tests for structured.ts module
|
|
3
|
-
*
|
|
4
|
-
* Tests all JSON extraction strategies, validation tools, and edge cases.
|
|
5
|
-
*/
|
|
6
|
-
import type { ToolContext } from "@opencode-ai/plugin";
|
|
7
|
-
import { describe, expect, it } from "bun:test";
|
|
8
|
-
import { z } from "zod";
|
|
9
|
-
import {
|
|
10
|
-
CellTreeSchema,
|
|
11
|
-
EvaluationSchema,
|
|
12
|
-
TaskDecompositionSchema,
|
|
13
|
-
} from "./schemas";
|
|
14
|
-
import {
|
|
15
|
-
JsonExtractionError,
|
|
16
|
-
StructuredValidationError,
|
|
17
|
-
extractJsonFromText,
|
|
18
|
-
formatZodErrors,
|
|
19
|
-
getSchemaByName,
|
|
20
|
-
structured_extract_json,
|
|
21
|
-
structured_parse_cell_tree,
|
|
22
|
-
structured_parse_decomposition,
|
|
23
|
-
structured_parse_evaluation,
|
|
24
|
-
structured_validate,
|
|
25
|
-
} from "./structured";
|
|
26
|
-
|
|
27
|
-
// ============================================================================
|
|
28
|
-
// 1. extractJsonFromText - All Strategies
|
|
29
|
-
// ============================================================================
|
|
30
|
-
|
|
31
|
-
describe("extractJsonFromText", () => {
|
|
32
|
-
describe("Strategy 1: Direct parse", () => {
|
|
33
|
-
it("extracts clean JSON directly", () => {
|
|
34
|
-
const [result] = extractJsonFromText('{"key": "value"}');
|
|
35
|
-
expect(result).toEqual({ key: "value" });
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it("handles nested objects", () => {
|
|
39
|
-
const input = '{"outer": {"inner": "value"}}';
|
|
40
|
-
const [result] = extractJsonFromText(input);
|
|
41
|
-
expect(result).toEqual({ outer: { inner: "value" } });
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it("handles arrays", () => {
|
|
45
|
-
const [result] = extractJsonFromText("[1, 2, 3]");
|
|
46
|
-
expect(result).toEqual([1, 2, 3]);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it("handles complex nested structures", () => {
|
|
50
|
-
const input = JSON.stringify({
|
|
51
|
-
epic: { title: "Test", description: "Desc" },
|
|
52
|
-
subtasks: [
|
|
53
|
-
{ title: "Task 1", files: ["a.ts", "b.ts"] },
|
|
54
|
-
{ title: "Task 2", files: ["c.ts"] },
|
|
55
|
-
],
|
|
56
|
-
});
|
|
57
|
-
const [result] = extractJsonFromText(input);
|
|
58
|
-
expect(result).toEqual({
|
|
59
|
-
epic: { title: "Test", description: "Desc" },
|
|
60
|
-
subtasks: [
|
|
61
|
-
{ title: "Task 1", files: ["a.ts", "b.ts"] },
|
|
62
|
-
{ title: "Task 2", files: ["c.ts"] },
|
|
63
|
-
],
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
describe("Strategy 2: JSON code block", () => {
|
|
69
|
-
it("extracts JSON from ```json code block", () => {
|
|
70
|
-
const input = '```json\n{"key": "value"}\n```';
|
|
71
|
-
const [result, method] = extractJsonFromText(input);
|
|
72
|
-
expect(result).toEqual({ key: "value" });
|
|
73
|
-
expect(method).toBe("json_code_block");
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it("handles code block with surrounding text", () => {
|
|
77
|
-
const input =
|
|
78
|
-
'Here is the result:\n```json\n{"key": "value"}\n```\nEnd of response';
|
|
79
|
-
const [result, method] = extractJsonFromText(input);
|
|
80
|
-
expect(result).toEqual({ key: "value" });
|
|
81
|
-
expect(method).toBe("json_code_block");
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it("handles multiline JSON in code block", () => {
|
|
85
|
-
const input = '```json\n{\n "key": "value",\n "key2": "value2"\n}\n```';
|
|
86
|
-
const [result, method] = extractJsonFromText(input);
|
|
87
|
-
expect(result).toEqual({ key: "value", key2: "value2" });
|
|
88
|
-
expect(method).toBe("json_code_block");
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
describe("Strategy 3: Generic code block", () => {
|
|
93
|
-
it("extracts JSON from unlabeled code block", () => {
|
|
94
|
-
const input = '```\n{"key": "value"}\n```';
|
|
95
|
-
const [result, method] = extractJsonFromText(input);
|
|
96
|
-
expect(result).toEqual({ key: "value" });
|
|
97
|
-
expect(method).toBe("any_code_block");
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it("prefers json-labeled block over generic block", () => {
|
|
101
|
-
const input =
|
|
102
|
-
'```\n{"wrong": true}\n```\n```json\n{"correct": true}\n```';
|
|
103
|
-
const [result, method] = extractJsonFromText(input);
|
|
104
|
-
expect(result).toEqual({ correct: true });
|
|
105
|
-
expect(method).toBe("json_code_block");
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
describe("Strategy 4: Brace matching for objects", () => {
|
|
110
|
-
it("extracts JSON with surrounding text", () => {
|
|
111
|
-
const input = 'Here is the result: {"key": "value"} and more text';
|
|
112
|
-
const [result, method] = extractJsonFromText(input);
|
|
113
|
-
expect(result).toEqual({ key: "value" });
|
|
114
|
-
expect(method).toBe("brace_match_object");
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it("extracts first balanced object when multiple present", () => {
|
|
118
|
-
const input = 'First: {"a": 1} Second: {"b": 2}';
|
|
119
|
-
const [result, method] = extractJsonFromText(input);
|
|
120
|
-
expect(result).toEqual({ a: 1 });
|
|
121
|
-
expect(method).toBe("brace_match_object");
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it("handles deeply nested objects", () => {
|
|
125
|
-
// Valid JSON will use direct_parse strategy
|
|
126
|
-
const deep = '{"a":{"b":{"c":{"d":"value"}}}}';
|
|
127
|
-
const [result, method] = extractJsonFromText(deep);
|
|
128
|
-
expect(result).toEqual({ a: { b: { c: { d: "value" } } } });
|
|
129
|
-
expect(method).toBe("direct_parse");
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it("handles strings containing braces", () => {
|
|
133
|
-
const input = 'text {"key": "value with { and } chars"} more text';
|
|
134
|
-
const [result, method] = extractJsonFromText(input);
|
|
135
|
-
expect(result).toEqual({ key: "value with { and } chars" });
|
|
136
|
-
expect(method).toBe("brace_match_object");
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it("handles escaped quotes in strings", () => {
|
|
140
|
-
// Valid JSON will use direct_parse strategy
|
|
141
|
-
const input = '{"key": "value with \\"quotes\\""}';
|
|
142
|
-
const [result, method] = extractJsonFromText(input);
|
|
143
|
-
expect(result).toEqual({ key: 'value with "quotes"' });
|
|
144
|
-
expect(method).toBe("direct_parse");
|
|
145
|
-
});
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
describe("Strategy 5: Bracket matching for arrays", () => {
|
|
149
|
-
it("extracts arrays with surrounding text", () => {
|
|
150
|
-
const input = "Here is an array: [1, 2, 3] end";
|
|
151
|
-
const [result, method] = extractJsonFromText(input);
|
|
152
|
-
expect(result).toEqual([1, 2, 3]);
|
|
153
|
-
expect(method).toBe("brace_match_array");
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it("handles nested arrays", () => {
|
|
157
|
-
// Valid JSON will use direct_parse strategy
|
|
158
|
-
const input = "[[1, 2], [3, 4]]";
|
|
159
|
-
const [result, method] = extractJsonFromText(input);
|
|
160
|
-
expect(result).toEqual([
|
|
161
|
-
[1, 2],
|
|
162
|
-
[3, 4],
|
|
163
|
-
]);
|
|
164
|
-
expect(method).toBe("direct_parse");
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it("prefers object matching over array when both present", () => {
|
|
168
|
-
const input = '{"obj": true} [1, 2, 3]';
|
|
169
|
-
const [result, method] = extractJsonFromText(input);
|
|
170
|
-
expect(result).toEqual({ obj: true });
|
|
171
|
-
expect(method).toBe("brace_match_object");
|
|
172
|
-
});
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
describe("Strategy 6: JSON repair", () => {
|
|
176
|
-
it("fixes single quotes in keys (limited support)", () => {
|
|
177
|
-
// The repair strategy has limited support for single quotes
|
|
178
|
-
// It primarily handles trailing commas and simple quote replacements
|
|
179
|
-
// This test documents a known limitation: complex single-quote cases may not parse
|
|
180
|
-
const input = "text {'key': 'value'} more";
|
|
181
|
-
expect(() => extractJsonFromText(input)).toThrow(JsonExtractionError);
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
it("fixes trailing commas in objects", () => {
|
|
185
|
-
const input = '{"key": "value",}';
|
|
186
|
-
const [result, method] = extractJsonFromText(input);
|
|
187
|
-
expect(result).toEqual({ key: "value" });
|
|
188
|
-
expect(method).toBe("repair_json");
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
it("fixes trailing commas in arrays", () => {
|
|
192
|
-
const input = "[1, 2, 3,]";
|
|
193
|
-
const [result, method] = extractJsonFromText(input);
|
|
194
|
-
expect(result).toEqual([1, 2, 3]);
|
|
195
|
-
expect(method).toBe("repair_json");
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
it("fixes multiple trailing commas", () => {
|
|
199
|
-
const input = '{"a": 1, "b": [1, 2,], "c": 3,}';
|
|
200
|
-
const [result, method] = extractJsonFromText(input);
|
|
201
|
-
expect(result).toEqual({ a: 1, b: [1, 2], c: 3 });
|
|
202
|
-
expect(method).toBe("repair_json");
|
|
203
|
-
});
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
describe("Error cases", () => {
|
|
207
|
-
it("throws JsonExtractionError for invalid JSON", () => {
|
|
208
|
-
expect(() => extractJsonFromText("not json at all")).toThrow(
|
|
209
|
-
JsonExtractionError,
|
|
210
|
-
);
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
it("throws JsonExtractionError for empty input", () => {
|
|
214
|
-
expect(() => extractJsonFromText("")).toThrow(JsonExtractionError);
|
|
215
|
-
expect(() => extractJsonFromText(" ")).toThrow(JsonExtractionError);
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
it("throws JsonExtractionError for unbalanced braces", () => {
|
|
219
|
-
expect(() => extractJsonFromText('{"key": "value"')).toThrow(
|
|
220
|
-
JsonExtractionError,
|
|
221
|
-
);
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
it("throws JsonExtractionError for malformed JSON", () => {
|
|
225
|
-
expect(() => extractJsonFromText('{"key": undefined}')).toThrow(
|
|
226
|
-
JsonExtractionError,
|
|
227
|
-
);
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
it("includes attempted strategies in error", () => {
|
|
231
|
-
try {
|
|
232
|
-
extractJsonFromText("not json");
|
|
233
|
-
expect(true).toBe(false); // Should not reach here
|
|
234
|
-
} catch (error) {
|
|
235
|
-
if (error instanceof JsonExtractionError) {
|
|
236
|
-
expect(error.attemptedStrategies.length).toBeGreaterThan(0);
|
|
237
|
-
expect(error.attemptedStrategies).toContain("direct_parse");
|
|
238
|
-
} else {
|
|
239
|
-
throw error;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
it("returns null for deeply nested input exceeding MAX_BRACE_DEPTH", () => {
|
|
245
|
-
// Build a deeply nested structure exceeding MAX_BRACE_DEPTH (100)
|
|
246
|
-
let deep = "text ";
|
|
247
|
-
for (let i = 0; i < 101; i++) {
|
|
248
|
-
deep += "{";
|
|
249
|
-
}
|
|
250
|
-
deep += '"key":"value"';
|
|
251
|
-
for (let i = 0; i < 101; i++) {
|
|
252
|
-
deep += "}";
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
expect(() => extractJsonFromText(deep)).toThrow(JsonExtractionError);
|
|
256
|
-
});
|
|
257
|
-
});
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
// ============================================================================
|
|
261
|
-
// 2. formatZodErrors
|
|
262
|
-
// ============================================================================
|
|
263
|
-
|
|
264
|
-
describe("formatZodErrors", () => {
|
|
265
|
-
it("formats single error with path", () => {
|
|
266
|
-
const schema = z.object({ name: z.string() });
|
|
267
|
-
try {
|
|
268
|
-
schema.parse({ name: 123 });
|
|
269
|
-
} catch (error) {
|
|
270
|
-
if (error instanceof z.ZodError) {
|
|
271
|
-
const formatted = formatZodErrors(error);
|
|
272
|
-
expect(formatted.length).toBeGreaterThan(0);
|
|
273
|
-
expect(formatted[0]).toContain("name");
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
it("formats multiple errors", () => {
|
|
279
|
-
const schema = z.object({
|
|
280
|
-
name: z.string(),
|
|
281
|
-
age: z.number(),
|
|
282
|
-
});
|
|
283
|
-
try {
|
|
284
|
-
schema.parse({ name: 123, age: "not a number" });
|
|
285
|
-
} catch (error) {
|
|
286
|
-
if (error instanceof z.ZodError) {
|
|
287
|
-
const formatted = formatZodErrors(error);
|
|
288
|
-
expect(formatted.length).toBe(2);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
it("formats nested path", () => {
|
|
294
|
-
const schema = z.object({
|
|
295
|
-
user: z.object({
|
|
296
|
-
name: z.string(),
|
|
297
|
-
}),
|
|
298
|
-
});
|
|
299
|
-
try {
|
|
300
|
-
schema.parse({ user: { name: 123 } });
|
|
301
|
-
} catch (error) {
|
|
302
|
-
if (error instanceof z.ZodError) {
|
|
303
|
-
const formatted = formatZodErrors(error);
|
|
304
|
-
expect(formatted[0]).toContain("user.name");
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
it("formats error without path", () => {
|
|
310
|
-
const schema = z.string();
|
|
311
|
-
try {
|
|
312
|
-
schema.parse(123);
|
|
313
|
-
} catch (error) {
|
|
314
|
-
if (error instanceof z.ZodError) {
|
|
315
|
-
const formatted = formatZodErrors(error);
|
|
316
|
-
expect(formatted.length).toBeGreaterThan(0);
|
|
317
|
-
// Zod error messages include description like "Expected string, received number"
|
|
318
|
-
// The format is: "path: message" for nested, or just "message" for top-level
|
|
319
|
-
expect(formatted[0]).toContain("expected string");
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
});
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
// ============================================================================
|
|
326
|
-
// 3. getSchemaByName
|
|
327
|
-
// ============================================================================
|
|
328
|
-
|
|
329
|
-
describe("getSchemaByName", () => {
|
|
330
|
-
it("returns EvaluationSchema for 'evaluation'", () => {
|
|
331
|
-
const schema = getSchemaByName("evaluation");
|
|
332
|
-
expect(schema).toBe(EvaluationSchema);
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
it("returns TaskDecompositionSchema for 'task_decomposition'", () => {
|
|
336
|
-
const schema = getSchemaByName("task_decomposition");
|
|
337
|
-
expect(schema).toBe(TaskDecompositionSchema);
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
it("returns CellTreeSchema for 'cell_tree'", () => {
|
|
341
|
-
const schema = getSchemaByName("cell_tree");
|
|
342
|
-
expect(schema).toBe(CellTreeSchema);
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
it("throws error for unknown schema name", () => {
|
|
346
|
-
expect(() => getSchemaByName("unknown")).toThrow("Unknown schema");
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
it("error message lists available schemas", () => {
|
|
350
|
-
try {
|
|
351
|
-
getSchemaByName("invalid");
|
|
352
|
-
} catch (error) {
|
|
353
|
-
if (error instanceof Error) {
|
|
354
|
-
expect(error.message).toContain("evaluation");
|
|
355
|
-
expect(error.message).toContain("task_decomposition");
|
|
356
|
-
expect(error.message).toContain("cell_tree");
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
});
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
// ============================================================================
|
|
363
|
-
// 4. StructuredValidationError
|
|
364
|
-
// ============================================================================
|
|
365
|
-
|
|
366
|
-
describe("StructuredValidationError", () => {
|
|
367
|
-
it("formats error bullets from ZodError", () => {
|
|
368
|
-
const schema = z.object({ name: z.string() });
|
|
369
|
-
let zodError: z.ZodError | null = null;
|
|
370
|
-
|
|
371
|
-
try {
|
|
372
|
-
schema.parse({ name: 123 });
|
|
373
|
-
} catch (error) {
|
|
374
|
-
if (error instanceof z.ZodError) {
|
|
375
|
-
zodError = error;
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
if (zodError) {
|
|
380
|
-
const err = new StructuredValidationError(
|
|
381
|
-
"Validation failed",
|
|
382
|
-
zodError,
|
|
383
|
-
'{"name": 123}',
|
|
384
|
-
);
|
|
385
|
-
expect(err.errorBullets.length).toBeGreaterThan(0);
|
|
386
|
-
expect(err.toFeedback()).toContain("- ");
|
|
387
|
-
}
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
it("handles null ZodError", () => {
|
|
391
|
-
const err = new StructuredValidationError(
|
|
392
|
-
"Custom error",
|
|
393
|
-
null,
|
|
394
|
-
"raw input",
|
|
395
|
-
);
|
|
396
|
-
expect(err.errorBullets).toEqual(["Custom error"]);
|
|
397
|
-
expect(err.toFeedback()).toBe("- Custom error");
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
it("includes extraction method when provided", () => {
|
|
401
|
-
const err = new StructuredValidationError(
|
|
402
|
-
"Error",
|
|
403
|
-
null,
|
|
404
|
-
"input",
|
|
405
|
-
"direct_parse",
|
|
406
|
-
);
|
|
407
|
-
expect(err.extractionMethod).toBe("direct_parse");
|
|
408
|
-
});
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
// ============================================================================
|
|
412
|
-
// 5. structured_extract_json tool
|
|
413
|
-
// ============================================================================
|
|
414
|
-
|
|
415
|
-
describe("structured_extract_json", () => {
|
|
416
|
-
const mockCtx = {} as ToolContext;
|
|
417
|
-
|
|
418
|
-
it("returns success for valid JSON", async () => {
|
|
419
|
-
const result = await structured_extract_json.execute(
|
|
420
|
-
{ text: '{"key": "value"}' },
|
|
421
|
-
mockCtx,
|
|
422
|
-
);
|
|
423
|
-
const parsed = JSON.parse(result);
|
|
424
|
-
expect(parsed.success).toBe(true);
|
|
425
|
-
expect(parsed.data).toEqual({ key: "value" });
|
|
426
|
-
expect(parsed.extraction_method).toBe("direct_parse");
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
it("returns success for JSON in code block", async () => {
|
|
430
|
-
const result = await structured_extract_json.execute(
|
|
431
|
-
{ text: '```json\n{"key": "value"}\n```' },
|
|
432
|
-
mockCtx,
|
|
433
|
-
);
|
|
434
|
-
const parsed = JSON.parse(result);
|
|
435
|
-
expect(parsed.success).toBe(true);
|
|
436
|
-
expect(parsed.data).toEqual({ key: "value" });
|
|
437
|
-
expect(parsed.extraction_method).toBe("json_code_block");
|
|
438
|
-
});
|
|
439
|
-
|
|
440
|
-
it("returns error for invalid JSON", async () => {
|
|
441
|
-
const result = await structured_extract_json.execute(
|
|
442
|
-
{ text: "not json" },
|
|
443
|
-
mockCtx,
|
|
444
|
-
);
|
|
445
|
-
const parsed = JSON.parse(result);
|
|
446
|
-
expect(parsed.success).toBe(false);
|
|
447
|
-
expect(parsed.error).toContain("Could not extract");
|
|
448
|
-
expect(parsed.attempted_strategies).toBeDefined();
|
|
449
|
-
});
|
|
450
|
-
|
|
451
|
-
it("includes raw input preview in error", async () => {
|
|
452
|
-
const longText = "x".repeat(300);
|
|
453
|
-
const result = await structured_extract_json.execute(
|
|
454
|
-
{ text: longText },
|
|
455
|
-
mockCtx,
|
|
456
|
-
);
|
|
457
|
-
const parsed = JSON.parse(result);
|
|
458
|
-
expect(parsed.raw_input_preview.length).toBeLessThanOrEqual(200);
|
|
459
|
-
});
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
// ============================================================================
|
|
463
|
-
// 6. structured_validate tool
|
|
464
|
-
// ============================================================================
|
|
465
|
-
|
|
466
|
-
describe("structured_validate", () => {
|
|
467
|
-
const mockCtx = {} as ToolContext;
|
|
468
|
-
|
|
469
|
-
describe("evaluation schema", () => {
|
|
470
|
-
it("validates correct evaluation", async () => {
|
|
471
|
-
const validEval = {
|
|
472
|
-
passed: true,
|
|
473
|
-
criteria: {
|
|
474
|
-
test: { passed: true, feedback: "good" },
|
|
475
|
-
},
|
|
476
|
-
overall_feedback: "All good",
|
|
477
|
-
retry_suggestion: null,
|
|
478
|
-
};
|
|
479
|
-
const result = await structured_validate.execute(
|
|
480
|
-
{
|
|
481
|
-
response: JSON.stringify(validEval),
|
|
482
|
-
schema_name: "evaluation",
|
|
483
|
-
},
|
|
484
|
-
mockCtx,
|
|
485
|
-
);
|
|
486
|
-
const parsed = JSON.parse(result);
|
|
487
|
-
expect(parsed.success).toBe(true);
|
|
488
|
-
expect(parsed.data).toEqual(validEval);
|
|
489
|
-
});
|
|
490
|
-
|
|
491
|
-
it("returns error for invalid evaluation", async () => {
|
|
492
|
-
const result = await structured_validate.execute(
|
|
493
|
-
{
|
|
494
|
-
response: '{"invalid": true}',
|
|
495
|
-
schema_name: "evaluation",
|
|
496
|
-
},
|
|
497
|
-
mockCtx,
|
|
498
|
-
);
|
|
499
|
-
const parsed = JSON.parse(result);
|
|
500
|
-
expect(parsed.success).toBe(false);
|
|
501
|
-
expect(parsed.errors.length).toBeGreaterThan(0);
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
it("handles empty response", async () => {
|
|
505
|
-
const result = await structured_validate.execute(
|
|
506
|
-
{
|
|
507
|
-
response: "",
|
|
508
|
-
schema_name: "evaluation",
|
|
509
|
-
},
|
|
510
|
-
mockCtx,
|
|
511
|
-
);
|
|
512
|
-
const parsed = JSON.parse(result);
|
|
513
|
-
expect(parsed.valid).toBe(false);
|
|
514
|
-
expect(parsed.error).toContain("empty");
|
|
515
|
-
});
|
|
516
|
-
|
|
517
|
-
it("handles whitespace-only response", async () => {
|
|
518
|
-
const result = await structured_validate.execute(
|
|
519
|
-
{
|
|
520
|
-
response: " \n ",
|
|
521
|
-
schema_name: "evaluation",
|
|
522
|
-
},
|
|
523
|
-
mockCtx,
|
|
524
|
-
);
|
|
525
|
-
const parsed = JSON.parse(result);
|
|
526
|
-
expect(parsed.valid).toBe(false);
|
|
527
|
-
});
|
|
528
|
-
});
|
|
529
|
-
|
|
530
|
-
describe("task_decomposition schema", () => {
|
|
531
|
-
it("validates correct decomposition", async () => {
|
|
532
|
-
const validDecomp = {
|
|
533
|
-
task: "Implement feature",
|
|
534
|
-
subtasks: [
|
|
535
|
-
{
|
|
536
|
-
title: "Task 1",
|
|
537
|
-
description: "Do thing",
|
|
538
|
-
files: ["a.ts"],
|
|
539
|
-
estimated_effort: "small",
|
|
540
|
-
},
|
|
541
|
-
],
|
|
542
|
-
};
|
|
543
|
-
const result = await structured_validate.execute(
|
|
544
|
-
{
|
|
545
|
-
response: JSON.stringify(validDecomp),
|
|
546
|
-
schema_name: "task_decomposition",
|
|
547
|
-
},
|
|
548
|
-
mockCtx,
|
|
549
|
-
);
|
|
550
|
-
const parsed = JSON.parse(result);
|
|
551
|
-
expect(parsed.success).toBe(true);
|
|
552
|
-
});
|
|
553
|
-
});
|
|
554
|
-
|
|
555
|
-
describe("cell_tree schema", () => {
|
|
556
|
-
it("validates correct bead tree", async () => {
|
|
557
|
-
const validTree = {
|
|
558
|
-
epic: { title: "Epic", description: "Desc" },
|
|
559
|
-
subtasks: [
|
|
560
|
-
{
|
|
561
|
-
title: "Task 1",
|
|
562
|
-
files: ["a.ts"],
|
|
563
|
-
dependencies: [],
|
|
564
|
-
estimated_complexity: 2,
|
|
565
|
-
},
|
|
566
|
-
],
|
|
567
|
-
};
|
|
568
|
-
const result = await structured_validate.execute(
|
|
569
|
-
{
|
|
570
|
-
response: JSON.stringify(validTree),
|
|
571
|
-
schema_name: "cell_tree",
|
|
572
|
-
},
|
|
573
|
-
mockCtx,
|
|
574
|
-
);
|
|
575
|
-
const parsed = JSON.parse(result);
|
|
576
|
-
expect(parsed.success).toBe(true);
|
|
577
|
-
});
|
|
578
|
-
});
|
|
579
|
-
|
|
580
|
-
it("extracts JSON from markdown before validation", async () => {
|
|
581
|
-
const evalObj = {
|
|
582
|
-
passed: true,
|
|
583
|
-
criteria: { test: { passed: true, feedback: "ok" } },
|
|
584
|
-
overall_feedback: "Good",
|
|
585
|
-
retry_suggestion: null,
|
|
586
|
-
};
|
|
587
|
-
const markdown = `Here is the eval:\n\`\`\`json\n${JSON.stringify(evalObj)}\n\`\`\``;
|
|
588
|
-
|
|
589
|
-
const result = await structured_validate.execute(
|
|
590
|
-
{
|
|
591
|
-
response: markdown,
|
|
592
|
-
schema_name: "evaluation",
|
|
593
|
-
},
|
|
594
|
-
mockCtx,
|
|
595
|
-
);
|
|
596
|
-
const parsed = JSON.parse(result);
|
|
597
|
-
expect(parsed.success).toBe(true);
|
|
598
|
-
expect(parsed.extractionMethod).toBe("json_code_block");
|
|
599
|
-
});
|
|
600
|
-
|
|
601
|
-
it("includes retry hint when attempts < max_retries", async () => {
|
|
602
|
-
const result = await structured_validate.execute(
|
|
603
|
-
{
|
|
604
|
-
response: '{"invalid": true}',
|
|
605
|
-
schema_name: "evaluation",
|
|
606
|
-
max_retries: 3,
|
|
607
|
-
},
|
|
608
|
-
mockCtx,
|
|
609
|
-
);
|
|
610
|
-
const parsed = JSON.parse(result);
|
|
611
|
-
expect(parsed.errors.some((e: string) => e.includes("try again"))).toBe(
|
|
612
|
-
true,
|
|
613
|
-
);
|
|
614
|
-
});
|
|
615
|
-
});
|
|
616
|
-
|
|
617
|
-
// ============================================================================
|
|
618
|
-
// 7. structured_parse_evaluation tool
|
|
619
|
-
// ============================================================================
|
|
620
|
-
|
|
621
|
-
describe("structured_parse_evaluation", () => {
|
|
622
|
-
const mockCtx = {} as ToolContext;
|
|
623
|
-
|
|
624
|
-
it("parses valid evaluation", async () => {
|
|
625
|
-
const validEval = {
|
|
626
|
-
passed: true,
|
|
627
|
-
criteria: {
|
|
628
|
-
type_safe: { passed: true, feedback: "All types validated" },
|
|
629
|
-
no_bugs: { passed: true, feedback: "No issues found" },
|
|
630
|
-
},
|
|
631
|
-
overall_feedback: "Excellent work",
|
|
632
|
-
retry_suggestion: null,
|
|
633
|
-
};
|
|
634
|
-
const result = await structured_parse_evaluation.execute(
|
|
635
|
-
{ response: JSON.stringify(validEval) },
|
|
636
|
-
mockCtx,
|
|
637
|
-
);
|
|
638
|
-
const parsed = JSON.parse(result);
|
|
639
|
-
|
|
640
|
-
expect(parsed.success).toBe(true);
|
|
641
|
-
expect(parsed.data.passed).toBe(true);
|
|
642
|
-
expect(parsed.summary.passed).toBe(true);
|
|
643
|
-
expect(parsed.summary.criteria_count).toBe(2);
|
|
644
|
-
expect(parsed.summary.failed_criteria).toEqual([]);
|
|
645
|
-
});
|
|
646
|
-
|
|
647
|
-
it("identifies failed criteria in summary", async () => {
|
|
648
|
-
const evalWithFailures = {
|
|
649
|
-
passed: false,
|
|
650
|
-
criteria: {
|
|
651
|
-
type_safe: { passed: true, feedback: "OK" },
|
|
652
|
-
no_bugs: { passed: false, feedback: "Found null pointer" },
|
|
653
|
-
patterns: { passed: false, feedback: "Missing error handling" },
|
|
654
|
-
},
|
|
655
|
-
overall_feedback: "Needs fixes",
|
|
656
|
-
retry_suggestion: "Add null checks and error handling",
|
|
657
|
-
};
|
|
658
|
-
const result = await structured_parse_evaluation.execute(
|
|
659
|
-
{ response: JSON.stringify(evalWithFailures) },
|
|
660
|
-
mockCtx,
|
|
661
|
-
);
|
|
662
|
-
const parsed = JSON.parse(result);
|
|
663
|
-
|
|
664
|
-
expect(parsed.success).toBe(true);
|
|
665
|
-
expect(parsed.summary.passed).toBe(false);
|
|
666
|
-
expect(parsed.summary.failed_criteria).toContain("no_bugs");
|
|
667
|
-
expect(parsed.summary.failed_criteria).toContain("patterns");
|
|
668
|
-
});
|
|
669
|
-
|
|
670
|
-
it("returns error for malformed JSON", async () => {
|
|
671
|
-
const result = await structured_parse_evaluation.execute(
|
|
672
|
-
{ response: "not json" },
|
|
673
|
-
mockCtx,
|
|
674
|
-
);
|
|
675
|
-
const parsed = JSON.parse(result);
|
|
676
|
-
|
|
677
|
-
expect(parsed.success).toBe(false);
|
|
678
|
-
expect(parsed.error).toContain("extract JSON");
|
|
679
|
-
expect(parsed.feedback).toBeDefined();
|
|
680
|
-
});
|
|
681
|
-
|
|
682
|
-
it("returns error for invalid evaluation schema", async () => {
|
|
683
|
-
const invalidEval = {
|
|
684
|
-
passed: "not a boolean", // Invalid type
|
|
685
|
-
criteria: {},
|
|
686
|
-
overall_feedback: "test",
|
|
687
|
-
retry_suggestion: null,
|
|
688
|
-
};
|
|
689
|
-
const result = await structured_parse_evaluation.execute(
|
|
690
|
-
{ response: JSON.stringify(invalidEval) },
|
|
691
|
-
mockCtx,
|
|
692
|
-
);
|
|
693
|
-
const parsed = JSON.parse(result);
|
|
694
|
-
|
|
695
|
-
expect(parsed.success).toBe(false);
|
|
696
|
-
expect(parsed.error).toContain("does not match schema");
|
|
697
|
-
expect(parsed.validation_errors).toBeDefined();
|
|
698
|
-
});
|
|
699
|
-
|
|
700
|
-
it("includes expected shape in error feedback", async () => {
|
|
701
|
-
const result = await structured_parse_evaluation.execute(
|
|
702
|
-
{ response: '{"wrong": "structure"}' },
|
|
703
|
-
mockCtx,
|
|
704
|
-
);
|
|
705
|
-
const parsed = JSON.parse(result);
|
|
706
|
-
|
|
707
|
-
expect(parsed.expected_shape).toBeDefined();
|
|
708
|
-
expect(parsed.expected_shape.passed).toBe("boolean");
|
|
709
|
-
});
|
|
710
|
-
});
|
|
711
|
-
|
|
712
|
-
// ============================================================================
|
|
713
|
-
// 8. structured_parse_decomposition tool
|
|
714
|
-
// ============================================================================
|
|
715
|
-
|
|
716
|
-
describe("structured_parse_decomposition", () => {
|
|
717
|
-
const mockCtx = {} as ToolContext;
|
|
718
|
-
|
|
719
|
-
it("parses valid decomposition", async () => {
|
|
720
|
-
const validDecomp = {
|
|
721
|
-
task: "Implement authentication",
|
|
722
|
-
reasoning: "Split by feature layer",
|
|
723
|
-
subtasks: [
|
|
724
|
-
{
|
|
725
|
-
title: "Auth service",
|
|
726
|
-
description: "Core logic",
|
|
727
|
-
files: ["src/auth.ts"],
|
|
728
|
-
estimated_effort: "medium",
|
|
729
|
-
},
|
|
730
|
-
{
|
|
731
|
-
title: "Auth UI",
|
|
732
|
-
description: "Login form",
|
|
733
|
-
files: ["src/components/Login.tsx"],
|
|
734
|
-
estimated_effort: "small",
|
|
735
|
-
},
|
|
736
|
-
],
|
|
737
|
-
};
|
|
738
|
-
const result = await structured_parse_decomposition.execute(
|
|
739
|
-
{ response: JSON.stringify(validDecomp) },
|
|
740
|
-
mockCtx,
|
|
741
|
-
);
|
|
742
|
-
const parsed = JSON.parse(result);
|
|
743
|
-
|
|
744
|
-
expect(parsed.success).toBe(true);
|
|
745
|
-
expect(parsed.data.task).toBe("Implement authentication");
|
|
746
|
-
expect(parsed.summary.subtask_count).toBe(2);
|
|
747
|
-
expect(parsed.summary.total_files).toBe(2);
|
|
748
|
-
expect(parsed.summary.files).toContain("src/auth.ts");
|
|
749
|
-
expect(parsed.summary.files).toContain("src/components/Login.tsx");
|
|
750
|
-
});
|
|
751
|
-
|
|
752
|
-
it("includes effort breakdown in summary", async () => {
|
|
753
|
-
const decomp = {
|
|
754
|
-
task: "Test",
|
|
755
|
-
subtasks: [
|
|
756
|
-
{
|
|
757
|
-
title: "T1",
|
|
758
|
-
description: "D1",
|
|
759
|
-
files: ["a.ts"],
|
|
760
|
-
estimated_effort: "small",
|
|
761
|
-
},
|
|
762
|
-
{
|
|
763
|
-
title: "T2",
|
|
764
|
-
description: "D2",
|
|
765
|
-
files: ["b.ts"],
|
|
766
|
-
estimated_effort: "small",
|
|
767
|
-
},
|
|
768
|
-
{
|
|
769
|
-
title: "T3",
|
|
770
|
-
description: "D3",
|
|
771
|
-
files: ["c.ts"],
|
|
772
|
-
estimated_effort: "medium",
|
|
773
|
-
},
|
|
774
|
-
],
|
|
775
|
-
};
|
|
776
|
-
const result = await structured_parse_decomposition.execute(
|
|
777
|
-
{ response: JSON.stringify(decomp) },
|
|
778
|
-
mockCtx,
|
|
779
|
-
);
|
|
780
|
-
const parsed = JSON.parse(result);
|
|
781
|
-
|
|
782
|
-
expect(parsed.summary.effort_breakdown.small).toBe(2);
|
|
783
|
-
expect(parsed.summary.effort_breakdown.medium).toBe(1);
|
|
784
|
-
});
|
|
785
|
-
|
|
786
|
-
it("handles dependencies in summary", async () => {
|
|
787
|
-
const decomp = {
|
|
788
|
-
task: "Test",
|
|
789
|
-
subtasks: [
|
|
790
|
-
{
|
|
791
|
-
title: "T1",
|
|
792
|
-
description: "D1",
|
|
793
|
-
files: ["a.ts"],
|
|
794
|
-
estimated_effort: "small",
|
|
795
|
-
},
|
|
796
|
-
{
|
|
797
|
-
title: "T2",
|
|
798
|
-
description: "D2",
|
|
799
|
-
files: ["b.ts"],
|
|
800
|
-
estimated_effort: "small",
|
|
801
|
-
},
|
|
802
|
-
],
|
|
803
|
-
dependencies: [{ from: 0, to: 1, type: "blocks" }],
|
|
804
|
-
};
|
|
805
|
-
const result = await structured_parse_decomposition.execute(
|
|
806
|
-
{ response: JSON.stringify(decomp) },
|
|
807
|
-
mockCtx,
|
|
808
|
-
);
|
|
809
|
-
const parsed = JSON.parse(result);
|
|
810
|
-
|
|
811
|
-
expect(parsed.summary.dependency_count).toBe(1);
|
|
812
|
-
});
|
|
813
|
-
|
|
814
|
-
it("deduplicates files in summary", async () => {
|
|
815
|
-
const decomp = {
|
|
816
|
-
task: "Test",
|
|
817
|
-
subtasks: [
|
|
818
|
-
{
|
|
819
|
-
title: "T1",
|
|
820
|
-
description: "D1",
|
|
821
|
-
files: ["shared.ts", "a.ts"],
|
|
822
|
-
estimated_effort: "small",
|
|
823
|
-
},
|
|
824
|
-
{
|
|
825
|
-
title: "T2",
|
|
826
|
-
description: "D2",
|
|
827
|
-
files: ["shared.ts", "b.ts"],
|
|
828
|
-
estimated_effort: "small",
|
|
829
|
-
},
|
|
830
|
-
],
|
|
831
|
-
};
|
|
832
|
-
const result = await structured_parse_decomposition.execute(
|
|
833
|
-
{ response: JSON.stringify(decomp) },
|
|
834
|
-
mockCtx,
|
|
835
|
-
);
|
|
836
|
-
const parsed = JSON.parse(result);
|
|
837
|
-
|
|
838
|
-
expect(parsed.summary.total_files).toBe(3); // shared.ts counted once
|
|
839
|
-
expect(parsed.summary.files).toEqual(["shared.ts", "a.ts", "b.ts"]);
|
|
840
|
-
});
|
|
841
|
-
|
|
842
|
-
it("returns error for invalid decomposition", async () => {
|
|
843
|
-
const result = await structured_parse_decomposition.execute(
|
|
844
|
-
{ response: '{"task": "Test"}' }, // Missing subtasks
|
|
845
|
-
mockCtx,
|
|
846
|
-
);
|
|
847
|
-
const parsed = JSON.parse(result);
|
|
848
|
-
|
|
849
|
-
expect(parsed.success).toBe(false);
|
|
850
|
-
expect(parsed.error).toContain("does not match schema");
|
|
851
|
-
});
|
|
852
|
-
});
|
|
853
|
-
|
|
854
|
-
// ============================================================================
|
|
855
|
-
// 9. structured_parse_cell_tree tool
|
|
856
|
-
// ============================================================================
|
|
857
|
-
|
|
858
|
-
describe("structured_parse_cell_tree", () => {
|
|
859
|
-
const mockCtx = {} as ToolContext;
|
|
860
|
-
|
|
861
|
-
it("parses valid bead tree", async () => {
|
|
862
|
-
const validTree = {
|
|
863
|
-
epic: {
|
|
864
|
-
title: "Add authentication",
|
|
865
|
-
description: "OAuth + session management",
|
|
866
|
-
},
|
|
867
|
-
subtasks: [
|
|
868
|
-
{
|
|
869
|
-
title: "OAuth integration",
|
|
870
|
-
description: "Connect to provider",
|
|
871
|
-
files: ["src/auth/oauth.ts"],
|
|
872
|
-
dependencies: [],
|
|
873
|
-
estimated_complexity: 3,
|
|
874
|
-
},
|
|
875
|
-
{
|
|
876
|
-
title: "Session store",
|
|
877
|
-
description: "Redis sessions",
|
|
878
|
-
files: ["src/auth/sessions.ts"],
|
|
879
|
-
dependencies: [0],
|
|
880
|
-
estimated_complexity: 2,
|
|
881
|
-
},
|
|
882
|
-
],
|
|
883
|
-
};
|
|
884
|
-
const result = await structured_parse_cell_tree.execute(
|
|
885
|
-
{ response: JSON.stringify(validTree) },
|
|
886
|
-
mockCtx,
|
|
887
|
-
);
|
|
888
|
-
const parsed = JSON.parse(result);
|
|
889
|
-
|
|
890
|
-
expect(parsed.success).toBe(true);
|
|
891
|
-
expect(parsed.data.epic.title).toBe("Add authentication");
|
|
892
|
-
expect(parsed.summary.subtask_count).toBe(2);
|
|
893
|
-
expect(parsed.summary.complexity_total).toBe(5);
|
|
894
|
-
});
|
|
895
|
-
|
|
896
|
-
it("calculates complexity total", async () => {
|
|
897
|
-
const tree = {
|
|
898
|
-
epic: { title: "Test" },
|
|
899
|
-
subtasks: [
|
|
900
|
-
{
|
|
901
|
-
title: "T1",
|
|
902
|
-
files: ["a.ts"],
|
|
903
|
-
dependencies: [],
|
|
904
|
-
estimated_complexity: 2,
|
|
905
|
-
},
|
|
906
|
-
{
|
|
907
|
-
title: "T2",
|
|
908
|
-
files: ["b.ts"],
|
|
909
|
-
dependencies: [],
|
|
910
|
-
estimated_complexity: 3,
|
|
911
|
-
},
|
|
912
|
-
{
|
|
913
|
-
title: "T3",
|
|
914
|
-
files: ["c.ts"],
|
|
915
|
-
dependencies: [],
|
|
916
|
-
estimated_complexity: 1,
|
|
917
|
-
},
|
|
918
|
-
],
|
|
919
|
-
};
|
|
920
|
-
const result = await structured_parse_cell_tree.execute(
|
|
921
|
-
{ response: JSON.stringify(tree) },
|
|
922
|
-
mockCtx,
|
|
923
|
-
);
|
|
924
|
-
const parsed = JSON.parse(result);
|
|
925
|
-
|
|
926
|
-
expect(parsed.summary.complexity_total).toBe(6);
|
|
927
|
-
});
|
|
928
|
-
|
|
929
|
-
it("lists unique files", async () => {
|
|
930
|
-
const tree = {
|
|
931
|
-
epic: { title: "Test" },
|
|
932
|
-
subtasks: [
|
|
933
|
-
{
|
|
934
|
-
title: "T1",
|
|
935
|
-
files: ["shared.ts", "a.ts"],
|
|
936
|
-
dependencies: [],
|
|
937
|
-
estimated_complexity: 1,
|
|
938
|
-
},
|
|
939
|
-
{
|
|
940
|
-
title: "T2",
|
|
941
|
-
files: ["shared.ts", "b.ts"],
|
|
942
|
-
dependencies: [],
|
|
943
|
-
estimated_complexity: 1,
|
|
944
|
-
},
|
|
945
|
-
],
|
|
946
|
-
};
|
|
947
|
-
const result = await structured_parse_cell_tree.execute(
|
|
948
|
-
{ response: JSON.stringify(tree) },
|
|
949
|
-
mockCtx,
|
|
950
|
-
);
|
|
951
|
-
const parsed = JSON.parse(result);
|
|
952
|
-
|
|
953
|
-
expect(parsed.summary.total_files).toBe(3);
|
|
954
|
-
expect(parsed.summary.files).toEqual(["shared.ts", "a.ts", "b.ts"]);
|
|
955
|
-
});
|
|
956
|
-
|
|
957
|
-
it("returns error for invalid bead tree", async () => {
|
|
958
|
-
const result = await structured_parse_cell_tree.execute(
|
|
959
|
-
{ response: '{"epic": {}}' }, // Missing required fields
|
|
960
|
-
mockCtx,
|
|
961
|
-
);
|
|
962
|
-
const parsed = JSON.parse(result);
|
|
963
|
-
|
|
964
|
-
expect(parsed.success).toBe(false);
|
|
965
|
-
expect(parsed.error).toContain("does not match schema");
|
|
966
|
-
});
|
|
967
|
-
|
|
968
|
-
it("includes expected shape in error", async () => {
|
|
969
|
-
const result = await structured_parse_cell_tree.execute(
|
|
970
|
-
{ response: '{"wrong": true}' },
|
|
971
|
-
mockCtx,
|
|
972
|
-
);
|
|
973
|
-
const parsed = JSON.parse(result);
|
|
974
|
-
|
|
975
|
-
expect(parsed.expected_shape).toBeDefined();
|
|
976
|
-
expect(parsed.expected_shape.epic).toBeDefined();
|
|
977
|
-
expect(parsed.expected_shape.subtasks).toBeDefined();
|
|
978
|
-
});
|
|
979
|
-
});
|
|
980
|
-
|
|
981
|
-
// ============================================================================
|
|
982
|
-
// 10. Edge Cases and Regression Tests
|
|
983
|
-
// ============================================================================
|
|
984
|
-
|
|
985
|
-
describe("Edge cases", () => {
|
|
986
|
-
it("handles JSON with unicode characters", () => {
|
|
987
|
-
const unicodeJson = '{"emoji": "🎉", "chinese": "你好"}';
|
|
988
|
-
const [result, _method] = extractJsonFromText(unicodeJson);
|
|
989
|
-
expect(result).toEqual({ emoji: "🎉", chinese: "你好" });
|
|
990
|
-
});
|
|
991
|
-
|
|
992
|
-
it("handles very long strings in JSON", () => {
|
|
993
|
-
const longString = "x".repeat(10000);
|
|
994
|
-
const json = JSON.stringify({ long: longString });
|
|
995
|
-
const [result, _method] = extractJsonFromText(json);
|
|
996
|
-
expect((result as Record<string, unknown>).long).toBe(longString);
|
|
997
|
-
});
|
|
998
|
-
|
|
999
|
-
it("handles JSON with null values", () => {
|
|
1000
|
-
const json = '{"key": null, "key2": "value"}';
|
|
1001
|
-
const [result, _method] = extractJsonFromText(json);
|
|
1002
|
-
expect(result).toEqual({ key: null, key2: "value" });
|
|
1003
|
-
});
|
|
1004
|
-
|
|
1005
|
-
it("handles JSON with boolean values", () => {
|
|
1006
|
-
const json = '{"t": true, "f": false}';
|
|
1007
|
-
const [result, _method] = extractJsonFromText(json);
|
|
1008
|
-
expect(result).toEqual({ t: true, f: false });
|
|
1009
|
-
});
|
|
1010
|
-
|
|
1011
|
-
it("handles JSON with number types", () => {
|
|
1012
|
-
const json = '{"int": 42, "float": 3.14, "exp": 1e10}';
|
|
1013
|
-
const [result, _method] = extractJsonFromText(json);
|
|
1014
|
-
expect(result).toEqual({ int: 42, float: 3.14, exp: 1e10 });
|
|
1015
|
-
});
|
|
1016
|
-
|
|
1017
|
-
it("handles mixed content markdown with multiple code blocks", () => {
|
|
1018
|
-
const markdown = `
|
|
1019
|
-
Some text here.
|
|
1020
|
-
|
|
1021
|
-
\`\`\`typescript
|
|
1022
|
-
const code = "not json";
|
|
1023
|
-
\`\`\`
|
|
1024
|
-
|
|
1025
|
-
And the result is:
|
|
1026
|
-
|
|
1027
|
-
\`\`\`json
|
|
1028
|
-
{"result": "success"}
|
|
1029
|
-
\`\`\`
|
|
1030
|
-
|
|
1031
|
-
More text.
|
|
1032
|
-
`;
|
|
1033
|
-
const [result, method] = extractJsonFromText(markdown);
|
|
1034
|
-
expect(result).toEqual({ result: "success" });
|
|
1035
|
-
expect(method).toBe("json_code_block");
|
|
1036
|
-
});
|
|
1037
|
-
|
|
1038
|
-
it("handles JSON with escaped characters", () => {
|
|
1039
|
-
const json =
|
|
1040
|
-
'{"path": "C:\\\\Users\\\\file.txt", "newline": "line1\\nline2"}';
|
|
1041
|
-
const [result, _method] = extractJsonFromText(json);
|
|
1042
|
-
const typedResult = result as Record<string, unknown>;
|
|
1043
|
-
expect(typedResult.path).toBe("C:\\Users\\file.txt");
|
|
1044
|
-
expect(typedResult.newline).toBe("line1\nline2");
|
|
1045
|
-
});
|
|
1046
|
-
});
|