forge-dev 0.1.2__tar.gz → 0.1.4__tar.gz

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.
Files changed (35) hide show
  1. {forge_dev-0.1.2 → forge_dev-0.1.4}/.github/workflows/release-please.yml +1 -0
  2. {forge_dev-0.1.2 → forge_dev-0.1.4}/.gitignore +1 -0
  3. forge_dev-0.1.4/.release-please-manifest.json +3 -0
  4. {forge_dev-0.1.2 → forge_dev-0.1.4}/CHANGELOG.md +14 -0
  5. {forge_dev-0.1.2 → forge_dev-0.1.4}/PKG-INFO +48 -10
  6. {forge_dev-0.1.2 → forge_dev-0.1.4}/README.md +47 -9
  7. {forge_dev-0.1.2 → forge_dev-0.1.4}/forge_core/cli.py +227 -9
  8. {forge_dev-0.1.2 → forge_dev-0.1.4}/forge_core/registry.py +21 -9
  9. {forge_dev-0.1.2 → forge_dev-0.1.4}/pyproject.toml +1 -1
  10. forge_dev-0.1.2/.release-please-manifest.json +0 -3
  11. forge_dev-0.1.2/CLAUDE.md +0 -81
  12. {forge_dev-0.1.2 → forge_dev-0.1.4}/.github/workflows/publish.yml +0 -0
  13. {forge_dev-0.1.2 → forge_dev-0.1.4}/.release-please-config.json +0 -0
  14. {forge_dev-0.1.2 → forge_dev-0.1.4}/claude_skill/SKILL.md +0 -0
  15. {forge_dev-0.1.2 → forge_dev-0.1.4}/forge_core/__init__.py +0 -0
  16. {forge_dev-0.1.2 → forge_dev-0.1.4}/forge_core/agents/__init__.py +0 -0
  17. {forge_dev-0.1.2 → forge_dev-0.1.4}/forge_core/auditor.py +0 -0
  18. {forge_dev-0.1.2 → forge_dev-0.1.4}/forge_core/detector.py +0 -0
  19. {forge_dev-0.1.2 → forge_dev-0.1.4}/forge_core/editor_bridge.py +0 -0
  20. {forge_dev-0.1.2 → forge_dev-0.1.4}/forge_core/models.py +0 -0
  21. {forge_dev-0.1.2 → forge_dev-0.1.4}/forge_core/phases/__init__.py +0 -0
  22. {forge_dev-0.1.2 → forge_dev-0.1.4}/forge_core/phases/coherence.py +0 -0
  23. {forge_dev-0.1.2 → forge_dev-0.1.4}/forge_core/phases/context.py +0 -0
  24. {forge_dev-0.1.2 → forge_dev-0.1.4}/forge_core/phases/intake.py +0 -0
  25. {forge_dev-0.1.2 → forge_dev-0.1.4}/forge_core/standards/api-first-design.yaml +0 -0
  26. {forge_dev-0.1.2 → forge_dev-0.1.4}/forge_core/standards/microservice-packaging.yaml +0 -0
  27. {forge_dev-0.1.2 → forge_dev-0.1.4}/forge_core/standards/observability.yaml +0 -0
  28. {forge_dev-0.1.2 → forge_dev-0.1.4}/forge_core/standards/security-baseline.yaml +0 -0
  29. {forge_dev-0.1.2 → forge_dev-0.1.4}/forge_core/standards/type-safety.yaml +0 -0
  30. {forge_dev-0.1.2 → forge_dev-0.1.4}/forge_core/templates/__init__.py +0 -0
  31. {forge_dev-0.1.2 → forge_dev-0.1.4}/forge_core/utils/__init__.py +0 -0
  32. {forge_dev-0.1.2 → forge_dev-0.1.4}/mcp_server/__init__.py +0 -0
  33. {forge_dev-0.1.2 → forge_dev-0.1.4}/mcp_server/server.py +0 -0
  34. {forge_dev-0.1.2 → forge_dev-0.1.4}/references/implementation-phases.md +0 -0
  35. {forge_dev-0.1.2 → forge_dev-0.1.4}/setup.sh +0 -0
