@flydocs/cli 0.6.0-alpha.2 → 0.6.0-alpha.21
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/dist/cli.js +705 -393
- package/package.json +1 -1
- package/template/.claude/CLAUDE.md +62 -63
- package/template/.claude/agents/implementation-agent.md +1 -1
- package/template/.claude/agents/pm-agent.md +1 -1
- package/template/.claude/commands/activate.md +1 -1
- package/template/.claude/commands/attach.md +1 -1
- package/template/.claude/commands/block.md +2 -2
- package/template/.claude/commands/capture.md +1 -1
- package/template/.claude/commands/close.md +1 -1
- package/template/.claude/commands/flydocs-setup.md +387 -74
- package/template/.claude/commands/flydocs-upgrade.md +48 -37
- package/template/.claude/commands/implement.md +1 -1
- package/template/.claude/commands/knowledge.md +61 -0
- package/template/.claude/commands/new-project.md +1 -1
- package/template/.claude/commands/onboard.md +275 -0
- package/template/.claude/commands/project-update.md +1 -1
- package/template/.claude/commands/refine.md +1 -1
- package/template/.claude/commands/review.md +1 -1
- package/template/.claude/commands/start-session.md +1 -1
- package/template/.claude/commands/status.md +1 -1
- package/template/.claude/commands/validate.md +1 -1
- package/template/.claude/commands/wrap-session.md +1 -1
- package/template/.claude/hooks/auto-approve.py +132 -0
- package/template/.claude/hooks/post-pr-check.py +108 -0
- package/template/.claude/hooks/post-transition-check.py +94 -0
- package/template/.claude/hooks/prompt-submit.py +513 -0
- package/template/.claude/hooks/session-start.py +146 -0
- package/template/.claude/hooks/stop-gate.py +109 -0
- package/template/.claude/settings.json +41 -4
- package/template/.claude/skills/README.md +23 -25
- package/template/.claude/skills/flydocs-workflow/SKILL.md +134 -42
- package/template/.claude/skills/flydocs-workflow/cursor-rule.mdc +9 -8
- package/template/.claude/skills/flydocs-workflow/reference/comment-templates.md +1 -0
- package/template/.claude/skills/flydocs-workflow/reference/golden-rules.md +28 -17
- package/template/.claude/skills/flydocs-workflow/reference/graph-schema.md +116 -0
- package/template/.claude/skills/flydocs-workflow/reference/pr-workflow.md +120 -0
- package/template/.claude/skills/flydocs-workflow/reference/priority-estimates.md +37 -15
- package/template/.claude/skills/flydocs-workflow/reference/service-descriptor-schema.md +251 -0
- package/template/.claude/skills/flydocs-workflow/reference/status-workflow.md +26 -26
- package/template/.claude/skills/flydocs-workflow/scripts/_local/__init__.py +0 -0
- package/template/.claude/skills/{flydocs-local/scripts/flydocs_api.py → flydocs-workflow/scripts/_local/file_store.py} +137 -47
- package/template/.claude/skills/flydocs-workflow/scripts/flydocs_api.py +693 -0
- package/template/{.flydocs → .claude/skills/flydocs-workflow}/scripts/generate_manifest.py +4 -4
- package/template/.claude/skills/{flydocs-context-graph → flydocs-workflow}/scripts/graph_build.py +132 -1
- package/template/.claude/skills/{flydocs-context-graph → flydocs-workflow}/scripts/graph_query.py +18 -5
- package/template/.claude/skills/{flydocs-context-graph → flydocs-workflow}/scripts/graph_session.py +1 -10
- package/template/.claude/skills/{flydocs-context-graph → flydocs-workflow}/scripts/graph_update.py +4 -4
- package/template/.claude/skills/{flydocs-context-graph → flydocs-workflow}/scripts/graph_utils.py +2 -1
- package/template/.claude/skills/flydocs-workflow/scripts/issues.py +489 -0
- package/template/.claude/skills/flydocs-workflow/scripts/projects.py +144 -0
- package/template/.claude/skills/flydocs-workflow/scripts/pull_services.py +128 -0
- package/template/.claude/skills/flydocs-workflow/scripts/push_service.py +132 -0
- package/template/.claude/skills/flydocs-workflow/scripts/session.py +54 -0
- package/template/.claude/skills/flydocs-workflow/scripts/workspace.py +860 -0
- package/template/.claude/skills/flydocs-workflow/session.md +63 -25
- package/template/.claude/skills/flydocs-workflow/stages/activate.md +18 -7
- package/template/.claude/skills/flydocs-workflow/stages/capture.md +10 -5
- package/template/.claude/skills/flydocs-workflow/stages/close.md +4 -3
- package/template/.claude/skills/flydocs-workflow/stages/implement.md +33 -9
- package/template/.claude/skills/flydocs-workflow/stages/refine.md +22 -6
- package/template/.claude/skills/flydocs-workflow/stages/review.md +16 -4
- package/template/.claude/skills/flydocs-workflow/stages/validate.md +3 -1
- package/template/.claude/skills/flydocs-workflow/templates/pr/default.md +33 -0
- package/template/.cursor/agents/implementation-agent.md +1 -1
- package/template/.cursor/agents/pm-agent.md +2 -2
- package/template/.cursor/hooks.json +10 -3
- package/template/.env.example +6 -6
- package/template/.flydocs/config.json +5 -18
- package/template/.flydocs/templates/README.md +13 -14
- package/template/.flydocs/templates/quick-capture.md +4 -8
- package/template/.flydocs/version +1 -1
- package/template/AGENTS.md +39 -32
- package/template/CHANGELOG.md +39 -0
- package/template/flydocs/README.md +1 -3
- package/template/flydocs/context/project.md +6 -3
- package/template/flydocs/design-system/README.md +3 -3
- package/template/flydocs/knowledge/INDEX.md +38 -53
- package/template/flydocs/knowledge/README.md +60 -9
- package/template/flydocs/knowledge/templates/decision.md +47 -0
- package/template/flydocs/knowledge/templates/feature.md +35 -0
- package/template/flydocs/knowledge/templates/note.md +25 -0
- package/template/manifest.json +24 -20
- package/template/.claude/skills/flydocs-cloud/SKILL.md +0 -111
- package/template/.claude/skills/flydocs-cloud/cursor-rule.mdc +0 -50
- package/template/.claude/skills/flydocs-cloud/scripts/assign.py +0 -22
- package/template/.claude/skills/flydocs-cloud/scripts/assign_cycle.py +0 -28
- package/template/.claude/skills/flydocs-cloud/scripts/assign_milestone.py +0 -22
- package/template/.claude/skills/flydocs-cloud/scripts/comment.py +0 -29
- package/template/.claude/skills/flydocs-cloud/scripts/create_issue.py +0 -63
- package/template/.claude/skills/flydocs-cloud/scripts/create_milestone.py +0 -35
- package/template/.claude/skills/flydocs-cloud/scripts/create_project.py +0 -33
- package/template/.claude/skills/flydocs-cloud/scripts/create_team.py +0 -39
- package/template/.claude/skills/flydocs-cloud/scripts/estimate.py +0 -29
- package/template/.claude/skills/flydocs-cloud/scripts/flydocs_api.py +0 -210
- package/template/.claude/skills/flydocs-cloud/scripts/get_issue.py +0 -24
- package/template/.claude/skills/flydocs-cloud/scripts/link.py +0 -28
- package/template/.claude/skills/flydocs-cloud/scripts/list_cycles.py +0 -28
- package/template/.claude/skills/flydocs-cloud/scripts/list_issues.py +0 -44
- package/template/.claude/skills/flydocs-cloud/scripts/list_labels.py +0 -19
- package/template/.claude/skills/flydocs-cloud/scripts/list_milestones.py +0 -28
- package/template/.claude/skills/flydocs-cloud/scripts/list_projects.py +0 -31
- package/template/.claude/skills/flydocs-cloud/scripts/list_teams.py +0 -19
- package/template/.claude/skills/flydocs-cloud/scripts/priority.py +0 -29
- package/template/.claude/skills/flydocs-cloud/scripts/project_update.py +0 -45
- package/template/.claude/skills/flydocs-cloud/scripts/set_labels.py +0 -68
- package/template/.claude/skills/flydocs-cloud/scripts/set_team.py +0 -41
- package/template/.claude/skills/flydocs-cloud/scripts/transition.py +0 -26
- package/template/.claude/skills/flydocs-cloud/scripts/update_description.py +0 -36
- package/template/.claude/skills/flydocs-cloud/scripts/update_issue.py +0 -82
- package/template/.claude/skills/flydocs-context-graph/SKILL.md +0 -87
- package/template/.claude/skills/flydocs-context-graph/schema.md +0 -78
- package/template/.claude/skills/flydocs-context-graph/scripts/graph_context.py +0 -338
- package/template/.claude/skills/flydocs-context7/SKILL.md +0 -105
- package/template/.claude/skills/flydocs-context7/cursor-rule.mdc +0 -49
- package/template/.claude/skills/flydocs-context7/scripts/context7.py +0 -293
- package/template/.claude/skills/flydocs-estimates/SKILL.md +0 -384
- package/template/.claude/skills/flydocs-figma/SKILL.md +0 -377
- package/template/.claude/skills/flydocs-figma/references/PROMPTING.md +0 -108
- package/template/.claude/skills/flydocs-figma/references/TROUBLESHOOTING.md +0 -112
- package/template/.claude/skills/flydocs-local/SKILL.md +0 -103
- package/template/.claude/skills/flydocs-local/cursor-rule.mdc +0 -43
- package/template/.claude/skills/flydocs-local/scripts/assign.py +0 -20
- package/template/.claude/skills/flydocs-local/scripts/comment.py +0 -27
- package/template/.claude/skills/flydocs-local/scripts/create_issue.py +0 -44
- package/template/.claude/skills/flydocs-local/scripts/estimate.py +0 -37
- package/template/.claude/skills/flydocs-local/scripts/get_issue.py +0 -20
- package/template/.claude/skills/flydocs-local/scripts/link.py +0 -41
- package/template/.claude/skills/flydocs-local/scripts/list_issues.py +0 -34
- package/template/.claude/skills/flydocs-local/scripts/priority.py +0 -37
- package/template/.claude/skills/flydocs-local/scripts/project_update.py +0 -67
- package/template/.claude/skills/flydocs-local/scripts/status_summary.py +0 -16
- package/template/.claude/skills/flydocs-local/scripts/transition.py +0 -24
- package/template/.claude/skills/flydocs-local/scripts/update_description.py +0 -35
- package/template/.claude/skills/flydocs-local/scripts/update_issue.py +0 -84
- package/template/.flydocs/hooks/auto-approve.py +0 -71
- package/template/.flydocs/hooks/prompt-submit.py +0 -277
- package/template/.flydocs/scripts/skill_manager.py +0 -541
- package/template/.flydocs/templates/bug.md +0 -166
- package/template/.flydocs/templates/chore.md +0 -110
- package/template/.flydocs/templates/feature.md +0 -173
- package/template/.flydocs/templates/idea.md +0 -122
- /package/template/{.flydocs → .claude}/hooks/post-edit.py +0 -0
- /package/template/.claude/skills/{flydocs-estimates/references → flydocs-workflow/reference}/provider-costs.md +0 -0
- /package/template/.claude/skills/flydocs-workflow/templates/{bug.md → issues/bug.md} +0 -0
- /package/template/.claude/skills/flydocs-workflow/templates/{chore.md → issues/chore.md} +0 -0
- /package/template/.claude/skills/flydocs-workflow/templates/{feature.md → issues/feature.md} +0 -0
- /package/template/.claude/skills/flydocs-workflow/templates/{idea.md → issues/idea.md} +0 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
PreToolUse Hook: Auto-approve FlyDocs scripts + workflow state nudges
|
|
4
|
+
|
|
5
|
+
1. Auto-approves Bash commands that execute scripts in the unified skill:
|
|
6
|
+
.claude/skills/flydocs-workflow/scripts/
|
|
7
|
+
|
|
8
|
+
2. Nudges the agent when Edit/Write is attempted but the active issue
|
|
9
|
+
isn't in IMPLEMENTING status (non-blocking, informational only).
|
|
10
|
+
|
|
11
|
+
Exit codes:
|
|
12
|
+
- 0 with JSON: Approved or nudge context (decision in output)
|
|
13
|
+
- 0 with no output: No opinion, continue normally
|
|
14
|
+
- 2: Block (stderr shown to AI)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import sys
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
import re
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Pattern matches the unified flydocs-workflow scripts directory.
|
|
25
|
+
# Uses ^ and $ anchors to prevent injection via command chaining
|
|
26
|
+
# (e.g., "python3 script.py; evil" would match without anchors).
|
|
27
|
+
APPROVED_PATTERN = re.compile(
|
|
28
|
+
r'^python3?\s+(?:["\']?(?:\$CLAUDE_PROJECT_DIR|\$\{CLAUDE_PROJECT_DIR\}|\.)["\']?/)?\.?claude/skills/flydocs-workflow/scripts/\w+\.py(?:\s+[^;&|`$()]*)?$'
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def should_approve(command: str) -> bool:
|
|
33
|
+
"""Check if command executes a FlyDocs skill script."""
|
|
34
|
+
return bool(APPROVED_PATTERN.match(command))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_script_info(command: str) -> tuple[str, str]:
|
|
38
|
+
"""Extract skill name and script name from command."""
|
|
39
|
+
match = re.search(
|
|
40
|
+
r'\.claude/skills/(flydocs-\w+)/scripts/(\w+\.py)', command
|
|
41
|
+
)
|
|
42
|
+
if match:
|
|
43
|
+
return match.group(1), match.group(2)
|
|
44
|
+
return "unknown", "unknown"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def check_workflow_state_for_edit() -> str | None:
|
|
48
|
+
"""Check if an active issue needs transition before code changes.
|
|
49
|
+
|
|
50
|
+
Returns a nudge message if the issue isn't in IMPLEMENTING, or None.
|
|
51
|
+
Non-blocking — the edit is allowed regardless.
|
|
52
|
+
"""
|
|
53
|
+
focus_file = Path('.flydocs/session/focus.md')
|
|
54
|
+
status_file = Path('.flydocs/session/status')
|
|
55
|
+
|
|
56
|
+
if not focus_file.exists():
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
content = focus_file.read_text()
|
|
61
|
+
match = re.search(r'[A-Z]+-[0-9]+', content)
|
|
62
|
+
if not match:
|
|
63
|
+
return None
|
|
64
|
+
issue_id = match.group(0)
|
|
65
|
+
except (OSError, IOError):
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
if not status_file.exists():
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
status = status_file.read_text().strip()
|
|
73
|
+
except (OSError, IOError):
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
if status in ('IMPLEMENTING', 'REVIEW', 'TESTING', 'COMPLETE', 'CANCELED'):
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
f'Issue {issue_id} is in {status}, not In Progress. '
|
|
81
|
+
f'Transition before making changes: '
|
|
82
|
+
f'python3 .claude/skills/flydocs-workflow/scripts/issues.py '
|
|
83
|
+
f'transition {issue_id} IMPLEMENTING "Starting implementation"'
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def main():
|
|
88
|
+
try:
|
|
89
|
+
input_data = json.load(sys.stdin)
|
|
90
|
+
except (json.JSONDecodeError, EOFError):
|
|
91
|
+
sys.exit(0)
|
|
92
|
+
|
|
93
|
+
tool_name = input_data.get('tool_name', '')
|
|
94
|
+
tool_input = input_data.get('tool_input', {})
|
|
95
|
+
|
|
96
|
+
# Auto-approve FlyDocs workflow scripts
|
|
97
|
+
if tool_name == 'Bash':
|
|
98
|
+
command = tool_input.get('command', '')
|
|
99
|
+
if should_approve(command):
|
|
100
|
+
skill, script = get_script_info(command)
|
|
101
|
+
result = {
|
|
102
|
+
"hookSpecificOutput": {
|
|
103
|
+
"permissionDecision": "allow",
|
|
104
|
+
"additionalContext": f"Auto-approved FlyDocs script: {skill}/{script}"
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
print(json.dumps(result))
|
|
108
|
+
sys.exit(0)
|
|
109
|
+
|
|
110
|
+
# Workflow state nudge for Edit/Write
|
|
111
|
+
if tool_name in ('Edit', 'Write'):
|
|
112
|
+
# Change to project dir for file reads
|
|
113
|
+
cwd = os.environ.get('CLAUDE_PROJECT_DIR', '')
|
|
114
|
+
if cwd and Path(cwd).is_dir():
|
|
115
|
+
os.chdir(cwd)
|
|
116
|
+
|
|
117
|
+
nudge = check_workflow_state_for_edit()
|
|
118
|
+
if nudge:
|
|
119
|
+
result = {
|
|
120
|
+
"hookSpecificOutput": {
|
|
121
|
+
"additionalContext": nudge
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
print(json.dumps(result))
|
|
125
|
+
sys.exit(0)
|
|
126
|
+
|
|
127
|
+
# No opinion
|
|
128
|
+
sys.exit(0)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
if __name__ == "__main__":
|
|
132
|
+
main()
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
FlyDocs Hook: post-pr-check.py
|
|
4
|
+
Triggered: PostToolUse (Bash)
|
|
5
|
+
Purpose: Warn when PRs are created without the standard template
|
|
6
|
+
|
|
7
|
+
Detects direct `gh pr create` or `glab mr create` commands that bypass
|
|
8
|
+
the `issues.py pr` dispatcher, and checks if the PR body contains
|
|
9
|
+
required sections.
|
|
10
|
+
|
|
11
|
+
Exit codes:
|
|
12
|
+
0 - Success (optional message output)
|
|
13
|
+
Non-zero - Non-blocking warning
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import re
|
|
18
|
+
import sys
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
REQUIRED_SECTIONS = ["## Summary", "## Test Plan"]
|
|
22
|
+
|
|
23
|
+
PR_CREATE_PATTERNS = [
|
|
24
|
+
re.compile(r"^g(?:h)\s+pr\s+create"),
|
|
25
|
+
re.compile(r"^glab\s+mr\s+create"),
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
# Pattern for our own dispatcher — don't warn on these
|
|
29
|
+
DISPATCHER_PATTERN = re.compile(
|
|
30
|
+
r"python3?\s+.*flydocs-workflow/scripts/issues\.py\s+pr"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def is_pr_create_command(command: str) -> bool:
|
|
35
|
+
"""Check if command creates a PR/MR directly (not through dispatcher)."""
|
|
36
|
+
if DISPATCHER_PATTERN.search(command):
|
|
37
|
+
return False
|
|
38
|
+
return any(p.search(command) for p in PR_CREATE_PATTERNS)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def extract_body(command: str) -> str | None:
|
|
42
|
+
"""Try to extract PR body from command arguments."""
|
|
43
|
+
# Match --body "..." or --body '...'
|
|
44
|
+
match = re.search(r'--body\s+["\'](.+?)["\']', command, re.DOTALL)
|
|
45
|
+
if match:
|
|
46
|
+
return match.group(1)
|
|
47
|
+
# Match --body "$(cat <<'EOF' ... EOF)" (heredoc pattern)
|
|
48
|
+
match = re.search(r"--body\s+\"\$\(cat\s+<<['\"]?EOF['\"]?\n(.+?)\nEOF", command, re.DOTALL)
|
|
49
|
+
if match:
|
|
50
|
+
return match.group(1)
|
|
51
|
+
# Match --description (GitLab)
|
|
52
|
+
match = re.search(r'--description\s+["\'](.+?)["\']', command, re.DOTALL)
|
|
53
|
+
if match:
|
|
54
|
+
return match.group(1)
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def check_body(body: str) -> list[str]:
|
|
59
|
+
"""Check if PR body contains required sections. Returns list of missing sections."""
|
|
60
|
+
missing = []
|
|
61
|
+
for section in REQUIRED_SECTIONS:
|
|
62
|
+
if section.lower() not in body.lower():
|
|
63
|
+
missing.append(section)
|
|
64
|
+
return missing
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def main() -> None:
|
|
68
|
+
try:
|
|
69
|
+
input_data = json.loads(sys.stdin.read())
|
|
70
|
+
except (json.JSONDecodeError, ValueError):
|
|
71
|
+
sys.exit(0)
|
|
72
|
+
|
|
73
|
+
tool_name = input_data.get("tool_name", "")
|
|
74
|
+
tool_input = input_data.get("tool_input", {})
|
|
75
|
+
|
|
76
|
+
if tool_name != "Bash":
|
|
77
|
+
sys.exit(0)
|
|
78
|
+
|
|
79
|
+
command = tool_input.get("command", "")
|
|
80
|
+
|
|
81
|
+
if not is_pr_create_command(command):
|
|
82
|
+
sys.exit(0)
|
|
83
|
+
|
|
84
|
+
# Check if body contains required sections
|
|
85
|
+
body = extract_body(command)
|
|
86
|
+
|
|
87
|
+
if body is None:
|
|
88
|
+
# No body found — PR was created without a body or with a file
|
|
89
|
+
print(
|
|
90
|
+
"[PR Template Notice] PR created without using `issues.py pr`. "
|
|
91
|
+
"For consistent PR descriptions with auto-populated issue context, "
|
|
92
|
+
"use: `python3 .claude/skills/flydocs-workflow/scripts/issues.py pr --issue <ref>`"
|
|
93
|
+
)
|
|
94
|
+
sys.exit(0)
|
|
95
|
+
|
|
96
|
+
missing = check_body(body)
|
|
97
|
+
if missing:
|
|
98
|
+
sections = ", ".join(missing)
|
|
99
|
+
print(
|
|
100
|
+
f"[PR Template Notice] PR body is missing required sections: {sections}. "
|
|
101
|
+
"Consider using `issues.py pr --issue <ref>` for standard template."
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
sys.exit(0)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
if __name__ == "__main__":
|
|
108
|
+
main()
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
FlyDocs Hook: post-transition-check.py
|
|
4
|
+
Triggered: PostToolUse (Bash)
|
|
5
|
+
Purpose: Validate that issue transitions include comments
|
|
6
|
+
|
|
7
|
+
Fires after every Bash command. Only acts on `issues.py transition`
|
|
8
|
+
commands. Warns when a transition is missing a comment argument and
|
|
9
|
+
flags unusual state transitions.
|
|
10
|
+
|
|
11
|
+
Exit codes:
|
|
12
|
+
0 - Always (non-blocking)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import re
|
|
17
|
+
import sys
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
TRANSITION_PATTERN = re.compile(
|
|
21
|
+
r"issues\.py\s+transition\s+(\S+)\s+(\S+)(?:\s+(.+))?"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
VALID_TRANSITIONS: dict[str, set[str]] = {
|
|
25
|
+
"BACKLOG": {"READY", "IMPLEMENTING", "CANCELED"},
|
|
26
|
+
"READY": {"IMPLEMENTING", "CANCELED"},
|
|
27
|
+
"IMPLEMENTING": {"REVIEW", "BLOCKED", "CANCELED"},
|
|
28
|
+
"BLOCKED": {"IMPLEMENTING", "CANCELED"},
|
|
29
|
+
"REVIEW": {"COMPLETE", "TESTING", "IMPLEMENTING", "CANCELED"},
|
|
30
|
+
"TESTING": {"COMPLETE", "IMPLEMENTING", "CANCELED"},
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def read_current_status() -> str | None:
|
|
35
|
+
"""Read current issue status from session file."""
|
|
36
|
+
try:
|
|
37
|
+
return Path(".flydocs/session/status").read_text().strip().upper()
|
|
38
|
+
except (OSError, ValueError):
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def build_output(message: str) -> str:
|
|
43
|
+
return json.dumps({"hookSpecificOutput": {"additionalContext": message}})
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def main() -> None:
|
|
47
|
+
try:
|
|
48
|
+
input_data = json.loads(sys.stdin.read())
|
|
49
|
+
except (json.JSONDecodeError, ValueError):
|
|
50
|
+
print("{}")
|
|
51
|
+
sys.exit(0)
|
|
52
|
+
|
|
53
|
+
if input_data.get("tool_name") != "Bash":
|
|
54
|
+
print("{}")
|
|
55
|
+
sys.exit(0)
|
|
56
|
+
|
|
57
|
+
command = input_data.get("tool_input", {}).get("command", "")
|
|
58
|
+
match = TRANSITION_PATTERN.search(command)
|
|
59
|
+
|
|
60
|
+
if not match:
|
|
61
|
+
print("{}")
|
|
62
|
+
sys.exit(0)
|
|
63
|
+
|
|
64
|
+
ref = match.group(1)
|
|
65
|
+
status = match.group(2).upper()
|
|
66
|
+
comment = match.group(3)
|
|
67
|
+
|
|
68
|
+
if not comment or not comment.strip():
|
|
69
|
+
msg = (
|
|
70
|
+
f"Transition to {status} is missing a comment. "
|
|
71
|
+
f"Every status transition requires a comment — add one now: "
|
|
72
|
+
f'python3 .claude/skills/flydocs-workflow/scripts/issues.py '
|
|
73
|
+
f'comment {ref} "<describe what changed>"'
|
|
74
|
+
)
|
|
75
|
+
print(build_output(msg))
|
|
76
|
+
sys.exit(0)
|
|
77
|
+
|
|
78
|
+
from_status = read_current_status()
|
|
79
|
+
if from_status and from_status in VALID_TRANSITIONS:
|
|
80
|
+
allowed = VALID_TRANSITIONS[from_status]
|
|
81
|
+
if status not in allowed:
|
|
82
|
+
msg = (
|
|
83
|
+
f"Unusual transition: {from_status} -> {status}. "
|
|
84
|
+
f"Verify this is intentional."
|
|
85
|
+
)
|
|
86
|
+
print(build_output(msg))
|
|
87
|
+
sys.exit(0)
|
|
88
|
+
|
|
89
|
+
print("{}")
|
|
90
|
+
sys.exit(0)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
if __name__ == "__main__":
|
|
94
|
+
main()
|