aboutsummaryrefslogtreecommitdiff
path: root/todo.org
diff options
context:
space:
mode:
Diffstat (limited to 'todo.org')
-rw-r--r--todo.org52
1 files changed, 49 insertions, 3 deletions
diff --git a/todo.org b/todo.org
index 2e613b9..75bac4c 100644
--- a/todo.org
+++ b/todo.org
@@ -39,8 +39,16 @@ Tags are assigned and refreshed by =task-audit=; =task-review= keeps them honest
* Rulesets Open Work
-** TODO [#A] Python and TypeScript bundles ship no secret-scan hook :bug:
-SCHEDULED: <2026-07-23 Thu>
+** DONE [#A] Python and TypeScript bundles ship no secret-scan hook :bug:
+CLOSED: [2026-07-23 Thu]
+:PROPERTIES:
+:LAST_REVIEWED: 2026-07-23
+:END:
+Fixed 2026-07-23. Both bundles now ship all four components: =githooks/pre-commit= (the secret scan, shared verbatim with the other bundles, plus a language-appropriate syntax gate), =claude/hooks/validate-python.sh= / =validate-typescript.sh=, =claude/settings.json= wiring the PostToolUse hook, and a seed =CLAUDE.md=. 53 new tests (13 + 13 hook, 13 + 14 pre-commit), suite green.
+
+Two findings from the build worth keeping. First, =node --check= must never be used on TypeScript: it ignores =--experimental-strip-types=, so it rejects valid TS (an =interface= reads as a syntax error) and accepts broken TS. Measured on node v26.4.0. The hook uses =tsc= filtered to TS1xxx (syntactic) diagnostics instead, which also keeps type errors out of scope — those need the whole project graph. Second, the install now warns when a bundle lacks a documented component, which is the half that stops this recurring: nobody will remember the seventh bundle either, so the installer says so.
+
+Left undone deliberately: re-running =make install-lang= on =work= and =clock-panel=. Both are other projects, so that's a cross-project action for Craig. See the polyglot task below — =clock-panel= is python + typescript and can no longer install both.
The =python= and =typescript= language bundles carry rules, a coverage script, and a gitignore fragment. They carry no =githooks/pre-commit=, no =claude/hooks/=, no =claude/settings.json=, and no =CLAUDE.md=. The =bash=, =elisp=, and =go= bundles carry all four.
The pre-commit hook is the secret scanner. So a project installing the Python or TypeScript bundle gets no credential scan on commit, no validate-on-edit hook, and no settings — while README's "Bundle structure" section documents all four as what each bundle follows, and notes.org describes the bundles as "rules + hooks + settings".
@@ -53,7 +61,12 @@ Grading: Major severity (a documented security control absent, silently, with th
Fix direction: port =githooks/pre-commit= to both bundles, adapting the language-specific half (the secret scan is common; =gofmt=/=shellcheck= becomes a formatter/linter check per language), add =claude/hooks/validate-*.sh= and =claude/settings.json=, and seed =CLAUDE.md=. Then make the gap loud: =install-lang= should warn when a bundle lacks a component the README documents, so the next partial bundle announces itself instead of installing quietly. Re-run =make install-lang= on =work= and =clock-panel= afterward.
-** TODO [#C] Two language bundles' pre-commit hooks lost the cd guard :bug:quick:solo:
+** DONE [#C] Two language bundles' pre-commit hooks lost the cd guard :bug:quick:solo:
+CLOSED: [2026-07-23 Thu]
+:PROPERTIES:
+:LAST_REVIEWED: 2026-07-23
+:END:
+Fixed 2026-07-23. =go= and =elisp= now read =cd "$REPO_ROOT" || exit 1=, matching =bash=; the two new bundles were written with the guard. All five siblings agree, all five shellcheck clean.
=languages/bash/githooks/pre-commit= line 8 reads =cd "$REPO_ROOT" || exit 1=. The =go= and =elisp= copies of the same line read a bare =cd "$REPO_ROOT"=. Three siblings of one file, one hardened and two not — the guard landed in bash and never propagated.
Low impact, stated honestly: git chdirs to the working-tree root before running a hook, so if =cd= fails the cwd is already correct and the checks still run against the right tree. Neither script sets =-e=, so a failure wouldn't abort them either. It takes a =GIT_WORK_TREE= or bare-repo edge case for =rev-parse --show-toplevel= to disagree with the cwd at all.
@@ -76,7 +89,28 @@ Craig approved 2026-07-23. Applied to =claude-templates/.ai/notes.org= and synce
CLOSED: [2026-07-23 Thu]
Craig approved 2026-07-23. Added =*** Clear temp/= to =wrap-it-up.org= Step 3, after the archive pass, and synced to the mirror. Two guards: confirm before deleting anything that reads as in-progress rather than throwaway (that belongs in =working/=, so move it there), and skip entirely where =temp/= isn't gitignored, since that means the project uses the directory for something else. This closes the last open clause of his 2026-07-20 working/temp capture.
+** TODO [#B] No two language bundles compose any more — polyglot is now refused :feature:spec:
+:PROPERTIES:
+:LAST_REVIEWED: 2026-07-23
+:END:
+Completing the python and typescript bundles (the [#A] above, fixed 2026-07-23) had a consequence worth deciding on deliberately: all five bundles now ship =claude/settings.json= and =githooks/pre-commit=, so *every* pair collides and =install-lang= refuses the second without =FORCE=1=.
+
+This reopens a decision that was closed on a premise that turned out to be a bug. On 2026-07-17 the call was "polyglot: case-by-case, no option-2 machinery — bundles already compose; only coverage-makefile.txt collides, and it's a manual paste." Bundles appeared to compose only because python and typescript were missing the two files everything else collides on. The composition was the defect, not a design property.
+
+Live impact: =clock-panel= is a python + typescript project. It can install one bundle's hooks or the other's, not both. Today it has neither, so nothing regressed underneath it, but the polyglot path it would have wanted is now closed. =work= is python-only and unaffected.
+
+The real question is what a polyglot project should get. Both files are single-owner by construction: =settings.json= would need its =PostToolUse= arrays merged, and =pre-commit= would need each language's checks concatenated behind one secret scan (which is already identical across all five). Neither merge is hard; the reason it was never built is that nobody had a project that needed it. Now one does.
+
+Options, roughly: (1) build the merge — settings arrays union, pre-commit composes per-language phases; (2) split the shared half out, so the secret scan is one file every bundle sources and only the language phase is per-bundle; (3) leave =FORCE=1= as the polyglot answer and document that the last install wins; (4) do nothing, since only one project is affected. Option 2 is the one that would also have prevented the [#A] — a shared secret scan can't go missing from a bundle that never had its own copy.
+
+Not [#A] because nothing is currently broken: no project is running with a bundle it lost. Not [#D] because a live project wants it and the decision is now forced rather than hypothetical.
+
+Pinned by =scripts/tests/install-lang-collision.bats= so the trade can't drift back unnoticed.
+
** TODO [#B] inbox-send leaves a phantom empty handoff in the target inbox :bug:solo:
+:PROPERTIES:
+:LAST_REVIEWED: 2026-07-23
+:END:
=inbox-send.py= writes straight to the destination path in the target project's =inbox/=. =Path.write_text= opens with mode =w=, which creates and truncates before any content is written, so *any* failure between opening and finishing leaves a zero-byte =.org= file sitting in another project's inbox.
That file is not inert. =inbox-status= counts it as a pending handoff, so it trips the receiving project's =inbox-boundary-check= Stop hook and blocks a turn there. The receiving agent then has to resolve a handoff with no content and no sender context, which it cannot do from the file. Meanwhile the sender saw an error and will most likely retry, so the target gets a second file too.
@@ -90,6 +124,9 @@ Grading: Major severity (a phantom handoff in a *different* project's inbox, unr
Fix direction: write to a temp file in the destination directory and =os.replace= into place, so the inbox only ever sees a complete file. Pin =encoding="utf-8"= on both =write_text= and the =read_text= in =resolve_roots=. Same treatment for =send_file=, whose =shutil.copy2= has the same shape. Test by forcing a mid-write failure and asserting the destination directory is unchanged.
** TODO [#C] Two Signal-channel gaps: unbounded send, second account never received :bug:
+:PROPERTIES:
+:LAST_REVIEWED: 2026-07-23
+:END:
Both found 2026-07-23 reading =claude-templates/bin/agent-text= and =scripts/signal-receive.sh=. Shellcheck is clean on all four =claude-templates/bin/= scripts; these are logic gaps, not style.
1. *The direct send has no timeout.* =agent-text='s relay path passes =-o ConnectTimeout=10= to ssh, but the local-account path calls =signal-cli send= with no bound at all. A stalled network or a lock contended by the receive timer hangs the call indefinitely. That matters more than it looks: agent-text is invoked *by agents*, so an unbounded call blocks the calling turn with no output, and the caller can't distinguish a hang from a slow send. The receive path already takes ~16s of wall clock every cadence, so the two contending on the same account is not hypothetical. Fix: wrap the direct send in =timeout=, matching the relay's bound, and let the existing non-zero path print the desktop-fallback message.
@@ -101,6 +138,9 @@ Both found 2026-07-23 reading =claude-templates/bin/agent-text= and =scripts/sig
Grading: Minor severity for both (one is a latent hang in a tool with a documented fallback, the other a warning on an account nothing currently depends on) x most users, frequently (the stale warning is a standing condition on this host; the hang needs a stall) = P3 = [#C].
** TODO [#C] Two smaller inbox-send defects: uncaught traceback, duplicate roots :bug:quick:solo:
+:PROPERTIES:
+:LAST_REVIEWED: 2026-07-23
+:END:
Both verified 2026-07-23, both in =.ai/scripts/inbox-send.py=, both low-harm. Grouped because they're the same file and the same fix session.
1. *Uncaught =PermissionError=.* =main= catches =(ValueError, FileNotFoundError)= around the send, but =send_file= reaches =shutil.copy2=, which raises =PermissionError= on an unreadable source. That is an =OSError=, not caught, so the user gets a raw Python traceback instead of the clean =inbox-send: <message>= error every other failure path produces. Reproduced with a =chmod 000= source. No partial file is left in this case. Fix: catch =OSError= alongside the other two.
@@ -110,6 +150,9 @@ Both verified 2026-07-23, both in =.ai/scripts/inbox-send.py=, both low-harm. Gr
Grading: Minor severity (one is cosmetic output noise, the other an ugly but accurate failure message; neither loses or misroutes a message) x rare edge case (an unreadable source file, or a roots config with an overlap) = P4... graded up to [#C] rather than [#D] because both fixes are one line each and sit in a script every project depends on for cross-project messaging.
** TODO [#C] lint-org todo-format checkers fire on spec files :bug:solo:
+:PROPERTIES:
+:LAST_REVIEWED: 2026-07-23
+:END:
=level2-done-without-closed= and =level-2-dated-header= encode =todo.org= completion conventions, but they run against every org file, including =docs/specs/=. A spec's Decisions section legitimately uses =** DONE <decision>= with no =CLOSED:= cookie, and its Review-and-iteration-history section legitimately uses =** <dated> — <who> — <role>= headings. Neither is a completion defect there.
Reproduced 2026-07-23 across rulesets' own specs: 100 findings over 7 of the 9 files in =docs/specs/= (docs-lifecycle 27, sentry-workflow 25, autonomous-batch 11, wrapup-routing 11, inbox-consolidation 10, agent-kb 8, encourage-kb 8; the two 2026-07-20 specs are clean). Every project carrying =docs/specs/= inherits the same noise on every sweep. Reported independently by takuzu's first sentry dogfood run.
@@ -119,6 +162,9 @@ Grading: Minor severity (judgment-kind output, so nothing mutates; the cost is n
Fix direction: scope both checkers away from =docs/specs/=, or gate them on the file carrying a =todo.org=-shaped structure. Note (established 2026-07-23) that these are *org-lint's own* checkers, not ones lint-org.el implements, so the fix filters upstream output by file path rather than editing a checker. Takuzu's alternative was to bless =CLOSED:= dates in =spec-create=, which fixes the symptom in new specs and leaves the nine existing ones noisy.
** TODO [#C] notes.org template trips four lint-org flags in every project :bug:quick:solo:
+:PROPERTIES:
+:LAST_REVIEWED: 2026-07-23
+:END:
The synced =claude-templates/.ai/notes.org= carries two boilerplate lines that open with markdown-style bold at column 0 (=**Session history is NOT in this file.**=, =**For protocols and conventions, see:**=). Org parses a line-initial =**= as a level-2 heading, so =misplaced-heading= flags both, and the =#+begin_example= block in the Pending Decisions instructions trips =invalid-block= twice. Every project inherits all four on every sweep.
Reproduced 2026-07-23. Confirms smoke's proposal. Additional finding from the same run: these two are =mechanical-fixed=, not judgment, so =lint-org --fix= silently rewrites the template's =**bold**= to org =*bold*=. The rewrite is correct org, but it lands on a synced template, so whichever project runs =--fix= first creates drift against canonical.