@kodel-ai/kodel 0.1.0-beta.0 → 0.1.0-beta.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/README.md +121 -13
- package/dist/kodel-hook.js +31 -8
- package/dist/kodel.js +7153 -3925
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,23 +1,131 @@
|
|
|
1
1
|
# Kodel
|
|
2
2
|
|
|
3
|
-
Spec-driven pipelines
|
|
3
|
+
Spec-driven pipelines that run inside the coding agent you already use, and are checked outside of it.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@kodel-ai/kodel)
|
|
6
|
+
[](https://github.com/kodelai/kodel-cli/blob/main/LICENSE)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
> **Status: beta.** Formats and commands may still change before 1.0. The full documentation is not
|
|
9
|
+
> written yet; until then this README and [AGENTS.md](https://github.com/kodelai/kodel-cli/blob/main/AGENTS.md) are the entry points.
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
- Your agent (Claude Code, Codex, OpenCode, ...) drives the run through MCP tools.
|
|
12
|
-
- Every step result is validated outside the agent, so "done" is verifiable.
|
|
13
|
-
- Run history stays in git and is analyzed to suggest improvements to the pipeline.
|
|
14
|
-
- Optional [tasks](docs/tasks.md): the agent writes down what it finds on the way ("create a task for
|
|
15
|
-
that bug"), you ask what is left, and a tracker such as Jira can sit behind the same interface.
|
|
11
|
+
## Quick start
|
|
16
12
|
|
|
17
|
-
|
|
13
|
+
Requirement: Node >= 22.19. Kodel works with Claude Code, Codex and OpenCode (see
|
|
14
|
+
[adapters](https://github.com/kodelai/kodel-cli/blob/main/adapters/README.md) for what is checked on a real agent).
|
|
18
15
|
|
|
19
|
-
|
|
16
|
+
**1. Install.**
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install -g @kodel-ai/kodel@beta
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**2. Set up your repository.** In the repository you want to work on:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
kodel init
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
It adds the MCP server, the skills, the config and the hooks for the agents it finds.
|
|
29
|
+
|
|
30
|
+
**3. Create a pipeline.** Restart your agent so it picks up the MCP server, then ask it to design one:
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
/kodel-design
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Follow the agent's instructions. When it proposes a pipeline, accept it in a terminal:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
kodel pipeline proposals show
|
|
40
|
+
kodel pipeline proposals apply <name>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**4. Run it.** Give the agent a task:
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
/kodel-run fix the login timeout
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The agent works through the steps, and the engine checks each result. When a run needs you, the agent
|
|
50
|
+
says what to look at and which command to decide with:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
kodel status # list the runs, or show one
|
|
54
|
+
kodel gate approve <run> # decide the human step a run is waiting at (or: gate reject)
|
|
55
|
+
kodel report <run> # where the time went and what was redone
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
An example of a full pipeline, with a plan, two human gates, a review and tests, is
|
|
59
|
+
[`examples/pipelines/feature.yaml`](https://github.com/kodelai/kodel-cli/blob/main/examples/pipelines/feature.yaml).
|
|
60
|
+
|
|
61
|
+
## Commands for people
|
|
62
|
+
|
|
63
|
+
These ask for confirmation and refuse to run without a terminal, on purpose: an agent can propose,
|
|
64
|
+
only a person decides.
|
|
65
|
+
|
|
66
|
+
| Command | What it does |
|
|
67
|
+
| --- | --- |
|
|
68
|
+
| `kodel init` | Set the project up for your coding agents. |
|
|
69
|
+
| `kodel status [run]` | List the runs, or show one. |
|
|
70
|
+
| `kodel wait <run>` | Stay until a run stops waiting for a person, print one line, exit. It changes nothing. |
|
|
71
|
+
| `kodel gate approve\|reject <run>` | Decide the human step a run is waiting at. |
|
|
72
|
+
| `kodel resume <run>` | Carry on a paused run, with more attempts for the step that stopped it. |
|
|
73
|
+
| `kodel ack <run>` | Accept the state of a run after you changed it by hand. |
|
|
74
|
+
| `kodel seal <run>` | Accept the files of a finished run after a deliberate change. |
|
|
75
|
+
| `kodel report [run]` | Where the time went and what was redone in a run. |
|
|
76
|
+
| `kodel pipeline changes ...` | Review a change to the pipeline of a run that is going. |
|
|
77
|
+
| `kodel pipeline proposals ...` | Review a pipeline the agent proposed outside a run. |
|
|
78
|
+
| `kodel feedback [text]` | Send a bug report, an idea or a note to the Kodel developers. |
|
|
79
|
+
|
|
80
|
+
Run `kodel <command> --help` for the options.
|
|
81
|
+
|
|
82
|
+
## How it is built
|
|
83
|
+
|
|
84
|
+
A Bun workspace in TypeScript, shipped as a single JavaScript file that runs on plain Node.
|
|
85
|
+
|
|
86
|
+
| Package | Role |
|
|
87
|
+
| --- | --- |
|
|
88
|
+
| `packages/pipeline-core` | The pure engine: pipeline spec, result checks, run state machine, redaction, and the MCP tool definitions. No file system, process or clock access. |
|
|
89
|
+
| `packages/local` | Local adapters: file state store, process executor, git snapshots, clock, local tasks. |
|
|
90
|
+
| `packages/cli` | The `kodel` command, including the human-only commands. |
|
|
91
|
+
| `packages/testkit` | Scripted and adversarial fake agents and fixtures (private). |
|
|
92
|
+
|
|
93
|
+
The engine knows nothing about transport or I/O, and node and check kinds are registered by `kind`.
|
|
94
|
+
That is what lets a server backend implement the same adapter interfaces later. The principles
|
|
95
|
+
behind this (untrusted agent input, fail-closed, redaction on every write, humans decide trust) are in
|
|
96
|
+
[AGENTS.md](https://github.com/kodelai/kodel-cli/blob/main/AGENTS.md). How Kodel reads the logs of your agent sessions is in
|
|
97
|
+
[docs/history.md](https://github.com/kodelai/kodel-cli/blob/main/docs/history.md).
|
|
98
|
+
|
|
99
|
+
## Something went wrong?
|
|
100
|
+
|
|
101
|
+
Open an [issue](https://github.com/kodelai/kodel-cli/issues/new/choose), or send a note straight from
|
|
102
|
+
the terminal:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
kodel feedback "gate refuses to run in tmux" --type bug # or: feature, feedback
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
It sends only your text and a short description of your system, shows both, and asks before it sends.
|
|
109
|
+
For a security problem, see [SECURITY.md](https://github.com/kodelai/kodel-cli/blob/main/SECURITY.md) instead.
|
|
110
|
+
|
|
111
|
+
## Contributing
|
|
112
|
+
|
|
113
|
+
To work on Kodel itself you need [Bun](https://bun.sh) as well as Node. `bun run build` produces
|
|
114
|
+
`packages/cli/dist/kodel.js`, the same file that is published.
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
bun install --frozen-lockfile
|
|
118
|
+
bun run typecheck
|
|
119
|
+
bun run lint
|
|
120
|
+
bun test
|
|
121
|
+
bun run build && node scripts/check-artifact.mjs
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
CI runs the same on Linux, macOS and Windows. Sign off your commits (`git commit -s`): the project uses
|
|
125
|
+
the Developer Certificate of Origin. Open a pull request, never push to `main`. Everything committed,
|
|
126
|
+
including comments and commit messages, is in English. Read [AGENTS.md](https://github.com/kodelai/kodel-cli/blob/main/AGENTS.md) before you start:
|
|
127
|
+
it also says what an AI agent may and may not do in this repository.
|
|
20
128
|
|
|
21
129
|
## License
|
|
22
130
|
|
|
23
|
-
Apache-2.0. See [LICENSE](LICENSE).
|
|
131
|
+
Apache-2.0. See [LICENSE](https://github.com/kodelai/kodel-cli/blob/main/LICENSE).
|
package/dist/kodel-hook.js
CHANGED
|
@@ -29,6 +29,12 @@ import { basename, dirname, isAbsolute, join as join2, relative, resolve, sep }
|
|
|
29
29
|
|
|
30
30
|
// ../local/src/git.ts
|
|
31
31
|
import { execFile } from "node:child_process";
|
|
32
|
+
import { devNull, tmpdir } from "node:os";
|
|
33
|
+
class DeadlineError extends Error {
|
|
34
|
+
constructor() {
|
|
35
|
+
super("deadline: git took longer than the time allowed for recording the changes");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
32
38
|
var SAFE_CONFIG = [
|
|
33
39
|
"-c",
|
|
34
40
|
"core.fsmonitor=false",
|
|
@@ -37,18 +43,35 @@ var SAFE_CONFIG = [
|
|
|
37
43
|
"-c",
|
|
38
44
|
"log.showSignature=false"
|
|
39
45
|
];
|
|
40
|
-
|
|
46
|
+
var NO_HOOKS = ["-c", `core.hooksPath=${devNull}`];
|
|
47
|
+
function commandName(args) {
|
|
48
|
+
for (let i = 0;i < args.length; i += 1) {
|
|
49
|
+
const arg = args[i] ?? "";
|
|
50
|
+
if (arg === "-c")
|
|
51
|
+
i += 1;
|
|
52
|
+
else if (!arg.startsWith("-"))
|
|
53
|
+
return arg;
|
|
54
|
+
}
|
|
55
|
+
return args[0] ?? "";
|
|
56
|
+
}
|
|
57
|
+
function git(args, cwd, env = {}, input, timeoutMs) {
|
|
41
58
|
return new Promise((resolveResult, reject) => {
|
|
42
59
|
const child = execFile("git", [...SAFE_CONFIG, ...args], {
|
|
43
60
|
cwd,
|
|
44
61
|
env: { ...process.env, ...env },
|
|
45
62
|
maxBuffer: 256 * 1024 * 1024,
|
|
46
63
|
encoding: "utf8",
|
|
47
|
-
windowsHide: true
|
|
64
|
+
windowsHide: true,
|
|
65
|
+
...timeoutMs === undefined ? {} : { timeout: timeoutMs, killSignal: "SIGKILL" }
|
|
48
66
|
}, (error, stdout, stderr) => {
|
|
49
67
|
if (error) {
|
|
68
|
+
const tooMuch = error.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER";
|
|
69
|
+
if (timeoutMs !== undefined && error.killed && !tooMuch) {
|
|
70
|
+
reject(new DeadlineError);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
50
73
|
const detail = (stderr || error.message).trim();
|
|
51
|
-
reject(new Error(`git ${args
|
|
74
|
+
reject(new Error(`git ${commandName(args)} failed: ${detail}`));
|
|
52
75
|
} else {
|
|
53
76
|
resolveResult({ stdout });
|
|
54
77
|
}
|
|
@@ -291,11 +314,11 @@ class HookRegistry {
|
|
|
291
314
|
}
|
|
292
315
|
}
|
|
293
316
|
function raced(work, ms, timer) {
|
|
294
|
-
return new Promise((
|
|
317
|
+
return new Promise((resolve, reject) => {
|
|
295
318
|
const cancel = timer.start(ms, () => reject(new Error(`timed out after ${ms} ms`)));
|
|
296
319
|
work.then((value) => {
|
|
297
320
|
cancel();
|
|
298
|
-
|
|
321
|
+
resolve(value);
|
|
299
322
|
}, (error) => {
|
|
300
323
|
cancel();
|
|
301
324
|
reject(error);
|
|
@@ -308,9 +331,9 @@ async function dispatch(registry, event, context, options) {
|
|
|
308
331
|
const parts = handlers.map(() => "");
|
|
309
332
|
await Promise.all(handlers.map(async (handler, index) => {
|
|
310
333
|
try {
|
|
311
|
-
const
|
|
312
|
-
if (handler.prints === true && typeof
|
|
313
|
-
parts[index] =
|
|
334
|
+
const text = await raced(Promise.resolve().then(() => handler.run(event, context)), options.timeoutMs, options.timer);
|
|
335
|
+
if (handler.prints === true && typeof text === "string")
|
|
336
|
+
parts[index] = text;
|
|
314
337
|
} catch (error) {
|
|
315
338
|
failures.push({
|
|
316
339
|
handler: handler.id,
|