個人的なタスク管理ツールとしてGitHub Issueを使うようにしてその仕組みを色々と作っているので、そのアーキテクチャについてのメモ書きです。 後述しますが、GitHubをベースとすることでプログラムでの拡張性が高いというのが特徴です。 セットアップが色々と必要になるためぱっと再現しやすい感じではなかったり一部未公開になってます。 需要があったらオープンソースとして公開できるように整えます。

GitHub Issuesとタスク管理ツールでの課題

自分の中で、タスク管理ツールとGitHub Issuesを両方使う場合に次の課題がありました。

  • 自分のタスクの半分以上はGitHubに何かしら紐づく情報(オープンソース、ブログ、仕事)であったため、GitHub Issueとの二重管理感がある
  • GitHub上で複数のリポジトリのタスクを管理するのが難しい

1つ目は、タスク管理ツールを使っても結局GitHub Issuesは使うので、二重管理っぽい感覚がでてしまう問題です。 タスク管理ツールは今までProducteevTickTickOmniFocusなどを使ってきました。 GTDなどのメソッドだと必ず通るInboxにあらゆるタスクはとりあえず置いて、そこから整理するのだと思いますが、 自分にとってこういったタスク管理ツールがInboxにならなかったという感じです。

2つ目の"GitHub上で複数のリポジトリのタスクを管理するのが難しい"に対応するものとしてUser-owned project boardsがあります。 しかし、このUser-owned project boardsは25リポジトリの制限ある点やリポジトリを明示的に紐付ける必要があるため、人軸のIssue管理には扱いにくそうでした。 (自分はリポジトリが1000コぐらいあったり、自分のリポジトリじゃないところにIssueがあるという理由もあります。)

複数のリポジトリのIssueを管理する問題に対応するために、My-Board-For-Github(MYFG)を作ったりもしました。 My-Board-For-Github(MYFG)はざっくり言えば、Issueのデータを特定のリポジトリにためて、それをカンバン表示することで、複数のリポジトリのIssueを管理するツールです。

My-Board-For-Github(MYFG)

あくまで、GitHubのIssueやPRのリファレンスだけを管理してステータスを表示するツールだったので、このツール自体にはタスクが追加できるわけではありません。 Issueをどこかに作って、My-Board-For-Github(MYFG)にIssueを追加するような仕組みです。 また、他人のリポジトリのIssueも追加できるので、チェックしたいIssueやレスポンス待ちのPull Requestを置いておいたりする用途で使っていました。

My-Board-For-Github(MYFG)は悪くはなかったのですが、ものすごい複雑なGraphQLクエリを書いているので、最初の表示が遅いという問題がありました。 また、Board自体にIssueを作れないので、気軽にIssueを作りにくくてあくまで管理ツールって感じでした。(あとカンバンは数が多くなると管理できない感じがした)

カンバンでステータスを見れるという点は結構良い体験でした。

そのため、個人的なタスクやGitHub Issueどちらも、自分用のGitHubプライベートリポジトリで管理すればよいのではと思って、missueという仕組みを作りました。

missue

missueは、GitHubリポジトリでIssue管理する仕組みをまとめたリポジトリで、なにかのツールというわけではないです。 GitHub Issuesでタスク管理するのを手助けするGitHub ActionsやシンプルなGitHub Issueクライアントアプリをまとめて単なるテンプレートリポジトリです。

現時点のmissueは次のような要素で構成されています。

基本的にはただのリポジトリでGitHub Issueを使ってタスク管理をします。 課題だった別リポジトリのIssueを管理するためにmissueでは"CR" Issueという概念を実装しています。

CR IssueはCross Reference Issueの略で、 CR ラベルがついていて、Issueのボディに別Issue/PRのURLが書かれているものを示しています。

例えば、次のIssueは別リポジトリの別Issueを参照するためのCR Issueです。

missueでは、CR Issueはリンク先のIssueのステータス(Open/Closed)と同期する仕組みが実装されています。 同期する仕組みはGitHub Actions(サーバサイド)とGitHub Issue Client(クライアントサイド)どちらも実装しているので、リンク先のIssueが閉じたらCR Issueも自動でCloseされるといった感じになります。 (クライアントが持っている機能はほぼこれだけです)

また、リポジトリに含んでいるUserScriptは、このCR Issueをワンクリックで作るためのGreasemonkeyスクリプトが入っています。 UserScriptを入れた状態(repoやtokenを設定する必要がある)でIssueやPRを開くと、自分のmissueリポジトリにワンクリックでCR Issueを作成できます。

userscript

missueベースのTODOリポジトリは次のような構造になっています。

overview

missueベースのTODOリポジトリの作り方

missueはただのテンプレートリポジトリなので、次のように自分用のリポジトリを作るだけです。

  1. Visit https://github.com/azu/missue/generate
  2. Input Repository name. Example) my-todo
  3. Select "Public" or "Private"
  4. Click "Create repository from template"

Template

Setup

CR Issue用のラベルなどをリポジトリに設定するGitHub Actionsを用意してあります。

