maf 0.6.0 → 0.7.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 +27 -0
- data/assets/coord +1 -0
- data/assets/dispatcher +17 -1
- data/install.md +26 -2
- data/lib/maf/cli.rb +2 -2
- data/lib/maf/flow/generator.rb +2 -0
- data/lib/maf/flow/options.rb +4 -2
- data/lib/maf/flow/prompt_text.rb +2 -2
- data/lib/maf/flow/sandbox_policy.rb +61 -0
- data/lib/maf/flow/validator.rb +1 -0
- data/lib/maf/flow.rb +3 -1
- data/lib/maf/menu.rb +2 -1
- data/lib/maf/sandbox_choice.rb +29 -0
- data/lib/maf/shared/sandbox/seatbelt.rb +2 -1
- data/lib/maf/version.rb +1 -1
- data/templates/roles.yml +49 -11
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0600cae448899ea324b41b1cc21c2fefc40f9f1b5b349bfd0d6ae6ea2fdd90dc
|
|
4
|
+
data.tar.gz: 32d55e40cfa8de776f1ba464a3316c8872737a1bfe4953bae45462678792046c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2efc01c78cd25e6a017361df5c735880c7ddad65d4b40e7e3b9c6b8659bb5b3fed7680009838ed9ff1e84cb0ec65c9f9261f70229800d50e35d71d860e4c0044
|
|
7
|
+
data.tar.gz: 752dd79b5d7ea15a8206a7aaf3b6097f21c5d8d51a7f45ef66d60822d46ea948272d0095b66a4959670f5d00495242c11774fcbd30784fd21e2ab65dd54bab0c
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.7.1] - 2026-10-29
|
|
6
|
+
|
|
7
|
+
- Changed: the dispatcher no longer escalates a crash or a LaunchServices denial of a macOS
|
|
8
|
+
service (a program in `/System/`, `MTLCompilerService`, `intelligencecontextd`). It writes a log
|
|
9
|
+
line only. Before, each one reached the project manager as an `ESCALATION` that needed no action.
|
|
10
|
+
- Fixed: the macOS sandbox lets an agent connect to the PostgreSQL socket in `/tmp`
|
|
11
|
+
(`/tmp/.s.PGSQL.<port>`). Before, a Rails test that used the socket got `Operation not permitted`.
|
|
12
|
+
- Fixed: when the pull request of a goal merges, coord keeps the goal branch while a task of the
|
|
13
|
+
goal is still open. Before, `coord gc` deleted it and the open task could not land.
|
|
14
|
+
|
|
15
|
+
## [0.7.0] - 2026-10-24
|
|
16
|
+
|
|
17
|
+
- Changed: the project manager interviews the user until no decision stays silently assumed. It asks
|
|
18
|
+
about the what only and gives no recommended option unless the user asks for one. It keeps a goal
|
|
19
|
+
brief in `$COORD_DIR/artifacts/<goal>/goal.md` and hands the goal to the architect only after the
|
|
20
|
+
user confirms a summary of the brief. Its default effort is now `high`.
|
|
21
|
+
- Changed: the architect plans the how from the goal brief and sends open what-questions to the
|
|
22
|
+
project manager. It cuts tasks as vertical slices and does wide renames as expand, migrate, contract.
|
|
23
|
+
- Changed: the tester ranks three to five hypotheses before it looks for the cause of a bug, and
|
|
24
|
+
the reviewer checks the diff against the task spec and the goal brief in a separate section.
|
|
25
|
+
- New: a new install turns the sandbox on for the dispatched runs. At the first install,
|
|
26
|
+
`maf add` writes `{ "mode": "auto", "scope": "dispatch" }` to `.maf/sandbox.json`. New flags
|
|
27
|
+
`--sandbox off|auto|seatbelt|bwrap` and `--sandbox-scope dispatch|all` choose a different
|
|
28
|
+
policy, at the install or later. The interactive `maf add` asks for the sandbox at the first
|
|
29
|
+
install. `install.md` has a new step that asks the user. An existing install keeps its policy:
|
|
30
|
+
with no file, the sandbox stays off. Turn it on with `maf add --sandbox auto`.
|
|
31
|
+
|
|
5
32
|
## [0.6.0] - 2026-10-22
|
|
6
33
|
|
|
7
34
|
- New: `.maf/sandbox.json` key `scope`. `dispatch` (the default) runs only the dispatched runs
|
data/assets/coord
CHANGED
|
@@ -1577,6 +1577,7 @@ end
|
|
|
1577
1577
|
def goal_reason(name)
|
|
1578
1578
|
goal = @tasks.find_short(name.delete_prefix("goal/"))
|
|
1579
1579
|
return "goal is not done" unless goal["status"] == "completed"
|
|
1580
|
+
return "goal has open tasks" if @tasks.for_goal(goal["uuid"]).any?
|
|
1580
1581
|
|
|
1581
1582
|
Git.ancestor?(@root, name, ref) ? nil : "not merged into #{ref}"
|
|
1582
1583
|
end
|
data/assets/dispatcher
CHANGED
|
@@ -758,6 +758,10 @@ module Dispatcher
|
|
|
758
758
|
"apps outside the sandbox. An app with its own sandbox (Chrome) also needs it off (--no-sandbox), " \
|
|
759
759
|
"because macOS cannot start a sandbox in a sandbox. Tell the user."
|
|
760
760
|
|
|
761
|
+
SYSTEM = %w[MTLCompilerService intelligencecontextd].freeze
|
|
762
|
+
IGNORED = "Dispatcher of %<worker>s: in the macOS sandbox, these macOS services crashed or were refused " \
|
|
763
|
+
"LaunchServices: %<apps>s. maf ignores them."
|
|
764
|
+
|
|
761
765
|
@seen = []
|
|
762
766
|
|
|
763
767
|
def self.guard(harness, config, env, &block)
|
|
@@ -774,9 +778,21 @@ module Dispatcher
|
|
|
774
778
|
def self.report(config, env, crashes, denied)
|
|
775
779
|
crashes = crashes.except(*@seen)
|
|
776
780
|
denied -= @seen
|
|
781
|
+
@seen.concat(crashes.keys, denied)
|
|
782
|
+
ignore(config, crashes.keys + denied)
|
|
783
|
+
escalate(config, env, crashes.reject { |app, _| system?(app) }, denied.reject { |app| system?(app) })
|
|
784
|
+
end
|
|
785
|
+
|
|
786
|
+
def self.system?(app) = app.start_with?("/System/") || SYSTEM.include?(File.basename(app, ".*"))
|
|
787
|
+
|
|
788
|
+
def self.ignore(config, apps)
|
|
789
|
+
ignored = apps.select { |app| system?(app) }.uniq
|
|
790
|
+
Log.line(format(IGNORED, worker: config.worker, apps: ignored.join(", "))) if ignored.any?
|
|
791
|
+
end
|
|
792
|
+
|
|
793
|
+
def self.escalate(config, env, crashes, denied)
|
|
777
794
|
return if crashes.empty? && denied.empty?
|
|
778
795
|
|
|
779
|
-
@seen.concat(crashes.keys, denied)
|
|
780
796
|
text = format(NOTICE, worker: config.worker, found: found(crashes, denied))
|
|
781
797
|
Log.line(text)
|
|
782
798
|
CoordCall.run(env, "escalate", text)
|
data/install.md
CHANGED
|
@@ -141,10 +141,33 @@ If the workflow is `panel`, propose a different model family for each of
|
|
|
141
141
|
`reviewer`, `skeptic`, and `auditor`. Example: Claude, GPT, and Gemini.
|
|
142
142
|
Different models miss different defects. The user makes the final choice.
|
|
143
143
|
|
|
144
|
+
## Step 4b - Ask the user for the sandbox
|
|
145
|
+
|
|
146
|
+
A dispatched agent runs without approval prompts. The sandbox limits its writes to the
|
|
147
|
+
project, the board, and the state paths of its harness. See `FLOW/docs/sandbox.md`.
|
|
148
|
+
|
|
149
|
+
Ask: "Which sandbox do you want? Choose one."
|
|
150
|
+
|
|
151
|
+
| Choice | Flags for Step 5 | Effect |
|
|
152
|
+
|---|---|---|
|
|
153
|
+
| Dispatched runs only (default) | none | The dispatched runs and the analyst run in the sandbox. Interactive sessions run on the host, with approvals. |
|
|
154
|
+
| Dispatched runs and interactive sessions | `--sandbox-scope all` | Each agent runs in the sandbox. |
|
|
155
|
+
| Off | `--sandbox off` | No agent runs in the sandbox. |
|
|
156
|
+
|
|
157
|
+
Recommend the default. If the user chooses `Off`, tell the user that a dispatched
|
|
158
|
+
agent can then change each file of the user.
|
|
159
|
+
|
|
160
|
+
On Linux, the sandbox needs bubblewrap 0.10 or later. Run `bwrap --version`.
|
|
161
|
+
If it is missing or older, tell the user to install it, or to choose `Off`.
|
|
162
|
+
On macOS, the sandbox needs nothing.
|
|
163
|
+
|
|
164
|
+
`maf add` writes the choice to `.maf/sandbox.json` at the first install.
|
|
165
|
+
A later `maf add` changes the file only with a `--sandbox` or `--sandbox-scope` flag.
|
|
166
|
+
|
|
144
167
|
## Step 5 - Add the agents
|
|
145
168
|
|
|
146
169
|
Give one `HARNESS:ROLE` argument per role. Add one `--model ROLE=MODEL` flag
|
|
147
|
-
per chosen model.
|
|
170
|
+
per chosen model. Add the sandbox flags from Step 4b.
|
|
148
171
|
|
|
149
172
|
Preview first. This writes nothing.
|
|
150
173
|
|
|
@@ -164,6 +187,7 @@ Then run the same command without `--check`.
|
|
|
164
187
|
1. Sets up the coordination layer.
|
|
165
188
|
2. Writes a role file for each role, in the format of its harness.
|
|
166
189
|
3. Writes a manifest at `$PROJECT/.maf/config.json`.
|
|
190
|
+
At the first install, also writes the sandbox policy at `$PROJECT/.maf/sandbox.json`.
|
|
167
191
|
4. Writes the graphify MCP server into `.maf/mcp/` for Claude Code and opencode.
|
|
168
192
|
`maf start` passes the file to the harness. The project's `.mcp.json` and `opencode.json` stay as they are.
|
|
169
193
|
For Codex and Hermes, `maf add` prints a command. Run it to add the server.
|
|
@@ -251,7 +275,7 @@ If `git ls-files .maf` lists files, an older maf version committed them. Run
|
|
|
251
275
|
|
|
252
276
|
## Step 7 - Report to the user
|
|
253
277
|
|
|
254
|
-
Report the generated files
|
|
278
|
+
Report the generated files and the sandbox line of `maf add`. Then print one `maf start` command for each worker.
|
|
255
279
|
Use the number of workers from Step 3b. A worker role with N workers gets the
|
|
256
280
|
names `ROLE_1` to `ROLE_N`. A role with one worker may use the bare role name.
|
|
257
281
|
Then give the user these instructions.
|
data/lib/maf/cli.rb
CHANGED
|
@@ -42,8 +42,8 @@ module Maf
|
|
|
42
42
|
|
|
43
43
|
class CLI
|
|
44
44
|
COMMANDS = {
|
|
45
|
-
"add" => "[HARNESS:ROLE[:MODEL] ...] [--model ROLE=MODEL] [--effort ROLE=LEVEL]
|
|
46
|
-
"the flow; without arguments it asks",
|
|
45
|
+
"add" => "[HARNESS:ROLE[:MODEL] ...] [--model ROLE=MODEL] [--effort ROLE=LEVEL] [--sandbox MODE] " \
|
|
46
|
+
"[--sandbox-scope dispatch|all] add agents and install the flow; without arguments it asks",
|
|
47
47
|
"remove" => "HARNESS:ROLE ... remove agents",
|
|
48
48
|
"update" => " regenerate the files of the current agents",
|
|
49
49
|
"agents" => " list the current agents",
|
data/lib/maf/flow/generator.rb
CHANGED
|
@@ -39,8 +39,10 @@ module Maf
|
|
|
39
39
|
|
|
40
40
|
def finish(results)
|
|
41
41
|
pending = @options.check? ? false : HookInstaller.new(@options.agents, @options.project).install.any?
|
|
42
|
+
sandbox = SandboxPolicy.new(@options).tap(&:write)
|
|
42
43
|
manifest.write
|
|
43
44
|
Report.new(@options.project, @roles, pending).print(results)
|
|
45
|
+
sandbox.report
|
|
44
46
|
end
|
|
45
47
|
|
|
46
48
|
def manifest = Manifest.new(@options, @roles)
|
data/lib/maf/flow/options.rb
CHANGED
|
@@ -5,7 +5,7 @@ module Maf
|
|
|
5
5
|
# Options parses the command line and holds the settings of one run.
|
|
6
6
|
class Options
|
|
7
7
|
attr_accessor :project, :agents, :role_efforts
|
|
8
|
-
attr_reader :removed, :models, :efforts, :hermes_dir, :list_roles
|
|
8
|
+
attr_reader :removed, :models, :efforts, :hermes_dir, :list_roles, :sandbox
|
|
9
9
|
|
|
10
10
|
# Each flag: the OptionParser arguments, and a block that runs in the
|
|
11
11
|
# Options instance with the flag value.
|
|
@@ -15,6 +15,8 @@ module Maf
|
|
|
15
15
|
[["--remove SPEC", "HARNESS:ROLE"], ->(v) { @removed << parse_agent(v) }],
|
|
16
16
|
[["--model PAIR", "ROLE=MODEL"], ->(v) { @models.store(*v.split("=", 2)) }],
|
|
17
17
|
[["--effort PAIR", "ROLE=LEVEL"], ->(v) { @efforts.store(*v.split("=", 2)) }],
|
|
18
|
+
[["--sandbox MODE", "off, auto, seatbelt, or bwrap"], ->(v) { @sandbox["mode"] = v }],
|
|
19
|
+
[["--sandbox-scope SCOPE", "dispatch or all"], ->(v) { @sandbox["scope"] = v }],
|
|
18
20
|
[["--hermes-dir DIR"], ->(v) { @hermes_dir = File.expand_path(v) }],
|
|
19
21
|
[["--check"], ->(_) { @check = true }],
|
|
20
22
|
[["--force"], ->(_) { @force = true }],
|
|
@@ -24,7 +26,7 @@ module Maf
|
|
|
24
26
|
|
|
25
27
|
def initialize(argv)
|
|
26
28
|
@argv, @agents, @removed, @models, @efforts = argv, [], [], {}, {}
|
|
27
|
-
@role_efforts = {}
|
|
29
|
+
@role_efforts, @sandbox = {}, {}
|
|
28
30
|
@check = @force = @list_roles = false
|
|
29
31
|
@bootstrap = true
|
|
30
32
|
end
|
data/lib/maf/flow/prompt_text.rb
CHANGED
|
@@ -248,8 +248,8 @@ module Maf
|
|
|
248
248
|
2. Turn the request into one goal. Create the goal at the start of the interview:
|
|
249
249
|
`coord goal add --title "<outcome>"`.
|
|
250
250
|
The command prints the goal id and creates the goal branch. The glossary draft path needs the goal id.
|
|
251
|
-
3. When the
|
|
252
|
-
coord msg --from project-manager architect "GOAL <goal-id>: <goal
|
|
251
|
+
3. When the user confirms the summary of the goal brief, hand the goal to the architect:
|
|
252
|
+
coord msg --from project-manager architect "GOAL <goal-id>: <goal>. Brief: $COORD_DIR/artifacts/<goal-id>/goal.md"
|
|
253
253
|
4. Check status with `coord goal list` and `coord goal show <goal-id>` when the user asks.
|
|
254
254
|
%{wait_instruction}
|
|
255
255
|
Never poll with sleep or with repeated status commands. Each check sends your whole context again.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Maf
|
|
4
|
+
module Flow
|
|
5
|
+
# SandboxPolicy writes the mode and the scope of .maf/sandbox.json.
|
|
6
|
+
# A new install gets DEFAULT. A later run changes the file only for a --sandbox flag.
|
|
7
|
+
class SandboxPolicy
|
|
8
|
+
FILE = File.join(MAF_DIR, "sandbox.json")
|
|
9
|
+
CHOICES = { "mode" => %w[off auto seatbelt bwrap], "scope" => %w[dispatch all] }.freeze
|
|
10
|
+
DEFAULT = { "mode" => "auto", "scope" => "dispatch" }.freeze
|
|
11
|
+
HINT = " Change it with: maf add --sandbox off|auto|seatbelt|bwrap --sandbox-scope dispatch|all"
|
|
12
|
+
|
|
13
|
+
def self.validate!(chosen)
|
|
14
|
+
chosen.each do |key, value|
|
|
15
|
+
allowed = CHOICES.fetch(key)
|
|
16
|
+
abort "flow: unknown sandbox #{key} '#{value}' (use #{allowed.join(", ")})" unless allowed.include?(value)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def initialize(options)
|
|
21
|
+
@options = options
|
|
22
|
+
@fresh = !File.exist?(File.join(options.project, MAF_DIR, "config.json"))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def write
|
|
26
|
+
return if @options.check? || (!@fresh && @options.sandbox.empty?)
|
|
27
|
+
|
|
28
|
+
@policy = (@fresh ? DEFAULT.merge(current) : current).merge(@options.sandbox)
|
|
29
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
30
|
+
File.write(path, "#{JSON.pretty_generate(@policy)}\n")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def report
|
|
34
|
+
return unless @policy
|
|
35
|
+
|
|
36
|
+
puts "", "Sandbox: #{summary} (#{FILE})", HINT
|
|
37
|
+
warn_bwrap if bwrap? && !bwrap_installed?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def path = File.join(@options.project, FILE)
|
|
43
|
+
|
|
44
|
+
def current
|
|
45
|
+
File.exist?(path) ? JSON.parse(File.read(path)) : {}
|
|
46
|
+
rescue JSON::ParserError
|
|
47
|
+
abort "flow: #{FILE} is not valid JSON. Fix the file, or delete it."
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def mode = @policy["mode"] || "off"
|
|
51
|
+
def summary = mode == "off" ? "off" : "#{mode}, scope #{@policy["scope"] || "dispatch"}"
|
|
52
|
+
def bwrap? = mode == "bwrap" || (mode == "auto" && !RUBY_PLATFORM.include?("darwin"))
|
|
53
|
+
def bwrap_installed? = ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).any? { File.executable?("#{_1}/bwrap") }
|
|
54
|
+
|
|
55
|
+
def warn_bwrap
|
|
56
|
+
puts "WARNING: the Linux sandbox needs bubblewrap 0.10 or later. Install it before a dispatched run,",
|
|
57
|
+
" or turn the sandbox off: maf add --sandbox off"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
data/lib/maf/flow/validator.rb
CHANGED
|
@@ -19,6 +19,7 @@ module Maf
|
|
|
19
19
|
|
|
20
20
|
# A removal may leave no agents. Only a run that removes nothing needs one.
|
|
21
21
|
def run
|
|
22
|
+
SandboxPolicy.validate!(@options.sandbox)
|
|
22
23
|
@options.agents = Roster.new(@options.project).merge(@options.agents, @options.removed)
|
|
23
24
|
abort "flow: no agents. Add one with --agent HARNESS:ROLE" if @options.agents.empty? && @options.removed.empty?
|
|
24
25
|
|
data/lib/maf/flow.rb
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
#
|
|
5
5
|
# maf add, maf remove, maf update, and maf roles run Flow::Generator.
|
|
6
6
|
# Options: --project DIR [--agent HARNESS:ROLE ...] [--remove HARNESS:ROLE ...]
|
|
7
|
-
# [--model ROLE=MODEL] [--effort ROLE=LEVEL] [--
|
|
7
|
+
# [--model ROLE=MODEL] [--effort ROLE=LEVEL] [--sandbox MODE] [--sandbox-scope SCOPE]
|
|
8
|
+
# [--hermes-dir DIR] [--check] [--force] [--no-bootstrap]
|
|
8
9
|
# [--list-roles]
|
|
9
10
|
#
|
|
10
11
|
# HARNESS is a harness of the registry: one file in lib/maf/shared/harnesses/.
|
|
@@ -61,6 +62,7 @@ require_relative "flow/mcp_config"
|
|
|
61
62
|
require_relative "flow/mcp_installer"
|
|
62
63
|
require_relative "flow/role_files"
|
|
63
64
|
require_relative "flow/manifest"
|
|
65
|
+
require_relative "flow/sandbox_policy"
|
|
64
66
|
require_relative "flow/models"
|
|
65
67
|
require_relative "flow/effort_report"
|
|
66
68
|
require_relative "flow/report"
|
data/lib/maf/menu.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "prompt"
|
|
4
|
+
require_relative "sandbox_choice"
|
|
4
5
|
|
|
5
6
|
module Maf
|
|
6
7
|
# Menu is the interactive mode of maf. It runs the same steps as the
|
|
@@ -28,7 +29,7 @@ module Maf
|
|
|
28
29
|
return if roles.empty?
|
|
29
30
|
|
|
30
31
|
models = show_models(harness)
|
|
31
|
-
Maf.flow(*roles.flat_map { |role| agent_args(harness, role, models) })
|
|
32
|
+
Maf.flow(*roles.flat_map { |role| agent_args(harness, role, models) }, *SandboxChoice.new(@prompt).args)
|
|
32
33
|
end
|
|
33
34
|
|
|
34
35
|
private
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Maf
|
|
4
|
+
# SandboxChoice asks for the sandbox of a new install. A later maf add keeps .maf/sandbox.json.
|
|
5
|
+
class SandboxChoice
|
|
6
|
+
CHOICES = [["Dispatched runs only (recommended)", %w[auto dispatch]],
|
|
7
|
+
["Dispatched runs and interactive sessions", %w[auto all]],
|
|
8
|
+
["Off", %w[off dispatch]]].freeze
|
|
9
|
+
|
|
10
|
+
def initialize(prompt)
|
|
11
|
+
@prompt = prompt
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def args
|
|
15
|
+
return [] if File.exist?(MANIFEST)
|
|
16
|
+
|
|
17
|
+
mode, scope = ask.last
|
|
18
|
+
["--sandbox", mode, "--sandbox-scope", scope]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def ask
|
|
24
|
+
CHOICES.each_with_index { |(label, _), i| @prompt.say(" #{i + 1}) #{label}") }
|
|
25
|
+
number = Integer(@prompt.ask("Sandbox (number, Enter = 1):").to_s, exception: false)
|
|
26
|
+
number&.between?(1, CHOICES.size) ? CHOICES[number - 1] : CHOICES.first
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -15,6 +15,7 @@ module Maf
|
|
|
15
15
|
DEVICES = '(literal "/dev/null") (literal "/dev/zero") (literal "/dev/dtracehelper") ' \
|
|
16
16
|
'(regex #"^/dev/tty") (regex #"^/dev/fd/") (regex #"^/dev/ptmx")'
|
|
17
17
|
DNS = '(path-literal "/private/var/run/mDNSResponder")'
|
|
18
|
+
POSTGRES = '(regex #"^/private/tmp/\\.s\\.PGSQL\\.[0-9]+$")'
|
|
18
19
|
TMP_PREFIX = "maf-sandbox-"
|
|
19
20
|
# The map database of LaunchServices only reads: it turns a type name into a type.
|
|
20
21
|
# Without it, pbcopy writes the text with a type that no app reads.
|
|
@@ -98,7 +99,7 @@ module Maf
|
|
|
98
99
|
end
|
|
99
100
|
|
|
100
101
|
def self.sockets(dirs)
|
|
101
|
-
allowed = [DNS, *dirs.map { |dir| "(subpath #{quote(dir)})" }]
|
|
102
|
+
allowed = [DNS, POSTGRES, *dirs.map { |dir| "(subpath #{quote(dir)})" }]
|
|
102
103
|
rules = allowed.map { |path| "(allow network-outbound (remote unix-socket #{path}))\n" }
|
|
103
104
|
"(deny network-outbound (remote unix-socket))\n#{rules.join}"
|
|
104
105
|
end
|
data/lib/maf/version.rb
CHANGED
data/templates/roles.yml
CHANGED
|
@@ -22,42 +22,57 @@ roles:
|
|
|
22
22
|
title: Project Manager
|
|
23
23
|
description: Talks with the user. Turns requests into goals for the architect. Reports status back to the user. Does not plan tasks or implement code.
|
|
24
24
|
duties: |
|
|
25
|
-
Focus:
|
|
25
|
+
Focus: what the goal must do, clear goals, and honest status reports.
|
|
26
|
+
You own the what. The architect owns the how.
|
|
26
27
|
|
|
27
28
|
Checks:
|
|
28
29
|
- Restate the request as one user-visible outcome.
|
|
29
|
-
- Interview the user in rounds before you
|
|
30
|
+
- Interview the user relentlessly, in rounds, before you hand the goal to the architect:
|
|
30
31
|
1. Map the request as a design tree. Each decision has the decisions that hang off it.
|
|
32
|
+
Find the what-branches in the request and the code. Do not use a fixed list of questions.
|
|
33
|
+
Ask only the questions that change what the user gets. Include the edge cases that the request opens.
|
|
31
34
|
2. Find the frontier: each decision whose prerequisites are settled.
|
|
32
35
|
3. Ask the whole frontier in one round. Number each question.
|
|
33
|
-
Give
|
|
36
|
+
Give the options for each, with the cost of each option. Then wait for the answers.
|
|
34
37
|
4. After the answers, find the frontier again. Ask the next round.
|
|
35
38
|
A question that depends on an open question waits for a later round.
|
|
36
39
|
5. Find each fact yourself, with the tools and the code. Ask the user only for decisions.
|
|
37
|
-
|
|
40
|
+
If a fact search still runs, ask the questions that do not need the fact now.
|
|
41
|
+
6. If an answer is "I do not know", give the options again with their effect on the user.
|
|
42
|
+
If the user still cannot decide, record the question under "Not yet specified".
|
|
43
|
+
The frontier is empty when you visited each branch and nothing stays silently assumed.
|
|
44
|
+
If the tree looks complete after one round, look again for the edge cases.
|
|
45
|
+
- Keep the goal brief in `$COORD_DIR/artifacts/<goal>/goal.md`. Update it after each round.
|
|
46
|
+
Sections: Problem, Outcome, Decisions (each with the user's reason), Acceptance criteria,
|
|
47
|
+
Out of scope, Not yet specified.
|
|
48
|
+
Write acceptance criteria that the user can observe. Write no libraries, code, or file paths: those are the how.
|
|
49
|
+
- When the frontier is empty, show the user a summary of the brief: the decisions, the open questions,
|
|
50
|
+
and the out-of-scope items. Ask the user to confirm the summary. Hand the goal to the architect only after the user confirms.
|
|
38
51
|
- Write each term to `$COORD_DIR/artifacts/<goal>/glossary-draft.md` the moment it resolves.
|
|
39
52
|
Do not wait for the end. One or two sentences per term. Domain meaning only, no implementation detail.
|
|
40
53
|
List the rejected words in an `_Avoid_` line. Never commit: the architect owns the committed `GLOSSARY.md`.
|
|
41
|
-
-
|
|
42
|
-
- Name what is out of scope for the goal.
|
|
54
|
+
- If the architect sends a question about the what, ask the user. Add the answer to `goal.md`.
|
|
43
55
|
- Run goals in parallel only if the goals change different parts of the code.
|
|
44
56
|
Goals that change the same code block each other at merge time.
|
|
45
57
|
- If a message starts with "ESCALATION", a role cannot fix a problem. Tell the user in plain words:
|
|
46
|
-
what failed, which task, and the impact. Give two or three options with
|
|
58
|
+
what failed, which task, and the impact. Give two or three options with the cost of each.
|
|
47
59
|
Wait for the answer. Then send the answer to the role with `coord msg`.
|
|
48
60
|
If the escalation names a blocked task, run `coord unblock <id>` after you send the answer.
|
|
49
61
|
- If the architect escalates a repeated review failure, forward the
|
|
50
|
-
options to the user. Do not send a new goal until the user decides.
|
|
62
|
+
options and their costs to the user. Do not send a new goal until the user decides.
|
|
51
63
|
|
|
52
64
|
Done when: the user has a report that states the outcome, open risks,
|
|
53
65
|
and the next decision the user must make.
|
|
54
66
|
|
|
55
67
|
Avoid:
|
|
56
|
-
-
|
|
68
|
+
- How-questions: libraries, code reuse, data model, and file layout. The architect decides them.
|
|
69
|
+
- A goal handoff before the user confirms the summary of the brief.
|
|
57
70
|
- Process detail in reports. The user needs outcomes and decisions.
|
|
58
71
|
- Optimistic status. Report blockers and failed checks as they are.
|
|
72
|
+
- A recommended option, yours or the architect's, unless the user asks for one.
|
|
73
|
+
A recommendation biases the user. The value of the interview is the user's own decision.
|
|
59
74
|
model_hint: "Strongest reasoning and long context, conversational. Cloud: Claude Opus 5.5 (default for the claude harness), GPT-5.x."
|
|
60
|
-
effort:
|
|
75
|
+
effort: high
|
|
61
76
|
can_edit: false
|
|
62
77
|
|
|
63
78
|
architect:
|
|
@@ -67,6 +82,11 @@ roles:
|
|
|
67
82
|
Focus: task decomposition, disjoint scopes, and verification of results.
|
|
68
83
|
|
|
69
84
|
Checks:
|
|
85
|
+
- Read the goal brief `$COORD_DIR/artifacts/<goal>/goal.md` before you plan. The brief holds the what.
|
|
86
|
+
Do not change a decision in the brief. You own the how: code reuse, libraries, data model, and file layout.
|
|
87
|
+
- If the brief does not answer a what-question, or a how-choice changes what the user sees,
|
|
88
|
+
send the question to the project manager. Do not decide it yourself.
|
|
89
|
+
If a project manager does not run, ask the user and add the answer to the brief.
|
|
70
90
|
- Read the affected code before you plan. Do not plan from file names only.
|
|
71
91
|
- Own the committed `GLOSSARY.md`. The project manager writes the terms to
|
|
72
92
|
`$COORD_DIR/artifacts/<goal>/glossary-draft.md`. Challenge a term that is vague, duplicate, or wrong.
|
|
@@ -79,6 +99,12 @@ roles:
|
|
|
79
99
|
Add an optional section only if it carries real value.
|
|
80
100
|
- Scale the plan to the goal. A small change gets one task.
|
|
81
101
|
- Split work only into independent paths. Each split adds merge and review cost.
|
|
102
|
+
- Cut each task as a vertical slice: one narrow path through each layer that it touches, testable alone.
|
|
103
|
+
A task for one layer only tests a guess about the other layers.
|
|
104
|
+
Put a refactor that makes the change easy in its own task, before the change.
|
|
105
|
+
- For a wide mechanical change, such as a rename across the code, use expand, migrate, contract.
|
|
106
|
+
First add the new form next to the old form. Then move the callers in batches. Then delete the old form.
|
|
107
|
+
Each step keeps the suite green.
|
|
82
108
|
- Give each path exactly one owner. Overlapping scopes cause lost edits.
|
|
83
109
|
- Order tasks by dependency. Put reviewer tasks after implementation.
|
|
84
110
|
- A reviewer task has no scope: omit `--scope`. A review scope such as `review/<id>` is not a path,
|
|
@@ -169,7 +195,15 @@ roles:
|
|
|
169
195
|
|
|
170
196
|
Checks:
|
|
171
197
|
- For a reported bug, write a failing test first. Confirm that the test fails for the reported reason.
|
|
198
|
+
The test must assert the exact symptom of the user, not a nearby failure.
|
|
199
|
+
Make the reproduction as small as possible: remove each input or step that the failure does not need.
|
|
200
|
+
- Before you look for the cause of a bug, write three to five hypotheses in order of probability.
|
|
201
|
+
For each hypothesis, state the change that makes the bug go away if the hypothesis is true.
|
|
202
|
+
Change one variable for each probe. Put the hypothesis that proved true in the report.
|
|
203
|
+
- Tag each temporary debug log with one prefix, for example `[DEBUG-a4f2]`. Remove each tagged log before done.
|
|
172
204
|
- Test behavior through public interfaces. Tests on internals break at each refactor.
|
|
205
|
+
If no public interface reaches the bug, report the missing seam to the architect.
|
|
206
|
+
A test at a wrong seam passes while the bug stays.
|
|
173
207
|
- Cover edge cases: empty input, boundaries, invalid input, and error paths.
|
|
174
208
|
- Follow the project's test layout, helpers, and fixtures.
|
|
175
209
|
- Keep tests deterministic. Control time, randomness, and the network.
|
|
@@ -181,7 +215,7 @@ roles:
|
|
|
181
215
|
Avoid:
|
|
182
216
|
- Edits to production code. Report the defect to the architect.
|
|
183
217
|
- Sleeps and retries that hide a flaky test. Isolate the test and report it.
|
|
184
|
-
- Assertions that only repeat the implementation.
|
|
218
|
+
- Assertions that only repeat the implementation. Take each expected value from the spec or a worked example.
|
|
185
219
|
- Deleted or weakened tests. A test removal needs architect approval.
|
|
186
220
|
model_hint: "Mechanical test work usually fits a local coder model; use cloud for subtle failure analysis."
|
|
187
221
|
effort: low
|
|
@@ -196,6 +230,10 @@ roles:
|
|
|
196
230
|
Checks, in this order:
|
|
197
231
|
- Vocabulary: if the diff changes `GLOSSARY.md`, check one meaning per term, no implementation detail,
|
|
198
232
|
no duplicate term, and no term that contradicts an existing entry.
|
|
233
|
+
- Spec: compare the diff with the task spec and the goal brief `$COORD_DIR/artifacts/<goal>/goal.md`.
|
|
234
|
+
Find requirements that are missing or half done, behavior that nobody asked for, and requirements done wrong.
|
|
235
|
+
Quote the spec line in each spec finding. Put the spec findings in their own section.
|
|
236
|
+
Code can follow each standard and still build the wrong thing.
|
|
199
237
|
- Correctness: logic errors, edge cases, error handling, and data loss.
|
|
200
238
|
- Security: injection, authorization gaps, secret exposure, and unsafe input.
|
|
201
239
|
- Regressions: changed behavior for existing callers and public interfaces.
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: maf
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dominik Alberski
|
|
@@ -106,6 +106,7 @@ files:
|
|
|
106
106
|
- lib/maf/flow/role_files.rb
|
|
107
107
|
- lib/maf/flow/role_stub.rb
|
|
108
108
|
- lib/maf/flow/roster.rb
|
|
109
|
+
- lib/maf/flow/sandbox_policy.rb
|
|
109
110
|
- lib/maf/flow/validator.rb
|
|
110
111
|
- lib/maf/flow/workflow.rb
|
|
111
112
|
- lib/maf/local_exclude.rb
|
|
@@ -121,6 +122,7 @@ files:
|
|
|
121
122
|
- lib/maf/restore.rb
|
|
122
123
|
- lib/maf/retire.rb
|
|
123
124
|
- lib/maf/role_limits.rb
|
|
125
|
+
- lib/maf/sandbox_choice.rb
|
|
124
126
|
- lib/maf/session_records.rb
|
|
125
127
|
- lib/maf/setup_agent.rb
|
|
126
128
|
- lib/maf/setup_agent/args.rb
|