@flydocs/cli 0.6.0-alpha.3 → 0.6.0-alpha.30

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 (151) hide show
  1. package/dist/cli.js +2054 -470
  2. package/package.json +1 -1
  3. package/template/.claude/CLAUDE.md +43 -48
  4. package/template/.claude/agents/implementation-agent.md +1 -1
  5. package/template/.claude/agents/pm-agent.md +1 -1
  6. package/template/.claude/commands/activate.md +1 -1
  7. package/template/.claude/commands/attach.md +1 -1
  8. package/template/.claude/commands/block.md +2 -2
  9. package/template/.claude/commands/capture.md +1 -1
  10. package/template/.claude/commands/close.md +1 -1
  11. package/template/.claude/commands/flydocs-setup.md +359 -72
  12. package/template/.claude/commands/flydocs-upgrade.md +26 -27
  13. package/template/.claude/commands/implement.md +1 -1
  14. package/template/.claude/commands/knowledge.md +61 -0
  15. package/template/.claude/commands/new-project.md +1 -1
  16. package/template/.claude/commands/onboard.md +275 -0
  17. package/template/.claude/commands/project-update.md +1 -1
  18. package/template/.claude/commands/refine.md +1 -1
  19. package/template/.claude/commands/review.md +1 -1
  20. package/template/.claude/commands/start-session.md +1 -1
  21. package/template/.claude/commands/status.md +1 -1
  22. package/template/.claude/commands/validate.md +1 -1
  23. package/template/.claude/commands/wrap-session.md +1 -1
  24. package/template/.claude/hooks/auto-approve.py +212 -0
  25. package/template/.claude/hooks/post-pr-check.py +108 -0
  26. package/template/.claude/hooks/post-transition-check.py +281 -0
  27. package/template/.claude/hooks/prompt-submit.py +554 -0
  28. package/template/.claude/hooks/session-start.py +262 -0
  29. package/template/.claude/hooks/stop-gate.py +162 -0
  30. package/template/.claude/settings.json +41 -4
  31. package/template/.claude/skills/README.md +23 -25
  32. package/template/.claude/skills/flydocs-workflow/SKILL.md +134 -42
  33. package/template/.claude/skills/flydocs-workflow/cursor-rule.mdc +9 -8
  34. package/template/.claude/skills/flydocs-workflow/reference/comment-templates.md +1 -0
  35. package/template/.claude/skills/flydocs-workflow/reference/golden-rules.md +28 -17
  36. package/template/.claude/skills/flydocs-workflow/reference/graph-schema.md +116 -0
  37. package/template/.claude/skills/flydocs-workflow/reference/pr-workflow.md +120 -0
  38. package/template/.claude/skills/flydocs-workflow/reference/priority-estimates.md +37 -15
  39. package/template/.claude/skills/flydocs-workflow/reference/service-descriptor-schema.md +260 -0
  40. package/template/.claude/skills/flydocs-workflow/reference/status-workflow.md +26 -26
  41. package/template/.claude/skills/flydocs-workflow/scripts/_local/__init__.py +0 -0
  42. package/template/.claude/skills/{flydocs-local/scripts/flydocs_api.py → flydocs-workflow/scripts/_local/file_store.py} +137 -47
  43. package/template/.claude/skills/flydocs-workflow/scripts/flydocs_api.py +724 -0
  44. package/template/{.flydocs → .claude/skills/flydocs-workflow}/scripts/generate_manifest.py +4 -4
  45. package/template/.claude/skills/{flydocs-context-graph → flydocs-workflow}/scripts/graph_build.py +132 -1
  46. package/template/.claude/skills/{flydocs-context-graph → flydocs-workflow}/scripts/graph_query.py +18 -5
  47. package/template/.claude/skills/{flydocs-context-graph → flydocs-workflow}/scripts/graph_session.py +1 -10
  48. package/template/.claude/skills/{flydocs-context-graph → flydocs-workflow}/scripts/graph_update.py +4 -4
  49. package/template/.claude/skills/{flydocs-context-graph → flydocs-workflow}/scripts/graph_utils.py +2 -1
  50. package/template/.claude/skills/flydocs-workflow/scripts/issues.py +738 -0
  51. package/template/.claude/skills/flydocs-workflow/scripts/projects.py +144 -0
  52. package/template/.claude/skills/flydocs-workflow/scripts/pull_services.py +128 -0
  53. package/template/.claude/skills/flydocs-workflow/scripts/push_service.py +132 -0
  54. package/template/.claude/skills/flydocs-workflow/scripts/session.py +54 -0
  55. package/template/.claude/skills/flydocs-workflow/scripts/test_enforcement.py +225 -0
  56. package/template/.claude/skills/flydocs-workflow/scripts/workspace.py +902 -0
  57. package/template/.claude/skills/flydocs-workflow/session.md +87 -29
  58. package/template/.claude/skills/flydocs-workflow/stages/activate.md +18 -7
  59. package/template/.claude/skills/flydocs-workflow/stages/capture.md +10 -5
  60. package/template/.claude/skills/flydocs-workflow/stages/close.md +4 -3
  61. package/template/.claude/skills/flydocs-workflow/stages/implement.md +33 -9
  62. package/template/.claude/skills/flydocs-workflow/stages/refine.md +22 -6
  63. package/template/.claude/skills/flydocs-workflow/stages/review.md +16 -4
  64. package/template/.claude/skills/flydocs-workflow/stages/validate.md +3 -1
  65. package/template/.claude/skills/flydocs-workflow/templates/pr/default.md +33 -0
  66. package/template/.cursor/agents/implementation-agent.md +1 -1
  67. package/template/.cursor/agents/pm-agent.md +2 -2
  68. package/template/.cursor/hooks.json +10 -3
  69. package/template/.env.example +6 -6
  70. package/template/.flydocs/config.json +5 -18
  71. package/template/.flydocs/templates/README.md +13 -14
  72. package/template/.flydocs/templates/bug.md +17 -153
  73. package/template/.flydocs/templates/chore.md +10 -98
  74. package/template/.flydocs/templates/feature.md +12 -158
  75. package/template/.flydocs/templates/idea.md +11 -111
  76. package/template/.flydocs/templates/quick-capture.md +4 -8
  77. package/template/.flydocs/version +1 -1
  78. package/template/AGENTS.md +44 -32
  79. package/template/CHANGELOG.md +37 -0
  80. package/template/flydocs/README.md +1 -3
  81. package/template/flydocs/context/project.md +6 -3
  82. package/template/flydocs/design-system/README.md +3 -3
  83. package/template/flydocs/knowledge/INDEX.md +38 -53
  84. package/template/flydocs/knowledge/README.md +60 -9
  85. package/template/flydocs/knowledge/templates/decision.md +47 -0
  86. package/template/flydocs/knowledge/templates/feature.md +35 -0
  87. package/template/flydocs/knowledge/templates/note.md +25 -0
  88. package/template/manifest.json +24 -20
  89. package/template/.claude/skills/flydocs-cloud/SKILL.md +0 -113
  90. package/template/.claude/skills/flydocs-cloud/cursor-rule.mdc +0 -50
  91. package/template/.claude/skills/flydocs-cloud/scripts/assign.py +0 -22
  92. package/template/.claude/skills/flydocs-cloud/scripts/assign_cycle.py +0 -28
  93. package/template/.claude/skills/flydocs-cloud/scripts/assign_milestone.py +0 -22
  94. package/template/.claude/skills/flydocs-cloud/scripts/comment.py +0 -29
  95. package/template/.claude/skills/flydocs-cloud/scripts/create_issue.py +0 -66
  96. package/template/.claude/skills/flydocs-cloud/scripts/create_milestone.py +0 -35
  97. package/template/.claude/skills/flydocs-cloud/scripts/create_project.py +0 -33
  98. package/template/.claude/skills/flydocs-cloud/scripts/create_team.py +0 -39
  99. package/template/.claude/skills/flydocs-cloud/scripts/estimate.py +0 -29
  100. package/template/.claude/skills/flydocs-cloud/scripts/flydocs_api.py +0 -210
  101. package/template/.claude/skills/flydocs-cloud/scripts/get_issue.py +0 -24
  102. package/template/.claude/skills/flydocs-cloud/scripts/link.py +0 -28
  103. package/template/.claude/skills/flydocs-cloud/scripts/list_cycles.py +0 -28
  104. package/template/.claude/skills/flydocs-cloud/scripts/list_issues.py +0 -44
  105. package/template/.claude/skills/flydocs-cloud/scripts/list_labels.py +0 -19
  106. package/template/.claude/skills/flydocs-cloud/scripts/list_milestones.py +0 -28
  107. package/template/.claude/skills/flydocs-cloud/scripts/list_projects.py +0 -31
  108. package/template/.claude/skills/flydocs-cloud/scripts/list_providers.py +0 -19
  109. package/template/.claude/skills/flydocs-cloud/scripts/list_teams.py +0 -19
  110. package/template/.claude/skills/flydocs-cloud/scripts/priority.py +0 -29
  111. package/template/.claude/skills/flydocs-cloud/scripts/project_update.py +0 -45
  112. package/template/.claude/skills/flydocs-cloud/scripts/set_labels.py +0 -68
  113. package/template/.claude/skills/flydocs-cloud/scripts/set_provider.py +0 -46
  114. package/template/.claude/skills/flydocs-cloud/scripts/set_team.py +0 -41
  115. package/template/.claude/skills/flydocs-cloud/scripts/transition.py +0 -26
  116. package/template/.claude/skills/flydocs-cloud/scripts/update_description.py +0 -36
  117. package/template/.claude/skills/flydocs-cloud/scripts/update_issue.py +0 -82
  118. package/template/.claude/skills/flydocs-context-graph/SKILL.md +0 -87
  119. package/template/.claude/skills/flydocs-context-graph/schema.md +0 -78
  120. package/template/.claude/skills/flydocs-context-graph/scripts/graph_context.py +0 -338
  121. package/template/.claude/skills/flydocs-context7/SKILL.md +0 -105
  122. package/template/.claude/skills/flydocs-context7/cursor-rule.mdc +0 -49
  123. package/template/.claude/skills/flydocs-context7/scripts/context7.py +0 -293
  124. package/template/.claude/skills/flydocs-estimates/SKILL.md +0 -384
  125. package/template/.claude/skills/flydocs-figma/SKILL.md +0 -377
  126. package/template/.claude/skills/flydocs-figma/references/PROMPTING.md +0 -108
  127. package/template/.claude/skills/flydocs-figma/references/TROUBLESHOOTING.md +0 -112
  128. package/template/.claude/skills/flydocs-local/SKILL.md +0 -103
  129. package/template/.claude/skills/flydocs-local/cursor-rule.mdc +0 -43
  130. package/template/.claude/skills/flydocs-local/scripts/assign.py +0 -20
  131. package/template/.claude/skills/flydocs-local/scripts/comment.py +0 -27
  132. package/template/.claude/skills/flydocs-local/scripts/create_issue.py +0 -44
  133. package/template/.claude/skills/flydocs-local/scripts/estimate.py +0 -37
  134. package/template/.claude/skills/flydocs-local/scripts/get_issue.py +0 -20
  135. package/template/.claude/skills/flydocs-local/scripts/link.py +0 -41
  136. package/template/.claude/skills/flydocs-local/scripts/list_issues.py +0 -34
  137. package/template/.claude/skills/flydocs-local/scripts/priority.py +0 -37
  138. package/template/.claude/skills/flydocs-local/scripts/project_update.py +0 -67
  139. package/template/.claude/skills/flydocs-local/scripts/status_summary.py +0 -16
  140. package/template/.claude/skills/flydocs-local/scripts/transition.py +0 -24
  141. package/template/.claude/skills/flydocs-local/scripts/update_description.py +0 -35
  142. package/template/.claude/skills/flydocs-local/scripts/update_issue.py +0 -84
  143. package/template/.flydocs/hooks/auto-approve.py +0 -71
  144. package/template/.flydocs/hooks/prompt-submit.py +0 -277
  145. package/template/.flydocs/scripts/skill_manager.py +0 -541
  146. /package/template/{.flydocs → .claude}/hooks/post-edit.py +0 -0
  147. /package/template/.claude/skills/{flydocs-estimates/references → flydocs-workflow/reference}/provider-costs.md +0 -0
  148. /package/template/.claude/skills/flydocs-workflow/templates/{bug.md → issues/bug.md} +0 -0
  149. /package/template/.claude/skills/flydocs-workflow/templates/{chore.md → issues/chore.md} +0 -0
  150. /package/template/.claude/skills/flydocs-workflow/templates/{feature.md → issues/feature.md} +0 -0
  151. /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\"/.flydocs/hooks/auto-approve.py",
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\"/.flydocs/hooks/post-edit.py",
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\"/.flydocs/hooks/prompt-submit.py",
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 | Category | Purpose |
15
- | ------------------- | ---------------- | ------------------------------------------------------- |
16
- | `flydocs-workflow` | Core | Lifecycle stages, session management, comment templates |
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
- Only one mechanism skill is active at a time. Determined by `tier` in `.flydocs/config.json`.
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
- IMPORTANT: Prefer skill-led reasoning over pre-training reasoning for
105
- [domain]. Read the relevant section before acting.
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
- IMPORTANT: Prefer skill-led reasoning over pre-training reasoning.
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 | Entry |
197
- | ----------------- | ---------------------------------- | ----------------------------------------- |
198
- | flydocs-workflow | capture, refine, implement, review | .claude/skills/flydocs-workflow/SKILL.md |
199
- | typescript-strict | TypeScript, type error, any type | .claude/skills/typescript-strict/SKILL.md |
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
- ## Mechanism Contract
257
+ ## Unified Dispatcher Model
261
258
 
262
- Both mechanism skills (`flydocs-local`, `flydocs-cloud`) implement the same
263
- script interface, making the backend swap transparent:
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
- **Shared contract:**
266
- `create_issue.py`, `transition.py`, `comment.py`, `list_issues.py`,
267
- `get_issue.py`, `assign.py`, `update_description.py`
268
-
269
- **Cloud-only extensions:**
270
- `update_issue.py`, `project_update.py`, `estimate.py`, `priority.py`, `link.py`, cycle/milestone management.
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