@flydocs/cli 0.6.0-alpha.2 → 0.6.0-alpha.20
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 +678 -392
- package/package.json +1 -1
- package/template/.claude/CLAUDE.md +62 -63
- package/template/.claude/agents/implementation-agent.md +1 -1
- package/template/.claude/agents/pm-agent.md +1 -1
- package/template/.claude/commands/activate.md +1 -1
- package/template/.claude/commands/attach.md +1 -1
- package/template/.claude/commands/block.md +2 -2
- package/template/.claude/commands/capture.md +1 -1
- package/template/.claude/commands/close.md +1 -1
- package/template/.claude/commands/flydocs-setup.md +387 -74
- package/template/.claude/commands/flydocs-upgrade.md +48 -37
- package/template/.claude/commands/implement.md +1 -1
- package/template/.claude/commands/knowledge.md +61 -0
- package/template/.claude/commands/new-project.md +1 -1
- package/template/.claude/commands/onboard.md +275 -0
- package/template/.claude/commands/project-update.md +1 -1
- package/template/.claude/commands/refine.md +1 -1
- package/template/.claude/commands/review.md +1 -1
- package/template/.claude/commands/start-session.md +1 -1
- package/template/.claude/commands/status.md +1 -1
- package/template/.claude/commands/validate.md +1 -1
- package/template/.claude/commands/wrap-session.md +1 -1
- package/template/.claude/hooks/auto-approve.py +132 -0
- package/template/.claude/hooks/post-pr-check.py +108 -0
- package/template/.claude/hooks/post-transition-check.py +94 -0
- package/template/.claude/hooks/prompt-submit.py +513 -0
- package/template/.claude/hooks/session-start.py +146 -0
- package/template/.claude/hooks/stop-gate.py +109 -0
- package/template/.claude/settings.json +41 -4
- package/template/.claude/skills/README.md +23 -25
- package/template/.claude/skills/flydocs-workflow/SKILL.md +134 -42
- package/template/.claude/skills/flydocs-workflow/cursor-rule.mdc +9 -8
- package/template/.claude/skills/flydocs-workflow/reference/comment-templates.md +1 -0
- package/template/.claude/skills/flydocs-workflow/reference/golden-rules.md +28 -17
- package/template/.claude/skills/flydocs-workflow/reference/graph-schema.md +116 -0
- package/template/.claude/skills/flydocs-workflow/reference/pr-workflow.md +120 -0
- package/template/.claude/skills/flydocs-workflow/reference/priority-estimates.md +37 -15
- package/template/.claude/skills/flydocs-workflow/reference/service-descriptor-schema.md +251 -0
- package/template/.claude/skills/flydocs-workflow/reference/status-workflow.md +26 -26
- package/template/.claude/skills/flydocs-workflow/scripts/_local/__init__.py +0 -0
- package/template/.claude/skills/{flydocs-local/scripts/flydocs_api.py → flydocs-workflow/scripts/_local/file_store.py} +137 -47
- package/template/.claude/skills/flydocs-workflow/scripts/flydocs_api.py +693 -0
- package/template/{.flydocs → .claude/skills/flydocs-workflow}/scripts/generate_manifest.py +4 -4
- package/template/.claude/skills/{flydocs-context-graph → flydocs-workflow}/scripts/graph_build.py +132 -1
- package/template/.claude/skills/{flydocs-context-graph → flydocs-workflow}/scripts/graph_query.py +18 -5
- package/template/.claude/skills/{flydocs-context-graph → flydocs-workflow}/scripts/graph_session.py +1 -10
- package/template/.claude/skills/{flydocs-context-graph → flydocs-workflow}/scripts/graph_update.py +4 -4
- package/template/.claude/skills/{flydocs-context-graph → flydocs-workflow}/scripts/graph_utils.py +2 -1
- package/template/.claude/skills/flydocs-workflow/scripts/issues.py +489 -0
- package/template/.claude/skills/flydocs-workflow/scripts/projects.py +144 -0
- package/template/.claude/skills/flydocs-workflow/scripts/pull_services.py +128 -0
- package/template/.claude/skills/flydocs-workflow/scripts/push_service.py +132 -0
- package/template/.claude/skills/flydocs-workflow/scripts/session.py +54 -0
- package/template/.claude/skills/flydocs-workflow/scripts/workspace.py +860 -0
- package/template/.claude/skills/flydocs-workflow/session.md +63 -25
- package/template/.claude/skills/flydocs-workflow/stages/activate.md +18 -7
- package/template/.claude/skills/flydocs-workflow/stages/capture.md +10 -5
- package/template/.claude/skills/flydocs-workflow/stages/close.md +4 -3
- package/template/.claude/skills/flydocs-workflow/stages/implement.md +33 -9
- package/template/.claude/skills/flydocs-workflow/stages/refine.md +22 -6
- package/template/.claude/skills/flydocs-workflow/stages/review.md +16 -4
- package/template/.claude/skills/flydocs-workflow/stages/validate.md +3 -1
- package/template/.claude/skills/flydocs-workflow/templates/pr/default.md +33 -0
- package/template/.cursor/agents/implementation-agent.md +1 -1
- package/template/.cursor/agents/pm-agent.md +2 -2
- package/template/.cursor/hooks.json +10 -3
- package/template/.env.example +6 -6
- package/template/.flydocs/config.json +5 -18
- package/template/.flydocs/templates/README.md +13 -14
- package/template/.flydocs/templates/quick-capture.md +4 -8
- package/template/.flydocs/version +1 -1
- package/template/AGENTS.md +39 -32
- package/template/CHANGELOG.md +39 -0
- package/template/flydocs/README.md +1 -3
- package/template/flydocs/context/project.md +6 -3
- package/template/flydocs/design-system/README.md +3 -3
- package/template/flydocs/knowledge/INDEX.md +38 -53
- package/template/flydocs/knowledge/README.md +60 -9
- package/template/flydocs/knowledge/templates/decision.md +47 -0
- package/template/flydocs/knowledge/templates/feature.md +35 -0
- package/template/flydocs/knowledge/templates/note.md +25 -0
- package/template/manifest.json +24 -20
- package/template/.claude/skills/flydocs-cloud/SKILL.md +0 -111
- package/template/.claude/skills/flydocs-cloud/cursor-rule.mdc +0 -50
- package/template/.claude/skills/flydocs-cloud/scripts/assign.py +0 -22
- package/template/.claude/skills/flydocs-cloud/scripts/assign_cycle.py +0 -28
- package/template/.claude/skills/flydocs-cloud/scripts/assign_milestone.py +0 -22
- package/template/.claude/skills/flydocs-cloud/scripts/comment.py +0 -29
- package/template/.claude/skills/flydocs-cloud/scripts/create_issue.py +0 -63
- package/template/.claude/skills/flydocs-cloud/scripts/create_milestone.py +0 -35
- package/template/.claude/skills/flydocs-cloud/scripts/create_project.py +0 -33
- package/template/.claude/skills/flydocs-cloud/scripts/create_team.py +0 -39
- package/template/.claude/skills/flydocs-cloud/scripts/estimate.py +0 -29
- package/template/.claude/skills/flydocs-cloud/scripts/flydocs_api.py +0 -210
- package/template/.claude/skills/flydocs-cloud/scripts/get_issue.py +0 -24
- package/template/.claude/skills/flydocs-cloud/scripts/link.py +0 -28
- package/template/.claude/skills/flydocs-cloud/scripts/list_cycles.py +0 -28
- package/template/.claude/skills/flydocs-cloud/scripts/list_issues.py +0 -44
- package/template/.claude/skills/flydocs-cloud/scripts/list_labels.py +0 -19
- package/template/.claude/skills/flydocs-cloud/scripts/list_milestones.py +0 -28
- package/template/.claude/skills/flydocs-cloud/scripts/list_projects.py +0 -31
- package/template/.claude/skills/flydocs-cloud/scripts/list_teams.py +0 -19
- package/template/.claude/skills/flydocs-cloud/scripts/priority.py +0 -29
- package/template/.claude/skills/flydocs-cloud/scripts/project_update.py +0 -45
- package/template/.claude/skills/flydocs-cloud/scripts/set_labels.py +0 -68
- package/template/.claude/skills/flydocs-cloud/scripts/set_team.py +0 -41
- package/template/.claude/skills/flydocs-cloud/scripts/transition.py +0 -26
- package/template/.claude/skills/flydocs-cloud/scripts/update_description.py +0 -36
- package/template/.claude/skills/flydocs-cloud/scripts/update_issue.py +0 -82
- package/template/.claude/skills/flydocs-context-graph/SKILL.md +0 -87
- package/template/.claude/skills/flydocs-context-graph/schema.md +0 -78
- package/template/.claude/skills/flydocs-context-graph/scripts/graph_context.py +0 -338
- package/template/.claude/skills/flydocs-context7/SKILL.md +0 -105
- package/template/.claude/skills/flydocs-context7/cursor-rule.mdc +0 -49
- package/template/.claude/skills/flydocs-context7/scripts/context7.py +0 -293
- package/template/.claude/skills/flydocs-estimates/SKILL.md +0 -384
- package/template/.claude/skills/flydocs-figma/SKILL.md +0 -377
- package/template/.claude/skills/flydocs-figma/references/PROMPTING.md +0 -108
- package/template/.claude/skills/flydocs-figma/references/TROUBLESHOOTING.md +0 -112
- package/template/.claude/skills/flydocs-local/SKILL.md +0 -103
- package/template/.claude/skills/flydocs-local/cursor-rule.mdc +0 -43
- package/template/.claude/skills/flydocs-local/scripts/assign.py +0 -20
- package/template/.claude/skills/flydocs-local/scripts/comment.py +0 -27
- package/template/.claude/skills/flydocs-local/scripts/create_issue.py +0 -44
- package/template/.claude/skills/flydocs-local/scripts/estimate.py +0 -37
- package/template/.claude/skills/flydocs-local/scripts/get_issue.py +0 -20
- package/template/.claude/skills/flydocs-local/scripts/link.py +0 -41
- package/template/.claude/skills/flydocs-local/scripts/list_issues.py +0 -34
- package/template/.claude/skills/flydocs-local/scripts/priority.py +0 -37
- package/template/.claude/skills/flydocs-local/scripts/project_update.py +0 -67
- package/template/.claude/skills/flydocs-local/scripts/status_summary.py +0 -16
- package/template/.claude/skills/flydocs-local/scripts/transition.py +0 -24
- package/template/.claude/skills/flydocs-local/scripts/update_description.py +0 -35
- package/template/.claude/skills/flydocs-local/scripts/update_issue.py +0 -84
- package/template/.flydocs/hooks/auto-approve.py +0 -71
- package/template/.flydocs/hooks/prompt-submit.py +0 -277
- package/template/.flydocs/scripts/skill_manager.py +0 -541
- package/template/.flydocs/templates/bug.md +0 -166
- package/template/.flydocs/templates/chore.md +0 -110
- package/template/.flydocs/templates/feature.md +0 -173
- package/template/.flydocs/templates/idea.md +0 -122
- /package/template/{.flydocs → .claude}/hooks/post-edit.py +0 -0
- /package/template/.claude/skills/{flydocs-estimates/references → flydocs-workflow/reference}/provider-costs.md +0 -0
- /package/template/.claude/skills/flydocs-workflow/templates/{bug.md → issues/bug.md} +0 -0
- /package/template/.claude/skills/flydocs-workflow/templates/{chore.md → issues/chore.md} +0 -0
- /package/template/.claude/skills/flydocs-workflow/templates/{feature.md → issues/feature.md} +0 -0
- /package/template/.claude/skills/flydocs-workflow/templates/{idea.md → issues/idea.md} +0 -0
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
"""FlyDocs Local
|
|
1
|
+
"""FlyDocs Local Backend — filesystem-based issue management.
|
|
2
|
+
|
|
3
|
+
All local tier operations are implemented here. The unified client
|
|
4
|
+
delegates to this module when tier is "local".
|
|
5
|
+
"""
|
|
2
6
|
|
|
3
7
|
import json
|
|
4
|
-
import os
|
|
5
8
|
import re
|
|
6
9
|
from datetime import datetime
|
|
7
10
|
from pathlib import Path
|
|
8
11
|
|
|
12
|
+
|
|
9
13
|
# Status directories map to workflow states
|
|
10
14
|
STATUSES = {
|
|
11
15
|
"BACKLOG": "backlog",
|
|
@@ -23,33 +27,28 @@ STATUSES = {
|
|
|
23
27
|
ISSUE_TYPES = {"feature", "bug", "chore", "idea"}
|
|
24
28
|
|
|
25
29
|
|
|
26
|
-
def _issues_root() -> Path:
|
|
27
|
-
"""Find flydocs/issues/ relative to
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (parent / ".flydocs" / "config.json").exists():
|
|
32
|
-
root = parent / "flydocs" / "issues"
|
|
33
|
-
root.mkdir(parents=True, exist_ok=True)
|
|
34
|
-
return root
|
|
35
|
-
raise RuntimeError("Not in a FlyDocs project (no .flydocs/config.json found)")
|
|
30
|
+
def _issues_root(project_root: Path) -> Path:
|
|
31
|
+
"""Find flydocs/issues/ relative to project root."""
|
|
32
|
+
root = project_root / "flydocs" / "issues"
|
|
33
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
34
|
+
return root
|
|
36
35
|
|
|
37
36
|
|
|
38
|
-
def _ensure_dirs() -> Path:
|
|
37
|
+
def _ensure_dirs(project_root: Path) -> Path:
|
|
39
38
|
"""Ensure all status directories exist."""
|
|
40
|
-
root = _issues_root()
|
|
39
|
+
root = _issues_root(project_root)
|
|
41
40
|
for dirname in set(STATUSES.values()):
|
|
42
41
|
(root / dirname).mkdir(exist_ok=True)
|
|
43
42
|
return root
|
|
44
43
|
|
|
45
44
|
|
|
46
|
-
def _counter_path() -> Path:
|
|
47
|
-
return
|
|
45
|
+
def _counter_path(project_root: Path) -> Path:
|
|
46
|
+
return project_root / ".flydocs" / "issues.counter"
|
|
48
47
|
|
|
49
48
|
|
|
50
|
-
def _next_id() -> str:
|
|
49
|
+
def _next_id(project_root: Path) -> str:
|
|
51
50
|
"""Auto-increment and return next FD-XXX identifier."""
|
|
52
|
-
path = _counter_path()
|
|
51
|
+
path = _counter_path(project_root)
|
|
53
52
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
54
53
|
current = int(path.read_text().strip()) if path.exists() else 0
|
|
55
54
|
next_num = current + 1
|
|
@@ -76,7 +75,6 @@ def _parse_issue(filepath: Path) -> dict:
|
|
|
76
75
|
if ":" in line:
|
|
77
76
|
key, val = line.split(":", 1)
|
|
78
77
|
val = val.strip()
|
|
79
|
-
# Handle numeric values
|
|
80
78
|
if val.isdigit():
|
|
81
79
|
val = int(val)
|
|
82
80
|
frontmatter[key.strip()] = val
|
|
@@ -96,9 +94,9 @@ def _parse_issue(filepath: Path) -> dict:
|
|
|
96
94
|
return {**frontmatter, "description": description, "comments": comments, "_path": filepath}
|
|
97
95
|
|
|
98
96
|
|
|
99
|
-
def _find_issue(ref: str) -> Path:
|
|
100
|
-
"""Find an issue file by its identifier
|
|
101
|
-
root = _issues_root()
|
|
97
|
+
def _find_issue(project_root: Path, ref: str) -> Path:
|
|
98
|
+
"""Find an issue file by its identifier across all directories."""
|
|
99
|
+
root = _issues_root(project_root)
|
|
102
100
|
prefix = ref.upper() + "-"
|
|
103
101
|
for dirname in set(STATUSES.values()):
|
|
104
102
|
dirpath = root / dirname
|
|
@@ -128,11 +126,14 @@ def _write_issue(filepath: Path, frontmatter: dict, description: str, comments:
|
|
|
128
126
|
filepath.write_text("".join(parts) + "\n")
|
|
129
127
|
|
|
130
128
|
|
|
131
|
-
|
|
129
|
+
# --- Public API (matches relay backend interface) ---
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def create_issue(project_root: Path, title: str, issue_type: str, description: str = "",
|
|
132
133
|
priority: int = 3, estimate: int = 0,
|
|
133
|
-
assignee: str = "", triage: bool = False) -> dict:
|
|
134
|
-
root = _ensure_dirs()
|
|
135
|
-
identifier = _next_id()
|
|
134
|
+
assignee: str = "", triage: bool = False, **_kwargs: object) -> dict:
|
|
135
|
+
root = _ensure_dirs(project_root)
|
|
136
|
+
identifier = _next_id(project_root)
|
|
136
137
|
slug = _slugify(title)
|
|
137
138
|
filename = f"{identifier}-{slug}.md"
|
|
138
139
|
now = datetime.now().strftime("%Y-%m-%d")
|
|
@@ -152,22 +153,22 @@ def create_issue(title: str, issue_type: str, description: str = "",
|
|
|
152
153
|
frontmatter["triage"] = "true"
|
|
153
154
|
|
|
154
155
|
_write_issue(filepath, frontmatter, description or f"## Context\n\n{title}", [])
|
|
155
|
-
return {"id": identifier, "identifier": identifier, "title": title}
|
|
156
|
+
return {"id": identifier, "identifier": identifier, "title": title, "url": ""}
|
|
156
157
|
|
|
157
158
|
|
|
158
|
-
def transition(ref: str,
|
|
159
|
-
filepath = _find_issue(ref)
|
|
159
|
+
def transition(project_root: Path, ref: str, status: str, comment: str) -> dict:
|
|
160
|
+
filepath = _find_issue(project_root, ref)
|
|
160
161
|
data = _parse_issue(filepath)
|
|
161
162
|
prev_status = _status_from_path(filepath)
|
|
162
163
|
|
|
164
|
+
new_status = status.upper()
|
|
163
165
|
if new_status not in STATUSES:
|
|
164
166
|
raise ValueError(f"Invalid status: {new_status}. Valid: {', '.join(STATUSES.keys())}")
|
|
165
167
|
|
|
166
|
-
target_dir = _issues_root() / STATUSES[new_status]
|
|
168
|
+
target_dir = _issues_root(project_root) / STATUSES[new_status]
|
|
167
169
|
target_dir.mkdir(exist_ok=True)
|
|
168
170
|
new_path = target_dir / filepath.name
|
|
169
171
|
|
|
170
|
-
# Append transition comment
|
|
171
172
|
now = datetime.now().strftime("%Y-%m-%d %H:%M")
|
|
172
173
|
data["comments"].append(f"**{new_status}** — {comment}\n_{now}_")
|
|
173
174
|
data["updated"] = datetime.now().strftime("%Y-%m-%d")
|
|
@@ -180,11 +181,11 @@ def transition(ref: str, new_status: str, comment: str) -> dict:
|
|
|
180
181
|
return {"success": True, "issue": ref, "previousStatus": prev_status, "newStatus": new_status}
|
|
181
182
|
|
|
182
183
|
|
|
183
|
-
def add_comment(ref: str,
|
|
184
|
-
filepath = _find_issue(ref)
|
|
184
|
+
def add_comment(project_root: Path, ref: str, body: str) -> dict:
|
|
185
|
+
filepath = _find_issue(project_root, ref)
|
|
185
186
|
data = _parse_issue(filepath)
|
|
186
187
|
now = datetime.now().strftime("%Y-%m-%d %H:%M")
|
|
187
|
-
data["comments"].append(f"{
|
|
188
|
+
data["comments"].append(f"{body}\n_{now}_")
|
|
188
189
|
data["updated"] = datetime.now().strftime("%Y-%m-%d")
|
|
189
190
|
|
|
190
191
|
fm = {k: v for k, v in data.items() if k not in ("description", "comments", "_path")}
|
|
@@ -192,9 +193,10 @@ def add_comment(ref: str, comment: str) -> dict:
|
|
|
192
193
|
return {"success": True, "commentId": len(data["comments"])}
|
|
193
194
|
|
|
194
195
|
|
|
195
|
-
def list_issues(status: str = "", assignee: str = "",
|
|
196
|
-
|
|
197
|
-
|
|
196
|
+
def list_issues(project_root: Path, status: str = "", assignee: str = "",
|
|
197
|
+
limit: int = 50, **_kwargs: object) -> list[dict]:
|
|
198
|
+
root = _issues_root(project_root)
|
|
199
|
+
results: list[dict] = []
|
|
198
200
|
dirs_to_scan = [STATUSES[status]] if status and status in STATUSES else list(set(STATUSES.values()))
|
|
199
201
|
|
|
200
202
|
for dirname in dirs_to_scan:
|
|
@@ -220,8 +222,8 @@ def list_issues(status: str = "", assignee: str = "", limit: int = 50) -> list[d
|
|
|
220
222
|
return results
|
|
221
223
|
|
|
222
224
|
|
|
223
|
-
def get_issue(ref: str) -> dict:
|
|
224
|
-
filepath = _find_issue(ref)
|
|
225
|
+
def get_issue(project_root: Path, ref: str, **_kwargs: object) -> dict:
|
|
226
|
+
filepath = _find_issue(project_root, ref)
|
|
225
227
|
data = _parse_issue(filepath)
|
|
226
228
|
return {
|
|
227
229
|
"id": data.get("id", ""),
|
|
@@ -236,10 +238,13 @@ def get_issue(ref: str) -> dict:
|
|
|
236
238
|
}
|
|
237
239
|
|
|
238
240
|
|
|
239
|
-
def assign_issue(ref: str, assignee: str) -> dict:
|
|
240
|
-
filepath = _find_issue(ref)
|
|
241
|
+
def assign_issue(project_root: Path, ref: str, assignee: str | None) -> dict:
|
|
242
|
+
filepath = _find_issue(project_root, ref)
|
|
241
243
|
data = _parse_issue(filepath)
|
|
242
|
-
|
|
244
|
+
if assignee is None:
|
|
245
|
+
data.pop("assignee", None)
|
|
246
|
+
else:
|
|
247
|
+
data["assignee"] = assignee
|
|
243
248
|
data["updated"] = datetime.now().strftime("%Y-%m-%d")
|
|
244
249
|
|
|
245
250
|
fm = {k: v for k, v in data.items() if k not in ("description", "comments", "_path")}
|
|
@@ -247,8 +252,8 @@ def assign_issue(ref: str, assignee: str) -> dict:
|
|
|
247
252
|
return {"success": True, "issue": ref, "assignee": assignee}
|
|
248
253
|
|
|
249
254
|
|
|
250
|
-
def update_description(ref: str, text: str) -> dict:
|
|
251
|
-
filepath = _find_issue(ref)
|
|
255
|
+
def update_description(project_root: Path, ref: str, text: str) -> dict:
|
|
256
|
+
filepath = _find_issue(project_root, ref)
|
|
252
257
|
data = _parse_issue(filepath)
|
|
253
258
|
data["updated"] = datetime.now().strftime("%Y-%m-%d")
|
|
254
259
|
|
|
@@ -257,9 +262,94 @@ def update_description(ref: str, text: str) -> dict:
|
|
|
257
262
|
return {"success": True, "issue": ref}
|
|
258
263
|
|
|
259
264
|
|
|
260
|
-
def
|
|
261
|
-
|
|
262
|
-
|
|
265
|
+
def update_issue(project_root: Path, ref: str, **fields: object) -> dict:
|
|
266
|
+
"""Bulk update — set multiple fields on an issue."""
|
|
267
|
+
filepath = _find_issue(project_root, ref)
|
|
268
|
+
data = _parse_issue(filepath)
|
|
269
|
+
updated: list[str] = []
|
|
270
|
+
|
|
271
|
+
# Handle state transition separately
|
|
272
|
+
state = fields.get("state")
|
|
273
|
+
comment = fields.get("comment")
|
|
274
|
+
if state and isinstance(state, str):
|
|
275
|
+
result = transition(project_root, ref, state, str(comment or f"Transitioned to {state}"))
|
|
276
|
+
updated.append("state")
|
|
277
|
+
# Re-find the file after transition (it may have moved)
|
|
278
|
+
filepath = _find_issue(project_root, ref)
|
|
279
|
+
data = _parse_issue(filepath)
|
|
280
|
+
|
|
281
|
+
# Handle comment separately (if no state transition already handled it)
|
|
282
|
+
if comment and isinstance(comment, str) and "state" not in updated:
|
|
283
|
+
add_comment(project_root, ref, comment)
|
|
284
|
+
updated.append("comment")
|
|
285
|
+
data = _parse_issue(filepath)
|
|
286
|
+
|
|
287
|
+
# Update simple fields
|
|
288
|
+
for field in ("title", "priority", "estimate", "assignee"):
|
|
289
|
+
val = fields.get(field)
|
|
290
|
+
if val is not None:
|
|
291
|
+
data[field] = val
|
|
292
|
+
updated.append(field)
|
|
293
|
+
|
|
294
|
+
# Update description
|
|
295
|
+
desc = fields.get("description")
|
|
296
|
+
if desc and isinstance(desc, str):
|
|
297
|
+
data["description"] = desc
|
|
298
|
+
updated.append("description")
|
|
299
|
+
|
|
300
|
+
data["updated"] = datetime.now().strftime("%Y-%m-%d")
|
|
301
|
+
fm = {k: v for k, v in data.items() if k not in ("description", "comments", "_path")}
|
|
302
|
+
_write_issue(filepath, fm, data.get("description", ""), data.get("comments", []))
|
|
303
|
+
|
|
304
|
+
return {"success": True, "issue": ref, "updated": updated}
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def estimate_issue(project_root: Path, ref: str, estimate: int) -> dict:
|
|
308
|
+
filepath = _find_issue(project_root, ref)
|
|
309
|
+
data = _parse_issue(filepath)
|
|
310
|
+
data["estimate"] = estimate
|
|
311
|
+
data["updated"] = datetime.now().strftime("%Y-%m-%d")
|
|
312
|
+
|
|
313
|
+
fm = {k: v for k, v in data.items() if k not in ("description", "comments", "_path")}
|
|
314
|
+
_write_issue(filepath, fm, data["description"], data.get("comments", []))
|
|
315
|
+
return {"success": True, "issue": ref, "estimate": estimate}
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def priority_issue(project_root: Path, ref: str, priority: int) -> dict:
|
|
319
|
+
filepath = _find_issue(project_root, ref)
|
|
320
|
+
data = _parse_issue(filepath)
|
|
321
|
+
data["priority"] = priority
|
|
322
|
+
data["updated"] = datetime.now().strftime("%Y-%m-%d")
|
|
323
|
+
|
|
324
|
+
fm = {k: v for k, v in data.items() if k not in ("description", "comments", "_path")}
|
|
325
|
+
_write_issue(filepath, fm, data["description"], data.get("comments", []))
|
|
326
|
+
return {"success": True, "issue": ref, "priority": priority}
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def link_issues(project_root: Path, ref: str, related_ref: str, link_type: str) -> dict:
|
|
330
|
+
add_comment(project_root, ref, f"Linked ({link_type}): {related_ref}")
|
|
331
|
+
add_comment(project_root, related_ref, f"Linked ({link_type}): {ref}")
|
|
332
|
+
return {"success": True, "type": link_type}
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def project_update(project_root: Path, health: str, body: str, **_kwargs: object) -> dict:
|
|
336
|
+
"""Write a project update as a markdown file."""
|
|
337
|
+
updates_dir = project_root / "flydocs" / "updates"
|
|
338
|
+
updates_dir.mkdir(parents=True, exist_ok=True)
|
|
339
|
+
|
|
340
|
+
now = datetime.now()
|
|
341
|
+
timestamp = now.strftime("%Y%m%d-%H%M%S")
|
|
342
|
+
filename = f"{timestamp}.md"
|
|
343
|
+
|
|
344
|
+
content = f"---\nhealth: {health}\ndate: {now.strftime('%Y-%m-%d')}\n---\n\n{body}\n"
|
|
345
|
+
(updates_dir / filename).write_text(content)
|
|
346
|
+
|
|
347
|
+
return {"success": True, "id": timestamp}
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def status_summary(project_root: Path) -> dict:
|
|
351
|
+
root = _issues_root(project_root)
|
|
352
|
+
counts: dict[str, int] = {}
|
|
263
353
|
total = 0
|
|
264
354
|
for status, dirname in STATUSES.items():
|
|
265
355
|
dirpath = root / dirname
|