space-architect 8.0.0 → 9.1.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.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +54 -0
  3. data/README.md +39 -464
  4. data/lib/space_architect/architect_project.rb +90 -39
  5. data/lib/space_architect/bug_report.rb +2 -1
  6. data/lib/space_architect/cli/architect.rb +189 -11
  7. data/lib/space_architect/cli/research.rb +1 -1
  8. data/lib/space_architect/cli.rb +9 -14
  9. data/lib/space_architect/loop_cost.rb +592 -0
  10. data/lib/space_architect/research/supervisor.rb +9 -2
  11. data/lib/space_architect/routing.rb +149 -0
  12. data/lib/space_architect/session_sync/bin_path.rb +1 -1
  13. data/lib/space_architect/session_sync/cursor.rb +1 -1
  14. data/lib/space_architect/session_sync/plist.rb +1 -1
  15. data/lib/{space_core → space_architect}/version.rb +2 -2
  16. data/lib/space_architect.rb +2 -1
  17. data/skill/architect/SKILL.md +3 -2
  18. data/skill/architect-vocabulary/SKILL.md +2 -2
  19. metadata +20 -177
  20. data/exe/space +0 -13
  21. data/exe/src +0 -13
  22. data/lib/space_architect/cli/space.rb +0 -29
  23. data/lib/space_architect/cli/src.rb +0 -38
  24. data/lib/space_core/atomic_write.rb +0 -21
  25. data/lib/space_core/cli/base_command.rb +0 -19
  26. data/lib/space_core/cli/build.rb +0 -27
  27. data/lib/space_core/cli/config.rb +0 -49
  28. data/lib/space_core/cli/current.rb +0 -16
  29. data/lib/space_core/cli/help.rb +0 -195
  30. data/lib/space_core/cli/helpers.rb +0 -115
  31. data/lib/space_core/cli/init.rb +0 -29
  32. data/lib/space_core/cli/list.rb +0 -24
  33. data/lib/space_core/cli/loop_status.rb +0 -47
  34. data/lib/space_core/cli/new.rb +0 -38
  35. data/lib/space_core/cli/pack.rb +0 -29
  36. data/lib/space_core/cli/path.rb +0 -16
  37. data/lib/space_core/cli/repeatable_options.rb +0 -84
  38. data/lib/space_core/cli/repo.rb +0 -76
  39. data/lib/space_core/cli/run.rb +0 -44
  40. data/lib/space_core/cli/shell.rb +0 -125
  41. data/lib/space_core/cli/show.rb +0 -21
  42. data/lib/space_core/cli/status.rb +0 -74
  43. data/lib/space_core/cli/use.rb +0 -17
  44. data/lib/space_core/cli.rb +0 -177
  45. data/lib/space_core/commands.rb +0 -51
  46. data/lib/space_core/config.rb +0 -152
  47. data/lib/space_core/errors.rb +0 -14
  48. data/lib/space_core/git_client.rb +0 -49
  49. data/lib/space_core/mise_client.rb +0 -37
  50. data/lib/space_core/oci_builder.rb +0 -56
  51. data/lib/space_core/oci_packer.rb +0 -103
  52. data/lib/space_core/oci_runner.rb +0 -89
  53. data/lib/space_core/paths.rb +0 -58
  54. data/lib/space_core/repo_reference.rb +0 -19
  55. data/lib/space_core/repo_resolver.rb +0 -167
  56. data/lib/space_core/shell_integration.rb +0 -438
  57. data/lib/space_core/slugger.rb +0 -16
  58. data/lib/space_core/space.rb +0 -174
  59. data/lib/space_core/space_store.rb +0 -319
  60. data/lib/space_core/state.rb +0 -86
  61. data/lib/space_core/templates/oci/dockerfile.erb +0 -73
  62. data/lib/space_core/templates/oci/dockerignore.erb +0 -17
  63. data/lib/space_core/templates/oci/entrypoint.sh.erb +0 -16
  64. data/lib/space_core/terminal.rb +0 -161
  65. data/lib/space_core/warnings.rb +0 -13
  66. data/lib/space_core/xdg.rb +0 -33
  67. data/lib/space_core.rb +0 -26
  68. data/lib/space_src/cli/clone.rb +0 -54
  69. data/lib/space_src/cli/config.rb +0 -65
  70. data/lib/space_src/cli/daemon.rb +0 -362
  71. data/lib/space_src/cli/options.rb +0 -21
  72. data/lib/space_src/cli/org.rb +0 -199
  73. data/lib/space_src/cli/repo.rb +0 -169
  74. data/lib/space_src/cli/shell.rb +0 -121
  75. data/lib/space_src/cli/status.rb +0 -75
  76. data/lib/space_src/cli/sync.rb +0 -161
  77. data/lib/space_src/cli.rb +0 -159
  78. data/lib/space_src/cloner.rb +0 -77
  79. data/lib/space_src/config/contract.rb +0 -54
  80. data/lib/space_src/config/duration.rb +0 -79
  81. data/lib/space_src/config/model.rb +0 -49
  82. data/lib/space_src/config/store.rb +0 -156
  83. data/lib/space_src/forge/client.rb +0 -31
  84. data/lib/space_src/forge/github.rb +0 -98
  85. data/lib/space_src/launchd/agent.rb +0 -195
  86. data/lib/space_src/launchd/plist.rb +0 -129
  87. data/lib/space_src/log_rotator.rb +0 -46
  88. data/lib/space_src/migration.rb +0 -43
  89. data/lib/space_src/nav.rb +0 -99
  90. data/lib/space_src/paths.rb +0 -72
  91. data/lib/space_src/scm/client.rb +0 -87
  92. data/lib/space_src/scm/git.rb +0 -232
  93. data/lib/space_src/scm/status.rb +0 -24
  94. data/lib/space_src/shell.rb +0 -90
  95. data/lib/space_src/shell_integration.rb +0 -321
  96. data/lib/space_src/state/lock.rb +0 -59
  97. data/lib/space_src/state/store.rb +0 -140
  98. data/lib/space_src/sync/engine.rb +0 -503
  99. data/lib/space_src/sync/repo_plan.rb +0 -215
  100. data/lib/space_src/sync/report.rb +0 -18
  101. data/lib/space_src/ui/interactive_reporter.rb +0 -280
  102. data/lib/space_src/ui/json_reporter.rb +0 -39
  103. data/lib/space_src/ui/mode.rb +0 -68
  104. data/lib/space_src/ui/plain_reporter.rb +0 -53
  105. data/lib/space_src/ui/reporter.rb +0 -48
  106. data/lib/space_src/version.rb +0 -7
  107. data/lib/space_src.rb +0 -38
