Files
document-haness/.agents/skills/writing-practitioner-guides/SKILL.md
T
DongHyeonkaandClaude Opus 5 ab59130196 chore: 이전 세션이 남긴 변경을 커밋한다
이번 파이프라인 작업과 무관하게 작업 트리에 남아 있던 것을 그대로 올린다.
사용자가 「전부 커밋」으로 정했고, 이번 작업과 섞이지 않게 커밋만 나눴다.

대부분은 clean-architecture-backend-template 의 그림 정본 재배치다 —
final/assets/diagrams/<이름>/ 에 있던 것이 CLAUDE.md 가 적은 배치인
final/assets/<이름>/ 로 옮겨졌고 .techviz/<이름>/ 이 함께 들어왔다.
삽입 줄의 대부분(3.15M)이 그 .techviz context.json 이다.

그 밖에 ca-tmpl·document-haness 의 정리, .claude/agents/ 열한 개,
writing-practitioner-guides 스킬, .playwright-mcp 세션 산출물,
scripts/check-ssot-facts.py 와 그 시험이 들어 있다.

이 커밋의 내용은 내가 만든 것이 아니라 이전 세션이 남긴 것이고 검증하지 않았다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 11:02:02 +09:00

16 KiB

name, description
name description
writing-practitioner-guides Use when writing hands-on guides, runbooks, troubleshooting docs, lab walkthroughs, or validation procedures that a human will execute themselves in a terminal over SSH. Symptoms that this applies - the reader is expected to type the commands, the doc teaches how to read a system rather than reporting a result, or a draft contains python -c, embedded JSON parsing, one-liners nobody types by hand, or a config file or YAML authored with printf/echo >>/heredoc instead of an editor.

Writing Practitioner Guides

Overview

Optimize for human operation, not command compactness.

A guide is not a script. The reader types each command, reads its raw output, and decides what to do next. Commands that are efficient for an agent to run once are often useless for a human learning to read a system.

The failure this prevents: an agent writes curl … | python3 -c 'import json…' because it produces a clean answer in one call. The reader gets the answer and learns nothing about the tool they will need at 3am.

The boundary

Three tiers. Pick the lowest one that fits.

interactive command  →  short pipeline  →  saved script file
Tier When Form
interactive reading state, one question kubectl get pods, ss -lntp, journalctl -u nginx -n 50
short pipeline filtering that a person would actually type ps aux | grep java, … | jq .status, for p in a b; do … done
saved script it has become a program write the file with an editor, then run it

Move to a saved script when any of these is true:

  • multiple branches (if) or nested loops
  • combining several requests and computing across them
  • non-trivial JSON transformation
  • you cannot tell what it does by reading it once
  • it will be run again later

For a saved script, the guide says to open an editor, shows the code as a separate file listing (not a terminal command), then shows the run command.

vim scripts/check_sessions.py
# file: scripts/check_sessions.py
...
python3 scripts/check_sessions.py

Terminal command ≠ program source. Never blur them with a heredoc.

Files the reader has to understand before changing them

The tiers above stop at the saved script. The same split reaches further: any file whose content the reader must read and understand to change it is written in an editor, not assembled by a shell one-liner. Config files and YAML are that kind of file.

조회·진단·실행은 CLI를 적극 사용하고, 사람이 내용을 이해하면서 작성해야 하는 설정 파일은 에디터를 사용한다.

Reading or acting on the system → CLI Authoring a file a human must understand → editor
CPU flags → grep /proc/cpuinfo, lscpu cloud-init YAML → nano kc-lab-1.yaml
service state → systemctl status systemd unit → sudo nano /etc/systemd/system/x.service
VM state → virsh list --all nginx.confsudo nano /etc/nginx/nginx.conf
network → ip addr, virsh net-list --all ~/.bashrcnano ~/.bashrc
logs → journalctl -u x Kubernetes manifest → nano deploy.yaml
fetch / copy → curl, cp, scp a script → nano x.shchmod +x x.sh./x.sh
create a VM → virt-install

This rule is not "replace sed with nano"

단순히 **「sednano로 바꿔라」**라고 하면 안 됩니다. 그러면 모든 shell 명령을 기계적으로 에디터 작업으로 바꿀 가능성이 큽니다.

The trigger is the file-authoring step, not the appearance of a shell tool.

Kind Examples Verdict
what an operator types by hand virsh, systemctl, ssh, curl, virt-install keep
reading / diagnosing grep, lsmod, cat, stat, groups keep
shell tricks that author a file printf >, echo >>, cat <<EOF, python3 -c, ssh '… cat > …' rewrite as an editor step

A pipe is not the problem. A 12-line virt-install is not the problem — creating the VM is that command's purpose, so the CLI is the right way to show it. sed is fine for a query or a throwaway substitution; it is wrong as the default interface for editing config, because what the reader will actually do during an incident is open the file and read what is in it.

Rewrite: ~/.bashrc

# before
echo 'export LIBVIRT_DEFAULT_URI=qemu:///system' >> ~/.bashrc
virsh uri
# after
nano ~/.bashrc
export LIBVIRT_DEFAULT_URI=qemu:///system
source ~/.bashrc
virsh uri

