forgedock 1.0.8 → 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/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.
@@ -0,0 +1,172 @@
1
+ # Intelligent, Idiot-Proof Onboarding — Design Spec
2
+
3
+ **Date:** 2026-06-09
4
+ **Status:** Approved (design); pending implementation plan
5
+ **Scope:** One milestone, four phases
6
+
7
+ ## Problem
8
+
9
+ Today's onboarding (`npx forgedock` TUI + `forgedock init` + the `forgedock-init`
10
+ skill) works but asks the first-time user ~12 questions across project / paths /
11
+ branches / optional sections, with little explanation of *why* each is asked or
12
+ *what* a good answer looks like. The deterministic auto-detect is decent, but the
13
+ genuinely hard sections (project board field mapping, satellites, review context,
14
+ verification endpoints) still fall back to manual prompts. There is no per-directory
15
+ control: ForgeDock commands install globally and there is no notion of "activate
16
+ here / stay silent there."
17
+
18
+ We want onboarding to be **intelligent** (AI fills the config), **idiot-proof**
19
+ (near-zero questions, everything explained), and **scoped** (a Claude Code hook
20
+ that activates ForgeDock only in directories the user has opted into).
21
+
22
+ ## Vision
23
+
24
+ Open `claude` in any repo → a SessionStart hook resolves the directory's ForgeDock
25
+ state → if managed and active and unconfigured, an AI agent infers the **entire**
26
+ `forge.yaml` from the codebase + GitHub → the user reviews **one annotated screen**
27
+ and presses Enter. No wizard. Per-directory enable/disable means ForgeDock only
28
+ wakes up where wanted.
29
+
30
+ ## Architecture — three pillars
31
+
32
+ ### Pillar A — Autopilot config generation (the "intelligence")
33
+
34
+ Layered backends behind **one interface** so the experience degrades gracefully
35
+ (the chosen "hybrid" model):
36
+
37
+ - **`init-detect`** — deterministic, no AI. Refactors today's git / tech-stack /
38
+ board detection into a pure module that returns a `ConfigDraft` plus a
39
+ **confidence score per field** (`high` = verified from a concrete source,
40
+ `medium` = inferred, `low` = guessed default).
41
+ - **`init-enrich`** — AI enrichment. Consumes a `ConfigDraft`, deeply infers the
42
+ hard sections, and raises confidences. Two interchangeable backends sharing one
43
+ contract:
44
+ - **skill backend** — runs inside Claude Code; no API key; uses existing CC auth.
45
+ *Primary path.*
46
+ - **api backend** — the CLI calls the Anthropic API directly with a BYO
47
+ `ANTHROPIC_API_KEY`. *Used when Claude Code is not present.*
48
+ - **`review-render`** — a single annotated review screen. Every field is shown with
49
+ its **source**, **confidence**, and a plain-language "why this value." Enter to
50
+ accept all; inline-edit to change any field. **Unknowable items get a sensible
51
+ default plus an inline `# TODO(forgedock:<field>)` flag in the YAML — never a
52
+ blocking prompt.**
53
+
54
+ **Backend selection logic:**
55
+ 1. Always run `init-detect` to produce the baseline draft.
56
+ 2. If running inside Claude Code → `init-enrich` via **skill** backend.
57
+ 3. Else if `ANTHROPIC_API_KEY` present → `init-enrich` via **api** backend.
58
+ 4. Else → skip enrichment; render the deterministic baseline (today's quality, but
59
+ through the new annotated review screen).
60
+
61
+ ### Pillar B — Claude Code hook + per-directory toggle
62
+
63
+ - **SessionStart hook**, installed into `~/.claude/settings.json` by
64
+ `forgedock install`. On each CC session it resolves the cwd's state and acts:
65
+ - **managed + active** → inject ForgeDock context (a `forge.yaml` summary +
66
+ available commands); if config is missing or stale, offer a one-shot autopilot
67
+ init.
68
+ - **managed + opted-out** → completely silent (no output, no context injection).
69
+ - **unmanaged** → an optional, one-time, suppressible "Enable ForgeDock here?"
70
+ nudge.
71
+ - **State model** ("marker + global opt-out"):
72
+ - A directory is **managed** iff it contains `forge.yaml` **or** a lightweight
73
+ `.forgedock` marker (lets a project signal "ForgeDock-managed" before a full
74
+ config exists).
75
+ - A central `~/.claude/forgedock/registry.json` tracks explicit **per-directory
76
+ opt-out** plus last-seen metadata. Opt-out wins over the marker.
77
+ - **New CLI commands:** `forgedock enable [dir]`, `forgedock disable [dir]`,
78
+ `forgedock status [dir]`.
79
+ - **Known constraint (explicit, accepted):** Claude Code slash-commands install
80
+ **globally**, so "disable per dir" means the **hook stays silent and injects
81
+ nothing** in that directory — it does not physically remove or hide the global
82
+ commands. This is the achievable and intended semantics.
83
+
84
+ ### Pillar C — Idiot-proofing (cross-cutting)
85
+
86
+ - **Self-healing `validate`** — on a validation failure, the AI proposes (and, with
87
+ confirmation, applies) the fix instead of only printing an error.
88
+ - **Actionable preflight** — failures (`gh` missing, not authed, Node too old) show
89
+ copy-paste fix commands (partially exists today; standardize it).
90
+ - **Escape hatch** — `--manual` / `--verbose` runs the full guided wizard for power
91
+ users who *want* every prompt.
92
+
93
+ ## Components & interfaces (designed for isolation)
94
+
95
+ | Unit | Purpose | Depends on | Testable in isolation? |
96
+ |------|---------|-----------|------------------------|
97
+ | `init-detect` | Pure baseline `ConfigDraft` + per-field confidence | git, fs (read-only) | Yes — fixture repos → expected draft |
98
+ | `init-enrich` (skill) | AI enrichment inside CC | CC session, draft contract | Contract test + recorded fixture |
99
+ | `init-enrich` (api) | AI enrichment via Anthropic API | `ANTHROPIC_API_KEY`, draft contract | Contract test against same interface |
100
+ | `review-render` | Annotated review/accept/edit TUI | draft + confidence, tui.mjs | Yes — render snapshot from a draft |
101
+ | `registry` | Resolve dir state; read/write opt-out + markers | fs | Yes — state-resolution matrix |
102
+ | `session-hook` | Thin SessionStart script; emits context JSON | registry | Yes — simulated SessionStart payloads |
103
+
104
+ **`ConfigDraft` contract** (shared by detect → enrich → render): a structured
105
+ object mirroring `forge.yaml` sections, where each leaf is `{ value, confidence,
106
+ source, why }`. This is the single seam that lets the three enrichment backends and
107
+ the renderer stay decoupled.
108
+
109
+ ## Data flow (autopilot happy path)
110
+
111
+ ```
112
+ open `claude` in repo
113
+ → SessionStart hook fires
114
+ → registry resolves: managed (forge.yaml absent) + active
115
+ → hook suggests autopilot init (or user runs `forgedock init`)
116
+ → init-detect → baseline ConfigDraft + confidences
117
+ → init-enrich (skill) → enriched ConfigDraft
118
+ → review-render → annotated screen → Enter to accept
119
+ → write forge.yaml (with any # TODO(forgedock:…) flags)
120
+ → validate (self-healing on failure)
121
+ ```
122
+
123
+ ## Error handling
124
+
125
+ - **No git remote / detached repo:** `init-detect` lowers confidence, leaves fields
126
+ as `low`-confidence defaults with TODO flags; never crashes.
127
+ - **`gh` unauthenticated:** GitHub-derived sections (board, satellites) are skipped
128
+ with a flagged TODO and a copy-paste `gh auth login` hint.
129
+ - **Enrichment backend unavailable / API error:** fall through the backend-selection
130
+ ladder; worst case is the deterministic baseline through the review screen.
131
+ - **`forge.yaml` already exists:** existing backup-and-overwrite behavior is
132
+ preserved; review screen shows a diff-style "changed vs current."
133
+ - **Registry file missing/corrupt:** treat as empty opt-out set; the hook fails open
134
+ to silent (never blocks a CC session).
135
+
136
+ ## Testing strategy
137
+
138
+ - **`registry`:** unit-test the full state matrix — managed/unmanaged × opted-in/out
139
+ × `forge.yaml` present/absent.
140
+ - **`init-detect`:** fixture repos (node, python, rust, multi-repo) → expected
141
+ `ConfigDraft` snapshots.
142
+ - **`session-hook`:** feed simulated SessionStart payloads; assert silent vs
143
+ context-injecting vs nudge output.
144
+ - **`init-enrich`:** test the `ConfigDraft` contract both backends must satisfy;
145
+ pin one recorded enrichment fixture for regression.
146
+ - **`review-render`:** snapshot the annotated screen from a known draft.
147
+
148
+ ## Phasing (milestone → issues)
149
+
150
+ 1. **Foundation** — `registry` module + `enable`/`disable`/`status` commands +
151
+ marker handling. Unblocks the hook.
152
+ 2. **Hook** — SessionStart hook install into `settings.json` + context injection +
153
+ opt-out respect + unmanaged nudge.
154
+ 3. **Autopilot brain** — `init-detect` refactor (`ConfigDraft` + confidence) →
155
+ `init-enrich` skill upgrade → annotated `review-render` screen.
156
+ 4. **Resilience** — BYO-key `api` enrichment backend + self-healing `validate` +
157
+ `--manual` escape hatch.
158
+
159
+ All four phases ship in **one milestone**.
160
+
161
+ ## Out of scope / non-goals
162
+
163
+ - Physically hiding global slash-commands per directory (see Pillar B constraint).
164
+ - A standalone web onboarding UI.
165
+ - Multi-user / team-shared registries (registry is per-machine, user-level).
166
+
167
+ ## Open questions
168
+
169
+ - Exact confidence thresholds that trigger a TODO flag vs a silent default
170
+ (tune during Phase 3).
171
+ - Whether the unmanaged-directory nudge defaults to on or off (lean: off, opt-in via
172
+ a global setting) — decide in Phase 2.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forgedock",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "GitHub as a knowledge graph for AI agents. Autonomous dev pipeline for Claude Code.",
5
5
  "bin": {
6
6
  "forgedock": "bin/forgedock.mjs"
@@ -33,6 +33,7 @@
33
33
  },
