@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
package/config.mjs
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
// Selection management for compose.mjs config. JSON is the only settings store.
|
|
2
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
+
import {
|
|
4
|
+
closeSync,
|
|
5
|
+
existsSync,
|
|
6
|
+
fsyncSync,
|
|
7
|
+
lstatSync,
|
|
8
|
+
mkdirSync,
|
|
9
|
+
openSync,
|
|
10
|
+
readFileSync,
|
|
11
|
+
realpathSync,
|
|
12
|
+
renameSync,
|
|
13
|
+
statSync,
|
|
14
|
+
unlinkSync,
|
|
15
|
+
writeFileSync,
|
|
16
|
+
} from "node:fs";
|
|
17
|
+
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
18
|
+
import { parseArgs } from "node:util";
|
|
19
|
+
import { fileURLToPath } from "node:url";
|
|
20
|
+
const SHIPPED_SKILLS = join(dirname(fileURLToPath(import.meta.url)), "skills");
|
|
21
|
+
import {
|
|
22
|
+
composeConfiguration,
|
|
23
|
+
configurationPath,
|
|
24
|
+
modifierList,
|
|
25
|
+
parseFragment,
|
|
26
|
+
skillSources,
|
|
27
|
+
splitSections,
|
|
28
|
+
} from "./composition.mjs";
|
|
29
|
+
import { renderConfig, renderHelp } from "./config-view.mjs";
|
|
30
|
+
|
|
31
|
+
const COMMANDS = ["catalog", "status", "validate", "preview", "set"];
|
|
32
|
+
const TOGGLES = ["enable-modifier", "disable-modifier", "enable-skill", "disable-skill"];
|
|
33
|
+
const revisionOf = (text) =>
|
|
34
|
+
text === null ? "missing" : createHash("sha256").update(text).digest("hex");
|
|
35
|
+
const object = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
36
|
+
|
|
37
|
+
function stringList(value, key) {
|
|
38
|
+
if (value === undefined) return;
|
|
39
|
+
if (!Array.isArray(value) || value.some((v) => typeof v !== "string" || !v.trim()))
|
|
40
|
+
throw new Error(`${key} must be an array of non-empty strings`);
|
|
41
|
+
if (new Set(value).size !== value.length) throw new Error(`${key} contains duplicates`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function validateStructure(config) {
|
|
45
|
+
if (!object(config)) throw new Error("configuration must be a JSON object");
|
|
46
|
+
for (const key of ["modifiers", "layers"]) stringList(config[key], key);
|
|
47
|
+
if (config.skills !== undefined && !object(config.skills))
|
|
48
|
+
throw new Error("skills must be an object");
|
|
49
|
+
for (const key of ["include", "exclude", "independent"])
|
|
50
|
+
stringList(config.skills?.[key], `skills.${key}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function validateSelection(config) {
|
|
54
|
+
validateStructure(config);
|
|
55
|
+
const known = new Set(modifierList().map((m) => m.name));
|
|
56
|
+
for (const name of config.modifiers ?? []) {
|
|
57
|
+
if (!known.has(name)) throw new Error(`unknown modifier "${name}" (see config catalog)`);
|
|
58
|
+
}
|
|
59
|
+
if (config.modifiers?.includes("question-cards") && !config.modifiers.includes("coded-offers"))
|
|
60
|
+
throw new Error(
|
|
61
|
+
"question-cards requires coded-offers; use --questions cards or disable question-cards",
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function readConfiguration(path) {
|
|
66
|
+
// Preserve the caller's config directory: relative layers use the same base as composition.
|
|
67
|
+
path = resolve(path);
|
|
68
|
+
let text = null;
|
|
69
|
+
try {
|
|
70
|
+
if (!lstatSync(path).isFile())
|
|
71
|
+
throw new Error("config must be a regular file, not a symlink or directory");
|
|
72
|
+
text = readFileSync(path, "utf8");
|
|
73
|
+
} catch (e) {
|
|
74
|
+
if (e.code !== "ENOENT") throw e;
|
|
75
|
+
}
|
|
76
|
+
let config = {};
|
|
77
|
+
try {
|
|
78
|
+
if (text !== null) config = JSON.parse(text.replace(/^\uFEFF/, ""));
|
|
79
|
+
} catch {
|
|
80
|
+
throw new Error("invalid JSON in configuration");
|
|
81
|
+
}
|
|
82
|
+
validateStructure(config);
|
|
83
|
+
return { path, text, revision: revisionOf(text), config };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function selectionReport(config, path) {
|
|
87
|
+
validateSelection(config);
|
|
88
|
+
const result = composeConfiguration(config, dirname(path));
|
|
89
|
+
const allSkills = skillSources(
|
|
90
|
+
{ ...config, skills: { ...config.skills, include: undefined, exclude: [] } },
|
|
91
|
+
dirname(path),
|
|
92
|
+
);
|
|
93
|
+
const selected = new Set(config.modifiers ?? []);
|
|
94
|
+
const modifiers = modifierList().map((m) => ({ ...m, enabled: selected.has(m.name) }));
|
|
95
|
+
const skills = [...allSkills].map(([name, source]) => ({
|
|
96
|
+
name,
|
|
97
|
+
source,
|
|
98
|
+
origin: source === join(SHIPPED_SKILLS, name) ? "shipped" : relative(dirname(path), source),
|
|
99
|
+
enabled: result.skills.has(name),
|
|
100
|
+
explicitOnly:
|
|
101
|
+
parseFragment(readFileSync(join(source, "SKILL.md"), "utf8"), source).meta[
|
|
102
|
+
"disable-model-invocation"
|
|
103
|
+
] === "true",
|
|
104
|
+
description:
|
|
105
|
+
parseFragment(readFileSync(join(source, "SKILL.md"), "utf8"), source).meta.description ?? "",
|
|
106
|
+
}));
|
|
107
|
+
const unavailable = (config.skills?.exclude ?? []).filter((name) => !allSkills.has(name));
|
|
108
|
+
const rules = splitSections(result.rules).sections.map((s) => ({
|
|
109
|
+
heading: s.heading,
|
|
110
|
+
source: "core",
|
|
111
|
+
}));
|
|
112
|
+
const origins = new Map();
|
|
113
|
+
for (const f of result.fragments) {
|
|
114
|
+
for (const section of splitSections(f.body).sections) origins.set(section.heading, f.source);
|
|
115
|
+
}
|
|
116
|
+
for (const rule of rules) {
|
|
117
|
+
rule.source = origins.get(rule.heading) ?? "core";
|
|
118
|
+
rule.origin =
|
|
119
|
+
rule.source === "core" || rule.source.startsWith("modifier ")
|
|
120
|
+
? rule.source
|
|
121
|
+
: relative(dirname(path), rule.source);
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
modifiers,
|
|
125
|
+
skills,
|
|
126
|
+
rules,
|
|
127
|
+
unavailable,
|
|
128
|
+
layers: (config.layers ?? ["."]).map((layer) => resolve(dirname(path), layer)),
|
|
129
|
+
fragments: result.fragments.map((f) => ({ source: f.source, ...f.meta })),
|
|
130
|
+
questions: selected.has("question-cards")
|
|
131
|
+
? "cards"
|
|
132
|
+
: selected.has("coded-offers")
|
|
133
|
+
? "coded"
|
|
134
|
+
: "plain",
|
|
135
|
+
warnings: result.warnings,
|
|
136
|
+
composedRules: result.rules,
|
|
137
|
+
hostLoading: "unverified",
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function changeSelection(config, options, path) {
|
|
142
|
+
const next = structuredClone(config);
|
|
143
|
+
const modifiers = new Set(next.modifiers ?? []);
|
|
144
|
+
const excluded = new Set(next.skills?.exclude ?? []);
|
|
145
|
+
const included = next.skills?.include === undefined ? null : new Set(next.skills.include);
|
|
146
|
+
const knownModifiers = new Set(modifierList().map((m) => m.name));
|
|
147
|
+
// Use discovery even for excluded skills; preserve unrelated existing exclusions.
|
|
148
|
+
const knownSkills = new Set(
|
|
149
|
+
skillSources(
|
|
150
|
+
{ ...next, skills: { ...next.skills, include: undefined, exclude: [] } },
|
|
151
|
+
dirname(path),
|
|
152
|
+
).keys(),
|
|
153
|
+
);
|
|
154
|
+
for (const [kind, known, choices] of [
|
|
155
|
+
["modifier", knownModifiers, modifiers],
|
|
156
|
+
["skill", knownSkills, excluded],
|
|
157
|
+
]) {
|
|
158
|
+
const enable = options[`enable-${kind}`] ?? [];
|
|
159
|
+
const disable = options[`disable-${kind}`] ?? [];
|
|
160
|
+
for (const name of [...enable, ...disable]) {
|
|
161
|
+
if (!known.has(name) && !(kind === "modifier" && disable.includes(name) && choices.has(name)))
|
|
162
|
+
throw new Error(`unknown ${kind} "${name}" (see config catalog)`);
|
|
163
|
+
if (enable.includes(name) && disable.includes(name))
|
|
164
|
+
throw new Error(`cannot enable and disable "${name}" together`);
|
|
165
|
+
if (
|
|
166
|
+
kind === "modifier" &&
|
|
167
|
+
options.questions !== undefined &&
|
|
168
|
+
["coded-offers", "question-cards"].includes(name)
|
|
169
|
+
)
|
|
170
|
+
throw new Error("use --questions or explicit question modifiers, not both");
|
|
171
|
+
}
|
|
172
|
+
for (const name of enable) {
|
|
173
|
+
if (kind === "modifier") choices.add(name);
|
|
174
|
+
else {
|
|
175
|
+
choices.delete(name);
|
|
176
|
+
if (included && existsSync(join(SHIPPED_SKILLS, name, "SKILL.md"))) included.add(name);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
for (const name of disable) {
|
|
180
|
+
if (kind === "modifier") choices.delete(name);
|
|
181
|
+
else choices.add(name);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (options.questions !== undefined) {
|
|
185
|
+
if (!["plain", "coded", "cards"].includes(options.questions))
|
|
186
|
+
throw new Error("--questions must be plain, coded or cards");
|
|
187
|
+
if (options.questions === "plain") modifiers.delete("coded-offers");
|
|
188
|
+
else modifiers.add("coded-offers");
|
|
189
|
+
if (options.questions === "cards") modifiers.add("question-cards");
|
|
190
|
+
else modifiers.delete("question-cards");
|
|
191
|
+
}
|
|
192
|
+
if (JSON.stringify([...modifiers]) !== JSON.stringify(next.modifiers ?? []))
|
|
193
|
+
next.modifiers = [...modifiers];
|
|
194
|
+
if (JSON.stringify([...excluded]) !== JSON.stringify(next.skills?.exclude ?? []))
|
|
195
|
+
next.skills = { ...next.skills, exclude: [...excluded] };
|
|
196
|
+
if (included) next.skills.include = [...included];
|
|
197
|
+
selectionReport(next, path); // Composition and legacy-name errors precede every write.
|
|
198
|
+
return next;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Cooperating CLI writers share an exclusive lock; expected revision guards stale previews.
|
|
202
|
+
// Manual editors do not share this lock. Recheck before rename, but they can still race that rename.
|
|
203
|
+
export function writeConfiguration(snapshot, next, expected) {
|
|
204
|
+
if (!expected) throw new Error("--apply requires --expect <revision> from status or a preview");
|
|
205
|
+
selectionReport(next, snapshot.path);
|
|
206
|
+
mkdirSync(dirname(snapshot.path), { recursive: true });
|
|
207
|
+
// Lock aliases of the same parent together without changing relative layer discovery.
|
|
208
|
+
const lock = `${join(realpathSync(dirname(snapshot.path)), basename(snapshot.path))}.lock`;
|
|
209
|
+
let lockFd;
|
|
210
|
+
try {
|
|
211
|
+
lockFd = openSync(lock, "wx", 0o600);
|
|
212
|
+
} catch (e) {
|
|
213
|
+
if (e.code === "EEXIST")
|
|
214
|
+
throw new Error(
|
|
215
|
+
`configuration is locked: ${lock}; inspect the owner before removing an abandoned lock`,
|
|
216
|
+
);
|
|
217
|
+
throw e;
|
|
218
|
+
}
|
|
219
|
+
const temporary = `${snapshot.path}.${randomUUID()}.tmp`;
|
|
220
|
+
try {
|
|
221
|
+
writeFileSync(lockFd, JSON.stringify({ pid: process.pid }));
|
|
222
|
+
const current = readConfiguration(snapshot.path);
|
|
223
|
+
if (current.revision !== expected || current.revision !== snapshot.revision)
|
|
224
|
+
throw new Error("configuration changed since preview; read status and preview again");
|
|
225
|
+
if (current.text !== null && JSON.stringify(current.config) === JSON.stringify(next))
|
|
226
|
+
return { changed: false, revision: current.revision };
|
|
227
|
+
const text = `${JSON.stringify(next, null, 2)}\n`;
|
|
228
|
+
const fd = openSync(
|
|
229
|
+
temporary,
|
|
230
|
+
"wx",
|
|
231
|
+
current.text === null ? 0o600 : statSync(current.path).mode & 0o777,
|
|
232
|
+
);
|
|
233
|
+
try {
|
|
234
|
+
writeFileSync(fd, text);
|
|
235
|
+
fsyncSync(fd);
|
|
236
|
+
} finally {
|
|
237
|
+
closeSync(fd);
|
|
238
|
+
}
|
|
239
|
+
if (readConfiguration(snapshot.path).revision !== current.revision)
|
|
240
|
+
throw new Error("configuration changed during validation; preview again");
|
|
241
|
+
renameSync(temporary, snapshot.path);
|
|
242
|
+
return { changed: true, revision: revisionOf(text) };
|
|
243
|
+
} finally {
|
|
244
|
+
try {
|
|
245
|
+
if (existsSync(temporary)) unlinkSync(temporary);
|
|
246
|
+
} finally {
|
|
247
|
+
try {
|
|
248
|
+
closeSync(lockFd);
|
|
249
|
+
} finally {
|
|
250
|
+
unlinkSync(lock);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function selectionChanges(beforeConfig, afterConfig) {
|
|
257
|
+
return [
|
|
258
|
+
{ key: "modifiers", before: beforeConfig.modifiers ?? [], after: afterConfig.modifiers ?? [] },
|
|
259
|
+
{
|
|
260
|
+
key: "skills.include",
|
|
261
|
+
before: beforeConfig.skills?.include ?? [],
|
|
262
|
+
after: afterConfig.skills?.include ?? [],
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
key: "skills.exclude",
|
|
266
|
+
before: beforeConfig.skills?.exclude ?? [],
|
|
267
|
+
after: afterConfig.skills?.exclude ?? [],
|
|
268
|
+
},
|
|
269
|
+
].filter(({ before, after }) => JSON.stringify(before) !== JSON.stringify(after));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function configCommand(argv) {
|
|
273
|
+
const command = argv[0] ?? "help";
|
|
274
|
+
if (command === "help" || command === "--help") return console.log(renderHelp());
|
|
275
|
+
if (!COMMANDS.includes(command))
|
|
276
|
+
throw new Error(`unknown config command "${command}" (see config help)`);
|
|
277
|
+
const { values } = parseArgs({
|
|
278
|
+
args: argv.slice(1),
|
|
279
|
+
options: {
|
|
280
|
+
config: { type: "string" },
|
|
281
|
+
help: { type: "boolean" },
|
|
282
|
+
json: { type: "boolean" },
|
|
283
|
+
rules: { type: "boolean" },
|
|
284
|
+
questions: { type: "string" },
|
|
285
|
+
apply: { type: "boolean" },
|
|
286
|
+
expect: { type: "string" },
|
|
287
|
+
...Object.fromEntries(TOGGLES.map((key) => [key, { type: "string", multiple: true }])),
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
if (values.help) return console.log(renderHelp());
|
|
291
|
+
const changing = Boolean(
|
|
292
|
+
values.questions !== undefined || TOGGLES.some((key) => values[key]?.length),
|
|
293
|
+
);
|
|
294
|
+
if (
|
|
295
|
+
(changing || values.apply || values.expect !== undefined) &&
|
|
296
|
+
!["set", "preview"].includes(command)
|
|
297
|
+
)
|
|
298
|
+
throw new Error("selection flags belong to config set or preview");
|
|
299
|
+
if (values.apply && command !== "set") throw new Error("--apply belongs to config set");
|
|
300
|
+
if (values.expect !== undefined && !values.apply) throw new Error("--expect requires --apply");
|
|
301
|
+
if (values.rules && values.json) throw new Error("choose --rules or --json, not both");
|
|
302
|
+
if (values.rules && command !== "preview") throw new Error("--rules belongs to config preview");
|
|
303
|
+
const snapshot = readConfiguration(configurationPath(values.config));
|
|
304
|
+
const next = changing ? changeSelection(snapshot.config, values, snapshot.path) : snapshot.config;
|
|
305
|
+
const report = selectionReport(next, snapshot.path);
|
|
306
|
+
const changes = selectionChanges(snapshot.config, next);
|
|
307
|
+
const applied = values.apply ? writeConfiguration(snapshot, next, values.expect) : null;
|
|
308
|
+
const output = {
|
|
309
|
+
command,
|
|
310
|
+
configPath: snapshot.path,
|
|
311
|
+
exists: values.apply || snapshot.text !== null,
|
|
312
|
+
revision: applied?.revision ?? snapshot.revision,
|
|
313
|
+
changes,
|
|
314
|
+
applied,
|
|
315
|
+
...report,
|
|
316
|
+
};
|
|
317
|
+
if (values.rules) return process.stdout.write(report.composedRules);
|
|
318
|
+
if (values.json) console.log(JSON.stringify(output, null, 2));
|
|
319
|
+
else console.log(renderConfig(output));
|
|
320
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Configure house rules
|
|
2
|
+
|
|
3
|
+
Use `node compose.mjs config help` to see the commands. Bun supports the same
|
|
4
|
+
interface. The CLI manages the existing `house-rules.json`; it keeps personal
|
|
5
|
+
layers, independent skill names and unrelated settings.
|
|
6
|
+
|
|
7
|
+
## Guided selection
|
|
8
|
+
|
|
9
|
+
Run `node compose.mjs setup --config <config-path>` in a terminal. Bun supports
|
|
10
|
+
the same command. The numbered flow uses ordinary line input; `--plain` selects
|
|
11
|
+
the same flow. Both input and output must be terminals. Redirected input or
|
|
12
|
+
output prints scripting instructions and exits 2 without saving. Use
|
|
13
|
+
`setup --json` for the existing read-only `config status --json` report.
|
|
14
|
+
|
|
15
|
+
Modifiers and skills have stable row numbers while searching. Use `toggle N`
|
|
16
|
+
or `toggle NAME`, with several choices separated by spaces. `search TEXT`,
|
|
17
|
+
`clear search`, `show selected` and `show all` change visibility only.
|
|
18
|
+
`help N` shows the full description, source and invocation policy.
|
|
19
|
+
Excluding a skill name excludes its personal override as well as the shipped
|
|
20
|
+
copy. Exclusions without a current source remain in the configuration.
|
|
21
|
+
|
|
22
|
+
Use `questions plain|coded|cards` for question preferences; these modifiers
|
|
23
|
+
are handled together rather than as individual toggles. `next` moves from
|
|
24
|
+
modifiers to skills, then review. `back` keeps the draft. Only `save` on review
|
|
25
|
+
writes configuration. Before a save attempt, `cancel`, end of input or Ctrl-C
|
|
26
|
+
leaves it unchanged.
|
|
27
|
+
A new config with no selection changes is not created.
|
|
28
|
+
|
|
29
|
+
Review includes personal rule origins and an equivalent `config set` command.
|
|
30
|
+
The command uses POSIX quoting on Unix and PowerShell quoting on Windows.
|
|
31
|
+
Personal prose can override the selected preferences; setup does not interpret
|
|
32
|
+
or edit it. Existing composition errors still block saving. Unknown stored
|
|
33
|
+
modifiers can be removed, and a question preset can repair its prerequisite.
|
|
34
|
+
Malformed configuration, legacy skill names and invalid layers require repair
|
|
35
|
+
outside setup.
|
|
36
|
+
|
|
37
|
+
If another writer changes configuration before save, setup preserves the
|
|
38
|
+
draft intent and refuses the stale write. Use `refresh` to compare that intent
|
|
39
|
+
with the fresh configuration, then explicitly save again. It never saves a
|
|
40
|
+
fresh comparison automatically. Saving preserves custom keys and their meaning;
|
|
41
|
+
the writer formats JSON, so existing whitespace is not preserved.
|
|
42
|
+
|
|
43
|
+
If verification fails after saving, configuration may already have changed.
|
|
44
|
+
Inspect `config status` or use `refresh` before another save. Cancelling ends
|
|
45
|
+
the session without undoing an earlier save attempt.
|
|
46
|
+
|
|
47
|
+
Setup saves selections only. Recompose output separately, then connect the
|
|
48
|
+
chosen hosts. It does not install, update, repair or remove managed output.
|
|
49
|
+
|
|
50
|
+
## Browse and inspect
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
node compose.mjs config catalog --config <config-path>
|
|
54
|
+
node compose.mjs config status --config <config-path>
|
|
55
|
+
node compose.mjs config validate --config <config-path>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`setup` initially shows current selections. Search for relevant additions or
|
|
59
|
+
use `show all` to browse the complete catalog. Review and saving do not require
|
|
60
|
+
a catalog-wide decision.
|
|
61
|
+
|
|
62
|
+
`catalog` shows modifiers, skills and rule sections. `status` shows enabled
|
|
63
|
+
selections and effective personal sources. Both use aligned tables, with color
|
|
64
|
+
in a terminal and stacked rows on narrow screens. `NO_COLOR` disables color.
|
|
65
|
+
Use `--json` for structured output without terminal styling.
|
|
66
|
+
|
|
67
|
+
A missing config is treated as an empty selection without creating files.
|
|
68
|
+
Modifiers are opt-in. Fresh installers copy the sample config with eight skills
|
|
69
|
+
and no modifiers. A missing config or an existing config without `skills.include`
|
|
70
|
+
keeps the full shipped set. The default config path is the same as the composer
|
|
71
|
+
uses. Pass `--config` to choose another.
|
|
72
|
+
|
|
73
|
+
`skills.include` is an optional list of shipped skill names. An empty list selects
|
|
74
|
+
no shipped skills; later shipped additions stay off unless listed. Unknown names,
|
|
75
|
+
malformed lists and duplicates are errors. Personal layer skills still load and
|
|
76
|
+
override shipped skills, even when the shipped name is absent from the list.
|
|
77
|
+
`skills.exclude` takes precedence across every layer. Existing independent names,
|
|
78
|
+
layers and custom settings retain their behavior. The catalog remains complete
|
|
79
|
+
so you can discover additions without enabling them.
|
|
80
|
+
|
|
81
|
+
Status distinguishes configured question preferences from effective rule
|
|
82
|
+
sources. Personal rules can override those preferences. Neither status nor
|
|
83
|
+
validation proves what a running agent has loaded.
|
|
84
|
+
|
|
85
|
+
## Preview and save
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
node compose.mjs config set --config <config-path> \
|
|
89
|
+
--enable-modifier swarmail --disable-skill hr-read-reddit --questions coded
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`set` previews by default. It shows the selection changes, composition summary,
|
|
93
|
+
personal rule overrides and current revision. Repeat the exact command with
|
|
94
|
+
`--apply --expect <revision>` to save the reviewed change. Read the new revision
|
|
95
|
+
from the result before another write.
|
|
96
|
+
|
|
97
|
+
Toggle flags can repeat. Enable or disable modifiers with `--enable-modifier`
|
|
98
|
+
and `--disable-modifier`; skills use `--enable-skill` and `--disable-skill`.
|
|
99
|
+
Enabling a skill removes its exclusion. When `skills.include` exists, enabling
|
|
100
|
+
a shipped skill also adds it to that list. Enabling a personal-only skill does
|
|
101
|
+
not add a shipped name. Disabling adds an exclusion, retaining the include list,
|
|
102
|
+
unrelated exclusions and custom settings. Configs without an include list keep
|
|
103
|
+
their existing selection behavior.
|
|
104
|
+
|
|
105
|
+
| Question preset | Enabled offer modifiers |
|
|
106
|
+
| ------------------- | ----------------------------------- |
|
|
107
|
+
| `--questions plain` | Neither offer modifier |
|
|
108
|
+
| `--questions coded` | `coded-offers` |
|
|
109
|
+
| `--questions cards` | `coded-offers` and `question-cards` |
|
|
110
|
+
|
|
111
|
+
Question presets preserve other modifiers. Choose a preset or explicit offer
|
|
112
|
+
modifier toggles, rather than combining both in one command. Cards still depend
|
|
113
|
+
on the agent host, provider and model. Review any personal Offers override
|
|
114
|
+
before saving; the CLI does not rewrite personal prose.
|
|
115
|
+
|
|
116
|
+
To repair invalid modifier selections, preview changes that produce a valid
|
|
117
|
+
selection. `--questions` can repair the offer dependency; `--disable-modifier`
|
|
118
|
+
can remove an unknown modifier already stored in the config. Malformed config
|
|
119
|
+
structure, unknown enables and unknown disables absent from the config remain
|
|
120
|
+
errors.
|
|
121
|
+
|
|
122
|
+
`preview` accepts the same selection flags without saving. Add `--rules` to
|
|
123
|
+
print the composed rules, or `--json` for the full report. Unknown choices,
|
|
124
|
+
contradictory flags, malformed config and composition errors stop the command
|
|
125
|
+
before it writes configuration. Older skill names need the
|
|
126
|
+
[skill name upgrade](skill-names.md) before composition.
|
|
127
|
+
|
|
128
|
+
## Write safeguards
|
|
129
|
+
|
|
130
|
+
Saving requires the revision reported by status or preview. A stale revision
|
|
131
|
+
fails without overwriting a newer config. Cooperating CLI writers share an
|
|
132
|
+
exclusive `<config-path>.lock` file; a competing writer fails immediately.
|
|
133
|
+
The CLI writes a sibling temporary file, then replaces the config atomically.
|
|
134
|
+
Repeated requests leave an unchanged config's bytes and modification time alone.
|
|
135
|
+
A missing config is created only by explicit apply.
|
|
136
|
+
|
|
137
|
+
Manual editors do not share the lock. Avoid editing the config while a CLI
|
|
138
|
+
write runs. The CLI checks again before replacement, but cannot exclude a
|
|
139
|
+
manual write racing that final replacement. It refuses symlink config files.
|
|
140
|
+
If an interrupted writer leaves a lock, inspect its recorded process ID and
|
|
141
|
+
confirm that writer has ended before removing the lock. The CLI never removes
|
|
142
|
+
another writer's lock automatically.
|
|
143
|
+
|
|
144
|
+
## Compose and connect
|
|
145
|
+
|
|
146
|
+
Saving changes only configuration. Recompose with the existing composer or run
|
|
147
|
+
the installer again. The installer retains previous composed skill directories;
|
|
148
|
+
manual `--skills-out` still requires an empty or absent directory.
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
node compose.mjs --config <config-path> --out <rules-path> \
|
|
152
|
+
--skills-out <new-skills-directory>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Connecting or copying that output to agent hosts remains a separate setup
|
|
156
|
+
step. Use `hr-house-rules-setup` for the guided workflow. Custom rule prose
|
|
157
|
+
still belongs in layer fragments; the protected completion sections stay
|
|
158
|
+
required.
|
|
159
|
+
|
|
160
|
+
## Reference output
|
|
161
|
+
|
|
162
|
+
`--out rules.md` writes selected procedure references to a sibling
|
|
163
|
+
`house-rules-references/` directory. Keep that directory beside the rules file
|
|
164
|
+
when copying it to a host. References do not depend on `skills.include` or
|
|
165
|
+
`skills.exclude`. Core Landing and Reporting always contribute their references; enabled modifiers
|
|
166
|
+
contribute theirs. Reporting is also bundled with `hr-what-next` for standalone
|
|
167
|
+
skill use. Excluding that skill does not remove the core reference. A personal replacement suppresses the resource it replaces.
|
|
168
|
+
|
|
169
|
+
The `multi-agent` modifier exports its complete agent-work procedure when enabled,
|
|
170
|
+
including review follow-ups and cleanup qualification. It remains independent
|
|
171
|
+
of selected skills; enabling reference output does not enable the modifier.
|
|
172
|
+
|
|
173
|
+
Use `--references-out <directory>` with `--out` to choose another destination;
|
|
174
|
+
links are rendered relative to the rules file. Keep both locations available.
|
|
175
|
+
Stdout and ordinary composition/configuration API calls retain full inline
|
|
176
|
+
procedures, so existing preview and copy-only workflows need no bundle.
|
|
177
|
+
|
|
178
|
+
A hash manifest permits repeat exports and updates to previously generated
|
|
179
|
+
files. Modified, missing or unrecognized files in that directory stop generation
|
|
180
|
+
before output writes. Reconcile them explicitly; generation never removes old
|
|
181
|
+
procedure files or unrelated files. Output directories must not overlap.
|
|
182
|
+
|
|
183
|
+
Core rule frontmatter may name one `reference` or a comma-separated `references`
|
|
184
|
+
list relative to `rules/core.md`. Use only one form; list paths and section headings
|
|
185
|
+
must be distinct and non-empty. Each target must be a regular file within the
|
|
186
|
+
source layer, with one level-2 heading matching the core section holding its
|
|
187
|
+
mandatory link. Default composition expands that section;
|
|
188
|
+
linked output retains its safety boundaries and reading trigger. Modifier
|
|
189
|
+
references use the same validation and output inventory.
|
|
190
|
+
|
|
191
|
+
For low-level `compose(coreText, fragments)`, use `loadFragments` for personal
|
|
192
|
+
layers so reference paths retain their source ownership. Directly parsed shipped
|
|
193
|
+
modifiers are recognized only when their label and body match the shipped file.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# npm release setup
|
|
2
|
+
|
|
3
|
+
The package name is `@bompus/house-rules`; the executable is `house-rules`.
|
|
4
|
+
It has no runtime dependencies or install hooks. Installing the package does
|
|
5
|
+
not install skills into an agent host. Composition writes the requested rules
|
|
6
|
+
file, its `house-rules-references/` directory when the rules include reference
|
|
7
|
+
procedures, and any `--skills-out` directory.
|
|
8
|
+
|
|
9
|
+
Keep the `package.json` version, changelog version and `v<version>` tag
|
|
10
|
+
identical.
|
|
11
|
+
|
|
12
|
+
## First publication
|
|
13
|
+
|
|
14
|
+
This one-time setup applies to 0.11.0, the first published version. The first publication requires an npm account that owns the `bompus` scope.
|
|
15
|
+
An earlier publication of another package does not establish current login or
|
|
16
|
+
scope access. Authenticate using npm's web login and complete its 2FA approval.
|
|
17
|
+
Do not put credentials into the repository or workflow.
|
|
18
|
+
|
|
19
|
+
On the selected release commit, run `npm run check:package -- --out <directory>`.
|
|
20
|
+
Publish that verified `house-rules.tgz` with `npm publish <tarball> --access public`.
|
|
21
|
+
Then configure its trusted publisher on npm for GitHub user `bompus`, repository
|
|
22
|
+
`house-rules`, workflow filename `npm-publish.yml`. Enable direct `npm publish`.
|
|
23
|
+
The default staged-publication permission alone does not permit this workflow.
|
|
24
|
+
|
|
25
|
+
The first GitHub release workflow checks the published tarball integrity and
|
|
26
|
+
skips an identical existing version. Different bytes at the same version fail
|
|
27
|
+
the workflow. Subsequent versions publish automatically after trusted publisher
|
|
28
|
+
setup. Do not create another release solely to test authentication.
|
|
29
|
+
|
|
30
|
+
## Subsequent releases
|
|
31
|
+
|
|
32
|
+
Publishing a stable GitHub release triggers `.github/workflows/npm-publish.yml`.
|
|
33
|
+
It verifies that the tagged commit belongs to `main`, checks the package version
|
|
34
|
+
and dated changelog entry, and checks the packed CLI and skill resources before
|
|
35
|
+
publishing the same tarball. Prereleases are excluded.
|
|
36
|
+
|
|
37
|
+
The workflow uses a GitHub-hosted runner and npm's OIDC trusted publishing.
|
|
38
|
+
It stores no npm token. npm requires Node 22.14 or newer and npm 11.5.1 or newer;
|
|
39
|
+
the workflow uses Node 26. npm automatically supplies provenance for this public
|
|
40
|
+
repository and package. See the [official trusted publishing documentation](https://docs.npmjs.com/trusted-publishers/).
|
|
41
|
+
|
|
42
|
+
CI runs `npm run check:package -- --bun`. It installs the actual local tarball
|
|
43
|
+
into isolated scratch with lifecycle scripts disabled, exercises the CLI under
|
|
44
|
+
Node and Bun, and compares composed skills and resources with the source.
|
|
45
|
+
Scratch honors `HOUSE_RULES_TEST_TMP` and is removed when the check exits normally.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Upgrading skill names
|
|
2
|
+
|
|
3
|
+
Every skill shipped by house-rules uses the `hr-` prefix. For example,
|
|
4
|
+
`handoff` becomes `hr-handoff`. Rule headings, modifier names and the
|
|
5
|
+
`house-rules` command stay the same. Skills from other sources keep their names.
|
|
6
|
+
|
|
7
|
+
## Before updating
|
|
8
|
+
|
|
9
|
+
1. In `house-rules.json`, replace each shipped skill name in `skills.exclude`
|
|
10
|
+
with its `hr-` name.
|
|
11
|
+
2. For each personal override of a shipped skill, rename its directory and
|
|
12
|
+
its frontmatter `name:` to the matching `hr-` name.
|
|
13
|
+
3. Update your skill invocations, rules, links and host settings to use the
|
|
14
|
+
new names.
|
|
15
|
+
|
|
16
|
+
Composition refuses unresolved old exclusion names, ambiguous old personal
|
|
17
|
+
folders and legacy frontmatter names in prefixed overrides before writing output. The error names the replacement. Fix the
|
|
18
|
+
config or layer, then compose again. The installer keeps the previous composed
|
|
19
|
+
skills until composition succeeds.
|
|
20
|
+
|
|
21
|
+
A personal override named `hr-handoff` replaces the shipped `hr-handoff`.
|
|
22
|
+
If your generic `handoff` is a separate skill you intend to keep alongside it,
|
|
23
|
+
record that decision in your config:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"skills": {
|
|
28
|
+
"independent": ["handoff"],
|
|
29
|
+
"exclude": ["hr-read-reddit"]
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`skills.independent` lets the listed personal skills keep names that match old
|
|
35
|
+
house-rules names. It does not create an alias or replace the prefixed skill.
|
|
36
|
+
`skills.exclude` uses the new names for shipped skills. An old name listed in
|
|
37
|
+
`skills.independent` refers to the independent personal skill and can be excluded
|
|
38
|
+
without excluding its prefixed counterpart. Unrelated personal
|
|
39
|
+
names need no entry in `skills.independent`.
|
|
40
|
+
|
|
41
|
+
## Connect hosts after composing
|
|
42
|
+
|
|
43
|
+
If a host reads the whole composed directory, point it at the new composed set.
|
|
44
|
+
If it has individual skill links or copied folders, replace its old house-rules
|
|
45
|
+
entries with the matching prefixed entries. Preserve unrelated skills.
|
|
46
|
+
Restart or reload the host and check that it discovers the new names.
|
|
47
|
+
|
|
48
|
+
The composer and installer do not edit host files or remove old host entries.
|
|
49
|
+
Do not keep both the old and new house-rules copies active. Historical skill
|
|
50
|
+
invocations need the new names; there are no compatibility aliases.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Example project
|
|
2
|
+
|
|
3
|
+
## Session
|
|
4
|
+
|
|
5
|
+
Before your first reply in a session, read § End of every reply and § Offers in
|
|
6
|
+
your user-level house rules in full. They apply here unchanged; this file only
|
|
7
|
+
adds to them and never removes the offer.
|
|
8
|
+
|
|
9
|
+
## Landing
|
|
10
|
+
|
|
11
|
+
This repository lands by direct push to `main` after `npm test` passes; that
|
|
12
|
+
is the landing path § Landing asks the repository to name. Never force-push
|
|
13
|
+
`main`.
|
|
14
|
+
|
|
15
|
+
## Commands
|
|
16
|
+
|
|
17
|
+
- Install: `npm ci`
|
|
18
|
+
- Test: `npm test`
|