@flydocs/cli 0.5.0-beta.9 → 0.6.0-alpha.10

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 (74) hide show
  1. package/README.md +6 -0
  2. package/dist/cli.js +1553 -414
  3. package/package.json +1 -1
  4. package/template/.claude/CLAUDE.md +11 -9
  5. package/template/.claude/agents/implementation-agent.md +0 -1
  6. package/template/.claude/agents/pm-agent.md +0 -1
  7. package/template/.claude/agents/research-agent.md +0 -1
  8. package/template/.claude/agents/review-agent.md +0 -1
  9. package/template/.claude/commands/flydocs-setup.md +202 -35
  10. package/template/.claude/commands/flydocs-upgrade.md +342 -0
  11. package/template/.claude/commands/knowledge.md +61 -0
  12. package/template/.claude/skills/flydocs-cloud/SKILL.md +66 -39
  13. package/template/.claude/skills/flydocs-cloud/cursor-rule.mdc +5 -5
  14. package/template/.claude/skills/flydocs-cloud/scripts/assign.py +17 -27
  15. package/template/.claude/skills/flydocs-cloud/scripts/assign_cycle.py +14 -30
  16. package/template/.claude/skills/flydocs-cloud/scripts/assign_milestone.py +10 -32
  17. package/template/.claude/skills/flydocs-cloud/scripts/comment.py +15 -25
  18. package/template/.claude/skills/flydocs-cloud/scripts/create_issue.py +42 -59
  19. package/template/.claude/skills/flydocs-cloud/scripts/create_milestone.py +26 -37
  20. package/template/.claude/skills/flydocs-cloud/scripts/create_project.py +24 -31
  21. package/template/.claude/skills/flydocs-cloud/scripts/create_team.py +39 -0
  22. package/template/.claude/skills/flydocs-cloud/scripts/delete_milestone.py +21 -0
  23. package/template/.claude/skills/flydocs-cloud/scripts/estimate.py +17 -22
  24. package/template/.claude/skills/flydocs-cloud/scripts/flydocs_api.py +113 -169
  25. package/template/.claude/skills/flydocs-cloud/scripts/get_estimate_scale.py +23 -0
  26. package/template/.claude/skills/flydocs-cloud/scripts/get_issue.py +6 -59
  27. package/template/.claude/skills/flydocs-cloud/scripts/link.py +16 -35
  28. package/template/.claude/skills/flydocs-cloud/scripts/list_cycles.py +21 -28
  29. package/template/.claude/skills/flydocs-cloud/scripts/list_issues.py +16 -77
  30. package/template/.claude/skills/flydocs-cloud/scripts/list_labels.py +19 -0
  31. package/template/.claude/skills/flydocs-cloud/scripts/list_milestones.py +21 -33
  32. package/template/.claude/skills/flydocs-cloud/scripts/list_projects.py +24 -38
  33. package/template/.claude/skills/flydocs-cloud/scripts/list_providers.py +19 -0
  34. package/template/.claude/skills/flydocs-cloud/scripts/list_statuses.py +19 -0
  35. package/template/.claude/skills/flydocs-cloud/scripts/list_teams.py +19 -0
  36. package/template/.claude/skills/flydocs-cloud/scripts/priority.py +10 -19
  37. package/template/.claude/skills/flydocs-cloud/scripts/project_update.py +36 -50
  38. package/template/.claude/skills/flydocs-cloud/scripts/refresh_labels.py +87 -0
  39. package/template/.claude/skills/flydocs-cloud/scripts/set_identity.py +38 -0
  40. package/template/.claude/skills/flydocs-cloud/scripts/set_labels.py +68 -0
  41. package/template/.claude/skills/flydocs-cloud/scripts/set_preferences.py +49 -0
  42. package/template/.claude/skills/flydocs-cloud/scripts/set_provider.py +46 -0
  43. package/template/.claude/skills/flydocs-cloud/scripts/set_status_mapping.py +69 -0
  44. package/template/.claude/skills/flydocs-cloud/scripts/set_team.py +42 -0
  45. package/template/.claude/skills/flydocs-cloud/scripts/transition.py +11 -52
  46. package/template/.claude/skills/flydocs-cloud/scripts/update_description.py +16 -27
  47. package/template/.claude/skills/flydocs-cloud/scripts/update_issue.py +43 -54
  48. package/template/.claude/skills/flydocs-cloud/scripts/update_milestone.py +42 -0
  49. package/template/.claude/skills/flydocs-cloud/scripts/validate_setup.py +139 -0
  50. package/template/.claude/skills/flydocs-local/SKILL.md +1 -1
  51. package/template/.claude/skills/flydocs-local/scripts/assign.py +13 -4
  52. package/template/.claude/skills/flydocs-local/scripts/flydocs_api.py +5 -2
  53. package/template/.claude/skills/flydocs-workflow/SKILL.md +23 -18
  54. package/template/.claude/skills/flydocs-workflow/reference/comment-templates.md +1 -0
  55. package/template/.claude/skills/flydocs-workflow/reference/pr-workflow.md +105 -0
  56. package/template/.claude/skills/flydocs-workflow/reference/priority-estimates.md +37 -15
  57. package/template/.claude/skills/flydocs-workflow/session.md +24 -16
  58. package/template/.claude/skills/flydocs-workflow/stages/capture.md +8 -3
  59. package/template/.claude/skills/flydocs-workflow/stages/close.md +4 -3
  60. package/template/.claude/skills/flydocs-workflow/stages/implement.md +28 -4
  61. package/template/.claude/skills/flydocs-workflow/stages/refine.md +20 -4
  62. package/template/.claude/skills/flydocs-workflow/stages/review.md +14 -2
  63. package/template/.env.example +16 -7
  64. package/template/.flydocs/config.json +4 -18
  65. package/template/.flydocs/hooks/prompt-submit.py +27 -4
  66. package/template/.flydocs/version +1 -1
  67. package/template/AGENTS.md +8 -8
  68. package/template/CHANGELOG.md +183 -0
  69. package/template/flydocs/knowledge/INDEX.md +38 -53
  70. package/template/flydocs/knowledge/README.md +60 -9
  71. package/template/flydocs/knowledge/templates/decision.md +47 -0
  72. package/template/flydocs/knowledge/templates/feature.md +35 -0
  73. package/template/flydocs/knowledge/templates/note.md +25 -0
  74. package/template/manifest.json +12 -4
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- """Assign an issue to a cycle (sprint)."""
2
+ """Assign a cycle to an issue via the FlyDocs Relay API."""
3
3
 
