jeo-code 0.4.4 → 0.4.5

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.ja.md CHANGED
@@ -150,11 +150,11 @@ CI は `.github/workflows/npm-publish.yml` で公開します — GitHub リリ
150
150
  ## 変更履歴 (Changelog)
151
151
 
152
152
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
153
+ - **[0.4.5]** (2026-06-14) — First-class filesystem make/remove tools.
153
154
  - **[0.4.4]** (2026-06-13) — Live subagent status mirroring, always-useful Ctrl+O activity tail, read lineRange crash guard.
154
155
  - **[0.4.3]** (2026-06-13) — Readability pass for autopilot, subagent activity, and worked-history review.
155
156
  - **[0.4.2]** (2026-06-13) — Thinking-loop termination guarantees (cycle guard + turn wall-clock budget), unboxed live status without step counters, self-contained `.jeo` namespace, live next-prompt input card, role-targeted model/thinking picker.
156
157
  - **[0.4.1]** (2026-06-12) — TUI card parity polish + done-time todo reconciliation.
157
- - **[0.4.0]** (2026-06-12) — Verified TUI, resilient engine, batch input, multilingual docs.
158
158
 
159
159
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
160
160
  <!-- CHANGELOG:END -->
package/README.ko.md CHANGED
@@ -150,11 +150,11 @@ CI는 `.github/workflows/npm-publish.yml`로 배포합니다 — GitHub 릴리
150
150
  ## 변경 이력 (Changelog)
151
151
 
152
152
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
153
+ - **[0.4.5]** (2026-06-14) — First-class filesystem make/remove tools.
153
154
  - **[0.4.4]** (2026-06-13) — Live subagent status mirroring, always-useful Ctrl+O activity tail, read lineRange crash guard.
154
155
  - **[0.4.3]** (2026-06-13) — Readability pass for autopilot, subagent activity, and worked-history review.
155
156
  - **[0.4.2]** (2026-06-13) — Thinking-loop termination guarantees (cycle guard + turn wall-clock budget), unboxed live status without step counters, self-contained `.jeo` namespace, live next-prompt input card, role-targeted model/thinking picker.
156
157
  - **[0.4.1]** (2026-06-12) — TUI card parity polish + done-time todo reconciliation.
157
- - **[0.4.0]** (2026-06-12) — Verified TUI, resilient engine, batch input, multilingual docs.
158
158
 
159
159
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
160
160
  <!-- CHANGELOG:END -->
package/README.md CHANGED
@@ -150,11 +150,11 @@ Required npm token permissions (repository secret `NPM_TOKEN`):
150
150
  ## Changelog
151
151
 
152
152
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
153
+ - **[0.4.5]** (2026-06-14) — First-class filesystem make/remove tools.
153
154
  - **[0.4.4]** (2026-06-13) — Live subagent status mirroring, always-useful Ctrl+O activity tail, read lineRange crash guard.
154
155
  - **[0.4.3]** (2026-06-13) — Readability pass for autopilot, subagent activity, and worked-history review.
155
156
  - **[0.4.2]** (2026-06-13) — Thinking-loop termination guarantees (cycle guard + turn wall-clock budget), unboxed live status without step counters, self-contained `.jeo` namespace, live next-prompt input card, role-targeted model/thinking picker.
156
157
  - **[0.4.1]** (2026-06-12) — TUI card parity polish + done-time todo reconciliation.
157
- - **[0.4.0]** (2026-06-12) — Verified TUI, resilient engine, batch input, multilingual docs.
158
158
 
159
159
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
160
160
  <!-- CHANGELOG:END -->
package/README.zh.md CHANGED
@@ -150,11 +150,11 @@ CI 通过 `.github/workflows/npm-publish.yml` 发布 — GitHub 发布 release
150
150
  ## 更新日志 (Changelog)
151
151
 
