forgedock 1.1.11 → 1.3.0

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.
Files changed (109) hide show
  1. package/README.md +9 -3
  2. package/bin/cli-spawn-shared.mjs +25 -0
  3. package/bin/engine/phases.mjs +178 -28
  4. package/bin/engine/state.mjs +39 -7
  5. package/bin/engine-cli.mjs +85 -3
  6. package/bin/engine.mjs +71 -8
  7. package/bin/forgedock.mjs +322 -20
  8. package/bin/hooks/pre-tool-use.mjs +345 -10
  9. package/bin/init-enrich-api.mjs +12 -4
  10. package/bin/init-enrich-cli.mjs +328 -0
  11. package/bin/init-enrich.mjs +137 -0
  12. package/bin/journey.mjs +116 -15
  13. package/bin/runner.mjs +653 -10
  14. package/commands/adopt.md +1 -1
  15. package/commands/analytics.md +10 -7
  16. package/commands/audit-agents.md +29 -6
  17. package/commands/audit.md +9 -20
  18. package/commands/autopilot.md +52 -78
  19. package/commands/changelog.md +1 -1
  20. package/commands/ci-audit.md +19 -10
  21. package/commands/cleanup.md +1 -1
  22. package/commands/compat-audit.md +1 -1
  23. package/commands/deploy-info.md +1 -1
  24. package/commands/deploy-pr.md +1 -1
  25. package/commands/design-bench.md +1 -1
  26. package/commands/design-render-critique-loop.md +1 -1
  27. package/commands/diagnose.md +1 -1
  28. package/commands/forge-stats.md +6 -5
  29. package/commands/forgedock-init.md +1 -1
  30. package/commands/geo-audit.md +9 -6
  31. package/commands/incident-response.md +15 -5
  32. package/commands/issue.md +302 -15
  33. package/commands/milestone.md +45 -18
  34. package/commands/optimize.md +1 -1
  35. package/commands/orchestrate/phase-1-resolve.md +20 -5
  36. package/commands/orchestrate/phase-4-execution.md +8 -1
  37. package/commands/orchestrate/phase-5-cleanup.md +43 -2
  38. package/commands/orchestrate/phase-6-report.md +1 -0
  39. package/commands/orchestrate.md +1 -1
  40. package/commands/pipeline-health/phase-4-proposals.md +40 -10
  41. package/commands/pipeline-health/phase-5-report.md +140 -13
  42. package/commands/pipeline-health.md +1 -1
  43. package/commands/pipeline-resume.md +1 -1
  44. package/commands/pipeline-status.md +1 -1
  45. package/commands/qa-sweep.md +27 -4
  46. package/commands/recover-orphans.md +1 -1
  47. package/commands/replay.md +16 -2
  48. package/commands/review-pr-agents/protocols.md +4 -0
  49. package/commands/review-pr-agents/spec-cli.md +179 -0
  50. package/commands/review-pr-agents.md +7 -4
  51. package/commands/review-pr-staging.md +44 -15
  52. package/commands/review-pr.md +122 -28
  53. package/commands/rollback.md +15 -5
  54. package/commands/security-audit.md +14 -11
  55. package/commands/signal-planner.md +32 -12
  56. package/commands/spec-doctor.md +1 -1
  57. package/commands/sync-ecosystem.md +54 -11
  58. package/commands/test-gate.md +49 -11
  59. package/commands/upgrade-deps.md +518 -0
  60. package/commands/validate.md +16 -5
  61. package/commands/work-on/build/architect.md +1 -1
  62. package/commands/work-on/build/context.md +1 -1
  63. package/commands/work-on/build/implement.md +2 -2
  64. package/commands/work-on/build/validate.md +64 -2
  65. package/commands/work-on/build.md +10 -2
  66. package/commands/work-on/close.md +1 -1
  67. package/commands/work-on/decompose.md +35 -25
  68. package/commands/work-on/investigate.md +2 -2
  69. package/commands/work-on/remediate.md +1 -1
  70. package/commands/work-on/review.md +5 -3
  71. package/commands/work-on-monolithic.md +1 -1
  72. package/commands/work-on.md +28 -7
  73. package/docs/AIDER.md +264 -0
  74. package/docs/AUTOPILOT-CRON.md +130 -0
  75. package/docs/CI.md +18 -0
  76. package/docs/CONFIG.md +41 -2
  77. package/docs/CURSOR.md +135 -0
  78. package/docs/OPENCODE.md +312 -0
  79. package/docs/demo.gif +0 -0
  80. package/docs/eval/README.md +4 -4
  81. package/docs/launch/show-hn.md +3 -3
  82. package/docs/site/annotations-explained.md +2 -2
  83. package/docs/site/architecture.md +180 -0
  84. package/docs/site/command-reference.md +38 -0
  85. package/docs/site/configuration.md +243 -0
  86. package/docs/site/for-companies.md +4 -4
  87. package/docs/site/forge-annotation-protocol.md +1 -1
  88. package/docs/site/github-is-the-memory.md +2 -2
  89. package/docs/site/how-it-works.md +1 -1
  90. package/docs/site/public/llms-full.txt +380 -0
  91. package/docs/site/public/llms.txt +35 -0
  92. package/docs/spec/review-protocol.md +6 -0
  93. package/docs/spec-graph-schema.md +3 -3
  94. package/package.json +2 -2
  95. package/packages/protocol/README.md +75 -0
  96. package/packages/protocol/src/card.js +114 -0
  97. package/packages/protocol/src/cli.js +4 -77
  98. package/packages/protocol/src/emit.js +14 -3
  99. package/packages/protocol/src/index.js +2 -1
  100. package/packages/protocol/src/parse.js +17 -8
  101. package/packages/protocol/src/types.js +1 -1
  102. package/packages/protocol/src/validate.js +11 -0
  103. package/scripts/check-command-side-effects.sh +261 -0
  104. package/scripts/check-spec-markers.sh +273 -0
  105. package/scripts/classify-lane.sh +29 -7
  106. package/scripts/forge-run.sh +322 -0
  107. package/templates/cross-repo-trigger.yml +282 -0
  108. package/templates/cursor/forge.mdc +148 -0
  109. package/templates/github-actions/forgedock-autopilot.yml +113 -0
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Periodic security posture audit — runs a scripted 4-phase checklist against repo files (not diffs), creates GitHub issues for confirmed findings
3
- argument-hint: [--repo <prefix>] [--phase 1|2|3|4|all] [--dry-run]
3
+ argument-hint: "[--repo <prefix>] [--phase 1|2|3|4|all] [--dry-run]"
4
4
  install: extras
