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
@@ -6,10 +6,10 @@ require "async/semaphore"
6
6
  require "pathname"
7
7
  require "time"
8
8
  require "dry/monads"
9
- require "space_architect/pristine/scm/git"
10
- require "space_architect/pristine/cloner"
9
+ require "space_src/scm/git"
10
+ require "space_src/cloner"
11
11
 
12
- module SpaceArchitect
12
+ module Space::Core
13
13
  class SpaceStore
14
14
  include Dry::Monads[:result, :maybe]
15
15
 
@@ -46,7 +46,7 @@ module SpaceArchitect
46
46
  init_git(path:, id:, git_client:) if git
47
47
  state.touch_recent(id)
48
48
  Success(space)
49
- rescue SpaceArchitect::Error => e
49
+ rescue Error => e
50
50
  Failure(e)
51
51
  end
52
52
 
@@ -67,7 +67,7 @@ module SpaceArchitect
67
67
  if looks_like_path?(value)
68
68
  begin
69
69
  return Success(Space.load(File.expand_path(value)))
70
- rescue SpaceArchitect::Error => e
70
+ rescue Error => e
71
71
  return Failure(e)
72
72
  end
73
73
  end
@@ -80,13 +80,13 @@ module SpaceArchitect
80
80
  end
81
81
 
82
82
  Failure(AmbiguousSpaceError.new("Space '#{value}' is ambiguous: #{matches.map(&:id).join(', ')}"))
83
- rescue SpaceArchitect::Error => e
83
+ rescue Error => e
84
84
  Failure(e)
85
85
  end
86
86
 
87
87
  def current(from: Dir.pwd)
88
88
  current_from_pwd(from:).to_result(CurrentSpaceMissingError.new("No current space found from #{from}. Run this inside a space or pass a space id."))
89
- rescue SpaceArchitect::Error => e
89
+ rescue Error => e
90
90
  Failure(e)
91
91
  end
92
92
 
@@ -112,15 +112,15 @@ module SpaceArchitect
112
112
  find(identifier).fmap { |space| state.touch_recent(space.id); space }
113
113
  end
114
114
 
115
- def add_repo(spec, from: Dir.pwd, scm: Pristine::SCM::Git.new, cloner: nil, mise_client: MiseClient.new)
115
+ def add_repo(spec, from: Dir.pwd, scm: ::Space::Src::SCM::Git.new, cloner: nil, mise_client: MiseClient.new)
116
116
  add_repos([spec], from:, scm:, cloner:, mise_client:).fmap(&:first)
117
117
  end
118
118
 
119
- def add_repos(specs, from: Dir.pwd, scm: Pristine::SCM::Git.new, cloner: nil, mise_client: MiseClient.new, reporter: nil)
119
+ def add_repos(specs, from: Dir.pwd, scm: ::Space::Src::SCM::Git.new, cloner: nil, mise_client: MiseClient.new, reporter: nil)
120
120
  current(from:).bind { |space| add_repos_to(space, specs, scm:, cloner:, mise_client:, reporter:) }
121
121
  end
122
122
 
123
- def add_repos_to(space, specs, scm: Pristine::SCM::Git.new, cloner: nil, mise_client: MiseClient.new, reporter: nil)
123
+ def add_repos_to(space, specs, scm: ::Space::Src::SCM::Git.new, cloner: nil, mise_client: MiseClient.new, reporter: nil)
124
124
  additions = prepare_repo_additions(space, specs)
125
125
  first_error = nil
126
126
 
@@ -148,7 +148,7 @@ module SpaceArchitect
148
148
  repo_data = space.add_repo(addition.fetch(:reference), relative_path: addition.fetch(:relative_path), now: now.call)
149
149
  { space: space, repo: repo_data, reference: addition.fetch(:reference), path: addition.fetch(:path) }
150
150
  end)
151
- rescue SpaceArchitect::Error => e
151
+ rescue Error => e
152
152
  Failure(e)
153
153
  end
154
154
 
@@ -178,7 +178,7 @@ module SpaceArchitect
178
178
  source = addition.fetch(:src_source)