4
4
  import sys
5
5
  from pathlib import Path
@@ -11,34 +11,18 @@ if len(sys.argv) < 2:
11
11
  fail("Usage: assign_cycle.py <ref> [cycle_id]")
12
12
 
13
13
  ref = sys.argv[1]
14
+ cycle_id = sys.argv[2] if len(sys.argv) >= 3 else None
15
+
14
16
  client = get_client()
15
17
 
16
- issue_uuid = client.resolve_issue_id(ref)
17
- if not issue_uuid:
18
- fail(f"Issue not found: {ref}")
19
-
20
- # Use specified cycle or active cycle
21
- cycle_id = sys.argv[2] if len(sys.argv) > 2 else None
22
- cycle_name = cycle_id or ""
23
- if not cycle_id:
24
- cycle = client.get_active_cycle()
25
- if not cycle:
26
- fail("No active cycle found")
27
- cycle_id = cycle["id"]
28
- cycle_name = cycle["name"]
29
-
30
- result = client.query(
31
- """mutation($id: String!, $cycleId: String!) {
32
- issueUpdate(id: $id, input: { cycleId: $cycleId }) {
33
- success
34
- issue { identifier }
35
- }
36
- }""",
37
- {"id": issue_uuid, "cycleId": cycle_id},
38
- )
39
-
40
- if not result.get("data", {}).get("issueUpdate", {}).get("success"):
41
- fail(f"Failed to assign cycle: {result}")
42
-
43
- issue = result["data"]["issueUpdate"]["issue"]
44
- output_json({"success": True, "issue": issue["identifier"], "cycle": cycle_name})
18
+ body: dict = {}
19
+ if cycle_id:
20
+ body["cycleId"] = cycle_id
21
+
22
+ result = client.put(f"/issues/{ref}/cycle", body)
23
+
24
+ output_json({
25
+ "success": result.get("success", True),
26
+ "issue": result.get("issue", ref),
27
+ "cycle": result.get("cycle", ""),
28
+ })
@@ -1,44 +1,22 @@
1
1
  #!/usr/bin/env python3
2
- """Assign an issue to a milestone."""
2
+ """Assign a milestone to an issue via the FlyDocs Relay API."""
3
3
 
4
- import argparse
5
4
  import sys
6
5
  from pathlib import Path
7
6
 
8
7
  sys.path.insert(0, str(Path(__file__).parent))
9
8
  from flydocs_api import get_client, output_json, fail
