beacon-framework 0.1.0__py3-none-any.whl

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 (36) hide show
  1. beacon/__init__.py +3 -0
  2. beacon/__main__.py +4 -0
  3. beacon/cli.py +177 -0
  4. beacon/installer.py +92 -0
  5. beacon/integrations/__init__.py +36 -0
  6. beacon/integrations/claude_code.py +123 -0
  7. beacon/manifest.py +55 -0
  8. beacon/resources/beacon.md +9 -0
  9. beacon/resources/integrations/claude_code/CLAUDE.md.fragment +137 -0
  10. beacon/resources/integrations/claude_code/commands/design/diagram.md +313 -0
  11. beacon/resources/integrations/claude_code/commands/design/evaluate.md +156 -0
  12. beacon/resources/integrations/claude_code/commands/design/wardley.md +156 -0
  13. beacon/resources/integrations/claude_code/commands/git/feature.md +57 -0
  14. beacon/resources/integrations/claude_code/commands/git/pr.md +78 -0
  15. beacon/resources/integrations/claude_code/commands/git/release.md +107 -0
  16. beacon/resources/integrations/claude_code/commands/init.md +179 -0
  17. beacon/resources/project-management/ADRs/ADR-000-template.md +60 -0
  18. beacon/resources/project-management/ADRs/README.md +31 -0
  19. beacon/resources/project-management/Background/00-problem-statement.md +48 -0
  20. beacon/resources/project-management/Background/01-final-architecture-document.md +172 -0
  21. beacon/resources/project-management/Prompts/01-SEED.md +61 -0
  22. beacon/resources/project-management/Prompts/02-DESIGN.md +78 -0
  23. beacon/resources/project-management/Prompts/03-BUILD.md +70 -0
  24. beacon/resources/project-management/Prompts/04-SHIP.md +75 -0
  25. beacon/resources/project-management/Roadmap/README.md +80 -0
  26. beacon/resources/project-management/Roadmap/archive/.gitkeep +0 -0
  27. beacon/resources/project-management/Work/README.md +60 -0
  28. beacon/resources/project-management/Work/analysis/.gitkeep +0 -0
  29. beacon/resources/project-management/Work/planning/.gitkeep +0 -0
  30. beacon/resources/project-management/Work/sessions/.gitkeep +0 -0
  31. beacon/upgrader.py +31 -0
  32. beacon_framework-0.1.0.dist-info/METADATA +92 -0
  33. beacon_framework-0.1.0.dist-info/RECORD +36 -0
  34. beacon_framework-0.1.0.dist-info/WHEEL +4 -0
  35. beacon_framework-0.1.0.dist-info/entry_points.txt +2 -0
  36. beacon_framework-0.1.0.dist-info/licenses/LICENSE +21 -0
