claude-dev-env 1.66.2 → 1.67.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/package.json +1 -1
- package/skills/anthropic-plan/SKILL.md +10 -2
- package/skills/anthropic-plan/scripts/anthropic_plan_scripts_constants/validate_packet_constants.py +1 -0
- package/skills/anthropic-plan/scripts/test_validate_packet.py +50 -0
- package/skills/anthropic-plan/scripts/validate_packet.py +20 -0
- package/skills/anthropic-plan/templates/reuse-audit.md +7 -0
- package/skills/anthropic-plan/templates/visual-plan.template.html +572 -0
- package/skills/anthropic-plan/workflow/plan-packet.contract.test.mjs +67 -0
- package/skills/anthropic-plan/workflow/plan-packet.mjs +115 -5
package/package.json
CHANGED
|
@@ -38,8 +38,10 @@ The workflow handles the full planning loop:
|
|
|
38
38
|
5. Run `scripts/validate_packet.py`.
|
|
39
39
|
6. Spawn `plan-packet-validator` in fresh context.
|
|
40
40
|
7. Repair packet findings up to the workflow cap.
|
|
41
|
-
8.
|
|
42
|
-
9.
|
|
41
|
+
8. Run the reuse audit: search the codebase for existing equivalents of each new file/symbol the packet introduces, write `validation/reuse-audit.md`, and gate approval on any unjustified reproduction.
|
|
42
|
+
9. Build a single-file offline visual HTML of the finished packet from `templates/visual-plan.template.html` and write it beside the packet as `visual-plan.html`.
|
|
43
|
+
10. Return packet path, validation state, and findings.
|
|
44
|
+
11. Stop before implementation.
|
|
43
45
|
|
|
44
46
|
## Packet Shape
|
|
45
47
|
|
|
@@ -63,6 +65,12 @@ The deterministic validator checks required files, placeholders, `Open Questions
|
|
|
63
65
|
|
|
64
66
|
The `plan-packet-validator` agent checks source accuracy, scope, enough implementation detail for a blind build agent, real TDD order, invented APIs or commands, and end-to-end acceptance criteria.
|
|
65
67
|
|
|
68
|
+
The reuse audit writes `validation/reuse-audit.md` with a per-item verdict for every new file or symbol the packet introduces and gates approval on any unjustified reproduction of existing behavior.
|
|
69
|
+
|
|
70
|
+
## Visualize
|
|
71
|
+
|
|
72
|
+
After validation and before approval, the workflow builds a single-file offline visual HTML of the finished packet from `templates/visual-plan.template.html` and writes it beside the packet as `visual-plan.html`. The file inlines all CSS and JavaScript and references no external assets, so it opens offline. It renders the packet as diagrams and compact cards — a stat hero, scenario strips, is/isn't cards, edit-recipe step sequences for the file-by-file change, reuse-audit verdict badges, and a checklist — rather than reproducing the markdown. Every label is written for the reviewer: the diagram says what each step does in plain words and leaves out code symbols (function names, selectors, test names), while each touched file keeps its repo-relative path dimmed for the build agent. Because it is generated after validation, `visual-plan.html` is not a required packet file.
|
|
73
|
+
|
|
66
74
|
## Rules
|
|
67
75
|
|
|
68
76
|
- Write docs only.
|
package/skills/anthropic-plan/scripts/anthropic_plan_scripts_constants/validate_packet_constants.py
CHANGED
|
@@ -25,6 +25,7 @@ ALL_REQUIRED_RELATIVE_PATHS: tuple[str, ...] = (
|
|
|
25
25
|
"validation/validator-report.md",
|
|
26
26
|
"validation/deterministic-checks.md",
|
|
27
27
|
"validation/unresolved-risks.md",
|
|
28
|
+
"validation/reuse-audit.md",
|
|
28
29
|
"handoff/build-prompt.md",
|
|
29
30
|
"handoff/review-prompt.md",
|
|
30
31
|
"handoff/verification-commands.md",
|
|
@@ -76,6 +76,14 @@ def valid_markdown_for(relative_path: str) -> str:
|
|
|
76
76
|
"Use only this packet. Read README.md, then context/source-map.md, then implementation/steps.md. "
|
|
77
77
|
"Do not rely on prior chat history."
|
|
78
78
|
)
|
|
79
|
+
if relative_path == "validation/reuse-audit.md":
|
|
80
|
+
return (
|
|
81
|
+
"# Reuse Audit\n\n"
|
|
82
|
+
"| Item | Kind | Verdict | Searched | Found | Decision | Evidence |\n"
|
|
83
|
+
"|---|---|---|---|---|---|---|\n"
|
|
84
|
+
"| authenticate_user | helper | reused | shared_utils/auth | existing public helper | call it directly | src/auth.py:12 |\n\n"
|
|
85
|
+
"Summary: reused 1.\n"
|
|
86
|
+
)
|
|
79
87
|
return f"# {relative_path}\n\nGrounded implementation detail for this packet file.\n"
|
|
80
88
|
|
|
81
89
|
|
|
@@ -403,3 +411,45 @@ def test_bullet_list_steps_naming_test_contract_passes(tmp_path: Path) -> None:
|
|
|
403
411
|
validator_run = run_validator(packet_directory)
|
|
404
412
|
|
|
405
413
|
assert validator_run.returncode == 0, validator_run.stderr
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def test_missing_reuse_audit_file_fails(tmp_path: Path) -> None:
|
|
417
|
+
packet_directory = tmp_path / "docs" / "plans" / "add-login"
|
|
418
|
+
write_valid_packet(packet_directory)
|
|
419
|
+
(packet_directory / "validation" / "reuse-audit.md").unlink()
|
|
420
|
+
|
|
421
|
+
validator_run = run_validator(packet_directory)
|
|
422
|
+
|
|
423
|
+
assert validator_run.returncode == 2
|
|
424
|
+
assert "missing required file: validation/reuse-audit.md" in validator_run.stderr
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def test_reuse_audit_without_verdict_keyword_fails(tmp_path: Path) -> None:
|
|
428
|
+
packet_directory = tmp_path / "docs" / "plans" / "add-login"
|
|
429
|
+
write_valid_packet(packet_directory)
|
|
430
|
+
(packet_directory / "validation" / "reuse-audit.md").write_text(
|
|
431
|
+
"# Reuse Audit\n\nNo audit was performed for this packet.\n",
|
|
432
|
+
encoding="utf-8",
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
validator_run = run_validator(packet_directory)
|
|
436
|
+
|
|
437
|
+
assert validator_run.returncode == 2
|
|
438
|
+
assert "reuse-audit.md must record a reuse verdict for each new item" in validator_run.stderr
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def test_reuse_audit_with_verdict_keyword_passes(tmp_path: Path) -> None:
|
|
442
|
+
packet_directory = tmp_path / "docs" / "plans" / "add-login"
|
|
443
|
+
write_valid_packet(packet_directory)
|
|
444
|
+
(packet_directory / "validation" / "reuse-audit.md").write_text(
|
|
445
|
+
"# Reuse Audit\n\n"
|
|
446
|
+
"| Item | Kind | Verdict | Searched | Found | Decision | Evidence |\n"
|
|
447
|
+
"|---|---|---|---|---|---|---|\n"
|
|
448
|
+
"| open_postgres_connection | helper | reused | shared_utils/theme_db | existing public helper | call it | shared_utils/theme_db/connection.py:20 |\n\n"
|
|
449
|
+
"Summary: reused 1.\n",
|
|
450
|
+
encoding="utf-8",
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
validator_run = run_validator(packet_directory)
|
|
454
|
+
|
|
455
|
+
assert validator_run.returncode == 0, validator_run.stderr
|
|
@@ -52,6 +52,7 @@ def validate_packet(packet_directory: Path) -> list[str]:
|
|
|
52
52
|
all_errors.extend(packet_json_errors(packet_directory))
|
|
53
53
|
all_errors.extend(source_map_errors(packet_directory))
|
|
54
54
|
all_errors.extend(tdd_plan_errors(packet_directory))
|
|
55
|
+
all_errors.extend(reuse_audit_errors(packet_directory))
|
|
55
56
|
all_errors.extend(implementation_step_errors(packet_directory))
|
|
56
57
|
all_errors.extend(build_prompt_errors(packet_directory))
|
|
57
58
|
return all_errors
|
|
@@ -185,6 +186,25 @@ def tdd_plan_errors(packet_directory: Path) -> list[str]:
|
|
|
185
186
|
return []
|
|
186
187
|
|
|
187
188
|
|
|
189
|
+
def reuse_audit_errors(packet_directory: Path) -> list[str]:
|
|
190
|
+
"""Return errors for a reuse audit that records no per-item verdict.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
packet_directory: Directory that should contain validation/reuse-audit.md.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
An error string when the reuse audit names no reuse verdict, else an empty list.
|
|
197
|
+
"""
|
|
198
|
+
reuse_audit_file = packet_directory / "validation" / "reuse-audit.md"
|
|
199
|
+
if not reuse_audit_file.is_file():
|
|
200
|
+
return []
|
|
201
|
+
reuse_audit_text = reuse_audit_file.read_text(encoding="utf-8").lower()
|
|
202
|
+
verdict_keywords = ("reused", "extract", "justified", "config-local", "reproduction")
|
|
203
|
+
if not any(each_keyword in reuse_audit_text for each_keyword in verdict_keywords):
|
|
204
|
+
return ["reuse-audit.md must record a reuse verdict for each new item"]
|
|
205
|
+
return []
|
|
206
|
+
|
|
207
|
+
|
|
188
208
|
def implementation_step_errors(packet_directory: Path) -> list[str]:
|
|
189
209
|
"""Return errors for implementation steps without test coverage.
|
|
190
210
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Reuse Audit
|
|
2
|
+
|
|
3
|
+
| Item | Kind | Verdict | Searched | Found | Decision | Evidence |
|
|
4
|
+
|---|---|---|---|---|---|---|
|
|
5
|
+
| _send_failure_alert | helper | reused | shared_utils/alerts | existing public helper covers the alert send | call the existing helper | shared_utils/alerts/notify.py:48 |
|
|
6
|
+
|
|
7
|
+
Summary: reused 1, extract-to-shared 0, new-justified 0, config-local 0, unjustified-reproduction 0.
|
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Visual Plan — Component Gallery Template</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root{
|
|
9
|
+
--bg:#0a0e16; --panel:#121a28; --panel2:#0f1622; --line:#223049;
|
|
10
|
+
--ink:#e6edf7; --muted:#93a4bd; --faint:#5f7390;
|
|
11
|
+
--fail:#ef4444; --sent:#22c55e; --skip:#647892; --amber:#f59e0b;
|
|
12
|
+
--mod:#a78bfa; --volt:#4cc9f0; --volt2:#4361ee;
|
|
13
|
+
--shadow:0 10px 30px rgba(0,0,0,.45);
|
|
14
|
+
}
|
|
15
|
+
*{box-sizing:border-box}
|
|
16
|
+
html,body{margin:0;padding:0}
|
|
17
|
+
body{
|
|
18
|
+
background:
|
|
19
|
+
radial-gradient(1100px 520px at 80% -10%, rgba(67,97,238,.16), transparent 60%),
|
|
20
|
+
radial-gradient(900px 480px at 0% 0%, rgba(76,201,240,.12), transparent 55%),
|
|
21
|
+
var(--bg);
|
|
22
|
+
color:var(--ink);
|
|
23
|
+
font:15px/1.5 ui-sans-serif,system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
|
|
24
|
+
-webkit-font-smoothing:antialiased;
|
|
25
|
+
}
|
|
26
|
+
.wrap{max-width:1080px;margin:0 auto;padding:34px 22px 90px}
|
|
27
|
+
.mono{font-family:ui-monospace,"Cascadia Code","SF Mono",Consolas,monospace}
|
|
28
|
+
|
|
29
|
+
/* ---------- header ---------- */
|
|
30
|
+
.kicker{display:inline-flex;align-items:center;gap:8px;font-size:12px;letter-spacing:.14em;
|
|
31
|
+
text-transform:uppercase;color:var(--volt);font-weight:700}
|
|
32
|
+
.kicker .dot{width:8px;height:8px;border-radius:50%;background:var(--volt);
|
|
33
|
+
box-shadow:0 0 12px var(--volt)}
|
|
34
|
+
h1{font-size:clamp(26px,4vw,40px);line-height:1.08;margin:14px 0 8px;letter-spacing:-.5px}
|
|
35
|
+
.sub{color:var(--muted);max-width:680px;font-size:16px}
|
|
36
|
+
.sub b{color:var(--ink)}
|
|
37
|
+
|
|
38
|
+
/* ---------- hero stat ---------- */
|
|
39
|
+
.hero{display:grid;grid-template-columns:1fr auto 1fr;gap:18px;align-items:stretch;
|
|
40
|
+
margin:26px 0 8px}
|
|
41
|
+
.stat{background:linear-gradient(180deg,var(--panel),var(--panel2));border:1px solid var(--line);
|
|
42
|
+
border-radius:16px;padding:20px 22px;box-shadow:var(--shadow)}
|
|
43
|
+
.stat .lab{font-size:12px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);font-weight:700}
|
|
44
|
+
.stat .big{font-size:clamp(40px,7vw,68px);font-weight:800;line-height:1;margin-top:8px;letter-spacing:-2px}
|
|
45
|
+
.stat .cap{color:var(--faint);font-size:13px;margin-top:6px}
|
|
46
|
+
.stat.bad{border-color:rgba(239,68,68,.4)}
|
|
47
|
+
.stat.bad .big{color:var(--fail)}
|
|
48
|
+
.stat.good{border-color:rgba(76,201,240,.45)}
|
|
49
|
+
.stat.good .big{color:var(--volt)}
|
|
50
|
+
.arrowcol{display:flex;align-items:center;justify-content:center;color:var(--faint);font-size:30px}
|
|
51
|
+
|
|
52
|
+
/* ---------- section ---------- */
|
|
53
|
+
section{margin-top:40px}
|
|
54
|
+
.h{display:flex;align-items:baseline;gap:12px;margin-bottom:6px}
|
|
55
|
+
.h .n{font-family:ui-monospace,monospace;color:var(--volt);font-weight:700;font-size:14px;
|
|
56
|
+
border:1px solid var(--line);border-radius:8px;padding:2px 9px;background:var(--panel2)}
|
|
57
|
+
.h h2{font-size:21px;margin:0;letter-spacing:-.3px}
|
|
58
|
+
.h .lead{color:var(--muted);font-size:14px;margin:0 0 0 auto;text-align:right;max-width:420px}
|
|
59
|
+
.card{background:linear-gradient(180deg,var(--panel),var(--panel2));border:1px solid var(--line);
|
|
60
|
+
border-radius:16px;padding:22px;box-shadow:var(--shadow);margin-top:14px}
|
|
61
|
+
|
|
62
|
+
/* ---------- cascade bar ---------- */
|
|
63
|
+
.cascade{display:flex;gap:2px;height:64px;align-items:flex-end;margin:6px 0 4px;flex-wrap:nowrap;overflow:hidden}
|
|
64
|
+
.cascade i{flex:1;background:linear-gradient(180deg,#ff6a6a,var(--fail));border-radius:2px 2px 0 0;
|
|
65
|
+
height:100%;opacity:.9;animation:rise .5s ease backwards}
|
|
66
|
+
.cascade i.ok{background:linear-gradient(180deg,#56e08a,var(--sent));height:62%}
|
|
67
|
+
@keyframes rise{from{transform:scaleY(.1);opacity:.2}to{transform:scaleY(1)}}
|
|
68
|
+
.meta{display:flex;gap:18px;flex-wrap:wrap;color:var(--muted);font-size:13px;margin-top:14px}
|
|
69
|
+
.pill{display:inline-flex;align-items:center;gap:7px;background:var(--panel2);border:1px solid var(--line);
|
|
70
|
+
border-radius:999px;padding:5px 12px;font-size:12.5px}
|
|
71
|
+
.pill .k{color:var(--faint)}
|
|
72
|
+
.pill .v{font-weight:700}
|
|
73
|
+
.pill .v.r{color:var(--fail)} .pill .v.g{color:var(--sent)} .pill .v.s{color:var(--skip)}
|
|
74
|
+
.rootcause{display:flex;gap:12px;align-items:center;margin-top:16px;padding:12px 14px;
|
|
75
|
+
border:1px dashed rgba(245,158,11,.45);background:rgba(245,158,11,.07);border-radius:12px;color:#f8d99a}
|
|
76
|
+
.rootcause b{color:#ffd27a}
|
|
77
|
+
|
|
78
|
+
/* ---------- the rule banner ---------- */
|
|
79
|
+
.rule{display:grid;grid-template-columns:auto 1fr;gap:18px;align-items:center;
|
|
80
|
+
border:1px solid rgba(76,201,240,.4);border-radius:18px;padding:22px 24px;
|
|
81
|
+
background:linear-gradient(110deg,rgba(67,97,238,.16),rgba(76,201,240,.07));box-shadow:var(--shadow)}
|
|
82
|
+
.bolt{font-size:40px;filter:drop-shadow(0 0 16px rgba(76,201,240,.6))}
|
|
83
|
+
.rule .txt{font-size:clamp(18px,2.6vw,26px);font-weight:700;line-height:1.25;letter-spacing:-.3px}
|
|
84
|
+
.rule .txt .hl{color:var(--volt)}
|
|
85
|
+
.rule .txt small{display:block;color:var(--muted);font-weight:500;font-size:13px;margin-top:8px;letter-spacing:0}
|
|
86
|
+
|
|
87
|
+
/* ---------- scenarios / row strips ---------- */
|
|
88
|
+
.scn{margin-top:16px;border:1px solid var(--line);border-radius:14px;padding:16px 18px;background:var(--panel2)}
|
|
89
|
+
.scn.headline{border-color:rgba(76,201,240,.4);background:linear-gradient(180deg,rgba(76,201,240,.06),var(--panel2))}
|
|
90
|
+
.scn-top{display:flex;align-items:center;gap:10px;margin-bottom:14px;flex-wrap:wrap}
|
|
91
|
+
.scn-title{font-weight:700;font-size:15px}
|
|
92
|
+
.tag{font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;border-radius:999px;padding:3px 10px}
|
|
93
|
+
.tag.stop{color:#ffd0d0;background:rgba(239,68,68,.16);border:1px solid rgba(239,68,68,.4)}
|
|
94
|
+
.tag.safe{color:#bff3d2;background:rgba(34,197,94,.14);border:1px solid rgba(34,197,94,.38)}
|
|
95
|
+
.tag.drain{color:#cfe0f5;background:rgba(100,120,146,.18);border:1px solid var(--line)}
|
|
96
|
+
.scn-note{margin-left:auto;color:var(--muted);font-size:13px}
|
|
97
|
+
|
|
98
|
+
.strip{display:flex;gap:10px;align-items:stretch;flex-wrap:wrap}
|
|
99
|
+
.row{width:78px;border-radius:12px;border:1px solid var(--line);background:var(--panel);
|
|
100
|
+
padding:10px 8px 8px;text-align:center;position:relative}
|
|
101
|
+
.row .ic{width:30px;height:30px;border-radius:9px;margin:2px auto 7px;display:flex;align-items:center;
|
|
102
|
+
justify-content:center;font-size:17px;font-weight:800}
|
|
103
|
+
.row.fail .ic{background:rgba(239,68,68,.16);color:var(--fail);border:1px solid rgba(239,68,68,.4)}
|
|
104
|
+
.row.sent .ic{background:rgba(34,197,94,.16);color:var(--sent);border:1px solid rgba(34,197,94,.4)}
|
|
105
|
+
.row.skip .ic{background:rgba(100,120,146,.18);color:#aebacd;border:1px solid var(--line)}
|
|
106
|
+
.row .rl{font-size:10.5px;color:var(--faint);text-transform:uppercase;letter-spacing:.05em}
|
|
107
|
+
.row .cnt{margin-top:8px;font-size:11px;color:var(--muted)}
|
|
108
|
+
.pips{display:flex;gap:4px;justify-content:center;margin-top:5px}
|
|
109
|
+
.pips u{width:9px;height:9px;border-radius:50%;background:#26334a;display:block}
|
|
110
|
+
.pips u.on{background:var(--fail);box-shadow:0 0 7px rgba(239,68,68,.7)}
|
|
111
|
+
.row.ghost{opacity:.32;filter:grayscale(.4)}
|
|
112
|
+
.row.ghost .ic{border-style:dashed}
|
|
113
|
+
.ghostlab{font-size:10px;color:var(--faint);margin-top:6px;font-style:italic}
|
|
114
|
+
|
|
115
|
+
.trip{display:flex;flex-direction:column;align-items:center;justify-content:center;width:96px;
|
|
116
|
+
border-radius:12px;border:1px solid rgba(239,68,68,.55);
|
|
117
|
+
background:linear-gradient(180deg,rgba(239,68,68,.22),rgba(239,68,68,.08))}
|
|
118
|
+
.trip .b{font-size:22px}
|
|
119
|
+
.trip .t{font-weight:800;color:#ff8c8c;font-size:13px;letter-spacing:.04em}
|
|
120
|
+
.trip .e{font-size:10.5px;color:#ffb3b3;margin-top:2px}
|
|
121
|
+
.arrow{display:flex;align-items:center;color:var(--faint);font-size:18px;padding:0 1px}
|
|
122
|
+
|
|
123
|
+
.legend{display:flex;gap:16px;flex-wrap:wrap;margin-top:8px;color:var(--muted);font-size:12.5px}
|
|
124
|
+
.legend span{display:inline-flex;align-items:center;gap:7px}
|
|
125
|
+
.sw{width:13px;height:13px;border-radius:4px;display:inline-block}
|
|
126
|
+
.sw.f{background:var(--fail)} .sw.s{background:var(--sent)} .sw.k{background:var(--skip)}
|
|
127
|
+
.sw.m{background:var(--mod)} .sw.a{background:var(--amber)}
|
|
128
|
+
|
|
129
|
+
/* ---------- two-col ---------- */
|
|
130
|
+
.cols{display:grid;grid-template-columns:1fr 1fr;gap:16px}
|
|
131
|
+
@media(max-width:760px){.cols{grid-template-columns:1fr}.hero{grid-template-columns:1fr}.arrowcol{transform:rotate(90deg)}}
|
|
132
|
+
.mean{border-radius:14px;padding:18px;border:1px solid var(--line);background:var(--panel2)}
|
|
133
|
+
.mean.is{border-color:rgba(34,197,94,.4);background:linear-gradient(180deg,rgba(34,197,94,.07),var(--panel2))}
|
|
134
|
+
.mean.isnt{border-color:rgba(239,68,68,.32)}
|
|
135
|
+
.mean .lab{font-size:12px;letter-spacing:.1em;text-transform:uppercase;font-weight:700;margin-bottom:10px}
|
|
136
|
+
.mean.is .lab{color:var(--sent)} .mean.isnt .lab{color:#ff9c9c}
|
|
137
|
+
.mean ul{margin:0;padding-left:0;list-style:none}
|
|
138
|
+
.mean li{display:flex;gap:9px;align-items:flex-start;margin:7px 0;font-size:14px}
|
|
139
|
+
.mean li .m{flex:0 0 auto;font-weight:800}
|
|
140
|
+
.mean.is li .m{color:var(--sent)} .mean.isnt li .m{color:var(--fail)}
|
|
141
|
+
.mean.isnt li{color:var(--muted);text-decoration:line-through;text-decoration-color:rgba(239,68,68,.5)}
|
|
142
|
+
|
|
143
|
+
/* ---------- edit-recipe (the change, in human steps) ---------- */
|
|
144
|
+
.reclegend{display:flex;gap:20px;flex-wrap:wrap;margin:14px 0 0;color:var(--muted);font-size:13.5px}
|
|
145
|
+
.reclegend span{display:inline-flex;align-items:center;gap:8px}
|
|
146
|
+
.recipe{border:1px solid var(--line);border-radius:16px;background:linear-gradient(180deg,var(--panel),var(--panel2));
|
|
147
|
+
box-shadow:var(--shadow);padding:18px 20px;margin-top:14px}
|
|
148
|
+
.rhead{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
|
|
149
|
+
.rhead .what{font-size:17px;color:var(--ink);font-weight:700;letter-spacing:-.2px}
|
|
150
|
+
.rhead .sz{margin-left:auto;font-size:12.5px;color:var(--faint);border:1px solid var(--line);border-radius:7px;
|
|
151
|
+
padding:3px 9px;background:var(--panel);white-space:nowrap}
|
|
152
|
+
.rpath{font-family:ui-monospace,monospace;font-size:12.5px;color:var(--faint);opacity:.9;margin-top:6px;word-break:break-all}
|
|
153
|
+
.oprow{display:flex;gap:9px;align-items:stretch;flex-wrap:wrap;margin-top:16px}
|
|
154
|
+
.opnode{flex:1 1 0;min-width:166px;border:1px solid var(--line);border-radius:12px;background:var(--panel2);
|
|
155
|
+
padding:14px 14px 13px;position:relative}
|
|
156
|
+
.opnode .ix{width:24px;height:24px;border-radius:7px;display:flex;align-items:center;justify-content:center;
|
|
157
|
+
font-size:13px;font-weight:800;margin-bottom:10px;border:1px solid var(--line)}
|
|
158
|
+
.opnode .op{font-size:15px;color:var(--ink);font-weight:600;line-height:1.32}
|
|
159
|
+
.opnode .sub{font-size:12.5px;color:var(--muted);margin-top:5px;line-height:1.4}
|
|
160
|
+
.opnode .tg{position:absolute;top:13px;right:13px;font-size:10px;font-weight:800;letter-spacing:.05em;
|
|
161
|
+
text-transform:uppercase;border-radius:999px;padding:3px 8px}
|
|
162
|
+
.opnode.reused{border-color:rgba(34,197,94,.4)}
|
|
163
|
+
.opnode.reused .ix{color:var(--sent);background:rgba(34,197,94,.14);border-color:rgba(34,197,94,.4)}
|
|
164
|
+
.opnode.reused .tg{color:#bff3d2;background:rgba(34,197,94,.14);border:1px solid rgba(34,197,94,.4)}
|
|
165
|
+
.opnode.mod{border-color:rgba(167,139,250,.5)}
|
|
166
|
+
.opnode.mod .ix{color:var(--mod);background:rgba(167,139,250,.16);border-color:rgba(167,139,250,.5)}
|
|
167
|
+
.opnode.mod .tg{color:#d8c9ff;background:rgba(167,139,250,.16);border:1px solid rgba(167,139,250,.5)}
|
|
168
|
+
.opnode.new{border-color:rgba(245,158,11,.45)}
|
|
169
|
+
.opnode.new .ix{color:var(--amber);background:rgba(245,158,11,.14);border-color:rgba(245,158,11,.45)}
|
|
170
|
+
.opnode.new .tg{color:#ffe2b0;background:rgba(245,158,11,.14);border:1px solid rgba(245,158,11,.45)}
|
|
171
|
+
.opconn{display:flex;align-items:center;color:var(--faint);font-size:18px}
|
|
172
|
+
@media(max-width:760px){.opconn{display:none}.opnode{flex:1 1 100%}}
|
|
173
|
+
.alsoadd{display:flex;align-items:center;gap:11px;flex-wrap:wrap;margin-top:14px;padding-top:13px;
|
|
174
|
+
border-top:1px solid var(--line);font-size:13.5px;color:var(--muted)}
|
|
175
|
+
.alsoadd .plus{flex:0 0 auto;width:21px;height:21px;border-radius:6px;display:flex;align-items:center;justify-content:center;
|
|
176
|
+
font-weight:800;font-size:14px;color:var(--amber);background:rgba(245,158,11,.14);border:1px solid rgba(245,158,11,.45)}
|
|
177
|
+
.alsoadd .txt b{color:var(--ink);font-weight:600}
|
|
178
|
+
.alsoadd .ap{font-family:ui-monospace,monospace;font-size:11.5px;color:var(--faint);opacity:.8;word-break:break-all}
|
|
179
|
+
.alsoadd .nt{margin-left:auto;font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:.05em;border-radius:999px;
|
|
180
|
+
padding:3px 8px;color:#ffe2b0;background:rgba(245,158,11,.16);border:1px solid rgba(245,158,11,.45);white-space:nowrap}
|
|
181
|
+
.nochange{margin-top:16px;color:var(--muted);font-size:13px;display:flex;gap:10px;flex-wrap:wrap;align-items:center}
|
|
182
|
+
.nochange .lk{font-family:ui-monospace,monospace;font-size:12px;color:var(--faint);border:1px solid var(--line);
|
|
183
|
+
border-radius:7px;padding:3px 8px;background:var(--panel)}
|
|
184
|
+
.nochange .lk::before{content:"🔒 ";}
|
|
185
|
+
|
|
186
|
+
/* ---------- tests ---------- */
|
|
187
|
+
.tests{display:grid;grid-template-columns:1fr 1fr;gap:14px}
|
|
188
|
+
@media(max-width:760px){.tests{grid-template-columns:1fr}}
|
|
189
|
+
.test{border:1px solid var(--line);border-radius:14px;padding:16px;background:var(--panel2);position:relative}
|
|
190
|
+
.test .rid{position:absolute;top:14px;right:14px;font-size:10px;font-weight:800;letter-spacing:.1em;
|
|
191
|
+
color:#ff9c9c;background:rgba(239,68,68,.14);border:1px solid rgba(239,68,68,.35);border-radius:6px;padding:2px 7px}
|
|
192
|
+
.test .tn{font-size:14.5px;color:var(--ink);font-weight:600;padding-right:54px;line-height:1.35}
|
|
193
|
+
.test .pin{color:var(--muted);font-size:13px;margin-top:10px}
|
|
194
|
+
.test .pin b{color:var(--volt)}
|
|
195
|
+
|
|
196
|
+
/* ---------- acceptance ---------- */
|
|
197
|
+
.acc{display:grid;grid-template-columns:1fr 1fr;gap:9px 18px}
|
|
198
|
+
@media(max-width:760px){.acc{grid-template-columns:1fr}}
|
|
199
|
+
.ck{display:flex;gap:10px;align-items:flex-start;font-size:13.5px;color:var(--muted)}
|
|
200
|
+
.ck .b{flex:0 0 auto;width:18px;height:18px;border-radius:5px;background:rgba(34,197,94,.16);
|
|
201
|
+
border:1px solid rgba(34,197,94,.45);color:var(--sent);display:flex;align-items:center;justify-content:center;
|
|
202
|
+
font-size:12px;font-weight:800;margin-top:1px}
|
|
203
|
+
.ck b{color:var(--ink);font-weight:600}
|
|
204
|
+
|
|
205
|
+
footer{margin-top:50px;color:var(--faint);font-size:12px;text-align:center;border-top:1px solid var(--line);padding-top:18px}
|
|
206
|
+
.codeflow{font-family:ui-monospace,monospace;font-size:12px;color:var(--muted);line-height:1.9;
|
|
207
|
+
background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:14px 16px;margin-top:14px;overflow-x:auto}
|
|
208
|
+
.codeflow .v{color:var(--volt)} .codeflow .g{color:var(--sent)} .codeflow .r{color:var(--fail)} .codeflow .c{color:var(--faint)}
|
|
209
|
+
|
|
210
|
+
/* reuse audit */
|
|
211
|
+
.auditsum{display:flex;gap:8px;flex-wrap:wrap;margin-top:8px}
|
|
212
|
+
.searchlog{margin-top:14px;border:1px dashed var(--line);border-radius:12px;padding:13px 15px;background:var(--panel)}
|
|
213
|
+
.searchlog .t{font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:var(--faint);font-weight:700;margin-bottom:8px}
|
|
214
|
+
.searchlog code{display:block;font-family:ui-monospace,monospace;font-size:12px;color:var(--muted);margin:4px 0;white-space:pre-wrap;word-break:break-word}
|
|
215
|
+
.searchlog code b{color:var(--volt)}
|
|
216
|
+
.searchlog code .hit{color:var(--sent)}
|
|
217
|
+
.audit{display:flex;flex-direction:column;gap:12px;margin-top:14px}
|
|
218
|
+
.arow{border:1px solid var(--line);border-radius:14px;background:var(--panel2);padding:14px 16px}
|
|
219
|
+
.arow.x{border-color:rgba(76,201,240,.4);background:linear-gradient(180deg,rgba(76,201,240,.06),var(--panel2))}
|
|
220
|
+
.ahead{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
|
|
221
|
+
.ahead .sym{font-size:14.5px;color:var(--ink);font-weight:700;letter-spacing:-.2px;word-break:break-word}
|
|
222
|
+
.badge{font-size:10px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;border-radius:999px;padding:3px 10px;white-space:nowrap}
|
|
223
|
+
.badge.reuse{color:#bff3d2;background:rgba(34,197,94,.14);border:1px solid rgba(34,197,94,.42)}
|
|
224
|
+
.badge.extract{color:#cfe6ff;background:rgba(76,201,240,.16);border:1px solid rgba(76,201,240,.5)}
|
|
225
|
+
.badge.newj{color:#ffe2b0;background:rgba(245,158,11,.14);border:1px solid rgba(245,158,11,.42)}
|
|
226
|
+
.badge.cfg{color:#cdd8ea;background:rgba(100,120,146,.16);border:1px solid var(--line)}
|
|
227
|
+
.lines{margin-top:10px;display:grid;grid-template-columns:80px 1fr;gap:5px 12px;font-size:13px}
|
|
228
|
+
.lines .k{color:var(--faint);font-size:11px;text-transform:uppercase;letter-spacing:.05em;padding-top:2px}
|
|
229
|
+
.lines .v{color:var(--muted)}
|
|
230
|
+
.lines .v b{color:var(--ink);font-weight:600}
|
|
231
|
+
.lines .v .mono{font-family:ui-monospace,monospace;font-size:12px;color:var(--volt)}
|
|
232
|
+
.grow{margin-top:14px;display:flex;gap:11px;align-items:center;border:1px solid rgba(76,201,240,.4);
|
|
233
|
+
background:rgba(76,201,240,.06);border-radius:12px;padding:12px 14px;color:#cfe6ff;font-size:13.5px}
|
|
234
|
+
</style>
|
|
235
|
+
</head>
|
|
236
|
+
<body>
|
|
237
|
+
<div class="wrap">
|
|
238
|
+
|
|
239
|
+
<!-- COMPONENT: kicker + hero title — use for: the packet name, the change title, and a one/two-sentence summary of the problem and the fix -->
|
|
240
|
+
<div class="kicker"><span class="dot"></span> Plan packet · short context line goes here</div>
|
|
241
|
+
<h1>Plan Title Goes Here</h1>
|
|
242
|
+
<p class="sub">One or two sentences that state the problem and the fix. Wrap the key terms
|
|
243
|
+
in <b>bold</b> so the reader catches the <b>core idea</b> at a glance.</p>
|
|
244
|
+
|
|
245
|
+
<!-- COMPONENT: stat hero — use for: a single before → after comparison, the headline metric that motivates the change (current bad number vs target good number) -->
|
|
246
|
+
<div class="hero">
|
|
247
|
+
<div class="stat bad">
|
|
248
|
+
<div class="lab">Before · context label</div>
|
|
249
|
+
<div class="big">120</div>
|
|
250
|
+
<div class="cap">the current cost or count, in plain terms</div>
|
|
251
|
+
</div>
|
|
252
|
+
<div class="arrowcol">➜</div>
|
|
253
|
+
<div class="stat good">
|
|
254
|
+
<div class="lab">After · context label</div>
|
|
255
|
+
<div class="big">4</div>
|
|
256
|
+
<div class="cap">the target cost or count once the change lands</div>
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
|
|
260
|
+
<!-- COMPONENT: section header — use for: the numbered title + right-aligned lead paragraph that opens every section below -->
|
|
261
|
+
<section>
|
|
262
|
+
<div class="h"><span class="n">01</span><h2>Section title for the motivating incident</h2>
|
|
263
|
+
<p class="lead">A short right-aligned lead that frames what this section shows and why it matters.</p>
|
|
264
|
+
</div>
|
|
265
|
+
|
|
266
|
+
<!-- COMPONENT: cascade bar — use for: a run or batch result rendered as many small bars (one bar per item), with summary pills and a root-cause banner; the bars are drawn by the inline script from generic counts -->
|
|
267
|
+
<div class="card">
|
|
268
|
+
<div class="cascade" id="cascade"></div>
|
|
269
|
+
<div class="meta">
|
|
270
|
+
<span class="pill"><span class="k">batch result</span> <span class="v g">success 20</span> · <span class="v s">skipped 0</span> · <span class="v r">failed 24</span></span>
|
|
271
|
+
<span class="pill"><span class="k">started</span> <span class="v">00:00:00</span></span>
|
|
272
|
+
<span class="pill"><span class="k">log</span> <span class="v mono">run_log_name.log</span></span>
|
|
273
|
+
</div>
|
|
274
|
+
<div class="rootcause">⚠️ <div><b>Root cause:</b> one short sentence naming the systemic fault that made every item fail, and why per-item retry could not heal it.</div></div>
|
|
275
|
+
</div>
|
|
276
|
+
</section>
|
|
277
|
+
|
|
278
|
+
<!-- COMPONENT: labeled rule banner — use for: the single headline rule or decision the plan introduces, stated once in large type with a short clarifying note -->
|
|
279
|
+
<section>
|
|
280
|
+
<div class="rule">
|
|
281
|
+
<div class="bolt">⚡</div>
|
|
282
|
+
<div class="txt">
|
|
283
|
+
<span class="hl">The headline condition</span> → the action that follows when it is met.
|
|
284
|
+
<small>A short clarifying note: what the rule does and does not do, any fixed value, and why there is no flag or toggle.</small>
|
|
285
|
+
</div>
|
|
286
|
+
</div>
|
|
287
|
+
</section>
|
|
288
|
+
|
|
289
|
+
<!-- COMPONENT: scenario row-strip with state pips — use for: walking through concrete item sequences; each .row is one item with a state (fail/sent/skip), optional counter and pips, and a .trip marker when the rule fires -->
|
|
290
|
+
<section>
|
|
291
|
+
<div class="h"><span class="n">02</span><h2>How the rule behaves on real sequences</h2>
|
|
292
|
+
<p class="lead">One short line on the shared state the sequences ride on. Each strip below is one item sequence.</p>
|
|
293
|
+
</div>
|
|
294
|
+
|
|
295
|
+
<!-- COMPONENT: legend — use for: a small swatch key that maps each state color to its meaning before the scenario strips -->
|
|
296
|
+
<div class="legend">
|
|
297
|
+
<span><i class="sw f"></i> failure → counter +1</span>
|
|
298
|
+
<span><i class="sw s"></i> success → counter resets to 0</span>
|
|
299
|
+
<span><i class="sw k"></i> benign skip → counter resets to 0</span>
|
|
300
|
+
</div>
|
|
301
|
+
|
|
302
|
+
<div class="scn headline">
|
|
303
|
+
<div class="scn-top">
|
|
304
|
+
<span class="scn-title">Headline scenario — the rule fires</span>
|
|
305
|
+
<span class="tag stop">trips</span>
|
|
306
|
+
<span class="scn-note">a short note on the outcome</span>
|
|
307
|
+
</div>
|
|
308
|
+
<div class="strip">
|
|
309
|
+
<div class="row fail"><div class="ic">✕</div><div class="rl">Item 1</div><div class="cnt">counter → 1</div><div class="pips"><u class="on"></u><u></u></div></div>
|
|
310
|
+
<div class="arrow">→</div>
|
|
311
|
+
<div class="row fail"><div class="ic">✕</div><div class="rl">Item 2</div><div class="cnt">counter → 2</div><div class="pips"><u class="on"></u><u class="on"></u></div></div>
|
|
312
|
+
<div class="arrow">→</div>
|
|
313
|
+
<div class="trip"><div class="b">⚡</div><div class="t">STOP</div><div class="e">1 alert sent</div></div>
|
|
314
|
+
<div class="arrow">→</div>
|
|
315
|
+
<div class="row fail ghost"><div class="ic">✕</div><div class="rl">Item 3</div><div class="ghostlab">never reached</div></div>
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
|
|
319
|
+
<div class="cols">
|
|
320
|
+
<div class="scn">
|
|
321
|
+
<div class="scn-top"><span class="scn-title">Many benign skips in a row</span><span class="tag drain">drains fully</span></div>
|
|
322
|
+
<div class="strip">
|
|
323
|
+
<div class="row skip"><div class="ic">⊘</div><div class="rl">skip</div><div class="pips"><u></u><u></u></div></div>
|
|
324
|
+
<div class="row skip"><div class="ic">⊘</div><div class="rl">skip</div><div class="pips"><u></u><u></u></div></div>
|
|
325
|
+
<div class="row skip"><div class="ic">⊘</div><div class="rl">skip</div><div class="pips"><u></u><u></u></div></div>
|
|
326
|
+
</div>
|
|
327
|
+
<div class="scn-note" style="margin:12px 0 0">counter stays 0 · no action taken</div>
|
|
328
|
+
</div>
|
|
329
|
+
|
|
330
|
+
<div class="scn">
|
|
331
|
+
<div class="scn-top"><span class="scn-title">A success resets the counter</span><span class="tag safe">no trip</span></div>
|
|
332
|
+
<div class="strip">
|
|
333
|
+
<div class="row fail"><div class="ic">✕</div><div class="rl">fail</div><div class="cnt">→ 1</div><div class="pips"><u class="on"></u><u></u></div></div>
|
|
334
|
+
<div class="arrow">→</div>
|
|
335
|
+
<div class="row sent"><div class="ic">✓</div><div class="rl">sent</div><div class="cnt">→ 0</div><div class="pips"><u></u><u></u></div></div>
|
|
336
|
+
<div class="arrow">→</div>
|
|
337
|
+
<div class="row fail"><div class="ic">✕</div><div class="rl">fail</div><div class="cnt">→ 1</div><div class="pips"><u class="on"></u><u></u></div></div>
|
|
338
|
+
</div>
|
|
339
|
+
<div class="scn-note" style="margin:12px 0 0">1 → 0 → 1 · never reaches the limit</div>
|
|
340
|
+
</div>
|
|
341
|
+
</div>
|
|
342
|
+
</section>
|
|
343
|
+
|
|
344
|
+
<!-- COMPONENT: code-flow block — use for: a short pseudocode or call-trace excerpt showing the precise signal the code reads, with .v/.g/.r/.c spans tinting key tokens and comments -->
|
|
345
|
+
<section>
|
|
346
|
+
<div class="h"><span class="n">03</span><h2>The signal the code reads</h2>
|
|
347
|
+
<p class="lead">One line on which value carries the signal and why it is the precise test.</p>
|
|
348
|
+
</div>
|
|
349
|
+
<div class="card" style="padding:18px 20px">
|
|
350
|
+
<div class="codeflow"><span class="c"># per item, inside the loop body</span><br>
|
|
351
|
+
count_before = state.<span class="v">failure_count</span><br>
|
|
352
|
+
outcome = <span class="c">await</span> pipeline.run_single(item)<br>
|
|
353
|
+
item_failed = state.<span class="v">failure_count</span> > count_before <span class="c"># the precise failure test</span><br>
|
|
354
|
+
<br>
|
|
355
|
+
<span class="r">failure</span> → state.consecutive += 1 <span class="c"># reaches the limit → record_stop(); return True</span><br>
|
|
356
|
+
<span class="g">success / skip</span> → state.consecutive = 0<br>
|
|
357
|
+
<br>
|
|
358
|
+
<span class="c"># stop signal travels up — no new teardown:</span><br>
|
|
359
|
+
return True → caller returns → runner <span class="v">finally</span>: report · summary · close
|
|
360
|
+
</div>
|
|
361
|
+
<div class="scn-note" style="margin:12px 0 0">A note on an edge case the signal already handles without extra branching.</div>
|
|
362
|
+
</div>
|
|
363
|
+
</section>
|
|
364
|
+
|
|
365
|
+
<!-- COMPONENT: is/isn't comparison cards — use for: scoping a term or decision; the left card lists what the change IS, the right card lists what it is NOT (struck through) -->
|
|
366
|
+
<section>
|
|
367
|
+
<div class="h"><span class="n">04</span><h2>What the change means here</h2></div>
|
|
368
|
+
<div class="cols">
|
|
369
|
+
<div class="mean is">
|
|
370
|
+
<div class="lab">✓ It is</div>
|
|
371
|
+
<ul>
|
|
372
|
+
<li><span class="m">→</span> The first thing the change actually does</li>
|
|
373
|
+
<li><span class="m">→</span> The second thing, reusing an existing path</li>
|
|
374
|
+
<li><span class="m">→</span> The third thing, through existing teardown</li>
|
|
375
|
+
</ul>
|
|
376
|
+
</div>
|
|
377
|
+
<div class="mean isnt">
|
|
378
|
+
<div class="lab">✕ It is not</div>
|
|
379
|
+
<ul>
|
|
380
|
+
<li><span class="m">×</span> A behavior the change deliberately avoids</li>
|
|
381
|
+
<li><span class="m">×</span> A second non-goal</li>
|
|
382
|
+
<li><span class="m">×</span> A configurable knob the change does not add</li>
|
|
383
|
+
</ul>
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
</section>
|
|
387
|
+
|
|
388
|
+
<!-- COMPONENT: edit-recipe step sequences — use for: the files the change touches. Describe each file by WHAT IT ACCOMPLISHES in plain language, as an ordered recipe of colored steps (reused / modified / new) — name the behavior, never the code symbol. Keep the file's repo-relative path in .rpath, dimmed, for the build agent. Fold a trivial one-line change into the recipe it supports as an "Also adds" line rather than giving it its own card. List untouched areas as dimmed paths below. -->
|
|
389
|
+
<section>
|
|
390
|
+
<div class="h"><span class="n">05</span><h2>The change — file by file</h2>
|
|
391
|
+
<p class="lead">Each file is described by what it accomplishes, in plain terms — an ordered recipe of steps, colored kept · changed · added.</p>
|
|
392
|
+
</div>
|
|
393
|
+
|
|
394
|
+
<!-- COMPONENT: recipe legend — use for: the three change-state colors, shown once above the recipes -->
|
|
395
|
+
<div class="reclegend">
|
|
396
|
+
<span><i class="sw s"></i> reused — existing behavior, kept</span>
|
|
397
|
+
<span><i class="sw m"></i> modified — existing behavior, changed</span>
|
|
398
|
+
<span><i class="sw a"></i> new — added by this change</span>
|
|
399
|
+
</div>
|
|
400
|
+
|
|
401
|
+
<!-- one recipe per touched file: a plain-language title, the dimmed path, an ordered row of colored steps, and an optional folded one-liner -->
|
|
402
|
+
<div class="recipe">
|
|
403
|
+
<div class="rhead">
|
|
404
|
+
<span class="what">What this file accomplishes, in plain language</span>
|
|
405
|
+
<span class="sz">~12–20 lines · 1 function</span>
|
|
406
|
+
</div>
|
|
407
|
+
<div class="rpath">path/relative/to/repo/root/the_changed_file.py</div>
|
|
408
|
+
<div class="oprow">
|
|
409
|
+
<div class="opnode reused"><div class="ix">1</div><div class="op">The first step, in human terms</div><div class="sub">a short clause on what it does and why</div><span class="tg">reused</span></div>
|
|
410
|
+
<div class="opconn">→</div>
|
|
411
|
+
<div class="opnode mod"><div class="ix">2</div><div class="op">The step that changes existing behavior</div><div class="sub">name the behavior, not the symbol</div><span class="tg">modified</span></div>
|
|
412
|
+
<div class="opconn">→</div>
|
|
413
|
+
<div class="opnode new"><div class="ix">3</div><div class="op">The step this change adds</div><div class="sub">what becomes true that wasn't before</div><span class="tg">new</span></div>
|
|
414
|
+
</div>
|
|
415
|
+
<div class="alsoadd">
|
|
416
|
+
<span class="plus">+</span>
|
|
417
|
+
<span class="txt"><b>Also adds a small supporting one-liner</b> folded into the file it serves, in plain language</span>
|
|
418
|
+
<span class="ap">path/relative/to/repo/root/supporting_file.py</span>
|
|
419
|
+
<span class="nt">new · 1 line</span>
|
|
420
|
+
</div>
|
|
421
|
+
</div>
|
|
422
|
+
|
|
423
|
+
<div class="recipe">
|
|
424
|
+
<div class="rhead">
|
|
425
|
+
<span class="what">The tests — proven on real data</span>
|
|
426
|
+
<span class="sz">new · across the test files</span>
|
|
427
|
+
</div>
|
|
428
|
+
<div class="rpath">path/relative/to/repo/root/tests/test_the_behavior.py</div>
|
|
429
|
+
<div class="oprow">
|
|
430
|
+
<div class="opnode new"><div class="ix">1</div><div class="op">The behavior the first test pins</div><div class="sub">stated as what it proves, not the function name</div><span class="tg">new</span></div>
|
|
431
|
+
<div class="opconn">→</div>
|
|
432
|
+
<div class="opnode new"><div class="ix">2</div><div class="op">The behavior the second test pins</div><div class="sub">one human clause each</div><span class="tg">new</span></div>
|
|
433
|
+
<div class="opconn">→</div>
|
|
434
|
+
<div class="opnode reused"><div class="ix">3</div><div class="op">An existing test extended</div><div class="sub">what the extension now also covers</div><span class="tg">reused</span></div>
|
|
435
|
+
</div>
|
|
436
|
+
</div>
|
|
437
|
+
|
|
438
|
+
<div class="nochange">Left untouched:
|
|
439
|
+
<span class="lk">path/to/untouched_area_one.py</span>
|
|
440
|
+
<span class="lk">path/to/untouched_area_two.py</span>
|
|
441
|
+
<span class="lk">path/to/untouched_area_three.py</span>
|
|
442
|
+
</div>
|
|
443
|
+
</section>
|
|
444
|
+
|
|
445
|
+
<!-- COMPONENT: reuse-audit verdict badges + search log + audit rows — use for: showing every new symbol was checked against the codebase first; the badge summary counts verdicts, the search log lists the searches run, each audit row records searched / found / verdict, and the .grow banner notes any scope growth -->
|
|
446
|
+
<section>
|
|
447
|
+
<div class="h"><span class="n">06</span><h2>Reuse audit — why each new piece exists</h2>
|
|
448
|
+
<p class="lead">Every new symbol checked against the codebase before building, with the searches and the verdict.</p>
|
|
449
|
+
</div>
|
|
450
|
+
|
|
451
|
+
<div class="card" style="padding:20px">
|
|
452
|
+
<div class="auditsum">
|
|
453
|
+
<span class="badge reuse">3 reused</span>
|
|
454
|
+
<span class="badge extract">1 extracted → shared</span>
|
|
455
|
+
<span class="badge newj">1 new (justified)</span>
|
|
456
|
+
<span class="badge cfg">1 config-local</span>
|
|
457
|
+
</div>
|
|
458
|
+
|
|
459
|
+
<div class="searchlog">
|
|
460
|
+
<div class="t">Searches run</div>
|
|
461
|
+
<code><b>grep -i</b> "\b(term_one|term_two)\b" --glob *.py → <span class="hit">existing_module · ExistingHelper</span></code>
|
|
462
|
+
<code><b>grep -i</b> "alternate_term" shared/ → <span class="hit">shared/example_helper.py</span></code>
|
|
463
|
+
<code><b>read</b> example_runner.py:100-140 · example_constants.py:11 · example_service.py:180</code>
|
|
464
|
+
</div>
|
|
465
|
+
|
|
466
|
+
<div class="audit">
|
|
467
|
+
|
|
468
|
+
<div class="arow x">
|
|
469
|
+
<div class="ahead"><span class="sym">the new shared kernel</span><span class="badge extract">extract → shared</span></div>
|
|
470
|
+
<div class="lines">
|
|
471
|
+
<span class="k">searched</span><span class="v">repo-wide for an existing helper that does this exact thing</span>
|
|
472
|
+
<span class="k">found</span><span class="v"><b>a second caller already hand-rolls this pattern</b> as a local copy with a different constant. A related helper exists but solves a different problem.</span>
|
|
473
|
+
<span class="k">verdict</span><span class="v">Reproduction. Extract one shared helper; both callers use it. Per-caller policy stays at the call site.</span>
|
|
474
|
+
</div>
|
|
475
|
+
</div>
|
|
476
|
+
|
|
477
|
+
<div class="arow">
|
|
478
|
+
<div class="ahead"><span class="sym">the existing action helper</span><span class="badge reuse">reused</span></div>
|
|
479
|
+
<div class="lines">
|
|
480
|
+
<span class="k">searched</span><span class="v">the existing action path in the service module</span>
|
|
481
|
+
<span class="k">found</span><span class="v">an existing helper already used by sibling paths</span>
|
|
482
|
+
<span class="k">verdict</span><span class="v">Reused verbatim — the change adds no new code here.</span>
|
|
483
|
+
</div>
|
|
484
|
+
</div>
|
|
485
|
+
|
|
486
|
+
<div class="arow">
|
|
487
|
+
<div class="ahead"><span class="sym">the new thin method</span><span class="badge newj">new (justified)</span></div>
|
|
488
|
+
<div class="lines">
|
|
489
|
+
<span class="k">searched</span><span class="v">the sibling methods that already perform the action</span>
|
|
490
|
+
<span class="k">found</span><span class="v">the siblings each mutate counters this method must leave untouched</span>
|
|
491
|
+
<span class="k">verdict</span><span class="v">Can't reuse — a new thin method that calls the shared action helper.</span>
|
|
492
|
+
</div>
|
|
493
|
+
</div>
|
|
494
|
+
|
|
495
|
+
<div class="arow">
|
|
496
|
+
<div class="ahead"><span class="sym">the new constants</span><span class="badge cfg">config-local</span></div>
|
|
497
|
+
<div class="lines">
|
|
498
|
+
<span class="k">searched</span><span class="v">the config module for an existing matching constant</span>
|
|
499
|
+
<span class="k">found</span><span class="v">no matching constant; a sibling uses a different value</span>
|
|
500
|
+
<span class="k">verdict</span><span class="v">Add to config/ — the value can't be shared because it differs.</span>
|
|
501
|
+
</div>
|
|
502
|
+
</div>
|
|
503
|
+
|
|
504
|
+
</div>
|
|
505
|
+
|
|
506
|
+
<div class="grow">⤴ This audit grows the change set — the new shared helper plus the second-caller rewire shown above.</div>
|
|
507
|
+
</div>
|
|
508
|
+
</section>
|
|
509
|
+
|
|
510
|
+
<!-- COMPONENT: test cards — use for: the test-first plan; each card carries a RED id, the behavior the test proves stated as a plain sentence (not the function name), and a one-line "Pins:" statement of what it locks in -->
|
|
511
|
+
<section>
|
|
512
|
+
<div class="h"><span class="n">07</span><h2>Test-first — written before the code</h2></div>
|
|
513
|
+
<div class="tests">
|
|
514
|
+
<div class="test"><span class="rid">RED 1</span>
|
|
515
|
+
<div class="tn">The action fires once the limit is reached</div>
|
|
516
|
+
<div class="pin">Pins: <b>the action fires at the limit</b> — one alert, nothing past it.</div>
|
|
517
|
+
</div>
|
|
518
|
+
<div class="test"><span class="rid">RED 2</span>
|
|
519
|
+
<div class="tn">A run of harmless skips never fires it</div>
|
|
520
|
+
<div class="pin">Pins the <b>must-not</b>: many skips drain fully, zero actions.</div>
|
|
521
|
+
</div>
|
|
522
|
+
<div class="test"><span class="rid">RED 3</span>
|
|
523
|
+
<div class="tn">A success in between resets the count</div>
|
|
524
|
+
<div class="pin">Pins the <b>reset</b>: fail → success → fail never reaches the limit.</div>
|
|
525
|
+
</div>
|
|
526
|
+
<div class="test"><span class="rid">RED 4</span>
|
|
527
|
+
<div class="tn">The alert names the step that failed</div>
|
|
528
|
+
<div class="pin">Pins the alert: the recorded step matches what was named.</div>
|
|
529
|
+
</div>
|
|
530
|
+
</div>
|
|
531
|
+
</section>
|
|
532
|
+
|
|
533
|
+
<!-- COMPONENT: acceptance checklist — use for: the "done when" criteria; each .ck is one checked item with a bold lead phrase and a short clause naming the observable outcome -->
|
|
534
|
+
<section>
|
|
535
|
+
<div class="h"><span class="n">08</span><h2>Done when</h2>
|
|
536
|
+
<p class="lead">A short line on the test-count change and that baseline failures elsewhere stay unchanged.</p>
|
|
537
|
+
</div>
|
|
538
|
+
<div class="card">
|
|
539
|
+
<div class="acc">
|
|
540
|
+
<div class="ck"><span class="b">✓</span><div><b>The action fires at the limit</b> — next item never reached, one alert.</div></div>
|
|
541
|
+
<div class="ck"><span class="b">✓</span><div><b>Benign skips never fire</b> — the run drains, counter at 0.</div></div>
|
|
542
|
+
<div class="ck"><span class="b">✓</span><div><b>A success or skip resets</b> the counter to 0.</div></div>
|
|
543
|
+
<div class="ck"><span class="b">✓</span><div><b>The alert names the step</b> via the named constant.</div></div>
|
|
544
|
+
<div class="ck"><span class="b">✓</span><div><b>Counters stay in sync</b> — the change adds neither failure nor name.</div></div>
|
|
545
|
+
<div class="ck"><span class="b">✓</span><div><b>Reuses the existing path</b> — no new teardown.</div></div>
|
|
546
|
+
<div class="ck"><span class="b">✓</span><div><b>The loop compares the named constant</b>, not a literal.</div></div>
|
|
547
|
+
<div class="ck"><span class="b">✓</span><div><b>The code-rules gate</b> passes on the changed lines.</div></div>
|
|
548
|
+
</div>
|
|
549
|
+
</div>
|
|
550
|
+
</section>
|
|
551
|
+
|
|
552
|
+
<!-- COMPONENT: footer — use for: the closing line naming the packet slug and the validator status -->
|
|
553
|
+
<footer>Plan title · plan packet <span class="mono">plan-slug-goes-here</span> · validator: approved, 0 open product questions</footer>
|
|
554
|
+
</div>
|
|
555
|
+
|
|
556
|
+
<script>
|
|
557
|
+
(function(){
|
|
558
|
+
var cascade=document.getElementById('cascade');
|
|
559
|
+
if(!cascade)return;
|
|
560
|
+
var total=44, failures=24, fragment=document.createDocumentFragment();
|
|
561
|
+
for(var i=0;i<total;i++){
|
|
562
|
+
var bar=document.createElement('i');
|
|
563
|
+
if(i>=failures)bar.className='ok';
|
|
564
|
+
bar.style.animationDelay=(i*7)+'ms';
|
|
565
|
+
bar.style.height=(i>=failures?62:(55+((i*13)%45)))+'%';
|
|
566
|
+
fragment.appendChild(bar);
|
|
567
|
+
}
|
|
568
|
+
cascade.appendChild(fragment);
|
|
569
|
+
})();
|
|
570
|
+
</script>
|
|
571
|
+
</body>
|
|
572
|
+
</html>
|
|
@@ -125,3 +125,70 @@ test('workflow error path returns the recovery keys', () => {
|
|
|
125
125
|
assert.match(catchBody, /\brecovered\b/);
|
|
126
126
|
assert.match(catchBody, /\brecoveryNote\b/);
|
|
127
127
|
});
|
|
128
|
+
|
|
129
|
+
test('workflow declares a reuse audit phase', () => {
|
|
130
|
+
assert.match(workflowSource, /Reuse audit/);
|
|
131
|
+
assert.match(workflowSource, /title:\s*'Reuse audit'/);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('reuse audit runner uses a structured schema and the reuse audit phase', () => {
|
|
135
|
+
const reuseAuditBody = functionBody('runReuseAudit');
|
|
136
|
+
assert.match(reuseAuditBody, /schema:\s*reuseAuditSchema\(\)/);
|
|
137
|
+
assert.match(reuseAuditBody, /phase:\s*'Reuse audit'/);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('reuse audit schema gates on allJustified', () => {
|
|
141
|
+
const reuseAuditSchemaBody = functionBody('reuseAuditSchema');
|
|
142
|
+
assert.match(reuseAuditSchemaBody, /allJustified/);
|
|
143
|
+
assert.match(reuseAuditSchemaBody, /findings/);
|
|
144
|
+
assert.match(reuseAuditSchemaBody, /summary/);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('reuse audit prompt searches shared_utils for existing equivalents', () => {
|
|
148
|
+
const reuseAuditPromptBody = functionBody('reuseAuditPrompt');
|
|
149
|
+
assert.match(reuseAuditPromptBody, /shared_utils/);
|
|
150
|
+
assert.match(reuseAuditPromptBody, /reuse-audit\.md/);
|
|
151
|
+
assert.match(reuseAuditPromptBody, /unjustified-reproduction/);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('workflow runs the reuse audit after writing the packet', () => {
|
|
155
|
+
const runBody = functionBody('runPlanPacketWorkflow');
|
|
156
|
+
const writeIndex = runBody.indexOf('await writePacket');
|
|
157
|
+
const reuseAuditIndex = runBody.indexOf('runReuseAudit');
|
|
158
|
+
assert.ok(writeIndex !== -1 && reuseAuditIndex !== -1);
|
|
159
|
+
assert.ok(writeIndex < reuseAuditIndex);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('workflow folds the reuse audit gate into the clean validation check', () => {
|
|
163
|
+
const runBody = functionBody('runPlanPacketWorkflow');
|
|
164
|
+
assert.match(runBody, /reuseAudit\.allJustified/);
|
|
165
|
+
assert.match(runBody, /reuseAuditFindings/);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test('workflow declares a visualize phase', () => {
|
|
169
|
+
assert.match(workflowSource, /title:\s*'Visualize'/);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test('workflow runs the visualize phase after validation', () => {
|
|
173
|
+
const runBody = functionBody('runPlanPacketWorkflow');
|
|
174
|
+
const visualHtmlIndex = runBody.indexOf('runVisualHtml(packetPath)');
|
|
175
|
+
const validationLoopIndex = runBody.indexOf('while (!hasCleanValidation(');
|
|
176
|
+
assert.ok(visualHtmlIndex !== -1 && validationLoopIndex !== -1);
|
|
177
|
+
assert.ok(visualHtmlIndex > validationLoopIndex);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('visual html schema carries the html path', () => {
|
|
181
|
+
const visualHtmlSchemaBody = functionBody('visualHtmlSchema');
|
|
182
|
+
assert.match(visualHtmlSchemaBody, /htmlPath/);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test('visual html prompt names the template and the output file', () => {
|
|
186
|
+
const visualHtmlPromptBody = functionBody('visualHtmlPrompt');
|
|
187
|
+
assert.match(visualHtmlPromptBody, /visual-plan\.template\.html/);
|
|
188
|
+
assert.match(visualHtmlPromptBody, /visual-plan\.html/);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test('workflow returns the visual html path', () => {
|
|
192
|
+
const runBody = functionBody('runPlanPacketWorkflow');
|
|
193
|
+
assert.match(runBody, /visualHtmlPath/);
|
|
194
|
+
});
|
|
@@ -6,6 +6,8 @@ export const meta = {
|
|
|
6
6
|
{ title: 'Discover', detail: 'Resolve repo root, read instructions, inspect matching source files, tests, configs, docs, skills, hooks, agents, and workflows.' },
|
|
7
7
|
{ title: 'Write packet', detail: 'Create the required docs/plans/<slug>/ tree with a thin README hub and detailed second-level docs.' },
|
|
8
8
|
{ title: 'Validate', detail: 'Run scripts/validate_packet.py, spawn plan-packet-validator in fresh context, and repair findings up to the cap.' },
|
|
9
|
+
{ title: 'Reuse audit', detail: 'Search the codebase for existing equivalents of each new symbol or file the packet introduces; write validation/reuse-audit.md with a per-item verdict; gate approval on any unjustified reproduction.' },
|
|
10
|
+
{ title: 'Visualize', detail: 'Build a single-file offline visual HTML of the finished packet from the visual-plan template; write it beside the packet as visual-plan.html.' },
|
|
9
11
|
{ title: 'Approval', detail: 'Return the packet path and validation verdict, then stop before implementation work.' },
|
|
10
12
|
],
|
|
11
13
|
}
|
|
@@ -79,6 +81,48 @@ function repairSchema() {
|
|
|
79
81
|
}
|
|
80
82
|
}
|
|
81
83
|
|
|
84
|
+
function reuseAuditSchema() {
|
|
85
|
+
return {
|
|
86
|
+
type: 'object',
|
|
87
|
+
additionalProperties: false,
|
|
88
|
+
properties: {
|
|
89
|
+
allJustified: { type: 'boolean' },
|
|
90
|
+
findings: {
|
|
91
|
+
type: 'array',
|
|
92
|
+
items: {
|
|
93
|
+
type: 'object',
|
|
94
|
+
additionalProperties: false,
|
|
95
|
+
properties: {
|
|
96
|
+
item: { type: 'string' },
|
|
97
|
+
kind: { type: 'string' },
|
|
98
|
+
verdict: { type: 'string' },
|
|
99
|
+
searched: { type: 'string' },
|
|
100
|
+
found: { type: 'string' },
|
|
101
|
+
decision: { type: 'string' },
|
|
102
|
+
evidence: { type: 'string' },
|
|
103
|
+
},
|
|
104
|
+
required: ['item', 'kind', 'verdict', 'searched', 'found', 'decision', 'evidence'],
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
summary: { type: 'string' },
|
|
108
|
+
},
|
|
109
|
+
required: ['allJustified', 'findings', 'summary'],
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function visualHtmlSchema() {
|
|
114
|
+
return {
|
|
115
|
+
type: 'object',
|
|
116
|
+
additionalProperties: false,
|
|
117
|
+
properties: {
|
|
118
|
+
htmlPath: { type: 'string' },
|
|
119
|
+
sectionsBuilt: { type: 'array', items: { type: 'string' } },
|
|
120
|
+
summary: { type: 'string' },
|
|
121
|
+
},
|
|
122
|
+
required: ['htmlPath', 'sectionsBuilt', 'summary'],
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
82
126
|
function normalizeRunInput(rawInput) {
|
|
83
127
|
if (rawInput && typeof rawInput === 'object') return rawInput
|
|
84
128
|
if (typeof rawInput !== 'string' || rawInput.trim() === '') return {}
|
|
@@ -190,16 +234,43 @@ function semanticValidationPrompt(packetPath) {
|
|
|
190
234
|
)
|
|
191
235
|
}
|
|
192
236
|
|
|
193
|
-
function repairPrompt(packetPath, deterministicValidation, semanticValidation) {
|
|
237
|
+
function repairPrompt(packetPath, deterministicValidation, semanticValidation, reuseAudit) {
|
|
194
238
|
return (
|
|
195
239
|
`Repair only the plan packet at ${packetPath}. Do not edit source code.\n\n` +
|
|
196
240
|
`Deterministic validation findings:\n${JSON.stringify(deterministicValidation.findings || [])}\n\n` +
|
|
197
241
|
`Semantic validation findings:\n${JSON.stringify(semanticValidation.findings || [])}\n\n` +
|
|
242
|
+
`Reuse audit findings:\n${JSON.stringify(reuseAudit?.findings || [])}\n\n` +
|
|
243
|
+
`For each reuse audit finding marked unjustified-reproduction, either record the reuse decision in the packet that justifies the new code, or change the plan to reuse the existing public helper or extract it to shared_utils; update validation/reuse-audit.md accordingly. ` +
|
|
198
244
|
`Make the packet pass by correcting documentation, adding missing source grounding, removing placeholders, strengthening TDD steps, and updating validation/validator-report.md. ` +
|
|
199
245
|
`If the Edit or Write tool is blocked by a worktree or isolation guard, recover automatically: stage the corrected files under a writable temporary directory with the Write tool, then copy them over the packet path with a filesystem copy. Set recovered=true with recoveryNote describing the staging path and copy; otherwise set recovered=false with an empty recoveryNote.`
|
|
200
246
|
)
|
|
201
247
|
}
|
|
202
248
|
|
|
249
|
+
function reuseAuditPrompt(packetPath) {
|
|
250
|
+
return (
|
|
251
|
+
`Run the reuse audit for the plan packet at ${packetPath}. Resolve the repo root from packet.json. Do not edit source code; only write the packet doc.\n\n` +
|
|
252
|
+
`Read implementation/file-plan.md, spec/interfaces.md, implementation/tdd-plan.md, and spec/scope.md in the packet to enumerate every new file, public symbol, helper, and constant the build introduces.\n\n` +
|
|
253
|
+
`For each item, search the codebase with grep, serena, or zoekt — repo-wide and specifically under shared_utils — for an existing implementation or near-equivalent behavior.\n\n` +
|
|
254
|
+
`Assign exactly one verdict per item from: reused (an existing public helper is used), extract-to-shared (an equivalent exists but is not shared or public and should be extracted), new-justified (genuinely new, with the reason reuse or extract was rejected), config-local (a constant living in config/), or unjustified-reproduction (reproduces existing behavior that could be made public or extracted, with no recorded justification).\n\n` +
|
|
255
|
+
`Write validation/reuse-audit.md into the packet: a markdown table with columns Item, Kind, Verdict, Searched, Found, Decision, Evidence using real file:line evidence, plus a one-line summary of verdict counts. Write concrete content only — no angle-bracket placeholder tokens and no todo, tbd, or placeholder words.\n\n` +
|
|
256
|
+
`Return the structured object. Set allJustified=false when any finding has verdict unjustified-reproduction.`
|
|
257
|
+
)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function visualHtmlPrompt(packetPath) {
|
|
261
|
+
return (
|
|
262
|
+
`Build a single-file, offline, diagram-first visual HTML of the finished plan packet at ${packetPath}. Do not edit source code or the packet markdown; only write the HTML view.\n\n` +
|
|
263
|
+
`Read the style template first and reuse its CSS and section components exactly:\n` +
|
|
264
|
+
`$HOME/.claude/skills/anthropic-plan/templates/visual-plan.template.html\n\n` +
|
|
265
|
+
`Then read the packet: README.md, packet.json, every file under spec/ and implementation/, and validation/reuse-audit.md. Translate the packet into the template's visual vocabulary — stat hero, scenario row strips, is/isn't cards, edit-recipe step sequences, verdict badges, and a checklist. Show the plan as diagrams and compact cards, never walls of prose, and never paste the markdown verbatim.\n\n` +
|
|
266
|
+
`Write for the reviewer — a person reading the plan, not the computer that runs the code. State every label as what a step accomplishes, in plain language. Drop code symbols from the picture: no function names, selector strings, call traces, or snake_case test names in the visible diagram — those stay in the packet markdown for the build agent. Keep each touched file's repo-relative path, but dim it (the .rpath / .ap style) so it sits quietly beneath the human description.\n\n` +
|
|
267
|
+
`Render the change (section 05) as edit-recipe step sequences, one recipe per touched file: a plain-language title for what the file accomplishes, the dimmed repo-relative path, then an ordered row of colored steps — reused (green), modified (violet), new (amber). Fold a trivial one-line change into the recipe it supports as an "Also adds" line rather than giving it its own card. Name each test by the behavior it proves, not its function name.\n\n` +
|
|
268
|
+
`Surface validation/reuse-audit.md as a Reuse audit section with one verdict badge per item (reused, extract-to-shared, new-justified, config-local, unjustified-reproduction), each item titled in plain language with its file path dimmed.\n\n` +
|
|
269
|
+
`Write the result to ${packetPath}/visual-plan.html. Inline all CSS and JavaScript; make no network calls and reference no external assets, so the file opens offline. If the Write tool is blocked by a worktree or isolation guard, stage the file under $CLAUDE_JOB_DIR/tmp with the Write tool, then copy it to the packet path.\n\n` +
|
|
270
|
+
`Return htmlPath set to the written file path, sectionsBuilt listing the section names you included, and a one-line summary.`
|
|
271
|
+
)
|
|
272
|
+
}
|
|
273
|
+
|
|
203
274
|
async function discoverContext(runInput, packetPath) {
|
|
204
275
|
return agent(discoveryPrompt(runInput, packetPath), {
|
|
205
276
|
label: `plan-packet-discover`,
|
|
@@ -238,8 +309,26 @@ async function runSemanticValidator(packetPath) {
|
|
|
238
309
|
})
|
|
239
310
|
}
|
|
240
311
|
|
|
241
|
-
async function
|
|
242
|
-
return agent(
|
|
312
|
+
async function runReuseAudit(packetPath) {
|
|
313
|
+
return agent(reuseAuditPrompt(packetPath), {
|
|
314
|
+
label: `plan-packet-reuse-audit`,
|
|
315
|
+
phase: 'Reuse audit',
|
|
316
|
+
schema: reuseAuditSchema(),
|
|
317
|
+
agentType: 'general-purpose',
|
|
318
|
+
})
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async function runVisualHtml(packetPath) {
|
|
322
|
+
return agent(visualHtmlPrompt(packetPath), {
|
|
323
|
+
label: `plan-packet-visual-html`,
|
|
324
|
+
phase: 'Visualize',
|
|
325
|
+
schema: visualHtmlSchema(),
|
|
326
|
+
agentType: 'general-purpose',
|
|
327
|
+
})
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async function repairPacket(packetPath, deterministicValidation, semanticValidation, reuseAudit) {
|
|
331
|
+
return agent(repairPrompt(packetPath, deterministicValidation, semanticValidation, reuseAudit), {
|
|
243
332
|
label: `plan-packet-repair`,
|
|
244
333
|
phase: 'Validate',
|
|
245
334
|
schema: repairSchema(),
|
|
@@ -255,8 +344,11 @@ async function runPlanPacketWorkflow(rawInput) {
|
|
|
255
344
|
let packetWrite = null
|
|
256
345
|
let deterministicValidation = null
|
|
257
346
|
let semanticValidation = null
|
|
347
|
+
let reuseAudit = null
|
|
258
348
|
let recovered = false
|
|
259
349
|
let recoveryNote = ''
|
|
350
|
+
let visualHtmlPath = ''
|
|
351
|
+
const visualHtmlFindings = []
|
|
260
352
|
const recordRecovery = (recovery) => {
|
|
261
353
|
if (recovery?.recovered !== true) return
|
|
262
354
|
recovered = true
|
|
@@ -267,20 +359,32 @@ async function runPlanPacketWorkflow(rawInput) {
|
|
|
267
359
|
const discoverySummary = await discoverContext(runInput, packetPath)
|
|
268
360
|
packetWrite = await writePacket(runInput, packetPath, discoverySummary)
|
|
269
361
|
recordRecovery(packetWrite)
|
|
362
|
+
reuseAudit = await runReuseAudit(packetPath)
|
|
270
363
|
deterministicValidation = await runDeterministicValidation(packetPath)
|
|
271
364
|
semanticValidation = await runSemanticValidator(packetPath)
|
|
272
365
|
const hasCleanValidation = () =>
|
|
273
|
-
deterministicValidation?.passed === true &&
|
|
366
|
+
deterministicValidation?.passed === true &&
|
|
367
|
+
semanticValidation &&
|
|
368
|
+
semanticValidation.allPassed === true &&
|
|
369
|
+
reuseAudit &&
|
|
370
|
+
reuseAudit.allJustified === true
|
|
274
371
|
|
|
275
372
|
while (!hasCleanValidation() && repairLoops < policy.maxRepairLoops) {
|
|
276
373
|
repairLoops += 1
|
|
277
|
-
const repair = await repairPacket(packetPath, deterministicValidation, semanticValidation)
|
|
374
|
+
const repair = await repairPacket(packetPath, deterministicValidation, semanticValidation, reuseAudit)
|
|
278
375
|
recordRecovery(repair)
|
|
376
|
+
reuseAudit = await runReuseAudit(packetPath)
|
|
279
377
|
deterministicValidation = await runDeterministicValidation(packetPath)
|
|
280
378
|
semanticValidation = await runSemanticValidator(packetPath)
|
|
281
379
|
}
|
|
282
380
|
|
|
283
381
|
const passed = hasCleanValidation()
|
|
382
|
+
try {
|
|
383
|
+
const visualHtml = await runVisualHtml(packetPath)
|
|
384
|
+
visualHtmlPath = visualHtml?.htmlPath || ''
|
|
385
|
+
} catch (visualHtmlError) {
|
|
386
|
+
visualHtmlFindings.push(String(visualHtmlError?.message || visualHtmlError))
|
|
387
|
+
}
|
|
284
388
|
return {
|
|
285
389
|
packetPath: packetWrite?.packetPath || packetPath,
|
|
286
390
|
slug: packetWrite?.slug || slugFromTask(runInput.task || runInput.prompt || runInput.arguments),
|
|
@@ -288,10 +392,13 @@ async function runPlanPacketWorkflow(rawInput) {
|
|
|
288
392
|
repairLoops,
|
|
289
393
|
deterministicFindings: deterministicValidation?.findings || [],
|
|
290
394
|
semanticFindings: semanticValidation?.findings || [],
|
|
395
|
+
reuseAuditFindings: reuseAudit?.findings || [],
|
|
291
396
|
implementationStarted: false,
|
|
292
397
|
approvalRequired: true,
|
|
293
398
|
recovered,
|
|
294
399
|
recoveryNote,
|
|
400
|
+
visualHtmlPath,
|
|
401
|
+
visualHtmlFindings,
|
|
295
402
|
}
|
|
296
403
|
} catch (workflowError) {
|
|
297
404
|
return {
|
|
@@ -308,10 +415,13 @@ async function runPlanPacketWorkflow(rawInput) {
|
|
|
308
415
|
detail: String(workflowError?.message || workflowError),
|
|
309
416
|
},
|
|
310
417
|
],
|
|
418
|
+
reuseAuditFindings: reuseAudit?.findings || [],
|
|
311
419
|
implementationStarted: false,
|
|
312
420
|
approvalRequired: true,
|
|
313
421
|
recovered,
|
|
314
422
|
recoveryNote,
|
|
423
|
+
visualHtmlPath,
|
|
424
|
+
visualHtmlFindings,
|
|
315
425
|
}
|
|
316
426
|
}
|
|
317
427
|
}
|