@floh-solutions/pharos-cli 0.23.0 → 0.24.0

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/skill/SKILL.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: pharos
3
- description: "Use for AZURE DEVOPS work specifically — when the user says Azure DevOps, ADO or dev.azure.com, or names an ADO work item by number (\"pick up 4821\", \"what's on 210\"). Covers reading or updating an ADO work item, ticket, bug, story or epic; the ADO board, backlog, sprint or iteration; what is assigned to you in Azure DevOps; reading or writing an ADO project wiki page and its comments; linking a plan to an epic; attaching a file to a work item or taking one off; putting an inline image into a description, a comment, a wiki page or a wiki comment; @mentioning somebody so they are actually notified; importing Markdown, Word or PDF files as wiki pages; and whether a failed Azure DevOps call is worth retrying. NOT for other trackers the Argus board, GitHub issues, Jira, Linear where \"task\", \"todo\" and \"board\" mean something else entirely."
3
+ description: "Use for AZURE DEVOPS work — when the user says Azure DevOps, ADO or dev.azure.com, or names an ADO work item by number (\"pick up 4821\", \"what's on 210\"). Covers reading or updating an ADO work item, ticket, bug, story or epic; the ADO board, backlog, sprint or iteration; what is assigned to you in Azure DevOps; reading or writing an ADO project wiki page and its comments; linking a plan to an epic; attaching a file to a work item or taking one off; inline images in a description, comment or wiki page; @mentioning somebody so they are actually notified; importing Markdown, Word or PDF files as wiki pages; and whether a failed ADO call is worth retrying. ALSO the GITHUB ISSUE work item EDGE: adopting an issue as a work item linked at both ends, bulk-adopting a repo, one reply reaching the reporter and the board at once, where they have drifted, and closing both ends together. NOT a GitHub CLI: listing, viewing or commenting on an issue is `gh`'s job. NOT for other trackers — the Argus board, Jira or Linear."
4
4
  license: Proprietary
5
5
  ---
6
6
 
@@ -61,19 +61,39 @@ wiki import <file...> .md .txt .docx .pdf .rtf .html -> pages. A
61
61
  --under <path> --as <name>
62
62
  comment list | add | edit | delete <target> is a work item id OR a wiki path
63
63
  comment react | unreact | reactors like dislike heart hooray smile confused
64
+ issue adopt <owner/name#45> a GitHub ISSUE becomes a work item, with BOTH
65
+ ends of the link written. --type --title --parent
66
+ issue link <owner/name#45> <id> join an existing pair — or finish a join that
67
+ half-happened. Only the missing half is written
68
+ issue say <owner/name#45> ONE message, TWO audiences: the whole of it to
69
+ the reporter on GitHub, a summary and that
70
+ comment's URL to the board. --summary
71
+ issue trail <id|owner/name#45> the whole trail from EITHER end, with the
72
+ evidence for each half. Read-only
73
+ issue drift where the two platforms DISAGREE — the report no
74
+ other tool can produce. --repo --limit --wiql
75
+ issue backfill <owner/name> bulk adopt every issue the board does not link
76
+ yet. Needs --yes. --limit --state --label
77
+ issue close <id|owner/name#45> close BOTH ends, then RE-READ both to say what
78
+ actually moved. --to --reason --all
64
79
  hooks list | check | create | repoint | delete service hooks for realtime.
65
80
  `check` needs --hub <url>; `list` shows the URL
66
81
  already in use
67
82
  plan <file> an implementation plan → a work item tree
68
83
  setup org, project, token → keychain + shell profile
69
84
  --install ask|all|a,b offer the optional
70
- capabilities (LibreOffice, poppler, converter)
85
+ capabilities (LibreOffice, poppler, converter,
86
+ the GitHub CLI)
87
+ --repo owner/name --gh-account <login> bind a
88
+ repository to the GitHub account that reaches
89
+ it. BOTH or NEITHER — `issue` needs this first
71
90
  doctor what is installed on THIS machine, what is
72
91
  missing, and what each missing thing costs
