forgedock 1.0.8 → 1.0.10

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.
@@ -1,534 +1,455 @@
1
1
  ---
2
- description: AI-powered forge.yaml config generator — scans codebase, queries GitHub, fills all optional sections interactively
3
- argument-hint: [--force | --section <name>]
2
+ description: init-enrich skill backend — consumes a ConfigDraft, deeply infers hard sections from codebase + GitHub, returns enriched ConfigDraft with raised confidences
3
+ argument-hint: <ConfigDraft JSON>
4
4
  ---
5
5
 
6
- # /forgedock-init — AI-Powered Config Generator
6
+ # /forgedock-init — init-enrich Skill Backend
7
7
 
8
8
  **Input**: $ARGUMENTS
9
9
 
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, ask targeted clarifying questions, and produce a complete `forge.yaml` with every applicable optional section filled.
10
+ You are the **init-enrich skill backend** for ForgeDock's intelligent onboarding pipeline. You consume a `ConfigDraft` JSON object produced by `init-detect`, deeply infer the hard sections (`project_board`, `repos.satellites`, `review`, `verification`) by scanning the codebase and querying GitHub, and return an enriched `ConfigDraft` with the same leaf shape and raised confidences.
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.
11
13
 
12
14
  **Agent model policy**: Default `model: "sonnet"`. Fallback: `model: "opus"` if Sonnet is rate-limited.
13
15
  **NEVER use plan mode (EnterPlanMode).**
14
16
 
15
17
  ---
16
18
 
17
- ## Argument Parsing
19
+ ## ConfigDraft Contract
18
20
 
19
- | Flag | Effect |
20
- |------|--------|
21
- | (none) | Fill all optional sections not yet configured |
22
- | `--force` | Overwrite ALL optional sections without asking |
23
- | `--section <name>` | Fill only one section: `repos`, `project_board`, `services`, `review`, or `verification` |
21
+ Every leaf in a `ConfigDraft` has the shape:
24
22
 
25
- Parse `$ARGUMENTS` and set:
26
- ```
27
- FORCE = true if --force present
28
- TARGET_SECTION = value from --section <name>, or "all"
23
+ ```json
24
+ { "value": "<string>", "confidence": "high|medium|low", "source": "<string>", "why": "<string>" }
29
25
  ```
30
26
 
31
- If `--section` was provided, validate the value immediately:
27
+ Confidence levels:
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
32
31
 
33
- ```bash
34
- ALLOWED_SECTIONS="repos project_board services review verification"
35
- if [ "$TARGET_SECTION" != "all" ]; then
36
- VALID=false
37
- for s in $ALLOWED_SECTIONS; do
38
- [ "$TARGET_SECTION" = "$s" ] && VALID=true && break
39
- done
40
- if [ "$VALID" = "false" ]; then
41
- echo "Error: '$TARGET_SECTION' is not a valid section name."
42
- echo ""
43
- echo "Valid sections: repos, project_board, services, review, verification"
44
- echo ""
45
- echo "Usage: /forgedock-init --section <name>"
46
- exit 1
47
- fi
48
- fi
49
- ```
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.
50
33
 
51
34
  ---
52
35
 
53
- ## Phase 1: Load forge.yaml
36
+ ## Phase 0: Parse Input and Extract Identity
54
37
 
55
- ### 1A: Locate and read
38
+ ### 0A: Parse ConfigDraft from $ARGUMENTS
56
39
 
57
- ```bash
58
- FORGE_YAML="${FORGE_CONFIG:-$(git rev-parse --show-toplevel 2>/dev/null)/forge.yaml}"
59
- if [ ! -f "$FORGE_YAML" ]; then
60
- echo "forge.yaml not found in current directory."
61
- echo ""
62
- echo "Run first: npx forgedock init"
63
- echo ""
64
- echo "This generates the required skeleton (project, paths, branches)."
65
- echo "Then re-run /forgedock-init to complete the optional sections."
66
- exit 1
67
- fi
68
- cat "$FORGE_YAML"
40
+ `$ARGUMENTS` contains a ConfigDraft JSON string. Parse it:
41
+
42
+ ```
43
+ DRAFT = JSON.parse($ARGUMENTS)
44
+ ```
45
+
46
+ If `$ARGUMENTS` is empty or not valid JSON, output an error and exit:
47
+
48
+ ```
49
+ Error: /forgedock-init expects a ConfigDraft JSON string as its argument.
50
+
51
+ Usage:
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)))"
54
+
55
+ Pass the output as the argument to /forgedock-init.
69
56
  ```
