Add initial project files and configurations for clawd_code

This commit is contained in:
ImBenji
2026-04-03 17:48:07 +01:00
parent 7541a5279b
commit c88a1badc7
273 changed files with 28339 additions and 0 deletions
+41
View File
@@ -0,0 +1,41 @@
import "package:test/test.dart";
import "package:clawd_code/src/tools/bash_tool.dart";
void main() {
late BashTool tool;
setUp(() {
tool = BashTool();
});
group("BashTool", () {
test("runs a simple echo command", () async {
final result = await tool.execute({"command": "echo hello"});
expect(result.trim(), equals("hello"));
});
test("captures multi-line output", () async {
final result = await tool.execute({"command": "printf 'line1\nline2\n'"});
expect(result, contains("line1"));
expect(result, contains("line2"));
});
test("includes exit code on failure", () async {
final result = await tool.execute({"command": "exit 1"});
expect(result, contains("Exit code: 1"));
});
test("throws on empty command", () async {
expect(
() => tool.execute({"command": " "}),
throwsA(isA<ArgumentError>()),
);
});
test("captures stderr output", () async {
final result = await tool.execute({"command": "echo errormsg >&2"});
expect(result, contains("errormsg"));
});
});
}
+60
View File
@@ -0,0 +1,60 @@
import "dart:io";
import "package:test/test.dart";
import "package:clawd_code/src/tools/file_read_tool.dart";
void main() {
late FileReadTool tool;
late Directory tempDir;
setUp(() async {
tool = FileReadTool();
tempDir = await Directory.systemTemp.createTemp("file_read_test_");
});
tearDown(() async {
await tempDir.delete(recursive: true);
});
group("FileReadTool", () {
test("reads a known file with line numbers", () async {
final f = File("${tempDir.path}/sample.txt");
await f.writeAsString("hello\nworld\n");
final result = await tool.execute({"file_path": f.path});
expect(result, contains("hello"));
expect(result, contains("world"));
// line numbers present
expect(result, contains("1"));
});
test("returns error for missing file", () async {
final result = await tool.execute({"file_path": "/does/not/exist.txt"});
expect(result.toLowerCase(), contains("error"));
});
test("respects offset and limit", () async {
final f = File("${tempDir.path}/multiline.txt");
await f.writeAsString("line1\nline2\nline3\nline4\nline5\n");
final result = await tool.execute({
"file_path": f.path,
"offset": 1,
"limit": 2,
});
expect(result, contains("line2"));
expect(result, contains("line3"));
expect(result, isNot(contains("line1")));
expect(result, isNot(contains("line4")));
});
test("empty file returns empty marker", () async {
final f = File("${tempDir.path}/empty.txt");
await f.writeAsString("");
final result = await tool.execute({"file_path": f.path});
expect(result, contains("1\t"));
});
});
}
+44
View File
@@ -0,0 +1,44 @@
import "package:test/test.dart";
import "package:clawd_code/src/utils/array_utils.dart";
void main() {
group("intersperse", () {
test("adds separator between elements", () {
final result = intersperse([1, 2, 3], (_) => 0);
expect(result, equals([1, 0, 2, 0, 3]));
});
test("empty list stays empty", () {
expect(intersperse([], (_) => 0), isEmpty);
});
test("single element no separator", () {
expect(intersperse([42], (_) => 0), equals([42]));
});
});
group("countWhere", () {
test("counts matching elements", () {
expect(countWhere([1, 2, 3, 4, 5], (x) => x % 2 == 0), equals(2));
});
test("zero when none match", () {
expect(countWhere([1, 3, 5], (x) => x % 2 == 0), equals(0));
});
});
group("uniq", () {
test("removes duplicates preserving order", () {
expect(uniq([1, 2, 1, 3, 2, 4]), equals([1, 2, 3, 4]));
});
test("empty stays empty", () {
expect(uniq([]), isEmpty);
});
test("strings work too", () {
expect(uniq(["a", "b", "a", "c"]), equals(["a", "b", "c"]));
});
});
}
+61
View File
@@ -0,0 +1,61 @@
import "package:test/test.dart";
import "package:clawd_code/src/utils/format_utils.dart";
void main() {
group("formatFileSize", () {
test("bytes when under 1kb", () {
expect(formatFileSize(512), equals("512 bytes"));
});
test("KB format", () {
expect(formatFileSize(2048), equals("2KB"));
});
test("MB format", () {
expect(formatFileSize(1024 * 1024 * 3), equals("3MB"));
});
});
group("formatSecondsShort", () {
test("formats ms as seconds", () {
expect(formatSecondsShort(1500), equals("1.5s"));
});
});
group("formatDuration", () {
test("seconds only", () {
expect(formatDuration(5000), equals("5s"));
});
test("minutes and seconds", () {
expect(formatDuration(90000), equals("1m 30s"));
});
test("zero returns 0s", () {
expect(formatDuration(0), equals("0s"));
});
});
group("formatNumber", () {
test("small numbers as-is", () {
expect(formatNumber(42), equals("42"));
});
test("thousands", () {
final result = formatNumber(1500);
expect(result.contains("k"), isTrue);
});
test("millions", () {
expect(formatNumber(2000000), equals("2m"));
});
});
group("formatTokens", () {
test("strips trailing .0", () {
expect(formatTokens(1000), isNot(contains(".0")));
});
});
}
+51
View File
@@ -0,0 +1,51 @@
import "package:test/test.dart";
import "package:clawd_code/src/utils/model_cost.dart";
void main() {
group("getCanonicalModelName", () {
test("strips date suffix", () {
expect(getCanonicalModelName("claude-sonnet-4-6-20241022"), equals("claude-sonnet-4-6"));
});
test("known model unchanged", () {
expect(getCanonicalModelName("claude-sonnet-4-6"), equals("claude-sonnet-4-6"));
});
});
group("getModelCosts", () {
test("returns correct costs for known model", () {
final costs = getModelCosts("claude-sonnet-4-6");
expect(costs.inputTokens, equals(3.0));
expect(costs.outputTokens, equals(15.0));
});
test("unknown model returns fallback", () {
final costs = getModelCosts("totally-made-up-model");
// should not throw, just returns default
expect(costs.inputTokens, greaterThan(0));
});
});
group("calculateUSDCost", () {
test("zero usage is zero cost", () {
final usage = TokenUsage(inputTokens: 0, outputTokens: 0);
expect(calculateUSDCost("claude-sonnet-4-6", usage), equals(0.0));
});
test("1M input tokens at \$3 rate", () {
final usage = TokenUsage(inputTokens: 1000000, outputTokens: 0);
final cost = calculateUSDCost("claude-sonnet-4-6", usage);
expect(cost, closeTo(3.0, 0.001));
});
});
group("formatModelPricing", () {
test("formats expected string", () {
final result = formatModelPricing(costTier3_15);
expect(result, contains("3"));
expect(result, contains("15"));
expect(result, contains("Mtok"));
});
});
}
+56
View File
@@ -0,0 +1,56 @@
import "package:test/test.dart";
import "package:clawd_code/src/utils/semver_utils.dart";
void main() {
group("semverOrder", () {
test("equal versions return 0", () {
expect(semverOrder("1.2.3", "1.2.3"), equals(0));
});
test("greater major returns 1", () {
expect(semverOrder("2.0.0", "1.0.0"), equals(1));
});
test("lesser patch returns -1", () {
expect(semverOrder("1.2.2", "1.2.3"), equals(-1));
});
test("strips v prefix", () {
expect(semverOrder("v1.0.0", "1.0.0"), equals(0));
});
});
group("semverGt / semverLt", () {
test("gt works", () {
expect(semverGt("2.0.0", "1.9.9"), isTrue);
expect(semverGt("1.0.0", "2.0.0"), isFalse);
});
test("lt works", () {
expect(semverLt("1.0.0", "1.0.1"), isTrue);
});
});
group("semverSatisfies", () {
test("caret range", () {
expect(semverSatisfies("1.2.3", "^1.0.0"), isTrue);
expect(semverSatisfies("2.0.0", "^1.0.0"), isFalse);
});
test("tilde range", () {
expect(semverSatisfies("1.2.5", "~1.2.0"), isTrue);
expect(semverSatisfies("1.3.0", "~1.2.0"), isFalse);
});
test("gte range", () {
expect(semverSatisfies("3.0.0", ">=2.0.0"), isTrue);
expect(semverSatisfies("1.9.9", ">=2.0.0"), isFalse);
});
test("exact match", () {
expect(semverSatisfies("1.2.3", "1.2.3"), isTrue);
expect(semverSatisfies("1.2.4", "1.2.3"), isFalse);
});
});
}
+75
View File
@@ -0,0 +1,75 @@
import "package:test/test.dart";
import "package:clawd_code/src/utils/string_utils.dart";
void main() {
group("escapeRegExp", () {
test("escapes dot and star", () {
expect(escapeRegExp("a.b*c"), equals(r"a\.b\*c"));
});
test("plain string unchanged", () {
expect(escapeRegExp("hello"), equals("hello"));
});
});
group("capitalize", () {
test("uppercases first char", () {
expect(capitalize("foo"), equals("Foo"));
});
test("empty string stays empty", () {
expect(capitalize(""), equals(""));
});
test("doesnt lowercase rest", () {
expect(capitalize("fOOBAR"), equals("FOOBAR"));
});
});
group("plural", () {
test("singular for 1", () {
expect(plural(1, "file"), equals("file"));
});
test("appends s for other counts", () {
expect(plural(3, "file"), equals("files"));
});
test("uses custom plural form", () {
expect(plural(2, "entry", "entries"), equals("entries"));
});
});
group("firstLineOf", () {
test("returns full string when no newline", () {
expect(firstLineOf("hello world"), equals("hello world"));
});
test("returns only first line", () {
expect(firstLineOf("line1\nline2\nline3"), equals("line1"));
});
});
group("countChar", () {
test("counts occurrences", () {
expect(countChar("banana", "a"), equals(3));
});
test("zero when not found", () {
expect(countChar("hello", "z"), equals(0));
});
});
group("truncate", () {
test("short string unchanged", () {
expect(truncate("hi", 10), equals("hi"));
});
test("truncates long string with ellipsis", () {
final result = truncate("hello world", 8);
expect(result.length, lessThanOrEqualTo(8));
expect(result.endsWith("..."), isTrue);
});
});
}