@shadowclone/skills 0.0.0-stage → 0.0.29

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.
Files changed (32) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -2
  3. package/package.json +15 -4
  4. package/skills/choose-by-consequence/SKILL.md +66 -0
  5. package/skills/design-deep-modules/SKILL.md +51 -0
  6. package/skills/diagnose-before-editing/SKILL.md +58 -0
  7. package/skills/guided-pr-review/SKILL.md +87 -0
  8. package/skills/guided-pr-review/assets/document.mjs +197 -0
  9. package/skills/guided-pr-review/assets/guide.mjs +196 -0
  10. package/skills/guided-pr-review/assets/notes.mjs +195 -0
  11. package/skills/guided-pr-review/assets/presentation.mjs +180 -0
  12. package/skills/guided-pr-review/assets/review.html +18 -0
  13. package/skills/guided-pr-review/references/report.md +118 -0
  14. package/skills/guided-pr-review/scripts/build.mjs +117 -0
  15. package/skills/guided-pr-review/scripts/check.mjs +193 -0
  16. package/skills/guided-pr-review/scripts/format.mjs +198 -0
  17. package/skills/guided-pr-review/scripts/git.mjs +199 -0
  18. package/skills/guided-pr-review/scripts/render.mjs +195 -0
  19. package/skills/plan-with-review-page/SKILL.md +73 -0
  20. package/skills/plan-with-review-page/assets/review-page.html +160 -0
  21. package/skills/plan-with-review-page/scripts/validate-page.mjs +158 -0
  22. package/skills/research-primary-sources/SKILL.md +51 -0
  23. package/skills/resolve-conflicts-by-intent/SKILL.md +53 -0
  24. package/skills/scope-confirmed-changes/SKILL.md +63 -0
  25. package/skills/shadowclone-review/SKILL.md +76 -0
  26. package/skills/shadowclone-work/SKILL.md +47 -0
  27. package/skills/tests-that-catch-bugs/SKILL.md +73 -0
  28. package/skills/typescript-type-safety/SKILL.md +52 -0
  29. package/skills/verify-and-review/SKILL.md +58 -0
  30. package/skills/verify-review-findings/SKILL.md +59 -0
  31. package/skills/write-plain-english/SKILL.md +69 -0
  32. package/skills/write-plain-english/scripts/check-ste.mjs +200 -0
