@pennyfarthing/core 7.8.1 → 7.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/pennyfarthing-dist/scripts/core/prime.sh +8 -0
- package/pennyfarthing_scripts/__init__.py +17 -0
- package/pennyfarthing_scripts/__pycache__/__init__.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/config.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira_epic_creation.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira_sync.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira_sync_story.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/sprint.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/workflow.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bellmode_hook.py +154 -0
- package/pennyfarthing_scripts/brownfield/__init__.py +35 -0
- package/pennyfarthing_scripts/brownfield/__main__.py +7 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/discover.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/brownfield/cli.py +131 -0
- package/pennyfarthing_scripts/brownfield/discover.py +753 -0
- package/pennyfarthing_scripts/common/__init__.py +49 -0
- package/pennyfarthing_scripts/common/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/output.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/config.py +65 -0
- package/pennyfarthing_scripts/common/output.py +180 -0
- package/pennyfarthing_scripts/config.py +21 -0
- package/pennyfarthing_scripts/git/__init__.py +29 -0
- package/pennyfarthing_scripts/git/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/__pycache__/create_branches.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/__pycache__/status_all.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/create_branches.py +439 -0
- package/pennyfarthing_scripts/git/status_all.py +310 -0
- package/pennyfarthing_scripts/hooks.py +455 -0
- package/pennyfarthing_scripts/jira/__init__.py +93 -0
- package/pennyfarthing_scripts/jira/__main__.py +10 -0
- package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/claim.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/client.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/compat.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/epic.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/mappings.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/story.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/sync.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/bidirectional.py +561 -0
- package/pennyfarthing_scripts/jira/claim.py +211 -0
- package/pennyfarthing_scripts/jira/cli.py +150 -0
- package/pennyfarthing_scripts/jira/client.py +613 -0
- package/pennyfarthing_scripts/jira/epic.py +176 -0
- package/pennyfarthing_scripts/jira/story.py +219 -0
- package/pennyfarthing_scripts/jira/sync.py +350 -0
- package/pennyfarthing_scripts/jira_bidirectional_sync.py +37 -0
- package/pennyfarthing_scripts/jira_epic_creation.py +30 -0
- package/pennyfarthing_scripts/jira_sync.py +36 -0
- package/pennyfarthing_scripts/jira_sync_story.py +30 -0
- package/pennyfarthing_scripts/output.py +37 -0
- package/pennyfarthing_scripts/preflight/__init__.py +17 -0
- package/pennyfarthing_scripts/preflight/__main__.py +10 -0
- package/pennyfarthing_scripts/preflight/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/__pycache__/finish.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/cli.py +141 -0
- package/pennyfarthing_scripts/preflight/finish.py +382 -0
- package/pennyfarthing_scripts/pretooluse_hook.py +142 -0
- package/pennyfarthing_scripts/prime/__init__.py +38 -0
- package/pennyfarthing_scripts/prime/__main__.py +8 -0
- package/pennyfarthing_scripts/prime/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/loader.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/persona.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/session.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/cli.py +220 -0
- package/pennyfarthing_scripts/prime/loader.py +239 -0
- package/pennyfarthing_scripts/sprint/__init__.py +66 -0
- package/pennyfarthing_scripts/sprint/__main__.py +10 -0
- package/pennyfarthing_scripts/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/status.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/archive.py +108 -0
- package/pennyfarthing_scripts/sprint/cli.py +124 -0
- package/pennyfarthing_scripts/sprint/loader.py +193 -0
- package/pennyfarthing_scripts/sprint/status.py +122 -0
- package/pennyfarthing_scripts/sprint/validator.py +405 -0
- package/pennyfarthing_scripts/sprint/work.py +192 -0
- package/pennyfarthing_scripts/story/__init__.py +67 -0
- package/pennyfarthing_scripts/story/__main__.py +10 -0
- package/pennyfarthing_scripts/story/cli.py +105 -0
- package/pennyfarthing_scripts/story/create.py +167 -0
- package/pennyfarthing_scripts/story/size.py +113 -0
- package/pennyfarthing_scripts/story/template.py +151 -0
- package/pennyfarthing_scripts/swebench.py +216 -0
- package/pennyfarthing_scripts/tests/__init__.py +1 -0
- package/pennyfarthing_scripts/tests/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_brownfield.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_git_utils.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_prime.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/conftest.py +106 -0
- package/pennyfarthing_scripts/tests/test_brownfield.py +842 -0
- package/pennyfarthing_scripts/tests/test_cli_modules.py +245 -0
- package/pennyfarthing_scripts/tests/test_common.py +180 -0
- package/pennyfarthing_scripts/tests/test_git_utils.py +866 -0
- package/pennyfarthing_scripts/tests/test_jira_package.py +334 -0
- package/pennyfarthing_scripts/tests/test_package_structure.py +372 -0
- package/pennyfarthing_scripts/tests/test_prime.py +397 -0
- package/pennyfarthing_scripts/tests/test_sprint_package.py +236 -0
- package/pennyfarthing_scripts/tests/test_sprint_validator.py +675 -0
- package/pennyfarthing_scripts/tests/test_story_package.py +156 -0
- package/pennyfarthing_scripts/welcome_hook.py +157 -0
- package/pennyfarthing_scripts/workflow.py +183 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pennyfarthing/core",
|
|
3
|
-
"version": "7.8.
|
|
3
|
+
"version": "7.8.2",
|
|
4
4
|
"description": "Claude Code agent framework with TDD workflow and persona system",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"pennyfarthing-dist/skills/",
|
|
19
19
|
"pennyfarthing-dist/templates/",
|
|
20
20
|
"pennyfarthing-dist/workflows/",
|
|
21
|
+
"pennyfarthing_scripts/",
|
|
21
22
|
"README.md"
|
|
22
23
|
],
|
|
23
24
|
"repository": {
|
|
@@ -4,4 +4,12 @@
|
|
|
4
4
|
#
|
|
5
5
|
# Thin wrapper around python -m pennyfarthing_scripts.prime
|
|
6
6
|
|
|
7
|
+
# Find the package root (where pennyfarthing_scripts lives)
|
|
8
|
+
# Works for both npm installs (node_modules/@pennyfarthing/core) and local dev
|
|
9
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
|
+
PACKAGE_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
|
11
|
+
|
|
12
|
+
# Set PYTHONPATH so Python can find pennyfarthing_scripts
|
|
13
|
+
export PYTHONPATH="${PACKAGE_ROOT}:${PYTHONPATH:-}"
|
|
14
|
+
|
|
7
15
|
exec python3 -m pennyfarthing_scripts.prime "$@"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pennyfarthing Scripts - Python utilities for agent orchestration.
|
|
3
|
+
|
|
4
|
+
Modules:
|
|
5
|
+
config: Project root detection, YAML configuration loading
|
|
6
|
+
output: Colored console output utilities (success, info, warn, error)
|
|
7
|
+
sprint: Sprint YAML parsing and story access
|
|
8
|
+
jira: Jira CLI wrapper and JiraClient for REST API operations
|
|
9
|
+
jira_sync: Async epic sync to Jira
|
|
10
|
+
jira_sync_story: Single story sync to Jira
|
|
11
|
+
jira_epic_creation: Create Jira epics from sprint YAML
|
|
12
|
+
jira_bidirectional_sync: Bidirectional sync between sprint YAML and Jira
|
|
13
|
+
preflight: Async preflight checks for workflow completion
|
|
14
|
+
swebench: SWE-bench patch parsing and scenario utilities
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
__version__ = "7.6.1"
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Bell Mode PostToolUse Hook (Python)
|
|
4
|
+
|
|
5
|
+
This hook is called by Claude Code after each tool execution.
|
|
6
|
+
When bell mode is enabled and there are queued messages, it returns
|
|
7
|
+
the first queued message as additionalContext to be injected into
|
|
8
|
+
Claude's next API call.
|
|
9
|
+
|
|
10
|
+
Configuration files:
|
|
11
|
+
.pennyfarthing/config.local.yaml - workflow.bell_mode: true/false
|
|
12
|
+
.pennyfarthing/bell-queue.json - [{"text": "...", "images": [...]}, ...]
|
|
13
|
+
|
|
14
|
+
Output format (when injecting):
|
|
15
|
+
{
|
|
16
|
+
"hookSpecificOutput": {
|
|
17
|
+
"hookEventName": "PostToolUse",
|
|
18
|
+
"additionalContext": "User feedback: <message>"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
Output when disabled or queue empty: (nothing - exit 0)
|
|
23
|
+
|
|
24
|
+
Story: MSSCI-12409 - Hook consistency and WheelHub consolidation
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import json
|
|
28
|
+
import sys
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
|
|
31
|
+
# Add parent directory to path for imports
|
|
32
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
33
|
+
|
|
34
|
+
from hooks import (
|
|
35
|
+
find_project_root,
|
|
36
|
+
get_cyclist_port,
|
|
37
|
+
send_to_cyclist,
|
|
38
|
+
output_hook_response,
|
|
39
|
+
HookResponse,
|
|
40
|
+
is_bell_mode_enabled,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def read_bell_queue(project_root: Path) -> list[dict]:
|
|
45
|
+
"""Read the bell message queue.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
project_root: Project root directory
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
List of queued messages, or empty list if none
|
|
52
|
+
"""
|
|
53
|
+
queue_path = project_root / ".pennyfarthing" / "bell-queue.json"
|
|
54
|
+
if not queue_path.exists():
|
|
55
|
+
return []
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
with open(queue_path) as f:
|
|
59
|
+
queue = json.load(f)
|
|
60
|
+
if isinstance(queue, list):
|
|
61
|
+
return queue
|
|
62
|
+
except (json.JSONDecodeError, OSError):
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
return []
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def dequeue_message(project_root: Path) -> None:
|
|
69
|
+
"""Remove the first message from the queue.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
project_root: Project root directory
|
|
73
|
+
"""
|
|
74
|
+
queue_path = project_root / ".pennyfarthing" / "bell-queue.json"
|
|
75
|
+
if not queue_path.exists():
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
with open(queue_path) as f:
|
|
80
|
+
queue = json.load(f)
|
|
81
|
+
|
|
82
|
+
if isinstance(queue, list) and len(queue) > 0:
|
|
83
|
+
queue = queue[1:] # Remove first item
|
|
84
|
+
with open(queue_path, "w") as f:
|
|
85
|
+
json.dump(queue, f)
|
|
86
|
+
except (json.JSONDecodeError, OSError):
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def notify_cyclist(project_root: Path, message_text: str) -> None:
|
|
91
|
+
"""Notify Cyclist browser that a queued message was consumed.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
project_root: Project root directory
|
|
95
|
+
message_text: The message text that was consumed
|
|
96
|
+
"""
|
|
97
|
+
try:
|
|
98
|
+
send_to_cyclist(
|
|
99
|
+
endpoint="/api/bell-consumed",
|
|
100
|
+
data={"text": message_text},
|
|
101
|
+
project_root=project_root,
|
|
102
|
+
timeout=5,
|
|
103
|
+
)
|
|
104
|
+
except Exception:
|
|
105
|
+
# Ignore errors - don't block hook
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def main() -> None:
|
|
110
|
+
"""Main entry point for PostToolUse bell mode hook."""
|
|
111
|
+
try:
|
|
112
|
+
# Read and discard stdin (required by hook protocol)
|
|
113
|
+
sys.stdin.read()
|
|
114
|
+
|
|
115
|
+
# Find project root
|
|
116
|
+
project_root = find_project_root()
|
|
117
|
+
if not project_root:
|
|
118
|
+
sys.exit(0)
|
|
119
|
+
|
|
120
|
+
# Check if bell mode is enabled
|
|
121
|
+
if not is_bell_mode_enabled(project_root):
|
|
122
|
+
sys.exit(0)
|
|
123
|
+
|
|
124
|
+
# Read queue
|
|
125
|
+
queue = read_bell_queue(project_root)
|
|
126
|
+
if not queue:
|
|
127
|
+
sys.exit(0)
|
|
128
|
+
|
|
129
|
+
# Get first message
|
|
130
|
+
first_message = queue[0]
|
|
131
|
+
message_text = first_message.get("text", "")
|
|
132
|
+
if not message_text:
|
|
133
|
+
sys.exit(0)
|
|
134
|
+
|
|
135
|
+
# Output hook response with additionalContext
|
|
136
|
+
output_hook_response(HookResponse(
|
|
137
|
+
event_name="PostToolUse",
|
|
138
|
+
additional_context=f"User feedback: {message_text}",
|
|
139
|
+
))
|
|
140
|
+
|
|
141
|
+
# Dequeue message and notify Cyclist (in background-ish - after output)
|
|
142
|
+
dequeue_message(project_root)
|
|
143
|
+
notify_cyclist(project_root, message_text)
|
|
144
|
+
|
|
145
|
+
sys.exit(0)
|
|
146
|
+
|
|
147
|
+
except Exception as e:
|
|
148
|
+
# On error, exit silently
|
|
149
|
+
print(f"[bellmode-hook] Error: {e}", file=sys.stderr)
|
|
150
|
+
sys.exit(0)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
if __name__ == "__main__":
|
|
154
|
+
main()
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Brownfield discovery module for Pennyfarthing.
|
|
3
|
+
|
|
4
|
+
Analyzes existing codebases and generates AI-ready documentation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pennyfarthing_scripts.brownfield.discover import (
|
|
8
|
+
DepthLevel,
|
|
9
|
+
ProjectType,
|
|
10
|
+
DiscoveryResult,
|
|
11
|
+
detect_project_type,
|
|
12
|
+
detect_tech_stack,
|
|
13
|
+
scan_directory_structure,
|
|
14
|
+
detect_architecture_patterns,
|
|
15
|
+
generate_project_overview,
|
|
16
|
+
generate_tech_stack_doc,
|
|
17
|
+
generate_source_tree_doc,
|
|
18
|
+
generate_ai_guidance_doc,
|
|
19
|
+
discover,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"DepthLevel",
|
|
24
|
+
"ProjectType",
|
|
25
|
+
"DiscoveryResult",
|
|
26
|
+
"detect_project_type",
|
|
27
|
+
"detect_tech_stack",
|
|
28
|
+
"scan_directory_structure",
|
|
29
|
+
"detect_architecture_patterns",
|
|
30
|
+
"generate_project_overview",
|
|
31
|
+
"generate_tech_stack_doc",
|
|
32
|
+
"generate_source_tree_doc",
|
|
33
|
+
"generate_ai_guidance_doc",
|
|
34
|
+
"discover",
|
|
35
|
+
]
|
|
Binary file
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Brownfield CLI - Analyze existing codebases.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python -m pennyfarthing_scripts.brownfield <subcommand> [args]
|
|
6
|
+
|
|
7
|
+
Subcommands:
|
|
8
|
+
scan Scan codebase and generate documentation
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import asyncio
|
|
13
|
+
import sys
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from pennyfarthing_scripts.brownfield.discover import (
|
|
18
|
+
DepthLevel,
|
|
19
|
+
discover,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def scan(args: list[str]) -> int:
|
|
24
|
+
"""Scan codebase and generate documentation."""
|
|
25
|
+
parser = argparse.ArgumentParser(
|
|
26
|
+
prog="brownfield scan",
|
|
27
|
+
description="Scan codebase and generate AI-ready documentation",
|
|
28
|
+
)
|
|
29
|
+
parser.add_argument(
|
|
30
|
+
"path",
|
|
31
|
+
nargs="?",
|
|
32
|
+
default=".",
|
|
33
|
+
help="Path to scan (default: current directory)",
|
|
34
|
+
)
|
|
35
|
+
parser.add_argument(
|
|
36
|
+
"--depth",
|
|
37
|
+
choices=["quick", "standard", "deep"],
|
|
38
|
+
default="standard",
|
|
39
|
+
help="Scan depth level",
|
|
40
|
+
)
|
|
41
|
+
parser.add_argument(
|
|
42
|
+
"--output", "-o",
|
|
43
|
+
help="Output directory for generated docs",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
parsed = parser.parse_args(args)
|
|
47
|
+
|
|
48
|
+
path = Path(parsed.path).resolve()
|
|
49
|
+
depth = DepthLevel(parsed.depth)
|
|
50
|
+
output_dir = Path(parsed.output) if parsed.output else None
|
|
51
|
+
|
|
52
|
+
result = asyncio.run(discover(path, depth, output_dir))
|
|
53
|
+
|
|
54
|
+
if not result.success:
|
|
55
|
+
print(f"Error: {result.error}", file=sys.stderr)
|
|
56
|
+
return 1
|
|
57
|
+
|
|
58
|
+
print(f"Discovery complete: {result.project_name}")
|
|
59
|
+
print(f"Type: {result.project_type.value}")
|
|
60
|
+
print(f"Tech stack: {len(result.tech_stack)} items detected")
|
|
61
|
+
|
|
62
|
+
return 0
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# Subcommand registry
|
|
66
|
+
SUBCOMMANDS = {
|
|
67
|
+
"scan": scan,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def cli(args: list[str] | None = None) -> int:
|
|
72
|
+
"""Main CLI entry point.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
args: Command line arguments (defaults to sys.argv[1:])
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Exit code
|
|
79
|
+
"""
|
|
80
|
+
if args is None:
|
|
81
|
+
args = sys.argv[1:]
|
|
82
|
+
|
|
83
|
+
parser = argparse.ArgumentParser(
|
|
84
|
+
prog="brownfield",
|
|
85
|
+
description="Brownfield codebase discovery for Pennyfarthing",
|
|
86
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
87
|
+
epilog="""
|
|
88
|
+
Subcommands:
|
|
89
|
+
scan [path] [opts] Scan codebase and generate docs
|
|
90
|
+
|
|
91
|
+
Examples:
|
|
92
|
+
brownfield scan
|
|
93
|
+
brownfield scan /path/to/project
|
|
94
|
+
brownfield scan --depth deep
|
|
95
|
+
brownfield scan . --output docs/brownfield
|
|
96
|
+
""",
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
parser.add_argument(
|
|
100
|
+
"subcommand",
|
|
101
|
+
nargs="?",
|
|
102
|
+
choices=list(SUBCOMMANDS.keys()),
|
|
103
|
+
help="Subcommand to run",
|
|
104
|
+
)
|
|
105
|
+
parser.add_argument(
|
|
106
|
+
"args",
|
|
107
|
+
nargs=argparse.REMAINDER,
|
|
108
|
+
help="Arguments for subcommand",
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
parsed = parser.parse_args(args)
|
|
112
|
+
|
|
113
|
+
if not parsed.subcommand:
|
|
114
|
+
parser.print_help()
|
|
115
|
+
return 0
|
|
116
|
+
|
|
117
|
+
handler = SUBCOMMANDS.get(parsed.subcommand)
|
|
118
|
+
if handler:
|
|
119
|
+
return handler(parsed.args)
|
|
120
|
+
else:
|
|
121
|
+
print(f"Unknown subcommand: {parsed.subcommand}", file=sys.stderr)
|
|
122
|
+
return 1
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def main(args: list[str] | None = None) -> int:
|
|
126
|
+
"""Alias for cli()."""
|
|
127
|
+
return cli(args)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
if __name__ == "__main__":
|
|
131
|
+
sys.exit(cli())
|