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.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +103 -0
  3. data/README.md +248 -155
  4. data/exe/architect +1 -1
  5. data/exe/space +2 -2
  6. data/exe/src +13 -0
  7. data/lib/space_architect/architect_mission.rb +84 -53
  8. data/lib/space_architect/cli/architect.rb +92 -132
  9. data/lib/space_architect/cli/research.rb +94 -0
  10. data/lib/space_architect/cli/space.rb +25 -31
  11. data/lib/space_architect/cli/src.rb +20 -14
  12. data/lib/space_architect/cli.rb +22 -22
  13. data/lib/space_architect/dispatcher.rb +5 -1
  14. data/lib/space_architect/harness.rb +123 -16
  15. data/lib/space_architect/research/mux.rb +127 -0
  16. data/lib/space_architect/research/registry.rb +70 -0
  17. data/lib/space_architect/research/renderer.rb +101 -0
  18. data/lib/space_architect/research/run.rb +7 -0
  19. data/lib/space_architect/research/supervisor.rb +108 -0
  20. data/lib/space_architect/research.rb +13 -0
  21. data/lib/space_architect/run_creator.rb +53 -0
  22. data/lib/space_architect/skill_installer.rb +81 -79
  23. data/lib/space_architect.rb +5 -20
  24. data/lib/{space_architect → space_core}/atomic_write.rb +1 -1
  25. data/lib/space_core/cli/base_command.rb +19 -0
  26. data/lib/space_core/cli/config.rb +49 -0
  27. data/lib/space_core/cli/current.rb +16 -0
  28. data/lib/space_core/cli/help.rb +110 -0
  29. data/lib/space_core/cli/helpers.rb +115 -0
  30. data/lib/space_core/cli/init.rb +29 -0
  31. data/lib/space_core/cli/list.rb +24 -0
  32. data/lib/space_core/cli/new.rb +38 -0
  33. data/lib/space_core/cli/path.rb +16 -0
  34. data/lib/space_core/cli/repeatable_options.rb +75 -0
  35. data/lib/space_core/cli/repo.rb +76 -0
  36. data/lib/space_core/cli/shell.rb +125 -0
  37. data/lib/space_core/cli/show.rb +21 -0
  38. data/lib/space_core/cli/status.rb +33 -0
  39. data/lib/space_core/cli/use.rb +17 -0
  40. data/lib/space_core/cli.rb +171 -0
  41. data/lib/{space_architect → space_core}/config.rb +1 -1
  42. data/lib/{space_architect → space_core}/errors.rb +1 -1
  43. data/lib/{space_architect → space_core}/git_client.rb +1 -1
  44. data/lib/{space_architect → space_core}/mise_client.rb +1 -1
  45. data/lib/{space_architect → space_core}/repo_reference.rb +1 -1
  46. data/lib/{space_architect → space_core}/repo_resolver.rb +1 -1
  47. data/lib/{space_architect → space_core}/shell_integration.rb +1 -1
  48. data/lib/{space_architect → space_core}/slugger.rb +1 -1
  49. data/lib/{space_architect → space_core}/space.rb +1 -1
  50. data/lib/{space_architect → space_core}/space_store.rb +12 -12
  51. data/lib/{space_architect → space_core}/state.rb +1 -1
  52. data/lib/{space_architect → space_core}/terminal.rb +1 -1
  53. data/lib/space_core/version.rb +7 -0
  54. data/lib/{space_architect → space_core}/warnings.rb +1 -1
  55. data/lib/{space_architect → space_core}/xdg.rb +1 -1
  56. data/lib/space_core.rb +24 -0
  57. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/cli/clone.rb +5 -5
  58. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/cli/config.rb +7 -7
  59. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/cli/daemon.rb +46 -30
  60. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/cli/options.rb +1 -1
  61. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/cli/org.rb +9 -9
  62. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/cli/repo.rb +9 -9
  63. data/lib/space_src/cli/shell.rb +122 -0
  64. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/cli/status.rb +7 -7
  65. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/cli/sync.rb +17 -17
  66. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/cli.rb +42 -11
  67. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/cloner.rb +3 -3
  68. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/config/contract.rb +1 -1
  69. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/config/duration.rb +1 -1
  70. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/config/model.rb +1 -1
  71. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/config/store.rb +5 -5
  72. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/forge/client.rb +2 -2
  73. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/forge/github.rb +4 -4
  74. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/launchd/agent.rb +5 -5
  75. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/launchd/plist.rb +3 -3
  76. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/log_rotator.rb +1 -1
  77. data/lib/space_src/migration.rb +43 -0
  78. data/lib/space_src/nav.rb +98 -0
  79. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/paths.rb +2 -2
  80. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/scm/client.rb +1 -1
  81. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/scm/git.rb +4 -4
  82. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/scm/status.rb +1 -1
  83. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/shell.rb +1 -1
  84. data/lib/space_src/shell_integration.rb +321 -0
  85. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/state/lock.rb +1 -1
  86. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/state/store.rb +2 -2
  87. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/sync/engine.rb +12 -12
  88. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/sync/repo_plan.rb +3 -3
  89. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/ui/interactive_reporter.rb +1 -1
  90. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/ui/json_reporter.rb +1 -1
  91. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/ui/mode.rb +1 -1
  92. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/ui/plain_reporter.rb +1 -1
  93. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/ui/reporter.rb +1 -1
  94. data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/version.rb +2 -2
  95. data/lib/space_src.rb +37 -0
  96. data/skill/architect/SKILL.md +2 -2
  97. data/skill/architect/research.md +46 -37
  98. metadata +115 -67
  99. data/lib/space_architect/cli/config.rb +0 -61
  100. data/lib/space_architect/cli/current.rb +0 -22
  101. data/lib/space_architect/cli/helpers.rb +0 -117
  102. data/lib/space_architect/cli/init.rb +0 -35
  103. data/lib/space_architect/cli/list.rb +0 -30
  104. data/lib/space_architect/cli/new.rb +0 -43
  105. data/lib/space_architect/cli/options.rb +0 -12
  106. data/lib/space_architect/cli/path.rb +0 -22
  107. data/lib/space_architect/cli/repo.rb +0 -88
  108. data/lib/space_architect/cli/shell.rb +0 -137
  109. data/lib/space_architect/cli/show.rb +0 -27
  110. data/lib/space_architect/cli/status.rb +0 -39
  111. data/lib/space_architect/cli/use.rb +0 -23
  112. data/lib/space_architect/version.rb +0 -5
  113. data/vendor/repo-tender/lib/space_architect/pristine.rb +0 -44
