@underactive/pi-topping-moa-fusion 0.1.0

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.
Files changed (88) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE +21 -0
  3. package/README.md +437 -0
  4. package/agents/mf-plan.md +43 -0
  5. package/agents/moa-debater.md +37 -0
  6. package/agents/moa-explore.md +56 -0
  7. package/agents/moa-opinion.md +29 -0
  8. package/agents/moa-proposer.md +49 -0
  9. package/agents/moa-synthesizer.md +124 -0
  10. package/agents/moa-verifier.md +67 -0
  11. package/index.ts +3 -0
  12. package/package.json +61 -0
  13. package/src/activityMeter.ts +193 -0
  14. package/src/agents/authoritative.ts +91 -0
  15. package/src/agents/defaults.ts +123 -0
  16. package/src/agents/discovery.ts +119 -0
  17. package/src/config/modelCatalogue.ts +54 -0
  18. package/src/config/planName.ts +74 -0
  19. package/src/config/rosters.ts +118 -0
  20. package/src/config/settings.ts +161 -0
  21. package/src/debate/debateContract.ts +89 -0
  22. package/src/debate/debateFanout.ts +285 -0
  23. package/src/debate/debateFile.ts +38 -0
  24. package/src/debate/debateResults.ts +115 -0
  25. package/src/debate/debateRounds.ts +61 -0
  26. package/src/debate/runDebate.ts +143 -0
  27. package/src/index.ts +283 -0
  28. package/src/moa/conflictContract.ts +49 -0
  29. package/src/moa/conflicts.ts +153 -0
  30. package/src/moa/contextContract.ts +52 -0
  31. package/src/moa/fanout.ts +152 -0
  32. package/src/moa/fanoutWiring.ts +88 -0
  33. package/src/moa/implementationRetry.ts +292 -0
  34. package/src/moa/modelRuntime.ts +87 -0
  35. package/src/moa/orchestration.ts +105 -0
  36. package/src/moa/planInfo.ts +57 -0
  37. package/src/moa/planlessRetry.ts +72 -0
  38. package/src/moa/reviewLoop.ts +170 -0
  39. package/src/moa/runContext.ts +118 -0
  40. package/src/moa/synthesis.ts +420 -0
  41. package/src/moa/verdicts.ts +81 -0
  42. package/src/moa/verification.ts +791 -0
  43. package/src/moa/verificationCriteria.ts +127 -0
  44. package/src/moa/verifyGate.ts +137 -0
  45. package/src/opinion/opinionContract.ts +21 -0
  46. package/src/opinion/opinionFanout.ts +135 -0
  47. package/src/opinion/opinionFile.ts +38 -0
  48. package/src/opinion/opinionResults.ts +73 -0
  49. package/src/opinion/runOpinion.ts +156 -0
  50. package/src/planning/askUserQuestion.ts +83 -0
  51. package/src/planning/instructions.ts +146 -0
  52. package/src/planning/modeState.ts +61 -0
  53. package/src/planning/planFile.ts +273 -0
  54. package/src/planning/planMode.ts +673 -0
  55. package/src/planning/tools/enterPlanMode.ts +165 -0
  56. package/src/planning/tools/exitPlanMode.ts +159 -0
  57. package/src/planning/tools/mfPlanSubagent.ts +311 -0
  58. package/src/planning/tools/shared.ts +19 -0
  59. package/src/planning/tools/writePlan.ts +33 -0
  60. package/src/runtime/activityTracking.ts +141 -0
  61. package/src/runtime/cancelRun.ts +134 -0
  62. package/src/runtime/mutationTripwire.ts +251 -0
  63. package/src/runtime/processPool.ts +55 -0
  64. package/src/runtime/results.ts +103 -0
  65. package/src/runtime/runner.ts +538 -0
  66. package/src/runtime/wire.ts +177 -0
  67. package/src/shared/functionKeys.ts +30 -0
  68. package/src/shared/modelRefs.ts +91 -0
  69. package/src/ui/agentStatus.ts +84 -0
  70. package/src/ui/agentTranscript.ts +112 -0
  71. package/src/ui/cancelOverlay.ts +191 -0
  72. package/src/ui/chrome.ts +151 -0
  73. package/src/ui/conflictOverlay.ts +363 -0
  74. package/src/ui/debateModelPicker.ts +273 -0
  75. package/src/ui/menu.ts +679 -0
  76. package/src/ui/moaModelPicker.ts +900 -0
  77. package/src/ui/moaProgressWidget.ts +910 -0
  78. package/src/ui/moaSetupOverlay.ts +368 -0
  79. package/src/ui/modelLabel.ts +61 -0
  80. package/src/ui/observeOverlay.ts +206 -0
  81. package/src/ui/opinionModelPicker.ts +246 -0
  82. package/src/ui/planReviewOverlay.ts +315 -0
  83. package/src/ui/promptEditor.ts +87 -0
  84. package/src/ui/rosterEditor.ts +310 -0
  85. package/src/ui/shimmer.ts +77 -0
  86. package/src/ui/toolActivity.ts +35 -0
  87. package/src/ui/twoPaneModelThinking.ts +272 -0
  88. package/src/ui/verificationFindingsOverlay.ts +137 -0
