space-architect 9.2.1 → 9.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 173cd44e1e27f8fc09d46bb0d18a5c7f26138731d4315bc6db1e7cd2c704e0a4
4
- data.tar.gz: cebbf1f5602efa92a9f3982f31fcc79c202549489531262cfaab3ecc1c25ba13
3
+ metadata.gz: 4fd0baee9b2db69fa1ef96c1b4993f75a8912b5b0898d012c13036a379e32a7f
4
+ data.tar.gz: 582172fa5c61e4a9419409cafdfa8701ca096a2621527141099944f3261c6592
5
5
  SHA512:
6
- metadata.gz: 44430c9d2fb759035df014e4023084424b66edd6dfea696f4e0e0f555ab7d0e3d418410ae8fa117936456fd3b4ea365cf9bf217ffadbb0be184e9ccb6835526a
7
- data.tar.gz: 5abb3a70daaca512e957ed2a577a45904e709be3cd05c6b57ae49f7e58ce9d612b64c33addb6ba191f49777826258e5b481e27c440bacdfd4f4e8d7f4d997dd0
6
+ metadata.gz: 59dd000d01b8fd0b42729d87f7f843f93690b0fbccf927e127426f352fa79aada6078a885b54e3342d3c901500fde93338be3198b77e871df85f7b28ef816f9e
7
+ data.tar.gz: 29f88c1fdec961ed7b53d9d9e4a3a3c156cadac2da157786bea28fca4b20b09cb786ab195d5eedf0877e11c901a2d140d66347bbee3fc52570a4a68225b78284
data/CHANGELOG.md CHANGED
@@ -5,6 +5,62 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [9.3.0] - 2026-10-17
9
+
10
+ ### Removed
11
+
12
+ - **The gem cut (observation-deck entry 2).** Session capture moved to the
13
+ session-sink pi extension (jetpks/pi-extensions), so the gem's entire
14
+ push/streaming surface is gone: `SpacePush`/`Packer` and the `architect
15
+ push` command; dispatch `--push-url/--push-host/--push-token`, `RunCreator`,
16
+ and the harness tee; `ConversationsClient` + `session_sync/**` and the
17
+ sessions CLI that drove them; `JobsClient` (its only consumer was the
18
+ removed server jobs API). looplog, runlog, cost, routing, and the loop
19
+ lifecycle are untouched.
20
+
21
+ ## [9.2.1] - 2026-10-15
22
+
23
+ ### Fixed
24
+
25
+ - dispatch copies the lane prompt into the build dir (`build/<id>-<lane>/`
26
+ `prompt.md`) at dispatch time, so the record is self-contained even if the
27
+ scratch source is removed.
28
+
29
+ ## [9.2.0] - 2026-10-15
30
+
31
+ ### Added
32
+
33
+ - **CLI observability:** `architect looplog` (follow every active loop run
34
+ live; `--list`/`--replay`/`--all`), `architect runlog` (pretty-print or
35
+ stream a run.jsonl), and full gate transcripts written to
36
+ `build/gate/<id>-<ts>.md`.
37
+
38
+ ### Changed
39
+
40
+ - Docs re-centered on the coordinator path; role agents installed user-level
41
+ by `architect install-agents`.
42
+
43
+ ## [9.1.1] - 2026-10-14
44
+
45
+ ### Fixed
46
+
47
+ - `architect provision` fast-forward-syncs the integration branch from the
48
+ default branch (or cuts exactly `--base REF`), refusing loudly on
49
+ divergence instead of silently building on a stale base.
50
+
51
+ ## [9.1.0] - 2026-10-13
52
+
53
+ ### Added
54
+
55
+ - **The narrator role:** a sixth loop role (cheap model, one file per settled
56
+ event) writing `notes/narrative-*.md` — verbatim facts, never a gate.
57
+
58
+ ### Changed
59
+
60
+ - `architect cost` gains human formatting (significant figures, `--precise`,
61
+ color) and a latency turn/generation split.
62
+ - `architect new` flocks the scaffold critical section across processes.
63
+
8
64
  ## [9.0.0] - 2026-10-09
9
65
 
10
66
  ### Changed
data/README.md CHANGED
@@ -12,19 +12,17 @@ task-scoped **spaces**: directories that hold repos, notes, and artifacts under
12
12
  one obvious filesystem root. Nothing lives only in a chat window — every
