wile 0.8.0 → 1.0.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/dist/agent/Dockerfile +1 -1
- package/dist/agent/README.md +20 -20
- package/dist/agent/entrypoint.sh +33 -21
- package/dist/agent/scripts/claude-stream.ts +159 -0
- package/dist/agent/scripts/codex-stream.ts +167 -0
- package/dist/agent/scripts/gemini-stream.ts +97 -0
- package/dist/agent/scripts/mock-claude.sh +48 -21
- package/dist/agent/scripts/mock-codex.sh +48 -21
- package/dist/agent/scripts/mock-gemini.sh +48 -21
- package/dist/agent/scripts/opencode-stream.ts +93 -0
- package/dist/agent/scripts/prompt-compact.md +36 -34
- package/dist/agent/scripts/prompt-preflight.md +0 -7
- package/dist/agent/scripts/prompt.md +26 -24
- package/dist/agent/scripts/test-additional-instructions.sh +4 -2
- package/dist/agent/scripts/test-env-project-docker.sh +5 -4
- package/dist/agent/scripts/test-iteration-limit.sh +4 -2
- package/dist/agent/scripts/test-prd-validation-docker.sh +151 -0
- package/dist/agent/scripts/test-preflight-claude-docker.sh +5 -4
- package/dist/agent/scripts/test-preflight-docker.sh +16 -13
- package/dist/agent/scripts/validate-compact.ts +134 -0
- package/dist/agent/scripts/validate-prd.ts +280 -0
- package/dist/agent/scripts/wile-compact.sh +6 -6
- package/dist/agent/scripts/wile-preflight.sh +4 -4
- package/dist/agent/scripts/wile.sh +4 -4
- package/dist/cli.js +236 -58
- package/package.json +10 -1
- package/dist/agent/scripts/claude-stream.js +0 -88
- package/dist/agent/scripts/codex-stream.js +0 -125
- package/dist/agent/scripts/gemini-stream.js +0 -63
- package/dist/agent/scripts/opencode-stream.js +0 -52
- package/dist/agent/scripts/validate-compact.js +0 -135
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
const readline = require("node:readline");
|
|
3
|
-
|
|
4
|
-
const rl = readline.createInterface({
|
|
5
|
-
input: process.stdin,
|
|
6
|
-
crlfDelay: Infinity
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
const writeLine = (value) => {
|
|
10
|
-
process.stdout.write(`${value}\n`);
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
const writeText = (text) => {
|
|
14
|
-
if (typeof text !== "string") return;
|
|
15
|
-
process.stdout.write(text);
|
|
16
|
-
if (!text.endsWith("\n")) {
|
|
17
|
-
process.stdout.write("\n");
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const formatChanges = (changes) => {
|
|
22
|
-
if (!Array.isArray(changes) || changes.length === 0) return "";
|
|
23
|
-
return changes
|
|
24
|
-
.map((change) => `${change.kind ?? "update"} ${change.path ?? "unknown"}`)
|
|
25
|
-
.join(", ");
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
rl.on("line", (line) => {
|
|
29
|
-
if (!line.trim()) return;
|
|
30
|
-
if (process.env.WILE_STREAM_JSON === "true") {
|
|
31
|
-
writeLine(line);
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
let payload;
|
|
36
|
-
try {
|
|
37
|
-
payload = JSON.parse(line);
|
|
38
|
-
} catch {
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const type = payload.type;
|
|
43
|
-
|
|
44
|
-
if (type === "thread.started") {
|
|
45
|
-
if (payload.thread_id) {
|
|
46
|
-
writeLine(`[system] thread: ${payload.thread_id}`);
|
|
47
|
-
}
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (type === "turn.failed") {
|
|
52
|
-
const message = payload.error?.message ?? "Unknown error";
|
|
53
|
-
writeLine(`[error] ${message}`);
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (type === "error") {
|
|
58
|
-
const message = payload.message ?? "Unknown error";
|
|
59
|
-
writeLine(`[error] ${message}`);
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (!payload.item) return;
|
|
64
|
-
|
|
65
|
-
const itemType = payload.item.type;
|
|
66
|
-
|
|
67
|
-
if (type === "item.completed" && itemType === "agent_message") {
|
|
68
|
-
writeText(payload.item.text);
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (type === "item.completed" && itemType === "reasoning") {
|
|
73
|
-
writeLine(`[thinking] ${payload.item.text ?? ""}`);
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (itemType === "command_execution") {
|
|
78
|
-
const command = payload.item.command ?? "command";
|
|
79
|
-
if (type === "item.started") {
|
|
80
|
-
writeLine(`[tool] shell: ${command}`);
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
if (type === "item.completed") {
|
|
84
|
-
const status = payload.item.status ?? "completed";
|
|
85
|
-
const exitCode =
|
|
86
|
-
payload.item.exit_code !== undefined && payload.item.exit_code !== null
|
|
87
|
-
? ` (exit ${payload.item.exit_code})`
|
|
88
|
-
: "";
|
|
89
|
-
writeLine(`[tool-result] shell ${status}${exitCode}`);
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (type === "item.completed" && itemType === "file_change") {
|
|
95
|
-
const detail = formatChanges(payload.item.changes);
|
|
96
|
-
writeLine(`[tool] file_change${detail ? `: ${detail}` : ""}`);
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (itemType === "mcp_tool_call") {
|
|
101
|
-
const server = payload.item.server ?? "mcp";
|
|
102
|
-
const tool = payload.item.tool ?? "tool";
|
|
103
|
-
if (type === "item.started") {
|
|
104
|
-
writeLine(`[tool] mcp ${server}/${tool}`);
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
if (type === "item.completed") {
|
|
108
|
-
const status = payload.item.status ?? "completed";
|
|
109
|
-
writeLine(`[tool-result] mcp ${server}/${tool} ${status}`);
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (itemType === "web_search") {
|
|
115
|
-
const query = payload.item.query ?? "";
|
|
116
|
-
if (type === "item.started") {
|
|
117
|
-
writeLine(`[tool] web_search${query ? `: ${query}` : ""}`);
|
|
118
|
-
}
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (type === "item.completed" && itemType === "error") {
|
|
123
|
-
writeLine(`[error] ${payload.item.message ?? "Unknown error"}`);
|
|
124
|
-
}
|
|
125
|
-
});
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
const readline = require("node:readline");
|
|
3
|
-
|
|
4
|
-
const rl = readline.createInterface({
|
|
5
|
-
input: process.stdin,
|
|
6
|
-
crlfDelay: Infinity
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
const writeLine = (value) => {
|
|
10
|
-
process.stdout.write(`${value}\n`);
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
const writeContent = (content) => {
|
|
14
|
-
if (typeof content !== "string") return;
|
|
15
|
-
process.stdout.write(content);
|
|
16
|
-
if (!content.endsWith("\n")) {
|
|
17
|
-
process.stdout.write("\n");
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
rl.on("line", (line) => {
|
|
22
|
-
if (!line.trim()) return;
|
|
23
|
-
let payload;
|
|
24
|
-
try {
|
|
25
|
-
payload = JSON.parse(line);
|
|
26
|
-
} catch {
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (payload.type === "init") {
|
|
31
|
-
const model = payload.model ?? "unknown";
|
|
32
|
-
writeLine(`[system] model: ${model}`);
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (payload.type === "message") {
|
|
37
|
-
if (payload.role === "assistant" && payload.content) {
|
|
38
|
-
writeContent(payload.content);
|
|
39
|
-
}
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (payload.type === "tool_use") {
|
|
44
|
-
const tool = payload.tool_name ?? "tool";
|
|
45
|
-
const params = payload.parameters ?? {};
|
|
46
|
-
const detail = Object.keys(params).length > 0 ? `: ${JSON.stringify(params)}` : "";
|
|
47
|
-
writeLine(`[tool] ${tool}${detail}`);
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (payload.type === "tool_result") {
|
|
52
|
-
const toolId = payload.tool_id ?? "unknown";
|
|
53
|
-
const status = payload.status ?? "unknown";
|
|
54
|
-
writeLine(`[tool-result] ${toolId} ${status}`);
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (payload.type === "error") {
|
|
59
|
-
const message =
|
|
60
|
-
payload.message ?? payload.error?.message ?? payload.error ?? "Unknown error";
|
|
61
|
-
writeLine(`[error] ${message}`);
|
|
62
|
-
}
|
|
63
|
-
});
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
const readline = require("node:readline");
|
|
3
|
-
|
|
4
|
-
const rl = readline.createInterface({
|
|
5
|
-
input: process.stdin,
|
|
6
|
-
crlfDelay: Infinity
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
const writeLine = (value) => {
|
|
10
|
-
process.stdout.write(`${value}\n`);
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
const formatToolLine = (tool, title, input) => {
|
|
14
|
-
if (title) return `[tool] ${tool}: ${title}`;
|
|
15
|
-
if (input && Object.keys(input).length > 0) {
|
|
16
|
-
return `[tool] ${tool}: ${JSON.stringify(input)}`;
|
|
17
|
-
}
|
|
18
|
-
return `[tool] ${tool}`;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
rl.on("line", (line) => {
|
|
22
|
-
if (!line.trim()) return;
|
|
23
|
-
let payload;
|
|
24
|
-
try {
|
|
25
|
-
payload = JSON.parse(line);
|
|
26
|
-
} catch {
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (payload.type === "text" && payload.part?.text) {
|
|
31
|
-
process.stdout.write(payload.part.text);
|
|
32
|
-
if (!payload.part.text.endsWith("\n")) {
|
|
33
|
-
process.stdout.write("\n");
|
|
34
|
-
}
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (payload.type === "tool_use" && payload.part) {
|
|
39
|
-
const tool = payload.part.tool ?? "tool";
|
|
40
|
-
const title = payload.part.state?.title ?? "";
|
|
41
|
-
const input = payload.part.state?.input ?? {};
|
|
42
|
-
writeLine(formatToolLine(tool, title, input));
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (payload.type === "error" && payload.error) {
|
|
47
|
-
const name = payload.error?.name ?? "error";
|
|
48
|
-
const message = payload.error?.data?.message ?? payload.error?.message ?? "";
|
|
49
|
-
writeLine(`[error] ${name}${message ? `: ${message}` : ""}`);
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
});
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
const fs = require("node:fs");
|
|
3
|
-
|
|
4
|
-
const prdPath = ".wile/prd.json";
|
|
5
|
-
const progressPath = ".wile/progress.txt";
|
|
6
|
-
const prdOriginalPath = ".wile/prd.json.original";
|
|
7
|
-
|
|
8
|
-
if (!fs.existsSync(prdPath)) {
|
|
9
|
-
console.error("Missing .wile/prd.json after compact.");
|
|
10
|
-
process.exit(1);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
if (!fs.existsSync(progressPath)) {
|
|
14
|
-
console.error("Missing .wile/progress.txt after compact.");
|
|
15
|
-
process.exit(1);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
let payload;
|
|
19
|
-
try {
|
|
20
|
-
payload = JSON.parse(fs.readFileSync(prdPath, "utf8"));
|
|
21
|
-
} catch {
|
|
22
|
-
console.error("Compacted .wile/prd.json is not valid JSON.");
|
|
23
|
-
process.exit(1);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (!payload || !Array.isArray(payload.userStories) || payload.userStories.length === 0) {
|
|
27
|
-
console.error("Compacted .wile/prd.json must contain at least one user story.");
|
|
28
|
-
process.exit(1);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const groupStory = payload.userStories.find((story) => story?.id === "GROUP-001");
|
|
32
|
-
if (!groupStory) {
|
|
33
|
-
console.error('Compacted .wile/prd.json must include a GROUP-001 story.');
|
|
34
|
-
process.exit(1);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const requiredKeys = ["id", "title", "tasks", "taskIds", "priority", "passes", "notes"];
|
|
38
|
-
for (const key of requiredKeys) {
|
|
39
|
-
if (!(key in groupStory)) {
|
|
40
|
-
console.error(`Compact output missing key: ${key}`);
|
|
41
|
-
process.exit(1);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (groupStory.id !== "GROUP-001") {
|
|
46
|
-
console.error('Compact output "id" must be GROUP-001.');
|
|
47
|
-
process.exit(1);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (typeof groupStory.title !== "string" || groupStory.title.trim().length === 0) {
|
|
51
|
-
console.error('Compact output "title" must be a non-empty string.');
|
|
52
|
-
process.exit(1);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (!Array.isArray(groupStory.tasks) || groupStory.tasks.length === 0) {
|
|
56
|
-
console.error('Compact output "tasks" must be a non-empty array.');
|
|
57
|
-
process.exit(1);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (!groupStory.tasks.every((task) => typeof task === "string" && task.trim().length > 0)) {
|
|
61
|
-
console.error('Compact output "tasks" must contain only non-empty strings.');
|
|
62
|
-
process.exit(1);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (typeof groupStory.taskIds !== "string" || groupStory.taskIds.trim().length === 0) {
|
|
66
|
-
console.error('Compact output "taskIds" must be a non-empty string.');
|
|
67
|
-
process.exit(1);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (typeof groupStory.priority !== "number" || !Number.isFinite(groupStory.priority)) {
|
|
71
|
-
console.error('Compact output "priority" must be a number.');
|
|
72
|
-
process.exit(1);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (groupStory.passes !== true) {
|
|
76
|
-
console.error('Compact output "passes" must be true.');
|
|
77
|
-
process.exit(1);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (groupStory.notes !== "Don't repeat task ids when starting the next one.") {
|
|
81
|
-
console.error('Compact output "notes" must match the required text.');
|
|
82
|
-
process.exit(1);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (fs.existsSync(prdOriginalPath)) {
|
|
86
|
-
let original;
|
|
87
|
-
try {
|
|
88
|
-
original = JSON.parse(fs.readFileSync(prdOriginalPath, "utf8"));
|
|
89
|
-
} catch {
|
|
90
|
-
console.error("Original .wile/prd.json.original is not valid JSON.");
|
|
91
|
-
process.exit(1);
|
|
92
|
-
}
|
|
93
|
-
if (!original || !Array.isArray(original.userStories)) {
|
|
94
|
-
console.error("Original .wile/prd.json.original is missing userStories.");
|
|
95
|
-
process.exit(1);
|
|
96
|
-
}
|
|
97
|
-
const originalPending = original.userStories.filter((story) => story?.passes === false);
|
|
98
|
-
const currentPending = payload.userStories.filter((story) => story?.passes === false);
|
|
99
|
-
if (originalPending.length !== currentPending.length) {
|
|
100
|
-
console.error("Compaction must preserve all passes:false stories.");
|
|
101
|
-
process.exit(1);
|
|
102
|
-
}
|
|
103
|
-
const missingPending = originalPending.filter(
|
|
104
|
-
(story) => !currentPending.some((current) => current?.id === story?.id)
|
|
105
|
-
);
|
|
106
|
-
if (missingPending.length > 0) {
|
|
107
|
-
console.error("Compaction removed passes:false stories.");
|
|
108
|
-
process.exit(1);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const originalGroups = original.userStories.filter((story) =>
|
|
112
|
-
typeof story?.id === "string" && story.id.startsWith("GROUP-"),
|
|
113
|
-
);
|
|
114
|
-
const currentGroups = payload.userStories.filter((story) =>
|
|
115
|
-
typeof story?.id === "string" && story.id.startsWith("GROUP-"),
|
|
116
|
-
);
|
|
117
|
-
const missingGroups = originalGroups.filter(
|
|
118
|
-
(story) => !currentGroups.some((current) => current?.id === story?.id),
|
|
119
|
-
);
|
|
120
|
-
if (missingGroups.length > 0) {
|
|
121
|
-
console.error("Compaction removed existing grouped stories.");
|
|
122
|
-
process.exit(1);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const progressText = fs.readFileSync(progressPath, "utf8");
|
|
127
|
-
if (!progressText.startsWith("# Wile Progress Log")) {
|
|
128
|
-
console.error("Compacted progress log must start with # Wile Progress Log.");
|
|
129
|
-
process.exit(1);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (!progressText.includes("## Codebase Patterns")) {
|
|
133
|
-
console.error("Compacted progress log must include ## Codebase Patterns.");
|
|
134
|
-
process.exit(1);
|
|
135
|
-
}
|