@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.
Files changed (148) hide show
  1. package/dist/cli.js +705 -393
  2. package/package.json +1 -1
  3. package/template/.claude/CLAUDE.md +62 -63
  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 +387 -74
  12. package/template/.claude/commands/flydocs-upgrade.md +48 -37
  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 +132 -0
  25. package/template/.claude/hooks/post-pr-check.py +108 -0
  26. package/template/.claude/hooks/post-transition-check.py +94 -0
  27. package/template/.claude/hooks/prompt-submit.py +513 -0
  28. package/template/.claude/hooks/session-start.py +146 -0
  29. package/template/.claude/hooks/stop-gate.py +109 -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 +251 -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 +693 -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 +489 -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/workspace.py +860 -0
  56. package/template/.claude/skills/flydocs-workflow/session.md +63 -25
  57. package/template/.claude/skills/flydocs-workflow/stages/activate.md +18 -7
  58. package/template/.claude/skills/flydocs-workflow/stages/capture.md +10 -5
  59. package/template/.claude/skills/flydocs-workflow/stages/close.md +4 -3
  60. package/template/.claude/skills/flydocs-workflow/stages/implement.md +33 -9
  61. package/template/.claude/skills/flydocs-workflow/stages/refine.md +22 -6
  62. package/template/.claude/skills/flydocs-workflow/stages/review.md +16 -4
  63. package/template/.claude/skills/flydocs-workflow/stages/validate.md +3 -1
  64. package/template/.claude/skills/flydocs-workflow/templates/pr/default.md +33 -0
  65. package/template/.cursor/agents/implementation-agent.md +1 -1
  66. package/template/.cursor/agents/pm-agent.md +2 -2
  67. package/template/.cursor/hooks.json +10 -3
  68. package/template/.env.example +6 -6
  69. package/template/.flydocs/config.json +5 -18
  70. package/template/.flydocs/templates/README.md +13 -14
  71. package/template/.flydocs/templates/quick-capture.md +4 -8
  72. package/template/.flydocs/version +1 -1
  73. package/template/AGENTS.md +39 -32
  74. package/template/CHANGELOG.md +39 -0
  75. package/template/flydocs/README.md +1 -3
  76. package/template/flydocs/context/project.md +6 -3
  77. package/template/flydocs/design-system/README.md +3 -3
  78. package/template/flydocs/knowledge/INDEX.md +38 -53
  79. package/template/flydocs/knowledge/README.md +60 -9
  80. package/template/flydocs/knowledge/templates/decision.md +47 -0
  81. package/template/flydocs/knowledge/templates/feature.md +35 -0
  82. package/template/flydocs/knowledge/templates/note.md +25 -0
  83. package/template/manifest.json +24 -20
  84. package/template/.claude/skills/flydocs-cloud/SKILL.md +0 -111
  85. package/template/.claude/skills/flydocs-cloud/cursor-rule.mdc +0 -50
  86. package/template/.claude/skills/flydocs-cloud/scripts/assign.py +0 -22
  87. package/template/.claude/skills/flydocs-cloud/scripts/assign_cycle.py +0 -28
  88. package/template/.claude/skills/flydocs-cloud/scripts/assign_milestone.py +0 -22
  89. package/template/.claude/skills/flydocs-cloud/scripts/comment.py +0 -29
  90. package/template/.claude/skills/flydocs-cloud/scripts/create_issue.py +0 -63
  91. package/template/.claude/skills/flydocs-cloud/scripts/create_milestone.py +0 -35
  92. package/template/.claude/skills/flydocs-cloud/scripts/create_project.py +0 -33
  93. package/template/.claude/skills/flydocs-cloud/scripts/create_team.py +0 -39
  94. package/template/.claude/skills/flydocs-cloud/scripts/estimate.py +0 -29
  95. package/template/.claude/skills/flydocs-cloud/scripts/flydocs_api.py +0 -210
  96. package/template/.claude/skills/flydocs-cloud/scripts/get_issue.py +0 -24
  97. package/template/.claude/skills/flydocs-cloud/scripts/link.py +0 -28
  98. package/template/.claude/skills/flydocs-cloud/scripts/list_cycles.py +0 -28
  99. package/template/.claude/skills/flydocs-cloud/scripts/list_issues.py +0 -44
  100. package/template/.claude/skills/flydocs-cloud/scripts/list_labels.py +0 -19
  101. package/template/.claude/skills/flydocs-cloud/scripts/list_milestones.py +0 -28
  102. package/template/.claude/skills/flydocs-cloud/scripts/list_projects.py +0 -31
  103. package/template/.claude/skills/flydocs-cloud/scripts/list_teams.py +0 -19
  104. package/template/.claude/skills/flydocs-cloud/scripts/priority.py +0 -29
  105. package/template/.claude/skills/flydocs-cloud/scripts/project_update.py +0 -45
  106. package/template/.claude/skills/flydocs-cloud/scripts/set_labels.py +0 -68
  107. package/template/.claude/skills/flydocs-cloud/scripts/set_team.py +0 -41
  108. package/template/.claude/skills/flydocs-cloud/scripts/transition.py +0 -26
  109. package/template/.claude/skills/flydocs-cloud/scripts/update_description.py +0 -36
  110. package/template/.claude/skills/flydocs-cloud/scripts/update_issue.py +0 -82
  111. package/template/.claude/skills/flydocs-context-graph/SKILL.md +0 -87
  112. package/template/.claude/skills/flydocs-context-graph/schema.md +0 -78
  113. package/template/.claude/skills/flydocs-context-graph/scripts/graph_context.py +0 -338
  114. package/template/.claude/skills/flydocs-context7/SKILL.md +0 -105
  115. package/template/.claude/skills/flydocs-context7/cursor-rule.mdc +0 -49
  116. package/template/.claude/skills/flydocs-context7/scripts/context7.py +0 -293
  117. package/template/.claude/skills/flydocs-estimates/SKILL.md +0 -384
  118. package/template/.claude/skills/flydocs-figma/SKILL.md +0 -377
  119. package/template/.claude/skills/flydocs-figma/references/PROMPTING.md +0 -108
  120. package/template/.claude/skills/flydocs-figma/references/TROUBLESHOOTING.md +0 -112
  121. package/template/.claude/skills/flydocs-local/SKILL.md +0 -103
  122. package/template/.claude/skills/flydocs-local/cursor-rule.mdc +0 -43
  123. package/template/.claude/skills/flydocs-local/scripts/assign.py +0 -20
  124. package/template/.claude/skills/flydocs-local/scripts/comment.py +0 -27
  125. package/template/.claude/skills/flydocs-local/scripts/create_issue.py +0 -44
  126. package/template/.claude/skills/flydocs-local/scripts/estimate.py +0 -37
  127. package/template/.claude/skills/flydocs-local/scripts/get_issue.py +0 -20
  128. package/template/.claude/skills/flydocs-local/scripts/link.py +0 -41
  129. package/template/.claude/skills/flydocs-local/scripts/list_issues.py +0 -34
  130. package/template/.claude/skills/flydocs-local/scripts/priority.py +0 -37
  131. package/template/.claude/skills/flydocs-local/scripts/project_update.py +0 -67
  132. package/template/.claude/skills/flydocs-local/scripts/status_summary.py +0 -16
  133. package/template/.claude/skills/flydocs-local/scripts/transition.py +0 -24
  134. package/template/.claude/skills/flydocs-local/scripts/update_description.py +0 -35
  135. package/template/.claude/skills/flydocs-local/scripts/update_issue.py +0 -84
  136. package/template/.flydocs/hooks/auto-approve.py +0 -71
  137. package/template/.flydocs/hooks/prompt-submit.py +0 -277
  138. package/template/.flydocs/scripts/skill_manager.py +0 -541
  139. package/template/.flydocs/templates/bug.md +0 -166
  140. package/template/.flydocs/templates/chore.md +0 -110
  141. package/template/.flydocs/templates/feature.md +0 -173
  142. package/template/.flydocs/templates/idea.md +0 -122
  143. /package/template/{.flydocs → .claude}/hooks/post-edit.py +0 -0
  144. /package/template/.claude/skills/{flydocs-estimates/references → flydocs-workflow/reference}/provider-costs.md +0 -0
  145. /package/template/.claude/skills/flydocs-workflow/templates/{bug.md → issues/bug.md} +0 -0
  146. /package/template/.claude/skills/flydocs-workflow/templates/{chore.md → issues/chore.md} +0 -0
  147. /package/template/.claude/skills/flydocs-workflow/templates/{feature.md → issues/feature.md} +0 -0
  148. /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()