@bompus/house-rules 0.0.0-stage → 0.11.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/CONTRIBUTING.md +54 -0
- package/LICENSE +21 -0
- package/README.md +347 -2
- package/THIRD_PARTY_NOTICES.md +188 -0
- package/compose.mjs +119 -0
- package/composition.mjs +353 -0
- package/config-view.mjs +194 -0
- package/config.mjs +320 -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 +193 -0
- package/docs/npm-release.md +45 -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 +40 -4
- package/rule-references.mjs +91 -0
- package/rules/core.md +431 -0
- package/rules/modifiers/coded-offers.md +40 -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 +19 -0
- package/rules/modifiers/multi-agent.md +34 -0
- package/rules/modifiers/no-attribution.md +10 -0
- package/rules/modifiers/plan-files.md +50 -0
- package/rules/modifiers/question-cards.md +48 -0
- package/rules/modifiers/release-batching.md +13 -0
- package/rules/modifiers/scratch-on-disk.md +15 -0
- package/rules/modifiers/shared-host-load.md +48 -0
- package/rules/modifiers/solo-operator.md +13 -0
- package/rules/modifiers/squash-landing.md +22 -0
- package/rules/modifiers/swarmail.md +12 -0
- package/rules/references/github-actions.md +45 -0
- package/rules/references/landing.md +39 -0
- package/rules/references/multi-agent.md +47 -0
- package/rules/references/release-batching.md +43 -0
- package/rules/references/squash-landing.md +44 -0
- package/setup.mjs +375 -0
- package/skills/hr-agent-guidance-audit/LICENSE +21 -0
- package/skills/hr-agent-guidance-audit/SKILL.md +86 -0
- package/skills/hr-agent-guidance-audit/agents/openai.yaml +2 -0
- package/skills/hr-agent-guidance-audit/references/records-and-lessons.md +60 -0
- package/skills/hr-agent-guidance-refresh/LICENSE +21 -0
- package/skills/hr-agent-guidance-refresh/SKILL.md +68 -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 +316 -0
- package/skills/hr-benchmarking/agents/openai.yaml +2 -0
- package/skills/hr-benchmarking/references/javascript.md +17 -0
- package/skills/hr-benchmarking/references/priority-findings-20261006.md +115 -0
- package/skills/hr-benchmarking/references/tools.md +160 -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 +101 -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-change-impact/SKILL.md +113 -0
- package/skills/hr-change-impact/agents/openai.yaml +5 -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 +147 -0
- package/skills/hr-code-review/agents/openai.yaml +3 -0
- package/skills/hr-code-review/references/evidence.md +57 -0
- package/skills/hr-design-exploration/SKILL.md +82 -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 +72 -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-handoff/SKILL.md +91 -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 +105 -0
- package/skills/hr-handoff/scripts/check-reconciliation.mjs +282 -0
- package/skills/hr-house-rules-setup/SKILL.md +100 -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 +142 -0
- package/skills/hr-lean-plan/agents/openai.yaml +5 -0
- package/skills/hr-maintainability-review/SKILL.md +176 -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 +261 -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 +71 -0
- package/skills/hr-pr-followup/agents/openai.yaml +2 -0
- package/skills/hr-pr-followup/references/review-helpers.md +90 -0
- package/skills/hr-pr-followup/scripts/check-pr-review.ts +510 -0
- package/skills/hr-pr-followup/scripts/resolve-pr-thread.ts +219 -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-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-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/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-what-next/SKILL.md +61 -0
- package/skills/hr-what-next/agents/openai.yaml +4 -0
- package/skills/hr-what-next/references/reporting.md +68 -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 +104 -0
- package/skills/hr-writing-pr/agents/openai.yaml +2 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Offline accounting check. See ../references/reconciliation.md for the input
|
|
3
|
+
// format and limits. No writes, host queries, or semantic-completeness claim.
|
|
4
|
+
// Exit 0: mechanically complete; 1: invalid/unreadable; 2: valid but partial.
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
6
|
+
import { dirname, resolve } from "node:path";
|
|
7
|
+
|
|
8
|
+
const errors = [];
|
|
9
|
+
const gaps = [];
|
|
10
|
+
const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
11
|
+
const text = (value) => typeof value === "string" && value.trim().length > 0;
|
|
12
|
+
const states = ["done", "open", "deferred"];
|
|
13
|
+
const key = (source, record) => JSON.stringify([source, record]);
|
|
14
|
+
|
|
15
|
+
function requireText(value, label) {
|
|
16
|
+
if (!text(value)) errors.push(`${label}: expected nonempty text`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function list(value, label) {
|
|
20
|
+
if (Array.isArray(value)) return value;
|
|
21
|
+
errors.push(`${label}: expected an array`);
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function entries(value, label) {
|
|
26
|
+
return list(value, label).filter((entry, index) => {
|
|
27
|
+
if (object(entry)) return true;
|
|
28
|
+
errors.push(`${label}[${index}]: expected an object`);
|
|
29
|
+
return false;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function timestamp(value, label) {
|
|
34
|
+
if (
|
|
35
|
+
!text(value) ||
|
|
36
|
+
!/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d{1,3})?Z$/.test(value) ||
|
|
37
|
+
!Number.isFinite(Date.parse(value)) ||
|
|
38
|
+
new Date(value).toISOString().slice(0, 19) !== value.slice(0, 19)
|
|
39
|
+
) {
|
|
40
|
+
errors.push(`${label}: expected a UTC ISO timestamp`);
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
return Date.parse(value);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function unique(entries, label) {
|
|
47
|
+
const map = new Map();
|
|
48
|
+
for (const entry of entries) {
|
|
49
|
+
requireText(entry.id, `${label}.id`);
|
|
50
|
+
if (map.has(entry.id)) errors.push(`${label}: duplicate id ${entry.id}`);
|
|
51
|
+
map.set(entry.id, entry);
|
|
52
|
+
}
|
|
53
|
+
return map;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function check(receipt, directory) {
|
|
57
|
+
if (!object(receipt)) throw new Error("receipt: expected an object");
|
|
58
|
+
if (receipt.version !== 1) errors.push("version: expected 1");
|
|
59
|
+
requireText(receipt.owner, "owner");
|
|
60
|
+
if (!["complete", "partial"].includes(receipt.coverage)) {
|
|
61
|
+
errors.push("coverage: expected complete or partial");
|
|
62
|
+
}
|
|
63
|
+
const sources = unique(entries(receipt.sources, "sources"), "source");
|
|
64
|
+
const items = unique(entries(receipt.items, "items"), "item");
|
|
65
|
+
const records = new Map();
|
|
66
|
+
const outcomes = new Map();
|
|
67
|
+
if (sources.size === 0) gaps.push("no source snapshots supplied");
|
|
68
|
+
|
|
69
|
+
for (const source of sources.values()) {
|
|
70
|
+
const label = `source ${source.id}`;
|
|
71
|
+
requireText(source.file, `${label}.file`);
|
|
72
|
+
requireText(source.snapshot, `${label}.snapshot`);
|
|
73
|
+
const declared = list(source.gaps, `${label}.gaps`);
|
|
74
|
+
for (const gap of declared) {
|
|
75
|
+
requireText(gap, `${label}.gaps[]`);
|
|
76
|
+
gaps.push(`${label}: ${gap}`);
|
|
77
|
+
}
|
|
78
|
+
if (source.expectedRecords === null) gaps.push(`${label}: total unknown`);
|
|
79
|
+
else if (!Number.isSafeInteger(source.expectedRecords) || source.expectedRecords < 0) {
|
|
80
|
+
errors.push(`${label}.expectedRecords: expected a nonnegative integer or null`);
|
|
81
|
+
}
|
|
82
|
+
if (!text(source.file)) continue;
|
|
83
|
+
let raw;
|
|
84
|
+
try {
|
|
85
|
+
raw = readFileSync(resolve(directory, source.file), "utf8");
|
|
86
|
+
} catch (error) {
|
|
87
|
+
if (error.code === "ENOENT" && declared.length > 0) {
|
|
88
|
+
gaps.push(`${label}: snapshot unavailable`);
|
|
89
|
+
} else errors.push(`${label}: cannot read snapshot (${error.code ?? "error"})`);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
let data;
|
|
93
|
+
try {
|
|
94
|
+
data = JSON.parse(raw);
|
|
95
|
+
} catch {
|
|
96
|
+
errors.push(`${label}: invalid snapshot JSON`);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (!object(data)) {
|
|
100
|
+
errors.push(`${label}: snapshot must be an object`);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const rows = unique(entries(data.records, `${label}.records`), `${label} record`);
|
|
104
|
+
if (Number.isSafeInteger(source.expectedRecords) && source.expectedRecords !== rows.size) {
|
|
105
|
+
gaps.push(`${label}: expected ${source.expectedRecords} records, read ${rows.size}`);
|
|
106
|
+
}
|
|
107
|
+
for (const row of rows.values()) {
|
|
108
|
+
requireText(row.text, `${label} record ${row.id}.text`);
|
|
109
|
+
records.set(key(source.id, row.id), row);
|
|
110
|
+
if (row.outcome !== undefined) {
|
|
111
|
+
if (!object(row.outcome) || !states.includes(row.outcome.state)) {
|
|
112
|
+
errors.push(`${label} record ${row.id}: invalid outcome`);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const item = row.outcome.item;
|
|
116
|
+
if (!items.has(item))
|
|
117
|
+
errors.push(`${label} record ${row.id}: unknown outcome item ${item}`);
|
|
118
|
+
const at = timestamp(row.at, `${label} record ${row.id}.at`);
|
|
119
|
+
const events = outcomes.get(item) ?? [];
|
|
120
|
+
events.push({ ...row.outcome, at, source: source.id, record: row.id });
|
|
121
|
+
outcomes.set(item, events);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const accounting = new Map();
|
|
127
|
+
for (const row of entries(receipt.accounting, "accounting")) {
|
|
128
|
+
const ref = key(row.source, row.record);
|
|
129
|
+
const label = `accounting ${row.source}/${row.record}`;
|
|
130
|
+
if (!records.has(ref)) errors.push(`${label}: unknown source record`);
|
|
131
|
+
if (accounting.has(ref)) errors.push(`${label}: duplicate accounting`);
|
|
132
|
+
const mapped = row.items === undefined ? [] : list(row.items, `${label}.items`);
|
|
133
|
+
if (mapped.length > 0 === text(row.context)) {
|
|
134
|
+
errors.push(`${label}: supply item IDs or a context-only reason`);
|
|
135
|
+
}
|
|
136
|
+
for (const item of mapped) {
|
|
137
|
+
if (!items.has(item)) errors.push(`${label}: unknown item ${item}`);
|
|
138
|
+
}
|
|
139
|
+
accounting.set(ref, mapped);
|
|
140
|
+
}
|
|
141
|
+
for (const ref of records.keys()) {
|
|
142
|
+
if (!accounting.has(ref)) errors.push(`unaccounted record ${ref}`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function evidence(value, label, item) {
|
|
146
|
+
const refs = entries(value, `${label}.evidence`);
|
|
147
|
+
if (refs.length === 0) errors.push(`${label}: evidence required`);
|
|
148
|
+
for (const ref of refs) {
|
|
149
|
+
const id = key(ref.source, ref.record);
|
|
150
|
+
if (!records.has(id)) errors.push(`${label}: unknown evidence ${id}`);
|
|
151
|
+
if (item && !accounting.get(id)?.includes(item)) {
|
|
152
|
+
errors.push(`${label}: evidence ${id} is not mapped to this item`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
for (const item of items.values()) {
|
|
158
|
+
const label = `item ${item.id}`;
|
|
159
|
+
requireText(item.owner, `${label}.owner`);
|
|
160
|
+
requireText(item.next, `${label}.next`);
|
|
161
|
+
if (!states.includes(item.state)) errors.push(`${label}: invalid state`);
|
|
162
|
+
evidence(item.evidence, label, item.id);
|
|
163
|
+
const events = (outcomes.get(item.id) ?? []).sort((a, b) => a.at - b.at);
|
|
164
|
+
for (let i = 0; i < events.length; i++) {
|
|
165
|
+
const event = events[i];
|
|
166
|
+
if (!accounting.get(key(event.source, event.record))?.includes(item.id)) {
|
|
167
|
+
errors.push(`${label}: outcome ${event.source}/${event.record} is not mapped to this item`);
|
|
168
|
+
}
|
|
169
|
+
const previous = events[i - 1];
|
|
170
|
+
if (previous?.at === event.at && previous.state !== event.state) {
|
|
171
|
+
errors.push(`${label}: conflicting outcomes at the same time`);
|
|
172
|
+
}
|
|
173
|
+
if (previous?.state === "done" && event.state !== "done" && event.reopens !== true) {
|
|
174
|
+
errors.push(`${label}: reopening needs a declared later request or contradictory evidence`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const latest = events.at(-1);
|
|
178
|
+
if (latest && latest.state !== item.state) {
|
|
179
|
+
errors.push(`${label}: state ${item.state} contradicts latest outcome ${latest.state}`);
|
|
180
|
+
}
|
|
181
|
+
if (
|
|
182
|
+
latest &&
|
|
183
|
+
!item.evidence?.some?.((ref) => ref.source === latest.source && ref.record === latest.record)
|
|
184
|
+
) {
|
|
185
|
+
errors.push(`${label}: latest outcome must be included in its evidence`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const git = receipt.git;
|
|
190
|
+
if (!object(git)) errors.push("git: explicit inventory required, including when empty");
|
|
191
|
+
else {
|
|
192
|
+
requireText(git.scope, "git.scope");
|
|
193
|
+
timestamp(git.checkedAt, "git.checkedAt");
|
|
194
|
+
evidence(git.evidence, "git");
|
|
195
|
+
const seen = new Set();
|
|
196
|
+
for (const group of ["ownedPrs", "dependencyPrs"]) {
|
|
197
|
+
for (const [index, pr] of list(git[group], `git.${group}`).entries()) {
|
|
198
|
+
const label = `git.${group}[${index}]`;
|
|
199
|
+
if (!object(pr)) {
|
|
200
|
+
errors.push(`${label}: expected an object`);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
let url;
|
|
204
|
+
try {
|
|
205
|
+
url = new URL(pr.url);
|
|
206
|
+
} catch {
|
|
207
|
+
errors.push(`${label}: invalid URL`);
|
|
208
|
+
}
|
|
209
|
+
if (url && (url.protocol !== "https:" || url.username || url.password)) {
|
|
210
|
+
errors.push(`${label}: expected an HTTPS URL without credentials`);
|
|
211
|
+
}
|
|
212
|
+
if (seen.has(pr.url)) errors.push(`${label}: duplicate PR across inventories`);
|
|
213
|
+
seen.add(pr.url);
|
|
214
|
+
requireText(pr.owner, `${label}.owner`);
|
|
215
|
+
if (group === "ownedPrs" && pr.owner !== receipt.owner) {
|
|
216
|
+
errors.push(`${label}: owner differs from receipt owner; record dependencies separately`);
|
|
217
|
+
}
|
|
218
|
+
if (group === "dependencyPrs" && pr.owner === receipt.owner) {
|
|
219
|
+
errors.push(`${label}: self-owned PR belongs in ownedPrs`);
|
|
220
|
+
}
|
|
221
|
+
if (!["open", "closed", "merged"].includes(pr.state))
|
|
222
|
+
errors.push(`${label}: invalid state`);
|
|
223
|
+
timestamp(pr.checkedAt, `${label}.checkedAt`);
|
|
224
|
+
requireText(pr.next, `${label}.next`);
|
|
225
|
+
evidence(pr.evidence, label);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
for (const [index, commit] of list(git.commitsWithoutPr, "git.commitsWithoutPr").entries()) {
|
|
229
|
+
const label = `git.commitsWithoutPr[${index}]`;
|
|
230
|
+
if (!object(commit)) {
|
|
231
|
+
errors.push(`${label}: expected an object`);
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
for (const field of ["repo", "branch", "sha", "next"])
|
|
235
|
+
requireText(commit[field], `${label}.${field}`);
|
|
236
|
+
if (typeof commit.sha !== "string" || !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/i.test(commit.sha)) {
|
|
237
|
+
errors.push(`${label}: expected a full Git object ID`);
|
|
238
|
+
}
|
|
239
|
+
if (commit.owner !== receipt.owner) errors.push(`${label}: owner differs from receipt owner`);
|
|
240
|
+
evidence(commit.evidence, label);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (receipt.coverage === "complete" && gaps.length > 0) {
|
|
244
|
+
errors.push("complete coverage claimed despite source gaps");
|
|
245
|
+
}
|
|
246
|
+
if (receipt.coverage === "partial" && gaps.length === 0)
|
|
247
|
+
gaps.push("receipt declares partial coverage");
|
|
248
|
+
return { sources: sources.size, records: records.size, items: items.size };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
let counts = { sources: 0, records: 0, items: 0 };
|
|
252
|
+
try {
|
|
253
|
+
if (process.argv.length !== 3) throw new Error("usage: check-reconciliation.mjs <receipt.json>");
|
|
254
|
+
const file = resolve(process.argv[2]);
|
|
255
|
+
counts = check(JSON.parse(readFileSync(file, "utf8")), dirname(file));
|
|
256
|
+
} catch (error) {
|
|
257
|
+
// Avoid printing an input excerpt from JSON.parse or filesystem paths.
|
|
258
|
+
errors.push(
|
|
259
|
+
error instanceof SyntaxError
|
|
260
|
+
? "receipt: invalid JSON"
|
|
261
|
+
: error.code
|
|
262
|
+
? `receipt: ${error.code}`
|
|
263
|
+
: error.message,
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
const partial = gaps.length > 0;
|
|
267
|
+
const status = errors.length > 0 ? "invalid" : partial ? "partial" : "complete";
|
|
268
|
+
console.log(
|
|
269
|
+
JSON.stringify(
|
|
270
|
+
{
|
|
271
|
+
status,
|
|
272
|
+
...counts,
|
|
273
|
+
errors,
|
|
274
|
+
gaps,
|
|
275
|
+
limits:
|
|
276
|
+
"Checks supplied accounting and declared outcomes only. Does not prove semantic completeness, export completeness, live PR state, or landing authorization.",
|
|
277
|
+
},
|
|
278
|
+
null,
|
|
279
|
+
2,
|
|
280
|
+
),
|
|
281
|
+
);
|
|
282
|
+
process.exitCode = errors.length > 0 ? 1 : partial ? 2 : 0;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hr-house-rules-setup
|
|
3
|
+
description: "Set up or change house rules for this user: pick opt-in modifiers, create the personal layer, compose the rules file and connect it to each agent host. Use when the user asks to install, configure or change their house rules or modifiers."
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# House rules setup
|
|
8
|
+
|
|
9
|
+
The user's choices live in a personal layer, by default
|
|
10
|
+
`~/.config/house-rules/`: `house-rules.json`, plus optional `rules/` and
|
|
11
|
+
`skills/` directories. Composition never edits host files; connecting a host
|
|
12
|
+
is a separate step the user approves.
|
|
13
|
+
|
|
14
|
+
The commands below use `node`. If only Bun is installed, replace `node` with
|
|
15
|
+
`bun` in every `compose.mjs` command.
|
|
16
|
+
|
|
17
|
+
For a user working directly in a terminal, use `node compose.mjs setup` for
|
|
18
|
+
numbered selection, review and explicit save. Read `docs/configuration.md`
|
|
19
|
+
for commands and safeguards. Saving completes only configuration selection;
|
|
20
|
+
composition and approved host connection remain separate steps below.
|
|
21
|
+
For an agent running without a terminal, use the preview/apply workflow below.
|
|
22
|
+
|
|
23
|
+
1. Find the house-rules checkout (the directory holding `compose.mjs`;
|
|
24
|
+
`install.sh` puts it in `${XDG_DATA_HOME:-~/.local/share}/house-rules`,
|
|
25
|
+
`install.ps1` in `%LOCALAPPDATA%\house-rules`).
|
|
26
|
+
When there is none, ask the user where to clone it.
|
|
27
|
+
Done when the checkout and the user's config path are known.
|
|
28
|
+
2. Inspect `node compose.mjs config status --config <path>`.
|
|
29
|
+
If status reports legacy skill names, stop inspection. Read the config and follow
|
|
30
|
+
`docs/skill-names.md` before continuing. If catalog or status reports
|
|
31
|
+
`unknown modifier` or `question-cards requires coded-offers`, read the config
|
|
32
|
+
for current choices. Follow `docs/configuration.md` and preview the user's
|
|
33
|
+
selection in step 3
|
|
34
|
+
with `--disable-modifier <name>` or `--questions`.
|
|
35
|
+
For any other status failure, show the error and preserve the existing file
|
|
36
|
+
or filesystem object. Show skill choices from the README's Skills table and
|
|
37
|
+
known personal skill directories. Stop before `config set`, composition or
|
|
38
|
+
host writes. Ask the user how to repair the configuration or select a usable
|
|
39
|
+
path. For `config must be a regular file, not a symlink or directory`, ask
|
|
40
|
+
them to select or create a regular config file. Resume this step after the
|
|
41
|
+
agreed repair or selected path passes `config validate`.
|
|
42
|
+
Preserve existing selections. Fresh installers use the sample config's small
|
|
43
|
+
skill set with no modifiers. When initializing a missing config manually,
|
|
44
|
+
show `examples/person/house-rules.json` and get the user's selection before
|
|
45
|
+
creating it; the CLI treats a missing config as the legacy full skill set.
|
|
46
|
+
Explain the current selection briefly. Ask about additions only when the
|
|
47
|
+
user's stated workflow needs them, or they ask to customize it. Use
|
|
48
|
+
`config catalog` to find those choices; show the full catalog only on request.
|
|
49
|
+
Personal skills override shipped ones. Keep independent names, layers,
|
|
50
|
+
unrelated settings and exclusions.
|
|
51
|
+
|
|
52
|
+
If the user wants Swarmail, offer its modifier. When its MCP tools are absent,
|
|
53
|
+
point to https://github.com/bompus/swarmail and ask before installing it.
|
|
54
|
+
If they want different question presentation, offer plain text, coded text,
|
|
55
|
+
or coded text with cards using `--questions plain|coded|cards`. Cards still
|
|
56
|
+
require a complete text offer. Explain that host, provider and model behavior
|
|
57
|
+
varies; card availability and presentation are unverified until checked.
|
|
58
|
+
Both text-only choices prohibit card tools. If personal rules still request
|
|
59
|
+
cards, resolve that conflict before changing those rules or composing.
|
|
60
|
+
Done when the user selects relevant changes or keeps the current setup.
|
|
61
|
+
|
|
62
|
+
3. Use `node compose.mjs config set --config <path>` with the selected
|
|
63
|
+
modifier and skill toggles or question preset. It previews
|
|
64
|
+
without saving. Read every change and personal rule override; resolve any
|
|
65
|
+
conflict before writing. Repeat those flags with `--apply --expect <revision>`
|
|
66
|
+
using the preview's revision. Done when it exits 0 and status confirms the
|
|
67
|
+
selection. A stale revision requires a fresh preview, not a blind retry.
|
|
68
|
+
The CLI preserves layers, custom settings and unrelated exclusions; it
|
|
69
|
+
does not edit personal rule prose or connect hosts. Read
|
|
70
|
+
`docs/configuration.md` for flags, write safeguards and lock recovery.
|
|
71
|
+
Create `rules/` and `skills/` beside the config when the user needs them.
|
|
72
|
+
Explain the file format once. A rules file starts with frontmatter naming
|
|
73
|
+
`replaces:`, `after:`, `before:` or `removes:` and a core heading, then its
|
|
74
|
+
own `## ` heading; without frontmatter it is appended at the end. A skill in
|
|
75
|
+
the personal `skills/` replaces the house-rules skill of the same name.
|
|
76
|
+
4. Run `node compose.mjs --config <path> --out <personal dir>/rules.md`. Done
|
|
77
|
+
when it exits 0; on an error, show it and fix the named file.
|
|
78
|
+
5. Ask which hosts the user runs, then show what connecting each one changes
|
|
79
|
+
and ask before writing:
|
|
80
|
+
- Claude Code: add `@<personal dir>/rules.md` on its own line in
|
|
81
|
+
`~/.claude/CLAUDE.md`.
|
|
82
|
+
- Hosts without file includes (for example a global `AGENTS.md`): copy the
|
|
83
|
+
composed file there between `<!-- house-rules:start -->` and
|
|
84
|
+
`<!-- house-rules:end -->` lines, replacing only that block on later runs.
|
|
85
|
+
Place the `house-rules-references/` directory beside that file, because
|
|
86
|
+
the written rules link to it.
|
|
87
|
+
- Hosts with a settings screen for user rules: paste the stdout output of
|
|
88
|
+
`node compose.mjs --config <path>`, which keeps the procedures inline.
|
|
89
|
+
Keep everything else in those files. Done when every chosen host is
|
|
90
|
+
connected or the user declined it.
|
|
91
|
+
6. For skills, compose them with `--skills-out <dir>` into a directory that
|
|
92
|
+
does not exist yet or is empty; when the previous run's directory is in the
|
|
93
|
+
way, ask before removing it. Then point or copy each host's skills
|
|
94
|
+
directory at it, again with approval.
|
|
95
|
+
7. When the user asks to evaluate reply behavior, use `evals/end-of-reply/run.mjs`
|
|
96
|
+
against their selected models.
|
|
97
|
+
|
|
98
|
+
For a repository, suggest the pointer pattern in `examples/project/AGENTS.md`:
|
|
99
|
+
point at § End of every reply and § Offers instead of restating them, and add
|
|
100
|
+
only what the project changes.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hr-issue-tracker-setup
|
|
3
|
+
description: "Configure a project's issue tracker and conventions for domain terms and architecture decision records (ADRs). Use when explicitly asked to establish or change those conventions; reuse existing configuration. General agent, CI and development-environment setup are outside this workflow."
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Project tracker setup
|
|
8
|
+
|
|
9
|
+
Make the project's tracker and domain-document conventions usable from its
|
|
10
|
+
agent guidance. Configure only the conventions the user requested.
|
|
11
|
+
|
|
12
|
+
## Establish what already exists
|
|
13
|
+
|
|
14
|
+
Read the project's instruction root and the tracker, glossary and ADR guidance
|
|
15
|
+
it names. Follow existing links before searching for another system. Identify
|
|
16
|
+
which tracker owns tasks, where its records live, and how domain terms and
|
|
17
|
+
decisions are recorded. A convention can be complete without a separate glossary
|
|
18
|
+
or ADR directory.
|
|
19
|
+
|
|
20
|
+
When the requested configuration already works, report its entry points and
|
|
21
|
+
leave it alone. Other workflows do not require this setup to run first.
|
|
22
|
+
|
|
23
|
+
## Resolve the missing conventions
|
|
24
|
+
|
|
25
|
+
Reuse the project's tracker and document locations. Do not create a second
|
|
26
|
+
tracker, replace an established one or impose glossary/ADR files by default.
|
|
27
|
+
If a choice is missing, ask only for the decisions that change the requested
|
|
28
|
+
configuration, such as which tracker to use or whether to record domain terms.
|
|
29
|
+
Use the active host's decision format and existing user answers.
|
|
30
|
+
|
|
31
|
+
Document how the chosen tracker represents tasks, child decisions, supporting
|
|
32
|
+
evidence, blockers and ready work. Use its existing concepts; add fields or
|
|
33
|
+
structure only when a missing distinction prevents the requested workflow.
|
|
34
|
+
|
|
35
|
+
## Update and verify the owning guidance
|
|
36
|
+
|
|
37
|
+
Edit the project's canonical guidance in place. Its instruction root should
|
|
38
|
+
point to the tracker and detailed domain-document conventions rather than
|
|
39
|
+
repeat them. Preserve project safeguards and generated loading adapters;
|
|
40
|
+
use their owning generator when an adapter needs updating.
|
|
41
|
+
|
|
42
|
+
Keep shared personal skills in their configured shared source and installation
|
|
43
|
+
scope. Project-only skills belong with the project. This workflow does not
|
|
44
|
+
install skills, configure agent accounts or set up CI and developer tooling.
|
|
45
|
+
|
|
46
|
+
Run the project's applicable guidance checks and verify the documented entry
|
|
47
|
+
points from the intended workspace. Report what changed, what was already
|
|
48
|
+
configured and any decision or check still unresolved. Do not claim runtime
|
|
49
|
+
loading from a file update alone.
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hr-lean-plan
|
|
3
|
+
description: "Draft or critique an implementation plan in the conversation, with the fewest moving parts, before implementation. Use when asked to plan, review or tighten an approach without changing files or writing code; saving a plan file or implementing the work falls outside this read-only mode."
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Lean plan
|
|
8
|
+
|
|
9
|
+
A plan is cheap to change and code is not. This skill spends effort on the plan
|
|
10
|
+
so the build has less to do. It has two modes with one bar: **write** a plan
|
|
11
|
+
from a request, or **critique** a plan someone else wrote. Either way the output
|
|
12
|
+
is a plan or a review of one; change no files and write no code here.
|
|
13
|
+
|
|
14
|
+
The bar: every step produces an outcome someone can check, and no step survives
|
|
15
|
+
that the goal does not need.
|
|
16
|
+
|
|
17
|
+
## Pin the goal
|
|
18
|
+
|
|
19
|
+
Before drafting steps, write down four things:
|
|
20
|
+
|
|
21
|
+
- **Done means**: what is observably true when the work is finished.
|
|
22
|
+
- **Out of scope**: what a reader might expect but this work will not do.
|
|
23
|
+
- **Must not regress**: behavior, data, interfaces or performance that has to
|
|
24
|
+
stay as it is.
|
|
25
|
+
- **The real constraint**: the one limit that shapes the plan, such as a
|
|
26
|
+
deadline, a frozen interface, a migration window or a team boundary.
|
|
27
|
+
|
|
28
|
+
Read the code, docs and config that touch the goal before asking anything. Ask
|
|
29
|
+
only questions whose answer would change the plan's shape; a detail you can
|
|
30
|
+
choose sensibly, choose and record as an assumption.
|
|
31
|
+
|
|
32
|
+
Push back on an unclear request before planning around it. Name what is
|
|
33
|
+
unclear, why it blocks a good plan and what would settle it. Common causes:
|
|
34
|
+
a scope verb such as "improve" or "clean up" with no measurable result;
|
|
35
|
+
constraints that conflict; no named owner for the change; a solution stated
|
|
36
|
+
before the problem; a load-bearing decision (schema, API, flag strategy)
|
|
37
|
+
left for implementation; a term with no settled meaning in this codebase;
|
|
38
|
+
several goals with no priority between them.
|
|
39
|
+
|
|
40
|
+
When an answer that decides the shape is missing, stop there: return the
|
|
41
|
+
questions and an outline headed **Provisional, pending answers**, with each
|
|
42
|
+
open question marked where
|
|
43
|
+
it would change a step.
|
|
44
|
+
|
|
45
|
+
## Shrink before you sequence
|
|
46
|
+
|
|
47
|
+
Look for a framing that deletes work before ordering it:
|
|
48
|
+
|
|
49
|
+
- Can an existing module, table, flag or endpoint carry the new behavior? Grow
|
|
50
|
+
the code that already owns that idea rather than adding a layer, service
|
|
51
|
+
or package for one feature.
|
|
52
|
+
- Does the plan need a migration, feature flag, compatibility shim or a second
|
|
53
|
+
code path running beside the first? Each one has to be built, tested and later
|
|
54
|
+
removed. Keep it only when the must-not-regress list or the real constraint
|
|
55
|
+
requires it, and say which.
|
|
56
|
+
- Would a narrower done-state, agreed with the user, remove a whole phase?
|
|
57
|
+
Offer it rather than assuming it.
|
|
58
|
+
- Does a step increase coupling or state in a cohesive module, or move work
|
|
59
|
+
across its boundary? Name the payoff and the behavior it could affect.
|
|
60
|
+
Route cross-cutting concerns such as auth, caching and error handling through
|
|
61
|
+
their existing owning layer. A new layer needs a reason the current one
|
|
62
|
+
cannot meet the goal.
|
|
63
|
+
|
|
64
|
+
Count the moving parts the plan adds: new files, modules, services,
|
|
65
|
+
dependencies, config keys, flags, migrations, background jobs and parallel
|
|
66
|
+
paths. A smaller count that still meets the done-state is the better plan.
|
|
67
|
+
|
|
68
|
+
## Write each step
|
|
69
|
+
|
|
70
|
+
Each step carries these slots:
|
|
71
|
+
|
|
72
|
+
- **Outcome**: a concrete state of the system, not an activity. "Orders API
|
|
73
|
+
returns `shipped_at`" is an outcome; "work on the orders API" is not.
|
|
74
|
+
- **Owner**: the person, team, agent or module that holds it.
|
|
75
|
+
- **Check**: how anyone confirms the outcome, tied to that outcome.
|
|
76
|
+
- **Depends on**: earlier steps it needs, or "none".
|
|
77
|
+
|
|
78
|
+
Steps such as "handle edge cases", "refactor as needed", "clean up" or "add
|
|
79
|
+
tests" are defects: name the cases, the refactor or the behavior under test,
|
|
80
|
+
or delete the step. For each step ask what breaks if it is removed; if nothing
|
|
81
|
+
in the done-state does, remove it.
|
|
82
|
+
|
|
83
|
+
Watch for conditionals. Several steps that say "if X, also do Y" mean a
|
|
84
|
+
decision has not been made. Make that decision, or a short time-boxed spike
|
|
85
|
+
whose result is the decision, an explicit early step, then write the later
|
|
86
|
+
steps for the chosen branch only.
|
|
87
|
+
|
|
88
|
+
## Order and slice
|
|
89
|
+
|
|
90
|
+
- Put the riskiest unknown first, so a bad answer changes the plan while it is
|
|
91
|
+
still cheap.
|
|
92
|
+
- Slice the work into pieces that can each merge on their own and leave the
|
|
93
|
+
system working. A slice that only makes sense once the next one lands is half
|
|
94
|
+
of a larger slice. Splitting the same coupling across pull requests does not
|
|
95
|
+
remove it.
|
|
96
|
+
- Check whether feature-specific behavior leaks into shared runtime paths.
|
|
97
|
+
Keep unrelated callers' behavior intact and name the boundary that does so;
|
|
98
|
+
isolation does not automatically require a new service or wrapper.
|
|
99
|
+
- Mark steps with no dependency between them as parallel.
|
|
100
|
+
- Make each cutover atomic. When it cannot be, name the intermediate state the
|
|
101
|
+
system will sit in, how long it may stay there, and how to roll back from it.
|
|
102
|
+
|
|
103
|
+
## Verify against outcomes
|
|
104
|
+
|
|
105
|
+
Verification answers "is the done-state true and did nothing on the
|
|
106
|
+
must-not-regress list break?" Map each done-state item to the check that proves
|
|
107
|
+
it: a test of the behavior at its real boundary, a command, a query, a
|
|
108
|
+
measurement or a manual observation with its expected result. "Add tests" with
|
|
109
|
+
no named behavior is not verification. When a check needs a test, `hr-test-audit`
|
|
110
|
+
decides whether that test is worth writing.
|
|
111
|
+
|
|
112
|
+
## Plan layout
|
|
113
|
+
|
|
114
|
+
Lay the plan out as:
|
|
115
|
+
|
|
116
|
+
1. Goal: done means, out of scope, must not regress, real constraint.
|
|
117
|
+
2. Assumptions you made and open questions, if any.
|
|
118
|
+
3. The approach in two to four sentences, including what you chose not to
|
|
119
|
+
build and why.
|
|
120
|
+
4. Steps, each with outcome, owner, check and dependencies.
|
|
121
|
+
5. Cutover and rollback, when the system passes through an intermediate state.
|
|
122
|
+
6. Moving-part count.
|
|
123
|
+
|
|
124
|
+
Use a table or diagram only when it changes how someone carries out the plan,
|
|
125
|
+
such as a dependency graph that shows which slices run in parallel. Prose and
|
|
126
|
+
lists cover the rest.
|
|
127
|
+
|
|
128
|
+
## Critiquing a plan
|
|
129
|
+
|
|
130
|
+
Read the plan against the sections above, then report three things in order:
|
|
131
|
+
|
|
132
|
+
1. **Blocks starting**: missing goal slots, undecided branches, steps with no
|
|
133
|
+
checkable outcome or owner, cutovers with no rollback, feature work routed
|
|
134
|
+
through shared paths with no isolation, and PR splits that leave the same
|
|
135
|
+
coupling in place and cannot merge independently.
|
|
136
|
+
2. **Would make it smaller**: steps, layers, flags, migrations or parallel paths
|
|
137
|
+
that can go, each with the reason and the moving-part count before and after.
|
|
138
|
+
3. **Revised outline**: the plan rewritten in the layout above, short enough to
|
|
139
|
+
compare with the original side by side.
|
|
140
|
+
|
|
141
|
+
Quote the plan's own wording when pointing at a defect. When the plan is
|
|
142
|
+
already lean, say so and list only what blocks starting.
|