ラベルを設定する

  1. Visit https://github.com/<your-name>/<your-repo>/actions?query=workflow%3A%22Sync+Cross+Reference+Issues%22
    • e.g. https://github.com/you/my-todo/actions?query=workflow%3A%22Sync+Cross+Reference+Issues%22
  2. Click "Run workflow"
  3. Setup labels for your repository

このGitHub Actionを実行するとhttps://github.com/azu/missue/labelsのように色々ラベルが設定されます。 デフォルトではOmniFocusのタグをイメージしたラベルが色々入っています。

labels

.github/labels.yml にラベルの定義があるので、自分で変更してもう一度Actionを実行すれば再設定できます。 "CR" ラベル以外は特別な意味を持っていないので自由に設定できます。

既存のIssueから"CR" Issueを作成する

CR Issueをワンクリックで作るUserScriptを使うのが楽です。

  1. Install Greasemonkey-like extension
  2. Install add-item-to-missue.user.js
  3. Add setting to the installed user script

Client

missueにはElectronベースのシンプルなクライアントアプリが入っています。 機能的にはCRラベルのステータス同期機能と初期表示のURLが指定できるだけのシンプルなクライアントです。

次のようにして、GitHub Token(https://github.com/settings/tokens/newからrepo権限で作成)とINPUT_URLに初期表示のURLを指定してビルドできます。

git clone https://github.com/azu/missue
cd missue/
cd client/
yarn intall
GITHUB_TOKEN=yourtoken INPUT_URL="https://github.com/your/private-todo/issues" npm run build

GitHubをタスク管理ツールとして使う利点

OmniFocusなどのタスク管理ツールとGitHub Issue両方使う場合には、次の2つの課題がありました。

  • GitHub Issueとの二重管理感がある
  • GitHub上で複数のリポジトリのタスクを管理するのが難しい

タスク管理ツール または GitHub どちらかに統一すれば解決しそうだったので、GitHubに統一したというのがmissueを作ったときの考えです。

missueの作る前に実現できるかや解決したい課題などについては次のProposalを書いています。

Proposalの背景でも書かれていますが、タスク管理ツール側に統一しなかったのは、GitHubの方が拡張性が高いと思ったためです。

背景

  • TODO管理はTODO管理アプリ内に閉じていることが多い
    • 外部連携などが難しい(メールからTODOを追加などが面倒)
    • GitHubはIFTTTやzapierなどすでにたくさんの連携方法がある
    • TODOアプリはすべてのことを流せる場所になってないと見なくなる
      • GitHubは十分それができる
      • メール to GitHub
      • Slack to GitHub
      • サイト to GitHub
      • GitHub to RSSなども別の箇所に流しやすい

IFTTT、zapier、APIを使ってGitHubにデータを流すことが簡単にできます。 また、GitHub Actionsがあるため、GitHubのイベントを元にした処理も簡単にできます。

GitHub Actionsを使えば、TODOアプリのサーバ側の拡張のように動作を増やせます。

missueには入っていないですが、自分のTODOリポジトリは次のような構造になっています。

My Issue

missueに入ってなかったものとしては、次の機能が拡張されています。

  • GmailのメールからGitHubへIssueの作成(未公開)
    • 整理できてないので未公開
  • Google App Script(GAS)でのリマインド機能(未公開)
  • InkdropのノートとGitHub Projectの同期

📝Memo: Google App Script(GAS)周りがちゃんと整理できてないので、一部未公開にしています。 需要があったら整理して公開するかもしれないので、言ってください。

またはSponsorware的にsponsorship thresholdを設定(70人にいったら公開とか)してみるのも面白いのかも。

Sponsor

GmailのメールからGitHubへIssueの作成(未公開)

GmailではGoogle App Script(GAS)を使ってサイドにパネルで表示する拡張を書けます。

これを使って、Gmailのメールの内容をそのままGitHub Issueとして作るGmail Addonを作って使っています。

Gmail → GitHub Issue

Google App Script(GAS)でのリマインド機能(未公開)

GitHub Actionsでは、GitHub Issueのコメントに対するActionを書けます。 例えば、次のようなActionをおけば、/remind 明日通知してといったコメントを書くと、Issueに対して翌日にリマインドしてくれる仕組みを作って使っています。 (/remindのパース部分はmissue/actions/remind/に公開しています)

remind

リマインド側はGoogle App Script(GAS)の指定時間に発火できるTriggerを使っています。 また、GASはAPIサーバとしても公開できるので便利です。

  • GitHubコメント → GASサーバにリクエスト → GASで ScriptApp.newTrigger("commentToGitHub").timeBased().at(triggerDate).create() を作成 → 指定時間になったらcommentToGitHubでコメントする
name: Remind
on:
  issue_comment:
    types: [ created ]
jobs:
  remind:
    if: |
      github.event_name == 'issue_comment' &&
      (github.event.comment.author_association == 'member' || github.event.comment.author_association == 'owner') &&
      startsWith(github.event.comment.body, '/remind')
    runs-on: ubuntu-latest
    steps:
      - name: checkout
        uses: actions/checkout@v2
      - name: setup Node
        uses: actions/setup-node@v1
        with:
          node-version: 14
      - name: setup
        run: yarn install
        working-directory: actions/remind
      - name: remind-at
        id: remind-at
        run: |
          set -ex
          b=$(echo -n "$")
          result=$(node_modules/.bin/ts-node-transpile-only index.ts "${b}")
          echo "::set-output name=result::${result}"
        working-directory: actions/remind
      - name: remind
        run: |
          # GASのスクリプトを叩く[未公開部分]
        working-directory: actions/remind
      - uses: actions/github-script@v3
        env:
          GITHUB_TOKEN: $
        with:
          script: |
            github.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              // JST
              body: '@$ `/remind` at ' + new Date($ + 9 * 3600000).toISOString().replace(/\..*/, '+09:00')
            })

  create_fail_comment:
    if: cancelled() || failure()
    needs: remind
    runs-on: ubuntu-latest
    steps:
      - uses: actions/github-script@v3
        env:
          GITHUB_TOKEN: $
        with:
          script: |
            github.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: "@$ `/remind` fail<br />Action https://github.com/" + process.env.GITHUB_REPOSITORY + "/runs/" + process.env.GITHUB_RUN_ID
            })

