feat: make fix-title lightweight by default
This commit is contained in:
parent
19c84ffc8c
commit
15ce3c0834
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
name: fix-title
|
||||
description: "Use when copied ChatGPT/LLM Markdown has semantically wrong heading hierarchy, especially discussion records with round headings and GPT reply blocks, or intake/artifact drafts with repeated accidental top-level headings. Agentic Markdown heading repair for one or more files: accept a file path array plus mode discussion or artifact, prefer subagent/thread isolation for large repairs, preserve originals, and produce fixed copies, heading plans, and reports."
|
||||
description: "Use when copied ChatGPT/LLM Markdown has semantically wrong heading hierarchy, especially discussion records with round headings and GPT reply blocks, or intake/artifact drafts with repeated accidental top-level headings. Agentic Markdown heading repair for one or more files: default to lightweight in-place repair that overwrites the source Markdown without sidecar files; use audit mode only when the caller asks for preserved originals, fixed copies, heading maps, JSON plans, or reports."
|
||||
---
|
||||
|
||||
# Fix Title
|
||||
|
|
@ -16,37 +16,113 @@ Required inputs:
|
|||
|
||||
Optional inputs:
|
||||
|
||||
- `output_dir`: output folder. If omitted, use repo-local `tmp/fix-title-output` when available; otherwise use `fix-title-output` beside the first source file.
|
||||
- `execution`: `auto`, `subagent`, `thread`, or `inline`. Default to `auto`.
|
||||
- `output`: `quick` or `audit`. Default to `quick`.
|
||||
- `execution`: `auto`, `subagent`, `thread`, or `inline`. Default to `inline` for quick single-file work and `auto` for audit or large batches.
|
||||
- `output_dir`: audit output folder. Ignore in quick mode unless the caller also asks for backup or reports.
|
||||
- `backup`: `never`, `always`, or `auto`. Default to `never`; rely on git unless the caller asks for backups or the file is outside version control.
|
||||
|
||||
Example user-facing request:
|
||||
|
||||
```text
|
||||
Use $fix-title with mode=artifact on:
|
||||
Use $fix-title with mode=artifact on C:\path\one.md
|
||||
```
|
||||
|
||||
This defaults to quick mode and overwrites `C:\path\one.md` directly.
|
||||
|
||||
Audit example:
|
||||
|
||||
```text
|
||||
Use $fix-title with mode=artifact and output=audit on:
|
||||
- C:\path\one.md
|
||||
- C:\path\two.md
|
||||
Output to C:\path\tmp\fix-title-output
|
||||
```
|
||||
|
||||
## Output Modes
|
||||
|
||||
### Quick
|
||||
|
||||
Use quick mode by default.
|
||||
|
||||
- Overwrite each source Markdown file in place.
|
||||
- Do not write `heading-map.json`, `heading-plan.json`, `.fixed.md`, heading reports, or batch reports.
|
||||
- Use in-memory edits and `scripts/semantic_heading_repair.py` helpers when practical.
|
||||
- Return a concise summary with changed heading counts and skipped files.
|
||||
- If a file is ambiguous, skip it and tell the caller why instead of creating audit artifacts by default.
|
||||
|
||||
Quick mode is for routine cleanup where the caller wants the document fixed, not a review package.
|
||||
|
||||
### Audit
|
||||
|
||||
Use audit mode only when the caller asks for auditability, preserved originals, fixed copies, maps, plans, reports, review evidence, batch reports, or no overwrite.
|
||||
|
||||
For each source file, write:
|
||||
|
||||
```text
|
||||
<stem>.heading-map.json
|
||||
<stem>.heading-plan.json
|
||||
<stem>.fixed.md
|
||||
<stem>.heading-report.md
|
||||
```
|
||||
|
||||
For batch runs, also write:
|
||||
|
||||
```text
|
||||
fix-title-batch-report.md
|
||||
```
|
||||
|
||||
The plan JSON is the audit boundary. Every changed heading must include:
|
||||
|
||||
```json
|
||||
{
|
||||
"line": 25,
|
||||
"from_level": 1,
|
||||
"to_level": 3,
|
||||
"reason": "This is a top-level section inside the GPT reply block under ## GPT."
|
||||
}
|
||||
```
|
||||
|
||||
The caller must verify returned files and reports in audit mode. Do not treat a subagent/thread message alone as proof of repair.
|
||||
|
||||
## Execution Isolation
|
||||
|
||||
Prefer isolation because semantic repair consumes context and does not depend on the caller's broader conversation.
|
||||
Quick single-file repairs should usually run inline. Prefer isolation only when semantic repair consumes significant context or the caller asks for it.
|
||||
|
||||
- `subagent`: Use when the host supports delegated agents. Pass only the skill path, input files, mode, output directory, and output contract.
|
||||
- `thread`: Use when the host supports child/background threads and the user explicitly asks for Thread/sub-session isolation or the batch is large enough to risk bloating the main context.
|
||||
- `inline`: Use only when no subagent/thread mechanism is available or the file is small.
|
||||
- `auto`: Choose `thread` when explicitly requested, otherwise choose `subagent` when available, otherwise `inline`.
|
||||
|
||||
The caller must verify returned files and reports. Do not treat a subagent/thread message alone as proof of repair.
|
||||
- `inline`: Use for quick mode and small files.
|
||||
- `subagent`: Use when the host supports delegated agents and the batch is large or audit mode is requested.
|
||||
- `thread`: Use when the host supports child/background threads and the user explicitly asks for Thread/sub-session execution.
|
||||
- `auto`: Choose `thread` when explicitly requested, otherwise choose `subagent` for audit or large batches when available, otherwise `inline`.
|
||||
|
||||
## Delegation Prompt
|
||||
|
||||
Send this shape to the isolated worker:
|
||||
For quick mode, send this shape to the isolated worker only when isolation is warranted:
|
||||
|
||||
```text
|
||||
Use $fix-title at <skill-path> to repair Markdown heading hierarchy.
|
||||
|
||||
Mode: <discussion|artifact>
|
||||
Output: quick
|
||||
Files:
|
||||
- <absolute-path-1>
|
||||
- <absolute-path-2>
|
||||
|
||||
Rules:
|
||||
- Read and write source files as UTF-8.
|
||||
- Overwrite each source Markdown file in place.
|
||||
- Do not write heading-map, heading-plan, fixed-copy, heading-report, or batch-report files.
|
||||
- Preserve non-heading content.
|
||||
- Apply only deliberate heading-level edits.
|
||||
- Flag ambiguous appended artifacts or uncertain heading relationships instead of silently making creative decisions.
|
||||
- Return changed heading counts and skipped files.
|
||||
```
|
||||
|
||||
For audit mode, send this shape:
|
||||
|
||||
```text
|
||||
Use $fix-title at <skill-path> to repair Markdown heading hierarchy.
|
||||
|
||||
Mode: <discussion|artifact>
|
||||
Output: audit
|
||||
Output directory: <output-dir>
|
||||
Files:
|
||||
- <absolute-path-1>
|
||||
|
|
@ -87,34 +163,6 @@ Use when copied GPT output is stored as a standalone planning/intake/artifact do
|
|||
|
||||
Example: after a root title, raw sibling headings `# 1.xxxx`, `# 2.xxx`, and `## 3.xxx` may all become `##` when they are peer sections.
|
||||
|
||||
## Output Contract
|
||||
|
||||
For each source file, write:
|
||||
|
||||
```text
|
||||
<stem>.heading-map.json
|
||||
<stem>.heading-plan.json
|
||||
<stem>.fixed.md
|
||||
<stem>.heading-report.md
|
||||
```
|
||||
|
||||
For batch runs, also write:
|
||||
|
||||
```text
|
||||
fix-title-batch-report.md
|
||||
```
|
||||
|
||||
The plan JSON is the audit boundary. Every changed heading must include:
|
||||
|
||||
```json
|
||||
{
|
||||
"line": 25,
|
||||
"from_level": 1,
|
||||
"to_level": 3,
|
||||
"reason": "This is a top-level section inside the GPT reply block under ## GPT."
|
||||
}
|
||||
```
|
||||
|
||||
## Internal Helper
|
||||
|
||||
Use `scripts/semantic_heading_repair.py` only as an internal importable helper. It is not a user-facing CLI.
|
||||
|
|
@ -124,13 +172,16 @@ Useful helper API:
|
|||
- `inspect_payload(path)`
|
||||
- `write_inspection(source, output, output_format="json")`
|
||||
- `prepare_batch_scaffold(files, mode, output_dir)`
|
||||
- `apply_edits(source, edits, output, report=None, mode="quick")`
|
||||
- `apply_edits_in_place(source, edits, mode="quick", report=None)`
|
||||
- `apply_plan(source, plan, output, report)`
|
||||
|
||||
The helper protects fenced code blocks, quoted headings, and indented code. It does not make semantic decisions.
|
||||
|
||||
## Repair Rules
|
||||
|
||||
- Never overwrite the source file unless the user explicitly asks for replacement.
|
||||
- Quick mode overwrites the source file by default.
|
||||
- Audit mode never overwrites the source file unless the caller explicitly asks for replacement after reviewing the fixed copy.
|
||||
- Do not mechanically add or subtract the same number of heading levels across a whole file.
|
||||
- Do not change non-heading content.
|
||||
- Do not change headings inside fenced code blocks, block quotes, or indented code.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
display_name: "Fix Title"
|
||||
short_description: "Agentic repair for Markdown heading trees."
|
||||
default_prompt: "Use $fix-title with files=[...] and mode=artifact or mode=discussion to repair Markdown heading hierarchy without overwriting sources."
|
||||
short_description: "Lightweight repair for Markdown heading trees."
|
||||
default_prompt: "Use $fix-title with files=[...] and mode=artifact or mode=discussion to repair Markdown heading hierarchy in place. Ask for output=audit when fixed copies, heading maps, JSON plans, or reports are needed."
|
||||
|
|
|
|||
|
|
@ -7,9 +7,11 @@ Recommended files:
|
|||
```text
|
||||
raw-discussion.md
|
||||
raw-artifact.md
|
||||
heading-plan.json
|
||||
audit-heading-plan.json
|
||||
```
|
||||
|
||||
The raw files should preserve original copied LLM/GPT Markdown before heading repair.
|
||||
The raw files should contain disposable copies of original LLM/GPT Markdown before heading repair.
|
||||
|
||||
Invoke the Skill through an agent prompt with `files=[...]`, `mode=discussion|artifact`, and an explicit `output_dir`. Do not overwrite the source during manual validation.
|
||||
Default manual validation should invoke the Skill with `files=[...]` and `mode=discussion|artifact`, then confirm the source copy was repaired in place without sidecar files.
|
||||
|
||||
Use `output=audit` and an explicit `output_dir` only when validating fixed-copy, heading-map, heading-plan, and report generation.
|
||||
|
|
|
|||
|
|
@ -274,14 +274,10 @@ def parse_plan(plan_path: pathlib.Path) -> tuple[str, list[Edit]]:
|
|||
return mode, edits
|
||||
|
||||
|
||||
def apply_plan(
|
||||
source: pathlib.Path,
|
||||
plan: pathlib.Path,
|
||||
output: pathlib.Path,
|
||||
report: pathlib.Path | None,
|
||||
) -> int:
|
||||
lines = source.read_text(encoding="utf-8").splitlines(keepends=True)
|
||||
mode, edits = parse_plan(plan)
|
||||
def _apply_edits_to_lines(
|
||||
lines: list[str],
|
||||
edits: list[Edit],
|
||||
) -> list[dict[str, Any]]:
|
||||
headings_by_line = {heading.line: heading for heading in iter_headings(lines)}
|
||||
changed: list[dict[str, Any]] = []
|
||||
|
||||
|
|
@ -312,23 +308,63 @@ def apply_plan(
|
|||
}
|
||||
)
|
||||
|
||||
return changed
|
||||
|
||||
|
||||
def apply_edits(
|
||||
source: pathlib.Path,
|
||||
edits: list[Edit],
|
||||
output: pathlib.Path,
|
||||
report: pathlib.Path | None = None,
|
||||
mode: str = "quick",
|
||||
plan_label: str = "in-memory edits",
|
||||
) -> int:
|
||||
lines = source.read_text(encoding="utf-8").splitlines(keepends=True)
|
||||
changed = _apply_edits_to_lines(lines, edits)
|
||||
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
output.write_text("".join(lines), encoding="utf-8")
|
||||
|
||||
if report is not None:
|
||||
report.parent.mkdir(parents=True, exist_ok=True)
|
||||
report.write_text(
|
||||
format_report(source, output, plan, mode, changed),
|
||||
format_report(source, output, plan_label, mode, changed),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
return len(changed)
|
||||
|
||||
|
||||
def apply_edits_in_place(
|
||||
source: pathlib.Path,
|
||||
edits: list[Edit],
|
||||
mode: str = "quick",
|
||||
report: pathlib.Path | None = None,
|
||||
) -> int:
|
||||
return apply_edits(
|
||||
source=source,
|
||||
edits=edits,
|
||||
output=source,
|
||||
report=report,
|
||||
mode=mode,
|
||||
plan_label="in-place edits",
|
||||
)
|
||||
|
||||
|
||||
def apply_plan(
|
||||
source: pathlib.Path,
|
||||
plan: pathlib.Path,
|
||||
output: pathlib.Path,
|
||||
report: pathlib.Path | None,
|
||||
) -> int:
|
||||
mode, edits = parse_plan(plan)
|
||||
return apply_edits(source, edits, output, report, mode, str(plan))
|
||||
|
||||
|
||||
def format_report(
|
||||
source: pathlib.Path,
|
||||
output: pathlib.Path,
|
||||
plan: pathlib.Path,
|
||||
plan: pathlib.Path | str,
|
||||
mode: str,
|
||||
changed: list[dict[str, Any]],
|
||||
) -> str:
|
||||
|
|
|
|||
|
|
@ -159,6 +159,35 @@ class SemanticHeadingRepairTest(unittest.TestCase):
|
|||
with self.assertRaisesRegex(ValueError, "expected level 1"):
|
||||
repair.apply_plan(source, plan, pathlib.Path(tmp) / "fixed.md", None)
|
||||
|
||||
def test_apply_edits_in_place_overwrites_source_without_sidecars(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = pathlib.Path(tmp)
|
||||
source = root / "source.md"
|
||||
source.write_text("# Title\n\n# Wrong peer\n", encoding="utf-8")
|
||||
|
||||
changed = repair.apply_edits_in_place(
|
||||
source,
|
||||
[
|
||||
repair.Edit(
|
||||
line=3,
|
||||
from_level=1,
|
||||
to_level=2,
|
||||
reason="Artifact sections after the title are children.",
|
||||
)
|
||||
],
|
||||
mode="artifact",
|
||||
)
|
||||
|
||||
self.assertEqual(changed, 1)
|
||||
self.assertEqual(
|
||||
source.read_text(encoding="utf-8"),
|
||||
"# Title\n\n## Wrong peer\n",
|
||||
)
|
||||
self.assertEqual(
|
||||
sorted(path.name for path in root.iterdir()),
|
||||
["source.md"],
|
||||
)
|
||||
|
||||
def test_prepare_batch_scaffold_accepts_file_array(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = pathlib.Path(tmp)
|
||||
|
|
|
|||
Loading…
Reference in New Issue