179
179
 
180
180
  if source&.directory?
181
- actual_cloner = cloner || Pristine::Cloner.new(base_dir: config.src_dir)
181
+ actual_cloner = cloner || ::Space::Src::Cloner.new(base_dir: config.src_dir)
182
182
  result = actual_cloner.call(name: reference.full_name, into: destination.dirname.to_s)
183
183
  raise GitError, "clone failed (copy): #{result.failure}" if result.failure?
184
184
  else
@@ -3,7 +3,7 @@
3
3
  require "yaml"
4
4
  require "pathname"
5
5
 
6
- module SpaceArchitect
6
+ module Space::Core
7
7
  class State
8
8
  DEFAULT_DATA = {
9
9
  "version" => 1,
@@ -3,7 +3,7 @@
3
3
  require "async"
4
4
  require "pastel"
5
5
 
6
- module SpaceArchitect
6
+ module Space::Core
7
7
  class Terminal
8
8
  SPINNER_FRAMES = %w[⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏].freeze
9
9
 
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Space
4
+ module Core
5
+ VERSION = "2.0.0.rc1"
6
+ end
7
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module SpaceArchitect
3
+ module Space::Core
4
4
  module Warnings
5
5
  module_function
6
6
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "pathname"
4
4
 
5
- module SpaceArchitect
5
+ module Space::Core
6
6
  module XDG
7
7
  module_function
8
8
 
data/lib/space_core.rb ADDED
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "space_core/version"
4
+
5
+ module Space::Core
6
+ end
7
+
8
+ require "space_core/errors"
9
+ require "space_core/warnings"
10
+ Space::Core::Warnings.disable_experimental!
11
+ require "space_core/atomic_write"
12
+ require "space_core/xdg"
13
+ require "space_core/config"
14
+ require "space_core/state"
15
+ require "space_core/slugger"
16
+ require "space_core/space"
17
+ require "space_core/repo_reference"
18
+ require "space_core/repo_resolver"
19
+ require "space_core/git_client"
20
+ require "space_core/mise_client"
21
+ require "space_core/space_store"
22
+ require "space_core/shell_integration"
23
+ require "space_core/terminal"
24
+ require "space_core/cli"
@@ -2,11 +2,11 @@
2
2
 
3
3
  require "pastel"
4
4
  require "dry/monads"
5
- require "space_architect/pristine/cli"
6
- require "space_architect/pristine/cli/options"
7
- require "space_architect/pristine/cloner"
5
+ require "space_src/cli"
6
+ require "space_src/cli/options"
7
+ require "space_src/cloner"
8
8
 
9
- module SpaceArchitect::Pristine
9
+ module Space::Src
10
10
  module CLI
11
11
  # `clone` command: APFS COW copy of an evergreen repo into a working dir.
12
12
  # Resolves each NAME against config.base_dir and copies via cp -Rc.
@@ -52,4 +52,4 @@ module SpaceArchitect::Pristine
52
52
  end
53
53
  end
54
54
 
55
- SpaceArchitect::Pristine::CLI::Registry.register "clone", SpaceArchitect::Pristine::CLI::Clone
55
+ Space::Src::CLI::Registry.register "clone", Space::Src::CLI::Clone
@@ -2,11 +2,11 @@
2
2
 
3
3
  require "pastel"
4
4
  require "yaml"
5
- require "space_architect/pristine/cli"
6
- require "space_architect/pristine/ui/mode"
7
- require "space_architect/pristine/cli/options"
5
+ require "space_src/cli"
6
+ require "space_src/ui/mode"
7
+ require "space_src/cli/options"
8
8
 
9
- module SpaceArchitect::Pristine
9
+ module Space::Src
10
10
  module CLI
11
11
  # `config` command group: path / show.
12
12
  module ConfigCmd
@@ -60,7 +60,7 @@ end
60
60
  # the CLI command group. We register under a different module name
61
61
  # (ConfigCmd) to avoid the constant clash, then alias the
62
62
  # registration key as "config".
63
- SpaceArchitect::Pristine::CLI::Registry.register "config" do |prefix|
64
- prefix.register "path", SpaceArchitect::Pristine::CLI::ConfigCmd::Path
65
- prefix.register "show", SpaceArchitect::Pristine::CLI::ConfigCmd::Show
63
+ Space::Src::CLI::Registry.register "config" do |prefix|
64
+ prefix.register "path", Space::Src::CLI::ConfigCmd::Path
65
+ prefix.register "show", Space::Src::CLI::ConfigCmd::Show
66
66
  end
@@ -3,17 +3,18 @@
3
3
  require "pastel"
4
4
  require "fileutils"
5
5
  require "dry/monads"
6
- require "space_architect/pristine/cli"
7
- require "space_architect/pristine/ui/mode"
8
- require "space_architect/pristine/cli/options"
9
- require "space_architect/pristine/launchd/agent"
10
- require "space_architect/pristine/launchd/plist"
11
-
12
- module SpaceArchitect::Pristine
6
+ require "space_src/cli"
7
+ require "space_src/ui/mode"
8
+ require "space_src/cli/options"
9
+ require "space_src/launchd/agent"
10
+ require "space_src/launchd/plist"
11
+ require "space_src/migration"
12
+
13
+ module Space::Src
13
14
  module CLI
14
15
  # `daemon` command group: install / uninstall / start / stop
15
16
  # / restart / status. Installs a per-user launchd agent
16
- # (`gui/<UID>`) that fires `repo-tender sync` on a
17
+ # (`gui/<UID>`) that fires `src sync` on a
17
18
  # `StartInterval`. The launchctl side is exercised ONLY
18
19
  # through an injected command runner (slice-4 gates G2–G4);
19
20
  # the live domain is proven by the manual real-Mac checklist
@@ -57,7 +58,7 @@ module SpaceArchitect::Pristine
57
58
  # toolchain-resolved ruby; pinned via mise.toml).
58
59
  # * `bin_path` — `RbConfig.ruby` + the script path
59
60
  # (we use `__dir__` of this file's caller; for the
60
- # gem install, this is `<gem>/bin/repo-tender`).
61
+ # gem install, this is `<gem>/exe/src`).
61
62
  #
62
63
  # In tests, we inject these via the `Resolve` object
63
64
  # (see below) — never call out to the shell.
@@ -78,7 +79,7 @@ module SpaceArchitect::Pristine
78
79
 
79
80
  def fail_with(cmd, msg)
80
81
  cmd.send(:err).puts msg
81
- SpaceArchitect::Pristine::CLI.record_outcome(Outcome.new(exit_code: 1, message: msg))
82
+ Space::Src::CLI.record_outcome(Outcome.new(exit_code: 1, message: msg))
82
83
  end
83
84
 
84
85
  # Bundle of resolved absolute paths the plist needs.
@@ -113,6 +114,15 @@ module SpaceArchitect::Pristine
113
114
  label = Launchd::Agent::DEFAULT_LABEL
114
115
  pp = plist_path(paths, label)
115
116
 
117
+ # Relabel: if old-identity plist exists, bootout (benign-failure-tolerant)
118
+ # and remove it before bootstrapping the new-label plist.
119
+ old_pp = plist_path(paths, Migration::OLD_LABEL)
120
+ if File.exist?(old_pp)
121
+ old_agent = make_agent(label: Migration::OLD_LABEL)
122
+ old_agent.uninstall
123
+ File.delete(old_pp) if File.exist?(old_pp)
124
+ end
125
+
116
126
  resolve = Resolve.detect(repo_root: Dir.pwd)
117
127
  xml = build_plist(resolve: resolve, config: config, paths: paths, label: label)
118
128
  FileUtils.mkdir_p(File.dirname(pp))
@@ -264,6 +274,12 @@ module SpaceArchitect::Pristine
264
274
  )
