space-architect 1.3.0 → 2.0.0.rc1
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 +103 -0
- data/README.md +248 -155
- data/exe/architect +1 -1
- data/exe/space +2 -2
- data/exe/src +13 -0
- data/lib/space_architect/architect_mission.rb +84 -53
- data/lib/space_architect/cli/architect.rb +92 -132
- data/lib/space_architect/cli/research.rb +94 -0
- data/lib/space_architect/cli/space.rb +25 -31
- data/lib/space_architect/cli/src.rb +20 -14
- data/lib/space_architect/cli.rb +22 -22
- data/lib/space_architect/dispatcher.rb +5 -1
- data/lib/space_architect/harness.rb +123 -16
- data/lib/space_architect/research/mux.rb +127 -0
- data/lib/space_architect/research/registry.rb +70 -0
- data/lib/space_architect/research/renderer.rb +101 -0
- data/lib/space_architect/research/run.rb +7 -0
- data/lib/space_architect/research/supervisor.rb +108 -0
- data/lib/space_architect/research.rb +13 -0
- data/lib/space_architect/run_creator.rb +53 -0
- data/lib/space_architect/skill_installer.rb +81 -79
- data/lib/space_architect.rb +5 -20
- data/lib/{space_architect → space_core}/atomic_write.rb +1 -1
- data/lib/space_core/cli/base_command.rb +19 -0
- data/lib/space_core/cli/config.rb +49 -0
- data/lib/space_core/cli/current.rb +16 -0
- data/lib/space_core/cli/help.rb +110 -0
- data/lib/space_core/cli/helpers.rb +115 -0
- data/lib/space_core/cli/init.rb +29 -0
- data/lib/space_core/cli/list.rb +24 -0
- data/lib/space_core/cli/new.rb +38 -0
- data/lib/space_core/cli/path.rb +16 -0
- data/lib/space_core/cli/repeatable_options.rb +75 -0
- data/lib/space_core/cli/repo.rb +76 -0
- data/lib/space_core/cli/shell.rb +125 -0
- data/lib/space_core/cli/show.rb +21 -0
- data/lib/space_core/cli/status.rb +33 -0
- data/lib/space_core/cli/use.rb +17 -0
- data/lib/space_core/cli.rb +171 -0
- data/lib/{space_architect → space_core}/config.rb +1 -1
- data/lib/{space_architect → space_core}/errors.rb +1 -1
- data/lib/{space_architect → space_core}/git_client.rb +1 -1
- data/lib/{space_architect → space_core}/mise_client.rb +1 -1
- data/lib/{space_architect → space_core}/repo_reference.rb +1 -1
- data/lib/{space_architect → space_core}/repo_resolver.rb +1 -1
- data/lib/{space_architect → space_core}/shell_integration.rb +1 -1
- data/lib/{space_architect → space_core}/slugger.rb +1 -1
- data/lib/{space_architect → space_core}/space.rb +1 -1
- data/lib/{space_architect → space_core}/space_store.rb +12 -12
- data/lib/{space_architect → space_core}/state.rb +1 -1
- data/lib/{space_architect → space_core}/terminal.rb +1 -1
- data/lib/space_core/version.rb +7 -0
- data/lib/{space_architect → space_core}/warnings.rb +1 -1
- data/lib/{space_architect → space_core}/xdg.rb +1 -1
- data/lib/space_core.rb +24 -0
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/cli/clone.rb +5 -5
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/cli/config.rb +7 -7
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/cli/daemon.rb +46 -30
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/cli/options.rb +1 -1
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/cli/org.rb +9 -9
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/cli/repo.rb +9 -9
- data/lib/space_src/cli/shell.rb +122 -0
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/cli/status.rb +7 -7
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/cli/sync.rb +17 -17
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/cli.rb +42 -11
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/cloner.rb +3 -3
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/config/contract.rb +1 -1
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/config/duration.rb +1 -1
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/config/model.rb +1 -1
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/config/store.rb +5 -5
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/forge/client.rb +2 -2
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/forge/github.rb +4 -4
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/launchd/agent.rb +5 -5
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/launchd/plist.rb +3 -3
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/log_rotator.rb +1 -1
- data/lib/space_src/migration.rb +43 -0
- data/lib/space_src/nav.rb +98 -0
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/paths.rb +2 -2
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/scm/client.rb +1 -1
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/scm/git.rb +4 -4
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/scm/status.rb +1 -1
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/shell.rb +1 -1
- data/lib/space_src/shell_integration.rb +321 -0
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/state/lock.rb +1 -1
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/state/store.rb +2 -2
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/sync/engine.rb +12 -12
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/sync/repo_plan.rb +3 -3
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/ui/interactive_reporter.rb +1 -1
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/ui/json_reporter.rb +1 -1
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/ui/mode.rb +1 -1
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/ui/plain_reporter.rb +1 -1
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/ui/reporter.rb +1 -1
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/version.rb +2 -2
- data/lib/space_src.rb +37 -0
- data/skill/architect/SKILL.md +2 -2
- data/skill/architect/research.md +46 -37
- metadata +115 -67
- data/lib/space_architect/cli/config.rb +0 -61
- data/lib/space_architect/cli/current.rb +0 -22
- data/lib/space_architect/cli/helpers.rb +0 -117
- data/lib/space_architect/cli/init.rb +0 -35
- data/lib/space_architect/cli/list.rb +0 -30
- data/lib/space_architect/cli/new.rb +0 -43
- data/lib/space_architect/cli/options.rb +0 -12
- data/lib/space_architect/cli/path.rb +0 -22
- data/lib/space_architect/cli/repo.rb +0 -88
- data/lib/space_architect/cli/shell.rb +0 -137
- data/lib/space_architect/cli/show.rb +0 -27
- data/lib/space_architect/cli/status.rb +0 -39
- data/lib/space_architect/cli/use.rb +0 -23
- data/lib/space_architect/version.rb +0 -5
- data/vendor/repo-tender/lib/space_architect/pristine.rb +0 -44
data/exe/space
CHANGED
data/exe/src
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
source_gemfile = File.expand_path("../Gemfile", __dir__)
|
|
5
|
+
if File.exist?(source_gemfile)
|
|
6
|
+
ENV["BUNDLE_GEMFILE"] ||= source_gemfile
|
|
7
|
+
require "bundler/setup"
|
|
8
|
+
$LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
require "space_src"
|
|
12
|
+
|
|
13
|
+
Space::Src::CLI.run(ARGV, $stdout, $stderr)
|
|
@@ -6,7 +6,7 @@ require "open3"
|
|
|
6
6
|
require "fileutils"
|
|
7
7
|
require "pathname"
|
|
8
8
|
|
|
9
|
-
module
|
|
9
|
+
module Space::Architect
|
|
10
10
|
# Manages an architect-loop mission inside a space: one self-contained file per
|
|
11
11
|
# iteration at architecture/I<NN>-<iteration>.md (Grounds / Specification / Acceptance Criteria / Builder
|
|
12
12
|
# Prompt / Builder Report / Verdict), grown one commit per section. The freeze
|
|
@@ -44,7 +44,7 @@ module SpaceArchitect
|
|
|
44
44
|
def init!
|
|
45
45
|
handoff_path = space.path.join("architecture", "ARCHITECT.md")
|
|
46
46
|
if handoff_path.exist?
|
|
47
|
-
raise Error, "architecture/ARCHITECT.md already exists — remove it first or edit it directly (idempotent guard)"
|
|
47
|
+
raise Space::Core::Error, "architecture/ARCHITECT.md already exists — remove it first or edit it directly (idempotent guard)"
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
FileUtils.mkdir_p(handoff_path.dirname)
|
|
@@ -54,7 +54,7 @@ module SpaceArchitect
|
|
|
54
54
|
b.merge("status" => "active", "current_iteration" => nil, "iterations" => [])
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
-
git_run("-C", space.path.to_s, "add", "architecture/ARCHITECT.md", Space::METADATA_FILE)
|
|
57
|
+
git_run("-C", space.path.to_s, "add", "architecture/ARCHITECT.md", Space::Core::Space::METADATA_FILE)
|
|
58
58
|
git_run("-C", space.path.to_s, "commit", "-m", "Initialize architect mission")
|
|
59
59
|
|
|
60
60
|
handoff_path
|
|
@@ -65,14 +65,14 @@ module SpaceArchitect
|
|
|
65
65
|
block = space.data["architect"] || {}
|
|
66
66
|
iterations = block["iterations"] || []
|
|
67
67
|
if iterations.any? { |s| s["name"] == name }
|
|
68
|
-
raise Error, "iteration '#{name}' already exists in space.yaml"
|
|
68
|
+
raise Space::Core::Error, "iteration '#{name}' already exists in space.yaml"
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
ordinal = (iterations.map { |s| s["ordinal"] || 0 }.max || 0) + 1
|
|
72
72
|
nn = format("%02d", ordinal)
|
|
73
73
|
rel = "architecture/I#{nn}-#{name}.md"
|
|
74
74
|
path = space.path.join(rel)
|
|
75
|
-
raise Error, "#{rel} already exists" if path.exist?
|
|
75
|
+
raise Space::Core::Error, "#{rel} already exists" if path.exist?
|
|
76
76
|
|
|
77
77
|
FileUtils.mkdir_p(path.dirname)
|
|
78
78
|
path.write(render_iteration(nn, name))
|
|
@@ -88,7 +88,7 @@ module SpaceArchitect
|
|
|
88
88
|
b
|
|
89
89
|
end
|
|
90
90
|
|
|
91
|
-
git_run("-C", space.path.to_s, "add", rel, Space::METADATA_FILE)
|
|
91
|
+
git_run("-C", space.path.to_s, "add", rel, Space::Core::Space::METADATA_FILE)
|
|
92
92
|
git_run("-C", space.path.to_s, "commit", "-m", "I#{nn}: scaffold #{name}")
|
|
93
93
|
|
|
94
94
|
path
|
|
@@ -114,15 +114,15 @@ module SpaceArchitect
|
|
|
114
114
|
entry = slice_entry(iteration)
|
|
115
115
|
rel = entry["file"]
|
|
116
116
|
path = space.path.join(rel)
|
|
117
|
-
raise Error, "#{rel} does not exist — run `architect new #{iteration}` first" unless path.exist?
|
|
117
|
+
raise Space::Core::Error, "#{rel} does not exist — run `architect new #{iteration}` first" unless path.exist?
|
|
118
118
|
unless path.read.match?(/^## Acceptance Criteria/)
|
|
119
|
-
raise Error, "#{rel} has no '## Acceptance Criteria' section — write the Acceptance Criteria before freezing"
|
|
119
|
+
raise Space::Core::Error, "#{rel} has no '## Acceptance Criteria' section — write the Acceptance Criteria before freezing"
|
|
120
120
|
end
|
|
121
121
|
|
|
122
122
|
if entry["freeze_sha"]
|
|
123
123
|
sha = entry["freeze_sha"]
|
|
124
124
|
if frozen_region_changed?(sha, rel)
|
|
125
|
-
raise Error,
|
|
125
|
+
raise Space::Core::Error,
|
|
126
126
|
"Frozen sections of #{rel} changed since freeze #{sha[0, 8]} — " \
|
|
127
127
|
"refusing to re-freeze. Restore them to their frozen state or use a new iteration."
|
|
128
128
|
end
|
|
@@ -159,7 +159,7 @@ module SpaceArchitect
|
|
|
159
159
|
def brief_new!(force: false)
|
|
160
160
|
brief_path = space.path.join("architecture", "BRIEF.md")
|
|
161
161
|
if brief_path.exist? && !force
|
|
162
|
-
raise Error, "architecture/BRIEF.md already exists — edit it directly (idempotent guard), or pass --force to overwrite"
|
|
162
|
+
raise Space::Core::Error, "architecture/BRIEF.md already exists — edit it directly (idempotent guard), or pass --force to overwrite"
|
|
163
163
|
end
|
|
164
164
|
|
|
165
165
|
FileUtils.mkdir_p(brief_path.dirname)
|
|
@@ -176,7 +176,7 @@ module SpaceArchitect
|
|
|
176
176
|
def write_section!(iteration, section, body:, append: false, lane: nil)
|
|
177
177
|
spec = SECTIONS[section]
|
|
178
178
|
unless spec
|
|
179
|
-
raise Error,
|
|
179
|
+
raise Space::Core::Error,
|
|
180
180
|
"Unknown section '#{section}' — one of: #{SECTIONS.keys.join(', ')}. " \
|
|
181
181
|
"(Acceptance Criteria is set by `architect freeze`; Builder Report by `architect evidence`.)"
|
|
182
182
|
end
|
|
@@ -184,10 +184,10 @@ module SpaceArchitect
|
|
|
184
184
|
entry = slice_entry(iteration)
|
|
185
185
|
rel = entry["file"]
|
|
186
186
|
path = space.path.join(rel)
|
|
187
|
-
raise Error, "#{rel} does not exist — run `architect new #{iteration}` first" unless path.exist?
|
|
187
|
+
raise Space::Core::Error, "#{rel} does not exist — run `architect new #{iteration}` first" unless path.exist?
|
|
188
188
|
|
|
189
189
|
if spec[:frozen] && entry["freeze_sha"]
|
|
190
|
-
raise Error,
|
|
190
|
+
raise Space::Core::Error,
|
|
191
191
|
"#{spec[:heading]} is frozen for #{iteration} (freeze #{entry["freeze_sha"][0, 8]}) — " \
|
|
192
192
|
"frozen sections are read-only after the freeze commit. Open a new iteration to change the contract."
|
|
193
193
|
end
|
|
@@ -211,13 +211,13 @@ module SpaceArchitect
|
|
|
211
211
|
entry = slice_entry(iteration)
|
|
212
212
|
rel = entry["file"]
|
|
213
213
|
path = space.path.join(rel)
|
|
214
|
-
raise Error, "#{rel} does not exist — run `architect new #{iteration}` first" unless path.exist?
|
|
214
|
+
raise Space::Core::Error, "#{rel} does not exist — run `architect new #{iteration}` first" unless path.exist?
|
|
215
215
|
|
|
216
216
|
id = iteration_id(entry)
|
|
217
217
|
report = space.path.join("build", lane ? "#{id}-#{lane}" : id, "report.md")
|
|
218
|
-
raise Error, "builder report not found: #{report}" unless report.exist?
|
|
218
|
+
raise Space::Core::Error, "builder report not found: #{report}" unless report.exist?
|
|
219
219
|
raw = report.read
|
|
220
|
-
raise Error, "builder report is empty: #{report}" if raw.strip.empty?
|
|
220
|
+
raise Space::Core::Error, "builder report is empty: #{report}" if raw.strip.empty?
|
|
221
221
|
|
|
222
222
|
block = lane ? "### #{lane}\n\n#{raw.rstrip}" : raw.rstrip
|
|
223
223
|
path.write(replace_section_body(path.read, "## Builder Report", block, append: !lane.nil?))
|
|
@@ -240,7 +240,7 @@ module SpaceArchitect
|
|
|
240
240
|
text =
|
|
241
241
|
if ref
|
|
242
242
|
out, _, st = git_capture("-C", space.path.to_s, "show", "#{ref}:#{rel}")
|
|
243
|
-
raise Error, "could not read #{rel} at #{ref}" unless st.success?
|
|
243
|
+
raise Space::Core::Error, "could not read #{rel} at #{ref}" unless st.success?
|
|
244
244
|
out
|
|
245
245
|
else
|
|
246
246
|
space.path.join(rel).read
|
|
@@ -255,27 +255,27 @@ module SpaceArchitect
|
|
|
255
255
|
def merge_lane!(iteration, lane, message: nil)
|
|
256
256
|
entry = slice_entry(iteration)
|
|
257
257
|
lane_entry = (entry["lanes"] || []).find { |l| l["name"] == lane }
|
|
258
|
-
raise Error, "No lane '#{lane}' recorded for iteration '#{iteration}'" unless lane_entry
|
|
258
|
+
raise Space::Core::Error, "No lane '#{lane}' recorded for iteration '#{iteration}'" unless lane_entry
|
|
259
259
|
|
|
260
260
|
checks = lane_mechanical_checks(entry, lane_entry)
|
|
261
261
|
if checks[:no_builder_commits] == false
|
|
262
|
-
raise Error, "Lane '#{lane}' has builder commits — the worktree is tampered (hard rule 7). Reset and re-dispatch; do not merge."
|
|
262
|
+
raise Space::Core::Error, "Lane '#{lane}' has builder commits — the worktree is tampered (hard rule 7). Reset and re-dispatch; do not merge."
|
|
263
263
|
end
|
|
264
264
|
if checks[:in_bounds] == false
|
|
265
|
-
raise Error, "Lane '#{lane}' wrote outside its declared touch set — out-of-bounds fails the lane. Reset and re-dispatch."
|
|
265
|
+
raise Space::Core::Error, "Lane '#{lane}' wrote outside its declared touch set — out-of-bounds fails the lane. Reset and re-dispatch."
|
|
266
266
|
end
|
|
267
267
|
|
|
268
268
|
repo = lane_entry["repo"]
|
|
269
269
|
repo_path = space.path.join("repos", repo)
|
|
270
270
|
id = iteration_id(entry)
|
|
271
271
|
wt_path = space.path.join(lane_entry["worktree"] || "build/#{id}-#{lane}/wt")
|
|
272
|
-
raise Error, "Worktree directory does not exist: #{wt_path}" unless wt_path.exist?
|
|
272
|
+
raise Space::Core::Error, "Worktree directory does not exist: #{wt_path}" unless wt_path.exist?
|
|
273
273
|
base_sha = lane_entry["base_sha"]
|
|
274
274
|
lane_branch = "lane/#{id}-#{lane}"
|
|
275
275
|
integration_branch = "lane/#{id}"
|
|
276
276
|
|
|
277
277
|
status_out, = git_capture("-C", wt_path.to_s, "status", "--porcelain")
|
|
278
|
-
raise Error, "Lane '#{lane}' worktree has no changes to integrate." if status_out.strip.empty?
|
|
278
|
+
raise Space::Core::Error, "Lane '#{lane}' worktree has no changes to integrate." if status_out.strip.empty?
|
|
279
279
|
|
|
280
280
|
git_run("-C", wt_path.to_s, "add", "-A")
|
|
281
281
|
git_run("-C", wt_path.to_s, "commit", "-m", message || "lane #{lane}: integrate")
|
|
@@ -291,7 +291,7 @@ module SpaceArchitect
|
|
|
291
291
|
unless mst.success?
|
|
292
292
|
conflicts, = git_capture("-C", repo_path.to_s, "diff", "--name-only", "--diff-filter=U")
|
|
293
293
|
git_capture("-C", repo_path.to_s, "merge", "--abort")
|
|
294
|
-
raise Error,
|
|
294
|
+
raise Space::Core::Error,
|
|
295
295
|
"Merge conflict integrating lane '#{lane}' (#{conflicts.split.join(", ")}) — the lane plan was " \
|
|
296
296
|
"not disjoint = a spec defect. Kill the conflicting lane and re-spec; do not hand-resolve. #{merr.strip}"
|
|
297
297
|
end
|
|
@@ -314,14 +314,14 @@ module SpaceArchitect
|
|
|
314
314
|
# Loop merge_lane! over the architect-supplied passing set, in order. Stops on the
|
|
315
315
|
# first conflict (a disjointness defect). Never decides which lanes pass.
|
|
316
316
|
def integrate!(iteration, lanes:, teardown: false)
|
|
317
|
-
raise Error, "No lanes given to integrate" if lanes.nil? || lanes.empty?
|
|
317
|
+
raise Space::Core::Error, "No lanes given to integrate" if lanes.nil? || lanes.empty?
|
|
318
318
|
|
|
319
319
|
merged = []
|
|
320
320
|
lanes.each do |lane|
|
|
321
321
|
merged << merge_lane!(iteration, lane)
|
|
322
|
-
rescue Error => e
|
|
322
|
+
rescue Space::Core::Error => e
|
|
323
323
|
done = merged.map { |m| m[:lane] }.join(", ")
|
|
324
|
-
raise Error, "Integrated #{done.empty? ? "(none)" : done} then stopped at '#{lane}': #{e.message}"
|
|
324
|
+
raise Space::Core::Error, "Integrated #{done.empty? ? "(none)" : done} then stopped at '#{lane}': #{e.message}"
|
|
325
325
|
end
|
|
326
326
|
|
|
327
327
|
if teardown
|
|
@@ -340,26 +340,26 @@ module SpaceArchitect
|
|
|
340
340
|
def run_gates(iteration, lane: nil)
|
|
341
341
|
entry = slice_entry(iteration)
|
|
342
342
|
freeze_sha = entry["freeze_sha"]
|
|
343
|
-
raise Error, "Iteration '#{iteration}' is not frozen — freeze before running gates." unless freeze_sha
|
|
343
|
+
raise Space::Core::Error, "Iteration '#{iteration}' is not frozen — freeze before running gates." unless freeze_sha
|
|
344
344
|
rel = entry["file"]
|
|
345
345
|
|
|
346
346
|
text, _, st = git_capture("-C", space.path.to_s, "show", "#{freeze_sha}:#{rel}")
|
|
347
|
-
raise Error, "could not read frozen #{rel} at #{freeze_sha[0, 8]}" unless st.success?
|
|
347
|
+
raise Space::Core::Error, "could not read frozen #{rel} at #{freeze_sha[0, 8]}" unless st.success?
|
|
348
348
|
commands = acceptance_criteria_commands(text)
|
|
349
|
-
raise Error, "no gate commands found in the frozen Acceptance Criteria of #{rel}" if commands.empty?
|
|
349
|
+
raise Space::Core::Error, "no gate commands found in the frozen Acceptance Criteria of #{rel}" if commands.empty?
|
|
350
350
|
|
|
351
351
|
lanes = entry["lanes"] || []
|
|
352
352
|
dir =
|
|
353
353
|
if lane
|
|
354
354
|
le = lanes.find { |l| l["name"] == lane }
|
|
355
|
-
raise Error, "No lane '#{lane}' recorded for iteration '#{iteration}'" unless le
|
|
355
|
+
raise Space::Core::Error, "No lane '#{lane}' recorded for iteration '#{iteration}'" unless le
|
|
356
356
|
space.path.join(le["worktree"] || "build/#{iteration_id(entry)}-#{lane}/wt")
|
|
357
357
|
else
|
|
358
358
|
repo = lanes.first&.dig("repo")
|
|
359
|
-
raise Error, "No lane/repo recorded for '#{iteration}' — cannot resolve a directory to run gates in" unless repo
|
|
359
|
+
raise Space::Core::Error, "No lane/repo recorded for '#{iteration}' — cannot resolve a directory to run gates in" unless repo
|
|
360
360
|
space.path.join("repos", repo)
|
|
361
361
|
end
|
|
362
|
-
raise Error, "directory does not exist: #{dir}" unless dir.exist?
|
|
362
|
+
raise Space::Core::Error, "directory does not exist: #{dir}" unless dir.exist?
|
|
363
363
|
|
|
364
364
|
commands.map do |row|
|
|
365
365
|
out, err, status = Open3.capture3(row[:command], chdir: dir.to_s)
|
|
@@ -369,20 +369,20 @@ module SpaceArchitect
|
|
|
369
369
|
|
|
370
370
|
def worktree_add(repo, iteration, lane, base: nil, harness: "claude-code", model: nil, variant: false, effort: nil, touch: nil)
|
|
371
371
|
if harness.to_s == "opencode" && (model.nil? || model == Harness::CLAUDE_DEFAULT_MODEL)
|
|
372
|
-
raise Error,
|
|
372
|
+
raise Space::Core::Error,
|
|
373
373
|
"Pass --model when using --harness opencode " \
|
|
374
374
|
"(#{Harness::CLAUDE_DEFAULT_MODEL} is a Claude model ID, not valid for opencode — " \
|
|
375
375
|
"try e.g. fireworks-ai/accounts/fireworks/models/glm-5p2)"
|
|
376
376
|
end
|
|
377
377
|
if effort && harness.to_s != "opencode"
|
|
378
|
-
raise Error,
|
|
378
|
+
raise Space::Core::Error,
|
|
379
379
|
"effort is opencode-only (sets opencode reasoningEffort) — " \
|
|
380
380
|
"set effort only on opencode lanes (harness: opencode)"
|
|
381
381
|
end
|
|
382
382
|
|
|
383
383
|
entry = slice_entry(iteration)
|
|
384
384
|
repo_path = space.path.join("repos", repo)
|
|
385
|
-
raise Error, "repos/#{repo} does not exist" unless repo_path.exist?
|
|
385
|
+
raise Space::Core::Error, "repos/#{repo} does not exist" unless repo_path.exist?
|
|
386
386
|
|
|
387
387
|
id = iteration_id(entry)
|
|
388
388
|
wt_path = space.path.join("build", "#{id}-#{lane}", "wt")
|
|
@@ -390,7 +390,7 @@ module SpaceArchitect
|
|
|
390
390
|
|
|
391
391
|
base_ref = base || "HEAD"
|
|
392
392
|
base_sha, _, wt_status = git_capture("-C", repo_path.to_s, "rev-parse", base_ref)
|
|
393
|
-
raise Error, "Could not resolve base ref '#{base_ref}' in #{repo}" unless wt_status.success?
|
|
393
|
+
raise Space::Core::Error, "Could not resolve base ref '#{base_ref}' in #{repo}" unless wt_status.success?
|
|
394
394
|
base_sha = base_sha.strip
|
|
395
395
|
|
|
396
396
|
branch = "lane/#{id}-#{lane}"
|
|
@@ -451,10 +451,10 @@ module SpaceArchitect
|
|
|
451
451
|
def variant_promote(iteration, winner)
|
|
452
452
|
entry = slice_entry(iteration)
|
|
453
453
|
variant_lanes = (entry["lanes"] || []).select { |l| l["variant"] }
|
|
454
|
-
raise Error, "Iteration '#{iteration}' has no variant set — nothing to promote" if variant_lanes.empty?
|
|
454
|
+
raise Space::Core::Error, "Iteration '#{iteration}' has no variant set — nothing to promote" if variant_lanes.empty?
|
|
455
455
|
|
|
456
456
|
names = variant_lanes.map { |l| l["name"] }
|
|
457
|
-
raise Error, "Cannot promote '#{winner}' — not a variant lane of iteration '#{iteration}'" unless names.include?(winner)
|
|
457
|
+
raise Space::Core::Error, "Cannot promote '#{winner}' — not a variant lane of iteration '#{iteration}'" unless names.include?(winner)
|
|
458
458
|
discarded_names = names - [winner]
|
|
459
459
|
|
|
460
460
|
update_architect_block do |b|
|
|
@@ -477,7 +477,7 @@ module SpaceArchitect
|
|
|
477
477
|
def variant_compare(iteration)
|
|
478
478
|
entry = slice_entry(iteration)
|
|
479
479
|
variant_lanes = (entry["lanes"] || []).select { |l| l["variant"] }
|
|
480
|
-
raise Error, "Iteration '#{iteration}' has no variant set — nothing to compare" if variant_lanes.empty?
|
|
480
|
+
raise Space::Core::Error, "Iteration '#{iteration}' has no variant set — nothing to compare" if variant_lanes.empty?
|
|
481
481
|
|
|
482
482
|
winner = entry["winner"]
|
|
483
483
|
{
|
|
@@ -500,7 +500,7 @@ module SpaceArchitect
|
|
|
500
500
|
def worktree_remove(iteration, lane)
|
|
501
501
|
entry = slice_entry(iteration)
|
|
502
502
|
lane_entry = (entry["lanes"] || []).find { |l| l["name"] == lane }
|
|
503
|
-
raise Error, "No lane '#{lane}' recorded for iteration '#{iteration}'" unless lane_entry
|
|
503
|
+
raise Space::Core::Error, "No lane '#{lane}' recorded for iteration '#{iteration}'" unless lane_entry
|
|
504
504
|
|
|
505
505
|
repo = lane_entry["repo"]
|
|
506
506
|
repo_path = space.path.join("repos", repo)
|
|
@@ -536,36 +536,67 @@ module SpaceArchitect
|
|
|
536
536
|
end
|
|
537
537
|
|
|
538
538
|
def dispatch(iteration, lane, model: nil, max_turns: 200,
|
|
539
|
-
claude_bin: nil, harness: nil, opencode_bin: nil, effort: nil
|
|
539
|
+
claude_bin: nil, harness: nil, opencode_bin: nil, effort: nil, detach: false,
|
|
540
|
+
push_url: nil, push_token: nil, push_host: nil, run_creator: nil,
|
|
541
|
+
push_client: nil)
|
|
542
|
+
raise Space::Core::Error, "Specify --push-host or --push-url, not both" if push_host && push_url
|
|
543
|
+
raise Space::Core::Error, "--push-host requires --push-token" if push_host && !push_token
|
|
544
|
+
raise Space::Core::Error, "--detach cannot be combined with --push-url or --push-host" \
|
|
545
|
+
if detach && (push_url || push_host)
|
|
546
|
+
|
|
540
547
|
entry = slice_entry(iteration)
|
|
541
548
|
lane_entry = (entry["lanes"] || []).find { |l| l["name"] == lane }
|
|
542
|
-
raise Error, "No lane '#{lane}' recorded for iteration '#{iteration}'" unless lane_entry
|
|
549
|
+
raise Space::Core::Error, "No lane '#{lane}' recorded for iteration '#{iteration}'" unless lane_entry
|
|
543
550
|
|
|
544
551
|
resolved_harness = harness || lane_entry["harness"] || "claude-code"
|
|
545
552
|
resolved_model = model || lane_entry["model"] || Harness::CLAUDE_DEFAULT_MODEL
|
|
546
553
|
resolved_effort = effort || lane_entry["effort"]
|
|
547
554
|
|
|
555
|
+
raise Space::Core::Error, "--push-host is only supported with the claude-code harness" \
|
|
556
|
+
if push_host && resolved_harness != "claude-code"
|
|
557
|
+
|
|
548
558
|
id = iteration_id(entry)
|
|
549
559
|
wt_path = space.path.join(lane_entry["worktree"] || "build/#{id}-#{lane}/wt")
|
|
550
|
-
raise Error, "Worktree directory does not exist: #{wt_path}" unless wt_path.exist?
|
|
560
|
+
raise Space::Core::Error, "Worktree directory does not exist: #{wt_path}" unless wt_path.exist?
|
|
551
561
|
|
|
552
562
|
build_dir = space.path.join("build", "#{id}-#{lane}")
|
|
553
563
|
prompt_path = build_dir.join("prompt.md")
|
|
554
564
|
run_log_path = build_dir.join("run.jsonl")
|
|
555
565
|
report_path = build_dir.join("report.md")
|
|
556
|
-
raise Error, "prompt.md not found: #{prompt_path}" unless prompt_path.exist?
|
|
566
|
+
raise Space::Core::Error, "prompt.md not found: #{prompt_path}" unless prompt_path.exist?
|
|
557
567
|
|
|
558
568
|
bin = resolved_harness == "claude-code" ? claude_bin : opencode_bin
|
|
559
569
|
harness_obj = Harness.for(resolved_harness, model: resolved_model, max_turns: max_turns,
|
|
560
570
|
bin: bin, config_dir: build_dir, effort: resolved_effort)
|
|
561
571
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
572
|
+
if detach
|
|
573
|
+
pid = harness_obj.run_detached(
|
|
574
|
+
prompt_path: prompt_path,
|
|
575
|
+
run_log_path: run_log_path,
|
|
576
|
+
chdir: wt_path
|
|
577
|
+
)
|
|
578
|
+
{ pid: pid, run_log: run_log_path, report: report_path, worktree: wt_path }
|
|
579
|
+
else
|
|
580
|
+
created_run_id = nil
|
|
581
|
+
if push_host
|
|
582
|
+
creator = run_creator || RunCreator.new(push_host, push_token)
|
|
583
|
+
created_run_id = creator.create
|
|
584
|
+
push_url = "#{push_host.chomp('/')}/runs/#{created_run_id}/ingest"
|
|
585
|
+
end
|
|
567
586
|
|
|
568
|
-
|
|
587
|
+
run_kwargs = { prompt_path: prompt_path, run_log_path: run_log_path, chdir: wt_path }
|
|
588
|
+
if resolved_harness == "claude-code"
|
|
589
|
+
run_kwargs[:push_url] = push_url if push_url
|
|
590
|
+
run_kwargs[:push_token] = push_token if push_token
|
|
591
|
+
run_kwargs[:push_client] = push_client if push_client
|
|
592
|
+
end
|
|
593
|
+
exit_code = harness_obj.run(**run_kwargs)
|
|
594
|
+
|
|
595
|
+
result = { exit_code: exit_code, run_log: run_log_path, report: report_path, worktree: wt_path }
|
|
596
|
+
result[:created_run_id] = created_run_id if created_run_id
|
|
597
|
+
result[:push_url] = push_url if push_url
|
|
598
|
+
result
|
|
599
|
+
end
|
|
569
600
|
end
|
|
570
601
|
|
|
571
602
|
private
|
|
@@ -579,7 +610,7 @@ module SpaceArchitect
|
|
|
579
610
|
def slice_entry(iteration)
|
|
580
611
|
block = space.data["architect"] || {}
|
|
581
612
|
entry = (block["iterations"] || []).find { |s| s["name"] == iteration }
|
|
582
|
-
raise Error, "Iteration '#{iteration}' not recorded in space.yaml — run `architect new #{iteration}` first" unless entry
|
|
613
|
+
raise Space::Core::Error, "Iteration '#{iteration}' not recorded in space.yaml — run `architect new #{iteration}` first" unless entry
|
|
583
614
|
entry
|
|
584
615
|
end
|
|
585
616
|
|
|
@@ -637,7 +668,7 @@ module SpaceArchitect
|
|
|
637
668
|
def replace_section_body(text, heading, new_block, append:)
|
|
638
669
|
lines = text.lines
|
|
639
670
|
start = lines.index { |l| l.chomp == heading }
|
|
640
|
-
raise Error, "section heading '#{heading}' not found in iteration file" unless start
|
|
671
|
+
raise Space::Core::Error, "section heading '#{heading}' not found in iteration file" unless start
|
|
641
672
|
|
|
642
673
|
finish = ((start + 1)...lines.length).find { |i| KNOWN_HEADINGS.include?(lines[i].chomp) } || lines.length
|
|
643
674
|
body = lines[(start + 1)...finish].join
|
|
@@ -735,7 +766,7 @@ module SpaceArchitect
|
|
|
735
766
|
out, err, status = Open3.capture3("git", *args)
|
|
736
767
|
return if status.success?
|
|
737
768
|
output = [out, err].map(&:strip).reject(&:empty?).join(" ")
|
|
738
|
-
raise Error, "git #{args.join(' ')} failed: #{output}"
|
|
769
|
+
raise Space::Core::Error, "git #{args.join(' ')} failed: #{output}"
|
|
739
770
|
end
|
|
740
771
|
|
|
741
772
|
def git_capture(*args)
|