70
57
 
71
- ### 1B: Extract required section values
58
+ ### 0B: Extract identity fields
72
59
 
73
- Parse these from the existing file — they are needed for all subsequent queries:
60
+ Extract these from the incoming draft for use in all GitHub queries:
74
61
 
75
62
  ```
76
- OWNER = project.owner
77
- REPO = project.repo
78
- REPO_ROOT = paths.root
63
+ OWNER = DRAFT.project.owner.value # GitHub org or username
64
+ REPO = DRAFT.project.repo.value # Repository name
65
+ REPO_ROOT = DRAFT.paths.root.value # Absolute path to repo root
79
66
  ```
80
67
 
81
- After extracting these values, validate that OWNER and REPO are non-empty and do not contain placeholder values before proceeding:
68
+ Validate that OWNER and REPO are non-empty and not placeholder values before proceeding with any GitHub queries:
82
69
 
83
- ```bash
84
- if [ -z "$OWNER" ] || [ "$OWNER" = "your-github-org" ]; then
85
- echo "Error: project.owner is not set in forge.yaml."
86
- echo ""
87
- echo "Open forge.yaml and fill in the 'project:' section:"
88
- echo " project:"
89
- echo " owner: \"your-actual-github-org-or-username\""
90
- echo ""
91
- echo "Then re-run /forgedock-init."
92
- exit 1
93
- fi
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"
94
74
 
95
- if [ -z "$REPO" ] || [ "$REPO" = "your-repo-name" ]; then
96
- echo "Error: project.repo is not set in forge.yaml."
97
- echo ""
98
- echo "Open forge.yaml and fill in the 'project:' section:"
99
- echo " project:"
100
- echo " repo: \"your-actual-repo-name\""
101
- echo ""
102
- echo "Then re-run /forgedock-init."
103
- exit 1
104
- fi
75
+ if REPO is empty or equals "your-repo-name":
76
+ set REPO = null
77
+ note: "project.repo is a placeholder — satellite enrichment skipped"
105
78
  ```
106
79
 
107
- ### 1C: Detect already-configured optional sections
80
+ These are non-blocking — an incomplete draft still gets codebase enrichment (review, verification).
108
81
 
109
- 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.
82
+ ### 0C: Check GitHub auth
110
83
 
111
84
  ```bash
112
- # Check which optional sections are already active (uncommented)
113
- grep -q "^repos:" "$FORGE_YAML" && HAS_REPOS=true || HAS_REPOS=false
114
- grep -q "^project_board:" "$FORGE_YAML" && HAS_PROJECT_BOARD=true || HAS_PROJECT_BOARD=false
115
- grep -q "^services:" "$FORGE_YAML" && HAS_SERVICES=true || HAS_SERVICES=false
116
- grep -q "^review:" "$FORGE_YAML" && HAS_REVIEW=true || HAS_REVIEW=false
117
- grep -q "^verification:" "$FORGE_YAML" && HAS_VERIFICATION=true || HAS_VERIFICATION=false
85
+ gh auth status 2>&1
118
86
  ```
119
87
 
120
- If `FORCE=false` and a section is already active, skip it (unless `--section` targets it explicitly). Tell the user which sections were skipped and why.
88
+ Set `GH_AUTHED = true` if the output does NOT contain "not logged in" or "You are not logged into". Set `GH_AUTHED = false` otherwise.
89
+
90
+ If `GH_AUTHED = false` or `OWNER = null`: GitHub-dependent enrichment phases (1, 2) will be skipped; their fields will be set to `low` confidence with a TODO. Codebase-only phases (3, 4) still run.
121
91
 
122
92
  ---
123
93
 
124
- ## Phase 2: Codebase Scan
94
+ ## Phase 1: Enrich project_board
125
95
 
126
- Run these scans unconditionally — they populate defaults that reduce the number of clarifying questions needed.
96
+ **Skip if**: `GH_AUTHED = false` or `OWNER = null`.
127
97
 
128
- ### 2A: Tech stack detection
98
+ ### 1A: Discover project boards
129
99
 
