@codions/portta 0.1.0 → 0.2.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 (86) hide show
  1. package/README.md +41 -88
  2. package/dist/assets/agents/hook.sh.tmpl +21 -0
  3. package/dist/assets/agents/instruction.md +8 -0
  4. package/dist/assets/agents/portta-context.ts.tmpl +24 -0
  5. package/dist/assets/skills/portta-adopt-compose/SKILL.md +220 -0
  6. package/dist/assets/skills/portta-dockerize/SKILL.md +118 -0
  7. package/dist/assets/skills/portta-host-context/SKILL.md +142 -0
  8. package/dist/cli.js +1542 -1017
  9. package/dist/documentation.json.gz +0 -0
  10. package/dist/runtime/.env.example +6 -42
  11. package/dist/runtime/VERSION +1 -1
  12. package/dist/runtime/docker/compose/features/web-dev.yaml +0 -2
  13. package/dist/runtime/docker/compose/features/web.yaml +0 -8
  14. package/dist/runtime/templates/README.md +3 -3
  15. package/dist/runtime/templates/overlays/06-monorepo.yaml +0 -7
  16. package/dist/runtime/templates/overlays/07-worktree.env +0 -1
  17. package/dist/runtime/templates/project/PORTTA.md +8 -7
  18. package/package.json +4 -15
  19. package/dist/acp-providers-M3DUUBYJ.js +0 -3
  20. package/dist/arktype-aI7TBD0R-SOJGD3SZ.js +0 -3
  21. package/dist/assets/skills/portta-workflows/SKILL.md +0 -285
  22. package/dist/assets/workflows/bake-off.workflow.js +0 -172
  23. package/dist/assets/workflows/code-review.workflow.js +0 -179
  24. package/dist/assets/workflows/deep-research.workflow.js +0 -223
  25. package/dist/assets/workflows/multi-provider-review.workflow.js +0 -169
  26. package/dist/assets/workflows/provider-debate.workflow.js +0 -141
  27. package/dist/assets/workflows/second-opinion.workflow.js +0 -176
  28. package/dist/chunk-3EZNE4MC.js +0 -6
  29. package/dist/chunk-4TGTGGMK.js +0 -4
  30. package/dist/chunk-4YGEG6YX.js +0 -1878
  31. package/dist/chunk-5SBYE3YH.js +0 -26
  32. package/dist/chunk-6J5HN4ZN.js +0 -3
  33. package/dist/chunk-6TV2V7RR.js +0 -3
  34. package/dist/chunk-CFWGQ7VJ.js +0 -19
  35. package/dist/chunk-EFZZFIRZ.js +0 -3
  36. package/dist/chunk-EH7H7D7J.js +0 -15
  37. package/dist/chunk-EYT7VTYA.js +0 -4
  38. package/dist/chunk-FRQCKCCK.js +0 -332
  39. package/dist/chunk-GI2EUNAK.js +0 -16
  40. package/dist/chunk-HB5T7XDN.js +0 -5
  41. package/dist/chunk-IILMQFWO.js +0 -14
  42. package/dist/chunk-LAYNTFDR.js +0 -70
  43. package/dist/chunk-MH5YJA5O.js +0 -2
  44. package/dist/chunk-O6U47HHQ.js +0 -341
  45. package/dist/chunk-OPVZAG2C.js +0 -10
  46. package/dist/chunk-PTKKSRMI.js +0 -223
  47. package/dist/chunk-Q3VNQ3LU.js +0 -3
  48. package/dist/chunk-QBKADN76.js +0 -4
  49. package/dist/chunk-QS5PHDYT.js +0 -240
  50. package/dist/chunk-SBVO6RXF.js +0 -87
  51. package/dist/chunk-T3TSZO4I.js +0 -7
  52. package/dist/chunk-TA2XHP5D.js +0 -4
  53. package/dist/chunk-UUSUJUAD.js +0 -7
  54. package/dist/chunk-UX7P3YHF.js +0 -3
  55. package/dist/chunk-VDSWNWUY.js +0 -168
  56. package/dist/chunk-WNZR7VOO.js +0 -145
  57. package/dist/chunk-YYSFNLBX.js +0 -5
  58. package/dist/cli-O2MMC25H.js +0 -98
  59. package/dist/completions-F4476JAU.js +0 -12
  60. package/dist/config-E5XLLPKE.js +0 -3
  61. package/dist/core-FSKGFGJO.js +0 -18
  62. package/dist/default-u_dwuiYb-OWTONHM6.js +0 -3
  63. package/dist/dispatch-ZCNGHCNP.js +0 -8
  64. package/dist/doctor-7F6ZXPZE.js +0 -5
  65. package/dist/effect-QlVUlMFu-4UNP2EGS.js +0 -3
  66. package/dist/esm-575VQW4T.js +0 -4
  67. package/dist/host.js +0 -8
  68. package/dist/init-HGEXUJP3.js +0 -245
  69. package/dist/linear-commands-XGHGICI7.js +0 -5
  70. package/dist/oneshot-DL57ZA55.js +0 -36
  71. package/dist/opencode-YDUTVKTG.js +0 -3
  72. package/dist/pi-L73UX3S5.js +0 -3
  73. package/dist/project-commands-2N44CRGM.js +0 -7
  74. package/dist/project-env-NWBVITHM.js +0 -4
  75. package/dist/run-commands-TCBNFJOT.js +0 -13
  76. package/dist/runtime/docker/compose/features/panel-host.yaml +0 -42
  77. package/dist/shell-ZWXPCUMD.js +0 -3
  78. package/dist/subprocess-jsonl-FQKDRENI.js +0 -3
  79. package/dist/supervisor.js +0 -179
  80. package/dist/sury-CWZTCd75-XQOYZHNU.js +0 -3
  81. package/dist/typebox-Dei93FPO-RCGWIHUQ.js +0 -3
  82. package/dist/valibot--1zFm7rT-DKLMYF44.js +0 -3
  83. package/dist/valibot-D_HTw1Gn-QTR63R64.js +0 -4
  84. package/dist/worktree-commands-F7REOKFJ.js +0 -50
  85. package/dist/zod-Bwrt9trS-6RSVELLV.js +0 -3
  86. package/dist/zod-DSgpEGAE-5JBFOUHY.js +0 -3
