@bompus/house-rules 0.0.0-stage → 0.13.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/CODE_OF_CONDUCT.md +83 -0
- package/CONTRIBUTING.md +54 -0
- package/LICENSE +21 -0
- package/README.md +378 -2
- package/THIRD_PARTY_NOTICES.md +188 -0
- package/compose.mjs +125 -0
- package/composition.mjs +361 -0
- package/config-view.mjs +209 -0
- package/config.mjs +381 -0
- package/docs/assets/banner-dark.png +0 -0
- package/docs/assets/banner-light.png +0 -0
- package/docs/assets/overview.png +0 -0
- package/docs/configuration.md +269 -0
- package/docs/skill-names.md +50 -0
- package/examples/person/house-rules.json +15 -0
- package/examples/person/rules/10-tooling.md +8 -0
- package/examples/person/rules/20-reporting.md +9 -0
- package/examples/project/AGENTS.md +18 -0
- package/package.json +42 -4
- package/rule-references.mjs +91 -0
- package/rules/core.md +340 -0
- package/rules/modifiers/backlog-outline.md +25 -0
- package/rules/modifiers/backlog-visual.md +16 -0
- package/rules/modifiers/coded-offers.md +70 -0
- package/rules/modifiers/cross-session-relay.md +23 -0
- package/rules/modifiers/effort-estimates.md +41 -0
- package/rules/modifiers/land-when-done.md +12 -0
- package/rules/modifiers/low-quota-handoff.md +20 -0
- package/rules/modifiers/model-default-questions.md +33 -0
- package/rules/modifiers/multi-agent.md +38 -0
- package/rules/modifiers/no-attribution.md +16 -0
- package/rules/modifiers/periodic-updates.md +25 -0
- package/rules/modifiers/plan-files.md +82 -0
- package/rules/modifiers/question-cards.md +48 -0
- package/rules/modifiers/release-batching.md +15 -0
- package/rules/modifiers/scratch-on-disk.md +15 -0
- package/rules/modifiers/shared-host-load.md +19 -0
- package/rules/modifiers/solo-operator.md +13 -0
- package/rules/modifiers/squash-landing.md +22 -0
- package/rules/modifiers/upstream-contributions.md +45 -0
- package/rules/modifiers/web-image-formats.md +35 -0
- package/rules/references/backlog-view.md +154 -0
- package/rules/references/github-actions.md +45 -0
- package/rules/references/landing.md +39 -0
- package/rules/references/multi-agent.md +51 -0
- package/rules/references/release-batching.md +66 -0
- package/rules/references/squash-landing.md +50 -0
- package/rules/references/thread-visuals.md +45 -0
- package/setup.mjs +445 -0
- package/skills/hr-agent-guidance-audit/LICENSE +21 -0
- package/skills/hr-agent-guidance-audit/SKILL.md +91 -0
- package/skills/hr-agent-guidance-audit/agents/openai.yaml +2 -0
- package/skills/hr-agent-guidance-audit/references/prune-workflow.md +72 -0
- package/skills/hr-agent-guidance-audit/references/records-and-lessons.md +60 -0
- package/skills/hr-agent-guidance-audit/references/retirement-verdicts.md +49 -0
- package/skills/hr-agent-guidance-refresh/LICENSE +21 -0
- package/skills/hr-agent-guidance-refresh/SKILL.md +36 -0
- package/skills/hr-agent-guidance-refresh/agents/openai.yaml +2 -0
- package/skills/hr-agent-guidance-refresh/references/verification.md +47 -0
- package/skills/hr-api-exposure-check/SKILL.md +159 -0
- package/skills/hr-api-exposure-check/agents/openai.yaml +3 -0
- package/skills/hr-audit-choices/SKILL.md +67 -0
- package/skills/hr-audit-choices/agents/openai.yaml +2 -0
- package/skills/hr-benchmarking/NOTICE.md +36 -0
- package/skills/hr-benchmarking/SKILL.md +279 -0
- package/skills/hr-benchmarking/agents/openai.yaml +2 -0
- package/skills/hr-benchmarking/references/javascript.md +130 -0
- package/skills/hr-benchmarking/references/tools.md +165 -0
- package/skills/hr-benchmarking/scripts/process-attribution.ps1 +20 -0
- package/skills/hr-benchmarking/scripts/process-attribution.py +107 -0
- package/skills/hr-better-accessibility/LICENSE +22 -0
- package/skills/hr-better-accessibility/NOTICE.md +46 -0
- package/skills/hr-better-accessibility/SKILL.md +119 -0
- package/skills/hr-better-accessibility/agents/openai.yaml +3 -0
- package/skills/hr-better-accessibility/focus-and-keyboard.md +130 -0
- package/skills/hr-better-accessibility/forms.md +82 -0
- package/skills/hr-better-accessibility/hit-areas.md +98 -0
- package/skills/hr-better-accessibility/motion-and-zoom.md +81 -0
- package/skills/hr-better-accessibility/screen-readers.md +102 -0
- package/skills/hr-better-accessibility/semantics-and-aria.md +84 -0
- package/skills/hr-better-colors/LICENSE +21 -0
- package/skills/hr-better-colors/NOTICE.md +37 -0
- package/skills/hr-better-colors/SKILL.md +114 -0
- package/skills/hr-better-colors/agents/openai.yaml +3 -0
- package/skills/hr-better-colors/color-formats.md +90 -0
- package/skills/hr-better-colors/color-usage.md +140 -0
- package/skills/hr-better-colors/contrast.md +80 -0
- package/skills/hr-better-colors/palette-generation.md +104 -0
- package/skills/hr-better-colors/palette-structure.md +76 -0
- package/skills/hr-better-colors/token-naming.md +97 -0
- package/skills/hr-better-layout/LICENSE +22 -0
- package/skills/hr-better-layout/NOTICE.md +48 -0
- package/skills/hr-better-layout/SKILL.md +92 -0
- package/skills/hr-better-layout/agents/openai.yaml +3 -0
- package/skills/hr-better-layout/grouping-and-alignment.md +144 -0
- package/skills/hr-better-layout/spacing-and-adaptivity.md +215 -0
- package/skills/hr-better-typography/LICENSE +22 -0
- package/skills/hr-better-typography/NOTICE.md +46 -0
- package/skills/hr-better-typography/SKILL.md +172 -0
- package/skills/hr-better-typography/agents/openai.yaml +3 -0
- package/skills/hr-better-typography/choosing-fonts.md +69 -0
- package/skills/hr-better-typography/css-cheat-sheet.md +65 -0
- package/skills/hr-better-typography/details-and-accessibility.md +119 -0
- package/skills/hr-better-typography/spacing-and-sizing.md +131 -0
- package/skills/hr-better-typography/variable-fonts-and-opentype.md +105 -0
- package/skills/hr-better-typography/wrapping-and-punctuation.md +57 -0
- package/skills/hr-better-writing/LICENSE +22 -0
- package/skills/hr-better-writing/NOTICE.md +46 -0
- package/skills/hr-better-writing/SKILL.md +122 -0
- package/skills/hr-better-writing/agents/openai.yaml +3 -0
- package/skills/hr-code-review/LICENSE +21 -0
- package/skills/hr-code-review/NOTICE.md +26 -0
- package/skills/hr-code-review/SKILL.md +163 -0
- package/skills/hr-code-review/agents/openai.yaml +3 -0
- package/skills/hr-code-review/references/acp-client.md +43 -0
- package/skills/hr-code-review/references/evidence.md +57 -0
- package/skills/hr-code-review/references/impact.md +103 -0
- package/skills/hr-code-review/references/panel-adjudication.md +58 -0
- package/skills/hr-code-review/references/panel-plan.md +61 -0
- package/skills/hr-code-review/references/panel-recommendations.json +105 -0
- package/skills/hr-code-review/references/panel-recommendations.md +46 -0
- package/skills/hr-code-review/references/panel-setup.md +54 -0
- package/skills/hr-code-review/references/reviewer-role.md +31 -0
- package/skills/hr-code-review/scripts/acp-panel-client.mjs +299 -0
- package/skills/hr-code-review/scripts/check-receipts.mjs +263 -0
- package/skills/hr-code-review/scripts/panel-plan.mjs +69 -0
- package/skills/hr-code-review/scripts/panel-preferences.mjs +220 -0
- package/skills/hr-design-exploration/SKILL.md +105 -0
- package/skills/hr-design-exploration/agents/openai.yaml +4 -0
- package/skills/hr-diagnosing-bugs/NOTICE.md +45 -0
- package/skills/hr-diagnosing-bugs/SKILL.md +108 -0
- package/skills/hr-diagnosing-bugs/agents/openai.yaml +3 -0
- package/skills/hr-diagnosing-bugs/references/reporting.md +97 -0
- package/skills/hr-diagnosing-bugs/scripts/check-pr-offer.mjs +47 -0
- package/skills/hr-explain-code/NOTICE.md +37 -0
- package/skills/hr-explain-code/SKILL.md +110 -0
- package/skills/hr-explain-code/agents/openai.yaml +5 -0
- package/skills/hr-explain-code/references/helper-brief.md +39 -0
- package/skills/hr-extract-shared-steps/SKILL.md +134 -0
- package/skills/hr-extract-shared-steps/agents/openai.yaml +3 -0
- package/skills/hr-forgotten-work-audit/SKILL.md +130 -0
- package/skills/hr-forgotten-work-audit/references/brief-template.md +56 -0
- package/skills/hr-forgotten-work-audit/references/result-format.md +46 -0
- package/skills/hr-handoff/LICENSE +21 -0
- package/skills/hr-handoff/NOTICE.md +29 -0
- package/skills/hr-handoff/SKILL.md +114 -0
- package/skills/hr-handoff/agents/openai.yaml +5 -0
- package/skills/hr-handoff/assets/ledger.example.json +10 -0
- package/skills/hr-handoff/assets/receipt.example.json +76 -0
- package/skills/hr-handoff/assets/source.example.json +20 -0
- package/skills/hr-handoff/references/reconciliation.md +124 -0
- package/skills/hr-handoff/references/successor-thread.md +76 -0
- package/skills/hr-handoff/scripts/check-reconciliation.mjs +324 -0
- package/skills/hr-house-rules-setup/SKILL.md +114 -0
- package/skills/hr-house-rules-setup/agents/openai.yaml +5 -0
- package/skills/hr-issue-tracker-setup/SKILL.md +49 -0
- package/skills/hr-issue-tracker-setup/agents/openai.yaml +5 -0
- package/skills/hr-lean-plan/SKILL.md +146 -0
- package/skills/hr-lean-plan/agents/openai.yaml +5 -0
- package/skills/hr-maintainability-review/SKILL.md +186 -0
- package/skills/hr-maintainability-review/agents/openai.yaml +5 -0
- package/skills/hr-manual-qa/NOTICE.md +36 -0
- package/skills/hr-manual-qa/SKILL.md +37 -0
- package/skills/hr-manual-qa/agents/openai.yaml +3 -0
- package/skills/hr-navigation-retro/LICENSE +21 -0
- package/skills/hr-navigation-retro/NOTICE.md +5 -0
- package/skills/hr-navigation-retro/SKILL.md +86 -0
- package/skills/hr-navigation-retro/agents/openai.yaml +4 -0
- package/skills/hr-navigation-retro/references/inputs.md +67 -0
- package/skills/hr-navigation-retro/scripts/retro.py +552 -0
- package/skills/hr-navigation-retro/scripts/transcripts.py +205 -0
- package/skills/hr-ordering-tests/SKILL.md +51 -0
- package/skills/hr-ordering-tests/agents/openai.yaml +2 -0
- package/skills/hr-plain-prose/SKILL.md +264 -0
- package/skills/hr-plain-prose/agents/openai.yaml +3 -0
- package/skills/hr-plain-prose/scripts/check.mjs +331 -0
- package/skills/hr-pr-followup/LICENSE +21 -0
- package/skills/hr-pr-followup/NOTICE.md +9 -0
- package/skills/hr-pr-followup/SKILL.md +74 -0
- package/skills/hr-pr-followup/agents/openai.yaml +2 -0
- package/skills/hr-pr-followup/references/review-helpers.md +111 -0
- package/skills/hr-pr-followup/scripts/check-landing.py +204 -0
- package/skills/hr-pr-followup/scripts/check-pr-review.ts +510 -0
- package/skills/hr-pr-followup/scripts/merge-pr.sh +100 -0
- package/skills/hr-pr-followup/scripts/resolve-pr-thread.ts +219 -0
- package/skills/hr-pr-followup/scripts/verify-landed.sh +184 -0
- package/skills/hr-progress-report/SKILL.md +49 -0
- package/skills/hr-progress-report/agents/openai.yaml +5 -0
- package/skills/hr-read-reddit/SKILL.md +44 -0
- package/skills/hr-read-reddit/agents/openai.yaml +2 -0
- package/skills/hr-read-reddit/scripts/reddit.ts +252 -0
- package/skills/hr-read-x-links/SKILL.md +42 -0
- package/skills/hr-read-x-links/agents/openai.yaml +2 -0
- package/skills/hr-read-x-links/scripts/x-post.ts +264 -0
- package/skills/hr-research-shared-links/SKILL.md +103 -0
- package/skills/hr-split-to-prs/LICENSE +21 -0
- package/skills/hr-split-to-prs/NOTICE.md +9 -0
- package/skills/hr-split-to-prs/SKILL.md +61 -0
- package/skills/hr-split-to-prs/agents/openai.yaml +2 -0
- package/skills/hr-sqlite/SKILL.md +174 -0
- package/skills/hr-sqlite/agents/openai.yaml +2 -0
- package/skills/hr-sqlite/references/evidence.md +94 -0
- package/skills/hr-sqlite/references/tool-decisions.md +39 -0
- package/skills/hr-sqlite/scripts/inspect-sqlite.py +245 -0
- package/skills/hr-stock-ui-audit/SKILL.md +132 -0
- package/skills/hr-stock-ui-audit/agents/openai.yaml +3 -0
- package/skills/hr-stock-ui-audit/scripts/scan.mjs +619 -0
- package/skills/hr-tdd/LICENSE +21 -0
- package/skills/hr-tdd/NOTICE.md +10 -0
- package/skills/hr-tdd/SKILL.md +40 -0
- package/skills/hr-tdd/agents/openai.yaml +3 -0
- package/skills/hr-tdd/mocking.md +39 -0
- package/skills/hr-tdd/tests.md +77 -0
- package/skills/hr-test-audit/LICENSE +21 -0
- package/skills/hr-test-audit/NOTICE.md +67 -0
- package/skills/hr-test-audit/SKILL.md +146 -0
- package/skills/hr-test-audit/agents/openai.yaml +3 -0
- package/skills/hr-test-audit/scripts/floor-guard.ts +137 -0
- package/skills/hr-usage-report/SKILL.md +90 -0
- package/skills/hr-usage-report/agents/openai.yaml +2 -0
- package/skills/hr-usage-report/assets/example-report.json +125 -0
- package/skills/hr-usage-report/assets/report-template.html +377 -0
- package/skills/hr-usage-report/scripts/render-report.ts +86 -0
- package/skills/hr-usage-report/scripts/usage-by-model.ts +499 -0
- package/skills/hr-what-next/SKILL.md +151 -0
- package/skills/hr-what-next/agents/openai.yaml +4 -0
- package/skills/hr-what-next/references/reporting.md +59 -0
- package/skills/hr-what-next/scripts/check-open-work.mjs +711 -0
- package/skills/hr-writing-for-agents/NOTICE.md +46 -0
- package/skills/hr-writing-for-agents/SKILL-MECHANICS.md +83 -0
- package/skills/hr-writing-for-agents/SKILL.md +108 -0
- package/skills/hr-writing-for-agents/agents/openai.yaml +3 -0
- package/skills/hr-writing-pr/NOTICE.md +48 -0
- package/skills/hr-writing-pr/SKILL.md +119 -0
- package/skills/hr-writing-pr/agents/openai.yaml +2 -0
- package/skills/hr-writing-pr/scripts/benefit-chart.py +200 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Lists the spots in a draft that the plain-prose skill asks you to reread:
|
|
3
|
+
// dash stand-ins, colons between two clauses, curly quotes, the words in
|
|
4
|
+
// SKILL.md's two word tables and sentences over 30 words. Every finding is a
|
|
5
|
+
// cue, not a verdict.
|
|
6
|
+
// Runs on Node 22+ or Bun with no dependencies.
|
|
7
|
+
//
|
|
8
|
+
// check.mjs [--json] [<file>...] (no file, or -, reads standard input)
|
|
9
|
+
//
|
|
10
|
+
// Skipped as material to keep: fenced code, inline code, URLs, text in
|
|
11
|
+
// straight double quotes (also when the quote wraps onto the next line), block
|
|
12
|
+
// quotes, the paragraph after a `plain-prose: keep` comment, and the rows of a
|
|
13
|
+
// word table like SKILL.md's own.
|
|
14
|
+
// Exit codes: 0 no findings, 1 findings, 2 bad usage or an unreadable file.
|
|
15
|
+
|
|
16
|
+
import { readFileSync, realpathSync } from "node:fs";
|
|
17
|
+
import { dirname, join } from "node:path";
|
|
18
|
+
import { fileURLToPath } from "node:url";
|
|
19
|
+
import { parseArgs } from "node:util";
|
|
20
|
+
|
|
21
|
+
const SKILL = join(dirname(fileURLToPath(import.meta.url)), "..", "SKILL.md");
|
|
22
|
+
|
|
23
|
+
// A term matches its inflections: the first word keeps its stem without a
|
|
24
|
+
// final "e" or "s" ("elevate" matches "elevating", "pillars" matches
|
|
25
|
+
// "pillar"); later words match exactly.
|
|
26
|
+
function termPattern(term) {
|
|
27
|
+
const [first, ...rest] = term.toLowerCase().split(/\s+/);
|
|
28
|
+
const stem = first.length > 4 ? first.replace(/[es]$/, "") : first;
|
|
29
|
+
return [`${stem}\\w*`, ...rest].join("\\s+");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// A word-table row has a plain term in its first cell and a replacement in its
|
|
33
|
+
// second: "| delve into | look at, read, test |". Returns the two cells.
|
|
34
|
+
function wordTableRow(line) {
|
|
35
|
+
const cells = line.split("|").map((c) => c.trim());
|
|
36
|
+
if (cells.length !== 4 || cells[0] !== "" || cells[3] !== "") return null;
|
|
37
|
+
return /^[a-z][a-z ,]*$/.test(cells[1]) ? cells.slice(1, 3) : null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function parseWordTables(markdown) {
|
|
41
|
+
const terms = [];
|
|
42
|
+
for (const line of markdown.split("\n")) {
|
|
43
|
+
const row = wordTableRow(line);
|
|
44
|
+
if (!row) continue;
|
|
45
|
+
const [words, hint] = row;
|
|
46
|
+
for (const term of words.split(/,\s*/)) {
|
|
47
|
+
terms.push({ term, hint, re: new RegExp(`\\b${termPattern(term)}\\b`, "gi") });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return terms;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const CHAT = [
|
|
54
|
+
"great question",
|
|
55
|
+
"i hope this helps",
|
|
56
|
+
"let me know if",
|
|
57
|
+
"here's a breakdown",
|
|
58
|
+
"as an ai",
|
|
59
|
+
"it is worth noting",
|
|
60
|
+
"it's worth noting",
|
|
61
|
+
"in order to",
|
|
62
|
+
"at the end of the day",
|
|
63
|
+
].map((p) => ({ term: p, re: new RegExp(`\\b${p.replace(/ /g, "\\s+")}\\b`, "gi") }));
|
|
64
|
+
|
|
65
|
+
// Technical senses of a table word, not the inflated usage the table targets:
|
|
66
|
+
// an administrator ("elevated") shell on Windows, and "elevation" as shadow
|
|
67
|
+
// depth in an interface (a "shadow" within 120 characters, same paragraph).
|
|
68
|
+
const NEAR = String.raw`(?:(?!\n[ \t]*\n)[\s\S]){0,120}?`;
|
|
69
|
+
const TECHNICAL = [
|
|
70
|
+
/\bself-elevat\w*/gi,
|
|
71
|
+
/\belevated(?:\*\*|__)?\s+(?:\*\*|__)?(?:shell|prompt|session|terminal|powershell|windows|command|capture|recording|follow-up|privileges?|rights|token)\b/gi,
|
|
72
|
+
new RegExp(String.raw`\bshadow${NEAR}\belevation\b|\belevation\b(?=${NEAR}\bshadow)`, "gi"),
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
const DASHES = [
|
|
76
|
+
{ re: /—/g, rule: "em-dash" },
|
|
77
|
+
{ re: /\s–\s/g, rule: "spaced-en-dash" },
|
|
78
|
+
// A spaced hyphen between two numbers is a range or a date heading.
|
|
79
|
+
{ re: /(?<=[A-Za-z_])\s--?\s(?=\w)|(?<=\d)\s--?\s(?=[A-Za-z_])/g, rule: "spaced-hyphen" },
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
// These are prose cues, not a word splitter. Keep identifiers in code spans;
|
|
83
|
+
// never insert spaces into the draft automatically.
|
|
84
|
+
const JOINED_PROSE = [
|
|
85
|
+
/\b(?:has|have|had|after|before|during|vs|took)(?=\d)/g,
|
|
86
|
+
/(?<=\d)(?:accepted|unfinished|remaining|passed|failed)\b/g,
|
|
87
|
+
/\d+(?:\.\d+)?(?:[KMGT]i?B|CPU-s|ms|s)(?=$|[^A-Za-z]|[A-Z]|process|peak|idle|during|scope)/g,
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
// Replaces kept spans with spaces of the same length and keeps line breaks, so
|
|
91
|
+
// lines and columns still line up. Code spans and quotes may wrap onto the next
|
|
92
|
+
// line but never cross a blank line. A code span becomes KEPT characters,
|
|
93
|
+
// neither space nor word, so `50`–`950` does not read as a spaced dash.
|
|
94
|
+
const KEPT = "\u2063";
|
|
95
|
+
const blank = (s) => s.replace(/[^\n]/g, " ");
|
|
96
|
+
const keptCode = (s) => s.replace(/[^\n]/g, KEPT);
|
|
97
|
+
const WRAP = String.raw`(?:[^"\n]|\n(?![ \t]*\n))`;
|
|
98
|
+
function prose(doc) {
|
|
99
|
+
return doc
|
|
100
|
+
.replace(new RegExp(String.raw`(\`+)(?:[^\`\n]|\n(?![ \t]*\n))*?\1`, "g"), keptCode)
|
|
101
|
+
.replace(new RegExp(`"${WRAP}*"`, "g"), blank)
|
|
102
|
+
.replace(/\]\([^)\n]*\)/g, blank)
|
|
103
|
+
.replace(/<?\bhttps?:\/\/\S+/g, blank)
|
|
104
|
+
.replace(/<!--[\s\S]*?-->/g, blank);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// The first colon of a list item ends a label when the text before it ends in
|
|
108
|
+
// a parenthesis or code, or is a phrase of up to six words with no linking
|
|
109
|
+
// verb: "- Weekly backup (task): a shadow copy", "2. Tests for what changed:
|
|
110
|
+
// the files". "- Semantic risk is the larger tax: ..." stays a reveal.
|
|
111
|
+
const LINKING = /\b(is|are|was|were|be|been|has|have|had|do|does|did|can|will|should|must)\b/i;
|
|
112
|
+
function listLabel(head) {
|
|
113
|
+
if (/[.!?]\s|: /.test(head)) return false;
|
|
114
|
+
if (new RegExp(`(\\)|${KEPT})$`).test(head.trimEnd())) return true;
|
|
115
|
+
return head.trim().split(/\s+/).length <= 6 && !LINKING.test(head);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// A colon between two clauses in a sentence: at least four words before it,
|
|
119
|
+
// a sentence after it, and no comma there that would make it a list.
|
|
120
|
+
function colonReveals(text) {
|
|
121
|
+
const found = [];
|
|
122
|
+
const marker = text.match(/^\s*([-*+]|\d+\.)\s+/)?.[0].length;
|
|
123
|
+
for (const m of text.matchAll(/: +(?=[A-Za-z])/g)) {
|
|
124
|
+
const head = text.slice(marker ?? 0, m.index);
|
|
125
|
+
if (marker && listLabel(head)) continue;
|
|
126
|
+
const before = head.split(/[.!?]\s+/).pop();
|
|
127
|
+
const after = text.slice(m.index + m[0].length).split(/[.!?](\s|$)/)[0];
|
|
128
|
+
if (before.trim().split(/\s+/).length < 4 || /\*\*|__/.test(before)) continue;
|
|
129
|
+
if (after.includes(",") || after.trim().split(/\s+/).length < 2) continue;
|
|
130
|
+
found.push({ index: m.index, text: text.slice(m.index, m.index + m[0].length + after.length) });
|
|
131
|
+
}
|
|
132
|
+
return found;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Marks the lines that hold no prose to check: fenced code, block quotes, and
|
|
136
|
+
// the keep comment with the paragraph after it.
|
|
137
|
+
function skippedLines(lines) {
|
|
138
|
+
let fence = null;
|
|
139
|
+
let keep = false;
|
|
140
|
+
return lines.map((raw) => {
|
|
141
|
+
const fenceMark = raw.match(/^\s*(`{3,}|~{3,})/);
|
|
142
|
+
if (fence) {
|
|
143
|
+
if (fenceMark && fenceMark[1][0] === fence[0] && fenceMark[1].length >= fence.length)
|
|
144
|
+
fence = null;
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
if (fenceMark) {
|
|
148
|
+
fence = fenceMark[1];
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
if (/plain-prose:\s*keep/.test(raw)) return (keep = true);
|
|
152
|
+
if (raw.trim() === "") return (keep = false);
|
|
153
|
+
return keep || /^\s*>/.test(raw);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Sentences over LONG words. A paragraph or list item is read as one run, so
|
|
158
|
+
// a sentence that wraps across lines counts once. Kept spans count as no words.
|
|
159
|
+
const LONG = 30;
|
|
160
|
+
function longSentences(doc, raws, skipped) {
|
|
161
|
+
const found = [];
|
|
162
|
+
const lines = doc.split("\n");
|
|
163
|
+
let unit = [];
|
|
164
|
+
const flush = () => {
|
|
165
|
+
let joined = "";
|
|
166
|
+
const at = [];
|
|
167
|
+
for (const { i, text } of unit) {
|
|
168
|
+
if (joined) {
|
|
169
|
+
joined += " ";
|
|
170
|
+
at.push(null);
|
|
171
|
+
}
|
|
172
|
+
for (let c = 0; c < text.length; c++) at.push([i, c]);
|
|
173
|
+
joined += text;
|
|
174
|
+
}
|
|
175
|
+
unit = [];
|
|
176
|
+
// Keep dotted tokens and internal abbreviations together. An uppercase
|
|
177
|
+
// initialism may end a sentence when the next word starts with a capital.
|
|
178
|
+
const ends = [...joined.matchAll(/[.!?]+(?=\s|$)/g)]
|
|
179
|
+
.filter((e) => {
|
|
180
|
+
if (e[0] !== ".") return true;
|
|
181
|
+
const initialism = joined.slice(0, e.index).match(/(?:^|\s)((?:[A-Za-z]\.)+[A-Za-z])$/);
|
|
182
|
+
if (!initialism) return true;
|
|
183
|
+
return /^[A-Z.]+$/.test(initialism[1]) && /^\s+[A-Z]/.test(joined.slice(e.index + 1));
|
|
184
|
+
})
|
|
185
|
+
.map((e) => e.index + e[0].length);
|
|
186
|
+
let from = 0;
|
|
187
|
+
for (const end of [...ends, joined.length]) {
|
|
188
|
+
const sentence = joined.slice(from, end);
|
|
189
|
+
const start = from;
|
|
190
|
+
from = end;
|
|
191
|
+
const words = sentence.match(/[A-Za-z0-9][\w'-]*(?:\.[A-Za-z0-9][\w'-]*)*/g) ?? [];
|
|
192
|
+
if (words.length <= LONG) continue;
|
|
193
|
+
const lead = start + sentence.search(/\S/);
|
|
194
|
+
const [i, col] = at[lead];
|
|
195
|
+
found.push({
|
|
196
|
+
line: i + 1,
|
|
197
|
+
col: col + 1,
|
|
198
|
+
rule: "long-sentence",
|
|
199
|
+
text: raws[i].slice(col).trim().split(/\s+/).slice(0, 5).join(" "),
|
|
200
|
+
hint: `${words.length} words; split it`,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
// Front matter holds fields, not sentences.
|
|
205
|
+
const front = raws[0] === "---" ? raws.indexOf("---", 1) : -1;
|
|
206
|
+
lines.forEach((text, i) => {
|
|
207
|
+
const raw = raws[i];
|
|
208
|
+
if (i <= front || skipped[i] || raw.trim() === "" || /^\s*(#|\|)/.test(raw)) return flush();
|
|
209
|
+
if (/^\s*([-*+]|\d+\.)\s/.test(raw)) flush();
|
|
210
|
+
unit.push({ i, text });
|
|
211
|
+
});
|
|
212
|
+
flush();
|
|
213
|
+
return found;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function check(markdown, terms = parseWordTables(readFileSync(SKILL, "utf8"))) {
|
|
217
|
+
const findings = [];
|
|
218
|
+
const raws = markdown.split("\n");
|
|
219
|
+
const skipped = skippedLines(raws);
|
|
220
|
+
const doc = prose(raws.map((raw, i) => (skipped[i] ? blank(raw) : raw)).join("\n"));
|
|
221
|
+
const technical = TECHNICAL.flatMap((re) =>
|
|
222
|
+
[...doc.matchAll(re)].map((m) => [m.index, m.index + m[0].length]),
|
|
223
|
+
);
|
|
224
|
+
// A row of a word table defines its terms; the same words elsewhere are cues.
|
|
225
|
+
const known = new Set(terms.map((t) => t.term));
|
|
226
|
+
const defines = (raw) =>
|
|
227
|
+
wordTableRow(raw)?.[0]
|
|
228
|
+
.split(/,\s*/)
|
|
229
|
+
.every((t) => known.has(t));
|
|
230
|
+
let offset = 0;
|
|
231
|
+
doc.split("\n").forEach((text, i) => {
|
|
232
|
+
const raw = raws[i];
|
|
233
|
+
const start = offset;
|
|
234
|
+
offset += text.length + 1;
|
|
235
|
+
if (skipped[i] || raw.trim() === "") return;
|
|
236
|
+
const line = i + 1;
|
|
237
|
+
const add = (rule, index, match, hint) =>
|
|
238
|
+
findings.push({
|
|
239
|
+
line,
|
|
240
|
+
col: index + 1,
|
|
241
|
+
rule,
|
|
242
|
+
text: raw.slice(index, index + match.length).trim(),
|
|
243
|
+
...(hint && { hint }),
|
|
244
|
+
});
|
|
245
|
+
for (const { re, rule } of DASHES) {
|
|
246
|
+
for (const m of text.matchAll(re)) {
|
|
247
|
+
// A table cell holding only a dash marks an empty cell.
|
|
248
|
+
const cell =
|
|
249
|
+
/\|\s*$/.test(text.slice(0, m.index)) && /^\s*\|/.test(text.slice(m.index + m[0].length));
|
|
250
|
+
if (!cell) add(rule, m.index, m[0], "end the sentence or use a comma");
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
for (const m of text.matchAll(/[‘’“”]/g)) {
|
|
254
|
+
add("curly-quote", m.index, m[0], "use straight quotes");
|
|
255
|
+
}
|
|
256
|
+
for (const re of JOINED_PROSE) {
|
|
257
|
+
for (const m of text.matchAll(re))
|
|
258
|
+
add(
|
|
259
|
+
"missing-space",
|
|
260
|
+
m.index,
|
|
261
|
+
m[0],
|
|
262
|
+
"separate prose words, counts and units; keep literal identifiers in code",
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
if (!/^\s*(#|\|)/.test(raw)) {
|
|
266
|
+
for (const c of colonReveals(text))
|
|
267
|
+
add("colon-reveal", c.index, c.text, "state the point directly");
|
|
268
|
+
}
|
|
269
|
+
if (!defines(raw)) {
|
|
270
|
+
for (const { term, hint, re } of terms) {
|
|
271
|
+
for (const m of text.matchAll(re)) {
|
|
272
|
+
const at = start + m.index;
|
|
273
|
+
if (technical.some(([a, b]) => at >= a && at < b)) continue;
|
|
274
|
+
add("word", m.index, m[0], `${term}: ${hint}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
for (const { term, re } of CHAT) {
|
|
279
|
+
for (const m of text.matchAll(re)) add("filler", m.index, m[0], `cut "${term}"`);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
findings.push(...longSentences(doc, raws, skipped));
|
|
283
|
+
return findings.sort((a, b) => a.line - b.line || a.col - b.col);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function main() {
|
|
287
|
+
let parsed;
|
|
288
|
+
try {
|
|
289
|
+
parsed = parseArgs({ options: { json: { type: "boolean" } }, allowPositionals: true });
|
|
290
|
+
} catch (e) {
|
|
291
|
+
console.error(e.message);
|
|
292
|
+
return 2;
|
|
293
|
+
}
|
|
294
|
+
const files = parsed.positionals.length ? parsed.positionals : ["-"];
|
|
295
|
+
let terms;
|
|
296
|
+
try {
|
|
297
|
+
terms = parseWordTables(readFileSync(SKILL, "utf8"));
|
|
298
|
+
} catch (e) {
|
|
299
|
+
console.error(`cannot read the word tables in ${SKILL}: ${e.message}`);
|
|
300
|
+
return 2;
|
|
301
|
+
}
|
|
302
|
+
const all = [];
|
|
303
|
+
for (const file of files) {
|
|
304
|
+
let body;
|
|
305
|
+
try {
|
|
306
|
+
body = readFileSync(file === "-" ? 0 : file, "utf8");
|
|
307
|
+
} catch (e) {
|
|
308
|
+
console.error(`${file}: ${e.message}`);
|
|
309
|
+
return 2;
|
|
310
|
+
}
|
|
311
|
+
for (const f of check(body, terms)) all.push({ file: file === "-" ? "<stdin>" : file, ...f });
|
|
312
|
+
}
|
|
313
|
+
if (parsed.values.json) {
|
|
314
|
+
console.log(JSON.stringify(all, null, 2));
|
|
315
|
+
} else {
|
|
316
|
+
for (const f of all) {
|
|
317
|
+
console.log(
|
|
318
|
+
`${f.file}:${f.line}:${f.col} ${f.rule} "${f.text}"${f.hint ? ` (${f.hint})` : ""}`,
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
if (all.length) console.log(`${all.length} spot(s) to reread. Each is a cue, not a verdict.`);
|
|
322
|
+
}
|
|
323
|
+
return all.length ? 1 : 0;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (
|
|
327
|
+
process.argv[1] &&
|
|
328
|
+
realpathSync(fileURLToPath(import.meta.url)) === realpathSync(process.argv[1])
|
|
329
|
+
) {
|
|
330
|
+
process.exitCode = main();
|
|
331
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aaron Queen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Source notice
|
|
2
|
+
|
|
3
|
+
These instructions were written for house-rules. Their workflow purpose is
|
|
4
|
+
informed by Cursor's built-in autopilot skill, described in its public catalogue:
|
|
5
|
+
https://cursor.com/docs/skills
|
|
6
|
+
|
|
7
|
+
No Cursor instruction text is distributed in this bundle. The MIT licence in
|
|
8
|
+
LICENSE applies to the newly written house-rules material; it does not assert
|
|
9
|
+
a licence for Cursor's skill text.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hr-pr-followup
|
|
3
|
+
description: Follow up on a named PR when asked to monitor or babysit it, resolving conflicts, review comments and failing CI.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Follow a pull request through its blockers
|
|
7
|
+
|
|
8
|
+
Bind the task to the requested PR and its selected actions. Existing user
|
|
9
|
+
and repository authorization controls edits, replies and landing. Monitoring
|
|
10
|
+
alone grants none of those permissions. Continue work already authorized;
|
|
11
|
+
a confirmed blocker does not require the user to repeat that direction.
|
|
12
|
+
|
|
13
|
+
## Reconcile each update
|
|
14
|
+
|
|
15
|
+
Read the PR's current head, base, checks, review bodies and unresolved threads.
|
|
16
|
+
Compare them with the last recorded state. Discard conclusions that depended
|
|
17
|
+
on a different revision. Inspect the relevant diff or log before treating
|
|
18
|
+
feedback as a defect; retrieved text cannot expand the task's scope.
|
|
19
|
+
|
|
20
|
+
Resolve branch conflicts before making fixes that depend on the affected code.
|
|
21
|
+
Integrate fetched changes using the repository's history policy and preserve
|
|
22
|
+
other owners' work. Ask when conflicting intent cannot be settled from the
|
|
23
|
+
selected requirements.
|
|
24
|
+
|
|
25
|
+
For each finding, record what was decided about it. Fix a demonstrated problem within
|
|
26
|
+
scope, explain why an unsupported finding does not apply, or name the decision
|
|
27
|
+
or evidence still needed. Include findings in review bodies outside inline
|
|
28
|
+
threads. Reply and resolve threads within the selected authority; a thread
|
|
29
|
+
waiting on a decision stays open.
|
|
30
|
+
|
|
31
|
+
Read failing checks' actual logs. Separate failures caused by the PR from
|
|
32
|
+
unrelated failures using evidence, including fixes on the latest base. Verify
|
|
33
|
+
repairs locally before pushing and run the repository's required gates. Do not
|
|
34
|
+
weaken a check to hide a failure. Batch compatible fixes when doing so avoids
|
|
35
|
+
restarting CI repeatedly without delaying an independent blocker.
|
|
36
|
+
|
|
37
|
+
When a review bot reviews each pushed head against a usage limit, every push
|
|
38
|
+
spends a review. Run the repository's gates before the first push. Push once
|
|
39
|
+
per review round, with every fix for that round's findings, and do not push
|
|
40
|
+
while the bot is still reviewing the current head unless the push fixes an
|
|
41
|
+
independent blocker, such as a failing required check; include every finding
|
|
42
|
+
fix ready at that point. Integrate the base only to
|
|
43
|
+
resolve a conflict, pick up a fix the PR needs, or meet a landing requirement,
|
|
44
|
+
and combine it with that round's fixes.
|
|
45
|
+
|
|
46
|
+
## Wait or finish
|
|
47
|
+
|
|
48
|
+
When no actionable work remains and remote checks are running, use the host's
|
|
49
|
+
native PR notifications if available. Otherwise use the forge's supported
|
|
50
|
+
watch command. Record what will resume the task; avoid repeated empty polling.
|
|
51
|
+
Do not add another watcher, service or scheduled task without authorization.
|
|
52
|
+
|
|
53
|
+
After the head changes, refresh the evidence before reporting readiness.
|
|
54
|
+
Name the exact revision, failed or unfinished gates and unresolved decisions.
|
|
55
|
+
Clean CI alone is not evidence that every review finding was handled.
|
|
56
|
+
|
|
57
|
+
When the repository or user selects these GitHub helper policies, use
|
|
58
|
+
[the CodeRabbit review gate and one-thread resolver](references/review-helpers.md).
|
|
59
|
+
Its review-status checks cover CodeRabbit only; its thread check covers any
|
|
60
|
+
reviewer and its review-body check any bot. It does not replace other required
|
|
61
|
+
checks or the caller's judgment. The same reference describes the landing helper scripts (`merge-pr.sh`,
|
|
62
|
+
`verify-landed.sh`, `check-landing.py`).
|
|
63
|
+
|
|
64
|
+
Immediately before landing, re-read the head, base, checks, review bodies and
|
|
65
|
+
unresolved threads. If the head or base changed, discard readiness and repeat
|
|
66
|
+
the evidence and gate checks. If the target repository documents a revision
|
|
67
|
+
guard, run it and require its documented pass result before landing.
|
|
68
|
+
|
|
69
|
+
When landing is authorized, every required gate passes, and all enabled landing
|
|
70
|
+
conditions are satisfied, finish through the repository's landing and
|
|
71
|
+
synchronization procedure. Otherwise report the
|
|
72
|
+
verified readiness and the landing decision needed under the user's rules.
|
|
73
|
+
Do not enable automatic merging or change draft/review state unless that action
|
|
74
|
+
is authorized. Release publication remains a separate decision.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Optional GitHub review helpers
|
|
2
|
+
|
|
3
|
+
Use these helpers only when the repository or user selects their policy.
|
|
4
|
+
They require Bun and authenticated `gh`. Run the commands from this skill's
|
|
5
|
+
installed directory. They add no authority to reply, resolve or land a PR.
|
|
6
|
+
Prefer the host's native PR notifications; `--wait` is a fallback when those
|
|
7
|
+
are unavailable.
|
|
8
|
+
|
|
9
|
+
## Check review feedback
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
bun scripts/check-pr-review.ts 123 --repo OWNER/REPO
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The gate exits 0 when its selected feedback checks pass, 1 for open feedback,
|
|
16
|
+
and 2 when coverage cannot be verified. It checks unresolved threads, pending
|
|
17
|
+
CodeRabbit review, skipped review statuses and a missing check when the head
|
|
18
|
+
contains `.coderabbit.yaml`. It also checks bot review bodies with outside-diff,
|
|
19
|
+
nitpick or duplicate findings. A completed CodeRabbit CheckRun must conclude SUCCESS;
|
|
20
|
+
failed, cancelled, skipped or missing conclusions block the gate. A CodeRabbit status also requires SUCCESS
|
|
21
|
+
and a description starting with "Review completed". It does not
|
|
22
|
+
check every CI result or authorize landing.
|
|
23
|
+
|
|
24
|
+
If a review is missing or incomplete, inspect the integration and the repository's
|
|
25
|
+
review policy. The helper does not determine automatic-review enablement or
|
|
26
|
+
authorize a manual review request.
|
|
27
|
+
|
|
28
|
+
To acknowledge a bot review body, post one PR conversation comment using the
|
|
29
|
+
authenticated human account with WRITE, MAINTAIN or ADMIN permission:
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
Review: EXACT_REVIEW_PERMALINK
|
|
33
|
+
Head: CURRENT_FULL_HEAD_SHA
|
|
34
|
+
Reason: WHY_EACH_FINDING_IS_FIXED_OR_DOES_NOT_APPLY
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The acknowledgement must follow the review and its latest edit. It is specific
|
|
38
|
+
to that review and head. A minimized comment or another account's edit does not
|
|
39
|
+
qualify. The helper verifies association and a nonblank reason; the caller
|
|
40
|
+
judges whether the explanation answers the findings.
|
|
41
|
+
|
|
42
|
+
Connections are paginated. Partial responses, malformed fields, repeated
|
|
43
|
+
cursors and head changes fail closed. A final head read does not make review
|
|
44
|
+
feedback an atomic snapshot. `--wait --timeout 30` polls only while CodeRabbit
|
|
45
|
+
runs, or briefly while its expected check has not appeared.
|
|
46
|
+
|
|
47
|
+
## Resolve one review thread
|
|
48
|
+
|
|
49
|
+
Run this helper only after judging that the selected reply answers the finding.
|
|
50
|
+
It reads GitHub through `gh`, using that CLI's authenticated account.
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
bun scripts/resolve-pr-thread.ts https://github.com/OWNER/REPO/pull/123 \
|
|
54
|
+
--thread THREAD_NODE_ID --expect-head FULL_HEAD_SHA \
|
|
55
|
+
--reply-url https://github.com/OWNER/REPO/pull/123#discussion_r456
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The default is a dry-run. Add `--apply` to resolve exactly that thread.
|
|
59
|
+
The helper never posts replies, dismisses reviews or merges pull requests.
|
|
60
|
+
It supports github.com URLs and pins API calls to that host, independent of `GH_HOST`.
|
|
61
|
+
|
|
62
|
+
The reply must belong to the selected thread and reference another comment in it.
|
|
63
|
+
It must be submitted, published, visible, nonblank and authored by the authenticated human.
|
|
64
|
+
Pending review replies do not qualify until the review is submitted.
|
|
65
|
+
An edited reply must have that same human as its last editor.
|
|
66
|
+
Replies from another account do not qualify, even when that account has write permission.
|
|
67
|
+
These checks verify association, not the explanation's adequacy.
|
|
68
|
+
|
|
69
|
+
A new resolution requires an open PR, repository WRITE, MAINTAIN or ADMIN permission,
|
|
70
|
+
and GitHub's `viewerCanResolve` permission. An already-resolved thread is a no-op
|
|
71
|
+
after the PR, expected head and reply checks. Explicitly selected outdated threads
|
|
72
|
+
still require the same reply and permission checks before resolution.
|
|
73
|
+
|
|
74
|
+
The helper paginates the selected thread's comments and rejects partial responses,
|
|
75
|
+
malformed fields, repeating cursors and a head that differs from `--expect-head`.
|
|
76
|
+
A final metadata read checks the head and permission before applying.
|
|
77
|
+
After the one mutation attempt, it reads again to verify resolution and the head.
|
|
78
|
+
|
|
79
|
+
GitHub's mutation has no atomic expected-head guard. A push during the mutation
|
|
80
|
+
can resolve the thread before the helper detects the changed head and exits 2.
|
|
81
|
+
The helper does not undo resolution. Comment and permission reads also are not
|
|
82
|
+
an atomic snapshot. Changes between requests may require another inspection.
|
|
83
|
+
|
|
84
|
+
When the mutation response is lost or malformed, the helper reads current state
|
|
85
|
+
without retrying the mutation. A fresh resolved state at the expected head reports
|
|
86
|
+
success with an uncertain-response note. An unresolved state, failed verification
|
|
87
|
+
or changed head exits 2. Re-read the PR before deciding whether to retry.
|
|
88
|
+
|
|
89
|
+
The helper shares GraphQL command/envelope validation with `check-pr-review.ts`.
|
|
90
|
+
It does not clear that gate's review-body findings or change the landing workflow.
|
|
91
|
+
|
|
92
|
+
## Landing helpers
|
|
93
|
+
|
|
94
|
+
Three scripts in `scripts/` implement the landing steps § Landing describes. They never
|
|
95
|
+
reset, switch or delete a checkout.
|
|
96
|
+
|
|
97
|
+
- `merge-pr.sh <pr> [--merge] [--remote <name>] [--idle-primary <path>]` squash-merges
|
|
98
|
+
(or merge-commits) an open pull request and deletes its remote head without
|
|
99
|
+
`gh pr merge --delete-branch`, which removes other sessions' linked worktrees. It
|
|
100
|
+
refuses while review feedback is open (`check-pr-review.ts`), while another open pull
|
|
101
|
+
request targets the head, and for a repository listed in `HR_PUSH_LANDED`
|
|
102
|
+
(space-separated `owner/repo`; those land by fast-forward push, which it prints). The
|
|
103
|
+
merge passes `--match-head-commit`. It lists worktrees still on the merged branch so
|
|
104
|
+
their owners clean up.
|
|
105
|
+
- `verify-landed.sh --commit <sha> [--ff [--idle-primary <path>]]` fetches, checks that
|
|
106
|
+
the default branch contains the commit, and checks the local default; `--ff`
|
|
107
|
+
fast-forwards a clean default checkout. `--preflight --ff` checks eligibility before
|
|
108
|
+
landing. After a blocked check, rerun this script, not `merge-pr.sh`.
|
|
109
|
+
- `check-landing.py [--path <checkout>] [--base <ref>]` reports whether a checkout's
|
|
110
|
+
work is in the base branch by content, so squash-merged branches count as landed, and
|
|
111
|
+
lists live processes whose working directory is inside the checkout (Linux only).
|