130
100
  ```bash
131
- # Node.js / TypeScript
132
- [ -f "$REPO_ROOT/package.json" ] && \
133
- cat "$REPO_ROOT/package.json" | grep -E '"name"|"dependencies"|"devDependencies"' | head -30
101
+ gh project list --owner "$OWNER" --format json 2>/dev/null \
102
+ | jq '[.projects[] | {number: .number, title: .title, id: .id}]'
103
+ ```
134
104
 
135
- # Python
136
- [ -f "$REPO_ROOT/pyproject.toml" ] && grep -E "^\[project\]|^name|^requires-python|^dependencies" "$REPO_ROOT/pyproject.toml" | head -20
137
- [ -f "$REPO_ROOT/requirements.txt" ] && head -20 "$REPO_ROOT/requirements.txt"
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).
138
106
 
139
- # Rust
140
- [ -f "$REPO_ROOT/Cargo.toml" ] && grep -E "^\[package\]|^name|^edition|^\[dependencies\]" "$REPO_ROOT/Cargo.toml" | head -20
107
+ If one project is found: select it automatically (high confidence — no ambiguity).
108
+ If multiple projects are found: select the first one (medium confidence — note that the first result may not be the right project board).
141
109
 
142
- # Ruby
143
- [ -f "$REPO_ROOT/Gemfile" ] && head -15 "$REPO_ROOT/Gemfile"
110
+ Record:
111
+ ```
112
+ PROJECT_NUMBER = first (or only) project number
113
+ PROJECT_TITLE = project title
114
+ PROJECT_ID = project id (the PVT_... node ID)
115
+ ```
144
116
 
145
- # Go
146
- [ -f "$REPO_ROOT/go.mod" ] && head -10 "$REPO_ROOT/go.mod"
117
+ ### 1B: Fetch field definitions
118
+
119
+ ```bash
120
+ gh project field-list "$PROJECT_NUMBER" --owner "$OWNER" --format json 2>/dev/null \
121
+ | jq '[.fields[] | {name: .name, id: .id, type: .type, options: (.options // [])}]'
147
122
  ```
148
123
 
149
- Build `TECH_STACK` description from findings (e.g., "Next.js 15, FastAPI, PostgreSQL").
124
+ If the query fails: skip Phase 1, emit low-confidence placeholder.
150
125
 
151
- ### 2B: Project context
126
+ ### 1C: Map fields to ForgeDock slots
152
127
 
153
- ```bash
154
- # CLAUDE.md — primary context source
155
- [ -f "$REPO_ROOT/CLAUDE.md" ] && cat "$REPO_ROOT/CLAUDE.md"
128
+ For each of the five ForgeDock field slots, search the field list case-insensitively:
156
129
 
157
- # README.md — fallback
158
- [ -f "$REPO_ROOT/README.md" ] && head -60 "$REPO_ROOT/README.md"
159
- ```
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" |
160
137
 
161
- Extract a 2–4 sentence project description for `review.context`.
138
+ For each slot:
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`
162
141
 
163
- ### 2C: Docker / service names
142
+ ### 1D: Map option IDs
164
143
 
165
- ```bash
166
- # Container name prefixes for verification section
167
- ls "$REPO_ROOT"/docker-compose*.yml 2>/dev/null
168
- for f in "$REPO_ROOT"/docker-compose*.yml; do
169
- [ -f "$f" ] || continue
170
- grep -E "^\s+container_name:|^\s+image:" "$f" | head -20
171
- done
172
- ```
144
+ For each single-select field that was successfully mapped, extract its option IDs.
173
145
 
174
- Extract container name prefixes (e.g., `acme-`, `my-app-`) for `verification.internal_service_patterns` and `verification.services`.
146
+ For each of the required option slots, match case-insensitively:
175
147
 
176
- ### 2D: Branch topology
148
+ **status options**: "Todo" / "To Do" → `todo`; "In Progress" / "In-Progress" → `in_progress`; "Done" / "Complete" → `done`
177
149
 
178
- ```bash
179
- # Staging branch detection
180
- git -C "$REPO_ROOT" branch -r | grep -E "staging|develop|dev" | head -5
150
+ **lane options**: "Fast" / "Fast Lane" → `fast`; "Feature" / "Feature Lane" → `feature`; "Sync" → `sync`
181
151
 
