@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 CHANGED
@@ -1,23 +1,131 @@
1
1
  # Kodel
2
2
 
3
- Spec-driven pipelines for solo developers, running inside the coding agent you already use.
3
+ Spec-driven pipelines that run inside the coding agent you already use, and are checked outside of it.
4
4
 
5
- > Status: early development. Nothing here is released yet, and the documentation will be
6
- > written once the first working version exists.
5
+ [![npm](https://img.shields.io/npm/v/@kodel-ai/kodel?include_prereleases)](https://www.npmjs.com/package/@kodel-ai/kodel)
6
+ [![License](https://img.shields.io/badge/license-Apache--2.0-blue)](https://github.com/kodelai/kodel-cli/blob/main/LICENSE)
7
7
 
8
- ## Idea
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
- - Describe *how you work* as a reusable pipeline file that lives in your repository.
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
- ## Repository
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
- See [AGENTS.md](AGENTS.md) for the layout and contribution rules.
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).
@@ -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
- function git(args, cwd, env = {}, input) {
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[0]} failed: ${detail}`));
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((resolve2, reject) => {
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
- resolve2(value);
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 text2 = await raced(Promise.resolve().then(() => handler.run(event, context)), options.timeoutMs, options.timer);
312
- if (handler.prints === true && typeof text2 === "string")
313
- parts[index] = text2;
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,