@paysdoc/devplatform
v1.2.0
Published
Forge ports and adapters (GitHub, GitLab, Jira) over a forge-neutral git/worktree core
Readme
@paysdoc/devplatform
A TypeScript library of forge-neutral git/worktree primitives plus GitHub, GitLab, and Jira adapters for building dev-workflow automation.
Extracted from AI_Dev_Workflow with history.
Releases are automated by semantic-release on every push to main.
Install
bun add @paysdoc/devplatform
# or
npm i @paysdoc/devplatformThe package ships three entry points:
import { BoardStatus, type Issue, type RepoContext } from '@paysdoc/devplatform'; // forge ports + domain model
import { forgeProviders, createForgeCredentials } from '@paysdoc/devplatform/providers'; // adapter assembly + GitHub/GitLab/Jira adapters
import { GitContext, consoleLogger } from '@paysdoc/devplatform/git'; // forge-neutral git/worktree coreA launch boundary obtains its TokenProvider and bootstrap GitIdentity from the forge name
alone, then hands both straight to GitContext — no GitHub- or GitLab-named import required:
import { Platform } from '@paysdoc/devplatform';
const { tokenProvider, gitIdentity } = createForgeCredentials({
forge: { codeHost: 'github', issueTracker: 'github' },
identity: { owner: 'acme', repo: 'webapp', platform: Platform.GitHub },
deps: { github: { appConfig: null, pat: process.env.GH_TOKEN } },
});
const gitContext = new GitContext({ owner: 'acme', repo: 'webapp', selfHost: false, gitIdentity, tokenProvider, frameworkRepoRoot, targetReposDir });What it does
- Forge-neutral git executor —
GitContext.exec()is the single spawn site, env merge, cwd resolution, and ENOENT rewrap for every git command; no forge semantics leak into it. - Identity-bound context construction — a
GitContextis built from a mandatory owner/repo/selfHost/tokenProvider/gitIdentity; incomplete identity fails at construction rather than at first use. - Worktree lifecycle management — create/ensure, list, remove, probe (health:
healthy/locked/prunable/missing), and reset (takeover) operations for git worktrees under.worktrees/. - Branch, commit, and remote operations — branch creation/switching, commits, and remote push/fetch helpers layered on the shared executor;
commitOps,branchOps, andisLeaseRejectionship from@paysdoc/devplatform/gitsince issue #11, for a consumer that wants to drive them directly. - Working-directory guard — turns a spawn ENOENT caused by a missing cwd into a diagnostic naming the path and repo identity, keyed off the OS-independent error
code. - Distributed-lock claim primitives — detached-worktree add/remove, empty-commit nonce marking, and a never-forced push used to implement an atomic winner/loser election (e.g. for upgrade claims).
- Process cleanup — kills processes still running inside a worktree directory before it is removed.
- Pluggable credential and logging ports —
TokenProvider(credential-purpose-scoped) andLoggerports let a consumer supply auth and logging without the core depending on either. - Bootstrap-only git reads — a narrow, structurally-exempt set of pre-context reads (origin remote URL, env/git-config identity) for use before a full
GitContextexists. - Forge-neutral provider interfaces —
IssueTracker,CodeHost, andBoardManagerports abstract issue tracking, PR/code hosting, and project boards across platforms. - Workspace validation helpers —
validateWorkingDirectoryandparseOwnerRepoFromUrlship from@paysdoc/devplatform/providersfor cross-adapter cwd/remote-URL checks shared by GitHub, GitLab, and Jira. forgeProviders()assembly function — binds oneRepoIdentifierto a full provider triple, validating identity, token provider shape, and context binding before constructing any adapter; rejects unknown or wrong-port forge names viaUnknownForgeError.createForgeCredentials()forge-keyed credential factory — resolves aTokenProviderand a bootstrapGitIdentityfromforge.codeHostalone, the wayforgeProviders()resolves the tracker/code-host/board: GitHub dispatches through the App-installation-token → PAT →gh auth tokenchain plus the App-bot identity; GitLab serves its configured token for both credential purposes plus an environment/git-config identity; an unknown code host is refused viaUnknownForgeErrorbefore any credential seam is touched.createLiteralTokenProvider— a fixed-stringTokenProviderfor tests and fixtures — ships from@paysdoc/devplatform/git(re-exported from the GitHub adapter for backwards compatibility).- GitHub adapter — issue tracker, code host, and Projects V2 board manager built on the
ghCLI, including issue/PR read and write operations, label management, GitHub App authentication, and token resolution. Since issue #11, its credential/identity helpers (createGitHubTokenProvider,resolveBootstrapGitIdentity,resolveContextToken,ghAuthToken,isGitHubAppConfigured,getInstallationToken) and the boundcreateGhRepoApiview ship from@paysdoc/devplatform/providerstoo, for a consumer switching over with no behaviour change —createForgeCredentialsremains the recommended, forge-neutral route that composes exactly those functions. - GitLab adapter — code host implementation backed by a
curl-based API client with injected configuration (no environment reads). - Jira adapter — issue tracker backed by the Jira REST API v3, including Markdown ↔ Atlassian Document Format (ADF) conversion.
- Board status model — a canonical, ordered set of board columns (
Blocked/Todo/In Progress/Review/Done) with colors and descriptions shared across board-capable adapters. - Injected configuration everywhere — GitLab, Jira, and GitHub App adapters take configuration as constructor arguments; none reads
process.envor files directly, keeping the library embeddable in any host. - Compiled ESM + type declarations —
bun run buildemitsdist/**/*.jsanddist/**/*.d.tsviatsc, with a three-entry-pointexportsmap (.,./providers,./git) and afilesallow-list sonpm packships onlydist/,README.md, andLICENSE. - Cucumber/Gherkin BDD suite —
bun run test:e2erunscucumber-jsover per-issue.featurefiles underfeatures/per-issue/, backed by step definitions and a shared Cucumber world/support layer, with promoted, reusable phrases tracked infeatures/regression/vocabulary.md. A@packagingsubset packs the real tarball into a clean consumer and proves a name resolves both at runtime and in the emitted.d.ts. - CI type-check, git/gh guard, unit-test, BDD, and package gate — GitHub Actions runs
bun install,bun run typecheck,bun run lint:git-guard,bun run test:unit, and the hermetic (not @packaging) BDD scenarios on every PR and push tomain, plus a second job that builds, packs, smoke-tests the tarball under both Node and Bun, and runs the@packagingBDD scenarios against it. - Automated releases via semantic-release — a
ReleaseGitHub Actions job runs semantic-release on every push tomain, using a commit parser that accepts an optional<agent-name>:prefix before the conventional type (build-agent: feat: …→ minor,review-patch-agent: fix: …→ patch,plan-orchestrator: chore: …→ no release), a PRrelease-dry-runjob that prints the computed next version before merge, and npm OIDC trusted publishing with anNPM_TOKENfallback. - Git/gh CI guard — an AST-based check (
bun run lint:git-guard,scripts/checkGitGhGuard.ts+scripts/guard/) that fails CI on a directgit/ghshell-out outsidesrc/git/andsrc/providers/github/(the two structurally-exempt packages), or on ad-hoc provider/GitContextconstruction anywhere outside the one-entrysrc/providers/forgeProviders.tsallowlist. - Claude Code agent guardrails — a hooked
.claude/settings.jsonand.claude/hooks/*scripts (pre/post-tool-use, notification, stop, subagent-stop) constrain and observe agent tool use in this repo.
Setup
- Install dependencies:
bun install - Type-check:
bun run typecheck - Run the unit test suite:
bun run test:unit - Run the BDD scenario suite:
bun run test:e2e - Build:
bun run build
This package takes all forge configuration (tokens, App credentials, GitLab/Jira settings) as
constructor arguments from the consumer — it reads no .env file and no process.env value as
its primary configuration path. There is no root .env.sample to copy for using the library
itself; the optional GIT_AUTHOR_*/GITHUB_APP_* environment fallbacks used only by the
bootstrap-identity readers are documented in app_docs/git-worktree-core.md and
app_docs/github-provider.md.
Releasing
Releases are computed by semantic-release from commit messages on
every push to main — see release.config.js. Commit headers must follow the conventional-commits format,
optionally preceded by a hyphenated agent name:
[<agent-name>: ]<type>[(<scope>)][!]: <subject>feat: …/<agent-name>: feat: …→ minor releasefix: …/<agent-name>: fix: …→ patch releasefeat!: …, or any commit with aBREAKING CHANGE:footer → major releasechore: …,docs: …, and other non-releasing types → no release
The agent-name prefix must be a lowercase, hyphenated token (e.g. build-agent, plan-orchestrator) so it
can never be confused with a conventional type. Every pull request runs a release-dry-run CI job that
prints the version the merge would compute, without publishing anything.
Publishing uses npm OIDC trusted publishing (no long-lived npm credential stored in the repository), falling
back to an NPM_TOKEN secret if one is present. The release baseline is the v1.0.0 git tag on the commit
of the first manually published version; the release workflow hard-fails if that tag is not reachable, so it
can never recompute or republish 1.0.0.
The npm trusted publisher for @paysdoc/devplatform must be linked to this exact GitHub Actions workflow
(owner paysdoc, repository devplatform, workflow filename release.yml, no environment) and must have
the direct npm publish action allowed. npm creates a new trusted publisher with npm stage publish
permission only; direct publishing is a separate tick under the publisher's "Allowed actions" that is off by
default. Without it the OIDC token exchange succeeds, provenance is signed, and the publish PUT itself is
then denied with 403 … OIDC permission denied for this action. Fix it on npmjs.com, or from an
authenticated, 2FA-enabled npm CLI (11.15+):
npm trust list @paysdoc/devplatform
npm trust github @paysdoc/devplatform --file release.yml --repo paysdoc/devplatform --allow-publish --allow-stage-publishDo not reach for NPM_TOKEN to work around this: npm is removing direct publishing via bypass-2FA tokens in
January 2027, and a stage-only token cannot serve @semantic-release/npm, which runs plain npm publish and
has no staged-publishing mode. The package's Publishing access setting may be "Require two-factor
authentication and disallow tokens" — that only restricts classic tokens, trusted publishers keep working.
semantic-release pushes the git tag before it publishes, so a run that fails at npm publish leaves an
orphan tag that a re-run will not republish (no new commits exist after the tag) and reports "no relevant
changes". Delete the orphan tag and any GitHub release it created, then trigger the workflow again — never
delete v1.0.0. After a successful publish, the registry metadata shows the new version within a couple of
minutes but the tarball itself can 404 for several minutes more; that is npm replication lag, not a failed
release.
Domain glossary
See UBIQUITOUS_LANGUAGE.md for the canonical terms used across this codebase (identity, forge, provider, worktree, etc.).
Project Structure
.adw/ ADW-generated project docs (project.md, providers.md, commands.md, conditional_docs.md, review_proof.md, scenarios.md)
.adw-version ADW template version marker
.claude/
commands/ ADW-copied slash commands (gitignored except install.md, prime.md)
hooks/ Claude Code lifecycle hooks (pre/post-tool-use, notification, stop, subagent-stop)
skills/ Agent skills (TDD, PRD authoring, architecture review, ubiquitous language, ...)
settings.json Agent permission/guardrail configuration
bun.lock Bun lockfile
.github/
workflows/ci.yml Typecheck + git/gh guard + unit test CI gate, build/pack/smoke-test package gate, and a PR-only release-dry-run job
workflows/release.yml Release automation: semantic-release on push to main (v1.0.0 baseline guard, OIDC + NPM_TOKEN fallback)
adw.yml ADW guardrails toggle (outside .adw/, survives regeneration)
app_docs/ Per-module documentation owned by conditional_docs.md routing (ADW-generated)
UBIQUITOUS_LANGUAGE.md Canonical domain glossary
LICENSE Package license
package.json Package manifest: entry-point exports map, files allow-list, scripts
cucumber.js Cucumber/BDD runner configuration (loads tsx, points at features/)
features/
per-issue/ Per-issue Gherkin feature files (e.g. feature-9.feature), tagged @adw-<issue>
regression/vocabulary.md Regression test vocabulary (promoted, reusable Given/When/Then phrases)
step_definitions/ Cucumber step definitions wiring Gherkin steps to the library's public surface
support/ Shared Cucumber world/support code (packagedConsumer helper, world.ts)
logs/<session-id>/ Claude Code hook session logs (chat, pre/post-tool-use, stop transcripts)
specs/ Per-issue implementation plans (ADW-generated), plus specs/patch/ for patch plans
release.config.js semantic-release configuration: agent-prefix-aware commit parser, branches, plugin list
scripts/
smokePackage.ts Builds, packs, and smoke-tests the tarball under Node + Bun via a table-driven dynamic-import key check (`bun run smoke:package`)
releaseDryRun.ts Runs `semantic-release --dry-run` and prints the computed next version (`bun run release:dry-run`)
checkGitGhGuard.ts CI git/gh guard entry point (`bun run lint:git-guard`) — dev-only, excluded from dist/
guard/ Guard rule modules: shell-out exempt-package set, construction allowlist, stdout report
src/
index.ts Root entry point ("."): forge ports + domain model only
__tests__/ Import-graph, package-exports, and release-config contract tests
git/ Forge-neutral git/worktree core (GitContext, worktree ops, bootstrap identity, process cleanup) — entry point "./git"; also re-exports commitOps/branchOps/isLeaseRejection since issue #11
literalTokenProvider.ts Fixed-string TokenProvider for tests/fixtures (re-exported from the GitHub adapter)
providers/ Forge provider ports and adapters — entry point "./providers"
github/ GitHub adapter (issue tracker, code host, board manager, App auth, gh CLI commands); its credential/identity helpers and createGhRepoApi are re-exported on this barrel since issue #11
gitlab/ GitLab adapter (API client, code host, type mappers, adapter-internal token provider + bootstrap identity)
jira/ Jira adapter (API client, issue tracker, ADF converter)
forgeProviders.ts Provider assembly function
forgeCredentials.ts Forge-keyed credential factory (TokenProvider + bootstrap GitIdentity)
types.ts Platform-agnostic provider interfaces
workspaceValidation.ts Cross-adapter cwd/remote-URL validation helpers
dist/ Build output (gitignored) — emitted by `bun run build`
tsconfig.json TypeScript strict-mode configuration (NodeNext modules/resolution)
tsconfig.build.json Build config: extends tsconfig.json, emits dist/**/*.js + dist/**/*.d.ts
vitest.config.ts Vitest configuration (JUnit output via $ADW_UNIT_TEST_REPORT_PATH)