Two reasons, and the second is the one that gets forgotten:

  1. The reader sees the chain — file → variable → reload this shell → virsh now resolves that URI. echo >> produces only the end state.
  2. echo >> is not idempotent. Someone who walks the guide a second time appends the same line again. Opening the file shows what is already there.

Rewrite: cloud-init meta-data

# before
printf 'instance-id: kc-lab-1-%s\nlocal-hostname: kc-lab-1\n' "$(date +%s)" > meta-kc-lab-1
# after
nano meta-kc-lab-1
instance-id: kc-lab-1-20260912
local-hostname: kc-lab-1

instance-id는 이전 cloud-init 실행과 다른 인스턴스로 인식시키기 위해 이전 값과 겹치지 않게 지정한다.

The printf form makes the reader decode %s, \n, $(...), date +%s and > before reaching the two keys the page is about. A document teaching cloud-init should not be teaching shell printf. The editor form also gives the one line about instance-id a place to sit, right where it is typed.

Rewrite: a file on a remote host

# before
ssh donghyeon@192.168.122.11 'umask 077; cat > ~/kc-lab-2.yaml' < kc-lab-2.yaml
ssh donghyeon@192.168.122.11 'cloud-init schema -c ~/kc-lab-2.yaml; rm -f ~/kc-lab-2.yaml'
# after
scp kc-lab-2.yaml donghyeon@192.168.122.11:~/
ssh donghyeon@192.168.122.11

then, on the guest:

chmod 600 ~/kc-lab-2.yaml
cloud-init schema -c ~/kc-lab-2.yaml
rm ~/kc-lab-2.yaml

The first line asked the reader to hold SSH, redirection, umask, file creation and local stdin at once. The rewrite uses more commands and puts fewer things in each: one action, one command. In a guide someone is learning from, that trade is the right way round.

One more rewrite belongs to this rule — replacing a python3 -c YAML check with the format's own validator. It sits in Command priority below, where the ranking it changes lives.

Command priority

Rank Reach for Examples
1 the system's own CLI kubectl, systemctl, psql, redis-cli, docker compose
2 standard OS tools ps, ss, lsof, free, top, dmesg
3 network / protocol tools curl, dig, openssl, nc, tcpdump
4 short Unix combinators grep, jq, awk, head, tail, less, watch
avoid python/node heredocs, python -c that processes data, giant awk programs, pipelines built to produce one tidy answer

The line is doing-the-work vs checking-a-fact, not the language.

# not fine — this is data processing the native tool should do
curl -s "$URL" | python3 -c '
import json,sys
for r in json.load(sys.stdin)["data"]["result"]:
    print(r["metric"]["pod"], r["value"][1])'

That one iterates, reshapes, and formats. jq, or the tool's own output flag, does that — and when neither is installed, say so and show the raw output instead of writing a parser.

grep, jq, awk, and a one-line for are what practitioners type. Do not ban them. Ban the ones written for the agent's convenience.

Validate with the format's own checker first

Checking a fact is allowed — but a hand-rolled syntax check is the last resort, not the default. If the domain ships a command that validates this file, that command is the step. Drop to a one-line syntax check only when nothing validates the format.

# before — checks that it parses as YAML, and nothing else
python3 -c 'import yaml,sys; yaml.safe_load(open("kc-lab-1.yaml")); print("YAML OK")'

# after — the domain's own validator: schema, keys, and deprecations too
cloud-init schema -c ~/kc-lab-2.yaml

Same rank elsewhere: nginx -t, sshd -t, systemd-analyze verify x.service, kubectl apply --dry-run=server -f deploy.yaml, terraform validate, docker compose config. Each one is also the command the reader will reach for when the service refuses to start, which a python3 -c line never becomes.

Two shapes of the same tool

Most tools have a reading form and a value-extracting form. Pick by what the reader does next with the output.

The reader will… Form Example
look at the response and judge reading form curl -I <url> · curl -v <url>
compare or count the value across runs or hosts extracting form curl -s -o /dev/null -w '%{http_code}\n' <url>

The extracting form hides everything except the field you chose. Use it only when that field is the answer — a status code you will compare before and after an injection, or a number you will repeat 900 times. When the reader is still figuring out what is wrong, they need the headers and the TLS handshake, not 200.

Same split elsewhere: kubectl get (read) vs -o jsonpath= (extract), systemctl status (read) vs systemctl show -p X --value (extract), psql interactive (read) vs psql -tAc (extract).

Show the reading form first at least once per tool. A reader who has only ever seen -w '%{http_code}' cannot debug a TLS error.

Progressive narrowing

Never jump to the precise command. Show the widening-to-narrowing path the reader will actually walk.

list / status  →  detail / describe  →  logs  →  targeted inspection
kubectl get pods                       # what is there
kubectl get pods -o wide               # where, and on which node
kubectl describe pod api-7c874-8m2js   # why is this one unhappy
kubectl logs api-7c874-8m2js           # what did it say
kubectl logs api-7c874-8m2js --previous  # what did it say before it died

Observe before filtering. Show the raw output at least once before piping it. A reader who has never seen ss -lntp output needs to see it whole.