10
9
 
11
- parser = argparse.ArgumentParser(description="Assign issue to milestone")
12
- parser.add_argument("ref", help="Issue reference (e.g., ENG-123)")
13
- parser.add_argument("milestone_id", nargs="?", default=None,
14
- help="Milestone UUID (positional)")
15
- parser.add_argument("--milestone", dest="milestone_flag", default=None,
16
- help="Milestone UUID (flag)")
17
- args = parser.parse_args()
18
-
19
- milestone_id = args.milestone_flag or args.milestone_id
20
- if not milestone_id:
21
- fail("Milestone ID required. Usage: assign_milestone.py <ref> <milestone_id> or --milestone <id>")
10
+ if len(sys.argv) < 3:
11
+ fail("Usage: assign_milestone.py <ref> <milestone_id>")
22
12
 
13
+ ref, milestone_id = sys.argv[1], sys.argv[2]
23
14
  client = get_client()
24
15
 
25
- issue_uuid = client.resolve_issue_id(args.ref)
26
- if not issue_uuid:
27
- fail(f"Issue not found: {args.ref}")
28
-
29
- result = client.query(
30
- """mutation($id: String!, $milestoneId: String!) {
31
- issueUpdate(id: $id, input: { projectMilestoneId: $milestoneId }) {
32
- success
33
- issue { identifier }
34
- }
35
- }""",
36
- {"id": issue_uuid, "milestoneId": milestone_id},
37
- )
38
-
39
- update = result.get("data", {}).get("issueUpdate", {})
40
- if not update.get("success"):
41
- fail(f"Failed to assign milestone: {result}")
16
+ result = client.put(f"/issues/{ref}/milestone", {"milestoneId": milestone_id})
42
17
 
43
- issue = update.get("issue", {})
44
- output_json({"success": True, "issue": issue.get("identifier", args.ref), "milestone": milestone_id})
18
+ output_json({
19
+ "success": result.get("success", True),
20
+ "issue": result.get("issue", ref),
21
+ "milestone": result.get("milestone", ""),
22
+ })
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- """Add a comment to an issue."""
2
+ """Add a comment to an issue via the FlyDocs Relay API."""
3
3
 
4
4
  import sys
5
5
  from pathlib import Path
@@ -8,32 +8,22 @@ sys.path.insert(0, str(Path(__file__).parent))
8
8
  from flydocs_api import get_client, output_json, fail
9
9
 
10
10
  if len(sys.argv) < 2:
11
- fail("Usage: comment.py <ref> [<comment>] (or pipe via stdin)")
11
+ fail("Usage: comment.py <ref> [comment] | stdin")
12
12
 
13
13
  ref = sys.argv[1]
14
- body = sys.argv[2] if len(sys.argv) > 2 else ""
15
- if not body and not sys.stdin.isatty():
16
- body = sys.stdin.read().strip()
17
- if not body:
18
- fail("Provide comment as argument or pipe via stdin")
19
- client = get_client()
20
-
21
- issue_uuid = client.resolve_issue_id(ref)
22
- if not issue_uuid:
23
- fail(f"Issue not found: {ref}")
24
14
 
25
- result = client.query(
26
- """mutation($id: String!, $body: String!) {
27
- commentCreate(input: { issueId: $id, body: $body }) {
28
- success
29
- comment { id }
30
- }
31
- }""",
32
- {"id": issue_uuid, "body": body},
33
- )
15
+ # Comment from arg > stdin
16
+ if len(sys.argv) >= 3:
17
+ body = sys.argv[2]
18
+ elif not sys.stdin.isatty():
19
+ body = sys.stdin.read().strip()
20
+ else:
21
+ fail("Provide comment as argument or via stdin")
34
22
 
35
- data = result.get("data", {}).get("commentCreate", {})
36
- if not data.get("success"):
37
- fail(f"Failed to add comment: {result}")
23
+ client = get_client()
24
+ result = client.post(f"/issues/{ref}/comment", {"body": body})
38
25
 
39
- output_json({"success": True, "commentId": data.get("comment", {}).get("id", "")})
26
+ output_json({
27
+ "success": result.get("success", True),
28
+ "commentId": result.get("commentId", ""),
29
+ })
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- """Create a new issue in Linear."""
2
+ """Create a new issue via the FlyDocs Relay API."""
3
3
 
4
4
  import argparse
5
5
  import sys
@@ -15,10 +15,12 @@ def main():
15
15
  parser.add_argument("--type", required=True, choices=["feature", "bug", "chore", "idea"], dest="issue_type")
