11. モジュール化と拡張

規模が大きくなったときのファイル構成。計画と実績を別ファイルに分け、
定型部分をマクロにまとめ、独自の属性を足す。

生成されたレポート

ファイル 内容
01-plan.html 計画とチケット番号 (ユーザー定義属性を列にしたもの)
02-progress.html 実績を反映した状態 (now = 2026-08-17)
03-people.html リソース一覧 (Slack ID つき)

実行結果

タスク チケット SP 開始 終了
設計 PROJ-101 8 08-03 08-07
実装 PROJ-102 21 08-10 08-25
コードレビュー REVIEW 3 08-25 08-28
フェーズ2     08-31 09-06
├ デプロイ PROJ-201 2 08-31 09-01
└ 経過観察 REVIEW 1 09-01 09-06

チケット番号と見積 SP の 2 列は extend で足した独自の属性。
デプロイと経過観察は subtasks.tji から taskprefix phase2 で流し込まれている。

ソース

main.tjp
/*
 * 11: ファイル分割・マクロ・ユーザー定義属性
 *
 * 実行: bundle exec tj3 -o 11-modular/out 11-modular/main.tjp
 *
 * 構成:
 *   main.tjp      プロジェクトヘッダとレポート (このファイル)
 *   macros.tji    マクロ定義
 *   resources.tji リソース定義
 *   tasks.tji     タスク定義 (計画)
 *   subtasks.tji  タスクの入れ子に取り込まれる断片
 *   actuals.tji   実績データ (supplement)
 */

project mod "モジュール構成" 2026-08-01 +3m {
  timezone "Asia/Tokyo"
  timeformat "%Y-%m-%d"
  now 2026-08-17
  trackingscenario plan

  /*
   * extend でタスクやリソースに独自の属性を追加できる。
   * 追加した属性はレポートの列としてそのまま使える。
   *
   * 型は text / number / date / reference / richtext。
   * extend は project ヘッダの中に書く。
   */
  extend task {
    text   Ticket "チケット"
    number Points "見積SP"
  }

  extend resource {
    text Slack "Slack ID"
  }
}

/*
 * include はそのファイルの中身をその場に展開する。
 * 取り込むファイルは .tji 拡張子でなければならない
 * (.tjp はプロジェクトヘッダを持つ独立ファイル用)。
 *
 * パスは「include を書いたファイルからの相対」で解決される。
 * 実行時のカレントディレクトリではない点に注意。
 *
 * 読み込み順は重要。マクロは使う前に定義されている必要があり、
 * supplement は対象のタスク/リソースが定義済みでなければならない。
 */
include "macros.tji"
include "resources.tji"
include "tasks.tji"
include "actuals.tji"

// ---- レポート ----

/*
 * extend で足した Ticket / Points / Slack をそのまま列に使える。
 */
taskreport plan "01-plan" {
  formats html
  headline "計画とチケット番号"
  columns name, Ticket, Points, start, end, effort, resources
}

taskreport progress "02-progress" {
  formats html
  headline "実績を反映した状態 (now = 2026-08-17)"
  columns name, Ticket, start, end, effort, complete, chart
}

resourcereport people "03-people" {
  formats html
  headline "リソース一覧 (ユーザー定義属性つき)"
  columns name, Slack, effort
  hideresource ~isleaf()
}
actuals.tji
/*
 * 実績データ (11-modular/actuals.tji)
 *
 * supplement を使うと、既に定義済みのタスク/リソースに
 * 後から属性を追加できる。
 *
 * 計画 (tasks.tji) と実績 (このファイル) を分けておくと、
 * 実績だけを別ツールで生成して差し替えられる。
 * supplement に渡すのはルートからの絶対IDである点に注意。
 */

supplement task design {
  booking alice 2026-08-03 - 2026-08-08 { sloppy 2 }
}

supplement task impl {
  booking bob 2026-08-10 - 2026-08-15 { sloppy 2 }
}

// リソースにも後付けできる
supplement resource carol {
  leaves annual "夏季休暇" 2026-08-24 - 2026-08-26
}
macros.tji
/*
 * マクロ定義 (11-modular/macros.tji)
 *
 * macro <ID> [ 本文 ]
 *   - 本文は角括弧で囲む。閉じ括弧 ] は必ず行の最後の文字にする
 *     (後ろに空白やコメントがあると閉じ括弧とみなされない)。
 *   - 呼び出しは ${ID}。引数つきなら ${ID "引数1" "引数2"}
 *   - 引数は本文中で ${1} ${2} ... と番号で参照する
 *   - 引数は必ずダブルクォートで囲む
 *   - ユーザー定義のマクロIDは大文字で始める (小文字は組み込み用に予約)
 */

