Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
4ffbe78
feat(prompts): unify beads-issue loops into a single orchestrator
inercia Jul 16, 2026
3cdc693
feat(loop): expose onTasks trigger delta to prompt templates
inercia Jul 16, 2026
ce3e7eb
feat(mcp): add mitto_workspace_ui_notify for aux-session toasts
inercia Jul 16, 2026
8b39a42
perf(shortcuts): gate global prompts list behind ?include_prompts=true
inercia Jul 16, 2026
5525324
fix(acpproc): extend cold MCP-init budget for cold session/new attempts
inercia Jul 16, 2026
b2c78b0
docs(prompts): minor tweaks to support-* builtin prompts
inercia Jul 16, 2026
a95f03a
feat(loop): add opt-in CoalesceDuringBusy=false for onTasks re-fire
inercia Jul 16, 2026
004fa58
feat(loops,prompts): adopt .Trigger.OnTasks.* + coalesceDuringBusy: f…
inercia Jul 16, 2026
8941466
fix(mcp,prompts): stop schema-required 'prompt' from overriding 'prom…
inercia Jul 16, 2026
7a7e84e
feat(beads,frontend): render sidebar phase pill as per-phase icons (m…
inercia Jul 16, 2026
c2aadc9
fix(beads,frontend): gate tasksList shortcut buttons through PromptPa…
inercia Jul 16, 2026
1aba4d4
feat(prompts): honor FixBugs/WorkOnFeatures flags in beads loop-proce…
inercia Jul 16, 2026
6e7eb29
fix(loop): enable arg-edit button for menu-scoped loop prompts (mitto…
inercia Jul 16, 2026
021b8e8
feat(beads-ui): open BeadsIssueView drawer immediately with loading/e…
inercia Jul 16, 2026
cf834ff
feat(prompts): §A worker seed — bead awareness, refs <id> commits, co…
inercia Jul 16, 2026
a150ac4
fix(loop): route onTasks prompt-resolve failures through auto-pause (…
inercia Jul 16, 2026
b8118d3
chore(prompts): tighten fix-phase closure with mandatory checklist + …
inercia Jul 16, 2026
0031889
fix(beads): stop gating workspace-tasks shortcuts by active conversat…
inercia Jul 16, 2026
396c638
feat(prompts): @mitto mention driver phase state machine (mitto-91wk)
inercia Jul 16, 2026
e61920f
feat(prompts): tier check + mandatory-closure discipline in beads pha…
inercia Jul 16, 2026
1837801
feat(mcp): model_tag on mitto_conversation_new and mitto_conversation…
inercia Jul 16, 2026
48bcc7b
fix(conversation): loudly log dropped tier switch when agent advertis…
inercia Jul 16, 2026
1a92754
feat(beads-ui): browser-style back/forward navigation history in Bead…
inercia Jul 16, 2026
02d30e8
fix(web): route non-viewable file links to system app or /api/files d…
inercia Jul 16, 2026
f699174
feat(beads): file-path attachments via metadata + bd-attach.sh helper
inercia Jul 16, 2026
6505ccd
fix(mitto-rtdr): mirror arguments into loop_arguments in loop-process…
inercia Jul 16, 2026
b1a4dcc
fix(mitto-zbfq): single stable Drawer mount across BeadsIssueView load
inercia Jul 16, 2026
0971ec9
refactor(conversation): move loop scheduling from internal/web (mitto…
inercia Jul 16, 2026
f337adf
refactor(web): move session route wrappers into handlers package (mit…
inercia Jul 16, 2026
2745b7a
refactor(web): extract handleConfig wrapper to handlers pkg (mitto-b8…
inercia Jul 16, 2026
9736f9f
test(integration): fix compile after tasks_baseline move (mitto-b8k.1)
inercia Jul 16, 2026
bdfeca0
refactor(config): extract beads_watcher into internal/beads/watcher (…
inercia Jul 16, 2026
d6af4df
refactor(session): split store.go into focused files (mitto-b8k.4)
inercia Jul 16, 2026
0209c35
refactor(config): extract CEL evaluator/context/tasks/templatefuncs i…
inercia Jul 16, 2026
5f83f8b
refactor(config): extract prompts (types, loader, cache, watcher, mig…
inercia Jul 17, 2026
fbd2713
refactor(config): extract workspaces/folders/constraint/runner-config…
inercia Jul 17, 2026
0b96886
refactor(acp): move ACP error taxonomy to internal/acp (mitto-2d0)
inercia Jul 17, 2026
7e5f521
test: remove loop_runner tests referencing missing LoopPrompt.NoProgr…
inercia Jul 17, 2026
d85f16d
fix(web): strip query/fragment before extension check in isNonViewabl…
inercia Jul 17, 2026
682bcf9
fix(web): percent-encode path segments before building file:// URL
inercia Jul 17, 2026
7a6bb8e
fix(session): log correct age when ArchivedAt falls back to UpdatedAt
inercia Jul 17, 2026
ac6d6ae
fix: SA4000 in loop_runner_test + restart_test import after acp packa…
inercia Jul 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions .augment/rules/44-beads-attachments.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
---
description: Beads issue file-path attachments — convention, metadata schema, and helper script
globs:
- "scripts/bd-attach.sh"
- ".beads/**/*"
keywords:
- beads attachments
- bd attach
- attachment
- issue attachment
- bd metadata
- attachments metadata
- bd-attach.sh
---

# Beads Issue Attachments

`bd` (beads 1.0.x) has no native attachment command. This project attaches file
paths to issues using the `attachments` key inside each issue's structured JSON
metadata column. Metadata syncs with `bd dolt push` / `pull` like any other
issue field, so attachments travel with the ticket to every collaborator.

## Schema

Each attachment is an object; `attachments` is a JSON array of them:

```json
{
"attachments": [
{
"path": "docs/screenshot.png",
"name": "Failure state",
"note": "Reproduces after clicking Save twice"
},
{ "path": "logs/2026-07-16-crash.log" }
]
}
```

- `path` (**required**, string): repo-relative path is **preferred** (portable
across clones); absolute paths are allowed for out-of-repo references.
- `name` (optional, string): short human label; defaults to the basename.
- `note` (optional, string): free-form description of what the attachment shows.

No write-time validation is performed (bd has no filesystem awareness); the
helper script warns on missing files at **read** time only.

## Usage — helper (preferred)

Use `scripts/bd-attach.sh` for day-to-day work; it hides the metadata-merge
plumbing and preserves other metadata keys:

```bash
# Attach a file
scripts/bd-attach.sh add mitto-aiq docs/screenshot.png \
--name "Failure state" --note "Reproduces after clicking Save twice"

# List (marks missing-on-disk entries with ✗)
scripts/bd-attach.sh list mitto-aiq

# Remove one entry by path
scripts/bd-attach.sh remove mitto-aiq docs/screenshot.png

# Wipe all attachments from an issue
scripts/bd-attach.sh clear mitto-aiq
```

Requires `bd` and `jq` on `PATH`.

## Usage — raw `bd` (advanced)

The helper is a thin wrapper around `bd update --metadata`. If you must go raw:

```bash
# Write (whole-blob replace — YOU are responsible for preserving other keys)
echo '{"attachments":[{"path":"docs/foo.png","name":"screenshot"}]}' \
> /tmp/att.json
bd update mitto-aiq --metadata @/tmp/att.json

# Read
bd show mitto-aiq --json | jq '.[0].metadata.attachments'
```

> ⚠ **Do not** use `bd update <id> --set-metadata 'attachments=[...]'` — that
> form stores the value as a **string**, not a parsed JSON array, and readers
> then have to double-decode it. Always use `--metadata @file.json` for
> structured attachments, or use the helper.

## Conventions

- Prefer **repo-relative** paths — they resolve on any clone of the repo.
- Keep `note` short; the description or a `bd comment` is the place for a
longer write-up.
- Multiple attachments per issue is fine; the array preserves insertion order.
- Attachments record the *path only*, not the file bytes. If the referenced
file may disappear, capture the relevant content in a comment or copy it into
the repo before attaching.

## Non-goals

The attachment mechanism is deliberately minimal:

- No byte storage or uploads.
- No MIME/type validation.
- No per-attachment access control.
- No UI integration (attachments are visible via `bd show --json`, the helper's
`list` sub-command, or direct inspection of the metadata column).

Byte-storage or UI features are separate proposals; open a new bead if you need
them.
145 changes: 94 additions & 51 deletions config/beads_loop_prompts_defects_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,21 @@ import (
"testing"
)

// TestBeadsLoopPrompts_Defects_mitto6am is the failing reproduction test for
// mitto-6am: the "Loop implementing features" / "Loop implementing feature"
// prompt family shares the same three structural defects as its bug-fix
// siblings (mitto-dj9, mitto-i5k, mitto-fko), plus a fourth Step 3f fan-out
// vector unique to the features driver.
//
// This test asserts the ABSENCE of the four defective patterns in the
// embedded builtin prompt YAMLs. While the defects are still present it
// fails; when the fix phase rewrites the prompts (prompt_name-based spawns,
// unconditional Done branch, recently-closed-parents filter, named worker
// prompt for Step 3f) it will flip to green.
// TestBeadsLoopPrompts_Defects_mitto6am is the regression guard for the four
// structural defects originally tracked as mitto-dj9, mitto-i5k, mitto-fko,
// and the unique mitto-6am Step 3f fan-out vector. The three list-level
// orchestrators (bugs-plural / features-plural / addressing-comments) have
// since been consolidated into a single "Loop processing beads" prompt
// (beads-issue-loop-processing.prompt.yaml), so the anti-defect assertions
// that originally targeted the plural orchestrators now target the merged
// prompt. The per-item drivers (bug / feature, singular) still exist and are
// still checked for defects 2 and 4.
func TestBeadsLoopPrompts_Defects_mitto6am(t *testing.T) {
const (
bugsOrch = "beads-issue-loop-fixing-bugs.prompt.yaml"
bugDriver = "beads-issue-loop-fixing-bug.prompt.yaml"
featsOrch = "beads-issue-loop-implementing-features.prompt.yaml"
featDriver = "beads-issue-loop-implementing-feature.prompt.yaml"
mergedOrch = "beads-issue-loop-processing.prompt.yaml"
bugDriver = "beads-issue-loop-fixing-bug.prompt.yaml"
featDriver = "beads-issue-loop-implementing-feature.prompt.yaml"
mentionDriver = "beads-issue-mention-driver.prompt.yaml"
)

load := func(name string) string {
Expand All @@ -34,28 +32,24 @@ func TestBeadsLoopPrompts_Defects_mitto6am(t *testing.T) {
return string(b)
}

// Defect 1 — placeholder short-circuit vector (mitto-dj9).
// Orchestrator Step 4 spawns children via `initial_prompt: <driverBody>` +
// `loop_prompt: <driverBody>` instead of `prompt_name:` + `arguments:`.
// Fix: replace with `prompt_name: "Loop fixing bug" | "Loop implementing feature"`.
// Defect 1 — placeholder short-circuit vector (mitto-dj9). Merged
// orchestrator §B/§C must spawn nested drivers via `prompt_name:` +
// `arguments:`, not via `initial_prompt: <driverBody>` /
// `loop_prompt: <driverBody>` placeholders.
placeholderInitial := regexp.MustCompile(`initial_prompt:\s*<driverBody>`)
placeholderLoop := regexp.MustCompile(`loop_prompt:\s*<driverBody>`)
for _, name := range []string{bugsOrch, featsOrch} {
body := load(name)
{
body := load(mergedOrch)
if placeholderInitial.MatchString(body) {
t.Errorf("[defect-1 placeholder-vector, mitto-dj9] %s still contains `initial_prompt: <driverBody>` at Step 4; expected `prompt_name:` + `arguments:` so the server expands the driver body from the named template", name)
t.Errorf("[defect-1 placeholder-vector, mitto-dj9] %s still contains `initial_prompt: <driverBody>`; expected `prompt_name:` + `arguments:` so the server expands the driver body from the named template", mergedOrch)
}
if placeholderLoop.MatchString(body) {
t.Errorf("[defect-1 placeholder-vector, mitto-dj9] %s still contains `loop_prompt: <driverBody>` at Step 4; expected `prompt_name:` + `arguments:` so the server expands the driver body from the named template", name)
t.Errorf("[defect-1 placeholder-vector, mitto-dj9] %s still contains `loop_prompt: <driverBody>`; expected `prompt_name:` + `arguments:` so the server expands the driver body from the named template", mergedOrch)
}
}

// Defect 2 — soft-gated Done branch (mitto-i5k).
// Per-item driver Done branch marks `bd close` as "optional but
// recommended", inviting the LLM to skip past `loop_enabled: false`.
// Fix: unconditional `bd close` + unconditional `loop_enabled: false`,
// evaluated as the very first branch against a mandatory fresh
// `bd show --json`.
// Defect 2 — soft-gated Done branch (mitto-i5k). Per-item driver Done
// branch must not mark `bd close` as "optional but recommended".
softClose := regexp.MustCompile(`bd close[^\n]*# optional but recommended`)
for _, name := range []string{bugDriver, featDriver} {
body := load(name)
Expand All @@ -64,38 +58,78 @@ func TestBeadsLoopPrompts_Defects_mitto6am(t *testing.T) {
}
}

// Defect 3 — subtask spawn after parent closes (mitto-fko).
// Orchestrator Step 2 enumerates via `bd ready` (falling back to
// `bd list --status open`) with no filter for entries whose
// `parent-child` dependency's parent was closed within the current
// outer run. The prompt should mention this filter explicitly so the
// LLM applies it.
//
// Signal the fix has been applied by requiring EITHER a mention of
// `parent-child` OR of `recently-closed` in the orchestrator body.
// This is a lint-style check on prompt text, not on behaviour: the
// fix must instruct the LLM to exclude recently-closed parents'
// children.
// Defect 3 — subtask spawn after parent closes (mitto-fko). The merged
// orchestrator's Step 2 must mention the parent-child / recently-closed
// filter so the LLM excludes beads whose parent was closed within the
// current outer run.
parentChild := regexp.MustCompile(`parent-child`)
recentlyClosed := regexp.MustCompile(`recently[- ]closed`)
for _, name := range []string{bugsOrch, featsOrch} {
body := load(name)
{
body := load(mergedOrch)
if !parentChild.MatchString(body) && !recentlyClosed.MatchString(body) {
t.Errorf("[defect-3 subtask-spawn, mitto-fko] %s Step 2 has no filter language for `parent-child` deps or `recently-closed` parents; expected instructions to exclude beads whose parent bead was closed within the current outer run", name)
t.Errorf("[defect-3 subtask-spawn, mitto-fko] %s Step 2 has no filter language for `parent-child` deps or `recently-closed` parents; expected instructions to exclude beads whose parent bead was closed within the current outer run", mergedOrch)
}
}

// Defect 4 — Step 3f grand-child fan-out with inline free-text worker
// prompts (features driver only, worse than the bug family because it
// spawns from *inside* an already-scheduled loop driver).
// The current text tells the LLM to synthesize a "fully self-contained
// worker prompt" inline; the fix registers that worker body as a named
// workspace prompt and spawns it via `prompt_name:` + `arguments:`.
// prompts (features driver only). The driver must not tell the LLM to
// synthesize a "self-contained worker prompt" inline.
body := load(featDriver)
if strings.Contains(body, "self-contained worker prompt") {
t.Errorf("[defect-4 step3f-inline-worker, mitto-6am-unique] %s Step 3f still tells the driver LLM to seed a `self-contained worker prompt` inline; expected `mitto_conversation_new(..., prompt_name: \"<worker>\", arguments: {...})` so the grand-child body is expanded server-side and cannot short-circuit to a placeholder", featDriver)
_ = body
}

// Defect 5 (mitto-91wk) — mention driver must also honor all three
// structural anti-patterns. §A in the merged orchestrator spawns the
// mention router; the router itself is a phase-based driver just like
// the bug/feature drivers, so it inherits the same guards.
{
// dj9: mention-driver's phase dispatches must use `prompt_name:`
// (server-expanded named body) and must NOT ship a free-text
// `prompt:` alongside — the free text short-circuits the name.
mentionBody := load(mentionDriver)
// Assert that dispatches do reference the four phase prompt names —
// if they don't, the driver is broken.
for _, phaseName := range []string{
"Mention — investigate phase",
"Mention — plan phase",
"Mention — implement phase",
"Mention — answer phase",
} {
if !strings.Contains(mentionBody, phaseName) {
t.Errorf("[defect-5 mention-driver-missing-phase, mitto-91wk] %s does not reference phase prompt %q — driver cannot dispatch its state machine", mentionDriver, phaseName)
}
}
// Assert §A spawn uses prompt_name (not initial_prompt) for the
// mention driver seed, matching the §B/§C pattern.
orchBody := load(mergedOrch)
if !strings.Contains(orchBody, `prompt_name: "Mention — driver"`) {
t.Errorf("[defect-5 §A placeholder-vector, mitto-91wk] %s §A does not spawn via `prompt_name: \"Mention — driver\"`; expected server-expanded named body", mergedOrch)
}
if !strings.Contains(orchBody, `loop_prompt_name: "Mention — driver"`) {
t.Errorf("[defect-5 §A placeholder-vector, mitto-91wk] %s §A does not set `loop_prompt_name: \"Mention — driver\"`; expected the router to re-fire via named prompt", mergedOrch)
}
}

// Defect 6 (mitto-91wk) — mention driver must not synthesize inline
// free-text worker prompts (mitto-6am vector applied to the mention
// router). All phase dispatches must go through named prompts.
{
mentionBody := load(mentionDriver)
if strings.Contains(mentionBody, "self-contained worker prompt") {
t.Errorf("[defect-6 mention-inline-worker, mitto-91wk/mitto-6am] %s tells the driver LLM to seed a `self-contained worker prompt` inline; expected `mitto_conversation_send_prompt(..., prompt_name: \"<phase>\", ...)` so the phase body is server-expanded", mentionDriver)
}
// The router must never do phase-label writes inline — those are the
// phase prompts' job. It is allowed to *describe* the labels in
// branching prose, but must not contain a literal `bd update ...
// --add-label mention-{investigated,planned,implemented,answered}`
// command line inside a shell block.
forbiddenLabelWrites := regexp.MustCompile(`bd update[^\n]*--add-label mention-(investigated|planned|implemented|answered)`)
if forbiddenLabelWrites.MatchString(mentionBody) {
t.Errorf("[defect-6 mention-inline-label-write, mitto-91wk] %s writes a phase-scoped `--add-label mention-*` command inline; expected the phase prompts to own their own label writes", mentionDriver)
}
}
}

// TestBeadsLoopPrompts_Defects_mitto_i5k_BranchOrder is the failing reproduction
Expand Down Expand Up @@ -124,8 +158,9 @@ func TestBeadsLoopPrompts_Defects_mitto6am(t *testing.T) {
// green.
func TestBeadsLoopPrompts_Defects_mitto_i5k_BranchOrder(t *testing.T) {
const (
bugDriver = "beads-issue-loop-fixing-bug.prompt.yaml"
featDriver = "beads-issue-loop-implementing-feature.prompt.yaml"
bugDriver = "beads-issue-loop-fixing-bug.prompt.yaml"
featDriver = "beads-issue-loop-implementing-feature.prompt.yaml"
mentionDriver = "beads-issue-mention-driver.prompt.yaml"
)

load := func(name string) string {
Expand All @@ -143,6 +178,14 @@ func TestBeadsLoopPrompts_Defects_mitto_i5k_BranchOrder(t *testing.T) {
}{
{bugDriver, "Step 3a: dispatch Investigate", "Step 3d: Done (handled inline)"},
{featDriver, "Step 3a: dispatch Plan", "Step 3e: Done (handled inline)"},
// mitto-91wk: mention driver applies the same branch-order rule.
// Its terminal branch is "Step 3f: Finalize (handled inline)" and
// must be enumerated BEFORE the first-stage "dispatch Investigate"
// / "dispatch Answer" branches. Both markers are text that appears
// only inside the Step 3 branch enumeration (not the later section
// headings), so the first-occurrence semantics of strings.Index
// pin the assertion to the enumeration ordering.
{mentionDriver, "Step 3a (classify)", "Step 3f: Finalize (handled inline)"},
}

for _, c := range cases {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,21 @@ prompt: |
{{- end }}

{{ if $target -}}
## Tier check

This phase is declared to run on the **Coding** tier (`preferredModels: [{modelTag: Coding}]`).
Active model: `{{ if .Session.ModelName }}{{ .Session.ModelName }}{{ else }}<unknown>{{ end }}` (tags: {{ if .Session.ModelTags }}{{ Join ", " .Session.ModelTags }}{{ else }}none{{ end }}).

{{ if Model "Coding" -}}
✓ Coding tier confirmed — proceed.
{{- else -}}
⚠ **Tier-degraded run.** The active model does not carry the `Coding` tag, so the transient `preferredModels` switch did not land (either the tag is not installed on any workspace profile, or the current model is unknown). Before doing any phase work, record the degradation on the bead so it is visible in the audit trail, then continue on best effort:

```bash
bd comment {{ $target }} "⚠ tier-degraded [phase: implement]: declared Coding but running on '{{ if .Session.ModelName }}{{ .Session.ModelName }}{{ else }}<unknown>{{ end }}' (tags: {{ if .Session.ModelTags }}{{ Join ", " .Session.ModelTags }}{{ else }}none{{ end }})."
```
{{- end }}

## Step 1 — Load LIVE state

Labels drift between runs. Load the bead's **current** state fresh:
Expand Down Expand Up @@ -81,7 +96,7 @@ prompt: |
## Step 3 — Record, advance the label{{ if $commit }}, and commit{{ end }}

```bash
bd comment {{ $target }} "Implementation: <what was built, files touched, any deviations from the plan and why>."
bd comment {{ $target }} "Implementation [tier: Coding]: <what was built, files touched, any deviations from the plan and why>."
bd update {{ $target }} --add-label implemented
```

Expand Down Expand Up @@ -145,3 +160,5 @@ prompt: |
- **Live state only.** Always re-read labels via `bd show --json` at the start of
the phase; never assume labels from a prior run.
- **Always log to the tracker** with `bd comment` so progress is auditable.
- **Tier tag every comment.** Prefix the summary with `Implementation [tier: Coding]:`
so the tier split is observable in the bead history (mitto-mpu5 acceptance criteria).
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,21 @@ prompt: |
{{- end }}

{{ if $target -}}
## Tier check

This phase is declared to run on the **Reasoning** tier (`preferredModels: [{modelTag: Reasoning}]`).
Active model: `{{ if .Session.ModelName }}{{ .Session.ModelName }}{{ else }}<unknown>{{ end }}` (tags: {{ if .Session.ModelTags }}{{ Join ", " .Session.ModelTags }}{{ else }}none{{ end }}).

{{ if Model "Reasoning" -}}
✓ Reasoning tier confirmed — proceed.
{{- else -}}
⚠ **Tier-degraded run.** The active model does not carry the `Reasoning` tag, so the transient `preferredModels` switch did not land (either the tag is not installed on any workspace profile, or the current model is unknown). Before doing any phase work, record the degradation on the bead so it is visible in the audit trail, then continue on best effort:

```bash
bd comment {{ $target }} "⚠ tier-degraded [phase: plan]: declared Reasoning but running on '{{ if .Session.ModelName }}{{ .Session.ModelName }}{{ else }}<unknown>{{ end }}' (tags: {{ if .Session.ModelTags }}{{ Join ", " .Session.ModelTags }}{{ else }}none{{ end }})."
```
{{- end }}

## Step 1 — Load LIVE state

Labels drift between runs. Load the bead's **current** state fresh:
Expand Down Expand Up @@ -73,7 +88,7 @@ prompt: |
bead and advance the state:

```bash
bd comment {{ $target }} "Plan: <approach, key design decisions, files/areas touched, any sub-issues created>."
bd comment {{ $target }} "Plan [tier: Reasoning]: <approach, key design decisions, files/areas touched, any sub-issues created>."
bd update {{ $target }} --add-label planned
```

Expand Down Expand Up @@ -114,3 +129,5 @@ prompt: |
- **Live state only.** Always re-read labels via `bd show --json` at the start of
the phase; never assume labels from a prior run or from `Item.*`.
- **Always log to the tracker** with `bd comment` so progress is auditable.
- **Tier tag every comment.** Prefix the plan with `Plan [tier: Reasoning]:` so the
tier split is observable in the bead history (mitto-mpu5 acceptance criteria).
Loading
Loading