34
34
  "files": [
35
35
  "bin/",
36
+ "!bin/tests/",
36
37
  "commands/",
37
38
  "docs/",
38
39
  "scripts/",
@@ -40,6 +41,9 @@
40
41
  "LICENSE",
41
42
  "README.md"
42
43
  ],
44
+ "scripts": {
45
+ "test": "node --test \"bin/tests/**/*.test.mjs\""
46
+ },
43
47
  "engines": {
44
48
  "node": ">=18"
45
49
  }
@@ -64,25 +64,31 @@ while read -r f; do
64
64
  done
65
65
  ROUTE_SEGMENT=$(echo "$URL_PATH" | sed 's|/api/v1/||; s|/.*||')
66
66
 
67
- # Check if next.config.js has a rewrite that might shadow this route
68
- if [ -f "$REPO_ROOT/web/next.config.js" ]; then
69
- # Use -F (fixed string) so ROUTE_SEGMENT is not interpreted as a regex pattern
70
- if grep -qF "$ROUTE_SEGMENT" "$REPO_ROOT/web/next.config.js" 2>/dev/null; then
71
- echo "WARNING: Route handler $f ($URL_PATH) — next.config.js references '$ROUTE_SEGMENT', may shadow this route"
72
- WARNINGS=$((WARNINGS + 1))
73
- else
74
- echo "OK: Route handler $f ($URL_PATH) — no shadowing rewrites found in next.config.js"
67
+ # Guard: skip shadow checks when ROUTE_SEGMENT is empty (e.g. /api/ root handlers).
68
+ # An empty ROUTE_SEGMENT causes grep -qF "" to match every line — false-positive WARNINGs.
69
+ if [ -z "$ROUTE_SEGMENT" ]; then
70
+ echo "OK: Route handler $f ($URL_PATH) — root handler, no segment to match, skipping shadow checks"
71
+ else
72
+ # Check if next.config.js has a rewrite that might shadow this route
73
+ if [ -f "$REPO_ROOT/web/next.config.js" ]; then
74
+ # Use -F (fixed string) so ROUTE_SEGMENT is not interpreted as a regex pattern
75
+ if grep -qF "$ROUTE_SEGMENT" "$REPO_ROOT/web/next.config.js" 2>/dev/null; then
76
+ echo "WARNING: Route handler $f ($URL_PATH) — next.config.js references '$ROUTE_SEGMENT', may shadow this route"
77
+ WARNINGS=$((WARNINGS + 1))
78
+ else
79
+ echo "OK: Route handler $f ($URL_PATH) — no shadowing rewrites found in next.config.js"
80
+ fi
75
81
  fi
