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 10. Don'ts, and the right way to stop the bot
11. Issues seen before 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. **`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 ## 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. 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. 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. 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 ## Files
| File | What it is | | 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/HANDOVER-DEV.md` | The dev notes skeleton |
| `templates/failure-classes.md` | 12 failure classes from Tebra's first months with ops | | `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/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/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/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/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: Rules:
- **Never run the bot.** Real bots act on live systems. Read code, config, logs and history only. - **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". - **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`. - **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. - **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) ## 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. 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. 5. The ops guide has no file:line, class or function names, or SQL.
6. `HANDOVER-DEV.md` accounts for all of F1-F12. 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 re
import shutil import shutil
import subprocess import subprocess
import sys
from pathlib import Path from pathlib import Path
import pytest import pytest
@@ -60,3 +61,10 @@ def test_handoff_lists_twelve_failure_classes():
def test_handoff_ops_guide_has_eleven_sections_in_order(): def test_handoff_ops_guide_has_eleven_sections_in_order():
guide = (ROOT / "skills" / "rpa-bot-handoff" / "templates" / "HANDOVER.md").read_text(encoding="utf-8") 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)) 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