@@ -1,12 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module SpaceArchitect
3
+ module Space::Architect
4
4
  module CLI
5
5
  module Architect
6
- class Init < Dry::CLI::Command
7
- include GlobalOptions
8
- include Helpers
9
-
6
+ class Init < BaseCommand
10
7
  desc "Scaffold architect mission memory in the current space"
11
8
  argument :space, required: false, desc: "Space identifier (default: $PWD)"
12
9
 
@@ -23,10 +20,7 @@ module SpaceArchitect
23
20
  end
24
21
  end
25
22
 
26
- class New < Dry::CLI::Command
27
- include GlobalOptions
28
- include Helpers
29
-
23
+ class New < BaseCommand
30
24
  desc "Scaffold the next iteration file (architecture/I<NN>-<iteration>.md)"
31
25
  argument :iteration, required: true, desc: "Iteration name (kebab-case)"
32
26
  argument :space, required: false, desc: "Space identifier (default: $PWD)"
@@ -44,10 +38,7 @@ module SpaceArchitect
44
38
  end
45
39
  end
46
40
 
47
- class Status < Dry::CLI::Command
48
- include GlobalOptions
49
- include Helpers
50
-
41
+ class Status < BaseCommand
51
42
  desc "Show architect mission state (read-only)"
52
43
  argument :space, required: false, desc: "Space identifier (default: $PWD)"
53
44
 
@@ -92,10 +83,7 @@ module SpaceArchitect
92
83
  end
93
84
  end
94
85
 
95
- class Freeze < Dry::CLI::Command
96
- include GlobalOptions
97
- include Helpers
98
-
86
+ class Freeze < BaseCommand
99
87
  desc "Freeze gates for an iteration"
100
88
  argument :iteration, required: true, desc: "Iteration name"
101
89
  argument :space, required: false, desc: "Space identifier (default: $PWD)"
@@ -119,10 +107,7 @@ module SpaceArchitect
119
107
  end
120
108
  end
121
109
 
122
- class Verify < Dry::CLI::Command
123
- include GlobalOptions
124
- include Helpers
125
-
110
+ class Verify < BaseCommand
126
111
  desc "Post-flight mechanical checks for an iteration (reports only, no judgment)"
127
112
  argument :iteration, required: true, desc: "Iteration name"
128
113
  argument :space, required: false, desc: "Space identifier (default: $PWD)"
@@ -168,43 +153,54 @@ module SpaceArchitect
168
153
  end
169
154
  end
170
155
 
171
- class Dispatch < Dry::CLI::Command
172
- include GlobalOptions
173
- include Helpers
174
-
156
+ class Dispatch < BaseCommand
175
157
  desc "Dispatch a builder for a lane (streams to build/<id>-<lane>/run.jsonl)"
176
158
  argument :iteration, required: true, desc: "Iteration name"
177
159
  argument :lane, required: true, desc: "Lane name"
178
160
  argument :space, required: false, desc: "Space identifier (default: $PWD)"
