reposeed 0.4.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 (52) hide show
  1. reposeed/__init__.py +10 -0
  2. reposeed/__main__.py +3 -0
  3. reposeed/cli.py +232 -0
  4. reposeed/constants.py +35 -0
  5. reposeed/detect.py +89 -0
  6. reposeed/doctor.py +122 -0
  7. reposeed/errors.py +4 -0
  8. reposeed/foundation/AGENTS.md +108 -0
  9. reposeed/foundation/CHANGELOG.md +5 -0
  10. reposeed/foundation/GRAPHIFY.md +96 -0
  11. reposeed/foundation/OPERATING_GUIDELINES.md +102 -0
  12. reposeed/foundation/PLATFORM_SUPPORT.md +69 -0
  13. reposeed/foundation/skills/flutter/flutter-conventions/SKILL.md +39 -0
  14. reposeed/foundation/skills/flutter/flutter-conventions/agents/openai.yaml +4 -0
  15. reposeed/foundation/skills/node/node-conventions/SKILL.md +37 -0
  16. reposeed/foundation/skills/node/node-conventions/agents/openai.yaml +4 -0
  17. reposeed/foundation/skills/php/php-conventions/SKILL.md +35 -0
  18. reposeed/foundation/skills/php/php-conventions/agents/openai.yaml +4 -0
  19. reposeed/foundation/skills/python/python-conventions/SKILL.md +39 -0
  20. reposeed/foundation/skills/python/python-conventions/agents/openai.yaml +4 -0
  21. reposeed/foundation/skills/universal/clarify-before-building/SKILL.md +61 -0
  22. reposeed/foundation/skills/universal/clarify-before-building/agents/openai.yaml +4 -0
  23. reposeed/foundation/skills/universal/code-review/SKILL.md +86 -0
  24. reposeed/foundation/skills/universal/code-review/agents/openai.yaml +4 -0
  25. reposeed/foundation/skills/universal/commit-messages/SKILL.md +67 -0
  26. reposeed/foundation/skills/universal/commit-messages/agents/openai.yaml +4 -0
  27. reposeed/foundation/skills/universal/compounding-knowledge/SKILL.md +59 -0
  28. reposeed/foundation/skills/universal/compounding-knowledge/agents/openai.yaml +4 -0
  29. reposeed/foundation/skills/universal/diagnosing-bugs/SKILL.md +89 -0
  30. reposeed/foundation/skills/universal/diagnosing-bugs/agents/openai.yaml +4 -0
  31. reposeed/foundation/skills/universal/maintain-changelog/SKILL.md +60 -0
  32. reposeed/foundation/skills/universal/maintain-changelog/agents/openai.yaml +4 -0
  33. reposeed/foundation/skills/universal/pr-description/SKILL.md +55 -0
  34. reposeed/foundation/skills/universal/pr-description/agents/openai.yaml +4 -0
  35. reposeed/foundation/skills/universal/security-checklist/SKILL.md +66 -0
  36. reposeed/foundation/skills/universal/security-checklist/agents/openai.yaml +4 -0
  37. reposeed/foundation/skills/universal/tdd/SKILL.md +79 -0
  38. reposeed/foundation/skills/universal/tdd/agents/openai.yaml +4 -0
  39. reposeed/foundation/skills/universal/verification-before-completion/SKILL.md +74 -0
  40. reposeed/foundation/skills/universal/verification-before-completion/agents/openai.yaml +4 -0
  41. reposeed/foundation/skills/universal/writing-plans/SKILL.md +89 -0
  42. reposeed/foundation/skills/universal/writing-plans/agents/openai.yaml +4 -0
  43. reposeed/graphify.py +556 -0
  44. reposeed/initializer.py +328 -0
  45. reposeed/managed_file.py +147 -0
  46. reposeed/templates.py +66 -0
  47. reposeed/validation.py +26 -0
  48. reposeed-0.4.0.dist-info/METADATA +248 -0
  49. reposeed-0.4.0.dist-info/RECORD +52 -0
  50. reposeed-0.4.0.dist-info/WHEEL +4 -0
  51. reposeed-0.4.0.dist-info/entry_points.txt +3 -0
  52. reposeed-0.4.0.dist-info/licenses/LICENSE +21 -0
