forgedock 1.0.7 → 1.0.9
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/bin/forge-utils.mjs +164 -0
- package/bin/forgedock.mjs +2054 -496
- package/bin/hooks/session-start.mjs +317 -0
- package/bin/init-detect.mjs +325 -0
- package/bin/init-enrich-api.mjs +203 -0
- package/bin/registry.mjs +358 -0
- package/bin/tui.mjs +453 -2
- package/commands/autopilot.md +6 -0
- package/commands/deploy-info.md +8 -8
- package/commands/forgedock-init.md +305 -384
- package/commands/orchestrate.md +3 -3
- package/commands/review-pr-staging.md +21 -21
- package/commands/review-pr.md +17 -17
- package/commands/rollback.md +2 -2
- package/commands/work-on/build/validate.md +4 -4
- package/commands/work-on-monolithic.md +18 -1
- package/commands/work-on.md +4 -4
- package/docs/CONFIG.md +167 -0
- package/docs/superpowers/specs/2026-06-09-intelligent-onboarding-design.md +172 -0
- package/package.json +5 -1
- package/scripts/verify-route-registration.sh +50 -19
- package/scripts/verify-sops-chain.sh +4 -2
package/commands/orchestrate.md
CHANGED
|
@@ -824,9 +824,9 @@ All tool commands are read from `forge.yaml → verification.commands`; each ste
|
|
|
824
824
|
|
|
825
825
|
```bash
|
|
826
826
|
# Read toolchain commands from forge.yaml
|
|
827
|
-
TS_TYPECHECK=$(
|
|
828
|
-
TS_BUILD=$(
|
|
829
|
-
PYTHON_FORMAT=$(
|
|
827
|
+
TS_TYPECHECK=$(yq '.verification.commands.typescript.typecheck // ""' forge.yaml 2>/dev/null || echo '')
|
|
828
|
+
TS_BUILD=$(yq '.verification.commands.typescript.build // ""' forge.yaml 2>/dev/null || echo '')
|
|
829
|
+
PYTHON_FORMAT=$(yq '.verification.commands.python.format // ""' forge.yaml 2>/dev/null || echo '')
|
|
830
830
|
|
|
831
831
|
# Check if this is a milestone batch with TypeScript changes
|
|
832
832
|
MILESTONE_BRANCH="milestone/{milestone_slug}"
|
|
@@ -14,6 +14,22 @@ Performs comprehensive review of `staging` before merging to `main`. Handles lar
|
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
+
## Config Resolution
|
|
18
|
+
|
|
19
|
+
Read `forge.yaml` to resolve branch names before running any commands:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
CONFIG_FILE="${FORGE_CONFIG:-forge.yaml}"
|
|
23
|
+
GH_REPO=$(yq '.project.owner + "/" + .project.repo' "$CONFIG_FILE")
|
|
24
|
+
GH_FLAG="-R $GH_REPO"
|
|
25
|
+
DEFAULT_BRANCH=$(yq '.branches.default' "$CONFIG_FILE")
|
|
26
|
+
STAGING_BRANCH=$(yq '.branches.staging' "$CONFIG_FILE")
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
All `$DEFAULT_BRANCH` and `$STAGING_BRANCH` references below are populated from `forge.yaml`.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
17
33
|
## Evidence-Based Review Protocol (ALL Agents)
|
|
18
34
|
|
|
19
35
|
### Diff-First Approach
|
|
@@ -70,22 +86,6 @@ Include ALL findings (CONFIRMED, LIKELY, POSSIBLE). One line per finding, sequen
|
|
|
70
86
|
|
|
71
87
|
---
|
|
72
88
|
|
|
73
|
-
## Config Resolution
|
|
74
|
-
|
|
75
|
-
Read `forge.yaml` to resolve branch names before running any commands:
|
|
76
|
-
|
|
77
|
-
```bash
|
|
78
|
-
CONFIG_FILE="${FORGE_CONFIG:-forge.yaml}"
|
|
79
|
-
GH_REPO=$(yq '.project.owner + "/" + .project.repo' "$CONFIG_FILE")
|
|
80
|
-
GH_FLAG="-R $GH_REPO"
|
|
81
|
-
DEFAULT_BRANCH=$(yq '.branches.default' "$CONFIG_FILE")
|
|
82
|
-
STAGING_BRANCH=$(yq '.branches.staging' "$CONFIG_FILE")
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
All `$DEFAULT_BRANCH` and `$STAGING_BRANCH` references below are populated from `forge.yaml`.
|
|
86
|
-
|
|
87
|
-
---
|
|
88
|
-
|
|
89
89
|
## Phase 0A: Open Review-Finding Gate (BLOCKING — runs before scope analysis)
|
|
90
90
|
|
|
91
91
|
**Purpose**: Prevent deploying commits that have known, unfixed review findings. The review system catches bugs before merging; this gate ensures the merge path acts on that information.
|
|
@@ -213,8 +213,8 @@ Create review chunks by priority: Billing/Pricing (CRITICAL) → Security/Auth (
|
|
|
213
213
|
Read `forge.yaml → verification.commands.python` for project-specific tool commands:
|
|
214
214
|
|
|
215
215
|
```bash
|
|
216
|
-
PYTHON_FORMAT=$(
|
|
217
|
-
PYTHON_LINT=$(
|
|
216
|
+
PYTHON_FORMAT=$(yq '.verification.commands.python.format // ""' forge.yaml 2>/dev/null || echo '')
|
|
217
|
+
PYTHON_LINT=$(yq '.verification.commands.python.lint // ""' forge.yaml 2>/dev/null || echo '')
|
|
218
218
|
|
|
219
219
|
if [ -n "$PYTHON_FORMAT" ]; then
|
|
220
220
|
eval "$PYTHON_FORMAT" 2>&1 | head -30
|
|
@@ -234,8 +234,8 @@ fi
|
|
|
234
234
|
Read `forge.yaml → verification.commands.typescript` for project-specific tool commands:
|
|
235
235
|
|
|
236
236
|
```bash
|
|
237
|
-
TS_TYPECHECK=$(
|
|
238
|
-
TS_BUILD=$(
|
|
237
|
+
TS_TYPECHECK=$(yq '.verification.commands.typescript.typecheck // ""' forge.yaml 2>/dev/null || echo '')
|
|
238
|
+
TS_BUILD=$(yq '.verification.commands.typescript.build // ""' forge.yaml 2>/dev/null || echo '')
|
|
239
239
|
|
|
240
240
|
if [ -n "$TS_TYPECHECK" ]; then
|
|
241
241
|
eval "$TS_TYPECHECK" 2>&1
|
|
@@ -261,7 +261,7 @@ Build failure is BLOCKING — deploy WILL fail. Typecheck alone misses SSG/prere
|
|
|
261
261
|
Read `forge.yaml → verification.commands.python.test`:
|
|
262
262
|
|
|
263
263
|
```bash
|
|
264
|
-
PYTHON_TEST=$(
|
|
264
|
+
PYTHON_TEST=$(yq '.verification.commands.python.test // ""' forge.yaml 2>/dev/null || echo '')
|
|
265
265
|
|
|
266
266
|
if [ -n "$PYTHON_TEST" ]; then
|
|
267
267
|
eval "$PYTHON_TEST" 2>&1 | tail -50
|
package/commands/review-pr.md
CHANGED
|
@@ -161,9 +161,9 @@ Read `forge.yaml → verification.commands.python` for project-specific tool com
|
|
|
161
161
|
|
|
162
162
|
```bash
|
|
163
163
|
# Read toolchain commands from forge.yaml
|
|
164
|
-
PYTHON_FORMAT=$(
|
|
165
|
-
PYTHON_LINT=$(
|
|
166
|
-
PYTHON_TYPECHECK=$(
|
|
164
|
+
PYTHON_FORMAT=$(yq '.verification.commands.python.format // ""' forge.yaml 2>/dev/null || echo '')
|
|
165
|
+
PYTHON_LINT=$(yq '.verification.commands.python.lint // ""' forge.yaml 2>/dev/null || echo '')
|
|
166
|
+
PYTHON_TYPECHECK=$(yq '.verification.commands.python.typecheck // ""' forge.yaml 2>/dev/null || echo '')
|
|
167
167
|
|
|
168
168
|
# Run format check
|
|
169
169
|
if [ -n "$PYTHON_FORMAT" ]; then
|
|
@@ -187,7 +187,7 @@ else
|
|
|
187
187
|
fi
|
|
188
188
|
|
|
189
189
|
# Always run compile check on changed Python files (fast, language-universal)
|
|
190
|
-
|
|
190
|
+
gh pr diff $ARGUMENTS --name-only | grep '\.py$' | while IFS= read -r f; do
|
|
191
191
|
python3 -m py_compile "$f" 2>&1
|
|
192
192
|
done
|
|
193
193
|
```
|
|
@@ -197,9 +197,9 @@ done
|
|
|
197
197
|
Read `forge.yaml → verification.commands.typescript` for project-specific tool commands:
|
|
198
198
|
|
|
199
199
|
```bash
|
|
200
|
-
TS_FORMAT=$(
|
|
201
|
-
TS_LINT=$(
|
|
202
|
-
TS_TYPECHECK=$(
|
|
200
|
+
TS_FORMAT=$(yq '.verification.commands.typescript.format // ""' forge.yaml 2>/dev/null || echo '')
|
|
201
|
+
TS_LINT=$(yq '.verification.commands.typescript.lint // ""' forge.yaml 2>/dev/null || echo '')
|
|
202
|
+
TS_TYPECHECK=$(yq '.verification.commands.typescript.typecheck // ""' forge.yaml 2>/dev/null || echo '')
|
|
203
203
|
|
|
204
204
|
# Run format check
|
|
205
205
|
if [ -n "$TS_FORMAT" ]; then
|
|
@@ -243,7 +243,7 @@ gh pr diff $ARGUMENTS | grep -oE "['\"][A-Za-z0-9+/=]{40,}['\"]" | head -10
|
|
|
243
243
|
|
|
244
244
|
### 2F: SQL Migration Validation (if *.sql changed)
|
|
245
245
|
```bash
|
|
246
|
-
|
|
246
|
+
gh pr diff $ARGUMENTS --name-only | grep "\.sql$" | while IFS= read -r sql_file; do
|
|
247
247
|
grep -E "FOR UPDATE" "$sql_file" | grep -qE "(SUM|COUNT|AVG|MIN|MAX)\s*\(" && echo "ERROR: FOR UPDATE with aggregate"
|
|
248
248
|
grep -qE "DROP (TABLE|COLUMN|INDEX)" "$sql_file" && ! grep -qE "IF EXISTS" "$sql_file" && echo "WARNING: DROP without IF EXISTS"
|
|
249
249
|
grep -qE "ALTER TABLE.*ADD COLUMN.*NOT NULL" "$sql_file" && ! grep -qE "DEFAULT" "$sql_file" && echo "WARNING: NOT NULL without DEFAULT"
|
|
@@ -263,7 +263,7 @@ Read `forge.yaml → verification.commands.{lang}.test` for the project's test c
|
|
|
263
263
|
```bash
|
|
264
264
|
# Check each configured language for a test command
|
|
265
265
|
for lang in python typescript go rust; do
|
|
266
|
-
TEST_CMD=$(
|
|
266
|
+
TEST_CMD=$(yq ".verification.commands.${lang}.test // \"\"" forge.yaml 2>/dev/null || echo '')
|
|
267
267
|
if [ -n "$TEST_CMD" ]; then
|
|
268
268
|
echo "=== Running tests (${lang}): ${TEST_CMD} ==="
|
|
269
269
|
eval "$TEST_CMD" 2>&1 | tail -30
|
|
@@ -273,8 +273,8 @@ for lang in python typescript go rust; do
|
|
|
273
273
|
done
|
|
274
274
|
|
|
275
275
|
# If no test commands were configured, log explicitly
|
|
276
|
-
PYTHON_TEST=$(
|
|
277
|
-
TS_TEST=$(
|
|
276
|
+
PYTHON_TEST=$(yq '.verification.commands.python.test // ""' forge.yaml 2>/dev/null || echo '')
|
|
277
|
+
TS_TEST=$(yq '.verification.commands.typescript.test // ""' forge.yaml 2>/dev/null || echo '')
|
|
278
278
|
if [ -z "$PYTHON_TEST" ] && [ -z "$TS_TEST" ]; then
|
|
279
279
|
echo "SKIPPED — no test commands configured in verification.commands"
|
|
280
280
|
fi
|
|
@@ -303,8 +303,8 @@ Read `forge.yaml → verification.commands.typescript.typecheck` and `.build`:
|
|
|
303
303
|
```bash
|
|
304
304
|
gh pr checkout $ARGUMENTS --detach 2>/dev/null
|
|
305
305
|
|
|
306
|
-
TS_TYPECHECK=$(
|
|
307
|
-
TS_BUILD=$(
|
|
306
|
+
TS_TYPECHECK=$(yq '.verification.commands.typescript.typecheck // ""' forge.yaml 2>/dev/null || echo '')
|
|
307
|
+
TS_BUILD=$(yq '.verification.commands.typescript.build // ""' forge.yaml 2>/dev/null || echo '')
|
|
308
308
|
|
|
309
309
|
if [ -n "$TS_TYPECHECK" ]; then
|
|
310
310
|
eval "$TS_TYPECHECK" 2>&1
|
|
@@ -337,10 +337,10 @@ Read `forge.yaml → verification.commands.python.format` and `.build`:
|
|
|
337
337
|
gh pr checkout $ARGUMENTS --detach 2>/dev/null
|
|
338
338
|
|
|
339
339
|
# Compile-check all changed Python files (language-universal — no config needed)
|
|
340
|
-
|
|
340
|
+
echo "$CHANGED_FILES" | grep '\.py$' | while IFS= read -r f; do python3 -m py_compile "$f" 2>&1; done
|
|
341
341
|
|
|
342
342
|
if [ "$REQUIRES_FULL_BUILD" = "true" ]; then
|
|
343
|
-
PYTHON_FORMAT=$(
|
|
343
|
+
PYTHON_FORMAT=$(yq '.verification.commands.python.format // ""' forge.yaml 2>/dev/null || echo '')
|
|
344
344
|
if [ -n "$PYTHON_FORMAT" ]; then
|
|
345
345
|
eval "$PYTHON_FORMAT" 2>&1
|
|
346
346
|
else
|
|
@@ -495,7 +495,7 @@ fi
|
|
|
495
495
|
# Python scoping hazard check — local imports that shadow module-level names
|
|
496
496
|
# A local `import X` makes X a local variable for the ENTIRE function scope.
|
|
497
497
|
# Any reference to X ABOVE the local import will crash with UnboundLocalError.
|
|
498
|
-
|
|
498
|
+
echo "$CHANGED_FILES" | grep -E '\.py$' | while IFS= read -r f; do
|
|
499
499
|
echo "=== Python Scoping Check: $f ==="
|
|
500
500
|
# Find function-scoped imports (indented import statements)
|
|
501
501
|
grep -nE "^\s+import [a-z]" "$f" 2>/dev/null | while read line; do
|
|
@@ -509,7 +509,7 @@ for f in $(echo "$CHANGED_FILES" | grep -E '\.py$'); do
|
|
|
509
509
|
done
|
|
510
510
|
|
|
511
511
|
# Config file assumption check (baked into Docker image vs volume-mounted)
|
|
512
|
-
|
|
512
|
+
echo "$CHANGED_FILES" | grep -E "config/.*\.(json|yaml|yml)$" | while IFS= read -r f; do
|
|
513
513
|
echo "=== Config File: $f ==="
|
|
514
514
|
grep -n "$(dirname $f)" docker-compose.yml 2>/dev/null || echo "WARNING: Config dir may not be mounted — changes may require --build"
|
|
515
515
|
grep -n "$(dirname $f)" services/*/Dockerfile 2>/dev/null || true
|
package/commands/rollback.md
CHANGED
|
@@ -122,7 +122,7 @@ for f in {reverted_python_files}; do
|
|
|
122
122
|
done
|
|
123
123
|
|
|
124
124
|
# Python format check (config-driven)
|
|
125
|
-
PYTHON_FORMAT=$(
|
|
125
|
+
PYTHON_FORMAT=$(yq '.verification.commands.python.format // ""' forge.yaml 2>/dev/null || echo '')
|
|
126
126
|
if [ -n "$PYTHON_FORMAT" ]; then
|
|
127
127
|
eval "$PYTHON_FORMAT" 2>&1 | head -20
|
|
128
128
|
else
|
|
@@ -130,7 +130,7 @@ else
|
|
|
130
130
|
fi
|
|
131
131
|
|
|
132
132
|
# TypeScript typecheck (config-driven)
|
|
133
|
-
TS_TYPECHECK=$(
|
|
133
|
+
TS_TYPECHECK=$(yq '.verification.commands.typescript.typecheck // ""' forge.yaml 2>/dev/null || echo '')
|
|
134
134
|
if [ -n "$TS_TYPECHECK" ]; then
|
|
135
135
|
eval "$TS_TYPECHECK" 2>&1 | head -20
|
|
136
136
|
else
|
|
@@ -94,7 +94,7 @@ Run after quality gate passes. All tool commands are read from `forge.yaml → v
|
|
|
94
94
|
```bash
|
|
95
95
|
cd {WORKTREE_PATH}
|
|
96
96
|
|
|
97
|
-
PYTHON_FORMAT=$(
|
|
97
|
+
PYTHON_FORMAT=$(yq '.verification.commands.python.format // ""' forge.yaml 2>/dev/null || echo '')
|
|
98
98
|
if [ -n "$PYTHON_FORMAT" ]; then
|
|
99
99
|
eval "$PYTHON_FORMAT" 2>&1
|
|
100
100
|
else
|
|
@@ -110,9 +110,9 @@ Failures in `py_compile` are BLOCKING — fix before continuing.
|
|
|
110
110
|
```bash
|
|
111
111
|
cd {WORKTREE_PATH}
|
|
112
112
|
|
|
113
|
-
TS_FORMAT=$(
|
|
114
|
-
TS_TYPECHECK=$(
|
|
115
|
-
TS_BUILD=$(
|
|
113
|
+
TS_FORMAT=$(yq '.verification.commands.typescript.format // ""' forge.yaml 2>/dev/null || echo '')
|
|
114
|
+
TS_TYPECHECK=$(yq '.verification.commands.typescript.typecheck // ""' forge.yaml 2>/dev/null || echo '')
|
|
115
|
+
TS_BUILD=$(yq '.verification.commands.typescript.build // ""' forge.yaml 2>/dev/null || echo '')
|
|
116
116
|
|
|
117
117
|
if [ -n "$TS_FORMAT" ]; then
|
|
118
118
|
eval "$TS_FORMAT" 2>&1
|
|
@@ -214,7 +214,24 @@ Fix HIGH/MEDIUM findings. Max 2 iterations. Skip for 1-file config/docs edits.
|
|
|
214
214
|
All client-side fetch/useSWR/apiFetch must use `/api/...` proxy routes, NEVER `/api/v1/...` directly.
|
|
215
215
|
|
|
216
216
|
### 3I: Deployment completeness check (MANDATORY)
|
|
217
|
-
|
|
217
|
+
Skip if no new env vars introduced.
|
|
218
|
+
|
|
219
|
+
For each new env var, verify present in ALL required locations:
|
|
220
|
+
|
|
221
|
+
| Location | Required for |
|
|
222
|
+
|----------|-------------|
|
|
223
|
+
| `.env.example` | All new vars |
|
|
224
|
+
| Secrets backend (see `deploy.secrets_backend`) | Secret vars — skip if backend is `none` or unset |
|
|
225
|
+
| `app/env_validation.py` | API service vars (if project has one) |
|
|
226
|
+
|
|
227
|
+
**Secrets backend check** *(trigger: `deploy.secrets_backend == "sops"`)*:
|
|
228
|
+
|
|
229
|
+
If the project uses SOPS, verify the new var is present in all SOPS chain locations:
|
|
230
|
+
- `infra/secrets/prod.enc.yaml` — SOPS-encrypted secret store
|
|
231
|
+
- `infra/decrypt-secrets.sh` ENV_MAPPING — maps SOPS key to env var name
|
|
232
|
+
|
|
233
|
+
If `deploy.secrets_backend` is absent or not `sops`, skip these checks and log:
|
|
234
|
+
> `SKIP: SOPS chain check — deploy.secrets_backend is not "sops". Configure deploy.secrets_backend in forge.yaml to enable.`
|
|
218
235
|
|
|
219
236
|
### 3J: Commit
|
|
220
237
|
Conventional prefix (fix/feat/refactor/docs). Reference #{NUMBER} in the commit message.
|
package/commands/work-on.md
CHANGED
|
@@ -753,7 +753,7 @@ All tool commands are read from `forge.yaml → verification.commands`. When a k
|
|
|
753
753
|
```bash
|
|
754
754
|
cd {WORKTREE_PATH}
|
|
755
755
|
|
|
756
|
-
PYTHON_FORMAT=$(
|
|
756
|
+
PYTHON_FORMAT=$(yq '.verification.commands.python.format // ""' forge.yaml 2>/dev/null || echo '')
|
|
757
757
|
if [ -n "$PYTHON_FORMAT" ]; then
|
|
758
758
|
eval "$PYTHON_FORMAT" 2>&1
|
|
759
759
|
else
|
|
@@ -769,9 +769,9 @@ python -m py_compile {PYTHON_FILES}
|
|
|
769
769
|
```bash
|
|
770
770
|
cd {WORKTREE_PATH}
|
|
771
771
|
|
|
772
|
-
TS_FORMAT=$(
|
|
773
|
-
TS_TYPECHECK=$(
|
|
774
|
-
TS_BUILD=$(
|
|
772
|
+
TS_FORMAT=$(yq '.verification.commands.typescript.format // ""' forge.yaml 2>/dev/null || echo '')
|
|
773
|
+
TS_TYPECHECK=$(yq '.verification.commands.typescript.typecheck // ""' forge.yaml 2>/dev/null || echo '')
|
|
774
|
+
TS_BUILD=$(yq '.verification.commands.typescript.build // ""' forge.yaml 2>/dev/null || echo '')
|
|
775
775
|
|
|
776
776
|
if [ -n "$TS_FORMAT" ]; then
|
|
777
777
|
eval "$TS_FORMAT" 2>&1
|
package/docs/CONFIG.md
CHANGED
|
@@ -12,6 +12,21 @@ echo "forge.yaml" >> .gitignore # if your credentials path is sensitive
|
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
15
|
+
## CLI Flags for `init`
|
|
16
|
+
|
|
17
|
+
`npx forgedock init` accepts two optional flags that change how configuration is generated:
|
|
18
|
+
|
|
19
|
+
| Flag | Behavior |
|
|
20
|
+
|------|----------|
|
|
21
|
+
| `--manual` | Skips AI autopilot enrichment entirely. Presents the annotated review screen with detection baseline values only (no AI-enriched suggestions). Detection still runs to provide defaults — you review and edit the raw inferred values on the same single-screen interface. |
|
|
22
|
+
| `--verbose` | Surfaces each field's detection source and confidence rationale during the init flow. Helpful for understanding where ForgeDock inferred values from (e.g. `git remote`, `package.json`, `gh api`). |
|
|
23
|
+
|
|
24
|
+
The two flags can be combined: `npx forgedock init --manual --verbose` shows the annotated review screen with detection baseline values and field provenance at each entry.
|
|
25
|
+
|
|
26
|
+
The default (`npx forgedock init` with no flags) remains the zero-question autopilot: AI enrichment fills in everything it can, and you review the result on a single annotated screen.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
15
30
|
## Schema Overview
|
|
16
31
|
|
|
17
32
|
| Section | Required | Purpose |
|
|
@@ -468,6 +483,96 @@ Do **not** mix the two patterns for the same block of variables (e.g., `yq` with
|
|
|
468
483
|
|
|
469
484
|
---
|
|
470
485
|
|
|
486
|
+
## ConfigDraft Contract
|
|
487
|
+
|
|
488
|
+
`ConfigDraft` is the shared data structure emitted by `bin/init-detect.mjs` and consumed by the AI enrichment backends (`init-enrich`) and the annotated review renderer (`review-render`). It mirrors the required sections of `forge.yaml` at a per-field granularity, adding provenance and confidence metadata.
|
|
489
|
+
|
|
490
|
+
### Field shape
|
|
491
|
+
|
|
492
|
+
Every leaf value in a `ConfigDraft` is a **ConfigField** object:
|
|
493
|
+
|
|
494
|
+
```ts
|
|
495
|
+
{
|
|
496
|
+
value: string, // The detected or inferred value
|
|
497
|
+
confidence: "high" | "medium" | "low", // How certain the detection was
|
|
498
|
+
source: string, // Human-readable label for where the value came from
|
|
499
|
+
why: string, // Plain-language explanation of why this value was chosen
|
|
500
|
+
}
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
**Confidence levels:**
|
|
504
|
+
|
|
505
|
+
| Level | Meaning |
|
|
506
|
+
|-------|---------|
|
|
507
|
+
| `"high"` | Verified from a concrete, unambiguous source (e.g., parsed from the git remote URL) |
|
|
508
|
+
| `"medium"` | Inferred from available signals; likely correct but not guaranteed (e.g., current branch name, name derived from repo slug) |
|
|
509
|
+
| `"low"` | Guessed default — no supporting evidence was found (e.g., no git remote, not a git repo) |
|
|
510
|
+
|
|
511
|
+
### ConfigDraft shape
|
|
512
|
+
|
|
513
|
+
```js
|
|
514
|
+
{
|
|
515
|
+
project: {
|
|
516
|
+
owner: ConfigField, // GitHub org or username
|
|
517
|
+
repo: ConfigField, // Repository name (without owner prefix)
|
|
518
|
+
name: ConfigField, // Human-readable project name
|
|
519
|
+
},
|
|
520
|
+
paths: {
|
|
521
|
+
root: ConfigField, // Absolute path to the project root
|
|
522
|
+
worktreeBase: ConfigField, // Absolute path to the git worktree base dir
|
|
523
|
+
},
|
|
524
|
+
branches: {
|
|
525
|
+
default: ConfigField, // Default branch (e.g. "main")
|
|
526
|
+
staging: ConfigField, // Staging branch for fast-lane PRs
|
|
527
|
+
},
|
|
528
|
+
meta: {
|
|
529
|
+
remoteDetected: boolean, // true iff a parseable git remote URL was found
|
|
530
|
+
},
|
|
531
|
+
}
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### Example output (high-confidence repo)
|
|
535
|
+
|
|
536
|
+
```js
|
|
537
|
+
{
|
|
538
|
+
project: {
|
|
539
|
+
owner: { value: "acme-org", confidence: "high", source: "git remote origin (SSH)", why: "Parsed from SSH remote URL: git@github.com:acme-org/acme-platform.git" },
|
|
540
|
+
repo: { value: "acme-platform", confidence: "high", source: "git remote origin (SSH)", why: "Parsed from SSH remote URL: git@github.com:acme-org/acme-platform.git" },
|
|
541
|
+
name: { value: "Acme Platform", confidence: "medium", source: "derived from repo slug", why: "Title-cased version of repo name 'acme-platform' (split on hyphens/underscores)" },
|
|
542
|
+
},
|
|
543
|
+
paths: {
|
|
544
|
+
root: { value: "/home/user/acme", confidence: "high", source: "process.cwd()", why: "Absolute path passed to detectConfig — the project root" },
|
|
545
|
+
worktreeBase: { value: "/home/user/acme/.claude/worktrees", confidence: "high", source: "derived from root", why: "Convention: {root}/.claude/worktrees" },
|
|
546
|
+
},
|
|
547
|
+
branches: {
|
|
548
|
+
default: { value: "main", confidence: "high", source: "git symbolic-ref refs/remotes/origin/HEAD", why: "Remote HEAD points to main" },
|
|
549
|
+
staging: { value: "staging", confidence: "high", source: "git branch -r", why: "Found 'origin/staging' in the remote branch listing" },
|
|
550
|
+
},
|
|
551
|
+
meta: { remoteDetected: true },
|
|
552
|
+
}
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
### Producing a ConfigDraft
|
|
556
|
+
|
|
557
|
+
```js
|
|
558
|
+
import { detectConfig } from "./bin/init-detect.mjs";
|
|
559
|
+
|
|
560
|
+
const draft = await detectConfig(process.cwd());
|
|
561
|
+
// draft.project.owner.value → "acme-org"
|
|
562
|
+
// draft.project.owner.confidence → "high"
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
`detectConfig(cwd)` is the sole public API. It never throws — every error path produces a `low`-confidence default.
|
|
566
|
+
|
|
567
|
+
### Consuming a ConfigDraft
|
|
568
|
+
|
|
569
|
+
Downstream consumers read `field.value` for the raw value and `field.confidence` to decide how to handle it:
|
|
570
|
+
|
|
571
|
+
- **`init-enrich`** (AI enrichment): passes `low`- and `medium`-confidence fields to the AI backend to raise their confidence; leaves `high`-confidence fields untouched.
|
|
572
|
+
- **`review-render`** (TUI review screen): shows each field with its source and why; highlights `low`-confidence fields with a `# TODO(forgedock:<field>)` annotation in the generated YAML.
|
|
573
|
+
|
|
574
|
+
---
|
|
575
|
+
|
|
471
576
|
## CLAUDE.md Integration
|
|
472
577
|
|
|
473
578
|
ForgeDock can inject a managed usage block into your project's `CLAUDE.md` so every Claude Code session opened in the repo automatically knows that ForgeDock drives development here and which commands to use.
|
|
@@ -510,6 +615,68 @@ npx forgedock integrate
|
|
|
510
615
|
|
|
511
616
|
---
|
|
512
617
|
|
|
618
|
+
## Per-Directory State Registry
|
|
619
|
+
|
|
620
|
+
ForgeDock tracks per-directory opt-out state in a central registry file on the local machine.
|
|
621
|
+
|
|
622
|
+
### Registry File Location
|
|
623
|
+
|
|
624
|
+
```
|
|
625
|
+
~/.claude/forgedock/registry.json
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
The directory (`~/.claude/forgedock/`) is created automatically on first use with mode `0700`. The file is never committed to version control — it is per-user, per-machine state.
|
|
629
|
+
|
|
630
|
+
### Registry Schema
|
|
631
|
+
|
|
632
|
+
```json
|
|
633
|
+
{
|
|
634
|
+
"version": 1,
|
|
635
|
+
"optedOut": {
|
|
636
|
+
"/absolute/path/to/project": { "at": "2026-06-09T12:00:00.000Z" }
|
|
637
|
+
},
|
|
638
|
+
"nudgeSeen": {
|
|
639
|
+
"/absolute/path/to/project": { "at": "2026-06-09T12:00:00.000Z" }
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
| Field | Type | Description |
|
|
645
|
+
|-------|------|-------------|
|
|
646
|
+
| `version` | number | Schema version — currently `1` |
|
|
647
|
+
| `optedOut` | object | Map of absolute directory paths to opt-out metadata |
|
|
648
|
+
| `optedOut[path].at` | string | ISO-8601 timestamp of when the opt-out was recorded |
|
|
649
|
+
| `nudgeSeen` | object | Map of absolute directory paths where the one-time "Enable ForgeDock here?" nudge has already been shown |
|
|
650
|
+
| `nudgeSeen[path].at` | string | ISO-8601 timestamp of when the nudge was shown for this directory |
|
|
651
|
+
|
|
652
|
+
### State Resolution
|
|
653
|
+
|
|
654
|
+
The `registry` module resolves one of three states for any directory:
|
|
655
|
+
|
|
656
|
+
| State | Meaning |
|
|
657
|
+
|-------|---------|
|
|
658
|
+
| `managed-active` | Directory contains `forge.yaml` or `.forgedock` and is **not** opted out — ForgeDock is active here |
|
|
659
|
+
| `managed-optedout` | Directory has a managed marker but the user has explicitly opted out — ForgeDock stays silent |
|
|
660
|
+
| `unmanaged` | No `forge.yaml` or `.forgedock` marker found — ForgeDock has no presence here |
|
|
661
|
+
|
|
662
|
+
**Opt-out wins over managed**: if a directory contains `forge.yaml` but its path is listed in `optedOut`, the state is `managed-optedout`.
|
|
663
|
+
|
|
664
|
+
### Failure Behaviour
|
|
665
|
+
|
|
666
|
+
A missing or corrupt `registry.json` is treated as an empty opt-out set. The registry always fails open — it never throws and never blocks a Claude Code session.
|
|
667
|
+
|
|
668
|
+
### Managing Opt-Out State
|
|
669
|
+
|
|
670
|
+
Use the `forgedock enable` and `forgedock disable` commands to add or remove a directory from the opt-out set:
|
|
671
|
+
|
|
672
|
+
```bash
|
|
673
|
+
npx forgedock enable [dir] # Remove directory from opt-out set (default: cwd)
|
|
674
|
+
npx forgedock disable [dir] # Add directory to opt-out set (default: cwd)
|
|
675
|
+
npx forgedock status [dir] # Show resolved state for a directory
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
---
|
|
679
|
+
|
|
513
680
|
## Complete Example
|
|
514
681
|
|
|
515
682
|
See [`forge.yaml.example`](../forge.yaml.example) at the repository root for a fully annotated example covering all sections.
|