73
92
  ```
74
93
 
75
94
  Text input: `--text` / `--file` / `--stdin`. Global: `--pretty` for a human,
76
- `--yes` for destructive verbs, `--dry-run` to preview.
95
+ `--yes` for destructive verbs, `--dry-run` to preview, `--max-writes <n>` to
96
+ change the per-invocation write cap (default 20; `0` is read-only).
77
97
 
78
98
  **`ADO_ORG`, `ADO_PROJECT` and `ADO_PAT` are already in the environment** after
79
99
  setup. Do not check them before working; a missing one announces itself as
@@ -83,8 +103,10 @@ setup. Do not check them before working; a missing one announces itself as
83
103
  commands further down name them with confidence they have not earned on yours.
84
104
  `pharos doctor` answers that in one call: what is present, its version and where
85
105
  it resolved, what is missing, and what each missing thing actually costs. It is
86
- read-only, offline, and works before anything is configured — so it is also the
87
- right first move when something behaves oddly.
106
+ read-only and works before anything is configured — so it is also the right
107
+ first move when something behaves oddly. Offline too, with one exception it
108
+ names: `gh auth status` validates every token against github.com and there is
109
+ no flag that stops it, so that probe runs only when `gh` is installed.
88
110
 
89
111
  `pharos setup --install ask` then offers to install them, driving Homebrew, apt
90
112
  or winget rather than vendoring anything. **Nothing installs without that flag**
@@ -596,6 +618,217 @@ leading slash**. Two things follow:
596
618
  website, that is why. `wiki links` and `unlink` both ask about **both** shapes,
597
619
  so old links still resolve and can still be removed.
598
620
 
621
+ ## GitHub issues: the join, and only the join
622
+
623
+ **These verbs are newer than the CLI on most machines. Check before you promise
624
+ one.** `pharos issue` shipped *after* **0.23.0**, so if `pharos --version` prints
625
+ 0.23.0 or lower it is not there and every verb below is an unknown command —
626
+ `npm i -g @floh-solutions/pharos-cli@latest` is the fix. This skill and the CLI
627
+ update by different routes and drift apart in both directions; `pharos doctor`
628
+ prints both versions.
629
+
630
+ **This is not a GitHub CLI and must not become one.** Listing, viewing and
631
+ plainly commenting on an issue is `gh`'s job and `gh` is better at it; closing
632
+ one *end* is `gh issue close`. Pharos owns the one thing `gh` cannot see: which
633
+ work item tracks this issue, written so it survives in both databases.
634
+
635
+ Azure DevOps has its own GitHub integration and **the link it makes carries
636
+ nothing** — title, body, comments, labels and state stay on their own island,
637
+ and the transition only ever fires from a commit or a PR merge, never from
638
+ closing an issue. That gap is the whole reason these verbs exist.
639
+
640
+ ```bash
641
+ pharos issue adopt contoso/widgets#45 --parent 39 # an issue → a work item, both ends
642
+ pharos issue link contoso/widgets#45 4821 # join a pair that already exists
643
+ pharos issue trail 4821 # …or trail contoso/widgets#45
644
+ pharos issue say contoso/widgets#45 --file reply.md
645
+ pharos issue drift # where the two disagree
646
+ pharos issue backfill contoso/widgets --limit 25 --yes
647
+ pharos issue close 4821 --text "Shipped in 1.4.0."
648
+ ```
649
+
650
+ ### Bind the repository first, or none of this runs
651
+
652
+ ```bash
653
+ pharos setup --repo contoso/widgets --gh-account alisina-tibata
654
+ ```
655
+
656
+ **Both flags or neither**, and there is deliberately **no fallback to whichever
657
+ account `gh` has active**: two accounts can each see a repository of the same
658
+ name, so the wrong one answers **200** for a different repository — a wrong
659
+ answer rather than an error.
660
+
661
+ An unbound repository is `"kind": "config"` on exit 2, naming
662
+ `~/.config/pharos/repos.json` — never a guess — **and it lists the repositories
663
+ that ARE bound**, so a typo is visible without reading the file. That check runs
664
+ before everything else here, so it is the first thing to fix. `gh` itself is
665
+ optional: without it every `issue` verb is refused by name with the reason and
666
+ the Azure DevOps verbs are untouched. `pharos doctor` reports both.
667
+
668
+ A failure that came from GitHub says **`"platform": "github"`**, because these
669
+ verbs touch two platforms in one call and which one refused is the first thing
670
+ to know. Its absence means Azure DevOps or this tool, as everywhere else.
671
+
672
+ ### `adopt` is safe to re-run. `say` is NOT. Do not generalise from one.
673
+
674
+ **Adopting the same issue twice never creates a second work item.** What it
675
+ does instead depends on the state the pair is in, and the exit code tells you
676
+ which:
677
+
678
+ | exit **0**, `created: false` | the issue carries a marker. You get the work item it names and nothing is written — the state you asked for already holds |
679
+ | exit **3**, `halfLinked: true` | a work item already links this issue and the issue says nothing about it. Refused, carrying `recover` — the link wants **finishing**, not repeating |
680
+ | exit **3**, `workItemIds` with two entries | two work items claim one issue. Refused, both named, no `recover`: picking one would be inventing an answer |
681
+
682
+ Azure DevOps is written **first**, because the work item id does not exist until
683
+ the create lands, so the one failure `adopt` can leave is always the same shape:
684
+ the board end written, the GitHub end not.
685
+
686
+ ```jsonc
687
+ { "error": { "createdWorkItem": 4821, "halfLinked": true, "wrote": ["hyperlink"],
688
+ "recover": "pharos issue link contoso/widgets#45 4821" } }
689
+ ```
690
+
691
+ **Read more than `kind` on that error**: a work item now exists, and reading
692
+ only the kind loses its id. `recover` is the literal command that finishes the
693
+ job, and nothing needs undoing first.
694
+
695
+ **Re-running `adopt` there is safe but it is not the repair** — it refuses,
696
+ because the row above is exactly the state it detects, and the refusal hands
697
+ back the same `recover` string. Take it either from the failure or from the
698
+ refusal; they are the same command. `link` writes only the half that is missing,
699
+ which makes it both the fix here and the ordinary way to join a pair that
700
+ already exists. `pharos issue drift` finds this state later if nobody acted on
701
+ it at the time — it reports it as `one-sided`, carrying the same repair.
702
+
703
+ Every `adopt` reports `boardCheck`. `{"ran": true}` means the board was searched
704
+ for a work item that already tracks this issue; `{"ran": false}` carries the
705
+ reason it could not be, and then a half-link made on **another machine** would
706
+ not have been seen — `pharos issue drift --repo <owner/name>` asks the same
707
+ question deliberately.
708
+
709
+ `say` is the opposite, and that is the trap. **GitHub is written first there**,
710
+ because the board's comment carries the GitHub comment's URL and that does not
711
+ exist until the POST returns. So a failure on the board half leaves a **public
712
+ comment already posted**, and re-running posts a second one:
713
+
714
+ ```jsonc
715
+ { "error": { "halfSaid": true, "wrote": ["github-comment"],
716
+ "github": { "commentUrl": "https://github.com/…#issuecomment-950" },
717
+ "recover": "pharos comment add 4821 --stdin", "adoComment": "…" } }
718
+ ```
719
+
720
+ Feed `adoComment` into `recover`. Do not re-run `say`.
721
+
722
+ ### `say` — one message, two audiences
723
+
724
+ | | GitHub | Azure DevOps |
725
+ |---|---|---|
726
+ | who reads it | the reporter, who has **no** Azure DevOps account and never will | the team |
727
+ | what they get | the full explanation | a summary, and the URL of the comment carrying the rest |
728
+
729
+ **The asymmetry is the verb.** The same words in both places is `gh issue
730
+ comment` followed by `pharos comment add`, and neither of those knows the other
731
+ happened. Without `--summary` the board gets the first paragraph cut to 200
732
+ characters — a guess, and allowed to be one *only* because the comment's URL
733
+ travels with it. GitHub is the record of what was said; the board never is.
734
+
735
+ **Never pass a message that already carries a `<!-- pharos:v1 … -->` trailer.**
736
+ It is refused (exit 3) before any call, on `say` and on `close`'s closing note
737
+ alike. That trailer means the text is Pharos's own output coming back round — a
738
+ comment you read and re-posted — and it is how a fan-out starts summarising its
739
+ own summaries. Every comment these verbs post carries one on the way out; you
740
+ never write one in.
741
+
742
+ `say` starts from the **issue** end only. A work item may hyperlink several
743
+ issues, so a bare id names no single reporter — `pharos issue trail <id>` is how
744
+ you find out which one you meant.
745
+
746
+ ### `trail`, and the evidence worth reading
747
+
748
+ Read-only, and it answers from **either** end with the same output shape. The
749
+ field to read is `evidence`: `["hyperlink","marker"]` is a complete link, and
750
+ either one **alone** is a link that only half exists — a finding, not a detail.
751
+ The halves live in different places on purpose: a `Hyperlink` relation on the
752
+ work item, and one comment on the issue carrying `AB#4821` plus the
753
+ machine-readable trailer.
754
+
755
+ **The marker goes in a comment, never the issue body.** Editing a reporter's
756
+ body collides with them and needs write access nobody has on a community issue.
757
+
758
+ ### `drift` — the report no other tool can produce
759
+
760
+ `gh` lists issues and Azure DevOps lists work items; **neither holds both
761
+ sides**, so neither can say *these two disagree*. Four kinds, because they want
762
+ four different actions:
763
+
764
+ | kind | what it means |
765
+ |---|---|
766
+ | `state` | both ends exist and disagree about whether the work is finished |
767
+ | `missing-issue` | the work item names an issue GitHub does not have |
768
+ | `one-sided` | one platform carries the link and the other does not — what a failed `adopt` leaves. Carries the `pharos issue link …` that repairs it |
769
+ | `unreadable` | the GitHub end could not be read, so nothing about this pair is known |
770
+
771
+ **It scans links, not issues.** An unadopted issue is not drift — that is
772
+ `backfill`'s question. On a repo with four hundred of them, counting "not
773
+ linked" as a problem buries three real findings under three hundred and
774
+ eighty-eight rows of noise.
775
+
776
+ **"Finished" is read from the project, never guessed**: each work item is judged
777
+ against its own type's categories and the report says which source it used —
778
+ `Resolved` is terminal on a Bug and open on a User Story. When the catalogue
779
+ cannot be read the stock names are used *and said out loud*, because a row is
780
+ uninterpretable without knowing what was counted as finished. An unreadable
781
+ repository is information rather than a fault and never fails the command.
782
+
783
+ ### `backfill` — the adoption path for the repo that already has 400 issues
784
+
785
+ ```bash
786
+ pharos issue backfill contoso/widgets # previews, then REFUSES (exit 3)
787
+ pharos issue backfill contoso/widgets --dry-run # the same preview, exit 0
788
+ pharos issue backfill contoso/widgets --label bug --parent 39 --limit 25 --max-writes 50 --yes
789
+ ```
790
+
791
+ **Read the preview before adding `--yes`.** It names the repository, the `gh`
792
+ account it would be reached as, **and the Azure DevOps organisation and project
793
+ the work items would land on** — nothing in the design pairs a repo with a
794
+ board, and a mis-aimed `adopt` is one work item where a mis-aimed `backfill
795
+ --yes` is four hundred, each with a public comment naming a board its reporter
796
+ has nothing to do with.
797
+
798
+ **Each adoption is two writes**, so four hundred issues is eight hundred against
799
+ a default cap of 20. That cost is worked out *before* anything is written and a
800
+ run that cannot finish is refused, naming both ways forward — rather than
801
+ stopping at write nineteen, which is the outcome you can reason about least.
802
+
803
+ **It is resumable and needs no state file.** The link *is* the progress record:
804
+ a second run finds the ones the first adopted already linked and carries on, in
805
+ issue-number order. `--state` defaults to `open`, because adopting a closed
806
+ issue creates a work item that is finished before anybody looks at it.
807
+
808
+ ### `close` — the ending of the trail
809
+
810
+ Closes **both** ends, comments on each pointing at the other, then **re-reads
811
+ both** and reports what actually moved. Two 200s are not proof: a workflow rule
812
+ can refuse a transition on a field the API happily accepted, so **`verified` is
813
+ what was there afterwards** rather than what was sent. Read it.
814
+
815
+ **There is no hardcodable `Closed`.** The state comes from the project's own
816
+ categories — exactly one terminal state for that type is used, and *several* is
817
+ a refusal naming them and `--to`, because `Done` and `Removed` are both finished
818
+ and mean opposite things. If the catalogue cannot be read at all it refuses
819
+ rather than guessing.
820
+
821
+ `--to`, **not** `--state`: `--state` already means `open|closed|all` on
822
+ `backfill`. Closing an already-closed pair writes nothing and exits 0. A work
823
+ item linking several issues is refused unless `--all` says so — "close 4821"
824
+ should not read as "close four strangers' issues".
825
+
826
+ **Pull requests are out of scope and are refused by name.** On GitHub's API
827
+ every PR is also an issue, so `adopt` on a PR number would otherwise mirror
828
+ something none of this models. `close` does not compose `Closes #45` /
829
+ `Fixes AB#123` into a PR body either: those fire on a PR *merge*, so there would
830
+ be nothing to compose into and nothing to verify.
831
+
599
832
  ## What `pharos` does NOT do — read this before you go looking