182
- # CI/CD target branches
183
- grep -rh "branches:" "$REPO_ROOT/.github/workflows/"*.yml 2>/dev/null | head -20
184
- ```
152
+ **priority options**: "P0" / "Critical" → `p0`; "P1" / "High" → `p1`; "P2" / "Medium" → `p2`; "P3" / "Low" → `p3`
185
153
 
186
- Use to confirm or suggest `branches.staging`.
154
+ **workflow options**: "Investigating" → `investigating`; "Building" → `building`; "In Review" / "In-Review" → `in_review`; "Merged" / "Complete" → `merged`
187
155
 
188
- ### 2E: API health endpoint hints
156
+ For each option:
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`
189
159
 
190
- ```bash
191
- # Look for health route definitions
192
- grep -rn "health\|/ping\|/status" "$REPO_ROOT" --include="*.py" --include="*.ts" --include="*.go" -l 2>/dev/null | head -10
193
- grep -rn "healthcheck\|health_endpoint\|HEALTH_URL" "$REPO_ROOT" --include="*.yml" --include="*.yaml" -l 2>/dev/null | head -5
160
+ ### 1E: Build project_board enrichment
161
+
162
+ Assemble the enriched `project_board` section of the draft. If Phase 1 was skipped entirely:
163
+
164
+ ```json
165
+ "project_board": {
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
+ }
194
172
  ```
195
173
 
174
+ **Important**: `project_board.project_id` must be a GitHub Projects v2 node ID with the `PVT_` prefix (e.g. `PVT_kwHOxxxxxxxxxxxxxxxx`). When emitting this field at high confidence, always include a note in `why` reminding the caller of this format requirement.
175
+
196
176
  ---
197
177
 
198
- ## Phase 3: GitHub Queries
178
+ ## Phase 2: Enrich repos.satellites
199
179
 
200
- Run these to gather live IDs. Failures are non-blocking — skip the section if the query fails.
180
+ **Skip if**: `GH_AUTHED = false` or `OWNER = null` or `REPO = null`.
201
181
 
202
- ### 3A: Check GitHub auth
182
+ ### 2A: List sibling repos
203
183
 
204
184
  ```bash
205
- gh auth status 2>&1
185
+ gh repo list "$OWNER" --limit 50 --json name,description,primaryLanguage 2>/dev/null \
186
+ | jq '[.[] | select(.name != "'"$REPO"'") | {name: .name, description: (.description // ""), language: (.primaryLanguage.name // "")}]'
206
187
  ```
207
188
 
208
- If not authenticated, skip all GitHub queries and note which sections will need manual completion.
189
+ If the query fails or returns empty: skip Phase 2, emit low-confidence placeholder.
209
190
 
210
- ### 3B: Project board discovery
191
+ ### 2B: Infer satellite candidates
211
192
 
212
- ```bash
213
- # List projects owned by this org/user
214
- gh project list --owner "$OWNER" --format json 2>/dev/null \
215
- | jq '.projects[] | {number: .number, title: .title, id: .id}'
216
- ```
193
+ For each sibling repo, apply heuristics to determine if it looks like a ForgeDock satellite (i.e., a repo the user might want to route issues to with a short prefix):
217
194
 
218
- If projects found: ask the user which one ForgeDock should use (or `none`). Record:
219
- - `PROJECT_NUMBER`
220
- - `PROJECT_ID` (the `PVT_...` node ID)
195
+ **Include as a satellite candidate if any of these match**:
196
+ - Name ends with `-sdk`, `-api`, `-mcp`, `-cli`, `-server`, `-worker`, `-plugin`
197
+ - Name contains `sdk`, `client`, `plugin`, `extension`, `lib`
198
+ - Description mentions "companion", "client for", "SDK for", "plugin for", "server for"
221
199
 
222
- ### 3C: Project board field IDs
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)
223
203
 
224
- ```bash
225
- # Only run if PROJECT_NUMBER is set
226
- gh project field-list "$PROJECT_NUMBER" --owner "$OWNER" --format json 2>/dev/null \
227
- | jq '.fields[] | {name: .name, id: .id, type: .type}'
228
- ```
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
229
208
 
230
- Map field names to their IDs:
231
- - Look for fields named `Status`, `Lane`, `Component`, `Priority`, `Workflow` (case-insensitive)
232
- - Record their `PVTSSF_...` IDs
209
+ ### 2C: Build repos enrichment
233
210
 
234
- ```bash
235
- # Get option IDs for single-select fields
236
- gh project field-list "$PROJECT_NUMBER" --owner "$OWNER" --format json 2>/dev/null \
237
- | jq '.fields[] | select(.type == "SINGLE_SELECT") | {name: .name, options: [.options[] | {name: .name, id: .id}]}'
238
- ```
211
+ For each satellite candidate:
239
212
 
240
- 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).
213
+ ```json
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
+ }
219
+ ```
241
220
 
242
- ### 3D: Satellite repo discovery
221
+ If no candidates found:
243
222
 
244
- ```bash
245
- # List all repos under this owner
246
- gh repo list "$OWNER" --limit 50 --json name,description,url 2>/dev/null \
247
- | jq '.[] | select(.name != "'"$REPO"'") | {name: .name, description: .description}'
223
+ ```json
224
+ "repos": {
225
+ "satellites": [],
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
+ }
248
228
  ```