13
13
  decision, spec, and verdict is committed to the space repo.
14
14
 
15
- It installs **one executable**, `architect`. The spaces substrate and the
16
- evergreen-checkout engine are two sibling gems:
15
+ It installs **one executable**, `architect`. The spaces substrate is a sibling
16
+ gem:
17
17
 
18
18
  | Gem | Dependency | What it provides |
19
19
  |-----|------------|------------------|
20
20
  | **space-cadet** | hard | The spaces substrate (`Space::Core`): create, manage & containerize task-scoped workspaces — the `space` binary — plus the copy-on-write evergreen-checkout substrate repos provision from |
21
- | **repo-tender** | soft | Evergreen clone tending — the `repo-tender` binary (with the deprecated `src` shim) — and the launchd agent the `architect sessions agent` commands drive. Install with `gem install repo-tender` when you want the session-sync rail — everything else works without it |
22
21
 
23
22
  ## Installation 📦
24
23
 
25
24
  ```bash
26
25
  gem install space-architect
27
- gem install repo-tender # optional: enables the sessions launchd agent
28
26
  ```
29
27
 
30
28
  The gem installs one executable: `architect`. 🎀 (Or add
@@ -956,13 +956,7 @@ module Space::Architect
956
956
 
957
957
  def dispatch(iteration, lane, model: nil, max_turns: 200, bin: nil, harness: nil, effort: nil,
958
958
  force: false, quiet: false,
959
- detach: false, push_url: nil, push_token: nil, push_host: nil, run_creator: nil,
960
- push_client: nil, timeout: nil, prompt: nil, now: Time.now)
961
- raise Space::Core::Error, "Specify --push-host or --push-url, not both" if push_host && push_url
962
- raise Space::Core::Error, "--push-host requires --push-token" if push_host && !push_token
963
- raise Space::Core::Error, "--detach cannot be combined with --push-url or --push-host" \
964
- if detach && (push_url || push_host)
965
-
959
+ detach: false, timeout: nil, prompt: nil, now: Time.now)
966
960
  err = quiet ? File.open(File::NULL, "w") : $stderr
967
961
 
968
962
  entry = slice_entry(iteration)
@@ -1026,26 +1020,14 @@ module Space::Architect
1026
1020
  result[:prompt_copied] = prompt_path if prompt
1027
1021
  result
1028
1022
  else
1029
- created_run_id = nil
1030
- if push_host
1031
- creator = run_creator || RunCreator.new(push_host, push_token)
1032
- created_run_id = creator.create
1033
- push_url = "#{push_host.chomp('/')}/runs/#{created_run_id}/ingest"
1034
- end
1035
-
1036
1023
  run_kwargs = { prompt_path: prompt_path, run_log_path: run_log_path, chdir: wt_path }
1037
1024
  run_kwargs[:timeout] = timeout if timeout
1038
- run_kwargs[:push_url] = push_url if push_url
1039
- run_kwargs[:push_token] = push_token if push_token
1040
- run_kwargs[:push_client] = push_client if push_client
1041
- run_kwargs[:err] = err if quiet
1025
+ run_kwargs[:err] = err if quiet
1042
1026
  exit_code = harness_obj.run(**run_kwargs)
1043
1027
 
1044
1028
  result = { exit_code: exit_code, run_log: run_log_path, report: report_path, worktree: wt_path }
1045
1029
  result[:prompt_copied] = prompt_path if prompt
1046
1030
  result[:timed_out] = true if exit_code == Harness::PiHarness::TIMEOUT_EXIT_CODE
1047
- result[:created_run_id] = created_run_id if created_run_id
1048
- result[:push_url] = push_url if push_url
1049
1031
  result
1050
1032
  end
1051
1033
  end
@@ -2,14 +2,10 @@
2
2
 
3
3
  require "json"
4
4
  require "fileutils"
5
- require_relative "../jobs_client"
6
- require_relative "../conversations_client"
7
5
  require_relative "../loop_cost"
8
- require_relative "../session_sync"
9
6
  require_relative "../runlog"
10
7
  require_relative "../looplog"
11
8
  require_relative "../extension_installer"
12
- require_relative "../space_push"
13
9
 