76
- fi
77
82
 
78
- # Check if nginx routes this path to Next.js or directly to backend
79
- if [ -f "$REPO_ROOT/infra/nginx/nginx.conf" ]; then
80
- # Check for location blocks mentioning ROUTE_SEGMENT — use two -F searches to avoid
81
- # embedding the variable in a regex: first find lines with "location", then filter
82
- # for the literal segment. This avoids ROUTE_SEGMENT being interpreted as a BRE.
83
- if grep -F "location" "$REPO_ROOT/infra/nginx/nginx.conf" 2>/dev/null | grep -qF "$ROUTE_SEGMENT"; then
84
- echo "WARNING: Route handler $f ($URL_PATH) — nginx.conf has a location block for '$ROUTE_SEGMENT', may bypass Next.js"
85
- WARNINGS=$((WARNINGS + 1))
83
+ # Check if nginx routes this path to Next.js or directly to backend
84
+ if [ -f "$REPO_ROOT/infra/nginx/nginx.conf" ]; then
85
+ # Check for location blocks mentioning ROUTE_SEGMENT — use two -F searches to avoid
86
+ # embedding the variable in a regex: first find lines with "location", then filter
87
+ # for the literal segment. This avoids ROUTE_SEGMENT being interpreted as a BRE.
88
+ if grep -F "location" "$REPO_ROOT/infra/nginx/nginx.conf" 2>/dev/null | grep -qF "$ROUTE_SEGMENT"; then
89
+ echo "WARNING: Route handler $f ($URL_PATH) — nginx.conf has a location block for '$ROUTE_SEGMENT', may bypass Next.js"
90
+ WARNINGS=$((WARNINGS + 1))
91
+ fi
86
92
  fi
87
93
  fi
88
94
  done < <(echo "$FILES" | while IFS= read -r line; do