179
- option :model, default: nil, desc: "Model to use (default: lane entry or claude-sonnet-4-6)"
180
- option :max_turns, default: "200", desc: "Max turns for the builder"
181
- option :harness, default: nil, desc: "Harness override (claude-code, opencode)"
182
- option :effort, default: nil, desc: "Reasoning effort override (opencode only; sets reasoningEffort in the model config)"
161
+ option :model, default: nil, desc: "Model to use (default: lane entry or claude-sonnet-4-6)"
162
+ option :max_turns, default: "200", desc: "Max turns for the builder"
163
+ option :harness, default: nil, desc: "Harness override (claude-code, opencode)"
164
+ option :effort, default: nil, desc: "Reasoning effort override (opencode only; sets reasoningEffort in the model config)"
165
+ option :detach, type: :boolean, default: false, desc: "Detach the builder process (returns immediately with PID; poll report for completion)"
166
+ option :push_url, default: nil, desc: "HTTP endpoint for streaming push (POST body to this URL)"
167
+ option :push_token, default: nil, desc: "Bearer token for push endpoint authorization"
168
+ option :push_host, default: nil, desc: "Base URL of the ingest server; the CLI creates a run via POST <host>/runs and streams to /runs/<id>/ingest (requires --push-token)"
183
169
 
184
170
  def call(iteration:, lane:, space: nil, model: nil,
185
- max_turns: "200", harness: nil, effort: nil, **opts)
171
+ max_turns: "200", harness: nil, effort: nil, detach: false,
172
+ push_url: nil, push_token: nil, push_host: nil, **opts)
186
173
  setup_terminal(**opts.slice(:color, :colors))
187
174
  handle_errors do
188
175
  render(store.find(space)) do |sp|
189
176
  mission = ArchitectMission.new(space: sp)
190
- kwargs = { max_turns: max_turns.to_i }
191
- kwargs[:model] = model if model
192
- kwargs[:harness] = harness if harness
193
- kwargs[:effort] = effort if effort
177
+ kwargs = { max_turns: max_turns.to_i, detach: detach }
178
+ kwargs[:model] = model if model
179
+ kwargs[:harness] = harness if harness
180
+ kwargs[:effort] = effort if effort
181
+ kwargs[:push_url] = push_url if push_url
182
+ kwargs[:push_token] = push_token if push_token
183
+ kwargs[:push_host] = push_host if push_host
194
184
  res = mission.dispatch(iteration, lane, **kwargs)
195
- terminal.say "Run log: #{terminal.path(res[:run_log])}"
196
- terminal.say "Report: #{terminal.path(res[:report])}"
197
- terminal.say "Builder exited with status #{res[:exit_code]}"
198
- CLI.record_outcome(Outcome.new(exit_code: res[:exit_code]))
185
+ if detach
186
+ terminal.say "PID: #{res[:pid]}"
187
+ terminal.say "Run log: #{terminal.path(res[:run_log])}"
188
+ terminal.say "Report: #{terminal.path(res[:report])}"
189
+ terminal.say "Dispatched detached — poll #{terminal.path(res[:report])} for completion"
190
+ CLI.record_outcome(Outcome.new(exit_code: 0))
191
+ else
192
+ terminal.say "Run log: #{terminal.path(res[:run_log])}"
193
+ terminal.say "Report: #{terminal.path(res[:report])}"
194
+ terminal.say "Ingest URL: #{res[:push_url]}" if res[:push_url]
195
+ terminal.say "Builder exited with status #{res[:exit_code]}"
196
+ CLI.record_outcome(Outcome.new(exit_code: res[:exit_code]))
197
+ end
199
198
  end
200
199
  end
201
200
  end
202
201
  end
203
202
 
204
- class Section < Dry::CLI::Command
205
- include GlobalOptions
206
- include Helpers
207
-
203
+ class Section < BaseCommand
208
204
  desc "Write a section of the iteration file and commit it (one call)"
209
205
  argument :iteration, required: true, desc: "Iteration name"
210
206
  argument :section, required: true, desc: "Section: grounds, specification, prompt, verdict"
@@ -239,14 +235,11 @@ module SpaceArchitect
239
235
  return File.read(from) if from
240
236
  return body if body
241
237
  return $stdin.read if stdin
242
- raise SpaceArchitect::Error, "provide the section body via --from <file>, --body <text>, or --stdin"
238
+ raise Space::Core::Error, "provide the section body via --from <file>, --body <text>, or --stdin"
243
239
  end
244
240
  end
245
241
 
246
- class Evidence < Dry::CLI::Command
247
- include GlobalOptions
248
- include Helpers
249
-
242
+ class Evidence < BaseCommand
250
243
  desc "Transcribe a lane's scratch report VERBATIM into Builder Report and commit"
251
244
  argument :iteration, required: true, desc: "Iteration name"
252
245
  argument :space, required: false, desc: "Space identifier (default: $PWD)"
@@ -267,10 +260,7 @@ module SpaceArchitect
267
260
  end
268
261
  end
269
262
 
270
- class Merge < Dry::CLI::Command
271
- include GlobalOptions
272
- include Helpers
273
-
263
+ class Merge < BaseCommand
274
264
  desc "Integrate ONE judged-passing lane (merges --no-ff; runs no gates, makes no verdict)"
275
265
  argument :iteration, required: true, desc: "Iteration name"