249
229
 
250
- Present the list and ask: "Do any of these serve as satellite repos that you'd route issues to with a prefix (e.g., `mcp:5`)?"
251
-
252
230
  ---
253
231
 
254
- ## Phase 4: Interactive Clarification
232
+ ## Phase 3: Enrich review
255
233
 
256
- Ask only about items NOT already determined by Phases 2–3. Keep questions grouped by section. Use `read` or present multiple-choice.
234
+ **Always runs** — does not require GitHub auth.
257
235
 
258
- ### 4A: repos section
236
+ ### 3A: Detect tech stack
259
237
 
260
- Only ask if satellite repos were found in 3D:
238
+ For each supported ecosystem, check for its manifest file under `$REPO_ROOT`:
261
239
 
240
+ **Node.js / TypeScript**:
241
+ ```bash
242
+ if [ -f "$REPO_ROOT/package.json" ]; then
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
262
251
  ```
263
- The following repos were found under {OWNER}:
264
- - {repo_name}: {description}
265
- (...)
266
252
 
267
- Do any of these serve as satellite repos for ForgeDock routing?
268
- If yes, enter a prefix:repo mapping (e.g., "mcp:acme-mcp-server"), or "none":
253
+ **Python**:
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
269
258
  ```
270
259
 
271
- ### 4B: project_board section
260
+ **Rust**:
261
+ ```bash
262
+ [ -f "$REPO_ROOT/Cargo.toml" ] && grep -E "^name|axum|actix|rocket|tokio|serde|sqlx" "$REPO_ROOT/Cargo.toml" | head -10
263
+ ```
272
264
 
273
- If project board found in 3B but field mapping is ambiguous:
265
+ **Go**:
266
+ ```bash
267
+ [ -f "$REPO_ROOT/go.mod" ] && head -10 "$REPO_ROOT/go.mod"
268
+ ```
274
269
 
270
+ **Ruby**:
271
+ ```bash
272
+ [ -f "$REPO_ROOT/Gemfile" ] && grep -E "rails|sinatra|roda|hanami|sidekiq" "$REPO_ROOT/Gemfile" | head -5
275
273
  ```
276
- Found project board: "{TITLE}" (#{PROJECT_NUMBER})
277
274
 
278
- Field mapping (press Enter to accept detected name, or type correct field name):
279
- Status field → detected: "{DETECTED}" (ID: {ID})
280
- Lane field → detected: "{DETECTED}" (ID: {ID})
281
- ...
275
+ **Docker / infrastructure**:
276
+ ```bash
277
+ ls "$REPO_ROOT"/docker-compose*.yml 2>/dev/null | head -5
278
+ [ -f "$REPO_ROOT/Dockerfile" ] && head -5 "$REPO_ROOT/Dockerfile"
282
279
  ```
283
280
 
284
- ### 4C: services section
281
+ Build a concise `TECH_STACK` string from the detected components, e.g. "Next.js 15, FastAPI, PostgreSQL 16, Docker". If multiple ecosystems are detected, list all. If nothing is detected, set `TECH_STACK = ""`.
285
282
 
286
- ```
287
- Does your project use any of the following analytics/monitoring services?
288
- [1] Umami (self-hosted)
289
- [2] Microsoft Clarity
290
- [3] Google Analytics 4
291
- [4] None / Skip this section
283
+ **Confidence**:
284
+ - One or more manifest files found → `medium` (detected from deps but not verified running)
285
+ - Nothing detected → `low`
292
286
 
