@warlock.js/ai-workspace 5.29.0 → 5.31.0

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.
Files changed (120) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +40 -21
  3. package/cjs/index.cjs +3295 -360
  4. package/cjs/index.cjs.map +1 -1
  5. package/esm/backends/local.d.mts +1 -1
  6. package/esm/backends/local.d.mts.map +1 -1
  7. package/esm/backends/local.mjs +199 -31
  8. package/esm/backends/local.mjs.map +1 -1
  9. package/esm/backends/mock.d.mts +12 -6
  10. package/esm/backends/mock.d.mts.map +1 -1
  11. package/esm/backends/mock.mjs +45 -3
  12. package/esm/backends/mock.mjs.map +1 -1
  13. package/esm/contracts/index.d.mts +3 -3
  14. package/esm/contracts/tool-io.type.d.mts +248 -11
  15. package/esm/contracts/tool-io.type.d.mts.map +1 -1
  16. package/esm/contracts/workspace-backend.contract.d.mts +78 -2
  17. package/esm/contracts/workspace-backend.contract.d.mts.map +1 -1
  18. package/esm/contracts/workspace-ops.contract.d.mts +41 -16
  19. package/esm/contracts/workspace-ops.contract.d.mts.map +1 -1
  20. package/esm/contracts/workspace-policy.type.d.mts +142 -14
  21. package/esm/contracts/workspace-policy.type.d.mts.map +1 -1
  22. package/esm/contracts/workspace.contract.d.mts +75 -22
  23. package/esm/contracts/workspace.contract.d.mts.map +1 -1
  24. package/esm/errors.d.mts +80 -6
  25. package/esm/errors.d.mts.map +1 -1
  26. package/esm/errors.mjs +23 -2
  27. package/esm/errors.mjs.map +1 -1
  28. package/esm/index.d.mts +13 -5
  29. package/esm/index.mjs +10 -2
  30. package/esm/ops.d.mts +22 -3
  31. package/esm/ops.d.mts.map +1 -1
  32. package/esm/ops.mjs +341 -82
  33. package/esm/ops.mjs.map +1 -1
  34. package/esm/policy/jail.mjs +73 -0
  35. package/esm/policy/jail.mjs.map +1 -0
  36. package/esm/policy/policy.d.mts +26 -16
  37. package/esm/policy/policy.d.mts.map +1 -1
  38. package/esm/policy/policy.mjs +62 -86
  39. package/esm/policy/policy.mjs.map +1 -1
  40. package/esm/policy/shell-check.d.mts +64 -0
  41. package/esm/policy/shell-check.d.mts.map +1 -0
  42. package/esm/policy/shell-check.mjs +627 -0
  43. package/esm/policy/shell-check.mjs.map +1 -0
  44. package/esm/policy/shell-parser.mjs +802 -0
  45. package/esm/policy/shell-parser.mjs.map +1 -0
  46. package/esm/policy/tokenize-command.d.mts +63 -0
  47. package/esm/policy/tokenize-command.d.mts.map +1 -0
  48. package/esm/policy/tokenize-command.mjs +76 -24
  49. package/esm/policy/tokenize-command.mjs.map +1 -1
  50. package/esm/shell-jobs.d.mts +45 -0
  51. package/esm/shell-jobs.d.mts.map +1 -0
  52. package/esm/shell-jobs.mjs +247 -0
  53. package/esm/shell-jobs.mjs.map +1 -0
  54. package/esm/todos.d.mts +22 -0
  55. package/esm/todos.d.mts.map +1 -0
  56. package/esm/todos.mjs +73 -0
  57. package/esm/todos.mjs.map +1 -0
  58. package/esm/tools/edit-file.d.mts +1 -1
  59. package/esm/tools/edit-file.d.mts.map +1 -1
  60. package/esm/tools/edit-file.mjs +3 -2
  61. package/esm/tools/edit-file.mjs.map +1 -1
  62. package/esm/tools/glob.d.mts +1 -1
  63. package/esm/tools/glob.d.mts.map +1 -1
  64. package/esm/tools/glob.mjs +3 -2
  65. package/esm/tools/glob.mjs.map +1 -1
  66. package/esm/tools/grep.d.mts +1 -1
  67. package/esm/tools/grep.d.mts.map +1 -1
  68. package/esm/tools/grep.mjs +3 -2
  69. package/esm/tools/grep.mjs.map +1 -1
  70. package/esm/tools/json-schemas.mjs +175 -0
  71. package/esm/tools/json-schemas.mjs.map +1 -0
  72. package/esm/tools/list-dir.d.mts +32 -0
  73. package/esm/tools/list-dir.d.mts.map +1 -0
  74. package/esm/tools/list-dir.mjs +50 -0
  75. package/esm/tools/list-dir.mjs.map +1 -0
  76. package/esm/tools/multi-edit.d.mts +38 -0
  77. package/esm/tools/multi-edit.d.mts.map +1 -0
  78. package/esm/tools/multi-edit.mjs +121 -0
  79. package/esm/tools/multi-edit.mjs.map +1 -0
  80. package/esm/tools/read-file.d.mts +5 -5
  81. package/esm/tools/read-file.d.mts.map +1 -1
  82. package/esm/tools/read-file.mjs +10 -20
  83. package/esm/tools/read-file.mjs.map +1 -1
  84. package/esm/tools/run-shell.d.mts +39 -13
  85. package/esm/tools/run-shell.d.mts.map +1 -1
  86. package/esm/tools/run-shell.mjs +60 -47
  87. package/esm/tools/run-shell.mjs.map +1 -1
  88. package/esm/tools/run-tests.d.mts +11 -2
  89. package/esm/tools/run-tests.d.mts.map +1 -1
  90. package/esm/tools/run-tests.mjs +15 -7
  91. package/esm/tools/run-tests.mjs.map +1 -1
  92. package/esm/tools/schema.mjs +24 -19
  93. package/esm/tools/schema.mjs.map +1 -1
  94. package/esm/tools/shell-kill.d.mts +25 -0
  95. package/esm/tools/shell-kill.d.mts.map +1 -0
  96. package/esm/tools/shell-kill.mjs +36 -0
  97. package/esm/tools/shell-kill.mjs.map +1 -0
  98. package/esm/tools/shell-output.d.mts +30 -0
  99. package/esm/tools/shell-output.d.mts.map +1 -0
  100. package/esm/tools/shell-output.mjs +49 -0
  101. package/esm/tools/shell-output.mjs.map +1 -0
  102. package/esm/tools/todo.d.mts +43 -0
  103. package/esm/tools/todo.d.mts.map +1 -0
  104. package/esm/tools/todo.mjs +126 -0
  105. package/esm/tools/todo.mjs.map +1 -0
  106. package/esm/tools/write-file.d.mts +1 -1
  107. package/esm/tools/write-file.d.mts.map +1 -1
  108. package/esm/tools/write-file.mjs +3 -2
  109. package/esm/tools/write-file.mjs.map +1 -1
  110. package/esm/workspace.d.mts +12 -9
  111. package/esm/workspace.d.mts.map +1 -1
  112. package/esm/workspace.mjs +109 -28
  113. package/esm/workspace.mjs.map +1 -1
  114. package/llms-full.txt +192 -35
  115. package/llms.txt +1 -1
  116. package/package.json +5 -4
  117. package/skills/README.md +2 -2
  118. package/skills/build-loop-agent/SKILL.md +7 -7
  119. package/skills/index.md +6 -1
  120. package/skills/use-a-workspace/SKILL.md +185 -28