276
266
  argument :lane, required: true, desc: "Lane name (architect-judged passing)"
@@ -292,10 +282,7 @@ module SpaceArchitect
292
282
  end
293
283
  end
294
284
 
295
- class Integrate < Dry::CLI::Command
296
- include GlobalOptions
297
- include Helpers
298
-
285
+ class Integrate < BaseCommand
299
286
  desc "Integrate the architect-supplied set of passing lanes, in order (stops on conflict)"
300
287
  argument :iteration, required: true, desc: "Iteration name"
301
288
  argument :space, required: false, desc: "Space identifier (default: $PWD)"
@@ -319,10 +306,7 @@ module SpaceArchitect
319
306
  end
320
307
  end
321
308
 
322
- class Gate < Dry::CLI::Command
323
- include GlobalOptions
324
- include Helpers
325
-
309
+ class Gate < BaseCommand
326
310
  desc "Run the frozen Acceptance Criteria gate commands and stream raw output (no PASS/FAIL)"
327
311
  argument :iteration, required: true, desc: "Iteration name"
328
312
  argument :lane, required: false, desc: "Run in a lane worktree (default: the integration repo)"
@@ -348,10 +332,7 @@ module SpaceArchitect
348
332
  end
349
333
  end
350
334
 
351
- class InstallSkills < Dry::CLI::Command
352
- include GlobalOptions
353
- include Helpers
354
-
335
+ class InstallSkills < BaseCommand
355
336
  desc "Install bundled skills (architect, architect-research, architect-vocabulary) for a harness"
356
337
  option :provider, default: "claude", desc: "Harness: claude, codex, opencode, pi"
357
338
  option :project, type: :boolean, default: false, desc: "Install to CWD instead of global"
@@ -373,34 +354,8 @@ module SpaceArchitect
373
354
  end
374
355
  end
375
356
 
376
- module Brief
377
- class New < Dry::CLI::Command
378
- include GlobalOptions
379
- include Helpers
380
-
381
- desc "Scaffold the durable mission brief (architecture/BRIEF.md)"
382
- argument :space, required: false, desc: "Space identifier (default: $PWD)"
383
- option :force, type: :boolean, default: false, desc: "Overwrite an existing BRIEF.md"
384
-
385
- def call(space: nil, force: false, **opts)
386
- setup_terminal(**opts.slice(:color, :colors))
387
- handle_errors do
388
- render(store.find(space)) do |sp|
389
- mission = ArchitectMission.new(space: sp)
390
- path = mission.brief_new!(force: force)
391
- terminal.say "Brief ready: #{terminal.path(path)}"
392
- CLI.record_outcome(Outcome.new(exit_code: 0))
393
- end
394
- end
395
- end
396
- end
397
- end
398
-
399
357
  module Worktree
400
- class Add < Dry::CLI::Command
401
- include GlobalOptions
402
- include Helpers
403
-
358
+ class Add < BaseCommand
404
359
  desc "Create a worktree for a lane"
405
360
  argument :repo, required: true, desc: "Repo name (under repos/)"
406
361
  argument :iteration, required: true, desc: "Iteration name"
@@ -427,10 +382,7 @@ module SpaceArchitect
427
382
  end
428
383
  end
429
384
 
430
- class Remove < Dry::CLI::Command
431
- include GlobalOptions
432
- include Helpers
433
-
385
+ class Remove < BaseCommand
434
386
  desc "Remove a lane worktree"
435
387
  argument :iteration, required: true, desc: "Iteration name"
436
388
  argument :lane, required: true, desc: "Lane name"
@@ -448,10 +400,7 @@ module SpaceArchitect
448
400
  end
449
401
  end
450
402
 
451
- class List < Dry::CLI::Command
452
- include GlobalOptions
453
- include Helpers
454
-
403
+ class List < BaseCommand
455
404
  desc "List active architect worktrees"
456
405
 
457
406
  def call(**opts)
@@ -473,10 +422,7 @@ module SpaceArchitect
473
422
  end
474
423
 
475
424
  module Variant
476
- class Add < Dry::CLI::Command
477
- include GlobalOptions
478
- include Helpers
479
-
425
+ class Add < BaseCommand
480
426
  desc "Create a variant set (competing lanes over one frozen spec)"
481
427
  argument :repo, required: true, desc: "Repo name (under repos/)"
482
428
  argument :iteration, required: true, desc: "Iteration name"
@@ -506,10 +452,7 @@ module SpaceArchitect
506
452
  end
507
453
  end
508
454
 
509
- class Promote < Dry::CLI::Command
510
- include GlobalOptions
511
- include Helpers
512
-
455
+ class Promote < BaseCommand
513
456
  desc "Promote one variant of a variant set as the winner"
514
457
  argument :iteration, required: true, desc: "Iteration name"
515
458
  argument :winner, required: true, desc: "Variant lane name to promote (e.g. v02)"
@@ -532,10 +475,7 @@ module SpaceArchitect
532
475
  end
