claude-dev-env 8.75.5 → 8.77.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/docs/templates/artifact-page/README.md +5 -2
- package/docs/templates/artifact-page/status-flowchart.html +98 -0
- package/docs/templates/html-plan/README.md +1 -1
- package/hooks/blocking/reply_length_gate.py +20 -0
- package/hooks/blocking/test_reply_length_gate.py +11 -3
- package/hooks/features/conduct-gates.md +1 -1
- package/hooks/features/session-context.md +2 -2
- package/hooks/hooks_constants/artifact_template_pointer_constants.py +15 -10
- package/hooks/hooks_constants/reply_length_gate_constants.py +2 -0
- package/hooks/hooks_constants/test_artifact_template_pointer_constants.py +26 -0
- package/hooks/hooks_constants/test_visual_reply_rules_constants.py +1 -0
- package/hooks/hooks_constants/visual_reply_rules_constants.py +10 -0
- package/hooks/session/artifact_template_pointer.py +11 -7
- package/hooks/session/test_artifact_template_pointer.py +19 -2
- package/hooks/test_context_budget_hooks.py +1 -0
- package/hooks/test_visual_reply_rules.py +45 -0
- package/hooks/visual_reply_rules.py +37 -0
- package/package.json +1 -1
- package/rules/visual-reply-rules.json +11 -1
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
# Artifact page template
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Three templates live here. Pick the one that fits the ask before you write a line.
|
|
4
4
|
|
|
5
5
|
| Ask | Template |
|
|
6
6
|
|---|---|
|
|
7
|
-
| A
|
|
7
|
+
| A status or progress report on one piece of work: what is done, where it is stuck, and what comes next. Prefer it for every such report unless the person asks for another shape | `status-flowchart.html` |
|
|
8
|
+
| A tracker, board, review page or dashboard that lists many items for people to read and click | `template.html` |
|
|
8
9
|
| A picture of how things connect: a map, a flow, who calls what, old next to new. Also any ask for "only visuals" or "no words" | `diagram.html` |
|
|
9
10
|
|
|
10
11
|
`diagram.html` draws the content in inline SVG: boxes for things, arrows for links, and color for state. Labels are a few words. Text in bordered cards is a tracker, so it never answers a visual-only ask.
|
|
11
12
|
|
|
13
|
+
`status-flowchart.html` reads left to right. Done steps are green boxes, each yes-or-no check is a diamond, a stuck step is an amber box under its check with a dashed recheck loop back to it, and the next step is a dashed box. A click on any shape opens its detail from the `detail-data` block below the chart. Replace the `<title>`, the `h1`, the svg `aria-label`, the shapes, the arrows and the `detail-data` entries with the work being reported. Keep each label to three words or fewer, in plain words with no abbreviations or item numbers.
|
|
14
|
+
|
|
12
15
|
Render the page and look at the screenshot before you publish. Headless Chromium renders at least 500 pixels wide, so take the screenshot at 800 wide.
|
|
13
16
|
|
|
14
17
|
`template.html` is the reference page for any artifact an agent builds: a tracker, a board, a review page, a dashboard. Start from it, keep its structure and house style, and replace the sample data and the parts the new page does not need.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<title>Status Flowchart</title>
|
|
2
|
+
<style>
|
|
3
|
+
:root{--bg:#f3f5f8;--surface:#ffffff;--fg:#18202c;--line:#7d8798;--ok:#16653d;--ok-soft:#e1f3e8;--warn:#7a4800;--warn-soft:#fdf0d6;--here:#2245a2;--here-soft:#e5ebfb;--font:"Segoe UI",system-ui,-apple-system,sans-serif}
|
|
4
|
+
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){--bg:#11151b;--surface:#1a2029;--fg:#e8ebf1;--line:#7c879c;--ok:#72d6a3;--ok-soft:#163326;--warn:#f2bc63;--warn-soft:#382b13;--here:#a9c0ff;--here-soft:#1f2a46;color-scheme:dark}}
|
|
5
|
+
:root[data-theme="dark"]{--bg:#11151b;--surface:#1a2029;--fg:#e8ebf1;--line:#7c879c;--ok:#72d6a3;--ok-soft:#163326;--warn:#f2bc63;--warn-soft:#382b13;--here:#a9c0ff;--here-soft:#1f2a46;color-scheme:dark}
|
|
6
|
+
body{background:var(--bg);color:var(--fg);font:16px/1.45 var(--font);margin:0;padding-inline:16px;padding-block:20px 40px}
|
|
7
|
+
.wrap{max-width:1120px;margin:0 auto;display:grid;gap:14px}
|
|
8
|
+
h1{font-size:22px;margin:0;text-wrap:balance}
|
|
9
|
+
.scroll{overflow-x:auto;background:var(--surface);border:1.5px solid var(--line);border-radius:14px}
|
|
10
|
+
svg{display:block;width:100%;min-width:820px;height:auto;color:var(--fg)}
|
|
11
|
+
svg text{font:600 15px var(--font);fill:currentColor}
|
|
12
|
+
svg .lbl{font:700 13px var(--font)}
|
|
13
|
+
.n{cursor:pointer}
|
|
14
|
+
.n:focus{outline:none}
|
|
15
|
+
.n:focus-visible .s,.n.on .s{stroke:var(--here);stroke-width:4}
|
|
16
|
+
.n:hover .s{stroke:var(--here)}
|
|
17
|
+
.s{stroke-width:2.5}
|
|
18
|
+
.done .s{fill:var(--ok-soft);stroke:var(--ok)}
|
|
19
|
+
.wait .s{fill:var(--warn-soft);stroke:var(--warn)}
|
|
20
|
+
.ask .s{fill:var(--surface);stroke:var(--line)}
|
|
21
|
+
.next .s{fill:var(--surface);stroke:var(--line);stroke-dasharray:6 5}
|
|
22
|
+
.start .s{fill:var(--here-soft);stroke:var(--here)}
|
|
23
|
+
.e{stroke:var(--line);stroke-width:2.5;fill:none}
|
|
24
|
+
.e.loop{stroke:var(--warn);stroke-dasharray:5 5}
|
|
25
|
+
.y{fill:var(--ok)!important}.no{fill:var(--warn)!important}
|
|
26
|
+
.det{background:var(--surface);border:2px solid var(--here);border-radius:14px;padding:12px 16px;display:grid;gap:4px;min-height:64px}
|
|
27
|
+
.det b{font-size:18px}
|
|
28
|
+
.det p{margin:0;max-width:70ch}
|
|
29
|
+
.key{display:flex;flex-wrap:wrap;gap:6px 16px;font-size:14px;font-weight:600}
|
|
30
|
+
.key span{display:inline-flex;align-items:center;gap:6px}
|
|
31
|
+
.key i{width:16px;height:12px;border-radius:3px;border:2px solid}
|
|
32
|
+
@media (prefers-reduced-motion:no-preference){.s{transition:stroke .12s}}
|
|
33
|
+
</style>
|
|
34
|
+
|
|
35
|
+
<div class="wrap">
|
|
36
|
+
<h1>Broker install fix: where it stands</h1>
|
|
37
|
+
<div class="key">
|
|
38
|
+
<span><i style="background:var(--ok-soft);border-color:var(--ok)"></i>Done</span>
|
|
39
|
+
<span><i style="background:var(--warn-soft);border-color:var(--warn)"></i>Stuck here now</span>
|
|
40
|
+
<span><i style="border-color:var(--line);border-style:dashed"></i>Next</span>
|
|
41
|
+
</div>
|
|
42
|
+
<div class="scroll">
|
|
43
|
+
<svg viewBox="0 0 1100 320" role="group" aria-label="Broker install fix flowchart. Main merged and tests pass. Two checks are stuck: GitHub still shows a conflict, and no checks ran. The merge waits for both.">
|
|
44
|
+
<defs>
|
|
45
|
+
<marker id="status-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0L10 5L0 10z" fill="currentColor" opacity=".7"/></marker>
|
|
46
|
+
<marker id="status-arrow-stuck" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0L10 5L0 10z" style="fill:var(--warn)"/></marker>
|
|
47
|
+
</defs>
|
|
48
|
+
|
|
49
|
+
<line class="e" x1="125" y1="95" x2="168" y2="95" marker-end="url(#status-arrow)"/>
|
|
50
|
+
<line class="e" x1="302" y1="95" x2="345" y2="95" marker-end="url(#status-arrow)"/>
|
|
51
|
+
<line class="e" x1="479" y1="95" x2="512" y2="95" marker-end="url(#status-arrow)"/>
|
|
52
|
+
<line class="e" x1="668" y1="95" x2="712" y2="95" marker-end="url(#status-arrow)"/>
|
|
53
|
+
<text class="lbl y" x="690" y="85" text-anchor="middle">yes</text>
|
|
54
|
+
<line class="e" x1="868" y1="95" x2="922" y2="95" marker-end="url(#status-arrow)"/>
|
|
55
|
+
<text class="lbl y" x="895" y="85" text-anchor="middle">yes</text>
|
|
56
|
+
|
|
57
|
+
<line class="e" x1="590" y1="150" x2="590" y2="208" marker-end="url(#status-arrow)"/>
|
|
58
|
+
<text class="lbl no" x="602" y="185">no</text>
|
|
59
|
+
<line class="e" x1="790" y1="150" x2="790" y2="208" marker-end="url(#status-arrow)"/>
|
|
60
|
+
<text class="lbl no" x="802" y="185">no</text>
|
|
61
|
+
|
|
62
|
+
<path class="e loop" d="M520 240 H496 V130 H560" marker-end="url(#status-arrow-stuck)"/>
|
|
63
|
+
<text class="lbl no" x="488" y="186" text-anchor="end">recheck</text>
|
|
64
|
+
<path class="e loop" d="M720 240 H694 V130 H760" marker-end="url(#status-arrow-stuck)"/>
|
|
65
|
+
|
|
66
|
+
<g class="n start" tabindex="0" data-k="start"><rect class="s" x="15" y="67" width="110" height="56" rx="28"/><text x="70" y="100" text-anchor="middle">Start fix</text></g>
|
|
67
|
+
<g class="n done" tabindex="0" data-k="merge"><rect class="s" x="170" y="67" width="132" height="56" rx="10"/><text x="236" y="100" text-anchor="middle">✓ Merge main</text></g>
|
|
68
|
+
<g class="n done" tabindex="0" data-k="tests"><rect class="s" x="347" y="67" width="132" height="56" rx="10"/><text x="413" y="100" text-anchor="middle">✓ 12 tests</text></g>
|
|
69
|
+
<g class="n ask" tabindex="0" data-k="q1"><polygon class="s" points="590,40 668,95 590,150 512,95"/><text x="590" y="91" text-anchor="middle">Conflict</text><text x="590" y="110" text-anchor="middle">cleared?</text></g>
|
|
70
|
+
<g class="n ask" tabindex="0" data-k="q2"><polygon class="s" points="790,40 868,95 790,150 712,95"/><text x="790" y="91" text-anchor="middle">Checks</text><text x="790" y="110" text-anchor="middle">ran?</text></g>
|
|
71
|
+
<g class="n next" tabindex="0" data-k="end"><rect class="s" x="924" y="67" width="150" height="56" rx="28"/><text x="999" y="100" text-anchor="middle">Merge</text></g>
|
|
72
|
+
<g class="n wait" tabindex="0" data-k="w1"><rect class="s" x="520" y="210" width="140" height="60" rx="10"/><text x="590" y="236" text-anchor="middle">⌛ Still shows</text><text x="590" y="256" text-anchor="middle">conflict</text></g>
|
|
73
|
+
<g class="n wait" tabindex="0" data-k="w2"><rect class="s" x="720" y="210" width="140" height="60" rx="10"/><text x="790" y="246" text-anchor="middle">⌛ Checks missing</text></g>
|
|
74
|
+
</svg>
|
|
75
|
+
</div>
|
|
76
|
+
<div class="det" id="det" aria-live="polite"></div>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<script type="application/json" id="detail-data">
|
|
80
|
+
{
|
|
81
|
+
"start":{"t":"Start fix","p":"Broker shared install path. A helper fixed it, then a hook blocked its commit. I committed and pushed."},
|
|
82
|
+
"merge":{"t":"Merge main: done","p":"Main merged in and pushed. 0 commits behind main. The test merge has no conflicts."},
|
|
83
|
+
"tests":{"t":"12 tests: done","p":"All 12 broker tests pass on the new commit."},
|
|
84
|
+
"q1":{"t":"Conflict cleared?","p":"GitHub's own merge check. It must clear before the merge."},
|
|
85
|
+
"w1":{"t":"Stuck: GitHub still shows a conflict","p":"No change after 15 minutes of checks every 30 seconds. My test merge shows no conflict."},
|
|
86
|
+
"q2":{"t":"Checks ran?","p":"A merge needs every check to pass on the current commit."},
|
|
87
|
+
"w2":{"t":"Stuck: no checks on the new commit","p":"The only check runs are from October 3. Main and the branch have no workflow files."},
|
|
88
|
+
"end":{"t":"Merge: next","p":"I hold the merge until both stuck items clear."}
|
|
89
|
+
}
|
|
90
|
+
</script>
|
|
91
|
+
<script>
|
|
92
|
+
const D=JSON.parse(document.getElementById('detail-data').textContent);
|
|
93
|
+
const det=document.getElementById('det');
|
|
94
|
+
const nodes=[...document.querySelectorAll('.n')];
|
|
95
|
+
function show(g){const d=D[g.dataset.k];if(!d)return;nodes.forEach(n=>n.classList.toggle('on',n===g));det.innerHTML='';const b=document.createElement('b');b.textContent=d.t;const p=document.createElement('p');p.textContent=d.p;det.append(b,p)}
|
|
96
|
+
nodes.forEach(g=>{g.setAttribute('role','button');g.addEventListener('click',()=>show(g));g.addEventListener('keydown',e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();show(g)}})});
|
|
97
|
+
const first=nodes.find(n=>n.classList.contains('wait'))||nodes[0];if(first)show(first);
|
|
98
|
+
</script>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# html-plan page template
|
|
2
2
|
|
|
3
|
-
Build a complex visual, such as a multi-part plan or evidence, from the html-plan skill. Load `house.css` from this folder after the plugin's `htmlplan.css`. The older [artifact page template](../artifact-page/README.md) stays available; use it when the request names it.
|
|
3
|
+
Build a complex visual, such as a multi-part plan or evidence, from the html-plan skill. A status or progress report prefers the [status flowchart](../artifact-page/status-flowchart.html), unless the person asks for another shape. Load `house.css` from this folder after the plugin's `htmlplan.css`. The older [artifact page template](../artifact-page/README.md) stays available; use it when the request names it.
|
|
4
4
|
|
|
5
5
|
## Get the skill
|
|
6
6
|
|
|
@@ -57,6 +57,7 @@ later pass, so this gate denies it and the model resends a shorter one.
|
|
|
57
57
|
|
|
58
58
|
from __future__ import annotations
|
|
59
59
|
|
|
60
|
+
import json
|
|
60
61
|
import os
|
|
61
62
|
import re
|
|
62
63
|
import sys
|
|
@@ -75,9 +76,14 @@ from visual_reply_rules import (
|
|
|
75
76
|
second_reply_violation,
|
|
76
77
|
visual_violation,
|
|
77
78
|
widget_anchor_violation,
|
|
79
|
+
widget_flowchart_advice,
|
|
78
80
|
)
|
|
79
81
|
from hooks_constants.hook_block_logger import log_hook_block
|
|
80
82
|
from hooks_constants.pre_tool_use_stdin import read_hook_input_dictionary_from_stdin
|
|
83
|
+
from hooks_constants.hook_specific_output_keys import (
|
|
84
|
+
HOOK_EVENT_NAME_KEY,
|
|
85
|
+
HOOK_SPECIFIC_OUTPUT_KEY,
|
|
86
|
+
)
|
|
81
87
|
from hooks_constants.reply_length_gate_constants import (
|
|
82
88
|
ALL_CHECKED_TOOL_NAMES,
|
|
83
89
|
ALL_DECISION_CARD_PROSE_KEYS,
|
|
@@ -101,6 +107,7 @@ from hooks_constants.reply_length_gate_constants import (
|
|
|
101
107
|
EM_DASH_MESSAGE,
|
|
102
108
|
EM_DASH_PATTERN,
|
|
103
109
|
FENCED_BLOCK_PATTERN,
|
|
110
|
+
ADDITIONAL_CONTEXT_KEY,
|
|
104
111
|
HOOK_EVENT_NAME,
|
|
105
112
|
INLINE_CODE_PATTERN,
|
|
106
113
|
LINE_BREAK_PATTERN,
|
|
@@ -322,6 +329,18 @@ def tool_violation(
|
|
|
322
329
|
return None if violation is None else (violation, reply_text)
|
|
323
330
|
|
|
324
331
|
|
|
332
|
+
def write_flowchart_advice(tool_name: object, all_tool_input: dict[str, object]) -> None:
|
|
333
|
+
"""Print the flowchart advice for an allowed widget call that is not a flowchart."""
|
|
334
|
+
mode_rules = active_mode_rules()
|
|
335
|
+
if not isinstance(tool_name, str) or mode_rules is None:
|
|
336
|
+
return
|
|
337
|
+
advice = widget_flowchart_advice(tool_name, all_tool_input, mode_rules)
|
|
338
|
+
if advice is None:
|
|
339
|
+
return
|
|
340
|
+
hook_event_output = {HOOK_EVENT_NAME_KEY: HOOK_EVENT_NAME, ADDITIONAL_CONTEXT_KEY: advice}
|
|
341
|
+
sys.stdout.write(json.dumps({HOOK_SPECIFIC_OUTPUT_KEY: hook_event_output}))
|
|
342
|
+
|
|
343
|
+
|
|
325
344
|
def main() -> int:
|
|
326
345
|
hook_input = read_hook_input_dictionary_from_stdin()
|
|
327
346
|
if hook_input is None:
|
|
@@ -332,6 +351,7 @@ def main() -> int:
|
|
|
332
351
|
return ALLOW_EXIT_CODE
|
|
333
352
|
checked = tool_violation(tool_name, tool_input, hook_input.get(TRANSCRIPT_PATH_KEY))
|
|
334
353
|
if checked is None:
|
|
354
|
+
write_flowchart_advice(tool_name, tool_input)
|
|
335
355
|
return ALLOW_EXIT_CODE
|
|
336
356
|
violation, reply_text = checked
|
|
337
357
|
block_reason = violation + RETRY_INSTRUCTION
|
|
@@ -635,13 +635,21 @@ def test_should_deny_an_anchor_link_inside_widget_code(
|
|
|
635
635
|
assert "does not open in the Claude app" in stderr_text
|
|
636
636
|
|
|
637
637
|
|
|
638
|
-
def
|
|
638
|
+
def test_should_allow_widget_code_with_no_anchor_and_advise_a_flowchart(
|
|
639
639
|
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
640
640
|
) -> None:
|
|
641
|
-
|
|
642
|
-
|
|
641
|
+
hook_input = {
|
|
642
|
+
"hook_event_name": "PreToolUse",
|
|
643
|
+
"tool_name": WIDGET_TOOL_NAME,
|
|
644
|
+
"tool_input": {"widget_code": "<div>Three steps</div>"},
|
|
645
|
+
}
|
|
646
|
+
monkeypatch.setattr(
|
|
647
|
+
"sys.stdin", io.TextIOWrapper(io.BytesIO(json.dumps(hook_input).encode("utf-8")))
|
|
643
648
|
)
|
|
649
|
+
exit_code = reply_length_gate.main()
|
|
644
650
|
assert exit_code == 0
|
|
651
|
+
advice_output = json.loads(capsys.readouterr().out)
|
|
652
|
+
assert "Redraw this widget as one SVG flowchart" in advice_output["hookSpecificOutput"]["additionalContext"]
|
|
645
653
|
|
|
646
654
|
|
|
647
655
|
def test_should_skip_the_mode_checks_when_the_switch_turns_the_mode_off(
|
|
@@ -30,7 +30,7 @@ Preconditions:
|
|
|
30
30
|
|
|
31
31
|
- Run each command from the repository root with Python and pytest available. The tests create transcript and payload fixtures.
|
|
32
32
|
|
|
33
|
-
- **Reply length.** Input is a reply with four sentences, a plain pull request number, an abbreviation, two sentences with no widget this turn, or widget code with an anchor link. Run `python -m pytest packages/claude-dev-env/hooks/blocking/test_reply_length_gate.py -q`. The adjacent test observes a denial and its count, link, or mode reason.
|
|
33
|
+
- **Reply length.** Input is a reply with four sentences, a plain pull request number, an abbreviation, two sentences with no widget this turn, or widget code with an anchor link. Run `python -m pytest packages/claude-dev-env/hooks/blocking/test_reply_length_gate.py -q`. The adjacent test observes a denial and its count, link, or mode reason. Widget code with no flowchart passes with a flowchart reminder in `additionalContext`.
|
|
34
34
|
- **Edit marker.** Input is an update message with strikethrough or an edit note. Run `python -m pytest packages/claude-dev-env/hooks/blocking/test_edit_marker_gate.py -q`. The adjacent test observes a denial; clean replacement text passes.
|
|
35
35
|
- **Handoff close.** Input is an issue comment that says the issue is closed and that the found defect belongs to another issue. Run `python -m pytest packages/claude-dev-env/hooks/blocking/test_issue_close_handoff_gate.py -q`. The adjacent test observes a denial; a close with a plain cross-reference and a routing comment on an open issue both pass.
|
|
36
36
|
- **Session title.** Input is a set session title call with no leading emoji or a name over 25 characters. Run `python -m pytest packages/claude-dev-env/hooks/blocking/test_session_title_format_gate.py -q`. The adjacent test observes a denial that names the broken rule; a well-formed title passes.
|
|
@@ -9,7 +9,7 @@ This family injects reminders and task guidance when an agent starts, resumes, s
|
|
|
9
9
|
- `session/working_style_prompt.py` injects the session's working style at session start.
|
|
10
10
|
- `session/advisor_rules_prompt.py` injects advisor guidance when the built-in advisor is enabled.
|
|
11
11
|
- `session/orchestrator_auto_starter.py` injects an orchestrator directive when its environment flag is enabled.
|
|
12
|
-
- `session/artifact_template_pointer.py` routes an agent that starts an artifact page by scope. One flow or one comparison is an inline SVG flowchart with minimal text. A multi-part plan or evidence is an html-plan page from `docs/templates/html-plan/README.md`. When the request names the older artifact page template, it points at `docs/templates/artifact-page/README.md`, which names the tracker template and the diagram template and which ask each fits.
|
|
12
|
+
- `session/artifact_template_pointer.py` routes an agent that starts an artifact page by scope. A status or progress report prefers the status flowchart from `docs/templates/artifact-page/status-flowchart.html`, unless the person asks for another shape. One flow or one comparison is an inline SVG flowchart with minimal text. A multi-part plan or evidence is an html-plan page from `docs/templates/html-plan/README.md`. When the request names the older artifact page template, it points at `docs/templates/artifact-page/README.md`, which names the tracker template and the diagram template and which ask each fits.
|
|
13
13
|
- `advisory/auto_mode_denial_quick_fix.py` proposes one `autoMode.allow` entry and a PowerShell block that writes it after an auto mode denial. It never retries the denied call.
|
|
14
14
|
|
|
15
15
|
## When it fires
|
|
@@ -30,7 +30,7 @@ Preconditions:
|
|
|
30
30
|
- **Working style.** Input is a SessionStart event. Run `python -m pytest packages/claude-dev-env/hooks/session/test_working_style_prompt.py -q`. The adjacent test observes the fixed context text.
|
|
31
31
|
- **Advisor guidance.** Input is SessionStart with `advisorModel` set. Run `python -m pytest packages/claude-dev-env/hooks/session/test_advisor_rules_prompt.py -q`. The adjacent test observes guidance only when the advisor setting is enabled.
|
|
32
32
|
- **Orchestrator start.** Input is SessionStart with its opt-in flag set. Run `python -m pytest packages/claude-dev-env/hooks/session/test_orchestrator_auto_starter.py -q`. The adjacent test observes an orchestrator directive.
|
|
33
|
-
- **Artifact template pointer.** Input is an `Artifact` quickstart call. Run `python -m pytest packages/claude-dev-env/hooks/session/test_artifact_template_pointer.py -q`. The adjacent test observes the html-plan path ahead of the template path in `additionalContext`, and silence on a publish call or another skill.
|
|
33
|
+
- **Artifact template pointer.** Input is an `Artifact` quickstart call. Run `python -m pytest packages/claude-dev-env/hooks/session/test_artifact_template_pointer.py -q`. The adjacent test observes the status flowchart path first, the html-plan path ahead of the template path in `additionalContext`, and silence on a publish call or another skill.
|
|
34
34
|
- **Denial quick fix.** Input is a `PermissionDenied` event with a bracketed rule label and a classifier verdict. Run `python -m pytest packages/claude-dev-env/hooks/advisory/test_auto_mode_denial_quick_fix.py -q`. The adjacent test observes the named rule, the allow entry, and the PowerShell block; a denial with no verdict gets a note and no block.
|
|
35
35
|
|
|
36
36
|
## Gotchas
|
|
@@ -10,10 +10,10 @@ __all__ = [
|
|
|
10
10
|
"ARTIFACT_TEMPLATE_PATH",
|
|
11
11
|
"ARTIFACT_TOOL_NAME",
|
|
12
12
|
"HTML_PLAN_TEMPLATE_README_PATH",
|
|
13
|
-
"
|
|
14
|
-
"POINTER_TEXT_PREFIX",
|
|
13
|
+
"POINTER_TEXT_TEMPLATE",
|
|
15
14
|
"SKILL_NAME_INPUT_KEY",
|
|
16
15
|
"SKILL_TOOL_NAME",
|
|
16
|
+
"STATUS_FLOWCHART_TEMPLATE_PATH",
|
|
17
17
|
"ALL_ARTIFACT_DESIGN_SKILL_NAMES",
|
|
18
18
|
]
|
|
19
19
|
|
|
@@ -26,12 +26,17 @@ ALL_ARTIFACT_DESIGN_SKILL_NAMES = frozenset({"artifact-design"})
|
|
|
26
26
|
TEMPLATES_DIRECTORY = Path(__file__).resolve().parents[2] / "docs" / "templates"
|
|
27
27
|
ARTIFACT_TEMPLATE_PATH = TEMPLATES_DIRECTORY / "artifact-page" / "README.md"
|
|
28
28
|
HTML_PLAN_TEMPLATE_README_PATH = TEMPLATES_DIRECTORY / "html-plan" / "README.md"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
"
|
|
29
|
+
STATUS_FLOWCHART_TEMPLATE_PATH = TEMPLATES_DIRECTORY / "artifact-page" / "status-flowchart.html"
|
|
30
|
+
POINTER_TEXT_TEMPLATE = (
|
|
31
|
+
"For a status or progress report on one piece of work, prefer the status"
|
|
32
|
+
" flowchart: done steps, yes-or-no checks, stuck steps with recheck loops, and"
|
|
33
|
+
" the next step, each opening its detail on click. Build it from"
|
|
34
|
+
" {path_of_status_flowchart} for every such report, multi-part ones included,"
|
|
35
|
+
" unless the person asks for another shape. For any other visual, pick by"
|
|
36
|
+
" scope. For one flow or one comparison, draw an inline SVG flowchart artifact"
|
|
37
|
+
" with minimal text, using the artifact-diagramming skill. For a multi-part"
|
|
38
|
+
" plan or evidence that is not a status report, build an html-plan page"
|
|
39
|
+
" following {path_of_plan_readme}. When"
|
|
40
|
+
" the request names the older artifact page template, pick the one that fits"
|
|
41
|
+
" the ask from {path_of_page_readme}."
|
|
37
42
|
)
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
"""Behavioral tests for the artifact template pointer's path."""
|
|
2
2
|
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
|
|
3
6
|
from hooks_constants import artifact_template_pointer_constants
|
|
4
7
|
|
|
5
8
|
|
|
@@ -13,6 +16,29 @@ def test_html_plan_readme_path_names_the_packaged_readme_and_house_stylesheet()
|
|
|
13
16
|
assert artifact_template_pointer_constants.HTML_PLAN_TEMPLATE_README_PATH.parent.joinpath("house.css").is_file()
|
|
14
17
|
|
|
15
18
|
|
|
19
|
+
def test_status_flowchart_path_names_the_packaged_template_file() -> None:
|
|
20
|
+
template_text = artifact_template_pointer_constants.STATUS_FLOWCHART_TEMPLATE_PATH.read_text(
|
|
21
|
+
encoding="utf-8"
|
|
22
|
+
)
|
|
23
|
+
assert "<svg" in template_text
|
|
24
|
+
assert "detail-data" in template_text
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_status_flowchart_gives_every_shape_a_detail_entry() -> None:
|
|
28
|
+
template_text = artifact_template_pointer_constants.STATUS_FLOWCHART_TEMPLATE_PATH.read_text(
|
|
29
|
+
encoding="ascii"
|
|
30
|
+
)
|
|
31
|
+
all_shape_keys = set(re.findall(r'data-k="([^"]+)"', template_text))
|
|
32
|
+
detail_block = re.search(
|
|
33
|
+
r'<script type="application/json" id="detail-data">(.*?)</script>', template_text, re.DOTALL
|
|
34
|
+
)
|
|
35
|
+
assert detail_block is not None
|
|
36
|
+
all_detail_keys = set(json.loads(detail_block.group(1)))
|
|
37
|
+
|
|
38
|
+
assert all_shape_keys
|
|
39
|
+
assert all_shape_keys == all_detail_keys
|
|
40
|
+
|
|
41
|
+
|
|
16
42
|
def test_every_exported_name_resolves_to_a_module_attribute() -> None:
|
|
17
43
|
all_missing_names = [
|
|
18
44
|
each_name
|
|
@@ -26,6 +26,7 @@ def test_rule_key_constants_name_rules_in_the_shipped_rule_list() -> None:
|
|
|
26
26
|
constants.NO_PICTURE_RULE_KEY,
|
|
27
27
|
constants.ABBREVIATIONS_RULE_KEY,
|
|
28
28
|
constants.ONE_ITEM_RULE_KEY,
|
|
29
|
+
constants.FLOWCHART_RULE_KEY,
|
|
29
30
|
} <= all_shipped_keys
|
|
30
31
|
assert constants.ALLOWED_CAPITALIZED_WORDS_KEY in rules_document
|
|
31
32
|
assert constants.LOWERCASE_ABBREVIATIONS_KEY in rules_document
|
|
@@ -36,6 +36,15 @@ TRACKER_NUMBER_PATTERN = re.compile(
|
|
|
36
36
|
r"\b(?:issues?|items?|cards?|tickets?|tasks?)\s*#?\d+", re.IGNORECASE
|
|
37
37
|
)
|
|
38
38
|
WIDGET_ANCHOR_PATTERN = re.compile(r"<a\b[^>]*\bhref\s*=", re.IGNORECASE)
|
|
39
|
+
FLOWCHART_RULE_KEY = "not_flowchart"
|
|
40
|
+
WIDGET_CODE_KEY = "widget_code"
|
|
41
|
+
WIDGET_INPUT_KEY = "input"
|
|
42
|
+
FLOWCHART_SVG_PATTERN = re.compile(r"<svg\b", re.IGNORECASE)
|
|
43
|
+
FLOWCHART_ARROW_PATTERN = re.compile(r"marker-end", re.IGNORECASE)
|
|
44
|
+
FLOWCHART_MARKER_BLOCK_PATTERN = re.compile(r"<marker\b[^>]*(?<!/)>.*?</marker>", re.IGNORECASE | re.DOTALL)
|
|
45
|
+
FLOWCHART_DIAMOND_PATTERN = re.compile(
|
|
46
|
+
r"<polygon\b|<path\b[^>]*\bd=[\"']M[^\"']*L[^\"']*L[^\"']*L[^\"']*Z[\"']", re.IGNORECASE
|
|
47
|
+
)
|
|
39
48
|
|
|
40
49
|
TRANSCRIPT_ENCODING = "utf-8"
|
|
41
50
|
ENTRY_TYPE_KEY = "type"
|
|
@@ -57,6 +66,7 @@ ABBREVIATION_MESSAGE = 'Abbreviation "{abbreviation}" in the text. {reminder}'
|
|
|
57
66
|
TRACKER_NUMBER_MESSAGE = 'Tracker number "{reference}" in the text. {reminder}'
|
|
58
67
|
NO_VISUAL_MESSAGE = "This reply has {sentence_count} sentences and no visual this turn. {reminder}"
|
|
59
68
|
SECOND_REPLY_MESSAGE = "A reply already went out this turn. {reminder}"
|
|
69
|
+
NOT_FLOWCHART_MESSAGE = "This widget is not a flowchart. {reminder}"
|
|
60
70
|
WIDGET_ANCHOR_MESSAGE = (
|
|
61
71
|
"A link sits inside the widget code, and a widget link does not open in the Claude app."
|
|
62
72
|
" Remove the anchor and put the page link in the reply text."
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""PreToolUse hook: route an agent about to build an artifact page by scope.
|
|
3
3
|
|
|
4
|
-
Registered on ``Artifact|Skill``. It adds one
|
|
5
|
-
when a session starts a new artifact page.
|
|
4
|
+
Registered on ``Artifact|Skill``. It adds one paragraph of ``additionalContext``
|
|
5
|
+
when a session starts a new artifact page. A status or progress report
|
|
6
|
+
prefers the status flowchart from
|
|
7
|
+
``docs/templates/artifact-page/status-flowchart.html``, unless the person asks
|
|
8
|
+
for another shape. One flow or one comparison is an
|
|
6
9
|
inline SVG flowchart with minimal text. A multi-part plan or evidence is an
|
|
7
10
|
html-plan page from ``docs/templates/html-plan/README.md``, and
|
|
8
11
|
``docs/templates/artifact-page/README.md`` stays the alternative:
|
|
@@ -34,10 +37,10 @@ from hooks_constants.artifact_template_pointer_constants import (
|
|
|
34
37
|
ARTIFACT_TEMPLATE_PATH,
|
|
35
38
|
ARTIFACT_TOOL_NAME,
|
|
36
39
|
HTML_PLAN_TEMPLATE_README_PATH,
|
|
37
|
-
|
|
38
|
-
POINTER_TEXT_PREFIX,
|
|
40
|
+
POINTER_TEXT_TEMPLATE,
|
|
39
41
|
SKILL_NAME_INPUT_KEY,
|
|
40
42
|
SKILL_TOOL_NAME,
|
|
43
|
+
STATUS_FLOWCHART_TEMPLATE_PATH,
|
|
41
44
|
)
|
|
42
45
|
from hooks_constants.hook_specific_output_keys import (
|
|
43
46
|
HOOK_EVENT_NAME_KEY,
|
|
@@ -81,9 +84,10 @@ def decide_hook_output(all_hook_fields: dict[str, object]) -> dict[str, object]
|
|
|
81
84
|
return {
|
|
82
85
|
HOOK_SPECIFIC_OUTPUT_KEY: {
|
|
83
86
|
HOOK_EVENT_NAME_KEY: PRE_TOOL_USE_EVENT_NAME,
|
|
84
|
-
ADDITIONAL_CONTEXT_KEY: (
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
ADDITIONAL_CONTEXT_KEY: POINTER_TEXT_TEMPLATE.format(
|
|
88
|
+
path_of_status_flowchart=STATUS_FLOWCHART_TEMPLATE_PATH,
|
|
89
|
+
path_of_plan_readme=HTML_PLAN_TEMPLATE_README_PATH,
|
|
90
|
+
path_of_page_readme=ARTIFACT_TEMPLATE_PATH,
|
|
87
91
|
),
|
|
88
92
|
}
|
|
89
93
|
}
|
|
@@ -13,6 +13,7 @@ import artifact_template_pointer
|
|
|
13
13
|
from hooks_constants.artifact_template_pointer_constants import (
|
|
14
14
|
ARTIFACT_TEMPLATE_PATH,
|
|
15
15
|
HTML_PLAN_TEMPLATE_README_PATH,
|
|
16
|
+
STATUS_FLOWCHART_TEMPLATE_PATH,
|
|
16
17
|
)
|
|
17
18
|
|
|
18
19
|
HOOK_SCRIPT = Path(__file__).resolve().parent / "artifact_template_pointer.py"
|
|
@@ -50,13 +51,29 @@ def test_pointer_routes_a_simple_visual_to_a_flowchart_ahead_of_html_plan() -> N
|
|
|
50
51
|
)
|
|
51
52
|
assert hook_output is not None
|
|
52
53
|
pointer_text = hook_output["hookSpecificOutput"]["additionalContext"]
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
simple_visual_position = pointer_text.index("one flow or one comparison")
|
|
55
|
+
simple_flowchart_position = pointer_text.index("flowchart", simple_visual_position)
|
|
56
|
+
assert simple_flowchart_position < pointer_text.index("multi-part plan or evidence")
|
|
55
57
|
assert pointer_text.index("multi-part plan or evidence") < pointer_text.index(
|
|
56
58
|
str(HTML_PLAN_TEMPLATE_README_PATH)
|
|
57
59
|
)
|
|
58
60
|
|
|
59
61
|
|
|
62
|
+
def test_pointer_prefers_the_status_flowchart_for_a_status_visual_first() -> None:
|
|
63
|
+
hook_output = artifact_template_pointer.decide_hook_output(
|
|
64
|
+
_payload("Artifact", {"action": "quickstart", "intent": "other"})
|
|
65
|
+
)
|
|
66
|
+
assert hook_output is not None
|
|
67
|
+
pointer_text = hook_output["hookSpecificOutput"]["additionalContext"]
|
|
68
|
+
assert pointer_text.index("status or progress") < pointer_text.index(
|
|
69
|
+
str(STATUS_FLOWCHART_TEMPLATE_PATH)
|
|
70
|
+
)
|
|
71
|
+
assert pointer_text.index(str(STATUS_FLOWCHART_TEMPLATE_PATH)) < pointer_text.index(
|
|
72
|
+
"one flow or one comparison"
|
|
73
|
+
)
|
|
74
|
+
assert "another shape" in pointer_text
|
|
75
|
+
|
|
76
|
+
|
|
60
77
|
def test_artifact_design_skill_gets_the_template_pointer() -> None:
|
|
61
78
|
hook_output = artifact_template_pointer.decide_hook_output(
|
|
62
79
|
_payload("Skill", {"skill": "artifact-design"})
|
|
@@ -24,6 +24,7 @@ CONTEXT_OUTPUT_PATTERN = re.compile(r"additionalContext|ADDITIONAL_CONTEXT|addit
|
|
|
24
24
|
UNMEASURED_REASON_BY_SCRIPT = {
|
|
25
25
|
"blocking/pre_tool_use_dispatcher.py": "relays its hosted hooks' output",
|
|
26
26
|
"blocking/bash_pre_tool_use_dispatcher.py": "relays its hosted hooks' output",
|
|
27
|
+
"blocking/reply_length_gate.py": "advises only on a widget while visual reply mode is on",
|
|
27
28
|
"lifecycle/nested_project_hooks.py": "relays a nested project's own hooks",
|
|
28
29
|
"routing/thread_spawn_pace_hook.py": "quotes a usage reading capped by its own constant",
|
|
29
30
|
"session/issue_tracker_session_starter.py": "emits only for a repository the user's "
|
|
@@ -8,6 +8,7 @@ _hooks_directory = str(Path(__file__).resolve().parent)
|
|
|
8
8
|
if _hooks_directory not in sys.path:
|
|
9
9
|
sys.path.insert(0, _hooks_directory)
|
|
10
10
|
|
|
11
|
+
from hooks_constants.visual_reply_rules_constants import RULES_FILE_PATH
|
|
11
12
|
from visual_reply_rules import (
|
|
12
13
|
abbreviation_violation,
|
|
13
14
|
load_rules,
|
|
@@ -17,6 +18,7 @@ from visual_reply_rules import (
|
|
|
17
18
|
visual_shown_this_turn,
|
|
18
19
|
visual_violation,
|
|
19
20
|
widget_anchor_violation,
|
|
21
|
+
widget_flowchart_advice,
|
|
20
22
|
)
|
|
21
23
|
|
|
22
24
|
SHIPPED_RULES = load_rules()
|
|
@@ -162,3 +164,46 @@ def test_second_reply_violation_denies_only_after_a_delivered_reply(tmp_path: Pa
|
|
|
162
164
|
)
|
|
163
165
|
assert second_reply_violation(str(transcript_path), SHIPPED_RULES) is not None
|
|
164
166
|
assert second_reply_violation(str(tmp_path / "missing.jsonl"), SHIPPED_RULES) is None
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
FLOWCHART_CODE = (
|
|
170
|
+
'<svg><path d="M0 20L40 0L80 20L40 40Z"/>'
|
|
171
|
+
'<path d="M80 20H120" marker-end="url(#a)"/></svg>'
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def test_widget_flowchart_advice_should_pass_an_svg_with_a_diamond_and_arrows() -> None:
|
|
176
|
+
rules = load_rules()
|
|
177
|
+
assert rules is not None
|
|
178
|
+
nested_input = {"input": {"widget_code": FLOWCHART_CODE}}
|
|
179
|
+
assert widget_flowchart_advice("mcp__hearthbot__post_widget", nested_input, rules) is None
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def test_widget_flowchart_advice_should_advise_on_cards_and_shapes_without_a_decision() -> None:
|
|
183
|
+
rules = load_rules()
|
|
184
|
+
assert rules is not None
|
|
185
|
+
card_input = {"widget_code": "<div class='card'>Merged</div>"}
|
|
186
|
+
no_diamond_input = {"widget_code": '<svg><path marker-end="url(#a)"/></svg>'}
|
|
187
|
+
arrowhead_only_input = {
|
|
188
|
+
"widget_code": '<svg><marker id="a"><polygon points="0 0,9 5,0 9"/></marker>'
|
|
189
|
+
'<rect/><path marker-end="url(#a)"/></svg>'
|
|
190
|
+
}
|
|
191
|
+
for each_input in (card_input, no_diamond_input, arrowhead_only_input):
|
|
192
|
+
reason = widget_flowchart_advice("mcp__hearthbot__post_widget", each_input, rules)
|
|
193
|
+
assert reason is not None and "diamond" in reason
|
|
194
|
+
assert widget_flowchart_advice("mcp__hearthbot__reply", card_input, rules) is None
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def test_widget_flowchart_advice_should_stay_quiet_when_the_rule_list_has_no_flowchart_rule(
|
|
198
|
+
tmp_path: Path,
|
|
199
|
+
) -> None:
|
|
200
|
+
shipped_document = json.loads(RULES_FILE_PATH.read_text(encoding="utf-8"))
|
|
201
|
+
shipped_document["rules"] = [
|
|
202
|
+
each for each in shipped_document["rules"] if each["key"] != "not_flowchart"
|
|
203
|
+
]
|
|
204
|
+
rules_path = tmp_path / "rules.json"
|
|
205
|
+
rules_path.write_text(json.dumps(shipped_document), encoding="utf-8")
|
|
206
|
+
rules = load_rules(rules_path)
|
|
207
|
+
assert rules is not None
|
|
208
|
+
card_input = {"widget_code": "<div class='card'>Merged</div>"}
|
|
209
|
+
assert widget_flowchart_advice("mcp__hearthbot__post_widget", card_input, rules) is None
|
|
@@ -58,6 +58,14 @@ from hooks_constants.visual_reply_rules_constants import (
|
|
|
58
58
|
TRANSCRIPT_ENCODING,
|
|
59
59
|
USER_ENTRY_TYPE,
|
|
60
60
|
WIDGET_ANCHOR_MESSAGE,
|
|
61
|
+
FLOWCHART_ARROW_PATTERN,
|
|
62
|
+
FLOWCHART_DIAMOND_PATTERN,
|
|
63
|
+
FLOWCHART_MARKER_BLOCK_PATTERN,
|
|
64
|
+
FLOWCHART_RULE_KEY,
|
|
65
|
+
FLOWCHART_SVG_PATTERN,
|
|
66
|
+
NOT_FLOWCHART_MESSAGE,
|
|
67
|
+
WIDGET_CODE_KEY,
|
|
68
|
+
WIDGET_INPUT_KEY,
|
|
61
69
|
WIDGET_ANCHOR_PATTERN,
|
|
62
70
|
WIDGET_TOOL_NAME_SUFFIX,
|
|
63
71
|
)
|
|
@@ -153,6 +161,35 @@ def widget_anchor_violation(tool_name: str, all_tool_input: dict[str, object]) -
|
|
|
153
161
|
return WIDGET_ANCHOR_MESSAGE if WIDGET_ANCHOR_PATTERN.search(serialized_input) else None
|
|
154
162
|
|
|
155
163
|
|
|
164
|
+
def widget_flowchart_advice(
|
|
165
|
+
tool_name: str, all_tool_input: dict[str, object], rules: VisualReplyRules
|
|
166
|
+
) -> str | None:
|
|
167
|
+
"""Return the advice for widget code that is not a flowchart, else None.
|
|
168
|
+
|
|
169
|
+
The gate passes this advice to the session and still allows the call.
|
|
170
|
+
|
|
171
|
+
A flowchart widget is one SVG with at least one arrow marker and one
|
|
172
|
+
diamond shape for a yes or no check. Shapes inside a ``<marker>`` block
|
|
173
|
+
are arrowheads, so they never count as the diamond::
|
|
174
|
+
|
|
175
|
+
<svg><path d="M0 0L9 5L0 9L-9 5Z"/><path marker-end="url(#a)"/></svg>
|
|
176
|
+
"""
|
|
177
|
+
all_rule_keys = {each.key for each in rules.all_rules}
|
|
178
|
+
if not tool_name.endswith(WIDGET_TOOL_NAME_SUFFIX) or FLOWCHART_RULE_KEY not in all_rule_keys:
|
|
179
|
+
return None
|
|
180
|
+
widget_code = all_tool_input.get(WIDGET_CODE_KEY)
|
|
181
|
+
if widget_code is None:
|
|
182
|
+
widget_input = all_tool_input.get(WIDGET_INPUT_KEY)
|
|
183
|
+
widget_code = widget_input.get(WIDGET_CODE_KEY) if isinstance(widget_input, dict) else None
|
|
184
|
+
code_text = FLOWCHART_MARKER_BLOCK_PATTERN.sub(
|
|
185
|
+
"", widget_code if isinstance(widget_code, str) else ""
|
|
186
|
+
)
|
|
187
|
+
all_shape_patterns = (FLOWCHART_SVG_PATTERN, FLOWCHART_ARROW_PATTERN, FLOWCHART_DIAMOND_PATTERN)
|
|
188
|
+
if all(each_pattern.search(code_text) for each_pattern in all_shape_patterns):
|
|
189
|
+
return None
|
|
190
|
+
return NOT_FLOWCHART_MESSAGE.format(reminder=rules.reminder(FLOWCHART_RULE_KEY))
|
|
191
|
+
|
|
192
|
+
|
|
156
193
|
def _parsed_entries(all_transcript_lines: Iterable[str]) -> list[dict[str, object]]:
|
|
157
194
|
all_entries = []
|
|
158
195
|
for each_line in all_transcript_lines:
|
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
{
|
|
9
9
|
"key": "no_picture",
|
|
10
10
|
"label": "No picture",
|
|
11
|
-
"reminder": "Show the item as one visual first, a widget or a published page. Then send one reply of one to three short sentences that states the outcome and each fact the user needs, and links the page."
|
|
11
|
+
"reminder": "Show the item as one visual first, a widget or a published page. Prefer a flowchart that runs left to right, with boxes for steps, a diamond for each yes or no check, and arrows between them. Then send one reply of one to three short sentences that states the outcome and each fact the user needs, and links the page."
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
14
|
"key": "too_many_words",
|
|
@@ -24,6 +24,16 @@
|
|
|
24
24
|
"key": "wall_of_text",
|
|
25
25
|
"label": "Wall of text",
|
|
26
26
|
"reminder": "Put each point, and each step the reader takes, on its own line."
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"key": "not_flowchart",
|
|
30
|
+
"label": "No flowchart",
|
|
31
|
+
"reminder": "Redraw this widget as one SVG flowchart that runs left to right, with boxes for steps, a diamond for each yes or no check, and arrows between them, unless the item has no steps or checks."
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"key": "use_flowchart",
|
|
35
|
+
"label": "Use the flowchart",
|
|
36
|
+
"reminder": "Show the status as the status flowchart from docs/templates/artifact-page/status-flowchart.html: done steps, yes-or-no checks, stuck steps with recheck loops, and the next step, each opening its detail on click, unless the person asked for another shape."
|
|
27
37
|
}
|
|
28
38
|
],
|
|
29
39
|
"allowed_capitalized_words": [
|