265
275
  pastel = Pastel.new(enabled: mode.color)
266
276
 
277
+ paths = CLI.make_paths
278
+ old_pp = plist_path(paths, Migration::OLD_LABEL)
279
+ if File.exist?(old_pp)
280
+ err.puts pastel.yellow("warning: stale agent #{Migration::OLD_LABEL} detected; run `src daemon install` to upgrade")
281
+ end
282
+
267
283
  label = Launchd::Agent::DEFAULT_LABEL
268
284
  agent = make_agent
269
285
  result = agent.status
@@ -283,12 +299,12 @@ module SpaceArchitect::Pristine
283
299
  end
284
300
  end
285
301
 
286
- # Detect the runtime paths the plist needs. The repo-tender
287
- # install path matters because the plist stores an absolute
288
- # `bin_path` — that is the script launchd invokes. We resolve
289
- # `bin_path` from the on-disk gem layout if we can, else fall
290
- # back to the directory the daemon command was run from.
291
- class SpaceArchitect::Pristine::CLI::Daemon::Helpers::Resolve
302
+ # Detect the runtime paths the plist needs. The src install path
303
+ # matters because the plist stores an absolute `bin_path` — that
304
+ # is the script launchd invokes. We resolve `bin_path` from the
305
+ # on-disk gem layout if we can, else fall back to the directory
306
+ # the daemon command was run from.
307
+ class Space::Src::CLI::Daemon::Helpers::Resolve
292
308
  # @param repo_root [String] absolute path of the working directory (where mise.toml is expected)