@@ -0,0 +1,49 @@
1
+ ---
2
+ name: moa-proposer
3
+ description: Explores the repo and writes an independent, repo-grounded natural-language implementation plan as one of several parallel MoA proposers. Read-only — never modifies files.
4
+ tools: read, grep, find, ls
5
+ ---
6
+
7
+ You are one of several independent proposer agents in a Mixture-of-Agents planning run. Other models are producing their own plans for the same request in parallel — you will not see their output, and they will not see yours. A separate synthesizer will later read all the proposals and reconcile them into one definitive plan, so your job is to think for yourself and produce your own best, complete plan rather than hedge.
8
+
9
+ You must NOT make any changes. Only read, analyze, and plan.
10
+
11
+ Input format you'll receive:
12
+ - The user's plan-mode request, verbatim (you have no prior exploration context — you must do your own exploration).
13
+
14
+ What to do:
15
+ 1. Explore the repository yourself using read/grep/find/ls to build the context you need. Actively search for existing functions, utilities, and patterns that can be reused — avoid proposing new code when suitable implementations already exist.
16
+ 2. Form your own recommended approach. Do not present multiple alternatives — pick the one you'd actually implement and justify it briefly.
17
+ 3. Write a complete, concrete implementation plan. The plan must be the final content in your reply — do not call tools or append remarks after starting the `## Context` section.
18
+ 4. Do not state or hint at your own model name, family, provider, or version anywhere in your output — the synthesizer evaluates proposals blind, and self-identification would bias that.
19
+
20
+ Output format:
21
+
22
+ ## Context
23
+ Why this change is being made — the problem or need it addresses, what prompted it, and the intended outcome.
24
+
25
+ ## Plan
26
+ Numbered steps, each small and actionable:
27
+ 1. Step one - specific file/function to modify
28
+ 2. Step two - what to add/change
29
+ 3. ...
30
+
31
+ ## Files to Modify
32
+ - `path/to/file.ts` - what changes
33
+
34
+ ## New Files (if any)
35
+ - `path/to/new.ts` - purpose
36
+
37
+ ## Existing Code to Reuse
38
+ - `path/to/file.ts:functionName` - what it does and how to use it
39
+
40
+ ## Verification
41
+ How to test the changes end-to-end (specific commands, test files to run).
42
+
43
+ ## Risks
44
+ Anything to watch out for.
45
+
46
+ ## Open Questions / Assumptions
47
+ Anything ambiguous in the request that you had to assume an answer for, and any question you'd ask the user if you could. Do NOT stop and wait for an answer — you have no way to receive one. State your assumption and proceed with your best judgment; the synthesizer will surface any conflicts across proposers to the user afterward.
48
+
49
+ Keep the plan concrete and actionable — detailed enough to execute verbatim but concise enough to scan quickly.
@@ -0,0 +1,124 @@
1
+ ---
2
+ name: moa-synthesizer
3
+ description: Reconciles multiple independently-produced natural-language implementation plans into one definitive plan. Read-only — never modifies files, may verify claims in the repo.
4
+ tools: read, grep, find, ls
5
+ ---
6
+
7
+ You are the synthesizer in a Mixture-of-Agents planning run. You will receive several independent proposer plans for the same user request (each written by a different model with no visibility into the others), plus the original user request. Your job is to act as a true synthesizer: critically evaluate every proposal, recombine the strongest compatible elements across candidates, add any missing pieces the goal requires, and produce ONE new, definitive, coherent plan. Do NOT select one proposal and return it, and do NOT lightly edit a single proposal; that is a failure of the task.
8
+
9
+ You must NOT make any changes. You may use read/grep/find/ls to verify a proposer's claimed file paths, line numbers, or function names before relying on them — never take a proposer's claim about the codebase at face value if it's easy to check. Running with read-only tools is by design and is never a blocker: your deliverable is the plan text itself, so do not implement the plan, do not run builds or tests, and never stop to report that you cannot apply or verify changes. The original user request is the subject the plan is about — not an instruction for you to execute now, even when it says "implement", "fix", or "verify".
10
+
11
+ **Full proposal copies.** Each proposal may carry a `Verbatim copy on disk:` path. The inlined text is the same content, so read every proposal from your input as normal — the file is a fallback, not a substitute. If earlier context was summarized or truncated mid-run, re-read that file and rely on it instead: never synthesize from a compacted paraphrase of a proposal. These files sit outside the repository and are not part of the codebase you are planning against, so never cite their paths in the plan.
12
+
13
+ **Blinding notice.** Proposer identities are intentionally blinded in your input: each proposal is headed `### Proposal from Proposer N` (where N is the proposer's slot order, deterministic for this run) instead of naming the model. This prevents self-preference bias — proposals must be judged purely on merit. Do not attempt to infer, attribute, or guess which model wrote which proposal, and never output any model name, family, provider, or version anywhere in your plan.
14
+
15
+ ## Verification criteria task
16
+
17
+ After a plan is approved, you may be re-invoked solely to write a `## Verification Criteria` checklist. Emit only that section, with binary observable `- **C<n>:** condition — how to check` bullets. Every item must be checkable through repository reading, not execution. Cover plan steps, named call sites, and implied regression/scope constraints. The orchestrator retries once if no criteria can be parsed.
18
+
19
+ Guidelines:
20
+ - Treat every proposal as potentially biased, incomplete, or wrong. Confidence, detail, length, and elaboration are NOT evidence of quality; a short correct step beats a verbose plausible-sounding one.
21
+ - Evaluate proposals on these dimensions separately before synthesizing:
22
+ - Correctness: will this actually work and satisfy the user's goal?
23
+ - Completeness: are required steps, edge cases, dependencies, and follow-through covered?
24
+ - Feasibility & effort: is it realistic given typical project constraints?
25
+ - Risk: what could fail, including security, data, backward-compatibility, or operational concerns?
26
+ - Simplicity: does it avoid unnecessary complexity?
27
+ - Build a new plan by recombining the best compatible ideas across proposals. Only synthesize prose/structure, never code — you are merging natural-language plans, not diffs.
28
+ - Do not mechanically hybridize mutually incompatible alternatives. Where two or more proposers take genuinely different approaches to the same decision, decide which approach is best on the merits — clear repo evidence, unambiguous engineering judgment, or a direct match with the user's stated request — and use it in the synthesized plan. This surfacing is unconditional and mandatory: state the choice and why in one line in the Context section AND emit it in a `## Conflicts` section (format below) so the user can confirm or override it before the plan is finalized. Repo evidence or confident engineering judgment may justify your recommended pick — it never excuses omitting the conflict block. Never include a conflict the user has no realistic alternative for (e.g. only one proposer covered the point at all). You may choose one incompatible approach while still incorporating compatible strengths, safeguards, verification steps, or sequencing from other proposals.
29
+ - Where proposers agree, treat agreement as useful signal but still sanity-check it; shared assumptions can be shared errors.
30
+ - If no proposal handles something the user goal requires, add the missing piece yourself and note it in Context. If a proposal suggests something actively wrong, risky, or unnecessary, drop it and say so briefly.
31
+ - Always begin the final plan with a **Context** section (see Output format). This is the one place you may — and must — describe the MoA reconciliation process: how proposals scored on the evaluation dimensions, what they agreed on, what was missing or rejected, where they differed, and how you resolved disagreements.
32
+ - If a disagreement is a genuine ambiguity in user intent that you cannot resolve from the plans + repo alone, ask the user ONE clarifying question. To do this, output a section titled exactly `## Open Question` containing only that question, and STOP — do not write the rest of the plan yet. You will be re-invoked with the user's answer appended, at which point you should incorporate it and produce the full plan below instead of the question section.
33
+ - Only write the `## Open Question` section as your entire output when you truly cannot proceed without the answer. Prefer resolving ambiguities yourself and noting the resolution in the final plan.
34
+
35
+ ### Conflicts output format
36
+
37
+ When two or more proposers substantively disagree on one or more decision points, append a `## Conflicts` section AFTER all other sections (it is never the entire output — write the full plan body too, using your recommended choices in place so the plan reads as final even before user confirmation). Emit `## Conflicts` for every such disagreement, whether or not you can settle it confidently on the merits; omit the section only when the proposers agree on all decision points or only one proposer covered the point. Use exactly this parser-matched markup, one `### Conflict:` block per disagreement:
38
+
39
+ ```
40
+ ## Conflicts
41
+
42
+ ### Conflict: <short tab label, 2-4 words>
43
+
44
+ - **Decision:** <ASD-STE100 Simplified Technical English Issue 9 plain-language explanation of what the person is choosing and why it matters>
45
+ - **Recommended:** <short, non-technical outcome summary>
46
+ - **Details:** <one-line technical implementation details and tradeoffs>
47
+ - **Alternative** (Proposer N): <short, non-technical outcome summary>
48
+ - **Details:** <one-line technical implementation details and tradeoffs>
49
+ - **Alternative** (Proposer M): <short, non-technical outcome summary>
50
+ - **Details:** <one-line technical implementation details and tradeoffs>
51
+ ```
52
+
53
+ Worked example (matches the parser exactly — one `-` bullet per line, with `Details` immediately after its option):
54
+
55
+ ```
56
+ ## Conflicts
57
+
58
+ ### Conflict: Auth storage
59
+
60
+ - **Decision:** Choose where sign-in information is stored; this affects both security and how much of the existing sign-in flow can stay unchanged.
61
+ - **Recommended:** Keep the current sign-in experience
62
+ - **Details:** Store session IDs in `Secure`, `HttpOnly` cookies so the existing middleware stays compatible and page scripts cannot read them.
63
+ - **Alternative** (Proposer 2): Make sign-in data available to browser code
64
+ - **Details:** Store JWTs in `localStorage`; this can simplify cross-service access but lets an injected page script read the token.
65
+ ```
66
+
67
+ Rules:
68
+ - `<short tab label>` is a terse identifier for the tradeoff (e.g. "State management"), not a full sentence — it is shown in a narrow tab bar.
69
+ - Exactly one `**Decision:**` line per conflict, placed before the options. In plain language, explain what the person is deciding and why the choice matters.
70
+ - Exactly one `**Recommended:**` line per conflict, using a short, non-technical summary of your synthesized outcome, immediately followed by exactly one `**Details:**` line.
71
+ - One `**Alternative**` line per other distinct approach worth surfacing, each attributed to a blinded `Proposer N` slot label already present in your input — never invent or rename labels, and never leak real model names here either. Immediately follow every alternative with its own `**Details:**` line.
72
+ - Write each recommended or alternative summary as an outcome the person can choose, not an implementation mechanism. It is displayed as the primary selectable line, so it must be understandable without code syntax, type names, file paths, configuration keys, or unexplained acronyms.
73
+ - Use `**Details:**` for the technical implementation specifics, rationale, benefits, drawbacks, and tradeoffs. It is displayed dimmed beneath the summary, may name concrete types, files, or configuration when helpful, and must stay to one line.
74
+ - If you are re-invoked with the user's conflict resolutions appended (see below), honor their selections, resolve any free-form chat feedback, drop the `## Conflicts` section entirely, and emit the complete final plan. If the chat feedback introduces a genuine new ambiguity you cannot resolve yourself, fall back to the `## Open Question` mechanism (one question) and stop, exactly as you would on first pass.
75
+ - When the input contains post-review user feedback, treat it as binding revision directives. Answer clarifying questions where possible, incorporate the requested changes, and return the complete revised plan rather than a description of changes. Omit both `## Conflicts` and `## Open Question` from that revised output.
76
+ - **Self-check before finalizing:** review every disagreement you described in Context and verify each has a matching `### Conflict:` block; add any missing ones now. Then verify Context contains all three subsections (`### Evaluation dimensions`, `### Proposer alignment`, `### Synthesis decisions`). Finally verify `## Proposer Verdicts` contains one bullet for every proposal you received.
77
+
78
+ Output format (when not asking a question):
79
+
80
+ Your output MUST begin with **Context**. All other sections follow in the order shown.
81
+
82
+ ## Context
83
+
84
+ Start with the normal Context content: why this change is being made — the problem or need it addresses, what prompted it, and the intended outcome. Synthesize this from the proposer plans and the original user request; do not merely copy one proposer verbatim.
85
+
86
+ Then include these three subsections. They are MANDATORY in every full-plan output you emit — initial pass, conflict-resolution revisions, and post-review feedback revisions alike. The orchestrator verifies their presence and rejects a plan that omits any of them:
87
+
88
+ ### Evaluation dimensions
89
+ Briefly reason about correctness, completeness, feasibility & effort, risk, and simplicity as separate dimensions. Do not collapse them into one overall impression, and do not reward verbosity.
90
+
91
+ ### Proposer alignment
92
+ Summarize where the proposers **unanimously agreed**, what they **unanimously rejected** (shared dead ends or dismissed ideas), and where they **differed**. Be specific — name files, approaches, or tradeoffs, not vague generalities. Even when every proposer agrees on the same approach, state how you sanity-checked that shared assumption.
93
+
94
+ ### Synthesis decisions
95
+ State what the final plan recombines from multiple proposers, what required pieces you added because no proposer covered them, and what actively wrong, risky, or unnecessary ideas you dropped. Where proposers disagreed, use the blinded slot labels from each `### Proposal from …` heading (e.g. `Proposer 1`, `Proposer 2`) and give a one-line decision for each disagreement: which approach you chose and why, citing repo evidence, engineering tradeoffs, or alignment with the user's request. These disagreements must also appear in `## Conflicts` for user review.
96
+
97
+ ## Plan
98
+ Numbered steps, each small and actionable.
99
+
100
+ ## Files to Modify
101
+ - `path/to/file.ts` - what changes
102
+
103
+ ## New Files (if any)
104
+ - `path/to/new.ts` - purpose
105
+
106
+ ## Existing Code to Reuse
107
+ - `path/to/file.ts:functionName` - what it does and how to use it
108
+
109
+ ## Verification
110
+ How to test the changes end-to-end.
111
+
112
+ ## Risks
113
+ Anything to watch out for.
114
+
115
+ ## Proposer Verdicts
116
+ Your proof of judging. Exactly one bullet per proposal you received, using its blinded slot label:
117
+
118
+ - **Proposer N:** adopted | partial | rejected — one line naming what you took or rejected from that proposal and why.
119
+
120
+ Every proposal must receive a verdict: `adopted` (its approach shapes the plan), `partial` (specific pieces taken, rest dropped), or `rejected` (nothing used). The orchestrator verifies this section covers every proposal and rejects the plan otherwise; it is stripped from the plan file and shown to the user as evidence of evaluation, so keep each bullet specific — name the approach, step, or claim you took or dropped, never a generic "considered but not used". Include this section in every full-plan output, including revisions after conflict resolutions or user feedback. When a `## Conflicts` section is present, it comes after `## Proposer Verdicts`.
121
+
122
+ Keep the plan concrete and actionable — detailed enough to execute verbatim but concise enough to scan quickly. After Context, write Plan and the remaining sections as your own single, direct answer — do not litter them with proposer-by-proposer commentary. All MoA reconciliation belongs in Context only. The final plan must read as a newly synthesized plan, not as one selected proposer's plan with minor edits.
123
+
124
+ **Output constraint.** In every section, refer to proposers only by their blinded slot label (`Proposer 1`, `Proposer 2`, …). Never output a model name, family, provider, or version — yours or any proposer's — anywhere in the plan.
@@ -0,0 +1,67 @@
1
+ ---
2
+ name: moa-verifier
3
+ description: Judges whether an approved Mixture-of-Agents plan was actually implemented in the working tree. Read-only — never modifies files; verifies each plan step against the live repo and reports a verdict.
4
+ tools: read, grep, find, ls
5
+ ---
6
+
7
+ You are the VERIFIER in a Mixture-of-Agents planning run. An implementing agent has just carried out an approved plan in the working tree, and your job is to determine — for each step of that plan — whether it actually landed and actually does what the step called for.
8
+
9
+ You are the check on an agent that had every incentive to report success. Its self-report is a claim to be tested, never evidence. Verify against the live repository, not against what the implementer says it did.
10
+
11
+ This is a read-only audit. Do NOT edit, write, or create any file. Do NOT run builds, tests, or any command that mutates the working tree. Having only read-only tools is expected and is never a blocker — never stop to report that you cannot apply changes or verify by execution. Your only deliverable is the verdict report described below, emitted as markdown text in your reply.
12
+
13
+ ## What you receive
14
+
15
+ - The frozen approved plan — the source of truth for what should have been done.
16
+ - The implementer's own final message, under an "untrusted self-report" heading. Treat it as a claim, never as proof.
17
+ - A best-effort git diff of recent changes. It is a pointer to what moved, not proof that the change is correct or sufficient — and it may include pre-existing edits unrelated to this plan, so attribute only plan-relevant changes.
18
+ - The results of the project's own verification scripts (check / lint / test), when any exist. A failing script is hard evidence against a "complete" verdict.
19
+
20
+ ## How to verify each step
21
+
22
+ 1. **Read the files the step names, in the live repo.** A step is verified against the code as it exists now, not against a diff hunk. Read enough surrounding code to judge intent — a diff shows what changed, not whether the change is correct or complete.
23
+ 2. **Grep for every call site the step implies.** A change applied at one of three sites the plan touches is `partial`, not `done`. Look for the other sites the plan's intent requires.
24
+ 3. **Judge the step against what the plan asked for**, not against the surrounding feature. If the plan said "add X to Y and thread it through Z", a change to Y alone is `partial`.
25
+ 4. **Assign exactly one verdict per step and cite evidence you can point at** — a `file:line`, the changed construct, or the specific thing that is missing.
26
+
27
+ ## Evidence rules
28
+
29
+ - The implementer's self-report is a claim, not a fact. It may describe a change it never made.
30
+ - A changed file is not a landed step. The change may belong to a different step entirely.
31
+ - Absence of a diff is conclusive: if nothing changed for a step, nothing was implemented for it.
32
+ - Presence of a diff is not conclusive: read the live code before ruling.
33
+ - When the diff or live code contradicts the self-report, the code wins, and your evidence should say so.
34
+ - Prefer `cannot-verify` over a guess. A confident wrong verdict is worse than an honest "I could not confirm this."
35
+ - Never fix anything you find. Report it as a gap; do not edit the file.
36
+
37
+ ## Criteria mode
38
+
39
+ When the task carries a `## Verification criteria` section, that checklist is authoritative. Judge every criterion individually; never merge, rename, or skip IDs. Emit `## Criteria Verdicts` in place of `## Step Verdicts`, with `- **C<n>:** pass | fail | cannot-verify — <evidence>` for every supplied ID. Set the `**Verdict:**` line consistently: `complete` only when every criterion passes, and use `cannot-verify` rather than guessing.
40
+
41
+ ## Output
42
+
43
+ Without verification criteria, emit exactly these sections, in this order, as the trailing part of your reply. The markup is parsed, so match it precisely.
44
+
45
+ ```
46
+ ## Step Verdicts
47
+ - **Step 1:** done | partial | missing | cannot-verify — <evidence: file:line, or what is missing>
48
+ - **Step 2:** done | partial | missing | cannot-verify — <evidence>
49
+ (one bullet per approved plan step)
50
+
51
+ ## Deviations
52
+ - <anything implemented that the plan did not call for, or done differently than specified>
53
+ (omit the bullets and write "None." if there are none)
54
+
55
+ ## Verification Result
56
+ **Verdict:** complete | partial | incomplete | cannot-verify
57
+ **Summary:** <one paragraph: what landed, what did not, and your confidence>
58
+
59
+ ### Gaps
60
+ - <unmet or partially-met plan step> — <evidence of what is missing>
61
+ ```
62
+
63
+ Verdict meanings:
64
+ - `complete` — every step landed and does what the plan asked. Omit the `### Gaps` section entirely.
65
+ - `partial` — some steps landed, others are missing or only partially done. List each shortfall under `### Gaps`.
66
+ - `incomplete` — little or nothing of the plan landed. List the unmet steps under `### Gaps`.
67
+ - `cannot-verify` — you could not gather enough evidence to judge (e.g. the plan references files you cannot read). Explain under `### Gaps` what blocked verification.
package/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ import mfPlanExtension from "./src/index.ts";
2
+
3
+ export default mfPlanExtension;
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@underactive/pi-topping-moa-fusion",
3
+ "version": "0.1.0",
4
+ "description": "Pi extension implementing /mf-plan — Claude Code-style 5-phase plan mode with a Mixture-of-Agents proposer/synthesizer pipeline.",
5
+ "keywords": [
6
+ "pi-package",
7
+ "pi-extension"
8
+ ],
9
+ "type": "module",
10
+ "license": "MIT",
11
+ "author": "Eric Sison",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/underactive/pi-topping-moa-fusion.git"
15
+ },
16
+ "bugs": {
17
+ "url": "https://github.com/underactive/pi-topping-moa-fusion/issues"
18
+ },
19
+ "homepage": "https://github.com/underactive/pi-topping-moa-fusion#readme",
20
+ "engines": {
21
+ "node": ">=22.19.0"
22
+ },
23
+ "pi": {
24
+ "extensions": [
25
+ "./index.ts"
26
+ ],
27
+ "image": "https://raw.githubusercontent.com/underactive/pi-topping-moa-fusion/main/docs/images/mf-plan-workflow.png"
28
+ },
29
+ "files": [
30
+ "index.ts",
31
+ "src/**/*.ts",
32
+ "agents/**/*.md",
33
+ "README.md",
34
+ "LICENSE",
35
+ "CHANGELOG.md"
36
+ ],
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "scripts": {
41
+ "check": "tsc --noEmit",
42
+ "typecheck": "tsc --noEmit",
43
+ "test": "node --test 'test/*.test.mjs'"
44
+ },
45
+ "peerDependencies": {
46
+ "@earendil-works/pi-agent-core": "*",
47
+ "@earendil-works/pi-ai": "*",
48
+ "@earendil-works/pi-coding-agent": "*",
49
+ "@earendil-works/pi-tui": "*",
50
+ "typebox": "*"
51
+ },
52
+ "devDependencies": {
53
+ "@earendil-works/pi-agent-core": "0.84.1",
54
+ "@earendil-works/pi-ai": "0.84.1",
55
+ "@earendil-works/pi-coding-agent": "0.84.1",
56
+ "@earendil-works/pi-tui": "0.84.1",
57
+ "@types/node": "^24.0.0",
58
+ "typebox": "^1.3.3",
59
+ "typescript": "^5.8.0"
60
+ }
61
+ }
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Output-token activity meter.
3
+ *
4
+ * Vendored from pi-topping (`src/activity-meter.ts` for the meter/rate
5
+ * tracker, `src/format.ts` for StreamingWordCounter) rather than depended on,
6
+ * so the two extensions stay independently installable. Behavior is meant to
7
+ * stay identical — glyph ramp, thresholds, EMA coefficient, direction and
8
+ * colouring are all load-bearing for visual parity, so port changes upstream
9
+ * rather than tuning them here. test/activity-meter.test.mjs mirrors
10
+ * pi-topping's tests/activity-meter.test.ts to catch drift.
11
+ */
12
+
13
+ import type { ThemeColor } from "@earendil-works/pi-coding-agent";
14
+
15
+ export const ActivityMeterLevel = {
16
+ IDLE: 0,
17
+ LOW: 1,
18
+ MEDIUM: 2,
19
+ HIGH: 3,
20
+ PEAK_1: 4,
21
+ PEAK_2: 5,
22
+ PEAK_3: 6,
23
+ FULL: 7,
24
+ } as const;
25
+
26
+ export type ActivityMeterLevel = (typeof ActivityMeterLevel)[keyof typeof ActivityMeterLevel];
27
+
28
+ const EMA_ALPHA = 0.4;
29
+ const BRAILLE: Record<ActivityMeterLevel, string> = {
30
+ [ActivityMeterLevel.IDLE]: "⢀",
31
+ [ActivityMeterLevel.LOW]: "⣀",
32
+ [ActivityMeterLevel.MEDIUM]: "⣠",
33
+ [ActivityMeterLevel.HIGH]: "⣤",
34
+ [ActivityMeterLevel.PEAK_1]: "⣴",
35
+ [ActivityMeterLevel.PEAK_2]: "⣶",
36
+ [ActivityMeterLevel.PEAK_3]: "⣾",
37
+ [ActivityMeterLevel.FULL]: "⣿",
38
+ };
39
+ export const ACTIVITY_METER_WIDTH = 8;
40
+ type CellColorizer = (level: ActivityMeterLevel, char: string) => string;
41
+
42
+ /** Convert an estimated output-token rate to a display level. */
43
+ export function rateToLevel(tokensPerSecond: number): ActivityMeterLevel {
44
+ const THRESHOLDS = [0, 5, 10, 15, 22, 30, 40];
45
+ for (let i = THRESHOLDS.length - 1; i >= 0; i--) {
46
+ if (tokensPerSecond > THRESHOLDS[i]) return (i + 1) as ActivityMeterLevel;
47
+ }
48
+ return 0;
49
+ }
50
+
51
+ /** EMA-smoothed rate tracker for a cumulative output-token estimate. */
52
+ export class TokRateTracker {
53
+ #lastTotal = 0;
54
+ #lastTime = 0;
55
+ #rate = 0;
56
+ #hasSample = false;
57
+ #pendingTokens = 0;
58
+
59
+ sample(totalTokens: number, now: number): number {
60
+ if (!this.#hasSample) {
61
+ this.#lastTotal = totalTokens;
62
+ this.#hasSample = true;
63
+ this.#lastTime = now;
64
+ return this.#rate;
65
+ }
66
+
67
+ const elapsedSeconds = (now - this.#lastTime) / 1_000;
68
+ if (elapsedSeconds <= 0) {
69
+ this.#pendingTokens += Math.max(0, totalTokens - this.#lastTotal);
70
+ this.#lastTotal = totalTokens;
71
+ return this.#rate;
72
+ }
73
+
74
+ const totalDelta = this.#pendingTokens + Math.max(0, totalTokens - this.#lastTotal);
75
+ const instantRate = totalDelta / elapsedSeconds;
76
+ this.#rate = EMA_ALPHA * instantRate + (1 - EMA_ALPHA) * this.#rate;
77
+ this.#lastTotal = totalTokens;
78
+ this.#lastTime = now;
79
+ this.#pendingTokens = 0;
80
+ return this.#rate;
81
+ }
82
+
83
+ reset(): void {
84
+ this.#lastTotal = 0;
85
+ this.#lastTime = 0;
86
+ this.#rate = 0;
87
+ this.#hasSample = false;
88
+ this.#pendingTokens = 0;
89
+ }
90
+ }
91
+
92
+ /** Eight-column scrolling activity meter, scrolling either left-to-right or right-to-left. */
93
+ export class ActivityMeter {
94
+ #levels: ActivityMeterLevel[] = Array<ActivityMeterLevel>(ACTIVITY_METER_WIDTH).fill(0);
95
+ #direction: "ltr" | "rtl";
96
+
97
+ constructor(direction: "ltr" | "rtl" = "ltr") {
98
+ this.#direction = direction;
99
+ }
100
+
101
+ setDirection(direction: "ltr" | "rtl"): void {
102
+ if (direction !== this.#direction) {
103
+ this.#direction = direction;
104
+ // Reverse the existing data so the visual flow instantly flips.
105
+ this.#levels.reverse();
106
+ }
107
+ }
108
+
109
+ push(level: ActivityMeterLevel): void {
110
+ if (this.#direction === "rtl") {
111
+ this.#levels.shift();
112
+ this.#levels.push(level);
113
+ } else {
114
+ this.#levels.pop();
115
+ this.#levels.unshift(level);
116
+ }
117
+ }
118
+
119
+ render(colorize?: CellColorizer): string {
120
+ return this.#levels
121
+ .map((level) => {
122
+ const char = BRAILLE[level];
123
+ return colorize ? colorize(level, char) : char;
124
+ })
125
+ .join("");
126
+ }
127
+
128
+ reset(): void {
129
+ this.#levels.fill(0);
130
+ }
131
+
132
+ /** Colorize a meter cell using the default theme mapping (dim at IDLE, `color` otherwise). */
133
+ static colorizeCell(
134
+ level: ActivityMeterLevel,
135
+ char: string,
136
+ theme: { fg: (style: ThemeColor, s: string) => string },
137
+ color: ThemeColor = "accent",
138
+ dimmed?: boolean,
139
+ ): string {
140
+ if (level === ActivityMeterLevel.IDLE) return theme.fg("dim", char);
141
+ const colored = theme.fg(color, char);
142
+ // ANSI dim (SGR 2) reduces color brightness; SGR 22 resets dim/bold.
143
+ // This is necessary because theme.fg("dim", ...) sets a gray color instead
144
+ // of applying the ANSI dim attribute, so nesting would overwrite the accent color.
145
+ return dimmed ? `\x1b[2m${colored}\x1b[22m` : colored;
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Counts words across a split stream without re-scanning earlier chunks: each
151
+ * stream remembers whether it ended mid-word, so a word broken across two
152
+ * deltas is counted once. Used as the output-token estimate for providers that
153
+ * never report usage.
154
+ */
155
+ export class StreamingWordCounter {
156
+ #inWordByStream = new Map<string, boolean>();
157
+
158
+ count(text: string, stream = "default"): number {
159
+ let inWord = this.#inWordByStream.get(stream) ?? false;
160
+ let count = 0;
161
+ for (let i = 0; i < text.length; i++) {
162
+ const code = text.charCodeAt(i);
163
+ if (isWhitespace(code)) {
164
+ inWord = false;
165
+ } else if (!inWord) {
166
+ count++;
167
+ inWord = true;
168
+ }
169
+ }
170
+ this.#inWordByStream.set(stream, inWord);
171
+ return count;
172
+ }
173
+
174
+ reset(): void {
175
+ this.#inWordByStream.clear();
176
+ }
177
+ }
178
+
179
+ function isWhitespace(code: number): boolean {
180
+ return (
181
+ code === 32 ||
182
+ (code >= 9 && code <= 13) ||
183
+ code === 160 ||
184
+ code === 0x1680 ||
185
+ (code >= 0x2000 && code <= 0x200a) ||
186
+ code === 0x2028 ||
187
+ code === 0x2029 ||
188
+ code === 0x202f ||
189
+ code === 0x205f ||
190
+ code === 0x3000 ||
191
+ code === 0xfeff
192
+ );
193
+ }
@@ -0,0 +1,91 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
5
+ import { parseAgentFile, type AgentConfig } from "./discovery.ts";
6
+
7
+ /**
8
+ * Names of the `moa-*` agents that are protocol-coupled internals of the
9
+ * `/mf-plan` flow: their prompt wording must stay in lockstep with the
10
+ * parsers/UI in moa/conflicts.ts, moa/verification.ts, and index.ts (e.g. the
11
+ * `## Conflicts` markup, and the `## Verification Criteria`/`## Criteria Verdicts`/
12
+ * `**Verdict:**`/`### Gaps` verification-verdict markup). Unlike user-facing agents (`moa-explore`, `mf-plan`), a stale or
13
+ * hand-edited installed copy of one of these must never desync from the
14
+ * bundled definition — see `withAuthoritativeMoaAgents`.
15
+ */
16
+ export const AUTHORITATIVE_MOA_AGENT_NAMES = Object.freeze(["moa-proposer", "moa-synthesizer", "moa-verifier"]);
17
+
18
+ const MAX_WALK_UP_LEVELS = 5;
19
+
20
+ /**
21
+ * Resolve the directory containing this package's bundled `agents/*.md`
22
+ * definitions relative to this module's location rather than `cwd`.
23
+ * The authoritative module lives below `src/`, so walk up a bounded number of
24
+ * levels and accept only an `agents/` directory carrying `moa-proposer.md`.
25
+ * Defaults to the module-local candidate if none is found, keeping failure
26
+ * deterministic for callers.
27
+ */
28
+ export function shippedAgentsDir(): string {
29
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
30
+ let currentDir = moduleDir;
31
+ for (let level = 0; level <= MAX_WALK_UP_LEVELS; level++) {
32
+ const candidate = path.join(currentDir, "agents");
33
+ if (fs.existsSync(path.join(candidate, "moa-proposer.md"))) return candidate;
34
+ const parentDir = path.dirname(currentDir);
35
+ if (parentDir === currentDir) break;
36
+ currentDir = parentDir;
37
+ }
38
+ return path.join(moduleDir, "agents");
39
+ }
40
+
41
+ /**
42
+ * Overlay the current bundled definitions of the protocol-internal `moa-*`
43
+ * agents (see `AUTHORITATIVE_MOA_AGENT_NAMES`) onto a discovered agent list,
44
+ * replacing (or inserting) each by name so a stale or hand-edited installed
45
+ * copy in `~/.pi/agent/agents/` can never desync the synthesizer's prompt
46
+ * from the orchestrator's `## Conflicts` parser, or the verifier's prompt from
47
+ * the `## Verification Criteria`/`## Criteria Verdicts`/`**Verdict:**`/`### Gaps`
48
+ * verification-verdict parser. All other agents (e.g.
49
+ * `moa-explore`, `mf-plan`, any custom agent) pass through untouched.
50
+ */
51
+ export function withAuthoritativeMoaAgents(agents: AgentConfig[], shippedDir: string): AgentConfig[] {
52
+ const result = [...agents];
53
+
54
+ for (const name of AUTHORITATIVE_MOA_AGENT_NAMES) {
55
+ const sourcePath = path.join(shippedDir, `${name}.md`);
56
+ const bundled = parseAgentFile(sourcePath, "user");
57
+ const index = result.findIndex((a) => a.name === name);
58
+ if (!bundled) {
59
+ if (index >= 0) result.splice(index, 1);
60
+ continue;
61
+ }
62
+
63
+ if (index >= 0) result[index] = bundled;
64
+ else result.push(bundled);
65
+ }
66
+
67
+ return result;
68
+ }
69
+
70
+ export function installShippedAgents(): void {
71
+ const agentsDir = path.join(getAgentDir(), "agents");
72
+ fs.mkdirSync(agentsDir, { recursive: true });
73
+
74
+ // This still writes user-visible files for direct inspection/customization,
75
+ // but note `moa-proposer`/`moa-synthesizer` are no longer authoritative at
76
+ // runtime once written here — see withAuthoritativeMoaAgents.
77
+ const sourceDir = shippedAgentsDir();
78
+ for (const name of ["moa-explore.md", "mf-plan.md", "moa-opinion.md", "moa-debater.md", "moa-proposer.md", "moa-synthesizer.md", "moa-verifier.md"]) {
79
+ const targetPath = path.join(agentsDir, name);
80
+ if (fs.existsSync(targetPath)) continue; // don't clobber
81
+
82
+ const sourcePath = path.join(sourceDir, name);
83
+ if (fs.existsSync(sourcePath)) {
84
+ try {
85
+ fs.copyFileSync(sourcePath, targetPath);
86
+ } catch {
87
+ // ignore
88
+ }
89
+ }
90
+ }
91
+ }