5
5
  ---
6
6
  <!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
@@ -515,20 +515,18 @@ gh label create "priority:P3" --color "C2E0C6" --description "Low priority — m
515
515
 
516
516
  ### 5C: Create GitHub issues for CONFIRMED findings
517
517
 
518
- For each CONFIRMED finding (not REVIEW-only), create an issue:
518
+ For each CONFIRMED finding (not REVIEW-only), create an issue — routed through the `/issue` create-hook's programmatic invocation contract (see `commands/issue.md` → Programmatic Invocation Contract) so dedup and mandatory-section validation run on every finding:
519
519
 
520
520
  ```bash
521
- gh issue create -R "{GH_REPO}" \
522
- --title "{ISSUE_TITLE}" \
523
- --label "security,audit-finding,{SEVERITY}" \
524
- --body "$(cat <<'ISSUE_EOF'
521
+ SECURITY_BODY_FILE=$(mktemp)
522
+ cat > "$SECURITY_BODY_FILE" <<'ISSUE_EOF'
525
523
  ## Problem
526
524
 
527
525
  {2-3 sentences describing the finding, its location, and exploitation path.}
528
526
 
529
527
  ## Evidence
530
528
 
531
- **File**: \`{FILE}\`
529
+ **File**: `{FILE}`
532
530
  **Line**: {LINE}
533
531
  **Finding**: {GREP_OUTPUT}
534
532
 
@@ -538,23 +536,28 @@ gh issue create -R "{GH_REPO}" \
538
536
 
539
537
  ## Affected Files
540
538
 
541
- 1. \`{FILE}\` — {what needs to change}
539
+ 1. `{FILE}` — {what needs to change}
542
540
 
543
541
  ## Acceptance Criteria
544
542
 
545
543
  - [ ] {Specific remediation step}
546
- - [ ] Verified by re-running \`/security-audit\` after fix
544
+ - [ ] Verified by re-running `/security-audit` after fix
547
545
 
548
546
  ## Context
549
547
 
550
548
  **Audited at**: {TIMESTAMP}
551
549
  **Audit phase**: {PHASE}
552
550
  **Severity**: {SEVERITY}
553
- **Source**: \`/security-audit\` run on \`{GH_REPO}\`
551
+ **Source**: `/security-audit` run on `{GH_REPO}`
554
552
  ISSUE_EOF
555
- )"
556
553
  ```