package/CHANGELOG.md CHANGED
@@ -4,6 +4,73 @@ All notable changes to `@warlock.js/ai-workspace` are documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). `@warlock.js/*` packages are released in lockstep — every package shares the same version number, so a version below may list only the changes that affected this package.
6
6
 
7
+ ## 5.31.0 - 2026-10-18
8
+
9
+ ### Upgrading
10
+
11
+ - **BREAKING** `ws.tools.all()` now also returns `multi_edit`, `list_dir`, `todo_write` and `todo_read` (plus `shell_output` / `shell_kill` when `shell.background` is set), and `readonly().tools.all()` adds `list_dir` and the todo tools. To keep the previous set, use `ws.tools.pick("readFile", "editFile", "writeFile", "runShell", "runTests", "grep", "glob")`.
12
+ - **BREAKING** `WorkspaceOps` gains `multiEdit`, `listDir`, `startShell`, `shellOutput`, `shellKill`, `listShells` and `dispose`, and `Workspace` gains those plus `readTodos` / `writeTodos`. A custom implementation or test double must add them.
13
+ - **BREAKING** With deny rules in force (`shell.deny`, `shell.denyPatterns` or `shell.allowCommand`) and no allowlist entry for it, a command that runs another command out of the policy's sight — `sh`/`bash -c`, `eval`, `source`, `xargs`, `sudo`, `setsid`, … — is now refused (`reason: "nested-command"`). List the runner in `shell.allow` to permit it deliberately. `shell.deny` now also matches case-insensitively.
14
+ - Shell refusal messages changed: they now name the cause and say how to fix the command. Branch on the new `WorkspacePolicyError.reason` instead of parsing `message`.
15
+ - `makeRunShellTool(ops, { background: true })` returns `ToolContract<RunShellInput, RunShellResult | RunShellBackgroundResult>`; without `background` the type is unchanged.
16
+
17
+ ### Added
18
+
19
+ - `list_dir` tool and `ws.listDir(path, { depth, ignore })`: a bounded, jailed directory listing (breadth-first, types and file sizes, `denyPaths` and `list.ignore` left out, symlinks reported but never followed), configured by the new `list` policy (`ignore`, `maxDepth`, `maxEntries`).
20
+ - `multi_edit` tool and `ws.multiEdit({ path, edits, expectHash })`: several exact-string replacements in one file, applied in order and written once — all of them or none. `WorkspaceEditError.editIndex` names the failing edit.
21
+ - Background commands: with `shell.background` (`{ maxJobs?, timeoutMs? }`), `run_shell({ background: true })` / `ws.startShell()` start a long-running command owned by the workspace; `shell_output` / `ws.shellOutput(id, { since, waitMs })` read its new output (newest `shell.maxOutputBytes` kept, with a `cursor`), and `shell_kill` / `ws.shellKill(id)` kill its whole process tree. `ws.listShells()` lists them; `WorkspaceJobError` reports an unknown id. Backends implement the new optional `start()`.
22
+ - `ws.dispose()` kills every background command (whole process trees) and makes the workspace refuse new commands; trees still running when the Node process exits are killed too.
23
+ - `todo_write` / `todo_read` tools and `ws.writeTodos()` / `ws.readTodos()`: an in-memory todo list per workspace, shared with its `readonly()` / `scope()` projections. `TodoStore` is exported.
24
+ - `shell.denyPatterns` (string prefix patterns or RegExps) and `shell.allowCommand(argv, ctx)` (return `true`, `false`, or a refusal explanation) check every command's arguments, so a policy can refuse `git push --force` or `rm -rf /` while allowing `git` and `rm`.
25
+ - `shell.interpreter: "sh"` — an explicit opt-in to shell syntax. The line runs through `/bin/sh -c` (`cmd.exe /d /v:off /s /c` on Windows) after it is parsed and every simple command in it — inside `;`, `&&`, `||`, pipes, subshells, `$( )`, backticks, here-documents and `NAME=value` prefixes — is checked against `deny` / `allow` / `denyPatterns` / `allowCommand`. Redirection targets and `cd` directories must stay in the jail (re-checked with symlinks resolved). Syntax the parser cannot read with confidence is refused. The default stays `"none"` (argv, no shell).
26
+ - `WorkspacePolicyError.reason` (`WorkspaceShellRefusalReason`: `"metacharacter"`, `"unbalanced-quotes"`, `"denied-executable"`, `"not-allowed"`, `"denied-pattern"`, `"nested-command"`, `"unsupported-syntax"`, `"path-escape"`) and `executable`, so hosts can phrase refusals themselves. In argv mode the message suggests the split, e.g. "`&&` is not supported in argv mode: run `git add -A`, then `git commit -m …`, as separate commands."
27
+ - `checkShellCommand(policy, command)` returns the shell policy's verdict (reason, message, every checked argv, paths) without running anything; `analyzeArgvCommand()` and `tokenizeCommand()` are exported.
28
+ - Backends may implement an optional `stat()`; the local and mock backends do. The mock backend's scripted commands accept `running: true` for background starts.
29
+ - The workspace skill and docs cover the new tools, shell mode, argument rules, background commands and `dispose()`.
30
+
31
+ ### Changed
32
+
33
+ - The docs, the skill and the path resolver's comments explain how the workspace jail differs from `@warlock.js/fs`'s new confined reader (`fs.confined`) and why the workspace keeps its own resolver: it accepts absolute paths inside the jail, `allowPaths` roots, `denyPaths`, links that stay inside and paths that do not exist yet. Behaviour is unchanged.
34
+
35
+ ### Fixed
36
+
37
+ - The package README no longer ends with a stray empty code block.
38
+ - The workspace tools now publish a JSON Schema of their input, so providers send the model the real parameter names instead of an empty object schema.
39
+ - `run_tests` quotes its `pattern` for the shell in use: single quotes under `interpreter: "sh"`, so `$` and backticks in a test-name filter stay literal.
40
+
41
+ ### Security
42
+
43
+ - Wrapper commands no longer hide the command they run: `env`, `time`, `timeout`, `nice`, `nohup`, `command`, `builtin`, `exec` and `find -exec` are unwrapped and the inner command is checked too, and a wrapper used with options the policy cannot read is refused. Before, `env rm -rf /` passed a `deny: ["rm"]` policy.
44
+ - `shell.deny` matches executable names case-insensitively, so `RM` on a case-insensitive filesystem no longer slips past `deny: ["rm"]`.
45
+ - A model-supplied `timeoutMs` on `run_shell` can now only lower `shell.timeoutMs`, as documented, never raise it.
46
+ - Shell mode removes `ENV`, `BASH_ENV`, `SHELLOPTS`, `BASHOPTS`, `CDPATH`, `IFS`, `PS4`, `GLOBIGNORE` and `BASH_FUNC_*` from the spawned environment, refuses assignments to such variables, refuses `&` (use background commands), and refuses arithmetic-evaluating constructs (`$(( ))`, array subscripts in builtins) that bash can use to run hidden commands.
47
+ - The docs, README and skill now state plainly that shell mode is a policy layer checked before `/bin/sh` runs the line (defence in depth), not an OS sandbox, and recommend running untrusted agents in a container, microVM or as a separate unprivileged user.
48
+ - Killing a background command, a timeout and `dispose()` signal only the process tree the workspace spawned (its own process group on POSIX, `taskkill /T` on its PID on Windows).
49
+
50
+ ## 5.30.0 - 2026-10-16
51
+
52
+ ### Upgrading
53
+
54
+ - `RunShellResult.cancelled` is now a required field: a custom `WorkspaceOps`/`Workspace` implementation or a test double that builds a `RunShellResult` must set it (`false` unless the command was cancelled).
55
+ - `Workspace.readFile()` and `WorkspaceOps.readFile()` now return `ReadFileResult` (adds `startLine`, `endLine`, `truncated`, `bytesCapped`, `totalBytes`); custom implementations must return those fields.
56
+ - A read window that starts past the end of the file now returns empty `content` instead of one numbered empty line.
57
+ - `read.maxBytes` now actually caps the returned `content`; a read that used to return a whole long file under a small cap now comes back cut, with `bytesCapped: true`.
58
+
59
+ ### Added
60
+
61
+ - `exec()` takes a per-call `signal` (`ExecOptions`); `run_shell` and `run_tests` pass the agent run's `ctx.signal`. An already-aborted signal never starts the command; an abort mid-run kills the process tree. Both resolve with the new `cancelled: true` on `RunShellResult`, never together with `timedOut`.
62
+ - `WorkspaceBackendExecOptions.signal` and `WorkspaceBackendExecResult.cancelled?` for custom backends; the local backend removes its abort listener when the command settles, and the mock backend honours a pre-aborted signal.
63
+ - Read results carry `startLine`, `endLine`, `truncated`, `bytesCapped` and `totalBytes`; `readFile()` and the `read_file` tool now return the same `ReadFileResult` for the same arguments.
64
+
65
+ ### Fixed
66
+
67
+ - `read.maxBytes` is enforced: the returned `content` (line-number prefixes and newlines included) never exceeds it, so a single long line no longer bypasses the cap. Lines are cut on a character boundary, never mid-character; `hash` remains the SHA-256 of the whole file.
68
+ - Command output clipped at `shell.maxOutputBytes` is cut on a character boundary instead of ending in a replacement character.
69
+ - A read window past the end of the file returns empty `content` instead of one numbered empty line.
70
+ - Writes, edits and removes of the same path are serialized, so two concurrent `edit_file` calls (the agent now dispatches a trip's tool calls concurrently) can no longer both pass the stale-hash guard and silently drop one edit; the second re-reads and is rejected as stale, or applies on top when it pinned no hash.
71
+ - Aborting a command after it has already exited (while its stdio is still draining) no longer reports it as `cancelled`; the tree is still reaped so the result settles.
72
+ - On Windows, a `taskkill` that cannot be started no longer crashes the process with an unhandled `'error'` event; the direct child is killed instead.
73
+
7
74
  ## 5.29.0 - 2026-10-01
8
75
 
9
76
  ### Changed
@@ -75,6 +142,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
75
142
  ### Changed
76
143
 
77
144
  - Lockstep release maintenance and dependency refresh.
145
+
78
146
  ## 5.20.0 - 2026-09-24
79
147
 
80
148
  ### Changed
package/README.md CHANGED
@@ -16,11 +16,14 @@ OS sandbox. Every path is `realpath`-resolved and must sit under `cwd` (or an
16
16
  `allowPaths` root); `denyPaths` globs are blocked even inside `cwd`; shell commands
17
17
  are gated by an executable allow/deny list; and `process.env` is **never** inherited
18
18
  wholesale (a command can't even find `node`/`npm` unless the policy opts in via
19
- `shell.inheritEnv: ["PATH"]`). What it does **not** do is contain what an *allowed*
19
+ `shell.inheritEnv: ["PATH"]`). What it does **not** do is contain what an _allowed_
20
20
  process does once it runs — an allowed `node` can still open a socket or read a file
