feat(grants): add grants-pp-cli — NIH+NSF+Grants.gov keyless CLI#1443
feat(grants): add grants-pp-cli — NIH+NSF+Grants.gov keyless CLI#1443laci141 wants to merge 1 commit into
Conversation
…ants.gov keyless CLI, 731 LOC, Phase 5 pass (13/13 tests), parseFlexible + AwardCap fixes Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Greptile SummaryThis PR introduces
Confidence Score: 3/5The CLI logic itself compiles and the filter functions are well-tested, but the submission is structurally incomplete for the catalog and has a silent data-correctness bug in the agency filter. Two real defects need fixing before this can be useful: the --agency flag sends a JSON string where the Grants.gov API requires an array (silently returning unfiltered results), and the entire PR is missing the publication artifacts that make the CLI discoverable and installable via the catalog. The Go code itself is clean and the tests pass, but the agency bug would mislead users relying on agency-scoped searches, and the missing .printing-press.json / SKILL.md means the CLI won't appear in registry.json or be installable through the catalog. library/health/grants/internal/sources/grantsgov.go (agencies field type), library/health/grants/internal/cli/search.go (post-fetch date filter), and the overall directory (missing .printing-press.json, SKILL.md, AGENTS.md, LICENSE, NOTICE, Makefile, .goreleaser.yaml, .golangci.yml, dogfood-results.json, .manuscripts/). Important Files Changed
|
| // grants-pp-cli — nyitott kutatási pályázatok kulcs nélkül / open research grants, keyless. | ||
| // Források / sources: Grants.gov (nyitott kiírások), NIH RePORTER + NSF (megítélt grantek). | ||
| package main | ||
|
|
||
| import ( | ||
| "os" | ||
|
|
||
| "grants-pp-cli/internal/cli" | ||
| ) | ||
|
|
||
| func main() { | ||
| os.Exit(cli.Run(os.Args[1:])) | ||
| } |
There was a problem hiding this comment.
Hand-built new CLI bypasses required publishing workflow
This PR adds a brand-new library/health/grants/ tree as a hand-constructed PR rather than through the /printing-press publish flow. Per this repo's AGENTS.md, any PR that creates a new library/<category>/<slug>/ directory must go through cli-printing-press → /printing-press-publish, which is the contract enforced by verify-library-conventions.yml.
The following required artifacts are absent from this PR diff: .printing-press.json (identity manifest; without it the CLI is invisible to registry.json and generate-skills.yml), SKILL.md (the catalog discovery surface; generate-skills.yml will have nothing to mirror), AGENTS.md, LICENSE, NOTICE, Makefile, .goreleaser.yaml, .golangci.yml, dogfood-results.json, and .manuscripts/<run-id>/{research,proofs}/. With 18 files in the diff versus the ~30+ the canonical layout requires, the PR is structurally incomplete.
The 🤖 Generated with Claude Code trailer in the PR description is explicitly listed in AGENTS.md as a "universal-PR-template tell, not publish-skill output." The PR title (feat(grants): add grants-pp-cli — NIH+NSF+Grants.gov keyless CLI) also violates the required shape of feat(<slug>): add <slug> exactly (no trailing dash-suffix description).
The right path forward is /printing-press-publish grants from a workspace that has the generator available, which will produce the properly shaped PR with all required artifacts.
Context Used: AGENTS.md (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| if agencyCode != "" { | ||
| payload["agencies"] = agencyCode | ||
| } |
There was a problem hiding this comment.
--agency filter sent as a plain string instead of an array
The Grants.gov search2 API expects the agencies field to be a JSON array (e.g. ["HHS-NIH11"]), not a bare string. Passing "agencies": "HHS-NIH11" will cause the API to silently ignore the filter, and users running grants-pp-cli search "cancer" --agency HHS-NIH11 will receive unfiltered results with no error message or indication that the filter did not apply.
| if agencyCode != "" { | |
| payload["agencies"] = agencyCode | |
| } | |
| if agencyCode != "" { | |
| payload["agencies"] = []string{agencyCode} | |
| } |
| if !cutoff.IsZero() { | ||
| opps = ClosingBefore(opps, cutoff) | ||
| } |
There was a problem hiding this comment.
--closing-before silently filters only within the fetched page
ClosingBefore is applied after SearchOpportunities returns at most --rows results. If --rows 15 is used (the default), any opportunities with matching deadlines beyond position 15 are never fetched and silently missed. The header line shows the total hit count from Grants.gov (e.g. "200 nyitott kiírás összesen") but the shown count reflects only the client-side filter on the fetched page, which will confuse users who expect deadline filtering to work across all available results. At minimum, a warning when cutoff is non-zero and len(opps) == *rows would signal that results may be truncated.
| if resp.StatusCode >= 500 { | ||
| lastErr = fmt.Errorf("%s: HTTP %d", req.URL.Host, resp.StatusCode) | ||
| time.Sleep(time.Second) | ||
| continue |
There was a problem hiding this comment.
5xx retry discards the response body before checking status
The body is fully read and resp.Body.Close() is called unconditionally at line 34, then the status is checked at line 38. This ordering is correct and avoids leaks. However, on a 5xx the already-read body is discarded and a fresh request is made. If the error body contained useful diagnostics (e.g. a rate-limit message), they are lost and the retry's lastErr only carries the status code. Consider including %.200s of the 5xx body in lastErr (matching what is done for 4xx at line 44) so the final error message is actionable.
New keyless CLI that searches open research grants (NIH RePORTER, NSF Awards, Grants.gov).
search(deadline, amount, eligibility filters),doctor(API health)Related: #1438 (retraction-checker pattern mirror)
🤖 Generated with Claude Code