claude-dev-env 8.56.2 → 8.58.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/.agents/skills/correction/SKILL.md +25 -0
- package/bin/ever-shipped-skills.mjs +1 -0
- package/bin/ever-shipped-skills.test.mjs +4 -0
- package/docs/templates/artifact-page/README.md +11 -0
- package/docs/templates/artifact-page/review-rounds.md +26 -0
- package/docs/templates/artifact-page/template.html +54 -6
- package/package.json +1 -1
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: correction
|
|
3
|
+
description: Turn a correction the user just made into a short handoff brief they paste into a corrections intake. Use when the user types /correction followed by what the agent got wrong.
|
|
4
|
+
argument-hint: "<what the agent got wrong, in the user's words>"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /correction
|
|
8
|
+
|
|
9
|
+
The user corrected an agent and wants the fix to stick. Write one brief they can paste into the place that turns corrections into lasting fixes.
|
|
10
|
+
|
|
11
|
+
`$ARGUMENTS` is the user's statement of the correction. Fill the other fields from this session: the transcript, the files and pull requests it touched, and the messages it sent. When the session does not settle a field, write `unknown` there.
|
|
12
|
+
|
|
13
|
+
Print the brief as one fenced `text` block, and nothing after it:
|
|
14
|
+
|
|
15
|
+
```text
|
|
16
|
+
Correction: <$ARGUMENTS, word for word>
|
|
17
|
+
Asked: <what the user asked for, quoted where a message carries it>
|
|
18
|
+
Agent did: <what the agent did, with the reply, file, or command that shows it>
|
|
19
|
+
Corrected to: <what the user wanted instead, or the fix already made>
|
|
20
|
+
Where: <repository, branch or pull request, files, tool, or thread>
|
|
21
|
+
Evidence: <file:line, log line, or quoted message that shows the miss>
|
|
22
|
+
Repeat: <yes, with the earlier time it happened | no | unknown>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Keep each field to one or two lines. Leave out the layer and the fix design; the intake decides those.
|
|
@@ -41,6 +41,10 @@ test('EVER_SHIPPED_SKILL_NAMES includes the pull request lifecycle skill', () =>
|
|
|
41
41
|
assert.equal(EVER_SHIPPED_SKILL_NAMES.has('pr-lifecycle'), true);
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
+
test('EVER_SHIPPED_SKILL_NAMES includes the correction skill', () => {
|
|
45
|
+
assert.equal(EVER_SHIPPED_SKILL_NAMES.has('correction'), true);
|
|
46
|
+
});
|
|
47
|
+
|
|
44
48
|
test('EVER_SHIPPED_SKILL_NAMES includes the windows scheduled task skill', () => {
|
|
45
49
|
assert.equal(EVER_SHIPPED_SKILL_NAMES.has('windows-scheduled-task'), true);
|
|
46
50
|
});
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
| Readable text | Text is 16px or larger and uses the full text color. Gray stays on lines and borders only. Text holds a 7:1 contrast ratio against its background. |
|
|
17
17
|
| Touch targets | Buttons and chips are 40px or taller, with a visible focus ring. |
|
|
18
18
|
| Copy fallback | The copy button uses the clipboard API and falls back to a hidden text area, then says when the copy is blocked. |
|
|
19
|
+
| Review rounds | Only the parts changed since the viewer's last look carry a thin bar and a "Changed" label. [review-rounds.md](review-rounds.md) holds the data fields and behavior. |
|
|
19
20
|
|
|
20
21
|
## House rules for a page built from it
|
|
21
22
|
|
|
@@ -23,3 +24,13 @@
|
|
|
23
24
|
- A page that needs extra files publishes them beside the page through the Artifact `files` field, and links them by relative path.
|
|
24
25
|
- A page that needs live data or saved answers declares the matching runtime capability. `localStorage` holds only per-viewer conveniences.
|
|
25
26
|
- Render the page at 400px in light and dark, and exercise every control, before publishing.
|
|
27
|
+
|
|
28
|
+
## Convert an existing page
|
|
29
|
+
|
|
30
|
+
Ask an agent: "convert <artifact link> to our format."
|
|
31
|
+
|
|
32
|
+
1. Read the published page with the Artifact tool's `read` action, and keep its data and every working control.
|
|
33
|
+
2. Map each part to its piece in `template.html`: status groups to board lanes, records to picture cards, filters to tabs and chips, and the data to the JSON data block.
|
|
34
|
+
3. Apply the house rules above and the template's color tokens.
|
|
35
|
+
4. Check that text is 16px or larger, text contrast is 7:1 or better, and the page has no horizontal scroll at 390px, in light and dark.
|
|
36
|
+
5. Publish to the same link as a new version, so the reviewer keeps one URL.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Marking what changed between review rounds
|
|
2
|
+
|
|
3
|
+
A page that a reviewer reads more than once shows what changed since their last look. The template marks only the parts that changed: each one gets a thin bar on its left edge and a small "Changed" label at its start. The card around them stays as it is, and its board chip gets a dot in the same color. There is no diff and no list of changes.
|
|
4
|
+
|
|
5
|
+
## Data
|
|
6
|
+
|
|
7
|
+
| Field | Where | Meaning |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| `round` | top of the data block | The review round this revision answers. Add one each time the page is republished after feedback. |
|
|
10
|
+
| `changed_round` | an item | The round in which this item last changed. |
|
|
11
|
+
| `changed_parts` | an item | The parts that changed: `title`, `next`, `summary`, `steps`, `links`. An item with no list gets no mark. |
|
|
12
|
+
|
|
13
|
+
When a revision answers feedback, set `round` to the new number and stamp each item it changed with that number and the parts it touched. Leave older stamps in place; they stop showing once the reviewer has seen their round.
|
|
14
|
+
|
|
15
|
+
## Behavior
|
|
16
|
+
|
|
17
|
+
- The page keeps the last round each viewer saw in `localStorage`. Parts stamped after that round are marked.
|
|
18
|
+
- A first visit, or a browser that blocks storage, marks the parts stamped in the current round.
|
|
19
|
+
- The marks hold through a reload in the same tab, through `sessionStorage`, and clear on the next visit.
|
|
20
|
+
- A changed summary opens by itself.
|
|
21
|
+
|
|
22
|
+
## Other pages
|
|
23
|
+
|
|
24
|
+
Stamp the smallest element that changed with `data-changed-round="N"`; it takes the bar and the "Changed" label, and a `<details>` element takes the label in its summary. Never stamp a whole card or section. An element with `data-change-mark="dot"` takes the dot and a screen reader label in place of the bar and label.
|
|
25
|
+
|
|
26
|
+
The `--change` and `--change-ink` tokens hold the mark colors in light and dark. Keep the label text at 7:1 or better against `--change`, and the bar at 3:1 or better against `--surface` and `--bg`.
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
--accent: #2245a2; --accent-soft: #e6ecfb; --accent-ink: #ffffff;
|
|
8
8
|
--ok: #165737; --ok-soft: #e3f3ea; --bad: #8b2621; --bad-soft: #fbe5e3; --warn: #6f4100; --warn-soft: #fbf0dc;
|
|
9
9
|
--violet: #553b95; --violet-soft: #efe9fa;
|
|
10
|
+
--change: #8a1f6b; --change-ink: #ffffff;
|
|
10
11
|
--font-body: "Manrope", system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
11
12
|
}
|
|
12
13
|
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) {
|
|
@@ -14,12 +15,14 @@
|
|
|
14
15
|
--accent: #a8bfff; --accent-soft: #1f2a45; --accent-ink: #0d1424;
|
|
15
16
|
--ok: #6fd3a0; --ok-soft: #173327; --bad: #ffa8a0; --bad-soft: #3a1c1a; --warn: #f0b860; --warn-soft: #362a14;
|
|
16
17
|
--violet: #c5b1f2; --violet-soft: #251f38;
|
|
18
|
+
--change: #f5a3dc; --change-ink: #1f0a19;
|
|
17
19
|
color-scheme: dark } }
|
|
18
20
|
:root[data-theme="dark"] {
|
|
19
21
|
--bg: #12151b; --surface: #1a1f28; --fg: #e6e9ef; --edge: #6d7891; --line: #2b3240;
|
|
20
22
|
--accent: #a8bfff; --accent-soft: #1f2a45; --accent-ink: #0d1424;
|
|
21
23
|
--ok: #6fd3a0; --ok-soft: #173327; --bad: #ffa8a0; --bad-soft: #3a1c1a; --warn: #f0b860; --warn-soft: #362a14;
|
|
22
24
|
--violet: #c5b1f2; --violet-soft: #251f38;
|
|
25
|
+
--change: #f5a3dc; --change-ink: #1f0a19;
|
|
23
26
|
color-scheme: dark }
|
|
24
27
|
* { box-sizing: border-box }
|
|
25
28
|
body { background: var(--bg); color: var(--fg); font-family: var(--font-body); font-size: 17px; line-height: 1.4; padding-inline: 16px; padding-block: 24px 48px; margin: 0 }
|
|
@@ -95,6 +98,12 @@ details.what[open] summary::before { content: "▾" }
|
|
|
95
98
|
.steps .sh { font-weight: 800 }
|
|
96
99
|
.step { display: flex; gap: 10px; align-items: flex-start }
|
|
97
100
|
.step .num { flex: none; width: 30px; height: 30px; border-radius: 50%; background: var(--warn); color: var(--surface); display: grid; place-items: center; font-weight: 800 }
|
|
101
|
+
.lchip.changed::before { content: ""; width: 10px; height: 10px; border-radius: 50%; background: var(--change); flex: none }
|
|
102
|
+
.changed { box-shadow: inset 4px 0 0 var(--change); padding-left: 12px; border-radius: 4px }
|
|
103
|
+
.next.changed { border-radius: 12px }
|
|
104
|
+
.lchip.changed { box-shadow: none; border-radius: 999px; padding-left: 10px; border-color: var(--change); display: inline-flex; align-items: center; gap: 6px }
|
|
105
|
+
.pill-changed { display: inline-flex; align-items: center; min-height: 28px; padding: 0 10px; border-radius: 999px; background: var(--change); color: var(--change-ink); font-size: 16px; font-weight: 800; margin-right: 8px; vertical-align: middle; flex: none; white-space: nowrap }
|
|
106
|
+
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap }
|
|
98
107
|
.empty { background: var(--surface); border: 2px dashed var(--edge); border-radius: 14px; padding: 20px; font-weight: 600 }
|
|
99
108
|
@media (hover: hover) { .lchip:hover, .fchip:hover, .tab:hover, .copy:hover { border-color: var(--accent) } }
|
|
100
109
|
</style>
|
|
@@ -117,6 +126,7 @@ details.what[open] summary::before { content: "▾" }
|
|
|
117
126
|
<script type="application/json" id="tracker-data">
|
|
118
127
|
{
|
|
119
128
|
"updated": "2026-01-15T17:30:00Z",
|
|
129
|
+
"round": 3,
|
|
120
130
|
"owners": [
|
|
121
131
|
{
|
|
122
132
|
"key": "web",
|
|
@@ -147,6 +157,8 @@ details.what[open] summary::before { content: "▾" }
|
|
|
147
157
|
"needs_you": true,
|
|
148
158
|
"card": true,
|
|
149
159
|
"next": "choose between the two dates in the linked plan.",
|
|
160
|
+
"changed_round": 3,
|
|
161
|
+
"changed_parts": ["next"],
|
|
150
162
|
"summary": [
|
|
151
163
|
"One release date everyone plans around",
|
|
152
164
|
"The launch checklist dated backward from it",
|
|
@@ -167,6 +179,8 @@ details.what[open] summary::before { content: "▾" }
|
|
|
167
179
|
"status": "blocked",
|
|
168
180
|
"card": true,
|
|
169
181
|
"next": "wait for the staging database to come back online.",
|
|
182
|
+
"changed_round": 2,
|
|
183
|
+
"changed_parts": ["next"],
|
|
170
184
|
"summary": [
|
|
171
185
|
"The login test passes ten runs in a row",
|
|
172
186
|
"The root cause written in the pull request"
|
|
@@ -186,6 +200,8 @@ details.what[open] summary::before { content: "▾" }
|
|
|
186
200
|
"status": "doing",
|
|
187
201
|
"card": true,
|
|
188
202
|
"next": "finish the index change and rerun the timing check.",
|
|
203
|
+
"changed_round": 3,
|
|
204
|
+
"changed_parts": ["summary"],
|
|
189
205
|
"summary": [
|
|
190
206
|
"Search answers in under one second",
|
|
191
207
|
"A timing check that guards it",
|
|
@@ -288,6 +304,8 @@ const STAGE_BY_KEY = Object.fromEntries(STAGES.map(s => [s.key, s]));
|
|
|
288
304
|
const TRACK = ["Card", "Builder", "Pull request", "Merged"];
|
|
289
305
|
const state = { tab: "open", owner: "all", query: "" };
|
|
290
306
|
try { const saved = JSON.parse(localStorage.getItem("work-tracker-view") || "{}"); if (saved.tab) state.tab = saved.tab; if (saved.owner) state.owner = saved.owner; } catch (e) {}
|
|
307
|
+
const REVIEW_ROUND = Number(TRACKER.round) || 0;
|
|
308
|
+
const CHANGE_BASELINE = changeBaseline();
|
|
291
309
|
const ownerByKey = Object.fromEntries(TRACKER.owners.map(o => [o.key, o]));
|
|
292
310
|
const isDone = it => it.status === "done";
|
|
293
311
|
function stageOf(it) {
|
|
@@ -300,6 +318,33 @@ function stageOf(it) {
|
|
|
300
318
|
const slug = it => "item-" + it.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
301
319
|
const fmt = iso => { if (!iso) return ""; try { return new Date(iso).toLocaleString(undefined, { month: "long", day: "numeric", hour: "numeric", minute: "2-digit" }); } catch (e) { return iso; } };
|
|
302
320
|
const el = (tag, cls, text) => { const n = document.createElement(tag); if (cls) n.className = cls; if (text != null) n.textContent = text; return n; };
|
|
321
|
+
function changeBaseline() {
|
|
322
|
+
try {
|
|
323
|
+
const held = JSON.parse(sessionStorage.getItem("work-tracker-baseline") || "null");
|
|
324
|
+
if (held && held.round === REVIEW_ROUND) return held.baseline;
|
|
325
|
+
} catch (e) {}
|
|
326
|
+
let baseline = REVIEW_ROUND - 1;
|
|
327
|
+
try {
|
|
328
|
+
const seen = localStorage.getItem("work-tracker-seen-round");
|
|
329
|
+
if (seen !== null && Number.isFinite(Number(seen))) baseline = Math.min(Number(seen), REVIEW_ROUND);
|
|
330
|
+
} catch (e) {}
|
|
331
|
+
try { sessionStorage.setItem("work-tracker-baseline", JSON.stringify({ round: REVIEW_ROUND, baseline })); } catch (e) {}
|
|
332
|
+
try { localStorage.setItem("work-tracker-seen-round", String(REVIEW_ROUND)); } catch (e) {}
|
|
333
|
+
return baseline;
|
|
334
|
+
}
|
|
335
|
+
function stampChange(node, it, part) {
|
|
336
|
+
const parts = [].concat(it.changed_parts || []);
|
|
337
|
+
if (it.changed_round && (part ? parts.includes(part) : parts.length)) node.dataset.changedRound = it.changed_round;
|
|
338
|
+
return node;
|
|
339
|
+
}
|
|
340
|
+
function markChanges() {
|
|
341
|
+
for (const node of document.querySelectorAll("[data-changed-round]")) {
|
|
342
|
+
if (Number(node.dataset.changedRound) <= CHANGE_BASELINE || node.classList.contains("changed")) continue;
|
|
343
|
+
node.classList.add("changed");
|
|
344
|
+
const label = node.querySelector(":scope > summary") || node;
|
|
345
|
+
label.prepend(node.dataset.changeMark === "dot" ? el("span", "sr", "Changed. ") : el("span", "pill-changed", "Changed"));
|
|
346
|
+
}
|
|
347
|
+
}
|
|
303
348
|
function save() { try { localStorage.setItem("work-tracker-view", JSON.stringify({ tab: state.tab, owner: state.owner })); } catch (e) {} }
|
|
304
349
|
function inView(it) {
|
|
305
350
|
if ((state.tab === "done") !== isDone(it)) return false;
|
|
@@ -329,7 +374,8 @@ function renderBoard(items) {
|
|
|
329
374
|
if (mine.length) {
|
|
330
375
|
const chips = el("div", "lchips");
|
|
331
376
|
for (const it of mine) {
|
|
332
|
-
const b = el("button", "lchip", it.title);
|
|
377
|
+
const b = stampChange(el("button", "lchip", it.title), it);
|
|
378
|
+
b.dataset.changeMark = "dot";
|
|
333
379
|
b.setAttribute("aria-label", "Go to " + it.title);
|
|
334
380
|
b.onclick = () => jumpTo(it);
|
|
335
381
|
chips.append(b);
|
|
@@ -398,16 +444,17 @@ function itemCard(it) {
|
|
|
398
444
|
band.append(proj);
|
|
399
445
|
card.append(band);
|
|
400
446
|
const body = el("div", "body");
|
|
401
|
-
body.append(el("h3", "title", it.title));
|
|
447
|
+
body.append(stampChange(el("h3", "title", it.title), it, "title"));
|
|
402
448
|
body.append(trackFor(it));
|
|
403
449
|
if (it.next) {
|
|
404
|
-
const next = el("div", "next");
|
|
450
|
+
const next = stampChange(el("div", "next"), it, "next");
|
|
405
451
|
next.append(el("span", "ic", isDone(it) ? "✅" : "👉"), el("span", null, it.next.charAt(0).toUpperCase() + it.next.slice(1)));
|
|
406
452
|
body.append(next);
|
|
407
453
|
}
|
|
408
454
|
const points = [].concat(it.summary || []);
|
|
409
455
|
if (points.length) {
|
|
410
|
-
const d = el("details", "what");
|
|
456
|
+
const d = stampChange(el("details", "what"), it, "summary");
|
|
457
|
+
d.open = d.dataset.changedRound > CHANGE_BASELINE;
|
|
411
458
|
d.append(el("summary", null, "What it delivers"));
|
|
412
459
|
const ul = el("ul", "points");
|
|
413
460
|
for (const p of points) ul.append(el("li", null, p));
|
|
@@ -415,12 +462,12 @@ function itemCard(it) {
|
|
|
415
462
|
body.append(d);
|
|
416
463
|
}
|
|
417
464
|
if (it.proposal && !isDone(it) && it.owner_steps && it.owner_steps.length) {
|
|
418
|
-
const st = el("div", "steps");
|
|
465
|
+
const st = stampChange(el("div", "steps"), it, "steps");
|
|
419
466
|
st.append(el("div", "sh", "Your steps to create " + it.proposal.name));
|
|
420
467
|
it.owner_steps.forEach((text, i) => { const row = el("div", "step"); row.append(el("span", "num", String(i + 1)), el("span", null, text)); st.append(row); });
|
|
421
468
|
body.append(st);
|
|
422
469
|
}
|
|
423
|
-
const actions = el("div", "actions");
|
|
470
|
+
const actions = stampChange(el("div", "actions"), it, "links");
|
|
424
471
|
for (const l of it.links || []) {
|
|
425
472
|
const a = el("a", "link");
|
|
426
473
|
a.append(el("span", null, "🔗"), el("span", null, l.label));
|
|
@@ -458,6 +505,7 @@ function render() {
|
|
|
458
505
|
list.append(h);
|
|
459
506
|
mine.forEach(i => list.append(itemCard(i)));
|
|
460
507
|
}
|
|
508
|
+
markChanges();
|
|
461
509
|
}
|
|
462
510
|
document.querySelectorAll(".tab").forEach(t => t.onclick = () => { state.tab = t.dataset.tab; save(); render(); });
|
|
463
511
|
document.getElementById("search").oninput = e => { state.query = e.target.value; render(); };
|