14
10
  module Space::Architect
15
11
  module CLI
@@ -215,41 +211,6 @@ module Space::Architect
215
211
  end
216
212
  end
217
213
 
218
- class Push < BaseCommand
219
- desc "Pack the current space and push it to a space-server for import"
220
- phase 53, "Project"
221
- argument :space, required: false, desc: "Space identifier (default: $PWD)"
222
- option :host, required: true, desc: "Base URL of the space-server"
223
- option :token, required: true, desc: "Bearer token for authorization"
224
-
225
- def call(space: nil, host: nil, token: nil, **opts)
226
- setup_terminal(**opts.slice(:color, :colors))
227
- handle_errors do
228
- raise Space::Core::Error, "--host is required" unless host
229
- raise Space::Core::Error, "--token is required" unless token
230
-
231
- render(store.find(space)) do |sp|
232
- bundle = SpacePush::Packer.new(sp.path).pack
233
- begin
234
- terminal.say "Packed #{sp.id}: #{format_bytes(File.size(bundle))}"
235
- summary = SpacePush.new(host, token).push(bundle)
236
- terminal.say "Imported: #{summary['slug']} (id #{summary['id']}) — " \
237
- "#{summary['iterations']} iterations, #{summary['artifacts']} artifacts, #{summary['runs']} runs"
238
- CLI.record_outcome(Outcome.new(exit_code: 0))
239
- ensure
240
- File.delete(bundle)
241
- end
242
- end
243
- end
244
- end
245
-
246
- private
247
-
248
- def format_bytes(size)
249
- format("%.1f MiB", size.to_f / (1024 * 1024))
250
- end
251
- end
252
-
253
214
  class Sync < BaseCommand
254
215
  desc "Sync tracked repo clones with their remotes (fast-forward only, no rebase/reset)"
255
216
  phase 53, "Project"
@@ -560,17 +521,14 @@ module Space::Architect
560
521
  option :force_effort, default: nil, desc: "Force the literal level onto the --thinking flag, skipping architect's validation — alias for --force-thinking/--force-reasoning (dispatch only; the binary's rejection is final)"
561
522
  option :force_thinking, default: nil, desc: "Force the literal level onto the --thinking flag, skipping architect's validation — alias for --force-effort/--force-reasoning (dispatch only; the binary's rejection is final)"
562
523
  option :force_reasoning, default: nil, desc: "Force the literal level onto the --thinking flag, skipping architect's validation — alias for --force-effort/--force-thinking (dispatch only; the binary's rejection is final)"
563
- option :quiet, type: :boolean, default: false, desc: "Suppress thinking-translation and harness run-time warn lines (liveness/push) on $stderr for this dispatch"
524
+ option :quiet, type: :boolean, default: false, desc: "Suppress thinking-translation and harness run-time warn lines (liveness) on $stderr for this dispatch"
564
525
  option :detach, type: :boolean, default: false, desc: "Detach the builder process (returns immediately with PID; poll report for completion)"
565
526
  option :timeout, default: "14400", desc: "Wall-clock timeout in seconds (0 disables; default 4h); foreground only"
566
- option :push_url, default: nil, desc: "HTTP endpoint for streaming push (POST body to this URL)"
567
- option :push_token, default: nil, desc: "Bearer token for push endpoint authorization"
568
- 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)"
569
527
 
570
528
  def call(iteration:, lane:, space: nil, prompt: nil, model: nil,
571
529
  max_turns: "200", harness: nil, effort: nil, thinking: nil, reasoning: nil,
572
530
  force_effort: nil, force_thinking: nil, force_reasoning: nil, quiet: false, detach: false,
573
- timeout: "14400", push_url: nil, push_token: nil, push_host: nil, **opts)
531
+ timeout: "14400", **opts)
574
532
  setup_terminal(**opts.slice(:color, :colors))
575
533
  handle_errors do
576
534
  level = resolve_thinking_alias(effort: effort, thinking: thinking, reasoning: reasoning)
@@ -588,9 +546,6 @@ module Space::Architect
588
546
  kwargs[:force] = true if forced_level
589
547
  kwargs[:quiet] = true if quiet
590
548
  kwargs[:timeout] = timeout.to_i unless detach
