From 6d9ba8e1f51253258a13b5eab2296550d4049b16 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Mon, 15 Jun 2026 14:37:21 +0000 Subject: [PATCH 01/23] =?UTF-8?q?=F0=9F=A4=96=20refactor:=20add=20tracked?= =?UTF-8?q?=20simplify=20workflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the simplify workflow into the tracked project workflow directory so child workspaces can inspect it. Refactor the workflow to keep agent structured-output schemas as constants before the workflow entrypoint, and simplify the orchestration code around lane prompts, synthesis, fixing, and argument parsing. --- _Generated with `mux` • Model: `openai:gpt-5.5` • Thinking: `xhigh` • Cost: `3422439{MUX_COSTS_USD:-unknown}`_ --- .mux/workflows/simplify.js | 455 +++++++++++++++++++++++++++++++++++++ 1 file changed, 455 insertions(+) create mode 100644 .mux/workflows/simplify.js diff --git a/.mux/workflows/simplify.js b/.mux/workflows/simplify.js new file mode 100644 index 0000000000..3bbe4a7186 --- /dev/null +++ b/.mux/workflows/simplify.js @@ -0,0 +1,455 @@ +// description: Review current changes for reuse, quality, and efficiency, then fix actionable issues. + +const DEFAULT_MAX_FINDINGS = 20; +const REVIEW_AGENT_ID = "explore"; +const EXEC_AGENT_ID = "exec"; +const REVIEW_LANES = [ + { + id: "reuse", + title: "Simplify: code reuse review", + instructions: [ + "Search for existing utilities and helpers that could replace newly written code.", + "Flag new functions that duplicate existing functionality and name the existing function to use instead.", + "Flag inline logic that could use an existing utility: string handling, path handling, environment checks, type guards, and similar patterns.", + ], + }, + { + id: "quality", + title: "Simplify: code quality review", + instructions: [ + "Find redundant state, cached values that could be derived, and observers/effects that could be direct calls.", + "Find parameter sprawl, copy-paste with slight variation, and leaky abstractions.", + "Find stringly-typed code and unnecessary JSX wrappers that add no layout value.", + ], + }, + { + id: "efficiency", + title: "Simplify: efficiency review", + instructions: [ + "Find redundant computations, repeated file reads, duplicate network/API calls, N+1 patterns, and missed concurrency.", + "Find hot-path bloat, recurring no-op updates, and updater wrappers that defeat same-reference no-op returns.", + "Find TOCTOU existence pre-checks, unbounded memory, missing cleanup, and overly broad reads or loads.", + ], + }, +]; + +const SEVERITY_SCHEMA = { type: "string", enum: ["high", "medium", "low"] }; +const FINDING_SCHEMA = { + type: "object", + required: ["id", "title", "severity", "filePaths", "rationale", "recommendation", "evidence"], + properties: { + id: { type: "string" }, + title: { type: "string" }, + severity: SEVERITY_SCHEMA, + filePaths: { type: "array", items: { type: "string" } }, + rationale: { type: "string" }, + recommendation: { type: "string" }, + evidence: { type: "array", items: { type: "string" } }, + }, +}; +const REVIEW_SCHEMA = { + type: "object", + required: ["summary", "findings"], + properties: { + summary: { type: "string" }, + findings: { type: "array", items: FINDING_SCHEMA }, + }, +}; +const SYNTHESIS_FINDING_SCHEMA = { + type: "object", + required: ["id", "title", "severity", "filePaths", "rationale", "fixPlan"], + properties: { + id: { type: "string" }, + title: { type: "string" }, + severity: SEVERITY_SCHEMA, + filePaths: { type: "array", items: { type: "string" } }, + rationale: { type: "string" }, + fixPlan: { type: "string" }, + }, +}; +const SKIPPED_FINDING_SCHEMA = { + type: "object", + required: ["id", "title", "reason"], + properties: { + id: { type: "string" }, + title: { type: "string" }, + reason: { type: "string" }, + }, +}; +const SYNTHESIS_SCHEMA = { + type: "object", + required: ["summary", "shouldFix", "actionableFindings", "skippedFindings", "validationPlan"], + properties: { + summary: { type: "string" }, + shouldFix: { type: "boolean" }, + actionableFindings: { type: "array", items: SYNTHESIS_FINDING_SCHEMA }, + skippedFindings: { type: "array", items: SKIPPED_FINDING_SCHEMA }, + validationPlan: { type: "array", items: { type: "string" } }, + }, +}; +const FIXER_SCHEMA = { + type: "object", + required: ["madeChanges", "fixedFindingIds", "skippedFindings", "validation"], + properties: { + madeChanges: { type: "boolean" }, + fixedFindingIds: { type: "array", items: { type: "string" } }, + skippedFindings: { + type: "array", + items: { + type: "object", + required: ["id", "reason"], + properties: { + id: { type: "string" }, + reason: { type: "string" }, + }, + }, + }, + validation: { + type: "array", + items: { + type: "object", + required: ["command", "status", "summary"], + properties: { + command: { type: "string" }, + status: { type: "string" }, + summary: { type: "string" }, + }, + }, + }, + }, +}; + +export default function simplifyWorkflow({ args, phase, log, action, parallelAgents, agent, applyPatch }) { + assert(action && parallelAgents && agent && applyPatch, "workflow runtime APIs are required"); + + const input = parseArgs(args); + if (input.help) return usageResult(); + + phase("capture-context", { target: input.target || "current git changes", fix: input.fix }); + const gitContext = collectGitContext(action, input); + log("Captured simplify context", { + target: input.target || "current git changes", + gitFailures: gitContext.failures.length, + }); + + const reviewContext = renderReviewContext(input, gitContext); + + phase("review", { lanes: REVIEW_LANES.map(function (lane) { return lane.id; }) }); + const reviewOutputs = parallelAgents( + REVIEW_LANES.map(function (lane) { + return { + id: lane.id + "-review", + title: lane.title, + agentId: REVIEW_AGENT_ID, + prompt: reviewPrompt(lane, input, reviewContext), + outputSchema: REVIEW_SCHEMA, + }; + }), + { maxParallel: REVIEW_LANES.length } + ).map(function (review) { + return mustObject(review.structuredOutput, "review structured output is required"); + }); + + const rawFindingCount = reviewOutputs.reduce(function (count, output) { + return count + asArray(output.findings).length; + }, 0); + + phase("synthesize", { rawFindingCount: rawFindingCount }); + const synthesis = agent({ + id: "synthesize-simplify-findings", + title: "Simplify: synthesize findings", + agentId: EXEC_AGENT_ID, + prompt: synthesisPrompt(input, reviewContext, reviewOutputs), + outputSchema: SYNTHESIS_SCHEMA, + }); + const synthesized = mustObject(synthesis.structuredOutput, "synthesis structured output is required"); + const actionableFindings = asArray(synthesized.actionableFindings); + + if (!input.fix || !synthesized.shouldFix || actionableFindings.length === 0) { + return { + reportMarkdown: reviewOnlyReport(input, synthesis.reportMarkdown), + structuredOutput: { + mode: input.fix ? "no-actionable-fixes" : "review-only", + gitContext: gitContext, + reviews: reviewOutputs, + synthesis: synthesized, + }, + }; + } + + phase("fix", { actionableFindingCount: actionableFindings.length }); + const fixer = agent({ + id: "fix-simplify-findings", + title: "Simplify: fix actionable findings", + agentId: EXEC_AGENT_ID, + prompt: fixPrompt(reviewContext, synthesized), + outputSchema: FIXER_SCHEMA, + }); + const fixerOutput = mustObject(fixer.structuredOutput, "fixer structured output is required"); + + if (!fixerOutput.madeChanges) { + return { + reportMarkdown: synthesis.reportMarkdown + "\n\n---\n\n## Fix pass\n\nThe fixer did not make file changes.\n\n" + fixer.reportMarkdown, + structuredOutput: { + mode: "fixer-made-no-changes", + gitContext: gitContext, + reviews: reviewOutputs, + synthesis: synthesized, + fix: { fixer: fixerOutput, applied: null }, + }, + }; + } + + phase("apply-fixes", { madeChanges: true }); + const applied = applyPatch({ + id: "apply-simplify-fixes", + source: fixer, + target: "parent", + threeWay: true, + onConflict: "return", + }); + + return { + reportMarkdown: fixReport(synthesis.reportMarkdown, fixer.reportMarkdown, applied), + structuredOutput: { + mode: "fix-attempted", + gitContext: gitContext, + reviews: reviewOutputs, + synthesis: synthesized, + fix: { fixer: fixerOutput, applied: applied }, + }, + }; +} + +function collectGitContext(action, input) { + const refs = gitRefs(input); + const failures = []; + return { + target: input.target, + refs: refs, + failures: failures, + status: gitSlice(failures, "status", function () { + return action.git.status({ id: "git-status", input: { includeIgnored: false }, builtInOnly: true }).output; + }), + changedFiles: gitSlice(failures, "changedFiles", function () { + return action.git.changedFiles({ id: "git-changed-files", input: refs, builtInOnly: true }).output; + }), + diffStat: gitSlice(failures, "diffStat", function () { + return action.git.diffStat({ id: "git-diff-stat", input: refs, builtInOnly: true }).output; + }), + diff: gitSlice(failures, "diff", function () { + return action.git.diff({ id: "git-diff", input: refs, builtInOnly: true }).output; + }), + }; +} + +function gitSlice(failures, name, read) { + try { + return read(); + } catch (error) { + failures.push({ name: name, error: String(error) }); + return null; + } +} + +function gitRefs(input) { + const refs = {}; + if (input.baseRef) refs.base = input.baseRef; + if (input.trunkRef) refs.trunk = input.trunkRef; + if (input.headRef) refs.head = input.headRef; + return refs; +} + +function renderReviewContext(input, gitContext) { + return fencedJson({ + input: { + target: input.target, + fix: input.fix, + baseRef: input.baseRef, + trunkRef: input.trunkRef, + headRef: input.headRef, + maxFindings: input.maxFindings, + }, + gitContext: gitContext, + }); +} + +function reviewPrompt(lane, input, reviewContext) { + return [ + readOnlyPrompt(), + "You are the " + lane.title + " lane. Review every changed file in the supplied Git context.", + "If an explicit target is provided and the Git diff is empty, inspect that target path in the workspace before making claims.", + "Allowed severity values are: high, medium, low. Return high-signal, actionable findings only; an empty findings array is fine.", + "The synthesis step will keep at most " + input.maxFindings + " actionable findings. Use stable finding ids and arrays for filePaths/evidence.", + "\nLane checklist:\n- " + lane.instructions.join("\n- "), + "\nReview context:\n" + reviewContext, + ].join("\n\n"); +} + +function synthesisPrompt(input, reviewContext, reviewOutputs) { + return [ + readOnlyPrompt(), + "Deduplicate and triage these simplify review findings. Keep actionableFindings to the " + input.maxFindings + " highest-value issues.", + "Fix actionable issues directly when fix mode is enabled. If a finding is false positive or not worth addressing, put it in skippedFindings without debating it.", + "Allowed severity values are: high, medium, low. Prefer minimal cleanup over broad refactors.", + "\nOriginal review context:\n" + reviewContext, + "\nLane outputs:\n" + fencedJson(reviewOutputs), + ].join("\n\n"); +} + +function fixPrompt(reviewContext, synthesized) { + return [ + "Fix the actionable simplify findings with minimal, correct, reviewable changes. Do not push, commit, or open a PR.", + "Preserve existing style and functionality. Run targeted validation for touched code when feasible and report exact commands/results.", + "If a finding is false positive or not worth addressing, skip it and note why. Set madeChanges true only when files changed.", + "\nOriginal review context:\n" + reviewContext, + "\nSynthesized findings:\n" + fencedJson(synthesized), + ].join("\n\n"); +} + +function parseArgs(args) { + const raw = args && typeof args === "object" ? args : {}; + const input = { + help: Boolean(raw.help), + fix: raw.fix !== false && raw.reviewOnly !== true, + target: text(raw.target), + baseRef: text(raw.baseRef || raw.base), + trunkRef: text(raw.trunkRef || raw.trunk), + headRef: text(raw.headRef || raw.head), + maxFindings: positiveInt(raw.maxFindings, DEFAULT_MAX_FINDINGS), + }; + const targetParts = []; + const tokens = tokenize(String(raw.input || "")); + + for (let index = 0; index < tokens.length; index += 1) { + const token = tokens[index]; + const valueFlag = parseValueFlag(tokens, index); + if (token === "--help" || token === "-h") input.help = true; + else if (token === "--review-only" || token === "--no-fix") input.fix = false; + else if (token === "--fix") input.fix = true; + else if (valueFlag) { + input[valueFlag.key] = valueFlag.key === "maxFindings" ? positiveInt(valueFlag.value, DEFAULT_MAX_FINDINGS) : valueFlag.value; + index = valueFlag.index; + } else targetParts.push(token); + } + + if (!input.target) input.target = targetParts.join(" ").trim(); + return input; +} + +function parseValueFlag(tokens, index) { + const flags = [ + { name: "--base", key: "baseRef" }, + { name: "--trunk", key: "trunkRef" }, + { name: "--head", key: "headRef" }, + { name: "--max-findings", key: "maxFindings" }, + ]; + const token = tokens[index]; + for (let flagIndex = 0; flagIndex < flags.length; flagIndex += 1) { + const flag = flags[flagIndex]; + if (token === flag.name) { + assert(index + 1 < tokens.length, flag.name + " requires a value"); + return { key: flag.key, value: tokens[index + 1], index: index + 1 }; + } + if (token.indexOf(flag.name + "=") === 0) { + return { key: flag.key, value: token.slice(flag.name.length + 1), index: index }; + } + } + return null; +} + +function tokenize(input) { + const tokens = []; + let current = ""; + let quote = ""; + let escaped = false; + for (let index = 0; index < input.length; index += 1) { + const char = input[index]; + if (escaped) { + current += char; + escaped = false; + } else if (quote && char === "\\") { + escaped = true; + } else if (quote) { + if (char === quote) quote = ""; + else current += char; + } else if (char === '"' || char === "'") { + quote = char; + } else if (/\s/.test(char)) { + if (current) tokens.push(current); + current = ""; + } else current += char; + } + assert(!quote, "unterminated quoted argument"); + if (escaped) current += "\\"; + if (current) tokens.push(current); + return tokens; +} + +function readOnlyPrompt() { + return "This is a read-only review step. Do not edit files, create commits, apply patches, push branches, or open PRs. Inspect repository evidence only as needed and report findings."; +} + +function reviewOnlyReport(input, markdown) { + const mode = input.fix ? "No actionable fixes were selected." : "Review-only mode; no fixes were applied."; + return markdown + "\n\n---\n\n## Simplify workflow result\n\n" + mode; +} + +function fixReport(synthesisMarkdown, fixerMarkdown, applied) { + const status = applied && applied.status ? applied.status : "unknown"; + const success = Boolean(applied && applied.success); + return synthesisMarkdown + "\n\n---\n\n## Fix pass\n\n" + fixerMarkdown + "\n\n### Patch application\n\n- Status: " + status + "\n- Success: " + String(success); +} + +function usageResult() { + return { + reportMarkdown: [ + "# simplify workflow", + "", + "Review current git changes for code reuse, quality, and efficiency, then fix actionable issues.", + "", + "## Usage", + "", + "- `/workflow simplify` — review current git changes and apply fixes.", + "- `/workflow simplify --review-only` — review and synthesize findings without applying fixes.", + "- `/workflow simplify --base main --head HEAD` — review a specific ref range.", + "- `/workflow simplify path/or/context` — provide an explicit target when there are no Git changes.", + "", + "## Options", + "", + "- `--review-only` / `--no-fix`", + "- `--fix`", + "- `--base `", + "- `--trunk `", + "- `--head `", + "- `--max-findings `", + ].join("\n"), + structuredOutput: { help: true }, + }; +} + +function fencedJson(value) { + return "```json\n" + JSON.stringify(value, null, 2) + "\n```"; +} + +function asArray(value) { + return Array.isArray(value) ? value : []; +} + +function mustObject(value, message) { + assert(value && typeof value === "object", message); + return value; +} + +function text(value) { + return typeof value === "string" ? value.trim() : ""; +} + +function positiveInt(value, fallback) { + const number = Number(value); + return Number.isFinite(number) && number >= 1 ? Math.floor(number) : fallback; +} + +function assert(condition, message) { + if (!condition) throw new Error(message); +} From a7373d27e910479bf10b2218d05d80456860cd70 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Mon, 15 Jun 2026 14:51:03 +0000 Subject: [PATCH 02/23] =?UTF-8?q?=F0=9F=A4=96=20refactor:=20tighten=20simp?= =?UTF-8?q?lify=20workflow=20prompts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement the self-review findings for the tracked simplify workflow: - cap raw diff text before fanning out review agents - pass compact diff metadata, not raw diffs, to synthesis/fix phases - render malformed workflow arguments as validation/usage output instead of assertion crashes No custom workflow actions are introduced. --- _Generated with `mux` • Model: `openai:gpt-5.5` • Thinking: `xhigh` • Cost: `3484700{MUX_COSTS_USD:-unknown}`_ --- .mux/workflows/simplify.js | 288 ++++++++++++++++++++++++++++--------- 1 file changed, 220 insertions(+), 68 deletions(-) diff --git a/.mux/workflows/simplify.js b/.mux/workflows/simplify.js index 3bbe4a7186..12d0d4487e 100644 --- a/.mux/workflows/simplify.js +++ b/.mux/workflows/simplify.js @@ -1,6 +1,8 @@ // description: Review current changes for reuse, quality, and efficiency, then fix actionable issues. const DEFAULT_MAX_FINDINGS = 20; +// Review agents get bounded diff text; synthesis/fix phases get metadata only. +const REVIEW_DIFF_CHAR_BUDGET = 60000; const REVIEW_AGENT_ID = "explore"; const EXEC_AGENT_ID = "exec"; const REVIEW_LANES = [ @@ -119,29 +121,44 @@ const FIXER_SCHEMA = { }, }; -export default function simplifyWorkflow({ args, phase, log, action, parallelAgents, agent, applyPatch }) { +export default function simplifyWorkflow({ + args, + phase, + log, + action, + parallelAgents, + agent, + applyPatch, +}) { assert(action && parallelAgents && agent && applyPatch, "workflow runtime APIs are required"); - const input = parseArgs(args); + const parsed = parseArgs(args); + if (parsed.error) return usageResult(parsed.error); + + const input = parsed.input; if (input.help) return usageResult(); phase("capture-context", { target: input.target || "current git changes", fix: input.fix }); const gitContext = collectGitContext(action, input); + const contexts = promptContexts(input, gitContext); log("Captured simplify context", { target: input.target || "current git changes", gitFailures: gitContext.failures.length, + diffCompactions: diffCompactions(contexts.outputGitContext).length, }); - const reviewContext = renderReviewContext(input, gitContext); - - phase("review", { lanes: REVIEW_LANES.map(function (lane) { return lane.id; }) }); + phase("review", { + lanes: REVIEW_LANES.map(function (lane) { + return lane.id; + }), + }); const reviewOutputs = parallelAgents( REVIEW_LANES.map(function (lane) { return { id: lane.id + "-review", title: lane.title, agentId: REVIEW_AGENT_ID, - prompt: reviewPrompt(lane, input, reviewContext), + prompt: reviewPrompt(lane, input, contexts.review), outputSchema: REVIEW_SCHEMA, }; }), @@ -159,10 +176,13 @@ export default function simplifyWorkflow({ args, phase, log, action, parallelAge id: "synthesize-simplify-findings", title: "Simplify: synthesize findings", agentId: EXEC_AGENT_ID, - prompt: synthesisPrompt(input, reviewContext, reviewOutputs), + prompt: synthesisPrompt(input, contexts.compact, reviewOutputs), outputSchema: SYNTHESIS_SCHEMA, }); - const synthesized = mustObject(synthesis.structuredOutput, "synthesis structured output is required"); + const synthesized = mustObject( + synthesis.structuredOutput, + "synthesis structured output is required" + ); const actionableFindings = asArray(synthesized.actionableFindings); if (!input.fix || !synthesized.shouldFix || actionableFindings.length === 0) { @@ -170,7 +190,7 @@ export default function simplifyWorkflow({ args, phase, log, action, parallelAge reportMarkdown: reviewOnlyReport(input, synthesis.reportMarkdown), structuredOutput: { mode: input.fix ? "no-actionable-fixes" : "review-only", - gitContext: gitContext, + gitContext: contexts.outputGitContext, reviews: reviewOutputs, synthesis: synthesized, }, @@ -182,17 +202,20 @@ export default function simplifyWorkflow({ args, phase, log, action, parallelAge id: "fix-simplify-findings", title: "Simplify: fix actionable findings", agentId: EXEC_AGENT_ID, - prompt: fixPrompt(reviewContext, synthesized), + prompt: fixPrompt(contexts.compact, synthesized), outputSchema: FIXER_SCHEMA, }); const fixerOutput = mustObject(fixer.structuredOutput, "fixer structured output is required"); if (!fixerOutput.madeChanges) { return { - reportMarkdown: synthesis.reportMarkdown + "\n\n---\n\n## Fix pass\n\nThe fixer did not make file changes.\n\n" + fixer.reportMarkdown, + reportMarkdown: + synthesis.reportMarkdown + + "\n\n---\n\n## Fix pass\n\nThe fixer did not make file changes.\n\n" + + fixer.reportMarkdown, structuredOutput: { mode: "fixer-made-no-changes", - gitContext: gitContext, + gitContext: contexts.outputGitContext, reviews: reviewOutputs, synthesis: synthesized, fix: { fixer: fixerOutput, applied: null }, @@ -213,7 +236,7 @@ export default function simplifyWorkflow({ args, phase, log, action, parallelAge reportMarkdown: fixReport(synthesis.reportMarkdown, fixer.reportMarkdown, applied), structuredOutput: { mode: "fix-attempted", - gitContext: gitContext, + gitContext: contexts.outputGitContext, reviews: reviewOutputs, synthesis: synthesized, fix: { fixer: fixerOutput, applied: applied }, @@ -229,10 +252,15 @@ function collectGitContext(action, input) { refs: refs, failures: failures, status: gitSlice(failures, "status", function () { - return action.git.status({ id: "git-status", input: { includeIgnored: false }, builtInOnly: true }).output; + return action.git.status({ + id: "git-status", + input: { includeIgnored: false }, + builtInOnly: true, + }).output; }), changedFiles: gitSlice(failures, "changedFiles", function () { - return action.git.changedFiles({ id: "git-changed-files", input: refs, builtInOnly: true }).output; + return action.git.changedFiles({ id: "git-changed-files", input: refs, builtInOnly: true }) + .output; }), diffStat: gitSlice(failures, "diffStat", function () { return action.git.diffStat({ id: "git-diff-stat", input: refs, builtInOnly: true }).output; @@ -260,49 +288,147 @@ function gitRefs(input) { return refs; } -function renderReviewContext(input, gitContext) { +function promptContexts(input, gitContext) { + const reviewGitContext = withCompactedDiff(gitContext, REVIEW_DIFF_CHAR_BUDGET); + const outputGitContext = withoutDiffText(gitContext, reviewGitContext.diff); + return { + review: renderContext(input, reviewGitContext), + compact: renderContext(input, outputGitContext), + outputGitContext: outputGitContext, + }; +} + +function renderContext(input, gitContext) { return fencedJson({ - input: { - target: input.target, - fix: input.fix, - baseRef: input.baseRef, - trunkRef: input.trunkRef, - headRef: input.headRef, - maxFindings: input.maxFindings, - }, + input: { target: input.target, fix: input.fix, maxFindings: input.maxFindings }, gitContext: gitContext, }); } +function withCompactedDiff(gitContext, budget) { + return { + target: gitContext.target, + refs: gitContext.refs, + failures: gitContext.failures, + status: gitContext.status, + changedFiles: gitContext.changedFiles, + diffStat: gitContext.diffStat, + diff: compactDiff(gitContext.diff, budget), + }; +} + +function withoutDiffText(gitContext, compactedDiff) { + return { + target: gitContext.target, + refs: gitContext.refs, + failures: gitContext.failures, + status: gitContext.status, + changedFiles: gitContext.changedFiles, + diffStat: gitContext.diffStat, + diff: diffSummary(gitContext.diff, compactedDiff), + }; +} + +function compactDiff(diff, budget) { + if (!diff || typeof diff !== "object") return diff; + + const compacted = { + base: diff.base, + head: diff.head, + mergeBase: diff.mergeBase, + truncated: diff.truncated, + workflowBudgetChars: budget, + workflowCompactions: [], + }; + let remaining = budget; + + ["branch", "staged", "unstaged"].forEach(function (field) { + const value = diff[field]; + if (typeof value !== "string") { + compacted[field] = value; + return; + } + + const included = Math.max(0, Math.min(value.length, remaining)); + compacted[field] = + included === value.length ? value : value.slice(0, included) + diffOmittedMessage(field); + remaining -= included; + if (included < value.length) { + compacted.workflowCompactions.push({ + field: field, + originalChars: value.length, + includedChars: included, + }); + } + }); + + return compacted; +} + +function diffSummary(diff, compactedDiff) { + if (!diff || typeof diff !== "object") return diff; + return { + base: diff.base, + head: diff.head, + mergeBase: diff.mergeBase, + truncated: diff.truncated, + workflowBudgetChars: compactedDiff && compactedDiff.workflowBudgetChars, + workflowCompactions: diffCompactions({ diff: compactedDiff }), + chars: { + branch: stringLength(diff.branch), + staged: stringLength(diff.staged), + unstaged: stringLength(diff.unstaged), + }, + }; +} + +function diffCompactions(gitContext) { + return asArray(gitContext && gitContext.diff && gitContext.diff.workflowCompactions); +} + +function diffOmittedMessage(field) { + return ( + "\n\n[Workflow prompt budget omitted the rest of the " + + field + + " diff. Inspect the file directly before making claims about omitted hunks.]" + ); +} + function reviewPrompt(lane, input, reviewContext) { return [ readOnlyPrompt(), "You are the " + lane.title + " lane. Review every changed file in the supplied Git context.", "If an explicit target is provided and the Git diff is empty, inspect that target path in the workspace before making claims.", + "Diff text is capped by workflowBudgetChars; if workflowCompactions or built-in truncated flags are present, inspect files directly before making claims about omitted hunks.", "Allowed severity values are: high, medium, low. Return high-signal, actionable findings only; an empty findings array is fine.", - "The synthesis step will keep at most " + input.maxFindings + " actionable findings. Use stable finding ids and arrays for filePaths/evidence.", + "The synthesis step will keep at most " + + input.maxFindings + + " actionable findings. Use stable finding ids and arrays for filePaths/evidence.", "\nLane checklist:\n- " + lane.instructions.join("\n- "), "\nReview context:\n" + reviewContext, ].join("\n\n"); } -function synthesisPrompt(input, reviewContext, reviewOutputs) { +function synthesisPrompt(input, compactContext, reviewOutputs) { return [ readOnlyPrompt(), - "Deduplicate and triage these simplify review findings. Keep actionableFindings to the " + input.maxFindings + " highest-value issues.", + "Deduplicate and triage these simplify review findings. Keep actionableFindings to the " + + input.maxFindings + + " highest-value issues.", "Fix actionable issues directly when fix mode is enabled. If a finding is false positive or not worth addressing, put it in skippedFindings without debating it.", "Allowed severity values are: high, medium, low. Prefer minimal cleanup over broad refactors.", - "\nOriginal review context:\n" + reviewContext, + "\nCompact review context without raw diff text:\n" + compactContext, "\nLane outputs:\n" + fencedJson(reviewOutputs), ].join("\n\n"); } -function fixPrompt(reviewContext, synthesized) { +function fixPrompt(compactContext, synthesized) { return [ "Fix the actionable simplify findings with minimal, correct, reviewable changes. Do not push, commit, or open a PR.", + "Use the compact context for file lists and diff metadata; inspect files directly instead of relying on raw diff text being embedded in this prompt.", "Preserve existing style and functionality. Run targeted validation for touched code when feasible and report exact commands/results.", "If a finding is false positive or not worth addressing, skip it and note why. Set madeChanges true only when files changed.", - "\nOriginal review context:\n" + reviewContext, + "\nCompact review context:\n" + compactContext, "\nSynthesized findings:\n" + fencedJson(synthesized), ].join("\n\n"); } @@ -318,23 +444,31 @@ function parseArgs(args) { headRef: text(raw.headRef || raw.head), maxFindings: positiveInt(raw.maxFindings, DEFAULT_MAX_FINDINGS), }; - const targetParts = []; - const tokens = tokenize(String(raw.input || "")); + const tokenized = tokenize(String(raw.input || "")); + if (tokenized.error) return { input: input, error: tokenized.error }; - for (let index = 0; index < tokens.length; index += 1) { - const token = tokens[index]; - const valueFlag = parseValueFlag(tokens, index); + const targetParts = []; + let index = 0; + while (index < tokenized.tokens.length) { + const token = tokenized.tokens[index]; + const valueFlag = parseValueFlag(tokenized.tokens, index); if (token === "--help" || token === "-h") input.help = true; else if (token === "--review-only" || token === "--no-fix") input.fix = false; else if (token === "--fix") input.fix = true; + else if (valueFlag && valueFlag.error) return { input: input, error: valueFlag.error }; else if (valueFlag) { - input[valueFlag.key] = valueFlag.key === "maxFindings" ? positiveInt(valueFlag.value, DEFAULT_MAX_FINDINGS) : valueFlag.value; - index = valueFlag.index; + input[valueFlag.key] = + valueFlag.key === "maxFindings" + ? positiveInt(valueFlag.value, DEFAULT_MAX_FINDINGS) + : valueFlag.value; + index = valueFlag.nextIndex; + continue; } else targetParts.push(token); + index += 1; } if (!input.target) input.target = targetParts.join(" ").trim(); - return input; + return { input: input, error: "" }; } function parseValueFlag(tokens, index) { @@ -348,11 +482,13 @@ function parseValueFlag(tokens, index) { for (let flagIndex = 0; flagIndex < flags.length; flagIndex += 1) { const flag = flags[flagIndex]; if (token === flag.name) { - assert(index + 1 < tokens.length, flag.name + " requires a value"); - return { key: flag.key, value: tokens[index + 1], index: index + 1 }; + if (index + 1 >= tokens.length) return { error: flag.name + " requires a value" }; + return { key: flag.key, value: tokens[index + 1], nextIndex: index + 2 }; } if (token.indexOf(flag.name + "=") === 0) { - return { key: flag.key, value: token.slice(flag.name.length + 1), index: index }; + const value = token.slice(flag.name.length + 1); + if (!value) return { error: flag.name + " requires a value" }; + return { key: flag.key, value: value, nextIndex: index + 1 }; } } return null; @@ -380,10 +516,10 @@ function tokenize(input) { current = ""; } else current += char; } - assert(!quote, "unterminated quoted argument"); + if (quote) return { tokens: tokens, error: "unterminated quoted argument" }; if (escaped) current += "\\"; if (current) tokens.push(current); - return tokens; + return { tokens: tokens, error: "" }; } function readOnlyPrompt() { @@ -391,40 +527,52 @@ function readOnlyPrompt() { } function reviewOnlyReport(input, markdown) { - const mode = input.fix ? "No actionable fixes were selected." : "Review-only mode; no fixes were applied."; + const mode = input.fix + ? "No actionable fixes were selected." + : "Review-only mode; no fixes were applied."; return markdown + "\n\n---\n\n## Simplify workflow result\n\n" + mode; } function fixReport(synthesisMarkdown, fixerMarkdown, applied) { const status = applied && applied.status ? applied.status : "unknown"; const success = Boolean(applied && applied.success); - return synthesisMarkdown + "\n\n---\n\n## Fix pass\n\n" + fixerMarkdown + "\n\n### Patch application\n\n- Status: " + status + "\n- Success: " + String(success); + return ( + synthesisMarkdown + + "\n\n---\n\n## Fix pass\n\n" + + fixerMarkdown + + "\n\n### Patch application\n\n- Status: " + + status + + "\n- Success: " + + String(success) + ); } -function usageResult() { +function usageResult(error) { + const lines = [ + "# simplify workflow", + "", + "Review current git changes for code reuse, quality, and efficiency, then fix actionable issues.", + "", + "## Usage", + "", + "- `/workflow simplify` — review current git changes and apply fixes.", + "- `/workflow simplify --review-only` — review and synthesize findings without applying fixes.", + "- `/workflow simplify --base main --head HEAD` — review a specific ref range.", + "- `/workflow simplify path/or/context` — provide an explicit target when there are no Git changes.", + "", + "## Options", + "", + "- `--review-only` / `--no-fix`", + "- `--fix`", + "- `--base `", + "- `--trunk `", + "- `--head `", + "- `--max-findings `", + ]; + if (error) lines.splice(2, 0, "", "**Argument error:** " + error); return { - reportMarkdown: [ - "# simplify workflow", - "", - "Review current git changes for code reuse, quality, and efficiency, then fix actionable issues.", - "", - "## Usage", - "", - "- `/workflow simplify` — review current git changes and apply fixes.", - "- `/workflow simplify --review-only` — review and synthesize findings without applying fixes.", - "- `/workflow simplify --base main --head HEAD` — review a specific ref range.", - "- `/workflow simplify path/or/context` — provide an explicit target when there are no Git changes.", - "", - "## Options", - "", - "- `--review-only` / `--no-fix`", - "- `--fix`", - "- `--base `", - "- `--trunk `", - "- `--head `", - "- `--max-findings `", - ].join("\n"), - structuredOutput: { help: true }, + reportMarkdown: lines.join("\n"), + structuredOutput: { help: true, error: error || "" }, }; } @@ -445,6 +593,10 @@ function text(value) { return typeof value === "string" ? value.trim() : ""; } +function stringLength(value) { + return typeof value === "string" ? value.length : 0; +} + function positiveInt(value, fallback) { const number = Number(value); return Number.isFinite(number) && number >= 1 ? Math.floor(number) : fallback; From 5ffe3144fab68adf7bc8803435a1c563186760a2 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Mon, 15 Jun 2026 15:11:42 +0000 Subject: [PATCH 03/23] =?UTF-8?q?=F0=9F=A4=96=20refactor:=20reduce=20simpl?= =?UTF-8?q?ify=20workflow=20overhead?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apply the latest simplify workflow dogfood findings: - reuse the base resolved by changedFiles for diffStat/diff calls - avoid an adapter allocation when summarizing workflow diff compactions Validation: - workflow_read simplify - workflow_run simplify --help - workflow_run simplify --base - bunx prettier --check .mux/workflows/simplify.js - make static-check --- _Generated with `mux` • Model: `openai:gpt-5.5` • Thinking: `xhigh` • Cost: `72154{MUX_COSTS_USD:-unknown}`_ --- .mux/workflows/simplify.js | 44 +++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/.mux/workflows/simplify.js b/.mux/workflows/simplify.js index 12d0d4487e..452782aad2 100644 --- a/.mux/workflows/simplify.js +++ b/.mux/workflows/simplify.js @@ -245,23 +245,30 @@ export default function simplifyWorkflow({ } function collectGitContext(action, input) { - const refs = gitRefs(input); + const requestedRefs = gitRefs(input); const failures = []; + const status = gitSlice(failures, "status", function () { + return action.git.status({ + id: "git-status", + input: { includeIgnored: false }, + builtInOnly: true, + }).output; + }); + const changedFiles = gitSlice(failures, "changedFiles", function () { + return action.git.changedFiles({ + id: "git-changed-files", + input: requestedRefs, + builtInOnly: true, + }).output; + }); + const refs = refsWithResolvedBase(requestedRefs, changedFiles); + return { target: input.target, refs: refs, failures: failures, - status: gitSlice(failures, "status", function () { - return action.git.status({ - id: "git-status", - input: { includeIgnored: false }, - builtInOnly: true, - }).output; - }), - changedFiles: gitSlice(failures, "changedFiles", function () { - return action.git.changedFiles({ id: "git-changed-files", input: refs, builtInOnly: true }) - .output; - }), + status: status, + changedFiles: changedFiles, diffStat: gitSlice(failures, "diffStat", function () { return action.git.diffStat({ id: "git-diff-stat", input: refs, builtInOnly: true }).output; }), @@ -288,6 +295,17 @@ function gitRefs(input) { return refs; } +function refsWithResolvedBase(refs, changedFiles) { + if (refs.base || refs.trunk || !changedFiles || typeof changedFiles.base !== "string") { + return refs; + } + if (!changedFiles.base) return refs; + + const resolved = { base: changedFiles.base }; + if (refs.head) resolved.head = refs.head; + return resolved; +} + function promptContexts(input, gitContext) { const reviewGitContext = withCompactedDiff(gitContext, REVIEW_DIFF_CHAR_BUDGET); const outputGitContext = withoutDiffText(gitContext, reviewGitContext.diff); @@ -373,7 +391,7 @@ function diffSummary(diff, compactedDiff) { mergeBase: diff.mergeBase, truncated: diff.truncated, workflowBudgetChars: compactedDiff && compactedDiff.workflowBudgetChars, - workflowCompactions: diffCompactions({ diff: compactedDiff }), + workflowCompactions: compactedDiff ? asArray(compactedDiff.workflowCompactions) : [], chars: { branch: stringLength(diff.branch), staged: stringLength(diff.staged), From 7c6b5ac02244fda28d35cff009b27badc0605ed0 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Mon, 15 Jun 2026 15:20:39 +0000 Subject: [PATCH 04/23] =?UTF-8?q?=F0=9F=A4=96=20refactor:=20add=20simplify?= =?UTF-8?q?=20no-op=20and=20metadata=20bounds?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apply the final dogfood findings for the simplify workflow: - return early when there are no reviewable changes - bound compact git metadata in prompts and outputs - compose finding schemas from shared core fields - use object spread for git context variants Validation: - workflow_read simplify - workflow_run simplify --help - workflow_run simplify --base - bunx prettier --check .mux/workflows/simplify.js - make static-check --- _Generated with `mux` • Model: `openai:gpt-5.5` • Thinking: `xhigh` • Cost: `149245{MUX_COSTS_USD:-unknown}`_ --- .mux/workflows/simplify.js | 149 ++++++++++++++++++++++++++++++------- 1 file changed, 121 insertions(+), 28 deletions(-) diff --git a/.mux/workflows/simplify.js b/.mux/workflows/simplify.js index 452782aad2..dbec3a052c 100644 --- a/.mux/workflows/simplify.js +++ b/.mux/workflows/simplify.js @@ -3,6 +3,8 @@ const DEFAULT_MAX_FINDINGS = 20; // Review agents get bounded diff text; synthesis/fix phases get metadata only. const REVIEW_DIFF_CHAR_BUDGET = 60000; +const METADATA_ARRAY_ITEM_BUDGET = 200; +const DIFF_STAT_CHAR_BUDGET = 20000; const REVIEW_AGENT_ID = "explore"; const EXEC_AGENT_ID = "exec"; const REVIEW_LANES = [ @@ -36,15 +38,18 @@ const REVIEW_LANES = [ ]; const SEVERITY_SCHEMA = { type: "string", enum: ["high", "medium", "low"] }; +const FINDING_CORE_PROPERTIES = { + id: { type: "string" }, + title: { type: "string" }, + severity: SEVERITY_SCHEMA, + filePaths: { type: "array", items: { type: "string" } }, + rationale: { type: "string" }, +}; const FINDING_SCHEMA = { type: "object", required: ["id", "title", "severity", "filePaths", "rationale", "recommendation", "evidence"], properties: { - id: { type: "string" }, - title: { type: "string" }, - severity: SEVERITY_SCHEMA, - filePaths: { type: "array", items: { type: "string" } }, - rationale: { type: "string" }, + ...FINDING_CORE_PROPERTIES, recommendation: { type: "string" }, evidence: { type: "array", items: { type: "string" } }, }, @@ -61,11 +66,7 @@ const SYNTHESIS_FINDING_SCHEMA = { type: "object", required: ["id", "title", "severity", "filePaths", "rationale", "fixPlan"], properties: { - id: { type: "string" }, - title: { type: "string" }, - severity: SEVERITY_SCHEMA, - filePaths: { type: "array", items: { type: "string" } }, - rationale: { type: "string" }, + ...FINDING_CORE_PROPERTIES, fixPlan: { type: "string" }, }, }; @@ -147,6 +148,18 @@ export default function simplifyWorkflow({ diffCompactions: diffCompactions(contexts.outputGitContext).length, }); + if (!hasReviewableContext(input, gitContext)) { + return { + reportMarkdown: "## Simplify workflow result\n\nNo reviewable changes found.", + structuredOutput: { + mode: "no-reviewable-changes", + gitContext: contexts.outputGitContext, + reviews: [], + synthesis: emptySynthesis("No reviewable changes found."), + }, + }; + } + phase("review", { lanes: REVIEW_LANES.map(function (lane) { return lane.id; @@ -306,9 +319,37 @@ function refsWithResolvedBase(refs, changedFiles) { return resolved; } +function hasReviewableContext(input, gitContext) { + if (input.target) return true; + if (asArray(gitContext.failures).length > 0) return true; + if (!gitContext.status || gitContext.status.clean !== true) return true; + return ( + hasArrayItems(gitContext.status.staged) || + hasArrayItems(gitContext.status.unstaged) || + hasArrayItems(gitContext.status.untracked) || + hasArrayItems(gitContext.changedFiles && gitContext.changedFiles.branch) || + hasArrayItems(gitContext.changedFiles && gitContext.changedFiles.staged) || + hasArrayItems(gitContext.changedFiles && gitContext.changedFiles.unstaged) || + hasArrayItems(gitContext.changedFiles && gitContext.changedFiles.untracked) || + hasText(gitContext.diff && gitContext.diff.branch) || + hasText(gitContext.diff && gitContext.diff.staged) || + hasText(gitContext.diff && gitContext.diff.unstaged) + ); +} + +function emptySynthesis(summary) { + return { + summary: summary, + shouldFix: false, + actionableFindings: [], + skippedFindings: [], + validationPlan: [], + }; +} + function promptContexts(input, gitContext) { - const reviewGitContext = withCompactedDiff(gitContext, REVIEW_DIFF_CHAR_BUDGET); - const outputGitContext = withoutDiffText(gitContext, reviewGitContext.diff); + const reviewGitContext = compactMetadata(withCompactedDiff(gitContext, REVIEW_DIFF_CHAR_BUDGET)); + const outputGitContext = compactMetadata(withoutDiffText(gitContext, reviewGitContext.diff)); return { review: renderContext(input, reviewGitContext), compact: renderContext(input, outputGitContext), @@ -323,30 +364,74 @@ function renderContext(input, gitContext) { }); } -function withCompactedDiff(gitContext, budget) { +function compactMetadata(gitContext) { return { - target: gitContext.target, - refs: gitContext.refs, - failures: gitContext.failures, - status: gitContext.status, - changedFiles: gitContext.changedFiles, - diffStat: gitContext.diffStat, - diff: compactDiff(gitContext.diff, budget), + ...gitContext, + status: compactStatus(gitContext.status), + changedFiles: compactChangedFiles(gitContext.changedFiles), + diffStat: compactDiffStat(gitContext.diffStat), }; } -function withoutDiffText(gitContext, compactedDiff) { +function compactStatus(status) { + if (!status || typeof status !== "object") return status; + return { + ...status, + staged: compactArray(status.staged, METADATA_ARRAY_ITEM_BUDGET), + unstaged: compactArray(status.unstaged, METADATA_ARRAY_ITEM_BUDGET), + untracked: compactArray(status.untracked, METADATA_ARRAY_ITEM_BUDGET), + ignored: compactArray(status.ignored, METADATA_ARRAY_ITEM_BUDGET), + }; +} + +function compactChangedFiles(changedFiles) { + if (!changedFiles || typeof changedFiles !== "object") return changedFiles; + return { + ...changedFiles, + branch: compactArray(changedFiles.branch, METADATA_ARRAY_ITEM_BUDGET), + staged: compactArray(changedFiles.staged, METADATA_ARRAY_ITEM_BUDGET), + unstaged: compactArray(changedFiles.unstaged, METADATA_ARRAY_ITEM_BUDGET), + untracked: compactArray(changedFiles.untracked, METADATA_ARRAY_ITEM_BUDGET), + }; +} + +function compactDiffStat(diffStat) { + if (!diffStat || typeof diffStat !== "object") return diffStat; return { - target: gitContext.target, - refs: gitContext.refs, - failures: gitContext.failures, - status: gitContext.status, - changedFiles: gitContext.changedFiles, - diffStat: gitContext.diffStat, - diff: diffSummary(gitContext.diff, compactedDiff), + ...diffStat, + branch: compactText(diffStat.branch, DIFF_STAT_CHAR_BUDGET), + staged: compactText(diffStat.staged, DIFF_STAT_CHAR_BUDGET), + unstaged: compactText(diffStat.unstaged, DIFF_STAT_CHAR_BUDGET), }; } +function compactArray(value, limit) { + if (!Array.isArray(value) || value.length <= limit) return value; + return { + total: value.length, + shown: value.slice(0, limit), + omitted: value.length - limit, + }; +} + +function compactText(value, limit) { + if (typeof value !== "string" || value.length <= limit) return value; + return ( + value.slice(0, limit) + + "\n\n[Workflow metadata budget omitted " + + (value.length - limit) + + " chars.]" + ); +} + +function withCompactedDiff(gitContext, budget) { + return { ...gitContext, diff: compactDiff(gitContext.diff, budget) }; +} + +function withoutDiffText(gitContext, compactedDiff) { + return { ...gitContext, diff: diffSummary(gitContext.diff, compactedDiff) }; +} + function compactDiff(diff, budget) { if (!diff || typeof diff !== "object") return diff; @@ -611,6 +696,14 @@ function text(value) { return typeof value === "string" ? value.trim() : ""; } +function hasArrayItems(value) { + return Array.isArray(value) && value.length > 0; +} + +function hasText(value) { + return typeof value === "string" && value.length > 0; +} + function stringLength(value) { return typeof value === "string" ? value.length : 0; } From 028ad3c4a59b3ee724e33023d00016ac4468f297 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Mon, 15 Jun 2026 15:33:51 +0000 Subject: [PATCH 05/23] =?UTF-8?q?=F0=9F=A4=96=20refactor:=20slim=20simplif?= =?UTF-8?q?y=20workflow=20helpers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address simplify workflow review cleanup: - turn the read-only prompt and no-op synthesis into constants - inline one-off context/diff helper wrappers - reuse optional chaining and existing tiny predicates where it shortens code - hoist the value-flag table out of parseValueFlag Validation: - workflow_read simplify - workflow_run simplify --help - workflow_run simplify --base - bunx prettier --check .mux/workflows/simplify.js - make static-check --- _Generated with `mux` • Model: `openai:gpt-5.5` • Thinking: `xhigh` • Cost: `423759{MUX_COSTS_USD:-unknown}`_ --- .mux/workflows/simplify.js | 78 +++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 44 deletions(-) diff --git a/.mux/workflows/simplify.js b/.mux/workflows/simplify.js index dbec3a052c..74e9e21fef 100644 --- a/.mux/workflows/simplify.js +++ b/.mux/workflows/simplify.js @@ -5,6 +5,15 @@ const DEFAULT_MAX_FINDINGS = 20; const REVIEW_DIFF_CHAR_BUDGET = 60000; const METADATA_ARRAY_ITEM_BUDGET = 200; const DIFF_STAT_CHAR_BUDGET = 20000; +const NO_REVIEWABLE_CHANGES_SUMMARY = "No reviewable changes found."; +const READ_ONLY_PROMPT = + "This is a read-only review step. Do not edit files, create commits, apply patches, push branches, or open PRs. Inspect repository evidence only as needed and report findings."; +const VALUE_FLAGS = [ + { name: "--base", key: "baseRef" }, + { name: "--trunk", key: "trunkRef" }, + { name: "--head", key: "headRef" }, + { name: "--max-findings", key: "maxFindings" }, +]; const REVIEW_AGENT_ID = "explore"; const EXEC_AGENT_ID = "exec"; const REVIEW_LANES = [ @@ -122,6 +131,14 @@ const FIXER_SCHEMA = { }, }; +const EMPTY_SYNTHESIS = { + summary: NO_REVIEWABLE_CHANGES_SUMMARY, + shouldFix: false, + actionableFindings: [], + skippedFindings: [], + validationPlan: [], +}; + export default function simplifyWorkflow({ args, phase, @@ -145,17 +162,17 @@ export default function simplifyWorkflow({ log("Captured simplify context", { target: input.target || "current git changes", gitFailures: gitContext.failures.length, - diffCompactions: diffCompactions(contexts.outputGitContext).length, + diffCompactions: asArray(contexts.outputGitContext.diff?.workflowCompactions).length, }); if (!hasReviewableContext(input, gitContext)) { return { - reportMarkdown: "## Simplify workflow result\n\nNo reviewable changes found.", + reportMarkdown: "## Simplify workflow result\n\n" + NO_REVIEWABLE_CHANGES_SUMMARY, structuredOutput: { mode: "no-reviewable-changes", gitContext: contexts.outputGitContext, reviews: [], - synthesis: emptySynthesis("No reviewable changes found."), + synthesis: EMPTY_SYNTHESIS, }, }; } @@ -337,19 +354,14 @@ function hasReviewableContext(input, gitContext) { ); } -function emptySynthesis(summary) { - return { - summary: summary, - shouldFix: false, - actionableFindings: [], - skippedFindings: [], - validationPlan: [], - }; -} - function promptContexts(input, gitContext) { - const reviewGitContext = compactMetadata(withCompactedDiff(gitContext, REVIEW_DIFF_CHAR_BUDGET)); - const outputGitContext = compactMetadata(withoutDiffText(gitContext, reviewGitContext.diff)); + const compactedGitContext = compactMetadata(gitContext); + const reviewDiff = compactDiff(gitContext.diff, REVIEW_DIFF_CHAR_BUDGET); + const reviewGitContext = { ...compactedGitContext, diff: reviewDiff }; + const outputGitContext = { + ...compactedGitContext, + diff: diffSummary(gitContext.diff, reviewDiff), + }; return { review: renderContext(input, reviewGitContext), compact: renderContext(input, outputGitContext), @@ -424,14 +436,6 @@ function compactText(value, limit) { ); } -function withCompactedDiff(gitContext, budget) { - return { ...gitContext, diff: compactDiff(gitContext.diff, budget) }; -} - -function withoutDiffText(gitContext, compactedDiff) { - return { ...gitContext, diff: diffSummary(gitContext.diff, compactedDiff) }; -} - function compactDiff(diff, budget) { if (!diff || typeof diff !== "object") return diff; @@ -485,10 +489,6 @@ function diffSummary(diff, compactedDiff) { }; } -function diffCompactions(gitContext) { - return asArray(gitContext && gitContext.diff && gitContext.diff.workflowCompactions); -} - function diffOmittedMessage(field) { return ( "\n\n[Workflow prompt budget omitted the rest of the " + @@ -499,7 +499,7 @@ function diffOmittedMessage(field) { function reviewPrompt(lane, input, reviewContext) { return [ - readOnlyPrompt(), + READ_ONLY_PROMPT, "You are the " + lane.title + " lane. Review every changed file in the supplied Git context.", "If an explicit target is provided and the Git diff is empty, inspect that target path in the workspace before making claims.", "Diff text is capped by workflowBudgetChars; if workflowCompactions or built-in truncated flags are present, inspect files directly before making claims about omitted hunks.", @@ -514,7 +514,7 @@ function reviewPrompt(lane, input, reviewContext) { function synthesisPrompt(input, compactContext, reviewOutputs) { return [ - readOnlyPrompt(), + READ_ONLY_PROMPT, "Deduplicate and triage these simplify review findings. Keep actionableFindings to the " + input.maxFindings + " highest-value issues.", @@ -575,20 +575,14 @@ function parseArgs(args) { } function parseValueFlag(tokens, index) { - const flags = [ - { name: "--base", key: "baseRef" }, - { name: "--trunk", key: "trunkRef" }, - { name: "--head", key: "headRef" }, - { name: "--max-findings", key: "maxFindings" }, - ]; const token = tokens[index]; - for (let flagIndex = 0; flagIndex < flags.length; flagIndex += 1) { - const flag = flags[flagIndex]; + for (let flagIndex = 0; flagIndex < VALUE_FLAGS.length; flagIndex += 1) { + const flag = VALUE_FLAGS[flagIndex]; if (token === flag.name) { if (index + 1 >= tokens.length) return { error: flag.name + " requires a value" }; return { key: flag.key, value: tokens[index + 1], nextIndex: index + 2 }; } - if (token.indexOf(flag.name + "=") === 0) { + if (token.startsWith(flag.name + "=")) { const value = token.slice(flag.name.length + 1); if (!value) return { error: flag.name + " requires a value" }; return { key: flag.key, value: value, nextIndex: index + 1 }; @@ -625,10 +619,6 @@ function tokenize(input) { return { tokens: tokens, error: "" }; } -function readOnlyPrompt() { - return "This is a read-only review step. Do not edit files, create commits, apply patches, push branches, or open PRs. Inspect repository evidence only as needed and report findings."; -} - function reviewOnlyReport(input, markdown) { const mode = input.fix ? "No actionable fixes were selected." @@ -697,11 +687,11 @@ function text(value) { } function hasArrayItems(value) { - return Array.isArray(value) && value.length > 0; + return asArray(value).length > 0; } function hasText(value) { - return typeof value === "string" && value.length > 0; + return stringLength(value) > 0; } function stringLength(value) { From ef7924739e9f21b3a4959dd861e95eb057f6f0b6 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Mon, 15 Jun 2026 15:54:58 +0000 Subject: [PATCH 06/23] =?UTF-8?q?=F0=9F=A4=96=20refactor:=20tighten=20simp?= =?UTF-8?q?lify=20workflow=20handoffs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .mux/workflows/simplify.js | 130 +++++++++++++++++++++++++------------ 1 file changed, 89 insertions(+), 41 deletions(-) diff --git a/.mux/workflows/simplify.js b/.mux/workflows/simplify.js index 74e9e21fef..33c1e9b8ea 100644 --- a/.mux/workflows/simplify.js +++ b/.mux/workflows/simplify.js @@ -1,10 +1,13 @@ // description: Review current changes for reuse, quality, and efficiency, then fix actionable issues. +// Workflow files execute as self-contained JavaScript; keep small helpers inline instead of importing repo utilities. const DEFAULT_MAX_FINDINGS = 20; // Review agents get bounded diff text; synthesis/fix phases get metadata only. const REVIEW_DIFF_CHAR_BUDGET = 60000; const METADATA_ARRAY_ITEM_BUDGET = 200; const DIFF_STAT_CHAR_BUDGET = 20000; +const REVIEW_EVIDENCE_ITEM_BUDGET = 3; +const REVIEW_EVIDENCE_CHAR_BUDGET = 500; const NO_REVIEWABLE_CHANGES_SUMMARY = "No reviewable changes found."; const READ_ONLY_PROMPT = "This is a read-only review step. Do not edit files, create commits, apply patches, push branches, or open PRs. Inspect repository evidence only as needed and report findings."; @@ -14,6 +17,9 @@ const VALUE_FLAGS = [ { name: "--head", key: "headRef" }, { name: "--max-findings", key: "maxFindings" }, ]; +const STATUS_ARRAY_FIELDS = ["staged", "unstaged", "untracked", "ignored"]; +const CHANGED_FILE_ARRAY_FIELDS = ["branch", "staged", "unstaged", "untracked"]; +const DIFF_FIELDS = ["branch", "staged", "unstaged"]; const REVIEW_AGENT_ID = "explore"; const EXEC_AGENT_ID = "exec"; const REVIEW_LANES = [ @@ -157,7 +163,7 @@ export default function simplifyWorkflow({ if (input.help) return usageResult(); phase("capture-context", { target: input.target || "current git changes", fix: input.fix }); - const gitContext = collectGitContext(action, input); + const gitContext = collectGitContext(action, input, log); const contexts = promptContexts(input, gitContext); log("Captured simplify context", { target: input.target || "current git changes", @@ -274,17 +280,17 @@ export default function simplifyWorkflow({ }; } -function collectGitContext(action, input) { +function collectGitContext(action, input, log) { const requestedRefs = gitRefs(input); const failures = []; - const status = gitSlice(failures, "status", function () { + const status = gitSlice(log, failures, "status", function () { return action.git.status({ id: "git-status", input: { includeIgnored: false }, builtInOnly: true, }).output; }); - const changedFiles = gitSlice(failures, "changedFiles", function () { + const changedFiles = gitSlice(log, failures, "changedFiles", function () { return action.git.changedFiles({ id: "git-changed-files", input: requestedRefs, @@ -299,20 +305,24 @@ function collectGitContext(action, input) { failures: failures, status: status, changedFiles: changedFiles, - diffStat: gitSlice(failures, "diffStat", function () { + diffStat: gitSlice(log, failures, "diffStat", function () { return action.git.diffStat({ id: "git-diff-stat", input: refs, builtInOnly: true }).output; }), - diff: gitSlice(failures, "diff", function () { + diff: gitSlice(log, failures, "diff", function () { return action.git.diff({ id: "git-diff", input: refs, builtInOnly: true }).output; }), }; } -function gitSlice(failures, name, read) { +function gitSlice(log, failures, name, read) { try { return read(); } catch (error) { - failures.push({ name: name, error: String(error) }); + const failure = { name: name, error: formatError(error) }; + failures.push(failure); + if (typeof log === "function") { + log("Git workflow action failed; continuing with partial simplify context", failure); + } return null; } } @@ -386,35 +396,29 @@ function compactMetadata(gitContext) { } function compactStatus(status) { - if (!status || typeof status !== "object") return status; - return { - ...status, - staged: compactArray(status.staged, METADATA_ARRAY_ITEM_BUDGET), - unstaged: compactArray(status.unstaged, METADATA_ARRAY_ITEM_BUDGET), - untracked: compactArray(status.untracked, METADATA_ARRAY_ITEM_BUDGET), - ignored: compactArray(status.ignored, METADATA_ARRAY_ITEM_BUDGET), - }; + return compactFields(status, STATUS_ARRAY_FIELDS, compactArray, METADATA_ARRAY_ITEM_BUDGET); } function compactChangedFiles(changedFiles) { - if (!changedFiles || typeof changedFiles !== "object") return changedFiles; - return { - ...changedFiles, - branch: compactArray(changedFiles.branch, METADATA_ARRAY_ITEM_BUDGET), - staged: compactArray(changedFiles.staged, METADATA_ARRAY_ITEM_BUDGET), - unstaged: compactArray(changedFiles.unstaged, METADATA_ARRAY_ITEM_BUDGET), - untracked: compactArray(changedFiles.untracked, METADATA_ARRAY_ITEM_BUDGET), - }; + return compactFields( + changedFiles, + CHANGED_FILE_ARRAY_FIELDS, + compactArray, + METADATA_ARRAY_ITEM_BUDGET + ); } function compactDiffStat(diffStat) { - if (!diffStat || typeof diffStat !== "object") return diffStat; - return { - ...diffStat, - branch: compactText(diffStat.branch, DIFF_STAT_CHAR_BUDGET), - staged: compactText(diffStat.staged, DIFF_STAT_CHAR_BUDGET), - unstaged: compactText(diffStat.unstaged, DIFF_STAT_CHAR_BUDGET), - }; + return compactFields(diffStat, DIFF_FIELDS, compactText, DIFF_STAT_CHAR_BUDGET); +} + +function compactFields(value, fields, compactor, limit) { + if (!value || typeof value !== "object") return value; + const compacted = { ...value }; + fields.forEach(function (field) { + compacted[field] = compactor(value[field], limit); + }); + return compacted; } function compactArray(value, limit) { @@ -449,7 +453,7 @@ function compactDiff(diff, budget) { }; let remaining = budget; - ["branch", "staged", "unstaged"].forEach(function (field) { + DIFF_FIELDS.forEach(function (field) { const value = diff[field]; if (typeof value !== "string") { compacted[field] = value; @@ -481,14 +485,18 @@ function diffSummary(diff, compactedDiff) { truncated: diff.truncated, workflowBudgetChars: compactedDiff && compactedDiff.workflowBudgetChars, workflowCompactions: compactedDiff ? asArray(compactedDiff.workflowCompactions) : [], - chars: { - branch: stringLength(diff.branch), - staged: stringLength(diff.staged), - unstaged: stringLength(diff.unstaged), - }, + chars: diffFieldLengths(diff), }; } +function diffFieldLengths(diff) { + const lengths = {}; + DIFF_FIELDS.forEach(function (field) { + lengths[field] = stringLength(diff[field]); + }); + return lengths; +} + function diffOmittedMessage(field) { return ( "\n\n[Workflow prompt budget omitted the rest of the " + @@ -518,24 +526,60 @@ function synthesisPrompt(input, compactContext, reviewOutputs) { "Deduplicate and triage these simplify review findings. Keep actionableFindings to the " + input.maxFindings + " highest-value issues.", - "Fix actionable issues directly when fix mode is enabled. If a finding is false positive or not worth addressing, put it in skippedFindings without debating it.", + "Do not edit files in this step. Produce triage and fix plans for the later fixer step. If a finding is false positive or not worth addressing, put it in skippedFindings without debating it.", "Allowed severity values are: high, medium, low. Prefer minimal cleanup over broad refactors.", "\nCompact review context without raw diff text:\n" + compactContext, - "\nLane outputs:\n" + fencedJson(reviewOutputs), + "\nCompacted lane outputs:\n" + fencedJson(compactReviewOutputs(reviewOutputs)), ].join("\n\n"); } function fixPrompt(compactContext, synthesized) { return [ - "Fix the actionable simplify findings with minimal, correct, reviewable changes. Do not push, commit, or open a PR.", + "Fix the actionable simplify findings with minimal, correct, reviewable changes. Do not push or open a PR.", + "If you change files, create one local commit containing only those changes so the workflow can export a patch artifact.", "Use the compact context for file lists and diff metadata; inspect files directly instead of relying on raw diff text being embedded in this prompt.", "Preserve existing style and functionality. Run targeted validation for touched code when feasible and report exact commands/results.", "If a finding is false positive or not worth addressing, skip it and note why. Set madeChanges true only when files changed.", "\nCompact review context:\n" + compactContext, - "\nSynthesized findings:\n" + fencedJson(synthesized), + "\nActionable findings:\n" + fencedJson(fixerPayload(synthesized)), ].join("\n\n"); } +function compactReviewOutputs(reviewOutputs) { + return asArray(reviewOutputs).map(function (output) { + return { + summary: output && output.summary, + findings: asArray(output && output.findings).map(compactReviewFinding), + }; + }); +} + +function compactReviewFinding(finding) { + return { + id: finding && finding.id, + title: finding && finding.title, + severity: finding && finding.severity, + filePaths: asArray(finding && finding.filePaths), + rationale: finding && finding.rationale, + recommendation: finding && finding.recommendation, + evidenceCount: asArray(finding && finding.evidence).length, + evidenceSamples: asArray(finding && finding.evidence) + .slice(0, REVIEW_EVIDENCE_ITEM_BUDGET) + .map(function (evidence) { + return compactText(evidence, REVIEW_EVIDENCE_CHAR_BUDGET); + }), + }; +} + +function fixerPayload(synthesized) { + return { + summary: synthesized && synthesized.summary, + shouldFix: Boolean(synthesized && synthesized.shouldFix), + actionableFindings: asArray(synthesized && synthesized.actionableFindings), + validationPlan: asArray(synthesized && synthesized.validationPlan), + }; +} + function parseArgs(args) { const raw = args && typeof args === "object" ? args : {}; const input = { @@ -703,6 +747,10 @@ function positiveInt(value, fallback) { return Number.isFinite(number) && number >= 1 ? Math.floor(number) : fallback; } +function formatError(error) { + return error && typeof error.message === "string" ? error.message : String(error); +} + function assert(condition, message) { if (!condition) throw new Error(message); } From 24b106a6185fb6146f568a2ebc267d9f4fad6979 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Mon, 15 Jun 2026 16:05:34 +0000 Subject: [PATCH 07/23] =?UTF-8?q?=F0=9F=A4=96=20refactor:=20trim=20simplif?= =?UTF-8?q?y=20workflow=20context=20reads?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Thomas Kosiewski --- .mux/workflows/simplify.js | 89 ++++++++++++++++++++++---------------- 1 file changed, 51 insertions(+), 38 deletions(-) diff --git a/.mux/workflows/simplify.js b/.mux/workflows/simplify.js index 33c1e9b8ea..baea3dff6e 100644 --- a/.mux/workflows/simplify.js +++ b/.mux/workflows/simplify.js @@ -204,7 +204,7 @@ export default function simplifyWorkflow({ }); const rawFindingCount = reviewOutputs.reduce(function (count, output) { - return count + asArray(output.findings).length; + return count + output.findings.length; }, 0); phase("synthesize", { rawFindingCount: rawFindingCount }); @@ -219,7 +219,7 @@ export default function simplifyWorkflow({ synthesis.structuredOutput, "synthesis structured output is required" ); - const actionableFindings = asArray(synthesized.actionableFindings); + const actionableFindings = synthesized.actionableFindings; if (!input.fix || !synthesized.shouldFix || actionableFindings.length === 0) { return { @@ -298,6 +298,7 @@ function collectGitContext(action, input, log) { }).output; }); const refs = refsWithResolvedBase(requestedRefs, changedFiles); + const readDiffs = shouldReadDiffs(changedFiles); return { target: input.target, @@ -305,12 +306,17 @@ function collectGitContext(action, input, log) { failures: failures, status: status, changedFiles: changedFiles, - diffStat: gitSlice(log, failures, "diffStat", function () { - return action.git.diffStat({ id: "git-diff-stat", input: refs, builtInOnly: true }).output; - }), - diff: gitSlice(log, failures, "diff", function () { - return action.git.diff({ id: "git-diff", input: refs, builtInOnly: true }).output; - }), + diffStat: readDiffs + ? gitSlice(log, failures, "diffStat", function () { + return action.git.diffStat({ id: "git-diff-stat", input: refs, builtInOnly: true }) + .output; + }) + : null, + diff: readDiffs + ? gitSlice(log, failures, "diff", function () { + return action.git.diff({ id: "git-diff", input: refs, builtInOnly: true }).output; + }) + : null, }; } @@ -336,14 +342,25 @@ function gitRefs(input) { } function refsWithResolvedBase(refs, changedFiles) { - if (refs.base || refs.trunk || !changedFiles || typeof changedFiles.base !== "string") { - return refs; + const base = + changedFiles && typeof changedFiles === "object" && typeof changedFiles.base === "string" + ? changedFiles.base + : ""; + if (refs.base || refs.trunk || !base) return refs; + return { ...refs, base }; +} + +// The diff actions only return branch/staged/unstaged hunks; untracked-only contexts +// are captured by changedFiles. +function shouldReadDiffs(changedFiles) { + if (!changedFiles || typeof changedFiles !== "object" || Array.isArray(changedFiles)) { + return true; } - if (!changedFiles.base) return refs; - - const resolved = { base: changedFiles.base }; - if (refs.head) resolved.head = refs.head; - return resolved; + return ( + hasArrayItems(changedFiles.branch) || + hasArrayItems(changedFiles.staged) || + hasArrayItems(changedFiles.unstaged) + ); } function hasReviewableContext(input, gitContext) { @@ -351,9 +368,6 @@ function hasReviewableContext(input, gitContext) { if (asArray(gitContext.failures).length > 0) return true; if (!gitContext.status || gitContext.status.clean !== true) return true; return ( - hasArrayItems(gitContext.status.staged) || - hasArrayItems(gitContext.status.unstaged) || - hasArrayItems(gitContext.status.untracked) || hasArrayItems(gitContext.changedFiles && gitContext.changedFiles.branch) || hasArrayItems(gitContext.changedFiles && gitContext.changedFiles.staged) || hasArrayItems(gitContext.changedFiles && gitContext.changedFiles.unstaged) || @@ -546,37 +560,36 @@ function fixPrompt(compactContext, synthesized) { } function compactReviewOutputs(reviewOutputs) { - return asArray(reviewOutputs).map(function (output) { + return reviewOutputs.map(function (output) { return { - summary: output && output.summary, - findings: asArray(output && output.findings).map(compactReviewFinding), + summary: output.summary, + findings: output.findings.map(compactReviewFinding), }; }); } function compactReviewFinding(finding) { + const evidence = finding.evidence; return { - id: finding && finding.id, - title: finding && finding.title, - severity: finding && finding.severity, - filePaths: asArray(finding && finding.filePaths), - rationale: finding && finding.rationale, - recommendation: finding && finding.recommendation, - evidenceCount: asArray(finding && finding.evidence).length, - evidenceSamples: asArray(finding && finding.evidence) - .slice(0, REVIEW_EVIDENCE_ITEM_BUDGET) - .map(function (evidence) { - return compactText(evidence, REVIEW_EVIDENCE_CHAR_BUDGET); - }), + id: finding.id, + title: finding.title, + severity: finding.severity, + filePaths: finding.filePaths, + rationale: finding.rationale, + recommendation: finding.recommendation, + evidenceCount: evidence.length, + evidenceSamples: evidence.slice(0, REVIEW_EVIDENCE_ITEM_BUDGET).map(function (evidenceItem) { + return compactText(evidenceItem, REVIEW_EVIDENCE_CHAR_BUDGET); + }), }; } function fixerPayload(synthesized) { return { - summary: synthesized && synthesized.summary, - shouldFix: Boolean(synthesized && synthesized.shouldFix), - actionableFindings: asArray(synthesized && synthesized.actionableFindings), - validationPlan: asArray(synthesized && synthesized.validationPlan), + summary: synthesized.summary, + shouldFix: synthesized.shouldFix, + actionableFindings: synthesized.actionableFindings, + validationPlan: synthesized.validationPlan, }; } @@ -731,7 +744,7 @@ function text(value) { } function hasArrayItems(value) { - return asArray(value).length > 0; + return Array.isArray(value) && value.length > 0; } function hasText(value) { From d47ab0f38d7a90ffdfed1f309c3174de42abc482 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Mon, 15 Jun 2026 16:20:57 +0000 Subject: [PATCH 08/23] =?UTF-8?q?=F0=9F=A4=96=20fix:=20apply=20simplify=20?= =?UTF-8?q?fixes=20onto=20dirty=20worktrees?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .mux/workflows/simplify.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.mux/workflows/simplify.js b/.mux/workflows/simplify.js index baea3dff6e..2dd496d5e8 100644 --- a/.mux/workflows/simplify.js +++ b/.mux/workflows/simplify.js @@ -265,6 +265,8 @@ export default function simplifyWorkflow({ source: fixer, target: "parent", threeWay: true, + // simplify fixes the dirty changes it just reviewed, so patch onto that worktree. + force: true, onConflict: "return", }); From eb89614f11b69f30060c49cdf76cefdc54cba674 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Mon, 15 Jun 2026 16:29:03 +0000 Subject: [PATCH 09/23] =?UTF-8?q?=F0=9F=A4=96=20fix:=20skip=20simplify=20a?= =?UTF-8?q?uto-fix=20for=20staged=20changes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .mux/workflows/simplify.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/.mux/workflows/simplify.js b/.mux/workflows/simplify.js index 2dd496d5e8..579bafce8b 100644 --- a/.mux/workflows/simplify.js +++ b/.mux/workflows/simplify.js @@ -233,6 +233,22 @@ export default function simplifyWorkflow({ }; } + // git am cannot apply onto a dirty index; keep the review result but skip auto-fix. + if (hasStagedChanges(gitContext)) { + return { + reportMarkdown: + synthesis.reportMarkdown + + "\n\n---\n\n## Simplify workflow result\n\n" + + "Auto-fix was skipped because staged changes are present. Unstage or commit staged changes, then rerun `/workflow simplify --fix`.", + structuredOutput: { + mode: "staged-changes-skip-fix", + gitContext: contexts.outputGitContext, + reviews: reviewOutputs, + synthesis: synthesized, + }, + }; + } + phase("fix", { actionableFindingCount: actionableFindings.length }); const fixer = agent({ id: "fix-simplify-findings", @@ -365,6 +381,13 @@ function shouldReadDiffs(changedFiles) { ); } +function hasStagedChanges(gitContext) { + return ( + hasArrayItems(gitContext.status && gitContext.status.staged) || + hasArrayItems(gitContext.changedFiles && gitContext.changedFiles.staged) + ); +} + function hasReviewableContext(input, gitContext) { if (input.target) return true; if (asArray(gitContext.failures).length > 0) return true; From ec1b61c2d2c71b8f33fbd592a1b75338390f2375 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Mon, 15 Jun 2026 16:36:07 +0000 Subject: [PATCH 10/23] =?UTF-8?q?=F0=9F=A4=96=20fix:=20refresh=20simplify?= =?UTF-8?q?=20apply=20preflight?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .mux/workflows/simplify.js | 67 +++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/.mux/workflows/simplify.js b/.mux/workflows/simplify.js index 579bafce8b..772be6380b 100644 --- a/.mux/workflows/simplify.js +++ b/.mux/workflows/simplify.js @@ -239,7 +239,7 @@ export default function simplifyWorkflow({ reportMarkdown: synthesis.reportMarkdown + "\n\n---\n\n## Simplify workflow result\n\n" + - "Auto-fix was skipped because staged changes are present. Unstage or commit staged changes, then rerun `/workflow simplify --fix`.", + stagedChangesSkipReason(), structuredOutput: { mode: "staged-changes-skip-fix", gitContext: contexts.outputGitContext, @@ -276,11 +276,31 @@ export default function simplifyWorkflow({ } phase("apply-fixes", { madeChanges: true }); + const applyPreflight = collectApplyPreflight(action, log, gitContext); + if (applyPreflight.skippedReason) { + return { + reportMarkdown: + synthesis.reportMarkdown + + "\n\n---\n\n## Fix pass\n\n" + + fixer.reportMarkdown + + "\n\n### Patch application\n\n" + + applyPreflight.skippedReason, + structuredOutput: { + mode: "apply-preflight-skip", + gitContext: contexts.outputGitContext, + reviews: reviewOutputs, + synthesis: synthesized, + fix: { fixer: fixerOutput, applied: applyPreflight }, + }, + }; + } + const applied = applyPatch({ id: "apply-simplify-fixes", source: fixer, target: "parent", threeWay: true, + expectedHeadSha: applyPreflight.expectedHeadSha, // simplify fixes the dirty changes it just reviewed, so patch onto that worktree. force: true, onConflict: "return", @@ -298,6 +318,51 @@ export default function simplifyWorkflow({ }; } +function collectApplyPreflight(action, log, gitContext) { + let status = null; + try { + status = action.git.status({ + id: "apply-git-status", + input: { includeIgnored: false }, + builtInOnly: true, + cache: false, + }).output; + } catch (error) { + const message = formatError(error); + log("Git status unavailable for simplify auto-fix preflight", { error: message }); + return failedApplyPreflight("Auto-fix was skipped because fresh Git status was unavailable."); + } + + if (!status || typeof status !== "object") { + return failedApplyPreflight("Auto-fix was skipped because fresh Git status was unavailable."); + } + if (hasArrayItems(status.staged)) { + return failedApplyPreflight(stagedChangesSkipReason()); + } + + const reviewedHeadSha = gitContext.status && gitContext.status.headSha; + if (typeof reviewedHeadSha !== "string" || !reviewedHeadSha) { + return failedApplyPreflight( + "Auto-fix was skipped because the reviewed HEAD snapshot is unavailable." + ); + } + if (status.headSha !== reviewedHeadSha) { + return failedApplyPreflight( + "Auto-fix was skipped because HEAD changed since review. Rerun `/workflow simplify --fix`." + ); + } + + return { success: true, status: "ready", expectedHeadSha: reviewedHeadSha }; +} + +function failedApplyPreflight(reason) { + return { success: false, status: "failed", skippedReason: reason, error: reason }; +} + +function stagedChangesSkipReason() { + return "Auto-fix was skipped because staged changes are present. Unstage or commit staged changes, then rerun `/workflow simplify --fix`."; +} + function collectGitContext(action, input, log) { const requestedRefs = gitRefs(input); const failures = []; From 3c4ec02d18ba9f6b20dd82912b476fc368d69592 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Mon, 15 Jun 2026 16:43:15 +0000 Subject: [PATCH 11/23] =?UTF-8?q?=F0=9F=A4=96=20fix:=20skip=20simplify=20f?= =?UTF-8?q?ixes=20for=20non-current=20heads?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .mux/workflows/simplify.js | 79 ++++++++++++++++++++++++++++++-------- 1 file changed, 63 insertions(+), 16 deletions(-) diff --git a/.mux/workflows/simplify.js b/.mux/workflows/simplify.js index 772be6380b..526a5d4aaf 100644 --- a/.mux/workflows/simplify.js +++ b/.mux/workflows/simplify.js @@ -235,18 +235,24 @@ export default function simplifyWorkflow({ // git am cannot apply onto a dirty index; keep the review result but skip auto-fix. if (hasStagedChanges(gitContext)) { - return { - reportMarkdown: - synthesis.reportMarkdown + - "\n\n---\n\n## Simplify workflow result\n\n" + - stagedChangesSkipReason(), - structuredOutput: { - mode: "staged-changes-skip-fix", - gitContext: contexts.outputGitContext, - reviews: reviewOutputs, - synthesis: synthesized, - }, - }; + return skipFixResult( + synthesis.reportMarkdown, + stagedChangesSkipReason(), + "staged-changes-skip-fix", + contexts.outputGitContext, + reviewOutputs, + synthesized + ); + } + if (!isRequestedHeadCurrent(gitContext.status)) { + return skipFixResult( + synthesis.reportMarkdown, + nonCurrentHeadSkipReason(), + "non-current-head-skip-fix", + contexts.outputGitContext, + reviewOutputs, + synthesized + ); } phase("fix", { actionableFindingCount: actionableFindings.length }); @@ -276,7 +282,7 @@ export default function simplifyWorkflow({ } phase("apply-fixes", { madeChanges: true }); - const applyPreflight = collectApplyPreflight(action, log, gitContext); + const applyPreflight = collectApplyPreflight(action, log, input, gitContext); if (applyPreflight.skippedReason) { return { reportMarkdown: @@ -318,12 +324,31 @@ export default function simplifyWorkflow({ }; } -function collectApplyPreflight(action, log, gitContext) { +function skipFixResult( + synthesisMarkdown, + reason, + mode, + outputGitContext, + reviewOutputs, + synthesized +) { + return { + reportMarkdown: synthesisMarkdown + "\n\n---\n\n## Simplify workflow result\n\n" + reason, + structuredOutput: { + mode: mode, + gitContext: outputGitContext, + reviews: reviewOutputs, + synthesis: synthesized, + }, + }; +} + +function collectApplyPreflight(action, log, input, gitContext) { let status = null; try { status = action.git.status({ id: "apply-git-status", - input: { includeIgnored: false }, + input: gitStatusInput(input), builtInOnly: true, cache: false, }).output; @@ -340,6 +365,10 @@ function collectApplyPreflight(action, log, gitContext) { return failedApplyPreflight(stagedChangesSkipReason()); } + if (!isRequestedHeadCurrent(status)) { + return failedApplyPreflight(nonCurrentHeadSkipReason()); + } + const reviewedHeadSha = gitContext.status && gitContext.status.headSha; if (typeof reviewedHeadSha !== "string" || !reviewedHeadSha) { return failedApplyPreflight( @@ -359,6 +388,10 @@ function failedApplyPreflight(reason) { return { success: false, status: "failed", skippedReason: reason, error: reason }; } +function nonCurrentHeadSkipReason() { + return "Auto-fix was skipped because the requested `--head` is not the current checkout. Check out that ref or rerun without `--head` before applying fixes."; +} + function stagedChangesSkipReason() { return "Auto-fix was skipped because staged changes are present. Unstage or commit staged changes, then rerun `/workflow simplify --fix`."; } @@ -369,7 +402,7 @@ function collectGitContext(action, input, log) { const status = gitSlice(log, failures, "status", function () { return action.git.status({ id: "git-status", - input: { includeIgnored: false }, + input: gitStatusInput(input), builtInOnly: true, }).output; }); @@ -416,6 +449,12 @@ function gitSlice(log, failures, name, read) { } } +function gitStatusInput(input) { + const statusInput = { includeIgnored: false }; + if (input.headRef) statusInput.head = input.headRef; + return statusInput; +} + function gitRefs(input) { const refs = {}; if (input.baseRef) refs.base = input.baseRef; @@ -446,6 +485,14 @@ function shouldReadDiffs(changedFiles) { ); } +function isRequestedHeadCurrent(status) { + if (!status || typeof status !== "object") return false; + const currentHeadSha = typeof status.headSha === "string" ? status.headSha : ""; + const requestedHeadSha = + typeof status.requestedHeadSha === "string" ? status.requestedHeadSha : currentHeadSha; + return Boolean(currentHeadSha && requestedHeadSha && currentHeadSha === requestedHeadSha); +} + function hasStagedChanges(gitContext) { return ( hasArrayItems(gitContext.status && gitContext.status.staged) || From fbce6b11f454a8c476e83c62d7d7ea90932b04af Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Mon, 15 Jun 2026 16:49:55 +0000 Subject: [PATCH 12/23] =?UTF-8?q?=F0=9F=A4=96=20fix:=20reject=20symbolic?= =?UTF-8?q?=20simplify=20heads?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .mux/workflows/simplify.js | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/.mux/workflows/simplify.js b/.mux/workflows/simplify.js index 526a5d4aaf..98d2baf167 100644 --- a/.mux/workflows/simplify.js +++ b/.mux/workflows/simplify.js @@ -244,7 +244,7 @@ export default function simplifyWorkflow({ synthesized ); } - if (!isRequestedHeadCurrent(gitContext.status)) { + if (!isRequestedHeadCurrent(gitContext.status, input)) { return skipFixResult( synthesis.reportMarkdown, nonCurrentHeadSkipReason(), @@ -365,7 +365,7 @@ function collectApplyPreflight(action, log, input, gitContext) { return failedApplyPreflight(stagedChangesSkipReason()); } - if (!isRequestedHeadCurrent(status)) { + if (!isRequestedHeadCurrent(status, input)) { return failedApplyPreflight(nonCurrentHeadSkipReason()); } @@ -389,7 +389,7 @@ function failedApplyPreflight(reason) { } function nonCurrentHeadSkipReason() { - return "Auto-fix was skipped because the requested `--head` is not the current checkout. Check out that ref or rerun without `--head` before applying fixes."; + return "Auto-fix was skipped because the requested `--head` is not the current checkout. Check out that branch/ref or pass the current commit SHA before applying fixes."; } function stagedChangesSkipReason() { @@ -485,12 +485,29 @@ function shouldReadDiffs(changedFiles) { ); } -function isRequestedHeadCurrent(status) { +function isRequestedHeadCurrent(status, input) { if (!status || typeof status !== "object") return false; const currentHeadSha = typeof status.headSha === "string" ? status.headSha : ""; const requestedHeadSha = typeof status.requestedHeadSha === "string" ? status.requestedHeadSha : currentHeadSha; - return Boolean(currentHeadSha && requestedHeadSha && currentHeadSha === requestedHeadSha); + if (!currentHeadSha || !requestedHeadSha || currentHeadSha !== requestedHeadSha) return false; + + const requestedHead = input && input.headRef ? input.headRef : ""; + if (!requestedHead || requestedHead === "HEAD") return true; + + const currentBranch = typeof status.branch === "string" ? status.branch : ""; + const currentBranchRef = currentBranch ? "refs/heads/" + currentBranch : ""; + const requestedHeadRef = + typeof status.requestedHeadRef === "string" ? status.requestedHeadRef : ""; + if (requestedHeadRef) return Boolean(currentBranchRef && requestedHeadRef === currentBranchRef); + if (currentBranch && (requestedHead === currentBranch || requestedHead === currentBranchRef)) { + return true; + } + return isGitCommitSha(requestedHead); +} + +function isGitCommitSha(value) { + return /^[0-9a-f]{7,40}$/i.test(value); } function hasStagedChanges(gitContext) { From 99f12ee0636617038c43a418e68fe947117f3c76 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Mon, 15 Jun 2026 16:54:06 +0000 Subject: [PATCH 13/23] =?UTF-8?q?=F0=9F=A4=96=20fix:=20require=20simplify?= =?UTF-8?q?=20head=20resolution?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .mux/workflows/simplify.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.mux/workflows/simplify.js b/.mux/workflows/simplify.js index 98d2baf167..4a93cf9ba4 100644 --- a/.mux/workflows/simplify.js +++ b/.mux/workflows/simplify.js @@ -488,11 +488,15 @@ function shouldReadDiffs(changedFiles) { function isRequestedHeadCurrent(status, input) { if (!status || typeof status !== "object") return false; const currentHeadSha = typeof status.headSha === "string" ? status.headSha : ""; + const requestedHead = input && input.headRef ? input.headRef : ""; const requestedHeadSha = - typeof status.requestedHeadSha === "string" ? status.requestedHeadSha : currentHeadSha; + typeof status.requestedHeadSha === "string" + ? status.requestedHeadSha + : requestedHead + ? "" + : currentHeadSha; if (!currentHeadSha || !requestedHeadSha || currentHeadSha !== requestedHeadSha) return false; - const requestedHead = input && input.headRef ? input.headRef : ""; if (!requestedHead || requestedHead === "HEAD") return true; const currentBranch = typeof status.branch === "string" ? status.branch : ""; From 4b04963da7d3050934ec088d2ecf6cd391adc3d6 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Mon, 15 Jun 2026 17:01:44 +0000 Subject: [PATCH 14/23] =?UTF-8?q?=F0=9F=A4=96=20fix:=20skip=20simplify=20f?= =?UTF-8?q?ixes=20for=20dirty=20worktrees?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .mux/workflows/simplify.js | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/.mux/workflows/simplify.js b/.mux/workflows/simplify.js index 4a93cf9ba4..b0f1ec8e2d 100644 --- a/.mux/workflows/simplify.js +++ b/.mux/workflows/simplify.js @@ -233,12 +233,12 @@ export default function simplifyWorkflow({ }; } - // git am cannot apply onto a dirty index; keep the review result but skip auto-fix. - if (hasStagedChanges(gitContext)) { + // Workflow child workspaces do not inherit parent dirt; keep the review result but skip auto-fix. + if (hasUncommittedChanges(gitContext)) { return skipFixResult( synthesis.reportMarkdown, - stagedChangesSkipReason(), - "staged-changes-skip-fix", + uncommittedChangesSkipReason(), + "uncommitted-changes-skip-fix", contexts.outputGitContext, reviewOutputs, synthesized @@ -307,8 +307,6 @@ export default function simplifyWorkflow({ target: "parent", threeWay: true, expectedHeadSha: applyPreflight.expectedHeadSha, - // simplify fixes the dirty changes it just reviewed, so patch onto that worktree. - force: true, onConflict: "return", }); @@ -361,8 +359,8 @@ function collectApplyPreflight(action, log, input, gitContext) { if (!status || typeof status !== "object") { return failedApplyPreflight("Auto-fix was skipped because fresh Git status was unavailable."); } - if (hasArrayItems(status.staged)) { - return failedApplyPreflight(stagedChangesSkipReason()); + if (hasUncommittedStatus(status)) { + return failedApplyPreflight(uncommittedChangesSkipReason()); } if (!isRequestedHeadCurrent(status, input)) { @@ -392,8 +390,8 @@ function nonCurrentHeadSkipReason() { return "Auto-fix was skipped because the requested `--head` is not the current checkout. Check out that branch/ref or pass the current commit SHA before applying fixes."; } -function stagedChangesSkipReason() { - return "Auto-fix was skipped because staged changes are present. Unstage or commit staged changes, then rerun `/workflow simplify --fix`."; +function uncommittedChangesSkipReason() { + return "Auto-fix was skipped because uncommitted changes are present. Commit or stash them, then rerun `/workflow simplify --fix`."; } function collectGitContext(action, input, log) { @@ -514,10 +512,20 @@ function isGitCommitSha(value) { return /^[0-9a-f]{7,40}$/i.test(value); } -function hasStagedChanges(gitContext) { +function hasUncommittedChanges(gitContext) { return ( - hasArrayItems(gitContext.status && gitContext.status.staged) || - hasArrayItems(gitContext.changedFiles && gitContext.changedFiles.staged) + hasUncommittedStatus(gitContext.status) || + hasArrayItems(gitContext.changedFiles && gitContext.changedFiles.staged) || + hasArrayItems(gitContext.changedFiles && gitContext.changedFiles.unstaged) || + hasArrayItems(gitContext.changedFiles && gitContext.changedFiles.untracked) + ); +} + +function hasUncommittedStatus(status) { + return ( + hasArrayItems(status && status.staged) || + hasArrayItems(status && status.unstaged) || + hasArrayItems(status && status.untracked) ); } From e358c93d898e8c4a650e88fa30c0ec01c3efe62e Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Mon, 15 Jun 2026 17:08:30 +0000 Subject: [PATCH 15/23] =?UTF-8?q?=F0=9F=A4=96=20fix:=20skip=20simplify=20r?= =?UTF-8?q?eview=20for=20untracked=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .mux/workflows/simplify.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.mux/workflows/simplify.js b/.mux/workflows/simplify.js index b0f1ec8e2d..f59f289e80 100644 --- a/.mux/workflows/simplify.js +++ b/.mux/workflows/simplify.js @@ -171,6 +171,19 @@ export default function simplifyWorkflow({ diffCompactions: asArray(contexts.outputGitContext.diff?.workflowCompactions).length, }); + if (hasUntrackedChanges(gitContext)) { + const reason = untrackedChangesSkipReason(); + return { + reportMarkdown: "## Simplify workflow result\n\n" + reason, + structuredOutput: { + mode: "untracked-changes-skip-review", + gitContext: contexts.outputGitContext, + reviews: [], + synthesis: { ...EMPTY_SYNTHESIS, summary: reason }, + }, + }; + } + if (!hasReviewableContext(input, gitContext)) { return { reportMarkdown: "## Simplify workflow result\n\n" + NO_REVIEWABLE_CHANGES_SUMMARY, @@ -390,6 +403,10 @@ function nonCurrentHeadSkipReason() { return "Auto-fix was skipped because the requested `--head` is not the current checkout. Check out that branch/ref or pass the current commit SHA before applying fixes."; } +function untrackedChangesSkipReason() { + return "Review was skipped because untracked file contents are not available to workflow child workspaces. Add them with `git add -N` or commit them, then rerun `/workflow simplify`."; +} + function uncommittedChangesSkipReason() { return "Auto-fix was skipped because uncommitted changes are present. Commit or stash them, then rerun `/workflow simplify --fix`."; } @@ -512,6 +529,13 @@ function isGitCommitSha(value) { return /^[0-9a-f]{7,40}$/i.test(value); } +function hasUntrackedChanges(gitContext) { + return ( + hasArrayItems(gitContext.status && gitContext.status.untracked) || + hasArrayItems(gitContext.changedFiles && gitContext.changedFiles.untracked) + ); +} + function hasUncommittedChanges(gitContext) { return ( hasUncommittedStatus(gitContext.status) || From 8e8e9b69d47be6e04c3e939a610b8d083d4ef98d Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Mon, 15 Jun 2026 17:15:37 +0000 Subject: [PATCH 16/23] =?UTF-8?q?=F0=9F=A4=96=20fix:=20scope=20simplify=20?= =?UTF-8?q?untracked=20skips?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .mux/workflows/simplify.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.mux/workflows/simplify.js b/.mux/workflows/simplify.js index f59f289e80..e6d846303a 100644 --- a/.mux/workflows/simplify.js +++ b/.mux/workflows/simplify.js @@ -171,7 +171,7 @@ export default function simplifyWorkflow({ diffCompactions: asArray(contexts.outputGitContext.diff?.workflowCompactions).length, }); - if (hasUntrackedChanges(gitContext)) { + if (hasOnlyUntrackedChanges(gitContext)) { const reason = untrackedChangesSkipReason(); return { reportMarkdown: "## Simplify workflow result\n\n" + reason, @@ -529,6 +529,18 @@ function isGitCommitSha(value) { return /^[0-9a-f]{7,40}$/i.test(value); } +function hasOnlyUntrackedChanges(gitContext) { + return ( + hasUntrackedChanges(gitContext) && + !hasArrayItems(gitContext.changedFiles && gitContext.changedFiles.branch) && + !hasArrayItems(gitContext.changedFiles && gitContext.changedFiles.staged) && + !hasArrayItems(gitContext.changedFiles && gitContext.changedFiles.unstaged) && + !hasText(gitContext.diff && gitContext.diff.branch) && + !hasText(gitContext.diff && gitContext.diff.staged) && + !hasText(gitContext.diff && gitContext.diff.unstaged) + ); +} + function hasUntrackedChanges(gitContext) { return ( hasArrayItems(gitContext.status && gitContext.status.untracked) || @@ -715,6 +727,7 @@ function reviewPrompt(lane, input, reviewContext) { "You are the " + lane.title + " lane. Review every changed file in the supplied Git context.", "If an explicit target is provided and the Git diff is empty, inspect that target path in the workspace before making claims.", "Diff text is capped by workflowBudgetChars; if workflowCompactions or built-in truncated flags are present, inspect files directly before making claims about omitted hunks.", + "Untracked paths in Git metadata are names only. Do not make findings about untracked files unless their contents are visible in a diff or explicit target.", "Allowed severity values are: high, medium, low. Return high-signal, actionable findings only; an empty findings array is fine.", "The synthesis step will keep at most " + input.maxFindings + From e48a26b656e90a652cbd21c07283059f774fb02a Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Mon, 15 Jun 2026 17:36:39 +0000 Subject: [PATCH 17/23] =?UTF-8?q?=F0=9F=A4=96=20fix:=20allow=20targeted=20?= =?UTF-8?q?simplify=20reviews?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .mux/workflows/simplify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.mux/workflows/simplify.js b/.mux/workflows/simplify.js index e6d846303a..3d54bb0e07 100644 --- a/.mux/workflows/simplify.js +++ b/.mux/workflows/simplify.js @@ -171,7 +171,7 @@ export default function simplifyWorkflow({ diffCompactions: asArray(contexts.outputGitContext.diff?.workflowCompactions).length, }); - if (hasOnlyUntrackedChanges(gitContext)) { + if (!input.target && hasOnlyUntrackedChanges(gitContext)) { const reason = untrackedChangesSkipReason(); return { reportMarkdown: "## Simplify workflow result\n\n" + reason, From 8b0bd836c109064718984fab297c73f85ce16243 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Mon, 15 Jun 2026 17:43:06 +0000 Subject: [PATCH 18/23] =?UTF-8?q?=F0=9F=A4=96=20fix:=20skip=20untracked=20?= =?UTF-8?q?simplify=20targets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .mux/workflows/simplify.js | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/.mux/workflows/simplify.js b/.mux/workflows/simplify.js index 3d54bb0e07..03d8bc7594 100644 --- a/.mux/workflows/simplify.js +++ b/.mux/workflows/simplify.js @@ -171,7 +171,7 @@ export default function simplifyWorkflow({ diffCompactions: asArray(contexts.outputGitContext.diff?.workflowCompactions).length, }); - if (!input.target && hasOnlyUntrackedChanges(gitContext)) { + if (hasOnlyUntrackedChanges(gitContext) && targetNeedsUntrackedContent(input, gitContext)) { const reason = untrackedChangesSkipReason(); return { reportMarkdown: "## Simplify workflow result\n\n" + reason, @@ -529,6 +529,33 @@ function isGitCommitSha(value) { return /^[0-9a-f]{7,40}$/i.test(value); } +function targetNeedsUntrackedContent(input, gitContext) { + if (!input.target) return true; + const target = normalizedPath(input.target); + if (!target) return false; + return untrackedPaths(gitContext).some(function (path) { + const untracked = normalizedPath(path); + return untracked === target || untracked.startsWith(target + "/"); + }); +} + +function untrackedPaths(gitContext) { + return asArray(gitContext.status && gitContext.status.untracked) + .concat(asArray(gitContext.changedFiles && gitContext.changedFiles.untracked)) + .map(filePath) + .filter(Boolean); +} + +function filePath(value) { + if (typeof value === "string") return value; + return value && typeof value.path === "string" ? value.path : ""; +} + +function normalizedPath(value) { + if (typeof value !== "string") return ""; + return value.trim().replace(/^\.\//, "").replace(/\/+$/, ""); +} + function hasOnlyUntrackedChanges(gitContext) { return ( hasUntrackedChanges(gitContext) && From c4a1bb4620b3a63ec1eff3e480a5b47a2c287a76 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Mon, 15 Jun 2026 17:47:03 +0000 Subject: [PATCH 19/23] =?UTF-8?q?=F0=9F=A4=96=20fix:=20guard=20mixed=20unt?= =?UTF-8?q?racked=20simplify=20targets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .mux/workflows/simplify.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.mux/workflows/simplify.js b/.mux/workflows/simplify.js index 03d8bc7594..28da2b13dc 100644 --- a/.mux/workflows/simplify.js +++ b/.mux/workflows/simplify.js @@ -171,7 +171,7 @@ export default function simplifyWorkflow({ diffCompactions: asArray(contexts.outputGitContext.diff?.workflowCompactions).length, }); - if (hasOnlyUntrackedChanges(gitContext) && targetNeedsUntrackedContent(input, gitContext)) { + if (shouldSkipForUntrackedContent(input, gitContext)) { const reason = untrackedChangesSkipReason(); return { reportMarkdown: "## Simplify workflow result\n\n" + reason, @@ -529,6 +529,13 @@ function isGitCommitSha(value) { return /^[0-9a-f]{7,40}$/i.test(value); } +function shouldSkipForUntrackedContent(input, gitContext) { + if (!hasUntrackedChanges(gitContext)) return false; + return input.target + ? targetNeedsUntrackedContent(input, gitContext) + : hasOnlyUntrackedChanges(gitContext); +} + function targetNeedsUntrackedContent(input, gitContext) { if (!input.target) return true; const target = normalizedPath(input.target); From b4690fd831e023a30943ab9d43e3594f27382bde Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Mon, 15 Jun 2026 17:52:42 +0000 Subject: [PATCH 20/23] =?UTF-8?q?=F0=9F=A4=96=20fix:=20treat=20root=20simp?= =?UTF-8?q?lify=20targets=20as=20untracked=20scope?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .mux/workflows/simplify.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.mux/workflows/simplify.js b/.mux/workflows/simplify.js index 28da2b13dc..401ea17e42 100644 --- a/.mux/workflows/simplify.js +++ b/.mux/workflows/simplify.js @@ -540,6 +540,7 @@ function targetNeedsUntrackedContent(input, gitContext) { if (!input.target) return true; const target = normalizedPath(input.target); if (!target) return false; + if (target === ".") return true; return untrackedPaths(gitContext).some(function (path) { const untracked = normalizedPath(path); return untracked === target || untracked.startsWith(target + "/"); @@ -560,7 +561,9 @@ function filePath(value) { function normalizedPath(value) { if (typeof value !== "string") return ""; - return value.trim().replace(/^\.\//, "").replace(/\/+$/, ""); + const trimmed = value.trim(); + if (trimmed === "." || trimmed === "./") return "."; + return trimmed.replace(/^\.\//, "").replace(/\/+$/, ""); } function hasOnlyUntrackedChanges(gitContext) { From 968ca51f94c9250840e9d2a88f3f039b4c794c3c Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Mon, 15 Jun 2026 17:59:17 +0000 Subject: [PATCH 21/23] =?UTF-8?q?=F0=9F=A4=96=20fix:=20harden=20simplify?= =?UTF-8?q?=20target=20freshness?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .mux/workflows/simplify.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.mux/workflows/simplify.js b/.mux/workflows/simplify.js index 401ea17e42..75c1816d06 100644 --- a/.mux/workflows/simplify.js +++ b/.mux/workflows/simplify.js @@ -380,6 +380,12 @@ function collectApplyPreflight(action, log, input, gitContext) { return failedApplyPreflight(nonCurrentHeadSkipReason()); } + if (!isReviewedBranchCurrent(gitContext.status, status, input)) { + return failedApplyPreflight( + "Auto-fix was skipped because the current branch changed since review. Rerun `/workflow simplify --fix`." + ); + } + const reviewedHeadSha = gitContext.status && gitContext.status.headSha; if (typeof reviewedHeadSha !== "string" || !reviewedHeadSha) { return failedApplyPreflight( @@ -500,6 +506,15 @@ function shouldReadDiffs(changedFiles) { ); } +function isReviewedBranchCurrent(reviewedStatus, currentStatus, input) { + if (input.headRef && isGitCommitSha(input.headRef)) return true; + const reviewedBranch = + reviewedStatus && typeof reviewedStatus.branch === "string" ? reviewedStatus.branch : ""; + const currentBranch = + currentStatus && typeof currentStatus.branch === "string" ? currentStatus.branch : ""; + return Boolean(reviewedBranch && currentBranch && reviewedBranch === currentBranch); +} + function isRequestedHeadCurrent(status, input) { if (!status || typeof status !== "object") return false; const currentHeadSha = typeof status.headSha === "string" ? status.headSha : ""; @@ -561,7 +576,7 @@ function filePath(value) { function normalizedPath(value) { if (typeof value !== "string") return ""; - const trimmed = value.trim(); + const trimmed = value.trim().replace(/\\/g, "/"); if (trimmed === "." || trimmed === "./") return "."; return trimmed.replace(/^\.\//, "").replace(/\/+$/, ""); } From 894db95788f533e8bbbf377dabe7bf90e4f40d3c Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Mon, 15 Jun 2026 18:06:48 +0000 Subject: [PATCH 22/23] =?UTF-8?q?=F0=9F=A4=96=20fix:=20preserve=20quoted?= =?UTF-8?q?=20simplify=20backslashes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .mux/workflows/simplify.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.mux/workflows/simplify.js b/.mux/workflows/simplify.js index 75c1816d06..cf53927130 100644 --- a/.mux/workflows/simplify.js +++ b/.mux/workflows/simplify.js @@ -914,7 +914,9 @@ function tokenize(input) { current += char; escaped = false; } else if (quote && char === "\\") { - escaped = true; + const next = input[index + 1]; + if (next === quote || next === "\\") escaped = true; + else current += char; } else if (quote) { if (char === quote) quote = ""; else current += char; From f04ea189fdea67ab9a813548aee0ec98771c1c43 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Mon, 15 Jun 2026 18:14:12 +0000 Subject: [PATCH 23/23] =?UTF-8?q?=F0=9F=A4=96=20fix:=20handle=20trailing?= =?UTF-8?q?=20simplify=20backslashes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .mux/workflows/simplify.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.mux/workflows/simplify.js b/.mux/workflows/simplify.js index cf53927130..d22b4e6b6c 100644 --- a/.mux/workflows/simplify.js +++ b/.mux/workflows/simplify.js @@ -541,7 +541,7 @@ function isRequestedHeadCurrent(status, input) { } function isGitCommitSha(value) { - return /^[0-9a-f]{7,40}$/i.test(value); + return /^[0-9a-f]{7,64}$/i.test(value); } function shouldSkipForUntrackedContent(input, gitContext) { @@ -915,8 +915,9 @@ function tokenize(input) { escaped = false; } else if (quote && char === "\\") { const next = input[index + 1]; - if (next === quote || next === "\\") escaped = true; - else current += char; + if (next === "\\" || (next === quote && !isClosingQuote(input, index + 1))) { + escaped = true; + } else current += char; } else if (quote) { if (char === quote) quote = ""; else current += char; @@ -933,6 +934,10 @@ function tokenize(input) { return { tokens: tokens, error: "" }; } +function isClosingQuote(input, quoteIndex) { + return quoteIndex + 1 >= input.length || /\s/.test(input[quoteIndex + 1]); +} + function reviewOnlyReport(input, markdown) { const mode = input.fix ? "No actionable fixes were selected."