space-architect 5.4.0 → 5.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +85 -0
- data/lib/space_architect/architect_project.rb +21 -11
- data/lib/space_architect/cli/architect.rb +7 -2
- data/lib/space_architect/session_sync/plist.rb +15 -9
- data/lib/space_core/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 71935f28a32069832af5b4d25f368d33c7efc505267a98c4f4b49f75a0084861
|
|
4
|
+
data.tar.gz: 2b067fdbdd5a950b204ef9fb16a3484a634e0b6acd2f9ca467e8f47adec29902
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 66a47fc8027809c4132ba9e5365694ab530d646be68b0cacec80d527e81dcb28f7d192afb4e76ea1e9b0489bada20013af2c32da066075e28f39bb407fc89243
|
|
7
|
+
data.tar.gz: f10a02847f4dafb408b98eb6a79f4b911bc0db7bf1856fbef18b751928f2e824ee5a5596db82ca94641f2fa5f55b037a671d9fed4daf0c8b54a374735b5d4baf
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,91 @@ 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
|
+
## [5.5.1] - 2026-07-25
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **Lane in-bounds check no longer false-FAILs on dotfile directories** — the
|
|
13
|
+
`touch_set` matcher built its `fnmatch` flags without `File::FNM_DOTMATCH`, so
|
|
14
|
+
a `dir/**` touch glob never matched a path with a dot-prefixed segment. Any
|
|
15
|
+
lane creating `.github/workflows/ci.yml` *inside its own declared directory*
|
|
16
|
+
was reported out-of-bounds by `architect verify` check (d) and hard-blocked by
|
|
17
|
+
`architect integrate` ("wrote outside its declared touch set"), with no
|
|
18
|
+
override — the touch set is frozen at spec time, so neither side of the
|
|
19
|
+
comparison could be adjusted legitimately and the merges had to be done by
|
|
20
|
+
hand. `merge_lane!`'s conflict classification carried the identical blind spot;
|
|
21
|
+
both sites now share one `in_touch_set?` predicate so the flags cannot drift
|
|
22
|
+
again.
|
|
23
|
+
|
|
24
|
+
## [5.5.0] - 2026-07-22
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- **`architect jobs list` provenance columns** — the table gains Harness /
|
|
29
|
+
Model / Lane (sourced from the list JSON's existing fields), blank when an
|
|
30
|
+
older row lacks them, so parallel lanes are tellable apart at a glance.
|
|
31
|
+
- **Server: pagination on the jobs, runs, and conversations indexes** — 50
|
|
32
|
+
rows per page with a `?page=N` param and Prev/Next controls; runs and
|
|
33
|
+
conversations were previously unbounded fetches. Conversations order
|
|
34
|
+
newest-updated first, and the index still never loads messages.
|
|
35
|
+
- **Server: job re-run prefill** — `GET /jobs/new?from=<job id>` (owner-gated)
|
|
36
|
+
prefills the new-job form field-for-field from that job's spec via the same
|
|
37
|
+
mapping profile application uses; Jobs/Show gains a "Run again" link.
|
|
38
|
+
- **Server: embedded live run view** — the run stream (SSE + rendering) is
|
|
39
|
+
extracted into a shared `RunStream` component and embedded on Jobs/Show
|
|
40
|
+
whenever the job has a run, so watching a job live no longer requires
|
|
41
|
+
leaving the job page.
|
|
42
|
+
- **Server: browser cancel + honest canceled lifecycle** — Jobs/Show renders a
|
|
43
|
+
Cancel control while a job is queued/running; runs gain a distinct terminal
|
|
44
|
+
status `canceled(4)` (a canceled job's run no longer reports `failed`), and
|
|
45
|
+
a cancel arriving during the env-image build skips the container spawn (and
|
|
46
|
+
secret resolution) entirely.
|
|
47
|
+
|
|
48
|
+
### Changed
|
|
49
|
+
|
|
50
|
+
- **`architect sessions agent install` emits a launchd-proof plist** — the
|
|
51
|
+
agent's ProgramArguments now open with the installing interpreter's absolute
|
|
52
|
+
ruby path (`RbConfig.ruby`) ahead of the absolute bin path, and the agent
|
|
53
|
+
environment carries a homebrew-inclusive PATH, so launchd's bare PATH can no
|
|
54
|
+
longer resolve macOS system Ruby via an env shebang (LoadError, agent exit
|
|
55
|
+
1). Reinstall existing agents to migrate; wrapper-script workarounds are
|
|
56
|
+
obsolete.
|
|
57
|
+
- **`architect dispatch --as-job` requires `--job-model`** — the missing model
|
|
58
|
+
now fails fast at dispatch with an error naming the flag, instead of
|
|
59
|
+
composing a spec the server rejects with a late 422.
|
|
60
|
+
|
|
61
|
+
### Fixed
|
|
62
|
+
|
|
63
|
+
- **Server: bulk session syncs no longer strand conversations at pending** —
|
|
64
|
+
transient DB-infrastructure errors (pool timeout, connection loss) inside an
|
|
65
|
+
import now re-raise so the queue's retry re-fires them, instead of being
|
|
66
|
+
swallowed as permanent failures; import concurrency is bounded below the DB
|
|
67
|
+
connection pool so a bulk backfill can't starve it.
|
|
68
|
+
- **Server: NUL bytes can no longer fail imports or live persists** — one
|
|
69
|
+
shared scrub now covers the claude_code, codex, and pi importers and the
|
|
70
|
+
live run persistor (Postgres jsonb rejects NUL outright;
|
|
71
|
+
`PG::UntranslatableCharacter` was failing real claude_code transcripts with
|
|
72
|
+
NUL in tool-block content).
|
|
73
|
+
- **Server: executor hardening** — a requeued job's raw Redis stream is
|
|
74
|
+
deleted at execution start (a surviving stale stream silently corrupted the
|
|
75
|
+
transcript); truncated failure evidence is scrubbed back to valid UTF-8 when
|
|
76
|
+
the byte boundary splits a multibyte character; the default sandbox base
|
|
77
|
+
image is `space-claude-base:v1` everywhere (dev bins previously defaulted to
|
|
78
|
+
`debian:stable-slim`).
|
|
79
|
+
- **Server: models-proxy timeout covers key resolution** — `op://` key
|
|
80
|
+
resolution now runs inside the same 10s budget as the upstream fetch, and
|
|
81
|
+
the providers/models action only rescues the operation's own failure
|
|
82
|
+
classes, so genuine bugs propagate instead of masquerading as
|
|
83
|
+
`upstream_error`.
|
|
84
|
+
- **Server: `parent_session_id` is clearable** — a re-import whose content no
|
|
85
|
+
longer implies a parent now nulls the stored link instead of preserving it
|
|
86
|
+
forever.
|
|
87
|
+
- **Server: theme-aware transcript turn bands** — per-turn tint colors ride
|
|
88
|
+
CSS custom properties with light and dark definitions (bands were
|
|
89
|
+
dark-calibrated and near-invisible in light mode); a mid-fetch form edit no
|
|
90
|
+
longer gets clobbered by the pi-extension sync on the job/profile forms; the
|
|
91
|
+
generated pi extension places `compat` per-model per the upstream types.
|
|
92
|
+
|
|
8
93
|
## [5.4.0] - 2026-07-21
|
|
9
94
|
|
|
10
95
|
### Changed
|
|
@@ -42,6 +42,13 @@ module Space::Architect
|
|
|
42
42
|
# Hard per-gate timeout. Generous relative to the full suite (~55s).
|
|
43
43
|
DEFAULT_GATE_TIMEOUT = 900
|
|
44
44
|
|
|
45
|
+
# Flags for matching a changed path against a lane's touch_set globs.
|
|
46
|
+
# PATHNAME keeps a single `*` from crossing `/`; EXTGLOB enables `{a,b}`;
|
|
47
|
+
# DOTMATCH lets a glob reach dotfile segments, so a `dir/**` touch set covers
|
|
48
|
+
# `dir/.github/workflows/ci.yml` — the standard deliverable for a lane preparing
|
|
49
|
+
# a directory to become a repo root.
|
|
50
|
+
TOUCH_FNM = File::FNM_PATHNAME | File::FNM_EXTGLOB | File::FNM_DOTMATCH
|
|
51
|
+
|
|
45
52
|
# Legacy sentinel: worktree_add used to seed prompt.md with this placeholder
|
|
46
53
|
# (dropped — the blind-overwrite tripped harness read-before-write guards, #48).
|
|
47
54
|
# dispatch still refuses to launch on this content, so stubs in old spaces
|
|
@@ -404,10 +411,7 @@ module Space::Architect
|
|
|
404
411
|
git_capture("-C", repo_path.to_s, "merge", "--abort")
|
|
405
412
|
conflict_files = conflicts.split
|
|
406
413
|
lane_touch_set = lane_entry["touch_set"] || []
|
|
407
|
-
|
|
408
|
-
outside = conflict_files.reject do |f|
|
|
409
|
-
lane_touch_set.any? { |g| File.fnmatch(g, f, fnm) || (g.end_with?("/**") && File.fnmatch("#{g}/*", f, fnm)) }
|
|
410
|
-
end
|
|
414
|
+
outside = conflict_files.reject { |f| in_touch_set?(f, lane_touch_set) }
|
|
411
415
|
if !lane_touch_set.empty? && outside.empty?
|
|
412
416
|
raise Space::Core::Error,
|
|
413
417
|
"Merge conflict integrating lane '#{lane}' (#{conflict_files.join(", ")}) — the lane plan was " \
|
|
@@ -939,6 +943,7 @@ module Space::Architect
|
|
|
939
943
|
resolve_dispatch_harness(lane_entry, model: model, harness: harness, effort: effort, force: force, err: err)
|
|
940
944
|
raise Space::Core::Error, "--as-job only supports the claude-code harness (lane '#{lane}' resolves to '#{resolved_harness}')" \
|
|
941
945
|
unless resolved_harness == "claude-code"
|
|
946
|
+
raise Space::Core::Error, "--job-model is required with --as-job" unless job_model
|
|
942
947
|
|
|
943
948
|
id = iteration_id(entry)
|
|
944
949
|
wt_path = space.path.join(lane_entry["worktree"] || "build/#{id}-#{lane}/wt")
|
|
@@ -1297,18 +1302,23 @@ module Space::Architect
|
|
|
1297
1302
|
i += 1
|
|
1298
1303
|
changed << orig if orig && !orig.empty?
|
|
1299
1304
|
end
|
|
1300
|
-
|
|
1301
|
-
changed.all? do |f|
|
|
1302
|
-
touch_set.any? do |g|
|
|
1303
|
-
File.fnmatch(g, f, fnm) ||
|
|
1304
|
-
(g.end_with?("/**") && File.fnmatch("#{g}/*", f, fnm))
|
|
1305
|
-
end
|
|
1306
|
-
end
|
|
1305
|
+
changed.all? { |f| in_touch_set?(f, touch_set) }
|
|
1307
1306
|
end
|
|
1308
1307
|
|
|
1309
1308
|
checks
|
|
1310
1309
|
end
|
|
1311
1310
|
|
|
1311
|
+
# Is a changed path inside a lane's declared touch set? Single-sourced so the
|
|
1312
|
+
# in-bounds check (d) and merge_lane!'s conflict classification can never drift.
|
|
1313
|
+
# A trailing `dir/**` is matched twice: bare (PATHNAME stops it at direct
|
|
1314
|
+
# children) and as `dir/**/*`, whose whole-component `**/` does cross `/`.
|
|
1315
|
+
def in_touch_set?(path, globs)
|
|
1316
|
+
globs.any? do |g|
|
|
1317
|
+
File.fnmatch(g, path, TOUCH_FNM) ||
|
|
1318
|
+
(g.end_with?("/**") && File.fnmatch("#{g}/*", path, TOUCH_FNM))
|
|
1319
|
+
end
|
|
1320
|
+
end
|
|
1321
|
+
|
|
1312
1322
|
# Replace (or, with append:, extend) the body of a "## Heading" section, leaving
|
|
1313
1323
|
# every other section byte-untouched. Append replaces a placeholder body (only a
|
|
1314
1324
|
# template comment) the first time, then stacks subsections after it. A supplied
|
|
@@ -349,6 +349,7 @@ module Space::Architect
|
|
|
349
349
|
if as_job
|
|
350
350
|
Jobs.require_credentials!(host, token)
|
|
351
351
|
raise Space::Core::Error, "--backend-url is required with --as-job" unless backend_url
|
|
352
|
+
raise Space::Core::Error, "--job-model is required with --as-job" unless job_model
|
|
352
353
|
raise Space::Core::Error, "--as-job cannot be combined with --push-url/--push-token/--push-host/--detach" \
|
|
353
354
|
if push_url || push_token || push_host || detach
|
|
354
355
|
end
|
|
@@ -884,8 +885,11 @@ module Space::Architect
|
|
|
884
885
|
if jobs.empty?
|
|
885
886
|
terminal.say "No jobs"
|
|
886
887
|
else
|
|
887
|
-
rows = jobs.map
|
|
888
|
-
|
|
888
|
+
rows = jobs.map do |j|
|
|
889
|
+
[j["id"], j["status"], j["run_id"], j["created_at"],
|
|
890
|
+
j["harness"], j["model"], j.dig("provenance", "lane")]
|
|
891
|
+
end
|
|
892
|
+
terminal.say terminal.table(%w[ID Status RunID CreatedAt Harness Model Lane], rows)
|
|
889
893
|
end
|
|
890
894
|
CLI.record_outcome(Outcome.new(exit_code: 0))
|
|
891
895
|
end
|
|
@@ -1012,6 +1016,7 @@ module Space::Architect
|
|
|
1012
1016
|
refresh_interval: interval.to_i,
|
|
1013
1017
|
log_dir: SessionSync.log_dir,
|
|
1014
1018
|
bin_path: SessionSync::BinPath.detect,
|
|
1019
|
+
ruby_bin: RbConfig.ruby,
|
|
1015
1020
|
host: host,
|
|
1016
1021
|
env: {SessionSync::TOKEN_ENV => resolved_token}
|
|
1017
1022
|
)
|
|
@@ -3,12 +3,15 @@
|
|
|
3
3
|
module Space::Architect
|
|
4
4
|
module SessionSync
|
|
5
5
|
# Hand-rolled launchd plist emitter for the session-sync agent, imitating
|
|
6
|
-
# Space::Src::Launchd::Plist's shape.
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
6
|
+
# Space::Src::Launchd::Plist's shape. Under launchd's bare PATH, a bare
|
|
7
|
+
# env-relative ruby binstub shebang resolves to macOS system Ruby instead
|
|
8
|
+
# of the caller's toolchain, so ProgramArguments names the interpreter
|
|
9
|
+
# explicitly (ruby_bin) rather than relying on shebang resolution — no
|
|
10
|
+
# mise wrapping needed, since the sync itself pins no toolchain at run
|
|
11
|
+
# time.
|
|
11
12
|
class Plist
|
|
13
|
+
DEFAULT_PATH = "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin"
|
|
14
|
+
|
|
12
15
|
HEADER = <<~XML
|
|
13
16
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
14
17
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
@@ -22,15 +25,17 @@ module Space::Architect
|
|
|
22
25
|
# @param refresh_interval [Integer] StartInterval in seconds (must be > 0).
|
|
23
26
|
# @param log_dir [String] Absolute directory for stdout/stderr logs.
|
|
24
27
|
# @param bin_path [String] Absolute path to the architect bin script.
|
|
28
|
+
# @param ruby_bin [String] Absolute path to the ruby to run bin_path under.
|
|
25
29
|
# @param host [String] --host value passed to `sessions sync`.
|
|
26
30
|
# @param env [Hash] EnvironmentVariables dict; must contain a non-empty
|
|
27
31
|
# SessionSync::TOKEN_ENV value (resolved by the caller —
|
|
28
|
-
# never resolved here).
|
|
32
|
+
# never resolved here). PATH defaults to DEFAULT_PATH
|
|
33
|
+
# unless the caller supplies its own.
|
|
29
34
|
# @return [String] the full plist XML.
|
|
30
|
-
def call(label:, refresh_interval:, log_dir:, bin_path:, host:, env:)
|
|
35
|
+
def call(label:, refresh_interval:, log_dir:, bin_path:, ruby_bin:, host:, env:)
|
|
31
36
|
raise ArgumentError, "label is required" if label.to_s.empty?
|
|
32
37
|
raise ArgumentError, "refresh_interval must be > 0" unless refresh_interval.is_a?(Integer) && refresh_interval > 0
|
|
33
|
-
%w[log_dir bin_path].each do |k|
|
|
38
|
+
%w[log_dir bin_path ruby_bin].each do |k|
|
|
34
39
|
v = binding.local_variable_get(k)
|
|
35
40
|
raise ArgumentError, "#{k} must be absolute (got #{v.inspect})" unless v.is_a?(String) && File.absolute_path?(v)
|
|
36
41
|
end
|
|
@@ -43,12 +48,13 @@ module Space::Architect
|
|
|
43
48
|
body = +""
|
|
44
49
|
body << key("Label") << string(label) << "\n"
|
|
45
50
|
body << key("ProgramArguments") << "\n" << array([
|
|
51
|
+
ruby_bin,
|
|
46
52
|
bin_path,
|
|
47
53
|
"sessions",
|
|
48
54
|
"sync",
|
|
49
55
|
"--host", host
|
|
50
56
|
])
|
|
51
|
-
body << key("EnvironmentVariables") << "\n" << dict(env)
|
|
57
|
+
body << key("EnvironmentVariables") << "\n" << dict({"PATH" => DEFAULT_PATH}.merge(env.to_h))
|
|
52
58
|
body << key("StartInterval") << integer(refresh_interval) << "\n"
|
|
53
59
|
body << key("RunAtLoad") << boolean(true) << "\n"
|
|
54
60
|
body << key("ProcessType") << string("Background") << "\n"
|
data/lib/space_core/version.rb
CHANGED