reposeed/__init__.py ADDED
@@ -0,0 +1,10 @@
1
+ """RepoSeed project initializer."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ __version__ = version("reposeed")
7
+ except PackageNotFoundError:
8
+ __version__ = "0.4.0"
9
+
10
+ __all__ = ["__version__"]
reposeed/__main__.py ADDED
@@ -0,0 +1,3 @@
1
+ from reposeed.cli import main
2
+
3
+ raise SystemExit(main())
reposeed/cli.py ADDED
@@ -0,0 +1,232 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ import sys
6
+ from collections.abc import Sequence
7
+ from pathlib import Path
8
+
9
+ from reposeed import __version__
10
+ from reposeed.constants import PLATFORM_LABELS, PLATFORMS, STACK_LABELS, STACKS
11
+ from reposeed.detect import detect_project
12
+ from reposeed.doctor import diagnose_project
13
+ from reposeed.errors import CliError
14
+ from reposeed.graphify import graphify_python, run_graphify
15
+ from reposeed.initializer import initialize_project
16
+
17
+
18
+ def main(argv: Sequence[str] | None = None) -> int:
19
+ arguments = list(argv if argv is not None else sys.argv[1:])
20
+ try:
21
+ if arguments[:1] == ["graphify"]:
22
+ return run_graphify(arguments[1:])
23
+ if arguments[:1] == ["_graphify-python"]:
24
+ return graphify_python()
25
+
26
+ parser = _create_parser()
27
+ namespace = parser.parse_args(arguments)
28
+ if namespace.command == "init":
29
+ return _run_init(namespace)
30
+ if namespace.command == "doctor":
31
+ return _run_doctor(namespace)
32
+ parser.print_help()
33
+ return 0
34
+ except CliError as error:
35
+ print(f"Error: {error}", file=sys.stderr)
36
+ return error.exit_code
37
+ except KeyboardInterrupt:
38
+ print("\nError: Initialization cancelled.", file=sys.stderr)
39
+ return 130
40
+
41
+
42
+ def _create_parser() -> argparse.ArgumentParser:
43
+ parser = argparse.ArgumentParser(
44
+ prog="reposeed",
45
+ description="Initialize portable agent instructions and skills for AI coding tools.",
46
+ )
47
+ parser.add_argument("--version", action="version", version=__version__)
48
+ subparsers = parser.add_subparsers(dest="command")
49
+
50
+ init = subparsers.add_parser("init", help="initialize agent standards in a project")
51
+ init.add_argument("directory", nargs="?", default=".", help="project directory")
52
+ init.add_argument(
53
+ "-p",
54
+ "--platforms",
55
+ help="comma-separated: codex, claude, cursor, antigravity",
56
+ )
57
+ init.add_argument(
58
+ "-s",
59
+ "--stacks",
60
+ help="comma-separated: node, python, php, flutter (default: auto-detect)",
61
+ )
62
+ init.add_argument("-y", "--yes", action="store_true", help="accept defaults")
63
+ init.add_argument(
64
+ "--no-graphify",
65
+ action="store_false",
66
+ dest="graphify",
67
+ default=True,
68
+ help="skip managed Graphify integration",
69
+ )
70
+ graphify_git_hooks = init.add_mutually_exclusive_group()
71
+ graphify_git_hooks.add_argument(
72
+ "--graphify-git-hooks",
73
+ action="store_true",
74
+ dest="graphify_git_hooks",
75
+ help="install Graphify Git auto-rebuild hooks (default with Graphify)",
76
+ )
77
+ graphify_git_hooks.add_argument(
78
+ "--no-graphify-git-hooks",
79
+ action="store_false",
80
+ dest="graphify_git_hooks",
81
+ help="skip or remove the default Graphify Git hooks",
82
+ )
83
+ init.set_defaults(graphify_git_hooks=None)
84
+ init.add_argument("--dry-run", action="store_true", help="show changes without writing")
85
+ init.add_argument("--force", action="store_true", help="replace conflicting generated files")
86
+ init.add_argument("--json", action="store_true", dest="json_output")
87
+
88
+ doctor = subparsers.add_parser("doctor", help="validate an initialized project")
89
+ doctor.add_argument("directory", nargs="?", default=".", help="project directory")
90
+ doctor.add_argument("--json", action="store_true", dest="json_output")
91
+
92
+ graphify = subparsers.add_parser(
93
+ "graphify",
94
+ help="run Graphify from the managed uvx environment",
95
+ )
96
+ graphify.add_argument("arguments", nargs=argparse.REMAINDER)
97
+ return parser
98
+
99
+
100
+ def _run_init(namespace: argparse.Namespace) -> int:
101
+ target = Path(namespace.directory).resolve()
102
+ detected = detect_project(target)
103
+ selected_platforms = _parse_list(namespace.platforms)
104
+ selected_stacks = _parse_list(namespace.stacks) if namespace.stacks is not None else None
105
+
106
+ if not namespace.yes and sys.stdin.isatty() and namespace.platforms is None:
107
+ selected_platforms = _prompt_selection(
108
+ "Coding agents",
109
+ list(PLATFORMS),
110
+ PLATFORM_LABELS,
111
+ list(PLATFORMS),
112
+ required=True,
113
+ )
114
+ if namespace.stacks is None:
115
+ selected_stacks = _prompt_selection(
116
+ "Application stacks",
117
+ list(STACKS),
118
+ STACK_LABELS,
119
+ list(detected["stacks"]),
120
+ required=False,
121
+ )
122
+
123
+ selected_platforms = selected_platforms or list(PLATFORMS)
124
+ if selected_stacks is None:
125
+ selected_stacks = list(detected["stacks"])
126
+
127
+ def report(message: str) -> None:
128
+ if not namespace.json_output:
129
+ print(f"→ {message}")
130
+
131
+ result = initialize_project(
132
+ target_directory=target,
133
+ platforms=selected_platforms,
134
+ stacks=selected_stacks,
135
+ graphify=namespace.graphify,
136
+ graphify_git_hooks=namespace.graphify_git_hooks,
137
+ dry_run=namespace.dry_run,
138
+ force=namespace.force,
139
+ report=report,
140
+ )
141
+ if namespace.json_output:
142
+ print(json.dumps(result, indent=2))
143
+ else:
144
+ print(_format_init_result(result, namespace.dry_run))
145
+ return 2 if any(item["status"] == "conflict" for item in result["operations"]) else 0
146
+
147
+
148
+ def _run_doctor(namespace: argparse.Namespace) -> int:
149
+ result = diagnose_project(Path(namespace.directory))
150
+ if namespace.json_output:
151
+ print(json.dumps(result, indent=2))
152
+ elif result["ok"]:
153
+ print(
154
+ f"Healthy: {len(result['checks'])} managed files verified in "
155
+ f"{result['targetDirectory']}"
156
+ )
157
+ else:
158
+ print(f"Found {len(result['issues'])} issue(s) in {result['targetDirectory']}:")
159
+ for issue in result["issues"]:
160
+ print(f"- {issue}")
161
+ return 0 if result["ok"] else 1
162
+
163
+
164
+ def _parse_list(value: str | None) -> list[str] | None:
165
+ if value is None:
166
+ return None
167
+ return list(dict.fromkeys(item.strip() for item in value.split(",") if item.strip()))
168
+
169
+
170
+ def _prompt_selection(
171
+ title: str,
172
+ choices: list[str],
173
+ labels: dict[str, str],
174
+ defaults: list[str],
175
+ *,
176
+ required: bool,
177
+ ) -> list[str]:
178
+ print(f"\n{title}:")
179
+ for index, choice in enumerate(choices, start=1):
180
+ suffix = " (default)" if choice in defaults else ""
181
+ print(f" {index}. {labels[choice]}{suffix}")
182
+ default_text = ",".join(defaults)
183
+ answer = input(f"Choose comma-separated names or numbers [{default_text}]: ").strip()
184
+ if not answer:
185
+ selected = defaults
186
+ else:
187
+ selected = []
188
+ for item in (part.strip() for part in answer.split(",")):
189
+ selected.append(choices[int(item) - 1] if item.isdigit() else item)
190
+ selected = list(dict.fromkeys(selected))
191
+ if required and not selected:
192
+ raise CliError("Select at least one option.")
193
+ return selected
194
+
195
+
196
+ def _format_init_result(result: dict[str, object], dry_run: bool) -> str:
197
+ operations = result["operations"]
198
+ groups: dict[str, list[dict[str, str]]] = {}
199
+ for operation in operations: # type: ignore[union-attr]
200
+ groups.setdefault(operation["status"], []).append(operation)
201
+ platforms = ", ".join(result["platforms"]) # type: ignore[arg-type]
202
+ stacks = ", ".join(result["stacks"]) or "universal only" # type: ignore[arg-type]
203
+ lines = [
204
+ f"{'Planned' if dry_run else 'Initialized'} {result['targetDirectory']}",
205
+ f"Platforms: {platforms}",
206
+ f"Stacks: {stacks}",
207
+ "Files: "
208
+ f"{len(groups.get('created', []))} created, "
209
+ f"{len(groups.get('updated', []))} updated, "
210
+ f"{len(groups.get('unchanged', []))} unchanged",
211
+ ]
212
+ if groups.get("conflict"):
213
+ paths = ", ".join(item["path"] for item in groups["conflict"])
214
+ lines.extend((f"Conflicts preserved: {paths}", "Review those files or rerun with --force."))
215
+ graphify = result["graphify"]
216
+ if graphify["enabled"]: # type: ignore[index]
217
+ status = "planned" if dry_run else "verified and configured"
218
+ lines.append(
219
+ f"Graphify {graphify['version']}: {status} through the uvx environment" # type: ignore[index]
220
+ )
221
+ for item in graphify["platforms"]: # type: ignore[index]
222
+ if item.get("platform"):
223
+ lines.append(f"Graphify ({item['platform']}): {item['status']}")
224
+ git_hooks = graphify.get("gitHooks", {}) # type: ignore[union-attr]
225
+ if git_hooks.get("enabled") or git_hooks.get("status") in {
226
+ "removed",
227
+ "planned-removal",
228
+ "skipped-no-git",
229
+ }:
230
+ lines.append(f"Graphify Git hooks: {git_hooks['status']}")
231
+ lines.append("Run `uvx reposeed doctor` to verify the installation.")
232
+ return "\n".join(lines)
reposeed/constants.py ADDED
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+
3
+ from importlib.resources import files
4
+
5
+ FOUNDATION_ROOT = files("reposeed").joinpath("foundation")
6
+
7
+ PLATFORMS = ("codex", "claude", "cursor", "antigravity")
8
+ STACKS = ("node", "python", "php", "flutter")
9
+
10
+ PLATFORM_LABELS = {
11
+ "codex": "Codex",
12
+ "claude": "Claude Code",
13
+ "cursor": "Cursor",
14
+ "antigravity": "Antigravity",
15
+ }
16
+ STACK_LABELS = {
17
+ "node": "Node.js / TypeScript",
18
+ "python": "Python",
19
+ "php": "PHP",
20
+ "flutter": "Flutter / Dart",
21
+ }
22
+
23
+ MANAGED_BEGIN = "<!-- reposeed:begin -->"
24
+ MANAGED_END = "<!-- reposeed:end -->"
25
+ MANIFEST_PATH = ".reposeed/manifest.json"
26
+ CONFIG_PATH = ".reposeed/config.json"
27
+
28
+ GRAPHIFY_PACKAGE = "graphifyy"
29
+ GRAPHIFY_VERSION = "0.9.49"
30
+ GRAPHIFY_PLATFORMS = {
31
+ "codex": "codex",
32
+ "claude": "claude",
33
+ "cursor": "cursor",
34
+ "antigravity": "antigravity",
35
+ }
reposeed/detect.py ADDED
@@ -0,0 +1,89 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+
8
+ def _read_json(path: Path) -> dict[str, Any] | None:
9
+ try:
10
+ return json.loads(path.read_text(encoding="utf-8"))
11
+ except (FileNotFoundError, json.JSONDecodeError):
12
+ return None
13
+
14
+
15
+ def detect_project(target_directory: Path) -> dict[str, list[Any]]:
16
+ package_json = _read_json(target_directory / "package.json")
17
+ stacks: list[str] = []
18
+ if package_json:
19
+ stacks.append("node")
20
+ if any(
21
+ (target_directory / name).exists()
22
+ for name in ("pyproject.toml", "requirements.txt", "setup.py")
23
+ ):
24
+ stacks.append("python")
25
+ if any((target_directory / name).exists() for name in ("composer.json", "artisan")):
26
+ stacks.append("php")
27
+ if (target_directory / "pubspec.yaml").exists():
28
+ stacks.append("flutter")
29
+
30
+ return {
31
+ "stacks": list(dict.fromkeys(stacks)),
32
+ "commands": _detect_commands(target_directory, package_json),
33
+ }
34
+
35
+
36
+ def _detect_commands(
37
+ target_directory: Path, package_json: dict[str, Any] | None
38
+ ) -> list[dict[str, str]]:
39
+ commands: list[dict[str, str]] = []
40
+ if package_json:
41
+ runner, install = "npm", "npm install"
42
+ if (target_directory / "pnpm-lock.yaml").exists():
43
+ runner, install = "pnpm", "pnpm install"
44
+ elif (target_directory / "yarn.lock").exists():
45
+ runner, install = "yarn", "yarn install"
46
+ elif any((target_directory / name).exists() for name in ("bun.lock", "bun.lockb")):
47
+ runner, install = "bun", "bun install"
48
+ commands.append({"label": "Install", "command": install})
49
+ scripts = package_json.get("scripts", {})
50
+ for name in ("lint", "typecheck", "test", "build"):
51
+ if scripts.get(name):
52
+ separator = " run " if runner == "npm" else " "
53
+ commands.append({"label": name.title(), "command": f"{runner}{separator}{name}"})
54
+
55
+ if (target_directory / "pyproject.toml").exists():
56
+ if (target_directory / "uv.lock").exists():
57
+ commands.append({"label": "Install Python", "command": "uv sync"})
58
+ commands.append({"label": "Test Python", "command": "pytest"})
59
+ elif (target_directory / "requirements.txt").exists():
60
+ commands.extend(
61
+ [
62
+ {
63
+ "label": "Install Python",
64
+ "command": "python -m pip install -r requirements.txt",
65
+ },
66
+ {"label": "Test Python", "command": "pytest"},
67
+ ]
68
+ )
69
+
70
+ if (target_directory / "composer.json").exists():
71
+ commands.extend(
72
+ [
73
+ {"label": "Install PHP", "command": "composer install"},
74
+ {"label": "Test PHP", "command": "composer test"},
75
+ ]
76
+ )
77
+ if (target_directory / "pubspec.yaml").exists():
78
+ commands.extend(
79
+ [
80
+ {"label": "Install Flutter", "command": "flutter pub get"},
81
+ {"label": "Analyze Flutter", "command": "flutter analyze"},
82
+ {"label": "Test Flutter", "command": "flutter test"},
83
+ ]
84
+ )
85
+
86
+ unique: dict[str, dict[str, str]] = {}
87
+ for command in commands:
88
+ unique.setdefault(command["command"], command)
89
+ return list(unique.values())
reposeed/doctor.py ADDED
@@ -0,0 +1,122 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ from reposeed.constants import CONFIG_PATH, GRAPHIFY_VERSION, MANIFEST_PATH
6
+ from reposeed.graphify import (
7
+ RunCommand,
8
+ default_run_command,
9
+ expected_graphify_files,
10
+ graphify_git_hooks_healthy,
11
+ inspect_graphify,
12
+ inspect_graphify_git_hooks,
13
+ )
14
+ from reposeed.managed_file import content_hash, extract_managed_block, read_json, read_text
15
+ from reposeed.validation import validate_skill
16
+
17
+
18
+ def diagnose_project(
19
+ target_directory: Path, run_command: RunCommand = default_run_command
20
+ ) -> dict[str, object]:
21
+ root = target_directory.resolve()
22
+ config = read_json(root / CONFIG_PATH)
23
+ manifest = read_json(root / MANIFEST_PATH)
24
+ issues: list[str] = []
25
+ checks: list[str] = []
26
+
27
+ if not config:
28
+ issues.append(f"{CONFIG_PATH} is missing; run `uvx reposeed init`.")
29
+ if not manifest:
30
+ issues.append(f"{MANIFEST_PATH} is missing; run `uvx reposeed init`.")
31
+
32
+ if manifest:
33
+ for relative_path, record in manifest.get("files", {}).items():
34
+ content = read_text(root / relative_path)
35
+ if content is None:
36
+ issues.append(f"{relative_path} is missing.")
37
+ continue
38
+ tracked = (
39
+ extract_managed_block(content) if record.get("mode") == "managed-block" else content
40
+ )
41
+ if tracked is None or content_hash(tracked) != record.get("sha256"):
42
+ issues.append(f"{relative_path} has local changes.")
43
+ else:
44
+ checks.append(f"{relative_path} matches the manifest.")
45
+
46
+ skills_directory = root / ".agents" / "skills"
47
+ if not skills_directory.is_dir():
48
+ issues.append(".agents/skills is missing.")
49
+ else:
50
+ for entry in sorted(skills_directory.iterdir()):
51
+ if not entry.is_dir():
52
+ continue
53
+ relative_path = f".agents/skills/{entry.name}/SKILL.md"
54
+ content = read_text(root / relative_path)
55
+ if content is None:
56
+ issues.append(f"{relative_path} is missing.")
57
+ else:
58
+ issues.extend(validate_skill(content, entry.name, relative_path))
59
+
60
+ graphify_config = config.get("graphify") if config else None
61
+ graphify_enabled = graphify_config is True or (
62
+ isinstance(graphify_config, dict) and graphify_config.get("enabled") is True
63
+ )
64
+ if graphify_enabled:
65
+ extraction_mode = (
66
+ graphify_config.get("extractionMode") if isinstance(graphify_config, dict) else None
67
+ )
68
+ if extraction_mode != "ast-only":
69
+ issues.append(
70
+ "Managed Graphify is not configured for AST-only extraction; rerun "
71
+ "uvx reposeed init."
72
+ )
73
+ else:
74
+ checks.append("Managed Graphify extraction mode is AST-only.")
75
+
76
+ expected_version = (
77
+ graphify_config.get("version", GRAPHIFY_VERSION)
78
+ if isinstance(graphify_config, dict)
79
+ else GRAPHIFY_VERSION
80
+ )
81
+ installed = inspect_graphify(root, run_command)
82
+ if not installed:
83
+ issues.append("Managed Graphify runtime is unavailable in the uvx environment.")
84
+ elif installed["version"] != expected_version:
85
+ issues.append(
86
+ f"Managed Graphify version is {installed['version']}; expected {expected_version}."
87
+ )
88
+ else:
89
+ checks.append(f"Managed Graphify {installed['version']} is available.")
90
+
91
+ graphify_platforms = (
92
+ graphify_config.get("platforms", config.get("platforms", []))
93
+ if isinstance(graphify_config, dict)
94
+ else config.get("platforms", [])
95
+ )
96
+ for relative_path in expected_graphify_files(graphify_platforms):
97
+ if read_text(root / relative_path) is None:
98
+ issues.append(f"{relative_path} is missing from the Graphify integration.")
99
+ else:
100
+ checks.append(f"{relative_path} is configured for Graphify.")
101
+
102
+ git_hooks = graphify_config.get("gitHooks", {}) if isinstance(graphify_config, dict) else {}
103
+ git_hooks_enabled = git_hooks is True or (
104
+ isinstance(git_hooks, dict) and git_hooks.get("enabled") is True
105
+ )
106
+ if git_hooks_enabled:
107
+ git_hook_status = inspect_graphify_git_hooks(root, run_command)
108
+ if graphify_git_hooks_healthy(git_hook_status):
109
+ checks.append("Graphify Git hooks are installed and current.")
110
+ else:
111
+ issues.append(
112
+ "Graphify Git hooks are missing or out of date; rerun "
113
+ "uvx reposeed init --graphify-git-hooks."
114
+ )
115
+
116
+ return {
117
+ "ok": not issues,
118
+ "targetDirectory": str(root),
119
+ "config": config,
120
+ "checks": checks,
121
+ "issues": issues,
122
+ }
reposeed/errors.py ADDED
@@ -0,0 +1,4 @@
1
+ class CliError(Exception):
2
+ def __init__(self, message: str, *, exit_code: int = 1) -> None:
3
+ super().__init__(message)
4
+ self.exit_code = exit_code
@@ -0,0 +1,108 @@
1
+ # Shared agent conventions
2
+
3
+ Use these conventions as the repository-wide baseline. Read
4
+ `AGENTS.local.md` before non-trivial work for this project's real commands and
5
+ exceptions. Guidance closer to the code takes precedence when it is more
6
+ specific.
7
+
8
+ ## Before changing code
9
+
10
+ - Surface material assumptions. Inspect the repository first; ask only when a
11
+ choice cannot be discovered and different answers would materially change
12
+ the result. State low-risk, reversible assumptions while proceeding.
13
+ - Define a checkable completion criterion before implementation. Include the
14
+ behavior to preserve, the behavior to add, and the verification that will
15
+ prove both.
16
+ - Build the smallest coherent change that meets that criterion. Avoid
17
+ speculative abstractions and unrelated cleanup.
18
+ - Match the surrounding architecture and style. Record a nearby issue instead
19
+ of silently expanding the task to fix it.
20
+ - Use the `clarify-before-building` skill for ambiguous or non-trivial work.
21
+ - Use `writing-plans` when asked for an execution-ready implementation plan;
22
+ planning-only requests do not authorize code changes.
23
+
24
+ ## Project knowledge
25
+
26
+ - Read `CONTEXT.md` before making a domain-model decision when it exists.
27
+ - Read relevant records in `docs/adr/` before changing an established
28
+ architectural seam. Add an ADR when a durable, consequential decision is
29
+ made and the repository uses that convention.
30
+ - Treat configuration, package scripts, tests, and existing code as the source
31
+ of truth. Keep `AGENTS.local.md` focused on conventions that cannot be
32
+ discovered cheaply from those files.
33
+
34
+ ## Context efficiency
35
+
36
+ - Start with the smallest reliable source: targeted search, configuration, and
37
+ relevant code or test slices. Expand only when the evidence is insufficient.
38
+ - Reuse existing indexes, summaries, and cached results. Reference large
39
+ artifacts by path instead of pasting or rereading them in full.
40
+ - Keep plans, progress updates, and handoffs concise and decision-focused.
41
+ - Use token-heavy semantic analysis or parallel agents only when their expected
42
+ value justifies the duplicated context; do not use them as routine discovery.
43
+
44
+ ## Implementation baseline
45
+
46
+ - Use `diagnosing-bugs` for bugs, failures, regressions, or unexpected
47
+ behavior. Confirm root cause before proposing a fix, and keep diagnosis
48
+ read-only unless a fix was requested.
49
+ - Use `tdd` when the user explicitly requests test-first development or a
50
+ red-green-refactor workflow.
51
+ - Add or update tests for behavior changes. A bug fix needs a regression test
52
+ that fails without the fix. If a test is impractical, explain the concrete
53
+ reason and provide proportionate verification.
54
+ - Validate untrusted input at system boundaries. Keep authorization checks on
55
+ the resource being accessed, set timeouts on external calls, and avoid
56
+ leaking internal errors to users.
57
+ - Keep secrets out of source, fixtures, logs, prompts, and commit history. Use
58
+ the project's established secret and configuration mechanism.
59
+ - Prefer existing dependencies and standard-library capabilities. Check
60
+ maintenance, licensing, security, bundle/runtime cost, and project fit before
61
+ adding a production dependency.
62
+ - Preserve public interfaces unless the requested change explicitly includes a
63
+ migration. Document intentional breaking changes.
64
+ - Write comments for constraints and non-obvious reasoning, not a narration of
65
+ the code.
66
+
67
+ ## Verification and delivery
68
+
69
+ - Run the narrowest relevant checks while iterating, then the project's full
70
+ lint, type-check, and test commands before handing off when practical.
71
+ - Report exactly what was verified and any remaining risk. Never describe a
72
+ check as passing if it was not run.
73
+ - Use `verification-before-completion` before claiming changed work is fixed,
74
+ passing, complete, or ready to ship.
75
+ - Use `code-review` for review-only requests and report actionable findings
76
+ before summaries. Do not modify reviewed code unless asked.
77
+ - Use `security-checklist` for auth, authorization, payments, user data, file
78
+ uploads, or external services.
79
+ - Use `maintain-changelog` before every commit so the dated changelog entry is
80
+ included in the same commit as the change.
81
+ - Use `commit-messages` when composing commits and `pr-description` when
82
+ preparing pull requests.
83
+ - Use `compounding-knowledge` after work that uncovered a reusable convention,
84
+ debugging technique, or repository-specific trap.
85
+
86
+ ## Collaboration and source control
87
+
88
+ - Preserve user-authored and unrelated working-tree changes. Resolve overlap
89
+ explicitly instead of overwriting it.
90
+ - Keep commits and pull requests focused on one reviewable outcome. Follow the
91
+ repository's branch and review policy; never push or merge unless the user or
92
+ workflow authorizes it.
93
+ - Check for active parallel work before editing shared files when the project
94
+ provides an active-work registry or coordination mechanism.
95
+
96
+ ## Optional knowledge graph
97
+
98
+ When `graphify-out/graph.json` exists, use `graphify query "<question>"` for
99
+ architectural discovery before broad repository scans, and set a small
100
+ `--budget` appropriate to the question. Prefer `query`, `path`, or `explain`
101
+ over loading the full report or raw graph. Preserve any Graphify-managed
102
+ sections when updating instruction files. See
103
+ `.reposeed/guides/GRAPHIFY.md` for setup and operating details.
104
+ After the final code edit in a task, refresh an existing graph with
105
+ `uvx reposeed graphify update .` before handoff. This AST-only refresh is
106
+ the complete managed update path. Managed Graphify is AST-only: do not run
107
+ semantic extraction, media transcription, deep mode, semantic deduplication,
108
+ or LLM community labeling through RepoSeed.
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ This file records meaningful changes to the project for humans and agents.
4
+
5
+ ## [Unreleased]