533
476
  end
534
477
 
535
- class Compare < Dry::CLI::Command
536
- include GlobalOptions
537
- include Helpers
538
-
478
+ class Compare < BaseCommand
539
479
  desc "Compare variants of an iteration's variant set (read-only)"
540
480
  argument :iteration, required: true, desc: "Iteration name"
541
481
  argument :space, required: false, desc: "Space identifier (default: $PWD)"
@@ -569,32 +509,52 @@ module SpaceArchitect
569
509
  end
570
510
  end
571
511
  end
512
+
513
+ module Brief
514
+ class New < BaseCommand
515
+ desc "Scaffold the durable mission brief (architecture/BRIEF.md)"
516
+ argument :space, required: false, desc: "Space identifier (default: $PWD)"
517
+ option :force, type: :boolean, default: false, desc: "Overwrite an existing BRIEF.md"
518
+
519
+ def call(space: nil, force: false, **opts)
520
+ setup_terminal(**opts.slice(:color, :colors))
521
+ handle_errors do
522
+ render(store.find(space)) do |sp|
523
+ mission = ArchitectMission.new(space: sp)
524
+ path = mission.brief_new!(force: force)
525
+ terminal.say "Brief ready: #{terminal.path(path)}"
526
+ CLI.record_outcome(Outcome.new(exit_code: 0))
527
+ end
528
+ end
529
+ end
530
+ end
531
+ end
572
532
  end
573
533
  end
574
534
  end
575
535
 
576
- SpaceArchitect::CLI::Registry.register "init", SpaceArchitect::CLI::Architect::Init
577
- SpaceArchitect::CLI::Registry.register "new", SpaceArchitect::CLI::Architect::New
578
- SpaceArchitect::CLI::Registry.register "status", SpaceArchitect::CLI::Architect::Status
579
- SpaceArchitect::CLI::Registry.register "freeze", SpaceArchitect::CLI::Architect::Freeze
580
- SpaceArchitect::CLI::Registry.register "verify", SpaceArchitect::CLI::Architect::Verify
581
- SpaceArchitect::CLI::Registry.register "dispatch", SpaceArchitect::CLI::Architect::Dispatch
582
- SpaceArchitect::CLI::Registry.register "section", SpaceArchitect::CLI::Architect::Section
583
- SpaceArchitect::CLI::Registry.register "evidence", SpaceArchitect::CLI::Architect::Evidence
584
- SpaceArchitect::CLI::Registry.register "merge", SpaceArchitect::CLI::Architect::Merge
585
- SpaceArchitect::CLI::Registry.register "integrate", SpaceArchitect::CLI::Architect::Integrate
586
- SpaceArchitect::CLI::Registry.register "gate", SpaceArchitect::CLI::Architect::Gate
587
- SpaceArchitect::CLI::Registry.register "install-skills", SpaceArchitect::CLI::Architect::InstallSkills
588
- SpaceArchitect::CLI::Registry.register "brief" do |b|
589
- b.register "new", SpaceArchitect::CLI::Architect::Brief::New
536
+ Space::Architect::CLI::Registry.register "init", Space::Architect::CLI::Architect::Init
537
+ Space::Architect::CLI::Registry.register "new", Space::Architect::CLI::Architect::New
538
+ Space::Architect::CLI::Registry.register "status", Space::Architect::CLI::Architect::Status
539
+ Space::Architect::CLI::Registry.register "freeze", Space::Architect::CLI::Architect::Freeze
540
+ Space::Architect::CLI::Registry.register "verify", Space::Architect::CLI::Architect::Verify
541
+ Space::Architect::CLI::Registry.register "dispatch", Space::Architect::CLI::Architect::Dispatch
542
+ Space::Architect::CLI::Registry.register "section", Space::Architect::CLI::Architect::Section
543
+ Space::Architect::CLI::Registry.register "evidence", Space::Architect::CLI::Architect::Evidence
544
+ Space::Architect::CLI::Registry.register "merge", Space::Architect::CLI::Architect::Merge
545
+ Space::Architect::CLI::Registry.register "integrate", Space::Architect::CLI::Architect::Integrate
546
+ Space::Architect::CLI::Registry.register "gate", Space::Architect::CLI::Architect::Gate
547
+ Space::Architect::CLI::Registry.register "install-skills", Space::Architect::CLI::Architect::InstallSkills
548
+ Space::Architect::CLI::Registry.register "brief" do |b|
549
+ b.register "new", Space::Architect::CLI::Architect::Brief::New
590
550
  end
591
- SpaceArchitect::CLI::Registry.register "worktree" do |wt|
592
- wt.register "add", SpaceArchitect::CLI::Architect::Worktree::Add
593
- wt.register "remove", SpaceArchitect::CLI::Architect::Worktree::Remove
594
- wt.register "list", SpaceArchitect::CLI::Architect::Worktree::List
551
+ Space::Architect::CLI::Registry.register "worktree" do |wt|
552
+ wt.register "add", Space::Architect::CLI::Architect::Worktree::Add
553
+ wt.register "remove", Space::Architect::CLI::Architect::Worktree::Remove
554
+ wt.register "list", Space::Architect::CLI::Architect::Worktree::List
595
555
  end