293
309
  # @return [Resolve]
294
310
  def self.detect(repo_root:)
@@ -300,7 +316,7 @@ class SpaceArchitect::Pristine::CLI::Daemon::Helpers::Resolve
300
316
  end
301
317
 
302
318
  def self.detect_mise_bin
303
- path = ENV["REPO_TENDER_MISE_BIN"]
319
+ path = ENV["SPACE_SRC_MISE_BIN"]
304
320
  return path if path && !path.empty?
305
321
  require "open3"
306
322
  out, _e, st = Open3.capture3("which", "mise")
@@ -308,7 +324,7 @@ class SpaceArchitect::Pristine::CLI::Daemon::Helpers::Resolve
308
324
  end
309
325
 
310
326
  def self.detect_ruby_bin(repo_root, mise_bin)
311
- path = ENV["REPO_TENDER_RUBY_BIN"]
327
+ path = ENV["SPACE_SRC_RUBY_BIN"]
312
328
  return path if path && !path.empty?
313
329
  # `mise exec -- which ruby` — but we avoid spawning in tests;
314
330
  # production path goes through here.
@@ -321,27 +337,27 @@ class SpaceArchitect::Pristine::CLI::Daemon::Helpers::Resolve
321
337
  end
322
338
 
323
339
  def self.detect_bin_path(repo_root)
324
- path = ENV["REPO_TENDER_BIN_PATH"]
340
+ path = ENV["SPACE_SRC_BIN_PATH"]
325
341
  return path if path && !path.empty?
326
- # Prefer the on-disk dev bin at `<repo_root>/bin/repo-tender`
342
+ # Prefer the on-disk dev bin at `<repo_root>/exe/src`
327
343
  # — it's what the human runs during testing, and the gem is
328
344
  # typically not `gem install`ed in a source checkout.
329
- dev = File.join(repo_root, "bin", "repo-tender")
345
+ dev = File.join(repo_root, "exe", "src")
330
346
  return dev if File.exist?(dev)
331
347
  # Next, an installed binary on PATH.
332
348
  require "open3"
333
- out, _e, st = Open3.capture3("which", "repo-tender")
349
+ out, _e, st = Open3.capture3("which", "src")
334
350
  return out.strip if st.success? && !out.strip.empty?
335
351
  # Last resort: the installed gem's bin (raises if not installed).
336
- Gem.bin_path("repo-tender", "repo-tender")
352
+ Gem.bin_path("space-architect", "src")
337
353
  end
338
354
  end
339
355
 