beacon/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """BEACON Framework: lifecycle and discipline for artifact-driven AI-assisted delivery."""
2
+
3
+ __version__ = "0.1.0"
beacon/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from beacon.cli import app
2
+
3
+ if __name__ == "__main__":
4
+ app()
beacon/cli.py ADDED
@@ -0,0 +1,177 @@
1
+ """BEACON CLI — install, upgrade, validate, and manage AI integrations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import typer
8
+ from rich.console import Console
9
+
10
+ from beacon import __version__, installer, integrations
11
+ from beacon.manifest import MANIFEST_RELPATH, Manifest
12
+ from beacon.upgrader import upgrade as upgrade_project
13
+
14
+ app = typer.Typer(
15
+ name="beacon",
16
+ help="BEACON Framework — lifecycle and discipline for artifact-driven AI-assisted delivery.",
17
+ no_args_is_help=True,
18
+ add_completion=False,
19
+ )
20
+ integration_app = typer.Typer(
21
+ name="integration",
22
+ help="Manage AI tool integrations (Claude Code, ...).",
23
+ no_args_is_help=True,
24
+ )
25
+ app.add_typer(integration_app)
26
+
27
+ console = Console()
28
+
29
+
30
+ def _resolve_root(here: bool, path: Path | None) -> Path:
31
+ if here and path is not None:
32
+ raise typer.BadParameter("Pass either --here or PATH, not both.")
33
+ if here:
34
+ return Path.cwd()
35
+ if path is not None:
36
+ return path.resolve()
37
+ return Path.cwd()
38
+
39
+
40
+ @app.command()
41
+ def version() -> None:
42
+ """Print the installed BEACON version."""
43
+ console.print(f"beacon {__version__}")
44
+
45
+
46
+ @app.command()
47
+ def init(
48
+ path: Path | None = typer.Argument(None, help="Project directory (default: cwd)."),
49
+ here: bool = typer.Option(False, "--here", help="Install into the current directory."),
50
+ ai: list[str] = typer.Option(
51
+ ["claude"],
52
+ "--ai",
53
+ help="AI integration(s) to wire up.",
54
+ ),
55
+ ) -> None:
56
+ """Install BEACON into a project. Idempotent: existing user files are preserved."""
57
+ root = _resolve_root(here, path)
58
+ root.mkdir(parents=True, exist_ok=True)
59
+
60
+ existing = Manifest.load(root)
61
+ manifest = existing or Manifest.fresh()
62
+
63
+ framework_written, user_seeded_written = installer.install_core(root, overwrite_framework=True)
64
+
65
+ for name in ai:
66
+ try:
67
+ integrations.install(name, root)
68
+ except KeyError as e:
69
+ raise typer.BadParameter(str(e)) from e
70
+
71
+ manifest.beacon_version = __version__
72
+ manifest.ai = sorted(set(manifest.ai) | set(ai))
73
+ manifest.framework_files = sorted(set(manifest.framework_files) | set(framework_written))
74
+ manifest.user_seeded_files = sorted(set(manifest.user_seeded_files) | set(user_seeded_written))
75
+ manifest.save(root)
76
+
77
+ console.print(f"[green]✓[/green] BEACON {__version__} installed at [cyan]{root}[/cyan]")
78
+ console.print(f" framework files written: {len(framework_written)}")
79
+ console.print(f" user-seeded files written: {len(user_seeded_written)} (existing left intact)")
80
+ console.print(f" integrations: {', '.join(manifest.ai) or 'none'}")
81
+ console.print(f" manifest: [dim]{MANIFEST_RELPATH}[/dim]")
82
+
83
+
84
+ @app.command()
85
+ def upgrade(
86
+ path: Path | None = typer.Argument(None, help="Project directory (default: cwd)."),
87
+ here: bool = typer.Option(False, "--here", help="Upgrade in the current directory."),
88
+ ) -> None:
89
+ """Refresh BEACON framework files. User-seeded content is left untouched."""
90
+ root = _resolve_root(here, path)
91
+ try:
92
+ manifest, touched = upgrade_project(root)
93
+ except FileNotFoundError as e:
94
+ raise typer.Exit(code=1) from typer.BadParameter(str(e))
95
+ console.print(f"[green]✓[/green] BEACON upgraded to {manifest.beacon_version}")
96
+ console.print(f" files refreshed: {len(touched)}")
97
+ console.print(f" integrations: {', '.join(manifest.ai) or 'none'}")
98
+
99
+
100
+ @app.command()
101
+ def check(
102
+ path: Path | None = typer.Argument(None, help="Project directory (default: cwd)."),
103
+ here: bool = typer.Option(False, "--here", help="Check the current directory."),
104
+ ) -> None:
105
+ """Validate a BEACON install: manifest present and required files in place."""
106
+ root = _resolve_root(here, path)
107
+ manifest = Manifest.load(root)
108
+ if manifest is None:
109
+ console.print(
110
+ f"[red]✗[/red] No BEACON manifest at {root / MANIFEST_RELPATH}. "
111
+ f"Run `beacon init` first."
112
+ )
113
+ raise typer.Exit(code=1)
114
+
115
+ missing: list[str] = []
116
+ for rel in manifest.framework_files:
117
+ if not (root / rel).exists():
118
+ missing.append(rel)
119
+
120
+ if missing:
121
+ console.print(f"[red]✗[/red] {len(missing)} framework files missing:")
122
+ for rel in missing:
123
+ console.print(f" - {rel}")
124
+ console.print(" Run `beacon upgrade` to restore.")
125
+ raise typer.Exit(code=1)
126
+
127
+ console.print(
128
+ f"[green]✓[/green] BEACON {manifest.beacon_version} OK at [cyan]{root}[/cyan] "
129
+ f"(integrations: {', '.join(manifest.ai) or 'none'})"
130
+ )
131
+
132
+
133
+ @integration_app.command("list")
134
+ def integration_list() -> None:
135
+ """List available AI integrations."""
136
+ for name in integrations.names():
137
+ console.print(f" - {name}")
138
+
139
+
140
+ @integration_app.command("add")
141
+ def integration_add(
142
+ name: str,
143
+ path: Path | None = typer.Argument(None, help="Project directory (default: cwd)."),
144
+ here: bool = typer.Option(False, "--here"),
145
+ ) -> None:
146
+ """Add an AI integration to an existing BEACON install."""
147
+ root = _resolve_root(here, path)
148
+ manifest = Manifest.load(root)
149
+ if manifest is None:
150
+ raise typer.BadParameter("No BEACON manifest. Run `beacon init` first.")
151
+ try:
152
+ integrations.install(name, root)
153
+ except KeyError as e:
154
+ raise typer.BadParameter(str(e)) from e
155
+ manifest.ai = sorted(set(manifest.ai) | {name})
156
+ manifest.save(root)
157
+ console.print(f"[green]✓[/green] integration added: {name}")
158
+
159
+
160
+ @integration_app.command("remove")
161
+ def integration_remove(
162
+ name: str,
163
+ path: Path | None = typer.Argument(None, help="Project directory (default: cwd)."),
164
+ here: bool = typer.Option(False, "--here"),
165
+ ) -> None:
166
+ """Remove an AI integration; manifest is updated accordingly."""
167
+ root = _resolve_root(here, path)
168
+ manifest = Manifest.load(root)
169
+ if manifest is None:
170
+ raise typer.BadParameter("No BEACON manifest. Run `beacon init` first.")
171
+ try:
172
+ integrations.remove(name, root)
173
+ except KeyError as e:
174
+ raise typer.BadParameter(str(e)) from e
175
+ manifest.ai = sorted(set(manifest.ai) - {name})
176
+ manifest.save(root)
177
+ console.print(f"[green]✓[/green] integration removed: {name}")
beacon/installer.py ADDED
@@ -0,0 +1,92 @@
1
+ """File installation logic for BEACON.
2
+
3
+ Two file categories with different upgrade semantics:
4
+
5
+ - **framework**: shipped by the package, always overwritten on ``beacon init``
6
+ and ``beacon upgrade``. Users should not edit these.
7
+ - **user_seeded**: shipped as a starting point. Written by ``init`` only if
8
+ absent; ``upgrade`` never touches them.
9
+
10
+ The classification is data, not code: see ``FRAMEWORK_RELPATHS`` and
11
+ ``USER_SEEDED_RELPATHS`` below.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import shutil
17
+ from importlib import resources
18
+ from pathlib import Path
19
+
20
+ # Paths are relative to the *target project root*, and mirror the layout under
21
+ # ``src/beacon/resources/``.
22
+ USER_SEEDED_RELPATHS: tuple[str, ...] = (
23
+ "beacon.md",
24
+ "project-management/Background/00-problem-statement.md",
25
+ "project-management/Background/01-final-architecture-document.md",
26
+ "project-management/Roadmap/README.md",
27
+ )
28
+
29
+ FRAMEWORK_RELPATHS: tuple[str, ...] = (
30
+ "project-management/ADRs/README.md",
31
+ "project-management/ADRs/ADR-000-template.md",
32
+ "project-management/Prompts/01-SEED.md",
33
+ "project-management/Prompts/02-DESIGN.md",
34
+ "project-management/Prompts/03-BUILD.md",
35
+ "project-management/Prompts/04-SHIP.md",
36
+ "project-management/Work/README.md",
37
+ )
38
+
39
+ GITKEEP_RELPATHS: tuple[str, ...] = (
40
+ "project-management/Roadmap/archive/.gitkeep",
41
+ "project-management/Work/analysis/.gitkeep",
42
+ "project-management/Work/planning/.gitkeep",
43
+ "project-management/Work/sessions/.gitkeep",
44
+ )
45
+
46
+
47
+ def _resource_root() -> Path:
48
+ """Return the on-disk path to the packaged ``resources/`` directory."""
49
+ return Path(str(resources.files("beacon").joinpath("resources")))
50
+
51
+
52
+ def _copy_one(src: Path, dst: Path) -> None:
53
+ dst.parent.mkdir(parents=True, exist_ok=True)
54
+ shutil.copyfile(src, dst)
55
+
56
+
57
+ def install_core(project_root: Path, *, overwrite_framework: bool) -> tuple[list[str], list[str]]:
58
+ """Install the BEACON core file set into ``project_root``.
59
+
60
+ ``overwrite_framework=False`` is the ``init`` behaviour (seed only what's
61
+ missing). ``True`` is the ``upgrade`` behaviour: refresh framework files,
62
+ leave user_seeded alone.
63
+ """
64
+ src_root = _resource_root()
65
+ framework_written: list[str] = []
66
+ user_seeded_written: list[str] = []
67
+
68
+ for rel in FRAMEWORK_RELPATHS:
69
+ src = src_root / rel
70
+ dst = project_root / rel
71
+ if dst.exists() and not overwrite_framework:
72
+ continue
73
+ _copy_one(src, dst)
74
+ framework_written.append(rel)
75
+
76
+ for rel in USER_SEEDED_RELPATHS:
77
+ src = src_root / rel
78
+ dst = project_root / rel
79
+ if dst.exists():
80
+ continue # never overwrite user-seeded files
81
+ _copy_one(src, dst)
82
+ user_seeded_written.append(rel)
83
+
84
+ for rel in GITKEEP_RELPATHS:
85
+ dst = project_root / rel
86
+ if dst.exists():
87
+ continue
88
+ dst.parent.mkdir(parents=True, exist_ok=True)
89
+ dst.touch()
90
+ framework_written.append(rel)
91
+
92
+ return framework_written, user_seeded_written
@@ -0,0 +1,36 @@
1
+ """AI tool integrations for BEACON.
2
+
3
+ The registry below is the single source of truth for which integrations exist.
4
+ Adding a new one (Copilot, Gemini, ...) is one entry plus one module.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from collections.abc import Callable
10
+ from pathlib import Path
11
+
12
+ from beacon.integrations import claude_code
13
+
14
+ # Each entry exposes (install, remove) callables. Both take ``project_root`` and
15
+ # return the list of relative paths written or removed.
16
+ Integration = tuple[Callable[[Path], list[str]], Callable[[Path], list[str]]]
17
+
18
+ REGISTRY: dict[str, Integration] = {
19
+ "claude": (claude_code.install, claude_code.remove),
20
+ }
21
+
22
+
23
+ def names() -> list[str]:
24
+ return sorted(REGISTRY.keys())
25
+
26
+
27
+ def install(name: str, project_root: Path) -> list[str]:
28
+ if name not in REGISTRY:
29
+ raise KeyError(f"Unknown integration: {name}. Available: {names()}")
30
+ return REGISTRY[name][0](project_root)
31
+
32
+
33
+ def remove(name: str, project_root: Path) -> list[str]:
34
+ if name not in REGISTRY:
35
+ raise KeyError(f"Unknown integration: {name}. Available: {names()}")
36
+ return REGISTRY[name][1](project_root)
@@ -0,0 +1,123 @@
1
+ """Claude Code integration.
2
+
3
+ Installs slash commands under ``.claude/commands/`` and a marker-delimited
4
+ fragment into ``.claude/CLAUDE.md``. The fragment is the only place where
5
+ BEACON's lifecycle and Spec Kit seams are described — by design.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import shutil
11
+ from importlib import resources
12
+ from pathlib import Path
13
+
14
+ INTEGRATION_NAME = "claude"
15
+ RESOURCE_SUBDIR = "integrations/claude_code"
16
+
17
+ # Slash command files installed under ``.claude/commands/``. Always overwritten
18
+ # by ``install`` / ``upgrade`` — BEACON owns these.
19
+ COMMAND_RELPATHS: tuple[str, ...] = (
20
+ ".claude/commands/init.md",
21
+ ".claude/commands/git/feature.md",
22
+ ".claude/commands/git/pr.md",
23
+ ".claude/commands/git/release.md",
24
+ ".claude/commands/design/diagram.md",
25
+ ".claude/commands/design/evaluate.md",
26
+ ".claude/commands/design/wardley.md",
27
+ )
28
+
29
+ CLAUDE_MD = Path(".claude/CLAUDE.md")
30
+ START_MARKER = "<!-- BEACON START -->"
31
+ END_MARKER = "<!-- BEACON END -->"
32
+
33
+
34
+ def _resource_root() -> Path:
35
+ return Path(str(resources.files("beacon").joinpath("resources").joinpath(RESOURCE_SUBDIR)))
36
+
37
+
38
+ def _copy_command(rel: str, project_root: Path) -> None:
39
+ src = _resource_root() / "commands" / rel.removeprefix(".claude/commands/")
40
+ dst = project_root / rel
41
+ dst.parent.mkdir(parents=True, exist_ok=True)
42
+ shutil.copyfile(src, dst)
43
+
44
+
45
+ def _read_fragment() -> str:
46
+ return (_resource_root() / "CLAUDE.md.fragment").read_text()
47
+
48
+
49
+ def _inject_fragment(project_root: Path) -> None:
50
+ """Write or refresh the BEACON-managed block in ``.claude/CLAUDE.md``.
51
+
52
+ If the file does not exist, create it with just the fragment.
53
+ If markers are present, replace what's between them.
54
+ If markers are absent, append the fragment.
55
+ """
56
+ path = project_root / CLAUDE_MD
57
+ fragment = _read_fragment().rstrip() + "\n"
58
+
59
+ if not path.exists():
60
+ path.parent.mkdir(parents=True, exist_ok=True)
61
+ path.write_text(fragment)
62
+ return
63
+
64
+ content = path.read_text()
65
+ start = content.find(START_MARKER)
66
+ end = content.find(END_MARKER)
67
+ if start != -1 and end != -1 and end > start:
68
+ end_line = content.find("\n", end)
69
+ end_idx = end_line + 1 if end_line != -1 else len(content)
70
+ new = content[:start] + fragment + content[end_idx:]
71
+ path.write_text(new)
72
+ return
73
+
74
+ sep = "" if content.endswith("\n") else "\n"
75
+ path.write_text(content + sep + "\n" + fragment)
76
+
77
+
78
+ def _strip_fragment(project_root: Path) -> None:
79
+ path = project_root / CLAUDE_MD
80
+ if not path.exists():
81
+ return
82
+ content = path.read_text()
83
+ start = content.find(START_MARKER)
84
+ end = content.find(END_MARKER)
85
+ if start == -1 or end == -1 or end <= start:
86
+ return
87
+ end_line = content.find("\n", end)
88
+ end_idx = end_line + 1 if end_line != -1 else len(content)
89
+ cleaned = content[:start] + content[end_idx:]
90
+ # Collapse consecutive blank lines at the seam
91
+ while "\n\n\n" in cleaned:
92
+ cleaned = cleaned.replace("\n\n\n", "\n\n")
93
+ path.write_text(cleaned)
94
+
95
+
96
+ def install(project_root: Path) -> list[str]:
97
+ """Install Claude Code integration. Returns relpaths written."""
98
+ written: list[str] = []
99
+ for rel in COMMAND_RELPATHS:
100
+ _copy_command(rel, project_root)
101
+ written.append(rel)
102
+ _inject_fragment(project_root)
103
+ written.append(str(CLAUDE_MD))
104
+ return written
105
+
106
+
107
+ def remove(project_root: Path) -> list[str]:
108
+ """Remove Claude Code integration. Returns relpaths removed/modified."""
109
+ removed: list[str] = []
110
+ for rel in COMMAND_RELPATHS:
111
+ p = project_root / rel
112
+ if p.exists():
113
+ p.unlink()
114
+ removed.append(rel)
115
+ # Clean empty parent dirs (best effort)
116
+ for parent in (p.parent, p.parent.parent):
117
+ try:
118
+ parent.rmdir()
119
+ except OSError:
120
+ break
121
+ _strip_fragment(project_root)
122
+ removed.append(str(CLAUDE_MD))
123
+ return removed
beacon/manifest.py ADDED
@@ -0,0 +1,55 @@
1
+ """BEACON installation manifest — the atomicity contract.
2
+
3
+ A manifest lives at ``project-management/.beacon/init-options.json`` and records:
4
+ - the package version used to install,
5
+ - which AI integrations are wired up,
6
+ - which files BEACON owns (``framework`` — overwritten by upgrade) versus
7
+ which it merely seeded (``user_seeded`` — never overwritten).
8
+
9
+ The manifest is what makes ``beacon upgrade`` safe: it knows exactly which
10
+ files it may touch and which belong to the user.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import json
16
+ from dataclasses import asdict, dataclass, field
17
+ from datetime import datetime, timezone
18
+ from pathlib import Path
19
+
20
+ from beacon import __version__
21
+
22
+ MANIFEST_RELPATH = Path("project-management/.beacon/init-options.json")
23
+
24
+
25
+ @dataclass
26
+ class Manifest:
27
+ beacon_version: str = __version__
28
+ installed_at: str = ""
29
+ ai: list[str] = field(default_factory=list)
30
+ framework_files: list[str] = field(default_factory=list)
31
+ user_seeded_files: list[str] = field(default_factory=list)
32
+
33
+ @classmethod
34
+ def fresh(cls) -> Manifest:
35
+ return cls(installed_at=datetime.now(timezone.utc).isoformat(timespec="seconds"))
36
+
37
+ @classmethod
38
+ def load(cls, project_root: Path) -> Manifest | None:
39
+ path = project_root / MANIFEST_RELPATH
40
+ if not path.exists():
41
+ return None
42
+ data = json.loads(path.read_text())
43
+ return cls(
44
+ beacon_version=data.get("beacon_version", ""),
45
+ installed_at=data.get("installed_at", ""),
46
+ ai=list(data.get("ai", [])),
47
+ framework_files=list(data.get("framework_files", [])),
48
+ user_seeded_files=list(data.get("user_seeded_files", [])),
49
+ )
50
+
51
+ def save(self, project_root: Path) -> Path:
52
+ path = project_root / MANIFEST_RELPATH
53
+ path.parent.mkdir(parents=True, exist_ok=True)
54
+ path.write_text(json.dumps(asdict(self), indent=2, sort_keys=True) + "\n")
55
+ return path
@@ -0,0 +1,9 @@
1
+ # Progress
2
+
3
+ > "Would I proudly sign my name to this?"
4
+
5
+ | Phase | Active Bullet | Branch | Last DEV deploy |
6
+ |---|---|---|---|
7
+ | SEED → **DESIGN** → BUILD → SHIP | `#— —` | `—` | `—` |
8
+
9
+ See [`project-management/Roadmap/README.md`](project-management/Roadmap/README.md) for the full plan.
@@ -0,0 +1,137 @@
1
+ <!-- BEACON START -->
2
+ <!-- Managed by `uvx beacon`. Edit content outside these markers. -->
3
+
4
+ ## BEACON Framework
5
+
6
+ You are a BEACON Framework assistant. Prime directive at all times:
7
+
8
+ > **"Would I proudly sign my name to this?"**
9
+
10
+ BEACON is a pragmatic, artifact-driven framework that combines tracer-bullet delivery with disciplined craftsmanship. Pair it with [Spec Kit](https://github.com/github/spec-kit) for the spec mechanics inside DESIGN and BUILD.
11
+
12
+ ### Phases
13
+
14
+ ```
15
+ SEED → DESIGN → BUILD → SHIP
16
+ ```
17
+
18
+ | Phase | Entry | Deliverables | Exit |
19
+ |---|---|---|---|
20
+ | **SEED** | "I have an idea for…" / new project | `project-management/Background/00-problem-statement.md` | One problem, one user, success criteria, non-goals |
21
+ | **DESIGN** | "How should we architect…" / "Break this down" | `specs/[feature]/{spec,plan,tasks}.md` (Spec Kit); `project-management/ADRs/ADR-NNN-*.md`; updated `Background/01-final-architecture-document.md` | spec, plan, tasks complete and reviewed |
22
+ | **BUILD** | "Starting bullet #N" | Working software per bullet; updated `beacon.md` + Roadmap | All tests pass; previous bullets unbroken; demoable |
23
+ | **SHIP** | All bullets complete | PR via `/git:pr`; `Work/` cleaned; patterns promoted to ADRs | PR merged to `main`; clean `Work/` |
24
+
25
+ Full prompts: `project-management/Prompts/0N-PHASE.md`.
26
+
27
+ ### Pipeline (Claude Code)
28
+
29
+ ```
30
+ /init # SEED — produces 00-problem-statement.md
31
+ └── /speckit.specify "<feature>" # DESIGN — produces specs/NNN-slug/spec.md
32
+ └── /speckit.plan # DESIGN — produces plan.md
33
+ └── /speckit.tasks # DESIGN — produces tasks.md
34
+ └── /speckit.implement # BUILD — executes tasks
35
+ └── /git:pr # SHIP — PR to develop
36
+ └── /git:release # SHIP — develop → main
37
+ ```
38
+
39
+ ### Tracer bullets
40
+
41
+ A tracer bullet is a complete minimal path through the system, end-to-end:
42
+ - Touches all layers (even minimally)
43
+ - Produces user-visible output
44
+ - Deployable as-is (even if limited)
45
+ - 2–4 hours max — split if larger
46
+ - Vertical, not horizontal: day 1 ships hardcoded end-to-end; day 2 adds real logic
47
+
48
+ One bullet per session. Scope creep goes to `project-management/Work/planning/future-features.md`.
49
+
50
+ ### Git workflow (two-branch environment model)
51
+
52
+ ```
53
+ main ── PROD (protected; PR from develop only; manual approval)
54
+ ↑ /git:release
55
+ develop ── DEV (protected; CI gates only)
56
+ ↑ /git:pr
57
+ NNN-slug ← spec work (from /speckit.specify)
58
+ feature/[slug] · fix/[slug] · chore/[slug] · docs/[slug] ← non-spec (/git:feature)
59
+ ```
60
+
61
+ | Command | Action |
62
+ |---|---|
63
+ | `/speckit.specify <feature>` | Create spec + `NNN-slug` branch |
64
+ | `/git:feature <name>` | Cut branch from `develop` for non-spec work |
65
+ | `/git:pr` | PR to `develop` |
66
+ | `/git:release` | PR `develop → main` with changelog |
67
+
68
+ Conventional Commits enforced by the `commit-msg` hook. Do not bypass.
69
+
70
+ ### Project management
71
+
72
+ ```
73
+ project-management/
74
+ ├── Background/ ← problem statement, architecture (PERMANENT)
75
+ ├── ADRs/ ← MADR-format decisions (PERMANENT, immutable)
76
+ ├── Roadmap/ ← cross-feature bullet dashboard (PERMANENT)
77
+ ├── Prompts/ ← phase prompts (PERMANENT, framework-owned)
78
+ └── Work/ ← scratchpad (TRANSIENT — delete after merge)
79
+ ├── sessions/ planning/ analysis/
80
+ ```
81
+
82
+ Anything important that lives only in `Work/` will be lost. Promote insights to ADRs before deleting.
83
+
84
+ ### Seams with Spec Kit
85
+
86
+ These are the only places BEACON and Spec Kit touch — everywhere else they are independent:
87
+
88
+ 1. `/init` → `/speckit.specify` — BEACON's SEED phase ends by bridging to Spec Kit's DESIGN.
89
+ 2. **Roadmap aggregates; tasks.md decomposes.** `Roadmap/README.md` is the cross-feature dashboard; `specs/[feature]/tasks.md` is the within-feature breakdown. Roadmap rows link to spec paths.
90
+ 3. **Feature-scoped research → `specs/[feature]/research.md`. Cross-cutting analysis → `Work/analysis/`.**
91
+ 4. **Constitution Check ≠ ADR.** `.specify/memory/constitution.md` = project principles enforced by Spec Kit's plan gate. `project-management/ADRs/` = specific decisions with rationale. They coexist.
92
+
93
+ ### Where principles live
94
+
95
+ | Document | Scope | Owner | Updated by |
96
+ |---|---|---|---|
97
+ | `pragmatic-principles.md` | Universal craftsperson agent OS | `beacon` package | `uvx beacon upgrade` |
98
+ | `.specify/memory/constitution.md` | This project's rules | `specify` | `/speckit.constitution` |
99
+ | `project-management/ADRs/` | Specific decisions, immutable | `beacon` package (template) + humans/agent | Manual / `/speckit.plan` discovery |
100
+
101
+ ### Pragmatic design principles (applied as constraints)
102
+
103
+ | Principle | Test |
104
+ |---|---|
105
+ | **DRY** | Is this logic defined in exactly one place? |
106
+ | **Orthogonality** | Can this change without forcing changes elsewhere? |
107
+ | **Reversibility** | What is the escape hatch if we change this decision? |
108
+ | **Simplicity** | Is this the simplest thing that could work? Function before class. Script before service. |
109
+ | **Broken Windows** | Any TODOs, warnings, or failing tests I'm walking past? |
110
+
111
+ ### Quality gates (before any bullet is "done")
112
+
113
+ ```bash
114
+ uv run ruff check --fix
115
+ uv run ruff format
116
+ uv run ty check
117
+ ```
118
+
119
+ Then ask: *"Would I sign my name to this?"* If not, refactor before committing.
120
+
121
+ ### WISDOM communication (ADRs, PRs, tradeoffs)
122
+
123
+ - **W**hat do you want the reader to understand?
124
+ - **I**nterest level and stake?
125
+ - **S**ophistication with this domain?
126
+ - **D**etail they need?
127
+ - **O**wnership you want to create?
128
+ - **M**otivation to engage?
129
+
130
+ ### Upgrading
131
+
132
+ - Upgrade BEACON: `uvx beacon upgrade` — refreshes `project-management/Prompts/` and templates; preserves your `Background/`, `ADRs/`, `Roadmap/`, `Work/`.
133
+ - Upgrade Spec Kit: `uvx specify integration upgrade` — refreshes `.specify/` and `.github/{agents,prompts}/`; does not touch BEACON files.
134
+
135
+ Neither upgrade can modify the other framework's directory.
136
+
137
+ <!-- BEACON END -->