591
- kwargs[:push_url] = push_url if push_url
592
- kwargs[:push_token] = push_token if push_token
593
- kwargs[:push_host] = push_host if push_host
594
549
  res = project.dispatch(iteration, lane, **kwargs)
595
550
  terminal.say "Prompt: #{prompt} → #{terminal.path(res[:prompt_copied])}" if res[:prompt_copied]
596
551
  if detach
@@ -607,7 +562,6 @@ module Space::Architect
607
562
  else
608
563
  terminal.say "Run log: #{terminal.path(res[:run_log])}"
609
564
  terminal.say "Report: #{terminal.path(res[:report])}"
610
- terminal.say "Ingest URL: #{res[:push_url]}" if res[:push_url]
611
565
  terminal.say "Builder exited with status #{res[:exit_code]}"
612
566
  unless res[:report].exist? && !res[:report].read.strip.empty?
613
567
  terminal.say "WARNING: no report at #{terminal.path(res[:report])} — the lane produced no deliverable."
@@ -953,7 +907,7 @@ module Space::Architect
953
907
  # --help, which :boolean would render as `--[no-]json`.
954
908
  option :json, type: :flag, default: false, desc: "Emit the frozen JSON contract instead of tables"
955
909
  option :space, desc: "Space identifier or path (default: nearest ancestor with space.yaml)"
956
- option :sessions_root, desc: "pi sessions root (default: ~/.pi/agent/sessions via SessionSync)"
910
+ option :sessions_root, desc: "pi sessions root (default: ~/.pi/agent/sessions)"
957
911
  option :since, desc: "Only artifacts starting at/after this ISO-8601 timestamp or YYYY-MM-DD date"
958
912
  option :by_iteration, type: :flag, default: false, desc: "Split the cost table into per-iteration windows"
959
913
  option :latency, type: :flag, default: false, desc: "Per-model latency: round wall, turn vs generation tok/s, tool wait share"
@@ -969,7 +923,7 @@ module Space::Architect
969
923
  render(store.find(space)) do |sp|