340
- SpaceArchitect::Pristine::CLI::Registry.register "daemon" do |prefix|
341
- prefix.register "install", SpaceArchitect::Pristine::CLI::Daemon::Install
342
- prefix.register "uninstall", SpaceArchitect::Pristine::CLI::Daemon::Uninstall
343
- prefix.register "start", SpaceArchitect::Pristine::CLI::Daemon::Start
344
- prefix.register "stop", SpaceArchitect::Pristine::CLI::Daemon::Stop
345
- prefix.register "restart", SpaceArchitect::Pristine::CLI::Daemon::Restart
346
- prefix.register "status", SpaceArchitect::Pristine::CLI::Daemon::Status
356
+ Space::Src::CLI::Registry.register "daemon" do |prefix|
357
+ prefix.register "install", Space::Src::CLI::Daemon::Install
358
+ prefix.register "uninstall", Space::Src::CLI::Daemon::Uninstall
359
+ prefix.register "start", Space::Src::CLI::Daemon::Start
360
+ prefix.register "stop", Space::Src::CLI::Daemon::Stop
361
+ prefix.register "restart", Space::Src::CLI::Daemon::Restart
362
+ prefix.register "status", Space::Src::CLI::Daemon::Status
347
363
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module SpaceArchitect::Pristine
3
+ module Space::Src
4
4
  module CLI
5
5
  # Shared output mode flags. Include in any command class to add:
6
6
  # --plain force plain text output (one line per event, ANSI-free)
@@ -2,11 +2,11 @@
2
2
 
3
3
  require "pastel"
4
4
  require "dry/monads"
5
- require "space_architect/pristine/cli"
6
- require "space_architect/pristine/ui/mode"
7
- require "space_architect/pristine/cli/options"
5
+ require "space_src/cli"
6
+ require "space_src/ui/mode"
7
+ require "space_src/cli/options"
8
8
 
9
- module SpaceArchitect::Pristine
9
+ module Space::Src
10
10
  module CLI
11
11
  # `org` command group: add / remove / list tracked orgs.
12
12
  # Same shape as `repo` but against `Config::OrgRef` (host + name
@@ -60,7 +60,7 @@ module SpaceArchitect::Pristine
60
60
 
61
61
  def fail_with(cmd, msg)
62
62
  cmd.send(:err).puts msg
63
- SpaceArchitect::Pristine::CLI.record_outcome(Outcome.new(exit_code: 1, message: msg))
63
+ Space::Src::CLI.record_outcome(Outcome.new(exit_code: 1, message: msg))
64
64
  end
65
65
  end
66
66
 
@@ -193,8 +193,8 @@ module SpaceArchitect::Pristine
193
193
  end
194
194
  end
195
195
 
196
- SpaceArchitect::Pristine::CLI::Registry.register "org" do |prefix|
197
- prefix.register "add", SpaceArchitect::Pristine::CLI::Org::Add
198
- prefix.register "remove", SpaceArchitect::Pristine::CLI::Org::Remove
199
- prefix.register "list", SpaceArchitect::Pristine::CLI::Org::List
196
+ Space::Src::CLI::Registry.register "org" do |prefix|
197
+ prefix.register "add", Space::Src::CLI::Org::Add
198
+ prefix.register "remove", Space::Src::CLI::Org::Remove
199
+ prefix.register "list", Space::Src::CLI::Org::List
200
200
  end
@@ -2,11 +2,11 @@
2
2
 
3
3
  require "pastel"
4
4
  require "dry/monads"
5
- require "space_architect/pristine/cli"
6
- require "space_architect/pristine/ui/mode"
7
- require "space_architect/pristine/cli/options"
5
+ require "space_src/cli"
6
+ require "space_src/ui/mode"
7
+ require "space_src/cli/options"
8
8
 
9
- module SpaceArchitect::Pristine
9
+ module Space::Src
10
10
  module CLI
11
11
  # `repo` command group: add / remove / list tracked repos.
12
12
  # Backed by Config::Store (the on-disk config.yaml is the
@@ -39,7 +39,7 @@ module SpaceArchitect::Pristine
39
39
 
40
40
  def fail_with(cmd, msg)
41
41
  cmd.send(:err).puts msg
42
- SpaceArchitect::Pristine::CLI.record_outcome(Outcome.new(exit_code: 1, message: msg))
42
+ Space::Src::CLI.record_outcome(Outcome.new(exit_code: 1, message: msg))
43
43
  end