// 開発タスクの定型: 担当 / 工数 / チケット番号
macro DevTask [
  allocate ${1}
  effort ${2}
  Ticket "${3}"
]

// レビュータスクの定型: 担当 / 期間
macro ReviewTask [
  allocate ${1}
  duration ${2}
  Ticket "REVIEW"
]
resources.tji
/*
 * リソース定義 (11-modular/resources.tji)
 *
 * extend で追加したユーザー定義属性 (Slack) をここで設定している。
 */

resource alice "Alice" {
  Slack "@alice"
}

resource bob "Bob" {
  Slack "@bob"
}

resource carol "Carol" {
  Slack "@carol"
}
subtasks.tji
/*
 * サブタスク定義 (11-modular/subtasks.tji)
 *
 * 親タスクの中に include されるので、ここでは task だけを並べる。
 */

task deploy "デプロイ" {
  ${DevTask "bob" "2d" "PROJ-201"}
  Points 2
}

task monitor "経過観察" {
  depends !deploy
  ${ReviewTask "alice" "5d"}
  Points 1
}
tasks.tji
/*
 * タスク定義 (11-modular/tasks.tji)
 *
 * macros.tji で定義したマクロを展開して記述量を減らしている。
 */

task design "設計" {
  ${DevTask "alice" "5d" "PROJ-101"}
  Points 8
}

task impl "実装" {
  depends !design
  ${DevTask "bob" "12d" "PROJ-102"}
  Points 21
}

task review "コードレビュー" {
  depends !impl
  ${ReviewTask "carol" "3d"}
  Points 3
}

/*
 * 入れ子のタスクを別ファイルに切り出す場合、
 * include はタスク定義の *中* には書けない (Unexpected token 'include')。
 * 空のコンテナを先に定義し、taskprefix 付きで include して子を流し込む。
 *
 * taskprefix を指定すると、取り込んだタスクは
 * そのIDのタスクの配下として定義される。
 */
task phase2 "フェーズ2" {
  depends !review
}

include "subtasks.tji" {
  taskprefix phase2
}

学ぶ内容

ファイル構成

ファイル 役割
main.tjp プロジェクトヘッダとレポート定義
macros.tji マクロ定義
resources.tji リソース定義
tasks.tji タスク定義 (計画)
subtasks.tji taskprefix で親タスク配下に流し込む断片
actuals.tji 実績データ (supplement)

上のソース一覧はこの構成をそのまま並べたもの。分割の仕方自体がこの段階の主題になる。

マクロ

macro DevTask [
  allocate ${1}
  effort ${2}
  Ticket "${3}"
]

呼び出しは ${DevTask "alice" "5d" "PROJ-101"}。
引数は本文中で ${1} ${2} と番号で参照し、必ずダブルクォートで囲む。

ユーザー定義のマクロ ID は大文字で始める (小文字は組み込み用に予約されている)。

ユーザー定義属性

project {} の中で extend する。

extend task {
  text   Ticket "チケット"
  number Points "見積SP"
}

追加した属性はレポートの列としてそのまま使える (columns name, Ticket, Points)。
型は text / number / date / reference / richtext。

supplement — 計画と実績の分離

supplement task design {
  booking alice 2026-08-03 - 2026-08-08 { sloppy 2 }
}

計画 (tasks.tji) と実績 (actuals.tji) を分けておくと、
実績だけを別ツールで生成して差し替えられる。実務で最も効く使い方。

ハマりどころ

  1. include はタスク定義の中には書けない (Unexpected token 'include')。
    入れ子タスクを別ファイルにするなら、空のコンテナを先に定義し、
    taskprefix 付きでトップレベルから include する
  2. 読み込み順が重要。マクロは使う前に定義され、
    supplement は対象が定義済みである必要がある
  3. パスは「include を書いたファイルからの相対」で解決される。実行時の CWD ではない
  4. 取り込むファイルは .tji 拡張子でなければならない (.tjp は独立ファイル用)
  5. macro の閉じ括弧 ] は行の最後の文字にする。
    後ろに空白やコメントがあると閉じたとみなされない
  6. supplement に渡すのはルートからの絶対 ID
  7. extend は project {} の中に書く

得られるもの

数百タスク規模の計画を、破綻せずに保守できる構成が組めるようになる。
チケット番号や見積ポイントといった独自情報も計画データに統合でき、
実績の受け渡しを自動化する下地になる。