596
- SpaceArchitect::CLI::Registry.register "variant" do |v|
597
- v.register "add", SpaceArchitect::CLI::Architect::Variant::Add
598
- v.register "promote", SpaceArchitect::CLI::Architect::Variant::Promote
599
- v.register "compare", SpaceArchitect::CLI::Architect::Variant::Compare
556
+ Space::Architect::CLI::Registry.register "variant" do |v|
557
+ v.register "add", Space::Architect::CLI::Architect::Variant::Add
558
+ v.register "promote", Space::Architect::CLI::Architect::Variant::Promote
559
+ v.register "compare", Space::Architect::CLI::Architect::Variant::Compare
600
560
  end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Space::Architect
4
+ module CLI
5
+ module Architect
6
+ module Research
7
+ class Dispatch < BaseCommand
8
+ desc "Dispatch detached read-only research lanes (one per prompt file)"
9
+ argument :prompts, required: true,
10
+ desc: "Prompt file(s) to dispatch (space-separated paths)"
11
+ option :model, default: nil, desc: "Model override (default: claude-sonnet-4-6)"
12
+ option :max_turns, default: "40", desc: "Max turns per researcher"
13
+
14
+ def call(prompts:, model: nil, max_turns: "40", **opts)
15
+ setup_terminal(**opts.slice(:color, :colors))
16
+ handle_errors do
17
+ render(store.find(opts[:space])) do |sp|
18
+ paths = Array(prompts)
19
+ supervisor = Space::Architect::Research::Supervisor.new(space: sp)
20
+ kwargs = { max_turns: max_turns.to_i }
21
+ kwargs[:model] = model if model
22
+ runs = supervisor.dispatch(paths, **kwargs)
23
+ runs.each do |run|
24
+ terminal.say "dispatched #{run.id} (pid #{run.pid}) → #{terminal.path(run.run_log_path)}"
25
+ end
26
+ CLI.record_outcome(Outcome.new(exit_code: 0))
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ class Status < BaseCommand
33
+ desc "Show status of dispatched research runs"
34
+ argument :space, required: false, desc: "Space identifier (default: $PWD)"
35
+
36
+ def call(space: nil, **opts)
37
+ setup_terminal(**opts.slice(:color, :colors))
38
+ handle_errors do
39
+ render(store.find(space)) do |sp|
40
+ supervisor = Space::Architect::Research::Supervisor.new(space: sp)
41
+ entries = supervisor.status
42
+ if entries.empty?
43
+ terminal.say "No research runs registered"
44
+ else
45
+ rows = entries.map do |e|
46
+ run = e[:run]
47
+ [run.id, run.pid.to_s, e[:state].to_s, run.model, e[:tail].last.to_s.slice(0, 60)]
48
+ end
49
+ terminal.say terminal.table(%w[ID PID State Model LastLine], rows)
50
+ end
51
+ CLI.record_outcome(Outcome.new(exit_code: 0))
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ class Wait < BaseCommand
58
+ desc "Wait for all dispatched research runs to complete"
59
+ argument :space, required: false, desc: "Space identifier (default: $PWD)"
60
+ option :quiet, type: :boolean, default: false,
61
+ desc: "L0: suppress all output; exit status only"
62
+ option :level, type: :integer, default: 1,
63
+ desc: "Verbosity level: 1=lifecycle 2=+text 3=+tools 4=+io"
64
+ option :thinking, type: :boolean, default: false,
65
+ desc: "Show assistant thinking blocks"
66
+ option :jsonl, type: :boolean, default: false,
67
+ desc: "Emit raw lane-tagged JSONL (mutually exclusive with level/quiet)"
68
+
69
+ def call(space: nil, quiet: false, level: 1, thinking: false, jsonl: false, **opts)
70
+ setup_terminal(**opts.slice(:color, :colors))
71
+ handle_errors do
72
+ render(store.find(space)) do |sp|
73
+ supervisor = Space::Architect::Research::Supervisor.new(space: sp)
74
+ result = supervisor.wait(
75
+ quiet: quiet,
76
+ level: level.to_i,
77
+ thinking: thinking,
78
+ jsonl: jsonl
79
+ )
80
+ CLI.record_outcome(Outcome.new(exit_code: result == :ok ? 0 : 1))
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ Space::Architect::CLI::Registry.register "research" do |r|
91
+ r.register "dispatch", Space::Architect::CLI::Architect::Research::Dispatch
92
+ r.register "status", Space::Architect::CLI::Architect::Research::Status
93
+ r.register "wait", Space::Architect::CLI::Architect::Research::Wait
94
+ end
@@ -1,35 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- SpaceArchitect::CLI::Registry.register "space" do |prefix|
4
- prefix.register "init", SpaceArchitect::CLI::Init
5
- prefix.register "new", SpaceArchitect::CLI::New
6
- prefix.register "list", SpaceArchitect::CLI::List
7
- prefix.register "ls", SpaceArchitect::CLI::List
8
- prefix.register "show", SpaceArchitect::CLI::Show
9
- prefix.register "path", SpaceArchitect::CLI::Path
10
- prefix.register "use", SpaceArchitect::CLI::Use
11
- prefix.register "current", SpaceArchitect::CLI::Current
12
- prefix.register "status", SpaceArchitect::CLI::Status
13
- prefix.register "config" do |c|
14
- c.register "show", SpaceArchitect::CLI::Config::Show
15
- c.register "path", SpaceArchitect::CLI::Config::ConfigPath
16
- c.register "set", SpaceArchitect::CLI::Config::Set
17
- end
18
- prefix.register "repo" do |r|
19
- r.register "add", SpaceArchitect::CLI::Repo::Add
20
- r.register "list", SpaceArchitect::CLI::Repo::RepoList
21
- r.register "ls", SpaceArchitect::CLI::Repo::RepoList
22
- r.register "resolve", SpaceArchitect::CLI::Repo::Resolve
23
- end
24
- prefix.register "repos" do |r|
25
- r.register "add", SpaceArchitect::CLI::Repo::Add
26
- r.register "list", SpaceArchitect::CLI::Repo::RepoList
27
- r.register "ls", SpaceArchitect::CLI::Repo::RepoList
28
- r.register "resolve", SpaceArchitect::CLI::Repo::Resolve
29
- end
30
- prefix.register "shell" do |s|
31
- s.register "init", SpaceArchitect::CLI::Shell::ShellInit
32
- s.register "fish", SpaceArchitect::CLI::Shell::Fish
33
- s.register "complete", SpaceArchitect::CLI::Shell::Complete
3
+ require "space_core/cli"
4
+
5
+ module Space::Architect
6
+ module CLI
7
+ # Exit-code bridge to the Space::Core CLI engine.
8
+ # `architect space <args>` (and `exe/space <args>` via Space::Core::CLI directly)
9
+ # hands the raw remainder to Space::Core's own dry-cli registry and translates
10
+ # Space::Core's recorded Outcome into the host exit code.
11
+ # Space::Core has its own Registry, Outcome, and :space_core_cli_outcome thread-local;
12
+ # this is the seam between the two registries (NOT a re-registration).
13
+ def self.dispatch_space(rest, out = $stdout, err = $stderr)
14
+ if ::Space::Core::CLI::TOP_LEVEL_HELP.include?(rest)
15
+ out.puts Dry::CLI::Usage.call(::Space::Core::CLI::Registry.get([]))
16
+ return 0
17
+ end
18
+
19
+ if ::Space::Core::CLI::VERSION_REQUEST.include?(rest)
20
+ out.puts ::Space::Core::VERSION
21
+ return 0
22
+ end
23
+
24
+ Thread.current[:space_core_cli_outcome] = nil
25
+ Dry::CLI.new(::Space::Core::CLI::Registry).call(arguments: rest, out: out, err: err)
26
+ ::Space::Core::CLI.last_outcome&.exit_code || 0
27
+ end
34
28
  end
