aboutsummaryrefslogtreecommitdiff
Commit message (Collapse)AuthorAgeFilesLines
* docs(todo): track pull-before-project ordering rule for protocols.orgCraig Jennings2026-05-131-0/+19
|
* chore: updating todo.org fileCraig Jennings2026-05-131-0/+4
|
* fix(respond-to-cj-comments): keep top/second-level tasks as DONECraig Jennings2026-05-131-2/+7
| | | | Only third-level and deeper tasks get the dated-heading rewrite. Top and second-level stay task-shaped so they remain visible in the agenda.
* feat(rules): add todo-entry format (terse heading, optional body)Craig Jennings2026-05-131-0/+58
|
* feat(rules): cross-project boundary rule + cj-comments preflightCraig Jennings2026-05-132-0/+98
| | | | | | | | | | | | New `claude-rules/cross-project.md` codifies the per-project `.ai/` scope boundary. Stop-and-ask when a request targets another project's files, inline numbered options, handoff-file convention when the user opts to do it from here. `/respond-to-cj-comments` gains a section-0 preflight (boundary check before reading the target file) and a section-7 handoff step (writes the carry-forward file in the target project's `inbox/` when the boundary crossing was approved).
* feat(respond-to-cj-comments): replace /tmp summary with STALLED-task patternCraig Jennings2026-05-131-4/+11
| | | | | | | | When the run leaves something needing Craig's input (a blocker, an open question, a draft awaiting sign-off), the skill now files a STALLED task in todo.org under the relevant parent — phrased so Craig can answer inline with a cj: comment — rather than writing a long summary to /tmp and opening it in emacsclient. The STALLED task lives next to the work, surfaces in his agenda, and gets resolved inline. The chat summary still gets written as the FYI recap; the STALLED task is the durable home for action items. Also documents the DONE→dated-heading convention: when a cj:-handled TODO completes, rewrite the heading itself into a "** YYYY-MM-DD Day @ HH:MM:SS -ZZZZ <desc>" event-log entry rather than advancing to DONE. This is Craig's standing convention for todo.org and his daily-prep docs — finished tasks turn into a dated log automatically. The description block at the top of the file got a small rewrite to match: STALLED-task language replaces the /tmp-summary phrasing, and the DONE→dated-heading rule is mentioned in the org-mode-handling clause.
* fix(commits): correct PR-review notification channel IDCraig Jennings2026-05-131-1/+1
| | | | Step 8 of the publish flow referenced C0AM2MWHCJU as the channel for the Slack PR-author ping. That ID is actually a 3-person mpdm (Craig/Vrezh/Kostya), not the intended 4-person PR-review group. Corrected to C0B1B0NH2N5 (Craig/Eric/Vrezh/Kostya) and added a clarifying note that the 3-person mpdm is not an "#ai" channel despite older references calling it that.
* chore: Claude Code settings — keep bypassPermissions default; ↵Craig Jennings2026-05-121-2/+6
| | | | | | notification/UI toggles Working copy had drifted permissions.defaultMode to "auto"; reset to "bypassPermissions" to match HEAD. Other tweaks: awaySummaryEnabled false, terminalProgressBarEnabled false, inputNeededNotifEnabled true, skipAutoPermissionPrompt true, skillListingBudgetFraction moved to top.
* chore(ai): session record for start-work pre-work + triage-intake fixesCraig Jennings2026-05-111-0/+71
| | | | Added a Phase 0 pre-work block to /start-work (fetch-and-reconcile against the base branch, plus a source-code check that the problem the ticket describes still exists). Reviewed and fixed triage-intake.org: Phase 3 now marks all unread INBOX mail across gmail/dmail/cmail, the Overview no longer contradicts the calendar source, and the Linear sweep got a non-GitHub-remote guard. Both shipped in earlier commits this session; this record archives the work.
* chore(ai): sync triage-intake workflow from claude-templatesCraig Jennings2026-05-113-2/+124
| | | | New on-demand triage-intake workflow. It scans every inbox source (the three mail accounts, Slack, Linear, open PRs, both calendars, recent todo.org edits), surfaces what moved, runs the Linear Dev-Review sweep, and marks all unread INBOX mail plus every touched Slack conversation read. Also registered in INDEX.org, and the stale triage-intake reference dropped from wrap-it-up.org.
* docs(todo): plan daily-prep delegation to triage-intakeCraig Jennings2026-05-111-0/+11
|
* docs(start-work): add Phase 0 pre-work (reconcile + source check)Craig Jennings2026-05-111-2/+51
| | | | | | | | Phase 0 was a single eligibility gate. Two gaps: the skill cut new branches from whatever HEAD happened to be, and trusted the ticket's premise without checking the tree. Two new sub-sections fix both before any state change. 0.2 fetches and fast-forwards the base branch, surfacing dirty or diverged state instead of auto-resolving. 0.3 reads the source, not git log, to confirm the problem the ticket describes still exists, with three dispositions for "the problem isn't there" findings: close, dig harder, or proceed with reduced confidence flagged at the Justify gate. Two matching anti-patterns added: skipping the reconcile, and taking the ticket's word for it.
* chore(ai): session record for todo-cleanup --archive-done + clean-todo workCraig Jennings2026-05-111-0/+75
| | | | todo-cleanup.el gained --archive-done (level-2 DONE/CANCELLED subtrees move to "Resolved"), with a 13-test ERT suite wired into make test. The wrap-up flow now runs both the hygiene pass and --archive-done, and clean-todo is the on-demand entry point.
* docs(workflows): add clean-todo workflowCraig Jennings2026-05-112-0/+60
| | | | clean-todo is the manual entry point for tidying todo.org: it runs the hygiene pass, then --archive-done (relocate completed level-2 subtrees into "Resolved"), then summarizes what changed and leaves the diff uncommitted for review. The wrap-up flow already does both passes at session end; clean-todo runs them on demand. It's listed in INDEX.org under the usual trigger phrases.
* docs(workflows): run todo-cleanup --archive-done in wrap-upCraig Jennings2026-05-111-3/+21
| | | | The wrap-up flow already runs the hygiene pass on todo.org; it now also runs --archive-done, which relocates completed level-2 subtrees from "Open Work" to "Resolved". Both passes are idempotent and skip cleanly when the file lacks the named sections, and any moves land in the wrap-up commit's diff for review before push.
* docs(todo): mark --archive-done task doneCraig Jennings2026-05-111-11/+13
|
* feat(todo-cleanup): add --archive-done mode with ERT test suiteCraig Jennings2026-05-114-30/+575
| | | | | | | | --archive-done moves every level-2 subtree whose TODO state is DONE or CANCELLED out of the "Open Work" section into the "Resolved" section of the same org file, subtree intact. Sections match on a unique level-1 heading containing "Open Work" (case-insensitive) and one containing "Resolved"; a missing or ambiguous section skips the file with a message rather than crashing. Only direct level-2 children move. A DONE entry nested under an open parent stays put. Opt-in, never run by default, doesn't also run the hygiene passes; --check previews without writing. The CLI dispatch moved into tc-main behind a guard so the new ERT suite can require the file without firing it. Hygiene mode is unchanged. 13 ERT cases (the repo's first elisp tests) cover the move and the stay-put cases, EOF with no final newline, missing or ambiguous sections, lowercase headings, idempotency, and --check. tests/fixtures/todo-sample.org is the synthetic sample, and the Makefile test target now runs the ERT suites alongside pytest.
* docs(workflows): require every worktree leftover to be resolved at wrapCraig Jennings2026-05-111-4/+35
| | | | Replace the old "intentional carryover" default in wrap-it-up.org. End every session with an empty `git status`: classify each leftover as a runtime artifact, a forgotten change, or pre-existing dirt, apply a concrete resolution unless the user explicitly defers, and log any deferral in the valediction so the next session knows it was a choice, not a miss.
* docs(todo): add --archive-done task for todo-cleanup.elCraig Jennings2026-05-111-0/+11
|
* chore(ai): sync template updates from claude-templatesCraig Jennings2026-05-115-20/+85
| | | | Pull in the latest maildir-flag-manager.py and cross-agent-comms doc updates from the claude-templates source.
* docs(todo): add make-audit and claude-templates-fold tasksCraig Jennings2026-05-111-0/+39
| | | | I added two [#B] entries. The first folds the standalone claude-templates repo into rulesets/claude-templates/ via subtree merge, bridging the path change with a transitional symlink while every project picks up the updated startup.org. The second adds a make audit target that diffs each .ai/-using project against the canonical template source. Both are on hold for now — the entries just record the plan.
* docs(todo): nest entries under a "Rulesets Open Work" headingCraig Jennings2026-05-111-194/+199
| | | | Add a single top-level "Rulesets Open Work" heading and demote every entry one level so the file has one root section.
* docs(commits): add Step 0 pre-flight reconcile and pre-push checkCraig Jennings2026-05-101-1/+51
| | | | | | | | The publish flow had no fetch step before commit, PR creation, or push. Long sessions or multi-machine work could land local commits on a stale base, producing non-fast-forward push failures that you have to unwind under publish-step pressure. Step 0 fetches all remotes and checks the current branch against its upstream before Step 1's code review. If the branch is behind, the rule branches on tree state and divergence shape: clean fast-forward, surface dirty-tree behind, or surface a true divergence and ask before rebasing or merging. The Step 0 wording covers the new-branch case (no upstream → skip the divergence check, the first push sets it). The Pre-push reconcile bullet in Merge Strategy handles the smaller window between Step 0 and the actual push. Reviewing and drafting can take several minutes; another machine or teammate can push during that window. One more fetch immediately before push is cheaper than recovering after a failed push.
* fix(gmail): Improve safe_filename to handle .. prefixesCraig Jennings2026-05-081-2/+11
| | | | | | Strip leading ".." sequences instead of stripping all leading dots, so dotfiles like ".gitignore" are preserved while still preventing directory traversal via "../foo" style names. ```
* docs(workflows): Update DeepSat repo path in daily-prepCraig Jennings2026-05-081-1/+1
|
* feat: Add cmail IMAP action script and test suiteCraig Jennings2026-05-084-0/+1786
| | | | | | | | Add cmail-action.py for IMAP triage operations against Proton Mail Bridge (list-unread, read, mark-read, star, unstar, trash, send, folders) mirroring the Gmail MCP workflow. Also add comprehensive tests for cmail-action, gmail-fetch-attachments, and maildir-flag-manager scripts.
* feat(scripts): add readability tool + pre-warm textstat in depsCraig Jennings2026-05-082-0/+116
| | | | | | | | Adds scripts/readability — a Python tool that prints standard readability metrics (Flesch Reading Ease, Flesch-Kincaid Grade, Gunning Fog, SMOG, Coleman-Liau, ARI, Dale-Chall, Linsear-Write) for one input file or as a side-by-side comparison of two. Self-contained via PEP 723 inline metadata: textstat is declared as the script's only dependency, and the `#!/usr/bin/env -S uv run --quiet --script` shebang lets uv resolve it on each invocation. The Makefile `deps` target now also pre-warms textstat in uv's cache so the first interactive run is fast.
* feat(claude-rules): add interaction.md — no popup menus for choicesCraig Jennings2026-05-081-0/+31
| | | | | | Codify the rule that AskUserQuestion's popup menu obscures the chat text the user needs to read to make the choice. Choice prompts go inline as numbered options instead, with a "pick a number" prompt at the end. Applies to all three approval gates in commits.md (commit message, PR description, PR review reply).
* chore(ai): correct stale ~/projects/work/ path references in workflows and ↵Craig Jennings2026-05-084-5/+5
| | | | scripts
* feat(wrap-it-up): add Linear Dev-Review sweep stepCraig Jennings2026-05-081-2/+23
| | | | Sweep Dev-Review tickets assigned to Craig before the wrap-up commit. Any whose linked PR has merged gets proposed for Done (chores, refactors, test backfills, dead-code removal) or PM Acceptance (real fixes or features users can verify). Tickets stuck in Dev Review after the PR merges hide real in-progress work. The step is idempotent and skips when Linear is not in use.
* feat(make): add bootstrap target for fresh-machine setupCraig Jennings2026-05-081-0/+9
| | | | Bootstrap chains install, install-hooks, and install-mcp into one command. The three targets stay split so routine re-symlinking stays cheap (no GPG pinentry, no network), but bootstrap gives the fresh-install case one entry point. The gap surfaced on a fresh machine where doctor flagged 4 hook warnings and 8 MCP failures.
* docs(todo): add follow-ups for category-3 rules and language-rule auditCraig Jennings2026-05-071-0/+19
| | | | | | | Two open decisions from the 2026-05-07 rulesets centralization pass: - Category-3 rule copies in the deepsat tree (`coding-rulesets/` and `orchestration_dashboard_mvp/`) — read each and decide between leave, sync, or symlink. - Language-specific rule files (python-testing, typescript-testing, elisp-testing, elisp) duplicated across multiple project mirrors — audit and possibly canonicalize.
* fix(commits): anchor .ai/ detection to repo root with :/ pathspecCraig Jennings2026-05-071-2/+4
| | | | | | | | The detection command for personal vs. general voice mode used `git ls-files .ai/`, which returns no matches when run from a subdirectory of the repo, even when `.ai/` is tracked at the root. That silently misclassified projects as personal-voice when they should have been general-voice. Switching to `git ls-files :/.ai/` anchors the search to the repo root via the `:/` pathspec, so the command works correctly from any cwd. I hit this myself today: ran the check from `claude-rules/` inside the rulesets repo, got an empty result, and applied `/voice personal` to a commit that should have used `/voice` (general mode).
* docs(commits): add bundled-review shape, voice mode gating, drop humanizerCraig Jennings2026-05-071-16/+101
| | | | | | | | I rewrote the PR review subflow into three explicit shapes. Shape 1 is a single review that bundles the verdict, the summary body, and zero or more inline pins into one `gh api .../reviews` call. Shape 2 is an issue-thread comment with no verdict. Shape 3 is a reply on an existing inline thread. The single-review path replaces the prior pattern where a Request-Changes verdict with line-specific findings needed separate `gh pr review` + `gh pr comment` calls. That fragmented the Slack notification and the review history. I migrated all `humanizer` references to `/voice personal`. The voice skill replaced humanizer, so the old name was dead. I dropped the two lineage mentions of "humanizer's signs of AI writing" since they pointed at a skill that no longer exists. I added a Voice mode and approval gate preamble at the top of Step 2. The mode is decided by whether `.ai/` is tracked in the repo. Gitignored or absent means personal-voice with the full approval gate. Tracked means general-voice and the gate is skipped, since the personal-only patterns (first-person rewrite, contractions, semicolon swap) don't fit a shared rules file. I also updated the Single-skill gate wrap-up paragraph at the end of Step 2 to reference both modes.
* feat: voice skill, make doctor, MCP token bundlingCraig Jennings2026-05-072-151/+310
| | | | | | | | Voice skill consolidates the prose-quality passes (humanizer plus universal good-writing rules plus personal-style) into one /voice invocation with two modes. General mode for arbitrary writing. Personal mode for commit messages, PR descriptions, and PR review comments. The standalone humanizer skill is retired. make doctor verifies ~/.claude/ live state matches the repo and settings.json. Eight checks covering skills, rules, hooks, settings.json hook references, plugins, MCP server registrations, and dangling symlinks. The MCP install pipeline now bundles Google Docs OAuth tokens alongside the GCP keys, so a fresh machine boots fully connected after make install-mcp without requiring a manual OAuth dance per profile.
* chore(skills): remove humanizer (superseded by voice)Craig Jennings2026-05-074-476/+212
| | | | | | | | I deleted humanizer/SKILL.md now that all three callers (commits.md, respond-to-cj-comments.md, start-work.md) invoke /voice instead. The 25 humanizer patterns live on as patterns 1-25 in voice/SKILL.md. Same source (Wikipedia's Signs of AI writing), same prose, same examples — just renumbered alongside the universal good-writing additions and the personal-only patterns. I also updated .ai/notes.org and .ai/workflows/wrap-it-up.org to reference /voice personal instead of the old humanizer + manual-passes flow. The wrap-it-up change landed upstream in claude-templates first so it survives the next startup rsync. todo.org gets the matching update: the voice TODO is marked DONE with a "Built and shipped" timestamp, the publish-mode terminology is renamed to personal-mode throughout, the V1 scope checklist is ticked, the open questions are resolved with the answers we landed on during implementation, and the migration section records the delete-not-alias decision.
* chore: migrate humanizer callers to /voice personalCraig Jennings2026-05-073-51/+30
| | | | | | | | | | I switched the three publish subflows in commits.md (commit messages, PR descriptions, PR review comments) from "run humanizer; apply five personal-style passes in order" to a single "run /voice personal" invocation. The new skill walks 39 patterns in one editorial review and absorbs the five passes wholesale, plus four more personal-style additions (felt-experience cut, fragment-in-prose rewrite, terse cut, public-artifact scope flag) and six universal good-writing patterns. The numbered steps in each subflow collapse from 5 to 4 (commits) and 9 to 8 (PRs) since the dedicated personal-style step folds into the voice invocation. The Multi-pass gate paragraph becomes a Single-skill gate. The mid-flow "all the passes" prompt now means re-run the full 39-pattern walk in personal mode rather than reapplying six discrete steps. I also updated respond-to-cj-comments.md to invoke /voice personal for public writing and /voice general for the lighter pass on internal notes when wanted, and updated start-work.md's Phase 7 summary to match. The humanizer skill itself stays in place for now. The next commit removes it.
* feat(skills): add voice skill (humanizer + universal + personal passes)Craig Jennings2026-05-071-0/+635
| | | | | | | | I built voice as a single skill that walks 39 numbered prose-editing patterns. The first 25 patterns come straight from the existing humanizer skill (Wikipedia's Signs of AI Writing). Patterns 26-31 add universal good-writing rules from Strunk & White, Orwell, the Plain English Campaign, and Garner — long-word → short-word, active-over-passive, comma splices, cliché flag, jargon-fragment-in-prose rewrite, and corporate-speak nominalizations. Patterns 32-39 are tagged "personal only" and cover first-person rewrite, semicolon swaps, contractions, sentence-split on conjunctions, felt-experience cut, sentence-fragment-in-prose rewrite, terse cut for rhetorical padding, and a public-artifact scope flag. Two modes determine which patterns get walked. General mode (default) walks 1-31 and is the right fit for research notes, philosophy and history essays, emails, README prose, journal entries, anything that isn't a commit, PR, or PR review comment. Personal mode walks all 39 patterns and is invoked explicitly by the publish flow in commits.md (and similar callers) so first-person and contraction enforcement don't leak into academic or literary writing where they don't belong. The follow-up commits migrate callers to /voice and remove the standalone humanizer skill.
* feat(make): add doctor target for ~/.claude drift detectionCraig Jennings2026-05-073-1/+264
| | | | | | | | | | | | =make doctor= scans =~/.claude/= and reports drift against the repo + settings.json. Read-only diagnostic. Eight checks cover skills, rules, default hooks, claude config, settings.json hook references, enabledPlugins, MCP server registrations, and dangling symlinks. Each line prints =ok= / =WARN= / =FAIL= with a final summary. Exit 1 on any FAIL. A sweep last night found =~/.claude/hooks/= didn't exist on this machine even though =settings.json= referenced a PreCompact hook there. Compaction would have silently failed to invoke it. doctor catches that kind of drift in one command instead of relying on a manual look. The MCP drift check reads =~/.claude.json= directly rather than parsing =claude mcp list=. The CLI has no JSON output and runs a per-server health probe (~10s). The JSON file is the user-scope source of truth for registrations and parses in well under a second. I verified by injecting four drift scenarios — removed hook symlink, removed skill symlink, moved-aside plugin data dir, unregistered MCP server. Each produced the expected =FAIL= line and exit 1. After restoring state, doctor came back clean (33 ok). Bundling four other improvement TODOs from the same sweep — =mcp/README.org=, =make uninstall-mcp= and =mcp/install.py --check=, a README.org section for the MCP install pipeline, and a token-rotation helper for =@a-bonus/google-docs-mcp= OAuth refresh. Plus a stale-bullet note on the existing =make remove= TODO (the bridge symlink it references was removed earlier).
* chore(mcp): mark install.py executableCraig Jennings2026-05-061-0/+0
| | | | The shebang was already there but the file mode was 644. The Makefile invokes via "python3 mcp/install.py" so it worked anyway, but the mode now matches the shebang.
* chore(ai): sync template updates from claude-templatesCraig Jennings2026-05-065-5/+12
| | | | | | I added a "Shell aliases (=ls= → =exa=)" note to protocols.org so future sessions know to use \ls when capturing ls output programmatically. exa prints nothing to non-TTY pipes, so the symptom looks like an empty directory. I hit this earlier in the session when a sweep came back blank for a directory I knew was populated. I also fixed three stale ~/projects/career/ examples in cross-agent-comms/ docs that didn't get updated when career was renamed to work, and the daily-prep.org path leak from last session (~/code/deepsat/... → ~/projects/work/deepsat/code/...). The authoritative edits live in claude-templates. These rulesets snapshots landed via the standard rsync from upstream.
* chore(make): remove unused claude-rules bridge symlinkCraig Jennings2026-05-061-18/+0
| | | | | | The bridge at ~/.claude/skills/claude-rules existed so a SKILL.md could resolve a relative path like ../claude-rules/testing.md from the install layout. No SKILL.md actually uses that pattern. Every reference I grepped — across debug, add-tests, and pairwise-tests — names the rule file by bare filename in prose, which doesn't go through any link. The symlink was defensive scaffolding for a use case that didn't land. The four rule files keep loading via ~/.claude/rules/ unchanged. Claude Code's skill harness was silently ignoring the bridge anyway because the target directory has no SKILL.md, so no behavior moves except that make install stops creating the dead entry. If a future SKILL.md wants deep-linking, the bridge can come back deliberately.
* feat(mcp): add user-scope MCP install pipelineCraig Jennings2026-05-065-1/+230
| | | | | | | | | | I needed a single source of truth for MCP server registration so a fresh machine boots with the full set instead of being rebuilt by hand. install.py decrypts mcp/secrets.env.gpg, expands ${VAR} placeholders in mcp/servers.json, and runs claude mcp add --scope user for anything not already registered. Idempotent. The encrypted bundle carries six values: the Google client id and secret, the Figma API key, the GCP OAuth keys JSON (base64), and the two @a-bonus/google-docs-mcp token caches (personal and work, base64). install.py writes the keys file and the two token files to the paths each package reads at startup, all mode 600. Bundling the Google Docs tokens lets a new machine connect google-docs-personal and google-docs-work without the interactive OAuth flow. Without the cached token, the package falls back to a browser-redirect flow that Claude Code's stdio MCP loader can't drive, so it shows "Failed to connect" until the user runs the npx command manually. Make target: install-mcp. Plaintext secrets and the decrypted keys file are gitignored.
* chore(ai): initialize project notes and Claude tooling surfacesCraig Jennings2026-05-0670-0/+11660
| | | | Replace the seed notes.org with project-specific context (layout, install modes, task tracker location, recent inflection point). Bring in the synced template surfaces (protocols, workflows, scripts, references, retrospectives, someday-maybe) as tracked content for this content/documentation project.
* chore(claude): bump skillListingBudgetFraction to 5%Craig Jennings2026-05-061-2/+3
| | | | Default 1% of context maps to ~8K chars (the floor). The current skill listing totals ~10K chars and overflows on small-context models, triggering the truncation warning. Bumping to 5% leaves headroom even with the model-invocable skills' full descriptions present, and the file-based skill descriptions are now <=1000 chars each.
* chore(commands): mark user-invoked commands disable-model-invocationCraig Jennings2026-05-0617-1/+30
| | | | Add disable-model-invocation: true to the user-triggered slash commands so the harness drops their descriptions from the model's preloaded skill listing while keeping /<name> routing intact. Skills meant for model recommendation (add-tests, debug, five-whys, frontend-design, humanizer, pairwise-tests, playwright-js, playwright-py, root-cause-trace) are unchanged.
* docs(skills): tighten descriptions under 1000 charsCraig Jennings2026-05-064-3/+8
| | | | Trim each long description to <=1000 chars while preserving content (phases, when-to-use, when-not-to, companion references). Frees enough per-session skill-listing budget to quiet the truncation warning on small-context models.
* feat(languages): add typescript bundle (Vitest-canonical)Craig Jennings2026-05-061-0/+214
| | | | Mirrors the python bundle's minimal shape: one language-specific file under claude/rules/. Vitest is canonical, with brief notes for Mocha+Chai and Angular Karma legacy idioms. Covers RTL query priorities, MSW for network mocking, it.each for parametrize, async patterns, and TS-specific discipline (no any in tests, prefer satisfies, etc.).
* feat(make): fzf-pick LANG when not set, mirror project pickerCraig Jennings2026-05-061-8/+29
| | | | Add a pick_lang_shell macro that fzf-picks from $(LANGUAGES) when LANG isn't set, mirroring pick_project_shell. Wire it into install-lang and diff so both vars are now optional. Blank $(LANG) when its origin is environment, since LANG is the standard POSIX locale env var and Make would otherwise inherit "en_US.UTF-8" and bypass the picker.
* feat(hooks): make destructive-bash-confirm opt-inCraig Jennings2026-05-061-3/+16
| | | | | | | | | | I added an OPTIN_HOOKS list in the Makefile and excluded those entries from the default install-hooks recipe. destructive-bash-confirm.py is the first opt-in. The recipe now prints the exact ln -s command for each opt-in so users can wire individual ones without consulting docs. uninstall-hooks and list still iterate the full HOOKS list so they keep handling opt-ins that someone has manually linked.