21
- its own user can reach. For untrusted code you want real isolation (a container /
22
- microVM backend), which is a later phase. Treat this as least-privilege guardrails
23
- for a **trusted** agent, not a security boundary around a hostile one.
21
+ its own user can reach. Shell mode (`interpreter: "sh"`) is likewise a policy layer
22
+ checked before `/bin/sh` runs the line — defence in depth, not a sandbox. For an
23
+ untrusted agent, run the workspace inside a container or microVM with only the
24
+ project mounted, or at least as a separate unprivileged user, and keep this policy
25
+ as a second layer. Treat it as least-privilege guardrails for a **trusted** agent,
26
+ not a security boundary around a hostile one.
24
27
 
25
28
  ## Install
26
29
 
@@ -41,7 +44,7 @@ import "@warlock.js/ai-workspace";
41
44
  ### 1. Minimal build-loop agent
42
45
 
43
46
  Hand the whole tool set to an agent and let it read, edit, and run tests until green.
44
- The failure comes back as tool *data*, so the agent self-corrects.
47
+ The failure comes back as tool _data_, so the agent self-corrects.
45
48
 
46
49
  ```ts
47
50
  import "@warlock.js/ai-workspace";
@@ -66,7 +69,7 @@ await dev.execute("`npm test` fails on the cart-total suite. Make it green.");
66
69
 
67
70
  A reviewer needs to read and search — never to write or run a shell. Pick exactly
68
71
  those three tools, or take a `readonly()` projection (which omits every mutating tool
69
- *and* rejects the mutating direct methods).
72
+ _and_ rejects the mutating direct methods).
70
73
 
71
74
  ```ts