152
152
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
153
+ - **[0.4.5]** (2026-06-14) — First-class filesystem make/remove tools.
153
154
  - **[0.4.4]** (2026-06-13) — Live subagent status mirroring, always-useful Ctrl+O activity tail, read lineRange crash guard.
154
155
  - **[0.4.3]** (2026-06-13) — Readability pass for autopilot, subagent activity, and worked-history review.
155
156
  - **[0.4.2]** (2026-06-13) — Thinking-loop termination guarantees (cycle guard + turn wall-clock budget), unboxed live status without step counters, self-contained `.jeo` namespace, live next-prompt input card, role-targeted model/thinking picker.
156
157
  - **[0.4.1]** (2026-06-12) — TUI card parity polish + done-time todo reconciliation.
157
- - **[0.4.0]** (2026-06-12) — Verified TUI, resilient engine, batch input, multilingual docs.
158
158
 
159
159
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
160
160
  <!-- CHANGELOG:END -->
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jeo-code",
3
- "version": "0.4.4",
3
+ "version": "0.4.5",
4
4
  "description": "Clean, highly optimized AI coding agent using spec-first loop",
5
5
  "type": "module",
6
6
  "main": "src/cli.ts",
@@ -11,7 +11,7 @@ import * as fs from "node:fs/promises";
11
11
  import * as path from "node:path";
12
12
  import type { Message } from "./loop";
13
13
  import { extractJsonObject } from "./json";
14
- import { readTool, writeTool, editTool, bashTool, findTool, searchTool, lsTool, type ToolResult } from "./tools";
14
+ import { readTool, writeTool, editTool, bashTool, findTool, searchTool, lsTool, mkdirTool, deleteTool, type ToolResult } from "./tools";
15
15
  import { webSearchTool, setWebSearchActiveModel } from "./web-search";
16
16
  import { friendlyProviderError, isContextOverflowError, isRefusalError } from "../util/provider-error";
17
17
  import { isRateLimitError } from "../util/retry";
@@ -50,6 +50,8 @@ export const DEFAULT_TOOLS: Record<string, ToolHandler> = {
50
50
  find: (a, cwd) => findTool(a.globPattern ?? a.pattern, cwd),
51
51
  search: (a, cwd) => searchTool(a.pattern, a.globPattern ?? "*", cwd, !!(a.ignoreCase ?? a.i), { before: a.before, after: a.after, context: a.context, maxMatches: a.maxMatches }),
52
52
  ls: (a, cwd) => lsTool(a.dirPath ?? a.path ?? a.dir ?? ".", cwd),
53
+ mkdir: (a, cwd) => mkdirTool(a.dirPath ?? a.path ?? a.dir, cwd),
54
+ delete: (a, cwd) => deleteTool(a.path ?? a.filePath ?? a.targetPath ?? a.dirPath, cwd, !!(a.recursive ?? a.r)),
53
55
  web_search: (a, cwd) => webSearchTool(a, cwd),
54
56
  };
55
57
 
