Add the two packages: TypeScript reader, Python pull-request writer - #1
Merged
Conversation
js/ ships @kingdom-community/github-docs, which fetches markdown out of a GitHub repository for rendering. Failure is a value rather than an exception, and nothing upstream -- body, header, URL, token -- is ever quoted back. python/ ships github-docs, which lands documentation edits as pull requests against a per-file branch, reusing an open PR for that branch when one exists. stdlib urllib only. Both are configured by the caller: repository, paths and token are arguments, not constants. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XBrW2fYYTzvhgK7WMFv92r
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds the actual library: one repo, two packages, because the read half and the write half were built in different languages.
@kingdom-community/github-docs(js/)github-docs(python/)Both at
0.1.0, MIT, unpublished. Two release workflows, bothworkflow_dispatch-only, neither fired.Read side (TypeScript)
Where it came from —
kingdom-community/kingdomfirstera-dot-com:services/githubContent.tsjs/src/client.tsutils/markdownUrl.tsjs/src/markdownUrl.ts(near-verbatim)utils/guides.tsjs/src/urls.ts+ part ofjs/src/catalogue.tsutils/lore.ts(allowlist parsing only)js/src/catalogue.ts__tests__/markdownUrl.test.ts,__tests__/guides.test.tsjs/test/*Plus repo-slug parsing folded in from
dansplugins-dot-com/utils/github.ts(MIT, same owner), reimplemented rather than copied and widened to accept a bare slug, a.gitsuffix, a/tree/main/...URL and the whitespace/slashes a human leaves in an environment variable.The two rules that had to survive extraction, and did:
fetchMarkdownnever rejects. Every way a fetch can go wrong — 404, rate limit, upstream 500, timeout, DNS failure, oversized body, a body that fails mid-stream — returns{status: 'unavailable'}.not-configuredandnot-listedare kept separate because they are deployment states rather than outages.What changed during generalisation:
DEFAULT_GUIDE_REPO/DEFAULT_LORE_REPOand theKFE_*env reads are gone. The package reads no environment at all;createDocsClient({repo, token, documents, ...})takes everything as arguments, so it can be configured twice in one process and tested without stubbingprocess.env.utils/lore.tsallowlist coupling is dropped. The idea survives as an optional, generic catalogue:documentsaccepts a list or the comma-separated string an env var comes in, and is deny-by-default when set. Unset means the whole repository is fetchable; an empty list means nothing is — an operator who has not filled it in publishes nothing rather than everything.title/summary/slugoverridable per entry.transportoption that defaults sensibly:apiwhen a token is configured,rawwhen it is not. The raw path still sends no credentials at all, which is now asserted in a test.resolveGuideLinkgrew afromPath, so links resolve against the containing document's directory rather than assuming every doc sits at the repo root; a link that climbs out of the repository is left as written rather than turned into a URL that is confidently wrong.ARCHITECTURE.md/MVP.md/ sibling services were rewritten to state the principle instead of pointing at a document the reader cannot open. The reasoning itself — why the URL check runs on the resolved value, why whitespace is stripped before validating, why the probe exists — is preserved.Write side (Python)
Where it came from —
~/kfe-staff-webapp(read-only, nothing cloned):github_docs.pypython/src/github_docs/client.pytest_github_docs.pypython/tests/test_client.pyThe flow is unchanged: look up default branch and tip SHA → ensure a per-file branch → PUT content via the Contents API → reuse the open PR for that branch or open one. Never a direct push.
What changed during generalisation:
REPO/TOKEN/TRACKED_FOLDERSconstants read fromKFE_*env vars became a frozenGitHubDocsConfigdataclass and aGitHubDocsClientinstance. The package reads no environment of its own.repo,token,allowed_roots,extensions,branch_prefix,api_base,timeout,user_agentand the commit-message / PR-title / PR-body templates are all configuration.allowed_roots=Nonemeans the whole repository; an empty tuple means nothing. The old code's implicit "empty tuple denies everything" is now deliberate and documented.urlopenis never called).GitHubDocsError. The write half deliberately does surface GitHub's message — a human is reading it and needs to know permissions vs conflict — so it is worth being explicit that the credential is the one thing that never travels with it.Document,DocumentSummary,SaveResult) with type hints throughout.branch_prefixdefault is nowdocs-edit/rather thanstaff-edit/.Tests
Also verified locally:
tsc --noEmitclean,tsc -p tsconfig.build.jsonemitsdist/with declarations, the built ESM imports and runs under Node 24,python -m buildproduces an sdist and wheel, and the 31 Python tests pass against the installed wheel rather than the source tree (which is what CI does).Deliberately left out
LoreLinkTarget,resolveLoreLinkand the "unpublished link renders as plain text" rule). It is closely tied to a site that serves lore at/lore/<slug>and knows which slugs are published; the general half — resolving a relative path against the containing document, refusing one that escapes the repo root — is here asresolveRepoPath/resolveDocLink.dansplugins-dot-com/utils/github.ts(getLatestRelease,getLatestReleasesWithRateLimit). It is about plugin downloads, not documentation, and itconsole.errors upstream text — which is exactly the rule this package exists to hold.