@@ -0,0 +1,59 @@
1
+ ---
2
+ name: verify-review-findings
3
+ description: 'Use when a pull request has review comments or bot findings to check. Also use it when the user says "are these comments right?", "triage the review", or "check what the bot found". Checks each finding against the code at the head of the pull request, and confirms the file, the line, and the claim. Asks what each suggested fix would break, reports a verdict for each finding, and waits. Not for writing the fix (use `scope-confirmed-changes`).'
4
+ metadata:
5
+ shadowclone-category: review
6
+ shadowclone-section: workflow
7
+ shadowclone-applies-when: when a pull request has review comments or bot findings
8
+ shadowclone-voice: "true"
9
+ ---
10
+ # Verify Review Findings
11
+
12
+ ## Use when
13
+
14
+ A pull request has review comments, bot findings, or a list of issues from another agent. A finding is a claim, not a fact. Check each one before you change code or reply.
15
+
16
+ ## Gates
17
+
18
+ 1. Did you read each finding at the head commit of the pull request? A finding about an older commit can be stale.
19
+ 2. For each finding, do the file and the line exist, and does the code there do what the finding says?
20
+ 3. Can you show the claim with a test, a command, or the code itself? If not, label it as not verified.
21
+ 4. Did you ask what each suggested fix would break, such as a caller, a test, or a documented behavior?
22
+ 5. Does each finding have a verdict: confirmed, partly right, wrong, stale, or not verified?
23
+ 6. Did you wait for the user before you changed code or replied to a reviewer?
24
+
25
+ ## Process
26
+
27
+ Write in the user's voice. Before you write, read `~/.agents/voice.md`. If it does not exist, build it once from the user's merged pull requests, review comments, and commit messages. Use only text that the user wrote, and name those sources in your handoff. Never overwrite an existing or linked `voice.md`.
28
+
29
+ 1. Fetch the pull request, and check out its head commit. Write down the commit hash.
30
+ 2. List each finding in one table, with its source, its `file:line`, and its claim.
31
+ 3. For each finding, read the code at that location and its callers. Compare the claim with the real behavior.
32
+ 4. When the claim is about behavior, run a focused test or a command that shows it.
33
+ 5. For each suggested fix, list what it changes for callers, tests, and users. A fix that is right in one place can break another place.
34
+ 6. Give each finding a verdict, with the evidence that supports it.
35
+ 7. Put the confirmed findings first, with the highest risk at the top.
36
+ 8. Report the table, and wait. Change code or reply only after the user decides.
37
+
38
+ ## Example
39
+
40
+ **Situation:** A review bot says that a function can return null and crash its caller. It suggests a null check.
41
+ **Easy route:** Add the null check, and reply "Fixed".
42
+ **Hidden cost:** The function never returns null, because a type guard on the line above handles that case. The extra check hides the real contract, and the reply tells the reviewer something false.
43
+ **Best route:** Read the function at the head commit, and run its test with an empty input. Report the finding as wrong, with the line that handles the case.
44
+ **Evidence:** The test with the empty input passed at the head commit. The report quoted the guard at its `file:line` and gave the verdict "wrong".
45
+
46
+ ## Guardrails
47
+
48
+ - Never change code or post a reply before the user decides.
49
+ - Do not accept a finding because a bot or a senior reviewer wrote it. Do not reject it for that reason either.
50
+ - Quote the code that proves each verdict. Do not paraphrase it.
51
+ - Keep each finding separate. Do not merge two findings into one verdict.
52
+ - A finding that you cannot check stays "not verified" in the report.
53
+
54
+ ## Completion
55
+
56
+ - The head commit hash that you checked.
57
+ - A table row for each finding: the source, the `file:line`, the claim, the verdict, and the evidence.
58
+ - Each command or test that you ran, with its result line.
59
+ - For each confirmed finding, what its fix changes for callers and tests.
@@ -0,0 +1,69 @@
1
+ ---
2
+ name: write-plain-english
3
+ description: 'Use when writing or editing prose that people read, such as documents, pull requests, commit messages, review comments, chat replies, or skills. Also use it when the user says "make this clearer", "simplify the wording", or "this is a wall of text". Rewrites the text in plain technical English, with one idea in each sentence and no dashes. Then runs a checker that reports long sentences and dashes. Not for checking code behavior (use `verify-and-review`).'
4
+ metadata:
5
+ shadowclone-category: writing
6
+ shadowclone-section: workflow
7
+ shadowclone-applies-when: before you write any text that a person reads
8
+ shadowclone-voice: "true"
9
+ shadowclone-always-on: "true"
10
+ ---
11
+ # Write Plain English
12
+
13
+ ## Use when
14
+
15
+ You write or edit any text that a person reads. Examples are a document, a pull request, a commit message, a review comment, a chat reply, and a skill. This skill is always on, so load it before you write.
16
+
17
+ The rules come from ASD-STE100 Simplified Technical English, at about 80% strictness. Fix every checker error. Fix each warning, or keep it and give a reason. Clarity comes first. If a rule makes the text wrong or unclear, break the rule.
18
+
19
+ ## Gates
20
+
21
+ 1. Does each sentence carry one idea, with 20 words or fewer for an instruction and 25 or fewer for a description?
22
+ 2. Is each instruction in the active voice, with the condition before the action?
23
+ 3. Do you use one term for one thing, every time?
24
+ 4. Is the text free of em dashes and en dashes?
25
+ 5. Did you run `scripts/check-ste.mjs` on the text, fix every error, and fix or explain each warning?
26
+ 6. Are code, commands, paths, and product names exactly as they were?
27
+
28
+ ## Process
29
+
30
+ Write in the user's voice. Before you write, read `~/.agents/voice.md`. If it does not exist, build it once from the user's merged pull requests, review comments, and commit messages. Use only text that the user wrote, and name those sources in your handoff. Never overwrite an existing or linked `voice.md`.
31
+
32
+ 1. Put the main point first. Give the reader the result, then the reason.
33
+ 2. Use common words that have one meaning. Use a single verb, not a phrasal verb: "start", not "kick off".
34
+ 3. Keep the articles "the" and "a". Do not drop words to save space.
35
+ 4. Use numbered steps for a procedure, with one action in each step.
36
+ 5. Put a condition before its instruction: "If the test fails, read the first error."
37
+ 6. Replace each dash with a comma, a period, parentheses, or two sentences. For a range, write "to", as in "2 to 5".
38
+ 7. Save the text to a file, and run `node scripts/check-ste.mjs <file>`. If Node is not available, run the script with `bun`. If neither one runs, say that the check did not run.
39
+ 8. Fix every error. For each warning, fix it, or keep it and say why the rule makes the text worse.
40
+
41
+ For a document, also follow these rules:
42
+
43
+ - Lead with the result. Put the reason and the detail after it.
44
+ - Write one topic in each paragraph.
45
+ - Name each heading for the task or the answer that the reader needs.
46
+ - Use a table to compare options. Use a numbered list for steps.
47
+ - Link to the text that already exists. Do not repeat it.
48
+
49
+ ## Example
50
+
51
+ **Situation:** A pull request explains a bug in one sentence of 45 words. The sentence joins the symptom, the cause, and the fix with "so" and "which", and it has an em dash.
52
+ **Easy route:** Keep the sentence. It is accurate, and a rewrite takes time.
53
+ **Hidden cost:** Reviewers skim it and miss the cause, so they ask about it in review. The dash also fails the prose check of the repository.
54
+ **Best route:** Split it into three sentences: the symptom, the cause, and the fix. Replace the dash with a period.
55
+ **Evidence:** Before the edit, the checker reported the 45 word sentence and the dash. After the edit, it printed "check-ste: 0 errors, 0 warnings in 1 file".
56
+
57
+ ## Guardrails
58
+
59
+ - Keep technical terms, identifiers, and quoted text exactly as they are. Do not paraphrase an error message.
60
+ - Do not change the meaning to obey a rule.
61
+ - Do not rewrite text that another person wrote, unless they ask. Suggest the change instead.
62
+ - Do not add filler, praise, or hedging words, such as "robust", "seamless", or "simply".
63
+ - Keep each term the same in the whole document.
64
+
65
+ ## Completion
66
+
67
+ - The checker command and its summary line, for example `check-ste: 0 errors, 1 warning in 1 file`.
68
+ - Each warning that you kept, with its `file:line` and your reason.
69
+ - The path of the voice file that you read, or the sources that you used to build it.
@@ -0,0 +1,200 @@
1
+ #!/usr/bin/env node
2
+ import { readFile } from "node:fs/promises";
3
+
4
+ const maximumWords = 25;
5
+ const dash = /[\u2013\u2014]/;
6
+ const passive =
7
+ /\b(?:is|are|was|were|be|been|being)\s+(?:not\s+)?(?:\w+ly\s+)?(\w{3,}(?:ed|en))\b/i;
8
+ const notParticiples = new Set(["often", "even", "seven", "eleven", "token", "listen", "golden"]);
9
+ const phrasalVerbs = [
10
+ "carry out",
11
+ "come up with",
12
+ "end up",
13
+ "figure out",
14
+ "find out",
15
+ "get rid of",
16
+ "go over",
17
+ "kick off",
18
+ "look into",
19
+ "make use of",
20
+ "point out",
21
+ "set up",
22
+ "take care of",
23
+ "turn out",
24
+ ];
25
+ const vagueWords = [
26
+ "basically",
27
+ "etc",
28
+ "leverage",
29
+ "numerous",
30
+ "robust",
31
+ "seamless",
32
+ "seamlessly",
33
+ "simply",
34
+ "utilize",
35
+ "various",
36
+ ];
37
+
38
+ function proseLines(text) {
39
+ const lines = text.split("\n");
40
+ const prose = [];
41
+ let fence = null;
42
+ let frontmatter = lines[0] === "---";
43
+
44
+ for (const [index, line] of lines.entries()) {
45
+ const number = index + 1;
46
+
47
+ if (frontmatter) {
48
+ if (index > 0 && line === "---") {
49
+ frontmatter = false;
50
+ }
51
+
52
+ const description = /^description:\s*(["']?)(.*)\1\s*$/.exec(line);
53
+
54
+ if (description) {
55
+ prose.push({ number, text: description[2].replace(/''/g, "'") });
56
+ }
57
+
58
+ continue;
59
+ }
60
+
61
+ const marker = /^\s*(`{3,}|~{3,})/.exec(line)?.[1];
62
+
63
+ if (marker) {
64
+ fence = fence === null ? marker : marker.startsWith(fence) ? null : fence;
65
+
66
+ continue;
67
+ }
68
+
69
+ if (fence !== null || /^\s*(?:#|\||$)/.test(line)) {
70
+ continue;
71
+ }
72
+
73
+ prose.push({
74
+ number,
75
+ text: line
76
+ .replace(/`[^`]*`/g, "Code")
77
+ .replace(/\[([^\]]*)\]\([^)]*\)/g, "$1")
78
+ .replace(/^\s*(?:[-*]|\d+\.)\s+/, "")
79
+ .replace(/\*\*([^*]+):\*\*/g, "$1:")
80
+ .replace(/[*_]/g, ""),
81
+ });
82
+ }
83
+
84
+ return prose;
85
+ }
86
+
87
+ function sentencesOf(text) {
88
+ return text
89
+ .split(/(?<=[.!?])\s+(?=["'(A-Z0-9])/)
90
+ .map((sentence) => sentence.trim())
91
+ .filter((sentence) => sentence.length > 0);
92
+ }
93
+
94
+ function wordCount(sentence) {
95
+ return sentence.split(/\s+/).filter((word) => /[A-Za-z0-9]/.test(word)).length;
96
+ }
97
+
98
+ function checkText(text) {
99
+ const findings = [];
100
+
101
+ for (const line of proseLines(text)) {
102
+ if (dash.test(line.text)) {
103
+ findings.push({
104
+ line: line.number,
105
+ level: "error",
106
+ rule: "dash",
107
+ message: "replace the em or en dash with a comma, a period, parentheses, or the word to",
108
+ });
109
+ }
110
+
111
+ for (const sentence of sentencesOf(line.text)) {
112
+ const words = wordCount(sentence);
113
+ const unquoted = sentence.replace(/"[^"]*"/g, " ");
114
+ const lower = ` ${unquoted.toLowerCase().replace(/[^a-z0-9' ]+/g, " ")} `;
115
+ const passiveMatch = passive.exec(unquoted);
116
+ const passiveWord = passiveMatch?.[1]?.toLowerCase();
117
+
118
+ if (words > maximumWords) {
119
+ findings.push({
120
+ line: line.number,
121
+ level: "error",
122
+ rule: "long-sentence",
123
+ message: `${words} words, split it to ${maximumWords} or fewer: "${sentence.slice(0, 60)}"`,
124
+ });
125
+ }
126
+
127
+ if (passiveMatch && passiveWord && !notParticiples.has(passiveWord)) {
128
+ findings.push({
129
+ line: line.number,
130
+ level: "warning",
131
+ rule: "passive",
132
+ message: `"${passiveMatch[0]}" may be passive, name who does the action`,
133
+ });
134
+ }
135
+
136
+ for (const verb of phrasalVerbs.filter((entry) => lower.includes(` ${entry} `))) {
137
+ findings.push({
138
+ line: line.number,
139
+ level: "warning",
140
+ rule: "phrasal-verb",
141
+ message: `"${verb}" is a phrasal verb, use a single verb`,
142
+ });
143
+ }
144
+
145
+ for (const word of vagueWords.filter((entry) => lower.includes(` ${entry} `))) {
146
+ findings.push({
147
+ line: line.number,
148
+ level: "warning",
149
+ rule: "vague-word",
150
+ message: `"${word}" is vague, say exactly what you mean`,
151
+ });
152
+ }
153
+ }
154
+ }
155
+
156
+ return findings;
157
+ }
158
+
159
+ async function main(files) {
160
+ if (files.length === 0) {
161
+ console.error("Usage: check-ste.mjs <file>...");
162
+
163
+ return 2;
164
+ }
165
+
166
+ let errors = 0;
167
+ let warnings = 0;
168
+
169
+ for (const file of files) {
170
+ let text;
171
+
172
+ try {
173
+ text = await readFile(file, "utf8");
174
+ } catch (error) {
175
+ console.error(`${file}: cannot read the file (${error.code ?? error.message})`);
176
+
177
+ return 2;
178
+ }
179
+
180
+ for (const finding of checkText(text)) {
181
+ console.log(`${file}:${finding.line}: ${finding.level} ${finding.rule} ${finding.message}`);
182
+
183
+ if (finding.level === "error") {
184
+ errors += 1;
185
+ } else {
186
+ warnings += 1;
187
+ }
188
+ }
189
+ }
190
+
191
+ const plural = (count, word) => `${count} ${word}${count === 1 ? "" : "s"}`;
192
+
193
+ console.log(
194
+ `check-ste: ${plural(errors, "error")}, ${plural(warnings, "warning")} in ${plural(files.length, "file")}`,
195
+ );
196
+
197
+ return errors > 0 ? 1 : 0;
198
+ }
199
+
200
+ process.exitCode = await main(process.argv.slice(2));