forgedock 1.1.8 → 1.1.9
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/README.md +10 -33
- package/bin/cinema.mjs +23 -5
- package/bin/engine-cli.mjs +137 -11
- package/bin/env-detect.mjs +249 -0
- package/bin/forge-utils.mjs +364 -4
- package/bin/forgedock.mjs +1064 -135
- package/bin/hooks/session-start.mjs +68 -3
- package/bin/init-detect.mjs +101 -12
- package/bin/journey.mjs +917 -16
- package/bin/recall.mjs +24 -7
- package/bin/registry.mjs +91 -3
- package/bin/runner.mjs +38 -2
- package/bin/tui.mjs +49 -3
- package/commands/adopt.md +1 -1
- package/commands/analytics.md +8 -8
- package/commands/audit.md +1 -1
- package/commands/autopilot.md +1 -1
- package/commands/ci-audit.md +1 -1
- package/commands/cleanup.md +87 -52
- package/commands/compat-audit.md +1 -1
- package/commands/deploy-info.md +1 -1
- package/commands/deploy-pr.md +1 -1
- package/commands/diagnose.md +1 -1
- package/commands/explain.md +1 -1
- package/commands/fix-ci.md +1 -1
- package/commands/forgedock-init.md +1 -1
- package/commands/incident-response.md +3 -3
- package/commands/issue.md +1 -1
- package/commands/optimize.md +1 -1
- package/commands/orchestrate/config.md +13 -3
- package/commands/orchestrate/phase-1-resolve.md +1 -1
- package/commands/orchestrate/phase-2-triage.md +1 -1
- package/commands/orchestrate/phase-2.5-synthesis.md +1 -1
- package/commands/orchestrate/phase-3-dependency.md +128 -20
- package/commands/orchestrate/phase-4-execution.md +598 -43
- package/commands/orchestrate/phase-5-cleanup.md +1 -1
- package/commands/orchestrate/phase-6-report.md +51 -3
- package/commands/orchestrate/safety.md +2 -2
- package/commands/qa-sweep.md +1 -1
- package/commands/quality-gate.md +1 -1
- package/commands/recover-orphans.md +5 -5
- package/commands/replay.md +1 -1
- package/commands/review-pr-agents/api.md +2 -2
- package/commands/review-pr-agents/auth.md +2 -2
- package/commands/review-pr-agents/billing.md +2 -2
- package/commands/review-pr-agents/concurrency.md +2 -2
- package/commands/review-pr-agents/database.md +2 -2
- package/commands/review-pr-agents/frontend.md +2 -2
- package/commands/review-pr-agents/infra.md +2 -2
- package/commands/review-pr-agents/protocols.md +1 -1
- package/commands/review-pr-agents/scraper.md +2 -2
- package/commands/review-pr-agents/security.md +2 -2
- package/commands/review-pr-staging.md +41 -3
- package/commands/review-pr.md +71 -6
- package/commands/rollback.md +1 -1
- package/commands/scope.md +1 -1
- package/commands/security-audit.md +1 -1
- package/commands/test-gate.md +1 -1
- package/commands/validate.md +1 -1
- package/commands/work-on/build/architect.md +1 -1
- package/commands/work-on/build/context.md +9 -6
- package/commands/work-on/build/implement.md +1 -1
- package/commands/work-on/build/validate.md +1 -1
- package/commands/work-on/build.md +31 -6
- package/commands/work-on/close.md +3 -2
- package/commands/work-on/decompose.md +1 -1
- package/commands/work-on/investigate.md +14 -5
- package/commands/work-on/remediate.md +331 -0
- package/commands/work-on/review.md +1 -1
- package/commands/work-on-monolithic.md +1 -1
- package/commands/work-on.md +194 -56
- package/docs/CI.md +2 -2
- package/docs/CODEX.md +1 -1
- package/docs/CONFIG.md +186 -0
- package/docs/site/command-reference.md +31 -1
- package/docs/site/getting-started.md +9 -34
- package/docs/site/index.md +1 -1
- package/docs/spec/label-state-machine.md +1 -1
- package/package.json +2 -1
- package/packages/protocol/src/types.js +1 -1
- package/scripts/check-command-docs-drift.sh +4 -2
- package/scripts/danger-zones.mjs +19 -12
- package/scripts/transition-label.sh +101 -13
- package/scripts/refresh-bot-token.sh +0 -97
package/docs/CONFIG.md
CHANGED
|
@@ -42,6 +42,7 @@ The default (no flags) is the annotated review screen: detection runs, AI enrich
|
|
|
42
42
|
| [`project`](#project-required) | **Yes** | GitHub identity (owner, repo, name) |
|
|
43
43
|
| [`paths`](#paths-required) | **Yes** | Local filesystem locations |
|
|
44
44
|
| [`branches`](#branches-required) | **Yes** | Branch naming conventions |
|
|
45
|
+
| [`agents`](#agents-optional) | No | Default model for pipeline agents |
|
|
45
46
|
| [`repos`](#repos-optional) | No | Multi-repository routing |
|
|
46
47
|
| [`project_board`](#project_board-optional) | No | GitHub Projects v2 integration |
|
|
47
48
|
| [`services`](#services-optional) | No | External service URLs and IDs |
|
|
@@ -125,6 +126,40 @@ branches:
|
|
|
125
126
|
|
|
126
127
|
---
|
|
127
128
|
|
|
129
|
+
## `agents` (OPTIONAL)
|
|
130
|
+
|
|
131
|
+
Model configuration for pipeline agents. `default_model` governs the main agent — the one you invoke directly (`work-on`, `orchestrate`, `review-pr`, etc.), referenced by every command spec's "Agent model policy" line. `subagent_model` governs the **child** sub-agents that a top-level command spawns internally — e.g. `/orchestrate`'s per-issue `/work-on` agents, `/review-pr`'s domain persona reviewers, `/analytics`'s parallel data-collection agents, `/incident-response`'s validation/root-cause agents.
|
|
132
|
+
|
|
133
|
+
```yaml
|
|
134
|
+
agents:
|
|
135
|
+
default_model: "sonnet"
|
|
136
|
+
subagent_model: "sonnet"
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
| Field | Type | Required | Default | Description |
|
|
140
|
+
|-------|------|----------|---------|-------------|
|
|
141
|
+
| `default_model` | string | No | `"sonnet"` | Short alias — `"sonnet"`, `"opus"`, or `"haiku"` — the same values the Agent/Task tool's `model` parameter accepts. This is the only form that works for interactive command-spec sub-agent spawning. |
|
|
142
|
+
| `subagent_model` | string | No | `default_model`, else `"sonnet"` | Short alias, same accepted values as `default_model`. Overrides the model used for child agents spawned internally by a top-level command (see above). Omit to inherit `default_model`. |
|
|
143
|
+
|
|
144
|
+
**Resolution order** (highest precedence first):
|
|
145
|
+
1. `--model <id>` — CLI flag, `npx forgedock run` only (headless; main agent only).
|
|
146
|
+
2. `FORGEDOCK_MODEL` env var — headless runner only (main agent only).
|
|
147
|
+
3. `agents.default_model` — this field. Governs the main agent.
|
|
148
|
+
4. Hardcoded default — `"sonnet"` for interactive agents, `"claude-sonnet-5"` for the headless runner (`bin/runner.mjs`'s `DEFAULT_MODEL`).
|
|
149
|
+
|
|
150
|
+
**Sub-agent resolution order** (highest precedence first):
|
|
151
|
+
1. `agents.subagent_model` — this field. Governs child agents spawned by a top-level command.
|
|
152
|
+
2. `agents.default_model` — falls back here when `subagent_model` is unset.
|
|
153
|
+
3. Hardcoded default — `"sonnet"`.
|
|
154
|
+
|
|
155
|
+
**Headless runner note**: `bin/runner.mjs` (`npx forgedock run`, used for CI/headless invocations) calls the Anthropic SDK directly and also accepts a full Anthropic model ID here as a pass-through escape hatch (e.g. `"claude-opus-4-6"`). A full model ID does **not** work for interactive Agent/Task tool calls in command specs — those only accept the short-alias enum (`sonnet`/`opus`/`haiku`/`fable`). Prefer the short alias unless you specifically need a headless-only override. `subagent_model` is interactive-only — it has no headless-runner equivalent, since the headless runner does not spawn child sub-agents.
|
|
156
|
+
|
|
157
|
+
Projects without this section see no behavior change — everything defaults exactly as it did before these fields existed.
|
|
158
|
+
|
|
159
|
+
**Commands that use this section**: all commands with an "Agent model policy" line (`default_model`, nearly every command spec) and all commands that spawn internal sub-agents — `orchestrate`, `review-pr`, `analytics`, `incident-response` (`subagent_model`).
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
128
163
|
## `repos` (OPTIONAL)
|
|
129
164
|
|
|
130
165
|
Multi-repository routing. Use this when your project spans multiple GitHub repositories and you want to route issues to satellite repos by prefix.
|
|
@@ -571,6 +606,26 @@ Colors are grouped by semantic meaning:
|
|
|
571
606
|
|
|
572
607
|
---
|
|
573
608
|
|
|
609
|
+
## GitHub App Install
|
|
610
|
+
|
|
611
|
+
During `npx forgedock`, the installer prompts you to install the ForgeDock GitHub App (`rapiercraft-forgedock`) on your account or org.
|
|
612
|
+
|
|
613
|
+
**What installing it does today**: registers the app against the account/org you choose. That's it.
|
|
614
|
+
|
|
615
|
+
**What it does NOT do (yet)**:
|
|
616
|
+
- It does **not** create a bot token. GitHub App *installation* tokens can only be minted with the app's private key, and that key is held solely by RapierCraft Studios (the app's owner) — your installation grants *your* installation ID against *RapierCraft's* app, not a key you can use yourself.
|
|
617
|
+
- It does **not** auto-refresh anything, and there is no background process managing token expiry.
|
|
618
|
+
- It does **not** change which `gh` auth context pipeline commands (`/work-on`, `/orchestrate`, etc.) use. Every pipeline `gh` call always uses whatever auth is currently active in your shell — your personal token unless you've manually configured something else.
|
|
619
|
+
- It does **not** wire up webhook-driven "automatic pipeline triggers" — no webhook receiver exists yet.
|
|
620
|
+
|
|
621
|
+
**Why this matters**: GitHub App installation tokens get materially higher API rate limits than a personal access token, and actions taken with them are attributed to the bot (`rapiercraft-forgedock[bot]`) rather than your personal account. Those benefits are real, but delivering them to arbitrary installers requires a hosted token-minting backend service that doesn't exist yet (tracked as forge#1890). Installing the app today is safe and free, but on its own changes nothing about how the pipeline authenticates.
|
|
622
|
+
|
|
623
|
+
`npx forgedock doctor` reports this status explicitly (Check 12: "GitHub App / bot token") so you aren't left guessing whether a bot token is active.
|
|
624
|
+
|
|
625
|
+
**If you want bot-token behavior today**: you'd need to register your own GitHub App (with your own private key), and drive your own token-refresh loop (mint an installation token via the app's `/app/installations/{id}/access_tokens` API, then `gh auth login --with-token`, repeating before the ~1-hour expiry). This is an advanced, self-hosted setup — ForgeDock does not package a generic version of this for you.
|
|
626
|
+
|
|
627
|
+
---
|
|
628
|
+
|
|
574
629
|
## `marketing` (OPTIONAL)
|
|
575
630
|
|
|
576
631
|
Controls opt-in growth features such as 'Powered by ForgeDock' footers on PR descriptions created by the pipeline.
|
|
@@ -730,6 +785,53 @@ See [Per-Directory State Registry](#per-directory-state-registry) below for how
|
|
|
730
785
|
|
|
731
786
|
---
|
|
732
787
|
|
|
788
|
+
## Persisted Toolset Home (`~/.forge/`)
|
|
789
|
+
|
|
790
|
+
`npx`/`npm` never installs `forgedock` to a stable location on your machine — it resolves to wherever `npx`'s cache (or a global npm install, or a git clone) happens to physically live. Historically, `~/.claude/commands/` and the SessionStart hook's script path were symlinked **directly** from that ephemeral location. When npm/npx (or the OS) later evicted its cache, those symlinks silently dangled — Claude Code sessions stopped getting ForgeDock context with no error shown.
|
|
791
|
+
|
|
792
|
+
As of this feature, every `npx forgedock` (or `npx forgedock update`) run copies ForgeDock's own installable payload into a stable, version-independent home:
|
|
793
|
+
|
|
794
|
+
```
|
|
795
|
+
~/.forge/
|
|
796
|
+
bin/ — the same bin/ tree bundled with the resolved package (hooks, CLI entry points)
|
|
797
|
+
commands/ — the slash-command specs (.md)
|
|
798
|
+
scripts/ — the universal pipeline-agent scripts (classify-lane.sh, etc.)
|
|
799
|
+
templates/ — scaffold templates (e.g. the demo repo)
|
|
800
|
+
version — plain-text file containing the source package's version (e.g. "1.7.2")
|
|
801
|
+
```
|
|
802
|
+
|
|
803
|
+
`~/.claude/commands/` symlinks and the SessionStart hook's baked-in script path are then built from **this** copy, not the original ephemeral source — so they keep working even after the npm/npx cache that originally served them is cleared.
|
|
804
|
+
|
|
805
|
+
### When It Runs
|
|
806
|
+
|
|
807
|
+
| Command | Behavior |
|
|
808
|
+
|---------|----------|
|
|
809
|
+
| `npx forgedock` / `npx forgedock install` | Copies the resolved package's payload into `~/.forge/` (content-compared — unchanged files are never rewritten) before linking commands, then links from `~/.forge/`, not the original source |
|
|
810
|
+
| `npx forgedock init` | Does not touch `~/.forge/` — `init` only writes `forge.yaml`, it never installs commands |
|
|
811
|
+
| `npx forgedock update` (npm/npx install) | Refreshes `~/.forge/` from whatever the currently-resolved package looks like, then relinks |
|
|
812
|
+
| `npx forgedock update` (git-clone install) | **Does not** touch `~/.forge/` at all — see the exemption below |
|
|
813
|
+
| `npx forgedock doctor` | Reports `~/.forge/version` and confirms `~/.forge/{bin,commands,scripts,templates}` exist |
|
|
814
|
+
|
|
815
|
+
The copy is idempotent: files whose content is already byte-identical are never rewritten, so steady-state re-runs are fast and don't generate false "updated" noise.
|
|
816
|
+
|
|
817
|
+
### The Git-Clone Exemption
|
|
818
|
+
|
|
819
|
+
If you run ForgeDock from a git clone (or a git worktree) of the repo itself — the development/self-hosted install mode — nothing is copied into `~/.forge/`. A git clone is already a stable, user-owned location; copying it into `~/.forge/` and linking commands from the copy instead of the clone would silently disconnect `git pull` (or `npx forgedock update`'s git branch) from what's actually linked into `~/.claude/commands/`. So:
|
|
820
|
+
|
|
821
|
+
- `~/.claude/commands/` continues to symlink directly from the clone, exactly as before this feature.
|
|
822
|
+
- `~/.forge/{bin,commands,scripts,templates}` correctly does **not** exist for this install mode — `npx forgedock doctor` treats that absence as healthy, not a warning.
|
|
823
|
+
|
|
824
|
+
### Not the Same as `~/.forge/{runs,index}`
|
|
825
|
+
|
|
826
|
+
`~/.forge/` also hosts two **unrelated, pre-existing** directories used by other parts of ForgeDock:
|
|
827
|
+
|
|
828
|
+
- `~/.forge/runs/` — durable engine run state for `npx forgedock run-issue` / `resume-stalled` (see `bin/engine.mjs`).
|
|
829
|
+
- `~/.forge/index/` — the `recall` knowledge index (see `bin/recall.mjs`, `docs/FORGE-PROTOCOL.md`).
|
|
830
|
+
|
|
831
|
+
These are **engine data**, not the toolset itself, and this feature does not read, write, or otherwise change them. `~/.forge/{bin,commands,scripts,templates,version}` (this section) and `~/.forge/{runs,index}` (engine data) are unrelated siblings that merely happen to live under the same `~/.forge/` root — don't conflate the two when reading `~/.forge/`'s contents.
|
|
832
|
+
|
|
833
|
+
---
|
|
834
|
+
|
|
733
835
|
## Per-Directory State Registry
|
|
734
836
|
|
|
735
837
|
ForgeDock tracks per-directory opt-out state in a central registry file on the local machine.
|
|
@@ -810,6 +912,90 @@ npx forgedock status [dir] # Show a directory's resolved state (default: cwd)
|
|
|
810
912
|
|
|
811
913
|
---
|
|
812
914
|
|
|
915
|
+
## Install Receipt
|
|
916
|
+
|
|
917
|
+
ForgeDock writes a machine-readable receipt of what the last install or update actually did, so debugging drift (wrong commands installed, wrong tier, stale hooks) can start from a recorded fact instead of re-deriving state from scratch.
|
|
918
|
+
|
|
919
|
+
### Receipt File Location
|
|
920
|
+
|
|
921
|
+
```
|
|
922
|
+
~/.forge/install-receipt.json
|
|
923
|
+
```
|
|
924
|
+
|
|
925
|
+
The directory (`~/.forge/`) is created automatically on first write with `mkdir(..., { recursive: true })`. The file is never committed to version control — it is per-user, per-machine state.
|
|
926
|
+
|
|
927
|
+
### When It's Written
|
|
928
|
+
|
|
929
|
+
- After every successful `npx forgedock install` (end of the onboarding journey, right after the "Forged." celebration screen).
|
|
930
|
+
- After every `npx forgedock update` — both the git-clone branch (fast-forward pull) and the npm branch (version-check advisory) route through the same repair step (`relinkAndHint()`), so the receipt is refreshed either way.
|
|
931
|
+
|
|
932
|
+
The write is best-effort and never blocks or fails the install/update it records: any error (permission denied, disk full, unusual `FORGE_HOME` layout) degrades silently to a no-op, matching the fail-open contract already used by the command-manifest and hook-registration steps.
|
|
933
|
+
|
|
934
|
+
### Receipt Schema
|
|
935
|
+
|
|
936
|
+
```json
|
|
937
|
+
{
|
|
938
|
+
"schemaVersion": 1,
|
|
939
|
+
"timestamp": "2026-07-10T12:00:00.000Z",
|
|
940
|
+
"forgedockVersion": "1.1.6",
|
|
941
|
+
"installMode": "npm",
|
|
942
|
+
"forgeHome": "/home/user/.npm/_npx/abc123/node_modules/forgedock",
|
|
943
|
+
"cwd": "/home/user/projects/my-repo",
|
|
944
|
+
"platform": {
|
|
945
|
+
"platform": "linux",
|
|
946
|
+
"platformLabel": "Linux",
|
|
947
|
+
"isWSL": false,
|
|
948
|
+
"wslDistro": null,
|
|
949
|
+
"shell": "bash"
|
|
950
|
+
},
|
|
951
|
+
"tier": "core",
|
|
952
|
+
"commands": {
|
|
953
|
+
"count": 42,
|
|
954
|
+
"list": ["work-on", "review-pr", "quality-gate", "orchestrate/phase-1-resolve"]
|
|
955
|
+
},
|
|
956
|
+
"hooks": {
|
|
957
|
+
"sessionStart": "registered",
|
|
958
|
+
"preToolUse": "registered",
|
|
959
|
+
"subagentStopEnforce": null
|
|
960
|
+
},
|
|
961
|
+
"forgeYaml": {
|
|
962
|
+
"present": true,
|
|
963
|
+
"validShape": true
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
```
|
|
967
|
+
|
|
968
|
+
| Field | Type | Description |
|
|
969
|
+
|-------|------|--------------|
|
|
970
|
+
| `schemaVersion` | number | Schema version — currently `1` |
|
|
971
|
+
| `timestamp` | string | ISO-8601 timestamp of this install/update |
|
|
972
|
+
| `forgedockVersion` | string | `version` field from `{forgeHome}/package.json` (empty string if unreadable) |
|
|
973
|
+
| `installMode` | `"npm"` \| `"git-clone"` | Detected from whether `{forgeHome}/.git` exists — same predicate `update()` already uses to choose its update strategy |
|
|
974
|
+
| `forgeHome` | string | Absolute path to the ForgeDock package install (`FORGE_HOME`) |
|
|
975
|
+
| `cwd` | string | Absolute path to the project directory the command was run in |
|
|
976
|
+
| `platform` | object | `detectEnvironment()`'s output — platform, human label, WSL status/distro, detected shell |
|
|
977
|
+
| `tier` | `"core"` \| `"extras"` | Whether `--extras` was passed (opt-in command tier, see `install: extras` frontmatter) |
|
|
978
|
+
| `commands` | object | `count` and `list` (relative slash-command names) of everything installed for the resolved tier, sourced from `findMarkdownFiles()` — never a separately maintained list |
|
|
979
|
+
| `hooks.sessionStart` | string \| null | SessionStart hook registration status (`"registered"`, `"already"`, `"skipped-malformed"`) |
|
|
980
|
+
| `hooks.preToolUse` | string \| null | PreToolUse enforcement hook status, or `null` when the installed Claude Code version doesn't support it |
|
|
981
|
+
| `hooks.subagentStopEnforce` | string \| null | Cleanup status of the (retired) SubagentStop enforce hook |
|
|
982
|
+
| `forgeYaml.present` | boolean | Whether `forge.yaml` exists in `cwd` at receipt-write time |
|
|
983
|
+
| `forgeYaml.validShape` | boolean | Whether `forge.yaml` contains the 3 required top-level sections (`project:`, `paths:`, `branches:`) — a lightweight regex check, not a full YAML parse |
|
|
984
|
+
|
|
985
|
+
### No PII or Secrets
|
|
986
|
+
|
|
987
|
+
The receipt never contains `process.env` values, GitHub tokens, API keys, or the contents of `forge.yaml` — only the `present`/`validShape` booleans above. Absolute filesystem paths (`forgeHome`, `cwd`) are included for drift debugging; they typically contain the OS username but nothing more sensitive, consistent with other per-machine ForgeDock state (e.g. `registry.json`'s path-keyed entries, see above).
|
|
988
|
+
|
|
989
|
+
### Reading the Receipt
|
|
990
|
+
|
|
991
|
+
```bash
|
|
992
|
+
cat ~/.forge/install-receipt.json
|
|
993
|
+
```
|
|
994
|
+
|
|
995
|
+
There is no CLI subcommand to read it yet — `doctor()` reading the receipt to detect drift is a deliberately deferred stretch goal, not part of this feature.
|
|
996
|
+
|
|
997
|
+
---
|
|
998
|
+
|
|
813
999
|
## Complete Example
|
|
814
1000
|
|
|
815
1001
|
See [`forge.yaml.example`](../forge.yaml.example) at the repository root for a fully annotated example covering all sections.
|
|
@@ -592,7 +592,37 @@ These are invoked automatically by `/work-on` but can also be run standalone.
|
|
|
592
592
|
| `/work-on/review` | Phase 5 | Review only |
|
|
593
593
|
| `/work-on/close` | Phase 6 | Close & cleanup only |
|
|
594
594
|
| `/work-on/decompose` | Phase 2 | Decompose into sub-issues |
|
|
595
|
-
| `/
|
|
595
|
+
| `/work-on/remediate` | Phase 0A.1 | Remediate a `needs-human` PR: fix findings, re-review, re-gate (`/work-on <pr> --remediate`) |
|
|
596
|
+
| `/review-pr-agents` | PR review | Agent catalog router read by `/review-pr` during dispatch (not user-invokable directly) |
|
|
597
|
+
|
|
598
|
+
`/review-pr` (core) reads the following per-persona and shared-protocol templates from `commands/review-pr-agents/` at runtime during Phase 3C dispatch. They ship as part of every install alongside `/review-pr` — not user-invokable directly.
|
|
599
|
+
|
|
600
|
+
| Command | Phase | Purpose |
|
|
601
|
+
|---------|-------|---------|
|
|
602
|
+
| `/review-pr-agents/protocols` | Phase 3C | Shared Evidence-Based/Structured-Findings/Input-Scoping protocols read before every persona template |
|
|
603
|
+
| `/review-pr-agents/security` | Phase 3C | General security & quality scan (always runs) |
|
|
604
|
+
| `/review-pr-agents/auth` | Phase 3C | Auth conventions auditor (AUTH domain) |
|
|
605
|
+
| `/review-pr-agents/billing` | Phase 3C | Billing/payments auditor (BILLING domain) |
|
|
606
|
+
| `/review-pr-agents/concurrency` | Phase 3C | Concurrency/locking auditor (CONCURRENCY domain) |
|
|
607
|
+
| `/review-pr-agents/database` | Phase 3C | Database/migration auditor (DATABASE domain) |
|
|
608
|
+
| `/review-pr-agents/frontend` | Phase 3C | Frontend conventions auditor (FRONTEND domain) |
|
|
609
|
+
| `/review-pr-agents/infra` | Phase 3C | Infra/deploy auditor (INFRA domain) |
|
|
610
|
+
| `/review-pr-agents/api` | Phase 3C | API/SDK contract auditor (API domain) |
|
|
611
|
+
| `/review-pr-agents/scraper` | Phase 3C | Scraping/anti-bot auditor (SCRAPING domain) |
|
|
612
|
+
|
|
613
|
+
`/orchestrate` (core) reads the following phase files from `commands/orchestrate/` at runtime. They ship as part of every install alongside `/orchestrate` — not user-invokable directly.
|
|
614
|
+
|
|
615
|
+
| Command | Phase | Purpose |
|
|
616
|
+
|---------|-------|---------|
|
|
617
|
+
| `/orchestrate/config` | Phase 0 | Hard rules, config resolution, multi-repo support — read first |
|
|
618
|
+
| `/orchestrate/phase-1-resolve` | Phase 1 | Resolve the issue set from input |
|
|
619
|
+
| `/orchestrate/phase-2-triage` | Phase 2 | Investigation-first triage, Wave 0 |
|
|
620
|
+
| `/orchestrate/phase-2.5-synthesis` | Phase 2.5 | Investigation synthesis and deconfliction |
|
|
621
|
+
| `/orchestrate/phase-3-dependency` | Phase 3 | Dependency analysis, DAG construction, execution plan |
|
|
622
|
+
| `/orchestrate/phase-4-execution` | Phase 4 | Streaming DAG execution, agent dispatch, stall detection |
|
|
623
|
+
| `/orchestrate/phase-5-cleanup` | Phase 5 | Post-batch cleanup sweep and agent audit |
|
|
624
|
+
| `/orchestrate/phase-6-report` | Phase 6 | Consolidated report and pipeline summary |
|
|
625
|
+
| `/orchestrate/safety` | Reference | Safety rules and examples |
|
|
596
626
|
|
|
597
627
|
---
|
|
598
628
|
|
|
@@ -49,9 +49,9 @@ Run the installer with `npx` from your project directory:
|
|
|
49
49
|
npx forgedock
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
This installs all 25+ ForgeDock command specs into
|
|
52
|
+
This installs all 25+ ForgeDock command specs into `~/.claude/commands/` — making them available as slash commands in every Claude Code session on this machine, not just this repo.
|
|
53
53
|
|
|
54
|
-
>
|
|
54
|
+
> Install is always global. `--global` is still accepted on the command line for backward compatibility, but it's a no-op — `npx forgedock` and `npx forgedock --global` do exactly the same thing.
|
|
55
55
|
|
|
56
56
|
**Verify the install:**
|
|
57
57
|
|
|
@@ -75,7 +75,7 @@ ForgeDock Doctor — Installation Health Check
|
|
|
75
75
|
|
|
76
76
|
It exits `0` when everything passes and `1` if any check fails, so you can also use it in CI.
|
|
77
77
|
|
|
78
|
-
> Prefer a quick spot-check? `ls
|
|
78
|
+
> Prefer a quick spot-check? `ls ~/.claude/commands/ | grep -E "work-on|review-pr|quality-gate"` should list `work-on.md`, `review-pr.md`, and `quality-gate.md`.
|
|
79
79
|
|
|
80
80
|
---
|
|
81
81
|
|
|
@@ -205,7 +205,7 @@ The quick fixes below cover the most common first-run issues. For the full list
|
|
|
205
205
|
|
|
206
206
|
**`/work-on` not found in Claude Code**
|
|
207
207
|
|
|
208
|
-
The symlink wasn't created. Re-run `npx forgedock` and check `ls
|
|
208
|
+
The symlink wasn't created. Re-run `npx forgedock` and check `ls ~/.claude/commands/`.
|
|
209
209
|
|
|
210
210
|
**`forge.yaml` not found**
|
|
211
211
|
|
|
@@ -221,37 +221,12 @@ The issue may have been marked `workflow:invalid`. Check the issue comments for
|
|
|
221
221
|
|
|
222
222
|
---
|
|
223
223
|
|
|
224
|
-
##
|
|
224
|
+
## A note on install location
|
|
225
225
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
**Option A: Keep the global install (no action required)**
|
|
229
|
-
|
|
230
|
-
Add `--global` to all future `npx forgedock install/update/uninstall/doctor` calls. Your existing setup is unchanged.
|
|
231
|
-
|
|
232
|
-
```bash
|
|
233
|
-
npx forgedock update --global # re-link after a version bump
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
**Option B: Switch to project-scoped (recommended for per-repo version control)**
|
|
237
|
-
|
|
238
|
-
1. Remove the global install:
|
|
239
|
-
```bash
|
|
240
|
-
npx forgedock uninstall --global
|
|
241
|
-
```
|
|
242
|
-
2. In each repo you want ForgeDock in, run a fresh project-scoped install:
|
|
243
|
-
```bash
|
|
244
|
-
cd /path/to/your-repo
|
|
245
|
-
npx forgedock install
|
|
246
|
-
```
|
|
247
|
-
3. Verify:
|
|
248
|
-
```bash
|
|
249
|
-
npx forgedock doctor # auto-detects project-scoped install
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
**Detect your current mode:**
|
|
226
|
+
ForgeDock briefly shipped a project-scoped-by-default install mode. It was backed out after causing a "split-brain" bug (`doctor`/`status` assumed project-scoped while the installer still wrote globally — [#1589](https://github.com/RapierCraftStudios/ForgeDock/issues/1589)), so install has always been global (`~/.claude/commands/`) since. If you have scripts or CI that still pass `--global`, there's nothing to change — it's accepted for backward compatibility and does exactly what a plain `npx forgedock` does.
|
|
253
227
|
|
|
254
228
|
```bash
|
|
255
|
-
npx forgedock
|
|
256
|
-
npx forgedock
|
|
229
|
+
npx forgedock update # re-link after a version bump
|
|
230
|
+
npx forgedock doctor # confirm the install is healthy
|
|
231
|
+
npx forgedock status # shows the install path (~/.claude/commands/)
|
|
257
232
|
```
|
package/docs/site/index.md
CHANGED
|
@@ -56,7 +56,7 @@ No more coordinating between agent sessions. No more lost context. No more start
|
|
|
56
56
|
npx forgedock
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
Installs commands into
|
|
59
|
+
Installs commands into `~/.claude/commands/`, available in every Claude Code session on this machine. `--global` is still accepted as a flag for backward compatibility, but install is always global — there's nothing to opt into.
|
|
60
60
|
|
|
61
61
|
**Requirements**: Claude Code, Node.js 18+, `gh` CLI authenticated with GitHub.
|
|
62
62
|
|
|
@@ -20,7 +20,7 @@ See also: [FORGE Annotation Protocol §6](forge-protocol-v1.md#6-label-state-mac
|
|
|
20
20
|
| `workflow:decomposed` | Issue decomposed into sub-issues | Decomposer agent |
|
|
21
21
|
| `needs-human` | Pipeline blocked, human intervention required | Any agent on error |
|
|
22
22
|
|
|
23
|
-
`workflow:awaiting-merge` vs `needs-human`: both are terminal (the pipeline stops advancing the issue automatically), but they mean different things. `needs-human` means the pipeline hit a condition it cannot resolve on its own (genuinely blocked — conflicting PR, failed verdict, calibration/trust escalation, etc.) and a human must diagnose and act. `workflow:awaiting-merge` means the opposite: the PR was previously escalated to `needs-human`, has since been remediated and re-reviewed to a clean `APPROVED` verdict with no mergeability blockers, but does not yet meet the automated auto-land bar (see forge#1809 Q1) — a human only needs to click merge, not diagnose a problem. `scripts/transition-label.sh` clears `needs-human` (best-effort)
|
|
23
|
+
`workflow:awaiting-merge` vs `needs-human`: both are terminal (the pipeline stops advancing the issue automatically), but they mean different things. `needs-human` means the pipeline hit a condition it cannot resolve on its own (genuinely blocked — conflicting PR, failed verdict, calibration/trust escalation, etc.) and a human must diagnose and act. `workflow:awaiting-merge` means the opposite: the PR was previously escalated to `needs-human`, has since been remediated and re-reviewed to a clean `APPROVED` verdict with no mergeability blockers, but does not yet meet the automated auto-land bar (see forge#1809 Q1) — a human only needs to click merge, not diagnose a problem. `scripts/transition-label.sh` only clears `needs-human` (best-effort) when the target state is `workflow:awaiting-merge` — every other forward transition leaves a pre-existing `needs-human` label untouched, preserving its sticky/terminal semantics.
|
|
24
24
|
|
|
25
25
|
## Transitions
|
|
26
26
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forgedock",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.9",
|
|
4
4
|
"description": "Autonomous dev pipeline for Claude Code. GitHub as institutional memory — every issue, PR, and annotation survives context resets and makes the next agent smarter.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"forgedock": "bin/forgedock.mjs"
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"examples/",
|
|
41
41
|
"packages/protocol/src/",
|
|
42
42
|
"scripts/",
|
|
43
|
+
"!scripts/refresh-bot-token.sh",
|
|
43
44
|
"templates/",
|
|
44
45
|
"LICENSE",
|
|
45
46
|
"README.md"
|
|
@@ -228,7 +228,7 @@ export const RESERVED_TYPES = {
|
|
|
228
228
|
},
|
|
229
229
|
|
|
230
230
|
// §4.2 Machine-surface cross-artifact annotation (Base64url encoded — design decision 2026-07-08)
|
|
231
|
-
// Format: <!-- FORGE:CARD v1 sha:{sha8hex} b64:{base64url_of_canonical_json} -->
|
|
231
|
+
// Format: <!-- FORGE:CARD: v1 sha:{sha8hex} b64:{base64url_of_canonical_json} -->
|
|
232
232
|
// The inline value carries the full encoding: "v1 sha:XXXXXXXX b64:YYYYYY..."
|
|
233
233
|
// This is an inline-value type — parse() extracts the full encoding as inlineValue.
|
|
234
234
|
CARD: {
|
|
@@ -96,7 +96,9 @@ declare -a DOCUMENTED_COMMANDS=()
|
|
|
96
96
|
|
|
97
97
|
while IFS= read -r line; do
|
|
98
98
|
# Match H3 heading: ### `/command-name` or ### `/command/sub`
|
|
99
|
-
|
|
99
|
+
# Character class includes `.` to support dotted phase filenames (e.g.
|
|
100
|
+
# commands/orchestrate/phase-2.5-synthesis.md -> /orchestrate/phase-2.5-synthesis).
|
|
101
|
+
if [[ "$line" =~ ^###[[:space:]]+\`(/[a-zA-Z0-9_./-]+)\` ]]; then
|
|
100
102
|
cmd="${BASH_REMATCH[1]}"
|
|
101
103
|
DOCUMENTED_COMMANDS+=("$cmd")
|
|
102
104
|
# Match table cell containing a bare command path: | `/command-name` | ...
|
|
@@ -105,7 +107,7 @@ while IFS= read -r line; do
|
|
|
105
107
|
# matching inline code examples in prose or fix-instruction blocks.
|
|
106
108
|
elif [[ "$line" =~ ^\| ]]; then
|
|
107
109
|
# Extract all backtick-wrapped tokens that look like command paths
|
|
108
|
-
while [[ "$line" =~ \`(/[a-zA-Z0-9_
|
|
110
|
+
while [[ "$line" =~ \`(/[a-zA-Z0-9_./-]+)\` ]]; do
|
|
109
111
|
cmd="${BASH_REMATCH[1]}"
|
|
110
112
|
DOCUMENTED_COMMANDS+=("$cmd")
|
|
111
113
|
# Remove the matched portion so the loop can find multiple tokens per line
|
package/scripts/danger-zones.mjs
CHANGED
|
@@ -333,21 +333,28 @@ function buildFileFindingStats(cards) {
|
|
|
333
333
|
function collectCommits(repoPath, sinceDate) {
|
|
334
334
|
const since = sinceDate || `${WINDOW_DAYS} days ago`;
|
|
335
335
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
336
|
+
// --name-only: list files changed per commit
|
|
337
|
+
// --pretty=format:COMMIT %H %ai: commit hash + author date (ISO)
|
|
338
|
+
// No pathspec = full repo scan for matrix building (this is intentional —
|
|
339
|
+
// we need the global commit stream to compute n(a) and N correctly)
|
|
340
|
+
// spawnSync with an argument array avoids shell interpolation of repoPath
|
|
341
|
+
// (operator/env-controlled input) — see #1842.
|
|
342
|
+
const result = spawnSync(
|
|
343
|
+
'git',
|
|
344
|
+
['-C', repoPath, 'log', '--name-only', `--since=${since}`, '--pretty=format:COMMIT %H %ai'],
|
|
345
|
+
{ encoding: 'utf8', maxBuffer: 50 * 1024 * 1024, timeout: 60000 },
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
if (result.error || result.status !== 0) {
|
|
349
|
+
const reason = result.error
|
|
350
|
+
? result.error.message
|
|
351
|
+
: `exit code ${result.status}${result.signal ? ` (signal ${result.signal})` : ''}`;
|
|
352
|
+
log(`WARNING: git log failed — ${reason}`);
|
|
348
353
|
return [];
|
|
349
354
|
}
|
|
350
355
|
|
|
356
|
+
const gitOutput = result.stdout;
|
|
357
|
+
|
|
351
358
|
const commits = [];
|
|
352
359
|
let current = null;
|
|
353
360
|
|
|
@@ -123,6 +123,13 @@ fi
|
|
|
123
123
|
# in between is GH_FLAG (e.g. -R RapierCraftStudios/forgedock).
|
|
124
124
|
# ---------------------------------------------------------------------------
|
|
125
125
|
|
|
126
|
+
# Deferred exit code (forge#1929) — set to 1 by the persistent stale-label
|
|
127
|
+
# removal failure branch below instead of exiting immediately, so that the
|
|
128
|
+
# unconditional needs-human clear step (scoped to awaiting-merge) always
|
|
129
|
+
# gets a chance to run before the script actually exits. Initialized here,
|
|
130
|
+
# before any conditional logic, so `set -u` never sees it unbound.
|
|
131
|
+
TRANSITION_EXIT_CODE=0
|
|
132
|
+
|
|
126
133
|
if [ "$#" -lt 2 ]; then
|
|
127
134
|
echo "ERROR: Usage: transition-label.sh <ISSUE_NUMBER> [GH_FLAG...] <TARGET_STATE>" >&2
|
|
128
135
|
echo " Example: transition-label.sh 674 -R RapierCraftStudios/forgedock investigating" >&2
|
|
@@ -292,29 +299,110 @@ done
|
|
|
292
299
|
if [ -n "$TO_REMOVE" ]; then
|
|
293
300
|
echo "Removing stale workflow:* labels present on the issue ($TO_REMOVE)..."
|
|
294
301
|
gh issue edit "$ISSUE_NUMBER" "${GH_ARGS[@]}" --remove-label "$TO_REMOVE" 2>/dev/null || true
|
|
302
|
+
|
|
303
|
+
# -------------------------------------------------------------------------
|
|
304
|
+
# Post-condition verification + single retry (forge#1915)
|
|
305
|
+
#
|
|
306
|
+
# The intersection fix above (forge#1810) already prevents the "unbootstrapped
|
|
307
|
+
# label 404" all-or-nothing failure — every candidate in $TO_REMOVE is known
|
|
308
|
+
# to exist on the issue at the time it was computed. But the removal call
|
|
309
|
+
# itself can still fail for reasons unrelated to label existence (a transient
|
|
310
|
+
# `gh`/GitHub API hiccup, rate limiting, a dropped connection), and that
|
|
311
|
+
# failure was previously swallowed unconditionally by `2>/dev/null || true`
|
|
312
|
+
# with no verification — leaving stale workflow:* labels stacked alongside
|
|
313
|
+
# the newly-added terminal label with no diagnostic trail. Live evidence:
|
|
314
|
+
# issue #1892 closed with both workflow:in-review and workflow:merged applied
|
|
315
|
+
# simultaneously because this exact removal call silently failed once.
|
|
316
|
+
#
|
|
317
|
+
# Fix: re-fetch the issue's labels after the removal call. If any label in
|
|
318
|
+
# $TO_REMOVE is still present, retry the removal once. If it's *still*
|
|
319
|
+
# present after the retry, this is a real, persistent failure — surface it
|
|
320
|
+
# loudly (stderr ERROR + non-zero exit) instead of the unconditional "OK".
|
|
321
|
+
#
|
|
322
|
+
# forge#1929: the non-zero exit is DEFERRED (via $TRANSITION_EXIT_CODE)
|
|
323
|
+
# rather than immediate. This block runs before the needs-human clear step
|
|
324
|
+
# further down, which is scoped to TARGET_STATE=awaiting-merge and must
|
|
325
|
+
# run regardless of this failure — it is unrelated to stale-label removal.
|
|
326
|
+
# An immediate `exit 1` here would silently skip that unrelated cleanup
|
|
327
|
+
# step in the one case (persistent removal failure) it's most important
|
|
328
|
+
# for the caller to see a consistent, fully-applied label state.
|
|
329
|
+
# -------------------------------------------------------------------------
|
|
330
|
+
POST_REMOVE_LABELS=$(gh issue view "$ISSUE_NUMBER" "${GH_ARGS[@]}" --json labels \
|
|
331
|
+
--jq '[.labels[].name] | join(",")' 2>/dev/null || echo "")
|
|
332
|
+
|
|
333
|
+
STILL_PRESENT=""
|
|
334
|
+
IFS=',' read -ra TO_REMOVE_CHECK <<< "$TO_REMOVE"
|
|
335
|
+
for candidate in "${TO_REMOVE_CHECK[@]}"; do
|
|
336
|
+
case ",$POST_REMOVE_LABELS," in
|
|
337
|
+
*",$candidate,"*)
|
|
338
|
+
STILL_PRESENT="${STILL_PRESENT:+$STILL_PRESENT,}$candidate"
|
|
339
|
+
;;
|
|
340
|
+
esac
|
|
341
|
+
done
|
|
342
|
+
|
|
343
|
+
if [ -n "$STILL_PRESENT" ]; then
|
|
344
|
+
echo "WARNING: label removal did not take effect for ($STILL_PRESENT) — retrying once..." >&2
|
|
345
|
+
gh issue edit "$ISSUE_NUMBER" "${GH_ARGS[@]}" --remove-label "$STILL_PRESENT" 2>/dev/null || true
|
|
346
|
+
|
|
347
|
+
POST_RETRY_LABELS=$(gh issue view "$ISSUE_NUMBER" "${GH_ARGS[@]}" --json labels \
|
|
348
|
+
--jq '[.labels[].name] | join(",")' 2>/dev/null || echo "")
|
|
349
|
+
|
|
350
|
+
STILL_PRESENT_AFTER_RETRY=""
|
|
351
|
+
IFS=',' read -ra RETRY_CHECK <<< "$STILL_PRESENT"
|
|
352
|
+
for candidate in "${RETRY_CHECK[@]}"; do
|
|
353
|
+
case ",$POST_RETRY_LABELS," in
|
|
354
|
+
*",$candidate,"*)
|
|
355
|
+
STILL_PRESENT_AFTER_RETRY="${STILL_PRESENT_AFTER_RETRY:+$STILL_PRESENT_AFTER_RETRY,}$candidate"
|
|
356
|
+
;;
|
|
357
|
+
esac
|
|
358
|
+
done
|
|
359
|
+
|
|
360
|
+
if [ -n "$STILL_PRESENT_AFTER_RETRY" ]; then
|
|
361
|
+
echo "ERROR: failed to remove stale label(s) ($STILL_PRESENT_AFTER_RETRY) from issue #$ISSUE_NUMBER after retry — label state machine now inconsistent (issue carries both '$EFFECTIVE_LABEL' and the stale label(s) above)." >&2
|
|
362
|
+
TRANSITION_EXIT_CODE=1
|
|
363
|
+
else
|
|
364
|
+
echo "Retry succeeded — stale label(s) removed."
|
|
365
|
+
fi
|
|
366
|
+
fi
|
|
295
367
|
else
|
|
296
368
|
echo "No stale workflow:* labels present on the issue — nothing to remove."
|
|
297
369
|
fi
|
|
298
370
|
|
|
299
371
|
# ---------------------------------------------------------------------------
|
|
300
|
-
# Clear needs-human (best-effort)
|
|
372
|
+
# Clear needs-human (best-effort) — SCOPED to workflow:awaiting-merge only
|
|
301
373
|
#
|
|
302
374
|
# needs-human is NOT a workflow:* label, so it is never included in
|
|
303
|
-
# VALID_STATES/REMOVE_LABELS above.
|
|
304
|
-
#
|
|
305
|
-
#
|
|
375
|
+
# VALID_STATES/REMOVE_LABELS above. It is a write-only, sticky, terminal
|
|
376
|
+
# label: once a human flags an issue/PR as needing attention, no routine
|
|
377
|
+
# forward-progress transition (investigating, ready-to-build, building,
|
|
378
|
+
# in-review, merged, invalid, decomposed) should silently clear it.
|
|
379
|
+
#
|
|
380
|
+
# The ONE deliberate exception is workflow:awaiting-merge (forge#1809/#1810):
|
|
381
|
+
# a remediated + re-reviewed PR that has moved off needs-human without yet
|
|
382
|
+
# meeting the auto-land bar. That is the single code path allowed to clear
|
|
383
|
+
# needs-human — every other TARGET_STATE must leave a pre-existing
|
|
384
|
+
# needs-human label untouched.
|
|
306
385
|
#
|
|
307
|
-
# This script only runs at explicit forward-progress transition points (the
|
|
308
|
-
# dispatcher STOPs on needs-human before ever reaching a transition-label.sh
|
|
309
|
-
# call in normal operation — see commands/work-on.md's terminal-state checks),
|
|
310
|
-
# so clearing needs-human here is safe: it only fires when something has
|
|
311
|
-
# deliberately decided to move the issue/PR to a new state, most notably
|
|
312
|
-
# workflow:awaiting-merge (a remediated + re-reviewed PR moving off
|
|
313
|
-
# needs-human without yet meeting the auto-land bar — see #1809 Q1).
|
|
314
386
|
# Best-effort: `|| true` so a missing label never fails the script under
|
|
315
387
|
# `set -euo pipefail`.
|
|
316
388
|
# ---------------------------------------------------------------------------
|
|
317
|
-
|
|
318
|
-
|
|
389
|
+
if [ "$TARGET_STATE" = "awaiting-merge" ]; then
|
|
390
|
+
echo "Clearing needs-human (best-effort, scoped to awaiting-merge)..."
|
|
391
|
+
gh issue edit "$ISSUE_NUMBER" "${GH_ARGS[@]}" --remove-label "needs-human" 2>/dev/null || true
|
|
392
|
+
else
|
|
393
|
+
echo "Skipping needs-human clear — TARGET_STATE is '$TARGET_STATE', not 'awaiting-merge'."
|
|
394
|
+
fi
|
|
395
|
+
|
|
396
|
+
# ---------------------------------------------------------------------------
|
|
397
|
+
# Final exit (forge#1929) — honor any deferred failure from the persistent
|
|
398
|
+
# stale-label removal check above. The needs-human clear step just above
|
|
399
|
+
# always ran first, regardless of that failure, so the caller-visible exit
|
|
400
|
+
# code still surfaces the same "fail loud" signal introduced by forge#1915,
|
|
401
|
+
# just after the unrelated cleanup step has had a chance to run.
|
|
402
|
+
# ---------------------------------------------------------------------------
|
|
403
|
+
if [ "$TRANSITION_EXIT_CODE" -ne 0 ]; then
|
|
404
|
+
echo "FAILED: $EFFECTIVE_LABEL set on issue #$ISSUE_NUMBER, but persistent stale-label removal failure occurred (see ERROR above)." >&2
|
|
405
|
+
exit "$TRANSITION_EXIT_CODE"
|
|
406
|
+
fi
|
|
319
407
|
|
|
320
408
|
echo "OK: $EFFECTIVE_LABEL set on issue #$ISSUE_NUMBER"
|