293
- Enter numbers separated by commas (e.g., "1,3"), or "4" to skip:
294
- ```
287
+ ### 3B: Extract project context
295
288
 
296
- If any selected, ask for the specific IDs/URLs required by each.
289
+ ```bash
290
+ # Primary: CLAUDE.md
291
+ if [ -f "$REPO_ROOT/CLAUDE.md" ]; then
292
+ cat "$REPO_ROOT/CLAUDE.md"
293
+ fi
297
294
 
298
- ```
299
- Do you have an API URL for health checks? (e.g., https://api.myproject.io)
300
- Enter URL or press Enter to skip:
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
301
299
  ```
302
300
 
303
- ### 4D: review section
301
+ From the content, synthesize a 2–4 sentence `REVIEW_CONTEXT` that captures:
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)
304
306
 
305
- ```
306
- Detected tech stack: {TECH_STACK}
307
+ If neither CLAUDE.md nor README.md exists: `REVIEW_CONTEXT = ""` at low confidence.
307
308
 
308
- Is this accurate? Add/edit if needed, or press Enter to accept:
309
- ```
309
+ **Confidence**:
310
+ - Synthesized from CLAUDE.md → `high`
311
+ - Synthesized from README.md → `medium`
312
+ - No source found → `low`
310
313
 
311
- ```
312
- Any additional context for code reviewers? (unusual conventions, deploy setup, known pitfalls)
313
- Press Enter to skip, or describe briefly:
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
+ }
314
321
  ```
315
322
 
316
- ### 4E: verification section
323
+ ---
317
324
 
318
- ```
319
- Detected potential health endpoint path: {DETECTED_PATH}
320
- Full health URL (e.g., https://api.myproject.io/health), or press Enter to skip:
325
+ ## Phase 4: Enrich verification
326
+
327
+ **Always runs** — does not require GitHub auth.
328
+
329
+ ### 4A: Detect health endpoint
330
+
331
+ ```bash
332
+ # Search for health route definitions in server code
333
+ grep -rn "health\|/ping\|/status" "$REPO_ROOT" \
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
321
341
  ```
322
342
 
343
+ From the results, infer the most likely health endpoint path:
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`
347
+
348
+ If an endpoint can be inferred:
349
+ ```json
350
+ { "value": "https://api.<detected-or-inferred-host>/<path>", "confidence": "medium", "source": "<file where found>", "why": "Health route found at <file>:<line>" }
323
351
  ```