72
75
  const ws = ai.workspace({ cwd: "/srv/acme-api" });
@@ -80,7 +83,7 @@ await reviewer.execute("Review src/cart for off-by-one bugs and summarize.");
80
83
 
81
84
  // Equivalent, as a projection of the same jail:
82
85
  const ro = ws.readonly();
83
- ai.agent({ model, tools: ro.tools.all() }); // read_file + grep + glob only
86
+ ai.agent({ model, tools: ro.tools.all() }); // read_file, list_dir, grep, glob, todo tools
84
87
  ```
85
88
 
86
89
  ### 3. Direct programmatic use (no LLM)
@@ -114,29 +117,46 @@ for (const file of await ws.glob("src/models/**/*.ts")) {
114
117
  }
115
118
  ```
116
119
 
117
- ## What's in this release (W1)
120
+ ## What's in the package
118
121
 
119
122
  - **Policy jail** — `cwd` path jail (`realpath`-resolved), `allowPaths` / `denyPaths`,
120
- a fail-closed shell allow/deny list (deny wins), per-command timeout + output cap,
121
- and opt-in `inheritEnv` (no blanket `process.env`).
123
+ a fail-closed shell allow/deny list (deny wins), argument rules (`denyPatterns`,
124
+ `allowCommand`), per-command timeout + output cap, and opt-in `inheritEnv` (no
125
+ blanket `process.env`).
126
+ - **argv by default, shell on request** — commands run as one argv without a shell;
127
+ a refusal names the operator and suggests the split. `shell.interpreter: "sh"`
128
+ opts in to pipes, `&&` and redirections, with every command in the line checked
129
+ and redirections kept inside the jail.
122
130
  - **Backends** — `local` (default; `@warlock.js/fs` + `node:child_process`) and
