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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: df94fb01940bd1e9d53fbd46bd33d023ef9584eebafcf018a11c71fcc2edf4e9
4
- data.tar.gz: 063fcde6929e5a1476ec12db56bcc65dff75dc1d9f3867208b4a8e91fa865335
3
+ metadata.gz: 0600cae448899ea324b41b1cc21c2fefc40f9f1b5b349bfd0d6ae6ea2fdd90dc
4
+ data.tar.gz: 32d55e40cfa8de776f1ba464a3316c8872737a1bfe4953bae45462678792046c
5
5
  SHA512:
6
- metadata.gz: f60c0cbcb6a60b2a079e09e3ccf9587c3501c1420f33cbfb6424c80211440d2831b1ddce1894d4adc171299ac7a25bd2cdebc84b2b8d565c54ff88623cbe44da
7
- data.tar.gz: 8a86dbb9aa864b37426c7db5f3d2e4d26ebd145086cc0f766053468f79d5863598ef9aadd645bce991879ac86acd2efbe962690ca8e660efa38fe2bda58ed12a
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. Then print one `maf start` command for each worker.
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] add agents and install " \
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",
@@ -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)
@@ -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
@@ -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 interview ends, hand the goal to the architect:
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
@@ -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] [--hermes-dir DIR] [--check] [--force] [--no-bootstrap]
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Maf
4
- VERSION = "0.6.0"
4
+ VERSION = "0.7.1"
5
5
  end
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: user intent, clear goals, and honest status reports.
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 plan the goal:
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 a recommended answer for each. Then wait for the answers.
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
- The interview ends when the frontier is empty.
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
- - Write acceptance criteria that the user can observe.
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 a recommended one.
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
- - Solution design in the goal. The architect owns the design.
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: medium
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.6.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