@flydocs/cli 0.6.0-alpha.3 → 0.6.0-alpha.31
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 +2053 -469
- package/package.json +1 -1
- package/template/.claude/CLAUDE.md +43 -48
- 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 +359 -72
- package/template/.claude/commands/flydocs-upgrade.md +26 -27
- 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 +212 -0
- package/template/.claude/hooks/post-pr-check.py +108 -0
- package/template/.claude/hooks/post-transition-check.py +281 -0
- package/template/.claude/hooks/prompt-submit.py +554 -0
- package/template/.claude/hooks/session-start.py +262 -0
- package/template/.claude/hooks/stop-gate.py +162 -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 +260 -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 +724 -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 +738 -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/test_enforcement.py +225 -0
- package/template/.claude/skills/flydocs-workflow/scripts/workspace.py +902 -0
- package/template/.claude/skills/flydocs-workflow/session.md +87 -29
- 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/bug.md +17 -153
- package/template/.flydocs/templates/chore.md +10 -98
- package/template/.flydocs/templates/feature.md +12 -158
- package/template/.flydocs/templates/idea.md +11 -111
- package/template/.flydocs/templates/quick-capture.md +4 -8
- package/template/.flydocs/version +1 -1
- package/template/AGENTS.md +44 -32
- package/template/CHANGELOG.md +37 -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 -113
- 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 -66
- 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_providers.py +0 -19
- 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_provider.py +0 -46
- 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 → .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,262 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
FlyDocs Hook: session-start.py
|
|
4
|
+
Triggered: When a new Claude Code session begins (SessionStart)
|
|
5
|
+
Purpose: Inject continuity context from previous session, active issue, and config state
|
|
6
|
+
|
|
7
|
+
Exit codes:
|
|
8
|
+
0 - Success (JSON output with additionalContext)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
import sys
|
|
15
|
+
from datetime import datetime, timezone
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_previous_session_summary() -> str | None:
|
|
20
|
+
"""Extract key details from last session summary."""
|
|
21
|
+
summary_file = Path('.flydocs/session/last-summary.json')
|
|
22
|
+
if not summary_file.exists():
|
|
23
|
+
return None
|
|
24
|
+
try:
|
|
25
|
+
data = json.loads(summary_file.read_text())
|
|
26
|
+
bits: list[str] = []
|
|
27
|
+
issues = data.get('issues', [])
|
|
28
|
+
if issues:
|
|
29
|
+
bits.append(f'issues={",".join(issues)}')
|
|
30
|
+
pending = data.get('pending', [])
|
|
31
|
+
if pending:
|
|
32
|
+
bits.append(f'pending={len(pending)}')
|
|
33
|
+
blockers = data.get('blockers', [])
|
|
34
|
+
if blockers:
|
|
35
|
+
bits.append(f'blockers={len(blockers)}')
|
|
36
|
+
notes = data.get('notes')
|
|
37
|
+
if notes:
|
|
38
|
+
bits.append(f'notes={notes[:80]}')
|
|
39
|
+
if bits:
|
|
40
|
+
return f'Last session: {" | ".join(bits)}'
|
|
41
|
+
except (json.JSONDecodeError, OSError, IOError):
|
|
42
|
+
pass
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def get_active_issue_context() -> str | None:
|
|
47
|
+
"""Build active issue status line from session files."""
|
|
48
|
+
issue_id = None
|
|
49
|
+
status = None
|
|
50
|
+
|
|
51
|
+
focus_file = Path('.flydocs/session/focus.md')
|
|
52
|
+
if focus_file.exists():
|
|
53
|
+
try:
|
|
54
|
+
match = re.search(r'[A-Z]+-[0-9]+', focus_file.read_text())
|
|
55
|
+
if match:
|
|
56
|
+
issue_id = match.group(0)
|
|
57
|
+
except (OSError, IOError):
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
status_file = Path('.flydocs/session/status')
|
|
61
|
+
if status_file.exists():
|
|
62
|
+
try:
|
|
63
|
+
status = status_file.read_text().strip()
|
|
64
|
+
except (OSError, IOError):
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
if not issue_id:
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
ac_part = ''
|
|
71
|
+
ac_file = Path('.flydocs/session/acceptance-criteria.md')
|
|
72
|
+
if ac_file.exists():
|
|
73
|
+
try:
|
|
74
|
+
content = ac_file.read_text()
|
|
75
|
+
total = len(re.findall(r'^\s*-\s*\[', content, re.MULTILINE))
|
|
76
|
+
done = len(re.findall(r'^\s*-\s*\[x\]', content, re.MULTILINE | re.IGNORECASE))
|
|
77
|
+
if total > 0:
|
|
78
|
+
ac_part = f' | AC: {done}/{total}'
|
|
79
|
+
except (OSError, IOError):
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
status_label = f' ({status})' if status else ''
|
|
83
|
+
return f'Active: {issue_id}{status_label}{ac_part}'
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def get_config_freshness() -> str | None:
|
|
87
|
+
"""Warn if validation cache is stale or missing (v1 configs)."""
|
|
88
|
+
# v2 configs use get_config_freshness_v2() instead
|
|
89
|
+
config_file = Path('.flydocs/config.json')
|
|
90
|
+
if config_file.exists():
|
|
91
|
+
try:
|
|
92
|
+
config = json.loads(config_file.read_text())
|
|
93
|
+
if config.get('configFormat') == 2:
|
|
94
|
+
return None # Skip v1 check for v2 configs
|
|
95
|
+
except (json.JSONDecodeError, OSError):
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
cache_file = Path('.flydocs/validation-cache.json')
|
|
99
|
+
if not cache_file.exists():
|
|
100
|
+
return None
|
|
101
|
+
try:
|
|
102
|
+
data = json.loads(cache_file.read_text())
|
|
103
|
+
timestamp_str = data.get('timestamp')
|
|
104
|
+
if not timestamp_str:
|
|
105
|
+
return None
|
|
106
|
+
cached_at = datetime.fromisoformat(timestamp_str.replace('Z', '+00:00'))
|
|
107
|
+
age_hours = (datetime.now(timezone.utc) - cached_at).total_seconds() / 3600
|
|
108
|
+
if age_hours > 24:
|
|
109
|
+
return 'Config validation stale (>24h) — consider running: python3 .claude/skills/flydocs-workflow/scripts/workspace.py validate'
|
|
110
|
+
except (json.JSONDecodeError, OSError, IOError, ValueError):
|
|
111
|
+
pass
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def get_config_freshness_v2() -> str | None:
|
|
116
|
+
"""Check config freshness via config/check endpoint (v2 configs only, FLY-540)."""
|
|
117
|
+
config_file = Path('.flydocs/config.json')
|
|
118
|
+
if not config_file.exists():
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
config = json.loads(config_file.read_text())
|
|
123
|
+
except (json.JSONDecodeError, OSError):
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
if config.get('configFormat') != 2 or config.get('tier') != 'cloud':
|
|
127
|
+
return None
|
|
128
|
+
|
|
129
|
+
# Resolve API key: env var > global credentials
|
|
130
|
+
api_key = os.environ.get('FLYDOCS_API_KEY')
|
|
131
|
+
if not api_key:
|
|
132
|
+
cred_file = Path.home() / '.flydocs' / 'credentials'
|
|
133
|
+
if cred_file.exists():
|
|
134
|
+
try:
|
|
135
|
+
cred = json.loads(cred_file.read_text())
|
|
136
|
+
api_key = cred.get('apiKey')
|
|
137
|
+
except (json.JSONDecodeError, OSError):
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
if not api_key:
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
# Call config/check — lightweight, ~50ms
|
|
144
|
+
base_url = os.environ.get('FLYDOCS_RELAY_URL', 'https://app.flydocs.ai/api/relay').rstrip('/')
|
|
145
|
+
try:
|
|
146
|
+
import urllib.request
|
|
147
|
+
req = urllib.request.Request(
|
|
148
|
+
f'{base_url}/config/check',
|
|
149
|
+
headers={
|
|
150
|
+
'Authorization': f'Bearer {api_key}',
|
|
151
|
+
'Accept': 'application/json',
|
|
152
|
+
},
|
|
153
|
+
)
|
|
154
|
+
with urllib.request.urlopen(req, timeout=5) as resp:
|
|
155
|
+
data = json.loads(resp.read().decode('utf-8'))
|
|
156
|
+
except Exception:
|
|
157
|
+
# Server unreachable — silently continue, never block a session
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
if data.get('needsSync'):
|
|
161
|
+
stale_fields: list[str] = []
|
|
162
|
+
local_config_version = config.get('configVersion', 0)
|
|
163
|
+
if data.get('configVersion', 0) > local_config_version:
|
|
164
|
+
stale_fields.append('config')
|
|
165
|
+
if data.get('templateVersion', 0) > 0:
|
|
166
|
+
stale_fields.append('templates')
|
|
167
|
+
if data.get('contextVersion', 0) > 0:
|
|
168
|
+
stale_fields.append('context')
|
|
169
|
+
detail = f' ({", ".join(stale_fields)})' if stale_fields else ''
|
|
170
|
+
return f'Config updated{detail} — run `flydocs sync`'
|
|
171
|
+
|
|
172
|
+
return None
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def check_skills_manifest() -> str | None:
|
|
176
|
+
"""Alert-mode skills manifest validation (FLY-541)."""
|
|
177
|
+
config_file = Path('.flydocs/config.json')
|
|
178
|
+
if not config_file.exists():
|
|
179
|
+
return None
|
|
180
|
+
|
|
181
|
+
try:
|
|
182
|
+
config = json.loads(config_file.read_text())
|
|
183
|
+
except (json.JSONDecodeError, OSError):
|
|
184
|
+
return None
|
|
185
|
+
|
|
186
|
+
expected = config.get('skills', {}).get('installed', [])
|
|
187
|
+
if not expected:
|
|
188
|
+
return None
|
|
189
|
+
|
|
190
|
+
skills_dir = Path('.claude/skills')
|
|
191
|
+
if not skills_dir.is_dir():
|
|
192
|
+
return f'Skills directory missing — run `flydocs sync`'
|
|
193
|
+
|
|
194
|
+
# Check for installed directories
|
|
195
|
+
try:
|
|
196
|
+
installed = {d.name for d in skills_dir.iterdir() if d.is_dir()}
|
|
197
|
+
except OSError:
|
|
198
|
+
return None
|
|
199
|
+
|
|
200
|
+
expected_set = set(expected)
|
|
201
|
+
missing = expected_set - installed
|
|
202
|
+
unexpected = installed - expected_set
|
|
203
|
+
|
|
204
|
+
warnings: list[str] = []
|
|
205
|
+
if missing:
|
|
206
|
+
warnings.append(f'Missing skills: {", ".join(sorted(missing))} — run `flydocs sync`')
|
|
207
|
+
if unexpected:
|
|
208
|
+
warnings.append(f'Unexpected skills: {", ".join(sorted(unexpected))}')
|
|
209
|
+
|
|
210
|
+
return ' | '.join(warnings) if warnings else None
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def main() -> None:
|
|
214
|
+
"""Main hook execution."""
|
|
215
|
+
try:
|
|
216
|
+
input_data = json.loads(sys.stdin.read())
|
|
217
|
+
except (json.JSONDecodeError, ValueError):
|
|
218
|
+
input_data = {}
|
|
219
|
+
|
|
220
|
+
cwd = input_data.get('cwd', '')
|
|
221
|
+
if cwd and Path(cwd).is_dir():
|
|
222
|
+
os.chdir(cwd)
|
|
223
|
+
elif os.environ.get('CLAUDE_PROJECT_DIR') and Path(os.environ['CLAUDE_PROJECT_DIR']).is_dir():
|
|
224
|
+
os.chdir(os.environ['CLAUDE_PROJECT_DIR'])
|
|
225
|
+
|
|
226
|
+
parts: list[str] = []
|
|
227
|
+
|
|
228
|
+
summary = get_previous_session_summary()
|
|
229
|
+
if summary:
|
|
230
|
+
parts.append(summary)
|
|
231
|
+
|
|
232
|
+
issue_ctx = get_active_issue_context()
|
|
233
|
+
if issue_ctx:
|
|
234
|
+
parts.append(issue_ctx)
|
|
235
|
+
|
|
236
|
+
freshness = get_config_freshness()
|
|
237
|
+
if freshness:
|
|
238
|
+
parts.append(freshness)
|
|
239
|
+
|
|
240
|
+
freshness_v2 = get_config_freshness_v2()
|
|
241
|
+
if freshness_v2:
|
|
242
|
+
parts.append(freshness_v2)
|
|
243
|
+
|
|
244
|
+
skills_warning = check_skills_manifest()
|
|
245
|
+
if skills_warning:
|
|
246
|
+
parts.append(skills_warning)
|
|
247
|
+
|
|
248
|
+
if not parts:
|
|
249
|
+
print('{}')
|
|
250
|
+
sys.exit(0)
|
|
251
|
+
|
|
252
|
+
output = {
|
|
253
|
+
"hookSpecificOutput": {
|
|
254
|
+
"additionalContext": " | ".join(parts)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
print(json.dumps(output))
|
|
258
|
+
sys.exit(0)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
if __name__ == '__main__':
|
|
262
|
+
main()
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
FlyDocs Hook: stop-gate.py
|
|
4
|
+
Triggered: Stop (every time the agent finishes responding)
|
|
5
|
+
Purpose: Gate agent completion on workflow state — blocks finish if issue
|
|
6
|
+
is still In Progress, warns if acceptance criteria are incomplete.
|
|
7
|
+
|
|
8
|
+
Exit codes:
|
|
9
|
+
0 - Allow stop (optional JSON output parsed)
|
|
10
|
+
2 - Block stop (stderr shown to Claude as instruction)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import re
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
DEBUG_HOOK = os.environ.get('DEBUG_HOOK', '0') == '1'
|
|
20
|
+
SCRIPT_DIR = Path(__file__).parent.resolve()
|
|
21
|
+
DEBUG_LOG = SCRIPT_DIR.parent / 'logs' / 'hook-debug.log'
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def debug_log(message: str) -> None:
|
|
25
|
+
"""Write debug message to log file if DEBUG_HOOK is enabled."""
|
|
26
|
+
if not DEBUG_HOOK:
|
|
27
|
+
return
|
|
28
|
+
try:
|
|
29
|
+
DEBUG_LOG.parent.mkdir(parents=True, exist_ok=True)
|
|
30
|
+
from datetime import datetime
|
|
31
|
+
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
32
|
+
with open(DEBUG_LOG, 'a') as f:
|
|
33
|
+
f.write(f'[{timestamp}] [stop-gate] {message}\n')
|
|
34
|
+
except (OSError, IOError):
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
ISSUE_ID_PATTERN = re.compile(r"[A-Z]+-[0-9]+")
|
|
38
|
+
CHECKBOX_DONE = re.compile(r"- \[x\]", re.IGNORECASE)
|
|
39
|
+
CHECKBOX_ALL = re.compile(r"- \[[ x]\]", re.IGNORECASE)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def read_file_safe(path):
|
|
43
|
+
"""Read file contents, returning None on any error."""
|
|
44
|
+
try:
|
|
45
|
+
return Path(path).read_text(encoding="utf-8")
|
|
46
|
+
except (OSError, ValueError):
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def extract_issue_id(text):
|
|
51
|
+
"""Extract first issue ID matching PROJ-123 pattern."""
|
|
52
|
+
match = ISSUE_ID_PATTERN.search(text)
|
|
53
|
+
return match.group(0) if match else None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def main():
|
|
57
|
+
# -- Parse stdin --
|
|
58
|
+
try:
|
|
59
|
+
input_data = json.loads(sys.stdin.read())
|
|
60
|
+
except (json.JSONDecodeError, ValueError):
|
|
61
|
+
input_data = {}
|
|
62
|
+
|
|
63
|
+
# -- Loop guard: if the hook itself triggered this stop, exit cleanly --
|
|
64
|
+
if input_data.get("stop_hook_active"):
|
|
65
|
+
sys.exit(0)
|
|
66
|
+
|
|
67
|
+
# -- Resolve working directory --
|
|
68
|
+
cwd = input_data.get("cwd") or os.environ.get("CLAUDE_PROJECT_DIR", "")
|
|
69
|
+
if not cwd:
|
|
70
|
+
sys.exit(0)
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
os.chdir(cwd)
|
|
74
|
+
except OSError:
|
|
75
|
+
sys.exit(0)
|
|
76
|
+
|
|
77
|
+
session_dir = Path(".flydocs/session")
|
|
78
|
+
|
|
79
|
+
# -- Check for active focus --
|
|
80
|
+
focus_text = read_file_safe(session_dir / "focus.md")
|
|
81
|
+
if not focus_text:
|
|
82
|
+
sys.exit(0)
|
|
83
|
+
|
|
84
|
+
issue_id = extract_issue_id(focus_text)
|
|
85
|
+
if not issue_id:
|
|
86
|
+
sys.exit(0)
|
|
87
|
+
|
|
88
|
+
# -- Read current status --
|
|
89
|
+
status_text = read_file_safe(session_dir / "status")
|
|
90
|
+
if not status_text:
|
|
91
|
+
sys.exit(0)
|
|
92
|
+
|
|
93
|
+
status = status_text.strip().upper()
|
|
94
|
+
debug_log(f"Issue {issue_id} status={status}")
|
|
95
|
+
|
|
96
|
+
# -- READY: warn that transition to IMPLEMENTING was missed --
|
|
97
|
+
if status == "READY":
|
|
98
|
+
# Check if any code edits happened (heuristic: git has uncommitted changes)
|
|
99
|
+
try:
|
|
100
|
+
import subprocess
|
|
101
|
+
result = subprocess.run(
|
|
102
|
+
["git", "diff", "--quiet"], capture_output=True, timeout=5
|
|
103
|
+
)
|
|
104
|
+
has_changes = result.returncode != 0
|
|
105
|
+
except (FileNotFoundError, subprocess.TimeoutExpired, subprocess.SubprocessError):
|
|
106
|
+
has_changes = False
|
|
107
|
+
|
|
108
|
+
if has_changes:
|
|
109
|
+
msg = (
|
|
110
|
+
"Issue {} is still in READY but code changes were made. "
|
|
111
|
+
"Transition to IMPLEMENTING before continuing:\n"
|
|
112
|
+
" python3 .claude/skills/flydocs-workflow/scripts/issues.py "
|
|
113
|
+
"transition {} IMPLEMENTING \"Starting implementation\""
|
|
114
|
+
).format(issue_id, issue_id)
|
|
115
|
+
sys.stderr.write(msg)
|
|
116
|
+
sys.exit(2)
|
|
117
|
+
|
|
118
|
+
# -- IMPLEMENTING: block completion --
|
|
119
|
+
if status == "IMPLEMENTING":
|
|
120
|
+
msg = (
|
|
121
|
+
"Issue {} is still In Progress. "
|
|
122
|
+
"Transition to Review before finishing:\n"
|
|
123
|
+
" python3 .claude/skills/flydocs-workflow/scripts/issues.py "
|
|
124
|
+
"transition {} REVIEW \"Implementation complete\""
|
|
125
|
+
).format(issue_id, issue_id)
|
|
126
|
+
sys.stderr.write(msg)
|
|
127
|
+
sys.exit(2)
|
|
128
|
+
|
|
129
|
+
# -- REVIEW: block on incomplete acceptance criteria --
|
|
130
|
+
if status == "REVIEW":
|
|
131
|
+
ac_text = read_file_safe(session_dir / "acceptance-criteria.md")
|
|
132
|
+
if ac_text:
|
|
133
|
+
total = len(CHECKBOX_ALL.findall(ac_text))
|
|
134
|
+
done = len(CHECKBOX_DONE.findall(ac_text))
|
|
135
|
+
if total > 0 and done < total:
|
|
136
|
+
msg = (
|
|
137
|
+
"Issue {}: {}/{} acceptance criteria checked. "
|
|
138
|
+
"All AC must be verified before completion. "
|
|
139
|
+
"Update the issue description with checked criteria:\n"
|
|
140
|
+
" python3 .claude/skills/flydocs-workflow/scripts/issues.py "
|
|
141
|
+
"description {} --text \"<updated description with checked AC>\""
|
|
142
|
+
).format(issue_id, done, total, issue_id)
|
|
143
|
+
sys.stderr.write(msg)
|
|
144
|
+
sys.exit(2)
|
|
145
|
+
|
|
146
|
+
# -- BLOCKED: inform that issue needs unblocking --
|
|
147
|
+
if status == "BLOCKED":
|
|
148
|
+
result = {
|
|
149
|
+
"hookSpecificOutput": {
|
|
150
|
+
"additionalContext": (
|
|
151
|
+
"Issue {} is BLOCKED. Resolve the blocker before continuing, "
|
|
152
|
+
"then transition back to IMPLEMENTING."
|
|
153
|
+
).format(issue_id)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
print(json.dumps(result))
|
|
157
|
+
|
|
158
|
+
sys.exit(0)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
if __name__ == "__main__":
|
|
162
|
+
main()
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
"hooks": {
|
|
3
3
|
"PreToolUse": [
|
|
4
4
|
{
|
|
5
|
-
"matcher": "Bash",
|
|
5
|
+
"matcher": "Bash|Edit|Write",
|
|
6
6
|
"hooks": [
|
|
7
7
|
{
|
|
8
8
|
"type": "command",
|
|
9
|
-
"command": "python3 \"$CLAUDE_PROJECT_DIR\"/.
|
|
9
|
+
"command": "python3 \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/auto-approve.py",
|
|
10
10
|
"timeout": 5
|
|
11
11
|
}
|
|
12
12
|
]
|
|
@@ -18,10 +18,25 @@
|
|
|
18
18
|
"hooks": [
|
|
19
19
|
{
|
|
20
20
|
"type": "command",
|
|
21
|
-
"command": "python3 \"$CLAUDE_PROJECT_DIR\"/.
|
|
21
|
+
"command": "python3 \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/post-edit.py",
|
|
22
22
|
"timeout": 30
|
|
23
23
|
}
|
|
24
24
|
]
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"matcher": "Bash",
|
|
28
|
+
"hooks": [
|
|
29
|
+
{
|
|
30
|
+
"type": "command",
|
|
31
|
+
"command": "python3 \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/post-pr-check.py",
|
|
32
|
+
"timeout": 10
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"type": "command",
|
|
36
|
+
"command": "python3 \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/post-transition-check.py",
|
|
37
|
+
"timeout": 5
|
|
38
|
+
}
|
|
39
|
+
]
|
|
25
40
|
}
|
|
26
41
|
],
|
|
27
42
|
"UserPromptSubmit": [
|
|
@@ -29,7 +44,29 @@
|
|
|
29
44
|
"hooks": [
|
|
30
45
|
{
|
|
31
46
|
"type": "command",
|
|
32
|
-
"command": "python3 \"$CLAUDE_PROJECT_DIR\"/.
|
|
47
|
+
"command": "python3 \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/prompt-submit.py",
|
|
48
|
+
"timeout": 10
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
],
|
|
53
|
+
"Stop": [
|
|
54
|
+
{
|
|
55
|
+
"hooks": [
|
|
56
|
+
{
|
|
57
|
+
"type": "command",
|
|
58
|
+
"command": "python3 \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/stop-gate.py",
|
|
59
|
+
"timeout": 5
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"SessionStart": [
|
|
65
|
+
{
|
|
66
|
+
"hooks": [
|
|
67
|
+
{
|
|
68
|
+
"type": "command",
|
|
69
|
+
"command": "python3 \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/session-start.py",
|
|
33
70
|
"timeout": 10
|
|
34
71
|
}
|
|
35
72
|
]
|
|
@@ -11,16 +11,12 @@
|
|
|
11
11
|
Owned and maintained by FlyDocs. May include executable scripts and premium
|
|
12
12
|
functionality gated via API relay.
|
|
13
13
|
|
|
14
|
-
| Skill
|
|
15
|
-
|
|
|
16
|
-
| `flydocs-workflow`
|
|
17
|
-
| `flydocs-local` | Mechanism (free) | File-based issue management |
|
|
18
|
-
| `flydocs-cloud` | Mechanism (paid) | Linear/provider issue management via API |
|
|
19
|
-
| `flydocs-context7` | Core | Library documentation lookup via Context7 API |
|
|
20
|
-
| `flydocs-figma` | Premium | Design extraction from Figma |
|
|
21
|
-
| `flydocs-estimates` | Premium | AI token/labor cost estimation |
|
|
14
|
+
| Skill | Purpose |
|
|
15
|
+
| ------------------ | ---------------------------------------------------------------------------- |
|
|
16
|
+
| `flydocs-workflow` | Unified skill — lifecycle, issues, projects, Figma, estimates, context graph |
|
|
22
17
|
|
|
23
|
-
|
|
18
|
+
All functionality consolidated into a single skill. Tier (local vs cloud) is
|
|
19
|
+
auto-detected from `.flydocs/config.json` — no separate mechanism skills.
|
|
24
20
|
|
|
25
21
|
### Unprefixed — Community Skills
|
|
26
22
|
|
|
@@ -101,8 +97,8 @@ The body serves as a **compressed index** — not a documentation dump. Target ~
|
|
|
101
97
|
```markdown
|
|
102
98
|
# Skill Name
|
|
103
99
|
|
|
104
|
-
|
|
105
|
-
|
|
100
|
+
Use this skill when performing [domain]-specific operations.
|
|
101
|
+
For general coding, skip this — just write code.
|
|
106
102
|
|
|
107
103
|
## Key Rules (always apply)
|
|
108
104
|
|
|
@@ -191,12 +187,13 @@ CLAUDE.md and AGENTS.md between markers:
|
|
|
191
187
|
|
|
192
188
|
## Skills Index
|
|
193
189
|
|
|
194
|
-
|
|
190
|
+
Consult the workflow skill for **issue operations and status transitions only**.
|
|
191
|
+
For general coding tasks, skip this — just write code.
|
|
195
192
|
|
|
196
|
-
| Skill | Triggers
|
|
197
|
-
| ----------------- |
|
|
198
|
-
| flydocs-workflow |
|
|
199
|
-
| typescript-strict | TypeScript, type error, any type
|
|
193
|
+
| Skill | Triggers | Entry |
|
|
194
|
+
| ----------------- | --------------------------------------------- | ----------------------------------------- |
|
|
195
|
+
| flydocs-workflow | create issue, transition, assign, close issue | .claude/skills/flydocs-workflow/SKILL.md |
|
|
196
|
+
| typescript-strict | TypeScript, type error, any type | .claude/skills/typescript-strict/SKILL.md |
|
|
200
197
|
|
|
201
198
|
<!-- flydocs:skills-manifest:end -->
|
|
202
199
|
```
|
|
@@ -257,17 +254,18 @@ For detailed patterns, see `reference/patterns.md`.
|
|
|
257
254
|
|
|
258
255
|
---
|
|
259
256
|
|
|
260
|
-
##
|
|
257
|
+
## Unified Dispatcher Model
|
|
261
258
|
|
|
262
|
-
|
|
263
|
-
|
|
259
|
+
All operations route through grouped dispatcher scripts in
|
|
260
|
+
`flydocs-workflow/scripts/`. The unified client (`flydocs_api.py`) auto-detects
|
|
261
|
+
tier from `.flydocs/config.json` — scripts never check tier directly.
|
|
264
262
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
`
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
263
|
+
| Dispatcher | Operations |
|
|
264
|
+
| -------------- | ------------------------------------------------------ |
|
|
265
|
+
| `issues.py` | create, get, list, transition, assign, update, comment |
|
|
266
|
+
| `projects.py` | list-projects, create-project, milestones, cycles |
|
|
267
|
+
| `workspace.py` | validate, labels, statuses, teams, config, identity |
|
|
268
|
+
| `session.py` | project-update, status-summary |
|
|
271
269
|
|
|
272
270
|
---
|
|
273
271
|
|