forgedock 1.0.10 → 1.0.11
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 +84 -0
- package/bin/forgedock.mjs +537 -5627
- package/bin/init-enrich-api.mjs +14 -4
- package/bin/tui.mjs +8 -5
- package/commands/forgedock-init.md +498 -317
- package/commands/work-on.md +4 -3
- package/docs/CONFIG.md +19 -0
- package/docs/FORGE-PROTOCOL.md +740 -0
- package/docs/anthropic-devrel-outreach.md +197 -0
- package/docs/articles/devto-how-i-built-an-ai-agent-that-never-forgets.md +165 -0
- package/docs/demo.tape +113 -0
- package/docs/distribution-submissions.md +181 -0
- package/docs/launch/reddit-r-claudeai.md +83 -0
- package/docs/launch/reddit-r-devops.md +80 -0
- package/docs/launch/reddit-r-localllama.md +86 -0
- package/docs/launch/reddit-r-programming.md +81 -0
- package/docs/launch/reddit-r-sideproject.md +84 -0
- package/docs/launch/show-hn.md +84 -0
- package/docs/site/command-reference.md +423 -0
- package/docs/site/forge-annotation-protocol.md +266 -0
- package/docs/site/getting-started.md +168 -0
- package/docs/site/how-it-works.md +191 -0
- package/docs/site/index.md +59 -0
- package/docs/site/vs-manual-workflows.md +167 -0
- package/docs/twitter-launch-thread.md +303 -0
- package/docs/youtube-demo-script.md +297 -0
- package/package.json +1 -1
|
@@ -1,455 +1,636 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
3
|
-
argument-hint: <
|
|
2
|
+
description: AI-powered forge.yaml config generator — scans codebase, queries GitHub, auto-fills all optional sections from detection
|
|
3
|
+
argument-hint: [--preserve | --interactive | --section <name>]
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# /forgedock-init —
|
|
6
|
+
# /forgedock-init — AI-Powered Config Generator
|
|
7
7
|
|
|
8
8
|
**Input**: $ARGUMENTS
|
|
9
9
|
|
|
10
|
-
You
|
|
11
|
-
|
|
12
|
-
**This is a backend, not a wizard.** You MUST NOT ask the user any interactive questions. You MUST NOT write any files. Unknowable items are represented as `low`-confidence TODO-flagged fields — never as blocking prompts.
|
|
10
|
+
You complete the `forge.yaml` configuration that `npx forgedock init` started. The CLI generates required sections (project, paths, branches) from auto-detection. Your job is to scan the codebase, query GitHub APIs, and produce a complete `forge.yaml` with every applicable optional section filled — using detected values directly, without asking for confirmation.
|
|
13
11
|
|
|
14
12
|
**Agent model policy**: Default `model: "sonnet"`. Fallback: `model: "opus"` if Sonnet is rate-limited.
|
|
15
13
|
**NEVER use plan mode (EnterPlanMode).**
|
|
16
14
|
|
|
17
15
|
---
|
|
18
16
|
|
|
19
|
-
##
|
|
17
|
+
## Argument Parsing
|
|
20
18
|
|
|
21
|
-
|
|
19
|
+
| Flag | Effect |
|
|
20
|
+
|------|--------|
|
|
21
|
+
| (none) | Fill all optional sections — overwrite existing values with newly detected ones, skip sections with nothing detected |
|
|
22
|
+
| `--preserve` | Keep existing active section values unchanged — only fill sections that are not yet configured |
|
|
23
|
+
| `--interactive` | Ask confirmation for every detected value and present menus for optional features |
|
|
24
|
+
| `--section <name>` | Fill only one section: `repos`, `project_board`, `services`, `review`, or `verification` |
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
Parse `$ARGUMENTS` and set:
|
|
27
|
+
```
|
|
28
|
+
PRESERVE = true if --preserve present
|
|
29
|
+
INTERACTIVE = true if --interactive present (restores full questionnaire behavior for all sections)
|
|
30
|
+
TARGET_SECTION = value from --section <name>, or "all"
|
|
25
31
|
```
|
|
26
32
|
|
|
27
|
-
|
|
28
|
-
- `high` — verified from a concrete, unambiguous source
|
|
29
|
-
- `medium` — inferred from available signals; likely correct
|
|
30
|
-
- `low` — guessed default or not found; caller should surface as a `# TODO(forgedock:<field>)` flag
|
|
31
|
-
|
|
32
|
-
This skill extends the draft by adding optional section fields. It never removes or downgrades existing fields. Required sections (project, paths, branches) are passed through unchanged.
|
|
33
|
-
|
|
34
|
-
---
|
|
35
|
-
|
|
36
|
-
## Phase 0: Parse Input and Extract Identity
|
|
37
|
-
|
|
38
|
-
### 0A: Parse ConfigDraft from $ARGUMENTS
|
|
33
|
+
If both `--preserve` and `--interactive` are present, `--interactive` takes precedence (`PRESERVE = false`).
|
|
39
34
|
|
|
40
|
-
|
|
35
|
+
If `--section` was provided, validate the value immediately:
|
|
41
36
|
|
|
42
|
-
```
|
|
43
|
-
|
|
37
|
+
```bash
|
|
38
|
+
ALLOWED_SECTIONS="repos project_board services review verification"
|
|
39
|
+
if [ "$TARGET_SECTION" != "all" ]; then
|
|
40
|
+
VALID=false
|
|
41
|
+
for s in $ALLOWED_SECTIONS; do
|
|
42
|
+
[ "$TARGET_SECTION" = "$s" ] && VALID=true && break
|
|
43
|
+
done
|
|
44
|
+
if [ "$VALID" = "false" ]; then
|
|
45
|
+
echo "Error: '$TARGET_SECTION' is not a valid section name."
|
|
46
|
+
echo ""
|
|
47
|
+
echo "Valid sections: repos, project_board, services, review, verification"
|
|
48
|
+
echo ""
|
|
49
|
+
echo "Usage: /forgedock-init --section <name>"
|
|
50
|
+
exit 1
|
|
51
|
+
fi
|
|
52
|
+
fi
|
|
44
53
|
```
|
|
45
54
|
|
|
46
|
-
|
|
55
|
+
---
|
|
47
56
|
|
|
48
|
-
|
|
49
|
-
Error: /forgedock-init expects a ConfigDraft JSON string as its argument.
|
|
57
|
+
## Phase 1: Load forge.yaml
|
|
50
58
|
|
|
51
|
-
|
|
52
|
-
The init-detect module produces the ConfigDraft. Example:
|
|
53
|
-
node -e "import('./bin/init-detect.mjs').then(m => m.detectConfig()).then(d => console.log(JSON.stringify(d)))"
|
|
59
|
+
### 1A: Locate and read
|
|
54
60
|
|
|
55
|
-
|
|
61
|
+
```bash
|
|
62
|
+
FORGE_YAML="$(pwd)/forge.yaml"
|
|
63
|
+
if [ ! -f "$FORGE_YAML" ]; then
|
|
64
|
+
echo "forge.yaml not found in current directory."
|
|
65
|
+
echo ""
|
|
66
|
+
echo "Run first: npx forgedock init"
|
|
67
|
+
echo ""
|
|
68
|
+
echo "This generates the required skeleton (project, paths, branches)."
|
|
69
|
+
echo "Then re-run /forgedock-init to complete the optional sections."
|
|
70
|
+
exit 1
|
|
71
|
+
fi
|
|
72
|
+
cat "$FORGE_YAML"
|
|
56
73
|
```
|
|
57
74
|
|
|
58
|
-
###
|
|
75
|
+
### 1B: Extract required section values
|
|
59
76
|
|
|
60
|
-
|
|
77
|
+
Parse these from the existing file — they are needed for all subsequent queries:
|
|
61
78
|
|
|
62
79
|
```
|
|
63
|
-
OWNER =
|
|
64
|
-
REPO =
|
|
65
|
-
REPO_ROOT =
|
|
80
|
+
OWNER = project.owner
|
|
81
|
+
REPO = project.repo
|
|
82
|
+
REPO_ROOT = paths.root
|
|
66
83
|
```
|
|
67
84
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
```
|
|
71
|
-
if OWNER is empty or equals "your-github-org":
|
|
72
|
-
set OWNER = null # GitHub queries will be skipped
|
|
73
|
-
note: "project.owner is a placeholder — board and satellite enrichment skipped"
|
|
85
|
+
After extracting these values, validate that OWNER and REPO are non-empty and do not contain placeholder values. If placeholders are found, attempt auto-detection from the git remote before erroring:
|
|
74
86
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
87
|
+
```bash
|
|
88
|
+
OWNER_IS_PLACEHOLDER=false
|
|
89
|
+
REPO_IS_PLACEHOLDER=false
|
|
90
|
+
[ -z "$OWNER" ] || [ "$OWNER" = "your-github-org" ] && OWNER_IS_PLACEHOLDER=true
|
|
91
|
+
[ -z "$REPO" ] || [ "$REPO" = "your-repo-name" ] && REPO_IS_PLACEHOLDER=true
|
|
92
|
+
|
|
93
|
+
if [ "$OWNER_IS_PLACEHOLDER" = "true" ] || [ "$REPO_IS_PLACEHOLDER" = "true" ]; then
|
|
94
|
+
echo "Placeholder values detected in forge.yaml — attempting auto-detection from git remote..."
|
|
95
|
+
|
|
96
|
+
REMOTE_URL=$(git -C "$REPO_ROOT" remote get-url origin 2>/dev/null || true)
|
|
97
|
+
DETECTED_OWNER=""
|
|
98
|
+
DETECTED_REPO=""
|
|
99
|
+
|
|
100
|
+
if [ -n "$REMOTE_URL" ]; then
|
|
101
|
+
# SSH format: git@github.com:owner/repo.git
|
|
102
|
+
if echo "$REMOTE_URL" | grep -qE '^git@[^:]+:'; then
|
|
103
|
+
DETECTED_OWNER=$(echo "$REMOTE_URL" | sed -E 's|^git@[^:]+:([^/]+)/.*|\1|')
|
|
104
|
+
DETECTED_REPO=$(echo "$REMOTE_URL" | sed -E 's|^git@[^:]+:[^/]+/||; s|\.git$||')
|
|
105
|
+
# HTTPS format: https://github.com/owner/repo.git
|
|
106
|
+
elif echo "$REMOTE_URL" | grep -qE '^https?://'; then
|
|
107
|
+
DETECTED_OWNER=$(echo "$REMOTE_URL" | sed -E 's|^https?://[^/]+/([^/]+)/.*|\1|')
|
|
108
|
+
DETECTED_REPO=$(echo "$REMOTE_URL" | sed -E 's|^https?://[^/]+/[^/]+/||; s|\.git$||')
|
|
109
|
+
fi
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
if [ -n "$DETECTED_OWNER" ] && [ -n "$DETECTED_REPO" ]; then
|
|
113
|
+
echo "Auto-detected: owner=\"$DETECTED_OWNER\" repo=\"$DETECTED_REPO\""
|
|
114
|
+
# Update forge.yaml in-place with detected values
|
|
115
|
+
[ "$OWNER_IS_PLACEHOLDER" = "true" ] && \
|
|
116
|
+
sed -i '/^project:/,/^[a-z]/{s|^ owner:.*| owner: "'"$DETECTED_OWNER"'"|}' "$FORGE_YAML"
|
|
117
|
+
[ "$REPO_IS_PLACEHOLDER" = "true" ] && \
|
|
118
|
+
sed -i "s|^ repo:.*| repo: \"$DETECTED_REPO\"|" "$FORGE_YAML"
|
|
119
|
+
echo "forge.yaml updated with auto-detected values. Continuing..."
|
|
120
|
+
# Re-read the corrected values
|
|
121
|
+
OWNER="$DETECTED_OWNER"
|
|
122
|
+
REPO="$DETECTED_REPO"
|
|
123
|
+
else
|
|
124
|
+
echo "Error: project.owner/project.repo contain placeholder values and auto-detection failed."
|
|
125
|
+
echo ""
|
|
126
|
+
echo "Could not detect owner/repo from git remote. Open forge.yaml and fill in the 'project:' section:"
|
|
127
|
+
echo " project:"
|
|
128
|
+
echo " owner: \"your-actual-github-org-or-username\""
|
|
129
|
+
echo " repo: \"your-actual-repo-name\""
|
|
130
|
+
echo ""
|
|
131
|
+
echo "Then re-run /forgedock-init."
|
|
132
|
+
exit 1
|
|
133
|
+
fi
|
|
134
|
+
fi
|
|
78
135
|
```
|
|
79
136
|
|
|
80
|
-
|
|
137
|
+
### 1C: Detect already-configured optional sections
|
|
81
138
|
|
|
82
|
-
|
|
139
|
+
For each optional section, check if it has an UNCOMMENTED entry in the file. A commented-out section (all lines starting with `#`) is NOT configured.
|
|
83
140
|
|
|
84
141
|
```bash
|
|
85
|
-
|
|
142
|
+
# Check which optional sections are already active (uncommented)
|
|
143
|
+
grep -q "^repos:" "$FORGE_YAML" && HAS_REPOS=true || HAS_REPOS=false
|
|
144
|
+
grep -q "^project_board:" "$FORGE_YAML" && HAS_PROJECT_BOARD=true || HAS_PROJECT_BOARD=false
|
|
145
|
+
grep -q "^services:" "$FORGE_YAML" && HAS_SERVICES=true || HAS_SERVICES=false
|
|
146
|
+
grep -q "^review:" "$FORGE_YAML" && HAS_REVIEW=true || HAS_REVIEW=false
|
|
147
|
+
grep -q "^verification:" "$FORGE_YAML" && HAS_VERIFICATION=true || HAS_VERIFICATION=false
|
|
86
148
|
```
|
|
87
149
|
|
|
88
|
-
|
|
150
|
+
If `PRESERVE=true` and a section is already active, skip it (unless `--section` targets it explicitly). For skipped sections, tell the user: `{section}: already configured — skipped (--preserve). Use /forgedock-init --section {section} to update.`
|
|
89
151
|
|
|
90
|
-
If `
|
|
152
|
+
If `PRESERVE=false` (the default) and a section is already active, it will be overwritten with newly detected values in Phase 5C — no prompt needed.
|
|
91
153
|
|
|
92
154
|
---
|
|
93
155
|
|
|
94
|
-
## Phase
|
|
156
|
+
## Phase 2: Codebase Scan
|
|
95
157
|
|
|
96
|
-
|
|
158
|
+
Run these scans unconditionally — they populate defaults that reduce the number of clarifying questions needed.
|
|
97
159
|
|
|
98
|
-
###
|
|
160
|
+
### 2A: Tech stack detection
|
|
99
161
|
|
|
100
162
|
```bash
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
If the query fails or returns an empty list: skip Phase 1 entirely. Add a `low`-confidence placeholder for the entire `project_board` section (see 1E).
|
|
163
|
+
# Node.js / TypeScript
|
|
164
|
+
[ -f "$REPO_ROOT/package.json" ] && \
|
|
165
|
+
cat "$REPO_ROOT/package.json" | grep -E '"name"|"dependencies"|"devDependencies"' | head -30
|
|
106
166
|
|
|
107
|
-
|
|
108
|
-
|
|
167
|
+
# Python
|
|
168
|
+
[ -f "$REPO_ROOT/pyproject.toml" ] && grep -E "^\[project\]|^name|^requires-python|^dependencies" "$REPO_ROOT/pyproject.toml" | head -20
|
|
169
|
+
[ -f "$REPO_ROOT/requirements.txt" ] && head -20 "$REPO_ROOT/requirements.txt"
|
|
109
170
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
PROJECT_NUMBER = first (or only) project number
|
|
113
|
-
PROJECT_TITLE = project title
|
|
114
|
-
PROJECT_ID = project id (the PVT_... node ID)
|
|
115
|
-
```
|
|
171
|
+
# Rust
|
|
172
|
+
[ -f "$REPO_ROOT/Cargo.toml" ] && grep -E "^\[package\]|^name|^edition|^\[dependencies\]" "$REPO_ROOT/Cargo.toml" | head -20
|
|
116
173
|
|
|
117
|
-
|
|
174
|
+
# Ruby
|
|
175
|
+
[ -f "$REPO_ROOT/Gemfile" ] && head -15 "$REPO_ROOT/Gemfile"
|
|
118
176
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
| jq '[.fields[] | {name: .name, id: .id, type: .type, options: (.options // [])}]'
|
|
177
|
+
# Go
|
|
178
|
+
[ -f "$REPO_ROOT/go.mod" ] && head -10 "$REPO_ROOT/go.mod"
|
|
122
179
|
```
|
|
123
180
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
### 1C: Map fields to ForgeDock slots
|
|
127
|
-
|
|
128
|
-
For each of the five ForgeDock field slots, search the field list case-insensitively:
|
|
129
|
-
|
|
130
|
-
| ForgeDock slot | Primary match | Fallback match |
|
|
131
|
-
|----------------|---------------|----------------|
|
|
132
|
-
| `status` | "Status" | "State" |
|
|
133
|
-
| `lane` | "Lane" | "Track", "Type" |
|
|
134
|
-
| `component` | "Component" | "Area", "Team" |
|
|
135
|
-
| `priority` | "Priority" | "Severity" |
|
|
136
|
-
| `workflow` | "Workflow" | "Stage", "Phase" |
|
|
181
|
+
Build `TECH_STACK` description from findings (e.g., "Next.js 15, FastAPI, PostgreSQL").
|
|
137
182
|
|
|
138
|
-
|
|
139
|
-
- If a field is found: confidence = `high`, source = `gh project field-list`, why = `Field named "<name>" (id: <id>) found in project "<PROJECT_TITLE>"`
|
|
140
|
-
- If no field matches: confidence = `low`, value = `""`, why = `No field matching "<slot>" found in project "<PROJECT_TITLE>"; add a field with this name and re-run /forgedock-init`
|
|
183
|
+
### 2B: Project context
|
|
141
184
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
For each of the required option slots, match case-insensitively:
|
|
185
|
+
```bash
|
|
186
|
+
# CLAUDE.md — primary context source
|
|
187
|
+
[ -f "$REPO_ROOT/CLAUDE.md" ] && cat "$REPO_ROOT/CLAUDE.md"
|
|
147
188
|
|
|
148
|
-
|
|
189
|
+
# README.md — fallback
|
|
190
|
+
[ -f "$REPO_ROOT/README.md" ] && head -60 "$REPO_ROOT/README.md"
|
|
191
|
+
```
|
|
149
192
|
|
|
150
|
-
|
|
193
|
+
Extract a 2–4 sentence project description for `review.context`.
|
|
151
194
|
|
|
152
|
-
|
|
195
|
+
### 2C: Docker / service names
|
|
153
196
|
|
|
154
|
-
|
|
197
|
+
```bash
|
|
198
|
+
# Container name prefixes for verification section
|
|
199
|
+
ls "$REPO_ROOT"/docker-compose*.yml 2>/dev/null
|
|
200
|
+
for f in "$REPO_ROOT"/docker-compose*.yml; do
|
|
201
|
+
[ -f "$f" ] || continue
|
|
202
|
+
grep -E "^\s+container_name:|^\s+image:" "$f" | head -20
|
|
203
|
+
done
|
|
204
|
+
```
|
|
155
205
|
|
|
156
|
-
|
|
157
|
-
- If matched: confidence = `high`, source = `gh project field-list (options)`, why = `Option "<name>" (id: <id>) found in field "<field_name>"`
|
|
158
|
-
- If not matched: confidence = `low`, value = `""`, why = `No option matching "<slot>" found in field "<field_name>"; add the option or set this value manually`
|
|
206
|
+
Extract container name prefixes (e.g., `acme-`, `my-app-`) for `verification.internal_service_patterns` and `verification.services`.
|
|
159
207
|
|
|
160
|
-
###
|
|
208
|
+
### 2D: Branch topology
|
|
161
209
|
|
|
162
|
-
|
|
210
|
+
```bash
|
|
211
|
+
# Staging branch detection
|
|
212
|
+
git -C "$REPO_ROOT" branch -r | grep -E "staging|develop|dev" | head -5
|
|
163
213
|
|
|
164
|
-
|
|
165
|
-
"
|
|
166
|
-
"owner": { "value": "", "confidence": "low", "source": "skipped", "why": "GitHub auth unavailable or project.owner is a placeholder; run gh auth login then re-run /forgedock-init" },
|
|
167
|
-
"project_number": { "value": "", "confidence": "low", "source": "skipped", "why": "GitHub auth unavailable; project board not queried" },
|
|
168
|
-
"project_id": { "value": "", "confidence": "low", "source": "skipped", "why": "GitHub auth unavailable; obtain via: gh project list --owner <owner> --format json | jq '.projects[] | .id' (value must start with PVT_)" },
|
|
169
|
-
"field_ids": { ... all slots at low confidence ... },
|
|
170
|
-
"option_ids": { ... all option slots at low confidence ... }
|
|
171
|
-
}
|
|
214
|
+
# CI/CD target branches
|
|
215
|
+
grep -rh "branches:" "$REPO_ROOT/.github/workflows/"*.yml 2>/dev/null | head -20
|
|
172
216
|
```
|
|
173
217
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
---
|
|
177
|
-
|
|
178
|
-
## Phase 2: Enrich repos.satellites
|
|
179
|
-
|
|
180
|
-
**Skip if**: `GH_AUTHED = false` or `OWNER = null` or `REPO = null`.
|
|
218
|
+
Use to confirm or suggest `branches.staging`.
|
|
181
219
|
|
|
182
|
-
###
|
|
220
|
+
### 2E: API health endpoint hints
|
|
183
221
|
|
|
184
222
|
```bash
|
|
185
|
-
|
|
186
|
-
|
|
223
|
+
# Look for health route definitions
|
|
224
|
+
grep -rn "health\|/ping\|/status" "$REPO_ROOT" --include="*.py" --include="*.ts" --include="*.go" -l 2>/dev/null | head -10
|
|
225
|
+
grep -rn "healthcheck\|health_endpoint\|HEALTH_URL" "$REPO_ROOT" --include="*.yml" --include="*.yaml" -l 2>/dev/null | head -5
|
|
187
226
|
```
|
|
188
227
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
### 2B: Infer satellite candidates
|
|
228
|
+
Build `DETECTED_HEALTH_PATH` from findings (e.g., `/health`, `/api/health`). If nothing found, leave empty.
|
|
192
229
|
|
|
193
|
-
|
|
230
|
+
### 2F: Analytics/services detection
|
|
194
231
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
-
|
|
198
|
-
- Description mentions "companion", "client for", "SDK for", "plugin for", "server for"
|
|
199
|
-
|
|
200
|
-
**Exclude**:
|
|
201
|
-
- Names that are clearly personal/fork repos (contain the word "fork", start with the owner name exactly)
|
|
202
|
-
- Repos with zero description and language = null (likely empty/template repos)
|
|
203
|
-
|
|
204
|
-
For each candidate, derive a prefix:
|
|
205
|
-
- Strip the `$OWNER-` prefix from the name if present (e.g. `acme-mcp-server` → `mcp-server`)
|
|
206
|
-
- Take the first word before any `-` (e.g. `mcp-server` → `mcp`)
|
|
207
|
-
- If the prefix conflicts with another candidate, use the first two words
|
|
208
|
-
|
|
209
|
-
### 2C: Build repos enrichment
|
|
232
|
+
```bash
|
|
233
|
+
# Umami — look for self-hosted instance config or tracking script
|
|
234
|
+
grep -rn "umami\|UMAMI_WEBSITE_ID\|umami.is" "$REPO_ROOT" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" --include="*.env*" -l 2>/dev/null | head -5
|
|
210
235
|
|
|
211
|
-
|
|
236
|
+
# Microsoft Clarity
|
|
237
|
+
grep -rn "clarity\|CLARITY_PROJECT_ID\|clarity.ms" "$REPO_ROOT" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.html" -l 2>/dev/null | head -5
|
|
212
238
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
"prefix": { "value": "<derived-prefix>", "confidence": "medium", "source": "gh repo list + name heuristic", "why": "Repo '<name>' looks like a satellite (name pattern: <pattern>); verify prefix is correct" },
|
|
216
|
-
"repo": { "value": "<owner>/<name>", "confidence": "high", "source": "gh repo list", "why": "Repo exists under owner <owner>" },
|
|
217
|
-
"staging_branch": { "value": "main", "confidence": "low", "source": "default", "why": "Could not verify staging branch without reading satellite repo; defaulting to 'main'" }
|
|
218
|
-
}
|
|
239
|
+
# Google Analytics 4
|
|
240
|
+
grep -rn "G-[A-Z0-9]\+\|GA4\|gtag\|NEXT_PUBLIC_GA_ID\|GA_MEASUREMENT_ID" "$REPO_ROOT" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.env*" -l 2>/dev/null | head -5
|
|
219
241
|
```
|
|
220
242
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
"_enrichment_note": { "value": "none", "confidence": "low", "source": "gh repo list", "why": "No repos under <owner> matched satellite heuristics; if satellites exist, add them manually to forge.yaml" }
|
|
227
|
-
}
|
|
228
|
-
```
|
|
243
|
+
Build `DETECTED_ANALYTICS` list from findings:
|
|
244
|
+
- `DETECTED_UMAMI=true` if umami references found
|
|
245
|
+
- `DETECTED_CLARITY=true` if Clarity references found
|
|
246
|
+
- `DETECTED_GA4=true` if GA4 references found
|
|
247
|
+
- If none found, `DETECTED_ANALYTICS_EMPTY=true`
|
|
229
248
|
|
|
230
249
|
---
|
|
231
250
|
|
|
232
|
-
## Phase 3:
|
|
251
|
+
## Phase 3: GitHub Queries
|
|
233
252
|
|
|
234
|
-
|
|
253
|
+
Run these to gather live IDs. Failures are non-blocking — skip the section if the query fails.
|
|
235
254
|
|
|
236
|
-
### 3A:
|
|
255
|
+
### 3A: Check GitHub auth
|
|
237
256
|
|
|
238
|
-
For each supported ecosystem, check for its manifest file under `$REPO_ROOT`:
|
|
239
|
-
|
|
240
|
-
**Node.js / TypeScript**:
|
|
241
257
|
```bash
|
|
242
|
-
|
|
243
|
-
# Extract name, framework dependencies
|
|
244
|
-
cat "$REPO_ROOT/package.json" | jq '{
|
|
245
|
-
name: .name,
|
|
246
|
-
deps: ((.dependencies // {}) + (.devDependencies // {})) | keys | map(select(
|
|
247
|
-
test("next|react|vue|nuxt|svelte|express|fastify|hono|nest|koa|astro|remix|gatsby|vite|esbuild|rollup|webpack|prisma|drizzle|sequelize|typeorm|mongoose")
|
|
248
|
-
))
|
|
249
|
-
}'
|
|
250
|
-
fi
|
|
258
|
+
gh auth status 2>&1
|
|
251
259
|
```
|
|
252
260
|
|
|
253
|
-
|
|
254
|
-
```bash
|
|
255
|
-
[ -f "$REPO_ROOT/pyproject.toml" ] && grep -E "^(name|requires-python)" "$REPO_ROOT/pyproject.toml" | head -5
|
|
256
|
-
[ -f "$REPO_ROOT/pyproject.toml" ] && grep -E "fastapi|django|flask|starlette|sqlalchemy|pydantic|alembic|celery|aiohttp" "$REPO_ROOT/pyproject.toml" | head -10
|
|
257
|
-
[ -f "$REPO_ROOT/requirements.txt" ] && grep -iE "fastapi|django|flask|starlette|sqlalchemy|pydantic|alembic|celery|aiohttp" "$REPO_ROOT/requirements.txt" | head -10
|
|
258
|
-
```
|
|
261
|
+
If not authenticated, skip all GitHub queries and note which sections will need manual completion.
|
|
259
262
|
|
|
260
|
-
|
|
261
|
-
```bash
|
|
262
|
-
[ -f "$REPO_ROOT/Cargo.toml" ] && grep -E "^name|axum|actix|rocket|tokio|serde|sqlx" "$REPO_ROOT/Cargo.toml" | head -10
|
|
263
|
-
```
|
|
263
|
+
### 3B: Project board discovery
|
|
264
264
|
|
|
265
|
-
**Go**:
|
|
266
265
|
```bash
|
|
267
|
-
|
|
266
|
+
# List projects owned by this org/user
|
|
267
|
+
gh project list --owner "$OWNER" --format json 2>/dev/null \
|
|
268
|
+
| jq '.projects[] | {number: .number, title: .title, id: .id}'
|
|
268
269
|
```
|
|
269
270
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
271
|
+
Store results in `FOUND_PROJECT_BOARDS`. Do NOT ask the user at this stage — Phase 4B decides based on the count of boards found. If exactly one is found, it will be used automatically. If multiple are found, Phase 4B will ask once. Record:
|
|
272
|
+
- `PROJECT_NUMBER`
|
|
273
|
+
- `PROJECT_ID` (the `PVT_...` node ID)
|
|
274
|
+
|
|
275
|
+
### 3C: Project board field IDs
|
|
274
276
|
|
|
275
|
-
**Docker / infrastructure**:
|
|
276
277
|
```bash
|
|
277
|
-
|
|
278
|
-
|
|
278
|
+
# Only run if PROJECT_NUMBER is set
|
|
279
|
+
gh project field-list "$PROJECT_NUMBER" --owner "$OWNER" --format json 2>/dev/null \
|
|
280
|
+
| jq '.fields[] | {name: .name, id: .id, type: .type}'
|
|
279
281
|
```
|
|
280
282
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
- One or more manifest files found → `medium` (detected from deps but not verified running)
|
|
285
|
-
- Nothing detected → `low`
|
|
286
|
-
|
|
287
|
-
### 3B: Extract project context
|
|
283
|
+
Map field names to their IDs:
|
|
284
|
+
- Look for fields named `Status`, `Lane`, `Component`, `Priority`, `Workflow` (case-insensitive)
|
|
285
|
+
- Record their `PVTSSF_...` IDs
|
|
288
286
|
|
|
289
287
|
```bash
|
|
290
|
-
#
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
fi
|
|
294
|
-
|
|
295
|
-
# Fallback: README.md (first 80 lines)
|
|
296
|
-
if [ ! -f "$REPO_ROOT/CLAUDE.md" ] && [ -f "$REPO_ROOT/README.md" ]; then
|
|
297
|
-
head -80 "$REPO_ROOT/README.md"
|
|
298
|
-
fi
|
|
288
|
+
# Get option IDs for single-select fields
|
|
289
|
+
gh project field-list "$PROJECT_NUMBER" --owner "$OWNER" --format json 2>/dev/null \
|
|
290
|
+
| jq '.fields[] | select(.type == "SINGLE_SELECT") | {name: .name, options: [.options[] | {name: .name, id: .id}]}'
|
|
299
291
|
```
|
|
300
292
|
|
|
301
|
-
|
|
302
|
-
- What the project does
|
|
303
|
-
- Its architecture (if mentioned)
|
|
304
|
-
- Any deploy or infrastructure notes relevant to a code reviewer
|
|
305
|
-
- Known conventions or pitfalls (if mentioned)
|
|
293
|
+
Map option names to IDs for `status` (Todo/In Progress/Done), `lane` (Fast/Feature/Sync), `priority` (P0/P1/P2/P3), `workflow` (Investigating/Building/In Review/Merged).
|
|
306
294
|
|
|
307
|
-
|
|
295
|
+
### 3D: Satellite repo discovery
|
|
308
296
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
### 3C: Build review enrichment
|
|
315
|
-
|
|
316
|
-
```json
|
|
317
|
-
"review": {
|
|
318
|
-
"tech_stack": { "value": "<TECH_STACK>", "confidence": "<high|medium|low>", "source": "<manifest files found>", "why": "<what was detected>" },
|
|
319
|
-
"context": { "value": "<REVIEW_CONTEXT>", "confidence": "<high|medium|low>", "source": "<CLAUDE.md|README.md|none>", "why": "<synthesis note>" }
|
|
320
|
-
}
|
|
297
|
+
```bash
|
|
298
|
+
# List all repos under this owner
|
|
299
|
+
gh repo list "$OWNER" --limit 50 --json name,description,url 2>/dev/null \
|
|
300
|
+
| jq '.[] | select(.name != "'"$REPO"'") | {name: .name, description: .description}'
|
|
321
301
|
```
|
|
322
302
|
|
|
323
|
-
|
|
303
|
+
Store results in `SIBLING_REPOS`. Assess satellite signals for each repo — strong signals are: separate CI workflow files, separate `package.json`, separate staging branch. Do NOT ask the user at this stage — Phase 4A decides whether to ask based on signal strength.
|
|
324
304
|
|
|
325
|
-
|
|
305
|
+
---
|
|
326
306
|
|
|
327
|
-
|
|
307
|
+
## Phase 4: Resolve Values for Optional Sections
|
|
308
|
+
|
|
309
|
+
**Decision rule** (applies to every sub-phase below):
|
|
310
|
+
- **Auto-detected value** → use it directly. No confirmation prompt.
|
|
311
|
+
- **Genuine ambiguity** (e.g., multiple project boards, multiple candidates for the same field) → ask once with the detected candidates listed.
|
|
312
|
+
- **Nothing detected** → skip the section silently. Do NOT offer a menu or ask "do you want X?".
|
|
313
|
+
- **`--interactive` flag** → override the above and ask confirmation/selection for every section, even when detection found a clear answer.
|
|
314
|
+
|
|
315
|
+
### 4A: repos section
|
|
316
|
+
|
|
317
|
+
**Determine `SATELLITE_CANDIDATES`**: From `SIBLING_REPOS` (Phase 3D), filter to repos with strong satellite signals — repos that have their own CI workflow files, separate `package.json`, or a staging branch distinct from this repo's staging branch. Repos without any of these signals are NOT satellite candidates.
|
|
318
|
+
|
|
319
|
+
**Decision**:
|
|
320
|
+
- `SATELLITE_CANDIDATES` is non-empty AND `INTERACTIVE=false` → log detected candidates but do NOT configure satellites automatically (routing prefix assignments require human intent). Ask once:
|
|
321
|
+
```
|
|
322
|
+
Detected potential satellite repos with separate CI/branches:
|
|
323
|
+
- {repo_name}: {description}
|
|
324
|
+
(...)
|
|
325
|
+
|
|
326
|
+
Do any of these serve as ForgeDock routing satellites? Enter prefix:repo mappings
|
|
327
|
+
(e.g., "mcp:acme-mcp-server,api:acme-api"), or press Enter to skip:
|
|
328
|
+
```
|
|
329
|
+
- `SATELLITE_CANDIDATES` is empty AND `INTERACTIVE=false` → skip this section silently.
|
|
330
|
+
- `INTERACTIVE=true` → present all sibling repos and ask which (if any) are satellites.
|
|
331
|
+
|
|
332
|
+
**Rationale**: Satellite routing prefix assignments require deliberate human intent — there is no safe auto-detection heuristic. Ask only when strong signals exist.
|
|
333
|
+
|
|
334
|
+
### 4B: project_board section
|
|
335
|
+
|
|
336
|
+
**Decision**:
|
|
337
|
+
- Exactly one project board found in 3B → use it. Auto-map field names to IDs. Only ask if field name matching is ambiguous (multiple fields could map to `Status`, etc.).
|
|
338
|
+
- Multiple project boards found → ask which one ForgeDock should use:
|
|
339
|
+
```
|
|
340
|
+
Multiple project boards found under {OWNER}:
|
|
341
|
+
[1] {TITLE_1} (#{NUMBER_1})
|
|
342
|
+
[2] {TITLE_2} (#{NUMBER_2})
|
|
343
|
+
...
|
|
344
|
+
|
|
345
|
+
Which board should ForgeDock use? Enter number, or press Enter to skip:
|
|
346
|
+
```
|
|
347
|
+
- Zero project boards found AND `INTERACTIVE=false` → skip this section silently.
|
|
348
|
+
- `INTERACTIVE=true` → present all boards and ask which one to use.
|
|
349
|
+
|
|
350
|
+
### 4C: services section
|
|
351
|
+
|
|
352
|
+
**Decision** (based on `DETECTED_ANALYTICS` from Phase 2F):
|
|
353
|
+
- `DETECTED_UMAMI=true` → record Umami; ask for the specific `UMAMI_URL` and `UMAMI_WEBSITE_ID` if not found in env/config files.
|
|
354
|
+
- `DETECTED_CLARITY=true` → record Clarity; ask for `CLARITY_PROJECT_ID` if not found in env/config files.
|
|
355
|
+
- `DETECTED_GA4=true` → record GA4; ask for `GA4_PROPERTY_ID` if not found in env/config files.
|
|
356
|
+
- `DETECTED_ANALYTICS_EMPTY=true` AND `INTERACTIVE=false` → skip this section silently.
|
|
357
|
+
- `INTERACTIVE=true` → present the full analytics menu regardless of detection:
|
|
358
|
+
```
|
|
359
|
+
Does your project use any of the following analytics/monitoring services?
|
|
360
|
+
[1] Umami (self-hosted)
|
|
361
|
+
[2] Microsoft Clarity
|
|
362
|
+
[3] Google Analytics 4
|
|
363
|
+
[4] None / Skip this section
|
|
364
|
+
|
|
365
|
+
Enter numbers separated by commas (e.g., "1,3"), or "4" to skip:
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
**API URL for health checks** (services.api_url):
|
|
369
|
+
- `DETECTED_HEALTH_PATH` is set (from Phase 2E) AND a domain is known → compose `API_URL` directly. Do not ask.
|
|
370
|
+
- Nothing detected AND `INTERACTIVE=false` → leave `api_url` empty in the section.
|
|
371
|
+
- `INTERACTIVE=true` → ask:
|
|
372
|
+
```
|
|
373
|
+
Do you have an API URL for health checks? (e.g., https://api.myproject.io)
|
|
374
|
+
Enter URL or press Enter to skip:
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### 4D: review section
|
|
378
|
+
|
|
379
|
+
**Decision**:
|
|
380
|
+
- `TECH_STACK` built in Phase 2A → use it directly as `review.tech_stack`. Do not ask for confirmation.
|
|
381
|
+
- `review.context` → derive from `REPO_ROOT/CLAUDE.md` or README.md (Phase 2B) without prompting. Use the extracted 2–4 sentence description.
|
|
382
|
+
- `INTERACTIVE=true` → show detected value and ask:
|
|
383
|
+
```
|
|
384
|
+
Detected tech stack: {TECH_STACK}
|
|
385
|
+
Is this accurate? Add/edit if needed, or press Enter to accept:
|
|
386
|
+
```
|
|
387
|
+
Then ask:
|
|
388
|
+
```
|
|
389
|
+
Any additional context for code reviewers? (unusual conventions, deploy setup, known pitfalls)
|
|
390
|
+
Press Enter to skip, or describe briefly:
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### 4E: verification section
|
|
394
|
+
|
|
395
|
+
**Decision**:
|
|
396
|
+
- `DETECTED_HEALTH_PATH` is set (from Phase 2E) AND a base domain is known → compose full `HEALTH_ENDPOINT` URL directly. Do not ask.
|
|
397
|
+
- Container name prefixes detected in Phase 2C → use them directly as `internal_service_patterns`. Do not ask for confirmation.
|
|
398
|
+
- Nothing detected for health endpoint AND nothing detected for containers AND `INTERACTIVE=false` → skip this section silently.
|
|
399
|
+
- `INTERACTIVE=true` → show detected values and ask for confirmation:
|
|
400
|
+
```
|
|
401
|
+
Detected potential health endpoint path: {DETECTED_PATH}
|
|
402
|
+
Full health URL (e.g., https://api.myproject.io/health), or press Enter to skip:
|
|
403
|
+
```
|
|
404
|
+
```
|
|
405
|
+
Detected container name prefixes: {PREFIXES}
|
|
406
|
+
Are these correct? Add/edit prefixes, or press Enter to accept:
|
|
407
|
+
```
|
|
328
408
|
|
|
329
|
-
|
|
409
|
+
---
|
|
330
410
|
|
|
411
|
+
## Phase 5: Write forge.yaml
|
|
412
|
+
|
|
413
|
+
### 5A: Preserve required sections
|
|
414
|
+
|
|
415
|
+
Read the current `forge.yaml` content. Extract the complete `project:`, `paths:`, and `branches:` blocks verbatim — they must not change.
|
|
416
|
+
|
|
417
|
+
### 5B: Build optional section YAML
|
|
418
|
+
|
|
419
|
+
For each optional section with detected or confirmed values (from Phase 4):
|
|
420
|
+
|
|
421
|
+
**repos** (if satellites confirmed):
|
|
422
|
+
```yaml
|
|
423
|
+
repos:
|
|
424
|
+
default:
|
|
425
|
+
repo: "{OWNER}/{REPO}"
|
|
426
|
+
staging_branch: "{branches.staging}"
|
|
427
|
+
satellites:
|
|
428
|
+
- prefix: "{PREFIX}"
|
|
429
|
+
repo: "{OWNER}/{SATELLITE_REPO}"
|
|
430
|
+
staging_branch: "main"
|
|
431
|
+
local_path: "{REPO_ROOT}/../{SATELLITE_REPO}"
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
**project_board** (if board and field IDs found):
|
|
435
|
+
```yaml
|
|
436
|
+
project_board:
|
|
437
|
+
owner: "{OWNER}"
|
|
438
|
+
project_number: {PROJECT_NUMBER}
|
|
439
|
+
project_id: "{PROJECT_ID}" # Must be a PVT_... node ID — obtain via: gh project list --owner <owner> --format json | jq '.projects[] | .id'
|
|
440
|
+
field_ids:
|
|
441
|
+
status: "{STATUS_FIELD_ID}"
|
|
442
|
+
lane: "{LANE_FIELD_ID}"
|
|
443
|
+
component: "{COMPONENT_FIELD_ID}"
|
|
444
|
+
priority: "{PRIORITY_FIELD_ID}"
|
|
445
|
+
workflow: "{WORKFLOW_FIELD_ID}"
|
|
446
|
+
option_ids:
|
|
447
|
+
status:
|
|
448
|
+
todo: "{TODO_OPTION_ID}"
|
|
449
|
+
in_progress: "{IN_PROGRESS_OPTION_ID}"
|
|
450
|
+
done: "{DONE_OPTION_ID}"
|
|
451
|
+
lane:
|
|
452
|
+
fast: "{FAST_OPTION_ID}"
|
|
453
|
+
feature: "{FEATURE_OPTION_ID}"
|
|
454
|
+
priority:
|
|
455
|
+
p0: "{P0_OPTION_ID}"
|
|
456
|
+
p1: "{P1_OPTION_ID}"
|
|
457
|
+
p2: "{P2_OPTION_ID}"
|
|
458
|
+
p3: "{P3_OPTION_ID}"
|
|
459
|
+
workflow:
|
|
460
|
+
investigating: "{INVESTIGATING_OPTION_ID}"
|
|
461
|
+
building: "{BUILDING_OPTION_ID}"
|
|
462
|
+
in_review: "{IN_REVIEW_OPTION_ID}"
|
|
463
|
+
merged: "{MERGED_OPTION_ID}"
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
**services** (if analytics/monitoring confirmed):
|
|
467
|
+
```yaml
|
|
468
|
+
services:
|
|
469
|
+
domain: "{ROOT_DOMAIN}"
|
|
470
|
+
api_url: "{API_URL}"
|
|
471
|
+
analytics:
|
|
472
|
+
# Only include subsections detected (or selected in --interactive mode)
|
|
473
|
+
umami: # if selected
|
|
474
|
+
url: "{UMAMI_URL}"
|
|
475
|
+
website_id: "{UMAMI_WEBSITE_ID}"
|
|
476
|
+
clarity: # if selected
|
|
477
|
+
project_id: "{CLARITY_PROJECT_ID}"
|
|
478
|
+
ga4: # if selected
|
|
479
|
+
property_id: "{GA4_PROPERTY_ID}"
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
**review** (if tech stack or context provided):
|
|
483
|
+
```yaml
|
|
484
|
+
review:
|
|
485
|
+
tech_stack: "{TECH_STACK}"
|
|
486
|
+
context: |
|
|
487
|
+
{PROJECT_CONTEXT}
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
**verification** (if health endpoint or containers found):
|
|
491
|
+
```yaml
|
|
492
|
+
verification:
|
|
493
|
+
health_endpoint: "{HEALTH_ENDPOINT}"
|
|
494
|
+
health_patterns:
|
|
495
|
+
- '"status": "ok"'
|
|
496
|
+
internal_service_patterns:
|
|
497
|
+
- "{SERVICE_PREFIX}"
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### 5C: Section merge strategy
|
|
501
|
+
|
|
502
|
+
For each optional section that is ALREADY ACTIVE in forge.yaml (detected in Phase 1C):
|
|
503
|
+
|
|
504
|
+
**Default behavior (`PRESERVE=false`)**:
|
|
505
|
+
- Overwrite the section with newly detected values.
|
|
506
|
+
- Record the old values before overwriting so the Phase 5D summary can show "was X → now Y".
|
|
507
|
+
- No prompt. This is the expected behavior when re-running `/forgedock-init` to refresh config.
|
|
508
|
+
|
|
509
|
+
**`--preserve` flag (`PRESERVE=true`)**:
|
|
510
|
+
- Keep the existing section content unchanged. Skip generating new content for this section.
|
|
511
|
+
- Phase 5D summary will show: `(preserved — existing values kept)`
|
|
512
|
+
|
|
513
|
+
**`--interactive` flag (`INTERACTIVE=true`)**:
|
|
514
|
+
- Show the detected new value alongside the existing value, then ask:
|
|
515
|
+
```
|
|
516
|
+
{section}: section already configured.
|
|
517
|
+
Current value: {EXISTING_VALUE}
|
|
518
|
+
Detected value: {DETECTED_VALUE}
|
|
519
|
+
Overwrite? [Y/n]
|
|
520
|
+
```
|
|
521
|
+
- Default is Y (overwrite). If user says N, keep existing content.
|
|
522
|
+
|
|
523
|
+
**`--section <name>` targeted mode**: Always overwrite the targeted section regardless of `PRESERVE` flag — the explicit targeting signals user intent to update that section.
|
|
524
|
+
|
|
525
|
+
### 5D: Write the file
|
|
526
|
+
|
|
527
|
+
Backup the existing file:
|
|
331
528
|
```bash
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
--include="*.py" --include="*.ts" --include="*.tsx" --include="*.go" --include="*.rs" --include="*.rb" \
|
|
335
|
-
-l 2>/dev/null | head -10
|
|
336
|
-
|
|
337
|
-
# Look for healthcheck patterns in compose/config files
|
|
338
|
-
grep -rn "healthcheck\|HEALTH_URL\|health_endpoint\|/health" \
|
|
339
|
-
"$REPO_ROOT" --include="*.yml" --include="*.yaml" --include="*.env*" \
|
|
340
|
-
-l 2>/dev/null | head -5
|
|
529
|
+
cp "$FORGE_YAML" "${FORGE_YAML}.bak"
|
|
530
|
+
echo "Backed up: forge.yaml → forge.yaml.bak"
|
|
341
531
|
```
|
|
342
532
|
|
|
343
|
-
|
|
344
|
-
- If a route like `GET /health` or `GET /ping` is found → use that path
|
|
345
|
-
- If a docker-compose healthcheck URL is found → extract the host:port+path
|
|
346
|
-
- If docker-compose has a `services.api` block → infer `http://localhost:<port>/health`
|
|
533
|
+
Write the complete new file: required sections (preserved verbatim) + active optional sections (newly generated) + inactive optional sections (left as commented-out blocks matching forge.yaml.example structure).
|
|
347
534
|
|
|
348
|
-
|
|
349
|
-
```json
|
|
350
|
-
{ "value": "https://api.<detected-or-inferred-host>/<path>", "confidence": "medium", "source": "<file where found>", "why": "Health route found at <file>:<line>" }
|
|
351
|
-
```
|
|
535
|
+
Print a summary of what was written. For sections that were overwritten (existed before AND PRESERVE=false), show the old → new values for any field that changed. For sections that were newly added (did not exist before), show just the new values.
|
|
352
536
|
|
|
353
|
-
If no endpoint found:
|
|
354
|
-
```json
|
|
355
|
-
{ "value": "", "confidence": "low", "source": "codebase scan", "why": "No health route detected; set this to the URL of your API's health endpoint" }
|
|
356
537
|
```
|
|
538
|
+
forge.yaml updated:
|
|
357
539
|
|
|
358
|
-
|
|
540
|
+
Required sections (preserved):
|
|
541
|
+
✓ project
|
|
542
|
+
✓ paths
|
|
543
|
+
✓ branches
|
|
359
544
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
545
|
+
Optional sections (filled):
|
|
546
|
+
✓ repos — 2 satellite(s) configured (new)
|
|
547
|
+
✓ project_board — field IDs from project "Acme Board" (#1)
|
|
548
|
+
was: project_number: 2 → now: project_number: 1
|
|
549
|
+
✓ review — tech stack updated
|
|
550
|
+
was: "Node.js" → now: "Node.js 20, TypeScript, PostgreSQL"
|
|
551
|
+
✓ verification — health endpoint configured (new)
|
|
552
|
+
✗ services — skipped (nothing detected)
|
|
366
553
|
|
|
367
|
-
|
|
554
|
+
Optional sections (skipped — still commented out):
|
|
555
|
+
- services
|
|
368
556
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
{ "value": "<prefix>", "confidence": "high", "source": "docker-compose.yml container_name entries", "why": "Containers named: <list>" }
|
|
557
|
+
Optional sections (preserved — --preserve flag):
|
|
558
|
+
- {section} — kept existing values unchanged
|
|
372
559
|
```
|
|
373
560
|
|
|
374
|
-
If
|
|
375
|
-
```json
|
|
376
|
-
{ "value": "", "confidence": "low", "source": "codebase scan", "why": "No docker-compose container_name entries found; add your service name prefix here (e.g., 'acme-')" }
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
### 4C: Build verification enrichment
|
|
380
|
-
|
|
381
|
-
```json
|
|
382
|
-
"verification": {
|
|
383
|
-
"health_endpoint": { "value": "...", "confidence": "...", "source": "...", "why": "..." },
|
|
384
|
-
"health_patterns": [
|
|
385
|
-
{ "value": "\"status\": \"ok\"", "confidence": "low", "source": "default", "why": "Standard ForgeDock health pattern; update if your API uses a different response shape" }
|
|
386
|
-
],
|
|
387
|
-
"internal_service_patterns": [
|
|
388
|
-
{ "value": "<prefix>", "confidence": "...", "source": "...", "why": "..." }
|
|
389
|
-
]
|
|
390
|
-
}
|
|
391
|
-
```
|
|
561
|
+
If nothing changed for an overwritten section (detected values match existing values), show `(no change)` instead of a diff.
|
|
392
562
|
|
|
393
563
|
---
|
|
394
564
|
|
|
395
|
-
## Phase
|
|
396
|
-
|
|
397
|
-
### 5A: Merge enrichments into the draft
|
|
398
|
-
|
|
399
|
-
Start from the incoming `DRAFT`. Merge the enriched sections:
|
|
400
|
-
- `DRAFT.project_board` = Phase 1 output (or low-confidence placeholder)
|
|
401
|
-
- `DRAFT.repos` = Phase 2 output (or low-confidence placeholder)
|
|
402
|
-
- `DRAFT.review` = Phase 3 output
|
|
403
|
-
- `DRAFT.verification` = Phase 4 output
|
|
565
|
+
## Phase 6: Validation
|
|
404
566
|
|
|
405
|
-
|
|
567
|
+
### 6A: Repo access
|
|
406
568
|
|
|
407
|
-
|
|
569
|
+
```bash
|
|
570
|
+
gh repo view "${OWNER}/${REPO}" --json name,url 2>/dev/null \
|
|
571
|
+
|| echo "WARNING: Cannot access ${OWNER}/${REPO} — check project.owner and project.repo"
|
|
572
|
+
```
|
|
408
573
|
|
|
409
|
-
|
|
574
|
+
### 6B: Path existence
|
|
410
575
|
|
|
411
|
-
```
|
|
412
|
-
{
|
|
413
|
-
"
|
|
414
|
-
"
|
|
415
|
-
"enriched_at": "<ISO 8601 timestamp>",
|
|
416
|
-
"enrichment_notes": ["<any skipped phases and reasons>"]
|
|
417
|
-
}
|
|
576
|
+
```bash
|
|
577
|
+
[ -d "$(grep 'root:' "$FORGE_YAML" | head -1 | awk '{print $2}' | tr -d '"')" ] \
|
|
578
|
+
&& echo "✓ paths.root exists" \
|
|
579
|
+
|| echo "WARNING: paths.root does not exist on this machine"
|
|
418
580
|
```
|
|
419
581
|
|
|
420
|
-
###
|
|
582
|
+
### 6C: Project board ID resolution
|
|
421
583
|
|
|
422
|
-
|
|
584
|
+
`project_board.project_id` must be a GitHub Projects v2 node ID with the `PVT_` prefix (e.g. `PVT_kwHOxxxxxxxxxxxxxxxx`). This value is returned by `gh project list --owner <owner> --format json | jq '.projects[] | .id'`. A manually-edited `forge.yaml` with an incorrect value (e.g. a project number instead of a node ID) will produce a confusing GraphQL error — the format check below catches this before attempting the API call.
|
|
423
585
|
|
|
424
|
-
```
|
|
425
|
-
|
|
586
|
+
```bash
|
|
587
|
+
# Only run if project_board section was written
|
|
588
|
+
if grep -q "^project_board:" "$FORGE_YAML"; then
|
|
589
|
+
PROJECT_ID=$(grep "project_id:" "$FORGE_YAML" | head -1 | awk '{print $2}' | tr -d '"')
|
|
590
|
+
# Validate PVT_ prefix before GraphQL call — any other format will produce a confusing API error
|
|
591
|
+
if [[ "$PROJECT_ID" != PVT_* ]]; then
|
|
592
|
+
echo "ERROR: project_board.project_id must start with 'PVT_' (got: '$PROJECT_ID')"
|
|
593
|
+
echo " Expected format: PVT_kwHOxxxxxxxxxxxxxxxx"
|
|
594
|
+
echo " Obtain the correct value with: gh project list --owner ${OWNER} --format json | jq '.projects[] | .id'"
|
|
595
|
+
else
|
|
596
|
+
gh api graphql -f query='query { node(id: "'"$PROJECT_ID"'") { id __typename } }' 2>/dev/null \
|
|
597
|
+
| jq -e '.data.node.id' > /dev/null \
|
|
598
|
+
&& echo "✓ project_board.project_id resolves" \
|
|
599
|
+
|| echo "WARNING: project_board.project_id may be invalid — verify with: gh project list --owner ${OWNER}"
|
|
600
|
+
fi
|
|
601
|
+
fi
|
|
426
602
|
```
|
|
427
603
|
|
|
428
|
-
|
|
429
|
-
- Single JSON object, pretty-printed (2-space indent)
|
|
430
|
-
- All sections present (project, paths, branches, project_board, repos, review, verification, meta)
|
|
431
|
-
- Every leaf is `{ value, confidence, source, why }` — no raw strings
|
|
432
|
-
- No trailing prose after the JSON blob
|
|
604
|
+
### 6D: Satellite repo access
|
|
433
605
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
606
|
+
```bash
|
|
607
|
+
# Only run if repos.satellites section was written
|
|
608
|
+
if grep -q "^repos:" "$FORGE_YAML"; then
|
|
609
|
+
# Extract satellite repo names and verify each is accessible
|
|
610
|
+
grep -A 20 "^repos:" "$FORGE_YAML" | grep "repo:" | tail -n +2 | while read -r line; do
|
|
611
|
+
SAT_REPO=$(echo "$line" | awk '{print $2}' | tr -d '"')
|
|
612
|
+
gh repo view "$SAT_REPO" --json name 2>/dev/null \
|
|
613
|
+
&& echo "✓ satellite repo ${SAT_REPO} accessible" \
|
|
614
|
+
|| echo "WARNING: satellite repo ${SAT_REPO} not accessible — check prefix and repo name"
|
|
615
|
+
done
|
|
616
|
+
fi
|
|
617
|
+
```
|
|
437
618
|
|
|
438
|
-
|
|
619
|
+
### 6E: Final report
|
|
439
620
|
|
|
440
621
|
```
|
|
441
|
-
|
|
442
|
-
## Enrichment Summary
|
|
622
|
+
forge.yaml is ready.
|
|
443
623
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
repos — <N satellite candidates found | skipped>
|
|
447
|
-
review — tech_stack=<value>, context from <source>
|
|
448
|
-
verification — health_endpoint=<value | not found>, <N> container patterns
|
|
624
|
+
Validation results:
|
|
625
|
+
{validation output from 6A–6D}
|
|
449
626
|
|
|
450
|
-
|
|
451
|
-
|
|
627
|
+
Next steps:
|
|
628
|
+
1. If forge.yaml contains sensitive paths (credentials file, local paths):
|
|
629
|
+
echo "forge.yaml" >> .gitignore
|
|
630
|
+
2. Test the pipeline:
|
|
631
|
+
/work-on next
|
|
632
|
+
3. If any WARNING appeared above, edit forge.yaml to fix the flagged values.
|
|
633
|
+
4. Backup file: forge.yaml.bak (delete when satisfied)
|
|
452
634
|
|
|
453
|
-
|
|
454
|
-
<list of field paths with low confidence and their why>
|
|
635
|
+
ForgeDock commands now use your project's config from forge.yaml.
|
|
455
636
|
```
|