600
833
 
601
834
  - **Free-text and code search.** Nothing here covers it. For work items,
@@ -606,6 +839,12 @@ leading slash**. Two things follow:
606
839
  - **Wiki content search.** `wiki tree` then `wiki read` is the only way through;
607
840
  there is no grep across pages.
608
841
  - Pull requests, builds, pipelines.
842
+ - **Anything on GitHub that is not the join.** No issue list, no issue view, no
843
+ plain comment, no labels or milestones, no pull requests — `gh` does all of it
844
+ better and a second GitHub CLI would only drift from it. The seven `issue`
845
+ verbs are the edge and deliberately nothing else.
846
+ - **Jira, Linear, and the Argus board.** Not covered, not planned. "Task",
847
+ "todo" and "board" mean something else there.
609
848
 
610
849
  **There is no Azure DevOps MCP server here any more, and that is deliberate.**
611
850
  It authenticated through the Azure CLI, so it opened a browser mid-task — which
@@ -617,9 +856,9 @@ routing around it: several things here exist in no other Azure DevOps tool at
617
856
  all — wiki page comments and reactions, attachment upload AND removal, inline
618
857
  images for a work item field or a wiki page, mentioning somebody in a form that
619
858
  actually notifies, importing a Word document or a PDF as a page, editing or
