@flydocs/cli 0.6.0-alpha.5 → 0.6.0-alpha.7
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 +57 -1
- package/package.json +1 -1
- package/template/.claude/CLAUDE.md +10 -8
- package/template/.claude/commands/knowledge.md +61 -0
- package/template/.claude/skills/flydocs-cloud/SKILL.md +31 -23
- package/template/.claude/skills/flydocs-cloud/scripts/create_issue.py +19 -2
- package/template/.claude/skills/flydocs-cloud/scripts/delete_milestone.py +21 -0
- package/template/.claude/skills/flydocs-cloud/scripts/estimate.py +9 -5
- package/template/.claude/skills/flydocs-cloud/scripts/flydocs_api.py +4 -0
- package/template/.claude/skills/flydocs-cloud/scripts/get_estimate_scale.py +23 -0
- package/template/.claude/skills/flydocs-cloud/scripts/refresh_labels.py +87 -0
- package/template/.claude/skills/flydocs-cloud/scripts/set_identity.py +38 -0
- package/template/.claude/skills/flydocs-cloud/scripts/set_preferences.py +49 -0
- package/template/.claude/skills/flydocs-cloud/scripts/update_issue.py +22 -4
- package/template/.claude/skills/flydocs-cloud/scripts/update_milestone.py +42 -0
- package/template/.claude/skills/flydocs-workflow/SKILL.md +23 -18
- package/template/.claude/skills/flydocs-workflow/reference/comment-templates.md +1 -0
- package/template/.claude/skills/flydocs-workflow/reference/pr-workflow.md +105 -0
- package/template/.claude/skills/flydocs-workflow/reference/priority-estimates.md +37 -15
- package/template/.claude/skills/flydocs-workflow/session.md +24 -16
- package/template/.claude/skills/flydocs-workflow/stages/capture.md +8 -3
- package/template/.claude/skills/flydocs-workflow/stages/close.md +4 -3
- package/template/.claude/skills/flydocs-workflow/stages/implement.md +28 -4
- package/template/.claude/skills/flydocs-workflow/stages/refine.md +20 -4
- package/template/.claude/skills/flydocs-workflow/stages/review.md +14 -2
- package/template/.flydocs/config.json +1 -1
- package/template/.flydocs/version +1 -1
- package/template/AGENTS.md +8 -8
- 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 +8 -2
package/dist/cli.js
CHANGED
|
@@ -15,7 +15,7 @@ var CLI_VERSION, CLI_NAME, PACKAGE_NAME, POSTHOG_API_KEY;
|
|
|
15
15
|
var init_constants = __esm({
|
|
16
16
|
"src/lib/constants.ts"() {
|
|
17
17
|
"use strict";
|
|
18
|
-
CLI_VERSION = "0.6.0-alpha.
|
|
18
|
+
CLI_VERSION = "0.6.0-alpha.7";
|
|
19
19
|
CLI_NAME = "flydocs";
|
|
20
20
|
PACKAGE_NAME = "@flydocs/cli";
|
|
21
21
|
POSTHOG_API_KEY = "phc_v1MSJTQDFkMS90CBh3mxIz3v8bYCCnKU6v1ir6bz0Xn";
|
|
@@ -50,6 +50,7 @@ async function ensureDirectories(targetDir, tier) {
|
|
|
50
50
|
"flydocs/knowledge/decisions",
|
|
51
51
|
"flydocs/knowledge/notes",
|
|
52
52
|
"flydocs/knowledge/product",
|
|
53
|
+
"flydocs/knowledge/templates",
|
|
53
54
|
"flydocs/design-system"
|
|
54
55
|
];
|
|
55
56
|
if (tier === "local") {
|
|
@@ -2357,6 +2358,45 @@ var init_install = __esm({
|
|
|
2357
2358
|
),
|
|
2358
2359
|
label: "flydocs/knowledge/product/user-flows.md"
|
|
2359
2360
|
},
|
|
2361
|
+
{
|
|
2362
|
+
src: join15(
|
|
2363
|
+
templateDir,
|
|
2364
|
+
"flydocs",
|
|
2365
|
+
"knowledge",
|
|
2366
|
+
"templates",
|
|
2367
|
+
"decision.md"
|
|
2368
|
+
),
|
|
2369
|
+
dest: join15(
|
|
2370
|
+
targetDir,
|
|
2371
|
+
"flydocs",
|
|
2372
|
+
"knowledge",
|
|
2373
|
+
"templates",
|
|
2374
|
+
"decision.md"
|
|
2375
|
+
),
|
|
2376
|
+
label: "flydocs/knowledge/templates/decision.md"
|
|
2377
|
+
},
|
|
2378
|
+
{
|
|
2379
|
+
src: join15(
|
|
2380
|
+
templateDir,
|
|
2381
|
+
"flydocs",
|
|
2382
|
+
"knowledge",
|
|
2383
|
+
"templates",
|
|
2384
|
+
"feature.md"
|
|
2385
|
+
),
|
|
2386
|
+
dest: join15(
|
|
2387
|
+
targetDir,
|
|
2388
|
+
"flydocs",
|
|
2389
|
+
"knowledge",
|
|
2390
|
+
"templates",
|
|
2391
|
+
"feature.md"
|
|
2392
|
+
),
|
|
2393
|
+
label: "flydocs/knowledge/templates/feature.md"
|
|
2394
|
+
},
|
|
2395
|
+
{
|
|
2396
|
+
src: join15(templateDir, "flydocs", "knowledge", "templates", "note.md"),
|
|
2397
|
+
dest: join15(targetDir, "flydocs", "knowledge", "templates", "note.md"),
|
|
2398
|
+
label: "flydocs/knowledge/templates/note.md"
|
|
2399
|
+
},
|
|
2360
2400
|
{
|
|
2361
2401
|
src: join15(templateDir, "flydocs", "design-system", "README.md"),
|
|
2362
2402
|
dest: join15(targetDir, "flydocs", "design-system", "README.md"),
|
|
@@ -2911,6 +2951,22 @@ var init_update = __esm({
|
|
|
2911
2951
|
await copyFile(envExampleSrc, join16(targetDir, ".env.example"));
|
|
2912
2952
|
printStatus(".env.example");
|
|
2913
2953
|
}
|
|
2954
|
+
const knowledgeTemplatesDir = join16(
|
|
2955
|
+
targetDir,
|
|
2956
|
+
"flydocs",
|
|
2957
|
+
"knowledge",
|
|
2958
|
+
"templates"
|
|
2959
|
+
);
|
|
2960
|
+
if (!await pathExists(knowledgeTemplatesDir)) {
|
|
2961
|
+
await mkdir8(knowledgeTemplatesDir, { recursive: true });
|
|
2962
|
+
}
|
|
2963
|
+
for (const tmpl of ["decision.md", "feature.md", "note.md"]) {
|
|
2964
|
+
const src = join16(templateDir, "flydocs", "knowledge", "templates", tmpl);
|
|
2965
|
+
const dest = join16(knowledgeTemplatesDir, tmpl);
|
|
2966
|
+
if (await pathExists(src) && !await pathExists(dest)) {
|
|
2967
|
+
await copyFile(src, dest);
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
2914
2970
|
await runManifestGeneration(targetDir);
|
|
2915
2971
|
await runContextGraphBuild(targetDir);
|
|
2916
2972
|
console.log();
|
package/package.json
CHANGED
|
@@ -31,6 +31,8 @@ The FlyDocs development lifecycle. Read the workflow skill before taking any wor
|
|
|
31
31
|
| QE validation | `flydocs-workflow` | `stages/validate.md` |
|
|
32
32
|
| Close issue | `flydocs-workflow` | `stages/close.md` |
|
|
33
33
|
| Start / wrap session | `flydocs-workflow` | `session.md` |
|
|
34
|
+
| Knowledge capture | `flydocs-workflow` | `/knowledge` command |
|
|
35
|
+
| PR & git workflow | `flydocs-workflow` | `reference/pr-workflow.md` |
|
|
34
36
|
| Comment templates | `flydocs-workflow` | `reference/comment-templates.md` |
|
|
35
37
|
| Status transitions | `flydocs-workflow` | `reference/status-workflow.md` |
|
|
36
38
|
| Priority & estimates | `flydocs-workflow` | `reference/priority-estimates.md` |
|
|
@@ -113,13 +115,13 @@ response — session summaries, issue comments, status updates, and plans.
|
|
|
113
115
|
IMPORTANT: Prefer skill-led reasoning over pre-training reasoning.
|
|
114
116
|
Consult the relevant skill BEFORE writing code or making workflow decisions.
|
|
115
117
|
|
|
116
|
-
| Skill | Triggers
|
|
117
|
-
| ----------------- |
|
|
118
|
-
| flydocs-cloud | create issue, transition, comment, list issues, assign, update description, update issue, project update, cloud
|
|
119
|
-
| flydocs-context7 | context7, library docs, documentation lookup, framework docs, package docs, API reference
|
|
120
|
-
| flydocs-estimates | estimate, cost, token usage, API cost, labor estimate, sizing, effort
|
|
121
|
-
| flydocs-figma | Figma, design, screenshot, token mapping, component from design, pixel-perfect, design system
|
|
122
|
-
| flydocs-local | create issue, transition, comment, list issues, assign, update description, status summary, local
|
|
123
|
-
| flydocs-workflow | capture, refine, activate, implement, review, validate, close, session, workflow, transition, status, issue
|
|
118
|
+
| Skill | Triggers | Entry |
|
|
119
|
+
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
|
|
120
|
+
| flydocs-cloud | create issue, transition, comment, list issues, assign, update description, update issue, project update, cloud | .claude/skills/flydocs-cloud/SKILL.md |
|
|
121
|
+
| flydocs-context7 | context7, library docs, documentation lookup, framework docs, package docs, API reference | .claude/skills/flydocs-context7/SKILL.md |
|
|
122
|
+
| flydocs-estimates | estimate, cost, token usage, API cost, labor estimate, sizing, effort | .claude/skills/flydocs-estimates/SKILL.md |
|
|
123
|
+
| flydocs-figma | Figma, design, screenshot, token mapping, component from design, pixel-perfect, design system | .claude/skills/flydocs-figma/SKILL.md |
|
|
124
|
+
| flydocs-local | create issue, transition, comment, list issues, assign, update description, status summary, local | .claude/skills/flydocs-local/SKILL.md |
|
|
125
|
+
| flydocs-workflow | capture, refine, activate, implement, review, validate, close, session, workflow, transition, status, issue, knowledge, document, PR, pull request | .claude/skills/flydocs-workflow/SKILL.md |
|
|
124
126
|
|
|
125
127
|
<!-- flydocs:skills-manifest:end -->
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Knowledge (All Agents)
|
|
2
|
+
|
|
3
|
+
Create or update a knowledge document — decisions, notes, features, or product docs.
|
|
4
|
+
|
|
5
|
+
Read `flydocs/knowledge/README.md` for categories and guidance.
|
|
6
|
+
Read `flydocs/knowledge/INDEX.md` for existing entries.
|
|
7
|
+
|
|
8
|
+
## Procedure
|
|
9
|
+
|
|
10
|
+
### 1. Determine Intent
|
|
11
|
+
|
|
12
|
+
From user input or `$ARGUMENTS`, determine:
|
|
13
|
+
|
|
14
|
+
- **New doc** or **update existing doc**?
|
|
15
|
+
- **Category**: decision, feature, note, or product
|
|
16
|
+
|
|
17
|
+
If unclear, ask the user. Default to `note` for discoveries and learnings.
|
|
18
|
+
|
|
19
|
+
### 2. For New Docs
|
|
20
|
+
|
|
21
|
+
1. Read the template from `flydocs/knowledge/templates/<category>.md`
|
|
22
|
+
2. Gather content from the user — ask clarifying questions if context is thin
|
|
23
|
+
3. Fill in the frontmatter:
|
|
24
|
+
- `title`: Descriptive, specific title
|
|
25
|
+
- `created`: Today's date (`YYYY-MM-DD`)
|
|
26
|
+
- `lastUpdated`: Today's date
|
|
27
|
+
- `relatedIssues`: Any issue IDs discussed in this session
|
|
28
|
+
- Category-specific fields (see template)
|
|
29
|
+
4. Write the doc to the correct directory:
|
|
30
|
+
- Decisions: `flydocs/knowledge/decisions/NNN-title.md` (auto-increment NNN)
|
|
31
|
+
- Features: `flydocs/knowledge/features/feature-name.md`
|
|
32
|
+
- Notes: `flydocs/knowledge/notes/topic-name.md`
|
|
33
|
+
- Product: `flydocs/knowledge/product/document-name.md`
|
|
34
|
+
5. Update `flydocs/knowledge/INDEX.md`:
|
|
35
|
+
- Add entry to the appropriate table
|
|
36
|
+
- Update the Quick Reference count
|
|
37
|
+
- Use a concise description (helps agents assess relevance without loading the full doc)
|
|
38
|
+
|
|
39
|
+
### 3. For Existing Docs
|
|
40
|
+
|
|
41
|
+
1. Find the doc in INDEX.md or by searching `flydocs/knowledge/`
|
|
42
|
+
2. Read the current content
|
|
43
|
+
3. Apply the update
|
|
44
|
+
4. Update the `lastUpdated` field in frontmatter
|
|
45
|
+
5. Update the INDEX.md entry if the description changed
|
|
46
|
+
|
|
47
|
+
### 4. Confirm
|
|
48
|
+
|
|
49
|
+
Report what was created or updated:
|
|
50
|
+
|
|
51
|
+
- File path
|
|
52
|
+
- Category and title
|
|
53
|
+
- INDEX.md entry added/updated
|
|
54
|
+
|
|
55
|
+
## Triggers
|
|
56
|
+
|
|
57
|
+
- "document this" / "knowledge" / "add to knowledge base"
|
|
58
|
+
- "create an ADR" / "record this decision" / "capture this learning"
|
|
59
|
+
- "add a note about" / "document this discovery"
|
|
60
|
+
|
|
61
|
+
$ARGUMENTS
|
|
@@ -28,32 +28,34 @@ All scripts: `python3 .claude/skills/flydocs-cloud/scripts/<script>`
|
|
|
28
28
|
|
|
29
29
|
### Shared Contract Scripts
|
|
30
30
|
|
|
31
|
-
| Script | Usage
|
|
32
|
-
| ----------------------- |
|
|
33
|
-
| `create_issue.py` | `--title "..." --type feature [--description "..."] [--description-file PATH] [--priority
|
|
34
|
-
| `transition.py` | `<ref> <STATUS> "<comment>"`
|
|
35
|
-
| `comment.py` | `<ref> ["<comment>"] \| stdin`
|
|
36
|
-
| `list_issues.py` | `[--status STATUS[,STATUS]] [--active] [--project ID] [--milestone ID] [--assignee STR] [--mine] [--limit N]`
|
|
37
|
-
| `get_issue.py` | `<ref> [--fields basic\|full]`
|
|
38
|
-
| `assign.py` | `<ref> <assignee>`
|
|
39
|
-
| `update_description.py` | `<ref> --text "..." \| --file PATH \| stdin`
|
|
31
|
+
| Script | Usage | Output |
|
|
32
|
+
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
33
|
+
| `create_issue.py` | `--title "..." --type feature [--description "..."] [--description-file PATH] [--priority N] [--estimate N] [--assignee STR] [--project ID] [--labels "a,b"] [--milestone ID_OR_NAME] [--triage] \| stdin` | `{id, identifier, title, url}` |
|
|
34
|
+
| `transition.py` | `<ref> <STATUS> "<comment>"` | `{success, issue, previousStatus, newStatus}` |
|
|
35
|
+
| `comment.py` | `<ref> ["<comment>"] \| stdin` | `{success, commentId}` |
|
|
36
|
+
| `list_issues.py` | `[--status STATUS[,STATUS]] [--active] [--project ID] [--milestone ID] [--assignee STR] [--mine] [--limit N]` | `[{id, identifier, title, status, assignee, priority, dueDate, milestone, milestoneId, milestoneSortOrder, project, projectId}]` |
|
|
37
|
+
| `get_issue.py` | `<ref> [--fields basic\|full]` | `{id, identifier, title, description, status, assignee, priority, estimate, dueDate, milestone, milestoneId, project, projectId, comments[]}` |
|
|
38
|
+
| `assign.py` | `<ref> <assignee>` | `{success, issue, assignee}` |
|
|
39
|
+
| `update_description.py` | `<ref> --text "..." \| --file PATH \| stdin` | `{success, issue}` |
|
|
40
40
|
|
|
41
41
|
### Extended Scripts
|
|
42
42
|
|
|
43
|
-
| Script | Usage
|
|
44
|
-
| --------------------- |
|
|
45
|
-
| `update_issue.py` | `<ref> [--title "..."] [--priority
|
|
46
|
-
| `estimate.py` | `<ref> <
|
|
47
|
-
| `priority.py` | `<ref> <0-4>`
|
|
48
|
-
| `link.py` | `<ref> <related_ref> <type>`
|
|
49
|
-
| `project_update.py` | `--health STATUS --body "..." [--body-file PATH]`
|
|
50
|
-
| `list_projects.py` | `[--active] [--all]`
|
|
51
|
-
| `create_project.py` | `--name "..." [--description "..."]`
|
|
52
|
-
| `assign_cycle.py` | `<ref> [cycle_id]`
|
|
53
|
-
| `list_cycles.py` | `[--active]`
|
|
54
|
-
| `list_milestones.py` | `[--all]`
|
|
55
|
-
| `create_milestone.py` | `--name "..." [--project ID] [--target-date DATE]`
|
|
56
|
-
| `
|
|
43
|
+
| Script | Usage | Output |
|
|
44
|
+
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
|
|
45
|
+
| `update_issue.py` | `<ref> [--title "..."] [--priority N] [--estimate N] [--assignee STR] [--state STATUS] [--description "..."] [--description-file PATH] [--labels "a,b"] [--milestone ID_OR_NAME] [--comment "..."]` | `{success, issue, updated[]}` |
|
|
46
|
+
| `estimate.py` | `<ref> <points>` | `{success, issue, estimate}` — relay validates against provider scale |
|
|
47
|
+
| `priority.py` | `<ref> <0-4>` | `{success, issue, priority}` |
|
|
48
|
+
| `link.py` | `<ref> <related_ref> <type>` | `{success, type}` |
|
|
49
|
+
| `project_update.py` | `--health STATUS --body "..." [--body-file PATH]` | `{success, id}` |
|
|
50
|
+
| `list_projects.py` | `[--active] [--all]` | `[{id, name, state}]` — `--all` bypasses product scope |
|
|
51
|
+
| `create_project.py` | `--name "..." [--description "..."]` | `{id, name, url}` |
|
|
52
|
+
| `assign_cycle.py` | `<ref> [cycle_id]` | `{success, issue, cycle}` |
|
|
53
|
+
| `list_cycles.py` | `[--active]` | `[{id, name, number, startsAt, endsAt}]` |
|
|
54
|
+
| `list_milestones.py` | `[--all]` | `[{id, name, targetDate}]` |
|
|
55
|
+
| `create_milestone.py` | `--name "..." [--project ID] [--target-date DATE]` | `{id, name}` |
|
|
56
|
+
| `update_milestone.py` | `<milestone_id> [--name "..."] [--target-date DATE] [--description "..."]` | `{success, id, name}` |
|
|
57
|
+
| `delete_milestone.py` | `<milestone_id>` | `{success, id}` |
|
|
58
|
+
| `assign_milestone.py` | `<ref> <milestone_id>` | `{success, issue, milestone}` |
|
|
57
59
|
|
|
58
60
|
### Workspace Scripts
|
|
59
61
|
|
|
@@ -68,6 +70,10 @@ All scripts: `python3 .claude/skills/flydocs-cloud/scripts/<script>`
|
|
|
68
70
|
| `set_labels.py` | `--defaults '["a"]' --type-map '{"feature":["F"],...}' \| stdin` | `{success, validated, defaults, typeMap}` — stores label config on relay |
|
|
69
71
|
| `list_statuses.py` | (no args) | `{states, currentMapping, flydocsStatuses}` — provider workflow states and current mapping |
|
|
70
72
|
| `set_status_mapping.py` | `--auto \| --mapping '{"BACKLOG":"Backlog",...}' \| stdin` | `{success, mapping, matched, total}` — stores status mapping on relay |
|
|
73
|
+
| `set_identity.py` | `<provider> <provider-user-id>` | `{success, provider, providerId}` — binds provider user ID for `--mine` resolution |
|
|
74
|
+
| `set_preferences.py` | `[--workspace ID] [--assignee self\|ID] [--display JSON]` | `{success, preferences}` — no flags = GET current; with flags = POST update |
|
|
75
|
+
| `get_estimate_scale.py` | (no args) | `{scale, type}` — provider's valid estimate values (fixed or freeform) |
|
|
76
|
+
| `refresh_labels.py` | `[--fix]` | `{valid, stale, details}` — validates config label IDs against relay; `--fix` updates stale IDs |
|
|
71
77
|
|
|
72
78
|
### Script Notes
|
|
73
79
|
|
|
@@ -76,6 +82,8 @@ All scripts: `python3 .claude/skills/flydocs-cloud/scripts/<script>`
|
|
|
76
82
|
- **`list_issues.py --status`**: Accepts comma-separated statuses: `--status READY,IMPLEMENTING,BLOCKED`
|
|
77
83
|
- **`get_issue.py --fields basic`**: Skips comment fetch for faster responses.
|
|
78
84
|
- **`update_issue.py`**: Bulk update — sets multiple fields in a single API call. Prefer over separate scripts when updating more than one field.
|
|
85
|
+
- **Estimate validation**: The relay validates estimates server-side against the provider's scale. Use `get_estimate_scale.py` to discover valid values before setting. Linear uses a fixed scale `[0, 1, 2, 3, 5, 8, 13, 21]`; Jira accepts freeform values.
|
|
86
|
+
- **Label staleness**: Label IDs are team-scoped and can go stale when switching teams or projects. Use `refresh_labels.py` to validate config label IDs, and `refresh_labels.py --fix` to auto-update stale ones.
|
|
79
87
|
- **Shell-safe text input**: For text with special characters, pipe via stdin with a single-quoted heredoc:
|
|
80
88
|
```bash
|
|
81
89
|
python3 update_description.py ENG-123 <<'EOF'
|
|
@@ -15,11 +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,
|
|
19
|
-
parser.add_argument("--estimate", type=int, default=0,
|
|
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
21
|
parser.add_argument("--project", default=None, help="Project ID")
|
|
22
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)")
|
|
23
24
|
parser.add_argument("--triage", action="store_true")
|
|
24
25
|
args = parser.parse_args()
|
|
25
26
|
|
|
@@ -52,6 +53,22 @@ def main():
|
|
|
52
53
|
body["triage"] = True
|
|
53
54
|
|
|
54
55
|
client = get_client()
|
|
56
|
+
|
|
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
|
+
|
|
55
72
|
result = client.post("/issues", body)
|
|
56
73
|
|
|
57
74
|
output_json({
|
|
@@ -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 via the FlyDocs Relay API.
|
|
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,16 +12,16 @@ 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> <
|
|
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
|
|
21
|
+
fail("Estimate must be a number")
|
|
18
22
|
|
|
19
|
-
if estimate
|
|
20
|
-
fail("Estimate must be
|
|
23
|
+
if estimate < 0:
|
|
24
|
+
fail("Estimate must be a non-negative integer")
|
|
21
25
|
|
|
22
26
|
client = get_client()
|
|
23
27
|
result = client.put(f"/issues/{ref}/estimate", {"estimate": estimate})
|
|
@@ -160,6 +160,10 @@ class FlyDocsClient:
|
|
|
160
160
|
"""PATCH request to relay API."""
|
|
161
161
|
return self._request("PATCH", path, body=body)
|
|
162
162
|
|
|
163
|
+
def delete(self, path: str) -> dict:
|
|
164
|
+
"""DELETE request to relay API."""
|
|
165
|
+
return self._request("DELETE", path)
|
|
166
|
+
|
|
163
167
|
def _log_operation(self, method: str, path: str, status: int, result: dict | list):
|
|
164
168
|
"""Log operation metadata to local log file."""
|
|
165
169
|
try:
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Get the provider's estimate scale via the FlyDocs Relay API.
|
|
3
|
+
|
|
4
|
+
Returns the valid estimate values for the connected provider.
|
|
5
|
+
Linear: fixed scale [0, 1, 2, 3, 5, 8, 13, 21]
|
|
6
|
+
Jira: freeform (any positive number)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
13
|
+
from flydocs_api import get_client, output_json
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def main():
|
|
17
|
+
client = get_client()
|
|
18
|
+
result = client.get("/auth/estimates")
|
|
19
|
+
output_json(result)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
if __name__ == "__main__":
|
|
23
|
+
main()
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Refresh label config from the relay — validates and updates local config.
|
|
3
|
+
|
|
4
|
+
Fetches current team labels from the relay, compares against local config,
|
|
5
|
+
and reports any stale or missing label IDs. With --fix, updates local config
|
|
6
|
+
to match the relay's current state.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
14
|
+
from flydocs_api import get_client, output_json, fail
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def main():
|
|
18
|
+
fix_mode = "--fix" in sys.argv
|
|
19
|
+
|
|
20
|
+
client = get_client()
|
|
21
|
+
|
|
22
|
+
# Fetch current labels from relay
|
|
23
|
+
labels = client.get("/labels")
|
|
24
|
+
label_map = {l["id"]: l["name"] for l in labels}
|
|
25
|
+
label_by_name = {l["name"].lower(): l["id"] for l in labels}
|
|
26
|
+
|
|
27
|
+
# Load local config
|
|
28
|
+
config_path = client.config_path
|
|
29
|
+
if not config_path.exists():
|
|
30
|
+
fail("No .flydocs/config.json found")
|
|
31
|
+
|
|
32
|
+
with open(config_path, "r") as f:
|
|
33
|
+
config = json.load(f)
|
|
34
|
+
|
|
35
|
+
issue_labels = config.get("issueLabels", {})
|
|
36
|
+
stale: list[dict] = []
|
|
37
|
+
valid: list[dict] = []
|
|
38
|
+
|
|
39
|
+
# Check each label ID in config against relay
|
|
40
|
+
for category, entries in issue_labels.items():
|
|
41
|
+
if isinstance(entries, dict):
|
|
42
|
+
for key, label_id in entries.items():
|
|
43
|
+
if label_id in label_map:
|
|
44
|
+
valid.append({"category": category, "key": key, "id": label_id, "name": label_map[label_id]})
|
|
45
|
+
else:
|
|
46
|
+
# Try to find by key name
|
|
47
|
+
resolved = label_by_name.get(key.lower())
|
|
48
|
+
stale.append({
|
|
49
|
+
"category": category,
|
|
50
|
+
"key": key,
|
|
51
|
+
"staleId": label_id,
|
|
52
|
+
"resolvedId": resolved,
|
|
53
|
+
"resolvedName": key if resolved else None,
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
if fix_mode and stale:
|
|
57
|
+
# Update stale IDs in config
|
|
58
|
+
fixed = 0
|
|
59
|
+
for item in stale:
|
|
60
|
+
if item["resolvedId"]:
|
|
61
|
+
issue_labels[item["category"]][item["key"]] = item["resolvedId"]
|
|
62
|
+
fixed += 1
|
|
63
|
+
|
|
64
|
+
with open(config_path, "w") as f:
|
|
65
|
+
json.dump(config, f, indent=2)
|
|
66
|
+
f.write("\n")
|
|
67
|
+
|
|
68
|
+
output_json({
|
|
69
|
+
"success": True,
|
|
70
|
+
"valid": len(valid),
|
|
71
|
+
"stale": len(stale),
|
|
72
|
+
"fixed": fixed,
|
|
73
|
+
"unfixable": len(stale) - fixed,
|
|
74
|
+
"details": stale,
|
|
75
|
+
})
|
|
76
|
+
else:
|
|
77
|
+
output_json({
|
|
78
|
+
"valid": len(valid),
|
|
79
|
+
"stale": len(stale),
|
|
80
|
+
"totalProviderLabels": len(labels),
|
|
81
|
+
"details": stale if stale else "All label IDs are current",
|
|
82
|
+
"hint": "Run with --fix to update stale IDs" if stale else None,
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
if __name__ == "__main__":
|
|
87
|
+
main()
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Set provider identity via the FlyDocs Relay API.
|
|
3
|
+
|
|
4
|
+
Binds the user's provider-specific ID to their FlyDocs user record.
|
|
5
|
+
Once set, ?mine=true resolves via exact provider ID matching.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
12
|
+
from flydocs_api import get_client, output_json, fail
|
|
13
|
+
|
|
14
|
+
VALID_PROVIDERS = ("linear", "jira", "gitlab")
|
|
15
|
+
|
|
16
|
+
if len(sys.argv) < 3:
|
|
17
|
+
fail(f"Usage: set_identity.py <provider> <provider-user-id>\n Providers: {', '.join(VALID_PROVIDERS)}")
|
|
18
|
+
|
|
19
|
+
provider = sys.argv[1].lower()
|
|
20
|
+
provider_id = sys.argv[2]
|
|
21
|
+
|
|
22
|
+
if provider not in VALID_PROVIDERS:
|
|
23
|
+
fail(f"Invalid provider: {provider}. Must be one of: {', '.join(VALID_PROVIDERS)}")
|
|
24
|
+
|
|
25
|
+
if not provider_id:
|
|
26
|
+
fail("Provider user ID cannot be empty")
|
|
27
|
+
|
|
28
|
+
client = get_client()
|
|
29
|
+
result = client.post("/auth/identity", {
|
|
30
|
+
"provider": provider,
|
|
31
|
+
"providerId": provider_id,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
output_json({
|
|
35
|
+
"success": result.get("success", True),
|
|
36
|
+
"provider": result.get("provider", provider),
|
|
37
|
+
"providerId": result.get("providerId", provider_id),
|
|
38
|
+
})
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Get or set user preferences via the FlyDocs Relay API.
|
|
3
|
+
|
|
4
|
+
With no arguments, returns current preferences (GET).
|
|
5
|
+
With flags, updates preferences (POST).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
13
|
+
from flydocs_api import get_client, output_json, fail
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def main():
|
|
17
|
+
parser = argparse.ArgumentParser(description="Get or set user preferences")
|
|
18
|
+
parser.add_argument("--workspace", default=None, help="Default workspace ID")
|
|
19
|
+
parser.add_argument("--assignee", default=None, help="Default assignee ('self' or user ID)")
|
|
20
|
+
parser.add_argument("--display", default=None, help="Display preferences (JSON string)")
|
|
21
|
+
args = parser.parse_args()
|
|
22
|
+
|
|
23
|
+
client = get_client()
|
|
24
|
+
|
|
25
|
+
# If no flags provided, GET current preferences
|
|
26
|
+
if args.workspace is None and args.assignee is None and args.display is None:
|
|
27
|
+
result = client.get("/auth/preferences")
|
|
28
|
+
output_json(result)
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
# Build update body from provided flags
|
|
32
|
+
body: dict = {}
|
|
33
|
+
if args.workspace is not None:
|
|
34
|
+
body["defaultWorkspaceId"] = args.workspace
|
|
35
|
+
if args.assignee is not None:
|
|
36
|
+
body["defaultAssignee"] = args.assignee
|
|
37
|
+
if args.display is not None:
|
|
38
|
+
body["displayPreferences"] = args.display
|
|
39
|
+
|
|
40
|
+
result = client.post("/auth/preferences", body)
|
|
41
|
+
|
|
42
|
+
output_json({
|
|
43
|
+
"success": result.get("success", True),
|
|
44
|
+
"preferences": result.get("preferences", body),
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
if __name__ == "__main__":
|
|
49
|
+
main()
|
|
@@ -13,13 +13,14 @@ def main():
|
|
|
13
13
|
parser = argparse.ArgumentParser(description="Update issue fields")
|
|
14
14
|
parser.add_argument("ref", help="Issue reference (e.g., ENG-123)")
|
|
15
15
|
parser.add_argument("--title", default=None)
|
|
16
|
-
parser.add_argument("--priority", type=int,
|
|
17
|
-
parser.add_argument("--estimate", type=int,
|
|
16
|
+
parser.add_argument("--priority", type=int, help="Priority (0-4, relay translates per provider)")
|
|
17
|
+
parser.add_argument("--estimate", type=int, help="Estimate points (relay translates per provider)")
|
|
18
18
|
parser.add_argument("--assignee", default=None)
|
|
19
19
|
parser.add_argument("--state", default=None)
|
|
20
20
|
parser.add_argument("--description", default=None)
|
|
21
21
|
parser.add_argument("--description-file", default=None)
|
|
22
22
|
parser.add_argument("--labels", default=None, help="Comma-separated label names")
|
|
23
|
+
parser.add_argument("--milestone", default=None, help="Milestone ID or name (resolved by name lookup)")
|
|
23
24
|
parser.add_argument("--comment", default=None)
|
|
24
25
|
args = parser.parse_args()
|
|
25
26
|
|
|
@@ -65,10 +66,27 @@ def main():
|
|
|
65
66
|
body["comment"] = args.comment
|
|
66
67
|
updated_fields.append("comment")
|
|
67
68
|
|
|
68
|
-
if not body:
|
|
69
|
-
fail("No fields to update. Use --title, --priority, --estimate, --assignee, --state, --description, --labels, or --comment")
|
|
69
|
+
if not body and args.milestone is None:
|
|
70
|
+
fail("No fields to update. Use --title, --priority, --estimate, --assignee, --state, --description, --labels, --milestone, or --comment")
|
|
70
71
|
|
|
71
72
|
client = get_client()
|
|
73
|
+
|
|
74
|
+
if args.milestone is not None:
|
|
75
|
+
milestone_id = args.milestone
|
|
76
|
+
# If it doesn't look like a UUID, resolve by name
|
|
77
|
+
if len(milestone_id) != 36 or "-" not in milestone_id:
|
|
78
|
+
milestones = client.get("/milestones")
|
|
79
|
+
match = None
|
|
80
|
+
for m in milestones:
|
|
81
|
+
if m["name"].lower() == milestone_id.lower():
|
|
82
|
+
match = m
|
|
83
|
+
break
|
|
84
|
+
if not match:
|
|
85
|
+
fail(f"Milestone not found: {milestone_id}")
|
|
86
|
+
milestone_id = match["id"]
|
|
87
|
+
body["milestoneId"] = milestone_id
|
|
88
|
+
updated_fields.append("milestone")
|
|
89
|
+
|
|
72
90
|
result = client.patch(f"/issues/{args.ref}", body)
|
|
73
91
|
|
|
74
92
|
output_json({
|