324
- Detected container name prefixes: {PREFIXES}
325
- Are these correct? Add/edit prefixes, or press Enter to accept:
352
+
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" }
326
356
  ```
327
357
 
328
- ---
358
+ ### 4B: Detect container names and service patterns
329
359
 
330
- ## Phase 5: Write forge.yaml
331
-
332
- ### 5A: Preserve required sections
333
-
334
- Read the current `forge.yaml` content. Extract the complete `project:`, `paths:`, and `branches:` blocks verbatim — they must not change.
335
-
336
- ### 5B: Build optional section YAML
337
-
338
- For each optional section the user confirmed:
339
-
340
- **repos** (if satellites confirmed):
341
- ```yaml
342
- repos:
343
- default:
344
- repo: "{OWNER}/{REPO}"
345
- staging_branch: "{branches.staging}"
346
- satellites:
347
- - prefix: "{PREFIX}"
348
- repo: "{OWNER}/{SATELLITE_REPO}"
349
- staging_branch: "main"
350
- local_path: "{REPO_ROOT}/../{SATELLITE_REPO}"
351
- ```
352
-
353
- **project_board** (if board and field IDs found):
354
- ```yaml
355
- project_board:
356
- owner: "{OWNER}"
357
- project_number: {PROJECT_NUMBER}
358
- project_id: "{PROJECT_ID}" # Must be a PVT_... node ID — obtain via: gh project list --owner <owner> --format json | jq '.projects[] | .id'
359
- field_ids:
360
- status: "{STATUS_FIELD_ID}"
361
- lane: "{LANE_FIELD_ID}"
362
- component: "{COMPONENT_FIELD_ID}"
363
- priority: "{PRIORITY_FIELD_ID}"
364
- workflow: "{WORKFLOW_FIELD_ID}"
365
- option_ids:
366
- status:
367
- todo: "{TODO_OPTION_ID}"
368
- in_progress: "{IN_PROGRESS_OPTION_ID}"
369
- done: "{DONE_OPTION_ID}"
370
- lane:
371
- fast: "{FAST_OPTION_ID}"
372
- feature: "{FEATURE_OPTION_ID}"
373
- priority:
374
- p0: "{P0_OPTION_ID}"
375
- p1: "{P1_OPTION_ID}"
376
- p2: "{P2_OPTION_ID}"
377
- p3: "{P3_OPTION_ID}"
378
- workflow:
379
- investigating: "{INVESTIGATING_OPTION_ID}"
380
- building: "{BUILDING_OPTION_ID}"
381
- in_review: "{IN_REVIEW_OPTION_ID}"
382
- merged: "{MERGED_OPTION_ID}"
383
- ```
384
-
385
- **services** (if analytics/monitoring confirmed):
386
- ```yaml
387
- services:
388
- domain: "{ROOT_DOMAIN}"
389
- api_url: "{API_URL}"
390
- analytics:
391
- # Only include subsections the user confirmed
392
- umami: # if selected
393
- url: "{UMAMI_URL}"
394
- website_id: "{UMAMI_WEBSITE_ID}"
395
- clarity: # if selected
396
- project_id: "{CLARITY_PROJECT_ID}"
397
- ga4: # if selected
398
- property_id: "{GA4_PROPERTY_ID}"
399
- ```
400
-
401
- **review** (if tech stack or context provided):
402
- ```yaml
403
- review:
404
- tech_stack: "{TECH_STACK}"
405
- context: |
406
- {PROJECT_CONTEXT}
407
- ```
408
-
409
- **verification** (if health endpoint or containers found):
410
- ```yaml
411
- verification:
412
- health_endpoint: "{HEALTH_ENDPOINT}"
413
- health_patterns:
414
- - '"status": "ok"'
415
- internal_service_patterns:
416
- - "{SERVICE_PREFIX}"
417
- ```
418
-
419
- ### 5C: Overwrite protection
420
-
421
- For each optional section that is ALREADY ACTIVE in forge.yaml (detected in Phase 1C):
422
-
423
- If `FORCE=false`:
424
- ```
425
- forge.yaml already has an active {section}: section.
426
- Overwrite it with newly detected values? [y/N]
427
- ```
428
-
429
- If user says N: keep existing content for that section.
430
-
431
- ### 5D: Write the file
432
-
433
- Backup the existing file:
434
360
  ```bash
435
- cp "$FORGE_YAML" "${FORGE_YAML}.bak"
436
- echo "Backed up: forge.yaml → forge.yaml.bak"
361
+ for f in "$REPO_ROOT"/docker-compose*.yml; do
362
+ [ -f "$f" ] || continue
363
+ grep -E "^\s+container_name:" "$f" | head -20
364
+ done
437
365
  ```
438
366
 
439
- 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).
367
+ Extract the common prefix from all container names (e.g., `acme-api`, `acme-worker` → prefix `acme-`).
440
368
 
441
- Print a summary of what was written:
369
+ If container names are found:
370
+ ```json
371
+ { "value": "<prefix>", "confidence": "high", "source": "docker-compose.yml container_name entries", "why": "Containers named: <list>" }
442
372
  ```
443
- forge.yaml updated:
444
373
 
445
- Required sections (preserved):
446
- ✓ project
447
- ✓ paths
448
- ✓ branches
374
+ If no docker-compose files found or no container_name entries:
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
+ ```
449
378
 
