forge-workflow 0.1.0-beta.5 → 0.1.0-beta.7
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/AGENTS.md +4 -0
- package/CHANGELOG.md +48 -0
- package/CLAUDE.md +0 -12
- package/CODING_STANDARDS.md +72 -0
- package/bin/forge.js +12 -1
- package/docs/guides/MIGRATION.md +3 -3
- package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +4 -0
- package/docs/reference/INSTALL.md +4 -0
- package/docs/reference/LEGACY_CLAIM_REPAIR.md +112 -0
- package/docs/reference/RELEASE.md +4 -4
- package/docs/reference/github-accounts.md +134 -0
- package/docs/reference/shepherd.md +63 -13
- package/lib/adapters/pr-state-adapter.js +15 -2
- package/lib/base-remote.js +138 -0
- package/lib/beta5-compatibility-evidence.js +1093 -0
- package/lib/bun-lockfile-proof.js +413 -0
- package/lib/bun-workflow-pins.js +461 -0
- package/lib/capabilities/index.js +9 -0
- package/lib/capabilities/model.js +141 -0
- package/lib/capabilities/probes.js +347 -0
- package/lib/codex-skills.js +2 -2
- package/lib/commands/_manifest.js +1 -0
- package/lib/commands/_registry.js +48 -18
- package/lib/commands/clean.js +57 -1
- package/lib/commands/doctor.js +37 -6
- package/lib/commands/gate.js +197 -27
- package/lib/commands/github.js +215 -0
- package/lib/commands/hooks.js +54 -6
- package/lib/commands/memory.js +66 -2
- package/lib/commands/merge.js +720 -73
- package/lib/commands/plan.js +33 -2
- package/lib/commands/pr.js +2 -0
- package/lib/commands/preflight.js +10 -2
- package/lib/commands/push.js +120 -9
- package/lib/commands/recall.js +95 -61
- package/lib/commands/release.js +23 -2
- package/lib/commands/remember.js +28 -4
- package/lib/commands/serve.js +26 -9
- package/lib/commands/setup.js +132 -4
- package/lib/commands/shepherd.js +578 -72
- package/lib/commands/ship.js +15 -69
- package/lib/commands/skill.js +8 -0
- package/lib/commands/team.js +47 -8
- package/lib/commands/test.js +163 -4
- package/lib/commands/validate.js +78 -22
- package/lib/commands/worktree.js +155 -19
- package/lib/fixtures/beta5-corpus/v1/README.md +9 -0
- package/lib/fixtures/beta5-corpus/v1/contract/command-contract.json +26 -0
- package/lib/fixtures/beta5-corpus/v1/contract/package-contract.json +13 -0
- package/lib/fixtures/beta5-corpus/v1/contract/workflow-stage-matrix.json +8 -0
- package/lib/fixtures/beta5-corpus/v1/manifest.json +25 -0
- package/lib/fixtures/beta5-corpus/v1/state/comments.jsonl +1 -0
- package/lib/fixtures/beta5-corpus/v1/state/config.yaml +6 -0
- package/lib/fixtures/beta5-corpus/v1/state/dependencies.jsonl +1 -0
- package/lib/fixtures/beta5-corpus/v1/state/issues.jsonl +2 -0
- package/lib/fixtures/beta5-corpus/v1/state/kernel.sql +20 -0
- package/lib/forge-issues.js +78 -0
- package/lib/gate-events.js +98 -10
- package/lib/github-context.js +308 -0
- package/lib/global-flags.js +1 -0
- package/lib/hook-renderer.js +29 -1
- package/lib/issue-render.js +19 -0
- package/lib/kernel/broker.js +723 -31
- package/lib/kernel/claim-reconciler.js +238 -0
- package/lib/kernel/lease-enforcer.js +9 -4
- package/lib/kernel/legacy-claim-repair.js +442 -0
- package/lib/kernel/live-claim-projection.js +26 -0
- package/lib/kernel/migrations.js +118 -3
- package/lib/kernel/readiness-model.js +184 -12
- package/lib/kernel/schema.js +49 -1
- package/lib/kernel/sqlite-driver.js +3322 -183
- package/lib/kernel/taxonomy-validator.js +4 -1
- package/lib/kernel/windows-private-acl.js +239 -0
- package/lib/memory/hygiene.js +191 -0
- package/lib/memory/router.js +94 -27
- package/lib/memory/usage-evidence.js +4 -0
- package/lib/memory-digest.js +59 -0
- package/lib/merge-rules.js +135 -17
- package/lib/npm-publish-workflow.js +233 -40
- package/lib/package-root.js +2 -0
- package/lib/pr-monitor/auto-actions.js +169 -28
- package/lib/pr-monitor/differ.js +110 -4
- package/lib/pr-monitor/events.js +0 -0
- package/lib/pr-monitor/flow-monitor.js +1424 -0
- package/lib/pr-monitor/gather.js +251 -44
- package/lib/pr-monitor/journal.js +0 -37
- package/lib/pr-monitor/monitor.js +117 -10
- package/lib/pr-monitor/process-identity.js +117 -0
- package/lib/pr-monitor/reconcile-executor.js +1101 -625
- package/lib/pr-monitor/reconcile.js +0 -0
- package/lib/pr-monitor/render-summary.js +121 -24
- package/lib/pr-monitor/review-preflight.js +269 -0
- package/lib/pr-monitor/shepherd-lease.js +28 -19
- package/lib/pr-monitor/verdict.js +438 -0
- package/lib/pr-monitor/watch-lifecycle.js +144 -38
- package/lib/pr-monitor/watch-owner.js +1414 -0
- package/lib/pr-monitor/watch.js +129 -58
- package/lib/pr-shepherd.js +17 -3
- package/lib/project-memory.js +145 -3
- package/lib/protected-state-authority.js +799 -4
- package/lib/protected-state-surfaces.js +181 -3
- package/lib/release-readiness.js +2 -3
- package/lib/review-adapter.js +65 -0
- package/lib/skills-sync.js +65 -32
- package/lib/validation/risk-manifest.js +339 -0
- package/lib/validation-receipt.js +190 -0
- package/lib/workflow/enforce-stage.js +44 -0
- package/lib/workflow/plan-authority.js +225 -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 +16 -4
- 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/commitlint.js +13 -15
- package/scripts/generate-risk-manifest.js +91 -0
- package/scripts/github-context-bridge.sh +10 -0
- package/scripts/legacy-claim-repair.js +145 -0
- package/scripts/lib/behavioral-eval-runtime.js +3 -2
- package/scripts/process-tree.js +14 -2
- package/scripts/protected-state-check.js +440 -17
- package/scripts/sync-agent-skills.js +333 -34
- package/scripts/test-full-suite.js +704 -18
- package/scripts/test-profile.js +13 -3
- package/scripts/test.js +96 -15
- package/skills/coverage.json +1 -0
- package/skills/review/SKILL.md +2 -0
- package/skills/review/evals/scorecard.json +2 -2
- package/skills/setup/SKILL.md +18 -0
- package/skills/setup/evals/scorecard.json +3 -3
- package/skills/shepherd/SKILL.md +19 -2
- package/skills/shepherd/evals/scorecard.json +3 -3
- package/skills/validate/SKILL.md +3 -0
- package/skills/validate/evals/scorecard.json +1 -1
|
@@ -22,22 +22,26 @@ There are two ways to run the shepherd, both under the single `forge shepherd`
|
|
|
22
22
|
verb:
|
|
23
23
|
|
|
24
24
|
- **`forge shepherd daemon` — the singleton reconcile daemon, the default
|
|
25
|
-
|
|
25
|
+
coordinator.** It acquires the machine-wide election lease for this repo
|
|
26
26
|
(exiting immediately as a clean no-op if a live daemon already owns it),
|
|
27
27
|
heartbeats, and converges the *entire* PR world every ~60s: self-registering
|
|
28
|
-
hand-opened PRs,
|
|
28
|
+
hand-opened PRs, recovering dead watcher generations, requesting cooperative
|
|
29
|
+
stops,
|
|
29
30
|
converging CI check state into kernel verdicts, and retiring merged/closed PRs.
|
|
30
|
-
It **self-retires** — releases the lease
|
|
31
|
-
|
|
31
|
+
It **self-retires** — releases the election lease and exits — once no PRs
|
|
32
|
+
remain open. Per-PR ownership is never stored in that lease. Forge wakes it
|
|
33
|
+
automatically after a successful
|
|
32
34
|
supported session start, every successful push, and every successful
|
|
33
35
|
non-dry-run ship. These are the only automatic firing seams; ordinary commands
|
|
34
36
|
do not launch it. Once running, an agent does not poll: the daemon owns the
|
|
35
37
|
convergence loop.
|
|
36
|
-
- **`forge shepherd <pr>` — one bounded pass.**
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
- **`forge shepherd <pr>` — one bounded convergence pass.** Runs local review
|
|
39
|
+
preflight, reads one PR's state, takes at most one Tier-A action, persists
|
|
40
|
+
bounded deltas/receipts, and exits. The point-in-time surface for a single PR
|
|
41
|
+
(see *Bounded-pass model* below).
|
|
39
42
|
- **`forge shepherd watch <pr>` / `watch --adopt`** — foreground streaming watch of
|
|
40
|
-
one PR, or (`--adopt`)
|
|
43
|
+
one PR, or (`--adopt`) reserve owner rows and start watchers for every
|
|
44
|
+
currently-open PR.
|
|
41
45
|
- **`forge shepherd events <pr> --since <seq>`** — the event deltas for a PR since a
|
|
42
46
|
cursor; **`forge shepherd <pr> --pull --json` / `--bundle`** read the kernel verdict
|
|
43
47
|
+ rollup without taking any action.
|
|
@@ -48,6 +52,12 @@ bare CLI use falls back to a detached launch from the stable common repository
|
|
|
48
52
|
root, never a disposable worktree cwd. No liveness check is needed first — the
|
|
49
53
|
O_EXCL singleton lease makes a duplicate start a clean no-op.
|
|
50
54
|
|
|
55
|
+
The daemon never kills watcher PIDs. A stop is a generation-conditional
|
|
56
|
+
`running -> stop_requested` transition in the Kernel; the matching watcher sees
|
|
57
|
+
that state, exits its loop, and releases only its own generation. A dead watcher
|
|
58
|
+
is recovered only after fresh PID and provider evidence is gathered outside the
|
|
59
|
+
transaction and the exact owner-row snapshot still matches inside it.
|
|
60
|
+
|
|
51
61
|
Embedding boundary:
|
|
52
62
|
|
|
53
63
|
```js
|
|
@@ -115,6 +125,16 @@ project's documented ergonomic — poll briefly, then stop and hand off. A pass
|
|
|
115
125
|
that finds checks still pending returns `PENDING`; the next scheduled pass picks
|
|
116
126
|
up from there.
|
|
117
127
|
|
|
128
|
+
Before remote mutation, the plain pass consolidates a probed CodeRabbit CLI with
|
|
129
|
+
strict lint, structural drift, Sonar parity, and affected tests. The result is
|
|
130
|
+
returned as `localPreflight`; CodeRabbit absence/auth loss is explicitly
|
|
131
|
+
`UNAVAILABLE`/`INCOMPLETE`, not green. Deterministic failure or actual local
|
|
132
|
+
review findings makes the remote decision dry-run for that pass. A local
|
|
133
|
+
checkout that is not the exact PR head is explicitly `NOT_APPLICABLE`. Output is
|
|
134
|
+
bounded to 128 `deltas` plus receipt IDs and one outcome handoff: `review` for
|
|
135
|
+
semantic feedback, explicit human-approved `merge` at `MERGE_READY`, or `verify`
|
|
136
|
+
after merged evidence. Shepherd never resolves threads and never merges.
|
|
137
|
+
|
|
118
138
|
`--watch`-style behavior, if desired, belongs in an external scheduler (cron, or
|
|
119
139
|
a `/loop`) that re-invokes the bounded pass with a debounce of at least 60
|
|
120
140
|
seconds and cancel-in-progress. The shepherd itself never waits in-process.
|
|
@@ -164,6 +184,7 @@ same verb (or `forge shepherd events`) on its own cadence.
|
|
|
164
184
|
| `MERGE_READY` | Required checks are green and the branch is up to date. The shepherd hands off — **a human merges in the GitHub UI.** |
|
|
165
185
|
| `ESCALATE` | A Tier-C condition (conflict, unreadable required set, persistent failure, oscillation, budget exhaustion). Context is posted to the PR. |
|
|
166
186
|
| `PENDING` | A Tier-A action was taken, or checks are still pending. Exit and await the next scheduled pass. |
|
|
187
|
+
| `INCOMPLETE` | Exact-head local review or durable convergence evidence is unavailable. Fail closed and retry after evidence is restored. |
|
|
167
188
|
| `HARD_STOP` | A permanent auth/scope failure that retrying cannot fix. Escalate to a human to widen token scope. |
|
|
168
189
|
|
|
169
190
|
## Action ladder
|
|
@@ -189,6 +210,13 @@ same verb (or `forge shepherd events`) on its own cadence.
|
|
|
189
210
|
- **HEAD-changed abort.** Before any mutating action it re-reads the head SHA; if
|
|
190
211
|
HEAD moved during the pass, the action aborts. The `shepherd:active` marker is
|
|
191
212
|
advisory only — it is not mutual exclusion.
|
|
213
|
+
- **One per-PR authority.** `kernel_pr_watch_owners` is the sole watcher owner
|
|
214
|
+
record. The daemon lease elects one reconciler but contains no watcher list;
|
|
215
|
+
journals and legacy PID/marker files cannot authorize a transition.
|
|
216
|
+
- **Fail-closed evidence.** Kernel unavailability, corrupt owner rows, stale
|
|
217
|
+
generations, changed provider/PID/receipt evidence, and an incomplete legacy
|
|
218
|
+
migration gate block mutation and are retried. No unlocked or filesystem
|
|
219
|
+
fallback becomes authority.
|
|
192
220
|
- **Auth taxonomy.** 401 (expiry) pauses and surfaces; 403 insufficient-scope is
|
|
193
221
|
a hard-stop; 403 with `Retry-After` honors the delay and resumes next pass.
|
|
194
222
|
|
|
@@ -248,8 +276,30 @@ neutral `forge/pr-monitor` check.
|
|
|
248
276
|
|
|
249
277
|
## State
|
|
250
278
|
|
|
251
|
-
|
|
252
|
-
`
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
279
|
+
The shared Kernel database stores one versioned `kernel_pr_watch_owners` row per
|
|
280
|
+
canonical `(repo, pr)`. Its generation-conditional phases are `starting`,
|
|
281
|
+
`running`, `stop_requested`, `terminal_pending`, `complete`, and `blocked`.
|
|
282
|
+
Starts reserve a store-minted generation before spawning; the child binds its
|
|
283
|
+
PID to that exact generation; heartbeats, stop requests, terminal receipts,
|
|
284
|
+
recovery, release, and reopen are narrow compare-and-swap transitions. Provider,
|
|
285
|
+
PID, and receipt checks happen outside the short SQLite transaction, followed by
|
|
286
|
+
an exact snapshot check inside it.
|
|
287
|
+
|
|
288
|
+
Public Memory and the per-PR journal under
|
|
289
|
+
`.forge/pr-monitor/<repo>-<pr>/` remain durable delivery and replay surfaces for
|
|
290
|
+
monitor events, verdicts, `forge shepherd watch`, and `events --since`; neither
|
|
291
|
+
is watcher ownership authority. A terminal receipt first moves the owner row to
|
|
292
|
+
`terminal_pending`; a later transaction completes it only after the watcher PID
|
|
293
|
+
is confirmed dead and the receipt/provider evidence still matches.
|
|
294
|
+
|
|
295
|
+
Upgrade from the retired filesystem model is fenced by the repository-local
|
|
296
|
+
`kernel_pr_watch_migration_gate`. The importer publishes `quarantined`, obtains
|
|
297
|
+
two identical bounded snapshots of the old lease/PID/marker evidence, binds that
|
|
298
|
+
snapshot hash before importing one PR per transaction, durably rereads the rows
|
|
299
|
+
and source, and then completes the exact gate/hash. Corrupt, changed, live-PID,
|
|
300
|
+
or unmappable evidence stays fail-closed as `blocked` or `conflict`. After the
|
|
301
|
+
gate is complete, legacy evidence is cleanup-only and never regains authority.
|
|
302
|
+
|
|
303
|
+
For 0.1, no receipt grants continuing lease authority. Consequential paths
|
|
304
|
+
re-probe live ownership, while canonical LeaseReceipt epoch/scope and the
|
|
305
|
+
same-actor/session release-reclaim ABA fix remain explicitly deferred.
|
|
@@ -102,6 +102,9 @@ function classifyAuthError(error) {
|
|
|
102
102
|
if (status === 401 || /HTTP 401|bad credentials|token expired/i.test(text)) {
|
|
103
103
|
return { class: 'expired' };
|
|
104
104
|
}
|
|
105
|
+
if (status === 429 || /HTTP 429/i.test(text)) {
|
|
106
|
+
return { class: 'rate-limit', retryAfter };
|
|
107
|
+
}
|
|
105
108
|
if (status === 403 || /HTTP 403/i.test(text)) {
|
|
106
109
|
if (retryAfter || /rate limit|secondary rate/i.test(text)) {
|
|
107
110
|
return { class: 'rate-limit', retryAfter };
|
|
@@ -116,6 +119,7 @@ class PrStateAdapter {
|
|
|
116
119
|
* @param {object} [options]
|
|
117
120
|
* @param {Function} [options.gh] - Runner for `gh` (cmd, args[]) → string.
|
|
118
121
|
* @param {Function} [options.git] - Runner for `git` (cmd, args[]) → string.
|
|
122
|
+
* @param {string} [options.repository] - Exact owner/repo for numeric PR reads.
|
|
119
123
|
*/
|
|
120
124
|
constructor(options = {}) {
|
|
121
125
|
this.id = options.id || 'pr-state-adapter';
|
|
@@ -131,6 +135,7 @@ class PrStateAdapter {
|
|
|
131
135
|
});
|
|
132
136
|
this._gh = options.gh || defaultRunner;
|
|
133
137
|
this._git = options.git || defaultRunner;
|
|
138
|
+
this._repository = options.repository || null;
|
|
134
139
|
this.lastProtectionStatus = null;
|
|
135
140
|
}
|
|
136
141
|
|
|
@@ -141,7 +146,11 @@ class PrStateAdapter {
|
|
|
141
146
|
* @returns {Promise<{ headSha: string, mergeable: string, mergeStateStatus: string, checks: object[], threads: object[] }>}
|
|
142
147
|
*/
|
|
143
148
|
async readState(pr) {
|
|
144
|
-
const raw = this._gh('gh', [
|
|
149
|
+
const raw = this._gh('gh', [
|
|
150
|
+
'pr', 'view', String(pr),
|
|
151
|
+
...(this._repository ? ['--repo', this._repository] : []),
|
|
152
|
+
'--json', PR_VIEW_FIELDS,
|
|
153
|
+
]);
|
|
145
154
|
const data = JSON.parse(raw || '{}');
|
|
146
155
|
const lifecycleReadable = typeof data.state === 'string'
|
|
147
156
|
&& ['OPEN', 'MERGED', 'CLOSED'].includes(data.state.toUpperCase())
|
|
@@ -732,7 +741,11 @@ class PrStateAdapter {
|
|
|
732
741
|
* @returns {Promise<number|null>} epoch ms of the head commit, or null.
|
|
733
742
|
*/
|
|
734
743
|
async readHeadCommitTime({ pr }) {
|
|
735
|
-
const raw = this._gh('gh', [
|
|
744
|
+
const raw = this._gh('gh', [
|
|
745
|
+
'pr', 'view', String(pr),
|
|
746
|
+
...(this._repository ? ['--repo', this._repository] : []),
|
|
747
|
+
'--json', 'commits', '-q', '.commits[-1].committedDate',
|
|
748
|
+
]);
|
|
736
749
|
const t = Date.parse(String(raw || '').trim());
|
|
737
750
|
return Number.isFinite(t) ? t : null;
|
|
738
751
|
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { execFileSync } = require('node:child_process');
|
|
4
|
+
|
|
5
|
+
const BASE_REMOTE_CANDIDATES = ['upstream', 'origin'];
|
|
6
|
+
const DEFAULT_BRANCH_CANDIDATES = ['main', 'master'];
|
|
7
|
+
const GIT_PROBE_TIMEOUT_MS = 120000;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Quiet git probe options. `stdio: 'pipe'` keeps failed probes from leaking to
|
|
11
|
+
* the terminal — these are speculative queries, not user-facing commands.
|
|
12
|
+
*
|
|
13
|
+
* @param {string} cwd - Working directory for the probe.
|
|
14
|
+
* @returns {object} execFileSync options.
|
|
15
|
+
*/
|
|
16
|
+
function getQuietProbeOptions(cwd) {
|
|
17
|
+
return { encoding: 'utf8', cwd, timeout: GIT_PROBE_TIMEOUT_MS, stdio: 'pipe' };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Resolve `refs/remotes/<remote>/HEAD` to the ref it points at, verifying the
|
|
22
|
+
* target actually exists.
|
|
23
|
+
*
|
|
24
|
+
* @param {Function} [exec] - Injected execFileSync.
|
|
25
|
+
* @param {string} [cwd] - Repository directory.
|
|
26
|
+
* @param {string} [remoteName] - Remote to probe.
|
|
27
|
+
* @returns {string|null} Fully-qualified ref, or null.
|
|
28
|
+
*/
|
|
29
|
+
function resolveRemoteHeadTarget(exec = execFileSync, cwd = process.cwd(), remoteName = undefined) {
|
|
30
|
+
if (!remoteName) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const symbolicRef = exec('git', ['symbolic-ref', `refs/remotes/${remoteName}/HEAD`], getQuietProbeOptions(cwd)).trim();
|
|
35
|
+
if (!symbolicRef) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
exec('git', ['rev-parse', '--verify', symbolicRef], getQuietProbeOptions(cwd));
|
|
39
|
+
return symbolicRef;
|
|
40
|
+
} catch (_error) { // NOSONAR S2486 - intentional: missing ref is the expected probe failure
|
|
41
|
+
void _error;
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* True when the remote has fetched tracking refs we can treat as a base:
|
|
48
|
+
* a recorded HEAD, or one of the conventional default branches.
|
|
49
|
+
*
|
|
50
|
+
* @param {Function} [exec] - Injected execFileSync.
|
|
51
|
+
* @param {string} [cwd] - Repository directory.
|
|
52
|
+
* @param {string} [remoteName] - Remote to probe.
|
|
53
|
+
* @returns {boolean} Whether a usable tracking base exists.
|
|
54
|
+
*/
|
|
55
|
+
function remoteHasTrackingBase(exec = execFileSync, cwd = process.cwd(), remoteName = undefined) {
|
|
56
|
+
if (!remoteName) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
if (resolveRemoteHeadTarget(exec, cwd, remoteName)) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for (const candidate of DEFAULT_BRANCH_CANDIDATES) {
|
|
64
|
+
try {
|
|
65
|
+
exec('git', ['rev-parse', '--verify', `refs/remotes/${remoteName}/${candidate}`], getQuietProbeOptions(cwd));
|
|
66
|
+
return true;
|
|
67
|
+
} catch (_error) { // NOSONAR S2486 - intentional: speculative probe, try next candidate
|
|
68
|
+
void _error;
|
|
69
|
+
// Probe next candidate.
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Resolve the remote that owns the integration base. `upstream` wins over
|
|
78
|
+
* `origin` so a fork-style checkout resolves the official repository rather
|
|
79
|
+
* than the contributor's fork.
|
|
80
|
+
*
|
|
81
|
+
* @param {Function} [exec] - Injected execFileSync.
|
|
82
|
+
* @param {string} [cwd] - Repository directory.
|
|
83
|
+
* @returns {string} Remote name ('origin' when nothing else qualifies).
|
|
84
|
+
*/
|
|
85
|
+
function resolveBaseRemote(exec = execFileSync, cwd = process.cwd()) {
|
|
86
|
+
for (const candidate of BASE_REMOTE_CANDIDATES) {
|
|
87
|
+
try {
|
|
88
|
+
exec('git', ['remote', 'get-url', candidate], getQuietProbeOptions(cwd));
|
|
89
|
+
if (remoteHasTrackingBase(exec, cwd, candidate)) {
|
|
90
|
+
return candidate;
|
|
91
|
+
}
|
|
92
|
+
} catch (_error) { // NOSONAR S2486 - intentional: speculative probe, try next candidate
|
|
93
|
+
void _error;
|
|
94
|
+
// Probe next candidate.
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return 'origin';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Resolve the base remote's default branch name.
|
|
103
|
+
*
|
|
104
|
+
* @param {Function} [exec] - Injected execFileSync.
|
|
105
|
+
* @param {string} [cwd] - Repository directory.
|
|
106
|
+
* @param {string} [remoteName] - Remote to probe; defaults to the base remote.
|
|
107
|
+
* @returns {string} Branch name ('master' when nothing else resolves).
|
|
108
|
+
*/
|
|
109
|
+
function resolveBaseBranch(exec = execFileSync, cwd = process.cwd(), remoteName = resolveBaseRemote(exec, cwd)) {
|
|
110
|
+
const symbolicRef = resolveRemoteHeadTarget(exec, cwd, remoteName);
|
|
111
|
+
if (symbolicRef) {
|
|
112
|
+
const match = new RegExp(`^refs/remotes/${remoteName}/(.+)$`).exec(symbolicRef);
|
|
113
|
+
if (match?.[1]) {
|
|
114
|
+
return match[1];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
for (const candidate of DEFAULT_BRANCH_CANDIDATES) {
|
|
119
|
+
try {
|
|
120
|
+
exec('git', ['rev-parse', '--verify', `refs/remotes/${remoteName}/${candidate}`], getQuietProbeOptions(cwd));
|
|
121
|
+
return candidate;
|
|
122
|
+
} catch (_error) { // NOSONAR S2486 - intentional: speculative probe, try next candidate
|
|
123
|
+
void _error;
|
|
124
|
+
// Probe next candidate.
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return 'master';
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
module.exports = {
|
|
132
|
+
BASE_REMOTE_CANDIDATES,
|
|
133
|
+
DEFAULT_BRANCH_CANDIDATES,
|
|
134
|
+
resolveRemoteHeadTarget,
|
|
135
|
+
remoteHasTrackingBase,
|
|
136
|
+
resolveBaseRemote,
|
|
137
|
+
resolveBaseBranch,
|
|
138
|
+
};
|