forgedock 1.1.4 → 1.1.6
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.
|
@@ -188,6 +188,10 @@ async function isForgeDockManagedCwd() {
|
|
|
188
188
|
/**
|
|
189
189
|
* Check whether a gh pr create command targets a forbidden base branch.
|
|
190
190
|
*
|
|
191
|
+
* Allows staging → main PRs (the deploy path) by detecting --head staging
|
|
192
|
+
* or falling back to the current git branch. Feature branches targeting
|
|
193
|
+
* main are still hard-blocked.
|
|
194
|
+
*
|
|
191
195
|
* @param {string} command
|
|
192
196
|
* @returns {string|null} Error message to show, or null if allowed.
|
|
193
197
|
*/
|
|
@@ -199,6 +203,12 @@ function checkPrTarget(command) {
|
|
|
199
203
|
if (!base) return null; // no --base flag — gh will use the default
|
|
200
204
|
|
|
201
205
|
if (FORBIDDEN_PR_BASES.includes(base.toLowerCase())) {
|
|
206
|
+
// Allow staging → main (the deploy path, not a pipeline violation).
|
|
207
|
+
const head = extractFlag(command, "--head") || extractFlag(command, "-H") || currentGitBranch();
|
|
208
|
+
if (head && /^staging$/.test(head)) {
|
|
209
|
+
return null; // deploy flow — allowed
|
|
210
|
+
}
|
|
211
|
+
|
|
202
212
|
return [
|
|
203
213
|
`[ForgeDock] BLOCKED: PR targets "${base}" — pipeline rule violation.`,
|
|
204
214
|
``,
|
|
@@ -206,6 +216,7 @@ function checkPrTarget(command) {
|
|
|
206
216
|
`A PR to "${base}" is a hard pipeline violation. Fix the --base flag.`,
|
|
207
217
|
``,
|
|
208
218
|
`Allowed targets: staging | milestone/<slug>`,
|
|
219
|
+
`Exception: staging → main (deploy flow) is allowed.`,
|
|
209
220
|
].join("\n");
|
|
210
221
|
}
|
|
211
222
|
|
|
@@ -315,6 +326,27 @@ function checkLabelTransition(command) {
|
|
|
315
326
|
return null; // valid transition — allow
|
|
316
327
|
}
|
|
317
328
|
|
|
329
|
+
// ---------------------------------------------------------------------------
|
|
330
|
+
// Git helpers
|
|
331
|
+
// ---------------------------------------------------------------------------
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Return the current git branch name, or null on any failure.
|
|
335
|
+
* Used to detect the staging → main deploy path.
|
|
336
|
+
*/
|
|
337
|
+
function currentGitBranch() {
|
|
338
|
+
try {
|
|
339
|
+
const { execFileSync: exec } = _require("child_process");
|
|
340
|
+
return exec("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
341
|
+
encoding: "utf-8",
|
|
342
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
343
|
+
timeout: 5000,
|
|
344
|
+
}).trim() || null;
|
|
345
|
+
} catch {
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
318
350
|
// ---------------------------------------------------------------------------
|
|
319
351
|
// Helpers
|
|
320
352
|
// ---------------------------------------------------------------------------
|
package/commands/autopilot.md
CHANGED
|
@@ -387,12 +387,47 @@ while true; do
|
|
|
387
387
|
break
|
|
388
388
|
fi
|
|
389
389
|
|
|
390
|
-
# Step 1:
|
|
391
|
-
|
|
390
|
+
# Step 1: Drive all open fast-lane issues through the durable engine.
|
|
391
|
+
# Each issue is dispatched individually via `forgedock run-issue` so the engine's
|
|
392
|
+
# phase table (fail-closed review gate, deterministic resume, lease-based concurrency)
|
|
393
|
+
# enforces every transition — not the LLM interpreting work-on.md.
|
|
394
|
+
# Fallback: if forgedock CLI is unavailable, delegate to /orchestrate (markdown-spec path).
|
|
395
|
+
echo "=== Fast Lane Iteration $FAST_LANE_ITERATIONS: Dispatching $OPEN_UNMILESTONED issues via engine ==="
|
|
396
|
+
|
|
397
|
+
FORGEDOCK_AVAILABLE=$(command -v forgedock >/dev/null 2>&1 && echo "true" || echo "false")
|
|
398
|
+
|
|
392
399
|
if [ "$DRY_RUN" = "false" ]; then
|
|
393
|
-
|
|
400
|
+
if [ "$FORGEDOCK_AVAILABLE" = "true" ]; then
|
|
401
|
+
# Engine-first dispatch: enumerate issues and run each through the durable engine.
|
|
402
|
+
# Issues are dispatched concurrently — the engine holds per-issue leases.
|
|
403
|
+
FAST_LANE_ISSUE_NUMS=$(gh issue list $GH_FLAG \
|
|
404
|
+
--state open \
|
|
405
|
+
--limit 200 \
|
|
406
|
+
--json number,milestone,labels \
|
|
407
|
+
--jq '[.[] | select(
|
|
408
|
+
.milestone == null and
|
|
409
|
+
(.labels | map(.name) | any(. == "workflow:merged" or . == "workflow:invalid" or . == "workflow:decomposed" or . == "needs-human") | not)
|
|
410
|
+
)] | .[].number' \
|
|
411
|
+
2>/dev/null || echo '')
|
|
412
|
+
|
|
413
|
+
for ISSUE_NUM in $FAST_LANE_ISSUE_NUMS; do
|
|
414
|
+
echo "Dispatching #$ISSUE_NUM via forgedock run-issue --lane staging"
|
|
415
|
+
forgedock run-issue "$ISSUE_NUM" --lane staging &
|
|
416
|
+
done
|
|
417
|
+
|
|
418
|
+
# Wait for all engine workers to complete before proceeding to deploy step.
|
|
419
|
+
wait
|
|
420
|
+
echo "Engine dispatch complete for fast-lane iteration $FAST_LANE_ITERATIONS"
|
|
421
|
+
else
|
|
422
|
+
echo "WARNING: forgedock CLI not in PATH — falling back to Skill(orchestrate, fast-lane)"
|
|
423
|
+
Skill("orchestrate", args="fast-lane")
|
|
424
|
+
fi
|
|
394
425
|
else
|
|
395
|
-
|
|
426
|
+
if [ "$FORGEDOCK_AVAILABLE" = "true" ]; then
|
|
427
|
+
echo "[DRY-RUN] Would dispatch each fast-lane issue via: forgedock run-issue <issue> --lane staging"
|
|
428
|
+
else
|
|
429
|
+
echo "[DRY-RUN] Would invoke: Skill(orchestrate, fast-lane)"
|
|
430
|
+
fi
|
|
396
431
|
fi
|
|
397
432
|
|
|
398
433
|
# Step 2: Deploy staging→main if staging has new commits
|
|
@@ -481,12 +516,43 @@ echo "$MILESTONES" | jq -c '.[]' | while IFS= read -r milestone; do
|
|
|
481
516
|
|
|
482
517
|
echo "=== Processing milestone: '$MS_TITLE' ($MS_PCT% complete, $MS_OPEN open) ==="
|
|
483
518
|
|
|
484
|
-
# Step 1:
|
|
519
|
+
# Step 1: Drive open issues in this milestone through the durable engine.
|
|
520
|
+
# Same engine-first dispatch as Phase 2 — fail-closed gates apply to milestone issues too.
|
|
521
|
+
# Fallback: if forgedock CLI is unavailable, delegate to /orchestrate.
|
|
485
522
|
if [ "$MS_OPEN" -gt 0 ]; then
|
|
523
|
+
FORGEDOCK_AVAILABLE=$(command -v forgedock >/dev/null 2>&1 && echo "true" || echo "false")
|
|
524
|
+
|
|
486
525
|
if [ "$DRY_RUN" = "false" ]; then
|
|
487
|
-
|
|
526
|
+
if [ "$FORGEDOCK_AVAILABLE" = "true" ]; then
|
|
527
|
+
MS_ISSUE_NUMS=$(gh issue list $GH_FLAG \
|
|
528
|
+
--state open \
|
|
529
|
+
--limit 200 \
|
|
530
|
+
--json number,milestone,labels \
|
|
531
|
+
--jq --arg slug "$MS_SLUG" \
|
|
532
|
+
'[.[] | select(
|
|
533
|
+
.milestone != null and
|
|
534
|
+
(.milestone.title | ascii_downcase | gsub("[^a-z0-9]+"; "-") | ltrimstr("-") | rtrimstr("-")) == $slug and
|
|
535
|
+
(.labels | map(.name) | any(. == "workflow:merged" or . == "workflow:invalid" or . == "workflow:decomposed" or . == "needs-human") | not)
|
|
536
|
+
)] | .[].number' \
|
|
537
|
+
2>/dev/null || echo '')
|
|
538
|
+
|
|
539
|
+
for ISSUE_NUM in $MS_ISSUE_NUMS; do
|
|
540
|
+
echo "Dispatching #$ISSUE_NUM via forgedock run-issue --lane milestone/$MS_SLUG"
|
|
541
|
+
forgedock run-issue "$ISSUE_NUM" --lane "milestone/$MS_SLUG" &
|
|
542
|
+
done
|
|
543
|
+
|
|
544
|
+
wait
|
|
545
|
+
echo "Engine dispatch complete for milestone '$MS_TITLE'"
|
|
546
|
+
else
|
|
547
|
+
echo "WARNING: forgedock CLI not in PATH — falling back to Skill(orchestrate, milestone $MS_SLUG)"
|
|
548
|
+
Skill("orchestrate", args="milestone $MS_SLUG")
|
|
549
|
+
fi
|
|
488
550
|
else
|
|
489
|
-
|
|
551
|
+
if [ "$FORGEDOCK_AVAILABLE" = "true" ]; then
|
|
552
|
+
echo "[DRY-RUN] Would dispatch each milestone issue via: forgedock run-issue <issue> --lane milestone/$MS_SLUG"
|
|
553
|
+
else
|
|
554
|
+
echo "[DRY-RUN] Would invoke: Skill(orchestrate, milestone $MS_SLUG)"
|
|
555
|
+
fi
|
|
490
556
|
fi
|
|
491
557
|
else
|
|
492
558
|
echo "Milestone '$MS_TITLE' has no open issues — checking if branch needs deploy"
|
package/commands/deploy-pr.md
CHANGED
|
@@ -392,8 +392,11 @@ while [ "$REVIEW_ITER" -lt "$MAX_REVIEW_ITER" ] && [ "$REVIEW_GATE_PASSED" = "fa
|
|
|
392
392
|
# This loop's next iteration will re-read the latest PR state.
|
|
393
393
|
fi
|
|
394
394
|
else
|
|
395
|
-
|
|
396
|
-
|
|
395
|
+
# Fail-closed: unknown verdict must block merge, not grant it.
|
|
396
|
+
# If FORGE:REVIEW comment is missing, malformed, or unparseable for any reason
|
|
397
|
+
# (race condition, context compaction, skill error, comment format mismatch),
|
|
398
|
+
# treat it as a failed review — REVIEW_GATE_PASSED remains false.
|
|
399
|
+
echo "ERROR: Could not determine review verdict from FORGE:REVIEW comment. Blocking merge — review verdict is required. (#1714)"
|
|
397
400
|
fi
|
|
398
401
|
done
|
|
399
402
|
|
|
@@ -443,15 +443,19 @@ Proceed? (yes / adjust / pick specific issues)
|
|
|
443
443
|
|
|
444
444
|
---
|
|
445
445
|
|
|
446
|
-
## Engine mode (
|
|
446
|
+
## Engine mode (default)
|
|
447
447
|
|
|
448
|
-
|
|
448
|
+
Dispatch each issue via the durable execution engine. This is the **default execution mode** for both interactive (`/autopilot`, `/orchestrate`) and headless/CI paths:
|
|
449
449
|
|
|
450
450
|
```bash
|
|
451
451
|
forgedock run-issue <issue> --lane <staging|milestone/slug>
|
|
452
452
|
```
|
|
453
453
|
|
|
454
|
-
The engine drives every phase transition deterministically, mirrors state to the `FORGE:STATE` block on the issue, and holds a lease. To recover stalls, scan in-flight issues' `FORGE:STATE`; any issue with an expired lease and a non-terminal state is re-dispatched with the same `forgedock run-issue <issue>` command — it resumes from the last committed phase (idempotent). This replaces the label-heuristic "already in progress" check and the resume-with-nagging loop.
|
|
454
|
+
The engine drives every phase transition deterministically, mirrors state to the `FORGE:STATE` block on the issue, and holds a lease. Its **fail-closed review gate** (`phases.mjs → detectOutcome`) means the PR must be confirmed merged before the phase is committed — missing or unparseable review comments are treated as failures, not approvals. To recover stalls, scan in-flight issues' `FORGE:STATE`; any issue with an expired lease and a non-terminal state is re-dispatched with the same `forgedock run-issue <issue>` command — it resumes from the last committed phase (idempotent). This replaces the label-heuristic "already in progress" check and the resume-with-nagging loop.
|
|
455
|
+
|
|
456
|
+
**Why engine-first**: The engine's phase table enforces gate semantics in code — not via LLM interpretation of markdown specs. This eliminates the class of bug where the LLM assumes a review approved when the FORGE:REVIEW comment is absent or unreadable (issue #1714). Interactive sessions that run `/work-on` via Skill invocations additionally bridge to the engine run-log via the SubagentStop hook (`bin/hooks/interactive-engine.mjs`) — state is durable across compaction and context resets.
|
|
457
|
+
|
|
458
|
+
**Fallback (no forgedock CLI)**: If `forgedock` is not in PATH, fall back to spawning Agent sub-agents that run `Skill("work-on", ...)` per issue. The SubagentStop hook still bridges these runs to the engine run-log for state persistence.
|
|
455
459
|
|
|
456
460
|
### Concurrency model: in-process worker pool + worktree-per-issue
|
|
457
461
|
|
|
@@ -171,7 +171,31 @@ Use `${ISSUE_LANE[$NUM]}` and `${ISSUE_PR_BASE[$NUM]}` to populate `{LANE}` and
|
|
|
171
171
|
|
|
172
172
|
### Step 4A: Dispatch ready issues
|
|
173
173
|
|
|
174
|
-
**
|
|
174
|
+
**Engine-first dispatch (default)**: When `forgedock` is in PATH, dispatch each ready issue via the durable engine rather than spawning prose Agent sub-agents. The engine's phase table enforces gate semantics in code — its fail-closed review gate and deterministic phase ordering are not subject to LLM interpretation.
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
# Engine-first dispatch: check CLI availability, then dispatch ready issues in parallel
|
|
178
|
+
FORGEDOCK_AVAILABLE=$(command -v forgedock >/dev/null 2>&1 && echo "true" || echo "false")
|
|
179
|
+
|
|
180
|
+
if [ "$FORGEDOCK_AVAILABLE" = "true" ]; then
|
|
181
|
+
for NUM in {ready_issue_numbers}; do
|
|
182
|
+
LANE="${ISSUE_LANE[$NUM]}"
|
|
183
|
+
PR_BASE="${ISSUE_PR_BASE[$NUM]}"
|
|
184
|
+
echo "Dispatching #$NUM via forgedock run-issue --lane $PR_BASE"
|
|
185
|
+
forgedock run-issue "$NUM" --lane "$PR_BASE" &
|
|
186
|
+
done
|
|
187
|
+
wait
|
|
188
|
+
echo "Engine dispatch complete — advancing to Step 4B (completion sweep)"
|
|
189
|
+
else
|
|
190
|
+
echo "WARNING: forgedock CLI not in PATH — falling back to Agent-spawn path (markdown-spec execution)"
|
|
191
|
+
# Fall through to Agent-spawn template below. The SubagentStop hook (bin/hooks/interactive-engine.mjs)
|
|
192
|
+
# bridges these runs to the engine run-log for state persistence even on the fallback path.
|
|
193
|
+
fi
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Agent-spawn path (fallback when forgedock CLI unavailable)**: When `FORGEDOCK_AVAILABLE=false`, spawn Agent sub-agents per issue using the template below. This preserves engine state via the SubagentStop hook even without the CLI.
|
|
197
|
+
|
|
198
|
+
**REMINDER: You MUST use the template below verbatim when on the Agent-spawn fallback path. Only fill in `{VARIABLES}`. Do NOT rewrite the agent prompt. Do NOT write custom implementation instructions. The agent MUST invoke `/work-on` via the Skill tool — this is the HARD RULE from the top of this file.**
|
|
175
199
|
|
|
176
200
|
For each **ready** issue (all predecessors resolved or no predecessors), spawn an Agent sub-agent that runs the full `/work-on` pipeline. On the initial dispatch, this is every issue with an empty predecessor set. On subsequent dispatches (triggered by agent completions in Step 4B), this is every newly-unblocked issue.
|
|
177
201
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forgedock",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
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"
|