970
924
  report = LoopCost.report(
971
925
  space: sp,
972
- sessions_root: sessions_root || SessionSync.default_pi_root,
926
+ sessions_root: sessions_root || default_pi_sessions_root,
973
927
  since: since,
974
928
  by_iteration: by_iteration,
975
929
  latency: latency
@@ -982,6 +936,12 @@ module Space::Architect
982
936
 
983
937
  private
984
938
 
939
+ # The pi sessions root Cost reads by default — the same value the cut
940
+ # session-sync rail's default computed.
941
+ def default_pi_sessions_root(env: ENV)
942
+ File.join(Space::Core::XDG.home(env: env), ".pi", "agent", "sessions")
943
+ end
944
+
985
945
  def render_cost_text(report, by_iteration, latency, precise)
986
946
  if latency
987
947
  latency_table(report["latency"], precise)
@@ -1340,235 +1300,6 @@ module Space::Architect
1340
1300
  end
1341
1301
  end
1342
1302
  end
1343
-
1344
- module Jobs
1345
- # dry-cli 1.4.1 only enforces `required: true` on argument, not on
1346
- # option (Dry::CLI::Parser#parse_required_params only ever consults
1347
- # command.required_arguments) — a missing --host/--token would
1348
- # otherwise surface as a raw ArgumentError from the **opts keyword
1349
- # splat, uncaught by handle_errors. Every subcommand below validates
1350
- # explicitly so the failure is a clean Space::Core::Error instead.
1351
- def self.require_credentials!(host, token)
1352
- raise Space::Core::Error, "--host is required" unless host
1353
- raise Space::Core::Error, "--token is required" unless token
1354
- end
1355
-
1356
- class List < BaseCommand
1357
- desc "List jobs for the authenticated user (owner-scoped, newest-first)"
1358
- option :host, required: true, desc: "Base URL of the space-server"
1359
- option :token, required: true, desc: "Bearer token for authorization"
1360
-
1361
- def call(host: nil, token: nil, **opts)
1362
- setup_terminal(**opts.slice(:color, :colors))
1363
- handle_errors do
1364
- Jobs.require_credentials!(host, token)
1365
- jobs = JobsClient.new(host, token).list
1366
- if jobs.empty?
1367
- terminal.say "No jobs"
1368
- else
1369
- rows = jobs.map do |j|
1370
- [j["id"], j["status"], j["run_id"], j["created_at"],
1371
- j["harness"], j["model"], j.dig("provenance", "lane")]
1372
- end
1373
- terminal.say terminal.table(%w[ID Status RunID CreatedAt Harness Model Lane], rows)
1374
- end
1375
- CLI.record_outcome(Outcome.new(exit_code: 0))
1376
- end
1377
- end
1378
- end
1379
-
1380
- class Show < BaseCommand
1381
- desc "Show a job's full JSON"
1382
- argument :id, required: true, desc: "Job id"
1383
- option :host, required: true, desc: "Base URL of the space-server"
1384
- option :token, required: true, desc: "Bearer token for authorization"
1385
-
1386
- def call(id:, host: nil, token: nil, **opts)
1387
- setup_terminal(**opts.slice(:color, :colors))
1388
- handle_errors do
1389
- Jobs.require_credentials!(host, token)
1390
- job = JobsClient.new(host, token).show(id)
1391
- terminal.say JSON.pretty_generate(job)
1392
- CLI.record_outcome(Outcome.new(exit_code: 0))
1393
- end
1394
- end
1395
- end
1396
-
1397
- class Cancel < BaseCommand
1398
- desc "Cancel a job"
1399
- argument :id, required: true, desc: "Job id"
1400
- option :host, required: true, desc: "Base URL of the space-server"
1401
- option :token, required: true, desc: "Bearer token for authorization"
1402
-
1403
- def call(id:, host: nil, token: nil, **opts)
1404
- setup_terminal(**opts.slice(:color, :colors))
1405
- handle_errors do
1406
- Jobs.require_credentials!(host, token)
1407
- result = JobsClient.new(host, token).cancel(id)
1408
- terminal.say JSON.pretty_generate(result)
1409
- CLI.record_outcome(Outcome.new(exit_code: 0))
1410
- end
1411
- end
1412
- end
1413
-
1414
- class Watch < BaseCommand
1415
- desc "Resolve a job's run and stream its live events (SSE) until run_complete"
1416
- argument :id, required: true, desc: "Job id"
1417
- option :host, required: true, desc: "Base URL of the space-server"
1418
- option :token, required: true, desc: "Bearer token for authorization"
1419
-
1420
- def call(id:, host: nil, token: nil, **opts)
1421
- setup_terminal(**opts.slice(:color, :colors))
1422
- handle_errors do
1423
- Jobs.require_credentials!(host, token)
1424
- client = JobsClient.new(host, token)
1425
- run_id = client.wait_for_run_id(id)
1426
- client.stream(run_id) { |data| terminal.say data }
1427
- CLI.record_outcome(Outcome.new(exit_code: 0))
1428
- end
1429
- end
1430
- end
1431
- end
1432
-
1433
- module Sessions
1434
- # See Jobs.require_credentials! above for why this is validated
1435
- # explicitly rather than relying on dry-cli's `required:` option flag.
1436
- def self.require_credentials!(host, token)
1437
- raise Space::Core::Error, "--host is required" unless host
1438
- raise Space::Core::Error, "--token or #{SessionSync::TOKEN_ENV} is required" unless token
1439
- end
1440
-
1441
- class Sync < BaseCommand
1442
- desc "Scan pi/claude session files and upload new/grown conversations to a space-server"
1443
- option :host, required: true, desc: "Base URL of the space-server"
1444
- option :token, default: nil, desc: "Bearer token for authorization (defaults to $#{SessionSync::TOKEN_ENV}; an explicit --token wins; an op:// ref is resolved once via `op read`)"
1445
- option :state_file, default: nil, desc: "Cursor YAML path (default: $XDG_STATE_HOME/space-architect/session-sync.yaml)"
1446
- option :pi_root, default: nil, desc: "Override the pi sessions root (default: ~/.pi/agent/sessions)"
1447
- option :claude_root, default: nil, desc: "Override the claude projects root (default: ~/.claude/projects)"
1448
- option :dry_run, type: :boolean, default: false, desc: "Report what would upload without uploading or recording the cursor"
1449
-
1450
- def call(host: nil, token: nil, state_file: nil, pi_root: nil, claude_root: nil, dry_run: false, **opts)
1451
- setup_terminal(**opts.slice(:color, :colors))
1452
- handle_errors do
1453
- resolved_token = token || ENV[SessionSync::TOKEN_ENV]
1454
- Sessions.require_credentials!(host, resolved_token)
1455
- runner = SessionSync::Runner.new(
1456
- client: ConversationsClient.new(host, resolved_token),
1457
- state_path: state_file || SessionSync.default_state_file,
1458
- pi_root: pi_root || SessionSync.default_pi_root,
1459
- claude_root: claude_root || SessionSync.default_claude_root,
1460
- dry_run: dry_run
1461
- )
1462
- results = runner.call
1463
- results.each do |r|
1464
- terminal.say "#{r[:action]}: #{r[:path]}#{r[:action] == :failed ? " (#{r[:status]}: #{r[:errors]})" : ""}"
1465
- end
1466
- failed = results.count { |r| r[:action] == :failed }
1467
- terminal.say "#{results.size} file(s): #{results.size - failed} ok, #{failed} failed"
1468
- CLI.record_outcome(Outcome.new(exit_code: failed.zero? ? 0 : 1))
1469
- end
1470
- end
1471
- end
1472
-
1473
- module Agent
1474
- module Helpers
1475
- def plist_path
1476
- File.join(SessionSync.launch_agents_dir, "#{SessionSync::LABEL}.plist")
1477
- end
1478
-
1479
- def format_failure(f) = f.is_a?(Hash) ? f.inspect : f.to_s
1480
-
1481
- # The launchd-agent seam is repo-tender's — a soft dependency, never a
1482
- # gemspec runtime dep. Required lazily so `sessions agent …` degrades
1483
- # with an actionable hint instead of failing at boot when the gem is
1484
- # absent; handle_errors turns the raise into a red message + exit 1.
1485
- def agent
1486
- require "repo_tender"
1487
- RepoTender::Launchd::Agent
1488
- rescue LoadError
1489
- raise Space::Core::Error, "sessions agent needs the repo-tender gem — install it with: gem install repo-tender"
1490
- end
1491
- end
1492
-
1493
- class Install < BaseCommand
1494
- include Helpers
1495
- desc "Install the launchd agent that periodically runs `architect sessions sync`"
1496
- option :host, required: true, desc: "Base URL of the space-server"
1497
- option :token, required: true, desc: "Bearer token for authorization (an op:// ref is resolved once at install time; the raw value is stored in the plist's EnvironmentVariables, not argv)"
1498
- option :interval, default: "900", desc: "StartInterval in seconds (default 900)"
1499
-
1500
- def call(host: nil, token: nil, interval: "900", **opts)
1501
- setup_terminal(**opts.slice(:color, :colors))
1502
- handle_errors do
1503
- Sessions.require_credentials!(host, token)
1504
- resolved_token = token.start_with?("op://") ? SessionSync.resolve_token(token) : token
1505
- pp = plist_path
1506
- xml = SessionSync::Plist.call(
1507
- label: SessionSync::LABEL,
1508
- refresh_interval: interval.to_i,
1509
- log_dir: SessionSync.log_dir,
1510
- bin_path: SessionSync::BinPath.detect,
1511
- ruby_bin: RbConfig.ruby,
1512
- host: host,
1513
- env: {SessionSync::TOKEN_ENV => resolved_token}
1514
- )
1515
- FileUtils.mkdir_p(File.dirname(pp))
1516
- File.write(pp, xml)
1517
- File.chmod(0o600, pp)
1518
-
1519
- result = agent.new(label: SessionSync::LABEL).install(pp)
1520
- raise Space::Core::Error, "bootstrap failed: #{format_failure(result.failure)}" if result.failure?
1521
-
1522
- terminal.say "Installed: #{terminal.path(pp)}"
1523
- CLI.record_outcome(Outcome.new(exit_code: 0))
1524
- end
1525
- end
1526
- end
1527
-
1528
- class Uninstall < BaseCommand
1529
- include Helpers
1530
- desc "Uninstall the session-sync launchd agent (bootout + remove the plist)"
1531
-
1532
- def call(**opts)
1533
- setup_terminal(**opts.slice(:color, :colors))
1534
- handle_errors do
1535
- pp = plist_path
1536
- result = agent.new(label: SessionSync::LABEL).uninstall
1537
- terminal.say "bootout reported: #{format_failure(result.failure)}" if result.failure?
1538
-
1539
- if File.exist?(pp)
1540
- File.delete(pp)
1541
- terminal.say "Removed plist: #{terminal.path(pp)}"
1542
- else
1543
- terminal.say "Plist not present: #{terminal.path(pp)}"
1544
- end
1545
- CLI.record_outcome(Outcome.new(exit_code: 0))
1546
- end
1547
- end
1548
- end
1549
-
1550
- class Status < BaseCommand
1551
- include Helpers
1552
- desc "Report the session-sync launchd agent's loaded/running/last-exit state"
1553
-
1554
- def call(**opts)
1555
- setup_terminal(**opts.slice(:color, :colors))
1556
- handle_errors do
1557
- result = agent.new(label: SessionSync::LABEL).status
1558
- raise Space::Core::Error, "status failed: #{format_failure(result.failure)}" if result.failure?
1559
-
1560
- s = result.success
1561
- terminal.say "label: #{SessionSync::LABEL}"
1562
- terminal.say "loaded: #{s[:loaded]}"
1563
- terminal.say "running: #{s[:running]}"
1564
- terminal.say "pid: #{s[:pid].inspect}"
1565
- terminal.say "last_exit: #{s[:last_exit].inspect}"
1566
- CLI.record_outcome(Outcome.new(exit_code: 0))
1567
- end
1568
- end
1569
- end
1570
- end
1571
- end
1572
1303
  end
1573
1304
  end
1574
1305
  end
@@ -1583,7 +1314,6 @@ Space::Architect::CLI::Registry.register "init", Space::Architect::CLI::Archit
1583
1314
  Space::Architect::CLI::Registry.register "ground", Space::Architect::CLI::Architect::Ground
1584
1315
  Space::Architect::CLI::Registry.register "new", Space::Architect::CLI::Architect::New
1585
1316
  Space::Architect::CLI::Registry.register "status", Space::Architect::CLI::Architect::Status
1586
- Space::Architect::CLI::Registry.register "push", Space::Architect::CLI::Architect::Push
1587
1317
  Space::Architect::CLI::Registry.register "sync", Space::Architect::CLI::Architect::Sync
1588
1318
  Space::Architect::CLI::Registry.register "rehearse", Space::Architect::CLI::Architect::Rehearse
1589
1319
  Space::Architect::CLI::Registry.register "freeze", Space::Architect::CLI::Architect::Freeze
@@ -1610,20 +1340,6 @@ Space::Architect::CLI::Registry.register "worktree" do |wt|
1610
1340
  wt.register "remove", Space::Architect::CLI::Architect::Worktree::Remove
1611
1341
  wt.register "list", Space::Architect::CLI::Architect::Worktree::List
1612
1342
  end
1613
- Space::Architect::CLI::Registry.register "jobs" do |j|
1614
- j.register "list", Space::Architect::CLI::Architect::Jobs::List
1615
- j.register "show", Space::Architect::CLI::Architect::Jobs::Show
1616
- j.register "watch", Space::Architect::CLI::Architect::Jobs::Watch
1617
- j.register "cancel", Space::Architect::CLI::Architect::Jobs::Cancel
1618
- end
1619
- Space::Architect::CLI::Registry.register "sessions" do |s|
1620
- s.register "sync", Space::Architect::CLI::Architect::Sessions::Sync
1621
- s.register "agent" do |a|
1622
- a.register "install", Space::Architect::CLI::Architect::Sessions::Agent::Install
1623
- a.register "uninstall", Space::Architect::CLI::Architect::Sessions::Agent::Uninstall
1624
- a.register "status", Space::Architect::CLI::Architect::Sessions::Agent::Status
1625
- end
1626
- end
1627
1343
  Space::Architect::CLI::Registry.register "variant" do |v|
1628
1344
  v.register "add", Space::Architect::CLI::Architect::Variant::Add
1629
1345
  v.register "promote", Space::Architect::CLI::Architect::Variant::Promote
@@ -1,12 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "async/process"
4
- require "async/http/client"
5
- require "async/http/endpoint"
6
- require "protocol/http/body/writable"
7
4
  require "json"
8
5
  require "pathname"
9
- require "uri"
10
6
 
11
7
  module Space::Architect
12
8
  module Harness
@@ -106,8 +102,7 @@ module Space::Architect
106
102
  @guard_path = guard_path
107
103
  end
108
104
 
109
- def run(prompt_path:, run_log_path:, chdir:, push_url: nil, push_token: nil,
110
- push_client: nil, timeout: nil, liveness_delay: LIVENESS_DELAY_SECONDS, err: $stderr)
105
+ def run(prompt_path:, run_log_path:, chdir:, timeout: nil, liveness_delay: LIVENESS_DELAY_SECONDS, err: $stderr)
111
106
  prompt_path = Pathname.new(prompt_path)
112
107
  run_log_path = Pathname.new(run_log_path)
113
108
 
@@ -117,7 +112,7 @@ module Space::Architect
117
112
  Sync do
118
113
  child = Async::Process::Child.new(*argv, chdir: chdir.to_s, in: prompt_io, out: w, err: log)
119
114
  w.close
120
- tasks = start_tee(r, log, push_url: push_url, push_token: push_token, push_client: push_client, err: err)
115
+ tasks = [Async { drain_pipe(r, log) }]
121
116
  timed_out = false
122
117
  timeout_task = nil
123
118
 
@@ -266,50 +261,6 @@ module Space::Architect
266
261
  # Best-effort: an internal read/parse failure must never break the run.
267
262
  end
268
263
 
269
- def start_tee(r, log, push_url:, push_token:, push_client:, err: $stderr)
270
- if push_url || push_client
271
- body = Protocol::HTTP::Body::Writable.new(queue: Thread::SizedQueue.new(32))
272
- push = Async { push_body(body, push_url: push_url, push_token: push_token, push_client: push_client, err: err) }
273
- [Async { tee_pipe(r, log, body, err: err) }, push]
274
- else
275
- [Async { drain_pipe(r, log) }]
276
- end
277
- end
278
-
279
- def push_body(body, push_url:, push_token:, push_client:, err: $stderr)
280
- path = push_url ? URI.parse(push_url).path : "/"
281
- headers = [["content-type", "application/x-ndjson"]]
282
- headers << ["authorization", "Bearer #{push_token}"] if push_token
283
- if push_client
284
- push_client.post(path, headers: headers, body: body).discard
285
- else
286
- Async::HTTP::Client.open(Async::HTTP::Endpoint.parse(push_url)) do |c|
287
- c.post(path, headers: headers, body: body).discard
288
- end
289
- end
290
- rescue StandardError => e
291
- err.puts "push_body: transport error (best-effort, run log intact): #{e.class}: #{e.message}"
292
- end
293
-
294
- def tee_pipe(r, log, body, err: $stderr)
295
- pushing = true
296
- while (chunk = r.gets)
297
- log.write(chunk)
298
- log.flush
299
- if pushing
300
- begin
301
- body.write(chunk)
302
- rescue StandardError => e
303
- err.puts "tee_pipe: push write failed (best-effort, continuing log): #{e.class}: #{e.message}"
304
- pushing = false
305
- end
306
- end
307
- end
308
- ensure
309
- body.close_write
310
- r.close
311
- end
312
-
313
264
  def drain_pipe(r, log)
314
265
  while (chunk = r.gets)
315
266
  log.write(chunk)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Space
4
4
  module Architect
5
- VERSION = "9.2.1"
5
+ VERSION = "9.3.0"
6
6
  end
7
7
  end
@@ -5,14 +5,10 @@ require_relative "space_architect/version"
5
5
 
6
6
  require_relative "space_architect/harness"
7
7
  require_relative "space_architect/routing"
8
- require_relative "space_architect/run_creator"
9
- require_relative "space_architect/space_push"
10
8
  require_relative "space_architect/gate_lint"
11
9
  require_relative "space_architect/gate_evaluator"
12
10
  require_relative "space_architect/architect_project"
13
11
  require_relative "space_architect/skill_installer"
14
12
  require_relative "space_architect/bug_report"
15
13
  require_relative "space_architect/research"
16
- require_relative "space_architect/conversations_client"
17
- require_relative "space_architect/session_sync"
18
14
  require_relative "space_architect/cli"