ai-forge-cli 0.1.2__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 (32) hide show
  1. ai_forge_cli-0.1.2/LICENSE +21 -0
  2. ai_forge_cli-0.1.2/PKG-INFO +8 -0
  3. ai_forge_cli-0.1.2/README.md +286 -0
  4. ai_forge_cli-0.1.2/pyproject.toml +21 -0
  5. ai_forge_cli-0.1.2/setup.cfg +4 -0
  6. ai_forge_cli-0.1.2/src/ai_forge_cli.egg-info/PKG-INFO +8 -0
  7. ai_forge_cli-0.1.2/src/ai_forge_cli.egg-info/SOURCES.txt +30 -0
  8. ai_forge_cli-0.1.2/src/ai_forge_cli.egg-info/dependency_links.txt +1 -0
  9. ai_forge_cli-0.1.2/src/ai_forge_cli.egg-info/entry_points.txt +2 -0
  10. ai_forge_cli-0.1.2/src/ai_forge_cli.egg-info/requires.txt +1 -0
  11. ai_forge_cli-0.1.2/src/ai_forge_cli.egg-info/top_level.txt +1 -0
  12. ai_forge_cli-0.1.2/src/cli/__init__.py +2 -0
  13. ai_forge_cli-0.1.2/src/cli/__main__.py +4 -0
  14. ai_forge_cli-0.1.2/src/cli/bundle.py +117 -0
  15. ai_forge_cli-0.1.2/src/cli/commands/__init__.py +28 -0
  16. ai_forge_cli-0.1.2/src/cli/commands/base.py +26 -0
  17. ai_forge_cli-0.1.2/src/cli/commands/context.py +66 -0
  18. ai_forge_cli-0.1.2/src/cli/commands/find.py +122 -0
  19. ai_forge_cli-0.1.2/src/cli/commands/init.py +447 -0
  20. ai_forge_cli-0.1.2/src/cli/commands/inspect.py +111 -0
  21. ai_forge_cli-0.1.2/src/cli/commands/list_cmd.py +72 -0
  22. ai_forge_cli-0.1.2/src/cli/commands/update.py +78 -0
  23. ai_forge_cli-0.1.2/src/cli/common.py +120 -0
  24. ai_forge_cli-0.1.2/src/cli/forge.py +65 -0
  25. ai_forge_cli-0.1.2/src/cli/index.py +156 -0
  26. ai_forge_cli-0.1.2/src/cli/walker.py +799 -0
  27. ai_forge_cli-0.1.2/tests/test_cli.py +134 -0
  28. ai_forge_cli-0.1.2/tests/test_find.py +113 -0
  29. ai_forge_cli-0.1.2/tests/test_index.py +63 -0
  30. ai_forge_cli-0.1.2/tests/test_init.py +141 -0
  31. ai_forge_cli-0.1.2/tests/test_update.py +70 -0
  32. ai_forge_cli-0.1.2/tests/test_walker.py +128 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 William James
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: ai-forge-cli
3
+ Version: 0.1.2
4
+ Summary: Context walker for the Forge L0-L5 spec system
5
+ Requires-Python: >=3.11
6
+ License-File: LICENSE
7
+ Requires-Dist: pyyaml>=6.0
8
+ Dynamic: license-file
@@ -0,0 +1,286 @@
1
+ <div align="center">
2
+
3
+ ```
4
+ ███████╗ ██████╗ ██████╗ ██████╗ ███████╗
5
+ ██╔════╝██╔═══██╗██╔══██╗██╔════╝ ██╔════╝
6
+ █████╗ ██║ ██║██████╔╝██║ ███╗█████╗
7
+ ██╔══╝ ██║ ██║██╔══██╗██║ ██║██╔══╝
8
+ ██║ ╚██████╔╝██║ ██║╚██████╔╝███████╗
9
+ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝
10
+ ```
11
+
12
+ **Spec-driven agent development.**
13
+ The agent drives the interview. The specs drive the code.
14
+
15
+ [![CI](https://github.com/GreyFlames07/forge/actions/workflows/ci.yml/badge.svg)](https://github.com/GreyFlames07/forge/actions/workflows/ci.yml)
16
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
17
+
18
+ </div>
19
+
20
+ ---
21
+
22
+ ## What it is
23
+
24
+ A six-layer YAML spec system, a Python CLI for context assembly, and **ten agent skills** that take a human from a vague product idea to working, audited, hardened, validated code — with the agent asking questions and the human answering, not the reverse.
25
+
26
+ Built on the premise that **people explain systems well under questioning but poorly when cold-prompted**. forge inverts the default "human prompts agent → agent implements" loop into "agent interviews human → structured spec emerges → agent implements from spec".
27
+
28
+ Runs in **Claude Code**, **OpenAI Codex CLI**, and any **agentskills.io-compatible client** (VS Code Copilot, Cursor).
29
+
30
+ ---
31
+
32
+ ## The pipeline
33
+
34
+ ```
35
+ idea
36
+
37
+ forge-discover → foundation (modules, L0 vocab, L1 conventions, L5 posture)
38
+
39
+ forge-decompose → atom inventory (stub files, module populated, entry-point hints)
40
+
41
+ forge-atom → complete specs (one atom at a time — three interview shapes)
42
+
43
+ forge-compose → L4 composition (flows + journeys from completed atoms)
44
+
45
+ forge-audit → quality gate (seven audit passes, severity-ranked findings)
46
+
47
+ forge-armour → security hardening (trust model, policies, abuse-case review)
48
+
49
+ forge-implement → code + tests (parallel subagents, test-before-impl isolation)
50
+
51
+ forge-validate → validation report (static analysis, test mapping, live interaction probes)
52
+
53
+ working system
54
+ ```
55
+
56
+ Each skill is a markdown directive file (agent-facing) plus a longer framework doc (human reference). Each uses the `forge` CLI to sense project state and load context — agents don't inline spec content in prompts.
57
+
58
+ ---
59
+
60
+ ## Install
61
+
62
+ ### Fresh machine — four commands
63
+
64
+ ```bash
65
+ git clone https://github.com/GreyFlames07/forge.git
66
+ cd forge
67
+ uv venv --python 3.13 .venv && uv pip install -e . pytest
68
+ ./scripts/install-skills.sh install
69
+ ```
70
+
71
+ This wires the `forge` binary into `~/.local/bin/` and symlinks the ten skills into `~/.claude/skills/`, `~/.codex/skills/`, and `~/.agents/skills/` — discoverable by every supported client.
72
+
73
+ ### Verify
74
+
75
+ ```
76
+ forge --version # shows installed forge version
77
+ forge --help # shows: init, update, context, list, inspect, find
78
+ .venv/bin/pytest # 54 passed
79
+ ```
80
+
81
+ ### Requirements
82
+
83
+ | | |
84
+ |---|---|
85
+ | Python | ≥ 3.13 |
86
+ | Package manager | [`uv`](https://docs.astral.sh/uv/) recommended; pip works |
87
+ | One agent client | Claude Code, Codex CLI, or an agentskills.io-compatible client |
88
+
89
+ If `forge: command not found`: add `~/.local/bin` to PATH.
90
+
91
+ ```bash
92
+ export PATH="$HOME/.local/bin:$PATH" # add to ~/.zshrc to persist
93
+ ```
94
+
95
+ ---
96
+
97
+ ## Quick start
98
+
99
+ Bootstrap a new project in any empty directory:
100
+
101
+ ```bash
102
+ mkdir ~/my-idea && cd ~/my-idea
103
+ forge init
104
+ ```
105
+
106
+ ```
107
+ ✦ INITIALISING FORGE
108
+ ▸ Forge init in /Users/you/my-idea
109
+
110
+ ✓ .forge/
111
+ ✓ 6 spec subdirectories
112
+ ✓ 12 schema templates → .forge/templates/
113
+ ✓ 30/30 skill symlinks → .claude/skills/, .codex/skills/, .agents/skills/
114
+
115
+ ───── Next steps ─────
116
+
117
+ Set the spec dir (add to your shell rc to persist):
118
+ export FORGE_SPEC_DIR="/Users/you/my-idea/.forge"
119
+
120
+ Start a session in this directory:
121
+ claude │ codex │ any agentskills.io client
122
+
123
+ Trigger a forge skill with a natural-language prompt:
124
+ "I want to build a tool that does X"
125
+ "Decompose the PAY module into atoms"
126
+ "Audit the specs before implementation"
127
+ "Harden the specs for security before implementation"
128
+ ```
129
+
130
+ Open an agent session in that directory and describe your idea in natural language. The relevant skill activates; the interview begins.
131
+
132
+ ---
133
+
134
+ ## CLI
135
+
136
+ | Command | Purpose |
137
+ |---|---|
138
+ | `forge init` | Scaffold a new project (`.forge/` + skill symlinks + schema templates) |
139
+ | `forge update` | Refresh init-managed project assets to the current Forge version |
140
+ | `forge --version` | Print the installed Forge CLI version |
141
+ | `forge list [--kind K]` | Enumerate entities in the spec dir |
142
+ | `forge inspect <id>` | Lightweight metadata probe |
143
+ | `forge context <id>` | Full implementation-ready bundle for an entity |
144
+ | `forge find <query>` | Substring search across names + descriptions |
145
+
146
+ Spec-dir resolution order: `--spec-dir` flag > `$FORGE_SPEC_DIR` env var > auto-discover (walks upward looking for `.forge/`).
147
+
148
+ Full CLI guide: [`docs/cli-guide.md`](docs/cli-guide.md).
149
+
150
+ ---
151
+
152
+ ## The ten skills
153
+
154
+ | Skill | Role | Input | Output |
155
+ |---|---|---|---|
156
+ | **forge-discover** | Interviewer (product framing) | Vague idea | Project foundation: modules, L0 skeleton, L1 conventions, L5 posture |
157
+ | **forge-decompose** | Structural extractor | One bounded module | Exhaustive atom stubs (four-pass extraction) |
158
+ | **forge-atom** | Contract specifier | One atom stub | Complete L3 spec + L0 cascades + module completions |
159
+ | **forge-compose** | Composition specifier | Completed atoms + project decisions | L4 flow/journey specs with explicit boundary/retry/compensation/idempotency decisions |
160
+ | **forge-audit** | Challenger / reviewer | Completed specs | Severity-ranked findings with inline edits; seven audit passes |
161
+ | **forge-armour** | Security challenger | Audited specs | Security hardening pass, trust-model capture, approved project/module/atom security edits |
162
+ | **forge-implement** | Orchestrator | Audited spec corpus | Code + tests, dep-graph parallel, test-before-impl isolation |
163
+ | **forge-validate** | Post-implementation validator | Implemented system + spec corpus | Validation report: static analysis, test-to-spec mapping, live interaction probes |
164
+ | **forge-test-writer** | Subagent | One entity + level | Unit/integration/system tests with audit doc |
165
+ | **forge-implementer** | Subagent | One entity | Implementation code, blind to tests |
166
+
167
+ Skills activate via natural-language prompts (universal) or slash-commands (Claude Code only).
168
+
169
+ Each skill has a framework doc (mental model) under `docs/skills/<skill>/framework.md` and a directive SKILL.md under `.agents/skills/<skill>/`.
170
+
171
+ ---
172
+
173
+ ## The spec system
174
+
175
+ Six layers, each a source-of-truth YAML file set.
176
+
177
+ | Layer | Purpose |
178
+ |---|---|
179
+ | **L0 Registry** | Vocabulary — types, errors, constants, external schemas, side-effect markers |
180
+ | **L1 Conventions** | Project-wide defaults — retry policy, logging, security posture, verification floors |
181
+ | **L2 Architecture** | Modules — ownership, tech stacks, persistence, permissions, policies |
182
+ | **L3 Behavior** | Atoms (smallest spec unit) + artifacts (non-executing deps) |
183
+ | **L4 Composition** | Flows (saga orchestrations) + journeys (user-facing paths) |
184
+ | **L5 Operations** | Runtime — platform, deployment, rate limiting, event semantics, observability (SLA targets, metrics, alerts) |
185
+
186
+ Schema reference: [`docs/framework-overview.md`](docs/framework-overview.md).
187
+ Full schema templates: [`src/templates/`](src/templates/).
188
+
189
+ ---
190
+
191
+ ## Repository layout
192
+
193
+ ```
194
+ forge/
195
+ ├── src/
196
+ │ ├── cli/ Python CLI package (the forge command)
197
+ │ ├── templates/ L0-L5 schema templates (symlinked into projects by forge init)
198
+ │ └── example/ Working example spec corpus (used by tests)
199
+ ├── .agents/skills/ The 10 forge skills (installed into agent clients)
200
+ │ ├── forge-discover/
201
+ │ ├── forge-decompose/
202
+ │ ├── forge-atom/
203
+ │ ├── forge-compose/
204
+ │ ├── forge-audit/
205
+ │ ├── forge-armour/
206
+ │ ├── forge-implement/
207
+ │ ├── forge-validate/
208
+ │ ├── forge-test-writer/
209
+ │ └── forge-implementer/
210
+ ├── docs/
211
+ │ ├── skills/ Framework docs for each skill (mental models)
212
+ │ ├── cli-guide.md Full CLI reference
213
+ │ └── framework-overview.md
214
+ ├── scripts/
215
+ │ └── install-skills.sh Global skill install + CLI symlink
216
+ ├── tests/ pytest suite — 54 tests
217
+ ├── pyproject.toml
218
+ └── README.md
219
+ ```
220
+
221
+ ---
222
+
223
+ ## Development
224
+
225
+ ### Testing
226
+
227
+ ```bash
228
+ .venv/bin/pytest -v
229
+ ```
230
+
231
+ The `src/example/` directory is a complete working spec corpus (a fictional payments app) that doubles as the test fixture and a reference for what a finished project looks like.
232
+
233
+ ```bash
234
+ export FORGE_SPEC_DIR="$(pwd)/src/example"
235
+ forge list # see what's there
236
+ forge context atm.pay.charge_card # inspect a full atom bundle
237
+ forge find charge # search across entities
238
+ ```
239
+
240
+ ### Adding a new skill
241
+
242
+ 1. Create `.agents/skills/<name>/SKILL.md` with [agentskills.io](https://agentskills.io) frontmatter (`name`, `description`).
243
+ 2. Add the skill to `SKILLS=()` in `scripts/install-skills.sh`.
244
+ 3. Add the skill to `SKILL_NAMES` in `src/cli/commands/init.py`.
245
+ 4. Add a framework doc under `docs/skills/<name>/framework.md` if the skill has a substantial mental model.
246
+ 5. `./scripts/install-skills.sh install` to wire up globally.
247
+ 6. In any existing projects: `forge update` to refresh their managed scaffolding and local skill links.
248
+
249
+ ### Uninstall
250
+
251
+ ```bash
252
+ ./scripts/install-skills.sh uninstall
253
+ ```
254
+
255
+ Removes all global skill symlinks and the `forge` CLI binary. Does not touch project-local `.forge/` directories (those are owned by each project).
256
+
257
+ ---
258
+
259
+ ## Contributing
260
+
261
+ Pull requests welcome. See [`CONTRIBUTING.md`](CONTRIBUTING.md) for:
262
+
263
+ - Branching and commit conventions (Conventional Commits)
264
+ - Local dev setup
265
+ - PR requirements and CI expectations
266
+ - Release process
267
+
268
+ The repo uses standard open-source governance: no direct pushes to `main`, PRs require code-owner approval, CI must be green, history stays linear.
269
+
270
+ Changes are tracked in [`CHANGELOG.md`](CHANGELOG.md) following [Keep a Changelog](https://keepachangelog.com).
271
+
272
+ ## Security
273
+
274
+ Security issues: see [`SECURITY.md`](SECURITY.md). Do not open public issues for vulnerabilities.
275
+
276
+ ## License
277
+
278
+ MIT — see [`LICENSE`](LICENSE).
279
+
280
+ ---
281
+
282
+ <div align="center">
283
+
284
+ <sub>Built for agent-driven development. Questions over prompts. Specs over intentions. Code follows.</sub>
285
+
286
+ </div>
@@ -0,0 +1,21 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ai-forge-cli"
7
+ version = "0.1.2"
8
+ description = "Context walker for the Forge L0-L5 spec system"
9
+ requires-python = ">=3.11"
10
+ dependencies = ["pyyaml>=6.0"]
11
+
12
+ [project.scripts]
13
+ forge = "cli.forge:main"
14
+
15
+ [tool.setuptools.packages.find]
16
+ where = ["src"]
17
+ include = ["cli*"]
18
+
19
+ [tool.pytest.ini_options]
20
+ testpaths = ["tests"]
21
+ pythonpath = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: ai-forge-cli
3
+ Version: 0.1.2
4
+ Summary: Context walker for the Forge L0-L5 spec system
5
+ Requires-Python: >=3.11
6
+ License-File: LICENSE
7
+ Requires-Dist: pyyaml>=6.0
8
+ Dynamic: license-file
@@ -0,0 +1,30 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/ai_forge_cli.egg-info/PKG-INFO
5
+ src/ai_forge_cli.egg-info/SOURCES.txt
6
+ src/ai_forge_cli.egg-info/dependency_links.txt
7
+ src/ai_forge_cli.egg-info/entry_points.txt
8
+ src/ai_forge_cli.egg-info/requires.txt
9
+ src/ai_forge_cli.egg-info/top_level.txt
10
+ src/cli/__init__.py
11
+ src/cli/__main__.py
12
+ src/cli/bundle.py
13
+ src/cli/common.py
14
+ src/cli/forge.py
15
+ src/cli/index.py
16
+ src/cli/walker.py
17
+ src/cli/commands/__init__.py
18
+ src/cli/commands/base.py
19
+ src/cli/commands/context.py
20
+ src/cli/commands/find.py
21
+ src/cli/commands/init.py
22
+ src/cli/commands/inspect.py
23
+ src/cli/commands/list_cmd.py
24
+ src/cli/commands/update.py
25
+ tests/test_cli.py
26
+ tests/test_find.py
27
+ tests/test_index.py
28
+ tests/test_init.py
29
+ tests/test_update.py
30
+ tests/test_walker.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ forge = cli.forge:main
@@ -0,0 +1 @@
1
+ pyyaml>=6.0
@@ -0,0 +1,2 @@
1
+ """Forge CLI — context walker for the L0-L5 spec system."""
2
+ __version__ = "0.1.0"
@@ -0,0 +1,4 @@
1
+ from cli.forge import main
2
+
3
+ if __name__ == "__main__":
4
+ raise SystemExit(main())
@@ -0,0 +1,117 @@
1
+ """
2
+ Bundle formatter — renders a walker output dict as YAML, JSON, or markdown.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ import json
8
+ from collections import OrderedDict
9
+ from typing import Any
10
+
11
+ import yaml
12
+
13
+
14
+ # Human-readable headers for each bundle section.
15
+ _SECTION_HEADERS: dict[str, str] = {
16
+ "target": "Target",
17
+ "l0_registry_slice": "L0 — Registry (sliced)",
18
+ "l1_conventions": "L1 — Conventions",
19
+ "l2_module": "L2 — Owner Module",
20
+ "l2_entry_points": "L2 — Invoking Entry Points",
21
+ "policies_applied": "L2 — Policies Applied",
22
+ "policies": "L2 — Policies",
23
+ "shared_module_interfaces": "L2 — Whitelisted Module Interfaces",
24
+ "l3_atom": "L3 — Target Atom",
25
+ "l3_artifact": "L3 — Target Artifact",
26
+ "owned_atoms": "L3 — Owned Atoms",
27
+ "owned_artifacts": "L3 — Owned Artifacts",
28
+ "called_atom_signatures": "L3 — Called Atom Signatures",
29
+ "producer_atom_signature": "L3 — Producer Atom Signature",
30
+ "source_artifacts": "L3 — Upstream Source Artifacts",
31
+ "consumer_signatures": "L3 — Consumer Signatures",
32
+ "training_artifact": "L3 — Training Artifact",
33
+ "handler_atoms": "L3 — Handler Atoms",
34
+ "step_atom_signatures": "L3 — Step Atom Signatures",
35
+ "l4_journey": "L4 — Target Journey",
36
+ "l4_orchestration": "L4 — Target Orchestration",
37
+ "invoked_orchestrations": "L4 — Invoked Orchestrations",
38
+ "l4_callers": "L4 — Callers (atoms consumed here)",
39
+ "l5_operations": "L5 — Operations",
40
+ }
41
+
42
+
43
+ # Ensure PyYAML emits OrderedDicts in insertion order.
44
+ def _represent_ordered_dict(dumper, data):
45
+ return dumper.represent_mapping("tag:yaml.org,2002:map", data.items())
46
+
47
+
48
+ yaml.add_representer(OrderedDict, _represent_ordered_dict)
49
+
50
+
51
+ def render(bundle: OrderedDict[str, Any], fmt: str = "yaml") -> str:
52
+ if fmt == "json":
53
+ return json.dumps(bundle, indent=2, default=_json_default)
54
+ if fmt == "markdown":
55
+ return _render_markdown(bundle)
56
+ return _render_yaml(bundle)
57
+
58
+
59
+ def _render_yaml(bundle: OrderedDict[str, Any]) -> str:
60
+ parts: list[str] = []
61
+ target = bundle.get("target") or {}
62
+ parts.append("# " + "=" * 66)
63
+ parts.append(f"# FORGE CONTEXT BUNDLE")
64
+ parts.append(f"# target: {target.get('id', '<unknown>')}")
65
+ parts.append(f"# kind: {target.get('kind', '<unknown>')}")
66
+ if target.get("atom_kind"):
67
+ parts.append(f"# atom_kind: {target['atom_kind']}")
68
+ parts.append("# " + "=" * 66)
69
+ parts.append("")
70
+
71
+ for key, value in bundle.items():
72
+ if key == "target":
73
+ continue
74
+ if value is None or (isinstance(value, (dict, list)) and not value):
75
+ continue
76
+ header = _SECTION_HEADERS.get(key, key)
77
+ parts.append("# " + "-" * 66)
78
+ parts.append(f"# {header}")
79
+ parts.append("# " + "-" * 66)
80
+ parts.append(yaml.dump({key: value}, sort_keys=False, allow_unicode=True,
81
+ default_flow_style=False, width=100).rstrip())
82
+ parts.append("")
83
+
84
+ return "\n".join(parts) + "\n"
85
+
86
+
87
+ def _render_markdown(bundle: OrderedDict[str, Any]) -> str:
88
+ target = bundle.get("target") or {}
89
+ parts: list[str] = [
90
+ f"# Forge context: `{target.get('id', '<unknown>')}`",
91
+ "",
92
+ f"- **kind**: `{target.get('kind', '<unknown>')}`",
93
+ ]
94
+ if target.get("atom_kind"):
95
+ parts.append(f"- **atom_kind**: `{target['atom_kind']}`")
96
+ parts.append("")
97
+
98
+ for key, value in bundle.items():
99
+ if key == "target":
100
+ continue
101
+ if value is None or (isinstance(value, (dict, list)) and not value):
102
+ continue
103
+ header = _SECTION_HEADERS.get(key, key)
104
+ parts.append(f"## {header}")
105
+ parts.append("")
106
+ parts.append("```yaml")
107
+ parts.append(yaml.dump({key: value}, sort_keys=False, allow_unicode=True,
108
+ default_flow_style=False, width=100).rstrip())
109
+ parts.append("```")
110
+ parts.append("")
111
+ return "\n".join(parts)
112
+
113
+
114
+ def _json_default(o: Any) -> Any:
115
+ if isinstance(o, OrderedDict):
116
+ return dict(o)
117
+ raise TypeError(f"Unserializable: {type(o)}")
@@ -0,0 +1,28 @@
1
+ """
2
+ Command registry.
3
+
4
+ To add a new command:
5
+ 1. Create `cli/commands/<name>.py` exposing NAME, HELP, register, run
6
+ (see `base.py` for the contract, or copy any existing command as a
7
+ starting point).
8
+ 2. Import it below and append to ALL_COMMANDS.
9
+
10
+ That's it — `forge.py` auto-discovers every command in ALL_COMMANDS,
11
+ builds its subparser, and wires dispatch via `args.handler`.
12
+ """
13
+
14
+ from cli.commands import context as _context
15
+ from cli.commands import find as _find
16
+ from cli.commands import init as _init
17
+ from cli.commands import inspect as _inspect
18
+ from cli.commands import list_cmd as _list_cmd
19
+ from cli.commands import update as _update
20
+
21
+ ALL_COMMANDS = [
22
+ _init,
23
+ _update,
24
+ _context,
25
+ _list_cmd,
26
+ _inspect,
27
+ _find,
28
+ ]
@@ -0,0 +1,26 @@
1
+ """
2
+ Command module contract.
3
+
4
+ Every module in `cli.commands` must expose:
5
+
6
+ NAME: str
7
+ The subcommand name. Used as `forge <NAME> ...` on the CLI.
8
+
9
+ HELP: str
10
+ One-line help text shown in `forge --help`.
11
+
12
+ def register(sub: argparse._SubParsersAction) -> None:
13
+ Add this command's subparser with all its arguments to the
14
+ provided _SubParsersAction. MUST call
15
+ `parser.set_defaults(handler=run)` on the created subparser so
16
+ dispatch works without any if/elif chain in forge.main.
17
+
18
+ def run(args: argparse.Namespace) -> int:
19
+ Execute the command. Return an exit code:
20
+ 0 = success
21
+ 1 = usage error (unknown id, missing spec dir, etc.)
22
+ 2 = soft warning (e.g., bundle emitted but has unresolved refs)
23
+
24
+ Commands should reuse helpers from `cli.common` rather than reimplementing
25
+ spec-dir loading, id suggestion, or description formatting.
26
+ """
@@ -0,0 +1,66 @@
1
+ """`forge context <id>` — build a full context bundle."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import sys
7
+
8
+ from cli import bundle as bundle_mod
9
+ from cli import common
10
+ from cli import index as index_mod
11
+ from cli import walker
12
+
13
+ NAME = "context"
14
+ HELP = "Build a full implementation-ready context bundle for an id."
15
+ DESCRIPTION = (
16
+ "Walks the spec dependency graph from <id> and emits everything "
17
+ "an agent needs to implement it: the target spec, referenced L0 "
18
+ "entries (sliced, not the whole registry), the owning module, "
19
+ "applicable policies, L1 conventions, L4 callers with derived "
20
+ "implications, and L5 operations."
21
+ )
22
+
23
+
24
+ def register(sub: argparse._SubParsersAction) -> None:
25
+ p = sub.add_parser(NAME, help=HELP, description=DESCRIPTION)
26
+ p.add_argument(
27
+ "id",
28
+ help="Target id. Must be an atom, module, journey, flow, or artifact.",
29
+ )
30
+ common.add_spec_dir_arg(p)
31
+ p.add_argument(
32
+ "--format", choices=["yaml", "json", "markdown"], default="yaml",
33
+ help=(
34
+ "Output format. yaml (default) is most token-efficient. "
35
+ "json is most parseable. markdown wraps each section in a "
36
+ "heading + code block for pasting into a chat."
37
+ ),
38
+ )
39
+ p.set_defaults(handler=run)
40
+
41
+
42
+ def run(args: argparse.Namespace) -> int:
43
+ idx, rc = common.load_index(args.spec_dir)
44
+ if rc != 0:
45
+ return rc
46
+
47
+ try:
48
+ index_mod.classify(idx, args.id)
49
+ except (KeyError, ValueError) as e:
50
+ print(f"error: {e}", file=sys.stderr)
51
+ common.suggest_similar(idx, args.id)
52
+ return 1
53
+
54
+ bundle, unresolved = walker.walk(idx, args.id)
55
+ output = bundle_mod.render(bundle, fmt=args.format)
56
+ sys.stdout.write(output)
57
+
58
+ if unresolved:
59
+ seen: set[str] = set()
60
+ uniq = [u for u in unresolved if not (u in seen or seen.add(u))]
61
+ print(f"\n# Unresolved references ({len(uniq)}):", file=sys.stderr)
62
+ for u in uniq:
63
+ print(f"# - {u}", file=sys.stderr)
64
+ return 2
65
+
66
+ return 0