123
131
  `mock` (in-memory, for hermetic disk-free tests).
124
- - **Seven tools** under `ws.tools.*` — `read_file`, `edit_file`, `write_file`,
125
- `run_shell`, `run_tests`, `grep`, `glob` — each a `ToolContract` ready for
126
- `ai.agent({ tools })`.
127
- - **Direct methods** — `readFile` / `writeFile` / `editFile` / `exec` / `grep` /
128
- `glob` / `exists` / `mkdir` / `remove`, sharing the exact same policy seam.
132
+ - **Tools** under `ws.tools.*` — `read_file`, `edit_file`, `multi_edit`, `write_file`,
133
+ `list_dir`, `run_shell` (optionally in the background), `shell_output`,
134
+ `shell_kill`, `run_tests`, `grep`, `glob`, `todo_write`, `todo_read` — each a
135
+ `ToolContract` (with a JSON Schema of its input) ready for `ai.agent({ tools })`.
136
+ - **Direct methods** — `readFile` / `writeFile` / `editFile` / `multiEdit` /
137
+ `listDir` / `exec` / `startShell` / `shellOutput` / `shellKill` / `listShells` /
138
+ `grep` / `glob` / `exists` / `mkdir` / `remove` / `readTodos` / `writeTodos`,
139
+ sharing the exact same policy seam, and `dispose()`.
140
+ - **Background commands** — with `shell.background`, dev servers and watchers run
141
+ as jobs owned by the workspace, read incrementally, and are killed (whole process
142
+ tree) by `shell_kill` or `dispose()`.
129
143
  - **`tools.all()` / `tools.pick(...)`** — every tool, or a least-privilege subset.