16
16
  parser.add_argument("--description", default="")
17
17
  parser.add_argument("--description-file", default="", dest="description_file")
18
- parser.add_argument("--priority", type=int, default=3, choices=range(5))
19
- parser.add_argument("--estimate", type=int, default=0, choices=[0, 1, 2, 3, 5])
18
+ parser.add_argument("--priority", type=int, default=3, help="Priority (0-4, relay translates per provider)")
19
+ parser.add_argument("--estimate", type=int, default=0, help="Estimate points (relay translates per provider)")
20
20
  parser.add_argument("--assignee", default=None)
21
- parser.add_argument("--project", default=None)
21
+ parser.add_argument("--project", default=None, help="Project ID")
22
+ parser.add_argument("--labels", default=None, help="Comma-separated ad-hoc label names")
23
+ parser.add_argument("--milestone", default=None, help="Milestone ID or name (resolved by name lookup)")
22
24
  parser.add_argument("--triage", action="store_true")
23
25
  args = parser.parse_args()
24
26
 
@@ -32,68 +34,49 @@ def main():
32
34
  elif not description and not sys.stdin.isatty():
33
35
  description = sys.stdin.read().strip()
34
36
 
35
- client = get_client()
36
-
37
- issue_input = {
38
- "teamId": client.team_id,
37
+ body: dict = {
39
38
  "title": args.title,
40
- "description": description,
39
+ "type": args.issue_type,
41
40
  "priority": args.priority,
42
41
  }
42
+ if description:
43
+ body["description"] = description
43
44
  if args.estimate:
44
- issue_input["estimate"] = args.estimate
45
-
46
- # Labels
47
- label_ids = []
48
- cat_id = client.get_category_label_id(args.issue_type)
49
- if cat_id:
50
- label_ids.append(cat_id)
51
- if args.triage:
52
- triage_id = client.get_other_label_id("triage")
53
- if triage_id:
54
- label_ids.append(triage_id)
55
- if label_ids:
56
- issue_input["labelIds"] = label_ids
57
-
58
- # Project
59
- project_id = args.project
60
- if not project_id:
61
- active = client.workspace.get("activeProjects", [])
62
- if active:
63
- project_id = active[0]
64
- if project_id:
65
- issue_input["projectId"] = project_id
66
-
67
- # Assignee
45
+ body["estimate"] = args.estimate
68
46
  if args.assignee:
69
- user_id, _ = client.resolve_user_id(args.assignee)
70
- if user_id:
71
- issue_input["assigneeId"] = user_id
72
-
73
- mutation = """mutation($input: IssueCreateInput!) {
74
- issueCreate(input: $input) {
75
- success
76
- issue { id identifier title url }
77
- }
78
- }"""
79
-
80
- result = client.query(mutation, {"input": issue_input})
47
+ body["assignee"] = args.assignee
48
+ if args.project:
49
+ body["projectId"] = args.project
50
+ if args.labels:
51
+ body["labels"] = [l.strip() for l in args.labels.split(",") if l.strip()]
52
+ if args.triage:
53
+ body["triage"] = True
81
54
 
82
- data = result.get("data", {}).get("issueCreate", {})
83
- if not data.get("success"):
84
- # Retry without labels if team mismatch (stale label IDs in config)
85
- errors = result.get("errors", [])
86
- label_error = any("label" in (e.get("message", "") or "").lower() for e in errors)
87
- if label_error and "labelIds" in issue_input:
88
- print("Label IDs rejected — retrying without labels...", file=sys.stderr)
89
- del issue_input["labelIds"]
90
- result = client.query(mutation, {"input": issue_input})
91
- data = result.get("data", {}).get("issueCreate", {})
92
- if not data.get("success"):
93
- fail(f"Failed to create issue: {result}")
55
+ client = get_client()
94
56
 
95
- issue = data["issue"]
96
- output_json({"id": issue["id"], "identifier": issue["identifier"], "title": issue["title"], "url": issue["url"]})
57
+ if args.milestone:
58
+ milestone_id = args.milestone
59
+ # If it doesn't look like a UUID, resolve by name
60
+ if len(milestone_id) != 36 or "-" not in milestone_id:
61
+ milestones = client.get("/milestones")
62
+ match = None
63
+ for m in milestones:
64
+ if m["name"].lower() == milestone_id.lower():
65
+ match = m
66
+ break
67
+ if not match:
68
+ fail(f"Milestone not found: {milestone_id}")
69
+ milestone_id = match["id"]
70
+ body["milestoneId"] = milestone_id
71
+
72
+ result = client.post("/issues", body)
73
+
74
+ output_json({
75
+ "id": result["id"],
76
+ "identifier": result["identifier"],
77
+ "title": result["title"],
78
+ "url": result["url"],
79
+ })
97
80
 
98
81
 
99
82
  if __name__ == "__main__":
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- """Create a milestone."""
2
+ """Create a milestone via the FlyDocs Relay API."""
3
3
 
4
4
  import argparse
5
5
  import sys
@@ -8,39 +8,28 @@ from pathlib import Path
8
8
  sys.path.insert(0, str(Path(__file__).parent))
9
9
  from flydocs_api import get_client, output_json, fail
10
10
 
11
- parser = argparse.ArgumentParser(description="Create milestone")
12
- parser.add_argument("--name", required=True)
13
- parser.add_argument("--project", default="", help="Project ID (defaults to first activeProject)")
14
- parser.add_argument("--target-date", default=None, help="YYYY-MM-DD")
15
- args = parser.parse_args()
16
-
17
- client = get_client()
18
-
19
- project_id = args.project
20
- if not project_id:
21
- active = client.workspace.get("activeProjects", [])
22
- if active:
23
- project_id = active[0]
24
- else:
25
- fail("No --project specified and no activeProjects in config")
26
-
27
- milestone_input = {"name": args.name, "projectId": project_id}
28
- if args.target_date:
29
- milestone_input["targetDate"] = args.target_date
30
-
31
- result = client.query(
32
- """mutation($input: ProjectMilestoneCreateInput!) {
33
- projectMilestoneCreate(input: $input) {
34
- success
35
- projectMilestone { id name }
36
- }
37
- }""",
38
- {"input": milestone_input},
39
- )
40
-
41
- data = result.get("data", {}).get("projectMilestoneCreate", {})
42
- if not data.get("success"):
43
- fail(f"Failed to create milestone: {result}")
44
-
45
- ms = data["projectMilestone"]
46
- output_json({"id": ms["id"], "name": ms["name"]})
11
+
12
+ def main():
13
+ parser = argparse.ArgumentParser(description="Create milestone")
14
+ parser.add_argument("--name", required=True)
15
+ parser.add_argument("--project", default=None, help="Project ID")
16
+ parser.add_argument("--target-date", default=None, dest="target_date")
17
+ args = parser.parse_args()
18
+
19
+ body: dict = {"name": args.name}
20
+ if args.project:
21
+ body["projectId"] = args.project
22
+ if args.target_date:
23
+ body["targetDate"] = args.target_date
24
+
25
+ client = get_client()
26
+ result = client.post("/milestones", body)
27
+
28
+ output_json({
29
+ "id": result["id"],
30
+ "name": result["name"],
31
+ })
32
+
33
+
34
+ if __name__ == "__main__":
35
+ main()
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- """Create a new project."""
2
+ """Create a project via the FlyDocs Relay API."""
3
3
 
4
4
  import argparse
5
5
  import sys
@@ -8,33 +8,26 @@ from pathlib import Path
8
8
  sys.path.insert(0, str(Path(__file__).parent))
9
9
  from flydocs_api import get_client, output_json, fail
10
10
 
11
- parser = argparse.ArgumentParser(description="Create project")
12
- parser.add_argument("--name", required=True)
13
- parser.add_argument("--description", default="")
14
- args = parser.parse_args()
15
-
16
- client = get_client()
17
-
18
- project_input = {
19
- "name": args.name,
20
- "teamIds": [client.team_id],
21
- }
22
- if args.description:
23
- project_input["description"] = args.description
24
-
25
- result = client.query(
26
- """mutation($input: ProjectCreateInput!) {
27
- projectCreate(input: $input) {
28
- success
29
- project { id name url }
30
- }
31
- }""",
32
- {"input": project_input},
33
- )
34
-
35
- data = result.get("data", {}).get("projectCreate", {})
36
- if not data.get("success"):
37
- fail(f"Failed to create project: {result}")
38
-
39
- project = data["project"]
40
- output_json({"id": project["id"], "name": project["name"], "url": project["url"]})
11
+
12
+ def main():
13
+ parser = argparse.ArgumentParser(description="Create project")
14
+ parser.add_argument("--name", required=True)
15
+ parser.add_argument("--description", default=None)
16
+ args = parser.parse_args()
17
+
18
+ body: dict = {"name": args.name}
19
+ if args.description:
20
+ body["description"] = args.description
21
+
22
+ client = get_client()
23
+ result = client.post("/projects", body)
24
+
25
+ output_json({
26
+ "id": result["id"],
27
+ "name": result["name"],
28
+ "url": result.get("url", ""),
29
+ })
30
+
31
+
32
+ if __name__ == "__main__":
33
+ main()
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env python3
2
+ """Create a team/project via the FlyDocs Relay API."""
3
+
4
+ import argparse
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ sys.path.insert(0, str(Path(__file__).parent))
9
+ from flydocs_api import get_client, output_json
10
+
11
+
12
+ def main():
13
+ parser = argparse.ArgumentParser(description="Create team or project")
14
+ parser.add_argument("--name", required=True)
15
+ parser.add_argument("--key", default=None, help="Team key (e.g., PROD). Auto-generated if omitted.")
16
+ parser.add_argument("--description", default=None)
17
+ parser.add_argument("--parent", default=None, dest="parent_id", help="Parent team ID for sub-team (Linear only, ignored for Jira)")
18
+ args = parser.parse_args()
19
+
20
+ body: dict = {"name": args.name}
21
+ if args.key:
22
+ body["key"] = args.key
23
+ if args.description:
24
+ body["description"] = args.description
25
+ if args.parent_id:
26
+ body["parentId"] = args.parent_id
27
+
28
+ client = get_client()
29
+ result = client.post("/teams", body)
30
+
31
+ output_json({
32
+ "id": result["id"],
33
+ "name": result["name"],
34
+ "key": result.get("key", ""),
35
+ })
36
+
37
+
38
+ if __name__ == "__main__":
39
+ main()
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env python3
2
+ """Delete a milestone via the FlyDocs Relay API."""
3
+
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ sys.path.insert(0, str(Path(__file__).parent))
8
+ from flydocs_api import get_client, output_json, fail
9
+
10
+ if len(sys.argv) < 2:
11
+ fail("Usage: delete_milestone.py <milestone_id>")
12
+
13
+ milestone_id = sys.argv[1]
14
+
15
+ client = get_client()
16
+ result = client.delete(f"/milestones/{milestone_id}")
17
+
18
+ output_json({
19
+ "success": result.get("success", True),
20
+ "id": result.get("id", milestone_id),
21
+ })
@@ -1,5 +1,9 @@
1
1
  #!/usr/bin/env python3
2
- """Set estimate on an issue."""
2
+ """Set estimate on an issue via the FlyDocs Relay API.
3
+
4
+ The relay validates the estimate against the provider's scale server-side.
5
+ Use get_estimate_scale.py to discover valid values before setting.
6
+ """
3
7
 
4
8
  import sys
5
9
  from pathlib import Path
@@ -8,31 +12,22 @@ sys.path.insert(0, str(Path(__file__).parent))
8
12
  from flydocs_api import get_client, output_json, fail
9
13
 
10
14
  if len(sys.argv) < 3:
11
- fail("Usage: estimate.py <ref> <1-5>")
15
+ fail("Usage: estimate.py <ref> <points>")
12
16
 
13
17
  ref = sys.argv[1]
14
18
  try:
15
19
  estimate = int(sys.argv[2])
16
20
  except ValueError:
17
- fail("Estimate must be a number (1-5)")
21
+ fail("Estimate must be a number")
22
+
23
+ if estimate < 0:
24
+ fail("Estimate must be a non-negative integer")
18
25
 
19
26
  client = get_client()
20
- issue_uuid = client.resolve_issue_id(ref)
21
- if not issue_uuid:
22
- fail(f"Issue not found: {ref}")
23
-
24
- result = client.query(
25
- """mutation($id: String!, $estimate: Int!) {
26
- issueUpdate(id: $id, input: { estimate: $estimate }) {
27
- success
28
- issue { identifier estimate }
29
- }
30
- }""",
31
- {"id": issue_uuid, "estimate": estimate},
32
- )
33
-
34
- if not result.get("data", {}).get("issueUpdate", {}).get("success"):
35
- fail(f"Failed to set estimate: {result}")
36
-
37
- issue = result["data"]["issueUpdate"]["issue"]
38
- output_json({"success": True, "issue": issue["identifier"], "estimate": issue["estimate"]})
27
+ result = client.put(f"/issues/{ref}/estimate", {"estimate": estimate})
28
+
29
+ output_json({
30
+ "success": result.get("success", True),
31
+ "issue": result.get("issue", ref),
32
+ "estimate": result.get("estimate", estimate),
33
+ })