@@ -15,5 +15,6 @@ jobs:
15
15
  steps:
16
16
  - uses: googleapis/release-please-action@v4
17
17
  with:
18
+ token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
18
19
  config-file: .release-please-config.json
19
20
  manifest-file: .release-please-manifest.json
@@ -12,3 +12,4 @@ build/
12
12
  venv/
13
13
  .env
14
14
  *.log
15
+ CLAUDE.md
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.1.4"
3
+ }
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.4](https://github.com/luiskcr/forge/compare/v0.1.3...v0.1.4) (2026-04-12)
4
+
5
+
6
+ ### Features
7
+
8
+ * refresh flow for already-initialized projects + config commands ([fae7242](https://github.com/luiskcr/forge/commit/fae7242072b5dfa6ae23c40bd601b906cc122ab2))
9
+
10
+ ## [0.1.3](https://github.com/luiskcr/forge/compare/v0.1.2...v0.1.3) (2026-04-12)
11
+
12
+
13
+ ### Documentation
14
+
15
+ * recommend uv as install method and document macOS Tahoe pyexpat issue ([292518a](https://github.com/luiskcr/forge/commit/292518a01ca7d546fa892a4618cced8033290524))
16
+
3
17
  ## [0.1.2](https://github.com/luiskcr/forge/compare/v0.1.1...v0.1.2) (2026-04-12)
4
18
 
5
19
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forge-dev
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: AI-Native Development Workflow Engine
5
5
  Author: NaiaTech
6
6
  License-Expression: MIT
@@ -48,35 +48,73 @@ Forge encodes a complete development paradigm — from receiving a vague require
48
48
 
49
49
  ## Installation
50
50
 
51
+ Forge is published on PyPI as `forge-dev`. The recommended way to install it is with [`uv`](https://github.com/astral-sh/uv), which manages its own Python and isolates CLI tools automatically:
52
+
53
+ ```bash
54
+ # 1. Install uv (one-time, if you don't have it)
55
+ curl -LsSf https://astral.sh/uv/install.sh | sh # macOS / Linux
56
+ # Windows PowerShell:
57
+ # powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
58
+
59
+ # 2. Install forge-dev as a global tool
60
+ uv tool install forge-dev
61
+
62
+ # 3. Verify
63
+ forge --version
64
+ ```
65
+
66
+ <details>
67
+ <summary>Alternative: <code>pipx</code></summary>
68
+
69
+ ```bash
70
+ pipx install forge-dev
71
+ forge --version
72
+ ```
73
+
74
+ </details>
75
+
76
+ <details>
77
+ <summary>Alternative: plain <code>pip</code></summary>
78
+
51
79
  ```bash
52
80
  pip install forge-dev
53
81
  ```
54
82
 
55
- Or install from source:
83
+ </details>
84
+
85
+ <details>
86
+ <summary>From source (for contributors)</summary>
56
87
 
57
88
  ```bash
58
89
  git clone https://github.com/luiskcr/forge.git
59
90
  cd forge
60
- pip install -e .
91
+ pip install -e ".[dev]"
61
92
  ```
62
93
 
63
- Requires Python ≥ 3.11.
94
+ </details>
95
+
96
+ Requires Python ≥ 3.11. If you use `uv`, it will download a compatible Python automatically.
64
97
 
65
98
  ### Updating
66
99
 
100
+ Use the same tool you installed with:
101
+
67
102
  ```bash
68
- pip install --upgrade forge-dev # latest stable
69
- pip install --upgrade forge-dev==0.1.2 # pin a specific version
70
- forge --version # verify
103
+ uv tool upgrade forge-dev # if installed via uv (recommended)
104
+ pipx upgrade forge-dev # if installed via pipx
105
+ pip install --upgrade forge-dev # if installed via pip
106
+ forge --version # verify the new version
71
107
  ```
72
108
 
73
- If you installed with `pipx`:
109
+ To pin a specific version:
74
110
 
75
111
  ```bash
76
- pipx upgrade forge-dev
112
+ uv tool install forge-dev==0.1.2 --force
77
113
  ```
78
114
 
79
- > Homebrew is not supported yet — Forge is distributed exclusively via PyPI.
115
+ > **Homebrew is not supported.** Forge is distributed exclusively via PyPI.
116
+
117
+ > **Note for macOS users on Tahoe (macOS 26):** the Homebrew bottles for `python@3.13` and `python@3.14` currently ship a broken `pyexpat` module (a `libexpat` symbol mismatch) that breaks `pip`, `pipx` and `venv`. If you hit `ImportError: Symbol not found: _XML_SetAllocTrackerActivationThreshold`, use `uv` instead — it bundles its own Python and avoids the issue entirely.
80
118
 
81
119
  ## How Forge Works
82
120
 
@@ -19,35 +19,73 @@ Forge encodes a complete development paradigm — from receiving a vague require
19
19
 
20
20
  ## Installation
21
21
 
22
+ Forge is published on PyPI as `forge-dev`. The recommended way to install it is with [`uv`](https://github.com/astral-sh/uv), which manages its own Python and isolates CLI tools automatically:
23
+
24
+ ```bash
25
+ # 1. Install uv (one-time, if you don't have it)
26
+ curl -LsSf https://astral.sh/uv/install.sh | sh # macOS / Linux
27
+ # Windows PowerShell:
28
+ # powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
29
+
30
+ # 2. Install forge-dev as a global tool
31
+ uv tool install forge-dev
32
+
33
+ # 3. Verify
34
+ forge --version
35
+ ```
36
+
37
+ <details>
38
+ <summary>Alternative: <code>pipx</code></summary>
39
+
40
+ ```bash
41
+ pipx install forge-dev
42
+ forge --version
43
+ ```
44
+
45
+ </details>
46
+
47
+ <details>
48
+ <summary>Alternative: plain <code>pip</code></summary>
49
+
22
50
  ```bash
23
51
  pip install forge-dev
24
52
  ```
25
53
 
26
- Or install from source:
54
+ </details>
55
+
56
+ <details>
57
+ <summary>From source (for contributors)</summary>
27
58
 
28
59
  ```bash
29
60
  git clone https://github.com/luiskcr/forge.git
30
61
  cd forge
31
- pip install -e .
62
+ pip install -e ".[dev]"
32
63
  ```
33
64
 
34
- Requires Python ≥ 3.11.
65
+ </details>
66
+
67
+ Requires Python ≥ 3.11. If you use `uv`, it will download a compatible Python automatically.
35
68
 
36
69
  ### Updating
37
70
 
71
+ Use the same tool you installed with:
72
+
38
73
  ```bash
39
- pip install --upgrade forge-dev # latest stable
40
- pip install --upgrade forge-dev==0.1.2 # pin a specific version
41
- forge --version # verify
74
+ uv tool upgrade forge-dev # if installed via uv (recommended)
75
+ pipx upgrade forge-dev # if installed via pipx
76
+ pip install --upgrade forge-dev # if installed via pip
77
+ forge --version # verify the new version
42
78
  ```
43
79
 
44
- If you installed with `pipx`:
80
+ To pin a specific version:
45
81
 
46
82
  ```bash
47
- pipx upgrade forge-dev
83
+ uv tool install forge-dev==0.1.2 --force
48
84
  ```
49
85
 
50
- > Homebrew is not supported yet — Forge is distributed exclusively via PyPI.
86
+ > **Homebrew is not supported.** Forge is distributed exclusively via PyPI.
87
+
88
+ > **Note for macOS users on Tahoe (macOS 26):** the Homebrew bottles for `python@3.13` and `python@3.14` currently ship a broken `pyexpat` module (a `libexpat` symbol mismatch) that breaks `pip`, `pipx` and `venv`. If you hit `ImportError: Symbol not found: _XML_SetAllocTrackerActivationThreshold`, use `uv` instead — it bundles its own Python and avoids the issue entirely.
51
89
 
52
90
  ## How Forge Works
53
91
 
@@ -28,22 +28,127 @@ def main() -> None:
28
28
  pass
29
29
 
30
30
 
31
+ def _refresh_initialized_project(project_path: Path, detection) -> None:
32
+ """Refresh an already-initialized project.
33
+
34
+ Called when `forge init` runs on a directory that already has `.forge/`.
35
+ Keeps the existing context as the source of truth, but:
36
+ - Tops up any UNSPECIFIED fields that the current detector can now resolve
37
+ (e.g., the user added a main.bicep since last init)
38
+ - Regenerates the Claude Code skill and discovery prompt (Forge-owned)
39
+ - Generates CLAUDE.md if it was deleted, but never overwrites a custom one
40
+ - Ensures .forge/overrides/ exists
41
+ """
42
+ from forge_core.editor_bridge import collect_unspecified_fields, write_editor_file
43
+ from forge_core.phases.context import load_context, save_context
44
+
45
+ ctx = load_context(project_path)
46
+ if ctx is None:
47
+ console.print(
48
+ "[red].forge/context.yaml is missing or unreadable. "
49
+ "Run `forge init --force` to recreate.[/red]"
50
+ )
51
+ return
52
+
53
+ # Top up unspecified fields from current detection
54
+ detected = detection.detected_stack or {}
55
+ topped_up: list[str] = []
56
+ mapping = {
57
+ "cloud": ("cloud", ctx.cloud.value),
58
+ "backend": ("backend", ctx.backend.value),
59
+ "frontend": ("frontend", ctx.frontend.value),
60
+ "database": ("database", ctx.database.value),
61
+ "auth": ("auth", ctx.auth.value),
62
+ }
63
+ ctx_data = ctx.model_dump(mode="json")
64
+ for det_key, (ctx_key, current) in mapping.items():
65
+ if current == "unspecified" and det_key in detected:
66
+ ctx_data[ctx_key] = detected[det_key]
67
+ topped_up.append(f"{ctx_key}={detected[det_key]}")
68
+ # CICD nested
69
+ if ctx_data.get("cicd", {}).get("iac") == "unspecified" and "iac" in detected:
70
+ ctx_data["cicd"]["iac"] = detected["iac"]
71
+ topped_up.append(f"cicd.iac={detected['iac']}")
72
+ if ctx_data.get("cicd", {}).get("provider") == "unspecified" and "cicd" in detected:
73
+ ctx_data["cicd"]["provider"] = detected["cicd"]
74
+ topped_up.append(f"cicd.provider={detected['cicd']}")
75
+
76
+ if topped_up:
77
+ from forge_core.models import ProjectContext
78
+ try:
79
+ ctx = ProjectContext(**ctx_data)
80
+ save_context(ctx, project_path)
81
+ except Exception as exc:
82
+ console.print(f"[red]Could not persist detected fields: {exc}[/red]")
83
+
84
+ # Ensure overrides/
85
+ overrides_dir = project_path / ".forge" / "overrides"
86
+ overrides_dir.mkdir(parents=True, exist_ok=True)
87
+
88
+ # Regenerate editor files
89
+ claude_path = project_path / "CLAUDE.md"
90
+ if not claude_path.exists():
91
+ write_editor_file(project_path, ctx, "claude")
92
+ claude_status = "[green]generated[/green]"
93
+ else:
94
+ # Don't overwrite a customized CLAUDE.md, but refresh OTHER editor files
95
+ # that already exist (cursor, copilot, etc.)
96
+ from forge_core.editor_bridge import sync_editor_file
97
+ sync_editor_file(project_path, ctx)
98
+ claude_status = "[dim]kept (customized or existing)[/dim]"
99
+
100
+ write_editor_file(project_path, ctx, "skill")
101
+
102
+ remaining = collect_unspecified_fields(ctx)
103
+ discovery_file = project_path / ".forge" / "discovery_prompt.md"
104
+ if remaining:
105
+ write_editor_file(project_path, ctx, "discovery")
106
+ elif discovery_file.exists():
107
+ discovery_file.unlink()
108
+
109
+ # Summary
110
+ lines = [
111
+ "[green]✓ Forge refreshed[/green]",
112
+ "",
113
+ f"Project: [bold]{ctx.name}[/bold]",
114
+ f"CLAUDE.md: {claude_status}",
115
+ ]
116
+ if topped_up:
117
+ lines.append(f"[green]Detected new:[/green] {', '.join(topped_up)}")
118
+ if remaining:
119
+ fields = ", ".join(f"`{r['key']}`" for r in remaining)
120
+ lines.append(f"[yellow]Still unspecified:[/yellow] {fields}")
121
+ lines.append("Read `.forge/discovery_prompt.md` in your AI editor.")
122
+ else:
123
+ lines.append("[green]All context fields resolved.[/green]")
124
+ lines.append("")
125
+ lines.append("Tip: use `forge init --force` to wipe context and re-run from scratch.")
126
+
127
+ console.print(Panel("\n".join(lines), title="🔄 Refreshed", border_style="green"))
128
+
129
+
31
130
  @main.command()
32
131
  @click.option("--name", "-n", help="Project name (default: directory name)")
33
132
  @click.option("--backend", "-b", help="Backend framework override")
34
133
  @click.option("--no-interactive", is_flag=True, help="Skip interactive questions")
35
- def init(name: str | None, backend: str | None, no_interactive: bool) -> None:
134
+ @click.option(
135
+ "--force",
136
+ is_flag=True,
137
+ help="Wipe existing .forge/context.yaml and re-run init from scratch",
138
+ )
139
+ def init(name: str | None, backend: str | None, no_interactive: bool, force: bool) -> None:
36
140
  """Initialize Forge in the current directory.
37
-
141
+
38
142
  Detects the project state and adapts:
39
143
  - Empty folder → guided setup conversation
40
144
  - Has docs → analyzes documents, generates brief
41
145
  - Has code → detects stack, offers assessment
42
- - Has .forge/ → offers to continue or re-evaluate
146
+ - Has .forge/ → refresh editor files + re-run detection (use --force to reset)
43
147
  """
44
148
  from forge_core.detector import detect_project
45
149
  from forge_core.phases.context import (
46
150
  get_questions_for_empty_project,
151
+ load_context,
47
152
  resolve_context,
48
153
  save_context,
49
154
  )
@@ -65,14 +170,29 @@ def init(name: str | None, backend: str | None, no_interactive: bool) -> None:
65
170
  ))
66
171
  console.print()
67
172
 
68
- if detection.has_forge:
69
- console.print("[yellow]This project already has Forge initialized.[/yellow]")
70
- console.print("Options:")
71
- console.print(" forge status — see current state")
72
- console.print(" forge assess — re-evaluate against latest standards")
73
- console.print(" forge audit — run audit agents")
173
+ if detection.has_forge and not force:
174
+ # Refresh mode: keep existing context, top up any newly detectable fields,
175
+ # regenerate editor files (skill, discovery prompt, CLAUDE.md if missing).
176
+ _refresh_initialized_project(project_path, detection)
74
177
  return
75
178
 
179
+ if detection.has_forge and force:
180
+ # Wipe context.yaml so the rest of the function proceeds as a fresh init.
181
+ (project_path / ".forge" / "context.yaml").unlink(missing_ok=True)
182
+ console.print("[yellow]--force: existing .forge/context.yaml removed.[/yellow]")
183
+ detection.has_forge = False
184
+ detection.forge_context_path = None
185
+ # Re-evaluate state so the branching below picks the right path
186
+ if detection.code_files or detection.config_files:
187
+ from forge_core.models import ProjectState
188
+ detection.state = ProjectState.HAS_CODE
189
+ elif detection.doc_files:
190
+ from forge_core.models import ProjectState
191
+ detection.state = ProjectState.HAS_DOCS
192
+ else:
193
+ from forge_core.models import ProjectState
194
+ detection.state = ProjectState.EMPTY
195
+
76
196
  if detection.is_empty:
77
197
  console.print("[cyan]Empty directory detected. Let's set up your project.[/cyan]")
78
198
  console.print()
@@ -633,6 +753,104 @@ def audit(files: tuple[str, ...], full: bool) -> None:
633
753
  )
634
754
 
635
755
 
756
+ @main.group()
757
+ def config() -> None:
758
+ """Manage the global Forge user config (~/.forge/user/config.yaml)."""
759
+ pass
760
+
761
+
762
+ @config.command("show")
763
+ def config_show() -> None:
764
+ """Print the current global user config."""
765
+ import yaml as _yaml
766
+
767
+ from forge_core.registry import USER_DIR
768
+
769
+ config_path = USER_DIR / "config.yaml"
770
+ if not config_path.exists():
771
+ console.print(f"[yellow]No config at {config_path}[/yellow]")
772
+ return
773
+ console.print(_yaml.dump(_yaml.safe_load(config_path.read_text()), sort_keys=False))
774
+
775
+
776
+ @config.command("reset")
777
+ @click.option("--yes", is_flag=True, help="Skip confirmation")
778
+ def config_reset(yes: bool) -> None:
779
+ """Regenerate ~/.forge/user/config.yaml with current defaults.
780
+
781
+ Use this after upgrading Forge if you're seeing stale options (e.g. the
782
+ config was written by an older version with hardcoded defaults like
783
+ `iac: pulumi`). The new defaults leave all stack fields as `unspecified`,
784
+ so you decide per-project or via `forge config set`.
785
+ """
786
+ from forge_core.registry import USER_DIR, _write_default_user_config # type: ignore
787
+
788
+ config_path = USER_DIR / "config.yaml"
789
+ if config_path.exists() and not yes:
790
+ if not click.confirm(
791
+ f"Overwrite {config_path} with the current Forge defaults?",
792
+ default=False,
793
+ ):
794
+ console.print("[yellow]Cancelled.[/yellow]")
795
+ return
796
+
797
+ config_path.parent.mkdir(parents=True, exist_ok=True)
798
+ _write_default_user_config(config_path)
799
+ console.print(f"[green]✓ {config_path} regenerated.[/green]")
800
+ console.print(
801
+ "[dim]All stack fields are now `unspecified` — project init will "
802
+ "ask you or detect them from the code.[/dim]"
803
+ )
804
+
805
+
806
+ @config.command("set")
807
+ @click.argument("assignments", nargs=-1, required=True)
808
+ def config_set(assignments: tuple[str, ...]) -> None:
809
+ """Set global defaults: `forge config set cloud=azure cicd.iac=bicep`."""
810
+ import yaml as _yaml
811
+
812
+ from forge_core.registry import USER_DIR
813
+
814
+ config_path = USER_DIR / "config.yaml"
815
+ data = _yaml.safe_load(config_path.read_text()) if config_path.exists() else {}
816
+
817
+ updated: list[str] = []
818
+ for assignment in assignments:
819
+ if "=" not in assignment:
820
+ console.print(f"[red]✗ '{assignment}' — expected key=value[/red]")
821
+ continue
822
+ key, raw = assignment.split("=", 1)
823
+ key = key.strip()
824
+ value: object = raw.strip()
825
+ if value in ("true", "True"):
826
+ value = True
827
+ elif value in ("false", "False"):
828
+ value = False
829
+ parts = key.split(".")
830
+ target = data
831
+ for p in parts[:-1]:
832
+ target = target.setdefault(p, {})
833
+ target[parts[-1]] = value
834
+ updated.append(f"{key}={raw}")
835
+
836
+ if not updated:
837
+ sys.exit(1)
838
+
839
+ # Validate by loading through UserConfig
840
+ from forge_core.models import UserConfig
841
+ try:
842
+ UserConfig(**data)
843
+ except Exception as exc:
844
+ console.print(f"[red]✗ Validation failed:[/red] {exc}")
845
+ sys.exit(1)
846
+
847
+ config_path.parent.mkdir(parents=True, exist_ok=True)
848
+ with open(config_path, "w") as f:
849
+ _yaml.dump(data, f, default_flow_style=False, sort_keys=False)
850
+ for u in updated:
851
+ console.print(f"[green]✓ {u}[/green]")
852
+
853
+
636
854
  @main.group()
637
855
  def context() -> None:
638
856
  """Inspect or update the project context (.forge/context.yaml).
@@ -186,16 +186,28 @@ def version_workflow(change_description: str) -> str:
186
186
  # ── Private helpers ────────────────────────────────────────────────────────
187
187
 
188
188
  def _write_default_user_config(path: Path) -> None:
189
- """Write the default user config with Luis's preferences."""
189
+ """Write the default user config with no opinionated choices.
190
+
191
+ All stack fields default to UNSPECIFIED so Forge never silently assumes
192
+ a cloud / IaC / auth / database choice. The user sets their preferences
193
+ explicitly with `forge config set key=value` (or by editing this file).
194
+
195
+ The non-stack fields (observability, api, ai) keep reasonable technical
196
+ defaults that don't lock the user into a vendor.
197
+ """
190
198
  config = UserConfig(
191
- cloud="azure",
199
+ cloud="unspecified",
192
200
  backend=None, # Ask each time
193
- frontend="react",
194
- database="postgresql",
195
- auth="azure-ad-b2c",
196
- ai={"enabled": True, "providers": ["anthropic", "openai"], "observability": True},
201
+ frontend="unspecified",
202
+ database="unspecified",
203
+ auth="unspecified",
204
+ ai={
205
+ "enabled": False,
206
+ "providers": [],
207
+ "observability": True,
208
+ },
197
209
  observability={
198
- "apm": "azure-app-insights",
210
+ "apm": "unspecified",
199
211
  "metrics": "prometheus",
200
212
  "logs": "loki",
201
213
  "dashboards": "grafana",
@@ -207,8 +219,8 @@ def _write_default_user_config(path: Path) -> None:
207
219
  "versioning": "url-prefix",
208
220
  },
209
221
  cicd={
210
- "provider": "github-actions",
211
- "iac": "pulumi",
222
+ "provider": "unspecified",
223
+ "iac": "unspecified",
212
224
  "environments": ["dev", "staging", "production"],
213
225
  },
214
226
  standards={
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "forge-dev"
7
- version = "0.1.2"
7
+ version = "0.1.4"
8
8
  description = "AI-Native Development Workflow Engine"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,3 +0,0 @@
1
- {
2
- ".": "0.1.2"
3
- }
forge_dev-0.1.2/CLAUDE.md DELETED
@@ -1,81 +0,0 @@
1
- # CLAUDE.md
2
-
3
- This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
-
5
- ## Project
6
-
7
- Forge is an **AI-Native Development Workflow Engine** — a Python CLI + MCP server that transforms requirements into production-ready code. It generates prompts and structured artifacts (briefs, plans, audit reports) that are consumed by LLM-backed editors (Claude Code, Cursor, Copilot). Forge itself does not call LLMs; it prepares and orchestrates the context.
8
-
9
- Published to PyPI as `forge-dev`. Installed binary is `forge`.
10
-
11
- ## Commands
12
-
13
- ```bash
14
- # Install for local dev (editable)
15
- pip install -e ".[dev]"
16
-
17
- # Run the CLI
18
- forge --help
19
- forge init # initialize .forge/ in cwd
20
- forge status # show project state
21
- forge sync --format claude # generate CLAUDE.md from Forge governance
22
- forge audit --full # build audit prompt for entire project
23
- forge intake <file> # process requirement doc into a brief
24
-
25
- # Run the MCP server
26
- python -m mcp_server.server
27
-
28
- # Quality gates
29
- ruff check .
30
- ruff format .
31
- mypy forge_core mcp_server
32
- pytest # testpaths = ["tests"] (no tests yet)
33
- pytest tests/path/to/test.py::test_name # single test
34
-
35
- # Release (trusted publishing via GitHub Actions)
36
- # 1. bump version in forge_core/__init__.py AND pyproject.toml
37
- # 2. gh release create vX.Y.Z --title "..." --notes "..."
38
- # → .github/workflows/publish.yml builds and publishes to PyPI automatically
39
- ```
40
-
41
- Python ≥3.11, ruff line-length 100, mypy strict mode.
42
-
43
- ## Architecture
44
-
45
- Forge has **three persistence layers** — understand these before touching code that reads/writes state:
46
-
47
- 1. **`~/.forge/`** — global registry (managed by `forge_core/registry.py`)
48
- - `core/`: upstream definitions (phases, agents, templates, standards) — updated via `forge upgrade`
49
- - `user/`: user customizations that must survive upgrades (standards, patterns, anti-patterns, mcps.yaml, config.yaml, history)
50
- - `versions/`: workflow version snapshots
51
- - **Invariant**: `forge upgrade` must never touch `user/`; project `.forge/` dirs are never touched by any global operation.
52
-
53
- 2. **`<project>/.forge/`** — per-project state
54
- - `context.yaml` (stack/cloud/auth decisions), `brief.yaml`, `plan.yaml`, `journal.md`, `audit/*.md`, `maturity.yaml`
55
- - Created by `forge init`, read by every other command.
56
-
57
- 3. **Editor bridge output** — `CLAUDE.md`, `.cursorrules`, etc. generated from (1)+(2) by `forge sync`. This is the *delivery mechanism* — Forge's entire value is translating governance into something the editor reads automatically.
58
-
59
- ### Module layout
60
-
61
- - `forge_core/cli.py` — Click command group; every subcommand lazy-imports its dependencies to keep `forge --help` fast. Each command is thin glue that calls into phases/auditor/editor_bridge.
62
- - `forge_core/registry.py` — sole owner of `~/.forge/`. Everything that reads or writes the global registry goes through here (`ensure_registry`, `load_user_config`, `load_standards`, `load_mcps`, `record_project`, `version_workflow`).
63
- - `forge_core/detector.py` — inspects a directory and classifies it (`is_empty` / `has_docs` / `has_existing_code` / `has_forge`). Drives the branching in `forge init`.
64
- - `forge_core/models.py` — Pydantic models for context, briefs, standards, MCPs, enums (Backend, Cloud, Auth, Regulatory, etc.). Serialization contract between all layers; breaking these changes all persisted state.
65
- - `forge_core/phases/` — one module per workflow phase. Each phase produces an artifact in `.forge/`:
66
- - `context.py` → `context.yaml` (+ `get_questions_for_empty_project`, `resolve_context`)
67
- - `intake.py` → `brief.yaml` (+ classification, prompt builder)
68
- - `coherence.py` → validates new standards against existing ones before they're added
69
- - `forge_core/auditor.py` — builds audit prompts (file-scoped and full-project) from context + standards. Does not execute audits; writes prompts to `.forge/audit/` for the LLM editor to consume.
70
- - `forge_core/editor_bridge.py` — central translator: (context + standards + patterns + journal) → editor-specific instruction file. Add new editor formats here; `cli.py sync` just dispatches by format name.
71
- - `forge_core/standards/*.yaml` — shipped baseline standards (security, observability, type-safety, api-first, microservice-packaging). These seed `~/.forge/core/standards/` on first run.
72
- - `mcp_server/server.py` — `FastMCP` wrapper exposing the same operations as MCP tools (`forge_init`, `forge_intake`, `forge_audit`, etc.). Input models use `extra="forbid"`; mirror CLI semantics exactly.
73
- - `claude_skill/SKILL.md` — Claude Code skill definition that teaches the agent when to invoke Forge.
74
-
75
- ### Key design rules
76
-
77
- - **Forge builds prompts; LLMs run them.** Commands like `intake`, `audit`, `assess` write a `*_prompt.md` file rather than calling an LLM. Don't add LLM client code to `forge_core`.
78
- - **Phases write artifacts, commands are glue.** If you're tempted to put logic in `cli.py`, it probably belongs in a phase module or `editor_bridge.py`.
79
- - **Registry paths live in `registry.py` only.** Don't hardcode `~/.forge/...` elsewhere; import `FORGE_HOME`, `CORE_DIR`, `USER_DIR`.
80
- - **CLI imports are lazy inside command bodies** (see `cli.py`) — preserve this to keep startup fast. Don't hoist imports to module top.
81
- - **Standards are YAML, loaded by name.** Adding a new built-in standard = drop a YAML in `forge_core/standards/` matching the existing shape; no code change.
File without changes