44
44
  end
45
45
 
@@ -163,8 +163,8 @@ end
163
163
  # Register the `repo` group + its subcommands. The block's `prefix`
164
164
  # is a Dry::CLI::Registry::Prefix proxy that namespaces the
165
165
  # subcommand names under "repo".
166
- SpaceArchitect::Pristine::CLI::Registry.register "repo" do |prefix|
167
- prefix.register "add", SpaceArchitect::Pristine::CLI::Repo::Add
168
- prefix.register "remove", SpaceArchitect::Pristine::CLI::Repo::Remove
169
- prefix.register "list", SpaceArchitect::Pristine::CLI::Repo::List
166
+ Space::Src::CLI::Registry.register "repo" do |prefix|
167
+ prefix.register "add", Space::Src::CLI::Repo::Add
168
+ prefix.register "remove", Space::Src::CLI::Repo::Remove
169
+ prefix.register "list", Space::Src::CLI::Repo::List
170
170
  end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "space_src/cli"
4
+ require "space_src/shell_integration"
5
+
6
+ module Space::Src
7
+ module CLI
8
+ module Shell
9
+ class Init < Dry::CLI::Command
10
+ desc "Print shell integration script"
11
+ argument :shell_name, required: true, desc: "Shell name (e.g. fish)"
12
+
13
+ def call(shell_name:, **)
14
+ out.puts ShellIntegration.for(shell_name)
15
+ CLI.record_outcome(Outcome.new(exit_code: 0))
16
+ rescue => e
17
+ err.puts "src shell init: #{e.message}"
18
+ CLI.record_outcome(Outcome.new(exit_code: 1))
19
+ end
20
+ end
21
+
22
+ class Fish < Dry::CLI::Command
23
+ desc "Manage fish shell integration: install, uninstall, path"
24
+ argument :subcommand, required: false, desc: "install, uninstall, or path (default: install)"
25
+ option :force, type: :boolean, default: false, desc: "Overwrite or remove existing shell files"
26
+
27
+ def call(subcommand: "install", force: false, **)
28
+ case subcommand
29
+ when "install"
30
+ result = ShellIntegration.install("fish", env: CLI.env, force: force)
31
+ out.puts fish_install_message(result.fetch(:action), result.fetch(:path))
32
+ out.puts fish_completions_install_message(result.fetch(:completions_action), result.fetch(:completions_path))
33
+ out.puts "Restart fish to load the integration in this terminal: exec fish"
34
+ CLI.record_outcome(Outcome.new(exit_code: 0))
35
+ when "uninstall"
36
+ result = ShellIntegration.uninstall("fish", env: CLI.env, force: force)
37
+ out.puts fish_uninstall_message(result.fetch(:action), result.fetch(:path))
38
+ out.puts fish_completions_uninstall_message(result.fetch(:completions_action), result.fetch(:completions_path))
39
+ CLI.record_outcome(Outcome.new(exit_code: 0))
40
+ when "path"
41
+ out.puts "Function: #{ShellIntegration.path_for("fish", env: CLI.env)}"
42
+ out.puts "Completions: #{ShellIntegration.completions_path_for("fish", env: CLI.env)}"
43
+ CLI.record_outcome(Outcome.new(exit_code: 0))
44
+ else
45
+ err.puts "Usage: src shell fish [install|uninstall|path]"
46
+ CLI.record_outcome(Outcome.new(exit_code: 1))
47
+ end
48
+ rescue => e
49
+ err.puts "src shell fish: #{e.message}"
50
+ CLI.record_outcome(Outcome.new(exit_code: 1))
51
+ end
52
+
53
+ private
54
+
55
+ def fish_install_message(action, path)
56
+ case action
57
+ when :unchanged then "Fish integration already installed: #{path}"
58
+ when :updated then "Updated fish integration: #{path}"
59
+ else "Installed fish integration: #{path}"
60
+ end
61
+ end
62
+
63
+ def fish_uninstall_message(action, path)
64
+ case action
65
+ when :missing then "Fish integration was not installed: #{path}"
66
+ else "Removed fish integration: #{path}"
67
+ end
68
+ end
69
+
70
+ def fish_completions_install_message(action, path)
71
+ case action
72
+ when :unchanged then "Fish completions already installed: #{path}"
73
+ when :updated then "Updated fish completions: #{path}"
74
+ else "Installed fish completions: #{path}"
75
+ end
76
+ end
77
+
78
+ def fish_completions_uninstall_message(action, path)
79
+ case action
80
+ when :missing then "Fish completions were not installed: #{path}"
81
+ else "Removed fish completions: #{path}"
82
+ end
83
+ end
84
+ end
85
+
86
+ class Complete < Dry::CLI::Command
87
+ desc "Print completion candidates"
88
+ argument :kind, required: true, desc: "Completion kind"
89
+ argument :extra, type: :array, required: false, desc: "Extra args for completion"
90
+
91
+ def call(kind:, extra: [], **)
92
+ case kind
93
+ when "checkouts"
94
+ begin
95
+ paths = CLI.make_paths
96
+ config = Config::Store.load(paths.config_file).success
97
+ Nav.scan(config.base_dir).each { |e| out.puts e[:target] }
98
+ rescue
99
+ # Never raise on a completion call — missing/unparseable config is fine.
100
+ end
101
+ CLI.record_outcome(Outcome.new(exit_code: 0))
102
+ when "shells"
103
+ out.puts "fish"
104
+ CLI.record_outcome(Outcome.new(exit_code: 0))
105
+ else
106
+ err.puts "Usage: src shell complete checkouts|shells"
107
+ CLI.record_outcome(Outcome.new(exit_code: 1))
108
+ end
109
+ rescue => e
110
+ err.puts "src shell complete: #{e.message}"
111
+ CLI.record_outcome(Outcome.new(exit_code: 1))
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+
118
+ Space::Src::CLI::Registry.register "shell" do |prefix|
119
+ prefix.register "init", Space::Src::CLI::Shell::Init
120
+ prefix.register "fish", Space::Src::CLI::Shell::Fish
121
+ prefix.register "complete", Space::Src::CLI::Shell::Complete
122
+ end
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "pastel"
4
- require "space_architect/pristine/cli"
5
- require "space_architect/pristine/ui/mode"
6
- require "space_architect/pristine/cli/options"
4
+ require "space_src/cli"
5
+ require "space_src/ui/mode"
6
+ require "space_src/cli/options"
7
7
 