@@ -1,59 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "fileutils"
4
-
5
- module Space::Src
6
- module State
7
- # Advisory process-level lock on a sidecar file derived from the
8
- # state file path. Serializes overlapping `sync` runs so the
9
- # later run bails cleanly rather than clobbering the in-flight
10
- # run's state with a last-writer-wins atomic rename (CF10).
11
- #
12
- # The lockfile is a persistent zero-byte sentinel — never unlinked.
13
- # Deleting a flock'd file while another fd holds it creates a race
14
- # where both processes think they hold the lock on different inodes.
15
- #
16
- # `LOCK_NB` is for daemon safety, not reactor yielding: a blocking
17
- # `LOCK_EX` would wedge a launchd tick forever behind a hung run.
18
- # `flock` and the file I/O it guards are ordinary blocking syscalls
19
- # with no Async scheduler hook — fine here, they're sub-millisecond
20
- # on a local state dir, same as the rest of `State::Store`.
21
- class Lock
22
- NOT_ACQUIRED = :not_acquired
23
-
24
- # Returns the sidecar lockfile path for a given state_file path.
25
- def self.path_for(state_file)
26
- "#{state_file}.lock"
27
- end
28
-
29
- # Acquires an exclusive non-blocking advisory lock on the sidecar
30
- # lockfile. Creates the file (and its parent directory) if missing.
31
- #
32
- # Yields to the block and returns its value when the lock is
33
- # acquired; releases the lock in an `ensure` so it is freed on
34
- # normal return, explicit `return`, AND any escaping exception
35
- # (including Interrupt / SignalException).
36
- #
37
- # Returns `NOT_ACQUIRED` *without* yielding when another process
38
- # already holds the lock. The caller decides what to do (e.g.
39
- # bail cleanly with a Success).
40
- def self.acquire(state_file)
41
- lock_path = path_for(state_file)
42
- FileUtils.mkdir_p(File.dirname(lock_path))
43
- f = File.open(lock_path, File::RDWR | File::CREAT)
44
- # Everything that can raise — `flock` itself can (EINTR on a
45
- # signal, ENOLCK on some filesystems) — runs inside the `begin`,
46
- # so the `ensure` always closes the fd (no leak). Closing the fd
47
- # releases the lock (it lives on the open file description), so
48
- # no explicit LOCK_UN is needed — and `close` can't be skipped
49
- # by a raising unlock.
50
- begin
51
- return NOT_ACQUIRED unless f.flock(File::LOCK_EX | File::LOCK_NB)
52
- yield
53
- ensure
54
- f.close
55
- end
56
- end
57
- end
58
- end
59
- end
@@ -1,140 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "yaml"
4
- require "fileutils"
5
- require "time"
6
- require "dry/monads"
7
-
8
- module Space::Src
9
- module State
10
- # Machine-managed state at $XDG_STATE_HOME/space-src/state.yaml.
11
- # Never hand-edited (per PRD §3.2). Per-repo + per-org records with
12
- # a fixed status enum; the store validates the enum and timestamp
13
- # format on write.
14
- class Store
15
- extend Dry::Monads[:result]
16
-
17
- STATUSES = %w[clean dirty diverged detached wrong_branch missing error].freeze
18
-
19
- Repo = Data.define(:default_branch, :last_fetch_at, :last_synced_at, :status, :last_error) do
20
- def initialize(default_branch: nil, last_fetch_at: nil, last_synced_at: nil, status: nil, last_error: nil)
21
- super
22
- end
23
-
24
- def to_h_compact
25
- {
26
- "default_branch" => default_branch,
27
- "last_fetch_at" => format_time(last_fetch_at),
28
- "last_synced_at" => format_time(last_synced_at),
29
- "status" => status,
30
- "last_error" => last_error
31
- }.compact
32
- end
33
-
34
- private
35
-
36
- def format_time(t)
37
- t.respond_to?(:iso8601) ? t.iso8601 : t
38
- end
39
- end
40
-
41
- Org = Data.define(:last_listed_at, :repo_count, :last_error) do
42
- def initialize(last_listed_at: nil, repo_count: 0, last_error: nil)
43
- super
44
- end
45
-
46
- def to_h_compact
47
- {
48
- "last_listed_at" => format_time(last_listed_at),
49
- "repo_count" => repo_count,
50
- "last_error" => last_error
51
- }.compact
52
- end
53
-
54
- private
55
-
56
- # `last_listed_at` may arrive as a Time (fresh from the
57
- # engine) or as a String (round-tripped from YAML, since
58
- # `to_h_compact` always emits the iso8601 form on
59
- # write). Both forms are written as the same string on
60
- # the next emit; the helper accepts either.
61
- def format_time(t)
62
- return nil if t.nil?
63
- t.respond_to?(:iso8601) ? t.iso8601 : t
64
- end
65
- end
66
-
67
- def self.load(path)
68
- raw = read_yaml(path)
69
- Success(build_state(raw))
70
- end
71
-
72
- def self.write(path, state)
73
- validation = validate(state)
74
- return validation if validation.failure?
75
-
76
- FileUtils.mkdir_p(File.dirname(path))
77
- tmp = "#{path}.tmp.#{Process.pid}"
78
- begin
79
- File.write(tmp, emit(state))
80
- File.rename(tmp, path)
81
- ensure
82
- File.delete(tmp) if File.exist?(tmp)
83
- end
84
- Success(state)
85
- end
86
-
87
- def self.validate(state)
88
- state.repos.each do |key, repo|
89
- unless STATUSES.include?(repo.status)
90
- return Failure({repos: {key => {status: ["must be one of: #{STATUSES.join(", ")}"]}}})
91
- end
92
- end
93
- Success(state)
94
- end
95
-
96
- def self.read_yaml(path)
97
- return {} unless File.exist?(path)
98
- # Time class permitted because state.yaml stores ISO8601
99
- # timestamps; Psych will deserialize them as Time when the
100
- # scalar is tagged. No other arbitrary classes allowed.
101
- YAML.safe_load_file(path, permitted_classes: [Symbol, Time], aliases: false) || {}
102
- end
103
-
104
- def self.build_state(raw)
105
- repos = (raw["repos"] || {}).each_with_object({}) do |(key, attrs), acc|
106
- acc[key] = Repo.new(
107
- default_branch: attrs["default_branch"],
108
- last_fetch_at: attrs["last_fetch_at"],
109
- last_synced_at: attrs["last_synced_at"],
110
- status: attrs["status"],
111
- last_error: attrs["last_error"]
112
- )
113
- end
114
- orgs = (raw["orgs"] || {}).each_with_object({}) do |(key, attrs), acc|
115
- acc[key] = Org.new(
116
- last_listed_at: attrs["last_listed_at"],
117
- repo_count: attrs["repo_count"] || 0,
118
- last_error: attrs["last_error"]
119
- )
120
- end
121
- State.new(repos: repos, orgs: orgs)
122
- end
123
-
124
- # State value object — top-level container.
125
- State = Data.define(:repos, :orgs) do
126
- def initialize(repos: {}, orgs: {})
127
- super
128
- end
129
- end
130
-
131
- def self.emit(state)
132
- payload = {
133
- "repos" => state.repos.each_with_object({}) { |(k, v), acc| acc[k] = v.to_h_compact },
134
- "orgs" => state.orgs.each_with_object({}) { |(k, v), acc| acc[k] = v.to_h_compact }
135
- }
136
- YAML.dump(payload, line_width: -1)
137
- end
138
- end
139
- end
140
- end