450
- Optional sections (filled):
451
- ✓ repos — 2 satellite(s) configured
452
- ✓ project_board — field IDs from project "Acme Board" (#1)
453
- ✗ services — skipped (no analytics services)
454
- ✓ review — tech stack + context
455
- ✓ verification — health endpoint configured
379
+ ### 4C: Build verification enrichment
456
380
 
457
- Optional sections (skipped — still commented out):
458
- - services
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
+ }
459
391
  ```
460
392
 
461
393
  ---
462
394
 
463
- ## Phase 6: Validation
395
+ ## Phase 5: Assemble and Return Enriched ConfigDraft
464
396
 
465
- ### 6A: Repo access
397
+ ### 5A: Merge enrichments into the draft
466
398
 
467
- ```bash
468
- gh repo view "${OWNER}/${REPO}" --json name,url 2>/dev/null \
469
- || echo "WARNING: Cannot access ${OWNER}/${REPO} — check project.owner and project.repo"
470
- ```
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
471
404
 
472
- ### 6B: Path existence
405
+ Never modify `DRAFT.project`, `DRAFT.paths`, `DRAFT.branches`, or `DRAFT.meta` — pass them through unchanged.
473
406
 
474
- ```bash
475
- [ -d "$(grep 'root:' "$FORGE_YAML" | head -1 | awk '{print $2}' | tr -d '"')" ] \
476
- && echo "✓ paths.root exists" \
477
- || echo "WARNING: paths.root does not exist on this machine"
407
+ ### 5B: Add enrichment metadata
408
+
409
+ Append to `DRAFT.meta`:
410
+
411
+ ```json
412
+ {
413
+ "enriched": true,
414
+ "enrichment_backend": "skill",
415
+ "enriched_at": "<ISO 8601 timestamp>",
416
+ "enrichment_notes": ["<any skipped phases and reasons>"]
417
+ }
478
418
  ```
479
419
 
480
- ### 6C: Project board ID resolution
420
+ ### 5C: Return the enriched ConfigDraft
481
421
 
482
- `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.
422
+ Output the complete enriched ConfigDraft as a JSON blob on stdout. This is the skill's return value — the caller (CLI orchestrator or review-render) reads it from the output.
483
423
 
484
- ```bash
485
- # Only run if project_board section was written
486
- if grep -q "^project_board:" "$FORGE_YAML"; then
487
- PROJECT_ID=$(grep "project_id:" "$FORGE_YAML" | head -1 | awk '{print $2}' | tr -d '"')
488
- # Validate PVT_ prefix before GraphQL call — any other format will produce a confusing API error
489
- if [[ "$PROJECT_ID" != PVT_* ]]; then
490
- echo "ERROR: project_board.project_id must start with 'PVT_' (got: '$PROJECT_ID')"
491
- echo " Expected format: PVT_kwHOxxxxxxxxxxxxxxxx"
492
- echo " Obtain the correct value with: gh project list --owner ${OWNER} --format json | jq '.projects[] | .id'"
493
- else
494
- gh api graphql -f query='query { node(id: "'"$PROJECT_ID"'") { id __typename } }' 2>/dev/null \
495
- | jq -e '.data.node.id' > /dev/null \
496
- && echo "✓ project_board.project_id resolves" \
497
- || echo "WARNING: project_board.project_id may be invalid — verify with: gh project list --owner ${OWNER}"
498
- fi
499
- fi
424
+ ```
425
+ <ENRICHED_DRAFT_JSON>
500
426
  ```
501
427
 
502
- ### 6D: Satellite repo access
428
+ **Output format requirements**:
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
503
433
 
504
- ```bash
505
- # Only run if repos.satellites section was written
506
- if grep -q "^repos:" "$FORGE_YAML"; then
507
- # Extract satellite repo names and verify each is accessible
508
- grep -A 20 "^repos:" "$FORGE_YAML" | grep "repo:" | tail -n +2 | while read -r line; do
509
- SAT_REPO=$(echo "$line" | awk '{print $2}' | tr -d '"')
510
- gh repo view "$SAT_REPO" --json name 2>/dev/null \
511
- && echo "✓ satellite repo ${SAT_REPO} accessible" \
512
- || echo "WARNING: satellite repo ${SAT_REPO} not accessible — check prefix and repo name"
513
- done
514
- fi
515
- ```
434
+ ---
435
+
436
+ ## Enrichment Summary (informational)
516
437
 
517
- ### 6E: Final report
438
+ After the JSON output, print a human-readable enrichment summary for the operator's benefit. This is NOT part of the machine-readable output — it is separated by a `---` divider:
518
439
 
519
440
  ```
520
- forge.yaml is ready.
441
+ ---
442
+ ## Enrichment Summary
521
443
 
522
- Validation results:
523
- {validation output from 6A–6D}
444
+ Sections enriched:
445
+ project_board — <high|medium|low: brief note>
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
524
449
 
525
- Next steps:
526
- 1. If forge.yaml contains sensitive paths (credentials file, local paths):
527
- echo "forge.yaml" >> .gitignore
528
- 2. Test the pipeline:
529
- /work-on next
530
- 3. If any WARNING appeared above, edit forge.yaml to fix the flagged values.
531
- 4. Backup file: forge.yaml.bak (delete when satisfied)
450
+ Skipped:
451
+ <phase> — <reason>
532
452
 
533
- ForgeDock commands now use your project's config from forge.yaml.
453
+ Low-confidence fields (will appear as TODO flags in forge.yaml):
454
+ <list of field paths with low confidence and their why>
534
455
  ```