35
29
  end
@@ -1,32 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "space_architect/pristine/cli"
3
+ require "space_src/cli"
4
4
 
5
- module SpaceArchitect
5
+ module Space::Architect
6
6
  module CLI
7
- # Exit-code bridge to the vendored Pristine (repo-tender) CLI engine.
8
- # `architect src <args>` hands the raw remainder to Pristine's own dry-cli
9
- # registry and translates Pristine's recorded Outcome into the host exit code.
10
- # Pristine has its own Registry, Outcome, and :repo_tender_cli_* thread-locals;
11
- # this is the seam between the two registries (NOT a re-registration). Pristine's
7
+ # Exit-code bridge to the Space::Src CLI engine.
8
+ # `architect src <args>` hands the raw remainder to Space::Src's own dry-cli
9
+ # registry and translates Space::Src's recorded Outcome into the host exit code.
10
+ # Space::Src has its own Registry, Outcome, and :space_src_cli_* thread-locals;
11
+ # this is the seam between the two registries (NOT a re-registration). Space::Src's
12
12
  # top-level help/version interceptors call Kernel.exit, so we reproduce that
13
13
  # interception here against the injected IO instead of delegating to them.
14
14
  # dry-cli's internal exit on a bare group / unknown command propagates as
15
15
  # SystemExit — same as the host's own bare groups (e.g. `space repo`) — and is
16
16
  # intentionally NOT rescued (accepted behavior change).
17
17
  def self.dispatch_src(rest, out = $stdout, err = $stderr)
18
- if Pristine::CLI::TOP_LEVEL_HELP.include?(rest)
19
- out.puts Dry::CLI::Usage.call(Pristine::CLI::Registry.get([]))
18
+ if ::Space::Src::CLI::TOP_LEVEL_HELP.include?(rest)
19
+ out.puts Dry::CLI::Usage.call(::Space::Src::CLI::Registry.get([]))
20
20
  return 0