@@ -63,8 +65,10 @@ export const TOOL_PROTOCOL = [
63
65
  "5. find {globPattern} — find files by name",
64
66
  "6. search {pattern, globPattern?, ignoreCase?, context?, maxMatches?} — grep (context: N lines around each match)",
65
67
  "7. ls {dirPath} — list a directory's entries (dirs first)",
66
- "8. web_search {query, recency?, limit?} search the web (Anthropic-native: synthesized answer + sources + citations)",
67
- "9. done {reason?} call when the task is fully implemented AND verified",
68
+ "8. mkdir {dirPath} create a directory (parents included; idempotent)",
69
+ "9. delete {path, recursive?} remove a file (or directory with recursive:true)",
70
+ "10. web_search {query, recency?, limit?} — search the web (Anthropic-native: synthesized answer + sources + citations)",
71
+ "11. done {reason?} — call when the task is fully implemented AND verified",
68
72
  "",
69
73
  "Reply with STRICT JSON only — no code fences. You MAY include an optional leading",
70
74
  '"reasoning" string (one short sentence on your plan) before "tool":',
@@ -233,7 +233,7 @@ export function bashCommandAllowed(command: string, prefixes: string[]): boolean
233
233
  */
234
234
  export function subagentToolset(role: SubagentRole): Record<string, ToolHandler> {
235
235
  if (role.readOnly) {
236
- const MUTATING = new Set(["write", "edit", "bash"]);
236
+ const MUTATING = new Set(["write", "edit", "bash", "mkdir", "delete"]);
237
237
  const ro: Record<string, ToolHandler> = {};
238
238
  for (const [name, handler] of Object.entries(DEFAULT_TOOLS)) {
239
239
  if (MUTATING.has(name)) continue;
@@ -812,3 +812,65 @@ export async function lsTool(
812
812
  return { success: false, output: "", error: err.message };
813
813
  }
814
814
  }
815
+
816
+ /**
817
+ * Create a directory (and any missing parents). Idempotent: an already-existing
818
+ * directory is a success, not an error — the model should not need to branch on
819
+ * existence. Respects the deep-interview mutation lock like write/edit.
820
+ */
821
+ export async function mkdirTool(
822
+ dirPath: string,
823
+ cwd: string = process.cwd()
824
+ ): Promise<ToolResult> {
825
+ try {
826
+ if (typeof dirPath !== "string" || dirPath.trim() === "") {
827
+ return { success: false, output: "", error: 'mkdir requires a non-empty "dirPath".' };
828
+ }
829
+ await assertMutationAllowed(dirPath, cwd);
830
+ const abs = path.resolve(cwd, dirPath);
831
+ const existing = await fs.stat(abs).catch(() => null);
832
+ if (existing && !existing.isDirectory()) {
833
+ return { success: false, output: "", error: `Path exists and is not a directory: ${dirPath}` };
834
+ }
835
+ await fs.mkdir(abs, { recursive: true });
836
+ return { success: true, output: `Directory ready: ${dirPath}` };
837
+ } catch (err: any) {
838
+ return { success: false, output: "", error: err.message };
839
+ }
840
+ }
841
+
842
+ /**
843
+ * Delete a file or directory. A directory requires `recursive: true` so a stray
844
+ * call cannot wipe a populated tree by accident. Missing paths are a soft error
845
+ * (nothing to delete) rather than a crash. Respects the mutation lock like
846
+ * write/edit; the file-freshness snapshot is cleared so a later write to the
847
+ * same path is not rejected as stale.
848
+ */
849
+ export async function deleteTool(
850
+ targetPath: string,
851
+ cwd: string = process.cwd(),
852
+ recursive: boolean = false
853
+ ): Promise<ToolResult> {
854
+ try {
855
+ if (typeof targetPath !== "string" || targetPath.trim() === "") {
856
+ return { success: false, output: "", error: 'delete requires a non-empty "path".' };
857
+ }
858
+ await assertMutationAllowed(targetPath, cwd);
859
+ const abs = path.resolve(cwd, targetPath);
860
+ if (abs === path.resolve(cwd)) {
861
+ return { success: false, output: "", error: "Refusing to delete the working directory itself." };
862
+ }
863
+ const st = await fs.stat(abs).catch(() => null);
864
+ if (!st) {
865
+ return { success: false, output: "", error: `Nothing to delete: ${targetPath} (does not exist).` };
866
+ }
867
+ if (st.isDirectory() && !recursive) {
868
+ return { success: false, output: "", error: `${targetPath} is a directory — pass recursive:true to remove it and its contents.` };
869
+ }
870
+ await fs.rm(abs, { recursive, force: false });
871
+ lastReadSnapshots.delete(abs); // a future write to this path must not be flagged stale
872
+ return { success: true, output: `Deleted ${st.isDirectory() ? "directory" : "file"}: ${targetPath}` };
873
+ } catch (err: any) {
874
+ return { success: false, output: "", error: err.message };
875
+ }
876
+ }