557
554
 
555
+ ```
556
+ Skill(skill="issue", args="--title \"{ISSUE_TITLE}\" --body-file \"$SECURITY_BODY_FILE\" --label \"security\" --label \"audit-finding\" --label \"{SEVERITY}\"")
557
+ ```
558
+
559
+ `security`, `audit-finding`, and `{SEVERITY}` are each passed as their own `--label` flag — `/issue`'s programmatic mode does not comma-split a single `--label` value. Note: `/issue` resolves the target repo from `forge.yaml → project.owner/repo` (or the satellite-prefix table), not from a caller-supplied `-R`/`--repo` flag — this is correct for the common case where `{GH_REPO}` matches `forge.yaml`'s project repo.
560
+
558
561
  ### 5D: Post audit summary comment (if invoked from a GitHub issue context)
559
562
 
560
563
  If this command was invoked from within a `/work-on` context or with an issue reference:
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Closed-loop autonomy — convert a production/analytics/incident signal into a dependency-ordered issue DAG, execute via /orchestrate, then verify the originating signal is resolved. Event-driven companion to /autopilot.
3
- argument-hint: [--signal '<json>' | --metric '<name> <threshold>' | --incident <issue#> | --geo | --dry-run | --max-issues N]
3
+ argument-hint: "[--signal '<json>' | --metric '<name> <threshold>' | --incident <issue#> | --geo | --dry-run | --max-issues N]"
4
4
  install: extras
5
5
  ---
6
6
  <!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
@@ -224,10 +224,10 @@ Detect cycles — if a cycle exists, break it by demoting the lower-priority uni
224
224
  Post the DAG to a tracker issue on GitHub. First, create the tracker issue:
225
225
 
226
226
  ```bash
227
- TRACKER_ISSUE=$(gh issue create -R {GH_REPO} \
228
- --title "signal-planner: DAG tracker for {SIGNAL.name}" \
229
- --label "signal-planner,priority:{SIGNAL.severity}" \
230
- --body "$(cat <<'TRACKER_EOF'
227
+ TRACKER_TITLE="signal-planner: DAG tracker for {SIGNAL.name}"
228
+ TRACKER_BODY_TMPFILE=$(mktemp)
229
+ trap 'rm -f "$TRACKER_BODY_TMPFILE"' EXIT
230
+ cat > "$TRACKER_BODY_TMPFILE" <<'TRACKER_EOF'
231
231
  ## Signal
232
232
 
233
233
  **Type**: {SIGNAL.type}
@@ -247,7 +247,18 @@ This tracker was created by \`/signal-planner\`. The dependency DAG below repres
247
247
  - [ ] All DAG issues merged
248
248
  - [ ] Originating signal verified resolved (FORGE:SIGNAL_RESOLVED posted)
249
249
  TRACKER_EOF
250
- )" --json number --jq '.number')
250
+ # Route through the /issue create-hook (canonical dedup + body validation) instead of a raw
251
+ # `gh issue create`. This body uses "## Acceptance" rather than the mandatory "## Acceptance
252
+ # Criteria"/"## Affected Files"/"## Problem" sections — /issue's Phase 3F will non-blockingly
253
+ # append placeholder mandatory sections; this is expected for a coordination tracker issue.
254
+ Skill(skill="issue", args="--title \"$TRACKER_TITLE\" --body-file \"$TRACKER_BODY_TMPFILE\" --label signal-planner --label \"priority:{SIGNAL.severity}\"")
255
+ rm -f "$TRACKER_BODY_TMPFILE"
256
+ trap - EXIT
257
+ TRACKER_ISSUE=$(gh issue list -R {GH_REPO} \
258
+ --state open \
259
+ --search "$TRACKER_TITLE" \
260
+ --json number,title \
261
+ --jq --arg t "$TRACKER_TITLE" '.[] | select(.title == $t) | .number' 2>/dev/null | head -1)
251
262
  ```
252
263
 
253
264
  Then post the `FORGE:PLAN_DAG` comment:
@@ -312,10 +323,10 @@ If governor would be breached, **hard fail** — do not create any issues, do no
312
323
  For each work unit with `existing == null`, create a GitHub issue:
313
324
 
314
325
  ```bash
315
- ISSUE_NUM=$(gh issue create -R {GH_REPO} \
316
- --title "{work_unit.title}" \
317
- --label "priority:{work_unit.priority},{work_unit.type}" \
318
- --body "$(cat <<'ISSUE_EOF'
326
+ WORK_UNIT_TITLE="{work_unit.title}"
327
+ WORK_UNIT_BODY_TMPFILE=$(mktemp)
328
+ trap 'rm -f "$WORK_UNIT_BODY_TMPFILE"' EXIT
329
+ cat > "$WORK_UNIT_BODY_TMPFILE" <<'ISSUE_EOF'
319
330
  ## Problem
320
331
 
321
332
  {work_unit.rationale — why this is needed to resolve the originating signal}
@@ -341,10 +352,19 @@ Signal context: {relevant subset of SIGNAL.context for this work unit}
341
352
  ---
342
353
  *Created by \`/signal-planner\` on $(date -u +%Y-%m-%dT%H:%M:%SZ). Will be investigated before any fix is applied.*
343
354
  ISSUE_EOF
344
- )" --json number --jq '.number')
355
+ # Route through the /issue create-hook (canonical dedup + body validation) instead of a raw
356
+ # `gh issue create`.
357
+ Skill(skill="issue", args="--title \"$WORK_UNIT_TITLE\" --body-file \"$WORK_UNIT_BODY_TMPFILE\" --label \"priority:{work_unit.priority}\" --label \"{work_unit.type}\"")
358
+ rm -f "$WORK_UNIT_BODY_TMPFILE"
359
+ trap - EXIT
360
+ ISSUE_NUM=$(gh issue list -R {GH_REPO} \
361
+ --state open \
362
+ --search "$WORK_UNIT_TITLE" \
363
+ --json number,title \
364
+ --jq --arg t "$WORK_UNIT_TITLE" '.[] | select(.title == $t) | .number' 2>/dev/null | head -1)
345
365
  ```
346
366
 
347
- Record each created issue number and update the DAG table in the tracker issue.
367
+ Record each created issue number and update the DAG table in the tracker issue. If `/issue` deduped this work unit against an existing open issue, `ISSUE_NUM` will come back empty — fall back to `work_unit.existing` (if already resolved during Phase 2) or treat the work unit as already covered and skip re-creation.
348
368
 
349
369
  ### 3C: Compile final DAG issue list
350
370
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Evidence-backed spec evolution — reads finding concentration for a spec file, drafts a spec diff, opens a spec-evolution PR with per-change citations
3
- argument-hint: [spec file path | "auto"]
3
+ argument-hint: "[spec file path | \"auto\"]"
4
4
  install: extras
5
5
  ---
6
6
  <!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Detect API changes, sync satellite repos, and publish releases
3
- argument-hint: [check | auto | status | publish | PR-number]
3
+ argument-hint: "[check | auto | status | publish | PR-number]"
4
4
  install: extras
5
5
  ---
6
6
  <!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
@@ -218,10 +218,16 @@ Create issues in the affected repos:
218
218
 
219
219
  ```bash
220
220
  # For satellite repos (separate GitHub repos):
221
- gh issue create -R {TARGET_REPO} \
222
- --title "feat(sync): {description of what API change needs to be reflected}" \
223
- --label "feature,priority:P2" \
224
- --body "$(cat <<'BODY_EOF'
221
+ #
222
+ # /issue resolves its target repo in Phase 1A from a `{prefix}:` token at the start of the
223
+ # free-text input, not from an explicit -R override — its multi-repo prefix table is keyed
224
+ # off forge.yaml → repos.satellites (the same registry this file already iterates). Prepend
225
+ # the satellite's configured {COMPONENT} (= satellites[].prefix) to the title so /issue routes
226
+ # to {TARGET_REPO} correctly instead of the default project repo.
227
+ SYNC_TITLE="feat(sync): {description of what API change needs to be reflected}"
228
+ SYNC_BODY_TMPFILE=$(mktemp)
229
+ trap 'rm -f "$SYNC_BODY_TMPFILE"' EXIT
230
+ cat > "$SYNC_BODY_TMPFILE" <<'BODY_EOF'
225
231
  ## Problem
226
232
 
227
233
  {1-3 sentences: what API or schema change in the upstream repo needs to be reflected in this satellite repo. What will break or be missing if this sync is not done.}
@@ -252,13 +258,41 @@ Files that need changes:
252
258
 
253
259
  {If applicable: describe the new API shape with an example}
254
260
  BODY_EOF
255
- )"
261
+ # Route through the /issue create-hook (canonical dedup + body validation) instead of a raw
262
+ # `gh issue create`. Prefixing the title with "{COMPONENT}: " lets /issue's Phase 1A resolve
263
+ # the satellite repo ({TARGET_REPO}) from forge.yaml → repos.satellites, matching how a human
264
+ # would invoke /issue for the same satellite (e.g. "mcp: add list_schemas tool"). The prefix
265
+ # is stripped by /issue's own routing — it does not become part of the created issue title.
266
+ Skill(skill="issue", args="\"{COMPONENT}: $SYNC_TITLE\" --title \"$SYNC_TITLE\" --body-file \"$SYNC_BODY_TMPFILE\" --label feature --label \"priority:P2\"")
267
+ rm -f "$SYNC_BODY_TMPFILE"
268
+ trap - EXIT
269
+ SYNC_ISSUE_NUMBER=$(gh issue list -R {TARGET_REPO} \
270
+ --state open \
271
+ --search "$SYNC_TITLE" \
272
+ --json number,title \
273
+ --jq --arg t "$SYNC_TITLE" '.[] | select(.title == $t) | .number' 2>/dev/null | head -1)
274
+
275
+ # Safety net: the "{COMPONENT}: " prefix trick above is not a documented part of the
276
+ # Programmatic Invocation Contract (issue.md has no --repo/-R flag for programmatic mode) —
277
+ # if /issue's Phase 1A did not honor the prefix and created the issue in the default project
278
+ # repo instead of {TARGET_REPO}, the lookup above returns empty and this would otherwise be
279
+ # silently treated as "deduped by /issue" further down. Distinguish the two cases explicitly.
280
+ if [ -z "$SYNC_ISSUE_NUMBER" ]; then
281
+ MISROUTED_CHECK=$(gh issue list \
282
+ --state open \
283
+ --search "$SYNC_TITLE" \
284
+ --json number,title \
285
+ --jq --arg t "$SYNC_TITLE" '.[] | select(.title == $t) | .number' 2>/dev/null | head -1)
286
+ if [ -n "$MISROUTED_CHECK" ]; then
287
+ echo "WARNING: '/issue' created #$MISROUTED_CHECK in the default project repo instead of {TARGET_REPO} — the '{COMPONENT}:' prefix routing did not take effect. Move/re-file the issue in {TARGET_REPO} manually."
288
+ fi
289
+ fi
256
290
 
257
291
  # For monorepo issues (SDKs, packages inside a single repo):
258
- gh issue create \
259
- --title "feat(sync): {description of what API change needs to be reflected}" \
260
- --label "feature,priority:P2" \
261
- --body "$(cat <<'BODY_EOF'
292
+ MONOREPO_SYNC_TITLE="feat(sync): {description of what API change needs to be reflected}"
293
+ MONOREPO_SYNC_BODY_TMPFILE=$(mktemp)
294
+ trap 'rm -f "$MONOREPO_SYNC_BODY_TMPFILE"' EXIT
295
+ cat > "$MONOREPO_SYNC_BODY_TMPFILE" <<'BODY_EOF'
262
296
  ## Problem
263
297
 
264
298
  {1-3 sentences: what API or schema change needs to be reflected in this SDK or package. What will be out of sync if not updated.}
@@ -285,7 +319,16 @@ Files that need changes:
285
319
  **Change**: {description of upstream API change}
286
320
  **Detected by**: `/sync-ecosystem`
287
321
  BODY_EOF
288
- )"
322
+ # Route through the /issue create-hook (canonical dedup + body validation) instead of a raw
323
+ # `gh issue create`. No prefix needed — this targets the default/monorepo repo.
324
+ Skill(skill="issue", args="--title \"$MONOREPO_SYNC_TITLE\" --body-file \"$MONOREPO_SYNC_BODY_TMPFILE\" --label feature --label \"priority:P2\"")
325
+ rm -f "$MONOREPO_SYNC_BODY_TMPFILE"
326
+ trap - EXIT
327
+ MONOREPO_SYNC_ISSUE_NUMBER=$(gh issue list \
328
+ --state open \
329
+ --search "$MONOREPO_SYNC_TITLE" \
330
+ --json number,title \
331
+ --jq --arg t "$MONOREPO_SYNC_TITLE" '.[] | select(.title == $t) | .number' 2>/dev/null | head -1)
289
332
  ```
290
333
 
291
334
  ### Add sync issues to Project board
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Deterministic deploy-gate — verify a staging→main bundle's acceptance criteria against running code before deploy
3
- argument-hint: [--prs "<N1 N2 ...>"] [--base <branch>]
3
+ argument-hint: "[--prs \"<N1 N2 ...>\"] [--base <branch>]"
4
4
  allowed-tools: Task, Bash, Read, Grep, Glob
5
5
  install: extras
6
6
  ---
@@ -520,10 +520,16 @@ else
520
520
  SOURCE_ISSUES_LIST="${SOURCE_ISSUES_LIST}\n- #${issue_num}"
521
521
  done
522
522
 
523
- ISSUE_NUM=$(gh issue create ${GH_FLAG} \
524
- --title "fix: test-gate FAIL — cluster '${cluster}' broken by staging→${DEFAULT_BRANCH} bundle" \
525
- --label "test-failure,priority:P1" \
526
- --body "$(cat <<ISSUE_EOF
523
+ TESTGATE_FAIL_TITLE="fix: test-gate FAIL — cluster '${cluster}' broken by staging→${DEFAULT_BRANCH} bundle"
524
+ # Defense-in-depth: /issue's arg tokenizer (commands/issue.md, forge#2094) uses
525
+ # an xargs-based tokenizer that never expands backtick/$(...) substitution, so
526
+ # this is no longer required for safety — but strip it anyway so the raw title
527
+ # stays readable if it round-trips through any other eval-based consumer.
528
+ # ${cluster} is config-controlled (forge.yaml verification.integration_tests), but sanitize
529
+ # unconditionally for defense-in-depth consistency with the other converted call sites.
530
+ TESTGATE_FAIL_TITLE=$(printf '%s' "$TESTGATE_FAIL_TITLE" | tr '`' "'" | sed 's/\$(/$ (/g')
531
+ TESTGATE_FAIL_BODY_FILE=$(mktemp)
532
+ cat <<ISSUE_EOF > "$TESTGATE_FAIL_BODY_FILE"
527
533
  ## Problem
528
534
 
529
535
  The \`/test-gate\` command detected a runtime failure in cluster \`${cluster}\` that was introduced by the staging→${DEFAULT_BRANCH} bundle. The failure did not exist on \`${BASE_BRANCH}\` (baseline PASS), confirming it is batch-introduced, not pre-existing.
@@ -570,7 +576,21 @@ $(echo -e "$SOURCE_ISSUES_LIST")
570
576
  $(yq ".verification.integration_tests[] | select(.cluster == \"${cluster}\") | .command" "$CONFIG_FILE")
571
577
  \`\`\`
572
578
  ISSUE_EOF
573
- )" --json number --jq '.number' 2>/dev/null || echo "FAILED")
579
+
580
+ # Route through the /issue create-hook's programmatic invocation contract (see
581
+ # commands/issue.md § "Programmatic Invocation Contract") instead of the raw issue-creation call.
582
+ Skill(skill="issue", args="--title \"$TESTGATE_FAIL_TITLE\" --body-file \"$TESTGATE_FAIL_BODY_FILE\" --label test-failure --label priority:P1")
583
+ rm -f "$TESTGATE_FAIL_BODY_FILE"
584
+
585
+ # /issue has no machine-readable return contract — resolve the created issue's number by
586
+ # exact-title search immediately after the call. Retry to absorb GitHub Search API indexing lag.
587
+ ISSUE_NUM=""
588
+ for _resolve_attempt in 1 2 3; do
589
+ ISSUE_NUM=$(gh issue list ${GH_FLAG} --search "in:title \"${TESTGATE_FAIL_TITLE}\"" --state open --limit 1 --json number --jq '.[0].number // empty' 2>/dev/null || echo "")
590
+ [ -n "$ISSUE_NUM" ] && break
591
+ sleep 2
592
+ done
593
+ [ -z "$ISSUE_NUM" ] && ISSUE_NUM="FAILED"
574
594
 
575
595
  if [ "$ISSUE_NUM" != "FAILED" ] && [ -n "$ISSUE_NUM" ]; then
576
596
  echo "Filed test-failure issue #${ISSUE_NUM} for cluster '${cluster}'"
@@ -786,10 +806,14 @@ if [ "${UNCOVERED_COUNT}" -gt 0 ]; then
786
806
  continue
787
807
  fi
788
808
 
789
- GAP_ISSUE_NUM=$(gh issue create ${GH_FLAG} \
790
- --title "fix: test-gap — uncovered criterion in issue #${GAP_ISSUE} (${GAP_TYPE})" \
791
- --label "test-gap" \
792
- --body "$(cat <<TGAP_EOF
809
+ TESTGAP_TITLE="fix: test-gap — uncovered criterion in issue #${GAP_ISSUE} (${GAP_TYPE})"
810
+ # Defense-in-depth: /issue's arg tokenizer (commands/issue.md, forge#2094) uses
811
+ # an xargs-based tokenizer that never expands backtick/$(...) substitution, so
812
+ # this is no longer required for safety — but strip it anyway so the raw title
813
+ # stays readable if it round-trips through any other eval-based consumer.
814
+ TESTGAP_TITLE=$(printf '%s' "$TESTGAP_TITLE" | tr '`' "'" | sed 's/\$(/$ (/g')
815
+ TESTGAP_BODY_FILE=$(mktemp)
816
+ cat <<TGAP_EOF > "$TESTGAP_BODY_FILE"
793
817
  ## Problem
794
818
 
795
819
  The \`/test-gate\` criteria-adequacy check (Phase 6) found a runtime-testable acceptance criterion in issue #${GAP_ISSUE} that is not covered by any configured test cluster. Coverage gaps cannot produce a clean PASS verdict — this criterion must be covered before the staging→${DEFAULT_BRANCH} bundle can pass the deploy gate.
@@ -822,7 +846,21 @@ Files to be identified during investigation. Start with the test suite for the \
822
846
  ${GAP_CRITERION}
823
847
  \`\`\`
824
848
  TGAP_EOF
825
- )" --json number --jq '.number' 2>/dev/null || echo "FAILED")
849
+
850
+ # Route through the /issue create-hook's programmatic invocation contract (see
851
+ # commands/issue.md § "Programmatic Invocation Contract") instead of the raw issue-creation call.
852
+ Skill(skill="issue", args="--title \"$TESTGAP_TITLE\" --body-file \"$TESTGAP_BODY_FILE\" --label test-gap")
853
+ rm -f "$TESTGAP_BODY_FILE"
854
+
855
+ # /issue has no machine-readable return contract — resolve the created issue's number by
856
+ # exact-title search immediately after the call. Retry to absorb GitHub Search API indexing lag.
857
+ GAP_ISSUE_NUM=""
858
+ for _resolve_attempt in 1 2 3; do
859
+ GAP_ISSUE_NUM=$(gh issue list ${GH_FLAG} --search "in:title \"${TESTGAP_TITLE}\"" --state open --limit 1 --json number --jq '.[0].number // empty' 2>/dev/null || echo "")
860
+ [ -n "$GAP_ISSUE_NUM" ] && break
861
+ sleep 2
862
+ done
863
+ [ -z "$GAP_ISSUE_NUM" ] && GAP_ISSUE_NUM="FAILED"
826
864
 
827
865
  if [ "$GAP_ISSUE_NUM" != "FAILED" ] && [ -n "$GAP_ISSUE_NUM" ]; then
828
866
  echo "Filed test-gap issue #${GAP_ISSUE_NUM} for uncovered criterion in #${GAP_ISSUE} (${GAP_TYPE})"