rpa-bot-handoff: end with HANDOVER.docx for the ops team

New last step: templates/to_docx.py turns the finished HANDOVER.md into a
Word document (python-docx), the skill checks it and gives that path to
the user as the deliverable. --selfcheck converts the Tebra example and
is run by tests/test_templates.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
VasanthandClaude Opus 5.5 committed 2026-10-28 15:01:42 +05:30
1 parent 3942fad6cd
commit f4c42f2956
4 files changed
+135 -2

No files matched your search

+4
View File
@@ -16,6 +16,8 @@ A Claude skill that writes the **ops guide** for an RPA bot that is being handed
10. Don'ts, and the right way to stop the bot
11. Issues seen before
**`HANDOVER.docx`**: the same ops guide as a Word document. This is what you give the ops team at the end.
**`HANDOVER-DEV.md`**: for developers. The check against 12 failure classes learned from Tebra Auto Post, the gaps to fix before handover (file:line), and the incident list.
## Install
@@ -26,6 +28,7 @@ In your bot project, ask Claude: *"write the ops guide for this bot"* or *"docum
1. Scans the code and history with subagents. It never runs the bot.
2. Asks up to three things: how ops reaches the dev team, what ops can open, and any unclear statuses.
3. Writes both files and checks them.
4. Turns the ops guide into `HANDOVER.docx` and gives you that document to hand to ops.
## Files
| File | What it is |
@@ -35,3 +38,4 @@ In your bot project, ask Claude: *"write the ops guide for this bot"* or *"docum
| `templates/HANDOVER-DEV.md` | The dev notes skeleton |
| `templates/failure-classes.md` | 12 failure classes from Tebra's first months with ops |
| `templates/reference/tebra-auto-post.md` | Both files filled in for Tebra, scrubbed |
| `templates/to_docx.py` | Turns `HANDOVER.md` into `HANDOVER.docx` (`--selfcheck` tests it) |
+10 -2
View File
@@ -15,6 +15,7 @@ Write two files in the bot's repo:
| `templates/HANDOVER-DEV.md` | The dev notes skeleton. |
| `templates/failure-classes.md` | 12 failure classes from Tebra Auto Post. Every one must be checked against the bot. |
| `templates/reference/tebra-auto-post.md` | A filled example of both files for Tebra, scrubbed. Copy its depth and wording, not its content. |
| `templates/to_docx.py` | Turns the finished `HANDOVER.md` into `HANDOVER.docx`, the document handed to ops. Needs `python-docx`. |
Rules:
- **Never run the bot.** Real bots act on live systems. Read code, config, logs and history only.
@@ -22,7 +23,7 @@ Rules:
- **Every wait is a number.** Take timeouts, retries and watchdog limits from the code, in seconds or minutes. Take normal durations from the logs when they show them; otherwise write "not measured".
- **Write what the code does, not what it should do.** If a status is misleading (an item needing follow-up shows green), say so plainly in the ops guide and add it to the gaps in `HANDOVER-DEV.md`.
- **Never copy** patient data (PHI), customer names, credentials, internal IPs, keys or `.env` values into either file. Logs are read for message types and durations only.
- Change no code. The only output is the two files.
- Change no code. The only output is the two files and `HANDOVER.docx`, the Word copy of the ops guide that is handed to ops at the end.
## 1. Scan (subagents are required)
@@ -86,4 +87,11 @@ Copy `templates/HANDOVER-DEV.md` and fill it:
4. Search both files for PHI, customer names, credentials, IPs, keys and `.env` values. There must be none.
5. The ops guide has no file:line, class or function names, or SQL.
6. `HANDOVER-DEV.md` accounts for all of F1-F12.
7. Report to the user: sections filled, number of exceptions listed, failure classes covered vs not applicable, and the gaps for the dev team.
## 6. Hand over the document
The skill always ends with one document the user can give to ops.
1. Run `python <skill>/templates/to_docx.py HANDOVER.md`. It writes `HANDOVER.docx` next to it. If `python-docx` is missing, run `pip install python-docx` first. If that is not allowed, say so and hand over `HANDOVER.md` instead.
2. Open the `.docx` with python-docx and check that it has 11 numbered section headings and that no `**` or `|---` is left in its text.
3. Give the user the path to `HANDOVER.docx` as the deliverable: "Give this to the ops team." `HANDOVER-DEV.md` stays in the repo for developers and is not handed to ops.
4. Report: sections filled, number of exceptions listed, failure classes covered vs not applicable, and the gaps for the dev team.
+113
View File
@@ -0,0 +1,113 @@
"""Turn the ops guide (HANDOVER.md) into a Word document ops can open, print and pass around.
python to_docx.py HANDOVER.md # writes HANDOVER.docx next to it
python to_docx.py HANDOVER.md -o guide.docx
python to_docx.py --selfcheck
Handles what the template uses: # headings, paragraphs, - bullets, - [ ] checkboxes, 1. steps,
| tables |, ``` blocks, **bold** and `code`. Needs python-docx (pip install python-docx).
"""
import argparse
import re
import sys
import tempfile
from pathlib import Path
from docx import Document
from docx.shared import Pt
INLINE = re.compile(r"(\*\*[^*]+\*\*|`[^`]+`)")
def add_runs(par, text):
for part in INLINE.split(text):
if part.startswith("**") and part.endswith("**"):
par.add_run(part[2:-2]).bold = True
elif part.startswith("`") and part.endswith("`"):
par.add_run(part[1:-1]).font.name = "Consolas"
elif part:
par.add_run(part)
def cells(line):
return [c.strip() for c in line.strip().strip("|").split("|")]
def convert(md_text, out_path):
doc = Document()
doc.styles["Normal"].font.name = "Calibri"
doc.styles["Normal"].font.size = Pt(10.5)
lines = md_text.splitlines()
i = 0
while i < len(lines):
line = lines[i]
if line.startswith("```"):
block = []
i += 1
while i < len(lines) and not lines[i].startswith("```"):
block.append(lines[i])
i += 1
run = doc.add_paragraph().add_run("\n".join(block))
run.font.name = "Consolas"
run.font.size = Pt(9.5)
elif line.startswith("|"):
rows = []
while i < len(lines) and lines[i].startswith("|"):
if not re.match(r"^\|[\s:|-]+\|\s*$", lines[i]): # skip the |---| separator
rows.append(cells(lines[i]))
i += 1
i -= 1
width = max(len(r) for r in rows)
table = doc.add_table(rows=len(rows), cols=width)
table.style = "Table Grid"
for r, row in enumerate(rows):
for c, text in enumerate(row):
par = table.cell(r, c).paragraphs[0]
add_runs(par, text)
if r == 0:
for run in par.runs:
run.bold = True
elif m := re.match(r"^(#{1,3})\s+(.*)", line):
doc.add_heading(m[2], level=len(m[1]) - 1 if len(m[1]) > 1 else 0)
elif m := re.match(r"^\s*- \[ \]\s+(.*)", line):
add_runs(doc.add_paragraph(style="List Bullet"), "☐ " + m[1])
elif m := re.match(r"^\s*[-*]\s+(.*)", line):
add_runs(doc.add_paragraph(style="List Bullet"), m[1])
elif m := re.match(r"^\s*\d+\.\s+(.*)", line):
add_runs(doc.add_paragraph(style="List Number"), m[1])
elif line.strip() and line.strip() != "---":
add_runs(doc.add_paragraph(), line.strip())
i += 1
doc.save(out_path)
return doc
def selfcheck():
example = Path(__file__).parent / "reference" / "tebra-auto-post.md"
guide = example.read_text(encoding="utf-8").split("# File 2:")[0].split("# File 1: `HANDOVER.md`")[1]
out = Path(tempfile.mkdtemp()) / "HANDOVER.docx"
convert(guide, out)
doc = Document(out)
heads = [p.text for p in doc.paragraphs if p.style.name.startswith("Heading")]
assert [h.split(".")[0] for h in heads if h[:1].isdigit()] == [str(n) for n in range(1, 12)], heads
assert len(doc.tables) >= 6, len(doc.tables)
assert doc.tables[0].cell(0, 0).text == "Where", doc.tables[0].cell(0, 0).text
assert not any("**" in p.text or "|---" in p.text for p in doc.paragraphs)
print(f"selfcheck ok: {len(heads)} headings, {len(doc.tables)} tables -> {out}")
if __name__ == "__main__":
ap = argparse.ArgumentParser()
ap.add_argument("md", nargs="?")
ap.add_argument("-o", "--out")
ap.add_argument("--selfcheck", action="store_true")
a = ap.parse_args()
if a.selfcheck:
selfcheck()
sys.exit(0)
if not a.md:
ap.error("give the HANDOVER.md path, or --selfcheck")
src = Path(a.md)
out = Path(a.out) if a.out else src.with_suffix(".docx")
convert(src.read_text(encoding="utf-8"), out)
print(f"wrote {out}")
+8
View File
@@ -2,6 +2,7 @@
import re
import shutil
import subprocess
import sys
from pathlib import Path
import pytest
@@ -60,3 +61,10 @@ def test_handoff_lists_twelve_failure_classes():
def test_handoff_ops_guide_has_eleven_sections_in_order():
guide = (ROOT / "skills" / "rpa-bot-handoff" / "templates" / "HANDOVER.md").read_text(encoding="utf-8")
assert [int(n) for n in re.findall(r"^## (\d+)\.", guide, re.MULTILINE)] == list(range(1, 12))
def test_handoff_docx_selfcheck():
pytest.importorskip("docx")
script = ROOT / "skills" / "rpa-bot-handoff" / "templates" / "to_docx.py"
r = subprocess.run([sys.executable, str(script), "--selfcheck"], capture_output=True, text=True)
assert r.returncode == 0, r.stderr