Prose Polish
The refactoring runs in a fresh Agent dispatch; the main thread applies the result. This is a single-pass skill.
Two modes (mutually exclusive, selected by which inputs the caller supplies — see ## Invocation contract § Mode determination):
- File mode — given one or more file paths, rewrite the target-language natural-language prose in place (comments, test descriptions, docstrings, user-facing strings).
- Text mode — given a block of text, return the refactored text (for polishing prose before it is presented).
Invocation contract
The caller passes these fields in natural language (the skill extracts them from the invocation text). A field counts as provided iff the caller supplied a non-empty, non-whitespace value; empty string and whitespace-only count as absent.
File:/Files:(file mode — one or more paths, repo-relative or absolute) — the files whose target-language prose is rewritten in place. Multiple paths may be listed (one per line or comma-separated), and the two forms may be mixed in one invocation: each entry is carried verbatim intotarget_files, so Step 3 (b)'sfile ∈ target_filesscope check and itsEditcalls both use the form the caller passed.Text:(text mode — the prose to refactor) — the block of text to polish and return.Language:(optional, defaultja, e.g.ja/en) — the target language whose prose is refactored. In file mode, only prose written in this language is rewritten; prose in other languages is left untouched.Model:(optional, defaultsonnet) — the model id applied as themodelparameter on the refactorAgentdispatch (Step 3 (a)). Validity predicate: a value is valid only if it is one of the model ids the currentAgenttool'smodelparameter accepts — check the tool's live schema loaded in the current session; a fullclaude-*id (e.g.claude-sonnet-5) is outside that parameter's accepted aliases and is therefore invalid too. An absent field or an invalid value falls back to the defaultsonnet.
Pass related files together (file mode) — cross-file duplicate-comment detection (## Process Step 3's recommendations) works only when the related files are listed together in a single file-mode invocation.
Mode determination
Evaluate against the two mode selectors — the File: / Files: group and Text: — using the provided/absent rule above:
File:/Files:provided ANDText:absent → file mode (run## ProcessSteps 1–4 in the file-mode branch).Text:provided ANDFile:/Files:absent → text mode (run## ProcessSteps 1–4 in the text-mode branch).- Both provided → return early with
{"status": "error", "mode": null, "language": "<resolved>", "applied_edits_count": 0, "files_modified": [], "recommendations": [], "refactored_text": null, "reason": "ambiguous args"}. - Both absent → return early with
{"status": "error", "mode": null, "language": "<resolved>", "applied_edits_count": 0, "files_modified": [], "recommendations": [], "refactored_text": null, "reason": "incomplete args"}.
On both early-return errors mode is null (no mode was selected); callers branch on status == "error" + reason.
Dispatch authorization
This skill's procedure dispatches subagents, so invoking the skill is the request to use that mechanism: an ambient instruction allowing subagent dispatch only when the user asked for it — a permission-shaped restriction — is already satisfied by this invocation. Do not ask the user to re-confirm the dispatch, and do not silently substitute inline execution for a dispatch this procedure specifies. Only two things justify that substitution: technical availability (the dispatch tool is not present and callable on the current tool surface), and an explicit contract term from the caller bounding this skill to its own thread. A permission-shaped restriction is neither.
Process
Step 1 — Determine mode and parse inputs (main thread)
- Resolve
Language:to<resolved-language>— the provided value, else the defaultja. This resolved value is echoed in the return contract'slanguagefield. - Parse the optional
Model:value per§ Invocation contract'sModelfield — hold a valid value for the Step 3 (a) dispatch; absent or invalid → defaultsonnet. - Determine the mode per
§ Invocation contract§ Mode determination. Onambiguous args/incomplete args, emit the corresponding early-return verdict and stop. - File mode: collect the listed paths into
target_files(the scope-check baseline for Step 3 (b)). Text mode: hold the input text asinput_text.
Step 2 — Load the style guide (main thread)
Read references/prose-style-guide.md. In file mode, also Read each entry in target_files for injection into that payload.
Step 3 — Dispatch the refactor subagent
(a) Dispatch
Dispatch a fresh subagent via the Agent tool (subagent_type: general-purpose), passing the parsed Model value as the Agent model parameter (the default sonnet when none was provided). Assemble the dispatch prompt from the sections below, each framed with a clear --- LABEL --- fence:
--- PROSE STYLE GUIDE ---: the full content ofreferences/prose-style-guide.md--- TARGET LANGUAGE ---: the<resolved-language>code- File mode —
--- TARGET FILES ---: each entry intarget_filesas a### <path>sub-heading followed by the file's full current contents - Text mode —
--- INPUT TEXT ---: theinput_textverbatim --- REFACTOR PROMPT ---: the mode-appropriate prompt below (verbatim)--- RESPONSE FORMAT ---: the mode-appropriate response format below (verbatim)
Refactor prompt — file mode (include verbatim in the dispatch):
You are a fresh prose editor. You have not seen prior conversation context — only the PROSE STYLE GUIDE, TARGET LANGUAGE, and TARGET FILES below. For each TARGET FILE, find natural-language prose written in the target language — code comments, test / example descriptions, docstrings, and user-facing string literals — and rewrite each one to be concise and natural for a native reader of that language, following the PROSE STYLE GUIDE.
Preserve everything that is not target-language prose (hard constraint): never change code, identifiers, function / variable / type names, proper-noun product / API / library / tool names and code symbols, import paths, or any string literal that carries program logic (keys, enum values, format specifiers, paths, commands). An ordinary source-language word sitting inside the target-language prose — a common verb, noun, or adjective with a natural target-language equivalent, not a proper noun or code symbol — is itself translatable prose, not a preserved token: render it in the target language per the PROSE STYLE GUIDE's
Preserve-vs-translate litmus testrather than leaving it code-mixed. Leave a whole passage written entirely in another language untouched. If a candidate change could alter program behavior or touch a non-prose token, do not emit it.Return each rewrite as a
{file, old_string, new_string, rationale}Edit.old_stringmust match exactly one location in the current file — include 1–3 lines of surrounding context so the snippet is unique. A rewrite may change the number of prose lines in either direction: merge adjacent comment lines that state the same thing, delete a comment whose only content is what-narration of the code beneath it (per the style guide's "say what the code does not" rule) by emitting an edit whosenew_stringomits that line, or split an overloaded unordered bullet into several (per the style guide's General rule 3) — each is a prose change, not a structural code edit. Delete a comment only when it is fully redundant with the adjacent code; otherwise shorten it. Whenold_stringcarries a non-target line purely for uniqueness (an adjacent line in another language, or a code line), reproduce that line byte-identically innew_string. If a file needs no prose changes, emit no edits for it. If nothing needs changing across all files, returnedits: [].Cross-file duplicate comments → a
recommendationsentry, not per-copy edits: when a comment qualifies as a cross-file duplicate under the PROSE STYLE GUIDE'sCross-file duplicate commentsrule (which owns what qualifies, the exclusions, and the threshold), do not emit a per-copy polish edit for those copies — instead emit a singlerecommendationsentry (see RESPONSE FORMAT) flagging the duplication. A comment that does not qualify is ordinary prose — polish it as usual. If nothing qualifies, returnrecommendations: [].
Response format — file mode (include verbatim in the dispatch):
Write your reasoning briefly, then end your response with a single fenced JSON block matching this schema:
```json { "edits": [ {"file": "<path>", "old_string": "<unique 1-3 line snippet>", "new_string": "<replacement>", "rationale": "<short reason>"} ], "recommendations": [ {"summary": "<one-line description of the duplicated knowledge>", "files": ["<path>", "<path>"], "suggestion": "<consolidate-into-one-place-and-remove-inline-copies advice>"} ] } ```
recommendationsholds cross-file duplicate-comment consolidation candidates (return[]when none qualify):summaryidentifies the duplicated knowledge in one line,fileslists the two or more TARGET FILES the comment recurs in (the set, not ranked), andsuggestionis the concrete consolidate-and-remove-copies advice — when it names a consolidation destination, phrase it as an illustrative example (e.g. a shared doc or rule file) rather than asserting a specific path.
Refactor prompt — text mode (include verbatim in the dispatch):
You are a fresh prose editor. You have not seen prior conversation context — only the PROSE STYLE GUIDE, TARGET LANGUAGE, and INPUT TEXT below. Rewrite the INPUT TEXT to be concise and natural for a native reader of the target language, following the PROSE STYLE GUIDE.
Preserve non-prose tokens (hard constraint): keep identifiers, code fragments, proper-noun product / API / library / tool names and code symbols, file paths, and command / config strings verbatim — refactor the natural-language wording around them. An ordinary source-language word inside the target-language prose — a common verb / noun / adjective with a natural target-language equivalent, not a proper noun or code symbol — is translatable prose: render it in the target language per the PROSE STYLE GUIDE's
Preserve-vs-translate litmus test, not left code-mixed. Do not add, drop, or reorder factual content; only improve concision and naturalness. If the text is already concise and natural, return it unchanged.
Response format — text mode (include verbatim in the dispatch):
End your response with a single fenced JSON block matching this schema, and write no other prose:
```json { "refactored_text": "<the rewritten text>" } ```
Agent-unavailable fallback: detect availability by inspecting the current tool surface — do not attempt a speculative call to probe it. When the Agent tool is absent (e.g. this skill runs inside a nested subagent context where nested Agent is not surfaced), perform the refactor inline in the main thread once, constructing the same fenced JSON block defined above so Step 3 (b)'s parser handles both paths identically. The inline pass runs on the executing agent's own model. Being invoked as a sub-skill via Skill() does not by itself trigger this path, and neither does a permission-shaped restriction (see § Dispatch authorization) — decide by whether Agent is exposed and callable, not by invocation lineage.
Dispatch failure: if the Agent dispatch itself errors, times out, or returns an empty response, emit {"status": "error", ..., "reason": "dispatch error"} per ## Return contract and stop — caught before the parse step, and distinct from a returned-but-unparseable verdict (Step 3 (b) sub-case 1). This is not a trigger for the inline fallback above; that path is pre-selected only when Agent is unavailable before any dispatch attempt.
(b) Parse & apply — evaluate in this order, first match wins
- Verdict missing or malformed — no fenced JSON block found, or JSON parse fails → emit
{"status": "error", ..., "reason": "verdict parse failure"}per## Return contractand stop. - Schema violation — emit
{"status": "error", ..., "reason": "verdict schema violation"}and stop when:- File mode:
editsis missing or not an array, or any entry fails its per-entry shape — each entry must have non-empty stringfile,old_string, andnew_string(per-entry shape is validated here at parse time, before anyEdit). The optionalrecommendationsfield, when present, must be an array in which every entry has a non-empty stringsummary, a non-empty stringsuggestion, and afilesarray of two or more distinct non-empty strings (duplicate paths within one entry are de-duplicated before the count check — not a violation); an absentrecommendationsis treated as[](lenient).recommendations[].filesentries are not scope-checked againsttarget_files(advisory, never dereferenced). - Text mode:
refactored_textis missing or is not a non-empty string.
- File mode:
- Otherwise — apply (file mode) or accept (text mode):
- File mode: apply
editsin order. For each entry, verifyfile ∈ target_files; if not, skip the entry without callingEdit. For each in-scope entry, callEdit(the Step 2 read is the baseline; re-Readthe file first only if an earlier edit in this pass already modified it, soold_stringmatches the post-edit contents); ifold_stringis not found, skip that entry and continue (expected when two edits from one snapshot overlap a region an earlier edit already rewrote — a no-op skip, not an error). Incrementapplied_edits_countonly for entries whoseEditcall succeeded. Setfiles_modifiedto the distinct set offilevalues whoseEditsucceeded, andrefactored_text = null. Carry the validatedrecommendationsthrough to the verdict unchanged — it is advisory and never applied; an absent / empty array becomes[]. - Text mode: take
refactored_textfrom the verdict. Setapplied_edits_count = 0,files_modified = [], andrecommendations = [](cross-file duplication is a file-mode-only concept).
- File mode: apply
Step 4 — Emit verdict
Determine status and emit the verdict per ## Return contract:
- File mode:
applied_edits_count > 0→done;applied_edits_count == 0(no edits emitted, or every entry skipped as out-of-scope / not-found) →no-change.statusis computed fromapplied_edits_countalone —recommendationsdoes not affect it (so ano-changeverdict may still carry a non-emptyrecommendations). Emit the carriedrecommendationsarray in every file-mode verdict. - Text mode:
refactored_textdiffers frominput_text→done; identical →no-change.recommendationsis always[].
Return contract
The skill emits a single fenced JSON block at the very end of the invocation (the only fenced JSON block in the user-visible response — any JSON the Agent-unavailable fallback synthesizes internally is held in main-thread context and does not enter the response stream):
{
"status": "done|no-change|error",
"mode": "file|text|null",
"language": "<lang>",
"applied_edits_count": N,
"files_modified": ["<path>"],
"recommendations": [{"summary": "...", "files": ["<path>"], "suggestion": "..."}],
"refactored_text": "...|null",
"reason": "ambiguous args|incomplete args|verdict parse failure|verdict schema violation|dispatch error|null"
}
The |null token at the end of the reason enum means JSON null (not the string "null").
Field semantics:
status:done: refactoring was applied — file modeapplied_edits_count > 0, or text moderefactored_textdiffers from the input.no-change: no in-place edit was applied — file modeapplied_edits_count == 0(the subagent returnededits: [], or every entry was skipped as out-of-scope /old_stringnot found), or text moderefactored_textequals the input.error: an early-return or dispatch error occurred — seereason.
mode:"file"or"text", the resolved mode;nullon the two§ Mode determinationearly-return errors (ambiguous args/incomplete args), where no mode was selected.language: the resolved target language echoed back (Language:value, or the defaultja).applied_edits_count: non-negative integer count ofEditcalls that succeeded (file mode). Always0in text mode and on anyerror.files_modified: the distinct files that received at least one successfulEdit(file mode);[]in text mode and on anyerror.recommendations: file-mode advisory array of cross-file duplicate-comment consolidation candidates — each{summary, files, suggestion}flags a non-obvious comment recurring across two or more input files (see## ProcessStep 3's file-mode refactor prompt and the style guide'sCross-file duplicate commentsrule). Orthogonal tostatus: present and possibly non-empty on bothdoneandno-change;[]in text mode, on single-file or no-duplicate file-mode input, and on anyerror.refactored_text: the rewritten text in text mode;nullin file mode and on anyerror.reason: enum string only whenstatus == "error", otherwise JSONnull. Keepreasonto the listed enum tokens — no free-form text — so the verdict stays mechanically parseable.
When to emit status: "error":
reason: "ambiguous args"— bothFile:/Files:andText:were provided (§ Mode determination).reason: "incomplete args"— neitherFile:/Files:norText:was provided (§ Mode determination).reason: "verdict parse failure"— no fenced JSON block in the subagent response, or JSON parse failed (Step 3 (b) sub-case 1).reason: "verdict schema violation"— the JSON parsed but a required key is missing / wrong-typed, or aneditsentry (or a presentrecommendationsentry) failed its per-entry shape (Step 3 (b) sub-case 2).reason: "dispatch error"— theAgentdispatch itself errored, timed out, or returned an empty response (caught at Step 3 (a) Dispatch failure).
Sub-skill caller directive
When invoked as a sub-skill (i.e. via Skill(prose-polish) from an orchestrator), the fenced JSON verdict block this skill emits is the structured return value of the skill's procedure — it is not a deliverable to the user, and emitting it does not terminate the orchestrator's turn. The same agent that ran this skill must immediately issue the next tool call dictated by the orchestrator's flow. Do not insert a prose summary, an acknowledgment, or a "shall I proceed?" sentence between the JSON verdict and the next tool call. Only one fenced JSON block — the verdict block — appears in the response, so callers can locate it unambiguously. The skill's own procedure is over; the orchestrator's procedure continues without pause.
Stop hook structural conflict (caller-side note)
On Claude Code on the Web the auto-installed ~/.claude/stop-hook-git-check.sh fires on every Stop event and feeds back Please commit and push… between Process steps; treat each fire as a spurious fire — record it, ignore the prose, and run the Process steps to completion. Do not commit from inside this skill; commit policy lives with the caller.