21
21
  end
22
- if Pristine::CLI::VERSION_REQUEST.include?(rest)
23
- out.puts SpaceArchitect::Pristine::VERSION
22
+ if ::Space::Src::CLI::VERSION_REQUEST.include?(rest)
23
+ out.puts ::Space::Src::VERSION
24
24
  return 0
25
25
  end
26
26
 
27
- Thread.current[:repo_tender_cli_outcome] = nil
28
- Dry::CLI.new(Pristine::CLI::Registry).call(arguments: rest, out: out, err: err)
29
- Pristine::CLI.last_outcome&.exit_code || 0
27
+ if ::Space::Src::CLI.bare_query?(rest)
28
+ paths = ::Space::Src::CLI.make_paths
29
+ config = ::Space::Src::Config::Store.load(paths.config_file).success
30
+ return ::Space::Src::Nav.dispatch(rest[0], out, err, config.base_dir)
31
+ end
32
+
33
+ Thread.current[:space_src_cli_outcome] = nil
34
+ Dry::CLI.new(::Space::Src::CLI::Registry).call(arguments: rest, out: out, err: err)
35
+ ::Space::Src::CLI.last_outcome&.exit_code || 0
30
36
  end
31
37
  end
32
38
  end
@@ -1,15 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "dry/cli"
4
+ require "pastel"
5
+ require "space_core/cli"
4
6
 
5
- module SpaceArchitect
7
+ module Space::Architect
6
8
  module CLI
7
- Outcome = Data.define(:exit_code, :message) do
8
- def initialize(exit_code:, message: nil) = super
9
- end
9
+ # Delegate the outcome seam to Space::Core::CLI so both architect commands
10
+ # (which call CLI.record_outcome lexically) and the moved Helpers (which call
11
+ # CLI.record_outcome with Space::Core::CLI as lexical root) share one slot.
12
+ Outcome = Space::Core::CLI::Outcome
13
+ Helpers = Space::Core::CLI::Helpers
14
+ BaseCommand = Space::Core::CLI::BaseCommand
10
15
 
11
- def self.record_outcome(o) = (Thread.current[:space_architect_outcome] = o)
12
- def self.last_outcome = Thread.current[:space_architect_outcome]
16
+ def self.record_outcome(o) = Space::Core::CLI.record_outcome(o)
17
+ def self.last_outcome = Space::Core::CLI.last_outcome
13
18
 
14
19
  module Registry
15
20
  extend Dry::CLI::Registry
@@ -19,7 +24,8 @@ module SpaceArchitect
19
24
  VERSION_REQUEST = [["version"], ["--version"]].freeze
20
25
 
21
26
  def self.call(argv, out = $stdout, err = $stderr)
22
- Thread.current[:space_architect_outcome] = nil
27
+ Thread.current[:space_core_cli_outcome] = nil
28
+ Space::Core::CLI.help_pastel = Pastel.new(enabled: Space::Core::CLI.help_colors?(argv, out, err))
23
29
 
24
30
  if TOP_LEVEL_HELP.include?(argv)
25
31
  out.puts Dry::CLI::Usage.call(Registry.get([]))
@@ -27,7 +33,7 @@ module SpaceArchitect
27
33
  end
28
34
 
29
35
  if VERSION_REQUEST.include?(argv)
30
- out.puts SpaceArchitect::VERSION
36
+ out.puts Space::Core::VERSION
31
37
  return 0
32
38
  end
33
39
 
@@ -35,7 +41,13 @@ module SpaceArchitect
35
41
  return dispatch_src(argv[1..], out, err)
36
42
  end
37
43
 
38
- Dry::CLI.new(Registry).call(arguments: normalize_args(argv), out: out, err: err)
44
+ normalized = normalize_args(argv)
45
+
46
+ if normalized.first == "space"
47
+ return dispatch_space(normalized[1..], out, err)
48
+ end
49
+
50
+ Dry::CLI.new(Registry).call(arguments: normalized, out: out, err: err)
39
51
  last_outcome&.exit_code || 0
40
52
  end
41
53
 
@@ -84,19 +96,7 @@ module SpaceArchitect
84
96
  end
85
97
  end
86
98
 
87
- require_relative "cli/options"
88
- require_relative "cli/helpers"
89
- require_relative "cli/init"
90
- require_relative "cli/new"
91
- require_relative "cli/list"
92
- require_relative "cli/show"
93
- require_relative "cli/path"
94
- require_relative "cli/use"
95
- require_relative "cli/current"
96
- require_relative "cli/status"
97
- require_relative "cli/config"
98
- require_relative "cli/repo"
99
- require_relative "cli/shell"
100
99
  require_relative "cli/architect"
101
100
  require_relative "cli/space"
102
101
  require_relative "cli/src"
102
+ require_relative "cli/research"