space-architect 9.2.1 → 9.4.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +89 -0
- data/README.md +2 -4
- data/lib/space_architect/architect_project.rb +3 -21
- data/lib/space_architect/cli/architect.rb +66 -295
- data/lib/space_architect/harness.rb +2 -51
- data/lib/space_architect/routing.rb +43 -0
- data/lib/space_architect/version.rb +1 -1
- data/lib/space_architect.rb +0 -4
- data/skill/architect/SKILL.md +145 -319
- data/skill/architect-action/SKILL.md +123 -0
- data/skill/architect-discuss/SKILL.md +252 -0
- data/skill/{architect → architect-discuss}/dispatch.md +5 -4
- data/skill/{architect → architect-discuss}/research.md +2 -1
- data/skill/architect-research/SKILL.md +3 -2
- data/skill/architect-status/SKILL.md +55 -0
- metadata +7 -45
- data/lib/space_architect/conversations_client.rb +0 -81
- data/lib/space_architect/jobs_client.rb +0 -147
- data/lib/space_architect/run_creator.rb +0 -53
- data/lib/space_architect/session_sync/bin_path.rb +0 -28
- data/lib/space_architect/session_sync/cursor.rb +0 -43
- data/lib/space_architect/session_sync/plist.rb +0 -111
- data/lib/space_architect/session_sync/runner.rb +0 -64
- data/lib/space_architect/session_sync/session_id.rb +0 -20
- data/lib/space_architect/session_sync.rb +0 -56
- data/lib/space_architect/space_push.rb +0 -274
- data/skill/architect-plan/SKILL.md +0 -114
- data/skill/architect-vocabulary/SKILL.md +0 -144
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "zlib"
|
|
4
|
-
require "tempfile"
|
|
5
|
-
require "json"
|
|
6
|
-
require "async/http/client"
|
|
7
|
-
require "async/http/endpoint"
|
|
8
|
-
require "protocol/http/body/readable"
|
|
9
|
-
|
|
10
|
-
module Space::Architect
|
|
11
|
-
# Gem-side sender for the space-server import mouth: packs the current space
|
|
12
|
-
# into a gzipped tar and POSTs it to <host>/spaces/import
|
|
13
|
-
# (Content-Type: application/gzip, Bearer auth). A 200 response is the
|
|
14
|
-
# server's import summary {id:, slug:, iterations:, artifacts:, runs:};
|
|
15
|
-
# any non-200 raises Space::Core::Error carrying the server's error body.
|
|
16
|
-
class SpacePush
|
|
17
|
-
IMPORT_PATH = "/spaces/import"
|
|
18
|
-
CONTENT_TYPE = "application/gzip"
|
|
19
|
-
|
|
20
|
-
def initialize(host, token, client: nil)
|
|
21
|
-
@host = host.chomp("/")
|
|
22
|
-
@token = token
|
|
23
|
-
@client = client
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
# POSTs a packed bundle (a gzip tar path) per the frozen import contract
|
|
27
|
-
# and returns the parsed summary hash. Raises Space::Core::Error on any
|
|
28
|
-
# non-200 — the CLI's handle_errors turns that into a stderr message and
|
|
29
|
-
# a nonzero exit.
|
|
30
|
-
def push(bundle_path)
|
|
31
|
-
Sync do
|
|
32
|
-
with_client do |c|
|
|
33
|
-
body = file_body(bundle_path)
|
|
34
|
-
begin
|
|
35
|
-
response = c.post(IMPORT_PATH, headers: headers, body: body)
|
|
36
|
-
parse_response(response)
|
|
37
|
-
ensure
|
|
38
|
-
body.close
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, SocketError => e
|
|
43
|
-
raise Space::Core::Error, "POST #{IMPORT_PATH} failed: #{e.message} (host: #{@host})"
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
private
|
|
47
|
-
|
|
48
|
-
def with_client
|
|
49
|
-
if @client
|
|
50
|
-
yield @client
|
|
51
|
-
else
|
|
52
|
-
Async::HTTP::Client.open(Async::HTTP::Endpoint.parse(@host)) { |c| yield c }
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def headers
|
|
57
|
-
[
|
|
58
|
-
["authorization", "Bearer #{@token}"],
|
|
59
|
-
["content-type", CONTENT_TYPE]
|
|
60
|
-
]
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# Streams the bundle off disk — the bundle is never slurped into memory.
|
|
64
|
-
# StreamBody's length drives the content-length on the wire (HTTP/1) and
|
|
65
|
-
# the HTTP/2 pseudo-header, so it must not also be set as a header.
|
|
66
|
-
def file_body(bundle_path)
|
|
67
|
-
StreamBody.new(File.open(bundle_path, "rb"), File.size(bundle_path))
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def parse_response(response)
|
|
71
|
-
status = response.status
|
|
72
|
-
body = response.read || ""
|
|
73
|
-
raise Space::Core::Error, "POST #{IMPORT_PATH} failed (#{status}): #{body[0, 200]}" unless status == 200
|
|
74
|
-
|
|
75
|
-
JSON.parse(body)
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
# A Protocol::HTTP body that lazily reads chunks off an IO, so the HTTP
|
|
79
|
-
# client streams the request body while the bundle stays on disk.
|
|
80
|
-
class StreamBody < Protocol::HTTP::Body::Readable
|
|
81
|
-
CHUNK_SIZE = 64 * 1024
|
|
82
|
-
|
|
83
|
-
def initialize(io, length)
|
|
84
|
-
@io = io
|
|
85
|
-
@length = length
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
attr :length
|
|
89
|
-
|
|
90
|
-
def read
|
|
91
|
-
@io.read(CHUNK_SIZE)
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def close(error = nil)
|
|
95
|
-
@io.close unless @io.closed?
|
|
96
|
-
super
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
# Packs a space into a gzipped tar on disk, streaming — neither the tar
|
|
101
|
-
# nor the space's files are held in memory.
|
|
102
|
-
#
|
|
103
|
-
# Bundle cut (correctness-critical): EXCLUDE repos/, the lanes' worktrees
|
|
104
|
-
# at build/<id>-<lane>/wt, tmp/, node_modules/ anywhere, .pi/, and *.log
|
|
105
|
-
# files. INCLUDE everything else — the importer's read set is space.yaml,
|
|
106
|
-
# .git/ (history carries iteration timestamps), architecture/, notes/,
|
|
107
|
-
# and build/<id>-<lane>/{prompt,report}.md + run.jsonl.
|
|
108
|
-
class Packer
|
|
109
|
-
BLOCK_SIZE = 512
|
|
110
|
-
PRUNED_DIRS = %w[repos tmp .pi node_modules]
|
|
111
|
-
WORKTREE_MATCH = %r{\Abuild/[^/]+/wt\z}
|
|
112
|
-
|
|
113
|
-
Entry = Struct.new(:name, :path, :type)
|
|
114
|
-
|
|
115
|
-
def initialize(root)
|
|
116
|
-
@root = root.to_s
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# Streams the space into a fresh tmpfile and returns its path. The
|
|
120
|
-
# caller owns the tmpfile (unlink it when done).
|
|
121
|
-
def pack
|
|
122
|
-
tmp = Tempfile.create(["architect-push", ".tar.gz"])
|
|
123
|
-
tmp.binmode
|
|
124
|
-
Zlib::GzipWriter.wrap(tmp) do |gz|
|
|
125
|
-
writer = TarWriter.new(gz)
|
|
126
|
-
walk { |entry| add(writer, entry) }
|
|
127
|
-
writer.finish
|
|
128
|
-
end
|
|
129
|
-
tmp.close
|
|
130
|
-
tmp.path
|
|
131
|
-
rescue
|
|
132
|
-
tmp.close
|
|
133
|
-
File.unlink(tmp.path) rescue nil
|
|
134
|
-
raise
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
private
|
|
138
|
-
|
|
139
|
-
def add(writer, entry)
|
|
140
|
-
if entry.type == :dir
|
|
141
|
-
writer.write_dir(entry.name, mtime: File.mtime(entry.path))
|
|
142
|
-
else
|
|
143
|
-
writer.write_file(entry.path, entry.name,
|
|
144
|
-
mode: File.stat(entry.path).mode & 0o7777, mtime: File.mtime(entry.path))
|
|
145
|
-
end
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
def walk(&)
|
|
149
|
-
walk_dir("", &)
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
def walk_dir(rel, &)
|
|
153
|
-
dir = rel.empty? ? @root : File.join(@root, rel)
|
|
154
|
-
Dir.children(dir).sort.each do |name| # paths:exempt — the dotfile-inclusive walk is deliberate (.git must ship); pruned subtrees (repos/, worktrees) are skipped before descending, so their bulk is never enumerated and the walk streams
|
|
155
|
-
child = rel.empty? ? name : "#{rel}/#{name}"
|
|
156
|
-
abs = File.join(@root, child)
|
|
157
|
-
stat = File.lstat(abs)
|
|
158
|
-
|
|
159
|
-
if stat.directory?
|
|
160
|
-
next if pruned?(child)
|
|
161
|
-
|
|
162
|
-
yield Entry.new("#{child}/", abs, :dir)
|
|
163
|
-
walk_dir(child, &)
|
|
164
|
-
elsif stat.file?
|
|
165
|
-
next if child.end_with?(".log")
|
|
166
|
-
|
|
167
|
-
yield Entry.new(child, abs, :file)
|
|
168
|
-
end
|
|
169
|
-
# Symlinks and other oddities are skipped: the bundle carries regular
|
|
170
|
-
# files and directories only, so it stays traversal-proof.
|
|
171
|
-
end
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
def pruned?(rel)
|
|
175
|
-
PRUNED_DIRS.include?(File.basename(rel)) || WORKTREE_MATCH.match?(rel)
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
# Minimal ustar writer — stdlib only, the Gemfile carries no tar gem.
|
|
179
|
-
# Regular files and directories; file contents stream 512-byte-aligned.
|
|
180
|
-
# Names longer than 100 bytes split across the ustar prefix field.
|
|
181
|
-
class TarWriter
|
|
182
|
-
BLOCK_SIZE = 512
|
|
183
|
-
TYPE_FILE = "0"
|
|
184
|
-
TYPE_DIR = "5"
|
|
185
|
-
|
|
186
|
-
def initialize(io)
|
|
187
|
-
@io = io
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
def write_dir(name, mtime:)
|
|
191
|
-
write_header(name, mode: 0o755, mtime: mtime, size: 0, type: TYPE_DIR)
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
def write_file(path, name, mode:, mtime:)
|
|
195
|
-
size = File.size(path)
|
|
196
|
-
write_header(name, mode: mode, mtime: mtime, size: size, type: TYPE_FILE)
|
|
197
|
-
IO.copy_stream(path, @io)
|
|
198
|
-
pad = (BLOCK_SIZE - (size % BLOCK_SIZE)) % BLOCK_SIZE
|
|
199
|
-
@io.write("\0" * pad) unless pad.zero?
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
# Two zero blocks terminate the archive.
|
|
203
|
-
def finish
|
|
204
|
-
@io.write("\0" * (BLOCK_SIZE * 2))
|
|
205
|
-
end
|
|
206
|
-
|
|
207
|
-
private
|
|
208
|
-
|
|
209
|
-
def write_header(name, mode:, mtime:, size:, type:)
|
|
210
|
-
name = name.b
|
|
211
|
-
split = split_name(name)
|
|
212
|
-
if split
|
|
213
|
-
base, prefix = split
|
|
214
|
-
else
|
|
215
|
-
# Not representable via the ustar prefix field — fall back to a
|
|
216
|
-
# GNU 'L' long-name entry, which every mainstream reader honors.
|
|
217
|
-
write_long_name(name)
|
|
218
|
-
base, prefix = name[0, 100], nil
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
header = String.new(encoding: Encoding::BINARY)
|
|
222
|
-
header << base.ljust(100, "\0")
|
|
223
|
-
header << octal(mode, 8)
|
|
224
|
-
header << octal(0, 8) # uid — the bundle is anonymous
|
|
225
|
-
header << octal(0, 8) # gid
|
|
226
|
-
header << octal(size, 12)
|
|
227
|
-
header << octal(mtime.to_i, 12)
|
|
228
|
-
header << " " * 8 # checksum placeholder
|
|
229
|
-
header << type
|
|
230
|
-
header << "\0" * 100 # linkname
|
|
231
|
-
header << "ustar\0" # magic
|
|
232
|
-
header << "00" # version
|
|
233
|
-
header << "\0" * 32 # uname
|
|
234
|
-
header << "\0" * 32 # gname
|
|
235
|
-
header << "\0" * 8 # devmajor
|
|
236
|
-
header << "\0" * 8 # devminor
|
|
237
|
-
header << (prefix || "").ljust(155, "\0")
|
|
238
|
-
header << "\0" * (BLOCK_SIZE - header.bytesize)
|
|
239
|
-
raise "tar header overflow: #{name}" if header.bytesize != BLOCK_SIZE
|
|
240
|
-
|
|
241
|
-
header[148, 8] = format("%06o\0 ", header.sum)
|
|
242
|
-
@io.write(header)
|
|
243
|
-
end
|
|
244
|
-
|
|
245
|
-
# GNU 'L' long-name entry: the full name NUL-terminated as the entry
|
|
246
|
-
# body, then the real header carrying the name's first 100 bytes.
|
|
247
|
-
def write_long_name(name)
|
|
248
|
-
content = "#{name}\0"
|
|
249
|
-
write_header("././@LongLink", mode: 0o644, mtime: Time.now, size: content.bytesize, type: "L")
|
|
250
|
-
@io.write(content)
|
|
251
|
-
pad = (BLOCK_SIZE - (content.bytesize % BLOCK_SIZE)) % BLOCK_SIZE
|
|
252
|
-
@io.write("\0" * pad) unless pad.zero?
|
|
253
|
-
end
|
|
254
|
-
|
|
255
|
-
# [base, prefix] split across the ustar name (100) and prefix (155)
|
|
256
|
-
# fields at a "/" boundary, or nil when the name cannot be split and
|
|
257
|
-
# needs a GNU long-name entry.
|
|
258
|
-
def split_name(name)
|
|
259
|
-
return [name, nil] if name.bytesize <= 100
|
|
260
|
-
|
|
261
|
-
min_cut = [name.bytesize - 101, 0].max # the name field must fit in 100 bytes
|
|
262
|
-
cut = name.index("/", min_cut)
|
|
263
|
-
return nil if cut.nil? || cut > 155
|
|
264
|
-
|
|
265
|
-
[name[(cut + 1)..], name[0, cut]]
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
def octal(value, width)
|
|
269
|
-
format("%0#{width - 1}o\0", value)
|
|
270
|
-
end
|
|
271
|
-
end
|
|
272
|
-
end
|
|
273
|
-
end
|
|
274
|
-
end
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: architect-plan
|
|
3
|
-
description: "Plan work for the architect loop and launch it as an autonomous coordinator run (/loop). Use when the user's intention is to START a loop: planning iterations, writing the plan doc (notes/plan-*.md), pinning role routing, launching the coordinator, and watching it run. For general architecture work without launching a loop (arbitration, judging, landing, iteration surgery, conversation), use /architect instead."
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Architect Plan
|
|
7
|
-
|
|
8
|
-
You are the ARCHITECT planning a coordinator run. The deliverable of this
|
|
9
|
-
skill is exactly one thing: a committed plan doc and a launched coordinator.
|
|
10
|
-
The loop's roles (spec → builders → judge, driven by the coordinator) do
|
|
11
|
-
everything after that — you never absorb a builder wakeup.
|
|
12
|
-
|
|
13
|
-
Read `/architect` (the main skill) first for roles, routing, and the hard
|
|
14
|
-
rules. This skill covers only the start.
|
|
15
|
-
|
|
16
|
-
## Procedure
|
|
17
|
-
|
|
18
|
-
1. **Ground (never skip).**
|
|
19
|
-
- `architect status` — iteration/verdict state; anything in flight?
|
|
20
|
-
- `architect looplog --list` — is a loop already running? Fresh transcripts
|
|
21
|
-
mean a live loop: don't double-start; report and ask.
|
|
22
|
-
- Read `architecture/ARCHITECT.md` (TOC + backlog), `architecture/BRIEF.md`
|
|
23
|
-
(the durable §-contract — cite it as BRIEF §N), the in-flight iteration
|
|
24
|
-
file if any, and the latest `notes/spec-digest-*.md` (the previous spec's
|
|
25
|
-
handoff: verification commands actually used, repo seams, house
|
|
26
|
-
conventions, open unknowns — carry its facts forward or they are lost).
|
|
27
|
-
- Read the target repo's `AGENTS.md`/`CLAUDE.md` → `README.md` → docs;
|
|
28
|
-
confirm the exact verification gate command.
|
|
29
|
-
- Learn the verification gate (test/lint/typecheck/build) from docs or CI
|
|
30
|
-
config — the plan's acceptance direction must name it.
|
|
31
|
-
2. **Scale the work.** Trivial fixes don't need the loop — say so and do it
|
|
32
|
-
inline. One PR-sized iteration = one ENTRY. Multi-iteration plans get
|
|
33
|
-
ordered ENTRYs (the coordinator works them in order, skipping finished
|
|
34
|
-
ones). If the open question is too uncertain to build, make the ENTRY a
|
|
35
|
-
spike (investigate-only; deliverable is a recommendation — see /architect).
|
|
36
|
-
3. **Write the plan doc** to `notes/plan-<UTC timestamp>-<name>.md`:
|
|
37
|
-
|
|
38
|
-
```markdown
|
|
39
|
-
# Plan — <name> (<one line>)
|
|
40
|
-
|
|
41
|
-
Operator plan <date>. <Repos/lanes in scope.>
|
|
42
|
-
|
|
43
|
-
Standing context the spec needs: prior iterations this builds on, routing
|
|
44
|
-
pins, and the post-landing follow-ups (outside-the-loop work the plan
|
|
45
|
-
deliberately excludes — name it so it isn't lost).
|
|
46
|
-
|
|
47
|
-
## ENTRY N — iteration <name> (repo: <repo>, <lane count> lane(s))
|
|
48
|
-
|
|
49
|
-
**Objective.** What to build and why — the reason, not just the ask. Cite
|
|
50
|
-
BRIEF §N. If this ENTRY pins model/thinking for a role, say it here (the
|
|
51
|
-
coordinator passes it in that spawn's model field).
|
|
52
|
-
|
|
53
|
-
**Grounds to read.** Exact paths: research/bug reports, source files being
|
|
54
|
-
ported, seam files, prior verdicts. The spec reads these before writing a
|
|
55
|
-
word.
|
|
56
|
-
|
|
57
|
-
**Acceptance direction (spec refines into frozen AC).** The properties the
|
|
58
|
-
finished iteration must have — at the level the spec can freeze, not
|
|
59
|
-
pre-written gates (freezing is the spec's job).
|
|
60
|
-
|
|
61
|
-
**Constraints.** Repos; files that must not change; out-of-scope;
|
|
62
|
-
house-style expectations; what the lane must NOT delete or move (say so
|
|
63
|
-
when a port is replacing something that lives elsewhere).
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
Each ENTRY must be self-contained — the spec agent receives the entry
|
|
67
|
-
verbatim and nothing else. Put the verification gate command in ENTRY
|
|
68
|
-
constraints or grounds.
|
|
69
|
-
4. **Pin routing if needed.** This plan's models come from `architect.yaml`
|
|
70
|
-
(space root overrides `~/.config/space-architect/architect.yaml` per role).
|
|
71
|
-
A one-plan pin goes in the plan header/ENTRY (the coordinator passes it in
|
|
72
|
-
that spawn's model field); a standing change edits `architect.yaml` —
|
|
73
|
-
routing rides the spawn contract and `/loop` resolves it at launch, so
|
|
74
|
-
there are no generated agent files to hand-sync (the role playbooks are
|
|
75
|
-
static, user-level, installed once by `architect install-agents`).
|
|
76
|
-
5. **Commit the plan doc** to the space — an uncommitted plan is scratch, and
|
|
77
|
-
decision-bearing documents live in `notes/` (not-committed = didn't
|
|
78
|
-
happen).
|
|
79
|
-
6. **Launch.** From an interactive pi session at the space root:
|
|
80
|
-
|
|
81
|
-
```
|
|
82
|
-
/loop --from notes/plan-<ts>-<name>.md
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
`/loop` refuses an empty plan, resolves the routing config (validates
|
|
86
|
-
every pin against the model registry — a pin it doesn't know throws
|
|
87
|
-
loudly, before any spawn — and injects the per-role pins into the
|
|
88
|
-
coordinator's task), and spawns `architect-coordinator` detached — your
|
|
89
|
-
session may end; the coordinator (12h ceiling) works the plan, narrating
|
|
90
|
-
every settled event to `notes/narrative-*.md`. If pi-subagents' RPC is not
|
|
91
|
-
up in the session, /loop says so — spawn the coordinator directly instead
|
|
92
|
-
(agent `architect-coordinator`, the space as cwd, the plan text verbatim as
|
|
93
|
-
the task, the coordinator's model pin from `architect.yaml`, `timeoutMs`
|
|
94
|
-
43200000).
|
|
95
|
-
7. **Report the handoff, then stop.** Tell the human: the plan path, the
|
|
96
|
-
coordinator run id, and how to watch —
|
|
97
|
-
`architect looplog` (live), `--list` (inventory),
|
|
98
|
-
`architect status`, `architect cost`. Escalations route through the
|
|
99
|
-
coordinator to your supervisor seat: rule and the coordinator steers; if
|
|
100
|
-
your session won't stay alive, say so — a coordinator with no listener
|
|
101
|
-
parks the question in `notes/escalation-<ts>.md` and stops via the exception
|
|
102
|
-
path rather than improvising.
|
|
103
|
-
|
|
104
|
-
## Failure modes to check before launching
|
|
105
|
-
|
|
106
|
-
- **Frozen-region edits** — never edit `architecture/` directly; the plan
|
|
107
|
-
steers, the spec writes sections via the CLI.
|
|
108
|
-
- **Stale base** — outside-the-loop commits that landed on a repo's main since
|
|
109
|
-
the last provision are fine (`architect provision` fast-forward-syncs
|
|
110
|
-
`project/<slug>` from the default branch or refuses loudly on divergence);
|
|
111
|
-
a deliberately diverged integration branch must be named in the plan.
|
|
112
|
-
- **Stale gem / extension** — if the plan exercises CLI behavior newer than
|
|
113
|
-
the installed gem, note the rebuild in standing context: a config that
|
|
114
|
-
moved ahead of the installed CLI rejects every command.
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: architect-vocabulary
|
|
3
|
-
description: >
|
|
4
|
-
Load the Architect system's vocabulary and a short "where you are"
|
|
5
|
-
orientation — space, project, iteration, lane, brief, builder, architect,
|
|
6
|
-
gate, freeze, verdict, research, variant set — for when you're standing in a
|
|
7
|
-
space-architect workspace (or working on the skill itself) and need the terms
|
|
8
|
-
understood in conversation but do NOT want to run the loop. Reference only:
|
|
9
|
-
it does not dispatch builders, freeze, judge, or write iteration files.
|
|
10
|
-
Invoke as /architect-vocabulary.
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
# Architect Vocabulary
|
|
14
|
-
|
|
15
|
-
This skill loads **terminology and orientation only**. It is the glossary, not
|
|
16
|
-
the loop.
|
|
17
|
-
|
|
18
|
-
## What this skill is — and isn't
|
|
19
|
-
|
|
20
|
-
- **Is:** the shared vocabulary of `space-architect` plus a quick orientation to
|
|
21
|
-
where things live, so these terms are understood for the rest of the session.
|
|
22
|
-
- **Isn't:** the build loop. Do **not** run `architect new` / `freeze` /
|
|
23
|
-
`dispatch` / `integrate`, do **not** write or edit anything under
|
|
24
|
-
`architecture/`, and do **not** render verdicts on builder work. That is the
|
|
25
|
-
separate **`/architect`** skill — invoke it deliberately when you actually
|
|
26
|
-
want to run the loop. Read-only `architect` commands (`status`, `show`) are
|
|
27
|
-
fine here.
|
|
28
|
-
|
|
29
|
-
## Vocabulary
|
|
30
|
-
|
|
31
|
-
**Roles**
|
|
32
|
-
|
|
33
|
-
- **architect** — the judgment role (a strong reasoning model, or a human, in
|
|
34
|
-
judgment mode): arbitrates disagreements, writes and freezes iteration files,
|
|
35
|
-
calls kill/continue, merges builder output. Never writes implementation code.
|
|
36
|
-
- **builder** — the implementation role: a cheaper model run headless via
|
|
37
|
-
`architect dispatch` (reference harness: `pi -p`), one per lane in its own
|
|
38
|
-
worktree. Reports
|
|
39
|
-
raw evidence; never grades its own work; never edits `architecture/`.
|
|
40
|
-
|
|
41
|
-
**The workspace**
|
|
42
|
-
|
|
43
|
-
- **space** — a task-scoped workspace directory holding repos, notes, and
|
|
44
|
-
artifacts under one root. `architect` finds it by walking up from `$PWD` to the
|
|
45
|
-
nearest `space.yaml`.
|
|
46
|
-
- **space.yaml** — the space's identity file: id, title, status, repos, notes,
|
|
47
|
-
tags, plus the `project:` block (project state — iterations, freeze shas,
|
|
48
|
-
lanes).
|
|
49
|
-
- **project** — an Architect Loop instance living inside a space; spans the
|
|
50
|
-
repos under `repos/`.
|
|
51
|
-
|
|
52
|
-
**The unit of work**
|
|
53
|
-
|
|
54
|
-
- **iteration** — one PR-sized unit of work, captured as a single self-contained
|
|
55
|
-
file `architecture/I<NN>-<name>.md`, grown section by section. Its sections:
|
|
56
|
-
- **Grounds** — *why*: research/brief distilled (optional).
|
|
57
|
-
- **Specification** — *what/how*: the full delegation contract.
|
|
58
|
-
- **Acceptance Criteria** — *proof*: prose conditions (AC1, AC2, …) the
|
|
59
|
-
architect judges against, plus a fenced ` ```gates ` block of runnable checks;
|
|
60
|
-
this is what gets frozen.
|
|
61
|
-
- **Builder Prompt** — the exact lane-prompt(s) dispatched.
|
|
62
|
-
- **Builder Report** — raw evidence, transcribed verbatim from build scratch.
|
|
63
|
-
- **Verdict** — rulings + per-AC PASS/FAIL/INVALID + KILL/CONTINUE.
|
|
64
|
-
- **lane** — a parallel slice of an iteration (1–4 per iteration), each
|
|
65
|
-
declaring a disjoint target repo + file-touch set. Lanes in different repos are
|
|
66
|
-
inherently disjoint; same-repo lanes that overlap files run as one. Each runs
|
|
67
|
-
in its own worktree under `build/<id>-<lane>/`.
|
|
68
|
-
- **worktree** — the isolated git worktree a lane builds in, off the target
|
|
69
|
-
repo's base commit, so lanes never collide.
|
|
70
|
-
- **dispatch** — launching a fresh headless builder for a lane (`architect
|
|
71
|
-
dispatch <iteration> <lane>`), streaming output to `build/<id>-<lane>/run.jsonl`.
|
|
72
|
-
|
|
73
|
-
**Contracts and checkpoints**
|
|
74
|
-
|
|
75
|
-
- **brief** (`architecture/BRIEF.md`) — the durable, §-numbered project contract
|
|
76
|
-
that spans iterations; frozen at the project level and cited as **BRIEF §N**.
|
|
77
|
-
- **ARCHITECT.md** (`architecture/ARCHITECT.md`) — the cross-iteration index /
|
|
78
|
-
table of contents and project-wide state.
|
|
79
|
-
- **freeze** ❄️ — committing the frozen region (Grounds / Specification /
|
|
80
|
-
Acceptance Criteria) *before* dispatch (`architect freeze`). Records the
|
|
81
|
-
**freeze_sha** in `space.yaml`; any later change to those sections is an
|
|
82
|
-
automatic iteration FAIL.
|
|
83
|
-
- **gate** — a structured entry in the ` ```gates ` block: `id`, `ac`, `cmd`,
|
|
84
|
-
optional `cwd`, and an `expect` hash (exit_code / stdout_match / threshold).
|
|
85
|
-
`architect gate` runs each gate's `cmd` and streams raw output — it is a runner,
|
|
86
|
-
never a judge. `architect freeze` lints the gates block before committing.
|
|
87
|
-
|
|
88
|
-
**Outcomes**
|
|
89
|
-
|
|
90
|
-
- **verdict** — the architect's ruling on an iteration, written after evidence:
|
|
91
|
-
- per-criterion: **PASS** / **FAIL** / **INVALID** (INVALID = not measured the
|
|
92
|
-
way the gate specifies).
|
|
93
|
-
- iteration-level: **KILL** / **CONTINUE**.
|
|
94
|
-
- **variant set** — an iteration built as multiple `(harness, model)` lanes over
|
|
95
|
-
one frozen spec, judged head-to-head against the same Acceptance Criteria; the
|
|
96
|
-
winner is selected with the human in the loop, not unilaterally.
|
|
97
|
-
|
|
98
|
-
**Research**
|
|
99
|
-
|
|
100
|
-
- **research** — two scales:
|
|
101
|
-
- **discovery scale** (brainstorming, technology selection, state-of-the-art)
|
|
102
|
-
→ the **`/architect-research`** skill: a scout maps the topic, the
|
|
103
|
-
orchestrator designs parallel researcher **lanes**, claims are verified
|
|
104
|
-
against sources, and the synthesis distills into a brief §section or an
|
|
105
|
-
iteration's Grounds.
|
|
106
|
-
- **iteration scale** → an inline fan-out run only when an iteration needs
|
|
107
|
-
facts the repo doesn't already have.
|
|
108
|
-
|
|
109
|
-
**Repos**
|
|
110
|
-
|
|
111
|
-
- **evergreen** / copy-on-write / **`src` engine** — repo provisioning: when an
|
|
112
|
-
up-to-date local copy exists under `src_dir`, `architect` copies it into
|
|
113
|
-
the space (copy-on-write on APFS) instead of cloning over the network. The
|
|
114
|
-
`src` engine (`space-src`) keeps those evergreen checkouts tended.
|
|
115
|
-
|
|
116
|
-
## Where you are
|
|
117
|
-
|
|
118
|
-
A space's directory layout:
|
|
119
|
-
|
|
120
|
-
```text
|
|
121
|
-
space.yaml # identity + project state (the project: block)
|
|
122
|
-
README.md
|
|
123
|
-
repos/ # the repos the project spans
|
|
124
|
-
notes/ # scratch, prompts, logs
|
|
125
|
-
architecture/ # ARCHITECT.md index + I<NN>-<name>.md iteration files (+ BRIEF.md)
|
|
126
|
-
build/ # lane worktrees + scratch: build/<id>-<lane>/
|
|
127
|
-
tmp/ # workspace-local temp — use instead of /tmp
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
Safe **read-only** commands to orient yourself (these don't run the loop):
|
|
131
|
-
|
|
132
|
-
```sh
|
|
133
|
-
architect status # project state: iterations, freeze shas, lanes, verdicts
|
|
134
|
-
space show # the space you're standing in (space-cadet binary)
|
|
135
|
-
space list # all your spaces
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
## Maintenance
|
|
139
|
-
|
|
140
|
-
This glossary is a **self-contained copy** of terms defined canonically in the
|
|
141
|
-
`architect` skill (`SKILL.md`) and the project `README.md`. It is installed as an
|
|
142
|
-
isolated skill, so it can't reference those at runtime — when the vocabulary
|
|
143
|
-
changes there, re-read this file against them and update it to keep the two from
|
|
144
|
-
drifting.
|