From d345e8a73f97b031674d53dd3ef381e39ac5c181 Mon Sep 17 00:00:00 2001 From: choiyounggi <74581798+choiyounggi@users.noreply.github.com> Date: Sun, 6 Sep 2026 21:54:57 +0900 Subject: [PATCH] knowledge: ingest 12 verified insight(s) --- .dev-loop/INGEST_REPORT.md | 372 +++++++----------- log.md | 11 + .../contact-details-from-scraped-pages.md | 66 ++++ .../llm/binding-instructions-for-agents.md | 5 + wiki/backend/index.md | 3 +- wiki/debugging/index.md | 2 +- wiki/debugging/methodology/reproduce-first.md | 15 +- .../pointer-attracted-particle-fields.md | 73 ++++ wiki/frontend/index.md | 1 + .../ci-cd/unparseable-workflow-file.md | 80 ++++ .../ci-cd/workflow-authored-pull-requests.md | 72 ++++ wiki/infrastructure/index.md | 2 + wiki/platforms/index.md | 2 +- .../toolchains/compiler-sysroot-on-macos.md | 7 +- wiki/qa/index.md | 2 +- wiki/qa/process/completion-claims.md | 2 + ...stcontainers-python-community-namespace.md | 77 ++++ wiki/testing/index.md | 2 + .../mocking/producer-wire-format-in-mocks.md | 69 ++++ wiki/testing/mocking/what-to-mock.md | 1 + 20 files changed, 633 insertions(+), 231 deletions(-) create mode 100644 wiki/backend/common/integrations/contact-details-from-scraped-pages.md create mode 100644 wiki/frontend/design/pointer-attracted-particle-fields.md create mode 100644 wiki/infrastructure/ci-cd/unparseable-workflow-file.md create mode 100644 wiki/infrastructure/ci-cd/workflow-authored-pull-requests.md create mode 100644 wiki/testing/data/testcontainers-python-community-namespace.md create mode 100644 wiki/testing/mocking/producer-wire-format-in-mocks.md diff --git a/.dev-loop/INGEST_REPORT.md b/.dev-loop/INGEST_REPORT.md index 3a965e5..a3a5ffd 100644 --- a/.dev-loop/INGEST_REPORT.md +++ b/.dev-loop/INGEST_REPORT.md @@ -1,237 +1,161 @@ -# Knowledge flush — 13 insight(s) ingested (21 claimed, 2 dropped, 6 released) +# Knowledge flush — 12 insight(s) -Cross-Check: 1× independent adversarial `claude` CLI headless pass over the 5 new pages — it refuted the changed-files gate page's "prettier exits 0 on an empty match set" claim; re-measured against Prettier 3.7.4, confirmed the reviewer was right (unmatched operand exits **2**), and rewrote the page, report rows 3/5 and `log.md`. Other 5 claim groups verdicted sound. Limits: the reviewer's sandbox blocked repo reads, so source-quote-supports-directive and self-contradiction dimensions went unaudited (details in `## Cross-Check`). - -Queue drained under run id `20260827-125731-38371` (this session is the detached -`hooks/auto-flush.sh` run; its step-0 acquire resolved re-entrantly to -`already-owned`, not to a competing holder). 21 rows were claimed; 13 are -ingested below, 2 are retired as out-of-layer, and 6 are released back to -`pending` for a later flush because each needs its own page rather than a row, -and rushing six more pages in one pass would have lowered the bar on all of them. +Run `20260906-213602-52875` (headless auto-flush), branch `knowledge/choiyounggi-20260906-213635`. +Claimed 12 rows with `queue-claim.js claim --max 12`; 18 rows remain pending for later flushes. +Outcome: 6 new pages, 5 merges into existing pages (one reconciling a contradiction), 0 drops, 0 folds. ## Verified best-practice -Every external claim below was live-fetched this session and quoted in the page's -`Sources` block. Field evidence carries the repo, date, and the measured numbers. - -| # | Claim | Sources checked | How verified | Confidence | -|---|-------|-----------------|--------------|------------| -| 1 | `now()` is `transaction_timestamp()` (fixed at transaction start) while `clock_timestamp()` "changes even within a single SQL statement"; `RETURNING` yields computed defaults "without needing a separate database query" | postgresql.org `functions-datetime`, `dml-returning`, `transaction-iso` | Fetched; both key sentences quoted verbatim into the page | verified | -| 2 | A boundary recomputed in a follow-up step is a *second, later* `now`, widening a `<= boundary` set | Field: `rtb-unified` `packages/orpc/src/routers/batch.ts` — codifies "one `now` per decision" and passes `now` into the boundary helper; its result type omits the boundary, which is the shape that invites recomputation | Read the invariant and the signature in the cited file | field-tested | -| 3 | **[CORRECTED BY CROSS-CHECK]** The vacuous-pass shapes for `prettier --check` are: no operands (rc **0**), all operands ignore-filtered (rc **0**), and unsupported extensions with `--ignore-unknown` (rc **0**). A pattern/operand matching nothing exits **2** — it prints the success sentence *and* an unmatched-pattern error | prettier.io CLI + ignore docs; local measurement, Prettier 3.7.4 | The first draft generalised "empty match set ⇒ exit 0" from a field log where both messages appeared together. The independent reviewer flagged it; I then ran all seven cases against a real binary and rewrote the page around the measured table | verified (re-measured) | -| 4 | zsh does not word-split unquoted parameter expansions by default, so `cmd $FILES` arrives as **one** operand | zsh FAQ ch. 3 (`SH_WORD_SPLIT`) | Fetched; quoted ("By default, zsh does not have that behaviour: the variable remains intact") | verified | -| 5 | The zsh word-split operand exits **2**, but its log still carries the success sentence — so the log misleads even though the exit code does not | Field 2026-08-24 (`rtb-unified`, zsh) + local measurement 2026-08-27 | Field log showed both messages together; the local run reproduced it as `rc=2`. The page now says explicitly that this row fails loudly *unless* `--no-error-on-unmatched-pattern` is set. Probe placement re-confirmed: `.claude/tmp/` is `.gitignore`d, so a probe there passes at rc 0 | verified (re-measured) | -| 6 | TypeScript applies excess-property/contextual typing to fresh object literals, so a value of a type can be constructed with the type's name absent from the text | typescriptlang.org handbook, *Object Types* | Fetched; confirmed the check follows from the contextual type, not from a written annotation | verified | -| 7 | `tsc`'s program is `files` ∪ `include` ∪ transitive imports; `exclude` "only changes which files are included as a result of the `include` setting" and does not stop an imported file entering the program | typescriptlang.org TSConfig `#include`, `#exclude` | Fetched; the `exclude` sentence quoted (it sharpens the rule to "in the program", not "in `include`") | verified | -| 8 | Consequence of 6+7 measured | Field 2026-08-24/25 (`rtb-unified`): `grep "DealViewer"` reported 3 construction sites, actual 8 — the missed set included production wiring `routers/deal.ts:38`; `ContractScopeActor` 7→~22. Separately, `packages/orpc/tsconfig.json` `include: ["src/**/*"]` produced 3 production + 13 api-test errors and **zero** for `__tests__/routers/deal.test.ts`, whose 6 sites appeared only as 6 failing tests | Counts recorded from the cited runs | verified | -| 9 | cgroup v2: `memory.peak` is max usage since creation/reset; at `memory.max` "the OOM killer is invoked in the cgroup"; in `memory.events`, `max` counts times usage "was about to go over the max boundary" — **distinct** from `oom_kill` | docs.kernel.org cgroup-v2 admin guide | Fetched; all four quoted. This corrected the candidate, which had read a non-zero `max` as a kill; the page now states the distinction explicitly | verified | -| 10 | An `exec`'d process joins the container's cgroup and is invisible to the application's own semaphore | kubernetes.io `manage-resources-containers`, `assign-memory-resource`, `kubectl exec` reference + field 2026-08-26 (review-bot pod, `limits.memory: 3Gi`): `memory.current` 2.54 GiB, `memory.peak` 3.0 GiB (at the limit), `memory.events: max 5`, while `maxConcurrentAgents: 20` reported free slots | Docs fetched; pod numbers from the cited measurement | verified | -| 11 | Basename-keyed mutation backups collide across directories and restore cross-writes; an untracked file's `git diff` is empty whether restored or destroyed | Field 2026-08-21 (`rtb-unified`, NEWRTB-2936): restore wrote `schemas/deal.ts` into `routers/deal.ts` → `Cannot find module './common.js'`, `grep -c dealRouter` = 0; **both files were 154 lines**, so a line-count check passed; after re-keying, M9/M10 flipped SURVIVED→KILLED. Plus stryker mutant-states / pitest for the verdict vocabulary | Reproduced end to end in the cited run | field-tested | -| 12 | A negative assertion is vacuous when the fixture never supplies the triggering input | Field 2026-08-25 (`rtb-unified`): with `staleQueuedJobIds: []` the code early-returned; the widening the assertion claimed to catch survived 116/116 green | Mutation applied and observed | field-tested | -| 13 | A body-level (non-inline) review finding cites no file, so rebutting from an assumed file rejects real defects | Field 2026-08-19 (PR #327 r16): quote matched `report.py:393/416/425`, not the assumed `fill_plan.py:307` — sibling modules, one already fixed | Grep resolved the quote to the real site | field-tested | -| 14 | Unifying two duplicate allowlists defaults to the union and silently widens each side | Field 2026-08-25 (`rtb-unified` PR #965): folding `DISPLAYABLE_ERROR_CODES` into `USER_FACING_ERROR_CODES` would have added `UNAUTHORIZED` + `VALIDATION_ERROR`, exposing raw server messages as inline UI errors; caught only by computing the difference first | Difference computed before the merge | field-tested | - -Not upgraded: nothing was marked `verified` on field evidence alone. Two pages -carry `confidence: field-tested` (`mutation-harness-file-custody`, -plus the pre-existing `evaluating-review-feedback`), and no candidate was -recorded as `verified` without a fetched primary source. +1. **Pointer-attracted particle field collapses (8a6e99abdfe31e54)** — `p += (t−p)·k` with a + stationary pointer is a contraction; nodes converge onto the cursor. Sources checked: + Wikipedia Banach fixed-point theorem ("admits a unique fixed point"), MDN `pointerleave` + ("moved out of the hit test boundaries"), MDN `requestAnimationFrame` ("paused in most + browsers when running in background tabs"). Field: cover-letter review + `.orchestration/reviews/t2-hero-code-intro-r1.md` F1 (collapse to ~8.7e-8 px; fix + `POINTER_INNER_RADIUS=32`, clamped step, out-of-bounds null, 1000-step test; 374 green). + Confidence: **verified** (mechanism doc-backed; numbers field). +2. **Firecrawl `onlyMainContent` drops the regions that hold contact details (407808eb70bcaf43)** + — docs.firecrawl.dev `/scrape` reference: `onlyMainContent` default `true`, "Only return the + main content of the page excluding headers, navs, footers, etc."; `includeTags`/`excludeTags` + exist. `tel:` preservation is not documented — kept as field data (22/30 sites expose `tel:`, + 16/30 have the number only outside main content, 0/30 gain a number from JS rendering). + Confidence: **verified** (mechanism) with the survey stated as field measurement. +3. **"Ask the user" binds only when the question tool is named and a hook checks the transcript + (ebbe51aeb2082d16)** — code.claude.com hooks doc: PreToolUse input carries `transcript_path`, + exit code 2 blocks. dev-loop PR #136 (merged 2026-08-23) introduced the wording without the + tool name; PR #144 (merged 2026-08-25) named `AskUserQuestion` and added + `hooks/orchestrate-ask-gate.sh` (present on main; 27 bats cases). Confidence: **field-tested** + directive on top of doc-verified hook mechanics (page stays `field-tested`). +4. **Column-0 line in `run: |` silently unregisters a workflow (d5776de0b6e9bedb)** — YAML 1.2.2 + §8.1.2 "terminated when encountering a line which is less indented"; GitHub workflow-syntax + doc: without `name` the file path is displayed; `gh workflow run` manual: needs + `on.workflow_dispatch`. Local repro: Ruby Psych fails at `line 13 column 1`; two `-m` flags + parse. Field: groundwork PR #16 (merged; 422 before, `sync dev-loop pin` listed after). The + "listed as active with no triggers" behavior is observed, not documented — said so on the + page. Confidence: **verified**. +5. **Actions may not create PRs until the repo setting is on (e88fdc004757a742)** — docs.github.com + Actions settings page ("Allow GitHub Actions to create and approve pull requests") and REST + `actions/permissions/workflow` with `can_approve_pull_request_reviews`. groundwork now reports + `true`; PR #18 (bot, closed) → #22 (bot, merged). Confidence: **verified**. +6. **Personal-repo ruleset refuses an `Integration` bypass actor (d58c3d2dc6afce8f)** — REST + rules reference lists `Integration` as an actor type; the personal-repo 422 is NOT in the + docs (agent searched; only `OrganizationAdmin` is documented as personal-inapplicable). Field: + groundwork ruleset 21371046 exists with `bypass_actors: []`, `pull_request` rule at 0 + approvals, required checks `bats/shellcheck/version-sync`. Row marked field-observed on the + page; the page as a whole stays **verified** because the recommended path (PR + self-merge) + rests on documented settings. +7. **GITHUB_TOKEN-pushed branches get no check runs (5a15e973f89b9ab8)** — docs.github.com + `github_token`: "events triggered by the GITHUB_TOKEN will not create a new workflow run" + (exceptions `workflow_dispatch`/`repository_dispatch`); PAT/App token advised. `gh pr merge` + manual (`--auto`, `--delete-branch`), auto-delete-branches doc, cli/cli#9073 (open: `--auto -d` + does not delete). Field re-checked live: PR #22 check-runs `total_count=0`, #25 and #27 = 6, + `delete_branch_on_merge=true`. Confidence: **verified**. +8. **`testcontainers.community.redis` (401fd399afd7f97b)** — upstream `src/testcontainers/redis.py` + shim emits the exact DeprecationWarning; `community/redis/__init__.py` is the new home; + commit ab6cca8e (2026-06-05) in release `testcontainers-v4.15.0` (PyPI 2026-07-24). Local + repro in a fresh Python 3.13 venv: warning printed, same class object, identical constructor + signature, 45 shims ↔ 45 `community/` packages. Confidence: **verified**. +9. **Reproduce a shell bug under the script's own interpreter (c04820c7f2947876)** — bash manual + Aliases ("Aliases are not expanded when the shell is not interactive"), Bash Startup Files, + zsh Files (`.zshrc` only "if the shell is interactive"). Field: dev-loop issue #145 (exists, + closed). On this machine today `command -v grep` is `/usr/bin/grep` in both contexts, so the + ugrep half is field-only; the directive is doc-backed. Confidence: **verified** mechanism, + field example. +10. **Assertion boundary vs symptom (c4350dda424a817e)** — Wikipedia Regression testing ("record a + test that exposes the bug"); field: linkly-crew PR #10 (public, merged) `core.rs:426-431`. + Confidence: **field-tested** rows on two `field-tested`/`verified` pages. +11. **Mock fabricates an id the producer formats differently (7ad0888f88a13776)** — Google Testing + Blog "Don't mock types you don't own" ("assumptions built into mocks may get out of date"), + Fowler ContractTest, Pact docs; field: linkly-crew PR #10 (`derive.ts` vs `dispatch.rs`, + 0/125 → 25/25). A Stripe "ids are opaque" quote the agent could not re-fetch was NOT cited. + Confidence: **verified**. +12. **Homebrew clang ignores `SDKROOT` (57aa89b96c58268f)** — the trigger already had a page + (`compiler-sysroot-on-macos`, verified 2026-08-29) whose step-2 row says "Export SDKROOT; + clang reads it as the default sysroot". Local repro on Homebrew clang 22.1.8 CONTRADICTS + that row for Homebrew LLVM: `clang -###` loads + `etc/clang/arm64-apple-darwin25.cfg` = `-isysroot …/MacOSX26.sdk` (absent), so `SDKROOT` + (unset / xcrun path / CLT path) all fail; `-isysroot` works; `--no-default-config` + + `SDKROOT` works. Mechanism sourced: clang UsersManual configuration files + (`--no-default-config`), Homebrew `llvm.rb` `write_config_files`. The candidate's own + "SDKROOT is not a fix" claim is therefore correct **for Homebrew LLVM only**, so the + contradiction was reconciled as a condition (see routing). Confidence: **verified**. ## Existing-layer check -Method: routed via `INDEX.md` → domain `index.md`; then built a full id+title -index of all 265 pre-existing pages and probed it with concept greps -(`clock_timestamp|clock skew`, `changed[- ]files|--ignore-unknown`, `tsconfig`, -`contextual typ|excess property`, `set difference|allowlist`, `cgroup`, -`basename|backup.*restore`, `2>&1`, `delta|baseline`) before deciding new vs merge. - -Pages read: testing-quality-source-text-wiring-assertions, testing-quality-tests-that-cannot-fail, backend-common-change-impact-call-site-enumeration, backend-common-change-impact-widening-a-closed-value-table, qa-process-evaluating-review-feedback, infrastructure-containers-host-cgroup-visibility, testing-quality-behavior-not-implementation - -Findings: - -- **Zero coverage** (→ new pages): changed-files-only gates, tsconfig/contextual - typing, allowlist set-difference, app-clock-vs-DB-timestamp, exec-into-a-running-container. - The concept greps returned no hits for these; the clock hits were incidental - (offline sync, token handling) and none compared an app clock to a DB column. -- **Already covered — one candidate all but retired.** The comment-stripping - insight is `source-text-wiring-assertions` step 2 verbatim ("Make the - assertion's subject the file with comments removed"), and its false-RED and - negative/count false-GREEN shapes are already edge rows. Only the *empty-slice* - consequence was new, so that alone was merged. -- **Line-cap conflict handled without breaking the invariant.** - `source-text-wiring-assertions` sits at exactly **120** body lines (the - documented cap). Rather than add a row and violate maintenance invariant 5, the - new nuance and the new field evidence were merged **in place** into an existing - edge row and an existing source bullet. Body count re-measured after editing: - still 120. -- **No conflicts found.** Nothing ingested contradicts an existing directive. - The one correction made was to a *candidate*, not to the wiki (item 9: the - `memory.events` `max` counter is approaches-to-limit, not kills). -- **Related links added both ways**: `tests-that-cannot-fail` ↔ - `mutation-harness-file-custody`; `widening-a-closed-value-table` ↔ - `compiler-as-call-site-inventory` (+ `errors-diagnostics-from-a-shared-code-path`); - `host-cgroup-visibility` → `exec-added-processes-and-the-memory-budget` - (from its existing self-monitoring row). -- **Indexes/log updated**: 4 domain indexes (+5 "load when" rows), `log.md` - appended. Root `INDEX.md` unchanged — no new domain. - -Gates run (the exact CI commands from `.github/workflows/test.yml`): -`node scripts/wiki-structure-checks.js wiki` → **pages: 270, indexes: 13, -findings: 0**; `node scripts/wiki-lint-prohibitions.js wiki` → **directives 72, -compliant 72, violations 0** (the 1 `info` is pre-existing in -`config/keys-ahead-of-their-consumer.md`, untouched); `bash scripts/check-versions.sh` -→ `ok: dev-loop 1.11.2`. The `bats tests/` job was **not** run — bats is not -installed on this machine, and this change touches only wiki markdown (no -scripts or hooks), so that suite's subject is unchanged. +Pages read: platforms-toolchains-compiler-sysroot-on-macos, infrastructure-agent-orchestration-unattended-worker-questions, platforms-shells-portable-shell-scripts, platforms-environment-path-resolution, platforms-tools-bsd-vs-gnu-cli, debugging-methodology-reproduce-first, testing-mocking-what-to-mock, backend-common-llm-binding-instructions-for-agents, qa-process-completion-claims, infrastructure-ci-cd-pipeline-structure, frontend-design-html-in-canvas, backend-common-integrations-robots-txt-and-source-selection, testing-strategy-failing-test-first, testing-quality-write-path-assertions + +Also read the PR-only page testcontainers-reaper-on-docker-desktop-macos on the #186 branch +(different trigger: Ryuk socket mount, not import paths) and the root `INDEX.md` plus the +infrastructure, testing, frontend, backend, debugging, qa, platforms domain indexes. + +- grep of main for `workflow_dispatch`, `can_approve`, `bypass_actors`, `GITHUB_TOKEN`, + `testcontainers`, `firecrawl`/`onlyMainContent`, `wire format`: no hits → new pages 1–6. +- `AskUserQuestion` hits only unattended-worker-questions (worker-side out-of-band channel; a + different trigger) → merged into binding-instructions-for-agents (body rows + sources; the + `related:` line was left untouched because #179 rewrites it — the back-link to + unattended-worker-questions is in the new edge-case row text instead). +- `sysroot` hits compiler-sysroot-on-macos → **conflict flagged and reconciled**: the step-2 + `SDKROOT` row now carries the "when no `-isysroot` reaches the driver" condition, a new edge + row explains the Homebrew config file, an Instead-of row and three Sources bullets were added; + `log.md` records it as a reconciled contradiction. Frontmatter untouched (#179 rewrites + `related:`), so the new URLs live in the body Sources section only. +- reproduce-first (untouched by any open PR): two edge rows + two Instead-of rows + sources + + frontmatter (`sources`, `last_verified`, `related` += path-resolution, completion-claims). +- completion-claims: one claim/evidence row inserted after "Bug fixed" and one Sources bullet + before the superpowers bullet — positions chosen so #179's hunks (last table row, appended + sources) stay one unchanged line away. Frontmatter untouched (#179 rewrites it). +- what-to-mock: one edge-case row linking the new producer-wire-format page; #179/#183 rewrite + its frontmatter and #183 edits the Do table and Sources, so nothing else was touched. +- Back-links NOT added (frontmatter owned by open PRs): html-in-canvas → pointer page (#181), + what-to-mock → producer-wire-format (#179/#183), unattended-worker-questions → binding + instructions (frontmatter untouched by PRs but the row text link suffices). +- Index rows: 6 new rows (infrastructure ci-cd ×2, backend integrations, testing data, testing + mocking, frontend design); load-when text extended on compiler-sysroot (platforms), + binding-instructions (backend), reproduce-first (debugging), completion-claims (qa). +- Lint: `wiki-structure-checks.js wiki` 282 pages / 0 findings; `wiki-lint-prohibitions.js wiki` + 0 violations; every new page ≤ 65 body lines, merged pages ≤ 94. ## Open-PR check -`gh pr list --repo choiyounggi/dev-loop --state open --search "head:knowledge/"` -returned **no open PRs**, and a second unfiltered `gh pr list --state open` -returned none either — the repository has zero open PRs at flush time. There -were therefore no in-flight sibling branches to diff against, and no -`git fetch origin ` / `git diff origin/main origin/ -- wiki/` -comparisons to run. - -Per-candidate verdict: **all 21 = `new`.** No `fold`, no `drop-as-pending-duplicate`. -(The 2 drops recorded below are out-of-layer drops, not pending-duplicate drops.) +Open `knowledge/*` heads listed and fetched: #186 (20260906-013856), #185 (20260906-003745), +#183 (20260904-133717), #182 (20260903-214027), #181 (20260903-203836), #180 (20260903-184706), +#179 (20260903-172728). Each diffed against `origin/main -- wiki/` and grepped for the candidate +terms (GitHub Actions, GITHUB_TOKEN, firecrawl, community.redis, AskUserQuestion, corr-, ugrep, +`/bin/sh`, assertion boundary, wire format). + +| Candidate | Overlapping open head | Verdict | +|-----------|-----------------------|---------| +| 8a6e99ab pointer field | none (#181 "spatial clamp" is server-side coordinate validation) | new | +| 407808eb Firecrawl | none | new | +| ebbe51ae ask-tool gate | #179 touches binding-instructions `related:` only; #180 unattended-worker-questions is worker-side | new (merge on main page, body only) | +| d5776de0 YAML block scalar | none | new | +| e88fdc00 create-PR setting | none | new | +| d58c3d2d ruleset bypass | none | new | +| 5a15e973 required checks | none | new | +| 401fd399 testcontainers import | #186 testcontainers-reaper (Ryuk socket; different trigger) | new (separate page; no link to the PR-only page so main stays link-clean) | +| c04820c7 shell repro path | #180/#181 edit portable-shell-scripts / path-resolution bodies on other rows | new (merged into reproduce-first, which no PR touches) | +| c4350dda assertion boundary | #179 edits completion-claims (different rows) | new (rows placed away from its hunks) | +| 7ad0888f mock wire format | #183 edits what-to-mock Do table + Sources | new (own page + one edge row) | +| 57aa89b9 sysroot | #179 touches compiler-sysroot `related:` only | new (body-only merge) | + +No fold and no pending-duplicate drop this run. The earlier sibling hash 35922b00 (same +sysroot trigger, 2026-08-05) is already `processed`; this row added the config-file mechanism +that page lacked, so it was merged rather than dropped. ## Routing decision -**New pages (5)** - -| Page | Domain/category | From | Why not an existing page | -|------|-----------------|------|--------------------------| -| `application-clock-vs-database-timestamps` | databases / transactions | `2b27d15d` + `bea92fdd` | No page compares an app clock to a DB column. `transactions` chosen over `schema-design` because the decisive content is transaction-time semantics (`now()` = transaction start ⇒ stamp order ≠ commit order) and the fix is a lock/isolation choice | -| `changed-files-only-gates` | infrastructure / ci-cd | `ff041061` + `4b9af3a0` | Zero grep hits. Both candidates are the same defect (a gate green with an empty subject) from two directions, so they became one page rather than two | -| `compiler-as-call-site-inventory` | backend / common / change-impact | `702dcf4e` + `94d55f2f` | `call-site-enumeration` is the sibling case (callers of a changed signature, Python positional-vs-keyword) and is at 80 body lines; the TS mechanism is *constructors of a type* with its own workflow, so per "one case per page" it is a separate page, cross-linked | -| `mutation-harness-file-custody` | testing / quality | `6a9de235` + `41fa1c87` | `harness-reverse-controls` covers scoring a harness; nothing covers the harness's custody of the tree. Both candidates are that one case (keying, and the read window) | -| `exec-added-processes-and-the-memory-budget` | infrastructure / containers | `7b9e8788` | `host-cgroup-visibility` is cross-pod read mechanics and explicitly routes self-monitoring elsewhere; `resource-limits-and-probes` is manifest authoring. This is a runtime preflight before adding load | - -No new category was created — all five landed in existing categories. - -**Merged into existing pages (5 candidates)** - -| Candidate | Merged into | Shape | -|-----------|-------------|-------| -| `91ef5d53` | `testing-quality-tests-that-cannot-fail` | +1 never-fails row, +1 Instead-of row, +1 source | -| `f189f423` | `testing-quality-source-text-wiring-assertions` | In-place extension of 1 edge row + 1 source bullet (page at the 120-line cap) | -| `bb6d8539` | `backend-common-change-impact-widening-a-closed-value-table` | +Do-this 6 & 7 (incl. a set-difference ruling table), +1 Instead-of row, +1 source | -| `60a817ee` | `qa-process-evaluating-review-feedback` | +2 edge rows, +1 Instead-of row, +1 source | -| `7b9e8788` | `infrastructure-containers-host-cgroup-visibility` | Cross-link from its self-monitoring row to the new page | - -**Dropped — out of layer (2, retired)** - -- `094dedf3` — a Figma MCP `inspect_node` → `get_dev_ready` children-fetch - workaround. The server is a private, org-internal MCP plugin; the behavior is - not publicly verifiable and the directive does not transfer to any other reader. -- `e165a365` — an `/rtb:review` remote-fallback runbook naming - `~/.claude/tools/rtb-remote-review.sh` and an internal pod. The transferable - kernel ("a two-provider review gate degraded to one provider is not a passed - gate") is already the subject of `qa-process-llm-review-pipelines`; what remains - is machine-specific paths. - -**Released back to `pending` (6)** — each needs its own page, not a row, and is -better served by a dedicated pass than by being appended here: -`81dc1f98` (naming the carrier field/type when a plan says "wire A to B"), -`b9ae304a` (`VAR="$(cmd 2>&1)"` mixing stderr into a value used as a path), -`fdd0b3c6` (monitor markers anchored at line start; delta rather than absolute -state; first cycle records a baseline), -`c2adb2be` (positional-order assertions on rendered SQL predicates), -`815e8cb9` (grep only *active* `DATABASE_URL` assignments, and confirm which -dotenv file the tool loads, before a destructive DB command), -`f1146adb` (CI ticket-key extraction scoped by changed-file intersection rather -than by mention). - -## Decision Log - -**Intent.** Drain the harvested `★ Insight` queue into reviewable wiki knowledge -without lowering the wiki's evidence bar. The queue held 21 rows accumulated over -several days; the goal was correct routing and real verification, not a high -ingest count. - -**Alternatives considered and rejected.** - -- *Ingest all 21 in this pass.* Rejected: six of them each need their own page, - and writing six more pages in one pass would have produced thin, weakly-sourced - entries. They are released to `pending`, not dropped, so the next flush takes - them with a full budget. -- *Append the two TypeScript candidates to `call-site-enumeration`.* Rejected: - that page is the sibling case (callers of a changed signature, Python - positional-vs-keyword). AGENTS.md requires one case per page, so the - constructor-enumeration case became its own page, cross-linked both ways. -- *Add a row to `source-text-wiring-assertions` for the empty-slice nuance.* - Rejected: that page is at exactly the documented 120-line body cap, so adding a - line would violate maintenance invariant 5. The nuance was merged **in place** - into an existing edge row instead; body re-measured at 120. -- *Drop the comment-stripping candidate entirely as a duplicate.* Rejected: its - directive is already the page's step 2, but the empty-slice consequence - (vacuous **green**, not the documented noisy red) was genuinely absent. -- *Claim a cross-check exemption because this PR cannot merge itself.* Rejected — - see below; the check found a real error, which is the argument against exempting. -- *Push to `origin`* as the skill's snippet does. Not available: this contributor - has no write access to `choiyounggi/dev-loop` (403). Used the pre-existing - `fork` remote, which is how every prior knowledge branch here was published. -- *Branch name from `git config user.name`.* The skill's ASCII sanitisation of a - Korean name yields an empty string → `anon`, defeating the attribution the - branch name exists for. Used the gh login, matching existing branch names. - -**Where reviewers should look hardest.** - -1. `infrastructure/ci-cd/changed-files-only-gates.md` — rewritten after the - cross-check. The measured table is the load-bearing part; please sanity-check - it against your own Prettier version, since the exit codes are version-visible - behaviour rather than a documented contract. -2. `databases/transactions/application-clock-vs-database-timestamps.md` step 5–6 — - the claim that timestamp order is not commit order, and that the remedy is a - lock/isolation level rather than finer clock resolution. `[추정]` on the MySQL - `NOW()`/`SYSDATE()` row: taken from general MySQL semantics, not fetched this - session like the PostgreSQL pages were. -3. `widening-a-closed-value-table.md` Do-this 6–7 — this inserts a security-shaped - concern (allowlist widening) into a page whose original subject was value - tables. If that reads as two cases, it should be split. -4. The 2 dropped candidates — if you consider private-tooling runbooks in scope - for this wiki, they should be restored rather than retired. - -## Cross-Check - -Independent adversarial pass via `claude` CLI headless (separate process, no -shared context), prompted to refute rather than confirm, over the five new pages' -technical claims. - -**It found a real error, and the page was rewritten because of it.** The reviewer -challenged the claim that `prettier --check` exits 0 on an empty match set, -arguing an unmatched pattern errors by default and that exit-0 belongs to the -ignore-filtered case. I resolved it by measurement rather than by argument — -running all seven cases against Prettier 3.7.4 — and the reviewer was right: -an unmatched operand exits **2** (while still printing the success sentence), -whereas the genuine silent vacuous passes are no-operands, all-ignore-filtered, -and `--ignore-unknown`-with-unsupported-extensions. The page, this report's -rows 3 and 5, and the `log.md` entry were all corrected. - -Verdicts on the other five claim groups: **sound** (PostgreSQL clock semantics — -noted as if anything *understated*; zsh word-splitting; TS contextual typing; -`tsc` program membership incl. `exclude`-does-not-stop-imports; cgroup v2 -`max` vs `oom_kill` and `kubectl exec` cgroup placement). - -Stated limits of the check: the reviewer's sandbox denied it read access to -`~/.dev-loop/repo/wiki`, so it adjudicated the six claims as quoted in its prompt -and could **not** audit (b) whether each `Sources` quote supports the directive it -is cited for, or (c) whether any page contradicts its own edge-case rows. Those -two dimensions remain unreviewed by an independent party and are the residual -risk in this PR. A first attempt also returned only the session's Stop-hook -output rather than a verdict; that run was discarded rather than read as -"no findings". - -## Review notes - -- PR-only, as required: no merge, no push to `main`. -- Commit is under the contributor's own ambient git identity - (`최영기 `, gh `dch0202-rsquare`); no assistant identity - and no `Co-Authored-By` trailer. The branch uses the gh login because - sanitizing the Korean `user.name` to ASCII yields an empty string, which the - skill's snippet would have turned into `anon` — that would have defeated the - attribution the branch name exists for. -- Scope purity: only `wiki/**`, four domain indexes, `log.md`, and this report. - Two untracked leftovers from earlier flushes - (`.dev-loop/CROSSCHECK_FINDINGS.md`, `.dev-loop/fold-note-73.md`) were left - untouched and unstaged. +| Candidate | Target | Page | +|-----------|--------|------| +| 8a6e99ab | frontend/design | **new** `pointer-attracted-particle-fields` | +| 407808eb | backend/common/integrations | **new** `contact-details-from-scraped-pages` | +| ebbe51ae | backend/common/llm | **merge** `binding-instructions-for-agents` | +| d5776de0 | infrastructure/ci-cd | **new** `unparseable-workflow-file` | +| e88fdc00 + d58c3d2d + 5a15e973 | infrastructure/ci-cd | **new** `workflow-authored-pull-requests` (one situation — a workflow landing its own PR — with four settings as a decision table) | +| 401fd399 | testing/data | **new** `testcontainers-python-community-namespace` | +| c04820c7 | debugging/methodology | **merge** `reproduce-first` | +| c4350dda | qa/process + debugging/methodology | **merge** `completion-claims` (claim/evidence row) and `reproduce-first` (edge row) | +| 7ad0888f | testing/mocking | **new** `producer-wire-format-in-mocks` + edge row in `what-to-mock` | +| 57aa89b9 | platforms/toolchains | **merge** `compiler-sysroot-on-macos` (contradiction reconciled as a condition) | + +No new category: ci-cd, integrations, data, design, mocking, methodology, process, llm and +toolchains all cover their candidates' triggers under their existing "route here" lines. diff --git a/log.md b/log.md index e2ee9ca..5c77b42 100644 --- a/log.md +++ b/log.md @@ -104,3 +104,14 @@ Append-only. Format: `## [YYYY-MM-DD] ` on 4.15.0+; same class and constructor (upstream shim source + commit ab6cca8 + fresh-venv repro) +## [2026-09-06] ingest | frontend-design-pointer-attracted-particle-fields — a fractional per-frame pull is a contraction; require release + inner-radius clamp and a many-step distance-floor test (Banach + MDN + cover-letter review F1) +## [2026-09-06] ingest | testing-mocking-producer-wire-format-in-mocks — lift an id's wire shape from the producer's tests, map the opaque token through the binding message (Google ToT + Fowler ContractTest + Pact + linkly-crew #10) +## [2026-09-06] ingest | platforms-toolchains-compiler-sysroot-on-macos — reconciled contradiction: Homebrew clang's `etc/clang/.cfg` pins `-isysroot`, so `SDKROOT` is ignored there; `-isysroot` or `--no-default-config` (clang UsersManual + llvm.rb + local repro on clang 22.1.8) +## [2026-09-06] ingest | backend-common-llm-binding-instructions-for-agents — "ask the user" binds only when the question tool is named and a transcript-reading PreToolUse gate checks it (Claude Code hooks doc + dev-loop #144) +## [2026-09-06] ingest | debugging-methodology-reproduce-first — reproduce a script bug under the script's own interpreter (aliases/rc files differ), and check an existing green test's assertion boundary against the symptom (bash/zsh manuals + dev-loop #145 + linkly-crew #10) +## [2026-09-06] ingest | qa-process-completion-claims — "the stage is healthy, its test passes" needs an assertion past the symptom (linkly-crew #10) +## [2026-09-06] ingest | testing-mocking-what-to-mock — edge row pointing to producer-wire-format-in-mocks diff --git a/wiki/backend/common/integrations/contact-details-from-scraped-pages.md b/wiki/backend/common/integrations/contact-details-from-scraped-pages.md new file mode 100644 index 0000000..e109079 --- /dev/null +++ b/wiki/backend/common/integrations/contact-details-from-scraped-pages.md @@ -0,0 +1,66 @@ +--- +id: backend-common-integrations-contact-details-from-scraped-pages +domain: backend +category: integrations +applies_to: [general, firecrawl] +confidence: verified +sources: + - https://docs.firecrawl.dev/api-reference/endpoint/scrape +last_verified: 2026-09-06 +related: [backend-common-integrations-robots-txt-and-source-selection, backend-common-reliability-timeouts-and-retries] +--- + +# Collecting Phone Numbers and Addresses From Company Sites With a Main-Content Scraper + +## When this applies + +Extracting contact details (phone, address, messenger handle) from company +websites through a scraping API or library that offers a "main content only" +mode (Firecrawl `onlyMainContent`, readability-style extractors); such a run +returns clean text with few or no phone numbers; deciding whether to add a +JavaScript-rendering fallback. + +## Do this + +1. **Turn the main-content filter off for contact extraction.** Sites put + contact details in the header bar, the footer, and floating widgets — + exactly the regions a main-content filter is defined to remove. Firecrawl's + `onlyMainContent` defaults to `true` and "excludes headers, navs, footers"; + set it to `false` (or list `header`, `footer`, `aside` and the theme's + wrapper selectors in `includeTags`) so the extractor sees the regions where + the numbers live. +2. **Extract from the raw HTML's link targets before any text heuristic.** + Request the HTML format alongside markdown and read `href` values: + +| Link | Yields | +|------|--------| +| `tel:+84…` | The canonical phone number, already normalized by the site | +| `https://zalo.me/…`, `https://wa.me/…`, `viber://…` | Messenger handles that are the primary contact on many SME sites | +| `mailto:` | Email without a regex over rendered text | + + Fall back to a regex over the full-page text only for sites with no such + links. +3. **Treat JavaScript rendering as the last fallback.** Measure before adding + it: fetch the static HTML and the rendered DOM for a sample of target sites + and diff the numbers found. Add rendering only for the sites where the diff + is non-empty. + +## Edge cases + +| Case | Then | +|------|------| +| The number appears only as an image or CSS-obfuscated spans | Neither mode returns it as text; record the site as "contact by image" and stop, rather than adding OCR to the pipeline for one site | +| The footer holds several numbers (branches, departments) | Keep all, labeled by the nearest heading or `aria-label`; a "first number wins" rule picks the fax line | +| `includeTags` is used instead of `onlyMainContent: false` | Include the wrapper elements the theme actually uses (`#footer`, `.topbar`) — a site that renders its footer as a `
` is not matched by the `footer` tag alone | + +## Instead of + +| If you are about to | Do this instead | Why | +|---------------------|-----------------|-----| +| Scrape in the default main-content mode and regex the markdown for phone numbers | Disable the main-content filter and read `tel:`/messenger hrefs from the HTML first | The default mode removes the header and footer where the numbers sit | +| Add headless rendering because the static fetch found no number | Diff static vs rendered on a sample first | In the measured sample no site injected a number by JavaScript alone; the missing numbers were in the filtered-out regions | + +## Sources + +- https://docs.firecrawl.dev/api-reference/endpoint/scrape — `onlyMainContent` (default `true`): "Only return the main content of the page excluding headers, navs, footers, etc. This is a deterministic HTML-level filter applied before markdown is generated"; `includeTags` / `excludeTags`: "Tags to include in the output" / "Tags to exclude from the output" +- Field measurement 2026-09-04 (30 Vietnamese company websites, static HTML vs rendered DOM): 16/30 sites carried their phone number only outside the main content (header, footer, floating widget); 22/30 exposed it as a `tel:` link; 0/30 gained a number after JavaScript rendering that the static HTML lacked diff --git a/wiki/backend/common/llm/binding-instructions-for-agents.md b/wiki/backend/common/llm/binding-instructions-for-agents.md index faf04f4..c81edc9 100644 --- a/wiki/backend/common/llm/binding-instructions-for-agents.md +++ b/wiki/backend/common/llm/binding-instructions-for-agents.md @@ -35,6 +35,7 @@ description or trigger line that decides when an instruction loads. | Output compliant in letter but wrong in shape | A positive recipe or template of the wanted shape — in head-to-head wording tests, adding prohibitions to a wrong-shape failure produced MORE of the unwanted content than no guidance | | A required element omitted | A structural template with the element as a REQUIRED slot | | Behavior that depends on the situation | Predicate-keyed rules ("when X, do A; when Y, do B") — a blanket rule with exemption clauses leaves the boundary to per-run improvisation | +| The instruction said "ask the user" and the agent asked in prose (a numbered list typed into the reply instead of the question tool's chooser) | Name the channel, not only the shape — "ask with the `AskUserQuestion` tool" — and gate the action that depends on the answer with a PreToolUse hook that reads `transcript_path` and denies (exit 2) until a call to that tool exists in the transcript. Shape words ("numbered list", "one at a time") leave the tool choice open, and prose is the cheapest rendering; wording alone does not hold the choice across sessions, a hook checks it mechanically | 3. Close loopholes from actual transcripts: quote the rationalization the agent produced and counter it by name; add counters only for observed @@ -62,6 +63,7 @@ description or trigger line that decides when an instruction loads. | The instruction must hold across models or versions | Re-run the baseline+instruction pair per model — binding wording is model-specific, and an upgrade can un-bind it | | Two instruction sources conflict (a skill vs a project rule) | State the precedence inside the artifact itself; unstated precedence gets resolved differently per run | | The instruction is a one-off for a single session | Skip the test harness and state the rule directly — the testing cost is justified by reuse, and a session instruction is consumed once | +| The gated question runs headless (no TTY) | The question tool resolves at once with empty answers, so the transcript carries a call with no human answer; have the hook also require a non-empty answer in the tool result, or route the decision out of band per [infrastructure-agent-orchestration-unattended-worker-questions] | ## Instead of @@ -70,8 +72,11 @@ description or trigger line that decides when an instruction loads. | Stack a third prohibition after two were ignored | Rewrite as a recipe or predicate rules, then retest | Prohibition stacking measured worse than no guidance for wrong-shape failures | | Ship a wording after one clean test run | Run 5+ repetitions plus a no-guidance control | A single run samples the good tail of the distribution; the control proves the instruction is doing the work | | Write "never do X" on its own | Pair it with the replacement action that makes it unnecessary | A bare prohibition invites improvisation at the boundary — the same rule this wiki's AGENTS.md enforces on its own pages | +| Fix a "asked in prose" regression by adding "use a numbered list" to the wording | Name the question tool in the wording and add the transcript-reading PreToolUse gate | The list instruction was already present when the regression happened; only the tool name plus a mechanical check binds the channel | ## Sources - https://github.com/obra/superpowers — writing-skills skill and its testing references: baseline-fails-first discipline, form-matched-to-failure table with head-to-head wording measurements, nuance-clause degradation, description-as-trigger-only rule; field-tested across the framework's own skill suite - https://github.com/ayghri/i-have-adhd/blob/main/skills/i-have-adhd/SKILL.md — "Pre-send check": each output rule paired with a pre-emit self-check predicate and its fix +- https://code.claude.com/docs/en/hooks — PreToolUse hook input carries `transcript_path` (the session's JSONL transcript) and `tool_input`; exit code 2 blocks the tool call and shows stderr to the model +- https://github.com/choiyounggi/dev-loop/pull/144 with `hooks/orchestrate-ask-gate.sh` (https://github.com/choiyounggi/dev-loop/blob/main/hooks/orchestrate-ask-gate.sh) — field reproduction 2026-08-25: PR #136 introduced the gate wording ("ask in one numbered round") without naming the tool and Gate 1 degraded to prose questions; #144 named `AskUserQuestion` in the skill text and added a PreToolUse(Bash) gate that scans the transcript for that tool call before the first worker launch (27 bats cases; 744 tests green) diff --git a/wiki/backend/index.md b/wiki/backend/index.md index 7f86f69..90b9e9d 100644 --- a/wiki/backend/index.md +++ b/wiki/backend/index.md @@ -95,7 +95,7 @@ Match your situation to a "load when" line; load only matching pages. |------|-----------| | [completion-response-validation](common/llm/completion-response-validation.md) | Consuming OpenAI-compatible `/chat/completions` output as a final artifact (summary, document, notification); LLM responses coming back empty or truncated while HTTP status is 200; a reasoning-family model may be routed onto the alias you call | | [context-window-budget](common/llm/context-window-budget.md) | Repointing an LLM client or agent CLI at a different model, a self-hosted server (vLLM/Ollama), or a gateway (LiteLLM); setting `max_tokens` for a client whose default was sized for a larger model; the first request after such a switch returns 400 with a context-window error; deciding where to set the cap (request body vs client env var vs gateway config) and how to point the base URL at a proxy; handling truncation that arrives as a normal 200 | -| [binding-instructions-for-agents](common/llm/binding-instructions-for-agents.md) | Authoring or editing a skill, system prompt, hook message, or CLAUDE.md/AGENTS.md rule that must change an agent's behavior under pressure; an instruction keeps getting rationalized around; choosing between prohibition, recipe, template-slot, and predicate-rule forms; writing a skill's description/trigger line; adding a pre-send self-check to an output-shape rule | +| [binding-instructions-for-agents](common/llm/binding-instructions-for-agents.md) | Authoring or editing a skill, system prompt, hook message, or CLAUDE.md/AGENTS.md rule that must change an agent's behavior under pressure; an instruction keeps getting rationalized around; choosing between prohibition, recipe, template-slot, and predicate-rule forms; writing a skill's description/trigger line; adding a pre-send self-check to an output-shape rule; an instruction said "ask the user" and the agent asked in prose instead of through the question tool | | [progressive-disclosure-artifacts](common/llm/progressive-disclosure-artifacts.md) | Authoring a skill or reference bundle that packages a large corpus (a book, manual, wiki) for an agent to consult on demand; an always-loaded skill file grew past a few thousand tokens; sizing per-chunk token budgets by content type and usage depth; an agent Reads an entire large source file to answer narrow questions | ### ml @@ -112,6 +112,7 @@ Match your situation to a "load when" line; load only matching pages. | [consumer-required-fields](common/integrations/consumer-required-fields.md) | Writing an adapter that maps one module's records into the payload a second module (scoring engine, plugin, external client) consumes, with the target shape taken from a docstring, README example, or sample payload; such an adapter runs end to end with no error and the downstream numbers come out low; deciding which mapped fields need their own assertion | | [robots-txt-and-source-selection](common/integrations/robots-txt-and-source-selection.md) | Choosing which site to fetch a published dataset from and reading its robots.txt to decide whether your client may crawl it; the file contains a `Disallow: /` and you are deciding whose group it belongs to; setting the crawler's User-Agent and checking that token against the file; robots.txt returned a non-200 status; the origin restricts your token and you are looking for a portal that republishes the same records | | [estimate-derived-thresholds](common/integrations/estimate-derived-thresholds.md) | Submitting an action to an external system whose actual outcome can differ from the decision-time estimate (market order fill vs quote) while persisting absolute trigger values derived from that estimate (SL/TP prices, alert thresholds); derived triggers fire immediately or at the wrong level right after the action confirms; choosing where to recompute them from the actual outcome | +| [contact-details-from-scraped-pages](common/integrations/contact-details-from-scraped-pages.md) | Extracting phone numbers, addresses, or messenger handles from company websites through a scraper with a "main content only" mode (Firecrawl `onlyMainContent`); the run returns clean text with few or no numbers; deciding whether to add a JavaScript-rendering fallback; choosing between `tel:`/messenger `href` extraction and text regex | ### storage diff --git a/wiki/debugging/index.md b/wiki/debugging/index.md index 521e247..b88dc1f 100644 --- a/wiki/debugging/index.md +++ b/wiki/debugging/index.md @@ -9,7 +9,7 @@ Match your situation to a "load when" line; load only matching pages. | Page | Load when | |------|-----------| -| [reproduce-first](methodology/reproduce-first.md) | A bug is reported or behavior is wrong and you are about to investigate or fix; deciding what to capture when full reproduction is impossible (prod-only, timing-dependent, one-off crash) | +| [reproduce-first](methodology/reproduce-first.md) | A bug is reported or behavior is wrong and you are about to investigate or fix; deciding what to capture when full reproduction is impossible (prod-only, timing-dependent, one-off crash); a script's command pasted into an interactive shell fails to reproduce the script's bug; a green test is about to be cited as proof a stage is healthy | | [isolate-by-bisection](methodology/isolate-by-bisection.md) | A bug reproduces but its location is unknown; it worked before / works in env A but not env B / fails with one input but not another — binary-searching versions (git bisect), code paths, data, or environment diffs | | [hypothesis-testing](methodology/hypothesis-testing.md) | You have a suspect cause and are about to "try a fix"; several suspects compete and you must pick what to test next; verifying that a fix that "worked" actually addressed the mechanism | | [probe-path-vs-operation-path](methodology/probe-path-vs-operation-path.md) | A precondition probe (login status, health, connectivity) reports success while the operation it gates fails with an auth/permission error; a browser page-load login check gates direct API calls made with stored cookies; deciding what a preflight probe must exercise under refresh-token cookie auth | diff --git a/wiki/debugging/methodology/reproduce-first.md b/wiki/debugging/methodology/reproduce-first.md index 3f16748..4e819f2 100644 --- a/wiki/debugging/methodology/reproduce-first.md +++ b/wiki/debugging/methodology/reproduce-first.md @@ -9,8 +9,12 @@ sources: - https://www.debuggingbook.org/html/DeltaDebugger.html - https://sre.google/sre-book/effective-troubleshooting/ - https://github.com/mattpocock/skills/blob/main/skills/engineering/diagnosing-bugs/SKILL.md -last_verified: 2026-08-24 -related: [debugging-methodology-hypothesis-testing, debugging-concurrency-intermittent-failures, debugging-signals-logs-and-correlation] + - https://www.gnu.org/software/bash/manual/html_node/Aliases.html + - https://www.gnu.org/software/bash/manual/html_node/Bash-Startup-Files.html + - https://zsh.sourceforge.io/Doc/Release/Files.html + - https://en.wikipedia.org/wiki/Regression_testing +last_verified: 2026-09-06 +related: [debugging-methodology-hypothesis-testing, debugging-concurrency-intermittent-failures, debugging-signals-logs-and-correlation, platforms-environment-path-resolution, qa-process-completion-claims] --- # Building a Reproduction Before Investigating a Bug @@ -83,6 +87,8 @@ When a full local reproduction is impossible, capture evidence instead: | Bug reproduces only on the reporter's machine | Diff the two environments one variable at a time — versions, locale, config — moving your environment toward theirs until it fails ([debugging-methodology-isolate-by-bisection]) | | Prod-only failure with no visible error — the client swallows it (a `.catch()` that ignores, an empty error handler) and the action just "does nothing" | Grep the production service logs for the endpoint path before reading more code: from the UI a 500 and a no-op are indistinguishable, and one server-side exception line kills whole families of hypotheses that local code reading cannot ([debugging-signals-logs-and-correlation]) | | No loop can be built after working down the whole construction ladder | Stop before forming hypotheses: state that plainly, list what was tried, and ask for one of — environment access, a redacted artifact, or temporary instrumentation shipped to capture the next occurrence | +| The bug is in a shell script (or a command it runs) and the command pasted into your interactive shell does not reproduce it — or reproduces the opposite | Run it the way production runs it: `sh -c '…'` for a `#!/bin/sh` script, `bash script.sh` for the script itself, `env -i sh -c '…'` for a daemon or CI context — then compare `command -v ` / `type ` in both contexts. An interactive shell expands aliases and has loaded `~/.zshrc`/`~/.bashrc`; a non-interactive `sh` does neither, so a bare name such as `grep` can resolve to a different program with different regex semantics ([platforms-environment-path-resolution]) | +| A green test already covers the failing path and you are about to cite it as "that stage is healthy" | Read where its last assertion sits relative to the symptom: a test that waits for events 1–2 says nothing about event 3. When the assertions stop before the symptom, build the reproduction that asserts past it (step 3) before trusting the stage ([qa-process-completion-claims]) | ## Instead of @@ -91,6 +97,8 @@ When a full local reproduction is impossible, capture evidence instead: | Write a fix based on the report's stated cause | Reproduce first, then diagnose from the reproduction | The reporter's diagnosis is an untested hypothesis; a fix for an unreproduced bug cannot be verified | | Verify the fix only against the full original scenario | Keep the minimal reproduction as an automated test and run it against the fix | The full scenario can pass for unrelated reasons; the minimal repro checks the exact failing mechanism | | Keep a 40-step reproduction because it "works" | Shrink until every remaining step is required | Every removable step is noise that widens the search space for the cause | +| Paste a failing script's command into your terminal to reproduce it | Invoke it under the script's own interpreter (`sh -c`, or run the script) and diff `command -v` between the two contexts | The interactive shell's aliases and rc-set `PATH` can pick a different binary than the script's `sh` does, giving a false "works for me" or a false failure | +| Treat an existing passing test as the reproduction | Check its assertion boundary against the symptom; add the assertion past the symptom | A test that stops asserting before the failure point is green on the broken build | ## Sources @@ -99,3 +107,6 @@ When a full local reproduction is impossible, capture evidence instead: - https://sre.google/sre-book/effective-troubleshooting/ — "simplify and reduce"; reproduction as the basis of diagnosis - Field context 2026-08 (silent-swallow row, field-tested): a prod-only bookmark bug where backend code, proxy, and browser click were all verified normal from the outside; one `journalctl | grep bookmark` surfaced PostgreSQL's "no unique or exclusion constraint matching the ON CONFLICT specification", pinning the cause to a deployed DB left on an old schema — a cause invisible in the repo's code - https://github.com/mattpocock/skills/blob/main/skills/engineering/diagnosing-bugs/SKILL.md — feedback-loop-first debugging discipline: the red-capable/deterministic/fast/agent-runnable loop criteria, the construction ladder, the tighten step, and the higher-reproduction-rate directive for non-deterministic bugs +- https://www.gnu.org/software/bash/manual/html_node/Aliases.html — "Aliases are not expanded when the shell is not interactive"; https://www.gnu.org/software/bash/manual/html_node/Bash-Startup-Files.html — a non-interactive bash reads only `$BASH_ENV`, not `~/.bashrc`; https://zsh.sourceforge.io/Doc/Release/Files.html — `.zshrc` is read only "if the shell is interactive" +- Field reproduction 2026-08-25 (dev-loop issue #145, `t1-detect`, macOS): `printf … \| LC_ALL=C grep -n '^[[:space:]]*─\{3,\}[[:space:]]*$'` typed into the interactive zsh matched 2 lines (`grep` resolved to `ugrep`); the same pipeline under `sh -c` matched 0 (`grep` resolved to `/usr/bin/grep`, byte-oriented under `LC_ALL=C`) — the production bug, invisible from the interactive shell +- https://en.wikipedia.org/wiki/Regression_testing — "when a bug is located and fixed, to record a test that exposes the bug and re-run that test regularly"; https://github.com/choiyounggi/linkly-crew/pull/10 — field reproduction 2026-09-02: the existing pump test (`core.rs:426-431`) awaited `RunStarted` + `SpecReady` and was cited as "core is fine" while the app stopped right after `SpecReady`; a new test asserting `TaskStateChanged` and a message after `SpecReady` reproduced the stall diff --git a/wiki/frontend/design/pointer-attracted-particle-fields.md b/wiki/frontend/design/pointer-attracted-particle-fields.md new file mode 100644 index 0000000..c55ef98 --- /dev/null +++ b/wiki/frontend/design/pointer-attracted-particle-fields.md @@ -0,0 +1,73 @@ +--- +id: frontend-design-pointer-attracted-particle-fields +domain: frontend +category: design +applies_to: [canvas, general] +confidence: verified +sources: + - https://en.wikipedia.org/wiki/Banach_fixed-point_theorem + - https://developer.mozilla.org/en-US/docs/Web/API/Element/pointerleave_event + - https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame + - https://github.com/choiyounggi/cover-letter +last_verified: 2026-09-06 +related: [frontend-design-anti-slop-visual-design, frontend-design-html-in-canvas, testing-quality-tests-that-cannot-fail] +--- + +# A Pointer-Attracted Particle Field Whose Nodes Must Not Pile Up + +## When this applies + +Writing or reviewing a canvas/WebGL background (particle network, constellation, +dot grid) whose per-frame update moves each node a fixed fraction of the +remaining distance toward the pointer (`p += (cursor - p) * k`); the effect +"clumps" after the visitor rests the mouse or scrolls away; a plan says "pull +N % per frame toward the cursor" and nothing else. + +## Do this + +1. **Read the rule as a contraction before shipping it.** With a stationary + target, `p ← p + k·(t − p)` with `0 < k < 1` is a contraction whose only + fixed point is `t`: every node within the pull radius converges onto the + cursor position and stays there. A small random drift only sets the clump's + radius (about drift ÷ k) — it does not restore the field. Two guards are + required, and a review asks for both: + +| Guard | Do | +|-------|----| +| Release | Store the pointer in canvas-local coordinates and set it to `null` when it falls outside `[0,w]×[0,h]` (or on the section's `pointerleave`). Without release, a cursor parked past the canvas edge, or off-screen while the page is scrolled, keeps pulling | +| Floor | Define an inner radius `r₀` and pull only while `r₀ < d < R`; clamp the step to `min(d·k, d − r₀)` so one step cannot cross the floor. Nodes then hold at `r₀` instead of stacking | + +2. **Demand the many-step regression test.** A pure-function test that steps + the update 1000× with a resting pointer and asserts `distance ≥ r₀` for + every node is the assertion that separates "pulls toward" from "collapses + onto"; a one-frame test passes both. Add a boundary case (a node already + inside `r₀` is untouched) and a component case (an in-bounds then + out-of-bounds `pointermove` delivers `null` to the next step). Show each + test red with its guard reverted ([testing-quality-tests-that-cannot-fail]). +3. **Make resume safe by construction.** `requestAnimationFrame` pauses in + background tabs, so the loop resumes later with whatever pointer it stored; + release (guard 1) is what makes the resumed state harmless. + +## Edge cases + +| Case | Then | +|------|------| +| Touch devices — no `pointermove` stream, one tap sets a pointer | Clear the pointer on `pointerup`/`pointercancel` as well, or skip the attraction for coarse pointers | +| The pull should feel springy rather than damped | A spring (`v += (t − p)·k; p += v; v *= damping`) overshoots but has the same fixed point — it still needs the floor and the release | +| Several attractors (multi-touch, decorative anchors) | Apply the floor per attractor and clamp the combined step against the nearest attractor's `r₀`; two individually clamped pulls can still sum past one floor | +| `prefers-reduced-motion: reduce` | Keep the drift, drop the pointer pull — the attraction is the motion the preference asks to remove | + +## Instead of + +| If you are about to | Do this instead | Why | +|---------------------|-----------------|-----| +| Approve "pull 2 % per frame toward the cursor" as a complete spec | Ask for the release condition and the inner radius and write them into the plan | The rule as stated converges every node in range onto one point | +| Test the pull with a single step call | Step it hundreds of times with a fixed pointer and assert the distance floor | Collapse is a limit behavior; one step cannot observe it | +| Store the pointer in page coordinates from a `window` listener | Convert to canvas-local coordinates and null it when out of bounds | Page coordinates outside the canvas still read as a valid attractor | + +## Sources + +- https://en.wikipedia.org/wiki/Banach_fixed-point_theorem — a contraction mapping "admits a unique fixed point" and the iterates `xₙ = T(xₙ₋₁)` converge to it +- https://developer.mozilla.org/en-US/docs/Web/API/Element/pointerleave_event — "fired when a pointing device is moved out of the hit test boundaries of an element" +- https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame — callbacks are "paused in most browsers when running in background tabs or hidden `