space-architect 9.1.0 → 9.1.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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 154e74a35cc7d14274f2f6fec65f40400727adbf3ce4bcf69000c2f00405a07e
|
|
4
|
+
data.tar.gz: 2d6407b32a61f8c056e866e12dceab65b6a0b35f281ec6b1721c73233517da49
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: db472c7ffcff0c4228f9c6bda1baad0c00cb0283defa3a686924267d51d252b9f24e3cdf65a8d50076f0354f5af1894ff6ff9799c2b783069a8bf52567206c2b
|
|
7
|
+
data.tar.gz: 444e06a1bbcbefe3f827a065cefba6383d04606215abf0ab016069f7b254a8c61d2343aa91a648a3a2be92ae1a47c97b18dbcdc63bb62a92d23c63b3b8b10e6f
|
|
@@ -929,12 +929,21 @@ module Space::Architect
|
|
|
929
929
|
base_explicit = !base.nil?
|
|
930
930
|
refuse_repoint_if_dirty!(iteration, lanes, base, iteration_id(entry)) if base_explicit && !force
|
|
931
931
|
|
|
932
|
+
# Sync-or-refuse ONCE per distinct repo of the selected set (lane order,
|
|
933
|
+
# first occurrence) BEFORE any worktree is cut — a divergence aborts the
|
|
934
|
+
# entire provision. An explicit --base is the deliberate case (#88): the
|
|
935
|
+
# pin is cut exactly as given, no sync, no divergence refusal.
|
|
936
|
+
synced = {}
|
|
937
|
+
unless base_explicit
|
|
938
|
+
lanes.map { |l| l["repo"] }.uniq.each { |repo| synced[repo] = sync_integration_branch!(repo, iteration) }
|
|
939
|
+
end
|
|
940
|
+
|
|
932
941
|
lanes.map do |l|
|
|
933
942
|
name = l["name"]
|
|
934
943
|
result = worktree_add(l["repo"], iteration, name, base: resolve_lane_base(l["repo"], base),
|
|
935
944
|
base_explicit: base_explicit, force: force, **recorded_lane_fields(l))
|
|
936
945
|
{ lane: name, worktree: result[:worktree], base_sha: result[:base_sha], outcome: result[:outcome],
|
|
937
|
-
discarded: result[:discarded] }
|
|
946
|
+
discarded: result[:discarded], synced: synced[l["repo"]] }
|
|
938
947
|
end
|
|
939
948
|
end
|
|
940
949
|
|
|
@@ -1622,6 +1631,15 @@ module Space::Architect
|
|
|
1622
1631
|
wt_path = space.path.join(lane_entry["worktree"] || "build/#{iteration_id(entry)}-#{lane}/wt")
|
|
1623
1632
|
return lane_entry if wt_path.exist? && worktree_registered?(repo_path, wt_path)
|
|
1624
1633
|
|
|
1634
|
+
# Same sync-or-refuse as provision, immediately before the cut — a
|
|
1635
|
+
# dispatch/gate/integrate/verify that materializes a missing worktree must
|
|
1636
|
+
# never cut it from a stale integration tip. Already-materialized lanes
|
|
1637
|
+
# returned above and stay a true no-op. The inform goes on $stderr, like
|
|
1638
|
+
# worktree_add's own informs on this path; no-op syncs print nothing.
|
|
1639
|
+
report = sync_integration_branch!(lane_entry["repo"], iteration)
|
|
1640
|
+
$stderr.puts "#{report[:repo]}: fast-forwarded #{report[:branch]} to #{report[:sha][0, 8]} — " \
|
|
1641
|
+
"synced before materializing #{iteration}/#{lane}" if report
|
|
1642
|
+
|
|
1625
1643
|
worktree_add(lane_entry["repo"], iteration, lane, base: resolve_lane_base(lane_entry["repo"], nil),
|
|
1626
1644
|
base_explicit: false, **recorded_lane_fields(lane_entry))
|
|
1627
1645
|
(slice_entry(iteration)["lanes"] || []).find { |l| l["name"] == lane }
|
|
@@ -1735,6 +1753,57 @@ module Space::Architect
|
|
|
1735
1753
|
st.success? && !out.strip.empty? ? out.strip : "HEAD"
|
|
1736
1754
|
end
|
|
1737
1755
|
|
|
1756
|
+
# Fast-forward the repo's project integration branch from its default branch
|
|
1757
|
+
# BEFORE any lane worktree is cut from it (I06 provision-sync — the I05 burn:
|
|
1758
|
+
# a worktree cut from a stale project/<slug> tip). Local-to-local only — no
|
|
1759
|
+
# fetch/pull, no remote-tracking ref is read or written. Compare before moving:
|
|
1760
|
+
# default not ahead → no-op (nil, callers print nothing); default ahead and
|
|
1761
|
+
# integration not ahead → fast-forward; both ahead → refuse (a human merges
|
|
1762
|
+
# deliberately, or pins --base) — --force has no bypass. Bang: it raises.
|
|
1763
|
+
# Returns nil for a no-op, or { repo:, branch:, sha: } for the moved ref so
|
|
1764
|
+
# callers can report the movement (never silently move a branch).
|
|
1765
|
+
def sync_integration_branch!(repo, iteration)
|
|
1766
|
+
repo_path = space.path.join("repos", repo)
|
|
1767
|
+
integration = project_integration_branch
|
|
1768
|
+
return nil unless branch_exists?(repo_path, integration)
|
|
1769
|
+
|
|
1770
|
+
default = repo_default_branch(repo_path)
|
|
1771
|
+
counts_out, counts_err, counts_st = git_capture("-C", repo_path.to_s, "rev-list", "--left-right", "--count",
|
|
1772
|
+
"#{default}...#{integration}")
|
|
1773
|
+
unless counts_st.success?
|
|
1774
|
+
raise Space::Core::Error, "Cannot compare #{default}...#{integration} in repos/#{repo} — #{counts_err.strip}"
|
|
1775
|
+
end
|
|
1776
|
+
|
|
1777
|
+
default_ahead, integration_ahead = counts_out.strip.split.map(&:to_i)
|
|
1778
|
+
return nil if default_ahead.zero?
|
|
1779
|
+
|
|
1780
|
+
default_tip, = git_capture("-C", repo_path.to_s, "rev-parse", default)
|
|
1781
|
+
integration_tip, = git_capture("-C", repo_path.to_s, "rev-parse", integration)
|
|
1782
|
+
if integration_ahead.positive?
|
|
1783
|
+
raise Space::Core::Error,
|
|
1784
|
+
"Refusing to provision '#{iteration}' — repos/#{repo} has diverged: #{default} (#{default_tip[0, 8]}) and " \
|
|
1785
|
+
"#{integration} (#{integration_tip[0, 8]}) each hold commits the other lacks " \
|
|
1786
|
+
"(#{default} ahead by #{default_ahead}, #{integration} ahead by #{integration_ahead}) — not fast-forwardable. " \
|
|
1787
|
+
"A human must merge #{default} into #{integration} deliberately, or pin the base explicitly with " \
|
|
1788
|
+
"`architect provision #{iteration} --base <sha>`. --force does not bypass this refusal."
|
|
1789
|
+
end
|
|
1790
|
+
|
|
1791
|
+
head_out, _, head_st = git_capture("-C", repo_path.to_s, "symbolic-ref", "--short", "HEAD")
|
|
1792
|
+
if head_st.success? && head_out.strip == integration
|
|
1793
|
+
# The integration branch is the clone's checked-out branch (the real
|
|
1794
|
+
# post-integrate state — merge_lane! checks it out) — fetch refuses a
|
|
1795
|
+
# checked-out dst, so fast-forward via merge instead.
|
|
1796
|
+
git_run("-C", repo_path.to_s, "merge", "--ff-only", default)
|
|
1797
|
+
else
|
|
1798
|
+
# Unprefixed refspecs are ff-only by construction — git rejects a non-ff
|
|
1799
|
+
# update here, so the branch can only ever move forward.
|
|
1800
|
+
git_run("-C", repo_path.to_s, "fetch", ".", "#{default}:#{integration}")
|
|
1801
|
+
end
|
|
1802
|
+
|
|
1803
|
+
new_tip, = git_capture("-C", repo_path.to_s, "rev-parse", integration)
|
|
1804
|
+
{ repo: repo, branch: integration, sha: new_tip.strip }
|
|
1805
|
+
end
|
|
1806
|
+
|
|
1738
1807
|
def slice_entry(iteration)
|
|
1739
1808
|
block = space.data["project"] || {}
|
|
1740
1809
|
entry = (block["iterations"] || []).find { |s| s["name"] == iteration }
|
|
@@ -562,7 +562,7 @@ module Space::Architect
|
|
|
562
562
|
phase 20, "Build"
|
|
563
563
|
argument :iteration, required: true, desc: "Iteration name"
|
|
564
564
|
argument :space, required: false, desc: "Space identifier (default: $PWD)"
|
|
565
|
-
option :base, default: nil, desc: "Base ref override (default: project/<slug>
|
|
565
|
+
option :base, default: nil, desc: "Base ref override (default: project/<slug> fast-forward-synced from the repo's default branch first — diverged → refuse; --base skips the sync and cuts exactly the given ref)"
|
|
566
566
|
option :lane, default: nil, desc: "Provision only this lane (default: all declared lanes)"
|
|
567
567
|
option :force, type: :boolean, default: false, desc: "Clear and re-create a stale (unregistered) worktree directory"
|
|
568
568
|
|
|
@@ -575,6 +575,12 @@ module Space::Architect
|
|
|
575
575
|
if results.empty?
|
|
576
576
|
terminal.say "No declared lanes to provision for '#{iteration}'"
|
|
577
577
|
else
|
|
578
|
+
# Sync report first (the sync ran before any cut): one line per
|
|
579
|
+
# moved branch — repo, branch, new short tip. No-op syncs are
|
|
580
|
+
# nil here and print nothing.
|
|
581
|
+
results.filter_map { |r| r[:synced] }.uniq.each do |s|
|
|
582
|
+
terminal.say "#{s[:repo]}: fast-forwarded #{s[:branch]} to #{s[:sha][0, 8]}"
|
|
583
|
+
end
|
|
578
584
|
results.each do |r|
|
|
579
585
|
state = case r[:outcome]
|
|
580
586
|
when :created then "created"
|
|
@@ -17,9 +17,9 @@ module Space::Architect
|
|
|
17
17
|
# XDG_CONFIG_HOME); space-local is architect.yaml beside the loaded
|
|
18
18
|
# space.yaml (the CLI already resolved the space — no walk-up).
|
|
19
19
|
module Routing
|
|
20
|
-
ROLES = %w[architect spec builder judge coordinator].freeze
|
|
21
|
-
# The
|
|
22
|
-
REQUIRED_ROLES = %w[spec builder judge coordinator].freeze
|
|
20
|
+
ROLES = %w[architect spec builder judge coordinator narrator].freeze
|
|
21
|
+
# The five agent roles that must be routed after the merge; `architect` is optional.
|
|
22
|
+
REQUIRED_ROLES = %w[spec builder judge coordinator narrator].freeze
|
|
23
23
|
ENTRY_KEYS = %w[model thinking].freeze
|
|
24
24
|
|
|
25
25
|
def self.global_path
|
|
@@ -34,7 +34,7 @@ module Space::Architect
|
|
|
34
34
|
|
|
35
35
|
# Parse one file's text into { role => {"model" => id, "thinking" => level|nil} }.
|
|
36
36
|
# +path+ is used only in error messages. Frozen v1 rules: integer version 1,
|
|
37
|
-
# the
|
|
37
|
+
# the six known roles, entries ⊆ {model, thinking} with a non-empty model,
|
|
38
38
|
# duplicate mapping keys rejected (psych's plain load silently keeps the
|
|
39
39
|
# last — the npm yaml the extension parses with throws), aliases accepted,
|
|
40
40
|
# a trailing :level suffix on model parsed via Harness.parse_model_suffix
|