space-architect 1.3.0 → 2.0.0.rc2
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 +103 -0
- data/README.md +267 -158
- data/exe/architect +1 -1
- data/exe/space +2 -2
- data/exe/src +13 -0
- data/lib/space_architect/{architect_mission.rb → architect_project.rb} +475 -125
- data/lib/space_architect/cli/architect.rb +248 -178
- data/lib/space_architect/cli/research.rb +94 -0
- data/lib/space_architect/cli/space.rb +25 -31
- data/lib/space_architect/cli/src.rb +20 -14
- data/lib/space_architect/cli.rb +22 -22
- data/lib/space_architect/dispatcher.rb +5 -1
- data/lib/space_architect/gate_evaluator.rb +65 -0
- data/lib/space_architect/gate_lint.rb +140 -0
- data/lib/space_architect/harness.rb +145 -17
- data/lib/space_architect/research/mux.rb +127 -0
- data/lib/space_architect/research/registry.rb +70 -0
- data/lib/space_architect/research/renderer.rb +101 -0
- data/lib/space_architect/research/run.rb +7 -0
- data/lib/space_architect/research/supervisor.rb +108 -0
- data/lib/space_architect/research.rb +13 -0
- data/lib/space_architect/run_creator.rb +53 -0
- data/lib/space_architect/skill_installer.rb +81 -79
- data/lib/space_architect/templates/architect.md.erb +15 -4
- data/lib/space_architect/templates/brief.md.erb +5 -5
- data/lib/space_architect/templates/iteration.md.erb +17 -6
- data/lib/space_architect.rb +8 -21
- data/lib/{space_architect → space_core}/atomic_write.rb +1 -1
- data/lib/space_core/cli/base_command.rb +19 -0
- data/lib/space_core/cli/build.rb +27 -0
- data/lib/space_core/cli/config.rb +49 -0
- data/lib/space_core/cli/current.rb +16 -0
- data/lib/space_core/cli/help.rb +123 -0
- data/lib/space_core/cli/helpers.rb +115 -0
- data/lib/space_core/cli/init.rb +29 -0
- data/lib/space_core/cli/list.rb +24 -0
- data/lib/space_core/cli/new.rb +38 -0
- data/lib/space_core/cli/pack.rb +29 -0
- data/lib/space_core/cli/path.rb +16 -0
- data/lib/space_core/cli/repeatable_options.rb +75 -0
- data/lib/space_core/cli/repo.rb +76 -0
- data/lib/space_core/cli/run.rb +29 -0
- data/lib/space_core/cli/shell.rb +125 -0
- data/lib/space_core/cli/show.rb +21 -0
- data/lib/space_core/cli/status.rb +33 -0
- data/lib/space_core/cli/use.rb +17 -0
- data/lib/space_core/cli.rb +177 -0
- data/lib/{space_architect → space_core}/config.rb +1 -1
- data/lib/{space_architect → space_core}/errors.rb +1 -1
- data/lib/{space_architect → space_core}/git_client.rb +1 -1
- data/lib/{space_architect → space_core}/mise_client.rb +1 -1
- data/lib/space_core/oci_builder.rb +56 -0
- data/lib/space_core/oci_packer.rb +99 -0
- data/lib/space_core/oci_runner.rb +73 -0
- data/lib/{space_architect → space_core}/repo_reference.rb +1 -1
- data/lib/{space_architect → space_core}/repo_resolver.rb +1 -1
- data/lib/{space_architect → space_core}/shell_integration.rb +1 -1
- data/lib/{space_architect → space_core}/slugger.rb +1 -1
- data/lib/{space_architect → space_core}/space.rb +11 -3
- data/lib/{space_architect → space_core}/space_store.rb +13 -13
- data/lib/{space_architect → space_core}/state.rb +1 -1
- data/lib/space_core/templates/oci/dockerfile.erb +63 -0
- data/lib/space_core/templates/oci/dockerignore.erb +17 -0
- data/lib/space_core/templates/oci/entrypoint.sh.erb +10 -0
- data/lib/{space_architect → space_core}/terminal.rb +1 -1
- data/lib/space_core/version.rb +7 -0
- data/lib/{space_architect → space_core}/warnings.rb +1 -1
- data/lib/{space_architect → space_core}/xdg.rb +1 -1
- data/lib/space_core.rb +24 -0
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/cli/clone.rb +5 -5
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/cli/config.rb +7 -7
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/cli/daemon.rb +46 -30
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/cli/options.rb +1 -1
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/cli/org.rb +9 -9
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/cli/repo.rb +9 -9
- data/lib/space_src/cli/shell.rb +122 -0
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/cli/status.rb +7 -7
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/cli/sync.rb +17 -17
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/cli.rb +42 -11
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/cloner.rb +3 -3
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/config/contract.rb +1 -1
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/config/duration.rb +1 -1
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/config/model.rb +1 -1
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/config/store.rb +5 -5
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/forge/client.rb +2 -2
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/forge/github.rb +4 -4
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/launchd/agent.rb +5 -5
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/launchd/plist.rb +3 -3
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/log_rotator.rb +1 -1
- data/lib/space_src/migration.rb +43 -0
- data/lib/space_src/nav.rb +98 -0
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/paths.rb +2 -2
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/scm/client.rb +1 -1
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/scm/git.rb +4 -4
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/scm/status.rb +1 -1
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/shell.rb +1 -1
- data/lib/space_src/shell_integration.rb +321 -0
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/state/lock.rb +1 -1
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/state/store.rb +2 -2
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/sync/engine.rb +12 -12
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/sync/repo_plan.rb +3 -3
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/ui/interactive_reporter.rb +1 -1
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/ui/json_reporter.rb +1 -1
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/ui/mode.rb +1 -1
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/ui/plain_reporter.rb +1 -1
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/ui/reporter.rb +1 -1
- data/{vendor/repo-tender/lib/space_architect/pristine → lib/space_src}/version.rb +2 -2
- data/lib/space_src.rb +37 -0
- data/skill/architect/SKILL.md +110 -54
- data/skill/architect/dispatch.md +147 -39
- data/skill/architect/research.md +46 -37
- data/skill/architect-research/SKILL.md +2 -2
- data/skill/architect-vocabulary/SKILL.md +24 -21
- metadata +127 -68
- data/lib/space_architect/cli/config.rb +0 -61
- data/lib/space_architect/cli/current.rb +0 -22
- data/lib/space_architect/cli/helpers.rb +0 -117
- data/lib/space_architect/cli/init.rb +0 -35
- data/lib/space_architect/cli/list.rb +0 -30
- data/lib/space_architect/cli/new.rb +0 -43
- data/lib/space_architect/cli/options.rb +0 -12
- data/lib/space_architect/cli/path.rb +0 -22
- data/lib/space_architect/cli/repo.rb +0 -88
- data/lib/space_architect/cli/shell.rb +0 -137
- data/lib/space_architect/cli/show.rb +0 -27
- data/lib/space_architect/cli/status.rb +0 -39
- data/lib/space_architect/cli/use.rb +0 -23
- data/lib/space_architect/version.rb +0 -5
- data/vendor/repo-tender/lib/space_architect/pristine.rb +0 -44
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "async/http/client"
|
|
4
|
+
require "async/http/endpoint"
|
|
5
|
+
require "json"
|
|
6
|
+
|
|
7
|
+
module Space::Architect
|
|
8
|
+
class RunCreator
|
|
9
|
+
def initialize(host, token, client: nil)
|
|
10
|
+
@host = host.chomp("/")
|
|
11
|
+
@token = token
|
|
12
|
+
@client = client
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# POSTs to /runs and returns the integer run id.
|
|
16
|
+
# Raises Space::Core::Error on any failure — never returns nil.
|
|
17
|
+
def create
|
|
18
|
+
Sync do
|
|
19
|
+
if @client
|
|
20
|
+
response = @client.post("/runs", headers: headers, body: nil)
|
|
21
|
+
parse_response(response)
|
|
22
|
+
else
|
|
23
|
+
Async::HTTP::Client.open(Async::HTTP::Endpoint.parse(@host)) do |c|
|
|
24
|
+
response = c.post("/runs", headers: headers, body: nil)
|
|
25
|
+
parse_response(response)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def headers
|
|
34
|
+
[
|
|
35
|
+
["authorization", "Bearer #{@token}"],
|
|
36
|
+
["content-type", "application/json"]
|
|
37
|
+
]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def parse_response(response)
|
|
41
|
+
status = response.status
|
|
42
|
+
body = response.read || ""
|
|
43
|
+
raise Space::Core::Error, "POST /runs failed (#{status}): #{body[0, 200]}" unless status == 201
|
|
44
|
+
|
|
45
|
+
parsed = JSON.parse(body)
|
|
46
|
+
id = parsed["id"]
|
|
47
|
+
raise Space::Core::Error, "POST /runs: missing or non-integer id in response: #{body[0, 200]}" \
|
|
48
|
+
unless id.is_a?(Integer)
|
|
49
|
+
|
|
50
|
+
id
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -3,103 +3,105 @@
|
|
|
3
3
|
require "fileutils"
|
|
4
4
|
require "pathname"
|
|
5
5
|
|
|
6
|
-
module
|
|
7
|
-
module
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def dest_root(provider, project:, env:, cwd: Dir.pwd)
|
|
16
|
-
case provider.to_s
|
|
17
|
-
when "claude"
|
|
18
|
-
base = project ? Pathname.new(cwd) : Pathname.new(XDG.home(env: env))
|
|
19
|
-
base.join(".claude", "skills")
|
|
20
|
-
when "codex"
|
|
21
|
-
base = project ? Pathname.new(cwd) : Pathname.new(XDG.home(env: env))
|
|
22
|
-
base.join(".agents", "skills")
|
|
23
|
-
when "opencode"
|
|
24
|
-
project ? Pathname.new(cwd).join(".opencode", "skills") : XDG.config_home(env: env).join("skills")
|
|
25
|
-
when "pi"
|
|
26
|
-
base = project ? Pathname.new(cwd) : Pathname.new(pi_agent_dir(env: env))
|
|
27
|
-
base.join("skills")
|
|
28
|
-
else
|
|
29
|
-
raise Error, "Unknown provider '#{provider}'. Expected one of: #{PROVIDERS.join(', ')}"
|
|
6
|
+
module Space
|
|
7
|
+
module Architect
|
|
8
|
+
module SkillInstaller
|
|
9
|
+
PROVIDERS = %w[claude codex opencode pi].freeze
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
def source_root
|
|
13
|
+
Pathname.new(__dir__).parent.parent.join("skill")
|
|
30
14
|
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def install(provider, project:, force:, env:, cwd: Dir.pwd, dry_run: false)
|
|
34
|
-
validate_provider!(provider)
|
|
35
|
-
dest = dest_root(provider, project: project, env: env, cwd: cwd)
|
|
36
|
-
results = []
|
|
37
15
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
16
|
+
def dest_root(provider, project:, env:, cwd: Dir.pwd)
|
|
17
|
+
case provider.to_s
|
|
18
|
+
when "claude"
|
|
19
|
+
base = project ? Pathname.new(cwd) : Pathname.new(Space::Core::XDG.home(env: env))
|
|
20
|
+
base.join(".claude", "skills")
|
|
21
|
+
when "codex"
|
|
22
|
+
base = project ? Pathname.new(cwd) : Pathname.new(Space::Core::XDG.home(env: env))
|
|
23
|
+
base.join(".agents", "skills")
|
|
24
|
+
when "opencode"
|
|
25
|
+
project ? Pathname.new(cwd).join(".opencode", "skills") : Space::Core::XDG.config_home(env: env).join("skills")
|
|
26
|
+
when "pi"
|
|
27
|
+
base = project ? Pathname.new(cwd) : Pathname.new(pi_agent_dir(env: env))
|
|
28
|
+
base.join("skills")
|
|
29
|
+
else
|
|
30
|
+
raise Space::Core::Error, "Unknown provider '#{provider}'. Expected one of: #{PROVIDERS.join(', ')}"
|
|
31
|
+
end
|
|
42
32
|
end
|
|
43
33
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
source_root.children.select(&:directory?)
|
|
49
|
-
end
|
|
34
|
+
def install(provider, project:, force:, env:, cwd: Dir.pwd, dry_run: false)
|
|
35
|
+
validate_provider!(provider)
|
|
36
|
+
dest = dest_root(provider, project: project, env: env, cwd: cwd)
|
|
37
|
+
results = []
|
|
50
38
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
39
|
+
source_skills.each do |skill_dir|
|
|
40
|
+
name = skill_dir.basename.to_s
|
|
41
|
+
skill_dest = dest.join(name)
|
|
42
|
+
results << install_skill(skill_dir, skill_dest, force: force, dry_run: dry_run)
|
|
43
|
+
end
|
|
55
44
|
|
|
56
|
-
|
|
57
|
-
|
|
45
|
+
{ dest_root: dest, skills: results, dry_run: dry_run }
|
|
46
|
+
end
|
|
58
47
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
48
|
+
def source_skills
|
|
49
|
+
source_root.children.select(&:directory?)
|
|
50
|
+
end
|
|
62
51
|
|
|
63
|
-
|
|
64
|
-
name = source.basename.to_s
|
|
52
|
+
private
|
|
65
53
|
|
|
66
|
-
|
|
67
|
-
if
|
|
68
|
-
return { name: name, action: :unchanged, path: dest }
|
|
69
|
-
end
|
|
54
|
+
def validate_provider!(provider)
|
|
55
|
+
return if PROVIDERS.include?(provider.to_s)
|
|
70
56
|
|
|
71
|
-
|
|
72
|
-
|
|
57
|
+
raise Space::Core::Error, "Unknown provider '#{provider}'. Expected one of: #{PROVIDERS.join(', ')}"
|
|
58
|
+
end
|
|
73
59
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
60
|
+
def pi_agent_dir(env:)
|
|
61
|
+
Pathname.new(env.fetch("PI_CODING_AGENT_DIR", File.join(Space::Core::XDG.home(env: env), ".pi", "agent")))
|
|
62
|
+
end
|
|
77
63
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
64
|
+
def install_skill(source, dest, force:, dry_run:)
|
|
65
|
+
name = source.basename.to_s
|
|
66
|
+
|
|
67
|
+
if dest.exist?
|
|
68
|
+
if same_content?(source, dest)
|
|
69
|
+
return { name: name, action: :unchanged, path: dest }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
unless force
|
|
73
|
+
return { name: name, action: :conflict, path: dest } if dry_run
|
|
74
|
+
|
|
75
|
+
raise Space::Core::Error,
|
|
76
|
+
"Refusing to overwrite existing skill at #{dest}. Re-run with --force."
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
unless dry_run
|
|
80
|
+
FileUtils.rm_rf(dest)
|
|
81
|
+
FileUtils.cp_r(source, dest)
|
|
82
|
+
end
|
|
83
|
+
{ name: name, action: dry_run ? :would_update : :updated, path: dest }
|
|
84
|
+
else
|
|
85
|
+
unless dry_run
|
|
86
|
+
FileUtils.mkdir_p(dest.parent)
|
|
87
|
+
FileUtils.cp_r(source, dest)
|
|
88
|
+
end
|
|
89
|
+
{ name: name, action: dry_run ? :would_install : :installed, path: dest }
|
|
87
90
|
end
|
|
88
|
-
{ name: name, action: dry_run ? :would_install : :installed, path: dest }
|
|
89
91
|
end
|
|
90
|
-
end
|
|
91
92
|
|
|
92
|
-
|
|
93
|
-
|
|
93
|
+
def same_content?(source, dest)
|
|
94
|
+
return false unless dest.directory?
|
|
94
95
|
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
source_files = Dir.glob("#{source}/**/*").reject { |f| File.directory?(f) }
|
|
97
|
+
dest_files = Dir.glob("#{dest}/**/*").reject { |f| File.directory?(f) }
|
|
97
98
|
|
|
98
|
-
|
|
99
|
+
return false if source_files.length != dest_files.length
|
|
99
100
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
source_files.sort.zip(dest_files.sort).all? do |sf, df|
|
|
102
|
+
rel = sf.sub("#{source}/", "")
|
|
103
|
+
df.end_with?(rel) && File.read(sf) == File.read(df)
|
|
104
|
+
end
|
|
103
105
|
end
|
|
104
106
|
end
|
|
105
107
|
end
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
> Cross-iteration table of contents for the Architect Loop. Per-iteration detail lives in
|
|
4
4
|
> architecture/I<NN>-<iteration>.md — this file only indexes the iterations and carries
|
|
5
|
-
>
|
|
5
|
+
> project-wide state. Keep it short (~150 lines): the next session must grok it
|
|
6
6
|
> in under a minute. Not in the committed architecture = didn't happen.
|
|
7
7
|
>
|
|
8
|
-
> Durable
|
|
9
|
-
> Create it with `architect brief new`. Edits to a §section are
|
|
8
|
+
> Durable project contract: `architecture/BRIEF.md` (numbered §sections, cited as BRIEF §N).
|
|
9
|
+
> Create it with `architect brief new`. Edits to a §section are project-scope decisions —
|
|
10
10
|
> log them in the Decisions log below, never as silent per-iteration drift.
|
|
11
11
|
|
|
12
12
|
## TL;DR (keep current)
|
|
@@ -34,13 +34,24 @@
|
|
|
34
34
|
|
|
35
35
|
## Iteration index
|
|
36
36
|
|
|
37
|
-
<!--
|
|
37
|
+
<!-- Rows are added here only for SCAFFOLDED iterations. `architect new <name>` allocates
|
|
38
|
+
the ordinal at spec-time and scaffolds architecture/I<NN>-<name>.md. Do NOT
|
|
39
|
+
pre-number planned work — un-numbered planned items live in the Backlog below. -->
|
|
38
40
|
|
|
39
41
|
| I# | Iteration | Status | freeze_sha | Integration branch | Verdict | File |
|
|
40
42
|
|----|-----------|--------|-----------|--------------------|---------|------|
|
|
41
43
|
|
|
42
44
|
Status values: speccing → frozen → dispatched → in-flight → awaiting-verdict → done.
|
|
43
45
|
|
|
46
|
+
## Backlog (ordered; no ordinal until `architect new`)
|
|
47
|
+
|
|
48
|
+
<!-- Next-up first. Each item gets its ordinal only when it is about to be specced:
|
|
49
|
+
run `architect new <name>` at that point to allocate the ordinal at spec-time and
|
|
50
|
+
scaffold the iteration file. Pre-numbering planned work forces renumber churn every
|
|
51
|
+
time priorities reshuffle. -->
|
|
52
|
+
|
|
53
|
+
- _[next planned item]_
|
|
54
|
+
|
|
44
55
|
## Open items for the human / architect
|
|
45
56
|
|
|
46
57
|
<!-- Blocking items: unresolved disagreements (which iteration), scope questions,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# BRIEF — <%= @_title %>
|
|
2
2
|
|
|
3
|
-
> Durable
|
|
3
|
+
> Durable project contract for the Architect Loop. The numbered §sections below are the
|
|
4
4
|
> stable, cross-iteration address space: every iteration cites them as **BRIEF §N** in its
|
|
5
5
|
> Grounds, Specification, Acceptance Criteria, and Verdict (e.g. `(BRIEF §3.1)`), the way each
|
|
6
|
-
> gate addresses its intent back to one frozen reference. Frozen early; edits are
|
|
6
|
+
> gate addresses its intent back to one frozen reference. Frozen early; edits are project-scope
|
|
7
7
|
> decisions logged in ARCHITECT.md's Decisions log, never silent per-iteration drift. Optional —
|
|
8
|
-
> a discovery
|
|
8
|
+
> a discovery project may defer this and cite per-iteration Grounds until the shape stabilizes,
|
|
9
9
|
> then promote the consolidated picture here once.
|
|
10
10
|
|
|
11
11
|
## 1. Goal & non-goals
|
|
@@ -38,6 +38,6 @@ frozen proof. An iteration's Specification cites its row here (e.g. "BRIEF §5 S
|
|
|
38
38
|
<!-- Known risks and the things a builder's PHASE 0 should challenge — cited from specs as
|
|
39
39
|
"BRIEF §6". -->
|
|
40
40
|
|
|
41
|
-
## 7. Definition of done (whole
|
|
41
|
+
## 7. Definition of done (whole project)
|
|
42
42
|
|
|
43
|
-
<!-- The
|
|
43
|
+
<!-- The project-level DoD. Iteration gates diff against "BRIEF §7 DoD". -->
|
|
@@ -32,16 +32,27 @@ Write + commit: `architect section <%= @_name %> specification --from <file>`. -
|
|
|
32
32
|
|
|
33
33
|
## Acceptance Criteria
|
|
34
34
|
|
|
35
|
-
<!-- PROOF.
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
<!-- PROOF. Write the prose conditions of correctness (AC1, AC2, …) that the
|
|
36
|
+
architect judges against. Runnable checks live in the fenced ```gates block below
|
|
37
|
+
(parsed at freeze time — absent or empty is allowed; malformed fails freeze).
|
|
38
|
+
`architect freeze <%= @_name %>` commits this file and records its SHA as
|
|
39
|
+
freeze_sha. Read-only afterward — any change to Grounds/Specification/Acceptance
|
|
40
|
+
Criteria = automatic iteration FAIL. -->
|
|
38
41
|
|
|
39
42
|
> Gate-pass is necessary, not sufficient: the architect also reads the diff against the
|
|
40
43
|
> cited **BRIEF §sections** intent and the §1 cardinal invariant before the verdict.
|
|
41
44
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
**AC1.** ...
|
|
46
|
+
|
|
47
|
+
```gates
|
|
48
|
+
# Each gate backs one prose AC above. Remove the comment markers to activate.
|
|
49
|
+
# - id: suite-green # unique slug within the iteration (required)
|
|
50
|
+
# ac: AC1 # which prose AC this gate backs (required)
|
|
51
|
+
# cwd: repos/my-repo # run dir, relative to space root (optional; consumed by I03)
|
|
52
|
+
# cmd: bundle exec rake test # shell command (required, non-empty)
|
|
53
|
+
# expect: # at least one of: exit_code, stdout_match, threshold
|
|
54
|
+
# exit_code: 0
|
|
55
|
+
```
|
|
45
56
|
|
|
46
57
|
## Builder Prompt
|
|
47
58
|
|
data/lib/space_architect.rb
CHANGED
|
@@ -1,27 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
require "
|
|
3
|
+
require "space_core"
|
|
4
|
+
require "space_src"
|
|
5
5
|
|
|
6
|
-
require_relative "space_architect/version"
|
|
7
|
-
require_relative "space_architect/errors"
|
|
8
|
-
require_relative "space_architect/warnings"
|
|
9
|
-
SpaceArchitect::Warnings.disable_experimental!
|
|
10
|
-
require_relative "space_architect/atomic_write"
|
|
11
|
-
require_relative "space_architect/xdg"
|
|
12
|
-
require_relative "space_architect/config"
|
|
13
|
-
require_relative "space_architect/state"
|
|
14
|
-
require_relative "space_architect/slugger"
|
|
15
|
-
require_relative "space_architect/space"
|
|
16
|
-
require_relative "space_architect/repo_reference"
|
|
17
|
-
require_relative "space_architect/repo_resolver"
|
|
18
|
-
require_relative "space_architect/git_client"
|
|
19
|
-
require_relative "space_architect/mise_client"
|
|
20
|
-
require_relative "space_architect/space_store"
|
|
21
|
-
require_relative "space_architect/shell_integration"
|
|
22
|
-
require_relative "space_architect/skill_installer"
|
|
23
|
-
require_relative "space_architect/terminal"
|
|
24
6
|
require_relative "space_architect/harness"
|
|
7
|
+
require_relative "space_architect/run_creator"
|
|
25
8
|
require_relative "space_architect/dispatcher"
|
|
26
|
-
require_relative "space_architect/
|
|
9
|
+
require_relative "space_architect/gate_lint"
|
|
10
|
+
require_relative "space_architect/gate_evaluator"
|
|
11
|
+
require_relative "space_architect/architect_project"
|
|
12
|
+
require_relative "space_architect/skill_installer"
|
|
13
|
+
require_relative "space_architect/research"
|
|
27
14
|
require_relative "space_architect/cli"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/cli"
|
|
4
|
+
|
|
5
|
+
module Space::Core::CLI
|
|
6
|
+
# Base for every space/architect command. dry-cli (>= 0.7.0) copies a
|
|
7
|
+
# superclass's options to its subclasses, so the global colour options are
|
|
8
|
+
# declared once here and inherited everywhere instead of being mixed in per
|
|
9
|
+
# command. Helpers (terminal/store/render) ride along by inheritance too.
|
|
10
|
+
#
|
|
11
|
+
# The `src` binary has its own output-mode system (--plain/--json) and does
|
|
12
|
+
# NOT inherit from this base.
|
|
13
|
+
class BaseCommand < Dry::CLI::Command
|
|
14
|
+
include Helpers
|
|
15
|
+
|
|
16
|
+
option :color, type: :string, default: "auto", desc: "Color output: auto, always, never"
|
|
17
|
+
option :colors, type: :string, desc: "Alias for --color"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../oci_packer"
|
|
4
|
+
require_relative "../oci_builder"
|
|
5
|
+
|
|
6
|
+
module Space::Core::CLI
|
|
7
|
+
class Build < BaseCommand
|
|
8
|
+
desc "Build (and tag) the OCI image for the current space"
|
|
9
|
+
|
|
10
|
+
def call(**opts)
|
|
11
|
+
setup_terminal(**opts.slice(:color, :colors))
|
|
12
|
+
handle_errors do
|
|
13
|
+
result = store.current.bind do |space|
|
|
14
|
+
out_dir = space.path.join("build", "oci")
|
|
15
|
+
Space::Core::OciPacker.new(space: space, output_dir: out_dir).generate.bind do
|
|
16
|
+
Space::Core::OciBuilder.new(space: space, output_dir: out_dir).command
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
render(result) do |argv|
|
|
20
|
+
terminal.say "Building: #{argv.join(' ')}"
|
|
21
|
+
out.flush
|
|
22
|
+
Kernel.exec(*argv)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Space::Core::CLI
|
|
4
|
+
module Config
|
|
5
|
+
class Show < BaseCommand
|
|
6
|
+
desc "Show current config"
|
|
7
|
+
|
|
8
|
+
def call(**opts)
|
|
9
|
+
setup_terminal(**opts.slice(:color, :colors))
|
|
10
|
+
handle_errors do
|
|
11
|
+
rows = Space::Core::Config::EDITABLE_KEYS.map do |key|
|
|
12
|
+
value = project_config.data[key]
|
|
13
|
+
[key, value.nil? ? "" : value.to_s]
|
|
14
|
+
end
|
|
15
|
+
terminal.say terminal.table(%w[Key Value], rows)
|
|
16
|
+
CLI.record_outcome(Outcome.new(exit_code: 0))
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class ConfigPath < BaseCommand
|
|
22
|
+
desc "Print the config file path"
|
|
23
|
+
|
|
24
|
+
def call(**opts)
|
|
25
|
+
setup_terminal(**opts.slice(:color, :colors))
|
|
26
|
+
handle_errors do
|
|
27
|
+
terminal.say terminal.path(project_config.path)
|
|
28
|
+
CLI.record_outcome(Outcome.new(exit_code: 0))
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
class Set < BaseCommand
|
|
34
|
+
desc "Set a config key"
|
|
35
|
+
argument :key, required: true, desc: "Config key"
|
|
36
|
+
argument :value, required: true, desc: "Config value"
|
|
37
|
+
|
|
38
|
+
def call(key:, value:, **opts)
|
|
39
|
+
setup_terminal(**opts.slice(:color, :colors))
|
|
40
|
+
handle_errors do
|
|
41
|
+
project_config.set(key, value)
|
|
42
|
+
stored = project_config.data[key]
|
|
43
|
+
terminal.success "Set #{key}=#{stored.nil? ? '' : stored.to_s}"
|
|
44
|
+
CLI.record_outcome(Outcome.new(exit_code: 0))
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Space::Core::CLI
|
|
4
|
+
class Current < BaseCommand
|
|
5
|
+
desc "Show the current space"
|
|
6
|
+
|
|
7
|
+
def call(**opts)
|
|
8
|
+
setup_terminal(**opts.slice(:color, :colors))
|
|
9
|
+
render(store.find) do |space|
|
|
10
|
+
terminal.say space.id.to_s
|
|
11
|
+
terminal.say terminal.path(space.path)
|
|
12
|
+
CLI.record_outcome(Outcome.new(exit_code: 0))
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/cli"
|
|
4
|
+
require "pastel"
|
|
5
|
+
|
|
6
|
+
module Space::Core::CLI
|
|
7
|
+
# Colourful replacement for dry-cli's plain `Usage` listing — the "global
|
|
8
|
+
# help" shown by `space`, `architect`, and every bare namespace (`space repo`,
|
|
9
|
+
# `worktree`, ...). Per-command help still flows through dry-cli's Banner,
|
|
10
|
+
# whose content we like; only the listing is reskinned.
|
|
11
|
+
#
|
|
12
|
+
# We reopen Dry::CLI::Usage.call (below) to delegate here, so BOTH the
|
|
13
|
+
# intercepted top-level help and dry-cli's own bare-namespace path get the
|
|
14
|
+
# same treatment from one place. Colour follows CLI.help_pastel, set once per
|
|
15
|
+
# invocation from the output stream's tty-ness and --color, so piped and test
|
|
16
|
+
# output stay plain. The `src` binary never loads space_core, so its own plain
|
|
17
|
+
# Usage is untouched.
|
|
18
|
+
module Help
|
|
19
|
+
# One tagline per binary — the root header is served for both `space` and
|
|
20
|
+
# `architect` from here, so we pick by $PROGRAM_NAME. The default covers the
|
|
21
|
+
# test runner and any other invocation where the binary can't be identified.
|
|
22
|
+
TAGLINES = {
|
|
23
|
+
"space" => "date-prefixed workspaces; repos provisioned on fibers at copy-on-write speed",
|
|
24
|
+
"architect" => "the Architect Loop: structured judgment plus a fleet of headless AI builders"
|
|
25
|
+
}.freeze
|
|
26
|
+
DEFAULT_TAGLINE = TAGLINES.fetch("space")
|
|
27
|
+
|
|
28
|
+
module_function
|
|
29
|
+
|
|
30
|
+
def call(result, pastel: CLI.help_pastel)
|
|
31
|
+
rows = listing(result)
|
|
32
|
+
width = rows.map { |label, _| label.length }.max || 0
|
|
33
|
+
|
|
34
|
+
lines = rows.map do |label, description|
|
|
35
|
+
painted = pastel.cyan(label.ljust(width))
|
|
36
|
+
description ? " #{painted} #{pastel.dim("# #{description}")}" : " #{painted}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
[header(result, pastel), pastel.bold("Commands:"), *lines, footer(result, pastel)]
|
|
40
|
+
.compact.join("\n")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# The richer header only makes sense at the true root (`space` / `architect`),
|
|
44
|
+
# not on every sub-namespace listing.
|
|
45
|
+
def header(result, pastel)
|
|
46
|
+
return unless result.names.empty?
|
|
47
|
+
|
|
48
|
+
"#{pastel.bold.cyan("space-architect")} #{pastel.dim(Space::Core::VERSION)} " \
|
|
49
|
+
"#{pastel.dim("— #{tagline}")}\n"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# The tagline for the binary in hand: `space` and `architect` each get their
|
|
53
|
+
# own, keyed off $PROGRAM_NAME (the same signal program_prefix reads).
|
|
54
|
+
def tagline
|
|
55
|
+
TAGLINES.fetch(File.basename($PROGRAM_NAME), DEFAULT_TAGLINE)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def footer(result, pastel)
|
|
59
|
+
"\n#{pastel.dim("Run `#{program_prefix(result)} <command> --help` for details on a command.")}"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# "space" at the root, "space repo" inside a namespace. The `space`/`architect`
|
|
63
|
+
# binaries inject their name into ARGV, so $PROGRAM_NAME and the leading
|
|
64
|
+
# namespace segment can collide ("space space ..."); drop the duplicate.
|
|
65
|
+
def program_prefix(result)
|
|
66
|
+
prog = File.basename($PROGRAM_NAME)
|
|
67
|
+
names = result.names.dup
|
|
68
|
+
names.shift if names.first == prog
|
|
69
|
+
[prog, *names].join(" ")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# [[label_with_banner, description_or_nil], ...] sorted by command name.
|
|
73
|
+
def listing(result)
|
|
74
|
+
result.children.sort_by { |name, _| name }.filter_map do |name, node|
|
|
75
|
+
next if node.hidden
|
|
76
|
+
|
|
77
|
+
[label(result, name, node), description(node)]
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def label(result, name, node)
|
|
82
|
+
"#{program_prefix(result)} #{name}#{banner(node)}"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def banner(node)
|
|
86
|
+
if node.command && node.leaf? && node.children?
|
|
87
|
+
" [ARGUMENT|SUBCOMMAND]"
|
|
88
|
+
elsif node.leaf?
|
|
89
|
+
arguments(node.command)
|
|
90
|
+
else
|
|
91
|
+
" [SUBCOMMAND]"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def arguments(command)
|
|
96
|
+
return "" unless command.respond_to?(:required_arguments)
|
|
97
|
+
|
|
98
|
+
names = command.required_arguments.map { |arg| arg.name.to_s.upcase }
|
|
99
|
+
names += command.optional_arguments.map { |arg| "[#{arg.name.to_s.upcase}]" }
|
|
100
|
+
names.empty? ? "" : " #{names.join(" ")}"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def description(node)
|
|
104
|
+
return unless node.leaf? && node.command.respond_to?(:description)
|
|
105
|
+
|
|
106
|
+
node.command.description
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Route dry-cli's plain namespace/root listing through our colourful renderer.
|
|
112
|
+
# We replace Usage.call wholesale and depend only on the LookupResult/Node API
|
|
113
|
+
# (children, command, leaf?/children?/hidden, names) rather than copying Usage's
|
|
114
|
+
# internals — see notes/ruby-cli-gems-report.md.
|
|
115
|
+
module Dry
|
|
116
|
+
class CLI
|
|
117
|
+
module Usage
|
|
118
|
+
def self.call(result)
|
|
119
|
+
Space::Core::CLI::Help.call(result)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|