8
- module SpaceArchitect::Pristine
8
+ module Space::Src
9
9
  module CLI
10
10
  # `status` command: render a per-repo evergreen table from
11
11
  # State::Store. Per G5, the rows must include each repo key
@@ -23,7 +23,7 @@ module SpaceArchitect::Pristine
23
23
  "error" => :red
24
24
  }.freeze
25
25
 
26
- desc "Show the per-repo evergreen status table (from $XDG_STATE_HOME/repo-tender/state.yaml)"
26
+ desc "Show the per-repo evergreen status table (from $XDG_STATE_HOME/space-src/state.yaml)"
27
27
 
28
28
  def call(plain: nil, json: nil, no_color: nil, quiet: nil, **)
29
29
  mode = UI::Mode.resolve(
@@ -37,7 +37,7 @@ module SpaceArchitect::Pristine
37
37
  state = State::Store.load(paths.state_file).success
38
38
 
39
39
  if state.repos.empty?
40
- out.puts "(no repos in state — run `repo-tender sync` to populate)"
40
+ out.puts "(no repos in state — run `src sync` to populate)"
41
41
  return CLI.record_outcome(Outcome.new(exit_code: 0))
42
42
  end
43
43
 
@@ -73,4 +73,4 @@ module SpaceArchitect::Pristine
73
73
  end
74
74
  end
75
75
 
76
- SpaceArchitect::Pristine::CLI::Registry.register "status", SpaceArchitect::Pristine::CLI::Status::Show
76
+ Space::Src::CLI::Registry.register "status", Space::Src::CLI::Status::Show