Repository navigation
Specs release
This specification defines the .agents/team.yaml releases section, the team release check <package@version> command and its human and JSON output contracts, the SemVer 2.0.0 version and channel rules, the four base public-record checks (npm, tag, github, changelog) and the dist-tags warning, the optional linear and activity release-record checks, the bounded HTTP and macOS Keychain contracts, and the scripts/check-release-date.ts pre-publish check. It lifts the contract of archived internal RFCs 0004 (0004-releases.md) and 0005 (0005-release-records.md) as built at v0.5.0 (52db9b75). It does not publish packages, create or push tags, edit GitHub releases, modify npm dist-tags, mutate Linear milestones or status updates, or write activity files; every operation in this specification is read-only and stateless.
-
Release declaration (
ReleaseDecl): one entry in the top-levelreleasessequence of.agents/team.yaml, binding an npmpackagename to agithubowner/repoand optionaltrusted_publishing,linear_*, andactivity_*settings (src/file/sections/releases.ts). -
SemVer 2.0.0 version: a version string matching
SEMVER_PATTERN(major.minor.patchwith optional-<prerelease>and+<build>, and no leadingvprefix;src/release/grammar.ts). -
Prerelease channel: the classification determined by
hasPrerelease(version)—truewhen the version carries a-<prerelease>part before any+<build>metadata,falseotherwise (src/release/grammar.ts). -
Check status (
Status): one ofpass,missing, orunknown(src/release/checks.ts).missingmeans a completed, interpretable read disproved a required condition;unknownmeans a required read failed, timed out, exceeded a budget cap, or returned an unexpected HTTP status or malformed structure. -
Compound check: a check whose verdict combines multiple part checks or sequential network reads (
npmwith provenance,tagref/object/compare, andlinearproject/milestone/update conditions) without short-circuiting required reads whose inputs are known (src/release/checks.ts). -
Endpoint read and HTTP attempt: one logical call to
Reader.read(url, request)(READ_CAP = 14per command invocation) and one underlyingfetchcall (ATTEMPT_CAP = 28per command invocation, including at most one retry per endpoint read;src/release/checks.ts,src/release/http.ts). -
Trusted publishing: a release declaration with
trusted_publishing: true, requiring an npm SLSA v1 provenance attestation (predicateType: "https://slsa.dev/provenance/v1") in addition to the version record and checksums (src/release/checks.ts). -
Linear release record: the paired
linear_projectUUID andlinear_keychain_serviceconfiguration verified by a single bounded GraphQLPOSTtohttps://api.linear.app/graphqlfor a completed milestone named<version>and an unarchived project status update on or after the changelog release date (src/release/checks.ts). -
Activity marker: the paired
activity_filepath andactivity_markertemplate verified by an exact single-line match on the repository's default branch (src/release/checks.ts).
releases is an optional top-level sequence in .agents/team.yaml (owner: false: it names public records and a Keychain service name, never a secret, and grants no seat authority, so editing it requires no owner approval digest update). When present, it MUST be a non-empty list of map entries (minItems: 1) with no unknown keys.
| Field | Type | Required | Default | Since | Meaning |
|---|---|---|---|---|---|
releases |
array of objects | no (minItems: 1 when present) |
[] |
0.5.0 | package release declarations read by team release check and validated by team check / team doctor
|
releases[].package |
string | yes | — | 0.5.0 | npm package name matching ^(?:@[a-z0-9][a-z0-9._-]*/)?[a-z0-9][a-z0-9._-]*$ (PACKAGE_PATTERN), at most 214 UTF-8 bytes (PACKAGE_BYTES), unique across releases
|
releases[].github |
string | yes | — | 0.5.0 | GitHub repository owner/repo matching ^[A-Za-z0-9][A-Za-z0-9._-]{0,99}/[A-Za-z0-9][A-Za-z0-9._-]{0,99}$ (GITHUB_PATTERN) |
releases[].trusted_publishing |
boolean | no | false |
0.5.0 | unquoted YAML boolean true or false only; when true, npm check requires SLSA v1 provenance attestation |
releases[].linear_project |
string | paired with linear_keychain_service
|
— | 0.5.0 | canonical lowercase UUID matching ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ (LINEAR_PROJECT_PATTERN) |
releases[].linear_keychain_service |
string | paired with linear_project
|
— | 0.5.0 | non-empty macOS Keychain service name, at most 255 UTF-8 bytes (KEYCHAIN_SERVICE_BYTES), containing no control characters (0x00–0x1f, 0x7f–0x9f); names the Keychain item, never the secret key |
releases[].activity_file |
string | paired with activity_marker
|
— | 0.5.0 | printable ASCII (0x20–0x7e) slash-separated repository-relative path, at most 512 UTF-8 bytes (ACTIVITY_FILE_BYTES), each segment matching ^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$ (ACTIVITY_SEGMENT) |
releases[].activity_marker |
string | paired with activity_file
|
— | 0.5.0 | non-empty printable ASCII (0x20–0x7e) line template, at most 200 UTF-8 bytes (ACTIVITY_MARKER_BYTES), no control characters, containing <package> exactly once and <version> exactly once |
Example .agents/team.yaml releases configuration:
releases:
- package: material
github: acme/material
trusted_publishing: true
linear_project: 73495f50-f9e0-4469-8585-e7af6aa49567
linear_keychain_service: linear-release-check
activity_file: docs/releases.md
activity_marker: "Released <package> <version>"
- package: "@acme/cli"
github: acme/cli| Symbol / Function | Pattern / Signature | Since | Meaning |
|---|---|---|---|
CORE |
(?:0|[1-9][0-9]*) |
0.5.0 | non-negative decimal integer without leading zeros |
PRERELEASE_IDENT |
(?:0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*) |
0.5.0 | dot-separated prerelease identifier (numeric without leading zeros, or alphanumeric/hyphen) |
BUILD_IDENT |
[0-9a-zA-Z-]+ |
0.5.0 | dot-separated build-metadata identifier |
SEMVER_PATTERN |
^${CORE}\.${CORE}\.${CORE}(?:-${PRERELEASE_IDENT}(?:\.${PRERELEASE_IDENT})*)?(?:\+${BUILD_IDENT}(?:\.${BUILD_IDENT})*)?$ |
0.5.0 | exact Semantic Versioning 2.0.0 version without a leading v
|
hasPrerelease(version) |
(version.split('+')[0]).includes('-') |
0.5.0 |
true for prerelease versions (1.2.0-rc.1), false for stable versions (1.2.0, 1.2.0+20261019) |
compareVersions(a, b) |
(a: string, b: string) => -1 | 0 | 1 | null |
0.5.0 | SemVer 2.0.0 §11 precedence comparison (ignoring build metadata); returns null when either input does not match SEMVER_PATTERN
|
3.3 Human output format and JSON output schema (src/commands/release.ts, schema/release.schema.json)
- Header line:
package <name>, version <version> - Table (
formatTable) with headersCHECK,STATE,DETAILand rows in the fixed ordernpm,tag,github,changelog, followed bylinear(when configured) andactivity(when configured). On a TTY,STATEcells are colored green forpass, red formissing, and yellow forunknown. - Optional warning line immediately after the table when
staleNextWarningreturns a string:warn npm's next dist-tag points at <next>, older than latest <latest>: run npm dist-tag add <name>@<latest> next - Summary line:
<n> check(s), <m> missing
Example plain output:
package material, version 0.2.0
CHECK STATE DETAIL
npm pass exact version, checksums, and provenance found
tag pass tag resolves to a commit on the default branch
github pass published release matches SemVer channel
changelog pass changelog entry has a valid release date
linear pass Linear milestone is complete and a qualifying status update exists
activity pass public activity marker found
6 checks, 0 missing
When --json is passed, normal check runs write one team.release v1 JSON object to stdout (additionalProperties: false):
| Field | Type | Required | Since | Meaning |
|---|---|---|---|---|
schema |
{ name: "team.release", version: 1 } |
yes | 0.5.0 | schema identifier |
format |
1 |
yes | 0.5.0 | payload format version |
package |
string (minLength: 1) |
yes | 0.5.0 | checked npm package name |
version |
string (minLength: 1) |
yes | 0.5.0 | checked SemVer 2.0.0 version |
checks |
object | yes | 0.5.0 | check outcomes keyed by check name (npm, tag, github, changelog required; linear, activity present when configured) |
checks.<name>.status |
"pass" | "missing" | "unknown"
|
yes | 0.5.0 | outcome of check <name>
|
checks.<name>.detail |
string (minLength: 1) |
yes | 0.5.0 | single-line diagnostic or confirmation message |
warning |
string (minLength: 1) |
no | 0.5.0 | present only when npm's next dist-tag points at a SemVer version older than latest
|
summary |
{ checks: integer, missing: integer, unknown: integer } |
yes | 0.5.0 | total checks run (4, 5, or 6) and counts of missing and unknown outcomes |
Example JSON output:
{
"schema": { "name": "team.release", "version": 1 },
"format": 1,
"package": "material",
"version": "0.2.0",
"checks": {
"npm": { "status": "pass", "detail": "exact version and checksums found" },
"tag": { "status": "pass", "detail": "tag resolves to a commit on the default branch" },
"github": { "status": "pass", "detail": "published release matches SemVer channel" },
"changelog": { "status": "pass", "detail": "changelog entry has a valid release date" }
},
"summary": { "checks": 4, "missing": 0, "unknown": 0 }
}- Without
--json, writesteam release: <message>\nto stderr. - With
--json, writes ateam.errorv1 envelope to stdout witherror.codeset to"release.usage"or"release.configuration",error.messageset to<message>, anderror.fixset tonull.
When the linear check runs, it sends a single HTTP POST to https://api.linear.app/graphql (LINEAR_URL) with headers Content-Type: application/json and Authorization: <key>, and JSON body { "query": RELEASE_RECORDS_QUERY, "variables": { "projectId": "<linear_project>", "first": 100 } }, where RELEASE_RECORDS_QUERY is byte for byte:
query ReleaseRecords($projectId: String!, $first: Int!) {
project(id: $projectId) {
id
archivedAt
projectMilestones(first: $first, includeArchived: true) {
nodes { name status }
pageInfo { hasNextPage }
}
projectUpdates(first: $first, includeArchived: true) {
nodes { createdAt archivedAt }
pageInfo { hasNextPage }
}
}
}team release check <package@version> [--json] [--file <path>]
| Argument / Option | Required | Default | Meaning |
|---|---|---|---|
check |
yes | — | the only subcommand of team release
|
<package@version> |
yes | — | target package name and SemVer 2.0.0 version separated at the last @ (at = target.lastIndexOf('@'), with at > 0) |
--json |
no | false |
emit team.release v1 (or team.error v1 on exit 64) as JSON on stdout; recognized anywhere in argv except immediately after --file
|
--file <path> |
no | main checkout's .agents/team.yaml
|
load the team file from <path> (loadTeamFile(io.cwd, { file, checkOnly: true })) |
Exit codes: 0 (release.passed), 1 (release.missing), 2 (release.unknown), 64 (release.usage or release.configuration).
Validates the .agents/team.yaml releases section against the schema and rules in §3.1 and §5 Rule 1 without making any network or Keychain calls.
bun scripts/check-release-date.ts <version>
Reads CHANGELOG.md from the repository root (process.cwd()). Invoked in .github/workflows/release.yml (bun scripts/check-release-date.ts "${GITHUB_REF_NAME#v}") before publishing to verify that CHANGELOG.md has a bracketed ## [<version>] - YYYY-MM-DD section with a real calendar date (not the YYYY-MM-DD placeholder). Exits 0 when the bracketed section exists with a valid calendar date, 1 when the section is missing or its date is a placeholder or invalid calendar date, and 2 when <version> (process.argv[2]) is missing or CHANGELOG.md cannot be read.
-
Configuration validation and pair completeness (
src/file/sections/releases.ts,src/release/grammar.ts).- When
releasesis present in.agents/team.yaml, it MUST be a non-empty sequence of maps containing only the allowed keys (package,github,trusted_publishing,linear_project,linear_keychain_service,activity_file,activity_marker). - Every entry MUST supply a valid
package(PACKAGE_PATTERN,<= 214bytes) andgithub(GITHUB_PATTERN), and no two entries inreleasesmay name the samepackage(releases names "<pkg>" twice). -
trusted_publishing, when present, MUST be an unquoted YAML boolean literaltrueorfalse(a release's trusted_publishing must be the boolean true or false). -
linear_projectandlinear_keychain_serviceMUST appear together or not at all (a release's linear_project and linear_keychain_service come together);activity_fileandactivity_markerMUST likewise appear together or not at all (a release's activity_file and activity_marker come together). - (Source:
src/file/sections/releases.ts,src/release/grammar.tsatv0.5.0; proof:test/release/section.test.ts,test/release/grammar.test.ts.)
- When
-
Invocation validation and exit-64 precedence (
src/commands/release.ts).-
team releaseMUST validate command-line syntax and argument grammars before reading the team file: any option parse error fromreadArgs(argv, ['file'], ['json']), a missing or unknown subcommand (sub !== 'check'), a missing<package@version>or extra positional argument, a target that cannot split into non-empty<package>and<version>atlastIndexOf('@') > 0, a<package>rejected bypackageProblem, or a<version>not matchingSEMVER_PATTERNMUST exit64withrelease.usageeven if the team file is missing or malformed. - Only after all CLI arguments pass validation is the team file loaded via
loadTeamFile(io.cwd, { file: args.values.file, checkOnly: true }). Any file load or validation failure MUST exit64withrelease.configuration(reporting the file path and 1-based line number when positive). - Only after the team file loads cleanly is
<package>looked up inteam.releases; if absent, the command MUST exit64withrelease.usage(<package> is not declared in the team file's releases). -
--jsonanywhere inargvexcept as the option value following--file(jsonRequested(argv)) MUST format even an exit-64 failure as ateam.errorv1 JSON object. - (Source:
src/commands/release.tsatv0.5.0; proof:test/release/command.test.ts.)
-
-
Check statuses, compound-read completion, and precedence (
src/release/checks.ts).- Every check MUST evaluate to
{ status: 'pass' | 'missing' | 'unknown', detail: string }. An HTTP404on a JSON record endpoint maps tomissing; a2xxresponse with a valid JSON object maps to the parsed record; any other HTTP status (including3xx), invalid JSON, non-object JSON root, failed or timed-out request, capped read, or over-limit body on a full-document endpoint maps tounknown. - A compound check MUST execute every required read whose inputs are known rather than short-circuiting after an earlier
missingcondition, and MUST combine part outcomes withcombine(parts, passDetail)using strict precedenceunknown>missing>pass, returning thedetailof the firstunknownpart (or, if none, the firstmissingpart). - A downstream read whose required input is unavailable because an upstream read resolved to
missingis not required and MUST NOT be requested; a downstream read whose input is unavailable because an upstream read resolved tounknownresolves tounknown. - Overall command exit code MUST be
0(release.passed) when every configured check ispass,2(release.unknown) when at least one configured check isunknown, and1(release.missing) otherwise. - (Source:
src/release/checks.ts,src/commands/release.tsatv0.5.0; proof:test/release/checks.test.ts,test/release/records.test.ts,test/release/command.test.ts.)
- Every check MUST evaluate to
-
The four base checks (
npm,tag,github,changelog) and thedist-tagswarning (src/release/checks.ts).-
npmcheck (npmCheck):- Reads
https://registry.npmjs.org/<encodeSegment(package)>/<encodeSegment(version)>. -
404->missing("npm has no <package>@<version>"); unreadable/non-2xx/non-object ->unknown("the npm record could not be read");name !== <package>orversion !== <version>->unknown("the npm record is not for this package and version");dist === undefined->missing("the npm record has no dist"); non-objectdist->unknown("the npm record's dist is not an object"). - Inspects both
dist.shasumanddist.integritybefore deciding (unknownbeatsmissing):undefined->missing("the npm record has no dist.<key>"); non-string ->unknown("dist.<key> is not a string");""->missing("the npm record has an empty dist.<key>"). - When
trusted_publishingistrue, also readshttps://registry.npmjs.org/-/npm/v1/attestations/<encodeSegment(package + "@" + version)>even if the version part wasmissingorunknown:404or anattestationsarray without an object havingpredicateType === "https://slsa.dev/provenance/v1"->missing("npm shows no provenance attestation for this version"); unreadable/non-2xx ->unknown("the attestation response could not be read"); non-arrayattestations->unknown("the attestation response has no attestations list"). - Pass detail:
"exact version, checksums, and provenance found"whentrusted_publishingistrue, else"exact version and checksums found".
- Reads
-
dist-tagswarning (staleNextWarning):- Reads
https://registry.npmjs.org/-/package/<encodeSegment(package)>/dist-tags. - Emits
"npm's next dist-tag points at <next>, older than latest <latest>: run npm dist-tag add <package>@<latest> next"if and only if the read succeeds with a JSON object whosenextandlatestfields are valid SemVer 2.0.0 strings andcompareVersions(next, latest) < 0. Any failed, capped, missing, or non-SemVer read producesundefinedand never alters any check status or exit code.
- Reads
-
Shared repository read (
readRepo):- Reads
https://api.github.com/repos/<encodeSegment(owner)>/<encodeSegment(repo)>once.404->missing("GitHub has no repository <owner>/<repo>"); unreadable or missing/empty stringdefault_branch->unknown("the repository could not be read"). Shared bytag,changelog, andactivity.
- Reads
-
tagcheck (tagCheck):- Reads
.../git/ref/tags/v<encodeSegment(version)>.404->missing("GitHub has no tag v<version>"); unreadable or malformedobject.{type,sha}(SHA_PATTERN = /^[0-9a-f]{40}$/) ->unknown("the tag could not be read"). - While
target.type === "tag", dereferences.../git/tags/<target.sha>through at most4tag objects (for (let i = 0; i < 4 && target.type === 'tag'; i++)):404->missing("GitHub has no tag object <sha>"); unreadable or malformedobject->unknown("a tag object could not be read"); iftarget.type === "tag"after4tag-object dereferences, fails withunknown("the tag chain needs a fifth tag object"). Anytarget.typeother than"commit"or"tag"->missing("the tag does not resolve to a commit"). - When both a resolved commit SHA and
readRepo'sdefault_branchare known, reads.../compare/<encodeSegment(commit)>...<encodeSegment(default_branch)>:404->missing("GitHub has no compare of the tag and the default branch"); top-levelstatusof"identical"or"ahead"->pass;"behind"or"diverged"->missing("tag commit is not an ancestor of the default branch"); any other or unreadable status ->unknown("the compare could not be read"). When the compare response is HTTP2xxand exceedsBODY_LIMIT(reply.kind === 'over-limit'),statusInHead(reply.prefix)extracts the top-level"status"string from the 1 MiB UTF-8 prefix if complete before the cutoff. - Combines the tag-chain part, the compare part (when both inputs were known), and any non-
okreadRepooutcome; pass detail is"tag resolves to a commit on the default branch".
- Reads
-
githubcheck (githubCheck):- Reads
.../releases/tags/v<encodeSegment(version)>.404->missing("GitHub has no release for v<version>"); unreadable or non-booleandraft/prerelease->unknown("the release could not be read");draft === true->missing("the release is a draft");prerelease !== hasPrerelease(version)->missing("the release is not marked as a prerelease"whenhasPrerelease(version)istrue, else"the release is marked as a prerelease"). Pass detail:"published release matches SemVer channel".
- Reads
-
changelogcheck (changelogCheck):- If
readRepoismissingorunknown, returns that status and detail without requestingCHANGELOG.md. - Otherwise reads
.../contents/CHANGELOG.md?ref=<encodeSegment(default_branch)>.404->missing("the default branch has no CHANGELOG.md"); unreadable or invalid base64/UTF-8filepayload (decodeContents) ->unknown("the changelog could not be read"). - Scans lines (stripping optional
\rbefore\n) for^## (?:\[<escaped-version>\]|<escaped-version>) - ([0-9]{4})-([0-9]{2})-([0-9]{2})[ \t]*$. If a matching line has a valid calendar date (1970 <= year <= 9999and valid month/day including leap-year rules), returnspass("changelog entry has a valid release date") andreleaseAt = Date.UTC(year, month - 1, day). Otherwise, if any line matched the heading prefix^## (?:\[<escaped-version>\]|<escaped-version>) -, returnsmissing("the changelog entry for <version> has an invalid date"), elsemissing("CHANGELOG.md has no entry for <version>").
- If
- (Source:
src/release/checks.tsatv0.5.0; proof:test/release/checks.test.ts.)
-
-
The optional
linearandactivitychecks (src/release/checks.ts,src/release/keychain.ts).-
linearcheck (linearCheck):- Runs if and only if the declaration includes
linear_projectandlinear_keychain_service. - Evaluates non-secret prerequisites in fixed order before touching the Keychain:
- If
changelog.outcome.status === 'unknown', returnsunknown("release date could not be read"). - If
changelog.outcome.status === 'missing', returnsmissing("release date is missing from changelog"). - If
!reader.canRead(), returnsunknown("Linear record could not be read").
- If
- Reads the API key at most once via
keychainReader(/usr/bin/security find-generic-password -s <service> -w): requiresprocess.platform === 'darwin',io.stdinIsTTY === true, subprocess exitstatus === 0withsignal === nullwithinKEYCHAIN_TIMEOUT_MS = 5000ms andSTDOUT_CAP = 4096bytes, passes stdout tokeyOfwhich strips one trailing\n(LF) only (a trailing\r\nleaves\r, which fails the printable ASCII check), and requires a non-empty string where every remaining character is printable non-whitespace ASCII (0x21..0x7e). Any failure returnsunknown("Keychain access was unavailable"). - Sends one
POSTtohttps://api.linear.app/graphqlwithRELEASE_RECORDS_QUERYandvariables: { projectId: config.project, first: 100 }. Any non-2xxreply, invalid JSON, top-level"errors"property, or non-objectdatareturnsunknown("Linear record could not be read"). - In
linearRecord, first checksdata.project: if missing, non-object, orproject.id !== config.project, returnsunknown("Linear record is incomplete") immediately. When the project row is well-formed, evaluates the remaining three conditions without short-circuiting in table order (unknown>missing>pass, keeping the first condition'sdetail):-
project.archivedAt:nullis active; a valid RFC 3339 timestamp string (timestampOf) ->missing("configured Linear project is archived"); any other value ->unknown("Linear record is incomplete"). -
project.projectMilestones: requiresnodesarray andpageInfo.hasNextPage === false, with stringnameandstatuson every node (elseunknown"Linear record is incomplete"). Counts nodes withname === version:0->missing("no matching Linear milestone found");> 1->unknown("multiple matching Linear milestones found");1withstatus !== 'done'->missing("matching Linear milestone is incomplete"). -
project.projectUpdates: requiresnodesarray andpageInfo.hasNextPage === false, where every node has a valid RFC 3339createdAtstring andarchivedAteithernullor a valid RFC 3339 string (elseunknown"Linear record is incomplete"). Counts nodes witharchivedAt === nullandtimestampOf(createdAt) >= releaseAt:0->missing("no qualifying Linear status update found").
-
- When no condition fails, returns
pass("Linear milestone is complete and a qualifying status update exists").
- Runs if and only if the declaration includes
-
activitycheck (activityCheck):- Runs if and only if the declaration includes
activity_fileandactivity_marker. - If
readRepoismissing, returnsmissing("public activity marker is missing"); ifreadRepoisunknown, returnsunknown("public activity file could not be read"). - Reads
.../contents/<path>?ref=<encodeSegment(default_branch)>where<path>encodes each/segment ofactivity_filewithencodeSegment.404->missing("public activity marker is missing"); unreadable or invalid base64/UTF-8filecontent ->unknown("public activity file could not be read"). - Replaces
<package>withnameand<version>withversioninactivity_marker, splits the decoded file on/\r\n|\n/(removing a single trailing""element left by a final newline, while preserving all spaces and tabs), and counts lines equal (===) to the expanded marker:0->missing("public activity marker is missing");> 1->unknown("multiple public activity markers found");1->pass("public activity marker found").
- Runs if and only if the declaration includes
- (Source:
src/release/checks.ts,src/release/keychain.tsatv0.5.0; proof:test/release/records.test.ts.)
-
-
Bounded network and secret-handling invariants (
src/release/http.ts,src/release/keychain.ts,src/release/checks.ts).- Every HTTP request MUST target only the allowlisted origins (
https://registry.npmjs.org,https://api.github.com,https://api.linear.app/graphql), encode dynamic path and query segments withencodeSegment(RFC 3986 unreserved[A-Za-z0-9._~-]or uppercase%HH), and setredirect: 'manual'so redirects are never followed and credentials never cross origins. - Each HTTP attempt MUST enforce
TIMEOUT_MS = 5000ms and a decoded UTF-8 body cap ofBODY_LIMIT = 1_048_576bytes (1 MiB). - Only timeouts, transport errors, and HTTP statuses
408,429, and500–599are retried, at most once per endpoint read (retried(attempt)). - One command invocation MUST NOT exceed
READ_CAP = 14endpoint reads orATTEMPT_CAP = 28HTTP attempts across all checks and warnings. - The command MUST NOT read
GITHUB_TOKEN,GH_TOKEN,NPM_TOKEN,LINEAR_API_KEY,.npmrc,ghconfiguration, or git credentials, and MUST NOT write any Keychain secret byte to stdout, stderr, JSON output, error messages, or the filesystem. - (Source:
src/release/http.ts,src/release/keychain.ts,src/release/checks.tsatv0.5.0; proof:test/release/checks.test.ts,test/release/records.test.ts.)
- Every HTTP request MUST target only the allowlisted origins (
-
Release-date changelog check script (
scripts/check-release-date.ts).-
bun scripts/check-release-date.ts <version>MUST require a non-empty<version>positional argument inprocess.argv[2](exiting2withUsage: bun scripts/check-release-date.ts <version>when missing) and readCHANGELOG.mdfromprocess.cwd()(exiting2whenCHANGELOG.mdcannot be read). It MUST scan lines ofCHANGELOG.mdfor the bracketed heading^## \[<escaped-version>\] - ([0-9]{4})-([0-9]{2})-([0-9]{2})[ \t]*$(unliketeam release check'schangelogCheck, unbracketed## <version> - YYYY-MM-DDheadings are not matched) and verify that the captured date is a real calendar date (UTC year/month/day round-trip), exiting0on match and1when the bracketed section is missing or its date is a placeholder (YYYY-MM-DD) or invalid calendar date. - (Source:
scripts/check-release-date.ts,.github/workflows/release.ymlatv0.5.0; proof:test/scripts/check-release-date.test.ts,test/release-workflow.test.ts.)
-
| Entry | Meaning | Since | Source at the tag | Note |
|---|---|---|---|---|
release.passed |
every check passed |
0.5.0 | contract/exit-codes.json |
team release check <pkg@ver> (exit 0) |
release.missing |
a check is missing |
0.5.0 | contract/exit-codes.json |
team release check <pkg@ver> (exit 1) |
release.unknown |
a check is unknown |
0.5.0 | contract/exit-codes.json |
team release check <pkg@ver> (exit 2) |
release.configuration |
the team file can't be read |
0.5.0 | contract/exit-codes.json |
team release check <pkg@ver> (exit 64) |
release.usage |
the invocation can't be read |
0.5.0 | contract/exit-codes.json |
team release check (exit 64) |
team release check is a stateless, read-only verification command over public registries (and an optional owner-interactive macOS Keychain read) and has no capability or operation gate row in src/permissions/map.ts at v0.5.0.
-
Linear pagination beyond 100 records:
RELEASE_RECORDS_QUERYrequestsfirst: 100forprojectMilestonesandprojectUpdates(includeArchived: true) and reportsunknown(Linear record is incomplete) whenpageInfo.hasNextPageis notfalse; whether cursor pagination or server-side filtering should be added when a project exceeds 100 milestones or status updates is open. -
Cross-platform credential store for Linear checks:
keychainReaderrequires macOS (/usr/bin/security) and an interactive terminal (stdinIsTTY === true), returningunknown(Keychain access was unavailable) on other platforms or headless sessions; whether a cross-platform credential helper is needed remains open.
-
Draft (2026-10-19): lifted from archived internal RFCs
0004-releases.mdand0005-release-records.mdand read againstsrc/commands/release.ts,src/file/sections/releases.ts,src/release/grammar.ts,src/release/http.ts,src/release/keychain.ts,src/release/checks.ts,schema/release.schema.json, andscripts/check-release-date.tsatv0.5.0 (52db9b75). -
Review fold (2026-10-19): aligned §3.4 Linear request headers, §4.3 and §5 Rule 7
scripts/check-release-date.tspositional<version>contract, §5 Rule 4 tag-chain 4-hop limit and exact detail strings, §5 Rule 5KEYCHAIN_TIMEOUT_MS/ LF-onlykeyOf/linearRecordproject guard, and §6contract/exit-codes.jsonmeanings withv0.5.0 (52db9b75).
Generated from docs/specs/release.md at v0.5.2 (1d1224a4) — edit the repository, not this page.