620
- deleting a work item comment, service hooks, and applying a change under a
621
- `test` op on `/rev` so a teammate who wrote first cannot be silently
622
- overwritten. Those are what the
859
+ deleting a work item comment, service hooks, the GitHub-issue join and the
860
+ drift report over it, and applying a change under a `test` op on `/rev` so a
861
+ teammate who wrote first cannot be silently overwritten. Those are what the
623
862
  guards are, and they are the reason to come back here rather than hand-roll.
624
863
 
625
864
  ## Start every task with one command
@@ -739,7 +978,12 @@ true` means stop re-checking the token.
739
978
  | `0` | it worked | carry on |
740
979
  | `1` | the call failed | check `kind`; retry only if it is `rateLimit` (wait `retryAfterMs`) or transient |
741
980
  | `2` | called wrong, or not configured | **never retry unchanged.** Fix the call, or the setup |
742
- | `3` | a guard here refused | re-run with `--yes` — after deciding it is right |
981
+ | `3` | a guard here refused | re-run with `--yes`, or raise `--max-writes` — after deciding it is right |
982
+
983
+ **A failure carrying `"platform": "github"` came from GitHub**, not from Azure
984
+ DevOps and not from here — `pharos issue` touches two platforms in one call, and
985
+ which one refused decides whether the fix is a token, a repo binding or a field.
986
+ Its absence means the other side, as it always did.
743
987
 
744
988
  `"kind": "conflict"` means somebody wrote first. Your work is still valid:
745
989
  re-read, re-apply. It carries both revisions — and note that **posting a comment
@@ -801,6 +1045,12 @@ it is about to break. Sub-pages move with their parent.
801
1045
  not only in a chat log nobody else can read.
802
1046
  5. Move the state when the work moves, not at the end.
803
1047
 
1048
+ **When the work started as a GitHub issue the loop gains two ends and changes
1049
+ one step**: `pharos issue adopt` before step 1, `pharos issue close` after step
1050
+ 5, and at step 4 the reply to the *reporter* is `pharos issue say` rather than a
1051
+ comment on the work item — a comment on the item is invisible to somebody with
1052
+ no Azure DevOps account, which is every reporter.
1053
+
804
1054
  **Every person uses their own token.** Board attribution is per-person, so
805
1055
  anything you do is recorded against whoever owns `ADO_PAT`. Never suggest
806
1056
  sharing one.
@@ -813,3 +1063,12 @@ profile that **scripted** shells read, and verifies both permission scopes —
813
1063
  Work Items and Wiki are separate in Azure DevOps, and a token missing the second
814
1064
  works fine until the first wiki write days later. A **new** shell is needed
815
1065
  afterwards.
1066
+
1067
+ **The GitHub half is configured separately and follows a different rule.** The
1068
+ environment carries no fourth secret: the credential comes from `gh`, and which
1069
+ account reaches which repository is a binding on disk, so a `config` error from
1070
+ an `issue` verb names `repos.json` rather than an environment variable.
1071
+
1072
+ ```bash
1073
+ pharos setup --repo contoso/widgets --gh-account alisina-tibata
1074
+ ```