forgedock 1.0.16 → 1.0.17
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 +21 -3
- package/bin/forgedock.mjs +1992 -540
- package/bin/github-app-manifest.json.license +2 -0
- package/bin/hooks/session-start.mjs +4 -5
- package/bin/registry.mjs +20 -0
- package/bin/tui.mjs +172 -96
- package/commands/adopt.md +433 -0
- package/commands/analytics.md +2 -0
- package/commands/audit-agents.md +2 -0
- package/commands/audit.md +2 -0
- package/commands/changelog.md +354 -0
- package/commands/cleanup.md +2 -0
- package/commands/compat-audit.md +330 -0
- package/commands/deploy-info.md +6 -5
- package/commands/diagnose.md +483 -0
- package/commands/explain.md +349 -0
- package/commands/forge-stats.md +2 -0
- package/commands/forgedock-init.md +2 -0
- package/commands/geo-audit.md +2 -0
- package/commands/incident-response.md +2 -0
- package/commands/issue.md +2 -0
- package/commands/milestone.md +2 -0
- package/commands/optimize.md +411 -0
- package/commands/orchestrate.md +118 -0
- package/commands/pipeline-health.md +60 -1
- package/commands/qa-sweep.md +2 -0
- package/commands/quality-gate.md +37 -0
- package/commands/replay.md +263 -0
- package/commands/resume.md +264 -0
- package/commands/review-pr-agents.md +5 -1
- package/commands/review-pr-staging.md +2 -0
- package/commands/review-pr.md +85 -3
- package/commands/rollback.md +2 -0
- package/commands/scope.md +280 -0
- package/commands/security-audit.md +2 -0
- package/commands/status.md +262 -0
- package/commands/sync-ecosystem.md +2 -0
- package/commands/validate.md +2 -0
- package/commands/work-on/build/architect.md +128 -18
- package/commands/work-on/build/context.md +130 -17
- package/commands/work-on/build/implement.md +2 -0
- package/commands/work-on/build/validate.md +2 -0
- package/commands/work-on/build.md +32 -7
- package/commands/work-on/close.md +85 -0
- package/commands/work-on/decompose.md +2 -0
- package/commands/work-on/investigate.md +30 -3
- package/commands/work-on/review.md +13 -1
- package/commands/work-on-monolithic.md +2 -0
- package/commands/work-on.md +624 -36
- package/docs/BRAND.md +51 -0
- package/docs/CONFIG.md +94 -0
- package/docs/articles/per-repo-adaptive-scripts-token-savings.md +174 -0
- package/docs/claude-breakpoints.json +47 -0
- package/docs/site/getting-started.md +21 -0
- package/package.json +3 -3
- package/scripts/classify-lane.sh +145 -0
- package/scripts/transition-label.sh +161 -0
- package/scripts/validate-pr-target.sh +30 -0
- package/templates/devdocs/README.md +72 -0
- package/templates/devdocs/agent/using-forgedock.md +3 -0
- package/templates/devdocs/agent/using-github.md +3 -0
- package/templates/devdocs/index.yaml +162 -0
- package/templates/devdocs/project/architecture.md +3 -0
- package/templates/devdocs/project/conventions.md +3 -0
- package/templates/devdocs/project/custom-instructions.md +3 -0
- package/templates/devdocs/project/glossary.md +3 -0
- package/templates/devdocs/project/stack.md +3 -0
- package/templates/devdocs/templates/commit-convention.md +3 -0
- package/templates/devdocs/templates/issue.md +3 -0
- package/templates/devdocs/templates/pull-request.md +3 -0
- package/templates/devdocs/templates/review-finding.md +3 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Pipeline-wide situational awareness — groups open issues by workflow state, shows active PRs, flags stale items, and reports milestone progress
|
|
3
|
+
argument-hint: [--repo <owner/repo> | --stale-days <N>]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /status — Pipeline Status
|
|
7
|
+
|
|
8
|
+
**Input**: $ARGUMENTS (optional: `--repo <owner/repo>`, `--stale-days <N>`)
|
|
9
|
+
|
|
10
|
+
Read-only snapshot of the ForgeDock pipeline. Shows open issues grouped by workflow label, active PRs with age, stale items, and milestone progress.
|
|
11
|
+
|
|
12
|
+
**This command is fully read-only.** It makes no label changes, posts no comments, and creates no issues.
|
|
13
|
+
|
|
14
|
+
**NEVER use plan mode (EnterPlanMode)** — it breaks execution context.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Config Resolution
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
CONFIG_FILE="${FORGE_CONFIG:-forge.yaml}"
|
|
22
|
+
GH_REPO=$(yq '.project.owner + "/" + .project.repo' "$CONFIG_FILE")
|
|
23
|
+
GH_FLAG="-R $GH_REPO"
|
|
24
|
+
STAGING_BRANCH=$(yq '.branches.staging' "$CONFIG_FILE")
|
|
25
|
+
|
|
26
|
+
# Optional: --repo flag overrides the default repo
|
|
27
|
+
if echo "$ARGUMENTS" | grep -q -- "--repo"; then
|
|
28
|
+
GH_REPO=$(echo "$ARGUMENTS" | sed 's/.*--repo[[:space:]]\([^[:space:]]*\).*/\1/')
|
|
29
|
+
GH_FLAG="-R $GH_REPO"
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# Optional: --stale-days flag (default: 7)
|
|
33
|
+
STALE_DAYS=7
|
|
34
|
+
if echo "$ARGUMENTS" | grep -q -- "--stale-days"; then
|
|
35
|
+
STALE_DAYS=$(echo "$ARGUMENTS" | sed 's/.*--stale-days[[:space:]]\([^[:space:]]*\).*/\1/')
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
STALE_THRESHOLD=$(date -d "${STALE_DAYS} days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \
|
|
39
|
+
|| date -v-${STALE_DAYS}d +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \
|
|
40
|
+
|| echo "")
|
|
41
|
+
|
|
42
|
+
echo "Repo: $GH_REPO"
|
|
43
|
+
echo "Staging branch: $STAGING_BRANCH"
|
|
44
|
+
echo "Stale threshold: ${STALE_DAYS} days"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Phase 1: Issues by Workflow State
|
|
50
|
+
|
|
51
|
+
Fetch all open issues with `workflow:*` labels. Group and count by state.
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
echo ""
|
|
55
|
+
echo "=== Pipeline Issues — $(date +%Y-%m-%d) ==="
|
|
56
|
+
echo ""
|
|
57
|
+
|
|
58
|
+
# Workflow states to display (in pipeline order)
|
|
59
|
+
STATES="workflow:investigating workflow:building workflow:in-review workflow:ready-to-build needs-human"
|
|
60
|
+
|
|
61
|
+
for LABEL in $STATES; do
|
|
62
|
+
ISSUES=$(gh issue list $GH_FLAG \
|
|
63
|
+
--state open \
|
|
64
|
+
--label "$LABEL" \
|
|
65
|
+
--limit 50 \
|
|
66
|
+
--json number,title,updatedAt,assignees \
|
|
67
|
+
--jq '.[] | "#\(.number) \(.title[:60]) [updated: \(.updatedAt[:10])]"' \
|
|
68
|
+
2>/dev/null || echo "")
|
|
69
|
+
|
|
70
|
+
COUNT=$(echo "$ISSUES" | grep -c '#' 2>/dev/null || echo 0)
|
|
71
|
+
[ "$COUNT" = "0" ] && [ -z "$ISSUES" ] && COUNT=0
|
|
72
|
+
|
|
73
|
+
if [ -n "$ISSUES" ] && [ "$COUNT" -gt 0 ]; then
|
|
74
|
+
echo "[$LABEL] ($COUNT open)"
|
|
75
|
+
echo "$ISSUES" | while IFS= read -r line; do
|
|
76
|
+
echo " $line"
|
|
77
|
+
done
|
|
78
|
+
echo ""
|
|
79
|
+
else
|
|
80
|
+
echo "[$LABEL] — none"
|
|
81
|
+
fi
|
|
82
|
+
done
|
|
83
|
+
|
|
84
|
+
# Issues with no workflow label (untracked / not yet entered pipeline)
|
|
85
|
+
UNTRACKED=$(gh issue list $GH_FLAG \
|
|
86
|
+
--state open \
|
|
87
|
+
--limit 20 \
|
|
88
|
+
--json number,title,labels,createdAt \
|
|
89
|
+
--jq '.[] | select(
|
|
90
|
+
(.labels | map(.name) | any(startswith("workflow:"))) | not
|
|
91
|
+
) | "#\(.number) \(.title[:60]) [created: \(.createdAt[:10])]"' \
|
|
92
|
+
2>/dev/null || echo "")
|
|
93
|
+
|
|
94
|
+
UNTRACKED_COUNT=$(echo "$UNTRACKED" | grep -c '#' 2>/dev/null || echo 0)
|
|
95
|
+
if [ -n "$UNTRACKED" ] && [ "$UNTRACKED_COUNT" -gt 0 ]; then
|
|
96
|
+
echo "[untracked] ($UNTRACKED_COUNT — not yet in pipeline)"
|
|
97
|
+
echo "$UNTRACKED" | while IFS= read -r line; do
|
|
98
|
+
echo " $line"
|
|
99
|
+
done
|
|
100
|
+
echo ""
|
|
101
|
+
fi
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Phase 2: Active PRs
|
|
107
|
+
|
|
108
|
+
Show open PRs targeting `staging` or any `milestone/*` branch, with age and review status.
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
echo "=== Active PRs ==="
|
|
112
|
+
echo ""
|
|
113
|
+
|
|
114
|
+
gh pr list $GH_FLAG \
|
|
115
|
+
--state open \
|
|
116
|
+
--limit 30 \
|
|
117
|
+
--json number,title,headRefName,baseRefName,createdAt,reviews,isDraft \
|
|
118
|
+
--jq '.[] | {
|
|
119
|
+
number: .number,
|
|
120
|
+
title: .title[:55],
|
|
121
|
+
head: .headRefName,
|
|
122
|
+
base: .baseRefName,
|
|
123
|
+
age_days: (((now - (.createdAt | fromdateiso8601)) / 86400) | floor),
|
|
124
|
+
draft: .isDraft,
|
|
125
|
+
review_state: (
|
|
126
|
+
if (.reviews | length) == 0 then "no review"
|
|
127
|
+
elif (.reviews | map(select(.state == "APPROVED")) | length) > 0 then "APPROVED"
|
|
128
|
+
elif (.reviews | map(select(.state == "CHANGES_REQUESTED")) | length) > 0 then "CHANGES REQUESTED"
|
|
129
|
+
else "COMMENTED"
|
|
130
|
+
end
|
|
131
|
+
)
|
|
132
|
+
} | " PR #\(.number) \(.title) [\(.age_days)d old] [\(.review_state)]\(if .draft then " [DRAFT]" else "" end)\n → \(.head) → \(.base)"' \
|
|
133
|
+
2>/dev/null || echo " (none)"
|
|
134
|
+
|
|
135
|
+
echo ""
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Phase 3: Stale Items
|
|
141
|
+
|
|
142
|
+
Flag issues and PRs with no activity in the last `$STALE_DAYS` days.
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
echo "=== Stale Items (no activity > ${STALE_DAYS} days) ==="
|
|
146
|
+
echo ""
|
|
147
|
+
|
|
148
|
+
if [ -z "$STALE_THRESHOLD" ]; then
|
|
149
|
+
echo " (skipped — could not compute stale threshold on this platform)"
|
|
150
|
+
else
|
|
151
|
+
# Stale open issues with workflow labels
|
|
152
|
+
STALE_ISSUES=$(gh issue list $GH_FLAG \
|
|
153
|
+
--state open \
|
|
154
|
+
--limit 100 \
|
|
155
|
+
--json number,title,updatedAt,labels \
|
|
156
|
+
--jq --arg threshold "$STALE_THRESHOLD" \
|
|
157
|
+
'[.[] |
|
|
158
|
+
select(
|
|
159
|
+
(.updatedAt < $threshold) and
|
|
160
|
+
(.labels | map(.name) | any(startswith("workflow:")))
|
|
161
|
+
)
|
|
162
|
+
] | .[] | " ISSUE #\(.number) \(.title[:55]) [last updated: \(.updatedAt[:10])] [\(.labels | map(select(.name | startswith("workflow:"))) | .[0].name // "no-workflow")]"' \
|
|
163
|
+
2>/dev/null || echo "")
|
|
164
|
+
|
|
165
|
+
STALE_PRS=$(gh pr list $GH_FLAG \
|
|
166
|
+
--state open \
|
|
167
|
+
--limit 50 \
|
|
168
|
+
--json number,title,updatedAt \
|
|
169
|
+
--jq --arg threshold "$STALE_THRESHOLD" \
|
|
170
|
+
'[.[] | select(.updatedAt < $threshold)] |
|
|
171
|
+
.[] | " PR #\(.number) \(.title[:55]) [last updated: \(.updatedAt[:10])]"' \
|
|
172
|
+
2>/dev/null || echo "")
|
|
173
|
+
|
|
174
|
+
if [ -z "$STALE_ISSUES" ] && [ -z "$STALE_PRS" ]; then
|
|
175
|
+
echo " None — pipeline is moving."
|
|
176
|
+
else
|
|
177
|
+
[ -n "$STALE_ISSUES" ] && echo "$STALE_ISSUES"
|
|
178
|
+
[ -n "$STALE_PRS" ] && echo "$STALE_PRS"
|
|
179
|
+
fi
|
|
180
|
+
fi
|
|
181
|
+
|
|
182
|
+
echo ""
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Phase 4: Milestone Progress
|
|
188
|
+
|
|
189
|
+
Show open milestones with open/closed issue counts and percentage complete.
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
echo "=== Milestone Progress ==="
|
|
193
|
+
echo ""
|
|
194
|
+
|
|
195
|
+
MILESTONES=$(gh api repos/$GH_REPO/milestones \
|
|
196
|
+
--jq '.[] | select(.state == "open") | {
|
|
197
|
+
title: .title,
|
|
198
|
+
number: .number,
|
|
199
|
+
open: .open_issues,
|
|
200
|
+
closed: .closed_issues,
|
|
201
|
+
total: (.open_issues + .closed_issues),
|
|
202
|
+
due: (.due_on // "no due date")[:10],
|
|
203
|
+
pct: (if (.open_issues + .closed_issues) > 0 then
|
|
204
|
+
((.closed_issues / (.open_issues + .closed_issues)) * 100) | floor
|
|
205
|
+
else 0 end)
|
|
206
|
+
} | "\(.title) [\(.pct)% done — \(.closed)/\(.total) issues] [due: \(.due)]"' \
|
|
207
|
+
2>/dev/null || echo "")
|
|
208
|
+
|
|
209
|
+
if [ -z "$MILESTONES" ]; then
|
|
210
|
+
echo " No active milestones."
|
|
211
|
+
else
|
|
212
|
+
echo "$MILESTONES" | while IFS= read -r line; do
|
|
213
|
+
echo " $line"
|
|
214
|
+
done
|
|
215
|
+
fi
|
|
216
|
+
|
|
217
|
+
echo ""
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Phase 5: Bottleneck Summary
|
|
223
|
+
|
|
224
|
+
Highlight states where issues are piling up or stuck.
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
echo "=== Bottleneck Summary ==="
|
|
228
|
+
echo ""
|
|
229
|
+
|
|
230
|
+
INVESTIGATING=$(gh issue list $GH_FLAG --state open --label "workflow:investigating" --limit 100 --json number --jq '. | length' 2>/dev/null || echo 0)
|
|
231
|
+
BUILDING=$(gh issue list $GH_FLAG --state open --label "workflow:building" --limit 100 --json number --jq '. | length' 2>/dev/null || echo 0)
|
|
232
|
+
IN_REVIEW=$(gh issue list $GH_FLAG --state open --label "workflow:in-review" --limit 100 --json number --jq '. | length' 2>/dev/null || echo 0)
|
|
233
|
+
BLOCKED=$(gh issue list $GH_FLAG --state open --label "needs-human" --limit 100 --json number --jq '. | length' 2>/dev/null || echo 0)
|
|
234
|
+
READY=$(gh issue list $GH_FLAG --state open --label "workflow:ready-to-build" --limit 100 --json number --jq '. | length' 2>/dev/null || echo 0)
|
|
235
|
+
|
|
236
|
+
TOTAL_INFLIGHT=$((INVESTIGATING + BUILDING + IN_REVIEW + READY))
|
|
237
|
+
|
|
238
|
+
echo " investigating : $INVESTIGATING"
|
|
239
|
+
echo " ready-to-build : $READY"
|
|
240
|
+
echo " building : $BUILDING"
|
|
241
|
+
echo " in-review : $IN_REVIEW"
|
|
242
|
+
echo " needs-human : $BLOCKED $([ "$BLOCKED" -gt 0 ] && echo '← ACTION REQUIRED' || true)"
|
|
243
|
+
echo ""
|
|
244
|
+
echo " Total in-flight: $TOTAL_INFLIGHT"
|
|
245
|
+
|
|
246
|
+
if [ "$BLOCKED" -gt 0 ]; then
|
|
247
|
+
echo ""
|
|
248
|
+
echo " ⚠ $BLOCKED issue(s) need human attention:"
|
|
249
|
+
gh issue list $GH_FLAG --state open --label "needs-human" --limit 20 \
|
|
250
|
+
--json number,title \
|
|
251
|
+
--jq '.[] | " #\(.number) \(.title[:70])"' \
|
|
252
|
+
2>/dev/null || true
|
|
253
|
+
fi
|
|
254
|
+
|
|
255
|
+
if [ "$IN_REVIEW" -gt 3 ]; then
|
|
256
|
+
echo ""
|
|
257
|
+
echo " ⚠ Review queue depth is $IN_REVIEW — consider running /review-pr or /orchestrate fast-lane"
|
|
258
|
+
fi
|
|
259
|
+
|
|
260
|
+
echo ""
|
|
261
|
+
echo "=== Status complete — $(date +%Y-%m-%d\ %H:%M:%S) ==="
|
|
262
|
+
```
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
description: Detect API changes, sync satellite repos, and publish releases
|
|
3
3
|
argument-hint: [check | auto | status | publish | PR-number]
|
|
4
4
|
---
|
|
5
|
+
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
6
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
5
7
|
|
|
6
8
|
# /sync-ecosystem — Cross-Project Sync & Publish
|
|
7
9
|
|
package/commands/validate.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
description: Independently verify if a reported issue is actually a problem before making code changes.
|
|
3
3
|
argument-hint: [issue description or #number]
|
|
4
4
|
---
|
|
5
|
+
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
6
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
5
7
|
|
|
6
8
|
# /validate — Independent Issue Verification
|
|
7
9
|
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
description: Pre-implementation architecture planning — traces ALL affected code paths and produces an ordered implementation plan before any code is written
|
|
3
3
|
argument-hint: [issue number] [--repo GH_REPO] [--gh-flag GH_FLAG] [--files AFFECTED_FILES]
|
|
4
4
|
---
|
|
5
|
+
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
6
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
5
7
|
|
|
6
8
|
# work-on/build/architect — Multi-Path Implementation Planning
|
|
7
9
|
|
|
@@ -11,6 +13,23 @@ argument-hint: [issue number] [--repo GH_REPO] [--gh-flag GH_FLAG] [--files AFFE
|
|
|
11
13
|
|
|
12
14
|
---
|
|
13
15
|
|
|
16
|
+
## COMPLEXITY_BAND Guard (check BEFORE all phases)
|
|
17
|
+
|
|
18
|
+
Read COMPLEXITY_BAND from the `<!-- FORGE:FAST_PATH -->` comment on the issue:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
COMPLEXITY_BAND=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
22
|
+
--jq '.[] | select(.body | contains("FORGE:FAST_PATH")) | .body' 2>/dev/null \
|
|
23
|
+
| sed -n 's/.*\*\*COMPLEXITY_BAND\*\*: \([A-Z_]*\).*/\1/p' | head -1)
|
|
24
|
+
COMPLEXITY_BAND="${COMPLEXITY_BAND:-STANDARD}"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**If COMPLEXITY_BAND: TRIVIAL** → skip all phases (A0 through A5), post NO comment, return empty plan to caller immediately. Do not read any files. This is not an error — trivial single-file changes have no cross-path consistency risk. <!-- Added: forge#679 -->
|
|
28
|
+
|
|
29
|
+
**If COMPLEXITY_BAND: STANDARD or COMPLEX** → proceed to Phase A0 below.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
14
33
|
## Mission
|
|
15
34
|
|
|
16
35
|
Eliminate cross-path inconsistency bugs before any code is written. The single biggest class of production bugs is a change applied to one code path but not all sibling paths that share the same logic. This agent traces the FULL request/data flow from every entry point, identifies every file that must change, and produces an ordered implementation plan the builder follows exactly.
|
|
@@ -127,12 +146,103 @@ fi
|
|
|
127
146
|
|
|
128
147
|
### Step 2: Secondary reads — other applicable project files
|
|
129
148
|
|
|
130
|
-
|
|
149
|
+
**Index-first path** (preferred when `index.yaml` exists): Use domain-filtered selective loading instead of O(N) enumerate. Falls back to full enumerate when index is absent. Skip `project/custom-instructions.md` (already read in Step 1). Sort by authority (`required` first).
|
|
131
150
|
|
|
132
151
|
```bash
|
|
133
152
|
PROJECT_CONVENTIONS=""
|
|
153
|
+
INDEX_PATH="${DEVDOCS_PATH}/index.yaml"
|
|
154
|
+
|
|
155
|
+
# --- Read devdocs.index_path override from forge.yaml (optional) ---
|
|
156
|
+
if [ -f "$FORGE_YAML_PATH" ]; then
|
|
157
|
+
INDEX_PATH_OVERRIDE=$(grep -A5 '^devdocs:' "$FORGE_YAML_PATH" \
|
|
158
|
+
| grep '^\s*index_path:' \
|
|
159
|
+
| head -1 \
|
|
160
|
+
| sed 's/.*index_path:\s*//' \
|
|
161
|
+
| tr -d '"'"'"' \
|
|
162
|
+
| tr -d '[:space:]')
|
|
163
|
+
if [ -n "$INDEX_PATH_OVERRIDE" ]; then
|
|
164
|
+
case "$INDEX_PATH_OVERRIDE" in
|
|
165
|
+
/*) INDEX_PATH="$INDEX_PATH_OVERRIDE" ;;
|
|
166
|
+
*) INDEX_PATH="{REPO_PATH}/${INDEX_PATH_OVERRIDE}" ;;
|
|
167
|
+
esac
|
|
168
|
+
fi
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
if [ -n "$DEVDOCS_PATH" ] && [ -f "$INDEX_PATH" ]; then
|
|
172
|
+
# --- Index-first loading path ---
|
|
173
|
+
echo "Devdocs index found at ${INDEX_PATH} — using selective domain loading"
|
|
174
|
+
|
|
175
|
+
# Read issue labels to determine domain(s)
|
|
176
|
+
ISSUE_LABELS=$(gh issue view {NUMBER} -R {GH_REPO} --json labels \
|
|
177
|
+
--jq '[.labels[].name] | join(" ")' 2>/dev/null || echo "")
|
|
178
|
+
echo "Issue labels: ${ISSUE_LABELS:-none}"
|
|
179
|
+
|
|
180
|
+
# Read index content
|
|
181
|
+
INDEX_CONTENT=$(cat "$INDEX_PATH" 2>/dev/null || echo "")
|
|
182
|
+
|
|
183
|
+
# Extract always_load paths (lines with "path:" under "always_load:" block)
|
|
184
|
+
ALWAYS_LOAD_PATHS=$(echo "$INDEX_CONTENT" \
|
|
185
|
+
| awk '/^always_load:/,/^[a-z]/' \
|
|
186
|
+
| grep '^\s*-\s*path:' \
|
|
187
|
+
| sed 's/.*path:\s*//' \
|
|
188
|
+
| tr -d '"'"'"' \
|
|
189
|
+
| tr -d '[:space:]')
|
|
190
|
+
|
|
191
|
+
# Extract domain blocks matching any issue label keyword
|
|
192
|
+
DOMAIN_PATHS=""
|
|
193
|
+
for label in $ISSUE_LABELS; do
|
|
194
|
+
KEYWORD=$(echo "$label" | sed 's/^workflow://; s/^priority://; s/^review-finding$//')
|
|
195
|
+
[ -z "$KEYWORD" ] && continue
|
|
196
|
+
# Sanitize KEYWORD before AWK injection — strip chars that would break awk regex delimiters
|
|
197
|
+
SAFE_KEYWORD=$(printf '%s' "$KEYWORD" | tr -cd 'a-zA-Z0-9_-')
|
|
198
|
+
[ -z "$SAFE_KEYWORD" ] && continue
|
|
199
|
+
|
|
200
|
+
BLOCK_PATHS=$(echo "$INDEX_CONTENT" \
|
|
201
|
+
| awk "/^ ${SAFE_KEYWORD}:/{found=1; next} found && /^ [a-z]/{found=0} found && /^\s*-\s*path:/{print}" \
|
|
202
|
+
| sed 's/.*path:\s*//' \
|
|
203
|
+
| tr -d '"'"'"' \
|
|
204
|
+
| tr -d '[:space:]')
|
|
205
|
+
|
|
206
|
+
if [ -n "$BLOCK_PATHS" ]; then
|
|
207
|
+
echo "Domain '${SAFE_KEYWORD}' matched — adding docs: $(echo "$BLOCK_PATHS" | tr '\n' ' ')"
|
|
208
|
+
DOMAIN_PATHS="${DOMAIN_PATHS}${BLOCK_PATHS}"$'\n'
|
|
209
|
+
fi
|
|
210
|
+
done
|
|
211
|
+
|
|
212
|
+
# Combine always_load + domain paths (deduplicate)
|
|
213
|
+
ALL_PATHS=$(printf "%s\n%s" "$ALWAYS_LOAD_PATHS" "$DOMAIN_PATHS" \
|
|
214
|
+
| grep -v '^$' | sort -u)
|
|
215
|
+
|
|
216
|
+
# When no domain labels matched, load only always_load entries
|
|
217
|
+
if [ -z "$DOMAIN_PATHS" ]; then
|
|
218
|
+
echo "No domain labels matched index — loading always_load entries only"
|
|
219
|
+
ALL_PATHS="$ALWAYS_LOAD_PATHS"
|
|
220
|
+
fi
|
|
221
|
+
|
|
222
|
+
APPLICABLE_FILES=""
|
|
223
|
+
while IFS= read -r rel_path; do
|
|
224
|
+
[ -z "$rel_path" ] && continue
|
|
225
|
+
# Skip custom-instructions — already read in Step 1
|
|
226
|
+
[ "$rel_path" = "project/custom-instructions.md" ] && continue
|
|
227
|
+
ABS_PATH="${DEVDOCS_PATH}/${rel_path}"
|
|
228
|
+
[ -f "$ABS_PATH" ] || { echo "WARN: index references missing file: ${rel_path} — skipping"; continue; }
|
|
229
|
+
|
|
230
|
+
FRONTMATTER=$(awk '/^---/{c++; if(c==1){next} if(c==2){exit}} c==1{print}' "$ABS_PATH" 2>/dev/null)
|
|
231
|
+
AUTHORITY=$(echo "$FRONTMATTER" | grep 'authority:' | head -1 | sed 's/.*authority:\s*//' | tr -d ' ')
|
|
232
|
+
case "$AUTHORITY" in
|
|
233
|
+
required) SORT_KEY="1" ;;
|
|
234
|
+
recommended) SORT_KEY="2" ;;
|
|
235
|
+
*) SORT_KEY="3" ;;
|
|
236
|
+
esac
|
|
237
|
+
APPLICABLE_FILES="${APPLICABLE_FILES}${SORT_KEY}|${ABS_PATH}"$'\n'
|
|
238
|
+
done <<< "$ALL_PATHS"
|
|
239
|
+
|
|
240
|
+
APPLICABLE_FILES=$(printf "%s" "$APPLICABLE_FILES" | sort | cut -d'|' -f2-)
|
|
241
|
+
|
|
242
|
+
elif [ -n "$DEVDOCS_PATH" ]; then
|
|
243
|
+
# --- Fallback: O(N) enumerate (backward compatible — no index.yaml present) ---
|
|
244
|
+
echo "No index.yaml found at ${INDEX_PATH} — falling back to full enumerate (backward compatible)"
|
|
134
245
|
|
|
135
|
-
if [ -n "$DEVDOCS_PATH" ]; then
|
|
136
246
|
APPLICABLE_FILES=""
|
|
137
247
|
|
|
138
248
|
while IFS= read -r -d '' mdfile; do
|
|
@@ -148,32 +258,31 @@ if [ -n "$DEVDOCS_PATH" ]; then
|
|
|
148
258
|
recommended) SORT_KEY="2" ;;
|
|
149
259
|
*) SORT_KEY="3" ;;
|
|
150
260
|
esac
|
|
151
|
-
APPLICABLE_FILES="${APPLICABLE_FILES}${SORT_KEY}|${mdfile}\n
|
|
261
|
+
APPLICABLE_FILES="${APPLICABLE_FILES}${SORT_KEY}|${mdfile}"$'\n'
|
|
152
262
|
fi
|
|
153
263
|
done < <(find "$DEVDOCS_PATH" -name "*.md" -print0 2>/dev/null)
|
|
154
264
|
|
|
155
265
|
APPLICABLE_FILES=$(printf "$APPLICABLE_FILES" | sort | cut -d'|' -f2-)
|
|
266
|
+
fi
|
|
156
267
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
268
|
+
while IFS= read -r mdfile; do
|
|
269
|
+
[ -z "$mdfile" ] && continue
|
|
270
|
+
TOTAL_LINES=$(wc -l < "$mdfile" 2>/dev/null || echo 0)
|
|
271
|
+
if [ "$TOTAL_LINES" -gt 200 ]; then
|
|
272
|
+
FILE_CONTENT=$(head -200 "$mdfile")
|
|
273
|
+
TRUNCATION_NOTE="_[Truncated at 200 lines — ${TOTAL_LINES} total.]_"
|
|
274
|
+
else
|
|
275
|
+
FILE_CONTENT=$(cat "$mdfile")
|
|
276
|
+
TRUNCATION_NOTE=""
|
|
277
|
+
fi
|
|
167
278
|
|
|
168
|
-
|
|
169
|
-
|
|
279
|
+
REL_PATH="${mdfile#${DEVDOCS_PATH}/}"
|
|
280
|
+
PROJECT_CONVENTIONS="${PROJECT_CONVENTIONS}
|
|
170
281
|
|
|
171
282
|
#### \`${REL_PATH}\`
|
|
172
283
|
${FILE_CONTENT}
|
|
173
284
|
${TRUNCATION_NOTE}"
|
|
174
|
-
|
|
175
|
-
fi
|
|
176
|
-
```
|
|
285
|
+
done <<< "$APPLICABLE_FILES"
|
|
177
286
|
|
|
178
287
|
### Step 3: Precedence rules to enforce
|
|
179
288
|
|
|
@@ -411,6 +520,7 @@ gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:ARCHITECT -->
|
|
|
411
520
|
## Skip Conditions
|
|
412
521
|
|
|
413
522
|
Skip this entire step (post nothing, return empty plan to caller) if:
|
|
523
|
+
- **COMPLEXITY_BAND: TRIVIAL** — checked via FORGE:FAST_PATH comment at entry (see guard above) <!-- Primary skip path: forge#679 -->
|
|
414
524
|
- Issue creates only **new files** with no callers to find (e.g. a new command file with no existing integration point yet)
|
|
415
525
|
- Issue is a 1-file config or docs edit with no code logic
|
|
416
526
|
- Issue title starts with "docs:" or "chore:"
|