forge-workflow 0.1.0-beta.6 → 0.1.0-beta.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/bin/forge-gh-proxy.js +15 -0
- package/bin/forge-github-credential.js +21 -0
- package/bin/forge.js +16 -1
- package/docs/guides/MIGRATION.md +4 -3
- package/docs/reference/RELEASE.md +5 -3
- package/docs/reference/VALIDATION.md +14 -0
- package/docs/reference/github-accounts.md +114 -38
- package/lib/bun-workflow-pins.js +129 -3
- package/lib/commands/github.js +234 -49
- package/lib/commands/merge.js +2 -2
- package/lib/commands/push.js +308 -47
- package/lib/commands/release.js +18 -1
- package/lib/commands/shepherd.js +1 -1
- package/lib/commands/test.js +8 -0
- package/lib/commands/validate.js +219 -67
- package/lib/gh-proxy.js +309 -0
- package/lib/github-context.js +160 -25
- package/lib/github-credential.js +47 -0
- package/lib/github-router.js +774 -0
- package/lib/kernel/migrations.js +1 -1
- package/lib/kernel/sqlite-driver.js +1 -1
- package/lib/memory/router.js +1 -1
- package/lib/memory/usage-evidence.js +1 -1
- package/lib/native-gh.js +177 -0
- package/lib/npm-publish-workflow.js +5 -5
- package/lib/pr-monitor/flow-monitor.js +127 -28
- package/lib/pr-monitor/reconcile-executor.js +1 -1
- package/lib/project-memory.js +1 -1
- package/lib/protected-state-authority.js +214 -13
- package/lib/release-readiness.js +57 -41
- package/lib/shell-utils.js +41 -15
- package/lib/test-workflow.js +389 -0
- package/lib/validation-receipt.js +305 -0
- package/lib/workflow-templates/test.yml +654 -0
- package/node_modules/@forge/contracts/compatibility-matrix.v1.json +1 -0
- package/node_modules/@forge/contracts/contract-baseline.v1.json +134 -0
- package/node_modules/@forge/contracts/fixtures/v1/canonical-hash.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/contract-inputs.v1.json +71 -0
- package/node_modules/@forge/contracts/fixtures/v1/feedback-secret-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/identity-conflict.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/malformed-envelope-inputs.v1.json +16 -0
- package/node_modules/@forge/contracts/fixtures/v1/malformed-missing-required.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/monitor-bounds-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/monitor-receipt-privacy-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/not-executed-receipt.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/privacy-redaction.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/retry-identical.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/stale-authority-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/structured-error-privacy-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-live-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-test-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/unknown-advisory-roundtrip.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/unknown-consequential-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/valid-full.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/valid-minimal.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/wrong-capability-digest.json +1 -0
- package/node_modules/@forge/contracts/index.js +32 -0
- package/node_modules/@forge/contracts/package.json +35 -0
- package/node_modules/@forge/contracts/schemas/v1/capability-manifest.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/claim-request.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/context-packet.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/delivery-receipt.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/feedback-report.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/lease-receipt.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/monitor-event.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/monitor-receipt.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/run-receipt.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/structured-error.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/work-packet.schema.json +1 -0
- package/node_modules/@forge/contracts/src/baseline.js +23 -0
- package/node_modules/@forge/contracts/src/canonical.js +151 -0
- package/node_modules/@forge/contracts/src/definitions.js +176 -0
- package/node_modules/@forge/contracts/src/identity.js +42 -0
- package/node_modules/@forge/contracts/src/schema.js +72 -0
- package/node_modules/@forge/contracts/src/validate.js +305 -0
- package/node_modules/@forge/flow/index.js +114 -0
- package/node_modules/@forge/flow/package.json +34 -0
- package/node_modules/@forge/flow/src/bounded-loop.js +415 -0
- package/node_modules/@forge/flow/src/efficiency-supervisor.js +89 -0
- package/node_modules/@forge/flow/src/executor.js +279 -0
- package/node_modules/@forge/flow/src/monitor-durability.js +419 -0
- package/node_modules/@forge/flow/src/monitor-runtime.js +460 -0
- package/node_modules/@forge/flow/src/process-lifecycle.js +469 -0
- package/node_modules/@forge/flow/src/skill-runtime.js +343 -0
- package/node_modules/@forge/memory/index.js +331 -0
- package/node_modules/@forge/memory/package.json +34 -0
- package/node_modules/@forge/memory/src/authority-provider.js +67 -0
- package/node_modules/@forge/memory/src/backend-registry.js +166 -0
- package/node_modules/@forge/memory/src/feedback-intake.js +243 -0
- package/node_modules/@forge/memory/src/pr-lifecycle-authority.js +946 -0
- package/node_modules/@forge/memory/src/usage-evidence.js +205 -0
- package/package.json +12 -2
- package/packages/flow/node_modules/@forge/contracts/compatibility-matrix.v1.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/contract-baseline.v1.json +134 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/canonical-hash.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/contract-inputs.v1.json +71 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/feedback-secret-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/identity-conflict.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/malformed-envelope-inputs.v1.json +16 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/malformed-missing-required.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/monitor-bounds-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/monitor-receipt-privacy-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/not-executed-receipt.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/privacy-redaction.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/retry-identical.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/stale-authority-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/structured-error-privacy-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-live-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-test-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/unknown-advisory-roundtrip.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/unknown-consequential-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/valid-full.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/valid-minimal.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/wrong-capability-digest.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/index.js +32 -0
- package/packages/flow/node_modules/@forge/contracts/package.json +35 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/capability-manifest.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/claim-request.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/context-packet.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/delivery-receipt.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/feedback-report.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/lease-receipt.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/monitor-event.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/monitor-receipt.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/run-receipt.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/structured-error.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/work-packet.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/src/baseline.js +23 -0
- package/packages/flow/node_modules/@forge/contracts/src/canonical.js +151 -0
- package/packages/flow/node_modules/@forge/contracts/src/definitions.js +176 -0
- package/packages/flow/node_modules/@forge/contracts/src/identity.js +42 -0
- package/packages/flow/node_modules/@forge/contracts/src/schema.js +72 -0
- package/packages/flow/node_modules/@forge/contracts/src/validate.js +305 -0
- package/packages/memory/node_modules/@forge/contracts/compatibility-matrix.v1.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/contract-baseline.v1.json +134 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/canonical-hash.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/contract-inputs.v1.json +71 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/feedback-secret-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/identity-conflict.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/malformed-envelope-inputs.v1.json +16 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/malformed-missing-required.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/monitor-bounds-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/monitor-receipt-privacy-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/not-executed-receipt.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/privacy-redaction.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/retry-identical.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/stale-authority-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/structured-error-privacy-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-live-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-test-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/unknown-advisory-roundtrip.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/unknown-consequential-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/valid-full.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/valid-minimal.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/wrong-capability-digest.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/index.js +32 -0
- package/packages/memory/node_modules/@forge/contracts/package.json +35 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/capability-manifest.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/claim-request.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/context-packet.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/delivery-receipt.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/feedback-report.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/lease-receipt.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/monitor-event.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/monitor-receipt.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/run-receipt.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/structured-error.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/work-packet.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/src/baseline.js +23 -0
- package/packages/memory/node_modules/@forge/contracts/src/canonical.js +151 -0
- package/packages/memory/node_modules/@forge/contracts/src/definitions.js +176 -0
- package/packages/memory/node_modules/@forge/contracts/src/identity.js +42 -0
- package/packages/memory/node_modules/@forge/contracts/src/schema.js +72 -0
- package/packages/memory/node_modules/@forge/contracts/src/validate.js +305 -0
- package/scripts/check-forge-token.js +85 -67
- package/scripts/gen-embedded-assets.mjs +31 -5
- package/scripts/test-full-suite.js +69 -15
- package/scripts/test.js +11 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,41 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
> **Note**: `/check` was renamed to `/validate` and `/merge` was renamed to `/premerge` in v0.0.3. Historical entries below may use the old names.
|
|
9
9
|
|
|
10
|
+
## [Unreleased]
|
|
11
|
+
|
|
12
|
+
## [0.1.0-beta.8] - 2026-09-26
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **Deterministic full generation for the test workflow.** `forge release generate-test-workflow --expect-head <sha>` renders `.github/workflows/test.yml` from its canonical staged template and Bun pin under an exact actor, worktree, source-HEAD, path, operation, and content-bound authorization. Bun pin updates delegate this workflow to the same renderer, preventing partial pin replacement from authorizing unrelated CI changes. (PR #567; issue `a541c8ca-579d-4b8e-9bfd-7980b7547302`)
|
|
17
|
+
- **Optional transparent GitHub account routing per clone.** `forge github use <login> --auto` extends the existing secure account binding to shell-resolved `gh` and HTTPS Git commands when Forge's router is first on PATH, so concurrent terminals and harness shells can use different native GitHub CLI accounts without per-launch wrapping. Unbound and non-enabled clones pass through unchanged; local-only commands and `gh auth` remain native, while network-capable aliases and extensions inherit the selected account. V1 is public `github.com` only; built-in destination conflicts and non-public targets fail closed, and arbitrary user-installed code remains outside Forge's wrong-destination guarantee. The machine-local router registry must refuse uninstall while another verified clone remains enabled and must never store credentials. Release acceptance requires the installed PATH chain and warm median targets of 250 ms for unbound/local-only routing and 1,000 ms for bound `gh` and helper resolution. Forge never stores a token. (PR #563; issue `8168bee5-b74e-44e5-8fd8-1b9ccb917c1d`)
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- **The npm package installs with Bun.** `@forge/flow`, `@forge/memory`, and `@forge/contracts` are now only bundled in `forge-workflow`, not also declared as registry `dependencies`; Bun resolved those entries from the registry, where the unpublished workspaces return 404, so `bun add forge-workflow@0.1.0-beta.7` failed. The workspaces stay linked for development as `devDependencies`, and npm installs are unchanged. The packed-install smoke now also installs the tarball with `bun add` and runs `forge --version` and `forge setup --quick --yes`. (issue `95ed8d5f-0578-48de-bc44-17b5afa48161`)
|
|
22
|
+
- **Canonical validation exposes an honest full-suite resource budget.** `forge validate --shards N` forwards a positive integer budget to the Forge scheduler. Windows budgets that cannot fund a required worker fail before test workers start; automatic defaults normalize visibly. Requested/effective or rejection evidence is retained and displayed by the CLI without changing required tests, deadlines, or receipt identity. (#574, issue `e0cb0671-735c-4980-944b-286d6f78fe48`)
|
|
23
|
+
- **Standalone package smoke installs identify packed packages before npm builds the install tree.** The fixture now carries each package name from the same successful `npm pack --json` result into its local tarball specification, avoiding npm's redundant add-request name discovery while preserving tarball extraction, bundled workspaces, installed identity checks, CLI version/setup coverage, and the existing deadlines. (issue `6b432d06-5ab3-4188-8814-0ae056430cac`)
|
|
24
|
+
- **Exact version queries avoid loading the command graph.** Direct `forge --version` and `forge -V` print the packaged version before loading command modules, reducing cold startup during standalone installation checks. Imported helpers and mixed command arguments keep their existing behavior. (issue `6b432d06-5ab3-4188-8814-0ae056430cac`)
|
|
25
|
+
- **The workflow projection hook test avoids a duplicate Git scan.** It retains the real commit-hook rejection and verifies the exact projection mismatch and zero authority consumption through existing fixture readers, without changing test deadlines or production authorization. (issue `e70683d6-f8df-471b-b859-a6dacb69825c`)
|
|
26
|
+
- **Segmented Flow monitor checkpoints no longer repeat full-tail compression searches.** Recovery planning bounds records once and adapts from the last accepted segment width while preserving payload limits, ordered records, integrity metadata, terminal snapshot placement, and fail-closed interrupted-plan recovery. (issues `9eb98ac0-ebe1-4604-a57a-5fefadad6682`, `6648ef9b-381a-4a19-ad9a-06341e62676f`)
|
|
27
|
+
- **The failed full-suite child regression now selects only its test gate.** Its synthetic validation fixture still proves fail-closed classification and stale receipt deletion without running unrelated repository checks. (issue `3ce7c502-ccaf-46e6-af12-961ff5ffd2e7`)
|
|
28
|
+
- **The smart-status CRLF fixture avoids a redundant wrapper on compatible Windows jq executables.** It verifies genuine CRLF bytes before selecting native jq, preserves the CRLF-producing fallback for LF-only commands and non-Windows hosts, and propagates jq failures through that wrapper without changing the 20-second test deadline or production status behavior. (issue `c8ece1ef-e410-45b8-98bc-ca184fa4f828`)
|
|
29
|
+
- **Forge pushes use the supervised full-suite runner when validation cannot be reused.** Consumer repositories retain their configured test command, and failed or incomplete runs prevent the push. Git arguments retain their order across Forge's passthrough delimiter. (issues `11e5ef49-5ae3-4cd0-a32e-0f233fc00ce8`, `e8b5e91f-d9f3-40e3-921a-e12c392054f1`)
|
|
30
|
+
- **Slow pre-push hooks no longer expire authorization for completed checks.** Reuse is bound to the live push, its worktree, branch, unchanged inputs, and completed gates; ended or mismatched invocations run the ordinary hooks. Quick pushes continue to omit tests explicitly. (issue `291d09b0-b3c4-41a6-8fa0-6b843e7fad05`)
|
|
31
|
+
- **The test workflow template marker no longer resembles a credential assignment.** Internal renderer code now describes `__BUN_VERSION__` as a placeholder, so the shipped-library secret scan accepts the deterministic workflow writer without weakening its detection rules or changing generated workflow bytes. (issue `0acc4a68-ab0a-4008-83bf-6e954d075007`)
|
|
32
|
+
|
|
33
|
+
## [0.1.0-beta.7] - 2026-09-11
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- **A clean full validation is reused by the next unchanged `forge push`.** Forge stores a one-hour, signed local receipt bound to the canonical worktree, exact HEAD, clean state, Forge, Bun, and Node runtimes, and complete nonzero gate results. Branch protection and lint still run; invalid or stale evidence falls back to the full suite, while raw Git pushes and CI remain independent.
|
|
38
|
+
- **A local full suite no longer reruns its already-included E2E directory.** Targeted E2E execution is unchanged, while full validation avoids an exact duplicate. (issue `4aed2cc8-57eb-451b-bb3a-02bbdd93af49`)
|
|
39
|
+
- **The shared public contract package is now `@forge/contracts`.** The product-neutral name replaces the former unpublished Memory-qualified name before beta.7, and all three scoped packages explicitly publish as public MIT packages linked to the public Forge repository. (issue `4d41ffb7-8793-4a17-a30b-92130db69672`)
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- **The npm package now includes every internal runtime workspace.** Root runtime imports use the `@forge/memory`, `@forge/contracts`, and `@forge/flow` package boundaries, and npm bundles those unpublished workspaces into `forge-workflow`. The installed-package smoke accepts npm 10 lifecycle output before its JSON payload, so Node 22 verifies the same package correctly. A fresh packed install now runs `forge --version` and `forge setup --quick --yes`, preventing the `MODULE_NOT_FOUND` failure in v0.1.0-beta.6. (issue `95372af8-da00-42e0-af64-0111ddab3405`)
|
|
44
|
+
|
|
10
45
|
## [0.1.0-beta.6] - 2026-09-11
|
|
11
46
|
|
|
12
47
|
### Added
|
|
@@ -23,6 +58,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
23
58
|
|
|
24
59
|
### Fixed
|
|
25
60
|
|
|
61
|
+
- **Bun workflow commit-boundary validation avoids redundant Windows process startup.** Protected-state checks batch the complete source/index pin snapshot into four Git calls and reuse successful executable resolution for an unchanged command environment; Windows full-suite scheduling defers unit work until subprocess workers release the machine, preserving every assertion and fail-closed check without increasing timeouts. (#557, issue `c56c483c-34ef-4354-a976-030464ac416e`)
|
|
26
62
|
- **Legacy claim repair now compares exact Windows file identities and timestamps.** BigInt filesystem metadata prevents rounded device/inode collisions from treating distinct files as aliases, while nanosecond timestamps keep recovery-state drift checks fail closed. (#556, issue `7e164066-4d41-4d14-90ef-1b7ee14e2b73`)
|
|
27
63
|
- **Dep-guard apply-decision tests no longer contend with shared Windows subprocess shards.** The process-heavy suite now uses the existing exclusive resource lane, with exact scheduler coverage and no timeout increase or exemption. (issue `762ae565-0059-4827-9c3d-b4249f2b3364`)
|
|
28
64
|
- **Bun workflow proof tests no longer contend with shared full-suite shards.** The process-heavy commit-boundary proof now uses the existing exclusive resource lane, with exact scheduler coverage and no timeout increase or exemption. (#552, issue `2daab8c0-34d6-4378-8cf4-dce6fdbfb86b`)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { isLocalGhInvocation, runNativeGh } = require('../lib/native-gh');
|
|
5
|
+
|
|
6
|
+
function runProxyEntrypoint(args, projectRoot = process.cwd(), options = {}) {
|
|
7
|
+
const ghArgs = args[0] === '--' ? args.slice(1) : args;
|
|
8
|
+
if (isLocalGhInvocation(ghArgs)) return (options.runNative || runNativeGh)(ghArgs, projectRoot, options);
|
|
9
|
+
const runProxy = options.runProxy || require('../lib/gh-proxy').runGhProxy;
|
|
10
|
+
return runProxy(ghArgs, projectRoot);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (require.main === module) process.exitCode = runProxyEntrypoint(process.argv.slice(2));
|
|
14
|
+
|
|
15
|
+
module.exports = { runProxyEntrypoint };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
|
|
6
|
+
function runCredentialEntrypoint(args, projectRoot = process.cwd(), options = {}) {
|
|
7
|
+
const operation = args[0] === '--' ? args[1] : args[0];
|
|
8
|
+
if (!['get', 'store', 'erase'].includes(operation)) return 1;
|
|
9
|
+
try {
|
|
10
|
+
const input = operation === 'get' ? (options.readInput || (() => fs.readFileSync(0, 'utf8')))() : '';
|
|
11
|
+
const runCredential = options.runCredential || require('../lib/github-credential').runCredentialHelper;
|
|
12
|
+
return runCredential(operation, { input, projectRoot });
|
|
13
|
+
} catch {
|
|
14
|
+
(options.writeError || (value => process.stderr.write(value)))('Forge could not read the Git credential request.\n');
|
|
15
|
+
return 1;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (require.main === module) process.exitCode = runCredentialEntrypoint(process.argv.slice(2));
|
|
20
|
+
|
|
21
|
+
module.exports = { runCredentialEntrypoint };
|
package/bin/forge.js
CHANGED
|
@@ -46,6 +46,11 @@ const { execSync } = require('node:child_process');
|
|
|
46
46
|
const packageDir = path.dirname(__dirname);
|
|
47
47
|
const packageJson = require('../package.json');
|
|
48
48
|
const VERSION = packageJson.version;
|
|
49
|
+
const args = process.argv.slice(2);
|
|
50
|
+
|
|
51
|
+
if (require.main === module && args.length === 1 && ['--version', '-V'].includes(args[0])) {
|
|
52
|
+
console.log(`Forge v${VERSION}`);
|
|
53
|
+
} else {
|
|
49
54
|
|
|
50
55
|
// Load PluginManager for discoverable agent architecture
|
|
51
56
|
const PluginManager = require('../lib/plugin-manager');
|
|
@@ -104,7 +109,6 @@ const { detectHusky, migrateHusky } = require('../lib/husky-migration');
|
|
|
104
109
|
|
|
105
110
|
// Get the project root (let allows reassignment after --path flag handling)
|
|
106
111
|
let projectRoot = process.env.INIT_CWD || process.cwd();
|
|
107
|
-
const args = process.argv.slice(2);
|
|
108
112
|
|
|
109
113
|
// Incremental setup state (set during main() from parsed flags)
|
|
110
114
|
let FORCE_MODE = false;
|
|
@@ -3650,6 +3654,16 @@ async function handleExternalServices(skipExternal, selectedAgents) {
|
|
|
3650
3654
|
|
|
3651
3655
|
async function main() {
|
|
3652
3656
|
let command = args[0];
|
|
3657
|
+
if (command === 'github' && args[1] === 'credential') {
|
|
3658
|
+
const { runCredentialEntrypoint } = require('./forge-github-credential');
|
|
3659
|
+
process.exitCode = runCredentialEntrypoint(args.slice(2), process.cwd());
|
|
3660
|
+
return;
|
|
3661
|
+
}
|
|
3662
|
+
if (command === 'github' && args[1] === 'proxy' && args[2] === '--') {
|
|
3663
|
+
const { runProxyEntrypoint } = require('./forge-gh-proxy');
|
|
3664
|
+
process.exitCode = runProxyEntrypoint(args.slice(2), process.cwd());
|
|
3665
|
+
return;
|
|
3666
|
+
}
|
|
3653
3667
|
const flags = parseFlags();
|
|
3654
3668
|
const suppressJsonIntrospectionOutput = ['options', 'explain'].includes(command) && args.includes('--json');
|
|
3655
3669
|
const suppressCommandJsonOutput = args.includes('--json');
|
|
@@ -4520,3 +4534,4 @@ module.exports = {
|
|
|
4520
4534
|
validateDirectoryPathInput,
|
|
4521
4535
|
validateUserInput,
|
|
4522
4536
|
};
|
|
4537
|
+
}
|
package/docs/guides/MIGRATION.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Migration Guide
|
|
2
2
|
|
|
3
|
-
Use this guide when moving older Forge docs, habits, or installed scaffolding toward the v0.1.0-beta.
|
|
3
|
+
Use this guide when moving older Forge docs, habits, or installed scaffolding toward the v0.1.0-beta.8 public framing.
|
|
4
4
|
|
|
5
5
|
## What Changed
|
|
6
6
|
|
|
@@ -39,8 +39,9 @@ forge setup --agents claude,cursor
|
|
|
39
39
|
|
|
40
40
|
## Version Labels
|
|
41
41
|
|
|
42
|
-
- `0.1.0-beta.
|
|
43
|
-
- `0.1.0-beta.
|
|
42
|
+
- `0.1.0-beta.8` is the current prerelease package version.
|
|
43
|
+
- `0.1.0-beta.7` is the previous prerelease package version; npm installs it, but Bun cannot, because it also declares the bundled runtime workspaces as registry dependencies. Upgrade directly to beta.8.
|
|
44
|
+
- `0.1.0-beta.6` omits required runtime workspaces from its npm tarball; do not install it.
|
|
44
45
|
- Internal labels such as `0.0.19` or `v3` describe roadmap slices or historical codenames. Do not present them as current package versions.
|
|
45
46
|
|
|
46
47
|
## Safe Upgrade Path
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
This page documents release readiness. Package publishing still requires the explicit publish step after merge.
|
|
4
4
|
|
|
5
|
-
## v0.1.0-beta.
|
|
5
|
+
## v0.1.0-beta.8 Boundary
|
|
6
6
|
|
|
7
|
-
v0.1.0-beta.
|
|
7
|
+
v0.1.0-beta.8 is the current prerelease boundary. The release branch declares `0.1.0-beta.8`; publish only after the release PR is merged, tagged, and validated on that exact SHA.
|
|
8
8
|
|
|
9
9
|
Keep these release steps explicit:
|
|
10
10
|
|
|
@@ -25,6 +25,8 @@ npm pack --dry-run
|
|
|
25
25
|
|
|
26
26
|
The trusted npm workflow is generated through `forge release generate-npm-workflow --expect-head "$(git rev-parse HEAD)"`; the full expected SHA must match the current checkout, and direct workflow edits remain blocked. The generated authorization is bound to that SHA, actor, worktree, path, and exact content, so moving HEAD before staging also fails closed. On a release event, the workflow resolves the tag once, runs the complete supported repository suite on that immutable commit, and allows publication only when the attributable suite receipt, verification checkout, and publish checkout all name the same SHA.
|
|
27
27
|
|
|
28
|
+
Generate the canonical test workflow through `forge release generate-test-workflow --expect-head "$(git rev-parse HEAD)"`. The command renders the staged `lib/workflow-templates/test.yml` with the staged manifest's exact Bun version, rejects unrelated target bytes, and binds the protected write to the expected HEAD and exact generated content. For a Bun version change, use `forge release update-bun-pins --expect-head "$(git rev-parse HEAD)"`; it delegates `test.yml` to the same full renderer while preserving the pin-only updates for the other workflows.
|
|
29
|
+
|
|
28
30
|
For docs-heavy changes, also run a Markdown link check if available. If no docs checker exists and adding one would broaden the PR, create a follow-up issue instead.
|
|
29
31
|
|
|
30
32
|
## Packaging Check
|
|
@@ -43,7 +45,7 @@ Release notes should include:
|
|
|
43
45
|
- adapter compatibility
|
|
44
46
|
- DeepWiki refresh checklist
|
|
45
47
|
|
|
46
|
-
The v0.1.0-beta.
|
|
48
|
+
The v0.1.0-beta.8 release notes live in [CHANGELOG.md](../../CHANGELOG.md).
|
|
47
49
|
|
|
48
50
|
## Rollback
|
|
49
51
|
|
|
@@ -19,6 +19,20 @@ bun run check
|
|
|
19
19
|
|
|
20
20
|
Security audit behavior distinguishes blocking high/critical vulnerabilities from lower-severity warnings.
|
|
21
21
|
|
|
22
|
+
## Canonical Validation Resource Budget
|
|
23
|
+
|
|
24
|
+
In a Forge source checkout, run the receipt-producing command from a clean, committed worktree:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
forge validate --shards 2
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`--shards N` accepts a positive integer resource budget for the full-suite scheduler. Unit workers cost one unit; on Windows, subprocess and exclusive workers cost two. An explicit budget below a required worker's cost fails before test workers start. Omitting the flag selects an automatic budget, which may increase to the minimum needed to run every lane. Output records the requested and effective budget, or the requested and minimum budget on rejection.
|
|
31
|
+
|
|
32
|
+
These weights govern Forge's lane workers; they do not impose an operating-system limit on every process a test may create. The budget changes scheduling, not required test coverage or deadlines. Consumer repositories cannot use this flag because their test commands do not implement Forge's scheduler contract.
|
|
33
|
+
|
|
34
|
+
A successful full validation can be reused by the next unchanged `forge push` under the existing receipt checks. Changing the source or validation inputs requires fresh evidence.
|
|
35
|
+
|
|
22
36
|
## Supporting Commands
|
|
23
37
|
|
|
24
38
|
```bash
|
|
@@ -28,74 +28,123 @@ from that session before interactive login; do not print their values.
|
|
|
28
28
|
Inside the work clone:
|
|
29
29
|
|
|
30
30
|
```sh
|
|
31
|
-
forge github use WORK_LOGIN
|
|
31
|
+
forge github use WORK_LOGIN --auto
|
|
32
32
|
forge github status
|
|
33
|
-
|
|
33
|
+
codex
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
Inside the personal clone, in another terminal:
|
|
37
37
|
|
|
38
38
|
```sh
|
|
39
|
-
forge github use PERSONAL_LOGIN
|
|
39
|
+
forge github use PERSONAL_LOGIN --auto
|
|
40
40
|
forge github status
|
|
41
|
-
|
|
41
|
+
t3
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
Replace the uppercase labels with your GitHub logins. Both sessions can remain
|
|
45
45
|
open together. Substitute any installed, trusted CLI harness or shell for `codex`
|
|
46
46
|
or `t3`, such as `claude`, `pwsh`, or `bash`.
|
|
47
47
|
|
|
48
|
-
`use` verifies the named stored account and repository access,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
48
|
+
`use --auto` verifies the named stored account and repository access, installs a
|
|
49
|
+
marked router beside the current Forge launcher, then writes only clone-local Git
|
|
50
|
+
settings: the account selector, an automatic-routing marker, and an absolute
|
|
51
|
+
GitHub HTTPS credential-helper route. It refuses to overwrite a non-Forge `gh`
|
|
52
|
+
launcher in that directory. It does not store a credential, log in, switch the
|
|
53
|
+
globally active account, or modify another clone. Linked Git
|
|
54
|
+
worktrees normally share this clone-local configuration; use separate clones when
|
|
55
|
+
you need separate bindings. The machine-local clone registration is durable before
|
|
56
|
+
automatic routing is enabled; disabling routing removes the clone setting before
|
|
57
|
+
its registry entry. If either ordering step cannot complete, automatic routing
|
|
58
|
+
stays disabled or fails closed.
|
|
59
|
+
|
|
60
|
+
The opt-in `gh` router reads the clone marker on each invocation. In an enabled
|
|
61
|
+
clone it selects the named account for the real GitHub CLI process; elsewhere it
|
|
62
|
+
passes through unchanged. Zero-argument, help, completion, configuration, and
|
|
63
|
+
other local-only `gh` operations also bypass account resolution. The complete
|
|
64
|
+
`gh auth` namespace always passes through so login and recovery remain native.
|
|
65
|
+
Network-capable built-in commands, configured aliases, and installed extensions
|
|
66
|
+
inherit the selected child account; they do not trigger per-command account
|
|
67
|
+
discovery. Built-in destinations from command arguments, URLs, `GH_HOST`,
|
|
68
|
+
`GH_REPO`, and repository inference are checked for conflicts and public-host
|
|
69
|
+
support before a credential is injected. Arbitrary code inside a user-installed
|
|
70
|
+
alias or extension is outside Forge's wrong-destination guarantee. HTTPS Git uses
|
|
71
|
+
the same account through the clone-local helper. SSH key selection remains
|
|
72
|
+
controlled by SSH configuration.
|
|
73
|
+
Enablement stops if another `gh` resolves before Forge on `PATH`, instead of
|
|
74
|
+
claiming automatic routing when the router cannot run. Run automatic setup from
|
|
75
|
+
an installed Forge command; transient `npx`/`bunx` package-runner shims are ignored.
|
|
76
|
+
|
|
77
|
+
Repositories can therefore be opened normally in T3 Code, VS Code, terminals,
|
|
78
|
+
Codex, or Claude Code. Shell-based `gh` and HTTPS Git commands started with that
|
|
79
|
+
clone as their working directory route automatically when the application PATH
|
|
80
|
+
resolves Forge's router first. Start a new terminal or application after changing
|
|
81
|
+
PATH order; POSIX shells may also need `hash -r`. A program that invokes an
|
|
82
|
+
absolute `gh` executable or bypasses shell command resolution cannot be
|
|
83
|
+
intercepted and must use `forge github run -- PROGRAM` or its own account setting.
|
|
84
|
+
|
|
85
|
+
`forge github use LOGIN` without `--auto` preserves the original explicit mode.
|
|
86
|
+
Supported Forge routes use the binding, and arbitrary trusted children can still
|
|
87
|
+
be launched with `forge github run -- PROGRAM`.
|
|
62
88
|
|
|
63
89
|
## Check or undo
|
|
64
90
|
|
|
65
91
|
```sh
|
|
66
92
|
forge github status --json
|
|
93
|
+
forge github auto --disable
|
|
67
94
|
forge github unset
|
|
95
|
+
forge github router --uninstall --force
|
|
68
96
|
```
|
|
69
97
|
|
|
70
98
|
Status reports the selected and verified login, clone-local binding source,
|
|
71
|
-
effective Git author, origin transport, helper classification, and repository
|
|
99
|
+
router reachability, effective Git author, origin transport, helper classification, and repository
|
|
72
100
|
access. It does not print tokens, raw remote URLs, or raw credential-helper
|
|
73
101
|
commands. Treat author and account labels as personal information when sharing
|
|
74
102
|
diagnostics.
|
|
75
103
|
|
|
76
|
-
`
|
|
77
|
-
|
|
78
|
-
|
|
104
|
+
`auto --disable` removes only Forge-owned transparent routing and keeps the clone
|
|
105
|
+
binding. `unset` is safe to repeat and removes both the binding and Forge-owned
|
|
106
|
+
routing. Forge records only canonical clone and Git common-directory paths in a
|
|
107
|
+
machine-local registry under the router lock; it stores no credentials. Current
|
|
108
|
+
enabled state comes from each clone's live `github.auto` value, and router
|
|
109
|
+
ownership comes from the launcher marker. Missing or corrupt registry state, an
|
|
110
|
+
enabled clone absent from the registry, or an unavailable registered clone fails
|
|
111
|
+
closed and leaves the shared router in place. The shared router remains while
|
|
112
|
+
another clone uses it and passes through unchanged outside enabled clones. When
|
|
113
|
+
`router --uninstall --force` runs, Forge prunes only entries it can prove are
|
|
114
|
+
disabled and refuses to remove marked router files while any verified clone
|
|
115
|
+
remains. None of these commands removes a
|
|
116
|
+
stored login or revokes authority from a running child: close that process after
|
|
117
|
+
changing or removing a binding.
|
|
118
|
+
|
|
119
|
+
The safe order is: disable or unset every opted-in clone, run
|
|
120
|
+
`router --uninstall --force` last, then uninstall Forge itself. Package managers
|
|
121
|
+
cannot clean clone-local Git configuration after the executable is gone. If that
|
|
122
|
+
order was missed, reinstall Forge and run `forge github auto --disable` or
|
|
123
|
+
`forge github unset`; these commands remove only Forge-owned configuration. Do not
|
|
124
|
+
use `git config --unset-all` because another local HTTPS helper may share the same
|
|
125
|
+
key.
|
|
79
126
|
|
|
80
127
|
## Three separate identities
|
|
81
128
|
|
|
82
129
|
| Identity | Controlled by |
|
|
83
130
|
| --- | --- |
|
|
84
|
-
| GitHub API account used by
|
|
131
|
+
| GitHub API account used by Forge and automatic `gh` routes | Clone-local `github.account` + `github.auto` |
|
|
85
132
|
| Commit author | Git `user.name` and `user.email` |
|
|
86
133
|
| Fetch/push authentication | SSH configuration or the HTTPS credential helper |
|
|
87
134
|
|
|
88
|
-
Forge does not change your author, origin, SSH keys
|
|
135
|
+
Forge does not change your author, origin, or SSH keys. Automatic mode owns only
|
|
136
|
+
the clone-local GitHub HTTPS helper it installs and refuses to replace another
|
|
137
|
+
clone-local helper.
|
|
89
138
|
Existing SSH keys and host aliases remain valid; custom aliases must resolve to
|
|
90
|
-
`github.com` for Forge's repository-access check.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
139
|
+
`github.com` for Forge's repository-access check. V1 automatic routing supports
|
|
140
|
+
public `github.com` only. An explicit non-public `--hostname` or remote host fails
|
|
141
|
+
closed before the selected public credential is injected; GitHub Enterprise
|
|
142
|
+
routing remains native and outside this feature. HTTPS and SSH GitHub.com origins
|
|
143
|
+
are supported; insecure HTTP/Git transports are not V1 targets.
|
|
144
|
+
|
|
145
|
+
Automatic mode routes canonical GitHub HTTPS remotes through the selected native
|
|
146
|
+
GitHub CLI account. SSH key selection remains independent; keep separate SSH
|
|
147
|
+
keys/aliases when using SSH. See
|
|
99
148
|
[GitHub's multi-account transport guide](https://docs.github.com/en/account-and-profile/how-tos/account-management/managing-multiple-accounts).
|
|
100
149
|
|
|
101
150
|
## Platforms and recovery
|
|
@@ -119,16 +168,43 @@ and `--path`. Git Bash is still required for Forge's existing Windows Bash helpe
|
|
|
119
168
|
- **Unsupported GitHub CLI:** upgrade `gh` to a version supporting named-account
|
|
120
169
|
token retrieval. Unbound projects do not need that capability.
|
|
121
170
|
- **Program not found:** install the harness and check its executable name/PATH.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
171
|
+
- **Router conflict:** another `gh` launcher already exists beside Forge. Forge
|
|
172
|
+
leaves it untouched; move or remove it deliberately before retrying.
|
|
173
|
+
- **Router shadowed:** put Forge's launcher directory before the native GitHub CLI
|
|
174
|
+
directory on PATH, restart the application, and rerun `use --auto`.
|
|
175
|
+
- **Explicit repository override:** `gh -R` and `gh --repo` still use the account
|
|
176
|
+
bound to the current working clone; run them from the clone whose identity you intend.
|
|
177
|
+
- **Configured `gh` alias or extension:** network-capable aliases and extensions inherit
|
|
178
|
+
the selected clone account. Local-only or account-management commands retain their
|
|
179
|
+
native bypass rules. Forge cannot guarantee the destination of arbitrary code inside
|
|
180
|
+
an opaque alias or extension; inspect it yourself if it targets another host.
|
|
181
|
+
|
|
182
|
+
The supported workflow entrypoints are `forge`, `forge-workflow`, and a compiled
|
|
183
|
+
Forge executable whose directory is on PATH; it installs the router beside itself. Rerun `use --auto`
|
|
184
|
+
after moving a compiled executable or changing Forge installation managers so
|
|
185
|
+
the marked launchers refresh their target. Direct developer invocation of `node bin/forge-cmd.js` is an
|
|
125
186
|
internal legacy utility, not an account-isolated V1 entrypoint.
|
|
126
187
|
|
|
188
|
+
## Performance and installed-product acceptance
|
|
189
|
+
|
|
190
|
+
Automatic routing is designed to be warm and local on the common path. Release
|
|
191
|
+
acceptance measures the added latency over at least five warm runs, excluding the
|
|
192
|
+
native command itself: a median of at most 250 ms for unbound or local-only
|
|
193
|
+
routing, 1,000 ms for a bound `gh` route before native command work, and 1,000 ms
|
|
194
|
+
for credential-helper resolution. These are acceptance targets, not a promise
|
|
195
|
+
about a slow machine or the remote GitHub operation.
|
|
196
|
+
|
|
197
|
+
The installed-product acceptance run must exercise the generated PATH launchers
|
|
198
|
+
and the real Forge router, proxy, and credential-helper chain in two concurrent
|
|
199
|
+
clone-local contexts, plus unbound and fail-closed cases. The native `gh` process
|
|
200
|
+
may be substituted only at that process boundary; Forge itself is not substituted.
|
|
201
|
+
The release run uses two securely stored accounts and never prints their tokens.
|
|
202
|
+
|
|
127
203
|
## CuraPod adoption
|
|
128
204
|
|
|
129
205
|
Only after this Forge feature is merged, the merged build is installed, and its
|
|
130
206
|
acceptance checks pass: open each intended CuraPod clone, run
|
|
131
|
-
`forge github use WORK_LOGIN`, verify `forge github status`, then
|
|
132
|
-
chosen harness
|
|
207
|
+
`forge github use WORK_LOGIN --auto`, verify `forge github status`, then open the
|
|
208
|
+
chosen harness normally. Keep personal clones separately bound
|
|
133
209
|
to `PERSONAL_LOGIN`. This example does not discover or modify existing CuraPod
|
|
134
210
|
repositories, remotes, keys, or accounts automatically.
|
package/lib/bun-workflow-pins.js
CHANGED
|
@@ -19,7 +19,6 @@ const BUN_WORKFLOW_SPECS = Object.freeze([
|
|
|
19
19
|
{ path: '.github/workflows/pr-monitor.yml', key: 'bun-version', count: 1 },
|
|
20
20
|
{ path: '.github/workflows/required-checks-bypass.yml', key: 'bun-version', count: 1 },
|
|
21
21
|
{ path: '.github/workflows/size-check.yml', key: 'bun-version', count: 1 },
|
|
22
|
-
{ path: '.github/workflows/test.yml', key: 'bun-version', count: 8 },
|
|
23
22
|
{ path: '.github/workflows/yaml-lint.yml', key: 'bun-version', count: 1 },
|
|
24
23
|
].map(Object.freeze));
|
|
25
24
|
|
|
@@ -128,6 +127,76 @@ function readSourcePackageManifest(projectRoot, sourceHead, execGit = secureExec
|
|
|
128
127
|
return Buffer.from(execGit('git', ['show', `${sourceHead}:package.json`], { ...options, encoding: 'buffer' }));
|
|
129
128
|
}
|
|
130
129
|
|
|
130
|
+
function parseRegularEntries(raw, expectedPaths, pattern, errorForPath) {
|
|
131
|
+
const entries = new Map();
|
|
132
|
+
for (const line of raw.trim().split(/\r?\n/).filter(Boolean)) {
|
|
133
|
+
const match = pattern.exec(line);
|
|
134
|
+
if (match) entries.set(match[2], match[1]);
|
|
135
|
+
}
|
|
136
|
+
for (const filePath of expectedPaths) {
|
|
137
|
+
if (!entries.has(filePath)) throw new Error(errorForPath(filePath));
|
|
138
|
+
}
|
|
139
|
+
return entries;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function readBlobBatch(projectRoot, objectIds, execGit) {
|
|
143
|
+
const output = Buffer.from(execGit('git', ['cat-file', '--batch'], {
|
|
144
|
+
cwd: projectRoot,
|
|
145
|
+
encoding: 'buffer',
|
|
146
|
+
input: Buffer.from(`${objectIds.join('\n')}\n`),
|
|
147
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
148
|
+
windowsHide: true,
|
|
149
|
+
}));
|
|
150
|
+
const blobs = [];
|
|
151
|
+
let offset = 0;
|
|
152
|
+
for (const expectedId of objectIds) {
|
|
153
|
+
const headerEnd = output.indexOf(0x0a, offset);
|
|
154
|
+
if (headerEnd < 0) throw new Error(`Git did not return blob metadata for ${expectedId}.`);
|
|
155
|
+
const match = /^([0-9a-f]+) blob (\d+)$/.exec(output.subarray(offset, headerEnd).toString('utf8'));
|
|
156
|
+
if (!match || match[1] !== expectedId) throw new Error(`Git did not return the expected blob ${expectedId}.`);
|
|
157
|
+
const contentStart = headerEnd + 1;
|
|
158
|
+
const contentEnd = contentStart + Number(match[2]);
|
|
159
|
+
if (contentEnd >= output.length || output[contentEnd] !== 0x0a) {
|
|
160
|
+
throw new Error(`Git returned an incomplete blob ${expectedId}.`);
|
|
161
|
+
}
|
|
162
|
+
blobs.push(Buffer.from(output.subarray(contentStart, contentEnd)));
|
|
163
|
+
offset = contentEnd + 1;
|
|
164
|
+
}
|
|
165
|
+
if (offset !== output.length) throw new Error('Git returned unexpected extra blob data.');
|
|
166
|
+
return blobs;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function readCompleteBunPinBatch(projectRoot, sourceHead, extraIndexedPaths = [], execGit = secureExecFileSync) {
|
|
170
|
+
const sourcePaths = ['package.json', ...BUN_WORKFLOW_SPECS.map(spec => spec.path)];
|
|
171
|
+
const indexedPaths = [...sourcePaths, ...extraIndexedPaths];
|
|
172
|
+
const options = {
|
|
173
|
+
cwd: projectRoot,
|
|
174
|
+
encoding: 'utf8',
|
|
175
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
176
|
+
windowsHide: true,
|
|
177
|
+
};
|
|
178
|
+
const sourceEntries = parseRegularEntries(
|
|
179
|
+
execGit('git', ['ls-tree', sourceHead, '--', ...sourcePaths], options),
|
|
180
|
+
sourcePaths,
|
|
181
|
+
/^100(?:644|755) blob ([0-9a-f]+)\t(.+)$/,
|
|
182
|
+
filePath => filePath === 'package.json'
|
|
183
|
+
? `package.json is not a regular file at ${sourceHead}.`
|
|
184
|
+
: `${filePath} is not a regular workflow file at ${sourceHead}.`,
|
|
185
|
+
);
|
|
186
|
+
const indexedEntries = parseRegularEntries(
|
|
187
|
+
execGit('git', ['ls-files', '--stage', '--', ...indexedPaths], options),
|
|
188
|
+
indexedPaths,
|
|
189
|
+
/^100(?:644|755) ([0-9a-f]+) 0\t(.+)$/,
|
|
190
|
+
filePath => `${filePath} is not one regular file in the Git index.`,
|
|
191
|
+
);
|
|
192
|
+
const sourceBlobs = readBlobBatch(projectRoot, sourcePaths.map(filePath => sourceEntries.get(filePath)), execGit);
|
|
193
|
+
const indexedBlobs = readBlobBatch(projectRoot, indexedPaths.map(filePath => indexedEntries.get(filePath)), execGit);
|
|
194
|
+
return {
|
|
195
|
+
source: new Map(sourcePaths.map((filePath, index) => [filePath, sourceBlobs[index]])),
|
|
196
|
+
indexed: new Map(indexedPaths.map((filePath, index) => [filePath, indexedBlobs[index]])),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
131
200
|
function resolveBunVersionTarget(projectRoot, options = {}) {
|
|
132
201
|
const indexReader = options.readIndexedPackageManifest || readIndexedPackageManifest;
|
|
133
202
|
const indexed = Buffer.from(indexReader(projectRoot, options.execGit));
|
|
@@ -308,16 +377,24 @@ async function applyWorkflowUpdate(projectRoot, update, context) {
|
|
|
308
377
|
return { success: true, result: { path: update.path, write, authorization, completion } };
|
|
309
378
|
}
|
|
310
379
|
|
|
380
|
+
/**
|
|
381
|
+
* Runs the Bun-pin, npm, and test workflow writers as one recoverable batch bound to the preflight
|
|
382
|
+
* target and generated test bytes. On failure, earlier writes are restored only while their bytes
|
|
383
|
+
* still match this batch's output; concurrent changes are preserved and reported as unrecovered.
|
|
384
|
+
*/
|
|
311
385
|
async function updateBunWorkflowPins(projectRoot, options = {}) {
|
|
312
386
|
const npmWorkflow = require('./npm-publish-workflow');
|
|
387
|
+
const testWorkflow = require('./test-workflow');
|
|
313
388
|
const { verifyExpectedHead } = npmWorkflow;
|
|
314
389
|
const head = verifyExpectedHead(projectRoot, options.expectedHead, options.resolveHead);
|
|
315
390
|
if (!head.success) return head;
|
|
316
391
|
const sourceHead = head.sourceHead;
|
|
317
|
-
const
|
|
392
|
+
const env = options.env || process.env;
|
|
393
|
+
const actor = options.actor || env.FORGE_PROTECTED_STATE_ACTOR || env.FORGE_ACTOR || env.USER || env.USERNAME || 'unknown';
|
|
318
394
|
|
|
319
395
|
let updates;
|
|
320
396
|
let npmUpdate;
|
|
397
|
+
let testUpdate;
|
|
321
398
|
try {
|
|
322
399
|
const target = resolveBunVersionTarget(projectRoot, options);
|
|
323
400
|
updates = BUN_WORKFLOW_SPECS.map(spec => {
|
|
@@ -371,6 +448,11 @@ async function updateBunWorkflowPins(projectRoot, options = {}) {
|
|
|
371
448
|
},
|
|
372
449
|
recoveryOperation: 'recover_npm_workflow',
|
|
373
450
|
};
|
|
451
|
+
testUpdate = (options.resolveTestWorkflowUpdate || testWorkflow.resolveTestWorkflowUpdate)(
|
|
452
|
+
projectRoot,
|
|
453
|
+
sourceHead,
|
|
454
|
+
options,
|
|
455
|
+
);
|
|
374
456
|
} catch (error) {
|
|
375
457
|
return { success: false, error: error.message };
|
|
376
458
|
}
|
|
@@ -437,12 +519,55 @@ async function updateBunWorkflowPins(projectRoot, options = {}) {
|
|
|
437
519
|
return failedUpdate(npm.error, results, attempts, authorizations, projectRoot, context, { npm });
|
|
438
520
|
}
|
|
439
521
|
|
|
522
|
+
const testAuthorization = {
|
|
523
|
+
actor,
|
|
524
|
+
path: testWorkflow.TEST_WORKFLOW_PATH,
|
|
525
|
+
sourceHead,
|
|
526
|
+
capabilityId: context.createCapabilityId(),
|
|
527
|
+
};
|
|
528
|
+
authorizations.push(testAuthorization);
|
|
529
|
+
attempts.push({
|
|
530
|
+
...testUpdate,
|
|
531
|
+
original: testUpdate.snapshot,
|
|
532
|
+
writeOptions: {
|
|
533
|
+
actor,
|
|
534
|
+
operation: 'generate_test_workflow',
|
|
535
|
+
viaForgeApi: true,
|
|
536
|
+
surface: 'workflows',
|
|
537
|
+
},
|
|
538
|
+
recoveryOperation: 'recover_test_workflow',
|
|
539
|
+
});
|
|
540
|
+
const testGenerator = options.generateTestWorkflow || testWorkflow.generateTestWorkflow;
|
|
541
|
+
let testWorkflowResult;
|
|
542
|
+
try {
|
|
543
|
+
testWorkflowResult = await testGenerator(projectRoot, {
|
|
544
|
+
actor,
|
|
545
|
+
env: options.env,
|
|
546
|
+
kernelDeps: options.kernelDeps,
|
|
547
|
+
expectedHead: sourceHead,
|
|
548
|
+
resolveHead: options.resolveHead,
|
|
549
|
+
capabilityId: testAuthorization.capabilityId,
|
|
550
|
+
createCapabilityId: () => testAuthorization.capabilityId,
|
|
551
|
+
expectedSnapshot: testUpdate.snapshot,
|
|
552
|
+
expectedUpdate: { version: testUpdate.version, content: testUpdate.content },
|
|
553
|
+
});
|
|
554
|
+
} catch (error) {
|
|
555
|
+
return failedUpdate(error.message, results, attempts, authorizations, projectRoot, context, { npm });
|
|
556
|
+
}
|
|
557
|
+
if (!testWorkflowResult.success) {
|
|
558
|
+
return failedUpdate(testWorkflowResult.error, results, attempts, authorizations, projectRoot, context, {
|
|
559
|
+
npm,
|
|
560
|
+
testWorkflow: testWorkflowResult,
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
|
|
440
564
|
return {
|
|
441
565
|
success: true,
|
|
442
566
|
version: updates[0].version,
|
|
443
|
-
paths: [...results.map(result => result.path), npm.path],
|
|
567
|
+
paths: [...results.map(result => result.path), npm.path, testWorkflowResult.path],
|
|
444
568
|
results,
|
|
445
569
|
npm,
|
|
570
|
+
testWorkflow: testWorkflowResult,
|
|
446
571
|
};
|
|
447
572
|
}
|
|
448
573
|
|
|
@@ -454,6 +579,7 @@ module.exports = {
|
|
|
454
579
|
readIndexedWorkflow,
|
|
455
580
|
readIndexedPackageManifest,
|
|
456
581
|
readSourcePackageManifest,
|
|
582
|
+
readCompleteBunPinBatch,
|
|
457
583
|
resolveBunVersionTarget,
|
|
458
584
|
resolveCurrentHead,
|
|
459
585
|
deriveBunWorkflowUpdate,
|