agent-harness-kit 0.9.0 → 0.10.1
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/package.json +1 -1
- package/src/core/upgrade.mjs +11 -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/inspect-module/scripts/module-summary.mjs +40 -8
- 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/session-end.sh.hbs +34 -5
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
"source": {
|
|
12
12
|
"source": "github",
|
|
13
13
|
"repo": "tuanle96/agent-harness-kit",
|
|
14
|
-
"ref": "v0.
|
|
14
|
+
"ref": "v0.10.1"
|
|
15
15
|
},
|
|
16
|
-
"version": "0.
|
|
16
|
+
"version": "0.10.1",
|
|
17
17
|
"description": "Solo-dev harness engineering kit — layered architecture, GC ritual, structural tests, review subagents.",
|
|
18
18
|
"category": "development",
|
|
19
19
|
"keywords": [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-harness-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "Solo-dev harness engineering kit — layered architecture, garbage-collection ritual, structural tests, review subagents. Optimized for Claude Code 2.1+.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Tuan Le"
|
package/README.md
CHANGED
|
@@ -59,8 +59,9 @@ Option B: install as a Claude Code plugin
|
|
|
59
59
|
| `/add-adr` | Add a numbered Architecture Decision Record |
|
|
60
60
|
| `/doc-drift-scan` | Find stale path/command references in `docs/` |
|
|
61
61
|
| `/debug-flow` | Run the failing flow before fixing it |
|
|
62
|
+
| `/deliver-html` | Ship an analysis/audit/plan as a self-contained HTML |
|
|
62
63
|
|
|
63
|
-
## Philosophy (
|
|
64
|
+
## Philosophy (5 axioms)
|
|
64
65
|
|
|
65
66
|
1. **CLAUDE.md is a table of contents, not an encyclopedia** (HumanLayer
|
|
66
67
|
measured ~150–200 instructions as the reliable cap; OpenAI's own root file
|
|
@@ -76,6 +77,15 @@ Option B: install as a Claude Code plugin
|
|
|
76
77
|
differentiator — see [Honest expectations](#honest-expectations).
|
|
77
78
|
4. **Garbage collection over Friday cleanup, scaled to solo** (OpenAI's
|
|
78
79
|
ritual, shrunk to top-3 fixes per week).
|
|
80
|
+
5. **HTML for human deliverables, Markdown for agent files.** Markdown is
|
|
81
|
+
the right format for files an agent reads-and-edits (CLAUDE.md, SKILL.md,
|
|
82
|
+
ADRs); HTML is the right format for documents a HUMAN reads-and-decides
|
|
83
|
+
(audit reports, analyses, plans, decision docs). A long Markdown
|
|
84
|
+
deliverable invites the human to scroll, miss the conclusion, and ask the
|
|
85
|
+
agent to clarify — burning more tokens than the HTML markup costs. The
|
|
86
|
+
`/deliver-html` skill writes self-contained HTML at repo root with a
|
|
87
|
+
shared dark-theme CSS; the rule is documented in golden principle #11 and
|
|
88
|
+
ADR-0002.
|
|
79
89
|
|
|
80
90
|
## Directory the kit drops into your repo
|
|
81
91
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-harness-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "Solo-dev harness engineering kit for Claude Code. Layered architecture, structural tests, garbage-collection ritual, review subagents — without the enterprise overhead.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/core/upgrade.mjs
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
pathForStack,
|
|
21
21
|
buildContext,
|
|
22
22
|
mergeHooksIntoSettings,
|
|
23
|
+
mergeStatusLineIntoSettings,
|
|
23
24
|
USER_OWNED_FILES as USER_OWNED_FROM_RENDERER,
|
|
24
25
|
EXEC_BITS,
|
|
25
26
|
SUPPORTED_HUMAN_LANGS,
|
|
@@ -313,6 +314,16 @@ export async function upgrade({ cwd, kitVersion, yes }) {
|
|
|
313
314
|
}
|
|
314
315
|
}
|
|
315
316
|
|
|
317
|
+
// v0.8 — statusLine injection. Mirrors renderAll's tail (render-templates.mjs:474-480).
|
|
318
|
+
// Idempotent; never clobbers a user-customised statusLine.command pointing elsewhere.
|
|
319
|
+
if (existsSync(resolve(cwd, "scripts/statusline.mjs"))) {
|
|
320
|
+
const sl = await mergeStatusLineIntoSettings(cwd);
|
|
321
|
+
if (sl.changed) {
|
|
322
|
+
lockfile.files[".claude/settings.json"] = sha256(sl.rawContent);
|
|
323
|
+
console.log(pc.dim(` ${pc.green("~")} .claude/settings.json (statusLine merged)`));
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
316
327
|
lockfile.version = kitVersion;
|
|
317
328
|
await writeFile(lockPath, JSON.stringify(lockfile, null, 2) + "\n");
|
|
318
329
|
|
|
@@ -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>
|
|
@@ -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="alt">
|
|
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> decision-doc</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>
|
|
@@ -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="good">
|
|
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> status-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>
|
|
@@ -83,17 +83,41 @@ function outboundDeps(target) {
|
|
|
83
83
|
return [...out].slice(0, 50);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
function inboundDeps(target) {
|
|
86
|
+
function inboundDeps(target, cfg) {
|
|
87
87
|
const relTarget = relative(ROOT, resolve(ROOT, target));
|
|
88
88
|
const name = relTarget.split("/").pop().replace(/\.[a-z]+$/i, "");
|
|
89
89
|
if (!name) return [];
|
|
90
90
|
const seen = new Set();
|
|
91
|
+
const patterns = [];
|
|
92
|
+
|
|
93
|
+
// Standard pattern: import/from/require referencing the directory name.
|
|
94
|
+
// Works for TS/JS/Python where the import path mirrors the dir name.
|
|
95
|
+
patterns.push(
|
|
96
|
+
new RegExp(`(import|from|require\\().*['"][^'"]*${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`),
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// Rust workspace pattern: when domain has `useIdentPattern` (e.g.
|
|
100
|
+
// "unibot_{layer}"), the crate is `use`d under its layer-derived ident
|
|
101
|
+
// — NOT the dir name. For `crates/unibot-types/` with pattern
|
|
102
|
+
// "unibot_{layer}", we should also match `use unibot_types::`. Without
|
|
103
|
+
// this branch the inbound list silently misses every workspace caller.
|
|
104
|
+
const layerInfo = whichLayer(target, cfg);
|
|
105
|
+
if (layerInfo) {
|
|
106
|
+
const domain = (cfg?.domains || []).find((d) => (d.name || "default") === layerInfo.domain);
|
|
107
|
+
if (domain?.useIdentPattern) {
|
|
108
|
+
const ident = domain.useIdentPattern.replace("{layer}", layerInfo.layer);
|
|
109
|
+
const escaped = ident.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
110
|
+
patterns.push(new RegExp(`\\b(?:pub\\s+)?use\\s+${escaped}\\b`));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
91
114
|
// Search the whole project root for references back to the target
|
|
92
115
|
// module. Filter out self-references.
|
|
93
|
-
const re
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
116
|
+
for (const re of patterns) {
|
|
117
|
+
for (const line of scan(".", re)) {
|
|
118
|
+
const m = line.match(/^([^:]+):\d+:/);
|
|
119
|
+
if (m && m[1] !== relTarget && !m[1].startsWith(`${relTarget}/`)) seen.add(m[1]);
|
|
120
|
+
}
|
|
97
121
|
}
|
|
98
122
|
return [...seen].slice(0, 30);
|
|
99
123
|
}
|
|
@@ -103,14 +127,22 @@ function readLayers() {
|
|
|
103
127
|
catch { return null; }
|
|
104
128
|
}
|
|
105
129
|
|
|
130
|
+
// Resolve the layer for a module path. Honors `layerDirPattern` on the
|
|
131
|
+
// domain so workspaces that prefix layer directories (e.g. Rust workspace
|
|
132
|
+
// `crates/unibot-types/` with `layerDirPattern: "unibot-{layer}"`) match
|
|
133
|
+
// correctly. Without this, paths with custom prefixes would silently fail
|
|
134
|
+
// to match and return layer:null — the bug that ships /inspect-module's
|
|
135
|
+
// most useful columns blank.
|
|
106
136
|
function whichLayer(target, cfg) {
|
|
107
137
|
if (!cfg?.domains) return null;
|
|
108
138
|
const rel = relative(ROOT, resolve(ROOT, target));
|
|
109
139
|
for (const d of cfg.domains) {
|
|
110
140
|
if (!d?.layers || !d.root) continue;
|
|
141
|
+
const pattern = d.layerDirPattern || "{layer}";
|
|
111
142
|
for (const layer of d.layers) {
|
|
112
|
-
const
|
|
113
|
-
|
|
143
|
+
const dirName = pattern.replace("{layer}", layer);
|
|
144
|
+
const prefix = `${d.root}/${dirName}/`;
|
|
145
|
+
if (rel.startsWith(prefix) || rel === `${d.root}/${dirName}`) {
|
|
114
146
|
return { domain: d.name || "default", layer };
|
|
115
147
|
}
|
|
116
148
|
}
|
|
@@ -135,7 +167,7 @@ function main() {
|
|
|
135
167
|
layer: whichLayer(target, cfg),
|
|
136
168
|
exports: listExports(target),
|
|
137
169
|
outbound: outboundDeps(target),
|
|
138
|
-
inbound: inboundDeps(target),
|
|
170
|
+
inbound: inboundDeps(target, cfg),
|
|
139
171
|
recent: recentCommits(target),
|
|
140
172
|
};
|
|
141
173
|
process.stdout.write(JSON.stringify(out, null, 2) + "\n");
|
|
@@ -48,6 +48,7 @@ CLAUDE.md tiny.
|
|
|
48
48
|
- `/structural-test-author <layer>` when adding a new structural rule.
|
|
49
49
|
- `/garbage-collection` every Friday or before tagging a release.
|
|
50
50
|
- `/eval-runner` before merging any change to a skill or agent file.
|
|
51
|
+
- `/deliver-html` when user wants an analysis / audit / plan / decision doc / next-actions report — HTML for humans, MD stays for agent files (principle #11).
|
|
51
52
|
|
|
52
53
|
## Subagents you should delegate to (do NOT inline these reviews)
|
|
53
54
|
|
|
@@ -47,6 +47,7 @@ luôn gọn.
|
|
|
47
47
|
- `/structural-test-author <layer>` khi thêm rule kiến trúc mới.
|
|
48
48
|
- `/garbage-collection` mỗi thứ Sáu hoặc trước khi tag release.
|
|
49
49
|
- `/eval-runner` trước khi merge bất kỳ thay đổi nào ở skill / agent file.
|
|
50
|
+
- `/deliver-html` khi user cần analysis / audit / plan / decision doc / next-actions — HTML cho human, MD giữ cho agent file (principle #11).
|
|
50
51
|
|
|
51
52
|
## Subagents nên ủy thác (KHÔNG inline review)
|
|
52
53
|
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# ADR 0002 — HTML for human deliverables, Markdown for agent files
|
|
2
|
+
|
|
3
|
+
- **Status:** accepted
|
|
4
|
+
- **Date:** {{now "yyyy-MM-dd"}}
|
|
5
|
+
- **Deciders:** project owner
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
The kit produces two distinct kinds of long-form output:
|
|
10
|
+
|
|
11
|
+
1. **Files an agent reads-and-edits.** `CLAUDE.md`, `SKILL.md`,
|
|
12
|
+
`.claude/agents/*.md`, `docs/architecture.md`, ADR notes, structural
|
|
13
|
+
reports written to stdout. These are line-oriented, diffable, and
|
|
14
|
+
typically loaded into the LLM context window.
|
|
15
|
+
2. **Documents a HUMAN reads-and-decides.** Audit reports, analyses, plans,
|
|
16
|
+
"next actions" reviews, status snapshots, decision docs. These are
|
|
17
|
+
self-contained artefacts that travel via email / Slack / PR attachments
|
|
18
|
+
and exist to surface a recommendation the human signs off on.
|
|
19
|
+
|
|
20
|
+
Anthropic's long-running-agent guide and the `agent-harness-kit` golden
|
|
21
|
+
principles both confirm Markdown is the right format for category 1: the
|
|
22
|
+
LLM tokenizes it cheaply, structural editing tools (`Edit`, `Write`) treat
|
|
23
|
+
it as native, and grep / sed / awk handle it without ceremony. Category 1
|
|
24
|
+
should remain Markdown.
|
|
25
|
+
|
|
26
|
+
Category 2 is where pain accumulates. A 500–800-line Markdown audit forces
|
|
27
|
+
the reader to:
|
|
28
|
+
|
|
29
|
+
- Scroll past sections that lack visual contrast.
|
|
30
|
+
- Render the file (terminal pager, GitHub preview, VS Code preview) before
|
|
31
|
+
it is readable at all.
|
|
32
|
+
- Skim and miss conclusions because every heading and bullet looks alike —
|
|
33
|
+
no severity badges, no border-left callouts, no grid layout.
|
|
34
|
+
|
|
35
|
+
The observed failure mode in this kit's own past sessions: the human reads
|
|
36
|
+
the Markdown report, asks the agent a follow-up that was answered in line
|
|
37
|
+
347, and burns another turn. That clarification turn costs more in tokens
|
|
38
|
+
(input replay + new output) than the +30-50% markup overhead of HTML.
|
|
39
|
+
|
|
40
|
+
## Decision
|
|
41
|
+
|
|
42
|
+
Adopt the rule documented as `docs/golden-principles.md` principle #11:
|
|
43
|
+
|
|
44
|
+
- **Human-facing deliverables ship as a single self-contained HTML file**
|
|
45
|
+
at repo root, produced by the `/deliver-html` skill against the shared
|
|
46
|
+
CSS at `.claude/skills/deliver-html/assets/report.css`.
|
|
47
|
+
- **Agent-facing files stay Markdown.** No exception.
|
|
48
|
+
|
|
49
|
+
Implementation details:
|
|
50
|
+
|
|
51
|
+
1. `/deliver-html` triggers on user intent: "analyze", "audit", "review",
|
|
52
|
+
"phân tích", "báo cáo", "plan", "proposal", "decision doc",
|
|
53
|
+
"next actions", and any similar prompt that calls for a long-form
|
|
54
|
+
deliverable.
|
|
55
|
+
2. The agent writes the body in Markdown (cheap tokens, easy reasoning).
|
|
56
|
+
The side-car `scripts/wrap-html.mjs` converts MD → HTML with three
|
|
57
|
+
templates (`decision-doc` | `audit-report` | `status-report`) and
|
|
58
|
+
inlines the shared CSS. No npm dependency: the converter is a
|
|
59
|
+
self-rolled subset (headings, paragraphs, lists, fenced code, tables,
|
|
60
|
+
blockquotes, inline formatting, links).
|
|
61
|
+
3. The Stop hook (`scripts/precompletion-checklist.sh`) emits a
|
|
62
|
+
non-blocking nudge when the user prompt matched a deliverable keyword
|
|
63
|
+
but the session produced only `.md` files at repo root.
|
|
64
|
+
4. Locale: the `<html lang="…">` attribute is read from
|
|
65
|
+
`harness.config.json` `.claudeMd.humanLanguage`. CSS is locale-agnostic.
|
|
66
|
+
|
|
67
|
+
## Consequences
|
|
68
|
+
|
|
69
|
+
Positive
|
|
70
|
+
|
|
71
|
+
- One canonical look for every audit, plan, and decision doc. Less drift
|
|
72
|
+
across reports.
|
|
73
|
+
- Human reads once, decides once. Measured benefit: each saved
|
|
74
|
+
clarification turn ≈ 2-5k output tokens + cached input replay; offsets
|
|
75
|
+
HTML markup overhead easily.
|
|
76
|
+
- Self-contained HTML — emailable, Slack-attachable, PR-comment-attachable
|
|
77
|
+
without a build step.
|
|
78
|
+
- Existing 5 HTML reports at repo root (`NEXT_ACTIONS.html`,
|
|
79
|
+
`PHAN_TICH.html`, `E2E_REPORT.html`, `E2E_CI_REPORT.html`,
|
|
80
|
+
`HOOK_AUDIT.html`) validate the pattern in practice — `/deliver-html`
|
|
81
|
+
formalises it.
|
|
82
|
+
|
|
83
|
+
Negative
|
|
84
|
+
|
|
85
|
+
- HTML output is ~30-50% larger in token count than the equivalent MD body.
|
|
86
|
+
Mitigation: the LLM writes MD; only the deterministic side-car emits
|
|
87
|
+
HTML, so the LLM token budget is not affected.
|
|
88
|
+
- HTML diffs are noisy in GitHub. Mitigation: deliverables are artefacts,
|
|
89
|
+
not source. Source-of-truth lives in the conversation / commit message;
|
|
90
|
+
the HTML file is a build output. CI can ignore `*.html` at repo root.
|
|
91
|
+
- Two formats to teach. Mitigation: the rule is "agent reads → MD,
|
|
92
|
+
human reads → HTML"; reviewers learn it on first encounter.
|
|
93
|
+
|
|
94
|
+
## Alternatives considered
|
|
95
|
+
|
|
96
|
+
- **Always Markdown.** Rejected: the failure mode this ADR closes is
|
|
97
|
+
exactly the "scrolling, miss-the-conclusion" loop that Markdown
|
|
98
|
+
invites for long deliverables. README / CHANGELOG remain MD because
|
|
99
|
+
npm/GitHub renders them and the install snippet must be copy-paste-able.
|
|
100
|
+
- **Generate PDF instead.** Rejected: solo-dev kit, no print pipeline,
|
|
101
|
+
PDFs are write-only on common review tools. HTML is editable in 90
|
|
102
|
+
seconds when a reviewer wants to amend.
|
|
103
|
+
- **Render Markdown server-side (Docusaurus / mdBook / GitHub Pages).**
|
|
104
|
+
Rejected: requires CI + deploy step for every report. HTML at repo root
|
|
105
|
+
opens with one click — zero friction.
|
|
106
|
+
- **Inline a renderer in the IDE.** Rejected: not portable when sending the
|
|
107
|
+
artefact to someone who is not running the kit.
|
|
108
|
+
|
|
109
|
+
## Out of scope
|
|
110
|
+
|
|
111
|
+
- Existing HTML reports at repo root keep their inline CSS for now.
|
|
112
|
+
Self-contained shipping artefacts trump DRY at solo scale. A future
|
|
113
|
+
cleanup may reference the shared CSS file by relative path — tracked in
|
|
114
|
+
`docs/tech-debt-tracker.md` if/when it becomes load-bearing.
|
|
115
|
+
- Localizing the CSS itself. Style is locale-agnostic by design; only the
|
|
116
|
+
`lang` attribute and body copy differ between locales.
|
|
@@ -119,6 +119,38 @@ domain. The agent reads the recommendation, invokes
|
|
|
119
119
|
`architecture-reviewer` (or documents why review is unnecessary), and the
|
|
120
120
|
loop guard (`stop_hook_active`) lets the next stop succeed.
|
|
121
121
|
|
|
122
|
+
## 11. HTML for human deliverables, Markdown for agent files
|
|
123
|
+
|
|
124
|
+
Files an agent reads-and-edits (`CLAUDE.md`, `.claude/skills/*/SKILL.md`,
|
|
125
|
+
`.claude/agents/*.md`, `docs/architecture.md`, `docs/adr/*.md`, ADR notes,
|
|
126
|
+
inline review output) stay as Markdown. Files a HUMAN reads-and-decides
|
|
127
|
+
(audit reports, analyses, plans, decision docs, next-actions reviews,
|
|
128
|
+
status snapshots) ship as self-contained HTML, written by the
|
|
129
|
+
`/deliver-html` skill against the shared dark-theme CSS.
|
|
130
|
+
|
|
131
|
+
Why: a 700-line Markdown deliverable forces the human to scroll, miss the
|
|
132
|
+
conclusion, and ask the agent to clarify — a wasted turn that costs more
|
|
133
|
+
tokens than the HTML overhead it was meant to avoid. HTML deliverables are
|
|
134
|
+
"read once, decide once." Markdown has no visual hierarchy strong enough to
|
|
135
|
+
support decision-grade reading at length.
|
|
136
|
+
Enforced by:
|
|
137
|
+
|
|
138
|
+
- `/deliver-html` skill triggers on user intent ("analyze", "audit",
|
|
139
|
+
"review", "phân tích", "báo cáo", "plan", "proposal", "decision doc",
|
|
140
|
+
"next actions") and writes `<slug>.html` at repo root.
|
|
141
|
+
- Stop hook nudge: when the prompt matches those keywords and the session
|
|
142
|
+
produced only `.md` files at repo root, the agent is reminded to invoke
|
|
143
|
+
`/deliver-html`. Non-blocking.
|
|
144
|
+
- ADR-0002 documents the trade-off (token cost +30-50% on the rendered
|
|
145
|
+
output, paid back by saving ≥1 clarification turn).
|
|
146
|
+
|
|
147
|
+
Counter-rules — when Markdown is still correct:
|
|
148
|
+
|
|
149
|
+
- `README.md`, `CHANGELOG.md` — npm/GitHub renders them; human installs/diffs.
|
|
150
|
+
- Stdout from `/review-this-pr`, `/garbage-collection`, structural reports —
|
|
151
|
+
agent consumes the output.
|
|
152
|
+
- Short summaries (< 30 lines) — answer inline, no file.
|
|
153
|
+
|
|
122
154
|
---
|
|
123
155
|
|
|
124
156
|
_Add new principles via `/structural-test-author`, which forces you to
|
|
@@ -202,6 +202,49 @@ if [ -f harness.config.json ] && have_jp && command -v git >/dev/null 2>&1; then
|
|
|
202
202
|
fi
|
|
203
203
|
fi
|
|
204
204
|
|
|
205
|
+
# Non-blocking nudge: HTML-for-humans (golden principle #11 / ADR-0002).
|
|
206
|
+
# When the session produced one or more deliverable-shaped .md files at repo
|
|
207
|
+
# root (i.e. not CLAUDE.md / AGENTS.md / README.md / CHANGELOG.md), suggest
|
|
208
|
+
# `/deliver-html`. Pure heuristic — never blocks the stop. Skip with
|
|
209
|
+
# `AHK_DISABLE_HTML_NUDGE=1`.
|
|
210
|
+
if [ "${AHK_DISABLE_HTML_NUDGE:-0}" != "1" ] && command -v git >/dev/null 2>&1; then
|
|
211
|
+
KIT_MDS="CLAUDE.md|AGENTS.md|README.md|CHANGELOG.md|LICENSE.md|CONTRIBUTING.md|CODE_OF_CONDUCT.md|SECURITY.md"
|
|
212
|
+
NEW_MD=$(
|
|
213
|
+
{
|
|
214
|
+
git ls-files --others --exclude-standard 2>/dev/null
|
|
215
|
+
git diff --name-only 2>/dev/null
|
|
216
|
+
git diff --name-only --cached 2>/dev/null
|
|
217
|
+
} \
|
|
218
|
+
| sort -u \
|
|
219
|
+
| grep -E '^[^/]+\.md$' \
|
|
220
|
+
| grep -Ev "^(${KIT_MDS})$" \
|
|
221
|
+
|| true
|
|
222
|
+
)
|
|
223
|
+
if [ -n "$NEW_MD" ]; then
|
|
224
|
+
NEW_HTML=$(
|
|
225
|
+
{
|
|
226
|
+
git ls-files --others --exclude-standard 2>/dev/null
|
|
227
|
+
git diff --name-only 2>/dev/null
|
|
228
|
+
git diff --name-only --cached 2>/dev/null
|
|
229
|
+
} \
|
|
230
|
+
| sort -u \
|
|
231
|
+
| grep -E '^[^/]+\.html$' \
|
|
232
|
+
|| true
|
|
233
|
+
)
|
|
234
|
+
if [ -z "$NEW_HTML" ]; then
|
|
235
|
+
{
|
|
236
|
+
echo
|
|
237
|
+
echo "[nudge] Repo root has new .md file(s) that look like human deliverables:"
|
|
238
|
+
echo "$NEW_MD" | sed 's/^/ - /'
|
|
239
|
+
echo
|
|
240
|
+
echo "Golden principle #11: HTML for human deliverables, MD for agent files."
|
|
241
|
+
echo "If these are reports/audits/plans/decision-docs, ship them via /deliver-html"
|
|
242
|
+
echo "instead. Non-blocking — suppress with AHK_DISABLE_HTML_NUDGE=1."
|
|
243
|
+
} >&2
|
|
244
|
+
fi
|
|
245
|
+
fi
|
|
246
|
+
fi
|
|
247
|
+
|
|
205
248
|
if [ ! -s "$TMPDIR_HOOK/failed.list" ]; then
|
|
206
249
|
exit 0
|
|
207
250
|
fi
|
|
@@ -4,13 +4,17 @@
|
|
|
4
4
|
# Claude Code docs).
|
|
5
5
|
#
|
|
6
6
|
# Output line shape:
|
|
7
|
-
# YYYY-MM-DD HH:MM | session_end | <reason> | <branch> | <sha>
|
|
7
|
+
# YYYY-MM-DD HH:MM | session_end | <reason> | <branch> | <sha> | <session_id>
|
|
8
8
|
#
|
|
9
9
|
# Example:
|
|
10
|
-
# 2026-05-16 19:00 | session_end | clear | main | abc1234
|
|
10
|
+
# 2026-05-16 19:00 | session_end | clear | main | abc1234 | sess_abc123
|
|
11
11
|
#
|
|
12
12
|
# Reasons (per Claude Code docs): clear, resume, logout, prompt_input_exit,
|
|
13
13
|
# bypass_permissions_disabled, other.
|
|
14
|
+
#
|
|
15
|
+
# Dedup: a line is only appended when (session_id, reason) differs from the
|
|
16
|
+
# most recent matching entry in PROGRESS.md. Prevents the duplicate-spam
|
|
17
|
+
# bug where Claude Code fires SessionEnd more than once on a single teardown.
|
|
14
18
|
set -eo pipefail
|
|
15
19
|
|
|
16
20
|
INPUT=$(cat)
|
|
@@ -30,10 +34,21 @@ jp() {
|
|
|
30
34
|
fi
|
|
31
35
|
}
|
|
32
36
|
|
|
33
|
-
REASON="
|
|
37
|
+
REASON=""
|
|
38
|
+
SESSION_ID=""
|
|
34
39
|
if have_jp; then
|
|
35
|
-
|
|
40
|
+
# Fallback chain: prefer .end_reason (current Claude Code key), accept
|
|
41
|
+
# .reason as a legacy synonym. Done as two separate jp calls because the
|
|
42
|
+
# Node-fallback (json-pick.mjs) only supports a single `// default` per
|
|
43
|
+
# expression — chaining `// .reason //` would parse-fail there.
|
|
44
|
+
REASON=$(echo "$INPUT" | jp '.end_reason // ""' 2>/dev/null || echo "")
|
|
45
|
+
if [ -z "$REASON" ] || [ "$REASON" = "null" ]; then
|
|
46
|
+
REASON=$(echo "$INPUT" | jp '.reason // ""' 2>/dev/null || echo "")
|
|
47
|
+
fi
|
|
48
|
+
SESSION_ID=$(echo "$INPUT" | jp '.session_id // ""' 2>/dev/null || echo "")
|
|
49
|
+
[ "$SESSION_ID" = "null" ] && SESSION_ID=""
|
|
36
50
|
fi
|
|
51
|
+
[ -z "$REASON" ] || [ "$REASON" = "null" ] && REASON="unknown"
|
|
37
52
|
|
|
38
53
|
BR="(no-git)"
|
|
39
54
|
SHA="(no-git)"
|
|
@@ -44,7 +59,21 @@ fi
|
|
|
44
59
|
|
|
45
60
|
mkdir -p .harness
|
|
46
61
|
TS=$(date +"%Y-%m-%d %H:%M")
|
|
47
|
-
|
|
62
|
+
LINE="$TS | session_end | $REASON | $BR | $SHA | $SESSION_ID"
|
|
63
|
+
|
|
64
|
+
# Idempotency guard: when the SessionEnd hook fires twice on the same
|
|
65
|
+
# teardown (Claude Code sometimes emits both `clear` and a follow-up
|
|
66
|
+
# `prompt_input_exit`, or repeats the same reason), drop the duplicate
|
|
67
|
+
# rather than spamming PROGRESS.md. Match on (session_id, reason): if
|
|
68
|
+
# both are present in the most recent entry for this session, skip.
|
|
69
|
+
DEDUP_KEY="| $REASON | $BR | $SHA | $SESSION_ID"
|
|
70
|
+
if [ -f .harness/PROGRESS.md ] && \
|
|
71
|
+
[ -n "$SESSION_ID" ] && \
|
|
72
|
+
tail -n 5 .harness/PROGRESS.md 2>/dev/null | grep -qF "$DEDUP_KEY"; then
|
|
73
|
+
: # duplicate within the last 5 entries — skip silently
|
|
74
|
+
else
|
|
75
|
+
echo "$LINE" >> .harness/PROGRESS.md
|
|
76
|
+
fi
|
|
48
77
|
|
|
49
78
|
# Rollup side-car — writes a JSONL record to .harness/telemetry.jsonl.
|
|
50
79
|
# Best-effort: never blocks the cleanup-only SessionEnd contract.
|