@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,66 @@
|
|
|
1
|
+
## Release batching
|
|
2
|
+
|
|
3
|
+
When preparing or publishing a release, follow the repository's versioning,
|
|
4
|
+
changelog, release-channel, authorization and verification requirements.
|
|
5
|
+
Where those requirements allow it, batch compatible changes instead of
|
|
6
|
+
publishing a release for every landed change. Landing and publishing are
|
|
7
|
+
separate actions; authorization for one does not authorize the other.
|
|
8
|
+
|
|
9
|
+
Record completed changes in the repository's pending-release record using its
|
|
10
|
+
existing convention or automation. Keep unfinished work in the task plan.
|
|
11
|
+
Before publishing, review approved backlog work and open pull requests with
|
|
12
|
+
their owners. Include compatible changes that are ready or intended for the
|
|
13
|
+
next release. Record the included changes, unfinished items and release cutoff
|
|
14
|
+
or readiness condition in the task plan. Backlog items do not authorize
|
|
15
|
+
implementation or landing. Do not hold a ready batch for unrelated, unapproved
|
|
16
|
+
or indefinite work.
|
|
17
|
+
|
|
18
|
+
Before any release or public announcement, audit every public-facing
|
|
19
|
+
material against the intended revision or artifact and current verified state:
|
|
20
|
+
the README and package READMEs, comparisons, performance metrics, graphics and
|
|
21
|
+
visuals (banners, charts, diagrams, screenshots and demo recordings, with their
|
|
22
|
+
alt text), relevant documentation, registry and marketplace listing text,
|
|
23
|
+
announcement plans and drafts, release notes and the release announcement.
|
|
24
|
+
|
|
25
|
+
Check each for old version numbers, figures and behavior. Regenerate or replace
|
|
26
|
+
a visual that shows them; where that is not possible, remove it or label the
|
|
27
|
+
version it shows. Record which materials were checked and the evidence for
|
|
28
|
+
claims in the task plan. Correct, add, remove or update content to resolve
|
|
29
|
+
outdated instructions, omissions, contradictions and unsupported claims.
|
|
30
|
+
|
|
31
|
+
Distinguish released, deployed and planned behavior; give performance claims
|
|
32
|
+
measurement conditions and evidence, and label unverified results. Preserve
|
|
33
|
+
historical results with their tested versions instead of presenting them as
|
|
34
|
+
current measurements.
|
|
35
|
+
|
|
36
|
+
Run the audit on every version bump, before the first publish step: the tag,
|
|
37
|
+
the registry or package publish, the GitHub release or any announcement. Land
|
|
38
|
+
each correction first. Landing the corrections is part of the audit; one made
|
|
39
|
+
after publishing means the audit did not finish. This applies to every public
|
|
40
|
+
repository, including ones created later.
|
|
41
|
+
|
|
42
|
+
Audit the pending changelog entries at the same point on every version bump,
|
|
43
|
+
public or private, and land the corrected changelog first. Check each entry
|
|
44
|
+
against the code that will ship: names, commands, counts and figures are
|
|
45
|
+
verified, not copied, and no transient number stays. Keep entries plain and no
|
|
46
|
+
longer than their reader needs. Merge entries that describe iterations of one
|
|
47
|
+
concern into one entry that states the final behavior; how a change evolved
|
|
48
|
+
stays in git. Keep whole any entry a user must act on (a breaking change, an
|
|
49
|
+
upgrade or a repair step); never merge it away. An urgent release does not narrow
|
|
50
|
+
this audit.
|
|
51
|
+
|
|
52
|
+
Recheck affected claims when the candidate or supporting evidence changes.
|
|
53
|
+
Before publishing, resolve unsupported or contradictory claims by correcting,
|
|
54
|
+
qualifying or removing them; record any remaining accuracy blocker. Do not
|
|
55
|
+
publish material with an unresolved accuracy blocker. Use existing evidence;
|
|
56
|
+
this audit does not authorize new benchmarks, probes, source changes outside
|
|
57
|
+
the task, or publication. Follow the owning repository's requirements for any
|
|
58
|
+
needed correction or measurement. An urgent release may narrow the audit to
|
|
59
|
+
its published materials and affected claims, but must still verify them.
|
|
60
|
+
|
|
61
|
+
An urgent security, regression or compatibility fix can ship without waiting
|
|
62
|
+
for unfinished backlog work. Every release's notes must cover all unreleased
|
|
63
|
+
changes included in its artifact, including an urgent release. Choose the
|
|
64
|
+
version for the combined changes according to the repository's scheme.
|
|
65
|
+
Publishing still requires its normal authorization and checks; this modifier
|
|
66
|
+
adds no permission to tag, publish or change release automation.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
## Squash landing
|
|
2
|
+
|
|
3
|
+
Prefer squash merges for pull requests unless the repository requires another
|
|
4
|
+
strategy or the user picks one. With the GitHub CLI that is
|
|
5
|
+
`gh pr merge <n> --squash`; a bare `gh pr merge` can create a merge commit.
|
|
6
|
+
Do not pass `--delete-branch` when other worktrees may hold the head branch;
|
|
7
|
+
delete the remote branch with `git push origin --delete <branch>` instead.
|
|
8
|
+
|
|
9
|
+
When you integrate a task branch locally instead of through a pull request,
|
|
10
|
+
land it as standard commits, never as a "Merge branch" commit: `git merge
|
|
11
|
+
--squash` then commit with a message in the repository's commit convention,
|
|
12
|
+
or a fast-forward when the base has not moved. After a pull request merges,
|
|
13
|
+
do not also squash the branch locally.
|
|
14
|
+
|
|
15
|
+
When a review bot reviews pull requests, merge only after its check finishes
|
|
16
|
+
and each finding is fixed or answered with a reason, threads resolved. Read
|
|
17
|
+
the review body too: findings outside the diff arrive there, not as threads.
|
|
18
|
+
|
|
19
|
+
Let an enabled automatic review start; do not comment to request one. When none
|
|
20
|
+
starts, check the integration's status instead of tagging the bot again. A bot's
|
|
21
|
+
config file in the repository does not show that it is enabled, and an existing
|
|
22
|
+
check on the head must report completion before you merge (for CodeRabbit, a
|
|
23
|
+
successful status whose description starts with "Review completed").
|
|
24
|
+
|
|
25
|
+
During integration, never switch, reset or remove the session's own checkout: no
|
|
26
|
+
`checkout` or `switch` to another ref, no `reset` or branch force-move, and no
|
|
27
|
+
`worktree remove` on it. Updating the default branch it has checked out
|
|
28
|
+
(`merge --squash`, or `merge --ff-only` to the fetched remote head after a remote merge)
|
|
29
|
+
under the fast-forward conditions in § Landing is expected; report other paths
|
|
30
|
+
to their owners.
|
|
31
|
+
|
|
32
|
+
After the merge, do not leave the session's own checkout on a landed branch.
|
|
33
|
+
Fetch first, then:
|
|
34
|
+
|
|
35
|
+
- When the content is fully in the base (a merged pull request whose remote
|
|
36
|
+
head is deleted, or an empty content diff after a squash) and the tree is
|
|
37
|
+
clean, move the checkout onto the fetched base: `git switch <base>` once the
|
|
38
|
+
local base is current, never merge, reset or stash. When another worktree
|
|
39
|
+
has the base checked out, detach at the fetched base (`git switch --detach
|
|
40
|
+
<remote>/<base>`) and create or switch to a branch before any further
|
|
41
|
+
commits.
|
|
42
|
+
- When an app manages the checkout, leave it for the app to retire. Report it;
|
|
43
|
+
it is not a blocker.
|
|
44
|
+
- When the tree is dirty or ownership is in doubt, stop: record the
|
|
45
|
+
merged commit, the checkout path and the owner wherever you track the task.
|
|
46
|
+
|
|
47
|
+
After each landing, update the task list and any active handoff with the
|
|
48
|
+
commit, the verification result and the remaining work, then retire the task
|
|
49
|
+
worktree and branch under § Cleanup. Stop earlier only when the user asks or
|
|
50
|
+
integration is blocked, and report the exact blocker.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
## Visuals in issues, discussions and comments
|
|
2
|
+
|
|
3
|
+
Use an image, chart or diagram only to show a spatial interface state, compare
|
|
4
|
+
before and after, show a trend across multiple measurements, or explain a flow
|
|
5
|
+
with branches. Otherwise use text, a table or a short code block. Most comments
|
|
6
|
+
are text. The exception is a qualified measured result. It lies outside its declared
|
|
7
|
+
variation criterion and the outputs are identical. An
|
|
8
|
+
issue, discussion or pull request that reports one opens with a headline chart
|
|
9
|
+
of it, so a reader sees the benefit before reading the body. A result with
|
|
10
|
+
one row that moved by less than 10% stays text and a table.
|
|
11
|
+
Draw it with the `hr-writing-pr` skill's `scripts/benefit-chart.py` when that skill
|
|
12
|
+
is installed, or with any charting tool. A comment that only adds data to an
|
|
13
|
+
existing thread stays text and a table.
|
|
14
|
+
|
|
15
|
+
Keep the exact numbers, steps or states in text beside the visual, and give the
|
|
16
|
+
image alt text, so a reader who cannot see it loses nothing. Many readers of an
|
|
17
|
+
issue or pull request are agents that cannot parse an image. Every value, label
|
|
18
|
+
and caveat in the visual also appears as body text, such as a table, a list or a
|
|
19
|
+
described state. The alt text says what the image shows, not only that it is a
|
|
20
|
+
chart. Label a mockup as an
|
|
21
|
+
illustration. Preview a chart or diagram design you have not used before with the
|
|
22
|
+
user before posting it.
|
|
23
|
+
|
|
24
|
+
The destination is usually public. Before posting, check the capture for private
|
|
25
|
+
paths, private project or repository names, local usernames, tokens and other
|
|
26
|
+
credentials, and follow any capture rule the repository or your host rules
|
|
27
|
+
state, such as a required theme.
|
|
28
|
+
|
|
29
|
+
Attach the file through the project host's supported route. On GitHub,
|
|
30
|
+
`gh issue create`, `gh issue edit`, `gh issue comment` and `gh pr comment`
|
|
31
|
+
accept `--attach '<file>#<alt text>'` in recent `gh` versions (check
|
|
32
|
+
`gh <command> --help`). In a browser form (the route for an issue form), use
|
|
33
|
+
the form's upload field when it renders. When it does not, add the file through
|
|
34
|
+
a text field's attach control and replace the default alt text `Image` that the
|
|
35
|
+
upload inserts. Do not commit the image to the repository or use a
|
|
36
|
+
third-party image host.
|
|
37
|
+
|
|
38
|
+
Where the CLI has no attach route (`gh discussion` has
|
|
39
|
+
no `--attach` flag) or refuses the upload, such as for lack of write access to
|
|
40
|
+
the repository, use a browser signed in to the user's GitHub account: the page's
|
|
41
|
+
upload field takes the file where `--attach` does not. Hand the user the exact
|
|
42
|
+
text and the file only when the host has no such browser or the upload fails
|
|
43
|
+
there, and do not post another way.
|
|
44
|
+
|
|
45
|
+
Posting to an external project still needs the user's direction.
|
package/setup.mjs
ADDED
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
// Numbered selection flow. Only the existing revision-checked config writer saves.
|
|
2
|
+
import { createInterface } from "node:readline";
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { parseArgs, stripVTControlCharacters } from "node:util";
|
|
8
|
+
import { configurationPath, modifierList } from "./composition.mjs";
|
|
9
|
+
import {
|
|
10
|
+
changeSelection,
|
|
11
|
+
QUESTION_MODIFIERS,
|
|
12
|
+
QUESTION_PRESETS,
|
|
13
|
+
configCommand,
|
|
14
|
+
readConfiguration,
|
|
15
|
+
selectionChanges,
|
|
16
|
+
selectionReport,
|
|
17
|
+
writeConfiguration,
|
|
18
|
+
} from "./config.mjs";
|
|
19
|
+
import { renderConfig } from "./config-view.mjs";
|
|
20
|
+
|
|
21
|
+
const HELP = `Choose guidance with numbered prompts:
|
|
22
|
+
house-rules setup [--config PATH] [--plain]
|
|
23
|
+
house-rules setup [--config PATH] --json
|
|
24
|
+
|
|
25
|
+
toggle N [N ...] or toggle NAME changes the listed choices.
|
|
26
|
+
questions default|plain|coded|cards chooses how questions are asked.
|
|
27
|
+
search TEXT, clear search, show selected, show all, help N
|
|
28
|
+
next, back, save (on Review), refresh (after a stale revision), cancel
|
|
29
|
+
|
|
30
|
+
Only an explicit save changes configuration. Output and hosts are separate.
|
|
31
|
+
For scripting, use house-rules config help and config set --apply --expect.
|
|
32
|
+
--json returns the existing config status report without prompting or writing.`;
|
|
33
|
+
|
|
34
|
+
// Terminal output must not execute control sequences from personal metadata or paths.
|
|
35
|
+
const safe = (value) => String(value).replace(/\p{Cc}/gu, " ");
|
|
36
|
+
const quote = (value) =>
|
|
37
|
+
process.platform === "win32"
|
|
38
|
+
? `'${value.replaceAll("'", "''")}'`
|
|
39
|
+
: `'${value.replaceAll("'", "'\\''")}'`;
|
|
40
|
+
|
|
41
|
+
// How this copy runs and how to apply a saved selection: the npm package has a
|
|
42
|
+
// `house-rules` command, a checkout from the installer is updated by running the installer again.
|
|
43
|
+
const entry = fileURLToPath(new URL("./compose.mjs", import.meta.url));
|
|
44
|
+
const parts = entry.split(/[\\/]/).map((part) => part.toLowerCase());
|
|
45
|
+
const packaged = parts.some(
|
|
46
|
+
(part, index) =>
|
|
47
|
+
part === "node_modules" && parts[index + 1] === "@bompus" && parts[index + 2] === "house-rules",
|
|
48
|
+
);
|
|
49
|
+
const cliCommand = () =>
|
|
50
|
+
packaged
|
|
51
|
+
? "house-rules"
|
|
52
|
+
: `${process.platform === "win32" ? "& " : ""}${quote(process.execPath)} ${quote(entry)}`;
|
|
53
|
+
|
|
54
|
+
const installerPath = () =>
|
|
55
|
+
join(dirname(entry), process.platform === "win32" ? "install.ps1" : "install.sh");
|
|
56
|
+
// The installer composes <config dir>/house-rules.json only.
|
|
57
|
+
const usesInstaller = (configPath) =>
|
|
58
|
+
!packaged && existsSync(installerPath()) && basename(configPath) === "house-rules.json";
|
|
59
|
+
|
|
60
|
+
function recomposeCommand(configPath) {
|
|
61
|
+
const configDir = dirname(configPath);
|
|
62
|
+
const checkout = dirname(entry);
|
|
63
|
+
const windows = process.platform === "win32";
|
|
64
|
+
const installer = installerPath();
|
|
65
|
+
if (!usesInstaller(configPath))
|
|
66
|
+
return `${cliCommand()} --config ${quote(configPath)} --out ${quote(join(configDir, "rules.md"))}`;
|
|
67
|
+
// The installer reads these variables, so a copy or config outside the defaults must name them.
|
|
68
|
+
const home = homedir();
|
|
69
|
+
const data =
|
|
70
|
+
process.env.XDG_DATA_HOME ?? (windows ? process.env.LOCALAPPDATA : join(home, ".local/share"));
|
|
71
|
+
const config = process.env.XDG_CONFIG_HOME ?? join(home, ".config");
|
|
72
|
+
const env = [
|
|
73
|
+
["HOUSE_RULES_DIR", checkout, join(data ?? "", "house-rules")],
|
|
74
|
+
["HOUSE_RULES_CONFIG_DIR", configDir, join(config, "house-rules")],
|
|
75
|
+
].filter(([, actual, normal]) => resolve(actual) !== resolve(normal));
|
|
76
|
+
return windows
|
|
77
|
+
? `${env.map(([name, value]) => `$env:${name}=${quote(value)}; `).join("")}& ${quote(installer)}`
|
|
78
|
+
: `${env.map(([name, value]) => `${name}=${quote(value)} `).join("")}sh ${quote(installer)}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function setupCommand(argv) {
|
|
82
|
+
let values;
|
|
83
|
+
try {
|
|
84
|
+
({ values } = parseArgs({
|
|
85
|
+
args: argv,
|
|
86
|
+
options: {
|
|
87
|
+
config: { type: "string" },
|
|
88
|
+
plain: { type: "boolean" },
|
|
89
|
+
json: { type: "boolean" },
|
|
90
|
+
help: { type: "boolean" },
|
|
91
|
+
},
|
|
92
|
+
}));
|
|
93
|
+
} catch (error) {
|
|
94
|
+
error.exitCode = 2;
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
if (values.help) return console.log(HELP);
|
|
98
|
+
if (values.json)
|
|
99
|
+
return configCommand([
|
|
100
|
+
"status",
|
|
101
|
+
"--json",
|
|
102
|
+
...(values.config ? ["--config", values.config] : []),
|
|
103
|
+
]);
|
|
104
|
+
let snapshot = readConfiguration(configurationPath(values.config));
|
|
105
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
106
|
+
console.error("setup requires a terminal for input and output; no selections saved.");
|
|
107
|
+
console.log(HELP);
|
|
108
|
+
process.exitCode = 2;
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const intent = new Map();
|
|
113
|
+
let questions,
|
|
114
|
+
stage = 0,
|
|
115
|
+
query = "",
|
|
116
|
+
selectedOnly = true,
|
|
117
|
+
stale = false;
|
|
118
|
+
let saveAttempted = false;
|
|
119
|
+
const options = () => {
|
|
120
|
+
const result = {};
|
|
121
|
+
if (questions !== undefined) result.questions = questions;
|
|
122
|
+
for (const { kind, name, enabled } of intent.values()) {
|
|
123
|
+
const key = `${enabled ? "enable" : "disable"}-${kind}`;
|
|
124
|
+
(result[key] ??= []).push(name);
|
|
125
|
+
}
|
|
126
|
+
return result;
|
|
127
|
+
};
|
|
128
|
+
const draft = () => {
|
|
129
|
+
try {
|
|
130
|
+
const next = changeSelection(snapshot.config, options(), snapshot.path);
|
|
131
|
+
return { next, report: selectionReport(next, snapshot.path) };
|
|
132
|
+
} catch (error) {
|
|
133
|
+
return { error };
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
// A valid projection supplies the catalog when stored selections need repair.
|
|
137
|
+
// It is never saved; only explicit user intent is applied to the original snapshot.
|
|
138
|
+
const known = new Set(modifierList().map((m) => m.name));
|
|
139
|
+
const catalog = () => {
|
|
140
|
+
const projection = structuredClone(snapshot.config);
|
|
141
|
+
projection.modifiers = (projection.modifiers ?? []).filter((name) => known.has(name));
|
|
142
|
+
if (
|
|
143
|
+
projection.modifiers.includes("question-cards") &&
|
|
144
|
+
!projection.modifiers.includes("coded-offers")
|
|
145
|
+
)
|
|
146
|
+
projection.modifiers.push("coded-offers");
|
|
147
|
+
if (projection.modifiers.includes("model-default-questions"))
|
|
148
|
+
projection.modifiers = projection.modifiers.filter(
|
|
149
|
+
(name) => !["coded-offers", "question-cards"].includes(name),
|
|
150
|
+
);
|
|
151
|
+
return selectionReport(projection, snapshot.path);
|
|
152
|
+
};
|
|
153
|
+
let baseline = catalog(); // Malformed layers, composition or legacy names still stop setup.
|
|
154
|
+
const rows = () => {
|
|
155
|
+
const current = draft();
|
|
156
|
+
const report = current.report ?? baseline;
|
|
157
|
+
const unavailable = (snapshot.config.modifiers ?? []).filter((name) => !known.has(name));
|
|
158
|
+
const modifiers = [
|
|
159
|
+
...report.modifiers.filter((m) => !QUESTION_MODIFIERS.includes(m.name)),
|
|
160
|
+
...unavailable.map((name) => ({
|
|
161
|
+
name,
|
|
162
|
+
enabled: true,
|
|
163
|
+
description: "Unavailable modifier; remove to repair the selection.",
|
|
164
|
+
})),
|
|
165
|
+
].map((row) => ({ ...row, kind: "modifier" }));
|
|
166
|
+
return [...modifiers, ...report.skills.map((row) => ({ ...row, kind: "skill" }))].map(
|
|
167
|
+
(row, index) => ({
|
|
168
|
+
...row,
|
|
169
|
+
number: index + 1,
|
|
170
|
+
enabled: intent.get(`${row.kind}:${row.name}`)?.enabled ?? row.enabled,
|
|
171
|
+
}),
|
|
172
|
+
);
|
|
173
|
+
};
|
|
174
|
+
const print = (text = "") => {
|
|
175
|
+
const width = Math.max(24, Math.min(process.stdout.columns || 80, 120));
|
|
176
|
+
for (const line of stripVTControlCharacters(String(text)).split("\n")) {
|
|
177
|
+
let rest = safe(line);
|
|
178
|
+
while (rest.length > width) {
|
|
179
|
+
let cut = rest.lastIndexOf(" ", width);
|
|
180
|
+
if (cut < 1) cut = width;
|
|
181
|
+
console.log(rest.slice(0, cut));
|
|
182
|
+
rest = rest.slice(cut).trimStart();
|
|
183
|
+
}
|
|
184
|
+
console.log(rest);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
const printCommand = (command) => {
|
|
188
|
+
if (/\p{Cc}/u.test(command)) {
|
|
189
|
+
print(
|
|
190
|
+
"The path contains control characters. Use config help with the original path arguments.",
|
|
191
|
+
);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
// Keep one logical line so terminal wrapping does not change copied shell syntax.
|
|
195
|
+
console.log(command);
|
|
196
|
+
};
|
|
197
|
+
const show = () => {
|
|
198
|
+
print(`Choose guidance / ${["Optional rules", "Skills", "Review"][stage]}`);
|
|
199
|
+
print(`Config: ${snapshot.path}`);
|
|
200
|
+
const current = draft();
|
|
201
|
+
if (current.error) print(`Selection needs repair: ${current.error.message}`);
|
|
202
|
+
if (stale)
|
|
203
|
+
print("Configuration changed. Use refresh for a new comparison, then review and save again.");
|
|
204
|
+
if (stage === 2 && current.report) {
|
|
205
|
+
print(
|
|
206
|
+
renderConfig({
|
|
207
|
+
command: "preview",
|
|
208
|
+
configPath: snapshot.path,
|
|
209
|
+
revision: snapshot.revision,
|
|
210
|
+
changes: selectionChanges(snapshot.config, current.next),
|
|
211
|
+
applied: null,
|
|
212
|
+
exists: snapshot.text !== null,
|
|
213
|
+
...current.report,
|
|
214
|
+
}),
|
|
215
|
+
);
|
|
216
|
+
const flags = Object.entries(options()).flatMap(([key, names]) =>
|
|
217
|
+
(Array.isArray(names) ? names : [names]).flatMap((name) => [`--${key}`, quote(name)]),
|
|
218
|
+
);
|
|
219
|
+
print(
|
|
220
|
+
"Reproduce with the existing config command (POSIX shell on Unix, PowerShell on Windows):",
|
|
221
|
+
);
|
|
222
|
+
printCommand(
|
|
223
|
+
`${cliCommand()} config set --config ${quote(snapshot.path)} ${flags.join(" ")} --apply --expect ${quote(snapshot.revision)}`,
|
|
224
|
+
);
|
|
225
|
+
print("Review personal rule overrides. Setup does not interpret or edit their prose.");
|
|
226
|
+
print(
|
|
227
|
+
"Only selections will be saved. Output is not regenerated and hosts are not connected by setup.",
|
|
228
|
+
);
|
|
229
|
+
print("save / back / cancel");
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
print("Core rules: always on. Optional rules are opt-in; skill selection follows your config.");
|
|
233
|
+
if (stage === 1) {
|
|
234
|
+
const skills = rows().filter((row) => row.kind === "skill");
|
|
235
|
+
print(
|
|
236
|
+
`${skills.filter((row) => row.enabled).length} of ${skills.length} available skills selected.`,
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
print(
|
|
240
|
+
`Question format: ${questions ?? baseline.questions}. Use questions default|plain|coded|cards.`,
|
|
241
|
+
);
|
|
242
|
+
const kind = stage === 0 ? "modifier" : "skill";
|
|
243
|
+
// With nothing selected yet, the selected-only view would be empty, so list every choice.
|
|
244
|
+
const nothingSelected = !rows().some((row) => row.kind === kind && row.enabled);
|
|
245
|
+
const visible = rows().filter(
|
|
246
|
+
(row) =>
|
|
247
|
+
row.kind === kind &&
|
|
248
|
+
(!selectedOnly || nothingSelected || row.enabled) &&
|
|
249
|
+
`${row.name} ${row.description}`.toLowerCase().includes(query.toLowerCase()),
|
|
250
|
+
);
|
|
251
|
+
for (const row of visible) {
|
|
252
|
+
print(
|
|
253
|
+
`${row.number}. [${row.enabled ? "on" : "off"}] ${row.name}${row.explicitOnly ? " (explicit only)" : ""}`,
|
|
254
|
+
);
|
|
255
|
+
print(` ${row.description.split(/(?<=\.)\s/)[0]}`);
|
|
256
|
+
}
|
|
257
|
+
if (!visible.length)
|
|
258
|
+
print(`No matches for ${JSON.stringify(query)}. Use clear search or show all.`);
|
|
259
|
+
if (selectedOnly && nothingSelected)
|
|
260
|
+
print("Nothing is selected yet, so every choice is listed. Use toggle N to select.");
|
|
261
|
+
else if (selectedOnly)
|
|
262
|
+
print("Showing current selections. Search for relevant additions or use show all.");
|
|
263
|
+
if (baseline.unavailable.length)
|
|
264
|
+
print(`Retained unavailable exclusions: ${baseline.unavailable.join(", ")}`);
|
|
265
|
+
print("toggle N [N ...] / search TEXT / help N / next / back / cancel");
|
|
266
|
+
};
|
|
267
|
+
const input = createInterface({ input: process.stdin, terminal: false });
|
|
268
|
+
const lines = input[Symbol.asyncIterator]();
|
|
269
|
+
let interrupted = false;
|
|
270
|
+
const interrupt = () => {
|
|
271
|
+
interrupted = true;
|
|
272
|
+
process.exitCode = 130;
|
|
273
|
+
input.close();
|
|
274
|
+
};
|
|
275
|
+
process.once("SIGINT", interrupt);
|
|
276
|
+
try {
|
|
277
|
+
print(
|
|
278
|
+
snapshot.text === null
|
|
279
|
+
? "New personal layer; no file has been created."
|
|
280
|
+
: "Existing selections loaded.",
|
|
281
|
+
);
|
|
282
|
+
print(
|
|
283
|
+
`CLI package version: ${JSON.parse(readFileSync(new URL("./package.json", import.meta.url), "utf8")).version}`,
|
|
284
|
+
);
|
|
285
|
+
show();
|
|
286
|
+
while (true) {
|
|
287
|
+
process.stdout.write("setup> ");
|
|
288
|
+
const line = await lines.next();
|
|
289
|
+
if (line.done || interrupted) {
|
|
290
|
+
print(
|
|
291
|
+
saveAttempted
|
|
292
|
+
? "Cancelled; inspect config status for the last save attempt."
|
|
293
|
+
: "Cancelled; no selections saved.",
|
|
294
|
+
);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
const command = line.value.trim();
|
|
298
|
+
try {
|
|
299
|
+
if (command === "cancel") {
|
|
300
|
+
print(
|
|
301
|
+
saveAttempted
|
|
302
|
+
? "Cancelled; inspect config status for the last save attempt."
|
|
303
|
+
: "Cancelled; no selections saved.",
|
|
304
|
+
);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (command === "next") {
|
|
308
|
+
if (stage === 1 && draft().error) throw draft().error;
|
|
309
|
+
stage = Math.min(2, stage + 1);
|
|
310
|
+
query = "";
|
|
311
|
+
selectedOnly = true;
|
|
312
|
+
} else if (command === "back") {
|
|
313
|
+
stage = Math.max(0, stage - 1);
|
|
314
|
+
query = "";
|
|
315
|
+
selectedOnly = true;
|
|
316
|
+
} else if (command.startsWith("search ")) {
|
|
317
|
+
query = command.slice(7);
|
|
318
|
+
selectedOnly = false;
|
|
319
|
+
} else if (command === "clear search") query = "";
|
|
320
|
+
else if (command === "show selected") selectedOnly = true;
|
|
321
|
+
else if (command === "show all") {
|
|
322
|
+
query = "";
|
|
323
|
+
selectedOnly = false;
|
|
324
|
+
} else if (command.startsWith("questions ")) {
|
|
325
|
+
const preset = command.slice(10);
|
|
326
|
+
if (!QUESTION_PRESETS.includes(preset))
|
|
327
|
+
throw new Error("Choose questions default|plain|coded|cards.");
|
|
328
|
+
questions = preset;
|
|
329
|
+
} else if (command.startsWith("toggle ") || command.startsWith("help ")) {
|
|
330
|
+
const help = command.startsWith("help ");
|
|
331
|
+
const names = command.slice(help ? 5 : 7).split(/\s+/);
|
|
332
|
+
const available = rows();
|
|
333
|
+
const chosen = names.map((name) => {
|
|
334
|
+
const matches = available.filter(
|
|
335
|
+
(row) => String(row.number) === name || row.name === name,
|
|
336
|
+
);
|
|
337
|
+
if (matches.length !== 1)
|
|
338
|
+
throw new Error(`Choose an unambiguous listed number or name: ${name}`);
|
|
339
|
+
return matches[0];
|
|
340
|
+
});
|
|
341
|
+
if (
|
|
342
|
+
!help &&
|
|
343
|
+
new Set(chosen.map((row) => `${row.kind}:${row.name}`)).size !== chosen.length
|
|
344
|
+
)
|
|
345
|
+
throw new Error("List each choice only once in a toggle command.");
|
|
346
|
+
if (
|
|
347
|
+
!help &&
|
|
348
|
+
chosen.some((row) => row.kind === "modifier" && !known.has(row.name) && !row.enabled)
|
|
349
|
+
)
|
|
350
|
+
throw new Error("An unavailable modifier can only be removed.");
|
|
351
|
+
for (const row of chosen) {
|
|
352
|
+
if (help) {
|
|
353
|
+
print(`${row.name}: ${row.description}`);
|
|
354
|
+
print(
|
|
355
|
+
`Source: ${row.origin ?? "shipped modifier"}${row.explicitOnly ? "; explicit invocation only" : ""}`,
|
|
356
|
+
);
|
|
357
|
+
if (row.kind === "skill")
|
|
358
|
+
print(
|
|
359
|
+
"Exclusion applies to this name in every layer, including a personal override.",
|
|
360
|
+
);
|
|
361
|
+
} else {
|
|
362
|
+
const enabled = !(intent.get(`${row.kind}:${row.name}`)?.enabled ?? row.enabled);
|
|
363
|
+
intent.set(`${row.kind}:${row.name}`, { kind: row.kind, name: row.name, enabled });
|
|
364
|
+
// A rule that expects a skill would otherwise warn on every compose.
|
|
365
|
+
for (const need of enabled ? (row.requires ?? "").split(",") : []) {
|
|
366
|
+
const skill = available.find((r) => r.kind === "skill" && r.name === need.trim());
|
|
367
|
+
// A skill listed in the same command keeps the toggle the user typed.
|
|
368
|
+
if (!skill || chosen.includes(skill)) continue;
|
|
369
|
+
if (intent.get(`skill:${skill.name}`)?.enabled ?? skill.enabled) continue;
|
|
370
|
+
intent.set(`skill:${skill.name}`, {
|
|
371
|
+
kind: "skill",
|
|
372
|
+
name: skill.name,
|
|
373
|
+
enabled: true,
|
|
374
|
+
});
|
|
375
|
+
print(`Also selected the ${skill.name} skill, which ${row.name} expects.`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
} else if (command === "refresh") {
|
|
380
|
+
if (!stale)
|
|
381
|
+
throw new Error(
|
|
382
|
+
"Refresh is available after a changed configuration; review the current draft first.",
|
|
383
|
+
);
|
|
384
|
+
snapshot = readConfiguration(snapshot.path);
|
|
385
|
+
baseline = catalog();
|
|
386
|
+
stale = false;
|
|
387
|
+
print(
|
|
388
|
+
"Fresh configuration loaded; retained selection intent is shown for review. Refresh did not write configuration.",
|
|
389
|
+
);
|
|
390
|
+
} else if (command === "save") {
|
|
391
|
+
if (stage !== 2) throw new Error("Use next to reach Review before saving.");
|
|
392
|
+
if (stale) throw new Error("Use refresh and review the new comparison before saving.");
|
|
393
|
+
const current = draft();
|
|
394
|
+
if (current.error) throw current.error;
|
|
395
|
+
if (!selectionChanges(snapshot.config, current.next).length) {
|
|
396
|
+
print("No selection changes; configuration was not written.");
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
saveAttempted = true;
|
|
400
|
+
try {
|
|
401
|
+
writeConfiguration(snapshot, current.next, snapshot.revision);
|
|
402
|
+
} catch (error) {
|
|
403
|
+
try {
|
|
404
|
+
if (readConfiguration(snapshot.path).revision !== snapshot.revision) stale = true;
|
|
405
|
+
} catch {
|
|
406
|
+
stale = true;
|
|
407
|
+
}
|
|
408
|
+
throw error;
|
|
409
|
+
}
|
|
410
|
+
let saved;
|
|
411
|
+
try {
|
|
412
|
+
saved = readConfiguration(snapshot.path);
|
|
413
|
+
if (JSON.stringify(saved.config) !== JSON.stringify(current.next))
|
|
414
|
+
throw new Error("Saved configuration changed; inspect config status.");
|
|
415
|
+
selectionReport(saved.config, saved.path);
|
|
416
|
+
} catch (error) {
|
|
417
|
+
stale = true;
|
|
418
|
+
throw new Error(
|
|
419
|
+
`Save completed but verification failed; inspect config status or refresh: ${error.message}`,
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
print(`Selections saved: ${saved.path}`);
|
|
423
|
+
print(
|
|
424
|
+
"Output was not regenerated and hosts were not connected by setup. To apply the selections, run:",
|
|
425
|
+
);
|
|
426
|
+
printCommand(recomposeCommand(saved.path));
|
|
427
|
+
if (!usesInstaller(saved.path))
|
|
428
|
+
print(
|
|
429
|
+
"This rebuilds the rules only; add --skills-out with a new or empty directory to rebuild the skills too.",
|
|
430
|
+
);
|
|
431
|
+
return;
|
|
432
|
+
} else
|
|
433
|
+
throw new Error(
|
|
434
|
+
"Unknown command. Use toggle, questions, search, help, next, back, save or cancel.",
|
|
435
|
+
);
|
|
436
|
+
} catch (error) {
|
|
437
|
+
print(`Cannot continue: ${error.message}`);
|
|
438
|
+
}
|
|
439
|
+
show();
|
|
440
|
+
}
|
|
441
|
+
} finally {
|
|
442
|
+
process.removeListener("SIGINT", interrupt);
|
|
443
|
+
input.close();
|
|
444
|
+
}
|
|
445
|
+
}
|
|
@@ -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.
|