Mutation ordering

observe → diagnose → reproduce → mutate

rm, kill, delete, UPDATE, restart, config change do not appear in the diagnosis phase. If a step changes state, say what it changes and how to undo it.

Shape of one step

Four elements, in this order. A command with no interpretation is not a step.

무엇을 확인하는가        one line — the question this answers
$ command                the command, short
어디를 봐야 하는가        which field/line in the output matters
이 결과가 의미하는 것     what it tells you, and what to do next

Show the real output. If it was measured, quote it verbatim; if it is illustrative, say so.

When the step changes state

The four elements above are the shape of a step that reads. A step that changes state needs a different one — otherwise the why, the failure symptoms and the special cases all pile into the same paragraph:

정보 밀도는 높은데 처음 따라 하는 사람의 시선 이동이 어렵습니다.

목적 → 행동(번호 매긴 명령) → 예상 결과 → 왜 필요한가 → 문제가 생기면
### 4. libvirt 기본 연결을 system으로 설정한다

목적
virsh가 사용자 세션이 아니라 시스템 libvirt에 연결되도록 한다.

1. 설정 파일을 연다.
$ nano ~/.bashrc

2. 다음 줄을 추가한다.
export LIBVIRT_DEFAULT_URI=qemu:///system

3. 저장한 설정을 현재 셸에 반영한다.
$ source ~/.bashrc

4. 확인한다.
$ virsh uri

예상 결과
qemu:///system

왜 필요한가
qemu:///session과 qemu:///system은 서로 다른 libvirt 연결이다.
VM을 system 쪽에 만들고 virsh가 session 쪽을 보고 있으면
VM을 만들었는데도 목록에서 찾지 못할 수 있다.

문제가 생기면
$ virsh uri
부터 확인한다.

A step that only reads takes the first shape; a step that changes state takes this one. Same headings every time, so the reader's eye lands in the same place on step 11 as on step 1.

Verify before publishing

Every command in a guide must have been run, or be marked as unverified. Check that the tools you reach for are actually installed on the machine the reader will be onjq and yamllint are absent more often than you expect, and a guide that assumes them sends the reader to install things mid-diagnosis. Two failures this catches, both real:

  • kubectl get endpoints — deprecated since v1.33, prints a warning
  • kubectl exec keycloak-0 -- curl … — the image has no curl, exit 127

A guide that teaches a stale or failing command makes the reader doubt their own environment.

No placeholders

<token> puts the value outside the document. Give the command that produces it. For secrets, confirm existence or length — never print the value.

TOKEN=$(ssh node1 'sudo cat /var/lib/rancher/k3s/server/node-token')
echo "${#TOKEN} chars"

One exception: a value an earlier step already printed on screen, which the reader carries into a later step in a different shell — a session id, a realm UUID. The producing command is already in the document, so the value is not outside it. Write that slot as {{NAME}} (uppercase, digits, underscore), and say in the prose above the block which step printed it.

Not ${NAME} — guides use $TOKEN, $SID and friends as real shell variables, and a reader who reads a placeholder as one will paste it unchanged. {{ }} is not shell syntax, so pasting it fails where the reader can see it.

Rationalization table

Excuse Reality
"The pipeline gives a clean answer" The reader needs to read the raw output, not your summary of it
"python -c is shorter than explaining" It is shorter for you. The reader learns nothing and cannot adapt it
"jq/awk are also programming" They are what practitioners type. The line is program vs command, not language
"I'll show the efficient way" Efficient for one run. This doc is for someone learning to read the system
"The reader can copy-paste it" Copy-paste is not the goal. Knowing where to look is
"I verified the logic mentally" Run it. Two commands in a recent guide were wrong and both looked right
"It's obvious what this output means" Then write the one line. If it is obvious it costs nothing
"-w '%{http_code}' is precise" Precise about one field. The reader debugging TLS needs -v, not 200
"It's fewer commands" Fewer for you to type. The reader cannot tell which action they are in the middle of
"The file ends up the same either way" Only on the first run. echo >> appends again every time someone repeats the guide
"So I should use nano for everything" No. The trigger is authoring a file the reader must understand. grep, virsh, a long virt-install stay as they are
"python3 -c only checks syntax" Then it misses the schema. If the format has a validator — cloud-init schema, nginx -t, --dry-run — that is the step

Red flags — stop and rewrite

  • python3 -c or a <<'PY' heredoc inside a guide
  • JSON parsed with a language runtime instead of jq or the tool's own -o
  • a pipeline whose purpose you cannot state in one clause
  • a command with no "what to look at" line under it
  • delete/kill/restart before any observation step
  • <placeholder> with no command that produces it
  • output shown that you never actually ran
  • only the extracting form of a tool appears, never the reading form
  • a config file or YAML built with printf >, echo >>, or cat <<EOF
  • one line stacking connect + redirect + file creation (ssh host 'cat > f' < f)
  • python3 -c checking syntax when the format has its own validator
  • a step that appends, so walking the guide twice appends the line twice

References

Per-technology command vocabulary — what practitioners reach for first, not an encyclopedia. Load only the one you need.