chore(memory): promote strong-fit tasks-and-targets drafts from memory-pipeline for review - #123
Conversation
…, CLI, and CI workflow (#128) * feat(#108): add pipeline directory scaffolding and types - Create agent-memory/_pending/<domain>/ for all 8 CHT domains - Add empty agent-memory/_skipped.ndjson for filter audit log - Define pipeline types in src/types/pipeline.ts: ScrapedPR, LinkedIssue, ReviewComment, FilterDecision, SkipLogEntry, ScraperError * feat(#108): implement PR scraper using gh CLI Synchronous scraper that collects structured data from medic/cht-core PRs for the memory distillation pipeline. - Fetches PR metadata, diff, review summaries, and linked issues - Uses execFileSync with array args (no shell injection risk) - 50 MB maxBuffer with ENOBUFS → ScraperError - Graceful fallback for org-membership (read:org not required) - Linked issues extracted via regex; GitHub sidebar links documented as a known limitation (requires GraphQL) - PENDING reviews filtered out - 22 unit tests, all coverage thresholds met * feat(#108): extend schema with provenance fields and add validator - Add 10 optional frontmatter fields to schema.json: source_pr, source_sha, distilled_at, reviewed_by, reviewed_at, confidence, entities, concepts, related_issues, stale - All new fields are optional (backward-compatible with existing entries) - Update TEMPLATE.md with commented stubs and field reference table - Add src/scripts/validate-schema.ts (gray-matter + ajv) to validate all agent-memory /**/*.md frontmatter; 30/30 existing entries pass - Add npm run validate-schema script - Fix YAML syntax error in 10729-smsparser-bugs-typos.md (unquoted colon) * fix(#108): extend linked-issue regex to match full GitHub issue URLs Bare keyword regex (e.g. "Fixes #123") missed the URL form used by some cht-core PRs ("Fixes https://github.com/…/issues/123"). Updated pattern captures both forms and deduplicates across them. Added 2 tests: URL extraction and bare+URL dedup. * feat(#108): add filter stage with deterministic rules and LLM triage Three-stage pipeline filter for ScrapedPR objects: - Stage 1: 5 deterministic skip rules (bot, revert, chore/docs/ci/build, lockfile-only, translation-only) - Stage 2: 3 deterministic distill rules (bug+issue+multiservice, feature+issue, shared-libs+multiconsumer) - Stage 3: LLM triage via ChatAnthropic with Zod structured output; graceful fallback to flag-for-human on failure or missing API key Also adds ScrapedPR.author field, FilterResult/FilterOptions types, and _skipped.ndjson audit log (append-only, skip/flag decisions only). DoD items verified: - All 5 skip rules + all 3 distill rules implemented and tested - touchesMultipleServices edge cases covered - LLM failure -> flag-for-human, no throw - skipLlm option bypasses LLM call - 211 tests passing, branches 89.24%, lines 99.29% * feat(#108): add OpenRouter support with Anthropic fallback to filter triage Switch LLM triage from hardcoded ChatAnthropic to a dual-provider setup: - OpenRouter (OPENROUTER_API_KEY) as primary via ChatOpenAI - Anthropic direct (ANTHROPIC_API_KEY) as fallback - Graceful flag-for-human when neither key is set Also extracts DEFAULT_TRIAGE_MODEL constant and caches the triage chain via a module-level singleton (getTriageChain) to avoid re-construction on every PR in a batch run. TRIAGE_MODEL env var overrides the default (anthropic/claude-haiku-4). * fix(#108): correct OpenRouter model ID and add smoke-test script 'anthropic/claude-haiku-4' is not a valid OpenRouter model ID — updated DEFAULT_TRIAGE_MODEL to 'anthropic/claude-haiku-4-5'. Also adds smoke-test.ts for manual end-to-end pipeline validation. * feat(#108): add distiller stage — LLM-powered knowledge draft generator Implements distillPR() which takes a ScrapedPR that passed the filter stage and produces a schema-valid markdown draft in agent-memory/_pending/<domain>/. - Structured LLM output via Zod (domain enum, required fields) with OpenRouter-primary / Anthropic fallback, matching filter.ts pattern - Deterministic markdown assembly in code; AJV validates frontmatter before the file is written - distillFn injection option for tests (no real API calls in test suite) - Graceful degradation: LLM failure → flag-for-human + _skipped.ndjson - 25 tests, 86.31% branch coverage, all thresholds met * refactor(#108): simplify distiller — fix bugs, extract shared constants, async I/O - Fix double-wrapped error prefix in distillPR catch block (was emitting "Distill LLM unavailable: Distill LLM unavailable: ..." on the no-key path) - Replace sync fs.*Sync calls with await fs.promises.* so batch processing does not block the event loop - Fix .slice-before-filter on review comments so non-empty reviews beyond index 2 are not silently dropped - Derive CHT_DOMAINS list in buildPrompt from the constant rather than a separate hardcoded string that could drift - Extract CHT_DOMAINS, DEFAULT_PIPELINE_LOG_PATH, DEFAULT_PIPELINE_OUTPUT_DIR to src/constants/index.ts; both filter.ts and distiller.ts now import them - Switch process.env bracket notation to dot notation in both scripts - Remove redundant `const parsed = matter` alias in distiller.spec.ts * feat(#108): extend smoke-test to run distiller stage end-to-end Adds distillPR call after a distill decision so smoke-test validates the full scrape → filter → distill pipeline in one run. * test: reproduce bug — second call with same draft content rejected as missing frontmatter * fix: replace gray-matter .matter property check with hasFrontmatter() string check gray-matter v4 caches parsed results in matter.cache[input]. When the same content string is parsed a second time the cached object is returned, which may not retain the 'matter' non-enumerable property. Checking the raw string with hasFrontmatter() before calling matter() is deterministic and cache- independent, making all apply-mode tests reliable. * fix: use dot notation for Record<string,unknown> property access * feat(#108): add OpenReviewOptions/ReviewPRResult types and open-review-pr npm script * docs(#108): document OPENROUTER_API_KEY, TRIAGE_MODEL, DISTILL_MODEL in .env.example * chore: update skipped log with smoke-test run entries * refactor(#108): extract shared schema-utils and clean up open-review-pr - Extract buildValidator, normalizeFrontmatter, hasFrontmatter, REPO_ROOT, SCHEMA_PATH into src/scripts/schema-utils.ts; import from both open-review-pr.ts and validate-schema.ts to eliminate duplication - Fix buggy parsed.matter check in validate-schema.ts to use hasFrontmatter() - Hoist buildValidator() call to module scope in open-review-pr.ts so schema is compiled once per process, not on every openReviewPR() invocation - Replace existsSync+readdirSync TOCTOU pattern with single readdirSync+catch - Deduplicate path.basename call in writeSkipEntry - Bound uniqueBranchName loop to 99 iterations with explicit throw on exhaust - Remove phase 1/2/3 what-not-why comments * feat(#108): add run-pipeline CLI, GitHub Actions workflow, and skipped log update - src/scripts/run-pipeline.ts: CLI entry point wiring Scraper → Filter → Distiller for single PR (--pr) or recent window (--since / default 24h) - .github/workflows/run-pipeline.yml: daily cron (06:00 UTC) + manual dispatch that runs the pipeline and commits knowledge drafts back to the branch - package.json: add run-pipeline npm script - agent-memory/_skipped.ndjson: update with latest smoke-test run entries * fix(#108): resolve CI lint failures — promote ajv/gray-matter to deps, fix auto-fixable errors - Move ajv, ajv-formats, gray-matter from devDependencies to dependencies so n/no-unpublished-require passes for src/scripts/ files - Add missing ajv to dependencies (only ajv-formats was listed) - Run eslint --fix to correct dot-notation and indent violations - Remove unused callCount variable in open-review-pr.spec.ts - Remove invalid @typescript-eslint/no-throw-literal eslint-disable comments in scraper.spec.ts (rule was renamed in typescript-eslint v6+) * test(#108): add branch-coverage tests to meet 85% threshold - Add normalizeFrontmatter lastUpdated→last_updated aliasing test (covers schema-utils.ts lines 49-50) - Add non-Error LLM throw test in distillPR (covers distiller.ts line 275 false branch) - Add empty linkedIssues prompt test (issueContext false branch) - Add undefined prBody prompt test (null-coalescing branch) Branch coverage: 83.53% → 85.06% (279/328) * fix(#108): resolve all SonarCloud quality gate failures Cognitive complexity reductions (max 5 per function): - smoke-test: extract processPR helper (complexity 11→1) - run-pipeline: extract processSinglePR + errorMessage (12→3, 8→3) - open-review-pr: extract parseDraft, findValidDrafts, collectValidPlans, buildDryRunResults, promoteDomain, executeApply (26→1) - filter: extract runLlmTriage + use Set for labels (7→2, 6→3) - scraper: extract fetchMetadata, fetchDiff, fetchReviews (13→2) - validate-schema: extract processEntry + logFileResult (8→4, 9→2) Code quality warnings: - Replace child_process with node:child_process in 3 files - Use import matter from 'gray-matter' (remove unnecessary require assertions) - Use String#endsWith for bot check, RegExp#exec for match - Number.parseInt over parseInt throughout - String#replaceAll over regex replace (distiller, open-review-pr) - Flip negated condition in run-pipeline IIFE - String(fm.source_pr) to prevent [object Object] in template - files.toSorted() over files.sort() in validate-schema - Update scraper.spec.ts proxyquire key for node:child_process Coverage: 83.53% → 85.41% (281/329 branches) * fix(#108): resolve remaining 7 SonarCloud issues - smoke-test: extract filterAndDistill helper (processPR complexity 6→3) + prefer node:os import - filter: extract lockfile/translation booleans in checkSkipRules (7→5) + extract isBugWithLinkedIssueAndMultiService/isFeatureWithLinkedIssue/ isSharedLibsWithMultiService helpers for checkDistillRules (7→3) - open-review-pr: pre-compute sourcePrStr as typed string to prevent [object Object] template literal stringification - validate-schema: add localeCompare comparator to toSorted() * fix(#108): extract checkSkipRules helpers to eliminate && complexity (7→5) * fix(#108): resolve S5852 ReDoS in LOCKFILE_PATTERN regex Replace unbounded .* with [^/]* in the lockfile alternation so the wildcard cannot overlap the (?:^|/) anchor. Eliminates super-linear backtracking (50k-char non-match: ~0.07ms) while preserving match behavior across root-level and nested *.lock paths. * fix(#108): use mkdtempSync for smoke-test temp paths Resolves SonarCloud hotspots flagging predictable temp paths in a publicly-writable directory. mkdtempSync creates a per-run dir with a random name and 0700 permissions. * feat(#108): reconcile frontmatter schema with #87 (camelCase) Adopts #87's converged camelCase schema.json and schema-utils.ts verbatim so the two PRs share one frontmatter contract and the 30 committed agent-memory files validate natively (no lastUpdated->last_updated alias). Distiller changes: - emit camelCase frontmatter (lastUpdated, issueNumber, issueUrl) plus the now-required services and techStack fields - derive issueNumber/issueUrl/id from the first linked issue, falling back to the PR number when the PR closes no issue - serialize frontmatter with js-yaml (correct quoting; fixes unquoted title) and AJV-validate every draft before writing — malformed drafts route to _skipped.ndjson as flag-for-human instead of landing in _pending/ Addresses the review's blocking schema-reconciliation item. * chore(#108): reset _skipped.ndjson to empty (drop dev-run log noise) * docs(#108): document OpenRouter provider-agnostic rationale * fix(#108): address review nits in pipeline stages - filter: narrow LOCKFILE_PATTERN to known lockfile names (no longer skips unrelated *.lock files); pattern is now fully literal (no ReDoS surface) - filter: drop redundant inner try/catch in llmTriage; runLlmTriage is the single error boundary (covers default + injected triageFn paths) - scraper: drop unresolvable linked issues instead of returning empty stubs, so a 404'd reference can't flip a filter skip into a distill - scraper: wrap metadata/reviews JSON.parse in ScraperError (carries prNumber) instead of leaking a raw SyntaxError - distiller: slugify falls back to 'untitled' for symbol-only/non-Latin titles - open-review-pr: escape Markdown special chars in draft titles in the PR body - tests: relabel mislabeled single-service filter test; add coverage for all of the above (359 passing) * fix(#108): address secondary review follow-ups 1-3 - scraper: fetch reviews with `--paginate --slurp` and flatten the array-of-pages instead of string-stitching `[...][...]` — no longer corrupts review bodies containing `] [` or breaks on empty pages - open-review-pr: isolate promotion per domain — a domain failure is recorded as a 'failed' ReviewPRResult and the rest still run; clean up the orphan remote branch when PR creation fails after push - filter tests: stub both LLM providers (ChatOpenAI + ChatAnthropic) and clear both API keys per-test, removing cross-spec env leakage; add an explicit OpenRouter primary-path test 363 passing, 86% branch coverage * fix(#108): address secondary review follow-ups 4-7 - run-pipeline: validate --since/--pr as positive integers (error instead of silently processing nothing); guard the CLI IIFE behind require.main so the module is importable, and cover its orchestration functions (now 100%). - scraper: handle reviews from deleted GitHub accounts (user: null) by falling back to the 'ghost' author instead of dereferencing null. - open-review-pr: resolve source_pr 'owner/repo#N' refs to working /pull/N URLs via a new sourcePrUrl helper, instead of a dead repo-home anchor. - package.json: add the smoke-test npm script. Tests: 380 passing; coverage 97.21% stmts / 86.65% branch (>= 95% gate). * fix(#108): reject partially-numeric --since/--pr (roborev MEDIUM) Number.parseInt silently truncated '123abc' -> 123 and '1.5' -> 1, so partially-numeric flag values passed the positive-integer check. Validate the raw string with /^[1-9]\d*$/ before parsing, via a parsePositiveIntArg helper. Adds regression tests for trailing characters and decimals. * fix(#108): reject oversized --since/--pr values (roborev LOW) A digit string large enough to overflow Number.parseInt passed the regex but produced a non-safe-integer (and an invalid lookback date downstream). Parse with Number() and require Number.isSafeInteger. Adds a regression test. * refactor(#108): clear SonarCloud findings on PR #109 new code - run-pipeline: use Number.NaN over NaN (S7773); throw TypeError for the argument-validation guard (S7786). - open-review-pr: use String.raw for the Markdown-escape replacement (S7780); RegExp.exec() over String#match in sourcePrUrl (S6594); reduce cognitive complexity of promoteDomain and executeApply (S3776) by extracting stageDrafts, deleteRemoteBranch, and promoteDomainSafely helpers. Behavior unchanged; 384 passing, coverage 97.2% (>= 95% gate). * fix(#108): restore missing JSDoc opening tag in constants/index.ts A `/**` line was dropped (likely during rebase), leaving the comment body as bare tokens that TypeScript parsed as code — causing 30+ TS errors and the "Build, lint, and test" CI failure.
|
Heads up, this one currently has merge conflicts with its base and will need a rebase before it can merge. |
1979c05 to
6a2742a
Compare
sugat009
left a comment
There was a problem hiding this comment.
Content review. Prose fidelity is high (8772 reproduces the real 283s->63s benchmark exactly). Blockers:
- issue (blocking): identity keys record the PR number on 15 of 35 drafts (verified);
8838closes no tracked issue. - issue (blocking): hallucinated identity on
9232.id/issueNumber/issueUrl= 137, but 137 ismedic/care-teams#137(a cross-repo link in the PR body) mis-stamped as cht-core; cht-core#137 is the unrelated "Restyle data records". PR #9232 actually closes #9231. - issue (blocking): 3 duplicate clusters (8 files): issue 9231 = [9232, 9282, 9317], 9431 = [9486, 9549], 9552 = [9553, 9555, 9569, 9570].
- issue:
related_issues: []empty on every draft;domainFit: strongon every draft. Forced picks:10390/10432(cht-datasource data-layer, tied to tasks only via the target docs),8932(touchestasks.component.tsandcontacts.component.tsequally),10786(telemetry +replications.js/purger.jspipeline). - nitpick: process-narrative leaks into prose on ~11-18 of 35 drafts (named reviewers, "CI green, all 47 checks pass", "the human reverted an AI change").
Clean bill: no secrets, no PII, schema 100% valid.
…ly, tasks-and-targets) Deterministic relink via #129's relink-issues tool: drafts whose identity keys recorded the merge PR now point at the resolved issue. Frontmatter id/issueNumber/issueUrl lines only; bodies untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ets) Per sugat009's review on #123: fix the hallucinated identity on 9232 — its stored key was medic/care-teams#137 mis-stamped as cht-core; the memory is now keyed cht-core-9231 (PR title scope) with the care-teams Related Issues lines dropped. Collapse the 3 clusters to one memory per issue with source_prs[]: 9231 (9232+9282+9317 sequential facets), 9431 (9486 + 4.13.x sibling 9549), and 9552 (9553+9555+9569+9570 — the two distinct sub-fixes attributed separately, backport lines noted). Drop 8838 (closes no tracked issue — skip-and-flag policy). Cross-domain dedup: 9099 (#6543 facet) moves to the authentication canonical; 10432 (#10344) to the contacts corpus's existing memory; 9975 (#9974) to the forms corpus's existing memory — refs recorded there in touch-ups. Forced fits re-annotated weak (10390 datasource, 8932 cross-component, 10786 telemetry pipeline). Process narrative scrubbed from 11 files (named reviewers, CI-status and AI-change chronology). All 35 mappings verified (live cht-core API + the reviewer's own closingIssuesReferences audit for rate-limited cluster members); validate-schema 89/89; no duplicate issueNumbers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
80e45db to
1818c23
Compare
Companion to the forms (#122) and tasks (#123) seeders' cross-domain dedup — this corpus canonically owns their issues, so the PR provenance is recorded here: the #9835 memory gains the report-side PRs (#10022 ReportQualifier groundwork, #10246 reported_date fix), and the #10344 memory gains #10432 (targets-by-contact-id datasource support). The 10570 draft (#10509, attachments in contact forms) is removed: the forms corpus's curated 10509 memory owns that issue and now records PR #10570. validate-schema 91/91; no duplicate issueNumbers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ion) Companion to the tasks (#123) seeder's cross-domain dedup: this corpus canonically owns issue #6543 (multi-facility users), so the aggregate- targets facet from that seeder is recorded here — PR #9099 added to source_prs with a one-line account (aggregate targets gated off for multi-facility users). The canonical now carries all three facets: webapp display (#9094), v3 users API + authorization (#9126), and aggregate-targets gating (#9099). validate-schema 98/98; no duplicate issueNumbers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rts) Companion to the tasks (#123) seeder's cross-domain dedup: this corpus canonically owns issue #9974 (open contact edit form from task), so the duplicate draft dropped there is recorded here — PR #9975 added to source_prs with a one-line account of the shipped mechanism. validate-schema 95/95; no duplicate issueNumbers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
sugat009
left a comment
There was a problem hiding this comment.
Content re-review (post-rework). Identity/dedup clean (incl. the 9232→issue 9231 cross-repo fix). Remaining:
- issue (accuracy):
9553invents a backport version (inline). - issue (domain):
10390(target-intervals in cht-datasource) is library extension →data-access(inline).10432was relocated to contacts; same treatment applies. - nitpick (leakage):
10324,9650Domain Rationale scaffolding; strip.
Cross-cutting (whole promote batch, not blocking this PR alone): filenames still encode the PR number while frontmatter is issue-keyed, and two draft schemas coexist (machine-distilled vs hand-authored) — worth a cleanup pass. Full detail + re-runnable Verify commands are in my consolidated review notes.
The largest cross-cutting item, moving the cht-datasource library-extension drafts to a first-class
data-accessdomain + a weightedsecondaryDomains[]field, is in the detaileddata-accessproposal comment on #122.
Every factual claim in the twenty-five drafts on this branch was checked against the cht-core commit it was distilled from. 319 claims confirmed; 71 corrections applied. Each correction was re-checked by a second pass instructed to refute it. Two drafts on this branch contradicted each other, and the source settled both: BACKPORT LINE. 9553 said the fix "was backported to the 4.1.x line (PR #9555)" while its sibling 9486 said 4.13.x. The backport commit c8a7f13ad is titled "...for 4.13.x (#9555)" and is an ancestor of origin/4.13.x but not of origin/4.1.x. 9553 corrected; 9486 was already right. TASK ORDERING. 10362 said tasks are "ordered by due date and then priority"; 9980 said priority descending with due date as tie-break. The shared comparator in shared-libs/task-utils runs a priority cascade first and only reaches compareDates() when priorities are invalid or equal, so 9980 is right and 10362 was inverted. Corrected in 10362. 10362 was wrong in three further ways: the notifications are Android device notifications delivered through globalThis.medicmobile_android, not in-app ones, and are inert in a browser; the new service reads no NgRx state, only RulesEngineService.contactsMarkedAsDirty; and the ordering comparator was not new logic but an extraction of an existing private function out of the tasks reducer. Also corrected: 10772 described its e2e spec as added when the commit modified it (only the target config was added), plus prior-state overstatements, A/M status confusions and telemetry key inexactness across the branch. Left unedited deliberately: 10390's cht-datasource module names, which conflict with 10423's but cannot be checked because its own commit is unrecoverable; and 10436's Mocha-harness attribution, for the same reason. Six proposed corrections did not survive re-checking. 108 claims remain unverifiable, chiefly on drafts whose PR numbers appear nowhere in cht-core history. Nothing was edited on the strength of a claim that could not be checked. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every draft in the batch was edited by the relink, dedup and grounding commits without its stamp moving, so all 25 failed the stale-timestamp check. Stamped today rather than with each file's last content-change date: the stamp edit is itself a commit, so a content date fails on the very commit that sets it - the rule learned twice on the configuration and messaging branches. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ks-and-targets) 10324, 10371, 10436 and 10507 all merged into the 10140_previous-month-targets feature branch and reached master only in that epic's squash, #10423 (622c625427). The epic reshaped them on the way: - the e2e directory was renamed analytics/ -> targets/, so the tests/e2e/default/analytics/analytics.wdio-spec.js these drafts name is tests/e2e/default/targets/analytics.wdio-spec.js on master; - webapp/src/ts/libs/config.ts, created by 10507 and renamed by 10436, does not exist on master at all - only .mocharc.js survives of the mocha harness, and the subtitle handling now lives in rules-engine.service.ts; - RulesEngineService.fetchTargets() is spelled as a bare fetchTargets() on the service (rules-engine.service.ts:501) and really did gain the reporting-period argument this draft describes. Every path is accurate for its own PR, which is why grounding at the anchor passed them; they are wrong only as directions for a reader looking at master today. Each now says so, with the landed location where there is one. This is the drift class the probe was built for, arriving in bulk because five drafts share one epic. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…argets) Same class the reviewer identified on #120 and #130: the grounding pass corrected the sections that assert mechanism against code and left summary and Design Choices asserting what it had just disproved. Every pair below was found by check-coherence over two passes and then verified against cht-core; in each case the corrected body was right and the stale side was rewritten to match it. - 10362: summary said the service is 'wired into task state' and Design Choices said it consumes task state, while Code Patterns says it reads no NgRx state - it subscribes to contactsMarkedAsDirty and fetches docs. - 10480: summary said the counter mirrors the unread-count pattern; Code Patterns says the PR generalises that flow (setUnreadCount became setBubbleCounter). Testing credited the rules-engine integration test with 'the count computation'; the engine contributes the showTask predicate (index.js:117) and the count is computed in the webapp. - 9232: summary said the filter appears only for multi-facility users and single-facility users see no change; Solution says there is no facility-count term, so everyone with a facility list gets it. - 9553: summary said the fix reconciles state against the configuration; the body says isStale only checks the blob has targets and aggregate keys and never reads the configured targets. Solution also said turnover re-scopes/resets emissions; Code Patterns says they are preserved. - 9705: Design Choices claimed the fix throws on WRITE errors; the change is to a read catch-all, and the file's one bulkDocs still only console.errors. - 10623: Design Choices said the filters were reused rather than built; overdue-filter and task-type-filter are new components, both on master. - 8932: summary called the flashed text 'empty-state messages'; Root Cause shows they are end-of-list messages gated on has-items being true. - 10371: Design Choices claimed a new reusable telemetry test util; tests/utils/telemetry.js pre-existed and was modified. - 10324: summary said targets analytics had no way to review prior periods; Problem says target-aggregates already had that filter since #9317. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Real defects: - 9553 called target-accuracy.wdio-spec.js an added e2e spec; it was modified. Its migrateStaleState sentence is also attributed to #9553, but the symbol enters in the #9569/#9570 follow-up - it is absent at dc47c51e4 and present on master at target-state.js:124. - 9980 called tasks.spec.ts added; diff-tree says modified. Drift - true at the anchor, gone from master, now time-scoped: - 10362's shared-libs/task-utils/test/order-by-due-date-and-priority.js, removed by #10701. - 9232's analytics-target-aggregates-sidebar-filter component and spec, folded into the shared analytics sidebar filter by the same #10140 epic that reshaped four other drafts in this batch; its modules.module.ts, dissolved by the Angular 19 standalone migration (#9759); and the can_view_old_filter_and_search / can_view_old_action_bar permissions, both retired from master. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
My drift annotation landed on the entities entry rather than the Related Files line - replace(...,1) hits the frontmatter occurrence first - and the colon inside it made YAML parse the item as a map, so validate-schema went 88/1. Reverted to the bare path, annotation moved to Related Files where prose belongs. Same mistake as 10278 on the configuration branch; entities is machine-readable and any downstream consumer matching on paths would miss an annotated one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nd-targets) Both had resisted an earlier fix because I corrected one statement and left a third one standing - the same one-section-at-a-time failure this whole exercise is about. 9232 made three incompatible claims about what gates the filter UI. The summary said multi-facility users only; I corrected it to 'has a facility list at all', which was also wrong; Code Patterns still said 'only when facility_id resolves to multiple facilities'. What Solution actually documents is canDisplayFilterButton() gating on !isAdmin plus the legacy permissions being absent, with facility count controlling only the radio group inside the sidebar. All four statements now say that. 9553 said the interval-turnover migration fires 'when the persisted reporting interval no longer matches the current CalendarInterval' while Code Patterns says it reuses the shape check rather than comparing intervals. My previous edit fixed the emissions half of that sentence and left the trigger clause. Rewritten: load() runs the shape check on every hydration. Also time-scoped the two legacy permission constants where they are quoted, both retired from master. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both are real imprecision the exhaustive pass caught, not tool artifacts: - 10390 lists five mocha specs as added for the new datasource module. Three were; test/qualifier.spec.ts and test/index.spec.ts already existed and PR #10390 extended them. - 10786 says it 'added sentinel replications.spec.js and mocha purger.spec.js coverage'. The coverage was added, but both spec files pre-existed and were modified. Reworded to 'extended the existing'. Small, but the whole point of the corpus is that a reader can trust a file list, and 'added' versus 'extended' is exactly the sort of detail an agent would act on. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…-and-targets) 9705 recurred in all three passes, which is the signal that separates a real finding from sampling noise - and it was mine. My earlier summary said the catch-all 'swallowed every error into a synthesised default document'. The code at the anchor's parent is 'if (err.status === 404)': only a 404 produced a fresh doc, every other rejection fell through returning nothing. So the failure mode was worse than the summary claimed - neither a document nor an error - and the Root Cause section had it right all along. Summary and Design Choices now match it. 9486 said aggregation was 'only triggered when the user navigated to certain pages', which its own Root Cause refutes: a mark-contacts-dirty change-feed hook already existed pre-fix (confirmed at dc3ef42ab^). What was missing was aggregation and persistence, not the trigger. Reworded. 9553 named handleIntervalTurnover in three places with no temporal qualifier; #9714 removed interval turnover from master altogether, so the function is gone. Time-scoped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Code Patterns listed both key files and then appended '(change subscription + debounce)', which reads as applying to both. The Solution says db-sync changed only to make inProgressSync awaitable, and the diff agrees - three lines, no subscription. Reordered so each file carries its own parenthetical. Found by one coherence pass of three. By the protocol that is sampling noise rather than a robust finding, but a single-pass finding was real once before on this branch (9022), so they get read rather than dismissed. The other single-pass finding this round was a genuine model error: its own rationale said 'the first is not a contradiction of the second' and it reported the pair anyway - the documented limit of a gate that proves quotes exist, not that they conflict. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both single-pass findings, both real - which is why single-pass findings get read here rather than written off as sampling noise. 8772's summary says the short-circuit fires 'when the key count exceeds 500'. The guard is 'if (!params?.keys || params.keys.length < MAX_QUERY_KEYS) return' with MAX_QUERY_KEYS = 500, so it fires at 500 or more, not above 500. A reader implementing against this would put the boundary in the wrong place. 10423 calls shared-libs/cht-datasource/test/target.spec.ts a modified datasource target module; diff-tree at 622c625427 reports A. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
10423 wrote 'Added/updated mocha unit tests for ...' over a list of four files that diff-tree reports as A, A, A, A. The hedge made the extractor guess per file and made a reader unable to tell which. All four were added, so the sentence now says so. 10480's Design Choices says the bubble counts 'Overdue' and due 'Today' tasks, while the Solution said 'tasks due in the future are intentionally excluded' - and a task due later today is in the future. Reworded to 'due after today', with the due-today case stated explicitly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…argets) Repeated ground and coherence passes over the same twenty-five drafts, on the standing rule that recurrence proves a finding real while a single appearance proves nothing. Every item below was checked against cht-core or the issue text, not reasoned about. Wrong facts: - 10362 named the comparator `order-by-due-date-and-priority` in two sections. That is its test file; the export is orderByDueDateAndPriority. - 11142 put FreetextFilterComponent in the tasks module. It lives in components/filters/freetext-filter/ and the Tasks page reuses it. - 10436's Testing called two page objects and both e2e helpers new; diff-tree says both page objects were modified and only targets-helper-functions.js was added. - 10623's Code Patterns credited reports and messages with dropping the duplicated lineage filter and omitted tasks.component.ts, which dropped its own removeUserFacility call in the same PR. Summaries left behind by earlier corrections, the class this branch is supposed to be closing: - 9232 called a regression a missing feature. #9231 says multi-facility users could not view aggregate targets at all in 4.9.0. - 10480 said the unread-count flow was generalised to carry the task count. setBubbleCounter still carries only reports and messages; getBubbleCounter spreads those and adds a task count off another slice. - 8772 stated the short-circuit condition and then its inverse in the same parenthetical. Both true, unreadable together, and two passes read it as a contradiction. Tense, where a file did not survive the 10140 epic: - 10507 called libs/config.ts a helper it introduced and updated at once, and gave it a present-tense home the same draft denies. - 10436 named it twice more unscoped. - 10390 had no "paths are as of this PR" banner at all, despite ten of the twenty-one files its PR touched being target-interval.* names that exist nowhere in cht-core. It gets the banner; the vocabulary rewrite belongs to the data-access work. - 10371's Code Patterns implied one component records both open and selection telemetry. analytics-filter.component.ts records only :open. 10480 also now says why a task due today counts: the due date is parsed date-only, so it sits at midnight, and #3943 asks for exactly overdue and due-today. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- 10423's Testing listed eight test files under one "Added". Six were added; the two e2e WDIO specs already existed and were modified. Both ground passes agreed on this one. - 10324's Testing opened the same way and is worse: its PR adds no test file at all. Every spec named was modified, and the sidebar-filter spec was renamed rather than created. - 9553's summary blamed "a CHT upgrade that changes target configuration". Root Cause blames the #9486 persisted-shape change and the code agrees — isStale is `(state) => !state || !state.targets || !state.aggregate`, a shape check that never reads configuration. added-versus-modified is now five of the findings on this branch. It is caught only when extraction samples the sentence, because deciding it means scoping "Added X, Y and Z" across a coordinated list. A regex attempt at that flagged 126 of 164 mentions, including one sentence reading "(modified, not created)", so it was dropped in favour of running more passes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
verify-drafts compares lastUpdated against the file mtime; the previous commit landed after the date rolled over. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- 10362's Domain Rationale said the implementation "centers on the task pipeline (new task-notifications service, tasks reducer, ...)" while its own Solution says the reducer is not consumed by the service and was only changed to import a comparator. The reducer comes out of the list. Same class as the rest of this branch: an interpretive section left asserting what the grounding pass had already disproved. - 9232 wrote "added new translation keys (api/resources/translations/messages-en.properties)". The keys are new; the file is not, and the parenthetical reads as though it were. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Solution said the migrated emissions are rewrapped "so handleIntervalTurnover can read them against the active interval", while Design Choices said the same function writes the PREVIOUS interval's target doc. Design Choices is right. handleIntervalTurnover returns early when stateCalculatedAt falls inside the current interval, and otherwise aggregates against calendarInterval.getInterval(monthStartDate, stateCalculatedAt) — the interval the stale state belongs to — and stores that as the target doc. Reading against the active interval is the one thing it never does. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ymbols e1d0df4 corrected which interval handleIntervalTurnover reads, and in doing so named calendarInterval.getInterval and stateCalculatedAt — both real at the anchor, both gone from master because #9718 (also in this batch) removed the interval-turnover mechanism entirely. The drift check flagged them in three of the last four passes. One scoping clause covers the sentence and points the reader at the 9718 draft, which documents the removal. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Both round-2 items are fixed, and putting this branch through the same verification the configuration and messaging batches got turned up a class none of us had seen — one epic reshaped five of these drafts before it landed. Details below, worst first. Your two items9553's invented backport version — fixed, and every number in the replacement checks out: git -C $CORE log -1 --format='%h %s' c8a7f13
# c8a7f13 fix(#9552): handle rules-engine stale state after upgrade for 4.13.x (#9555)
git -C $CORE branch -r --contains c8a7f13 | grep -E 'origin/[0-9.]+x$' # origin/4.13.x only
curl -s https://api.github.com/repos/medic/cht-core/pulls/9555 | jq -r '.base.ref' # 4.13.xThe draft now also records why there is no 4.1.x backport: that line's tip is 10324 and 9650 Domain Rationale scaffolding — stripped. The thing worth your attention: one epic, five drafts
git -C $CORE ls-tree --name-only -r origin/master shared-libs/cht-datasource/src | grep target
# .../src/local/target.ts .../src/remote/target.ts .../src/target.ts ← no -interval
git -C $CORE log --all --diff-filter=A -- '*target-interval*' | wc -l # 0 — never on any branchThe four that stay here are now time-scoped with their landed locations. 10390 is not — its vocabulary needs a rewrite, not a patch, and I would rather flag that than half-fix it. It does now carry the same "paths are as of this PR" banner as the others, so nobody reads those ten names as directions to master in the meantime. See below. Everything else the pass foundTwelve self-contradictions across nine drafts, the same class you identified on #120: the grounding pass corrected the sections that assert mechanism against code and left Then five more that my own first round of fixes created — correcting one section and leaving a sibling saying the old thing, which is the failure mode this whole exercise is about, committed by the person fixing it. 9232 and 9553 each needed three passes before every section agreed. Then eighteen more across thirteen drafts in a final round of repeated passes, each checked against cht-core rather than reasoned about. Worth naming, because two were mine and several are classes I had not been testing for:
I also swept all three domains mechanically for any path a draft lists that is absent from All 25 timestamps were stale — the relink, dedup and grounding commits never bumped them. 10390 is left for the data-access workYour inline comment asks to move it to
Doing it here would mean three open PRs racing to add the same enum value, and a corpus where one extender moved while the other seven did not. There is now a second reason: 10390's entire file and API vocabulary needs rewriting to the landed Gate
Those are not single-pass numbers. On this exact content the gate ran four Two I also re-ran configuration and messaging under the corrected tooling to be sure none of the five tool fixes weakened an earlier verdict: configuration 99 grounded / 0 ungrounded, messaging 225 grounded / 0 ungrounded (its 27 unverifiable are the five anchorless drafts carved out of #120, unchanged). One of the fixes actually removed a false finding that had appeared on #120's branch — 10230's Four caveats I would rather state than have you find. The first is the one that matters.
|
sugat009
left a comment
There was a problem hiding this comment.
Round 4. The rework resolved the round-3 self-contradiction class and CI is green, so most of this branch is in good shape: 10279 and 10337 came back fully clean, the 9553 fabricated 4.1.x backport is gone, and leakage is stripped from 10324 and 9650.
Requesting changes on five items I verified myself against the PRs' own diffs and cht-core master. Four are blocking-ish and one is a label error an agent would grep for.
On triage, so this is not a wall of noise: I deliberately dropped about six items that were pure archaeology. Claims like which PR first added a symbol, or what a signature looked like at an old commit, are not worth fixing when the draft states today's reality correctly, and recording the old shape would actively mislead an agent trying to call it. Where a draft states a true-on-master fact with a historical verb ("gains X", "at this commit"), the only real fix is the verb, and I have not filed those. What I have filed is limited to: false on current master, semantically inverted, or fabricated.
One pattern worth naming. The two 9553 paragraphs are byte-identical to the pre-rework version, so 19 commits rewrote everything around them and never touched them. The 10436 and 10362 items are the mirror image of round 3: the pass verified that symbols exist but not that the attribution was right. A sweep of every "this PR added X" and "on master Y" sentence against gh pr view --json files and git grep origin/master would close the whole class.
Each was re-derived from the PRs' own diffs and cht-core master before editing,
not taken on the reviewer's word. All five checked out.
9553 - the symptom was inverted. Issue #9552 reports a crash, not wrong numbers:
`Object.keys(state.targets)` in aggregateStoredTargetEmissions throws
`TypeError: Cannot convert undefined or null to object` because a pre-#9486 blob
is a bare targets map. Interval detection was never the defect --
handleIntervalTurnover already did `moment(stateCalculatedAt).isBetween(...)` and
`calendarInterval.getInterval(...)` at fe795fb^, and #9569 added no interval logic
at all (+1 line in rules-state-store.js, +9/-4 in target-state.js, rest tests).
Title, summary, Problem, Root Cause, Solution, Testing and Related Issues now all
tell the crash story; the e2e case is named ('should handle old format of the
rules-state-store') instead of being called a configuration change.
10436 - the harness attribution was backwards. This PR *removed* the mocha pieces
#10507 had added: config.spec.ts +0/-103, tsconfig.mocha.json +0/-9, .mocharc.js
+0/-2, libs/config.ts +0/-26, all deletions. webapp/tsconfig.spec.json is the
webapp-root karma tsconfig, is on master, and only gained sinon-chai types here.
`getValueFromFunction` was deleted outright (zero-hit on master), not folded --
its role is now getReportingMonth in rules-engine.service.ts, which this PR added.
10362 - `orderByDueDateAndPriority` is not a task-utils export on master.
task-utils exports only setTaskState; the comparator is at reducers/tasks.ts:15
and the service imports it from @mm-reducers/tasks. #10701 moved it back, its
description saying task-utils is for report SMS tasks, not rules-engine tasks.
The draft now records the round trip and names master's location.
10324 - the radio labels are both wrong and the contrast is imaginary. Both
filters render `targets.this_month.subtitle` / `targets.last_month.subtitle` =
"This month" / "Last month"; "Previous month" is zero-hit in messages-en and is
only the ReportingPeriod.PREVIOUS enum value.
10371 - the telemetry segment is `:reporting-period`, matching
collectFilterSelectionTelemetry('reporting-period') and the key its own Solution
already quoted.
Two more on these same drafts, from sweeping the class rather than the instance.
10371 carried the identical epic-rename misattribution filed only against 10324:
the e2e analytics/ -> targets/ rename was #10480 (bed454652) on master, not the
#10423 squash. And 10324 credited itself with the sidebar's `telemetryKey` input,
which it never touches -- #10371 added it; 10324 added userFacilities and
showFacilityFilter.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ded it 10436's correction leaves 10507 as the only other draft describing webapp/src/ts/libs/config.ts, and it attributed the file's disappearance to "the epic" generically. It was #10436 specifically, later in the same epic, which also removed the two mocha pieces 10507 lists in Related Files. Naming the deleting PR makes the pair agree and gives a reader one hop to the whole story. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Re-running the full gate over the reworked branch surfaced these on drafts the round-4 review did not name. Each adjudicated against cht-core before rewriting. 9486 - three passes, because the first two fixes were themselves wrong. The summary said targets were computed "only ... when visiting specific pages", which its own Root Cause contradicts with two 120s ensure-freshness debounces (`ENSURE_FRESHNESS_SECS = 120` at dc3ef42ab^, used twice). Correcting that to "a 120s debounce" then contradicted the same line's "two separate" -- and correcting *that* left Problem claiming nothing ran on incoming changes while Root Cause said the hook called updateEmissionsFor per change. At dc3ef42ab^ that function only does `rulesStateStore.markDirty(contactIds)`: it invalidates, it does not recompute. Problem now says exactly that. 9705 - the Problem opened by locating the swallowed error on the write, while the summary, the next sentence, Root Cause, Solution and Design Choices all locate it on the read that precedes the write. The read is right; the opener is now neutral about which call failed. 10390 - Domain Rationale said "all changed files are shared-libs/cht-datasource plus one API controller", but the PR also changes api/src/routing.js, adds integration tests under the top-level tests/integration/ tree, and touches .mocharc.js and package.json. Reworded to state the scope without under-counting. 8932 - the title called the flashed strings "empty states" while Root Cause shows both are gated on the has-items flag being true (`hasTasks`, `hasContacts`), which makes them end-of-list messages, the opposite of an empty state. Title and the two tags follow. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Round 5 re-review request:All five items are fixed. Each was re-derived from the PRs' own diffs and cht-core Then I did the sweep you asked for in the review body — "every 'this PR added X' Your five10436, the mocha harness (blocking). You were right that the PR removed all of The replacement says where subtitle derivation actually lives, which is your 9553, the symptom and mechanism (blocking ×2). Both were inverted, and both git -C $CORE show 'dc47c51^:shared-libs/rules-engine/src/target-state.js' | grep -n 'Object.keys(state.targets)'
# 43, 67, 227 — a pre-#9486 bare map has no .targets, so Function.keys throws
git -C $CORE show fe795fb --stat | tail -6
# +1 rules-state-store.js, +13/-6 target-state.js, rest tests — no interval logic
git -C $CORE show 'fe795fb^:shared-libs/rules-engine/src/provider-wireup.js' | sed -n '313,332p'
# handleIntervalTurnover, isBetween(currentInterval...), getInterval(...) all already thereSo the turnover facet is the same shape bug: 10362, the comparator (blocking). Confirmed on master — 10324, the radio labels (blocking). Confirmed: both filters use 10371, the telemetry segment. The sweep, and what it caughtYour closing note was the useful part of the review, so I mechanised both halves Path half ( Attribution half ( On the fixed corpus the file-level screen returns 64 candidates and the symbol-level It did find one real instance of your class, on a draft you had already reviewed: 10324 credited itself with the sidebar's # 10324's additions to the component: userFacilities, showFacilityFilter — no telemetryKey
# 10371's additions: @Input() telemetryKey: string = 'target_aggregates';Fixed, and it now points at #10371. Worth flagging one thing the sweep surfaced that I deliberately did not Five more the gate found on drafts you did not name9486 took three passes because my first two fixes were each wrong in a new way 9705 — the Problem opened by putting the swallowed error on the write while 10390 — Domain Rationale said "all changed files are shared-libs/cht-datasource 8932 — the title called the flashed strings "empty states" while Root Cause 10371 — after the segment fix, the Solution read as though everything new was 10390 still stays putUnchanged from last round and for the same reasons: the taxonomy change is your own Gate
The sequence matters more than the totals. Across the round the gate ran 14 Two gate findings were the gate misreading itself, and they are worth naming The first changed the prose anyway. A pass flagged The second I left alone: one pass in fourteen flagged Two |
sugat009
left a comment
There was a problem hiding this comment.
Approving. All five items from review 4845979803 are fixed, and I re-verified each against the PRs' own diffs and cht-core master rather than taking the commit message's word for it.
- 9553 now tells the crash story throughout (title, summary, Problem, Root Cause, Related Issues):
TypeError: Cannot convert undefined or null to objectatObject.keysinaggregateStoredTargetEmissions, with the 4.12 to 4.13.x report. The turnover paragraph correctly states that detection pre-existed (handleIntervalTurnover'sisBetweenandcalendarInterval.getIntervalwere both present before #9569) and that #9569 added no interval logic, and it adds that #9718 has since removed the mechanism entirely. - 10436 states the deletions with exact counts and puts
webapp/tsconfig.spec.jsoncorrectly on master. - 10362, 10324, 10371 all corrected; 10324 now documents explicitly that "Previous month" is only the
ReportingPeriod.PREVIOUSenum value and never a UI string, which is the distinction that made the original wrong.
Two of these went past what I filed, which I want to credit specifically. On 10436 I could only establish that getValueFromFunction was zero-hit on master; you found the actual successor, and it checks out: getReportingMonth is at rules-engine.service.ts:588 on master and PR 10436's own diff adds it. On 10362 you recorded the full round trip (added to the reducer by #9980, moved to task-utils here, reversed by #10701) rather than just correcting master's location.
The self-found fixes in 58a6ca9 and 4ec1d12 are real precision gains rather than churn, and I found no new error introduced by them. The standout is 9486: the old summary said targets were only computed on page visits, and the correction that there were already two once-per-session 120s ensure-freshness debounces verifies exactly at the anchor's parent. ENSURE_FRESHNESS_SECS = 120, one debounce calling fetchTaskDocsForAllContacts() and one calling fetchTargets(), each armed once inside the isEnabled block and never re-armed. That is a claim I never flagged, caught by your own gate.
Also worth noting the method: re-deriving each item from primary sources instead of trusting the review is the right instinct, and it is what caught my own overreach earlier in this batch.
Pre-merge checks: CI green on all four at 58a6ca9, diff confined to agent-memory/, and schema.json is byte-identical to main now that #120 and #130 have merged, so it carries no duplicate change.
One open thread I am deliberately not blocking on. The 10390 domain question (cht-datasource work belonging in data-access) was parked on 2026-07-21 pending the domain existing. #152 now adds data-access and secondaryDomains, so that is answerable whenever you want to sequence it, and this PR's reword of 10390's rationale (naming the controller, the routing.js registration and the test wiring) makes the file set read as even more clearly library-centric. Happy for that to be a follow-up rather than a change here.
Resolving my stale threads on this PR since the underlying items are all addressed.
… we broke
The important one first.
**10071: an earlier commit on this branch replaced true claims with false ones.**
The sweep concluded that `createReport` was fabricated and that
`src/qualifier.ts` "was never part of this work", both checked against the
epic squash — because at that moment the child PR's own merge commit was not
in the clone. It is now, and it says the opposite:
git diff-tree --no-commit-id --name-status -r -M cab214534 d40e65bae7
# M shared-libs/cht-datasource/src/local/report.ts
# M shared-libs/cht-datasource/src/qualifier.ts
# M shared-libs/cht-datasource/test/local/report.spec.ts
# M shared-libs/cht-datasource/test/local/person.spec.ts
git show d40e65bae7:shared-libs/cht-datasource/src/local/report.ts | sed -n '81p'
# export const createReport = ({
`createReport` is real at this PR, taking a `ReportQualifier` and rejecting
`_rev`; the qualifier.ts change is one line, exporting `ReportQualifier` so
the adapter can name it. The #10083 squash then renamed the operation to
`Report.v1.create`, moved it into a `v1` namespace and replaced the qualifier
with `Input.v1.ReportInput` from a new `src/input.ts` — none of which is in
this PR's diff.
So the original draft was right and we corrected it into being wrong. The
draft now records both views and says which is which, the way the 10140 epic
children on #123 do. The flat statement "there is no `createReport` symbol
anywhere in cht-core's production code" is gone; it was false.
This is the exact laundering this exercise exists to prevent, and it happened
here because an anchor moved between passes: the clone acquired the child
commit mid-run, so passes before and after disagree about which tree to judge.
Worth knowing that an unresolvable anchor is not a stable property of a clone.
Also:
8336 cited ngo-create.xlsx as a regenerated fixture; it is `A`, the one
file the PR adds. Cites two genuinely regenerated fixtures instead.
8740 a second `relevant.js` in Design Choices, sibling of the one already
reworded — the same third-party path in the same draft, missed first
time because only one occurrence was quoted.
10756 said the widget parses `cht:unique_tel`. It parses the rendered
`data-cht-unique_tel`; `cht:unique_tel` is what pyxform emits into the
XForm instance.
9340 "behavior is selected from appearance … rather than the field type"
was too absolute. `_init` reads
`$wrapper.attr('data-cht-unique_tel') === 'true' || deprecated.isDeprecated($wrapper)`,
so the legacy shape still turns dup-checking on through the second
branch — which is why the draft can say legacy behaviour is preserved
without contradicting itself.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ontacts)
Every item was checked against the PR's own diff and cht-core master before
being changed, not taken on the review's word. All four held up.
**9281 -- the getAll AsyncGenerator was inverted.** The draft said the generator
yields pages and showed a nested loop. It yields individual docs:
git -C $CORE show refs/verify/pr9281:shared-libs/cht-datasource/src/libs/data-context.ts
# getDocumentStream ... : AsyncGenerator<T, void>
# for (const doc of docs.data) { yield doc; }
git -C $CORE show refs/verify/pr9281:.../test/libs/data-context.spec.ts
# 131: it('yields document one by one'
Rewritten to the flat `for await (const person of Person.v1.getAll(ctx)(q))`
shape. Two facts found while verifying and now recorded: the PR squash-merged
into the `9193-api-endpoints-for-getting-contacts-by-type` feature branch
(`bf8a77da`, not an ancestor of master) and reached master only via #9311
(`34dd0303c`); and the helper was renamed before landing -- at #9311's squash it
is already `getPagedGenerator` in `libs/core.ts`, with `getDocumentStream` absent
and the signature already `AsyncGenerator<Person, null>`. Time-scoped, not
silently corrected to master's shape. The same PR also swapped getPage's numeric
`skip` for a string `cursor` and moved it ahead of `limit`
(`- return fn(personType, limit, skip)` / `+ return fn(personType, cursor, limit)`).
**10043 / 10057 / 9266 / 9281 / 9835 -- data-access, deferred with disclosure.**
The reviewer's own "extend vs use" rule is satisfied: all four anchor PRs touch
`shared-libs/cht-datasource` and nothing else (`git diff-tree --name-only` per
squash). Deferred per the reviewer's own sequencing on #122 -- "one coordinated
schema/taxonomy PR ... Not blocking any single PR" -- and the #123 precedent.
`data-access` is not a valid `domain` today (`agent-memory/schema.json` CHTDomain
enum holds 9 values, none of them it) and PR #152 adds it, open and unmerged, so
re-keying here would race #152 for the same enum value. Each of the five now says
so in its own text rather than leaving the reader to infer it.
**9007 -- Domain Rationale leakage stripped.** "Per the infrastructure pitfall"
is classifier scaffolding; replaced with the substantive reason. While verifying,
the vague `page_size` prose was pinned to the real constant:
`- private readonly PAGE_SIZE = 50;` / `+ private readonly PAGE_SIZE = 25;`.
Also dropped "verified with a manual quick test" -- PR #9007's body has an
entirely unchecked review checklist and says nothing about manual testing.
**9915 -- the dropped attribution, justified rather than restored.** Round 2
reworded "Reviewer verified the correct workflow (xlsx edit -> xml regeneration)
was followed" to drop "Reviewer". Restoring it would re-assert something the diff
contradicts: of PR #9924's 29 changed `.xml` files only 17 have a same-named
`.xlsx` beside them; the other 12 are the place create/edit forms, expanded from
4 shared `PLACE_TYPE-*.xlsx` templates and edited directly. The section now
states what is checkable from the diff. Counts corrected against the real file
list (50 files, all M -- 29 xml / 21 xlsx; default/app 10->11, covid-19/contact
6->8).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ntacts) Closes the loose end from #123's review, which said `10432` "was relocated to contacts". It is here -- as `10344-targets-by-contact-id-cht-datasource.md`, keyed by the issue (#10344) rather than the PR (#10432), which is why looking for a `10432-*` file finds nothing. Nothing was dropped. What it needed was scoping, because PR #10432 never merged: git -C $CORE merge-base --is-ancestor refs/verify/pr10432 origin/master; echo $? # 1 git -C $CORE grep -c byContactUuids origin/master # no output git -C $CORE grep -lc byContactUuids refs/verify/pr10432 # 12 files The draft asserted all of it as shipped behaviour. It now opens with a banner saying otherwise and carries `stale: true`. Two claims in the first draft of that banner were wrong and are fixed here: - It said no commit in cht-core history references the PR. One does -- `db9694ef0 feat(#10344): support targets by contact id in cht-datasource (#10432)` -- it is simply not reachable from master. Stated that way now. - It listed `bindGenerator()` among symbols existing "only on that open PR's branch". `bindGenerator` is on master in six files, added by epic #10423 (`622c62542`); #10432 introduces its own independently, the epic not being an ancestor of the PR. Master's `target-aggregates.service.ts:35` binds `Target.v1.getAll`, not the `TargetInterval.v1.getAll` this draft names. So the summary's flat "None of this API exists on master" was also too strong. Code Patterns and Design Choices credited this proposal with `bindGenerator`; both now point at #10423. The contact-UUID filtering vocabulary really is PR-only, and the epic really does lack it (`git grep -c byContactUuids 622c62542` -> 0), so the rest of the banner stands. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rounds 3-5 on #123 found more defects outside the reviewer's list than in it, so all 37 drafts were swept for the same classes. Every finding below was settled against the anchor PR's own diff or `origin/master`, never reasoned about. `9264`, `9390` and `10713` came back clean and are untouched. Worst first. **9230 -- the whole draft was polarity-inverted.** It said leftover action-bar logic *prevented* editing a home place and the fix *restored* it. The real bug is the opposite: the Edit button was wrongly *enabled*. git -C $CORE show 9f900220 -- .../contacts-content.component.ts # - canEdit: ... this.userSettings?.facility_id !== this.selectedContact?.doc?._id, # + canEdit: ... !this.userSettings?.facility_id?.includes(...doc?._id), # issue #9229: "Old action bar should PREVENT users with multiple facilities # assigned from editing the homeplace" Once `facility_id` became an array, `['x'] !== 'x'` is always true, so `canEdit` was always true. Title, summary, Problem, Root Cause, Solution, Design Choices, Domain Rationale and the `tags` all carried the inversion; all were rewritten together. Also time-scoped: the action bar and this `canEdit` block were removed from master by #9361, so `stale: true`. **8684 -- describes a feature that is not on master at all.** `stale: false` was the most damaging assertion in the corpus. git -C $CORE merge-base --is-ancestor 59a1dbd2 origin/master; echo $? # 1 git -C $CORE branch -a --contains 59a1dbd2 # 4.4.1-FR-barcode, 4-4-cares, ... for s in search_by_barcode BarcodeDetector can_use_barcode_scanner; do git -C $CORE grep -l $s origin/master | wc -l; done # 0 0 0 PR #8684 merged into the `4.4.1-FR-barcode` release branch and issue #6669 is still open. Now `stale: true` with the landing recorded. Two more: it credited itself with `browser-detector.service.ts` (`M` here; added by #7568 in 2022, this PR adds one method), and misquoted the telemetry literal -- `barcode_no_detected` where the code says `barcode_not_detected`. **8984 -- the 50-report cap was described backwards.** The draft said the summary saw "only the first 50 reports". `search.js` slices the *tail* of the date-sorted rows, so it keeps the 50 most **recent** and drops the oldest -- which is why issue #8815 is titled "Only **last** 50 reports for contact are provided" and reproduces by submitting 50 reports *after* the pregnancy. Corrected in all four places, plus `search.service.ts` annotated as not modified by this PR and the separate `DISPLAY_LIMIT = 50` display cap disclosed. **9601 and 9625 -- prose transcribed from a PR description, not its merged code.** `9601` named `is_canonical`, a `duplicate_info` section and `context.duplicate_check`; none exists at the merge commit or on master (the real shapes are an `[duplicate-contacts]` content-projection slot and a top-level `duplicate_check`). `9625` claimed freetext search for person and place; the PR adds `getUuidsPage`/ `getUuids` to contact and report only, and creates two controllers rather than adding endpoints to four. Both were independently flagged by `ground-claims`. **Attribution corrected on 9295, 9368, 9090, 9177, 10141.** Five drafts credited themselves with files or symbols another PR introduced -- #9295 called five files new that #9090 created and are `M` in its own diff; #9368 claimed `/api/v1/person` when its only added route is `/api/v1/place` and person was already in its parent tree; #9090 tagged four files it created as #9176's; #9177 put `getByUuid` in `place.ts` when it lives in `index.ts`; #10141 claimed a public export that #10157 added. Non-existent namespace members (`Person.v1.getPageByType`, `Place.v1.getByType`, `Person.V1`) corrected to the real exports, with the `getDatasource()` facade names distinguished from them. **Drift disclosed, not silently corrected, on 8684, 9230, 9426, 10777, 10804.** Each edit was followed by a re-read of the whole draft; that pass caught a further nine sibling contradictions the individual findings had not named -- `9230`'s Domain Rationale, `8995`'s YAML title, `9295`'s Design Choices and Testing, `9368`'s Related Issues and Domain Rationale, `9090`'s Related Issues, `9625`'s summary and Domain Rationale, `10141`'s `concepts` -- which is the failure mode this exercise is about. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Promotes 35 strong-fit
tasks-and-targetsdrafts fromagent-memory/_pending/intoagent-memory/domains/tasks-and-targets/issues/for squad content review.Categories: feature (21), bug (9), improvement (5)
Themes: rules engine (banish nools, stale-state migration), aggregate targets & filters, task filtering/telemetry, reporting periods.
All 35 carry
domainFit: strong+ a## Domain Rationalesection. 2 weak-fit drafts deferred to Stream C.seeding-claude-cli-v2(feat(#108): seeding pipeline - CLI provider, domain-rationale, infrastructure domain, concurrency #119) — retarget tomainafter the schema lands.validate-schema: passing, 0 failures.