pikuri-workspace 0.0.3 → 0.0.4
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 +4 -4
- data/README.md +31 -16
- data/lib/pikuri/{tool → workspace}/confirmer.rb +3 -3
- data/lib/pikuri/{tool → workspace}/edit.rb +14 -14
- data/lib/pikuri/workspace/filesystem.rb +543 -0
- data/lib/pikuri/{tool → workspace}/glob.rb +29 -17
- data/lib/pikuri/{tool → workspace}/grep.rb +28 -16
- data/lib/pikuri/workspace/project_root.rb +102 -0
- data/lib/pikuri/workspace/read.rb +411 -0
- data/lib/pikuri/{tool → workspace}/write.rb +14 -14
- data/lib/pikuri-workspace.rb +16 -13
- metadata +20 -18
- data/lib/pikuri/tool/read.rb +0 -254
- data/lib/pikuri/tool/workspace.rb +0 -150
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4be40e803f487eb130b8ee758b86efb7a744c92691a1dd103aae68d91e59a2f9
|
|
4
|
+
data.tar.gz: 981478770e9e7f8acef1fb1ca2b1896cf6a25c7eb73c01c1d57592f81583cf0f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a211df218e4270acbe737f624cc8a77cc145d8a051c3500131036692168996c800a0110a27bd714a7cad3fb5cb9dd3e25fdfdeefb917fb73ccead021cd807c94
|
|
7
|
+
data.tar.gz: fe2ff7510012d619caa166252ccef80148750e35fa58a68cd1722cec372758dc17e5605cbe380c4f106538b301134402da32e0fb13c6f23d6acec7c4aceb301e
|
data/README.md
CHANGED
|
@@ -4,15 +4,17 @@ Filesystem tools + Workspace/Confirmer seams for the
|
|
|
4
4
|
[pikuri](https://codeberg.org/mvysny/pikuri) AI-assistant toolkit.
|
|
5
5
|
|
|
6
6
|
Self-contained "operate on a directory tree" toolkit:
|
|
7
|
-
- `Pikuri::
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
- `Pikuri::Workspace::Filesystem` — scopes filesystem access to a
|
|
8
|
+
project root + explicit readable / writable prefix lists, with an
|
|
9
|
+
optional ephemeral temp playground. Rejects `..`-escapes and
|
|
10
|
+
symlinks that resolve outside the configured roots.
|
|
11
|
+
- `Pikuri::Workspace::Confirmer` — abstract base + `AUTO_APPROVE` /
|
|
11
12
|
`TERMINAL` for user-state mutations.
|
|
12
|
-
- Five file tools: `Pikuri::
|
|
13
|
-
`Pikuri::
|
|
13
|
+
- Five file tools: `Pikuri::Workspace::Read`,
|
|
14
|
+
`Pikuri::Workspace::Write`, `Pikuri::Workspace::Edit`,
|
|
15
|
+
`Pikuri::Workspace::Grep`, `Pikuri::Workspace::Glob`.
|
|
14
16
|
|
|
15
|
-
No shell execution — `Pikuri::
|
|
17
|
+
No shell execution — `Pikuri::Code::Bash` ships in
|
|
16
18
|
[`pikuri-code`](../pikuri-code/README.md) on top of these.
|
|
17
19
|
|
|
18
20
|
## Install
|
|
@@ -28,23 +30,36 @@ gem 'pikuri-workspace'
|
|
|
28
30
|
require 'pikuri-core'
|
|
29
31
|
require 'pikuri-workspace'
|
|
30
32
|
|
|
31
|
-
workspace = Pikuri::
|
|
32
|
-
confirmer = Pikuri::
|
|
33
|
+
workspace = Pikuri::Workspace::Filesystem.new(project_root: Dir.pwd)
|
|
34
|
+
confirmer = Pikuri::Workspace::Confirmer::TERMINAL
|
|
33
35
|
|
|
34
36
|
agent = Pikuri::Agent.new(
|
|
35
37
|
transport: ...,
|
|
36
38
|
system_prompt: ...,
|
|
37
39
|
) do |c|
|
|
38
|
-
c.add_tool Pikuri::
|
|
39
|
-
c.add_tool Pikuri::
|
|
40
|
-
c.add_tool Pikuri::
|
|
41
|
-
c.add_tool Pikuri::
|
|
42
|
-
c.add_tool Pikuri::
|
|
40
|
+
c.add_tool Pikuri::Workspace::Read.new(workspace: workspace)
|
|
41
|
+
c.add_tool Pikuri::Workspace::Grep.new(workspace: workspace)
|
|
42
|
+
c.add_tool Pikuri::Workspace::Glob.new(workspace: workspace)
|
|
43
|
+
c.add_tool Pikuri::Workspace::Edit.new(workspace: workspace)
|
|
44
|
+
c.add_tool Pikuri::Workspace::Write.new(workspace: workspace, confirmer: confirmer)
|
|
43
45
|
c.add_listener ...
|
|
44
46
|
end
|
|
45
47
|
```
|
|
46
48
|
|
|
47
|
-
`Workspace
|
|
49
|
+
`Workspace` is the "look-but-don't-leak" guard around filesystem
|
|
48
50
|
access. Read tools route through `#resolve_for_read(path)`; mutating
|
|
49
51
|
tools route through `#resolve_for_write(path)` + the Confirmer's
|
|
50
|
-
`#confirm?(prompt:)`.
|
|
52
|
+
`#confirm?(prompt:)`. Pass `temp: true` to mint an ephemeral
|
|
53
|
+
writable playground via `Dir.mktmpdir` — its path is exposed as
|
|
54
|
+
`workspace.temp` and auto-removed at process exit.
|
|
55
|
+
|
|
56
|
+
## Further reading
|
|
57
|
+
|
|
58
|
+
- **Narrative walkthrough:** the "Workspace seam" and "Confirmer
|
|
59
|
+
seam" sections of
|
|
60
|
+
[chapter 8 of the pikuri guide](../docs/guide/08-code.md) — what
|
|
61
|
+
each kwarg of `Filesystem.new` controls, when to use the
|
|
62
|
+
`AllowAll` variant, and how the seams fit alongside `Sandbox`.
|
|
63
|
+
- **API reference:** browse the YARD docs at
|
|
64
|
+
<https://rubydoc.info/gems/pikuri-workspace> (once published),
|
|
65
|
+
or run `bundle exec yard` in this directory for a local copy.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Pikuri
|
|
4
|
-
|
|
4
|
+
module Workspace
|
|
5
5
|
# Port for asking the user to confirm a potentially destructive tool
|
|
6
|
-
# operation — currently {
|
|
7
|
-
# {
|
|
6
|
+
# operation — currently {Pikuri::Code::Bash} (every command) and
|
|
7
|
+
# {Write} (overwrite of an existing file with non-identical
|
|
8
8
|
# content). Subclass and implement {#confirm?}.
|
|
9
9
|
#
|
|
10
10
|
# == Why a Boolean return
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Pikuri
|
|
4
|
-
|
|
4
|
+
module Workspace
|
|
5
5
|
# The +edit+ tool — exact-string replacement on an existing file.
|
|
6
|
-
# Instantiating +
|
|
7
|
-
# {Tool#to_ruby_llm_tool} wiring is identical to any bundled
|
|
8
|
-
# Same shape as {
|
|
6
|
+
# Instantiating +Edit.new(workspace: ws)+ produces a tool whose
|
|
7
|
+
# {Pikuri::Tool#to_ruby_llm_tool} wiring is identical to any bundled
|
|
8
|
+
# tool's. Same shape as {Read} (workspace captured by +execute+; no
|
|
9
9
|
# confirmer needed).
|
|
10
10
|
#
|
|
11
11
|
# == Why no confirmer
|
|
12
12
|
#
|
|
13
13
|
# The +old_string+ argument is itself an implicit read-check: the
|
|
14
14
|
# model can't write a correct +old_string+ without having seen the
|
|
15
|
-
# file (via {
|
|
15
|
+
# file (via {Read} or out-of-band), so the blast radius of any
|
|
16
16
|
# Edit is bounded by the model's actual knowledge of file state.
|
|
17
17
|
# That makes Edit safe to execute without prompting — by contrast,
|
|
18
|
-
# {
|
|
18
|
+
# {Write} requires a confirmer because a hallucinated 500-line
|
|
19
19
|
# +content+ could clobber unread bytes.
|
|
20
20
|
#
|
|
21
21
|
# == Matching is strict (no fuzz cascade)
|
|
@@ -23,7 +23,7 @@ module Pikuri
|
|
|
23
23
|
# +old_string+ must match the file byte-for-byte. v1 ships *no*
|
|
24
24
|
# fallback replacer (no whitespace-normalized, line-trimmed, block-
|
|
25
25
|
# anchor, etc.). Predictability beats fuzz: when an Edit fails, the
|
|
26
|
-
# model re-reads with {
|
|
26
|
+
# model re-reads with {Read} and retries — clear failure mode,
|
|
27
27
|
# no compounding-heuristic risk. opencode runs a 9-replacer cascade
|
|
28
28
|
# under the hood despite its own description saying "must match
|
|
29
29
|
# exactly"; pi stays strict. We match pi.
|
|
@@ -32,7 +32,7 @@ module Pikuri
|
|
|
32
32
|
#
|
|
33
33
|
# The one structural exception to "strict bytes": files with CRLF
|
|
34
34
|
# line endings get matched in LF space, and the original line ending
|
|
35
|
-
# is restored on write. Reason: {
|
|
35
|
+
# is restored on write. Reason: {Read} renders content via
|
|
36
36
|
# +each_line+ + +chomp+, which strips +\r\n+ to +\n+ in what the
|
|
37
37
|
# model sees. A pure strict byte-match would then never succeed on
|
|
38
38
|
# CRLF files because the model can only ever supply LF. opencode and
|
|
@@ -62,7 +62,7 @@ module Pikuri
|
|
|
62
62
|
# multi-match error suggesting more context or +replace_all+.
|
|
63
63
|
# * File missing / is a directory / is binary → respective error.
|
|
64
64
|
# * Workspace boundary violation / EACCES → standard rescue path.
|
|
65
|
-
class Edit < Tool
|
|
65
|
+
class Edit < Pikuri::Tool
|
|
66
66
|
# Description shown to the LLM. Follows the opencode-shape (summary
|
|
67
67
|
# + +Usage:+ bullets) prescribed by the project's tool-description
|
|
68
68
|
# convention. Per-parameter constraints live in the parameter
|
|
@@ -82,7 +82,7 @@ module Pikuri
|
|
|
82
82
|
- CRLF files are matched in LF space; the original line endings are preserved on write.
|
|
83
83
|
DESC
|
|
84
84
|
|
|
85
|
-
# @param workspace [
|
|
85
|
+
# @param workspace [Filesystem] captured for path resolution;
|
|
86
86
|
# all reads/writes route through +workspace.resolve_for_write+
|
|
87
87
|
# (Edit modifies, so it uses the write-set even though it doesn't
|
|
88
88
|
# create files).
|
|
@@ -122,7 +122,7 @@ module Pikuri
|
|
|
122
122
|
# binary), match +old_string+ in line-ending-normalized form, and
|
|
123
123
|
# write the result back preserving the file's original line endings.
|
|
124
124
|
#
|
|
125
|
-
# @param workspace [
|
|
125
|
+
# @param workspace [Filesystem]
|
|
126
126
|
# @param path [String] raw path as supplied by the LLM
|
|
127
127
|
# @param old_string [String] text to find
|
|
128
128
|
# @param new_string [String] text to substitute in
|
|
@@ -137,9 +137,9 @@ module Pikuri
|
|
|
137
137
|
return "Error: file not found: #{path}" unless resolved.exist?
|
|
138
138
|
return "Error: #{path} is a directory" if resolved.directory?
|
|
139
139
|
|
|
140
|
+
return "Error: cannot edit binary file: #{path}" if Pikuri::FileType.binary?(resolved)
|
|
141
|
+
|
|
140
142
|
raw = resolved.binread
|
|
141
|
-
sample = raw.byteslice(0, Tool::Read::BINARY_SAMPLE_BYTES)
|
|
142
|
-
return "Error: cannot edit binary file: #{path}" if Tool::Read.binary?(sample)
|
|
143
143
|
|
|
144
144
|
crlf = raw.include?("\r\n")
|
|
145
145
|
content = crlf ? raw.gsub("\r\n", "\n") : raw
|
|
@@ -173,7 +173,7 @@ module Pikuri
|
|
|
173
173
|
resolved.write(final)
|
|
174
174
|
|
|
175
175
|
"Edited #{path}: replaced #{replaced} occurrence#{replaced == 1 ? '' : 's'}."
|
|
176
|
-
rescue
|
|
176
|
+
rescue Filesystem::Error => e
|
|
177
177
|
"Error: #{e.message}"
|
|
178
178
|
rescue Errno::EACCES => e
|
|
179
179
|
"Error: cannot edit #{path}: #{e.message}"
|