InkdropのノートとGitHub Projectの同期

これ自体はTODO管理とはあまり関係ないですが、sync-github-project-todo-mdのInkdropプラグインを書いて、 GitHub ProjectをInkdropのノートと同期できるようにしています。

特定のGitHub Projectの内容をMarkdownとしてPullしてInkdropのノートに追加できます。 InkdropではGFMのチェックボックスに対応してるので、チェックをいれてからPushするとGitHub ProjectのIssueを閉じたりできます。

これは、今日やるタスクみたいのをGitHub Projectで管理しつつ、その日にどこまでやったのかは手元のノートアプリで見たくなったので書きました。 (日報的なイメージ)

自分用のTODOリポジトリがあれば、自分用のGitHub Project BoardがTODOリポジトリ内に作れます。(他の人のタスクと混ざらない) また、GitHub Actionsを使えば、特定の条件でIssueをBoardに追加できたりするので柔軟性がありそうです。

その他のユースケース

  • 買い物リストなど一時的なメモ/タスクとの連携
  • 複数のトピックを同時に扱うissue
    • IssueをGitHub Discussionsに変換しに変換しDiscussionで扱う
    • Discussionではコメントごとにスレッドでリプライを書けるので、複数のトピックをまとめて扱いやすい
    • 例) 旅行先を決める - 複数の候補をトピックにして中身をコメントのリプライで書いていき、最終的な候補を決める

おわりに

まだGitHub Issueベースのタスク管理に移行してから10日ぐらいしか経ってないですが、感覚的には悪くはありません。

既存のタスク管理ツールを使ってるときは、そのタスク管理ツールに合わせないとちゃんと使えないことが多かったです。

GitHub IssueベースならAPIやActionsで拡張できるので、その辺は柔軟性があって便利です。 一方で、GitHub Issueは通知系や日付管理系(マイルストーンぐらいしか日付データがない)は弱めなので、その辺はGoogle App Script(GAS)やカレンダーアプリなどと連携して補う必要があります。 GitHub ActionsはPrivateリポジトリでも月2000+分は無料で使えるので、イベントドリブンで処理すれば十分です。

ActionsflowみたいなCronでひたすら回るやり方は厳しいと思います。(GitHub Actionsの用途外っぽい)

TODOアプリはJavaScriptの練習課題としても良く扱われる題材で、GitHub Issueより良くできたクライアントはたくさんあります。 ただ、TODOはいろいろなリソースを集約した結果なので、必然的にいろいろな情報を扱う必要があって、良いクライアントアプリだけでは限界があるのかなと思いました。

いろいろな情報を集約するには、クライアントアプリを使っていないときの処理であるサーバ側での処理の拡張性が大切なのかなと思ってきました。 GitHubはAPIやGitHub Actionsがあるので、この辺が簡単に拡張できるというのが良かった点です。 (GitHub GraphQL APIがとても便利なので、UI上できることはほぼAPIでできます)

いろいろなタスクを集約するアプローチとしてはTacoなどもあります(My-Board-For-Github(MYFG)がこのアプローチとも言える)が、Taco自体はタスクを作るわけではありません。 GitHub Issueは最低限Todoに必要な機能(タイトル、本文、画像、ラベル、ステータス)がありつつ、CR Issueのようにリファレンス用のIssue管理もできるのでなんとかなる感じです。

オフラインモードがないとか、Issueの追加のステップがまだ多いとかの問題はありますが、その辺が欲しくなったらGitHub APIを使ったクライアントを書けば良いのかなと思います。 モバイルはGitHub for mobileで見れるので必要十分かなと思いました。

元々GitHubを使ってる時間が長かったので、集約場所をGitHubに寄せるのが適切だったという話なのかもしれません。