@@ -1,169 +0,0 @@
1
- // Multi-provider review: the same change reviewed twice — once by Codex, once by Claude — each
2
- // over the ENTIRE feature/branch, then a synthesis pass merges both: consensus findings (both
3
- // providers saw it → highest confidence), unique catches, and disagreements called out.
4
- // Run with: portta flow run workflow multi-provider-review [--input-json '{"target": "<ref|path|diff>"}']
5
- // or: portta flow workflows run multi-provider-review [--args '{"target": "<ref|path|diff>"}']
6
-
7
- export const meta = {
8
- name: "multi-provider-review",
9
- description:
10
- "Dual-provider review — Codex and Claude each review the full feature/branch independently, then a synthesis merges both: consensus, unique catches, disagreements.",
11
- defaultSandbox: "read-only",
12
- workspace: {
13
- default: "current_branch",
14
- allowed: ["isolated_worktree", "new_branch", "current_branch"],
15
- mutatesRepository: false,
16
- reason: "Both reviewers inspect the active checkout read-only.",
17
- },
18
- phases: [
19
- { title: "Review", detail: "Codex and Claude each review the entire change independently" },
20
- { title: "Synthesize", detail: "merge both reviews: consensus, unique findings, ranked report" },
21
- ],
22
- }
23
-
24
- const MAX_FINDINGS_PER_REVIEW = 15
25
-
26
- const REVIEWERS = [
27
- { provider: "codex", model: "gpt-5.5", name: "Codex" },
28
- { provider: "claude-code", model: "claude-fable-5", name: "Claude" },
29
- ]
30
-
31
- const target =
32
- typeof args === "string" && args.trim()
33
- ? args.trim()
34
- : args && typeof args === "object" && typeof args.target === "string" && args.target.trim()
35
- ? args.target.trim()
36
- : "the current branch in its entirety — every change relative to its merge-base with the " +
37
- "repository's default branch (git merge-base, then git diff <base>...HEAD), plus any " +
38
- "uncommitted changes; if already on the default branch, the most recent commit plus any " +
39
- "uncommitted changes"
40
-
41
- const REVIEW_SCHEMA = {
42
- type: "object",
43
- required: ["assessment", "findings"],
44
- properties: {
45
- assessment: { type: "string" },
46
- findings: {
47
- type: "array",
48
- items: {
49
- type: "object",
50
- required: ["file", "title", "why", "severity"],
51
- properties: {
52
- file: { type: "string" },
53
- line: { type: "number" },
54
- title: { type: "string" },
55
- why: { type: "string" },
56
- severity: { type: "string", enum: ["critical", "high", "medium", "low"] },
57
- },
58
- },
59
- },
60
- },
61
- }
62
-
63
- const SYNTHESIS_SCHEMA = {
64
- type: "object",
65
- required: ["report", "findings"],
66
- properties: {
67
- report: { type: "string" },
68
- findings: {
69
- type: "array",
70
- items: {
71
- type: "object",
72
- required: ["file", "title", "why", "severity", "foundBy"],
73
- properties: {
74
- file: { type: "string" },
75
- line: { type: "number" },
76
- title: { type: "string" },
77
- why: { type: "string" },
78
- severity: { type: "string", enum: ["critical", "high", "medium", "low"] },
79
- foundBy: { type: "array", items: { type: "string", enum: REVIEWERS.map((r) => r.name) } },
80
- },
81
- },
82
- },
83
- },
84
- }
85
-
86
- // ---------------------------------------------------------------------------
87
- // Review — both providers read the same full change, blind to each other.
88
- // The prompts are identical so differences in output reflect the models, not
89
- // the framing; neither is told another reviewer exists.
90
- // ---------------------------------------------------------------------------
91
- phase("Review")
92
- log(`dual-provider review of ${target}`)
93
-
94
- const reviewPrompt =
95
- `You are conducting a complete, independent code review of ${target}. Review the WHOLE change, ` +
96
- `not a slice of it: read the full diff and enough surrounding code to judge it in context. ` +
97
- `Cover correctness, security, error handling, concurrency, resource handling, API contracts, ` +
98
- `performance, and test coverage.\n\n` +
99
- `Report up to ${MAX_FINDINGS_PER_REVIEW} concrete findings: file, line when you can pinpoint ` +
100
- `it, a short title, why it is a real problem, and a severity (critical|high|medium|low). Only ` +
101
- `report issues you can point to in the code — no speculation, no style nits. Also give an ` +
102
- `overall assessment (2-4 sentences): the state of the change, its main risks, and whether it ` +
103
- `looks ready to merge.`
104
-
105
- const reviews = (
106
- await parallel(
107
- REVIEWERS.map((r) => () =>
108
- agent(reviewPrompt, {
109
- provider: r.provider,
110
- model: r.model,
111
- label: `review: ${r.name}`,
112
- phase: "Review",
113
- schema: REVIEW_SCHEMA,
114
- }).then((review) => ({
115
- reviewer: r.name,
116
- assessment: review.assessment,
117
- findings: (review.findings || []).slice(0, MAX_FINDINGS_PER_REVIEW),
118
- })),
119
- ),
120
- )
121
- ).filter(Boolean)
122
-
123
- if (reviews.length === 0) throw new Error("both provider reviews failed — nothing to synthesize")
124
- if (reviews.length < REVIEWERS.length)
125
- log(`warning: only ${reviews.map((r) => r.reviewer).join(", ")} completed — synthesis will lack cross-provider consensus`)
126
- for (const r of reviews) log(`${r.reviewer}: ${r.findings.length} findings`)
127
-
128
- // ---------------------------------------------------------------------------
129
- // Synthesize — one agent (run-default provider; override with --provider) sees
130
- // both labeled reviews and merges them into a single ranked picture.
131
- // ---------------------------------------------------------------------------
132
- phase("Synthesize")
133
-
134
- const synthesis = await agent(
135
- `You are synthesizing ${reviews.length} independent full code reviews of ${target}, each from a ` +
136
- `different model provider. Merge them into one picture:\n` +
137
- `1. Match findings that describe the same underlying issue — even when worded differently or ` +
138
- `pointing at slightly different lines — and merge them into one finding with foundBy listing ` +
139
- `every reviewer that caught it. Consensus findings are the highest-confidence ones; rank them ` +
140
- `first, then by severity.\n` +
141
- `2. Keep findings only one reviewer caught, attributed via foundBy. Spot-check any that look ` +
142
- `dubious against the actual code and drop clear false positives (say so in the report).\n` +
143
- `3. Where the reviews DISAGREE — about the same code, or in their overall assessments — call ` +
144
- `the disagreement out explicitly in the report rather than silently picking a side.\n\n` +
145
- `The report should open with a 2-4 sentence overall verdict that synthesizes both assessments, ` +
146
- `then note where the reviewers agreed and disagreed, then walk the merged findings in ranked ` +
147
- `order (severity, foundBy, file:line, what is wrong, fix direction in a sentence).\n\n` +
148
- `Reviews (JSON):\n${JSON.stringify(reviews, null, 2)}`,
149
- { label: "synthesize", phase: "Synthesize", schema: SYNTHESIS_SCHEMA },
150
- )
151
-
152
- const SEVERITY_RANK = { critical: 0, high: 1, medium: 2, low: 3 }
153
- const findings = (synthesis.findings || [])
154
- .slice()
155
- .sort(
156
- (a, b) =>
157
- (b.foundBy || []).length - (a.foundBy || []).length ||
158
- SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity] ||
159
- a.file.localeCompare(b.file),
160
- )
161
-
162
- const consensus = findings.filter((f) => (f.foundBy || []).length > 1).length
163
- log(`${findings.length} merged findings (${consensus} consensus)`)
164
-
165
- return {
166
- reviews: reviews.map((r) => ({ reviewer: r.reviewer, assessment: r.assessment, findingCount: r.findings.length })),
167
- findings,
168
- report: synthesis.report,
169
- }
@@ -1,141 +0,0 @@
1
- // Provider debate: one provider proposes a position, the other attacks it, the proposer rebuts
2
- // or concedes, for N rounds — then a judge rules on what actually survived. The cross-provider
3
- // attacker is the point: a model is measurably worse at finding holes in its own reasoning than
4
- // another model is.
5
- // Run with: portta flow run workflow provider-debate --input '<question>'
6
- // or: portta flow workflows run provider-debate --args '"<question>"'
7
- // (or --args '{"question": "...", "rounds": 2, "proposer": "codex" | "claude-code"}')
8
-
9
- export const meta = {
10
- name: "provider-debate",
11
- description:
12
- "Adversarial debate — one provider proposes, the other attacks, N rounds of rebuttal, then a judge rules on what survived, what was conceded, and what to actually do.",
13
- defaultSandbox: "read-only",
14
- workspace: {
15
- default: "current_branch",
16
- allowed: ["isolated_worktree", "new_branch", "current_branch"],
17
- mutatesRepository: false,
18
- reason: "The debate is read-only and normally needs no Git isolation.",
19
- },
20
- phases: [
21
- { title: "Propose", detail: "the proposer takes a concrete position" },
22
- { title: "Debate", detail: "attack and rebuttal, one exchange per round" },
23
- { title: "Verdict", detail: "a judge rules: survived, conceded, open, recommendation" },
24
- ],
25
- }
26
-
27
- const PROVIDERS = {
28
- codex: { provider: "codex", model: "gpt-5.5", name: "Codex" },
29
- "claude-code": { provider: "claude-code", model: "claude-fable-5", name: "Claude" },
30
- }
31
-
32
- const question =
33
- typeof args === "string" && args.trim()
34
- ? args.trim()
35
- : args && typeof args === "object" && typeof args.question === "string" && args.question.trim()
36
- ? args.question.trim()
37
- : null
38
- if (!question) {
39
- throw new Error(`provider-debate needs a question — pass --args '"<question>"' or --args '{"question": "..."}'`)
40
- }
41
-
42
- const proposerId = args && typeof args === "object" && args.proposer !== undefined ? args.proposer : "codex"
43
- const proposer = PROVIDERS[proposerId]
44
- if (!proposer) throw new Error(`unknown proposer "${proposerId}" — expected "codex" or "claude-code"`)
45
- const attacker = proposer === PROVIDERS.codex ? PROVIDERS["claude-code"] : PROVIDERS.codex
46
-
47
- const rawRounds = args && typeof args === "object" && typeof args.rounds === "number" ? args.rounds : 2
48
- const rounds = Math.max(1, Math.min(5, Math.floor(rawRounds)))
49
-
50
- const VERDICT_SCHEMA = {
51
- type: "object",
52
- required: ["recommendation", "survived", "conceded", "open", "report"],
53
- properties: {
54
- recommendation: { type: "string" },
55
- survived: { type: "array", items: { type: "string" } },
56
- conceded: { type: "array", items: { type: "string" } },
57
- open: { type: "array", items: { type: "string" } },
58
- report: { type: "string" },
59
- },
60
- }
61
-
62
- const transcriptText = (transcript) =>
63
- transcript
64
- .map((t) => `--- Round ${t.round} ---\nObjections:\n${t.critique}\n\nRebuttal:\n${t.rebuttal}`)
65
- .join("\n\n")
66
-
67
- // ---------------------------------------------------------------------------
68
- // Propose — a concrete, attackable position.
69
- // ---------------------------------------------------------------------------
70
- phase("Propose")
71
- log(`debate: ${question} (${proposer.name} proposes, ${attacker.name} attacks, ${rounds} round${rounds === 1 ? "" : "s"})`)
72
-
73
- const proposal = await agent(
74
- `Take a clear position on the following and propose a concrete answer or design:\n\n${question}\n\n` +
75
- `If this concerns the repository you are in, read the relevant code first and ground your ` +
76
- `position in it. Make your reasoning, assumptions, and tradeoffs explicit — this proposal will ` +
77
- `be scrutinized line by line, so vagueness counts against it.`,
78
- { provider: proposer.provider, model: proposer.model, label: `propose: ${proposer.name}`, phase: "Propose" },
79
- )
80
-
81
- // ---------------------------------------------------------------------------
82
- // Debate — each round is one attack/rebuttal exchange. The rebuttal ends with
83
- // the full current proposal, which becomes the target of the next attack.
84
- // ---------------------------------------------------------------------------
85
- phase("Debate")
86
-
87
- const transcript = []
88
- let current = proposal
89
-
90
- for (let round = 1; round <= rounds; round++) {
91
- const history = transcript.length > 0 ? `\n\nDebate so far:\n${transcriptText(transcript)}` : ""
92
-
93
- const critique = await agent(
94
- `You are the adversarial examiner in round ${round} of ${rounds} of a structured debate.\n\n` +
95
- `Question:\n${question}\n\n` +
96
- `Current proposal:\n${current}${history}\n\n` +
97
- `Attack the proposal with the strongest objections you can ground in evidence: flaws in ` +
98
- `reasoning, missing considerations, risks, and concretely better alternatives. Where the ` +
99
- `question concerns this repository, verify your objections against the actual code — a ` +
100
- `refuted attack weakens your side. Do not be agreeable, and do not repeat objections that ` +
101
- `earlier rebuttals already answered; move to stronger ground instead.`,
102
- { provider: attacker.provider, model: attacker.model, label: `attack: round ${round}`, phase: "Debate" },
103
- )
104
-
105
- const rebuttal = await agent(
106
- `You are the proposer in round ${round} of ${rounds} of a structured debate, defending your proposal.\n\n` +
107
- `Question:\n${question}\n\n` +
108
- `Your current proposal:\n${current}${history}\n\n` +
109
- `New objections:\n${critique}\n\n` +
110
- `Respond to each objection in turn: defend it with reasoning and evidence (check the code ` +
111
- `where it applies), or concede it explicitly and revise. Conceding a weak point is better ` +
112
- `than defending it badly. End with your full current proposal, revised where you conceded.`,
113
- { provider: proposer.provider, model: proposer.model, label: `rebut: round ${round}`, phase: "Debate" },
114
- )
115
-
116
- transcript.push({ round, critique, rebuttal })
117
- current = rebuttal
118
- }
119
-
120
- // ---------------------------------------------------------------------------
121
- // Verdict — the judge runs on the run-default provider (override with
122
- // --provider); it rules on the merits, not on who argued better.
123
- // ---------------------------------------------------------------------------
124
- phase("Verdict")
125
-
126
- const verdict = await agent(
127
- `You are the judge of a structured debate. Rule on the merits of the arguments, not the rhetoric.\n\n` +
128
- `Question:\n${question}\n\n` +
129
- `Initial proposal:\n${proposal}\n\n` +
130
- `Full debate:\n${transcriptText(transcript)}\n\n` +
131
- `Where a decisive claim about this repository can be checked, check it yourself rather than ` +
132
- `taking either side's word. Deliver:\n` +
133
- `- recommendation: the position the asker should act on (it may differ from both sides),\n` +
134
- `- survived: the claims that withstood attack,\n` +
135
- `- conceded: the points the proposer gave up or clearly lost,\n` +
136
- `- open: unresolved questions worth investigating before committing,\n` +
137
- `- report: a concise walkthrough of how you weighed it.`,
138
- { label: "verdict", phase: "Verdict", schema: VERDICT_SCHEMA },
139
- )
140
-
141
- return { question, proposer: proposer.name, attacker: attacker.name, proposal, transcript, verdict }
@@ -1,176 +0,0 @@
1
- // Second opinion: ask both providers cheaply; if they agree, merge and return — if they
2
- // disagree, escalate both to deep effort with the other's answer in hand, then adjudicate.
3
- // Disagreement-triggered escalation: consensus-grade confidence at roughly half the cost of
4
- // always running the full panel.
5
- // Run with: portta flow run workflow second-opinion --input '<question>'
6
- // or: portta flow workflows run second-opinion --args '"<question>"'
7
-
8
- export const meta = {
9
- name: "second-opinion",
10
- description:
11
- "Cheap consensus check — Codex and Claude answer at low effort; agreement returns a merged answer, disagreement escalates both to deep effort and adjudicates.",
12
- defaultSandbox: "read-only",
13
- workspace: {
14
- default: "current_branch",
15
- allowed: ["isolated_worktree", "new_branch", "current_branch"],
16
- mutatesRepository: false,
17
- reason: "The comparison is read-only and normally needs no Git isolation.",
18
- },
19
- phases: [
20
- { title: "Ask", detail: "both providers answer independently at low effort" },
21
- { title: "Compare", detail: "do the answers materially agree?" },
22
- { title: "Escalate", detail: "deep reconsideration of the disagreements — only on a split" },
23
- { title: "Answer", detail: "merge the consensus, or adjudicate the split" },
24
- ],
25
- }
26
-
27
- const PROVIDERS = [
28
- { provider: "codex", model: "gpt-5.5", name: "Codex" },
29
- { provider: "claude-code", model: "claude-fable-5", name: "Claude" },
30
- ]
31
-
32
- const question =
33
- typeof args === "string" && args.trim()
34
- ? args.trim()
35
- : args && typeof args === "object" && typeof args.question === "string" && args.question.trim()
36
- ? args.question.trim()
37
- : null
38
- if (!question) {
39
- throw new Error(`second-opinion needs a question — pass --args '"<question>"' or --args '{"question": "..."}'`)
40
- }
41
-
42
- const ANSWER_SCHEMA = {
43
- type: "object",
44
- required: ["answer", "keyPoints"],
45
- properties: { answer: { type: "string" }, keyPoints: { type: "array", items: { type: "string" } } },
46
- }
47
-
48
- // ---------------------------------------------------------------------------
49
- // Ask — identical prompts, low effort, blind to each other.
50
- // ---------------------------------------------------------------------------
51
- phase("Ask")
52
- log(`asking both providers: ${question}`)
53
-
54
- const asked = (
55
- await parallel(
56
- PROVIDERS.map((p) => () =>
57
- agent(
58
- `Answer this question concretely and commit to a position:\n\n${question}\n\n` +
59
- `If it concerns the repository you are in, ground your answer in the actual code. Give ` +
60
- `your answer plus the key points it rests on.`,
61
- { provider: p.provider, model: p.model, effort: "low", label: `ask: ${p.name}`, phase: "Ask", schema: ANSWER_SCHEMA },
62
- ).then((a) => ({ name: p.name, provider: p.provider, model: p.model, answer: a.answer, keyPoints: a.keyPoints })),
63
- ),
64
- )
65
- ).filter(Boolean)
66
-
67
- if (asked.length === 0) throw new Error("both providers failed to answer")
68
- if (asked.length === 1) {
69
- log(`warning: only ${asked[0].name} answered — no second opinion to compare`)
70
- return { answer: asked[0].answer, agreed: null, escalated: false, answers: asked }
71
- }
72
-
73
- // ---------------------------------------------------------------------------
74
- // Compare — agreement on substance, not wording. A failed comparator counts
75
- // as disagreement: escalating needlessly costs tokens, trusting a missing
76
- // comparison costs correctness.
77
- // ---------------------------------------------------------------------------
78
- phase("Compare")
79
-
80
- const cmp = await agent(
81
- `Two independent answers to the same question:\n\nQuestion:\n${question}\n\n` +
82
- `Answer 1 (${asked[0].name}):\n${asked[0].answer}\nKey points: ${asked[0].keyPoints.join("; ")}\n\n` +
83
- `Answer 2 (${asked[1].name}):\n${asked[1].answer}\nKey points: ${asked[1].keyPoints.join("; ")}\n\n` +
84
- `Do they reach the same substantive conclusion? Ignore wording, structure, and depth — flag ` +
85
- `only material disagreements that would change what the asker does.`,
86
- {
87
- effort: "low",
88
- label: "compare",
89
- phase: "Compare",
90
- schema: {
91
- type: "object",
92
- required: ["agree", "disagreements"],
93
- properties: { agree: { type: "boolean" }, disagreements: { type: "array", items: { type: "string" } } },
94
- },
95
- },
96
- )
97
-
98
- const agree = cmp ? cmp.agree : false
99
- const disagreements = cmp ? cmp.disagreements : ["comparison agent failed — treating the answers as unverified"]
100
-
101
- if (agree) {
102
- phase("Answer")
103
- log("answers agree — merging")
104
- const merged = await agent(
105
- `Two independent analyses of this question reached the same conclusion. Merge them into one ` +
106
- `answer — keep the strongest specifics of each, no filler.\n\nQuestion:\n${question}\n\n` +
107
- `${asked.map((a) => `Answer (${a.name}):\n${a.answer}`).join("\n\n")}`,
108
- { effort: "low", label: "merge", phase: "Answer" },
109
- )
110
- return { answer: merged, agreed: true, escalated: false, answers: asked }
111
- }
112
-
113
- // ---------------------------------------------------------------------------
114
- // Escalate — each provider reconsiders at deep effort with the other's answer
115
- // and the specific disagreements in hand.
116
- // ---------------------------------------------------------------------------
117
- phase("Escalate")
118
- log(`answers disagree — escalating: ${disagreements.join("; ")}`)
119
-
120
- const finals = (
121
- await parallel(
122
- asked.map((a, i) => () => {
123
- const other = asked[1 - i]
124
- return agent(
125
- `You previously answered this question:\n\n${question}\n\nYour answer:\n${a.answer}\n\n` +
126
- `An equally capable independent analysis disagreed. Its answer:\n${other.answer}\n\n` +
127
- `The material disagreements:\n${disagreements.map((d) => `- ${d}`).join("\n")}\n\n` +
128
- `Reconsider at full depth: verify the contested points yourself (read the code, check the ` +
129
- `claims) rather than restating your position. Then either defend your answer with ` +
130
- `evidence or change it. Changing your mind on the evidence is success, not failure. ` +
131
- `State your final answer.`,
132
- { provider: a.provider, model: a.model, effort: "xhigh", label: `reconsider: ${a.name}`, phase: "Escalate", schema: { type: "object", required: ["answer", "changed"], properties: { answer: { type: "string" }, changed: { type: "boolean" } } } },
133
- ).then((f) => ({ name: a.name, answer: f.answer, changed: f.changed }))
134
- }),
135
- )
136
- ).filter(Boolean)
137
-
138
- // ---------------------------------------------------------------------------
139
- // Answer — adjudicate what remains. Runs on the run-default provider
140
- // (override with --provider).
141
- // ---------------------------------------------------------------------------
142
- phase("Answer")
143
-
144
- const adj = await agent(
145
- `You are the final adjudicator. Two analyses answered the same question, disagreed, and each ` +
146
- `reconsidered at full depth with the other's answer in hand.\n\nQuestion:\n${question}\n\n` +
147
- `Initial disagreements:\n${disagreements.map((d) => `- ${d}`).join("\n")}\n\n` +
148
- `Final positions:\n${finals.map((f) => `${f.name}${f.changed ? " (revised)" : " (held)"}:\n${f.answer}`).join("\n\n")}\n\n` +
149
- `Write the single answer the asker should act on. If the deep passes converged, merge them; if ` +
150
- `not, decide on the merits — verify the decisive points yourself where feasible. Note anything ` +
151
- `that remains genuinely unresolved.`,
152
- {
153
- effort: "xhigh",
154
- label: "adjudicate",
155
- phase: "Answer",
156
- schema: {
157
- type: "object",
158
- required: ["answer", "resolution", "unresolved"],
159
- properties: {
160
- answer: { type: "string" },
161
- resolution: { type: "string", enum: ["consensus", "adjudicated"] },
162
- unresolved: { type: "array", items: { type: "string" } },
163
- },
164
- },
165
- },
166
- )
167
-
168
- return {
169
- answer: adj.answer,
170
- agreed: false,
171
- escalated: true,
172
- resolution: adj.resolution,
173
- unresolved: adj.unresolved,
174
- disagreements,
175
- positions: finals,
176
- }
@@ -1,6 +0,0 @@
1
- #!/usr/bin/env node
2
- import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
3
- import{q as a,t as m,v as d}from"./chunk-OPVZAG2C.js";import{a as c}from"./chunk-4TGTGGMK.js";import{c as u}from"./chunk-UX7P3YHF.js";import{a as i}from"./chunk-T3TSZO4I.js";import{a as o}from"./chunk-6J5HN4ZN.js";import{realpathSync as v}from"node:fs";import{dirname as P,resolve as g}from"node:path";var s=class extends Error{static{o(this,"CommandUsageError")}};function B(t){if(!t||t.length===0)return;let e={};for(let r of t){let n=r.indexOf("=");if(n<=0)throw new s("--env must use KEY=VALUE");e[r.slice(0,n)]=r.slice(n+1)}return e}o(B,"parseEnvOverrides");function x(t,e){return t instanceof Error?t.message.startsWith("HTTP")?t.message:t.message.includes("fetch")||t.message.includes("Unable to connect")?d(e):t.message:String(t)}o(x,"formatServerError");async function H(t,e){try{return await e()}catch(r){
4
- throw new Error(x(r,t))}}o(H,"withServerConnection");function y(t=process.cwd()){try{let e=c("git",["rev-parse","--git-common-dir"],{cwd:t});if(e.success){let n=e.stdout.toString().trim();if(n)return P(g(t,n))}let r=c("git",["rev-parse","--show-toplevel"],{cwd:t});return r.success?r.stdout.toString().trim():null}catch{return null}}o(y,"resolveProjectRoot");function p(t){try{return v(t)}catch{return g(t)}}o(p,"canonicalizePath");async function b(t,e=process.cwd()){let r=a(t),n=y(e);if(!n)throw new s(
5
- `Not inside a git repository, so ${i} can't tell which project this command targets. cd into a project served by ${i} (\`${u()} project ls\` lists them) and try again.`);let{projects:l}=await m(r).fetchProjects(),h=p(n),f=l.find(w=>p(w.path)===h);if(!f)throw new s(`This project (${n}) isn't served by ${i} on port ${t}. Run \`${u()} project add\` in it first.`);return`${r}/${f.prefix}`}o(b,"resolveProjectBaseUrl");async function I(t,e=process.cwd()){try{return(await b(t,e)).slice(a(t).length).replace(
6
- /^\/+|\/+$/g,"")||void 0}catch{return}}o(I,"resolveProjectPrefix");export{s as a,B as b,x as c,H as d,b as e,I as f};
@@ -1,4 +0,0 @@
1
- #!/usr/bin/env node
2
- import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
3
- import{c as s}from"./chunk-EH7H7D7J.js";import{a as r}from"./chunk-6J5HN4ZN.js";import{spawnSync as i}from"node:child_process";import{existsSync as c,readFileSync as l}from"node:fs";import{basename as f,join as a}from"node:path";function p(t,e,n){let u=s(t)??t,o=i(u,e,{...n,encoding:"buffer",stdio:["ignore","pipe","pipe"]});return{success:o.status===0&&!o.error,stdout:o.stdout??Buffer.alloc(0),stderr:o.stderr??Buffer.alloc(0)}}r(p,"run");function g(t){return s(t)}r(g,"resolveExecutablePath");function b(t){return g(t)!==null}r(b,"which");function v(){let t=p("git",["rev-parse","--show-toplevel"]);return t.success?t.stdout.toString().trim():null}r(v,"getGitRoot");function S(t){let e=a(t,"package.json");if(c(e))try{let n=JSON.
4
- parse(l(e,"utf-8"));if(n.name)return n.name}catch{}return f(t)}r(S,"detectProjectName");export{p as a,g as b,b as c,v as d,S as e};