130
- - **`readonly()`** — a projection that vends only read/grep/glob and rejects every
131
- mutating direct method.
144
+ - **`readonly()`** — a projection that vends only the read-side tools (read, list,
145
+ grep, glob, todos) and rejects every mutating direct method.
132
146
  - **`scope(subdir)`** — a sub-jailed view rooted at `subdir` (tighter blast radius).
133
147
  - **Read-before-edit guard** — `read_file` returns a SHA-256 `hash`; `edit_file`
134
148
  requires an exact, unique `oldString` (or `replaceAll`) and rejects on an
135
149
  `expectHash` mismatch.
150
+ - **Bounded reads** — `read.maxBytes` caps the UTF-8 bytes a read returns (line
151
+ numbers included), cutting on a character boundary and flagging `bytesCapped`;
152
+ the `hash` still covers the whole file.
153
+ - **Cancellable commands** — `exec(cmd, { signal })`, and the agent run's signal
154
+ in `run_shell` / `run_tests`, stop the command (process tree killed, or never
155
+ started when already aborted) and report `cancelled: true`.
136
156
 
137
- **Deferred to later phases** (intentionally absent here): the transactional
138
- lifecycle (`snapshot` / `restore` / `diff` / `changes`), `on()` hooks, `dispose`,
139
- `usage()`, and the worktree / container backends plus provisioning helpers.
157
+ **Not in the package yet**: the transactional lifecycle (`snapshot` / `restore` /
158
+ `diff` / `changes`), `on()` hooks, `usage()`, and the worktree / container backends
159
+ plus provisioning helpers.
140
160
 
141
161
  ## Skills
142
162
 
@@ -146,4 +166,3 @@ Progressive-disclosure, per-task docs live under [`skills/`](./skills/README.md)
146
166
  and operate it (policy, tools, direct methods, `readonly` / `scope`).
147
167
  - [`build-loop-agent/`](./skills/build-loop-agent/SKILL.md) — wire `ws.tools.all()`
148
168
  into a coding agent that reads → edits → runs tests until green.
149
- ```