agent-harness-kit 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +11 -1
- package/bin/cli.mjs +21 -0
- package/package.json +1 -1
- package/src/core/doctor.mjs +24 -0
- package/src/core/render-templates.mjs +29 -0
- package/src/core/upgrade.mjs +81 -60
- package/src/templates/.claude/agents/api-consistency-reviewer.md.vi +37 -0
- package/src/templates/.claude/agents/architecture-reviewer.md.vi.hbs +45 -0
- package/src/templates/.claude/agents/performance-reviewer.md.vi +39 -0
- package/src/templates/.claude/agents/reliability-reviewer.md.vi +42 -0
- package/src/templates/.claude/agents/security-reviewer.md.vi +43 -0
- package/src/templates/.claude/hooks/hooks.json +22 -0
- package/src/templates/.claude/output-styles/harness-terse.md +42 -0
- package/src/templates/.claude/settings.json.hbs +1 -0
- package/src/templates/.claude/skills/add-adr/SKILL.md.vi +64 -0
- package/src/templates/.claude/skills/add-feature/SKILL.md.vi.hbs +50 -0
- package/src/templates/.claude/skills/debug-flow/SKILL.md.vi.hbs +42 -0
- package/src/templates/.claude/skills/deliver-html/SKILL.md.hbs +96 -0
- package/src/templates/.claude/skills/deliver-html/SKILL.md.vi.hbs +89 -0
- package/src/templates/.claude/skills/deliver-html/assets/report.css +233 -0
- package/src/templates/.claude/skills/deliver-html/scripts/wrap-html.mjs +0 -0
- package/src/templates/.claude/skills/deliver-html/templates/audit-report.html.tmpl +29 -0
- package/src/templates/.claude/skills/deliver-html/templates/decision-doc.html.tmpl +29 -0
- package/src/templates/.claude/skills/deliver-html/templates/status-report.html.tmpl +29 -0
- package/src/templates/.claude/skills/doc-drift-scan/SKILL.md.vi +52 -0
- package/src/templates/.claude/skills/eval-runner/SKILL.md.vi +59 -0
- package/src/templates/.claude/skills/garbage-collection/SKILL.md.vi.hbs +58 -0
- package/src/templates/.claude/skills/i18n-add-locale/SKILL.md +52 -0
- package/src/templates/.claude/skills/i18n-add-locale/SKILL.md.vi +56 -0
- package/src/templates/.claude/skills/i18n-add-locale/scripts/locale-scaffold.mjs +120 -0
- package/src/templates/.claude/skills/inspect-app/SKILL.md.vi +61 -0
- package/src/templates/.claude/skills/inspect-module/SKILL.md.vi.hbs +57 -0
- package/src/templates/.claude/skills/map-domain/SKILL.md +42 -0
- package/src/templates/.claude/skills/map-domain/SKILL.md.vi +42 -0
- package/src/templates/.claude/skills/map-domain/scripts/domain-map.mjs +145 -0
- package/src/templates/.claude/skills/propose-harness-improvement/SKILL.md.vi +49 -0
- package/src/templates/.claude/skills/propose-harness-improvement/scripts/improvement-bundle.mjs +172 -0
- package/src/templates/.claude/skills/refactor-feature/SKILL.md +60 -0
- package/src/templates/.claude/skills/refactor-feature/SKILL.md.vi +64 -0
- package/src/templates/.claude/skills/refactor-feature/scripts/feature-diff.mjs +146 -0
- package/src/templates/.claude/skills/review-this-pr/SKILL.md +59 -0
- package/src/templates/.claude/skills/review-this-pr/SKILL.md.vi +63 -0
- package/src/templates/.claude/skills/review-this-pr/scripts/pr-review-driver.mjs +152 -0
- package/src/templates/.claude/skills/structural-test-author/SKILL.md.vi.hbs +50 -0
- package/src/templates/.claude/skills/write-skill/SKILL.md.vi +43 -0
- package/src/templates/.harness/eval/rubrics/feature-step-done.mjs +148 -0
- package/src/templates/.harness/eval/tasks/feature-step-done.answer.md +53 -0
- package/src/templates/.harness/eval/tasks/feature-step-done.json +10 -0
- package/src/templates/.harness/eval/tasks/feature-step-done.prompt.md +43 -0
- package/src/templates/.mcp.json.example +35 -0
- package/src/templates/CLAUDE.md.hbs +1 -0
- package/src/templates/CLAUDE.md.vi.hbs +1 -0
- package/src/templates/docs/adr/0002-html-first-for-humans.md.hbs +116 -0
- package/src/templates/docs/golden-principles.md.hbs +32 -0
- package/src/templates/scripts/precompletion-checklist.sh.hbs +43 -0
- package/src/templates/scripts/pretooluse-edit-guard.sh.hbs +115 -0
- package/src/templates/scripts/session-end.sh.hbs +6 -0
- package/src/templates/scripts/session-rollup.mjs +96 -0
- package/src/templates/scripts/session-start.sh.hbs +25 -0
- package/src/templates/scripts/subagent-stop.sh.hbs +76 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: harness-terse
|
|
3
|
+
description: Solo-dev terse output style for the agent-harness-kit. Cuts ceremonial wrappers (decorative summaries, "let me explain my plan" preambles, "in summary" closers) and biases toward Vietnamese-flavoured English when the user writes mixed VN/EN. Tuned for code-review and refactor work, where the diff is the deliverable and prose around it is noise.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Output style: harness-terse
|
|
7
|
+
|
|
8
|
+
You are operating inside the agent-harness-kit — a solo-developer Claude
|
|
9
|
+
Code harness with structural tests, skill side-cars, and a tight
|
|
10
|
+
human-in-the-loop pattern. The user reads diffs and tool calls directly;
|
|
11
|
+
prose is for genuine signal, not narration.
|
|
12
|
+
|
|
13
|
+
## Rules
|
|
14
|
+
|
|
15
|
+
1. **No decorative summaries.** Skip "I'll start by…", "Now let me…",
|
|
16
|
+
"In summary…" and other rituals. State what changed, in one or two
|
|
17
|
+
sentences.
|
|
18
|
+
2. **No "let me read the file" preambles.** State the action and call
|
|
19
|
+
the tool — the user sees both.
|
|
20
|
+
3. **Diff > prose.** When a code change is the deliverable, point at the
|
|
21
|
+
files and let the diff speak. Only add prose where the diff is not
|
|
22
|
+
self-explanatory.
|
|
23
|
+
4. **Use `path:line` for code references** so the user can jump.
|
|
24
|
+
5. **Match the user's language.** If they write in Vietnamese, reply in
|
|
25
|
+
Vietnamese. If mixed VN/EN, mirror their balance.
|
|
26
|
+
6. **End turns with what changed + what's next, one sentence each.** No
|
|
27
|
+
bullet lists summarising the previous turn — the user sees the tool
|
|
28
|
+
calls.
|
|
29
|
+
7. **When uncertain, ask one focused question.** Don't pad with multiple
|
|
30
|
+
"or alternatively" branches.
|
|
31
|
+
8. **Skills are first-class.** When a user request maps to a skill,
|
|
32
|
+
invoke it rather than freestyle the work. Skills carry the harness's
|
|
33
|
+
safety net (structural tests, baseline monotonic, side-cars).
|
|
34
|
+
|
|
35
|
+
## What this style is NOT for
|
|
36
|
+
|
|
37
|
+
- Long-form explanations to non-technical stakeholders.
|
|
38
|
+
- Tutorial / educational responses where worked-out reasoning matters.
|
|
39
|
+
- First-time user onboarding where the user explicitly asks for verbose
|
|
40
|
+
guidance.
|
|
41
|
+
|
|
42
|
+
In those cases, fall back to Claude Code's default style.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<!-- LOCALE_TODO: translate body to vi -->
|
|
2
|
+
<!-- Source: .claude/skills/add-adr/SKILL.md -->
|
|
3
|
+
<!-- Edit only the markdown body — keep frontmatter verbatim so the kit's renderer + Claude Code parse it identically across locales. -->
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
name: add-adr
|
|
7
|
+
description: Use this skill whenever a decision is made about architecture, dependencies, frameworks, naming conventions, or layer order. Creates a numbered ADR (Architecture Decision Record) in `docs/adr/` in the canonical Nygard format. Always invoke this before changing layer order, adding a layer, swapping a major dependency, or introducing a new external service.
|
|
8
|
+
allowed-tools: Read, Write, Glob
|
|
9
|
+
suggested-turns: 6
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Steps
|
|
13
|
+
|
|
14
|
+
1. **Find the next number.** List `docs/adr/` and pick the highest existing
|
|
15
|
+
number + 1 (zero-padded to 4 digits).
|
|
16
|
+
2. **Generate the file.** Write `docs/adr/{NNNN}-{kebab-title}.md` with the
|
|
17
|
+
sections below.
|
|
18
|
+
3. **Update affected configs.** If the ADR changes layer order or adds a
|
|
19
|
+
layer, update `harness.config.json` AND the structural-test config in the
|
|
20
|
+
same commit as the ADR.
|
|
21
|
+
4. **Append to the index.** Add a one-line entry under "Recent decisions" in
|
|
22
|
+
`docs/architecture.md`.
|
|
23
|
+
|
|
24
|
+
## ADR template (write exactly this shape)
|
|
25
|
+
|
|
26
|
+
```markdown
|
|
27
|
+
# ADR <NNNN> — <title>
|
|
28
|
+
|
|
29
|
+
- **Status:** proposed | accepted | superseded by <link>
|
|
30
|
+
- **Date:** YYYY-MM-DD
|
|
31
|
+
- **Deciders:** <names or "project owner">
|
|
32
|
+
|
|
33
|
+
## Context
|
|
34
|
+
|
|
35
|
+
<What forces are in play? What constraints? What did we learn that triggered this?>
|
|
36
|
+
|
|
37
|
+
## Decision
|
|
38
|
+
|
|
39
|
+
<What we decided. Single sentence then a list.>
|
|
40
|
+
|
|
41
|
+
## Consequences
|
|
42
|
+
|
|
43
|
+
Positive: ...
|
|
44
|
+
Negative: ...
|
|
45
|
+
|
|
46
|
+
## Alternatives considered
|
|
47
|
+
|
|
48
|
+
- <alternative>: <why rejected>
|
|
49
|
+
- <alternative>: <why rejected>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Output contract
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
### ADR: <NNNN>-<slug>
|
|
56
|
+
### Status: <status>
|
|
57
|
+
### Configs updated: <list or "none">
|
|
58
|
+
### docs/architecture.md updated: yes/no
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Anti-patterns
|
|
62
|
+
|
|
63
|
+
- Don't write an ADR for a one-line refactor — those go in commit messages.
|
|
64
|
+
- Don't change the status of an existing ADR retroactively. Supersede it.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: add-feature
|
|
3
|
+
description: Use this skill whenever the user asks to add, implement, or build a new feature, capability, endpoint, page, command, or anything user-visible. Enforces the Anthropic two-fold harness pattern — read feature_list.json, pick exactly one feature, implement incrementally, run the structural test on every save, and never declare "done" without updating the JSON. Always invoke this skill instead of writing new feature code freehand.
|
|
4
|
+
allowed-tools: Read, Edit, Write, Bash(npm run:*), Bash(pytest:*), Bash(ruff:*), Bash(git:*), Glob, Grep
|
|
5
|
+
suggested-turns: 25
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Các bước
|
|
9
|
+
|
|
10
|
+
1. **Đọc `feature_list.json`.** Xác nhận feature tồn tại và `passes:
|
|
11
|
+
false`. Nếu user mô tả feature chưa có trong danh sách, **dừng lại**:
|
|
12
|
+
hỏi xem có nên thêm qua `/add-adr` trước không.
|
|
13
|
+
2. **Đọc `docs/architecture.md`** cho domain bị ảnh hưởng. Xác định
|
|
14
|
+
những layer nào sẽ thay đổi.
|
|
15
|
+
3. **Chạy `/inspect-module`** trên mỗi module bị ảnh hưởng. Làm điều này
|
|
16
|
+
ngay cả khi bạn nghĩ đã hiểu khu vực đó — phải kiểm chứng, không phỏng đoán.
|
|
17
|
+
4. **Lập kế hoạch trước.** Viết một đoạn văn ngắn vào `.harness/PLAN.md`
|
|
18
|
+
*trước khi thay đổi code*. (Pattern theo Anthropic Claude 4 prompt-guide.)
|
|
19
|
+
5. **Bắt đầu từ thay đổi nhỏ nhất.** Sửa ít nhất đủ để một `steps[]`
|
|
20
|
+
chuyển từ failing → passing.
|
|
21
|
+
6. **Chạy structural test.** {{#if isPython}}`python -m harness.structural_test`{{else}}`npm run harness:check`{{/if}}.
|
|
22
|
+
Nếu fail, sửa vi phạm trước khi tiếp tục — không bao giờ disable test.
|
|
23
|
+
7. **Smoke test.** Chạy smoke test liên quan trong `scripts/dev-up.sh`.
|
|
24
|
+
8. **Cập nhật `feature_list.json` CHỈ** bằng cách đổi field `passes` của
|
|
25
|
+
một item. Không bao giờ xóa hoặc viết lại items. (Quy tắc Anthropic
|
|
26
|
+
"JSON hơn Markdown": "model ít có khả năng thay đổi/ghi đè JSON files
|
|
27
|
+
so với Markdown.")
|
|
28
|
+
9. **Append vào PROGRESS.** Một dòng vào `.harness/PROGRESS.md`:
|
|
29
|
+
`YYYY-MM-DD HH:MM | <feature_id> | done`.
|
|
30
|
+
10. **Commit.** Message: `feat(<domain>): <feature_id> - <ngắn gọn>`.
|
|
31
|
+
|
|
32
|
+
## Các failure mode cần tránh (mỗi dòng tương ứng một lỗi đã quan sát thực tế)
|
|
33
|
+
|
|
34
|
+
- Không tuyên bố feature đã xong khi chưa chạy smoke test.
|
|
35
|
+
- Không đánh dấu `passes: true` khi structural test còn failing.
|
|
36
|
+
- Không thêm feature mới vào `feature_list.json` giữa session — đề xuất
|
|
37
|
+
cho session sau qua ADR.
|
|
38
|
+
- Không refactor code không liên quan trong cùng commit.
|
|
39
|
+
|
|
40
|
+
## Output contract
|
|
41
|
+
|
|
42
|
+
Sau khi triển khai, tóm tắt:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
### Feature: <id>
|
|
46
|
+
### Files changed: <list>
|
|
47
|
+
### Structural test: PASS|FAIL
|
|
48
|
+
### Smoke test: PASS|FAIL
|
|
49
|
+
### Reviewer subagents to invoke: architecture-reviewer, security-reviewer (nếu chạm auth/IO), reliability-reviewer (nếu chạm retries/timeouts)
|
|
50
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<!-- LOCALE_TODO: translate body to vi -->
|
|
2
|
+
<!-- Source: .claude/skills/debug-flow/SKILL.md.hbs -->
|
|
3
|
+
<!-- Edit only the markdown body — keep frontmatter verbatim so the kit's renderer + Claude Code parse it identically across locales. -->
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
name: debug-flow
|
|
7
|
+
description: Use this skill whenever the user reports a bug, unexpected output, or "this doesn't work". Runs the dev server, drives the failing flow via Playwright MCP if installed (else captures stdout/stderr), and produces a minimal repro before any fix. Mirrors the OpenAI Chrome-DevTools-Protocol-into-runtime pattern at solo scale — verify the failure before you propose a fix.
|
|
8
|
+
allowed-tools: Read, Bash({{devCmd}}), Bash(curl:*), Bash(playwright:*), Bash(scripts/dev-up.sh)
|
|
9
|
+
suggested-turns: 20
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Steps
|
|
13
|
+
|
|
14
|
+
1. **Start the dev server** via `scripts/dev-up.sh`. Wait for the readiness
|
|
15
|
+
probe.
|
|
16
|
+
2. **Drive the failing flow.**
|
|
17
|
+
- If the bug is UI: use Playwright MCP (`mcp__playwright__*`) — the
|
|
18
|
+
Anthropic claude.ai-clone pattern.
|
|
19
|
+
- If MCP unavailable: fall back to `curl -i` + screenshot via
|
|
20
|
+
`scrot`/`screencapture`/`gnome-screenshot`.
|
|
21
|
+
3. **Capture context.** Request payload (if any), response status, stderr
|
|
22
|
+
tail (last 50 lines), last 3 git commits.
|
|
23
|
+
4. **Write a minimal repro** to `.harness/repros/<date>-<slug>.md` with:
|
|
24
|
+
environment, steps, expected, actual.
|
|
25
|
+
5. **Only then propose a fix.** Run the structural test and the relevant
|
|
26
|
+
smoke test after the fix. Re-run the repro to confirm.
|
|
27
|
+
|
|
28
|
+
## Output contract
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
### Repro saved: .harness/repros/<filename>
|
|
32
|
+
### Failure mode: <one-line summary>
|
|
33
|
+
### Smallest failing input: <code or curl command>
|
|
34
|
+
### Proposed fix location: <file:line>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Anti-patterns
|
|
38
|
+
|
|
39
|
+
- Don't propose a fix before reproducing the bug locally.
|
|
40
|
+
- Don't fix more than the user reported in the same commit.
|
|
41
|
+
- Don't add a defensive try/except over the failing call without
|
|
42
|
+
understanding why it fails.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: deliver-html
|
|
3
|
+
description: Use this skill whenever the user asks to analyze, audit, review, summarize, produce a report, write a plan, make a proposal, draft a decision doc, list "next actions", or any other task that produces a DOCUMENT a HUMAN reads-and-acts-on. Outputs a self-contained <slug>.html at repo root using the shared dark-theme CSS. Why HTML and not Markdown: golden principle #9 — Markdown is great for files an agent reads-and-edits (CLAUDE.md, SKILL.md, ADRs), but a HUMAN reading a 700-line MD deliverable will scroll, miss the conclusion, and ask the agent to clarify — burning turns and tokens. HTML deliverable is read once, decided once. Do NOT use this skill for files the agent itself reads (those stay MD), for stdout output from /review-this-pr or /garbage-collection (pass-through MD), or for short summaries under ~30 lines (overhead not worth it).
|
|
4
|
+
allowed-tools: Read, Write, Bash(node .claude/skills/deliver-html/scripts/wrap-html.mjs:*), Bash(cat:*), Bash(ls:*)
|
|
5
|
+
suggested-turns: 6
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## When to use
|
|
9
|
+
|
|
10
|
+
Trigger words from the user (English / Vietnamese):
|
|
11
|
+
|
|
12
|
+
- "analyze X", "audit X", "review X for me" / "phân tích X", "audit X", "review giúp tôi"
|
|
13
|
+
- "produce a report on X" / "báo cáo về X", "tổng kết X"
|
|
14
|
+
- "plan for X", "proposal for X", "decision doc for X" / "plan cho X", "đề xuất X"
|
|
15
|
+
- "what should we do about X", "next actions for X" / "next actions cho X"
|
|
16
|
+
|
|
17
|
+
Do **NOT** use for:
|
|
18
|
+
|
|
19
|
+
- Editing `CLAUDE.md`, `SKILL.md`, `docs/*.md`, or any agent-read file (those stay MD).
|
|
20
|
+
- Pass-through stdout from `/review-this-pr`, `/garbage-collection`, `/inspect-module` (the agent itself consumes that — keep it MD).
|
|
21
|
+
- Short summaries (< 30 lines of body). Just answer inline.
|
|
22
|
+
- Files that will be diffed in a PR. Source-of-truth stays MD.
|
|
23
|
+
|
|
24
|
+
## Steps
|
|
25
|
+
|
|
26
|
+
1. **Pick a template** based on what the user is asking for:
|
|
27
|
+
- `decision-doc` (default) — analysis + recommendation + next actions
|
|
28
|
+
- `audit-report` — findings table with severity, current vs. target state
|
|
29
|
+
- `status-report` — shipped progress + remaining work + KPIs
|
|
30
|
+
|
|
31
|
+
2. **Write the MD body** in working memory. Use standard GitHub-flavored
|
|
32
|
+
Markdown: `#`/`##`/`###` headings, paragraphs, `-`/`1.` lists, fenced
|
|
33
|
+
```` ```code``` ````, `> blockquote`, `| a | b |` tables, and inline
|
|
34
|
+
`code`/`**bold**`/`*italic*`/`[link](url)`.
|
|
35
|
+
|
|
36
|
+
You may also use these CSS hooks via raw HTML for richer visuals:
|
|
37
|
+
- `<div class="card good|warn|bad|info|next">…</div>` — bordered callout
|
|
38
|
+
- `<span class="pill good|warn|bad|info|alt">P0</span>` — inline badge
|
|
39
|
+
- `<div class="grid2|grid3">…</div>` — column layout
|
|
40
|
+
- `<div class="stat good|warn|info"><div class="lbl">…</div><div class="num">…</div></div>` — stat tile
|
|
41
|
+
|
|
42
|
+
3. **Pick a slug** (kebab-case, derived from title). Default: same dir as
|
|
43
|
+
CWD (repo root). Example: title "Phân tích auth flow" → file
|
|
44
|
+
`phan-tich-auth-flow.html`.
|
|
45
|
+
|
|
46
|
+
4. **Render** with the side-car script:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
node .claude/skills/deliver-html/scripts/wrap-html.mjs \
|
|
50
|
+
--title "Phân tích auth flow" \
|
|
51
|
+
--subtitle "Bằng chứng, lập luận, next actions" \
|
|
52
|
+
--template decision-doc \
|
|
53
|
+
--in /tmp/body.md \
|
|
54
|
+
--out phan-tich-auth-flow.html
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The script:
|
|
58
|
+
- Reads `assets/report.css` (single source of truth for the style).
|
|
59
|
+
- Auto-detects locale from `harness.config.json` `.claudeMd.humanLanguage` (override with `--lang`).
|
|
60
|
+
- Converts MD → HTML (self-rolled subset: headings, lists, code blocks,
|
|
61
|
+
tables, blockquotes, links, inline formatting — no npm dependency).
|
|
62
|
+
- Writes `<slug>.html` at the path you pass.
|
|
63
|
+
|
|
64
|
+
5. **Print the deliverable contract** (the script already does this — copy it
|
|
65
|
+
into your response):
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
### Deliverable
|
|
69
|
+
**File:** <path> (<size>)
|
|
70
|
+
**Template:** decision-doc | audit-report | status-report
|
|
71
|
+
**Lang:** vi | en
|
|
72
|
+
**Open:** `open <slug>.html` (macOS) / `xdg-open <slug>.html` (linux)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Output contract
|
|
76
|
+
|
|
77
|
+
The script writes exactly one file (`<slug>.html`) at the requested path and
|
|
78
|
+
prints the deliverable contract on stdout. The HTML is self-contained
|
|
79
|
+
(CSS inlined) so it can be emailed / sent on Slack / attached to a PR with no
|
|
80
|
+
dependencies.
|
|
81
|
+
|
|
82
|
+
## Anti-patterns
|
|
83
|
+
|
|
84
|
+
- **Don't write raw HTML markup in your MD body** beyond the CSS hook
|
|
85
|
+
patterns above (cards, pills, grids, stats). The wrap script handles the
|
|
86
|
+
document chrome — your job is content.
|
|
87
|
+
- **Don't inline `<style>` blocks in the body.** The wrap script injects
|
|
88
|
+
shared CSS once. Inline overrides drift over time.
|
|
89
|
+
- **Don't use this skill for `docs/*.md` updates.** Those are agent-read;
|
|
90
|
+
they stay MD per principle #9.
|
|
91
|
+
- **Don't claim "done" without writing the file.** The deliverable IS the
|
|
92
|
+
file. If `wrap-html.mjs` errored, do not move on.
|
|
93
|
+
- **Don't open a deliverable shorter than ~30 lines as HTML.** A short
|
|
94
|
+
answer belongs in the chat response, not a file.
|
|
95
|
+
- **Don't translate the CSS.** Style is locale-agnostic. Only the `lang`
|
|
96
|
+
attribute and body copy localize.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: deliver-html
|
|
3
|
+
description: Use this skill whenever the user asks to analyze, audit, review, summarize, produce a report, write a plan, make a proposal, draft a decision doc, list "next actions", or any other task that produces a DOCUMENT a HUMAN reads-and-acts-on. Outputs a self-contained <slug>.html at repo root using the shared dark-theme CSS. Why HTML and not Markdown: golden principle #9 — Markdown is great for files an agent reads-and-edits (CLAUDE.md, SKILL.md, ADRs), but a HUMAN reading a 700-line MD deliverable will scroll, miss the conclusion, and ask the agent to clarify — burning turns and tokens. HTML deliverable is read once, decided once. Do NOT use this skill for files the agent itself reads (those stay MD), for stdout output from /review-this-pr or /garbage-collection (pass-through MD), or for short summaries under ~30 lines (overhead not worth it).
|
|
4
|
+
allowed-tools: Read, Write, Bash(node .claude/skills/deliver-html/scripts/wrap-html.mjs:*), Bash(cat:*), Bash(ls:*)
|
|
5
|
+
suggested-turns: 6
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Khi nào dùng
|
|
9
|
+
|
|
10
|
+
Trigger keyword từ user (tiếng Việt / English):
|
|
11
|
+
|
|
12
|
+
- "phân tích X", "audit X", "review giúp tôi" / "analyze X", "audit X", "review X for me"
|
|
13
|
+
- "báo cáo về X", "tổng kết X" / "produce a report on X"
|
|
14
|
+
- "plan cho X", "đề xuất X", "decision doc cho X" / "plan for X", "proposal for X"
|
|
15
|
+
- "next actions cho X", "ta nên làm gì với X"
|
|
16
|
+
|
|
17
|
+
**KHÔNG** dùng cho:
|
|
18
|
+
|
|
19
|
+
- Chỉnh sửa `CLAUDE.md`, `SKILL.md`, `docs/*.md`, hoặc bất kỳ file agent đọc (giữ MD).
|
|
20
|
+
- Output stdout từ `/review-this-pr`, `/garbage-collection`, `/inspect-module` (agent tự consume — giữ MD).
|
|
21
|
+
- Tóm tắt ngắn (< 30 dòng body). Trả lời inline.
|
|
22
|
+
- File sẽ được diff trong PR. Source-of-truth phải MD.
|
|
23
|
+
|
|
24
|
+
## Các bước
|
|
25
|
+
|
|
26
|
+
1. **Chọn template** theo yêu cầu user:
|
|
27
|
+
- `decision-doc` (mặc định) — phân tích + đề xuất + next actions
|
|
28
|
+
- `audit-report` — bảng findings có severity, trạng thái hiện tại vs. mong muốn
|
|
29
|
+
- `status-report` — đã ship + còn lại + KPI
|
|
30
|
+
|
|
31
|
+
2. **Viết body bằng MD** trong working memory. Dùng GitHub-flavored Markdown
|
|
32
|
+
thông thường: `#`/`##`/`###` headings, paragraph, `-`/`1.` list, fenced
|
|
33
|
+
```` ```code``` ````, `> blockquote`, `| a | b |` table, inline
|
|
34
|
+
`code`/`**bold**`/`*italic*`/`[link](url)`.
|
|
35
|
+
|
|
36
|
+
Có thể nhúng các CSS hook qua raw HTML cho visual tốt hơn:
|
|
37
|
+
- `<div class="card good|warn|bad|info|next">…</div>` — callout viền màu
|
|
38
|
+
- `<span class="pill good|warn|bad|info|alt">P0</span>` — badge inline
|
|
39
|
+
- `<div class="grid2|grid3">…</div>` — bố cục cột
|
|
40
|
+
- `<div class="stat good|warn|info"><div class="lbl">…</div><div class="num">…</div></div>` — ô số liệu
|
|
41
|
+
|
|
42
|
+
3. **Chọn slug** (kebab-case từ title). Default: cùng thư mục CWD (repo root).
|
|
43
|
+
Ví dụ: title "Phân tích auth flow" → file `phan-tich-auth-flow.html`.
|
|
44
|
+
|
|
45
|
+
4. **Render** bằng side-car script:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
node .claude/skills/deliver-html/scripts/wrap-html.mjs \
|
|
49
|
+
--title "Phân tích auth flow" \
|
|
50
|
+
--subtitle "Bằng chứng, lập luận, next actions" \
|
|
51
|
+
--template decision-doc \
|
|
52
|
+
--in /tmp/body.md \
|
|
53
|
+
--out phan-tich-auth-flow.html
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Script sẽ:
|
|
57
|
+
- Đọc `assets/report.css` (single source of truth cho style).
|
|
58
|
+
- Auto-detect locale từ `harness.config.json` `.claudeMd.humanLanguage` (override bằng `--lang`).
|
|
59
|
+
- Convert MD → HTML (self-rolled subset: heading, list, code block, table,
|
|
60
|
+
blockquote, link, inline format — không cần npm dependency).
|
|
61
|
+
- Ghi `<slug>.html` tại path bạn truyền.
|
|
62
|
+
|
|
63
|
+
5. **In deliverable contract** (script tự in — bạn copy vào response):
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
### Deliverable
|
|
67
|
+
**File:** <path> (<size>)
|
|
68
|
+
**Template:** decision-doc | audit-report | status-report
|
|
69
|
+
**Lang:** vi | en
|
|
70
|
+
**Open:** `open <slug>.html` (macOS) / `xdg-open <slug>.html` (linux)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Output contract
|
|
74
|
+
|
|
75
|
+
Script ghi đúng 1 file (`<slug>.html`) tại path đã yêu cầu và in deliverable
|
|
76
|
+
contract ra stdout. HTML self-contained (CSS inline) nên có thể gửi qua email
|
|
77
|
+
/ Slack / đính kèm PR mà không cần dependency.
|
|
78
|
+
|
|
79
|
+
## Anti-patterns
|
|
80
|
+
|
|
81
|
+
- **Đừng viết raw HTML trong MD body** ngoài các CSS hook đã liệt kê
|
|
82
|
+
(cards, pills, grids, stats). Wrap script lo phần document chrome — bạn lo nội dung.
|
|
83
|
+
- **Đừng inline `<style>` trong body.** Wrap script inject shared CSS một lần.
|
|
84
|
+
Inline override sẽ drift theo thời gian.
|
|
85
|
+
- **Đừng dùng skill này cho `docs/*.md`.** Đó là agent-read; giữ MD theo principle #9.
|
|
86
|
+
- **Đừng claim "xong" mà chưa ghi file.** Deliverable CHÍNH LÀ cái file.
|
|
87
|
+
Nếu `wrap-html.mjs` lỗi, dừng — không chuyển bước.
|
|
88
|
+
- **Đừng tạo HTML cho output < 30 dòng.** Câu ngắn → trả lời inline trong chat.
|
|
89
|
+
- **Đừng dịch CSS.** Style là locale-agnostic. Chỉ `lang` attribute và body copy localize.
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/* report.css — single source of truth for deliver-html HTML output.
|
|
2
|
+
*
|
|
3
|
+
* Extracted from NEXT_ACTIONS.html / PHAN_TICH.html so every deliverable
|
|
4
|
+
* shares the same dark Github-flavored look. Edit here, every future HTML
|
|
5
|
+
* deliverable picks it up. Existing HTML files at repo root keep inline
|
|
6
|
+
* copies (self-contained shipping artefacts — see ADR-0002).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
:root {
|
|
10
|
+
--bg: #0f1419;
|
|
11
|
+
--panel: #161b22;
|
|
12
|
+
--panel2: #1c2128;
|
|
13
|
+
--border: #30363d;
|
|
14
|
+
--text: #e6edf3;
|
|
15
|
+
--muted: #8b949e;
|
|
16
|
+
--accent: #58a6ff;
|
|
17
|
+
--accent2: #a371f7;
|
|
18
|
+
--good: #3fb950;
|
|
19
|
+
--warn: #d29922;
|
|
20
|
+
--bad: #f85149;
|
|
21
|
+
--code-bg: #1c2128;
|
|
22
|
+
--mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
* { box-sizing: border-box; }
|
|
26
|
+
html, body { margin: 0; padding: 0; background: var(--bg); color: var(--text); }
|
|
27
|
+
body {
|
|
28
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
29
|
+
line-height: 1.65;
|
|
30
|
+
font-size: 16px;
|
|
31
|
+
}
|
|
32
|
+
.wrap { max-width: 1180px; margin: 0 auto; padding: 32px 24px 80px; }
|
|
33
|
+
|
|
34
|
+
/* ----- Hero ----- */
|
|
35
|
+
header.hero {
|
|
36
|
+
border: 1px solid var(--border);
|
|
37
|
+
background: linear-gradient(180deg, #161b22 0%, #0f1419 100%);
|
|
38
|
+
border-radius: 14px;
|
|
39
|
+
padding: 30px 36px;
|
|
40
|
+
margin-bottom: 28px;
|
|
41
|
+
position: relative;
|
|
42
|
+
}
|
|
43
|
+
header.hero::after {
|
|
44
|
+
content: attr(data-badge);
|
|
45
|
+
position: absolute;
|
|
46
|
+
top: 24px;
|
|
47
|
+
right: 32px;
|
|
48
|
+
background: rgba(163, 113, 247, 0.15);
|
|
49
|
+
color: var(--accent2);
|
|
50
|
+
border: 1px solid rgba(163, 113, 247, 0.5);
|
|
51
|
+
border-radius: 6px;
|
|
52
|
+
padding: 4px 12px;
|
|
53
|
+
font-size: 12px;
|
|
54
|
+
font-weight: 700;
|
|
55
|
+
letter-spacing: 0.08em;
|
|
56
|
+
}
|
|
57
|
+
header.hero[data-badge-tone="good"]::after {
|
|
58
|
+
color: var(--good);
|
|
59
|
+
background: rgba(63, 185, 80, 0.15);
|
|
60
|
+
border-color: rgba(63, 185, 80, 0.5);
|
|
61
|
+
}
|
|
62
|
+
header.hero[data-badge-tone="warn"]::after {
|
|
63
|
+
color: var(--warn);
|
|
64
|
+
background: rgba(210, 153, 34, 0.15);
|
|
65
|
+
border-color: rgba(210, 153, 34, 0.5);
|
|
66
|
+
}
|
|
67
|
+
header.hero[data-badge-tone="bad"]::after {
|
|
68
|
+
color: var(--bad);
|
|
69
|
+
background: rgba(248, 81, 73, 0.15);
|
|
70
|
+
border-color: rgba(248, 81, 73, 0.5);
|
|
71
|
+
}
|
|
72
|
+
header.hero h1 { margin: 0 0 8px; font-size: 30px; letter-spacing: -0.02em; }
|
|
73
|
+
header.hero .sub { color: var(--muted); margin: 0 0 16px; font-size: 15px; }
|
|
74
|
+
header.hero .meta {
|
|
75
|
+
display: flex;
|
|
76
|
+
gap: 22px;
|
|
77
|
+
flex-wrap: wrap;
|
|
78
|
+
font-size: 13px;
|
|
79
|
+
color: var(--muted);
|
|
80
|
+
}
|
|
81
|
+
header.hero .meta span strong { color: var(--text); }
|
|
82
|
+
header.hero .tldr {
|
|
83
|
+
margin-top: 18px;
|
|
84
|
+
padding: 14px 18px;
|
|
85
|
+
background: rgba(88, 166, 255, 0.07);
|
|
86
|
+
border: 1px solid rgba(88, 166, 255, 0.25);
|
|
87
|
+
border-radius: 8px;
|
|
88
|
+
font-size: 14.5px;
|
|
89
|
+
}
|
|
90
|
+
header.hero .tldr strong { color: var(--accent); }
|
|
91
|
+
|
|
92
|
+
/* ----- Typography ----- */
|
|
93
|
+
h2 {
|
|
94
|
+
margin-top: 44px;
|
|
95
|
+
margin-bottom: 14px;
|
|
96
|
+
padding-bottom: 8px;
|
|
97
|
+
border-bottom: 1px solid var(--border);
|
|
98
|
+
font-size: 22px;
|
|
99
|
+
}
|
|
100
|
+
h2 .num {
|
|
101
|
+
display: inline-block;
|
|
102
|
+
width: 30px;
|
|
103
|
+
height: 30px;
|
|
104
|
+
line-height: 30px;
|
|
105
|
+
text-align: center;
|
|
106
|
+
border-radius: 8px;
|
|
107
|
+
background: var(--panel2);
|
|
108
|
+
color: var(--accent);
|
|
109
|
+
font-size: 14px;
|
|
110
|
+
font-weight: 600;
|
|
111
|
+
margin-right: 10px;
|
|
112
|
+
border: 1px solid var(--border);
|
|
113
|
+
}
|
|
114
|
+
h3 { margin-top: 28px; margin-bottom: 8px; font-size: 17px; color: var(--accent); }
|
|
115
|
+
h4 { margin-top: 18px; margin-bottom: 6px; font-size: 15px; }
|
|
116
|
+
h5 { margin: 12px 0 6px; font-size: 13.5px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.06em; }
|
|
117
|
+
h6 { margin: 10px 0 4px; font-size: 12.5px; color: var(--muted); }
|
|
118
|
+
p { margin: 10px 0 14px; }
|
|
119
|
+
a { color: var(--accent); text-decoration: none; }
|
|
120
|
+
a:hover { text-decoration: underline; }
|
|
121
|
+
|
|
122
|
+
/* ----- Code ----- */
|
|
123
|
+
code, kbd {
|
|
124
|
+
font-family: var(--mono);
|
|
125
|
+
background: var(--code-bg);
|
|
126
|
+
border: 1px solid var(--border);
|
|
127
|
+
border-radius: 4px;
|
|
128
|
+
padding: 1px 6px;
|
|
129
|
+
font-size: 12.5px;
|
|
130
|
+
}
|
|
131
|
+
pre {
|
|
132
|
+
background: var(--code-bg);
|
|
133
|
+
border: 1px solid var(--border);
|
|
134
|
+
border-radius: 8px;
|
|
135
|
+
padding: 14px 16px;
|
|
136
|
+
overflow-x: auto;
|
|
137
|
+
font-family: var(--mono);
|
|
138
|
+
font-size: 12.5px;
|
|
139
|
+
line-height: 1.55;
|
|
140
|
+
}
|
|
141
|
+
pre code { background: none; border: none; padding: 0; }
|
|
142
|
+
|
|
143
|
+
/* ----- Tables ----- */
|
|
144
|
+
table { width: 100%; border-collapse: collapse; margin: 14px 0 20px; font-size: 14px; }
|
|
145
|
+
th, td { border: 1px solid var(--border); padding: 9px 12px; text-align: left; vertical-align: top; }
|
|
146
|
+
th { background: var(--panel); font-weight: 600; }
|
|
147
|
+
tr:nth-child(even) td { background: rgba(255, 255, 255, 0.02); }
|
|
148
|
+
|
|
149
|
+
/* ----- Lists ----- */
|
|
150
|
+
ul, ol { padding-left: 22px; }
|
|
151
|
+
li { margin: 5px 0; }
|
|
152
|
+
|
|
153
|
+
/* ----- Cards (border-left accent) ----- */
|
|
154
|
+
.card { border: 1px solid var(--border); border-radius: 10px; padding: 18px 22px; background: var(--panel); margin: 16px 0; }
|
|
155
|
+
.card.good { border-left: 4px solid var(--good); }
|
|
156
|
+
.card.warn { border-left: 4px solid var(--warn); }
|
|
157
|
+
.card.bad { border-left: 4px solid var(--bad); }
|
|
158
|
+
.card.info { border-left: 4px solid var(--accent); }
|
|
159
|
+
.card.next { border-left: 4px solid var(--accent2); }
|
|
160
|
+
.card h4 { margin-top: 0; }
|
|
161
|
+
|
|
162
|
+
/* ----- Pills (inline badges) ----- */
|
|
163
|
+
.pill {
|
|
164
|
+
display: inline-block;
|
|
165
|
+
padding: 2px 8px;
|
|
166
|
+
border-radius: 12px;
|
|
167
|
+
font-size: 11.5px;
|
|
168
|
+
font-weight: 600;
|
|
169
|
+
letter-spacing: 0.04em;
|
|
170
|
+
text-transform: uppercase;
|
|
171
|
+
border: 1px solid var(--border);
|
|
172
|
+
margin-right: 6px;
|
|
173
|
+
}
|
|
174
|
+
.pill.good { background: rgba(63, 185, 80, 0.12); color: var(--good); border-color: rgba(63, 185, 80, 0.5); }
|
|
175
|
+
.pill.warn { background: rgba(210, 153, 34, 0.12); color: var(--warn); border-color: rgba(210, 153, 34, 0.5); }
|
|
176
|
+
.pill.bad { background: rgba(248, 81, 73, 0.12); color: var(--bad); border-color: rgba(248, 81, 73, 0.5); }
|
|
177
|
+
.pill.info { background: rgba(88, 166, 255, 0.12); color: var(--accent); border-color: rgba(88, 166, 255, 0.5); }
|
|
178
|
+
.pill.alt { background: rgba(163, 113, 247, 0.12);color: var(--accent2); border-color: rgba(163, 113, 247, 0.5); }
|
|
179
|
+
|
|
180
|
+
/* ----- Grids ----- */
|
|
181
|
+
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin: 14px 0; }
|
|
182
|
+
.grid3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; margin: 14px 0; }
|
|
183
|
+
.grid4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin: 14px 0; }
|
|
184
|
+
@media (max-width: 800px) {
|
|
185
|
+
.grid2, .grid3, .grid4 { grid-template-columns: 1fr; }
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/* ----- Stat tiles ----- */
|
|
189
|
+
.stat { border: 1px solid var(--border); border-radius: 10px; padding: 16px 18px; background: var(--panel); }
|
|
190
|
+
.stat .num { font-size: 28px; font-weight: 700; letter-spacing: -0.02em; }
|
|
191
|
+
.stat .lbl { color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 6px; }
|
|
192
|
+
.stat.good .num { color: var(--good); }
|
|
193
|
+
.stat.warn .num { color: var(--warn); }
|
|
194
|
+
.stat.bad .num { color: var(--bad); }
|
|
195
|
+
.stat.info .num { color: var(--accent); }
|
|
196
|
+
.stat.alt .num { color: var(--accent2); }
|
|
197
|
+
|
|
198
|
+
/* ----- Blockquote ----- */
|
|
199
|
+
blockquote {
|
|
200
|
+
border-left: 4px solid var(--accent);
|
|
201
|
+
margin: 14px 0;
|
|
202
|
+
padding: 8px 16px;
|
|
203
|
+
color: var(--muted);
|
|
204
|
+
background: var(--panel2);
|
|
205
|
+
border-radius: 0 8px 8px 0;
|
|
206
|
+
}
|
|
207
|
+
blockquote p { margin: 4px 0; }
|
|
208
|
+
|
|
209
|
+
/* ----- TOC ----- */
|
|
210
|
+
.toc { background: var(--panel); border: 1px solid var(--border); border-radius: 10px; padding: 14px 22px; margin: 20px 0 28px; font-size: 14px; }
|
|
211
|
+
.toc h4 { margin: 6px 0 8px; color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: 0.08em; }
|
|
212
|
+
.toc ol { margin: 0; padding-left: 22px; columns: 2; }
|
|
213
|
+
@media (max-width: 800px) { .toc ol { columns: 1; } }
|
|
214
|
+
|
|
215
|
+
/* ----- Side-by-side diff ----- */
|
|
216
|
+
.diff-side { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin: 14px 0; }
|
|
217
|
+
.diff-side .col { border: 1px solid var(--border); border-radius: 10px; background: var(--panel); padding: 14px 16px; }
|
|
218
|
+
.diff-side .col.before { border-top: 3px solid var(--warn); }
|
|
219
|
+
.diff-side .col.after { border-top: 3px solid var(--good); }
|
|
220
|
+
.diff-side .col h5 { margin: 0 0 8px; }
|
|
221
|
+
@media (max-width: 800px) { .diff-side { grid-template-columns: 1fr; } }
|
|
222
|
+
|
|
223
|
+
/* ----- Horizontal rule ----- */
|
|
224
|
+
hr { border: 0; border-top: 1px solid var(--border); margin: 24px 0; }
|
|
225
|
+
|
|
226
|
+
/* ----- Footer ----- */
|
|
227
|
+
footer {
|
|
228
|
+
margin-top: 60px;
|
|
229
|
+
padding-top: 20px;
|
|
230
|
+
border-top: 1px solid var(--border);
|
|
231
|
+
color: var(--muted);
|
|
232
|
+
font-size: 12.5px;
|
|
233
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="{{lang}}">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
6
|
+
<title>{{title}}</title>
|
|
7
|
+
<style>
|
|
8
|
+
{{css}}
|
|
9
|
+
</style>
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<div class="wrap">
|
|
13
|
+
<header class="hero" data-badge="{{badge}}" data-badge-tone="warn">
|
|
14
|
+
<h1>{{title}}</h1>
|
|
15
|
+
<p class="sub">{{subtitle}}</p>
|
|
16
|
+
<div class="meta">
|
|
17
|
+
<span><strong>Generated:</strong> {{generatedAt}}</span>
|
|
18
|
+
<span><strong>Template:</strong> audit-report</span>
|
|
19
|
+
</div>
|
|
20
|
+
</header>
|
|
21
|
+
|
|
22
|
+
{{content}}
|
|
23
|
+
|
|
24
|
+
<footer>
|
|
25
|
+
<p>Generated by agent-harness-kit / <code>/deliver-html</code> — HTML for humans, MD for agents.</p>
|
|
26
|
+
</footer>
|
|
27
|
+
</div>
|
|
28
|
+
</body>
|
|
29
|
+
</html>
|